Integrar M5Stack Dial en HA

Integrar M5Stack Dial en HA

En esta entrada vamos a integrar M5Stack Dial en HA, un dispositivo con múltiples funciones muy interesantes para controlar nuestra instancia.

M5Stack Dial

M5Stack ya es una marca conocida para nosotros, por creaciones como el M5Stack CoreS3SE o el histórico Atom Echo. Hoy vamos a integrar M5Stack Dial en HA, un dispositivo integrado por los siguientes componentes:

  • Placa M5StampS3, un microcontrolador basado en el ESP32-S3.
  • Pantalla táctil circular TFT de 1’28 pulgadas.
  • Encoder giratorio alrededor de la pantalla y un botón en la parte inferior.
  • Lector de etiquetas y tarjetas RFID (13.56MHz), aunque me parece muy poco preciso.
  • Zumbador para emitir sonidos.
  • Puerto de conexión para baterías con un circuito de carga integrado.
  • 2 Puertos de expansión para I2C y GPIO.

Todas estas características unidas en un sólo dispositivo prefabricado, listo para ser usado, lo convierten en un ‘gadget’ muy interesante. Además, como está basado en un ESP32-S3 vamos a poder integrar M5Stack Dial en HA utilizando ESPHome.

Mr. Avocado

Como es habitual, vamos a aprovechar todas estas funcionalidades para llevar a cabo un proyecto interesante. Esta vez se trata de un dispositivo especial, ya que lo hemos diseñado junto a nuestros Patreon. Empezando por el nombre, «Mr. Avocado» (en honor al mítico «Mr. Potato»), la idea era crear un dispositivo multifuncional con las siguientes características:

  • Fecha y hora en la pantalla de bloqueo, que aparece automáticamente pasados unos segundos
  • Apagado automático de pantalla para ahorro de energía pasados unos segundos
  • Interfaz optimizada para controlar 8 dispositivos
  • Función despertador con tono personalizado, programable desde Home Assistant
  • Lector de etiquetas y tarjetas NFC/RFID
  • Al estar basado en una ESP32-S3, puedes activar el Bluetooth Proxy para utilizarlo como sensor de presencia con Bermuda.

Requisitos previos

Para poder integrar M5Stack Dial en HA necesitas:

  • Lógicamente, un M5Stack Dial.
  • Haber instalado ESPHome en Home Assistant.
  • Un cable USB-C para alimentar la placa de DATOS (con un cable de carga no vas a poder instalar el software).
  • Opcionalmente, el soporte que hemos diseñado para convertirlo en Mr. Avocado.

-52%
Codificador de controlador programable M5Stack M5Dial, placa de desarrollo rotativa M5StampS3, pantalla táctil redonda de 1,28 pulgadas, Control de hogar inteligente
Aliexpress
43,72€ 91,18€
Codificador de controlador programable M5Stack M5Dial, placa de desarrollo rotativa M5StampS3, pantalla táctil redonda de 1,28 pulgadas, Control de hogar...
Controlador programable M5Stack M5Dial a través de tipo perilla, pantalla táctil Circular de 1,28 ", ESP32S3
Aliexpress
43,79€
Controlador programable M5Stack M5Dial a través de tipo perilla, pantalla táctil Circular de 1,28 ", ESP32S3
-10%
Controlador programable M5Stack M5Dial a través de tipo perilla, pantalla táctil Circular de 1,28 ", ESP32S3
Aliexpress
42,92€ 47,69€
Controlador programable M5Stack M5Dial a través de tipo perilla, pantalla táctil Circular de 1,28 ", ESP32S3
Controlador programable M5Stack M5Dial a través de tipo perilla, pantalla táctil Circular de 1,28 ", ESP32S3
Aliexpress
43,84€
Controlador programable M5Stack M5Dial a través de tipo perilla, pantalla táctil Circular de 1,28 ", ESP32S3
*Algún precio puede haber cambiado desde la última revisión
🥑 Si estás empezando con ESPHome, te recomiendo que veas el taller de la academia para sacarle el máximo partido!

Configuración en ESPHome

Sigue estos pasos para integrar M5Stack Dial en HA:

  1. En Home Assistant, ve a tu complemento de ESPHome, pulsa en “New device” y “Continue”.
  2. Dale un nombre a tu dispositivo (por ejemplo, “M5stack Dial”) y pulsa en “Next”.
  3. Como tipo de dispositivo selecciona “ESP32-S3”. Observarás de fondo que se ha creado un nuevo bloque para tu dispositivo.
  4. Pulsa en “Skip” y haz clic en “Edit”, sobre el bloque de tu dispositivo. Copia el código que aparece y consérvalo, ya que vas a necesitar alguna parte del mismo.
  5. Copia el siguiente código y úsalo para reemplazar el código anterior en ESPHome.
substitutions:

# Device customization
# Personalización del dispositivo

  name: m5stack-dial
  friendly_name: M5Stack Dial
  background_color: 'fab02b'
  background_image: https://aguacatec.es/wp-content/uploads/2025/02/mravocado_background_white.jpg
  background_image_saver: https://aguacatec.es/wp-content/uploads/2025/02/mravocado_bg_off.jpg
  background_image_device: https://aguacatec.es/wp-content/uploads/2025/02/mravocado_bg_device.jpeg

# Icons
# Iconos

  icon_1: mdi:led-strip-variant
  icon_2: mdi:thermostat
  icon_3: mdi:robot-vacuum
  icon_4: mdi:printer
  icon_5: mdi:printer-3d-nozzle
  icon_6: mdi:fan
  icon_7: mdi:air-humidifier
  icon_8: mdi:ceiling-light

