Les Ampelmännchen sont un symbole populaire de l'Allemagne de l'Est, qui apparaît sur les feux de circulation pour piétons à chaque coin de rue. Il existe même une chaîne de vente au détail basée à Berlin qui s'inspire de leur design.
Je suis tombé sur un ensemble de signaux Ampelmann d'occasion dans un marché aux puces et je voulais les contrôler depuis mon téléphone. Si vous souhaitez faire de même, continuez à lire !
Avertissement de santé : ce projet utilise une alimentation secteur de 220 V. Je ne suis pas électricien. Suivez ces instructions à vos propres risques.
Je voulais construire quelque chose avec une esthétique berlinoise et avec lequel les visiteurs de chez moi pourraient interagir avec plaisir. Malheureusement, notre nombre de visiteurs a fortement diminué cette année, mais vous espérez toujours une grande réaction en 2021... ?
Chaque appareil de votre réseau local possède sa propre adresse IP privée (par exemple 192.168.1.20
). Certains routeurs tels que la FritzBox vous permettent également de naviguer par noms d'hôtes locaux, de sorte que 192.168.1.20
soit également accessible sur mydevice.fritz.box
. Pour le feu de circulation, le nom d'hôte de l'appareil est traffic-light
afin que nous puissions le visiter sur http://traffic-light.fritz.box
.
La webapp est une application réactive très simple d’une seule page. Il montre :
Le code se trouve dans le répertoire /webapp. Aucune dépendance externe n'est requise puisqu'elle repose uniquement sur les fonctionnalités standard du navigateur, telles que la transformation CSS, WebSockets et XHR. Vous pouvez prévisualiser l'application en cours d'exécution ici même si elle ne contrôlera rien, puisque bien sûr vous n'êtes pas sur mon réseau local. Si vous le visitiez depuis le réseau local, par exemple sur http://traffic-light.fritz.box
cela fonctionnerait pleinement.
Lors du chargement de la page, l'application effectue une requête GET pour trouver l'état actuel sur /api/status
, puis ouvre une connexion WebSocket au serveur sur le port 81
. Les mises à jour de statut ultérieures seront toujours effectuées via WebSocket, afin de synchroniser plusieurs clients. Chaque fois qu'un événement Websocket arrive, nous appliquons les modifications à un seul objet state
global. Peu de temps après, la méthode updateScreen()
applique ces modifications au DOM.
Au démarrage, nous détectons également si l'utilisateur est sur un appareil mobile ou de bureau, pour gérer soit les événements tactiles, soit les événements de clic. Nous utilisons en fait l'événement touchend
pour envoyer des commandes au serveur, car cela fonctionnait de manière plus fiable sur l'iPhone X. Glisser vers le haut depuis le bas de l'écran pour quitter Safari déclenchait l'événement touchstart
, rendant impossible la sortie de l'application sans l'allumer. le feu vert !
Enfin, nous souhaitons réduire la charge sur le serveur autant que possible. N'oubliez pas que l'ESP8266 fonctionne sur un processeur 80 MHz avec seulement 50 Ko de RAM environ. Ce n'est PAS un appareil costaud. Ainsi lorsque le navigateur est inactif, on déconnecte le websocket. Lorsque l'onglet ou le navigateur se rouvre, nous vérifions à nouveau l'état et reconnectons le WebSocket.
L'ESP8266 est occupé à gérer les requêtes API et le code de synchronisation, il ne dispose donc pas des ressources nécessaires pour servir l'application Web elle-même. De plus, il est difficile d'apporter des modifications esthétiques à l'application Web si je dois me connecter physiquement au matériel à chaque fois que je souhaite appliquer une mise à jour.
Le fichier index.html de l'application Web suit le principe d'application d'une seule page selon lequel tout doit être rendu par Javascript, ce qui rend le contenu HTML lui-même très petit. Comme 550 octets de petite taille. Tout le reste est chargé par le navigateur du client, sans qu'il soit nécessaire de faire d'autres appels au serveur. La webapp est donc en fait hébergée dans son intégralité sur GitHub Pages, un outil d'hébergement de sites statiques gratuit. Frapper /index.html
effectue en fait une demande de proxy vers les pages GitHub et renvoie le résultat au navigateur client.
Nous pouvons désormais modifier n'importe quoi dans l'application Web et le serveur n'est pas affecté. Super! Enfin, presque...
La plupart du code de cette application Web se trouve dans les fichiers CSS et JS, et non dans index.html
lui-même. Les navigateurs mettent en cache tous les fichiers chargés pendant une période indéterminée avant de les redemander. Si index.html ne change pas, mais que nous avons déployé une nouvelle version JS, comment nos clients sauront-ils qu'ils doivent charger la nouvelle version JS ?
Lorsque nous poussons une nouvelle version de notre code vers la branche git master
, une action GitHub s'exécute, qui exécute le déploiement sur les pages GitHub où la page est réellement servie au public. L'astuce ici consiste à ajouter le suffixe ?version=latest
à la fin de nos propres fichiers CSS et JS, dans le index.html
. Avant de copier le contenu dans la branche gh-pages
, l'action utilise la commande sed
pour remplacer ce " latest
" par la valeur de la variable $GITHUB_SHA
, qui est en fait le dernier ID de validation sur la branche master
. (par exemple une valeur comme b43200422c4f5da6dd70676456737e5af46cb825
).
Ensuite, la prochaine fois qu'un client visitera l'application Web, le navigateur verra une nouvelle valeur différente après le ?version=
et demandera le nouveau fichier JS ou CSS mis à jour, qu'il n'aura pas déjà mis en cache.
Voir la méthode setup(void)
dans traffic-light-controller.ino
et la section Code Arduino pour savoir comment cela fonctionne dans la pratique.
J'ai décidé d'utiliser REST et WebSockets en tandem. REST est principalement utilisé par les clients pour contrôler le serveur. Les WebSockets sont utilisés pour diffuser des informations d'état aux clients. Il existe de nombreux outils comme Postman qui vous permettent d'expérimenter facilement les API REST, j'ai donc trouvé cela plus pratique.
API HTTP : reportez-vous à la documentation Swagger ici.
API WebSocket : la connexion websocket envoie des blobs JSON que l'application Web utilise pour mettre à jour son état interne. Un événement websocket peut contenir un ou plusieurs champs à mettre à jour. Un exemple contenant des informations environnementales pourrait ressembler à :
{
"redTemperature" : 21.6 ,
"greenTemperature" : 22.7 ,
"greenHumidity" : 55 ,
"redHumidity" : 59
}
Aucune donnée n'est actuellement envoyée du client au serveur via le websocket, bien que cela soit possible.
Le code Arduino se trouve dans un seul fichier qui comprend des commentaires explicatifs.
Cela commence par un ensemble de définitions pour les emplacements des broches, les importations de bibliothèques et les valeurs codées en dur pour des éléments tels que les types de contenu HTTP et les valeurs des codes de réponse. Viennent ensuite un ensemble de variables qui peuvent changer au moment de l'exécution, toutes préfixées par des traits de soulignement. Quelques objets sont également initialisés ici, notamment ceux du serveur Web, du serveur de socket Web, du client WiFi et des capteurs de température. L'"horloge système" est maintenue par le champ _currentMillis
.
Après le démarrage, la méthode setup(void)
s'exécute. Après avoir configuré les broches, il crée les mappages nécessaires pour les points de terminaison REST et démarre les serveurs à l'écoute des demandes des clients. La méthode loop(void)
est en charge de tout le reste. À chaque cycle, il traite toutes les requêtes Web en attente, met à jour le cycle rythmique et lit les capteurs si nécessaire. Si nous sommes en mode fête, cela définira l'état actuel du flash/impulsion.
Le rythme (pour le mode fête) est codé en dur pour jouer la séquence dans le champ RHYTHM_PATTERN
, mais en théorie, il pourrait être modifié au moment de l'exécution par autre chose. Chaque fois que nous appelons la méthode rhythm()
, nous utilisons les valeurs actuelles _bpm
et _currentMillis
pour déterminer quelle position nous devrions occuper dans le motif. Ceci est stocké dans le champ _rhythmStep
.
Pendant le motif rythmique, il y a des périodes où les deux relais sont réellement désactivés. Mais comme les lumières sont des ampoules à incandescence, elles ne démarrent ni n’arrêtent d’émettre de la lumière instantanément. Il semble que les ampoules mettent environ 1,7 seconde pour s’allumer ou s’éteindre complètement. Ainsi, en ajoutant une période dans le motif pendant laquelle les deux sont éteintes, nous obtenons un motif de pulsations douces pendant que les ampoules se réchauffent et refroidissent.
Dans la méthode partyFlash()
, nous obtenons l'élément de motif qui est censé être actuellement affiché (ou les deux doivent être désactivés) et appelons lightSwitch(...)
avec les paramètres appropriés. lightSwitch(...)
appelle à son tour sendToWebSocketClients(...)
afin que tous les clients connectés soient mis à jour vers le nouvel état.
Si l'utilisateur clique simplement sur l'une des lumières pour l'allumer ou l'éteindre, le processus est similaire, mais traité comme une requête REST. L'une des méthodes handleX
est appelée, qui valide la demande et appelle à son tour lightSwitch(...)
.
À des intervalles moins fréquents, nous vérifions la température des deux boîtiers et l'envoyons également via WebSocket à tous les clients. Ceci n'est actuellement utilisé qu'à des fins d'information, mais il pourrait être utilisé pour désactiver les lumières lorsque la température dépasse une certaine limite de sécurité.
Merci à @mrcosta pour son aide dans la révision de cet article.