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 %}