Working ESPHome YAML for M5StickC

This is cobbled together from lots of stuff I found online, and I wasn’t taking notes on sources when I did it. Apologies to those that did some heavy lifting before I came along.

This post is for the M5StickC made by M5Stack. I don’t have any relationship with the company; I just have a half-dozen of these things and wanted to port them from Arduino to ESPHome. Note that the M5StickC is now discontinued, and they’re sort of a ticking timebomb of obsolescence. As others have reported, the PMU checks the battery level when powered on, and if it’s too low it doesn’t boot. So at some point, the batteries will hold so little charge it will be impossible to boot the device. The company does make a bunch of other devices (most don’t have batteries), so hopefully this info will be useful for a long while. For this reason (and the fact that none of the battery-saving functionality of the PMU is working in ESPHome), you probably shouldn’t attempt to run the M5StickC on battery.

I think the M5 products are great ecosystem because they allow you to connect together different sensors and controllers without soldering and they already come in cases so your projects don’t look like an IED (improvised explosive device).

Here’s some info I found that others will probably find useful:

Component Supported in ESPHome Interface Details
Serial Peripheral Interface (SPI) Bus Yes GPIO 13, 15 I believe this is a low-level interface used by other components. GPIO 13 is clk_pin and GPIO 15 is mosi_pin
I2C Bus Yes GPIO 21, 22, 26 Device has two I2C busses. The internal bus is connected to the IMU and PMU and uses GPIO 21 for SDA and GPIO 22 for SCL. The external bus is for external “HATs” connected via the 8 EXT pins and uses GPIO 0 for SDA and GPIO 26 for SCL.
HAT Port Yes GPIO 26 & 36 Used to connect external HAT devices. Some devices uses I2C and others are directly accessed via GPIO pins
Grove Port Yes GPIO 32 & 33 Used to connect external UNIT devices. Some devices uses I2C and others are directly accessed via GPIO pins
Red Status LED Light Yes GPIO 10 You can turn the LED on and off from Home Assistant and/or ESPHome YAML, but if it’s flashing it means there’s a warning or error.
Buttons Yes GPIO 37 & 39 Button A (the one with “M5” printed on it that’s located next to the display) is GPIO 37. Button B (the one on the side furthest from the USB-C port) is GPIO 39
MPU6886 6-axis intertial measurement unit (IMU) Yes I2C Internal 0x68 This also has a temperature sensor, but it’s measuring the internal temperature of the device not external temperature.
ST7735S 80x160 Pixel Color Display Yes GPIO 5, 13, 15, 18, 23 See GitHub - airy10/esphome-m5stickC: esphome components for the M5StickC and ESP32 Water Leak Detector (with notification) — ESPHome
IR Transmitter Yes GPIO 9 See Turn M5StickC Into Universal IR Remote (Home Automation) - M5Stack Projects
BM8563 Real-Time Clock (RTC) No I2C Internal 0x51 Used to put the device to sleep for a specified period of time to save battery.
SPM1423 Microphone No GPIO 33 There’s no ESPHome support for the SPM1423 chip.
AXP192 Power Management Unit (PMU) Yes I2C Internal 0x34 This is needed to use the display. From the PMU, you can also get the battery charge level. See GitHub - airy10/esphome-m5stickC: esphome components for the M5StickC
VL53L0X Time of Flight (ToF) HAT for measuring distances up to about 2 meters Yes I2C External 0x29, GPIO 26 Connected to the external I2C bus
Passive Infrared (PIR) Motion Sensor UNIT Yes GPIO 32 This is a binary sensor and there’s no ability to adjust it’s sensitivity.

NOTE: From the above, it looks like GPIO 0 is used to send a serial clock frequency, so I’m baffled as to why the external I2C bus definition has the SDA and SCL channels seemingly reversed. But it works for me.

I haven’t set up any I2C UNIT peripherals in ESPHome (yet) so I’m not sure if the Grove port uses the internal or external I2C bus. I assume it uses the external bus. I also have (yet) tried to use the display or PMU in ESPHome. The links above are to work already done by others.

Below is the ESPHome YAML I wrote for the M5StickC and a ToF HAT. Below that is the corresponding Home Assistant YAML to make a binary motion sensor based of the ToF sensor’s distance data.

substitutions:
  devicename: m5stick-stairwell-top
  upper_devicename: M5Stick-Stairwell-Top

esphome:
  name: $devicename
  friendly_name: $upper_devicename

esp32:
  board: m5stick-c
  framework:
    type: arduino

# Enable logging
logger:

# Enable Home Assistant API
api:
  encryption:
    key: !secret api_key

ota:
  password: "SETTHISYOURSELF"

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

  # Enable fallback hotspot (captive portal) in case wifi connection fails
  ap:
    ssid: "M5Stick-Stairwell-Top"
    password: "SETTHISYOURSELF"

captive_portal:

# Built-in Serial Peripheral Interface (SPI)
spi:
  clk_pin: GPIO13
  mosi_pin: GPIO15

