Geekmagic Ultra: Mini estación meteorológica compatible con ESPHome

Geekmagic Ultra: Mini estación meteorológica compatible con ESPHome

Después de pasar el asistente de Xiaozhi a ESPHome, me ha saltado este pequeño aparato, el Geekmagic Ultra, un dispositivo con ESP8266 y que permite usarlo de forma nativa como estación meteorológica o bien, ponerlo con ESPHome para poder hacer lo que queramos e integrarlo en nuestro Home Assistant.

Vídeo de la review / tutorial del Geekmagic Ultra con ESPHome

Comandos usados

Para aquellos que queréis tener la lista de comandos listos para copiar y pegar, podéis ver el vídeo y aquí os paso los códigos:

Antes de nada, os muestro el pinout del Geekmagic Ultra:

Ahora, deberíais usar las pinzas y el USB-Uart que muestro en el vídeo.

Para dumpear el esp8266:

Linux:

esptool.py -p /dev/ttyUSBX -b 115200 read_flash 0 0x400000 backup_esp8266.bin

Donde ttyUSBX es el número del puerto que ha pillado el dispositivo USB-UART

Windows:

esptool.py -p COMX -b 115200 read_flash 0 0x400000 backup_esp8266.bin

Donde COMX es el COM de vuestro serie

Este es el último código de ESPHome que muestro en el vídeo (recordad adaptarlo a los nombres de vuestros dispositivos/entidades):

esphome:
  name: geekmagic
  friendly_name: Mini Display TV

esp8266:
  board: esp01_1m

logger:
api:
web_server:
  port: 80

ota:
  - platform: esphome

wifi:
  ssid: !secret wifi_ssid
  password: !secret wifi_password
  ap:
    ssid: "Mini-Display-Tv"
    password: "12345678"

captive_portal:

spi:
  clk_pin: GPIO14
  mosi_pin: GPIO13
  interface: hardware
  id: spihwd

output:
  - platform: esp8266_pwm
    pin: GPIO05
    frequency: 200 Hz
    inverted: true
    id: backlight_pwm

light:
  - platform: monochromatic
    output: backlight_pwm
    name: "Backlight"
    id: backlight
    restore_mode: RESTORE_AND_ON

# ---------------------------------------------------------
# FUENTES CORREGIDAS (Sin duplicados)
# ---------------------------------------------------------
font:
  - file: "gfonts://Roboto"
    id: font_main
    size: 20
    glyphs: "0123456789.,: %°CW"

  - file: "gfonts://Roboto"
    id: font_small
    size: 14
    # He añadido la 'K' mayúscula que faltaba para el "OK"
    glyphs: " !"%()+,-.:0123456789ABCDEFILMOPRSTUWKabcdefghilmnorstuy°Ññ"

  - file: "materialdesignicons-webfont.ttf"
    id: icon_font
    size: 24
    glyphs: [
      "U000F0339", # Rayo
      "U000F050F", # Termometro
      "U000F0591", # Lavadora
      "U000F042D", # Impresora
      "U000F011B"  # Gato
    ]

color:
  - id: c_bg
    hex: "000000" # Negro Fondo
  - id: c_white
    hex: "FFFFFF"
  - id: c_gray
    hex: "333333" # Gris para fondos de barras
  - id: c_accent
    hex: "00D2FF" # Azul Cyan
  - id: c_warn
    hex: "FF9800" # Naranja
  - id: c_alert
    hex: "FF0033" # Rojo
  - id: c_good
    hex: "4CAF50" # Verde

# ---------------------------------------------------------
# SENSORES
# ---------------------------------------------------------
sensor:
  - platform: homeassistant
    id: consumo
    entity_id: sensor.consumocasa_channel_1_power
  - platform: homeassistant
    id: temperatura
    entity_id: sensor.habitacion_1_temperature
  - platform: homeassistant
    id: lavadora
    entity_id: sensor.lavadora_consumo
  - platform: homeassistant
    id: p1s_pc
    entity_id: sensor.p1s_progreso_de_la_impresion
  # Nuevo sensor de nivel de arena (0-100)
  - platform: homeassistant
    id: nivel_arena
    entity_id: sensor.pb01009g25030010664_cat_litter_level

text_sensor:
  - platform: homeassistant
    id: p1s_state
    entity_id: sensor.p1s_print_status


binary_sensor:
  # Nuevo sensor binario de basura llena
  - platform: homeassistant
    id: basura_llena
    entity_id: binary_sensor.pb01009g25030010664_garbage_can_full

time:
  - platform: homeassistant
    id: current_time

