From fd985e014c3fe1f35adb262c2e44374bc8aeb996 Mon Sep 17 00:00:00 2001 From: Bastian de Byl Date: Fri, 25 Sep 2026 12:42:33 -0400 Subject: [PATCH] fix(hass): driveway lights ignore TV mode, ramped evening dimming, bump to 2026.9.3 The sunset automation required TV mode off, so an afternoon of TV skipped the driveway string lights entirely (Sep 22 and 23) - not an outage. The driveway now has its own sunset -1h automation, with catch-up on restart or switch reconnect before 23:00. Evening brightness lives in one script (evening_lights_apply) that blends between the old step levels; a 5-minute ramp from 20:30 eases lights that are on and leaves alone any a person has changed by hand. The Dining Hall no longer bumps to 50% at 21:30. TV off after 23:30 now only turns off the living room glow instead of bringing the whole house back to full brightness, and TV on/off leave the lights alone in daylight. Lights-out moves to 23:30, and a 01:00 sweep catches anything switched back on at the wall. Co-Authored-By: Claude Opus 5.5 --- .../roles/podman/files/hass/automations.yaml | 554 +++++------------- .../podman/files/hass/configuration.yaml | 1 + ansible/roles/podman/files/hass/scripts.yaml | 76 +++ .../podman/tasks/containers/home/hass.yml | 1 + ansible/roles/podman/tasks/main.yml | 2 +- 5 files changed, 237 insertions(+), 397 deletions(-) create mode 100644 ansible/roles/podman/files/hass/scripts.yaml diff --git a/ansible/roles/podman/files/hass/automations.yaml b/ansible/roles/podman/files/hass/automations.yaml index fabd5c6..13969e0 100644 --- a/ansible/roles/podman/files/hass/automations.yaml +++ b/ansible/roles/podman/files/hass/automations.yaml @@ -192,7 +192,8 @@ mode: single - id: '1762116115638' alias: Light - TV On - description: '' + description: TV mode on; once it's dark, dim the living room and turn off the + lights that glare on the TV triggers: - type: turned_on device_id: 18a9bb7a2a32be4371da447767ef50a9 @@ -204,28 +205,32 @@ - action: input_boolean.turn_on target: entity_id: input_boolean.tv_mode - - action: light.turn_on - metadata: {} - data: - brightness_pct: 5 - target: - area_id: living_room - - type: turn_off - device_id: 03a12d2360d9954aed19c2449070725a - entity_id: 7c1e7db73799cc3f90948b5118596985 - domain: light - - type: turn_off - device_id: 800eddbeeda071225f181a14cb9527e0 - entity_id: 521a92ddd8be76c7eddfc544f81f6020 - domain: light - - type: turn_off - device_id: 3f7f65571d9bb0833433996f1f6725bd - entity_id: 7407afe14783543252c666d5ff7c5d5c - domain: light + - if: + - condition: or + conditions: + - condition: sun + after: sunset + after_offset: "-01:00:00" + - condition: sun + before: sunrise + then: + - action: light.turn_on + data: + brightness_pct: 5 + target: + area_id: living_room + - action: light.turn_off + target: + entity_id: + - light.kitchen_wall_light + - light.dining_hall + - light.bathroom_hallway mode: single - id: new_tv_off alias: Light - TV Off - Restore - description: Restores appropriate lighting level when TV turns off based on time + description: Evening (sunset -1h to 23:30) brings the lights back to the + scheduled level; late night (23:30 to sunrise) only turns off the TV glow; + daytime leaves the lights alone triggers: - type: turned_off device_id: 18a9bb7a2a32be4371da447767ef50a9 @@ -239,399 +244,156 @@ entity_id: input_boolean.tv_mode - choose: - conditions: + - condition: sun + after: sunset + after_offset: "-01:00:00" - condition: time - after: '22:30:00' - before: '23:45:00' + before: '23:30:00' sequence: - # Late dim levels - - type: turn_on - device_id: f31e4f9bf8fa3687a07aeb4430eaef38 - entity_id: b79934d97f3bb9d8a3da47c76d03ded4 - domain: light - brightness_pct: 1 - - type: turn_on - device_id: 03a12d2360d9954aed19c2449070725a - entity_id: 7c1e7db73799cc3f90948b5118596985 - domain: light - brightness_pct: 1 - - type: turn_on - device_id: 800eddbeeda071225f181a14cb9527e0 - entity_id: 521a92ddd8be76c7eddfc544f81f6020 - domain: light - brightness_pct: 25 - - type: turn_on - device_id: 3f7f65571d9bb0833433996f1f6725bd - entity_id: 7407afe14783543252c666d5ff7c5d5c - domain: light - brightness_pct: 10 - - type: turn_on - device_id: 21eb2bd28aba2ee361a22af92e8b2d16 - entity_id: 81c486d682afcc94e98e377475cc92fc - domain: light - brightness_pct: 10 + - action: script.evening_lights_apply + data: + apply: force - conditions: - - condition: time - after: '21:30:00' - before: '22:30:00' + - condition: or + conditions: + - condition: time + after: '23:30:00' + - condition: sun + before: sunrise sequence: - # Mid dim levels - - type: turn_on - device_id: f31e4f9bf8fa3687a07aeb4430eaef38 - entity_id: b79934d97f3bb9d8a3da47c76d03ded4 - domain: light - brightness_pct: 25 - - type: turn_on - device_id: 03a12d2360d9954aed19c2449070725a - entity_id: 7c1e7db73799cc3f90948b5118596985 - domain: light - brightness_pct: 25 - - type: turn_on - device_id: 800eddbeeda071225f181a14cb9527e0 - entity_id: 521a92ddd8be76c7eddfc544f81f6020 - domain: light - brightness_pct: 50 - - type: turn_on - device_id: 3f7f65571d9bb0833433996f1f6725bd - entity_id: 7407afe14783543252c666d5ff7c5d5c - domain: light - brightness_pct: 25 - - type: turn_on - device_id: 21eb2bd28aba2ee361a22af92e8b2d16 - entity_id: 81c486d682afcc94e98e377475cc92fc - domain: light - brightness_pct: 25 - - conditions: - - condition: time - after: '21:00:00' - before: '21:30:00' - sequence: - # Early dim levels - - type: turn_on - device_id: 03a12d2360d9954aed19c2449070725a - entity_id: 7c1e7db73799cc3f90948b5118596985 - domain: light - brightness_pct: 50 - - type: turn_on - device_id: f31e4f9bf8fa3687a07aeb4430eaef38 - entity_id: b79934d97f3bb9d8a3da47c76d03ded4 - domain: light - brightness_pct: 50 - - type: turn_on - device_id: 3f7f65571d9bb0833433996f1f6725bd - entity_id: 7407afe14783543252c666d5ff7c5d5c - domain: light - brightness_pct: 50 - - type: turn_on - device_id: 21eb2bd28aba2ee361a22af92e8b2d16 - entity_id: 81c486d682afcc94e98e377475cc92fc - domain: light - brightness_pct: 50 - default: - # Full brightness (before 21:00 after sunset) - - type: turn_on - device_id: 800eddbeeda071225f181a14cb9527e0 - entity_id: 521a92ddd8be76c7eddfc544f81f6020 - domain: light - brightness_pct: 25 - - type: turn_on - device_id: f31e4f9bf8fa3687a07aeb4430eaef38 - entity_id: b79934d97f3bb9d8a3da47c76d03ded4 - domain: light - brightness_pct: 100 - - type: turn_on - device_id: 03a12d2360d9954aed19c2449070725a - entity_id: 7c1e7db73799cc3f90948b5118596985 - domain: light - brightness_pct: 100 - - type: turn_on - device_id: 3f7f65571d9bb0833433996f1f6725bd - entity_id: 7407afe14783543252c666d5ff7c5d5c - domain: light - brightness_pct: 75 - - type: turn_on - device_id: 21eb2bd28aba2ee361a22af92e8b2d16 - entity_id: 81c486d682afcc94e98e377475cc92fc - domain: light - brightness_pct: 100 + - action: light.turn_off + target: + area_id: living_room mode: single -- id: 'sunset_lights_on' - alias: Lights - Sunset On - description: Turn on lights 1 hour before sunset +- id: driveway_lights_on + alias: Driveway String Lights On + description: On 1 hour before sunset whether or not the TV is on. Also catches + up if Home Assistant restarts or the switch reconnects before the 23:00 off triggers: - trigger: sun event: sunset offset: "-01:00:00" + id: sunset + - trigger: homeassistant + event: start + - trigger: state + entity_id: switch.driveway_string_lights + from: unavailable + to: 'off' conditions: - condition: state - entity_id: input_boolean.tv_mode + entity_id: switch.driveway_string_lights state: 'off' + - condition: or + conditions: + - condition: trigger + id: sunset + - condition: and + conditions: + - condition: sun + after: sunset + after_offset: "-01:00:00" + - condition: time + before: '23:00:00' actions: - - type: turn_on - device_id: 1fa1aca8f90daf94a2a7baf8a3abc158 - entity_id: 58d101e63456fd8e088d3a3b63f3a0f9 - domain: switch - - type: turn_on - device_id: 800eddbeeda071225f181a14cb9527e0 - entity_id: 521a92ddd8be76c7eddfc544f81f6020 - domain: light - brightness_pct: 25 - - type: turn_on - device_id: f31e4f9bf8fa3687a07aeb4430eaef38 - entity_id: b79934d97f3bb9d8a3da47c76d03ded4 - domain: light - brightness_pct: 100 - - type: turn_on - device_id: 03a12d2360d9954aed19c2449070725a - entity_id: 7c1e7db73799cc3f90948b5118596985 - domain: light - brightness_pct: 100 - - type: turn_on - device_id: 3f7f65571d9bb0833433996f1f6725bd - entity_id: 7407afe14783543252c666d5ff7c5d5c - domain: light - brightness_pct: 75 - - type: turn_on - device_id: 21eb2bd28aba2ee361a22af92e8b2d16 - entity_id: 81c486d682afcc94e98e377475cc92fc - domain: light - brightness_pct: 100 + - action: switch.turn_on + target: + entity_id: switch.driveway_string_lights mode: single -- id: 'evening_dim_2100' - alias: Lights - Evening Dim (21:00) - description: Dim lights at 21:00 - only affects lights that are ON +- id: 'sunset_lights_on' + alias: Lights - Sunset On + description: Turn on lights 1 hour before sunset. If the TV is on, the living + room gets the TV glow and the lights that glare on the TV stay off triggers: - - trigger: time - at: "21:00:00" - conditions: - - condition: state - entity_id: input_boolean.tv_mode - state: 'off' - actions: - - if: - - condition: device - device_id: 03a12d2360d9954aed19c2449070725a - domain: light - entity_id: 7c1e7db73799cc3f90948b5118596985 - type: is_on - then: - - type: turn_on - device_id: 03a12d2360d9954aed19c2449070725a - entity_id: 7c1e7db73799cc3f90948b5118596985 - domain: light - brightness_pct: 50 - - if: - - condition: device - device_id: f31e4f9bf8fa3687a07aeb4430eaef38 - domain: light - entity_id: b79934d97f3bb9d8a3da47c76d03ded4 - type: is_on - then: - - type: turn_on - device_id: f31e4f9bf8fa3687a07aeb4430eaef38 - entity_id: b79934d97f3bb9d8a3da47c76d03ded4 - domain: light - brightness_pct: 50 - - if: - - condition: device - device_id: 3f7f65571d9bb0833433996f1f6725bd - domain: light - entity_id: 7407afe14783543252c666d5ff7c5d5c - type: is_on - then: - - type: turn_on - device_id: 3f7f65571d9bb0833433996f1f6725bd - entity_id: 7407afe14783543252c666d5ff7c5d5c - domain: light - brightness_pct: 50 - - if: - - condition: device - device_id: 21eb2bd28aba2ee361a22af92e8b2d16 - domain: light - entity_id: 81c486d682afcc94e98e377475cc92fc - type: is_on - then: - - type: turn_on - device_id: 21eb2bd28aba2ee361a22af92e8b2d16 - entity_id: 81c486d682afcc94e98e377475cc92fc - domain: light - brightness_pct: 50 - mode: single -- id: 'mid_dim_2130' - alias: Lights - Mid Dim (21:30) - description: Dim lights at 21:30 - only affects lights that are ON - triggers: - - trigger: time - at: "21:30:00" - conditions: - - condition: state - entity_id: input_boolean.tv_mode - state: 'off' - actions: - - if: - - condition: device - device_id: f31e4f9bf8fa3687a07aeb4430eaef38 - domain: light - entity_id: b79934d97f3bb9d8a3da47c76d03ded4 - type: is_on - then: - - type: turn_on - device_id: f31e4f9bf8fa3687a07aeb4430eaef38 - entity_id: b79934d97f3bb9d8a3da47c76d03ded4 - domain: light - brightness_pct: 25 - - if: - - condition: device - device_id: 03a12d2360d9954aed19c2449070725a - domain: light - entity_id: 7c1e7db73799cc3f90948b5118596985 - type: is_on - then: - - type: turn_on - device_id: 03a12d2360d9954aed19c2449070725a - entity_id: 7c1e7db73799cc3f90948b5118596985 - domain: light - brightness_pct: 25 - - if: - - condition: device - device_id: 800eddbeeda071225f181a14cb9527e0 - domain: light - entity_id: 521a92ddd8be76c7eddfc544f81f6020 - type: is_on - then: - - type: turn_on - device_id: 800eddbeeda071225f181a14cb9527e0 - entity_id: 521a92ddd8be76c7eddfc544f81f6020 - domain: light - brightness_pct: 50 - - if: - - condition: device - device_id: 3f7f65571d9bb0833433996f1f6725bd - domain: light - entity_id: 7407afe14783543252c666d5ff7c5d5c - type: is_on - then: - - type: turn_on - device_id: 3f7f65571d9bb0833433996f1f6725bd - entity_id: 7407afe14783543252c666d5ff7c5d5c - domain: light - brightness_pct: 25 - - if: - - condition: device - device_id: 21eb2bd28aba2ee361a22af92e8b2d16 - domain: light - entity_id: 81c486d682afcc94e98e377475cc92fc - type: is_on - then: - - type: turn_on - device_id: 21eb2bd28aba2ee361a22af92e8b2d16 - entity_id: 81c486d682afcc94e98e377475cc92fc - domain: light - brightness_pct: 25 - mode: single -- id: 'late_dim_2230' - alias: Lights - Late Dim (22:30) - description: Dim lights at 22:30 - only affects lights that are ON - triggers: - - trigger: time - at: "22:30:00" - conditions: - - condition: state - entity_id: input_boolean.tv_mode - state: 'off' - actions: - - if: - - condition: device - device_id: f31e4f9bf8fa3687a07aeb4430eaef38 - domain: light - entity_id: b79934d97f3bb9d8a3da47c76d03ded4 - type: is_on - then: - - type: turn_on - device_id: f31e4f9bf8fa3687a07aeb4430eaef38 - entity_id: b79934d97f3bb9d8a3da47c76d03ded4 - domain: light - brightness_pct: 1 - - if: - - condition: device - device_id: 03a12d2360d9954aed19c2449070725a - domain: light - entity_id: 7c1e7db73799cc3f90948b5118596985 - type: is_on - then: - - type: turn_on - device_id: 03a12d2360d9954aed19c2449070725a - entity_id: 7c1e7db73799cc3f90948b5118596985 - domain: light - brightness_pct: 1 - - if: - - condition: device - device_id: 800eddbeeda071225f181a14cb9527e0 - domain: light - entity_id: 521a92ddd8be76c7eddfc544f81f6020 - type: is_on - then: - - type: turn_on - device_id: 800eddbeeda071225f181a14cb9527e0 - entity_id: 521a92ddd8be76c7eddfc544f81f6020 - domain: light - brightness_pct: 25 - - if: - - condition: device - device_id: 3f7f65571d9bb0833433996f1f6725bd - domain: light - entity_id: 7407afe14783543252c666d5ff7c5d5c - type: is_on - then: - - type: turn_on - device_id: 3f7f65571d9bb0833433996f1f6725bd - entity_id: 7407afe14783543252c666d5ff7c5d5c - domain: light - brightness_pct: 10 - - if: - - condition: device - device_id: 21eb2bd28aba2ee361a22af92e8b2d16 - domain: light - entity_id: 81c486d682afcc94e98e377475cc92fc - type: is_on - then: - - type: turn_on - device_id: 21eb2bd28aba2ee361a22af92e8b2d16 - entity_id: 81c486d682afcc94e98e377475cc92fc - domain: light - brightness_pct: 10 - mode: single -- id: 'lights_out_2345' - alias: Lights - Out (23:45) - description: Turn off all lights at 23:45 - triggers: - - trigger: time - at: "23:45:00" + - trigger: sun + event: sunset + offset: "-01:00:00" conditions: [] actions: - - type: turn_off - device_id: 3f7f65571d9bb0833433996f1f6725bd - entity_id: 7407afe14783543252c666d5ff7c5d5c - domain: light - - type: turn_off - device_id: f31e4f9bf8fa3687a07aeb4430eaef38 - entity_id: b79934d97f3bb9d8a3da47c76d03ded4 - domain: light - - type: turn_off - device_id: 03a12d2360d9954aed19c2449070725a - entity_id: 7c1e7db73799cc3f90948b5118596985 - domain: light - - type: turn_off - device_id: 800eddbeeda071225f181a14cb9527e0 - entity_id: 521a92ddd8be76c7eddfc544f81f6020 - domain: light - - type: turn_off - device_id: 03eb359bf2344a58bebfe1e9c5bcfadd - entity_id: a30b2da3cd80a5b4c927e1608b91eb65 - domain: light - - type: turn_off - device_id: 21eb2bd28aba2ee361a22af92e8b2d16 - entity_id: 81c486d682afcc94e98e377475cc92fc - domain: light + - action: script.evening_lights_apply + data: + apply: force + - if: + - condition: state + entity_id: input_boolean.tv_mode + state: 'on' + then: + - action: light.turn_on + data: + brightness_pct: 5 + target: + area_id: living_room + mode: single +- id: evening_dim_ramp + alias: Lights - Evening Dim Ramp + description: Every 5 minutes from 20:30 to 23:30, ease the lights that are on + toward the schedule in script.evening_lights_apply + triggers: + - trigger: time_pattern + minutes: /5 + conditions: + - condition: time + after: '20:30:00' + before: '23:30:00' + actions: + - action: script.evening_lights_apply + data: + apply: ramp + mode: single +- id: 'lights_out_2345' + alias: Lights - Out (23:30) + description: Turn off all lights at 23:30. While the TV is on the living room + is left as it is + triggers: + - trigger: time + at: "23:30:00" + conditions: [] + actions: + - action: light.turn_off + target: + entity_id: + - light.kitchen_lights + - light.kitchen_wall_light + - light.dining_hall + - light.dining_room + - light.bathroom_hallway + - if: + - condition: state + entity_id: input_boolean.tv_mode + state: 'off' + then: + - action: light.turn_off + target: + entity_id: light.living_room + mode: single +- id: lights_sweep_0100 + alias: Lights - Sweep (01:00) + description: Catch anything turned back on after lights-out. While the TV is + on the living room is left as it is + triggers: + - trigger: time + at: "01:00:00" + conditions: [] + actions: + - action: light.turn_off + target: + entity_id: + - light.kitchen_lights + - light.kitchen_wall_light + - light.dining_hall + - light.dining_room + - light.bathroom_hallway + - action: switch.turn_off + target: + entity_id: switch.driveway_string_lights + - if: + - condition: state + entity_id: input_boolean.tv_mode + state: 'off' + then: + - action: light.turn_off + target: + entity_id: light.living_room mode: single - id: '1768862300896' alias: Bedroom On diff --git a/ansible/roles/podman/files/hass/configuration.yaml b/ansible/roles/podman/files/hass/configuration.yaml index 04c73ae..a25d51e 100644 --- a/ansible/roles/podman/files/hass/configuration.yaml +++ b/ansible/roles/podman/files/hass/configuration.yaml @@ -23,6 +23,7 @@ homeassistant: media: /share automation: !include automations.yaml +script: !include scripts.yaml input_boolean: tv_mode: diff --git a/ansible/roles/podman/files/hass/scripts.yaml b/ansible/roles/podman/files/hass/scripts.yaml new file mode 100644 index 0000000..83cbd32 --- /dev/null +++ b/ansible/roles/podman/files/hass/scripts.yaml @@ -0,0 +1,76 @@ +evening_lights_apply: + alias: Evening Lights - Apply Schedule + description: >- + Sets each evening light to its scheduled brightness for the current time, + blending linearly between the points in `schedule`. apply=force turns the + lights on (sunset, TV off); apply=ramp only eases lights that are already + on, and leaves alone any light someone has changed by hand. While TV mode + is on the living room and the lights that glare on the TV are left alone. + mode: queued + fields: + apply: + description: "force: turn lights on at the target. ramp: only adjust lights that are already on." + example: ramp + selector: + select: + options: + - force + - ramp + variables: + # [minute of day, brightness %] - 1230 = 20:30, 1260 = 21:00, + # 1290 = 21:30, 1350 = 22:30. Before the first point a light sits at the + # first value; after the last it holds the last value until lights-out. + schedule: + light.kitchen_lights: [[1230, 100], [1260, 50], [1290, 25], [1350, 1]] + light.kitchen_wall_light: [[1230, 100], [1260, 50], [1290, 25], [1350, 1]] + light.bathroom_hallway: [[1230, 75], [1260, 50], [1290, 25], [1350, 10]] + light.living_room: [[1230, 100], [1260, 50], [1290, 25], [1350, 10]] + light.dining_hall: [[1290, 25], [1350, 15]] + tv_mode_lights: + - light.living_room + - light.kitchen_wall_light + - light.dining_hall + - light.bathroom_hallway + apply_mode: "{{ apply | default('ramp') }}" + sequence: + - repeat: + for_each: "{{ schedule.keys() | list }}" + sequence: + - variables: + light: "{{ repeat.item }}" + # [target now, target 5 minutes ago - what the last ramp tick set] + levels: >- + {%- macro at(pts, t) -%} + {%- if t <= pts[0][0] -%}{{ pts[0][1] }} + {%- elif t >= pts[-1][0] -%}{{ pts[-1][1] }} + {%- else -%} + {%- for i in range(pts | length - 1) if pts[i][0] <= t < pts[i + 1][0] -%} + {{ (pts[i][1] + (pts[i + 1][1] - pts[i][1]) * (t - pts[i][0]) / (pts[i + 1][0] - pts[i][0])) | round(0) | int }} + {%- endfor -%} + {%- endif -%} + {%- endmacro -%} + {%- set t = now().hour * 60 + now().minute -%} + {{ [at(schedule[repeat.item], t) | int, at(schedule[repeat.item], t - 5) | int] }} + current: "{{ ((state_attr(repeat.item, 'brightness') or 0) / 2.55) | round(0) | int }}" + skip: "{{ is_state('input_boolean.tv_mode', 'on') and repeat.item in tv_mode_lights }}" + - choose: + - conditions: "{{ not skip and apply_mode == 'force' }}" + sequence: + - action: light.turn_on + target: + entity_id: "{{ light }}" + data: + brightness_pct: "{{ levels[0] }}" + transition: 2 + # A light more than 10 points off the last tick was set by hand; the + # biggest scheduled change in 5 minutes is ~8 + - conditions: >- + {{ not skip and apply_mode == 'ramp' and is_state(light, 'on') + and (current - levels[1]) | abs <= 10 and current != levels[0] }} + sequence: + - action: light.turn_on + target: + entity_id: "{{ light }}" + data: + brightness_pct: "{{ levels[0] }}" + transition: 60 diff --git a/ansible/roles/podman/tasks/containers/home/hass.yml b/ansible/roles/podman/tasks/containers/home/hass.yml index d47cfbd..786c4e8 100644 --- a/ansible/roles/podman/tasks/containers/home/hass.yml +++ b/ansible/roles/podman/tasks/containers/home/hass.yml @@ -25,6 +25,7 @@ loop: - configuration.yaml - automations.yaml + - scripts.yaml - name: flush handlers ansible.builtin.meta: flush_handlers diff --git a/ansible/roles/podman/tasks/main.yml b/ansible/roles/podman/tasks/main.yml index 28a0c9a..16f8815 100644 --- a/ansible/roles/podman/tasks/main.yml +++ b/ansible/roles/podman/tasks/main.yml @@ -39,7 +39,7 @@ - import_tasks: containers/home/hass.yml vars: - image: ghcr.io/home-assistant/home-assistant:2026.8.3 + image: ghcr.io/home-assistant/home-assistant:2026.9.3 tags: hass - import_tasks: containers/home/partsy.yml