# Built-in I2C Bus. Bus_external is for devices connected via the HAT pins. Not sure which interface is used for devices connected via the Grove port
i2c:
  - id: i2c_internal
    sda: 21
    scl: 22
    scan: true
  - id: i2c_external
    sda: 0
    scl: 26
    scan: true

# Built-in Red Status LED Light. See https://esphome.io/components/light/status_led.html
light:
  - platform: status_led
    name: "LED Light"
    restore_mode: ALWAYS_ON
    pin:
      number: GPIO10
      inverted: true

# Built-in buttons. See https://esphome.io/components/button/
binary_sensor:
  - platform: gpio
    pin:
      number: GPIO37
      inverted: true
    id: button_a
    name: Button A
  - platform: gpio
    pin:
      number: GPIO39
      inverted: true
    id: button_b
    name: Button B

# Built-in infrared transmitter. See https://esphome.io/components/remote_transmitter.html and https://m5stack.hackster.io/alessandro-polselli/turn-m5stickc-into-universal-ir-remote-home-automation-d3ec0d
remote_transmitter:
  pin: GPIO9
  carrier_duty_percent: 50%

# Built-in 6-axis intertial measurement unit (IMU) that also includes a temperature sensor
sensor:
  - platform: mpu6886
    i2c_id: i2c_internal
    address: 0x68
    accel_x:
      name: "MPU6886 Accel X"
    accel_y:
      name: "MPU6886 Accel Y"
    accel_z:
      name: "MPU6886 Accel z"
    gyro_x:
      name: "MPU6886 Gyro X"
    gyro_y:
      name: "MPU6886 Gyro Y"
    gyro_z:
      name: "MPU6886 Gyro z"
    temperature:
      name: "MPU6886 Temperature"

# ToF HAT distance sensor (I'm converting the returned values, which are in meters, to inches)
  - platform: vl53l0x
    name: Distance
    device_class: "distance"
    unit_of_measurement: "in"
    i2c_id: i2c_external
    address: 0x29
#    enable_pin: GPIO26   [this works for me with and without this line]
    update_interval: 100ms
    long_range: false
    filters:
      - lambda: |-
          if (isnan(x)) {
            return 0;
          } else {
            return x;
          }
      - multiply: 39.37
      - sliding_window_moving_average:
          window_size: 5
          send_every: 5
      - delta: 10.0

My first hurdle to get the ToF HAT to work as a sort of motion detector was figuring out that the M5StickC has two I2C busses and that the ToF HAT was on the second (external) bus. I wasted a lot of time trying to use an Arduino library when I couldn’t get the ESPHome vl53l0x platform to work. Then I had to figure out how to convert it’s distance readings into a binary motion sensor in Home Assistant. In my case, I have the M5StickC at the top of my stairs, aimed across the stairs.

According to the specifications for the VVL53L0X ToF sensor, it takes about 35 milliseconds to do a reading. Because of the additional filters I applied, I had to increase the update interval to 50ms to avoid warnings like “Distance - update called before prior reading complete - initiated:0 waiting_for_interrupt:1” meaning that the device hadn’t finished processing one reading before the next reading was received. And then I was getting a lot of invalid readings that I thought might be due to the device getting too hot, so I increased the update interval to 100ms.

Explanation of filters:

  • lamba - If nothing is detected, the sensor returns “NaN”. When Home Assistant gets a NaN value, the history graph will just show a flat horizontal line on the last valid reading. This is confusing. So this filter converts any NaN readings to zero.
  • multipy - By default, the ToF sensor returns readings in meters. Being an American, I think in inches not meters. So this converts the values to inches.
  • sliding_window_moving_average - This averages the results across 5 readings to avoid spurious false-positives. With an update frequency of 100ms, 5 readings is a half-second.
  • delta - This causes the device to only transmit readings to Home Assistant if the change from one reading to the next changes by 10 inches. This way, the M5Stick isn’t flooding my network and Home Assistant with the same reading over and over again, but I still have about a half-second response time when motion is detected.

In Home Assistant, I created a template binary sensor to convert the distance measurements returned by the ToF sensor to something that I can more easily use in automations. In my case, when I walk up or down the stairs past the sensor, it reports distance values between about 4" and 40". If a reading is received that’s in that range, it’s considered motion. Otherwise, the templated sensor reports clear. Below is the code from my configuration.yaml file. The “float(0)” bits convert the ‘unknown’ values sent when nothing is detected to a distance of zero, which is outside of the range I’m using to detect motion.

template:
  - binary_sensor:
      - name: "Stairwell top motion sensor"
        unique_id: stairwell_top_motion_sensor
        device_class: motion
        state: >
          {% if states('sensor.m5stick_stairwell_top_distance')|float(0) > 4 and states('sensor.m5stick_stairwell_top_distance')|float(0) < 40 %}
            true
          {% else %}
            false
          {% endif %}

5 Likes

Brilliant work! That’s fantastic stuff :star_struck::star_struck:

hello! any update regarding the integration of the mic into esphome? this guide is super helpfull by the way, thanks a lot

I’ve recently acquired one of these devices and I can use it fine apart from the display.

The display driver in ESPHome appears to have been updated since the original post, so you now need to provide additional parameters.

