TigerJython4Kids
HomeTurtlegrafikRobotikDatenbanken
mqtt

15. RECHNERKOMMUNIKATION MIT MQTT

 

 

DU LERNST HIER...

 

wie du über das Internet mehrere Systeme (Oxocard, Raspberry Pi, Arduino, PC) miteinander verbindest, um Kurzinformationen auszutauschen. Das MQTT-Protokoll (Message Queuing Telemetry Transfer) wurde erfunden, um den Datenaustausch  schlank zu halten. Es verwendet TCP/IP als Transport-Protokoll. Im Zentrum, als quasi als Nabe eines Rades,  befindet sich ein MQTT-Server, der "Broker" genannt wird. Geräte, die untereinander Informationen austauschen möchten sind sozusagen über Speichen mit dem Broker verbunden. Sie werden MQTT-Clients genannt.

 

 

MUSTERBEISPIELE

 

Ein Client (Publisher) kann zu einem bestimmten Thema (Topic) Informationen (Payload) publizierten. Andere Clients (Subscriber) können auf dieses Thema ein Abonnement abschliessen.  Der Publisher sendet seine Informationen an den Broker, der sie an alle abonnierten Subscriber weiter leitet. Es gibt keine automatische Rückmeldung an den Publisher, ob und wer die Information tatsächlich gelesen hat.

In einem typischen Musterbeispiel ist der Publisher ein Microcontroller, der Messdaten sammelt, beispielsweise die Lufttemperatur oder andere Zustandsparameter eines Systems, z.B. einen Zählerstand- Er publiziert die Informationen alle Minuten und diese können von beliebig vielen Subscriber abholt werden. (Da ein Publisher auch gleichzeitig Subscriber sein kann, können Informationen auch ausgetauscht werden.)


Zuerst schreibst du einen Publisher, der die Anzahl Tastenbetätigungen als Zählerstand publiziert. Du verwendest dazu einen MQTT-Broker kennen, der seine Dienste gratis zur Verfügung stellt. Du kannst wählen zwischen den folgenden Servern:

test.mosquitto.org,  m2m.eclipse.org, broker.hivemq.com

Du nimmst die Verbindung zu einem dieser Server über das Internet auf, wobei du einen WLAN-Accesspoint mit einer dir bekannten SSID/Passwort verwendet, beispielsweise über einen Tethering-Dienst auf deinem Smartphone.

Mit Wlan.connect() loggst du dich dort ein. Nachfolgend erstellst du mit client = MQTTClient(broker) ein Client-Objekt und stellst mit client.connect() die Verbindung zum Broker her. Jedes Mal, wenn du die linke untere Taste drückst, publizierst du mit client.publish() den neuen Wert. Auf dem LED-Display zeigst du einige Informationen als Scrolltext dar.

Programm:

from mqttclient import MQTTClient
from oxobutton import *
from oxocard import *

broker = "broker.hivemq.com" 
topic = "/ch/count"
btn1 = Button(BUTTON_R2) # button at right bottom
btn2 = Button(BUTTON_L2) # button at left bottom

bigTextScroll("Connect AP.")
Wlan.connect("myssid", "mypassword")
bigTextScroll("Connect broker.")
client = MQTTClient(broker)
client.connect()
count = 0
bigTextScroll("Press left button.")
while not btn1.wasPressed():
    if btn2.wasPressed():
        count += 1
        if count == 100:
            count = 0
        display(count)
        client.publish(topic, str(count))
client.disconnect()
bigTextScroll("Disconnected.")
► In Zwischenablage kopieren

Um die Informationen abzuholen, schreibst du einen Subscriber, der wiederum über einen Accesspoint auf das Internet kommt (es kann der gleiche sein). Auch hier erzeugst du ein Client-Objekt und registrierst eine Callbackfunktion onMessageReceived(), die vom System automatisch aufgerufen wird, wenn eine Mitteilung zum abonnierten Topic eintrifft. Dazu musst du mit client.subscribe() das Topic abonnieren. Auch hier zeigst du auf dem LED-Display einige Informationen, insbesondere den erhaltenen Zählerwert dar.

Programm:

from mqttclient import MQTTClient
from oxobutton import *
from time import sleep
from oxocard import *

def onMessageReceived(topic, payload):
    display(payload)

broker = "broker.hivemq.com"
topic = "/ch/count"
button = Button(BUTTON_R2) # button at right bottom

bigTextScroll("Connect AP.")
Wlan.connect("myssid", "mypassword")
bigTextScroll("Connect broker.")
client = MQTTClient(broker)
client.set_callback(onMessageReceived)
client.connect()
bigTextScroll("Subscribing.")
client.subscribe(topic)
while not button.wasPressed():
    sleep(0.5)