# ---------------------------------------------------------
# PANTALLA
# ---------------------------------------------------------
display:
  - platform: mipi_spi
    model: st7789v
    spi_id: spihwd
    dimensions:
      height: 240
      width: 240
    invert_colors: true
    dc_pin: GPIO00
    reset_pin: GPIO02
    color_depth: 16
    update_interval: 5s
    id: disp
    spi_mode: mode3
    lambda: |-
      // Limpiar pantalla
      it.fill(id(c_bg));

      // 1. HEADER
      auto time = id(current_time).now();
      it.printf(120, 5, id(font_small), id(c_white), TextAlign::TOP_CENTER, "%02d:%02d", time.hour, time.minute);
      it.line(10, 25, 230, 25, id(c_gray));

      // 2. DATOS DE CASA
      int y_row1 = 40;
      
      // Consumo
      it.print(40, y_row1, id(icon_font), id(c_warn), TextAlign::TOP_CENTER, "U000F0339");
      if (id(consumo).has_state()) {
        it.printf(40, y_row1 + 25, id(font_main), id(c_white), TextAlign::TOP_CENTER, "%.0f", id(consumo).state);
        it.printf(40, y_row1 + 45, id(font_small), id(c_gray), TextAlign::TOP_CENTER, "W");
      }

      // Temperatura
      it.print(120, y_row1, id(icon_font), id(c_alert), TextAlign::TOP_CENTER, "U000F050F");
      if (id(temperatura).has_state()) {
        it.printf(120, y_row1 + 25, id(font_main), id(c_white), TextAlign::TOP_CENTER, "%.1f", id(temperatura).state);
        it.printf(120, y_row1 + 45, id(font_small), id(c_gray), TextAlign::TOP_CENTER, "°C");
      }

      // Lavadora
      Color c_lav = id(c_white);
      if (id(lavadora).state > 5) c_lav = id(c_accent);
      it.print(200, y_row1, id(icon_font), c_lav, TextAlign::TOP_CENTER, "U000F0591");
      if (id(lavadora).has_state()) {
        it.printf(200, y_row1 + 25, id(font_main), id(c_white), TextAlign::TOP_CENTER, "%.0f", id(lavadora).state);
        it.printf(200, y_row1 + 45, id(font_small), id(c_gray), TextAlign::TOP_CENTER, "W");
      }

      // 3. IMPRESORA P1S (ARRIBA)
      int y_p1s = 110;
      it.filled_rectangle(5, y_p1s, 230, 55, Color(20, 20, 20)); 
      it.print(15, y_p1s + 8, id(icon_font), id(c_white), TextAlign::TOP_LEFT, "U000F042D");
      it.print(45, y_p1s + 10, id(font_small), id(c_white), TextAlign::TOP_LEFT, "Bambu P1S");

      std::string info_p1s = "---";
      Color st_color_p1s = id(c_gray);
      std::string raw_p1s = id(p1s_state).state;
      
      if (raw_p1s == "running" || raw_p1s == "printing") { info_p1s = "Print"; st_color_p1s = id(c_good); }
      else if (raw_p1s == "finish") { info_p1s = "Done"; st_color_p1s = id(c_accent); }
      else if (raw_p1s == "idle") { info_p1s = "Idle"; st_color_p1s = id(c_gray); }
      else { info_p1s = raw_p1s; }

      it.printf(225, y_p1s + 10, id(font_small), st_color_p1s, TextAlign::TOP_RIGHT, "%s", info_p1s.c_str());

      it.filled_rectangle(15, y_p1s + 38, 210, 8, id(c_gray)); 
      if (id(p1s_pc).has_state()) {
          float pct_p1s = id(p1s_pc).state;
          int fill_w_p1s = (int)(210 * (pct_p1s / 100.0));
          if (fill_w_p1s > 210) fill_w_p1s = 210;
          if (fill_w_p1s > 0) it.filled_rectangle(15, y_p1s + 38, fill_w_p1s, 8, st_color_p1s);
      }

      // 4. SECCIÓN ARENERO + GATO (ABAJO)
      int y_cat = 180;
      
      // ZONA IZQUIERDA (BARRA ARENA)
      it.print(10, y_cat, id(font_small), id(c_white), TextAlign::TOP_LEFT, "Arena");
      
      if (id(nivel_arena).has_state()) {
        it.printf(160, y_cat, id(font_small), id(c_accent), TextAlign::TOP_RIGHT, "%.0f%%", id(nivel_arena).state);
        
        it.filled_rectangle(10, y_cat + 25, 150, 15, id(c_gray)); // Fondo
        
        float pct_arena = id(nivel_arena).state;
        if (pct_arena > 100) pct_arena = 100;
        if (pct_arena < 0) pct_arena = 0;
        
        int fill_w_arena = (int)(150 * (pct_arena / 100.0));
        Color c_bar_arena = id(c_accent);
        if (pct_arena < 15) c_bar_arena = id(c_alert); // Rojo si queda poca
        
        if (fill_w_arena > 0) {
            it.filled_rectangle(10, y_cat + 25, fill_w_arena, 15, c_bar_arena);
        }
      }

      // ZONA DERECHA (GATO)
      Color c_gato = id(c_good); // Verde por defecto
      
      // Si el sensor binario dice que está lleno, poner ROJO FIJO
      if (id(basura_llena).state) {
          c_gato = id(c_alert); 
      }

      // Dibujar icono
      it.print(200, y_cat + 5, id(icon_font), c_gato, TextAlign::TOP_CENTER, "U000F011B");
      
      // Texto debajo
      if (id(basura_llena).state) {
         it.print(200, y_cat + 35, id(font_small), id(c_alert), TextAlign::TOP_CENTER, "FULL");
      } else {
         it.print(200, y_cat + 35, id(font_small), id(c_gray), TextAlign::TOP_CENTER, "OK");
      }

