Los Ampelmännchen son un símbolo popular de Alemania del Este y se muestran en los semáforos para peatones en cada esquina. Incluso hay una cadena minorista con sede en Berlín que se inspira en su diseño.
Encontré un conjunto de señales Ampelmann de segunda mano en un mercadillo y quise controlarlas desde mi teléfono. Si quieres hacer lo mismo, ¡sigue leyendo!
Advertencia de salud: este proyecto utiliza alimentación de red de 220 V. No soy electricista. Siga estas instrucciones bajo su propio riesgo.
Quería construir algo con una estética berlinesa y que fuera divertido interactuar con los visitantes de mi casa. Desafortunadamente, nuestro número de visitantes disminuyó drásticamente este año, pero ¿todavía esperamos una gran reacción en 2021...?
Cada dispositivo en su red local tiene su propia dirección IP privada (por ejemplo, 192.168.1.20
). Algunos enrutadores, como FritzBox, también le permiten navegar por nombres de host locales, de modo que también se puede acceder a 192.168.1.20
en mydevice.fritz.box
. Para el semáforo, el nombre de host del dispositivo es traffic-light
, por lo que podemos visitarlo en http://traffic-light.fritz.box
.
La aplicación web es una aplicación de una sola página responsiva muy simple. Muestra:
El código está en el directorio /webapp. No se requieren dependencias externas, ya que solo se basa en funciones estándar del navegador, como transformación CSS, WebSockets y XHR. Puedes obtener una vista previa de la aplicación en ejecución aquí, aunque no controlará nada, ya que, por supuesto, no estás en mi LAN. Si lo visitaste desde la red local, por ejemplo en http://traffic-light.fritz.box
funcionará perfectamente.
Al cargar la página, la aplicación realiza una solicitud GET para encontrar el estado actual en /api/status
y luego abre una conexión WebSocket al servidor en el puerto 81
. Las actualizaciones de estado posteriores siempre se realizarán a través de WebSocket, para mantener sincronizados a varios clientes. Cada vez que llega un evento websocket, aplicamos los cambios a un único objeto state
global. Poco después, el método updateScreen()
aplica esos cambios al DOM.
Al iniciar, también detectamos si el usuario está en un dispositivo móvil o de escritorio, para manejar eventos táctiles o de clic. De hecho, usamos el evento touchend
para enviar comandos al servidor, porque esto funcionó de manera más confiable en el iPhone X. Deslizar hacia arriba desde la parte inferior de la pantalla para salir de Safari activaba el evento touchstart
, lo que hacía imposible salir de la aplicación sin encenderla. ¡la luz verde!
Finalmente, queremos reducir la carga en el servidor siempre que sea posible. Recuerde que el ESP8266 se ejecuta en un procesador de 80 MHz con sólo unos 50 kB de RAM. NO es un dispositivo robusto. Entonces, cuando el navegador está inactivo, desconectamos el websocket. Cuando se vuelve a abrir la pestaña o el navegador, verificamos nuevamente el estado y volvemos a conectar el WebSocket.
El ESP8266 está ocupado manejando solicitudes de API y código de tiempo, por lo que no tiene los recursos necesarios para atender la aplicación web en sí. Además, realizar cambios estéticos en la aplicación web es difícil si necesito conectarme físicamente al hardware cada vez que quiero aplicar una actualización.
El index.html de la aplicación web sigue el principio de aplicación de una sola página de que todo debe representarse mediante Javascript, lo que hace que el contenido HTML en sí sea muy pequeño. Como 550 bytes pequeños. Todo lo demás lo carga el navegador del cliente, sin necesidad de realizar más llamadas al servidor. Entonces, la aplicación web está alojada en su totalidad en GitHub Pages, una herramienta gratuita de alojamiento de sitios estáticos. Al presionar /index.html
en realidad se realiza una solicitud de proxy a las páginas de GitHub y se devuelve el resultado al navegador del cliente.
Ahora podemos cambiar cualquier cosa en la aplicación web y el servidor no se ve afectado. ¡Excelente! Bueno, casi...
La mayor parte del código de esta aplicación web se encuentra en los archivos CSS y JS, no en el propio index.html
. Los navegadores almacenan en caché los archivos cargados durante un período de tiempo indeterminado antes de volver a solicitarlos. Si index.html no cambia, pero hemos implementado una nueva versión de JS, ¿cómo sabrán nuestros clientes que necesitan cargar la nueva versión de JS?
Cuando enviamos cualquier versión nueva de nuestro código a la rama git master
, se ejecuta una acción de GitHub que ejecuta la implementación en las páginas de GitHub donde la página realmente se muestra al público. El truco aquí consiste en agregar el sufijo ?version=latest
al final de nuestros propios archivos CSS y JS, en index.html
. Antes de copiar el contenido a la rama gh-pages
, la acción usa el comando sed
para reemplazar ese " latest
" con el valor de la variable $GITHUB_SHA
, que en realidad es el último ID de confirmación en la rama master
. (por ejemplo, un valor como b43200422c4f5da6dd70676456737e5af46cb825
).
Luego, la próxima vez que un cliente visite la aplicación web, el navegador verá un valor nuevo y diferente después de ?version=
y solicitará el archivo JS o CSS nuevo y actualizado, que aún no habrá almacenado en caché.
Consulte el método setup(void)
en traffic-light-controller.ino
y la sección Código Arduino para saber cómo funciona en la práctica.
Decidí usar REST y WebSockets en conjunto. REST lo utilizan principalmente los clientes para controlar el servidor. Los WebSockets se utilizan para transmitir información de estado a los clientes. Hay muchas herramientas como Postman que te permiten experimentar fácilmente con las API REST, por lo que esto me pareció más conveniente.
API HTTP: consulte la documentación de Swagger aquí.
API WebSocket: la conexión websocket envía blobs JSON que la aplicación web utiliza para actualizar su estado interno. Un evento websocket puede contener uno o más campos para actualizar. Un ejemplo que contiene información medioambiental podría verse así:
{
"redTemperature" : 21.6 ,
"greenTemperature" : 22.7 ,
"greenHumidity" : 55 ,
"redHumidity" : 59
}
Actualmente no se envían datos desde el cliente al servidor a través del websocket, aunque esto es posible.
El código arduino está todo dentro de un único archivo que incluye comentarios explicativos.
Comienza con un conjunto de definiciones para ubicaciones de pines, importaciones de bibliotecas y valores codificados para cosas como tipos de contenido HTTP y valores de códigos de respuesta. A continuación hay un conjunto de variables que pueden cambiar en tiempo de ejecución, todas con el prefijo de guión bajo. Aquí también se inicializan algunos objetos, incluidos los del servidor web, el servidor de socket web, el cliente WiFi y los sensores de temperatura. El "reloj del sistema" lo mantiene el campo _currentMillis
.
Después del arranque, se ejecuta el método setup(void)
. Después de realizar algunas configuraciones de pines, crea las asignaciones necesarias para los puntos finales REST e inicia los servidores a escuchar las solicitudes de los clientes. El método loop(void)
se encarga de todo lo demás. En cada ciclo, procesa cualquier solicitud web pendiente, actualiza el ciclo de ritmo y lee los sensores si es necesario. Si estamos en modo fiesta, establecerá el estado actual de flash/pulso.
El ritmo (para el modo fiesta) está codificado para reproducir la secuencia en el campo RHYTHM_PATTERN
, pero en teoría podría cambiarse en tiempo de ejecución a cualquier otra cosa. Cada vez que llamamos al método rhythm()
, usamos los valores actuales _bpm
y _currentMillis
para determinar en qué posición deberíamos estar en el patrón. Esto se almacena en el campo _rhythmStep
.
Durante el patrón de ritmo, hay períodos en los que ambos relés están realmente apagados. Pero debido a que las luces son bombillas incandescentes, no comienzan ni dejan de emitir luz instantáneamente. Parece que las bombillas tardan unos 1,7 segundos en encenderse o apagarse por completo. Entonces, al agregar un período dentro del patrón en el que ambos se apagan, terminamos con un patrón de pulsaciones suaves a medida que las bombillas se calientan y se enfrían.
En el método partyFlash()
, obtenemos el elemento del patrón que se supone que se muestra actualmente (o ambos deben estar apagados) y llamamos lightSwitch(...)
con los parámetros apropiados. lightSwitch(...)
a su vez llama sendToWebSocketClients(...)
para que todos los clientes conectados se actualicen al nuevo estado.
Si el usuario simplemente hace clic en una de las luces para encenderla o apagarla, el proceso es similar, pero se maneja como una solicitud REST. Se llama a uno de los métodos handleX
, que valida la solicitud y, a su vez, llama a lightSwitch(...)
.
En intervalos menos frecuentes, verificamos la temperatura de los dos gabinetes y también la enviamos a través de WebSocket a todos los clientes. Actualmente, esto solo se usa con fines informativos, pero podría usarse para desactivar las luces cuando la temperatura excede algún límite de seguridad.
Crédito a @mrcosta por su ayuda en la revisión de este artículo.