Created
June 12, 2025 19:03
-
-
Save trackzero/5e638fdeb6f635cc00fc2ae5707c3efa to your computer and use it in GitHub Desktop.
M5 Atom Matrix medicine tracker in Home Assistant
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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