# Sounds
# Sonidos

  menu_sound: 'beep:d=64,o=5,b=255:c7'
  alarm_sound: 'xmen:d=4,o=6,b=200:16f#5,16g5,16b5,16d,c#,8b5,8f#5,p,16f#5,16g5,16b5,16d,c#,8b5,8g5,p,16f#5,16g5,16b5,16d,c#,8b5,8d,2p,8c#,8b5,2p'


# Example of Lights
# Ejemplo de Luces

  desk_led: light.tira_led_escritorio
  lamp: light.lampara

# Example of Thermostat
# Ejemplo de Termostatos

  climate: climate.salon
  aircon: climate.aircon

# Example of Vacuum
# Ejemplo de Aspirador

  vacuum: vacuum.robot_aspirador

# Example of Switches
# Ejemplo de Enchufes

  printer: switch.regleta_l3
  printer3d: switch.regleta_l4

# Example of dehumidifier
# Ejemplo de Deshumidificador

  dehumidifier: humidifier.deshumidificador

# NFC/RFID Tags
# Etiquetas NFC/RFID

#  tag1: C3-DB-4F-28
#  tag2: 03-55-E5-13

# Other settings
# Otros ajustes

  allowed_characters: " ¿?¡!#%'()+,-./:°0123456789ABCDEFGHIJKLMNOPQRSTUVWYZabcdefghijklmnopqrstuvwxyzáéíóú"

################################################################################################################


esphome:
  name: ${name}
  friendly_name: ${friendly_name}
  on_boot:
    then:
      - pcf8563.read_time:
      - display.page.show: home
  platformio_options:
    board_build.flash_mode: dio

esp32:
  board: esp32-s3-devkitc-1
  flash_size: 8MB
  framework:
    type: esp-idf

wifi:
  ssid: !secret wifi_ssid
  password: !secret wifi_password

  # Enable fallback hotspot (captive portal) in case wifi connection fails
  ap:
    ssid: "M5Stack-Dial Fallback Hotspot"
    password: "Aosad564JQR"

api:
  encryption:
    key: "QYmasdasdsd71H8/dlyD1BI5cU10X234234fhg="
  services:
    - service: play_sound
      variables:
        song: string
        volume: int 
      then:
        - lambda: "id(script_rtttl_play).execute(song, volume);"
script:
  - id: script_rtttl_play
    parameters:
      song: string
      volume: int 
    mode: single
    then:
      - lambda: |-
          float volume_f = (volume>0) ? ((float)clamp(volume, 0, 100))/100.0f : 1.0f;
          id(buzzer).set_max_power(volume_f);
      - rtttl.play:
          rtttl: !lambda 'return (song.find('':'') == std::string::npos) ? ("song:d=16,o=5,b=100:" + song).c_str() : song.c_str();'

ota:
  - platform: esphome
    password: "0935e9dsasdfgdb3d8934c"

logger:

captive_portal:

