UK MET Office Severe Weather Warnings

I did look at the core RSS integration, but it seems (I may be wrong) that it only generates events and doesn’t populate a sensor. I’m not sure whether it’s possible to create and populate sensors from automations triggered by the events and it sounds like it could get complicated.

I’ll take a look at Feedparser though, that looks interesting, thanks for the info.

Hi,
Did you had the chance to update the Lovelace Config? I’m curious about it! :slight_smile:
Cheers

not sure what you mean.

they have recently changed their format, I now use this:

- type: markdown
    style: "ha-card { --mdc-icon-size: 60px; }"
    content: |
      {% if state_attr('sensor.weather_warnings','items') != None %}
        {% for item in state_attr('sensor.weather_warnings','items') %}
          {% for type, icon in [('rain', 'weather-pouring'), ('thunderstorms', 'weather-lightning-rainy'),
                                ('wind', 'weather-windy'), ('snow', 'weather-snowy-heavy'), 
                                ('lightning', 'weather-lightning'), ('ice', 'car-traction-control'),
                                ('fog', 'weather-fog')] if type == item.description[0].split(' ')[3]|trim(',') %}
      ---
      | | | |
      | --- | --- | --- |
      | <font color = {%- if 'Yellow' == item.description[0].split(' ')[0]|trim(',') %}'gold'
                      {%- elif 'Amber' == item.description[0].split(' ')[0]|trim(',') %}'darkorange'
                      {%- else %}'firebrick'
                      {%- endif %}><ha-icon icon={{ "'mdi:" + icon + "'" }}></ha-icon></font> | | {{ item.description|trim("'[]'") }}  |
          {% endfor %}
        {% endfor %}
      {% endif %}
1 Like

Thanks! It helped me a lot to improve my code! great stuff :slight_smile:

Sorry, not been around for a while. They changed the format a few weeks back, my code now looks very similar to what you’ve posted. I’m sure it will help others, thanks.

I have started a new thread around the new(-ish) Met Office DataHub but maybe someone here has already got the necessary to help me with this.
How do I get the JSON from my python calls into Homeassistant and one of your lovely cards?
Thanks

for any one interested. I’ve got this working with feedparser rather than nodered. Massive props to @eggman as I wouldn’t have been able to do this with their work.

image

- platform: feedparser
  name: Weather Alerts
  feed_url: "https://www.metoffice.gov.uk/public/data/PWSCache/WarningsRSS/Region/wm"
  date_format: "%a, %b %d %I:%M %p"

lovelace card

type: 'custom:stack-in-card'
cards:
  - type: weather-forecast
    entity: weather.openweathermap
  - type: markdown
    style: |
      ha-card {
          --iron-icon-width: 50px;
          --iron-icon-height: 50px;
        }
    content: >
      {% if state_attr('sensor.weather_alerts','entries') != 0 %}
        {% for item in state_attr('sensor.weather_alerts','entries') %}
          {% for type, icon in [('rain', 'weather-pouring'), ('thunderstorms', 'weather-lightning-rainy'),
                                ('wind', 'weather-windy'), ('snow', 'weather-snowy-heavy'), 
                                ('lightning', 'weather-lightning'), ('ice', 'car-traction-control'),
                                ('fog', 'weather-fog')] if type == item.summary.split(' ')[3]|trim(',') %}
      ---

      | | | |

      | --- | --- | --- |

      | <font color = {%- if 'Yellow' == item.summary.split(' ')[0]|trim(',')
      %}'gold'
                      {%- elif 'Amber' == item.summary.split(' ')[0]|trim(',') %}'darkorange'
                      {%- else %}'firebrick'
                      {%- endif %}><ha-icon icon={{ "'mdi:" + icon + "'" }}></ha-icon></font> | | {{ item.summary|trim("'[]'") }}  |
          {% endfor %}
        {% endfor %}
      {% endif %}

1 Like

I’ve made some improvements to my feedparser version previously posted. This now supports “extream heat” and “thunderstorm” alerts. I’ve also reworked the summary text into something which I can layout a little better. Date time formats can be changed to your liking simply by updating the format codes.

You will also need to update the region in the regex to match your region. I strip it out because I know my region and only interested in the actual places affected.

Sensor

- platform: feedparser
  name: Weather Alerts
  feed_url: "https://www.metoffice.gov.uk/public/data/PWSCache/WarningsRSS/Region/wm"
  date_format: "%a, %b %d %I:%M %p"

Lovelace Card - needs card_mod

type: custom:stack-in-card
cards:
  - type: weather-forecast
    entity: weather.openweathermap
  - type: conditional
    conditions:
      - entity: sensor.weather_alerts
        state_not: '0'
    card:
      type: markdown
      card_mod:
        style:
          .: |
            ha-card {
              --mdc-icon-size: 40px;
            }
          ha-markdown:
            $: |
              td {
                vertical-align: top;
              }
      content: |
        {% if state_attr('sensor.weather_alerts','entries') != 0 %}
          {% for item in state_attr('sensor.weather_alerts','entries') %}
            {% for type, icon in [('rain', 'weather-pouring'), ('thunderstorms', 'weather-lightning-rainy'),
                                  ('wind', 'weather-windy'), ('snow', 'weather-snowy-heavy'), 
                                  ('lightning', 'weather-lightning'), ('ice', 'car-traction-control'),
                                  ('fog', 'weather-fog'), ('extreme heat', 'weather-sunny-alert'), ('thunderstorm', 'weather-lightning')] if type == item.summary | regex_findall_index('.*warning of (.*) affecting.*', ignorecase=True) %}
              {% set color = item.summary.split(' ')[0] %}
              {% set summary = item.summary | regex_findall_index('(.*) affecting West Midlands: (.*) valid from (.*) to (.*)', ignorecase=True) %}
              {% set time_from = as_timestamp(strptime(summary[2], "%H%M %a %d %b")) | timestamp_custom("%H:%M %d/%m") %}
              {% set time_to = as_timestamp(strptime(summary[3], "%H%M %a %d %b")) | timestamp_custom("%H:%M %d/%m") %}
        | | | |
        | --- | --- | --- |
        | <font color = {%- if 'Yellow' == color %}'gold'
                        {%- elif 'Amber' == color %}'darkorange'
                        {%- else %}'firebrick'
                        {%- endif %}><ha-icon icon={{ "'mdi:" + icon + "'" }}></ha-icon></font> | | **{{ summary[0] | title }}**<br />{{ time_from }} - {{ time_to }}<br />{{ summary[1] }} |
            {% endfor %}
          {% endfor %}
        {% endif %}

Hope someone finds this useful

4 Likes

Thank you for this! exactly what I have been looking for

1 Like

You are most welcome. Credit to the OP for coming up with the idea in the first place.

1 Like

If anyone else spends hours trying to get this working, don’t forget to edit this regex:

regex_findall_index('(.*) affecting West Midlands: (.*) valid from (.*) to (.*)', ignorecase=True) %}

and change West Midlands to whatever is reported in your sensor.weather_alerts state!

1 Like

Hi I am having problems with this. I am using this feed trying to capture alerts for London: Met Office warnings for London & South East England

I recently configured this OK using the details above. For South East change the last part of the URL in the Feedparser sensor from “wm” to “se” and in the Lovelace card change “West Midlands” to “London & South East England”. You’ll need to add Feedparser via HACS if you don’t already have it. I used the standard “vertical-stack” for the cards - you’ll need to add “stack-in-card” via HACS if you want to use that.

1 Like

Ah perfect :+1:

Ok, I don’t post this here because I’m particularly proud of it, in fact I wholeheartedly welcome improvements…

This is a sensor with a state of the number of current weather warnings with the attribute ‘warnings’ giving a list of current warnings and the validity period in natural language.

For example currently my sensor shows this:

As I said I just post it here in case it is interesting to anyone, not as an example of great coding!

I use it for phone notifications. Persistent notifications and as others do here, a Lovelace card.

image

sensor:
  #================================================================================
  #=== RSS Feed for Met Office Severe weather warnings (London and the South East)
  #=== Note that these warnings do not all necessarily affect 'Greater London'
  #================================================================================
  - platform: feedparser
    name: Met Office RSS Feed South East Weather Warnings
    feed_url: 'http://metoffice.gov.uk/public/data/PWSCache/WarningsRSS/Region/se'
    date_format: '%a, %b %d %I:%M %p'



template:
  #=== Met Office London Weather Warnings
  - trigger:
      - platform: state
        entity_id: sensor.met_office_rss_feed_south_east_weather_warnings
        attribute: entries

      - platform: homeassistant
        event: start

      - platform: state
        entity_id: sensor.date

    sensor:
      name: Met Office London Weather Warnings
      state: >
        {% set ns = namespace(warning_count = 0) %}
        {% for entries in state_attr('sensor.met_office_rss_feed_south_east_weather_warnings', 'entries') %}
          {% if 'Greater London' in states.sensor.met_office_rss_feed_south_east_weather_warnings.attributes.entries[loop.index - 1].summary %}
            {% set ns.warning_count = ns.warning_count + 1 %}
          {% endif %}
        {% endfor %}

        {{ ns.warning_count }}
      attributes:
        warnings: >
          {% set warning_count = states('sensor.met_office_london_weather_warnings') %}
          {% set ns = namespace(json = '[') %}

          {% for entries in states.sensor.met_office_rss_feed_south_east_weather_warnings.attributes.entries %}
            {% if 'Greater London' in states.sensor.met_office_rss_feed_south_east_weather_warnings.attributes.entries[loop.index - 1].summary %}
              {% set ns.json = ns.json + '{"warning": "' + states.sensor.met_office_rss_feed_south_east_weather_warnings.attributes.entries[loop.index - 1].title + '", "period": "' %}

              {% set summary = states.sensor.met_office_rss_feed_south_east_weather_warnings.attributes.entries[loop.index - 1].summary %}
              {% set valid_period = summary.split(' valid from ')[1] %}
              {% set valid_from = valid_period.split(' to ')[0] %}
              {% set valid_to = valid_period.split(' to ')[1] %}

              {% set days_map = { 'Sat': 'Saturday', 'Sun': 'Sunday', 'Mon': 'Monday', 'Tue': 'Tuesday',
                                  'Wed': 'Wednesday', 'Thu': 'Thursday', 'Fri': 'Friday'} %}

              {% set months_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'} %}

              {#                                                  #}
              {#-- Macro to build the valid from and to phrases --#}
              {#   --------------------------------------------   #}
              {#                                                  #}

              {% macro valid_from_to(validity_time) %}

              {#                              #}
              {#-- Extract hour and minutes --#}
              {#                              #}
              {% set validity_hour = validity_time.split(' ')[0][0:2] | int %}
              {% set validity_minutes = validity_time.split(' ')[0][2:4] | int %}

              {#                           #}
              {#-- Get period of the day --#}
              {#                           #}
              {% if validity_hour == 12 %}
                {% set validity_day_period = '' %}
              {% elif validity_hour == 0 %}
                {% set validity_day_period = '' %}
              {% elif validity_hour < 12 %}
                {% set validity_day_period = "morning" %}
              {% elif validity_hour > 16 %}
                {% set validity_day_period = "evening" %}
              {% else %}
                {% set validity_day_period = "afternoon" %}
              {% endif %}

              {#                                                  #}
              {#-- Convert time to 12 hour format and get AM/PM --#}
              {#                                                  #}
              {% set am_pm = '' %}
              {% if validity_hour == 12 and validity_minutes == 0 %}
                {% set validity_hour = 'noon' %}
              {% elif validity_hour == 11 and (50 < validity_minutes <= 59) %}
                {% set validity_hour = 'noon' %}
              {% elif validity_hour == 0  and validity_minutes == 0 %}
                {% set validity_hour = 'the beginning of ' %}
              {% elif validity_hour == 23 and (50 < validity_minutes <= 59) %}
                {% set validity_hour = 'the end of ' %}
              {% elif validity_hour > 12 %}
                {% set am_pm = 'pm' %}
                {% set validity_hour = validity_hour - 12 %}
              {% else %}
                {% set am_pm = 'am' %}
              {% endif %}

              {#                     #}
              {#-- Extract minutes --#}              
              {#                     #}
              {% if (0 <= validity_minutes < 10) or (50 < validity_minutes <= 59) %}
                {% set validity_minutes = am_pm %}
              {% else %}
                {% if validity_minutes < 10 %}
                  {% set validity_minutes = '0' ~ validity_minutes %}
                {% endif %}
                {% set validity_minutes = ':' ~ validity_minutes ~ am_pm %}
              {% endif %}

              {#                                 #}
              {#-- Extract day, month and date --#}
              {#                                 #}
              {% set validity_day = days_map[validity_time.split(' ')[1][0:3]] %}
              {% set validity_month = months_map[validity_time.split(' ')[-1]] %}
              {% set validity_date = validity_time.split(' ')[-2] | int | string %}

              {#                         #}
              {#-- Get suffix for date --#}
              {#                         #}
              {% if validity_date  in ('1', '21', '31') %}
                {% set suffix = 'st ' %}
              {% elif validity_date   in ('2', '22') %}
                {% set suffix = 'nd ' %}
              {% elif validity_date   in ('3', '23') %}
                {% set suffix = 'rd ' %}
              {% else %}
                {% set suffix = 'th ' %}
              {% endif %}

              {#                                        #}
              {#-- Build the final phrase             --#}
              {#-- First we need to zero pad the date --#}
              {#                                        #}
              {% set zero_padded_date = '{:02}'.format(validity_time[5:].split(' ')[1] | int) %}
              {% set zero_padded_date = validity_time[5:].split(' ')[0] + ' ' + zero_padded_date + ' ' + validity_time[5:].split(' ')[2] %}
              {% if as_timestamp(now()) | timestamp_custom('%a %d %b') == zero_padded_date %}
                {% if validity_hour == 'noon' %}
                  {% set phrase = validity_hour  ~ ' today' %}
                {% elif validity_hour == 'the beginning of ' %}
                  {% set phrase = validity_hour  ~ ' tomorrow' %}
                {% else %}
                  {% set phrase = validity_hour ~ validity_minutes ~ ' this ' ~ validity_day_period %}
                {% endif %}
              {% elif now().day + 1 == validity_date | int %}
                {% set phrase = validity_hour ~ validity_minutes ~ ' tomorrow ' ~ validity_day_period %}
              {% elif now().day - 1 == validity_date | int %}
                {% set phrase = validity_hour ~ validity_minutes ~ ' yesterday ' ~ validity_day_period %}
              {% else %}
                {% if validity_hour == 'the beginning of ' or 
                      validity_hour == 'the end of ' %}
                  {% set phrase = validity_hour ~ validity_minutes ~ validity_day ~ ' the ' ~ validity_date ~ suffix ~ 'of ' ~ validity_month %}
                {% else %}
                  {% set phrase = validity_hour ~ validity_minutes ~ ' on ' ~ validity_day ~ ' the ' ~ validity_date ~ suffix ~ 'of ' ~ validity_month %}
                {% endif %}
              {% endif %}

              {{ phrase }}

              {% endmacro %}

              {#                  #}
              {#-- End of Macro --#}
              {#   ------------   #}
              {#                  #}

              {#                                           #}
              {#-- Get the valid to and valid from phrases #}
              {#                                           #}
              {% if valid_to[:2] == '00' %}
                {% if valid_to[2:4] == '00' %}
                  {% set to = 'the beginning of ' ~ days_map[valid_to[5:8]] %}
                {% else %}
                  {% set to = valid_to[2:4] ~ ' minutes past midnight on' ~ valid_to[4:] %}
                {% endif %}
              {% else %}
                {% set to = valid_from_to(valid_to) %}
              {% endif %}

              {% if valid_from[:2] == '00' %}
                {% if valid_from[2:4] == '00' %}
                  {% set from = 'the beginning of ' ~ days_map[valid_to[5:8]] %}
                {% else %}
                  {% set from = valid_from[2:4] ~ ' minutes past midnight on' ~ valid_from[4:] %}
                {% endif %}
              {% else %}
                {% set from = valid_from_to(valid_from) %}
              {% endif %}

              {% if 'the beginning of' in from and
                    'the end of' not in to %}
                {% if now().strftime('%A') in from %}
                  {% set from = 'the beginning of today' %}
                {% elif (as_timestamp(now()) + 86400) | timestamp_custom('%A') in from %}
                  {% set from = 'the beginning of tomorrow' %}
                {% else %}
                {% endif %}

              {% elif 'the beginning of' not in to and
                    'the end of' in to %}
                {% if now().strftime('%A') in from %}
                  {% set to = 'the end of today' %}
                {% elif (as_timestamp(now()) + 86400) | timestamp_custom('%A') in from %}
                  {% set to = 'the end of tomorrow' %}
                {% else %}
                {% endif %}

              {% elif 'the beginning of' in from and
                    'the end of' in to %}
                {% if now().strftime('%A') in from %}
                  {% set from = 'All day today' %}
                {% elif (as_timestamp(now()) + 86400) | timestamp_custom('%A') in from %}
                  {% set from = 'All day tomorrow' %}
                {% else %}
                  {% set from = from.replace('the beginning of', 'All day on') + '."' %}
                {% endif %}
              {% endif %}

              {% set ns.json = ns.json + 'Valid from ' + from | trim + ' until ' + to | trim + '."' %}

              {% set ns.json = ns.json + '}, ' %}

            {% endif %}
          {% endfor %}

          {% if ns.json == '[' %}
            {% set ns.json = ns.json + ']' %}
          {% else %}
            {% set ns.json = ns.json[0:-3] + '}]' %}
          {% endif %}

          {{ ns.json }}

Curious to see if anyone is generating notifications when their sensor.weather_alerts entity increases by one and if anyone can think of a better way to handle a notification to say a new alert has been issued:

alias: Weather Alerts
description: 'Send notification when a weather alert is issued'
trigger:
  - platform: state
    entity_id: sensor.weather_alerts
condition:
  - condition: template
    value_template: '{{ trigger.to_state.state | int > trigger.from_state.state | int }}'
action:
  - service: notify.mobile_app_nick
    data:
      message: https://www.metoffice.gov.uk/weather/warnings-and-advice/uk-warnings
      title: 'Check Weather Warnings! '
mode: single

I’m not sure if this is the answer to your question but I think so…

And as previously stated this was all written ages ago, it’s not great code but it works so I leave it alone.

I allow for up to five warnings at any one time;

#================
#=== Input Texts
#================
input_text:
  weather_warning1:
    min: 0
    max: 255

... etc ...

  weather_warning5:
    min: 0
    max: 255

I have the sensors as my previous post.

And I have this automation that saves the warnings:

  #==============================
  #=== Populate Weather Warnings
  #==============================
  - alias: Weather Populate Weather Warnings
    id: weather_populate_weather_warnings
    trigger:

      - platform: state
        entity_id: sensor.met_office_london_weather_warnings

      - platform: homeassistant
        event: start

    action:
      #=== Empty weather warning input texts
      - service: input_text.set_value
        data:
          entity_id:
            - input_text.weather_warning1
            - input_text.weather_warning2
            - input_text.weather_warning3
            - input_text.weather_warning4
            - input_text.weather_warning5
          value: ""

      #=== Check there are any weather warnings for London
      - condition: numeric_state
        entity_id: sensor.met_office_london_weather_warnings
        above: 0

      #=== Populate the input text for each warning
      - repeat:
          count: >
            {{ states('sensor.met_office_london_weather_warnings') }}
          sequence:
            - service: input_text.set_value
              data:
                entity_id: input_text.weather_warning{{ repeat.index }}
                value: >
                  {% set warning = state_attr('sensor.met_office_london_weather_warnings', 'warnings')[repeat.index-1].warning %}
                  {% set warning = warning.replace('affecting London & South East England)', 'affecting London') + '. ' %}
                  {% set period = state_attr('sensor.met_office_london_weather_warnings', 'warnings')[repeat.index-1].period %}
                  {{ warning + period }}

And this automation that handles the notifications (script.notify is my notification engine):

  #===============================
  #=== Notify of weather warnings
  #===============================
  - alias: Notify Weather Warnings
    id: notify_weather_warnings
    trigger:
      - platform: state
        entity_id: sensor.met_office_london_weather_warnings

      - platform: homeassistant
        event: start

    variables:
      message: >
        {% if states('sensor.met_office_london_weather_warnings') | int > 0 %}
          {% for warning in state_attr('sensor.met_office_london_weather_warnings', 'warnings') %}
            {%- set warning = state_attr('sensor.met_office_london_weather_warnings', 'warnings')[loop.index-1].warning %}
            {%- set warning = warning.replace('affecting London & South East England', 'affecting London') + '. ' %}
            {%- set period = state_attr('sensor.met_office_london_weather_warnings', 'warnings')[loop.index-1].period %}
            {%- set message = warning ~ period %}
            {%- if not loop.last %}
              {% set message = message ~ '<br>' %}
            {% endif %}
            {{- message }}
          {%- endfor %}
        {% endif %}
    action:
      #=== Remove all current notifications
      - service: persistent_notification.dismiss
        data:
          notification_id: weather_warning

      - service: script.notify
        data:
          tell: me
          mobile_app_tag: WEATHER_WARNING
          message: clear_notification

      #=== Continue if there are any weather warnings for London
      - condition: numeric_state
        entity_id: sensor.met_office_london_weather_warnings
        above: 0

      #=== Create HA Persistant notification
      - service: script.notify
        data_template:
          show: True
          notification_id: weather_warning
          title: 🌩️ Met Office Weather Warnings
          message: >
            {%- set message = message | replace('Red','<font color=firebrick>RED</font>') %}
            {%- set message = message | replace('Amber','<font color=DarkOrange>AMBER</font>') %}
            {%- set message = message | replace('Yellow','<font color=yellow>YELLOW</font>') %}

            {{ message }}

      #=== Notify Me
      - choose:
          - conditions: >
              {{ is_state('input_boolean.met_office_weather_warnings_notify_me', 'on') }}
            sequence:
              - service: script.notify
                data:
                  tell: me
                  title: 🌩️ Met Office Weather Warnings
                  mobile_app_tag: WEATHER_WARNING
                  message: >
                    {%- set message = message | replace('Red','<b><span style="color: red">RED</span></b>') %}
                    {%- set message = message | replace('Amber','<b><span style="color: #FF8C00">AMBER</span></b>') %}
                    {%- set message = message | replace('Yellow','<b><span style="color: yellow">YELLOW</span></b>') %}

                    {{ message }}

By way of thanks for this great contribution, just to note that current UK weather warnings seem to be combining “snow, ice” into a single alert, so I needed to add this to the alert dictionary for it to be parsed properly in the markdown card:

            {% for type, icon in [('rain', 'weather-pouring'), ('thunderstorms', 'weather-lightning-rainy'),
                                  ('wind', 'weather-windy'), ('snow', 'weather-snowy-heavy'), 
                                  ('snow, ice', 'weather-snowy-heavy'),
                                  ('lightning', 'weather-lightning'), ('ice', 'car-traction-control'),
                                  ('fog', 'weather-fog'), ('extreme heat', 'weather-sunny-alert'), ('thunderstorm', 'weather-lightning')] if type == item.summary | regex_findall_index('.*warning of (.*) affecting.*', ignorecase=True) %}

...  rest of code here

           {% endfor %}
5 Likes

Thanks for the heads up.

I was wondering what the code would be to open the BBC weather warnings site, by clicking on the text that this entity generates. Is that possible at all?