Deutsch   English 

15. Smart Home 2: Geräte über das Internet steuern

Kombiniert man den micro:bit mit einem LinkUp-Board (ESP32-Coprozessor), so kann man Geräte und Systeme über das Internet steuern. Es gibt verschiedene Szenarien:

1.

Verwendung von HTTP: Ein Webserver auf dem ESP zeigt eine Webseite mit Ein-/Ausschaltbuttons. Der Browser eines RemoteManagers sendet einen HTTP-Request mit dem Schaltbefehl als URL -Filename oder als URL-Parameter

2.

Verwendung von MQTT: Der ESP subscribiert ein Topic und empfängt über den Broker den Schaltbefehl als Ein-/Ausschaltmessages.  Dieser wird von einem RemoteManager als Wert des Topic publiziert

3.

Verwendung eines Cloud-Servers (ThinkSpeak): Der ESP liest regelmässig mit HTTP- GET-Requests den Wert in einer Tabelle. Ein RemoteManager setzt in der Cloud den Schaltbefehl als Tabelleneintrag

In den folgenden Beispielen wird eine LED-Lampe ein-/ausgeschaltet (siehe Kapitel SMART HOME 1) Es könnte aber auch irgendein anderes Gerät sein, das mit einem digitalen Signal auf P0 geschaltet wird. Falls du kein Gerät hast, so erkennst du den Schaltungszustand auf dem Display des micro:bits als Image.YES und Image.NO.

 

Szenario 1: Webserver

Es wird das Konzept aus dem Kapitel Webserver übernommen. Je nach Ansprüchen kann die Webseite mehr oder weniger komfortable dargestellt werden, hier z.B. mit zwei Buttons. Dazu wird der HTML-Code für Website zuerst auf den ESP kopiert:

# SmartHome3HTML.py
from linkup import *

html = """<!DOCTYPE html>
<html>
  <head>
    <meta name="viewport" content="width=device-width, initial-scale=1">
  </head>
  <body> 
    <h2>My Smart Home</H2>
    <button style="font-size:22px; height:50px;width:130px" 
            onclick="window.location.href='on'">Light ON</button>&nbsp;&nbsp;
    <button style="font-size:22px; height:50px;width:130px" 
            onclick="window.location.href='off'">Light OFF</button>
</body>
</html>
"""   

print("Saving HTML...")
saveHTML(html)
print("Done")zwei Buttons. 
► In Zwischenablage kopieren

Dies entspricht folgender Anzeige:

Das Programm auf  dem micro:bit startet einen Webserver:

# SmartHome3.py
from microbit import *
from linkup import *

def onRequest(clientIP, filename, params):
    if filename == "/on":
        pin0.write_digital(1)
        display.show(Image.YES)
    elif filename == "/off":
        pin0.write_digital(0)
        display.show(Image.NO)

ipAddress = connectAP(ssid = "mySSID", password = "myPassword")
display.scroll(ipAddress)
pin0.write_digital(0)
display.show(Image.NO)
startHTTPServer(onRequest)
► In Zwischenablage kopieren

Erklärungen zum Programmcode:
display.scroll(ipAddress)): Zeigt die vom Router erhaltene IP-Adresse an
def onRequest(): Callback bei einem eingehenden GET-Request. Es ist kein Rückgabewert nötig, da es sich um eine statische (immer gleichbleibende) Webseite handelt

Dieses Szenario setzt voraus, dass der Benutzer Zugang zum Webserver hat. Dazu muss er sich entweder auf dem gleichen Router wie der ESP einloggen oder der Router muss so konfiguriert werden, dass man von aussen darauf zugreifen kann. Das Verfahren ist hier beschrieben: https://www.tigerjython4kids.ch/index.php?inhalt_links=robotik/navigation.inc.php&inhalt_mitte=robotik/iot/temperatur.inc.php

Vorteile:

Nachteile:

Dieses Szenario ist dann zu empfehlen, wenn relativ selten Daten übertragen werden.

Anmerkung: In einer Variante dieses Szenarios wird der Access Point auf dem ESP gestartet. Smartphones, Tablets und Desktops, die sich in der Nähe befinden, loggen sich auf diesem AP ein und verwenden die URL: 192.168.4.1. Ein Internet-Router entfällt.


