Created
April 28, 2026 23:04
-
-
Save Dianoga/9e04441593865810203be36179fefb8e to your computer and use it in GitHub Desktop.
Zigbee2MQTT AM25 Blind Watchdog
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| blueprint: | |
| name: Zigbee2MQTT AM25 Blind Watchdog | |
| description: > | |
| Watches a Zigbee2MQTT AM25 blind command topic. If the cover does not appear | |
| to respond within the timeout, sends a Zigbee2MQTT device reconfigure request | |
| and optionally replays the original command after a successful reconfigure. | |
| domain: automation | |
| author: local | |
| input: | |
| z2m_friendly_name: | |
| name: Zigbee2MQTT friendly name | |
| description: > | |
| The exact Zigbee2MQTT friendly_name for this blind. | |
| Example: Living Room Left Blind | |
| selector: | |
| text: | |
| cover_entity: | |
| name: Home Assistant cover entity | |
| description: > | |
| The Home Assistant cover entity created by Zigbee2MQTT discovery. | |
| selector: | |
| entity: | |
| filter: | |
| domain: cover | |
| command_topic: | |
| name: Blind command topic | |
| description: > | |
| The MQTT topic Home Assistant/Zigbee2MQTT uses to command this blind. | |
| Usually zigbee2mqtt/FRIENDLY_NAME/set. | |
| default: zigbee2mqtt/CHANGE_ME/set | |
| selector: | |
| text: | |
| configure_request_topic: | |
| name: Zigbee2MQTT configure request topic | |
| description: > | |
| Usually zigbee2mqtt/bridge/request/device/configure. | |
| default: zigbee2mqtt/bridge/request/device/configure | |
| selector: | |
| text: | |
| configure_response_topic: | |
| name: Zigbee2MQTT configure response topic | |
| description: > | |
| Usually zigbee2mqtt/bridge/response/device/configure. | |
| default: zigbee2mqtt/bridge/response/device/configure | |
| selector: | |
| text: | |
| response_timeout_seconds: | |
| name: Response timeout | |
| description: > | |
| How long to wait for the blind entity to show movement, target position, | |
| or state change before declaring the command failed. | |
| default: 25 | |
| selector: | |
| number: | |
| min: 5 | |
| max: 180 | |
| step: 1 | |
| unit_of_measurement: seconds | |
| mode: slider | |
| configure_timeout_seconds: | |
| name: Reconfigure timeout | |
| description: > | |
| How long to wait for Zigbee2MQTT to confirm the device reconfigure. | |
| default: 90 | |
| selector: | |
| number: | |
| min: 10 | |
| max: 300 | |
| step: 5 | |
| unit_of_measurement: seconds | |
| mode: slider | |
| movement_threshold: | |
| name: Movement threshold | |
| description: > | |
| Minimum current_position change that counts as a response. | |
| default: 2 | |
| selector: | |
| number: | |
| min: 1 | |
| max: 20 | |
| step: 1 | |
| unit_of_measurement: "%" | |
| mode: slider | |
| position_tolerance: | |
| name: Position tolerance | |
| description: > | |
| How close current_position must be to the target position to count as success. | |
| default: 3 | |
| selector: | |
| number: | |
| min: 0 | |
| max: 15 | |
| step: 1 | |
| unit_of_measurement: "%" | |
| mode: slider | |
| replay_original_command: | |
| name: Replay original command | |
| description: > | |
| After a successful reconfigure, publish the original blind command again. | |
| default: true | |
| selector: | |
| boolean: | |
| watch_stop_commands: | |
| name: Watch STOP commands | |
| description: > | |
| STOP commands are harder to verify because a successful STOP may not | |
| produce a state or position update. Leave this off unless you specifically | |
| want STOP failures to trigger reconfigure. | |
| default: false | |
| selector: | |
| boolean: | |
| notify_on_failure: | |
| name: Notify if recovery fails | |
| description: > | |
| Create a persistent Home Assistant notification if the blind does not | |
| respond and Zigbee2MQTT does not confirm a successful reconfigure. | |
| default: true | |
| selector: | |
| boolean: | |
| mode: single | |
| max_exceeded: silent | |
| triggers: | |
| - trigger: mqtt | |
| topic: !input command_topic | |
| variables: | |
| z2m_friendly_name: !input z2m_friendly_name | |
| cover_entity: !input cover_entity | |
| command_topic: !input command_topic | |
| configure_request_topic: !input configure_request_topic | |
| configure_response_topic: !input configure_response_topic | |
| response_timeout_seconds: !input response_timeout_seconds | |
| configure_timeout_seconds: !input configure_timeout_seconds | |
| movement_threshold: !input movement_threshold | |
| position_tolerance: !input position_tolerance | |
| replay_original_command: !input replay_original_command | |
| watch_stop_commands: !input watch_stop_commands | |
| notify_on_failure: !input notify_on_failure | |
| original_payload: "{{ trigger.payload }}" | |
| command: "{{ trigger.payload_json | default({}) }}" | |
| command_state: "{{ command.state | default('') | upper }}" | |
| is_cover_command: >- | |
| {{ | |
| command.position is defined | |
| or command_state in ['OPEN', 'CLOSE'] | |
| or (watch_stop_commands | bool and command_state == 'STOP') | |
| }} | |
| before_state: "{{ states(cover_entity) }}" | |
| before_position: "{{ state_attr(cover_entity, 'current_position') }}" | |
| target_position: >- | |
| {% if command.position is defined %} | |
| {{ command.position | int }} | |
| {% elif command_state == 'OPEN' %} | |
| 100 | |
| {% elif command_state == 'CLOSE' %} | |
| 0 | |
| {% else %} | |
| {{ none }} | |
| {% endif %} | |
| has_target_position: >- | |
| {{ target_position not in [none, 'none', 'None', ''] }} | |
| already_at_target: >- | |
| {% if has_target_position and before_position is number %} | |
| {{ ((before_position | int) - (target_position | int)) | abs <= (position_tolerance | int) }} | |
| {% else %} | |
| false | |
| {% endif %} | |
| transaction_id: >- | |
| watchdog_{{ z2m_friendly_name | |
| | replace(' ', '_') | |
| | replace('/', '_') | |
| | replace('-', '_') }}_{{ now().timestamp() | int }} | |
| conditions: | |
| - condition: template | |
| value_template: "{{ is_cover_command }}" | |
| # Do not treat "set to the position it is already at" as a failure. | |
| - condition: template | |
| value_template: "{{ not already_at_target }}" | |
| actions: | |
| - action: system_log.write | |
| data: | |
| level: info | |
| message: >- | |
| AM25 watchdog armed for {{ z2m_friendly_name }}. | |
| Command={{ original_payload }}, | |
| before_state={{ before_state }}, | |
| before_position={{ before_position }}, | |
| target_position={{ target_position }}. | |
| - wait_template: >- | |
| {% set cur = state_attr(cover_entity, 'current_position') %} | |
| {% set st = states(cover_entity) %} | |
| {% if command_state == 'STOP' %} | |
| {{ st != before_state }} | |
| {% elif has_target_position and cur is number and before_position is number %} | |
| {{ | |
| ((cur | int) - (target_position | int)) | abs <= (position_tolerance | int) | |
| or | |
| ((cur | int) - (before_position | int)) | abs >= (movement_threshold | int) | |
| }} | |
| {% elif has_target_position and cur is number %} | |
| {{ ((cur | int) - (target_position | int)) | abs <= (position_tolerance | int) }} | |
| {% else %} | |
| {{ st != before_state }} | |
| {% endif %} | |
| timeout: | |
| seconds: "{{ response_timeout_seconds | int }}" | |
| continue_on_timeout: true | |
| - choose: | |
| - conditions: | |
| - condition: template | |
| value_template: "{{ wait.completed }}" | |
| sequence: | |
| - action: system_log.write | |
| data: | |
| level: info | |
| message: >- | |
| AM25 watchdog saw a response from {{ z2m_friendly_name }}; | |
| no reconfigure needed. | |
| default: | |
| - action: system_log.write | |
| data: | |
| level: warning | |
| message: >- | |
| AM25 watchdog timeout for {{ z2m_friendly_name }}; | |
| sending Zigbee2MQTT reconfigure request. | |
| - action: mqtt.publish | |
| data: | |
| topic: "{{ configure_request_topic }}" | |
| payload: >- | |
| {{ | |
| { | |
| "id": z2m_friendly_name, | |
| "transaction": transaction_id | |
| } | to_json | |
| }} | |
| qos: 0 | |
| retain: false | |
| - wait_for_trigger: | |
| - trigger: mqtt | |
| topic: !input configure_response_topic | |
| payload: "{{ transaction_id }}" | |
| value_template: "{{ value_json.transaction | default('') }}" | |
| timeout: | |
| seconds: "{{ configure_timeout_seconds | int }}" | |
| continue_on_timeout: true | |
| - choose: | |
| - conditions: | |
| - condition: template | |
| value_template: >- | |
| {{ | |
| wait.completed | |
| and wait.trigger.payload_json.status | default('') == 'ok' | |
| }} | |
| sequence: | |
| - action: system_log.write | |
| data: | |
| level: warning | |
| message: >- | |
| Zigbee2MQTT reconfigure completed for {{ z2m_friendly_name }}. | |
| - choose: | |
| - conditions: | |
| - condition: template | |
| value_template: "{{ replay_original_command | bool }}" | |
| sequence: | |
| - delay: "00:00:02" | |
| - action: system_log.write | |
| data: | |
| level: warning | |
| message: >- | |
| Replaying original command for {{ z2m_friendly_name }}: | |
| {{ original_payload }} | |
| - action: mqtt.publish | |
| data: | |
| topic: "{{ command_topic }}" | |
| payload: "{{ original_payload }}" | |
| qos: 0 | |
| retain: false | |
| # Keep the automation running briefly so mode: single drops | |
| # the replay-triggered copy of itself. | |
| - delay: "00:00:05" | |
| default: | |
| - action: system_log.write | |
| data: | |
| level: error | |
| message: >- | |
| AM25 watchdog recovery failed for {{ z2m_friendly_name }}. | |
| No successful Zigbee2MQTT configure response was received. | |
| - choose: | |
| - conditions: | |
| - condition: template | |
| value_template: "{{ notify_on_failure | bool }}" | |
| sequence: | |
| - action: persistent_notification.create | |
| data: | |
| title: "Blind watchdog failed" | |
| message: >- | |
| {{ z2m_friendly_name }} did not respond to command | |
| {{ original_payload }}, and Zigbee2MQTT did not confirm | |
| a successful reconfigure within | |
| {{ configure_timeout_seconds }} seconds. |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment