TCP/IP Sockets

Es wird die Implementierung einer TCP/IP Kommunikation vorgestellt. Die Schnittstelle wird durch das Erstellen einer Socket-Verbindung realisiert. Hierfür wird auf die Standardfunktionalitäten von JavaScript (insb. StreamWriter) zurückgegriffen.

1. Einleitung


Mittels Sockets kann eine Kommunikation zum Datenaustausch zwischen dem Roboter und weiteren via Netzwerk erreichbaren Teilnehmern (Anlage, Kamera, etc.) hergestellt werden. Die Kommunikation erfolgt bidirektional. Das heißt, Daten können empfangen sowie gesendet werden.

In dem folgenden Artikel wird beschrieben, wie eine solche Kommunikation über Sockets aufgebaut und Daten übertragen werden können.

Der Roboter agiert hierbei als Client und die Gegenstelle als Server. 

2. Aufbau der Verbindung

Der Roboter wird mittels eines Netzwerkkabels mit der Gegenstelle verbunden.

Für den Verbindungsaufbau wird die IP-Adresse der Gegenstelle sowie der Port benötigt. Es ist darauf zu achten, dass die IP-Adressen der beiden Teilnehmer sich im selben Bereich befinden. Das heißt, die vordersten 9 Stellen der IP-Adressen müssen gleich sein und die letzten 3 Stellen müssen sich voneinander unterscheiden.

In folgendem Artikel ist beschrieben, wie sich die IP-Adresse des Roboters einstellen lässt. IP-Adresse ändern

Mittels des folgenden Befehls kann eine Verbindung über einen Socket hergestellt werden. Der Roboter agiert hierbei als Client und die Gegenstelle als Server.

var IP_Adresse = "10.125.1.122";
var Port = 3000;

var socket = new java.net.Socket(IP_Adresse, Port);

 Das gleiche Funktioniert auch mit Timeout: 

var IP_Adresse = "10.125.1.122";
var Port = 3000;

var socket = new java.net.Socket();
socket.connect(new  java.net.InetSocketAddress(IP_Adresse, Port), 10000); //10s timeout

3. Informationen senden

Mittels der folgenden Funktion kann per StreamWriter eine Nachricht als Zeichenkette über den Socket gesendet werden.

function schreibeNachricht(socket, nachricht) 
{
        var printWriter = 
              new java.io.PrintWriter(
                       new java.io.OutputStreamWriter(
                               socket.getOutputStream()));
        printWriter.print(nachricht);
       printWriter.flush();
}

Die Funktion wird über folgende Zeile aufgerufen. In der Klammer wird als zweites Argument die gewünschte Nachricht übergeben.

var Nachricht = "Hello World!";
schreibeNachricht(socket, Nachricht);

3.1.  Senden von raw-Daten

Sollen raw-Daten, zum Beispiel in Form eines hexadezimalen Byte Arrays,  über den Socket gesendet werden, kann folgende Funktion genutzt werden.

function schreibeNachricht(socket, nachricht)
{
    var jNachricht = Java.to(nachricht,"byte[]");
    
    var dout = new java.io.DataOutputStream(socket.getOutputStream());
    dout.write(jNachricht);
    dout.flush();
}

Die Funktion wird über folgende Zeile aufgerufen. In der Klammer wird als zweites Argument die gewünschte Nachricht übergeben. Hierbei wird 01hex 00hex 00hex 00hex übertragen.

var connect_cam =[0x01, 0x00, 0x00, 0x00];
schreibeNachricht(socket, connect_cam);

4. Informationen lesen

Mittels der folgenden Funktion kann per StreamReader eine Nachricht als Zeichenkette über den Socket gelesen werden.

function leseNachricht(socket) 
{
    var bufferedReader =
        new java.io.BufferedReader(
            new java.io.InputStreamReader(
                socket.getInputStream()));
    var charArrayType = Java.type("char[]");
    var buffer = new charArrayType(200);
    var anzahlZeichen = bufferedReader.read(buffer, 0, 200);
    var nachricht = new java.lang.String(buffer);
    return nachricht;
}

Die Funktion wird über folgende Zeile aufgerufen. In die Variable result wird die Nachricht geschrieben. Diese wird anschließend als Meldung in horstFX ausgegeben.

var result = leseNachricht(socket);
show_info(result);

4.1. Einlesen von raw-Daten