client.disconnect()
bigTextScroll("Disconnected.")
► In Zwischenablage kopieren

Weil eingehende Mitteilungen automatisch die Funktion onMessageReceived() aufrufen, kannst du dich im Hauptprogramm darauf beschränken, in einer Schleife zu prüfen, ob du das Programm mit einem Buttonklick abbrechen willst.

 

 

MIT CALLBACK-FUNKTIONEN UMGEHEN

 

Das Programmieren mit Callbacks muss gelernt sein, da es etwas ungewöhnlich ist, dass das laufende Programm irgendwann und an irgendeiner Stelle durch das System unterbrochen wird und nachfolgend die Callbackfunktion zur Ausführung gelangt. Als Leitplanke gilt, dass die Callbackfunktion möglichst kurz sein soll, d.h. möglichst rasch zurückkehren muss. Sie darf also in der Regel keine sleep() und keine Wiederholschleifen enthalten. In vielen Fällen genügt es, wenn du in der Callbackfunktion eine globale Variable neu zuweist, deren neuer Wert du im Hauptprogramm verwendest. Nach der Rückkehr aus dem Callback fährt das Programm genau dort weiter, wo es unterbrochen wurde.

Im folgenden Beispiel bewegt sich auf einer Oxocard die Schlange endlos noch rechts (mit Rücksprung auf die linke Seite) oder endlos nach links (mit Rücksprung auf die rechte Seite). Sie "hört" auf Befehle "RIGHT", "LEFT" oder "STOP", die von einer anderen Oxocard gesendet werden, je nachdem ob man diese nach rechts oder nach links neigt, bzw horizontal hältst.  
Publisher
 
Subscriber

 

Zuerst betrachtest du das Steuerungsprogramm, das auf einem MQTT-Broker das Topic /ch/roll publiziert. In der Endlosschleife wird vom Beschleunigungssensor der Roll-Winkel geholt und je nach Bereich die Zustandsvariable state auf einen der drei Werte gesetzt. Aus Effizienzgründen willst du aber nur im Fall, wo sich der Zustand ändert, den neuen Zustand publizieren und den ersten Buchstaben auf dem LED-Display ausschreiben. Der Trick, um dies zu erreichen, ist die Verwendung einer Variablen oldState, die den vorhergehenden Zustand abspeichert. Nur wenn state und oldState verschieden sind, wird der neue Zustand übertragen und oldState auf state gesetzt.

Programm:

from mqttclient import MQTTClient
from oxocard import *
from oxoaccelerometer import *

acc = Accelerometer.create()

#broker = "m2m.eclipse.org"
#broker = "test.mosquitto.org"
broker = "broker.hivemq.com" 
topic = "/ch/roll"

insertBigChar("V", RED)
Wlan.connect("myssid", "mypassword")
client = MQTTClient(broker)
client.connect()
oldState = ""
state = "STOP"

while True:
    roll = acc.getRoll()
    if roll > -10 and roll < 10:
        state = "STOP"
    elif roll > 20:
        state = "RIGHT"
    elif roll < -20:
        state = "LEFT"
    if state != oldState:
        client.publish(topic, state)
        insertBigChar(state[0])
        oldState = state
    sleep(0.01)
► In Zwischenablage kopieren

Du kannst das Programm testen, indem du den MQTT-Subscriber auf dem PC laufen lässt.

Nun zur Oxocard mit der laufenden Schlange: Du musst zuerst wieder den MQTT-Client erzeugen und ebenfalls das Topic /ch/roll abonnieren. Am besten ist es, wenn du dir vorstellst, dass das Programm in drei Zuständen sein kann: Im Zustand "RIGHT", wenn sich die Schlange nach rechts, im Zustand "LEFT", wenn sich die Schlage nach links bewegt und im Zustand "STOP", wenn die Schlage still steht.

Der Zustandswechsel wird dir als Message vom Broker mitgeteilt. Dabei wird vom System her die Callbackfunktion onMessageReceived() aufgerufen und wir haben es so eingerichtet, dass die payload gerade einer der drei Zustandswerte ist. Darum genügt es, im Callback der Variablen state den Wert von payload zuzuweisen.

def onMessageReceived(topic, payload):
    global state
    state = payload

(Wir haben uns also extrem gut an die Regel gehalten, dass Callbacks möglichst kurz sein sollen!)
In der Schleife des Hauptprogramms musst du noch mit if-Bedingungen dafür sorgen, dass die Schlange wieder auf die andere Seite hüpft, wenn sie am Ende der Zeile angekommen ist.

