API Documentation

Websocket API - Nodejs Environment

The Websocket API can also be incorporated into the Node.js infrastructure, among other things.

  • Setup
  • Requests & Response
  • Info keys
  • Command keys
  • Example

Installation:

The basic requirement here is a functioning Node.js environment.

In Node.js environment you can find some implementations of websocket clients. In our example we use "websocket" because it follows the W3C standard and our code is compatible to the browser javascript.

Installation (in the console):

npm install websocket

Inclusion of the module in the file:

var WebSocket = require('websocket').w3cwebsocket;

Connection:

To establish a websocket connection, the connection data in the local network is required in addition to the activation of the module. By default, the API runs on port 7000, but this can be changed in the menu. In addition, the token defined in the Rotoclear menu is required for each interaction.

var 
    roto_clear_ip = "192.168.2.231",
    roto_clear_api_port = "7000",
    my_api_token = "0123456789";

Websocket connection:

To establish the connection we simply need the IP address and the port over which the API program can be reached. The API is not ssl encrypted, so we use 'ws' as prefix.

var connection = new WebSocket("ws://" + roto_clear_ip + ":" + roto_clear_api_port);

Once the connection is established, a message can be sent to the Rotoclear device using the following example function. Note that the messages are tokenized here so that the API will allow your requests.

function sendToAPI(message) {
  message["token"] = my_api_token;          
  connection.send(JSON.stringify(message));  
}

