Files
Home-Assistant/esphome/cat-medication-tracker.yaml

294 lines
7.6 KiB
YAML

substitutions:
name: cat-medication-tracker
friendly_name: "Cat Medication Tracker"
esphome:
name: ${name}
friendly_name: ${friendly_name}
includes:
- spi_helper.h
on_boot:
- priority: 100
then:
- light.turn_on: backlight
- priority: -100
then:
- lambda: |-
// MADCTL 0x48: MY=0, MX=1, BGR=1 — correct portrait, no mirror for ESP32-2432S035R
// ESP-IDF SPI master API used to bypass ESPHome's buffered display layer
spi_device_handle_t disp_fix;
spi_device_interface_config_t cfg = {};
cfg.clock_speed_hz = 1000000;
cfg.mode = 0;
cfg.spics_io_num = -1; // manual CS
cfg.queue_size = 1;
if (spi_bus_add_device(SPI2_HOST, &cfg, &disp_fix) == ESP_OK) {
gpio_set_level((gpio_num_t)15, 0); // CS low
gpio_set_level((gpio_num_t)2, 0); // DC = command
spi_transaction_t t = {};
t.length = 8;
t.flags = SPI_TRANS_USE_TXDATA;
t.tx_data[0] = 0x36; // MADCTL register
spi_device_polling_transmit(disp_fix, &t);
gpio_set_level((gpio_num_t)2, 1); // DC = data
t.tx_data[0] = 0x48; // MY=0, MX=1, BGR=1
spi_device_polling_transmit(disp_fix, &t);
gpio_set_level((gpio_num_t)15, 1); // CS high
spi_bus_remove_device(disp_fix);
}
- component.update: my_display
esp32:
board: esp32dev
framework:
type: arduino
logger:
level: DEBUG
logs:
xpt2046: WARN
api:
encryption:
key: !secret api_encryption_key
ota:
platform: esphome
password: !secret ota_password
wifi:
ssid: !secret wifi_iot_ssid
password: !secret wifi_password
ap:
ssid: "${name}-fallback"
password: !secret fallback_password
captive_portal:
time:
- platform: homeassistant
id: homeassistant_time
on_time:
- seconds: 0
minutes: 0
hours: 0
then:
- switch.turn_off: penelope_medicated
- switch.turn_off: tess_medicated
- script.execute: update_display
spi:
- id: tft_spi
clk_pin: GPIO14
mosi_pin: GPIO13
miso_pin: GPIO12
display:
- platform: mipi_spi
model: ILI9488
spi_id: tft_spi
cs_pin: GPIO15
dc_pin: GPIO2
reset_pin: GPIO4
rotation: 0
invert_colors: false
color_order: bgr
data_rate: 10MHz
dimensions:
width: 320
height: 480
id: my_display
auto_clear_enabled: false
update_interval: 2s
color_depth: 16
buffer_size: 25%
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
it.fill(light_grey);
// Border: green if all done, red otherwise
bool all_done = id(penelope_medicated).state && id(tess_medicated).state;
auto border_color = all_done ? green : red;
int border = 10;
it.filled_rectangle(0, 0, 320, border, border_color);
it.filled_rectangle(0, 480 - border, 320, border, border_color);
it.filled_rectangle(0, 0, border, 480, border_color);
it.filled_rectangle(320 - border, 0, border, 480, border_color);
// Title
it.printf(160, 30, id(title_font), black, TextAlign::TOP_CENTER, "Cat Meds");
// Penelope button
int btn_x = 40;
int btn_y = 90;
int btn_w = 240;
int btn_h = 120;
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 - 20, id(status_font), white, TextAlign::CENTER, "DONE");
}
// Tess button
btn_y = 230;
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 - 20, id(status_font), white, TextAlign::CENTER, "DONE");
}
// Reset button
int reset_x = 110;
int reset_y = 395;
int reset_w = 100;
int reset_h = 55;
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
id: my_touchscreen
spi_id: tft_spi
cs_pin: GPIO33
update_interval: 250ms
threshold: 1200
calibration:
x_min: 280
x_max: 3850
y_min: 340
y_max: 3860
# Touch buttons as binary sensors
binary_sensor:
- platform: touchscreen
touchscreen_id: my_touchscreen
name: "Penelope Button"
id: penelope_button
x_min: 40
x_max: 280
y_min: 90
y_max: 210
on_press:
then:
- switch.toggle: penelope_medicated
- platform: touchscreen
touchscreen_id: my_touchscreen
name: "Tess Button"
id: tess_button
x_min: 40
x_max: 280
y_min: 230
y_max: 350
on_press:
then:
- switch.toggle: tess_medicated
- platform: touchscreen
touchscreen_id: my_touchscreen
name: "Reset Button"
id: reset_button
x_min: 110
x_max: 210
y_min: 395
y_max: 450
on_press:
then:
- switch.turn_off: penelope_medicated
- switch.turn_off: tess_medicated
- 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
# Backlight control
output:
- platform: gpio
pin: GPIO27
id: backlight_pwm
inverted: false
light:
- platform: binary
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
# 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