Portada! Creando un reloj binario (tercera parte)

unreal4u

I solve problems.
Miembro del Equipo
ADMIN
Se incorporó
2 Octubre 2005
Mensajes
13.442
Demo del setup general


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:

Breadboard con todos los componentes puestos


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!
 

Boogiepop

Fanático
Se incorporó
2 Septiembre 2005
Mensajes
1.053
Vamos por los 25 likes, gracias por mantenernos informados y muy bien explicado todo.

Suerte
 

unreal4u

I solve problems.
Miembro del Equipo
ADMIN
Se incorporó
2 Octubre 2005
Mensajes
13.442
Vamos por los 25 likes, gracias por mantenernos informados y muy bien explicado todo.

Suerte

gracias jajajaj! No serán los artículos más populares del planeta, pero al menos quedarán disponibles en internet y en español que guías de este tipo es muy pero muy difícil de encontrar en otros idiomas que no sea inglés.

Puedo btw adelantar que ayer terminé el proyecto pero todavía tengo para unas 3 semanas de guías pq el proyecto me salió largo y bastante complicado jajajaj

Así que atentos al resultado final, cuidado Xiaomi que ahí viene @unreal4u ! :p

Saludos.
 
Subir