En esta entrada vamos a integrar M5Stack Dial en HA, un dispositivo con múltiples funciones muy interesantes para controlar nuestra instancia.
Índice
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.
🥑 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:
- En Home Assistant, ve a tu complemento de ESPHome, pulsa en “New device” y “Continue”.
- Dale un nombre a tu dispositivo (por ejemplo, “M5stack Dial”) y pulsa en “Next”.
- Como tipo de dispositivo selecciona “ESP32-S3”. Observarás de fondo que se ha creado un nuevo bloque para tu dispositivo.
- 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.
- 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".
- 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"
- 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.
- Ahora sí, pulsa en “Save” e “Install”. Selecciona “Manual download” y espera a que se compile el código.
- Cuando termine, selecciona la opción “Modern format” para descargar el fichero ‘.bin’ correspondiente.
- Conecta el M5Stack Dial con el cable USB-C de datos por el puerto que trae en la parte inferior a tu ordenador.
- Ahora ve a la página de ESPHome y pulsa en “Connect”. En la ventana emergente selecciona tu placa y pulsa en “Conectar”.
- Ahora pulsa en “Install” y selecciona el fichero ‘.bin’ obtenido en el paso 9. De nuevo, pulsa en “Install”.
- 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.
- 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!




Ofertas & Descuentos
Diseños 3D
Academia





























