############################################################################### # PACKAGE: Outdoor lighting controls ############################################################################### # # Version: # 2026-07-20 v0.3 # - Synchronized the documented Number 16 schedule with the implementation. # # 2026-05-09 v0.2 # - Refactored scheduled control to use editable schedule lists. # - Switched lights now use explicit ON/OFF schedule entries instead of # ON plus duration. # - Added dimmer schedule event tracking so long ramps are not restarted by # the 5 minute scheduler. # - Added manual-on start tracking for switched lights so scheduled events # can override a temporary manual-on period. # # 2026-05-09 v0.1 # - Initial package for outdoor switched and dimmable lighting. # # Notes: # - Edit schedules inside the per-light reconcile/apply scripts below. # - The schedule rows are deliberately near the top of each script. # - Dimmable schedule format: # trigger: sunset, offset_minutes, target_brightness, ramp_minutes # trigger: time, at, target_brightness, ramp_minutes # - Switched schedule format: # trigger: sunset, offset_minutes, target_state # trigger: time, at, target_state # - For dimmable schedules, fixed time entries override sun based entries if # the real sunset offset would occur after the following fixed time. # - The scheduler runs every 5 minutes. Dimming ramps also step every 5 minutes # by default. # - Manual-off suppression lasts until the next day's first scheduled ON. # ############################################################################### input_boolean: outdoor_lighting_automation_enable: name: Outdoor Lighting Automation - Master Enable icon: mdi:outdoor-lamp initial: true outdoor_lighting_deck_coloured_automation_enable: name: Outdoor Lighting Automation - Deck Coloured Lights icon: mdi:string-lights initial: true outdoor_lighting_patio_automation_enable: name: Outdoor Lighting Automation - Patio Lights icon: mdi:coach-lamp initial: true outdoor_lighting_number_16_automation_enable: name: Outdoor Lighting Automation - View Road Number 16 icon: mdi:numeric-16-box initial: true outdoor_lighting_main_entrance_automation_enable: name: Outdoor Lighting Automation - Main Entrance Outside LEDs icon: mdi:led-strip-variant initial: true input_datetime: outdoor_lighting_deck_suppress_until: name: Outdoor Lighting Deck Suppress Until has_date: true has_time: true outdoor_lighting_deck_manual_on_started: name: Outdoor Lighting Deck Manual On Started has_date: true has_time: true outdoor_lighting_patio_suppress_until: name: Outdoor Lighting Patio Suppress Until has_date: true has_time: true outdoor_lighting_patio_manual_on_started: name: Outdoor Lighting Patio Manual On Started has_date: true has_time: true outdoor_lighting_number_16_last_schedule_event: name: Outdoor Lighting Number 16 Last Schedule Event has_date: true has_time: true outdoor_lighting_main_entrance_suppress_until: name: Outdoor Lighting Main Entrance Suppress Until has_date: true has_time: true outdoor_lighting_main_entrance_last_schedule_event: name: Outdoor Lighting Main Entrance Last Schedule Event has_date: true has_time: true outdoor_lighting_main_entrance_last_override_press: name: Outdoor Lighting Main Entrance Last Override Press has_date: true has_time: true input_select: outdoor_lighting_main_entrance_override_mode: name: Outdoor Lighting Main Entrance Override Mode options: - none - return_to_schedule - turn_off initial: none icon: mdi:gesture-tap-button timer: outdoor_lighting_deck_manual_on: name: Outdoor Lighting Deck Manual On Timer duration: "01:00:00" outdoor_lighting_patio_manual_on: name: Outdoor Lighting Patio Manual On Timer duration: "01:00:00" outdoor_lighting_main_entrance_override: name: Outdoor Lighting Main Entrance Override Timer duration: "01:00:00" script: ############################################################################# # Shared dimmable ramp scripts ############################################################################# outdoor_lighting_ramp_number_16: alias: Outdoor Lighting - Ramp View Road Number 16 mode: restart fields: target_brightness: description: Target brightness from 0 to 100. Zero means off. ramp_seconds: description: Ramp duration in seconds. sequence: - variables: brightness_entity: number.front_entranceway_led_light_controls_number_16_led_light_brightness switch_entity: switch.front_entranceway_led_light_controls_number_16_led_light ramp_update_seconds: 300 target: > {% set raw = target_brightness | int(0) %} {{ [[raw, 0] | max, 100] | min }} ramp_s: "{{ ramp_seconds | int(0) }}" current: > {% if is_state(switch_entity, 'on') %} {{ states(brightness_entity) | float(0) }} {% else %} 0 {% endif %} raw_steps: > {% if ramp_s | int(0) <= 0 %} 0 {% else %} {{ ((ramp_s | int(0)) / (ramp_update_seconds | int(300))) | round(0, 'ceil') | int }} {% endif %} steps: > {% if ramp_s | int(0) <= 0 %} 1 {% else %} {{ (raw_steps | int(0)) + 1 }} {% endif %} step_delay_seconds: > {% if (steps | int(1)) <= 1 %} 0 {% else %} {{ ((ramp_s | int(0)) / ((steps | int(1)) - 1)) | round(0, 'ceil') | int }} {% endif %} - repeat: count: "{{ steps | int(1) }}" sequence: - variables: next_value: > {% set val = (current | float(0)) + (((target | float(0)) - (current | float(0))) * (repeat.index | int) / (steps | int(1))) %} {{ [[val | round(0) | int, 0] | max, 100] | min }} - action: number.set_value target: entity_id: number.front_entranceway_led_light_controls_number_16_led_light_brightness data: value: "{{ next_value | int(0) }}" - if: - condition: template value_template: "{{ (target | int(0)) > 0 and is_state('switch.front_entranceway_led_light_controls_number_16_led_light', 'off') }}" then: - action: switch.turn_on target: entity_id: switch.front_entranceway_led_light_controls_number_16_led_light - if: - condition: template value_template: "{{ repeat.index < (steps | int(1)) }}" then: - delay: seconds: "{{ step_delay_seconds | int(0) }}" - if: - condition: template value_template: "{{ (target | int(0)) <= 0 }}" then: - action: switch.turn_off target: entity_id: switch.front_entranceway_led_light_controls_number_16_led_light outdoor_lighting_ramp_main_entrance: alias: Outdoor Lighting - Ramp Main Entrance Outside LEDs mode: restart fields: target_brightness: description: Target brightness from 0 to 100. Zero means off. ramp_seconds: description: Ramp duration in seconds. sequence: - variables: brightness_entity: number.front_entranceway_led_light_controls_main_entrance_outside_led_lights_brightness switch_entity: switch.front_entranceway_led_light_controls_main_entrance_outside_led_lights ramp_update_seconds: 300 target: > {% set raw = target_brightness | int(0) %} {{ [[raw, 0] | max, 100] | min }} ramp_s: "{{ ramp_seconds | int(0) }}" current: > {% if is_state(switch_entity, 'on') %} {{ states(brightness_entity) | float(0) }} {% else %} 0 {% endif %} raw_steps: > {% if ramp_s | int(0) <= 0 %} 0 {% else %} {{ ((ramp_s | int(0)) / (ramp_update_seconds | int(300))) | round(0, 'ceil') | int }} {% endif %} steps: > {% if ramp_s | int(0) <= 0 %} 1 {% else %} {{ (raw_steps | int(0)) + 1 }} {% endif %} step_delay_seconds: > {% if (steps | int(1)) <= 1 %} 0 {% else %} {{ ((ramp_s | int(0)) / ((steps | int(1)) - 1)) | round(0, 'ceil') | int }} {% endif %} - repeat: count: "{{ steps | int(1) }}" sequence: - variables: next_value: > {% set val = (current | float(0)) + (((target | float(0)) - (current | float(0))) * (repeat.index | int) / (steps | int(1))) %} {{ [[val | round(0) | int, 0] | max, 100] | min }} - action: number.set_value target: entity_id: number.front_entranceway_led_light_controls_main_entrance_outside_led_lights_brightness data: value: "{{ next_value | int(0) }}" - if: - condition: template value_template: "{{ (target | int(0)) > 0 and is_state('switch.front_entranceway_led_light_controls_main_entrance_outside_led_lights', 'off') }}" then: - action: switch.turn_on target: entity_id: switch.front_entranceway_led_light_controls_main_entrance_outside_led_lights - if: - condition: template value_template: "{{ repeat.index < (steps | int(1)) }}" then: - delay: seconds: "{{ step_delay_seconds | int(0) }}" - if: - condition: template value_template: "{{ (target | int(0)) <= 0 }}" then: - action: switch.turn_off target: entity_id: switch.front_entranceway_led_light_controls_main_entrance_outside_led_lights ############################################################################# # 1) Outdoor Deck Coloured Lights # Switch: switch.esp_downstloungeoutside_relay_1_outside_lights # Override: binary_sensor.esp_downstloungeoutside_button_1_outside_lights ############################################################################# outdoor_lighting_reconcile_deck_coloured: alias: Outdoor Lighting - Reconcile Deck Coloured Lights mode: restart sequence: - condition: state entity_id: input_boolean.outdoor_lighting_automation_enable state: "on" - condition: state entity_id: input_boolean.outdoor_lighting_deck_coloured_automation_enable state: "on" ########################################################################### # EDIT SCHEDULE HERE # Format: trigger, offset_minutes, target_state # Current schedule: # - sunset +60, ON # - sunset +240, OFF ########################################################################### - variables: schedule: - trigger: sunset offset_minutes: 60 target_state: "on" - trigger: sunset offset_minutes: 240 target_state: "off" - variables: selected_schedule: > {% set now_ts = as_timestamp(now()) %} {% set next_setting_raw = state_attr('sun.sun', 'next_setting') %} {% if next_setting_raw %} {% set next_setting = as_datetime(next_setting_raw) | as_local %} {% if next_setting.date() == now().date() %} {% set today_sunset = next_setting %} {% else %} {% set today_sunset = next_setting - timedelta(days=1) %} {% endif %} {% else %} {% set today_sunset = none %} {% endif %} {% set ns = namespace(best_ts=0, best_state='off') %} {% for day_offset in [-1, 0] %} {% for item in schedule %} {% if item.trigger == 'sunset' and today_sunset %} {% set event_dt = today_sunset + timedelta(days=day_offset) + timedelta(minutes=item.offset_minutes | int(0)) %} {% elif item.trigger == 'time' %} {% set event_dt = today_at(item.at) + timedelta(days=day_offset) %} {% else %} {% set event_dt = none %} {% endif %} {% if event_dt %} {% set event_ts = as_timestamp(event_dt, 0) %} {% if event_ts <= now_ts and event_ts > ns.best_ts %} {% set ns.best_ts = event_ts %} {% set ns.best_state = item.target_state %} {% endif %} {% endif %} {% endfor %} {% endfor %} {{ ns.best_state }}|{{ ns.best_ts | int(0) }} selected_target_state: "{{ selected_schedule.split('|')[0] }}" selected_event_ts: "{{ selected_schedule.split('|')[1] | int(0) }}" suppress_active: > {% set ts = as_timestamp(states('input_datetime.outdoor_lighting_deck_suppress_until'), 0) %} {{ ts > as_timestamp(now()) }} manual_on_active: "{{ is_state('timer.outdoor_lighting_deck_manual_on', 'active') }}" manual_on_started_ts: "{{ as_timestamp(states('input_datetime.outdoor_lighting_deck_manual_on_started'), 0) }}" manual_on_still_valid: > {{ manual_on_active | bool and (selected_event_ts | int(0)) <= (manual_on_started_ts | int(0)) }} should_be_on: > {% if suppress_active | bool %} false {% elif manual_on_still_valid | bool %} true {% else %} {{ selected_target_state == 'on' }} {% endif %} schedule_has_overridden_manual: > {{ manual_on_active | bool and not (manual_on_still_valid | bool) }} - if: - condition: template value_template: "{{ schedule_has_overridden_manual | bool }}" then: - action: timer.cancel target: entity_id: timer.outdoor_lighting_deck_manual_on - choose: - conditions: - condition: template value_template: "{{ should_be_on | bool }}" sequence: - action: switch.turn_on target: entity_id: switch.esp_downstloungeoutside_relay_1_outside_lights default: - action: switch.turn_off target: entity_id: switch.esp_downstloungeoutside_relay_1_outside_lights ############################################################################# # 2) Patio Lights # Switch: switch.esp_garageentrylights_relay_2_patio_lights # Override: binary_sensor.esp_garageentrylights_button_2_patio_lights ############################################################################# outdoor_lighting_reconcile_patio: alias: Outdoor Lighting - Reconcile Patio Lights mode: restart sequence: - condition: state entity_id: input_boolean.outdoor_lighting_automation_enable state: "on" - condition: state entity_id: input_boolean.outdoor_lighting_patio_automation_enable state: "on" ########################################################################### # EDIT SCHEDULE HERE # Format: trigger, offset_minutes, target_state # Current schedule: # - sunset -30, ON # - sunset +150, OFF ########################################################################### - variables: schedule: - trigger: sunset offset_minutes: -30 target_state: "on" - trigger: sunset offset_minutes: 150 target_state: "off" - variables: selected_schedule: > {% set now_ts = as_timestamp(now()) %} {% set next_setting_raw = state_attr('sun.sun', 'next_setting') %} {% if next_setting_raw %} {% set next_setting = as_datetime(next_setting_raw) | as_local %} {% if next_setting.date() == now().date() %} {% set today_sunset = next_setting %} {% else %} {% set today_sunset = next_setting - timedelta(days=1) %} {% endif %} {% else %} {% set today_sunset = none %} {% endif %} {% set ns = namespace(best_ts=0, best_state='off') %} {% for day_offset in [-1, 0] %} {% for item in schedule %} {% if item.trigger == 'sunset' and today_sunset %} {% set event_dt = today_sunset + timedelta(days=day_offset) + timedelta(minutes=item.offset_minutes | int(0)) %} {% elif item.trigger == 'time' %} {% set event_dt = today_at(item.at) + timedelta(days=day_offset) %} {% else %} {% set event_dt = none %} {% endif %} {% if event_dt %} {% set event_ts = as_timestamp(event_dt, 0) %} {% if event_ts <= now_ts and event_ts > ns.best_ts %} {% set ns.best_ts = event_ts %} {% set ns.best_state = item.target_state %} {% endif %} {% endif %} {% endfor %} {% endfor %} {{ ns.best_state }}|{{ ns.best_ts | int(0) }} selected_target_state: "{{ selected_schedule.split('|')[0] }}" selected_event_ts: "{{ selected_schedule.split('|')[1] | int(0) }}" suppress_active: > {% set ts = as_timestamp(states('input_datetime.outdoor_lighting_patio_suppress_until'), 0) %} {{ ts > as_timestamp(now()) }} manual_on_active: "{{ is_state('timer.outdoor_lighting_patio_manual_on', 'active') }}" manual_on_started_ts: "{{ as_timestamp(states('input_datetime.outdoor_lighting_patio_manual_on_started'), 0) }}" manual_on_still_valid: > {{ manual_on_active | bool and (selected_event_ts | int(0)) <= (manual_on_started_ts | int(0)) }} should_be_on: > {% if suppress_active | bool %} false {% elif manual_on_still_valid | bool %} true {% else %} {{ selected_target_state == 'on' }} {% endif %} schedule_has_overridden_manual: > {{ manual_on_active | bool and not (manual_on_still_valid | bool) }} - if: - condition: template value_template: "{{ schedule_has_overridden_manual | bool }}" then: - action: timer.cancel target: entity_id: timer.outdoor_lighting_patio_manual_on - choose: - conditions: - condition: template value_template: "{{ should_be_on | bool }}" sequence: - action: switch.turn_on target: entity_id: switch.esp_garageentrylights_relay_2_patio_lights default: - action: switch.turn_off target: entity_id: switch.esp_garageentrylights_relay_2_patio_lights ############################################################################# # 3) View Road Street Number 16 # Brightness: number.front_entranceway_led_light_controls_number_16_led_light_brightness # Switch: switch.front_entranceway_led_light_controls_number_16_led_light ############################################################################# outdoor_lighting_apply_number_16_current_target: alias: Outdoor Lighting - Apply Current Target - View Road Number 16 mode: restart fields: force_reconcile: description: Force immediate reconciliation even if the schedule event has already been applied. ramp_seconds: description: Optional ramp seconds override. Use -1 or omit to use the schedule ramp. sequence: - condition: state entity_id: input_boolean.outdoor_lighting_automation_enable state: "on" - condition: state entity_id: input_boolean.outdoor_lighting_number_16_automation_enable state: "on" ########################################################################### # EDIT SCHEDULE HERE # Format: trigger, offset_minutes/at, target_brightness, ramp_minutes # Current schedule: # - sunset -30, 60, 30 # - 20:00, 50, 120 # - 01:00, 20, 120 # - 06:00, 0, 120 ########################################################################### - variables: schedule: - trigger: sunset offset_minutes: -30 target_brightness: 60 ramp_minutes: 30 - trigger: time at: "20:00:00" target_brightness: 50 ramp_minutes: 120 - trigger: time at: "01:00:00" target_brightness: 20 ramp_minutes: 120 - trigger: time at: "06:00:00" target_brightness: 0 ramp_minutes: 120 - variables: selected_schedule: > {% set now_ts = as_timestamp(now()) %} {% set next_setting_raw = state_attr('sun.sun', 'next_setting') %} {% if next_setting_raw %} {% set next_setting = as_datetime(next_setting_raw) | as_local %} {% if next_setting.date() == now().date() %} {% set today_sunset = next_setting %} {% else %} {% set today_sunset = next_setting - timedelta(days=1) %} {% endif %} {% else %} {% set today_sunset = none %} {% endif %} {% set ns = namespace(best_ts=0, best_target=0, best_ramp=0) %} {% for day_offset in [-1, 0] %} {% for item in schedule %} {% if item.trigger == 'sunset' and today_sunset %} {% set event_dt = today_sunset + timedelta(days=day_offset) + timedelta(minutes=item.offset_minutes | int(0)) %} {% elif item.trigger == 'time' %} {% set event_dt = today_at(item.at) + timedelta(days=day_offset) %} {% else %} {% set event_dt = none %} {% endif %} {% if event_dt %} {% set valid = namespace(ok=true) %} {% if item.trigger == 'sunset' %} {% set idx = loop.index0 %} {% set nxt = namespace(found=false, dt=none) %} {% for step in range(1, schedule | length) %} {% set j = (idx + step) % (schedule | length) %} {% set next_item = schedule[j] %} {% if not nxt.found and next_item.trigger == 'time' %} {% set next_dt = today_at(next_item.at) + timedelta(days=day_offset) %} {% if next_dt <= event_dt %} {% set next_dt = next_dt + timedelta(days=1) %} {% endif %} {% set nxt.dt = next_dt %} {% set nxt.found = true %} {% endif %} {% endfor %} {% if nxt.found and event_dt >= nxt.dt %} {% set valid.ok = false %} {% endif %} {% endif %} {% set event_ts = as_timestamp(event_dt, 0) %} {% if valid.ok and event_ts <= now_ts and event_ts > ns.best_ts %} {% set ns.best_ts = event_ts %} {% set ns.best_target = item.target_brightness | int(0) %} {% set ns.best_ramp = (item.ramp_minutes | int(0) * 60) %} {% endif %} {% endif %} {% endfor %} {% endfor %} {{ ns.best_target }}|{{ ns.best_ramp }}|{{ ns.best_ts | int(0) }} selected_target: "{{ selected_schedule.split('|')[0] | int(0) }}" selected_ramp_seconds: "{{ selected_schedule.split('|')[1] | int(0) }}" selected_event_ts: "{{ selected_schedule.split('|')[2] | int(0) }}" last_event_ts: "{{ as_timestamp(states('input_datetime.outdoor_lighting_number_16_last_schedule_event'), 0) | int(0) }}" do_force: "{{ force_reconcile | default(false) | bool }}" requested_ramp_seconds: "{{ ramp_seconds | default(-1) | int(-1) }}" ramp_to_use: > {% if requested_ramp_seconds | int(-1) >= 0 %} {{ requested_ramp_seconds | int(0) }} {% elif do_force | bool %} 0 {% else %} {{ selected_ramp_seconds | int(0) }} {% endif %} should_apply: > {{ do_force | bool or (selected_event_ts | int(0)) != (last_event_ts | int(0)) }} - if: - condition: template value_template: "{{ should_apply | bool }}" then: - action: input_datetime.set_datetime target: entity_id: input_datetime.outdoor_lighting_number_16_last_schedule_event data: datetime: "{{ (selected_event_ts | int(0)) | timestamp_custom('%Y-%m-%d %H:%M:%S', true) }}" - action: script.outdoor_lighting_ramp_number_16 data: target_brightness: "{{ selected_target | int(0) }}" ramp_seconds: "{{ ramp_to_use | int(0) }}" ############################################################################# # 4) Main Entrance Outside LED Lights # Brightness: number.front_entranceway_led_light_controls_main_entrance_outside_led_lights_brightness # Switch: switch.front_entranceway_led_light_controls_main_entrance_outside_led_lights # Override: binary_sensor.esp_entrancelightswitch_button_2_external_light_controller ############################################################################# outdoor_lighting_apply_main_entrance_current_target: alias: Outdoor Lighting - Apply Current Target - Main Entrance Outside LEDs mode: restart fields: force_reconcile: description: Force immediate reconciliation even if the schedule event has already been applied. ramp_seconds: description: Optional ramp seconds override. Use -1 or omit to use the schedule ramp. sequence: - condition: state entity_id: input_boolean.outdoor_lighting_automation_enable state: "on" - condition: state entity_id: input_boolean.outdoor_lighting_main_entrance_automation_enable state: "on" - condition: state entity_id: input_select.outdoor_lighting_main_entrance_override_mode state: "none" ########################################################################### # EDIT SCHEDULE HERE # Format: trigger, offset_minutes/at, target_brightness, ramp_minutes # Current schedule: # - sunset -30, 30, 30 # - 20:00, 20, 30 # - 23:00, 0, 30 ########################################################################### - variables: schedule: - trigger: sunset offset_minutes: -30 target_brightness: 30 ramp_minutes: 30 - trigger: time at: "20:00:00" target_brightness: 20 ramp_minutes: 30 - trigger: time at: "23:00:00" target_brightness: 0 ramp_minutes: 30 - variables: suppress_active: > {% set ts = as_timestamp(states('input_datetime.outdoor_lighting_main_entrance_suppress_until'), 0) %} {{ ts > as_timestamp(now()) }} selected_schedule: > {% set now_ts = as_timestamp(now()) %} {% set next_setting_raw = state_attr('sun.sun', 'next_setting') %} {% if next_setting_raw %} {% set next_setting = as_datetime(next_setting_raw) | as_local %} {% if next_setting.date() == now().date() %} {% set today_sunset = next_setting %} {% else %} {% set today_sunset = next_setting - timedelta(days=1) %} {% endif %} {% else %} {% set today_sunset = none %} {% endif %} {% set ns = namespace(best_ts=0, best_target=0, best_ramp=0) %} {% for day_offset in [-1, 0] %} {% for item in schedule %} {% if item.trigger == 'sunset' and today_sunset %} {% set event_dt = today_sunset + timedelta(days=day_offset) + timedelta(minutes=item.offset_minutes | int(0)) %} {% elif item.trigger == 'time' %} {% set event_dt = today_at(item.at) + timedelta(days=day_offset) %} {% else %} {% set event_dt = none %} {% endif %} {% if event_dt %} {% set valid = namespace(ok=true) %} {% if item.trigger == 'sunset' %} {% set idx = loop.index0 %} {% set nxt = namespace(found=false, dt=none) %} {% for step in range(1, schedule | length) %} {% set j = (idx + step) % (schedule | length) %} {% set next_item = schedule[j] %} {% if not nxt.found and next_item.trigger == 'time' %} {% set next_dt = today_at(next_item.at) + timedelta(days=day_offset) %} {% if next_dt <= event_dt %} {% set next_dt = next_dt + timedelta(days=1) %} {% endif %} {% set nxt.dt = next_dt %} {% set nxt.found = true %} {% endif %} {% endfor %} {% if nxt.found and event_dt >= nxt.dt %} {% set valid.ok = false %} {% endif %} {% endif %} {% set event_ts = as_timestamp(event_dt, 0) %} {% if valid.ok and event_ts <= now_ts and event_ts > ns.best_ts %} {% set ns.best_ts = event_ts %} {% set ns.best_target = item.target_brightness | int(0) %} {% set ns.best_ramp = (item.ramp_minutes | int(0) * 60) %} {% endif %} {% endif %} {% endfor %} {% endfor %} {{ ns.best_target }}|{{ ns.best_ramp }}|{{ ns.best_ts | int(0) }} selected_target: > {% if suppress_active | bool %} 0 {% else %} {{ selected_schedule.split('|')[0] | int(0) }} {% endif %} selected_ramp_seconds: "{{ selected_schedule.split('|')[1] | int(0) }}" selected_event_ts: "{{ selected_schedule.split('|')[2] | int(0) }}" last_event_ts: "{{ as_timestamp(states('input_datetime.outdoor_lighting_main_entrance_last_schedule_event'), 0) | int(0) }}" do_force: "{{ force_reconcile | default(false) | bool }}" requested_ramp_seconds: "{{ ramp_seconds | default(-1) | int(-1) }}" ramp_to_use: > {% if requested_ramp_seconds | int(-1) >= 0 %} {{ requested_ramp_seconds | int(0) }} {% elif do_force | bool %} 0 {% else %} {{ selected_ramp_seconds | int(0) }} {% endif %} ramp_script_active: "{{ is_state('script.outdoor_lighting_ramp_main_entrance', 'on') }}" should_apply: > {% if suppress_active | bool and ramp_script_active | bool %} false {% else %} {{ do_force | bool or suppress_active | bool or (selected_event_ts | int(0)) != (last_event_ts | int(0)) }} {% endif %} - if: - condition: template value_template: "{{ should_apply | bool }}" then: - if: - condition: template value_template: "{{ not (suppress_active | bool) }}" then: - action: input_datetime.set_datetime target: entity_id: input_datetime.outdoor_lighting_main_entrance_last_schedule_event data: datetime: "{{ (selected_event_ts | int(0)) | timestamp_custom('%Y-%m-%d %H:%M:%S', true) }}" - action: script.outdoor_lighting_ramp_main_entrance data: target_brightness: "{{ selected_target | int(0) }}" ramp_seconds: "{{ ramp_to_use | int(0) }}" ############################################################################# # Reconcile all ############################################################################# outdoor_lighting_reconcile_all: alias: Outdoor Lighting - Reconcile All Outdoor Lights mode: restart fields: force_reconcile: description: Force dimmable lights to reconcile immediately. sequence: - action: script.outdoor_lighting_reconcile_deck_coloured - action: script.outdoor_lighting_reconcile_patio - action: script.outdoor_lighting_apply_number_16_current_target data: force_reconcile: "{{ force_reconcile | default(false) | bool }}" ramp_seconds: > {% if force_reconcile | default(false) | bool %} 0 {% else %} -1 {% endif %} - action: script.outdoor_lighting_apply_main_entrance_current_target data: force_reconcile: "{{ force_reconcile | default(false) | bool }}" ramp_seconds: > {% if force_reconcile | default(false) | bool %} 0 {% else %} -1 {% endif %} automation: ############################################################################# # Startup, scheduler, and enable switch cleanup ############################################################################# - id: outdoor_lighting_reconcile_on_start alias: Outdoor Lighting - Reconcile On Home Assistant Start mode: single trigger: - platform: homeassistant event: start action: - delay: "00:00:30" - action: input_select.select_option target: entity_id: input_select.outdoor_lighting_main_entrance_override_mode data: option: none - action: timer.cancel target: entity_id: - timer.outdoor_lighting_deck_manual_on - timer.outdoor_lighting_patio_manual_on - timer.outdoor_lighting_main_entrance_override - action: script.outdoor_lighting_reconcile_all data: force_reconcile: true - id: outdoor_lighting_scheduler_tick alias: Outdoor Lighting - Scheduler Tick mode: single trigger: - platform: time_pattern minutes: "/5" condition: - condition: state entity_id: input_boolean.outdoor_lighting_automation_enable state: "on" action: - action: script.outdoor_lighting_reconcile_all data: force_reconcile: false - id: outdoor_lighting_reconcile_when_enable_changes alias: Outdoor Lighting - Reconcile When Automation Enable Changes mode: queued trigger: - platform: state entity_id: - input_boolean.outdoor_lighting_automation_enable - input_boolean.outdoor_lighting_deck_coloured_automation_enable - input_boolean.outdoor_lighting_patio_automation_enable - input_boolean.outdoor_lighting_number_16_automation_enable - input_boolean.outdoor_lighting_main_entrance_automation_enable to: "on" action: - action: script.outdoor_lighting_reconcile_all data: force_reconcile: true - id: outdoor_lighting_cleanup_when_master_disabled alias: Outdoor Lighting - Cleanup When Master Automation Disabled mode: single trigger: - platform: state entity_id: input_boolean.outdoor_lighting_automation_enable to: "off" action: - action: script.turn_off target: entity_id: - script.outdoor_lighting_ramp_number_16 - script.outdoor_lighting_ramp_main_entrance - script.outdoor_lighting_apply_number_16_current_target - script.outdoor_lighting_apply_main_entrance_current_target - action: timer.cancel target: entity_id: - timer.outdoor_lighting_deck_manual_on - timer.outdoor_lighting_patio_manual_on - timer.outdoor_lighting_main_entrance_override - action: input_select.select_option target: entity_id: input_select.outdoor_lighting_main_entrance_override_mode data: option: none ############################################################################# # Switched light override buttons ############################################################################# - id: outdoor_lighting_deck_coloured_override_button alias: Outdoor Lighting - Deck Coloured Lights - Override Button mode: restart trigger: - platform: state entity_id: binary_sensor.esp_downstloungeoutside_button_1_outside_lights to: "on" condition: - condition: state entity_id: input_boolean.outdoor_lighting_automation_enable state: "on" - condition: state entity_id: input_boolean.outdoor_lighting_deck_coloured_automation_enable state: "on" action: - variables: was_on_at_press: "{{ is_state('switch.esp_downstloungeoutside_relay_1_outside_lights', 'on') }}" - delay: "00:00:01" - choose: - conditions: - condition: template value_template: "{{ was_on_at_press | bool }}" sequence: - action: input_datetime.set_datetime target: entity_id: input_datetime.outdoor_lighting_deck_suppress_until data: datetime: >- {%- set next_setting_raw = state_attr('sun.sun', 'next_setting') -%} {%- set next_setting = as_datetime(next_setting_raw) | as_local -%} {%- if next_setting.date() == now().date() -%} {%- set today_sunset = next_setting -%} {%- else -%} {%- set today_sunset = next_setting - timedelta(days=1) -%} {%- endif -%} {{ (today_sunset + timedelta(days=1, minutes=60)).strftime('%Y-%m-%d %H:%M:%S') }} - action: timer.cancel target: entity_id: timer.outdoor_lighting_deck_manual_on - action: switch.turn_off target: entity_id: switch.esp_downstloungeoutside_relay_1_outside_lights default: - action: input_datetime.set_datetime target: entity_id: input_datetime.outdoor_lighting_deck_suppress_until data: datetime: "1970-01-01 00:00:00" - action: input_datetime.set_datetime target: entity_id: input_datetime.outdoor_lighting_deck_manual_on_started data: datetime: "{{ now().strftime('%Y-%m-%d %H:%M:%S') }}" - action: switch.turn_on target: entity_id: switch.esp_downstloungeoutside_relay_1_outside_lights - action: timer.start target: entity_id: timer.outdoor_lighting_deck_manual_on data: duration: "01:00:00" - id: outdoor_lighting_patio_override_button alias: Outdoor Lighting - Patio Lights - Override Button mode: restart trigger: - platform: state entity_id: binary_sensor.esp_garageentrylights_button_2_patio_lights to: "on" condition: - condition: state entity_id: input_boolean.outdoor_lighting_automation_enable state: "on" - condition: state entity_id: input_boolean.outdoor_lighting_patio_automation_enable state: "on" action: - variables: was_on_at_press: "{{ is_state('switch.esp_garageentrylights_relay_2_patio_lights', 'on') }}" - delay: "00:00:01" - choose: - conditions: - condition: template value_template: "{{ was_on_at_press | bool }}" sequence: - action: input_datetime.set_datetime target: entity_id: input_datetime.outdoor_lighting_patio_suppress_until data: datetime: >- {%- set next_setting_raw = state_attr('sun.sun', 'next_setting') -%} {%- set next_setting = as_datetime(next_setting_raw) | as_local -%} {%- if next_setting.date() == now().date() -%} {%- set today_sunset = next_setting -%} {%- else -%} {%- set today_sunset = next_setting - timedelta(days=1) -%} {%- endif -%} {{ (today_sunset + timedelta(days=1, minutes=-30)).strftime('%Y-%m-%d %H:%M:%S') }} - action: timer.cancel target: entity_id: timer.outdoor_lighting_patio_manual_on - action: switch.turn_off target: entity_id: switch.esp_garageentrylights_relay_2_patio_lights default: - action: input_datetime.set_datetime target: entity_id: input_datetime.outdoor_lighting_patio_suppress_until data: datetime: "1970-01-01 00:00:00" - action: input_datetime.set_datetime target: entity_id: input_datetime.outdoor_lighting_patio_manual_on_started data: datetime: "{{ now().strftime('%Y-%m-%d %H:%M:%S') }}" - action: switch.turn_on target: entity_id: switch.esp_garageentrylights_relay_2_patio_lights - action: timer.start target: entity_id: timer.outdoor_lighting_patio_manual_on data: duration: "01:00:00" - id: outdoor_lighting_deck_coloured_manual_on_timer_finished alias: Outdoor Lighting - Deck Coloured Lights - Manual On Timer Finished mode: single trigger: - platform: event event_type: timer.finished event_data: entity_id: timer.outdoor_lighting_deck_manual_on action: - action: input_datetime.set_datetime target: entity_id: input_datetime.outdoor_lighting_deck_manual_on_started data: datetime: "1970-01-01 00:00:00" - action: script.outdoor_lighting_reconcile_deck_coloured - id: outdoor_lighting_patio_manual_on_timer_finished alias: Outdoor Lighting - Patio Lights - Manual On Timer Finished mode: single trigger: - platform: event event_type: timer.finished event_data: entity_id: timer.outdoor_lighting_patio_manual_on action: - action: input_datetime.set_datetime target: entity_id: input_datetime.outdoor_lighting_patio_manual_on_started data: datetime: "1970-01-01 00:00:00" - action: script.outdoor_lighting_reconcile_patio ############################################################################# # Main entrance dimmable override button ############################################################################# - id: outdoor_lighting_main_entrance_override_button alias: Outdoor Lighting - Main Entrance Outside LEDs - Override Button mode: restart trigger: - platform: state entity_id: binary_sensor.esp_entrancelightswitch_button_2_external_light_controller to: "on" condition: - condition: state entity_id: input_boolean.outdoor_lighting_automation_enable state: "on" - condition: state entity_id: input_boolean.outdoor_lighting_main_entrance_automation_enable state: "on" action: - variables: override_ramp_seconds: 60 was_on_at_press: "{{ is_state('switch.front_entranceway_led_light_controls_main_entrance_outside_led_lights', 'on') }}" - delay: "00:00:01" - variables: last_press_age_seconds: > {% set last_ts = as_timestamp(states('input_datetime.outdoor_lighting_main_entrance_last_override_press'), 0) %} {% if last_ts <= 0 %} 999999 {% else %} {{ (as_timestamp(now()) - last_ts) | int(999999) }} {% endif %} - choose: - conditions: - condition: template value_template: > {{ not is_state('input_select.outdoor_lighting_main_entrance_override_mode', 'none') and (last_press_age_seconds | int(999999)) <= 60 }} sequence: - action: timer.cancel target: entity_id: timer.outdoor_lighting_main_entrance_override - action: input_select.select_option target: entity_id: input_select.outdoor_lighting_main_entrance_override_mode data: option: none - action: input_datetime.set_datetime target: entity_id: input_datetime.outdoor_lighting_main_entrance_suppress_until data: datetime: >- {%- set next_setting_raw = state_attr('sun.sun', 'next_setting') -%} {%- set next_setting = as_datetime(next_setting_raw) | as_local -%} {%- if next_setting.date() == now().date() -%} {%- set today_sunset = next_setting -%} {%- else -%} {%- set today_sunset = next_setting - timedelta(days=1) -%} {%- endif -%} {%- set tomorrow_sun_on = today_sunset + timedelta(days=1, minutes=-30) -%} {%- set tomorrow_2000 = today_at('20:00:00') + timedelta(days=1) -%} {%- if tomorrow_sun_on < tomorrow_2000 -%} {{ tomorrow_sun_on.strftime('%Y-%m-%d %H:%M:%S') }} {%- else -%} {{ tomorrow_2000.strftime('%Y-%m-%d %H:%M:%S') }} {%- endif -%} - action: script.outdoor_lighting_ramp_main_entrance data: target_brightness: 0 ramp_seconds: "{{ override_ramp_seconds }}" - conditions: - condition: template value_template: > {{ not is_state('input_select.outdoor_lighting_main_entrance_override_mode', 'none') and (last_press_age_seconds | int(999999)) > 60 and (last_press_age_seconds | int(999999)) <= 3600 }} sequence: - action: timer.cancel target: entity_id: timer.outdoor_lighting_main_entrance_override - action: input_select.select_option target: entity_id: input_select.outdoor_lighting_main_entrance_override_mode data: option: none - action: script.outdoor_lighting_apply_main_entrance_current_target data: force_reconcile: true ramp_seconds: "{{ override_ramp_seconds }}" default: - action: input_datetime.set_datetime target: entity_id: input_datetime.outdoor_lighting_main_entrance_last_override_press data: datetime: "{{ now().strftime('%Y-%m-%d %H:%M:%S') }}" - choose: - conditions: - condition: template value_template: "{{ was_on_at_press | bool }}" sequence: - action: input_select.select_option target: entity_id: input_select.outdoor_lighting_main_entrance_override_mode data: option: return_to_schedule default: - action: input_select.select_option target: entity_id: input_select.outdoor_lighting_main_entrance_override_mode data: option: turn_off - action: script.outdoor_lighting_ramp_main_entrance data: target_brightness: 100 ramp_seconds: "{{ override_ramp_seconds }}" - action: timer.start target: entity_id: timer.outdoor_lighting_main_entrance_override data: duration: "01:00:00" - id: outdoor_lighting_main_entrance_override_timer_finished alias: Outdoor Lighting - Main Entrance Outside LEDs - Override Timer Finished mode: single trigger: - platform: event event_type: timer.finished event_data: entity_id: timer.outdoor_lighting_main_entrance_override condition: - condition: state entity_id: input_boolean.outdoor_lighting_automation_enable state: "on" - condition: state entity_id: input_boolean.outdoor_lighting_main_entrance_automation_enable state: "on" action: - variables: override_ramp_seconds: 60 mode_at_finish: "{{ states('input_select.outdoor_lighting_main_entrance_override_mode') }}" - action: input_select.select_option target: entity_id: input_select.outdoor_lighting_main_entrance_override_mode data: option: none - choose: - conditions: - condition: template value_template: "{{ mode_at_finish == 'return_to_schedule' }}" sequence: - action: script.outdoor_lighting_apply_main_entrance_current_target data: force_reconcile: true ramp_seconds: "{{ override_ramp_seconds }}" - conditions: - condition: template value_template: "{{ mode_at_finish == 'turn_off' }}" sequence: - action: script.outdoor_lighting_ramp_main_entrance data: target_brightness: 0 ramp_seconds: "{{ override_ramp_seconds }}"