The wxalerts_alert event
Every new hazard fires a wxalerts_alert event on the Home Assistant bus.
The event data is the alert itself:
| Field | Notes |
|---|---|
same |
County SAME code |
topic |
Feed topic, unique per hazard |
id |
Feed-side alert id |
vtec |
KMOB.TO.W.0012.2026 |
event |
Tornado Warning |
severity |
Extreme / Severe / Moderate / Minor |
urgency |
Immediate / Expected / Future |
certainty |
Observed / Likely / Possible |
headline |
One-line summary |
instruction |
Protective-action text |
onset, ends |
ISO 8601 |
action |
VTEC action code |
Trigger on the event, not the state
For warnings, the event is the better trigger, and the difference is not stylistic.
A state trigger fires on a transition. If a tornado warning is live for your
county and the NWS issues a second one for a different storm, no entity
changes: the binary sensor is already on, the severity sensor is already
Extreme, the count goes 1 → 2 and back to 1 later. The event fires once per
hazard, so the second warning announces itself.
It also stays quiet for the right things. An update to a hazard already in
effect, whether a CON continuation or an EXT extension, reuses the same topic and
fires no second event, so an hour-long severe thunderstorm warning does not
re-notify you every time the office refreshes it.
State triggers are still the right tool for ambient things: a light that should be red while anything is live, a dashboard that shows the current severity.
A critical notification for a tornado warning
automation:
- alias: Tornado warning, critical push
triggers:
- trigger: event
event_type: wxalerts_alert
event_data:
event: Tornado Warning
actions:
- action: notify.mobile_app_phone
data:
title: "{{ trigger.event.data.headline }}"
message: "{{ trigger.event.data.instruction }}"
data:
push:
interruption-level: critical
Match on severity: Extreme instead of a specific event name to catch tornado
emergencies and particularly dangerous situation warnings in one rule.
Announce it out loud, and light the house
automation:
- alias: Warning announcement
triggers:
- trigger: event
event_type: wxalerts_alert
conditions:
- condition: template
value_template: "{{ trigger.event.data.severity in ['Extreme', 'Severe'] }}"
actions:
- action: light.turn_on
target:
entity_id: light.hallway
data:
color_name: red
brightness_pct: 100
- action: tts.speak
target:
entity_id: tts.piper
data:
media_player_entity_id: media_player.kitchen
message: >-
{{ trigger.event.data.event }} until
{{ as_timestamp(trigger.event.data.ends) | timestamp_custom('%-I:%M %p') }}.
{{ trigger.event.data.instruction }}
Ambient state: a light that follows severity
automation:
- alias: Alert beacon follows severity
triggers:
- trigger: state
entity_id: sensor.wxalerts_home_highest_severity
actions:
- choose:
- conditions: "{{ states('sensor.wxalerts_home_highest_severity') == 'Extreme' }}"
sequence:
- action: light.turn_on
target: { entity_id: light.alert_beacon }
data: { color_name: red, effect: fast_pulse }
- conditions: "{{ states('sensor.wxalerts_home_highest_severity') == 'Severe' }}"
sequence:
- action: light.turn_on
target: { entity_id: light.alert_beacon }
data: { color_name: orange }
default:
- action: light.turn_off
target: { entity_id: light.alert_beacon }
Lightning: react to the rate, not the presence
A single flash 30 km away is not news. A count that climbs sharply is.
automation:
- alias: Lightning closing in
triggers:
- trigger: numeric_state
entity_id: sensor.wxalerts_home_lightning_flashes
above: 20
for: "00:02:00"
actions:
- action: notify.mobile_app_phone
data:
message: >-
{{ states('sensor.wxalerts_home_lightning_flashes') }} flashes nearby in the
last {{ state_attr('sensor.wxalerts_home_lightning_flashes', 'window_minutes') }}
minutes. Bring the dog in.
Tune the threshold to your box size: 20 flashes in 15 minutes means something very different at 4.9 km than at 156 km. Watch the sensor through one storm season before trusting a number.
If you want the outdoor automations to stand down before the storm is
genuinely gone, gate on last_flash rather than the count dropping to zero:
condition:
- condition: template
value_template: >-
{{ (now() - (state_attr('sensor.wxalerts_home_lightning_flashes', 'last_flash')
| as_datetime | default(now() - timedelta(hours=1)))).total_seconds() > 1800 }}
A Markdown card for live alerts
The alerts attribute is a full list, sorted most severe first, so one card
renders whatever is live.
type: markdown
content: |
{% set alerts = state_attr('sensor.wxalerts_home_active_alerts', 'alerts') %}
{% if alerts %}
{% for a in alerts %}
### {{ a.event }}
**{{ a.severity }}** · until {{ as_timestamp(a.ends) | timestamp_custom('%a %-I:%M %p') }}
{{ a.headline }}
{{ a.instruction }}
{% if not loop.last %}---{% endif %}
{% endfor %}
{% else %}
No active alerts.
{% endif %}
A map card with the hazards on it
type: map
entities:
- zone.home
geo_location_sources:
- wxalerts
hours_to_show: 0
Markers sit on each polygon’s centroid. To draw the real shapes, read the
geometry attribute off a marker. It is full GeoJSON, live in the state
machine.
Conditional card: only show weather when there is weather
type: conditional
conditions:
- condition: state
entity: binary_sensor.wxalerts_home_active_alert
state: "on"
card:
type: entities
entities:
- sensor.wxalerts_home_highest_severity
- sensor.wxalerts_home_active_alerts
- binary_sensor.wxalerts_home_tornado_alert