API – Dokumentation

Websocket API - Nodejs Environment

Die Websocket API lässt sich unter anderem auch in der Node.js Infrastruktur einbauen.

  • Installation
  • Requests & Response
  • Info keys
  • Command keys
  • Beispiele

Installation:

Grundvoraussetzung ist hier ein funktionierendes Node.js Environment.

In Node.js Environment lassen sich einige Implementierungen von Websocket Clients finden. In unserem Beispiel verwenden wir “websocket”, da es dem W3C Standard folgt und unser Code zum Browser Javascript kompatible ist.

Installation (in der Console):

npm install websocket

Einbindung des Moduls in der Datei:

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

Connection:

Für den Aufbau einer Websocket Verbindung werden neben der Freischaltung des Moduls, die Verbindungsdaten im lokalen Netzwerk benötigt. Standardmäßig läuft die API auf dem Port 7000. Dies kann jedoch im Menü verstellt werden. Außerdem wird das festgelegte Token für jede Interaktion benötigt.

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

Websocket connection:

Um die Verbindung aufzubauen benötigen wir schlicht die IP-Adresse und den Port über den die API erreichbar ist. Die API ist nicht ssl verschlüsselt, sodass wir als Prefix ‘ws’ verwenden.

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

Ist die Verbindung aufgestellt, so kann über nachfolgendes Funktionsbeispiel eine Nachricht an den Steuerrechner geschickt werden. Beachte, dass die Nachrichten hier mit dem Token versehen werden, damit die API deine Anfragen zulässt.

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

In Javascript ist gibt es nur begrenzte Kontrolle über die Input-Output Schnittstellen (IO’s), deshalb können wir kein serielles oder direktes Verbindungs- bzw. Kommunikationsprotokoll nutzen. Stattdessen haben wir die Möglichkeit die Websocket-Verbindung mit Events zu beschreiben und auf Events zu reagieren.
Ist eine serielle Beschreibung einer Prozedur aber notwendig, lässt sich diese in Javascript durch asynchrone Programmierung verwirklichen. Das jedoch ist außerhalb des Scopes hier.

Im Nachfolgenden beschreiben wir diese Events in unserem Programm.

Onopen Event

Wenn das ‘onopen’ Event bei der Verbindung getriggert wird, heißt es dass die Verbindung gelungen ist. In dem Funktionsblock, kann dann die Kommunikation zur API starten.

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

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

Onerror Event

Durch dieses Event lassen sich Verbindungsschwierigkeiten diagnostizieren.

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

Onmessage Event

Auf jede Anfrage antwortet die API mit einer Nachricht. Es gibt grob gesehen 2 Arten von Nachrichten. Zum einen JSON formatierte Text-Daten. Zum anderen JPEG-Bilder in Form von rohen Bytebuffern. Diese beiden Möglichkeiten werden in der nachfolgenden Implementierung der onmessage Funktion abgebildet.

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 u can use FileReader
    // in Nodejs Environment 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

Wie bereits angedeutet, reagiert die API auf jede Anfrage mit einer Antwort. Wenn wir zum Beispiel im ‘onopen’ Event oder sonst irgendwo die folgende Nachricht verschicken

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

Dann wird das System eine JSON-formatierte Textnachricht zurückschicken. Diese wird dann in unserem ‘onmessage’ Block zu einem JSON geparst und das Ergebnis ist dann zum Beispiel:

{
  "cam1-light": true,
}

Es können auch eine Reihe an Anfragen gleichzeitig verschickt werden. ‘infokann zum Beispiel sowohl ein string als auch ein Array von strings sein:

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

Ein mögliche Antwort auf diese Anfrage wäre dann:

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

Neben Information können auch Befehle an die API verschickt werden. Im Vergleich zu den vorherigen Beispielen steht hier nun nicht mehr ‘info’ als Keys. Stattdessen werden die Keys parametrisiert.

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

Auch hier antwortet die API entsprechend. Dabei werden jedoch die Befehle validiert und die möglichen Grenzen der Werte beachtet. So dass die Antwort auf diese Kommandos dann wie folgt aussieht:

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

Info keys:

Überblick über die möglichen Informations-Anfragen:

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. Which sensor is selected?
cam2-sensor 1 or 2 (int) Some Camera heads has 2 Sensors. Which sensor is selected?
main-camera 1 or 2 (int) Which 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) To which value is the contrast set?
sharpness 1 to 15 (int) To which value is the sharpness set?
saturation -7 to 7 (int) To which value is the saturation set?
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:

Überblick über die möglichen Befehle:

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) Which 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 "FullHD", "HD" (string) "FullHD", "HD" (string) What resolution is to be set? "FullHD": 1920x1080, "HD": 1280x720
auto-white-balance (bool) (bool) Switch automatic white balance on or off.
noise-supression (bool) (bool) Switch noise supression on or off.
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) Set jpeg compression quality.
contrast 1 to 2.5 (float) 1 to 2.5 (float) Set the value of the contrast.
sharpness 1 to 15 (int) 1 to 15 (int) Set the value of the sharpness.
saturation -7 to 7 (int) -7 to 7 (int) Set the value of the saturation.
red-gain -128 to 127 (int) -128 to 127 (int) Set red-gain, if 'auto-white-balance' is off.
blue-gain -128 to 127 (int) -128 to 127 (int) Set blue-gain, 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) control video recording
jpeg (any) (Blob) take and get image in jpeg format

Beispiel

  • 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()