I have the following configuration, but the screen is just black. If I upload a custom sketch from PlatformIO then I can get the screen to display various colours etc, but with this lambda the screen is just black, no lines at all:

esphome:
  name: m5stick01
  friendly_name: m5stick01

esp32:
  board: m5stick-c
  framework:
    type: arduino

# Enable logging
logger:

# Enable Home Assistant API
api:
  encryption:
    key: "<ENC_KEY>"

ota:
  password: "<OTA PASS>"

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

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

captive_portal:
    
# Built-in Serial Peripheral Interface (SPI)
spi:
  clk_pin: GPIO13
  mosi_pin: GPIO15

# Built-in I2C Bus. Bus_external is for devices connected via the HAT pins. Not sure which interface is used for devices connected via the Grove port
i2c:
  - id: i2c_internal
    sda: 21
    scl: 22
    scan: true
  - id: i2c_external
    sda: 0
    scl: 26
    scan: true

# Built-in Red Status LED Light. See https://esphome.io/components/light/status_led.html
light:
  - platform: status_led
    name: "LED Light"
    restore_mode: ALWAYS_OFF
    pin:
      number: GPIO10
      inverted: true

# Built-in buttons. See https://esphome.io/components/button/
binary_sensor:
  - platform: gpio
    pin:
      number: GPIO37
      inverted: true
    id: button_a
    name: Button A
  - platform: gpio
    pin:
      number: GPIO39
      inverted: true
    id: button_b
    name: Button B

# Built-in infrared transmitter. See https://esphome.io/components/remote_transmitter.html and https://m5stack.hackster.io/alessandro-polselli/turn-m5stickc-into-universal-ir-remote-home-automation-d3ec0d
remote_transmitter:
  pin: GPIO9
  carrier_duty_percent: 50%

# Built-in 6-axis intertial measurement unit (IMU) that also includes a temperature sensor
sensor:
  - platform: mpu6886
    i2c_id: i2c_internal
    address: 0x68
    accel_x:
      name: "MPU6886 Accel X"
    accel_y:
      name: "MPU6886 Accel Y"
    accel_z:
      name: "MPU6886 Accel z"
    gyro_x:
      name: "MPU6886 Gyro X"
    gyro_y:
      name: "MPU6886 Gyro Y"
    gyro_z:
      name: "MPU6886 Gyro z"
    temperature:
      name: "MPU6886 Temperature"

# Fonts
font:
  - file: 'arial.ttf'
    id: font1
    size: 8

  - file: 'arial.ttf'
    id: font2
    size: 36

  - file: 'arial.ttf'
    id: font3
    size: 14

# builtin 80x160 TFT
display:
  - platform: st7735
    cs_pin: GPIO5
    dc_pin: GPIO23
    reset_pin: GPIO18
    device_height: 80
    device_width: 160
    row_start: 1
    col_start: 1
    model: "INITR_MINI160X80"
    rotation: 270
    update_interval: 2s
    lambda: |-
      it.line(0, 0, 50, 50);

time:
  - platform: homeassistant
    id: homeassistant_time
  - platform: sntp
    id: sntp_time

Hi Matthew. I did get the display on my M5Stick working in ESPHome, though all I did was fill it with the color green until motion was detected by the TOF HAT and then the script would change the color to red for a few seconds. Unfortunately, somewhere along the way the device has disappeared from ESPHome so I don’t have the YAML.

But I’ve got two suggestions for you to try:

  1. Try adding the AXP192 power management configuration lines from https://github.com/airy10/esphome-m5stickC/blob/6f4d842a2a2baf7e0b51ac90bcae1423af898de5/sample-config/m5stickc.yaml as the AXP192 controls the display’s brightness. It’s possible your display is working, but it’s so dim you can’t see it. There more on this topic at Do you have an m5stick-c working with current esphome? - #28 by wjcarpenter

  2. Try removing the configuration lines for the infrared transmitter and the status LED. I vaguely recall that there was some weird conflict with one of those (I think it was the IR transmitter) and one of the other built-in components.

1 Like

Amazing, thanks, I’ll give them a go now.

I’m using it as a tally light anyway, so a full screen of colour is perfectly fine!

This did it.

I now have a split-screen of coloured fuzzyness and white with black dots, but at least the screen is working!

Thanks!

1 Like

Is there a way to fully turn off the display to save power?

Hi @mcsquared88, like @JJordan, I’m also interested in integration of the mic in esphome. As a reference I’ve found that ESPHome Voice Assistant - #40 by ChrisThomas has gotten this combination to work but M5StickC seems like a nicer option

i2s_audio:
  i2s_lrclk_pin: GPIO15

microphone:
  - platform: i2s_audio
    id: mic
    bits_per_sample: 32bit
    adc_type: external
    i2s_din_pin: GPIO13
    channel: right
    pdm: true

Thanks for posting this information mcsquared88. I got the ToF HAT sensor working in espHome with the config you provided. Did you ever get the Grove Port config figured out? I have a ToF sensor that attaches via the Grove Port but I have been unable to work out what is the right config to read from it.