Die Ampelmännchen sind ein beliebtes Symbol aus Ostdeutschland, das an jeder Straßenecke an den Fußgängerampeln zu sehen ist. Es gibt sogar eine Berliner Einzelhandelskette, die sich von ihrem Design inspirieren lässt.
Auf einem Flohmarkt bin ich auf einen Satz gebrauchter Ampelmann-Signale gestoßen und wollte diese von meinem Telefon aus steuern. Wenn Sie das Gleiche tun möchten, lesen Sie weiter!
Gesundheitswarnung: Dieses Projekt verwendet 220 V Netzstrom. Ich bin kein Elektriker. Befolgen Sie diese Anweisungen auf eigenes Risiko.
Ich wollte etwas mit Berliner Ästhetik bauen und es den Besuchern meines Hauses Spaß machen, damit zu interagieren. Leider sind unsere Besucherzahlen dieses Jahr stark zurückgegangen, aber wir hoffen trotzdem auf eine tolle Resonanz im Jahr 2021...?
Jedes Gerät in Ihrem lokalen Netzwerk verfügt über eine eigene private IP-Adresse (z. B. 192.168.1.20
). Bei manchen Routern wie der FritzBox lässt sich auch nach lokalen Hostnamen suchen, sodass 192.168.1.20
auch unter mydevice.fritz.box
erreichbar ist. Für die Ampel lautet der Hostname des Geräts traffic-light
, sodass wir ihn unter http://traffic-light.fritz.box
besuchen können.
Die Webapp ist eine sehr einfache responsive Single-Page-Anwendung. Es zeigt:
Der Code befindet sich im Verzeichnis /webapp. Es sind keine externen Abhängigkeiten erforderlich, da es lediglich auf Standardbrowserfunktionen wie CSS-Transformation, WebSockets und XHR basiert. Sie können hier eine Vorschau der laufenden Anwendung anzeigen, obwohl diese nichts steuert, da Sie sich natürlich nicht in meinem LAN befinden. Wenn Sie es aus dem lokalen Netzwerk aufrufen, z. B. unter http://traffic-light.fritz.box
würde es vollständig funktionieren.
Beim Laden der Seite stellt die Anwendung eine GET-Anfrage, um den aktuellen Status unter /api/status
zu ermitteln, und öffnet dann eine WebSocket-Verbindung zum Server an Port 81
. Nachfolgende Statusaktualisierungen erfolgen immer über den WebSocket, um mehrere Clients synchron zu halten. Jedes Mal, wenn ein Websocket-Ereignis eintrifft, wenden wir die Änderungen auf ein einzelnes globales state
an. Kurz darauf wendet die updateScreen()
-Methode diese Änderungen auf das DOM an.
Beim Start erkennen wir auch, ob sich der Benutzer auf einem mobilen oder Desktop-Gerät befindet, um entweder Berührungsereignisse oder Klickereignisse zu verarbeiten. Tatsächlich verwenden wir das touchend
-Ereignis, um Befehle an den Server zu senden, da touchstart
auf dem iPhone grünes Licht!
Schließlich wollen wir die Belastung des Servers wo immer möglich reduzieren. Denken Sie daran, dass der ESP8266 auf einem 80-MHz -Prozessor mit nur etwa 50 KB RAM läuft. Es ist KEIN bulliges Gerät. Wenn der Browser also inaktiv ist, trennen wir den WebSocket. Wenn die Registerkarte oder der Browser erneut geöffnet wird, prüfen wir erneut den Status und verbinden den WebSocket erneut.
Der ESP8266 ist damit beschäftigt, API-Anfragen und Timing-Code zu verarbeiten, sodass er nicht über die notwendigen Ressourcen verfügt, um die Webanwendung selbst zu bedienen. Außerdem ist es schwierig, kosmetische Änderungen an der Webanwendung vorzunehmen, wenn ich jedes Mal, wenn ich ein Update anwenden möchte, eine physische Verbindung zur Hardware herstellen muss.
Die index.html der Webanwendung folgt dem Prinzip der Einzelseitenanwendung, dass alles durch Javascript gerendert werden sollte, wodurch der HTML-Inhalt selbst sehr klein wird. Etwa 550 Byte klein. Alles andere wird vom Browser des Clients geladen, ohne dass weitere Aufrufe an den Server erforderlich sind. Die Webanwendung wird also tatsächlich vollständig auf GitHub Pages gehostet, einem kostenlosen Tool zum Hosten statischer Websites. Durch Klicken auf /index.html
wird tatsächlich eine Proxy-Anfrage an GitHub-Seiten gestellt und das Ergebnis an den Client-Browser zurückgegeben.
Jetzt können wir alles in der Webanwendung ändern, und der Server bleibt davon unberührt. Großartig! Na ja, fast...
Der größte Teil des Codes für diese Webanwendung befindet sich in den CSS- und JS-Dateien, nicht in index.html
selbst. Browser speichern alle geladenen Dateien für einen unbestimmten Zeitraum zwischen, bevor sie erneut angefordert werden. Wenn sich index.html nicht ändert, wir aber eine neue JS-Version bereitgestellt haben, woher wissen unsere Kunden dann, dass sie die neue JS-Version laden müssen?
Wenn wir eine neue Version unseres Codes an den Git- master
-Zweig übertragen, wird eine GitHub-Aktion ausgeführt, die die Bereitstellung auf GitHub-Seiten ausführt, wo die Seite tatsächlich der Öffentlichkeit bereitgestellt wird. Der Trick besteht darin, das Suffix ?version=latest
an das Ende unserer eigenen CSS- und JS-Dateien in der index.html
anzuhängen. Bevor der Inhalt in den gh-pages
-Zweig kopiert wird, verwendet die Aktion den Befehl sed
um „ latest
“ durch den Wert der Variablen $GITHUB_SHA
zu ersetzen, bei der es sich tatsächlich um die letzte Commit-ID im master
-Zweig handelt. (z. B. ein Wert wie b43200422c4f5da6dd70676456737e5af46cb825
).
Wenn ein Client das nächste Mal die Webanwendung besucht, sieht der Browser nach dem ?version=
einen neuen, anderen Wert und fordert die neue, aktualisierte JS- oder CSS-Datei an, die er noch nicht zwischengespeichert hat.
Sehen Sie sich die Methode setup(void)
in traffic-light-controller.ino
und den Abschnitt Arduino-Code an, um zu erfahren, wie dies in der Praxis funktioniert.
Ich habe mich entschieden, sowohl REST als auch WebSockets gleichzeitig zu verwenden. REST wird hauptsächlich von Clients zur Steuerung des Servers verwendet. WebSockets werden verwendet, um Statusinformationen an Clients zu senden. Es gibt viele Tools wie Postman, mit denen Sie problemlos mit REST-APIs experimentieren können, daher fand ich dies praktischer.
HTTP-API: Weitere Informationen finden Sie in der Swagger-Dokumentation hier.
WebSocket-API: Die WebSocket-Verbindung sendet JSON-Blobs, die die Webanwendung verwendet, um ihren internen Status zu aktualisieren. Ein Websocket-Ereignis kann ein oder mehrere zu aktualisierende Felder enthalten. Ein Beispiel mit Umweltinformationen könnte wie folgt aussehen:
{
"redTemperature" : 21.6 ,
"greenTemperature" : 22.7 ,
"greenHumidity" : 55 ,
"redHumidity" : 59
}
Derzeit werden keine Daten vom Client über den Websocket an den Server gesendet, obwohl dies möglich ist.
Der Arduino-Code befindet sich vollständig in einer einzigen Datei, die erläuternde Kommentare enthält.
Es beginnt mit einer Reihe von Definitionen für Pin-Positionen, Bibliotheksimporten und fest codierten Werten für Dinge wie HTTP-Inhaltstypen und Antwortcodewerten. Darauf folgt eine Reihe von Variablen, die sich zur Laufzeit ändern können und denen jeweils Unterstriche vorangestellt sind. Hier werden auch einige Objekte initialisiert, unter anderem für den Webserver, den Web-Socket-Server, den WLAN-Client und die Temperatursensoren. Die „Systemuhr“ wird durch das Feld _currentMillis
verwaltet.
Nach dem Booten wird die Methode setup(void)
ausgeführt. Nach der Pin-Einrichtung werden die erforderlichen Zuordnungen für die REST-Endpunkte erstellt und die Server werden gestartet, um auf Client-Anfragen zu warten. Für alles andere ist die Methode loop(void)
zuständig. In jedem Zyklus verarbeitet es alle ausstehenden Webanfragen, aktualisiert den Rhythmuszyklus und liest bei Bedarf die Sensoren aus. Wenn wir uns im Partymodus befinden, wird der aktuelle Blitz-/Impulsstatus festgelegt.
Der Rhythmus (für den Partymodus) ist fest codiert, um die Sequenz im Feld RHYTHM_PATTERN
abzuspielen, aber theoretisch könnte er zur Laufzeit in alles andere geändert werden. Jedes Mal, wenn wir die Methode rhythm()
aufrufen, verwenden wir die aktuellen Werte _bpm
und _currentMillis
um zu ermitteln, an welcher Position wir uns im Muster befinden sollten. Dies wird im Feld _rhythmStep
gespeichert.
Während des Rhythmusmusters gibt es Phasen, in denen tatsächlich beide Relais ausgeschaltet sind. Da es sich bei den Lichtern jedoch um Glühlampen handelt, schalten sie sich nicht sofort ein oder hören auf, Licht auszusenden. Es sieht so aus, als ob es etwa 1,7 Sekunden dauert, bis die Glühbirnen vollständig ein- oder ausgeschaltet sind. Indem wir also innerhalb des Musters einen Zeitraum hinzufügen, in dem beide ausgeschaltet sind, erhalten wir am Ende ein sanftes pulsierendes Muster, während die Glühbirnen aufwärmen und abkühlen.
In der Methode partyFlash()
holen wir uns das Musterelement, das aktuell angezeigt werden soll (oder beide sollen ausgeschaltet sein) und rufen lightSwitch(...)
mit den entsprechenden Parametern auf. lightSwitch(...)
ruft wiederum sendToWebSocketClients(...)
auf, sodass alle verbundenen Clients auf den neuen Status aktualisiert werden.
Wenn der Benutzer einfach auf eines der Lichter klickt, um es ein- oder auszuschalten, ist der Vorgang ähnlich, wird jedoch als REST-Anfrage behandelt. Eine der handleX
-Methoden wird aufgerufen, die die Anfrage validiert und wiederum lightSwitch(...)
aufruft.
In selteneren Abständen überprüfen wir die Temperatur der beiden Gehäuse und senden diese ebenfalls per WebSocket an alle Clients. Dies wird derzeit nur zu Informationszwecken verwendet, könnte jedoch dazu verwendet werden, die Lichter zu deaktivieren, wenn die Temperatur einen bestimmten Sicherheitsgrenzwert überschreitet.
Wir danken @mrcosta für seine Hilfe bei der Durchsicht dieses Artikels.