UK Met Office Weather Warning TTS Sensor Help

Hello everyone, I’m trying to get a sensor working that pulls in the UK’s Met Office local weather warning XML and parse it into a TTS friendly list that I can use in a morning report. I’m bringing in the XML ok using feedparser:

sensor:
  - platform: feedparser
    name: Met Office RSS West Midlands Weather Warnings
    feed_url: "http://metoffice.gov.uk/public/data/PWSCache/WarningsRSS/Region/wm"
    date_format: "%a, %b %d %I:%M %p"
    scan_interval: 1

which returns XML. For today (2 weather warnings for lightning):

<?xml version="1.0" encoding="UTF-8"?>
<rss xmlns:dc="http://purl.org/dc/elements/1.1/" version="2.0">
    <channel>
        <title>Met Office warnings for West Midlands</title>
        <link>https://www.metoffice.gov.uk/weather/warnings-and-advice/uk-warnings</link>
        <description>Weather warnings of severe and extreme weather from the Met Office</description>
        <language>en-gb</language>
        <copyright>(c) Crown copyright</copyright>
        <pubDate>Wed, 03 Sep 2025 13:00:57 GMT</pubDate>
        <dc:creator>[email protected]</dc:creator>
        <item>
            <title>Yellow warning of thunderstorm affecting West Midlands</title>
            <link>https://www.metoffice.gov.uk/weather/warnings-and-advice/uk-warnings#?date=2025-09-04&amp;id=c95c6723-6233-4847-9734-cfebbc9483e8&amp;referrer=rss</link>
            <description>Yellow warning of thunderstorm affecting West Midlands: Herefordshire, Shropshire, Staffordshire, Stoke-on-Trent, Telford and Wrekin, Warwickshire, West Midlands Conurbation, Worcestershire valid from 0100 Thu 04 Sep to 1600 Thu 04 Sep</description>
            <guid isPermaLink="false">https://www.metoffice.gov.uk/weather/warnings-and-advice/uk-warnings#?date=2025-09-04&amp;id=c95c6723-6233-4847-9734-cfebbc9483e8&amp;referrer=rss&amp;region=West Midlands</guid>
            <enclosure length="17217" type="image/png" url="https://data.consumer-digital.api.metoffice.gov.uk/v1/warnings/rss/image/yellow-thunderstorm.png"/>
        </item>
        <item>
            <title>Yellow warning of thunderstorm affecting West Midlands</title>
            <link>https://www.metoffice.gov.uk/weather/warnings-and-advice/uk-warnings#?date=2025-09-03&amp;id=38613662-8c3d-415b-8311-8d5133de680c&amp;referrer=rss</link>
            <description>Yellow warning of thunderstorm affecting West Midlands: Herefordshire, Shropshire, Staffordshire, Warwickshire, West Midlands Conurbation, Worcestershire valid from 1000 Wed 03 Sep to 1900 Wed 03 Sep</description>
            <guid isPermaLink="false">https://www.metoffice.gov.uk/weather/warnings-and-advice/uk-warnings#?date=2025-09-03&amp;id=38613662-8c3d-415b-8311-8d5133de680c&amp;referrer=rss&amp;region=West Midlands</guid>
            <enclosure length="17217" type="image/png" url="https://data.consumer-digital.api.metoffice.gov.uk/v1/warnings/rss/image/yellow-thunderstorm.png"/>
        </item>
    </channel>
</rss>

I’ve created an attempted template sensor to try and parse this but I’m struggling. I need to check for both the presence of West Midlands Conurbation and Wolverhampton (as we may have local warnings that don’t affect the wider west midlands), combine multiple warnings if applicable whilst keeping the detail of the event,time/date range and severity for informational reasons. Any help or pointers would be appreciated, cheers.