In Javascript there is limited control over the input-output interfaces (IO's), so we cannot use a serial or direct connection or communication protocol. Instead, we have the ability to write events to the websocket connection and respond to events.
(However, if a serial description of a procedure is necessary, it can be implemented in Javascript using asynchronous programming. That, however, is outside Scope's scope here).

In the following we describe these events in our program.

Onopen Event

If the 'onopen' event is triggered during the connection, it means that the connection has succeeded. In the function block, the communication to the Rotoclear API can start.

connection.onopen = function() {
  /* now you can send messages to api */

  /* Example */
  sendToAPI({ info: "cam1-exists" });
};

Onerror Event

Connection difficulties can be diagnosed through this event.

connection.onerror = function(error) {
  console.log('WebSocket Error ' + error); 
};

Onmessage Event

The API responds to each request with a message. There are roughly 2 types of messages. One is Json formatted text data. The other is Jpeg images in the form of raw byte buffers. These two possibilities are mapped in the following implementation of the onmessage function.

connection.onmessage = function(evt) { 
  if (typeof evt.data === "string") {  
    var api_message = JSON.parse(evt.data);
    console.debug(api_message);

    // Use data from api
    // ....

  } else if (typeof evt.data === "object") {
    // handle raw jpeg stream
    // in Browser you can use FileReader
    
    // in Nodejs enviroment you can save this image
    require('fs').writeFile(“nameOfNewFile.jpg”, Buffer.from(evt.data), function (err) {
      if (err) return console.log(err);
    });
  }
};

API - Requests and Response

As already indicated, the API responds to each request with a response. For example, if we send the following message in the 'onopen' event or anywhere else.

sendToAPI({
  info: "cam1-exists"
});

Then the system will return a json formatted text message. This is then parsed into a JSON in our 'onmessage' block and the result is then for example:

{
  "cam1-light": true,
}

A number of queries can also be sent at the same time. For example, 'info' can be both a string and an array of strings:

sendToAPI({
  info: ["cam1-light", "brightness", "cam2-exists", "main-camera"]
});

A possible response to this request would then be:

{
  "cam1-light": true,
  "brightness": 1,
  "cam2-exists": false,
  "main-camera": 2,
}

Besides information, commands can also be sent to the API. Compared to the previous examples, we use the keyword directly and add a parameter to it.

sendToAPI({ 
  "resolution": "HD",
  "jpeg-quality": 100,
  "cam1-light": true,
});

Here, too, the API responds accordingly. However, the commands are validated and the possible limits of the values are observed. The response to these commands could then look like this:

{
  "resolution": "HD"
  "jpeg-quality": 90,
  "cam1-light": true,
}

Info keys:

Overview of possible keys in information requests:

API Key Response (type) Description
cam1-exists (bool) camera 1 exists
cam2-exists (bool) camera 2 exists
cam1-light (bool) camera 1 light on?
cam2-light (bool) camera 2 light on?
cam1-sensor 1 or 2 (int) Some Camera heads has 2 Sensors. Witch sensor is selected?
cam2-sensor 1 or 2 (int) Some Camera heads has 2 Sensors. Witch sensor is selected?
main-camera 1 or 2 (int) Witch camera input is main?
resolution "4K", "FullHD", "HD" (string) What resolution is currently set? "4K": 3840x2160, "FullHD": 1920x1080, "HD": 1280x720
auto-white-balance (bool) Is automatic white balance switched on?
noise-supression (bool) Is noise supression switched on?
auto-exposure (bool) Should the exposure of the image be aligned automatically?
brightness -7 to 7(int) If 'auto-exposure' is on, then you can setup brightness
gain 0 to 42 (int) If 'auto-exposure' is off, then you can setup gain
jpeg-quality 1.0 to 100.0 (float) jpeg compression quality
contrast 1 to 2.5 (float)  
sharpness 1 to 15 (int)  
saturation -7 to 7 (int)  
red-gain -128 to 127 (int) If 'auto-white-balance' is off
blue-gain -128 to 127 (int) If 'auto-white-balance' is off
pip-mode 0 to 4 (int) Picture in picture modus:
0 ≙ only current camera, 1 ≙ pip is top left, 2 ≙ top right, 3 ≙ bottom left, 4 ≙ bottom right

Command keys:

Overview of the possible keys and parameters in command requests:

API Key Paramer (type) Response (type) Description
cam1-light (bool) (bool) camera 1 light on?
cam2-light (bool) (bool) camera 2 light on?
main-camera 1 or 2 (int) 1 or 2 (int) Witch camera input is main?
cam1-sensor 1 or 2 (int) 1 or 2 (int) Some camera heads have 2 sensors. With this you can choose between the sensors.
Be careful, if your head does not have 2 sensors, it will mirror your image!
cam2-sensor 1 or 2 (int) 1 or 2 (int) Some camera heads have 2 sensors. With this you can choose between the sensors.
Be careful, if your head does not have 2 sensors, it will mirror your image!
resolution "4K", "FullHD", "HD" (string) "4K", "FullHD", "HD" (string) What resolution is currently set? "4K": 3840x2160, "FullHD": 1920x1080, "HD": 1280x720
auto-white-balance (bool) (bool) Is automatic white balance switched on?
noise-supression (bool) (bool) Is noise supression switched on?
auto-exposure (bool) (bool) Should the exposure of the image be aligned automatically?
brightness -7 to 7(int) -7 to 7(int) If 'auto-exposure' is on, then you can setup brightness
gain 0 to 42 (int) 0 to 42 (int) If 'auto-exposure' is off, then you can setup gain
jpeg-quality 1.0 to 100.0 (float) 1.0 to 100.0 (float) jpeg compression quality
contrast 1 to 2.5 (float) 1 to 2.5 (float)  
sharpness 1 to 15 (int) 1 to 15 (int)  
saturation -7 to 7 (int) -7 to 7 (int)  
red-gain -128 to 127 (int) -128 to 127 (int) If 'auto-white-balance' is off
blue-gain -128 to 127 (int) -128 to 127 (int) If 'auto-white-balance' is off
pip-mode 0 to 4 (int) 0 to 4 (int) Picture in picture modus:
0 ≙ only current camera, 1 ≙ pip is top left, 2 ≙ top right, 3 ≙ bottom left, 4 ≙ bottom right
snap (any) (string) trigger snapshoot
record "start", "stop", "pause", "pause_release" (string) "start", "stop", "pause", "pause_release" (string) controll video recording
jpeg (any) (Blob) take and get image in jpeg format

Example

  • Node.js
  • C#
  • NIM
  • Python

Node.js

#!/usr/bin/env node

// Example for Nodejs - JS
//
// 1. Connect to websocket-server
// 2. Check camera 1 is connected and the light is on (on open)
// 3. If camera 1 is connected and the light is off then turn the light on
// 4. Take a jpg image and save it as file
// 5. Close connection
//
// Install dependencies:
//   npm install websocket
//


var W3CWebSocket = require('websocket').w3cwebsocket;
fs = require('fs');

const RotoClearIp = "192.168.0.102"
const RotoClearApiPort = "7000"
const myApiToken = "0123456789"
const imageName = 'myNewImage.jpg'

var connection = new W3CWebSocket("ws://" + RotoClearIp + ":" + RotoClearApiPort);

function customSendToAPI(message) {
  message["token"] = myApiToken;
  // Send message to api server. Only stringified json object is accepted
  connection.send(JSON.stringify(message));
}


connection.onerror = function () {
  console.log('Connection Error');
};

connection.onopen = function () {
  customSendToAPI({ info: ["cam1-light", "cam1-exists"] });
};

connection.onclose = function () {
  console.log('Client Closed');
};

connection.onmessage = function (e) {
  if (typeof e.data === "string") {
    // if data is of type string, then it is json
    var api_message = JSON.parse(e.data);

    if (api_message["cam1-exists"] && !api_message["cam1-light"]) {
      customSendToAPI({ "cam1-light": true });
    } else if (api_message["cam1-light"]) {
      customSendToAPI({ "jpeg": true });
    }

  } else if (typeof e.data === "object") {
    // if data is of type object, then it is jpeg
    fs.writeFile(imageName, Buffer.from(e.data), function (err) {
      if (err) return console.log(err);
    });

    // close connection after write image to file
    connection.close()
  }
};

C#

// Dotnet Websocket API Example
// 
// Install dotnetSDK:
//   see:  https://docs.microsoft.com/de-de/dotnet/core/install
// 
// Init project in current directory:
//   dotnet new console
// 
// copy the content to your Program.cs and replace the namespace "dotnet_websocket_api_example" with yours


using System;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Net.WebSockets;
using System.IO;

namespace dotnet_websocket_api_example
{
  class Program
  {
    const string ServerIp = "192.168.0.107";
    const int ServerPort = 7000;
    const string APIToken = "0123456789";
    static string JpgFileName = "APIImage.jpg";


    public static Task SendString(ClientWebSocket ws, String data, CancellationToken cancellation)
    {
      var encoded = Encoding.UTF8.GetBytes(data);
      var buffer = new ArraySegment<Byte>(encoded, 0, encoded.Length);
      return ws.SendAsync(buffer, WebSocketMessageType.Text, true, cancellation);
    }

    public static async Task<String> ReadString(ClientWebSocket ws)
    {
      ArraySegment<Byte> buffer = new ArraySegment<byte>(new Byte[8192]);

      WebSocketReceiveResult result = null;

      using (var ms = new MemoryStream())
      {
        do
        {
          result = await ws.ReceiveAsync(buffer, CancellationToken.None);
          ms.Write(buffer.Array, buffer.Offset, result.Count);
        }
        while (!result.EndOfMessage);

        ms.Seek(0, SeekOrigin.Begin);

        using (var reader = new StreamReader(ms, Encoding.UTF8))
          return reader.ReadToEnd();
      }
    }

    public static async Task ReadAndSaveImage(ClientWebSocket ws)
    {
      ArraySegment<Byte> buffer = new ArraySegment<byte>(new Byte[8192]);

      WebSocketReceiveResult result = null;

      using (var ms = new MemoryStream())
      {
        do
        {
          result = await ws.ReceiveAsync(buffer, CancellationToken.None);
          ms.Write(buffer.Array, buffer.Offset, result.Count);
        }
        while (!result.EndOfMessage);

        ms.Seek(0, SeekOrigin.Begin);

        using (FileStream file = new FileStream(JpgFileName, FileMode.Create, System.IO.FileAccess.Write))
        {
          Console.WriteLine("Save Image from API to " + JpgFileName);
          ms.WriteTo(file);
        }
      }
    }

    static async Task Main(string[] args)
    {
      // Define the cancellation token.
      CancellationTokenSource source = new CancellationTokenSource();
      CancellationToken token = source.Token;


      using (var socket = new ClientWebSocket())
      {

        try
        {
          await socket.ConnectAsync(new Uri("ws://" + ServerIp + ":" + ServerPort), CancellationToken.None);

          await SendString(socket, "{\"token\": \"" + APIToken + "\", \"info\": [\"cam1-light\", \"cam1-exists\"]}", token);
          string message = await ReadString(socket);

          // here you can handle message 
          Console.WriteLine("Message from API: " + message);


          // We try now to get a image from API
          await SendString(socket, "{\"token\": \"" + APIToken + "\", \"jpeg\": true }", token);
          await ReadAndSaveImage(socket);
        }
        catch (Exception ex)
        {
          Console.WriteLine($"ERROR - {ex.Message}");
        }

      }
    }
  }
}

NIM

# Example in NIM - Serial Process
#
# 1. Connect to websocket-server
# 2. Check camera 1 is connected and the light is on
# 3. If camera 1 is connected and the light is off then turn the light on
# 4. Take a jpg image and save it as file
# 5. Close connection
#

import strformat, strutils, json
import ws, asyncdispatch

const
  rotoclear_ip = "192.168.0.102" 
  rotoclear_api_port = "7000" 
  my_api_token = "0123456789"

proc runProcess() {.async.} = 
  # create connection
  let conn = await newWebSocket(&"ws://{rotoclear_ip}:{rotoclear_api_port}") 
  
  # get info from API
  var message = %*{
    "token": my_api_token,
    "info": ["cam1-light", "cam1-exists"]
  }
  await conn.send($message)
  let
    response = await conn.receiveStrPacket()
    respMsg = parseJson(response)

  if respMsg.hasKey("cam1-exists"): 
    if respMsg["cam1-exists"].getBool():
      
      if not respMsg["cam1-light"].getBool(): 
        message = %*{ 
          "token": my_api_token, 
          "cam1-light": true 
        }

        await conn.send($message)
        echo await conn.receiveStrPacket() 

    # get jpeg as blob and save it
    message = %*{ "token": my_api_token, "jpeg": "" }
    await conn.send($message)
    let data: seq[byte] = await conn.receiveBinaryPacket()
    writeFile("myCurrentImg.jpg", data) 
  
  conn.close()


waitFor runProcess()

Python3

#!/usr/bin/python3

# Example in Python3
#
# 1. Connect to websocket-server
# 2. Check camera 1 is connected and the light is on
# 3. If camera 1 is connected and the light is off then turn the light on
# 4. Take a jpg image and save it as file
# 5. Close connection
#
# Install dependencies:
#   pip install websocket-client
#   pip install json
#

from websocket import create_connection
import json

ROTOCLEAR_IP = "10.19.8.64"
ROTOCLEAR_API_PORT = "7000"
MY_API_TOKEN = "0123456789"


ws = create_connection("ws://" + ROTOCLEAR_IP + ":" + ROTOCLEAR_API_PORT)

jsontext = json.dumps({"token": MY_API_TOKEN, "info": ["cam1-light", "cam1-exists"]})
ws.send(jsontext)
result = json.loads(ws.recv())

if result["cam1-exists"]:
    if not result["cam1-light"]:
        jsontext = json.dumps({"token": MY_API_TOKEN, "cam1-light": True})
        ws.send(jsontext)
        result = ws.recv()

    # get jpeg as blob and save it
    jsontext = json.dumps({"token": MY_API_TOKEN, "jpeg": True})
    ws.send(jsontext)
    result = ws.recv()

    f = open("myCurrentImg.jpg", "wb")
    f.write(result)
    f.close()

ws.close()