binary_sensor:
  - platform: gpio
    name: "Front Button"
    id: front_button
    pin:
      number: GPIO42
      inverted: true
    internal: true
    on_press:
      then:
        - if:
            condition:
              switch.is_on: menu_sounds
            then:
            - rtttl.play: ${menu_sound}
        - if:
            condition:
              light.is_on: backlight
            then:
            - if:
                condition:
                  display.is_displaying_page: device_control
                then:
                - if:
                    condition:
                      lambda: |-
                        return id(device) == 1;
                    then:
                    - homeassistant.action:
                        service: light.toggle
                        data:
                          entity_id: ${desk_led}
                - if:
                    condition:
                      lambda: |-
                        return id(device) == 2;
                    then:
                    - homeassistant.action:
                        service: climate.toggle
                        data:
                          entity_id: ${climate}
                - if:
                    condition:
                      lambda: |-
                        return id(device) == 3;
                    then:
                    - if:
                        condition:
                          lambda: 'return id(device_vacuum).state == "cleaning";'
                        then:
                        - homeassistant.action:
                            service: vacuum.pause
                            data:
                              entity_id: ${vacuum}
                        else:
                        - homeassistant.action:
                            service: vacuum.start
                            data:
                              entity_id: ${vacuum}
                - if:
                    condition:
                      lambda: |-
                        return id(device) == 4;
                    then:
                    - homeassistant.action:
                        service: switch.toggle
                        data:
                          entity_id: ${printer}
                - if:
                    condition:
                      lambda: |-
                        return id(device) == 5;
                    then:
                    - homeassistant.action:
                        service: switch.toggle
                        data:
                          entity_id: ${printer3d}
                - if:
                    condition:
                      lambda: |-
                        return id(device) == 6;
                    then:
                    - homeassistant.action:
                        service: climate.toggle
                        data:
                          entity_id: ${aircon}
                - if:
                    condition:
                      lambda: |-
                        return id(device) == 7;
                    then:
                    - homeassistant.action:
                        service: humidifier.toggle
                        data:
                          entity_id: ${dehumidifier}
                - if:
                    condition:
                      lambda: |-
                        return id(device) == 8;
                    then:
                    - homeassistant.action:
                        service: light.toggle
                        data:
                          entity_id: ${lamp}
            - if:
                condition:
                  display.is_displaying_page: locked_screen
                then:
                - switch.turn_on: mravocado_display
                - light.turn_on:
                    id: backlight
                    brightness: 100%
                - display.page.show: home
            - if:
                condition:
                  display.is_displaying_page: home
                then:
                - if:
                    condition:
                      lambda: |-
                        return id(device) > 0;
                    then:
                    - light.turn_on:
                        id: backlight
                        brightness: 100%
                    - display.page.show: device_control
            else:
            - switch.turn_on: mravocado_display
            - light.turn_on:
                id: backlight
                brightness: 100%
            - display.page.show: home
        - lambda: |-
            id(inactivity_time) = 0;

  - platform: gpio
    name: Hold Button
    pin: GPIO46
    internal: True

  - platform: touchscreen
    name: "Home Button"
    internal: true
    x_min: 0   
    x_max: 240  
    y_min: 0   
    y_max: 80  
    page_id: device_control
    on_press:
      - display.page.show: home
      - if:
          condition:
            switch.is_on: menu_sounds
          then:
          - rtttl.play: ${menu_sound}
      - lambda: |-
          id(inactivity_time) = 0;

  - platform: touchscreen
    name: "Device Button"
    internal: true
    x_min: 81   
    x_max: 160  
    y_min: 80   
    y_max: 240  
    page_id: device_control
    on_press:
      - if:
          condition:
            switch.is_on: menu_sounds
          then:
          - rtttl.play: ${menu_sound}
      - if:
          condition:
            lambda: |-
              return id(device) == 1;
          then:
          - homeassistant.action:
              service: light.toggle
              data:
                entity_id: ${desk_led}
      - if:
          condition:
            lambda: |-
              return id(device) == 2;
          then:
          - homeassistant.action:
              service: climate.toggle
              data:
                entity_id: ${climate}
      - if:
          condition:
            lambda: |-
              return id(device) == 3;
          then:
          - if:
              condition:
                lambda: 'return id(device_vacuum).state == "cleaning";'
              then:
              - homeassistant.action:
                  service: vacuum.pause
                  data:
                    entity_id: ${vacuum}
              else:
              - homeassistant.action:
                  service: vacuum.start
                  data:
                    entity_id: ${vacuum}
      - if:
          condition:
            lambda: |-
              return id(device) == 4;
          then:
          - homeassistant.action:
              service: switch.toggle
              data:
                entity_id: ${printer}
      - if:
          condition:
            lambda: |-
              return id(device) == 5;
          then:
          - homeassistant.action:
              service: switch.toggle
              data:
                entity_id: ${printer3d}
      - if:
          condition:
            lambda: |-
              return id(device) == 6;
          then:
          - homeassistant.action:
              service: climate.toggle
              data:
                entity_id: ${aircon}
      - if:
          condition:
            lambda: |-
              return id(device) == 7;
          then:
          - homeassistant.action:
              service: humidifier.toggle
              data:
                entity_id: ${dehumidifier}
      - if:
          condition:
            lambda: |-
              return id(device) == 8;
          then:
          - homeassistant.action:
              service: light.toggle
              data:
                entity_id: ${lamp}
      - lambda: |-
          id(inactivity_time) = 0;

  - platform: touchscreen
    name: "Minus Button"
    internal: true
    x_min: 0   
    x_max: 80  
    y_min: 80   
    y_max: 240  
    page_id: device_control
    on_press:
      - if:
          condition:
            switch.is_on: menu_sounds
          then:
          - rtttl.play: ${menu_sound}
      - if:
          condition:
            display.is_displaying_page: device_control
          then:
          - if:
              condition:
                lambda: |-
                  return id(device) == 1;
              then:
              - homeassistant.action:
                  service: light.turn_on
                  data:
                    entity_id: ${desk_led}
                    brightness_step_pct: '-10'
          - if:
              condition:
                lambda: |-
                  return id(device) == 2;
              then:
              - homeassistant.action:
                  service: climate.set_temperature
                  data:
                    entity_id: ${climate}
                  data_template:
                    temperature: '{{ my_variable | float }}'
                  variables:
                    my_variable: |-
                      return id(thermostat_temperature).state - 1.0;
          - if:
              condition:
                lambda: |-
                  return id(device) == 6;
              then:
              - homeassistant.action:
                  service: climate.set_temperature
                  data:
                    entity_id: ${aircon}
                  data_template:
                    temperature: '{{ my_variable | float }}'
                  variables:
                    my_variable: |-
                      return id(aircon_temperature).state - 1.0;
          - if:
              condition:
                lambda: |-
                  return id(device) == 7;
              then:
              - homeassistant.action:
                  service: humidifier.set_humidity
                  data:
                    entity_id: ${dehumidifier}
                  data_template:
                    humidity: '{{ my_variable | float }}'
                  variables:
                    my_variable: |-
                      return id(dehumidifier_humidity).state - 5.0;
          - if:
              condition:
                lambda: |-
                  return id(device) == 8;
              then:
              - homeassistant.action:
                  service: light.turn_on
                  data:
                    entity_id: ${lamp}
                    brightness_step_pct: '-10'
      - lambda: |-
          id(inactivity_time) = 0;

  - platform: touchscreen
    name: "Plus Button"
    internal: true
    x_min: 161   
    x_max: 240  
    y_min: 80   
    y_max: 240  
    page_id: device_control
    on_press:
      - if:
          condition:
            switch.is_on: menu_sounds
          then:
          - rtttl.play: ${menu_sound}
      - if:
          condition:
            display.is_displaying_page: device_control
          then:
          - if:
              condition:
                lambda: |-
                  return id(device) == 1;
              then:
              - homeassistant.action:
                  service: light.turn_on
                  data:
                    entity_id: ${desk_led}
                    brightness_step_pct: '10'
          - if:
              condition:
                lambda: |-
                  return id(device) == 2;
              then:
              - homeassistant.action:
                  service: climate.set_temperature
                  data:
                    entity_id: ${climate}
                  data_template:
                    temperature: '{{ my_variable | float }}'
                  variables:
                    my_variable: |-
                      return id(thermostat_temperature).state + 1.0;
          - if:
              condition:
                lambda: |-
                  return id(device) == 3;
              then:
              - homeassistant.action:
                  service: vacuum.return_to_base
                  data:
                    entity_id: ${vacuum}
          - if:
              condition:
                lambda: |-
                  return id(device) == 6;
              then:
              - homeassistant.action:
                  service: climate.set_temperature
                  data:
                    entity_id: ${aircon}
                  data_template:
                    temperature: '{{ my_variable | float }}'
                  variables:
                    my_variable: |-
                      return id(aircon_temperature).state + 1.0;
          - if:
              condition:
                lambda: |-
                  return id(device) == 7;
              then:
              - homeassistant.action:
                  service: humidifier.set_humidity
                  data:
                    entity_id: ${dehumidifier}
                  data_template:
                    humidity: '{{ my_variable | float }}'
                  variables:
                    my_variable: |-
                      return id(dehumidifier_humidity).state + 5.0;
          - if:
              condition:
                lambda: |-
                  return id(device) == 8;
              then:
              - homeassistant.action:
                  service: light.turn_on
                  data:
                    entity_id: ${lamp}
                    brightness_step_pct: '10'
      - lambda: |-
          id(inactivity_time) = 0;

#  - platform: rc522
#    uid: ${tag1}
#    name: "NFC Tag"
#    on_press:
#    - homeassistant.action:
#        service: light.toggle
#        data:
#          entity_id: ${desk_led}

button:
  - platform: template
    name: "Alarm"
    id: alarm_sound
    icon: "mdi:bell-ring"
    on_press:
    - rtttl.play: ${alarm_sound}
    - switch.turn_on: screen_saver
    - lambda: |-
        id(inactivity_time) = 0;

color:
  - id: background_color
    hex: ${background_color}
  - id: icon_on
    hex: 'f28800'
  - id: icon_off
    hex: 'e7aa77'
  - id: icon_big_on
    hex: 'ffebbf'
  - id: icon_big_off
    hex: 'f78f1d'
  - id: dark_orange
    hex: 'd2750b'
  - id: light_orange
    hex: 'f9c699'

font:
  - file: "gfonts://Space Grotesk"
    id: clock_time
    size: 40
    glyphs: ${allowed_characters}
  - file: "gfonts://Space Grotesk"
    id: secondary
    size: 18
    glyphs: ${allowed_characters}

globals:
  - id: inactivity_time
    type: int
    restore_value: no
    initial_value: '0'
  - id: device
    type: int
    restore_value: no
    initial_value: '0'

i2c:
  - id: internal_i2c
    sda: GPIO11
    scl: GPIO12
    scan: False

image:
  - file: ${background_image}
    id: background_image
    resize: 245x245
    type: RGB
    transparency: alpha_channel

  - file: ${background_image_saver}
    id: background_image_saver
    resize: 245x245
    type: RGB
    transparency: alpha_channel
  - file: ${background_image_device}
    id: background_image_device
    resize: 245x245
    type: RGB
    transparency: alpha_channel

  - file: mdi:home
    id: icon_home
    resize: 40x40
    type: BINARY
    transparency: chroma_key
  - file: mdi:plus-thick
    id: plus
    resize: 30x30
    type: BINARY
    transparency: chroma_key
  - file: mdi:minus-thick
    id: minus
    resize: 30x30
    type: BINARY
    transparency: chroma_key
  - file: mdi:home-map-marker
    id: vacuum_dock
    resize: 30x30
    type: BINARY
    transparency: chroma_key
  - file: mdi:play-box
    id: play_icon
    resize: 30x30
    type: BINARY
    transparency: chroma_key
  - file: mdi:pause-box
    id: pause_icon
    resize: 30x30
    type: BINARY
    transparency: chroma_key

  - file: ${icon_1}
    id: icon_1
    resize: 33x33
    type: BINARY
    transparency: chroma_key
  - file: ${icon_1}
    id: icon_1_big
    resize: 100x100
    type: BINARY
    transparency: chroma_key
    
  - file: ${icon_2}
    id: icon_2
    resize: 33x33
    type: BINARY
    transparency: chroma_key
  - file: ${icon_2}
    id: icon_2_big
    resize: 100x100
    type: BINARY
    transparency: chroma_key
    
  - file: ${icon_3}
    id: icon_3
    resize: 33x33
    type: BINARY
    transparency: chroma_key
  - file: ${icon_3}
    id: icon_3_big
    resize: 100x100
    type: BINARY
    transparency: chroma_key
    
  - file: ${icon_4}
    id: icon_4
    resize: 33x33
    type: BINARY
    transparency: chroma_key
  - file: ${icon_4}
    id: icon_4_big
    resize: 100x100
    type: BINARY
    transparency: chroma_key
    
  - file: ${icon_5}
    id: icon_5
    resize: 33x33
    type: BINARY
    transparency: chroma_key
  - file: ${icon_5}
    id: icon_5_big
    resize: 100x100
    type: BINARY
    transparency: chroma_key
    
  - file: ${icon_6}
    id: icon_6
    resize: 33x33
    type: BINARY
    transparency: chroma_key
  - file: ${icon_6}
    id: icon_6_big
    resize: 100x100
    type: BINARY
    transparency: chroma_key
    
  - file: ${icon_7}
    id: icon_7
    resize: 33x33
    type: BINARY
    transparency: chroma_key
  - file: ${icon_7}
    id: icon_7_big
    resize: 100x100
    type: BINARY
    transparency: chroma_key
    
  - file: ${icon_8}
    id: icon_8
    resize: 33x33
    type: BINARY
    transparency: chroma_key
  - file: ${icon_8}
    id: icon_8_big
    resize: 100x100
    type: BINARY
    transparency: chroma_key
    
interval:
  - interval: 1s
    then:
      - lambda: |-
          id(inactivity_time) += 1;

          if (id(auto_lock).state) {
            if (id(inactivity_time) > id(screen_saver_time).state && id(inactivity_time) < id(auto_lock_time_out).state ) {
                id(screen_saver).turn_on();
            } 
            if (id(inactivity_time) > id(auto_lock_time_out).state) {
                id(backlight_pwm).turn_off();
                id(mravocado_display).turn_off();
                id(screen_saver).turn_off();
            }
          }

          else {
            if (id(inactivity_time) > id(screen_saver_time).state) {
                id(screen_saver).turn_on();
            } 
          }

light:
  - platform: monochromatic
    name: "Backlight"
    output: backlight_pwm
    id: backlight
    default_transition_length: 0s
    restore_mode: ALWAYS_ON
    internal: True

number:
  - platform: template
    name: "Auto Lock"
    id: auto_lock_time_out
    icon: "mdi:timer-sand"
    optimistic: true
    min_value: 20
    max_value: 300
    step: 10
    unit_of_measurement: "s"
    restore_value: true
  - platform: template
    name: "Screen Saver"
    id: screen_saver_time
    icon: "mdi:screen-rotation-lock"
    optimistic: true
    min_value: 10
    max_value: 300
    step: 10
    unit_of_measurement: "s"
    restore_value: true

output:
  - platform: ledc
    pin: GPIO3
    id: buzzer
  - platform: ledc
    pin: GPIO9
    id: backlight_pwm

#rc522_i2c:
#  - i2c_id: internal_i2c
#    id: tag_reader
#    address: 0x28
#    on_tag:
#      then:
#        - rtttl.play: "success:d=24,o=5,b=100:c,g,b"
#        - homeassistant.tag_scanned: !lambda 'return x;'

rtttl:
  output: buzzer

sensor:
  - platform: rotary_encoder
    id: encoder
    pin_a: GPIO40
    pin_b: GPIO41
    on_clockwise:
      then:
        - if:
            condition:
              switch.is_on: menu_sounds
            then:
            - rtttl.play: ${menu_sound}
        - if:
            condition:
              display.is_displaying_page: home
            then:
            - lambda: |-
                if (id(device) == 8) {
                  id(device) = 1;
                }
                else {
                  id(device) += 1;
                }
        - if:
            condition:
              display.is_displaying_page: device_control
            then:
            - if:
                condition:
                  lambda: |-
                    return id(device) == 1;
                then:
                - homeassistant.action:
                    service: light.turn_on
                    data:
                      entity_id: ${desk_led}
                      brightness_step_pct: '10'
            - if:
                condition:
                  lambda: |-
                    return id(device) == 2;
                then:
                - homeassistant.action:
                    service: climate.set_temperature
                    data:
                      entity_id: ${climate}
                    data_template:
                      temperature: '{{ my_variable | float }}'
                    variables:
                      my_variable: |-
                        return id(thermostat_temperature).state + 1.0;
            - if:
                condition:
                  lambda: |-
                    return id(device) == 3;
                then:
                - homeassistant.action:
                    service: vacuum.return_to_base
                    data:
                      entity_id: ${vacuum}
            - if:
                condition:
                  lambda: |-
                    return id(device) == 6;
                then:
                - homeassistant.action:
                    service: climate.set_temperature
                    data:
                      entity_id: ${aircon}
                    data_template:
                      temperature: '{{ my_variable | float }}'
                    variables:
                      my_variable: |-
                        return id(aircon_temperature).state + 1.0;
            - if:
                condition:
                  lambda: |-
                    return id(device) == 7;
                then:
                - homeassistant.action:
                    service: humidifier.set_humidity
                    data:
                      entity_id: ${dehumidifier}
                    data_template:
                      humidity: '{{ my_variable | float }}'
                    variables:
                      my_variable: |-
                        return id(dehumidifier_humidity).state + 5.0;
            - if:
                condition:
                  lambda: |-
                    return id(device) == 8;
                then:
                - homeassistant.action:
                    service: light.turn_on
                    data:
                      entity_id: ${lamp}
                      brightness_step_pct: '10'
        - lambda: |-
            id(inactivity_time) = 0;
    on_anticlockwise:
      then:
        - if:
            condition:
              switch.is_on: menu_sounds
            then:
            - rtttl.play: ${menu_sound}
        - if:
            condition:
              display.is_displaying_page: home
            then:
            - lambda: |-
                if (id(device) == 1) {
                  id(device) = 8;
                }
                if (id(device) == 0) {
                  id(device) = 8;
                }
                else {
                  id(device) -= 1;
                }
        - if:
            condition:
              display.is_displaying_page: device_control
            then:
            - if:
                condition:
                  lambda: |-
                    return id(device) == 1;
                then:
                - homeassistant.action:
                    service: light.turn_on
                    data:
                      entity_id: ${desk_led}
                      brightness_step_pct: '-10'
            - if:
                condition:
                  lambda: |-
                    return id(device) == 2;
                then:
                - homeassistant.action:
                    service: climate.set_temperature
                    data:
                      entity_id: ${climate}
                    data_template:
                      temperature: '{{ my_variable | float }}'
                    variables:
                      my_variable: |-
                        return id(thermostat_temperature).state - 1.0;
            - if:
                condition:
                  lambda: |-
                    return id(device) == 3;
                then:
                - if:
                    condition:
                      lambda: 'return id(device_vacuum).state == "cleaning";'
                    then:
                    - homeassistant.action:
                        service: vacuum.pause
                        data:
                          entity_id: ${vacuum}
                    else:
                    - homeassistant.action:
                        service: vacuum.start
                        data:
                          entity_id: ${vacuum}
            - if:
                condition:
                  lambda: |-
                    return id(device) == 6;
                then:
                - homeassistant.action:
                    service: climate.set_temperature
                    data:
                      entity_id: ${aircon}
                    data_template:
                      temperature: '{{ my_variable | float }}'
                    variables:
                      my_variable: |-
                        return id(aircon_temperature).state - 1.0;
            - if:
                condition:
                  lambda: |-
                    return id(device) == 7;
                then:
                - homeassistant.action:
                    service: humidifier.set_humidity
                    data:
                      entity_id: ${dehumidifier}
                    data_template:
                      humidity: '{{ my_variable | float }}'
                    variables:
                      my_variable: |-
                        return id(dehumidifier_humidity).state - 5.0;
            - if:
                condition:
                  lambda: |-
                    return id(device) == 8;
                then:
                - homeassistant.action:
                    service: light.turn_on
                    data:
                      entity_id: ${lamp}
                      brightness_step_pct: '-10'
        - lambda: |-
            id(inactivity_time) = 0;

  - platform: homeassistant
    id: desk_led_brightness
    entity_id: ${desk_led}
    attribute: brightness
    internal: true
    filters:
      - lambda: |-
          if (isnan(x)) { return 0; }
          else { return x; }

  - platform: homeassistant
    id: thermostat_temperature
    entity_id: ${climate}
    attribute: temperature
    internal: true

  - platform: homeassistant
    id: aircon_temperature
    entity_id: ${aircon}
    attribute: temperature
    internal: true

  - platform: homeassistant
    id: dehumidifier_humidity
    entity_id: ${dehumidifier}
    attribute: humidity
    internal: true

  - platform: homeassistant
    id: lamp_brightness
    entity_id: ${lamp}
    attribute: brightness
    internal: true
    filters:
      - lambda: |-
          if (isnan(x)) { return 0; }
          else { return x; }