template:
  - sensor:
      - name: Met Office Warnings TTS
        state: >
          {% set warnings = state_attr('sensor.met_office_rss_west_midlands_weather_warnings', 'entries') %}
          {% set relevant_warnings = [] %}
          {% if warnings %}
            {% for warning in warnings %}
              {% if 'West Midlands Conurbation' in warning.description or 'Wolverhampton' in warning.description %}
                {% set warning_parts = warning.description.split('valid from ') %}
                {% if warning_parts|length > 1 %}
                  {% set warning_text = warning_parts[0]|replace('Yellow warning of', 'Yellow')|replace('affecting West Midlands:', '') %}
                  {% set valid_time = warning_parts[1] %}
                  {% set relevant_warnings = relevant_warnings + [warning_text ~ ' valid from ' ~ valid_time] %}
                {% else %}
                  {% set relevant_warnings = relevant_warnings + [warning.description] %}
                {% endif %}
              {% endif %}
            {% endfor %}
          {% endif %}
          {% if relevant_warnings|length > 0 %}
            {{ relevant_warnings|join('. Next, ') }}
          {% else %}
            No weather warnings in place today
          {% endif %}

but that’s breaking/defaulting to no weather warnings in place for today. It would be nice if the Met Office integration offered weather warnings, but it doesn’t.

1 Like

How about several separate template sensors? You can combine them in the TTS sentence rather than trying to get a single template to do the job.

I have a custom intent like this:

CustomWeather:
  action:
    - action: script.tts_response
      data:
        tts_sentence: >-
          {% if is_state('sensor.met_office_data','OK') %}
          {{ states('sensor.starter_phrase')}} {{ states('sensor.weather_forecast') }}
          And the temperature outside is {{ states('sensor.outdoor_temperature') | round }} degrees. {{states('sensor.feels_like') }} {{ states('sensor.wind') }} Looking forward, {{ states('sensor.dry') }}
          {% else %}
          {{ states('sensor.met_office_data') }} But, {{ states('sensor.raining') }} And the temperature outside is {{ states('sensor.outdoor_temperature') | round }} degrees. {{states('sensor.feels_like') }} {{ states('sensor.wind') }}
          {% endif %}
          {% if states('sensor.weather_alerts') | float(0) > 0 %}
          {{ states('sensor.weather_alert_voice') }}.
          {% endif %}
          {% if states('sensor.uk_flood_alerts') | float(0) > 0 %}
          The Environment Agency has issued a warning of {{ states('sensor.local_flood_alert') }}
          {% endif %}
          {% if states('sensor.uk_heat_alert') != "No alert" and states('sensor.uk_heat_alert') != "unavailable" %}
          The UK Health Security Agency has issued a {{ states('sensor.ukhsa_heat_voice') }} affecting south west England
          {% endif %}
          {% if states('sensor.uk_cold_alert') != "No alert" and states('sensor.uk_heat_alert') != "unavailable" %}
          The UK Health Security Agency has issued a {{ states('sensor.ukhsa_cold_voice') }} affecting south west England
          {% endif %}

An advantage is that you can mix and match the various components in other TTS sentences as well.

sensor.starter_phrase is a random expression, “OK then…”, “Right, well…” etc. The other sensors collect data from the Met Office forecasts and alerts as well as from a weather station in the garden - so that you don’t get a situation where the Met Office says it’s “cloudy” but you only have to look out of the window to see it’s pouring.

After much trial and error I think I have it.

Note the template sensor only returns results/warnings for my area (Wolverhampton and/or West Midlands Conurbation)

Configuration.yaml:

sensor:
  - platform: feedparser
    name: "Met Office RSS West Midlands Weather Warnings"
    feed_url: "https://www.metoffice.gov.uk/public/data/PWSCache/WarningsRSS/Region/wm"
    date_format: "%a, %d %b %Y %H:%M:%S %Z"
    scan_interval:
      hours: 1