Szenario 2: MQTT

Das Verfahren ist im Kapitel MQTT-Kommunikation beschrieben. Auf dem ESP wird ein Client eingerichtet, der das Topic /ch/lamp subscribiert und auf die Payloads on und off reagiert. Damit die Verbindung nicht nach einer gewissen Zeit geschossen wird, sendet der Client in Abständen von rund 10 s einen Ping-Request an den Broker.

# SmartHome4.py
from microbit import *
import mqtt

host = "broker.hivemq.com" 
topic = "/ch/lamp"

mqtt.connectAP("mySSID", "myPassword")
mqtt.broker(host)
mqtt.connect()
mqtt.subscribe(topic)
pin0.write_digital(0)
display.show(Image.NO)
count = 0
while True:
    count += 1
    if count % 10 == 0:
        mqtt.ping()    
    topic, payload = mqtt.receive()
    if topic != None:
        if payload == "on":
            pin0.write_digital(1)
            display.show(Image.YES)
        elif payload == "off":
            pin0.write_digital(0)
            display.show(Image.NO)
    delay(1000)
► In Zwischenablage kopieren

Erklärungen zum Programmcode:
if count % 10 == 0: Nach jeweils 10 Schleifendurchgängen von ungefähr 1 s wird eine Ping gesendet
if topic != None: Die Funktion receive() gibt das Tupel (None, None) zurück, wenn keine Message empfangen wurde. Falls der Broker die Verbindung schliesst, gibt sie None zurück und das Programm bricht ab. Um das System robust zu machen, sollte bei Fehlern der Client neu starten

Robuster Client:
Statt einem Ping wird ca. alle 10 s eine Statusmessage mit dem Topic /ch/smarthome/state gesendet.

# SmartHome5.py
from microbit import *
import mqtt, sys

host = "broker.hivemq.com" 
topic_switch = "/ch/lamp"
topic_state = "/ch/lamp/state"
pin0.write_digital(0)
display.show(Image.NO)
while True:
    try:
        print("Connecting to AP...")      
        if mqtt.connectAP("mySSID", "myPassword"):
            print("OK. Connecting to broker...")      
            mqtt.broker(host)
            if mqtt.connect():
                print("OK. Connection to broker established")      
                mqtt.subscribe(topic_switch)
                count = 0
                state = "OFF"
                while True:
                    count += 1
                    if count % 10 == 0:
                        print("Publishing topic:", topic_state, "payload:", state)
                        if not mqtt.publish(topic_state, state):
                            print("Error. Publish failed")
                            break
                    data = mqtt.receive()
                    if data == None:
                        print("Error. Connection to broker lost")
                        break    
                    topic, payload = data
                    if topic != None:
                        print("Got topic:", topic, "payload:", payload)
                        if payload == "on":
                            pin0.write_digital(1)
                            display.show(Image.YES)
                            state = "ON"
                        elif payload == "off":
                            pin0.write_digital(0)
                            display.show(Image.NO)
                            state = "OFF"
                    delay(1000)
            else:
                print("Error. Broker unreachable")      
        else:
            print("Error. Can't connect to AP")      
    except Exception as e:
        sys.print_exception(e)
► In Zwischenablage kopieren

Erklärungen zum Programmcode:
while True: Das Programm wird immer wieder eine neue Verbindung erstellen, wenn ein Fehler auftritt
mqtt.publish(topic_state, state): der Client publiziert ungefähr alle 10 Sekunden den Schaltzustand. Dies dient auch dazu, dass die Verbindung auch ohne Ping offen bleibt. Zudem kann ein externer Client durch subcribieren von "/ch/smarthome/state" überprüfen, ob das System funktioniert.

Wie üblich kann das Umschalten mit einem MQTT-Client durchgeführt werden, der auf einem PC (z.B. mit MQTTBox) oder auf einem Smartphone (z.B. mit MQTT Snooper oder MQTT Dash) läuft.

Vorteile:

Nachteile:


Szenario 3: Cloudserver

