diff --git a/.gitignore b/.gitignore index 56649e0..930ed0f 100644 --- a/.gitignore +++ b/.gitignore @@ -11,6 +11,7 @@ !*.txt* !*.json* !*.ui.yaml +!*.py # Whitelist subdirectory yaml files !*/*.yaml @@ -23,10 +24,14 @@ !*/*.ui.yaml !*/*.h !*/*.py +!*/*/*.py +!*/*/*.yaml # Whitelist specific folders (uncomment as needed) !automations/ +!packages/ +!scenes/ !scripts/ !blueprints/ !esphome/ @@ -37,6 +42,12 @@ !esphome/components/** !esphome/components/max17043/** !custom_components/** +!esphome/chore-tracker/*.py +!esphome/chore-tracker/*.yaml +!esphome/chore-tracker/ +!esphome/chore-tracker/** +!esphome/chore-tracker-esphome.yaml +!chore-tracker-ha.yaml # !packages/ # !themes/ # !node-red/ diff --git a/configuration.yaml b/configuration.yaml index 0096c64..dc0b5c0 100644 --- a/configuration.yaml +++ b/configuration.yaml @@ -1,3 +1,5 @@ +homeassistant: + packages: !include_dir_named packages/ # Loads default set of integrations. Do not remove. default_config: diff --git a/esphome/.gitignore b/esphome/.gitignore deleted file mode 100644 index d8b4157..0000000 --- a/esphome/.gitignore +++ /dev/null @@ -1,5 +0,0 @@ -# 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 diff --git a/esphome/chore-tracker-esphome.yaml b/esphome/chore-tracker-esphome.yaml new file mode 100644 index 0000000..8ceaa22 --- /dev/null +++ b/esphome/chore-tracker-esphome.yaml @@ -0,0 +1,2948 @@ +################################################################################ +# chore-tracker-esphome.yaml (AUTO-GENERATED — edit chores_config.yaml) +# Kids: Emma, Liam, Zoe +# +# HOME SCREEN BEHAVIOUR: +# Red outline = chores incomplete +# Solid green = all chores done ✓ +# Resets to red automatically at midnight +################################################################################ + +esphome: + name: chore-tracker + friendly_name: "Chore Tracker" + +esp32: + board: esp32s3box + framework: + type: esp-idf + +wifi: + ssid: !secret wifi_ssid + password: !secret wifi_password + ap: + ssid: "Chore Tracker Hotspot" + password: "choretracker" + +captive_portal: +logger: + level: INFO + +api: + encryption: + key: !secret api_encryption_key + +ota: + - platform: esphome + password: !secret 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: + - id: i2c_touch + sda: GPIO19 + scl: GPIO20 + frequency: 400kHz + +output: + - platform: ledc + pin: GPIO17 + id: backlight_output + +light: + - platform: monochromatic + output: backlight_output + name: "Display Backlight" + id: backlight + restore_mode: ALWAYS_ON + +# ── Time: drives the daily reset ───────────────────────────────────────────── +time: + - platform: homeassistant + id: ha_time + on_time: + - hours: 0 + minutes: 0 + seconds: 0 + then: + - lambda: |- + // Reset all kids via named scripts (resets switches + refreshes UI) + id(reset_emma_chores).execute(); + id(reset_liam_chores).execute(); + id(reset_zoe_chores).execute(); + - lvgl.page.show: page_home + +fonts: + - file: "gfonts://Nunito:wght@900" + id: font_title + size: 40 + - file: "gfonts://Nunito:wght@800" + id: font_name + size: 30 + - file: "gfonts://Nunito:wght@700" + id: font_med + size: 22 + - file: "gfonts://Nunito:wght@600" + id: font_small + size: 17 + - file: "gfonts://Nunito:wght@600" + id: font_tiny + size: 13 + +# ── Switches — one per chore per kid, synced to HA ─────────────────────────── +switch: + + # ── Emma's chores ────────────────────────── - platform: template + name: "Emma - Make Bed" + id: emma_make_bed + icon: mdi:star + optimistic: true + restore_mode: RESTORE_DEFAULT_OFF + on_turn_on: + then: + - script.execute: update_emma_ui + # Push to HA so it stays in sync when toggled on the screen + - homeassistant.service: + service: switch.turn_on + data: + entity_id: switch.chore_tracker_emma_make_bed + on_turn_off: + then: + - script.execute: update_emma_ui + # Push to HA so it stays in sync when toggled on the screen + - homeassistant.service: + service: switch.turn_off + data: + entity_id: switch.chore_tracker_emma_make_bed + - platform: template + name: "Emma - Brush Teeth" + id: emma_brush_teeth + icon: mdi:star + optimistic: true + restore_mode: RESTORE_DEFAULT_OFF + on_turn_on: + then: + - script.execute: update_emma_ui + # Push to HA so it stays in sync when toggled on the screen + - homeassistant.service: + service: switch.turn_on + data: + entity_id: switch.chore_tracker_emma_brush_teeth + on_turn_off: + then: + - script.execute: update_emma_ui + # Push to HA so it stays in sync when toggled on the screen + - homeassistant.service: + service: switch.turn_off + data: + entity_id: switch.chore_tracker_emma_brush_teeth + - platform: template + name: "Emma - Tidy Room" + id: emma_tidy_room + icon: mdi:star + optimistic: true + restore_mode: RESTORE_DEFAULT_OFF + on_turn_on: + then: + - script.execute: update_emma_ui + # Push to HA so it stays in sync when toggled on the screen + - homeassistant.service: + service: switch.turn_on + data: + entity_id: switch.chore_tracker_emma_tidy_room + on_turn_off: + then: + - script.execute: update_emma_ui + # Push to HA so it stays in sync when toggled on the screen + - homeassistant.service: + service: switch.turn_off + data: + entity_id: switch.chore_tracker_emma_tidy_room + - platform: template + name: "Emma - Homework" + id: emma_homework + icon: mdi:star + optimistic: true + restore_mode: RESTORE_DEFAULT_OFF + on_turn_on: + then: + - script.execute: update_emma_ui + # Push to HA so it stays in sync when toggled on the screen + - homeassistant.service: + service: switch.turn_on + data: + entity_id: switch.chore_tracker_emma_homework + on_turn_off: + then: + - script.execute: update_emma_ui + # Push to HA so it stays in sync when toggled on the screen + - homeassistant.service: + service: switch.turn_off + data: + entity_id: switch.chore_tracker_emma_homework + - platform: template + name: "Emma - Set Table" + id: emma_set_table + icon: mdi:star + optimistic: true + restore_mode: RESTORE_DEFAULT_OFF + on_turn_on: + then: + - script.execute: update_emma_ui + # Push to HA so it stays in sync when toggled on the screen + - homeassistant.service: + service: switch.turn_on + data: + entity_id: switch.chore_tracker_emma_set_table + on_turn_off: + then: + - script.execute: update_emma_ui + # Push to HA so it stays in sync when toggled on the screen + - homeassistant.service: + service: switch.turn_off + data: + entity_id: switch.chore_tracker_emma_set_table + - platform: template + name: "Emma - Take Out Trash" + id: emma_take_out_trash + icon: mdi:star + optimistic: true + restore_mode: RESTORE_DEFAULT_OFF + on_turn_on: + then: + - script.execute: update_emma_ui + # Push to HA so it stays in sync when toggled on the screen + - homeassistant.service: + service: switch.turn_on + data: + entity_id: switch.chore_tracker_emma_take_out_trash + on_turn_off: + then: + - script.execute: update_emma_ui + # Push to HA so it stays in sync when toggled on the screen + - homeassistant.service: + service: switch.turn_off + data: + entity_id: switch.chore_tracker_emma_take_out_trash + - platform: template + name: "Emma - Feed Pet" + id: emma_feed_pet + icon: mdi:star + optimistic: true + restore_mode: RESTORE_DEFAULT_OFF + on_turn_on: + then: + - script.execute: update_emma_ui + # Push to HA so it stays in sync when toggled on the screen + - homeassistant.service: + service: switch.turn_on + data: + entity_id: switch.chore_tracker_emma_feed_pet + on_turn_off: + then: + - script.execute: update_emma_ui + # Push to HA so it stays in sync when toggled on the screen + - homeassistant.service: + service: switch.turn_off + data: + entity_id: switch.chore_tracker_emma_feed_pet + - platform: template + name: "Emma - Water Plants" + id: emma_water_plants + icon: mdi:star + optimistic: true + restore_mode: RESTORE_DEFAULT_OFF + on_turn_on: + then: + - script.execute: update_emma_ui + # Push to HA so it stays in sync when toggled on the screen + - homeassistant.service: + service: switch.turn_on + data: + entity_id: switch.chore_tracker_emma_water_plants + on_turn_off: + then: + - script.execute: update_emma_ui + # Push to HA so it stays in sync when toggled on the screen + - homeassistant.service: + service: switch.turn_off + data: + entity_id: switch.chore_tracker_emma_water_plants + + # ── Liam's chores ────────────────────────── - platform: template + name: "Liam - Make Bed" + id: liam_make_bed + icon: mdi:star + optimistic: true + restore_mode: RESTORE_DEFAULT_OFF + on_turn_on: + then: + - script.execute: update_liam_ui + # Push to HA so it stays in sync when toggled on the screen + - homeassistant.service: + service: switch.turn_on + data: + entity_id: switch.chore_tracker_liam_make_bed + on_turn_off: + then: + - script.execute: update_liam_ui + # Push to HA so it stays in sync when toggled on the screen + - homeassistant.service: + service: switch.turn_off + data: + entity_id: switch.chore_tracker_liam_make_bed + - platform: template + name: "Liam - Brush Teeth" + id: liam_brush_teeth + icon: mdi:star + optimistic: true + restore_mode: RESTORE_DEFAULT_OFF + on_turn_on: + then: + - script.execute: update_liam_ui + # Push to HA so it stays in sync when toggled on the screen + - homeassistant.service: + service: switch.turn_on + data: + entity_id: switch.chore_tracker_liam_brush_teeth + on_turn_off: + then: + - script.execute: update_liam_ui + # Push to HA so it stays in sync when toggled on the screen + - homeassistant.service: + service: switch.turn_off + data: + entity_id: switch.chore_tracker_liam_brush_teeth + - platform: template + name: "Liam - Tidy Room" + id: liam_tidy_room + icon: mdi:star + optimistic: true + restore_mode: RESTORE_DEFAULT_OFF + on_turn_on: + then: + - script.execute: update_liam_ui + # Push to HA so it stays in sync when toggled on the screen + - homeassistant.service: + service: switch.turn_on + data: + entity_id: switch.chore_tracker_liam_tidy_room + on_turn_off: + then: + - script.execute: update_liam_ui + # Push to HA so it stays in sync when toggled on the screen + - homeassistant.service: + service: switch.turn_off + data: + entity_id: switch.chore_tracker_liam_tidy_room + - platform: template + name: "Liam - Homework" + id: liam_homework + icon: mdi:star + optimistic: true + restore_mode: RESTORE_DEFAULT_OFF + on_turn_on: + then: + - script.execute: update_liam_ui + # Push to HA so it stays in sync when toggled on the screen + - homeassistant.service: + service: switch.turn_on + data: + entity_id: switch.chore_tracker_liam_homework + on_turn_off: + then: + - script.execute: update_liam_ui + # Push to HA so it stays in sync when toggled on the screen + - homeassistant.service: + service: switch.turn_off + data: + entity_id: switch.chore_tracker_liam_homework + - platform: template + name: "Liam - Set Table" + id: liam_set_table + icon: mdi:star + optimistic: true + restore_mode: RESTORE_DEFAULT_OFF + on_turn_on: + then: + - script.execute: update_liam_ui + # Push to HA so it stays in sync when toggled on the screen + - homeassistant.service: + service: switch.turn_on + data: + entity_id: switch.chore_tracker_liam_set_table + on_turn_off: + then: + - script.execute: update_liam_ui + # Push to HA so it stays in sync when toggled on the screen + - homeassistant.service: + service: switch.turn_off + data: + entity_id: switch.chore_tracker_liam_set_table + - platform: template + name: "Liam - Take Out Trash" + id: liam_take_out_trash + icon: mdi:star + optimistic: true + restore_mode: RESTORE_DEFAULT_OFF + on_turn_on: + then: + - script.execute: update_liam_ui + # Push to HA so it stays in sync when toggled on the screen + - homeassistant.service: + service: switch.turn_on + data: + entity_id: switch.chore_tracker_liam_take_out_trash + on_turn_off: + then: + - script.execute: update_liam_ui + # Push to HA so it stays in sync when toggled on the screen + - homeassistant.service: + service: switch.turn_off + data: + entity_id: switch.chore_tracker_liam_take_out_trash + - platform: template + name: "Liam - Feed Pet" + id: liam_feed_pet + icon: mdi:star + optimistic: true + restore_mode: RESTORE_DEFAULT_OFF + on_turn_on: + then: + - script.execute: update_liam_ui + # Push to HA so it stays in sync when toggled on the screen + - homeassistant.service: + service: switch.turn_on + data: + entity_id: switch.chore_tracker_liam_feed_pet + on_turn_off: + then: + - script.execute: update_liam_ui + # Push to HA so it stays in sync when toggled on the screen + - homeassistant.service: + service: switch.turn_off + data: + entity_id: switch.chore_tracker_liam_feed_pet + - platform: template + name: "Liam - Water Plants" + id: liam_water_plants + icon: mdi:star + optimistic: true + restore_mode: RESTORE_DEFAULT_OFF + on_turn_on: + then: + - script.execute: update_liam_ui + # Push to HA so it stays in sync when toggled on the screen + - homeassistant.service: + service: switch.turn_on + data: + entity_id: switch.chore_tracker_liam_water_plants + on_turn_off: + then: + - script.execute: update_liam_ui + # Push to HA so it stays in sync when toggled on the screen + - homeassistant.service: + service: switch.turn_off + data: + entity_id: switch.chore_tracker_liam_water_plants + + # ── Zoe's chores ────────────────────────── - platform: template + name: "Zoe - Make Bed" + id: zoe_make_bed + icon: mdi:star + optimistic: true + restore_mode: RESTORE_DEFAULT_OFF + on_turn_on: + then: + - script.execute: update_zoe_ui + # Push to HA so it stays in sync when toggled on the screen + - homeassistant.service: + service: switch.turn_on + data: + entity_id: switch.chore_tracker_zoe_make_bed + on_turn_off: + then: + - script.execute: update_zoe_ui + # Push to HA so it stays in sync when toggled on the screen + - homeassistant.service: + service: switch.turn_off + data: + entity_id: switch.chore_tracker_zoe_make_bed + - platform: template + name: "Zoe - Brush Teeth" + id: zoe_brush_teeth + icon: mdi:star + optimistic: true + restore_mode: RESTORE_DEFAULT_OFF + on_turn_on: + then: + - script.execute: update_zoe_ui + # Push to HA so it stays in sync when toggled on the screen + - homeassistant.service: + service: switch.turn_on + data: + entity_id: switch.chore_tracker_zoe_brush_teeth + on_turn_off: + then: + - script.execute: update_zoe_ui + # Push to HA so it stays in sync when toggled on the screen + - homeassistant.service: + service: switch.turn_off + data: + entity_id: switch.chore_tracker_zoe_brush_teeth + - platform: template + name: "Zoe - Tidy Room" + id: zoe_tidy_room + icon: mdi:star + optimistic: true + restore_mode: RESTORE_DEFAULT_OFF + on_turn_on: + then: + - script.execute: update_zoe_ui + # Push to HA so it stays in sync when toggled on the screen + - homeassistant.service: + service: switch.turn_on + data: + entity_id: switch.chore_tracker_zoe_tidy_room + on_turn_off: + then: + - script.execute: update_zoe_ui + # Push to HA so it stays in sync when toggled on the screen + - homeassistant.service: + service: switch.turn_off + data: + entity_id: switch.chore_tracker_zoe_tidy_room + - platform: template + name: "Zoe - Homework" + id: zoe_homework + icon: mdi:star + optimistic: true + restore_mode: RESTORE_DEFAULT_OFF + on_turn_on: + then: + - script.execute: update_zoe_ui + # Push to HA so it stays in sync when toggled on the screen + - homeassistant.service: + service: switch.turn_on + data: + entity_id: switch.chore_tracker_zoe_homework + on_turn_off: + then: + - script.execute: update_zoe_ui + # Push to HA so it stays in sync when toggled on the screen + - homeassistant.service: + service: switch.turn_off + data: + entity_id: switch.chore_tracker_zoe_homework + - platform: template + name: "Zoe - Set Table" + id: zoe_set_table + icon: mdi:star + optimistic: true + restore_mode: RESTORE_DEFAULT_OFF + on_turn_on: + then: + - script.execute: update_zoe_ui + # Push to HA so it stays in sync when toggled on the screen + - homeassistant.service: + service: switch.turn_on + data: + entity_id: switch.chore_tracker_zoe_set_table + on_turn_off: + then: + - script.execute: update_zoe_ui + # Push to HA so it stays in sync when toggled on the screen + - homeassistant.service: + service: switch.turn_off + data: + entity_id: switch.chore_tracker_zoe_set_table + - platform: template + name: "Zoe - Take Out Trash" + id: zoe_take_out_trash + icon: mdi:star + optimistic: true + restore_mode: RESTORE_DEFAULT_OFF + on_turn_on: + then: + - script.execute: update_zoe_ui + # Push to HA so it stays in sync when toggled on the screen + - homeassistant.service: + service: switch.turn_on + data: + entity_id: switch.chore_tracker_zoe_take_out_trash + on_turn_off: + then: + - script.execute: update_zoe_ui + # Push to HA so it stays in sync when toggled on the screen + - homeassistant.service: + service: switch.turn_off + data: + entity_id: switch.chore_tracker_zoe_take_out_trash + - platform: template + name: "Zoe - Feed Pet" + id: zoe_feed_pet + icon: mdi:star + optimistic: true + restore_mode: RESTORE_DEFAULT_OFF + on_turn_on: + then: + - script.execute: update_zoe_ui + # Push to HA so it stays in sync when toggled on the screen + - homeassistant.service: + service: switch.turn_on + data: + entity_id: switch.chore_tracker_zoe_feed_pet + on_turn_off: + then: + - script.execute: update_zoe_ui + # Push to HA so it stays in sync when toggled on the screen + - homeassistant.service: + service: switch.turn_off + data: + entity_id: switch.chore_tracker_zoe_feed_pet + - platform: template + name: "Zoe - Water Plants" + id: zoe_water_plants + icon: mdi:star + optimistic: true + restore_mode: RESTORE_DEFAULT_OFF + on_turn_on: + then: + - script.execute: update_zoe_ui + # Push to HA so it stays in sync when toggled on the screen + - homeassistant.service: + service: switch.turn_on + data: + entity_id: switch.chore_tracker_zoe_water_plants + on_turn_off: + then: + - script.execute: update_zoe_ui + # Push to HA so it stays in sync when toggled on the screen + - homeassistant.service: + service: switch.turn_off + data: + entity_id: switch.chore_tracker_zoe_water_plants + + +# ── Sensors — reported to HA ───────────────────────────────────────────────── +sensor: + - platform: template + name: "Emma Chores Done" + id: emma_chores_done + accuracy_decimals: 0 + update_interval: 2s + lambda: |- + int done = 0; + if (id(emma_make_bed).state) done++; + if (id(emma_brush_teeth).state) done++; + if (id(emma_tidy_room).state) done++; + if (id(emma_homework).state) done++; + if (id(emma_set_table).state) done++; + if (id(emma_take_out_trash).state) done++; + if (id(emma_feed_pet).state) done++; + if (id(emma_water_plants).state) done++; + return done; + + - platform: template + name: "Emma Stars Today" + id: emma_stars + unit_of_measurement: "stars" + accuracy_decimals: 0 + update_interval: 2s + lambda: |- + int stars = 0; + if (id(emma_make_bed).state) stars += 1; + if (id(emma_brush_teeth).state) stars += 1; + if (id(emma_tidy_room).state) stars += 2; + if (id(emma_homework).state) stars += 3; + if (id(emma_set_table).state) stars += 1; + if (id(emma_take_out_trash).state) stars += 2; + if (id(emma_feed_pet).state) stars += 1; + if (id(emma_water_plants).state) stars += 1; + return stars; + + - platform: template + name: "Liam Chores Done" + id: liam_chores_done + accuracy_decimals: 0 + update_interval: 2s + lambda: |- + int done = 0; + if (id(liam_make_bed).state) done++; + if (id(liam_brush_teeth).state) done++; + if (id(liam_tidy_room).state) done++; + if (id(liam_homework).state) done++; + if (id(liam_set_table).state) done++; + if (id(liam_take_out_trash).state) done++; + if (id(liam_feed_pet).state) done++; + if (id(liam_water_plants).state) done++; + return done; + + - platform: template + name: "Liam Stars Today" + id: liam_stars + unit_of_measurement: "stars" + accuracy_decimals: 0 + update_interval: 2s + lambda: |- + int stars = 0; + if (id(liam_make_bed).state) stars += 1; + if (id(liam_brush_teeth).state) stars += 1; + if (id(liam_tidy_room).state) stars += 2; + if (id(liam_homework).state) stars += 3; + if (id(liam_set_table).state) stars += 1; + if (id(liam_take_out_trash).state) stars += 2; + if (id(liam_feed_pet).state) stars += 1; + if (id(liam_water_plants).state) stars += 1; + return stars; + + - platform: template + name: "Zoe Chores Done" + id: zoe_chores_done + accuracy_decimals: 0 + update_interval: 2s + lambda: |- + int done = 0; + if (id(zoe_make_bed).state) done++; + if (id(zoe_brush_teeth).state) done++; + if (id(zoe_tidy_room).state) done++; + if (id(zoe_homework).state) done++; + if (id(zoe_set_table).state) done++; + if (id(zoe_take_out_trash).state) done++; + if (id(zoe_feed_pet).state) done++; + if (id(zoe_water_plants).state) done++; + return done; + + - platform: template + name: "Zoe Stars Today" + id: zoe_stars + unit_of_measurement: "stars" + accuracy_decimals: 0 + update_interval: 2s + lambda: |- + int stars = 0; + if (id(zoe_make_bed).state) stars += 1; + if (id(zoe_brush_teeth).state) stars += 1; + if (id(zoe_tidy_room).state) stars += 2; + if (id(zoe_homework).state) stars += 3; + if (id(zoe_set_table).state) stars += 1; + if (id(zoe_take_out_trash).state) stars += 2; + if (id(zoe_feed_pet).state) stars += 1; + if (id(zoe_water_plants).state) stars += 1; + return stars; + + + +# ── Scripts — update LVGL visuals when chore state changes ─────────────────── +script: + # Reset all of Emma's chores (used by manual reset button + midnight) + - id: reset_emma_chores + mode: single + then: + - lambda: |- + id(emma_make_bed).turn_off(); + id(emma_brush_teeth).turn_off(); + id(emma_tidy_room).turn_off(); + id(emma_homework).turn_off(); + id(emma_set_table).turn_off(); + id(emma_take_out_trash).turn_off(); + id(emma_feed_pet).turn_off(); + id(emma_water_plants).turn_off(); + - script.execute: update_emma_ui + + + - id: update_emma_ui + mode: single + then: + - lambda: |- + int done = (id(emma_make_bed).state ? 1 : 0) + (id(emma_brush_teeth).state ? 1 : 0) + (id(emma_tidy_room).state ? 1 : 0) + (id(emma_homework).state ? 1 : 0) + (id(emma_set_table).state ? 1 : 0) + (id(emma_take_out_trash).state ? 1 : 0) + (id(emma_feed_pet).state ? 1 : 0) + (id(emma_water_plants).state ? 1 : 0); + int stars = (id(emma_make_bed).state ? 1 : 0) + (id(emma_brush_teeth).state ? 1 : 0) + (id(emma_tidy_room).state ? 2 : 0) + (id(emma_homework).state ? 3 : 0) + (id(emma_set_table).state ? 1 : 0) + (id(emma_take_out_trash).state ? 2 : 0) + (id(emma_feed_pet).state ? 1 : 0) + (id(emma_water_plants).state ? 1 : 0); + int total = 8; + + // ── Sidebar progress & stars ────────────────────────────────────── + lv_bar_set_value(id(progress_bar_emma), done, LV_ANIM_ON); + char buf[32]; + snprintf(buf, sizeof(buf), "%d / 8", done); + lv_label_set_text(id(progress_label_emma), buf); + snprintf(buf, sizeof(buf), "⭐ %d star%s", stars, stars == 1 ? "" : "s"); + lv_label_set_text(id(sidebar_stars_emma), buf); + + // ── All-done celebration text ───────────────────────────────────── + if (done == total) { + lv_label_set_text(id(all_done_label_emma), "🎉 All done!"); + } else { + lv_label_set_text(id(all_done_label_emma), ""); + } + + // ── Home screen button: RED outline = incomplete, GREEN = complete ─ + if (done == total) { + // Solid green — all chores done + lv_obj_set_style_bg_opa(id(home_btn_emma), LV_OPA_COVER, LV_PART_MAIN); + lv_obj_set_style_bg_color(id(home_btn_emma), lv_color_hex(0x6BCB77), LV_PART_MAIN); + lv_obj_set_style_border_width(id(home_btn_emma), 0, LV_PART_MAIN); + lv_label_set_text(id(home_status_emma), "✓ all done!"); + lv_obj_set_style_text_color(id(home_status_emma), lv_color_hex(0xFFFFFF), LV_PART_MAIN); + } else { + // Transparent fill with red border — incomplete + lv_obj_set_style_bg_opa(id(home_btn_emma), LV_OPA_TRANSP, LV_PART_MAIN); + lv_obj_set_style_border_color(id(home_btn_emma), lv_color_hex(0xFF4757), LV_PART_MAIN); + lv_obj_set_style_border_width(id(home_btn_emma), 5, LV_PART_MAIN); + snprintf(buf, sizeof(buf), "%d left", total - done); + lv_label_set_text(id(home_status_emma), buf); + lv_obj_set_style_text_color(id(home_status_emma), lv_color_hex(0xFF4757), LV_PART_MAIN); + } + + // ── Individual chore card colours ───────────────────────────────── + // Make Bed + if (id(emma_make_bed).state) { + lv_obj_set_style_bg_color(id(card_emma_make_bed), lv_color_hex(0xF0FFF4), LV_PART_MAIN); + lv_obj_set_style_bg_opa(id(card_emma_make_bed), LV_OPA_COVER, LV_PART_MAIN); + lv_obj_set_style_border_color(id(card_emma_make_bed), lv_color_hex(0x6BCB77), LV_PART_MAIN); + lv_obj_set_style_border_width(id(card_emma_make_bed), 3, LV_PART_MAIN); + lv_label_set_text(id(check_emma_make_bed), "✅"); + } else { + lv_obj_set_style_bg_color(id(card_emma_make_bed), lv_color_hex(0xFFFFFF), LV_PART_MAIN); + lv_obj_set_style_bg_opa(id(card_emma_make_bed), LV_OPA_COVER, LV_PART_MAIN); + lv_obj_set_style_border_color(id(card_emma_make_bed), lv_color_hex(0xEEEEEE), LV_PART_MAIN); + lv_obj_set_style_border_width(id(card_emma_make_bed), 2, LV_PART_MAIN); + lv_label_set_text(id(check_emma_make_bed), ""); + } + // Brush Teeth + if (id(emma_brush_teeth).state) { + lv_obj_set_style_bg_color(id(card_emma_brush_teeth), lv_color_hex(0xF0FFF4), LV_PART_MAIN); + lv_obj_set_style_bg_opa(id(card_emma_brush_teeth), LV_OPA_COVER, LV_PART_MAIN); + lv_obj_set_style_border_color(id(card_emma_brush_teeth), lv_color_hex(0x6BCB77), LV_PART_MAIN); + lv_obj_set_style_border_width(id(card_emma_brush_teeth), 3, LV_PART_MAIN); + lv_label_set_text(id(check_emma_brush_teeth), "✅"); + } else { + lv_obj_set_style_bg_color(id(card_emma_brush_teeth), lv_color_hex(0xFFFFFF), LV_PART_MAIN); + lv_obj_set_style_bg_opa(id(card_emma_brush_teeth), LV_OPA_COVER, LV_PART_MAIN); + lv_obj_set_style_border_color(id(card_emma_brush_teeth), lv_color_hex(0xEEEEEE), LV_PART_MAIN); + lv_obj_set_style_border_width(id(card_emma_brush_teeth), 2, LV_PART_MAIN); + lv_label_set_text(id(check_emma_brush_teeth), ""); + } + // Tidy Room + if (id(emma_tidy_room).state) { + lv_obj_set_style_bg_color(id(card_emma_tidy_room), lv_color_hex(0xF0FFF4), LV_PART_MAIN); + lv_obj_set_style_bg_opa(id(card_emma_tidy_room), LV_OPA_COVER, LV_PART_MAIN); + lv_obj_set_style_border_color(id(card_emma_tidy_room), lv_color_hex(0x6BCB77), LV_PART_MAIN); + lv_obj_set_style_border_width(id(card_emma_tidy_room), 3, LV_PART_MAIN); + lv_label_set_text(id(check_emma_tidy_room), "✅"); + } else { + lv_obj_set_style_bg_color(id(card_emma_tidy_room), lv_color_hex(0xFFFFFF), LV_PART_MAIN); + lv_obj_set_style_bg_opa(id(card_emma_tidy_room), LV_OPA_COVER, LV_PART_MAIN); + lv_obj_set_style_border_color(id(card_emma_tidy_room), lv_color_hex(0xEEEEEE), LV_PART_MAIN); + lv_obj_set_style_border_width(id(card_emma_tidy_room), 2, LV_PART_MAIN); + lv_label_set_text(id(check_emma_tidy_room), ""); + } + // Homework + if (id(emma_homework).state) { + lv_obj_set_style_bg_color(id(card_emma_homework), lv_color_hex(0xF0FFF4), LV_PART_MAIN); + lv_obj_set_style_bg_opa(id(card_emma_homework), LV_OPA_COVER, LV_PART_MAIN); + lv_obj_set_style_border_color(id(card_emma_homework), lv_color_hex(0x6BCB77), LV_PART_MAIN); + lv_obj_set_style_border_width(id(card_emma_homework), 3, LV_PART_MAIN); + lv_label_set_text(id(check_emma_homework), "✅"); + } else { + lv_obj_set_style_bg_color(id(card_emma_homework), lv_color_hex(0xFFFFFF), LV_PART_MAIN); + lv_obj_set_style_bg_opa(id(card_emma_homework), LV_OPA_COVER, LV_PART_MAIN); + lv_obj_set_style_border_color(id(card_emma_homework), lv_color_hex(0xEEEEEE), LV_PART_MAIN); + lv_obj_set_style_border_width(id(card_emma_homework), 2, LV_PART_MAIN); + lv_label_set_text(id(check_emma_homework), ""); + } + // Set Table + if (id(emma_set_table).state) { + lv_obj_set_style_bg_color(id(card_emma_set_table), lv_color_hex(0xF0FFF4), LV_PART_MAIN); + lv_obj_set_style_bg_opa(id(card_emma_set_table), LV_OPA_COVER, LV_PART_MAIN); + lv_obj_set_style_border_color(id(card_emma_set_table), lv_color_hex(0x6BCB77), LV_PART_MAIN); + lv_obj_set_style_border_width(id(card_emma_set_table), 3, LV_PART_MAIN); + lv_label_set_text(id(check_emma_set_table), "✅"); + } else { + lv_obj_set_style_bg_color(id(card_emma_set_table), lv_color_hex(0xFFFFFF), LV_PART_MAIN); + lv_obj_set_style_bg_opa(id(card_emma_set_table), LV_OPA_COVER, LV_PART_MAIN); + lv_obj_set_style_border_color(id(card_emma_set_table), lv_color_hex(0xEEEEEE), LV_PART_MAIN); + lv_obj_set_style_border_width(id(card_emma_set_table), 2, LV_PART_MAIN); + lv_label_set_text(id(check_emma_set_table), ""); + } + // Take Out Trash + if (id(emma_take_out_trash).state) { + lv_obj_set_style_bg_color(id(card_emma_take_out_trash), lv_color_hex(0xF0FFF4), LV_PART_MAIN); + lv_obj_set_style_bg_opa(id(card_emma_take_out_trash), LV_OPA_COVER, LV_PART_MAIN); + lv_obj_set_style_border_color(id(card_emma_take_out_trash), lv_color_hex(0x6BCB77), LV_PART_MAIN); + lv_obj_set_style_border_width(id(card_emma_take_out_trash), 3, LV_PART_MAIN); + lv_label_set_text(id(check_emma_take_out_trash), "✅"); + } else { + lv_obj_set_style_bg_color(id(card_emma_take_out_trash), lv_color_hex(0xFFFFFF), LV_PART_MAIN); + lv_obj_set_style_bg_opa(id(card_emma_take_out_trash), LV_OPA_COVER, LV_PART_MAIN); + lv_obj_set_style_border_color(id(card_emma_take_out_trash), lv_color_hex(0xEEEEEE), LV_PART_MAIN); + lv_obj_set_style_border_width(id(card_emma_take_out_trash), 2, LV_PART_MAIN); + lv_label_set_text(id(check_emma_take_out_trash), ""); + } + // Feed Pet + if (id(emma_feed_pet).state) { + lv_obj_set_style_bg_color(id(card_emma_feed_pet), lv_color_hex(0xF0FFF4), LV_PART_MAIN); + lv_obj_set_style_bg_opa(id(card_emma_feed_pet), LV_OPA_COVER, LV_PART_MAIN); + lv_obj_set_style_border_color(id(card_emma_feed_pet), lv_color_hex(0x6BCB77), LV_PART_MAIN); + lv_obj_set_style_border_width(id(card_emma_feed_pet), 3, LV_PART_MAIN); + lv_label_set_text(id(check_emma_feed_pet), "✅"); + } else { + lv_obj_set_style_bg_color(id(card_emma_feed_pet), lv_color_hex(0xFFFFFF), LV_PART_MAIN); + lv_obj_set_style_bg_opa(id(card_emma_feed_pet), LV_OPA_COVER, LV_PART_MAIN); + lv_obj_set_style_border_color(id(card_emma_feed_pet), lv_color_hex(0xEEEEEE), LV_PART_MAIN); + lv_obj_set_style_border_width(id(card_emma_feed_pet), 2, LV_PART_MAIN); + lv_label_set_text(id(check_emma_feed_pet), ""); + } + // Water Plants + if (id(emma_water_plants).state) { + lv_obj_set_style_bg_color(id(card_emma_water_plants), lv_color_hex(0xF0FFF4), LV_PART_MAIN); + lv_obj_set_style_bg_opa(id(card_emma_water_plants), LV_OPA_COVER, LV_PART_MAIN); + lv_obj_set_style_border_color(id(card_emma_water_plants), lv_color_hex(0x6BCB77), LV_PART_MAIN); + lv_obj_set_style_border_width(id(card_emma_water_plants), 3, LV_PART_MAIN); + lv_label_set_text(id(check_emma_water_plants), "✅"); + } else { + lv_obj_set_style_bg_color(id(card_emma_water_plants), lv_color_hex(0xFFFFFF), LV_PART_MAIN); + lv_obj_set_style_bg_opa(id(card_emma_water_plants), LV_OPA_COVER, LV_PART_MAIN); + lv_obj_set_style_border_color(id(card_emma_water_plants), lv_color_hex(0xEEEEEE), LV_PART_MAIN); + lv_obj_set_style_border_width(id(card_emma_water_plants), 2, LV_PART_MAIN); + lv_label_set_text(id(check_emma_water_plants), ""); + } + + + # Reset all of Liam's chores (used by manual reset button + midnight) + - id: reset_liam_chores + mode: single + then: + - lambda: |- + id(liam_make_bed).turn_off(); + id(liam_brush_teeth).turn_off(); + id(liam_tidy_room).turn_off(); + id(liam_homework).turn_off(); + id(liam_set_table).turn_off(); + id(liam_take_out_trash).turn_off(); + id(liam_feed_pet).turn_off(); + id(liam_water_plants).turn_off(); + - script.execute: update_liam_ui + + + - id: update_liam_ui + mode: single + then: + - lambda: |- + int done = (id(liam_make_bed).state ? 1 : 0) + (id(liam_brush_teeth).state ? 1 : 0) + (id(liam_tidy_room).state ? 1 : 0) + (id(liam_homework).state ? 1 : 0) + (id(liam_set_table).state ? 1 : 0) + (id(liam_take_out_trash).state ? 1 : 0) + (id(liam_feed_pet).state ? 1 : 0) + (id(liam_water_plants).state ? 1 : 0); + int stars = (id(liam_make_bed).state ? 1 : 0) + (id(liam_brush_teeth).state ? 1 : 0) + (id(liam_tidy_room).state ? 2 : 0) + (id(liam_homework).state ? 3 : 0) + (id(liam_set_table).state ? 1 : 0) + (id(liam_take_out_trash).state ? 2 : 0) + (id(liam_feed_pet).state ? 1 : 0) + (id(liam_water_plants).state ? 1 : 0); + int total = 8; + + // ── Sidebar progress & stars ────────────────────────────────────── + lv_bar_set_value(id(progress_bar_liam), done, LV_ANIM_ON); + char buf[32]; + snprintf(buf, sizeof(buf), "%d / 8", done); + lv_label_set_text(id(progress_label_liam), buf); + snprintf(buf, sizeof(buf), "⭐ %d star%s", stars, stars == 1 ? "" : "s"); + lv_label_set_text(id(sidebar_stars_liam), buf); + + // ── All-done celebration text ───────────────────────────────────── + if (done == total) { + lv_label_set_text(id(all_done_label_liam), "🎉 All done!"); + } else { + lv_label_set_text(id(all_done_label_liam), ""); + } + + // ── Home screen button: RED outline = incomplete, GREEN = complete ─ + if (done == total) { + // Solid green — all chores done + lv_obj_set_style_bg_opa(id(home_btn_liam), LV_OPA_COVER, LV_PART_MAIN); + lv_obj_set_style_bg_color(id(home_btn_liam), lv_color_hex(0x6BCB77), LV_PART_MAIN); + lv_obj_set_style_border_width(id(home_btn_liam), 0, LV_PART_MAIN); + lv_label_set_text(id(home_status_liam), "✓ all done!"); + lv_obj_set_style_text_color(id(home_status_liam), lv_color_hex(0xFFFFFF), LV_PART_MAIN); + } else { + // Transparent fill with red border — incomplete + lv_obj_set_style_bg_opa(id(home_btn_liam), LV_OPA_TRANSP, LV_PART_MAIN); + lv_obj_set_style_border_color(id(home_btn_liam), lv_color_hex(0xFF4757), LV_PART_MAIN); + lv_obj_set_style_border_width(id(home_btn_liam), 5, LV_PART_MAIN); + snprintf(buf, sizeof(buf), "%d left", total - done); + lv_label_set_text(id(home_status_liam), buf); + lv_obj_set_style_text_color(id(home_status_liam), lv_color_hex(0xFF4757), LV_PART_MAIN); + } + + // ── Individual chore card colours ───────────────────────────────── + // Make Bed + if (id(liam_make_bed).state) { + lv_obj_set_style_bg_color(id(card_liam_make_bed), lv_color_hex(0xF0FFF4), LV_PART_MAIN); + lv_obj_set_style_bg_opa(id(card_liam_make_bed), LV_OPA_COVER, LV_PART_MAIN); + lv_obj_set_style_border_color(id(card_liam_make_bed), lv_color_hex(0x6BCB77), LV_PART_MAIN); + lv_obj_set_style_border_width(id(card_liam_make_bed), 3, LV_PART_MAIN); + lv_label_set_text(id(check_liam_make_bed), "✅"); + } else { + lv_obj_set_style_bg_color(id(card_liam_make_bed), lv_color_hex(0xFFFFFF), LV_PART_MAIN); + lv_obj_set_style_bg_opa(id(card_liam_make_bed), LV_OPA_COVER, LV_PART_MAIN); + lv_obj_set_style_border_color(id(card_liam_make_bed), lv_color_hex(0xEEEEEE), LV_PART_MAIN); + lv_obj_set_style_border_width(id(card_liam_make_bed), 2, LV_PART_MAIN); + lv_label_set_text(id(check_liam_make_bed), ""); + } + // Brush Teeth + if (id(liam_brush_teeth).state) { + lv_obj_set_style_bg_color(id(card_liam_brush_teeth), lv_color_hex(0xF0FFF4), LV_PART_MAIN); + lv_obj_set_style_bg_opa(id(card_liam_brush_teeth), LV_OPA_COVER, LV_PART_MAIN); + lv_obj_set_style_border_color(id(card_liam_brush_teeth), lv_color_hex(0x6BCB77), LV_PART_MAIN); + lv_obj_set_style_border_width(id(card_liam_brush_teeth), 3, LV_PART_MAIN); + lv_label_set_text(id(check_liam_brush_teeth), "✅"); + } else { + lv_obj_set_style_bg_color(id(card_liam_brush_teeth), lv_color_hex(0xFFFFFF), LV_PART_MAIN); + lv_obj_set_style_bg_opa(id(card_liam_brush_teeth), LV_OPA_COVER, LV_PART_MAIN); + lv_obj_set_style_border_color(id(card_liam_brush_teeth), lv_color_hex(0xEEEEEE), LV_PART_MAIN); + lv_obj_set_style_border_width(id(card_liam_brush_teeth), 2, LV_PART_MAIN); + lv_label_set_text(id(check_liam_brush_teeth), ""); + } + // Tidy Room + if (id(liam_tidy_room).state) { + lv_obj_set_style_bg_color(id(card_liam_tidy_room), lv_color_hex(0xF0FFF4), LV_PART_MAIN); + lv_obj_set_style_bg_opa(id(card_liam_tidy_room), LV_OPA_COVER, LV_PART_MAIN); + lv_obj_set_style_border_color(id(card_liam_tidy_room), lv_color_hex(0x6BCB77), LV_PART_MAIN); + lv_obj_set_style_border_width(id(card_liam_tidy_room), 3, LV_PART_MAIN); + lv_label_set_text(id(check_liam_tidy_room), "✅"); + } else { + lv_obj_set_style_bg_color(id(card_liam_tidy_room), lv_color_hex(0xFFFFFF), LV_PART_MAIN); + lv_obj_set_style_bg_opa(id(card_liam_tidy_room), LV_OPA_COVER, LV_PART_MAIN); + lv_obj_set_style_border_color(id(card_liam_tidy_room), lv_color_hex(0xEEEEEE), LV_PART_MAIN); + lv_obj_set_style_border_width(id(card_liam_tidy_room), 2, LV_PART_MAIN); + lv_label_set_text(id(check_liam_tidy_room), ""); + } + // Homework + if (id(liam_homework).state) { + lv_obj_set_style_bg_color(id(card_liam_homework), lv_color_hex(0xF0FFF4), LV_PART_MAIN); + lv_obj_set_style_bg_opa(id(card_liam_homework), LV_OPA_COVER, LV_PART_MAIN); + lv_obj_set_style_border_color(id(card_liam_homework), lv_color_hex(0x6BCB77), LV_PART_MAIN); + lv_obj_set_style_border_width(id(card_liam_homework), 3, LV_PART_MAIN); + lv_label_set_text(id(check_liam_homework), "✅"); + } else { + lv_obj_set_style_bg_color(id(card_liam_homework), lv_color_hex(0xFFFFFF), LV_PART_MAIN); + lv_obj_set_style_bg_opa(id(card_liam_homework), LV_OPA_COVER, LV_PART_MAIN); + lv_obj_set_style_border_color(id(card_liam_homework), lv_color_hex(0xEEEEEE), LV_PART_MAIN); + lv_obj_set_style_border_width(id(card_liam_homework), 2, LV_PART_MAIN); + lv_label_set_text(id(check_liam_homework), ""); + } + // Set Table + if (id(liam_set_table).state) { + lv_obj_set_style_bg_color(id(card_liam_set_table), lv_color_hex(0xF0FFF4), LV_PART_MAIN); + lv_obj_set_style_bg_opa(id(card_liam_set_table), LV_OPA_COVER, LV_PART_MAIN); + lv_obj_set_style_border_color(id(card_liam_set_table), lv_color_hex(0x6BCB77), LV_PART_MAIN); + lv_obj_set_style_border_width(id(card_liam_set_table), 3, LV_PART_MAIN); + lv_label_set_text(id(check_liam_set_table), "✅"); + } else { + lv_obj_set_style_bg_color(id(card_liam_set_table), lv_color_hex(0xFFFFFF), LV_PART_MAIN); + lv_obj_set_style_bg_opa(id(card_liam_set_table), LV_OPA_COVER, LV_PART_MAIN); + lv_obj_set_style_border_color(id(card_liam_set_table), lv_color_hex(0xEEEEEE), LV_PART_MAIN); + lv_obj_set_style_border_width(id(card_liam_set_table), 2, LV_PART_MAIN); + lv_label_set_text(id(check_liam_set_table), ""); + } + // Take Out Trash + if (id(liam_take_out_trash).state) { + lv_obj_set_style_bg_color(id(card_liam_take_out_trash), lv_color_hex(0xF0FFF4), LV_PART_MAIN); + lv_obj_set_style_bg_opa(id(card_liam_take_out_trash), LV_OPA_COVER, LV_PART_MAIN); + lv_obj_set_style_border_color(id(card_liam_take_out_trash), lv_color_hex(0x6BCB77), LV_PART_MAIN); + lv_obj_set_style_border_width(id(card_liam_take_out_trash), 3, LV_PART_MAIN); + lv_label_set_text(id(check_liam_take_out_trash), "✅"); + } else { + lv_obj_set_style_bg_color(id(card_liam_take_out_trash), lv_color_hex(0xFFFFFF), LV_PART_MAIN); + lv_obj_set_style_bg_opa(id(card_liam_take_out_trash), LV_OPA_COVER, LV_PART_MAIN); + lv_obj_set_style_border_color(id(card_liam_take_out_trash), lv_color_hex(0xEEEEEE), LV_PART_MAIN); + lv_obj_set_style_border_width(id(card_liam_take_out_trash), 2, LV_PART_MAIN); + lv_label_set_text(id(check_liam_take_out_trash), ""); + } + // Feed Pet + if (id(liam_feed_pet).state) { + lv_obj_set_style_bg_color(id(card_liam_feed_pet), lv_color_hex(0xF0FFF4), LV_PART_MAIN); + lv_obj_set_style_bg_opa(id(card_liam_feed_pet), LV_OPA_COVER, LV_PART_MAIN); + lv_obj_set_style_border_color(id(card_liam_feed_pet), lv_color_hex(0x6BCB77), LV_PART_MAIN); + lv_obj_set_style_border_width(id(card_liam_feed_pet), 3, LV_PART_MAIN); + lv_label_set_text(id(check_liam_feed_pet), "✅"); + } else { + lv_obj_set_style_bg_color(id(card_liam_feed_pet), lv_color_hex(0xFFFFFF), LV_PART_MAIN); + lv_obj_set_style_bg_opa(id(card_liam_feed_pet), LV_OPA_COVER, LV_PART_MAIN); + lv_obj_set_style_border_color(id(card_liam_feed_pet), lv_color_hex(0xEEEEEE), LV_PART_MAIN); + lv_obj_set_style_border_width(id(card_liam_feed_pet), 2, LV_PART_MAIN); + lv_label_set_text(id(check_liam_feed_pet), ""); + } + // Water Plants + if (id(liam_water_plants).state) { + lv_obj_set_style_bg_color(id(card_liam_water_plants), lv_color_hex(0xF0FFF4), LV_PART_MAIN); + lv_obj_set_style_bg_opa(id(card_liam_water_plants), LV_OPA_COVER, LV_PART_MAIN); + lv_obj_set_style_border_color(id(card_liam_water_plants), lv_color_hex(0x6BCB77), LV_PART_MAIN); + lv_obj_set_style_border_width(id(card_liam_water_plants), 3, LV_PART_MAIN); + lv_label_set_text(id(check_liam_water_plants), "✅"); + } else { + lv_obj_set_style_bg_color(id(card_liam_water_plants), lv_color_hex(0xFFFFFF), LV_PART_MAIN); + lv_obj_set_style_bg_opa(id(card_liam_water_plants), LV_OPA_COVER, LV_PART_MAIN); + lv_obj_set_style_border_color(id(card_liam_water_plants), lv_color_hex(0xEEEEEE), LV_PART_MAIN); + lv_obj_set_style_border_width(id(card_liam_water_plants), 2, LV_PART_MAIN); + lv_label_set_text(id(check_liam_water_plants), ""); + } + + + # Reset all of Zoe's chores (used by manual reset button + midnight) + - id: reset_zoe_chores + mode: single + then: + - lambda: |- + id(zoe_make_bed).turn_off(); + id(zoe_brush_teeth).turn_off(); + id(zoe_tidy_room).turn_off(); + id(zoe_homework).turn_off(); + id(zoe_set_table).turn_off(); + id(zoe_take_out_trash).turn_off(); + id(zoe_feed_pet).turn_off(); + id(zoe_water_plants).turn_off(); + - script.execute: update_zoe_ui + + + - id: update_zoe_ui + mode: single + then: + - lambda: |- + int done = (id(zoe_make_bed).state ? 1 : 0) + (id(zoe_brush_teeth).state ? 1 : 0) + (id(zoe_tidy_room).state ? 1 : 0) + (id(zoe_homework).state ? 1 : 0) + (id(zoe_set_table).state ? 1 : 0) + (id(zoe_take_out_trash).state ? 1 : 0) + (id(zoe_feed_pet).state ? 1 : 0) + (id(zoe_water_plants).state ? 1 : 0); + int stars = (id(zoe_make_bed).state ? 1 : 0) + (id(zoe_brush_teeth).state ? 1 : 0) + (id(zoe_tidy_room).state ? 2 : 0) + (id(zoe_homework).state ? 3 : 0) + (id(zoe_set_table).state ? 1 : 0) + (id(zoe_take_out_trash).state ? 2 : 0) + (id(zoe_feed_pet).state ? 1 : 0) + (id(zoe_water_plants).state ? 1 : 0); + int total = 8; + + // ── Sidebar progress & stars ────────────────────────────────────── + lv_bar_set_value(id(progress_bar_zoe), done, LV_ANIM_ON); + char buf[32]; + snprintf(buf, sizeof(buf), "%d / 8", done); + lv_label_set_text(id(progress_label_zoe), buf); + snprintf(buf, sizeof(buf), "⭐ %d star%s", stars, stars == 1 ? "" : "s"); + lv_label_set_text(id(sidebar_stars_zoe), buf); + + // ── All-done celebration text ───────────────────────────────────── + if (done == total) { + lv_label_set_text(id(all_done_label_zoe), "🎉 All done!"); + } else { + lv_label_set_text(id(all_done_label_zoe), ""); + } + + // ── Home screen button: RED outline = incomplete, GREEN = complete ─ + if (done == total) { + // Solid green — all chores done + lv_obj_set_style_bg_opa(id(home_btn_zoe), LV_OPA_COVER, LV_PART_MAIN); + lv_obj_set_style_bg_color(id(home_btn_zoe), lv_color_hex(0x6BCB77), LV_PART_MAIN); + lv_obj_set_style_border_width(id(home_btn_zoe), 0, LV_PART_MAIN); + lv_label_set_text(id(home_status_zoe), "✓ all done!"); + lv_obj_set_style_text_color(id(home_status_zoe), lv_color_hex(0xFFFFFF), LV_PART_MAIN); + } else { + // Transparent fill with red border — incomplete + lv_obj_set_style_bg_opa(id(home_btn_zoe), LV_OPA_TRANSP, LV_PART_MAIN); + lv_obj_set_style_border_color(id(home_btn_zoe), lv_color_hex(0xFF4757), LV_PART_MAIN); + lv_obj_set_style_border_width(id(home_btn_zoe), 5, LV_PART_MAIN); + snprintf(buf, sizeof(buf), "%d left", total - done); + lv_label_set_text(id(home_status_zoe), buf); + lv_obj_set_style_text_color(id(home_status_zoe), lv_color_hex(0xFF4757), LV_PART_MAIN); + } + + // ── Individual chore card colours ───────────────────────────────── + // Make Bed + if (id(zoe_make_bed).state) { + lv_obj_set_style_bg_color(id(card_zoe_make_bed), lv_color_hex(0xF0FFF4), LV_PART_MAIN); + lv_obj_set_style_bg_opa(id(card_zoe_make_bed), LV_OPA_COVER, LV_PART_MAIN); + lv_obj_set_style_border_color(id(card_zoe_make_bed), lv_color_hex(0x6BCB77), LV_PART_MAIN); + lv_obj_set_style_border_width(id(card_zoe_make_bed), 3, LV_PART_MAIN); + lv_label_set_text(id(check_zoe_make_bed), "✅"); + } else { + lv_obj_set_style_bg_color(id(card_zoe_make_bed), lv_color_hex(0xFFFFFF), LV_PART_MAIN); + lv_obj_set_style_bg_opa(id(card_zoe_make_bed), LV_OPA_COVER, LV_PART_MAIN); + lv_obj_set_style_border_color(id(card_zoe_make_bed), lv_color_hex(0xEEEEEE), LV_PART_MAIN); + lv_obj_set_style_border_width(id(card_zoe_make_bed), 2, LV_PART_MAIN); + lv_label_set_text(id(check_zoe_make_bed), ""); + } + // Brush Teeth + if (id(zoe_brush_teeth).state) { + lv_obj_set_style_bg_color(id(card_zoe_brush_teeth), lv_color_hex(0xF0FFF4), LV_PART_MAIN); + lv_obj_set_style_bg_opa(id(card_zoe_brush_teeth), LV_OPA_COVER, LV_PART_MAIN); + lv_obj_set_style_border_color(id(card_zoe_brush_teeth), lv_color_hex(0x6BCB77), LV_PART_MAIN); + lv_obj_set_style_border_width(id(card_zoe_brush_teeth), 3, LV_PART_MAIN); + lv_label_set_text(id(check_zoe_brush_teeth), "✅"); + } else { + lv_obj_set_style_bg_color(id(card_zoe_brush_teeth), lv_color_hex(0xFFFFFF), LV_PART_MAIN); + lv_obj_set_style_bg_opa(id(card_zoe_brush_teeth), LV_OPA_COVER, LV_PART_MAIN); + lv_obj_set_style_border_color(id(card_zoe_brush_teeth), lv_color_hex(0xEEEEEE), LV_PART_MAIN); + lv_obj_set_style_border_width(id(card_zoe_brush_teeth), 2, LV_PART_MAIN); + lv_label_set_text(id(check_zoe_brush_teeth), ""); + } + // Tidy Room + if (id(zoe_tidy_room).state) { + lv_obj_set_style_bg_color(id(card_zoe_tidy_room), lv_color_hex(0xF0FFF4), LV_PART_MAIN); + lv_obj_set_style_bg_opa(id(card_zoe_tidy_room), LV_OPA_COVER, LV_PART_MAIN); + lv_obj_set_style_border_color(id(card_zoe_tidy_room), lv_color_hex(0x6BCB77), LV_PART_MAIN); + lv_obj_set_style_border_width(id(card_zoe_tidy_room), 3, LV_PART_MAIN); + lv_label_set_text(id(check_zoe_tidy_room), "✅"); + } else { + lv_obj_set_style_bg_color(id(card_zoe_tidy_room), lv_color_hex(0xFFFFFF), LV_PART_MAIN); + lv_obj_set_style_bg_opa(id(card_zoe_tidy_room), LV_OPA_COVER, LV_PART_MAIN); + lv_obj_set_style_border_color(id(card_zoe_tidy_room), lv_color_hex(0xEEEEEE), LV_PART_MAIN); + lv_obj_set_style_border_width(id(card_zoe_tidy_room), 2, LV_PART_MAIN); + lv_label_set_text(id(check_zoe_tidy_room), ""); + } + // Homework + if (id(zoe_homework).state) { + lv_obj_set_style_bg_color(id(card_zoe_homework), lv_color_hex(0xF0FFF4), LV_PART_MAIN); + lv_obj_set_style_bg_opa(id(card_zoe_homework), LV_OPA_COVER, LV_PART_MAIN); + lv_obj_set_style_border_color(id(card_zoe_homework), lv_color_hex(0x6BCB77), LV_PART_MAIN); + lv_obj_set_style_border_width(id(card_zoe_homework), 3, LV_PART_MAIN); + lv_label_set_text(id(check_zoe_homework), "✅"); + } else { + lv_obj_set_style_bg_color(id(card_zoe_homework), lv_color_hex(0xFFFFFF), LV_PART_MAIN); + lv_obj_set_style_bg_opa(id(card_zoe_homework), LV_OPA_COVER, LV_PART_MAIN); + lv_obj_set_style_border_color(id(card_zoe_homework), lv_color_hex(0xEEEEEE), LV_PART_MAIN); + lv_obj_set_style_border_width(id(card_zoe_homework), 2, LV_PART_MAIN); + lv_label_set_text(id(check_zoe_homework), ""); + } + // Set Table + if (id(zoe_set_table).state) { + lv_obj_set_style_bg_color(id(card_zoe_set_table), lv_color_hex(0xF0FFF4), LV_PART_MAIN); + lv_obj_set_style_bg_opa(id(card_zoe_set_table), LV_OPA_COVER, LV_PART_MAIN); + lv_obj_set_style_border_color(id(card_zoe_set_table), lv_color_hex(0x6BCB77), LV_PART_MAIN); + lv_obj_set_style_border_width(id(card_zoe_set_table), 3, LV_PART_MAIN); + lv_label_set_text(id(check_zoe_set_table), "✅"); + } else { + lv_obj_set_style_bg_color(id(card_zoe_set_table), lv_color_hex(0xFFFFFF), LV_PART_MAIN); + lv_obj_set_style_bg_opa(id(card_zoe_set_table), LV_OPA_COVER, LV_PART_MAIN); + lv_obj_set_style_border_color(id(card_zoe_set_table), lv_color_hex(0xEEEEEE), LV_PART_MAIN); + lv_obj_set_style_border_width(id(card_zoe_set_table), 2, LV_PART_MAIN); + lv_label_set_text(id(check_zoe_set_table), ""); + } + // Take Out Trash + if (id(zoe_take_out_trash).state) { + lv_obj_set_style_bg_color(id(card_zoe_take_out_trash), lv_color_hex(0xF0FFF4), LV_PART_MAIN); + lv_obj_set_style_bg_opa(id(card_zoe_take_out_trash), LV_OPA_COVER, LV_PART_MAIN); + lv_obj_set_style_border_color(id(card_zoe_take_out_trash), lv_color_hex(0x6BCB77), LV_PART_MAIN); + lv_obj_set_style_border_width(id(card_zoe_take_out_trash), 3, LV_PART_MAIN); + lv_label_set_text(id(check_zoe_take_out_trash), "✅"); + } else { + lv_obj_set_style_bg_color(id(card_zoe_take_out_trash), lv_color_hex(0xFFFFFF), LV_PART_MAIN); + lv_obj_set_style_bg_opa(id(card_zoe_take_out_trash), LV_OPA_COVER, LV_PART_MAIN); + lv_obj_set_style_border_color(id(card_zoe_take_out_trash), lv_color_hex(0xEEEEEE), LV_PART_MAIN); + lv_obj_set_style_border_width(id(card_zoe_take_out_trash), 2, LV_PART_MAIN); + lv_label_set_text(id(check_zoe_take_out_trash), ""); + } + // Feed Pet + if (id(zoe_feed_pet).state) { + lv_obj_set_style_bg_color(id(card_zoe_feed_pet), lv_color_hex(0xF0FFF4), LV_PART_MAIN); + lv_obj_set_style_bg_opa(id(card_zoe_feed_pet), LV_OPA_COVER, LV_PART_MAIN); + lv_obj_set_style_border_color(id(card_zoe_feed_pet), lv_color_hex(0x6BCB77), LV_PART_MAIN); + lv_obj_set_style_border_width(id(card_zoe_feed_pet), 3, LV_PART_MAIN); + lv_label_set_text(id(check_zoe_feed_pet), "✅"); + } else { + lv_obj_set_style_bg_color(id(card_zoe_feed_pet), lv_color_hex(0xFFFFFF), LV_PART_MAIN); + lv_obj_set_style_bg_opa(id(card_zoe_feed_pet), LV_OPA_COVER, LV_PART_MAIN); + lv_obj_set_style_border_color(id(card_zoe_feed_pet), lv_color_hex(0xEEEEEE), LV_PART_MAIN); + lv_obj_set_style_border_width(id(card_zoe_feed_pet), 2, LV_PART_MAIN); + lv_label_set_text(id(check_zoe_feed_pet), ""); + } + // Water Plants + if (id(zoe_water_plants).state) { + lv_obj_set_style_bg_color(id(card_zoe_water_plants), lv_color_hex(0xF0FFF4), LV_PART_MAIN); + lv_obj_set_style_bg_opa(id(card_zoe_water_plants), LV_OPA_COVER, LV_PART_MAIN); + lv_obj_set_style_border_color(id(card_zoe_water_plants), lv_color_hex(0x6BCB77), LV_PART_MAIN); + lv_obj_set_style_border_width(id(card_zoe_water_plants), 3, LV_PART_MAIN); + lv_label_set_text(id(check_zoe_water_plants), "✅"); + } else { + lv_obj_set_style_bg_color(id(card_zoe_water_plants), lv_color_hex(0xFFFFFF), LV_PART_MAIN); + lv_obj_set_style_bg_opa(id(card_zoe_water_plants), LV_OPA_COVER, LV_PART_MAIN); + lv_obj_set_style_border_color(id(card_zoe_water_plants), lv_color_hex(0xEEEEEE), LV_PART_MAIN); + lv_obj_set_style_border_width(id(card_zoe_water_plants), 2, LV_PART_MAIN); + lv_label_set_text(id(check_zoe_water_plants), ""); + } + + + +# ── LVGL UI ─────────────────────────────────────────────────────────────────── +lvgl: + displays: + - main_display + touchscreens: + - touch + theme: + btn: + radius: 20 + border_width: 0 + pages: + - id: page_home + bg_color: 0xFFF9F0 + widgets: + - label: + x: 0 + y: 38 + width: 800 + align: TOP_MID + text: "⭐ Chore Tracker" + text_font: font_title + text_color: 0x2D3436 + - label: + x: 0 + y: 96 + width: 800 + align: TOP_MID + text: "Tap a name to check off chores" + text_font: font_small + text_color: 0xB2BEC3 + # Reset All button — bottom right, subtle red + - btn: + x: 660 + y: 428 + width: 130 + height: 40 + bg_color: 0xFF4757 + radius: 12 + on_click: + then: + - script.execute: reset_emma_chores + - script.execute: reset_liam_chores + - script.execute: reset_zoe_chores + widgets: + - label: + align: CENTER + text: "↺ Reset All" + text_color: 0xFFFFFF + text_font: font_tiny + # Emma — starts red (incomplete), turns green when all done + - btn: + id: home_btn_emma + x: 80 + y: 140 + width: 200 + height: 200 + bg_color: 0xFFFFFF + bg_opa: TRANSP + border_color: 0xFF4757 + border_width: 5 + radius: 24 + on_click: + then: + - lvgl.page.show: page_emma + widgets: + - label: + align: CENTER + y: -38 + text: "🐶" + text_font: font_title + - label: + align: CENTER + y: 28 + text: "Emma" + text_font: font_name + text_color: 0x2D3436 + - label: + id: home_status_emma + align: CENTER + y: 68 + text: "not done" + text_font: font_tiny + text_color: 0xFF4757 + + # Liam — starts red (incomplete), turns green when all done + - btn: + id: home_btn_liam + x: 300 + y: 140 + width: 200 + height: 200 + bg_color: 0xFFFFFF + bg_opa: TRANSP + border_color: 0xFF4757 + border_width: 5 + radius: 24 + on_click: + then: + - lvgl.page.show: page_liam + widgets: + - label: + align: CENTER + y: -38 + text: "🐱" + text_font: font_title + - label: + align: CENTER + y: 28 + text: "Liam" + text_font: font_name + text_color: 0x2D3436 + - label: + id: home_status_liam + align: CENTER + y: 68 + text: "not done" + text_font: font_tiny + text_color: 0xFF4757 + + # Zoe — starts red (incomplete), turns green when all done + - btn: + id: home_btn_zoe + x: 520 + y: 140 + width: 200 + height: 200 + bg_color: 0xFFFFFF + bg_opa: TRANSP + border_color: 0xFF4757 + border_width: 5 + radius: 24 + on_click: + then: + - lvgl.page.show: page_zoe + widgets: + - label: + align: CENTER + y: -38 + text: "🐻" + text_font: font_title + - label: + align: CENTER + y: 28 + text: "Zoe" + text_font: font_name + text_color: 0x2D3436 + - label: + id: home_status_zoe + align: CENTER + y: 68 + text: "not done" + text_font: font_tiny + text_color: 0xFF4757 + + + - id: page_emma + bg_color: 0xFFF9F0 + widgets: + + # ── Sidebar ────────────────────────────────────────────────────────── + - obj: + x: 0 + y: 0 + width: 180 + height: 480 + bg_color: 0x4D96FF + border_width: 0 + radius: 0 + pad_all: 0 + widgets: + - label: + x: 0 + y: 20 + width: 180 + align: TOP_MID + text: "🐶" + text_font: font_title + - label: + x: 0 + y: 74 + width: 180 + align: TOP_MID + text: "Emma" + text_font: font_name + text_color: 0xFFFFFF + - label: + id: sidebar_stars_emma + x: 0 + y: 120 + width: 180 + align: TOP_MID + text: "⭐ 0 stars" + text_font: font_med + text_color: 0xFFD93D + + # Progress bar + - bar: + id: progress_bar_emma + x: 16 + y: 164 + width: 148 + height: 16 + bg_color: 0x2A6FCC + indicator: + bg_color: 0xFFFFFF + value: 0 + min_value: 0 + max_value: 8 + - label: + id: progress_label_emma + x: 0 + y: 188 + width: 180 + align: TOP_MID + text: "0 / 8" + text_font: font_tiny + text_color: 0xDDEEFF + + # All-done celebration label (hidden until complete) + - label: + id: all_done_label_emma + x: 0 + y: 214 + width: 180 + align: TOP_MID + text: "" + text_font: font_small + text_color: 0xFFFFFF + + # Manual reset button (parent use — above back button) + - btn: + x: 16 + y: 362 + width: 148 + height: 44 + bg_color: 0xFF4757 + radius: 14 + on_click: + then: + - script.execute: reset_emma_chores + widgets: + - label: + align: CENTER + text: "↺ Reset" + text_color: 0xFFFFFF + text_font: font_small + + # Back button + - btn: + x: 16 + y: 418 + width: 148 + height: 46 + bg_color: 0x2A6FCC + radius: 14 + on_click: + then: + - lvgl.page.show: page_home + widgets: + - label: + align: CENTER + text: "◀ Home" + text_color: 0xFFFFFF + text_font: font_small + + # ── Chore cards ─────────────────────────────────────────────────────── + # Make Bed + - btn: + id: card_emma_make_bed + x: 190 + y: 10 + width: 193 + height: 138 + bg_color: 0xFFFFFF + border_color: 0xEEEEEE + border_width: 2 + radius: 20 + shadow_color: 0xCCCCCC + shadow_width: 4 + shadow_ofs_y: 3 + on_click: + then: + - switch.toggle: emma_make_bed + widgets: + - label: + id: check_emma_make_bed + align: TOP_RIGHT + x: -10 + y: 8 + text: "" + text_font: font_med + - label: + align: CENTER + y: -34 + text: "🛏️" + text_font: font_med + - label: + align: CENTER + y: 23 + text: "Make Bed" + text_font: font_small + text_color: 0x2D3436 + - label: + align: CENTER + y: 45 + text: "⭐ 1" + text_font: font_tiny + text_color: 0x7A5900 + bg_color: 0xFFD93D + radius: 8 + pad_left: 8 + pad_right: 8 + pad_top: 2 + pad_bottom: 2 + + # Brush Teeth + - btn: + id: card_emma_brush_teeth + x: 393 + y: 10 + width: 193 + height: 138 + bg_color: 0xFFFFFF + border_color: 0xEEEEEE + border_width: 2 + radius: 20 + shadow_color: 0xCCCCCC + shadow_width: 4 + shadow_ofs_y: 3 + on_click: + then: + - switch.toggle: emma_brush_teeth + widgets: + - label: + id: check_emma_brush_teeth + align: TOP_RIGHT + x: -10 + y: 8 + text: "" + text_font: font_med + - label: + align: CENTER + y: -34 + text: "🦷" + text_font: font_med + - label: + align: CENTER + y: 23 + text: "Brush Teeth" + text_font: font_small + text_color: 0x2D3436 + - label: + align: CENTER + y: 45 + text: "⭐ 1" + text_font: font_tiny + text_color: 0x7A5900 + bg_color: 0xFFD93D + radius: 8 + pad_left: 8 + pad_right: 8 + pad_top: 2 + pad_bottom: 2 + + # Tidy Room + - btn: + id: card_emma_tidy_room + x: 596 + y: 10 + width: 193 + height: 138 + bg_color: 0xFFFFFF + border_color: 0xEEEEEE + border_width: 2 + radius: 20 + shadow_color: 0xCCCCCC + shadow_width: 4 + shadow_ofs_y: 3 + on_click: + then: + - switch.toggle: emma_tidy_room + widgets: + - label: + id: check_emma_tidy_room + align: TOP_RIGHT + x: -10 + y: 8 + text: "" + text_font: font_med + - label: + align: CENTER + y: -34 + text: "🧹" + text_font: font_med + - label: + align: CENTER + y: 23 + text: "Tidy Room" + text_font: font_small + text_color: 0x2D3436 + - label: + align: CENTER + y: 45 + text: "⭐ 2" + text_font: font_tiny + text_color: 0x7A5900 + bg_color: 0xFFD93D + radius: 8 + pad_left: 8 + pad_right: 8 + pad_top: 2 + pad_bottom: 2 + + # Homework + - btn: + id: card_emma_homework + x: 190 + y: 158 + width: 193 + height: 138 + bg_color: 0xFFFFFF + border_color: 0xEEEEEE + border_width: 2 + radius: 20 + shadow_color: 0xCCCCCC + shadow_width: 4 + shadow_ofs_y: 3 + on_click: + then: + - switch.toggle: emma_homework + widgets: + - label: + id: check_emma_homework + align: TOP_RIGHT + x: -10 + y: 8 + text: "" + text_font: font_med + - label: + align: CENTER + y: -34 + text: "📚" + text_font: font_med + - label: + align: CENTER + y: 23 + text: "Homework" + text_font: font_small + text_color: 0x2D3436 + - label: + align: CENTER + y: 45 + text: "⭐ 3" + text_font: font_tiny + text_color: 0x7A5900 + bg_color: 0xFFD93D + radius: 8 + pad_left: 8 + pad_right: 8 + pad_top: 2 + pad_bottom: 2 + + # Set Table + - btn: + id: card_emma_set_table + x: 393 + y: 158 + width: 193 + height: 138 + bg_color: 0xFFFFFF + border_color: 0xEEEEEE + border_width: 2 + radius: 20 + shadow_color: 0xCCCCCC + shadow_width: 4 + shadow_ofs_y: 3 + on_click: + then: + - switch.toggle: emma_set_table + widgets: + - label: + id: check_emma_set_table + align: TOP_RIGHT + x: -10 + y: 8 + text: "" + text_font: font_med + - label: + align: CENTER + y: -34 + text: "🍽️" + text_font: font_med + - label: + align: CENTER + y: 23 + text: "Set Table" + text_font: font_small + text_color: 0x2D3436 + - label: + align: CENTER + y: 45 + text: "⭐ 1" + text_font: font_tiny + text_color: 0x7A5900 + bg_color: 0xFFD93D + radius: 8 + pad_left: 8 + pad_right: 8 + pad_top: 2 + pad_bottom: 2 + + # Take Out Trash + - btn: + id: card_emma_take_out_trash + x: 596 + y: 158 + width: 193 + height: 138 + bg_color: 0xFFFFFF + border_color: 0xEEEEEE + border_width: 2 + radius: 20 + shadow_color: 0xCCCCCC + shadow_width: 4 + shadow_ofs_y: 3 + on_click: + then: + - switch.toggle: emma_take_out_trash + widgets: + - label: + id: check_emma_take_out_trash + align: TOP_RIGHT + x: -10 + y: 8 + text: "" + text_font: font_med + - label: + align: CENTER + y: -34 + text: "🗑️" + text_font: font_med + - label: + align: CENTER + y: 23 + text: "Take Out Trash" + text_font: font_small + text_color: 0x2D3436 + - label: + align: CENTER + y: 45 + text: "⭐ 2" + text_font: font_tiny + text_color: 0x7A5900 + bg_color: 0xFFD93D + radius: 8 + pad_left: 8 + pad_right: 8 + pad_top: 2 + pad_bottom: 2 + + # Feed Pet + - btn: + id: card_emma_feed_pet + x: 190 + y: 306 + width: 193 + height: 138 + bg_color: 0xFFFFFF + border_color: 0xEEEEEE + border_width: 2 + radius: 20 + shadow_color: 0xCCCCCC + shadow_width: 4 + shadow_ofs_y: 3 + on_click: + then: + - switch.toggle: emma_feed_pet + widgets: + - label: + id: check_emma_feed_pet + align: TOP_RIGHT + x: -10 + y: 8 + text: "" + text_font: font_med + - label: + align: CENTER + y: -34 + text: "🐾" + text_font: font_med + - label: + align: CENTER + y: 23 + text: "Feed Pet" + text_font: font_small + text_color: 0x2D3436 + - label: + align: CENTER + y: 45 + text: "⭐ 1" + text_font: font_tiny + text_color: 0x7A5900 + bg_color: 0xFFD93D + radius: 8 + pad_left: 8 + pad_right: 8 + pad_top: 2 + pad_bottom: 2 + + # Water Plants + - btn: + id: card_emma_water_plants + x: 393 + y: 306 + width: 193 + height: 138 + bg_color: 0xFFFFFF + border_color: 0xEEEEEE + border_width: 2 + radius: 20 + shadow_color: 0xCCCCCC + shadow_width: 4 + shadow_ofs_y: 3 + on_click: + then: + - switch.toggle: emma_water_plants + widgets: + - label: + id: check_emma_water_plants + align: TOP_RIGHT + x: -10 + y: 8 + text: "" + text_font: font_med + - label: + align: CENTER + y: -34 + text: "🌱" + text_font: font_med + - label: + align: CENTER + y: 23 + text: "Water Plants" + text_font: font_small + text_color: 0x2D3436 + - label: + align: CENTER + y: 45 + text: "⭐ 1" + text_font: font_tiny + text_color: 0x7A5900 + bg_color: 0xFFD93D + radius: 8 + pad_left: 8 + pad_right: 8 + pad_top: 2 + pad_bottom: 2 + + + - id: page_liam + bg_color: 0xFFF9F0 + widgets: + + # ── Sidebar ────────────────────────────────────────────────────────── + - obj: + x: 0 + y: 0 + width: 180 + height: 480 + bg_color: 0xC77DFF + border_width: 0 + radius: 0 + pad_all: 0 + widgets: + - label: + x: 0 + y: 20 + width: 180 + align: TOP_MID + text: "🐱" + text_font: font_title + - label: + x: 0 + y: 74 + width: 180 + align: TOP_MID + text: "Liam" + text_font: font_name + text_color: 0xFFFFFF + - label: + id: sidebar_stars_liam + x: 0 + y: 120 + width: 180 + align: TOP_MID + text: "⭐ 0 stars" + text_font: font_med + text_color: 0xFFD93D + + # Progress bar + - bar: + id: progress_bar_liam + x: 16 + y: 164 + width: 148 + height: 16 + bg_color: 0x8B42CC + indicator: + bg_color: 0xFFFFFF + value: 0 + min_value: 0 + max_value: 8 + - label: + id: progress_label_liam + x: 0 + y: 188 + width: 180 + align: TOP_MID + text: "0 / 8" + text_font: font_tiny + text_color: 0xDDEEFF + + # All-done celebration label (hidden until complete) + - label: + id: all_done_label_liam + x: 0 + y: 214 + width: 180 + align: TOP_MID + text: "" + text_font: font_small + text_color: 0xFFFFFF + + # Manual reset button (parent use — above back button) + - btn: + x: 16 + y: 362 + width: 148 + height: 44 + bg_color: 0xFF4757 + radius: 14 + on_click: + then: + - script.execute: reset_liam_chores + widgets: + - label: + align: CENTER + text: "↺ Reset" + text_color: 0xFFFFFF + text_font: font_small + + # Back button + - btn: + x: 16 + y: 418 + width: 148 + height: 46 + bg_color: 0x8B42CC + radius: 14 + on_click: + then: + - lvgl.page.show: page_home + widgets: + - label: + align: CENTER + text: "◀ Home" + text_color: 0xFFFFFF + text_font: font_small + + # ── Chore cards ─────────────────────────────────────────────────────── + # Make Bed + - btn: + id: card_liam_make_bed + x: 190 + y: 10 + width: 193 + height: 138 + bg_color: 0xFFFFFF + border_color: 0xEEEEEE + border_width: 2 + radius: 20 + shadow_color: 0xCCCCCC + shadow_width: 4 + shadow_ofs_y: 3 + on_click: + then: + - switch.toggle: liam_make_bed + widgets: + - label: + id: check_liam_make_bed + align: TOP_RIGHT + x: -10 + y: 8 + text: "" + text_font: font_med + - label: + align: CENTER + y: -34 + text: "🛏️" + text_font: font_med + - label: + align: CENTER + y: 23 + text: "Make Bed" + text_font: font_small + text_color: 0x2D3436 + - label: + align: CENTER + y: 45 + text: "⭐ 1" + text_font: font_tiny + text_color: 0x7A5900 + bg_color: 0xFFD93D + radius: 8 + pad_left: 8 + pad_right: 8 + pad_top: 2 + pad_bottom: 2 + + # Brush Teeth + - btn: + id: card_liam_brush_teeth + x: 393 + y: 10 + width: 193 + height: 138 + bg_color: 0xFFFFFF + border_color: 0xEEEEEE + border_width: 2 + radius: 20 + shadow_color: 0xCCCCCC + shadow_width: 4 + shadow_ofs_y: 3 + on_click: + then: + - switch.toggle: liam_brush_teeth + widgets: + - label: + id: check_liam_brush_teeth + align: TOP_RIGHT + x: -10 + y: 8 + text: "" + text_font: font_med + - label: + align: CENTER + y: -34 + text: "🦷" + text_font: font_med + - label: + align: CENTER + y: 23 + text: "Brush Teeth" + text_font: font_small + text_color: 0x2D3436 + - label: + align: CENTER + y: 45 + text: "⭐ 1" + text_font: font_tiny + text_color: 0x7A5900 + bg_color: 0xFFD93D + radius: 8 + pad_left: 8 + pad_right: 8 + pad_top: 2 + pad_bottom: 2 + + # Tidy Room + - btn: + id: card_liam_tidy_room + x: 596 + y: 10 + width: 193 + height: 138 + bg_color: 0xFFFFFF + border_color: 0xEEEEEE + border_width: 2 + radius: 20 + shadow_color: 0xCCCCCC + shadow_width: 4 + shadow_ofs_y: 3 + on_click: + then: + - switch.toggle: liam_tidy_room + widgets: + - label: + id: check_liam_tidy_room + align: TOP_RIGHT + x: -10 + y: 8 + text: "" + text_font: font_med + - label: + align: CENTER + y: -34 + text: "🧹" + text_font: font_med + - label: + align: CENTER + y: 23 + text: "Tidy Room" + text_font: font_small + text_color: 0x2D3436 + - label: + align: CENTER + y: 45 + text: "⭐ 2" + text_font: font_tiny + text_color: 0x7A5900 + bg_color: 0xFFD93D + radius: 8 + pad_left: 8 + pad_right: 8 + pad_top: 2 + pad_bottom: 2 + + # Homework + - btn: + id: card_liam_homework + x: 190 + y: 158 + width: 193 + height: 138 + bg_color: 0xFFFFFF + border_color: 0xEEEEEE + border_width: 2 + radius: 20 + shadow_color: 0xCCCCCC + shadow_width: 4 + shadow_ofs_y: 3 + on_click: + then: + - switch.toggle: liam_homework + widgets: + - label: + id: check_liam_homework + align: TOP_RIGHT + x: -10 + y: 8 + text: "" + text_font: font_med + - label: + align: CENTER + y: -34 + text: "📚" + text_font: font_med + - label: + align: CENTER + y: 23 + text: "Homework" + text_font: font_small + text_color: 0x2D3436 + - label: + align: CENTER + y: 45 + text: "⭐ 3" + text_font: font_tiny + text_color: 0x7A5900 + bg_color: 0xFFD93D + radius: 8 + pad_left: 8 + pad_right: 8 + pad_top: 2 + pad_bottom: 2 + + # Set Table + - btn: + id: card_liam_set_table + x: 393 + y: 158 + width: 193 + height: 138 + bg_color: 0xFFFFFF + border_color: 0xEEEEEE + border_width: 2 + radius: 20 + shadow_color: 0xCCCCCC + shadow_width: 4 + shadow_ofs_y: 3 + on_click: + then: + - switch.toggle: liam_set_table + widgets: + - label: + id: check_liam_set_table + align: TOP_RIGHT + x: -10 + y: 8 + text: "" + text_font: font_med + - label: + align: CENTER + y: -34 + text: "🍽️" + text_font: font_med + - label: + align: CENTER + y: 23 + text: "Set Table" + text_font: font_small + text_color: 0x2D3436 + - label: + align: CENTER + y: 45 + text: "⭐ 1" + text_font: font_tiny + text_color: 0x7A5900 + bg_color: 0xFFD93D + radius: 8 + pad_left: 8 + pad_right: 8 + pad_top: 2 + pad_bottom: 2 + + # Take Out Trash + - btn: + id: card_liam_take_out_trash + x: 596 + y: 158 + width: 193 + height: 138 + bg_color: 0xFFFFFF + border_color: 0xEEEEEE + border_width: 2 + radius: 20 + shadow_color: 0xCCCCCC + shadow_width: 4 + shadow_ofs_y: 3 + on_click: + then: + - switch.toggle: liam_take_out_trash + widgets: + - label: + id: check_liam_take_out_trash + align: TOP_RIGHT + x: -10 + y: 8 + text: "" + text_font: font_med + - label: + align: CENTER + y: -34 + text: "🗑️" + text_font: font_med + - label: + align: CENTER + y: 23 + text: "Take Out Trash" + text_font: font_small + text_color: 0x2D3436 + - label: + align: CENTER + y: 45 + text: "⭐ 2" + text_font: font_tiny + text_color: 0x7A5900 + bg_color: 0xFFD93D + radius: 8 + pad_left: 8 + pad_right: 8 + pad_top: 2 + pad_bottom: 2 + + # Feed Pet + - btn: + id: card_liam_feed_pet + x: 190 + y: 306 + width: 193 + height: 138 + bg_color: 0xFFFFFF + border_color: 0xEEEEEE + border_width: 2 + radius: 20 + shadow_color: 0xCCCCCC + shadow_width: 4 + shadow_ofs_y: 3 + on_click: + then: + - switch.toggle: liam_feed_pet + widgets: + - label: + id: check_liam_feed_pet + align: TOP_RIGHT + x: -10 + y: 8 + text: "" + text_font: font_med + - label: + align: CENTER + y: -34 + text: "🐾" + text_font: font_med + - label: + align: CENTER + y: 23 + text: "Feed Pet" + text_font: font_small + text_color: 0x2D3436 + - label: + align: CENTER + y: 45 + text: "⭐ 1" + text_font: font_tiny + text_color: 0x7A5900 + bg_color: 0xFFD93D + radius: 8 + pad_left: 8 + pad_right: 8 + pad_top: 2 + pad_bottom: 2 + + # Water Plants + - btn: + id: card_liam_water_plants + x: 393 + y: 306 + width: 193 + height: 138 + bg_color: 0xFFFFFF + border_color: 0xEEEEEE + border_width: 2 + radius: 20 + shadow_color: 0xCCCCCC + shadow_width: 4 + shadow_ofs_y: 3 + on_click: + then: + - switch.toggle: liam_water_plants + widgets: + - label: + id: check_liam_water_plants + align: TOP_RIGHT + x: -10 + y: 8 + text: "" + text_font: font_med + - label: + align: CENTER + y: -34 + text: "🌱" + text_font: font_med + - label: + align: CENTER + y: 23 + text: "Water Plants" + text_font: font_small + text_color: 0x2D3436 + - label: + align: CENTER + y: 45 + text: "⭐ 1" + text_font: font_tiny + text_color: 0x7A5900 + bg_color: 0xFFD93D + radius: 8 + pad_left: 8 + pad_right: 8 + pad_top: 2 + pad_bottom: 2 + + + - id: page_zoe + bg_color: 0xFFF9F0 + widgets: + + # ── Sidebar ────────────────────────────────────────────────────────── + - obj: + x: 0 + y: 0 + width: 180 + height: 480 + bg_color: 0xFF6B9D + border_width: 0 + radius: 0 + pad_all: 0 + widgets: + - label: + x: 0 + y: 20 + width: 180 + align: TOP_MID + text: "🐻" + text_font: font_title + - label: + x: 0 + y: 74 + width: 180 + align: TOP_MID + text: "Zoe" + text_font: font_name + text_color: 0xFFFFFF + - label: + id: sidebar_stars_zoe + x: 0 + y: 120 + width: 180 + align: TOP_MID + text: "⭐ 0 stars" + text_font: font_med + text_color: 0xFFD93D + + # Progress bar + - bar: + id: progress_bar_zoe + x: 16 + y: 164 + width: 148 + height: 16 + bg_color: 0xCC3A6F + indicator: + bg_color: 0xFFFFFF + value: 0 + min_value: 0 + max_value: 8 + - label: + id: progress_label_zoe + x: 0 + y: 188 + width: 180 + align: TOP_MID + text: "0 / 8" + text_font: font_tiny + text_color: 0xDDEEFF + + # All-done celebration label (hidden until complete) + - label: + id: all_done_label_zoe + x: 0 + y: 214 + width: 180 + align: TOP_MID + text: "" + text_font: font_small + text_color: 0xFFFFFF + + # Manual reset button (parent use — above back button) + - btn: + x: 16 + y: 362 + width: 148 + height: 44 + bg_color: 0xFF4757 + radius: 14 + on_click: + then: + - script.execute: reset_zoe_chores + widgets: + - label: + align: CENTER + text: "↺ Reset" + text_color: 0xFFFFFF + text_font: font_small + + # Back button + - btn: + x: 16 + y: 418 + width: 148 + height: 46 + bg_color: 0xCC3A6F + radius: 14 + on_click: + then: + - lvgl.page.show: page_home + widgets: + - label: + align: CENTER + text: "◀ Home" + text_color: 0xFFFFFF + text_font: font_small + + # ── Chore cards ─────────────────────────────────────────────────────── + # Make Bed + - btn: + id: card_zoe_make_bed + x: 190 + y: 10 + width: 193 + height: 138 + bg_color: 0xFFFFFF + border_color: 0xEEEEEE + border_width: 2 + radius: 20 + shadow_color: 0xCCCCCC + shadow_width: 4 + shadow_ofs_y: 3 + on_click: + then: + - switch.toggle: zoe_make_bed + widgets: + - label: + id: check_zoe_make_bed + align: TOP_RIGHT + x: -10 + y: 8 + text: "" + text_font: font_med + - label: + align: CENTER + y: -34 + text: "🛏️" + text_font: font_med + - label: + align: CENTER + y: 23 + text: "Make Bed" + text_font: font_small + text_color: 0x2D3436 + - label: + align: CENTER + y: 45 + text: "⭐ 1" + text_font: font_tiny + text_color: 0x7A5900 + bg_color: 0xFFD93D + radius: 8 + pad_left: 8 + pad_right: 8 + pad_top: 2 + pad_bottom: 2 + + # Brush Teeth + - btn: + id: card_zoe_brush_teeth + x: 393 + y: 10 + width: 193 + height: 138 + bg_color: 0xFFFFFF + border_color: 0xEEEEEE + border_width: 2 + radius: 20 + shadow_color: 0xCCCCCC + shadow_width: 4 + shadow_ofs_y: 3 + on_click: + then: + - switch.toggle: zoe_brush_teeth + widgets: + - label: + id: check_zoe_brush_teeth + align: TOP_RIGHT + x: -10 + y: 8 + text: "" + text_font: font_med + - label: + align: CENTER + y: -34 + text: "🦷" + text_font: font_med + - label: + align: CENTER + y: 23 + text: "Brush Teeth" + text_font: font_small + text_color: 0x2D3436 + - label: + align: CENTER + y: 45 + text: "⭐ 1" + text_font: font_tiny + text_color: 0x7A5900 + bg_color: 0xFFD93D + radius: 8 + pad_left: 8 + pad_right: 8 + pad_top: 2 + pad_bottom: 2 + + # Tidy Room + - btn: + id: card_zoe_tidy_room + x: 596 + y: 10 + width: 193 + height: 138 + bg_color: 0xFFFFFF + border_color: 0xEEEEEE + border_width: 2 + radius: 20 + shadow_color: 0xCCCCCC + shadow_width: 4 + shadow_ofs_y: 3 + on_click: + then: + - switch.toggle: zoe_tidy_room + widgets: + - label: + id: check_zoe_tidy_room + align: TOP_RIGHT + x: -10 + y: 8 + text: "" + text_font: font_med + - label: + align: CENTER + y: -34 + text: "🧹" + text_font: font_med + - label: + align: CENTER + y: 23 + text: "Tidy Room" + text_font: font_small + text_color: 0x2D3436 + - label: + align: CENTER + y: 45 + text: "⭐ 2" + text_font: font_tiny + text_color: 0x7A5900 + bg_color: 0xFFD93D + radius: 8 + pad_left: 8 + pad_right: 8 + pad_top: 2 + pad_bottom: 2 + + # Homework + - btn: + id: card_zoe_homework + x: 190 + y: 158 + width: 193 + height: 138 + bg_color: 0xFFFFFF + border_color: 0xEEEEEE + border_width: 2 + radius: 20 + shadow_color: 0xCCCCCC + shadow_width: 4 + shadow_ofs_y: 3 + on_click: + then: + - switch.toggle: zoe_homework + widgets: + - label: + id: check_zoe_homework + align: TOP_RIGHT + x: -10 + y: 8 + text: "" + text_font: font_med + - label: + align: CENTER + y: -34 + text: "📚" + text_font: font_med + - label: + align: CENTER + y: 23 + text: "Homework" + text_font: font_small + text_color: 0x2D3436 + - label: + align: CENTER + y: 45 + text: "⭐ 3" + text_font: font_tiny + text_color: 0x7A5900 + bg_color: 0xFFD93D + radius: 8 + pad_left: 8 + pad_right: 8 + pad_top: 2 + pad_bottom: 2 + + # Set Table + - btn: + id: card_zoe_set_table + x: 393 + y: 158 + width: 193 + height: 138 + bg_color: 0xFFFFFF + border_color: 0xEEEEEE + border_width: 2 + radius: 20 + shadow_color: 0xCCCCCC + shadow_width: 4 + shadow_ofs_y: 3 + on_click: + then: + - switch.toggle: zoe_set_table + widgets: + - label: + id: check_zoe_set_table + align: TOP_RIGHT + x: -10 + y: 8 + text: "" + text_font: font_med + - label: + align: CENTER + y: -34 + text: "🍽️" + text_font: font_med + - label: + align: CENTER + y: 23 + text: "Set Table" + text_font: font_small + text_color: 0x2D3436 + - label: + align: CENTER + y: 45 + text: "⭐ 1" + text_font: font_tiny + text_color: 0x7A5900 + bg_color: 0xFFD93D + radius: 8 + pad_left: 8 + pad_right: 8 + pad_top: 2 + pad_bottom: 2 + + # Take Out Trash + - btn: + id: card_zoe_take_out_trash + x: 596 + y: 158 + width: 193 + height: 138 + bg_color: 0xFFFFFF + border_color: 0xEEEEEE + border_width: 2 + radius: 20 + shadow_color: 0xCCCCCC + shadow_width: 4 + shadow_ofs_y: 3 + on_click: + then: + - switch.toggle: zoe_take_out_trash + widgets: + - label: + id: check_zoe_take_out_trash + align: TOP_RIGHT + x: -10 + y: 8 + text: "" + text_font: font_med + - label: + align: CENTER + y: -34 + text: "🗑️" + text_font: font_med + - label: + align: CENTER + y: 23 + text: "Take Out Trash" + text_font: font_small + text_color: 0x2D3436 + - label: + align: CENTER + y: 45 + text: "⭐ 2" + text_font: font_tiny + text_color: 0x7A5900 + bg_color: 0xFFD93D + radius: 8 + pad_left: 8 + pad_right: 8 + pad_top: 2 + pad_bottom: 2 + + # Feed Pet + - btn: + id: card_zoe_feed_pet + x: 190 + y: 306 + width: 193 + height: 138 + bg_color: 0xFFFFFF + border_color: 0xEEEEEE + border_width: 2 + radius: 20 + shadow_color: 0xCCCCCC + shadow_width: 4 + shadow_ofs_y: 3 + on_click: + then: + - switch.toggle: zoe_feed_pet + widgets: + - label: + id: check_zoe_feed_pet + align: TOP_RIGHT + x: -10 + y: 8 + text: "" + text_font: font_med + - label: + align: CENTER + y: -34 + text: "🐾" + text_font: font_med + - label: + align: CENTER + y: 23 + text: "Feed Pet" + text_font: font_small + text_color: 0x2D3436 + - label: + align: CENTER + y: 45 + text: "⭐ 1" + text_font: font_tiny + text_color: 0x7A5900 + bg_color: 0xFFD93D + radius: 8 + pad_left: 8 + pad_right: 8 + pad_top: 2 + pad_bottom: 2 + + # Water Plants + - btn: + id: card_zoe_water_plants + x: 393 + y: 306 + width: 193 + height: 138 + bg_color: 0xFFFFFF + border_color: 0xEEEEEE + border_width: 2 + radius: 20 + shadow_color: 0xCCCCCC + shadow_width: 4 + shadow_ofs_y: 3 + on_click: + then: + - switch.toggle: zoe_water_plants + widgets: + - label: + id: check_zoe_water_plants + align: TOP_RIGHT + x: -10 + y: 8 + text: "" + text_font: font_med + - label: + align: CENTER + y: -34 + text: "🌱" + text_font: font_med + - label: + align: CENTER + y: 23 + text: "Water Plants" + text_font: font_small + text_color: 0x2D3436 + - label: + align: CENTER + y: 45 + text: "⭐ 1" + text_font: font_tiny + text_color: 0x7A5900 + bg_color: 0xFFD93D + radius: 8 + pad_left: 8 + pad_right: 8 + pad_top: 2 + pad_bottom: 2 \ No newline at end of file diff --git a/esphome/chore-tracker/chore-tracker-dashboard.yaml b/esphome/chore-tracker/chore-tracker-dashboard.yaml new file mode 100644 index 0000000..9334dc7 --- /dev/null +++ b/esphome/chore-tracker/chore-tracker-dashboard.yaml @@ -0,0 +1,133 @@ +################################################################################ +# chore-tracker-dashboard.yaml (AUTO-GENERATED — edit chores_config.yaml) +# Kids: Jordyn, Declan, Chloe +################################################################################ + +title: "Chore Tracker" +views: + - title: Today + icon: mdi:checkbox-marked-circle + path: chores + cards: + - type: horizontal-stack + cards: + - type: markdown + content: > + ### 😺 Jordyn + {{ + '✅ All done!' if states('sensor.jordyn_all_chores_done') == 'True' + else states('sensor.jordyn_chores_done_today') ~ '/5 chores done' + }} + + - type: markdown + content: > + ### 🤓 Declan + {{ + '✅ All done!' if states('sensor.declan_all_chores_done') == 'True' + else states('sensor.declan_chores_done_today') ~ '/4 chores done' + }} + + - type: markdown + content: > + ### 🌝 Chloe + {{ + '✅ All done!' if states('sensor.chloe_all_chores_done') == 'True' + else states('sensor.chloe_chores_done_today') ~ '/6 chores done' + }} + + - type: button + name: "↺ Reset ALL Chores" + icon: mdi:restart-alert + tap_action: + action: call-service + service: input_button.press + target: + entity_id: input_button.reset_all_chores + - type: entities + title: "😺 Jordyn's Chores" + entities: + - entity: switch.chore_tracker_jordyn_make_bed + name: "🛏️ Make Bed" + + - entity: switch.chore_tracker_jordyn_brush_teeth + name: "🦷 Brush Teeth" + + - entity: switch.chore_tracker_jordyn_tidy_room + name: "🧹 Tidy Room" + + - entity: switch.chore_tracker_jordyn_homework + name: "📚 Homework" + + - entity: switch.chore_tracker_jordyn_feed_dog + name: "🐾 Feed Dog" + - type: divider + - entity: sensor.jordyn_chores_done_today + name: "Chores done today" + - entity: sensor.jordyn_all_chores_done + name: "✅ All Done?" + - type: button + 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" + entities: + - entity: switch.chore_tracker_declan_make_bed + name: "🛏️ Make Bed" + + - 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" + - type: divider + - entity: sensor.declan_chores_done_today + name: "Chores done today" + - entity: sensor.declan_all_chores_done + name: "✅ All Done?" + - type: button + 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" + entities: + - entity: switch.chore_tracker_chloe_make_bed + name: "🛏️ Make Bed" + + - entity: switch.chore_tracker_chloe_brush_teeth + name: "🦷 Brush Teeth" + + - 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" + - type: divider + - entity: sensor.chloe_chores_done_today + name: "Chores done today" + - entity: sensor.chloe_all_chores_done + name: "✅ All Done?" + - type: button + name: "↺ Reset Chloe's Chores" + tap_action: + action: call-service + service: input_button.press + target: + entity_id: input_button.chloe_reset_chores + diff --git a/esphome/chore-tracker/chore-tracker-esphome.yaml b/esphome/chore-tracker/chore-tracker-esphome.yaml new file mode 100644 index 0000000..3ef805f --- /dev/null +++ b/esphome/chore-tracker/chore-tracker-esphome.yaml @@ -0,0 +1,1757 @@ +################################################################################ +# chore-tracker-esphome.yaml (AUTO-GENERATED — edit chores_config.yaml) +# Kids: Jordyn, Declan, Chloe +# +# HOME SCREEN BEHAVIOUR: +# Red outline = chores incomplete +# Solid green = all chores done ✓ +# Resets to red automatically at midnight +################################################################################ + +esphome: + name: chore-tracker + friendly_name: "Chore Tracker" + +esp32: + board: esp32s3box + framework: + type: esp-idf + +wifi: + ssid: !secret wifi_iot_ssid + password: !secret wifi_password + ap: + ssid: "Chore Tracker Hotspot" + password: "choretracker" + +captive_portal: +logger: + level: INFO + +api: + encryption: + key: !secret api_encryption_key + +ota: + - platform: esphome + password: !secret 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: + - id: i2c_touch + sda: GPIO19 + scl: GPIO20 + frequency: 400kHz + +output: + - platform: ledc + pin: GPIO17 + id: backlight_output + +light: + - platform: monochromatic + output: backlight_output + name: "Display Backlight" + id: backlight + restore_mode: ALWAYS_ON + +# ── Midnight reset ──────────────────────────────────────────────────────────── +time: + - platform: homeassistant + id: ha_time + on_time: + - hours: 0 + minutes: 0 + seconds: 0 + then: + - lambda: |- + id(reset_jordyn_chores).execute(); + id(reset_declan_chores).execute(); + id(reset_chloe_chores).execute(); + - lvgl.page.show: page_home + +fonts: + - file: "gfonts://Nunito:wght@900" + id: font_title + size: 40 + - file: "gfonts://Nunito:wght@800" + id: font_name + size: 30 + - file: "gfonts://Nunito:wght@700" + id: font_med + size: 22 + - file: "gfonts://Nunito:wght@600" + id: font_small + size: 17 + - file: "gfonts://Nunito:wght@600" + id: font_tiny + size: 13 + +# ── Switches — one per chore per kid, synced to HA ─────────────────────────── +switch: + + # ── Jordyn's chores ────────────────────────── - platform: template + name: "Jordyn - Make Bed" + id: jordyn_make_bed + icon: mdi:checkbox-marked-circle + optimistic: true + restore_mode: RESTORE_DEFAULT_OFF + on_turn_on: + then: + - script.execute: update_jordyn_ui + - homeassistant.service: + service: switch.turn_on + data: + entity_id: switch.chore_tracker_jordyn_make_bed + on_turn_off: + then: + - script.execute: update_jordyn_ui + - homeassistant.service: + service: switch.turn_off + data: + entity_id: switch.chore_tracker_jordyn_make_bed + - platform: template + name: "Jordyn - Brush Teeth" + id: jordyn_brush_teeth + icon: mdi:checkbox-marked-circle + optimistic: true + restore_mode: RESTORE_DEFAULT_OFF + on_turn_on: + then: + - script.execute: update_jordyn_ui + - homeassistant.service: + service: switch.turn_on + data: + entity_id: switch.chore_tracker_jordyn_brush_teeth + on_turn_off: + then: + - script.execute: update_jordyn_ui + - homeassistant.service: + service: switch.turn_off + data: + entity_id: switch.chore_tracker_jordyn_brush_teeth + - platform: template + name: "Jordyn - Tidy Room" + id: jordyn_tidy_room + icon: mdi:checkbox-marked-circle + optimistic: true + restore_mode: RESTORE_DEFAULT_OFF + on_turn_on: + then: + - script.execute: update_jordyn_ui + - homeassistant.service: + service: switch.turn_on + data: + entity_id: switch.chore_tracker_jordyn_tidy_room + on_turn_off: + then: + - script.execute: update_jordyn_ui + - homeassistant.service: + service: switch.turn_off + data: + entity_id: switch.chore_tracker_jordyn_tidy_room + - platform: template + name: "Jordyn - Homework" + id: jordyn_homework + icon: mdi:checkbox-marked-circle + optimistic: true + restore_mode: RESTORE_DEFAULT_OFF + on_turn_on: + then: + - script.execute: update_jordyn_ui + - homeassistant.service: + service: switch.turn_on + data: + entity_id: switch.chore_tracker_jordyn_homework + on_turn_off: + then: + - script.execute: update_jordyn_ui + - homeassistant.service: + service: switch.turn_off + data: + entity_id: switch.chore_tracker_jordyn_homework + - platform: template + name: "Jordyn - Feed Dog" + id: jordyn_feed_dog + icon: mdi:checkbox-marked-circle + optimistic: true + restore_mode: RESTORE_DEFAULT_OFF + on_turn_on: + then: + - script.execute: update_jordyn_ui + - homeassistant.service: + service: switch.turn_on + data: + entity_id: switch.chore_tracker_jordyn_feed_dog + on_turn_off: + then: + - script.execute: update_jordyn_ui + - homeassistant.service: + service: switch.turn_off + data: + entity_id: switch.chore_tracker_jordyn_feed_dog + + # ── Declan's chores ────────────────────────── - platform: template + name: "Declan - Make Bed" + id: declan_make_bed + icon: mdi:checkbox-marked-circle + optimistic: true + restore_mode: RESTORE_DEFAULT_OFF + on_turn_on: + then: + - script.execute: update_declan_ui + - homeassistant.service: + service: switch.turn_on + data: + entity_id: switch.chore_tracker_declan_make_bed + on_turn_off: + then: + - script.execute: update_declan_ui + - homeassistant.service: + service: switch.turn_off + data: + entity_id: switch.chore_tracker_declan_make_bed + - platform: template + name: "Declan - Brush Teeth" + id: declan_brush_teeth + icon: mdi:checkbox-marked-circle + optimistic: true + restore_mode: RESTORE_DEFAULT_OFF + on_turn_on: + then: + - script.execute: update_declan_ui + - homeassistant.service: + service: switch.turn_on + data: + entity_id: switch.chore_tracker_declan_brush_teeth + on_turn_off: + then: + - script.execute: update_declan_ui + - homeassistant.service: + service: switch.turn_off + data: + entity_id: switch.chore_tracker_declan_brush_teeth + - platform: template + name: "Declan - Set Table" + id: declan_set_table + icon: mdi:checkbox-marked-circle + optimistic: true + restore_mode: RESTORE_DEFAULT_OFF + on_turn_on: + then: + - script.execute: update_declan_ui + - homeassistant.service: + service: switch.turn_on + data: + entity_id: switch.chore_tracker_declan_set_table + on_turn_off: + then: + - script.execute: update_declan_ui + - homeassistant.service: + service: switch.turn_off + data: + entity_id: switch.chore_tracker_declan_set_table + - platform: template + name: "Declan - Take Out Trash" + id: declan_take_out_trash + icon: mdi:checkbox-marked-circle + optimistic: true + restore_mode: RESTORE_DEFAULT_OFF + on_turn_on: + then: + - script.execute: update_declan_ui + - homeassistant.service: + service: switch.turn_on + data: + entity_id: switch.chore_tracker_declan_take_out_trash + on_turn_off: + then: + - script.execute: update_declan_ui + - homeassistant.service: + service: switch.turn_off + data: + entity_id: switch.chore_tracker_declan_take_out_trash + + # ── Chloe's chores ────────────────────────── - platform: template + name: "Chloe - Make Bed" + id: chloe_make_bed + icon: mdi:checkbox-marked-circle + optimistic: true + restore_mode: RESTORE_DEFAULT_OFF + on_turn_on: + then: + - script.execute: update_chloe_ui + - homeassistant.service: + service: switch.turn_on + data: + entity_id: switch.chore_tracker_chloe_make_bed + on_turn_off: + then: + - script.execute: update_chloe_ui + - homeassistant.service: + service: switch.turn_off + data: + entity_id: switch.chore_tracker_chloe_make_bed + - platform: template + name: "Chloe - Brush Teeth" + id: chloe_brush_teeth + icon: mdi:checkbox-marked-circle + optimistic: true + restore_mode: RESTORE_DEFAULT_OFF + on_turn_on: + then: + - script.execute: update_chloe_ui + - homeassistant.service: + service: switch.turn_on + data: + entity_id: switch.chore_tracker_chloe_brush_teeth + on_turn_off: + then: + - script.execute: update_chloe_ui + - homeassistant.service: + service: switch.turn_off + data: + entity_id: switch.chore_tracker_chloe_brush_teeth + - platform: template + name: "Chloe - Water Plants" + id: chloe_water_plants + icon: mdi:checkbox-marked-circle + optimistic: true + restore_mode: RESTORE_DEFAULT_OFF + on_turn_on: + then: + - script.execute: update_chloe_ui + - homeassistant.service: + service: switch.turn_on + data: + entity_id: switch.chore_tracker_chloe_water_plants + on_turn_off: + then: + - script.execute: update_chloe_ui + - homeassistant.service: + service: switch.turn_off + data: + entity_id: switch.chore_tracker_chloe_water_plants + - platform: template + name: "Chloe - Homework" + id: chloe_homework + icon: mdi:checkbox-marked-circle + optimistic: true + restore_mode: RESTORE_DEFAULT_OFF + on_turn_on: + then: + - script.execute: update_chloe_ui + - homeassistant.service: + service: switch.turn_on + data: + entity_id: switch.chore_tracker_chloe_homework + on_turn_off: + then: + - script.execute: update_chloe_ui + - homeassistant.service: + service: switch.turn_off + data: + entity_id: switch.chore_tracker_chloe_homework + - platform: template + name: "Chloe - Tidy Room" + id: chloe_tidy_room + icon: mdi:checkbox-marked-circle + optimistic: true + restore_mode: RESTORE_DEFAULT_OFF + on_turn_on: + then: + - script.execute: update_chloe_ui + - homeassistant.service: + service: switch.turn_on + data: + entity_id: switch.chore_tracker_chloe_tidy_room + on_turn_off: + then: + - script.execute: update_chloe_ui + - homeassistant.service: + service: switch.turn_off + data: + entity_id: switch.chore_tracker_chloe_tidy_room + - platform: template + name: "Chloe - Practice Piano" + id: chloe_practice_piano + icon: mdi:checkbox-marked-circle + optimistic: true + restore_mode: RESTORE_DEFAULT_OFF + on_turn_on: + then: + - script.execute: update_chloe_ui + - homeassistant.service: + service: switch.turn_on + data: + entity_id: switch.chore_tracker_chloe_practice_piano + on_turn_off: + then: + - script.execute: update_chloe_ui + - homeassistant.service: + service: switch.turn_off + data: + entity_id: switch.chore_tracker_chloe_practice_piano + + +# ── Sensors — reported to HA ───────────────────────────────────────────────── +sensor: + - platform: template + name: "Jordyn Chores Done" + id: jordyn_chores_done + accuracy_decimals: 0 + update_interval: 2s + lambda: |- + int done = 0; + if (id(jordyn_make_bed).state) done++; + if (id(jordyn_brush_teeth).state) done++; + if (id(jordyn_tidy_room).state) done++; + if (id(jordyn_homework).state) done++; + if (id(jordyn_feed_dog).state) done++; + return done; + + - platform: template + name: "Declan Chores Done" + id: declan_chores_done + accuracy_decimals: 0 + update_interval: 2s + lambda: |- + int done = 0; + if (id(declan_make_bed).state) done++; + if (id(declan_brush_teeth).state) done++; + if (id(declan_set_table).state) done++; + if (id(declan_take_out_trash).state) done++; + return done; + + - platform: template + name: "Chloe Chores Done" + id: chloe_chores_done + accuracy_decimals: 0 + update_interval: 2s + lambda: |- + int done = 0; + if (id(chloe_make_bed).state) done++; + if (id(chloe_brush_teeth).state) done++; + if (id(chloe_water_plants).state) done++; + if (id(chloe_homework).state) done++; + if (id(chloe_tidy_room).state) done++; + if (id(chloe_practice_piano).state) done++; + return done; + + + +# ── Scripts — reset + UI update ─────────────────────────────────────────────── +script: + - id: reset_jordyn_chores + mode: single + then: + - lambda: |- + id(jordyn_make_bed).turn_off(); + id(jordyn_brush_teeth).turn_off(); + id(jordyn_tidy_room).turn_off(); + id(jordyn_homework).turn_off(); + id(jordyn_feed_dog).turn_off(); + - script.execute: update_jordyn_ui + + + - id: update_jordyn_ui + mode: single + then: + - lambda: |- + int done = (id(jordyn_make_bed).state ? 1 : 0) + (id(jordyn_brush_teeth).state ? 1 : 0) + (id(jordyn_tidy_room).state ? 1 : 0) + (id(jordyn_homework).state ? 1 : 0) + (id(jordyn_feed_dog).state ? 1 : 0); + int total = 5; + char buf[24]; + + // Progress bar + label + lv_bar_set_value(id(progress_bar_jordyn), done, LV_ANIM_ON); + snprintf(buf, sizeof(buf), "%d / 5", done); + lv_label_set_text(id(progress_label_jordyn), buf); + + // All-done message in sidebar + lv_label_set_text(id(all_done_label_jordyn), done == total ? "\U0001F389 All done!" : ""); + + // Home button colour + if (done == total) { + lv_obj_set_style_bg_opa(id(home_btn_jordyn), LV_OPA_COVER, LV_PART_MAIN); + lv_obj_set_style_bg_color(id(home_btn_jordyn), lv_color_hex(0x6BCB77), LV_PART_MAIN); + lv_obj_set_style_border_width(id(home_btn_jordyn), 0, LV_PART_MAIN); + lv_label_set_text(id(home_status_jordyn), "\u2713 all done!"); + lv_obj_set_style_text_color(id(home_status_jordyn), lv_color_hex(0xFFFFFF), LV_PART_MAIN); + } else { + lv_obj_set_style_bg_opa(id(home_btn_jordyn), LV_OPA_TRANSP, LV_PART_MAIN); + lv_obj_set_style_border_color(id(home_btn_jordyn), lv_color_hex(0xFF4757), LV_PART_MAIN); + lv_obj_set_style_border_width(id(home_btn_jordyn), 5, LV_PART_MAIN); + snprintf(buf, sizeof(buf), "%d left", total - done); + lv_label_set_text(id(home_status_jordyn), buf); + lv_obj_set_style_text_color(id(home_status_jordyn), lv_color_hex(0xFF4757), LV_PART_MAIN); + } + + // Card colours + if (id(jordyn_make_bed).state) { + lv_obj_set_style_bg_color(id(card_jordyn_make_bed), lv_color_hex(0xF0FFF4), LV_PART_MAIN); + lv_obj_set_style_border_color(id(card_jordyn_make_bed), lv_color_hex(0x6BCB77), LV_PART_MAIN); + lv_obj_set_style_border_width(id(card_jordyn_make_bed), 3, LV_PART_MAIN); + lv_label_set_text(id(check_jordyn_make_bed), "\u2705"); + } else { + lv_obj_set_style_bg_color(id(card_jordyn_make_bed), lv_color_hex(0xFFFFFF), LV_PART_MAIN); + lv_obj_set_style_border_color(id(card_jordyn_make_bed), lv_color_hex(0xEEEEEE), LV_PART_MAIN); + lv_obj_set_style_border_width(id(card_jordyn_make_bed), 2, LV_PART_MAIN); + lv_label_set_text(id(check_jordyn_make_bed), ""); + } + if (id(jordyn_brush_teeth).state) { + lv_obj_set_style_bg_color(id(card_jordyn_brush_teeth), lv_color_hex(0xF0FFF4), LV_PART_MAIN); + lv_obj_set_style_border_color(id(card_jordyn_brush_teeth), lv_color_hex(0x6BCB77), LV_PART_MAIN); + lv_obj_set_style_border_width(id(card_jordyn_brush_teeth), 3, LV_PART_MAIN); + lv_label_set_text(id(check_jordyn_brush_teeth), "\u2705"); + } else { + lv_obj_set_style_bg_color(id(card_jordyn_brush_teeth), lv_color_hex(0xFFFFFF), LV_PART_MAIN); + lv_obj_set_style_border_color(id(card_jordyn_brush_teeth), lv_color_hex(0xEEEEEE), LV_PART_MAIN); + lv_obj_set_style_border_width(id(card_jordyn_brush_teeth), 2, LV_PART_MAIN); + lv_label_set_text(id(check_jordyn_brush_teeth), ""); + } + if (id(jordyn_tidy_room).state) { + lv_obj_set_style_bg_color(id(card_jordyn_tidy_room), lv_color_hex(0xF0FFF4), LV_PART_MAIN); + lv_obj_set_style_border_color(id(card_jordyn_tidy_room), lv_color_hex(0x6BCB77), LV_PART_MAIN); + lv_obj_set_style_border_width(id(card_jordyn_tidy_room), 3, LV_PART_MAIN); + lv_label_set_text(id(check_jordyn_tidy_room), "\u2705"); + } else { + lv_obj_set_style_bg_color(id(card_jordyn_tidy_room), lv_color_hex(0xFFFFFF), LV_PART_MAIN); + lv_obj_set_style_border_color(id(card_jordyn_tidy_room), lv_color_hex(0xEEEEEE), LV_PART_MAIN); + lv_obj_set_style_border_width(id(card_jordyn_tidy_room), 2, LV_PART_MAIN); + lv_label_set_text(id(check_jordyn_tidy_room), ""); + } + if (id(jordyn_homework).state) { + lv_obj_set_style_bg_color(id(card_jordyn_homework), lv_color_hex(0xF0FFF4), LV_PART_MAIN); + lv_obj_set_style_border_color(id(card_jordyn_homework), lv_color_hex(0x6BCB77), LV_PART_MAIN); + lv_obj_set_style_border_width(id(card_jordyn_homework), 3, LV_PART_MAIN); + lv_label_set_text(id(check_jordyn_homework), "\u2705"); + } else { + lv_obj_set_style_bg_color(id(card_jordyn_homework), lv_color_hex(0xFFFFFF), LV_PART_MAIN); + lv_obj_set_style_border_color(id(card_jordyn_homework), lv_color_hex(0xEEEEEE), LV_PART_MAIN); + lv_obj_set_style_border_width(id(card_jordyn_homework), 2, LV_PART_MAIN); + lv_label_set_text(id(check_jordyn_homework), ""); + } + if (id(jordyn_feed_dog).state) { + lv_obj_set_style_bg_color(id(card_jordyn_feed_dog), lv_color_hex(0xF0FFF4), LV_PART_MAIN); + lv_obj_set_style_border_color(id(card_jordyn_feed_dog), lv_color_hex(0x6BCB77), LV_PART_MAIN); + lv_obj_set_style_border_width(id(card_jordyn_feed_dog), 3, LV_PART_MAIN); + lv_label_set_text(id(check_jordyn_feed_dog), "\u2705"); + } else { + lv_obj_set_style_bg_color(id(card_jordyn_feed_dog), lv_color_hex(0xFFFFFF), LV_PART_MAIN); + lv_obj_set_style_border_color(id(card_jordyn_feed_dog), lv_color_hex(0xEEEEEE), LV_PART_MAIN); + lv_obj_set_style_border_width(id(card_jordyn_feed_dog), 2, LV_PART_MAIN); + lv_label_set_text(id(check_jordyn_feed_dog), ""); + } + + + - id: reset_declan_chores + mode: single + then: + - lambda: |- + id(declan_make_bed).turn_off(); + id(declan_brush_teeth).turn_off(); + id(declan_set_table).turn_off(); + id(declan_take_out_trash).turn_off(); + - script.execute: update_declan_ui + + + - id: update_declan_ui + mode: single + then: + - lambda: |- + int done = (id(declan_make_bed).state ? 1 : 0) + (id(declan_brush_teeth).state ? 1 : 0) + (id(declan_set_table).state ? 1 : 0) + (id(declan_take_out_trash).state ? 1 : 0); + int total = 4; + char buf[24]; + + // Progress bar + label + lv_bar_set_value(id(progress_bar_declan), done, LV_ANIM_ON); + snprintf(buf, sizeof(buf), "%d / 4", done); + lv_label_set_text(id(progress_label_declan), buf); + + // All-done message in sidebar + lv_label_set_text(id(all_done_label_declan), done == total ? "\U0001F389 All done!" : ""); + + // Home button colour + if (done == total) { + lv_obj_set_style_bg_opa(id(home_btn_declan), LV_OPA_COVER, LV_PART_MAIN); + lv_obj_set_style_bg_color(id(home_btn_declan), lv_color_hex(0x6BCB77), LV_PART_MAIN); + lv_obj_set_style_border_width(id(home_btn_declan), 0, LV_PART_MAIN); + lv_label_set_text(id(home_status_declan), "\u2713 all done!"); + lv_obj_set_style_text_color(id(home_status_declan), lv_color_hex(0xFFFFFF), LV_PART_MAIN); + } else { + lv_obj_set_style_bg_opa(id(home_btn_declan), LV_OPA_TRANSP, LV_PART_MAIN); + lv_obj_set_style_border_color(id(home_btn_declan), lv_color_hex(0xFF4757), LV_PART_MAIN); + lv_obj_set_style_border_width(id(home_btn_declan), 5, LV_PART_MAIN); + snprintf(buf, sizeof(buf), "%d left", total - done); + lv_label_set_text(id(home_status_declan), buf); + lv_obj_set_style_text_color(id(home_status_declan), lv_color_hex(0xFF4757), LV_PART_MAIN); + } + + // Card colours + if (id(declan_make_bed).state) { + lv_obj_set_style_bg_color(id(card_declan_make_bed), lv_color_hex(0xF0FFF4), LV_PART_MAIN); + lv_obj_set_style_border_color(id(card_declan_make_bed), lv_color_hex(0x6BCB77), LV_PART_MAIN); + lv_obj_set_style_border_width(id(card_declan_make_bed), 3, LV_PART_MAIN); + lv_label_set_text(id(check_declan_make_bed), "\u2705"); + } else { + lv_obj_set_style_bg_color(id(card_declan_make_bed), lv_color_hex(0xFFFFFF), LV_PART_MAIN); + lv_obj_set_style_border_color(id(card_declan_make_bed), lv_color_hex(0xEEEEEE), LV_PART_MAIN); + lv_obj_set_style_border_width(id(card_declan_make_bed), 2, LV_PART_MAIN); + lv_label_set_text(id(check_declan_make_bed), ""); + } + if (id(declan_brush_teeth).state) { + lv_obj_set_style_bg_color(id(card_declan_brush_teeth), lv_color_hex(0xF0FFF4), LV_PART_MAIN); + lv_obj_set_style_border_color(id(card_declan_brush_teeth), lv_color_hex(0x6BCB77), LV_PART_MAIN); + lv_obj_set_style_border_width(id(card_declan_brush_teeth), 3, LV_PART_MAIN); + lv_label_set_text(id(check_declan_brush_teeth), "\u2705"); + } else { + lv_obj_set_style_bg_color(id(card_declan_brush_teeth), lv_color_hex(0xFFFFFF), LV_PART_MAIN); + lv_obj_set_style_border_color(id(card_declan_brush_teeth), lv_color_hex(0xEEEEEE), LV_PART_MAIN); + lv_obj_set_style_border_width(id(card_declan_brush_teeth), 2, LV_PART_MAIN); + lv_label_set_text(id(check_declan_brush_teeth), ""); + } + if (id(declan_set_table).state) { + lv_obj_set_style_bg_color(id(card_declan_set_table), lv_color_hex(0xF0FFF4), LV_PART_MAIN); + lv_obj_set_style_border_color(id(card_declan_set_table), lv_color_hex(0x6BCB77), LV_PART_MAIN); + lv_obj_set_style_border_width(id(card_declan_set_table), 3, LV_PART_MAIN); + lv_label_set_text(id(check_declan_set_table), "\u2705"); + } else { + lv_obj_set_style_bg_color(id(card_declan_set_table), lv_color_hex(0xFFFFFF), LV_PART_MAIN); + lv_obj_set_style_border_color(id(card_declan_set_table), lv_color_hex(0xEEEEEE), LV_PART_MAIN); + lv_obj_set_style_border_width(id(card_declan_set_table), 2, LV_PART_MAIN); + lv_label_set_text(id(check_declan_set_table), ""); + } + if (id(declan_take_out_trash).state) { + lv_obj_set_style_bg_color(id(card_declan_take_out_trash), lv_color_hex(0xF0FFF4), LV_PART_MAIN); + lv_obj_set_style_border_color(id(card_declan_take_out_trash), lv_color_hex(0x6BCB77), LV_PART_MAIN); + lv_obj_set_style_border_width(id(card_declan_take_out_trash), 3, LV_PART_MAIN); + lv_label_set_text(id(check_declan_take_out_trash), "\u2705"); + } else { + lv_obj_set_style_bg_color(id(card_declan_take_out_trash), lv_color_hex(0xFFFFFF), LV_PART_MAIN); + lv_obj_set_style_border_color(id(card_declan_take_out_trash), lv_color_hex(0xEEEEEE), LV_PART_MAIN); + lv_obj_set_style_border_width(id(card_declan_take_out_trash), 2, LV_PART_MAIN); + lv_label_set_text(id(check_declan_take_out_trash), ""); + } + + + - id: reset_chloe_chores + mode: single + then: + - lambda: |- + id(chloe_make_bed).turn_off(); + id(chloe_brush_teeth).turn_off(); + id(chloe_water_plants).turn_off(); + id(chloe_homework).turn_off(); + id(chloe_tidy_room).turn_off(); + id(chloe_practice_piano).turn_off(); + - script.execute: update_chloe_ui + + + - id: update_chloe_ui + mode: single + then: + - lambda: |- + int done = (id(chloe_make_bed).state ? 1 : 0) + (id(chloe_brush_teeth).state ? 1 : 0) + (id(chloe_water_plants).state ? 1 : 0) + (id(chloe_homework).state ? 1 : 0) + (id(chloe_tidy_room).state ? 1 : 0) + (id(chloe_practice_piano).state ? 1 : 0); + int total = 6; + char buf[24]; + + // Progress bar + label + lv_bar_set_value(id(progress_bar_chloe), done, LV_ANIM_ON); + snprintf(buf, sizeof(buf), "%d / 6", done); + lv_label_set_text(id(progress_label_chloe), buf); + + // All-done message in sidebar + lv_label_set_text(id(all_done_label_chloe), done == total ? "\U0001F389 All done!" : ""); + + // Home button colour + if (done == total) { + lv_obj_set_style_bg_opa(id(home_btn_chloe), LV_OPA_COVER, LV_PART_MAIN); + lv_obj_set_style_bg_color(id(home_btn_chloe), lv_color_hex(0x6BCB77), LV_PART_MAIN); + lv_obj_set_style_border_width(id(home_btn_chloe), 0, LV_PART_MAIN); + lv_label_set_text(id(home_status_chloe), "\u2713 all done!"); + lv_obj_set_style_text_color(id(home_status_chloe), lv_color_hex(0xFFFFFF), LV_PART_MAIN); + } else { + lv_obj_set_style_bg_opa(id(home_btn_chloe), LV_OPA_TRANSP, LV_PART_MAIN); + lv_obj_set_style_border_color(id(home_btn_chloe), lv_color_hex(0xFF4757), LV_PART_MAIN); + lv_obj_set_style_border_width(id(home_btn_chloe), 5, LV_PART_MAIN); + snprintf(buf, sizeof(buf), "%d left", total - done); + lv_label_set_text(id(home_status_chloe), buf); + lv_obj_set_style_text_color(id(home_status_chloe), lv_color_hex(0xFF4757), LV_PART_MAIN); + } + + // Card colours + if (id(chloe_make_bed).state) { + lv_obj_set_style_bg_color(id(card_chloe_make_bed), lv_color_hex(0xF0FFF4), LV_PART_MAIN); + lv_obj_set_style_border_color(id(card_chloe_make_bed), lv_color_hex(0x6BCB77), LV_PART_MAIN); + lv_obj_set_style_border_width(id(card_chloe_make_bed), 3, LV_PART_MAIN); + lv_label_set_text(id(check_chloe_make_bed), "\u2705"); + } else { + lv_obj_set_style_bg_color(id(card_chloe_make_bed), lv_color_hex(0xFFFFFF), LV_PART_MAIN); + lv_obj_set_style_border_color(id(card_chloe_make_bed), lv_color_hex(0xEEEEEE), LV_PART_MAIN); + lv_obj_set_style_border_width(id(card_chloe_make_bed), 2, LV_PART_MAIN); + lv_label_set_text(id(check_chloe_make_bed), ""); + } + if (id(chloe_brush_teeth).state) { + lv_obj_set_style_bg_color(id(card_chloe_brush_teeth), lv_color_hex(0xF0FFF4), LV_PART_MAIN); + lv_obj_set_style_border_color(id(card_chloe_brush_teeth), lv_color_hex(0x6BCB77), LV_PART_MAIN); + lv_obj_set_style_border_width(id(card_chloe_brush_teeth), 3, LV_PART_MAIN); + lv_label_set_text(id(check_chloe_brush_teeth), "\u2705"); + } else { + lv_obj_set_style_bg_color(id(card_chloe_brush_teeth), lv_color_hex(0xFFFFFF), LV_PART_MAIN); + lv_obj_set_style_border_color(id(card_chloe_brush_teeth), lv_color_hex(0xEEEEEE), LV_PART_MAIN); + lv_obj_set_style_border_width(id(card_chloe_brush_teeth), 2, LV_PART_MAIN); + lv_label_set_text(id(check_chloe_brush_teeth), ""); + } + if (id(chloe_water_plants).state) { + lv_obj_set_style_bg_color(id(card_chloe_water_plants), lv_color_hex(0xF0FFF4), LV_PART_MAIN); + lv_obj_set_style_border_color(id(card_chloe_water_plants), lv_color_hex(0x6BCB77), LV_PART_MAIN); + lv_obj_set_style_border_width(id(card_chloe_water_plants), 3, LV_PART_MAIN); + lv_label_set_text(id(check_chloe_water_plants), "\u2705"); + } else { + lv_obj_set_style_bg_color(id(card_chloe_water_plants), lv_color_hex(0xFFFFFF), LV_PART_MAIN); + lv_obj_set_style_border_color(id(card_chloe_water_plants), lv_color_hex(0xEEEEEE), LV_PART_MAIN); + lv_obj_set_style_border_width(id(card_chloe_water_plants), 2, LV_PART_MAIN); + lv_label_set_text(id(check_chloe_water_plants), ""); + } + if (id(chloe_homework).state) { + lv_obj_set_style_bg_color(id(card_chloe_homework), lv_color_hex(0xF0FFF4), LV_PART_MAIN); + lv_obj_set_style_border_color(id(card_chloe_homework), lv_color_hex(0x6BCB77), LV_PART_MAIN); + lv_obj_set_style_border_width(id(card_chloe_homework), 3, LV_PART_MAIN); + lv_label_set_text(id(check_chloe_homework), "\u2705"); + } else { + lv_obj_set_style_bg_color(id(card_chloe_homework), lv_color_hex(0xFFFFFF), LV_PART_MAIN); + lv_obj_set_style_border_color(id(card_chloe_homework), lv_color_hex(0xEEEEEE), LV_PART_MAIN); + lv_obj_set_style_border_width(id(card_chloe_homework), 2, LV_PART_MAIN); + lv_label_set_text(id(check_chloe_homework), ""); + } + if (id(chloe_tidy_room).state) { + lv_obj_set_style_bg_color(id(card_chloe_tidy_room), lv_color_hex(0xF0FFF4), LV_PART_MAIN); + lv_obj_set_style_border_color(id(card_chloe_tidy_room), lv_color_hex(0x6BCB77), LV_PART_MAIN); + lv_obj_set_style_border_width(id(card_chloe_tidy_room), 3, LV_PART_MAIN); + lv_label_set_text(id(check_chloe_tidy_room), "\u2705"); + } else { + lv_obj_set_style_bg_color(id(card_chloe_tidy_room), lv_color_hex(0xFFFFFF), LV_PART_MAIN); + lv_obj_set_style_border_color(id(card_chloe_tidy_room), lv_color_hex(0xEEEEEE), LV_PART_MAIN); + lv_obj_set_style_border_width(id(card_chloe_tidy_room), 2, LV_PART_MAIN); + lv_label_set_text(id(check_chloe_tidy_room), ""); + } + if (id(chloe_practice_piano).state) { + lv_obj_set_style_bg_color(id(card_chloe_practice_piano), lv_color_hex(0xF0FFF4), LV_PART_MAIN); + lv_obj_set_style_border_color(id(card_chloe_practice_piano), lv_color_hex(0x6BCB77), LV_PART_MAIN); + lv_obj_set_style_border_width(id(card_chloe_practice_piano), 3, LV_PART_MAIN); + lv_label_set_text(id(check_chloe_practice_piano), "\u2705"); + } else { + lv_obj_set_style_bg_color(id(card_chloe_practice_piano), lv_color_hex(0xFFFFFF), LV_PART_MAIN); + lv_obj_set_style_border_color(id(card_chloe_practice_piano), lv_color_hex(0xEEEEEE), LV_PART_MAIN); + lv_obj_set_style_border_width(id(card_chloe_practice_piano), 2, LV_PART_MAIN); + lv_label_set_text(id(check_chloe_practice_piano), ""); + } + + + +# ── LVGL UI ─────────────────────────────────────────────────────────────────── +lvgl: + displays: + - main_display + touchscreens: + - touch + theme: + btn: + radius: 20 + border_width: 0 + pages: + - id: page_home + bg_color: 0xFFF9F0 + widgets: + - label: + x: 0 + y: 36 + width: 800 + align: TOP_MID + text: "Chore Tracker" + text_font: font_title + text_color: 0x2D3436 + - label: + x: 0 + y: 90 + width: 800 + align: TOP_MID + text: "Tap a name to check off chores" + text_font: font_small + text_color: 0xB2BEC3 + - btn: + x: 640 + y: 424 + width: 148 + height: 40 + bg_color: 0xFF4757 + radius: 12 + on_click: + then: + - script.execute: reset_jordyn_chores + - script.execute: reset_declan_chores + - script.execute: reset_chloe_chores + widgets: + - label: + align: CENTER + text: "↺ Reset All" + text_color: 0xFFFFFF + text_font: font_tiny + - btn: + id: home_btn_jordyn + x: 80 + y: 130 + width: 200 + height: 210 + bg_color: 0xFFFFFF + bg_opa: TRANSP + border_color: 0xFF4757 + border_width: 5 + radius: 24 + on_click: + then: + - lvgl.page.show: page_jordyn + widgets: + - label: + align: CENTER + y: -45 + text: "😺" + text_font: font_title + - label: + align: CENTER + y: 22 + text: "Jordyn" + text_font: font_name + text_color: 0x2D3436 + - label: + id: home_status_jordyn + align: CENTER + y: 66 + text: "not done" + text_font: font_tiny + text_color: 0xFF4757 + + - btn: + id: home_btn_declan + x: 300 + y: 130 + width: 200 + height: 210 + bg_color: 0xFFFFFF + bg_opa: TRANSP + border_color: 0xFF4757 + border_width: 5 + radius: 24 + on_click: + then: + - lvgl.page.show: page_declan + widgets: + - label: + align: CENTER + y: -45 + text: "🤓" + text_font: font_title + - label: + align: CENTER + y: 22 + text: "Declan" + text_font: font_name + text_color: 0x2D3436 + - label: + id: home_status_declan + align: CENTER + y: 66 + text: "not done" + text_font: font_tiny + text_color: 0xFF4757 + + - btn: + id: home_btn_chloe + x: 520 + y: 130 + width: 200 + height: 210 + bg_color: 0xFFFFFF + bg_opa: TRANSP + border_color: 0xFF4757 + border_width: 5 + radius: 24 + on_click: + then: + - lvgl.page.show: page_chloe + widgets: + - label: + align: CENTER + y: -45 + text: "🌝" + text_font: font_title + - label: + align: CENTER + y: 22 + text: "Chloe" + text_font: font_name + text_color: 0x2D3436 + - label: + id: home_status_chloe + align: CENTER + y: 66 + text: "not done" + text_font: font_tiny + text_color: 0xFF4757 + + + - id: page_jordyn + bg_color: 0xFFF9F0 + widgets: + + # ── Sidebar ────────────────────────────────────────────────────────── + - obj: + x: 0 + y: 0 + width: 172 + height: 480 + bg_color: 0x4D96FF + border_width: 0 + radius: 0 + pad_all: 0 + widgets: + - label: + x: 0 + y: 22 + width: 172 + align: TOP_MID + text: "😺" + text_font: font_title + - label: + x: 0 + y: 76 + width: 172 + align: TOP_MID + text: "Jordyn" + text_font: font_name + text_color: 0xFFFFFF + - bar: + id: progress_bar_jordyn + x: 14 + y: 132 + width: 144 + height: 16 + bg_color: 0x2A6FCC + indicator: + bg_color: 0xFFFFFF + value: 0 + min_value: 0 + max_value: 5 + - label: + id: progress_label_jordyn + x: 0 + y: 156 + width: 172 + align: TOP_MID + text: "0 / 5" + text_font: font_small + text_color: 0xDDEEFF + - label: + id: all_done_label_jordyn + x: 0 + y: 192 + width: 172 + align: TOP_MID + text: "" + text_font: font_small + text_color: 0xFFFFFF + - btn: + x: 14 + y: 364 + width: 144 + height: 44 + bg_color: 0xFF4757 + radius: 14 + on_click: + then: + - script.execute: reset_jordyn_chores + widgets: + - label: + align: CENTER + text: "↺ Reset" + text_color: 0xFFFFFF + text_font: font_small + - btn: + x: 14 + y: 420 + width: 144 + height: 44 + bg_color: 0x2A6FCC + radius: 14 + on_click: + then: + - lvgl.page.show: page_home + widgets: + - label: + align: CENTER + text: "◀ Home" + text_color: 0xFFFFFF + text_font: font_small + + # ── Chore cards ─────────────────────────────────────────────────────── + - btn: + id: card_jordyn_make_bed + x: 182 + y: 10 + width: 196 + height: 138 + bg_color: 0xFFFFFF + border_color: 0xEEEEEE + border_width: 2 + radius: 20 + shadow_color: 0xCCCCCC + shadow_width: 4 + shadow_ofs_y: 3 + on_click: + then: + - switch.toggle: jordyn_make_bed + widgets: + - label: + id: check_jordyn_make_bed + align: TOP_RIGHT + x: -10 + y: 8 + text: "" + text_font: font_med + - label: + align: CENTER + y: -34 + text: "🛏️" + text_font: font_med + - label: + align: CENTER + y: 17 + text: "Make Bed" + text_font: font_small + text_color: 0x2D3436 + + - btn: + id: card_jordyn_brush_teeth + x: 388 + y: 10 + width: 196 + height: 138 + bg_color: 0xFFFFFF + border_color: 0xEEEEEE + border_width: 2 + radius: 20 + shadow_color: 0xCCCCCC + shadow_width: 4 + shadow_ofs_y: 3 + on_click: + then: + - switch.toggle: jordyn_brush_teeth + widgets: + - label: + id: check_jordyn_brush_teeth + align: TOP_RIGHT + x: -10 + y: 8 + text: "" + text_font: font_med + - label: + align: CENTER + y: -34 + text: "🦷" + text_font: font_med + - label: + align: CENTER + y: 17 + text: "Brush Teeth" + text_font: font_small + text_color: 0x2D3436 + + - btn: + id: card_jordyn_tidy_room + x: 594 + y: 10 + width: 196 + height: 138 + bg_color: 0xFFFFFF + border_color: 0xEEEEEE + border_width: 2 + radius: 20 + shadow_color: 0xCCCCCC + shadow_width: 4 + shadow_ofs_y: 3 + on_click: + then: + - switch.toggle: jordyn_tidy_room + widgets: + - label: + id: check_jordyn_tidy_room + align: TOP_RIGHT + x: -10 + y: 8 + text: "" + text_font: font_med + - label: + align: CENTER + y: -34 + text: "🧹" + text_font: font_med + - label: + align: CENTER + y: 17 + text: "Tidy Room" + text_font: font_small + text_color: 0x2D3436 + + - btn: + id: card_jordyn_homework + x: 182 + y: 158 + width: 196 + height: 138 + bg_color: 0xFFFFFF + border_color: 0xEEEEEE + border_width: 2 + radius: 20 + shadow_color: 0xCCCCCC + shadow_width: 4 + shadow_ofs_y: 3 + on_click: + then: + - switch.toggle: jordyn_homework + widgets: + - label: + id: check_jordyn_homework + align: TOP_RIGHT + x: -10 + y: 8 + text: "" + text_font: font_med + - label: + align: CENTER + y: -34 + text: "📚" + text_font: font_med + - label: + align: CENTER + y: 17 + text: "Homework" + text_font: font_small + text_color: 0x2D3436 + + - btn: + id: card_jordyn_feed_dog + x: 388 + y: 158 + width: 196 + height: 138 + bg_color: 0xFFFFFF + border_color: 0xEEEEEE + border_width: 2 + radius: 20 + shadow_color: 0xCCCCCC + shadow_width: 4 + shadow_ofs_y: 3 + on_click: + then: + - switch.toggle: jordyn_feed_dog + widgets: + - label: + id: check_jordyn_feed_dog + align: TOP_RIGHT + x: -10 + y: 8 + text: "" + text_font: font_med + - label: + align: CENTER + y: -34 + text: "🐾" + text_font: font_med + - label: + align: CENTER + y: 17 + text: "Feed Dog" + text_font: font_small + text_color: 0x2D3436 + + + - id: page_declan + bg_color: 0xFFF9F0 + widgets: + + # ── Sidebar ────────────────────────────────────────────────────────── + - obj: + x: 0 + y: 0 + width: 172 + height: 480 + bg_color: 0xC77DFF + border_width: 0 + radius: 0 + pad_all: 0 + widgets: + - label: + x: 0 + y: 22 + width: 172 + align: TOP_MID + text: "🤓" + text_font: font_title + - label: + x: 0 + y: 76 + width: 172 + align: TOP_MID + text: "Declan" + text_font: font_name + text_color: 0xFFFFFF + - bar: + id: progress_bar_declan + x: 14 + y: 132 + width: 144 + height: 16 + bg_color: 0x8B42CC + indicator: + bg_color: 0xFFFFFF + value: 0 + min_value: 0 + max_value: 4 + - label: + id: progress_label_declan + x: 0 + y: 156 + width: 172 + align: TOP_MID + text: "0 / 4" + text_font: font_small + text_color: 0xDDEEFF + - label: + id: all_done_label_declan + x: 0 + y: 192 + width: 172 + align: TOP_MID + text: "" + text_font: font_small + text_color: 0xFFFFFF + - btn: + x: 14 + y: 364 + width: 144 + height: 44 + bg_color: 0xFF4757 + radius: 14 + on_click: + then: + - script.execute: reset_declan_chores + widgets: + - label: + align: CENTER + text: "↺ Reset" + text_color: 0xFFFFFF + text_font: font_small + - btn: + x: 14 + y: 420 + width: 144 + height: 44 + bg_color: 0x8B42CC + radius: 14 + on_click: + then: + - lvgl.page.show: page_home + widgets: + - label: + align: CENTER + text: "◀ Home" + text_color: 0xFFFFFF + text_font: font_small + + # ── Chore cards ─────────────────────────────────────────────────────── + - btn: + id: card_declan_make_bed + x: 182 + y: 10 + width: 196 + height: 138 + bg_color: 0xFFFFFF + border_color: 0xEEEEEE + border_width: 2 + radius: 20 + shadow_color: 0xCCCCCC + shadow_width: 4 + shadow_ofs_y: 3 + on_click: + then: + - switch.toggle: declan_make_bed + widgets: + - label: + id: check_declan_make_bed + align: TOP_RIGHT + x: -10 + y: 8 + text: "" + text_font: font_med + - label: + align: CENTER + y: -34 + text: "🛏️" + text_font: font_med + - label: + align: CENTER + y: 17 + text: "Make Bed" + text_font: font_small + text_color: 0x2D3436 + + - btn: + id: card_declan_brush_teeth + x: 388 + y: 10 + width: 196 + height: 138 + bg_color: 0xFFFFFF + border_color: 0xEEEEEE + border_width: 2 + radius: 20 + shadow_color: 0xCCCCCC + shadow_width: 4 + shadow_ofs_y: 3 + on_click: + then: + - switch.toggle: declan_brush_teeth + widgets: + - label: + id: check_declan_brush_teeth + align: TOP_RIGHT + x: -10 + y: 8 + text: "" + text_font: font_med + - label: + align: CENTER + y: -34 + text: "🦷" + text_font: font_med + - label: + align: CENTER + y: 17 + text: "Brush Teeth" + text_font: font_small + text_color: 0x2D3436 + + - btn: + id: card_declan_set_table + x: 594 + y: 10 + width: 196 + height: 138 + bg_color: 0xFFFFFF + border_color: 0xEEEEEE + border_width: 2 + radius: 20 + shadow_color: 0xCCCCCC + shadow_width: 4 + shadow_ofs_y: 3 + on_click: + then: + - switch.toggle: declan_set_table + widgets: + - label: + id: check_declan_set_table + align: TOP_RIGHT + x: -10 + y: 8 + text: "" + text_font: font_med + - label: + align: CENTER + y: -34 + text: "🍽️" + text_font: font_med + - label: + align: CENTER + y: 17 + text: "Set Table" + text_font: font_small + text_color: 0x2D3436 + + - btn: + id: card_declan_take_out_trash + x: 182 + y: 158 + width: 196 + height: 138 + bg_color: 0xFFFFFF + border_color: 0xEEEEEE + border_width: 2 + radius: 20 + shadow_color: 0xCCCCCC + shadow_width: 4 + shadow_ofs_y: 3 + on_click: + then: + - switch.toggle: declan_take_out_trash + widgets: + - label: + id: check_declan_take_out_trash + align: TOP_RIGHT + x: -10 + y: 8 + text: "" + text_font: font_med + - label: + align: CENTER + y: -34 + text: "🗑️" + text_font: font_med + - label: + align: CENTER + y: 17 + text: "Take Out Trash" + text_font: font_small + text_color: 0x2D3436 + + + - id: page_chloe + bg_color: 0xFFF9F0 + widgets: + + # ── Sidebar ────────────────────────────────────────────────────────── + - obj: + x: 0 + y: 0 + width: 172 + height: 480 + bg_color: 0xFF6B9D + border_width: 0 + radius: 0 + pad_all: 0 + widgets: + - label: + x: 0 + y: 22 + width: 172 + align: TOP_MID + text: "🌝" + text_font: font_title + - label: + x: 0 + y: 76 + width: 172 + align: TOP_MID + text: "Chloe" + text_font: font_name + text_color: 0xFFFFFF + - bar: + id: progress_bar_chloe + x: 14 + y: 132 + width: 144 + height: 16 + bg_color: 0xCC3A6F + indicator: + bg_color: 0xFFFFFF + value: 0 + min_value: 0 + max_value: 6 + - label: + id: progress_label_chloe + x: 0 + y: 156 + width: 172 + align: TOP_MID + text: "0 / 6" + text_font: font_small + text_color: 0xDDEEFF + - label: + id: all_done_label_chloe + x: 0 + y: 192 + width: 172 + align: TOP_MID + text: "" + text_font: font_small + text_color: 0xFFFFFF + - btn: + x: 14 + y: 364 + width: 144 + height: 44 + bg_color: 0xFF4757 + radius: 14 + on_click: + then: + - script.execute: reset_chloe_chores + widgets: + - label: + align: CENTER + text: "↺ Reset" + text_color: 0xFFFFFF + text_font: font_small + - btn: + x: 14 + y: 420 + width: 144 + height: 44 + bg_color: 0xCC3A6F + radius: 14 + on_click: + then: + - lvgl.page.show: page_home + widgets: + - label: + align: CENTER + text: "◀ Home" + text_color: 0xFFFFFF + text_font: font_small + + # ── Chore cards ─────────────────────────────────────────────────────── + - btn: + id: card_chloe_make_bed + x: 182 + y: 10 + width: 196 + height: 138 + bg_color: 0xFFFFFF + border_color: 0xEEEEEE + border_width: 2 + radius: 20 + shadow_color: 0xCCCCCC + shadow_width: 4 + shadow_ofs_y: 3 + on_click: + then: + - switch.toggle: chloe_make_bed + widgets: + - label: + id: check_chloe_make_bed + align: TOP_RIGHT + x: -10 + y: 8 + text: "" + text_font: font_med + - label: + align: CENTER + y: -34 + text: "🛏️" + text_font: font_med + - label: + align: CENTER + y: 17 + text: "Make Bed" + text_font: font_small + text_color: 0x2D3436 + + - btn: + id: card_chloe_brush_teeth + x: 388 + y: 10 + width: 196 + height: 138 + bg_color: 0xFFFFFF + border_color: 0xEEEEEE + border_width: 2 + radius: 20 + shadow_color: 0xCCCCCC + shadow_width: 4 + shadow_ofs_y: 3 + on_click: + then: + - switch.toggle: chloe_brush_teeth + widgets: + - label: + id: check_chloe_brush_teeth + align: TOP_RIGHT + x: -10 + y: 8 + text: "" + text_font: font_med + - label: + align: CENTER + y: -34 + text: "🦷" + text_font: font_med + - label: + align: CENTER + y: 17 + text: "Brush Teeth" + text_font: font_small + text_color: 0x2D3436 + + - btn: + id: card_chloe_water_plants + x: 594 + y: 10 + width: 196 + height: 138 + bg_color: 0xFFFFFF + border_color: 0xEEEEEE + border_width: 2 + radius: 20 + shadow_color: 0xCCCCCC + shadow_width: 4 + shadow_ofs_y: 3 + on_click: + then: + - switch.toggle: chloe_water_plants + widgets: + - label: + id: check_chloe_water_plants + align: TOP_RIGHT + x: -10 + y: 8 + text: "" + text_font: font_med + - label: + align: CENTER + y: -34 + text: "🌱" + text_font: font_med + - label: + align: CENTER + y: 17 + text: "Water Plants" + text_font: font_small + text_color: 0x2D3436 + + - btn: + id: card_chloe_homework + x: 182 + y: 158 + width: 196 + height: 138 + bg_color: 0xFFFFFF + border_color: 0xEEEEEE + border_width: 2 + radius: 20 + shadow_color: 0xCCCCCC + shadow_width: 4 + shadow_ofs_y: 3 + on_click: + then: + - switch.toggle: chloe_homework + widgets: + - label: + id: check_chloe_homework + align: TOP_RIGHT + x: -10 + y: 8 + text: "" + text_font: font_med + - label: + align: CENTER + y: -34 + text: "📚" + text_font: font_med + - label: + align: CENTER + y: 17 + text: "Homework" + text_font: font_small + text_color: 0x2D3436 + + - btn: + id: card_chloe_tidy_room + x: 388 + y: 158 + width: 196 + height: 138 + bg_color: 0xFFFFFF + border_color: 0xEEEEEE + border_width: 2 + radius: 20 + shadow_color: 0xCCCCCC + shadow_width: 4 + shadow_ofs_y: 3 + on_click: + then: + - switch.toggle: chloe_tidy_room + widgets: + - label: + id: check_chloe_tidy_room + align: TOP_RIGHT + x: -10 + y: 8 + text: "" + text_font: font_med + - label: + align: CENTER + y: -34 + text: "🧹" + text_font: font_med + - label: + align: CENTER + y: 17 + text: "Tidy Room" + text_font: font_small + text_color: 0x2D3436 + + - btn: + id: card_chloe_practice_piano + x: 594 + y: 158 + width: 196 + height: 138 + bg_color: 0xFFFFFF + border_color: 0xEEEEEE + border_width: 2 + radius: 20 + shadow_color: 0xCCCCCC + shadow_width: 4 + shadow_ofs_y: 3 + on_click: + then: + - switch.toggle: chloe_practice_piano + widgets: + - label: + id: check_chloe_practice_piano + align: TOP_RIGHT + x: -10 + y: 8 + text: "" + text_font: font_med + - label: + align: CENTER + y: -34 + text: "🎹" + text_font: font_med + - label: + align: CENTER + y: 17 + text: "Practice Piano" + text_font: font_small + text_color: 0x2D3436 + + diff --git a/esphome/chore-tracker/chores_config.yaml b/esphome/chore-tracker/chores_config.yaml new file mode 100644 index 0000000..8f47785 --- /dev/null +++ b/esphome/chore-tracker/chores_config.yaml @@ -0,0 +1,70 @@ +################################################################################ +# chores_config.yaml — YOUR SINGLE SOURCE OF TRUTH +# +# Edit this file to change kids, chores, and settings. +# Then run: python3 generate.py +################################################################################ + +settings: + device_name: chore-tracker + friendly_name: "Chore Tracker" + wifi_ssid: !secret wifi_iot_ssid + wifi_password: !secret wifi_password + api_key: !secret api_encryption_key + ota_password: !secret ota_password + reset_time: "00:00:00" + reminder_time: "18:00" + notify_service: notify.notify + +# ── KIDS ────────────────────────────────────────────────────────────────────── +# Each kid has their own chore list. +# name, avatar, color, color_dark, and chores are all required. +# +kids: + - name: Jordyn + avatar: "😺" + color: "4D96FF" + color_dark: "2A6FCC" + chores: + - name: Make Bed + icon: "🛏️" + - name: Brush Teeth + icon: "🦷" + - name: Tidy Room + icon: "🧹" + - name: Homework + icon: "📚" + - name: Feed Dog + icon: "🐾" + + - name: Declan + avatar: "🤓" + color: "C77DFF" + color_dark: "8B42CC" + chores: + - name: Make Bed + icon: "🛏️" + - name: Brush Teeth + icon: "🦷" + - name: Set Table + icon: "🍽️" + - name: Take Out Trash + icon: "🗑️" + + - name: Chloe + avatar: "🌝" + color: "FF6B9D" + color_dark: "CC3A6F" + chores: + - name: Make Bed + icon: "🛏️" + - name: Brush Teeth + icon: "🦷" + - name: Water Plants + icon: "🌱" + - name: Homework + icon: "📚" + - name: Tidy Room + icon: "🧹" + - name: Practice Piano + icon: "🎹" \ No newline at end of file diff --git a/esphome/chore-tracker/generate.py b/esphome/chore-tracker/generate.py new file mode 100644 index 0000000..4038820 --- /dev/null +++ b/esphome/chore-tracker/generate.py @@ -0,0 +1,895 @@ +#!/usr/bin/env python3 +""" +generate.py — Chore Tracker Code Generator +========================================== +Reads chores_config.yaml and generates: + • chore-tracker-esphome.yaml (flash to ESP32 via ESPHome) + • chore-tracker-ha.yaml (Home Assistant config) + • chore-tracker-dashboard.yaml (Lovelace dashboard) + +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. + +Usage: + python3 generate.py + python3 generate.py --config my_other_config.yaml + python3 generate.py --out ./output_dir +""" + +import argparse +import sys +from pathlib import Path + +try: + import yaml +except ImportError: + print("PyYAML not found. Install it with: pip install pyyaml") + sys.exit(1) + + +# ───────────────────────────────────────────────────────────────────────────── +# Helpers +# ───────────────────────────────────────────────────────────────────────────── + +def slug(name: str) -> str: + return name.lower().replace(" ", "_").replace("-", "_") + +def kid_slug(kid: dict) -> str: + return slug(kid["name"]) + +def eid(kid: dict, chore: dict) -> str: + return f"{kid_slug(kid)}_{slug(chore['name'])}" + +def ha_switch(kid: dict, chore: dict) -> str: + return f"switch.chore_tracker_{eid(kid, chore)}" + +def get_chores(kid: dict) -> list: + """Each kid must define their own chore list.""" + return kid["chores"] + + +# ───────────────────────────────────────────────────────────────────────────── +# ESPHome generator +# ───────────────────────────────────────────────────────────────────────────── + +def gen_esphome(cfg: dict) -> str: + s = cfg["settings"] + kids = cfg["kids"] + + # ── Switches ────────────────────────────────────────────────────────────── + switch_blocks = [] + for kid in kids: + chores = get_chores(kid) + ks = kid_slug(kid) + switch_blocks.append(f"\n # ── {kid['name']}'s chores ──────────────────────────") + for chore in chores: + e = eid(kid, chore) + switch_blocks.append(f"""\ + - platform: template + name: "{kid['name']} - {chore['name']}" + id: {e} + icon: mdi:checkbox-marked-circle + optimistic: true + restore_mode: RESTORE_DEFAULT_OFF + on_turn_on: + then: + - script.execute: update_{ks}_ui + - homeassistant.service: + service: switch.turn_on + data: + entity_id: switch.chore_tracker_{e} + on_turn_off: + then: + - script.execute: update_{ks}_ui + - homeassistant.service: + service: switch.turn_off + data: + entity_id: switch.chore_tracker_{e} +""") + + # ── Sensors (chores-done count only) ────────────────────────────────────── + sensor_blocks = [] + for kid in kids: + chores = get_chores(kid) + ks = kid_slug(kid) + done_lines = "\n".join( + f" if (id({eid(kid, c)}).state) done++;" for c in chores + ) + sensor_blocks.append(f"""\ + - platform: template + name: "{kid['name']} Chores Done" + id: {ks}_chores_done + accuracy_decimals: 0 + update_interval: 2s + lambda: |- + int done = 0; +{done_lines} + return done; + +""") + + # ── Midnight reset ──────────────────────────────────────────────────────── + reset_calls = "\n".join( + f" id(reset_{kid_slug(k)}_chores).execute();" + for k in kids + ) + + lvgl_pages = _gen_lvgl_pages(kids) + scripts = _gen_scripts(kids) + + return f"""\ +################################################################################ +# chore-tracker-esphome.yaml (AUTO-GENERATED — edit chores_config.yaml) +# Kids: {", ".join(k["name"] for k in kids)} +# +# HOME SCREEN BEHAVIOUR: +# Red outline = chores incomplete +# Solid green = all chores done ✓ +# Resets to red automatically at midnight +################################################################################ + +esphome: + name: {s["device_name"]} + friendly_name: "{s["friendly_name"]}" + +esp32: + board: esp32s3box + framework: + type: esp-idf + +wifi: + ssid: {s["wifi_ssid"]} + password: {s["wifi_password"]} + ap: + ssid: "{s['friendly_name']} Hotspot" + password: "choretracker" + +captive_portal: +logger: + level: INFO + +api: + encryption: + key: {s["api_key"]} + +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: + - id: i2c_touch + sda: GPIO19 + scl: GPIO20 + frequency: 400kHz + +output: + - platform: ledc + pin: GPIO17 + id: backlight_output + +light: + - platform: monochromatic + output: backlight_output + name: "Display Backlight" + id: backlight + restore_mode: ALWAYS_ON + +# ── Midnight reset ──────────────────────────────────────────────────────────── +time: + - platform: homeassistant + id: ha_time + on_time: + - hours: 0 + minutes: 0 + seconds: 0 + then: + - lambda: |- +{reset_calls} + - lvgl.page.show: page_home + +fonts: + - file: "gfonts://Nunito:wght@900" + id: font_title + size: 40 + - file: "gfonts://Nunito:wght@800" + id: font_name + size: 30 + - file: "gfonts://Nunito:wght@700" + id: font_med + size: 22 + - file: "gfonts://Nunito:wght@600" + id: font_small + size: 17 + - file: "gfonts://Nunito:wght@600" + id: font_tiny + size: 13 + +# ── Switches — one per chore per kid, synced to HA ─────────────────────────── +switch: +{"".join(switch_blocks)} + +# ── Sensors — reported to HA ───────────────────────────────────────────────── +sensor: +{"".join(sensor_blocks)} + +# ── Scripts — reset + UI update ─────────────────────────────────────────────── +script: +{scripts} + +# ── LVGL UI ─────────────────────────────────────────────────────────────────── +lvgl: + displays: + - main_display + touchscreens: + - touch + theme: + btn: + radius: 20 + border_width: 0 + pages: +{lvgl_pages} +""" + + +# ───────────────────────────────────────────────────────────────────────────── +# LVGL page builder +# ───────────────────────────────────────────────────────────────────────────── + +def _gen_lvgl_pages(kids: list) -> str: + pages = [] + + # ── HOME PAGE ───────────────────────────────────────────────────────────── + n = len(kids) + btn_w = min(200, max(140, (760 - 20 * (n - 1)) // n)) + total_w = btn_w * n + 20 * (n - 1) + start_x = (800 - total_w) // 2 + btn_y = 130 + btn_h = 210 + + reset_all_calls = "\n".join( + f" - script.execute: reset_{kid_slug(k)}_chores" + for k in kids + ) + + home_btns = "" + for i, kid in enumerate(kids): + x = start_x + i * (btn_w + 20) + ks = kid_slug(kid) + home_btns += f"""\ + - btn: + 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 + radius: 24 + on_click: + then: + - lvgl.page.show: page_{ks} + widgets: + - label: + align: CENTER + y: -45 + text: "{kid['avatar']}" + text_font: font_title + - label: + align: CENTER + y: 22 + text: "{kid['name']}" + text_font: font_name + text_color: 0x2D3436 + - label: + id: home_status_{ks} + align: CENTER + y: 66 + text: "not done" + text_font: font_tiny + text_color: 0xFF4757 + +""" + + pages.append(f"""\ + - id: page_home + bg_color: 0xFFF9F0 + widgets: + - label: + x: 0 + y: 36 + width: 800 + align: TOP_MID + text: "Chore Tracker" + text_font: font_title + text_color: 0x2D3436 + - label: + x: 0 + y: 90 + width: 800 + align: TOP_MID + text: "Tap a name to check off chores" + text_font: font_small + text_color: 0xB2BEC3 + - btn: + x: 640 + y: 424 + width: 148 + height: 40 + bg_color: 0xFF4757 + radius: 12 + on_click: + then: +{reset_all_calls} + widgets: + - label: + align: CENTER + text: "↺ Reset All" + text_color: 0xFFFFFF + text_font: font_tiny +{home_btns}""") + + # ── PER-KID CHORE PAGES ─────────────────────────────────────────────────── + for kid in kids: + chores = get_chores(kid) + ks = kid_slug(kid) + n_chores = len(chores) + + cols = 3 + rows = (n_chores + cols - 1) // cols + sidebar_w = 172 + gap = 10 + content_w = 800 - sidebar_w - gap * 2 + card_w = (content_w - gap * (cols - 1)) // cols + card_h = min(138, (480 - 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) + e = eid(kid, chore) + icon_y = -(card_h // 4) + label_y = card_h // 8 + card_widgets += f"""\ + - btn: + id: card_{e} + x: {x} + y: {y} + width: {card_w} + height: {card_h} + bg_color: 0xFFFFFF + border_color: 0xEEEEEE + border_width: 2 + radius: 20 + shadow_color: 0xCCCCCC + shadow_width: 4 + shadow_ofs_y: 3 + on_click: + then: + - switch.toggle: {e} + widgets: + - label: + id: check_{e} + align: TOP_RIGHT + x: -10 + y: 8 + text: "" + text_font: font_med + - label: + align: CENTER + y: {icon_y} + text: "{chore['icon']}" + text_font: font_med + - label: + align: CENTER + y: {label_y} + text: "{chore['name']}" + text_font: font_small + text_color: 0x2D3436 + +""" + + pages.append(f"""\ + - id: page_{ks} + bg_color: 0xFFF9F0 + widgets: + + # ── Sidebar ────────────────────────────────────────────────────────── + - obj: + x: 0 + y: 0 + width: {sidebar_w} + height: 480 + bg_color: 0x{kid['color']} + border_width: 0 + radius: 0 + pad_all: 0 + widgets: + - label: + x: 0 + y: 22 + width: {sidebar_w} + align: TOP_MID + text: "{kid['avatar']}" + text_font: font_title + - label: + x: 0 + y: 76 + width: {sidebar_w} + align: TOP_MID + text: "{kid['name']}" + text_font: font_name + text_color: 0xFFFFFF + - bar: + id: progress_bar_{ks} + x: 14 + y: 132 + width: {sidebar_w - 28} + height: 16 + bg_color: 0x{kid['color_dark']} + indicator: + bg_color: 0xFFFFFF + value: 0 + min_value: 0 + max_value: {n_chores} + - label: + id: progress_label_{ks} + x: 0 + y: 156 + width: {sidebar_w} + align: TOP_MID + text: "0 / {n_chores}" + text_font: font_small + text_color: 0xDDEEFF + - label: + id: all_done_label_{ks} + x: 0 + y: 192 + width: {sidebar_w} + align: TOP_MID + text: "" + text_font: font_small + text_color: 0xFFFFFF + - btn: + x: 14 + y: 364 + width: {sidebar_w - 28} + height: 44 + bg_color: 0xFF4757 + radius: 14 + on_click: + then: + - script.execute: reset_{ks}_chores + widgets: + - label: + align: CENTER + text: "↺ Reset" + text_color: 0xFFFFFF + text_font: font_small + - btn: + x: 14 + y: 420 + width: {sidebar_w - 28} + height: 44 + bg_color: 0x{kid['color_dark']} + radius: 14 + on_click: + then: + - lvgl.page.show: page_home + widgets: + - label: + align: CENTER + text: "◀ Home" + text_color: 0xFFFFFF + text_font: font_small + + # ── Chore cards ─────────────────────────────────────────────────────── +{card_widgets}""") + + return "\n".join(pages) + + +# ───────────────────────────────────────────────────────────────────────────── +# Script builder +# ───────────────────────────────────────────────────────────────────────────── + +def _gen_scripts(kids: list) -> str: + blocks = [] + for kid in kids: + chores = get_chores(kid) + ks = kid_slug(kid) + n = len(chores) + + card_updates = "" + for chore in chores: + e = eid(kid, chore) + card_updates += f"""\ + if (id({e}).state) {{ + lv_obj_set_style_bg_color(id(card_{e}), lv_color_hex(0xF0FFF4), LV_PART_MAIN); + lv_obj_set_style_border_color(id(card_{e}), lv_color_hex(0x6BCB77), LV_PART_MAIN); + lv_obj_set_style_border_width(id(card_{e}), 3, LV_PART_MAIN); + lv_label_set_text(id(check_{e}), "\\u2705"); + }} else {{ + lv_obj_set_style_bg_color(id(card_{e}), lv_color_hex(0xFFFFFF), LV_PART_MAIN); + lv_obj_set_style_border_color(id(card_{e}), lv_color_hex(0xEEEEEE), LV_PART_MAIN); + lv_obj_set_style_border_width(id(card_{e}), 2, LV_PART_MAIN); + lv_label_set_text(id(check_{e}), ""); + }} +""" + + count_done = " + ".join(f"(id({eid(kid,c)}).state ? 1 : 0)" for c in chores) + reset_lines = "\n".join( + f" id({eid(kid,c)}).turn_off();" for c in chores + ) + + # Reset script + blocks.append(f"""\ + - id: reset_{ks}_chores + mode: single + then: + - lambda: |- +{reset_lines} + - script.execute: update_{ks}_ui + +""") + + # UI update script + blocks.append(f"""\ + - id: update_{ks}_ui + mode: single + then: + - lambda: |- + int done = {count_done}; + int total = {n}; + char buf[24]; + + // Progress bar + label + lv_bar_set_value(id(progress_bar_{ks}), done, LV_ANIM_ON); + snprintf(buf, sizeof(buf), "%d / {n}", done); + 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!" : ""); + + // Home button colour + 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); + lv_obj_set_style_border_width(id(home_btn_{ks}), 0, LV_PART_MAIN); + 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); + 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); + }} + + // Card colours +{card_updates} +""") + + return "\n".join(blocks) + + +# ───────────────────────────────────────────────────────────────────────────── +# Home Assistant YAML generator +# ───────────────────────────────────────────────────────────────────────────── + +def gen_ha(cfg: dict) -> str: + s = cfg["settings"] + kids = cfg["kids"] + + sensor_blocks = [] + for kid in kids: + chores = get_chores(kid) + ks = kid_slug(kid) + n = len(chores) + states_list = ", ".join(f"states('{ha_switch(kid,c)}')" for c in chores) + + sensor_blocks.append(f"""\ + - name: "{kid['name']} Chores Done Today" + unique_id: {ks}_chores_done_today + icon: mdi:check-circle + state: > + {{% set chores = [{states_list}] %}} + {{{{ chores | select('equalto', 'on') | list | count }}}} + + - name: "{kid['name']} All Chores Done" + unique_id: {ks}_all_chores_done + icon: mdi:check-all + state: > + {{{{ states('sensor.{ks}_chores_done_today') | int == {n} }}}} + +""") + + # input_button helpers + input_buttons = "".join(f"""\ + {kid_slug(k)}_reset_chores: + name: "Reset {k['name']}'s Chores" + icon: mdi:restart +""" for k in kids) + input_buttons += """\ + reset_all_chores: + name: "Reset All Chores" + icon: mdi:restart-alert +""" + + # Automations + auto_blocks = [] + + # Per-kid HA reset button + for kid in kids: + ks = kid_slug(kid) + off_calls = "\n".join( + f" - service: switch.turn_off\n target:\n entity_id: {ha_switch(kid, c)}" + for c in get_chores(kid) + ) + auto_blocks.append(f"""\ + - id: {ks}_reset_from_ha + alias: "Chore Tracker — Reset {kid['name']}'s chores from HA" + trigger: + - platform: state + entity_id: input_button.{ks}_reset_chores + action: +{off_calls} + +""") + + # Reset All from HA + all_off_calls = "\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" + trigger: + - platform: state + entity_id: input_button.reset_all_chores + action: +{all_off_calls} + +""") + + # 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!" + trigger: + - platform: state + entity_id: sensor.{ks}_all_chores_done + to: "True" + action: + - service: {s['notify_service']} + data: + title: "🎉 {kid['name']} finished all chores!" + message: "{kid['name']} completed all {n} chores today!" + +""") + + # Evening reminder + reminder_actions = "".join(f"""\ + - if: + condition: template + value_template: "{{{{ states('sensor.{kid_slug(k)}_all_chores_done') != 'True' }}}}" + then: + - service: {s['notify_service']} + data: + 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. +""" for k in kids) + + auto_blocks.append(f"""\ + - id: chore_reminder_evening + alias: "Chore Tracker — Evening reminder" + trigger: + - platform: time + at: "{s['reminder_time']}:00" + action: +{reminder_actions} +""") + + return f"""\ +################################################################################ +# chore-tracker-ha.yaml (AUTO-GENERATED — edit chores_config.yaml) +# 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 +################################################################################ + +input_button: +{input_buttons} +template: + - sensor: +{"".join(sensor_blocks)} +automation: +{"".join(auto_blocks)} +""" + + +# ───────────────────────────────────────────────────────────────────────────── +# Lovelace dashboard generator +# ───────────────────────────────────────────────────────────────────────────── + +def gen_dashboard(cfg: dict) -> str: + kids = cfg["kids"] + + summary_cards = "\n".join(f"""\ + - type: markdown + content: > + ### {kid['avatar']} {kid['name']} + {{{{ + '✅ 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) + + chore_cards = [] + for kid in kids: + chores = get_chores(kid) + ks = kid_slug(kid) + chore_rows = "\n".join(f"""\ + - entity: {ha_switch(kid, c)} + name: "{c['icon']} {c['name']}" +""" for c in chores) + + chore_cards.append(f"""\ + - type: entities + title: "{kid['avatar']} {kid['name']}'s Chores" + entities: +{chore_rows} - type: divider + - entity: sensor.{ks}_chores_done_today + name: "Chores done today" + - entity: sensor.{ks}_all_chores_done + name: "✅ All Done?" + - type: button + name: "↺ Reset {kid['name']}'s Chores" + tap_action: + action: call-service + service: input_button.press + target: + entity_id: input_button.{ks}_reset_chores +""") + + return f"""\ +################################################################################ +# chore-tracker-dashboard.yaml (AUTO-GENERATED — edit chores_config.yaml) +# Kids: {", ".join(k["name"] for k in kids)} +################################################################################ + +title: "Chore Tracker" +views: + - title: Today + icon: mdi:checkbox-marked-circle + path: chores + cards: + - type: horizontal-stack + cards: +{summary_cards} + - type: button + name: "↺ Reset ALL Chores" + icon: mdi:restart-alert + tap_action: + action: call-service + service: input_button.press + target: + entity_id: input_button.reset_all_chores +{"".join(chore_cards)} +""" + + +# ───────────────────────────────────────────────────────────────────────────── +# Main +# ───────────────────────────────────────────────────────────────────────────── + +def main(): + parser = argparse.ArgumentParser(description="Chore Tracker config generator") + parser.add_argument("--config", default="chores_config.yaml") + parser.add_argument("--out", default=".") + args = parser.parse_args() + + config_path = Path(args.config) + if not config_path.exists(): + print(f"❌ Config not found: {config_path}") + sys.exit(1) + + out_dir = Path(args.out) + out_dir.mkdir(parents=True, exist_ok=True) + + class SecretLoader(yaml.SafeLoader): + pass + SecretLoader.add_constructor( + "!secret", + lambda loader, node: f"!secret {loader.construct_scalar(node)}" + ) + + with open(config_path) as f: + cfg = yaml.load(f, Loader=SecretLoader) + + kids = cfg.get("kids", []) + if not kids: + 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) + + 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() + + files = { + "chore-tracker-esphome.yaml": gen_esphome(cfg), + "chore-tracker-ha.yaml": gen_ha(cfg), + "chore-tracker-dashboard.yaml": gen_dashboard(cfg), + } + + for name, content in files.items(): + path = out_dir / name + path.write_text(content) + print(f"📄 {path}") + + 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") + + +if __name__ == "__main__": + main() \ No newline at end of file diff --git a/packages/chore-tracker-ha.yaml b/packages/chore-tracker-ha.yaml new file mode 100644 index 0000000..b4dd7d1 --- /dev/null +++ b/packages/chore-tracker-ha.yaml @@ -0,0 +1,258 @@ +################################################################################ +# chore-tracker-ha.yaml (AUTO-GENERATED — edit chores_config.yaml) +# Kids: Jordyn, Declan, Chloe +# +# BIDIRECTIONAL SYNC: +# Screen → HA: Each switch calls homeassistant.service on toggle +# HA → Screen: ESPHome native API handles this automatically +################################################################################ + +input_button: + jordyn_reset_chores: + name: "Reset Jordyn's Chores" + icon: mdi:restart + declan_reset_chores: + name: "Reset Declan's Chores" + icon: mdi:restart + chloe_reset_chores: + name: "Reset Chloe's Chores" + icon: mdi:restart + reset_all_chores: + name: "Reset All Chores" + icon: mdi:restart-alert + +template: + - sensor: + - name: "Jordyn Chores Done Today" + 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')] %} + {{ 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 }} + + - 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')] %} + {{ 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 }} + + - 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')] %} + {{ 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 }} + + +automation: + - id: jordyn_reset_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 + - 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 + - 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 + + - id: declan_reset_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 + - 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 + + - id: chloe_reset_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 + - 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 + + - id: reset_all_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 + - 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 + - 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 + - service: switch.turn_off + target: + entity_id: switch.chore_tracker_declan_make_bed + - 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 + - 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 + + - id: jordyn_all_done_notify + alias: "Chore Tracker — Jordyn all done!" + trigger: + - platform: state + entity_id: sensor.jordyn_all_chores_done + to: "True" + action: + - service: notify.notify + data: + title: "🎉 Jordyn finished all chores!" + message: "Jordyn completed all 5 chores today!" + + - id: declan_all_done_notify + alias: "Chore Tracker — Declan all done!" + trigger: + - platform: state + entity_id: sensor.declan_all_chores_done + to: "True" + action: + - service: notify.notify + data: + title: "🎉 Declan finished all chores!" + message: "Declan completed all 4 chores today!" + + - id: chloe_all_done_notify + alias: "Chore Tracker — Chloe all done!" + trigger: + - platform: state + entity_id: sensor.chloe_all_chores_done + to: "True" + action: + - service: notify.notify + data: + title: "🎉 Chloe finished all chores!" + message: "Chloe completed all 6 chores today!" + + - id: chore_reminder_evening + alias: "Chore Tracker — Evening reminder" + trigger: + - platform: time + at: "18:00:00" + action: + - if: + condition: template + value_template: "{{ states('sensor.jordyn_all_chores_done') != 'True' }}" + then: + - service: notify.notify + data: + title: "📋 Jordyn has unfinished chores" + message: > + Jordyn has done + {{ states('sensor.jordyn_chores_done_today') }}/5 chores today. + - if: + condition: template + value_template: "{{ states('sensor.declan_all_chores_done') != 'True' }}" + then: + - service: notify.notify + data: + title: "📋 Declan has unfinished chores" + message: > + Declan has done + {{ states('sensor.declan_chores_done_today') }}/4 chores today. + - if: + condition: template + value_template: "{{ states('sensor.chloe_all_chores_done') != 'True' }}" + then: + - service: notify.notify + data: + title: "📋 Chloe has unfinished chores" + message: > + Chloe has done + {{ states('sensor.chloe_chores_done_today') }}/6 chores today. + +