Programm:

from oxosnake import *
from mqttclient import MQTTClient
from oxobutton import *

def onMessageReceived(topic, payload):
    # State receiver
    global state
    state = payload

#broker = "m2m.eclipse.org"
#broker = "test.mosquitto.org"
broker = "broker.hivemq.com"
topic = "/ch/roll"
state = "RIGHT"

makeSnake(pos = (4, 4), heading = 90, speed = 70)
Wlan.connect("myssid", "mypassword")
client = MQTTClient(broker)
client.registerCallback(onMessageReceived)
client.connect()
client.subscribe(topic)
while True: 
    # State dispatcher
    if state == "LEFT":
        setHeading(-90)
        forward()
    elif state == "RIGHT":
        setHeading(90)
        forward()
    elif state == "STOP":
        sleep(0.1)

    # Snake round-robin    
    if getX() == 11:
        setX(0)    
    if getX() == -4:
        setX(7)
► In Zwischenablage kopieren

 

 

MERKE DIR...

 

Mit MQTT kannst du sehr einfach kurze Informationen an mehrere Empfänger versenden. Dazu benötigst du einen MQTT-Broker, der für den Datenaustausch zwischen MQTT-Clients zuständig ist. Diese können Informationen zu einem Topic publizieren oder sich auf ein bestimmtes Topics abonnieren. Für den Datenaustausch werden nur wenige Bytes verwendet,  er ist daher effizient und kostengünstig.

Zu Testzwecken kannst du auch einen Publisher oder Subscriber verwenden, der unter TigerJython auf dem PC läuft. Du kannst die Programme von hier downloaden. Du kannst für deine Tests auch einen Online verfügbaren MQTT-Client verwenden, beispielsweise auf http://www.hivemq.com/demos/websocket-client .

 

 

ZUM SELBST LÖSEN

 

 

1.

Verbessere die beiden oberen Programmbeispiele so, dass es eine Fehleranzeige gibt, falls das Einloggen auf dem Accesspoint oder das Einloggen auf dem Broker nicht gelingt. Anleitung: Beide connect()-Methoden geben bei Erfolg True und bei Misserfolg False zurück. Du kannst im Fehlerfall ein vielsagendes Bild auf dem LED-Display anzeigen.


2a.

Eine Oxocard erfasst den momentanen Roll-Winkel und publiziert diesen alle 0.1 Sekunden unter irgendeinem Topic, z.B. /ch/roll. Eine zweite Oxocard stellt diesen Wert auf dem Display dar. Um den Datentransfer zu optimieren, kannst du den Roll-Winkel nur dann übermitteln, falls er sich geändert hat.


2b*.

Stelle den Roll-Winkel auch auf einem PC dar, entweder nur im Ausgabefenster oder sogar als Grafik.



 

3.
Eine Oxocard misst den Pitch-Winkel pitch und sendet den Wert value = int(pitch / 10)
an eine zweite Oxocard, die den Wert mit einer horizontalen Linie im Bereich y = 0 bis y = 7 darstellt. Die Linie ganz unten soll einem value von 0 und die Linie ganz oben einem value von 7 entsprechen.
 
Publisher
 
Subscriber

 

 

 

 

15-1
Fachliche Hinweise:

Falls dir nur eine Oxocard zur Verfügung steht oder zu Testzwecken kannst du als MQTT-Client deinen Computer verwenden.

Publisher:
Auf dem Computer startest du (mit dem grünen Knopf) folgendes Programm:

from time import sleep
from gconsole import *
from mqttclient import MQTTClient

#host = "broker.dynns.com"
#host = "test.mosquitto.org"
host = "broker.hivemq.com" 
#host = "m2m.eclipse.org"       

topic = "/ch/count"
   
makeConsole()

m = MQTTClient()

if m.connect(host):
    gprintln("Connection to " + host + " established")    
    n = 0
    while not isDisposed():
        gprintln("Sending: " + str(n))
        m.publish(topic, n)
        n += 1
        sleep(0.01)
else:
   gprintln("Connection to " + host + " failed")    

Subsriber:
Auf dem Computer startest du (mit dem grünen Knopf) folgendes Programm:

from paho.mqtt import subscribe

#host = "m2m.eclipse.org"
host = "broker.hivemq.com"
topic = "/ch/count"

def messageReceived(client, userdata, message):
    print "messageReceived - payload:", message.payload

print "subscribing to host", host, "for topic", topic
subscribe.callback(messageReceived, topic, hostname = host)