Auf einem externen Server werden Informationen gespeichert, die gleichermassen vom micro:bit und RemoteManager gelesen und verändert werden können. Damit ist es möglich, Informationen auszutauschen. Hier wird der Cloudserver von ThingSpeak verwendet, wie es im Kapitel Cloudcomputing beschrieben wurde. Als erstes muss also ein Konto eingerichtet und ein Channel definiert werden, der in diesem Beispiel das einzige Field1 hat.

In dieses Feld schreibt der RemoteManager entweder die Zahl 1 oder 0, je nachdem, ob der micro:bit das einen Ein- aus Ausschaltbefehl ausführen soll. Dazu liest dieser in kurzen Zeitabständen den Wert mit folgendem Programm aus:

# SmartHome6.py
from linkup import *
from microbit import *

readKey = "B1DYQYCZUGF0H3UZSL"
channelID = "8042864"

def extractValue(response):
    try:
        start = response.find('{')
        end = response.rfind('}')
        dataDict = eval(response[start : end + 1])
        return dataDict['feeds'][0]['field1']
    except:
        return ""

pin0.write_digital(0)
display.show(Image.NO)
print("Connecting to AP...")
if connectAP("MySSID", "MyPassword"):
    print("Connecting to AP successful")
    while True:
        url = "http://api.thingspeak.com/channels/"+channelID+"/feeds.json?api_key="+readKey+"&results=1" 
        response = httpGet(url)
        print("got response:", response)
        value = extractValue(response)
        print("got value:", value)
        if value == '1':
            pin0.write_digital(1)
            display.show(Image.YES)
        elif value == '0':
            pin0.write_digital(0)
            display.show(Image.NO)
        delay(5000)
else:
    print("Connection to AP failed")
    display.show(Image.SAD)
► In Zwischenablage kopieren

Erklärungen zum Programmcode:
response = httpGet(url): Man entnimmt das Format für das Auslesen von Daten bei der Channel-Definition im ThingSpeak-Konto:
http://api.thingspeak.com/channels/CHANNNELID/feeds.json?api_key=READKEY&results=1
wobei CHANNELID und READKEY den aktuellen Werten entsprechen muss. Dabei liefert der Server als Response einen String im JSON-Format zurück. Dieser Teil beginnt und endet mit einer geschweiften Klammer
extractValue(response): Aus dem Response-String wird die JSON-Zeile herausgenommen und mit eval() in ein Python-Dictionary umgewandelt. Aus diesem entnimmt man dann die gewünschte Information. Falls irgend etwas schief geht und eine Exception auftritt, wird ein leerer String zurückgegeben.

Der RemoteManager ist eine Applikation, welche den Wert in der Tabelle auf 0 oder 1 setzt. Dazu muss gemäss den Angaben von ThinkSpeak der GET-Request
http://api.thingspeak.com/update?api_key=WRITEKEY&field1=VALUE
ausgeführt werden, wo WRITEKEY der Write API Key des Channels und VALUE entweder 0 oder 1 ist.
Zum Test kann man diese Zeile in einem Browser eingeben. Um das Schalten mit einem Mausklick auszuführen, gibt es verschiedene Möglichkeiten zur Ausführung des , GET-Requests:

# SmartHome6GUI.py
from tcpcom import *
from entrydialog import *

url_on = "http://api.thingspeak.com/update?api_key=0IKY85R1E3AO403U&field1=1"
url_off = "http://api.thingspeak.com/update?api_key=0IKY85R1E3AO403U&field1=0"

btn1 = ButtonEntry("On")
btn2 = ButtonEntry("Off")
pane1 = EntryPane(btn1, btn2)
dlg = EntryDialog(pane1)
dlg.setTitle("Remote Lamp Manager")

while not dlg.isDisposed():
    if btn1.isTouched():
        HTTPClient.getRequest(url_on)
    elif btn2.isTouched():
        HTTPClient.getRequest(url_off)
► In Zwischenablage kopieren

Nach dem Start erscheint ein Dialog mit zwei Buttons, die man klickt kann, um das Gerät ein- oder auszuschalten.

Vorteile:

Nachteile: