- Se incorporó
- 2 Octubre 2005
- Mensajes
- 13.601
En las primeras dos partes de esta serie de artículos vimos algunas opciones de reloj, mientras que en la segunda entrega creé la lista de materiales a ocupar y los encargué. Hoy vamos a ver un poco más en profundidad la parte que hará funcionar el reloj: la parte de programación propiamente tal!
Parte 4: Software
Antes de siquiera encargar las piezas, tuve que obviamente revisar si mi idea era factible o no de realizar en código. Mi idea es que tenga que invertir la menor cantidad de tiempo posible en actualizaciones a futuro, y nada mejor para eso que ESPHome: es un framework que me permite realizar algo una vez y de ahí olvidarme por completo. Si es que necesito alguna actualización saldrá por si sola, pero no me tengo que preocupar ni de la comunicación con MQTT ni tampoco de los detalles de implementación.
Sin embargo, como lo que quiero hacer es bastante avanzado, tendré que ver qué tal lo pueda llevar a cabo.
Mi fallback será siempre que puedo ocupar Arduino IDE y hacerlo yo mismo. El código del reloj será quizás el más complicado pero aún así será bastante fácil, como muestra creé el siguiente algoritmo usando MicroBits, que es una plataforma que le puede enseñar a jóvenes a introducirse en el mundo de la programación:
Una vez que me llegaron las piezas, me puse manos a la obra y empecé con lo básico.
En la casa he instalado hasta el momento dos dispositivos con el WT32-ETH01 y han funcionado genial. Sin embargo, como este dispositivo no tiene soporte para I²C lo tuve que cambiar por un ESP32 DevKitC v4.
Como ya mencioné usé para esto ESPHome: es una herramienta que permite programar un ESP32 o un ESP8266 mediante YAML y ellos se encargan de crear, compilar y flashear el dispositivo (OTA), todo en tu red local sin que el aparato tenga que salir de tu red local. Además de esto, como es una herramienta adicional de Home Assistant también se integra muy bien con este último. No es necesario hacer nada para declarar sensores o configuraciones.
No iré en profundidad de cómo instalar o ejecutar ESPHome ya que eso da para otro artículo más (con 25 likes haré un artículo al respecto!) pero el código consiste un poco de boilerplate en primer lugar, considerando el hecho de que para este proyecto estoy utilizando un ESP32 DevKitC en vez de un WT32-ETH01:
YAML:
esphome:
name: binary-clock
friendly_name: binary-clock
comment: Controls a 5x6 matrix of lights and several sensors that interact with each other
esp32:
board: nodemcu-32s
framework:
type: arduino
# Enable logging
logger:
# Enable Home Assistant API
api:
encryption:
key: "XXX"
ota:
password: "XXX"
wifi:
ssid: !secret wifi_ssid
password: !secret wifi_password
# Enable fallback hotspot (captive portal) in case wifi connection fails
ap:
ssid: "Binary-Clock Fallback Hotspot"
password: "XXX"
captive_portal:
Este código simplemente define el tipo de board con el que estamos trabajando y define las credenciales de la WiFi y otros settings como la integración con Home Assistant.
Acto seguido incluimos también algunas configuraciones básicas que ocuparemos:
YAML:
mqtt:
broker: !secret mqtt_host
username: !secret mqtt_user
password: !secret mqtt_password
client_id: binary-clock
id: mqtt_client
font:
- file: "gfonts://Roboto"
id: font_time
size: 50
- file:
type: gfonts
family: Roboto
weight: 400
id: font_date
size: 15
- file:
type: gfonts
family: Arimo
id: font_notifications
size: 13
globals:
# Clock brightness is a number that goes from 0 (do NOT dim) up to 100 (make the led matrix dimmer)
# Its value depend on the brightness of the light sensor
- id: clock_brightness
type: int
restore_value: yes
# TODO include the measurements of the light sensor to determine LED brightness
# TODO It looks good up to -100, but will have to recalibrate once I build the case
initial_value: '0'
- id: mqtt_notification_counter
type: int
restore_value: no
initial_value: '0'
time:
- platform: homeassistant
id: local_time
# Denotes Amsterdam: https://github.com/nayarsystems/posix_tz_db/blob/master/zones.csv
timezone: CET-1CEST,M3.5.0,M10.5.0/3
i2c:
sda: 21
scl: 22
text_sensor:
- platform: version
name: version
- platform: mqtt_subscribe
name: "Public notifications"
id: broker_notifications
internal: True
topic: notifications/public
on_raw_value:
then:
- lambda: |-
id(mqtt_notification_counter)++;
- display.page.show: notification_page
- component.update: oled_display
sensor:
- platform: uptime
name: "uptime"
update_interval: 60s
- platform: wifi_signal
name: "WiFi signal"
update_interval: 10s
Aquí empiezan las partes interesantes: ya que queremos utilizar la pequeña pantalla para mostrar notificaciones, pensé que lo mejor sería tener una suscripción a mi broker cosa que pueda imprimir cualquier cosa ahí y mostrarlo en pantalla. El tópico al cual me suscribiré será
notifications/public
ya que el dispositivo estará a vista y paciencia de todas las visitas. Quizás en el futuro tendré otro tópico que también me muestre información más privada (ejemplo: Mac address de un nuevo dispositivo en la red WiFi de la casa o estados de presencia que no quiero que las visitas vean) en otra pieza pero todavía no me decido.Luego también aprovecho de decirle a ESPHome que me baje el font Roboto desde Google Fonts con ciertas características, lo mismo con el Font Arimo. Estos serán los fonts que se mostrarán en la pantalla OLED con distintos propósitos.
Después necesito saber qué hora es en el ESP32 mismo: para esto le digo que le vaya a preguntar a Home Assistant la hora, y aprovecho de pasarle el string que identifica el TimeZone donde vivo. Para America/Santiago esto sería
<-04>4<-03>,M9.1.6/24,M4.1.6/24
.Finalmente, también configuro los pines a los que la pantalla estará conectada. Los puertos I²C del ESP32 son GPIO21 para SDA (señal de datos, también conocido como SDI) y GPIO22 para SCL (señal de reloj, también conocido como SCK). Para mayor información sobre qué es I²C y cómo funciona, refiérase a este artículo que fue el que yo ocupé para saber qué conectar dónde.
Finalmente, creo algunos sensores: el primero simplemente me publica en Home Assistant qué versión estoy corriendo, tiene una salida del tipo
2023.4.3 May 2 2023, 22:40:41
y me puede servir en el futuro saber este pequeño dato de forma fácil.El segundo sensor es un sensor interno de ESPHome y no será publicado de vuelta a Home Assistant: me revisa el tópico
notifications/public
y cada vez que hay un nuevo mensaje ahí, incrementa el contador global de notificaciones y luego le pasa la pelota a una 'página': la hora en la pantalla OLED está en una página, y las notificaciones es básicamente otra página. Esto me permite cambiar rápidamente entre las dos.El tercer y cuarto sensor son sensores más simples que publican información sobre el uptime y qué tan fuerte es la señal de la WiFi.
Ya con eso listo, puedo proseguir a configurar los dispositivos que estarán conectados:
YAML:
- platform: adc
pin: 33
name: "Brightness"
update_interval: 16s
device_class: illuminance
unit_of_measurement: v
filters:
- lambda: |-
ESP_LOGD("clock_brightness", "Current value is %.04f", x);
// TODO Calculate brightness factor, store value in id(clock_brightness)
return x;
- platform: dht
pin: 17
model: AM2302
temperature:
name: "Temperature"
accuracy_decimals: 1
humidity:
name: "Humidity"
accuracy_decimals: 1
update_interval: 60s
El primer sensor es el de iluminación: todavía no tengo implementado esta parte del código, ya que el factor y fórmula final dependerá de la posición del reloj en el living y también dependerá del case, pero la idea es que la iluminación ambiental me calculará la intensidad a la que brillará la matriz. Básicamente tendré que calibrarlo a mano una vez que tenga todas las piezas armadas y listas.
El segundo sensor es el sensor de humedad y temperatura: cada 60 segundos simplemente publicará estos datos.
YAML:
- platform: rotary_encoder
name: "Rotary Encoder"
pin_a: 16
pin_b: 5
resolution: 1
accuracy_decimals: 0
on_clockwise:
- lambda: |-
static int effect_index = 0;
id(led_matrix_light).turn_on().set_effect(id(led_matrix_light).get_effects().at(effect_index)->get_name().c_str()).perform();
effect_index += 1;
if (effect_index < id(led_matrix_light).get_effects().size()) {
effect_index = -1;
id(led_matrix_light).turn_off().perform();
}
return;
on_anticlockwise:
- lambda: |-
static int effect_index = 0;
id(led_matrix_light).turn_on().set_effect(id(led_matrix_light).get_effects().at(effect_index)->get_name().c_str()).perform();
effect_index -= 1;
if (effect_index < id(led_matrix_light).get_effects().size()) {
effect_index = id(led_matrix_light).get_effects().size() + 1;
id(led_matrix_light).turn_off().perform();
}
return;
Finalmente, tengo la implementación del rotary encoder, aunque pequeño disclaimer: esta parte todavía no funciona bien. Cada tantas vueltas se me queda pegado todo el sistema y por el momento no tengo idea si es porque hay interferencia entre los cables o es un bug en mi código. También pueden ser contactos sueltos debido a que en este momento tengo todo en un breadboard:
Una vez que arme el producto final le echaré un nuevo vistazo a esta parte.
Finalmente, lo último antes de cerrar este artículo que ya se me alargó demasiado son los sensores que me faltan: el de movimiento y el botón del rotary encoder:
YAML:
binary_sensor:
- platform: gpio
pin: 27
name: "PIR sensor"
device_class: motion
id: pir_motion_sensor
filters:
# TODO Will probably be in the area of 60s - 120s when finished
- delayed_off: 15s
- lambda: |-
if (x) {
// TODO Resume with the effect we ended the last time
id(led_matrix_light).turn_on().set_effect("Easy Binary clock").perform();
} else {
id(led_matrix_light).turn_off().perform();
}
return x;
- platform: gpio
pin: 13
id: rotary_button_press
name: "Rotary Button Press"
filters:
- invert
En la siguiente entrega de esta serie de artículos, se viene la parte más entretenida del proyecto que son fotos de la construcción para el primer demo!