Add Cat Medication Tracker configuration: Implement display, touchscreen, and medication state management
This commit is contained in:
238
esphome/cat-medication-tracker.yaml
Normal file
238
esphome/cat-medication-tracker.yaml
Normal file
@@ -0,0 +1,238 @@
|
|||||||
|
substitutions:
|
||||||
|
name: cat-medication-tracker
|
||||||
|
friendly_name: "Cat Medication Tracker"
|
||||||
|
|
||||||
|
esphome:
|
||||||
|
name: ${name}
|
||||||
|
friendly_name: ${friendly_name}
|
||||||
|
|
||||||
|
esp32:
|
||||||
|
board: esp32dev
|
||||||
|
framework:
|
||||||
|
type: arduino
|
||||||
|
|
||||||
|
# Enable logging
|
||||||
|
logger:
|
||||||
|
|
||||||
|
# Enable Home Assistant API
|
||||||
|
api:
|
||||||
|
encryption:
|
||||||
|
key: !secret api_encryption_key
|
||||||
|
|
||||||
|
ota:
|
||||||
|
platform: esphome
|
||||||
|
password: !secret ota_password
|
||||||
|
|
||||||
|
wifi:
|
||||||
|
ssid: !secret wifi_ssid
|
||||||
|
password: !secret wifi_password
|
||||||
|
ap:
|
||||||
|
ssid: "${name}-fallback"
|
||||||
|
password: !secret wifi_password
|
||||||
|
|
||||||
|
captive_portal:
|
||||||
|
|
||||||
|
# Time component for midnight reset
|
||||||
|
time:
|
||||||
|
- platform: homeassistant
|
||||||
|
id: homeassistant_time
|
||||||
|
on_time:
|
||||||
|
# Reset at midnight
|
||||||
|
- seconds: 0
|
||||||
|
minutes: 0
|
||||||
|
hours: 0
|
||||||
|
then:
|
||||||
|
- switch.turn_off: penelope_medicated
|
||||||
|
- switch.turn_off: tess_medicated
|
||||||
|
- script.execute: update_display
|
||||||
|
|
||||||
|
# SPI for display and touchscreen
|
||||||
|
spi:
|
||||||
|
- id: tft_spi
|
||||||
|
clk_pin: GPIO14
|
||||||
|
mosi_pin: GPIO13
|
||||||
|
miso_pin: GPIO12
|
||||||
|
|
||||||
|
# ILI9341 Display (2.4" 240x320)
|
||||||
|
display:
|
||||||
|
- platform: ili9xxx
|
||||||
|
model: ILI9341
|
||||||
|
spi_id: tft_spi
|
||||||
|
cs_pin: GPIO15
|
||||||
|
dc_pin: GPIO2
|
||||||
|
reset_pin: GPIO4
|
||||||
|
rotation: 0
|
||||||
|
dimensions:
|
||||||
|
width: 240
|
||||||
|
height: 320
|
||||||
|
id: my_display
|
||||||
|
lambda: |-
|
||||||
|
// Colors
|
||||||
|
auto red = Color(255, 0, 0);
|
||||||
|
auto green = Color(0, 200, 0);
|
||||||
|
auto light_grey = Color(200, 200, 200);
|
||||||
|
auto white = Color(255, 255, 255);
|
||||||
|
auto black = Color(0, 0, 0);
|
||||||
|
auto dark_grey = Color(80, 80, 80);
|
||||||
|
|
||||||
|
// Fill background with light grey
|
||||||
|
it.fill(light_grey);
|
||||||
|
|
||||||
|
// Border color: green if all cats medicated, red otherwise
|
||||||
|
bool all_done = id(penelope_medicated).state && id(tess_medicated).state;
|
||||||
|
auto border_color = all_done ? green : red;
|
||||||
|
|
||||||
|
// Draw border (8 pixels thick)
|
||||||
|
int border = 8;
|
||||||
|
it.filled_rectangle(0, 0, 240, border, border_color); // Top
|
||||||
|
it.filled_rectangle(0, 320 - border, 240, border, border_color); // Bottom
|
||||||
|
it.filled_rectangle(0, 0, border, 320, border_color); // Left
|
||||||
|
it.filled_rectangle(240 - border, 0, border, 320, border_color); // Right
|
||||||
|
|
||||||
|
// Title
|
||||||
|
it.printf(120, 25, id(title_font), black, TextAlign::TOP_CENTER, "Cat Meds");
|
||||||
|
|
||||||
|
// Penelope button (top button)
|
||||||
|
int btn_x = 30;
|
||||||
|
int btn_y = 60;
|
||||||
|
int btn_w = 180;
|
||||||
|
int btn_h = 80;
|
||||||
|
|
||||||
|
auto penelope_color = id(penelope_medicated).state ? green : red;
|
||||||
|
it.filled_rectangle(btn_x, btn_y, btn_w, btn_h, penelope_color);
|
||||||
|
it.rectangle(btn_x, btn_y, btn_w, btn_h, dark_grey);
|
||||||
|
it.printf(btn_x + btn_w/2, btn_y + btn_h/2, id(button_font), white, TextAlign::CENTER, "Penelope");
|
||||||
|
if (id(penelope_medicated).state) {
|
||||||
|
it.printf(btn_x + btn_w/2, btn_y + btn_h - 15, id(status_font), white, TextAlign::CENTER, "DONE");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Tess button (middle button)
|
||||||
|
btn_y = 155;
|
||||||
|
auto tess_color = id(tess_medicated).state ? green : red;
|
||||||
|
it.filled_rectangle(btn_x, btn_y, btn_w, btn_h, tess_color);
|
||||||
|
it.rectangle(btn_x, btn_y, btn_w, btn_h, dark_grey);
|
||||||
|
it.printf(btn_x + btn_w/2, btn_y + btn_h/2, id(button_font), white, TextAlign::CENTER, "Tess");
|
||||||
|
if (id(tess_medicated).state) {
|
||||||
|
it.printf(btn_x + btn_w/2, btn_y + btn_h - 15, id(status_font), white, TextAlign::CENTER, "DONE");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reset button (bottom, smaller)
|
||||||
|
int reset_x = 70;
|
||||||
|
int reset_y = 260;
|
||||||
|
int reset_w = 100;
|
||||||
|
int reset_h = 40;
|
||||||
|
it.filled_rectangle(reset_x, reset_y, reset_w, reset_h, dark_grey);
|
||||||
|
it.rectangle(reset_x, reset_y, reset_w, reset_h, black);
|
||||||
|
it.printf(reset_x + reset_w/2, reset_y + reset_h/2, id(status_font), white, TextAlign::CENTER, "RESET");
|
||||||
|
|
||||||
|
# XPT2046 Touchscreen
|
||||||
|
touchscreen:
|
||||||
|
- platform: xpt2046
|
||||||
|
spi_id: tft_spi
|
||||||
|
cs_pin: GPIO33
|
||||||
|
interrupt_pin: GPIO36
|
||||||
|
calibration:
|
||||||
|
x_min: 280
|
||||||
|
x_max: 3850
|
||||||
|
y_min: 340
|
||||||
|
y_max: 3860
|
||||||
|
on_touch:
|
||||||
|
then:
|
||||||
|
- lambda: |-
|
||||||
|
int touch_x = touch.x;
|
||||||
|
int touch_y = touch.y;
|
||||||
|
|
||||||
|
// Penelope button bounds: x=30-210, y=60-140
|
||||||
|
if (touch_x >= 30 && touch_x <= 210 && touch_y >= 60 && touch_y <= 140) {
|
||||||
|
id(penelope_medicated).toggle();
|
||||||
|
}
|
||||||
|
// Tess button bounds: x=30-210, y=155-235
|
||||||
|
else if (touch_x >= 30 && touch_x <= 210 && touch_y >= 155 && touch_y <= 235) {
|
||||||
|
id(tess_medicated).toggle();
|
||||||
|
}
|
||||||
|
// Reset button bounds: x=70-170, y=260-300
|
||||||
|
else if (touch_x >= 70 && touch_x <= 170 && touch_y >= 260 && touch_y <= 300) {
|
||||||
|
id(penelope_medicated).turn_off();
|
||||||
|
id(tess_medicated).turn_off();
|
||||||
|
}
|
||||||
|
- script.execute: update_display
|
||||||
|
|
||||||
|
# Backlight control
|
||||||
|
output:
|
||||||
|
- platform: ledc
|
||||||
|
pin: GPIO21
|
||||||
|
id: backlight_pwm
|
||||||
|
|
||||||
|
light:
|
||||||
|
- platform: monochromatic
|
||||||
|
output: backlight_pwm
|
||||||
|
name: "${friendly_name} Backlight"
|
||||||
|
id: backlight
|
||||||
|
restore_mode: ALWAYS_ON
|
||||||
|
|
||||||
|
# Medication state switches (exposed to Home Assistant)
|
||||||
|
switch:
|
||||||
|
- platform: template
|
||||||
|
name: "Penelope Medicated"
|
||||||
|
id: penelope_medicated
|
||||||
|
optimistic: true
|
||||||
|
restore_mode: RESTORE_DEFAULT_OFF
|
||||||
|
on_turn_on:
|
||||||
|
- script.execute: update_display
|
||||||
|
on_turn_off:
|
||||||
|
- script.execute: update_display
|
||||||
|
|
||||||
|
- platform: template
|
||||||
|
name: "Tess Medicated"
|
||||||
|
id: tess_medicated
|
||||||
|
optimistic: true
|
||||||
|
restore_mode: RESTORE_DEFAULT_OFF
|
||||||
|
on_turn_on:
|
||||||
|
- script.execute: update_display
|
||||||
|
on_turn_off:
|
||||||
|
- script.execute: update_display
|
||||||
|
|
||||||
|
- platform: template
|
||||||
|
name: "Reset All Medications"
|
||||||
|
id: reset_all
|
||||||
|
optimistic: false
|
||||||
|
turn_on_action:
|
||||||
|
- switch.turn_off: penelope_medicated
|
||||||
|
- switch.turn_off: tess_medicated
|
||||||
|
|
||||||
|
# Binary sensors for Home Assistant dashboard
|
||||||
|
binary_sensor:
|
||||||
|
- platform: template
|
||||||
|
name: "Penelope Medication Status"
|
||||||
|
lambda: 'return id(penelope_medicated).state;'
|
||||||
|
device_class: running
|
||||||
|
|
||||||
|
- platform: template
|
||||||
|
name: "Tess Medication Status"
|
||||||
|
lambda: 'return id(tess_medicated).state;'
|
||||||
|
device_class: running
|
||||||
|
|
||||||
|
- platform: template
|
||||||
|
name: "All Cats Medicated"
|
||||||
|
lambda: 'return id(penelope_medicated).state && id(tess_medicated).state;'
|
||||||
|
device_class: running
|
||||||
|
|
||||||
|
# Script to update display
|
||||||
|
script:
|
||||||
|
- id: update_display
|
||||||
|
then:
|
||||||
|
- component.update: my_display
|
||||||
|
|
||||||
|
# Fonts
|
||||||
|
font:
|
||||||
|
- file: "gfonts://Roboto"
|
||||||
|
id: title_font
|
||||||
|
size: 28
|
||||||
|
|
||||||
|
- file: "gfonts://Roboto"
|
||||||
|
id: button_font
|
||||||
|
size: 24
|
||||||
|
|
||||||
|
- file: "gfonts://Roboto"
|
||||||
|
id: status_font
|
||||||
|
size: 14
|
||||||
Reference in New Issue
Block a user