Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
aed97280b8 | ||
|
|
5f0080e11c | ||
|
|
af7f268888 | ||
|
|
994cfb6b60 | ||
|
|
12f8ba2def | ||
|
|
5b8a3f24c2 | ||
|
|
c5b7408b18 | ||
|
|
360e86e46a | ||
|
|
4626019a3e | ||
|
|
43f416ab69 | ||
|
|
0ec94a909e | ||
|
|
5e50c9a296 | ||
|
|
4104e995c0 | ||
|
|
d7f0584e17 | ||
|
|
472b66cf0e | ||
|
|
17633d5909 | ||
|
|
c469aa4aae | ||
|
|
0bd960dbf7 | ||
|
|
5a0ccf6b36 | ||
|
|
bdd3d4b0ae | ||
|
|
91c3996f93 | ||
|
|
3c940f8027 | ||
|
|
f672de484e | ||
|
|
19adc71fef | ||
|
|
495efeb672 | ||
|
|
14c50015a6 | ||
|
|
5e51a1f9aa | ||
|
|
34c4e54c13 | ||
|
|
f6f922f364 | ||
|
|
9d14e8a513 | ||
|
|
5bddada3b8 | ||
|
|
7b9d81b21e | ||
|
|
e7e22b48fb | ||
|
|
8c48e04ad9 | ||
|
|
d28e388eda | ||
|
|
35d815de79 | ||
|
|
2f4108eab5 | ||
|
|
72d92e46f6 | ||
|
|
fc8d869f69 | ||
|
|
f076657834 | ||
|
|
f2514f8b22 | ||
|
|
057744abce | ||
|
|
8ebac52160 | ||
|
|
0be12509d1 | ||
|
|
64249539d3 | ||
|
|
c69a867a2e | ||
|
|
9cabeaa06b | ||
|
|
352f59f4cf | ||
|
|
f46a8c7f58 | ||
|
|
25d4744f45 | ||
|
|
71f8b14408 | ||
|
|
8ad42f1d7f | ||
|
|
d7da8a7805 | ||
|
|
ac3d126bc1 |
@@ -61,6 +61,9 @@
|
||||
### ESPHome Folders ###
|
||||
!esphome/
|
||||
!esphome/fonts/**
|
||||
!esphome/bambu-display/
|
||||
!esphome/bambu-display/**
|
||||
esphome/bambu-display/**/__pycache__/
|
||||
!esphome/ha-remote/
|
||||
!esphome/ha-remote/components/
|
||||
!esphome/ha-remote/components/**
|
||||
@@ -101,6 +104,7 @@ ip_bans.yaml
|
||||
# =============================================
|
||||
# Generated files and folders - always ignore
|
||||
# =============================================
|
||||
esphome/.device-builder.json
|
||||
.storage
|
||||
.cloud
|
||||
.google.token
|
||||
|
||||
@@ -0,0 +1,34 @@
|
||||
- id: '1792458749185'
|
||||
alias: Declan - NestQuest Access Block
|
||||
description: Blocks network access for Declan's devices once the remaining daily
|
||||
quest sensor reaches zero.
|
||||
use_blueprint:
|
||||
path: Maniacs-Mansion/unifi_quest_access.yaml
|
||||
input:
|
||||
remaining_sensor: sensor.nestquest_declan_quests_remaining_today
|
||||
client_switches:
|
||||
- switch.declan_iphone_blocked
|
||||
- switch.default_blocked_2
|
||||
- switch.iphone_blocked_2
|
||||
- switch.iphone_blocked_3
|
||||
- id: '1792458829477'
|
||||
alias: Jordyn - NestQuest Access Block
|
||||
description: Automatically blocks network access for Jordyn's devices once the remaining
|
||||
daily quests sensor reaches zero.
|
||||
use_blueprint:
|
||||
path: nestquest/unifi_quest_access.yaml
|
||||
input:
|
||||
remaining_sensor: sensor.nestquest_jordyn_quests_remaining_today
|
||||
client_switches:
|
||||
- switch.jordyn_iphone_blocked
|
||||
- switch.default_blocked_3
|
||||
- id: '1792458886560'
|
||||
alias: Chloe - NestQuest Access Block
|
||||
description: Automatically blocks network access for Chloe's device once the daily
|
||||
NestQuest allowance has been exhausted.
|
||||
use_blueprint:
|
||||
path: nestquest/unifi_quest_access.yaml
|
||||
input:
|
||||
remaining_sensor: sensor.nestquest_chloe_quests_remaining_today
|
||||
client_switches:
|
||||
- switch.switch_v1_blocked
|
||||
|
||||
@@ -1,987 +0,0 @@
|
||||
# Home and lighting automations.
|
||||
|
||||
- id: '1760000000001'
|
||||
alias: Fireplace mantle outlet morning schedule
|
||||
description: Turn the fireplace mantle outlet on each morning.
|
||||
triggers:
|
||||
- trigger: time
|
||||
at: '05:30:00'
|
||||
conditions: []
|
||||
actions:
|
||||
- action: switch.turn_on
|
||||
target:
|
||||
entity_id: switch.fireplace
|
||||
mode: single
|
||||
|
||||
# Evening living-room motion automations disabled.
|
||||
#
|
||||
# - 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: '1788748800000'
|
||||
alias: Living room nightly shutoff
|
||||
description: Turn off all living-room lights and the fireplace at bedtime.
|
||||
triggers:
|
||||
- trigger: time
|
||||
at: '22:00:00'
|
||||
id: weeknight
|
||||
- trigger: time
|
||||
at: '00:00:00'
|
||||
id: weekend_night
|
||||
conditions:
|
||||
- condition: or
|
||||
conditions:
|
||||
- condition: and
|
||||
conditions:
|
||||
- condition: trigger
|
||||
id: weeknight
|
||||
- condition: time
|
||||
weekday:
|
||||
- sun
|
||||
- mon
|
||||
- tue
|
||||
- wed
|
||||
- thu
|
||||
- condition: and
|
||||
conditions:
|
||||
- condition: trigger
|
||||
id: weekend_night
|
||||
- condition: time
|
||||
weekday:
|
||||
- sat
|
||||
- sun
|
||||
actions:
|
||||
- action: light.turn_off
|
||||
target:
|
||||
entity_id:
|
||||
- light.living_room_light_2
|
||||
- light.living_room_lamp_1
|
||||
- light.small_living_room_lamp
|
||||
- action: switch.turn_off
|
||||
target:
|
||||
entity_id: switch.fireplace
|
||||
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: 05: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: 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: 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
|
||||
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
|
||||
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: '22:00:00'
|
||||
before: 02:00:00
|
||||
weekday:
|
||||
- sat
|
||||
- fri
|
||||
- thu
|
||||
- wed
|
||||
- tue
|
||||
- mon
|
||||
- sun
|
||||
- condition: time
|
||||
after: 09:00:00
|
||||
before: '16: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: 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: 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: 08: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
|
||||
|
||||
- id: freddy_fazbear_mask_schedule
|
||||
alias: Freddy Fazbear Mask - Sunrise on, bedtime off
|
||||
description: Turn the Freddy Fazbear Mask on at sunrise; off at 10:00 PM Sun-Thu and 11:59 PM Fri-Sat.
|
||||
mode: single
|
||||
triggers:
|
||||
- trigger: sun
|
||||
event: sunrise
|
||||
id: mask_on
|
||||
- trigger: time
|
||||
at: '22:00:00'
|
||||
id: mask_off_school_night
|
||||
- trigger: time
|
||||
at: '23:59:00'
|
||||
id: mask_off_weekend
|
||||
conditions: []
|
||||
actions:
|
||||
- choose:
|
||||
- conditions:
|
||||
- condition: trigger
|
||||
id: mask_on
|
||||
sequence:
|
||||
- action: homeassistant.turn_on
|
||||
target:
|
||||
entity_id: switch.freddy_fazbear_mask
|
||||
- conditions:
|
||||
- condition: trigger
|
||||
id: mask_off_school_night
|
||||
- condition: time
|
||||
weekday:
|
||||
- sun
|
||||
- mon
|
||||
- tue
|
||||
- wed
|
||||
- thu
|
||||
sequence:
|
||||
- action: homeassistant.turn_off
|
||||
target:
|
||||
entity_id: switch.freddy_fazbear_mask
|
||||
- conditions:
|
||||
- condition: trigger
|
||||
id: mask_off_weekend
|
||||
- condition: time
|
||||
weekday:
|
||||
- fri
|
||||
- sat
|
||||
sequence:
|
||||
- action: homeassistant.turn_off
|
||||
target:
|
||||
entity_id: switch.freddy_fazbear_mask
|
||||
@@ -0,0 +1,87 @@
|
||||
##############################################
|
||||
############ OFFICE AUTOMATIONS ##############
|
||||
##############################################
|
||||
|
||||
- id: '1740099961116'
|
||||
alias: Office Lights turn 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
|
||||
- action: switch.turn_on
|
||||
target:
|
||||
entity_id: switch.office_wax_warmer_socket_1
|
||||
- type: turn_on
|
||||
device_id: 397d7f06bfbbeaf3014d989a1e32ce07
|
||||
entity_id: 92505d68c2d00ef59e8ba61c3e3db32b
|
||||
domain: switch
|
||||
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: '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
|
||||
- action: switch.turn_off
|
||||
target:
|
||||
entity_id: switch.office_wax_warmer_socket_1
|
||||
- type: turn_off
|
||||
device_id: 397d7f06bfbbeaf3014d989a1e32ce07
|
||||
entity_id: 92505d68c2d00ef59e8ba61c3e3db32b
|
||||
domain: switch
|
||||
mode: single
|
||||
|
||||
- id: '1739997032502'
|
||||
alias: Office echo Plug Power Toggle Automation
|
||||
description: ''
|
||||
triggers:
|
||||
- trigger: time
|
||||
at: '20:00:00'
|
||||
conditions: []
|
||||
actions:
|
||||
- type: turn_off
|
||||
device_id: 31d67bb445f5c8920a1dc076d0d71e76
|
||||
entity_id: 6c1fd91e0ff15419f7bdb6b9e233cffc
|
||||
domain: switch
|
||||
mode: single
|
||||
@@ -0,0 +1,516 @@
|
||||
###############################################
|
||||
########### FIRST FLOOR AUTOMATIONS ###########
|
||||
###############################################
|
||||
|
||||
###################
|
||||
#### ALL AREAS ####
|
||||
###################
|
||||
|
||||
- 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: 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: '1739980877023'
|
||||
alias: First floor lights off at 9AM
|
||||
description: Turn off first floor lights, outdoor porch decoration outlets, and the Outside Lamppost at 9AM.
|
||||
triggers:
|
||||
- trigger: time
|
||||
at: 09:00:00
|
||||
conditions: []
|
||||
actions:
|
||||
- action: switch.turn_off
|
||||
continue_on_error: true
|
||||
target:
|
||||
entity_id:
|
||||
- switch.washing_machine_socket_1
|
||||
- switch.porch_fan_socket_1
|
||||
- switch.big_family_room_lamp
|
||||
- switch.studio_plug_1_2
|
||||
- switch.porch_decorations_3
|
||||
- switch.outside_right_porch_outlet_2
|
||||
- switch.outside_lamppost
|
||||
- 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: '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: 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: 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
|
||||
|
||||
###########################
|
||||
####### FAMILY ROOM #######
|
||||
###########################
|
||||
|
||||
- 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: 05: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
|
||||
enabled: false
|
||||
- type: turn_on
|
||||
device_id: 00ce230936f351558f177d5ed229759b
|
||||
entity_id: 9ef15fb77ca7e81bcb6c7551329f50aa
|
||||
domain: switch
|
||||
mode: single
|
||||
|
||||
- id: '1740929956187'
|
||||
alias: Turn off family room lights when empty
|
||||
description: ''
|
||||
triggers:
|
||||
- trigger: state
|
||||
entity_id:
|
||||
- binary_sensor.meat_heater_motion
|
||||
to: 'off'
|
||||
for:
|
||||
hours: 0
|
||||
minutes: 30
|
||||
seconds: 0
|
||||
- trigger: time
|
||||
at: '09:00:00'
|
||||
- trigger: time
|
||||
at: '22:00:00'
|
||||
- trigger: event
|
||||
event_type: automation_reloaded
|
||||
conditions:
|
||||
- condition: state
|
||||
entity_id: binary_sensor.meat_heater_motion
|
||||
state: 'off'
|
||||
for: '00:30:00'
|
||||
- condition: or
|
||||
conditions:
|
||||
- condition: time
|
||||
after: '22:00:00'
|
||||
before: 02:00:00
|
||||
weekday:
|
||||
- sat
|
||||
- fri
|
||||
- thu
|
||||
- wed
|
||||
- tue
|
||||
- mon
|
||||
- sun
|
||||
- condition: time
|
||||
after: 09:00:00
|
||||
before: '16:00:00'
|
||||
weekday:
|
||||
- sat
|
||||
- fri
|
||||
- thu
|
||||
- wed
|
||||
- tue
|
||||
- mon
|
||||
- sun
|
||||
actions:
|
||||
- type: turn_off
|
||||
device_id: 389d36aa5a0ecfd3eb2e1ec8795464d0
|
||||
entity_id: 0ae75b98556682467fedf0ff441346ef
|
||||
domain: light
|
||||
mode: single
|
||||
|
||||
- id: '1742691696852'
|
||||
alias: Family room turn 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: 77092d966014d6eb6222928cce911caf
|
||||
entity_id: af55f6e7e0ffa642407923fc6e8cdaf3
|
||||
domain: light
|
||||
- type: turn_off
|
||||
device_id: 6f341a97517eecd7e4befd4633bb0c3a
|
||||
entity_id: 4b21efa79430d63e813137d08a8df60e
|
||||
domain: light
|
||||
mode: single
|
||||
|
||||
- id: '1765116337200'
|
||||
alias: Turn off family room tv when empty
|
||||
description: ''
|
||||
triggers:
|
||||
- entity_id:
|
||||
- binary_sensor.hsjq_motion
|
||||
to:
|
||||
- 'off'
|
||||
for:
|
||||
hours: 0
|
||||
minutes: 15
|
||||
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: '1770433866161'
|
||||
alias: Turn off kitchen sink light 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
|
||||
|
||||
- id: '1788748800000'
|
||||
alias: Living room nightly shutoff
|
||||
description: Turn off all living-room lights and the fireplace at bedtime.
|
||||
triggers:
|
||||
- trigger: time
|
||||
at: '22:30:00'
|
||||
id: weeknight
|
||||
- trigger: time
|
||||
at: '00:00:00'
|
||||
id: weekend_night
|
||||
conditions:
|
||||
- condition: or
|
||||
conditions:
|
||||
- condition: and
|
||||
conditions:
|
||||
- condition: trigger
|
||||
id: weeknight
|
||||
- condition: time
|
||||
weekday:
|
||||
- sun
|
||||
- mon
|
||||
- tue
|
||||
- wed
|
||||
- thu
|
||||
- condition: and
|
||||
conditions:
|
||||
- condition: trigger
|
||||
id: weekend_night
|
||||
- condition: time
|
||||
weekday:
|
||||
- sat
|
||||
- sun
|
||||
actions:
|
||||
- action: light.turn_off
|
||||
target:
|
||||
entity_id:
|
||||
- light.living_room_light_2
|
||||
- light.living_room_lamp_1
|
||||
- light.small_living_room_lamp
|
||||
- action: switch.turn_off
|
||||
target:
|
||||
entity_id: switch.fireplace
|
||||
mode: single
|
||||
|
||||
- id: '1760000000001'
|
||||
alias: Fireplace mantle wax warmer on with motion
|
||||
description: Turn on the mantle wax warmer when motion is detected in the living room.
|
||||
triggers:
|
||||
- trigger: state
|
||||
entity_id: binary_sensor.living_room_motion_2
|
||||
to: 'on'
|
||||
conditions: []
|
||||
actions:
|
||||
- action: switch.turn_on
|
||||
target:
|
||||
entity_id: switch.fireplace
|
||||
mode: single
|
||||
|
||||
- id: 'fireplace_mantle_wax_warmer_off_when_empty'
|
||||
alias: Fireplace mantle wax warmer off when empty
|
||||
description: Turn off the mantle wax warmer after one hour without living room occupancy.
|
||||
triggers:
|
||||
- trigger: state
|
||||
entity_id: binary_sensor.living_room_occupancy
|
||||
to: 'off'
|
||||
for: '01:00:00'
|
||||
conditions: []
|
||||
actions:
|
||||
- action: switch.turn_off
|
||||
target:
|
||||
entity_id: switch.fireplace
|
||||
mode: single
|
||||
|
||||
- id: 'fireplace_mantle_outlet_morning_schedule'
|
||||
alias: Fireplace mantle outlet morning schedule
|
||||
description: Turn the fireplace mantle outlet on each morning.
|
||||
triggers:
|
||||
- trigger: time
|
||||
at: '05:30:00'
|
||||
conditions: []
|
||||
actions:
|
||||
- action: switch.turn_on
|
||||
target:
|
||||
entity_id: switch.fireplace
|
||||
mode: single
|
||||
@@ -0,0 +1,163 @@
|
||||
###################################################
|
||||
############ SECOND FLOOR AUTOMATIONS #############
|
||||
###################################################
|
||||
|
||||
###########
|
||||
### MBR ###
|
||||
###########
|
||||
|
||||
- id: '1756172550854'
|
||||
alias: Parents ceiling fan off at 8AM
|
||||
description: ''
|
||||
triggers:
|
||||
- trigger: time
|
||||
at: 08:00:00
|
||||
conditions: []
|
||||
actions:
|
||||
- type: turn_off
|
||||
device_id: 31622aa8682d29be0d631680776131c3
|
||||
entity_id: dbc4e84732121a8cc63eb1b6e95fd78b
|
||||
domain: switch
|
||||
mode: single
|
||||
|
||||
# - id: '1757038271421'
|
||||
# alias: Parents ceiling fan off when it’s below 55
|
||||
# description: Recheck the cold-weather cutoff when the nighttime window starts and after startup or reload.
|
||||
# triggers:
|
||||
# - trigger: numeric_state
|
||||
# entity_id:
|
||||
# - sensor.openweathermap_temperature
|
||||
# below: 55
|
||||
# - trigger: time
|
||||
# at: '23:00:00'
|
||||
# - trigger: homeassistant
|
||||
# event: start
|
||||
# - trigger: event
|
||||
# event_type: automation_reloaded
|
||||
# conditions:
|
||||
# - condition: numeric_state
|
||||
# entity_id: sensor.openweathermap_temperature
|
||||
# below: 55
|
||||
# - 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: Parents ceiling fan on above 70
|
||||
description: Turn on above 70 unless the nighttime cold-weather cutoff takes priority.
|
||||
triggers:
|
||||
- trigger: numeric_state
|
||||
entity_id:
|
||||
- sensor.bedroom_temperature
|
||||
above: 70
|
||||
conditions:
|
||||
- condition: template
|
||||
value_template: >-
|
||||
{{ not ((now().hour >= 23 or now().hour < 6)
|
||||
and is_number(states('sensor.openweathermap_temperature'))
|
||||
and states('sensor.openweathermap_temperature') | float(55) < 55) }}
|
||||
actions:
|
||||
- type: turn_on
|
||||
device_id: 31622aa8682d29be0d631680776131c3
|
||||
entity_id: dbc4e84732121a8cc63eb1b6e95fd78b
|
||||
domain: switch
|
||||
mode: single
|
||||
|
||||
- id: '1739971331649'
|
||||
alias: Master Bedroom bedside lights, ceiling fan and air purifier on at 8PM
|
||||
description: Turn on both bedside lights, the ceiling fan outlet and the air purifier at 20:00.
|
||||
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
|
||||
- action: switch.turn_on
|
||||
target:
|
||||
entity_id: switch.parents_ceiling_fan
|
||||
mode: single
|
||||
|
||||
- id: '1739835025769'
|
||||
alias: Turn on master 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
|
||||
mode: single
|
||||
|
||||
- id: '1739840523909'
|
||||
alias: Turn off master bedroom bedside lights in the evening when no one is in the room
|
||||
description: ''
|
||||
triggers:
|
||||
- trigger: state
|
||||
entity_id:
|
||||
- binary_sensor.bedroom_occupancy
|
||||
to: 'off'
|
||||
for:
|
||||
hours: 0
|
||||
minutes: 10
|
||||
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
|
||||
@@ -0,0 +1,104 @@
|
||||
################################################
|
||||
############## OUTSIDE AUTOMATIONS #############
|
||||
################################################
|
||||
|
||||
- id: '1769311377293'
|
||||
alias: Front Porch outlets turn off at 10P
|
||||
description: ''
|
||||
triggers:
|
||||
- trigger: time
|
||||
at: '22:00:00'
|
||||
conditions: []
|
||||
actions:
|
||||
- type: turn_off
|
||||
device_id: e04306aa947ba9901108c363d8109ca0
|
||||
entity_id: 12662d2294935b2a21500e3f6d69ba15
|
||||
domain: switch
|
||||
# Lamppost shutoff moved to Porch Decorations Schedule below.
|
||||
# - type: turn_off
|
||||
# device_id: c0de273e40c6f4f1cafe4db27e5a6782
|
||||
# entity_id: e6f4af2d9bc05634f994af34bad9a477
|
||||
# domain: switch
|
||||
- type: turn_off
|
||||
device_id: e2343a74462cc1434c4acfa2e32d1364
|
||||
entity_id: ea84e174f0fa3316b9923c4254454a37
|
||||
domain: switch
|
||||
mode: single
|
||||
|
||||
- id: porch_decorations_schedule
|
||||
alias: Porch Decorations Schedule
|
||||
description: >-
|
||||
Turn on Porch Decorations 1, Porch Decor 1, Porch Decor 2, Porch Decor 3,
|
||||
Porch Decor 1 Smart Plug, and Right Porch Outlet 2 one hour before sunset.
|
||||
Turn them off at 22:30 Sunday through Thursday and at 23:45 Friday and
|
||||
Saturday, along with the Outside Lamppost.
|
||||
mode: single
|
||||
triggers:
|
||||
- trigger: sun
|
||||
event: sunset
|
||||
offset: "-01:00:00"
|
||||
id: decorations_on
|
||||
- trigger: time
|
||||
at: "22:30:00"
|
||||
id: decorations_off_weeknight
|
||||
- trigger: time
|
||||
at: "23:45:00"
|
||||
id: decorations_off_weekend
|
||||
conditions: []
|
||||
actions:
|
||||
- choose:
|
||||
- conditions:
|
||||
- condition: trigger
|
||||
id: decorations_on
|
||||
sequence:
|
||||
- action: switch.turn_on
|
||||
continue_on_error: true
|
||||
target:
|
||||
entity_id:
|
||||
- switch.washing_machine_socket_1
|
||||
- switch.porch_fan_socket_1
|
||||
- switch.big_family_room_lamp
|
||||
- switch.studio_plug_1_2
|
||||
- switch.porch_decorations_3
|
||||
- switch.outside_right_porch_outlet_2
|
||||
- conditions:
|
||||
- condition: trigger
|
||||
id: decorations_off_weeknight
|
||||
- condition: time
|
||||
weekday:
|
||||
- sun
|
||||
- mon
|
||||
- tue
|
||||
- wed
|
||||
- thu
|
||||
sequence:
|
||||
- action: switch.turn_off
|
||||
continue_on_error: true
|
||||
target:
|
||||
entity_id:
|
||||
- switch.washing_machine_socket_1
|
||||
- switch.porch_fan_socket_1
|
||||
- switch.big_family_room_lamp
|
||||
- switch.studio_plug_1_2
|
||||
- switch.porch_decorations_3
|
||||
- switch.outside_right_porch_outlet_2
|
||||
- switch.outside_lamppost
|
||||
- conditions:
|
||||
- condition: trigger
|
||||
id: decorations_off_weekend
|
||||
- condition: time
|
||||
weekday:
|
||||
- fri
|
||||
- sat
|
||||
sequence:
|
||||
- action: switch.turn_off
|
||||
continue_on_error: true
|
||||
target:
|
||||
entity_id:
|
||||
- switch.washing_machine_socket_1
|
||||
- switch.porch_fan_socket_1
|
||||
- switch.big_family_room_lamp
|
||||
- switch.studio_plug_1_2
|
||||
- switch.porch_decorations_3
|
||||
- switch.outside_right_porch_outlet_2
|
||||
- switch.outside_lamppost
|
||||
@@ -0,0 +1,195 @@
|
||||
###################################################################
|
||||
################## HOLIDAY AUTOMATIONS ############################
|
||||
###################################################################
|
||||
|
||||
#################
|
||||
#### GENERAL ####
|
||||
#################
|
||||
|
||||
- id: '1759631437832'
|
||||
alias: 'Turn off decorations '
|
||||
description: Midnight recovery sweep for decorations, including porch outlets and the Outside Lamppost already shut off by the decorations schedule.
|
||||
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: eae417dc05ec7c9f1da72f76f32bff18
|
||||
entity_id: 8ebd9a84e00e9a8a4c05860cb01767ef
|
||||
domain: switch
|
||||
enabled: true
|
||||
- type: turn_off
|
||||
device_id: 89d1cfaee4e3bfdda1bbfe6b5bc7abb9
|
||||
entity_id: 416b554cc68bf1090bde534f837951dc
|
||||
domain: switch
|
||||
- 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
|
||||
enabled: false
|
||||
mode: single
|
||||
|
||||
###################
|
||||
#### HALLOWEEN ####
|
||||
###################
|
||||
|
||||
- id: freddy_fazbear_mask_schedule
|
||||
alias: Freddy Fazbear Mask - Sunrise on, bedtime off
|
||||
description: Turn the Freddy Fazbear Mask on at sunrise; off at 10:00 PM Sun-Thu and 11:59 PM Fri-Sat.
|
||||
mode: single
|
||||
triggers:
|
||||
- trigger: sun
|
||||
event: sunrise
|
||||
id: mask_on
|
||||
- trigger: time
|
||||
at: '22:00:00'
|
||||
id: mask_off_school_night
|
||||
- trigger: time
|
||||
at: '23:59:00'
|
||||
id: mask_off_weekend
|
||||
conditions: []
|
||||
actions:
|
||||
- choose:
|
||||
- conditions:
|
||||
- condition: trigger
|
||||
id: mask_on
|
||||
sequence:
|
||||
- action: homeassistant.turn_on
|
||||
target:
|
||||
entity_id: switch.freddy_fazbear_mask
|
||||
- conditions:
|
||||
- condition: trigger
|
||||
id: mask_off_school_night
|
||||
- condition: time
|
||||
weekday:
|
||||
- sun
|
||||
- mon
|
||||
- tue
|
||||
- wed
|
||||
- thu
|
||||
sequence:
|
||||
- action: homeassistant.turn_off
|
||||
target:
|
||||
entity_id: switch.freddy_fazbear_mask
|
||||
- conditions:
|
||||
- condition: trigger
|
||||
id: mask_off_weekend
|
||||
- condition: time
|
||||
weekday:
|
||||
- fri
|
||||
- sat
|
||||
sequence:
|
||||
- action: homeassistant.turn_off
|
||||
target:
|
||||
entity_id: switch.freddy_fazbear_mask
|
||||
|
||||
###################
|
||||
#### CHRISTMAS ####
|
||||
###################
|
||||
|
||||
- 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
|
||||
@@ -1,213 +0,0 @@
|
||||
# Safety, device, and household alert automations.
|
||||
- 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.notify_events_alerts
|
||||
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.notify_events_cats
|
||||
metadata: {}
|
||||
data:
|
||||
message: Triple Threat's Anti-Stinky Juice is Empty
|
||||
title: Triple Threat's Anti-Stinky Juice is Empty
|
||||
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.notify_events_cats
|
||||
metadata: {}
|
||||
data:
|
||||
message: The Deuce's Litter is Low
|
||||
title: The Deuce's Litter is Low
|
||||
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.notify_events_cats
|
||||
metadata: {}
|
||||
data:
|
||||
message: Triple Threat's Litter is Low
|
||||
title: Triple Threat's Litter is Low
|
||||
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.notify_events_cats
|
||||
metadata: {}
|
||||
data:
|
||||
message: Cat Crapper 3000's Litter is Low
|
||||
title: 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.notify_events_cats
|
||||
metadata: {}
|
||||
data:
|
||||
message: Poop Box's Litter is Low
|
||||
title: Poop Box's Litter is Low
|
||||
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.notify_events_cats
|
||||
metadata: {}
|
||||
data:
|
||||
message: Cat Crapper 3000 is full of crap
|
||||
title: CLEAN Cat Crapper 3000 - it's full of crap
|
||||
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.notify_events_cats
|
||||
metadata: {}
|
||||
data:
|
||||
message: The Deuce is full of deuces (laundry room)
|
||||
title: Too many dueces have been dropped!
|
||||
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.notify_events_cats
|
||||
metadata: {}
|
||||
data:
|
||||
message: The Poop Box has runneth over (basement, left side)
|
||||
title: The poop box is bursting!!
|
||||
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.notify_events_cats
|
||||
metadata: {}
|
||||
data:
|
||||
message: Triple Threat can't take no more! (Basement, right side)
|
||||
title: Triple Threat can't take no more!
|
||||
mode: single
|
||||
|
||||
- id: '1782932601001'
|
||||
alias: Luma Smart Litter Box Waste Bin Full Notification
|
||||
description: Notify when the Petlibro Luma Smart Litter Box waste bin is full.
|
||||
triggers:
|
||||
- trigger: state
|
||||
entity_id:
|
||||
- binary_sensor.luma_smart_litter_box_waste_bin_full
|
||||
enabled: true
|
||||
to: 'on'
|
||||
conditions: []
|
||||
actions:
|
||||
- action: notify.notify_events_cats
|
||||
metadata: {}
|
||||
data:
|
||||
message: Luma Smart Litter Box is full of crap
|
||||
title: CLEAN Luma Smart Litter Box - it's full of crap
|
||||
mode: single
|
||||
|
||||
- id: '1782932601004'
|
||||
alias: Luma Smart Litter Box Door Error Notification
|
||||
description: Notify when the Petlibro Luma Smart Litter Box reports a door error.
|
||||
triggers:
|
||||
- trigger: state
|
||||
entity_id:
|
||||
- binary_sensor.luma_smart_litter_box_door_error
|
||||
enabled: true
|
||||
to: 'on'
|
||||
conditions: []
|
||||
actions:
|
||||
- action: notify.notify_events_cats
|
||||
metadata: {}
|
||||
data:
|
||||
message: Luma Smart Litter Box has a door error
|
||||
title: Luma Smart Litter Box door error
|
||||
mode: single
|
||||
@@ -1,213 +0,0 @@
|
||||
# Safety, device, and household alert automations.
|
||||
- id: '1742155540986'
|
||||
alias: 'Garage Floodlight Low Battery Notification '
|
||||
description: ''
|
||||
triggers:
|
||||
- trigger: state
|
||||
entity_id: binary_sensor.garage_floodlight_battery
|
||||
to: 'on'
|
||||
conditions: []
|
||||
actions:
|
||||
- action: script.notify_events_alerts
|
||||
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.notify_events_cats
|
||||
metadata: {}
|
||||
data:
|
||||
message: Triple Threat's Anti-Stinky Juice is Empty
|
||||
title: Triple Threat's Anti-Stinky Juice is Empty
|
||||
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.notify_events_cats
|
||||
metadata: {}
|
||||
data:
|
||||
message: The Deuce's Litter is Low
|
||||
title: The Deuce's Litter is Low
|
||||
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.notify_events_cats
|
||||
metadata: {}
|
||||
data:
|
||||
message: Triple Threat's Litter is Low
|
||||
title: Triple Threat's Litter is Low
|
||||
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.notify_events_cats
|
||||
metadata: {}
|
||||
data:
|
||||
message: Cat Crapper 3000's Litter is Low
|
||||
title: 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.notify_events_cats
|
||||
metadata: {}
|
||||
data:
|
||||
message: Poop Box's Litter is Low
|
||||
title: Poop Box's Litter is Low
|
||||
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.notify_events_cats
|
||||
metadata: {}
|
||||
data:
|
||||
message: Cat Crapper 3000 is full of crap
|
||||
title: CLEAN Cat Crapper 3000 - it's full of crap
|
||||
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.notify_events_cats
|
||||
metadata: {}
|
||||
data:
|
||||
message: The Deuce is full of deuces (laundry room)
|
||||
title: Too many dueces have been dropped!
|
||||
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.notify_events_cats
|
||||
metadata: {}
|
||||
data:
|
||||
message: The Poop Box has runneth over (basement, left side)
|
||||
title: The poop box is bursting!!
|
||||
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.notify_events_cats
|
||||
metadata: {}
|
||||
data:
|
||||
message: Triple Threat can't take no more! (Basement, right side)
|
||||
title: Triple Threat can't take no more!
|
||||
mode: single
|
||||
|
||||
- id: '1782932601001'
|
||||
alias: Luma Smart Litter Box Waste Bin Full Notification
|
||||
description: Notify when the Petlibro Luma Smart Litter Box waste bin is full.
|
||||
triggers:
|
||||
- trigger: state
|
||||
entity_id:
|
||||
- binary_sensor.luma_smart_litter_box_waste_bin_full
|
||||
enabled: true
|
||||
to: 'on'
|
||||
conditions: []
|
||||
actions:
|
||||
- action: notify.notify_events_cats
|
||||
metadata: {}
|
||||
data:
|
||||
message: Luma Smart Litter Box is full of crap
|
||||
title: CLEAN Luma Smart Litter Box - it's full of crap
|
||||
mode: single
|
||||
|
||||
- id: '1782932601004'
|
||||
alias: Luma Smart Litter Box Door Error Notification
|
||||
description: Notify when the Petlibro Luma Smart Litter Box reports a door error.
|
||||
triggers:
|
||||
- trigger: state
|
||||
entity_id:
|
||||
- binary_sensor.luma_smart_litter_box_door_error
|
||||
enabled: true
|
||||
to: 'on'
|
||||
conditions: []
|
||||
actions:
|
||||
- action: notify.notify_events_cats
|
||||
metadata: {}
|
||||
data:
|
||||
message: Luma Smart Litter Box has a door error
|
||||
title: Luma Smart Litter Box door error
|
||||
mode: single
|
||||
@@ -0,0 +1,122 @@
|
||||
- id: '1742842372471'
|
||||
alias: The Poop Box has runneth over Notification
|
||||
description: Notify when the litter-box alert flag turns on.
|
||||
use_blueprint:
|
||||
path: local/litter_binary_notification.yaml
|
||||
input:
|
||||
alert_sensor: binary_sensor.poop_box_wastebin_filled
|
||||
title: The poop box is bursting!!
|
||||
message: The Poop Box has runneth over (basement, left side)
|
||||
|
||||
- id: '1742842458817'
|
||||
alias: Triple Threat Full Notification
|
||||
description: Notify when the litter-box alert flag turns on.
|
||||
use_blueprint:
|
||||
path: local/litter_binary_notification.yaml
|
||||
input:
|
||||
alert_sensor: binary_sensor.triple_threat_wastebin_filled
|
||||
title: Triple Threat can't take no more!
|
||||
message: Triple Threat can't take no more! (Basement, right side)
|
||||
|
||||
- id: '1782932601001'
|
||||
alias: Luma Smart Litter Box Waste Bin Full Notification
|
||||
description: Notify when the Petlibro Luma Smart Litter Box waste bin is full.
|
||||
use_blueprint:
|
||||
path: local/litter_binary_notification.yaml
|
||||
input:
|
||||
alert_sensor: binary_sensor.luma_smart_litter_box_waste_bin_full
|
||||
title: CLEAN Luma Smart Litter Box - it's full of crap
|
||||
message: Luma Smart Litter Box is full of crap
|
||||
|
||||
- id: '1782932601004'
|
||||
alias: Luma Smart Litter Box Door Error Notification
|
||||
description: Notify when the Petlibro Luma Smart Litter Box reports a door error.
|
||||
use_blueprint:
|
||||
path: local/litter_binary_notification.yaml
|
||||
input:
|
||||
alert_sensor: binary_sensor.luma_smart_litter_box_door_error
|
||||
title: Luma Smart Litter Box door error
|
||||
message: Luma Smart Litter Box has a door error
|
||||
|
||||
- id: '1742841764585'
|
||||
alias: Cat Crapper 3000 Low Litter Level Notification
|
||||
description: Alert at or below 65 after two minutes; refill above 65 rearms the alert.
|
||||
use_blueprint:
|
||||
path: local/litter_low_notification.yaml
|
||||
input:
|
||||
litter_sensor: sensor.cat_crapper_3000_litter_level
|
||||
threshold: 65
|
||||
title: Cat Crapper 3000's Litter is Low
|
||||
message: Cat Crapper 3000's Litter is Low
|
||||
|
||||
- id: '1742841838536'
|
||||
alias: Poop Box Low Litter Level Notification
|
||||
description: Alert at or below 65 after two minutes; refill above 65 rearms the alert.
|
||||
use_blueprint:
|
||||
path: local/litter_low_notification.yaml
|
||||
input:
|
||||
litter_sensor: sensor.poop_box_litter_level
|
||||
threshold: 65
|
||||
title: Poop Box's Litter is Low
|
||||
message: Poop Box's Litter is Low
|
||||
|
||||
- id: '1742842192548'
|
||||
alias: Cat Crapper 3000 is full of crap notification
|
||||
description: Notify when the litter-box alert flag turns on.
|
||||
use_blueprint:
|
||||
path: local/litter_binary_notification.yaml
|
||||
input:
|
||||
alert_sensor: binary_sensor.cat_crapper_3000_wastebin_filled
|
||||
title: CLEAN Cat Crapper 3000 - it's full of crap
|
||||
message: Cat Crapper 3000 is full of crap
|
||||
|
||||
- id: '1742842261527'
|
||||
alias: The Deuce is full of deuces notification
|
||||
description: Notify when the litter-box alert flag turns on.
|
||||
use_blueprint:
|
||||
path: local/litter_binary_notification.yaml
|
||||
input:
|
||||
alert_sensor: binary_sensor.the_deuce_wastebin_filled
|
||||
title: Too many dueces have been dropped!
|
||||
message: The Deuce is full of deuces (laundry room)
|
||||
|
||||
- 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.notify_events_cats
|
||||
metadata: {}
|
||||
data:
|
||||
message: Triple Threat's Anti-Stinky Juice is Empty
|
||||
title: Triple Threat's Anti-Stinky Juice is Empty
|
||||
mode: single
|
||||
|
||||
- id: '1742841650033'
|
||||
alias: The Deuce Litter Level
|
||||
description: Alert at or below 65 after two minutes; refill above 65 rearms the alert.
|
||||
use_blueprint:
|
||||
path: local/litter_low_notification.yaml
|
||||
input:
|
||||
litter_sensor: sensor.the_deuce_litter_level
|
||||
threshold: 65
|
||||
title: The Deuce's Litter is Low
|
||||
message: The Deuce's Litter is Low
|
||||
|
||||
- id: '1742841709247'
|
||||
alias: Triple Threat Litter Level Notification
|
||||
description: Alert at or below 65 after two minutes; refill above 65 rearms the alert.
|
||||
use_blueprint:
|
||||
path: local/litter_low_notification.yaml
|
||||
input:
|
||||
litter_sensor: sensor.triple_threat_litter_level
|
||||
threshold: 65
|
||||
title: Triple Threat's Litter is Low
|
||||
message: Triple Threat's Litter is Low
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
- id: '1742155540986'
|
||||
alias: 'Garage Floodlight Low Battery Notification '
|
||||
description: ''
|
||||
triggers:
|
||||
- trigger: state
|
||||
entity_id: binary_sensor.garage_floodlight_battery
|
||||
to: 'on'
|
||||
conditions: []
|
||||
actions:
|
||||
- action: script.notify_events_alerts
|
||||
metadata: {}
|
||||
data:
|
||||
message: Garage floodlight batteries low
|
||||
title: Garage floodlight batteries low
|
||||
mode: single
|
||||
@@ -1,107 +0,0 @@
|
||||
# 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,88 @@
|
||||
# # One controller owns the Blink group and its cameras' motion detection.
|
||||
# - id: '1758196295864'
|
||||
# alias: Inside Blink Cameras - Reconcile Family Presence
|
||||
# description: >-
|
||||
# Disarm when either parent is home. Enable camera motion detection and arm
|
||||
# only when both trackers have valid away states. Reconcile on startup,
|
||||
# automation reload and Blink recovery; unknown location never means away.
|
||||
# triggers:
|
||||
# - trigger: state
|
||||
# entity_id:
|
||||
# - device_tracker.joshuas_iphone_of_pain
|
||||
# - device_tracker.pollys_iphone
|
||||
# to: null
|
||||
# - trigger: homeassistant
|
||||
# event: start
|
||||
# - trigger: event
|
||||
# event_type: automation_reloaded
|
||||
# - trigger: state
|
||||
# entity_id: alarm_control_panel.blink_inside
|
||||
# to:
|
||||
# - armed_away
|
||||
# - disarmed
|
||||
# conditions: []
|
||||
# actions:
|
||||
# - choose:
|
||||
# - conditions:
|
||||
# - condition: template
|
||||
# value_template: >-
|
||||
# {{ is_state('device_tracker.joshuas_iphone_of_pain', 'home')
|
||||
# or is_state('device_tracker.pollys_iphone', 'home') }}
|
||||
# sequence:
|
||||
# - if:
|
||||
# - condition: template
|
||||
# value_template: >-
|
||||
# {{ has_value('alarm_control_panel.blink_inside')
|
||||
# and not is_state('alarm_control_panel.blink_inside', 'disarmed') }}
|
||||
# then:
|
||||
# - action: alarm_control_panel.alarm_disarm
|
||||
# continue_on_error: true
|
||||
# target:
|
||||
# entity_id: alarm_control_panel.blink_inside
|
||||
# # Recheck after the network action, before changing camera settings.
|
||||
# - condition: template
|
||||
# value_template: >-
|
||||
# {{ is_state('device_tracker.joshuas_iphone_of_pain', 'home')
|
||||
# or is_state('device_tracker.pollys_iphone', 'home') }}
|
||||
# - action: camera.disable_motion_detection
|
||||
# continue_on_error: true
|
||||
# target:
|
||||
# device_id:
|
||||
# - 3df00a0796efe006e4ea349a9752faba
|
||||
# - 0c13483d761efe3b85e534fadd323fe3
|
||||
# - 5aa158596e48441ea606dd881a34402d
|
||||
# - d045a0a68c3c4d52a7beb2a28fdf6993
|
||||
# - 044c43ea05ceff58b1209f6441c24183
|
||||
# - conditions:
|
||||
# - condition: template
|
||||
# value_template: >-
|
||||
# {{ states('device_tracker.joshuas_iphone_of_pain')
|
||||
# not in ['home', 'unknown', 'unavailable', '']
|
||||
# and states('device_tracker.pollys_iphone')
|
||||
# not in ['home', 'unknown', 'unavailable', '']
|
||||
# and has_value('alarm_control_panel.blink_inside') }}
|
||||
# sequence:
|
||||
# - action: camera.enable_motion_detection
|
||||
# # One unavailable camera must not prevent arming the rest of the group.
|
||||
# continue_on_error: true
|
||||
# target:
|
||||
# device_id:
|
||||
# - 3df00a0796efe006e4ea349a9752faba
|
||||
# - 0c13483d761efe3b85e534fadd323fe3
|
||||
# - 5aa158596e48441ea606dd881a34402d
|
||||
# - d045a0a68c3c4d52a7beb2a28fdf6993
|
||||
# - 044c43ea05ceff58b1209f6441c24183
|
||||
# - condition: template
|
||||
# value_template: >-
|
||||
# {{ states('device_tracker.joshuas_iphone_of_pain')
|
||||
# not in ['home', 'unknown', 'unavailable', '']
|
||||
# and states('device_tracker.pollys_iphone')
|
||||
# not in ['home', 'unknown', 'unavailable', ''] }}
|
||||
# - if:
|
||||
# - condition: template
|
||||
# value_template: "{{ not is_state('alarm_control_panel.blink_inside', 'armed_away') }}"
|
||||
# then:
|
||||
# - action: alarm_control_panel.alarm_arm_away
|
||||
# target:
|
||||
# entity_id: alarm_control_panel.blink_inside
|
||||
# mode: restart
|
||||
@@ -1,739 +0,0 @@
|
||||
# Kids routines, school prep, and announcement automations.
|
||||
|
||||
- id: '1788133654306'
|
||||
alias: Declan - Non-school day afternoon medication reminder
|
||||
description: Remind Declan to take his medicine at 2 PM on non-school days.
|
||||
triggers:
|
||||
- trigger: time
|
||||
at: '14:00:00'
|
||||
conditions:
|
||||
- condition: or
|
||||
conditions:
|
||||
- condition: time
|
||||
weekday:
|
||||
- sat
|
||||
- sun
|
||||
- condition: state
|
||||
entity_id: calendar.holidays_in_united_states
|
||||
state: 'on'
|
||||
- condition: template
|
||||
value_template: >
|
||||
{{ states('calendar.family_events') == 'on'
|
||||
and 'no school' in (state_attr('calendar.family_events', 'message')
|
||||
| default('') | lower) }}
|
||||
actions:
|
||||
- action: notify.alexa_media
|
||||
data:
|
||||
target:
|
||||
- media_player.living_room
|
||||
- media_player.declan_s_room
|
||||
message: Declan, this is your reminder to take your afternoon medicine.
|
||||
- action: notify.notify_events_alerts
|
||||
data:
|
||||
title: Declan medication reminder
|
||||
message: Declan's afternoon medicine reminder.
|
||||
mode: single
|
||||
|
||||
- id: '1788133654303'
|
||||
alias: Set weekday announcement speakers to volume 5
|
||||
description: Set the school-announcement speakers to volume level 5 each weekday.
|
||||
triggers:
|
||||
- trigger: time
|
||||
at: '07:10:00'
|
||||
conditions:
|
||||
- condition: time
|
||||
weekday:
|
||||
- mon
|
||||
- tue
|
||||
- wed
|
||||
- thu
|
||||
- fri
|
||||
actions:
|
||||
- action: media_player.volume_set
|
||||
target:
|
||||
entity_id:
|
||||
- media_player.family_room
|
||||
- media_player.living_room
|
||||
- media_player.kitchen
|
||||
- media_player.dinning_room
|
||||
- media_player.declan_s_room
|
||||
data:
|
||||
volume_level: 0.5
|
||||
mode: single
|
||||
|
||||
- id: '1788133651043'
|
||||
alias: Set Jordyns announcement speaker to volume 6
|
||||
description: Set the school-announcement speakers to volume level 6 each weekday.
|
||||
triggers:
|
||||
- trigger: time
|
||||
at: '07:35:00'
|
||||
conditions:
|
||||
- condition: time
|
||||
weekday:
|
||||
- tue
|
||||
actions:
|
||||
- action: media_player.volume_set
|
||||
target:
|
||||
entity_id:
|
||||
- media_player.jordyn_s_room
|
||||
data:
|
||||
volume_level: 0.6
|
||||
mode: single
|
||||
|
||||
- id: '1743380000904'
|
||||
alias: Jordyn OT Wake Up Call 1
|
||||
description: ''
|
||||
triggers:
|
||||
- at: '07:40:00'
|
||||
trigger: time
|
||||
enabled: true
|
||||
conditions:
|
||||
- condition: time
|
||||
weekday:
|
||||
- tue
|
||||
actions:
|
||||
- data:
|
||||
message: Jordyn, time to wake up and get out of bed for OT! Get dressed, put on deodorant, brush your teeth, and brush your hair. You got this!
|
||||
action: notify.alexa_media_jordyn_s_room
|
||||
mode: single
|
||||
|
||||
- id: '1743380036904'
|
||||
alias: Jordyn OT Wake Up Call 2
|
||||
description: ''
|
||||
triggers:
|
||||
- at: '07:43:00'
|
||||
trigger: time
|
||||
enabled: true
|
||||
conditions:
|
||||
- condition: time
|
||||
weekday:
|
||||
- tue
|
||||
actions:
|
||||
- data:
|
||||
message: Jordyn, you need to wake up and get out of bed for OT! Get dressed, put on deodorant, brush your teeth, and brush your hair. You got this!
|
||||
action: notify.alexa_media_jordyn_s_room
|
||||
mode: single
|
||||
|
||||
- id: '1743382700904'
|
||||
alias: Jordyn OT Wake Up Call 3
|
||||
description: ''
|
||||
triggers:
|
||||
- at: '07:46:00'
|
||||
trigger: time
|
||||
enabled: true
|
||||
conditions:
|
||||
- condition: time
|
||||
weekday:
|
||||
- tue
|
||||
actions:
|
||||
- data:
|
||||
message: Jordyn, get out of bed! Move! Move! Move! Move! Get dressed, put on deodorant, brush your teeth, and brush your hair. You got this!
|
||||
action: notify.alexa_media_jordyn_s_room
|
||||
mode: single
|
||||
|
||||
- id: '1788133654304'
|
||||
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:30:00'
|
||||
trigger: time
|
||||
enabled: true
|
||||
conditions:
|
||||
- condition: time
|
||||
weekday:
|
||||
- fri
|
||||
- sat
|
||||
- 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 8 PM if the Google Family Calendar
|
||||
event is 'Girls at Home'
|
||||
triggers:
|
||||
- trigger: time
|
||||
at: '20:00:00'
|
||||
enabled: true
|
||||
conditions:
|
||||
- condition: time
|
||||
weekday:
|
||||
- sun
|
||||
- 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
|
||||
- media_player.jordyn_s_room
|
||||
- media_player.dining_room
|
||||
data:
|
||||
media:
|
||||
media_content_id: Jordyn Weekday Bedtime Routine
|
||||
media_content_type: routine
|
||||
metadata: {}
|
||||
action: media_player.play_media
|
||||
mode: single
|
||||
|
||||
- id: '1743311126135'
|
||||
alias: Jordyn-Bedtime-10-minute-warning-Notification
|
||||
description: Triggers an Alexa notification at 7:50 PM if the Google Family Calendar
|
||||
event is 'Girls at Home'
|
||||
triggers:
|
||||
- trigger: time
|
||||
at: '19:50:00'
|
||||
enabled: true
|
||||
conditions:
|
||||
- condition: time
|
||||
weekday:
|
||||
- sun
|
||||
- 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:
|
||||
- action: notify.alexa_media
|
||||
data:
|
||||
target:
|
||||
- media_player.living_room
|
||||
- media_player.dinning_room
|
||||
message: Jordyn - ten minutes left until it's time to put away screens and get a bedtime snack.
|
||||
mode: single
|
||||
|
||||
- id: '1743311106135'
|
||||
alias: Jordyn-Bedtime-10-minute-warning-weekend-Notification
|
||||
description: Triggers an Alexa notification at 8:20 PM if the Google Family Calendar
|
||||
event is 'Girls at Home'
|
||||
triggers:
|
||||
- trigger: time
|
||||
at: '20:20:00'
|
||||
enabled: true
|
||||
conditions:
|
||||
- condition: time
|
||||
weekday:
|
||||
- fri
|
||||
- sat
|
||||
- condition: state
|
||||
entity_id: calendar.family_events
|
||||
state: 'on'
|
||||
- condition: template
|
||||
value_template: '{{ state_attr(''calendar.family_events'', ''message'') == ''Girls
|
||||
at Home'' }}'
|
||||
actions:
|
||||
- action: notify.alexa_media
|
||||
data:
|
||||
target:
|
||||
- media_player.living_room
|
||||
- media_player.dinning_room
|
||||
message: Jordyn - ten minutes left until it's time to put away screens and get a bedtime snack.
|
||||
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: Jordyn - School Prep Announcement
|
||||
description: ''
|
||||
triggers:
|
||||
- at: 08:25:00
|
||||
trigger: time
|
||||
conditions:
|
||||
- condition: or
|
||||
conditions:
|
||||
- condition: time
|
||||
weekday:
|
||||
- wed
|
||||
- condition: and
|
||||
conditions:
|
||||
- condition: time
|
||||
weekday:
|
||||
- mon
|
||||
- condition: template
|
||||
value_template: '{{ now().strftime(''%U'') | int % 2 == 1 }}'
|
||||
actions:
|
||||
- target:
|
||||
entity_id: media_player.hisense_g204x_cd41
|
||||
action: media_player.media_pause
|
||||
data: {}
|
||||
- data:
|
||||
target:
|
||||
- media_player.living_room
|
||||
- media_player.jordyn_s_room
|
||||
- media_player.kitchen
|
||||
- media_player.dinning_room
|
||||
message: Jordyn this is your reminder to take your medicine if you haven't, pack your snack and fill your water bottle. Please make sure your
|
||||
hair is brushed nicely.
|
||||
action: notify.alexa_media
|
||||
mode: single
|
||||
|
||||
- id: '1788131534302'
|
||||
alias: Jordyn - Time for School Announcement
|
||||
description: ''
|
||||
triggers:
|
||||
- at: 08:38:00
|
||||
trigger: time
|
||||
conditions:
|
||||
- condition: or
|
||||
conditions:
|
||||
- condition: time
|
||||
weekday:
|
||||
- wed
|
||||
- condition: and
|
||||
conditions:
|
||||
- condition: time
|
||||
weekday:
|
||||
- mon
|
||||
- condition: template
|
||||
value_template: '{{ now().strftime(''%U'') | int % 2 == 1 }}'
|
||||
actions:
|
||||
- target:
|
||||
entity_id: media_player.hisense_g204x_cd41
|
||||
action: media_player.turn_off
|
||||
data: {}
|
||||
- data:
|
||||
target:
|
||||
- media_player.living_room
|
||||
- media_player.jordyn_s_room
|
||||
- media_player.kitchen
|
||||
- media_player.dinning_room
|
||||
- media_player.family_room
|
||||
message: Time for school! Last call to get your water bottle, snack, and put on your shoes. Good luck, have fun, don't die!!
|
||||
action: notify.alexa_media
|
||||
mode: single
|
||||
|
||||
|
||||
- id: '1788133654305'
|
||||
alias: Jordyn - School and OT Prep Announcement
|
||||
description: ''
|
||||
triggers:
|
||||
- at: 07:50:00
|
||||
trigger: time
|
||||
conditions:
|
||||
- condition: time
|
||||
weekday:
|
||||
- tue
|
||||
actions:
|
||||
- target:
|
||||
entity_id: media_player.hisense_g204x_cd41
|
||||
action: media_player.media_pause
|
||||
data: {}
|
||||
- data:
|
||||
target:
|
||||
- media_player.living_room
|
||||
- media_player.jordyn_s_room
|
||||
- media_player.kitchen
|
||||
- media_player.dinning_room
|
||||
message: Jordyn, you have OT today. This is your reminder to take your medicine if you haven't, pack your snack and fill your water bottle. Please make sure your
|
||||
hair is brushed nicely.
|
||||
action: notify.alexa_media
|
||||
mode: single
|
||||
|
||||
- id: '1756211851043'
|
||||
alias: Jordyn - Time for OT & School Announcement
|
||||
description: ''
|
||||
triggers:
|
||||
- at: 08:13:00
|
||||
trigger: time
|
||||
conditions:
|
||||
- condition: time
|
||||
weekday:
|
||||
- tue
|
||||
actions:
|
||||
- target:
|
||||
entity_id: media_player.hisense_g204x_cd41
|
||||
action: media_player.turn_off
|
||||
data: {}
|
||||
- data:
|
||||
target:
|
||||
- media_player.living_room
|
||||
- media_player.jordyn_s_room
|
||||
- media_player.kitchen
|
||||
- media_player.dinning_room
|
||||
message: Time to head out for OT and school! Last call to quickly get your water bottle, snack, and put on your shoes. Good luck, have fun, don't die!!
|
||||
action: notify.alexa_media
|
||||
mode: single
|
||||
|
||||
- id: '1788133654301'
|
||||
alias: Declan - School Prep Announcement
|
||||
description: ''
|
||||
triggers:
|
||||
- at: 07: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.kitchen
|
||||
- media_player.dinning_room
|
||||
message: Declan this is your reminder to pack your snack and fill your water bottle.
|
||||
action: notify.alexa_media
|
||||
mode: single
|
||||
|
||||
- id: '1788133654302'
|
||||
alias: Declan - Time for School Announcement
|
||||
description: ''
|
||||
triggers:
|
||||
- at: 07:38: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.kitchen
|
||||
- media_player.dinning_room
|
||||
message: Time for school! Last call to get your water bottle, snack, and put on your shoes. Good luck, have fun, don't die!!
|
||||
action: notify.alexa_media
|
||||
mode: single
|
||||
@@ -0,0 +1,73 @@
|
||||
####################################################################
|
||||
############ DAY CAMP ANNOUNCEMENTS AND AUTOMATIONS ################
|
||||
####################################################################
|
||||
|
||||
- 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:
|
||||
- condition: state
|
||||
entity_id: binary_sensor.day_camp_today
|
||||
state: 'on'
|
||||
- target:
|
||||
entity_id: media_player.hisense_g204x_cd41
|
||||
action: media_player.media_pause
|
||||
data: {}
|
||||
continue_on_error: true
|
||||
- data:
|
||||
message: 'Time to prepare for day camp. Please do the following: Fill water bottles, put on deodorant, brush teeth, and Jordyn, brush your hair.'
|
||||
targets:
|
||||
- media_player.living_room
|
||||
- media_player.declan_s_room
|
||||
- media_player.jordyn_s_room
|
||||
- media_player.kitchen
|
||||
action: script.family_announcement
|
||||
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:
|
||||
- condition: state
|
||||
entity_id: binary_sensor.day_camp_today
|
||||
state: 'on'
|
||||
- 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:
|
||||
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.'
|
||||
targets:
|
||||
- media_player.living_room
|
||||
action: script.family_announcement
|
||||
- target:
|
||||
entity_id: media_player.hisense_g204x_cd41
|
||||
action: media_player.turn_off
|
||||
data: {}
|
||||
continue_on_error: true
|
||||
mode: single
|
||||
|
||||
|
||||
@@ -0,0 +1,101 @@
|
||||
###################################################################
|
||||
################ DECLAN'S ALARMS AND ANNOUNCEMENTS ################
|
||||
###################################################################
|
||||
|
||||
- id: '1788133654306'
|
||||
alias: Declan - Non-school day afternoon medication reminder
|
||||
description: Remind Declan to take his medicine at 2 PM on non-school days.
|
||||
triggers:
|
||||
- trigger: time
|
||||
at: '14:00:00'
|
||||
conditions:
|
||||
- condition: state
|
||||
entity_id: binary_sensor.blackhawk_school_day
|
||||
state: 'off'
|
||||
actions:
|
||||
- action: script.family_announcement
|
||||
continue_on_error: true
|
||||
data:
|
||||
message: Declan, this is your reminder to take your afternoon medicine.
|
||||
targets:
|
||||
- media_player.living_room
|
||||
- media_player.declan_s_room
|
||||
- action: script.notify_events_alerts
|
||||
data:
|
||||
title: Declan medication reminder
|
||||
message: Declan's afternoon medicine reminder.
|
||||
mode: single
|
||||
|
||||
|
||||
- id: '1788133654301'
|
||||
alias: Declan - School Prep Announcement
|
||||
description: ''
|
||||
triggers:
|
||||
- at: 07:20:00
|
||||
trigger: time
|
||||
conditions:
|
||||
- condition: time
|
||||
weekday:
|
||||
- mon
|
||||
- tue
|
||||
- wed
|
||||
- thu
|
||||
- fri
|
||||
- condition: state
|
||||
entity_id: binary_sensor.blackhawk_school_day
|
||||
state: 'on'
|
||||
- condition: state
|
||||
entity_id: binary_sensor.day_camp_today
|
||||
state: 'off'
|
||||
actions:
|
||||
- target:
|
||||
entity_id: media_player.hisense_g204x_cd41
|
||||
action: media_player.media_pause
|
||||
data: {}
|
||||
continue_on_error: true
|
||||
- data:
|
||||
message: Declan this is your reminder to pack your snack and fill your water bottle.
|
||||
targets:
|
||||
- media_player.living_room
|
||||
- media_player.declan_s_room
|
||||
- media_player.kitchen
|
||||
action: script.family_announcement
|
||||
mode: single
|
||||
|
||||
|
||||
- id: '1788133654302'
|
||||
alias: Declan - Time for School Announcement
|
||||
description: ''
|
||||
triggers:
|
||||
- at: 07:38:00
|
||||
trigger: time
|
||||
conditions:
|
||||
- condition: time
|
||||
weekday:
|
||||
- mon
|
||||
- tue
|
||||
- wed
|
||||
- thu
|
||||
- fri
|
||||
- condition: state
|
||||
entity_id: binary_sensor.blackhawk_school_day
|
||||
state: 'on'
|
||||
- condition: state
|
||||
entity_id: binary_sensor.day_camp_today
|
||||
state: 'off'
|
||||
actions:
|
||||
- target:
|
||||
entity_id: media_player.hisense_g204x_cd41
|
||||
action: media_player.turn_off
|
||||
data: {}
|
||||
continue_on_error: true
|
||||
- data:
|
||||
message: Time for school! Last call to get your water bottle, snack, and put on your shoes. Good luck, have fun, don't die!!
|
||||
targets:
|
||||
- media_player.living_room
|
||||
- media_player.declan_s_room
|
||||
- media_player.kitchen
|
||||
action: script.family_announcement
|
||||
mode: single
|
||||
|
||||
|
||||
@@ -0,0 +1,367 @@
|
||||
#################################################################
|
||||
############## JORDYN'S ALARMS AND ANNOUNCEMENTS ################
|
||||
#################################################################
|
||||
|
||||
- id: '1788133651043'
|
||||
alias: Set Jordyns announcement speaker to volume 6
|
||||
description: Set Jordyn’s speaker to volume 6 before Tuesday OT reminders.
|
||||
triggers:
|
||||
- trigger: time
|
||||
at: '07:35:00'
|
||||
conditions:
|
||||
- condition: time
|
||||
weekday:
|
||||
- tue
|
||||
actions:
|
||||
- action: media_player.volume_set
|
||||
target:
|
||||
entity_id:
|
||||
- media_player.jordyn_s_room
|
||||
data:
|
||||
volume_level: 0.6
|
||||
mode: single
|
||||
|
||||
###################################################
|
||||
############### JORDYN'S OT ALARMS ################
|
||||
###################################################
|
||||
|
||||
- id: '1743380000904'
|
||||
alias: Jordyn - OT Wake Up Calls
|
||||
description: Tuesday wake-up calls at 07:40, 07:43 and 07:46, each with its own message.
|
||||
triggers:
|
||||
- trigger: time
|
||||
at: 07:40:00
|
||||
id: first
|
||||
- trigger: time
|
||||
at: 07:43:00
|
||||
id: second
|
||||
- trigger: time
|
||||
at: 07:46:00
|
||||
id: third
|
||||
conditions:
|
||||
- condition: time
|
||||
weekday:
|
||||
- tue
|
||||
actions:
|
||||
- variables:
|
||||
messages:
|
||||
first: Jordyn, time to wake up and get out of bed for OT! Get dressed, put on deodorant, brush your teeth, and brush your hair. You got this!
|
||||
second: Jordyn, you need to wake up and get out of bed for OT! Get dressed, put on deodorant, brush your teeth, and brush your hair. You got this!
|
||||
third: Jordyn, get out of bed! Move! Move! Move! Move! Get dressed, put on deodorant, brush your teeth, and brush your hair. You got this!
|
||||
- condition: template
|
||||
value_template: '{{ trigger is defined and trigger.id in messages }}'
|
||||
- action: script.family_announcement
|
||||
data:
|
||||
targets:
|
||||
- media_player.jordyn_s_room
|
||||
message: '{{ messages[trigger.id] }}'
|
||||
mode: queued
|
||||
|
||||
- id: '1788133654305'
|
||||
alias: Jordyn - School and OT Prep Announcement
|
||||
description: ''
|
||||
triggers:
|
||||
- at: 07:50:00
|
||||
trigger: time
|
||||
conditions:
|
||||
- condition: time
|
||||
weekday:
|
||||
- tue
|
||||
actions:
|
||||
- target:
|
||||
entity_id: media_player.hisense_g204x_cd41
|
||||
action: media_player.media_pause
|
||||
data: {}
|
||||
continue_on_error: true
|
||||
- data:
|
||||
message: Jordyn, you have OT today. This is your reminder to take your medicine if you haven't, pack your snack and fill your water bottle. Please make sure your hair is brushed nicely.
|
||||
targets:
|
||||
- media_player.living_room
|
||||
- media_player.jordyn_s_room
|
||||
- media_player.kitchen
|
||||
- media_player.family_room_show
|
||||
action: script.family_announcement
|
||||
mode: single
|
||||
|
||||
|
||||
- id: '1756211851043'
|
||||
alias: Jordyn - Time for OT & School Announcement
|
||||
description: ''
|
||||
triggers:
|
||||
- at: 08:13:00
|
||||
trigger: time
|
||||
conditions:
|
||||
- condition: time
|
||||
weekday:
|
||||
- tue
|
||||
actions:
|
||||
- target:
|
||||
entity_id: media_player.hisense_g204x_cd41
|
||||
action: media_player.turn_off
|
||||
data: {}
|
||||
continue_on_error: true
|
||||
- data:
|
||||
message: Time to head out for OT{{ ' and school' if is_state('binary_sensor.blackhawk_school_day', 'on') else '' }}! Last call to quickly get your water bottle, snack, and put on your shoes. Good luck, have fun, don't die!!
|
||||
targets:
|
||||
- media_player.living_room
|
||||
- media_player.jordyn_s_room
|
||||
- media_player.kitchen
|
||||
action: script.family_announcement
|
||||
mode: single
|
||||
|
||||
|
||||
#################################################
|
||||
############### JORDYN'S SCHOOL ALARMS ##########
|
||||
#################################################
|
||||
|
||||
- id: '1756211568290'
|
||||
alias: Jordyn - School Prep Announcement
|
||||
description: ''
|
||||
triggers:
|
||||
- at: 08:25:00
|
||||
trigger: time
|
||||
conditions:
|
||||
- condition: or
|
||||
conditions:
|
||||
- condition: time
|
||||
weekday:
|
||||
- wed
|
||||
- condition: and
|
||||
conditions:
|
||||
- condition: time
|
||||
weekday:
|
||||
- mon
|
||||
- condition: template
|
||||
value_template: '{{ is_state(''binary_sensor.girls_at_home'', ''on'') }}'
|
||||
- condition: state
|
||||
entity_id: binary_sensor.blackhawk_school_day
|
||||
state: 'on'
|
||||
- condition: state
|
||||
entity_id: binary_sensor.day_camp_today
|
||||
state: 'off'
|
||||
actions:
|
||||
- target:
|
||||
entity_id: media_player.hisense_g204x_cd41
|
||||
action: media_player.media_pause
|
||||
data: {}
|
||||
continue_on_error: true
|
||||
- data:
|
||||
message: Jordyn this is your reminder to take your medicine if you haven't, pack your snack and fill your water bottle. Please make sure your hair is brushed nicely.
|
||||
targets:
|
||||
- media_player.living_room
|
||||
- media_player.jordyn_s_room
|
||||
- media_player.kitchen
|
||||
action: script.family_announcement
|
||||
mode: single
|
||||
|
||||
|
||||
- id: '1788131534302'
|
||||
alias: Jordyn - Time for School Announcement
|
||||
description: ''
|
||||
triggers:
|
||||
- at: 08:41:00
|
||||
trigger: time
|
||||
conditions:
|
||||
- condition: or
|
||||
conditions:
|
||||
- condition: time
|
||||
weekday:
|
||||
- wed
|
||||
- condition: and
|
||||
conditions:
|
||||
- condition: time
|
||||
weekday:
|
||||
- mon
|
||||
- condition: template
|
||||
value_template: '{{ is_state(''binary_sensor.girls_at_home'', ''on'') }}'
|
||||
- condition: state
|
||||
entity_id: binary_sensor.blackhawk_school_day
|
||||
state: 'on'
|
||||
- condition: state
|
||||
entity_id: binary_sensor.day_camp_today
|
||||
state: 'off'
|
||||
actions:
|
||||
- target:
|
||||
entity_id: media_player.hisense_g204x_cd41
|
||||
action: media_player.turn_off
|
||||
data: {}
|
||||
continue_on_error: true
|
||||
- data:
|
||||
message: Time for school! Last call to get your water bottle, snack, and put on your shoes. Good luck, have fun, don't die!!
|
||||
targets:
|
||||
- media_player.living_room
|
||||
- media_player.jordyn_s_room
|
||||
- media_player.kitchen
|
||||
- media_player.family_room_show
|
||||
action: script.family_announcement
|
||||
mode: single
|
||||
|
||||
|
||||
##################################################
|
||||
############### JORDYN'S BEDTIME ALARMS ##########
|
||||
##################################################
|
||||
|
||||
- id: '1788133654304'
|
||||
alias: Jordyn-Bedtime-Snack-Notification-Weekend
|
||||
description: Triggers an Alexa notification at 8:30 PM if the Google Calendar event
|
||||
is 'Girls at Home'
|
||||
triggers:
|
||||
- at: '20:30:00'
|
||||
trigger: time
|
||||
enabled: true
|
||||
conditions:
|
||||
- condition: time
|
||||
weekday:
|
||||
- fri
|
||||
- sat
|
||||
- condition: state
|
||||
entity_id: binary_sensor.girls_at_home
|
||||
state: 'on'
|
||||
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 8 PM if the Google Family Calendar
|
||||
event is 'Girls at Home'
|
||||
triggers:
|
||||
- trigger: time
|
||||
at: '20:00:00'
|
||||
enabled: true
|
||||
conditions:
|
||||
- condition: time
|
||||
weekday:
|
||||
- sun
|
||||
- mon
|
||||
- tue
|
||||
- wed
|
||||
- condition: state
|
||||
entity_id: binary_sensor.girls_at_home
|
||||
state: 'on'
|
||||
actions:
|
||||
- target:
|
||||
entity_id:
|
||||
- media_player.living_room
|
||||
- media_player.jordyn_s_room
|
||||
data:
|
||||
media:
|
||||
media_content_id: Jordyn Weekday Bedtime Routine
|
||||
media_content_type: routine
|
||||
metadata: {}
|
||||
action: media_player.play_media
|
||||
mode: single
|
||||
|
||||
- id: '1743311126135'
|
||||
alias: Jordyn - Bedtime Ten Minute Warning
|
||||
description: Warn at 19:50 on Sun-Wed or 20:20 on Fri-Sat while Girls at Home is active.
|
||||
triggers:
|
||||
- trigger: time
|
||||
at: '19:50:00'
|
||||
id: weekday
|
||||
- trigger: time
|
||||
at: '20:20:00'
|
||||
id: weekend
|
||||
conditions:
|
||||
- condition: state
|
||||
entity_id: binary_sensor.girls_at_home
|
||||
state: 'on'
|
||||
- condition: or
|
||||
conditions:
|
||||
- condition: and
|
||||
conditions:
|
||||
- condition: trigger
|
||||
id: weekday
|
||||
- condition: time
|
||||
weekday:
|
||||
- sun
|
||||
- mon
|
||||
- tue
|
||||
- wed
|
||||
- condition: and
|
||||
conditions:
|
||||
- condition: trigger
|
||||
id: weekend
|
||||
- condition: time
|
||||
weekday:
|
||||
- fri
|
||||
- sat
|
||||
actions:
|
||||
- action: script.family_announcement
|
||||
data:
|
||||
message: Jordyn - ten minutes left until it's time to put away screens and get a bedtime snack.
|
||||
targets:
|
||||
- media_player.living_room
|
||||
mode: single
|
||||
|
||||
- id: '1743385356527'
|
||||
alias: Jordyn’s Light on at 7:30PM
|
||||
description: ''
|
||||
triggers:
|
||||
- trigger: time
|
||||
at: '19:30: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:30:00'
|
||||
conditions:
|
||||
- condition: and
|
||||
conditions:
|
||||
- condition: time
|
||||
weekday:
|
||||
- fri
|
||||
- sat
|
||||
- sun
|
||||
- condition: state
|
||||
entity_id: binary_sensor.girls_at_home
|
||||
state: 'on'
|
||||
actions:
|
||||
- type: turn_on
|
||||
device_id: ef731a5e6e666bf4fc20cc321f596029
|
||||
entity_id: 9629035816fddf7de8926e4e4c29aa34
|
||||
domain: switch
|
||||
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: '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
|
||||
@@ -0,0 +1,75 @@
|
||||
- id: '1744503044809'
|
||||
alias: Daily Alexa Announcements for Kids
|
||||
description: Weekday summer routine on days with no camp; messages use stable trigger IDs.
|
||||
triggers:
|
||||
- trigger: time
|
||||
at: 08:00:00
|
||||
id: breakfast
|
||||
- trigger: time
|
||||
at: 09:00:00
|
||||
id: morning_play
|
||||
- trigger: time
|
||||
at: '10:00:00'
|
||||
id: snack
|
||||
- trigger: time
|
||||
at: '10:15:00'
|
||||
id: quiet_play
|
||||
- trigger: time
|
||||
at: '11:00:00'
|
||||
id: creative
|
||||
- trigger: time
|
||||
at: '12:00:00'
|
||||
id: lunch
|
||||
- trigger: time
|
||||
at: '12:30:00'
|
||||
id: chores
|
||||
- trigger: time
|
||||
at: '13:00:00'
|
||||
id: tv_break
|
||||
- trigger: time
|
||||
at: '13:30:00'
|
||||
id: reading
|
||||
- trigger: time
|
||||
at: '14:00:00'
|
||||
id: movement
|
||||
- trigger: time
|
||||
at: '14:30:00'
|
||||
id: relax
|
||||
conditions:
|
||||
- condition: state
|
||||
entity_id: binary_sensor.kids_summer_schedule_day
|
||||
state: 'on'
|
||||
- condition: state
|
||||
entity_id: binary_sensor.day_camp_today
|
||||
state: 'off'
|
||||
actions:
|
||||
- variables:
|
||||
messages:
|
||||
breakfast: Time for breakfast and a little TV!
|
||||
morning_play: Outside play time! If it’s bad weather, do your learning activities inside.
|
||||
snack: Snack time!
|
||||
quiet_play: Time for quiet play or outside fun.
|
||||
creative: Creative time! Draw, color, or write something cool!
|
||||
lunch: Lunch time! Wash up and head to the table.
|
||||
chores: Happy Helper Time! Do your chores like a champ!
|
||||
tv_break: TV break — relax and enjoy.
|
||||
reading: Time to read a book or listen to a story.
|
||||
movement: Let’s move! Try a Minecraft brain break!
|
||||
relax: Time to relax with mindfulness or yoga.
|
||||
- condition: template
|
||||
value_template: '{{ trigger is defined and trigger.id in messages }}'
|
||||
- action: script.family_announcement
|
||||
data:
|
||||
message: '{{ messages[trigger.id] }}'
|
||||
targets:
|
||||
- media_player.living_room
|
||||
- media_player.jordyn_s_room
|
||||
- media_player.chloe_s_room
|
||||
- media_player.declan_s_room
|
||||
- media_player.kitchen
|
||||
delivery:
|
||||
type: announce
|
||||
method: all
|
||||
mode: queued
|
||||
|
||||
|
||||
@@ -0,0 +1,320 @@
|
||||
- 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
|
||||
|
||||
### Status Notifications ###
|
||||
|
||||
- id: '1767118574570'
|
||||
alias: Centauri Carbon Status Updates
|
||||
description: Centauri Carbon progress notifications; intentionally disabled at startup.
|
||||
initial_state: false
|
||||
use_blueprint:
|
||||
path: danielcherubini/elegoo_printer_progress.yaml
|
||||
input:
|
||||
error_status_reason_entity: sensor.centauri_carbon_error_status_reason
|
||||
notify_device: eabafa43fa1148e4facc8e5a4151958d
|
||||
current_status_entity: sensor.centauri_carbon_current_status
|
||||
print_status_entity: sensor.centauri_carbon_print_status
|
||||
percent_complete_entity: sensor.centauri_carbon_percent_complete
|
||||
|
||||
### Printer Light Automations ###
|
||||
|
||||
- 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: {}
|
||||
- action: switch.turn_on
|
||||
target:
|
||||
entity_id: switch.jordyn_s_a1_camera_jordyn_a1_camera_flash
|
||||
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
|
||||
data:
|
||||
color_temp_kelvin: 6500
|
||||
brightness_pct: 100
|
||||
- action: switch.turn_on
|
||||
target:
|
||||
entity_id: switch.declan_s_a1_camera_declan_a1_camera_flash
|
||||
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
|
||||
- action: switch.turn_off
|
||||
target:
|
||||
entity_id: switch.jordyn_s_a1_camera_jordyn_a1_camera_flash
|
||||
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
|
||||
data: {}
|
||||
- action: switch.turn_off
|
||||
target:
|
||||
entity_id: switch.declan_s_a1_camera_declan_a1_camera_flash
|
||||
mode: single
|
||||
- id: '1770944104889'
|
||||
alias: Prints-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: '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'',''paused'',''pause'',''prepare'',''preparing''] }}'
|
||||
actions:
|
||||
- action: light.turn_off
|
||||
metadata: {}
|
||||
data: {}
|
||||
target:
|
||||
entity_id: light.p1s_01p09c470102673_chamber_light
|
||||
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: Turn the chamber light off after a completed or cancelled print.
|
||||
triggers:
|
||||
- device_id: 615a0b19447425670a6b61ec2343c791
|
||||
domain: bambu_lab
|
||||
type: event_print_finished
|
||||
trigger: device
|
||||
- device_id: 615a0b19447425670a6b61ec2343c791
|
||||
domain: bambu_lab
|
||||
type: event_print_canceled
|
||||
trigger: device
|
||||
conditions: []
|
||||
actions:
|
||||
- action: light.turn_off
|
||||
metadata: {}
|
||||
target:
|
||||
entity_id: light.constipation_orion_chamber_light
|
||||
data: {}
|
||||
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
|
||||
data:
|
||||
color_temp_kelvin: 6500
|
||||
brightness_pct: 100
|
||||
- action: switch.turn_on
|
||||
target:
|
||||
entity_id: switch.polly_a1_mini_camera_polly_a1_mini_cam_flash
|
||||
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
|
||||
data: {}
|
||||
- action: switch.turn_off
|
||||
target:
|
||||
entity_id: switch.polly_a1_mini_camera_polly_a1_mini_cam_flash
|
||||
mode: single
|
||||
|
||||
### Printer Exhaust Fan Control ###
|
||||
|
||||
- id: fdm_printer_exhaust_fans
|
||||
alias: FDM Printer Exhaust Fans During Prints
|
||||
description: >-
|
||||
Turn on the FDM Printer Exhaust Fans outlet when Constipation Orion,
|
||||
Prints Charming, or Printney Spears (Centauri Carbon) is printing. Leave it
|
||||
on until all three have been finished for 30 minutes. A pause keeps the
|
||||
fans on. An unknown printer status does not start the shutoff timer.
|
||||
mode: restart
|
||||
triggers:
|
||||
- trigger: state
|
||||
entity_id:
|
||||
- sensor.constipation_orion_print_status
|
||||
- sensor.p1s_01p09c470102673_print_status
|
||||
- sensor.centauri_carbon_print_status
|
||||
- trigger: homeassistant
|
||||
event: start
|
||||
- trigger: event
|
||||
event_type: automation_reloaded
|
||||
conditions: []
|
||||
actions:
|
||||
- variables:
|
||||
active_states:
|
||||
- printing
|
||||
- running
|
||||
- pause
|
||||
- paused
|
||||
- pausing
|
||||
- prepare
|
||||
- preparing
|
||||
printers:
|
||||
- sensor.constipation_orion_print_status
|
||||
- sensor.p1s_01p09c470102673_print_status
|
||||
- sensor.centauri_carbon_print_status
|
||||
- if:
|
||||
- condition: template
|
||||
value_template: >-
|
||||
{{ expand(printers) | selectattr('state', 'in', active_states) | list | count > 0 }}
|
||||
then:
|
||||
- action: switch.turn_on
|
||||
target:
|
||||
entity_id: switch.studio_plug_2
|
||||
else:
|
||||
- condition: template
|
||||
value_template: "{{ trigger.platform == 'state' }}"
|
||||
- condition: template
|
||||
value_template: >-
|
||||
{{ expand(printers) | selectattr('state', 'in', ['unknown', 'unavailable']) | list | count == 0 }}
|
||||
- delay:
|
||||
minutes: 30
|
||||
- condition: template
|
||||
value_template: >-
|
||||
{{ expand(printers) | selectattr('state', 'in', active_states) | list | count == 0
|
||||
and expand(printers) | selectattr('state', 'in', ['unknown', 'unavailable']) | list | count == 0 }}
|
||||
- action: switch.turn_off
|
||||
target:
|
||||
entity_id: switch.studio_plug_2
|
||||
@@ -17,77 +17,24 @@
|
||||
detection_frequency: /30
|
||||
notification_settings: standard
|
||||
notification_service: notify.mobile_app_joshuas_iphone_of_pain
|
||||
|
||||
### Status Notifications ###
|
||||
|
||||
- id: '1767118574570'
|
||||
alias: Centauri Carbon Status Updates
|
||||
description: ''
|
||||
description: Centauri Carbon progress notifications; intentionally disabled at startup.
|
||||
initial_state: false
|
||||
use_blueprint:
|
||||
path: danielcherubini/elegoo_printer_progress.yaml
|
||||
input:
|
||||
error_status_reason_entity: sensor.saturn_iv_error_status_reason
|
||||
error_status_reason_entity: sensor.centauri_carbon_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
|
||||
current_status_entity: sensor.centauri_carbon_current_status
|
||||
print_status_entity: sensor.centauri_carbon_print_status
|
||||
percent_complete_entity: sensor.centauri_carbon_percent_complete
|
||||
|
||||
### Printer Light Automations ###
|
||||
|
||||
- id: '1770938475431'
|
||||
alias: Jordyn-A1-lights-on-during-print
|
||||
description: ''
|
||||
@@ -111,13 +58,6 @@
|
||||
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
|
||||
@@ -138,7 +78,6 @@
|
||||
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
|
||||
@@ -193,48 +132,12 @@
|
||||
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: Prints-Charming-Lights-On-Print-Started
|
||||
description: 'Repaired by agent: replaced deleted Bambu device trigger with P1S
|
||||
@@ -253,6 +156,31 @@
|
||||
entity_id: light.p1s_01p09c470102673_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'',''paused'',''pause'',''prepare'',''preparing''] }}'
|
||||
actions:
|
||||
- action: light.turn_off
|
||||
metadata: {}
|
||||
data: {}
|
||||
target:
|
||||
entity_id: light.p1s_01p09c470102673_chamber_light
|
||||
mode: single
|
||||
- id: '1770944259063'
|
||||
alias: Constipation-Orion-Light-On-When-Print-Starts
|
||||
description: ''
|
||||
@@ -271,12 +199,16 @@
|
||||
mode: single
|
||||
- id: '1770944373316'
|
||||
alias: Constipation-Orion-Light-Off-Print-Finishes
|
||||
description: ''
|
||||
description: Turn the chamber light off after a completed or cancelled print.
|
||||
triggers:
|
||||
- device_id: 615a0b19447425670a6b61ec2343c791
|
||||
domain: bambu_lab
|
||||
type: event_print_finished
|
||||
trigger: device
|
||||
- device_id: 615a0b19447425670a6b61ec2343c791
|
||||
domain: bambu_lab
|
||||
type: event_print_canceled
|
||||
trigger: device
|
||||
conditions: []
|
||||
actions:
|
||||
- action: light.turn_off
|
||||
@@ -285,31 +217,6 @@
|
||||
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: ''
|
||||
@@ -325,7 +232,6 @@
|
||||
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
|
||||
@@ -353,101 +259,68 @@
|
||||
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
|
||||
|
||||
### Printer Exhaust Fan Control ###
|
||||
|
||||
- id: fdm_printer_exhaust_fans
|
||||
alias: FDM Printer Exhaust Fans During Prints
|
||||
description: >-
|
||||
Turn on the FDM Printer Exhaust Fans outlet when Constipation Orion,
|
||||
Prints Charming, or Printney Spears (Centauri Carbon) is printing. Leave it
|
||||
on until all three have been finished for 30 minutes. A pause keeps the
|
||||
fans on. An unknown printer status does not start the shutoff timer.
|
||||
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
|
||||
entity_id:
|
||||
- sensor.constipation_orion_print_status
|
||||
- sensor.p1s_01p09c470102673_print_status
|
||||
- sensor.centauri_carbon_print_status
|
||||
- trigger: homeassistant
|
||||
event: start
|
||||
- trigger: event
|
||||
event_type: automation_reloaded
|
||||
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
|
||||
- variables:
|
||||
active_states:
|
||||
- printing
|
||||
- running
|
||||
- pause
|
||||
- paused
|
||||
- pausing
|
||||
- prepare
|
||||
- preparing
|
||||
printers:
|
||||
- sensor.constipation_orion_print_status
|
||||
- sensor.p1s_01p09c470102673_print_status
|
||||
- sensor.centauri_carbon_print_status
|
||||
- if:
|
||||
- condition: template
|
||||
value_template: >-
|
||||
{{ expand(printers) | selectattr('state', 'in', active_states) | list | count > 0 }}
|
||||
then:
|
||||
- action: switch.turn_on
|
||||
target:
|
||||
entity_id: switch.studio_plug_2
|
||||
else:
|
||||
- condition: template
|
||||
value_template: "{{ trigger.platform == 'state' }}"
|
||||
- condition: template
|
||||
value_template: >-
|
||||
{{ expand(printers) | selectattr('state', 'in', ['unknown', 'unavailable']) | list | count == 0 }}
|
||||
- delay:
|
||||
minutes: 30
|
||||
- condition: template
|
||||
value_template: >-
|
||||
{{ expand(printers) | selectattr('state', 'in', active_states) | list | count == 0
|
||||
and expand(printers) | selectattr('state', 'in', ['unknown', 'unavailable']) | list | count == 0 }}
|
||||
- action: switch.turn_off
|
||||
target:
|
||||
entity_id: switch.studio_plug_2
|
||||
@@ -0,0 +1,71 @@
|
||||
- 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 Resin Heater after Print
|
||||
description: >-
|
||||
Turn off the heater five minutes after a terminal print status. Any status
|
||||
change cancels the pending shutoff; busy or unavailable printers never pass
|
||||
the shutdown check. Exhaust fan control is disabled pending replacement.
|
||||
triggers:
|
||||
- trigger: state
|
||||
entity_id:
|
||||
- sensor.saturn_iv_print_status
|
||||
- sensor.saturn_iv_current_status
|
||||
to: null
|
||||
- trigger: homeassistant
|
||||
event: start
|
||||
- trigger: event
|
||||
event_type: automation_reloaded
|
||||
conditions: []
|
||||
actions:
|
||||
- condition: template
|
||||
value_template: >-
|
||||
{{ states('sensor.saturn_iv_print_status')
|
||||
in ['complete', 'completed', 'finish', 'idle', 'failed', 'cancelled', 'canceled', 'stopped']
|
||||
and states('sensor.saturn_iv_current_status')
|
||||
in ['idle', 'complete', 'completed', 'finish', 'stopped'] }}
|
||||
- delay:
|
||||
minutes: 5
|
||||
- condition: template
|
||||
value_template: >-
|
||||
{{ states('sensor.saturn_iv_print_status')
|
||||
in ['complete', 'completed', 'finish', 'idle', 'failed', 'cancelled', 'canceled', 'stopped']
|
||||
and states('sensor.saturn_iv_current_status')
|
||||
in ['idle', 'complete', 'completed', 'finish', 'stopped'] }}
|
||||
- type: turn_off
|
||||
device_id: fc9f0c9a71eaa4bd6aa14417425f61cb
|
||||
entity_id: a91e4bedcc7c5d57563d88bd58d67c9c
|
||||
domain: switch
|
||||
- type: turn_off
|
||||
device_id: 1b1350195291ecd20cf7c69604c09699
|
||||
entity_id: 74c861175e7d5036c2253583f59d1100
|
||||
domain: switch
|
||||
enabled: false
|
||||
mode: restart
|
||||
- id: '1767816737180'
|
||||
alias: Saturn 4 Complete Notification
|
||||
description: Disabled because the Saturn progress blueprint already sends completion notifications.
|
||||
initial_state: false
|
||||
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
|
||||
@@ -47,6 +47,7 @@
|
||||
# data:
|
||||
# cycle: true
|
||||
# mode: single
|
||||
|
||||
# - id: '1772978826069'
|
||||
# alias: Kiosk Navigate Display
|
||||
# description: ''
|
||||
@@ -60,52 +61,3 @@
|
||||
# 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,49 @@
|
||||
# - 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
|
||||
# }}
|
||||
|
||||
# '
|
||||
@@ -1,111 +0,0 @@
|
||||
# Climate and plug automations.
|
||||
|
||||
- id: 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
|
||||
|
||||
- id: dehumidifier_tank_full_shutoff
|
||||
alias: Dehumidifier Tank Full - Shut Off Parents Window Fan
|
||||
description: When the dehumidifier tank level sensor detects water, turn off the Parents Window Fan outlet and send an alerts-channel notification.
|
||||
mode: single
|
||||
triggers:
|
||||
- trigger: state
|
||||
entity_id: binary_sensor.water_leak_sensor_moisture
|
||||
to: 'on'
|
||||
conditions: []
|
||||
actions:
|
||||
- action: switch.turn_off
|
||||
target:
|
||||
entity_id: switch.parents_window_fan
|
||||
- action: notify.notify_events_alerts
|
||||
data:
|
||||
|
||||
title: Dehumidifier tank is full
|
||||
message: Dehumidifier tank is full — Dehumidifer outlet has been turned off.
|
||||
|
||||
- id: ac_tank_full_shutoff
|
||||
alias: AC Tank Full - Shut Off Office AC Power
|
||||
description: When the AC Tank Sensor detects water, turn off the Office AC Power outlet and send an alerts-channel notification.
|
||||
mode: single
|
||||
triggers:
|
||||
- trigger: state
|
||||
entity_id: binary_sensor.ac_tank_sensor_moisture
|
||||
to: 'on'
|
||||
conditions: []
|
||||
actions:
|
||||
- action: switch.turn_off
|
||||
target:
|
||||
entity_id: switch.studio_plug_2
|
||||
- action: notify.notify_events_alerts
|
||||
data:
|
||||
title: AC tank is full
|
||||
message: AC Tank Sensor detected water — Office AC Power outlet has been turned off.
|
||||
|
||||
- id: dehumidifier_tank_sensor_low_battery
|
||||
alias: Dehumidifier Tank Sensor Low Battery
|
||||
description: Alert when the dehumidifier tank level sensor battery is at 10% or lower.
|
||||
mode: single
|
||||
triggers:
|
||||
- trigger: numeric_state
|
||||
entity_id: sensor.water_leak_sensor_battery
|
||||
below: 10.1
|
||||
conditions: []
|
||||
actions:
|
||||
- action: notify.notify_events_alerts
|
||||
data:
|
||||
title: Dehumidifier tank sensor battery low
|
||||
message: Dehumidifier tank level sensor battery is at {{ states('sensor.water_leak_sensor_battery') }}%. Replace soon.
|
||||
|
||||
- id: ac_tank_sensor_low_battery
|
||||
alias: AC Tank Sensor Low Battery
|
||||
description: Alert when the AC tank sensor battery is at 10% or lower.
|
||||
mode: single
|
||||
triggers:
|
||||
- trigger: numeric_state
|
||||
entity_id: sensor.ac_tank_sensor_battery
|
||||
below: 10.1
|
||||
conditions: []
|
||||
actions:
|
||||
- action: notify.notify_events_alerts
|
||||
data:
|
||||
title: AC tank sensor battery low
|
||||
message: AC Tank Sensor battery is at {{ states('sensor.ac_tank_sensor_battery') }}%. Replace soon.
|
||||
@@ -1,62 +0,0 @@
|
||||
# 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
|
||||
|
||||
- id: dehumidifier_tank_full_shutoff
|
||||
alias: Dehumidifier Tank Full - Shut Off Parents Window Fan
|
||||
description: When the dehumidifier tank level sensor detects water, turn off the Parents Window Fan outlet and send an alerts-channel notification.
|
||||
mode: single
|
||||
triggers:
|
||||
- trigger: state
|
||||
entity_id: binary_sensor.water_leak_sensor_moisture
|
||||
to: 'on'
|
||||
conditions: []
|
||||
actions:
|
||||
- action: switch.turn_off
|
||||
target:
|
||||
entity_id: switch.parents_window_fan
|
||||
- action: script.notify_events_alerts
|
||||
data:
|
||||
title: Dehumidifier tank is full
|
||||
message: Dehumidifier tank is full — Parents Window Fan outlet has been turned off.
|
||||
@@ -1,62 +0,0 @@
|
||||
# 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
|
||||
|
||||
- id: dehumidifier_tank_full_shutoff
|
||||
alias: Dehumidifier Tank Full - Shut Off Parents Window Fan
|
||||
description: When the dehumidifier tank level sensor detects water, turn off the Parents Window Fan outlet and send an alerts-channel notification.
|
||||
mode: single
|
||||
triggers:
|
||||
- trigger: state
|
||||
entity_id: binary_sensor.water_leak_sensor_moisture
|
||||
to: 'on'
|
||||
conditions: []
|
||||
actions:
|
||||
- action: switch.turn_off
|
||||
target:
|
||||
entity_id: switch.parents_window_fan
|
||||
- action: notify.notify_events_alerts
|
||||
data:
|
||||
title: Dehumidifier tank is full
|
||||
message: Dehumidifier tank is full — Parents Window Fan outlet has been turned off.
|
||||
@@ -0,0 +1,48 @@
|
||||
- id: ac_controls_matter_plugs
|
||||
alias: AC controls Matter plugs
|
||||
description: Reconcile each plug with current cooling activity after HVAC changes, startup, reload and plug recovery. Keep existing plug states if climate status is unknown.
|
||||
mode: restart
|
||||
triggers:
|
||||
- trigger: state
|
||||
entity_id: climate.meat_heater
|
||||
attribute: hvac_action
|
||||
to: null
|
||||
- trigger: state
|
||||
entity_id: climate.meat_heater
|
||||
to: null
|
||||
- trigger: state
|
||||
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
|
||||
to: null
|
||||
- trigger: homeassistant
|
||||
event: start
|
||||
- trigger: event
|
||||
event_type: automation_reloaded
|
||||
conditions: []
|
||||
actions:
|
||||
- condition: template
|
||||
value_template: >-
|
||||
{{ has_value('climate.meat_heater')
|
||||
and (is_state('climate.meat_heater', 'off')
|
||||
or state_attr('climate.meat_heater', 'hvac_action')
|
||||
not in [none, 'unknown', 'unavailable', '']) }}
|
||||
- variables:
|
||||
desired_state: "{{ 'on' if is_state_attr('climate.meat_heater', 'hvac_action', 'cooling') and not is_state('climate.meat_heater', 'off') else 'off' }}"
|
||||
- repeat:
|
||||
for_each:
|
||||
- switch.smart_wi_fi_plug
|
||||
- switch.smart_wi_fi_plug_2
|
||||
- switch.smart_wi_fi_plug_3
|
||||
- switch.smart_wi_fi_plug_4
|
||||
sequence:
|
||||
- if:
|
||||
- condition: template
|
||||
value_template: "{{ has_value(repeat.item) and not is_state(repeat.item, desired_state) }}"
|
||||
then:
|
||||
- action: "{{ 'switch.turn_on' if desired_state == 'on' else 'switch.turn_off' }}"
|
||||
continue_on_error: true
|
||||
target:
|
||||
entity_id: "{{ repeat.item }}"
|
||||
@@ -0,0 +1,25 @@
|
||||
- id: dehumidifier_tank_full_shutoff
|
||||
alias: Dehumidifier Tank Full - Shut Off Parents Window Fan
|
||||
description: Keep the outlet off while wet and report whether shutdown was confirmed.
|
||||
use_blueprint:
|
||||
path: local/tank_full_interlock.yaml
|
||||
input:
|
||||
moisture_sensor: binary_sensor.water_leak_sensor_moisture
|
||||
outlet: switch.parents_window_fan
|
||||
title: Dehumidifier tank is full
|
||||
equipment_name: Dehumidifier
|
||||
|
||||
- id: dehumidifier_tank_sensor_low_battery
|
||||
alias: Dehumidifier Tank Sensor Low Battery
|
||||
description: Alert when the dehumidifier tank level sensor battery is at 10% or lower.
|
||||
mode: single
|
||||
triggers:
|
||||
- trigger: numeric_state
|
||||
entity_id: sensor.water_leak_sensor_battery
|
||||
below: 10.1
|
||||
conditions: []
|
||||
actions:
|
||||
- action: script.notify_events_alerts
|
||||
data:
|
||||
title: Dehumidifier tank sensor battery low
|
||||
message: Dehumidifier tank level sensor battery is at {{ states('sensor.water_leak_sensor_battery') }}%. Replace soon.
|
||||
@@ -0,0 +1,29 @@
|
||||
- id: ac_tank_full_shutoff
|
||||
alias: AC Tank Full
|
||||
description: Alert when the AC tank sensor is wet. This does not switch an outlet.
|
||||
mode: single
|
||||
triggers:
|
||||
- trigger: state
|
||||
entity_id: binary_sensor.ac_tank_sensor_moisture
|
||||
to: "on"
|
||||
conditions: []
|
||||
actions:
|
||||
- action: script.notify_events_alerts
|
||||
data:
|
||||
title: AC tank is full
|
||||
message: AC tank sensor detected water.
|
||||
|
||||
- id: ac_tank_sensor_low_battery
|
||||
alias: AC Tank Sensor Low Battery
|
||||
description: Alert when the AC tank sensor battery is at 10% or lower.
|
||||
mode: single
|
||||
triggers:
|
||||
- trigger: numeric_state
|
||||
entity_id: sensor.ac_tank_sensor_battery
|
||||
below: 10.1
|
||||
conditions: []
|
||||
actions:
|
||||
- action: script.notify_events_alerts
|
||||
data:
|
||||
title: AC tank sensor battery low
|
||||
message: AC Tank Sensor battery is at {{ states('sensor.ac_tank_sensor_battery') }}%. Replace soon.
|
||||
@@ -0,0 +1,79 @@
|
||||
- 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
|
||||
- condition: state
|
||||
entity_id: binary_sensor.day_camp_today
|
||||
state: 'on'
|
||||
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: '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:
|
||||
- condition: state
|
||||
entity_id: binary_sensor.day_camp_today
|
||||
state: 'on'
|
||||
- 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:
|
||||
- condition: state
|
||||
entity_id: binary_sensor.day_camp_today
|
||||
state: 'off'
|
||||
- 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
|
||||
@@ -0,0 +1,28 @@
|
||||
- id: '1788133654303'
|
||||
alias: Set weekday announcement speakers to volume 5
|
||||
description: Set the school-announcement speakers to volume level 5 each weekday.
|
||||
triggers:
|
||||
- trigger: time
|
||||
at: '07:10:00'
|
||||
conditions:
|
||||
- condition: time
|
||||
weekday:
|
||||
- mon
|
||||
- tue
|
||||
- wed
|
||||
- thu
|
||||
- fri
|
||||
- condition: state
|
||||
entity_id: binary_sensor.blackhawk_school_day
|
||||
state: 'on'
|
||||
actions:
|
||||
- action: media_player.volume_set
|
||||
target:
|
||||
entity_id:
|
||||
- media_player.family_room_show
|
||||
- media_player.living_room
|
||||
- media_player.kitchen
|
||||
- media_player.declan_s_room
|
||||
data:
|
||||
volume_level: 0.5
|
||||
mode: single
|
||||
@@ -0,0 +1,67 @@
|
||||
blueprint:
|
||||
name: NestQuest afternoon reminder
|
||||
description: 'Sends an afternoon nudge listing ONLY the children who still have
|
||||
open quests today. Children who are present and all clear are omitted (the nudge
|
||||
is for stragglers, not a roll call), children who are not present today are omitted
|
||||
entirely, and the household rollup sensors are excluded by the same present guard.
|
||||
When every present child is already done the automation sends NOTHING at all:
|
||||
a template condition stops the run before the notification. The notify service
|
||||
is chosen by the household when importing this blueprint — NestQuest never hard-codes
|
||||
a personal notify target.'
|
||||
domain: automation
|
||||
input:
|
||||
afternoon_time:
|
||||
name: Afternoon time
|
||||
description: Local time of day the reminder is sent.
|
||||
default: '15:00'
|
||||
selector:
|
||||
time: {}
|
||||
notify_target:
|
||||
name: Notify service
|
||||
description: The Home Assistant notify service to call for the reminder, for
|
||||
example notify.mobile_app_your_phone — a placeholder; pick the notify service
|
||||
for your own device.
|
||||
selector:
|
||||
text:
|
||||
multiline: false
|
||||
multiple: false
|
||||
send_reminder:
|
||||
name: Send the reminder
|
||||
description: Turn off to keep the automation installed but skip the notification
|
||||
entirely.
|
||||
default: true
|
||||
selector:
|
||||
boolean: {}
|
||||
source_url: https://github.com/Maniacs-Mansion/NestQuest/blob/main/custom_components/nestquest/blueprints/automation/afternoon_reminder.yaml
|
||||
mode: single
|
||||
variables:
|
||||
send_reminder: !input send_reminder
|
||||
triggers:
|
||||
- trigger: time
|
||||
at: !input afternoon_time
|
||||
conditions:
|
||||
- condition: template
|
||||
value_template: '{{ send_reminder | bool }}'
|
||||
- condition: template
|
||||
value_template: "{%- set ns = namespace(any_open=false) -%}\n{%- for due in states.sensor\n
|
||||
\ | selectattr('entity_id', 'search', '^sensor\\\\.nestquest_.*_quests_due_today(_\\\\d+)?$')
|
||||
-%}\n {%- if due.attributes.get('present') -%}\n {%- set remaining_values
|
||||
= states.sensor\n | selectattr('entity_id', 'search', '^sensor\\\\.nestquest_.*_quests_remaining_today(_\\\\d+)?$')\n
|
||||
\ | selectattr('attributes.child_id', 'eq', due.attributes.child_id)\n
|
||||
\ | map(attribute='state')\n | list -%}\n {%- if remaining_values
|
||||
and (remaining_values | first | int) > 0 -%}\n {%- set ns.any_open = true
|
||||
-%}\n {%- endif -%}\n {%- endif -%}\n{%- endfor -%}\n{{- ns.any_open -}}\n"
|
||||
actions:
|
||||
- action: !input notify_target
|
||||
data:
|
||||
title: NestQuest afternoon reminder
|
||||
message: "{%- set ns = namespace(lines=[]) -%}\n{%- for due in states.sensor\n
|
||||
\ | selectattr('entity_id', 'search', '^sensor\\\\.nestquest_.*_quests_due_today(_\\\\d+)?$')
|
||||
-%}\n {%- if due.attributes.get('present') -%}\n {%- set remaining_values
|
||||
= states.sensor\n | selectattr('entity_id', 'search', '^sensor\\\\.nestquest_.*_quests_remaining_today(_\\\\d+)?$')\n
|
||||
\ | selectattr('attributes.child_id', 'eq', due.attributes.child_id)\n
|
||||
\ | map(attribute='state')\n | list -%}\n {%- if remaining_values
|
||||
and (remaining_values | first | int) > 0 -%}\n {%- set line = due.attributes.child_name
|
||||
~ ' still has ' ~ (remaining_values | first | int) ~ ' quest(s) open.' -%}\n
|
||||
\ {%- set ns.lines = ns.lines + [line] -%}\n {%- endif -%}\n {%- endif
|
||||
-%}\n{%- endfor -%}\n{{- ns.lines | join('\\n') -}}"
|
||||
@@ -0,0 +1,87 @@
|
||||
blueprint:
|
||||
name: NestQuest day complete celebration
|
||||
description: 'Celebrates the moment a child clears every quest for the day: the
|
||||
automation is driven by the nestquest_child_day_complete event, which NestQuest
|
||||
only fires when quests were owed and all of them are complete — a zero-quest day
|
||||
can never trigger this celebration. Once-per-child-per-day dedupe: point last_celebrated_helper
|
||||
at ONE input_text helper (create it under Settings, Devices and Services, Helpers
|
||||
— plain text). The automation records each celebrated child as "<child_id>:<today>"
|
||||
inside that helper, and a child who un-completes a quest and completes it again
|
||||
the same day is celebrated once, not twice — while a SECOND child clearing their
|
||||
day the same date is still celebrated in full. The helper is pruned to today''s
|
||||
entries on every celebration, so it never grows past one entry per child per day.
|
||||
A helper that does not exist yet, or has never been set, lets the first celebration
|
||||
through. The notification message may use the event payload''s child_name, quests_due
|
||||
and quests_completed. The notify service is chosen by the household when importing
|
||||
this blueprint — NestQuest never hard-codes a personal notify target.'
|
||||
domain: automation
|
||||
input:
|
||||
notify_target:
|
||||
name: Notify service
|
||||
description: The Home Assistant notify service to call for the celebration,
|
||||
for example notify.mobile_app_your_phone — a placeholder; pick the notify
|
||||
service for your own device.
|
||||
selector:
|
||||
text:
|
||||
multiline: false
|
||||
multiple: false
|
||||
celebration_message:
|
||||
name: Celebration message
|
||||
description: 'The message sent when a child clears every quest today. May use
|
||||
the event payload: trigger.event.data.child_name, trigger.event.data.quests_due
|
||||
and trigger.event.data.quests_completed.'
|
||||
default: Legendary! {{ trigger.event.data.child_name }} cleared every quest
|
||||
today!
|
||||
selector:
|
||||
text:
|
||||
multiline: false
|
||||
multiple: false
|
||||
celebrate:
|
||||
name: Send the celebration
|
||||
description: Turn off to keep the automation installed but skip the notification
|
||||
entirely.
|
||||
default: true
|
||||
selector:
|
||||
boolean: {}
|
||||
last_celebrated_helper:
|
||||
name: Last-celebrated helper
|
||||
description: A single input_text helper remembering which children were already
|
||||
celebrated today, as "<child_id>:<date>" entries separated by commas. The
|
||||
automation skips a child whose entry for today is already stored, and rewrites
|
||||
the helper with only today's entries plus the new one after each celebration.
|
||||
selector:
|
||||
entity:
|
||||
filter:
|
||||
- domain:
|
||||
- input_text
|
||||
multiple: false
|
||||
reorder: false
|
||||
source_url: https://github.com/Maniacs-Mansion/NestQuest/blob/main/custom_components/nestquest/blueprints/automation/day_complete_celebration.yaml
|
||||
mode: queued
|
||||
variables:
|
||||
celebrate: !input celebrate
|
||||
last_celebrated_helper: !input last_celebrated_helper
|
||||
triggers:
|
||||
- trigger: event
|
||||
event_type: nestquest_child_day_complete
|
||||
conditions:
|
||||
- condition: template
|
||||
value_template: '{{ celebrate | bool }}'
|
||||
- condition: template
|
||||
value_template: "{{ (trigger.event.data.child_id ~ ':' ~ now().strftime('%Y-%m-%d'))\n
|
||||
\ not in ((states(last_celebrated_helper) or '')\n .split(',')\n |
|
||||
select('ne', '')\n | list) }}"
|
||||
actions:
|
||||
- action: !input notify_target
|
||||
data:
|
||||
title: NestQuest day complete
|
||||
message: !input celebration_message
|
||||
- action: input_text.set_value
|
||||
target:
|
||||
entity_id: !input last_celebrated_helper
|
||||
data:
|
||||
value: "{%- set today = now().strftime('%Y-%m-%d') -%} {%- set ns = namespace(entries=[])
|
||||
-%} {%- for entry in (states(last_celebrated_helper) or '').split(',') -%}\n
|
||||
\ {%- if entry and entry.endswith(':' ~ today) -%}\n {%- set ns.entries =
|
||||
ns.entries + [entry] -%}\n {%- endif -%}\n{%- endfor -%} {{ (ns.entries + [trigger.event.data.child_id
|
||||
~ ':' ~ today]) | join(',') }}"
|
||||
@@ -0,0 +1,89 @@
|
||||
blueprint:
|
||||
name: NestQuest end-of-day report
|
||||
description: Sends the parent an end-of-day report on how the day went. It runs
|
||||
deliberately BEFORE NestQuest's midnight missed-quest sweep, so it reads the same
|
||||
day's data (what is still open at report time, and how much of the day was completed)
|
||||
rather than tomorrow-morning's hindsight. Every PRESENT child gets a completion
|
||||
line, so the parent always receives the day's summary; children who still owe
|
||||
quests get their open count called out on that line, and children who are not
|
||||
present today are omitted entirely. A child whose remaining-count sensor is unavailable
|
||||
is reported with the completion line only. By default the report is skipped when
|
||||
every present child is already done — turn on send-even-when-clear to always get
|
||||
the day's completion summary. The notify service is chosen by the household when
|
||||
importing this blueprint — NestQuest never hard-codes a personal notify target.
|
||||
domain: automation
|
||||
input:
|
||||
report_time:
|
||||
name: Report time
|
||||
description: Local time of day the report is sent — deliberately before the
|
||||
midnight missed-quest sweep, so the report reads the same day's data.
|
||||
default: '20:00'
|
||||
selector:
|
||||
time: {}
|
||||
notify_target:
|
||||
name: Notify service
|
||||
description: The Home Assistant notify service to call for the report, for example
|
||||
notify.mobile_app_your_phone — a placeholder; pick the notify service for
|
||||
your own device.
|
||||
selector:
|
||||
text:
|
||||
multiline: false
|
||||
multiple: false
|
||||
send_report:
|
||||
name: Send the report
|
||||
description: Turn off to keep the automation installed but skip the notification
|
||||
entirely.
|
||||
default: true
|
||||
selector:
|
||||
boolean: {}
|
||||
send_even_when_clear:
|
||||
name: Send even when clear
|
||||
description: Send the report even when every present child has already finished
|
||||
their quests today. When off (the default), an all-clear day sends nothing.
|
||||
default: false
|
||||
selector:
|
||||
boolean: {}
|
||||
source_url: https://github.com/Maniacs-Mansion/NestQuest/blob/main/custom_components/nestquest/blueprints/automation/end_of_day_report.yaml
|
||||
mode: single
|
||||
variables:
|
||||
send_report: !input send_report
|
||||
send_even_when_clear: !input send_even_when_clear
|
||||
triggers:
|
||||
- trigger: time
|
||||
at: !input report_time
|
||||
conditions:
|
||||
- condition: template
|
||||
value_template: '{{ send_report | bool }}'
|
||||
- condition: template
|
||||
value_template: "{%- set ns = namespace(any_open=false) -%}\n{%- for due in states.sensor\n
|
||||
\ | selectattr('entity_id', 'search', '^sensor\\\\.nestquest_.*_quests_due_today(_\\\\d+)?$')
|
||||
-%}\n {%- if due.attributes.get('present') -%}\n {%- set remaining_values
|
||||
= states.sensor\n | selectattr('entity_id', 'search', '^sensor\\\\.nestquest_.*_quests_remaining_today(_\\\\d+)?$')\n
|
||||
\ | selectattr('attributes.child_id', 'eq', due.attributes.child_id)\n
|
||||
\ | map(attribute='state')\n | list -%}\n {%- if not remaining_values
|
||||
or (remaining_values | first | int(-1)) != 0 -%}\n {%- set ns.any_open =
|
||||
true -%}\n {%- endif -%}\n {%- endif -%}\n{%- endfor -%}\n{{- send_even_when_clear
|
||||
| bool or ns.any_open -}}\n"
|
||||
actions:
|
||||
- action: !input notify_target
|
||||
data:
|
||||
title: NestQuest end-of-day report
|
||||
message: "{%- set ns = namespace(lines=[]) -%}\n{%- for due in states.sensor\n
|
||||
\ | selectattr('entity_id', 'search', '^sensor\\\\.nestquest_.*_quests_due_today(_\\\\d+)?$')
|
||||
-%}\n {%- if due.attributes.get('present') -%}\n {%- set due_count = due.state
|
||||
| int -%}\n {%- set completed_values = states.sensor\n | selectattr('entity_id',
|
||||
'search', '^sensor\\\\.nestquest_.*_quests_completed_today(_\\\\d+)?$')\n |
|
||||
selectattr('attributes.child_id', 'eq', due.attributes.child_id)\n |
|
||||
map(attribute='state')\n | list -%}\n {%- set remaining_values =
|
||||
states.sensor\n | selectattr('entity_id', 'search', '^sensor\\\\.nestquest_.*_quests_remaining_today(_\\\\d+)?$')\n
|
||||
\ | selectattr('attributes.child_id', 'eq', due.attributes.child_id)\n
|
||||
\ | map(attribute='state')\n | list -%}\n {%- set completed_count
|
||||
= (completed_values | first | int) if completed_values else 0 -%}\n {%- set
|
||||
remaining_count = (remaining_values | first | int) if remaining_values else
|
||||
none -%}\n {%- if remaining_count is not none and remaining_count > 0 -%}\n
|
||||
\ {%- set line = due.attributes.child_name ~ ': ' ~ remaining_count ~ '
|
||||
still open (' ~ completed_count ~ '/' ~ due_count ~ ' done).' -%}\n {%- else
|
||||
-%}\n {%- set line = due.attributes.child_name ~ ': ' ~ completed_count
|
||||
~ '/' ~ due_count ~ ' done.' -%}\n {%- endif -%}\n {%- set ns.lines =
|
||||
ns.lines + [line] -%}\n {%- endif -%}\n{%- endfor -%}\n{{- ns.lines | join('\\n')
|
||||
-}}"
|
||||
@@ -0,0 +1,92 @@
|
||||
blueprint:
|
||||
name: NestQuest UniFi quest access
|
||||
description: One automation per child. Completely block the selected UniFi clients
|
||||
after the local weekday/weekend cutoff while that child has quests remaining TODAY.
|
||||
Restore network access immediately when the count reaches zero, and at midnight
|
||||
even if yesterday was incomplete. A missing or unavailable quest sensor is treated
|
||||
as still open after the cutoff; verify the sensor before enabling this automation.
|
||||
The selected UniFi client switches are ON when access is allowed and OFF when
|
||||
blocked. Do not select infrastructure or adult devices.
|
||||
domain: automation
|
||||
input:
|
||||
remaining_sensor:
|
||||
name: Child's quests remaining today
|
||||
description: Select this child's NestQuest quests remaining today sensor.
|
||||
selector:
|
||||
entity:
|
||||
filter:
|
||||
- integration: nestquest
|
||||
domain:
|
||||
- sensor
|
||||
multiple: false
|
||||
reorder: false
|
||||
client_switches:
|
||||
name: Child's UniFi client access switches
|
||||
description: Select every UniFi client access switch for this child. These switches
|
||||
are ON when network access is allowed and OFF when blocked.
|
||||
selector:
|
||||
entity:
|
||||
multiple: true
|
||||
filter:
|
||||
- integration: unifi
|
||||
domain:
|
||||
- switch
|
||||
reorder: false
|
||||
weekday_cutoff:
|
||||
name: Monday-Friday cutoff
|
||||
default: '18:25:00'
|
||||
selector:
|
||||
time: {}
|
||||
weekend_cutoff:
|
||||
name: Saturday-Sunday cutoff
|
||||
default: '14:00:00'
|
||||
selector:
|
||||
time: {}
|
||||
source_url: https://github.com/Maniacs-Mansion/NestQuest/blob/main/custom_components/nestquest/blueprints/automation/unifi_quest_access.yaml
|
||||
mode: restart
|
||||
variables:
|
||||
remaining_sensor: !input remaining_sensor
|
||||
client_switches: !input client_switches
|
||||
weekday_cutoff: !input weekday_cutoff
|
||||
weekend_cutoff: !input weekend_cutoff
|
||||
triggers:
|
||||
- trigger: state
|
||||
entity_id: !input remaining_sensor
|
||||
- trigger: time
|
||||
at: !input weekday_cutoff
|
||||
- trigger: time
|
||||
at: !input weekend_cutoff
|
||||
- trigger: time
|
||||
at: 00:00:00
|
||||
- trigger: homeassistant
|
||||
event: start
|
||||
- trigger: time_pattern
|
||||
minutes: /5
|
||||
conditions:
|
||||
- condition: template
|
||||
value_template: '{{ client_switches | count > 0 }}'
|
||||
actions:
|
||||
- variables:
|
||||
should_block: "{{ now().strftime('%H:%M') >=\n (weekday_cutoff[:5] if now().weekday()
|
||||
< 5 else weekend_cutoff[:5])\n and (states(remaining_sensor) | int(-1)) !=
|
||||
0 }}"
|
||||
- repeat:
|
||||
for_each: '{{ client_switches }}'
|
||||
sequence:
|
||||
- choose:
|
||||
- conditions:
|
||||
- condition: template
|
||||
value_template: '{{ should_block | bool and is_state(repeat.item, ''on'')
|
||||
}}'
|
||||
sequence:
|
||||
- action: switch.turn_off
|
||||
target:
|
||||
entity_id: '{{ repeat.item }}'
|
||||
- conditions:
|
||||
- condition: template
|
||||
value_template: '{{ not (should_block | bool) and is_state(repeat.item,
|
||||
''off'') }}'
|
||||
sequence:
|
||||
- action: switch.turn_on
|
||||
target:
|
||||
entity_id: '{{ repeat.item }}'
|
||||
@@ -0,0 +1,29 @@
|
||||
blueprint:
|
||||
name: Litter box - binary alert notification
|
||||
description: Notify the cats channel when a litter-box waste-bin or error flag turns on.
|
||||
domain: automation
|
||||
input:
|
||||
alert_sensor:
|
||||
name: Alert sensor
|
||||
selector:
|
||||
entity:
|
||||
domain: binary_sensor
|
||||
title:
|
||||
name: Notification title
|
||||
selector:
|
||||
text: {}
|
||||
message:
|
||||
name: Notification message
|
||||
selector:
|
||||
text: {}
|
||||
triggers:
|
||||
- trigger: state
|
||||
entity_id: !input alert_sensor
|
||||
to: 'on'
|
||||
conditions: []
|
||||
actions:
|
||||
- action: notify.notify_events_cats
|
||||
data:
|
||||
title: !input title
|
||||
message: !input message
|
||||
mode: queued
|
||||
@@ -0,0 +1,41 @@
|
||||
blueprint:
|
||||
name: Litter level - low notification
|
||||
description: Notify once after a numeric litter level stays at or below its threshold for two minutes. Refilling above the threshold rearms the alert.
|
||||
domain: automation
|
||||
input:
|
||||
litter_sensor:
|
||||
name: Litter level sensor
|
||||
selector:
|
||||
entity:
|
||||
domain: sensor
|
||||
threshold:
|
||||
name: Low level threshold (inclusive)
|
||||
default: 65
|
||||
selector:
|
||||
number:
|
||||
min: 0
|
||||
max: 100
|
||||
title:
|
||||
name: Notification title
|
||||
selector:
|
||||
text: {}
|
||||
message:
|
||||
name: Notification message
|
||||
selector:
|
||||
text: {}
|
||||
trigger_variables:
|
||||
litter_sensor: !input litter_sensor
|
||||
threshold: !input threshold
|
||||
triggers:
|
||||
- trigger: template
|
||||
value_template: >-
|
||||
{{ is_number(states(litter_sensor))
|
||||
and states(litter_sensor) | float(101) <= threshold | float(65) }}
|
||||
for: '00:02:00'
|
||||
conditions: []
|
||||
actions:
|
||||
- action: notify.notify_events_cats
|
||||
data:
|
||||
title: !input title
|
||||
message: !input message
|
||||
mode: single
|
||||
@@ -0,0 +1,73 @@
|
||||
blueprint:
|
||||
name: Tank full - outlet interlock and verified notification
|
||||
description: >-
|
||||
Request outlet shutdown when a tank is wet, including attempts to turn it
|
||||
back on, startup and recovery. Verify the outlet reports off before claiming
|
||||
success. Retry unconfirmed shutdowns every five minutes while wet.
|
||||
domain: automation
|
||||
input:
|
||||
moisture_sensor:
|
||||
name: Tank moisture sensor
|
||||
selector:
|
||||
entity:
|
||||
domain: binary_sensor
|
||||
outlet:
|
||||
name: Protected outlet
|
||||
selector:
|
||||
entity:
|
||||
domain: switch
|
||||
title:
|
||||
name: Alert title
|
||||
selector:
|
||||
text: {}
|
||||
equipment_name:
|
||||
name: Equipment name for the notification
|
||||
selector:
|
||||
text: {}
|
||||
variables:
|
||||
outlet: !input outlet
|
||||
equipment_name: !input equipment_name
|
||||
triggers:
|
||||
- trigger: state
|
||||
entity_id: !input moisture_sensor
|
||||
to: 'on'
|
||||
id: wet
|
||||
- trigger: state
|
||||
entity_id: !input outlet
|
||||
to: 'on'
|
||||
id: outlet_on
|
||||
- trigger: homeassistant
|
||||
event: start
|
||||
id: startup
|
||||
- trigger: event
|
||||
event_type: automation_reloaded
|
||||
id: reload
|
||||
- trigger: time_pattern
|
||||
minutes: '/5'
|
||||
id: retry
|
||||
conditions:
|
||||
- condition: state
|
||||
entity_id: !input moisture_sensor
|
||||
state: 'on'
|
||||
- condition: template
|
||||
value_template: "{{ trigger.id != 'retry' or not is_state(outlet, 'off') }}"
|
||||
actions:
|
||||
- action: switch.turn_off
|
||||
continue_on_error: true
|
||||
target:
|
||||
entity_id: !input outlet
|
||||
- wait_template: "{{ is_state(outlet, 'off') }}"
|
||||
timeout: '00:00:10'
|
||||
continue_on_timeout: true
|
||||
- action: script.notify_events_alerts
|
||||
data:
|
||||
title: !input title
|
||||
message: >-
|
||||
{{ equipment_name }} tank sensor detected water.
|
||||
{% if is_state(outlet, 'off') %}
|
||||
The outlet reports off.
|
||||
{% else %}
|
||||
Shutoff could not be confirmed (outlet state: {{ states(outlet) }}).
|
||||
Check the equipment and outlet.
|
||||
{% endif %}
|
||||
mode: restart
|
||||
@@ -0,0 +1,90 @@
|
||||
blueprint:
|
||||
name: NestQuest afternoon reminder
|
||||
description: >-
|
||||
Sends an afternoon nudge listing ONLY the children who still have
|
||||
open quests today. Children who are present and all clear are
|
||||
omitted (the nudge is for stragglers, not a roll call), children
|
||||
who are not present today are omitted entirely, and the household
|
||||
rollup sensors are excluded by the same present guard. When every
|
||||
present child is already done the automation sends NOTHING at all:
|
||||
a template condition stops the run before the notification. The
|
||||
notify service is chosen by the household when importing this
|
||||
blueprint — NestQuest never hard-codes a personal notify target.
|
||||
domain: automation
|
||||
input:
|
||||
afternoon_time:
|
||||
name: Afternoon time
|
||||
description: Local time of day the reminder is sent.
|
||||
default: "15:00"
|
||||
selector:
|
||||
time: {}
|
||||
notify_target:
|
||||
name: Notify service
|
||||
description: >-
|
||||
The Home Assistant notify service to call for the reminder,
|
||||
for example notify.mobile_app_your_phone — a placeholder;
|
||||
pick the notify service for your own device.
|
||||
selector:
|
||||
text: {}
|
||||
send_reminder:
|
||||
name: Send the reminder
|
||||
description: >-
|
||||
Turn off to keep the automation installed but skip the
|
||||
notification entirely.
|
||||
default: true
|
||||
selector:
|
||||
boolean: {}
|
||||
|
||||
mode: single
|
||||
|
||||
variables:
|
||||
send_reminder: !input send_reminder
|
||||
|
||||
triggers:
|
||||
- trigger: time
|
||||
at: !input afternoon_time
|
||||
|
||||
conditions:
|
||||
- condition: template
|
||||
value_template: "{{ send_reminder | bool }}"
|
||||
# Send nothing when every present child is already done: the guard is
|
||||
# false unless at least one present child has remaining quests today.
|
||||
- condition: template
|
||||
value_template: |
|
||||
{%- set ns = namespace(any_open=false) -%}
|
||||
{%- for due in states.sensor
|
||||
| selectattr('entity_id', 'search', '^sensor\\.nestquest_.*_quests_due_today(_\\d+)?$') -%}
|
||||
{%- if due.attributes.get('present') -%}
|
||||
{%- set remaining_values = states.sensor
|
||||
| selectattr('entity_id', 'search', '^sensor\\.nestquest_.*_quests_remaining_today(_\\d+)?$')
|
||||
| selectattr('attributes.child_id', 'eq', due.attributes.child_id)
|
||||
| map(attribute='state')
|
||||
| list -%}
|
||||
{%- if remaining_values and (remaining_values | first | int) > 0 -%}
|
||||
{%- set ns.any_open = true -%}
|
||||
{%- endif -%}
|
||||
{%- endif -%}
|
||||
{%- endfor -%}
|
||||
{{- ns.any_open -}}
|
||||
|
||||
actions:
|
||||
- action: !input notify_target
|
||||
data:
|
||||
title: NestQuest afternoon reminder
|
||||
message: |
|
||||
{%- set ns = namespace(lines=[]) -%}
|
||||
{%- for due in states.sensor
|
||||
| selectattr('entity_id', 'search', '^sensor\\.nestquest_.*_quests_due_today(_\\d+)?$') -%}
|
||||
{%- if due.attributes.get('present') -%}
|
||||
{%- set remaining_values = states.sensor
|
||||
| selectattr('entity_id', 'search', '^sensor\\.nestquest_.*_quests_remaining_today(_\\d+)?$')
|
||||
| selectattr('attributes.child_id', 'eq', due.attributes.child_id)
|
||||
| map(attribute='state')
|
||||
| list -%}
|
||||
{%- if remaining_values and (remaining_values | first | int) > 0 -%}
|
||||
{%- set line = due.attributes.child_name ~ ' still has ' ~ (remaining_values | first | int) ~ ' quest(s) open.' -%}
|
||||
{%- set ns.lines = ns.lines + [line] -%}
|
||||
{%- endif -%}
|
||||
{%- endif -%}
|
||||
{%- endfor -%}
|
||||
{{- ns.lines | join('\n') -}}
|
||||
@@ -0,0 +1,109 @@
|
||||
blueprint:
|
||||
name: NestQuest day complete celebration
|
||||
description: >-
|
||||
Celebrates the moment a child clears every quest for the day: the
|
||||
automation is driven by the nestquest_child_day_complete event,
|
||||
which NestQuest only fires when quests were owed and all of them
|
||||
are complete — a zero-quest day can never trigger this
|
||||
celebration. Once-per-child-per-day dedupe: point
|
||||
last_celebrated_helper at ONE input_text helper (create it under
|
||||
Settings, Devices and Services, Helpers — plain text). The
|
||||
automation records each celebrated child as
|
||||
"<child_id>:<today>" inside that helper, and a child who
|
||||
un-completes a quest and completes it again the same day is
|
||||
celebrated once, not twice — while a SECOND child clearing their
|
||||
day the same date is still celebrated in full. The helper is
|
||||
pruned to today's entries on every celebration, so it never
|
||||
grows past one entry per child per day. A helper that does not
|
||||
exist yet, or has never been set, lets the first celebration
|
||||
through. The notification message may use the event payload's
|
||||
child_name, quests_due and quests_completed. The notify service
|
||||
is chosen by the household when importing this blueprint —
|
||||
NestQuest never hard-codes a personal notify target.
|
||||
domain: automation
|
||||
input:
|
||||
notify_target:
|
||||
name: Notify service
|
||||
description: >-
|
||||
The Home Assistant notify service to call for the celebration,
|
||||
for example notify.mobile_app_your_phone — a placeholder;
|
||||
pick the notify service for your own device.
|
||||
selector:
|
||||
text: {}
|
||||
celebration_message:
|
||||
name: Celebration message
|
||||
description: >-
|
||||
The message sent when a child clears every quest today. May
|
||||
use the event payload: trigger.event.data.child_name,
|
||||
trigger.event.data.quests_due and trigger.event.data.quests_completed.
|
||||
default: "Legendary! {{ trigger.event.data.child_name }} cleared every quest today!"
|
||||
selector:
|
||||
text: {}
|
||||
celebrate:
|
||||
name: Send the celebration
|
||||
description: >-
|
||||
Turn off to keep the automation installed but skip the
|
||||
notification entirely.
|
||||
default: true
|
||||
selector:
|
||||
boolean: {}
|
||||
last_celebrated_helper:
|
||||
name: Last-celebrated helper
|
||||
description: >-
|
||||
A single input_text helper remembering which children were
|
||||
already celebrated today, as "<child_id>:<date>" entries
|
||||
separated by commas. The automation skips a child whose entry
|
||||
for today is already stored, and rewrites the helper with only
|
||||
today's entries plus the new one after each celebration.
|
||||
selector:
|
||||
entity:
|
||||
filter:
|
||||
- domain: input_text
|
||||
|
||||
mode: queued
|
||||
|
||||
variables:
|
||||
celebrate: !input celebrate
|
||||
last_celebrated_helper: !input last_celebrated_helper
|
||||
|
||||
triggers:
|
||||
- trigger: event
|
||||
event_type: nestquest_child_day_complete
|
||||
|
||||
conditions:
|
||||
- condition: template
|
||||
value_template: "{{ celebrate | bool }}"
|
||||
# Once-per-child-per-day dedupe: celebrate only when THIS child has
|
||||
# no equal entry for today in the helper. Entries compare as whole
|
||||
# comma-separated tokens — a substring check would wrongly suppress
|
||||
# child 1 after child 11 was celebrated. An unset or missing helper
|
||||
# reads as '' and celebrates.
|
||||
- condition: template
|
||||
value_template: >-
|
||||
{{ (trigger.event.data.child_id ~ ':' ~ now().strftime('%Y-%m-%d'))
|
||||
not in ((states(last_celebrated_helper) or '')
|
||||
.split(',')
|
||||
| select('ne', '')
|
||||
| list) }}
|
||||
|
||||
actions:
|
||||
- action: !input notify_target
|
||||
data:
|
||||
title: NestQuest day complete
|
||||
message: !input celebration_message
|
||||
# Record this child's celebration, keeping only today's entries so the
|
||||
# helper never outgrows its text limit and stale entries cannot block
|
||||
# next week's celebrations.
|
||||
- action: input_text.set_value
|
||||
target:
|
||||
entity_id: !input last_celebrated_helper
|
||||
data:
|
||||
value: >-
|
||||
{%- set today = now().strftime('%Y-%m-%d') -%}
|
||||
{%- set ns = namespace(entries=[]) -%}
|
||||
{%- for entry in (states(last_celebrated_helper) or '').split(',') -%}
|
||||
{%- if entry and entry.endswith(':' ~ today) -%}
|
||||
{%- set ns.entries = ns.entries + [entry] -%}
|
||||
{%- endif -%}
|
||||
{%- endfor -%}
|
||||
{{ (ns.entries + [trigger.event.data.child_id ~ ':' ~ today]) | join(',') }}
|
||||
@@ -0,0 +1,121 @@
|
||||
blueprint:
|
||||
name: NestQuest end-of-day report
|
||||
description: >-
|
||||
Sends the parent an end-of-day report on how the day went. It runs
|
||||
deliberately BEFORE NestQuest's midnight missed-quest sweep, so it
|
||||
reads the same day's data (what is still open at report time, and
|
||||
how much of the day was completed) rather than tomorrow-morning's
|
||||
hindsight. Every PRESENT child gets a completion line, so the
|
||||
parent always receives the day's summary; children who still owe
|
||||
quests get their open count called out on that line, and children
|
||||
who are not present today are omitted entirely. A child whose
|
||||
remaining-count sensor is unavailable is reported with the
|
||||
completion line only. By default the report is skipped when every
|
||||
present child is already done — turn on send-even-when-clear to
|
||||
always get the day's completion summary. The notify service is
|
||||
chosen by the household when importing this blueprint — NestQuest
|
||||
never hard-codes a personal notify target.
|
||||
domain: automation
|
||||
input:
|
||||
report_time:
|
||||
name: Report time
|
||||
description: >-
|
||||
Local time of day the report is sent — deliberately before the
|
||||
midnight missed-quest sweep, so the report reads the same
|
||||
day's data.
|
||||
default: "20:00"
|
||||
selector:
|
||||
time: {}
|
||||
notify_target:
|
||||
name: Notify service
|
||||
description: >-
|
||||
The Home Assistant notify service to call for the report,
|
||||
for example notify.mobile_app_your_phone — a placeholder;
|
||||
pick the notify service for your own device.
|
||||
selector:
|
||||
text: {}
|
||||
send_report:
|
||||
name: Send the report
|
||||
description: >-
|
||||
Turn off to keep the automation installed but skip the
|
||||
notification entirely.
|
||||
default: true
|
||||
selector:
|
||||
boolean: {}
|
||||
send_even_when_clear:
|
||||
name: Send even when clear
|
||||
description: >-
|
||||
Send the report even when every present child has already
|
||||
finished their quests today. When off (the default), an
|
||||
all-clear day sends nothing.
|
||||
default: false
|
||||
selector:
|
||||
boolean: {}
|
||||
|
||||
mode: single
|
||||
|
||||
variables:
|
||||
send_report: !input send_report
|
||||
send_even_when_clear: !input send_even_when_clear
|
||||
|
||||
triggers:
|
||||
- trigger: time
|
||||
at: !input report_time
|
||||
|
||||
conditions:
|
||||
- condition: template
|
||||
value_template: "{{ send_report | bool }}"
|
||||
# Skip an all-clear day unless the household opted into
|
||||
# send-even-when-clear: the guard is false only when every present
|
||||
# child has a usable remaining count of zero. A MISSING or
|
||||
# non-numeric remaining sensor reads as -1 and counts as open —
|
||||
# a degraded state must reach the parent, not look like all-clear.
|
||||
- condition: template
|
||||
value_template: |
|
||||
{%- set ns = namespace(any_open=false) -%}
|
||||
{%- for due in states.sensor
|
||||
| selectattr('entity_id', 'search', '^sensor\\.nestquest_.*_quests_due_today(_\\d+)?$') -%}
|
||||
{%- if due.attributes.get('present') -%}
|
||||
{%- set remaining_values = states.sensor
|
||||
| selectattr('entity_id', 'search', '^sensor\\.nestquest_.*_quests_remaining_today(_\\d+)?$')
|
||||
| selectattr('attributes.child_id', 'eq', due.attributes.child_id)
|
||||
| map(attribute='state')
|
||||
| list -%}
|
||||
{%- if not remaining_values or (remaining_values | first | int(-1)) != 0 -%}
|
||||
{%- set ns.any_open = true -%}
|
||||
{%- endif -%}
|
||||
{%- endif -%}
|
||||
{%- endfor -%}
|
||||
{{- send_even_when_clear | bool or ns.any_open -}}
|
||||
|
||||
actions:
|
||||
- action: !input notify_target
|
||||
data:
|
||||
title: NestQuest end-of-day report
|
||||
message: |
|
||||
{%- set ns = namespace(lines=[]) -%}
|
||||
{%- for due in states.sensor
|
||||
| selectattr('entity_id', 'search', '^sensor\\.nestquest_.*_quests_due_today(_\\d+)?$') -%}
|
||||
{%- if due.attributes.get('present') -%}
|
||||
{%- set due_count = due.state | int -%}
|
||||
{%- set completed_values = states.sensor
|
||||
| selectattr('entity_id', 'search', '^sensor\\.nestquest_.*_quests_completed_today(_\\d+)?$')
|
||||
| selectattr('attributes.child_id', 'eq', due.attributes.child_id)
|
||||
| map(attribute='state')
|
||||
| list -%}
|
||||
{%- set remaining_values = states.sensor
|
||||
| selectattr('entity_id', 'search', '^sensor\\.nestquest_.*_quests_remaining_today(_\\d+)?$')
|
||||
| selectattr('attributes.child_id', 'eq', due.attributes.child_id)
|
||||
| map(attribute='state')
|
||||
| list -%}
|
||||
{%- set completed_count = (completed_values | first | int) if completed_values else 0 -%}
|
||||
{%- set remaining_count = (remaining_values | first | int) if remaining_values else none -%}
|
||||
{%- if remaining_count is not none and remaining_count > 0 -%}
|
||||
{%- set line = due.attributes.child_name ~ ': ' ~ remaining_count ~ ' still open (' ~ completed_count ~ '/' ~ due_count ~ ' done).' -%}
|
||||
{%- else -%}
|
||||
{%- set line = due.attributes.child_name ~ ': ' ~ completed_count ~ '/' ~ due_count ~ ' done.' -%}
|
||||
{%- endif -%}
|
||||
{%- set ns.lines = ns.lines + [line] -%}
|
||||
{%- endif -%}
|
||||
{%- endfor -%}
|
||||
{{- ns.lines | join('\n') -}}
|
||||
@@ -0,0 +1,78 @@
|
||||
blueprint:
|
||||
name: NestQuest morning summary
|
||||
description: >-
|
||||
Sends a morning notification listing what each child still owes
|
||||
today. Only children whose NestQuest presence sensor is on are
|
||||
included; children who are not present today are omitted entirely.
|
||||
The notify service is chosen by the household when importing this
|
||||
blueprint — NestQuest never hard-codes a personal notify target.
|
||||
domain: automation
|
||||
input:
|
||||
morning_time:
|
||||
name: Morning time
|
||||
description: Local time of day the summary is sent.
|
||||
default: "08:00"
|
||||
selector:
|
||||
time: {}
|
||||
notify_target:
|
||||
name: Notify service
|
||||
description: >-
|
||||
The Home Assistant notify service to call for the summary,
|
||||
for example notify.mobile_app_your_phone — a placeholder;
|
||||
pick the notify service for your own device.
|
||||
selector:
|
||||
text: {}
|
||||
send_summary:
|
||||
name: Send the summary
|
||||
description: >-
|
||||
Turn off to keep the automation installed but skip the
|
||||
notification entirely.
|
||||
default: true
|
||||
selector:
|
||||
boolean: {}
|
||||
|
||||
mode: single
|
||||
|
||||
variables:
|
||||
send_summary: !input send_summary
|
||||
|
||||
triggers:
|
||||
- trigger: time
|
||||
at: !input morning_time
|
||||
|
||||
conditions:
|
||||
- condition: template
|
||||
value_template: "{{ send_summary | bool }}"
|
||||
|
||||
actions:
|
||||
- action: !input notify_target
|
||||
data:
|
||||
title: NestQuest morning summary
|
||||
message: |
|
||||
{%- set ns = namespace(lines=[]) -%}
|
||||
{%- for due in states.sensor
|
||||
| selectattr('entity_id', 'search', '^sensor\\.nestquest_.*_quests_due_today(_\\d+)?$') -%}
|
||||
{%- if due.attributes.get('present') -%}
|
||||
{%- set remaining_values = states.sensor
|
||||
| selectattr('entity_id', 'search', '^sensor\\.nestquest_.*_quests_remaining_today(_\\d+)?$')
|
||||
| selectattr('attributes.child_id', 'eq', due.attributes.child_id)
|
||||
| map(attribute='state')
|
||||
| list -%}
|
||||
{%- if remaining_values -%}
|
||||
{%- set remaining = remaining_values | first | int -%}
|
||||
{%- if remaining > 0 -%}
|
||||
{%- set line = due.attributes.child_name ~ ' owes ' ~ remaining ~ ' quest(s) today.' -%}
|
||||
{%- else -%}
|
||||
{%- set line = due.attributes.child_name ~ ' is all clear today (0 quests remaining).' -%}
|
||||
{%- endif -%}
|
||||
{%- else -%}
|
||||
{%- set line = due.attributes.child_name ~ "'s quest count is unavailable today." -%}
|
||||
{%- endif -%}
|
||||
{%- set ns.lines = ns.lines + [line] -%}
|
||||
{%- endif -%}
|
||||
{%- endfor -%}
|
||||
{%- if ns.lines -%}
|
||||
{{- ns.lines | join('\n') -}}
|
||||
{%- else -%}
|
||||
No children present today.
|
||||
{%- endif -%}
|
||||
@@ -0,0 +1,96 @@
|
||||
blueprint:
|
||||
name: NestQuest UniFi quest access
|
||||
description: >-
|
||||
One automation per child. Completely block the selected UniFi clients
|
||||
after the local weekday/weekend cutoff while that child has quests
|
||||
remaining TODAY. Restore network access immediately when the count
|
||||
reaches zero, and at midnight even if yesterday was incomplete.
|
||||
A missing or unavailable quest sensor is treated as still open after
|
||||
the cutoff; verify the sensor before enabling this automation. The
|
||||
selected UniFi client switches are ON when access is allowed and
|
||||
OFF when blocked. Do not select infrastructure or adult devices.
|
||||
domain: automation
|
||||
input:
|
||||
remaining_sensor:
|
||||
name: Child's quests remaining today
|
||||
description: Select this child's NestQuest quests remaining today sensor.
|
||||
selector:
|
||||
entity:
|
||||
filter:
|
||||
- integration: nestquest
|
||||
domain: sensor
|
||||
client_switches:
|
||||
name: Child's UniFi client access switches
|
||||
description: >-
|
||||
Select every UniFi client access switch for this child. These
|
||||
switches are ON when network access is allowed and OFF when blocked.
|
||||
selector:
|
||||
entity:
|
||||
multiple: true
|
||||
filter:
|
||||
- integration: unifi
|
||||
domain: switch
|
||||
weekday_cutoff:
|
||||
name: Monday-Friday cutoff
|
||||
default: "18:25:00"
|
||||
selector:
|
||||
time: {}
|
||||
weekend_cutoff:
|
||||
name: Saturday-Sunday cutoff
|
||||
default: "14:00:00"
|
||||
selector:
|
||||
time: {}
|
||||
|
||||
mode: restart
|
||||
|
||||
variables:
|
||||
remaining_sensor: !input remaining_sensor
|
||||
client_switches: !input client_switches
|
||||
weekday_cutoff: !input weekday_cutoff
|
||||
weekend_cutoff: !input weekend_cutoff
|
||||
|
||||
triggers:
|
||||
- trigger: state
|
||||
entity_id: !input remaining_sensor
|
||||
- trigger: time
|
||||
at: !input weekday_cutoff
|
||||
- trigger: time
|
||||
at: !input weekend_cutoff
|
||||
- trigger: time
|
||||
at: "00:00:00"
|
||||
- trigger: homeassistant
|
||||
event: start
|
||||
# Reconcile after missed events or a temporary UniFi outage.
|
||||
- trigger: time_pattern
|
||||
minutes: "/5"
|
||||
|
||||
conditions:
|
||||
- condition: template
|
||||
value_template: "{{ client_switches | count > 0 }}"
|
||||
|
||||
actions:
|
||||
- variables:
|
||||
should_block: >-
|
||||
{{ now().strftime('%H:%M') >=
|
||||
(weekday_cutoff[:5] if now().weekday() < 5 else weekend_cutoff[:5])
|
||||
and (states(remaining_sensor) | int(-1)) != 0 }}
|
||||
- repeat:
|
||||
for_each: "{{ client_switches }}"
|
||||
sequence:
|
||||
- choose:
|
||||
- conditions:
|
||||
- condition: template
|
||||
value_template: >-
|
||||
{{ should_block | bool and is_state(repeat.item, 'on') }}
|
||||
sequence:
|
||||
- action: switch.turn_off
|
||||
target:
|
||||
entity_id: "{{ repeat.item }}"
|
||||
- conditions:
|
||||
- condition: template
|
||||
value_template: >-
|
||||
{{ not (should_block | bool) and is_state(repeat.item, 'off') }}
|
||||
sequence:
|
||||
- action: switch.turn_on
|
||||
target:
|
||||
entity_id: "{{ repeat.item }}"
|
||||
@@ -27,7 +27,9 @@ frontend:
|
||||
lovelace:
|
||||
dashboards: !include dashboards/dashboards.yaml
|
||||
|
||||
automation: !include_dir_merge_list automations/
|
||||
automation manual: !include_dir_merge_list automations/
|
||||
automation ui: !include automations.yaml
|
||||
script: !include scripts.yaml
|
||||
scene: !include scenes.yaml
|
||||
input_boolean: !include helpers/input_boolean.yaml
|
||||
input_number: !include helpers/input_number.yaml
|
||||
|
||||
@@ -0,0 +1,219 @@
|
||||
# Bambu and Elegoo fleet display
|
||||
|
||||
ESPHome/LVGL monitor for the **Sunton ESP32-8048S043C**, with an 800×480 landscape RGB
|
||||
display, GT911 capacitive touch, 16 MB flash and 8 MB octal PSRAM. The hardware pins
|
||||
and LCD timings follow the [ESPHome device reference](https://devices.esphome.io/devices/sunton-esp32-8048s043c/).
|
||||
This configuration is for the **C** capacitive-touch version.
|
||||
|
||||
Inspired by [BambuHelper](https://github.com/Keralots/BambuHelper), this implementation
|
||||
subscribes to the existing Home Assistant Bambu Lab and Elegoo Printer entities over
|
||||
a single encrypted ESPHome API connection. Home Assistant handles the printer connections. No Bambu
|
||||
access codes, cloud credentials or additional Home Assistant automations are needed.
|
||||
Home Assistant must be running and its printer integrations must have current printer data.
|
||||
|
||||
## Pages and navigation
|
||||
|
||||

|
||||
|
||||
The configured swipe order is:
|
||||
|
||||
1. Print Status Overview — one steam gauge per printer. The filled part of
|
||||
each ring is that printer's print progress, drawn in the active filament
|
||||
color. Black filament keeps a grey ring behind the gauge so it stays visible
|
||||
on the black page. The center shows the percent complete in white, with the
|
||||
printer name and estimated finish time underneath.
|
||||
The local time is in the top-right corner. Tap a gauge to open that printer.
|
||||
2. PrintsCharming — P1S, Office, both four-slot AMS units.
|
||||
3. Polly-Pocket — A1 mini, Office, external spool.
|
||||
4. Ds-Turd-Flinger — A1, Upstairs, four-slot AMS.
|
||||
5. Jordyns-Layer-Slayer — A1, Upstairs, four-slot AMS.
|
||||
6. Constipation-Orion — Office, left/right nozzles and both four-slot AMS units.
|
||||
AMS A uses `constipation_orion_ams_1`; AMS B retains its original
|
||||
`h2c_31b8ap5a2200288_ams_2` entity prefix.
|
||||
7. Centauri Carbon — Elegoo FDM, Office, nozzle/bed current and target temperatures
|
||||
and model/auxiliary/enclosure fan speeds.
|
||||
8. Saturn — Elegoo resin, Garage, progress, UV LED temperature, current/total layers,
|
||||
elapsed time, remaining time and estimated finish.
|
||||
|
||||

|
||||
|
||||
Swipe left for the next page or right for the previous page. Navigation wraps
|
||||
from the last printer back to the fleet page. Previous/Next buttons provide the
|
||||
same navigation. Swipes propagate through the fleet gauges and filament
|
||||
slots. A swipe consumes the rest of that touch so dragging across a gauge or a
|
||||
navigation button does not also click it.
|
||||
|
||||
FDM pages show print status, job name, progress, nozzle and bed current/target
|
||||
temperatures, remaining time, estimated finish time, current/total layers, print
|
||||
stage, fan speeds and filament colors/types. The layout follows BambuHelper's
|
||||
classic gauges: black background, bright green accents, six arc gauges and a
|
||||
vertical AMS column. Nozzle arcs are orange and the bed arc is cyan. For dual-nozzle
|
||||
printers the auxiliary-fan position becomes the right-nozzle gauge. The active
|
||||
filament slot has a green outline. Print errors use red; paused prints use amber.
|
||||
|
||||
**HMS means Health Management System.** Its binary sensor means the Bambu integration
|
||||
has one or more printer health notifications, which can include information or
|
||||
maintenance messages as well as faults. Tap the status in the upper-right corner
|
||||
or the scrolling message above the footer to open the health-details panel. It
|
||||
shows the codes, severities and descriptions reported by Home Assistant, for up to
|
||||
three current HMS notices, plus print-error details. Scroll vertically for long
|
||||
messages and tap Close to return. If more than three notices exist, the panel says
|
||||
how many additional notices are available in Home Assistant.
|
||||
|
||||
Elegoo pages use the same status-tap details panel for `print_error` and
|
||||
`error_status_reason` enum values. `none` and `ok` are normal states; unknown or
|
||||
unavailable errors do not imply a fault. Elegoo pages use `sdcp_status` for printer
|
||||
connectivity and do not subscribe to Bambu HMS or AMS entities. The Saturn page
|
||||
omits nozzle, bed, fan and filament widgets.
|
||||
|
||||
The status uses **HEALTH INFO** (cyan) for information-only notifications,
|
||||
**HEALTH NOTICE** (amber) for common/unknown severities or incomplete details, and
|
||||
**HEALTH ERROR** (red) for fatal/serious severities. Severity comes from the Bambu
|
||||
integration's `N-Severity` attributes. Missing or unloaded messages never establish
|
||||
an information-only status. The display does not dismiss or clear printer alerts.
|
||||
The [`hms_errors` sensor attributes](https://github.com/greghesp/ha-bambulab/blob/main/custom_components/bambu_lab/pybambu/models.py)
|
||||
provide `Count`, `1-Code`, `1-Error`, `1-Severity`, and the corresponding numbered
|
||||
fields for subsequent notices. Stale attributes are ignored when the sensor is off.
|
||||
|
||||
When Home Assistant disconnects or a printer is offline, its live metrics are cleared
|
||||
to `--` rather than continuing to show cached values as live. Unknown individual
|
||||
values also show `--`. Remaining time respects the HA sensor's `h`, `min`, `s` or `ms`
|
||||
unit, and temperature labels use the imported HA temperature unit. Only the visible
|
||||
page is refreshed each second; every printer's subscriptions remain connected.
|
||||
Auxiliary and exhaust fans are imported only for printers with those sensors;
|
||||
unsupported fan readings show `--`. Finish time is estimated from HA's remaining
|
||||
time and the HA-synchronized clock. Set `time.timezone` in the main YAML if your
|
||||
timezone differs from `America/New_York`.
|
||||
|
||||
This version provides monitoring and local page navigation. Camera feeds, cover
|
||||
images, printer pause/stop controls, power switching, cloud login and automatic
|
||||
printer rotation are outside its current scope.
|
||||
|
||||
## Install
|
||||
|
||||
1. Keep both device files and the entire `bambu-display/` folder together in the
|
||||
ESPHome configuration directory. `bambu-printer-display.yaml` is the first board
|
||||
and `bambu-printer-display-2.yaml` is the second. They share `board.yaml` and the
|
||||
generated fleet package, which is checked in, so the ESPHome dashboard can compile
|
||||
either file without running the generator first.
|
||||
2. Supply the existing `api_encryption_key`, `wifi_iot_ssid` and `wifi_password`
|
||||
entries in that directory's `secrets.yaml`.
|
||||
3. Use ESPHome **2026.9.0 or newer**. Compile and install the matching device file
|
||||
over that board's USB-to-UART bridge for the first flash; subsequent installs can
|
||||
use encrypted OTA. Flash `bambu-printer-display.yaml` to the first panel and
|
||||
`bambu-printer-display-2.yaml` to the second. Each file has its own ESPHome name,
|
||||
so both panels can be online together.
|
||||
4. Add **Bambu Printer Display** or **Bambu Printer Display 2** through Home
|
||||
Assistant's ESPHome integration using the same API encryption key. Each display
|
||||
only subscribes to entities, so it does not require permission to perform Home
|
||||
Assistant actions.
|
||||
5. Confirm all pages receive data and verify touch direction and color order on the
|
||||
physical panel. Entity IDs match the repository's `ha-entities.txt` snapshot;
|
||||
renamed or disabled entities in live HA must be corrected/enabled there.
|
||||
|
||||
The USB D+/D− GPIO19/20 pins are used by the board's touch bus. Logging therefore
|
||||
uses UART0. ESPHome may still warn about these two pins; they are intentional for
|
||||
this board. If GT911 is not found at `0x5D`, inspect the I²C scan and use its reported
|
||||
address (some hardware revisions select `0x14`). Backlight is GPIO2; RGB data pins
|
||||
and timings are explicit in `board.yaml` rather than borrowed from a 5″ board.
|
||||
|
||||
The display and LVGL both use **big-endian RGB565**. With `mipi_rgb`'s named
|
||||
`red`/`green`/`blue` pin groups, its generated bus pin map already exchanges the
|
||||
two bytes. Selecting little-endian pixels corrupts colors on the physical panel
|
||||
even though a host preview can look correct. Keep both byte-order settings aligned
|
||||
with the bus mapping; changing the RGB/BGR label alone is insufficient for this
|
||||
RPI panel's direct framebuffer path.
|
||||
|
||||
To check the physical colors, open the device's **Display Color Test** diagnostic
|
||||
button in Home Assistant. It shows labeled red, green, blue, white, cyan and magenta
|
||||
blocks. Swipe to return to the printers. This test page is skipped during normal
|
||||
printer navigation. Its expected image is [previews/color_test.png](previews/color_test.png).
|
||||
|
||||
CLI checks from the repository root:
|
||||
|
||||
```sh
|
||||
esphome config esphome/bambu-printer-display.yaml
|
||||
esphome config esphome/bambu-printer-display-2.yaml
|
||||
esphome compile esphome/bambu-printer-display.yaml
|
||||
esphome compile esphome/bambu-printer-display-2.yaml
|
||||
```
|
||||
|
||||
## Change or expand the fleet
|
||||
|
||||
Edit **`printers.yaml`**, then regenerate and flash the display:
|
||||
|
||||
```sh
|
||||
python esphome/bambu-display/generate.py
|
||||
python esphome/bambu-display/generate.py --check
|
||||
```
|
||||
|
||||
The generator requires PyYAML (already included in ESPHome's Python environment).
|
||||
Do not hand-edit `generated.yaml`. There is no two-printer limit in the generator;
|
||||
adding a printer creates its own subscriptions, widgets, swipe page and fleet gauge. The practical
|
||||
limit depends on the board's available RAM and flash, and adding printers requires
|
||||
regenerating and installing the firmware. Seven printers are configured; generation
|
||||
is also tested with nine. Each Bambu printer's filament strip supports up to eight slots.
|
||||
|
||||
```yaml
|
||||
- id: another_printer
|
||||
name: Another Printer
|
||||
subtitle: Workshop
|
||||
entity_prefix: my_bambu_printer
|
||||
entities:
|
||||
progress: sensor.renamed_progress
|
||||
time_left: sensor.renamed_remaining_time
|
||||
filament_banks:
|
||||
- label: A
|
||||
entity_prefix: my_bambu_printer_ams_1
|
||||
```
|
||||
|
||||
For a dual-nozzle printer add `dual_nozzle: true`. For external filament add
|
||||
`external_spools: [{label: EXT, entity: sensor.my_external_spool}]`. Per-metric
|
||||
`entities` overrides use the keys in `NUMERIC`, `DUAL`, `TEXT` and `BINARY` inside
|
||||
`generate.py`. Use `extra_fans: [aux_fan, exhaust_fan]` only where those HA entities
|
||||
exist; their mappings are in `EXTRA_FANS`. A remaining-time override also updates its unit subscription.
|
||||
Filament entries use the Bambu integration's `type`, `color` and `active` attributes.
|
||||
|
||||
For Elegoo printers, set `profile: elegoo_fdm` or `profile: elegoo_resin` (the default
|
||||
profile is `bambu`). These profiles map progress to `percent_complete`. The Centauri Carbon leaves
|
||||
that sensor at 0 for most of a job, so an active Elegoo print falls back to the
|
||||
layer count and then to elapsed time divided by `total_print_time`. Job name maps to
|
||||
`file_name`, remaining time to `remaining_print_time`, and total layers to
|
||||
`total_layers`. The FDM profile reads targets from the HA `number` entities
|
||||
`target_nozzle_temp` and `target_bed_temp`; it only monitors their state. Per-metric
|
||||
`entities` overrides support `sensor`, `binary_sensor` and `number` domains.
|
||||
Sensor types, duration units and enum states follow the
|
||||
[Elegoo integration definitions](https://github.com/danielcherubini/elegoo-homeassistant/blob/main/custom_components/elegoo_printer/definitions.py).
|
||||
|
||||
The configured Elegoo prefixes are `centauri_carbon` and `saturn`, matching the
|
||||
repository entity snapshot. Some older automations refer to `saturn_iv`; if your
|
||||
live integration still uses that prefix, change the Saturn `entity_prefix` before
|
||||
regenerating. If individual entities have different names in your integration
|
||||
version, use `entities` overrides, for example:
|
||||
|
||||
```yaml
|
||||
entities:
|
||||
print_error_reason: sensor.saturn_current_print_error_status_reason
|
||||
```
|
||||
|
||||
No new HA helpers are required. `dual_nozzle`, filament slots and `extra_fans` are
|
||||
Bambu options; the Elegoo FDM profile includes its three fan subscriptions.
|
||||
|
||||
## Development checks and screenshots
|
||||
|
||||
```sh
|
||||
python -m unittest discover -s esphome/bambu-display -v
|
||||
clang++ -std=c++17 -Wall -Wextra -Werror esphome/bambu-display/test_dashboard.cpp -o /tmp/bambu-dashboard-test
|
||||
/tmp/bambu-dashboard-test
|
||||
python esphome/bambu-display/preview.py
|
||||
```
|
||||
|
||||
`preview.py` compiles the same generated LVGL widgets for ESPHome's SDL host with
|
||||
clearly synthetic telemetry, then captures all seven pages and a synthetic health
|
||||
notice into `previews/`. The demo notice is not a diagnosis of any real printer. It needs
|
||||
ESPHome, Pillow, a C++ compiler and SDL2 (`sdl2-config` on PATH). It runs headlessly,
|
||||
without connecting to Home Assistant or any printer. It also injects LVGL pointer
|
||||
samples to check left/right swipes, page wraparound and consumption of a swipe that
|
||||
starts on a navigation button. These are software layout
|
||||
previews; physical panel timings, touch and Wi-Fi still need a hardware check.
|
||||
The preview also checks Elegoo telemetry, error text and clearing of offline data.
|
||||
@@ -0,0 +1,114 @@
|
||||
# Shared Sunton ESP32-8048S043C board for every fleet display.
|
||||
# Device name and friendly name come from the top-level YAML substitutions.
|
||||
esphome:
|
||||
name: ${device_name}
|
||||
friendly_name: ${friendly_name}
|
||||
min_version: 2026.9.0
|
||||
includes:
|
||||
- bambu-display/dashboard.h
|
||||
|
||||
esp32:
|
||||
board: esp32-s3-devkitc-1
|
||||
flash_size: 16MB
|
||||
flash_mode: dio
|
||||
cpu_frequency: 240MHz
|
||||
framework:
|
||||
type: esp-idf
|
||||
sdkconfig_options:
|
||||
CONFIG_ESP32S3_DATA_CACHE_64KB: "y"
|
||||
CONFIG_ESP32S3_DATA_CACHE_LINE_64B: "y"
|
||||
CONFIG_SPIRAM_FETCH_INSTRUCTIONS: "y"
|
||||
CONFIG_SPIRAM_RODATA: "y"
|
||||
|
||||
psram:
|
||||
mode: octal
|
||||
speed: 80MHz
|
||||
|
||||
logger:
|
||||
hardware_uart: UART0 # GPIO19/20 are the touch bus, not native USB.
|
||||
|
||||
api:
|
||||
id: ha_api
|
||||
encryption:
|
||||
key: !secret api_encryption_key
|
||||
|
||||
ota:
|
||||
- platform: esphome
|
||||
encryption:
|
||||
key: !secret api_encryption_key
|
||||
|
||||
wifi:
|
||||
ssid: !secret wifi_iot_ssid
|
||||
password: !secret wifi_password
|
||||
|
||||
i2c:
|
||||
id: touch_bus
|
||||
sda: GPIO19
|
||||
scl: GPIO20
|
||||
scan: true
|
||||
|
||||
display:
|
||||
- platform: mipi_rgb
|
||||
id: panel_display
|
||||
model: RPI
|
||||
dimensions: 800x480
|
||||
color_order: RGB
|
||||
# Named RGB data_pins are byte-swapped by mipi_rgb's pin mapping.
|
||||
# Send big-endian RGB565 pixels to match that wiring transformation.
|
||||
byte_order: big_endian
|
||||
de_pin: GPIO40
|
||||
hsync_pin: GPIO39
|
||||
vsync_pin: GPIO41
|
||||
pclk_pin: GPIO42
|
||||
pclk_inverted: true
|
||||
pclk_frequency: 16MHz
|
||||
hsync_front_porch: 8
|
||||
hsync_pulse_width: 4
|
||||
hsync_back_porch: 8
|
||||
vsync_front_porch: 8
|
||||
vsync_pulse_width: 4
|
||||
vsync_back_porch: 8
|
||||
data_pins:
|
||||
red: [GPIO45, GPIO48, GPIO47, GPIO21, GPIO14]
|
||||
green: [GPIO5, GPIO6, GPIO7, GPIO15, GPIO16, GPIO4]
|
||||
blue: [GPIO8, GPIO3, GPIO46, GPIO9, GPIO1]
|
||||
auto_clear_enabled: false
|
||||
update_interval: never
|
||||
|
||||
touchscreen:
|
||||
- platform: gt911
|
||||
id: panel_touch
|
||||
display: panel_display
|
||||
i2c_id: touch_bus
|
||||
address: 0x5D
|
||||
# Polling avoids the GT911 INT pin's boot/address-selection behaviour.
|
||||
update_interval: 20ms
|
||||
|
||||
output:
|
||||
- platform: ledc
|
||||
id: backlight_output
|
||||
pin: GPIO2
|
||||
|
||||
light:
|
||||
- platform: monochromatic
|
||||
id: panel_backlight
|
||||
name: Backlight
|
||||
output: backlight_output
|
||||
restore_mode: ALWAYS_ON
|
||||
default_transition_length: 0s
|
||||
|
||||
time:
|
||||
- platform: homeassistant
|
||||
id: ha_time
|
||||
timezone: America/New_York
|
||||
|
||||
lvgl:
|
||||
id: dashboard
|
||||
displays: [panel_display]
|
||||
touchscreens: [panel_touch]
|
||||
buffer_size: 12%
|
||||
color_depth: 16
|
||||
byte_order: big_endian # Must match mipi_rgb's named RGB pin mapping above.
|
||||
bg_color: 0x000000
|
||||
text_color: 0xEEEEEE
|
||||
default_font: montserrat_18
|
||||
@@ -0,0 +1,438 @@
|
||||
#pragma once
|
||||
|
||||
#include <algorithm>
|
||||
#include <cctype>
|
||||
#include <cmath>
|
||||
#include <cstdio>
|
||||
#include <cstdint>
|
||||
#include <cstdlib>
|
||||
#include <string>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
namespace bambu_display {
|
||||
|
||||
constexpr uint32_t GREEN = 0x00FF00;
|
||||
constexpr uint32_t MUTED = 0xAEB3B7;
|
||||
constexpr uint32_t RED = 0xFF737B;
|
||||
constexpr uint32_t AMBER = 0xF2C572;
|
||||
constexpr uint32_t TRACK = 0x19232B;
|
||||
constexpr uint32_t RING_BACKING = 0x6E767E;
|
||||
|
||||
inline std::string lower(std::string text) {
|
||||
for (char &c : text) c = static_cast<char>(std::tolower(static_cast<unsigned char>(c)));
|
||||
return text;
|
||||
}
|
||||
|
||||
inline bool valid_text(const std::string &text) {
|
||||
const auto value = lower(text);
|
||||
return !value.empty() && value != "unknown" && value != "unavailable" && value != "none";
|
||||
}
|
||||
|
||||
inline bool is_on(const std::string &text) {
|
||||
const auto value = lower(text);
|
||||
return value == "on" || value == "true" || value == "1";
|
||||
}
|
||||
|
||||
inline bool elegoo_fault(const std::string &text) {
|
||||
const auto value = lower(text);
|
||||
return valid_text(value) && value != "ok" && value != "0" && value != "off" && value != "false";
|
||||
}
|
||||
|
||||
inline std::string humanize(std::string text) {
|
||||
if (!valid_text(text)) return "--";
|
||||
std::replace(text.begin(), text.end(), '_', ' ');
|
||||
if (!text.empty()) text[0] = static_cast<char>(std::toupper(static_cast<unsigned char>(text[0])));
|
||||
return text;
|
||||
}
|
||||
|
||||
inline int gauge_value(float value, int maximum) {
|
||||
return std::isfinite(value) ? static_cast<int>(std::clamp(value, 0.0f, float(maximum))) : 0;
|
||||
}
|
||||
|
||||
inline std::string number(float value) {
|
||||
if (!std::isfinite(value) || value < 0 || value > 1000000) return "--";
|
||||
char buffer[24];
|
||||
snprintf(buffer, sizeof(buffer), "%.0f", value);
|
||||
return buffer;
|
||||
}
|
||||
|
||||
inline std::string temperature(float current, float target, const std::string &unit) {
|
||||
return number(current) + " / " + number(target) + (valid_text(unit) ? " " + unit : "");
|
||||
}
|
||||
|
||||
inline std::string remaining(float value, const std::string &unit) {
|
||||
if (!std::isfinite(value) || value < 0) return "--";
|
||||
double minutes;
|
||||
if (unit == "h") minutes = double(value) * 60;
|
||||
else if (unit == "min") minutes = value;
|
||||
else if (unit == "s") minutes = double(value) / 60;
|
||||
else if (unit == "ms") minutes = double(value) / 60000;
|
||||
else return "--"; // Never silently interpret hours as minutes.
|
||||
if (minutes > 1000000) return "--";
|
||||
const auto rounded = static_cast<int>(std::ceil(minutes - 0.001));
|
||||
char buffer[32];
|
||||
snprintf(buffer, sizeof(buffer), "%dh %02dm", rounded / 60, rounded % 60);
|
||||
return buffer;
|
||||
}
|
||||
|
||||
inline int duration_seconds(float value, const std::string &unit) {
|
||||
if (!std::isfinite(value) || value < 0) return -1;
|
||||
double seconds;
|
||||
if (unit == "h") seconds = double(value) * 3600;
|
||||
else if (unit == "min") seconds = double(value) * 60;
|
||||
else if (unit == "s") seconds = value;
|
||||
else if (unit == "ms") seconds = double(value) / 1000;
|
||||
else return -1;
|
||||
return seconds <= 60000000 ? static_cast<int>(std::round(seconds)) : -1;
|
||||
}
|
||||
|
||||
inline float celsius(float value, const std::string &unit) {
|
||||
return unit == "°F" || unit == "F" ? (value - 32.0f) * 5.0f / 9.0f : value;
|
||||
}
|
||||
|
||||
inline uint32_t filament_color(std::string value) {
|
||||
if (!value.empty() && value[0] == '#') value.erase(0, 1);
|
||||
// Bambu publishes RGB or RGBA, with alpha at the end.
|
||||
if (value.size() != 6 && value.size() != 8) return TRACK;
|
||||
uint32_t rgb = 0;
|
||||
for (size_t i = 0; i < value.size(); ++i) {
|
||||
const char c = static_cast<char>(std::tolower(static_cast<unsigned char>(value[i])));
|
||||
const int digit = c >= '0' && c <= '9' ? c - '0' : c >= 'a' && c <= 'f' ? c - 'a' + 10 : -1;
|
||||
if (digit < 0) return TRACK;
|
||||
if (i < 6) rgb = (rgb << 4) | digit;
|
||||
}
|
||||
return rgb;
|
||||
}
|
||||
|
||||
// A black ring disappears on the black page. Anything this dark needs a grey arc behind it.
|
||||
inline bool filament_is_black(uint32_t color) {
|
||||
return ((color >> 16) & 0xFF) <= 0x28 && ((color >> 8) & 0xFF) <= 0x28 && (color & 0xFF) <= 0x28;
|
||||
}
|
||||
|
||||
struct Tray {
|
||||
std::string label, type, color, active;
|
||||
};
|
||||
|
||||
struct HealthNotice { std::string code, message, severity; };
|
||||
|
||||
struct Snapshot {
|
||||
bool connected = false;
|
||||
std::string online, status, task, stage, print_error, hms_error;
|
||||
std::string hms_count, print_error_message, print_error_code;
|
||||
std::string print_error_reason, uv_unit, elapsed_unit;
|
||||
bool elegoo = false, resin = false;
|
||||
float uv = NAN, elapsed = NAN, total_time = NAN;
|
||||
std::vector<HealthNotice> health_notices;
|
||||
std::string remaining_unit, nozzle_unit, bed_unit, total_time_unit;
|
||||
std::string finish_time;
|
||||
float progress = NAN, nozzle = NAN, nozzle_target = NAN, bed = NAN, bed_target = NAN;
|
||||
float time_left = NAN, layer = NAN, total_layers = NAN, fan = NAN;
|
||||
float aux_fan = NAN, exhaust_fan = NAN;
|
||||
bool dual_nozzle = false;
|
||||
float left_nozzle = NAN, left_target = NAN, right_nozzle = NAN, right_target = NAN;
|
||||
std::vector<Tray> trays;
|
||||
|
||||
bool live() const { return connected && is_on(online) && valid_text(status); }
|
||||
// Centauri firmware parks on milestone names such as preheating_completed for
|
||||
// most of a job, so any status that is not a known idle or finished state is active.
|
||||
bool job_active() const {
|
||||
auto idle = [](const std::string &value) {
|
||||
return value.empty() || value == "unknown" || value == "unavailable" || value == "none" ||
|
||||
value == "idle" || value == "standby" || value == "ready" || value == "complete" ||
|
||||
value == "completed" || value == "finish" || value == "finished" || value == "cancelled" ||
|
||||
value == "canceled" || value == "stopped" || value == "stop" || value == "failed";
|
||||
};
|
||||
const auto state = lower(status);
|
||||
const auto machine = lower(stage);
|
||||
if (machine == "printing" || machine == "paused" || machine == "busy" || machine == "heating" ||
|
||||
machine == "preheating" || machine == "running")
|
||||
return true;
|
||||
return !idle(state);
|
||||
}
|
||||
// The Centauri reports percent_complete as 0 while CurrentLayer stays 0.
|
||||
// Layers, then elapsed time against the total, still describe the job.
|
||||
float completion() const {
|
||||
if (!elegoo) return progress;
|
||||
if (std::isfinite(progress) && progress > 0.0f) return std::clamp(progress, 0.0f, 100.0f);
|
||||
if (!job_active()) return std::isfinite(progress) ? 0.0f : NAN;
|
||||
if (std::isfinite(layer) && std::isfinite(total_layers) && layer > 0.0f && total_layers > 0.0f)
|
||||
return std::clamp(100.0f * layer / total_layers, 0.0f, 100.0f);
|
||||
const int elapsed_s = duration_seconds(elapsed, elapsed_unit);
|
||||
const int total_s = duration_seconds(total_time, total_time_unit);
|
||||
if (elapsed_s > 0 && total_s > 0)
|
||||
return std::clamp(100.0f * static_cast<float>(elapsed_s) / static_cast<float>(total_s), 0.0f, 100.0f);
|
||||
return std::isfinite(progress) ? 0.0f : NAN;
|
||||
}
|
||||
bool has_print_error() const {
|
||||
if (!elegoo) return is_on(print_error);
|
||||
return elegoo_fault(print_error) || elegoo_fault(print_error_reason) || lower(status) == "error";
|
||||
}
|
||||
std::string error_message() const {
|
||||
if (!elegoo) return valid_text(print_error_message) ? print_error_message : "Print error reported; tap status for details";
|
||||
std::string message;
|
||||
for (const auto &text : {print_error, print_error_reason}) {
|
||||
if (!elegoo_fault(text)) continue;
|
||||
if (!message.empty()) message += " / ";
|
||||
message += humanize(text);
|
||||
}
|
||||
return message.empty() ? "Print error reported; tap status for details" : message;
|
||||
}
|
||||
int notice_count() const {
|
||||
int loaded = 0;
|
||||
for (const auto ¬ice : health_notices)
|
||||
if (valid_text(notice.code) || valid_text(notice.message)) ++loaded;
|
||||
char *end;
|
||||
const long count = std::strtol(hms_count.c_str(), &end, 10);
|
||||
return end != hms_count.c_str() && *end == '\0' && count >= 0 && count < 10000
|
||||
? std::max(loaded, static_cast<int>(count)) : loaded;
|
||||
}
|
||||
int health_severity_rank() const {
|
||||
int rank = 1, loaded = 0;
|
||||
for (const auto ¬ice : health_notices) {
|
||||
if (!valid_text(notice.code) && !valid_text(notice.message)) continue;
|
||||
++loaded;
|
||||
const auto severity = lower(notice.severity);
|
||||
rank = std::max(rank, severity == "fatal" || severity == "serious" ? 3 : severity == "info" ? 1 : 2);
|
||||
}
|
||||
// Missing/unloaded messages cannot establish an info-only notification.
|
||||
return loaded == 0 || notice_count() > loaded ? std::max(rank, 2) : rank;
|
||||
}
|
||||
std::string alert_summary() const {
|
||||
if (has_print_error()) return error_message();
|
||||
if (is_on(hms_error)) {
|
||||
for (const auto ¬ice : health_notices)
|
||||
if (valid_text(notice.message)) return notice.message;
|
||||
return "Printer health notification; tap status for details";
|
||||
}
|
||||
return "";
|
||||
}
|
||||
std::string health_details() const {
|
||||
if (!live()) return "Printer health data is unavailable. Waiting for a live connection.";
|
||||
std::string details;
|
||||
if (has_print_error()) {
|
||||
details = "PRINT ERROR\n";
|
||||
if (valid_text(print_error_code)) details += print_error_code + "\n";
|
||||
details += error_message();
|
||||
details += "\n\n";
|
||||
}
|
||||
if (is_on(hms_error)) {
|
||||
details += "HMS = Health Management System\n";
|
||||
int loaded = 0;
|
||||
for (const auto ¬ice : health_notices) {
|
||||
if (!valid_text(notice.code) && !valid_text(notice.message)) continue;
|
||||
details += "\nNotice " + std::to_string(++loaded);
|
||||
if (valid_text(notice.severity)) details += " / " + notice.severity;
|
||||
details += "\n";
|
||||
if (valid_text(notice.code)) details += notice.code + "\n";
|
||||
details += valid_text(notice.message) ? notice.message : "Message not supplied by Home Assistant.";
|
||||
details += "\n";
|
||||
}
|
||||
if (loaded == 0) details += "\nNotification is active; details have not arrived from Home Assistant.\n";
|
||||
if (notice_count() > loaded)
|
||||
details += "\n" + std::to_string(notice_count() - loaded) + " more notification(s). View all in Home Assistant.\n";
|
||||
}
|
||||
return details.empty() ? "No current printer health notifications." : details;
|
||||
}
|
||||
std::string status_text() const {
|
||||
if (!connected) return "HA OFFLINE";
|
||||
if (lower(online) == "off" || lower(online) == "false") return "PRINTER OFFLINE";
|
||||
if (!live()) return "SYNCING";
|
||||
if (has_print_error()) return "PRINT ERROR";
|
||||
if (is_on(hms_error))
|
||||
return health_severity_rank() >= 3 ? "HEALTH ERROR" : health_severity_rank() == 1 ? "HEALTH INFO" : "HEALTH NOTICE";
|
||||
const auto state = lower(status);
|
||||
if (state == "running" || state == "printing") return "PRINTING";
|
||||
if (state == "pause" || state == "paused") return "PAUSED";
|
||||
if (state == "finish" || state == "finished" || state == "complete" || state == "completed") return "COMPLETE";
|
||||
auto text = humanize(status);
|
||||
for (char &c : text) c = static_cast<char>(std::toupper(static_cast<unsigned char>(c)));
|
||||
return text;
|
||||
}
|
||||
std::string overview_task() const {
|
||||
if (!live()) return connected ? "Waiting for printer data" : "Waiting for Home Assistant";
|
||||
return valid_text(task) ? task : "No active print";
|
||||
}
|
||||
std::string overview_meta() const {
|
||||
if (!live()) return "--";
|
||||
const float percent = completion();
|
||||
const std::string progress_text = std::isfinite(percent)
|
||||
? number(std::clamp(percent, 0.0f, 100.0f)) + "%" : "--";
|
||||
const std::string time_line = remaining(time_left, remaining_unit);
|
||||
if (!finish_time.empty() && time_line != "--")
|
||||
return progress_text + " " + finish_time + " " + time_line;
|
||||
return progress_text + " " + time_line;
|
||||
}
|
||||
// Active tray is the spool the printer loaded most recently. Without one, keep green.
|
||||
uint32_t active_filament_color() const {
|
||||
for (const auto &tray : trays)
|
||||
if (is_on(tray.active)) {
|
||||
const uint32_t color = filament_color(tray.color);
|
||||
if (color != TRACK) return color;
|
||||
}
|
||||
return GREEN;
|
||||
}
|
||||
uint32_t status_color() const {
|
||||
if (!live()) return MUTED;
|
||||
const auto state = lower(status);
|
||||
if (has_print_error() || state == "failed") return RED;
|
||||
if (is_on(hms_error))
|
||||
return health_severity_rank() >= 3 ? RED : health_severity_rank() == 1 ? 0x00E5E5 : AMBER;
|
||||
if (state == "pause" || state == "paused" || state == "pausing") return AMBER;
|
||||
return GREEN;
|
||||
}
|
||||
};
|
||||
|
||||
struct FleetCounts {
|
||||
int attention = 0, printing = 0, paused = 0, idle = 0, offline = 0, syncing = 0;
|
||||
};
|
||||
|
||||
inline void tally(FleetCounts &counts, const Snapshot &data) {
|
||||
if (!data.connected || lower(data.online) == "off" || lower(data.online) == "false") {
|
||||
++counts.offline;
|
||||
return;
|
||||
}
|
||||
if (!data.live()) {
|
||||
++counts.syncing;
|
||||
return;
|
||||
}
|
||||
if (data.has_print_error() || is_on(data.hms_error) || lower(data.status) == "failed") {
|
||||
++counts.attention;
|
||||
return;
|
||||
}
|
||||
const auto state = lower(data.status);
|
||||
if (state == "running" || state == "printing") ++counts.printing;
|
||||
else if (state == "pause" || state == "paused" || state == "pausing") ++counts.paused;
|
||||
else ++counts.idle;
|
||||
}
|
||||
|
||||
inline std::string fleet_summary(const FleetCounts &counts) {
|
||||
const std::pair<int, const char *> items[] = {
|
||||
{counts.attention, "attention"}, {counts.printing, "printing"}, {counts.paused, "paused"},
|
||||
{counts.idle, "idle"}, {counts.offline, "offline"}, {counts.syncing, "syncing"}};
|
||||
std::string text;
|
||||
for (const auto &item : items) {
|
||||
if (!item.first) continue;
|
||||
if (!text.empty()) text += ", ";
|
||||
text += std::to_string(item.first) + " " + item.second;
|
||||
}
|
||||
return text.empty() ? "Syncing" : text;
|
||||
}
|
||||
|
||||
} // namespace bambu_display
|
||||
|
||||
#ifdef USE_LVGL
|
||||
#include <lvgl.h>
|
||||
|
||||
namespace bambu_display {
|
||||
|
||||
struct TrayWidgets { lv_obj_t *card, *swatch, *label; };
|
||||
struct GaugeWidgets { lv_obj_t *arc, *value, *detail; };
|
||||
struct Widgets {
|
||||
lv_obj_t *status, *task, *top_progress, *remaining, *layers, *stage, *health_details;
|
||||
GaugeWidgets progress, nozzle, bed, fan, aux, exhaust;
|
||||
std::vector<TrayWidgets> trays;
|
||||
GaugeWidgets uv{}, layer{};
|
||||
lv_obj_t *elapsed = nullptr;
|
||||
};
|
||||
struct OverviewWidgets {
|
||||
lv_obj_t *arc, *value, *finish, *backing;
|
||||
};
|
||||
|
||||
inline void set_text(lv_obj_t *label, const std::string &text) {
|
||||
if (text != lv_label_get_text(label)) lv_label_set_text(label, text.c_str());
|
||||
}
|
||||
|
||||
inline void render_gauge(const GaugeWidgets &widgets, float value, float position,
|
||||
int maximum, const std::string &detail) {
|
||||
lv_arc_set_value(widgets.arc, gauge_value(position, maximum));
|
||||
set_text(widgets.value, number(value));
|
||||
set_text(widgets.detail, detail);
|
||||
}
|
||||
|
||||
inline void render(const Snapshot &data, const Widgets &widgets) {
|
||||
const bool live = data.live();
|
||||
const uint32_t color = data.status_color();
|
||||
set_text(widgets.status, data.status_text());
|
||||
lv_obj_set_style_text_color(widgets.status, lv_color_hex(color), 0);
|
||||
lv_obj_set_style_arc_color(widgets.progress.arc, lv_color_hex(color), LV_PART_INDICATOR);
|
||||
const float percent = data.completion();
|
||||
lv_bar_set_value(widgets.top_progress, live ? gauge_value(percent, 100) : 0, LV_ANIM_OFF);
|
||||
render_gauge(widgets.progress, live && std::isfinite(percent)
|
||||
? std::clamp(percent, 0.0f, 100.0f) : NAN, live ? percent : NAN, 100,
|
||||
live ? remaining(data.time_left, data.remaining_unit) : "--");
|
||||
set_text(widgets.task, live ? (valid_text(data.task) ? data.task : "No active print")
|
||||
: data.connected ? "Waiting for printer data" : "Waiting for Home Assistant");
|
||||
if (data.resin) {
|
||||
render_gauge(widgets.uv, live ? data.uv : NAN,
|
||||
live ? celsius(data.uv, data.uv_unit) : NAN, 100,
|
||||
live && valid_text(data.uv_unit) ? data.uv_unit : "--");
|
||||
const float layer_percent = std::isfinite(data.layer) && std::isfinite(data.total_layers)
|
||||
&& data.layer >= 0 && data.total_layers > 0 ? 100 * data.layer / data.total_layers : NAN;
|
||||
render_gauge(widgets.layer, live ? data.layer : NAN, live ? layer_percent : NAN, 100,
|
||||
live ? "/ " + number(data.total_layers) : "--");
|
||||
set_text(widgets.elapsed, "Elapsed: " + (live ? remaining(data.elapsed, data.elapsed_unit) : "--"));
|
||||
} else {
|
||||
const float nozzle = data.dual_nozzle ? data.left_nozzle : data.nozzle;
|
||||
const float target = data.dual_nozzle ? data.left_target : data.nozzle_target;
|
||||
render_gauge(widgets.nozzle, live ? nozzle : NAN, live ? celsius(nozzle, data.nozzle_unit) : NAN,
|
||||
300, live ? "/ " + number(target) + " " + data.nozzle_unit : "--");
|
||||
render_gauge(widgets.bed, live ? data.bed : NAN, live ? celsius(data.bed, data.bed_unit) : NAN,
|
||||
120, live ? "/ " + number(data.bed_target) + " " + data.bed_unit : "--");
|
||||
render_gauge(widgets.fan, live ? data.fan : NAN, live ? data.fan : NAN, 100, "%");
|
||||
if (data.dual_nozzle)
|
||||
render_gauge(widgets.aux, live ? data.right_nozzle : NAN,
|
||||
live ? celsius(data.right_nozzle, data.nozzle_unit) : NAN,
|
||||
300, live ? "/ " + number(data.right_target) + " " + data.nozzle_unit : "--");
|
||||
else
|
||||
render_gauge(widgets.aux, live ? data.aux_fan : NAN, live ? data.aux_fan : NAN, 100, "%");
|
||||
render_gauge(widgets.exhaust, live ? data.exhaust_fan : NAN, live ? data.exhaust_fan : NAN, 100, "%");
|
||||
}
|
||||
std::string time_line = live ? remaining(data.time_left, data.remaining_unit) : "--";
|
||||
if (live && !data.finish_time.empty()) time_line = data.finish_time + " | " + time_line;
|
||||
set_text(widgets.remaining, time_line);
|
||||
set_text(widgets.layers, live ? "Layer " + number(data.layer) + " / " + number(data.total_layers) : "--");
|
||||
std::string stage = live ? humanize(data.stage) : "Telemetry unavailable";
|
||||
if (live && (data.has_print_error() || is_on(data.hms_error))) stage = data.alert_summary();
|
||||
set_text(widgets.stage, stage);
|
||||
lv_obj_set_style_text_color(widgets.stage, lv_color_hex(live && !data.alert_summary().empty() ? color : MUTED), 0);
|
||||
set_text(widgets.health_details, data.health_details());
|
||||
for (size_t i = 0; i < std::min(data.trays.size(), widgets.trays.size()); ++i) {
|
||||
const auto &tray = data.trays[i];
|
||||
const auto &widget = widgets.trays[i];
|
||||
set_text(widget.label, tray.label + "\n" + (live && valid_text(tray.type) ? tray.type : "--"));
|
||||
lv_obj_set_style_bg_color(widget.swatch, lv_color_hex(live ? filament_color(tray.color) : TRACK), 0);
|
||||
lv_obj_set_style_border_color(widget.card, lv_color_hex(live && is_on(tray.active) ? GREEN : TRACK), 0);
|
||||
}
|
||||
}
|
||||
|
||||
inline void render_overview(const Snapshot &data, const OverviewWidgets &widgets) {
|
||||
const bool live = data.live();
|
||||
const uint32_t filament = data.active_filament_color();
|
||||
const float percent = data.completion();
|
||||
const float shown = live && std::isfinite(percent)
|
||||
? std::clamp(percent, 0.0f, 100.0f) : NAN;
|
||||
const bool black = filament_is_black(filament);
|
||||
lv_obj_set_style_arc_color(widgets.arc, lv_color_hex(black ? RING_BACKING : TRACK), LV_PART_MAIN);
|
||||
lv_obj_set_style_arc_color(widgets.arc, lv_color_hex(filament), LV_PART_INDICATOR);
|
||||
lv_arc_set_value(widgets.arc, live ? gauge_value(percent, 100) : 0);
|
||||
if (black) {
|
||||
lv_obj_set_style_arc_color(widgets.backing, lv_color_hex(RING_BACKING), LV_PART_MAIN);
|
||||
lv_obj_set_style_arc_color(widgets.backing, lv_color_hex(RING_BACKING), LV_PART_INDICATOR);
|
||||
lv_obj_clear_flag(widgets.backing, LV_OBJ_FLAG_HIDDEN);
|
||||
} else {
|
||||
lv_obj_add_flag(widgets.backing, LV_OBJ_FLAG_HIDDEN);
|
||||
}
|
||||
set_text(widgets.value, number(shown));
|
||||
lv_obj_set_style_text_color(widgets.value, lv_color_hex(0xFFFFFF), 0);
|
||||
set_text(widgets.finish, live && !data.finish_time.empty() ? data.finish_time : "--");
|
||||
}
|
||||
|
||||
inline void render_fleet_summary(lv_obj_t *label, const FleetCounts &counts) {
|
||||
set_text(label, fleet_summary(counts));
|
||||
const uint32_t color = counts.attention ? RED : counts.printing ? GREEN : MUTED;
|
||||
lv_obj_set_style_text_color(label, lv_color_hex(color), 0);
|
||||
}
|
||||
|
||||
} // namespace bambu_display
|
||||
#endif
|
||||
@@ -0,0 +1,485 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Generate the ESPHome fleet package from printers.yaml. Requires PyYAML."""
|
||||
|
||||
import argparse
|
||||
import json
|
||||
from pathlib import Path
|
||||
import re
|
||||
|
||||
import yaml
|
||||
|
||||
HERE = Path(__file__).resolve().parent
|
||||
NUMERIC = {
|
||||
'progress': 'print_progress', 'nozzle': 'nozzle_temperature',
|
||||
'nozzle_target': 'nozzle_target_temperature', 'bed': 'bed_temperature',
|
||||
'bed_target': 'bed_target_temperature', 'time_left': 'remaining_time',
|
||||
'layer': 'current_layer', 'total_layers': 'total_layer_count',
|
||||
'fan': 'cooling_fan_speed',
|
||||
}
|
||||
DUAL = {
|
||||
'left_nozzle': 'left_nozzle_temperature', 'left_target': 'left_nozzle_target_temperature',
|
||||
'right_nozzle': 'right_nozzle_temperature', 'right_target': 'right_nozzle_target_temperature',
|
||||
}
|
||||
EXTRA_FANS = {'aux_fan': 'aux_fan_speed', 'exhaust_fan': 'chamber_fan_speed'}
|
||||
TEXT = {'status': 'print_status', 'task': 'task_name', 'stage': 'current_stage'}
|
||||
BINARY = {'online': 'online', 'print_error': 'print_error', 'hms_error': 'hms_errors'}
|
||||
ELEGOO_COMMON = {'progress': 'percent_complete', 'time_left': 'remaining_print_time',
|
||||
'layer': 'current_layer', 'total_layers': 'total_layers',
|
||||
'total_time': 'total_print_time'}
|
||||
ELEGOO_FDM = {'nozzle': 'nozzle_temperature', 'bed': 'bed_temperature',
|
||||
'elapsed': 'current_print_time',
|
||||
'nozzle_target': ('number', 'target_nozzle_temp'),
|
||||
'bed_target': ('number', 'target_bed_temp'), 'fan': 'model_fan_speed',
|
||||
'aux_fan': 'auxiliary_fan_speed', 'exhaust_fan': 'enclosure_fan_speed'}
|
||||
ELEGOO_RESIN = {'uv': 'uv_led_temp', 'elapsed': 'current_print_time'}
|
||||
BG, CARD, TRACK, TEXT_COLOR, MUTED, GREEN = (
|
||||
0x000000, 0x000000, 0x19232B, 0xEEEEEE, 0xAEB3B7, 0x00FF00)
|
||||
RING_BACKING = 0x6E767E
|
||||
ORANGE, CYAN = 0xFFAA00, 0x00E5E5
|
||||
|
||||
|
||||
class Lambda(str):
|
||||
"""A C++ lambda serialized with ESPHome's YAML tag."""
|
||||
|
||||
|
||||
class Dumper(yaml.SafeDumper):
|
||||
def ignore_aliases(self, data):
|
||||
return True
|
||||
|
||||
|
||||
Dumper.add_representer(Lambda, lambda dumper, text: dumper.represent_scalar('!lambda', text, style='|'))
|
||||
|
||||
|
||||
def entity(printer, key, suffix, domain='sensor'):
|
||||
return printer.get('entities', {}).get(key, f'{domain}.{printer["entity_prefix"]}_{suffix}')
|
||||
|
||||
|
||||
def trays_for(printer):
|
||||
result = []
|
||||
for bank in printer.get('filament_banks', []):
|
||||
for slot in range(1, 5):
|
||||
result.append({'label': f'{bank["label"]}{slot}',
|
||||
'entity': f'sensor.{bank["entity_prefix"]}_tray_{slot}'})
|
||||
return result + printer.get('external_spools', [])
|
||||
|
||||
|
||||
def validate_printers(printers):
|
||||
if not isinstance(printers, list) or not printers:
|
||||
raise ValueError('printers must be a nonempty list')
|
||||
ids = set()
|
||||
for printer in printers:
|
||||
for key in ('id', 'name', 'subtitle', 'entity_prefix'):
|
||||
if not isinstance(printer.get(key), str) or not printer[key]:
|
||||
raise ValueError(f'Each printer needs a nonempty {key}')
|
||||
pid = printer['id']
|
||||
if not re.fullmatch(r'[a-z][a-z0-9_]*', pid) or pid in ids:
|
||||
raise ValueError(f'Invalid or duplicate printer id: {pid}')
|
||||
ids.add(pid)
|
||||
profile = printer.get('profile', 'bambu')
|
||||
if profile not in ('bambu', 'elegoo_fdm', 'elegoo_resin'):
|
||||
raise ValueError(f'{pid}: unsupported profile: {profile}')
|
||||
if profile != 'bambu' and (printer.get('dual_nozzle') or trays_for(printer) or printer.get('extra_fans')):
|
||||
raise ValueError(f'{pid}: dual_nozzle, filament slots and extra_fans require the bambu profile')
|
||||
if len(trays_for(printer)) > 8:
|
||||
raise ValueError(f'{pid}: the filament strip fits up to eight slots')
|
||||
if 'dual_nozzle' in printer and not isinstance(printer['dual_nozzle'], bool):
|
||||
raise ValueError(f'{pid}: dual_nozzle must be true or false')
|
||||
if any(key not in EXTRA_FANS for key in printer.get('extra_fans', [])):
|
||||
raise ValueError(f'{pid}: extra_fans supports aux_fan and exhaust_fan')
|
||||
for value in printer.get('entities', {}).values():
|
||||
if not isinstance(value, str) or not re.fullmatch(r'(sensor|binary_sensor|number)\.[a-z0-9_]+', value):
|
||||
raise ValueError(f'{pid}: invalid entity override: {value}')
|
||||
|
||||
|
||||
def label(text, x, y, width, font=18, **kwargs):
|
||||
return {'label': {'text': text, 'x': x, 'y': y, 'width': width,
|
||||
'height': font + 8, 'text_color': TEXT_COLOR,
|
||||
'text_font': f'montserrat_{font}', 'long_mode': 'DOT',
|
||||
'gesture_bubble': True, **kwargs}}
|
||||
|
||||
|
||||
def gauge(pid, key, title, x, y, color, maximum):
|
||||
return [
|
||||
{'arc': {'id': f'{pid}_{key}_arc', 'x': x + 8, 'y': y, 'width': 154, 'height': 154,
|
||||
'min_value': 0, 'max_value': maximum, 'value': 0,
|
||||
'start_angle': 135, 'end_angle': 45, 'arc_width': 13,
|
||||
'arc_color': TRACK, 'indicator': {'arc_color': color, 'arc_width': 13},
|
||||
'adjustable': False, 'gesture_bubble': True}},
|
||||
label('--', x + 14, y + 47, 142, 32, id=f'{pid}_{key}_label', text_align='CENTER'),
|
||||
label('--', x + 14, y + 86, 142, 16, id=f'{pid}_{key}_detail', text_align='CENTER', text_color=MUTED),
|
||||
label(title, x, y + 137, 170, 18, text_align='CENTER', text_color=color),
|
||||
]
|
||||
|
||||
|
||||
def fdm_gauges(printer):
|
||||
pid = printer['id']
|
||||
widgets = gauge(pid, 'nozzle', 'Nozzle L' if printer.get('dual_nozzle') else 'Nozzle',
|
||||
202, 62, ORANGE, 300)
|
||||
widgets += gauge(pid, 'bed', 'Bed', 388, 62, CYAN, 120)
|
||||
widgets += gauge(pid, 'fan', 'Part', 16, 229, 0xEEEEEE, 100)
|
||||
widgets += gauge(pid, 'aux', 'Nozzle R' if printer.get('dual_nozzle') else 'Aux',
|
||||
202, 229, ORANGE if printer.get('dual_nozzle') else 0xEEEEEE,
|
||||
300 if printer.get('dual_nozzle') else 100)
|
||||
widgets += gauge(pid, 'exhaust', 'Exhaust', 388, 229, GREEN, 100)
|
||||
return widgets
|
||||
|
||||
|
||||
def page_for(printer, display_index, total):
|
||||
pid = printer['id']
|
||||
resin = printer.get('profile') == 'elegoo_resin'
|
||||
widgets = [
|
||||
{'bar': {'id': f'{pid}_top_progress', 'x': 0, 'y': 0, 'width': 800, 'height': 5,
|
||||
'min_value': 0, 'max_value': 100, 'value': 0, 'bg_color': TRACK,
|
||||
'indicator': {'bg_color': GREEN}, 'gesture_bubble': True}},
|
||||
label(printer['name'], 16, 17, 485, 26, text_color=GREEN),
|
||||
label(f'{display_index} / {total}', 520, 24, 70, 14, text_color=MUTED, text_align='CENTER'),
|
||||
label('SYNCING', 598, 21, 186, 16, id=f'{pid}_status_label', text_align='RIGHT', text_color=MUTED,
|
||||
clickable=True, on_click=[{'lvgl.widget.show': f'{pid}_health_panel'}]),
|
||||
{'obj': {'x': 578, 'y': 64, 'width': 1, 'height': 362, 'bg_color': TRACK,
|
||||
'border_width': 0, 'pad_all': 0, 'scrollable': False, 'gesture_bubble': True}},
|
||||
]
|
||||
widgets += gauge(pid, 'progress', 'Progress', 16, 62, GREEN, 100)
|
||||
if resin:
|
||||
widgets += gauge(pid, 'uv', 'UV LED', 202, 62, ORANGE, 100)
|
||||
widgets += gauge(pid, 'layer', 'Layer', 388, 62, CYAN, 100)
|
||||
widgets += [label('FINISH / TIME REMAINING', 16, 260, 544, 18,
|
||||
text_align='CENTER', text_color=MUTED),
|
||||
label('--', 16, 354, 544, 20, id=f'{pid}_elapsed_label',
|
||||
text_align='CENTER', text_color=MUTED)]
|
||||
else:
|
||||
widgets += fdm_gauges(printer)
|
||||
widgets += [
|
||||
label('--', 16, 300 if resin else 401, 544, 26, id=f'{pid}_remaining_label', text_color=GREEN, text_align='CENTER'),
|
||||
label('Telemetry unavailable', 16, 430, 544, 12, id=f'{pid}_stage_label', text_color=MUTED,
|
||||
text_align='CENTER', long_mode='SCROLL_CIRCULAR', clickable=True,
|
||||
on_click=[{'lvgl.widget.show': f'{pid}_health_panel'}]),
|
||||
label('--', 594, 397, 190, 16, id=f'{pid}_layers_label', text_color=MUTED, text_align='CENTER'),
|
||||
]
|
||||
trays = trays_for(printer)
|
||||
for i, tray in enumerate(trays):
|
||||
bank = i // 4
|
||||
slot = i % 4
|
||||
if slot == 0:
|
||||
title = 'AMS ' + tray['label'][:-1] if tray['label'][-1:].isdigit() else 'EXTERNAL SPOOL'
|
||||
widgets.append(label(title, 594, 70 + bank * 145, 190, 20,
|
||||
text_align='CENTER', text_color=MUTED))
|
||||
widgets.append({'obj': {
|
||||
'id': f'{pid}_tray_{i}_card', 'x': 594 + 48 * slot, 'y': 102 + bank * 145,
|
||||
'width': 44, 'height': 82, 'bg_color': BG, 'radius': 5,
|
||||
'border_width': 1, 'border_color': TRACK, 'pad_all': 0,
|
||||
'scrollable': False, 'gesture_bubble': True,
|
||||
'widgets': [
|
||||
{'obj': {'id': f'{pid}_tray_{i}_swatch', 'x': 3, 'y': 3, 'width': 36,
|
||||
'height': 40, 'bg_color': TRACK, 'radius': 4, 'border_width': 0,
|
||||
'pad_all': 0, 'scrollable': False, 'gesture_bubble': True}},
|
||||
label(f'{tray["label"]}\n--', 1, 46, 40, 12, height=34,
|
||||
id=f'{pid}_tray_{i}_label', text_align='CENTER'),
|
||||
]}})
|
||||
if not trays:
|
||||
widgets.append(label('ELEGOO' if printer.get('profile', '').startswith('elegoo_') else 'No AMS', 594, 150, 190, 20,
|
||||
text_color=MUTED, text_align='CENTER'))
|
||||
widgets.append(label(printer['subtitle'], 594, 185, 190, 16,
|
||||
text_color=MUTED, text_align='CENTER'))
|
||||
for direction, x, text in (('previous', 16, '<'), ('next', 720, '>')):
|
||||
widgets.append({'button': {'x': x, 'y': 446, 'width': 64, 'height': 30,
|
||||
'bg_color': TRACK, 'radius': 8, 'border_width': 0,
|
||||
'pad_all': 0, 'gesture_bubble': True,
|
||||
'widgets': [{'label': {'text': text, 'align': 'CENTER',
|
||||
'text_color': TEXT_COLOR, 'text_font': 'montserrat_18'}}],
|
||||
'on_click': [{f'lvgl.page.{direction}': {}}]}})
|
||||
widgets.append(label('Waiting for Home Assistant', 94, 449, 612, 14,
|
||||
id=f'{pid}_task_label', text_align='CENTER'))
|
||||
widgets.append({'obj': {
|
||||
'id': f'{pid}_health_panel', 'x': 24, 'y': 62, 'width': 752, 'height': 366,
|
||||
'hidden': True, 'bg_color': BG, 'bg_opa': 'COVER', 'border_width': 2,
|
||||
'border_color': MUTED, 'radius': 10, 'pad_all': 0, 'scrollable': False,
|
||||
'gesture_bubble': False,
|
||||
'widgets': [
|
||||
label('Printer health / ' + printer['name'], 16, 14, 716, 20),
|
||||
{'obj': {'x': 12, 'y': 50, 'width': 724, 'height': 246,
|
||||
'bg_color': BG, 'border_width': 0, 'pad_all': 0,
|
||||
'scroll_dir': 'VER', 'scrollbar_mode': 'AUTO', 'gesture_bubble': False,
|
||||
'widgets': [label('No current health notifications.', 0, 0, 700, 18,
|
||||
id=f'{pid}_health_details', long_mode='WRAP', height='SIZE_CONTENT')]}},
|
||||
{'button': {'x': 624, 'y': 310, 'width': 108, 'height': 40, 'bg_color': TRACK,
|
||||
'radius': 6, 'widgets': [{'label': {'text': 'CLOSE', 'align': 'CENTER'}}],
|
||||
'on_click': [{'lvgl.widget.hide': f'{pid}_health_panel'}]}},
|
||||
]}})
|
||||
return {'id': f'{pid}_page', 'bg_color': BG, 'bg_opa': 'COVER',
|
||||
'pad_all': 0, 'scrollable': False, 'scrollbar_mode': 'OFF',
|
||||
'on_swipe_left': [
|
||||
{'lambda': Lambda('if (auto *input = lv_indev_active()) lv_indev_wait_release(input);')},
|
||||
{'lvgl.page.next': {'animation': 'MOVE_LEFT', 'time': '180ms'}}],
|
||||
'on_swipe_right': [
|
||||
{'lambda': Lambda('if (auto *input = lv_indev_active()) lv_indev_wait_release(input);')},
|
||||
{'lvgl.page.previous': {'animation': 'MOVE_RIGHT', 'time': '180ms'}}],
|
||||
'on_load': [{'script.execute': f'{pid}_refresh'}], 'widgets': widgets}
|
||||
|
||||
|
||||
def snapshot_lines(printer, expressions, trays):
|
||||
profile = printer.get('profile', 'bambu')
|
||||
elegoo = profile.startswith('elegoo_')
|
||||
resin = profile == 'elegoo_resin'
|
||||
lines = ['bambu_display::Snapshot data;',
|
||||
'data.connected = id(ha_api).is_connected_with_state_subscription();']
|
||||
for key, expr in expressions.items():
|
||||
if not key.startswith(('tray_', 'hms_1_', 'hms_2_', 'hms_3_')):
|
||||
lines.append(f'data.{key} = {expr};')
|
||||
if elegoo:
|
||||
lines.append('data.elegoo = true;')
|
||||
else:
|
||||
for number in range(1, 4):
|
||||
lines.append('data.health_notices.push_back({' + ', '.join(
|
||||
expressions[f'hms_{number}_{key}'] for key in ('code', 'message', 'severity')) + '});')
|
||||
if resin:
|
||||
lines.append('data.resin = true;')
|
||||
if printer.get('dual_nozzle'):
|
||||
lines.append('data.dual_nozzle = true;')
|
||||
lines += [
|
||||
'const int seconds_left = bambu_display::duration_seconds(data.time_left, data.remaining_unit);',
|
||||
'const auto clock = id(ha_time).now();',
|
||||
'if (clock.is_valid() && seconds_left >= 0)',
|
||||
' data.finish_time = esphome::ESPTime::from_epoch_local(clock.timestamp + seconds_left).strftime("%H:%M");',
|
||||
]
|
||||
for i, tray in enumerate(trays):
|
||||
lines.append('data.trays.push_back({' + ', '.join([
|
||||
json.dumps(tray['label']), expressions[f'tray_{i}_type'],
|
||||
expressions[f'tray_{i}_color'], expressions[f'tray_{i}_active']]) + '});')
|
||||
return lines
|
||||
|
||||
|
||||
def overview_slots(count):
|
||||
"""Place one steam gauge per printer under the page title."""
|
||||
columns = 4 if count > 4 else max(count, 1)
|
||||
rows = (count + columns - 1) // columns
|
||||
top, bottom = 46, 432
|
||||
row_h = (bottom - top) // rows
|
||||
arc = min(154, (800 // columns) - 24, row_h - 48)
|
||||
col_w = 800 // columns
|
||||
slots = []
|
||||
for index in range(count):
|
||||
row, col = divmod(index, columns)
|
||||
in_row = min(columns, count - row * columns)
|
||||
x0 = (800 - in_row * col_w) // 2
|
||||
gauge_w = arc + 16
|
||||
slots.append((x0 + col * col_w + (col_w - gauge_w) // 2,
|
||||
top + row * row_h, arc))
|
||||
return slots
|
||||
|
||||
|
||||
def overview_gauge(printer, x, y, arc):
|
||||
pid = printer['id']
|
||||
scale = arc / 154
|
||||
value_font = 32 if arc >= 130 else 18
|
||||
open_page = [{'lvgl.page.show': f'{pid}_page'}]
|
||||
inner = max(10, int(round(13 * scale)))
|
||||
inner_x = x + int(round(8 * scale))
|
||||
number_y = y + int(round(arc * 0.30))
|
||||
pad = 8
|
||||
return [
|
||||
{'arc': {'id': f'{pid}_overview_ring', 'x': inner_x - pad // 2, 'y': y - pad // 2,
|
||||
'width': arc + pad, 'height': arc + pad, 'min_value': 0, 'max_value': 100,
|
||||
'value': 100, 'start_angle': 135, 'end_angle': 45,
|
||||
'arc_width': inner + pad, 'arc_color': RING_BACKING,
|
||||
'indicator': {'arc_color': RING_BACKING, 'arc_width': inner + pad},
|
||||
'adjustable': False, 'hidden': True, 'gesture_bubble': True, 'clickable': True,
|
||||
'on_click': open_page}},
|
||||
{'arc': {'id': f'{pid}_overview_arc', 'x': inner_x, 'y': y,
|
||||
'width': arc, 'height': arc, 'min_value': 0, 'max_value': 100, 'value': 0,
|
||||
'start_angle': 135, 'end_angle': 45, 'arc_width': inner, 'arc_color': TRACK,
|
||||
'indicator': {'arc_color': GREEN, 'arc_width': inner}, 'adjustable': False,
|
||||
'gesture_bubble': True, 'clickable': True, 'on_click': open_page}},
|
||||
label('--', inner_x, number_y, arc, value_font, id=f'{pid}_overview_value',
|
||||
text_align='CENTER', text_color=0xFFFFFF, clickable=True, on_click=open_page),
|
||||
label('%', inner_x, number_y + (34 if value_font >= 28 else 20), arc, 16,
|
||||
id=f'{pid}_overview_percent', text_align='CENTER', text_color=0xFFFFFF,
|
||||
clickable=True, on_click=open_page),
|
||||
label(printer['name'], x, y + arc + 2, arc + 16, 14, text_align='CENTER',
|
||||
text_color=TEXT_COLOR, clickable=True, on_click=open_page),
|
||||
label('--', x, y + arc + 22, arc + 16, 14, id=f'{pid}_overview_finish',
|
||||
text_align='CENTER', text_color=TEXT_COLOR, clickable=True, on_click=open_page),
|
||||
]
|
||||
|
||||
|
||||
def overview_page(printers):
|
||||
widgets = [
|
||||
label('Print Status Overview', 16, 10, 520, 22, text_color=GREEN),
|
||||
label('--:--', 548, 12, 236, 18, id='fleet_clock', text_align='RIGHT', text_color=TEXT_COLOR),
|
||||
]
|
||||
for printer, (x, y, arc) in zip(printers, overview_slots(len(printers))):
|
||||
widgets += overview_gauge(printer, x, y, arc)
|
||||
for direction, x, text in (('previous', 16, '<'), ('next', 720, '>')):
|
||||
widgets.append({'button': {'x': x, 'y': 446, 'width': 64, 'height': 30,
|
||||
'bg_color': TRACK, 'radius': 8, 'border_width': 0,
|
||||
'pad_all': 0, 'gesture_bubble': True,
|
||||
'widgets': [{'label': {'text': text, 'align': 'CENTER',
|
||||
'text_color': TEXT_COLOR, 'text_font': 'montserrat_18'}}],
|
||||
'on_click': [{f'lvgl.page.{direction}': {}}]}})
|
||||
widgets.append(label(f'1 / {len(printers) + 1}', 94, 449, 612, 14,
|
||||
text_align='CENTER', text_color=MUTED))
|
||||
return {'id': 'fleet_page', 'bg_color': BG, 'bg_opa': 'COVER',
|
||||
'pad_all': 0, 'scrollable': False, 'scrollbar_mode': 'OFF',
|
||||
'on_swipe_left': [
|
||||
{'lambda': Lambda('if (auto *input = lv_indev_active()) lv_indev_wait_release(input);')},
|
||||
{'lvgl.page.next': {'animation': 'MOVE_LEFT', 'time': '180ms'}}],
|
||||
'on_swipe_right': [
|
||||
{'lambda': Lambda('if (auto *input = lv_indev_active()) lv_indev_wait_release(input);')},
|
||||
{'lvgl.page.previous': {'animation': 'MOVE_RIGHT', 'time': '180ms'}}],
|
||||
'on_load': [{'script.execute': 'fleet_refresh'}], 'widgets': widgets}
|
||||
|
||||
|
||||
def fleet_refresh_lines(snapshots):
|
||||
lines = []
|
||||
for pid, snapshot in snapshots:
|
||||
lines.append('{')
|
||||
lines.extend(snapshot)
|
||||
lines.append('bambu_display::render_overview(data, {' + ', '.join([
|
||||
f'id({pid}_overview_arc)', f'id({pid}_overview_value)',
|
||||
f'id({pid}_overview_finish)', f'id({pid}_overview_ring)']) + '});')
|
||||
lines.append('}')
|
||||
lines += [
|
||||
'{',
|
||||
'const auto clock = id(ha_time).now();',
|
||||
'std::string clock_text = "--:--";',
|
||||
'if (clock.is_valid()) {',
|
||||
' clock_text = esphome::ESPTime::from_epoch_local(clock.timestamp).strftime("%I:%M %p");',
|
||||
' if (!clock_text.empty() && clock_text[0] == \'0\') clock_text.erase(0, 1);',
|
||||
'}',
|
||||
'bambu_display::set_text(id(fleet_clock), clock_text);',
|
||||
'}',
|
||||
]
|
||||
return lines
|
||||
|
||||
|
||||
def color_test_page():
|
||||
widgets = [label('DISPLAY COLOR TEST', 16, 18, 768, 28),
|
||||
label('Each block must match its label. Swipe to return to the printers.',
|
||||
16, 62, 768, 18)]
|
||||
colors = [('RED', 0xFF0000), ('GREEN', 0x00FF00), ('BLUE', 0x0000FF),
|
||||
('WHITE', 0xFFFFFF), ('CYAN', 0x00FFFF), ('MAGENTA', 0xFF00FF)]
|
||||
for i, (name, color) in enumerate(colors):
|
||||
widgets += [
|
||||
{'obj': {'x': 16 + i * 129, 'y': 118, 'width': 122, 'height': 200,
|
||||
'bg_color': color, 'bg_opa': 'COVER', 'border_width': 0,
|
||||
'scrollable': False, 'gesture_bubble': True}},
|
||||
label(name, 16 + i * 129, 332, 122, 18, text_align='CENTER'),
|
||||
]
|
||||
return {'id': 'color_test_page', 'skip': True, 'bg_color': BG, 'bg_opa': 'COVER',
|
||||
'pad_all': 0, 'scrollable': False,
|
||||
'on_swipe_left': [{'lvgl.page.next': {}}],
|
||||
'on_swipe_right': [{'lvgl.page.previous': {}}], 'widgets': widgets}
|
||||
|
||||
|
||||
def generate(printers):
|
||||
validate_printers(printers)
|
||||
package = {'sensor': [], 'text_sensor': [], 'script': [],
|
||||
'interval': [{'interval': '1s', 'then': []}], 'lvgl': {'pages': []}}
|
||||
snapshots = []
|
||||
page_total = len(printers) + 1
|
||||
for index, printer in enumerate(printers):
|
||||
pid = printer['id']
|
||||
profile = printer.get('profile', 'bambu')
|
||||
elegoo = profile.startswith('elegoo_')
|
||||
resin = profile == 'elegoo_resin'
|
||||
fields = {**ELEGOO_COMMON, **(ELEGOO_RESIN if resin else ELEGOO_FDM)} if elegoo else dict(NUMERIC)
|
||||
if printer.get('dual_nozzle'):
|
||||
fields.update(DUAL)
|
||||
fields.update({key: EXTRA_FANS[key] for key in printer.get('extra_fans', [])})
|
||||
expressions = {}
|
||||
for key, suffix in fields.items():
|
||||
sid = f'{pid}_{key}'
|
||||
domain, suffix = suffix if isinstance(suffix, tuple) else ('sensor', suffix)
|
||||
eid = entity(printer, key, suffix, domain)
|
||||
package['sensor'].append({'platform': 'homeassistant', 'id': sid,
|
||||
'entity_id': eid, 'internal': True})
|
||||
expressions[key] = f'id({sid}).state'
|
||||
if elegoo:
|
||||
text_fields = {key: (entity(printer, key, suffix), None) for key, suffix in {
|
||||
'status': 'print_status', 'task': 'file_name', 'stage': 'current_status',
|
||||
'print_error': 'print_error', 'print_error_reason': 'error_status_reason'}.items()}
|
||||
text_fields['online'] = (entity(printer, 'online', 'sdcp_status', 'binary_sensor'), None)
|
||||
else:
|
||||
text_fields = {key: (entity(printer, key, suffix), None) for key, suffix in TEXT.items()}
|
||||
text_fields.update({key: (entity(printer, key, suffix, 'binary_sensor'), None)
|
||||
for key, suffix in BINARY.items()})
|
||||
hms_entity = entity(printer, 'hms_error', BINARY['hms_error'], 'binary_sensor')
|
||||
text_fields['hms_count'] = (hms_entity, 'Count')
|
||||
for number in range(1, 4):
|
||||
for key, attribute in [('message', 'Error'), ('code', 'Code'), ('severity', 'Severity')]:
|
||||
text_fields[f'hms_{number}_{key}'] = (hms_entity, f'{number}-{attribute}')
|
||||
print_error_entity = entity(printer, 'print_error', BINARY['print_error'], 'binary_sensor')
|
||||
text_fields['print_error_message'] = (print_error_entity, 'error')
|
||||
text_fields['print_error_code'] = (print_error_entity, 'code')
|
||||
for key, numeric_key in [('remaining_unit', 'time_left'), ('nozzle_unit', 'nozzle'),
|
||||
('bed_unit', 'bed'), ('uv_unit', 'uv'), ('elapsed_unit', 'elapsed'),
|
||||
('total_time_unit', 'total_time')]:
|
||||
if numeric_key in fields:
|
||||
suffix = fields[numeric_key]
|
||||
domain, suffix = suffix if isinstance(suffix, tuple) else ('sensor', suffix)
|
||||
text_fields[key] = (entity(printer, numeric_key, suffix, domain), 'unit_of_measurement')
|
||||
trays = trays_for(printer)
|
||||
for i, tray in enumerate(trays):
|
||||
for key, attr in [('type', 'type'), ('color', 'color'), ('active', 'active')]:
|
||||
text_fields[f'tray_{i}_{key}'] = (tray['entity'], attr)
|
||||
for key, (eid, attribute) in text_fields.items():
|
||||
sid = f'{pid}_{key}'
|
||||
subscription = {'platform': 'homeassistant', 'id': sid, 'entity_id': eid, 'internal': True}
|
||||
if attribute:
|
||||
subscription['attribute'] = attribute
|
||||
package['text_sensor'].append(subscription)
|
||||
expressions[key] = f'id({sid}).state'
|
||||
lines = snapshot_lines(printer, expressions, trays)
|
||||
snapshots.append((pid, list(lines)))
|
||||
tray_widgets = ['{' + ', '.join(f'id({pid}_tray_{i}_{part})'
|
||||
for part in ('card', 'swatch', 'label')) + '}'
|
||||
for i in range(len(trays))]
|
||||
refs = [f'id({pid}_{key})' for key in (
|
||||
'status_label', 'task_label', 'top_progress', 'remaining_label', 'layers_label', 'stage_label',
|
||||
'health_details')]
|
||||
for key in ('progress', 'nozzle', 'bed', 'fan', 'aux', 'exhaust'):
|
||||
refs.append('{' + ', '.join(f'id({pid}_{key}_{part})' for part in ('arc', 'label', 'detail')) + '}'
|
||||
if not resin or key == 'progress' else '{}')
|
||||
refs.append('{' + ', '.join(tray_widgets) + '}')
|
||||
if resin:
|
||||
for key in ('uv', 'layer'):
|
||||
refs.append('{' + ', '.join(f'id({pid}_{key}_{part})' for part in ('arc', 'label', 'detail')) + '}')
|
||||
refs.append(f'id({pid}_elapsed_label)')
|
||||
lines.append('bambu_display::render(data, {' + ', '.join(refs) + '});')
|
||||
package['script'].append({'id': f'{pid}_refresh', 'then': [{'lambda': Lambda('\n'.join(lines))}]})
|
||||
package['interval'][0]['then'].append({'if': {
|
||||
'condition': {'lvgl.page.is_showing': f'{pid}_page'},
|
||||
'then': [{'script.execute': f'{pid}_refresh'}]}})
|
||||
package['lvgl']['pages'].append(page_for(printer, index + 2, page_total))
|
||||
package['script'].append({'id': 'fleet_refresh', 'then': [
|
||||
{'lambda': Lambda('\n'.join(fleet_refresh_lines(snapshots)))}]})
|
||||
package['interval'][0]['then'].append({'if': {
|
||||
'condition': {'lvgl.page.is_showing': 'fleet_page'},
|
||||
'then': [{'script.execute': 'fleet_refresh'}]}})
|
||||
package['lvgl']['pages'].insert(0, overview_page(printers))
|
||||
package['lvgl']['pages'].append(color_test_page())
|
||||
package['button'] = [{'platform': 'template', 'name': 'Display Color Test',
|
||||
'entity_category': 'diagnostic',
|
||||
'on_press': [{'lvgl.page.show': 'color_test_page'}]}]
|
||||
return package
|
||||
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser(description=__doc__)
|
||||
parser.add_argument('--check', action='store_true', help='Fail if generated.yaml is stale')
|
||||
args = parser.parse_args()
|
||||
printers = yaml.safe_load((HERE / 'printers.yaml').read_text())['printers']
|
||||
package = generate(printers)
|
||||
content = ('# AUTO-GENERATED by bambu-display/generate.py. Edit printers.yaml or generate.py.\n'
|
||||
+ yaml.dump(package, Dumper=Dumper, sort_keys=False, allow_unicode=True, width=110))
|
||||
destination = HERE / 'generated.yaml'
|
||||
if args.check:
|
||||
if not destination.exists() or destination.read_text() != content:
|
||||
raise SystemExit('generated.yaml is stale; run generate.py')
|
||||
print(f'Generated configuration is current ({len(printers)} printer pages plus fleet overview).')
|
||||
else:
|
||||
destination.write_text(content)
|
||||
print(f'Generated {destination.name} with {len(printers)} printer pages plus fleet overview.')
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
@@ -0,0 +1,197 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Render the real LVGL pages with demo telemetry using ESPHome's headless SDL host."""
|
||||
|
||||
import argparse
|
||||
import json
|
||||
from pathlib import Path
|
||||
import subprocess
|
||||
import tempfile
|
||||
|
||||
from PIL import Image
|
||||
import yaml
|
||||
|
||||
from generate import Dumper, HERE, Lambda, generate
|
||||
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser(description=__doc__)
|
||||
parser.add_argument('--output', type=Path, default=HERE / 'previews')
|
||||
args = parser.parse_args()
|
||||
printers = yaml.safe_load((HERE / 'printers.yaml').read_text())['printers']
|
||||
package = generate(printers)
|
||||
demo_values = {'progress': 67, 'nozzle': 218, 'nozzle_target': 220, 'bed': 60,
|
||||
'bed_target': 60, 'time_left': 1.75, 'layer': 134, 'total_layers': 200,
|
||||
'fan': 80, 'aux_fan': 45, 'exhaust_fan': 30, 'left_nozzle': 219, 'left_target': 220,
|
||||
'right_nozzle': 37, 'right_target': 0, 'uv': 42, 'elapsed': 30,
|
||||
'total_time': 200}
|
||||
demo_text = {'status': 'running', 'task': 'Workshop organizer - plate 2',
|
||||
'stage': 'printing', 'online': 'on', 'print_error': 'off', 'hms_error': 'off',
|
||||
'hms_count': '0', 'print_error_message': '', 'print_error_code': '',
|
||||
'remaining_unit': 'h', 'nozzle_unit': '°C', 'bed_unit': '°C',
|
||||
'uv_unit': '°C', 'elapsed_unit': 'min', 'total_time_unit': 'min',
|
||||
'print_error_reason': 'ok'}
|
||||
colors = ['#60DE9AFF', '#F4C96BFF', '#74A9E8FF', '#F8F4ECFF',
|
||||
'#DF8BBFFF', '#FA936BFF', '#8E79CAFF', '#4CC9C1FF']
|
||||
for printer in printers:
|
||||
prefix = printer['id'] + '_'
|
||||
for section in ('sensor', 'text_sensor'):
|
||||
for entry in package[section]:
|
||||
if not entry['id'].startswith(prefix):
|
||||
continue
|
||||
key = entry['id'][len(prefix):]
|
||||
if section == 'sensor':
|
||||
value = str(demo_values[key])
|
||||
if key == 'time_left' and printer.get('profile', '').startswith('elegoo_'):
|
||||
value = '105'
|
||||
else:
|
||||
if key.startswith('tray_'):
|
||||
_, index, attribute = key.split('_')
|
||||
text = {'type': 'PLA', 'color': colors[int(index) % len(colors)],
|
||||
'active': 'on' if index == '0' else 'off'}[attribute]
|
||||
elif key.startswith(('hms_1_', 'hms_2_', 'hms_3_')):
|
||||
text = ''
|
||||
else:
|
||||
text = demo_text[key]
|
||||
if printer.get('profile', '').startswith('elegoo_'):
|
||||
text = {'status': 'printing', 'print_error': 'none',
|
||||
'remaining_unit': 'min'}.get(key, text)
|
||||
value = 'std::string(' + json.dumps(text, ensure_ascii=False) + ')'
|
||||
entry.clear()
|
||||
entry.update({'platform': 'template', 'id': prefix + key, 'internal': True,
|
||||
'lambda': Lambda('return ' + value + ';'), 'update_interval': '60s'})
|
||||
for script in package['script']:
|
||||
code = script['then'][0]['lambda']
|
||||
script['then'][0]['lambda'] = Lambda(code.replace(
|
||||
'id(ha_api).is_connected_with_state_subscription()', 'true'))
|
||||
|
||||
args.output.mkdir(parents=True, exist_ok=True)
|
||||
with tempfile.TemporaryDirectory(prefix='bambu-lvgl-preview-') as work:
|
||||
root = Path(work)
|
||||
# Inject real LVGL pointer samples; test bubbling through cards/buttons
|
||||
# without a GUI window or a physical touchscreen.
|
||||
touch_header = root / 'preview_touch.h'
|
||||
touch_header.write_text('''#pragma once
|
||||
#include <lvgl.h>
|
||||
namespace bambu_preview {
|
||||
inline int x = 0, y = 0;
|
||||
inline bool pressed = false;
|
||||
inline void setup() {
|
||||
auto *input = lv_indev_create();
|
||||
lv_indev_set_type(input, LV_INDEV_TYPE_POINTER);
|
||||
lv_indev_set_display(input, lv_display_get_default());
|
||||
lv_indev_set_read_cb(input, [](lv_indev_t *, lv_indev_data_t *data) {
|
||||
data->point.x = x;
|
||||
data->point.y = y;
|
||||
data->state = pressed ? LV_INDEV_STATE_PRESSED : LV_INDEV_STATE_RELEASED;
|
||||
});
|
||||
}
|
||||
}
|
||||
''')
|
||||
steps = [{'component.update': entry['id']}
|
||||
for section in ('sensor', 'text_sensor') for entry in package[section]]
|
||||
steps += [{'delay': '1s'},
|
||||
{'lvgl.page.show': 'fleet_page'},
|
||||
{'script.execute': 'fleet_refresh'}, {'delay': '300ms'},
|
||||
{'snapshot.take': {'id': 'panel_display', 'filename': 'fleet.bmp'}},
|
||||
{'lambda': Lambda(
|
||||
'if (std::string(lv_label_get_text(id(fleet_clock))) == "--:--") '
|
||||
'{ ESP_LOGE("preview", "Fleet clock render failed"); exit(7); }')}]
|
||||
for printer in printers:
|
||||
pid = printer['id']
|
||||
steps.append({'lambda': Lambda(
|
||||
f'if (std::string(lv_label_get_text(id({pid}_overview_value))) != "67" || '
|
||||
f'lv_arc_get_value(id({pid}_overview_arc)) != 67 || '
|
||||
f'std::string(lv_label_get_text(id({pid}_overview_percent))) != "%" || '
|
||||
f'std::string(lv_label_get_text(id({pid}_overview_finish))) == "--") '
|
||||
f'{{ ESP_LOGE("preview", "Fleet gauge render failed: {pid}"); exit(7); }}')})
|
||||
for printer in printers:
|
||||
steps += [{'lvgl.page.show': printer['id'] + '_page'},
|
||||
{'script.execute': printer['id'] + '_refresh'}, {'delay': '300ms'},
|
||||
{'snapshot.take': {'id': 'panel_display', 'filename': printer['id'] + '.bmp'}}]
|
||||
if printer.get('profile', '').startswith('elegoo_'):
|
||||
pid = printer['id']
|
||||
checks = {f'{pid}_status_label': 'PRINTING', f'{pid}_progress_detail': '1h 45m'}
|
||||
if printer['profile'] == 'elegoo_resin':
|
||||
checks.update({f'{pid}_uv_label': '42', f'{pid}_layer_label': '134',
|
||||
f'{pid}_elapsed_label': 'Elapsed: 0h 30m'})
|
||||
for widget, expected in checks.items():
|
||||
steps.append({'lambda': Lambda(f'if (std::string(lv_label_get_text(id({widget}))) != {json.dumps(expected)}) {{ ESP_LOGE("preview", "Elegoo telemetry render failed: {widget}"); exit(4); }}')})
|
||||
steps += [
|
||||
{'text_sensor.template.publish': {'id': f'{pid}_print_error_reason', 'state': 'filament_runout'}},
|
||||
{'script.execute': f'{pid}_refresh'},
|
||||
{'lambda': Lambda(f'if (std::string(lv_label_get_text(id({pid}_status_label))) != "PRINT ERROR" || std::string(lv_label_get_text(id({pid}_health_details))).find("Filament runout") == std::string::npos) {{ ESP_LOGE("preview", "Elegoo error render failed"); exit(5); }}')},
|
||||
{'text_sensor.template.publish': {'id': f'{pid}_online', 'state': 'off'}},
|
||||
{'script.execute': f'{pid}_refresh'},
|
||||
{'lambda': Lambda(f'if (std::string(lv_label_get_text(id({pid}_status_label))) != "PRINTER OFFLINE" || std::string(lv_label_get_text(id({pid}_progress_label))) != "--") {{ ESP_LOGE("preview", "Elegoo offline render failed"); exit(6); }}')},
|
||||
{'text_sensor.template.publish': {'id': f'{pid}_print_error_reason', 'state': 'ok'}},
|
||||
{'text_sensor.template.publish': {'id': f'{pid}_online', 'state': 'on'}},
|
||||
{'script.execute': f'{pid}_refresh'},
|
||||
]
|
||||
steps += [{'lvgl.page.show': 'color_test_page'}, {'delay': '300ms'},
|
||||
{'snapshot.take': {'id': 'panel_display', 'filename': 'color_test.bmp'}}]
|
||||
demo_printer = printers[0]['id']
|
||||
for key, value in {'hms_error': 'on', 'hms_count': '1', 'hms_1_code': 'DEMO_CODE',
|
||||
'hms_1_severity': 'common',
|
||||
'hms_1_message': 'DEMO: A printer maintenance notification. This is synthetic preview data, not an actual fault diagnosis.'}.items():
|
||||
steps.append({'text_sensor.template.publish': {'id': f'{demo_printer}_{key}', 'state': value}})
|
||||
steps += [{'lvgl.page.show': demo_printer + '_page'},
|
||||
{'script.execute': demo_printer + '_refresh'},
|
||||
{'lvgl.widget.show': demo_printer + '_health_panel'}, {'delay': '300ms'},
|
||||
{'snapshot.take': {'id': 'panel_display', 'filename': 'health_notice.bmp'}},
|
||||
{'lvgl.widget.hide': demo_printer + '_health_panel'},
|
||||
{'text_sensor.template.publish': {'id': demo_printer + '_hms_error', 'state': 'off'}},
|
||||
{'script.execute': demo_printer + '_refresh'}]
|
||||
steps += [{'lvgl.page.show': 'fleet_page'}, {'delay': '250ms'},
|
||||
{'lambda': Lambda('bambu_preview::setup();')}]
|
||||
for x, y, expect_hidden in [(690, 30, False), (700, 394, True)]:
|
||||
steps += [
|
||||
{'lambda': Lambda(f'bambu_preview::x = {x}; bambu_preview::y = {y}; bambu_preview::pressed = true;')},
|
||||
{'delay': '70ms'}, {'lambda': Lambda('bambu_preview::pressed = false;')}, {'delay': '150ms'},
|
||||
{'lambda': Lambda(f'if (lv_obj_has_flag(id({demo_printer}_health_panel), LV_OBJ_FLAG_HIDDEN) != {str(expect_hidden).lower()}) {{ ESP_LOGE("preview", "Health panel tap failed"); exit(3); }}')},
|
||||
]
|
||||
if len(printers) > 1:
|
||||
# First -> second -> first -> last -> first, then a swipe starting
|
||||
# on NEXT must advance exactly once even when released over PREV.
|
||||
# Fleet -> first printer -> fleet -> last printer -> fleet, then a
|
||||
# swipe starting on NEXT advances exactly once.
|
||||
gestures = [(650, 180, 180, printers[0]['id']),
|
||||
(180, 650, 180, 'fleet'),
|
||||
(180, 650, 180, printers[-1]['id']),
|
||||
(650, 180, 180, 'fleet'),
|
||||
(730, 60, 460, printers[0]['id'])]
|
||||
for start, end, y, expected in gestures:
|
||||
steps += [
|
||||
{'lambda': Lambda(f'bambu_preview::x = {start}; bambu_preview::y = {y}; bambu_preview::pressed = true;')},
|
||||
{'delay': '70ms'},
|
||||
{'lambda': Lambda(f'bambu_preview::x = {end};')}, {'delay': '70ms'},
|
||||
{'lambda': Lambda('bambu_preview::pressed = false;')}, {'delay': '350ms'},
|
||||
{'lambda': Lambda(f'if (!id({expected}_page).is_showing()) {{ ESP_LOGE("preview", "Swipe navigation failed"); exit(2); }}')},
|
||||
]
|
||||
steps += [{'lambda': Lambda('exit(0);')}]
|
||||
config = {'esphome': {'name': 'bambu-display-preview',
|
||||
'includes': [str(HERE / 'dashboard.h'), str(touch_header)],
|
||||
'on_boot': {'priority': -100, 'then': steps}},
|
||||
'host': {}, 'logger': {'level': 'WARN'},
|
||||
'time': [{'platform': 'host', 'id': 'ha_time', 'timezone': 'America/New_York'}],
|
||||
'display': [{'platform': 'sdl', 'id': 'panel_display', 'dimensions': '800x480',
|
||||
'headless': True, 'auto_clear_enabled': False, 'update_interval': 'never'}],
|
||||
'lvgl': {'id': 'dashboard', 'displays': ['panel_display'], 'buffer_size': '100%',
|
||||
'color_depth': 16, 'bg_color': 0x000000, 'text_color': 0xEEEEEE,
|
||||
'default_font': 'montserrat_18', **package.pop('lvgl')}, **package}
|
||||
filename = root / 'preview.yaml'
|
||||
filename.write_text(yaml.dump(config, Dumper=Dumper, sort_keys=False, allow_unicode=True))
|
||||
subprocess.run(['esphome', 'compile', str(filename)], check=True)
|
||||
binary = root / '.esphome/build/bambu-display-preview/.pioenvs/bambu-display-preview/program'
|
||||
subprocess.run([str(binary)], check=True, timeout=30, cwd=root)
|
||||
for name in ['fleet'] + [printer['id'] for printer in printers] + ['color_test', 'health_notice']:
|
||||
bmp = root / '.esphome/snapshots/bambu-display-preview' / (name + '.bmp')
|
||||
with Image.open(bmp) as image:
|
||||
image.save(args.output / (name + '.png'))
|
||||
print(f'Saved {len(printers)} demo screenshots to {args.output}')
|
||||
print('Passed LVGL pointer swipe tests, including wraparound and button gesture consumption.')
|
||||
print('Passed status-tap health panel open and Close-button tests.')
|
||||
print('Passed Elegoo telemetry, text-error and offline rendering checks.')
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
|
After Width: | Height: | Size: 34 KiB |
|
After Width: | Height: | Size: 11 KiB |
|
After Width: | Height: | Size: 39 KiB |
|
After Width: | Height: | Size: 34 KiB |
|
After Width: | Height: | Size: 23 KiB |
|
After Width: | Height: | Size: 34 KiB |
|
After Width: | Height: | Size: 34 KiB |
|
After Width: | Height: | Size: 38 KiB |
|
After Width: | Height: | Size: 27 KiB |
@@ -0,0 +1,63 @@
|
||||
# Order here is the swipe order. Add/remove entries and rerun generate.py.
|
||||
# entity_prefix is the HA entity ID between the domain and the metric suffix.
|
||||
# Optional entities: {metric: sensor.custom_entity} overrides individual mappings.
|
||||
printers:
|
||||
- id: prints_charming
|
||||
name: PrintsCharming
|
||||
subtitle: P1S / Office
|
||||
entity_prefix: p1s_01p09c470102673
|
||||
extra_fans: [aux_fan, exhaust_fan]
|
||||
filament_banks:
|
||||
- label: A
|
||||
entity_prefix: p1s_01p09c470102673_ams_1
|
||||
- label: B
|
||||
entity_prefix: p1s_01p09c470102673_ams_2
|
||||
|
||||
- id: polly_pocket
|
||||
name: Polly-Pocket
|
||||
subtitle: A1 mini / Office
|
||||
entity_prefix: a1mini_0309ca580403653
|
||||
external_spools:
|
||||
- label: EXT
|
||||
entity: sensor.a1mini_0309ca580403653_externalspool_external_spool
|
||||
|
||||
- id: ds_turd_flinger
|
||||
name: Ds-Turd-Flinger
|
||||
subtitle: A1 / Upstairs
|
||||
entity_prefix: a1_03919d540806387
|
||||
filament_banks:
|
||||
- label: A
|
||||
entity_prefix: a1_03919d540806387_ams_1
|
||||
|
||||
- id: jordyns_layer_slayer
|
||||
name: Jordyns-Layer-Slayer
|
||||
subtitle: A1 / Upstairs
|
||||
entity_prefix: jordyns_layer_slayer
|
||||
filament_banks:
|
||||
- label: A
|
||||
entity_prefix: jordyns_layer_slayer_ams
|
||||
|
||||
- id: constipation_orion
|
||||
name: Constipation-Orion
|
||||
subtitle: Dual nozzle / Office
|
||||
entity_prefix: constipation_orion
|
||||
dual_nozzle: true
|
||||
extra_fans: [exhaust_fan]
|
||||
filament_banks:
|
||||
- label: A
|
||||
entity_prefix: constipation_orion_ams_1
|
||||
- label: B
|
||||
# This unit retains its original hardware-based HA entity names.
|
||||
entity_prefix: h2c_31b8ap5a2200288_ams_2
|
||||
|
||||
- id: centauri_carbon
|
||||
name: Centauri Carbon
|
||||
subtitle: FDM / Office
|
||||
entity_prefix: centauri_carbon
|
||||
profile: elegoo_fdm
|
||||
|
||||
- id: saturn
|
||||
name: Saturn
|
||||
subtitle: Resin / Garage
|
||||
entity_prefix: saturn
|
||||
profile: elegoo_resin
|
||||
@@ -0,0 +1,175 @@
|
||||
#include "dashboard.h"
|
||||
#include <cassert>
|
||||
|
||||
int main() {
|
||||
using namespace bambu_display;
|
||||
assert(remaining(1.75f, "h") == "1h 45m");
|
||||
assert(remaining(105.0f, "min") == "1h 45m");
|
||||
assert(remaining(6300.0f, "s") == "1h 45m");
|
||||
assert(remaining(6300000.0f, "ms") == "1h 45m");
|
||||
assert(remaining(0.15f, "h") == "0h 09m");
|
||||
assert(remaining(NAN, "h") == "--");
|
||||
assert(remaining(-1, "min") == "--");
|
||||
assert(remaining(10, "") == "--");
|
||||
assert(remaining(INFINITY, "h") == "--");
|
||||
assert(number(NAN) == "--");
|
||||
assert(temperature(219, 220, "°C") == "219 / 220 °C");
|
||||
assert(filament_color("#00FF00FF") == GREEN);
|
||||
assert(filament_color("60de9a") == 0x60DE9A);
|
||||
assert(filament_color("#60DE9AZZ") == TRACK);
|
||||
assert(filament_color("unknown") == TRACK);
|
||||
assert(gauge_value(NAN, 100) == 0);
|
||||
assert(gauge_value(110, 100) == 100);
|
||||
assert(gauge_value(-10, 100) == 0);
|
||||
assert(duration_seconds(1.75f, "h") == 6300);
|
||||
assert(duration_seconds(105, "min") == 6300);
|
||||
assert(duration_seconds(6300000, "ms") == 6300);
|
||||
assert(duration_seconds(NAN, "h") == -1);
|
||||
assert(duration_seconds(1, "") == -1);
|
||||
assert(std::abs(celsius(392, "°F") - 200) < 0.01f);
|
||||
|
||||
Snapshot data;
|
||||
data.online = "on";
|
||||
data.status = "running";
|
||||
assert(!data.live());
|
||||
assert(data.status_text() == "HA OFFLINE");
|
||||
data.connected = true;
|
||||
assert(data.live());
|
||||
assert(data.status_text() == "PRINTING");
|
||||
data.online = "off";
|
||||
assert(!data.live());
|
||||
assert(data.status_text() == "PRINTER OFFLINE");
|
||||
data.online = "unavailable";
|
||||
assert(data.status_text() == "SYNCING");
|
||||
data.online = "on";
|
||||
data.status = "unknown";
|
||||
assert(!data.live());
|
||||
data.status = "pause";
|
||||
assert(data.status_text() == "PAUSED");
|
||||
assert(data.status_color() == AMBER);
|
||||
data.print_error = "on";
|
||||
assert(data.status_text() == "PRINT ERROR");
|
||||
assert(data.status_color() == RED);
|
||||
data.print_error = "off";
|
||||
data.hms_error = "True";
|
||||
assert(data.status_text() == "HEALTH NOTICE");
|
||||
assert(data.status_color() == AMBER);
|
||||
assert(data.health_details().find("have not arrived") != std::string::npos);
|
||||
data.hms_count = "1";
|
||||
data.health_notices = {{"DEMO_CODE", "A synthetic information message", "info"}};
|
||||
assert(data.status_text() == "HEALTH INFO");
|
||||
assert(data.health_details().find("DEMO_CODE") != std::string::npos);
|
||||
assert(data.alert_summary() == "A synthetic information message");
|
||||
data.health_notices.push_back({"DEMO_2", "A synthetic serious message", "serious"});
|
||||
data.hms_count = "2";
|
||||
assert(data.status_text() == "HEALTH ERROR");
|
||||
assert(data.status_color() == RED);
|
||||
assert(data.health_details().find("A synthetic serious message") != std::string::npos);
|
||||
data.health_notices.pop_back();
|
||||
data.hms_count = "5";
|
||||
assert(data.status_text() == "HEALTH NOTICE");
|
||||
assert(data.health_details().find("4 more") != std::string::npos);
|
||||
data.hms_error = "off";
|
||||
assert(data.health_details() == "No current printer health notifications.");
|
||||
data.status = "finish";
|
||||
assert(data.status_text() == "COMPLETE");
|
||||
assert(data.status_color() == GREEN);
|
||||
|
||||
// Elegoo exposes error enums rather than Bambu binary errors/HMS notices.
|
||||
data.elegoo = true;
|
||||
data.print_error = "none";
|
||||
data.print_error_reason = "ok";
|
||||
data.status = "printing";
|
||||
assert(data.live());
|
||||
assert(!data.has_print_error());
|
||||
assert(data.status_text() == "PRINTING");
|
||||
data.print_error_reason = "filament_runout";
|
||||
assert(data.status_text() == "PRINT ERROR");
|
||||
assert(data.status_color() == RED);
|
||||
assert(data.health_details().find("Filament runout") != std::string::npos);
|
||||
data.print_error = "invalid_resolution";
|
||||
assert(data.alert_summary() == "Invalid resolution / Filament runout");
|
||||
data.print_error_reason = "unavailable";
|
||||
assert(data.alert_summary() == "Invalid resolution");
|
||||
data.print_error = "unknown";
|
||||
assert(!data.has_print_error());
|
||||
data.status = "error";
|
||||
assert(data.has_print_error());
|
||||
data.status = "pausing";
|
||||
assert(data.status_color() == AMBER);
|
||||
data.status = "complete";
|
||||
assert(data.status_text() == "COMPLETE");
|
||||
data.status = "preheating_completed";
|
||||
data.stage = "printing";
|
||||
data.progress = 0;
|
||||
data.layer = 0;
|
||||
data.total_layers = 400;
|
||||
data.elapsed = 30;
|
||||
data.elapsed_unit = "min";
|
||||
data.total_time = 60;
|
||||
data.total_time_unit = "min";
|
||||
assert(std::fabs(data.completion() - 50.0f) < 0.01f);
|
||||
data.layer = 100;
|
||||
assert(std::fabs(data.completion() - 25.0f) < 0.01f);
|
||||
data.progress = 67;
|
||||
assert(std::fabs(data.completion() - 67.0f) < 0.01f);
|
||||
data.progress = 0;
|
||||
data.layer = 0;
|
||||
data.status = "idle";
|
||||
data.stage = "idle";
|
||||
assert(data.completion() == 0.0f);
|
||||
data.online = "off";
|
||||
assert(!data.live());
|
||||
assert(data.status_text() == "PRINTER OFFLINE");
|
||||
assert(data.health_details().find("unavailable") != std::string::npos);
|
||||
|
||||
data.connected = true;
|
||||
data.online = "on";
|
||||
data.elegoo = false;
|
||||
data.print_error = "off";
|
||||
data.print_error_reason = "ok";
|
||||
data.hms_error = "off";
|
||||
data.status = "running";
|
||||
data.task = "Workshop organizer";
|
||||
data.progress = 67;
|
||||
data.time_left = 1.75f;
|
||||
data.remaining_unit = "h";
|
||||
data.finish_time = "16:05";
|
||||
assert(data.overview_task() == "Workshop organizer");
|
||||
assert(data.overview_meta() == "67% 16:05 1h 45m");
|
||||
data.task = "";
|
||||
assert(data.overview_task() == "No active print");
|
||||
data.connected = false;
|
||||
assert(data.overview_task() == "Waiting for Home Assistant");
|
||||
assert(data.overview_meta() == "--");
|
||||
data.trays = {{"A1", "PLA", "#FF8800FF", "off"}, {"A2", "PETG", "00FF00", "on"}};
|
||||
assert(data.active_filament_color() == GREEN);
|
||||
data.trays[1].active = "off";
|
||||
data.trays[0].active = "on";
|
||||
assert(data.active_filament_color() == 0xFF8800);
|
||||
data.trays.clear();
|
||||
assert(data.active_filament_color() == GREEN);
|
||||
assert(filament_is_black(0x000000));
|
||||
assert(filament_is_black(filament_color("#000000FF")));
|
||||
assert(filament_is_black(0x202020));
|
||||
assert(!filament_is_black(0xFFFFFF));
|
||||
assert(!filament_is_black(GREEN));
|
||||
assert(!filament_is_black(0xFF8800));
|
||||
|
||||
FleetCounts counts;
|
||||
data.connected = true;
|
||||
data.online = "on";
|
||||
data.status = "printing";
|
||||
tally(counts, data);
|
||||
data.status = "pause";
|
||||
tally(counts, data);
|
||||
data.print_error = "on";
|
||||
tally(counts, data);
|
||||
data.print_error = "off";
|
||||
data.online = "off";
|
||||
tally(counts, data);
|
||||
data.online = "unavailable";
|
||||
data.connected = true;
|
||||
tally(counts, data);
|
||||
assert(fleet_summary(counts) == "1 attention, 1 printing, 1 paused, 1 offline, 1 syncing");
|
||||
}
|
||||
@@ -0,0 +1,201 @@
|
||||
"""Run with: python -m unittest discover -s esphome/bambu-display -v."""
|
||||
|
||||
import copy
|
||||
import unittest
|
||||
|
||||
import yaml
|
||||
|
||||
from generate import HERE, generate, validate_printers
|
||||
|
||||
|
||||
class FleetTests(unittest.TestCase):
|
||||
def setUp(self):
|
||||
self.printers = yaml.safe_load((HERE / 'printers.yaml').read_text())['printers']
|
||||
|
||||
def test_all_subscriptions_match_repository_entities(self):
|
||||
known = {line.split(' | ')[0] for line in
|
||||
(HERE.parent / 'ha-entities.txt').read_text().splitlines()}
|
||||
package = generate(self.printers)
|
||||
subscribed = {entry['entity_id'] for domain in ('sensor', 'text_sensor')
|
||||
for entry in package[domain]}
|
||||
self.assertFalse(subscribed - known)
|
||||
|
||||
def test_fleet_can_expand_beyond_configured_printers(self):
|
||||
expected_count = len(self.printers) + 2
|
||||
for index in (8, 9):
|
||||
extra = copy.deepcopy(self.printers[0])
|
||||
extra.update(id=f'printer_{index}', entity_prefix=f'new_printer_{index}')
|
||||
self.printers.append(extra)
|
||||
package = generate(self.printers)
|
||||
self.assertEqual(len([page for page in package['lvgl']['pages'] if not page.get('skip')]), expected_count + 1)
|
||||
self.assertEqual(len(package['script']), expected_count + 1)
|
||||
subscriptions = {entry['id']: entry['entity_id'] for entry in package['sensor']}
|
||||
self.assertEqual(subscriptions['printer_9_progress'], 'sensor.new_printer_9_print_progress')
|
||||
for page in package['lvgl']['pages']:
|
||||
self.assertIn('lvgl.page.next', page['on_swipe_left'][-1])
|
||||
self.assertIn('lvgl.page.previous', page['on_swipe_right'][-1])
|
||||
|
||||
def test_elegoo_profiles_use_available_metrics_and_domains(self):
|
||||
package = generate(self.printers)
|
||||
subscriptions = {entry['id']: entry for domain in ('sensor', 'text_sensor')
|
||||
for entry in package[domain]}
|
||||
self.assertEqual(subscriptions['centauri_carbon_nozzle_target']['entity_id'],
|
||||
'number.centauri_carbon_target_nozzle_temp')
|
||||
self.assertEqual(subscriptions['centauri_carbon_fan']['entity_id'],
|
||||
'sensor.centauri_carbon_model_fan_speed')
|
||||
for pid in ('centauri_carbon', 'saturn'):
|
||||
self.assertEqual(subscriptions[f'{pid}_online']['entity_id'], f'binary_sensor.{pid}_sdcp_status')
|
||||
self.assertEqual(subscriptions[f'{pid}_remaining_unit']['entity_id'], f'sensor.{pid}_remaining_print_time')
|
||||
self.assertEqual(subscriptions[f'{pid}_print_error']['entity_id'], f'sensor.{pid}_print_error')
|
||||
self.assertEqual(subscriptions[f'{pid}_print_error_reason']['entity_id'], f'sensor.{pid}_error_status_reason')
|
||||
self.assertNotIn(f'{pid}_hms_error', subscriptions)
|
||||
self.assertNotIn('saturn_nozzle', subscriptions)
|
||||
self.assertNotIn('saturn_fan', subscriptions)
|
||||
self.assertEqual(subscriptions['saturn_uv_unit']['entity_id'], 'sensor.saturn_uv_led_temp')
|
||||
self.assertEqual(subscriptions['saturn_elapsed_unit']['entity_id'], 'sensor.saturn_current_print_time')
|
||||
|
||||
def test_resin_page_contains_only_resin_gauges(self):
|
||||
package = generate(self.printers)
|
||||
page = next(page for page in package['lvgl']['pages'] if page['id'] == 'saturn_page')
|
||||
arcs = {widget['arc']['id'] for widget in page['widgets'] if 'arc' in widget}
|
||||
self.assertEqual(arcs, {'saturn_progress_arc', 'saturn_uv_arc', 'saturn_layer_arc'})
|
||||
|
||||
def test_number_overrides_are_supported(self):
|
||||
self.printers[-2]['entities'] = {'nozzle_target': 'number.custom_target'}
|
||||
package = generate(self.printers)
|
||||
entry = next(row for row in package['sensor'] if row['id'] == 'centauri_carbon_nozzle_target')
|
||||
self.assertEqual(entry['entity_id'], 'number.custom_target')
|
||||
|
||||
def test_reject_unknown_profiles(self):
|
||||
self.printers[0]['profile'] = 'unsupported'
|
||||
with self.assertRaisesRegex(ValueError, 'unsupported profile'):
|
||||
generate(self.printers)
|
||||
|
||||
def test_overrides_apply_to_values_and_unit_subscriptions(self):
|
||||
self.printers[0]['entities'] = {'time_left': 'sensor.custom_remaining'}
|
||||
package = generate(self.printers)
|
||||
for domain, key in [('sensor', 'time_left'), ('text_sensor', 'remaining_unit')]:
|
||||
entry = next(row for row in package[domain] if row['id'] == f'prints_charming_{key}')
|
||||
self.assertEqual(entry['entity_id'], 'sensor.custom_remaining')
|
||||
|
||||
def test_orion_has_both_ams_units_with_their_actual_entity_prefixes(self):
|
||||
package = generate(self.printers)
|
||||
types = [entry['entity_id'] for entry in package['text_sensor']
|
||||
if entry['id'].startswith('constipation_orion_tray_') and entry.get('attribute') == 'type']
|
||||
self.assertEqual(len(types), 8)
|
||||
self.assertIn('sensor.constipation_orion_ams_1_tray_1', types)
|
||||
self.assertIn('sensor.h2c_31b8ap5a2200288_ams_2_tray_4', types)
|
||||
|
||||
def test_health_messages_import_numbered_ha_attributes(self):
|
||||
package = generate(self.printers)
|
||||
subscriptions = {entry['id']: entry for entry in package['text_sensor']}
|
||||
message = subscriptions['prints_charming_hms_1_message']
|
||||
self.assertEqual(message['entity_id'], 'binary_sensor.p1s_01p09c470102673_hms_errors')
|
||||
self.assertEqual(message['attribute'], '1-Error')
|
||||
self.assertEqual(subscriptions['prints_charming_hms_3_severity']['attribute'], '3-Severity')
|
||||
|
||||
def test_reject_empty_duplicate_and_malformed_printer_ids(self):
|
||||
for invalid in ([], self.printers + [self.printers[0]],
|
||||
[{**self.printers[0], 'id': 'invalid-id'}]):
|
||||
with self.subTest(invalid=invalid), self.assertRaises(ValueError):
|
||||
validate_printers(invalid)
|
||||
|
||||
def test_reject_overflowing_filament_strip(self):
|
||||
self.printers[0]['external_spools'] = [{'label': 'EXT', 'entity': 'sensor.external_spool'}]
|
||||
with self.assertRaisesRegex(ValueError, 'eight slots'):
|
||||
generate(self.printers)
|
||||
|
||||
def test_progress_and_dual_nozzle_widgets_fit_the_panel(self):
|
||||
package = generate(self.printers)
|
||||
for page in package['lvgl']['pages']:
|
||||
for widget in page['widgets']:
|
||||
props = next(iter(widget.values()))
|
||||
self.assertGreaterEqual(props.get('x', 0), 0)
|
||||
self.assertGreaterEqual(props.get('y', 0), 0)
|
||||
self.assertLessEqual(props.get('x', 0) + props.get('width', 0), 800)
|
||||
self.assertLessEqual(props.get('y', 0) + props.get('height', 0), 480)
|
||||
|
||||
def test_rgb565_primaries_reach_the_correct_panel_bits(self):
|
||||
# mipi_rgb emits named RGB data pins with bytes exchanged, then copies
|
||||
# LVGL RGB565 bytes straight into the ESP32 little-endian framebuffer.
|
||||
# Exercise red, green, blue and combined primaries through that path.
|
||||
class Loader(yaml.SafeLoader):
|
||||
pass
|
||||
for tag in ('!secret', '!include'):
|
||||
Loader.add_constructor(tag, lambda loader, node: loader.construct_scalar(node))
|
||||
config = yaml.load((HERE / 'board.yaml').read_text(), Loader=Loader)
|
||||
display = config['display'][0]
|
||||
pins = display['data_pins']
|
||||
natural = pins['blue'] + pins['green'] + pins['red']
|
||||
driver = natural[8:] + natural[:8]
|
||||
order = {'big_endian': 'big', 'little_endian': 'little'}[config['lvgl']['byte_order']]
|
||||
for color in (0xF800, 0x07E0, 0x001F, 0xFFFF, 0x07FF, 0xF81F):
|
||||
memory_word = int.from_bytes(color.to_bytes(2, order), 'little')
|
||||
panel_word = sum(((memory_word >> i) & 1) << natural.index(pin)
|
||||
for i, pin in enumerate(driver))
|
||||
self.assertEqual(panel_word, color, f'RGB565 color {color:#06x} is wired incorrectly')
|
||||
|
||||
def test_both_boards_share_one_hardware_package(self):
|
||||
class Loader(yaml.SafeLoader):
|
||||
pass
|
||||
Loader.add_constructor('!include', lambda loader, node: loader.construct_scalar(node))
|
||||
names = set()
|
||||
for filename in ('bambu-printer-display.yaml', 'bambu-printer-display-2.yaml'):
|
||||
config = yaml.load((HERE.parent / filename).read_text(), Loader=Loader)
|
||||
names.add(config['substitutions']['device_name'])
|
||||
self.assertEqual(config['packages']['board'], 'bambu-display/board.yaml')
|
||||
self.assertEqual(config['packages']['fleet'], 'bambu-display/generated.yaml')
|
||||
self.assertEqual(len(names), 2)
|
||||
|
||||
def test_fleet_overview_lists_every_printer(self):
|
||||
package = generate(self.printers)
|
||||
page = package['lvgl']['pages'][0]
|
||||
self.assertEqual(page['id'], 'fleet_page')
|
||||
self.assertFalse(page.get('skip'))
|
||||
arcs = [widget['arc'] for widget in page['widgets']
|
||||
if widget.get('arc', {}).get('id', '').endswith('_overview_arc')]
|
||||
self.assertEqual([arc['id'][:-len('_overview_arc')] for arc in arcs],
|
||||
[printer['id'] for printer in self.printers])
|
||||
backings = [widget['arc'] for widget in page['widgets']
|
||||
if widget.get('arc', {}).get('id', '').endswith('_overview_ring')]
|
||||
self.assertEqual([arc['id'][:-len('_overview_ring')] for arc in backings],
|
||||
[printer['id'] for printer in self.printers])
|
||||
for backing, arc in zip(backings, arcs):
|
||||
self.assertTrue(backing['hidden'])
|
||||
self.assertEqual(backing['arc_color'], 0x6E767E)
|
||||
self.assertGreater(backing['width'], arc['width'])
|
||||
self.assertGreaterEqual(backing['x'], 0)
|
||||
self.assertGreaterEqual(backing['y'], 0)
|
||||
self.assertLessEqual(backing['x'] + backing['width'], 800)
|
||||
self.assertLessEqual(backing['y'] + backing['height'], 480)
|
||||
for arc, printer in zip(arcs, self.printers):
|
||||
self.assertEqual(arc['on_click'], [{'lvgl.page.show': printer['id'] + '_page'}])
|
||||
self.assertGreaterEqual(arc['width'], 120)
|
||||
self.assertLessEqual(arc['width'], 154)
|
||||
self.assertEqual(arc['value'], 0)
|
||||
self.assertEqual(arc['arc_color'], 0x19232B)
|
||||
self.assertEqual(arc['start_angle'], 135)
|
||||
self.assertLessEqual(arc['x'] + arc['width'], 800)
|
||||
self.assertLessEqual(arc['y'] + arc['height'], 480)
|
||||
code = next(script['then'][0]['lambda'] for script in package['script'] if script['id'] == 'fleet_refresh')
|
||||
for printer in self.printers:
|
||||
self.assertIn(f'id({printer["id"]}_overview_arc)', code)
|
||||
self.assertIn(f'id({printer["id"]}_overview_finish)', code)
|
||||
self.assertIn(f'id({printer["id"]}_overview_ring)', code)
|
||||
titles = [next(iter(widget.values())).get('text') for widget in page['widgets']]
|
||||
self.assertIn('Print Status Overview', titles)
|
||||
self.assertNotIn('fleet_summary', {next(iter(widget.values())).get('id') for widget in page['widgets']})
|
||||
shown = [step['if']['condition']['lvgl.page.is_showing'] for step in package['interval'][0]['then']]
|
||||
self.assertIn('fleet_page', shown)
|
||||
charming = next(item for item in package['lvgl']['pages'] if item['id'] == 'prints_charming_page')
|
||||
numbers = [next(iter(widget.values())).get('text') for widget in charming['widgets']]
|
||||
self.assertIn(f'2 / {len(self.printers) + 1}', numbers)
|
||||
|
||||
def test_diagnostic_color_page_is_skipped_during_normal_navigation(self):
|
||||
package = generate(self.printers)
|
||||
page = next(page for page in package['lvgl']['pages'] if page['id'] == 'color_test_page')
|
||||
self.assertTrue(page['skip'])
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main()
|
||||
@@ -0,0 +1,10 @@
|
||||
# Second Sunton ESP32-8048S043C fleet display. Same pages and hardware as
|
||||
# bambu-printer-display.yaml, with its own ESPHome name so both boards can
|
||||
# join Home Assistant. Edit bambu-display/printers.yaml, then run generate.py.
|
||||
substitutions:
|
||||
device_name: bambu-printer-display-2
|
||||
friendly_name: Bambu Printer Display 2
|
||||
|
||||
packages:
|
||||
board: !include bambu-display/board.yaml
|
||||
fleet: !include bambu-display/generated.yaml
|
||||
@@ -0,0 +1,10 @@
|
||||
# Sunton ESP32-8048S043C: 4.3" 800x480 RGB LCD + GT911 capacitive touch.
|
||||
# Edit bambu-display/printers.yaml, then run its generate.py to change the fleet.
|
||||
# The second board is bambu-printer-display-2.yaml; both include bambu-display/board.yaml.
|
||||
substitutions:
|
||||
device_name: bambu-printer-display
|
||||
friendly_name: Bambu Printer Display
|
||||
|
||||
packages:
|
||||
board: !include bambu-display/board.yaml
|
||||
fleet: !include bambu-display/generated.yaml
|
||||
@@ -1,275 +0,0 @@
|
||||
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
|
||||
|
||||
esp32_camera:
|
||||
name: "${friendly_name}"
|
||||
external_clock:
|
||||
pin: GPIO5
|
||||
# 10MHz (half the usual 20MHz) slows the sensor's line rate, so each of the
|
||||
# 1200 exposure lines below collects roughly twice the light. Costs
|
||||
# framerate, which does not matter for chamber monitoring.
|
||||
frequency: 10MHz
|
||||
i2c_pins:
|
||||
sda: GPIO8
|
||||
scl: GPIO9
|
||||
data_pins: [GPIO16, GPIO18, GPIO21, GPIO17, GPIO14, GPIO7, GPIO6, GPIO4]
|
||||
vsync_pin: GPIO1
|
||||
href_pin: GPIO2
|
||||
pixel_clock_pin: GPIO15
|
||||
|
||||
# Resolution & quality (ESP32-S3 with Octal PSRAM handles XGA easily)
|
||||
resolution: 1024X768
|
||||
jpeg_quality: 10
|
||||
max_framerate: 20 fps
|
||||
idle_framerate: 0.2 fps
|
||||
frame_buffer_count: 2
|
||||
|
||||
# Orientation
|
||||
vertical_flip: true
|
||||
horizontal_mirror: false
|
||||
|
||||
# Image tuning for chamber monitoring through tinted door.
|
||||
# Low contrast + high brightness lifts the shadows; the LED strips are
|
||||
# allowed to blow out since the chamber is what matters.
|
||||
brightness: 2
|
||||
contrast: -2
|
||||
saturation: 0
|
||||
|
||||
# Exposure & gain are MANUAL on purpose. The two LED strips in frame kept the
|
||||
# auto-exposure meter satisfied, so AEC never lengthened the exposure and the
|
||||
# tinted-door scene stayed black regardless of gain ceiling or AE level.
|
||||
# Pinning both to max forces a bright chamber; the strips clip out entirely.
|
||||
aec_mode: manual
|
||||
aec_value: 1200 # max integration time (0-1200 lines)
|
||||
aec2: true
|
||||
ae_level: 2 # only consulted if AEC is flipped back to auto
|
||||
agc_mode: manual
|
||||
agc_value: 30 # max sensor gain (0-30)
|
||||
agc_gain_ceiling: 128X # only consulted if AGC is flipped back to auto
|
||||
wb_mode: auto
|
||||
|
||||
# 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
|
||||
|
||||
# Flip exposure/gain back to automatic for A/B comparison without a reflash.
|
||||
# Off (the default) = manual exposure per the esp32_camera block above.
|
||||
- platform: template
|
||||
name: "${friendly_name} Auto Exposure"
|
||||
optimistic: true
|
||||
restore_mode: ALWAYS_OFF
|
||||
turn_on_action:
|
||||
- lambda: |-
|
||||
sensor_t *s = esp_camera_sensor_get();
|
||||
if (!s) return;
|
||||
s->set_exposure_ctrl(s, 1);
|
||||
s->set_gain_ctrl(s, 1);
|
||||
turn_off_action:
|
||||
- lambda: |-
|
||||
sensor_t *s = esp_camera_sensor_get();
|
||||
if (!s) return;
|
||||
s->set_exposure_ctrl(s, 0);
|
||||
s->set_gain_ctrl(s, 0);
|
||||
|
||||
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();'
|
||||
|
||||
# Dynamic controls exposed to Home Assistant for real-time camera tuning
|
||||
number:
|
||||
# The two that actually drive image brightness in manual mode.
|
||||
- platform: template
|
||||
name: "${friendly_name} Exposure"
|
||||
min_value: 0
|
||||
max_value: 1200
|
||||
step: 25
|
||||
initial_value: 1200
|
||||
optimistic: true
|
||||
set_action:
|
||||
- lambda: |-
|
||||
sensor_t *s = esp_camera_sensor_get();
|
||||
if (s) s->set_aec_value(s, (int)x);
|
||||
|
||||
- platform: template
|
||||
name: "${friendly_name} Gain"
|
||||
min_value: 0
|
||||
max_value: 30
|
||||
step: 1
|
||||
initial_value: 30
|
||||
optimistic: true
|
||||
set_action:
|
||||
- lambda: |-
|
||||
sensor_t *s = esp_camera_sensor_get();
|
||||
if (s) s->set_agc_gain(s, (int)x);
|
||||
|
||||
- platform: template
|
||||
name: "${friendly_name} Brightness"
|
||||
min_value: -2
|
||||
max_value: 2
|
||||
step: 1
|
||||
initial_value: 2
|
||||
optimistic: true
|
||||
set_action:
|
||||
- lambda: |-
|
||||
sensor_t *s = esp_camera_sensor_get();
|
||||
if (s) s->set_brightness(s, (int)x);
|
||||
|
||||
- platform: template
|
||||
name: "${friendly_name} Contrast"
|
||||
min_value: -2
|
||||
max_value: 2
|
||||
step: 1
|
||||
initial_value: -2
|
||||
optimistic: true
|
||||
set_action:
|
||||
- lambda: |-
|
||||
sensor_t *s = esp_camera_sensor_get();
|
||||
if (s) s->set_contrast(s, (int)x);
|
||||
|
||||
- platform: template
|
||||
name: "${friendly_name} AE Level"
|
||||
min_value: -2
|
||||
max_value: 2
|
||||
step: 1
|
||||
initial_value: 2
|
||||
optimistic: true
|
||||
set_action:
|
||||
- lambda: |-
|
||||
sensor_t *s = esp_camera_sensor_get();
|
||||
if (s) s->set_ae_level(s, (int)x);
|
||||
|
||||
select:
|
||||
# Caps how far AGC may amplify. Higher = brighter but noisier; back this off
|
||||
# if the chamber image gets too grainy at 128X.
|
||||
- platform: template
|
||||
name: "${friendly_name} Gain Ceiling"
|
||||
optimistic: true
|
||||
initial_option: "128X"
|
||||
options:
|
||||
- "2X"
|
||||
- "4X"
|
||||
- "8X"
|
||||
- "16X"
|
||||
- "32X"
|
||||
- "64X"
|
||||
- "128X"
|
||||
set_action:
|
||||
- lambda: |-
|
||||
sensor_t *s = esp_camera_sensor_get();
|
||||
if (!s) return;
|
||||
int idx = 6;
|
||||
if (x == "2X") idx = 0;
|
||||
else if (x == "4X") idx = 1;
|
||||
else if (x == "8X") idx = 2;
|
||||
else if (x == "16X") idx = 3;
|
||||
else if (x == "32X") idx = 4;
|
||||
else if (x == "64X") idx = 5;
|
||||
s->set_gainceiling(s, (gainceiling_t)idx);
|
||||
|
||||
@@ -1,140 +0,0 @@
|
||||
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 @@
|
||||
{}
|
||||
@@ -0,0 +1,32 @@
|
||||
# Keep this file safe: it contains the unique keys for this screen.
|
||||
# Wi-Fi comes from the secrets.yaml of ESPHome Device Builder.
|
||||
substitutions:
|
||||
DEVICE_NAME: "family-room-control"
|
||||
DEVICE_FRIENDLY_NAME: "Family Room Control"
|
||||
LANGUAGE: "en"
|
||||
DISPLAY_MODEL: "ST7789V"
|
||||
GITHUB_REF: "screens-v0.4.93"
|
||||
|
||||
esphome:
|
||||
name: "family-room-control"
|
||||
friendly_name: "Family Room Control"
|
||||
|
||||
packages:
|
||||
display:
|
||||
url: https://github.com/MaxGramser/homeassistant_espscreen
|
||||
ref: screens-v0.4.93
|
||||
files: [packages/cyd.yaml]
|
||||
refresh: 0s
|
||||
local_overrides: !include family-room-control.local.yaml
|
||||
|
||||
api:
|
||||
encryption:
|
||||
key: "T0Qgy1qdHCXD0IiO72V0AqUb1WL3F9pOQ/zg2RlG9sg="
|
||||
ota:
|
||||
- platform: esphome
|
||||
password: "zPMI1ZoPpQIu8yulgUiUTi8pPT8UClg9"
|
||||
allow_partition_access: true
|
||||
wifi:
|
||||
ssid: !secret wifi_ssid
|
||||
password: !secret wifi_password
|
||||
power_save_mode: none
|
||||
@@ -1,127 +0,0 @@
|
||||
# Lights-only first version for the Family Room remote.
|
||||
# Entity IDs came from the project's HA snapshot; verify them in live HA.
|
||||
|
||||
substitutions:
|
||||
device_name: family-room-remote
|
||||
friendly_name: Family Room Remote
|
||||
|
||||
esphome:
|
||||
name: ${device_name}
|
||||
friendly_name: ${friendly_name}
|
||||
|
||||
packages:
|
||||
hardware: !include panel-remote-common.yaml
|
||||
|
||||
binary_sensor:
|
||||
- platform: homeassistant
|
||||
id: standing_lamp_state
|
||||
entity_id: light.living_room_lamp_1
|
||||
publish_initial_state: true
|
||||
on_state:
|
||||
- lvgl.widget.update:
|
||||
id: standing_lamp_button
|
||||
state:
|
||||
checked: !lambda return x;
|
||||
- platform: homeassistant
|
||||
id: small_lamp_state
|
||||
entity_id: light.small_family_room_lamp
|
||||
publish_initial_state: true
|
||||
on_state:
|
||||
- lvgl.widget.update:
|
||||
id: small_lamp_button
|
||||
state:
|
||||
checked: !lambda return x;
|
||||
- platform: homeassistant
|
||||
id: tv_stand_state
|
||||
entity_id: light.family_room_tv_stand
|
||||
publish_initial_state: true
|
||||
on_state:
|
||||
- lvgl.widget.update:
|
||||
id: tv_stand_button
|
||||
state:
|
||||
checked: !lambda return x;
|
||||
|
||||
lvgl:
|
||||
on_idle:
|
||||
- timeout: 45s
|
||||
then:
|
||||
- light.turn_on:
|
||||
id: panel_backlight
|
||||
brightness: 15%
|
||||
pages:
|
||||
- id: room_page
|
||||
widgets:
|
||||
- label:
|
||||
x: 12
|
||||
y: 8
|
||||
text: Family Room
|
||||
- button:
|
||||
id: standing_lamp_button
|
||||
x: 10
|
||||
y: 45
|
||||
width: 145
|
||||
height: 75
|
||||
checkable: true
|
||||
widgets:
|
||||
- label:
|
||||
align: CENTER
|
||||
text: Standing Lamp
|
||||
on_click:
|
||||
- homeassistant.action:
|
||||
action: light.toggle
|
||||
data:
|
||||
entity_id: light.living_room_lamp_1
|
||||
- button:
|
||||
id: small_lamp_button
|
||||
x: 165
|
||||
y: 45
|
||||
width: 145
|
||||
height: 75
|
||||
checkable: true
|
||||
widgets:
|
||||
- label:
|
||||
align: CENTER
|
||||
text: Small Lamp
|
||||
on_click:
|
||||
- homeassistant.action:
|
||||
action: light.toggle
|
||||
data:
|
||||
entity_id: light.small_family_room_lamp
|
||||
- button:
|
||||
id: tv_stand_button
|
||||
x: 10
|
||||
y: 135
|
||||
width: 145
|
||||
height: 75
|
||||
checkable: true
|
||||
widgets:
|
||||
- label:
|
||||
align: CENTER
|
||||
text: TV Stand
|
||||
on_click:
|
||||
- homeassistant.action:
|
||||
action: light.toggle
|
||||
data:
|
||||
entity_id: light.family_room_tv_stand
|
||||
- button:
|
||||
x: 165
|
||||
y: 135
|
||||
width: 145
|
||||
height: 75
|
||||
widgets:
|
||||
- label:
|
||||
align: CENTER
|
||||
text: All Off
|
||||
on_click:
|
||||
- homeassistant.action:
|
||||
action: light.turn_off
|
||||
data:
|
||||
entity_id: light.living_room_lamp_1
|
||||
- homeassistant.action:
|
||||
action: light.turn_off
|
||||
data:
|
||||
entity_id: light.small_family_room_lamp
|
||||
- homeassistant.action:
|
||||
action: light.turn_off
|
||||
data:
|
||||
entity_id: light.family_room_tv_stand
|
||||
@@ -76,6 +76,17 @@ light:
|
||||
|
||||
# Effects - you can enable these for more features!
|
||||
effects:
|
||||
- strobe:
|
||||
name: "Red Flash"
|
||||
colors:
|
||||
- state: true
|
||||
brightness: 100%
|
||||
red: 100%
|
||||
green: 0%
|
||||
blue: 0%
|
||||
duration: 500ms
|
||||
- state: false
|
||||
duration: 250ms
|
||||
# Basic effects
|
||||
- random:
|
||||
name: "Random"
|
||||
|
||||
@@ -1,198 +0,0 @@
|
||||
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();'
|
||||
@@ -1,98 +0,0 @@
|
||||
# Lights-only first version for the Living Room remote.
|
||||
# Entity IDs came from the project's HA snapshot; verify them in live HA.
|
||||
|
||||
substitutions:
|
||||
device_name: living-room-remote
|
||||
friendly_name: Living Room Remote
|
||||
|
||||
esphome:
|
||||
name: ${device_name}
|
||||
friendly_name: ${friendly_name}
|
||||
|
||||
packages:
|
||||
hardware: !include panel-remote-common.yaml
|
||||
|
||||
binary_sensor:
|
||||
- platform: homeassistant
|
||||
id: ceiling_light_state
|
||||
entity_id: light.living_room_light_2
|
||||
publish_initial_state: true
|
||||
on_state:
|
||||
- lvgl.widget.update:
|
||||
id: ceiling_light_button
|
||||
state:
|
||||
checked: !lambda return x;
|
||||
- platform: homeassistant
|
||||
id: small_lamp_state
|
||||
entity_id: light.small_living_room_lamp
|
||||
publish_initial_state: true
|
||||
on_state:
|
||||
- lvgl.widget.update:
|
||||
id: small_lamp_button
|
||||
state:
|
||||
checked: !lambda return x;
|
||||
|
||||
lvgl:
|
||||
on_idle:
|
||||
- timeout: 45s
|
||||
then:
|
||||
- light.turn_on:
|
||||
id: panel_backlight
|
||||
brightness: 15%
|
||||
pages:
|
||||
- id: room_page
|
||||
widgets:
|
||||
- label:
|
||||
x: 12
|
||||
y: 8
|
||||
text: Living Room
|
||||
- button:
|
||||
id: ceiling_light_button
|
||||
x: 10
|
||||
y: 45
|
||||
width: 145
|
||||
height: 75
|
||||
checkable: true
|
||||
widgets:
|
||||
- label:
|
||||
align: CENTER
|
||||
text: Main Light
|
||||
on_click:
|
||||
- homeassistant.action:
|
||||
action: light.toggle
|
||||
data:
|
||||
entity_id: light.living_room_light_2
|
||||
- button:
|
||||
id: small_lamp_button
|
||||
x: 165
|
||||
y: 45
|
||||
width: 145
|
||||
height: 75
|
||||
checkable: true
|
||||
widgets:
|
||||
- label:
|
||||
align: CENTER
|
||||
text: Small Lamp
|
||||
on_click:
|
||||
- homeassistant.action:
|
||||
action: light.toggle
|
||||
data:
|
||||
entity_id: light.small_living_room_lamp
|
||||
- button:
|
||||
x: 10
|
||||
y: 135
|
||||
width: 300
|
||||
height: 75
|
||||
widgets:
|
||||
- label:
|
||||
align: CENTER
|
||||
text: All Lights Off
|
||||
on_click:
|
||||
- homeassistant.action:
|
||||
action: light.turn_off
|
||||
data:
|
||||
entity_id: light.living_room_light_2
|
||||
- homeassistant.action:
|
||||
action: light.turn_off
|
||||
data:
|
||||
entity_id: light.small_living_room_lamp
|
||||
@@ -0,0 +1 @@
|
||||
{}
|
||||
@@ -0,0 +1,32 @@
|
||||
# Keep this file safe: it contains the unique keys for this screen.
|
||||
# Wi-Fi comes from the secrets.yaml of ESPHome Device Builder.
|
||||
substitutions:
|
||||
DEVICE_NAME: "living-room"
|
||||
DEVICE_FRIENDLY_NAME: "Living Room"
|
||||
LANGUAGE: "en"
|
||||
DISPLAY_MODEL: "ST7789V"
|
||||
GITHUB_REF: "screens-v0.4.93"
|
||||
|
||||
esphome:
|
||||
name: "living-room"
|
||||
friendly_name: "Living Room"
|
||||
|
||||
packages:
|
||||
display:
|
||||
url: https://github.com/MaxGramser/homeassistant_espscreen
|
||||
ref: screens-v0.4.93
|
||||
files: [packages/cyd.yaml]
|
||||
refresh: 0s
|
||||
local_overrides: !include living-room.local.yaml
|
||||
|
||||
api:
|
||||
encryption:
|
||||
key: "UPxExwkTfV//nSJtp+kcRkf11KFVsW4BSV0b/Nyyus8="
|
||||
ota:
|
||||
- platform: esphome
|
||||
password: "ODRQoZGsPXZ_A-js0Sn3S9MNYPqEM8on"
|
||||
allow_partition_access: true
|
||||
wifi:
|
||||
ssid: !secret wifi_ssid
|
||||
password: !secret wifi_password
|
||||
power_save_mode: none
|
||||
@@ -24,7 +24,7 @@ api:
|
||||
|
||||
ota:
|
||||
- platform: esphome
|
||||
password: !secret ota_password
|
||||
encryption:
|
||||
|
||||
wifi:
|
||||
ssid: !secret wifi_iot_ssid
|
||||
@@ -87,6 +87,7 @@ touchscreen:
|
||||
- platform: ft5x06
|
||||
id: panel_touch
|
||||
i2c_id: touch_bus
|
||||
address: 0x38
|
||||
display: panel_display
|
||||
interrupt_pin: GPIO48
|
||||
on_touch:
|
||||
|
||||
@@ -18,7 +18,7 @@ api:
|
||||
|
||||
ota:
|
||||
- platform: esphome
|
||||
password: !secret ota_password
|
||||
encryption:
|
||||
|
||||
wifi:
|
||||
ssid: !secret wifi_iot_ssid
|
||||
@@ -44,7 +44,6 @@ display:
|
||||
dc_pin: GPIO18
|
||||
reset_pin: GPIO3
|
||||
dimensions: 240x320
|
||||
rotation: 90
|
||||
data_rate: 8MHz
|
||||
invert_colors: true
|
||||
auto_clear_enabled: false
|
||||
@@ -73,6 +72,7 @@ touchscreen:
|
||||
- platform: ft5x06
|
||||
id: panel_touch
|
||||
i2c_id: touch_bus
|
||||
address: 0x38
|
||||
display: panel_display
|
||||
interrupt_pin: GPIO48
|
||||
on_touch:
|
||||
|
||||
@@ -0,0 +1,363 @@
|
||||
substitutions:
|
||||
device_name: resin-curing-timer
|
||||
outlet_entity: switch.resin_uv_light # Home Assistant UV light outlet.
|
||||
display_model: ESP32-2432S028-7789 # 2.8" CYD with ST7789V (USB-C + micro-USB).
|
||||
|
||||
esphome:
|
||||
name: ${device_name}
|
||||
friendly_name: Resin Curing Timer
|
||||
min_version: 2026.9.0
|
||||
|
||||
esp32:
|
||||
board: esp32dev
|
||||
framework:
|
||||
type: esp-idf
|
||||
|
||||
logger:
|
||||
|
||||
api:
|
||||
id: ha_api
|
||||
encryption:
|
||||
key: !secret api_encryption_key
|
||||
|
||||
ota:
|
||||
- platform: esphome
|
||||
encryption:
|
||||
key: !secret api_encryption_key
|
||||
|
||||
wifi:
|
||||
ssid: !secret wifi_iot_ssid
|
||||
password: !secret wifi_password
|
||||
|
||||
spi:
|
||||
- id: tft_spi
|
||||
clk_pin: GPIO14
|
||||
mosi_pin: GPIO13
|
||||
- id: touch_spi
|
||||
clk_pin: GPIO25
|
||||
mosi_pin: GPIO32
|
||||
miso_pin: GPIO39
|
||||
|
||||
output:
|
||||
- platform: ledc
|
||||
pin: GPIO21
|
||||
id: backlight_output
|
||||
|
||||
light:
|
||||
- platform: monochromatic
|
||||
output: backlight_output
|
||||
name: Backlight
|
||||
id: backlight
|
||||
restore_mode: ALWAYS_ON
|
||||
default_transition_length: 0s
|
||||
|
||||
display:
|
||||
- platform: mipi_spi
|
||||
id: tft
|
||||
model: ${display_model}
|
||||
spi_id: tft_spi
|
||||
dimensions: 240x320 # Native portrait size; LVGL rotates to 320x240 landscape.
|
||||
auto_clear_enabled: false
|
||||
update_interval: never
|
||||
|
||||
touchscreen:
|
||||
- platform: xpt2046
|
||||
id: touch
|
||||
spi_id: touch_spi
|
||||
display: tft
|
||||
cs_pin: GPIO33
|
||||
interrupt_pin: GPIO36
|
||||
threshold: 400
|
||||
# Starting calibration only: verify all four corners on your board.
|
||||
calibration:
|
||||
x_min: 200
|
||||
x_max: 3900
|
||||
y_min: 200
|
||||
y_max: 3900
|
||||
transform:
|
||||
swap_xy: true
|
||||
mirror_x: true
|
||||
on_touch:
|
||||
- logger.log:
|
||||
format: "Touch x=%d y=%d raw_x=%d raw_y=%d"
|
||||
args: [touch.x, touch.y, touch.x_raw, touch.y_raw]
|
||||
|
||||
time:
|
||||
- platform: homeassistant
|
||||
id: ha_time
|
||||
|
||||
text_sensor:
|
||||
- platform: homeassistant
|
||||
id: timer_state
|
||||
entity_id: timer.resin_curing
|
||||
internal: true
|
||||
- platform: homeassistant
|
||||
id: outlet_state
|
||||
entity_id: ${outlet_entity}
|
||||
internal: true
|
||||
- platform: homeassistant
|
||||
id: timer_duration
|
||||
entity_id: timer.resin_curing
|
||||
attribute: duration
|
||||
internal: true
|
||||
# Keep epoch seconds as text: ESPHome numeric sensors use float32, which
|
||||
# loses minute-level precision for contemporary Unix timestamps.
|
||||
- platform: homeassistant
|
||||
id: deadline
|
||||
entity_id: sensor.resin_curing_deadline
|
||||
internal: true
|
||||
|
||||
script:
|
||||
- id: request_start
|
||||
parameters:
|
||||
minutes: int
|
||||
then:
|
||||
- if:
|
||||
condition:
|
||||
api.connected:
|
||||
state_subscription_only: true
|
||||
then:
|
||||
- homeassistant.event:
|
||||
event: esphome.resin_curing_timer
|
||||
data:
|
||||
controller: ${device_name}
|
||||
command: start
|
||||
minutes: !lambda return to_string(minutes);
|
||||
- id: request_stop
|
||||
then:
|
||||
- if:
|
||||
condition:
|
||||
api.connected:
|
||||
state_subscription_only: true
|
||||
then:
|
||||
- homeassistant.event:
|
||||
event: esphome.resin_curing_timer
|
||||
data:
|
||||
controller: ${device_name}
|
||||
command: stop
|
||||
|
||||
# Only Home Assistant's reported state drives the status and countdown.
|
||||
interval:
|
||||
- interval: 1s
|
||||
then:
|
||||
- lvgl.label.update:
|
||||
id: status_label
|
||||
text: !lambda |-
|
||||
if (!id(ha_api).is_connected_with_state_subscription())
|
||||
return std::string("Home Assistant offline");
|
||||
if (!id(outlet_state).has_state() ||
|
||||
(id(outlet_state).state != "on" && id(outlet_state).state != "off"))
|
||||
return std::string("Outlet unavailable");
|
||||
if (id(outlet_state).state == "on") {
|
||||
if (id(timer_state).state == "active")
|
||||
return std::string("UV light ON | Time remaining");
|
||||
return std::string("UV light ON | No active timer");
|
||||
}
|
||||
return std::string("UV light OFF | Choose a time");
|
||||
- lvgl.label.update:
|
||||
id: countdown_label
|
||||
text: !lambda |-
|
||||
if (!id(ha_api).is_connected_with_state_subscription())
|
||||
return std::string("--:--");
|
||||
if (!id(timer_state).has_state() ||
|
||||
(id(timer_state).state != "active" && id(timer_state).state != "idle"))
|
||||
return std::string("--:--");
|
||||
if (id(timer_state).state == "idle") {
|
||||
if (!id(ha_time).now().is_valid()) return std::string("--:--");
|
||||
return id(ha_time).now().strftime("%H:%M");
|
||||
}
|
||||
const char *raw = id(deadline).state.c_str();
|
||||
char *tail;
|
||||
const double end = strtod(raw, &tail);
|
||||
if (!id(ha_time).now().is_valid() || tail == raw || *tail != '\0' ||
|
||||
!std::isfinite(end) || end <= 0)
|
||||
return std::string("Syncing...");
|
||||
const int remaining = std::max(0, static_cast<int>(
|
||||
end - id(ha_time).now().timestamp));
|
||||
char buffer[16];
|
||||
snprintf(buffer, sizeof(buffer), "%02d:%02d", remaining / 60, remaining % 60);
|
||||
return std::string(buffer);
|
||||
# Highlight only the preset confirmed by Home Assistant, never a local tap.
|
||||
- lvgl.widget.update:
|
||||
id: preset_30
|
||||
state:
|
||||
checked: !lambda |-
|
||||
return id(ha_api).is_connected_with_state_subscription() &&
|
||||
id(timer_state).state == "active" && id(outlet_state).state == "on" &&
|
||||
id(timer_duration).state == "0:30:00";
|
||||
- lvgl.widget.update:
|
||||
id: preset_45
|
||||
state:
|
||||
checked: !lambda |-
|
||||
return id(ha_api).is_connected_with_state_subscription() &&
|
||||
id(timer_state).state == "active" && id(outlet_state).state == "on" &&
|
||||
id(timer_duration).state == "0:45:00";
|
||||
- lvgl.widget.update:
|
||||
id: preset_60
|
||||
state:
|
||||
checked: !lambda |-
|
||||
return id(ha_api).is_connected_with_state_subscription() &&
|
||||
id(timer_state).state == "active" && id(outlet_state).state == "on" &&
|
||||
id(timer_duration).state == "1:00:00";
|
||||
- lvgl.widget.update:
|
||||
id: preset_90
|
||||
state:
|
||||
checked: !lambda |-
|
||||
return id(ha_api).is_connected_with_state_subscription() &&
|
||||
id(timer_state).state == "active" && id(outlet_state).state == "on" &&
|
||||
id(timer_duration).state == "1:30:00";
|
||||
|
||||
lvgl:
|
||||
displays: [tft]
|
||||
rotation: 90 # Landscape; LVGL also rotates touch coordinates.
|
||||
touchscreens: [touch]
|
||||
buffer_size: 25%
|
||||
bg_color: 0x1C2638
|
||||
text_color: 0xD6E8F0
|
||||
default_font: montserrat_16
|
||||
theme:
|
||||
button:
|
||||
bg_color: 0x7ED4E6
|
||||
bg_opa: COVER
|
||||
radius: 30
|
||||
width: 60
|
||||
height: 60
|
||||
border_width: 0
|
||||
shadow_width: 0
|
||||
pad_all: 0
|
||||
text_color: 0x102A3A
|
||||
pressed:
|
||||
bg_color: 0xC4F4FA
|
||||
checked:
|
||||
bg_color: 0xF7D264
|
||||
text_color: 0x102A3A
|
||||
pages:
|
||||
- id: timer_page
|
||||
bg_color: 0x1C2638
|
||||
bg_opa: COVER
|
||||
pad_all: 0
|
||||
scrollbar_mode: "OFF"
|
||||
widgets:
|
||||
- label:
|
||||
text: "RESIN CURING"
|
||||
x: 14
|
||||
y: 20
|
||||
text_font: montserrat_14
|
||||
- button:
|
||||
id: stop_button
|
||||
x: 242
|
||||
y: 10
|
||||
width: 64
|
||||
height: 34
|
||||
radius: 17
|
||||
bg_color: 0x343044
|
||||
text_color: 0xF7C0BE
|
||||
pressed:
|
||||
bg_color: 0x793E48
|
||||
widgets:
|
||||
- label:
|
||||
text: "STOP"
|
||||
align: CENTER
|
||||
text_font: montserrat_12
|
||||
on_click:
|
||||
- script.execute: request_stop
|
||||
- label:
|
||||
id: status_label
|
||||
text: "Connecting..."
|
||||
x: 0
|
||||
y: 114
|
||||
width: 320
|
||||
text_align: CENTER
|
||||
text_font: montserrat_12
|
||||
text_color: 0xBED1DF
|
||||
- label:
|
||||
id: countdown_label
|
||||
text: "--:--"
|
||||
x: 0
|
||||
y: 49
|
||||
width: 320
|
||||
text_align: CENTER
|
||||
text_font: montserrat_48
|
||||
text_color: 0xA7EFF5
|
||||
# All four time presets share one horizontal row on the 320x240 page.
|
||||
- button:
|
||||
id: preset_30
|
||||
x: 10
|
||||
y: 150
|
||||
widgets:
|
||||
- label:
|
||||
text: "30"
|
||||
align: CENTER
|
||||
text_font: montserrat_24
|
||||
on_click:
|
||||
- script.execute:
|
||||
id: request_start
|
||||
minutes: 30
|
||||
- label:
|
||||
text: "minutes"
|
||||
x: 10
|
||||
y: 217
|
||||
width: 60
|
||||
text_align: CENTER
|
||||
text_font: montserrat_12
|
||||
- button:
|
||||
id: preset_45
|
||||
x: 90
|
||||
y: 150
|
||||
widgets:
|
||||
- label:
|
||||
text: "45"
|
||||
align: CENTER
|
||||
text_font: montserrat_24
|
||||
on_click:
|
||||
- script.execute:
|
||||
id: request_start
|
||||
minutes: 45
|
||||
- label:
|
||||
text: "minutes"
|
||||
x: 90
|
||||
y: 217
|
||||
width: 60
|
||||
text_align: CENTER
|
||||
text_font: montserrat_12
|
||||
- button:
|
||||
id: preset_60
|
||||
x: 170
|
||||
y: 150
|
||||
widgets:
|
||||
- label:
|
||||
text: "60"
|
||||
align: CENTER
|
||||
text_font: montserrat_24
|
||||
on_click:
|
||||
- script.execute:
|
||||
id: request_start
|
||||
minutes: 60
|
||||
- label:
|
||||
text: "minutes"
|
||||
x: 170
|
||||
y: 217
|
||||
width: 60
|
||||
text_align: CENTER
|
||||
text_font: montserrat_12
|
||||
- button:
|
||||
id: preset_90
|
||||
x: 250
|
||||
y: 150
|
||||
widgets:
|
||||
- label:
|
||||
text: "90"
|
||||
align: CENTER
|
||||
text_font: montserrat_24
|
||||
on_click:
|
||||
- script.execute:
|
||||
id: request_start
|
||||
minutes: 90
|
||||
- label:
|
||||
text: "minutes"
|
||||
x: 250
|
||||
y: 217
|
||||
width: 60
|
||||
text_align: CENTER
|
||||
text_font: montserrat_12
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# Timer helpers
|
||||
# Example:
|
||||
# bedtime_timer:
|
||||
# name: Bedtime Timer
|
||||
# duration: "00:30:00"
|
||||
# icon: mdi:timer
|
||||
# Used by esphome/resin-curing-timer.yaml and its companion HA package.
|
||||
resin_curing:
|
||||
name: Resin Curing
|
||||
duration: "00:30:00"
|
||||
icon: mdi:timer-outline
|
||||
restore: true
|
||||
|
||||
@@ -0,0 +1,161 @@
|
||||
# School dates transcribed from the supplied Blackhawk 2026-2027 calendar.
|
||||
# Early-dismissal days are school days. May 4-6 are closed for kindergarten
|
||||
# only and are not excluded from the older children's schedule.
|
||||
template:
|
||||
# Fetch overlapping events together; calendar.message exposes only one event.
|
||||
- triggers:
|
||||
- trigger: homeassistant
|
||||
event: start
|
||||
- trigger: event
|
||||
event_type: event_template_reloaded
|
||||
- trigger: time
|
||||
at: '00:00:00'
|
||||
- trigger: time_pattern
|
||||
minutes: '/15'
|
||||
- trigger: state
|
||||
entity_id: calendar.family_events
|
||||
actions:
|
||||
- variables:
|
||||
requested_day: "{{ now().date().isoformat() }}"
|
||||
agenda: {}
|
||||
- action: calendar.get_events
|
||||
continue_on_error: true
|
||||
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 is defined and agenda is mapping
|
||||
and 'calendar.family_events' in agenda
|
||||
and 'events' in agenda['calendar.family_events'] }}
|
||||
sensor:
|
||||
- name: Family Day Events
|
||||
unique_id: family_day_events
|
||||
state: "{{ requested_day }}"
|
||||
attributes:
|
||||
refreshed_at: "{{ now().isoformat() }}"
|
||||
events: "{{ agenda['calendar.family_events']['events'] }}"
|
||||
|
||||
- binary_sensor:
|
||||
- name: Family Schedule Ready
|
||||
unique_id: family_schedule_ready
|
||||
state: >-
|
||||
{{ has_value('calendar.family_events')
|
||||
and is_state('sensor.family_day_events', now().date().isoformat())
|
||||
and as_timestamp(now())
|
||||
- as_timestamp(state_attr('sensor.family_day_events', 'refreshed_at'), 0) < 1200 }}
|
||||
|
||||
- name: Day Camp Today
|
||||
unique_id: family_day_camp_today
|
||||
availability: "{{ is_state('binary_sensor.family_schedule_ready', 'on') }}"
|
||||
state: >-
|
||||
{% set ns = namespace(camp=false) %}
|
||||
{% for event in state_attr('sensor.family_day_events', 'events') or [] %}
|
||||
{% set summary = event.summary | default('', true) | lower %}
|
||||
{% if summary | regex_search('\\bcamp\\b')
|
||||
and not summary | regex_search('\\b(no|cancelled|canceled)\\s+(day\\s+)?camp\\b|\\bcamp\\s+(is\\s+)?(cancelled|canceled)\\b') %}
|
||||
{% set ns.camp = true %}
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
{{ ns.camp }}
|
||||
|
||||
- name: Family No School Today
|
||||
unique_id: family_no_school_today
|
||||
availability: "{{ is_state('binary_sensor.family_schedule_ready', 'on') }}"
|
||||
state: >-
|
||||
{% set ns = namespace(no_school=false) %}
|
||||
{% for event in state_attr('sensor.family_day_events', 'events') or [] %}
|
||||
{% set summary = event.summary | default('', true) | lower %}
|
||||
{% if summary | regex_search('\\b(no school|school closed|snow day)\\b') %}
|
||||
{% set ns.no_school = true %}
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
{{ ns.no_school }}
|
||||
|
||||
- name: Girls At Home
|
||||
unique_id: family_girls_at_home
|
||||
availability: "{{ is_state('binary_sensor.family_schedule_ready', 'on') }}"
|
||||
state: >-
|
||||
{% set ns = namespace(home=false) %}
|
||||
{% for event in state_attr('sensor.family_day_events', 'events') or [] %}
|
||||
{% if event.summary | default('', true) | trim | lower == 'girls at home' %}
|
||||
{% set start = as_datetime(event.start | default(''), none) %}
|
||||
{% set end = as_datetime(event.end | default(''), none) %}
|
||||
{% if start is not none and end is not none %}
|
||||
{# All-day dates have no timezone; interpret them as local dates. #}
|
||||
{% if start.tzinfo is none %}
|
||||
{% set start = start.replace(tzinfo=now().tzinfo) %}
|
||||
{% endif %}
|
||||
{% if end.tzinfo is none %}
|
||||
{% set end = end.replace(tzinfo=now().tzinfo) %}
|
||||
{% endif %}
|
||||
{% if start <= now() < end %}
|
||||
{% set ns.home = true %}
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
{{ ns.home }}
|
||||
|
||||
- name: Blackhawk Scheduled School Day
|
||||
unique_id: blackhawk_scheduled_school_day
|
||||
# Do not silently extend an old district calendar into a new school year.
|
||||
availability: >-
|
||||
{{ '2026-08-01' <= now().date().isoformat() <= '2027-07-31' }}
|
||||
state: >-
|
||||
{% set day = now().date().isoformat() %}
|
||||
{% set closed = [
|
||||
'2026-09-07', '2026-10-12', '2026-11-11',
|
||||
'2026-11-24', '2026-11-25', '2026-11-26', '2026-11-27', '2026-11-30',
|
||||
'2026-12-23', '2026-12-24', '2026-12-25',
|
||||
'2026-12-28', '2026-12-29', '2026-12-30', '2026-12-31',
|
||||
'2027-01-01', '2027-01-18', '2027-02-15',
|
||||
'2027-03-25', '2027-03-26', '2027-03-29', '2027-05-07'
|
||||
] %}
|
||||
{{ '2026-08-19' <= day <= '2027-05-27'
|
||||
and now().weekday() < 5 and day not in closed }}
|
||||
attributes:
|
||||
school_year: '2026-2027'
|
||||
first_day: '2026-08-19'
|
||||
last_day: '2027-05-27'
|
||||
calendar_valid_until: '2027-07-31'
|
||||
|
||||
- name: Blackhawk School Day
|
||||
unique_id: blackhawk_school_day
|
||||
# Scheduled closures remain known even if the family calendar is down.
|
||||
# On regular school days, require fresh data for snow-day overrides.
|
||||
availability: >-
|
||||
{{ has_value('binary_sensor.blackhawk_scheduled_school_day')
|
||||
and (is_state('binary_sensor.blackhawk_scheduled_school_day', 'off')
|
||||
or has_value('binary_sensor.family_no_school_today')) }}
|
||||
state: >-
|
||||
{{ is_state('binary_sensor.blackhawk_scheduled_school_day', 'on')
|
||||
and is_state('binary_sensor.family_no_school_today', 'off') }}
|
||||
|
||||
- name: Kids Summer Schedule Day
|
||||
unique_id: kids_summer_schedule_day
|
||||
availability: "{{ has_value('binary_sensor.blackhawk_school_day') }}"
|
||||
state: >-
|
||||
{% set day = now().date().isoformat() %}
|
||||
{{ now().weekday() < 5
|
||||
and ('2026-08-01' <= day < '2026-08-19'
|
||||
or '2027-05-27' < day <= '2027-07-31') }}
|
||||
|
||||
automation:
|
||||
- id: family_schedule_sync_legacy_camp_helper
|
||||
alias: Family Schedule - Sync Day Camp Helper
|
||||
description: Keep the existing camp helper compatible with the shared calendar sensor.
|
||||
triggers:
|
||||
- trigger: state
|
||||
entity_id: binary_sensor.day_camp_today
|
||||
to:
|
||||
- 'on'
|
||||
- 'off'
|
||||
actions:
|
||||
- action: "{{ 'input_boolean.turn_on' if is_state('binary_sensor.day_camp_today', 'on') else 'input_boolean.turn_off' }}"
|
||||
target:
|
||||
entity_id: input_boolean.day_camp_today
|
||||
mode: restart
|
||||
@@ -0,0 +1,117 @@
|
||||
# Companion to esphome/resin-curing-timer.yaml.
|
||||
# Keep the outlet and controller IDs in sync with that file's substitutions.
|
||||
# The timer helper is defined in helpers/timer.yaml.
|
||||
template:
|
||||
- sensor:
|
||||
- name: Resin Curing Deadline
|
||||
default_entity_id: sensor.resin_curing_deadline
|
||||
unique_id: resin_curing_deadline
|
||||
icon: mdi:timer-sand
|
||||
# The ESPHome display expects Unix epoch seconds, rather than an ISO date.
|
||||
state: >-
|
||||
{{ as_timestamp(state_attr('timer.resin_curing', 'finishes_at'), 0) | int
|
||||
if is_state('timer.resin_curing', 'active') else 0 }}
|
||||
|
||||
automation:
|
||||
- id: resin_curing_timer_control
|
||||
alias: Resin Curing Timer - Control UV Light
|
||||
description: >-
|
||||
Handle the CYD presets and STOP button, turn off the UV outlet when the
|
||||
timer ends, and reconcile the timer and outlet after restart or recovery.
|
||||
triggers:
|
||||
- trigger: event
|
||||
event_type: esphome.resin_curing_timer
|
||||
event_data:
|
||||
controller: resin-curing-timer
|
||||
id: command
|
||||
- trigger: state
|
||||
entity_id: timer.resin_curing
|
||||
to:
|
||||
- idle
|
||||
- paused
|
||||
id: timer_stopped
|
||||
- trigger: event
|
||||
event_type: timer.finished
|
||||
event_data:
|
||||
entity_id: timer.resin_curing
|
||||
id: timer_finished
|
||||
- trigger: state
|
||||
entity_id: switch.resin_uv_light
|
||||
to:
|
||||
- "on"
|
||||
- "off"
|
||||
id: outlet_changed
|
||||
- trigger: homeassistant
|
||||
event: start
|
||||
id: startup
|
||||
- trigger: event
|
||||
event_type: automation_reloaded
|
||||
id: reload
|
||||
# Retry a missed shutoff once the outlet recovers, including after downtime.
|
||||
- trigger: time_pattern
|
||||
seconds: "/30"
|
||||
id: reconcile
|
||||
actions:
|
||||
- choose:
|
||||
- conditions:
|
||||
- condition: template
|
||||
value_template: >-
|
||||
{{ trigger.id == 'command'
|
||||
and trigger.event.data.get('command') == 'start'
|
||||
and trigger.event.data.get('minutes') | string
|
||||
in ['30', '45', '60', '90']
|
||||
and states('switch.resin_uv_light') in ['on', 'off'] }}
|
||||
sequence:
|
||||
# Arm the shutoff before requesting power. Each preset replaces
|
||||
# the remaining time, matching the duration shown on the CYD.
|
||||
- action: timer.start
|
||||
target:
|
||||
entity_id: timer.resin_curing
|
||||
data:
|
||||
duration: "{{ (trigger.event.data.minutes | int) * 60 }}"
|
||||
- action: switch.turn_on
|
||||
target:
|
||||
entity_id: switch.resin_uv_light
|
||||
- conditions:
|
||||
- condition: template
|
||||
value_template: >-
|
||||
{{ trigger.id == 'command'
|
||||
and trigger.event.data.get('command') == 'stop' }}
|
||||
sequence:
|
||||
- action: switch.turn_off
|
||||
continue_on_error: true
|
||||
target:
|
||||
entity_id: switch.resin_uv_light
|
||||
- action: timer.cancel
|
||||
target:
|
||||
entity_id: timer.resin_curing
|
||||
default:
|
||||
# Ignore malformed commands. Recheck current state when a queued
|
||||
# event runs so an old expiry cannot stop a newly started session.
|
||||
- condition: template
|
||||
value_template: "{{ trigger.id != 'command' }}"
|
||||
- choose:
|
||||
- conditions:
|
||||
- condition: template
|
||||
value_template: >-
|
||||
{{ not is_state('timer.resin_curing', 'active')
|
||||
and not is_state('switch.resin_uv_light', 'off') }}
|
||||
sequence:
|
||||
- action: switch.turn_off
|
||||
continue_on_error: true
|
||||
target:
|
||||
entity_id: switch.resin_uv_light
|
||||
- conditions:
|
||||
- condition: state
|
||||
entity_id: timer.resin_curing
|
||||
state: active
|
||||
- condition: state
|
||||
entity_id: switch.resin_uv_light
|
||||
state: "off"
|
||||
sequence:
|
||||
# A physical/HA outlet shutoff ends the displayed countdown.
|
||||
- action: timer.cancel
|
||||
target:
|
||||
entity_id: timer.resin_curing
|
||||
mode: queued
|
||||
max: 20
|
||||
@@ -1,13 +1,13 @@
|
||||
check_day_camp:
|
||||
alias: Check Day Camp
|
||||
sequence:
|
||||
- condition: template
|
||||
value_template: "{{ has_value('binary_sensor.day_camp_today') }}"
|
||||
- choose:
|
||||
- conditions:
|
||||
- condition: template
|
||||
value_template: >-
|
||||
{{ states('calendar.family_events') == 'on' and
|
||||
'camp' in (state_attr('calendar.family_events', 'message') | lower) and
|
||||
'no' not in (state_attr('calendar.family_events', 'message') | lower) }}
|
||||
- condition: state
|
||||
entity_id: binary_sensor.day_camp_today
|
||||
state: 'on'
|
||||
sequence:
|
||||
- service: input_boolean.turn_on
|
||||
target:
|
||||
@@ -17,16 +17,12 @@ check_day_camp:
|
||||
target:
|
||||
entity_id: input_boolean.day_camp_today
|
||||
|
||||
- service: system_log.write
|
||||
data:
|
||||
message: >
|
||||
Calendar Check: State={{ states('calendar.family_events') }},
|
||||
Message={{ state_attr('calendar.family_events', 'message')
|
||||
| default('') }}
|
||||
level: debug
|
||||
mode: restart
|
||||
|
||||
notify_events_alerts:
|
||||
alias: Notify.Events Alerts
|
||||
mode: queued
|
||||
max: 20
|
||||
fields:
|
||||
message:
|
||||
required: true
|
||||
@@ -42,3 +38,30 @@ notify_events_alerts:
|
||||
title: "{{ title | default('Home Assistant') }}"
|
||||
data:
|
||||
token: !secret notify_event_alerts_token
|
||||
|
||||
family_announcement:
|
||||
alias: Family Announcement
|
||||
description: Deliver family announcements in order, with common Alexa settings.
|
||||
mode: queued
|
||||
max: 20
|
||||
fields:
|
||||
targets:
|
||||
required: true
|
||||
selector:
|
||||
entity:
|
||||
domain: media_player
|
||||
multiple: true
|
||||
message:
|
||||
required: true
|
||||
selector:
|
||||
text: {}
|
||||
delivery:
|
||||
description: Optional Alexa delivery options, such as type and method.
|
||||
selector:
|
||||
object: {}
|
||||
sequence:
|
||||
- action: notify.alexa_media
|
||||
data:
|
||||
target: "{{ targets }}"
|
||||
message: "{{ message }}"
|
||||
data: "{{ delivery | default({}) }}"
|
||||
|
||||