template:
  - sensor:
      - name: "Met Office Weather Warnings"
        unique_id: met_office_weather_warnings
        state: >
          {% set entries = state_attr('sensor.met_office_rss_west_midlands_weather_warnings','entries') or [] %}
          {% set relevant = entries | selectattr('summary','search','West Midlands Conurbation|Wolverhampton') | list %}
          {{ relevant | length }} active warnings
        attributes:
          warning_count: >
            {% set entries = state_attr('sensor.met_office_rss_west_midlands_weather_warnings','entries') or [] %}
            {% set relevant = entries | selectattr('summary','search','West Midlands Conurbation|Wolverhampton') | list %}
            {{ relevant | length }}
          warning_1_title: >
            {% set entries = state_attr('sensor.met_office_rss_west_midlands_weather_warnings','entries') or [] %}
            {% set relevant = entries | selectattr('summary','search','West Midlands Conurbation|Wolverhampton') | list %}
            {% if relevant | length >= 1 %}{{ relevant[0].title }}{% endif %}
          warning_1_summary: >
            {% set entries = state_attr('sensor.met_office_rss_west_midlands_weather_warnings','entries') or [] %}
            {% set relevant = entries | selectattr('summary','search','West Midlands Conurbation|Wolverhampton') | list %}
            {% if relevant | length >= 1 %}{{ relevant[0].summary }}{% endif %}
          warning_2_title: >
            {% set entries = state_attr('sensor.met_office_rss_west_midlands_weather_warnings','entries') or [] %}
            {% set relevant = entries | selectattr('summary','search','West Midlands Conurbation|Wolverhampton') | list %}
            {% if relevant | length >= 2 %}{{ relevant[1].title }}{% endif %}
          warning_2_summary: >
            {% set entries = state_attr('sensor.met_office_rss_west_midlands_weather_warnings','entries') or [] %}
            {% set relevant = entries | selectattr('summary','search','West Midlands Conurbation|Wolverhampton') | list %}
            {% if relevant | length >= 2 %}{{ relevant[1].summary }}{% endif %}
          simple_message: >
            {% set entries = state_attr('sensor.met_office_rss_west_midlands_weather_warnings','entries') or [] %}
            {% set relevant = entries | selectattr('summary','search','West Midlands Conurbation|Wolverhampton') | list %}
            {% set count = relevant | length %}
            {% if count == 0 %}
              There are currently no weather warnings in place for your area.
            {% else %}
              {% set ns = namespace(warnings=[]) %}
              {% for i in range(count) %}
                {% set warning = relevant[i] %}
                {% set title = warning.title %}
                {% set color = title.split(' ')[0] | capitalize %}
                {% set warning_type = title.split(' warning of ')[1].split(' affecting')[0] %}
                {% set ns.warnings = ns.warnings + [color ~ ' ' ~ warning_type ~ ' warning'] %}
              {% endfor %}
              There are currently {{ count }} weather warning{% if count > 1 %}s{% endif %} in place: {{ ns.warnings | join(' and ') }}.
            {% endif %}
          full_message: >
            {% set entries = state_attr('sensor.met_office_rss_west_midlands_weather_warnings','entries') or [] %}
            {% set relevant = entries | selectattr('summary','search','West Midlands Conurbation|Wolverhampton') | list %}
            {% set count = relevant | length %}
            {% if count == 0 %}
              There are currently no weather warnings in place for your area.
            {% else %}
              {% set day_map = {'Mon':'Monday','Tue':'Tuesday','Wed':'Wednesday','Thu':'Thursday','Fri':'Friday','Sat':'Saturday','Sun':'Sunday'} %}
              {% set month_map = {'Jan':'January','Feb':'February','Mar':'March','Apr':'April','May':'May','Jun':'June','Jul':'July','Aug':'August','Sep':'September','Oct':'October','Nov':'November','Dec':'December'} %}
              {% set ns = namespace(messages=[]) %}
              {% for i in range(count) %}
                {% set warning = relevant[i] %}
                {% set title = warning.title %}
                {% set summary = warning.summary %}
                {% set color = title.split(' ')[0] | capitalize %}
                {% set warning_type = title.split(' warning of ')[1].split(' affecting')[0] %}
                {% set time_text = '' %}
                {% if 'valid from' in summary %}
                  {% set time_part = summary.split('valid from ')[1] %}
                  {% set time_parts = time_part.split(' to ') %}
                  {% if time_parts | length >= 1 %}
                    {% set start_parts = time_parts[0].split(' ') %}
                    {% if start_parts | length >= 4 %}
                      {% set start_time = start_parts[0] %}
                      {% set start_day = day_map.get(start_parts[1], start_parts[1]) %}
                      {% set start_date = start_parts[2] | int %}
                      {% set start_month = month_map.get(start_parts[3], start_parts[3]) %}
                      {% set start_hour = start_time[0:2] | int %}
                      {% set start_minute = start_time[2:4] | int %}
                      {% set start_period = 'am' if start_hour < 12 else 'pm' %}
                      {% set start_hour_12 = start_hour % 12 %}
                      {% if start_hour_12 == 0 %}{% set start_hour_12 = 12 %}{% endif %}
                      {% set start_ordinal = 'th' %}
                      {% if start_date % 10 == 1 and start_date % 100 != 11 %}{% set start_ordinal = 'st' %}
                      {% elif start_date % 10 == 2 and start_date % 100 != 12 %}{% set start_ordinal = 'nd' %}
                      {% elif start_date % 10 == 3 and start_date % 100 != 13 %}{% set start_ordinal = 'rd' %}{% endif %}
                      {% set time_text = ' from ' ~ start_hour_12 ~ (':%02d' % start_minute if start_minute > 0 else '') ~ start_period ~ ' on ' ~ start_day ~ ' the ' ~ start_date ~ start_ordinal ~ ' of ' ~ start_month %}
                    {% endif %}
                    {% if time_parts | length >= 2 %}
                      {% set end_parts = time_parts[1].split(' ') %}
                      {% if end_parts | length >= 4 %}
                        {% set end_time = end_parts[0] %}
                        {% set end_day = day_map.get(end_parts[1], end_parts[1]) %}
                        {% set end_date = end_parts[2] | int %}
                        {% set end_month = month_map.get(end_parts[3], end_parts[3]) %}
                        {% set end_hour = end_time[0:2] | int %}
                        {% set end_minute = end_time[2:4] | int %}
                        {% set end_period = 'am' if end_hour < 12 else 'pm' %}
                        {% set end_hour_12 = end_hour % 12 %}
                        {% if end_hour_12 == 0 %}{% set end_hour_12 = 12 %}{% endif %}
                        {% set end_ordinal = 'th' %}
                        {% if end_date % 10 == 1 and end_date % 100 != 11 %}{% set end_ordinal = 'st' %}
                        {% elif end_date % 10 == 2 and end_date % 100 != 12 %}{% set end_ordinal = 'nd' %}
                        {% elif end_date % 10 == 3 and end_date % 100 != 13 %}{% set end_ordinal = 'rd' %}{% endif %}
                        {% set time_text = time_text ~ ' until ' ~ end_hour_12 ~ (':%02d' % end_minute if end_minute > 0 else '') ~ end_period ~ ' on ' ~ end_day ~ ' the ' ~ end_date ~ end_ordinal ~ ' of ' ~ end_month %}
                      {% endif %}
                    {% endif %}
                  {% endif %}
                {% endif %}
                {% set ns.messages = ns.messages + [color ~ ' ' ~ warning_type ~ ' warning' ~ time_text] %}
              {% endfor %}
              There are currently {{ count }} weather warning{% if count > 1 %}s{% endif %} in place: {{ ns.messages | join('. ') }}.
            {% endif %}

