Kommunikation

Pub/Sub-Systeme

Im Internet der Dinge (Internet of Things, IoT) tauschen viele Millionen Geräte, Sensoren und Aktoren untereinander Daten aus. Jedoch sind diese oft nicht sehr leistungsfähig und nur vorübergehend erreichbar, weil die Netzverbindung unterbrochen ist oder sie nur zeit- und ortsabhängig benötigt wird. Wie baut man nun eine Infrastruktur mit unzuverlässigen Teilnehmern (Clients), die nicht einmal in der Lage sind, die Liste der Empfänger für ihre Nachrichten zu verwalten?

Eine mögliche Lösung bieten sogenannte Publish/Subscribe-Systeme (Pub/Sub). Hier übernimmt ein Vermittler (Broker) die Aufgabe, Nachrichten an die richtigen Empfänger weiterzuleiten. Jedes Gerät kann Daten senden (publish) oder dem Vermittler gegenüber mitteilen (subscribe), an welcher Art Nachrichten (topic) es interessiert ist und diese später empfangen.

Pub/Sub
Figure 1. Pub/Sub mit verschiedenen Teilnehmern

MQTT

MQTT[1] ist ein standardisiertes Nachrichtenprotokoll für Pub/Subs. Es wird seit 1999 stetig weiterentwickelt, bietet verschiedene Übertragungsgarantien (Quality of Service oder QoS) für den Nachrichtentransport und zeichnet sich durch geringe Ansprüche an Verbindungsqualität und Rechenleistung aus.

QoS Level

Für die Operationen subscribe und publish können als Option verschiedene QoS-Level festgelegt werden. Das MQTT-Protokoll sichert daraufhin über ggf. zusätzliche Nachrichten deren Einhaltung ab.

  • Level 0: Die Nachricht erreicht die Empfänger höchstens einmal.

  • Level 1: Die Nachricht erreicht die Empfänger mindestens einmal.

  • Level 2: Die Nachricht erreicht die Empfänger genau einmal.

Mit Erhöhen des QoS-Levels steigt die Komplexität des Protokolls und die Anzahl der verschickten Nachrichten im Hintergrund.

Im RoboLab wird MQTT auf QoS Level 2 eingesetzt.

Nutzung im RoboLab

Jeder Roboter nimmt während der Erkundung regelmäßig Kontakt zu seinem Mutterschiff auf, das ihn zuvor auf dem Planeten abgesetzt hat. Aufgrund der dichten Atmosphäre gelingt dies jedoch nur mit den Verstärkern der Versorgungsstationen, die an den Kreuzungspunkten der Pfade eingerichtet wurden.

Dort eröffnet der Roboter eine Übertragung und schickt seine geschätzte neue Position. Er empfängt vom Mutterschiff eine Bestätigung und möglicherweise verschiedene andere Nachrichten. Es wird dabei zwischen Planeten-Nachrichten, zum Beispiel nützlichen Informationen wie neuen Pfaden, und den direkten Anweisungen des Mutterschiffs, also einem Erkundungsziel unterschieden.

Nach der letzten gesendeten oder empfangenen Nachricht wird ein Timeout von 3 Sekunden abgewartet, bevor die Kommunikation an dem Knoten beendet und die Erkundung fortgesetzt wird. Allgemein gesprochen gilt eine Übertragung an einem Knoten also als beendet, wenn der Roboter 3 Sekunden lang keine Nachricht mehr empfangen hat.

Das Timeout von 3 Sekunden bedeutet nicht, dass zwischen zwei gesendeten oder empfangenen Nachrichten jeweils 3 Sekunden Funkstille herrschen sollen.
Es ist auch nicht erforderlich, an jedem Knoten eine neue Verbindung zu erstellen und diese danach wieder zu schließen.

Verbindungsmöglichkeiten

Die Kommunikation mit dem MQTT-Broker erfolgt grundsätzlich TLS-verschlüsselt. Im RoboLab bieten wir sowohl die Standard-Verbindung als auch Websockets an.

Standard-Verbindung

URL: mothership.inf.tu-dresden.de
PORT: 8883

Websocket-Verbindung

URL: mothership.inf.tu-dresden.de
PORT: 9002
Python-Client-Parameter: transport="websockets"

Nachrichten

Die Kommunikation zwischen Roboter (Client) und Mutterschiff (Server) erfolgt im JSON-Format[2].
Nachrichten des Roboters haben dabei immer den from-Typ "client", Antwortnachrichten darauf vom Mutterschiff den from-Typ "server".
Alle anderen Nachrichten haben den from-Typ "debug".
Weiterhin werden alle Keywords in camelCase-Notation angegebenen.

