Last active
December 25, 2025 18:06
-
-
Save yougotborked/5f3270e8fc6da9f82f72832cc48946a1 to your computer and use it in GitHub Desktop.
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
| # Tuya Zigbee Siren/Doorbell (ZHA) — Use the *existing HA entities* (select/number/siren/switch) | |
| # | |
| # This version does NOT write raw Zigbee datapoints. | |
| # It uses the same entity interfaces you see on the device page: | |
| # - select.<device>_alarm_volume | |
| # - select.<device>_alarm_ringtone | |
| # - number.<device>_alarm_duration | |
| # - siren.<device> (or switch.<device>) | |
| # | |
| # Why your original blueprint likely failed: | |
| # - The template used `is search(p, ignorecase=True)` which HA’s template test doesn’t support. | |
| # That can cause template rendering errors and prevent the select from being called. | |
| # - Even when it renders, option labels often don’t match `Low/Medium/High` exactly. | |
| # | |
| # How to handle “wrong ringtone names/order” while still using the UI entities: | |
| # - Create an `input_select` helper and copy/paste the *exact* options from your device’s | |
| # select.<device>_alarm_ringtone attributes. | |
| # - In this blueprint, you pick that helper; the script forwards the helper’s current value | |
| # to the device select entity. This way you’re selecting the same “item” you would pick | |
| # manually, even if the labels are nonsense. | |
| blueprint: | |
| name: Tuya Zigbee Siren/Doorbell (Entity Interface) | |
| description: "Set Tuya Zigbee siren volume/ringtone/duration using the existing ZHA entities, then activate." | |
| domain: script | |
| input: | |
| devices: | |
| name: Tuya Zigbee Devices (ZHA) | |
| selector: | |
| device: | |
| integration: zha | |
| multiple: true | |
| # Optional: drive ringtone selection from a helper so you can use the *device’s own* option labels. | |
| ringtone_helper: | |
| name: Ringtone helper (input_select) | |
| description: "Optional. Pick an input_select whose options match the siren's ringtone list. The helper's state is forwarded to select.*_alarm_ringtone." | |
| default: "" | |
| selector: | |
| entity: | |
| domain: input_select | |
| # Optional: same idea for volume (only needed if the device's volume labels aren't Low/Medium/High). | |
| volume_helper: | |
| name: Volume helper (input_select) | |
| description: "Optional. If set, forwards helper state to select.*_alarm_volume. Otherwise maps Low/Medium/High to the device options by position (0/1/2) or label match." | |
| default: "" | |
| selector: | |
| entity: | |
| domain: input_select | |
| melody: | |
| name: Ringtone choice (fallback) | |
| description: "Used only if ringtone_helper is not provided. Must match an option on the device (exact text)." | |
| default: "" | |
| selector: | |
| text: {} | |
| volume: | |
| name: Volume (fallback) | |
| description: "Used only if volume_helper is not provided." | |
| default: Medium | |
| selector: | |
| select: | |
| options: [Low, Medium, High] | |
| duration: | |
| name: Duration (seconds) | |
| default: 15 | |
| selector: | |
| number: | |
| min: 1 | |
| max: 180 | |
| step: 1 | |
| mode: slider | |
| pre_stop: | |
| name: Turn off before configuring | |
| description: "Some Tuya sirens ignore config writes while alarming." | |
| default: true | |
| selector: | |
| boolean: {} | |
| variables: | |
| devices: !input devices | |
| duration_s: !input duration | |
| volume_label: !input volume | |
| ringtone_fallback: !input melody | |
| ringtone_helper: !input ringtone_helper | |
| volume_helper: !input volume_helper | |
| pre_stop: !input pre_stop | |
| sequence: | |
| - repeat: | |
| for_each: "{{ devices | list }}" | |
| sequence: | |
| - variables: | |
| ents: "{{ device_entities(repeat.item) | list }}" | |
| number_ents: "{{ ents | select('search','^number\\.') | list }}" | |
| select_ents: "{{ ents | select('search','^select\\.') | list }}" | |
| switch_ents: "{{ ents | select('search','^switch\\.') | list }}" | |
| siren_ents: "{{ ents | select('search','^siren\\.') | list }}" | |
| vol_entity: > | |
| {% set c = select_ents | select('search', '_alarm_volume$') | list %} | |
| {{ c[0] if c|length>0 else '' }} | |
| ring_entity: > | |
| {% set c = select_ents | select('search', '_alarm_ringtone$') | list %} | |
| {{ c[0] if c|length>0 else '' }} | |
| dur_entity: > | |
| {% set c = number_ents | select('search', '_alarm_duration$') | list %} | |
| {{ c[0] if c|length>0 else '' }} | |
| siren_entity: "{{ siren_ents[0] if siren_ents|length>0 else '' }}" | |
| sw_entity: > | |
| {% set pref = switch_ents | select('search','siren|alarm|doorbell|buzzer') | list %} | |
| {{ pref[0] if pref|length>0 else (switch_ents[0] if switch_ents|length>0 else '') }} | |
| # Resolve volume option: | |
| # 1) If volume_helper provided, use its state verbatim. | |
| # 2) Else try label match (case-insensitive). | |
| # 3) Else fall back by index (Low=0, Medium=1, High=2). | |
| resolved_volume: > | |
| {% if not vol_entity %}{{ '' }} | |
| {% else %} | |
| {% set opts = (state_attr(vol_entity, 'options') or []) | list %} | |
| {% if volume_helper %} | |
| {{ states(volume_helper) }} | |
| {% else %} | |
| {% set want = (volume_label|string)|lower %} | |
| {% set match = '' %} | |
| {% for o in opts %} | |
| {% if not match and ((o|string)|lower == want) %} | |
| {% set match = o %} | |
| {% endif %} | |
| {% endfor %} | |
| {% if not match %} | |
| {% set idx = {'low':0,'medium':1,'high':2}.get(want, 1) %} | |
| {{ opts[idx] if opts|length>idx else (opts[0] if opts|length>0 else '') }} | |
| {% else %} | |
| {{ match }} | |
| {% endif %} | |
| {% endif %} | |
| {% endif %} | |
| # Resolve ringtone option: | |
| # 1) If ringtone_helper provided, use its state verbatim. | |
| # 2) Else use the free-text fallback (must exactly match an option). | |
| resolved_ringtone: > | |
| {% if not ring_entity %}{{ '' }} | |
| {% else %} | |
| {% if ringtone_helper %} | |
| {{ states(ringtone_helper) }} | |
| {% else %} | |
| {{ ringtone_fallback }} | |
| {% endif %} | |
| {% endif %} | |
| # Optional: stop first (helps when the device ignores config writes while active) | |
| - choose: | |
| - conditions: "{{ pre_stop }}" | |
| sequence: | |
| - choose: | |
| - conditions: "{{ siren_entity|length > 0 }}" | |
| sequence: | |
| - service: siren.turn_off | |
| target: { entity_id: "{{ siren_entity }}" } | |
| - conditions: "{{ siren_entity|length == 0 and sw_entity|length > 0 }}" | |
| sequence: | |
| - service: switch.turn_off | |
| target: { entity_id: "{{ sw_entity }}" } | |
| - delay: "00:00:00.30" | |
| # Duration | |
| - choose: | |
| - conditions: "{{ dur_entity|length > 0 }}" | |
| sequence: | |
| - service: number.set_value | |
| target: { entity_id: "{{ dur_entity }}" } | |
| data: | |
| value: "{{ duration_s | float }}" | |
| - delay: "00:00:00.20" | |
| # Volume | |
| - choose: | |
| - conditions: "{{ vol_entity|length > 0 and resolved_volume|length > 0 }}" | |
| sequence: | |
| - service: select.select_option | |
| target: { entity_id: "{{ vol_entity }}" } | |
| data: | |
| option: "{{ resolved_volume }}" | |
| - delay: "00:00:00.20" | |
| # Ringtone | |
| - choose: | |
| - conditions: "{{ ring_entity|length > 0 and resolved_ringtone|length > 0 }}" | |
| sequence: | |
| - service: select.select_option | |
| target: { entity_id: "{{ ring_entity }}" } | |
| data: | |
| option: "{{ resolved_ringtone }}" | |
| - delay: "00:00:00.20" | |
| # Activate | |
| - choose: | |
| - conditions: "{{ siren_entity|length > 0 }}" | |
| sequence: | |
| - service: siren.turn_on | |
| target: { entity_id: "{{ siren_entity }}" } | |
| - conditions: "{{ siren_entity|length == 0 and sw_entity|length > 0 }}" | |
| sequence: | |
| - service: switch.turn_on | |
| target: { entity_id: "{{ sw_entity }}" } | |
| mode: parallel | |
| max_exceeded: silent |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment