# yamllint disable rule:line-length --- blueprint: name: "AWTRIX 3 DWD weather warnings ⛈️" description: |- **Blueprint version: 2.0.0** Show weather warnings issued by DWD on your AWTRIX device. When upgrading from an earlier unversioned release, restart AWTRIX once to remove the former `jc-23` app after this blueprint publishes `dwd-warnings`. ## ⚠️ REQUIREMENTS ⚠️ You MUST upload the warning icons to your AWTRIX device before using this blueprint. Download and run the upload script: curl -fsSLO https://raw.githubusercontent.com/jc-23/home-assistant/main/icons/upload_icon.sh bash upload_icon.sh CLOCK_HOST_OR_IP A legend for the warning types is available at: https://www.dwd.de/DE/leistungen/opendata/help/warnungen/warning_codes_pdf.pdf?__blob=publicationFile&v=5 Updates for this blueprint: https://raw.githubusercontent.com/jc-23/home-assistant/main/blueprints/automation/awtrix_weather_warning_dwd.yaml Credits go to Jeef for the original AWTRIX Weather blueprint. domain: automation author: JC-23 homeassistant: min_version: 2024.10.0 input: awtrix: name: AWTRIX Device description: Select your AWTRIX device(s). selector: device: filter: - integration: mqtt manufacturer: Blueforcer model: AWTRIX 3 multiple: true weather_warning_var: name: DWD weather warning sensors description: >- Select one or more current-warning sensors provided by the [Deutscher Wetterdienst (DWD) Weather Warnings](https://www.home-assistant.io/integrations/dwd_weather_warnings/) integration. When the selection contains multiple regions, their shortened names are added to the displayed warnings. selector: entity: filter: - integration: dwd_weather_warnings domain: sensor multiple: true weather_preliminary_warning_var: name: DWD preliminary weather warning sensors description: >- Optionally select one or more sensors provided by the [Deutscher Wetterdienst (DWD) Preliminary Weather Warnings](https://www.home-assistant.io/integrations/dwd_weather_warnings/) integration. Current and preliminary sensors for the same region count as one region. default: [] selector: entity: filter: - integration: dwd_weather_warnings domain: sensor multiple: true warning_level: name: Show warnings for severity level (and above) description: |- 1 = warning (default) 2 = significant 3 = severe 4 = extreme selector: number: max: 4 min: 1 step: 1.0 default: 1 show_details: name: Show warning details description: Append the official description after the time range when available. selector: boolean: {} default: false message_repeat: name: Warning repeat 🔄 description: How often should the warning message be repeated on screen. selector: number: min: 1 max: 10 step: 1.0 default: 2 send_on_change_only: name: Show only when warnings change description: Skip publishing identical warning and preliminary warning payloads. selector: boolean: {} default: false play_beep: name: Play beep on new warning description: Enable to send an RTTTL tone with the MQTT payload. selector: boolean: {} default: false beep_rtttl: name: RTTTL melody description: Custom RTTTL string that will be played when the beep option is enabled. default: Beep:d=16,o=5,b=180:c6 selector: text: {} full_day_names: name: Full weekday names description: Enable to display the day name in full (e.g. Sonntag instead of So). selector: boolean: {} default: false icons_behavior: name: The behavior for the icons description: |- This option defines how the icons behave on the AWTRIX display. - "Don't move": Icon doesn't move - "Move with text once": Icon moves with text and will not appear again - "Move with text": Icon moves with text but appears again when the text starts to scroll again. selector: select: options: - "Don't move" - "Move with text once" - "Move with text" default: "Move with text" icon_fog: name: Icon for fog description: |- This is the icon ID which maps to the warning type: `59` ![](https://developer.lametric.com/content/apps/icon_thumbs/17055_icon_thumb.gif?v=1) selector: text: {} default: ww-fog icon_frost: name: Icon for frost description: |- This is the icon ID which maps to the warning type: `22,82` ![](https://developer.lametric.com/content/apps/icon_thumbs/55543_icon_thumb.gif?v=1) selector: text: {} default: ww-frost icon_thunderstorm: name: Icon for thunderstorms description: |- This is the icon ID which maps to the warning type: `31,33,34,36,38,40,41,42,44,45,46,48,49,95,96` ![](https://developer.lametric.com/content/apps/icon_thumbs/49299_icon_thumb.gif?v=4) selector: text: {} default: ww-thunderstorm icon_pouring: name: Icon for pouring rain description: |- This is the default icon which maps to the warning type: `61,62,63,64,65,66` ![](https://developer.lametric.com/content/apps/icon_thumbs/49300_icon_thumb.gif?v=1) selector: text: {} default: ww-pouring icon_snow: name: Icon for snow fall description: |- This is the icon ID which maps to the warning type: `70,71,72,73,74,75,76` ![](https://developer.lametric.com/content/apps/icon_thumbs/2289_icon_thumb.gif?v=1) selector: text: {} default: ww-snow icon_radition: name: Icon for UV radiation/heat description: |- This is the icon ID which maps to the warning type: `246,247,248` ![](https://developer.lametric.com/content/apps/icon_thumbs/55544_icon_thumb.gif?v=1) selector: text: {} default: ww-radiation icon_wind: name: Icon for wind description: |- This is the icon ID which maps to the warning type: `51,52,53,54,55,56` ![](https://developer.lametric.com/content/apps/icon_thumbs/3363_icon_thumb.gif?v=1) selector: text: {} default: ww-wind icon_thaw: name: Icon for thaw description: |- This is the icon ID which maps to the warning type: `88,89` ![](https://developer.lametric.com/content/apps/icon_thumbs/22866_icon_thumb.gif?v=1) selector: text: {} default: ww-thaw icon_blackice: name: Icon for black ice description: |- This is the icon ID which maps to the warning type: `84,85,86,87` ![](https://developer.lametric.com/content/apps/icon_thumbs/24231_icon_thumb.gif?v=1) selector: text: {} default: ww-blackice icon_warning: name: Icon for other warnings description: |- This is the icon ID which maps to any other warning type. ![](https://developer.lametric.com/content/apps/icon_thumbs/16754_icon_thumb.gif?v=1) selector: text: {} default: ww-generic mode: restart variables: device_ids: !input awtrix app_topic: dwd-warnings devices_topics: |- {%- set ns = namespace(devices=[]) -%} {%- for device_id in device_ids -%} {%- set device_topic_entities = device_entities(device_id) | select('search', 'device_topic') | list -%} {%- if device_topic_entities | length > 0 -%} {%- set device = states(device_topic_entities | first) -%} {%- if device not in ['unknown', 'unavailable', 'none', ''] -%} {%- set ns.devices = ns.devices + [device ~ '/custom/' ~ app_topic] -%} {%- endif -%} {%- endif -%} {%- endfor -%} {{ ns.devices }} weather_warning_input: !input weather_warning_var weather_warning_vars: >- {{ [weather_warning_input] if weather_warning_input is string else weather_warning_input }} weather_preliminary_warning_input: !input weather_preliminary_warning_var weather_preliminary_warning_vars: >- {{ [] if weather_preliminary_warning_input in [none, ''] else [weather_preliminary_warning_input] if weather_preliminary_warning_input is string else weather_preliminary_warning_input }} all_weather_warning_vars: >- {{ weather_warning_vars + weather_preliminary_warning_vars }} region_names: >- {%- set result = namespace(items=[]) -%} {%- for entity_id in all_weather_warning_vars -%} {%- set region_name = state_attr(entity_id, 'region_name') | default('', true) | regex_replace('^(Gemeinde|Stadt|Hansestadt)\\s+', '') | trim -%} {%- if region_name and region_name not in result.items -%} {%- set result.items = result.items + [region_name] -%} {%- endif -%} {%- endfor -%} {{ result.items }} show_region_name: "{{ region_names | length > 1 }}" warning_level_to_show: !input warning_level message_repeat: !input message_repeat icons_behavior: !input icons_behavior send_on_change_only: !input send_on_change_only play_beep: !input play_beep beep_rtttl: !input beep_rtttl icon_frost: !input icon_frost icon_thunderstorm: !input icon_thunderstorm icon_pouring: !input icon_pouring icon_snow: !input icon_snow icon_blackice: !input icon_blackice icon_wind: !input icon_wind icon_fog: !input icon_fog icon_thaw: !input icon_thaw icon_radition: !input icon_radition icon_warning: !input icon_warning full_day_names: !input full_day_names show_details: !input show_details should_publish: >- {% macro build_signature(attrs, severity, prefix) -%} {%- set result = namespace(items=[]) -%} {%- set count = attrs.get('warning_count', 0) | int(0) -%} {%- for idx in range(1, count + 1) -%} {%- set level = attrs.get('warning_' ~ idx ~ '_level') -%} {%- if level is not none and level | int(0) >= severity -%} {%- set pieces = [ prefix, attrs.get('region_name'), level | int(0), attrs.get('warning_' ~ idx ~ '_type'), attrs.get('warning_' ~ idx ~ '_start'), attrs.get('warning_' ~ idx ~ '_end'), attrs.get('warning_' ~ idx ~ '_name'), attrs.get('warning_' ~ idx ~ '_color'), attrs.get('warning_' ~ idx ~ '_description') if show_details else '' ] -%} {%- set normalized = namespace(items=[]) -%} {%- for piece in pieces -%} {%- set normalized.items = normalized.items + [piece | default('', true) | string] -%} {%- endfor -%} {%- set result.items = result.items + [normalized.items | join('~')] -%} {%- endif -%} {%- endfor -%} {{ result.items | sort | join('||') }} {%- endmacro %} {% if not send_on_change_only %} {{ true }} {% elif trigger is not defined or trigger.id in ['startup', 'refresh'] %} {{ true }} {% elif trigger.to_state is not defined or trigger.to_state is none %} {{ false }} {% elif trigger.from_state is not defined or trigger.from_state is none %} {{ true }} {% else %} {% set prefix = 'pre' if trigger.id == 'preliminary_changed' else 'warn' %} {{ build_signature(trigger.to_state.attributes, warning_level_to_show | int, prefix) != build_signature(trigger.from_state.attributes, warning_level_to_show | int, prefix) }} {% endif %} should_beep: >- {% if not play_beep or trigger is not defined %} {{ false }} {% elif trigger.id not in ['warning_changed', 'preliminary_changed'] %} {{ false }} {% elif trigger.to_state is not defined or trigger.to_state is none %} {{ false }} {% else %} {% macro contains_new_or_escalated_warning(current, previous, severity) -%} {%- set result = namespace(beep=false) -%} {%- set current_count = current.get('warning_count', 0) | int(0) -%} {%- set previous_count = previous.get('warning_count', 0) | int(0) -%} {%- for current_idx in range(1, current_count + 1) -%} {%- set current_level = current.get('warning_' ~ current_idx ~ '_level') | int(0) -%} {%- if current_level >= severity -%} {%- set match = namespace(found=false, level=0) -%} {%- set current_type = current.get('warning_' ~ current_idx ~ '_type') | int(0) -%} {%- set current_name = current.get('warning_' ~ current_idx ~ '_name') | default('', true) -%} {%- set current_start = current.get('warning_' ~ current_idx ~ '_start') | default('', true) -%} {%- for previous_idx in range(1, previous_count + 1) -%} {%- set previous_type = previous.get('warning_' ~ previous_idx ~ '_type') | int(0) -%} {%- set previous_name = previous.get('warning_' ~ previous_idx ~ '_name') | default('', true) -%} {%- set previous_start = previous.get('warning_' ~ previous_idx ~ '_start') | default('', true) -%} {%- if current_type == previous_type and current_name == previous_name and current_start == previous_start -%} {%- set match.found = true -%} {%- set match.level = [match.level, previous.get('warning_' ~ previous_idx ~ '_level') | int(0)] | max -%} {%- endif -%} {%- endfor -%} {%- if not match.found or current_level > match.level -%} {%- set result.beep = true -%} {%- endif -%} {%- endif -%} {%- endfor -%} {{ result.beep }} {%- endmacro %} {% set previous = trigger.from_state.attributes if trigger.from_state is defined and trigger.from_state is not none else {} %} {{ contains_new_or_escalated_warning(trigger.to_state.attributes, previous, warning_level_to_show | int) }} {% endif %} payload: |- {%- set day_names = { 'Mon': ['Mo', 'Montag'], 'Tue': ['Di', 'Dienstag'], 'Wed': ['Mi', 'Mittwoch'], 'Thu': ['Do', 'Donnerstag'], 'Fri': ['Fr', 'Freitag'], 'Sat': ['Sa', 'Samstag'], 'Sun': ['So', 'Sonntag'] } -%} {%- set day_index = 1 if full_day_names else 0 -%} {%- set frames = namespace(items=[]) -%} {%- macro warning_icon(warning_type) -%} {%- if warning_type in [22, 82] -%} {{ icon_frost }} {%- elif warning_type in [31, 33, 34, 36, 38, 40, 41, 42, 44, 45, 46, 48, 49, 95, 96] -%} {{ icon_thunderstorm }} {%- elif warning_type in [51, 52, 53, 54, 55, 56] -%} {{ icon_wind }} {%- elif warning_type == 59 -%} {{ icon_fog }} {%- elif warning_type in [61, 62, 63, 64, 65, 66] -%} {{ icon_pouring }} {%- elif warning_type in [70, 71, 72, 73, 74, 75, 76] -%} {{ icon_snow }} {%- elif warning_type in [84, 85, 86, 87] -%} {{ icon_blackice }} {%- elif warning_type in [88, 89] -%} {{ icon_thaw }} {%- elif warning_type in [246, 247, 248] -%} {{ icon_radition }} {%- else -%} {{ icon_warning }} {%- endif -%} {%- endmacro -%} {%- macro push_icon(icons_behavior) -%} {%- if icons_behavior == "Don't move" -%} 0 {%- elif icons_behavior == "Move with text once" -%} 1 {%- else -%} 2 {%- endif -%} {%- endmacro -%} {%- macro append_warnings(entity_id, text_prefix) -%} {%- set count = state_attr(entity_id, 'warning_count') | int(0) -%} {%- set region_name = state_attr(entity_id, 'region_name') | default('', true) | regex_replace('^(Gemeinde|Stadt|Hansestadt)\\s+', '') | trim -%} {%- for idx in range(1, count + 1) -%} {%- set level = state_attr(entity_id, 'warning_' ~ idx ~ '_level') -%} {%- if level is not none and level >= warning_level_to_show | int -%} {%- set name = state_attr(entity_id, 'warning_' ~ idx ~ '_name') | default('', true) -%} {%- set description = state_attr(entity_id, 'warning_' ~ idx ~ '_description') -%} {%- set warning_type = state_attr(entity_id, 'warning_' ~ idx ~ '_type') | int(0) -%} {%- set color = state_attr(entity_id, 'warning_' ~ idx ~ '_color') -%} {%- set start_ts = state_attr(entity_id, 'warning_' ~ idx ~ '_start') | as_timestamp -%} {%- set end_ts = state_attr(entity_id, 'warning_' ~ idx ~ '_end') | as_timestamp -%} {%- set start_day = start_ts | timestamp_custom('%a', local=true) -%} {%- set end_day = end_ts | timestamp_custom('%a', local=true) -%} {%- set start_time = start_ts | timestamp_custom('%H:%M', local=true) -%} {%- set end_time = end_ts | timestamp_custom('%H:%M', local=true) -%} {%- set start_label = day_names[start_day][day_index] if start_day in day_names else start_day -%} {%- set end_label = day_names[end_day][day_index] if end_day in day_names else end_day -%} {%- set name_clean = name | replace('VORABINFORMATION ', '') -%} {%- set text = text_prefix ~ 'stufe ' ~ level ~ ': ' ~ name_clean ~ ', ' ~ start_label ~ ' ' ~ start_time ~ ' Uhr - ' ~ end_label ~ ' ' ~ end_time ~ ' Uhr' -%} {%- if show_region_name and region_name -%} {%- set text = region_name ~ ': ' ~ text -%} {%- endif -%} {%- if show_details and description -%} {%- set desc_clean = description | replace('\n', ' ') | regex_replace('\\s+', ' ') | trim -%} {%- if desc_clean -%} {%- set text = (text ~ ' : ' ~ desc_clean) | truncate(320, True, '…') -%} {%- endif -%} {%- endif -%} {%- set frame = { 'icon': warning_icon(warning_type), 'pushIcon': push_icon(icons_behavior) | int, 'text': text, 'repeat': message_repeat | int, 'color': color, } -%} {%- set frames.items = frames.items + [frame] -%} {%- endif -%} {%- endfor -%} {%- endmacro -%} {%- for entity_id in weather_warning_vars -%} {{- append_warnings(entity_id, 'Warn') -}} {%- endfor -%} {%- for entity_id in weather_preliminary_warning_vars -%} {{- append_warnings(entity_id, 'Vorwarn') -}} {%- endfor -%} {%- if frames.items | length == 0 -%} {{ '' }} {%- elif frames.items | length == 1 -%} {{ frames.items[0] | tojson }} {%- else -%} {{ frames.items | tojson }} {%- endif -%} triggers: - trigger: state entity_id: !input weather_warning_var id: warning_changed - trigger: state entity_id: !input weather_preliminary_warning_var id: preliminary_changed - trigger: homeassistant event: start id: startup - trigger: time_pattern minutes: "/15" id: refresh conditions: [] actions: - choose: - conditions: - condition: template value_template: "{{ should_publish }}" sequence: - repeat: for_each: "{{ devices_topics }}" sequence: - action: mqtt.publish data: qos: 0 retain: false topic: "{{ repeat.item }}" payload: '' - choose: - conditions: - condition: template value_template: "{{ should_beep and payload != '' }}" sequence: - action: mqtt.publish data: qos: 0 retain: false topic: "{{ repeat.item | replace('/custom/' ~ app_topic, '/rtttl') }}" payload: "{{ beep_rtttl }}" - action: mqtt.publish data: qos: 0 retain: false topic: "{{ repeat.item }}" payload: "{{ payload }}"