I then use the variable in my tts message:

actions:
  - variables:
      weather_warnings: "{{ state_attr('sensor.met_office_weather_warnings', 'full_message') }}"
... variables continued
  - alias: speak Weather warnings
    data:
      media_player_entity_id:
        - media_player.mini_speakers_2
        - media_player.music_assistant_syncgroup_w9kfj5gj
      message: "{{weather_warnings}}"
      cache: false
    target:
      entity_id: tts.piper
    action: tts.speak

I’ve tested this with 0, 1 and 2 weather warnings. As it happens there’s 1 in place here for today (in developer tools) - Note the tts friendly format of the full_message - took me forever to get working:

sensor.met_office_weather_warnings
Met Office Weather Warnings
1 active warnings
warning_count: 1
warning_1_title: Yellow warning of wind affecting West Midlands
warning_1_summary: Yellow warning of wind affecting West Midlands: Herefordshire, Shropshire, Staffordshire, Stoke-on-Trent, Telford and Wrekin, Warwickshire, West Midlands Conurbation, Worcestershire valid from 1900 Sun 14 Sep to 1700 Mon 15 Sep
warning_2_title: 
warning_2_summary: 
simple_message: There are currently 1 weather warning in place: Yellow wind warning.
full_message: There are currently 1 weather warning in place: Yellow wind warning from 7pm on Sunday the 14th of September until 5pm on Monday the 15th of September.
friendly_name: Met Office Weather Warnings
1 Like