Alle auf den jeweiligen Topics verschicken Nachrichten findet ihr hier gelistet:

Nachrichten-Arten

Nicht alle vom Mutterschiff verschickten Nachrichten werden hier aufgeführt.
Um die Entwicklung zu erleichtern, sendet das Mutterschiff Nachrichten mit dem from-Typ "debug".
Diese enthalten nützliche Informationen, dadurch sind Fehler schneller erkenn- und behebbar.

Wichtig: Debug-Nachrichten werden zur Prüfung NICHT gesendet!

Einige Platzhalter kurz erläutert:

<GROUP> = Group name / ID (z.B. 001)
<PLANET> = Planet name
<TEXT> = Integer / String placeholder
<PASS> = Group MQTT-password, see your skill-test ticket
Xs, Ys, Ds = Start coordinates and direction
Xe, Ye, De = End coordinates and direction
Xc, Yc, Dc = End coordinates and direction, possibly corrected
Xt, Yt = Target coordinates

Beispiele:

Explorer: explorer/001
Planet: planet/Gromit/001

Nachrichten, die vom Server initiiert werden

Häufige Fehler & Hinweise

  • Verschickte Nachrichten liegen im Format UTF-8 vor und müssen dementsprechend zuerst mit message.payload.decode('utf-8') dekodiert werden, damit es bei der Weiterverarbeitung der enthaltenen Strings nicht zu Fehlern kommt.

  • Häufig werden keine Nachrichten empfangen, da das Skript oder Modul für die Kommunikation direkt nach Start des Listeners schon wieder beendet wird. Eine while-Schleife kann hier Abhilfe schaffen.

  • Koordinaten, Richtungsangaben sowie Gewichte der Pfade werden als ganze Zahlen (Integer) geschickt.

    • Für die Richtungsangaben werden Abkürzungen der Himmelsrichtungen in Form von Grad-Angaben 0 (Nord), 90 (Ost), 180 (Süd), 270 (West) verwendet.

  • Ihr könnt die Kommunikation, nach Installation geeigneter Werkzeuge, auch zuerst lokal auf eurem Rechner testen.
    Wir empfehlen dazu die Nutzung des Clients MQTT Explorer (Konfigurationsbeispiel siehe unten).
    Bitte beachtet, dass das Mutterschiff (also der Server) für die Roboter nur über das WLAN RoboLab Playground erreichbar ist.

    • Username und Password müssen mit den Daten der Gruppe befüllt werden

    • Unter Advanced muss ggf. noch die vordefinierte Client-ID entfernt werden (Feld leeren), dann sollte die Verbindung klappen

mqttexplorer01

Code-Beispiel

Zur Herstellung der zuvor beschriebenen Kommunikation wird im RoboLab die Bibliothek paho-mqtt verwendet. Es ist sinnvoll, sich einen Überblick über die API und das eingesetzte Protokoll MQTT zu verschaffen und nicht einfach nur das Code-Beispiel zu verwenden.

#!/usr/bin/env python3

import json
import paho.mqtt.client as mqtt
import ssl

# this is a helper method that catches errors and prints them
# it is necessary because on_message is called by paho-mqtt in a different thread and exceptions
# are not handled in that thread
#
# you don't need to change this method at all
def on_message_excepthandler(client, data, message):
    try:
        on_message(client, data, message)
    except:
        import traceback
        traceback.print_exc()
        raise

# Callback function for receiving messages
def on_message(client, data, message):
    print('Got message with topic "{}":'.format(message.topic))
    data = json.loads(message.payload.decode('utf-8'))
    print(json.dumps(data, indent=2))
    print("\n")


# Basic configuration of MQTT
client = mqtt.Client(client_id="<GROUP>", clean_session=False, protocol=mqtt.MQTTv31)

client.on_message = on_message_excepthandler # Assign pre-defined callback function to MQTT client
client.tls_set(tls_version=ssl.PROTOCOL_TLS)
client.username_pw_set('<GROUP>', password='<PASS>') # Your group credentials, see the python skill-test for your group password
client.connect('mothership.inf.tu-dresden.de', port=8883)
client.subscribe('explorer/<GROUP>', qos=2) # Subscribe to topic explorer/xxx

# Start listening to incoming messages in the background
client.loop_start()

while True:
    user_input = input('Enter disconnect to close the connection...\n')

    if user_input == 'disconnect':
        break

    # you could add some code to send a message here

client.loop_stop()
client.disconnect()
print("Connection closed, program ended!")