Compare commits
40 Commits
battery-ic
...
31144a4692
| Author | SHA1 | Date | |
|---|---|---|---|
| 31144a4692 | |||
|
|
7c347fd524 | ||
| 8d6359cdeb | |||
|
|
f60e040bc7 | ||
|
|
0a66aa438c | ||
| 705e74d9de | |||
| 4b31b54914 | |||
| ba4d815201 | |||
| 1c575a7185 | |||
| d853f5f54f | |||
|
|
0963024b91 | ||
|
|
ce6eacbebb | ||
|
|
8e619a8361 | ||
|
|
344ed66c36 | ||
|
|
fc8567b6db | ||
|
|
deec641949 | ||
|
|
b026f2937a | ||
|
|
76e40b2fef | ||
|
|
0ae5f13859 | ||
|
|
7316d9c38b | ||
|
|
e7aa4dad9b | ||
| b9913fa8fb | |||
|
|
5dc58e147b | ||
|
|
83a4d116ec | ||
|
|
00e06c80c6 | ||
|
|
929f41674a | ||
|
|
d6ddd44787 | ||
|
|
49d87e7777 | ||
|
|
0c66d45cc6 | ||
|
|
128388f0ac | ||
|
|
f6f2b71851 | ||
| ed7deb6cb8 | |||
| 5dcb436063 | |||
| 918ca361fc | |||
|
|
88a9caabaf | ||
|
|
2eaad35aaa | ||
|
|
c69d955080 | ||
|
|
e75763ba4d | ||
|
|
53c3add254 | ||
|
|
e1715b4ca5 |
@@ -1 +1 @@
|
|||||||
2026.2.2
|
2026.2.3
|
||||||
27
.gitignore
vendored
27
.gitignore
vendored
@@ -11,18 +11,43 @@
|
|||||||
!*.txt*
|
!*.txt*
|
||||||
!*.json*
|
!*.json*
|
||||||
!*.ui.yaml
|
!*.ui.yaml
|
||||||
|
!*.py
|
||||||
|
|
||||||
# Whitelist subdirectory yaml files
|
# Whitelist subdirectory yaml files
|
||||||
!*/*.yaml
|
!*/*.yaml
|
||||||
!*/*.yml
|
!*/*.yml
|
||||||
|
!*/*.md
|
||||||
|
!*/*.sh
|
||||||
|
!*/*.js*
|
||||||
|
!*/*.txt*
|
||||||
|
!*/*.json*
|
||||||
|
!*/*.ui.yaml
|
||||||
|
!*/*.h
|
||||||
|
!*/*.py
|
||||||
|
!*/*/*.py
|
||||||
|
!*/*/*.yaml
|
||||||
|
|
||||||
|
|
||||||
# Whitelist specific folders (uncomment as needed)
|
# Whitelist specific folders (uncomment as needed)
|
||||||
!automations/
|
!automations/
|
||||||
|
!packages/
|
||||||
|
!scenes/
|
||||||
!scripts/
|
!scripts/
|
||||||
!blueprints/
|
!blueprints/
|
||||||
!esphome/
|
!esphome/
|
||||||
!esphome/ha-remote/
|
!esphome/ha-remote/
|
||||||
!custom_components/
|
!esphome/ha-remote/components/
|
||||||
|
!esphome/ha-remote/components/**
|
||||||
|
!esphome/ha-remote/components/max17043/
|
||||||
|
!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/
|
# !packages/
|
||||||
# !themes/
|
# !themes/
|
||||||
# !node-red/
|
# !node-red/
|
||||||
|
|||||||
206
automations.yaml
206
automations.yaml
@@ -224,7 +224,7 @@
|
|||||||
conditions:
|
conditions:
|
||||||
- condition: time
|
- condition: time
|
||||||
after: '21:00:00'
|
after: '21:00:00'
|
||||||
before: 07:00:00
|
before: 07:30:00
|
||||||
actions:
|
actions:
|
||||||
- action: notify.mobile_app_joshuas_iphone_of_pain
|
- action: notify.mobile_app_joshuas_iphone_of_pain
|
||||||
metadata: {}
|
metadata: {}
|
||||||
@@ -1031,11 +1031,11 @@
|
|||||||
- action: notify.mobile_app_joshuas_iphone_of_pain
|
- action: notify.mobile_app_joshuas_iphone_of_pain
|
||||||
metadata: {}
|
metadata: {}
|
||||||
data:
|
data:
|
||||||
message: The Deuce is full of deuces
|
message: The Deuce is full of deuces (laundry room)
|
||||||
- action: notify.mobile_app_pollys_iphone
|
- action: notify.mobile_app_pollys_iphone
|
||||||
metadata: {}
|
metadata: {}
|
||||||
data:
|
data:
|
||||||
message: The Deuce is full of deuces
|
message: The Deuce is full of deuces (laundry room)
|
||||||
- action: notify.mailgun_smtp
|
- action: notify.mailgun_smtp
|
||||||
metadata: {}
|
metadata: {}
|
||||||
data:
|
data:
|
||||||
@@ -1043,6 +1043,10 @@
|
|||||||
title: Too many dueces have been dropped!
|
title: Too many dueces have been dropped!
|
||||||
target: joshua@cnjmail.com
|
target: joshua@cnjmail.com
|
||||||
enabled: false
|
enabled: false
|
||||||
|
- action: notify.mobile_app_chloes_iphone
|
||||||
|
metadata: {}
|
||||||
|
data:
|
||||||
|
message: The Deuce is full of deuces (laundry room)
|
||||||
mode: single
|
mode: single
|
||||||
- id: '1742842372471'
|
- id: '1742842372471'
|
||||||
alias: The Poop Box has runneth over Notification
|
alias: The Poop Box has runneth over Notification
|
||||||
@@ -1058,11 +1062,15 @@
|
|||||||
- action: notify.mobile_app_joshuas_iphone_of_pain
|
- action: notify.mobile_app_joshuas_iphone_of_pain
|
||||||
metadata: {}
|
metadata: {}
|
||||||
data:
|
data:
|
||||||
message: The Poop Box has runneth over
|
message: The Poop Box has runneth over (basement, left side)
|
||||||
- action: notify.mobile_app_pollys_iphone
|
- action: notify.mobile_app_pollys_iphone
|
||||||
metadata: {}
|
metadata: {}
|
||||||
data:
|
data:
|
||||||
message: The Poop Box has runneth over
|
message: The Poop Box has runneth over (basement, left side)
|
||||||
|
- action: notify.mobile_app_chloes_iphone
|
||||||
|
metadata: {}
|
||||||
|
data:
|
||||||
|
message: The Poop Box has runneth over (basement, left side)
|
||||||
- action: notify.mailgun_smtp
|
- action: notify.mailgun_smtp
|
||||||
metadata: {}
|
metadata: {}
|
||||||
data:
|
data:
|
||||||
@@ -1085,11 +1093,15 @@
|
|||||||
- action: notify.mobile_app_joshuas_iphone_of_pain
|
- action: notify.mobile_app_joshuas_iphone_of_pain
|
||||||
metadata: {}
|
metadata: {}
|
||||||
data:
|
data:
|
||||||
message: Triple Threat can't take no more!
|
message: Triple Threat can't take no more! (Basement, right side)
|
||||||
- action: notify.mobile_app_pollys_iphone
|
- action: notify.mobile_app_pollys_iphone
|
||||||
metadata: {}
|
metadata: {}
|
||||||
data:
|
data:
|
||||||
message: Triple Threat can't take no more!
|
message: Triple Threat can't take no more! (Basement, right side)
|
||||||
|
- action: notify.mobile_app_chloes_iphone
|
||||||
|
metadata: {}
|
||||||
|
data:
|
||||||
|
message: Triple Threat can't take no more! (Basement, right side)
|
||||||
- action: notify.mailgun_smtp
|
- action: notify.mailgun_smtp
|
||||||
metadata: {}
|
metadata: {}
|
||||||
data:
|
data:
|
||||||
@@ -1900,44 +1912,6 @@
|
|||||||
entity_id: 84d24335edc946153ed4f81ac7906f3c
|
entity_id: 84d24335edc946153ed4f81ac7906f3c
|
||||||
domain: switch
|
domain: switch
|
||||||
mode: single
|
mode: single
|
||||||
- id: '1770043717316'
|
|
||||||
alias: turn on jordyn a1 cam light
|
|
||||||
description: ''
|
|
||||||
triggers:
|
|
||||||
- device_id: 6d6eebb0e8ea6f46ac2ec3a3a5a5eae3
|
|
||||||
domain: bambu_lab
|
|
||||||
type: event_print_started
|
|
||||||
trigger: device
|
|
||||||
conditions: []
|
|
||||||
actions:
|
|
||||||
- type: turn_on
|
|
||||||
device_id: d174bbc498cde1d02e455cb133c04675
|
|
||||||
entity_id: 0736793ae0d143db76ea7c64d86f4268
|
|
||||||
domain: switch
|
|
||||||
mode: single
|
|
||||||
- id: '1770045082546'
|
|
||||||
alias: Turn off Jordyn a1 camera
|
|
||||||
description: ''
|
|
||||||
triggers:
|
|
||||||
- device_id: 6d6eebb0e8ea6f46ac2ec3a3a5a5eae3
|
|
||||||
domain: bambu_lab
|
|
||||||
type: event_print_finished
|
|
||||||
trigger: device
|
|
||||||
- device_id: 6d6eebb0e8ea6f46ac2ec3a3a5a5eae3
|
|
||||||
domain: bambu_lab
|
|
||||||
type: event_print_failed
|
|
||||||
trigger: device
|
|
||||||
- device_id: 6d6eebb0e8ea6f46ac2ec3a3a5a5eae3
|
|
||||||
domain: bambu_lab
|
|
||||||
type: event_print_canceled
|
|
||||||
trigger: device
|
|
||||||
conditions: []
|
|
||||||
actions:
|
|
||||||
- type: turn_off
|
|
||||||
device_id: d174bbc498cde1d02e455cb133c04675
|
|
||||||
entity_id: 0736793ae0d143db76ea7c64d86f4268
|
|
||||||
domain: switch
|
|
||||||
mode: single
|
|
||||||
- id: '1770433866161'
|
- id: '1770433866161'
|
||||||
alias: Turn off Kitchen Sink at Sunrise
|
alias: Turn off Kitchen Sink at Sunrise
|
||||||
description: ''
|
description: ''
|
||||||
@@ -1960,6 +1934,10 @@
|
|||||||
domain: bambu_lab
|
domain: bambu_lab
|
||||||
type: event_print_started
|
type: event_print_started
|
||||||
trigger: device
|
trigger: device
|
||||||
|
- device_id: 6d6eebb0e8ea6f46ac2ec3a3a5a5eae3
|
||||||
|
domain: bambu_lab
|
||||||
|
type: event_print_error_cleared
|
||||||
|
trigger: device
|
||||||
conditions: []
|
conditions: []
|
||||||
actions:
|
actions:
|
||||||
- action: light.turn_on
|
- action: light.turn_on
|
||||||
@@ -1969,7 +1947,7 @@
|
|||||||
data: {}
|
data: {}
|
||||||
- type: turn_on
|
- type: turn_on
|
||||||
device_id: d174bbc498cde1d02e455cb133c04675
|
device_id: d174bbc498cde1d02e455cb133c04675
|
||||||
entity_id: c1781c1af12384eeafe080e11876b286
|
entity_id: 0736793ae0d143db76ea7c64d86f4268
|
||||||
domain: switch
|
domain: switch
|
||||||
- action: light.turn_on
|
- action: light.turn_on
|
||||||
metadata: {}
|
metadata: {}
|
||||||
@@ -1987,13 +1965,25 @@
|
|||||||
domain: bambu_lab
|
domain: bambu_lab
|
||||||
type: event_print_started
|
type: event_print_started
|
||||||
trigger: device
|
trigger: device
|
||||||
|
- device_id: 299743e9ce66334a0f3bac10eb24cf31
|
||||||
|
domain: bambu_lab
|
||||||
|
type: event_print_error_cleared
|
||||||
|
trigger: device
|
||||||
conditions: []
|
conditions: []
|
||||||
actions:
|
actions:
|
||||||
- action: light.turn_on
|
- action: light.turn_on
|
||||||
metadata: {}
|
metadata: {}
|
||||||
target:
|
target:
|
||||||
entity_id: light.a1_03919d540806387_chamber_light
|
entity_id:
|
||||||
data: {}
|
- light.a1_03919d540806387_chamber_light
|
||||||
|
- light.led_controller_polly_pocket_declan_s_led_controller
|
||||||
|
data:
|
||||||
|
color_temp_kelvin: 6500
|
||||||
|
brightness_pct: 100
|
||||||
|
- type: turn_on
|
||||||
|
device_id: c63f4513655bdd5b44b998f45b491136
|
||||||
|
entity_id: fe6d0eee24eb1b397d37b7389137b5b5
|
||||||
|
domain: switch
|
||||||
mode: single
|
mode: single
|
||||||
- id: '1770938855253'
|
- id: '1770938855253'
|
||||||
alias: Jordyn-a1-light-off-print-send
|
alias: Jordyn-a1-light-off-print-send
|
||||||
@@ -2003,6 +1993,10 @@
|
|||||||
domain: bambu_lab
|
domain: bambu_lab
|
||||||
type: event_print_finished
|
type: event_print_finished
|
||||||
trigger: device
|
trigger: device
|
||||||
|
- device_id: 6d6eebb0e8ea6f46ac2ec3a3a5a5eae3
|
||||||
|
domain: bambu_lab
|
||||||
|
type: event_print_canceled
|
||||||
|
trigger: device
|
||||||
conditions: []
|
conditions: []
|
||||||
actions:
|
actions:
|
||||||
- action: light.turn_off
|
- action: light.turn_off
|
||||||
@@ -2015,7 +2009,7 @@
|
|||||||
transition: 60
|
transition: 60
|
||||||
- type: turn_off
|
- type: turn_off
|
||||||
device_id: d174bbc498cde1d02e455cb133c04675
|
device_id: d174bbc498cde1d02e455cb133c04675
|
||||||
entity_id: c1781c1af12384eeafe080e11876b286
|
entity_id: 0736793ae0d143db76ea7c64d86f4268
|
||||||
domain: switch
|
domain: switch
|
||||||
mode: single
|
mode: single
|
||||||
- id: '1770943298493'
|
- id: '1770943298493'
|
||||||
@@ -2024,15 +2018,25 @@
|
|||||||
triggers:
|
triggers:
|
||||||
- device_id: 299743e9ce66334a0f3bac10eb24cf31
|
- device_id: 299743e9ce66334a0f3bac10eb24cf31
|
||||||
domain: bambu_lab
|
domain: bambu_lab
|
||||||
type: event_print_started
|
type: event_print_finished
|
||||||
|
trigger: device
|
||||||
|
- device_id: 299743e9ce66334a0f3bac10eb24cf31
|
||||||
|
domain: bambu_lab
|
||||||
|
type: event_print_canceled
|
||||||
trigger: device
|
trigger: device
|
||||||
conditions: []
|
conditions: []
|
||||||
actions:
|
actions:
|
||||||
- action: light.turn_off
|
- action: light.turn_off
|
||||||
metadata: {}
|
metadata: {}
|
||||||
target:
|
target:
|
||||||
entity_id: light.a1_03919d540806387_chamber_light
|
entity_id:
|
||||||
|
- light.a1_03919d540806387_chamber_light
|
||||||
|
- light.led_controller_polly_pocket_declan_s_led_controller
|
||||||
data: {}
|
data: {}
|
||||||
|
- type: turn_off
|
||||||
|
device_id: c63f4513655bdd5b44b998f45b491136
|
||||||
|
entity_id: fe6d0eee24eb1b397d37b7389137b5b5
|
||||||
|
domain: switch
|
||||||
mode: single
|
mode: single
|
||||||
- id: '1770943760193'
|
- id: '1770943760193'
|
||||||
alias: Jordyn-A1-Flash-Red-When-Print-Error
|
alias: Jordyn-A1-Flash-Red-When-Print-Error
|
||||||
@@ -2042,6 +2046,10 @@
|
|||||||
domain: bambu_lab
|
domain: bambu_lab
|
||||||
type: event_print_error
|
type: event_print_error
|
||||||
trigger: device
|
trigger: device
|
||||||
|
- device_id: 6d6eebb0e8ea6f46ac2ec3a3a5a5eae3
|
||||||
|
domain: bambu_lab
|
||||||
|
type: event_print_failed
|
||||||
|
trigger: device
|
||||||
conditions: []
|
conditions: []
|
||||||
actions:
|
actions:
|
||||||
- action: light.turn_on
|
- action: light.turn_on
|
||||||
@@ -2055,24 +2063,15 @@
|
|||||||
- 0
|
- 0
|
||||||
effect: Flash
|
effect: Flash
|
||||||
brightness_pct: 100
|
brightness_pct: 100
|
||||||
mode: single
|
- type: turn_off
|
||||||
- id: '1770943916864'
|
device_id: d174bbc498cde1d02e455cb133c04675
|
||||||
alias: Jordyn-A1-LED-White-When-Error-Cleared
|
entity_id: 0736793ae0d143db76ea7c64d86f4268
|
||||||
description: ''
|
domain: switch
|
||||||
triggers:
|
- action: light.turn_off
|
||||||
- device_id: 6d6eebb0e8ea6f46ac2ec3a3a5a5eae3
|
|
||||||
domain: bambu_lab
|
|
||||||
type: event_print_error_cleared
|
|
||||||
trigger: device
|
|
||||||
conditions: []
|
|
||||||
actions:
|
|
||||||
- action: light.turn_on
|
|
||||||
metadata: {}
|
metadata: {}
|
||||||
target:
|
target:
|
||||||
entity_id: light.jordyn_s_led_controller_jordyn_s_led_controller
|
entity_id: light.jordyns_layer_slayer_chamber_light
|
||||||
data:
|
data: {}
|
||||||
color_temp_kelvin: 6500
|
|
||||||
brightness_pct: 100
|
|
||||||
mode: single
|
mode: single
|
||||||
- id: '1770944104889'
|
- id: '1770944104889'
|
||||||
alias: Pints-Charming-Lights-On-Print-Started
|
alias: Pints-Charming-Lights-On-Print-Started
|
||||||
@@ -2134,7 +2133,76 @@
|
|||||||
actions:
|
actions:
|
||||||
- action: light.turn_off
|
- action: light.turn_off
|
||||||
metadata: {}
|
metadata: {}
|
||||||
target:
|
|
||||||
entity_id: light.constipation_orion_chamber_light
|
|
||||||
data: {}
|
data: {}
|
||||||
|
target:
|
||||||
|
entity_id: light.p1s_01p09c470102673_chamber_light
|
||||||
|
mode: single
|
||||||
|
- id: '1771090372998'
|
||||||
|
alias: Declans Closet Door Battery Low Notification
|
||||||
|
description: ''
|
||||||
|
triggers:
|
||||||
|
- trigger: numeric_state
|
||||||
|
entity_id:
|
||||||
|
- sensor.declans_closet_door_battery
|
||||||
|
below: 30
|
||||||
|
conditions: []
|
||||||
|
actions:
|
||||||
|
- action: notify.mobile_app_pollys_iphone
|
||||||
|
metadata: {}
|
||||||
|
data:
|
||||||
|
message: Declan's closet sensor battery is low.
|
||||||
|
- action: notify.mobile_app_joshuas_iphone_of_pain
|
||||||
|
metadata: {}
|
||||||
|
data:
|
||||||
|
message: Declan's closet sensor battery is low.
|
||||||
|
mode: single
|
||||||
|
- id: '1771272500745'
|
||||||
|
alias: polly-pocket-a1-mini-lights-on-during-print
|
||||||
|
description: ''
|
||||||
|
triggers:
|
||||||
|
- device_id: 1cf63ab34db5ac50291c1477cddf2b9a
|
||||||
|
domain: bambu_lab
|
||||||
|
type: event_print_started
|
||||||
|
trigger: device
|
||||||
|
conditions: []
|
||||||
|
actions:
|
||||||
|
- action: light.turn_on
|
||||||
|
metadata: {}
|
||||||
|
target:
|
||||||
|
entity_id:
|
||||||
|
- light.a1mini_0309ca580403653_chamber_light
|
||||||
|
- light.polly_s_led_controller_polly_s_led_controller
|
||||||
|
data:
|
||||||
|
color_temp_kelvin: 6500
|
||||||
|
brightness_pct: 100
|
||||||
|
- type: turn_on
|
||||||
|
device_id: a78c9554b983d1e2459194cc0fee22d3
|
||||||
|
entity_id: d430d19aa2f118038cb01a3157865ed0
|
||||||
|
domain: switch
|
||||||
|
mode: single
|
||||||
|
- id: '1771272773906'
|
||||||
|
alias: polly-pocket-a1-mini-lights-off-during-print
|
||||||
|
description: ''
|
||||||
|
triggers:
|
||||||
|
- device_id: 1cf63ab34db5ac50291c1477cddf2b9a
|
||||||
|
domain: bambu_lab
|
||||||
|
type: event_print_finished
|
||||||
|
trigger: device
|
||||||
|
- device_id: 1cf63ab34db5ac50291c1477cddf2b9a
|
||||||
|
domain: bambu_lab
|
||||||
|
type: event_print_canceled
|
||||||
|
trigger: device
|
||||||
|
conditions: []
|
||||||
|
actions:
|
||||||
|
- action: light.turn_off
|
||||||
|
metadata: {}
|
||||||
|
target:
|
||||||
|
entity_id:
|
||||||
|
- light.a1mini_0309ca580403653_chamber_light
|
||||||
|
- light.polly_s_led_controller_polly_s_led_controller
|
||||||
|
data: {}
|
||||||
|
- type: turn_off
|
||||||
|
device_id: a78c9554b983d1e2459194cc0fee22d3
|
||||||
|
entity_id: d430d19aa2f118038cb01a3157865ed0
|
||||||
|
domain: switch
|
||||||
mode: single
|
mode: single
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
homeassistant:
|
||||||
|
packages: !include_dir_named packages/
|
||||||
|
|
||||||
# Loads default set of integrations. Do not remove.
|
# Loads default set of integrations. Do not remove.
|
||||||
default_config:
|
default_config:
|
||||||
|
|||||||
5
esphome/.gitignore
vendored
5
esphome/.gitignore
vendored
@@ -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
|
|
||||||
47
esphome/MAX17048_component.h
Normal file
47
esphome/MAX17048_component.h
Normal file
@@ -0,0 +1,47 @@
|
|||||||
|
#include "esphome.h"
|
||||||
|
|
||||||
|
#define MAX17048_ADDRESS 0x36
|
||||||
|
#define MAX17048_VCELL 0x02 // voltage
|
||||||
|
#define MAX17048_SOC 0x04 // percentage
|
||||||
|
#define MAX17048_MODE 0x06
|
||||||
|
#define MAX17048_VERSION 0x08
|
||||||
|
#define MAX17048_CONFIG 0x0c
|
||||||
|
#define MAX17048_COMMAND 0xfe
|
||||||
|
|
||||||
|
class MAX17048Sensor : public PollingComponent, public Sensor {
|
||||||
|
public:
|
||||||
|
Sensor *voltage_sensor = new Sensor();
|
||||||
|
Sensor *percentage_sensor = new Sensor();
|
||||||
|
|
||||||
|
MAX17048Sensor() : PollingComponent(10000) {}
|
||||||
|
|
||||||
|
void setup() override {
|
||||||
|
// Initialize the device here. Usually Wire.begin() will be called in here,
|
||||||
|
// though that call is unnecessary if you have an 'i2c:' entry in your config
|
||||||
|
ESP_LOGD("custom", "Starting up MAX17048 sensor");
|
||||||
|
|
||||||
|
Wire.begin();
|
||||||
|
}
|
||||||
|
|
||||||
|
uint16_t read16(uint8_t reg) {
|
||||||
|
uint16_t temp;
|
||||||
|
Wire.begin();
|
||||||
|
Wire.beginTransmission(MAX17048_ADDRESS);
|
||||||
|
Wire.write(reg);
|
||||||
|
Wire.endTransmission();
|
||||||
|
Wire.requestFrom(MAX17048_ADDRESS, 2);
|
||||||
|
temp = (uint16_t)Wire.read() << 8;
|
||||||
|
temp |= (uint16_t)Wire.read();
|
||||||
|
Wire.endTransmission();
|
||||||
|
return temp;
|
||||||
|
}
|
||||||
|
|
||||||
|
void update() override {
|
||||||
|
float voltage = (float)(read16(MAX17048_VCELL)) * 78.125 / 1000000;
|
||||||
|
voltage_sensor->publish_state(voltage);
|
||||||
|
|
||||||
|
uint16_t percentage_tmp = read16(MAX17048_SOC);
|
||||||
|
float percentage = (float)(percentage_tmp) / 256;
|
||||||
|
percentage_sensor->publish_state(percentage);
|
||||||
|
}
|
||||||
|
};
|
||||||
1759
esphome/chore-tracker-esphome.yaml
Normal file
1759
esphome/chore-tracker-esphome.yaml
Normal file
File diff suppressed because it is too large
Load Diff
133
esphome/chore-tracker/chore-tracker-dashboard.yaml
Normal file
133
esphome/chore-tracker/chore-tracker-dashboard.yaml
Normal file
@@ -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
|
||||||
|
|
||||||
70
esphome/chore-tracker/chores_config.yaml
Normal file
70
esphome/chore-tracker/chores_config.yaml
Normal file
@@ -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: "🎹"
|
||||||
895
esphome/chore-tracker/generate.py
Normal file
895
esphome/chore-tracker/generate.py
Normal file
@@ -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()
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
substitutions:
|
substitutions:
|
||||||
device_name: declan-a1-camera
|
device_name: declan-a1-camera
|
||||||
friendly_name: "Declan's A1 Camera"
|
friendly_name: "Declan's A1 Camera"
|
||||||
|
|
||||||
esphome:
|
esphome:
|
||||||
name: ${device_name}
|
name: ${device_name}
|
||||||
friendly_name: ${friendly_name}
|
friendly_name: ${friendly_name}
|
||||||
@@ -10,12 +10,9 @@ esp32:
|
|||||||
board: esp32dev
|
board: esp32dev
|
||||||
framework:
|
framework:
|
||||||
type: esp-idf
|
type: esp-idf
|
||||||
#cpu_frequency: 240MHZ
|
|
||||||
|
|
||||||
# Enable logging
|
|
||||||
logger:
|
logger:
|
||||||
|
|
||||||
# Enable Home Assistant API
|
|
||||||
api:
|
api:
|
||||||
encryption:
|
encryption:
|
||||||
key: !secret api_encryption_key
|
key: !secret api_encryption_key
|
||||||
@@ -55,21 +52,13 @@ esp32_camera:
|
|||||||
href_pin: GPIO23
|
href_pin: GPIO23
|
||||||
pixel_clock_pin: GPIO22
|
pixel_clock_pin: GPIO22
|
||||||
power_down_pin: GPIO32
|
power_down_pin: GPIO32
|
||||||
resolution: 640x480
|
resolution: 1024x768
|
||||||
jpeg_quality: 20
|
jpeg_quality: 15
|
||||||
vertical_flip: False
|
vertical_flip: true
|
||||||
horizontal_mirror: False
|
horizontal_mirror: false
|
||||||
max_framerate: 22 fps
|
max_framerate: 20 fps
|
||||||
idle_framerate: 0.1 fps
|
idle_framerate: 0.05 fps
|
||||||
|
|
||||||
# Image settings
|
|
||||||
name: ${device_name}
|
name: ${device_name}
|
||||||
#brightness: 1
|
|
||||||
#contrast: 0
|
|
||||||
# agc_gain_ceiling: 2X
|
|
||||||
# agc_mode: MANUAL
|
|
||||||
# agc_value: 5
|
|
||||||
# ...
|
|
||||||
|
|
||||||
esp32_camera_web_server:
|
esp32_camera_web_server:
|
||||||
- port: 8080
|
- port: 8080
|
||||||
|
|||||||
56
esphome/fdm-exhaust-fan-control.yaml
Normal file
56
esphome/fdm-exhaust-fan-control.yaml
Normal file
@@ -0,0 +1,56 @@
|
|||||||
|
substitutions:
|
||||||
|
device_name: fdm-exhaust-fan-control
|
||||||
|
friendly_name: "FDM Exhaust Fan Control"
|
||||||
|
|
||||||
|
esphome:
|
||||||
|
name: ${device_name}
|
||||||
|
friendly_name: ${friendly_name}
|
||||||
|
|
||||||
|
esp32:
|
||||||
|
board: seeed_xiao_esp32c3
|
||||||
|
framework:
|
||||||
|
type: esp-idf
|
||||||
|
|
||||||
|
# Enable logging
|
||||||
|
logger:
|
||||||
|
|
||||||
|
# Enable Home Assistant API
|
||||||
|
api:
|
||||||
|
encryption:
|
||||||
|
key: !secret api_encryption_key
|
||||||
|
|
||||||
|
ota:
|
||||||
|
- platform: esphome
|
||||||
|
password: !secret ota_password
|
||||||
|
|
||||||
|
wifi:
|
||||||
|
ssid: !secret wifi_iot_ssid
|
||||||
|
password: !secret wifi_password
|
||||||
|
ap:
|
||||||
|
ssid: "${friendly_name} Fallback"
|
||||||
|
password: !secret fallback_password
|
||||||
|
|
||||||
|
captive_portal:
|
||||||
|
|
||||||
|
web_server:
|
||||||
|
port: 80
|
||||||
|
|
||||||
|
output:
|
||||||
|
- platform: gpio
|
||||||
|
pin:
|
||||||
|
number: GPIO3
|
||||||
|
inverted: true # Fix for driver inversion
|
||||||
|
id: fan_80mm_output
|
||||||
|
- platform: gpio
|
||||||
|
pin:
|
||||||
|
number: GPIO4
|
||||||
|
inverted: true
|
||||||
|
id: fan_120mm_output
|
||||||
|
|
||||||
|
fan:
|
||||||
|
- platform: binary
|
||||||
|
name: "80mm Exhaust Fan"
|
||||||
|
output: fan_80mm_output
|
||||||
|
- platform: binary
|
||||||
|
name: "120mm Exhaust Fan"
|
||||||
|
output: fan_120mm_output
|
||||||
@@ -1,51 +0,0 @@
|
|||||||
substitutions:
|
|
||||||
device_name: ha-family-room-remote
|
|
||||||
friendly_name: "Family Room HA Remote"
|
|
||||||
|
|
||||||
esphome:
|
|
||||||
name: ${device_name}
|
|
||||||
friendly_name: ${friendly_name}
|
|
||||||
on_boot:
|
|
||||||
priority: -10
|
|
||||||
then:
|
|
||||||
- light.turn_on: ha_remote_backlight
|
|
||||||
- lvgl.page.show: home
|
|
||||||
- lambda: |-
|
|
||||||
id(last_activity_ms) = millis();
|
|
||||||
|
|
||||||
esp32:
|
|
||||||
board: esp32-s3-devkitc-1
|
|
||||||
framework:
|
|
||||||
type: esp-idf
|
|
||||||
|
|
||||||
logger:
|
|
||||||
level: WARN
|
|
||||||
|
|
||||||
api:
|
|
||||||
encryption:
|
|
||||||
key: !secret api_encryption_key
|
|
||||||
|
|
||||||
# Enable over-the-air updates
|
|
||||||
ota:
|
|
||||||
- platform: esphome
|
|
||||||
password: !secret ota_password
|
|
||||||
|
|
||||||
wifi:
|
|
||||||
ssid: !secret wifi_iot_ssid
|
|
||||||
password: !secret wifi_password
|
|
||||||
power_save_mode: NONE
|
|
||||||
fast_connect: true
|
|
||||||
|
|
||||||
# Enable fallback hotspot (captive portal) in case wifi connection fails
|
|
||||||
ap:
|
|
||||||
ssid: "${friendly_name} Fallback"
|
|
||||||
password: !secret fallback_password
|
|
||||||
|
|
||||||
captive_portal:
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
packages:
|
|
||||||
base: !include ha-remote/ha-remote-1.base.yaml
|
|
||||||
bindings: !include ha-remote/ha-remote-1.bindings.yaml
|
|
||||||
ui: !include ha-remote/ha-remote-1.ui.yaml
|
|
||||||
1
esphome/ha-remote/components/max17043/__init__.py
Normal file
1
esphome/ha-remote/components/max17043/__init__.py
Normal file
@@ -0,0 +1 @@
|
|||||||
|
CODEOWNERS = ["@blacknell"]
|
||||||
20
esphome/ha-remote/components/max17043/automation.h
Normal file
20
esphome/ha-remote/components/max17043/automation.h
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
|
||||||
|
#pragma once
|
||||||
|
#include "esphome/core/automation.h"
|
||||||
|
#include "max17043.h"
|
||||||
|
|
||||||
|
namespace esphome {
|
||||||
|
namespace max17043 {
|
||||||
|
|
||||||
|
template<typename... Ts> class SleepAction : public Action<Ts...> {
|
||||||
|
public:
|
||||||
|
explicit SleepAction(MAX17043Component *max17043) : max17043_(max17043) {}
|
||||||
|
|
||||||
|
void play(Ts... x) override { this->max17043_->sleep_mode(); }
|
||||||
|
|
||||||
|
protected:
|
||||||
|
MAX17043Component *max17043_;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace max17043
|
||||||
|
} // namespace esphome
|
||||||
72
esphome/ha-remote/components/max17043/max17043.cpp
Normal file
72
esphome/ha-remote/components/max17043/max17043.cpp
Normal file
@@ -0,0 +1,72 @@
|
|||||||
|
#include "max17043.h"
|
||||||
|
#include "esphome/core/log.h"
|
||||||
|
|
||||||
|
namespace esphome {
|
||||||
|
namespace max17043 {
|
||||||
|
|
||||||
|
// MAX174043 is a 1-Cell Fuel Gauge with ModelGauge and Low-Battery Alert
|
||||||
|
// Consult the datasheet at https://www.analog.com/en/products/max17043.html
|
||||||
|
|
||||||
|
static const char *const TAG = "max17043";
|
||||||
|
|
||||||
|
static const uint8_t MAX17043_VCELL = 0x02;
|
||||||
|
static const uint8_t MAX17043_SOC = 0x04;
|
||||||
|
static const uint8_t MAX17043_CONFIG = 0x0c;
|
||||||
|
|
||||||
|
static const uint16_t MAX17043_CONFIG_POWER_UP_DEFAULT = 0x971C;
|
||||||
|
static const uint16_t MAX17043_CONFIG_SAFE_MASK = 0xFF1F; // mask out sleep bit (7), unused bit (6) and alert bit (4)
|
||||||
|
static const uint16_t MAX17043_CONFIG_SLEEP_MASK = 0x0080;
|
||||||
|
|
||||||
|
void MAX17043Component::update() {
|
||||||
|
uint16_t raw_voltage, raw_percent;
|
||||||
|
|
||||||
|
if (this->voltage_sensor_ != nullptr) {
|
||||||
|
if (!this->read_byte_16(MAX17043_VCELL, &raw_voltage)) {
|
||||||
|
this->status_set_warning("Unable to read MAX17043_VCELL");
|
||||||
|
} else {
|
||||||
|
float voltage = (1.25 * (float) (raw_voltage >> 4)) / 1000.0;
|
||||||
|
this->voltage_sensor_->publish_state(voltage);
|
||||||
|
this->status_clear_warning();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (this->battery_remaining_sensor_ != nullptr) {
|
||||||
|
if (!this->read_byte_16(MAX17043_SOC, &raw_percent)) {
|
||||||
|
this->status_set_warning("Unable to read MAX17043_SOC");
|
||||||
|
} else {
|
||||||
|
float percent = (float) ((raw_percent >> 8) + 0.003906f * (raw_percent & 0x00ff));
|
||||||
|
this->battery_remaining_sensor_->publish_state(percent);
|
||||||
|
this->status_clear_warning();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void MAX17043Component::setup() {
|
||||||
|
ESP_LOGCONFIG(TAG, "Setting up MAX17043...");
|
||||||
|
// Compatible mode for MAX17048/variant boards:
|
||||||
|
// avoid setup-time register writes on the shared touch I2C bus.
|
||||||
|
}
|
||||||
|
|
||||||
|
void MAX17043Component::dump_config() {
|
||||||
|
ESP_LOGCONFIG(TAG, "MAX17043:");
|
||||||
|
LOG_I2C_DEVICE(this);
|
||||||
|
if (this->is_failed()) {
|
||||||
|
ESP_LOGE(TAG, "Communication with MAX17043 failed");
|
||||||
|
}
|
||||||
|
LOG_UPDATE_INTERVAL(this);
|
||||||
|
LOG_SENSOR(" ", "Battery Voltage", this->voltage_sensor_);
|
||||||
|
LOG_SENSOR(" ", "Battery Level", this->battery_remaining_sensor_);
|
||||||
|
}
|
||||||
|
|
||||||
|
float MAX17043Component::get_setup_priority() const { return setup_priority::DATA; }
|
||||||
|
|
||||||
|
void MAX17043Component::sleep_mode() {
|
||||||
|
if (!this->is_failed()) {
|
||||||
|
if (!this->write_byte_16(MAX17043_CONFIG, MAX17043_CONFIG_POWER_UP_DEFAULT | MAX17043_CONFIG_SLEEP_MASK)) {
|
||||||
|
ESP_LOGW(TAG, "Unable to write the sleep bit to config register");
|
||||||
|
this->status_set_warning();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace max17043
|
||||||
|
} // namespace esphome
|
||||||
29
esphome/ha-remote/components/max17043/max17043.h
Normal file
29
esphome/ha-remote/components/max17043/max17043.h
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "esphome/core/component.h"
|
||||||
|
#include "esphome/components/sensor/sensor.h"
|
||||||
|
#include "esphome/components/i2c/i2c.h"
|
||||||
|
|
||||||
|
namespace esphome {
|
||||||
|
namespace max17043 {
|
||||||
|
|
||||||
|
class MAX17043Component : public PollingComponent, public i2c::I2CDevice {
|
||||||
|
public:
|
||||||
|
void setup() override;
|
||||||
|
void dump_config() override;
|
||||||
|
float get_setup_priority() const override;
|
||||||
|
void update() override;
|
||||||
|
void sleep_mode();
|
||||||
|
|
||||||
|
void set_voltage_sensor(sensor::Sensor *voltage_sensor) { voltage_sensor_ = voltage_sensor; }
|
||||||
|
void set_battery_remaining_sensor(sensor::Sensor *battery_remaining_sensor) {
|
||||||
|
battery_remaining_sensor_ = battery_remaining_sensor;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected:
|
||||||
|
sensor::Sensor *voltage_sensor_{nullptr};
|
||||||
|
sensor::Sensor *battery_remaining_sensor_{nullptr};
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace max17043
|
||||||
|
} // namespace esphome
|
||||||
77
esphome/ha-remote/components/max17043/sensor.py
Normal file
77
esphome/ha-remote/components/max17043/sensor.py
Normal file
@@ -0,0 +1,77 @@
|
|||||||
|
from esphome import automation
|
||||||
|
from esphome.automation import maybe_simple_id
|
||||||
|
import esphome.codegen as cg
|
||||||
|
from esphome.components import i2c, sensor
|
||||||
|
import esphome.config_validation as cv
|
||||||
|
from esphome.const import (
|
||||||
|
CONF_BATTERY_LEVEL,
|
||||||
|
CONF_BATTERY_VOLTAGE,
|
||||||
|
CONF_ID,
|
||||||
|
DEVICE_CLASS_BATTERY,
|
||||||
|
DEVICE_CLASS_VOLTAGE,
|
||||||
|
ENTITY_CATEGORY_DIAGNOSTIC,
|
||||||
|
STATE_CLASS_MEASUREMENT,
|
||||||
|
UNIT_PERCENT,
|
||||||
|
UNIT_VOLT,
|
||||||
|
)
|
||||||
|
|
||||||
|
DEPENDENCIES = ["i2c"]
|
||||||
|
|
||||||
|
max17043_ns = cg.esphome_ns.namespace("max17043")
|
||||||
|
MAX17043Component = max17043_ns.class_(
|
||||||
|
"MAX17043Component", cg.PollingComponent, i2c.I2CDevice
|
||||||
|
)
|
||||||
|
|
||||||
|
# Actions
|
||||||
|
SleepAction = max17043_ns.class_("SleepAction", automation.Action)
|
||||||
|
|
||||||
|
CONFIG_SCHEMA = (
|
||||||
|
cv.Schema(
|
||||||
|
{
|
||||||
|
cv.GenerateID(): cv.declare_id(MAX17043Component),
|
||||||
|
cv.Optional(CONF_BATTERY_VOLTAGE): sensor.sensor_schema(
|
||||||
|
unit_of_measurement=UNIT_VOLT,
|
||||||
|
accuracy_decimals=3,
|
||||||
|
device_class=DEVICE_CLASS_VOLTAGE,
|
||||||
|
state_class=STATE_CLASS_MEASUREMENT,
|
||||||
|
entity_category=ENTITY_CATEGORY_DIAGNOSTIC,
|
||||||
|
),
|
||||||
|
cv.Optional(CONF_BATTERY_LEVEL): sensor.sensor_schema(
|
||||||
|
unit_of_measurement=UNIT_PERCENT,
|
||||||
|
accuracy_decimals=3,
|
||||||
|
device_class=DEVICE_CLASS_BATTERY,
|
||||||
|
state_class=STATE_CLASS_MEASUREMENT,
|
||||||
|
entity_category=ENTITY_CATEGORY_DIAGNOSTIC,
|
||||||
|
),
|
||||||
|
}
|
||||||
|
)
|
||||||
|
.extend(cv.polling_component_schema("60s"))
|
||||||
|
.extend(i2c.i2c_device_schema(0x36))
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
async def to_code(config):
|
||||||
|
var = cg.new_Pvariable(config[CONF_ID])
|
||||||
|
await cg.register_component(var, config)
|
||||||
|
await i2c.register_i2c_device(var, config)
|
||||||
|
|
||||||
|
if voltage_config := config.get(CONF_BATTERY_VOLTAGE):
|
||||||
|
sens = await sensor.new_sensor(voltage_config)
|
||||||
|
cg.add(var.set_voltage_sensor(sens))
|
||||||
|
|
||||||
|
if CONF_BATTERY_LEVEL in config:
|
||||||
|
sens = await sensor.new_sensor(config[CONF_BATTERY_LEVEL])
|
||||||
|
cg.add(var.set_battery_remaining_sensor(sens))
|
||||||
|
|
||||||
|
|
||||||
|
MAX17043_ACTION_SCHEMA = maybe_simple_id(
|
||||||
|
{
|
||||||
|
cv.Required(CONF_ID): cv.use_id(MAX17043Component),
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@automation.register_action("max17043.sleep_mode", SleepAction, MAX17043_ACTION_SCHEMA)
|
||||||
|
async def max17043_sleep_mode_to_code(config, action_id, template_arg, args):
|
||||||
|
paren = await cg.get_variable(config[CONF_ID])
|
||||||
|
return cg.new_Pvariable(action_id, template_arg, paren)
|
||||||
@@ -3,12 +3,16 @@ psram:
|
|||||||
speed: 80MHz
|
speed: 80MHz
|
||||||
|
|
||||||
i2c:
|
i2c:
|
||||||
|
id: i2c_main
|
||||||
sda: 8
|
sda: 8
|
||||||
scl: 9
|
scl: 9
|
||||||
|
frequency: 400kHz
|
||||||
|
scan: false
|
||||||
|
|
||||||
# CH422G I/O expander (Waveshare uses it for LCD reset/backlight/touch reset)
|
# CH422G I/O expander (Waveshare uses it for LCD reset/backlight/touch reset)
|
||||||
ch422g:
|
ch422g:
|
||||||
- id: ch422g_hub
|
- id: ch422g_hub
|
||||||
|
i2c_id: i2c_main
|
||||||
|
|
||||||
# --- Backlight control (CH422G IO2 is common for Waveshare LCD BL) ---
|
# --- Backlight control (CH422G IO2 is common for Waveshare LCD BL) ---
|
||||||
switch:
|
switch:
|
||||||
@@ -81,7 +85,8 @@ display:
|
|||||||
touchscreen:
|
touchscreen:
|
||||||
platform: gt911
|
platform: gt911
|
||||||
id: touch_panel
|
id: touch_panel
|
||||||
update_interval: 120ms
|
i2c_id: i2c_main
|
||||||
|
interrupt_pin: 4
|
||||||
reset_pin:
|
reset_pin:
|
||||||
ch422g: ch422g_hub
|
ch422g: ch422g_hub
|
||||||
number: 1
|
number: 1
|
||||||
@@ -104,6 +109,8 @@ font:
|
|||||||
- "\U000F0335" # mdi:lightbulb
|
- "\U000F0335" # mdi:lightbulb
|
||||||
- "\U000F0425" # mdi:power
|
- "\U000F0425" # mdi:power
|
||||||
- "\U000F0426" # mdi:power-plug
|
- "\U000F0426" # mdi:power-plug
|
||||||
|
- "\U000F07E9" # mdi:power-socket-us
|
||||||
|
- "\U000F1A26" # mdi:toggle-switch-variant-off
|
||||||
# Battery status icons
|
# Battery status icons
|
||||||
- "\U000F0079" # mdi:battery
|
- "\U000F0079" # mdi:battery
|
||||||
- "\U000F12A1" # mdi:battery-low
|
- "\U000F12A1" # mdi:battery-low
|
||||||
@@ -127,3 +134,43 @@ font:
|
|||||||
# Note: On ESP32-S3-Touch-LCD-7, GPIO14 is used by the RGB display bus,
|
# Note: On ESP32-S3-Touch-LCD-7, GPIO14 is used by the RGB display bus,
|
||||||
# so it cannot be reused as ADC for battery telemetry in this display mode.
|
# so it cannot be reused as ADC for battery telemetry in this display mode.
|
||||||
|
|
||||||
|
# --- Battery fuel gauge (Adafruit MAX17048 via compatible driver) ---
|
||||||
|
sensor:
|
||||||
|
- platform: max17043
|
||||||
|
id: max17048_battery
|
||||||
|
i2c_id: i2c_main
|
||||||
|
update_interval: 120s
|
||||||
|
battery_voltage:
|
||||||
|
name: "Remote Battery Voltage"
|
||||||
|
id: remote_battery_voltage
|
||||||
|
entity_category: diagnostic
|
||||||
|
device_class: voltage
|
||||||
|
state_class: measurement
|
||||||
|
accuracy_decimals: 3
|
||||||
|
on_value:
|
||||||
|
then:
|
||||||
|
- lambda: |-
|
||||||
|
ESP_LOGI("battery", "Voltage: %.3f V", x);
|
||||||
|
battery_level:
|
||||||
|
name: "Remote Battery Level"
|
||||||
|
id: remote_battery_level
|
||||||
|
entity_category: diagnostic
|
||||||
|
device_class: battery
|
||||||
|
state_class: measurement
|
||||||
|
accuracy_decimals: 0
|
||||||
|
on_value:
|
||||||
|
then:
|
||||||
|
- lambda: |-
|
||||||
|
const float pct = x;
|
||||||
|
ESP_LOGI("battery", "Level: %.0f%%", pct);
|
||||||
|
# - platform: custom
|
||||||
|
# lambda: |-
|
||||||
|
# auto max17048_sensor = new MAX17048Sensor();
|
||||||
|
# App.register_component(max17048_sensor);
|
||||||
|
# return {max17048_sensor->voltage_sensor, max17048_sensor->percentage_sensor};
|
||||||
|
# sensors:
|
||||||
|
# - name: "Voltage"
|
||||||
|
# unit_of_measurement: V
|
||||||
|
# accuracy_decimals: 2
|
||||||
|
# - name: "Percentage"
|
||||||
|
# unit_of_measurement: '%'
|
||||||
|
|||||||
@@ -17,6 +17,9 @@ text_sensor:
|
|||||||
const lv_color_t text_color = (x == "on") ? lv_color_hex(0x001A33) : lv_color_hex(0xE3E2E6);
|
const lv_color_t text_color = (x == "on") ? lv_color_hex(0x001A33) : lv_color_hex(0xE3E2E6);
|
||||||
auto *btn = id(btn_family_room_tv_stand);
|
auto *btn = id(btn_family_room_tv_stand);
|
||||||
lv_obj_set_style_text_color(btn, text_color, static_cast<lv_style_selector_t>(LV_PART_MAIN | LV_STATE_DEFAULT));
|
lv_obj_set_style_text_color(btn, text_color, static_cast<lv_style_selector_t>(LV_PART_MAIN | LV_STATE_DEFAULT));
|
||||||
|
for (uint32_t i = 0; i < lv_obj_get_child_cnt(btn); i++) {
|
||||||
|
lv_obj_set_style_text_color(lv_obj_get_child(btn, i), text_color, static_cast<lv_style_selector_t>(LV_PART_MAIN | LV_STATE_DEFAULT));
|
||||||
|
}
|
||||||
- platform: homeassistant
|
- platform: homeassistant
|
||||||
id: ts_small_family_room_lamp
|
id: ts_small_family_room_lamp
|
||||||
entity_id: light.small_family_room_lamp
|
entity_id: light.small_family_room_lamp
|
||||||
@@ -31,6 +34,9 @@ text_sensor:
|
|||||||
const lv_color_t text_color = (x == "on") ? lv_color_hex(0x22001F) : lv_color_hex(0xE3E2E6);
|
const lv_color_t text_color = (x == "on") ? lv_color_hex(0x22001F) : lv_color_hex(0xE3E2E6);
|
||||||
auto *btn = id(btn_small_family_room_lamp);
|
auto *btn = id(btn_small_family_room_lamp);
|
||||||
lv_obj_set_style_text_color(btn, text_color, static_cast<lv_style_selector_t>(LV_PART_MAIN | LV_STATE_DEFAULT));
|
lv_obj_set_style_text_color(btn, text_color, static_cast<lv_style_selector_t>(LV_PART_MAIN | LV_STATE_DEFAULT));
|
||||||
|
for (uint32_t i = 0; i < lv_obj_get_child_cnt(btn); i++) {
|
||||||
|
lv_obj_set_style_text_color(lv_obj_get_child(btn, i), text_color, static_cast<lv_style_selector_t>(LV_PART_MAIN | LV_STATE_DEFAULT));
|
||||||
|
}
|
||||||
- platform: homeassistant
|
- platform: homeassistant
|
||||||
id: ts_living_room_lamp_1
|
id: ts_living_room_lamp_1
|
||||||
entity_id: light.living_room_lamp_1
|
entity_id: light.living_room_lamp_1
|
||||||
@@ -45,6 +51,9 @@ text_sensor:
|
|||||||
const lv_color_t text_color = (x == "on") ? lv_color_hex(0x3D0002) : lv_color_hex(0xE3E2E6);
|
const lv_color_t text_color = (x == "on") ? lv_color_hex(0x3D0002) : lv_color_hex(0xE3E2E6);
|
||||||
auto *btn = id(btn_family_room_standing_lamp);
|
auto *btn = id(btn_family_room_standing_lamp);
|
||||||
lv_obj_set_style_text_color(btn, text_color, static_cast<lv_style_selector_t>(LV_PART_MAIN | LV_STATE_DEFAULT));
|
lv_obj_set_style_text_color(btn, text_color, static_cast<lv_style_selector_t>(LV_PART_MAIN | LV_STATE_DEFAULT));
|
||||||
|
for (uint32_t i = 0; i < lv_obj_get_child_cnt(btn); i++) {
|
||||||
|
lv_obj_set_style_text_color(lv_obj_get_child(btn, i), text_color, static_cast<lv_style_selector_t>(LV_PART_MAIN | LV_STATE_DEFAULT));
|
||||||
|
}
|
||||||
- platform: homeassistant
|
- platform: homeassistant
|
||||||
id: ts_living_room_light_2
|
id: ts_living_room_light_2
|
||||||
entity_id: light.living_room_light_2
|
entity_id: light.living_room_light_2
|
||||||
@@ -59,6 +68,9 @@ text_sensor:
|
|||||||
const lv_color_t text_color = (x == "on") ? lv_color_hex(0x001A33) : lv_color_hex(0xE3E2E6);
|
const lv_color_t text_color = (x == "on") ? lv_color_hex(0x001A33) : lv_color_hex(0xE3E2E6);
|
||||||
auto *btn = id(btn_living_room_main_light);
|
auto *btn = id(btn_living_room_main_light);
|
||||||
lv_obj_set_style_text_color(btn, text_color, static_cast<lv_style_selector_t>(LV_PART_MAIN | LV_STATE_DEFAULT));
|
lv_obj_set_style_text_color(btn, text_color, static_cast<lv_style_selector_t>(LV_PART_MAIN | LV_STATE_DEFAULT));
|
||||||
|
for (uint32_t i = 0; i < lv_obj_get_child_cnt(btn); i++) {
|
||||||
|
lv_obj_set_style_text_color(lv_obj_get_child(btn, i), text_color, static_cast<lv_style_selector_t>(LV_PART_MAIN | LV_STATE_DEFAULT));
|
||||||
|
}
|
||||||
- platform: homeassistant
|
- platform: homeassistant
|
||||||
id: ts_patio_light_1
|
id: ts_patio_light_1
|
||||||
entity_id: light.patio_light_1
|
entity_id: light.patio_light_1
|
||||||
@@ -79,10 +91,22 @@ text_sensor:
|
|||||||
auto *btn2 = id(btn_outside_patio_light);
|
auto *btn2 = id(btn_outside_patio_light);
|
||||||
if (x == "on") {
|
if (x == "on") {
|
||||||
lv_obj_set_style_text_color(btn1, lv_color_hex(0x1A0C00), static_cast<lv_style_selector_t>(LV_PART_MAIN | LV_STATE_DEFAULT));
|
lv_obj_set_style_text_color(btn1, lv_color_hex(0x1A0C00), static_cast<lv_style_selector_t>(LV_PART_MAIN | LV_STATE_DEFAULT));
|
||||||
|
for (uint32_t i = 0; i < lv_obj_get_child_cnt(btn1); i++) {
|
||||||
|
lv_obj_set_style_text_color(lv_obj_get_child(btn1, i), lv_color_hex(0x1A0C00), static_cast<lv_style_selector_t>(LV_PART_MAIN | LV_STATE_DEFAULT));
|
||||||
|
}
|
||||||
lv_obj_set_style_text_color(btn2, lv_color_hex(0x001A33), static_cast<lv_style_selector_t>(LV_PART_MAIN | LV_STATE_DEFAULT));
|
lv_obj_set_style_text_color(btn2, lv_color_hex(0x001A33), static_cast<lv_style_selector_t>(LV_PART_MAIN | LV_STATE_DEFAULT));
|
||||||
|
for (uint32_t i = 0; i < lv_obj_get_child_cnt(btn2); i++) {
|
||||||
|
lv_obj_set_style_text_color(lv_obj_get_child(btn2, i), lv_color_hex(0x001A33), static_cast<lv_style_selector_t>(LV_PART_MAIN | LV_STATE_DEFAULT));
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
lv_obj_set_style_text_color(btn1, off_color, static_cast<lv_style_selector_t>(LV_PART_MAIN | LV_STATE_DEFAULT));
|
lv_obj_set_style_text_color(btn1, off_color, static_cast<lv_style_selector_t>(LV_PART_MAIN | LV_STATE_DEFAULT));
|
||||||
|
for (uint32_t i = 0; i < lv_obj_get_child_cnt(btn1); i++) {
|
||||||
|
lv_obj_set_style_text_color(lv_obj_get_child(btn1, i), off_color, static_cast<lv_style_selector_t>(LV_PART_MAIN | LV_STATE_DEFAULT));
|
||||||
|
}
|
||||||
lv_obj_set_style_text_color(btn2, off_color, static_cast<lv_style_selector_t>(LV_PART_MAIN | LV_STATE_DEFAULT));
|
lv_obj_set_style_text_color(btn2, off_color, static_cast<lv_style_selector_t>(LV_PART_MAIN | LV_STATE_DEFAULT));
|
||||||
|
for (uint32_t i = 0; i < lv_obj_get_child_cnt(btn2); i++) {
|
||||||
|
lv_obj_set_style_text_color(lv_obj_get_child(btn2, i), off_color, static_cast<lv_style_selector_t>(LV_PART_MAIN | LV_STATE_DEFAULT));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
- platform: homeassistant
|
- platform: homeassistant
|
||||||
id: ts_kitchen_sink_light
|
id: ts_kitchen_sink_light
|
||||||
@@ -98,6 +122,9 @@ text_sensor:
|
|||||||
const lv_color_t text_color = (x == "on") ? lv_color_hex(0x001A33) : lv_color_hex(0xE3E2E6);
|
const lv_color_t text_color = (x == "on") ? lv_color_hex(0x001A33) : lv_color_hex(0xE3E2E6);
|
||||||
auto *btn = id(btn_kitchen_sink_light);
|
auto *btn = id(btn_kitchen_sink_light);
|
||||||
lv_obj_set_style_text_color(btn, text_color, static_cast<lv_style_selector_t>(LV_PART_MAIN | LV_STATE_DEFAULT));
|
lv_obj_set_style_text_color(btn, text_color, static_cast<lv_style_selector_t>(LV_PART_MAIN | LV_STATE_DEFAULT));
|
||||||
|
for (uint32_t i = 0; i < lv_obj_get_child_cnt(btn); i++) {
|
||||||
|
lv_obj_set_style_text_color(lv_obj_get_child(btn, i), text_color, static_cast<lv_style_selector_t>(LV_PART_MAIN | LV_STATE_DEFAULT));
|
||||||
|
}
|
||||||
- platform: homeassistant
|
- platform: homeassistant
|
||||||
id: ts_office_lamp_2
|
id: ts_office_lamp_2
|
||||||
entity_id: light.office_lamp_2
|
entity_id: light.office_lamp_2
|
||||||
@@ -112,6 +139,9 @@ text_sensor:
|
|||||||
const lv_color_t text_color = (x == "on") ? lv_color_hex(0x001A33) : lv_color_hex(0xE3E2E6);
|
const lv_color_t text_color = (x == "on") ? lv_color_hex(0x001A33) : lv_color_hex(0xE3E2E6);
|
||||||
auto *btn = id(btn_office_lamp);
|
auto *btn = id(btn_office_lamp);
|
||||||
lv_obj_set_style_text_color(btn, text_color, static_cast<lv_style_selector_t>(LV_PART_MAIN | LV_STATE_DEFAULT));
|
lv_obj_set_style_text_color(btn, text_color, static_cast<lv_style_selector_t>(LV_PART_MAIN | LV_STATE_DEFAULT));
|
||||||
|
for (uint32_t i = 0; i < lv_obj_get_child_cnt(btn); i++) {
|
||||||
|
lv_obj_set_style_text_color(lv_obj_get_child(btn, i), text_color, static_cast<lv_style_selector_t>(LV_PART_MAIN | LV_STATE_DEFAULT));
|
||||||
|
}
|
||||||
- platform: homeassistant
|
- platform: homeassistant
|
||||||
id: ts_office_led_strip
|
id: ts_office_led_strip
|
||||||
entity_id: light.led_strip_controller_led_strip_controller
|
entity_id: light.led_strip_controller_led_strip_controller
|
||||||
@@ -126,6 +156,9 @@ text_sensor:
|
|||||||
const lv_color_t text_color = (x == "on") ? lv_color_hex(0x22001F) : lv_color_hex(0xE3E2E6);
|
const lv_color_t text_color = (x == "on") ? lv_color_hex(0x22001F) : lv_color_hex(0xE3E2E6);
|
||||||
auto *btn = id(btn_office_led_strip);
|
auto *btn = id(btn_office_led_strip);
|
||||||
lv_obj_set_style_text_color(btn, text_color, static_cast<lv_style_selector_t>(LV_PART_MAIN | LV_STATE_DEFAULT));
|
lv_obj_set_style_text_color(btn, text_color, static_cast<lv_style_selector_t>(LV_PART_MAIN | LV_STATE_DEFAULT));
|
||||||
|
for (uint32_t i = 0; i < lv_obj_get_child_cnt(btn); i++) {
|
||||||
|
lv_obj_set_style_text_color(lv_obj_get_child(btn, i), text_color, static_cast<lv_style_selector_t>(LV_PART_MAIN | LV_STATE_DEFAULT));
|
||||||
|
}
|
||||||
- platform: homeassistant
|
- platform: homeassistant
|
||||||
id: ts_garage_cam_light
|
id: ts_garage_cam_light
|
||||||
entity_id: light.esp32_saturn4_cam_esp32_saturn4_cam_light
|
entity_id: light.esp32_saturn4_cam_esp32_saturn4_cam_light
|
||||||
@@ -140,6 +173,9 @@ text_sensor:
|
|||||||
const lv_color_t text_color = (x == "on") ? lv_color_hex(0x001A33) : lv_color_hex(0xE3E2E6);
|
const lv_color_t text_color = (x == "on") ? lv_color_hex(0x001A33) : lv_color_hex(0xE3E2E6);
|
||||||
auto *btn = id(btn_garage_cam_light);
|
auto *btn = id(btn_garage_cam_light);
|
||||||
lv_obj_set_style_text_color(btn, text_color, static_cast<lv_style_selector_t>(LV_PART_MAIN | LV_STATE_DEFAULT));
|
lv_obj_set_style_text_color(btn, text_color, static_cast<lv_style_selector_t>(LV_PART_MAIN | LV_STATE_DEFAULT));
|
||||||
|
for (uint32_t i = 0; i < lv_obj_get_child_cnt(btn); i++) {
|
||||||
|
lv_obj_set_style_text_color(lv_obj_get_child(btn, i), text_color, static_cast<lv_style_selector_t>(LV_PART_MAIN | LV_STATE_DEFAULT));
|
||||||
|
}
|
||||||
- platform: homeassistant
|
- platform: homeassistant
|
||||||
id: ts_office_echo_plug
|
id: ts_office_echo_plug
|
||||||
entity_id: switch.office_echo_plug
|
entity_id: switch.office_echo_plug
|
||||||
@@ -154,6 +190,9 @@ text_sensor:
|
|||||||
const lv_color_t text_color = (x == "on") ? lv_color_hex(0x1A0C00) : lv_color_hex(0xE3E2E6);
|
const lv_color_t text_color = (x == "on") ? lv_color_hex(0x1A0C00) : lv_color_hex(0xE3E2E6);
|
||||||
auto *btn = id(btn_office_echo_plug);
|
auto *btn = id(btn_office_echo_plug);
|
||||||
lv_obj_set_style_text_color(btn, text_color, static_cast<lv_style_selector_t>(LV_PART_MAIN | LV_STATE_DEFAULT));
|
lv_obj_set_style_text_color(btn, text_color, static_cast<lv_style_selector_t>(LV_PART_MAIN | LV_STATE_DEFAULT));
|
||||||
|
for (uint32_t i = 0; i < lv_obj_get_child_cnt(btn); i++) {
|
||||||
|
lv_obj_set_style_text_color(lv_obj_get_child(btn, i), text_color, static_cast<lv_style_selector_t>(LV_PART_MAIN | LV_STATE_DEFAULT));
|
||||||
|
}
|
||||||
- platform: homeassistant
|
- platform: homeassistant
|
||||||
id: ts_office_end_table_lamp_outlet
|
id: ts_office_end_table_lamp_outlet
|
||||||
entity_id: switch.office_end_table_lamp_socket_1
|
entity_id: switch.office_end_table_lamp_socket_1
|
||||||
@@ -168,6 +207,9 @@ text_sensor:
|
|||||||
const lv_color_t text_color = (x == "on") ? lv_color_hex(0x001F15) : lv_color_hex(0xE3E2E6);
|
const lv_color_t text_color = (x == "on") ? lv_color_hex(0x001F15) : lv_color_hex(0xE3E2E6);
|
||||||
auto *btn = id(btn_office_end_table_lamp_outlet);
|
auto *btn = id(btn_office_end_table_lamp_outlet);
|
||||||
lv_obj_set_style_text_color(btn, text_color, static_cast<lv_style_selector_t>(LV_PART_MAIN | LV_STATE_DEFAULT));
|
lv_obj_set_style_text_color(btn, text_color, static_cast<lv_style_selector_t>(LV_PART_MAIN | LV_STATE_DEFAULT));
|
||||||
|
for (uint32_t i = 0; i < lv_obj_get_child_cnt(btn); i++) {
|
||||||
|
lv_obj_set_style_text_color(lv_obj_get_child(btn, i), text_color, static_cast<lv_style_selector_t>(LV_PART_MAIN | LV_STATE_DEFAULT));
|
||||||
|
}
|
||||||
- platform: homeassistant
|
- platform: homeassistant
|
||||||
id: ts_office_wax_warmer_outlet
|
id: ts_office_wax_warmer_outlet
|
||||||
entity_id: switch.office_wax_warmer_socket_1
|
entity_id: switch.office_wax_warmer_socket_1
|
||||||
@@ -182,6 +224,9 @@ text_sensor:
|
|||||||
const lv_color_t text_color = (x == "on") ? lv_color_hex(0x1E0F3F) : lv_color_hex(0xE3E2E6);
|
const lv_color_t text_color = (x == "on") ? lv_color_hex(0x1E0F3F) : lv_color_hex(0xE3E2E6);
|
||||||
auto *btn = id(btn_office_wax_warmer_outlet);
|
auto *btn = id(btn_office_wax_warmer_outlet);
|
||||||
lv_obj_set_style_text_color(btn, text_color, static_cast<lv_style_selector_t>(LV_PART_MAIN | LV_STATE_DEFAULT));
|
lv_obj_set_style_text_color(btn, text_color, static_cast<lv_style_selector_t>(LV_PART_MAIN | LV_STATE_DEFAULT));
|
||||||
|
for (uint32_t i = 0; i < lv_obj_get_child_cnt(btn); i++) {
|
||||||
|
lv_obj_set_style_text_color(lv_obj_get_child(btn, i), text_color, static_cast<lv_style_selector_t>(LV_PART_MAIN | LV_STATE_DEFAULT));
|
||||||
|
}
|
||||||
- platform: homeassistant
|
- platform: homeassistant
|
||||||
id: ts_outside_lamppost_outlet_1
|
id: ts_outside_lamppost_outlet_1
|
||||||
entity_id: switch.lamppost_outlets_socket_1
|
entity_id: switch.lamppost_outlets_socket_1
|
||||||
@@ -196,6 +241,9 @@ text_sensor:
|
|||||||
const lv_color_t text_color = (x == "on") ? lv_color_hex(0x22001F) : lv_color_hex(0xE3E2E6);
|
const lv_color_t text_color = (x == "on") ? lv_color_hex(0x22001F) : lv_color_hex(0xE3E2E6);
|
||||||
auto *btn = id(btn_outside_lamppost_outlet_1);
|
auto *btn = id(btn_outside_lamppost_outlet_1);
|
||||||
lv_obj_set_style_text_color(btn, text_color, static_cast<lv_style_selector_t>(LV_PART_MAIN | LV_STATE_DEFAULT));
|
lv_obj_set_style_text_color(btn, text_color, static_cast<lv_style_selector_t>(LV_PART_MAIN | LV_STATE_DEFAULT));
|
||||||
|
for (uint32_t i = 0; i < lv_obj_get_child_cnt(btn); i++) {
|
||||||
|
lv_obj_set_style_text_color(lv_obj_get_child(btn, i), text_color, static_cast<lv_style_selector_t>(LV_PART_MAIN | LV_STATE_DEFAULT));
|
||||||
|
}
|
||||||
- platform: homeassistant
|
- platform: homeassistant
|
||||||
id: ts_outside_lamppost_outlet_2
|
id: ts_outside_lamppost_outlet_2
|
||||||
entity_id: switch.lamppost_outlets_socket_2
|
entity_id: switch.lamppost_outlets_socket_2
|
||||||
@@ -210,6 +258,9 @@ text_sensor:
|
|||||||
const lv_color_t text_color = (x == "on") ? lv_color_hex(0x1A0C00) : lv_color_hex(0xE3E2E6);
|
const lv_color_t text_color = (x == "on") ? lv_color_hex(0x1A0C00) : lv_color_hex(0xE3E2E6);
|
||||||
auto *btn = id(btn_outside_lamppost_outlet_2);
|
auto *btn = id(btn_outside_lamppost_outlet_2);
|
||||||
lv_obj_set_style_text_color(btn, text_color, static_cast<lv_style_selector_t>(LV_PART_MAIN | LV_STATE_DEFAULT));
|
lv_obj_set_style_text_color(btn, text_color, static_cast<lv_style_selector_t>(LV_PART_MAIN | LV_STATE_DEFAULT));
|
||||||
|
for (uint32_t i = 0; i < lv_obj_get_child_cnt(btn); i++) {
|
||||||
|
lv_obj_set_style_text_color(lv_obj_get_child(btn, i), text_color, static_cast<lv_style_selector_t>(LV_PART_MAIN | LV_STATE_DEFAULT));
|
||||||
|
}
|
||||||
- platform: homeassistant
|
- platform: homeassistant
|
||||||
id: ts_outside_porch_decor_outlet
|
id: ts_outside_porch_decor_outlet
|
||||||
entity_id: switch.washing_machine_socket_1
|
entity_id: switch.washing_machine_socket_1
|
||||||
@@ -224,6 +275,9 @@ text_sensor:
|
|||||||
const lv_color_t text_color = (x == "on") ? lv_color_hex(0x001F15) : lv_color_hex(0xE3E2E6);
|
const lv_color_t text_color = (x == "on") ? lv_color_hex(0x001F15) : lv_color_hex(0xE3E2E6);
|
||||||
auto *btn = id(btn_outside_porch_decor_outlet);
|
auto *btn = id(btn_outside_porch_decor_outlet);
|
||||||
lv_obj_set_style_text_color(btn, text_color, static_cast<lv_style_selector_t>(LV_PART_MAIN | LV_STATE_DEFAULT));
|
lv_obj_set_style_text_color(btn, text_color, static_cast<lv_style_selector_t>(LV_PART_MAIN | LV_STATE_DEFAULT));
|
||||||
|
for (uint32_t i = 0; i < lv_obj_get_child_cnt(btn); i++) {
|
||||||
|
lv_obj_set_style_text_color(lv_obj_get_child(btn, i), text_color, static_cast<lv_style_selector_t>(LV_PART_MAIN | LV_STATE_DEFAULT));
|
||||||
|
}
|
||||||
- platform: homeassistant
|
- platform: homeassistant
|
||||||
id: ts_living_room_garland_switch
|
id: ts_living_room_garland_switch
|
||||||
entity_id: switch.big_family_room_lamp
|
entity_id: switch.big_family_room_lamp
|
||||||
@@ -238,6 +292,9 @@ text_sensor:
|
|||||||
const lv_color_t text_color = (x == "on") ? lv_color_hex(0x001F15) : lv_color_hex(0xE3E2E6);
|
const lv_color_t text_color = (x == "on") ? lv_color_hex(0x001F15) : lv_color_hex(0xE3E2E6);
|
||||||
auto *btn = id(btn_living_room_garland_switch);
|
auto *btn = id(btn_living_room_garland_switch);
|
||||||
lv_obj_set_style_text_color(btn, text_color, static_cast<lv_style_selector_t>(LV_PART_MAIN | LV_STATE_DEFAULT));
|
lv_obj_set_style_text_color(btn, text_color, static_cast<lv_style_selector_t>(LV_PART_MAIN | LV_STATE_DEFAULT));
|
||||||
|
for (uint32_t i = 0; i < lv_obj_get_child_cnt(btn); i++) {
|
||||||
|
lv_obj_set_style_text_color(lv_obj_get_child(btn, i), text_color, static_cast<lv_style_selector_t>(LV_PART_MAIN | LV_STATE_DEFAULT));
|
||||||
|
}
|
||||||
- platform: homeassistant
|
- platform: homeassistant
|
||||||
id: ts_living_room_calendar_switch
|
id: ts_living_room_calendar_switch
|
||||||
entity_id: switch.digital_calendar
|
entity_id: switch.digital_calendar
|
||||||
@@ -252,6 +309,9 @@ text_sensor:
|
|||||||
const lv_color_t text_color = (x == "on") ? lv_color_hex(0x1E0F3F) : lv_color_hex(0xE3E2E6);
|
const lv_color_t text_color = (x == "on") ? lv_color_hex(0x1E0F3F) : lv_color_hex(0xE3E2E6);
|
||||||
auto *btn = id(btn_living_room_calendar_switch);
|
auto *btn = id(btn_living_room_calendar_switch);
|
||||||
lv_obj_set_style_text_color(btn, text_color, static_cast<lv_style_selector_t>(LV_PART_MAIN | LV_STATE_DEFAULT));
|
lv_obj_set_style_text_color(btn, text_color, static_cast<lv_style_selector_t>(LV_PART_MAIN | LV_STATE_DEFAULT));
|
||||||
|
for (uint32_t i = 0; i < lv_obj_get_child_cnt(btn); i++) {
|
||||||
|
lv_obj_set_style_text_color(lv_obj_get_child(btn, i), text_color, static_cast<lv_style_selector_t>(LV_PART_MAIN | LV_STATE_DEFAULT));
|
||||||
|
}
|
||||||
- platform: homeassistant
|
- platform: homeassistant
|
||||||
id: ts_upstairs_airquality_oled
|
id: ts_upstairs_airquality_oled
|
||||||
entity_id: switch.airqualitysensor_3_oled_power
|
entity_id: switch.airqualitysensor_3_oled_power
|
||||||
@@ -266,6 +326,9 @@ text_sensor:
|
|||||||
const lv_color_t text_color = (x == "on") ? lv_color_hex(0x001A33) : lv_color_hex(0xE3E2E6);
|
const lv_color_t text_color = (x == "on") ? lv_color_hex(0x001A33) : lv_color_hex(0xE3E2E6);
|
||||||
auto *btn = id(btn_upstairs_airquality_oled);
|
auto *btn = id(btn_upstairs_airquality_oled);
|
||||||
lv_obj_set_style_text_color(btn, text_color, static_cast<lv_style_selector_t>(LV_PART_MAIN | LV_STATE_DEFAULT));
|
lv_obj_set_style_text_color(btn, text_color, static_cast<lv_style_selector_t>(LV_PART_MAIN | LV_STATE_DEFAULT));
|
||||||
|
for (uint32_t i = 0; i < lv_obj_get_child_cnt(btn); i++) {
|
||||||
|
lv_obj_set_style_text_color(lv_obj_get_child(btn, i), text_color, static_cast<lv_style_selector_t>(LV_PART_MAIN | LV_STATE_DEFAULT));
|
||||||
|
}
|
||||||
- platform: homeassistant
|
- platform: homeassistant
|
||||||
id: ts_upstairs_camera_motion
|
id: ts_upstairs_camera_motion
|
||||||
entity_id: switch.upstairs_camera_motion_detection
|
entity_id: switch.upstairs_camera_motion_detection
|
||||||
@@ -280,6 +343,9 @@ text_sensor:
|
|||||||
const lv_color_t text_color = (x == "on") ? lv_color_hex(0x22001F) : lv_color_hex(0xE3E2E6);
|
const lv_color_t text_color = (x == "on") ? lv_color_hex(0x22001F) : lv_color_hex(0xE3E2E6);
|
||||||
auto *btn = id(btn_upstairs_camera_motion);
|
auto *btn = id(btn_upstairs_camera_motion);
|
||||||
lv_obj_set_style_text_color(btn, text_color, static_cast<lv_style_selector_t>(LV_PART_MAIN | LV_STATE_DEFAULT));
|
lv_obj_set_style_text_color(btn, text_color, static_cast<lv_style_selector_t>(LV_PART_MAIN | LV_STATE_DEFAULT));
|
||||||
|
for (uint32_t i = 0; i < lv_obj_get_child_cnt(btn); i++) {
|
||||||
|
lv_obj_set_style_text_color(lv_obj_get_child(btn, i), text_color, static_cast<lv_style_selector_t>(LV_PART_MAIN | LV_STATE_DEFAULT));
|
||||||
|
}
|
||||||
- platform: homeassistant
|
- platform: homeassistant
|
||||||
id: ts_garage_fume_exhaust_fan
|
id: ts_garage_fume_exhaust_fan
|
||||||
entity_id: switch.fume_exhaust_fan
|
entity_id: switch.fume_exhaust_fan
|
||||||
@@ -294,6 +360,9 @@ text_sensor:
|
|||||||
const lv_color_t text_color = (x == "on") ? lv_color_hex(0x22001F) : lv_color_hex(0xE3E2E6);
|
const lv_color_t text_color = (x == "on") ? lv_color_hex(0x22001F) : lv_color_hex(0xE3E2E6);
|
||||||
auto *btn = id(btn_garage_fume_exhaust_fan);
|
auto *btn = id(btn_garage_fume_exhaust_fan);
|
||||||
lv_obj_set_style_text_color(btn, text_color, static_cast<lv_style_selector_t>(LV_PART_MAIN | LV_STATE_DEFAULT));
|
lv_obj_set_style_text_color(btn, text_color, static_cast<lv_style_selector_t>(LV_PART_MAIN | LV_STATE_DEFAULT));
|
||||||
|
for (uint32_t i = 0; i < lv_obj_get_child_cnt(btn); i++) {
|
||||||
|
lv_obj_set_style_text_color(lv_obj_get_child(btn, i), text_color, static_cast<lv_style_selector_t>(LV_PART_MAIN | LV_STATE_DEFAULT));
|
||||||
|
}
|
||||||
- platform: homeassistant
|
- platform: homeassistant
|
||||||
id: ts_garage_resin_printer_heater
|
id: ts_garage_resin_printer_heater
|
||||||
entity_id: switch.resin_printer_heater
|
entity_id: switch.resin_printer_heater
|
||||||
@@ -308,4 +377,7 @@ text_sensor:
|
|||||||
const lv_color_t text_color = (x == "on") ? lv_color_hex(0x1A0C00) : lv_color_hex(0xE3E2E6);
|
const lv_color_t text_color = (x == "on") ? lv_color_hex(0x1A0C00) : lv_color_hex(0xE3E2E6);
|
||||||
auto *btn = id(btn_garage_resin_printer_heater);
|
auto *btn = id(btn_garage_resin_printer_heater);
|
||||||
lv_obj_set_style_text_color(btn, text_color, static_cast<lv_style_selector_t>(LV_PART_MAIN | LV_STATE_DEFAULT));
|
lv_obj_set_style_text_color(btn, text_color, static_cast<lv_style_selector_t>(LV_PART_MAIN | LV_STATE_DEFAULT));
|
||||||
|
for (uint32_t i = 0; i < lv_obj_get_child_cnt(btn); i++) {
|
||||||
|
lv_obj_set_style_text_color(lv_obj_get_child(btn, i), text_color, static_cast<lv_style_selector_t>(LV_PART_MAIN | LV_STATE_DEFAULT));
|
||||||
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -102,7 +102,7 @@ lvgl:
|
|||||||
text_color: 0xE3E2E6
|
text_color: 0xE3E2E6
|
||||||
pages:
|
pages:
|
||||||
#############################################
|
#############################################
|
||||||
# Family Room #
|
# Family Room #
|
||||||
#############################################
|
#############################################
|
||||||
- id: home
|
- id: home
|
||||||
widgets:
|
widgets:
|
||||||
@@ -259,8 +259,11 @@ lvgl:
|
|||||||
styles: md3_chip
|
styles: md3_chip
|
||||||
widgets:
|
widgets:
|
||||||
- label:
|
- label:
|
||||||
|
id: nav_battery_icon_home
|
||||||
align: center
|
align: center
|
||||||
text: "\U000F10CD" # mdi:battery-alert-variant-outline-alert-variant-outline
|
text_font: mdi_icons
|
||||||
|
text_color: 0xC4C6CF
|
||||||
|
text: "\U000F10CD" # mdi:battery-alert-variant-outline
|
||||||
- button:
|
- button:
|
||||||
x: 100
|
x: 100
|
||||||
y: 0
|
y: 0
|
||||||
@@ -518,7 +521,7 @@ lvgl:
|
|||||||
x: 18
|
x: 18
|
||||||
y: 56
|
y: 56
|
||||||
styles: md3_title
|
styles: md3_title
|
||||||
text: "Main Light"
|
text: "Large Living Room Lamp"
|
||||||
- label:
|
- label:
|
||||||
x: 18
|
x: 18
|
||||||
y: 90
|
y: 90
|
||||||
@@ -543,7 +546,7 @@ lvgl:
|
|||||||
- homeassistant.service:
|
- homeassistant.service:
|
||||||
service: light.toggle
|
service: light.toggle
|
||||||
data:
|
data:
|
||||||
entity_id: light.patio_light_1
|
entity_id: light.small_living_room_lamp
|
||||||
widgets:
|
widgets:
|
||||||
- label:
|
- label:
|
||||||
x: 204
|
x: 204
|
||||||
@@ -560,7 +563,7 @@ lvgl:
|
|||||||
x: 18
|
x: 18
|
||||||
y: 56
|
y: 56
|
||||||
styles: md3_title
|
styles: md3_title
|
||||||
text: "Patio Light"
|
text: "Small Living Room Lamp"
|
||||||
- label:
|
- label:
|
||||||
x: 18
|
x: 18
|
||||||
y: 90
|
y: 90
|
||||||
@@ -592,7 +595,7 @@ lvgl:
|
|||||||
y: 16
|
y: 16
|
||||||
styles: md3_icon
|
styles: md3_icon
|
||||||
text_font: mdi_icons
|
text_font: mdi_icons
|
||||||
text: "\U000F0425" # mdi:power
|
text: "\U000F1A26" # mdi:toggle-switch-variant-off
|
||||||
- label:
|
- label:
|
||||||
x: 18
|
x: 18
|
||||||
y: 16
|
y: 16
|
||||||
@@ -634,7 +637,7 @@ lvgl:
|
|||||||
y: 16
|
y: 16
|
||||||
styles: md3_icon
|
styles: md3_icon
|
||||||
text_font: mdi_icons
|
text_font: mdi_icons
|
||||||
text: "\U000F0425" # mdi:power
|
text: "\U000F1A26" # mdi:toggle-switch-variant-off
|
||||||
- label:
|
- label:
|
||||||
x: 18
|
x: 18
|
||||||
y: 16
|
y: 16
|
||||||
@@ -666,7 +669,10 @@ lvgl:
|
|||||||
styles: md3_chip
|
styles: md3_chip
|
||||||
widgets:
|
widgets:
|
||||||
- label:
|
- label:
|
||||||
|
id: nav_battery_icon_living_room
|
||||||
align: center
|
align: center
|
||||||
|
text_font: mdi_icons
|
||||||
|
text_color: 0xC4C6CF
|
||||||
text: "\U000F10CD" # mdi:battery-alert-variant-outline
|
text: "\U000F10CD" # mdi:battery-alert-variant-outline
|
||||||
- button:
|
- button:
|
||||||
x: 100
|
x: 100
|
||||||
@@ -947,7 +953,10 @@ lvgl:
|
|||||||
styles: md3_chip
|
styles: md3_chip
|
||||||
widgets:
|
widgets:
|
||||||
- label:
|
- label:
|
||||||
|
id: nav_battery_icon_kitchen
|
||||||
align: center
|
align: center
|
||||||
|
text_font: mdi_icons
|
||||||
|
text_color: 0xC4C6CF
|
||||||
text: "\U000F10CD" # mdi:battery-alert-variant-outline
|
text: "\U000F10CD" # mdi:battery-alert-variant-outline
|
||||||
- button:
|
- button:
|
||||||
x: 100
|
x: 100
|
||||||
@@ -1196,7 +1205,7 @@ lvgl:
|
|||||||
y: 16
|
y: 16
|
||||||
styles: md3_icon
|
styles: md3_icon
|
||||||
text_font: mdi_icons
|
text_font: mdi_icons
|
||||||
text: "\U000F0425" # mdi:power
|
text: "\U000F1A26" # mdi:toggle-switch-variant-off
|
||||||
- label:
|
- label:
|
||||||
x: 18
|
x: 18
|
||||||
y: 16
|
y: 16
|
||||||
@@ -1238,7 +1247,7 @@ lvgl:
|
|||||||
y: 16
|
y: 16
|
||||||
styles: md3_icon
|
styles: md3_icon
|
||||||
text_font: mdi_icons
|
text_font: mdi_icons
|
||||||
text: "\U000F0425" # mdi:power
|
text: "\U000F1A26" # mdi:toggle-switch-variant-off
|
||||||
- label:
|
- label:
|
||||||
x: 18
|
x: 18
|
||||||
y: 16
|
y: 16
|
||||||
@@ -1270,7 +1279,10 @@ lvgl:
|
|||||||
styles: md3_chip
|
styles: md3_chip
|
||||||
widgets:
|
widgets:
|
||||||
- label:
|
- label:
|
||||||
|
id: nav_battery_icon_upstairs
|
||||||
align: center
|
align: center
|
||||||
|
text_font: mdi_icons
|
||||||
|
text_color: 0xC4C6CF
|
||||||
text: "\U000F10CD" # mdi:battery-alert-variant-outline
|
text: "\U000F10CD" # mdi:battery-alert-variant-outline
|
||||||
- button:
|
- button:
|
||||||
x: 100
|
x: 100
|
||||||
@@ -1603,7 +1615,7 @@ lvgl:
|
|||||||
y: 16
|
y: 16
|
||||||
styles: md3_icon
|
styles: md3_icon
|
||||||
text_font: mdi_icons
|
text_font: mdi_icons
|
||||||
text: "\U000F0426" # mdi:power-plug
|
text: "\U000F07E9" # mdi:power-socket-us
|
||||||
- label:
|
- label:
|
||||||
x: 18
|
x: 18
|
||||||
y: 16
|
y: 16
|
||||||
@@ -1645,7 +1657,7 @@ lvgl:
|
|||||||
y: 16
|
y: 16
|
||||||
styles: md3_icon
|
styles: md3_icon
|
||||||
text_font: mdi_icons
|
text_font: mdi_icons
|
||||||
text: "\U000F0426" # mdi:power-plug
|
text: "\U000F07E9" # mdi:power-socket-us
|
||||||
- label:
|
- label:
|
||||||
x: 18
|
x: 18
|
||||||
y: 16
|
y: 16
|
||||||
@@ -1687,7 +1699,7 @@ lvgl:
|
|||||||
y: 16
|
y: 16
|
||||||
styles: md3_icon
|
styles: md3_icon
|
||||||
text_font: mdi_icons
|
text_font: mdi_icons
|
||||||
text: "\U000F0426" # mdi:power-plug
|
text: "\U000F07E9" # mdi:power-socket-us
|
||||||
- label:
|
- label:
|
||||||
x: 18
|
x: 18
|
||||||
y: 16
|
y: 16
|
||||||
@@ -1719,7 +1731,10 @@ lvgl:
|
|||||||
styles: md3_chip
|
styles: md3_chip
|
||||||
widgets:
|
widgets:
|
||||||
- label:
|
- label:
|
||||||
|
id: nav_battery_icon_office
|
||||||
align: center
|
align: center
|
||||||
|
text_font: mdi_icons
|
||||||
|
text_color: 0xC4C6CF
|
||||||
text: "\U000F10CD" # mdi:battery-alert-variant-outline
|
text: "\U000F10CD" # mdi:battery-alert-variant-outline
|
||||||
- button:
|
- button:
|
||||||
x: 100
|
x: 100
|
||||||
@@ -2010,7 +2025,7 @@ lvgl:
|
|||||||
y: 16
|
y: 16
|
||||||
styles: md3_icon
|
styles: md3_icon
|
||||||
text_font: mdi_icons
|
text_font: mdi_icons
|
||||||
text: "\U000F0425" # mdi:power
|
text: "\U000F1A26" # mdi:toggle-switch-variant-off
|
||||||
- label:
|
- label:
|
||||||
x: 18
|
x: 18
|
||||||
y: 16
|
y: 16
|
||||||
@@ -2052,7 +2067,7 @@ lvgl:
|
|||||||
y: 16
|
y: 16
|
||||||
styles: md3_icon
|
styles: md3_icon
|
||||||
text_font: mdi_icons
|
text_font: mdi_icons
|
||||||
text: "\U000F0425" # mdi:power
|
text: "\U000F1A26" # mdi:toggle-switch-variant-off
|
||||||
- label:
|
- label:
|
||||||
x: 18
|
x: 18
|
||||||
y: 16
|
y: 16
|
||||||
@@ -2084,7 +2099,10 @@ lvgl:
|
|||||||
styles: md3_chip
|
styles: md3_chip
|
||||||
widgets:
|
widgets:
|
||||||
- label:
|
- label:
|
||||||
|
id: nav_battery_icon_garage
|
||||||
align: center
|
align: center
|
||||||
|
text_font: mdi_icons
|
||||||
|
text_color: 0xC4C6CF
|
||||||
text: "\U000F10CD" # mdi:battery-alert-variant-outline
|
text: "\U000F10CD" # mdi:battery-alert-variant-outline
|
||||||
- button:
|
- button:
|
||||||
x: 100
|
x: 100
|
||||||
@@ -2375,7 +2393,7 @@ lvgl:
|
|||||||
y: 16
|
y: 16
|
||||||
styles: md3_icon
|
styles: md3_icon
|
||||||
text_font: mdi_icons
|
text_font: mdi_icons
|
||||||
text: "\U000F0426" # mdi:power-plug
|
text: "\U000F07E9" # mdi:power-socket-us
|
||||||
- label:
|
- label:
|
||||||
x: 18
|
x: 18
|
||||||
y: 16
|
y: 16
|
||||||
@@ -2417,7 +2435,7 @@ lvgl:
|
|||||||
y: 16
|
y: 16
|
||||||
styles: md3_icon
|
styles: md3_icon
|
||||||
text_font: mdi_icons
|
text_font: mdi_icons
|
||||||
text: "\U000F0426" # mdi:power-plug
|
text: "\U000F07E9" # mdi:power-socket-us
|
||||||
- label:
|
- label:
|
||||||
x: 18
|
x: 18
|
||||||
y: 16
|
y: 16
|
||||||
@@ -2459,7 +2477,7 @@ lvgl:
|
|||||||
y: 16
|
y: 16
|
||||||
styles: md3_icon
|
styles: md3_icon
|
||||||
text_font: mdi_icons
|
text_font: mdi_icons
|
||||||
text: "\U000F0426" # mdi:power-plug
|
text: "\U000F07E9" # mdi:power-socket-us
|
||||||
- label:
|
- label:
|
||||||
x: 18
|
x: 18
|
||||||
y: 16
|
y: 16
|
||||||
@@ -2491,7 +2509,10 @@ lvgl:
|
|||||||
styles: md3_chip
|
styles: md3_chip
|
||||||
widgets:
|
widgets:
|
||||||
- label:
|
- label:
|
||||||
|
id: nav_battery_icon_outside
|
||||||
align: center
|
align: center
|
||||||
|
text_font: mdi_icons
|
||||||
|
text_color: 0xC4C6CF
|
||||||
text: "\U000F10CD" # mdi:battery-alert-variant-outline
|
text: "\U000F10CD" # mdi:battery-alert-variant-outline
|
||||||
- button:
|
- button:
|
||||||
x: 100
|
x: 100
|
||||||
|
|||||||
@@ -50,20 +50,20 @@ esp32_camera:
|
|||||||
href_pin: GPIO23
|
href_pin: GPIO23
|
||||||
pixel_clock_pin: GPIO22
|
pixel_clock_pin: GPIO22
|
||||||
power_down_pin: GPIO32
|
power_down_pin: GPIO32
|
||||||
resolution: 800x600
|
resolution: 1024x768
|
||||||
jpeg_quality: 15
|
jpeg_quality: 20
|
||||||
vertical_flip: False
|
vertical_flip: False
|
||||||
horizontal_mirror: False
|
horizontal_mirror: False
|
||||||
max_framerate: 20 fps
|
max_framerate: 40 fps
|
||||||
idle_framerate: 0.05 fps
|
idle_framerate: 0.05 fps
|
||||||
|
|
||||||
# Image settings
|
# Image settings
|
||||||
name: esp32-jordyn-a1-cam
|
name: esp32-jordyn-a1-cam
|
||||||
brightness: 1
|
# brightness: 1
|
||||||
#contrast: 0
|
# #contrast: 0
|
||||||
agc_gain_ceiling: 2X
|
# agc_gain_ceiling: 2X
|
||||||
agc_mode: MANUAL
|
# agc_mode: MANUAL
|
||||||
agc_value: 5
|
# agc_value: 5
|
||||||
# ...
|
# ...
|
||||||
|
|
||||||
esp32_camera_web_server:
|
esp32_camera_web_server:
|
||||||
|
|||||||
@@ -1,6 +1,10 @@
|
|||||||
|
substitutions:
|
||||||
|
device_name: polly-a1-mini-camera
|
||||||
|
friendly_name: "Polly's A1 Camera"
|
||||||
|
|
||||||
esphome:
|
esphome:
|
||||||
name: polly-a1-mini-camera
|
name: ${device_name}
|
||||||
friendly_name: polly-a1-mini-camera
|
friendly_name: ${friendly_name}
|
||||||
|
|
||||||
esp32:
|
esp32:
|
||||||
board: esp32dev
|
board: esp32dev
|
||||||
@@ -10,6 +14,7 @@ esp32:
|
|||||||
|
|
||||||
# Enable logging
|
# Enable logging
|
||||||
logger:
|
logger:
|
||||||
|
level: INFO
|
||||||
|
|
||||||
# Enable Home Assistant API
|
# Enable Home Assistant API
|
||||||
api:
|
api:
|
||||||
@@ -21,16 +26,20 @@ ota:
|
|||||||
password: "58f86a5e60abe60886cec2f65a1d3da5"
|
password: "58f86a5e60abe60886cec2f65a1d3da5"
|
||||||
|
|
||||||
wifi:
|
wifi:
|
||||||
ssid: !secret wifi_ssid
|
ssid: !secret wifi_iot_ssid
|
||||||
password: !secret wifi_password
|
password: !secret wifi_password
|
||||||
|
|
||||||
# Enable fallback hotspot (captive portal) in case wifi connection fails
|
# Enable fallback hotspot (captive portal) in case wifi connection fails
|
||||||
ap:
|
ap:
|
||||||
ssid: "Polly-A1-Mini-Camera"
|
ssid: "${friendly_name} Fallback"
|
||||||
password: "7so1xpjNMIMr"
|
password: !secret wifi_password
|
||||||
|
|
||||||
captive_portal:
|
captive_portal:
|
||||||
|
|
||||||
|
# Web server for standalone control (optional, but useful)
|
||||||
|
web_server:
|
||||||
|
port: 80
|
||||||
|
|
||||||
i2c:
|
i2c:
|
||||||
- id: camera_i2c
|
- id: camera_i2c
|
||||||
sda: GPIO26
|
sda: GPIO26
|
||||||
@@ -50,14 +59,15 @@ esp32_camera:
|
|||||||
href_pin: GPIO23
|
href_pin: GPIO23
|
||||||
pixel_clock_pin: GPIO22
|
pixel_clock_pin: GPIO22
|
||||||
power_down_pin: GPIO32
|
power_down_pin: GPIO32
|
||||||
resolution: 800x600
|
resolution: 1024X768
|
||||||
jpeg_quality: 20
|
jpeg_quality: 20
|
||||||
#horizontal_mirror: False
|
vertical_flip: False
|
||||||
|
horizontal_mirror: True
|
||||||
max_framerate: 20 fps
|
max_framerate: 20 fps
|
||||||
idle_framerate: 0.05 fps
|
idle_framerate: 0.05 fps
|
||||||
|
|
||||||
# Image settings
|
# Image settings
|
||||||
name: esp32-polly-a1-mini-cam
|
name: ${device_name}
|
||||||
# brightness: 1
|
# brightness: 1
|
||||||
# contrast: 0
|
# contrast: 0
|
||||||
# agc_gain_ceiling: 2X
|
# agc_gain_ceiling: 2X
|
||||||
@@ -66,13 +76,13 @@ esp32_camera:
|
|||||||
# ...
|
# ...
|
||||||
|
|
||||||
esp32_camera_web_server:
|
esp32_camera_web_server:
|
||||||
- port: 80
|
|
||||||
mode: STREAM
|
|
||||||
- port: 8080
|
- port: 8080
|
||||||
|
mode: STREAM
|
||||||
|
- port: 8081
|
||||||
mode: SNAPSHOT
|
mode: SNAPSHOT
|
||||||
|
|
||||||
switch:
|
switch:
|
||||||
- platform: gpio
|
- platform: gpio
|
||||||
name: "polly-a1-mini-cam-flash"
|
name: "${device_name}-flash"
|
||||||
pin: 4
|
pin: 4
|
||||||
|
|
||||||
188
esphome/voice-assistant-1.yaml
Normal file
188
esphome/voice-assistant-1.yaml
Normal file
@@ -0,0 +1,188 @@
|
|||||||
|
esphome:
|
||||||
|
name: voice-assistant-1
|
||||||
|
friendly_name: Voice-Assistant-1
|
||||||
|
name_add_mac_suffix: false
|
||||||
|
platformio_options:
|
||||||
|
board_build.flash_mode: dio
|
||||||
|
build_flags:
|
||||||
|
- -DCONFIG_ESP32S3_DEFAULT_CPU_FREQ_240=y
|
||||||
|
- -DCONFIG_FREERTOS_HZ=1000
|
||||||
|
board_build.partitions: partitions.csv
|
||||||
|
on_boot:
|
||||||
|
priority: 600
|
||||||
|
then:
|
||||||
|
- if:
|
||||||
|
condition:
|
||||||
|
switch.is_off: mute
|
||||||
|
then:
|
||||||
|
- micro_wake_word.start:
|
||||||
|
|
||||||
|
esp32:
|
||||||
|
board: esp32-s3-devkitc-1
|
||||||
|
framework:
|
||||||
|
type: esp-idf
|
||||||
|
|
||||||
|
# Enable logging
|
||||||
|
logger:
|
||||||
|
level: WARN
|
||||||
|
|
||||||
|
# Enable Home Assistant API
|
||||||
|
api:
|
||||||
|
encryption:
|
||||||
|
key: "oUOJJQHDaBvzKLAqfh3RwCEy9/KoFcj7AUgCSgTW/D8="
|
||||||
|
|
||||||
|
ota:
|
||||||
|
- platform: esphome
|
||||||
|
password: "48c1380a3d4d99d28bff36b2dc5c5e50"
|
||||||
|
|
||||||
|
wifi:
|
||||||
|
ssid: !secret wifi_iot_ssid
|
||||||
|
password: !secret wifi_password
|
||||||
|
|
||||||
|
# Enable fallback hotspot (captive portal) in case wifi connection fails
|
||||||
|
ap:
|
||||||
|
ssid: "Voice-Assistant-1"
|
||||||
|
password: "pypEyxtF4xJ5"
|
||||||
|
|
||||||
|
captive_portal:
|
||||||
|
|
||||||
|
# web_server:
|
||||||
|
# port: 80
|
||||||
|
|
||||||
|
psram:
|
||||||
|
mode: octal
|
||||||
|
speed: 80MHz
|
||||||
|
|
||||||
|
light:
|
||||||
|
- platform: esp32_rmt_led_strip
|
||||||
|
id: led_ww
|
||||||
|
rgb_order: GRB
|
||||||
|
chipset: ws2812
|
||||||
|
pin: GPIO16
|
||||||
|
num_leds: 4
|
||||||
|
name: "LED bar"
|
||||||
|
effects:
|
||||||
|
- pulse:
|
||||||
|
- pulse:
|
||||||
|
name: fast pulse
|
||||||
|
transition_length: 250ms
|
||||||
|
update_interval: 250ms
|
||||||
|
- addressable_scan:
|
||||||
|
name: scan
|
||||||
|
move_interval: 100ms
|
||||||
|
scan_width: 1
|
||||||
|
|
||||||
|
switch:
|
||||||
|
- platform: template
|
||||||
|
id: mute
|
||||||
|
name: "Mute microphone"
|
||||||
|
optimistic: true
|
||||||
|
on_turn_on:
|
||||||
|
- micro_wake_word.stop:
|
||||||
|
- voice_assistant.stop:
|
||||||
|
- light.turn_on:
|
||||||
|
id: led_ww
|
||||||
|
red: 100%
|
||||||
|
green: 0%
|
||||||
|
blue: 0%
|
||||||
|
brightness: 30%
|
||||||
|
- delay: 2s
|
||||||
|
- light.turn_off:
|
||||||
|
id: led_ww
|
||||||
|
- light.turn_on:
|
||||||
|
id: led_ww
|
||||||
|
red: 100%
|
||||||
|
green: 0%
|
||||||
|
blue: 0%
|
||||||
|
brightness: 30%
|
||||||
|
on_turn_off:
|
||||||
|
- micro_wake_word.start:
|
||||||
|
- light.turn_on:
|
||||||
|
id: led_ww
|
||||||
|
red: 0%
|
||||||
|
green: 100%
|
||||||
|
blue: 0%
|
||||||
|
brightness: 60%
|
||||||
|
effect: fast pulse
|
||||||
|
- delay: 2s
|
||||||
|
- light.turn_off:
|
||||||
|
id: led_ww
|
||||||
|
|
||||||
|
i2s_audio:
|
||||||
|
- id: i2s_mic
|
||||||
|
i2s_lrclk_pin: GPIO6 # WS
|
||||||
|
i2s_bclk_pin: GPIO7 # SCK
|
||||||
|
- id: i2s_spk
|
||||||
|
i2s_lrclk_pin: GPIO45 # Use free pins on your board
|
||||||
|
i2s_bclk_pin: GPIO46 # Use free pins on your board
|
||||||
|
|
||||||
|
microphone:
|
||||||
|
- platform: i2s_audio
|
||||||
|
id: va_mic
|
||||||
|
adc_type: external
|
||||||
|
i2s_din_pin: GPIO4 # SD
|
||||||
|
channel: left
|
||||||
|
pdm: false
|
||||||
|
bits_per_sample: 32bit
|
||||||
|
i2s_audio_id: i2s_mic
|
||||||
|
|
||||||
|
output:
|
||||||
|
- platform: gpio
|
||||||
|
pin:
|
||||||
|
number: GPIO8
|
||||||
|
allow_other_uses: true
|
||||||
|
id: set_low_speaker
|
||||||
|
|
||||||
|
speaker:
|
||||||
|
- platform: i2s_audio
|
||||||
|
id: va_speaker
|
||||||
|
i2s_audio_id: i2s_spk
|
||||||
|
dac_type: external
|
||||||
|
i2s_dout_pin:
|
||||||
|
number: GPIO8 # DIN pin of the MAX98357A Audio Amplifier
|
||||||
|
allow_other_uses: true
|
||||||
|
channel: mono
|
||||||
|
bits_per_sample: 32bit
|
||||||
|
sample_rate: 16000
|
||||||
|
|
||||||
|
micro_wake_word:
|
||||||
|
models:
|
||||||
|
- model: hey_jarvis
|
||||||
|
on_wake_word_detected:
|
||||||
|
- voice_assistant.start:
|
||||||
|
- light.turn_on:
|
||||||
|
id: led_ww
|
||||||
|
red: 100%
|
||||||
|
green: 100%
|
||||||
|
blue: 100%
|
||||||
|
brightness: 30%
|
||||||
|
effect: scan
|
||||||
|
|
||||||
|
voice_assistant:
|
||||||
|
id: va
|
||||||
|
microphone: va_mic
|
||||||
|
speaker: va_speaker
|
||||||
|
noise_suppression_level: 2.0
|
||||||
|
auto_gain: 31dBFS
|
||||||
|
volume_multiplier: 4.0
|
||||||
|
on_client_connected:
|
||||||
|
- if:
|
||||||
|
condition:
|
||||||
|
switch.is_off: mute
|
||||||
|
then:
|
||||||
|
- micro_wake_word.start:
|
||||||
|
on_client_disconnected:
|
||||||
|
- micro_wake_word.stop:
|
||||||
|
- voice_assistant.stop:
|
||||||
|
on_stt_end:
|
||||||
|
then:
|
||||||
|
- light.turn_off: led_ww
|
||||||
|
on_error:
|
||||||
|
- micro_wake_word.start:
|
||||||
|
on_end:
|
||||||
|
then:
|
||||||
|
- light.turn_off: led_ww
|
||||||
|
- wait_until:
|
||||||
|
not:
|
||||||
|
voice_assistant.is_running:
|
||||||
|
- micro_wake_word.start:
|
||||||
258
packages/chore-tracker-ha.yaml
Normal file
258
packages/chore-tracker-ha.yaml
Normal file
@@ -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.
|
||||||
|
|
||||||
|
|
||||||
Reference in New Issue
Block a user