Mittels der folgenden Funktion können raw-Daten dezimal codiert als Byte-Array über den Socket gelesen werden.

function leseNachricht(socket, size)
{
    var ByteArray = Java.type("byte[]");
    var dIn = new java.io.DataInputStream(socket.getInputStream());

    var message = new ByteArray(size);
    dIn.readFully(message, 0, message.length); 

    var jsMessage =  Java.from(message);
    return jsMessage;
}

Die Funktion wird über folgende Zeile aufgerufen. In die Variable result wird die Nachricht geschrieben. Diese wird anschließend als Meldung in horstFX ausgegeben.

var result = leseNachricht(socket);
show_info(result);

4.2. Einlesen bis zu einem bestimmten Zeichen (z.B. carriage return)

Mittels der folgenden Funktion kann die Socket bis zu einem definierten Zeichen ausgelesen werden.

Oft wird am Ende eines zu übertragenden Strings ein Trennzeichen (Semikolon, Zeilenumbruch, etc.) geschrieben. Mit der folgenden Funktion wird die Socket solange ausgelesen, bis das definierte Trennzeichen erkannt/eingelesen wurde. Dadurch wird sichergestellt, dass die gesamten Informationen übertragen wurden.

function leseNachricht(socket, msgSperator)
{
    var ByteHelper = Java.type("de.fruitcore.robot.core.util.ByteHelper");

    var ByteArray = Java.type("byte[]");
    var dIn = new java.io.DataInputStream(socket.getInputStream());
   
    var full_msg = new ByteHelper();;
   
    var buffer = new ByteArray(1);
       
    while(1){
        dIn.readFully(buffer, 0, buffer.length);
        full_msg.putByte(buffer[0]);
        if(msgSperator == buffer[0]){
            break;
        }
    }

    var nachricht = new java.lang.String(full_msg.toBytes(), java.nio.charset.StandardCharsets.UTF_8);

    return nachricht;
}

Die Funktion wird über folgende Zeile aufgerufen. In der Variable delimiter wird das Trennzeichen definiert, bis zu dem die Socket ausgelesen werden soll. In die Variable result wird die Nachricht geschrieben.

var delimiter = "\r".charCodeAt(0); //Trennzeichen, z.B. CR Carriage Return (ggf. anpassen)
var result = leseNachricht(socket, delimiter);

4.3.  Einlesen der Nachricht bis zu einem bestimmten Timeout

Ein Timeout, falls einige Zeit keine Nachricht von der Kamera gesendet wird, kann über folgende Zeile definiert werden. Ansonsten könnte das Programm nicht abgebrochen werden, da der Roboter ständig auf die Nachricht wartet.

// 10 sek Timeout beim Einlesen der Nachricht ueber einen Socket
socket.setSoTimeout(10000);

Die Funktion zum Einlesen der Nachricht kann dazu folgendermaßen angepasst werden.

function leseNachricht(Socket) {
    var bufferedReader =
        new java.io.BufferedReader(
            new java.io.InputStreamReader(
                socket.getInputStream()));
    var charArrayType = Java.type("char[]");
    var buffer = new charArrayType(1000);
    try {
        var anzahlZeichen = bufferedReader.read(buffer, 0, 1000);  
        var nachricht = new java.lang.String(buffer);
    } catch (e) {
        var nachricht = "Timeout: Keine Nachricht erhalten";
    }
    return nachricht;
}

5. Sicherstellen, dass der Socket geschlossen wird

var IP_Adresse = "10.125.1.122";
var Port = 3000;
 
var socket = new java.net.Socket();
socket.connect(new  java.net.InetSocketAddress(IP_Adresse, Port), 10000); //10s timeout
LOG.info("connected");

try {
//#############code begin#############
  
//#############code end #############
} finally {
  socket.close();
}

Durch den try finally Block wird sichergestellt, das socket.close() aufgerufen wird. Das Funktioniert bei Programmierfehlern oder auch beim Abbrechen über den Abbrechen-Button.
Die gesamte Logik muss nun zwischen ##code begin## und ##code end ## Programmiert werden. Außerdem muss darauf geachtet werden, dass Funktionen außerhalb des try finally Block’s definiert werden.

6. Testen der Kommunikation

Mittels verschiedener Programme lässt sich die Kommunikation sehr einfach testen. Es muss lediglich Server ausgewählt sowie die IP-Adresse des Roboters und der Port eingetragen werden. 

Basis Progamm für Socketverbindungen