Compare commits

...

112 Commits

Author SHA1 Message Date
Joshua King
c1934f3806 Update display initialization comments for clarity and accuracy 2026-03-07 08:40:16 -05:00
Joshua King
38b3ab8f56 Fix display orientation settings for correct portrait mode 2026-03-07 08:34:06 -05:00
Joshua King
a0e2687bf5 Fix display orientation settings for correct portrait mode 2026-03-07 08:17:59 -05:00
Joshua King
c95ad4cd2c Change display rotation from 180 to 0 degrees for correct orientation 2026-03-07 08:02:00 -05:00
Joshua King
d32f26da94 Update ILI9488 display rotation to 180 degrees for correct orientation 2026-03-06 21:38:41 -05:00
Joshua King
a4700b673e Refactor SPI handling in cat-medication-tracker to use ESP-IDF API for improved performance and reliability 2026-03-06 21:34:11 -05:00
Joshua King
3bd797bcc5 Refactor ESPHome configuration to include SPI.h via spi_helper.h 2026-03-06 21:28:10 -05:00
Joshua King
1a486240a0 Add SPI.h include to platformio build flags for ESPHome configuration 2026-03-06 21:22:23 -05:00
Joshua King
ef0841de3f Fix display orientation and update MADCTL settings for correct portrait mode 2026-03-06 21:17:53 -05:00
Joshua King
6434885ee9 Update ILI9488 display rotation to 180 degrees for correct orientation 2026-03-06 21:07:14 -05:00
Joshua King
72cc7c4524 Update ILI9488 display rotation to correct orientation 2026-03-06 21:04:49 -05:00
Joshua King
ed17ec0a4d Update ILI9488 display initialization to correct portrait orientation 2026-03-06 20:56:47 -05:00
Joshua King
a8537e087c Fix horizontal mirroring for ILI9488 display by updating MADCTL command handling 2026-03-06 20:45:08 -05:00
Joshua King
82ba5d420c Fix horizontal mirroring for ILI9488 display by updating MADCTL settings and resetting rotation to 0 2026-03-06 20:32:08 -05:00
Joshua King
a2eb2b6106 Update display rotation for ILI9488 to 180 degrees in Cat Medication Tracker 2026-03-06 20:21:57 -05:00
Joshua King
34c92deb26 Update display rotation for ILI9488 in Cat Medication Tracker 2026-03-06 20:18:07 -05:00
Joshua King
560449b1c3 Remove horizontal mirroring for ILI9488 display in Cat Medication Tracker 2026-03-06 16:22:09 -05:00
Joshua King
9e2b0b108c Enable horizontal mirroring for ILI9488 display in Cat Medication Tracker 2026-03-06 16:05:42 -05:00
Joshua King
653d708b78 Remove horizontal mirroring fix for ILI9488 display from update script 2026-03-06 15:54:26 -05:00
Joshua King
56a378f783 Fix horizontal mirroring for ILI9488 display by overriding MADCTL command 2026-03-06 15:48:09 -05:00
Joshua King
812c3fd820 Fix horizontal mirroring for ILI9488 display and update display configuration 2026-03-06 15:28:43 -05:00
Joshua King
cced711c68 Update display configuration to use ili9xxx platform and adjust rotation and mirroring settings 2026-03-06 15:22:09 -05:00
Joshua King
0569620c6c Update display rotation to 180 degrees and disable horizontal mirroring in Cat Medication Tracker 2026-03-06 15:12:30 -05:00
Joshua King
d75297fde4 Enable horizontal mirroring for display in Cat Medication Tracker 2026-03-06 14:56:49 -05:00
Joshua King
5d761acf69 Remove reset duration from display configuration and update backlight GPIO pin in Cat Medication Tracker 2026-03-06 14:42:13 -05:00
Joshua King
fd0d061f96 Update display configuration for improved stability and adjust backlight settings in Cat Medication Tracker 2026-03-06 14:26:43 -05:00
Joshua King
c684ecba49 Refactor display initialization and update configuration for improved stability in Cat Medication Tracker 2026-03-06 14:14:14 -05:00
Joshua King
1b8ec2f489 Add touchscreen support and medication control buttons in Cat Medication Tracker 2026-03-06 14:08:12 -05:00
Joshua King
495224d3eb Refactor display configuration and reduce data rate to enhance stability in Cat Medication Tracker 2026-03-06 14:05:07 -05:00
Joshua King
eea8f239fa Set display data rate to 20MHz for optimal performance in Cat Medication Tracker 2026-03-06 13:52:42 -05:00
Joshua King
6dcab86122 Refactor backlight control and update display configuration in Cat Medication Tracker 2026-03-06 13:50:48 -05:00
Joshua King
7d332586ea Adjust display data rate to 20MHz and invert backlight control in Cat Medication Tracker configuration 2026-03-06 13:37:29 -05:00
Joshua King
2220cec970 Change ESP32 framework type from esp-idf to arduino in Cat Medication Tracker configuration 2026-03-06 13:25:41 -05:00
Joshua King
ee85f43a92 Update color depth for display in Cat Medication Tracker to enhance visual quality 2026-03-06 13:11:56 -05:00
Joshua King
3a0d7da7f6 Add notes on PSRAM usage and display buffer strategy for ESP32-32E 2026-03-06 12:45:44 -05:00
Joshua King
31aaab69b7 Update display configuration in Cat Medication Tracker: change display platform to mipi_spi and adjust color depth and buffer size 2026-03-06 12:43:47 -05:00
Joshua King
a41523a7ac Enable PSRAM support for enhanced TFT framebuffer usage in Cat Medication Tracker 2026-03-06 11:58:41 -05:00
Joshua King
71f35fe822 Add image publishing functionality to Litter Box Camera: implement base64 encoding and MQTT publishing on image capture 2026-03-06 08:55:37 -05:00
Joshua King
6dd6e39c04 Update display configuration for Cat Medication Tracker: change model to ILI9488 and adjust dimensions and button placements 2026-03-06 08:49:02 -05:00
Joshua King
01e02033f6 Fix MQTT client ID configuration in Litter Box Camera setup 2026-03-05 16:39:00 -05:00
Joshua King
be876dc24d Add MQTT client ID configuration to Litter Box Camera setup 2026-03-05 16:36:36 -05:00
Joshua King
9b86dca27e Add Litter Box Camera configuration for ESP32-S3 with camera and sensor integration 2026-03-05 16:32:49 -05:00
Joshua King
b1bb0deb0e Refactor boot sequence: adjust on_boot priority and delay for backlight; change display update interval to 2s; set logger level to DEBUG 2026-03-04 20:30:51 -05:00
Joshua King
f32acd86e4 Refactor boot sequence: adjust on_boot actions for backlight and display update; change display update interval to never; update backlight control to use GPIO 2026-03-04 20:20:30 -05:00
Joshua King
45c9186f25 Refactor boot sequence: remove boot_test_mode global variable; simplify backlight control and update display logic 2026-03-04 20:02:55 -05:00
Joshua King
ccb50f1c37 Enhance boot sequence: add boot_test_mode global variable; adjust backlight delay and update display logic for test mode 2026-03-04 19:58:19 -05:00
Joshua King
0c3f065f64 Refactor boot sequence: update display component on boot and adjust touchscreen settings; set display update interval to 2s 2026-03-04 19:51:11 -05:00
Joshua King
eadb67b2fc Enhance boot sequence: turn on backlight and execute display update script; adjust touchscreen update interval to 100ms 2026-03-04 19:44:45 -05:00
Joshua King
a9b9559c23 Update touchscreen configuration: change interrupt_pin to update_interval 2026-03-04 19:18:17 -05:00
Joshua King
487f3f0207 Add color_palette setting to display configuration 2026-03-04 18:27:29 -05:00
Joshua King
7ad2a7b498 Remove draw_from_origin setting from display configuration 2026-03-04 18:19:58 -05:00
Joshua King
2681623838 Update ESP32 framework type to esp-idf and enable drawing from origin in display configuration 2026-03-04 18:17:48 -05:00
Joshua King
57f5745afe Remove unnecessary buffer_type setting from display configuration 2026-03-04 18:08:57 -05:00
Joshua King
039c3a901c Fix WiFi configuration: Update SSID to use correct secret and remove unnecessary settings; Enhance display configuration: Adjust color order, data rate, and buffer type 2026-03-04 18:05:23 -05:00
Joshua King
dd0b85137f Fix WiFi configuration: Update SSID to use correct secret and adjust fallback password settings 2026-03-04 17:37:54 -05:00
Joshua King
f0c41a6fdc Fix WiFi configuration: Update SSID to use correct secret and streamline fallback password 2026-03-04 17:29:30 -05:00
Joshua King
58e4ca6c03 Enhance touchscreen functionality: Add buttons for medication control and simplify touch event handling 2026-03-04 17:23:41 -05:00
Joshua King
4aecb61673 Enhance touchscreen handling: Add threshold for touch sensitivity and filter out phantom touches to improve accuracy 2026-03-04 17:16:44 -05:00
Joshua King
72f03f9045 Fix WiFi fallback password and update display configuration: Change fallback password to secret and disable color inversion 2026-03-04 17:06:50 -05:00
Joshua King
c43a3767dd Add Cat Medication Tracker configuration: Implement display, touchscreen, and medication state management 2026-03-04 17:04:06 -05:00
Joshua King
240fe06229 Enhance touchscreen handling: Implement backlight control on first touch and debounce touch events to improve responsiveness 2026-03-02 21:39:09 -05:00
Joshua King
0acf0ab8bf Refactor touchscreen button handling: Simplify button logic and implement service calls for light and fan control 2026-03-02 20:18:14 -05:00
d56fa9d393 updates 2026-03-02 20:14:22 -05:00
9cdc4ecba0 Refactor fan control configuration: Fix GPIO pin inversion, add fan IDs, and implement a switch for controlling all fans 2026-03-02 14:36:42 -05:00
root
8aa8ed3273 Merge branch 'main' of ssh://code.cubecraftcreations.com:2288/overseer/Home-Assistant 2026-03-01 18:33:52 -05:00
Joshua King
ee07fa4ff7 Refactor volume control: Add volume slider for speaker and adjust volume multiplier for improved audio management 2026-03-01 18:33:23 -05:00
root
9ccd19cc45 Merge branch 'main' of ssh://code.cubecraftcreations.com:2288/overseer/Home-Assistant 2026-03-01 17:54:42 -05:00
Joshua King
8bb8f09f64 Refactor wake word detection: Set wake word to "Hey Jarvis" and adjust light behavior on error 2026-03-01 17:54:27 -05:00
root
1937262e8f git ignore update 2026-03-01 17:36:37 -05:00
Joshua King
9a91f775c9 Refactor logging level in voice assistant configuration: Change from WARN to DEBUG for improved troubleshooting 2026-03-01 17:32:46 -05:00
Joshua King
b976b7f133 Refactor button colors for light and fan states: Update to a consistent color scheme for better visibility 2026-03-01 08:39:34 -05:00
Joshua King
176a923b67 Refactor backlight control: Adjust on_boot priority and remove unnecessary backlight activation commands 2026-03-01 08:29:56 -05:00
Joshua King
bd845f3f8e Refactor backlight control: Ensure backlight activation is performed correctly during touch events 2026-02-28 21:41:44 -05:00
Joshua King
71ee0ffc4f Refactor master bedroom remote configuration: Update display button labels and implement backlight control based on user interaction 2026-02-28 21:35:58 -05:00
Joshua King
6f451bfd10 Refactor master bedroom remote configuration: Change display rotation to 0 degrees for corrected orientation 2026-02-28 21:30:11 -05:00
Joshua King
e416ebe743 Refactor display configuration: Add dimensions for the display to improve layout accuracy 2026-02-28 21:27:19 -05:00
Joshua King
8521e9bef4 Refactor display configuration: Change display rotation to 90 degrees for improved orientation 2026-02-28 21:23:52 -05:00
Joshua King
7db6976212 Refactor master bedroom remote configuration: Update display data rate and add component updates for improved responsiveness 2026-02-28 21:17:34 -05:00
Joshua King
35b501e978 Refactor master bedroom remote configuration: Change framework type to Arduino and optimize display memory usage with palette mode 2026-02-28 21:09:25 -05:00
Joshua King
7698a0b79d Refactor SPI configuration: Update display and touchscreen SPI settings for improved clarity and functionality 2026-02-28 21:04:00 -05:00
Joshua King
d8f7786cb3 Refactor master bedroom remote configuration: Update boot sequence to initialize backlight and change display data rate for improved performance 2026-02-28 20:57:44 -05:00
Joshua King
4386f37c1b Refactor master bedroom remote configuration: Update entity IDs and friendly names for improved setup alignment 2026-02-28 20:47:58 -05:00
Joshua King
d4214ca983 Refactor touchscreen calibration: Update calibration parameters to use a structured format for improved readability 2026-02-28 20:37:56 -05:00
Joshua King
4256550c6d Fix WiFi fallback AP password: Update to use the correct WiFi password for fallback access point 2026-02-28 20:34:08 -05:00
Joshua King
884cf15451 Update WiFi configuration: Change SSID reference and adjust display settings for improved functionality 2026-02-28 19:52:21 -05:00
Joshua King
661b40339b Refactor master bedroom remote configuration: Update entity IDs and improve touch event handling for enhanced functionality 2026-02-28 19:48:28 -05:00
Joshua King
5053d2662f Update LED bar configuration: Increase number of LEDs from 4 to 8 for enhanced lighting effects 2026-02-28 19:13:29 -05:00
Joshua King
d926c3d9bf Add master bedroom remote configuration: Implement ESPHome setup with display, touchscreen, and entity controls for lights and fan 2026-02-28 15:47:57 -05:00
Joshua King
d6c2b189ef Refactor chore tracker UI: Adjust button dimensions, colors, and positions for improved layout and readability 2026-02-28 13:05:45 -05:00
Joshua King
338025d55b Enhance chore tracker: Set default icons for chores without specified icons and add warnings for missing icons during generation 2026-02-28 12:48:44 -05:00
Joshua King
9b4908c39b Refactor chore tracker: Update chore names and IDs for improved clarity and consistency 2026-02-28 11:59:20 -05:00
Joshua King
689b173f30 Refactor chore tracker configuration and update chore counts
- Updated chore counts for Jordyn, Declan, and Chloe in the dashboard and configuration files.
- Modified chore names and icons for Jordyn, Declan, and Chloe to reflect new tasks.
- Adjusted automation scripts to align with the updated chore structure and counts.
- Ensured notifications reflect the correct number of completed chores for each child.
2026-02-28 11:52:51 -05:00
Joshua King
f0d6e97cd0 Update chore tracker UI: Change button styles and icons for Jordyn, Declan, and Chloe for improved visibility and consistency. 2026-02-28 09:57:19 -05:00
Joshua King
7af2d0681c Refactor chore tracker for kids' names and chores: Update Emma, Liam, and Zoe to Jordyn, Declan, and Chloe. Adjust YAML configurations, automation, and notifications accordingly. 2026-02-28 09:44:33 -05:00
Joshua King
b635c771ec Refactor chore tracker for new kids: Emma, Liam, and Zoe
- Updated dashboard YAML to reflect new kids and their chores.
- Modified chores configuration to include new kids with appropriate icons and settings.
- Adjusted generation script to handle new MDI glyphs for icons.
- Revised Home Assistant integration to support new kids, including input buttons and automation for chores.
- Ensured all references to old kids (Jordyn, Declan, Chloe) are replaced with new names and corresponding logic.
2026-02-28 09:41:42 -05:00
Joshua King
c2ad832a82 Refactor chore tracker configuration for improved consistency and clarity 2026-02-28 08:49:25 -05:00
Joshua King
eb88e59550 Update ESP32 board configuration to esp32-s3-devkitc-1 for consistency 2026-02-28 08:34:37 -05:00
Joshua King
c63275d484 Rename 'btn' to 'button' for consistency in chore-tracker YAML and generator script 2026-02-28 08:13:13 -05:00
Joshua King
d7ed0c085c Update font configuration in chore-tracker YAML for improved consistency 2026-02-28 08:10:35 -05:00
Joshua King
3f697d690a Update font files in generate.py for improved typography consistency 2026-02-28 08:09:42 -05:00
Joshua King
2474ab72b3 Whitelist fonts directory in esphome for inclusion in version control 2026-02-28 08:05:05 -05:00
Joshua King
23e39c1fe5 Refactor font configuration in chore-tracker files for consistency 2026-02-27 22:28:32 -05:00
Joshua King
617cf7d7d1 Refactor font weight configuration in chore-tracker YAML for consistency 2026-02-27 22:26:02 -05:00
Joshua King
c4ae98cab2 Refactor font configuration in chore-tracker YAML files for improved clarity and consistency 2026-02-27 22:23:43 -05:00
Joshua King
b7b78c0785 Refactor font configuration in chore-tracker YAML files for consistency 2026-02-27 22:21:50 -05:00
Joshua King
dc815af608 Refactor display pin configuration in chore-tracker YAML files for consistency 2026-02-27 22:19:26 -05:00
Joshua King
21f97a5458 Fix formatting for chore switch comments in chore-tracker configuration 2026-02-27 22:17:51 -05:00
Joshua King
d42a23dfab Remove chore-tracker-ha.yaml configuration file and associated automations for chore tracking. 2026-02-27 22:15:50 -05:00
Joshua King
c07d58c98b Add chore tracker configuration for kids' chores with reset buttons and notifications 2026-02-27 22:08:02 -05:00
31144a4692 Merge branch 'main' of ssh://code.cubecraftcreations.com:2288/overseer/Home-Assistant 2026-02-27 21:59:51 -05:00
8d6359cdeb Merge branch 'main' of ssh://code.cubecraftcreations.com:2288/overseer/Home-Assistant 2026-02-27 21:57:05 -05:00
705e74d9de Udpates 2026-02-27 20:36:32 -05:00
13 changed files with 1692 additions and 1069 deletions