spi:
  id: spi_bus
  mosi_pin: GPIO5
  clk_pin: GPIO6

switch:
  - platform: template
    name: "Auto Lock"
    id: auto_lock
    icon: "mdi:lock-clock"
    optimistic: true
    restore_mode: 'restore_default_off'
  - platform: template
    name: "Display"
    id: mravocado_display
    icon: "mdi:fit-to-screen"
    optimistic: true
    restore_mode: 'always_on'
    on_turn_on:
      - light.turn_on:
          id: backlight
          brightness: 100%
      - lambda: |-
          id(inactivity_time) = 0;
    on_turn_off:
      - light.turn_off: backlight
      - display.page.show: home
      - lambda: |-
          id(device) = 0;
  - platform: template
    name: "Screen Saver"
    id: screen_saver
    icon: "mdi:screen-rotation-lock"
    optimistic: true
    restore_mode: 'always_off'
    internal: true
    on_turn_on:
      - light.turn_on:
          id: backlight
          brightness: 50%
      - display.page.show: locked_screen
      - lambda: |-
          id(device) = 0;

  - platform: template
    name: "Menu Sounds"
    id: menu_sounds
    icon: "mdi:playlist-music"
    optimistic: true
    restore_mode: 'restore_default_on'

text_sensor:

  - platform: homeassistant
    id: device_desk_led
    entity_id: ${desk_led}
    internal: true
  - platform: homeassistant
    id: device_thermostat
    entity_id: ${climate}
    internal: true
  - platform: homeassistant
    id: device_vacuum
    entity_id: ${vacuum}
    internal: true
  - platform: homeassistant
    id: device_printer
    entity_id: ${printer}
    internal: true
  - platform: homeassistant
    id: device_printer3d
    entity_id: ${printer3d}
    internal: true
  - platform: homeassistant
    id: device_dehumidifier
    entity_id: ${dehumidifier}
    internal: true
  - platform: homeassistant
    id: device_aircon
    entity_id: ${aircon}
    internal: true
  - platform: homeassistant
    id: device_lamp
    entity_id: ${lamp}
    internal: true

time:
  # RTC
  - platform: pcf8563
    id: rtctime
    i2c_id: internal_i2c
    address: 0x51
    update_interval: never
  - platform: homeassistant
    id: esptime
    on_time_sync:
      then:
        - pcf8563.write_time:

touchscreen:
  - platform: ft5x06
    id: touchscreen_mravocado
    i2c_id: internal_i2c
    address: 0x38

