
esphome-m5stack-atom-matrix
The source code for this utility is here https://github.com/glynnbird/esphome-m5stack-atom-matrix
Notes for my future self on how to build a tiny Home Assistant dashboard out of a M5Stack Atom Matrix 1.1, which is a ESP32 development board which has:
- Wifi
- a 5x5 matrix of colour LEDs
- a button (press the screen)
- other IO (not used on this project)
The idea was to show the status of various smart home devices by illuminating the LEDs:
- Is the heatpump on?
- Is the home battery charging?
- Is the car charger charging?
- Is there activity on the doorbell?
- Is the hoover running?
- Who is in the house?
and three simple bar charts showing the state of batteries for:
- the home battery
- car 1
- car 2
Getting Started
You need a fully functioning Home Assistant setup with some sensors. The YAML below will not work out-of-the-box, but you can use it as a guide on how to make your own. I borrowed heavily from this blog post without which I wouldn’t have got anywhere.
In Home Assistant add the ESPHome app: see https://esphome.io/install/getting-started/.
I added followed the “Add Device” flow, to get the device paired with Home Assistant - providing the Wifi name and password. I defined the device as an ESP32 variant, using the M5Stack Atom Lite as the jumping off point (as the M5Stack Atom Matrix wasn’t an option).
USB/Serial Driver
I was running on a Mac an required a driver to allow cabled connection between the Mac and the M5Stack. This is required for the first flashing of firmware - after that, software updates can be done “over the air”.
It may just be me, but I found that the USB C -> USB A cable I was using didn’t work. It only worked with a USB C -> USB C cable. I lost a lot of time finding this out.
Button
The first thing I created was a binary sensor to allow the M5Stack’s button to be available to Home Assistant:
binary_sensor:
- platform: gpio
name: "Main Button"
id: mainbutton
pin:
number: 39
inverted: true
Why “39”? The M5Stack documentation pin map lists GPIO39 as where the button is connected. The “inverted” flag simply swaps the on/off orientation.
With the minimal configuration that the setup wizard provides and the binary_sensor config, the device should be auto-discovered in your Home Assistant devices list. Add a device and set up a test automation to do something when the button is pressed.
At this point, the M5Stack is on your Wifi network can be updated over-the-air, which is much more convenient.
Sensors
To get data to flow from Home Assistant back to the M5Stack we need more YAML markup to define which Home Assistant entities we need access to. These could be binary sensors, text sensors or just “sensor”s (numeric values).
Binary sensors
The on/off flags are added to our binary_sensor block, but this time the platform is homeassistant indicating that data originates from HA and is sent to the M5Stack:
# Binary Sensors
binary_sensor:
- platform: gpio
name: "Main Button"
id: mainbutton
pin:
number: 39
inverted: true
- platform: homeassistant
id: "heatpump_is_on"
entity_id: input_boolean.heatpump_is_on
- platform: homeassistant
id: "battery_is_charging"
entity_id: input_boolean.battery_is_charging
- platform: homeassistant
id: "ohme_is_charging"
entity_id: input_boolean.ohme_is_charging
- platform: homeassistant
id: "doorbell_activity_boolean"
entity_id: input_boolean.doorbell_activity_boolean
Text sensors
We also need some text sensors from Home Assistant, whose values we need to know about
text_sensor:
- platform: homeassistant
id: phone1
entity_id: device_tracker.phone1
- platform: homeassistant
id: phone2
entity_id: device_tracker.phone2
- platform: homeassistant
id: phone3
entity_id: device_tracker.phone3
- platform: homeassistant
id: henry
entity_id: vacuum.henry
- platform: homeassistant
id: ohmestatus
entity_id: sensor.ohme_home_pro_status
(Numeric) sensors
The “sensors” are just Home Assistant sensors that resolve to numbers, like states of charge of various batteries:
sensor:
- platform: homeassistant
id: "car1_battery"
entity_id: sensor.car1_battery_soc
- platform: homeassistant
id: "car2_battery"
entity_id: sensor.car2_battery_soc
- platform: homeassistant
id: "house_battery"
entity_id: sensor.house_battery_soc
Lights
Borrowing from the blog post, we need to configure how the LED lights are controlled with the ESPHome Light Component:
# Lights
light:
# LED Matrix as a strip
- platform: esp32_rmt_led_strip
channel_colors: GRB
pin: GPIO27
num_leds: 25
chipset: ws2812
name: led_matrix_light
id: led_matrix_light
color_correct: [30%, 30%, 30%]
Pin 27 corresponds to the GPIO used to control the lights on the M5Stack pin map. The LEDs at full brightness are too bright, so 30% was fine for indoor use. The WS2812 is the type of LED controller on the M5Stack Atom Matrix.
Display
To use the 5x5 matrix of LEDs a display to draw things on, we need to use the ESPHome Display component:
# Displays
display:
# LED Matrix as an addressable grid
- platform: addressable_light
id: led_matrix_display
addressable_light_id: led_matrix_light
width: 5
height: 5
rotation: 270
update_interval: 1000ms
lambda: |-
// Define available colours
Color Red = Color(0xFF0000);
Color Green = Color(0x00FF00);
Color Blue = Color(0x0000FF);
Color White = Color(0xFFFFFF);
Color Black = Color(0x000000);
Color Orange = Color(0xFFA500);
Color Yellow = Color(0xFFFF00);
Color Magenta = Color(0xFF00FF);
Color Cyan = Color(0x00FFFF);
Color Silver = Color(0xC0C0C0);
// devices
if(id(heatpump_is_on).state) {it.line(0, 0, 0, 0, Red);}
if(id(battery_is_charging).state) {it.line(1, 0, 1, 0, Magenta);}
if(id(ohme_is_charging).state) {
it.line(2, 0, 2, 0, Yellow);
} else if (id(ohmestatus).state == "plugged_in") {
it.line(2, 0, 2, 0, Silver);
}
if(id(doorbell_activity_boolean).state) {it.line(3, 0, 3, 0, Green);}
if(id(henry).state == "cleaning") {it.line(4, 0, 4, 0, Blue);}
// people
if(id(phone1).state == "home") { it.line(0, 1, 0, 1, Orange); }
if(id(phone2).state == "home") {it.line(2,1, 2, 1, Orange);}
if(id(phone3).state == "home") {it.line(4, 1, 4, 1, Orange);}
if(id(phone1).state == "not_home") {it.line(0, 1, 0, 1, Silver);}
if(id(phone2).state == "not_home") {it.line(2,1, 2, 1, Silver);}
if(id(phone3).state == "not_home") {it.line(4, 1, 4, 1, Silver);}
// batteries
int pixels = (int) round((4*id(car1_battery).get_state())/100);
if (pixels >= 0 && pixels <= 4) {
it.line(0,2, pixels, 2, Blue);
}
pixels = (int) round((4*id(car2_battery).get_state())/100);
if (pixels >= 0 && pixels <= 4) {
it.line(0,3, pixels, 3, Red);
}
pixels = (int) round((4*id(house_battery).get_state())/100);
if (pixels >= 0 && pixels <= 4) {
it.line(0,4, pixels, 4, Green);
}
The addressable_light display allows cartesian coordinates to be used to draw lines and shapes onto the 5x5 grid. I chose a rotation of 270, so that the power connector was on the left of the display as I looked at it.
The
update_intervalwas initially 16ms, but I found that adding debug output in the Lambda to be too much at that rate. So I set it to1000msin the end.
The lambda is the C++ code that is executed every update_interval to decide which LEDs are illuminated using which colours.
The code is a series of if statements make decisions based on the Home Assistant entities’ states and illuminating LEDs to suit. The battery “progress bars” are just lines created by scaling the 0-100 battery sensors to a 0-4 line length.
Debugging
It can be useful to add something into the M5Stack’s logs for debugging. I used the following in the Lambda:
ESP_LOGI("soc", "Value of my sensor: %f", id(car1_battery).get_state());