Skip to content

Instantly share code, notes, and snippets.

@trackzero
Created June 12, 2025 19:03
Show Gist options
  • Save trackzero/5e638fdeb6f635cc00fc2ae5707c3efa to your computer and use it in GitHub Desktop.
Save trackzero/5e638fdeb6f635cc00fc2ae5707c3efa to your computer and use it in GitHub Desktop.
M5 Atom Matrix medicine tracker in Home Assistant
esphome:
name: medication-tracker
friendly_name: medication-tracker
esp32:
board: m5stack-atom
framework:
type: arduino
# Enable logging
logger:
#level: DEBUG
# Enable Home Assistant API
api:
encryption:
key: !secret medtracker_key
# this was a debug service I used to iterate through my glyphs.
services:
- service: test_next_day
then:
- lambda: |-
id(test_day) = (id(test_day) + 1) % 7;
ESP_LOGD("test_mode", "test_day is now: %d", id(test_day));
Color current_color = id(med_taken_state).state ? Color(0, 255, 0) : Color(255, 0, 0);
// Clear the display
id(matrix_display).fill(Color(0,0,0));
// Define the custom character bitmaps
const uint8_t m_char[] = {0,0,0,0,0, 1,1,1,1,1, 1,0,1,0,1, 1,0,1,0,1, 1,0,1,0,1};
const uint8_t tu_char[] = {1,1,1,0,0, 0,1,0,0,0, 0,1,1,0,1, 0,1,1,0,1, 0,0,1,1,1};
const uint8_t w_char[] = {1,0,0,0,1, 1,0,1,0,1, 1,0,1,0,1, 1,0,1,0,1, 0,1,0,1,0};
const uint8_t th_char[] = {1,1,1,0,0, 0,1,0,0,0, 0,1,1,0,1, 0,1,1,1,1, 0,0,1,0,1};
const uint8_t f_char[] = {0,1,1,1,0, 0,1,0,0,0, 0,1,1,1,0, 0,1,0,0,0, 0,1,0,0,0};
const uint8_t sa_char[] = {1,1,0,0,0, 1,0,0,1,0, 1,1,1,0,1, 0,0,1,1,1, 1,1,1,0,1};
const uint8_t su_char[] = {1,1,1,0,0, 1,0,0,0,0, 1,1,1,0,1, 0,0,1,0,1, 1,1,1,1,1};
const uint8_t* days[] = {m_char, tu_char, w_char, th_char, f_char, sa_char, su_char};
int day_index = id(test_day);
// Loop through the 25 pixels and draw the character
for (int i = 0; i < 25; ++i) {
if (days[day_index][i]) {
int x = i % 5;
int y = i / 5;
id(matrix_display).draw_pixel_at(x, y, current_color);
}
}
globals:
- id: test_day
type: int
initial_value: '0'
ota:
- platform: esphome
password: !secret otapwd
wifi:
ssid: !secret wifi_ssid
password: !secret wifi_password
# Enable fallback hotspot (captive portal) in case wifi connection fails
ap:
ssid: "M5-Atom-Matrix Fallback Hotspot"
password: "whatever"
# Use the Home Assistant time source
time:
- platform: homeassistant
id: ha_time
# Define the LED matrix display
light:
- platform: neopixelbus
variant: ws2812
type: GRB
pin: GPIO27
num_leds: 25
id: matrix_light
name: "Atom Matrix Light"
# Limit brightness to 50% to reduce power draw and prevent it from blinding you
color_correct: [50%, 50%, 50%]
binary_sensor:
# Define the button on the ATOM Matrix
- platform: gpio
pin:
number: GPIO39
inverted: true
name: "Atom Matrix Button"
filters:
# Debounce filter to prevent multiple clicks from a single press
- delayed_on_off: 25ms
on_press:
# Action 1: Tell Home Assistant the button was pressed
- homeassistant.service:
service: input_boolean.turn_on
data:
entity_id: input_boolean.medication_taken
# Action 2: Provide visual feedback by drawing the custom character
# note, this is not as instant as I was attempting, it waits for the 5s refresh interval
- lambda: |-
auto time = id(ha_time).now();
if (time.hour >= 8) {
id(matrix_display).fill(Color(0,0,0));
// Define the custom character bitmaps based on a 5x5 grid
const uint8_t m_char[] = {0,0,0,0,0, 1,1,1,1,1, 1,0,1,0,1, 1,0,1,0,1, 1,0,1,0,1};
const uint8_t tu_char[] = {1,1,1,0,0, 0,1,0,0,0, 0,1,1,0,1, 0,1,1,0,1, 0,0,1,1,1};
const uint8_t w_char[] = {1,0,0,0,1, 1,0,1,0,1, 1,0,1,0,1, 1,0,1,0,1, 0,1,0,1,0};
const uint8_t th_char[] = {1,1,1,0,0, 0,1,0,0,0, 0,1,1,0,1, 0,1,1,1,1, 0,0,1,0,1};
const uint8_t f_char[] = {0,1,1,1,0, 0,1,0,0,0, 0,1,1,1,0, 0,1,0,0,0, 0,1,0,0,0};
const uint8_t sa_char[] = {1,1,0,0,0, 1,0,0,1,0, 1,1,1,0,1, 0,0,1,1,1, 1,1,1,0,1};
const uint8_t su_char[] = {1,1,1,0,0, 1,0,0,0,0, 1,1,1,0,1, 0,0,1,0,1, 1,1,1,1,1};
const uint8_t* days[] = {m_char, tu_char, w_char, th_char, f_char, sa_char, su_char};
// Convert day of week (Sun=1, Mon=2...) to array index (Mon=0, Tue=1...)
int day_index = (time.day_of_week == 1) ? 6 : (time.day_of_week - 2);
auto color = Color(0, 255, 0);
// Loop through the 25 pixels and draw the character
for (int i = 0; i < 25; ++i) {
if (days[day_index][i]) {
int x = i % 5;
int y = i / 5;
id(matrix_display).draw_pixel_at(x, y, color);
}
}
id(refresh_display).execute();
}
# Create a sensor that mirrors the state of the Home Assistant helper. This is an input_boolean helper in HA
- platform: homeassistant
id: med_taken_state
name: "Medication Taken State"
entity_id: input_boolean.medication_taken
# Optional: Create a sensor to check if test mode is on
- platform: homeassistant
id: test_mode_active
entity_id: input_boolean.medication_tracker_test_mode
script:
- id: refresh_display
then:
- component.update: matrix_display
# Display the glyph for the day of the week on the matrix using the global color
display:
- platform: addressable_light
id: matrix_display
addressable_light_id: matrix_light
width: 5
height: 5
rotation: 0
update_interval: 5s
lambda: |-
auto time = id(ha_time).now();
// Only show the display between 8 AM and 2 PM (14:00)
if (time.hour >= 14 && id(med_taken_state).state) { // <-- Check the state directly
it.fill(Color(0,0,0));
return;
}
if (time.hour >= 8) {
Color current_color = id(med_taken_state).state ? Color(0, 255, 0) : Color(255, 0, 0); // <-- Check the state directly here too
it.fill(Color(0,0,0));
// Define the custom character bitmaps based on your spreadsheet
const uint8_t m_char[] = {0,0,0,0,0, 1,1,1,1,1, 1,0,1,0,1, 1,0,1,0,1, 1,0,1,0,1};
const uint8_t tu_char[] = {1,1,1,0,0, 0,1,0,0,0, 0,1,1,0,1, 0,1,1,0,1, 0,0,1,1,1};
const uint8_t w_char[] = {1,0,0,0,1, 1,0,1,0,1, 1,0,1,0,1, 1,0,1,0,1, 0,1,0,1,0};
const uint8_t th_char[] = {1,1,1,0,0, 0,1,0,0,0, 0,1,1,0,1, 0,1,1,1,1, 0,0,1,0,1};
const uint8_t f_char[] = {0,1,1,1,0, 0,1,0,0,0, 0,1,1,1,0, 0,1,0,0,0, 0,1,0,0,0};
const uint8_t sa_char[] = {1,1,0,0,0, 1,0,0,1,0, 1,1,1,0,1, 0,0,1,1,1, 1,1,1,0,1};
const uint8_t su_char[] = {1,1,1,0,0, 1,0,0,0,0, 1,1,1,0,1, 0,0,1,0,1, 1,1,1,1,1};
const uint8_t* days[] = {m_char, tu_char, w_char, th_char, f_char, sa_char, su_char};
int day_index;
if (id(test_mode_active).state) {
// Test mode cycles M, Tu, W, Th, F, Sa, Su
day_index = id(test_day);
} else {
// Convert day of week (Sun=1, Mon=2...) to array index (Mon=0, Tue=1...)
day_index = (time.day_of_week == 1) ? 6 : (time.day_of_week - 2);
}
// Loop through the 25 pixels and draw the character
for (int i = 0; i < 25; ++i) {
if (days[day_index][i]) {
int x = i % 5;
int y = i / 5;
it.draw_pixel_at(x, y, current_color);
}
}
} else {
// Clear the display outside of the active hours
it.fill(Color(0,0,0));
}
## unnecessary additions to activate additional sensors
# Define I2C interface
i2c:
sda: 25
scl: 21
scan: true
#frequency: 200kHz
sensor:
- platform: mpu6886
address: 0x68
update_interval: 60s # Defaults to 60s, but seems to work fine up to at least 300ms
#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: "Medication Tracker Temperature"
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment