initial commit

This commit is contained in:
root
2026-06-28 14:27:20 -04:00
commit ae0f1f559e
115 changed files with 30411 additions and 0 deletions
+128
View File
@@ -0,0 +1,128 @@
# Ignore everything by default
*
# Whitelist config files
!*.yaml
!*.yml
!.gitignore
!*.md
!*.sh
!*.js*
!*.txt*
!*.json*
!*.ui.yaml
!*.py
!*.conf
# Whitelist subdirectory yaml files
!*/*.yaml
!*/*.yml
!*/*.md
!*/*.sh
!*/*.js*
!*/*.txt*
!*/*.json*
!*/*.ui.yaml
!*/*.h
!*/*.py
!*/*/*.py
!*/*/*.yaml
# Whitelist specific folders (uncomment as needed)
### Automations ###
!automations/
!automations/**
### Packages ###
!packages/
!packages/**
!packages/**
!packages/chore-tracker-ha.yaml
### Scenes ###
!scenes/
### Scripts ###
!scripts/
### Blueprints ###
!blueprints/
!blueprints/**
!blueprints/automation/**
!blueprints/automation/homeassistant/*.yaml
!blueprints/automation/nberkturner/*.yaml
### Dashboards ###
!dashboards/
!dashboards/**
### ESPHome Folders ###
!esphome/
!esphome/fonts/**
!esphome/ha-remote/
!esphome/ha-remote/components/
!esphome/ha-remote/components/**
!esphome/ha-remote/components/max17043/
!esphome/components/
!esphome/components/**
!esphome/components/max17043/**
!custom_components/**
!esphome/chore-tracker/*.py
!esphome/chore-tracker/*.yaml
!esphome/chore-tracker/
!esphome/chore-tracker/**
!esphome/family-room-remote/
!esphome/family-room-remote/**
!esphome/family-room-remote/*.yaml
!esphome/chore-tracker-esphome.yaml
!esphome/respeaker-satellite/
!esphome/respeaker-satellite/**
!esphome/respeaker-satellite/*.yaml
!esphome/jarvis-satellite/
!esphome/jarvis-satellite/**
### Helpers ###
!helpers/
!helpers/**
### Themes ###
!themes/
!themes/**
# =============================================
# SENSITIVE FILES - always ignore these
# =============================================
secrets.yaml
known_devices.yaml
ip_bans.yaml
# =============================================
# Generated files and folders - always ignore
# =============================================
.storage
.cloud
.google.token
home-assistant.log
home-assistant_v2.db
home-assistant_v2.db-shm
home-assistant_v2.db-wal
# Dependencies and cache
deps/
tts/
www/
custom_components/
image/
ms365_storage/
# Pickle / session files
*.pickle
*.db
*.db-shm
*.db-wal
# OS files
.DS_Store
Thumbs.db
+903
View File
@@ -0,0 +1,903 @@
# Home and lighting automations.
- id: '1738951431044'
alias: Turn off living room lights
description: ''
triggers:
- entity_id:
- binary_sensor.hsjq_motion
to: 'off'
for:
hours: 0
minutes: 15
seconds: 0
trigger: state
conditions:
- condition: time
after: 09:00:00
before: 06:00:00
weekday:
- sun
- mon
- tue
- wed
- thu
- fri
- sat
actions:
- target:
entity_id: media_player.hisense_g204x_cd41
action: media_player.turn_off
data: {}
enabled: false
- action: light.turn_off
data: {}
target:
entity_id:
- light.living_room_light_2
mode: single
- id: '1739826648241'
alias: Turn on Living room lights motion sense
description: ''
triggers:
- trigger: state
entity_id:
- binary_sensor.hsjq_motion
to: 'on'
conditions:
- condition: and
conditions:
- condition: sun
after: sunset
- condition: time
before: '22:30:00'
actions:
- action: light.turn_on
metadata: {}
data:
color_temp_kelvin: 3701
brightness_pct: 78
target:
device_id:
- d76728b35b6f17a89be03ef00645812e
entity_id: light.living_room_light_2
mode: single
- id: '1739835025769'
alias: Turn on bedroom lights with motion
description: ''
triggers:
- trigger: state
entity_id:
- binary_sensor.bedroom_motion
to: 'on'
conditions:
- condition: time
after: '16:00:00'
before: '20:00:00'
weekday:
- sun
- mon
- tue
- wed
- thu
- fri
- sat
- condition: sun
after: sunset
actions:
- type: turn_on
device_id: a8b108e7836eeb9c66cd0169afc4a486
entity_id: d48390b30da2add2f866a8cec5dc2726
domain: switch
- type: turn_on
device_id: d371342d65df3ddbd28fb841b6ce0a13
entity_id: 7df615008eff2c537a1d6ac4be76def4
domain: switch
- delay: 00:10:00
mode: single
- id: '1739840523909'
alias: Turn off bedside lights in the evening
description: ''
triggers:
- trigger: state
entity_id:
- binary_sensor.bedroom_occupancy
to: 'off'
for:
hours: 0
minutes: 1
seconds: 0
conditions:
- condition: time
after: '16:00:00'
before: '20:00:00'
weekday:
- sun
- mon
- tue
- wed
- thu
- fri
- sat
actions:
- type: turn_off
device_id: a8b108e7836eeb9c66cd0169afc4a486
entity_id: d48390b30da2add2f866a8cec5dc2726
domain: switch
- type: turn_off
device_id: d371342d65df3ddbd28fb841b6ce0a13
entity_id: 7df615008eff2c537a1d6ac4be76def4
domain: switch
mode: single
- id: '1739841110645'
alias: Turn on family room in the morning
description: ''
triggers:
- trigger: state
entity_id:
- binary_sensor.meat_heater_motion
to: 'on'
conditions:
- condition: time
after: 06:00:00
before: 07:45:00
weekday:
- sun
- mon
- tue
- wed
- thu
- fri
- sat
actions:
- type: turn_on
device_id: 389d36aa5a0ecfd3eb2e1ec8795464d0
entity_id: 0ae75b98556682467fedf0ff441346ef
domain: light
brightness_pct: 20
enabled: true
- type: turn_on
device_id: 6f341a97517eecd7e4befd4633bb0c3a
entity_id: 4b21efa79430d63e813137d08a8df60e
domain: light
- type: turn_on
device_id: 77092d966014d6eb6222928cce911caf
entity_id: af55f6e7e0ffa642407923fc6e8cdaf3
domain: light
enabled: false
- type: turn_on
device_id: 3096deb74c1dddf893e78fcdfa5b23aa
entity_id: aa15e9f5b3c00639b64501acd18f2081
domain: switch
- type: turn_on
device_id: 58dfbd5f60265682b98691217b9bd038
entity_id: abf15d05be0dadf1bba145f0835d3b3a
domain: switch
- type: turn_on
device_id: 2fde35e8a4dfe850d53d35a650d05b6e
entity_id: c4ec82fc55ef125a4b39f842e06b7261
domain: switch
- type: turn_on
device_id: 4f7810cf96260d17363b63542447ffe7
entity_id: 70347118804cc5d1b5770362ab369251
domain: switch
- type: turn_on
device_id: 84566a79d41ebedd4512d2418195a898
entity_id: fb7b78a1f6d3146585e2f93be33bf4c1
domain: switch
- type: turn_on
device_id: 88c1bfde7eba191a46899d4c59754512
entity_id: 594a0d24aa2fd2dc2156d9918a2204bf
domain: switch
- type: turn_on
device_id: 00ce230936f351558f177d5ed229759b
entity_id: 9ef15fb77ca7e81bcb6c7551329f50aa
domain: switch
mode: single
- id: '1739970708082'
alias: Lights On Before Sunrise
description: ''
triggers:
- trigger: time
at: 05:45:00
conditions: []
actions:
- type: turn_on
device_id: c58fc0103836dffe365aeb33056cee71
entity_id: 37852e5f667fe36826f2ac1ae9f2376a
domain: switch
- type: turn_on
device_id: 7ed0dde2b0b802820f57f3f9f515adfe
entity_id: de439b7d82897ca4cf67b8aeb53b4eee
domain: switch
- type: turn_on
device_id: 389d36aa5a0ecfd3eb2e1ec8795464d0
entity_id: 0ae75b98556682467fedf0ff441346ef
domain: light
- type: turn_on
device_id: 31d67bb445f5c8920a1dc076d0d71e76
entity_id: 6c1fd91e0ff15419f7bdb6b9e233cffc
domain: switch
- type: turn_on
device_id: 77092d966014d6eb6222928cce911caf
entity_id: af55f6e7e0ffa642407923fc6e8cdaf3
domain: light
enabled: false
- type: turn_on
device_id: 00ce230936f351558f177d5ed229759b
entity_id: 9ef15fb77ca7e81bcb6c7551329f50aa
domain: switch
enabled: false
- type: turn_on
device_id: 6f341a97517eecd7e4befd4633bb0c3a
entity_id: 4b21efa79430d63e813137d08a8df60e
domain: light
enabled: false
mode: single
- id: '1739970910638'
alias: 10PM Turn Jordyn's Lamp Off
description: ''
triggers:
- trigger: time
at: '22:00:00'
conditions: []
actions:
- type: turn_off
device_id: ef731a5e6e666bf4fc20cc321f596029
entity_id: 9629035816fddf7de8926e4e4c29aa34
domain: switch
mode: single
- id: '1739971192633'
alias: Turn On Lights in Evening
description: ''
triggers:
- trigger: sun
event: sunset
offset: -00:30:00
conditions: []
actions:
- type: turn_on
device_id: e04306aa947ba9901108c363d8109ca0
entity_id: 12662d2294935b2a21500e3f6d69ba15
domain: switch
enabled: true
- type: turn_on
device_id: e2343a74462cc1434c4acfa2e32d1364
entity_id: ea84e174f0fa3316b9923c4254454a37
domain: switch
enabled: true
- type: turn_on
device_id: c0de273e40c6f4f1cafe4db27e5a6782
entity_id: e6f4af2d9bc05634f994af34bad9a477
domain: switch
enabled: true
- type: turn_on
device_id: 389d36aa5a0ecfd3eb2e1ec8795464d0
entity_id: 0ae75b98556682467fedf0ff441346ef
domain: light
brightness_pct: 50
enabled: true
- type: turn_on
device_id: 7ed0dde2b0b802820f57f3f9f515adfe
entity_id: de439b7d82897ca4cf67b8aeb53b4eee
domain: switch
- type: turn_on
device_id: a1fdc7c838bdded4d8e90bb7941476a3
entity_id: 84d24335edc946153ed4f81ac7906f3c
domain: switch
enabled: true
- type: turn_on
device_id: a8b108e7836eeb9c66cd0169afc4a486
entity_id: d48390b30da2add2f866a8cec5dc2726
domain: switch
- type: turn_on
device_id: d371342d65df3ddbd28fb841b6ce0a13
entity_id: 7df615008eff2c537a1d6ac4be76def4
domain: switch
- type: turn_on
device_id: 872cdc7e609c8df8544349fceae0828e
entity_id: df6f91a7fc4619ff7d664e12518fc9c2
domain: switch
enabled: true
- type: turn_on
device_id: 6f341a97517eecd7e4befd4633bb0c3a
entity_id: 4b21efa79430d63e813137d08a8df60e
domain: light
brightness_pct: 15
- type: turn_on
device_id: 40d38ce4c97de6008962e2b3d84896c9
entity_id: d794e094113a14e6de0db3e5d3f65e88
domain: light
brightness_pct: 50
- type: turn_on
device_id: 84566a79d41ebedd4512d2418195a898
entity_id: fb7b78a1f6d3146585e2f93be33bf4c1
domain: switch
- type: turn_on
device_id: 31622aa8682d29be0d631680776131c3
entity_id: dbc4e84732121a8cc63eb1b6e95fd78b
domain: switch
enabled: false
- type: turn_on
device_id: 3096deb74c1dddf893e78fcdfa5b23aa
entity_id: aa15e9f5b3c00639b64501acd18f2081
domain: switch
enabled: false
- type: turn_on
device_id: eae417dc05ec7c9f1da72f76f32bff18
entity_id: 8ebd9a84e00e9a8a4c05860cb01767ef
domain: switch
enabled: false
- type: turn_on
device_id: 2fde35e8a4dfe850d53d35a650d05b6e
entity_id: c4ec82fc55ef125a4b39f842e06b7261
domain: switch
enabled: false
- type: turn_on
device_id: 58dfbd5f60265682b98691217b9bd038
entity_id: abf15d05be0dadf1bba145f0835d3b3a
domain: switch
enabled: false
- type: turn_on
device_id: 4f7810cf96260d17363b63542447ffe7
entity_id: 70347118804cc5d1b5770362ab369251
domain: switch
enabled: false
- type: turn_on
device_id: 88c1bfde7eba191a46899d4c59754512
entity_id: 594a0d24aa2fd2dc2156d9918a2204bf
domain: switch
enabled: false
- type: turn_on
device_id: 77092d966014d6eb6222928cce911caf
entity_id: af55f6e7e0ffa642407923fc6e8cdaf3
domain: light
enabled: false
- type: turn_on
device_id: e117bada67c4b115d445163c93d2f78c
entity_id: ee44672a0acd4c35658f69b1ec71bdf7
domain: light
enabled: false
mode: single
- id: '1739971331649'
alias: Master Bedroom On At 8PM
description: ''
triggers:
- trigger: time
at: '20:00:00'
conditions: []
actions:
- type: turn_on
device_id: a8b108e7836eeb9c66cd0169afc4a486
entity_id: d48390b30da2add2f866a8cec5dc2726
domain: switch
- type: turn_on
device_id: d371342d65df3ddbd28fb841b6ce0a13
entity_id: 7df615008eff2c537a1d6ac4be76def4
domain: switch
- type: turn_on
device_id: 8b5cc6f6d2e727522ad3069656e2020f
entity_id: ff7e86ebc87458f60b69d1599ff06d13
domain: fan
mode: single
- id: '1739980877023'
alias: Downstairs Lights off at 9AM
description: ''
triggers:
- trigger: time
at: 09:00:00
conditions: []
actions:
- type: turn_off
device_id: 8b5cc6f6d2e727522ad3069656e2020f
entity_id: ff7e86ebc87458f60b69d1599ff06d13
domain: fan
- action: light.turn_off
metadata: {}
data: {}
target:
device_id:
- 389d36aa5a0ecfd3eb2e1ec8795464d0
- type: turn_off
device_id: 6f341a97517eecd7e4befd4633bb0c3a
entity_id: 4b21efa79430d63e813137d08a8df60e
domain: light
- type: turn_off
device_id: 77092d966014d6eb6222928cce911caf
entity_id: af55f6e7e0ffa642407923fc6e8cdaf3
domain: light
- type: turn_off
device_id: 3096deb74c1dddf893e78fcdfa5b23aa
entity_id: aa15e9f5b3c00639b64501acd18f2081
domain: switch
enabled: false
- type: turn_off
device_id: 58dfbd5f60265682b98691217b9bd038
entity_id: abf15d05be0dadf1bba145f0835d3b3a
domain: switch
enabled: false
- type: turn_off
device_id: 2fde35e8a4dfe850d53d35a650d05b6e
entity_id: c4ec82fc55ef125a4b39f842e06b7261
domain: switch
enabled: false
- type: turn_off
device_id: 4f7810cf96260d17363b63542447ffe7
entity_id: 70347118804cc5d1b5770362ab369251
domain: switch
enabled: false
- type: turn_off
device_id: d76728b35b6f17a89be03ef00645812e
entity_id: 7c25efba75f15af0be530ff9c9b9c729
domain: light
enabled: false
- type: turn_off
device_id: 84566a79d41ebedd4512d2418195a898
entity_id: fb7b78a1f6d3146585e2f93be33bf4c1
domain: switch
enabled: false
- type: turn_off
device_id: d76728b35b6f17a89be03ef00645812e
entity_id: 7c25efba75f15af0be530ff9c9b9c729
domain: light
- type: turn_off
device_id: 9d37094a9a9d22b1fb8bf640ca072083
entity_id: f0a27ed262997bfc54caf8a75ac15619
domain: light
- type: turn_off
device_id: 88c1bfde7eba191a46899d4c59754512
entity_id: 594a0d24aa2fd2dc2156d9918a2204bf
domain: switch
enabled: false
- type: turn_off
device_id: e117bada67c4b115d445163c93d2f78c
entity_id: ee44672a0acd4c35658f69b1ec71bdf7
domain: light
enabled: false
mode: single
- id: '1739997032502'
alias: Echo Plug Power Toggle Automation
description: ''
triggers:
- trigger: time
at: '17:00:00'
conditions: []
actions:
- type: turn_off
device_id: 31d67bb445f5c8920a1dc076d0d71e76
entity_id: 6c1fd91e0ff15419f7bdb6b9e233cffc
domain: switch
mode: single
- id: '1740099961116'
alias: Office Lights on with Motion
description: ''
triggers:
- trigger: state
entity_id:
- binary_sensor.office_motion
to: 'on'
conditions: []
actions:
- action: light.turn_on
metadata: {}
data:
brightness_pct: 100
color_temp_kelvin: 6500
target:
device_id:
- 42b9a4c3e4edfd663f2b8a0a95c3997c
- 889b4b9e38814db0249b94d2dea4ae43
- type: turn_on
device_id: 397d7f06bfbbeaf3014d989a1e32ce07
entity_id: 92505d68c2d00ef59e8ba61c3e3db32b
domain: switch
- type: turn_on
device_id: e2b7cf374f656d6ffbca52e988ef6ddb
entity_id: 5a8a6ccbe11b22be7e4c2709f647e45d
domain: switch
mode: single
- id: '1740100159283'
alias: Turn off lights when no one is in office
description: ''
triggers:
- trigger: state
entity_id:
- binary_sensor.office_occupancy
to: 'off'
for:
hours: 1
minutes: 0
seconds: 0
conditions: []
actions:
- action: light.turn_off
metadata: {}
data: {}
target:
device_id:
- 42b9a4c3e4edfd663f2b8a0a95c3997c
- 889b4b9e38814db0249b94d2dea4ae43
- type: turn_off
device_id: 397d7f06bfbbeaf3014d989a1e32ce07
entity_id: 92505d68c2d00ef59e8ba61c3e3db32b
domain: switch
- type: turn_off
device_id: e2b7cf374f656d6ffbca52e988ef6ddb
entity_id: 5a8a6ccbe11b22be7e4c2709f647e45d
domain: switch
mode: single
- id: '1740929956187'
alias: Turn off Family room when no one is in it.
description: ''
triggers:
- trigger: state
entity_id:
- binary_sensor.meat_heater_motion
to: 'off'
for:
hours: 0
minutes: 30
seconds: 0
conditions:
- condition: or
conditions:
- condition: time
after: '20:00:00'
before: 02:00:00
weekday:
- sat
- fri
- thu
- wed
- tue
- mon
- sun
- condition: time
after: 09:00:00
before: '18:00:00'
weekday:
- sat
- fri
- thu
- wed
- tue
- mon
- sun
actions:
- type: turn_off
device_id: 389d36aa5a0ecfd3eb2e1ec8795464d0
entity_id: 0ae75b98556682467fedf0ff441346ef
domain: light
- type: turn_off
device_id: 7ed0dde2b0b802820f57f3f9f515adfe
entity_id: de439b7d82897ca4cf67b8aeb53b4eee
domain: switch
enabled: false
- type: turn_off
device_id: 389d36aa5a0ecfd3eb2e1ec8795464d0
entity_id: 0ae75b98556682467fedf0ff441346ef
domain: light
mode: single
- id: '1742691391008'
alias: Turn on office echo
description: ''
triggers:
- trigger: time
at: '18:00:00'
conditions: []
actions:
- type: turn_on
device_id: 31d67bb445f5c8920a1dc076d0d71e76
entity_id: 6c1fd91e0ff15419f7bdb6b9e233cffc
domain: switch
mode: single
- id: '1742691696852'
alias: Family Room Off at Midnight
description: ''
triggers:
- trigger: time
at: 00:00:00
conditions: []
actions:
- type: turn_off
device_id: 389d36aa5a0ecfd3eb2e1ec8795464d0
entity_id: 0ae75b98556682467fedf0ff441346ef
domain: light
- type: turn_off
device_id: 7ed0dde2b0b802820f57f3f9f515adfe
entity_id: de439b7d82897ca4cf67b8aeb53b4eee
domain: switch
- type: turn_off
device_id: 77092d966014d6eb6222928cce911caf
entity_id: af55f6e7e0ffa642407923fc6e8cdaf3
domain: light
- type: turn_off
device_id: 6f341a97517eecd7e4befd4633bb0c3a
entity_id: 4b21efa79430d63e813137d08a8df60e
domain: light
mode: single
- id: '1751505592389'
alias: Turn off bug zapper at 1030PM
description: ''
triggers:
- trigger: time
at: '22:30:00'
conditions: []
actions:
- type: turn_off
device_id: 872cdc7e609c8df8544349fceae0828e
entity_id: df6f91a7fc4619ff7d664e12518fc9c2
domain: switch
mode: single
- id: '1756172550854'
alias: Turn off Fan in AM
description: ''
triggers:
- trigger: time
at: 06:00:00
conditions: []
actions:
- type: turn_off
device_id: 31622aa8682d29be0d631680776131c3
entity_id: dbc4e84732121a8cc63eb1b6e95fd78b
domain: switch
mode: single
- id: '1757038271421'
alias: Turn off ceiling fan when its below 55
description: ''
triggers:
- trigger: numeric_state
entity_id:
- sensor.openweathermap_temperature
below: 55
conditions:
- condition: time
after: '23:00:00'
before: 06:00:00
actions:
- type: turn_off
device_id: 31622aa8682d29be0d631680776131c3
entity_id: dbc4e84732121a8cc63eb1b6e95fd78b
domain: switch
mode: single
- id: '1757038394971'
alias: Ceiling fan on above 71
description: ''
triggers:
- trigger: numeric_state
entity_id:
- sensor.bedroom_temperature
above: 70
conditions: []
actions:
- type: turn_on
device_id: 31622aa8682d29be0d631680776131c3
entity_id: dbc4e84732121a8cc63eb1b6e95fd78b
domain: switch
mode: single
- id: '1759631437832'
alias: 'Turn off decorations '
description: ''
triggers:
- trigger: time
at: 00:00:00
conditions: []
actions:
- type: turn_off
device_id: a1fdc7c838bdded4d8e90bb7941476a3
entity_id: 84d24335edc946153ed4f81ac7906f3c
domain: switch
- type: turn_off
device_id: 872cdc7e609c8df8544349fceae0828e
entity_id: df6f91a7fc4619ff7d664e12518fc9c2
domain: switch
- type: turn_off
device_id: eae417dc05ec7c9f1da72f76f32bff18
entity_id: 8ebd9a84e00e9a8a4c05860cb01767ef
domain: switch
enabled: true
- type: turn_off
device_id: 3096deb74c1dddf893e78fcdfa5b23aa
entity_id: aa15e9f5b3c00639b64501acd18f2081
domain: switch
- type: turn_off
device_id: 58dfbd5f60265682b98691217b9bd038
entity_id: abf15d05be0dadf1bba145f0835d3b3a
domain: switch
- type: turn_off
device_id: 4f7810cf96260d17363b63542447ffe7
entity_id: 70347118804cc5d1b5770362ab369251
domain: switch
- type: turn_off
device_id: 2fde35e8a4dfe850d53d35a650d05b6e
entity_id: c4ec82fc55ef125a4b39f842e06b7261
domain: switch
- type: turn_off
device_id: e117bada67c4b115d445163c93d2f78c
entity_id: ee44672a0acd4c35658f69b1ec71bdf7
domain: light
- type: turn_off
device_id: 88c1bfde7eba191a46899d4c59754512
entity_id: 594a0d24aa2fd2dc2156d9918a2204bf
domain: switch
- type: turn_off
device_id: 84566a79d41ebedd4512d2418195a898
entity_id: fb7b78a1f6d3146585e2f93be33bf4c1
domain: switch
- type: turn_off
device_id: c0de273e40c6f4f1cafe4db27e5a6782
entity_id: e6f4af2d9bc05634f994af34bad9a477
domain: switch
mode: single
- id: '1762279134131'
alias: Turn on decorations in the AM
description: ''
triggers:
- trigger: time
at: 06:00:00
conditions: []
actions:
- type: turn_on
device_id: eae417dc05ec7c9f1da72f76f32bff18
entity_id: 8ebd9a84e00e9a8a4c05860cb01767ef
domain: switch
enabled: false
- type: turn_on
device_id: a1fdc7c838bdded4d8e90bb7941476a3
entity_id: 84d24335edc946153ed4f81ac7906f3c
domain: switch
enabled: false
- type: turn_on
device_id: 3096deb74c1dddf893e78fcdfa5b23aa
entity_id: aa15e9f5b3c00639b64501acd18f2081
domain: switch
- type: turn_on
device_id: 58dfbd5f60265682b98691217b9bd038
entity_id: abf15d05be0dadf1bba145f0835d3b3a
domain: switch
- type: turn_on
device_id: 2fde35e8a4dfe850d53d35a650d05b6e
entity_id: c4ec82fc55ef125a4b39f842e06b7261
domain: switch
- type: turn_on
device_id: 4f7810cf96260d17363b63542447ffe7
entity_id: 70347118804cc5d1b5770362ab369251
domain: switch
- type: turn_on
device_id: 84566a79d41ebedd4512d2418195a898
entity_id: fb7b78a1f6d3146585e2f93be33bf4c1
domain: switch
- type: turn_on
device_id: e117bada67c4b115d445163c93d2f78c
entity_id: ee44672a0acd4c35658f69b1ec71bdf7
domain: light
- type: turn_on
device_id: 00ce230936f351558f177d5ed229759b
entity_id: 9ef15fb77ca7e81bcb6c7551329f50aa
domain: switch
- type: turn_on
device_id: 88c1bfde7eba191a46899d4c59754512
entity_id: 594a0d24aa2fd2dc2156d9918a2204bf
domain: switch
mode: single
- id: '1763411863796'
alias: Holiday Christmas Pandora at 5PM
description: Trigger Alexa routine to play Christmas Hits Holiday Playlist on Pandora
triggers:
- at: '17:00:00'
trigger: time
conditions:
- condition: template
value_template: '{{ now().month == 11 or now().month == 12 }}
'
actions:
- target:
entity_id: media_player.family_room_show
data:
media:
media_content_id: Play Christmas Music
media_content_type: routine
metadata: {}
action: media_player.play_media
mode: single
- id: '1765116337200'
alias: Turn off living room tv when empty
description: ''
triggers:
- entity_id:
- binary_sensor.hsjq_motion
to:
- 'off'
for:
hours: 0
minutes: 30
seconds: 0
trigger: state
conditions:
- condition: time
after: '18:00:00'
before: 06:00:00
weekday:
- sun
- mon
- tue
- wed
- thu
- fri
- sat
actions:
- target:
entity_id: media_player.hisense_g204x_cd41
action: media_player.turn_off
data: {}
enabled: true
mode: single
- id: '1769311377293'
alias: Front Porch Off at 10P
description: ''
triggers:
- trigger: time
at: '22:00:00'
conditions: []
actions:
- type: turn_off
device_id: e04306aa947ba9901108c363d8109ca0
entity_id: 12662d2294935b2a21500e3f6d69ba15
domain: switch
- type: turn_off
device_id: c0de273e40c6f4f1cafe4db27e5a6782
entity_id: e6f4af2d9bc05634f994af34bad9a477
domain: switch
- type: turn_off
device_id: e2343a74462cc1434c4acfa2e32d1364
entity_id: ea84e174f0fa3316b9923c4254454a37
domain: switch
- type: turn_off
device_id: 872cdc7e609c8df8544349fceae0828e
entity_id: df6f91a7fc4619ff7d664e12518fc9c2
domain: switch
- type: turn_off
device_id: a1fdc7c838bdded4d8e90bb7941476a3
entity_id: 84d24335edc946153ed4f81ac7906f3c
domain: switch
mode: single
- id: '1770433866161'
alias: Turn off Kitchen Sink at Sunrise
description: ''
triggers:
- trigger: sun
event: sunrise
offset: 01:00:00
conditions: []
actions:
- type: turn_off
device_id: 40d38ce4c97de6008962e2b3d84896c9
entity_id: d794e094113a14e6de0db3e5d3f65e88
domain: light
mode: single
@@ -0,0 +1,485 @@
# Safety, device, and household alert automations.
- id: '1739061820991'
alias: Turn off washing machine when leak sensed
description: ''
triggers:
- type: moist
device_id: 84e0d8044eb13e64c899357f840dcacb
entity_id: 2f41825acbe754ebfa788a77c197138c
domain: binary_sensor
trigger: device
conditions: []
actions:
- type: turn_off
device_id: 613a1b8ef47539c9d0ebe6a87b13ca26
entity_id: 721e7dd29be7dbcb6e634ec5d505c5f4
domain: switch
- action: notify.notify
metadata: {}
data:
message: Washing Machine Leak Sensor has been triggered - please check!
mode: single
- id: '1739841729184'
alias: Declans Closet Open Notification
description: ''
initial_state: false
triggers:
- trigger: state
entity_id:
- input_boolean.hermes_disabled_automation_placeholder
to: 'on'
conditions:
- condition: time
after: '21:00:00'
before: 07:30:00
actions:
- action: notify.mobile_app_joshuas_iphone_of_pain
metadata: {}
data:
message: Declans Closet is Open
- action: notify.mobile_app_pollys_iphone
metadata: {}
data:
message: Declans Closet is Open
mode: single
- id: '1739841929885'
alias: Jordyns Door Open Notification
description: ''
triggers:
- trigger: state
entity_id:
- binary_sensor.jordyns_door_door
to: 'on'
conditions:
- condition: time
after: '19:45:00'
before: 07:45:00
actions:
- action: notify.mobile_app_joshuas_iphone_of_pain
metadata: {}
data:
message: Jordyns door is open
mode: single
- id: '1739842035606'
alias: Declans Door Closed Notification
description: ''
initial_state: false
triggers:
- trigger: state
entity_id:
- input_boolean.hermes_disabled_automation_placeholder
to: 'off'
conditions:
- condition: time
after: '21:00:00'
before: 07:00:00
actions:
- action: notify.mobile_app_joshuas_iphone_of_pain
metadata: {}
data:
message: Declans Closet is Closed
- action: notify.mobile_app_pollys_iphone
metadata: {}
data:
message: Declans Closet is Closed
mode: single
- id: '1739932866970'
alias: Washing Machine Leak Sensor Battery Low Notification
description: ''
triggers:
- trigger: numeric_state
entity_id:
- sensor.washing_machine_water_sensor_battery
below: 25
conditions: []
actions:
- action: notify.persistent_notification
metadata: {}
data:
message: Please change washing machine leak sensor battery soon!
- action: notify.mobile_app_joshuas_iphone_of_pain
metadata: {}
data:
message: Please change washing machine leak sensor battery soon!
- action: notify.mobile_app_pollys_iphone
metadata: {}
data:
message: Please change washing machine leak sensor battery soon!
mode: single
- id: '1739932945304'
alias: Jordyn's Door Sensor Low Battery Notification
description: ''
triggers:
- trigger: numeric_state
entity_id:
- sensor.jordyns_door_battery
below: 25
conditions: []
actions:
- action: notify.persistent_notification
metadata: {}
data:
message: Please change washing machine leak sensor battery soon!
- action: notify.mobile_app_joshuas_iphone_of_pain
metadata: {}
data:
message: Please change washing machine leak sensor battery soon!
- action: notify.mobile_app_pollys_iphone
metadata: {}
data:
message: Please change washing machine leak sensor battery soon!
mode: single
- id: '1739933032829'
alias: Deep Freezer Low Battery Notification
description: ''
triggers:
- trigger: numeric_state
entity_id:
- sensor.deep_freezer_battery
below: 25
conditions: []
actions:
- action: notify.persistent_notification
metadata: {}
data:
message: Please change washing machine leak sensor battery soon!
- action: notify.mobile_app_joshuas_iphone_of_pain
metadata: {}
data:
message: Please change washing machine leak sensor battery soon!
- action: notify.mobile_app_pollys_iphone
metadata: {}
data:
message: Please change washing machine leak sensor battery soon!
mode: single
- id: '1739933290261'
alias: Deep Freezer Left Open Notification
description: ''
triggers:
- trigger: state
entity_id:
- binary_sensor.deep_freezer_door
to: 'on'
for:
hours: 0
minutes: 2
seconds: 0
conditions: []
actions:
- action: notify.html5
metadata: {}
data:
message: Deep freezer has been left open - please check.
- action: notify.persistent_notification
metadata: {}
data:
message: Deep freezer has been left open - please check.
- action: notify.mobile_app_joshuas_iphone_of_pain
metadata: {}
data:
message: Deep freezer has been left open - please check.
- action: notify.mobile_app_pollys_iphone
metadata: {}
data:
message: Deep freezer has been left open - please check.
mode: single
- id: '1742155540986'
alias: 'Garage Floodlight Low Battery Notification '
description: ''
triggers:
- trigger: state
entity_id: binary_sensor.garage_floodlight_battery
to: 'on'
conditions: []
actions:
- action: notify.mobile_app_joshuas_iphone_of_pain
metadata: {}
data:
message: Garage floodlight batteries low
title: Garage floodlight batteries low
mode: single
- id: '1742841280696'
alias: Triple Threat Anti-Stink Juice Empty
description: ''
triggers:
- trigger: state
entity_id:
- sensor.triple_threat_spray_deodorant_liquid
enabled: true
to: '0'
conditions: []
actions:
- action: notify.mobile_app_joshuas_iphone_of_pain
metadata: {}
data:
message: Triple Threat's Anti-Stinky Juice is Empty
- action: notify.mobile_app_pollys_iphone
metadata: {}
data:
message: Triple Threat's Anti-Stinky Juice is Empty
- action: notify.mailgun_smtp
metadata: {}
data:
message: Triple Threat's Anti-Stinky Juice is Empty
title: Triple Threat's Anti-Stinky Juice is Empty
target: joshua@cnjmail.com polly@cnjmail.com
mode: single
- id: '1742841650033'
alias: The Deuce Litter Level
description: ''
triggers:
- trigger: state
entity_id:
- sensor.the_deuce_litter_level
enabled: true
to: '65'
conditions: []
actions:
- action: notify.mobile_app_joshuas_iphone_of_pain
metadata: {}
data:
message: The Deuce's Litter is Low
- action: notify.mobile_app_pollys_iphone
metadata: {}
data:
message: The Deuce's Litter is Low
- action: notify.mailgun_smtp
metadata: {}
data:
message: The Deuce's Litter is Low
title: The Deuce's Litter is Low
target: joshua@cnjmail.com
enabled: false
mode: single
- id: '1742841709247'
alias: Triple Threat Litter Level Notification
description: ''
triggers:
- trigger: state
entity_id:
- sensor.triple_threat_litter_level
enabled: true
conditions: []
actions:
- action: notify.mobile_app_joshuas_iphone_of_pain
metadata: {}
data:
message: Triple Threat's Litter is Low
- action: notify.mobile_app_pollys_iphone
metadata: {}
data:
message: Triple Threat's Litter is Low
- action: notify.mailgun_smtp
metadata: {}
data:
message: Triple Threat's Litter is Low
title: Triple Threat's Litter is Low
target: joshua@cnjmail.com
enabled: false
mode: single
- id: '1742841764585'
alias: Cat Crapper 3000 Low Litter Level Notification
description: ''
triggers:
- trigger: state
entity_id:
- sensor.cat_crapper_3000_litter_level
enabled: true
to: '65'
conditions: []
actions:
- action: notify.mobile_app_joshuas_iphone_of_pain
metadata: {}
data:
message: Cat Crapper 3000's Litter is Low
- action: notify.mobile_app_pollys_iphone
metadata: {}
data:
message: Cat Crapper 3000's Litter is Low
mode: single
- id: '1742841838536'
alias: Poop Box Low Litter Level Notification
description: ''
triggers:
- trigger: state
entity_id:
- sensor.poop_box_litter_level
enabled: true
to: '65'
conditions: []
actions:
- action: notify.mobile_app_joshuas_iphone_of_pain
metadata: {}
data:
message: Poop Box's Litter is Low
- action: notify.mobile_app_pollys_iphone
metadata: {}
data:
message: Poop Box's Litter is Low
- action: notify.mailgun_smtp
metadata: {}
data:
message: Poop Box's Litter is Low
title: Poop Box's Litter is Low
target: joshua@cnjmail.com
enabled: falsef
mode: single
- id: '1742842192548'
alias: Cat Crapper 3000 is full of crap notification
description: ''
triggers:
- trigger: state
entity_id:
- binary_sensor.cat_crapper_3000_wastebin_filled
enabled: true
to: 'on'
conditions: []
actions:
- action: notify.mobile_app_joshuas_iphone_of_pain
metadata: {}
data:
message: Cat Crapper 3000 is full of crap
- action: notify.mobile_app_pollys_iphone
metadata: {}
data:
message: Cat Crapper 3000 is full of crap
- action: notify.mailgun_smtp
metadata: {}
data:
message: Cat Crapper 3000 is full of crap
title: CLEAN Cat Crapper 3000 - it's full of crap
target: joshua@cnjmail.com
enabled: false
mode: single
- id: '1742842261527'
alias: The Deuce is full of deuces notification
description: ''
triggers:
- trigger: state
entity_id:
- binary_sensor.the_deuce_wastebin_filled
enabled: true
to: 'on'
conditions: []
actions:
- action: notify.mobile_app_joshuas_iphone_of_pain
metadata: {}
data:
message: The Deuce is full of deuces (laundry room)
- action: notify.mobile_app_pollys_iphone
metadata: {}
data:
message: The Deuce is full of deuces (laundry room)
- action: notify.mailgun_smtp
metadata: {}
data:
message: The Deuce is full of deuces
title: Too many dueces have been dropped!
target: joshua@cnjmail.com
enabled: false
- action: notify.mobile_app_chloes_iphone
metadata: {}
data:
message: The Deuce is full of deuces (laundry room)
mode: single
- id: '1742842372471'
alias: The Poop Box has runneth over Notification
description: ''
triggers:
- trigger: state
entity_id:
- binary_sensor.poop_box_wastebin_filled
enabled: true
to: 'on'
conditions: []
actions:
- action: notify.mobile_app_joshuas_iphone_of_pain
metadata: {}
data:
message: The Poop Box has runneth over (basement, left side)
- action: notify.mobile_app_pollys_iphone
metadata: {}
data:
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
metadata: {}
data:
message: The poop box has too many presents!
title: The poop box is bursting!!
target: joshua@cnjmail.com
enabled: false
mode: single
- id: '1742842458817'
alias: Triple Threat Full Notification
description: ''
triggers:
- trigger: state
entity_id:
- binary_sensor.triple_threat_wastebin_filled
enabled: true
to: 'on'
conditions: []
actions:
- action: notify.mobile_app_joshuas_iphone_of_pain
metadata: {}
data:
message: Triple Threat can't take no more! (Basement, right side)
- action: notify.mobile_app_pollys_iphone
metadata: {}
data:
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
metadata: {}
data:
message: The Triple Threat is down for the count!
title: Triple Threat can't take no more!
target: joshua@cnjmail.com
enabled: false
mode: single
- id: '1771090372998'
alias: Declans Closet Door Battery Low Notification
description: ''
initial_state: false
triggers:
- trigger: numeric_state
entity_id:
- input_number.last_unavailable_count
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
+107
View File
@@ -0,0 +1,107 @@
# Presence and camera/security automations.
- id: '1742155298923'
alias: Disarm cameras when Joshua comes home
description: ''
triggers:
- trigger: zone
entity_id: device_tracker.joshuas_iphone_of_pain
zone: zone.home
event: enter
conditions: []
actions:
- action: camera.disable_motion_detection
metadata: {}
data: {}
target:
device_id:
- 3df00a0796efe006e4ea349a9752faba
- 0c13483d761efe3b85e534fadd323fe3
- 5aa158596e48441ea606dd881a34402d
- d045a0a68c3c4d52a7beb2a28fdf6993
- 044c43ea05ceff58b1209f6441c24183
mode: single
- id: '1742155338014'
alias: Disable Cameras when Polly comes home
description: ''
triggers:
- trigger: zone
entity_id: device_tracker.pollys_iphone
zone: zone.home
event: enter
conditions: []
actions:
- action: camera.disable_motion_detection
metadata: {}
data: {}
target:
device_id:
- 3df00a0796efe006e4ea349a9752faba
- 0c13483d761efe3b85e534fadd323fe3
- 5aa158596e48441ea606dd881a34402d
- d045a0a68c3c4d52a7beb2a28fdf6993
- 044c43ea05ceff58b1209f6441c24183
mode: single
- id: '1754414591718'
alias: Arm Inside Blink Cameras When Everyone Leaves
description: Arm Blink 'Inside' camera group when both Joshua and Polly are not
home
triggers:
- trigger: zone
entity_id: device_tracker.joshuas_iphone_of_pain
zone: zone.home
event: leave
- trigger: zone
entity_id: device_tracker.pollys_iphone
zone: zone.home
event: leave
conditions:
- condition: device
device_id: eabafa43fa1148e4facc8e5a4151958d
domain: device_tracker
entity_id: 74ded93c7bee3648dccc7f024095b7c6
type: is_not_home
- condition: device
device_id: 638383852928fbd17397baa190c2d853
domain: device_tracker
entity_id: 0f494c69f886f56391b07f0517b0c0f3
type: is_not_home
actions:
- target:
entity_id: alarm_control_panel.blink_inside
action: alarm_control_panel.alarm_arm_away
mode: single
- id: '1758196295864'
alias: Disarm Blink when family is home
description: Arm Blink 'Inside' camera group when both Joshua and Polly are not
home
triggers:
- trigger: zone
entity_id: device_tracker.joshuas_iphone_of_pain
zone: zone.home
event: enter
- trigger: zone
entity_id: device_tracker.pollys_iphone
zone: zone.home
event: enter
conditions:
- condition: device
device_id: eabafa43fa1148e4facc8e5a4151958d
domain: device_tracker
entity_id: 74ded93c7bee3648dccc7f024095b7c6
type: is_home
- condition: device
device_id: 638383852928fbd17397baa190c2d853
domain: device_tracker
entity_id: 0f494c69f886f56391b07f0517b0c0f3
type: is_home
actions:
- action: alarm_control_panel.alarm_disarm
target:
entity_id:
- alarm_control_panel.blink_inside
data: {}
mode: single
+427
View File
@@ -0,0 +1,427 @@
# Kids routines, school prep, and announcement automations.
- id: '1743380000904'
alias: Jordyn-Bedtime-Snack-Notification-Weekend
description: Triggers an Alexa notification at 8:00 PM if the Google Calendar event
is 'Girls at Home'
triggers:
- at: '20:00:00'
trigger: time
enabled: true
conditions:
- condition: time
weekday:
- sat
- sun
- condition: state
entity_id: calendar.family_events
state: 'on'
- condition: template
value_template: '{{ state_attr(''calendar.family_events'', ''message'') == ''Girls
at Home'' }}'
actions:
- data:
message: Jordyn, time to put away screens and get a bedtime snack.
action: notify.alexa_media_living_room
mode: single
- id: '1743381226135'
alias: Jordyn-Bedtime-Snack-Notification
description: Triggers an Alexa notification at 7:30 PM if the Google Family Calendar
event is 'Girls at Home'
triggers:
- trigger: time
at: '20:00:00'
enabled: true
conditions:
- condition: time
weekday:
- mon
- tue
- wed
- condition: state
entity_id: calendar.family_events
state: 'on'
- condition: template
value_template: '{{ state_attr(''calendar.family_events'', ''message'') == ''Girls
at Home'' }}'
actions:
- target:
entity_id: media_player.living_room
data:
media:
media_content_id: Jordyn Weekday Bedtime Routine
media_content_type: routine
metadata: {}
action: media_player.play_media
mode: single
- id: '1743385356527'
alias: Jordyns Light on at 7PM
description: ''
triggers:
- trigger: time
at: '19:00:00'
conditions:
- condition: time
weekday:
- mon
- tue
actions:
- type: turn_on
device_id: ef731a5e6e666bf4fc20cc321f596029
entity_id: 9629035816fddf7de8926e4e4c29aa34
domain: switch
mode: single
- id: '1743385432089'
alias: Jordyns light on weekend
description: ''
triggers:
- trigger: time
at: '19:00:00'
conditions:
- condition: and
conditions:
- condition: time
weekday:
- fri
- sat
- sun
- condition: template
value_template: '{{ now().strftime(''%U'') | int % 2 == 1 }}'
actions:
- type: turn_on
device_id: ef731a5e6e666bf4fc20cc321f596029
entity_id: 9629035816fddf7de8926e4e4c29aa34
domain: switch
mode: single
- id: '1744560941050'
alias: Jordyns Lamp on at 730
description: ''
triggers:
- trigger: time
at: '19:30:00'
conditions: []
actions:
- type: turn_on
device_id: ef731a5e6e666bf4fc20cc321f596029
entity_id: 9629035816fddf7de8926e4e4c29aa34
domain: switch
mode: single
- id: '1744503044129'
alias: Set Alexa Announcement Volume
description: Set Alexa announcement volume for structured daily routine
triggers:
- at: 08:15:00
trigger: time
conditions:
- condition: time
weekday:
- mon
- tue
- wed
- thu
- fri
actions:
- data:
media:
media_content_id: Set kids speakers to 6
media_content_type: routine
metadata: {}
action: media_player.play_media
target:
device_id: 7c55da01c1d0e4ee7900852bb7621b1e
mode: single
- id: '1744503044809'
alias: Daily Alexa Announcements for Kids
description: Schedule of Alexa announcements for structured daily routine
trigger:
- platform: time
at: 09:00:00
- platform: time
at: '10:00:00'
- platform: time
at: '10:15:00'
- platform: time
at: '11:00:00'
- platform: time
at: '12:00:00'
- platform: time
at: '12:30:00'
- platform: time
at: '13:00:00'
- platform: time
at: '13:30:00'
- platform: time
at: '14:00:00'
- platform: time
at: '14:30:00'
- platform: time
at: '19:29:00'
condition:
- condition: time
weekday:
- mon
- tue
- wed
- thu
- fri
action:
- service: calendar.get_events
target:
entity_id: calendar.family_events
data:
start_date_time: '{{ today_at(''00:00'') }}'
end_date_time: '{{ today_at(''00:00'') + timedelta(days=1) }}'
response_variable: agenda
- condition: template
value_template: "{{ (agenda['calendar.family_events']['events'] | default([]))\n
\ | selectattr('summary', 'defined')\n | map(attribute='summary')\n | map('lower')\n
\ | select('search', 'camp')\n | list | count == 0 }}\n"
- variables:
messages:
08:00:00: Time for breakfast and a little TV!
09:00:00: Outside play time! If its bad weather, do your learning activities
inside.
'10:00:00': Snack time!
'10:15:00': Time for quiet play or outside fun.
'11:00:00': Creative time! Draw, color, or write something cool!
'12:00:00': Lunch time! Wash up and head to the table.
'12:30:00': Happy Helper Time! Do your chores like a champ!
'13:00:00': TV break — relax and enjoy.
'13:30:00': Time to read a book or listen to a story.
'14:00:00': Lets move! Try a Minecraft brain break!
'14:30:00': Time to relax with mindfulness or yoga.
- service: notify.alexa_media
data:
target:
- media_player.living_room
- media_player.jordyn_s_room
- media_player.chloe_s_room
- media_player.declan_s_room
- media_player.kitchen
message: '{{ messages[now().strftime(''%H:%M:%S'')] }}'
data:
type: announce
method: all
mode: single
- id: '1744503044813'
alias: Announce day camp preparation and pause TV
triggers:
- at: 08:25:00
trigger: time
conditions:
- condition: time
weekday:
- mon
- tue
- wed
- thu
- fri
actions:
- service: calendar.get_events
target:
entity_id: calendar.family_events
data:
start_date_time: '{{ today_at(''00:00'') }}'
end_date_time: '{{ today_at(''00:00'') + timedelta(days=1) }}'
response_variable: agenda
- condition: template
value_template: "{{ (agenda['calendar.family_events']['events'] | default([]))\n
\ | selectattr('summary', 'defined')\n | map(attribute='summary')\n | map('lower')\n
\ | select('search', 'camp')\n | list | count > 0 }}\n"
- target:
entity_id: media_player.hisense_g204x_cd41
action: media_player.media_pause
data: {}
- data:
target:
- media_player.living_room
- media_player.declan_s_room
- media_player.jordyn_s_room
- media_player.kitchen
message: 'Time to prepare for day camp. Please do the following: Fill water
bottles, put on deodorant, brush teeth, and Jordyn, brush your hair.
'
action: notify.alexa_media
mode: single
- id: '1744503044814'
alias: Announce time to leave for day camp and turn off TV
triggers:
- at: 08:40:00
trigger: time
conditions:
- condition: time
weekday:
- mon
- tue
- wed
- thu
- fri
actions:
- service: calendar.get_events
target:
entity_id: calendar.family_events
data:
start_date_time: '{{ today_at(''00:00'') }}'
end_date_time: '{{ today_at(''00:00'') + timedelta(days=1) }}'
response_variable: agenda
- condition: template
value_template: "{{ (agenda['calendar.family_events']['events'] | default([]))\n
\ | selectattr('summary', 'defined')\n | map(attribute='summary')\n | map('lower')\n
\ | select('search', 'camp')\n | list | count > 0 }}\n"
- target:
entity_id: media_player.basement_show
data:
media_content_id: Turn Kids TV Off
media_content_type: routine
action: media_player.play_media
- data:
target:
- media_player.living_room
message: 'It is time to leave for day camp. Please do the following: Put on
shoes, get lunch, get water bottle, and grab your bag if needed.
'
action: notify.alexa_media
- target:
entity_id: media_player.hisense_g204x_cd41
action: media_player.turn_off
data: {}
mode: single
- id: '1744503044145'
alias: Reset Alexa Announcement Volume - Camp Days
description: Set Alexa announcement volume for structured daily routine
trigger:
- platform: time
at: 09:15:00
condition:
- condition: time
weekday:
- mon
- tue
- wed
- thu
- fri
action:
- service: calendar.get_events
target:
entity_id: calendar.family_events
data:
start_date_time: '{{ today_at(''00:00'') }}'
end_date_time: '{{ today_at(''00:00'') + timedelta(days=1) }}'
response_variable: agenda
- condition: template
value_template: "{{ (agenda['calendar.family_events']['events'] | default([]))\n
\ | selectattr('summary', 'defined')\n | map(attribute='summary')\n | map('lower')\n
\ | select('search', 'camp')\n | list | count > 0 }}\n"
- service: media_player.play_media
data:
media_content_id: Reset Alexa Volume
media_content_type: routine
target:
entity_id: media_player.basement_show
mode: single
- id: '1744503044146'
alias: Reset Alexa Announcement Volume - No Camp Days
description: Set Alexa announcement volume for structured daily routine
trigger:
- platform: time
at: '19:17:00'
condition:
- condition: time
weekday:
- mon
- tue
- wed
- thu
- fri
action:
- service: calendar.get_events
target:
entity_id: calendar.family_events
data:
start_date_time: '{{ today_at(''00:00'') }}'
end_date_time: '{{ today_at(''00:00'') + timedelta(days=1) }}'
response_variable: agenda
- condition: template
value_template: "{{ (agenda['calendar.family_events']['events'] | default([]))\n
\ | selectattr('summary', 'defined')\n | map(attribute='summary')\n | map('lower')\n
\ | select('search', 'camp')\n | list | count == 0 }}\n"
- service: media_player.play_media
data:
media_content_id: Reset Alexa Volume
media_content_type: routine
target:
entity_id: media_player.basement_show
mode: single
- id: '1756211568290'
alias: School Prep Announcement
description: ''
triggers:
- at: 08:20:00
trigger: time
conditions:
- condition: time
weekday:
- mon
- tue
- wed
- thu
- fri
actions:
- target:
entity_id: media_player.hisense_g204x_cd41
action: media_player.media_pause
data: {}
- data:
target:
- media_player.living_room
- media_player.declan_s_room
- media_player.jordyn_s_room
- media_player.kitchen
message: This is your reminder to pack your snack, fill your water bottles,
put on deodorant, socks, and brush your teeth. Jordyn please make sure your
hair is brushed nicely.
action: notify.alexa_media
mode: single
- id: '1756211855375'
alias: Time for School Announcement
description: ''
triggers:
- at: 08:42:00
trigger: time
conditions:
- condition: time
weekday:
- mon
- tue
- wed
- thu
- fri
actions:
- target:
entity_id: media_player.hisense_g204x_cd41
action: media_player.turn_off
data: {}
- data:
target:
- media_player.living_room
- media_player.declan_s_room
- media_player.jordyn_s_room
- media_player.kitchen
message: Time for school! Last call for water bottles, snacks, and socks. Have
a great day!!
action: notify.alexa_media
mode: single
+486
View File
@@ -0,0 +1,486 @@
- id: '1743176311797'
alias: Bambu Lab - Spaghetti Detection
description: ''
use_blueprint:
path: nberktumer/spaghetti_detection.yaml
input:
home_assistant_host: http://192.168.1.86:8123
obico_host: http://192.168.1.85:3333
obico_auth_token: 5b3dd0ff-7e41-47f4-9027-9e609813ae66
printer_current_stage_sensor: sensor.p1s_01p09c470102673_current_stage
printer_print_status_sensor: sensor.p1s_01p09c470102673_print_status
printer_camera: camera.p1s_01p09c470102673_camera
printer_pause_button: button.p1s_01p09c470102673_pause_printing
printer_resume_button: button.p1s_01p09c470102673_resume_printing
printer_stop_button: button.p1s_01p09c470102673_stop_printing
printer_chamber_light: light.p1s_01p09c470102673_chamber_light
detection_frequency: /30
notification_settings: standard
notification_service: notify.mobile_app_joshuas_iphone_of_pain
- id: '1767118574570'
alias: Centauri Carbon Status Updates
description: ''
initial_state: false
use_blueprint:
path: danielcherubini/elegoo_printer_progress.yaml
input:
error_status_reason_entity: sensor.saturn_iv_error_status_reason
notify_device: eabafa43fa1148e4facc8e5a4151958d
current_status_entity: sensor.saturn_iv_current_status
print_status_entity: sensor.saturn_iv_print_status
percent_complete_entity: sensor.saturn_iv_percent_complete
- id: '1767118623336'
alias: Saturn Status Notifications
description: ''
use_blueprint:
path: danielcherubini/elegoo_printer_progress.yaml
input:
percent_complete_entity: sensor.saturn_iv_percent_complete
print_status_entity: sensor.saturn_iv_print_status
current_status_entity: sensor.saturn_iv_current_status
error_status_reason_entity: sensor.saturn_iv_error_status_reason
notify_device: eabafa43fa1148e4facc8e5a4151958d
enable_status_notifications: false
- id: '1767120051057'
alias: Turn off Heater and Exhaust Fan after Resin Print
description: ''
triggers:
- trigger: state
entity_id:
- sensor.saturn_iv_print_status
from: null
to:
- complete
conditions: []
actions:
- delay:
hours: 0
minutes: 5
seconds: 0
milliseconds: 0
- type: turn_off
device_id: fc9f0c9a71eaa4bd6aa14417425f61cb
entity_id: a91e4bedcc7c5d57563d88bd58d67c9c
domain: switch
- delay:
hours: 0
minutes: 5
seconds: 0
milliseconds: 0
- type: turn_off
device_id: 1b1350195291ecd20cf7c69604c09699
entity_id: 74c861175e7d5036c2253583f59d1100
domain: switch
mode: single
- id: '1767816737180'
alias: Saturn 4 Complete Notification
description: ''
triggers:
- trigger: state
entity_id:
- sensor.saturn_iv_print_status
to:
- complete
conditions: []
actions:
- action: notify.mobile_app_joshuas_iphone_of_pain
metadata: {}
data:
message: Saturn 4 print complete.
mode: single
- id: '1769017759647'
alias: Turn-on-Saturn-4-ESP-Cam-Light
description: ''
triggers:
- trigger: state
entity_id:
- sensor.saturn_iv_print_status
to:
- printing
conditions: []
actions:
- type: turn_on
device_id: a2c694d04457b843b9c88c95a7f37217
entity_id: 194545427a0728392552740cc750bc98
domain: light
brightness_pct: 100
mode: single
- id: '1769017886614'
alias: Turn-Off-ESPSATURNCAM-Light-After-Printing
description: ''
triggers:
- trigger: state
entity_id:
- sensor.saturn_iv_print_status
to:
- complete
conditions: []
actions:
- type: turn_off
device_id: a2c694d04457b843b9c88c95a7f37217
entity_id: 194545427a0728392552740cc750bc98
domain: light
mode: single
- id: '1770938475431'
alias: Jordyn-A1-lights-on-during-print
description: ''
triggers:
- device_id: 6d6eebb0e8ea6f46ac2ec3a3a5a5eae3
domain: bambu_lab
type: event_print_started
trigger: device
- device_id: 6d6eebb0e8ea6f46ac2ec3a3a5a5eae3
domain: bambu_lab
type: event_print_error_cleared
trigger: device
conditions: []
actions:
- action: light.turn_on
metadata: {}
target:
entity_id: light.jordyns_layer_slayer_chamber_light
data: {}
- type: turn_on
device_id: d174bbc498cde1d02e455cb133c04675
entity_id: 0736793ae0d143db76ea7c64d86f4268
domain: switch
- action: light.turn_on
metadata: {}
target:
entity_id: light.jordyn_s_led_controller_jordyn_s_led_controller
data:
color_temp_kelvin: 6500
brightness_pct: 100
mode: single
- id: '1770938581699'
alias: Ds turd flinger Light on during print
description: ''
triggers:
- device_id: 299743e9ce66334a0f3bac10eb24cf31
domain: bambu_lab
type: event_print_started
trigger: device
- device_id: 299743e9ce66334a0f3bac10eb24cf31
domain: bambu_lab
type: event_print_error_cleared
trigger: device
conditions: []
actions:
- action: light.turn_on
metadata: {}
target:
entity_id:
- 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
- id: '1770938855253'
alias: Jordyn-a1-light-off-print-send
description: ''
triggers:
- device_id: 6d6eebb0e8ea6f46ac2ec3a3a5a5eae3
domain: bambu_lab
type: event_print_finished
trigger: device
- device_id: 6d6eebb0e8ea6f46ac2ec3a3a5a5eae3
domain: bambu_lab
type: event_print_canceled
trigger: device
conditions: []
actions:
- action: light.turn_off
metadata: {}
target:
entity_id:
- light.jordyns_layer_slayer_chamber_light
- light.jordyn_s_led_controller_jordyn_s_led_controller
data:
transition: 60
- type: turn_off
device_id: d174bbc498cde1d02e455cb133c04675
entity_id: 0736793ae0d143db76ea7c64d86f4268
domain: switch
mode: single
- id: '1770943298493'
alias: Ds turd flinger Light Off after print
description: ''
triggers:
- device_id: 299743e9ce66334a0f3bac10eb24cf31
domain: bambu_lab
type: event_print_finished
trigger: device
- device_id: 299743e9ce66334a0f3bac10eb24cf31
domain: bambu_lab
type: event_print_canceled
trigger: device
conditions: []
actions:
- action: light.turn_off
metadata: {}
target:
entity_id:
- light.a1_03919d540806387_chamber_light
- light.led_controller_polly_pocket_declan_s_led_controller
data: {}
- type: turn_off
device_id: c63f4513655bdd5b44b998f45b491136
entity_id: fe6d0eee24eb1b397d37b7389137b5b5
domain: switch
mode: single
- id: '1770943760193'
alias: Jordyn-A1-Flash-Red-When-Print-Error
description: ''
triggers:
- device_id: 6d6eebb0e8ea6f46ac2ec3a3a5a5eae3
domain: bambu_lab
type: event_print_error
trigger: device
- device_id: 6d6eebb0e8ea6f46ac2ec3a3a5a5eae3
domain: bambu_lab
type: event_print_failed
trigger: device
conditions: []
actions:
- action: light.turn_on
metadata: {}
target:
entity_id: light.jordyn_s_led_controller_jordyn_s_led_controller
data:
rgb_color:
- 255
- 0
- 0
effect: Flash
brightness_pct: 100
- type: turn_off
device_id: d174bbc498cde1d02e455cb133c04675
entity_id: 0736793ae0d143db76ea7c64d86f4268
domain: switch
- action: light.turn_off
metadata: {}
target:
entity_id: light.jordyns_layer_slayer_chamber_light
data: {}
mode: single
- id: '1770944104889'
alias: Pints-Charming-Lights-On-Print-Started
description: 'Repaired by agent: replaced deleted Bambu device trigger with P1S
print_status entity trigger.'
triggers:
- trigger: state
entity_id: sensor.p1s_01p09c470102673_print_status
to:
- printing
- running
conditions: []
actions:
- action: light.turn_on
metadata: {}
target:
entity_id: light.p1s_01p09c470102673_chamber_light
data: {}
mode: single
- id: '1770944259063'
alias: Constipation-Orion-Light-On-When-Print-Starts
description: ''
triggers:
- device_id: 615a0b19447425670a6b61ec2343c791
domain: bambu_lab
type: event_print_started
trigger: device
conditions: []
actions:
- action: light.turn_on
metadata: {}
target:
entity_id: light.constipation_orion_chamber_light
data: {}
mode: single
- id: '1770944373316'
alias: Constipation-Orion-Light-Off-Print-Finishes
description: ''
triggers:
- device_id: 615a0b19447425670a6b61ec2343c791
domain: bambu_lab
type: event_print_finished
trigger: device
conditions: []
actions:
- action: light.turn_off
metadata: {}
target:
entity_id: light.constipation_orion_chamber_light
data: {}
mode: single
- id: '1770944453024'
alias: Prints-Charming-Light-Off-Print-Completes
description: 'Repaired by agent: replaced deleted Bambu device trigger with P1S
print_status entity trigger.'
triggers:
- trigger: state
entity_id: sensor.p1s_01p09c470102673_print_status
to:
- finish
- complete
- completed
- idle
- failed
- cancelled
conditions:
- condition: template
value_template: '{{ trigger.from_state is not none and trigger.from_state.state
in [''printing'',''running''] }}'
actions:
- action: light.turn_off
metadata: {}
data: {}
target:
entity_id: light.p1s_01p09c470102673_chamber_light
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
- id: '1772979000001'
alias: Constipation-Orion 80mm Exhaust Fan On During Print
description: ''
triggers:
- trigger: device
device_id: 615a0b19447425670a6b61ec2343c791
domain: bambu_lab
type: event_print_started
conditions: []
actions:
- action: fan.turn_on
target:
entity_id: fan.fdm_exhaust_fan_control_80mm_exhaust_fan
mode: single
- id: '1772979000002'
alias: Turn Off 80mm Exhaust Fan 15 Minutes After Constipation-Orion Print Finishes
description: ''
triggers:
- trigger: device
device_id: 615a0b19447425670a6b61ec2343c791
domain: bambu_lab
type: event_print_finished
- trigger: device
device_id: 615a0b19447425670a6b61ec2343c791
domain: bambu_lab
type: event_print_canceled
conditions: []
actions:
- action: fan.turn_on
target:
entity_id: fan.fdm_exhaust_fan_control_80mm_exhaust_fan
- delay:
hours: 0
minutes: 15
seconds: 0
- condition: template
value_template: '{{ states(''sensor.constipation_orion_print_status'') != ''printing''
}}'
- action: fan.turn_off
target:
entity_id: fan.fdm_exhaust_fan_control_80mm_exhaust_fan
mode: restart
- id: '1772979000003'
alias: Prints Charming Or Printney Spears 120mm Exhaust Fan On During Print
description: 'Repaired by agent: replaced deleted Bambu device trigger with P1S
print_status entity trigger.'
triggers:
- trigger: state
entity_id: sensor.p1s_01p09c470102673_print_status
to:
- printing
- running
conditions: []
actions:
- action: fan.turn_on
target:
entity_id: fan.fdm_exhaust_fan_control_120mm_exhaust_fan
mode: single
- id: '1772979000004'
alias: Turn Off 120mm Exhaust Fan 15 Minutes After Print Completion
description: 'Repaired by agent: replaced deleted Bambu device triggers with P1S
print_status entity trigger.'
triggers:
- trigger: state
entity_id: sensor.p1s_01p09c470102673_print_status
to:
- finish
- complete
- completed
- idle
- failed
- cancelled
conditions:
- condition: template
value_template: '{{ trigger.from_state is not none and trigger.from_state.state
in [''printing'',''running''] }}'
actions:
- action: fan.turn_on
target:
entity_id: fan.fdm_exhaust_fan_control_120mm_exhaust_fan
- delay:
hours: 0
minutes: 15
seconds: 0
- condition: template
value_template: '{{ states(''sensor.p1s_01p09c470102673_print_status'') != ''printing''
}}'
- action: fan.turn_off
target:
entity_id: fan.fdm_exhaust_fan_control_120mm_exhaust_fan
mode: restart
@@ -0,0 +1,111 @@
- id: '1772978762478'
alias: Kiosk Rotate Dashboards
description: During 8:30 AM-4:00 PM, show summer schedule only on weekdays with
no camp event today; otherwise rotate normally.
triggers:
- minutes: /1
trigger: time_pattern
actions:
- action: calendar.get_events
target:
entity_id: calendar.family_events
data:
start_date_time: '{{ today_at(''00:00'') }}'
end_date_time: '{{ today_at(''00:00'') + timedelta(days=1) }}'
response_variable: agenda
- variables:
camp_events: '{{ (agenda[''calendar.family_events''][''events''] | default([]))
| selectattr(''summary'', ''defined'') | map(attribute=''summary'') | map(''lower'')
| select(''search'', ''camp'') | list | count }}'
should_show_summer: '{{ now().weekday() < 5 and today_at(''08:29:59'') <= now()
< today_at(''16:00'') and (camp_events | int(0)) == 0 }}'
current_view: '{{ states(''input_select.kiosk_view'') }}'
- choose:
- conditions:
- condition: template
value_template: '{{ should_show_summer | bool }}'
sequence:
- action: input_select.select_option
target:
entity_id: input_select.kiosk_view
data:
option: summer-schedule-kiosk
- conditions:
- condition: template
value_template: '{{ current_view == ''summer-schedule-kiosk'' and not (should_show_summer
| bool) }}'
sequence:
- action: input_select.select_next
target:
entity_id: input_select.kiosk_view
data:
cycle: true
default:
- action: input_select.select_next
target:
entity_id: input_select.kiosk_view
data:
cycle: true
mode: single
- id: '1772978826069'
alias: Kiosk Navigate Display
description: ''
triggers:
- trigger: state
entity_id: input_select.kiosk_view
actions:
- data:
browser_id:
- raspberry-kiosk
path: /{{ states('input_select.kiosk_view') }}
action: browser_mod.navigate
mode: restart
- id: '1774407470375'
alias: ESPHome Fleet Update
description: ''
triggers:
- at: 03:00:00
trigger: time
- event_type: esphome_fleet_update_trigger
trigger: event
conditions:
- condition: template
value_template: "{{ expand(integration_entities('esphome'))\n | selectattr('entity_id',\
\ 'contains', 'update')\n | selectattr('state', 'eq', 'on')\n | list | count\
\ > 0 }}\n"
actions:
- variables:
pending_devices: "{{ expand(integration_entities('esphome'))\n | selectattr('entity_id',\
\ 'contains', 'update')\n | selectattr('state', 'eq', 'on')\n | map(attribute='entity_id')\
\ | list }}\n"
pending_names: "{{ expand(integration_entities('esphome'))\n | selectattr('entity_id',\
\ 'contains', 'update')\n | selectattr('state', 'eq', 'on')\n | map(attribute='name')\
\ | join(', ') }}\n"
- action: notify.persistent_notification
data:
title: ESPHome Fleet Update Starting
message: 'Updating {{ pending_devices | count }} device(s): {{ pending_names
}}
'
- repeat:
for_each: '{{ pending_devices }}'
sequence:
- action: update.install
target:
entity_id: '{{ repeat.item }}'
continue_on_error: true
- wait_template: '{{ not is_state(repeat.item, ''on'') }}
'
timeout: 00:05:00
continue_on_timeout: true
- delay:
minutes: 2
- action: notify.persistent_notification
data:
title: ESPHome Fleet Update Complete
message: 'Finished updating {{ pending_devices | count }} device(s): {{ pending_names
}}
'
+44
View File
@@ -0,0 +1,44 @@
# Climate and plug automations.
- id: hermes_ac_controls_matter_plugs
alias: AC controls Matter plugs
description: Turn the four Tapo Matter plugs on while the Ecobee is actively cooling, and off when cooling stops.
mode: restart
triggers:
- trigger: state
entity_id: climate.meat_heater
attribute: hvac_action
to: cooling
id: ac_on
- trigger: state
entity_id: climate.meat_heater
attribute: hvac_action
from: cooling
id: ac_off
conditions: []
actions:
- choose:
- conditions:
- condition: trigger
id: ac_on
sequence:
- action: switch.turn_on
continue_on_error: true
target:
entity_id:
- switch.smart_wi_fi_plug
- switch.smart_wi_fi_plug_2
- switch.smart_wi_fi_plug_3
- switch.smart_wi_fi_plug_4
- conditions:
- condition: trigger
id: ac_off
sequence:
- action: switch.turn_off
continue_on_error: true
target:
entity_id:
- switch.smart_wi_fi_plug
- switch.smart_wi_fi_plug_2
- switch.smart_wi_fi_plug_3
- switch.smart_wi_fi_plug_4
@@ -0,0 +1,73 @@
# ------------------------------------------------------------
# BAMBU: PrintsCharming (P1S)
# ------------------------------------------------------------
- alias: "Print Log - PrintsCharming - Start"
id: print_log_printcharming_start
description: "Creates Notion entry when PrintsCharming begins printing"
mode: single
trigger:
- platform: state
entity_id: sensor.p1s_01p09c470102673_print_status
to: "running"
condition:
- condition: template
value_template: "{{ trigger.from_state.state != 'running' }}"
- condition: template
value_template: "{{ states('input_text.notion_page_id_printcharming') | length < 10 }}"
action:
- delay: "00:00:05"
- action: rest_command.notion_create_print
data:
file_name: "{{ states('sensor.p1s_01p09c470102673_gcode_filename') }}"
printer_name: "PrintsCharming"
filament_type: "{{ state_attr('sensor.p1s_01p09c470102673_active_tray', 'type') | default('Unknown') }}"
filament_color: "{{ state_attr('sensor.p1s_01p09c470102673_active_tray', 'color') | default('Unknown') }}"
started: "{{ now().strftime('%Y-%m-%dT%H:%M:%S') }}"
response_variable: notion_response
- action: input_text.set_value
target:
entity_id: input_text.notion_page_id_printcharming
data:
value: "{{ notion_response.content.id | default('') }}"
- alias: "Print Log - PrintsCharming - Finish"
id: print_log_printcharming_finish
description: "Updates Notion entry when PrintsCharming finishes, fails, or is cancelled"
mode: single
trigger:
- platform: state
entity_id: sensor.p1s_01p09c470102673_print_status
to: "finish"
id: completed
- platform: state
entity_id: sensor.p1s_01p09c470102673_print_status
to: "failed"
id: failed
- platform: state
entity_id: sensor.p1s_01p09c470102673_print_status
to: "cancelled"
id: cancelled
condition:
- condition: template
value_template: "{{ states('input_text.notion_page_id_printcharming') | length > 10 }}"
action:
- delay: "00:00:05"
- variables:
page_id: "{{ states('input_text.notion_page_id_printcharming') }}"
- condition: template
value_template: "{{ page_id | length > 10 }}"
- action: rest_command.notion_update_print
data:
page_id: "{{ page_id }}"
status: >
{% if trigger.id == 'completed' %}Completed
{% elif trigger.id == 'failed' %}Failed
{% else %}Cancelled{% endif %}
weight: "{{ states('sensor.p1s_01p09c470102673_print_weight') | float(0) }}"
length: "{{ states('sensor.p1s_01p09c470102673_print_length') | float(0) }}"
completed: "{{ now().strftime('%Y-%m-%dT%H:%M:%S') }}"
- action: input_text.set_value
target:
entity_id: input_text.notion_page_id_printcharming
data:
value: ""
@@ -0,0 +1,73 @@
# ------------------------------------------------------------
# BAMBU: Ds-Turd-Flinger (A1)
# ------------------------------------------------------------
- alias: "Print Log - Ds-Turd-Flinger - Start"
id: print_log_turd_flinger_start
description: "Creates Notion entry when Ds-Turd-Flinger begins printing"
mode: single
trigger:
- platform: state
entity_id: sensor.a1_03919d540806387_print_status
to: "running"
condition:
- condition: template
value_template: "{{ trigger.from_state.state != 'running' }}"
- condition: template
value_template: "{{ states('input_text.notion_page_id_turd_flinger') | length < 10 }}"
action:
- delay: "00:00:05"
- action: rest_command.notion_create_print
data:
file_name: "{{ states('sensor.a1_03919d540806387_gcode_filename') }}"
printer_name: "Ds-Turd-Flinger"
filament_type: "{{ state_attr('sensor.a1_03919d540806387_active_tray', 'type') | default('Unknown') }}"
filament_color: "{{ state_attr('sensor.a1_03919d540806387_active_tray', 'color') | default('Unknown') }}"
started: "{{ now().strftime('%Y-%m-%dT%H:%M:%S') }}"
response_variable: notion_response
- action: input_text.set_value
target:
entity_id: input_text.notion_page_id_turd_flinger
data:
value: "{{ notion_response.content.id | default('') }}"
- alias: "Print Log - Ds-Turd-Flinger - Finish"
id: print_log_turd_flinger_finish
description: "Updates Notion entry when Ds-Turd-Flinger finishes, fails, or is cancelled"
mode: single
trigger:
- platform: state
entity_id: sensor.a1_03919d540806387_print_status
to: "finish"
id: completed
- platform: state
entity_id: sensor.a1_03919d540806387_print_status
to: "failed"
id: failed
- platform: state
entity_id: sensor.a1_03919d540806387_print_status
to: "cancelled"
id: cancelled
condition:
- condition: template
value_template: "{{ states('input_text.notion_page_id_turd_flinger') | length > 10 }}"
action:
- delay: "00:00:05"
- variables:
page_id: "{{ states('input_text.notion_page_id_turd_flinger') }}"
- condition: template
value_template: "{{ page_id | length > 10 }}"
- action: rest_command.notion_update_print
data:
page_id: "{{ page_id }}"
status: >
{% if trigger.id == 'completed' %}Completed
{% elif trigger.id == 'failed' %}Failed
{% else %}Cancelled{% endif %}
weight: "{{ states('sensor.a1_03919d540806387_print_weight') | float(0) }}"
length: "{{ states('sensor.a1_03919d540806387_print_length') | float(0) }}"
completed: "{{ now().strftime('%Y-%m-%dT%H:%M:%S') }}"
- action: input_text.set_value
target:
entity_id: input_text.notion_page_id_turd_flinger
data:
value: ""
@@ -0,0 +1,73 @@
# ------------------------------------------------------------
# BAMBU: Polly-Pocket (A1 Mini)
# ------------------------------------------------------------
- alias: "Print Log - Polly-Pocket - Start"
id: print_log_polly_pocket_start
description: "Creates Notion entry when Polly-Pocket begins printing"
mode: single
trigger:
- platform: state
entity_id: sensor.a1mini_0309ca580403653_print_status
to: "running"
condition:
- condition: template
value_template: "{{ trigger.from_state.state != 'running' }}"
- condition: template
value_template: "{{ states('input_text.notion_page_id_polly_pocket') | length < 10 }}"
action:
- delay: "00:00:05"
- action: rest_command.notion_create_print
data:
file_name: "{{ states('sensor.a1mini_0309ca580403653_gcode_filename') }}"
printer_name: "Polly-Pocket"
filament_type: "{{ state_attr('sensor.a1mini_0309ca580403653_active_tray', 'type') | default('Unknown') }}"
filament_color: "{{ state_attr('sensor.a1mini_0309ca580403653_active_tray', 'color') | default('Unknown') }}"
started: "{{ now().strftime('%Y-%m-%dT%H:%M:%S') }}"
response_variable: notion_response
- action: input_text.set_value
target:
entity_id: input_text.notion_page_id_polly_pocket
data:
value: "{{ notion_response.content.id | default('') }}"
- alias: "Print Log - Polly-Pocket - Finish"
id: print_log_polly_pocket_finish
description: "Updates Notion entry when Polly-Pocket finishes, fails, or is cancelled"
mode: single
trigger:
- platform: state
entity_id: sensor.a1mini_0309ca580403653_print_status
to: "finish"
id: completed
- platform: state
entity_id: sensor.a1mini_0309ca580403653_print_status
to: "failed"
id: failed
- platform: state
entity_id: sensor.a1mini_0309ca580403653_print_status
to: "cancelled"
id: cancelled
condition:
- condition: template
value_template: "{{ states('input_text.notion_page_id_polly_pocket') | length > 10 }}"
action:
- delay: "00:00:05"
- variables:
page_id: "{{ states('input_text.notion_page_id_polly_pocket') }}"
- condition: template
value_template: "{{ page_id | length > 10 }}"
- action: rest_command.notion_update_print
data:
page_id: "{{ page_id }}"
status: >
{% if trigger.id == 'completed' %}Completed
{% elif trigger.id == 'failed' %}Failed
{% else %}Cancelled{% endif %}
weight: "{{ states('sensor.a1mini_0309ca580403653_print_weight') | float(0) }}"
length: "{{ states('sensor.a1mini_0309ca580403653_print_length') | float(0) }}"
completed: "{{ now().strftime('%Y-%m-%dT%H:%M:%S') }}"
- action: input_text.set_value
target:
entity_id: input_text.notion_page_id_polly_pocket
data:
value: ""
@@ -0,0 +1,73 @@
# ------------------------------------------------------------
# BAMBU: Constipation-Orion
# ------------------------------------------------------------
- alias: "Print Log - Constipation-Orion - Start"
id: print_log_constipation_orion_start
description: "Creates Notion entry when Constipation-Orion begins printing"
mode: single
trigger:
- platform: state
entity_id: sensor.constipation_orion_print_status
to: "running"
condition:
- condition: template
value_template: "{{ trigger.from_state.state != 'running' }}"
- condition: template
value_template: "{{ states('input_text.notion_page_id_constipation_orion') | length < 10 }}"
action:
- delay: "00:00:05"
- action: rest_command.notion_create_print
data:
file_name: "{{ states('sensor.constipation_orion_gcode_filename') }}"
printer_name: "Constipation-Orion"
filament_type: "{{ state_attr('sensor.constipation_orion_active_tray', 'type') | default('Unknown') }}"
filament_color: "{{ state_attr('sensor.constipation_orion_active_tray', 'color') | default('Unknown') }}"
started: "{{ now().strftime('%Y-%m-%dT%H:%M:%S') }}"
response_variable: notion_response
- action: input_text.set_value
target:
entity_id: input_text.notion_page_id_constipation_orion
data:
value: "{{ notion_response.content.id | default('') }}"
- alias: "Print Log - Constipation-Orion - Finish"
id: print_log_constipation_orion_finish
description: "Updates Notion entry when Constipation-Orion finishes, fails, or is cancelled"
mode: single
trigger:
- platform: state
entity_id: sensor.constipation_orion_print_status
to: "finish"
id: completed
- platform: state
entity_id: sensor.constipation_orion_print_status
to: "failed"
id: failed
- platform: state
entity_id: sensor.constipation_orion_print_status
to: "cancelled"
id: cancelled
condition:
- condition: template
value_template: "{{ states('input_text.notion_page_id_constipation_orion') | length > 10 }}"
action:
- delay: "00:00:05"
- variables:
page_id: "{{ states('input_text.notion_page_id_constipation_orion') }}"
- condition: template
value_template: "{{ page_id | length > 10 }}"
- action: rest_command.notion_update_print
data:
page_id: "{{ page_id }}"
status: >
{% if trigger.id == 'completed' %}Completed
{% elif trigger.id == 'failed' %}Failed
{% else %}Cancelled{% endif %}
weight: "{{ states('sensor.constipation_orion_print_weight') | float(0) }}"
length: "{{ states('sensor.constipation_orion_print_length') | float(0) }}"
completed: "{{ now().strftime('%Y-%m-%dT%H:%M:%S') }}"
- action: input_text.set_value
target:
entity_id: input_text.notion_page_id_constipation_orion
data:
value: ""
@@ -0,0 +1,73 @@
# ------------------------------------------------------------
# BAMBU: Jordyns-Layer-Slayer
# ------------------------------------------------------------
- alias: "Print Log - Jordyns-Layer-Slayer - Start"
id: print_log_jordyns_layer_slayer_start
description: "Creates Notion entry when Jordyns-Layer-Slayer begins printing"
mode: single
trigger:
- platform: state
entity_id: sensor.jordyns_layer_slayer_print_status
to: "running"
condition:
- condition: template
value_template: "{{ trigger.from_state.state != 'running' }}"
- condition: template
value_template: "{{ states('input_text.notion_page_id_jordyns_layer_slayer') | length < 10 }}"
action:
- delay: "00:00:05"
- action: rest_command.notion_create_print
data:
file_name: "{{ states('sensor.jordyns_layer_slayer_gcode_filename') }}"
printer_name: "Jordyns-Layer-Slayer"
filament_type: "{{ state_attr('sensor.jordyns_layer_slayer_active_tray', 'type') | default('Unknown') }}"
filament_color: "{{ state_attr('sensor.jordyns_layer_slayer_active_tray', 'color') | default('Unknown') }}"
started: "{{ now().strftime('%Y-%m-%dT%H:%M:%S') }}"
response_variable: notion_response
- action: input_text.set_value
target:
entity_id: input_text.notion_page_id_jordyns_layer_slayer
data:
value: "{{ notion_response.content.id | default('') }}"
- alias: "Print Log - Jordyns-Layer-Slayer - Finish"
id: print_log_jordyns_layer_slayer_finish
description: "Updates Notion entry when Jordyns-Layer-Slayer finishes, fails, or is cancelled"
mode: single
trigger:
- platform: state
entity_id: sensor.jordyns_layer_slayer_print_status
to: "finish"
id: completed
- platform: state
entity_id: sensor.jordyns_layer_slayer_print_status
to: "failed"
id: failed
- platform: state
entity_id: sensor.jordyns_layer_slayer_print_status
to: "cancelled"
id: cancelled
condition:
- condition: template
value_template: "{{ states('input_text.notion_page_id_jordyns_layer_slayer') | length > 10 }}"
action:
- delay: "00:00:05"
- variables:
page_id: "{{ states('input_text.notion_page_id_jordyns_layer_slayer') }}"
- condition: template
value_template: "{{ page_id | length > 10 }}"
- action: rest_command.notion_update_print
data:
page_id: "{{ page_id }}"
status: >
{% if trigger.id == 'completed' %}Completed
{% elif trigger.id == 'failed' %}Failed
{% else %}Cancelled{% endif %}
weight: "{{ states('sensor.jordyns_layer_slayer_print_weight') | float(0) }}"
length: "{{ states('sensor.jordyns_layer_slayer_print_length') | float(0) }}"
completed: "{{ now().strftime('%Y-%m-%dT%H:%M:%S') }}"
- action: input_text.set_value
target:
entity_id: input_text.notion_page_id_jordyns_layer_slayer
data:
value: ""
@@ -0,0 +1,75 @@
# ------------------------------------------------------------
# ELEGOO: Printney Spears (Centauri Carbon)
# Note: Uses current_status + file_name sensors (Elegoo integration)
# No weight, length, or filament data available
# Status values: "printing" / "complete" / "failed" — verify in Dev Tools
# ------------------------------------------------------------
- alias: "Print Log - Printney Spears - Start"
id: print_log_printney_spears_start
description: "Creates Notion entry when Printney Spears begins printing"
initial_state: false
mode: single
trigger:
- platform: state
entity_id: sensor.saturn_iv_print_status
from:
- "idle"
- "stopped"
- "complete"
to: "printing"
condition:
- condition: template
value_template: "{{ states('input_text.notion_page_id_centauri_carbon') | length < 10 }}"
action:
- delay: "00:00:05"
- action: rest_command.notion_create_print
data:
file_name: "{{ states('sensor.centauri_carbon_file_name') }}"
printer_name: "Printney Spears"
filament_type: "Unknown"
filament_color: "Unknown"
started: "{{ now().strftime('%Y-%m-%dT%H:%M:%S') }}"
response_variable: notion_response
- action: input_text.set_value
target:
entity_id: input_text.notion_page_id_centauri_carbon
data:
value: "{{ notion_response.content.id | default('') }}"
- alias: "Print Log - Printney Spears - Finish"
id: print_log_printney_spears_finish
description: "Updates Notion entry when Printney Spears finishes or fails"
initial_state: false
mode: single
trigger:
- platform: state
entity_id: sensor.saturn_iv_print_status
to: "complete"
id: completed
- platform: state
entity_id: sensor.saturn_iv_print_status
to: "failed"
id: failed
condition:
- condition: template
value_template: "{{ states('input_text.notion_page_id_centauri_carbon') | length > 10 }}"
action:
- delay: "00:00:05"
- variables:
page_id: "{{ states('input_text.notion_page_id_centauri_carbon') }}"
- condition: template
value_template: "{{ page_id | length > 10 }}"
- action: rest_command.notion_update_print
data:
page_id: "{{ page_id }}"
status: >
{% if trigger.id == 'completed' %}Completed
{% else %}Failed{% endif %}
weight: 0
length: 0
completed: "{{ now().strftime('%Y-%m-%dT%H:%M:%S') }}"
- action: input_text.set_value
target:
entity_id: input_text.notion_page_id_centauri_carbon
data:
value: ""
@@ -0,0 +1,73 @@
# ------------------------------------------------------------
# ELEGOO: Saturn (resin printer)
# Note: Uses current_status + file_name sensors (Elegoo integration)
# No weight, length, or filament data available
# Status values: "printing" / "complete" / "failed" — verify in Dev Tools
# ------------------------------------------------------------
- alias: "Print Log - Saturn - Start"
id: print_log_saturn_start
description: "Creates Notion entry when Saturn begins printing"
mode: single
trigger:
- platform: state
entity_id: sensor.saturn_iv_print_status
from:
- "idle"
- "stopped"
- "complete"
to: "printing"
condition:
- condition: template
value_template: "{{ states('input_text.notion_page_id_saturn') | length < 10 }}"
action:
- delay: "00:00:05"
- action: rest_command.notion_create_print
data:
file_name: "{{ states('sensor.saturn_file_name') }}"
printer_name: "Saturn"
filament_type: "Unknown"
filament_color: "Unknown"
started: "{{ now().strftime('%Y-%m-%dT%H:%M:%S') }}"
response_variable: notion_response
- action: input_text.set_value
target:
entity_id: input_text.notion_page_id_saturn
data:
value: "{{ notion_response.content.id | default('') }}"
- alias: "Print Log - Saturn - Finish"
id: print_log_saturn_finish
description: "Updates Notion entry when Saturn finishes or fails"
mode: single
trigger:
- platform: state
entity_id: sensor.saturn_iv_print_status
to: "complete"
id: completed
- platform: state
entity_id: sensor.saturn_iv_print_status
to: "failed"
id: failed
condition:
- condition: template
value_template: "{{ states('input_text.notion_page_id_saturn') | length > 10 }}"
action:
- delay: "00:00:05"
- variables:
page_id: "{{ states('input_text.notion_page_id_saturn') }}"
- condition: template
value_template: "{{ page_id | length > 10 }}"
- action: rest_command.notion_update_print
data:
page_id: "{{ page_id }}"
status: >
{% if trigger.id == 'completed' %}Completed
{% else %}Failed{% endif %}
weight: 0
length: 0
completed: "{{ now().strftime('%Y-%m-%dT%H:%M:%S') }}"
- action: input_text.set_value
target:
entity_id: input_text.notion_page_id_saturn
data:
value: ""
@@ -0,0 +1,213 @@
blueprint:
name: ESPHome Device Auto Bulk Update
description: 'Automatically updates ESPHome devices when a specified number of them
have pending updates. Optionally sends a notification before updating.
DEVICE FIRMWARE ENTITIES MUST BE ENABLED
FLOW:
1. Triggers when device update count exceeds the threshold, or daily sweep at
the start of the time window.
2. Checks the time window and device count conditions are met.
3. Sends a notification listing devices to be updated with Update Now and Cancel
buttons.
4. Waits for the delay period listening for a button response.
5. If Cancel is tapped - sends a cancellation notification and stops.
6. If Update Now is tapped or delay expires - proceeds with updates.
7. Updates all devices at once or sequentially depending on mode selected.
8. Sends a completion notification listing updated devices.
9. Waits out the cooldown period before the automation can trigger again.
'
domain: automation
source_url: https://github.com/bferd/homeassistant-blueprints/blob/main/esphome_auto_bulk_update.yaml
input:
update_threshold:
name: Device Update Threshold
description: How many devices need a pending update before updates are pushed.
default: 1
selector:
number:
min: 1.0
max: 20.0
step: 1.0
mode: slider
update_delay:
name: Delay Before Updating
description: How many minutes to wait before pushing updates.
default: 30
selector:
number:
min: 0.0
max: 60.0
step: 1.0
mode: slider
unit_of_measurement: minutes
cooldown:
name: Cooldown After Update
description: Minutes to wait after updates complete before the automation can
trigger again.
default: 30
selector:
number:
min: 0.0
max: 120.0
step: 1.0
mode: slider
unit_of_measurement: minutes
time_window_start:
name: Update Window Start
description: Earliest time of day updates can be triggered.
default: 08:00:00
selector:
time: {}
time_window_end:
name: Update Window End
description: Latest time of day updates can be triggered.
default: '21:00:00'
selector:
time: {}
notify_device:
name: Notification Device
description: Device to notify before updates are pushed.
selector:
device:
integration: mobile_app
multiple: false
update_mode:
name: Update Mode
description: Update all devices at once or one at a time sequentially.
default: all_at_once
selector:
select:
options:
- label: All at once
value: all_at_once
- label: One at a time (Sequential)
value: sequential
custom_value: false
multiple: false
sort: false
sequential_delay:
name: Delay Between Sequential Updates
description: Minutes to wait between each device when using sequential mode.
default: 0
selector:
number:
min: 0.0
max: 10.0
step: 1.0
mode: slider
unit_of_measurement: minutes
variables:
update_threshold: !input update_threshold
update_delay: !input update_delay
cooldown: !input cooldown
update_mode: !input update_mode
sequential_delay: !input sequential_delay
device_list: "{{ expand(integration_entities('esphome'))\n | selectattr(\"entity_id\",
\"contains\", \"update\")\n | selectattr(\"state\", \"eq\", \"on\")\n | map(attribute='entity_id')
| list }}\n"
device_names: "{{ expand(integration_entities('esphome'))\n | selectattr(\"entity_id\",
\"contains\", \"update\")\n | selectattr(\"state\", \"eq\", \"on\")\n | map(attribute='name')
| join(', ') }}\n"
device_count: "{{ expand(integration_entities('esphome'))\n | selectattr(\"entity_id\",
\"contains\", \"update\")\n | selectattr(\"state\", \"eq\", \"on\")\n | list
| count }}\n"
trigger:
- platform: template
value_template: "{{ expand(integration_entities('esphome'))\n | selectattr(\"entity_id\",
\"contains\", \"update\")\n | selectattr(\"state\", \"eq\", \"on\")\n | list
| count > update_threshold }}\n"
- platform: time
at: !input time_window_start
- platform: time_pattern
hours: /1
condition:
- condition: time
after: !input time_window_start
before: !input time_window_end
- condition: template
value_template: "{{ expand(integration_entities('esphome'))\n | selectattr(\"entity_id\",
\"contains\", \"update\")\n | selectattr(\"state\", \"eq\", \"on\")\n | list
| count > update_threshold }}\n"
action:
- domain: mobile_app
type: notify
device_id: !input notify_device
title: ESPHome Device Update(s)
message: '{{ device_count }} device(s) need updating: {{ device_names }}. Updates
will begin in {{ update_delay }} minute(s).
'
data:
sticky: 'true'
persistent: 'true'
actions:
- action: UPDATE_NOW_ESPHOME
title: Update Now
- action: CANCEL_ESPHOME_UPDATE
title: Cancel
- wait_for_trigger:
- platform: event
event_type: mobile_app_notification_action
event_data:
action: CANCEL_ESPHOME_UPDATE
- platform: event
event_type: mobile_app_notification_action
event_data:
action: UPDATE_NOW_ESPHOME
timeout:
minutes: '{{ update_delay }}'
continue_on_timeout: true
- if:
- condition: template
value_template: "{{ wait.trigger is not none and\n wait.trigger.event.data.action
== 'CANCEL_ESPHOME_UPDATE' }}\n"
then:
- domain: mobile_app
type: notify
device_id: !input notify_device
title: ESPHome Update Cancelled
message: ESPHome device updates have been cancelled.
else:
- if:
- condition: template
value_template: '{{ update_mode == ''sequential'' }}'
then:
- repeat:
for_each: '{{ device_list }}'
sequence:
- action: update.install
continue_on_error: true
data: {}
target:
entity_id: '{{ repeat.item }}'
- delay:
minutes: '{{ 0 if repeat.last else sequential_delay }}'
else:
- action: update.install
continue_on_error: true
data: {}
target:
entity_id: '{{ device_list }}'
- domain: mobile_app
type: notify
device_id: !input notify_device
title: ESPHome Updates Complete
message: '{{ device_count }} device(s) have been updated: {{ device_names }}.
'
- delay:
minutes: '{{ cooldown }}'
mode: single
@@ -0,0 +1,335 @@
blueprint:
name: Elegoo Printer Progress Notification (v4)
description: Sends notifications for printer progress, status changes, and error
states.
author: Daniel Cherubini
homeassistant:
min_version: 2024.6.0
domain: automation
input:
percent_complete_entity:
name: Percent Complete Entity
description: The percent complete sensor for the Elegoo printer (look for entities
ending in '_percent_complete').
selector:
entity:
domain:
- sensor
integration: elegoo_printer
multiple: false
reorder: false
print_status_entity:
name: Print Status Entity
description: The print status sensor (should end with '_print_status').
selector:
entity:
domain:
- sensor
integration: elegoo_printer
device_class:
- enum
multiple: false
reorder: false
current_status_entity:
name: Current Status Entity
description: The current status sensor (should end with '_current_status').
selector:
entity:
domain:
- sensor
integration: elegoo_printer
device_class:
- enum
multiple: false
reorder: false
error_status_reason_entity:
name: Error Status Reason Entity
description: The error status reason sensor (should end with '_current_print_error_status_reason').
selector:
entity:
domain:
- sensor
integration: elegoo_printer
device_class:
- enum
multiple: false
reorder: false
notify_device:
name: Notification Device
description: The device to send notifications to.
selector:
device:
integration: mobile_app
multiple: false
percentage_divisor:
name: Notification Frequency
description: Notify when the percentage complete is divisible by this number.
Use 1 to be notified on every percentage change.
selector:
select:
options:
- '1'
- '2'
- '5'
sort: false
custom_value: false
multiple: false
default: '5'
camera_entity:
name: Printer Camera (Optional)
description: Override the default camera entity for the printer. Leave blank
to use the default camera from the printer device.
default: ''
selector:
entity:
domain:
- camera
multiple: false
reorder: false
dashboard_url:
name: Dashboard URL (Optional)
description: The path to open when the notification is clicked (e.g., '/dashboard-example/example',
not the full URL).
default: ''
enable_status_notifications:
name: Enable Status Change Notifications
description: Send notifications when printer status changes (e.g., homing, heating,
etc.)
default: true
selector:
boolean: {}
source_url: https://github.com/danielcherubini/elegoo-homeassistant/blob/main/blueprints/automation/elegoo_printer/elegoo_printer_progress.yaml
mode: single
max_exceeded: silent
variables:
percent_complete_entity: !input percent_complete_entity
notify_device: !input notify_device
percentage_divisor: !input percentage_divisor
camera_entity_input: !input camera_entity
dashboard_url: !input dashboard_url
enable_status_notifications: !input enable_status_notifications
print_status_entity: !input print_status_entity
current_status_entity: !input current_status_entity
error_status_reason_entity: !input error_status_reason_entity
printer_device: '{{ device_id(percent_complete_entity) }}'
notification_group: '{{ device_attr(printer_device, ''name'') | slugify }}'
end_time_entity: '{{ device_entities(printer_device) | select(''search'', ''_end_time'')
| first }}'
file_name_entity: '{{ device_entities(printer_device) | select(''search'', ''_file_name'')
| first }}'
current_layer_entity: '{{ device_entities(printer_device) | select(''search'', ''_current_layer'')
| first }}'
total_layers_entity: '{{ device_entities(printer_device) | select(''search'', ''_total_layers'')
| first }}'
camera_entity: "{{ (camera_entity_input\n if camera_entity_input != ''\n else
(device_entities(printer_device) | select('match', '^camera\\.') | first))\n |
default('', true) }}"
trigger:
- platform: state
entity_id: !input percent_complete_entity
- platform: state
entity_id: !input print_status_entity
- platform: state
entity_id: !input current_status_entity
- platform: state
entity_id: !input error_status_reason_entity
condition: []
action:
- choose:
- conditions:
- condition: template
value_template: '{{ trigger.entity_id == percent_complete_entity }}'
- condition: template
value_template: '{{ states(percent_complete_entity) not in [''unknown'', ''unavailable'']
}}'
- condition: template
value_template: '{{ states(percent_complete_entity) | int(0) < 100 }}'
- condition: template
value_template: '{{ states(percent_complete_entity) | int(0) % (percentage_divisor
| int) == 0 }}'
- condition: template
value_template: '{{ current_status_entity != none and states(current_status_entity)
== ''printing'' }}'
- condition: template
value_template: '{{ notify_device != '''' }}'
sequence:
- device_id: !input notify_device
domain: mobile_app
type: notify
title: 'Printing: {{ states(percent_complete_entity) | int(0) }}%
Layer: {{ (states(current_layer_entity)|default(''?'', true)) }}/{{ (states(total_layers_entity)|default(''?'',
true)) }}'
message: '{{ states(file_name_entity)|default(''Unknown file'', true) }}'
data:
chronometer: true
when: '{{ as_timestamp(states(end_time_entity))|int if end_time_entity !=
none and states(end_time_entity) not in [''unknown'', ''unavailable''] else
0 }}'
progress: '{{ states(percent_complete_entity)|int(0) }}'
progress_max: 100
image: '{{ (''/api/camera_proxy/'' ~ camera_entity) if (camera_entity|default('''',
true)) != '''' else '''' }}'
url: '{{ dashboard_url }}'
clickAction: '{{ dashboard_url }}'
group: '{{ notification_group }}'
channel: '{{ notification_group }}'
tag: '{{ notification_group }}'
alert_once: true
sticky: true
push:
interruption-level: passive
- conditions:
- condition: template
value_template: '{{ notify_device != '''' }}'
- condition: template
value_template: '{{ states(print_status_entity) == ''complete'' }}'
- condition: or
conditions:
- condition: and
conditions:
- condition: template
value_template: '{{ trigger.entity_id == print_status_entity }}'
- condition: template
value_template: '{{ trigger.from_state.state != ''complete'' }}'
- condition: and
conditions:
- condition: template
value_template: '{{ trigger.entity_id == percent_complete_entity }}'
sequence:
- device_id: !input notify_device
domain: mobile_app
type: notify
title: "\U0001F389 Print Complete!"
message: Print has finished successfully
data:
image: '{{ (''/api/camera_proxy/'' ~ camera_entity) if (camera_entity|default('''',
true)) != '''' else '''' }}'
url: '{{ dashboard_url }}'
clickAction: '{{ dashboard_url }}'
group: '{{ notification_group }}'
channel: '{{ notification_group }}'
tag: '{{ notification_group }}'
sticky: true
alert_once: true
push:
interruption-level: time-sensitive
- conditions:
- condition: template
value_template: '{{ trigger.entity_id == print_status_entity }}'
- condition: template
value_template: '{{ notify_device != '''' }}'
- condition: template
value_template: '{{ enable_status_notifications }}'
- condition: template
value_template: '{{ states(print_status_entity) not in [''complete'', ''stopped'',
''stopping'', ''idle'', ''unknown'', ''unavailable''] }}'
sequence:
- device_id: !input notify_device
domain: mobile_app
type: notify
title: Printer Status Update
message: "{% set status = states(print_status_entity) %} {% set file_name =
states(file_name_entity)|default('Unknown file', true) %} {% if status ==
'homing' %}\n \U0001F3E0 Printer is homing\n{% elif status == 'printing'
%}\n \U0001F5A8 Started printing: {{ file_name }}\n{% elif status == 'paused'
or status == 'pausing' %}\n ⏸️ Print paused: {{ file_name }}\n{% elif status
== 'loading' %}\n \U0001F4E5 Loading filament\n{% elif status == 'dropping'
%}\n \U0001F53D Platform dropping\n{% elif status == 'lifting' %}\n \U0001F53C
Platform lifting\n{% elif status == 'file_checking' %}\n \U0001F4C1 Checking
print file\n{% elif status == 'recovery' or status == 'printing_recovery'
%}\n \U0001F504 Print recovery in progress\n{% elif status == 'preheating'
%}\n \U0001F525 Preheating for print\n{% elif status == 'leveling' %}\n \U0001F4D0
Bed leveling in progress\n{% else %}\n \U0001F4CA Status: {{ status }}\n{%
endif %}"
data:
url: '{{ dashboard_url }}'
clickAction: '{{ dashboard_url }}'
group: '{{ notification_group }}'
channel: '{{ notification_group }}'
tag: '{{ notification_group }}_status'
alert_once: true
push:
interruption-level: passive
- conditions:
- condition: template
value_template: '{{ trigger.entity_id == current_status_entity }}'
- condition: template
value_template: '{{ notify_device != '''' }}'
- condition: template
value_template: '{{ enable_status_notifications }}'
- condition: template
value_template: '{{ states(current_status_entity) not in [''idle'', ''printing'',
''unknown'', ''unavailable''] }}'
sequence:
- device_id: !input notify_device
domain: mobile_app
type: notify
title: Machine Status Update
message: "{% set status = states(current_status_entity) %} {% set file_name
= states(file_name_entity)|default('Unknown file', true) %} {% if status ==
'file_transferring' %}\n \U0001F4C1 File transfer in progress\n{% elif status
== 'exposure_testing' %}\n \U0001F52C Exposure test running\n{% elif status
== 'devices_testing' %}\n \U0001F527 Device self-check running\n{% elif status
== 'leveling' %}\n \U0001F4D0 Bed leveling in progress\n{% elif status ==
'loading_unloading' %}\n \U0001F4E5\U0001F4E4 Loading/unloading filament\n{%
else %}\n \U0001F4CA Machine status: {{ status }}\n{% endif %}"
data:
url: '{{ dashboard_url }}'
clickAction: '{{ dashboard_url }}'
group: '{{ notification_group }}'
channel: '{{ notification_group }}'
tag: '{{ notification_group }}_machine_status'
alert_once: true
push:
interruption-level: passive
- conditions:
- condition: template
value_template: '{{ trigger.entity_id == error_status_reason_entity }}'
- condition: template
value_template: '{{ notify_device != '''' }}'
- condition: template
value_template: '{{ states(error_status_reason_entity) not in [''ok'', ''none'',
''unknown'', ''unavailable''] }}'
sequence:
- device_id: !input notify_device
domain: mobile_app
type: notify
title: "\U0001F6A8 Critical Printer Error!"
message: "{% set error = states(error_status_reason_entity) %} {% set file_name
= states(file_name_entity)|default('current print', true) %} {% if error ==
'filament_runout' %}\n \U0001F9F5 Filament runout detected during {{ file_name
}}! Please load new filament.\n{% elif error == 'filament_about_to_runout'
%}\n ⚠️ Filament runout imminent for {{ file_name }}! Please prepare new
filament.\n{% elif error == 'filament_jam' %}\n \U0001F6AB Filament jam detected
during {{ file_name }}! Please check the extruder.\n{% elif error == 'temp_error'
%}\n \U0001F321 Temperature error detected! Please check nozzle and bed
temperatures.\n{% elif error == 'level_failed' %}\n \U0001F4D0 Bed leveling
failed! Please check the bed leveling system.\n{% elif error == 'home_failed'
or error == 'home_failed_x' or error == 'home_failed_y' or error == 'home_failed_z'
%}\n \U0001F3E0 Homing failed! Please check the printer axes and endstops.\n{%
elif error == 'bed_adhesion_failed' %}\n \U0001F6CF Print detached from
bed during {{ file_name }}!\n{% elif error == 'move_abnormal' %}\n ⚙️ Motor
movement abnormality detected!\n{% elif error == 'file_error' %}\n \U0001F4C1
Print file error during {{ file_name }}!\n{% elif error == 'udisk_remove'
%}\n \U0001F4BE USB drive was removed during printing!\n{% elif error ==
'nozzle_temp_sensor_offline' %}\n \U0001F321 Nozzle temperature sensor is
offline!\n{% elif error == 'bed_temp_sensor_offline' %}\n \U0001F321 Bed
temperature sensor is offline!\n{% elif error == 'camera_error' %}\n \U0001F4F7
Camera connection error!\n{% elif error == 'network_error' %}\n \U0001F310
Network connection error!\n{% elif error == 'server_connect_failed' %}\n \U0001F5A5
Server connection failed!\n{% elif error == 'disconnect_app' %}\n \U0001F4F1
Controlling app disconnected during print!\n{% else %}\n ⚠️ Error: {{ error
}}\n{% endif %}"
data:
image: '{{ (''/api/camera_proxy/'' ~ camera_entity) if (camera_entity|default('''',
true)) != '''' else '''' }}'
url: '{{ dashboard_url }}'
clickAction: '{{ dashboard_url }}'
group: '{{ notification_group }}'
channel: '{{ notification_group }}'
tag: '{{ notification_group }}_critical_error'
sticky: true
alert_once: false
push:
interruption-level: time-sensitive
@@ -0,0 +1,66 @@
blueprint:
name: Duck area players volume while assist in progress
description: 'Temporarily lowers the volume of media players in the same area when
any selected Assist Satellite starts listening, then restores their volume after
the interaction ends.
'
domain: automation
input:
satellites:
name: Assist Satellites
description: List of Assist Satellite entities to monitor.
selector:
entity:
domain:
- assist_satellite
multiple: true
reorder: false
duck_volume:
name: Duck Volume Level
description: Volume level to set while assist is listening (e.g. 0.2 = 20%).
default: 0.2
selector:
number:
min: 0.0
max: 1.0
step: 0.01
unit_of_measurement: fraction (01)
mode: slider
source_url: https://raw.githubusercontent.com/formatBCE/Respeaker-Lite-ESPHome-integration/refs/heads/main/blueprints/automation/formatbce/duck_players_on_satellite_working.yaml
trigger:
- platform: state
entity_id: !input satellites
to: listening
variables:
v_duck_volume: !input duck_volume
v_players: "{{ states.media_player\n | selectattr('state', 'equalto', 'playing')\n
\ | selectattr('attributes.volume_level', 'defined')\n | selectattr('entity_id',
'in', area_entities(area_id(trigger.entity_id)))\n | rejectattr('entity_id',
'in', device_entities(device_id(trigger.entity_id)))\n | rejectattr('attributes.active_queue',
'in', device_entities(device_id(trigger.entity_id)))\n | map(attribute='entity_id')\n
\ | list\n}}"
v_volumes: "{% set vol = namespace(umes = []) %} {% for i in v_players %}\n {%
set vol.umes = vol.umes + [{'id': i, 'volume_diff': state_attr(i, 'volume_level')
- v_duck_volume }] %}\n{% endfor %} {{ vol.umes }}"
action:
- service: media_player.volume_set
data:
volume_level: '{{ v_duck_volume }}'
target:
entity_id: '{{ v_players }}'
- wait_for_trigger:
- platform: template
value_template: '{{ is_state(trigger.entity_id, ''idle'') }}'
timeout: 00:03:00
- repeat:
for_each: '{{ v_volumes }}'
sequence:
- service: media_player.volume_set
data:
volume_level: '{{ state_attr(repeat.item.id, ''volume_level'') + repeat.item.volume_diff
}}'
target:
entity_id: '{{ repeat.item.id }}'
mode: parallel
max: 10
@@ -0,0 +1,44 @@
blueprint:
name: Play TTS URI via Media Player
description: Listen for a TTS URI event from an ESPHome device and play it using
a selected media player.
domain: automation
input:
tts_device:
name: ESPHome TTS Device
description: Select the ESPHome device that sends the TTS URI event.
selector:
device:
entity:
- domain:
- assist_satellite
filter:
- manufacturer: formatbce
model: Respeaker Lite Satellite
- manufacturer: formatbce
model: Koala Satellite
multiple: false
target_media_player:
name: Target Media Player
description: Media player entity that should play the TTS URI.
selector:
entity:
domain:
- media_player
multiple: false
reorder: false
source_url: https://raw.githubusercontent.com/formatBCE/Respeaker-Lite-ESPHome-integration/refs/heads/main/blueprints/automation/formatbce/redirect_respeaker_tts.yaml
trigger:
- platform: event
event_type: esphome.tts_uri
event_data:
device_id: !input tts_device
action:
- service: media_player.play_media
target:
entity_id: !input target_media_player
data:
announce: true
media_content_type: music
media_content_id: '{{ trigger.event.data.uri }}'
mode: single
@@ -0,0 +1,43 @@
blueprint:
name: Set Respeaker Alarm Time from Time Helper
domain: automation
input:
datetime_helper:
name: Datetime Helper
description: The input_datetime helper that triggers the alarm time update.
Must have time.
selector:
entity:
filter:
- domain:
- input_datetime
multiple: false
reorder: false
esphome_device:
name: Respeaker Device
description: The ESPHome device with set_alarm_time service
selector:
device:
entity:
- domain:
- assist_satellite
filter:
- manufacturer: formatbce
model: Respeaker Lite Satellite
- manufacturer: formatbce
model: Koala Satellite
- manufacturer: formatbce
model: Respeaker XVF3800 Satellite
multiple: false
source_url: https://raw.githubusercontent.com/formatBCE/Respeaker-Lite-ESPHome-integration/refs/heads/main/blueprints/automation/formatbce/set_respeaker_alarm_time_from_input_datetime_helper.yaml
variables:
esphome_device: !input esphome_device
triggers:
- trigger: state
entity_id: !input datetime_helper
actions:
- action: esphome.{{ device_attr(esphome_device, 'name') | slugify }}_set_alarm_time
data:
alarm_time_hh_mm: '{{ trigger.to_state.attributes.hour }}:{{ trigger.to_state.attributes.minute
}}'
mode: single
@@ -0,0 +1,58 @@
blueprint:
name: Motion-activated Light
description: Turn on a light when motion is detected.
domain: automation
source_url: https://github.com/home-assistant/core/blob/dev/homeassistant/components/automation/blueprints/motion_light.yaml
author: Home Assistant
input:
motion_entity:
name: Motion Sensor
selector:
entity:
filter:
- device_class: occupancy
domain: binary_sensor
- device_class: motion
domain: binary_sensor
light_target:
name: Light
selector:
target:
entity:
domain: light
no_motion_wait:
name: Wait time
description: Time to leave the light on after last motion is detected.
default: 120
selector:
number:
min: 0
max: 3600
unit_of_measurement: seconds
# If motion is detected within the delay,
# we restart the script.
mode: restart
max_exceeded: silent
triggers:
trigger: state
entity_id: !input motion_entity
from: "off"
to: "on"
actions:
- alias: "Turn on the light"
action: light.turn_on
target: !input light_target
- alias: "Wait until there is no motion from device"
wait_for_trigger:
trigger: state
entity_id: !input motion_entity
from: "on"
to: "off"
- alias: "Wait the number of seconds that has been set"
delay: !input no_motion_wait
- alias: "Turn off the light"
action: light.turn_off
target: !input light_target
@@ -0,0 +1,50 @@
blueprint:
name: Zone Notification
description: Send a notification to a device when a person leaves a specific zone.
domain: automation
source_url: https://github.com/home-assistant/core/blob/dev/homeassistant/components/automation/blueprints/notify_leaving_zone.yaml
author: Home Assistant
input:
person_entity:
name: Person
selector:
entity:
filter:
domain: person
zone_entity:
name: Zone
selector:
entity:
filter:
domain: zone
notify_device:
name: Device to notify
description: Device needs to run the official Home Assistant app to receive notifications.
selector:
device:
filter:
integration: mobile_app
triggers:
trigger: state
entity_id: !input person_entity
variables:
zone_entity: !input zone_entity
# This is the state of the person when it's in this zone.
zone_state: "{{ states[zone_entity].name }}"
person_entity: !input person_entity
person_name: "{{ states[person_entity].name }}"
conditions:
condition: template
# The first case handles leaving the Home zone which has a special state when zoning called 'home'.
# The second case handles leaving all other zones.
value_template: "{{ zone_entity == 'zone.home' and trigger.from_state.state == 'home' and trigger.to_state.state != 'home' or trigger.from_state.state == zone_state and trigger.to_state.state != zone_state }}"
actions:
- alias: "Notify that a person has left the zone"
domain: mobile_app
type: notify
device_id: !input notify_device
message: "{{ person_name }} has left {{ zone_state }}"
@@ -0,0 +1,514 @@
blueprint:
name: Bambu Lab - Spaghetti Detection
description: Bambu Lab - Spaghetti Detection
domain: automation
input:
home_assistant_host:
name: Home Assistant Host
description: Home Assistant host
default: http://192.168.1.123:8123
obico_host:
name: Obico ML API Host
description: Obico ML API host
default: http://192.168.1.123:3333
obico_auth_token:
name: Obico ML API Auth Token
description: Obico ML API authentication token
default: obico_api_secret
detection_frequency:
name: Detection Frequency
description: The detection algorithm will run in every defined seconds
default: /5
selector:
select:
options:
- label: Every second
value: /1
- label: Every 5 seconds
value: /5
- label: Every 10 seconds
value: /10
- label: Every 30 seconds
value: /30
- label: Every 60 seconds
value: /59
multiple: false
mode: dropdown
sort: false
custom_value: false
auto_turn_on_light:
name: Automatically Turn On Printer Lights
description: Turns on printer lights before spaghetti detection operation
default: true
selector:
boolean: {}
notification_settings:
name: Notification Settings
description: Type of notification to send after detecting a failure
default: standard
selector:
select:
mode: dropdown
options:
- label: Critical Notification
value: critical
- label: Standard Notification
value: standard
- label: None
value: none
sort: false
multiple: false
custom_value: false
failure_action:
name: On Failure Action
description: What to do after detecting a failure
default: pause
selector:
select:
mode: dropdown
options:
- label: Pause
value: pause
- label: Stop
value: stop
- label: Warn
value: warn
sort: false
multiple: false
custom_value: false
notification_service:
name: Mobile devices notification service
description: The notification service for mobile devices (eg. notify.mobile_app_<your_device_id_here>).
You can provide both a notify group or a single notify device here.
default: notify.notify
selector:
text: {}
printer_print_status_sensor:
name: Printer Print Status Sensor
description: Bambu Lab printer print status sensor
selector:
entity:
filter:
- integration: bambu_lab
domain:
- sensor
device_class:
- enum
multiple: false
printer_current_stage_sensor:
name: Printer Current Stage Sensor
description: Bambu Lab printer current stage sensor
selector:
entity:
filter:
- integration: bambu_lab
domain:
- sensor
device_class:
- enum
multiple: false
printer_camera:
name: Printer Camera Entity
description: Bambu Lab printer camera entity
selector:
entity:
filter:
- domain:
- camera
multiple: false
printer_pause_button:
name: Printer Pause Button Entity
description: Bambu Lab printer pause button entity
selector:
entity:
filter:
- integration: bambu_lab
domain:
- button
multiple: false
printer_resume_button:
name: Printer Resume Button Entity
description: Bambu Lab printer resume button entity
selector:
entity:
filter:
- integration: bambu_lab
domain:
- button
multiple: false
printer_stop_button:
name: Printer Stop Button Entity
description: Bambu Lab printer stop button entity
selector:
entity:
filter:
- integration: bambu_lab
domain:
- button
multiple: false
printer_chamber_light:
name: Printer Chamber Light
description: Bambu Lab printer chamber light
selector:
entity:
filter:
- integration: bambu_lab
domain:
- light
multiple: false
source_url: https://github.com/nberktumer/ha-bambu-lab-p1-spaghetti-detection/blob/main/blueprints/spaghetti_detection.yaml
variables:
HOME_ASSISTANT_HOST_VAR: !input home_assistant_host
PRINTER_CAMERA_VAR: !input printer_camera
FAILURE_ACTION_VAR: !input failure_action
NOTIFICATION_SETTINGS_VAR: !input notification_settings
DETECTION_FREQUENCY_VAR: !input detection_frequency
mode: single
max_exceeded: silent
trigger:
- platform: state
entity_id:
- !input printer_current_stage_sensor
to: printing
id: BAMBU_LAB_PRINTER_STAGE_CHANGE
- platform: event
event_type: mobile_app_notification_action
id: BAMBU_LAB_PAUSE_PRINTING
event_data:
action: BAMBU_LAB_PAUSE_PRINTING
- platform: event
event_type: mobile_app_notification_action
id: BAMBU_LAB_RESUME_PRINTING
event_data:
action: BAMBU_LAB_RESUME_PRINTING
- platform: event
event_type: mobile_app_notification_action
id: BAMBU_LAB_STOP_PRINTING
event_data:
action: BAMBU_LAB_STOP_PRINTING
- trigger: time_pattern
id: BAMBU_LAB_DETECTION_TRIGGER
seconds: !input detection_frequency
condition: []
action:
- choose:
- conditions:
- condition: trigger
id: BAMBU_LAB_PRINTER_STAGE_CHANGE
sequence:
- service: number.set_value
data:
value: 0
target:
entity_id:
- number.bambu_lab_p1_spaghetti_detection_current_frame_number
- number.bambu_lab_p1_spaghetti_detection_ewm_mean
- number.bambu_lab_p1_spaghetti_detection_rolling_mean_short
- number.bambu_lab_p1_spaghetti_detection_rolling_mean_long
- number.bambu_lab_p1_spaghetti_detection_normalized_p
- number.bambu_lab_p1_spaghetti_detection_adjusted_ewm_mean
- number.bambu_lab_p1_spaghetti_detection_p_sum
- if:
- condition: and
conditions:
- condition: state
entity_id: !input printer_chamber_light
state: 'off'
- condition: template
value_template: !input auto_turn_on_light
then:
- service: light.turn_on
target:
entity_id:
- !input printer_chamber_light
- conditions:
- condition: trigger
id:
- BAMBU_LAB_PAUSE_PRINTING
- BAMBU_LAB_RESUME_PRINTING
- BAMBU_LAB_STOP_PRINTING
sequence:
- choose:
- conditions:
- condition: trigger
id:
- BAMBU_LAB_PAUSE_PRINTING
sequence:
- service: button.press
data: {}
target:
entity_id: !input printer_pause_button
- conditions:
- condition: trigger
id: BAMBU_LAB_RESUME_PRINTING
sequence:
- service: button.press
data: {}
target:
entity_id: !input printer_resume_button
- conditions:
- condition: trigger
id: BAMBU_LAB_STOP_PRINTING
sequence:
- service: button.press
data: {}
target:
entity_id: !input printer_stop_button
- conditions:
- condition: trigger
id: BAMBU_LAB_DETECTION_TRIGGER
sequence:
- if:
- condition: not
conditions:
- condition: state
entity_id: !input printer_print_status_sensor
state: running
then:
- stop: ''
- if:
- condition: template
value_template: '{{ now().second % (DETECTION_FREQUENCY_VAR | replace(''/'', '''') | int(0)) > 0 }}'
then:
- stop: ''
- if:
- condition: and
conditions:
- condition: state
entity_id: !input printer_chamber_light
state: 'off'
- condition: template
value_template: !input auto_turn_on_light
then:
- service: light.turn_on
target:
entity_id:
- !input printer_chamber_light
- service: bambu_lab_p1_spaghetti_detection.predict
data:
obico_host: !input obico_host
obico_auth_token: !input obico_auth_token
image_url: '{{ HOME_ASSISTANT_HOST_VAR }}{{ state_attr(PRINTER_CAMERA_VAR,
''entity_picture'') }}'
response_variable: result
- service: number.set_value
data:
value: '{{ result.result.detections | map(attribute=1) | sum | float }}'
target:
entity_id: number.bambu_lab_p1_spaghetti_detection_p_sum
- service: number.set_value
data:
value: '{{ states(''number.bambu_lab_p1_spaghetti_detection_current_frame_number'')
| float + 1 }}'
target:
entity_id: number.bambu_lab_p1_spaghetti_detection_current_frame_number
- service: number.set_value
data:
value: '{{ states(''number.bambu_lab_p1_spaghetti_detection_lifetime_frame_number'')
| float + 1 }}'
target:
entity_id: number.bambu_lab_p1_spaghetti_detection_lifetime_frame_number
- service: number.set_value
data:
value: '{{ (states(''number.bambu_lab_p1_spaghetti_detection_p_sum'') | float)
* (2 / (12 + 1)) + (states(''number.bambu_lab_p1_spaghetti_detection_ewm_mean'')
| float) * (1 - (2 / (12 + 1))) }}'
target:
entity_id: number.bambu_lab_p1_spaghetti_detection_ewm_mean
- service: number.set_value
data:
value: '{{ (states(''number.bambu_lab_p1_spaghetti_detection_rolling_mean_short'')
| float) + ((states(''number.bambu_lab_p1_spaghetti_detection_p_sum'') |
float) - (states(''number.bambu_lab_p1_spaghetti_detection_rolling_mean_short'')
| float)) / (310 if 310 <= (states(''number.bambu_lab_p1_spaghetti_detection_current_frame_number'')
| float) else (states(''number.bambu_lab_p1_spaghetti_detection_current_frame_number'')
| float) + 1) }}'
target:
entity_id: number.bambu_lab_p1_spaghetti_detection_rolling_mean_short
- service: number.set_value
data:
value: '{{ (states(''number.bambu_lab_p1_spaghetti_detection_rolling_mean_long'')
| float) + ((states(''number.bambu_lab_p1_spaghetti_detection_p_sum'') |
float) - (states(''number.bambu_lab_p1_spaghetti_detection_rolling_mean_long'')
| float)) / (7200 if 7200 <= (states(''number.bambu_lab_p1_spaghetti_detection_lifetime_frame_number'')
| float) else (states(''number.bambu_lab_p1_spaghetti_detection_lifetime_frame_number'')
| float) + 1) }}'
entity_id: number.bambu_lab_p1_spaghetti_detection_rolling_mean_long
- if:
- condition: numeric_state
entity_id: number.bambu_lab_p1_spaghetti_detection_current_frame_number
below: 30
then:
- stop: ''
alias: if current_frame_num < 30
- service: number.set_value
data:
value: '{{ (states(''number.bambu_lab_p1_spaghetti_detection_ewm_mean'') |
float) - (states(''number.bambu_lab_p1_spaghetti_detection_rolling_mean_long'')
| float) }}'
target:
entity_id: number.bambu_lab_p1_spaghetti_detection_adjusted_ewm_mean
- service: number.set_value
data:
value: '{{ ((states(''number.bambu_lab_p1_spaghetti_detection_rolling_mean_short'')
| float) - (states(''number.bambu_lab_p1_spaghetti_detection_rolling_mean_long'')
| float)) * 3.8 }}'
target:
entity_id: number.bambu_lab_p1_spaghetti_detection_rolling_mean_diff
- service: number.set_value
data:
value: '{{ min(0.78, max(0.33, (states(''number.bambu_lab_p1_spaghetti_detection_rolling_mean_diff'')
| float))) }}'
target:
entity_id: number.bambu_lab_p1_spaghetti_detection_thresh_warning
- service: number.set_value
data:
value: '{{ (states(''number.bambu_lab_p1_spaghetti_detection_thresh_warning'')
| float) * 1.75 }}'
target:
entity_id: number.bambu_lab_p1_spaghetti_detection_thresh_failure
- service: number.set_value
data:
value: '{{ (states(''number.bambu_lab_p1_spaghetti_detection_ewm_mean'') |
float) - (states(''number.bambu_lab_p1_spaghetti_detection_rolling_mean_long'')
| float) }}'
target:
entity_id: number.bambu_lab_p1_spaghetti_detection_p
- choose:
- conditions:
- condition: numeric_state
entity_id: number.bambu_lab_p1_spaghetti_detection_p
above: number.bambu_lab_p1_spaghetti_detection_thresh_failure
sequence:
- service: number.set_value
data:
value: '{{ min(1.0, max(2.0 / 3.0, ((((states(''number.bambu_lab_p1_spaghetti_detection_p'')
| float) - (states(''number.bambu_lab_p1_spaghetti_detection_thresh_failure'')
| float)) * (1.0 - 2.0 / 3.0)) / ((states(''number.bambu_lab_p1_spaghetti_detection_thresh_failure'')
| float) * 1.5 - (states(''number.bambu_lab_p1_spaghetti_detection_thresh_failure'')
| float))) + 2.0 / 3.0)) }}'
target:
entity_id: number.bambu_lab_p1_spaghetti_detection_normalized_p
- conditions:
- condition: numeric_state
entity_id: number.bambu_lab_p1_spaghetti_detection_p
above: number.bambu_lab_p1_spaghetti_detection_thresh_warning
sequence:
- service: number.set_value
data:
value: '{{ min(2.0 / 3.0, max(1.0 / 3.0, ((((states(''number.bambu_lab_p1_spaghetti_detection_p'')
| float) - (states(''number.bambu_lab_p1_spaghetti_detection_thresh_warning'')
| float)) * (2.0 / 3.0 - 1.0 / 3.0)) / ((states(''number.bambu_lab_p1_spaghetti_detection_thresh_failure'')
| float) - (states(''number.bambu_lab_p1_spaghetti_detection_thresh_warning'')
| float))) + 1.0 / 3.0)) }}'
target:
entity_id: number.bambu_lab_p1_spaghetti_detection_normalized_p
default:
- service: number.set_value
data:
value: '{{ min(1.0 / 3.0, max(0, ((states(''number.bambu_lab_p1_spaghetti_detection_p'')
| float) * 1.0 / 3.0) / (states(''number.bambu_lab_p1_spaghetti_detection_thresh_warning'')
| float))) }}'
target:
entity_id: number.bambu_lab_p1_spaghetti_detection_normalized_p
- if:
- condition: numeric_state
entity_id: number.bambu_lab_p1_spaghetti_detection_adjusted_ewm_mean
below: 0.38
then:
- stop: ''
- if:
- condition: and
conditions:
- condition: numeric_state
entity_id: number.bambu_lab_p1_spaghetti_detection_adjusted_ewm_mean
below: 0.78
- condition: numeric_state
entity_id: number.bambu_lab_p1_spaghetti_detection_adjusted_ewm_mean
below: number.bambu_lab_p1_spaghetti_detection_rolling_mean_diff
then:
- stop: ''
- if:
- condition: template
value_template: '{{ now() - states(''datetime.bambu_lab_p1_spaghetti_detection_last_notify_time'')
| as_datetime | as_local < timedelta(minutes=1) }}'
then:
- stop: ''
alias: if now() - last_notify_time < 1min
- choose:
- conditions:
- condition: template
value_template: '{{ FAILURE_ACTION_VAR == ''pause'' }}'
sequence:
- service: button.press
data: {}
target:
entity_id: !input printer_pause_button
- conditions:
- condition: template
value_template: '{{ FAILURE_ACTION_VAR == ''stop'' }}'
sequence:
- service: button.press
data: {}
target:
entity_id: !input printer_stop_button
- choose:
- conditions:
- condition: template
value_template: '{{ NOTIFICATION_SETTINGS_VAR == ''critical'' }}'
sequence:
- service: !input notification_service
data:
title: Bambu Lab - Spaghetti Detected
message: 'Confidence: {{ (states(''number.bambu_lab_p1_spaghetti_detection_normalized_p'')
| float * 100) | int }}%'
data:
image: '{{ HOME_ASSISTANT_HOST_VAR }}{{ state_attr(PRINTER_CAMERA_VAR,
''entity_picture'') }}'
ttl: 0
priority: high
channel: alarm_stream
push:
sound:
name: default
critical: 1
volume: 0.75
actions:
- action: BAMBU_LAB_RESUME_PRINTING
title: Resume Printing
- action: BAMBU_LAB_STOP_PRINTING
title: Stop Printing
- conditions:
- condition: template
value_template: '{{ NOTIFICATION_SETTINGS_VAR == ''standard'' }}'
sequence:
- service: !input notification_service
data:
title: Bambu Lab - Spaghetti Detected
message: 'Confidence: {{ (states(''number.bambu_lab_p1_spaghetti_detection_normalized_p'')
| float * 100) | int }}%'
data:
image: '{{ HOME_ASSISTANT_HOST_VAR }}{{ state_attr(PRINTER_CAMERA_VAR,
''entity_picture'') }}'
actions:
- action: BAMBU_LAB_RESUME_PRINTING
title: Resume Printing
- action: BAMBU_LAB_STOP_PRINTING
title: Stop Printing
- service: number.set_value
data:
value: 0
target:
entity_id:
- number.bambu_lab_p1_spaghetti_detection_current_frame_number
- number.bambu_lab_p1_spaghetti_detection_ewm_mean
- number.bambu_lab_p1_spaghetti_detection_rolling_mean_short
- number.bambu_lab_p1_spaghetti_detection_rolling_mean_long
- number.bambu_lab_p1_spaghetti_detection_normalized_p
- number.bambu_lab_p1_spaghetti_detection_adjusted_ewm_mean
- number.bambu_lab_p1_spaghetti_detection_p_sum
- service: datetime.set_value
data:
datetime: '{{ now() }}'
target:
entity_id: datetime.bambu_lab_p1_spaghetti_detection_last_notify_time
@@ -0,0 +1,86 @@
blueprint:
name: Confirmable Notification
description: >-
A script that sends an actionable notification with a confirmation before
running the specified action.
domain: script
source_url: https://github.com/home-assistant/core/blob/master/homeassistant/components/script/blueprints/confirmable_notification.yaml
author: Home Assistant
input:
notify_device:
name: Device to notify
description: Device needs to run the official Home Assistant app to receive notifications.
selector:
device:
filter:
integration: mobile_app
title:
name: "Title"
description: "The title of the button shown in the notification."
default: ""
selector:
text:
message:
name: "Message"
description: "The message body"
selector:
text:
confirm_text:
name: "Confirmation Text"
description: "Text to show on the confirmation button"
default: "Confirm"
selector:
text:
confirm_action:
name: "Confirmation Action"
description: "Action to run when notification is confirmed"
default: []
selector:
action:
dismiss_text:
name: "Dismiss Text"
description: "Text to show on the dismiss button"
default: "Dismiss"
selector:
text:
dismiss_action:
name: "Dismiss Action"
description: "Action to run when notification is dismissed"
default: []
selector:
action:
mode: restart
sequence:
- alias: "Set up variables"
variables:
action_confirm: "{{ 'CONFIRM_' ~ context.id }}"
action_dismiss: "{{ 'DISMISS_' ~ context.id }}"
- alias: "Send notification"
domain: mobile_app
type: notify
device_id: !input notify_device
title: !input title
message: !input message
data:
actions:
- action: "{{ action_confirm }}"
title: !input confirm_text
- action: "{{ action_dismiss }}"
title: !input dismiss_text
- alias: "Awaiting response"
wait_for_trigger:
- platform: event
event_type: mobile_app_notification_action
event_data:
action: "{{ action_confirm }}"
- platform: event
event_type: mobile_app_notification_action
event_data:
action: "{{ action_dismiss }}"
- choose:
- conditions: "{{ wait.trigger.event.data.action == action_confirm }}"
sequence: !input confirm_action
- conditions: "{{ wait.trigger.event.data.action == action_dismiss }}"
sequence: !input dismiss_action
@@ -0,0 +1,27 @@
blueprint:
name: Invert a binary sensor
description: Creates a binary_sensor which holds the inverted value of a reference binary_sensor
domain: template
source_url: https://github.com/home-assistant/core/blob/dev/homeassistant/components/template/blueprints/inverted_binary_sensor.yaml
input:
reference_entity:
name: Binary sensor to be inverted
description: The binary_sensor which needs to have its value inverted
selector:
entity:
domain: binary_sensor
variables:
reference_entity: !input reference_entity
binary_sensor:
state: >
{% if states(reference_entity) == 'on' %}
off
{% elif states(reference_entity) == 'off' %}
on
{% else %}
{{ states(reference_entity) }}
{% endif %}
# delay_on: not_used in this example
# delay_off: not_used in this example
# auto_off: not_used in this example
availability: "{{ states(reference_entity) not in ('unknown', 'unavailable') }}"
+49
View File
@@ -0,0 +1,49 @@
homeassistant:
packages: !include_dir_named packages/
# Loads default set of integrations. Do not remove.
default_config:
# Load frontend themes from the themes folder
frontend:
themes: !include_dir_merge_named themes
lovelace:
dashboards: !include dashboards/dashboards.yaml
automation: !include_dir_merge_list automations/
scene: !include scenes.yaml
input_boolean: !include helpers/input_boolean.yaml
input_number: !include helpers/input_number.yaml
input_select: !include helpers/input_select.yaml
input_datetime: !include helpers/input_datetime.yaml
counter: !include helpers/counter.yaml
timer: !include helpers/timer.yaml
input_text: !include helpers/input_text.yaml
rest_command: !include helpers/rest_command.yaml
shell_command: !include helpers/shell_command.yaml
http:
use_x_frame_options: false
use_x_forwarded_for: true
trusted_proxies:
- 192.168.1.180
- 192.168.1.1
- 192.168.1.62
- 192.168.1.210
### SMTP ###
notify:
- name: "mailgun_smtp"
platform: smtp
server: "smtp.mailgun.org"
port: 587
timeout: 15
sender: "nukamail@vault983.com"
encryption: starttls
username: "nukamail@mail.vault983.com"
password: !secret email_password
recipient:
- "joshua@cnjmail.com"
- "polly@cnjmail.com"
sender_name: "Vault 983 Home Assistant"
+5
View File
@@ -0,0 +1,5 @@
mode: yaml
title: At-a-Glance
icon: mdi:eye-outline
show_in_sidebar: true
filename: dashboards/views/at-a-glance-dashboard.yaml
+3
View File
@@ -0,0 +1,3 @@
voice-alarms: !include voice-alarms.yaml
family-hub: !include family-hub.yaml
at-a-glance: !include at-a-glance.yaml
+5
View File
@@ -0,0 +1,5 @@
mode: yaml
title: Family Hub
icon: mdi:home-heart
show_in_sidebar: true
filename: dashboards/views/family-hub-dashboard.yaml
+193
View File
@@ -0,0 +1,193 @@
title: At-a-Glance
theme: dakboard-dark
views:
- title: At-a-Glance
path: at-a-glance
icon: mdi:eye-outline
theme: dakboard-dark
type: masonry
cards:
# ── Header ──
- type: markdown
content: |
# {{ now().strftime("%A, %B %d, %Y") }}
## {{ now().strftime("%I:%M %p") }}
card_mod:
style: |
ha-card {
text-align: center;
background: none;
box-shadow: none;
padding: 0 0 4px 0;
}
ha-card h1 {
font-size: 1.2em;
margin: 0;
font-weight: 300;
color: var(--secondary-text-color);
}
ha-card h2 {
font-size: 2em;
margin: 2px 0 0 0;
font-weight: 400;
}
# ── Weather Now ──
- type: weather-forecast
entity: weather.home
name: Now
show_forecast: false
show_current: true
round_temperature: true
# ── Indoor Climate ──
- type: heading
heading: Indoor Climate
heading_style: title
icon: mdi:home-thermometer-outline
- type: grid
columns: 3
square: false
cards:
- type: tile
entity: sensor.living_room_temperature
name: Living Room
icon: mdi:sofa
color: amber
- type: tile
entity: sensor.office_temperature
name: Office
icon: mdi:desk
color: teal
- type: tile
entity: sensor.bedroom_temperature
name: Master Bedroom
icon: mdi:bed
color: purple
- type: tile
entity: sensor.upstairs_temperature
name: Upstairs
icon: mdi:stairs
color: indigo
- type: tile
entity: sensor.blink_family_room_temperature
name: Family Room
icon: mdi:television
color: red
- type: tile
entity: sensor.blink_kitchen_temperature
name: Kitchen
icon: mdi:stove
color: deep-orange
# ── 3D Printer Status ──
- type: heading
heading: 3D Printers
heading_style: title
icon: mdi:printer-3d-nozzle
- type: grid
columns: 2
square: false
cards:
- type: tile
entity: binary_sensor.p1s_01p09c470102673_online
name: PrintsCharming
icon: mdi:printer-3d-nozzle
show_entity_picture: false
color: green
- type: tile
entity: binary_sensor.a1_03919d540806387_online
name: Ds-Turd-Flinger
icon: mdi:printer-3d-nozzle
show_entity_picture: false
color: green
- type: tile
entity: binary_sensor.a1mini_0309ca580403653_online
name: Polly-Pocket
icon: mdi:printer-3d-nozzle
show_entity_picture: false
color: green
- type: tile
entity: binary_sensor.constipation_orion_online
name: Constipation-Orion
icon: mdi:printer-3d-nozzle
show_entity_picture: false
color: green
- type: tile
entity: binary_sensor.jordyns_layer_slayer_online
name: Jordyn's Layer Slayer
icon: mdi:printer-3d-nozzle
show_entity_picture: false
color: green
- type: tile
entity: binary_sensor.centauri_carbon_sdcp_status
name: Centauri Carbon
icon: mdi:printer-3d-nozzle
show_entity_picture: false
color: green
# ── Litter Boxes ──
- type: heading
heading: Litter Boxes
heading_style: title
icon: mdi:cat
- type: grid
columns: 2
square: false
cards:
- type: tile
entity: binary_sensor.cat_crapper_3000_wastebin_filled
name: Cat Crapper 3000
icon: mdi:delete-empty
show_entity_picture: false
color: red
- type: tile
entity: binary_sensor.cat_crapper_3000_sand_lack
name: CC3000 Litter
icon: mdi:grain
show_entity_picture: false
color: amber
- type: tile
entity: binary_sensor.poop_box_wastebin_filled
name: Poop Box
icon: mdi:delete-empty
show_entity_picture: false
color: red
- type: tile
entity: binary_sensor.triple_threat_wastebin_filled
name: Triple Threat
icon: mdi:delete-empty
show_entity_picture: false
color: red
# ── Security ──
- type: heading
heading: Security
heading_style: title
icon: mdi:shield-home-outline
badges:
- type: entity
entity: alarm_control_panel.blink_front
name: Front
- type: entity
entity: alarm_control_panel.blink_inside
name: Inside
- type: entity
entity: alarm_control_panel.blink_garage
name: Garage
- type: entity
entity: alarm_control_panel.blink_rear
name: Rear
# ── Recent Activity ──
- type: logbook
entities:
- automation.announce_day_camp_preparation_and_pause_tv
- automation.announce_time_to_leave_for_day_camp_and_turn_off_tv
- automation.bedtime_snack_notification
- automation.chore_tracker_evening_reminder
hours_to_show: 24
title: Recent Family Activity
+173
View File
@@ -0,0 +1,173 @@
title: Family Hub
theme: dakboard-dark
views:
- title: Family Hub
path: family-hub
icon: mdi:home-heart
theme: dakboard-dark
type: masonry
cards:
# ── Date & Greeting Header ──
- type: markdown
content: |
# {{ now().strftime("%A, %B %d") }}
{% set h = now().hour %}
{% if h < 12 %}Good morning{% elif h < 17 %}Good afternoon{% else %}Good evening{% endif %}, family!
card_mod:
style: |
ha-card {
text-align: center;
background: none;
box-shadow: none;
padding: 0 0 8px 0;
}
ha-card h1 {
font-size: 1.4em;
margin: 0 0 4px 0;
font-weight: 300;
color: var(--secondary-text-color);
}
ha-card p {
font-size: 1.1em;
margin: 0;
font-weight: 400;
}
entity_id:
- sensor.time
# ── Clock ──
- type: clock
clock_size: large
clock_style: analog
show_seconds: true
no_background: true
analog_options:
border: false
ticks: hour
card_mod:
style: |
ha-card {
background: none;
box-shadow: none;
text-align: center;
}
# ── Weather ──
- type: weather-forecast
entity: weather.home
name: Weather
show_forecast: true
forecast_type: daily
secondary_info_attribute: extrema
round_temperature: false
# ── Family Calendar ──
- type: calendar
title: Upcoming Events
initial_view: listWeek
entities:
- calendar.family_events
- calendar.birthdays
- calendar.holidays_in_united_states
- calendar.gmail_calendar
card_mod:
style: |
ha-card {
--calendar-day-color: var(--primary-text-color);
--calendar-day-weekday-color: var(--secondary-text-color);
}
# ── Quick Glance ──
- type: heading
heading: Quick Status
heading_style: title
icon: mdi:home-lightning-bolt-outline
badges:
- type: entity
entity: sensor.openweathermap_temperature
name: Outside
- type: entity
entity: sensor.living_room_temperature
name: Living Room
- type: entity
entity: sensor.office_temperature
name: Office
- type: grid
columns: 3
square: false
cards:
# Outdoor temp
- type: tile
entity: sensor.openweathermap_temperature
name: Outside
icon: mdi:thermometer
show_entity_picture: false
color: blue
# Living Room
- type: tile
entity: sensor.living_room_temperature
name: Living Room
icon: mdi:sofa
show_entity_picture: false
color: amber
# Office
- type: tile
entity: sensor.office_temperature
name: Office
icon: mdi:desk
show_entity_picture: false
color: teal
# Master Bedroom
- type: tile
entity: sensor.bedroom_temperature
name: Master Bedroom
icon: mdi:bed
show_entity_picture: false
color: purple
# Upstairs
- type: tile
entity: sensor.upstairs_temperature
name: Upstairs
icon: mdi:stairs
show_entity_picture: false
color: indigo
# Family Room (Blink)
- type: tile
entity: sensor.blink_family_room_temperature
name: Family Room
icon: mdi:television
show_entity_picture: false
color: red
# ── Shopping List ──
- type: todo-list
entity: todo.shopping_list
title: Shopping List
hide_completed: true
display_order: none
# ── Chore Tracker Quick View ──
- type: entities
title: Chores Today
show_header_toggle: false
entities:
- entity: binary_sensor.cat_medication_tracker_all_cats_medicated
name: Cats medicated
- entity: binary_sensor.cat_medication_tracker_penelope_medication_status
name: Penelope meds
- entity: binary_sensor.cat_medication_tracker_tess_medication_status
name: Tess meds
# ── Family Presence ──
- type: heading
heading: Presence
heading_style: title
icon: mdi:account-group
badges:
- type: entity
entity: binary_sensor.bedroom_occupancy
name: Bedroom
- type: entity
entity: binary_sensor.upstairs_occupancy
name: Upstairs
@@ -0,0 +1,71 @@
title: Voice Alarms
views:
- title: Voice Alarms
path: voice-alarms
icon: mdi:alarm-panel
type: panel
cards:
- type: vertical-stack
cards:
- type: markdown
content: |
# Voice Alarms
These alarms are managed by Home Assistant and announced on the selected
Jarvis and ReSpeaker satellites.
- type: grid
columns: 2
square: false
cards:
- type: entities
title: Office Jarvis
show_header_toggle: false
entities:
- entity: assist_satellite.voice_assistant_1_assist_satellite
name: Satellite
- entity: input_boolean.voice_alarm_office_jarvis_enabled
name: Alarm enabled
- entity: input_datetime.voice_alarm_office_jarvis_time
name: Alarm time
- entity: script.test_voice_alarm_office_jarvis
name: Test alarm
- type: entities
title: ReSpeaker Family Room
show_header_toggle: false
entities:
- entity: assist_satellite.respeaker_lite_satellite_assist_satellite
name: Satellite
- entity: input_boolean.voice_alarm_respeaker_family_room_enabled
name: Alarm enabled
- entity: input_datetime.voice_alarm_respeaker_family_room_time
name: Alarm time
- entity: script.test_voice_alarm_respeaker_family_room
name: Test alarm
- type: entities
title: ReSpeaker Living Room
show_header_toggle: false
entities:
- entity: assist_satellite.respeaker_satellite_living_room_assist_satellite
name: Satellite
- entity: input_boolean.voice_alarm_respeaker_living_room_enabled
name: Alarm enabled
- entity: input_datetime.voice_alarm_respeaker_living_room_time
name: Alarm time
- entity: script.test_voice_alarm_respeaker_living_room
name: Test alarm
- type: entities
title: ReSpeaker Master Bedroom
show_header_toggle: false
entities:
- entity: assist_satellite.respeaker_satellite_master_bedroom_assist_satellite
name: Satellite
- entity: input_boolean.voice_alarm_respeaker_master_bedroom_enabled
name: Alarm enabled
- entity: input_datetime.voice_alarm_respeaker_master_bedroom_time
name: Alarm time
- entity: script.test_voice_alarm_respeaker_master_bedroom
name: Test alarm
+5
View File
@@ -0,0 +1,5 @@
mode: yaml
title: Voice Alarms
icon: mdi:alarm-panel
show_in_sidebar: true
filename: dashboards/views/voice-alarms-dashboard.yaml
+14
View File
@@ -0,0 +1,14 @@
{
"dashboard_view": "cards",
"theme": "system",
"navigator_visible": true,
"device_editor_layout": "both",
"secrets_editor_layout": "visual",
"table_page_size": 25,
"table_column_visibility": {},
"table_sort_column": null,
"table_sort_direction": null,
"experience_level": "expert",
"remote_compute_only": false,
"onboarding_completed_version": 0
}
+485
View File
@@ -0,0 +1,485 @@
{
"_board_id_user_set_migrated": true,
"airqualitysensor-3.yaml": {
"mac_address": "1C:DB:D4:5A:D6:38"
},
"_dashboard_identity": {
"dashboard_id": "tEjFIsUFc3OUB8auNqhhKLKr6NoX9POx"
},
"master_bedroom_remote.yaml": {
"mac_address": "B0:CB:D8:D6:CB:2C"
},
"esphome-web-0fdcf4.yaml": {
"mac_address": "A0:85:E3:0F:DC:F4"
},
"_firmware_jobs": [
{
"job_id": "454c2dcf9ffc",
"configuration": "airqualitysensor-2.yaml",
"job_type": "upload",
"status": "failed",
"created_at": "2026-06-23T15:59:03.820028+00:00",
"started_at": "2026-06-23T15:59:03.820176+00:00",
"completed_at": "2026-06-23T15:59:24.360620+00:00",
"exit_code": 1,
"error": null,
"port": "OTA",
"new_name": "",
"depends_on": "",
"dependency_released": false,
"progress": null,
"remote_peer": "",
"remote_job_id": "",
"remote_peer_label": "",
"device_name": "",
"device_friendly_name": "",
"source": "local",
"source_pin_sha256": "",
"source_label": "",
"source_esphome_version": ""
},
{
"job_id": "b0b33805fea9",
"configuration": "airqualitysensor-2.yaml",
"job_type": "compile",
"status": "completed",
"created_at": "2026-06-23T15:58:51.081966+00:00",
"started_at": "2026-06-23T15:58:51.082171+00:00",
"completed_at": "2026-06-23T15:59:03.816937+00:00",
"exit_code": 0,
"error": null,
"port": "",
"new_name": "",
"depends_on": "",
"dependency_released": false,
"progress": null,
"remote_peer": "",
"remote_job_id": "",
"remote_peer_label": "",
"device_name": "",
"device_friendly_name": "",
"source": "local",
"source_pin_sha256": "",
"source_label": "",
"source_esphome_version": ""
},
{
"job_id": "a3b449b55379",
"configuration": "airqualitysensor-1.yaml",
"job_type": "upload",
"status": "failed",
"created_at": "2026-06-23T12:12:15.609235+00:00",
"started_at": "2026-06-23T12:12:15.609406+00:00",
"completed_at": "2026-06-23T12:12:36.129802+00:00",
"exit_code": 1,
"error": null,
"port": "OTA",
"new_name": "",
"depends_on": "",
"dependency_released": false,
"progress": null,
"remote_peer": "",
"remote_job_id": "",
"remote_peer_label": "",
"device_name": "",
"device_friendly_name": "",
"source": "local",
"source_pin_sha256": "",
"source_label": "",
"source_esphome_version": ""
},
{
"job_id": "959592b15d5d",
"configuration": "airqualitysensor-1.yaml",
"job_type": "compile",
"status": "completed",
"created_at": "2026-06-23T12:12:02.678379+00:00",
"started_at": "2026-06-23T12:12:02.678577+00:00",
"completed_at": "2026-06-23T12:12:15.596683+00:00",
"exit_code": 0,
"error": null,
"port": "",
"new_name": "",
"depends_on": "",
"dependency_released": false,
"progress": null,
"remote_peer": "",
"remote_job_id": "",
"remote_peer_label": "",
"device_name": "",
"device_friendly_name": "",
"source": "local",
"source_pin_sha256": "",
"source_label": "",
"source_esphome_version": ""
},
{
"job_id": "f210d3e38469",
"configuration": "home-energy-monitor.yaml",
"job_type": "upload",
"status": "completed",
"created_at": "2026-06-23T12:10:06.335084+00:00",
"started_at": "2026-06-23T12:10:06.335247+00:00",
"completed_at": "2026-06-23T12:10:16.319195+00:00",
"exit_code": 0,
"error": null,
"port": "OTA",
"new_name": "",
"depends_on": "",
"dependency_released": false,
"progress": 100,
"remote_peer": "",
"remote_job_id": "",
"remote_peer_label": "",
"device_name": "",
"device_friendly_name": "",
"source": "local",
"source_pin_sha256": "",
"source_label": "",
"source_esphome_version": ""
},
{
"job_id": "e7f4e049b845",
"configuration": "home-energy-monitor.yaml",
"job_type": "compile",
"status": "completed",
"created_at": "2026-06-23T12:08:13.748861+00:00",
"started_at": "2026-06-23T12:08:13.749101+00:00",
"completed_at": "2026-06-23T12:10:06.331091+00:00",
"exit_code": 0,
"error": null,
"port": "",
"new_name": "",
"depends_on": "",
"dependency_released": false,
"progress": null,
"remote_peer": "",
"remote_job_id": "",
"remote_peer_label": "",
"device_name": "",
"device_friendly_name": "",
"source": "local",
"source_pin_sha256": "",
"source_label": "",
"source_esphome_version": ""
},
{
"job_id": "49018100eb10",
"configuration": "esphome-web-0fdcf4.yaml",
"job_type": "upload",
"status": "completed",
"created_at": "2026-06-22T07:42:25.069069+00:00",
"started_at": "2026-06-22T07:42:25.069205+00:00",
"completed_at": "2026-06-22T07:42:35.367662+00:00",
"exit_code": 0,
"error": null,
"port": "OTA",
"new_name": "",
"depends_on": "",
"dependency_released": false,
"progress": 100,
"remote_peer": "",
"remote_job_id": "",
"remote_peer_label": "",
"device_name": "",
"device_friendly_name": "",
"source": "local",
"source_pin_sha256": "",
"source_label": "",
"source_esphome_version": ""
},
{
"job_id": "b018ef39d4ae",
"configuration": "esphome-web-0fdcf4.yaml",
"job_type": "compile",
"status": "completed",
"created_at": "2026-06-22T07:40:28.776647+00:00",
"started_at": "2026-06-22T07:40:28.776800+00:00",
"completed_at": "2026-06-22T07:42:25.066345+00:00",
"exit_code": 0,
"error": null,
"port": "",
"new_name": "",
"depends_on": "",
"dependency_released": false,
"progress": null,
"remote_peer": "",
"remote_job_id": "",
"remote_peer_label": "",
"device_name": "",
"device_friendly_name": "",
"source": "local",
"source_pin_sha256": "",
"source_label": "",
"source_esphome_version": ""
},
{
"job_id": "a6fe4e12b390",
"configuration": "esp32-h2c-camera.yaml",
"job_type": "upload",
"status": "completed",
"created_at": "2026-06-22T07:38:05.100640+00:00",
"started_at": "2026-06-22T07:38:05.100779+00:00",
"completed_at": "2026-06-22T07:38:20.875311+00:00",
"exit_code": 0,
"error": null,
"port": "OTA",
"new_name": "",
"depends_on": "",
"dependency_released": false,
"progress": 100,
"remote_peer": "",
"remote_job_id": "",
"remote_peer_label": "",
"device_name": "",
"device_friendly_name": "",
"source": "local",
"source_pin_sha256": "",
"source_label": "",
"source_esphome_version": ""
},
{
"job_id": "89e9be54a789",
"configuration": "esp32-h2c-camera.yaml",
"job_type": "compile",
"status": "completed",
"created_at": "2026-06-22T07:36:19.527708+00:00",
"started_at": "2026-06-22T07:36:19.527864+00:00",
"completed_at": "2026-06-22T07:38:05.097814+00:00",
"exit_code": 0,
"error": null,
"port": "",
"new_name": "",
"depends_on": "",
"dependency_released": false,
"progress": null,
"remote_peer": "",
"remote_job_id": "",
"remote_peer_label": "",
"device_name": "",
"device_friendly_name": "",
"source": "local",
"source_pin_sha256": "",
"source_label": "",
"source_esphome_version": ""
},
{
"job_id": "6375c915454b",
"configuration": "master_bedroom_remote.yaml",
"job_type": "upload",
"status": "completed",
"created_at": "2026-06-22T07:34:04.950629+00:00",
"started_at": "2026-06-22T07:34:04.950767+00:00",
"completed_at": "2026-06-22T07:34:15.205602+00:00",
"exit_code": 0,
"error": null,
"port": "OTA",
"new_name": "",
"depends_on": "",
"dependency_released": false,
"progress": 100,
"remote_peer": "",
"remote_job_id": "",
"remote_peer_label": "",
"device_name": "",
"device_friendly_name": "",
"source": "local",
"source_pin_sha256": "",
"source_label": "",
"source_esphome_version": ""
},
{
"job_id": "b78f643229b3",
"configuration": "master_bedroom_remote.yaml",
"job_type": "compile",
"status": "completed",
"created_at": "2026-06-22T07:32:02.047324+00:00",
"started_at": "2026-06-22T07:32:02.047531+00:00",
"completed_at": "2026-06-22T07:34:04.947621+00:00",
"exit_code": 0,
"error": null,
"port": "",
"new_name": "",
"depends_on": "",
"dependency_released": false,
"progress": null,
"remote_peer": "",
"remote_job_id": "",
"remote_peer_label": "",
"device_name": "",
"device_friendly_name": "",
"source": "local",
"source_pin_sha256": "",
"source_label": "",
"source_esphome_version": ""
},
{
"job_id": "2ce572f89e47",
"configuration": "laundry-litter-box-camera.yaml",
"job_type": "upload",
"status": "completed",
"created_at": "2026-06-22T07:29:16.588267+00:00",
"started_at": "2026-06-22T07:29:16.588413+00:00",
"completed_at": "2026-06-22T07:29:56.324221+00:00",
"exit_code": 0,
"error": null,
"port": "OTA",
"new_name": "",
"depends_on": "",
"dependency_released": false,
"progress": 100,
"remote_peer": "",
"remote_job_id": "",
"remote_peer_label": "",
"device_name": "",
"device_friendly_name": "",
"source": "local",
"source_pin_sha256": "",
"source_label": "",
"source_esphome_version": ""
},
{
"job_id": "e416079bc9ee",
"configuration": "laundry-litter-box-camera.yaml",
"job_type": "compile",
"status": "completed",
"created_at": "2026-06-22T07:27:37.929185+00:00",
"started_at": "2026-06-22T07:27:37.929410+00:00",
"completed_at": "2026-06-22T07:29:16.583896+00:00",
"exit_code": 0,
"error": null,
"port": "",
"new_name": "",
"depends_on": "",
"dependency_released": false,
"progress": null,
"remote_peer": "",
"remote_job_id": "",
"remote_peer_label": "",
"device_name": "",
"device_friendly_name": "",
"source": "local",
"source_pin_sha256": "",
"source_label": "",
"source_esphome_version": ""
},
{
"job_id": "e87fc4673ed1",
"configuration": "airqualitysensor-3.yaml",
"job_type": "upload",
"status": "completed",
"created_at": "2026-06-22T07:06:34.226565+00:00",
"started_at": "2026-06-22T07:06:34.226708+00:00",
"completed_at": "2026-06-22T07:06:45.682261+00:00",
"exit_code": 0,
"error": null,
"port": "OTA",
"new_name": "",
"depends_on": "",
"dependency_released": false,
"progress": 100,
"remote_peer": "",
"remote_job_id": "",
"remote_peer_label": "",
"device_name": "",
"device_friendly_name": "",
"source": "local",
"source_pin_sha256": "",
"source_label": "",
"source_esphome_version": ""
},
{
"job_id": "564fcb99c4f1",
"configuration": "airqualitysensor-3.yaml",
"job_type": "compile",
"status": "completed",
"created_at": "2026-06-22T07:04:34.798415+00:00",
"started_at": "2026-06-22T07:04:34.798571+00:00",
"completed_at": "2026-06-22T07:06:34.223520+00:00",
"exit_code": 0,
"error": null,
"port": "",
"new_name": "",
"depends_on": "",
"dependency_released": false,
"progress": null,
"remote_peer": "",
"remote_job_id": "",
"remote_peer_label": "",
"device_name": "",
"device_friendly_name": "",
"source": "local",
"source_pin_sha256": "",
"source_label": "",
"source_esphome_version": ""
},
{
"job_id": "49799549d47e",
"configuration": "cat-medication-tracker.yaml",
"job_type": "upload",
"status": "completed",
"created_at": "2026-06-22T07:02:21.611975+00:00",
"started_at": "2026-06-22T07:02:21.612142+00:00",
"completed_at": "2026-06-22T07:02:30.511802+00:00",
"exit_code": 0,
"error": null,
"port": "OTA",
"new_name": "",
"depends_on": "",
"dependency_released": false,
"progress": 100,
"remote_peer": "",
"remote_job_id": "",
"remote_peer_label": "",
"device_name": "",
"device_friendly_name": "",
"source": "local",
"source_pin_sha256": "",
"source_label": "",
"source_esphome_version": ""
},
{
"job_id": "a46e164b02dd",
"configuration": "cat-medication-tracker.yaml",
"job_type": "compile",
"status": "completed",
"created_at": "2026-06-22T07:00:00.362587+00:00",
"started_at": "2026-06-22T07:00:00.362775+00:00",
"completed_at": "2026-06-22T07:02:21.608428+00:00",
"exit_code": 0,
"error": null,
"port": "",
"new_name": "",
"depends_on": "",
"dependency_released": false,
"progress": null,
"remote_peer": "",
"remote_job_id": "",
"remote_peer_label": "",
"device_name": "",
"device_friendly_name": "",
"source": "local",
"source_pin_sha256": "",
"source_label": "",
"source_esphome_version": ""
}
],
"attic-climate-sensor.yaml": {
"mac_address": "CC:8D:A2:C0:70:C4"
},
"cat-medication-tracker.yaml": {
"mac_address": "B0:CB:D8:03:5E:14"
},
"declan-a1-camera.yaml": {
"mac_address": "D4:E9:F4:A3:59:E8"
},
"esp32-h2c-camera.yaml": {
"mac_address": "E8:F6:0A:87:12:B0"
},
"laundry-litter-box-camera.yaml": {
"mac_address": "D4:E9:F4:A3:59:E8"
},
"home-energy-monitor.yaml": {
"mac_address": "8C:4F:00:AD:42:00"
}
}
View File
+5
View File
@@ -0,0 +1,5 @@
# Gitignore settings for ESPHome
# This is an example and may include too much for your use-case.
# You can modify this file to suit your needs.
/.esphome/
/secrets.yaml
+47
View 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);
}
};
+824
View File
@@ -0,0 +1,824 @@
substitutions: #substitute your own values in this section
internal_temp_sensor: sensor.meat_heater_current_temperature #entity from Home Assistant
outside_temp_sensor: sensor.home_realfeel_temperature #entity from Home Assistant
weather_entity: weather.home #entity from Home Assistant
todays_forecast_high_entity: sensor.home_realfeel_temperature_max_day_0 #entity from Home Assistant
todays_forecast_low_entity: sensor.home_realfeel_temperature_min_day_0 #entity from Home Assistant
device_name: airqualitysensor-basement
friendly_name: "Air Sensor - Basement"
esphome:
name: ${device_name}
friendly_name: ${friendly_name}
on_boot:
priority: -100
then:
- light.turn_on:
id: led
brightness: 0.4
effect: "Rainbow Effect"
esp32:
board: esp32-s3-devkitc-1
cpu_frequency: 240MHz
variant: esp32s3
flash_size: 16MB
framework:
type: esp-idf
# Enable logging
logger:
level: INFO
# Enable Home Assistant API
api:
encryption:
key: "gnQzjc98wQKUP3qX6+FeU9JchMPtjJwlKQejOB/mQDU="
ota:
- platform: esphome
password: "d4d36e1c98d4b7e2069295540a20a1aa"
wifi:
ssid: !secret wifi_iot_ssid
password: !secret wifi_password
# Prevents aggressive reconnection attempts
power_save_mode: none # Use 'light' for battery devices
# Slower but more reliable connection
fast_connect: true
# Handle connection failures gracefully
on_connect:
- logger.log: "Wi-Fi connected!"
on_disconnect:
- logger.log: "Wi-Fi disconnected!"
# Enable fallback hotspot (captive portal) in case wifi connection fails
ap:
ssid: "Airqualitysensor-1"
password: "MpBryi70smN4"
# Reduce mDNS traffic
mdns:
disabled: false # Set to true if using static IPs and don't need discovery
# Watchdog to auto-reboot if things go wrong
interval:
- interval: 5min
then:
- if:
condition:
and:
- lambda: 'return millis() > 300000;' # >5 min uptime
- not:
wifi.connected:
then:
- logger.log: "Wi-Fi not connected for 5 min, rebooting..."
- delay: 10s
- lambda: 'App.safe_reboot();'
captive_portal:
web_server:
port: 80
script:
# SCRIPT 1: Runs when the mode is changed from the select
- id: update_led_state
then:
- lambda: |-
if (!id(iaq_reading).has_state() || id(iaq_reading).state == "Booting...") {
return; // Exit script, leave rainbow alone
}
std::string quality = id(iaq_reading).state;
float lux = id(ambient_light).state;
std::string mode = id(led_mode).state;
float brightness = 0.15;
float r = 0.0, g = 0.0, b = 0.0;
std::string selected_effect = "None";
// --- 1. Determine desired color ---
if (quality == "Excellent") { r = 0.0; g = 1.0; b = 0.0; }
else if (quality == "Good") { r = 0.3; g = 0.6; b = 0.3; }
else if (quality == "Moderate") { r = 1.0; g = 0.85; b = 0.38; }
else if (quality == "Poor") { r = 1.0; g = 0.0; b = 0.0; }
else if (quality == "Unhealthy") {
r = 1.0; g = 0.0; b = 1.0;
selected_effect = "Alert Flash"; // Alert overrides mode
}
// --- 2. Determine desired effect based on mode (if not alerting) ---
if (selected_effect != "Alert Flash") {
if (mode == "Breathing") {
selected_effect = "Breathing";
} else if (mode == "Scanner") {
selected_effect = "Scanner";
}
}
// --- 3. Apply changes ---
auto call = id(led).turn_on();
call.set_rgb(r, g, b); // Always set color
if (selected_effect == "None") {
// We want a solid light (Auto/Manual)
if (mode == "Auto") {
if (isnan(lux)) lux = 0;
brightness = 0.15 + (lux / 200.0) * (0.7 - 0.15);
if (brightness > 0.7) brightness = 0.7;
if (brightness < 0.15) brightness = 0.15;
} else { // Manual
brightness = id(iaq_led_brightness).state;
}
call.set_brightness(brightness);
}
// This script *always* sets the effect
call.set_effect(selected_effect);
call.perform();
# SCRIPT 2: Runs when IAQ *value* changes (every 10s)
- id: update_led_color
then:
- lambda: |-
if (!id(iaq_reading).has_state() || id(iaq_reading).state == "Booting...") {
return; // Exit script, leave rainbow alone
}
std::string quality = id(iaq_reading).state;
std::string mode = id(led_mode).state;
float lux = id(ambient_light).state;
float r = 0.0, g = 0.0, b = 0.0;
// --- 1. Determine desired color ---
if (quality == "Excellent") { r = 0.0; g = 1.0; b = 0.0; }
else if (quality == "Good") { r = 0.3; g = 0.6; b = 0.3; }
else if (quality == "Moderate") { r = 1.0; g = 0.85; b = 0.38; }
else if (quality == "Poor") { r = 1.0; g = 0.0; b = 0.0; }
else if (quality == "Unhealthy") {
r = 1.0; g = 0.0; b = 1.0;
// Force Alert Flash and EXIT
id(led).turn_on().set_rgb(r, g, b).set_effect("Alert Flash").perform();
return;
}
// --- 2. If we are here, AQ is OK. Reset Effect! ---
auto call = id(led).turn_on();
call.set_rgb(r, g, b);
// Determine which effect to restore based on the Select Mode
std::string restore_effect = "None";
if (mode == "Breathing") restore_effect = "Breathing";
else if (mode == "Scanner") restore_effect = "Scanner";
// Explicitly set the effect (This stops the Alert Flash)
call.set_effect(restore_effect);
// --- 3. Apply Brightness ---
if (mode == "Auto") {
float brightness = 0.15;
if (isnan(lux)) lux = 0;
brightness = 0.15 + (lux / 200.0) * (0.7 - 0.15);
if (brightness > 0.7) brightness = 0.7;
if (brightness < 0.15) brightness = 0.15;
call.set_brightness(brightness);
} else if (mode == "Manual") {
call.set_brightness(id(iaq_led_brightness).state);
}
call.perform();
globals:
- id: iaq_index
type: int
restore_value: no
initial_value: '0'
- id: computed_brightness
type: float
restore_value: no
initial_value: '0.15'
uart:
tx_pin: GPIO17
rx_pin: GPIO18
baud_rate: 9600
light:
- platform: esp32_rmt_led_strip
rgb_order: GRB
chipset: WS2812
pin: GPIO16
num_leds: 5
name: "LED"
id: led
icon: mdi:led-on
default_transition_length: 0s
disabled_by_default: False
effects:
- pulse:
name: "Breathing"
min_brightness: 0.15
max_brightness: 0.45
transition_length: 3000ms
update_interval: 3000ms
- addressable_scan:
name: "Scanner"
scan_width: 2
move_interval: 100ms
- flicker:
name: "Alert Flash"
alpha: 95%
intensity: 1.5%
- addressable_rainbow:
name: "Rainbow Effect"
speed: 10
width: 50
i2c:
sda: GPIO8
scl: GPIO9
scan: true
id: bus_a
frequency: 100kHz
switch:
- platform: template
name: "OLED Power"
id: oled_power
optimistic: true
restore_mode: RESTORE_DEFAULT_ON
turn_on_action:
- logger.log: "OLED turned ON"
turn_off_action:
- logger.log: "OLED turned OFF"
select:
- platform: template
name: "LED Mode"
id: led_mode
options:
- "Auto"
- "Manual"
- "Breathing"
- "Scanner"
optimistic: true
restore_value: true
on_value:
then:
- script.execute: update_led_state
number:
- platform: template
name: "IAQ LED Brightness"
id: iaq_led_brightness
min_value: 0.15
max_value: 1.0
step: 0.01
optimistic: true
restore_value: true
sensor:
- platform: uptime
name: Uptime Sensor
- platform: homeassistant
id: inside_temperature
entity_id: $internal_temp_sensor
internal: true
- platform: homeassistant
id: outside_temperature
entity_id: $outside_temp_sensor
internal: true
- platform: homeassistant
id: todays_forecast_high
entity_id: $todays_forecast_high_entity
internal: true
- platform: homeassistant
id: todays_forecast_low
entity_id: $todays_forecast_low_entity
internal: true
- platform: pmsx003
type: PMSX003
update_interval: 60s
pm_1_0:
id: pm1
name: "PM <1.0µm Concentration"
pm_2_5:
id: pm25
name: "PM <2.5µm Concentration"
pm_10_0:
id: pm10
name: "PM <10.0µm Concentration"
- platform: bme680
temperature:
name: "BME680 Temperature"
id: bmetemp
oversampling: 16x
pressure:
name: "BME680 Pressure"
id: bmepressure
humidity:
name: "BME680 Humidity"
id: bmehum
gas_resistance:
name: "BME680 Gas Resistance"
id: bmegas
address: 0x77
update_interval: 60s
## CO²/VOC Sensor
- platform: ccs811
eco2:
name: "CCS811 CO²"
accuracy_decimals: 0
id: eco2
tvoc:
name: "CCS811 T-VOC"
accuracy_decimals: 0
id: tvoc
address: 0x5A
update_interval: 60s
temperature: bmetemp
humidity: bmehum
## After Calibration, Uncomment and change "baseline:"
baseline: 0x9CB1
- platform: template
name: "Humidity Sensor"
id: humi
unit_of_measurement: "%"
accuracy_decimals: 1
lambda: |-
return id(bmehum).state;
update_interval: 60s
- platform: wifi_signal
name: AQ WiFi Signal
update_interval: 60s
internal: true
- platform: veml7700
address: 0x10
update_interval: 60s
# short variant of sensor definition:
ambient_light:
name: "Ambient Light"
id: ambient_light
filters:
- sliding_window_moving_average:
window_size: 5
send_every: 1
- platform: wifi_signal
name: "${device_name} WiFi Signal"
update_interval: 60s
- platform: uptime
name: "${device_name} Uptime"
update_interval: 60s
font:
- file: "fonts/Roboto-Regular.ttf"
id: robotto
size: 10
- file: "fonts/Roboto-Regular.ttf"
id: font2
size: 12
- file: "fonts/Poppins-Regular.ttf"
id: font1
size: 10
- file: "fonts/Poppins-Regular.ttf"
id: poppinslarger
size: 12
- file: "fonts/Poppins-SemiBold.ttf"
id: poppinsbold
size: 10
- file: 'fonts/materialdesignicons-webfont.ttf'
id: font3
size: 18
glyphs:
- "\U000F13D5" #mdi:home-minus-outline
- file: 'fonts/materialdesignicons-webfont.ttf'
id: font4
size: 40
glyphs:
- "\U000F0594" #"clear-night"
- "\U000F0590" #"cloudy"
- "\U000F0591" #"fog"
- "\U000F0592" #"hail"
- "\U000F0593" #"lightning"
- "\U000F067E" #"lightning-rainy"
- "\U000F0595" #"partlycloudy"
- "\U000F0596" #"pouring"
- "\U000F0597" #"rainy"
- "\U000F0598" #"snowy"
- "\U000F067F" #"snowy-rainy"
- "\U000F0599" #"sunny"
- "\U000F059D" #"windy"
- "\U000F059E" #"windy-variant"
display:
- platform: ssd1306_i2c
model: "SSD1306 128x64"
address: 0x3C
id: oled_display
rotation: 0
i2c_id: bus_a
pages:
- id: page_air_quality
lambda: |-
if (id(oled_power).state) { // only draw if display is ON
{
// Combine the final string first
char full_text[64];
if (id(iaq_reading).has_state()) {
sprintf(full_text, "Air Quality: %s", id(iaq_reading).state.c_str());
} else {
sprintf(full_text, "Air Quality: Booting...");
}
int x, y, w, h;
it.get_text_bounds(0, 0, full_text, id(poppinsbold), TextAlign::TOP_LEFT, &x, &y, &w, &h);
int center_x = (128 - w) / 2; // OLED is 128px wide
it.printf(center_x, 0, id(poppinsbold), "%s", full_text);
}
// --- Temp + Humidity on same line ---
it.printf(0, 16, id(font1), "TEMP: %.1f°C", id(bmetemp).state);
it.printf(70, 16, id(font1), "HUM: %.0f%%", id(bmehum).state);
// --- Other sensor lines ---
it.printf(0, 26, id(font1), "CO2: %.0f ppm", id(eco2).state);
it.printf(0, 36, id(font1), "TVOC: %.0f ppb", id(tvoc).state);
if (!isnan(id(pm25).state)) {
it.printf(0, 46, id(font1), "PM2.5: %.0f", id(pm25).state);
} else {
it.printf(0, 46, id(font1), "PM2.5: ---");
}
}
- id: page_environment
lambda: |-
if (id(oled_power).state) { // only draw if display is ON
{
// Combine the final string first
char full_text[64];
if (id(iaq_reading).has_state()) {
sprintf(full_text, "Air Quality: %s", id(iaq_reading).state.c_str());
} else {
sprintf(full_text, "Air Quality: Booting...");
}
int x, y, w, h;
it.get_text_bounds(0, 0, full_text, id(poppinsbold), TextAlign::TOP_LEFT, &x, &y, &w, &h);
int center_x = (128 - w) / 2; // OLED is 128px wide
it.printf(center_x, 0, id(poppinsbold), "%s", full_text);
}
// --- Temp + Humidity on same line ---
it.printf(0, 16, id(font1), "TEMP: %.1f°C", id(bmetemp).state);
it.printf(70, 16, id(font1), "HUM: %.0f%%", id(bmehum).state);
// --- Other sensor lines ---
it.printf(0, 26, id(font1), "CO2: %.0f ppm", id(ambient_light).state);
}
- id: page_weather
lambda: |-
if (id(weather_state).has_state()) {
std::map<std::string, std::string> weather_icon_map
{
{"clear-night", "\U000F0594"},
{"cloudy", "\U000F0590"},
{"fog", "\U000F0591"},
{"hail", "\U000F0592"},
{"lightning", "\U000F0593"},
{"lightning-rainy", "\U000F067E"},
{"partlycloudy", "\U000F0595"},
{"pouring", "\U000F0596"},
{"rainy", "\U000F0597"},
{"snowy", "\U000F0598"},
{"snowy-rainy", "\U000F067F"},
{"sunny", "\U000F0599"},
{"windy", "\U000F059D"},
{"windy-variant", "\U000F059E"},
};
it.printf(0, it.get_height(), id(font4), TextAlign::BASELINE_LEFT, weather_icon_map[id(weather_state).state.c_str()].c_str());
}
// Print time in HH:MM format
it.strftime(0, 0, id(font1), TextAlign::TOP_LEFT, "%H:%M %a", id(homeassistant_time).now());
//Print day of week
//it.strftime(40, 0, id(font1), TextAlign::TOP_LEFT, "%a", id(homeassistant_time).now());
it.line(0, 20, it.get_width(), 20);
//Print home icon
//it.printf(70, 0, id(font3), "\U000F13D5");
// Print inside temperature (from homeassistant sensor)
if (id(inside_temperature).has_state()) {
it.printf(it.get_width(), 0, id(font1), TextAlign::TOP_RIGHT , "%7.1f°", id(inside_temperature).state);
}
// Print outside temperature (from homeassistant sensor)
if (id(outside_temperature).has_state()) {
it.printf(42, 32, id(font2), "%.1f°", id(outside_temperature).state);
}
// Print Forecast High
if (id(todays_forecast_high).has_state()) {
it.printf(it.get_width(), 32, id(font1), TextAlign::TOP_RIGHT, "%7.1f°", id(todays_forecast_high).state);
}
// Print Forecast Low
if (id(todays_forecast_low).has_state()) {
it.printf(it.get_width(), 48, id(font1), TextAlign::TOP_RIGHT, "%7.1f°", id(todays_forecast_low).state);
}
text_sensor:
- platform: homeassistant
id: weather_state
name: "Current Weather Icon"
entity_id: $weather_entity
internal: true
- platform: template
name: "PM 2.5 Air Quality"
icon: mdi:air-filter
id: aq_reading
lambda: |-
if (id(pm25).state <= 12) {
return {"Good"};
}
else if ((id(pm25).state >= 12.1) && (id(pm25).state <= 35.4)) {
return {"Moderate"};
}
else if ((id(pm25).state >= 35.5) && (id(pm25).state <= 55.4)) {
return {"Unhealthy(SG)"};
}
else if ((id(pm25).state >= 55.5) && (id(pm25).state <= 150.4)) {
return {"Unhealthy"};
}
else if ((id(pm25).state >= 150.5) && (id(pm25).state <= 250.4)) {
return {"Very Unhealthy"};
}
else if (id(pm25).state >= 250.5) {
return {"Hazardous"};
}
return {};
update_interval: 60s
- platform: template
name: "PM 10 Air Quality"
icon: mdi:air-filter
id: aq_10_reading
lambda: |-
if (id(pm10).state <= 54) {
return {"Good"};
}
else if ((id(pm10).state >= 55) && (id(pm10).state <= 154)) {
return {"Moderate"};
}
else if ((id(pm10).state >= 155) && (id(pm10).state <= 254)) {
return {"Unhealthy(SG)"};
}
else if ((id(pm10).state >= 255) && (id(pm10).state <= 354)) {
return {"Unhealthy"};
}
else if ((id(pm10).state >= 355) && (id(pm10).state <= 424)) {
return {"Very Unhealthy"};
}
else if (id(pm10).state >= 425) {
return {"Hazardous"};
}
return {};
update_interval: 60s
- platform: template
name: "Livingroom IAQ"
icon: "mdi:air-filter"
id: iaq_reading
lambda: |-
// 1. Safety Check: Ensure all sensors have valid numbers
if (isnan(id(humi).state) || isnan(id(pm25).state) || isnan(id(eco2).state) || isnan(id(tvoc).state)) {
return {"Booting..."};
}
id(iaq_index) = 0;
if (id(humi).state < 10 or id(humi).state > 90) {
id(iaq_index) += 1;
}
else if (id(humi).state < 20 or id(humi).state > 80) {
id(iaq_index) += 2;
}
else if (id(humi).state < 30 or id(humi).state > 70) {
id(iaq_index) += 3;
}
else if (id(humi).state < 40 or id(humi).state > 60) {
id(iaq_index) += 4;
}
else if (id(humi).state >= 40 and id(humi).state <= 60) {
id(iaq_index) += 5;
}
if (id(pm25).state <= 12) {
id(iaq_index) += 6;
}
else if ((id(pm25).state >= 12.1) && (id(pm25).state <= 35.4)) {
id(iaq_index) += 5;
}
else if ((id(pm25).state >= 35.5) && (id(pm25).state <= 55.4)) {
id(iaq_index) += 4;
}
else if ((id(pm25).state >= 55.5) && (id(pm25).state <= 150.4)) {
id(iaq_index) += 3;
}
else if ((id(pm25).state >= 150.5) && (id(pm25).state <= 250.4)) {
id(iaq_index) += 2;
}
else if (id(pm25).state >= 250.5) {
id(iaq_index) += 1;
}
if (id(eco2).state <= 600) {
id(iaq_index) += 5;
}
else if (id(eco2).state <= 800) {
id(iaq_index) += 4;
}
else if (id(eco2).state <= 1500) {
id(iaq_index) += 3;
}
else if (id(eco2).state <= 1800) {
id(iaq_index) += 2;
}
else if (id(eco2).state > 1800) {
id(iaq_index) += 1;
}
if (id(tvoc).state <= 65) {
id(iaq_index) += 5;
}
else if (id(tvoc).state <= 220) {
id(iaq_index) += 4;
}
else if (id(tvoc).state <= 660) {
id(iaq_index) += 3;
}
else if (id(tvoc).state <= 2200) {
id(iaq_index) += 2;
}
else if (id(tvoc).state > 2200) {
id(iaq_index) += 1;
}
ESP_LOGD("main", "Current IAQ index %d", id(iaq_index));
if (id(iaq_index) <= 11) {
return {"Unhealthy"};
}
else if (id(iaq_index) <= 14) {
return {"Poor"};
}
else if (id(iaq_index) <= 17) {
return {"Moderate"};
}
else if (id(iaq_index) <= 19) {
return {"Good"};
}
else if (id(iaq_index) > 19) {
return {"Excellent"};
}
return {};
update_interval: 60s
on_value:
then:
- script.execute: update_led_color
- platform: template
name: "Livingroom IAQ Calculation"
icon: "mdi:air-filter"
id: iaq_reading_calculation
lambda: |-
id(iaq_index) = 0;
if (id(humi).state < 10 or id(humi).state > 90) {
id(iaq_index) += 1;
}
else if (id(humi).state < 20 or id(humi).state > 80) {
id(iaq_index) += 2;
}
else if (id(humi).state < 30 or id(humi).state > 70) {
id(iaq_index) += 3;
}
else if (id(humi).state < 40 or id(humi).state > 60) {
id(iaq_index) += 4;
}
else if (id(humi).state >= 40 and id(humi).state <= 60) {
id(iaq_index) += 5;
}
if (id(pm25).state <= 12) {
id(iaq_index) += 6;
}
else if ((id(pm25).state >= 12.1) && (id(pm25).state <= 35.4)) {
id(iaq_index) += 5;
}
else if ((id(pm25).state >= 35.5) && (id(pm25).state <= 55.4)) {
id(iaq_index) += 4;
}
else if ((id(pm25).state >= 55.5) && (id(pm25).state <= 150.4)) {
id(iaq_index) += 3;
}
else if ((id(pm25).state >= 150.5) && (id(pm25).state <= 250.4)) {
id(iaq_index) += 2;
}
else if (id(pm25).state >= 250.5) {
id(iaq_index) += 1;
}
if (id(eco2).state <= 600) {
id(iaq_index) += 5;
}
else if (id(eco2).state <= 800) {
id(iaq_index) += 4;
}
else if (id(eco2).state <= 1500) {
id(iaq_index) += 3;
}
else if (id(eco2).state <= 1800) {
id(iaq_index) += 2;
}
else if (id(eco2).state > 1800) {
id(iaq_index) += 1;
}
if (id(tvoc).state <= 65) {
id(iaq_index) += 5;
}
else if (id(tvoc).state <= 220) {
id(iaq_index) += 4;
}
else if (id(tvoc).state <= 660) {
id(iaq_index) += 3;
}
else if (id(tvoc).state <= 2200) {
id(iaq_index) += 2;
}
else if (id(tvoc).state > 2200) {
id(iaq_index) += 1;
}
ESP_LOGD("main", "Current IAQ index %d", id(iaq_index));
return std::to_string(id(iaq_index));
update_interval: 60s
- platform: wifi_info
ip_address:
name: "${device_name} IP Address"
ssid:
name: "${device_name} Connected SSID"
bssid:
name: "${device_name} BSSID"
time:
- platform: homeassistant
id: homeassistant_time
on_time:
- seconds: 0
minutes: 0
hours: 6 # 6 AM
then:
- logger.log: "Automatic morning turn ON OLED"
- switch.turn_on: oled_power
- seconds: 0
minutes: 0
hours: 23 # 11 PM
then:
- logger.log: "Automatic evening turn OFF OLED"
- switch.turn_off: oled_power
+825
View File
@@ -0,0 +1,825 @@
substitutions: #substitute your own values in this section
internal_temp_sensor: sensor.meat_heater_current_temperature #entity from Home Assistant
outside_temp_sensor: sensor.home_realfeel_temperature #entity from Home Assistant
weather_entity: weather.home #entity from Home Assistant
todays_forecast_high_entity: sensor.home_realfeel_temperature_max_day_0 #entity from Home Assistant
todays_forecast_low_entity: sensor.home_realfeel_temperature_min_day_0 #entity from Home Assistant
device_name: airqualitysensor-mainfloor
friendly_name: "Air Sensor - Main Floor"
esphome:
name: ${device_name}
friendly_name: ${friendly_name}
on_boot:
priority: -100
then:
- light.turn_on:
id: led
brightness: 0.4
effect: "Rainbow Effect"
esp32:
board: esp32-s3-devkitc-1
cpu_frequency: 240MHz
variant: esp32s3
flash_size: 16MB
framework:
type: esp-idf
# Enable logging
logger:
level: INFO
# Enable Home Assistant API
api:
encryption:
key: "JPYycQ6Uxuubxrfu66M/xAyUPGEl2DiJBCbpCCs9w/4="
ota:
- platform: esphome
password: "42087a27f07378b5499426cf544a6fa6"
wifi:
ssid: !secret wifi_iot_ssid
password: !secret wifi_password
# Enable fallback hotspot (captive portal) in case wifi connection fails
ap:
ssid: "Airqualitysensor-2"
password: "uhEm8rcU8sMF"
# Prevents aggressive reconnection attempts
power_save_mode: none # Use 'light' for battery devices
# Slower but more reliable connection
fast_connect: false
# Handle connection failures gracefully
on_connect:
- logger.log: "Wi-Fi connected!"
on_disconnect:
- logger.log: "Wi-Fi disconnected!"
# Reduce mDNS traffic
mdns:
disabled: false # Set to true if using static IPs and don't need discovery
#captive_portal:
web_server:
port: 80
time:
- platform: homeassistant
id: homeassistant_time
on_time:
- seconds: 0
minutes: 0
hours: 6 # 6 AM
then:
- logger.log: "Automatic morning turn ON OLED"
- switch.turn_on: oled_power
- seconds: 0
minutes: 0
hours: 23 # 11 PM
then:
- logger.log: "Automatic evening turn OFF OLED"
- switch.turn_off: oled_power
script:
# SCRIPT 1: Runs when the mode is changed from the select
- id: update_led_state
then:
- lambda: |-
if (!id(iaq_reading).has_state() || id(iaq_reading).state == "Booting...") {
return; // Exit script, leave rainbow alone
}
std::string quality = id(iaq_reading).state;
float lux = id(ambient_light).state;
std::string mode = id(led_mode).state;
float brightness = 0.15;
float r = 0.0, g = 0.0, b = 0.0;
std::string selected_effect = "None";
// --- 1. Determine desired color ---
if (quality == "Excellent") { r = 0.0; g = 1.0; b = 0.0; }
else if (quality == "Good") { r = 0.3; g = 0.6; b = 0.3; }
else if (quality == "Moderate") { r = 1.0; g = 0.85; b = 0.38; }
else if (quality == "Poor") { r = 1.0; g = 0.0; b = 0.0; }
else if (quality == "Unhealthy") {
r = 1.0; g = 0.0; b = 1.0;
selected_effect = "Alert Flash"; // Alert overrides mode
}
// --- 2. Determine desired effect based on mode (if not alerting) ---
if (selected_effect != "Alert Flash") {
if (mode == "Breathing") {
selected_effect = "Breathing";
} else if (mode == "Scanner") {
selected_effect = "Scanner";
}
}
// --- 3. Apply changes ---
auto call = id(led).turn_on();
call.set_rgb(r, g, b); // Always set color
if (selected_effect == "None") {
// We want a solid light (Auto/Manual)
if (mode == "Auto") {
if (isnan(lux)) lux = 0;
brightness = 0.15 + (lux / 200.0) * (0.7 - 0.15);
if (brightness > 0.7) brightness = 0.7;
if (brightness < 0.15) brightness = 0.15;
} else { // Manual
brightness = id(iaq_led_brightness).state;
}
call.set_brightness(brightness);
}
// This script *always* sets the effect
call.set_effect(selected_effect);
call.perform();
# SCRIPT 2: Runs when IAQ *value* changes (every 10s)
- id: update_led_color
then:
- lambda: |-
if (!id(iaq_reading).has_state() || id(iaq_reading).state == "Booting...") {
return; // Exit script, leave rainbow alone
}
std::string quality = id(iaq_reading).state;
std::string mode = id(led_mode).state;
float lux = id(ambient_light).state;
float r = 0.0, g = 0.0, b = 0.0;
// --- 1. Determine desired color ---
if (quality == "Excellent") { r = 0.0; g = 1.0; b = 0.0; }
else if (quality == "Good") { r = 0.3; g = 0.6; b = 0.3; }
else if (quality == "Moderate") { r = 1.0; g = 0.85; b = 0.38; }
else if (quality == "Poor") { r = 1.0; g = 0.0; b = 0.0; }
else if (quality == "Unhealthy") {
r = 1.0; g = 0.0; b = 1.0;
// Force Alert Flash and EXIT
id(led).turn_on().set_rgb(r, g, b).set_effect("Alert Flash").perform();
return;
}
// --- 2. If we are here, AQ is OK. Reset Effect! ---
auto call = id(led).turn_on();
call.set_rgb(r, g, b);
// Determine which effect to restore based on the Select Mode
std::string restore_effect = "None";
if (mode == "Breathing") restore_effect = "Breathing";
else if (mode == "Scanner") restore_effect = "Scanner";
// Explicitly set the effect (This stops the Alert Flash)
call.set_effect(restore_effect);
// --- 3. Apply Brightness ---
if (mode == "Auto") {
float brightness = 0.15;
if (isnan(lux)) lux = 0;
brightness = 0.15 + (lux / 200.0) * (0.7 - 0.15);
if (brightness > 0.7) brightness = 0.7;
if (brightness < 0.15) brightness = 0.15;
call.set_brightness(brightness);
} else if (mode == "Manual") {
call.set_brightness(id(iaq_led_brightness).state);
}
call.perform();
globals:
- id: iaq_index
type: int
restore_value: no
initial_value: '0'
- id: computed_brightness
type: float
restore_value: no
initial_value: '0.15'
uart:
tx_pin: GPIO17
rx_pin: GPIO18
baud_rate: 9600
light:
- platform: esp32_rmt_led_strip
rgb_order: GRB
chipset: WS2812
pin: GPIO16
num_leds: 5
name: "LED"
id: led
icon: mdi:led-on
default_transition_length: 0s
disabled_by_default: False
effects:
- pulse:
name: "Breathing"
min_brightness: 0.15
max_brightness: 0.45
transition_length: 3000ms
update_interval: 3000ms
- addressable_scan:
name: "Scanner"
scan_width: 2
move_interval: 100ms
- flicker:
name: "Alert Flash"
alpha: 95%
intensity: 1.5%
- addressable_rainbow:
name: "Rainbow Effect"
speed: 10
width: 50
i2c:
sda: GPIO8
scl: GPIO9
scan: true
id: bus_a
frequency: 100kHz
switch:
- platform: template
name: "OLED Power"
id: oled_power
optimistic: true
restore_mode: RESTORE_DEFAULT_ON
turn_on_action:
- logger.log: "OLED turned ON"
turn_off_action:
- logger.log: "OLED turned OFF"
select:
- platform: template
name: "LED Mode"
id: led_mode
options:
- "Auto"
- "Manual"
- "Breathing"
- "Scanner"
optimistic: true
restore_value: true
on_value:
then:
- script.execute: update_led_state
number:
- platform: template
name: "IAQ LED Brightness"
id: iaq_led_brightness
min_value: 0.15
max_value: 1.0
step: 0.01
optimistic: true
restore_value: true
sensor:
- platform: uptime
name: Uptime Sensor
- platform: homeassistant
id: inside_temperature
entity_id: $internal_temp_sensor
internal: true
- platform: homeassistant
id: outside_temperature
entity_id: $outside_temp_sensor
internal: true
- platform: homeassistant
id: todays_forecast_high
entity_id: $todays_forecast_high_entity
internal: true
- platform: homeassistant
id: todays_forecast_low
entity_id: $todays_forecast_low_entity
internal: true
- platform: pmsx003
type: PMSX003
update_interval: 60s
pm_1_0:
id: pm1
name: "PM <1.0µm Concentration"
pm_2_5:
id: pm25
name: "PM <2.5µm Concentration"
pm_10_0:
id: pm10
name: "PM <10.0µm Concentration"
- platform: bme680
temperature:
name: "BME680 Temperature"
id: bmetemp
oversampling: 16x
pressure:
name: "BME680 Pressure"
id: bmepressure
humidity:
name: "BME680 Humidity"
id: bmehum
gas_resistance:
name: "BME680 Gas Resistance"
id: bmegas
address: 0x77
update_interval: 60s
## CO²/VOC Sensor
- platform: ccs811
eco2:
name: "CCS811 CO²"
accuracy_decimals: 0
id: eco2
tvoc:
name: "CCS811 T-VOC"
accuracy_decimals: 0
id: tvoc
address: 0x5A
update_interval: 60s
temperature: bmetemp
humidity: bmehum
## After Calibration, Uncomment and change "baseline:"
baseline: 0x9CB1
- platform: template
name: "Humidity Sensor"
id: humi
unit_of_measurement: "%"
accuracy_decimals: 1
lambda: |-
return id(bmehum).state;
update_interval: 60s
- platform: wifi_signal
name: AQ WiFi Signal
update_interval: 60s
internal: true
- platform: veml7700
address: 0x10
update_interval: 60s
# short variant of sensor definition:
ambient_light:
name: "Ambient Light"
id: ambient_light
filters:
- sliding_window_moving_average:
window_size: 5
send_every: 1
- platform: wifi_signal
name: "${device_name} WiFi Signal"
update_interval: 60s
- platform: uptime
name: "${device_name} Uptime"
update_interval: 60s
font:
- file: "fonts/Roboto-Regular.ttf"
id: robotto
size: 10
- file: "fonts/Roboto-Regular.ttf"
id: font2
size: 12
- file: "fonts/Poppins-Regular.ttf"
id: font1
size: 10
- file: "fonts/Poppins-Regular.ttf"
id: poppinslarger
size: 12
- file: "fonts/Poppins-SemiBold.ttf"
id: poppinsbold
size: 10
- file: 'fonts/materialdesignicons-webfont.ttf'
id: font3
size: 18
glyphs:
- "\U000F13D5" #mdi:home-minus-outline
- file: 'fonts/materialdesignicons-webfont.ttf'
id: font4
size: 40
glyphs:
- "\U000F0594" #"clear-night"
- "\U000F0590" #"cloudy"
- "\U000F0591" #"fog"
- "\U000F0592" #"hail"
- "\U000F0593" #"lightning"
- "\U000F067E" #"lightning-rainy"
- "\U000F0595" #"partlycloudy"
- "\U000F0596" #"pouring"
- "\U000F0597" #"rainy"
- "\U000F0598" #"snowy"
- "\U000F067F" #"snowy-rainy"
- "\U000F0599" #"sunny"
- "\U000F059D" #"windy"
- "\U000F059E" #"windy-variant"
display:
- platform: ssd1306_i2c
model: "SSD1306 128x64"
address: 0x3C
id: oled_display
rotation: 0
i2c_id: bus_a
pages:
- id: page_air_quality
lambda: |-
if (id(oled_power).state) { // only draw if display is ON
{
// Combine the final string first
char full_text[64];
if (id(iaq_reading).has_state()) {
sprintf(full_text, "Air Quality: %s", id(iaq_reading).state.c_str());
} else {
sprintf(full_text, "Air Quality: Booting...");
}
int x, y, w, h;
it.get_text_bounds(0, 0, full_text, id(poppinsbold), TextAlign::TOP_LEFT, &x, &y, &w, &h);
int center_x = (128 - w) / 2; // OLED is 128px wide
it.printf(center_x, 0, id(poppinsbold), "%s", full_text);
}
// --- Temp + Humidity on same line ---
it.printf(0, 16, id(font1), "TEMP: %.1f°C", id(bmetemp).state);
it.printf(70, 16, id(font1), "HUM: %.0f%%", id(bmehum).state);
// --- Other sensor lines ---
it.printf(0, 26, id(font1), "CO2: %.0f ppm", id(eco2).state);
it.printf(0, 36, id(font1), "TVOC: %.0f ppb", id(tvoc).state);
if (!isnan(id(pm25).state)) {
it.printf(0, 46, id(font1), "PM2.5: %.0f", id(pm25).state);
} else {
it.printf(0, 46, id(font1), "PM2.5: ---");
}
}
- id: page_environment
lambda: |-
if (id(oled_power).state) { // only draw if display is ON
{
// Combine the final string first
char full_text[64];
if (id(iaq_reading).has_state()) {
sprintf(full_text, "Air Quality: %s", id(iaq_reading).state.c_str());
} else {
sprintf(full_text, "Air Quality: Booting...");
}
int x, y, w, h;
it.get_text_bounds(0, 0, full_text, id(poppinsbold), TextAlign::TOP_LEFT, &x, &y, &w, &h);
int center_x = (128 - w) / 2; // OLED is 128px wide
it.printf(center_x, 0, id(poppinsbold), "%s", full_text);
}
// --- Temp + Humidity on same line ---
it.printf(0, 16, id(font1), "TEMP: %.1f°C", id(bmetemp).state);
it.printf(70, 16, id(font1), "HUM: %.0f%%", id(bmehum).state);
// --- Other sensor lines ---
it.printf(0, 26, id(font1), "CO2: %.0f ppm", id(ambient_light).state);
}
- id: page_weather
lambda: |-
if (id(weather_state).has_state()) {
std::map<std::string, std::string> weather_icon_map
{
{"clear-night", "\U000F0594"},
{"cloudy", "\U000F0590"},
{"fog", "\U000F0591"},
{"hail", "\U000F0592"},
{"lightning", "\U000F0593"},
{"lightning-rainy", "\U000F067E"},
{"partlycloudy", "\U000F0595"},
{"pouring", "\U000F0596"},
{"rainy", "\U000F0597"},
{"snowy", "\U000F0598"},
{"snowy-rainy", "\U000F067F"},
{"sunny", "\U000F0599"},
{"windy", "\U000F059D"},
{"windy-variant", "\U000F059E"},
};
it.printf(0, it.get_height(), id(font4), TextAlign::BASELINE_LEFT, weather_icon_map[id(weather_state).state.c_str()].c_str());
}
// Print time in HH:MM format
it.strftime(0, 0, id(font1), TextAlign::TOP_LEFT, "%H:%M %a", id(homeassistant_time).now());
//Print day of week
//it.strftime(40, 0, id(font1), TextAlign::TOP_LEFT, "%a", id(homeassistant_time).now());
it.line(0, 20, it.get_width(), 20);
//Print home icon
//it.printf(70, 0, id(font3), "\U000F13D5");
// Print inside temperature (from homeassistant sensor)
if (id(inside_temperature).has_state()) {
it.printf(it.get_width(), 0, id(font1), TextAlign::TOP_RIGHT , "%7.1f°", id(inside_temperature).state);
}
// Print outside temperature (from homeassistant sensor)
if (id(outside_temperature).has_state()) {
it.printf(42, 32, id(font2), "%.1f°", id(outside_temperature).state);
}
// Print Forecast High
if (id(todays_forecast_high).has_state()) {
it.printf(it.get_width(), 32, id(font1), TextAlign::TOP_RIGHT, "%7.1f°", id(todays_forecast_high).state);
}
// Print Forecast Low
if (id(todays_forecast_low).has_state()) {
it.printf(it.get_width(), 48, id(font1), TextAlign::TOP_RIGHT, "%7.1f°", id(todays_forecast_low).state);
}
text_sensor:
- platform: homeassistant
id: weather_state
name: "Current Weather Icon"
entity_id: $weather_entity
internal: true
- platform: template
name: "PM 2.5 Air Quality"
icon: mdi:air-filter
id: aq_reading
lambda: |-
if (id(pm25).state <= 12) {
return {"Good"};
}
else if ((id(pm25).state >= 12.1) && (id(pm25).state <= 35.4)) {
return {"Moderate"};
}
else if ((id(pm25).state >= 35.5) && (id(pm25).state <= 55.4)) {
return {"Unhealthy(SG)"};
}
else if ((id(pm25).state >= 55.5) && (id(pm25).state <= 150.4)) {
return {"Unhealthy"};
}
else if ((id(pm25).state >= 150.5) && (id(pm25).state <= 250.4)) {
return {"Very Unhealthy"};
}
else if (id(pm25).state >= 250.5) {
return {"Hazardous"};
}
return {};
update_interval: 60s
- platform: template
name: "PM 10 Air Quality"
icon: mdi:air-filter
id: aq_10_reading
lambda: |-
if (id(pm10).state <= 54) {
return {"Good"};
}
else if ((id(pm10).state >= 55) && (id(pm10).state <= 154)) {
return {"Moderate"};
}
else if ((id(pm10).state >= 155) && (id(pm10).state <= 254)) {
return {"Unhealthy(SG)"};
}
else if ((id(pm10).state >= 255) && (id(pm10).state <= 354)) {
return {"Unhealthy"};
}
else if ((id(pm10).state >= 355) && (id(pm10).state <= 424)) {
return {"Very Unhealthy"};
}
else if (id(pm10).state >= 425) {
return {"Hazardous"};
}
return {};
update_interval: 60s
- platform: template
name: "Livingroom IAQ"
icon: "mdi:air-filter"
id: iaq_reading
lambda: |-
// 1. Safety Check: Ensure all sensors have valid numbers
if (isnan(id(humi).state) || isnan(id(pm25).state) || isnan(id(eco2).state) || isnan(id(tvoc).state)) {
return {"Booting..."};
}
id(iaq_index) = 0;
if (id(humi).state < 10 or id(humi).state > 90) {
id(iaq_index) += 1;
}
else if (id(humi).state < 20 or id(humi).state > 80) {
id(iaq_index) += 2;
}
else if (id(humi).state < 30 or id(humi).state > 70) {
id(iaq_index) += 3;
}
else if (id(humi).state < 40 or id(humi).state > 60) {
id(iaq_index) += 4;
}
else if (id(humi).state >= 40 and id(humi).state <= 60) {
id(iaq_index) += 5;
}
if (id(pm25).state <= 12) {
id(iaq_index) += 6;
}
else if ((id(pm25).state >= 12.1) && (id(pm25).state <= 35.4)) {
id(iaq_index) += 5;
}
else if ((id(pm25).state >= 35.5) && (id(pm25).state <= 55.4)) {
id(iaq_index) += 4;
}
else if ((id(pm25).state >= 55.5) && (id(pm25).state <= 150.4)) {
id(iaq_index) += 3;
}
else if ((id(pm25).state >= 150.5) && (id(pm25).state <= 250.4)) {
id(iaq_index) += 2;
}
else if (id(pm25).state >= 250.5) {
id(iaq_index) += 1;
}
if (id(eco2).state <= 600) {
id(iaq_index) += 5;
}
else if (id(eco2).state <= 800) {
id(iaq_index) += 4;
}
else if (id(eco2).state <= 1500) {
id(iaq_index) += 3;
}
else if (id(eco2).state <= 1800) {
id(iaq_index) += 2;
}
else if (id(eco2).state > 1800) {
id(iaq_index) += 1;
}
if (id(tvoc).state <= 65) {
id(iaq_index) += 5;
}
else if (id(tvoc).state <= 220) {
id(iaq_index) += 4;
}
else if (id(tvoc).state <= 660) {
id(iaq_index) += 3;
}
else if (id(tvoc).state <= 2200) {
id(iaq_index) += 2;
}
else if (id(tvoc).state > 2200) {
id(iaq_index) += 1;
}
ESP_LOGD("main", "Current IAQ index %d", id(iaq_index));
if (id(iaq_index) <= 11) {
return {"Unhealthy"};
}
else if (id(iaq_index) <= 14) {
return {"Poor"};
}
else if (id(iaq_index) <= 17) {
return {"Moderate"};
}
else if (id(iaq_index) <= 19) {
return {"Good"};
}
else if (id(iaq_index) > 19) {
return {"Excellent"};
}
return {};
update_interval: 60s
on_value:
then:
- script.execute: update_led_color
- platform: template
name: "Livingroom IAQ Calculation"
icon: "mdi:air-filter"
id: iaq_reading_calculation
lambda: |-
id(iaq_index) = 0;
if (id(humi).state < 10 or id(humi).state > 90) {
id(iaq_index) += 1;
}
else if (id(humi).state < 20 or id(humi).state > 80) {
id(iaq_index) += 2;
}
else if (id(humi).state < 30 or id(humi).state > 70) {
id(iaq_index) += 3;
}
else if (id(humi).state < 40 or id(humi).state > 60) {
id(iaq_index) += 4;
}
else if (id(humi).state >= 40 and id(humi).state <= 60) {
id(iaq_index) += 5;
}
if (id(pm25).state <= 12) {
id(iaq_index) += 6;
}
else if ((id(pm25).state >= 12.1) && (id(pm25).state <= 35.4)) {
id(iaq_index) += 5;
}
else if ((id(pm25).state >= 35.5) && (id(pm25).state <= 55.4)) {
id(iaq_index) += 4;
}
else if ((id(pm25).state >= 55.5) && (id(pm25).state <= 150.4)) {
id(iaq_index) += 3;
}
else if ((id(pm25).state >= 150.5) && (id(pm25).state <= 250.4)) {
id(iaq_index) += 2;
}
else if (id(pm25).state >= 250.5) {
id(iaq_index) += 1;
}
if (id(eco2).state <= 600) {
id(iaq_index) += 5;
}
else if (id(eco2).state <= 800) {
id(iaq_index) += 4;
}
else if (id(eco2).state <= 1500) {
id(iaq_index) += 3;
}
else if (id(eco2).state <= 1800) {
id(iaq_index) += 2;
}
else if (id(eco2).state > 1800) {
id(iaq_index) += 1;
}
if (id(tvoc).state <= 65) {
id(iaq_index) += 5;
}
else if (id(tvoc).state <= 220) {
id(iaq_index) += 4;
}
else if (id(tvoc).state <= 660) {
id(iaq_index) += 3;
}
else if (id(tvoc).state <= 2200) {
id(iaq_index) += 2;
}
else if (id(tvoc).state > 2200) {
id(iaq_index) += 1;
}
ESP_LOGD("main", "Current IAQ index %d", id(iaq_index));
return std::to_string(id(iaq_index));
update_interval: 60s
- platform: wifi_info
ip_address:
name: "${device_name} IP Address"
ssid:
name: "${device_name} Connected SSID"
bssid:
name: "${device_name} BSSID"
# Watchdog to auto-reboot if things go wrong
interval:
- interval: 5min
then:
- if:
condition:
and:
- lambda: 'return millis() > 300000;' # >5 min uptime
- not:
wifi.connected:
then:
- logger.log: "Wi-Fi not connected for 5 min; auto-reboot temporarily disabled for debugging"
- delay: 10s
+825
View File
@@ -0,0 +1,825 @@
substitutions: #substitute your own values in this section
internal_temp_sensor: sensor.meat_heater_current_temperature #entity from Home Assistant
outside_temp_sensor: sensor.home_realfeel_temperature #entity from Home Assistant
weather_entity: weather.home #entity from Home Assistant
todays_forecast_high_entity: sensor.home_realfeel_temperature_max_day_0 #entity from Home Assistant
todays_forecast_low_entity: sensor.home_realfeel_temperature_min_day_0 #entity from Home Assistant
device_name: airqualitysensor-upstairs
friendly_name: "Air Sensor - Upstairs"
esphome:
name: ${device_name}
friendly_name: ${friendly_name}
on_boot:
priority: -100
then:
- light.turn_on:
id: led
brightness: 0.4
effect: "Rainbow Effect"
esp32:
board: esp32-s3-devkitc-1
cpu_frequency: 240MHz
variant: esp32s3
flash_size: 16MB
framework:
type: esp-idf
# Enable logging
logger:
level: INFO
# Enable Home Assistant API
api:
encryption:
key: "p7SGIY1i+hwzZCQMliScoY2B98Qva1nPGUnzTr+8knE="
ota:
- platform: esphome
password: "81bd35b0916ed26675b611274e652689"
wifi:
ssid: !secret wifi_iot_ssid
password: !secret wifi_password
# Enable fallback hotspot (captive portal) in case wifi connection fails
ap:
ssid: "Airqualitysensor-3"
password: "a8CsGahAGljj"
# Prevents aggressive reconnection attempts
power_save_mode: none # Use 'light' for battery devices
# Slower but more reliable connection
fast_connect: false
# Handle connection failures gracefully
on_connect:
- logger.log: "Wi-Fi connected!"
on_disconnect:
- logger.log: "Wi-Fi disconnected!"
# Reduce mDNS traffic
mdns:
disabled: false # Set to true if using static IPs and don't need discovery
#captive_portal:
web_server:
port: 80
time:
- platform: homeassistant
id: homeassistant_time
on_time:
- seconds: 0
minutes: 0
hours: 6 # 6 AM
then:
- logger.log: "Automatic morning turn ON OLED"
- switch.turn_on: oled_power
- seconds: 0
minutes: 0
hours: 19 # 7 PM
then:
- logger.log: "Automatic evening turn OFF OLED"
- switch.turn_off: oled_power
script:
# SCRIPT 1: Runs when the mode is changed from the select
- id: update_led_state
then:
- lambda: |-
if (!id(iaq_reading).has_state() || id(iaq_reading).state == "Booting...") {
return; // Exit script, leave rainbow alone
}
std::string quality = id(iaq_reading).state;
float lux = id(ambient_light).state;
std::string mode = id(led_mode).state;
float brightness = 0.15;
float r = 0.0, g = 0.0, b = 0.0;
std::string selected_effect = "None";
// --- 1. Determine desired color ---
if (quality == "Excellent") { r = 0.0; g = 1.0; b = 0.0; }
else if (quality == "Good") { r = 0.3; g = 0.6; b = 0.3; }
else if (quality == "Moderate") { r = 1.0; g = 0.85; b = 0.38; }
else if (quality == "Poor") { r = 1.0; g = 0.0; b = 0.0; }
else if (quality == "Unhealthy") {
r = 1.0; g = 0.0; b = 1.0;
selected_effect = "Alert Flash"; // Alert overrides mode
}
// --- 2. Determine desired effect based on mode (if not alerting) ---
if (selected_effect != "Alert Flash") {
if (mode == "Breathing") {
selected_effect = "Breathing";
} else if (mode == "Scanner") {
selected_effect = "Scanner";
}
}
// --- 3. Apply changes ---
auto call = id(led).turn_on();
call.set_rgb(r, g, b); // Always set color
if (selected_effect == "None") {
// We want a solid light (Auto/Manual)
if (mode == "Auto") {
if (isnan(lux)) lux = 0;
brightness = 0.15 + (lux / 200.0) * (0.7 - 0.15);
if (brightness > 0.7) brightness = 0.7;
if (brightness < 0.15) brightness = 0.15;
} else { // Manual
brightness = id(iaq_led_brightness).state;
}
call.set_brightness(brightness);
}
// This script *always* sets the effect
call.set_effect(selected_effect);
call.perform();
# SCRIPT 2: Runs when IAQ *value* changes (every 10s)
- id: update_led_color
then:
- lambda: |-
if (!id(iaq_reading).has_state() || id(iaq_reading).state == "Booting...") {
return; // Exit script, leave rainbow alone
}
std::string quality = id(iaq_reading).state;
std::string mode = id(led_mode).state;
float lux = id(ambient_light).state;
float r = 0.0, g = 0.0, b = 0.0;
// --- 1. Determine desired color ---
if (quality == "Excellent") { r = 0.0; g = 1.0; b = 0.0; }
else if (quality == "Good") { r = 0.3; g = 0.6; b = 0.3; }
else if (quality == "Moderate") { r = 1.0; g = 0.85; b = 0.38; }
else if (quality == "Poor") { r = 1.0; g = 0.0; b = 0.0; }
else if (quality == "Unhealthy") {
r = 1.0; g = 0.0; b = 1.0;
// Force Alert Flash and EXIT
id(led).turn_on().set_rgb(r, g, b).set_effect("Alert Flash").perform();
return;
}
// --- 2. If we are here, AQ is OK. Reset Effect! ---
auto call = id(led).turn_on();
call.set_rgb(r, g, b);
// Determine which effect to restore based on the Select Mode
std::string restore_effect = "None";
if (mode == "Breathing") restore_effect = "Breathing";
else if (mode == "Scanner") restore_effect = "Scanner";
// Explicitly set the effect (This stops the Alert Flash)
call.set_effect(restore_effect);
// --- 3. Apply Brightness ---
if (mode == "Auto") {
float brightness = 0.15;
if (isnan(lux)) lux = 0;
brightness = 0.15 + (lux / 200.0) * (0.7 - 0.15);
if (brightness > 0.7) brightness = 0.7;
if (brightness < 0.15) brightness = 0.15;
call.set_brightness(brightness);
} else if (mode == "Manual") {
call.set_brightness(id(iaq_led_brightness).state);
}
call.perform();
globals:
- id: iaq_index
type: int
restore_value: no
initial_value: '0'
- id: computed_brightness
type: float
restore_value: no
initial_value: '0.15'
uart:
tx_pin: GPIO17
rx_pin: GPIO18
baud_rate: 9600
light:
- platform: esp32_rmt_led_strip
rgb_order: GRB
chipset: WS2812
pin: GPIO16
num_leds: 5
name: "LED"
id: led
icon: mdi:led-on
default_transition_length: 0s
disabled_by_default: False
effects:
- pulse:
name: "Breathing"
min_brightness: 0.15
max_brightness: 0.45
transition_length: 3000ms
update_interval: 3000ms
- addressable_scan:
name: "Scanner"
scan_width: 2
move_interval: 100ms
- flicker:
name: "Alert Flash"
alpha: 95%
intensity: 1.5%
- addressable_rainbow:
name: "Rainbow Effect"
speed: 10
width: 50
i2c:
sda: GPIO8
scl: GPIO9
scan: true
id: bus_a
frequency: 100kHz
switch:
- platform: template
name: "OLED Power"
id: oled_power
optimistic: true
restore_mode: RESTORE_DEFAULT_ON
turn_on_action:
- logger.log: "OLED turned ON"
turn_off_action:
- logger.log: "OLED turned OFF"
select:
- platform: template
name: "LED Mode"
id: led_mode
options:
- "Auto"
- "Manual"
- "Breathing"
- "Scanner"
optimistic: true
restore_value: true
on_value:
then:
- script.execute: update_led_state
number:
- platform: template
name: "IAQ LED Brightness"
id: iaq_led_brightness
min_value: 0.15
max_value: 1.0
step: 0.01
optimistic: true
restore_value: true
sensor:
- platform: uptime
name: Uptime Sensor
- platform: homeassistant
id: inside_temperature
entity_id: $internal_temp_sensor
internal: true
- platform: homeassistant
id: outside_temperature
entity_id: $outside_temp_sensor
internal: true
- platform: homeassistant
id: todays_forecast_high
entity_id: $todays_forecast_high_entity
internal: true
- platform: homeassistant
id: todays_forecast_low
entity_id: $todays_forecast_low_entity
internal: true
- platform: pmsx003
type: PMSX003
update_interval: 60s
pm_1_0:
id: pm1
name: "PM <1.0µm Concentration"
pm_2_5:
id: pm25
name: "PM <2.5µm Concentration"
pm_10_0:
id: pm10
name: "PM <10.0µm Concentration"
- platform: bme680
temperature:
name: "BME680 Temperature"
id: bmetemp
oversampling: 16x
pressure:
name: "BME680 Pressure"
id: bmepressure
humidity:
name: "BME680 Humidity"
id: bmehum
gas_resistance:
name: "BME680 Gas Resistance"
id: bmegas
address: 0x77
update_interval: 60s
## CO²/VOC Sensor
- platform: ccs811
eco2:
name: "CCS811 CO²"
accuracy_decimals: 0
id: eco2
tvoc:
name: "CCS811 T-VOC"
accuracy_decimals: 0
id: tvoc
address: 0x5A
update_interval: 60s
temperature: bmetemp
humidity: bmehum
## After Calibration, Uncomment and change "baseline:"
baseline: 0x9CB1
- platform: template
name: "Humidity Sensor"
id: humi
unit_of_measurement: "%"
accuracy_decimals: 1
lambda: |-
return id(bmehum).state;
update_interval: 60s
- platform: wifi_signal
name: AQ WiFi Signal
update_interval: 60s
internal: true
- platform: veml7700
address: 0x10
update_interval: 60s
# short variant of sensor definition:
ambient_light:
name: "Ambient Light"
id: ambient_light
filters:
- sliding_window_moving_average:
window_size: 5
send_every: 1
# Diagnostic sensors
- platform: wifi_signal
name: "${device_name} WiFi Signal"
update_interval: 60s
- platform: uptime
name: "${device_name} Uptime"
update_interval: 60s
font:
- file: "fonts/Roboto-Regular.ttf"
id: robotto
size: 10
- file: "fonts/Roboto-Regular.ttf"
id: font2
size: 12
- file: "fonts/Poppins-Regular.ttf"
id: font1
size: 10
- file: "fonts/Poppins-Regular.ttf"
id: poppinslarger
size: 12
- file: "fonts/Poppins-SemiBold.ttf"
id: poppinsbold
size: 10
- file: 'fonts/materialdesignicons-webfont.ttf'
id: font3
size: 18
glyphs:
- "\U000F13D5" #mdi:home-minus-outline
- file: 'fonts/materialdesignicons-webfont.ttf'
id: font4
size: 40
glyphs:
- "\U000F0594" #"clear-night"
- "\U000F0590" #"cloudy"
- "\U000F0591" #"fog"
- "\U000F0592" #"hail"
- "\U000F0593" #"lightning"
- "\U000F067E" #"lightning-rainy"
- "\U000F0595" #"partlycloudy"
- "\U000F0596" #"pouring"
- "\U000F0597" #"rainy"
- "\U000F0598" #"snowy"
- "\U000F067F" #"snowy-rainy"
- "\U000F0599" #"sunny"
- "\U000F059D" #"windy"
- "\U000F059E" #"windy-variant"
display:
- platform: ssd1306_i2c
model: "SSD1306 128x64"
address: 0x3C
id: oled_display
rotation: 0
i2c_id: bus_a
pages:
- id: page_air_quality
lambda: |-
if (id(oled_power).state) { // only draw if display is ON
{
// Combine the final string first
char full_text[64];
if (id(iaq_reading).has_state()) {
sprintf(full_text, "Air Quality: %s", id(iaq_reading).state.c_str());
} else {
sprintf(full_text, "Air Quality: Booting...");
}
int x, y, w, h;
it.get_text_bounds(0, 0, full_text, id(poppinsbold), TextAlign::TOP_LEFT, &x, &y, &w, &h);
int center_x = (128 - w) / 2; // OLED is 128px wide
it.printf(center_x, 0, id(poppinsbold), "%s", full_text);
}
// --- Temp + Humidity on same line ---
it.printf(0, 16, id(font1), "TEMP: %.1f°C", id(bmetemp).state);
it.printf(70, 16, id(font1), "HUM: %.0f%%", id(bmehum).state);
// --- Other sensor lines ---
it.printf(0, 26, id(font1), "CO2: %.0f ppm", id(eco2).state);
it.printf(0, 36, id(font1), "TVOC: %.0f ppb", id(tvoc).state);
if (!isnan(id(pm25).state)) {
it.printf(0, 46, id(font1), "PM2.5: %.0f", id(pm25).state);
} else {
it.printf(0, 46, id(font1), "PM2.5: ---");
}
}
- id: page_environment
lambda: |-
if (id(oled_power).state) { // only draw if display is ON
{
// Combine the final string first
char full_text[64];
if (id(iaq_reading).has_state()) {
sprintf(full_text, "Air Quality: %s", id(iaq_reading).state.c_str());
} else {
sprintf(full_text, "Air Quality: Booting...");
}
int x, y, w, h;
it.get_text_bounds(0, 0, full_text, id(poppinsbold), TextAlign::TOP_LEFT, &x, &y, &w, &h);
int center_x = (128 - w) / 2; // OLED is 128px wide
it.printf(center_x, 0, id(poppinsbold), "%s", full_text);
}
// --- Temp + Humidity on same line ---
it.printf(0, 16, id(font1), "TEMP: %.1f°C", id(bmetemp).state);
it.printf(70, 16, id(font1), "HUM: %.0f%%", id(bmehum).state);
// --- Other sensor lines ---
it.printf(0, 26, id(font1), "CO2: %.0f ppm", id(ambient_light).state);
}
- id: page_weather
lambda: |-
if (id(weather_state).has_state()) {
std::map<std::string, std::string> weather_icon_map
{
{"clear-night", "\U000F0594"},
{"cloudy", "\U000F0590"},
{"fog", "\U000F0591"},
{"hail", "\U000F0592"},
{"lightning", "\U000F0593"},
{"lightning-rainy", "\U000F067E"},
{"partlycloudy", "\U000F0595"},
{"pouring", "\U000F0596"},
{"rainy", "\U000F0597"},
{"snowy", "\U000F0598"},
{"snowy-rainy", "\U000F067F"},
{"sunny", "\U000F0599"},
{"windy", "\U000F059D"},
{"windy-variant", "\U000F059E"},
};
it.printf(0, it.get_height(), id(font4), TextAlign::BASELINE_LEFT, weather_icon_map[id(weather_state).state.c_str()].c_str());
}
// Print time in HH:MM format
it.strftime(0, 0, id(font1), TextAlign::TOP_LEFT, "%H:%M %a", id(homeassistant_time).now());
//Print day of week
//it.strftime(40, 0, id(font1), TextAlign::TOP_LEFT, "%a", id(homeassistant_time).now());
it.line(0, 20, it.get_width(), 20);
//Print home icon
//it.printf(70, 0, id(font3), "\U000F13D5");
// Print inside temperature (from homeassistant sensor)
if (id(inside_temperature).has_state()) {
it.printf(it.get_width(), 0, id(font1), TextAlign::TOP_RIGHT , "%7.1f°", id(inside_temperature).state);
}
// Print outside temperature (from homeassistant sensor)
if (id(outside_temperature).has_state()) {
it.printf(42, 32, id(font2), "%.1f°", id(outside_temperature).state);
}
// Print Forecast High
if (id(todays_forecast_high).has_state()) {
it.printf(it.get_width(), 32, id(font1), TextAlign::TOP_RIGHT, "%7.1f°", id(todays_forecast_high).state);
}
// Print Forecast Low
if (id(todays_forecast_low).has_state()) {
it.printf(it.get_width(), 48, id(font1), TextAlign::TOP_RIGHT, "%7.1f°", id(todays_forecast_low).state);
}
text_sensor:
- platform: homeassistant
id: weather_state
name: "Current Weather Icon"
entity_id: $weather_entity
internal: true
- platform: template
name: "PM 2.5 Air Quality"
icon: mdi:air-filter
id: aq_reading
lambda: |-
if (id(pm25).state <= 12) {
return {"Good"};
}
else if ((id(pm25).state >= 12.1) && (id(pm25).state <= 35.4)) {
return {"Moderate"};
}
else if ((id(pm25).state >= 35.5) && (id(pm25).state <= 55.4)) {
return {"Unhealthy(SG)"};
}
else if ((id(pm25).state >= 55.5) && (id(pm25).state <= 150.4)) {
return {"Unhealthy"};
}
else if ((id(pm25).state >= 150.5) && (id(pm25).state <= 250.4)) {
return {"Very Unhealthy"};
}
else if (id(pm25).state >= 250.5) {
return {"Hazardous"};
}
return {};
update_interval: 60s
- platform: template
name: "PM 10 Air Quality"
icon: mdi:air-filter
id: aq_10_reading
lambda: |-
if (id(pm10).state <= 54) {
return {"Good"};
}
else if ((id(pm10).state >= 55) && (id(pm10).state <= 154)) {
return {"Moderate"};
}
else if ((id(pm10).state >= 155) && (id(pm10).state <= 254)) {
return {"Unhealthy(SG)"};
}
else if ((id(pm10).state >= 255) && (id(pm10).state <= 354)) {
return {"Unhealthy"};
}
else if ((id(pm10).state >= 355) && (id(pm10).state <= 424)) {
return {"Very Unhealthy"};
}
else if (id(pm10).state >= 425) {
return {"Hazardous"};
}
return {};
update_interval: 60s
- platform: template
name: "Livingroom IAQ"
icon: "mdi:air-filter"
id: iaq_reading
lambda: |-
// 1. Safety Check: Ensure all sensors have valid numbers
if (isnan(id(humi).state) || isnan(id(pm25).state) || isnan(id(eco2).state) || isnan(id(tvoc).state)) {
return {"Booting..."};
}
id(iaq_index) = 0;
if (id(humi).state < 10 or id(humi).state > 90) {
id(iaq_index) += 1;
}
else if (id(humi).state < 20 or id(humi).state > 80) {
id(iaq_index) += 2;
}
else if (id(humi).state < 30 or id(humi).state > 70) {
id(iaq_index) += 3;
}
else if (id(humi).state < 40 or id(humi).state > 60) {
id(iaq_index) += 4;
}
else if (id(humi).state >= 40 and id(humi).state <= 60) {
id(iaq_index) += 5;
}
if (id(pm25).state <= 12) {
id(iaq_index) += 6;
}
else if ((id(pm25).state >= 12.1) && (id(pm25).state <= 35.4)) {
id(iaq_index) += 5;
}
else if ((id(pm25).state >= 35.5) && (id(pm25).state <= 55.4)) {
id(iaq_index) += 4;
}
else if ((id(pm25).state >= 55.5) && (id(pm25).state <= 150.4)) {
id(iaq_index) += 3;
}
else if ((id(pm25).state >= 150.5) && (id(pm25).state <= 250.4)) {
id(iaq_index) += 2;
}
else if (id(pm25).state >= 250.5) {
id(iaq_index) += 1;
}
if (id(eco2).state <= 600) {
id(iaq_index) += 5;
}
else if (id(eco2).state <= 800) {
id(iaq_index) += 4;
}
else if (id(eco2).state <= 1500) {
id(iaq_index) += 3;
}
else if (id(eco2).state <= 1800) {
id(iaq_index) += 2;
}
else if (id(eco2).state > 1800) {
id(iaq_index) += 1;
}
if (id(tvoc).state <= 65) {
id(iaq_index) += 5;
}
else if (id(tvoc).state <= 220) {
id(iaq_index) += 4;
}
else if (id(tvoc).state <= 660) {
id(iaq_index) += 3;
}
else if (id(tvoc).state <= 2200) {
id(iaq_index) += 2;
}
else if (id(tvoc).state > 2200) {
id(iaq_index) += 1;
}
ESP_LOGD("main", "Current IAQ index %d", id(iaq_index));
if (id(iaq_index) <= 11) {
return {"Unhealthy"};
}
else if (id(iaq_index) <= 14) {
return {"Poor"};
}
else if (id(iaq_index) <= 17) {
return {"Moderate"};
}
else if (id(iaq_index) <= 19) {
return {"Good"};
}
else if (id(iaq_index) > 19) {
return {"Excellent"};
}
return {};
update_interval: 60s
on_value:
then:
- script.execute: update_led_color
- platform: template
name: "Livingroom IAQ Calculation"
icon: "mdi:air-filter"
id: iaq_reading_calculation
lambda: |-
id(iaq_index) = 0;
if (id(humi).state < 10 or id(humi).state > 90) {
id(iaq_index) += 1;
}
else if (id(humi).state < 20 or id(humi).state > 80) {
id(iaq_index) += 2;
}
else if (id(humi).state < 30 or id(humi).state > 70) {
id(iaq_index) += 3;
}
else if (id(humi).state < 40 or id(humi).state > 60) {
id(iaq_index) += 4;
}
else if (id(humi).state >= 40 and id(humi).state <= 60) {
id(iaq_index) += 5;
}
if (id(pm25).state <= 12) {
id(iaq_index) += 6;
}
else if ((id(pm25).state >= 12.1) && (id(pm25).state <= 35.4)) {
id(iaq_index) += 5;
}
else if ((id(pm25).state >= 35.5) && (id(pm25).state <= 55.4)) {
id(iaq_index) += 4;
}
else if ((id(pm25).state >= 55.5) && (id(pm25).state <= 150.4)) {
id(iaq_index) += 3;
}
else if ((id(pm25).state >= 150.5) && (id(pm25).state <= 250.4)) {
id(iaq_index) += 2;
}
else if (id(pm25).state >= 250.5) {
id(iaq_index) += 1;
}
if (id(eco2).state <= 600) {
id(iaq_index) += 5;
}
else if (id(eco2).state <= 800) {
id(iaq_index) += 4;
}
else if (id(eco2).state <= 1500) {
id(iaq_index) += 3;
}
else if (id(eco2).state <= 1800) {
id(iaq_index) += 2;
}
else if (id(eco2).state > 1800) {
id(iaq_index) += 1;
}
if (id(tvoc).state <= 65) {
id(iaq_index) += 5;
}
else if (id(tvoc).state <= 220) {
id(iaq_index) += 4;
}
else if (id(tvoc).state <= 660) {
id(iaq_index) += 3;
}
else if (id(tvoc).state <= 2200) {
id(iaq_index) += 2;
}
else if (id(tvoc).state > 2200) {
id(iaq_index) += 1;
}
ESP_LOGD("main", "Current IAQ index %d", id(iaq_index));
return std::to_string(id(iaq_index));
update_interval: 60s
- platform: wifi_info
ip_address:
name: "${device_name} IP Address"
ssid:
name: "${device_name} Connected SSID"
bssid:
name: "${device_name} BSSID"
# Watchdog to auto-reboot if things go wrong
interval:
- interval: 5min
then:
- if:
condition:
and:
- lambda: 'return millis() > 300000;' # >5 min uptime
- not:
wifi.connected:
then:
- logger.log: "Wi-Fi not connected for 5 min; auto-reboot temporarily disabled for debugging"
- delay: 10s
+26
View File
@@ -0,0 +1,26 @@
- type: grid
column_span: 3
cards:
- type: heading
heading: Attic Climate
- type: markdown
content: >
🌡️ **Temperature**
{{ states('sensor.attic_climate_sensor_attic_climate_sensor_temperature') | float(0) | round(1) }} °F
💧 **Humidity**
{{ states('sensor.attic_climate_sensor_attic_climate_sensor_humidity') | float(0) | round(1) }} %
🌀 **Pressure**
{{ states('sensor.attic_climate_sensor_attic_climate_sensor_barometric_pressure') | float(0) | round(2) }} inHg
🔋 **Battery**
{{ states('sensor.attic_climate_sensor_attic_climate_sensor_battery_percentage') | int(0) }}%
🕐 **Last updated**
{{ states.sensor.attic_climate_sensor_attic_climate_sensor_temperature.last_changed | as_timestamp | timestamp_custom('%-I:%M %p, %b %-d') }}
+246
View File
@@ -0,0 +1,246 @@
# ============================================================
# Attic Climate Sensor — ESP32-C3 SuperMini (Battery-Powered)
# Board: ESP32-C3 SuperMini (RISC-V, single-core, 160MHz)
# Sensor: AHT20 + BMP280 combo (I2C)
# Battery: 18650 Li-Ion
# Schedule: Wake every 6 hours, report to HA, deep sleep
# OTA: Hold GPIO3 LOW (jumper to GND) to prevent deep sleep
# ============================================================
#
# WIRING:
# AHT20/BMP280 Combo Board:
# SDA → GPIO4
# SCL → GPIO5
# VCC → 3.3V
# GND → GND
#
# Battery Voltage Monitor (voltage divider):
# BAT+ → 100kΩ → GPIO0 (ADC) → 100kΩ → GND
#
# OTA Stay-Awake Jumper:
# GPIO3 → 2-pin header → GND (bridge to stay awake)
#
# NOTES:
# - GPIO8 = onboard blue LED (active LOW / inverted)
# - GPIO9 = BOOT button (avoid for general use)
# - GPIO2, GPIO10 = strapping pins (avoid)
# - All GPIO are 3.3V logic only
# ============================================================
substitutions:
device_name: "attic-climate-sensor"
friendly_name: "Attic Climate Sensor"
# Deep sleep duration: 6 hours = 21600 seconds
sleep_duration: "21600s"
# Max awake time before forced sleep (safety net)
run_duration: "60s"
esphome:
name: ${device_name}
friendly_name: ${friendly_name}
on_boot:
priority: -10
then:
- wait_until:
condition:
api.connected:
timeout: 45s
# Give sensors time to stabilize and publish
- delay: 5s
- if:
condition:
binary_sensor.is_on: ota_stay_awake
then:
- logger.log: "OTA jumper detected — staying awake for maintenance"
- deep_sleep.prevent: deep_sleep_control
- light.turn_on: onboard_led
else:
- logger.log: "Publishing complete — entering deep sleep"
- deep_sleep.enter: deep_sleep_control
esp32:
board: esp32-c3-devkitm-1
variant: esp32c3
framework:
type: arduino
# ── Logging ────────────────────────────────────────────────
logger:
level: DEBUG
# The C3 SuperMini uses native USB — hardware UART is on GPIO20/21
# For production, reduce to save power:
# level: WARN
# ── Network ────────────────────────────────────────────────
wifi:
ssid: !secret wifi_iot_ssid
password: !secret wifi_password
# Static IP dramatically reduces WiFi connect time (~3-5s saved per wake)
manual_ip:
static_ip: !secret attic_sensor_ip
gateway: !secret gateway
subnet: !secret subnet
dns1: !secret dns1
fast_connect: true
power_save_mode: none
ap:
ssid: "${device_name}-fallback"
password: !secret wifi_password
captive_portal:
# ── API & OTA ──────────────────────────────────────────────
api:
encryption:
key: !secret api_encryption_key
ota:
- platform: esphome
password: !secret ota_password
# ── I2C Bus ────────────────────────────────────────────────
# AHT20: 0x38 | BMP280: 0x77 (or 0x76 depending on board)
# Using GPIO4 (SDA) and GPIO5 (SCL) — safe general-purpose pins
i2c:
sda: GPIO4
scl: GPIO5
scan: true
# ── Deep Sleep ─────────────────────────────────────────────
deep_sleep:
id: deep_sleep_control
run_duration: ${run_duration}
sleep_duration: ${sleep_duration}
# ── Status LED ─────────────────────────────────────────────
# Onboard blue LED on GPIO8 (active LOW / inverted)
output:
- platform: gpio
pin:
number: GPIO8
inverted: true
id: blue_led_output
light:
- platform: binary
name: "${friendly_name} Status LED"
output: blue_led_output
id: onboard_led
entity_category: diagnostic
# ── Binary Sensors ─────────────────────────────────────────
binary_sensor:
# OTA Stay-Awake Jumper
# Wire a 2-pin header from GPIO3 → GND
# Bridge jumper = stay awake for OTA | Remove = normal deep sleep
- platform: gpio
pin:
number: GPIO3
mode:
input: true
pullup: true
inverted: true # LOW (jumper bridged) = ON = stay awake
name: "${friendly_name} OTA Stay Awake"
id: ota_stay_awake
entity_category: diagnostic
on_press:
- logger.log: "OTA jumper enabled — preventing deep sleep"
- deep_sleep.prevent: deep_sleep_control
- light.turn_on: onboard_led
on_release:
- logger.log: "OTA jumper removed — allowing deep sleep"
- deep_sleep.allow: deep_sleep_control
- light.turn_off: onboard_led
# ── Sensors ────────────────────────────────────────────────
sensor:
# ── AHT20: Temperature & Humidity ──
- platform: aht10
variant: AHT20
temperature:
name: "${friendly_name} Temperature"
unit_of_measurement: "°F"
accuracy_decimals: 1
filters:
- lambda: return x * 9.0f / 5.0f + 32.0f;
- sliding_window_moving_average:
window_size: 3
send_every: 1
humidity:
name: "${friendly_name} Humidity"
accuracy_decimals: 1
filters:
- sliding_window_moving_average:
window_size: 3
send_every: 1
update_interval: 5s
# ── BMP280: Barometric Pressure (+ backup temp) ──
- platform: bmp280_i2c
address: 0x77 # Change to 0x76 if your board uses that
temperature:
name: "${friendly_name} BMP280 Temperature"
unit_of_measurement: "°F"
accuracy_decimals: 1
filters:
- lambda: return x * 9.0f / 5.0f + 32.0f;
disabled_by_default: true
pressure:
name: "${friendly_name} Barometric Pressure"
accuracy_decimals: 1
unit_of_measurement: "inHg"
filters:
- multiply: 0.0295299831
update_interval: 5s
# ── Battery Voltage via ADC ──
# Voltage divider: 100kΩ + 100kΩ from BAT+ to GND, midpoint → GPIO0
# GPIO0 is ADC1_CH0 on the C3 — safe for ADC use
# 2:1 divider ratio — multiply by 2 to get actual battery voltage
- platform: adc
pin: GPIO0
name: "${friendly_name} Battery Voltage"
id: battery_voltage
accuracy_decimals: 2
update_interval: 5s
attenuation: 11db
filters:
- multiply: 2.0
# ── Battery Percentage (estimated) ──
# 18650 range: ~3.0V (empty) to ~4.2V (full)
- platform: template
name: "${friendly_name} Battery Percentage"
unit_of_measurement: "%"
device_class: battery
accuracy_decimals: 0
update_interval: 5s
lambda: |-
float voltage = id(battery_voltage).state;
if (voltage >= 4.2) return 100.0;
if (voltage <= 3.0) return 0.0;
return (voltage - 3.0) / (4.2 - 3.0) * 100.0;
# ── WiFi Signal ──
- platform: wifi_signal
name: "${friendly_name} WiFi Signal"
update_interval: 10s
entity_category: diagnostic
# ── Text Sensors ───────────────────────────────────────────
text_sensor:
- platform: wifi_info
ip_address:
name: "${friendly_name} IP Address"
entity_category: diagnostic
# ── Switches ───────────────────────────────────────────────
switch:
# Manual deep sleep trigger from HA (for testing)
- platform: template
name: "${friendly_name} Force Sleep"
icon: "mdi:sleep"
entity_category: config
turn_on_action:
- deep_sleep.enter: deep_sleep_control
+347
View File
@@ -0,0 +1,347 @@
substitutions:
device_name: cat-medication-tracker
friendly_name: "Cat Medication Tracker"
esphome:
name: ${device_name}
friendly_name: ${friendly_name}
includes:
- spi_helper.h
on_boot:
- priority: 100
then:
- light.turn_on: backlight
- priority: -100
then:
- lambda: |-
// MADCTL 0x48: MY=0, MX=1, BGR=1 — correct portrait, no mirror for ESP32-2432S035R
// ESP-IDF SPI master API used to bypass ESPHome's buffered display layer
spi_device_handle_t disp_fix;
spi_device_interface_config_t cfg = {};
cfg.clock_speed_hz = 1000000;
cfg.mode = 0;
cfg.spics_io_num = -1; // manual CS
cfg.queue_size = 1;
if (spi_bus_add_device(SPI2_HOST, &cfg, &disp_fix) == ESP_OK) {
gpio_set_level((gpio_num_t)15, 0); // CS low
gpio_set_level((gpio_num_t)2, 0); // DC = command
spi_transaction_t t = {};
t.length = 8;
t.flags = SPI_TRANS_USE_TXDATA;
t.tx_data[0] = 0x36; // MADCTL register
spi_device_polling_transmit(disp_fix, &t);
gpio_set_level((gpio_num_t)2, 1); // DC = data
t.tx_data[0] = 0x48; // MY=0, MX=1, BGR=1
spi_device_polling_transmit(disp_fix, &t);
gpio_set_level((gpio_num_t)15, 1); // CS high
spi_bus_remove_device(disp_fix);
}
- component.update: my_display
esp32:
board: esp32dev
framework:
type: arduino
logger:
level: INFO
api:
encryption:
key: !secret api_encryption_key
ota:
platform: esphome
password: !secret ota_password
wifi:
ssid: !secret wifi_iot_ssid
password: !secret wifi_password
manual_ip:
static_ip: 192.168.69.230
gateway: 192.168.69.1
subnet: 255.255.255.0
ap:
ssid: "${device_name}-fallback"
password: !secret fallback_password
# Prevents aggressive reconnection attempts
power_save_mode: none # Use 'light' for battery devices
# Slower but more reliable connection
fast_connect: true
# Handle connection failures gracefully
on_connect:
- logger.log: "Wi-Fi connected!"
on_disconnect:
- logger.log: "Wi-Fi disconnected!"
# Reduce mDNS traffic
mdns:
disabled: true # Set to true if you use static IPs and don't need discovery
captive_portal:
web_server:
port: 80
time:
- platform: homeassistant
id: homeassistant_time
on_time:
- seconds: 0
minutes: 0
hours: 0
then:
- switch.turn_off: penelope_medicated
- switch.turn_off: tess_medicated
- script.execute: update_display
spi:
- id: tft_spi
clk_pin: GPIO14
mosi_pin: GPIO13
miso_pin: GPIO12
display:
- platform: mipi_spi
model: ILI9488
spi_id: tft_spi
cs_pin: GPIO15
dc_pin: GPIO2
reset_pin: GPIO4
rotation: 0
invert_colors: false
color_order: bgr
data_rate: 10MHz
dimensions:
width: 320
height: 480
id: my_display
auto_clear_enabled: false
update_interval: 2s
color_depth: 16
buffer_size: 25%
lambda: |-
// Colors
auto red = Color(255, 0, 0);
auto green = Color(0, 200, 0);
auto light_grey = Color(200, 200, 200);
auto white = Color(255, 255, 255);
auto black = Color(0, 0, 0);
auto dark_grey = Color(80, 80, 80);
// Fill background
it.fill(light_grey);
// Border: green if all done, red otherwise
bool all_done = id(penelope_medicated).state && id(tess_medicated).state;
auto border_color = all_done ? green : red;
int border = 10;
it.filled_rectangle(0, 0, 320, border, border_color);
it.filled_rectangle(0, 480 - border, 320, border, border_color);
it.filled_rectangle(0, 0, border, 480, border_color);
it.filled_rectangle(320 - border, 0, border, 480, border_color);
// Title
it.printf(160, 30, id(title_font), black, TextAlign::TOP_CENTER, "Cat Meds");
// Penelope button
int btn_x = 40;
int btn_y = 90;
int btn_w = 240;
int btn_h = 120;
auto penelope_color = id(penelope_medicated).state ? green : red;
it.filled_rectangle(btn_x, btn_y, btn_w, btn_h, penelope_color);
it.rectangle(btn_x, btn_y, btn_w, btn_h, dark_grey);
it.printf(btn_x + btn_w/2, btn_y + btn_h/2, id(button_font), white, TextAlign::CENTER, "Penelope");
if (id(penelope_medicated).state) {
it.printf(btn_x + btn_w/2, btn_y + btn_h - 20, id(status_font), white, TextAlign::CENTER, "DONE");
}
// Tess button
btn_y = 230;
auto tess_color = id(tess_medicated).state ? green : red;
it.filled_rectangle(btn_x, btn_y, btn_w, btn_h, tess_color);
it.rectangle(btn_x, btn_y, btn_w, btn_h, dark_grey);
it.printf(btn_x + btn_w/2, btn_y + btn_h/2, id(button_font), white, TextAlign::CENTER, "Tess");
if (id(tess_medicated).state) {
it.printf(btn_x + btn_w/2, btn_y + btn_h - 20, id(status_font), white, TextAlign::CENTER, "DONE");
}
// Reset button
int reset_x = 110;
int reset_y = 395;
int reset_w = 100;
int reset_h = 55;
it.filled_rectangle(reset_x, reset_y, reset_w, reset_h, dark_grey);
it.rectangle(reset_x, reset_y, reset_w, reset_h, black);
it.printf(reset_x + reset_w/2, reset_y + reset_h/2, id(status_font), white, TextAlign::CENTER, "RESET");
# XPT2046 Touchscreen
touchscreen:
- platform: xpt2046
id: my_touchscreen
spi_id: tft_spi
cs_pin: GPIO33
update_interval: 250ms
threshold: 1200
calibration:
x_min: 280
x_max: 3850
y_min: 340
y_max: 3860
transform:
mirror_y: false
# Touch buttons as binary sensors
binary_sensor:
- platform: touchscreen
touchscreen_id: my_touchscreen
name: "Penelope Button"
id: penelope_button
x_min: 40
x_max: 280
y_min: 230
y_max: 350
on_press:
then:
- switch.toggle: penelope_medicated
- platform: touchscreen
touchscreen_id: my_touchscreen
name: "Tess Button"
id: tess_button
x_min: 40
x_max: 280
y_min: 90
y_max: 210
on_press:
then:
- switch.toggle: tess_medicated
- platform: touchscreen
touchscreen_id: my_touchscreen
name: "Reset Button"
id: reset_button
x_min: 110
x_max: 210
y_min: 10 #395
y_max: 50 #450
on_press:
then:
- switch.turn_off: penelope_medicated
- switch.turn_off: tess_medicated
- platform: template
name: "Penelope Medication Status"
lambda: 'return id(penelope_medicated).state;'
device_class: running
- platform: template
name: "Tess Medication Status"
lambda: 'return id(tess_medicated).state;'
device_class: running
- platform: template
name: "All Cats Medicated"
lambda: 'return id(penelope_medicated).state && id(tess_medicated).state;'
device_class: running
# Backlight control
output:
- platform: gpio
pin: GPIO27
id: backlight_pwm
inverted: false
light:
- platform: binary
output: backlight_pwm
name: "${friendly_name} Backlight"
id: backlight
restore_mode: ALWAYS_ON
# Medication state switches (exposed to Home Assistant)
switch:
- platform: template
name: "Penelope Medicated"
id: penelope_medicated
optimistic: true
restore_mode: RESTORE_DEFAULT_OFF
on_turn_on:
- script.execute: update_display
on_turn_off:
- script.execute: update_display
- platform: template
name: "Tess Medicated"
id: tess_medicated
optimistic: true
restore_mode: RESTORE_DEFAULT_OFF
on_turn_on:
- script.execute: update_display
on_turn_off:
- script.execute: update_display
- platform: template
name: "Reset All Medications"
id: reset_all
optimistic: false
turn_on_action:
- switch.turn_off: penelope_medicated
- switch.turn_off: tess_medicated
# Script to update display
script:
- id: update_display
then:
- component.update: my_display
# Fonts
font:
- file: "gfonts://Roboto"
id: title_font
size: 28
- file: "gfonts://Roboto"
id: button_font
size: 24
- file: "gfonts://Roboto"
id: status_font
size: 14
# Diagnostic sensors
sensor:
- platform: wifi_signal
name: "${device_name} WiFi Signal"
update_interval: 60s
- platform: uptime
name: "${device_name} Uptime"
update_interval: 60s
text_sensor:
- platform: wifi_info
ip_address:
name: "${device_name} IP Address"
ssid:
name: "${device_name} Connected SSID"
bssid:
name: "${device_name} BSSID"
# Watchdog to auto-reboot if things go wrong
interval:
- interval: 5min
then:
- if:
condition:
and:
- lambda: 'return millis() > 300000;' # >5 min uptime
- not:
wifi.connected:
then:
- logger.log: "Wi-Fi not connected for 5 min, rebooting..."
- delay: 10s
- lambda: 'App.safe_reboot();'
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,115 @@
################################################################################
# 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') ~ '/4 chores done'
}}
- type: markdown
content: >
### 󱑷 Declan
{{
'All done!' if states('sensor.declan_all_chores_done') == 'True'
else states('sensor.declan_chores_done_today') ~ '/2 chores done'
}}
- type: markdown
content: >
### 󱚣 Chloe
{{
'All done!' if states('sensor.chloe_all_chores_done') == 'True'
else states('sensor.chloe_chores_done_today') ~ '/3 chores done'
}}
- type: button
name: "Reset ALL Chores"
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_put_on_underwear
name: "󰋣 Put on underwear"
- entity: switch.chore_tracker_jordyn_brush_teeth
name: "󰦩 Brush Teeth"
- entity: switch.chore_tracker_jordyn_fill_water_bowls
name: "󰆫 Fill water bowls"
- entity: switch.chore_tracker_jordyn_restock_cat_food_cans
name: "󱜜 Restock Cat Food Cans"
- type: divider
- entity: sensor.jordyn_chores_done_today
name: "Chores done today"
- entity: sensor.jordyn_all_chores_done
name: "All Done?"
- 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_take_morning_pill
name: "󰐂 Take morning pill"
- entity: switch.chore_tracker_declan_scoop_dog_poop
name: "󰇷 Scoop Dog Poop"
- type: divider
- entity: sensor.declan_chores_done_today
name: "Chores done today"
- entity: sensor.declan_all_chores_done
name: "All Done?"
- 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_fill_kitty_feeders
name: "󰊩 Fill kitty feeders"
- entity: switch.chore_tracker_chloe_scoop_kitty_litter
name: "󰇷 Scoop Kitty Litter"
- entity: switch.chore_tracker_chloe_replace_kitty_litter_bags
name: "󰇷 Replace Kitty Litter Bags"
- type: divider
- entity: sensor.chloe_chores_done_today
name: "Chores done today"
- entity: sensor.chloe_all_chores_done
name: "All Done?"
- type: button
name: "Reset Chloe's Chores"
tap_action:
action: call-service
service: input_button.press
target:
entity_id: input_button.chloe_reset_chores
+81
View File
@@ -0,0 +1,81 @@
################################################################################
# chores_config.yaml — YOUR SINGLE SOURCE OF TRUTH
#
# Edit this file to change kids, chores, and settings.
# Then run: python3 generate.py
#
# ICONS: Use MDI codepoints from https://pictogrammers.com/library/mdi/
# Format: "\U000FXXXX" e.g. mdi:bed = "\U000F02E3"
# The kid avatar field also uses MDI codepoints.
#
# Common chore icons:
# Bed: "\U000F02E3" mdi:bed
# Brush teeth: "\U000F09A9" mdi:toothbrush
# Broom: "\U000F00A8" mdi:broom
# Book: "\U000F0219" mdi:book-open-variant
# Dog: "\U000F01F9" mdi:dog
# Paw: "\U000F0265" mdi:paw
# Silverware: "\U000F03E7" mdi:silverware-fork-knife
# Trash: "\U000F05B8" mdi:trash-can
# Watering can: "\U000F1B25" mdi:watering-can
# Piano: "\U000F0F9E" mdi:piano
# Dumbbell: "\U000F0F1E" mdi:dumbbell
# Shower: "\U000F0467" mdi:shower
################################################################################
settings:
device_name: chore-tracker
friendly_name: "Chore Tracker"
wifi_ssid: !secret wifi_iot_ssid
wifi_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
static_ip: 192.168.69.115
gateway: 192.168.69.1
subnet: 255.255.255.0
# ── KIDS ──────────────────────────────────────────────────────────────────────
# avatar: MDI codepoint shown in the sidebar on the kid's chore page
# color / color_dark: hex colours for that kid's sidebar
#
kids:
- name: Jordyn
avatar: "\U000F011B" # mdi:cat
color: "5F5980" # #5F5980 is a nice light blue, but it's a bit hard to read when used as a background colour for the sidebar. So I use a darker version of the same colour for the sidebar background.
color_dark: "2F3061" # #2F3061 is a darker version of #5F5980, and is easier to read when used as a background colour for the sidebar.
chores:
- name: Put on underwear
icon: "\U000F02E3" # mdi:bed
- name: Brush Teeth
icon: "\U000F09A9" # mdi:toothbrush
- name: Fill water bowls
icon: "\U000F01AB" # mdi:cup-water
- name: Restock Cat Food Cans
icon: "\U000F171C" # mdi:food-turkey
- name: Declan
avatar: "\U000F1477" # mdi:wizard-hat
color: "720026" # #720026 is a deep red, used for the kid's sidebar background.
color_dark: "4F000B" # #4F000B is a darker version of #720026, easier to read when used as a background colour for the sidebar.
chores:
- name: Take morning pill
icon: "\U000F0402" # mdi:pill
- name: Scoop Dog Poop
icon: "\U000F01F7" # mdi:emoticon-poop
- name: Chloe
avatar: "\U000F16A3" # mdi:robot-excited
color: "73683B" # #73683B is a light brown, used for the kid's sidebar background.
color_dark: "583E23" # #583E23 is a darker version of #73683B, easier to read when used as a background colour for the sidebar.
chores:
- name: Fill kitty feeders
icon: "\U000F02A9" # mdi:bowl-outline
- name: Scoop Kitty Litter
icon: "\U000F01F7" # mdi:emoticon-poop
- name: Replace Kitty Litter Bags
icon: "\U000F01F7" # mdi:emoticon-poop
File diff suppressed because it is too large Load Diff
+1
View File
@@ -0,0 +1 @@
CODEOWNERS = ["@blacknell"]
+20
View 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
+96
View File
@@ -0,0 +1,96 @@
#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...");
uint16_t config_reg;
if (this->write(&MAX17043_CONFIG, 1) != i2c::ERROR_OK) {
this->status_set_warning();
return;
}
if (this->read(reinterpret_cast<uint8_t *>(&config_reg), 2) != i2c::ERROR_OK) {
this->status_set_warning();
return;
}
config_reg = i2c::i2ctohs(config_reg) & MAX17043_CONFIG_SAFE_MASK;
ESP_LOGV(TAG, "MAX17043 CONFIG register reads 0x%X", config_reg);
if (config_reg != MAX17043_CONFIG_POWER_UP_DEFAULT) {
ESP_LOGW(TAG, "Non-standard CONFIG register 0x%X at 0x36; continuing in compatible mode", config_reg);
return;
}
// need to write back to config register to reset the sleep bit
if (!this->write_byte_16(MAX17043_CONFIG, MAX17043_CONFIG_POWER_UP_DEFAULT)) {
this->status_set_error("sleep reset failed");
this->mark_failed();
return;
}
}
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
View 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
View 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)
+122
View File
@@ -0,0 +1,122 @@
substitutions:
device_name: declan-a1-camera
friendly_name: "Declan's A1 Camera"
esphome:
name: ${device_name}
friendly_name: ${friendly_name}
esp32:
board: esp32dev
framework:
type: esp-idf
logger:
api:
encryption:
key: !secret api_encryption_key
ota:
- platform: esphome
password: !secret ota_password
wifi:
ssid: !secret wifi_iot_ssid
password: !secret wifi_password
manual_ip:
static_ip: 192.168.69.220
gateway: 192.168.69.1
subnet: 255.255.255.0
ap:
ssid: "${friendly_name} Fallback"
password: !secret fallback_password
# Prevents aggressive reconnection attempts
power_save_mode: none # Use 'light' for battery devices
# Slower but more reliable connection
fast_connect: true
# Handle connection failures gracefully
on_connect:
- logger.log: "Wi-Fi connected!"
on_disconnect:
- logger.log: "Wi-Fi disconnected!"
# Reduce mDNS traffic
mdns:
disabled: true # Set to true if you use static IPs and don't need discovery
# Diagnostic sensors
sensor:
- platform: wifi_signal
name: "${device_name} WiFi Signal"
update_interval: 60s
- platform: uptime
name: "${device_name} Uptime"
update_interval: 60s
text_sensor:
- platform: wifi_info
ip_address:
name: "${device_name} IP Address"
ssid:
name: "${device_name} Connected SSID"
bssid:
name: "${device_name} BSSID"
# Watchdog to auto-reboot if things go wrong
interval:
- interval: 5min
then:
- if:
condition:
and:
- lambda: 'return millis() > 300000;' # >5 min uptime
- not:
wifi.connected:
then:
- logger.log: "Wi-Fi not connected for 5 min, rebooting..."
- delay: 10s
- lambda: 'App.safe_reboot();'
captive_portal:
i2c:
- id: camera_i2c
sda: GPIO26
scl: GPIO27
psram:
mode: quad
speed: 80MHz
esp32_camera:
external_clock:
pin: GPIO0
frequency: 20MHz
i2c_id: camera_i2c
data_pins: [GPIO5, GPIO18, GPIO19, GPIO21, GPIO36, GPIO39, GPIO34, GPIO35]
vsync_pin: GPIO25
href_pin: GPIO23
pixel_clock_pin: GPIO22
power_down_pin: GPIO32
resolution: 1024x768
jpeg_quality: 15
vertical_flip: true
horizontal_mirror: false
max_framerate: 20 fps
idle_framerate: 0.05 fps
name: ${device_name}
esp32_camera_web_server:
- port: 8080
mode: STREAM
- port: 8081
mode: SNAPSHOT
switch:
- platform: gpio
name: "${device_name}-flash"
pin: 4
+146
View File
@@ -0,0 +1,146 @@
substitutions:
device_name: esp32-h2c-camera
friendly_name: "H2C Camera"
esphome:
name: ${device_name}
friendly_name: ${friendly_name}
esp32:
board: esp32-s3-devkitc-1
framework:
type: esp-idf
# Required for ESP32 camera buffering.
psram:
mode: octal
logger:
api:
encryption:
key: !secret api_encryption_key
ota:
- platform: esphome
password: !secret ota_password
wifi:
ssid: !secret wifi_iot_ssid
password: !secret wifi_password
manual_ip:
static_ip: 192.168.69.90
gateway: 192.168.69.1
subnet: 255.255.255.0
ap:
ssid: "${friendly_name} Fallback"
password: !secret fallback_password
# Prevents aggressive reconnection attempts
power_save_mode: none # Use 'light' for battery devices
# Slower but more reliable connection
fast_connect: true
# Handle connection failures gracefully
on_connect:
- logger.log: "Wi-Fi connected!"
on_disconnect:
- logger.log: "Wi-Fi disconnected!"
# Reduce mDNS traffic
mdns:
disabled: true # Set to true if you use static IPs and don't need discovery
# captive_portal:
web_server:
port: 80
i2c:
- id: camera_i2c
sda: GPIO8
scl: GPIO9
scan: false
esp32_camera:
name: "${friendly_name}"
external_clock:
pin: GPIO5
frequency: 20MHz
i2c_id: camera_i2c
data_pins: [GPIO16, GPIO18, GPIO21, GPIO17, GPIO14, GPIO7, GPIO6, GPIO4]
vsync_pin: GPIO1
href_pin: GPIO2
pixel_clock_pin: GPIO15
# Start conservative for reliability; increase later if stable.
resolution: 800X600
jpeg_quality: 15
max_framerate: 20 fps
idle_framerate: 0.2 fps
frame_buffer_count: 1
# Common orientation defaults for this module.
vertical_flip: true
horizontal_mirror: false
brightness: 1
# Camera endpoints:
# Stream: http://<ip>:8080
# Snapshot: http://<ip>:8081
esp32_camera_web_server:
- port: 8080
mode: stream
- port: 8081
mode: snapshot
switch:
# DFRobot board IR illumination control pin.
- platform: gpio
name: "${friendly_name} IR LED"
pin: GPIO47
restore_mode: ALWAYS_OFF
sensor:
- platform: wifi_signal
name: "${friendly_name} WiFi Signal"
update_interval: 60s
- platform: uptime
name: "${friendly_name} Uptime"
- platform: wifi_signal
name: "${device_name} WiFi Signal"
update_interval: 60s
- platform: uptime
name: "${device_name} Uptime"
update_interval: 60s
button:
- platform: restart
name: "${friendly_name} Restart"
text_sensor:
- platform: wifi_info
ip_address:
name: "${device_name} IP Address"
ssid:
name: "${device_name} Connected SSID"
bssid:
name: "${device_name} BSSID"
# Watchdog to auto-reboot if things go wrong
interval:
- interval: 5min
then:
- if:
condition:
and:
- lambda: 'return millis() > 300000;' # >5 min uptime
- not:
wifi.connected:
then:
- logger.log: "Wi-Fi not connected for 5 min, rebooting..."
- delay: 10s
- lambda: 'App.safe_reboot();'
+140
View File
@@ -0,0 +1,140 @@
substitutions:
device_name: saturn4-printer-cam
hostname: "Saturn 4 Printer Cam"
friendly_name: "Saturn 4 Printer Cam"
device_description: ESP32-CAM module in Saturn 4 Ultra Resin printer.
esphome:
name: ${device_name}
friendly_name: ${friendly_name}
esp32:
board: esp32dev
framework:
type: esp-idf
# Enable logging
logger:
# Enable Home Assistant API
api:
encryption:
key: "3Zxo91fAfA7wpZ4DAYFxGarCRIGQr+0rUJH2taJo7ds="
ota:
- platform: esphome
password: "d2e50b2ad36ed7de4ecbfd90725765e3"
wifi:
ssid: !secret wifi_iot_ssid
password: !secret wifi_password
# Enable fallback hotspot (captive portal) in case wifi connection fails
ap:
ssid: "Esp32-Saturn4-Cam"
password: "klRzTYuqPJXm"
# Prevents aggressive reconnection attempts
power_save_mode: none # Use 'light' for battery devices
# Slower but more reliable connection
fast_connect: true
# Handle connection failures gracefully
on_connect:
- logger.log: "Wi-Fi connected!"
on_disconnect:
- logger.log: "Wi-Fi disconnected!"
# Reduce mDNS traffic
mdns:
disabled: false # Set to true if you use static IPs and don't need discovery
# Diagnostic sensors
sensor:
- platform: wifi_signal
name: "${device_name} WiFi Signal"
update_interval: 60s
- platform: uptime
name: "${device_name} Uptime"
update_interval: 60s
text_sensor:
- platform: wifi_info
ip_address:
name: "${device_name} IP Address"
ssid:
name: "${device_name} Connected SSID"
bssid:
name: "${device_name} BSSID"
# Watchdog to auto-reboot if things go wrong
interval:
- interval: 5min
then:
- if:
condition:
and:
- lambda: 'return millis() > 300000;' # >5 min uptime
- not:
wifi.connected:
then:
- logger.log: "Wi-Fi not connected for 5 min, rebooting..."
- delay: 10s
- lambda: 'App.safe_reboot();'
captive_portal:
# Example configuration entry
i2c:
- id: camera_i2c
sda: GPIO26
scl: GPIO27
psram:
mode: quad
speed: 80MHz
# Flashlight # you can control this flashlight within Homeassistant or mqtt to shine on the birds. use it with an timer!
output:
- platform: ledc
pin: GPIO4
id: gpio_4
channel: 2
## GPIO_4 is the flash light pin
light:
- platform: monochromatic
output: gpio_4
name: $hostname light
restore_mode: RESTORE_AND_OFF
icon: mdi:flash
esp32_camera:
external_clock:
pin: GPIO0
frequency: 20MHz
i2c_id: camera_i2c
data_pins: [GPIO5, GPIO18, GPIO19, GPIO21, GPIO36, GPIO39, GPIO34, GPIO35]
vsync_pin: GPIO25
href_pin: GPIO23
pixel_clock_pin: GPIO22
power_down_pin: GPIO32
resolution: 640x480 # you can use: [1600x1200 , 1280x1024 , 1024x768 , 800x600, 640x480 ] for best performance (FPS) use 1024 of lower resolution.
jpeg_quality: 12 #The JPEG quality that the camera should encode images with. From 10 (best) to 63 (worst)
aec2: true
ae_level: 2
brightness: 2 #The brightness to apply to the picture, default 0
contrast: 2
saturation: -2
vertical_flip: False
# Image settings
name: ${device_name}
# ...
esp32_camera_web_server:
- port: 80
mode: STREAM
- port: 8080
mode: SNAPSHOT
+152
View File
@@ -0,0 +1,152 @@
substitutions:
device_name: salt-sensor
friendly_name: "Salt Level Sensor"
esphome:
name: ${device_name}
friendly_name: ${friendly_name}
esp32:
board: esp32-c3-devkitm-1
framework:
type: arduino
# Enable logging
logger:
# Enable Home Assistant API
api:
encryption:
key: "EaifDkFuB9N+qDKHW6B4k/n495FWLVmcp6DBODKzK10="
ota:
- platform: esphome
password: "7ce613915c51252c2810c07c889b4a5e"
wifi:
ssid: !secret wifi_iot_ssid
password: !secret wifi_password
# Enable fallback hotspot (captive portal) in case wifi connection fails
ap:
ssid: "Salt-Sensor Fallback Hotspot"
password: "ZoBRDcj54rwW"
# Prevents aggressive reconnection attempts
power_save_mode: none # Use 'light' for battery devices
# Slower but more reliable connection
fast_connect: true
# Handle connection failures gracefully
on_connect:
- logger.log: "Wi-Fi connected!"
on_disconnect:
- logger.log: "Wi-Fi disconnected!"
# Reduce mDNS traffic
mdns:
disabled: false # Set to true if you use static IPs and don't need discovery
# Watchdog to auto-reboot if things go wrong
interval:
- interval: 5min
then:
- if:
condition:
and:
- lambda: 'return millis() > 300000;' # >5 min uptime
- not:
wifi.connected:
then:
- logger.log: "Wi-Fi not connected for 5 min, rebooting..."
- delay: 10s
- lambda: 'App.safe_reboot();'
captive_portal:
# Enable Web server.
web_server:
port: 80
# Sync time with Home Assistant.
time:
- platform: homeassistant
id: homeassistant_time
# Text sensors with general information.
text_sensor:
# Expose ESPHome version as sensor.
- platform: version
name: salt_level_sensor ESPHome Version
# Expose WiFi information as sensors.
- platform: wifi_info
ip_address:
name: salt_level_sensor IP
ssid:
name: salt_level_sensor SSID
bssid:
name: salt_level_sensor BSSID
sensor:
# Uptime sensor.
- platform: uptime
name: salt_level_sensor Uptime
# WiFi Signal sensor.
- platform: wifi_signal
name: salt_level_sensor WiFi Signal
update_interval: 60s
# Diagnostic sensors
- platform: wifi_signal
name: "${device_name} WiFi Signal"
update_interval: 60s
- platform: uptime
name: "${device_name} Uptime"
update_interval: 60s
# Ultrasonic sensor to measure salt level.
# - platform: ultrasonic
# trigger_pin: GPIO6
# echo_pin: GPIO7
# name: "Salt level in percent"
# update_interval: 1h
# unit_of_measurement: "%"
# # pulse_time: 20us
# # accuracy_decimals: 4
# filters:
# - lambda: return(0.42-x)*(100/0.42);
# # - multiply: 100
# - exponential_moving_average:
# alpha: 0.001
# send_every: 1
# - clamp:
# min_value: 20
# max_value: 100
# on_value_range:
# above: 200
# then:
# - homeassistant.service:
# service: notify.mailgun
# data:
# target:
# -"joshua@cnjmail.com"
# title: "Add Salt"
# message: "Add Salt to the Water Softener Tank"
# - homeassistant.service:
# service: notify.mobile_app_joshuas_iphone_of_pain
# data:
# message: Water softener is low on salt, please add salt.
# Ultrasonic sensor to measure salt level.
- platform: ultrasonic
trigger_pin: GPIO6
echo_pin: GPIO7
name: "Salt level in cm"
update_interval: .5h
unit_of_measurement: "cm"
# pulse_time: 20us
# accuracy_decimals: 4
filters:
- lambda: return(0.83-x)*(100/0.83);
+38
View File
@@ -0,0 +1,38 @@
substitutions:
name: family-room-remote
friendly_name: "Family Room Remote"
packages:
base: !include family-room-remote/family-room-remote.base.yaml
ui: !include family-room-remote/family-room-remote.ui.yaml
bindings: !include family-room-remote/family-room-remote.bindings.yaml
esphome:
name: ${name}
friendly_name: ${friendly_name}
name_add_mac_suffix: false
esp32:
board: esp32-s3-devkitc-1
variant: ESP32S3
flash_size: 16MB
framework:
type: esp-idf
wifi:
ssid: !secret wifi_iot_ssid
password: !secret wifi_password
ap:
ssid: "${friendly_name} Fallback"
password: !secret fallback_password
api:
encryption:
key: !secret api_encryption_key
ota:
- platform: esphome
password: !secret ota_password
logger:
baud_rate: 0
@@ -0,0 +1,173 @@
psram:
mode: octal
speed: 80MHz
i2c:
id: i2c_main
sda: GPIO19
scl: GPIO20
frequency: 400kHz
scan: false
# --- Backlight control (GPIO2 direct) ---
switch:
- platform: gpio
name: "LCD Backlight Raw"
id: lcd_backlight_raw
restore_mode: ALWAYS_ON
pin:
number: GPIO2
mode:
output: true
light:
- platform: binary
name: "Family Room Remote Backlight"
output: lcd_backlight_out
id: ha_remote_backlight
restore_mode: ALWAYS_ON
output:
- platform: template
id: lcd_backlight_out
type: binary
write_action:
- if:
condition:
lambda: return state;
then:
- switch.turn_on: lcd_backlight_raw
else:
- switch.turn_off: lcd_backlight_raw
# --- Inactivity tracking (off after 2 minutes, wake on touch) ---
globals:
- id: last_activity_ms
type: uint32_t
restore_value: no
initial_value: '0'
interval:
- interval: 1s
then:
- lambda: |-
if (id(last_activity_ms) == 0) id(last_activity_ms) = millis();
- if:
condition:
lambda: |-
const uint32_t idle_s = (millis() - id(last_activity_ms)) / 1000;
return idle_s == 120;
then:
- light.turn_off: ha_remote_backlight
# --- Display (ST7262 parallel RGB, 800x480) ---
display:
- platform: rpi_dpi_rgb
id: main_display
dimensions:
width: 800
height: 480
de_pin: GPIO40
hsync_pin: GPIO39
vsync_pin: GPIO41
pclk_pin: GPIO42
pclk_frequency: 16MHz
color_order: BGR
data_pins:
red:
- GPIO45
- GPIO48
- GPIO47
- GPIO21
- GPIO14
green:
- GPIO5
- GPIO6
- GPIO7
- GPIO15
- GPIO16
- GPIO4
blue:
- GPIO8
- GPIO3
- GPIO46
- GPIO9
- GPIO1
hsync_back_porch: 48
hsync_front_porch: 40
hsync_pulse_width: 48
vsync_back_porch: 32
vsync_front_porch: 13
vsync_pulse_width: 3
update_interval: never
auto_clear_enabled: false
# --- Touch (GT911 capacitive) ---
# NOTE: interrupt_pin GPIO18 requires soldering the R17 bridge on the board.
# If not soldered, remove the interrupt_pin line (falls back to polling).
touchscreen:
platform: gt911
id: touch_panel
i2c_id: i2c_main
reset_pin: GPIO38
on_touch:
then:
- lambda: |-
id(last_activity_ms) = millis();
- light.turn_on: ha_remote_backlight
# --- MDI Icon Font ---
font:
- file: "https://raw.githubusercontent.com/Templarian/MaterialDesign-Webfont/master/fonts/materialdesignicons-webfont.ttf"
id: mdi_icons
size: 24
bpp: 4
glyphs:
- "\U000F0335" # mdi:lightbulb
- "\U000F0425" # mdi:power
- "\U000F0079" # mdi:battery
- "\U000F12A1" # mdi:battery-low
- "\U000F12A2" # mdi:battery-medium
- "\U000F12A3" # mdi:battery-high
- "\U000F12A4" # mdi:battery-charging-low
- "\U000F12A5" # mdi:battery-charging-medium
- "\U000F12A6" # mdi:battery-charging-high
- "\U000F0091" # mdi:battery-unknown
- "\U000F10CD" # mdi:battery-alert-variant-outline
# --- Battery fuel gauge (MAX17048 via I2C) ---
# DISABLED for testing - suspected display interference
# 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
# 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: |-
# ESP_LOGI("battery", "Level: %.0f%%", x);
# // Update the LVGL battery icon based on level
# lv_obj_t *lbl = id(battery_icon);
# if (x < 10)
# lv_label_set_text(lbl, "\U000F10CD"); // battery-alert-variant-outline
# else if (x < 30)
# lv_label_set_text(lbl, "\U000F12A1"); // battery-low
# else if (x < 70)
# lv_label_set_text(lbl, "\U000F12A2"); // battery-medium
# else
# lv_label_set_text(lbl, "\U000F12A3"); // battery-high
@@ -0,0 +1,59 @@
text_sensor:
- platform: wifi_info
ip_address:
name: "Family Room Remote IP"
id: ip_addr
- platform: homeassistant
id: ts_family_room_tv_stand
entity_id: light.family_room_tv_stand
internal: true
on_value:
then:
- lvgl.widget.update:
id: btn_family_room_tv_stand
state:
checked: !lambda return x == "on";
- lambda: |-
const lv_color_t text_color = (x == "on") ? lv_color_hex(0x001A33) : lv_color_hex(0xE3E2E6);
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));
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
id: ts_small_family_room_lamp
entity_id: light.small_family_room_lamp
internal: true
on_value:
then:
- lvgl.widget.update:
id: btn_small_family_room_lamp
state:
checked: !lambda return x == "on";
- lambda: |-
const lv_color_t text_color = (x == "on") ? lv_color_hex(0x22001F) : lv_color_hex(0xE3E2E6);
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));
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
id: ts_big_family_room_lamp
entity_id: switch.big_family_room_lamp
internal: true
on_value:
then:
- lvgl.widget.update:
id: btn_big_family_room_lamp
state:
checked: !lambda return x == "on";
- lambda: |-
const lv_color_t text_color = (x == "on") ? lv_color_hex(0x001F15) : lv_color_hex(0xE3E2E6);
auto *btn = id(btn_big_family_room_lamp);
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));
}
@@ -0,0 +1,231 @@
lvgl:
displays:
- main_display
touchscreens:
- touch_panel
disp_bg_color: 0x141218
style_definitions:
# ── Top App Bar ──
- id: md3_header
bg_color: 0x1D1B20
bg_opa: 100%
border_width: 0
radius: 0
pad_all: 0
shadow_width: 0
# ── Status Chip ──
- id: md3_chip
bg_color: 0x2B2930
bg_opa: 100%
border_color: 0x49454F
border_width: 1
border_opa: 100%
radius: 8
pad_all: 0
# ── Text Styles ──
- id: md3_title
text_color: 0xE3E2E6
text_opa: 100%
- id: md3_meta
text_color: 0xC4C6CF
text_opa: 100%
- id: md3_icon
text_color: 0xC4C6CF
text_opa: 100%
# ── Card Variants ──
- id: md3_card_blue
bg_color: 0x2B2930
bg_opa: 100%
border_color: 0x49454F
border_width: 1
border_opa: 100%
radius: 16
shadow_width: 0
- id: md3_card_pink
bg_color: 0x2B2930
bg_opa: 100%
border_color: 0x49454F
border_width: 1
border_opa: 100%
radius: 16
shadow_width: 0
- id: md3_card_green
bg_color: 0x2B2930
bg_opa: 100%
border_color: 0x49454F
border_width: 1
border_opa: 100%
radius: 16
shadow_width: 0
theme:
label:
text_color: 0xE3E2E6
button:
text_color: 0xE3E2E6
pages:
- id: home
widgets:
# ── Header ──
- obj:
x: 0
y: 0
width: 800
height: 64
styles: md3_header
widgets:
- label:
x: 24
y: 20
text_color: 0xE3E2E6
text: "Family Room"
# ── TV Stand ──
- button:
id: btn_family_room_tv_stand
x: 24
y: 80
width: 234
height: 300
checkable: true
styles: md3_card_blue
checked:
bg_color: 0xA8C7FA
bg_opa: 100%
border_color: 0xA8C7FA
border_opa: 100%
text_color: 0x001A33
on_click:
- homeassistant.service:
service: light.toggle
data:
entity_id: light.family_room_tv_stand
widgets:
- label:
x: 194
y: 16
styles: md3_icon
text_font: mdi_icons
text: "\U000F0335" # mdi:lightbulb
- label:
x: 18
y: 16
styles: md3_meta
text: "LIGHT"
- label:
x: 18
y: 56
styles: md3_title
text: "TV Stand"
- label:
x: 18
y: 90
styles: md3_meta
text: "Tap to toggle"
# ── Small Lamp ──
- button:
id: btn_small_family_room_lamp
x: 282
y: 80
width: 234
height: 300
checkable: true
styles: md3_card_pink
checked:
bg_color: 0xFFD7F1
bg_opa: 100%
border_color: 0xFFD7F1
border_opa: 100%
text_color: 0x22001F
on_click:
- homeassistant.service:
service: light.toggle
data:
entity_id: light.small_family_room_lamp
widgets:
- label:
x: 194
y: 16
styles: md3_icon
text_font: mdi_icons
text: "\U000F0335" # mdi:lightbulb
- label:
x: 18
y: 16
styles: md3_meta
text: "LIGHT"
- label:
x: 18
y: 56
styles: md3_title
text: "Small Lamp"
- label:
x: 18
y: 90
styles: md3_meta
text: "Tap to toggle"
# ── Standing Lamp ──
- button:
id: btn_big_family_room_lamp
x: 540
y: 80
width: 236
height: 300
checkable: true
styles: md3_card_green
checked:
bg_color: 0xB5CCBA
bg_opa: 100%
border_color: 0xB5CCBA
border_opa: 100%
text_color: 0x001F15
on_click:
- homeassistant.service:
service: light.toggle
data:
entity_id: light.living_room_lamp_1
widgets:
- label:
x: 196
y: 16
styles: md3_icon
text_font: mdi_icons
text: "\U000F0335" # mdi:lightbulb
- label:
x: 18
y: 16
styles: md3_meta
text: "LIGHT"
- label:
x: 18
y: 56
styles: md3_title
text: "Standing Lamp"
- label:
x: 18
y: 90
styles: md3_meta
text: "Tap to toggle"
# ── Status Bar ──
- obj:
x: 0
y: 400
width: 800
height: 80
styles: md3_header
widgets:
- obj:
x: 8
y: 20
width: 84
height: 40
styles: md3_chip
widgets:
- label:
id: battery_icon
align: center
text_font: mdi_icons
text_color: 0xC4C6CF
text: "\U000F10CD" # mdi:battery-alert-variant-outline
+132
View File
@@ -0,0 +1,132 @@
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
manual_ip:
static_ip: 192.168.69.110
gateway: 192.168.69.1
subnet: 255.255.255.0
ap:
ssid: "${friendly_name} Fallback"
password: !secret fallback_password
# Prevents aggressive reconnection attempts
power_save_mode: none # Use 'light' for battery devices
# Slower but more reliable connection
fast_connect: true
# Handle connection failures gracefully
on_connect:
- logger.log: "Wi-Fi connected!"
on_disconnect:
- logger.log: "Wi-Fi disconnected!"
# Reduce mDNS traffic
mdns:
disabled: true # Set to true if you use static IPs and don't need discovery
captive_portal:
web_server:
port: 80
# GPIO Outputs for fan control
# IMPORTANT: inverted: true because NPN transistor driver inverts the logic
# GPIO HIGH -> NPN ON -> Gate LOW -> Fan OFF
# GPIO LOW -> NPN OFF -> Gate HIGH (5V via pull-up) -> Fan ON
output:
- platform: gpio
pin:
number: GPIO3 # D1 on XIAO
inverted: true
id: fan_80mm_output
- platform: gpio
pin:
number: GPIO4 # D2 on XIAO
inverted: true
id: fan_120mm_output
# Fan entities for Home Assistant
fan:
- platform: binary
name: "80mm Exhaust Fan"
id: fan_80mm
output: fan_80mm_output
- platform: binary
name: "120mm Exhaust Fan"
id: fan_120mm
output: fan_120mm_output
# Optional: Add a switch to control both fans together
switch:
- platform: template
name: "All Fans"
id: all_fans
turn_on_action:
- fan.turn_on: fan_80mm
- fan.turn_on: fan_120mm
turn_off_action:
- fan.turn_off: fan_80mm
- fan.turn_off: fan_120mm
lambda: |-
return id(fan_80mm).state && id(fan_120mm).state;
# Diagnostic sensors
sensor:
- platform: wifi_signal
name: "${device_name} WiFi Signal"
update_interval: 60s
- platform: uptime
name: "${device_name} Uptime"
update_interval: 60s
text_sensor:
- platform: wifi_info
ip_address:
name: "${device_name} IP Address"
ssid:
name: "${device_name} Connected SSID"
bssid:
name: "${device_name} BSSID"
# Watchdog to auto-reboot if things go wrong
interval:
- interval: 5min
then:
- if:
condition:
and:
- lambda: 'return millis() > 300000;' # >5 min uptime
- not:
wifi.connected:
then:
- logger.log: "Wi-Fi not connected for 5 min, rebooting..."
- delay: 10s
- lambda: 'App.safe_reboot();'
File diff suppressed because it is too large Load Diff
@@ -0,0 +1 @@
CODEOWNERS = ["@blacknell"]
@@ -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
@@ -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
@@ -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
@@ -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)
+176
View File
@@ -0,0 +1,176 @@
psram:
mode: octal
speed: 80MHz
i2c:
id: i2c_main
sda: 8
scl: 9
frequency: 400kHz
scan: false
# CH422G I/O expander (Waveshare uses it for LCD reset/backlight/touch reset)
ch422g:
- id: ch422g_hub
i2c_id: i2c_main
# --- Backlight control (CH422G IO2 is common for Waveshare LCD BL) ---
switch:
- platform: gpio
name: "LCD Backlight Raw"
id: lcd_backlight_raw
restore_mode: ALWAYS_ON
pin:
ch422g: ch422g_hub
number: 2
mode:
output: true
# A nicer HA-exposed control (so you can also automate it from HA)
light:
- platform: binary
name: "HA Remote Backlight"
output: lcd_backlight_out
id: ha_remote_backlight
output:
- platform: template
id: lcd_backlight_out
type: binary
write_action:
- if:
condition:
lambda: return state;
then:
- switch.turn_on: lcd_backlight_raw
else:
- switch.turn_off: lcd_backlight_raw
# --- Inactivity tracking (dim/off + wake on touch) ---
globals:
- id: last_activity_ms
type: uint32_t
restore_value: no
initial_value: '0'
interval:
- interval: 1s
then:
- lambda: |-
if (id(last_activity_ms) == 0) id(last_activity_ms) = millis();
# Turn off backlight ONCE at 2 minutes idle.
- if:
condition:
lambda: |-
const uint32_t idle_s = (millis() - id(last_activity_ms)) / 1000;
return idle_s == 120;
then:
- light.turn_off: ha_remote_backlight
# --- Display ---
display:
- platform: mipi_rgb
model: ESP32-S3-TOUCH-LCD-7-800X480
id: main_display
update_interval: never
auto_clear_enabled: false
reset_pin:
ch422g: ch422g_hub
number: 3
mode:
output: true
# --- Touch ---
touchscreen:
platform: gt911
id: touch_panel
i2c_id: i2c_main
interrupt_pin: 4
reset_pin:
ch422g: ch422g_hub
number: 1
mode:
output: true
on_touch:
then:
- lambda: |-
id(last_activity_ms) = millis();
- light.turn_on: ha_remote_backlight
# --- MDI Icon Font ---
font:
- file: "https://raw.githubusercontent.com/Templarian/MaterialDesign-Webfont/master/fonts/materialdesignicons-webfont.ttf"
id: mdi_icons
size: 24
bpp: 4
glyphs:
# Tile icons
- "\U000F0335" # mdi:lightbulb
- "\U000F0425" # mdi:power
- "\U000F0426" # mdi:power-plug
- "\U000F07E9" # mdi:power-socket-us
- "\U000F1A26" # mdi:toggle-switch-variant-off
# Battery status icons
- "\U000F0079" # mdi:battery
- "\U000F12A1" # mdi:battery-low
- "\U000F12A2" # mdi:battery-medium
- "\U000F12A3" # mdi:battery-high
- "\U000F12A4" # mdi:battery-charging-low
- "\U000F12A5" # mdi:battery-charging-medium
- "\U000F12A6" # mdi:battery-charging-high
- "\U000F0091" # mdi:battery-unknown
- "\U000F10CD" # mdi:battery-alert-variant-outline
# Navigation bar icons
- "\U000F04B9" # mdi:sofa
- "\U000F06B5" # mdi:lamp
- "\U000F04DE" # mdi:stove
- "\U000F12BD" # mdi:stairs-up
- "\U000F1239" # mdi:desk
- "\U000F06D9" # mdi:garage
- "\U000F0531" # mdi:tree
# --- LVGL UI ---
# 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.
# --- 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: '%'
+383
View File
@@ -0,0 +1,383 @@
text_sensor:
- platform: wifi_info
ip_address:
name: "HA Remote IP"
id: ip_addr
- platform: homeassistant
id: ts_family_room_tv_stand
entity_id: light.family_room_tv_stand
internal: true
on_value:
then:
- lvgl.widget.update:
id: btn_family_room_tv_stand
state:
checked: !lambda return x == "on";
- lambda: |-
const lv_color_t text_color = (x == "on") ? lv_color_hex(0x001A33) : lv_color_hex(0xE3E2E6);
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));
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
id: ts_small_family_room_lamp
entity_id: light.small_family_room_lamp
internal: true
on_value:
then:
- lvgl.widget.update:
id: btn_small_family_room_lamp
state:
checked: !lambda return x == "on";
- lambda: |-
const lv_color_t text_color = (x == "on") ? lv_color_hex(0x22001F) : lv_color_hex(0xE3E2E6);
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));
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
id: ts_living_room_lamp_1
entity_id: light.living_room_lamp_1
internal: true
on_value:
then:
- lvgl.widget.update:
id: btn_family_room_standing_lamp
state:
checked: !lambda return x == "on";
- lambda: |-
const lv_color_t text_color = (x == "on") ? lv_color_hex(0x3D0002) : lv_color_hex(0xE3E2E6);
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));
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
id: ts_living_room_light_2
entity_id: light.living_room_light_2
internal: true
on_value:
then:
- lvgl.widget.update:
id: btn_living_room_main_light
state:
checked: !lambda return x == "on";
- lambda: |-
const lv_color_t text_color = (x == "on") ? lv_color_hex(0x001A33) : lv_color_hex(0xE3E2E6);
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));
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
id: ts_patio_light_1
entity_id: light.patio_light_1
internal: true
on_value:
then:
- lvgl.widget.update:
id: btn_living_room_patio_light
state:
checked: !lambda return x == "on";
- lvgl.widget.update:
id: btn_outside_patio_light
state:
checked: !lambda return x == "on";
- lambda: |-
const lv_color_t off_color = lv_color_hex(0xE3E2E6);
auto *btn1 = id(btn_living_room_patio_light);
auto *btn2 = id(btn_outside_patio_light);
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));
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));
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 {
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));
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
id: ts_kitchen_sink_light
entity_id: light.kitchen_sink_light
internal: true
on_value:
then:
- lvgl.widget.update:
id: btn_kitchen_sink_light
state:
checked: !lambda return x == "on";
- lambda: |-
const lv_color_t text_color = (x == "on") ? lv_color_hex(0x001A33) : lv_color_hex(0xE3E2E6);
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));
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
id: ts_office_lamp_2
entity_id: light.office_lamp_2
internal: true
on_value:
then:
- lvgl.widget.update:
id: btn_office_lamp
state:
checked: !lambda return x == "on";
- lambda: |-
const lv_color_t text_color = (x == "on") ? lv_color_hex(0x001A33) : lv_color_hex(0xE3E2E6);
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));
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
id: ts_office_led_strip
entity_id: light.led_strip_controller_led_strip_controller
internal: true
on_value:
then:
- lvgl.widget.update:
id: btn_office_led_strip
state:
checked: !lambda return x == "on";
- lambda: |-
const lv_color_t text_color = (x == "on") ? lv_color_hex(0x22001F) : lv_color_hex(0xE3E2E6);
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));
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
id: ts_garage_cam_light
entity_id: light.esp32_saturn4_cam_esp32_saturn4_cam_light
internal: true
on_value:
then:
- lvgl.widget.update:
id: btn_garage_cam_light
state:
checked: !lambda return x == "on";
- lambda: |-
const lv_color_t text_color = (x == "on") ? lv_color_hex(0x001A33) : lv_color_hex(0xE3E2E6);
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));
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
id: ts_office_echo_plug
entity_id: switch.office_echo_plug
internal: true
on_value:
then:
- lvgl.widget.update:
id: btn_office_echo_plug
state:
checked: !lambda return x == "on";
- lambda: |-
const lv_color_t text_color = (x == "on") ? lv_color_hex(0x1A0C00) : lv_color_hex(0xE3E2E6);
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));
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
id: ts_office_end_table_lamp_outlet
entity_id: switch.office_end_table_lamp_socket_1
internal: true
on_value:
then:
- lvgl.widget.update:
id: btn_office_end_table_lamp_outlet
state:
checked: !lambda return x == "on";
- lambda: |-
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);
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
id: ts_office_wax_warmer_outlet
entity_id: switch.office_wax_warmer_socket_1
internal: true
on_value:
then:
- lvgl.widget.update:
id: btn_office_wax_warmer_outlet
state:
checked: !lambda return x == "on";
- lambda: |-
const lv_color_t text_color = (x == "on") ? lv_color_hex(0x1E0F3F) : lv_color_hex(0xE3E2E6);
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));
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
id: ts_outside_lamppost_outlet_1
entity_id: switch.lamppost_outlets_socket_1
internal: true
on_value:
then:
- lvgl.widget.update:
id: btn_outside_lamppost_outlet_1
state:
checked: !lambda return x == "on";
- lambda: |-
const lv_color_t text_color = (x == "on") ? lv_color_hex(0x22001F) : lv_color_hex(0xE3E2E6);
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));
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
id: ts_outside_lamppost_outlet_2
entity_id: switch.lamppost_outlets_socket_2
internal: true
on_value:
then:
- lvgl.widget.update:
id: btn_outside_lamppost_outlet_2
state:
checked: !lambda return x == "on";
- lambda: |-
const lv_color_t text_color = (x == "on") ? lv_color_hex(0x1A0C00) : lv_color_hex(0xE3E2E6);
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));
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
id: ts_outside_porch_decor_outlet
entity_id: switch.washing_machine_socket_1
internal: true
on_value:
then:
- lvgl.widget.update:
id: btn_outside_porch_decor_outlet
state:
checked: !lambda return x == "on";
- lambda: |-
const lv_color_t text_color = (x == "on") ? lv_color_hex(0x001F15) : lv_color_hex(0xE3E2E6);
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));
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
id: ts_living_room_garland_switch
entity_id: switch.big_family_room_lamp
internal: true
on_value:
then:
- lvgl.widget.update:
id: btn_living_room_garland_switch
state:
checked: !lambda return x == "on";
- lambda: |-
const lv_color_t text_color = (x == "on") ? lv_color_hex(0x001F15) : lv_color_hex(0xE3E2E6);
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));
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
id: ts_living_room_calendar_switch
entity_id: switch.digital_calendar
internal: true
on_value:
then:
- lvgl.widget.update:
id: btn_living_room_calendar_switch
state:
checked: !lambda return x == "on";
- lambda: |-
const lv_color_t text_color = (x == "on") ? lv_color_hex(0x1E0F3F) : lv_color_hex(0xE3E2E6);
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));
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
id: ts_upstairs_airquality_oled
entity_id: switch.airqualitysensor_3_oled_power
internal: true
on_value:
then:
- lvgl.widget.update:
id: btn_upstairs_airquality_oled
state:
checked: !lambda return x == "on";
- lambda: |-
const lv_color_t text_color = (x == "on") ? lv_color_hex(0x001A33) : lv_color_hex(0xE3E2E6);
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));
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
id: ts_upstairs_camera_motion
entity_id: switch.upstairs_camera_motion_detection
internal: true
on_value:
then:
- lvgl.widget.update:
id: btn_upstairs_camera_motion
state:
checked: !lambda return x == "on";
- lambda: |-
const lv_color_t text_color = (x == "on") ? lv_color_hex(0x22001F) : lv_color_hex(0xE3E2E6);
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));
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
id: ts_garage_fume_exhaust_fan
entity_id: switch.fume_exhaust_fan
internal: true
on_value:
then:
- lvgl.widget.update:
id: btn_garage_fume_exhaust_fan
state:
checked: !lambda return x == "on";
- lambda: |-
const lv_color_t text_color = (x == "on") ? lv_color_hex(0x22001F) : lv_color_hex(0xE3E2E6);
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));
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
id: ts_garage_resin_printer_heater
entity_id: switch.resin_printer_heater
internal: true
on_value:
then:
- lvgl.widget.update:
id: btn_garage_resin_printer_heater
state:
checked: !lambda return x == "on";
- lambda: |-
const lv_color_t text_color = (x == "on") ? lv_color_hex(0x1A0C00) : lv_color_hex(0xE3E2E6);
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));
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));
}
File diff suppressed because it is too large Load Diff
+202
View File
@@ -0,0 +1,202 @@
substitutions:
friendly_name: "Home Energy Monitor"
device_name: home-energy-monitor
update_time: 59s
# SCT-013-000 (100A/50mA) calibration value
current_cal: '27518'
# Jameco 9VAC Transformer (board v1.3+)
voltage_cal: '7305'
esphome:
name: ${device_name}
friendly_name: ${friendly_name}
esp32:
board: nodemcu-32s
framework:
type: esp-idf
# Enable logging
logger:
level: INFO
# Enable Home Assistant API
api:
encryption:
key: !secret api_encryption_key
# Enable OTA updates
ota:
- platform: esphome
wifi:
ssid: !secret wifi_iot_ssid
password: !secret wifi_password
# Fallback hotspot if Wi-Fi fails
ap:
ssid: ${device_name}
password: !secret wifi_password
# Prevents aggressive reconnection attempts
power_save_mode: none # Use 'light' for battery devices
# Slower but more reliable connection
fast_connect: true
# Handle connection failures gracefully
on_connect:
- logger.log: "Wi-Fi connected!"
on_disconnect:
- logger.log: "Wi-Fi disconnected!"
# Reduce mDNS traffic
mdns:
disabled: true # Set to true if using static IPs and don't need discovery
#captive_portal:
web_server:
port: 80
# SPI bus configuration
spi:
clk_pin: 18
miso_pin: 19
mosi_pin: 23
sensor:
# ──────────────────────────────────
# IC1: Channels 1-3 (CS pin 5)
# ──────────────────────────────────
- platform: atm90e32
cs_pin: 5
phase_a:
voltage:
name: ${device_name} Volts
id: ic1Volts
accuracy_decimals: 1
current:
name: ${device_name} CT1 Amps
id: ct1Amps
power:
name: ${device_name} CT1 Watts
id: ct1Watts
gain_voltage: ${voltage_cal}
gain_ct: ${current_cal}
phase_b:
current:
name: ${device_name} CT2 Amps
id: ct2Amps
power:
name: ${device_name} CT2 Watts
id: ct2Watts
gain_voltage: ${voltage_cal}
gain_ct: ${current_cal}
phase_c:
current:
name: ${device_name} CT3 Amps
id: ct3Amps
power:
name: ${device_name} CT3 Watts
id: ct3Watts
gain_voltage: ${voltage_cal}
gain_ct: ${current_cal}
frequency:
name: ${device_name} Frequency
line_frequency: 60Hz
gain_pga: 1X
update_interval: ${update_time}
# ──────────────────────────────────
# IC2: Channels 4-6 (CS pin 4)
# ──────────────────────────────────
- platform: atm90e32
cs_pin: 4
phase_a:
current:
name: ${device_name} CT4 Amps
id: ct4Amps
power:
name: ${device_name} CT4 Watts
id: ct4Watts
gain_voltage: ${voltage_cal}
gain_ct: ${current_cal}
phase_b:
current:
name: ${device_name} CT5 Amps
id: ct5Amps
power:
name: ${device_name} CT5 Watts
id: ct5Watts
gain_voltage: ${voltage_cal}
gain_ct: ${current_cal}
phase_c:
current:
name: ${device_name} CT6 Amps
id: ct6Amps
power:
name: ${device_name} CT6 Watts
id: ct6Watts
gain_voltage: ${voltage_cal}
gain_ct: ${current_cal}
line_frequency: 60Hz
gain_pga: 1X
update_interval: ${update_time}
# ──────────────────────────────────
# Calculated: Total Home Power
# (CT1 = Main Leg 1, CT2 = Main Leg 2)
# ──────────────────────────────────
- platform: template
name: ${device_name} Total Watts
id: totalWatts
lambda: "return id(ct1Watts).state + id(ct2Watts).state;"
accuracy_decimals: 1
unit_of_measurement: W
device_class: power
update_interval: ${update_time}
- platform: total_daily_energy
name: ${device_name} Total Daily Energy
power_id: totalWatts
unit_of_measurement: kWh
accuracy_decimals: 2
filters:
- multiply: 0.001
# Diagnostic sensors
- platform: wifi_signal
name: "${device_name} WiFi Signal"
update_interval: 60s
- platform: uptime
name: "${device_name} Uptime"
update_interval: 60s
time:
- platform: homeassistant
id: homeassistant_time
text_sensor:
- platform: wifi_info
ip_address:
name: "${device_name} IP Address"
ssid:
name: "${device_name} Connected SSID"
bssid:
name: "${device_name} BSSID"
# Watchdog to auto-reboot if things go wrong
interval:
- interval: 5min
then:
- if:
condition:
and:
- lambda: 'return millis() > 300000;' # >5 min uptime
- not:
wifi.connected:
then:
- logger.log: "Wi-Fi not connected for 5 min, rebooting..."
- delay: 10s
- lambda: 'App.safe_reboot();'
@@ -0,0 +1,49 @@
# ===== I2S Audio =====
i2s_audio:
- id: i2s_in
i2s_lrclk_pin: ${i2s_in_lrclk_pin}
i2s_bclk_pin: ${i2s_in_bclk_pin}
- id: i2s_out
i2s_lrclk_pin: ${i2s_out_lrclk_pin}
i2s_bclk_pin: ${i2s_out_bclk_pin}
microphone:
- platform: i2s_audio
id: mic
i2s_audio_id: i2s_in
i2s_din_pin: ${mic_din_pin}
adc_type: external
pdm: false
bits_per_sample: 32bit
channel: left
speaker:
- platform: i2s_audio
id: spk
i2s_audio_id: i2s_out
i2s_dout_pin: ${speaker_dout_pin}
dac_type: external
bits_per_sample: 32bit
channel: mono
sample_rate: 16000
# Voice Assistant needs the speaker to fully stop after playback so it can
# leave RESPONSE_FINISHED and return to IDLE for the next wake word.
timeout: 500ms
rtttl:
id: volume_beep
speaker: spk
# ===== Wake Word =====
micro_wake_word:
id: mww
microphone: mic
models:
- model: okay_nabu
id: wake_word_model
on_wake_word_detected:
- voice_assistant.start:
wake_word: !lambda return wake_word;
- light.turn_on:
id: led_bar
effect: "Listening Pulse"
@@ -0,0 +1,125 @@
packages:
jarvis-satellite-led: !include jarvis-satellite-led.yaml
jarvis-satellite-voice: !include jarvis-satellite-voice.yaml
jarvis-satellite-audio: !include jarvis-satellite-audio.yaml
jarvis-satellite-ui: !include jarvis-satellite-ui.yaml
substitutions:
# Flash & PSRAM
flash_size: "8MB"
psram_mode: "quad"
psram_speed: "80MHz"
# CPU Frequency
cpu_frequency: "240MHz"
# I2S Microphone pins
i2s_in_lrclk_pin: GPIO6
i2s_in_bclk_pin: GPIO7
mic_din_pin: GPIO4
# I2S Speaker pins
i2s_out_lrclk_pin: GPIO45
i2s_out_bclk_pin: GPIO46
speaker_dout_pin: GPIO8
# LED bar
led_pin: GPIO16
led_num_leds: "8"
# Microphone sensitivity (tuned for INMP441)
mic_noise_suppression_level: "2"
mic_auto_gain: "10dBFS"
mic_volume_multiplier: "4.0"
# Button pins
btn_vol_up_pin: GPIO1
btn_vol_down_pin: GPIO2
btn_mute_pin: GPIO3
btn_wake_pin: GPIO10
# Change this to true in case you have a hidden SSID at home.
hidden_ssid: "true"
globals:
- id: volume_level
type: float
restore_value: true
initial_value: '0.8'
esphome:
platformio_options:
board_build.flash_mode: dio
on_boot:
- priority: -100
then:
- lambda: id(spk).set_volume(id(volume_level));
esp32:
board: esp32-s3-devkitc-1
variant: esp32s3
cpu_frequency: ${cpu_frequency}
flash_size: ${flash_size}
framework:
type: esp-idf
version: recommended
sdkconfig_options:
CONFIG_ESP32S3_DEFAULT_CPU_FREQ_240: "y"
CONFIG_ESP32S3_DATA_CACHE_64KB: "y"
CONFIG_ESP32S3_DATA_CACHE_LINE_64B: "y"
CONFIG_ESP32S3_INSTRUCTION_CACHE_32KB: "y"
# Moves instructions and read only data from flash into PSRAM on boot.
# Both enabled allows instructions to execute while a flash operation is in progress without needing to be placed in IRAM.
# Considerably speeds up mWW at the cost of using more PSRAM.
CONFIG_SPIRAM_RODATA: "y"
CONFIG_SPIRAM_FETCH_INSTRUCTIONS: "y"
CONFIG_BT_ALLOCATION_FROM_SPIRAM_FIRST: "y"
CONFIG_BT_BLE_DYNAMIC_ENV_MEMORY: "y"
CONFIG_MBEDTLS_EXTERNAL_MEM_ALLOC: "y"
CONFIG_MBEDTLS_SSL_PROTO_TLS1_3: "y"
wifi:
id: wifi_id
fast_connect: ${hidden_ssid}
network:
enable_ipv6: true
http_request:
web_server:
port: 80
psram:
mode: ${psram_mode}
speed: ${psram_speed}
captive_portal:
api:
encryption:
key: !secret api_encryption_key
on_client_connected:
- delay: 50ms
- if:
condition:
not:
lambda: 'return id(mww).is_running();'
then:
- micro_wake_word.start:
on_client_disconnected:
- micro_wake_word.stop:
- voice_assistant.stop:
logger:
level: DEBUG
logs:
micro_wake_word: DEBUG
voice_assistant: DEBUG
ota:
- platform: esphome
password: !secret ota_password
@@ -0,0 +1,111 @@
# ===== Physical Buttons =====
# Note: esp32_touch is not supported on ESP32-S3 with ESP-IDF.
# Wire physical momentary buttons to these pins (active LOW with internal pullup).
binary_sensor:
- platform: gpio
pin:
number: ${btn_vol_up_pin}
mode: INPUT_PULLUP
inverted: true
name: "Volume Up"
on_press:
- lambda: |-
float vol = min(1.0f, id(volume_level) + 0.1f);
id(volume_level) = vol;
id(spk).set_volume(vol);
- rtttl.play:
id: volume_beep
rtttl: "beep:d=32,o=6,b=180:c"
- light.turn_on:
id: led_bar
brightness: 100%
effect: "none"
- delay: 150ms
- light.turn_on:
id: led_bar
effect: "Idle Breathe"
- platform: gpio
pin:
number: ${btn_vol_down_pin}
mode: INPUT_PULLUP
inverted: true
name: "Volume Down"
on_press:
- lambda: |-
float vol = max(0.0f, id(volume_level) - 0.1f);
id(volume_level) = vol;
id(spk).set_volume(vol);
- rtttl.play:
id: volume_beep
rtttl: "beep:d=32,o=5,b=180:c"
- light.turn_on:
id: led_bar
brightness: 30%
effect: "none"
- delay: 150ms
- light.turn_on:
id: led_bar
effect: "Idle Breathe"
- platform: gpio
pin:
number: ${btn_mute_pin}
mode: INPUT_PULLUP
inverted: true
name: "Mic Mute"
on_press:
- switch.toggle: mic_mute
- platform: gpio
pin:
number: ${btn_wake_pin}
mode: INPUT_PULLUP
inverted: true
name: "Wake"
on_press:
- if:
condition:
switch.is_off: mic_mute
then:
- voice_assistant.start:
- light.turn_on:
id: led_bar
effect: "Listening Pulse"
else:
- light.turn_on:
id: led_bar
effect: "Error Flash"
- delay: 500ms
- light.turn_on:
id: led_bar
red: 100%
green: 30%
blue: 0%
brightness: 50%
effect: "none"
# ===== Mic Mute Switch =====
switch:
- platform: template
id: mic_mute
name: "Microphone Mute"
icon: "mdi:microphone-off"
optimistic: true
restore_mode: RESTORE_DEFAULT_OFF
on_turn_on:
- voice_assistant.stop:
- light.turn_on:
id: led_bar
effect: "none"
- light.turn_on:
id: led_bar
red: 100%
green: 30%
blue: 0%
brightness: 50%
on_turn_off:
- light.turn_on:
id: led_bar
effect: "Idle Breathe"
- voice_assistant.start:
@@ -0,0 +1,67 @@
# ===== LED Bar (WS2812B) =====
light:
- platform: esp32_rmt_led_strip
id: led_bar
chipset: WS2812
rgb_order: GRB
pin: ${led_pin}
num_leds: ${led_num_leds}
name: "Voice Satellite LEDs"
default_transition_length: 200ms
effects:
- addressable_lambda:
name: "Idle Breathe"
update_interval: 50ms
lambda: |-
static float brightness = 0;
static int direction = 1;
brightness += direction * 0.02;
if (brightness >= 1.0) { brightness = 1.0; direction = -1; }
if (brightness <= 0.1) { brightness = 0.1; direction = 1; }
for (int i = 0; i < it.size(); i++) {
it[i] = Color(0, 0, (int)(40 * brightness));
}
- addressable_lambda:
name: "Listening Pulse"
update_interval: 30ms
lambda: |-
static int pos = 0;
pos = (pos + 1) % (it.size() * 2);
for (int i = 0; i < it.size(); i++) {
int dist = abs(pos - i);
if (pos >= it.size()) dist = abs((it.size() * 2 - pos) - i);
int bright = max(0, 255 - dist * 60);
it[i] = Color(0, bright, bright);
}
- addressable_lambda:
name: "Processing"
update_interval: 80ms
lambda: |-
static int offset = 0;
offset = (offset + 1) % it.size();
for (int i = 0; i < it.size(); i++) {
if ((i + offset) % 2 == 0) {
it[i] = Color(80, 0, 180);
} else {
it[i] = Color(0, 0, 0);
}
}
- addressable_lambda:
name: "Speaking"
update_interval: 40ms
lambda: |-
static int wave = 0;
wave = (wave + 1) % 16;
for (int i = 0; i < it.size(); i++) {
int bright = (sin((wave + i * 2) * 0.4) + 1) * 127;
it[i] = Color(0, bright, 0);
}
- addressable_lambda:
name: "Error Flash"
update_interval: 100ms
lambda: |-
static bool on = false;
on = !on;
for (int i = 0; i < it.size(); i++) {
it[i] = on ? Color(255, 0, 0) : Color(0, 0, 0);
}
@@ -0,0 +1,53 @@
# ===== Web UI / HA Entities =====
script:
- id: publish_current_time
then:
- lambda: |-
auto time_now = id(homeassistant_time).now();
id(current_time_sensor).publish_state(time_now.strftime("%H:%M"));
time:
- platform: homeassistant
id: homeassistant_time
on_time_sync:
- script.execute: publish_current_time
on_time:
- seconds: 0
minutes: /1
then:
- script.execute: publish_current_time
text_sensor:
- platform: template
name: "Current device time"
id: current_time_sensor
icon: mdi:clock
number:
- platform: template
name: "Volume"
id: volume_number
icon: mdi:volume-high
entity_category: config
min_value: 0
max_value: 100
step: 5
unit_of_measurement: "%"
lambda: return id(volume_level) * 100.0f;
set_action:
- lambda: |-
float vol = x / 100.0f;
id(volume_level) = vol;
id(spk).set_volume(vol);
select:
- platform: logger
name: "Logger Level"
disabled_by_default: true
button:
- platform: restart
name: "Restart"
icon: mdi:restart
entity_category: config
@@ -0,0 +1,97 @@
# ===== Wake Word Sensitivity =====
select:
- platform: template
name: "Wake Word Sensitivity"
optimistic: true
initial_option: "Slightly sensitive"
restore_value: true
entity_category: config
icon: mdi:ear-hearing
options:
- Slightly sensitive
- Moderately sensitive
- Very sensitive
on_value:
lambda: |-
if (x == "Slightly sensitive") {
id(wake_word_model).set_probability_cutoff(247); // 0.97
} else if (x == "Moderately sensitive") {
id(wake_word_model).set_probability_cutoff(235); // 0.92
} else if (x == "Very sensitive") {
id(wake_word_model).set_probability_cutoff(212); // 0.83
}
# ===== Voice Assistant =====
voice_assistant:
id: va
microphone: mic
speaker: spk
micro_wake_word: mww
noise_suppression_level: ${mic_noise_suppression_level}
auto_gain: ${mic_auto_gain}
volume_multiplier: ${mic_volume_multiplier}
on_listening:
- logger.log: "[VA] on_listening — mic open, sending audio to HA"
- light.turn_on:
id: led_bar
effect: "Listening Pulse"
on_stt_vad_end:
- logger.log: "[VA] on_stt_vad_end — speech detected, processing"
- light.turn_on:
id: led_bar
effect: "Processing"
on_stt_end:
- lambda: |-
ESP_LOGI("va", "STT result: %s", x.c_str());
- light.turn_off:
id: led_bar
on_tts_start:
- lambda: |-
ESP_LOGI("va", "TTS start: %s", x.c_str());
- light.turn_on:
id: led_bar
effect: "Speaking"
on_tts_end:
- logger.log: "[VA] on_tts_end"
on_tts_stream_end:
- logger.log: "[VA] on_tts_stream_end"
on_error:
- lambda: |-
ESP_LOGE("va", "VA error: code=%s, message=%s", code.c_str(), message.c_str());
- if:
condition:
lambda: 'return code != "duplicate_wake_up_detected";'
then:
- light.turn_on:
id: led_bar
effect: "Error Flash"
- delay: 2s
- micro_wake_word.start:
on_end:
- logger.log: "[VA] on_end"
- wait_until:
not:
voice_assistant.is_running:
- light.turn_off:
id: led_bar
- if:
condition:
and:
- voice_assistant.connected:
- not:
micro_wake_word.is_running:
then:
- micro_wake_word.start:
on_idle:
- logger.log: "[VA] on_idle"
- light.turn_on:
id: led_bar
effect: "Idle Breathe"
- if:
condition:
and:
- voice_assistant.connected:
- not:
micro_wake_word.is_running:
then:
- micro_wake_word.start:
+13
View File
@@ -0,0 +1,13 @@
{% set ns = namespace(lines=[]) %}
{% for domain, entities in states | groupby('domain') %}
{% set ns.lines = ns.lines + ['[' ~ domain ~ ']'] %}
{% for s in entities | sort(attribute='entity_id') %}
{% set area = area_name(s.entity_id) if area_name(s.entity_id) else 'No area' %}
{% set friendly = state_attr(s.entity_id, 'friendly_name') if state_attr(s.entity_id, 'friendly_name') else s.object_id %}
{% set ns.lines = ns.lines + [
s.entity_id ~ ' | ' ~ friendly ~ ' | ' ~ area
] %}
{% endfor %}
{% set ns.lines = ns.lines + [''] %}
{% endfor %}
{{ ns.lines | join('\n') }}
+135
View File
@@ -0,0 +1,135 @@
substitutions:
device_name: jordyn-a1-camera
friendly_name: "Jordyn A1 Camera"
esphome:
name: ${device_name}
friendly_name: ${friendly_name}
esp32:
board: esp32dev
framework:
type: esp-idf
#cpu_frequency: 240MHZ
# Enable logging
logger:
# Enable Home Assistant API
api:
encryption:
key: "PDB5LTKAcNWMC+MeE03/SKpqX42pcnITzmfwu761LjI="
ota:
- platform: esphome
password: "d207583702e4b979e28a43f6f52d19ae"
wifi:
ssid: !secret wifi_iot_ssid
password: !secret wifi_password
# Enable fallback hotspot (captive portal) in case wifi connection fails
ap:
ssid: "Jordyn-A1-Camera"
password: "jN4rzGokp5mt"
# Prevents aggressive reconnection attempts
power_save_mode: none # Use 'light' for battery devices
# Slower but more reliable connection
fast_connect: true
# Handle connection failures gracefully
on_connect:
- logger.log: "Wi-Fi connected!"
on_disconnect:
- logger.log: "Wi-Fi disconnected!"
# Reduce mDNS traffic
mdns:
disabled: false # Set to true if you use static IPs and don't need discovery
#captive_portal:
web_server:
port: 80
i2c:
- id: camera_i2c
sda: GPIO26
scl: GPIO27
psram:
mode: quad
speed: 80MHz
esp32_camera:
external_clock:
pin: GPIO0
frequency: 20MHz
i2c_id: camera_i2c
data_pins: [GPIO5, GPIO18, GPIO19, GPIO21, GPIO36, GPIO39, GPIO34, GPIO35]
vsync_pin: GPIO25
href_pin: GPIO23
pixel_clock_pin: GPIO22
power_down_pin: GPIO32
resolution: 1024x768
jpeg_quality: 20
vertical_flip: False
horizontal_mirror: False
max_framerate: 40 fps
idle_framerate: 0.05 fps
# Image settings
name: esp32-jordyn-a1-cam
# brightness: 1
# #contrast: 0
# agc_gain_ceiling: 2X
# agc_mode: MANUAL
# agc_value: 5
# ...
esp32_camera_web_server:
- port: 80
mode: STREAM
- port: 8080
mode: SNAPSHOT
switch:
- platform: gpio
name: "jordyn-a1-cam-flash"
pin: 4
# Diagnostic sensors
sensor:
- platform: wifi_signal
name: "${device_name} WiFi Signal"
update_interval: 60s
- platform: uptime
name: "${device_name} Uptime"
update_interval: 60s
text_sensor:
- platform: wifi_info
ip_address:
name: "${device_name} IP Address"
ssid:
name: "${device_name} Connected SSID"
bssid:
name: "${device_name} BSSID"
# Watchdog to auto-reboot if things go wrong
interval:
- interval: 5min
then:
- if:
condition:
and:
- lambda: 'return millis() > 300000;' # >5 min uptime
- not:
wifi.connected:
then:
- logger.log: "Wi-Fi not connected for 5 min, rebooting..."
- delay: 10s
- lambda: 'App.safe_reboot();'
+122
View File
@@ -0,0 +1,122 @@
substitutions:
device_name: laundry-litter-box-camera
friendly_name: "Laundry Litter Box Camera"
esphome:
name: ${device_name}
friendly_name: ${friendly_name}
esp32:
board: esp32dev
framework:
type: esp-idf
logger:
api:
encryption:
key: !secret api_encryption_key
ota:
- platform: esphome
password: !secret ota_password
wifi:
ssid: !secret wifi_iot_ssid
password: !secret wifi_password
manual_ip:
static_ip: 192.168.69.220
gateway: 192.168.69.1
subnet: 255.255.255.0
ap:
ssid: "laundry-litter-cam-fallback"
password: !secret fallback_password
# Prevents aggressive reconnection attempts
power_save_mode: none # Use 'light' for battery devices
# Slower but more reliable connection
fast_connect: true
# Handle connection failures gracefully
on_connect:
- logger.log: "Wi-Fi connected!"
on_disconnect:
- logger.log: "Wi-Fi disconnected!"
# Reduce mDNS traffic
mdns:
disabled: true # Set to true if you use static IPs and don't need discovery
# Diagnostic sensors
sensor:
- platform: wifi_signal
name: "${device_name} WiFi Signal"
update_interval: 60s
- platform: uptime
name: "${device_name} Uptime"
update_interval: 60s
text_sensor:
- platform: wifi_info
ip_address:
name: "${device_name} IP Address"
ssid:
name: "${device_name} Connected SSID"
bssid:
name: "${device_name} BSSID"
# Watchdog to auto-reboot if things go wrong
interval:
- interval: 5min
then:
- if:
condition:
and:
- lambda: 'return millis() > 300000;' # >5 min uptime
- not:
wifi.connected:
then:
- logger.log: "Wi-Fi not connected for 5 min, rebooting..."
- delay: 10s
- lambda: 'App.safe_reboot();'
captive_portal:
i2c:
- id: camera_i2c
sda: GPIO26
scl: GPIO27
psram:
mode: quad
speed: 80MHz
esp32_camera:
external_clock:
pin: GPIO0
frequency: 20MHz
i2c_id: camera_i2c
data_pins: [GPIO5, GPIO18, GPIO19, GPIO21, GPIO36, GPIO39, GPIO34, GPIO35]
vsync_pin: GPIO25
href_pin: GPIO23
pixel_clock_pin: GPIO22
power_down_pin: GPIO32
resolution: 1024x768
jpeg_quality: 15
vertical_flip: true
horizontal_mirror: false
max_framerate: 20 fps
idle_framerate: 0.05 fps
name: ${device_name}
esp32_camera_web_server:
- port: 8080
mode: STREAM
- port: 8081
mode: SNAPSHOT
switch:
- platform: gpio
name: "${device_name}-flash"
pin: 4
+202
View File
@@ -0,0 +1,202 @@
substitutions:
device_name: led-controller-jordyn-a1
friendly_name: "Jordyn's LED Controller"
esphome:
name: ${device_name}
friendly_name: ${friendly_name}
esp32:
board: esp32-c3-devkitm-1
framework:
type: esp-idf
# Enable logging
logger:
# Enable Home Assistant API
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
manual_ip:
static_ip: 192.168.69.80
gateway: 192.168.69.1
subnet: 255.255.255.0
# Enable fallback hotspot (captive portal) in case wifi connection fails
ap:
ssid: "${friendly_name} Fallback"
password: !secret fallback_password
# Prevents aggressive reconnection attempts
power_save_mode: none # Use 'light' for battery devices
# Slower but more reliable connection
fast_connect: true
# Handle connection failures gracefully
on_connect:
- logger.log: "Wi-Fi connected!"
on_disconnect:
- logger.log: "Wi-Fi disconnected!"
# Reduce mDNS traffic
mdns:
disabled: false # Set to true if you use static IPs and don't need discovery
#captive_portal:
web_server:
port: 80
# LED Light configuration
light:
- platform: esp32_rmt_led_strip
rgb_order: GRB
chipset: WS2812
pin: GPIO7
num_leds: 60
name: "${friendly_name}"
id: led_strip
# Default color on boot
restore_mode: RESTORE_DEFAULT_OFF
# Color correction for more accurate colors (optional)
# Uncomment and adjust if your LEDs look too blue/green
# color_correct: [100%, 100%, 100%]
# Effects - you can enable these for more features!
effects:
# Basic effects
- random:
name: "Random"
transition_length: 5s
update_interval: 7s
- strobe:
name: "Strobe"
colors:
- state: true
brightness: 100%
red: 100%
green: 100%
blue: 100%
duration: 500ms
- state: false
duration: 250ms
- flicker:
name: "Flicker"
alpha: 95%
intensity: 1.5%
# Rainbow effects
- addressable_rainbow:
name: "Rainbow"
speed: 10
width: 50
- addressable_color_wipe:
name: "Color Wipe"
colors:
- red: 100%
green: 0%
blue: 0%
num_leds: 1
- red: 0%
green: 100%
blue: 0%
num_leds: 1
- red: 0%
green: 0%
blue: 100%
num_leds: 1
add_led_interval: 100ms
reverse: false
- addressable_scan:
name: "Scan"
move_interval: 100ms
scan_width: 3
- addressable_twinkle:
name: "Twinkle"
twinkle_probability: 5%
progress_interval: 4ms
- addressable_fireworks:
name: "Fireworks"
update_interval: 32ms
spark_probability: 10%
use_random_color: true
fade_out_rate: 120
# Pulse effect
- pulse:
name: "Pulse"
transition_length: 1s
update_interval: 1s
# Sensors for monitoring
sensor:
# WiFi Signal Strength
- platform: wifi_signal
name: "${friendly_name} WiFi Signal"
update_interval: 60s
# Uptime
- platform: uptime
name: "${friendly_name} Uptime"
# Diagnostic sensors
- platform: wifi_signal
name: "${device_name} WiFi Signal"
update_interval: 60s
- platform: uptime
name: "${device_name} Uptime"
update_interval: 60s
# Text sensors
text_sensor:
# ESPHome version
- platform: version
name: "${friendly_name} ESPHome Version"
# WiFi info
- platform: wifi_info
ip_address:
name: "${device_name} IP Address"
ssid:
name: "${device_name} Connected SSID"
bssid:
name: "${device_name} BSSID"
# Button to restart ESP32
button:
- platform: restart
name: "${friendly_name} Restart"
# Watchdog to auto-reboot if things go wrong
interval:
- interval: 5min
then:
- if:
condition:
and:
- lambda: 'return millis() > 300000;' # >5 min uptime
- not:
wifi.connected:
then:
- logger.log: "Wi-Fi not connected for 5 min, rebooting..."
- delay: 10s
- lambda: 'App.safe_reboot();'
+198
View File
@@ -0,0 +1,198 @@
substitutions:
device_name: led-controller-polly-a1m
friendly_name: "Polly's LED Controller"
esphome:
name: ${device_name}
friendly_name: ${friendly_name}
esp32:
board: esp32-c3-devkitm-1
framework:
type: esp-idf
# Enable logging
logger:
# Enable Home Assistant API
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
manual_ip:
static_ip: 192.168.69.181
gateway: 192.168.69.1
subnet: 255.255.255.0
# Enable fallback hotspot (captive portal) in case wifi connection fails
ap:
ssid: "${friendly_name} Fallback"
password: !secret fallback_password
# Prevents aggressive reconnection attempts
power_save_mode: none # Use 'light' for battery devices
# Slower but more reliable connection
fast_connect: true
# Handle connection failures gracefully
on_connect:
- logger.log: "Wi-Fi connected!"
on_disconnect:
- logger.log: "Wi-Fi disconnected!"
# Reduce mDNS traffic
mdns:
disabled: false # Set to true if you use static IPs and don't need discovery
#captive_portal:
web_server:
port: 80
# LED Light configuration
light:
- platform: esp32_rmt_led_strip
rgb_order: GRB
chipset: WS2812
pin: GPIO7
num_leds: 10
name: "${friendly_name}"
id: led_strip
# Default color on boot
restore_mode: RESTORE_DEFAULT_OFF
# Effects - you can enable these for more features!
effects:
# Basic effects
- random:
name: "Random"
transition_length: 5s
update_interval: 7s
- strobe:
name: "Strobe"
colors:
- state: true
brightness: 100%
red: 100%
green: 100%
blue: 100%
duration: 500ms
- state: false
duration: 250ms
- flicker:
name: "Flicker"
alpha: 95%
intensity: 1.5%
# Rainbow effects
- addressable_rainbow:
name: "Rainbow"
speed: 10
width: 50
- addressable_color_wipe:
name: "Color Wipe"
colors:
- red: 100%
green: 0%
blue: 0%
num_leds: 1
- red: 0%
green: 100%
blue: 0%
num_leds: 1
- red: 0%
green: 0%
blue: 100%
num_leds: 1
add_led_interval: 100ms
reverse: false
- addressable_scan:
name: "Scan"
move_interval: 100ms
scan_width: 3
- addressable_twinkle:
name: "Twinkle"
twinkle_probability: 5%
progress_interval: 4ms
- addressable_fireworks:
name: "Fireworks"
update_interval: 32ms
spark_probability: 10%
use_random_color: true
fade_out_rate: 120
# Pulse effect
- pulse:
name: "Pulse"
transition_length: 1s
update_interval: 1s
# Sensors for monitoring
sensor:
# WiFi Signal Strength
- platform: wifi_signal
name: "${friendly_name} WiFi Signal"
update_interval: 60s
# Uptime
- platform: uptime
name: "${friendly_name} Uptime"
# Diagnostic sensors
- platform: wifi_signal
name: "${device_name} WiFi Signal"
update_interval: 60s
- platform: uptime
name: "${device_name} Uptime"
update_interval: 60s
# Text sensors
text_sensor:
# ESPHome version
- platform: version
name: "${friendly_name} ESPHome Version"
# WiFi info
- platform: wifi_info
ip_address:
name: "${device_name} IP Address"
ssid:
name: "${device_name} Connected SSID"
bssid:
name: "${device_name} BSSID"
# Button to restart ESP32
button:
- platform: restart
name: "${friendly_name} Restart"
# Watchdog to auto-reboot if things go wrong
interval:
- interval: 5min
then:
- if:
condition:
and:
- lambda: 'return millis() > 300000;' # >5 min uptime
- not:
wifi.connected:
then:
- logger.log: "Wi-Fi not connected for 5 min, rebooting..."
- delay: 10s
- lambda: 'App.safe_reboot();'
+340
View File
@@ -0,0 +1,340 @@
substitutions:
name: mbr-ha-remote
friendly_name: "MBR HA Remote"
# Home Assistant entity IDs - UPDATE THESE TO MATCH YOUR SETUP
light_1_entity: switch.pollys_light
light_2_entity: switch.joshuas_light
fan_entity: switch.parents_ceiling_fan
esphome:
name: ${name}
friendly_name: ${friendly_name}
on_boot:
priority: -100
then:
- lambda: |-
id(last_interaction_ms) = millis();
id(backlight_is_on) = true;
- component.update: my_display
esp32:
board: esp32dev
framework:
type: arduino
logger:
api:
encryption:
key: !secret api_encryption_key
ota:
platform: esphome
password: !secret ota_password
wifi:
ssid: !secret wifi_iot_ssid
password: !secret wifi_password
ap:
ssid: "${name} Fallback"
password: !secret wifi_password
# Prevents aggressive reconnection attempts
power_save_mode: none # Use 'light' for battery devices
# Slower but more reliable connection
fast_connect: true
# Handle connection failures gracefully
on_connect:
- logger.log: "Wi-Fi connected!"
on_disconnect:
- logger.log: "Wi-Fi disconnected!"
# Reduce mDNS traffic
mdns:
disabled: false # Set to true if you use static IPs and don't need discovery
#captive_portal:
web_server:
port: 80
# SPI for display and touchscreen (CYD uses two separate SPI buses)
spi:
- id: tft_spi
clk_pin: GPIO14
mosi_pin: GPIO13
miso_pin: GPIO12
- id: touch_spi
clk_pin: GPIO25
mosi_pin: GPIO32
miso_pin: GPIO39
# ILI9341 Display (2.8" 320x240)
display:
- platform: ili9xxx
model: ili9341
spi_id: tft_spi
cs_pin: GPIO15
dc_pin: GPIO2
dimensions:
width: 320
height: 240
# Use palette mode to lower display buffer memory usage.
color_palette: 8BIT
data_rate: 20MHz
update_interval: never
rotation: 0
invert_colors: false
id: my_display
lambda: |-
it.fill(Color(0x1A1A2E));
it.print(160, 20, id(title_font), Color(0xFFFFFF), TextAlign::TOP_CENTER, "Room Remote");
// Button 1: All Toggle (top-left)
if (id(all_state)) {
it.filled_rectangle(20, 50, 130, 80, Color(0x4CAF50));
} else {
it.filled_rectangle(20, 50, 130, 80, Color(0x424242));
}
it.print(85, 90, id(button_font), Color(0x000000), TextAlign::CENTER, "Ceiling Fan");
// Button 2: Light 1 (top-right)
if (id(light1_state)) {
it.filled_rectangle(170, 50, 130, 80, Color(0x4CAF50));
} else {
it.filled_rectangle(170, 50, 130, 80, Color(0x424242));
}
it.print(235, 90, id(button_font), Color(0x000000), TextAlign::CENTER, "Polly's Light");
// Button 3: Light 2 (bottom-left)
if (id(light2_state)) {
it.filled_rectangle(20, 150, 130, 80, Color(0x4CAF50));
} else {
it.filled_rectangle(20, 150, 130, 80, Color(0x424242));
}
it.print(85, 190, id(button_font), Color(0x000000), TextAlign::CENTER, "Joshua's Light");
// Button 4: Fan (bottom-right)
if (id(fan_state)) {
it.filled_rectangle(170, 150, 130, 80, Color(0x4CAF50));
} else {
it.filled_rectangle(170, 150, 130, 80, Color(0x424242));
}
it.print(235, 190, id(button_font), Color(0x000000), TextAlign::CENTER, "Toggle All");
# XPT2046 Touchscreen
touchscreen:
- platform: xpt2046
spi_id: touch_spi
cs_pin: GPIO33
interrupt_pin: GPIO36
calibration:
x_min: 280
x_max: 3860
y_min: 340
y_max: 3860
on_touch:
- lambda: |-
uint32_t now = millis();
id(last_interaction_ms) = now;
// Wake backlight on first touch only, don't also trigger a button
if (!id(backlight_is_on)) {
auto call = id(backlight).turn_on();
call.perform();
id(backlight_is_on) = true;
id(last_button_press_ms) = now;
return;
}
// Debounce: ignore repeated touch events within 600ms
if ((uint32_t)(now - id(last_button_press_ms)) < 600) return;
id(last_button_press_ms) = now;
ESP_LOGD("touch", "Touch at x=%d, y=%d", touch.x, touch.y);
// Button 1: Toggle All (top-left)
if (touch.x >= 20 && touch.x <= 150 && touch.y >= 50 && touch.y <= 130) {
ESP_LOGD("touch", "Toggle All pressed");
id(light1_state) = !id(light1_state);
id(light2_state) = !id(light2_state);
id(fan_state) = !id(fan_state);
id(all_state) = id(light1_state) && id(light2_state) && id(fan_state);
id(svc_toggle_all).execute();
// Button 2: Polly's Light (top-right)
} else if (touch.x >= 170 && touch.x <= 300 && touch.y >= 50 && touch.y <= 130) {
ESP_LOGD("touch", "Polly's Light pressed");
id(light1_state) = !id(light1_state);
id(all_state) = id(light1_state) && id(light2_state) && id(fan_state);
id(svc_toggle_polly).execute();
// Button 3: Joshua's Light (bottom-left)
} else if (touch.x >= 20 && touch.x <= 150 && touch.y >= 150 && touch.y <= 230) {
ESP_LOGD("touch", "Joshua's Light pressed");
id(light2_state) = !id(light2_state);
id(all_state) = id(light1_state) && id(light2_state) && id(fan_state);
id(svc_toggle_joshua).execute();
// Button 4: Ceiling Fan (bottom-right)
} else if (touch.x >= 170 && touch.x <= 300 && touch.y >= 150 && touch.y <= 230) {
ESP_LOGD("touch", "Ceiling Fan pressed");
id(fan_state) = !id(fan_state);
id(all_state) = id(light1_state) && id(light2_state) && id(fan_state);
id(svc_toggle_fan).execute();
}
id(my_display).update();
# Backlight control
output:
- platform: ledc
pin: GPIO21
id: backlight_output
light:
- platform: monochromatic
output: backlight_output
name: "Display Backlight"
id: backlight
restore_mode: ALWAYS_ON
# Fonts
font:
- file: "gfonts://Nunito"
id: title_font
size: 24
- file: "gfonts://Nunito"
id: button_font
size: 20
# Scripts to call HA services (lambdas can't call homeassistant.service directly)
script:
- id: svc_toggle_all
then:
- homeassistant.service:
service: switch.toggle
data:
entity_id: ${light_1_entity}
- homeassistant.service:
service: switch.toggle
data:
entity_id: ${light_2_entity}
- homeassistant.service:
service: switch.toggle
data:
entity_id: ${fan_entity}
- id: svc_toggle_polly
then:
- homeassistant.service:
service: switch.toggle
data:
entity_id: ${light_1_entity}
- id: svc_toggle_joshua
then:
- homeassistant.service:
service: switch.toggle
data:
entity_id: ${light_2_entity}
- id: svc_toggle_fan
then:
- homeassistant.service:
service: switch.toggle
data:
entity_id: ${fan_entity}
# Global state tracking
globals:
- id: all_state
type: bool
initial_value: "false"
- id: light1_state
type: bool
initial_value: "false"
- id: light2_state
type: bool
initial_value: "false"
- id: fan_state
type: bool
initial_value: "false"
- id: last_interaction_ms
type: uint32_t
initial_value: "0"
- id: last_button_press_ms
type: uint32_t
initial_value: "0"
- id: backlight_is_on
type: bool
initial_value: "true"
# Backlight timeout
interval:
- interval: 100ms
then:
- if:
condition:
lambda: 'return id(backlight_is_on) && ((uint32_t)(millis() - id(last_interaction_ms)) > 5000);'
then:
- light.turn_off: backlight
- lambda: 'id(backlight_is_on) = false;'
# Watchdog to auto-reboot if things go wrong
- interval: 5min
then:
- if:
condition:
and:
- lambda: 'return millis() > 300000;' # >5 min uptime
- not:
wifi.connected:
then:
- logger.log: "Wi-Fi not connected for 5 min, rebooting..."
- delay: 10s
- lambda: 'App.safe_reboot();'
# Import states from Home Assistant
text_sensor:
- platform: homeassistant
entity_id: ${light_1_entity}
id: ha_light1_state
on_value:
then:
- lambda: 'id(light1_state) = (x == "on");'
- lambda: 'id(all_state) = id(light1_state) && id(light2_state) && id(fan_state);'
- component.update: my_display
- platform: homeassistant
entity_id: ${light_2_entity}
id: ha_light2_state
on_value:
then:
- lambda: 'id(light2_state) = (x == "on");'
- lambda: 'id(all_state) = id(light1_state) && id(light2_state) && id(fan_state);'
- component.update: my_display
- platform: homeassistant
entity_id: ${fan_entity}
id: ha_fan_state
on_value:
then:
- lambda: 'id(fan_state) = (x == "on");'
- lambda: 'id(all_state) = id(light1_state) && id(light2_state) && id(fan_state);'
- component.update: my_display
# ESPHome version
- platform: version
name: "${friendly_name} ESPHome Version"
# WiFi info
- platform: wifi_info
ip_address:
name: "${device_name} IP Address"
ssid:
name: "${device_name} Connected SSID"
bssid:
name: "${device_name} BSSID"
+33
View File
@@ -0,0 +1,33 @@
packages:
jarvis-satellite: !include jarvis-satellite/jarvis-satellite-base.yaml
### Change the following for each satellite device, e.g. "jarvis-satellite-office" and "jarvis-satellite-bedroom" and to match each ESP board pinout
substitutions:
flash_size: "8MB"
psram_mode: "quad"
psram_speed: "80MHz"
i2s_in_lrclk_pin: GPIO6
i2s_in_bclk_pin: GPIO7
mic_din_pin: GPIO4
i2s_out_lrclk_pin: GPIO45
i2s_out_bclk_pin: GPIO46
speaker_dout_pin: GPIO8
led_pin: GPIO16
led_num_leds: "8"
btn_vol_up_pin: GPIO1
btn_vol_down_pin: GPIO2
btn_mute_pin: GPIO3
btn_wake_pin: GPIO10
cpu_frequency: "240MHz"
esphome:
name: office-jarvis-satellite
friendly_name: "Office Jarvis"
wifi:
ssid: !secret wifi_iot_ssid
password: !secret wifi_password
ap:
ssid: "Jarvis-Satellite-AP"
password: !secret wifi_password
+133
View File
@@ -0,0 +1,133 @@
substitutions:
device_name: polly-a1-mini-camera
friendly_name: "Polly's A1 Camera"
esphome:
name: ${device_name}
friendly_name: ${friendly_name}
esp32:
board: esp32dev
framework:
type: esp-idf
# minimum_chip_revision: 3.1
# Enable logging
logger:
level: INFO
# Enable Home Assistant API
api:
encryption:
key: "iLF5QCgTVCwgaA5vjTGez03Dfjvns8JvkcwmUN4NXR8="
ota:
- platform: esphome
password: "58f86a5e60abe60886cec2f65a1d3da5"
wifi:
ssid: !secret wifi_iot_ssid
password: !secret wifi_password
manual_ip:
static_ip: 192.168.69.194
gateway: 192.168.69.1
subnet: 255.255.255.0
# Enable fallback hotspot (captive portal) in case wifi connection fails
ap:
ssid: "${friendly_name} Fallback"
password: !secret wifi_password
# Prevents aggressive reconnection attempts
power_save_mode: none # Use 'light' for battery devices
# Slower but more reliable connection
fast_connect: true
# Handle connection failures gracefully
on_connect:
- logger.log: "Wi-Fi connected!"
on_disconnect:
- logger.log: "Wi-Fi disconnected!"
# Reduce mDNS traffic
mdns:
disabled: true # Set to true if you use static IPs and don't need discovery
#captive_portal:
web_server:
port: 80
i2c:
- id: camera_i2c
sda: GPIO26
scl: GPIO27
psram:
mode: quad
speed: 80MHz
esp32_camera:
external_clock:
pin: GPIO0
frequency: 20MHz
i2c_id: camera_i2c
data_pins: [GPIO5, GPIO18, GPIO19, GPIO21, GPIO36, GPIO39, GPIO34, GPIO35]
vsync_pin: GPIO25
href_pin: GPIO23
pixel_clock_pin: GPIO22
power_down_pin: GPIO32
resolution: 1024X768
jpeg_quality: 20
vertical_flip: False
horizontal_mirror: True
max_framerate: 20 fps
idle_framerate: 0.05 fps
# Image settings
name: ${device_name}
esp32_camera_web_server:
- port: 8080
mode: STREAM
- port: 8081
mode: SNAPSHOT
switch:
- platform: gpio
name: "${device_name}-flash"
pin: 4
# Diagnostic sensors
sensor:
- platform: wifi_signal
name: "${device_name} WiFi Signal"
update_interval: 60s
- platform: uptime
name: "${device_name} Uptime"
update_interval: 60s
text_sensor:
- platform: wifi_info
ip_address:
name: "${device_name} IP Address"
ssid:
name: "${device_name} Connected SSID"
bssid:
name: "${device_name} BSSID"
# Watchdog to auto-reboot if things go wrong
interval:
- interval: 5min
then:
- if:
condition:
and:
- lambda: 'return millis() > 300000;' # >5 min uptime
- not:
wifi.connected:
then:
- logger.log: "Wi-Fi not connected for 5 min, rebooting..."
- delay: 10s
- lambda: 'App.safe_reboot();'
+10
View File
@@ -0,0 +1,10 @@
packages:
respeaker-satellite: !include respeaker-satellite/respeaker-satellite-base.yaml
esphome:
name: respeaker-satellite-fr
friendly_name: "Respeaker Satellite Family Room"
wifi:
ssid: !secret wifi_iot_ssid
password: !secret wifi_password
+10
View File
@@ -0,0 +1,10 @@
packages:
respeaker-satellite: !include respeaker-satellite/respeaker-satellite-base.yaml
esphome:
name: respeaker-satellite-lr
friendly_name: "Respeaker Satellite Living Room"
wifi:
ssid: !secret wifi_iot_ssid
password: !secret wifi_password
+10
View File
@@ -0,0 +1,10 @@
packages:
respeaker-satellite: !include respeaker-satellite/respeaker-satellite-base.yaml
esphome:
name: respeaker-satellite-mbr
friendly_name: "Respeaker Satellite Master Bedroom"
wifi:
ssid: !secret wifi_iot_ssid
password: !secret wifi_password
File diff suppressed because it is too large Load Diff
+3
View File
@@ -0,0 +1,3 @@
#pragma once
#include "driver/spi_master.h"
#include "driver/gpio.h"
+7
View File
@@ -0,0 +1,7 @@
# Counter helpers
# Example:
# daily_steps:
# name: Daily Steps
# initial: 0
# step: 1
# icon: mdi:counter
+9
View File
@@ -0,0 +1,9 @@
# For day camp automation
day_camp_today:
name: Day Camp Today
initial: off
# Placeholder used only in disabled/retired YAML automations to avoid references to deleted entities.
hermes_disabled_automation_placeholder:
name: Hermes Disabled Automation Placeholder
initial: off
+7
View File
@@ -0,0 +1,7 @@
# Input datetime helpers
# Example:
# alarm_time:
# name: Alarm Time
# has_time: true
# has_date: false
# icon: mdi:alarm
+15
View File
@@ -0,0 +1,15 @@
# Input number helpers
# Example:
# volume_level:
# name: Volume Level
# min: 0
# max: 100
# step: 1
# icon: mdi:volume-high
last_unavailable_count:
name: Last Unavailable Entity Count
min: 0
max: 10000
step: 1
mode: box
+9
View File
@@ -0,0 +1,9 @@
# Input select helpers
# Example:
# house_mode:
# name: House Mode
# options:
# - Home
# - Away
# - Night
# icon: mdi:home
+27
View File
@@ -0,0 +1,27 @@
notion_page_id_printcharming:
name: "Notion Page ID - PrintsCharming"
max: 100
notion_page_id_turd_flinger:
name: "Notion Page ID - Ds-Turd-Flinger"
max: 100
notion_page_id_polly_pocket:
name: "Notion Page ID - Polly-Pocket"
max: 100
notion_page_id_constipation_orion:
name: "Notion Page ID - Constipation-Orion"
max: 100
notion_page_id_jordyns_layer_slayer:
name: "Notion Page ID - Jordyns-Layer-Slayer"
max: 100
notion_page_id_centauri_carbon:
name: "Notion Page ID - Centauri Carbon"
max: 100
notion_page_id_saturn:
name: "Notion Page ID - Saturn"
max: 100
+37
View File
@@ -0,0 +1,37 @@
# REST commands to create and update Notion pages.
notion_create_print:
url: "https://api.notion.com/v1/pages"
method: POST
content_type: "application/json"
headers:
Authorization: !secret notion_token_bearer
Notion-Version: "2022-06-28"
payload: >
{
"parent": { "database_id": "c7bab550efd740909f656f101697832d" },
"properties": {
"File Name": { "title": [{ "text": { "content": "{{ file_name }}" } }] },
"Status": { "select": { "name": "Printing" } },
"Printer": { "select": { "name": "{{ printer_name }}" } },
"Filament Type": { "select": { "name": "{{ filament_type }}" } },
"Filament Color": { "rich_text": [{ "text": { "content": "{{ filament_color }}" } }] },
"date:Started:start": { "date": { "start": "{{ started }}", "time_zone": "America/New_York" } }
}
}
notion_update_print:
url: "https://api.notion.com/v1/pages/{{ page_id }}"
method: PATCH
content_type: "application/json"
headers:
Authorization: !secret notion_token_bearer
Notion-Version: "2022-06-28"
payload: >
{
"properties": {
"Status": { "select": { "name": "{{ status }}" } },
"Weight (g)": { "number": {{ weight }} },
"Length (mm)": { "number": {{ length }} },
"date:Completed:start": { "date": { "start": "{{ completed }}", "time_zone": "America/New_York" } }
}
}
+1
View File
@@ -0,0 +1 @@
git_autocommit: "/config/scripts/git_autocommit.sh"
+6
View File
@@ -0,0 +1,6 @@
# Timer helpers
# Example:
# bedtime_timer:
# name: Bedtime Timer
# duration: "00:30:00"
# icon: mdi:timer

Some files were not shown because too many files have changed in this diff Show More