initial commit
This commit is contained in:
+128
@@ -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
|
||||||
@@ -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 it’s 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: Declan’s 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: Declan’s Closet is Open
|
||||||
|
- action: notify.mobile_app_pollys_iphone
|
||||||
|
metadata: {}
|
||||||
|
data:
|
||||||
|
message: Declan’s Closet is Open
|
||||||
|
mode: single
|
||||||
|
|
||||||
|
- id: '1739841929885'
|
||||||
|
alias: Jordyn’s 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: Jordyn’s door is open
|
||||||
|
mode: single
|
||||||
|
|
||||||
|
- id: '1739842035606'
|
||||||
|
alias: Declan’s 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: Declan’s Closet is Closed
|
||||||
|
- action: notify.mobile_app_pollys_iphone
|
||||||
|
metadata: {}
|
||||||
|
data:
|
||||||
|
message: Declan’s 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
|
||||||
@@ -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
|
||||||
@@ -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: Jordyn’s 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: Jordyn’s 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: Jordyn’s 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 it’s 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': Let’s 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
|
||||||
@@ -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: D’s 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: D’s 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
|
||||||
|
}}
|
||||||
|
|
||||||
|
'
|
||||||
@@ -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 (0–1)
|
||||||
|
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
|
||||||
+43
@@ -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') }}"
|
||||||
@@ -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"
|
||||||
@@ -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
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
voice-alarms: !include voice-alarms.yaml
|
||||||
|
family-hub: !include family-hub.yaml
|
||||||
|
at-a-glance: !include at-a-glance.yaml
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
mode: yaml
|
||||||
|
title: Family Hub
|
||||||
|
icon: mdi:home-heart
|
||||||
|
show_in_sidebar: true
|
||||||
|
filename: dashboards/views/family-hub-dashboard.yaml
|
||||||
@@ -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
|
||||||
@@ -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
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
mode: yaml
|
||||||
|
title: Voice Alarms
|
||||||
|
icon: mdi:alarm-panel
|
||||||
|
show_in_sidebar: true
|
||||||
|
filename: dashboards/views/voice-alarms-dashboard.yaml
|
||||||
@@ -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
|
||||||
|
}
|
||||||
@@ -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"
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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
|
||||||
@@ -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);
|
||||||
|
}
|
||||||
|
};
|
||||||
@@ -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
|
||||||
@@ -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
|
||||||
@@ -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
|
||||||
@@ -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') }}
|
||||||
@@ -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
|
||||||
@@ -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
|
||||||
|
|
||||||
@@ -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
@@ -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,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
|
||||||
@@ -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)
|
||||||
@@ -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
|
||||||
@@ -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();'
|
||||||
@@ -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
|
||||||
@@ -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);
|
||||||
@@ -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
|
||||||
@@ -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)
|
||||||
@@ -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: '%'
|
||||||
@@ -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
@@ -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:
|
||||||
@@ -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') }}
|
||||||
@@ -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();'
|
||||||
@@ -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
|
||||||
@@ -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();'
|
||||||
@@ -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();'
|
||||||
@@ -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"
|
||||||
@@ -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
|
||||||
@@ -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();'
|
||||||
@@ -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
|
||||||
@@ -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
|
||||||
@@ -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
@@ -0,0 +1,3 @@
|
|||||||
|
#pragma once
|
||||||
|
#include "driver/spi_master.h"
|
||||||
|
#include "driver/gpio.h"
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
# Counter helpers
|
||||||
|
# Example:
|
||||||
|
# daily_steps:
|
||||||
|
# name: Daily Steps
|
||||||
|
# initial: 0
|
||||||
|
# step: 1
|
||||||
|
# icon: mdi:counter
|
||||||
@@ -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
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
# Input datetime helpers
|
||||||
|
# Example:
|
||||||
|
# alarm_time:
|
||||||
|
# name: Alarm Time
|
||||||
|
# has_time: true
|
||||||
|
# has_date: false
|
||||||
|
# icon: mdi:alarm
|
||||||
@@ -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
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
# Input select helpers
|
||||||
|
# Example:
|
||||||
|
# house_mode:
|
||||||
|
# name: House Mode
|
||||||
|
# options:
|
||||||
|
# - Home
|
||||||
|
# - Away
|
||||||
|
# - Night
|
||||||
|
# icon: mdi:home
|
||||||
@@ -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
|
||||||
@@ -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" } }
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
git_autocommit: "/config/scripts/git_autocommit.sh"
|
||||||
@@ -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
Reference in New Issue
Block a user