Fix multiple mode:restart state machine bugs in Motion Light
- Fix brightness threshold falsely resetting state to NONE during light transition ramp-up (brightness temporarily below threshold != light off) - Fix false manual override from Zigbee attribute-only updates (on→on) by requiring meaningful state change (on→off or off→on) - Fix disable guard to also accept ENABLING state (not just ENABLED) - Move state updates before service calls to survive mode:restart cancellation - Add ENABLING sub-case handler for motion-cleared-during-enable scenario - Add CASE 1 default handler for restart recovery disable path - Add comprehensive debug logging at automation entry and CASE 1 entry - Change default timeout_delay to 0 and grace_period to 2s - Remove unused is_debug/is_base_debug variables Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -141,7 +141,7 @@ blueprint:
|
|||||||
description: >
|
description: >
|
||||||
Delay before turning off the light after all motion sensors
|
Delay before turning off the light after all motion sensors
|
||||||
clear. Set to 0 for immediate turn off.
|
clear. Set to 0 for immediate turn off.
|
||||||
default: 120
|
default: 0
|
||||||
selector:
|
selector:
|
||||||
number:
|
number:
|
||||||
min: 0
|
min: 0
|
||||||
@@ -393,7 +393,7 @@ blueprint:
|
|||||||
Some devices (especially Zigbee) report delayed state updates
|
Some devices (especially Zigbee) report delayed state updates
|
||||||
that can be mistaken for manual control. Increase this value
|
that can be mistaken for manual control. Increase this value
|
||||||
if you see false manual overrides in the debug log.
|
if you see false manual overrides in the debug log.
|
||||||
default: 10
|
default: 2
|
||||||
selector:
|
selector:
|
||||||
number:
|
number:
|
||||||
min: 0
|
min: 0
|
||||||
@@ -527,12 +527,6 @@ condition: !input user_condition
|
|||||||
# =============================================================================
|
# =============================================================================
|
||||||
variables:
|
variables:
|
||||||
|
|
||||||
# ---------------------------------------------------------------------------
|
|
||||||
# Debug Flags
|
|
||||||
# ---------------------------------------------------------------------------
|
|
||||||
is_debug: false # Detailed debug for specific actions
|
|
||||||
is_base_debug: false # Basic debug info at start
|
|
||||||
|
|
||||||
# ---------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------
|
||||||
# State Machine Constants
|
# State Machine Constants
|
||||||
# ---------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------
|
||||||
@@ -826,7 +820,7 @@ variables:
|
|||||||
# Should we disable the light? (Motion cleared OR condition switch turned off)
|
# Should we disable the light? (Motion cleared OR condition switch turned off)
|
||||||
must_be_disabled_preview: >
|
must_be_disabled_preview: >
|
||||||
{{ ((not all_of_condition_switches_on) or motion_all_off) | bool }}
|
{{ ((not all_of_condition_switches_on) or motion_all_off) | bool }}
|
||||||
must_be_disabled_guard: "{{ state_is_enabled }}"
|
must_be_disabled_guard: "{{ state_is_enabled or state_is_enabling }}"
|
||||||
must_be_disabled: >
|
must_be_disabled: >
|
||||||
{{ must_be_disabled_preview and must_be_disabled_guard }}
|
{{ must_be_disabled_preview and must_be_disabled_guard }}
|
||||||
|
|
||||||
@@ -836,22 +830,61 @@ variables:
|
|||||||
action:
|
action:
|
||||||
|
|
||||||
# ---------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------
|
||||||
# DEBUG: Log basic info (enable by setting is_base_debug: true)
|
# DEBUG: Log entry state on every trigger (helps trace mode: restart issues)
|
||||||
# ---------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------
|
||||||
- choose:
|
- choose:
|
||||||
- conditions:
|
- conditions: "{{ enable_debug_notifications }}"
|
||||||
- condition: template
|
|
||||||
value_template: "{{ is_base_debug }}"
|
|
||||||
sequence:
|
sequence:
|
||||||
- service: persistent_notification.create
|
- service: persistent_notification.create
|
||||||
data:
|
data:
|
||||||
title: "Debug Info - Motion Light"
|
title: "Motion Light Debug - ENTRY"
|
||||||
message: >
|
message: >
|
||||||
must_be_enabled_preview: {{ must_be_enabled_preview }},
|
=== Automation Triggered ===
|
||||||
must_be_disabled_preview: {{ must_be_disabled_preview }},
|
Time: {{ now().strftime('%H:%M:%S.%f')[:12] }}
|
||||||
must_be_disabled: {{ must_be_disabled }},
|
Trigger ID: {{ trigger_id }}
|
||||||
must_be_disabled_guard: {{ must_be_disabled_guard }},
|
Trigger Entity: {{ trigger.entity_id | default('N/A') }}
|
||||||
trigger_id: {{ trigger.id }}
|
Trigger From: {{ trigger.from_state.state | default('N/A') }}
|
||||||
|
Trigger To: {{ trigger.to_state.state | default('N/A') }}
|
||||||
|
|
||||||
|
=== State Machine ===
|
||||||
|
State: {{ motion_light_state }} (NONE=0, ENABLED=1, ENABLING=2, MANUAL=3)
|
||||||
|
state_is_none: {{ state_is_none }}
|
||||||
|
state_is_enabling: {{ state_is_enabling }}
|
||||||
|
state_is_enabled: {{ state_is_enabled }}
|
||||||
|
state_is_manual: {{ state_is_manual }}
|
||||||
|
|
||||||
|
=== Decision Variables ===
|
||||||
|
motion_on: {{ motion_on }} ({{ count_of_enabled_sensor }} sensors)
|
||||||
|
all_of_condition_switches_on: {{ all_of_condition_switches_on }}
|
||||||
|
luminance_ok: {{ luminance_ok }}
|
||||||
|
time_condition_ok: {{ time_condition_ok }}
|
||||||
|
any_device_on: {{ any_device_on }}
|
||||||
|
|
||||||
|
=== Enable/Disable Logic ===
|
||||||
|
must_be_enabled_preview: {{ must_be_enabled_preview }}
|
||||||
|
must_be_enabled_guard: {{ must_be_enabled_guard }}
|
||||||
|
must_be_enabled: {{ must_be_enabled }}
|
||||||
|
must_be_disabled_preview: {{ must_be_disabled_preview }}
|
||||||
|
must_be_disabled_guard: {{ must_be_disabled_guard }}
|
||||||
|
must_be_disabled: {{ must_be_disabled }}
|
||||||
|
|
||||||
|
=== Which CASE will match ===
|
||||||
|
CASE 1 (state changed): {{ trigger_id == 'light_state_changed' or trigger_id == 'switch_state_changed' }}
|
||||||
|
CASE 2 (enable): {{ must_be_enabled }}
|
||||||
|
CASE 3 (disable): {{ must_be_disabled }}
|
||||||
|
|
||||||
|
=== Grace Period ===
|
||||||
|
{%- set last_ts = automation_state.get(state_motion_light_last_action_timestamp, none) -%}
|
||||||
|
{%- set grace = (transition_duration | float(0)) + (manual_override_grace_period | float(2)) -%}
|
||||||
|
last_action_ts: {{ last_ts | default('not set') }}
|
||||||
|
grace_period: {{ grace }}s
|
||||||
|
{%- if last_ts is not none -%}
|
||||||
|
{%- set parsed = last_ts | as_datetime -%}
|
||||||
|
{%- if parsed is not none %}
|
||||||
|
time_since_action: {{ (now() - parsed).total_seconds() | round(2) }}s
|
||||||
|
grace_expired: {{ (now() - parsed).total_seconds() > grace }}
|
||||||
|
{%- endif -%}
|
||||||
|
{%- endif %}
|
||||||
|
|
||||||
# ===========================================================================
|
# ===========================================================================
|
||||||
# MAIN STATE MACHINE
|
# MAIN STATE MACHINE
|
||||||
@@ -867,6 +900,26 @@ action:
|
|||||||
- condition: template
|
- condition: template
|
||||||
value_template: "{{ trigger_id == 'light_state_changed' or trigger_id == 'switch_state_changed' }}"
|
value_template: "{{ trigger_id == 'light_state_changed' or trigger_id == 'switch_state_changed' }}"
|
||||||
sequence:
|
sequence:
|
||||||
|
# Debug: log which CASE 1 sub-case will fire
|
||||||
|
- choose:
|
||||||
|
- conditions: "{{ enable_debug_notifications }}"
|
||||||
|
sequence:
|
||||||
|
- service: persistent_notification.create
|
||||||
|
data:
|
||||||
|
title: "Motion Light Debug - CASE 1"
|
||||||
|
message: >
|
||||||
|
CASE 1: light/switch state changed
|
||||||
|
Time: {{ now().strftime('%H:%M:%S.%f')[:12] }}
|
||||||
|
Trigger: {{ trigger_id }}
|
||||||
|
Entity: {{ trigger.entity_id | default('N/A') }}
|
||||||
|
From: {{ trigger.from_state.state | default('N/A') }} → To: {{ trigger.to_state.state | default('N/A') }}
|
||||||
|
Meaningful change: {{ trigger.from_state.state != trigger.to_state.state }}
|
||||||
|
Light brightness: {{ state_attr(reference_light, 'brightness') | default('N/A') }}
|
||||||
|
State: {{ motion_light_state }}
|
||||||
|
state_is_enabling: {{ state_is_enabling }}
|
||||||
|
state_is_enabled: {{ state_is_enabled }}
|
||||||
|
must_be_disabled_preview: {{ must_be_disabled_preview }}
|
||||||
|
|
||||||
- choose:
|
- choose:
|
||||||
|
|
||||||
# ----- Sub-case: Light/Switch turned OFF -----
|
# ----- Sub-case: Light/Switch turned OFF -----
|
||||||
@@ -874,12 +927,13 @@ action:
|
|||||||
- conditions:
|
- conditions:
|
||||||
- condition: template
|
- condition: template
|
||||||
value_template: >
|
value_template: >
|
||||||
{# BUG FIX: Changed from 'res = false' to 'res = true' for AND logic #}
|
{# Check actual on/off state only — do NOT use brightness_threshold here.
|
||||||
|
Brightness threshold is for the enable guard (any_device_on), not for
|
||||||
|
detecting whether the light was actually turned off. During transitions,
|
||||||
|
brightness may temporarily be below threshold while the light is still on. #}
|
||||||
{% set res = true %}
|
{% set res = true %}
|
||||||
{% if light_entity is not none %}
|
{% if light_entity is not none %}
|
||||||
{% set brightness = state_attr(light_entity, 'brightness') | int(0) %}
|
{% set res = res and is_state(light_entity, 'off') %}
|
||||||
{% set light_off = is_state(light_entity, 'off') or brightness < brightness_threshold %}
|
|
||||||
{% set res = res and light_off %}
|
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% if switch_entity is not none %}
|
{% if switch_entity is not none %}
|
||||||
{% set res = res and is_state(switch_entity, 'off') %}
|
{% set res = res and is_state(switch_entity, 'off') %}
|
||||||
@@ -898,34 +952,116 @@ action:
|
|||||||
{{ automation_state_global | combine({ automation_state_key: new_automation_state }) | tojson }}
|
{{ automation_state_global | combine({ automation_state_key: new_automation_state }) | tojson }}
|
||||||
|
|
||||||
# ----- Sub-case: Automation just turned on the light -----
|
# ----- Sub-case: Automation just turned on the light -----
|
||||||
# Transition from ENABLING to ENABLED
|
# Transition from ENABLING to ENABLED, or disable immediately
|
||||||
|
# if motion already cleared during the ENABLING phase
|
||||||
- conditions:
|
- conditions:
|
||||||
- condition: template
|
- condition: template
|
||||||
value_template: "{{ state_is_enabling }}"
|
value_template: "{{ state_is_enabling }}"
|
||||||
sequence:
|
sequence:
|
||||||
- service: input_text.set_value
|
- choose:
|
||||||
target:
|
# If disable conditions are already met (motion cleared
|
||||||
entity_id: "{{ automation_state_entity }}"
|
# while light was still in ENABLING state), skip ENABLED
|
||||||
data:
|
# and go straight to disable
|
||||||
value: >
|
- conditions:
|
||||||
{% set new_automation_state = (automation_state | combine({ state_motion_light_state: automation_state_enabled })) %}
|
- condition: template
|
||||||
{{ automation_state_global | combine({ automation_state_key: new_automation_state }) | tojson }}
|
value_template: "{{ must_be_disabled_preview }}"
|
||||||
|
sequence:
|
||||||
|
# Reset state to NONE
|
||||||
|
- service: input_text.set_value
|
||||||
|
target:
|
||||||
|
entity_id: "{{ automation_state_entity }}"
|
||||||
|
data:
|
||||||
|
value: >
|
||||||
|
{% set new_automation_state = (automation_state | combine({ state_motion_light_state: automation_state_none })) %}
|
||||||
|
{{ automation_state_global | combine({ automation_state_key: new_automation_state }) | tojson }}
|
||||||
|
|
||||||
|
# Turn OFF or restore lights
|
||||||
|
- choose:
|
||||||
|
- conditions:
|
||||||
|
- condition: template
|
||||||
|
value_template: "{{ resolved_all_lights | length > 0 }}"
|
||||||
|
sequence:
|
||||||
|
- variables:
|
||||||
|
last_brightness: "{{ automation_state.get(state_motion_light_last_brightness, 0) | int }}"
|
||||||
|
- choose:
|
||||||
|
- conditions:
|
||||||
|
- condition: template
|
||||||
|
value_template: "{{ last_brightness > 0 }}"
|
||||||
|
sequence:
|
||||||
|
- service: light.turn_on
|
||||||
|
target:
|
||||||
|
entity_id: "{{ resolved_all_lights }}"
|
||||||
|
data:
|
||||||
|
brightness: "{{ last_brightness }}"
|
||||||
|
transition: "{{ transition_duration }}"
|
||||||
|
default:
|
||||||
|
- service: light.turn_off
|
||||||
|
target:
|
||||||
|
entity_id: "{{ resolved_all_lights }}"
|
||||||
|
data:
|
||||||
|
transition: "{{ transition_duration }}"
|
||||||
|
|
||||||
|
# Turn OFF switches
|
||||||
|
- choose:
|
||||||
|
- conditions:
|
||||||
|
- condition: template
|
||||||
|
value_template: "{{ resolved_all_switches | length > 0 }}"
|
||||||
|
sequence:
|
||||||
|
- service: switch.turn_off
|
||||||
|
target:
|
||||||
|
entity_id: "{{ resolved_all_switches }}"
|
||||||
|
|
||||||
|
# Execute disable callback
|
||||||
|
- choose:
|
||||||
|
- conditions:
|
||||||
|
- condition: template
|
||||||
|
value_template: "{{ disable_action != [] }}"
|
||||||
|
sequence: !input disable_action
|
||||||
|
|
||||||
|
# Debug notification
|
||||||
|
- choose:
|
||||||
|
- conditions: "{{ enable_debug_notifications }}"
|
||||||
|
sequence:
|
||||||
|
- service: persistent_notification.create
|
||||||
|
data:
|
||||||
|
title: "Motion Light Debug"
|
||||||
|
message: >
|
||||||
|
Action: DISABLE (enabling interrupted)
|
||||||
|
Time: {{ now().strftime('%H:%M:%S') }}
|
||||||
|
Trigger: {{ trigger_id }}
|
||||||
|
|
||||||
|
# Normal case: motion still active, transition to ENABLED
|
||||||
|
default:
|
||||||
|
- service: input_text.set_value
|
||||||
|
target:
|
||||||
|
entity_id: "{{ automation_state_entity }}"
|
||||||
|
data:
|
||||||
|
value: >
|
||||||
|
{% set new_automation_state = (automation_state | combine({ state_motion_light_state: automation_state_enabled })) %}
|
||||||
|
{{ automation_state_global | combine({ automation_state_key: new_automation_state }) | tojson }}
|
||||||
|
|
||||||
# ----- Sub-case: User manually changed the light -----
|
# ----- Sub-case: User manually changed the light -----
|
||||||
# Transition from ENABLED to MANUAL (user took control)
|
# Transition from ENABLED to MANUAL (user took control)
|
||||||
|
# Only triggers on meaningful state changes (on→off or off→on),
|
||||||
|
# NOT on attribute-only updates (on→on) which Zigbee devices
|
||||||
|
# commonly send as the light settles after a transition.
|
||||||
# Grace period: ignore state changes shortly after the automation
|
# Grace period: ignore state changes shortly after the automation
|
||||||
# turns on the light to avoid false manual override detection.
|
# turns on the light to avoid false manual override detection.
|
||||||
# Some devices (especially Zigbee) report delayed state updates.
|
|
||||||
- conditions:
|
- conditions:
|
||||||
- condition: template
|
- condition: template
|
||||||
value_template: >
|
value_template: >
|
||||||
{% set last_ts = automation_state.get(state_motion_light_last_action_timestamp, none) %}
|
{% set meaningful_change = trigger.from_state.state != trigger.to_state.state %}
|
||||||
{% set grace = (transition_duration | float(0)) + (manual_override_grace_period | float(10)) %}
|
{% if not meaningful_change %}
|
||||||
{% if state_is_enabled and last_ts is not none %}
|
{{ false }}
|
||||||
{% set parsed = last_ts | as_datetime %}
|
|
||||||
{{ parsed is none or (now() - parsed).total_seconds() > grace }}
|
|
||||||
{% else %}
|
{% else %}
|
||||||
{{ state_is_enabled }}
|
{% set last_ts = automation_state.get(state_motion_light_last_action_timestamp, none) %}
|
||||||
|
{% set grace = (transition_duration | float(0)) + (manual_override_grace_period | float(2)) %}
|
||||||
|
{% if state_is_enabled and last_ts is not none %}
|
||||||
|
{% set parsed = last_ts | as_datetime %}
|
||||||
|
{{ parsed is none or (now() - parsed).total_seconds() > grace }}
|
||||||
|
{% else %}
|
||||||
|
{{ state_is_enabled }}
|
||||||
|
{% endif %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
sequence:
|
sequence:
|
||||||
# BUG FIX: Fixed YAML structure - was 'data: >' instead of 'data:' with 'value: >'
|
# BUG FIX: Fixed YAML structure - was 'data: >' instead of 'data:' with 'value: >'
|
||||||
@@ -961,6 +1097,81 @@ action:
|
|||||||
New State: MANUAL
|
New State: MANUAL
|
||||||
Trigger: {{ trigger_id }}
|
Trigger: {{ trigger_id }}
|
||||||
|
|
||||||
|
# ----- Default: No sub-case matched -----
|
||||||
|
# This handles the case where a light_state_changed trigger fires
|
||||||
|
# during the grace period (e.g., Zigbee delayed state reports) while
|
||||||
|
# the disable path was already in progress but got cancelled by
|
||||||
|
# mode: restart. If disable conditions are met, turn off directly.
|
||||||
|
default:
|
||||||
|
- choose:
|
||||||
|
- conditions:
|
||||||
|
- condition: template
|
||||||
|
value_template: "{{ (state_is_enabled or state_is_enabling) and must_be_disabled_preview }}"
|
||||||
|
sequence:
|
||||||
|
# Reset state to NONE first (before turn-off triggers another restart)
|
||||||
|
- service: input_text.set_value
|
||||||
|
target:
|
||||||
|
entity_id: "{{ automation_state_entity }}"
|
||||||
|
data:
|
||||||
|
value: >
|
||||||
|
{% set new_automation_state = (automation_state | combine({ state_motion_light_state: automation_state_none })) %}
|
||||||
|
{{ automation_state_global | combine({ automation_state_key: new_automation_state }) | tojson }}
|
||||||
|
|
||||||
|
# Turn OFF or restore lights
|
||||||
|
- choose:
|
||||||
|
- conditions:
|
||||||
|
- condition: template
|
||||||
|
value_template: "{{ resolved_all_lights | length > 0 }}"
|
||||||
|
sequence:
|
||||||
|
- variables:
|
||||||
|
last_brightness: "{{ automation_state.get(state_motion_light_last_brightness, 0) | int }}"
|
||||||
|
- choose:
|
||||||
|
- conditions:
|
||||||
|
- condition: template
|
||||||
|
value_template: "{{ last_brightness > 0 }}"
|
||||||
|
sequence:
|
||||||
|
- service: light.turn_on
|
||||||
|
target:
|
||||||
|
entity_id: "{{ resolved_all_lights }}"
|
||||||
|
data:
|
||||||
|
brightness: "{{ last_brightness }}"
|
||||||
|
transition: "{{ transition_duration }}"
|
||||||
|
default:
|
||||||
|
- service: light.turn_off
|
||||||
|
target:
|
||||||
|
entity_id: "{{ resolved_all_lights }}"
|
||||||
|
data:
|
||||||
|
transition: "{{ transition_duration }}"
|
||||||
|
|
||||||
|
# Turn OFF switches
|
||||||
|
- choose:
|
||||||
|
- conditions:
|
||||||
|
- condition: template
|
||||||
|
value_template: "{{ resolved_all_switches | length > 0 }}"
|
||||||
|
sequence:
|
||||||
|
- service: switch.turn_off
|
||||||
|
target:
|
||||||
|
entity_id: "{{ resolved_all_switches }}"
|
||||||
|
|
||||||
|
# Execute disable callback
|
||||||
|
- choose:
|
||||||
|
- conditions:
|
||||||
|
- condition: template
|
||||||
|
value_template: "{{ disable_action != [] }}"
|
||||||
|
sequence: !input disable_action
|
||||||
|
|
||||||
|
# Debug notification
|
||||||
|
- choose:
|
||||||
|
- conditions: "{{ enable_debug_notifications }}"
|
||||||
|
sequence:
|
||||||
|
- service: persistent_notification.create
|
||||||
|
data:
|
||||||
|
title: "Motion Light Debug"
|
||||||
|
message: >
|
||||||
|
Action: DISABLE (restart recovery)
|
||||||
|
Time: {{ now().strftime('%H:%M:%S') }}
|
||||||
|
Trigger: {{ trigger_id }}
|
||||||
|
|
||||||
# -----------------------------------------------------------------------
|
# -----------------------------------------------------------------------
|
||||||
# CASE 2: Enable Path (Motion Detected, Should Turn On)
|
# CASE 2: Enable Path (Motion Detected, Should Turn On)
|
||||||
# -----------------------------------------------------------------------
|
# -----------------------------------------------------------------------
|
||||||
@@ -988,6 +1199,21 @@ action:
|
|||||||
{{ state_attr(reference_light, 'brightness') | int(0) }}
|
{{ state_attr(reference_light, 'brightness') | int(0) }}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
|
# Update state to ENABLING BEFORE turning on the light.
|
||||||
|
# This must happen first because mode: restart may cancel
|
||||||
|
# subsequent steps if the light state change fires immediately.
|
||||||
|
- service: input_text.set_value
|
||||||
|
target:
|
||||||
|
entity_id: "{{ automation_state_entity }}"
|
||||||
|
data:
|
||||||
|
value: >
|
||||||
|
{% set new_automation_state = (automation_state | combine({
|
||||||
|
state_motion_light_state: automation_state_enabling,
|
||||||
|
state_motion_light_last_action_timestamp: date_time_now,
|
||||||
|
state_motion_light_last_brightness: last_brightness
|
||||||
|
})) %}
|
||||||
|
{{ automation_state_global | combine({ automation_state_key: new_automation_state }) | tojson }}
|
||||||
|
|
||||||
# Scene activation path
|
# Scene activation path
|
||||||
- choose:
|
- choose:
|
||||||
- conditions:
|
- conditions:
|
||||||
@@ -1028,19 +1254,6 @@ action:
|
|||||||
target:
|
target:
|
||||||
entity_id: "{{ resolved_all_switches }}"
|
entity_id: "{{ resolved_all_switches }}"
|
||||||
|
|
||||||
# Update state to ENABLING (waiting for light state change confirmation)
|
|
||||||
- service: input_text.set_value
|
|
||||||
target:
|
|
||||||
entity_id: "{{ automation_state_entity }}"
|
|
||||||
data:
|
|
||||||
value: >
|
|
||||||
{% set new_automation_state = (automation_state | combine({
|
|
||||||
state_motion_light_state: automation_state_enabling,
|
|
||||||
state_motion_light_last_action_timestamp: date_time_now,
|
|
||||||
state_motion_light_last_brightness: last_brightness
|
|
||||||
})) %}
|
|
||||||
{{ automation_state_global | combine({ automation_state_key: new_automation_state }) | tojson }}
|
|
||||||
|
|
||||||
# Execute enable callback action
|
# Execute enable callback action
|
||||||
- choose:
|
- choose:
|
||||||
- conditions:
|
- conditions:
|
||||||
@@ -1114,15 +1327,49 @@ action:
|
|||||||
- delay:
|
- delay:
|
||||||
seconds: "{{ dim_duration }}"
|
seconds: "{{ dim_duration }}"
|
||||||
|
|
||||||
|
# Read last_brightness before resetting state
|
||||||
|
- variables:
|
||||||
|
last_brightness: "{{ automation_state.get(state_motion_light_last_brightness, 0) | int }}"
|
||||||
|
|
||||||
|
# Update state to NONE BEFORE turning off the light.
|
||||||
|
# This must happen first because mode: restart may cancel
|
||||||
|
# subsequent steps if the light state change fires during
|
||||||
|
# turn_off transition, which could falsely trigger manual override.
|
||||||
|
- service: input_text.set_value
|
||||||
|
target:
|
||||||
|
entity_id: "{{ automation_state_entity }}"
|
||||||
|
data:
|
||||||
|
value: >
|
||||||
|
{% set new_automation_state = (automation_state | combine({ state_motion_light_state: automation_state_none })) %}
|
||||||
|
{{ automation_state_global | combine({ automation_state_key: new_automation_state }) | tojson }}
|
||||||
|
|
||||||
|
# Execute disable callback action (before turn-off to avoid restart cancellation)
|
||||||
|
- choose:
|
||||||
|
- conditions:
|
||||||
|
- condition: template
|
||||||
|
value_template: "{{ disable_action != [] }}"
|
||||||
|
sequence: !input disable_action
|
||||||
|
|
||||||
|
# Debug notification (before turn-off to avoid restart cancellation)
|
||||||
|
- choose:
|
||||||
|
- conditions: "{{ enable_debug_notifications }}"
|
||||||
|
sequence:
|
||||||
|
- service: persistent_notification.create
|
||||||
|
data:
|
||||||
|
title: "Motion Light Debug"
|
||||||
|
message: >
|
||||||
|
Action: DISABLE
|
||||||
|
Time: {{ now().strftime('%H:%M:%S') }}
|
||||||
|
Timeout: {{ timeout }}s
|
||||||
|
Min On Duration: {{ min_on_duration }}s
|
||||||
|
Dim Before Off: {{ enable_dim_before_off }}
|
||||||
|
|
||||||
# Turn OFF or restore the lights
|
# Turn OFF or restore the lights
|
||||||
- choose:
|
- choose:
|
||||||
- conditions:
|
- conditions:
|
||||||
- condition: template
|
- condition: template
|
||||||
value_template: "{{ resolved_all_lights | length > 0 }}"
|
value_template: "{{ resolved_all_lights | length > 0 }}"
|
||||||
sequence:
|
sequence:
|
||||||
- variables:
|
|
||||||
last_brightness: "{{ automation_state.get(state_motion_light_last_brightness, 0) | int }}"
|
|
||||||
|
|
||||||
- choose:
|
- choose:
|
||||||
# Restore previous brightness if it was set
|
# Restore previous brightness if it was set
|
||||||
- conditions:
|
- conditions:
|
||||||
@@ -1153,33 +1400,3 @@ action:
|
|||||||
- service: switch.turn_off
|
- service: switch.turn_off
|
||||||
target:
|
target:
|
||||||
entity_id: "{{ resolved_all_switches }}"
|
entity_id: "{{ resolved_all_switches }}"
|
||||||
|
|
||||||
# Update state to NONE (ready for next motion event)
|
|
||||||
- service: input_text.set_value
|
|
||||||
target:
|
|
||||||
entity_id: "{{ automation_state_entity }}"
|
|
||||||
data:
|
|
||||||
value: >
|
|
||||||
{% set new_automation_state = (automation_state | combine({ state_motion_light_state: automation_state_none })) %}
|
|
||||||
{{ automation_state_global | combine({ automation_state_key: new_automation_state }) | tojson }}
|
|
||||||
|
|
||||||
# Execute disable callback action
|
|
||||||
- choose:
|
|
||||||
- conditions:
|
|
||||||
- condition: template
|
|
||||||
value_template: "{{ disable_action != [] }}"
|
|
||||||
sequence: !input disable_action
|
|
||||||
|
|
||||||
# Debug notification
|
|
||||||
- choose:
|
|
||||||
- conditions: "{{ enable_debug_notifications }}"
|
|
||||||
sequence:
|
|
||||||
- service: persistent_notification.create
|
|
||||||
data:
|
|
||||||
title: "Motion Light Debug"
|
|
||||||
message: >
|
|
||||||
Action: DISABLE
|
|
||||||
Time: {{ now().strftime('%H:%M:%S') }}
|
|
||||||
Timeout: {{ timeout }}s
|
|
||||||
Min On Duration: {{ min_on_duration }}s
|
|
||||||
Dim Before Off: {{ enable_dim_before_off }}
|
|
||||||
|
|||||||
@@ -1,3 +1,3 @@
|
|||||||
{
|
{
|
||||||
"version": "2.2.3"
|
"version": "2.2.9"
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user