Product

How to Integrate the M5Stack Dial with Home Assistant (Full ESPHome Guide)

In this article, we’ll integrate the M5Stack Dial into Home Assistant (HA) — a multifunctional system with many interesting features to control our setup.

Index

  • M5Stack Dial
  • Mr. Avocado
  • Prerequisites
  • Configuration in ESPHome
  • Device Customization
  • Screensaver Control
  • Customize the Wallpaper
  • Menu Configuration
  • Sounds and Alarm Clock
  • NFC Reader
  • Mr. Avocado Support

M5Stack Dial

M5Stack is already a well-known brand to us, with creations like the M5Stack CoreS3SE and the classic Atom Echo. Today, we are going to integrate the M5Stack Dial into Home Assistant — a device that includes the following components:

  • M5StampS3 board, a microcontroller based on the ESP32-S3.
  • 1.28-inch circular TFT touchscreen display.
  • Rotary encoder surrounding the screen, with a push button at the bottom.
  • RFID and card reader (13.56MHz), although I find it to be not very precise.
  • Buzzer for sound output.
  • Battery connection port with built-in charging circuitry.
  • Two expansion ports for I2C and GPIO.
    m5stack-core-dial

All these features packed into a single, ready-to-use device which make the M5Dial a truly compelling gadget. And since it's powered by an ESP32-S3, we can easily integrate it into Home Assistant using ESPHome.

Mr. Avocado

As usual, we wanted to make the most of these features by building a fun and practical project. This time, it's something special — a device co-designed with our Patreon community.

We named it Mr. Avocado, a playful nod to the iconic “Mr. Potato.” The goal was to create a multifunctional device with the following capabilities:

  • Lock screen that shows the current date and time after a few seconds of inactivity
  • Automatic screen-off to save power
  • A streamlined interface to control up to 8 devices
  • Alarm clock with a custom ringtone, configurable via Home Assistant
  • NFC/RFID reader for tags and cards
  • And thanks to the ESP32-S3, it can also act as a Bluetooth Proxy to serve as a presence sensor with Bermuda.

Prerequisites

To integrate the M5Dial into Home Assistant, you’ll need:

  • Obviously, an M5Dial
  • ESPHome installed and set up in Home Assistant
  • A USB-C data cable to power and program the board (charging-only cables won’t work for installing the software)
  • Optionally, the custom stand we designed to turn it into Mr. Avocado

🥑 If you’re just getting started with ESPHome, I highly recommend checking out the academy workshop — it’s a great way to get the most out of it!

ESPHome Setup

Follow these steps to integrate the M5Stack Dial into Home Assistant:

1.        In Home Assistant, open the ESPHome add-on, click “New Device”, then “Continue.”

2.       Give your device a name (for example, “M5Stack Dial”) and click “Next.”

3.       For the device type, select “ESP32-S3.” You’ll see that a new tile has been created for your device.

4.       Click “Skip”, then “Edit” on your device’s tile. Copy the default code that appears and save it—you’ll need parts of it later.

5.       Now, copy the code below and use it to replace the default code in 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));
          }

    

 

⚠️ While we’ve managed to implement all these features for Mr. Avocado, my recommendation is to comment out (or remove) any parts of the code you’re not planning to use. This will help improve the device’s performance and prevent it from freezing.

6.       This code does not include the credentials needed for your device to connect to your Wi-Fi network and your Home Assistant instance. You’ll need to add them manually.
Specifically, I’m referring to the following lines from the code you copied in Step 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"

    

 

7.       What you need to do is to find the corresponding lines in the code (they’re at the top) and add your Wi-Fi and Home Assistant credentials there.

8.       Now, click “Save” and then “Install.” Choose “Manual download” and wait for the code to compile.

9.       Once the compilation is complete, select the “Modern format” option to download the corresponding .bin file.

10.     Connect the M5Stack Dial to your computer using a USB-C data cable via the port on the bottom of the device.

11.       Next, go to the ESPHome web page and click “Connect.” In the popup window, select your board and click “Connect.”

12.      Then click “Install” again and choose the .bin file you downloaded in step 9. Click “Install” once more to flash the firmware.

13.      Return to Home Assistant and go to Settings > Devices & Services.
In most cases, your device should be automatically discovered and appear at the top, waiting for you to click “Configure.”
If not, click “Add Integration,” search for “ESPHome,” and enter your board’s IP address in the Host field. As always, it’s a good idea to assign a static IP to your device in your router settings to avoid connection issues later on.

14.     To finish, go to Settings > Devices & Services > ESPHome, click the “Configure” link next to your device, and in the popup window, check the box that says, “Allow this device to make Home Assistant API calls,” then click “Submit.”
This will allow us to control devices directly from the screen.
 

Device Customization

Alright, you’ve successfully integrated the M5Stack Dial into Home Assistant as Mr. Avocado. Now let’s go over how to take full advantage of its features.

Screensaver Control

To protect the screen, we’ve added a customizable screensaver function. You can easily tweak it by accessing the entities exposed by the device in Home Assistant. Pay special attention to these three controls.

m5stack-home-assistant-screensaver-setting

After a few seconds of inactivity (you can set the duration using the “Screen Saver” slider), the menu automatically hides, showing the clock and date with reduced screen brightness.
Additionally, if you enable “Auto Lock,” the screen will turn off completely a few seconds later (adjustable using the “Auto Lock” slider).

Customize the Wallpaper

Of course, you can use our default Mr. Avocado background images — or replace them with your own.
Just update the image references in the first few lines of the code to point to your preferred files.

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

    

Menu Configuration

You can also customize which devices you want to control and the icons that represent them. Throughout the code, you’ll find examples for controlling different types of entities—lights, climate, vacuum, switches, humidifiers, and more. But if you understand the logic, you can control any Home Assistant device!

The example menu is optimized for 8 devices, but you can add more icons or even paginate the menu. Plus, if you press on any device, you’ll see advanced controls using the rotary encoder.

Sounds and Alarm Clock

Since Mr. Avocado has a built-in buzzer, you can use it as an alarm or timer, making it sound whenever you want. Just use the “Alarm” button entity exposed by the device. You can also enable or disable a beep sound when navigating through the menu.

m5stack-home-assistant-sound-setting

By the way, you can customize the alarm tone, as I explained in this article.

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'

    

NFC Reader

Mr. Avocado also includes an NFC/RFID reader, although it’s not its strongest feature — I find the tag detection to be somewhat imprecise.
Still, if you want to use it, be sure to check out this article where I explain in detail how to create automations for each specific tag.

Mr. Avocado Stand

By the way, if you like it, you can also get our custom Mr. Avocado stand!

Thanks to the threaded mount built into the M5Stack Dial, attaching it is very easy. Just screw the device onto the stand’s base and route the USB-C power cable through the rear opening.
I recommend using a 90º angled USB-C cable to make positioning easier (although you can also rotate the screen to your liking).

Also, keep in mind the case has space at the top and an opening at the bottom—both intentionally designed so you can access the two expansion ports (I2C and GPIO) to add your favorite sensors.

If you have a 3D printer, you can download this stand I designed from our Patreon profile. If not, you can also purchase it from La R3D and have it shipped to your home!

m5stack-dial-home-assistant-integrations

Source: AguacaTEC

Author: TitoTB

0 comments

  • There are no comments yet. Be the first one to post a comment on this article!

Leave a comment

Please note, comments must be approved before they are published

Liquid error (layout/theme line 224): Could not find asset snippets/pe-disco-countdown-timer.liquid