6
.gitignore vendored
View File

@@ -30,11 +30,14 @@
# Whitelist specific folders (uncomment as needed)
!automations/
!packages/
!packages/**
!packages/**
!packages/chore-tracker-ha.yaml
!scenes/
!scripts/
!blueprints/
!esphome/
!esphome/fonts/**
!esphome/ha-remote/
!esphome/ha-remote/components/
!esphome/ha-remote/components/**
@@ -47,7 +50,6 @@
!esphome/chore-tracker/
!esphome/chore-tracker/**
!esphome/chore-tracker-esphome.yaml
!chore-tracker-ha.yaml
# !packages/
# !themes/
# !node-red/

5
esphome/.gitignore vendored Normal file
View File

@@ -0,0 +1,5 @@
# Gitignore settings for ESPHome
# This is an example and may include too much for your use-case.
# You can modify this file to suit your needs.
/.esphome/
/secrets.yaml

View File

@@ -0,0 +1,293 @@
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

File diff suppressed because it is too large Load Diff

View File

@@ -13,30 +13,30 @@ views:
cards:
- type: markdown
content: >
### 😺 Jordyn
### 󰄛 Jordyn
{{
'All done!' if states('sensor.jordyn_all_chores_done') == 'True'
else states('sensor.jordyn_chores_done_today') ~ '/5 chores done'
'All done!' if states('sensor.jordyn_all_chores_done') == 'True'
else states('sensor.jordyn_chores_done_today') ~ '/4 chores done'
}}
- type: markdown
content: >
### 🤓 Declan
### 󱑷 Declan
{{
'All done!' if states('sensor.declan_all_chores_done') == 'True'
else states('sensor.declan_chores_done_today') ~ '/4 chores done'
'All done!' if states('sensor.declan_all_chores_done') == 'True'
else states('sensor.declan_chores_done_today') ~ '/2 chores done'
}}
- type: markdown
content: >
### 🌝 Chloe
### 󱚣 Chloe
{{
'All done!' if states('sensor.chloe_all_chores_done') == 'True'
else states('sensor.chloe_chores_done_today') ~ '/6 chores done'
'All done!' if states('sensor.chloe_all_chores_done') == 'True'
else states('sensor.chloe_chores_done_today') ~ '/3 chores done'
}}
- type: button
name: "Reset ALL Chores"
name: "Reset ALL Chores"
icon: mdi:restart-alert
tap_action:
action: call-service
@@ -44,87 +44,69 @@ views:
target:
entity_id: input_button.reset_all_chores
- type: entities
title: "😺 Jordyn's Chores"
title: "󰄛 Jordyn's Chores"
entities:
- entity: switch.chore_tracker_jordyn_make_bed
name: "🛏️ Make Bed"
- entity: switch.chore_tracker_jordyn_put_on_underwear
name: "󰋣 Put on underwear"
- entity: switch.chore_tracker_jordyn_brush_teeth
name: "🦷 Brush Teeth"
name: "󰦩 Brush Teeth"
- entity: switch.chore_tracker_jordyn_tidy_room
name: "🧹 Tidy Room"
- entity: switch.chore_tracker_jordyn_fill_water_bowls
name: "󰆫 Fill water bowls"
- entity: switch.chore_tracker_jordyn_homework
name: "📚 Homework"
- entity: switch.chore_tracker_jordyn_feed_dog
name: "🐾 Feed Dog"
- entity: switch.chore_tracker_jordyn_restock_cat_food_cans
name: "󱜜 Restock Cat Food Cans"
- type: divider
- entity: sensor.jordyn_chores_done_today
name: "Chores done today"
- entity: sensor.jordyn_all_chores_done
name: "All Done?"
name: "All Done?"
- type: button
name: "Reset Jordyn's Chores"
name: "Reset Jordyn's Chores"
tap_action:
action: call-service
service: input_button.press
target:
entity_id: input_button.jordyn_reset_chores
- type: entities
title: "🤓 Declan's Chores"
title: "󱑷 Declan's Chores"
entities:
- entity: switch.chore_tracker_declan_make_bed
name: "🛏️ Make Bed"
- entity: switch.chore_tracker_declan_take_morning_pill
name: "󰐂 Take morning pill"
- entity: switch.chore_tracker_declan_brush_teeth
name: "🦷 Brush Teeth"
- entity: switch.chore_tracker_declan_set_table
name: "🍽️ Set Table"
- entity: switch.chore_tracker_declan_take_out_trash
name: "🗑️ Take Out Trash"
- entity: switch.chore_tracker_declan_scoop_dog_poop
name: "󰇷 Scoop Dog Poop"
- type: divider
- entity: sensor.declan_chores_done_today
name: "Chores done today"
- entity: sensor.declan_all_chores_done
name: "All Done?"
name: "All Done?"
- type: button
name: "Reset Declan's Chores"
name: "Reset Declan's Chores"
tap_action:
action: call-service
service: input_button.press
target:
entity_id: input_button.declan_reset_chores
- type: entities
title: "🌝 Chloe's Chores"
title: "󱚣 Chloe's Chores"
entities:
- entity: switch.chore_tracker_chloe_make_bed
name: "🛏️ Make Bed"
- entity: switch.chore_tracker_chloe_fill_kitty_feeders
name: "󰊩 Fill kitty feeders"
- entity: switch.chore_tracker_chloe_brush_teeth
name: "🦷 Brush Teeth"
- entity: switch.chore_tracker_chloe_scoop_kitty_litter
name: "󰇷 Scoop Kitty Litter"
- entity: switch.chore_tracker_chloe_water_plants
name: "🌱 Water Plants"
- entity: switch.chore_tracker_chloe_homework
name: "📚 Homework"
- entity: switch.chore_tracker_chloe_tidy_room
name: "🧹 Tidy Room"
- entity: switch.chore_tracker_chloe_practice_piano
name: "🎹 Practice Piano"
- entity: switch.chore_tracker_chloe_replace_kitty_litter_bags
name: "󰇷 Replace Kitty Litter Bags"
- type: divider
- entity: sensor.chloe_chores_done_today
name: "Chores done today"
- entity: sensor.chloe_all_chores_done
name: "All Done?"
name: "All Done?"
- type: button
name: "Reset Chloe's Chores"
name: "Reset Chloe's Chores"
tap_action:
action: call-service
service: input_button.press

View File

@@ -3,12 +3,30 @@
#
# Edit this file to change kids, chores, and settings.
# Then run: python3 generate.py
#
# ICONS: Use MDI codepoints from https://pictogrammers.com/library/mdi/
# Format: "\U000FXXXX" e.g. mdi:bed = "\U000F02E3"
# The kid avatar field also uses MDI codepoints.
#
# Common chore icons:
# Bed: "\U000F02E3" mdi:bed
# Brush teeth: "\U000F09A9" mdi:toothbrush
# Broom: "\U000F00A8" mdi:broom
# Book: "\U000F0219" mdi:book-open-variant
# Dog: "\U000F01F9" mdi:dog
# Paw: "\U000F0265" mdi:paw
# Silverware: "\U000F03E7" mdi:silverware-fork-knife
# Trash: "\U000F05B8" mdi:trash-can
# Watering can: "\U000F1B25" mdi:watering-can
# Piano: "\U000F0F9E" mdi:piano
# Dumbbell: "\U000F0F1E" mdi:dumbbell
# Shower: "\U000F0467" mdi:shower
################################################################################
settings:
device_name: chore-tracker
friendly_name: "Chore Tracker"
wifi_ssid: !secret wifi_iot_ssid
wifi_ssid: !secret wifi_ssid
wifi_password: !secret wifi_password
api_key: !secret api_encryption_key
ota_password: !secret ota_password
@@ -17,54 +35,42 @@ settings:
notify_service: notify.notify
# ── KIDS ──────────────────────────────────────────────────────────────────────
# Each kid has their own chore list.
# name, avatar, color, color_dark, and chores are all required.
# avatar: MDI codepoint shown in the sidebar on the kid's chore page
# color / color_dark: hex colours for that kid's sidebar
#
kids:
- name: Jordyn
avatar: "😺"
color: "4D96FF"
color_dark: "2A6FCC"
avatar: "\U000F011B" # mdi:cat
color: "5F5980" # #5F5980 is a nice light blue, but it's a bit hard to read when used as a background colour for the sidebar. So I use a darker version of the same colour for the sidebar background.
color_dark: "2F3061" # #2F3061 is a darker version of #5F5980, and is easier to read when used as a background colour for the sidebar.
chores:
- name: Make Bed
icon: "🛏️"
- name: Put on underwear
icon: "\U000F02E3" # mdi:bed
- name: Brush Teeth
icon: "🦷"
- name: Tidy Room
icon: "🧹"
- name: Homework
icon: "📚"
- name: Feed Dog
icon: "🐾"
icon: "\U000F09A9" # mdi:toothbrush
- name: Fill water bowls
icon: "\U000F01AB" # mdi:cup-water
- name: Restock Cat Food Cans
icon: "\U000F171C" # mdi:food-turkey
- name: Declan
avatar: "🤓"
color: "C77DFF"
color_dark: "8B42CC"
avatar: "\U000F1477" # mdi:wizard-hat
color: "720026" # #720026 is a deep red, used for the kid's sidebar background.
color_dark: "4F000B" # #4F000B is a darker version of #720026, easier to read when used as a background colour for the sidebar.
chores:
- name: Make Bed
icon: "🛏️"
- name: Brush Teeth
icon: "🦷"
- name: Set Table
icon: "🍽️"
- name: Take Out Trash
icon: "🗑️"
- name: Take morning pill
icon: "\U000F0402" # mdi:pill
- name: Scoop Dog Poop
icon: "\U000F01F7" # mdi:emoticon-poop
- name: Chloe
avatar: "🌝"
color: "FF6B9D"
color_dark: "CC3A6F"
avatar: "\U000F16A3" # mdi:robot-excited
color: "73683B" # #73683B is a light brown, used for the kid's sidebar background.
color_dark: "583E23" # #583E23 is a darker version of #73683B, easier to read when used as a background colour for the sidebar.
chores:
- name: Make Bed
icon: "🛏️"
- name: Brush Teeth
icon: "🦷"
- name: Water Plants
icon: "🌱"
- name: Homework
icon: "📚"
- name: Tidy Room
icon: "🧹"
- name: Practice Piano
icon: "🎹"
- name: Fill kitty feeders
icon: "\U000F02A9" # mdi:bowl-outline
- name: Scoop Kitty Litter
icon: "\U000F01F7" # mdi:emoticon-poop
- name: Replace Kitty Litter Bags
icon: "\U000F01F7" # mdi:emoticon-poop

View File

@@ -7,12 +7,14 @@ Reads chores_config.yaml and generates:
• chore-tracker-ha.yaml (Home Assistant config)
• chore-tracker-dashboard.yaml (Lovelace dashboard)
Target hardware: Waveshare ESP32-S3-Touch-LCD-7 (800x480)
Home screen behaviour:
- Each kid's button has a RED outline while any chores remain undone
- Button turns SOLID GREEN when ALL chores are complete
- Resets to red automatically at midnight
No star tracking — chores are simply done or not done.
Each kid must define their own chores list in chores_config.yaml.
Usage:
python3 generate.py
@@ -35,6 +37,10 @@ except ImportError:
# Helpers
# ─────────────────────────────────────────────────────────────────────────────
# Screen padding — space kept clear around all edges (pixels)
PAD = 12
def slug(name: str) -> str:
return name.lower().replace(" ", "_").replace("-", "_")
@@ -47,9 +53,15 @@ def eid(kid: dict, chore: dict) -> str:
def ha_switch(kid: dict, chore: dict) -> str:
return f"switch.chore_tracker_{eid(kid, chore)}"
# mdi:checkbox-marked-circle-outline — safe default if icon is omitted
DEFAULT_ICON = "\U000F0133"
def get_chores(kid: dict) -> list:
"""Each kid must define their own chore list."""
return kid["chores"]
chores = kid["chores"]
for c in chores:
if "icon" not in c:
c["icon"] = DEFAULT_ICON
return chores
# ─────────────────────────────────────────────────────────────────────────────
@@ -57,15 +69,15 @@ def get_chores(kid: dict) -> list:
# ─────────────────────────────────────────────────────────────────────────────
def gen_esphome(cfg: dict) -> str:
s = cfg["settings"]
s = cfg["settings"]
kids = cfg["kids"]
# ── Switches ──────────────────────────────────────────────────────────────
# ── Switches (one per chore per kid) ──────────────────────────────────────
switch_blocks = []
for kid in kids:
chores = get_chores(kid)
ks = kid_slug(kid)
switch_blocks.append(f"\n # ── {kid['name']}'s chores ──────────────────────────")
switch_blocks.append(f"\n # ── {kid['name']}'s chores ──────────────────────────\n")
for chore in chores:
e = eid(kid, chore)
switch_blocks.append(f"""\
@@ -91,7 +103,7 @@ def gen_esphome(cfg: dict) -> str:
entity_id: switch.chore_tracker_{e}
""")
# ── Sensors (chores-done count only) ──────────────────────────────────────
# ── Sensors (chores-done count only — no stars) ────────────────────────
sensor_blocks = []
for kid in kids:
chores = get_chores(kid)
@@ -112,7 +124,7 @@ def gen_esphome(cfg: dict) -> str:
""")
# ── Midnight reset ────────────────────────────────────────────────────────
# ── Midnight reset calls ──────────────────────────────────────────────────
reset_calls = "\n".join(
f" id(reset_{kid_slug(k)}_chores).execute();"
for k in kids
@@ -121,6 +133,13 @@ def gen_esphome(cfg: dict) -> str:
lvgl_pages = _gen_lvgl_pages(kids)
scripts = _gen_scripts(kids)
# Collect all unique MDI glyphs used (avatars + chore icons)
all_glyphs = sorted(set(
[k["avatar"] for k in kids] +
[c["icon"] for k in kids for c in get_chores(k)]
))
glyphs_str = "".join(all_glyphs)
return f"""\
################################################################################
# chore-tracker-esphome.yaml (AUTO-GENERATED — edit chores_config.yaml)
@@ -128,19 +147,30 @@ def gen_esphome(cfg: dict) -> str:
#
# HOME SCREEN BEHAVIOUR:
# Red outline = chores incomplete
# Solid green = all chores done
# Solid green = all chores done
# Resets to red automatically at midnight
#
# Hardware: Waveshare ESP32-S3-Touch-LCD-7 (800x480)
################################################################################
esphome:
name: {s["device_name"]}
friendly_name: "{s["friendly_name"]}"
on_boot:
priority: -10
then:
- light.turn_on: backlight
- lvgl.page.show: page_home
esp32:
board: esp32s3box
board: esp32-s3-devkitc-1
framework:
type: esp-idf
psram:
mode: octal
speed: 80MHz
wifi:
ssid: {s["wifi_ssid"]}
password: {s["wifi_password"]}
@@ -160,54 +190,58 @@ ota:
- platform: esphome
password: {s["ota_password"]}
# ── Display — Waveshare ESP32-S3 7" (adjust pins for your board revision) ─────
display:
- platform: rpi_dpi_rgb
id: main_display
auto_clear_enabled: false
color_order: RGB
dimensions:
width: 800
height: 480
de_pin:
number: GPIO40
ignore_strapping_warning: true
hsync_pin:
number: GPIO39
ignore_strapping_warning: true
vsync_pin:
number: GPIO41
pclk_pin: GPIO42
data_pins:
red: [GPIO45, GPIO48, GPIO47, GPIO21, GPIO14]
green: [GPIO5, GPIO6, GPIO7, GPIO15, GPIO16, GPIO4]
blue: [GPIO8, GPIO3, GPIO46, GPIO9, GPIO1]
touchscreen:
- platform: gt911
id: touch
display: main_display
i2c_id: i2c_touch
interrupt_pin: GPIO2
reset_pin: GPIO38
# ── I2C ───────────────────────────────────────────────────────────────────────
i2c:
- id: i2c_touch
sda: GPIO19
scl: GPIO20
frequency: 400kHz
sda: 8
scl: 9
# ── CH422G IO Expander (controls LCD reset, touch reset, backlight) ───────────
ch422g:
- id: ch422g_hub
# ── Display ───────────────────────────────────────────────────────────────────
display:
- platform: mipi_rgb
model: ESP32-S3-TOUCH-LCD-7-800X480
id: main_display
update_interval: never
auto_clear_enabled: false
reset_pin:
ch422g: ch422g_hub
number: 3
mode:
output: true
# ── Touchscreen ───────────────────────────────────────────────────────────────
touchscreen:
platform: gt911
id: touch
update_interval: 120ms
reset_pin:
ch422g: ch422g_hub
number: 1
mode:
output: true
# ── Backlight ─────────────────────────────────────────────────────────────────
output:
- platform: ledc
pin: GPIO17
id: backlight_output
- platform: template
id: lcd_backlight_out
type: binary
write_action:
- if:
condition:
lambda: return state;
then:
- switch.turn_on: lcd_backlight_raw
else:
- switch.turn_off: lcd_backlight_raw
light:
- platform: monochromatic
output: backlight_output
- platform: binary
name: "Display Backlight"
output: lcd_backlight_out
id: backlight
restore_mode: ALWAYS_ON
# ── Midnight reset ────────────────────────────────────────────────────────────
time:
@@ -222,25 +256,46 @@ time:
{reset_calls}
- lvgl.page.show: page_home
fonts:
- file: "gfonts://Nunito:wght@900"
# ── Fonts (place files in /config/esphome/fonts/) ────────────────────────────
font:
- file: "fonts/Nunito-Black.ttf"
id: font_title
size: 40
- file: "gfonts://Nunito:wght@800"
- file: "fonts/Nunito-ExtraBold.ttf"
id: font_name
size: 30
- file: "gfonts://Nunito:wght@700"
- file: "fonts/Nunito-Bold.ttf"
id: font_med
size: 22
- file: "gfonts://Nunito:wght@600"
- file: "fonts/Nunito-SemiBold.ttf"
id: font_small
size: 17
- file: "gfonts://Nunito:wght@600"
- file: "fonts/Nunito-SemiBold.ttf"
id: font_tiny
size: 13
# MDI icon font — used for chore icons and kid avatars
- file: "fonts/materialdesignicons-webfont.ttf"
id: font_mdi_large
size: 48
bpp: 4
glyphs: {glyphs_str}
- file: "fonts/materialdesignicons-webfont.ttf"
id: font_mdi_small
size: 32
bpp: 4
glyphs: {glyphs_str}
# ── Switches — one per chore per kid, synced to HA ───────────────────────────
# ── Switches — backlight raw + one per chore per kid ─────────────────────────
switch:
- platform: gpio
id: lcd_backlight_raw
name: "LCD Backlight Raw"
restore_mode: ALWAYS_ON
pin:
ch422g: ch422g_hub
number: 2
mode:
output: true
{"".join(switch_blocks)}
# ── Sensors — reported to HA ─────────────────────────────────────────────────
@@ -258,7 +313,7 @@ lvgl:
touchscreens:
- touch
theme:
btn:
button:
radius: 20
border_width: 0
pages:
@@ -274,11 +329,12 @@ def _gen_lvgl_pages(kids: list) -> str:
pages = []
# ── HOME PAGE ─────────────────────────────────────────────────────────────
n = len(kids)
btn_w = min(200, max(140, (760 - 20 * (n - 1)) // n))
n = len(kids)
usable_w = 800 - PAD * 2
btn_w = min(200, max(140, (usable_w - 40 - 20 * (n - 1)) // n))
total_w = btn_w * n + 20 * (n - 1)
start_x = (800 - total_w) // 2
btn_y = 130
start_x = PAD + (usable_w - total_w) // 2
btn_y = PAD + 118
btn_h = 210
reset_all_calls = "\n".join(
@@ -291,16 +347,15 @@ def _gen_lvgl_pages(kids: list) -> str:
x = start_x + i * (btn_w + 20)
ks = kid_slug(kid)
home_btns += f"""\
- btn:
- button:
id: home_btn_{ks}
x: {x}
y: {btn_y}
width: {btn_w}
height: {btn_h}
bg_color: 0xFFFFFF
bg_opa: TRANSP
border_color: 0xFF4757
border_width: 5
bg_color: 0xFF4757
bg_opa: COVER
border_width: 0
radius: 24
on_click:
then:
@@ -310,20 +365,20 @@ def _gen_lvgl_pages(kids: list) -> str:
align: CENTER
y: -45
text: "{kid['avatar']}"
text_font: font_title
text_font: font_mdi_large
- label:
align: CENTER
y: 22
text: "{kid['name']}"
text_font: font_name
text_color: 0x2D3436
text_color: 0xFFFFFF
- label:
id: home_status_{ks}
align: CENTER
y: 66
text: "not done"
text_font: font_tiny
text_color: 0xFF4757
text_color: 0xFFFFFF
"""
@@ -334,7 +389,7 @@ def _gen_lvgl_pages(kids: list) -> str:
- label:
x: 0
y: 36
width: 800
width: {800 - PAD * 2}
align: TOP_MID
text: "Chore Tracker"
text_font: font_title
@@ -342,14 +397,14 @@ def _gen_lvgl_pages(kids: list) -> str:
- label:
x: 0
y: 90
width: 800
width: {800 - PAD * 2}
align: TOP_MID
text: "Tap a name to check off chores"
text_font: font_small
text_color: 0xB2BEC3
- btn:
- button:
x: 640
y: 424
y: 428
width: 148
height: 40
bg_color: 0xFF4757
@@ -360,7 +415,7 @@ def _gen_lvgl_pages(kids: list) -> str:
widgets:
- label:
align: CENTER
text: "Reset All"
text: "Reset All"
text_color: 0xFFFFFF
text_font: font_tiny
{home_btns}""")
@@ -375,21 +430,21 @@ def _gen_lvgl_pages(kids: list) -> str:
rows = (n_chores + cols - 1) // cols
sidebar_w = 172
gap = 10
content_w = 800 - sidebar_w - gap * 2
content_w = 800 - sidebar_w - gap - 12 # right pad
card_w = (content_w - gap * (cols - 1)) // cols
card_h = min(138, (480 - gap * (rows + 1)) // rows)
card_h = min(138, (480 - PAD - gap * (rows + 1)) // rows)
card_widgets = ""
for idx, chore in enumerate(chores):
row = idx // cols
col = idx % cols
x = sidebar_w + gap + col * (card_w + gap)
y = gap + row * (card_h + gap)
y = 12 + row * (card_h + gap)
e = eid(kid, chore)
icon_y = -(card_h // 4)
label_y = card_h // 8
card_widgets += f"""\
- btn:
- button:
id: card_{e}
x: {x}
y: {y}
@@ -417,7 +472,8 @@ def _gen_lvgl_pages(kids: list) -> str:
align: CENTER
y: {icon_y}
text: "{chore['icon']}"
text_font: font_med
text_font: font_mdi_small
text_color: 0x{kid['color']}
- label:
align: CENTER
y: {label_y}
@@ -449,7 +505,7 @@ def _gen_lvgl_pages(kids: list) -> str:
width: {sidebar_w}
align: TOP_MID
text: "{kid['avatar']}"
text_font: font_title
text_font: font_mdi_large
- label:
x: 0
y: 76
@@ -488,9 +544,9 @@ def _gen_lvgl_pages(kids: list) -> str:
text: ""
text_font: font_small
text_color: 0xFFFFFF
- btn:
- button:
x: 14
y: 364
y: {480 - PAD - 44 - 6 - 44}
width: {sidebar_w - 28}
height: 44
bg_color: 0xFF4757
@@ -501,12 +557,12 @@ def _gen_lvgl_pages(kids: list) -> str:
widgets:
- label:
align: CENTER
text: "Reset"
text: "Reset"
text_color: 0xFFFFFF
text_font: font_small
- btn:
- button:
x: 14
y: 420
y: {480 - PAD - 44}
width: {sidebar_w - 28}
height: 44
bg_color: 0x{kid['color_dark']}
@@ -517,7 +573,7 @@ def _gen_lvgl_pages(kids: list) -> str:
widgets:
- label:
align: CENTER
text: "Home"
text: "Home"
text_color: 0xFFFFFF
text_font: font_small
@@ -560,7 +616,6 @@ def _gen_scripts(kids: list) -> str:
f" id({eid(kid,c)}).turn_off();" for c in chores
)
# Reset script
blocks.append(f"""\
- id: reset_{ks}_chores
mode: single
@@ -571,7 +626,6 @@ def _gen_scripts(kids: list) -> str:
""")
# UI update script
blocks.append(f"""\
- id: update_{ks}_ui
mode: single
@@ -587,9 +641,9 @@ def _gen_scripts(kids: list) -> str:
lv_label_set_text(id(progress_label_{ks}), buf);
// All-done message in sidebar
lv_label_set_text(id(all_done_label_{ks}), done == total ? "\\U0001F389 All done!" : "");
lv_label_set_text(id(all_done_label_{ks}), done == total ? "All done!" : "");
// Home button colour
// Home button: RED outline = incomplete, SOLID GREEN = all done
if (done == total) {{
lv_obj_set_style_bg_opa(id(home_btn_{ks}), LV_OPA_COVER, LV_PART_MAIN);
lv_obj_set_style_bg_color(id(home_btn_{ks}), lv_color_hex(0x6BCB77), LV_PART_MAIN);
@@ -597,9 +651,9 @@ def _gen_scripts(kids: list) -> str:
lv_label_set_text(id(home_status_{ks}), "\\u2713 all done!");
lv_obj_set_style_text_color(id(home_status_{ks}), lv_color_hex(0xFFFFFF), LV_PART_MAIN);
}} else {{
lv_obj_set_style_bg_opa(id(home_btn_{ks}), LV_OPA_TRANSP, LV_PART_MAIN);
lv_obj_set_style_border_color(id(home_btn_{ks}), lv_color_hex(0xFF4757), LV_PART_MAIN);
lv_obj_set_style_border_width(id(home_btn_{ks}), 5, LV_PART_MAIN);
lv_obj_set_style_bg_opa(id(home_btn_{ks}), LV_OPA_COVER, LV_PART_MAIN);
lv_obj_set_style_bg_color(id(home_btn_{ks}), lv_color_hex(0xFF4757), LV_PART_MAIN);
lv_obj_set_style_border_width(id(home_btn_{ks}), 0, LV_PART_MAIN);
snprintf(buf, sizeof(buf), "%d left", total - done);
lv_label_set_text(id(home_status_{ks}), buf);
lv_obj_set_style_text_color(id(home_status_{ks}), lv_color_hex(0xFF4757), LV_PART_MAIN);
@@ -617,7 +671,7 @@ def _gen_scripts(kids: list) -> str:
# ─────────────────────────────────────────────────────────────────────────────
def gen_ha(cfg: dict) -> str:
s = cfg["settings"]
s = cfg["settings"]
kids = cfg["kids"]
sensor_blocks = []
@@ -643,7 +697,6 @@ def gen_ha(cfg: dict) -> str:
""")
# input_button helpers
input_buttons = "".join(f"""\
{kid_slug(k)}_reset_chores:
name: "Reset {k['name']}'s Chores"
@@ -655,10 +708,8 @@ def gen_ha(cfg: dict) -> str:
icon: mdi:restart-alert
"""
# Automations
auto_blocks = []
# Per-kid HA reset button
for kid in kids:
ks = kid_slug(kid)
off_calls = "\n".join(
@@ -667,7 +718,7 @@ def gen_ha(cfg: dict) -> str:
)
auto_blocks.append(f"""\
- id: {ks}_reset_from_ha
alias: "Chore Tracker Reset {kid['name']}'s chores from HA"
alias: "Chore Tracker - Reset {kid['name']}'s chores from HA"
trigger:
- platform: state
entity_id: input_button.{ks}_reset_chores
@@ -676,29 +727,27 @@ def gen_ha(cfg: dict) -> str:
""")
# Reset All from HA
all_off_calls = "\n".join(
all_off = "\n".join(
f" - service: switch.turn_off\n target:\n entity_id: {ha_switch(kid, c)}"
for kid in kids for c in get_chores(kid)
)
auto_blocks.append(f"""\
- id: reset_all_from_ha
alias: "Chore Tracker Reset ALL chores from HA"
alias: "Chore Tracker - Reset ALL chores from HA"
trigger:
- platform: state
entity_id: input_button.reset_all_chores
action:
{all_off_calls}
{all_off}
""")
# All-done notification
for kid in kids:
ks = kid_slug(kid)
n = len(get_chores(kid))
auto_blocks.append(f"""\
- id: {ks}_all_done_notify
alias: "Chore Tracker {kid['name']} all done!"
alias: "Chore Tracker - {kid['name']} all done!"
trigger:
- platform: state
entity_id: sensor.{ks}_all_chores_done
@@ -706,12 +755,11 @@ def gen_ha(cfg: dict) -> str:
action:
- service: {s['notify_service']}
data:
title: "🎉 {kid['name']} finished all chores!"
title: "{kid['name']} finished all chores!"
message: "{kid['name']} completed all {n} chores today!"
""")
# Evening reminder
reminder_actions = "".join(f"""\
- if:
condition: template
@@ -719,7 +767,7 @@ def gen_ha(cfg: dict) -> str:
then:
- service: {s['notify_service']}
data:
title: "📋 {k['name']} has unfinished chores"
title: "{k['name']} has unfinished chores"
message: >
{k['name']} has done
{{{{ states('sensor.{kid_slug(k)}_chores_done_today') }}}}/{len(get_chores(k))} chores today.
@@ -727,7 +775,7 @@ def gen_ha(cfg: dict) -> str:
auto_blocks.append(f"""\
- id: chore_reminder_evening
alias: "Chore Tracker Evening reminder"
alias: "Chore Tracker - Evening reminder"
trigger:
- platform: time
at: "{s['reminder_time']}:00"
@@ -741,8 +789,8 @@ def gen_ha(cfg: dict) -> str:
# Kids: {", ".join(k["name"] for k in kids)}
#
# BIDIRECTIONAL SYNC:
# Screen HA: Each switch calls homeassistant.service on toggle
# HA Screen: ESPHome native API handles this automatically
# Screen to HA: Each switch calls homeassistant.service on toggle
# HA to Screen: ESPHome native API handles this automatically
################################################################################
input_button:
@@ -767,7 +815,7 @@ def gen_dashboard(cfg: dict) -> str:
content: >
### {kid['avatar']} {kid['name']}
{{{{
'All done!' if states('sensor.{kid_slug(kid)}_all_chores_done') == 'True'
'All done!' if states('sensor.{kid_slug(kid)}_all_chores_done') == 'True'
else states('sensor.{kid_slug(kid)}_chores_done_today') ~ '/{len(get_chores(kid))} chores done'
}}}}
""" for kid in kids)
@@ -789,9 +837,9 @@ def gen_dashboard(cfg: dict) -> str:
- entity: sensor.{ks}_chores_done_today
name: "Chores done today"
- entity: sensor.{ks}_all_chores_done
name: "All Done?"
name: "All Done?"
- type: button
name: "Reset {kid['name']}'s Chores"
name: "Reset {kid['name']}'s Chores"
tap_action:
action: call-service
service: input_button.press
@@ -815,7 +863,7 @@ views:
cards:
{summary_cards}
- type: button
name: "Reset ALL Chores"
name: "Reset ALL Chores"
icon: mdi:restart-alert
tap_action:
action: call-service
@@ -859,18 +907,24 @@ def main():
print("❌ No kids defined in config!")
sys.exit(1)
# Validate every kid has their own chore list
errors = [k["name"] for k in kids if "chores" not in k or not k["chores"]]
if errors:
print(f"❌ These kids have no chores defined: {', '.join(errors)}")
print(" Add a 'chores:' list under each kid in chores_config.yaml")
sys.exit(1)
# Warn about missing icons (will use default, not crash)
for k in kids:
for c in k.get("chores", []):
if "icon" not in c:
print(f"⚠️ {k['name']} / '{c['name']}' has no icon — using default")
total_chores = sum(len(get_chores(k)) for k in kids)
print(f"✅ Config loaded: {len(kids)} kid(s), {total_chores} total chores")
for k in kids:
chores = get_chores(k)
print(f" {k['avatar']} {k['name']}: {len(chores)} chores{', '.join(c['name'] for c in chores)}")
print(f" {k['avatar']} {k['name']}: {len(chores)} chores"
f"{', '.join(c['name'] for c in chores)}")
print()
files = {
@@ -886,9 +940,9 @@ def main():
print()
print("✅ Done!")
print(" 1. Flash chore-tracker-esphome.yaml ESPHome dashboard")
print(" 2. Merge chore-tracker-ha.yaml HA config + restart HA")
print(" 3. Paste chore-tracker-dashboard.yaml New HA dashboard")
print(" 1. Flash chore-tracker-esphome.yaml -> ESPHome dashboard")
print(" 2. Merge chore-tracker-ha.yaml -> HA config + restart HA")
print(" 3. Paste chore-tracker-dashboard.yaml -> New HA dashboard")
if __name__ == "__main__":

View File

@@ -35,18 +35,45 @@ captive_portal:
web_server:
port: 80
# GPIO Outputs for fan control
# IMPORTANT: inverted: true because NPN transistor driver inverts the logic
# GPIO HIGH -> NPN ON -> Gate LOW -> Fan OFF
# GPIO LOW -> NPN OFF -> Gate HIGH (5V via pull-up) -> Fan ON
output:
- platform: gpio
pin: GPIO3
pin:
number: GPIO3 # D1 on XIAO
inverted: true
id: fan_80mm_output
- platform: gpio
pin: GPIO4
pin:
number: GPIO4 # D2 on XIAO
inverted: true
id: fan_120mm_output
# Fan entities for Home Assistant
fan:
- platform: binary
name: "80mm Exhaust Fan"
id: fan_80mm
output: fan_80mm_output
- platform: binary
name: "120mm Exhaust Fan"
output: fan_120mm_output
id: fan_120mm
output: fan_120mm_output
# Optional: Add a switch to control both fans together
switch:
- platform: template
name: "All Fans"
id: all_fans
turn_on_action:
- fan.turn_on: fan_80mm
- fan.turn_on: fan_120mm
turn_off_action:
- fan.turn_off: fan_80mm
- fan.turn_off: fan_120mm
lambda: |-
return id(fan_80mm).state && id(fan_120mm).state;

335
esphome/litter-box-cam.yaml Normal file
View File

@@ -0,0 +1,335 @@
# =============================================================================
# LITTER BOX MONITOR - DFRobot ESP32-S3 AI Camera
# =============================================================================
substitutions:
device_name: litter-box-cam
friendly_name: "Litter Box Camera"
# -----------------------------------------------------------------------------
# Core Device Configuration
# -----------------------------------------------------------------------------
esphome:
name: ${device_name}
friendly_name: ${friendly_name}
platformio_options:
build_flags: "-DBOARD_HAS_PSRAM"
board_build.arduino.memory_type: qio_opi
esp32:
board: esp32-s3-devkitc-1
framework:
type: arduino
version: latest
flash_size: 16MB
# -----------------------------------------------------------------------------
# PSRAM Configuration (Critical for camera)
# -----------------------------------------------------------------------------
psram:
mode: octal
speed: 80MHz
# -----------------------------------------------------------------------------
# Logging & API
# -----------------------------------------------------------------------------
logger:
level: INFO
api:
encryption:
key: !secret api_encryption_key
services:
- service: capture_and_analyze
then:
- script.execute: capture_image_script
- service: update_status
variables:
needs_scooping: bool
litter_level_low: bool
cat_present: bool
cleanliness_score: int
then:
- lambda: |-
id(needs_scooping_sensor).publish_state(needs_scooping);
id(litter_level_low_sensor).publish_state(litter_level_low);
id(cat_present_sensor).publish_state(cat_present);
id(cleanliness_score_sensor).publish_state(cleanliness_score);
id(last_analysis_time).publish_state(id(homeassistant_time).now().timestamp);
if (cat_present) {
id(daily_cat_visits) += 1;
id(cat_visits_sensor).publish_state(id(daily_cat_visits));
}
ota:
- platform: esphome
password: !secret ota_password
# -----------------------------------------------------------------------------
# WiFi Configuration
# -----------------------------------------------------------------------------
wifi:
ssid: !secret wifi_iot_ssid
password: !secret wifi_password
ap:
ssid: "${device_name}-AP"
password: !secret wifi_password
captive_portal:
# -----------------------------------------------------------------------------
# MQTT Configuration
# -----------------------------------------------------------------------------
mqtt:
id: mqtt_client
broker: !secret mqtt_broker
username: !secret mqtt_username
password: !secret mqtt_password
topic_prefix: litter_box
# -----------------------------------------------------------------------------
# Time Synchronization
# -----------------------------------------------------------------------------
time:
- platform: homeassistant
id: homeassistant_time
on_time:
- seconds: 0
minutes: 0
hours: 0
then:
- lambda: |-
id(daily_cat_visits) = 0;
id(cat_visits_sensor).publish_state(0);
# -----------------------------------------------------------------------------
# Global Variables
# -----------------------------------------------------------------------------
globals:
- id: daily_cat_visits
type: int
restore_value: yes
initial_value: '0'
- id: motion_cooldown_active
type: bool
initial_value: 'false'
- id: publish_next_image
type: bool
initial_value: 'false'
# -----------------------------------------------------------------------------
# Camera Configuration (OV3660 - DFR1154 Specific)
# -----------------------------------------------------------------------------
esp32_camera:
id: litter_cam
name: "Litter Box Camera"
# Hardware Interface - DFR1154 Pinout
external_clock:
pin: GPIO5
frequency: 20MHz
i2c_pins:
sda: GPIO8
scl: GPIO9
data_pins: [GPIO16, GPIO18, GPIO21, GPIO17, GPIO14, GPIO7, GPIO6, GPIO4]
vsync_pin: GPIO1
href_pin: GPIO2
pixel_clock_pin: GPIO15
# Image Parameters
max_framerate: 10 fps
idle_framerate: 0.1 fps
resolution: 800x600
jpeg_quality: 10
vertical_flip: true
horizontal_mirror: false
aec_mode: AUTO
agc_mode: AUTO
wb_mode: AUTO
brightness: 0
contrast: 1
saturation: 0
on_image:
then:
- lambda: |-
if (!id(publish_next_image)) return;
id(publish_next_image) = false;
const uint8_t *data = x->get_data();
size_t len = x->get_data_length();
size_t out_len = 4 * ((len + 2) / 3);
char *buf = (char *)malloc(out_len + 1);
if (!buf) return;
static const char b64[] =
"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
size_t j = 0;
for (size_t i = 0; i < len; i += 3) {
uint32_t b = ((uint32_t)data[i] << 16);
if (i + 1 < len) b |= ((uint32_t)data[i + 1] << 8);
if (i + 2 < len) b |= data[i + 2];
buf[j++] = b64[(b >> 18) & 0x3F];
buf[j++] = b64[(b >> 12) & 0x3F];
buf[j++] = (i + 1 < len) ? b64[(b >> 6) & 0x3F] : '=';
buf[j++] = (i + 2 < len) ? b64[b & 0x3F] : '=';
}
buf[j] = '\0';
id(mqtt_client).publish("litter_box/camera/image", buf);
ESP_LOGI("capture", "Published image (%d bytes raw, %d base64)", len, j);
id(status_message).publish_state("Image sent for analysis");
free(buf);
# Web server for manual viewing
esp32_camera_web_server:
- port: 8080
mode: stream
- port: 8081
mode: snapshot
# -----------------------------------------------------------------------------
# GPIO Outputs
# -----------------------------------------------------------------------------
output:
- id: led_status
platform: gpio
pin: GPIO3
- id: ir_led
platform: gpio
pin: GPIO47
- id: speaker_gain
platform: gpio
pin: GPIO41
- id: speaker_mode
platform: gpio
pin: GPIO40
# -----------------------------------------------------------------------------
# Lights
# -----------------------------------------------------------------------------
light:
- platform: binary
name: "Status LED"
id: status_led
output: led_status
- platform: binary
name: "IR Night Vision"
id: ir_night_vision
output: ir_led
restore_mode: RESTORE_DEFAULT_OFF
# -----------------------------------------------------------------------------
# Binary Sensors
# -----------------------------------------------------------------------------
binary_sensor:
- platform: template
name: "Motion Detected"
id: motion_detected
device_class: motion
- platform: template
name: "Needs Scooping"
id: needs_scooping_sensor
device_class: problem
icon: "mdi:shovel"
- platform: template
name: "Litter Level Low"
id: litter_level_low_sensor
device_class: problem
icon: "mdi:cup-outline"
- platform: template
name: "Cat Present"
id: cat_present_sensor
device_class: occupancy
icon: "mdi:cat"
# -----------------------------------------------------------------------------
# Sensors
# -----------------------------------------------------------------------------
sensor:
- platform: wifi_signal
name: "WiFi Signal"
update_interval: 60s
- platform: template
name: "Cleanliness Score"
id: cleanliness_score_sensor
unit_of_measurement: "%"
icon: "mdi:star"
accuracy_decimals: 0
- platform: template
name: "Daily Cat Visits"
id: cat_visits_sensor
icon: "mdi:paw"
accuracy_decimals: 0
- platform: template
name: "Last Analysis"
id: last_analysis_time
device_class: timestamp
icon: "mdi:clock-outline"
# -----------------------------------------------------------------------------
# Text Sensors
# -----------------------------------------------------------------------------
text_sensor:
- platform: version
name: "ESPHome Version"
- platform: template
name: "Status Message"
id: status_message
icon: "mdi:message-text"
# -----------------------------------------------------------------------------
# Switches
# -----------------------------------------------------------------------------
switch:
- platform: restart
name: "Restart Camera"
- platform: template
name: "Auto Capture Enabled"
id: auto_capture_enabled
optimistic: true
restore_mode: RESTORE_DEFAULT_ON
# -----------------------------------------------------------------------------
# Buttons
# -----------------------------------------------------------------------------
button:
- platform: template
name: "Capture Now"
icon: "mdi:camera"
on_press:
- script.execute: capture_image_script
# -----------------------------------------------------------------------------
# Scripts
# -----------------------------------------------------------------------------
script:
- id: capture_image_script
mode: single
then:
- lambda: 'ESP_LOGI("capture", "Capturing image...");'
- light.turn_on: status_led
- delay: 200ms
- lambda: |-
id(publish_next_image) = true;
id(litter_cam).request_image(esphome::esp32_camera::SINGLE_SHOT);
- light.turn_off: status_led
- lambda: 'id(motion_cooldown_active) = true;'
- delay: 30s
- lambda: 'id(motion_cooldown_active) = false;'

View File

@@ -0,0 +1,293 @@
substitutions:
name: mbr-ha-remote
friendly_name: "MBR HA Remote"
# Home Assistant entity IDs - UPDATE THESE TO MATCH YOUR SETUP
light_1_entity: switch.pollys_light
light_2_entity: switch.joshuas_light
fan_entity: switch.parents_ceiling_fan
esphome:
name: ${name}
friendly_name: ${friendly_name}
on_boot:
priority: -100
then:
- lambda: |-
id(last_interaction_ms) = millis();
id(backlight_is_on) = true;
- component.update: my_display
esp32:
board: esp32dev
framework:
type: arduino
logger:
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:
# SPI for display and touchscreen (CYD uses two separate SPI buses)
spi:
- id: tft_spi
clk_pin: GPIO14
mosi_pin: GPIO13
miso_pin: GPIO12
- id: touch_spi
clk_pin: GPIO25
mosi_pin: GPIO32
miso_pin: GPIO39
# ILI9341 Display (2.8" 320x240)
display:
- platform: ili9xxx
model: ili9341
spi_id: tft_spi
cs_pin: GPIO15
dc_pin: GPIO2
dimensions:
width: 320
height: 240
# Use palette mode to lower display buffer memory usage.
color_palette: 8BIT
data_rate: 20MHz
update_interval: never
rotation: 0
invert_colors: false
id: my_display
lambda: |-
it.fill(Color(0x1A1A2E));
it.print(160, 20, id(title_font), Color(0xFFFFFF), TextAlign::TOP_CENTER, "Room Remote");
// Button 1: All Toggle (top-left)
if (id(all_state)) {
it.filled_rectangle(20, 50, 130, 80, Color(0x4CAF50));
} else {
it.filled_rectangle(20, 50, 130, 80, Color(0x424242));
}
it.print(85, 90, id(button_font), Color(0xFFFFFF), TextAlign::CENTER, "Toggle All");
// Button 2: Light 1 (top-right)
if (id(light1_state)) {
it.filled_rectangle(170, 50, 130, 80, Color(0x4CAF50));
} else {
it.filled_rectangle(170, 50, 130, 80, Color(0x424242));
}
it.print(235, 90, id(button_font), Color(0xFFFFFF), TextAlign::CENTER, "Polly's Light");
// Button 3: Light 2 (bottom-left)
if (id(light2_state)) {
it.filled_rectangle(20, 150, 130, 80, Color(0x4CAF50));
} else {
it.filled_rectangle(20, 150, 130, 80, Color(0x424242));
}
it.print(85, 190, id(button_font), Color(0xFFFFFF), TextAlign::CENTER, "Joshua's Light");
// Button 4: Fan (bottom-right)
if (id(fan_state)) {
it.filled_rectangle(170, 150, 130, 80, Color(0x4CAF50));
} else {
it.filled_rectangle(170, 150, 130, 80, Color(0x424242));
}
it.print(235, 190, id(button_font), Color(0xFFFFFF), TextAlign::CENTER, "Ceiling Fan");
# XPT2046 Touchscreen
touchscreen:
- platform: xpt2046
spi_id: touch_spi
cs_pin: GPIO33
interrupt_pin: GPIO36
calibration:
x_min: 280
x_max: 3860
y_min: 340
y_max: 3860
on_touch:
- lambda: |-
uint32_t now = millis();
id(last_interaction_ms) = now;
// Wake backlight on first touch only, don't also trigger a button
if (!id(backlight_is_on)) {
auto call = id(backlight).turn_on();
call.perform();
id(backlight_is_on) = true;
id(last_button_press_ms) = now;
return;
}
// Debounce: ignore repeated touch events within 600ms
if ((uint32_t)(now - id(last_button_press_ms)) < 600) return;
id(last_button_press_ms) = now;
ESP_LOGD("touch", "Touch at x=%d, y=%d", touch.x, touch.y);
// Button 1: Toggle All (top-left)
if (touch.x >= 20 && touch.x <= 150 && touch.y >= 50 && touch.y <= 130) {
ESP_LOGD("touch", "Toggle All pressed");
id(light1_state) = !id(light1_state);
id(light2_state) = !id(light2_state);
id(fan_state) = !id(fan_state);
id(all_state) = id(light1_state) && id(light2_state) && id(fan_state);
id(svc_toggle_all).execute();
// Button 2: Polly's Light (top-right)
} else if (touch.x >= 170 && touch.x <= 300 && touch.y >= 50 && touch.y <= 130) {
ESP_LOGD("touch", "Polly's Light pressed");
id(light1_state) = !id(light1_state);
id(all_state) = id(light1_state) && id(light2_state) && id(fan_state);
id(svc_toggle_polly).execute();
// Button 3: Joshua's Light (bottom-left)
} else if (touch.x >= 20 && touch.x <= 150 && touch.y >= 150 && touch.y <= 230) {
ESP_LOGD("touch", "Joshua's Light pressed");
id(light2_state) = !id(light2_state);
id(all_state) = id(light1_state) && id(light2_state) && id(fan_state);
id(svc_toggle_joshua).execute();
// Button 4: Ceiling Fan (bottom-right)
} else if (touch.x >= 170 && touch.x <= 300 && touch.y >= 150 && touch.y <= 230) {
ESP_LOGD("touch", "Ceiling Fan pressed");
id(fan_state) = !id(fan_state);
id(all_state) = id(light1_state) && id(light2_state) && id(fan_state);
id(svc_toggle_fan).execute();
}
id(my_display).update();
# Backlight control
output:
- platform: ledc
pin: GPIO21
id: backlight_output
light:
- platform: monochromatic
output: backlight_output
name: "Display Backlight"
id: backlight
restore_mode: ALWAYS_ON
# Fonts
font:
- file: "gfonts://Nunito"
id: title_font
size: 24
- file: "gfonts://Nunito"
id: button_font
size: 20
# Scripts to call HA services (lambdas can't call homeassistant.service directly)
script:
- id: svc_toggle_all
then:
- homeassistant.service:
service: switch.toggle
data:
entity_id: ${light_1_entity}
- homeassistant.service:
service: switch.toggle
data:
entity_id: ${light_2_entity}
- homeassistant.service:
service: switch.toggle
data:
entity_id: ${fan_entity}
- id: svc_toggle_polly
then:
- homeassistant.service:
service: switch.toggle
data:
entity_id: ${light_1_entity}
- id: svc_toggle_joshua
then:
- homeassistant.service:
service: switch.toggle
data:
entity_id: ${light_2_entity}
- id: svc_toggle_fan
then:
- homeassistant.service:
service: switch.toggle
data:
entity_id: ${fan_entity}
# Global state tracking
globals:
- id: all_state
type: bool
initial_value: "false"
- id: light1_state
type: bool
initial_value: "false"
- id: light2_state
type: bool
initial_value: "false"
- id: fan_state
type: bool
initial_value: "false"
- id: last_interaction_ms
type: uint32_t
initial_value: "0"
- id: last_button_press_ms
type: uint32_t
initial_value: "0"
- id: backlight_is_on
type: bool
initial_value: "true"
# Backlight timeout
interval:
- interval: 100ms
then:
- if:
condition:
lambda: 'return id(backlight_is_on) && ((uint32_t)(millis() - id(last_interaction_ms)) > 30000);'
then:
- light.turn_off: backlight
- lambda: 'id(backlight_is_on) = false;'
# Import states from Home Assistant
text_sensor:
- platform: homeassistant
entity_id: ${light_1_entity}
id: ha_light1_state
on_value:
then:
- lambda: 'id(light1_state) = (x == "on");'
- lambda: 'id(all_state) = id(light1_state) && id(light2_state) && id(fan_state);'
- component.update: my_display
- platform: homeassistant
entity_id: ${light_2_entity}
id: ha_light2_state
on_value:
then:
- lambda: 'id(light2_state) = (x == "on");'
- lambda: 'id(all_state) = id(light1_state) && id(light2_state) && id(fan_state);'
- component.update: my_display
- platform: homeassistant
entity_id: ${fan_entity}
id: ha_fan_state
on_value:
then:
- lambda: 'id(fan_state) = (x == "on");'
- lambda: 'id(all_state) = id(light1_state) && id(light2_state) && id(fan_state);'
- component.update: my_display

3
esphome/spi_helper.h Normal file
View File

@@ -0,0 +1,3 @@
#pragma once
#include "driver/spi_master.h"
#include "driver/gpio.h"

View File

@@ -16,7 +16,6 @@ esphome:
switch.is_off: mute
then:
- micro_wake_word.start:
esp32:
board: esp32-s3-devkitc-1
framework:
@@ -24,7 +23,7 @@ esp32:
# Enable logging
logger:
level: WARN
level: DEBUG
# Enable Home Assistant API
api:
@@ -59,7 +58,7 @@ light:
rgb_order: GRB
chipset: ws2812
pin: GPIO16
num_leds: 4
num_leds: 8
name: "LED bar"
effects:
- pulse:
@@ -72,6 +71,23 @@ light:
move_interval: 100ms
scan_width: 1
number:
- platform: template
id: va_volume_percent
name: "VA Speaker Volume"
min_value: 10
max_value: 100
step: 5
unit_of_measurement: "%"
mode: slider
optimistic: true
restore_value: true
initial_value: 80
set_action:
- speaker.volume_set:
id: va_speaker
volume: !lambda "return x / 100.0f;"
switch:
- platform: template
id: mute
@@ -150,6 +166,7 @@ micro_wake_word:
- model: hey_jarvis
on_wake_word_detected:
- voice_assistant.start:
wake_word: "Hey Jarvis"
- light.turn_on:
id: led_ww
red: 100%
@@ -162,9 +179,10 @@ voice_assistant:
id: va
microphone: va_mic
speaker: va_speaker
use_wake_word: false
noise_suppression_level: 2.0
auto_gain: 31dBFS
volume_multiplier: 4.0
volume_multiplier: 8.0
on_client_connected:
- if:
condition:
@@ -178,11 +196,23 @@ voice_assistant:
then:
- light.turn_off: led_ww
on_error:
- micro_wake_word.start:
- light.turn_on:
id: led_ww
red: 100%
green: 0%
blue: 0%
brightness: 30%
effect: fast pulse
- delay: 1s
- light.turn_off: led_ww
on_end:
then:
- light.turn_off: led_ww
- wait_until:
not:
voice_assistant.is_running:
- micro_wake_word.start:
- if:
condition:
switch.is_off: mute
then:
- micro_wake_word.start:

View File

@@ -3,8 +3,8 @@
# Kids: Jordyn, Declan, Chloe
#
# BIDIRECTIONAL SYNC:
# Screen HA: Each switch calls homeassistant.service on toggle
# HA Screen: ESPHome native API handles this automatically
# Screen to HA: Each switch calls homeassistant.service on toggle
# HA to Screen: ESPHome native API handles this automatically
################################################################################
input_button:
@@ -27,163 +27,127 @@ template:
unique_id: jordyn_chores_done_today
icon: mdi:check-circle
state: >
{% set chores = [states('switch.chore_tracker_jordyn_make_bed'), states('switch.chore_tracker_jordyn_brush_teeth'), states('switch.chore_tracker_jordyn_tidy_room'), states('switch.chore_tracker_jordyn_homework'), states('switch.chore_tracker_jordyn_feed_dog')] %}
{% set chores = [states('switch.chore_tracker_jordyn_put_on_underwear'), states('switch.chore_tracker_jordyn_brush_teeth'), states('switch.chore_tracker_jordyn_fill_water_bowls'), states('switch.chore_tracker_jordyn_restock_cat_food_cans')] %}
{{ chores | select('equalto', 'on') | list | count }}
- name: "Jordyn All Chores Done"
unique_id: jordyn_all_chores_done
icon: mdi:check-all
state: >
{{ states('sensor.jordyn_chores_done_today') | int == 5 }}
{{ states('sensor.jordyn_chores_done_today') | int == 4 }}
- name: "Declan Chores Done Today"
unique_id: declan_chores_done_today
icon: mdi:check-circle
state: >
{% set chores = [states('switch.chore_tracker_declan_make_bed'), states('switch.chore_tracker_declan_brush_teeth'), states('switch.chore_tracker_declan_set_table'), states('switch.chore_tracker_declan_take_out_trash')] %}
{% set chores = [states('switch.chore_tracker_declan_take_morning_pill'), states('switch.chore_tracker_declan_scoop_dog_poop')] %}
{{ chores | select('equalto', 'on') | list | count }}
- name: "Declan All Chores Done"
unique_id: declan_all_chores_done
icon: mdi:check-all
state: >
{{ states('sensor.declan_chores_done_today') | int == 4 }}
{{ states('sensor.declan_chores_done_today') | int == 2 }}
- name: "Chloe Chores Done Today"
unique_id: chloe_chores_done_today
icon: mdi:check-circle
state: >
{% set chores = [states('switch.chore_tracker_chloe_make_bed'), states('switch.chore_tracker_chloe_brush_teeth'), states('switch.chore_tracker_chloe_water_plants'), states('switch.chore_tracker_chloe_homework'), states('switch.chore_tracker_chloe_tidy_room'), states('switch.chore_tracker_chloe_practice_piano')] %}
{% set chores = [states('switch.chore_tracker_chloe_fill_kitty_feeders'), states('switch.chore_tracker_chloe_scoop_kitty_litter'), states('switch.chore_tracker_chloe_replace_kitty_litter_bags')] %}
{{ chores | select('equalto', 'on') | list | count }}
- name: "Chloe All Chores Done"
unique_id: chloe_all_chores_done
icon: mdi:check-all
state: >
{{ states('sensor.chloe_chores_done_today') | int == 6 }}
{{ states('sensor.chloe_chores_done_today') | int == 3 }}
automation:
- id: jordyn_reset_from_ha
alias: "Chore Tracker Reset Jordyn's chores from HA"
alias: "Chore Tracker - Reset Jordyn's chores from HA"
trigger:
- platform: state
entity_id: input_button.jordyn_reset_chores
action:
- service: switch.turn_off
target:
entity_id: switch.chore_tracker_jordyn_make_bed
entity_id: switch.chore_tracker_jordyn_put_on_underwear
- service: switch.turn_off
target:
entity_id: switch.chore_tracker_jordyn_brush_teeth
- service: switch.turn_off
target:
entity_id: switch.chore_tracker_jordyn_tidy_room
entity_id: switch.chore_tracker_jordyn_fill_water_bowls
- service: switch.turn_off
target:
entity_id: switch.chore_tracker_jordyn_homework
- service: switch.turn_off
target:
entity_id: switch.chore_tracker_jordyn_feed_dog
entity_id: switch.chore_tracker_jordyn_restock_cat_food_cans
- id: declan_reset_from_ha
alias: "Chore Tracker Reset Declan's chores from HA"
alias: "Chore Tracker - Reset Declan's chores from HA"
trigger:
- platform: state
entity_id: input_button.declan_reset_chores
action:
- service: switch.turn_off
target:
entity_id: switch.chore_tracker_declan_make_bed
entity_id: switch.chore_tracker_declan_take_morning_pill
- service: switch.turn_off
target:
entity_id: switch.chore_tracker_declan_brush_teeth
- service: switch.turn_off
target:
entity_id: switch.chore_tracker_declan_set_table
- service: switch.turn_off
target:
entity_id: switch.chore_tracker_declan_take_out_trash
entity_id: switch.chore_tracker_declan_scoop_dog_poop
- id: chloe_reset_from_ha
alias: "Chore Tracker Reset Chloe's chores from HA"
alias: "Chore Tracker - Reset Chloe's chores from HA"
trigger:
- platform: state
entity_id: input_button.chloe_reset_chores
action:
- service: switch.turn_off
target:
entity_id: switch.chore_tracker_chloe_make_bed
entity_id: switch.chore_tracker_chloe_fill_kitty_feeders
- service: switch.turn_off
target:
entity_id: switch.chore_tracker_chloe_brush_teeth
entity_id: switch.chore_tracker_chloe_scoop_kitty_litter
- service: switch.turn_off
target:
entity_id: switch.chore_tracker_chloe_water_plants
- service: switch.turn_off
target:
entity_id: switch.chore_tracker_chloe_homework
- service: switch.turn_off
target:
entity_id: switch.chore_tracker_chloe_tidy_room
- service: switch.turn_off
target:
entity_id: switch.chore_tracker_chloe_practice_piano
entity_id: switch.chore_tracker_chloe_replace_kitty_litter_bags
- id: reset_all_from_ha
alias: "Chore Tracker Reset ALL chores from HA"
alias: "Chore Tracker - Reset ALL chores from HA"
trigger:
- platform: state
entity_id: input_button.reset_all_chores
action:
- service: switch.turn_off
target:
entity_id: switch.chore_tracker_jordyn_make_bed
entity_id: switch.chore_tracker_jordyn_put_on_underwear
- service: switch.turn_off
target:
entity_id: switch.chore_tracker_jordyn_brush_teeth
- service: switch.turn_off
target:
entity_id: switch.chore_tracker_jordyn_tidy_room
entity_id: switch.chore_tracker_jordyn_fill_water_bowls
- service: switch.turn_off
target:
entity_id: switch.chore_tracker_jordyn_homework
entity_id: switch.chore_tracker_jordyn_restock_cat_food_cans
- service: switch.turn_off
target:
entity_id: switch.chore_tracker_jordyn_feed_dog
entity_id: switch.chore_tracker_declan_take_morning_pill
- service: switch.turn_off
target:
entity_id: switch.chore_tracker_declan_make_bed
entity_id: switch.chore_tracker_declan_scoop_dog_poop
- service: switch.turn_off
target:
entity_id: switch.chore_tracker_declan_brush_teeth
entity_id: switch.chore_tracker_chloe_fill_kitty_feeders
- service: switch.turn_off
target:
entity_id: switch.chore_tracker_declan_set_table
entity_id: switch.chore_tracker_chloe_scoop_kitty_litter
- service: switch.turn_off
target:
entity_id: switch.chore_tracker_declan_take_out_trash
- service: switch.turn_off
target:
entity_id: switch.chore_tracker_chloe_make_bed
- service: switch.turn_off
target:
entity_id: switch.chore_tracker_chloe_brush_teeth
- service: switch.turn_off
target:
entity_id: switch.chore_tracker_chloe_water_plants
- service: switch.turn_off
target:
entity_id: switch.chore_tracker_chloe_homework
- service: switch.turn_off
target:
entity_id: switch.chore_tracker_chloe_tidy_room
- service: switch.turn_off
target:
entity_id: switch.chore_tracker_chloe_practice_piano
entity_id: switch.chore_tracker_chloe_replace_kitty_litter_bags
- id: jordyn_all_done_notify
alias: "Chore Tracker Jordyn all done!"
alias: "Chore Tracker - Jordyn all done!"
trigger:
- platform: state
entity_id: sensor.jordyn_all_chores_done
@@ -191,11 +155,11 @@ automation:
action:
- service: notify.notify
data:
title: "🎉 Jordyn finished all chores!"
message: "Jordyn completed all 5 chores today!"
title: "Jordyn finished all chores!"
message: "Jordyn completed all 4 chores today!"
- id: declan_all_done_notify
alias: "Chore Tracker Declan all done!"
alias: "Chore Tracker - Declan all done!"
trigger:
- platform: state
entity_id: sensor.declan_all_chores_done
@@ -203,11 +167,11 @@ automation:
action:
- service: notify.notify
data:
title: "🎉 Declan finished all chores!"
message: "Declan completed all 4 chores today!"
title: "Declan finished all chores!"
message: "Declan completed all 2 chores today!"
- id: chloe_all_done_notify
alias: "Chore Tracker Chloe all done!"
alias: "Chore Tracker - Chloe all done!"
trigger:
- platform: state
entity_id: sensor.chloe_all_chores_done
@@ -215,11 +179,11 @@ automation:
action:
- service: notify.notify
data:
title: "🎉 Chloe finished all chores!"
message: "Chloe completed all 6 chores today!"
title: "Chloe finished all chores!"
message: "Chloe completed all 3 chores today!"
- id: chore_reminder_evening
alias: "Chore Tracker Evening reminder"
alias: "Chore Tracker - Evening reminder"
trigger:
- platform: time
at: "18:00:00"
@@ -230,29 +194,29 @@ automation:
then:
- service: notify.notify
data:
title: "📋 Jordyn has unfinished chores"
title: "Jordyn has unfinished chores"
message: >
Jordyn has done
{{ states('sensor.jordyn_chores_done_today') }}/5 chores today.
{{ states('sensor.jordyn_chores_done_today') }}/4 chores today.
- if:
condition: template
value_template: "{{ states('sensor.declan_all_chores_done') != 'True' }}"
then:
- service: notify.notify
data:
title: "📋 Declan has unfinished chores"
title: "Declan has unfinished chores"
message: >
Declan has done
{{ states('sensor.declan_chores_done_today') }}/4 chores today.
{{ states('sensor.declan_chores_done_today') }}/2 chores today.
- if:
condition: template
value_template: "{{ states('sensor.chloe_all_chores_done') != 'True' }}"
then:
- service: notify.notify
data:
title: "📋 Chloe has unfinished chores"
title: "Chloe has unfinished chores"
message: >
Chloe has done
{{ states('sensor.chloe_chores_done_today') }}/6 chores today.
{{ states('sensor.chloe_chores_done_today') }}/3 chores today.