display:
  - platform: ili9xxx
    id: round_display
    model: GC9A01A
    cs_pin: GPIO7
    reset_pin: GPIO8
    update_interval: 0.05s
    dc_pin: GPIO4
    invert_colors: true
    pages:
      - id: locked_screen
        lambda: |-
          it.fill(id(background_color));
          it.image(0, 0, id(background_image_saver));

          it.strftime(120, 40, id(clock_time), TextAlign::CENTER, "%H:%M", id(esptime).now());
          it.strftime(120, 200, id(secondary), TextAlign::CENTER, "%d/%m/%y", id(esptime).now());
      - id: home
        lambda: |-
          it.fill(id(background_color));
          it.image(0, 0, id(background_image));

          if (id(device) == 1) { it.image(103, 4, id(icon_1), id(icon_on)); }
          else { it.image(103, 4, id(icon_1), id(icon_off)); }

          if (id(device) == 2) { it.image(175, 35, id(icon_2), id(icon_on)); }
          else { it.image(175, 35, id(icon_2), id(icon_off)); }

          if (id(device) == 3) { it.image(205, 105, id(icon_3), id(icon_on)); }
          else { it.image(205, 105, id(icon_3), id(icon_off)); }

          if (id(device) == 4) { it.image(175, 175, id(icon_4), id(icon_on)); }
          else { it.image(175, 175, id(icon_4), id(icon_off)); }

          if (id(device) == 5) { it.image(103, 205, id(icon_5), id(icon_on)); }
          else { it.image(103, 205, id(icon_5), id(icon_off)); }

          if (id(device) == 6) { it.image(30, 175, id(icon_6), id(icon_on)); }
          else { it.image(30, 175, id(icon_6), id(icon_off)); }

          if (id(device) == 7) { it.image(5, 105, id(icon_7), id(icon_on)); }
          else { it.image(5, 105, id(icon_7), id(icon_off)); }

          if (id(device) == 8) { it.image(30, 35, id(icon_8), id(icon_on)); }
          else { it.image(30, 35, id(icon_8), id(icon_off)); }

      - id: device_control
        lambda: |-
          it.fill(id(background_color));
          it.image(0, 0, id(background_image_device));

          it.image(98, 10, id(icon_home), id(light_orange));

          if (id(device) == 1) {

            if (id(device_desk_led).state == "on") { it.image(70, 80, id(icon_1_big), id(icon_big_on)); }
            else { it.image(70, 80, id(icon_1_big), id(icon_big_off)); }

            it.image(25, 115, id(minus), id(light_orange));
            it.printf(120, 210, id(secondary), TextAlign::CENTER, "%.0f %%", ((id(desk_led_brightness).state / 255) * 100));
            it.image(185, 115, id(plus), id(light_orange));
          }

          if (id(device) == 2) {

            if (id(device_thermostat).state == "heat") { it.image(70, 80, id(icon_2_big), id(icon_big_on)); }
            else { it.image(70, 80, id(icon_2_big), id(icon_big_off)); }

            it.image(25, 115, id(minus), id(light_orange));
            it.printf(120, 210, id(secondary), TextAlign::CENTER, "%.1f°C", id(thermostat_temperature).state);
            it.image(185, 115, id(plus), id(light_orange));
          }

          if (id(device) == 3) {

            if (id(device_vacuum).state == "cleaning") { it.image(70, 80, id(icon_3_big), id(icon_big_on)); }
            else { it.image(70, 80, id(icon_3_big), id(icon_big_off)); }


            if (id(device_vacuum).state == "cleaning") {
            it.image(25, 115, id(pause_icon), id(light_orange));
            }
            else {
            it.image(25, 115, id(play_icon), id(light_orange));
            }

            it.printf(120, 210, id(secondary), TextAlign::CENTER, "%s", id(device_vacuum).state.c_str());
            it.image(185, 115, id(vacuum_dock), id(light_orange));
          }

          if (id(device) == 4) {

            if (id(device_printer).state == "on") { it.image(70, 80, id(icon_4_big), id(icon_big_on)); }
            else { it.image(70, 80, id(icon_4_big), id(icon_big_off)); }

            it.printf(120, 210, id(secondary), TextAlign::CENTER, "%s", id(device_printer).state.c_str());
          }

          if (id(device) == 5) {

            if (id(device_printer3d).state == "on") { it.image(70, 80, id(icon_5_big), id(icon_big_on)); }
            else { it.image(70, 80, id(icon_5_big), id(icon_big_off)); }

            it.printf(120, 210, id(secondary), TextAlign::CENTER, "%s", id(device_printer3d).state.c_str());
          }

          if (id(device) == 6) {

            if (id(device_aircon).state == "cool") { it.image(70, 80, id(icon_6_big), id(icon_big_on)); }
            else { it.image(70, 80, id(icon_6_big), id(icon_big_off)); }

            it.image(25, 115, id(minus), id(light_orange));
            it.printf(120, 210, id(secondary), TextAlign::CENTER, "%.1f°C", id(aircon_temperature).state);
            it.image(185, 115, id(plus), id(light_orange));
          }

          if (id(device) == 7) {

            if (id(device_dehumidifier).state == "on") { it.image(70, 80, id(icon_7_big), id(icon_big_on)); }
            else { it.image(70, 80, id(icon_7_big), id(icon_big_off)); }

            it.image(25, 115, id(minus), id(light_orange));
            it.printf(120, 210, id(secondary), TextAlign::CENTER, "%.1f°C", id(dehumidifier_humidity).state);
            it.image(185, 115, id(plus), id(light_orange));
          }

          if (id(device) == 8) {

            if (id(device_lamp).state == "on") { it.image(70, 80, id(icon_8_big), id(icon_big_on)); }
            else { it.image(70, 80, id(icon_8_big), id(icon_big_off)); }

            it.image(25, 115, id(minus), id(light_orange));
            it.printf(120, 210, id(secondary), TextAlign::CENTER, "%.0f %%", ((id(lamp_brightness).state / 255) * 100));
            it.image(185, 115, id(plus), id(light_orange));
          }
⚠️ Aunque hemos conseguido crear todas esta funciones para Mr. Avocado, mi consejo es que comentes (o elimines) el código de aquellas que no vayas a utilizar. De esta forma mejorarás el rendimiento del dispositivo y evitarás que se quede "congelado".
  1. Parte importante. En este código no se han incluido las credenciales para que el dispositivo se conecte a tu WiFi y tu instancia de Home Assistant y tienes que incluirlas manualmente. En concreto me refiero a las siguientes líneas del código que has copiado en el paso 4.
# Enable Home Assistant API
api:
  encryption:
    key: "bg6hash6sjdjsdjk02hh0qnQeYVwm123vdfKE8BP5"

ota:
  - platform: esphome
    password: "asddasda27aab65a48484502b332f"

wifi:
  ssid: !secret wifi_ssid
  password: !secret wifi_password

  # Enable fallback hotspot (captive portal) in case wifi connection fails
  ap:
    ssid: "Assist Fallback Hotspot"
    password: "ZsasdasdHGP2234"
  1. Lo que tienes que hacer es, encontrar las líneas correspondientes en el código (está al principio) y añadir la información correspondiente.
  2. Ahora sí, pulsa en “Save” e “Install”. Selecciona “Manual download” y espera a que se compile el código. 
  3. Cuando termine, selecciona la opción “Modern format” para descargar el fichero ‘.bin’ correspondiente.
  4. Conecta el M5Stack Dial con el cable USB-C de datos por el puerto que trae en la parte inferior a tu ordenador.
  5. Ahora ve a la página de ESPHome y pulsa en “Connect”. En la ventana emergente selecciona tu placa y pulsa en “Conectar”.
  6. Ahora pulsa en “Install” y selecciona el fichero ‘.bin’ obtenido en el paso 9. De nuevo, pulsa en “Install”.
  7. Vuelve a Home Assistant y ve a Configuración > Dispositivos y servicios. Lo normal es que tu dispositivo haya sido descubierto y aparezca en la parte superior, esperando únicamente a que pulses el botón de “Configurar”. De lo contrario pulsa en el botón de “Añadir integración”, busca “ESPHome” e introduce la IP de tu placa en el campo ‘Host’.  Como siempre, te recomiendo que asignes una IP fija en tu router para evitar fallos en el futuro si esta cambia.
  8. Para terminar ve a Configuración > Dispositivos y servicios > ESPHome. Pulsa sobre el enlace «Configurar» correspondiente tu dispositivo. En la ventana emergente marca la casilla «Permitir que el dispositivo realice acciones de Home Assistant» y pulsa en «Enviar». Esto va a permitir que podamos controlar nuestros dispositivos desde la pantalla.

