News
M5Stack News.

If you’re just getting started with Home Assistant, one of the easiest and most fun projects to try is smart lighting. This guide walks through how to set up the M5Atom Lite—a compact ESP32-based module—as a smart RGB light controller, fully integrated with Home Assistant using ESPHome.

What You’ll Need

  • Home Assistant installed and running
  • A stable Wi-Fi connection
  • A USB data cable to connect with computer

1. Environment Setup

Before we begin, make sure you have Home Assistant installed. You can follow the official documentation for your preferred platform.

Once Home Assistant is up and running:

  • Go to SettingsAdd-onsAdd-on Store
  • Search for and install the ESPHome add-on
  • After installation, enable Show in sidebar for quick access

2. Adding the Device

1.     Open the ESPHome sidebar and click NEW DEVICE in the lower right corner.

2.   Click CONTINUE when the setup screen appears. 

3.   Name your device (e.g., Atom-Lite), then proceed.

4.  On the device type screen:

o   Uncheck Use recommended settings

o   Select ESP32 → choose M5Stack-ATOM

5.   Click NEXT and copy the encryption key that appears.


6.   Choose Manual download to begin compiling the firmware.

3. Firmware Setup & Compilation

Back on the ESPHome dashboard, you’ll now see your new Atom-Lite device listed.

1.     Click EDIT to open the YAML configuration editor

2.   Replace the content with the following configuration (update your Wi-Fi credentials!):

esphome:
  name: atom-lite
  friendly_name: Atom-Lite

esp32:
  board: m5stack-atom
  framework:
    type: arduino

logger:
api:
  encryption:
    key: "*********"

ota:
  - platform: esphome
    password: "*****************"

wifi:
  ssid: "*********"
  password: "***********"
  ap:
    ssid: "Atom-Lite Fallback Hotspot"
    password: "jFsIc2XGuKRe"

captive_portal:

binary_sensor:
  - platform: gpio
    pin:
      number: GPIO39
      mode: INPUT
      inverted: true
    name: "Atom Button"
    id: atom_button
    filters:
      - delayed_on: 50ms
      - delayed_off: 50ms
    on_multi_click:
      - timing:
          - ON for at most 0.8s
          - OFF for at most 0.5s
          - ON for at most 0.8s
          - OFF for at least 0.2s
        then:
          - logger.log: "Double Clicked"
          - light.turn_on:
              id: atom_light
              red: 100%
              blue: 50%
              green: 20%
              brightness: 50%
      - timing:
          - ON for at least 0.8s
        then:
          - logger.log: "Single Long Clicked"
          - light.turn_on:
              id: atom_light
              green: 100%
              blue: 50%
              red: 30%
              brightness: 100%

light:
  - platform: neopixelbus
    type: GRB
    pin: GPIO27
    num_leds: 1
    variant: sk6812
    name: "Atom RGB Light"
    id: atom_light
    restore_mode: RESTORE_DEFAULT_OFF
    effects:
      - random:
          name: "Random"
          transition_length: 1s
          update_interval: 1s 

    

 

3.   Click SAVE, then INSTALL → Manual download to compile

Note: The first compilation may take several minutes, depending on your setup and network.

4. Flash the Firmware to M5Atom Lite

After compiling the firmware:

1.     Click DOWNLOAD, and choose Factory format

2.   Connect the M5Atom Lite to your computer using a USB-C data cable

3.   Back in ESPHome, select INSTALL → Plug into this computer

4.  Click Open ESPHome Web

5.   Press CONNECT, then select the detected serial port

6.   Click INSTALL and wait for the  firmware installation to complete.

Once flashing is complete, the device will restart and attempt to connect to your Wi-Fi.

5. Add the Device to Home Assistant

Once Atom Lite is online:    

1.     Open Settings → Integrations in Home Assistant

    2.   Under Discovered, click ADD and follow the prompts to integrate it

    6. Create an Automation

    You can now set up a basic automation using the button to control the light:

    1.     In the Home Assistant page, go to Settings → Device& Service

    2.   Locate ESPHomehit Atom-Lite → click Automations 

    3.   Select Create new automation → ADD TRIGGER → Entity State → Atom Button 

    4.  In the When section, change the status from Off to On

    5.  In the Then do section, select ADD ACTION → Light → Toggle → + Choose entity → Atom-Lite RGB Light → Save

    This simple setup turns the button into a light switch for the RGB LED.

    7. Add Atom RGB Light to Your Dashboard

    To control the RGB light from the Home Assistant interface:

      1.     Go to Overview → Edit

      2.   In the By card page, input Light on the search cards Select the Light card type

      3.  Choose the Atom RGB Light entity

      4.   Save the changes

      The light can now be toggled and color-adjusted directly from the dashboard.

      8. Demo & Behavior

      Here’s how your new Atom-Lite smart RGB light behaves:

      • Single Click → RGB light toggles pink
      • Long Press (2+ seconds) → RGB light switches to green
      • Light state updates in real-time and can also be controlled via the Home Assistant dashboard

      Conclusion

      Home Assistant makes smart home control simple, with M5Stack Atom-Lite and ESPHome, setting up RGB lighting is just the start. With the same process, you can go further by adding mode device like a human presence sensor to detect movement, turn on lights automatically, or turn on the AC and set it to the optimal temperature when someone enters the room.

      2025-08-05

      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

      2025-07-15

      Launched in October 2023 by M5Stack, the StickC Plus2 has quickly become a popular choice among makers, educators, and embedded system developers. It’s widely used in IoT projects, embedded systems, and cybersecurity applications. Many users even compare it with Flipper Zero as affordable alternatives for their similar roles in wireless testing and cybersecurity tools.
      product image of M5StickC Plus2

      In this article, we’ll walk you through the StickC Plus2’s key features, programming options, and practical project ideas.

      What’s StickC Plus2?

      The M5StickC Plus2 is a compact, all-in-one ESP32-S3 development board designed for rapid prototyping and embedded applications. It integrates a 1.14” TFT display, IMU, microphone, infrared transmitter, and rechargeable battery — all within an ultra-portable form factor.

      With support for Arduino, UIFlow2, and MicroPython, the StickC Plus2 is well-suited for a wide range of applications, from IoT devices and wearable interfaces to educational tools and lightweight cybersecurity projects.

      M5StickC Plu2 vs StickC Plus

      M5StickC Plus2 builds on the StickC Plus with a new ESP32-PICO-V3-02 chip, increased battery capacity, improved Wi-Fi and infrared performance, and an upgraded CH9102 UART for more stable USB communication. However, the original StickC Plus has a built-in battery management IC that Plus2 lacks.

      The differences between M5StickC Plus and Plus 2
      Additionally, the two devices differ slightly in their power-on and power-off behavior.

      M5StickC vs Flipper Zero

      M5StickC Plus2 is a compact ESP32-S3 board ideal for developers looking to build custom IoT or cybersecurity tools. With third-party firmware (Like Bruce),  it can emulate some Flipper Zero features like RF, IR, and USB HID.

      Flipper Zero, on the other hand, is a ready-made hacking device with built-in wireless protocols, perfect for users who want an all-in-one security testing tool without programming.

      Getting Started with Bruce Firmware on M5StickC Plus2

      Designed for the ESP32 microcontroller platform, Bruce merges Wi-Fi, RF (Radio Frequency), BLE (Bluetooth Low Energy), IR, and USB capabilities into a single, modular offensive toolkit tailored for red team activities and security research.

      bruce firmware for M5Stack

      The easiest way to install Bruce on your M5Stack device is with M5Burner, M5Stack’s official firmware flashing tool.
      If you haven’t downloaded it yet, click here to get started.

      1.    Find the Bruce Firmware on M5Burner

      Open M5Burner > Select STICKC > Scroll down and you’ll see Bruce for StickC plus2 (or simply type “Bruce” into the search bar to locate it quickly.)

      Search result of Bruce in M5Burner

      2.  Download the Bruce Firmware to M5StickC Plus2

      Click Download > Connect your device via a USB cable > Click Burn, select the corresponding USB port and default baud rate 1500000 > Hit Start to begin flashinghow to burn bruce to stickC plus2

      Note: Bruce is an open-source, community-developed firmware project not officially affiliated with M5Stack. A warning may pop up when you download the firmware.

      3.  Exploring the Bruce Firmware Functions on the device

      Once you see the message "Burn successfully, click here to return", click it to finish, and press the reset button on your device once to let it start. Now you can begin exploring its features!image shown bruce is burnt successfully on stickC Plus2

      Note: If any issues occur during flashing, you may need to install a USB driver on your computer, or long-press the reset button on the main controller to enter download mode. For details, refer to the documentation page of your specific main controller device.

      ⚠️ Disclaimer: All use must comply with local laws. Use only for educational or testing purposes on your own devices.

      1.    Wi-Fi Attacks: Beacon flooding, deauthentication, EvilPortal phishing pages, EAPOL handshake capture, ARP spoofing, and wardriving GPS mapping.

      The captive portal is one of Bruce’s most well-known features, commonly used for Wi-Fi phishing attacks. To launch it, go to Wi-Fi > Evil Portal, enter the target Wi-Fi name, and specify an IP address (or use the default). The phishing portal will begin broadcasting immediately.

      steps to launch captive portal on StickC Plus2

      On the screen, you'll see two URLs, these are used to view captured credentials and spoofed SSIDs. When a user attempts to log in through the fake portal, their credentials are captured by the device.

      StickC Plus2 screen showing URLs

      Public Wi-Fi networks present inherent security risks. It is advisable to avoid using such networks whenever possible. If usage is necessary, refrain from logging into personal accounts or transmitting sensitive information to minimize potential exposure. 

      2.  Infrared & BadUSB: Offers IR transmission and reception, along with USB HID keyboard / mouse simulation on supported boards.

      For example, navigate to the main menu, select the BadUSB section, choose a script, then run it to start USB keystroke injection. You can also watch this quick video by Pirata to see it in action: https://www.youtube.com/shorts/F_7QlGVx-XU

      BadUSB running on StickC Plus2

      3.    Sub-GHz & RF Hacking: Integrates modules like CC1101 and NRF24 for jamming, scanning, and replaying RF signals.

      4.  RFID / NFC Tools: Uses the PN532 chip to support Mifare Ultralight tag emulation, reading, writing, and P2P communication.

      5.  Bluetooth LE: Scanning, beacon broadcasting, and early BLE payload experiments were implemented as early as version 1.3.

      6.  Web Interface & Scripting: Features a full web UI for module control, LittleFS/SD card file management, and an integrated JavaScript scripting interface for automation and extension.

      Related Topic: Is Bruce Firmware Legal?

      Bruce is licensed under the AGPL-3.0 for its firmware, emphasizing true software freedom. It supports a variety of ESP32 and ESP32-S3 development boards, except for the M5StickC Plus2, it's also suitable for: Cardputer, Core1 (Basic), Core2, CoreS3 / SE, StickC-Plus. It lowers the barrier for both beginners and experts looking to dive into embedded offensive tooling.

      2025-07-02

      Meshtastic is an open-source, off-grid communication project that uses LoRa (Long Range) radio technology to enable secure, long-distance messaging without the internet or cellular networks. It leverages point-to-point(P2P) and mesh communication in areas with no internet or cellular access by forming a self-sufficient network of devices. Utilizing ultra-low power hardware and license-free frequency bands, it enables long-range message transmission, making it ideal for outdoor adventures, emergency rescue, rural connectivity, and low-power IoT applications.

      the topology of meshtastic network

      In this guide, we'll walk you through building your very own Meshtastic LoRa node using Module LoRa868 and ESP32-based M5Stack Core series controllers.

      What You’ll Need

      Step 1: Flashing the Meshtastic Firmware

      The easiest way to install Meshtastic firmware on your M5Stack device is with M5Burner, a simple and free intuitive firmware flashing tool developed by M5Stack.

      1.    Locate the Meshtastic Firmware

      Open M5Burner > Select ALL from the list in the left column > Input "Meshtastic" into the search bar and choose the firmware that match your device > Click Download.

      Find meshtastic firmware in M5Burner

      If you haven’t installed M5Burner, click here and follow the installation steps to download it to your computer.

        2. Connect Your Device and Start Flashing

             Connect the main controller to your computer via USB data cable > Click Burn > Select the correct USB port and set baud rate to 1500000 > Click Start to begin flashing.

        burn meshtastic firmware from M5Burner

             Wait for the message "Burn successfully" > Click "Click here to return" > When the Meshtastic logo "//\" appears on the screen, disconnect the device from your computer.

        Note: if flashing fails, try installing the USB driver on your computer or long-press the reset button on the main controller to enter download mode. For more information, refer to your device’s documentation page.

        Step 2: Configure the Module LoRa868 v1.2 and Connect Antenna

        After the firmware successfully downloaded on the device:

             Check M5Burner firmware for pin info and set DIP switches by following Module LoRa868 v1.2 DIP Switch Guide (for Core1/Core2: long pins 2,5,7 and short pin 1 ON).

        checking pin info and set DIP switch on M5 Core

             Remove red dust cap > Install antenna > Connect the module to the device.

             Power on the device, then the Meshtastic logo "//\" will appear.

        meshtastic logo showing on M5 core screen

        Safety Warning

        Do NOT connect or power on the device without installing the antenna, as this may cause permanent hardware damage!

        Step 3: Install the Meshtastic App on your iOS or Android device

        1.     Install the Meshtastic app on Your Phone

        Download the Meshtastic app from the Google Play or Download APK from GitHub(for Android) or App Store(for IOS).

        2.   Pair the Device

        Open the Meshtastic app and follow the on-screen instructions to pair your device via Bluetooth—nearby devices will be detected automatically. The iOS and Android Meshtastic apps offer similar features but have different interfaces, so setup steps and screenshots are shown separately for each platform.

        iOS Setup

           

        3. Configure Device Settings

        After pairing, you could set the Lora region, select the appropriate region (e.g., EU 868MHz), username in the app.

        When running Meshtastic, the ESP32 can't use Wi-Fi and Bluetooth simultaneously. Bluetooth is enabled by default. If you turn on Wi-Fi, Bluetooth will be disabled. To re-enable it, connect the device to your computer via USB and use the Meshtastic Web Client in Chrome to disable Wi-Fi.

        Step 4: Add GPS To Your Meshtastic Node

        Core1/Core2 with LoRa868 v1.2 doesn’t have built-in GPS, but you can share your smartphone’s GPS location with the device. It's useful for team members to track each other during outdoor activities.

        iOS Setup

         

        Step 5: Send and Receive Message

        With other nodes show up in the list, you're connected to the mesh and can start messaging via the Meshtastic app. 

        iOS Chat

         

        Is Meshtastic Legal?

        Yes, it’s legal. Meshtastic operates on license-free frequency bands such as 433 / 470 / 868 / 915 MHz which is in full compliance with FCC regulations. 

        How far does Meshtastic work?

        The estimated range of this Meshtastic setup is around 4 km (2.49 miles). But the range between two Meshtastic nodes varies based on antenna setup, and environmental conditions. You may try moving the device around to test the range, check the signal to ensure stable connectivity.

        2025-06-04

        For those exploring what hardware ChatGPT runs on, the traditional answer involves large-scale cloud infrastructure. However, with the OpenAI API and lightweight microcontrollers like the M5Stack ESP32-based AtomS3R, it’s now possible to build a compact, connected ChatGPT AI device. Paired with the Atomic Echo Base for audio I/O, this setup enables a tiny AI voice assistant capable of real-time voice interaction via Wi-Fi.

        In this article, we’ll walk you through how to build your own AI-powered voice assistant using OpenAI—no coding required.

        a ChatGPT Voice Assistant built on M5 AtomS3R and Atomic Echo Base

        M5Stack AtomS3R

        The M5Stack AtomS3R is a compact microcontroller powered by the ESP32-S3 chip, measuring just 24 × 24 mm. It supports Wi-Fi, Bluetooth, and offline voice wake-up, making it ideal for building portable AI voice assistant and IoT applications.

        Required Hardware

        Download OpenAI Voice Assistant for AtomS3R Firmware from M5Burner

        M5Burner is a tool that enables creators to upload firmware and allows users to flash it onto M5Stack devices. If you haven’t downloaded it before, please select the version compatible with your operating system to proceed.

        Software Version Download Link
        M5Burner_Windows Download
        M5Burner_MacOS Download
        M5Burner_Linux Download

         

        1.     Download the OpenAI Firmware

        Double click M5Burner > Locate the OpenAI Voice Assistant for AtomS3R Firmware > Click Download.

        Find the "OpenAI Voice Assistant for AtomS3R" Firmware in M5Burner

        2.   Get Your OpenAI API Key

        An API key is required after clicking Download. Visit OpenAI's platform > Complete registration and login > Review pricing for Realtime API and select the package > Navigate to the API Keys section and create a new key

        get OpenAI key on OpenAI platform

        3.   Firmware Flashing

        I.              Input your Wi-Fi connection information and OpenAI API keys in the pop-up window > Hit Next

        input Wi-Fi password and OpenAI key to burn

        II.            Connect your AtomS3R via USB-C > Press and hold the Reset button for ~2 seconds until the green LED turns on, then release to enter the download mode.

        III.         Select the correct COM port and click “Start” to start flashing.

        start flashing
        image showing burn successfully

        Talking to Your AI Assistant

        Once completed, your device will reboot and connect to OpenAI for real-time voice interaction. You could speak directly to your assistant and receive instant responses.

        the atom voice assistant in connecting and ready status

        If you prefer a more customizable approach to integrate the OpenAI into your project instead of downloading the prebuilt firmware from M5Burner, you could visit GitHub for the original source code.

        2025-05-22

        May 9, 2025 – M5Stack, a leader in modular IoT and embedded development platforms, unveils Tab5, a next-generation 5-inch smart touch terminal powered by the advanced 400MHz ESP32-P4 dual-core RISC-V processor. Designed for industrial control, smart home hubs, edge intelligence, and IoT applications, Tab5 offers high performance, versatile interfaces, and seamless connectivity through Wi-Fi 6 and Bluetooth 5.2 in one compact tablet. Combining multimedia capabilities with modular expandability, Tab5 empowers developers to create flexible, scalable solutions for a wide range of industries.

        High-Performance Core for Embedded Intelligence

        At the heart of Tab5 is the ESP32-P4 dual-core RISC-V processor, running at 400MHz and backed by 32MB of PSRAM and 16MB Flash, delivering robust performance for embedded applications and edge computing. While the ESP32-P4 handles processing tasks, wireless connectivity is provided by the onboard ESP32-C6-MINI-1U module, which supports Wi-Fi 6 and Bluetooth 5.2. Equipped with 3D internal antennas, this module ensures high-throughput, low-latency communication across a wide range of IoT scenarios.

        Immersive Display and Interactive Design

        Tab5 features a 5-inch IPS touchscreen with a 1280×720 resolution and a GT911 capacitive multi-touch panel, providing responsive touch interaction and high-resolution visuals for an intuitive user experience. Built-in 2MP camera (1600×1200 resolution) and dual microphones with a speaker enable intelligent interactions such as facial recognition, image processing, and voice commands. The camera interface uses MIPI-CSI to enable HD video capture and edge AI tasks such as object tracking.

        Additional onboard features including RESET/BOOT buttons, a BMI270 six-axis IMU for motion sensing, and a Micro SD card slot provide enhanced control, motion tracking, and local data storage—empowering developers to create multi-modal edge applications.

        Rich Interfaces and Modular Expandability

        Tab5 is designed for flexibility and scalability with a comprehensive suite of I/O interfaces:

        • RS485, Grove, M5Bus, GPIO.EXT
        • USB-A (Host) and USB-C (USB 2.0 OTG)
        • 3.5mm audio jack
        • Support for UART, I2C, and ADC protocols
        • STAMP solder pads for easy integration of communication modules like Cat.M, NB-IoT, or LoRaWAN

        These interfaces ensure seamless integration with the M5Stack hardware ecosystem, enabling plug-and-play expansion through a wide range of functional modules.

        Battery Support & Deployment Options

        Tab5 is available in two variants:

        • Tab5 Kit – Includes a removable 2S NP-F550 7.4V 2000mAh lithium-ion battery
        • Tab5 – Battery not included

        Both models support external NP-F550 lithium batteries, offering flexible deployment options for fixed installations or mobile applications.

        Versatile Applications Across Industries

        Tab5 is engineered to address diverse application scenarios across industries. In industrial settings, it functions as an effective HMI for control panels and data visualization. Its multi-protocol wireless support makes it ideal for smart home hubs and gateways. In education and maker spaces, it supports UIFlow 2.0, Arduino IDE, ESP-IDF and PlatformIO, enabling flexibility for both beginners and advanced developers. With a built-in camera and dual microphones, it also enables AI vision and voice interaction for use cases like smart kiosks and voice-controlled terminals.

        “Designed for seamless development, the Tab5 delivers innovation and usability, empowering creators to turn ideas into reality,” said Jimmy Lai, Founder and CEO of M5Stack.

        Built to Expand

        To further enhance Tab5’s utility, a dedicated keyboard accessory is in development — stay tuned for the full reveal. Designed for seamless integration with the M5Stack ecosystem, Tab5 supports a broad range of functional modules, offering developers the flexibility to tailor solutions across diverse application needs.

        Tab5 is now available through M5Stack’s official store and global distributors. For more details, please visit www.m5stack.com.

        2025-05-08

        Today, we’re checking out another great device for running our assistant with excellent performance, and learning how to activate Assist using the M5Stack CoreS3SE.

         

        Index

        • M5Stack CoreS3SE
        • M5Stack CoreS3SE vs ESP32-S3-BOX-3
        • Prerequisites
        • Configuring the M5Stack CoreS3SE
        • Custom support

        M5Stack CoreS3SE

        Well, let's start with the basics. M5Stack is the brand behind well-known devices like the Atom Echo. As I mentioned before, this was the first external device used to interact with Home Assistant. I’m convinced that if you're getting into the world of local assistants, you’re already familiar with it.

        But Atom Echo isn’t the only option. M5Stack also makes a variety of ESP-based devices that are easy to integrate with Home Assistant. Think of them like custom ESPHome builds, but without the hassle of soldering, wiring, or configuring components from scratch.

        Now that the introduction is done, today I want to introduce you to the M5Stack CoreS3SE, a device that’ll definitely remind you of the ESP32-S3-BOX-3 we looked at recently, as we can also activate Assist with the M5Stack CoreS3SE.

        M5Stack CoreS3SE vs ESP32-S3-BOX-3

        Since the goal of this guide is to activate Assist using the M5Stack CoreS3SE, I’ll go over the differences and similarities I’ve found between the two devices.

        • Both run on the ESP32-S3 chip and are fully compatible with ESPHome.
        • Both come with two microphones for better voice recognition.
        • Both have a built-in speaker and a touchscreen display.
        • The setup process is nearly identical for both.
        • The CoreS3SE is about €10 cheaper than the BOX-3B.
        • The CoreS3SE has a black frame, while the BOX-3B comes in white.
        • The CoreS3SE is a bit smaller and more square in shape compared to the BOX-3B.
        • The M5Stack CoreS3SE doesn’t come with a stand or USB-C cable by default, whereas the ESP32-S3-BOX-3B does (which we’ll actually turn to our advantage, as you’ll see at the end of this article).

        Prerequisites


        To activate Assist on the M5Stack CoreS3SE, you’ll need:

        • You have set up Assist in Home Assistant.
        • A M5Stack CoreS3SE device.
        • A USB-C data cable to power the DATA board (with a charging cable you will not be able to install the software) .

        🥑 If you're setting up Assist, I highly recommend checking out the workshop from the academy to get the most out of it!

        Configuring the M5Stack CoreS3SE

        Follow these preparation steps to get your M5Stack CoreS3SE up and running:

        1.  In Home Assistant, go to your ESPHome add-on, click on “New Device”, then “Continue”.

        2.  Give your device a name (e.g., “Assist”) and click “Next”.

        3.  For the device type, select “ESP32-S3”. You’ll see a new block for your device appear in the background.

        4.  Click “Skip”, then click “Edit” on your device’s card. Copy the code that appears and keep it handy — you’ll need part of it later.

        5.  Head over to the GitHub page linked in the guide, copy the provided code, and replace the original ESPHome code with it.

        6.  Important: This new code doesn’t include your Wi-Fi or Home Assistant credentials, so you’ll need to manually add them. Specifically, look for the lines from the original code that you copied in step 4 and insert them into the new code.

        # 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 find the corresponding lines in the code  (it's at the beginning)  and  add the corresponding information . This code snippet would look like this:

        # Enable Home Assistant API
        api:
          encryption:
            key: "1fPr5BBxCfGiLLPgu/OEILB1T4XUdXN4Sh2pic4mgQk="
          on_client_connected:
            - script.execute: draw_display
          on_client_disconnected:
            - script.execute: draw_display
        
        ota:
          - platform: esphome
            password: "a048862eecd273b682fde5d1a93acc36"
        wifi:
          ssid: !secret wifi_ssid
          password: !secret wifi_password
          # Enable fallback hotspot (captive portal) in case wifi connection fails
          ap:
            ssid: "M5Stack-Cores3Se"
            password: "uCh6BjJ34Tnl"
          on_connect:
            - script.execute: draw_display
            - delay: 5s # Gives time for improv results to be transmitted 
          on_disconnect:
            - script.execute: draw_display

         

        8.  Now click “Save” and then “Install.” Select “Manual download” and wait for the code to compile. It might take a while, so feel free to do something else in the meantime.

        9.  Once it’s finished, choose the “Modern format” option to download the corresponding .bin file.

        10.  Connect the M5Stack CoreS3SE to your computer using a USB-C data cable, plugging it into the port on the left side of the device.

        11.  Go to the ESPHome page and click “Connect.” In the pop-up window, select your device and click “Connect” again.

        12.  Click “Install” and select the .bin file you downloaded in step 9. Then click “Install” again to upload it to the device.

        13.  You may see a message saying “HA not found.” Don’t worry — this is normal. In Home Assistant, go to Settings > Devices & Services, where the device should appear as discovered. Click “Configure” and then “Submit.”

        14. That’s it! You can now activate Assist with the M5Stack CoreS3SE. By default, just say “Ok, Nabu,” and it’ll respond using your preferred assistant settings.

        Personalized support

        As I mentioned in the comparison, the M5Stack CoreS3SE doesn't come with a standard stand, which gives us the opportunity to create one to our liking. For example, this time I wanted to create a simple and elegant stand, taking advantage of the black frame.

        If you have a 3D printer, you can download this stand I designed for FREE from our Patreon page

         

        Source: AguacaTEC
        Author: TitoTB
        2025-04-30

        If you've ever needed to update firmware on an STM32-based device, you know the struggle—setting up an debugger, dealing with drivers, and ensuring proper connections. What if you could do it all without a dedicated programmer, using just your M5Stack Core2 or CoreS3? Enter M5 DAPLink, a powerful solution that transforms your M5 device into a standalone offline programmer.

        Why Use M5 DAPLink?

        Imagine being able to flash firmware anywhere, anytime—without needing a PC connection or extra hardware. Whether you're in the field, a classroom, or a factory line, M5 DAPLink makes firmware updates seamless. Just load your firmware onto a MicroSD card (for Core2) or a virtual USB drive (for CoreS3), and you're ready to go!

        1. Preparations

        Required hardware:
        • Core2 / CoreS3
        • Module Bus
        • MicroSD card
        • Card reader
        • Male-to-female Dupont wires
        • Female-to-female Dupont wires

        2. Flashing the DAPLink Firmware

        M5Burner
        Download the M5Burner firmware flashing tool for your operating system from the links below. Extract and launch the application.

        Software Version

        Download Link

        M5Burner_Windows

         Download

        M5Burner_MacOS

         Download

        M5Burner_Linux

         Download

        Open the burner tool, select the corresponding device type from the left menu, and download the matching firmware for your device.

        CoreS3 DAPLink

        Download the firmware for CoreS3: CoreS3 → CoreS3 DAPLink. Refer to the CoreS3 documentation to learn how to enter download mode. Once the device is detected by your computer, proceed with flashing.

        CoreS2 DAPLink

        Download the firmware for Core2: Core2 → Core2 DAPLink. Refer to the Core2 documentation to install the required USB driver. Once the device is detected, proceed with flashing.

         

        3. Importing Flashing Algorithms and Firmware

        Download the algorithm package below. This package, along with the firmware, is imported into the host device and used to match different chip models during flashing. Some algorithms are preloaded in the firmware, while manual import allows for additional algorithm support. Import methods vary by device—refer to the details below.

        algorithm


        • Virtual USB Drive Import
        This method is currently only supported for CoreS3.
        Extract the algorithm package and copy it to the CoreS3 virtual USB drive. Create a program folder in the root directory to store the firmware files (hex/bin) for flashing.

        • MicroSD Import
        This method is currently only supported for Core2.
        Extract the algorithm package and copy it to the MicroSD card. Create a program folder in the root directory to store the firmware files (hex/bin). The directory structure is the same as the CoreS3 virtual USB method.

        • Web Import
        This method works for both Core2 and CoreS3. Imported data is automatically saved to the device's flash storage partition. (Note: For Core2 with an SD card, files are stored on the SD card. For CoreS3, safely eject the virtual USB drive before importing via the web.)
        Power on the device to enable its AP hotspot. Connect your computer to the hotspot and visit 192.168.4.1 in a browser. Click Program to navigate to the file upload page, then upload the algorithm and firmware files.

        4. Device Connection

        The DAPLink pin mappings for the firmware are as follows:

        For example, to update the firmware of a Unit EXT.IO2, locate the programming pads after opening the device casing and connect them according to the pin mapping above. If contact is unstable, tilt the Dupont wire pins to ensure proper connection.

        5. Starting the Flashing Process

        After importing the algorithms and firmware, the device will display available options upon startup. Select the algorithm and firmware matching your target device. Click Idle, then Busy to begin flashing. (Note: Some chips, like STM32F0xx series, may require pressing Busy twice.)

        6. Using with Module Bus

        For daily DAPLink debugging, the Module Bus is highly recommended for easier wiring. It extends the MBus interface to the board's edge and includes two sets of 2.54-15P 90° headers for seamless Dupont wire connections.

        Why M5 DAPLink is a Game-Changer

        • No extra hardware needed – Your M5 device becomes a portable STM32 programmer.
        • Works offline – No need for a PC once set up.
        • Flexible import methods – USB, SD card, or web upload.
        • Perfect for fieldwork and education – Quick firmware updates anywhere.

        With M5 DAPLink, you turn ideas into reality faster—no hassle, no complicated setups. Ready to give it a try? Download the firmware today and start flashing like a pro!

        2025-04-08

        In this article, we will integrate the M5Stack Air Quality Kit with Home Assistant to monitor air quality.

        Index

        • Air Quality Sensor
        • M5Stack Air Quality Kit
        • Prerequisites
        • Configuration in ESPHome
        • Device Information

        Air Quality Sensor

        While air quality may not be a concern for everyone, those of us living in large cities or near industrial areas are increasingly worried about the air we breathe at home. This concern is not unfounded—numerous studies have shown that long-term exposure to pollutants can lead to respiratory diseases such as asthma and bronchitis. Over time, it can also shorten lifespan and increase the risk of chronic illnesses like lung cancer.

        From this perspective, home automation can help mitigate these effects by monitoring air quality, sending alerts when pollution levels rise, or even activating ventilation or air purification systems. If you're concerned about overall environmental pollution, you can refer to indexes like the World Air Quality Index.

        However, whether you distrust external data (for instance, if monitoring stations are conveniently placed in green zones) or simply want to measure the specific data in your own home, an air quality sensor is essential. When it comes to finding a sensor that is comprehensive, integrable, and reasonably priced, debates always arise.

        M5Stack Air Quality Kit

        M5Stack is a well-known brand that offers devices like the M5Stack CoreS3SE and the historically significant Atom Echo. In this case, we’ll integrate the M5Stack Air Quality Kit with Home Assistant. This device is based on the ESP32S3FN8 chip and can measure CO2, VOCs, PM1.0, PM2.5, PM4, and PM10 particles, along with temperature and humidity (though some reviews suggest the accuracy of the latter two may be questionable). It also features an e-ink display and a built-in battery.

         

        By the way, while this article focuses on integrating the M5Stack  Air Quality Kit with Home Assistant, you can also use it directly with your mobile device to monitor its readings. The video below explains the setup process.

        Prerequisites

        To integrate the M5Stack Air Quality Kit into Home Assistant, you will need:

        • M5Stack Air Quality Kit 
        • ESPHome installed in Home Assistant.
        • A USB-C cable to power the DATA board (a charging-only cable will not allow software installation).

        🥑 If you’re new to ESPHome, I recommend checking out the Academy workshop to get the most of it!

        Configuration in ESPHome

        Follow these steps to integrate the M5Stack Air Quality Kit into Home Assistant:

        1. In Home Assistant, go to your ESPHome plugin, click “New Device,” and then click “Continue.”

        2. Name your device (e.g., “ Air Quality Kit”) and click “Next.”

        3. Select “ESP32-S3” as the device type. You'll notice that a new block has been created for your device in the background.

        4. Click “Skip” and click “Edit” on the device block above. Copy the code that appears and save it, as you will need some parts of it later.

        5. Copy the following code (which I found on reddit and edited slightly) and replace the above code in ESPHome.

        substitutions:
          devicename: lounge-airq
          friendlyname: Lounge AirQ
          location: Lounge
          sensor_interval: 10s
        
        esphome:
          name: ${devicename}
          friendly_name: ${friendlyname}
          area: ${location}
          platformio_options:
            board_build.mcu: esp32s3
            board_build.name: "M5Stack StampS3"
            board_build.upload.flash_size: 8MB
            board_build.upload.maximum_size: 8388608
            board_build.vendor: M5Stack
          on_boot:
          - priority: 800
            then:
              - output.turn_on: enable
          - priority: 800
            then:
              - pcf8563.read_time
        
        esp32:
          board: esp32-s3-devkitc-1 #m5stack-stamps3
          variant: esp32s3
          framework:
            type: arduino
        
        # Enable logging
        logger:
        
        # Enable Home Assistant API
        api:
          encryption:
            key: REDACTED
        
        ota:
          - platform: esphome
            password: REDACTED
        
        wifi:
          ssid: !secret wifi_ssid
          password: !secret wifi_password
        
          # Enable fallback hotspot (captive portal) in case wifi connection fails
          ap:
            ssid: "Lounge-Airq Fallback Hotspot"
            password: REDACTED
        
        captive_portal:
        
        output:
          - platform: gpio
            pin: GPIO10
            id: enable
        
        web_server:
           port: 80
           include_internal: true
        
        i2c:
          sda: GPIO11
          scl: GPIO12
          scan: true
          frequency: 100kHz
          id: bus_a
        
        spi:
          clk_pin: GPIO05
          mosi_pin: GPIO06
        
        time:
          - platform: pcf8563
            address: 0x51
            update_interval: 10min
          - platform: homeassistant
            id: esptime
        
        light:
          - platform: esp32_rmt_led_strip
            rgb_order: GRB
            pin: GPIO21
            num_leds: 1
            rmt_channel: 0
            chipset: SK6812
            name: "LED"
            restore_mode: ALWAYS_OFF
            id: id_led
        
        text_sensor:
          - platform: wifi_info
            ip_address:
              name: IP
            ssid:
              name: SSID
            bssid:
              name: BSSID
            mac_address:
              name: MAC
            dns_address:
              name: DNS
        
          - platform: template
            name: "VOC IAQ Classification"
            id: iaq_voc
            icon: "mdi:checkbox-marked-circle-outline"
            lambda: |-
              if (int(id(voc).state) < 100.0) {
                return {"Great"};
              }
              else if (int(id(voc).state) <= 200.0) {
                return {"Good"};
              }
              else if (int(id(voc).state) <= 300.0) {
                return {"Light"};
              }
              else if (int(id(voc).state) <= 400.0) {
                return {"Moderate"};
              }
              else if (int(id(voc).state) <= 500.0) {
                return {"Heavy"};
              }
              else {
                return {"unknown"};
              }
        
          - platform: template
            name: "NOX IAQ Classification"
            id: iaq_nox
            icon: "mdi:checkbox-marked-circle-outline"
            lambda: |-
              if (int(id(nox).state) < 100.0) {
                return {"Great"};
              }
              else if (int(id(nox).state) <= 200.0) {
                return {"Good"};
              }
              else if (int(id(nox).state) <= 300.0) {
                return {"Light"};
              }
              else if (int(id(nox).state) <= 400.0) {
                return {"Moderate"};
              }
              else if (int(id(nox).state) <= 500.0) {
                return {"Heavy"};
              }
              else {
                return {"unknown"};
              }
        
        sensor:
          - platform: scd4x
            co2:
              name: CO2
              id: CO2
              filters:
                - lambda: |-
                    float MIN_VALUE = 300.0;
                    float MAX_VALUE = 2500.0;
                    if (MIN_VALUE <= x && x <= MAX_VALUE) return x;
                    else return {};         
            temperature:
              name: CO2 Temperature
              id: CO2_temperature
              filters:
                - lambda: |-
                    float MIN_VALUE = -40.0;
                    float MAX_VALUE = 100.0;
                    if (MIN_VALUE <= x && x <= MAX_VALUE) return x;
                    else return {};      
            humidity:
              name: CO2 Humidity
              id: CO2_humidity
              filters:
                - lambda: |-
                    float MIN_VALUE = 0.0;
                    float MAX_VALUE = 100.0;
                    if (MIN_VALUE <= x && x <= MAX_VALUE) return x;
                    else return {};      
            altitude_compensation: 0m
            address: 0x62
            update_interval: $sensor_interval
        
          - platform: wifi_signal # Reports the WiFi signal strength/RSSI in dB
            name: "Wifi Signal dB"
            id: wifi_signal_db
            update_interval: 60s
            entity_category: "diagnostic"
        
          - platform: sen5x
            id: sen55
            pm_1_0:
              name: "PM 1"
              id: PM1_0
              accuracy_decimals: 2
            pm_2_5:
              name: "PM 2.5"
              id: PM2_5
              accuracy_decimals: 2
            pm_4_0:
              name: "PM 4"
              id: PM4_0
              accuracy_decimals: 2
            pm_10_0:
              name: "PM 10"
              id: PM10_0
              accuracy_decimals: 2
            temperature:
              name: "SEN55 Temperature"
              id: sen55_temperature
              accuracy_decimals: 2
            humidity:
              name: "SEN55 Humidity"
              id: sen55_humidity
              accuracy_decimals: 2
            voc:
              name: VOC
              id: voc
              accuracy_decimals: 2
              algorithm_tuning:
                index_offset: 100
                learning_time_offset_hours: 12
                learning_time_gain_hours: 12
                gating_max_duration_minutes: 180
                std_initial: 50
                gain_factor: 230
            nox:
              name: NOX
              id: nox
              accuracy_decimals: 2      
              algorithm_tuning:
                index_offset: 100
                learning_time_offset_hours: 12
                learning_time_gain_hours: 12
                gating_max_duration_minutes: 180
                std_initial: 50
                gain_factor: 230
            temperature_compensation:
              offset: 0
              normalized_offset_slope: 0
              time_constant: 0
            acceleration_mode: low
            store_baseline: true
            address: 0x69
            update_interval: $sensor_interval
        
          - platform: template
            name: Temperature
            id: temperature
            lambda: |-
              return (( id(sen55_temperature).state + id(CO2_temperature).state ) / 2 ) - id(temperature_offset).state;
            unit_of_measurement: "°C"
            icon: "mdi:thermometer"
            device_class: "temperature"
            state_class: "measurement"
            update_interval: $sensor_interval
            accuracy_decimals: 2
        
          - platform: template
            name: Humidity
            id: humidity
            lambda: |-
              return (( id(sen55_humidity).state + id(CO2_humidity).state ) / 2) - id(humidity_offset).state;
            unit_of_measurement: "%"
            icon: "mdi:water-percent"
            device_class: "humidity"
            state_class: "measurement"    
            update_interval: $sensor_interval
            accuracy_decimals: 2
        
        binary_sensor:
          - platform: gpio
            name: Button A
            pin:
              number: GPIO0
              ignore_strapping_warning: true
              mode:
                input: true
              inverted: true
            on_press:
              then:
                - component.update: disp
              
          - platform: gpio
            pin:
              number: GPIO08
              mode:
                input: true
                pullup: true
              inverted: true
            name: Button B
            
          - platform: gpio
            pin:
              number: GPIO46
              ignore_strapping_warning: true
            name: Button Hold
        
          - platform: gpio
            pin: 
              number: GPIO42
            name: Button Power
        
        button:
          - platform: restart
            name: Restart
            
          - platform: template
            name: "CO2 Force Manual Calibration"
            entity_category: "config"
            on_press:
              then:
                - scd4x.perform_forced_calibration:
                    value: !lambda 'return id(co2_cal).state;'
        
          - platform: template
            name: "SEN55 Force Manual Clean"
            entity_category: "config"
            on_press:
              then:
                - sen5x.start_fan_autoclean: sen55
        
        number:
          - platform: template
            name: "CO2 Calibration Value"
            optimistic: true
            min_value: 400
            max_value: 1000
            step: 5
            id: co2_cal
            icon: "mdi:molecule-co2"
            entity_category: "config"
        
          - platform: template
            name: Humidity Offset
            id: humidity_offset
            restore_value: true
            initial_value: 0.0
            min_value: -70.0
            max_value: 70.0
            entity_category: "CONFIG"
            unit_of_measurement: "%"
            optimistic: true
            update_interval: never
            step: 0.1
            mode: box
        
          - platform: template
            name: Temperature Offset
            id: temperature_offset
            restore_value: true
            initial_value: 0.0
            min_value: -70.0
            max_value: 70.0
            entity_category: "CONFIG"
            unit_of_measurement: "°C"
            optimistic: true
            update_interval: never
            step: 0.1
            mode: box
        
        display:
          - platform: waveshare_epaper
            model: 1.54inv2
            id: disp
            cs_pin: GPIO04
            dc_pin: GPIO03
            reset_pin: GPIO02
            busy_pin:
              number: GPIO01
              inverted: false
            full_update_every: 6
            reset_duration: 2ms
            update_interval: 10s
            lambda: |-
              auto now = id(esptime).now().strftime("%H:%M %d/%m/%y").c_str();
              it.printf(it.get_width()/2, 0, id(f12), TextAlign::TOP_CENTER, "${location} @ %s", now);
        
              it.print(0, 23, id(f24), TextAlign::TOP_LEFT, "PM 1: "); 
              it.print(0, 48, id(f24), TextAlign::TOP_LEFT, "PM 2.5: "); 
              it.print(0, 73, id(f24), TextAlign::TOP_LEFT, "PM 4: "); 
              it.print(0, 98, id(f24), TextAlign::TOP_LEFT, "PM 10: "); 
              it.print(0, 123, id(f24), TextAlign::TOP_LEFT, "CO2: "); 
              it.print(0, 148, id(f24), TextAlign::TOP_LEFT, "VOC: ");
              it.print(0, 173, id(f24), TextAlign::TOP_LEFT, "NOX: ");
        
              it.printf(it.get_width(), 23, id(f24), TextAlign::TOP_RIGHT, "%.0f", id(PM1_0).state);
              it.printf(it.get_width(), 48, id(f24), TextAlign::TOP_RIGHT, "%.0f", id(PM2_5).state);
              it.printf(it.get_width(), 73, id(f24), TextAlign::TOP_RIGHT, "%.0f", id(PM4_0).state); 
              it.printf(it.get_width(), 98, id(f24), TextAlign::TOP_RIGHT, "%.0f", id(PM10_0).state);
              it.printf(it.get_width(), 123, id(f24), TextAlign::TOP_RIGHT, "%.0fppm", id(CO2).state);
              it.printf(it.get_width(), 148, id(f24), TextAlign::TOP_RIGHT, "%.0f", id(voc).state);
              it.printf(it.get_width(), 173, id(f24), TextAlign::TOP_RIGHT, "%.0f", id(nox).state);
        
        font:
          - file:
              type: gfonts
              family: Noto Sans Display
              weight: 500
            glyphs: ['&', '@', '!', ',', '.', '"', '%', '(', ')', '+', '-', '_', ':', '°', '0',
                '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E',
                'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S',
                'T', 'U', 'V', 'W', 'X', 'Y', 'Z', ' ', 'a', 'b', 'c', 'd', 'e', 'f',
                'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't',
                'u', 'v', 'w', 'x', 'y', 'z','å', 'ä', 'ö', '/', 'µ', '³', '’']
            id: f16
            size: 16
          - file:
              type: gfonts
              family: Noto Sans Display
              weight: 500
            glyphs: ['&', '@', '!', ',', '.', '"', '%', '(', ')', '+', '-', '_', ':', '°', '0',
                '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E',
                'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S',
                'T', 'U', 'V', 'W', 'X', 'Y', 'Z', ' ', 'a', 'b', 'c', 'd', 'e', 'f',
                'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't',
                'u', 'v', 'w', 'x', 'y', 'z','å', 'ä', 'ö', '/', 'µ', '³', '’']
            id: f18
            size: 18
          - file:
              type: gfonts
              family: Noto Sans Display
              weight: 500
            id: f12
            size: 12
            glyphs: ['&', '@', '!', ',', '.', '"', '%', '(', ')', '+', '-', '_', ':', '°', '0',
                '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E',
                'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S',
                'T', 'U', 'V', 'W', 'X', 'Y', 'Z', ' ', 'a', 'b', 'c', 'd', 'e', 'f',
                'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't',
                'u', 'v', 'w', 'x', 'y', 'z','å', 'ä', 'ö', '/', 'µ', '³', '’']
          - file:
              type: gfonts
              family: Noto Sans Display
              weight: 500
            id: f24
            size: 24
            glyphs: ['&', '@', '!', ',', '.', '"', '%', '(', ')', '+', '-', '_', ':', '°', '0',
                '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E',
                'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S',
                'T', 'U', 'V', 'W', 'X', 'Y', 'Z', ' ', 'a', 'b', 'c', 'd', 'e', 'f',
                'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't',
                'u', 'v', 'w', 'x', 'y', 'z','å', 'ä', 'ö', '/', 'µ', '³', '’']
          - file:
              type: gfonts
              family: Noto Sans Display
              weight: 500
            id: f36
            size: 36
            glyphs: ['&', '@', '!', ',', '.', '"', '%', '(', ')', '+', '-', '_', ':', '°', '0',
                '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E',
                'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S',
                'T', 'U', 'V', 'W', 'X', 'Y', 'Z', ' ', 'a', 'b', 'c', 'd', 'e', 'f',
                'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't',
                'u', 'v', 'w', 'x', 'y', 'z','å', 'ä', 'ö', '/', 'µ', '³', '’']
          - file:
              type: gfonts
              family: Noto Sans Display
              weight: 500
            id: f48
            size: 48
            glyphs: ['&', '@', '!', ',', '.', '"', '%', '(', ')', '+', '-', '_', ':', '°', '0',
                '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E',
                'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S',
                'T', 'U', 'V', 'W', 'X', 'Y', 'Z', ' ', 'a', 'b', 'c', 'd', 'e', 'f',
                'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't',
                'u', 'v', 'w', 'x', 'y', 'z','å', 'ä', 'ö', '/', 'µ', '³', '’']
          - file:
              type: gfonts
              family: Noto Sans Display
              weight: 500
            id: f32
            size: 32
            glyphs: ['&', '@', '!', ',', '.', '"', '%', '(', ')', '+', '-', '_', ':', '°', '0',
                '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E',
                'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S',
                'T', 'U', 'V', 'W', 'X', 'Y', 'Z', ' ', 'a', 'b', 'c', 'd', 'e', 'f',
                'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't',
                'u', 'v', 'w', 'x', 'y', 'z','å', 'ä', 'ö', '/', 'µ', '³', '’']
        
          - file:
              type: gfonts
              family: Noto Sans Display
              weight: 500
            id: f64
            size: 64
            glyphs: ['&', '@', '!', ',', '.', '"', '%', '(', ')', '+', '-', '_', ':', '°', '0',
                '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E',
                'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S',
                'T', 'U', 'V', 'W', 'X', 'Y', 'Z', ' ', 'a', 'b', 'c', 'd', 'e', 'f',
                'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't',
                'u', 'v', 'w', 'x', 'y', 'z','å', 'ä', 'ö', '/', 'µ', '³', '’']
        
          - file:
              type: gfonts
              family: Noto Sans Display
              weight: 800
            id: f64b
            size: 64
            glyphs: ['&', '@', '!', ',', '.', '"', '%', '(', ')', '+', '-', '_', ':', '°', '0',
                '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E',
                'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S',
                'T', 'U', 'V', 'W', 'X', 'Y', 'Z', ' ', 'a', 'b', 'c', 'd', 'e', 'f',
                'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't',
                'u', 'v', 'w', 'x', 'y', 'z','å', 'ä', 'ö', '/', 'µ', '³', '’']
        
          - file:
              type: gfonts
              family: Noto Sans Display
              weight: 800
            id: f55b
            size: 55
            glyphs: ['&', '@', '!', ',', '.', '"', '%', '(', ')', '+', '-', '_', ':', '°', '0',
                '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E',
                'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S',
                'T', 'U', 'V', 'W', 'X', 'Y', 'Z', ' ', 'a', 'b', 'c', 'd', 'e', 'f',
                'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't',
                'u', 'v', 'w', 'x', 'y', 'z','å', 'ä', 'ö', '/', 'µ', '³', '’']
        
          - file: 
              type: gfonts
              family: Material Symbols Sharp
              weight: 400
            id: font_weather_icons_xsmall
            size: 20
            glyphs:
              - "\U0000F159" # clear-night
              - "\U0000F15B" # cloudy
              - "\U0000F172" # partlycloudy
              - "\U0000E818" # fog      
              - "\U0000F67F" # hail
              - "\U0000EBDB" # lightning, lightning-rainy
              - "\U0000F61F" # pouring
              - "\U0000F61E" # rainy
              - "\U0000F61C" # snowy
              - "\U0000F61D" # snowy-rainy
              - "\U0000E81A" # sunny
              - "\U0000EFD8" # windy, windy-variant
              - "\U0000F7F3" # exceptional
          - file: 
              type: gfonts
              family: Material Symbols Sharp
              weight: 400
            id: font_weather_icons_small
            size: 32
            glyphs:
              - "\U0000F159" # clear-night
              - "\U0000F15B" # cloudy
              - "\U0000F172" # partlycloudy
              - "\U0000E818" # fog      
              - "\U0000F67F" # hail
              - "\U0000EBDB" # lightning, lightning-rainy
              - "\U0000F61F" # pouring
              - "\U0000F61E" # rainy
              - "\U0000F61C" # snowy
              - "\U0000F61D" # snowy-rainy
              - "\U0000E81A" # sunny
              - "\U0000EFD8" # windy, windy-variant
              - "\U0000F7F3" # exceptional
        
          - file:
              type: gfonts
              family: Open Sans
              weight: 700    
            id: font_clock
            glyphs: "0123456789:"
            size: 70
          - file:
              type: gfonts
              family: Open Sans
              weight: 700    
            id: font_clock_big
            glyphs: "0123456789:"
            size: 100
          - file: "gfonts://Roboto"
            id: font_temp
            size: 28
          - file:
              type: gfonts
              family: Open Sans
              weight: 500    
            id: font_small
            size: 30
            glyphs: "!\"%()+=,-_.:°0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ abcdefghijklmnopqrstuvwxyz→»"
          - file:
              type: gfonts
              family: Open Sans
              weight: 500    
            id: font_medium
            size: 45
            glyphs: "!\"%()+=,-_.:°0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ abcdefghijklmnopqrstuvwxyz→»"
          - file:
              type: gfonts
              family: Open Sans
              weight: 300    
            id: font_xsmall
            size: 16  
            glyphs: "!\"%()+=,-_.:°0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ abcdefghijklmnopqrstuvwxyz→»"
        
        
        
            

         

        6. Important: This code does not include the credentials for connecting the device to your Wi-Fi and Home Assistant instance, so you will need to enter them manually. Specifically, I am referring to the following lines of code that 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 find the corresponding lines in the code (at the beginning) and add the necessary information.

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

        9. When finished, select the “Modern format” option to download the corresponding .bin” file.

        10. Connect the M5Stack Air Quality Kit to your computer using the USB-C data cable via the port on the bottom.

        11. Now go to the ESPHome page   and click "Connect." In the pop-up window, select your board and click "Connect."

        12. Now click on “Install” and select the '.bin' file obtained in step 9. Again, click on “Install”.

        13. Go back to Home Assistant and navigate to Settings > Devices & Services. Your device should be discovered and appear at the top, ready for you to click the “Configure” button. If not, click the “Add Integration” button, search for "ESPHome," and enter your board’s IP address in the "Host" field.

        Device information

        If you navigate to Settings > Devices & Services > ESPHome and select the M5Stack Air Quality Kit, you'll find several entities providing information about air quality.

        Additionally, the buttons on the device's top-left corner have also been exposed as entities. This means you can create an automation to trigger an action when you press them, such as activating your ventilation system.

        To set this up, go to Settings > Automations and Scenes > Create Automation. In the "When" section, add a "State" trigger. In the entity field, select the one corresponding to your device (e.g., 'binary_sensor.airq_button_a'), and in the "To" field, choose "On." Then, simply add the desired actions.

        Source: AguacaTEC
        Author: TitoTB

        2025-03-31

        On November 18, 2024, M5Stack opened its doors to individual visitors for the first time. More than 70 visitors from all around the world attended the event, with some showcasing their projects built with M5 products or sharing their stories with M5Stack.

        M5Stack Open Day 2024

        The event began at 3:00 PM, with many visitors checking in and receiving their gifts—custom T-shirts featuring the M5Stack logo. After a group photo, the first agenda kicked off: The Factory and Office Tour.

        M5Stack Factory and Office Tour

        The visitors were divided into three groups, with introductions provided in Chinese, English, and Japanese. M5Stack operates an all-in-one facility where product design, production, packaging, quality control, marketing, and shipping are seamlessly managed under one place. Visitors could take a close look at the whole flow.

        Factory of M5Stack

        In the show board area, we display all the products we've created so far, including the classic 5x5 stack series—such as M5Basic Core, M5Core2, M5Core3, and Stick series—like the M5StickC, along with the Stamp Series (M5Cardputer, M5StampFly), and the Atom series (M5Atom Echo), among others. M5Stack’s CEO, Jimmy Lai, also explains "What is M5Stack?"

        M5Stack product show board

        M5Stack is an open-source hardware IoT solution provider, primarily focused on products built around the ESP32. We also offer a range of accessories and our own visual programming platform, UIFlow. The "M" in M5Stack stands for Module, "5" represents the 5x5 cm size, and "Stack" refers to their stackable design. 

        M5Stack is committed to delivering convenient, stackable and easy-to-use development components and tools, with at least one new product every Friday, we launch over 50 new products every year. We provide not only standard products, also customized products. Our partner includes Amazon, Microsoft, Arduino, SONY, etc.

        Projects or Stories with M5Stack

        After the tour, it’s time for the Meetup. This year, we have 9 speakers. Jimmy began by sharing the company’s history, followed by presentations from the speakers. TAKASU Masakazu shared the success stories of M5Stack in Japan, where the Japanese market accounts for 35% of M5Stack’s global sales. Many companies and schools in Japan use M5Stack products, and you can even find books on how to use M5Stack products on Amazon. 

        TAKASU Masakazu share the M5Stack's successful story in Japan

        Other speakers introduced their innovative projects, including the Creative Dot Matrix Clock, the M5Unified Library, M5Cuffbox, and the Blue Tears Incubator, how to make stack-chan bigger, etc.

        speaker shows his 3 projects using M5stack's product

        It was a truly enjoyable experience to come together with M5Stack fans from all over. We appreciate everyone’s enthusiasm and participation, and we’re already looking forward to welcoming you again next year. See you then!

        M5Stack Open Day 2024

        2024-11-28

        Time flies, and 2023 has come to an end. M5Stack's products and services have accompanied users worldwide throughout the year. 2023 was a year of continuous innovation for M5Stack, with 76 new products released at a rate of one per week. As we set sail for 2024, let's look back at the brilliance of the past year and unveil the top 10 best-selling products of 2023!

         1. ATOM LITE

        Once again, Atom Lite tops this year's list of best-selling products, following on from last year's success. Launched in 2020, Atom Lite's outstanding features and enduring vitality have won over a wide range of users worldwide.

        Atom Lite user case @Peter Neufeld

        Atom Lite is a compact development board, measuring only 24 * 24mm, which provides more GPIO pins for users to customize freely. It is particularly suitable for embedded smart hardware development.

        This development board uses the ESP32-PICO-D4 solution as the main controller, integrating Wi-Fi module and built-in 3D antenna. It has 4MB of SPI flash memory, as well as Infra-Red (IR) infrared, RGB LED, buttons, and GROVE/HY2.0 interfaces. Additionally, the onboard Type-C interface enables fast program uploading and downloading, and there is an M2 screw hole on the back for fixation.

        2. ATOM ECHO

        Also released in 2020, Atom Echo has undoubtedly become a dark horse product this year. In 2023, with the power of Home Assistant and ChatGPT, the Atom Echo has sold out again and again. The ability to connect to ChatGPT, act as a personal voice assistant with customisable wake words, and do it all for just $13 makes it a must-have smart home device.

        Atom Echo voice assistant use case @Home Assistant

        Atom Echo is a programmable smart speaker. Despite its small size of only 24 * 24 * 17mm, it boasts impressive functionality. With the built-in wireless capabilities of the ESP32, it can easily connect to devices such as smartphones and tablets for seamless music playback. It can also stream specified media music via Wi-Fi.

        To facilitate voice functionality, Atom Echo integrates an STT (Speech-to-Text) service internally. Users only need to burn specified firmware to enable this feature and perform various operations through voice commands. Users can also write code to integrate Atom Echo with cloud platforms like AWS and Google, utilizing the built-in microphone and speaker for voice interaction, enabling certain AI capabilities, such as voice control, intelligent conversations, and IoT functionalities.

        The speaker has a built-in RGB LED (SK6812) that visually displays the connection status. In addition to being used as a smart speaker, it still retains the control capabilities of the ATOM series and can be connected to external devices via the GROVE interface. The M2 screw hole on the back facilitates secure installation.

        3. M5STICKC PLUS

        M5StickC Plus is a powerful, portable and expandable development board. Since its release in 2020, it has consistently been at the top of various best-seller lists. At the end of 2022, we welcomed M5StickC Plus2 and officially said goodbye to M5StickC Plus.

        M5StickC Plus is a powerful microcontroller module that is compact and easy to carry. Despite its small size, M5StickC Plus integrates rich hardware resources, including infrared, RTC, microphone, LED, IMU, buttons, buzzers, and PMU.

        M5StickC Plus is equipped with the ESP32-PICO-D4 processor, providing powerful computing and communication capabilities. It features a 1.14-inch color TFT display that can show images, text, and user interfaces, offering a good interactive experience. Additionally, M5StickC Plus has a built-in battery for standalone use, eliminating the need for an external power source.

        The module is equipped with various sensors, including an accelerometer, gyroscope, and magnetometer, enabling it to sense and measure object motion and environmental conditions. It also has Wi-Fi and Bluetooth connectivity, allowing data transmission and remote control with other devices. The interface supports HAT and Unit series products. This compact and exquisite development tool inspires endless creative possibilities.

        4. M5CORE2

        M5Core2 is the second-generation host of the M5Stack Core series, featuring an ESP32 chip and a touchscreen. It is known for its easy stacking, expandability, and rapid development capabilities. Since its release in 2020, Core2 has consistently been a hot seller in both domestic and international markets, loved by a large number of users. Amazon Web Services (AWS) and M5Stack have collaborated to launch Core2 for AWS. Now, we have the Core2 V1.1 version.

        M5Core2 is a versatile development kit with a 2-inch IPS touchscreen display. It is built around the ESP32 microcontroller, which provides Wi-Fi and Bluetooth connectivity. The Core2 module offers a variety of built-in features, including an accelerometer, gyroscope, magnetometer, speaker, microphone, and more.

        The development kit supports UIFlow, a graphical programming interface, as well as Arduino and MicroPython. This allows users to choose their preferred programming language and easily develop applications. The modular design of M5Core2 enables stacking of additional modules, called Units, on top of the base module, expanding its capabilities and creating custom projects.

        M5Core2 also includes a built-in 390mAh battery, enabling portable and standalone use. It has a USB Type-C interface for charging and programming, as well as an SD card slot for extended storage.

        5. ULTRASONIC I2C Unit

        Ultrasonic I2C Unit is an ultrasonic ranging sensor with an I2C communication interface. The hardware features the RCWL-9620 ultrasonic ranging chip paired with a 16mm probe, enabling precise ranging within a range of 2cm to 450cm (with an accuracy of ±2%). As an I2C slave device, the sensor can share the bus resources with other I2C devices, allowing for efficient use of IO pins. It is ideal for applications such as obstacle avoidance in robots and liquid-level detection.

         

        6. ENVIII Unit

        ENV III is an environmental sensor that integrates the SHT30 and QMP6988 sensors, used for detecting temperature, humidity, and atmospheric pressure data. The SHT30 is a high-precision, low-power digital temperature and humidity sensor that supports the I2C interface (SHT30: 0x44, QMP6988: 0x70). The QMP6988 is an absolute pressure sensor designed for mobile applications, known for its high accuracy and stability, making it suitable for environmental data collection and monitoring projects.

         

        7. AtomS3

        AtomS3 is a highly integrated programmable controller based on the ESP32-S3 main controller. It features an ESP32-S3 main controller with built-in Wi-Fi capability and an 8M onboard flash memory. The controller also includes a 0.85-inch IPS screen with programmable buttons below it. Additionally, it is equipped with a 5V to 3.3V circuit, a 6-axis gyroscope sensor (MPU6886), an onboard Type-C interface (for power and firmware download), an HY2.0-4P expansion port, and six GPIO pins and power pins reserved at the bottom for easy expansion of various applications.

        With a product size of only 24x24x13mm, the AtomS3 is perfect for various embedded smart device applications. It provides powerful functionality and flexible expandability, making it suitable for developing a wide range of embedded systems and smart devices.

        8. PIR Motion Unit

        PIR Motion Unit is a human infrared sensor unit. It belongs to the "passive infrared detector" category and works by detecting the infrared radiation emitted or reflected by humans or objects. When infrared radiation is detected, it outputs a high-level signal and maintains a delay for a certain period (keeping the signal high and allowing for repeated triggering) until the trigger signal disappears (returns to a low level). The PIR Motion Unit is suitable for various applications that require detecting human activity and motion, including security, automation, energy management, and people counting.

        9. BASIC V2.7

        Basic is the first-generation host of M5Stack, offering high cost-effectiveness and a rich collection of case resources. Through continuous optimization and development, it has now iterated to the V2.7 version.

        Basic V2.7 features a 2.0-inch color TFT LCD screen with a resolution of 320x240, which can display graphical interfaces and text information. It is also equipped with three programmable buttons, a programmable touchscreen, a speaker, and a set of expansion pins to support the connection and expansion of various peripheral devices.

        This development board uses the ESP32 Internet of Things chip, integrates Wi-Fi capability, and has 16MB of SPI flash memory. As a low-power dual-core processor, it performs exceptionally well in various application scenarios.

        10. ATOM U

        ATOM U is a compact and flexible Internet of Things voice recognition development board. It uses the Espressif ESP32 main control chip with two low-power Xtensa® 32-bit LX6 microprocessors, operating at a high frequency of up to 240MHz. ATOM U integrates USB-A interface, IR emitter, and programmable LED lights, allowing for plug-and-play convenience for program uploading, downloading, and debugging. It also includes a Wi-Fi module and a built-in digital microphone (SPM1423 PDM) for clear audio recording, making it suitable for various IoT human-machine interaction and speech-to-text (STT) input recognition scenarios.

        ATOM U supports low-code development and provides features such as UIFlow graphical programming platform, script programming, no-compilation execution, and cloud pushing. It is fully compatible with mainstream development platforms such as Arduino and ESP32-IDF.

        ATOM U has a high level of integration, including USB-A programming/power interface, IR emitter, programmable RGB light, and buttons. The professionally tuned RF circuit ensures stable and reliable wireless communication quality.

        M5Stack's success is inextricably linked to our community's support, and we thank you for it. We wish you a Happy New Year! We will continue to provide more and better services to meet your needs in the coming year.

        2024-01-03

         The Build2gether Inclusive Innovation Challenge, hosted by Europe's top university ETH Zurich and organised by the world-renowned hardware community Hackster.io, recently concluded. The competition, in collaboration with partners M5Stack, Google, Blues, PCBWay and Useful Sensors, offered a total prize pool of $40,000.

        This global competition aimed to encourage people to use innovative technology to help individuals with physical disabilities overcome challenges in their daily lives and build a more inclusive and equitable future. The competition received a total of 194 submissions, with many participants choosing M5Stack products. In this blog post, we have selected some of the award-winning projects to explore the development stories behind them!

         1.Haptic Vision Assist

        This project uses M5Stack's controllers and sensors to provide simple, cost-effective and efficient obstacle warnings using infrared laser and haptic technology to visually impaired users to aid their mobility.

        The author, Colonel Panic, witnessed the daily challenges faced by visually impaired people while living with his fiancée and her son, both of whom have inherited Retinitis Pigmentosa, a genetic eye disease. Motivated to help improve the lives of visually impaired people, Colonel Panic learned to use M5Stack products and UIFlow within a year. To quickly become proficient in the use of M5Stack, he challenged himself to create a prototype for a new idea every day.

        Project link: https://www.hackster.io/colonelpanic/haptic-vision-assist-ac670f

         

        2. Wall Early Warning System for Swimming Laps

        This project uses Bluetooth beacon technology to warn visually impaired users when approaching the pool wall, preventing accidental collisions due to poor visibility while swimming.

        Have you ever hit a wall while swimming? What about doing the backstroke? Ouch! Now imagine having poor eyesight; everything becomes even more challenging! Inspired by personal experience and discussions with disabled mentors after joining a competitive group, author David Barrett came up with the idea for this project.

        Project link: https://www.hackster.io/3DPrinterDoctor/wall-early-warning-system-for-swimming-laps-4ba356

         3.Intercom and Smart Controller Mounted on Wheelchair and Roll

        This project allows people who use wheelchairs or walkers to receive real-time doorbell notifications via Telegram, see who is at the door, and remotely control door access.

        Imagine hearing the doorbell ring and feeling nervous. You might even rush to open the door. These actions may seem simple for able-bodied people, but they are inconvenient for people who use wheelchairs. This project effectively addresses this pain point.

        Project link:

        https://www.hackster.io/ecasti/intercom-and-smart-controller-mounted-on-wheelchair-and-roll-9a4691

         4. Motion Controller for People with Limited Arm Function

        This project focuses on developing a motion controller for gaming purposes specifically designed for individuals with limited arm function due to conditions such as muscular dystrophy. For PC gamers, the mouse is a crucial peripheral device. However, mice require a flat surface to function properly and demand users to sit in front of a desk, constantly extending their arms, which can be challenging for some individuals with disabilities.

        Therefore, Yahya Khalid created a simple controller using the compact AtomS3, which can effectively replace a mouse. With this device, users can adjust sensitivity to meet their specific needs, and hand position or orientation is not limited, accommodating individuals with restricted hand movements.

        Project link: https://www.hackster.io/yahya-khalid/motion-controller-for-people-with-limited-arm-function-4e15d3

         5. Cracked road detection for better travel

        This project can help people who use wheelchairs to be aware of road potholes in advance to avoid accidental falls while travelling.

        One of the challenges faced by wheelchair users is encountering cracked roads, where if the user is not concentrating, they could fall into a pothole and potentially injure themselves. Hendra Kusumah aims to create a solution that is easy to build and program, requires no soldering, and costs less than $50.

        Project link:

        https://www.hackster.io/hendra/cracked-road-detection-for-better-travel-e00340

        6. Cheza Pona

        This project is an innovative gaming platform that redefines inclusive skill development, specifically designed for people with disabilities such as cerebral palsy and muscular dystrophy. Cheza Pona provides dynamic and adaptive gaming experiences.

        According to a 2017 study by the World Health Organization, only 1 in 10 people with disabilities in low-income countries have access to the assistive technology they need. In response to this challenge, Ryan Kiprotich and Mergery Wanjiru proposed a solution called Cheza Pona, which means "play better" in Swahili. They transformed the typically monotonous physical therapy into an enjoyable gaming experience. Using the low-cost M5StickC, they created a game where players can use the built-in inertial measurement unit sensor to move sideways and avoid blocks in the game to score points.

        Project link: https://www.hackster.io/512307/cheza-pona-2d0c47

        7. Move-It!

        This project turns M5StickC int to an IMU-based gaming device - a small wearable device whose movement can provide input to games.

        The project was mainly aimed at developing something small which can help people with Muscular Dystrophy play games without over-exerting and tiring out faster. I won the M5Stack Gift card and ordered the M5StickC and a mini dual button unit. The idea was to use IMU input from the M5StickC as serial data and process it as keyboard input.

        Project link: https://www.hackster.io/nvraghavendra2000/move-it-2371ed

         

        2023-12-26