Si queréis los links a los productos que he usado en el vídeo, aquí los tenéis:

USB-Uart: https://s.click.aliexpress.com/e/_EzR9oBW
Pinzas Cleqee: https://s.click.aliexpress.com/e/_EHpEAFQ
Cables dupont (deben ser hembra-hembra F-F): https://s.click.aliexpress.com/e/_EIwrOqk
Destornillador: https://s.click.aliexpress.com/e/_EuabsaM

Cupones Aliexpress activos:

✂️ ESCD02 (-2€ para más de 15€)
✂️ ESCD04 (-4€ para más de 29€)
✂️ ESCD07 (-7€ para más de 49€)
✂️ ESCD09 (-9€ para más de 69€)
✂️ ESCD13 (-13€ para más de 99€)
✂️ ESCD20 (-20€ para más de 159€)
✂️ ESCD25 (-25€ para más de 209€)
✂️ ESCD40 (-40€ para más de 329€)
✂️ ESCD55 (-55€ para más de 459€)
✂️ ESCD60 (-60€ para más de 569€)

✂️ IFPZ1LEE (-2$ para más de 15$)
✂️ IFPXGXTQ (-4$ para más de 29$)
✂️ IFPJKLT2 (-7$ para más de 49$)
✂️ IFP7NQTY (-9$ para más de 69$)
✂️ IFPHW4ZE (-13$ para más de 99$)
✂️ IFPPAHJX (-20$ para más de 159$)
✂️ IFPO38WM (-25$ para más de 209$)
✂️ IFPKSSAS (-40$ para más de 329$)
✂️ IFPMXUJ6 (-55$ para más de 459$)
✂️ IFP8CPVA (-60$ para más de 529$)

✂️ IFPJQUB0 (-3$ para más de 29$)
✂️ IFPCQSUE (-6$ para más de 59$)
✂️ IFPGPKET (-9$ para más de 89$)
✂️ IFPI3EHA (-16$ para más de 149$)
✂️ IFPFOX4R (-23$ para más de 199$)
✂️ IFPXB6DN (-30$ para más de 269$)
✂️ IFPKIJK1 (-40$ para más de 369$)
✂️ IFPN9ONF (-50$ para más de 469$)
✂️ IFPJHBPY (-60$ para más de 599$)
✂️ IFPKFHJJ (-70$ para más de 699$)

Más cupones:

✂️ IFPJ7ZZH (-3$ para más de 29$)
✂️ IFPQ0RSI (-6$ para más de 59$)
✂️ IFPACEWN (-9$ para más de 89$)
✂️ IFPRKZB1 (-16$ para más de 149$)
✂️ IFPZZAN6 (-23$ para más de 199$)
✂️ IFPDHCWF (-30$ para más de 269$)
✂️ IFPSXSV1 (-40$ para más de 369$)
✂️ IFPDF8RA (-50$ para más de 469$)
✂️ IFP4IO2P (-60$ para más de 599$)
✂️ IFPTTFK6 (-70$ para más de 699$)

Otros específicos:

✂️ IFPGMTKJ (-5$ para más de 39$)
✂️ IFPK9AKK (-10$ para más de 89$)
✂️ IFPDSISD (-25$ para más de 149$)
✂️ IFPZWXFH (-45$ para más de 259$)
✂️ IFPVHT9K (-60$ para más de 349$)
✂️ IFPK9FDL (-70$ para más de 459$)
✂️ IFPDLDPJ (-120$ para más de 599$)

Encuentra las ofertas:

🔥CUPONES LIMITADOS,¡ATENTO QUE VOLARÁN!🔥Por si queréis más ofertas podéis verlas aquí.
* Si os salen agotados, podéis avisarme en los comentarios e intento buscar nuevos.

La entrada Geekmagic Ultra: Mini estación meteorológica compatible con ESPHome se publicó primero en Domótica en Casa.

Eduardo Ruiz

Informático de vocación, apasionado de la tecnología y blogger desde hace mucho tiempo, escribo de lo que me entretiene y disfruto con ello. Si deseas seguirme, usa el rss del blog o en mi perfil de .