Personalización del dispositivo

Vale, ya has conseguido integrar M5Stack Dial en HA en su versión Mr. Avocado. Ahora voy a explicarte cómo explotar todas sus funciones.

Control del salvapantallas

Para proteger la pantalla hemos implementado una función «salvapantallas» que puedes configurar como quieras. Para ello sólo tienes que acceder a las entidades que expone el dispositivo en Home Assistant y fijarte en estos tres controles.

De forma automática, el menú se oculta transcurridos unos segundos (que puedes configurar con el ‘slider’ de «Screen Saver») y aparece el reloj y la fecha, con el brillo de la pantalla reducido. Además, si activas la función «Auto Lock», la pantalla se apagará por completo unos segundos después (ajusta el valor con el ‘slider’ «Auto Lock»).

Personaliza el fondo de pantalla

Por supuesto, puede usar nuestras imágenes de fondo de Mr. Avocado, o cambiarlas por las que prefieras. Basta con que incluyas la referencia a las imágenes que te gusten en las primeras líneas del código.

substitutions:

# Device customization
# Personalización del dispositivo

  background_image: https://aguacatec.es/wp-content/uploads/2025/02/mravocado_background_white.jpg
  background_image_saver: https://aguacatec.es/wp-content/uploads/2025/02/mravocado_bg_off.jpg
  background_image_device: https://aguacatec.es/wp-content/uploads/2025/02/mravocado_bg_device.jpeg

Configuración del menú

De igual manera puedes personalizar los dispositivos que quieras controlar, y los iconos que los representan. A lo largo del código encontrarás ejemplos para controlar distintos tipos de entidades (light, climate, vacuum, switch, humidifier…). No obstante, si observas la lógica, podrás controlar cualquier dispositivo de Home Assistant!

El menú de ejemplo está optimizado para 8 dispositivos, pero podrías incluir más iconos, e incluso paginar el menú. Además, si pulsas en cada uno de los dispositivos observarás controles avanzados utilizando el ‘encoder’.

Sonidos y despertador

Como nuestro Mr. Avocado tiene un ‘buzzer’, puedes utilizarlo como despertador o alarma haciéndolo sonar cuando tú quieras. Sólo tienes que utilizar la entidad «Alarm» de tipo botón que expone el dispositivo. También puedes activar o desactivar un ‘bip’ al moverte por el menú.

Por cierto! puedes personalizar el tono de la alarma, tal y como te expliqué en esta entrada.

substitutions:

# Sounds
# Sonidos

  menu_sound: 'beep:d=64,o=5,b=255:c7'
  alarm_sound: 'xmen:d=4,o=6,b=200:16f#5,16g5,16b5,16d,c#,8b5,8f#5,p,16f#5,16g5,16b5,16d,c#,8b5,8g5,p,16f#5,16g5,16b5,16d,c#,8b5,8d,2p,8c#,8b5,2p'

Lector NFC

Mr. Avocado también incorpora una lector NFC/RFID, aunque no es su mejor habilidad… ya que me parece que la detección de etiquetas es poco precisa. No obstante, si quieres utilizarlo, no dejes de leer esta entrada en la que te explico en detalle cómo puedes crear automatizaciones específica para cada etiqueta.

Soporte Mr. Avocado

Por cierto, si te ha gustado, también puedes ponerle nuestro soporte de Mr. Avocado!

Gracias a la rosca que incorpora el M5Stack Dial la colocación es muy sencilla. Sólo tienes que enrroscar el dispositivo en la tapa, y pasar el cable USB-C de alimentación por el hueco trasero. Por cierto, te recomiendo uno de estos cables con codo de 90º para facilitar su colocación (aunque también puedes rotar la pantalla a tu gusto).

Por cierto, no pierdas de vista que la carcasa tiene espacio en la parte superior y otro hueco en la parte inferior. Ambos aspectos han sido diseñados de forma intencionada para que puedas aprovechar los 2 puertos de expansión (I2C y GPIO) para añadir tus sensores favoritos.

⭐ Si tienes una impresora 3D puedes descargar este soporte que he diseñado desde nuestro perfil de Patreon. Y si no, también puedes comprarlo en La R3D y recibirlo en casa!
🛟 ¿Dudas? Si necesitas ayuda entra aquí 👈 🎁 Y si te ha gustado y quieres más... 🥑

¡Copiado!
20%
Descuento en todos los productos que funcionan con Curve
¡Copiado!
12%
Descuento en todos los productos de la tienda de Zemismart
¡Copiado!
10%
Descuento en todos los productos de la tienda de Shelly Spain
¡Copiado!
10%
Descuento en todos los productos probados por Aguacatec
¡Copiado!
10%
Descuento en todos los productos de Home Assistant
¡Copiado!
10%
Descuento en todos los productos de la tienda.
¡Copiado!
5%
Descuento en las pantallas de la serie CrowPanel
¡Copiado!
5%
Descuento en todos los productos de la tienda de Lilygo