Enhancing commute information with official road incident reports [UK roads]

After switching to a new job, I now commute to an office two days a week on one of the busier motorways in the UK and have found the Waze travel time integration does not provide sufficient information on my commmute for my tastes.

Having considered various options, I’ve now added the ability to view current “Unplanned incident” reports for the motorway I use from the government owned body responsible for operating the motorways and major roads in England - National Highways.

I thought this might be worth sharing in case anyone else is trying to do something similar.

Components

  1. Route-appropriate RSS xml feed from National Highways Traffic RSS
    For my purposes I used the current incidents / unplanned events feed for the M1 motorway and stored it in my secrets.yaml

  2. RESTful integration
    Added to configuration.yaml with a scan interval of once per week as I use the homeassistant update entity service to refresh on demand.
    The RESTful integration handles the conversion of XML to JSON automatically and provides sensor.m1_incidents with a set of incidents contained under the attribute item.

rest:
# m1 traffic northbound / southbound - scan weekly (can be on pulled on demand)
  - authentication: basic
    scan_interval: 604800
    resource: !secret trafficrssuri
    method: GET
    timeout: 30
    sensor:
      - name: "M1 Incidents"
        json_attributes_path: "$.rss.channel"
        value_template: "OK"
        json_attributes:
          - item
  1. Two trigger-based template sensors added to templates.yaml to filter the reported incidents down to a “per commute” level when the RESTful sensor is updated.
    Each template provides a numeric state of the number of relevant incidents for the route together with a list for display in a markdown card on one of my dashboard views.

    I consider an incident “relevant” if
    a. it matches the commute direction
    b. it is ongoing at the time the template is triggered
    c. the incident details contain one or more of the the relevant motorway junctions
    (the code below contains junction examples not the actual ones for privacy reasons)

# M1 traffic incidents rest sensor updates
- trigger:
   - platform: state
     entity_id: sensor.m1_incidents
     attribute: item
  sensor:
    - name: "Traffic Alerts To Home"
      unique_id: traffic_alerts_to_home
      availability: "{{ is_state('sensor.m1_incidents', 'OK') }}"
      icon: mdi:highway
      state: >-
        {% set route='m1 northbound' %}
        {% set junctions = '^.*(j5|j6|j7|j8|j9).*' %}
        {% set my_json=state_attr('sensor.m1_incidents', 'item') %}
        {% set ns=namespace(counter=0) %}
        {%- if my_json[0] is defined %}
               {%- for i in range(0, my_json | count) -%}
                       {%- if (route in my_json[i].title | lower) and (my_json[i].description | regex_search(junctions, ignorecase=True))
                               and (my_json[i].eventStart | as_datetime < now() < my_json[i].eventEnd | as_datetime) -%}
                              {%- set ns.counter=ns.counter+1 -%}
                       {%- endif -%}
               {%- endfor -%}
        {%- elif my_json.author is defined -%}
               {%- if (route in my_json.title | lower) and (my_json.description | regex_search(junctions, ignorecase=True))
                       and (my_json.eventStart | as_datetime < now() < my_json.eventEnd | as_datetime) -%}
                      {%- set ns.counter=ns.counter+1 -%}
               {%- endif -%}
        {%- endif -%}
        {{ ns.counter | int(0) }}
      attributes:
        # formatted for markdown
        incidents: >-
          {% set route='m1 northbound' %}
          {% set junctions = '^.*(j5|j6|j7|j8|j9).*' %}
          {% set my_json=state_attr('sensor.m1_incidents', 'item') %}
          {%- if my_json[0] is defined %}
                 {%- for i in range(0, my_json | count) -%}
                         {%- if (route in my_json[i].title | lower) and (my_json[i].description | regex_search(junctions, ignorecase=True))
                                 and (my_json[i].eventStart | as_datetime < now() < my_json[i].eventEnd | as_datetime) -%}
                                {{ my_json[i].description | replace('Location : ', 'Location: ') | trim }}</br>
                         {%- endif -%}
                 {%- endfor -%}
          {%- elif my_json.author is defined -%}
                 {%- if (route in my_json.title | lower) and (my_json.description | regex_search(junctions, ignorecase=True))
                         and (my_json.eventStart | as_datetime < now() < my_json.eventEnd | as_datetime) -%}
                        {{ my_json.description | replace('Location : ', 'Location: ') | trim }}</br>
                 {%- endif -%}
          {%- endif -%}
    - name: "Traffic Alerts To Work"
      unique_id: traffic_alerts_to_work
      availability: "{{ is_state('sensor.m1_incidents', 'OK') }}"
      icon: mdi:highway
      state: >-
        {% set route='m1 southbound' %}
        {% set junctions = '^.*(j5|j6|j7|j8|j9).*' %}
        {% set my_json=state_attr('sensor.m1_incidents', 'item') %}
        {% set counter=0 %}
        {%- if my_json[0] is defined -%}
               {%- for i in range(0, my_json | count) -%}
                       {%- if (route in my_json[i].title | lower) and (my_json[i].description | regex_search(junctions, ignorecase=True))
                               and (my_json[i].eventStart | as_datetime < now() < my_json[i].eventEnd | as_datetime) -%}
                              {%- set counter=counter+1 -%}
                       {%- endif -%}
               {%- endfor -%}
        {%- elif my_json.author is defined -%}
               {%- if (route in my_json.title | lower) and (my_json.description | regex_search(junctions, ignorecase=True))
                       and (my_json.eventStart | as_datetime < now() < my_json.eventEnd | as_datetime) -%}
                      {%- set counter=counter+1 -%}
               {%- endif -%}
        {%- endif -%}
        {{ counter | int(0) }}
      attributes:
        # formatted for markdown
        incidents: >-
          {% set route='m1 sorthbound' %}
         {% set junctions = '^.*(j5|j6|j7|j8|j9).*' %}
          {% set my_json=state_attr('sensor.m1_incidents', 'item') %}
          {%- if my_json[0] is defined %}
                 {%- for i in range(0, my_json | count) -%}
                         {%- if (route in my_json[i].title | lower) and (my_json[i].description | regex_search(junctions, ignorecase=True))
                                 and (my_json[i].eventStart | as_datetime < now() < my_json[i].eventEnd | as_datetime) -%}
                                {{ my_json[i].description | replace('Location : ', 'Location: ') | trim }}</br>
                         {%- endif -%}
                 {%- endfor -%}
          {%- elif my_json.author is defined -%}
                 {%- if (route in my_json.title | lower) and (my_json.description | regex_search(junctions, ignorecase=True))
                         and (my_json.eventStart | as_datetime < now() < my_json.eventEnd | as_datetime) -%}
                        {{ my_json.description | replace('Location : ', 'Location: ') | trim }}</br>
                 {%- endif -%}
          {%- endif -%}
  1. An automation that runs shortly before my usual commute times and then at intervals until I leave home / office (as appropriate).
    The automation triggers the updates of the commute relevant Waze travel time sensors and RESTful sensor (allowing for completion before continuing),
    e.g.
- service: homeassistant.update_entity
  target:
    entity_id:
      - sensor.commute_to_work
      - sensor.commute_to_work_alt
      - sensor.m1_incidents
- delay: "00:01:00"

The automation then builds and sends a notification to my mobile via the (Android) companion app providing:
a. any travel routes returned (of the two variants queried per commute)
b. an incident count from the relevant template sensor
c. icon colour coding of the notification based on the best travel time estimate and the number of incidents found.
d. a clickAction link to the lovelace view including the incidents markdown description where one or more incidents have been reported.

**EDIT : corrected scope of counter variable in #3 trigger based template sensor to use the proper namespaced variable definition - this ensures the variable value is maintained through each loop

3 Likes

Thank you very much for this. Have set it up following your instructions but a few questions:

  1. Regarding the automation, do you have the code for the bit that builds and sends the notification to the phone and the other a,b,c & d points you mentioned in the last part of your post?

  2. I want to monitor two motorways, the M1 & M25. I take it I can just duplicate the actions but rename the sensor to sensor.m1 & sensor.m25?

  3. When testing the state of the sensors, all I see is “OK” and no further attributes or information. Having said that, there are no entries in the xml so maybe this is the reason. Might have to see when unplanned incidents happen.

Thanks again for all your help on this

  1. This is the essential parts of the automation that builds and sends the notification (with some redactions for privacy).
    Note:
  • it depends on 2 sets of 2 Waze sensors for travel time, a composite weather sensor for weather conditions and is for an Android phone.
  • the link to /lovelace-map/traffic opens a dashboard view with a markdown card containing traffic incident details.
  action:
    - variables:
        start_inc: "{{ (15 if now().hour <= 6 else 30) | int(30) }}"
        expected: "{{ 75 if is_state('person.me', 'home') else 75 }}"
        best_time: >
          {% set wrking = 0 %}
          {% if is_state('person.me', 'home') %}
                {% if (not is_state('sensor.commute_to_work', 'unknown')) %}{% set wrking=state_attr('sensor.commute_to_work','duration')|float(0) %}{% endif %}
                {% if (not is_state('sensor.commute_to_work_alt', 'unknown')) %}
                      {% if state_attr('sensor.commute_to_work_alt', 'duration' )|float(0) < wrking|float(0) %}{% set wrking=state_attr('sensor.commute_to_work_alt','duration')|float(0) %}{% endif %}
                {% endif %}
          {% else %}
                {% if (not is_state('sensor.commute_to_home', 'unknown')) %}{% set wrking=state_attr('sensor.commute_to_home','duration')|float(0) %}{% endif %}
                {% if (not is_state('sensor.commute_to_home_alt', 'unknown')) %}
                      {% if state_attr('sensor.commute_to_home_alt', 'duration' )|float(0) < wrking|float(0) %}{% set wrking=state_attr('sensor.commute_to_home_alt','duration')|float(0) %}{% endif %}
                {% endif %}
          {% endif %}
          {{ wrking }}
        traffic_incidents: "{{ states('sensor.traffic_alerts_to_work')|int(0) if is_state('person.me', 'home') else states('sensor.traffic_alerts_to_home')|int(0) }}"
        weather_warning: >
          {%- if (is_state('sensor.composite_snow_next_3hrs', 'True') or is_state('sensor.composite_snow_next_12hrs', 'True')) -%}
                 {%- if (is_state('sensor.composite_snow_next_3hrs', 'True') and is_state('sensor.snow_next_12hrs', 'True')) -%}
                        <b>Snow is forecast for the day</b>.
                 {%- elif is_state('sensor.composite_snow_next_3hrs', 'True') -%}
                        <b>Snow is forecast within the next <b>3 hours</b>.
                 {%- elif is_state('sensor.composite_snow_next_12hrs', 'True') -%}
                        <b>Snow is predicted over the next 12 hours</b>.
                 {%- endif -%}
          {%- endif -%}
        msg_text: >
          {% set route1 = "" %}
          {% set route2 = "" %}
          {%- if is_state('person.me', 'home') -%}
                {%- if (not is_state('sensor.commute_to_work', 'unknown')) -%}
                      {% set route1=" - " + state_attr('sensor.commute_to_work','route') + ": " %}
                      {% set route1=route1 + (state_attr('sensor.commute_to_work','distance')|float(0)|round(2,default=0)|string) + " miles for " %}
                      {% set route1=route1 + (state_attr('sensor.commute_to_work','duration')|float(0)|round(0,default=0)|string) + state_attr('sensor.commute_to_work','unit_of_measurement') + "." %}
                {%- endif -%}
                {%- if (not is_state('sensor.commute_to_work_alt', 'unknown')) -%}
                      {% set route2=" - " + state_attr('sensor.commute_to_work_alt', 'route') + ": " %}
                      {% set route2=route2 + (state_attr('sensor.commute_to_work_alt','distance')|float(0)|round(2,default=0)|string) + " miles for " %}
                      {% set route2=route2 + (state_attr('sensor.commute_to_work_alt','duration')|float(0)|round(0,default=0)|string) + state_attr('sensor.commute_to_work_alt','unit_of_measurement') + "." %}
                {%- endif -%}
                Routes to Office:<br>
                {%- if route1 != "" -%}{{ route1 }}<br> {%- endif -%}
                {%- if route2 != "" and route2 != route1 -%}{{ route2 }}<br>{%- endif -%}
                {%- if route1 == "" and route2 == "" -%}No routes found<br>{%- endif -%}
          {%- else -%}
                {%- if (not is_state('sensor.commute_to_home', 'unknown')) -%}
                      {% set route1=" - " + state_attr('sensor.commute_to_home', 'route') + " : " %}
                      {% set route1=route1 + (state_attr('sensor.commute_to_home','distance')|float(0)|round(2,default=0)|string) + " miles for " %}
                      {% set route1=route1 + (state_attr('sensor.commute_to_home','duration')|float(0)|round(0,default=0)|string) + state_attr('sensor.commute_to_home','unit_of_measurement') + "." %}
                {%- endif -%}
                {%- if (not is_state('sensor.commute_to_home_alt', 'unknown')) -%}
                      {% set route2=" - " + state_attr('sensor.commute_to_home_alt','route') + " : " %}
                      {% set route2=route2 + (state_attr('sensor.commute_to_home_alt','distance')|float(0)|round(2,default=0)|string) + " miles for " %}
                      {% set route2=route2 + (state_attr('sensor.commute_to_home_alt','duration')|float(0)|round(0,default=0)|string) + state_attr('sensor.commute_to_home_alt','unit_of_measurement') + "." %}
                {%- endif -%}
                Routes to Home:<br<>
                {%- if route1 != "" %}{{ route1 }}<br>{%- endif -%}
                {%- if route2 != "" and route2 != route1 -%}{{ route2 }}<br>{%- endif -%}
                {%- if route1 == "" and route2 == "" -%}No routes found<br>{%- endif -%}
          {%- endif -%}
          <br><i>{{ traffic_incidents }} traffic incidents reported on M1</i>
          {%- if (weather_warning | trim | length > 0) -%}
                 <br>{{ weather_warning }}
          {%- endif -%}
    - choose:
        - conditions:
            - condition: template
              value_template: "{{ (best_time|float(0) > expected|float(0)) and (traffic_incidents|int(0) > 0) }}"
          sequence:
            - service: notify.mobile_app
              data:
                title: "Commute Warning"
                message: "{{ msg_text }}"
                data:
                  group: Information
                  color: "red"
                  tag: commute_time
                  clickAction: "/lovelace-map/traffic"
        - conditions:
            - condition: template
              value_template: "{{ ((best_time|float(0) > expected|float(0)) or (weather_warning | trim | length > 0)) and (traffic_incidents|int(0) == 0) }}"
          sequence:
            - service: notify.mobile_app_turk
              data:
                title: "Commute Warning"
                message: "{{ msg_text }}"
                data:
                  group: Information
                  color: "red"
                  tag: commute_time
        - conditions:
            - condition: template
              value_template: "{{ (best_time|float(0) <= expected|float(0)) and ((traffic_incidents|int(0) > 0) or (weather_warning | trim | length > 0)) }}"
          sequence:
            - service: notify.mobile_app_turk
              data:
                title: "Commute Warning"
                message: "{{ msg_text }}"
                data:
                  group: Information
                  color: "orange"
                  tag: commute_time
                  clickAction: "/lovelace-map/traffic"
      default:
        - service: notify.mobile_app
          data:
            title: "Commute Advisory"
            message: "{{ msg_text }}"
            data:
              group: Information
              tag: commute_time
  1. Duplicating the above steps for each of the motorways (rest sensor + trigger template sensor) should provide the relevant information for each.

  2. the rest sensor state should be OK with an attribute item listing the relevant incidents.
    The trigger based template sensor referred to above provides a count of incidents - this should be 0 where there are no incidents found.

Thanks for sharing Taz, very helpful.

Out of interest, why do you have 2 travel sensors? Alternative directions? I only have one waze sensor each way for my commute to work so trying to see how your code can fit in.

Also, is the below line in your code the sensor.m1_incidents as referenced above? I am trying again to understand what sensor this code relates to:

states(‘sensor.traffic_alerts_to_work’)

Lastly I really like the idea of having snow forecast or weather warning alerts considered, however not sure what sensor or integration you are using as my weather integrations don’t include snow forecast entities.

Any help appreciated,

Thanks again for your work on this, really great idea

I have two possible routes between work and home so have 2 sensors for each direction.

The traffic_alerts_to_home and traffic_alerts_,to_work sensors are trigger baded template sensors mentioned in the lower part of the original post that extract info from the rest sensor, basically providing formatted summary information for the notification and the Lovelace view referenced in notifications.

Worth noting that I update the rest sensors and travel sensors as part of the notification automation and then delay for 1 minute before continuing the automation - this ensures the notification info is up to date.

Thanks Taz, will look at modifying for my needs, really appreciate you sharing the code.

Did you say you have a markdown card to display the m1.sensor incidents? Would you mind sharing this code perhaps as a starting point for my dashboard?

What weather sensor / integration are you using? None of mine show snow sensors!

Card:

type: markdown
content: >+
  *Last updated: {{
  states.sensor.traffic_alerts_to_home.last_changed.strftime('%d-%b-%Y %H:%M')
  }}*


  **To Office: M1 Southbound - {{ states('sensor.traffic_alerts_to_work') }}
  incidents**


  {{ state_attr('sensor.traffic_alerts_to_work', 'incidents') }}</br>


  **To Home: M1 Northbound - {{ states('sensor.traffic_alerts_to_home') }}
  incidents**


  {{ state_attr('sensor.traffic_alerts_to_home', 'incidents') }}</br>

title: Reported Traffic Incidents

I use the template weather provider to combine weather information from the met office and openweathermap

Thanks again for this!

I have got the markdown card working but the number of incidents comes through as “unknown”. This could be because there currently are no incidents in the source XML but looking at your trigger template sensor code, would have thought it should report as 0 state instead.

I thought maybe it would update when I manually ran the RESTful sensor, but this didn’t change the state either.

Anyway, will test again and see if when there is an incident it changes!

Tried for the last few hours and can only get the state of the sensor to be “unknown”.

I tested the state code from the template sensor in developer tools and it successfully returns 0. However not in the state!

Any ideas please?

What does the rest sensor return in it’s attributes?

What is the code for the template sensor?

Rest sensor.incidents_m1

item:
  author: [email protected]
  category:
    - Congestion
    - Minor Disruption - up to 15 minutes delay
  description: >-
    Location : The M1 southbound entry slip at junction J13 . 

    Reason : Congestion. 

    Status : Currently Active. 

    Return To Normal : Normal traffic conditions are expected between 19:30 and
    19:45 on 17 January 2024. 

    Delay : There are currently delays of 10 minutes against expected traffic.
  guid:
    "@isPermaLink": "false"
    "#text": GUID1-4657044
  link: http://www.trafficengland.com/?evtID=4657044
  pubDate: Wed, 17 Jan 2024 19:34:36 GMT
  title: M1 J13 southbound access | Southbound | Congestion
  reference: UF-24-01-17-301232
  road: M1
  region: Eastern
  county: Bedfordshire
  latitude: "52.02634"
  longitude: "-0.60132074"
  overallStart: "2024-01-17T17:57:20+00:00"
  overallEnd: "2024-01-17T19:39:29+00:00"
  eventStart: "2024-01-17T17:57:20+00:00"
  eventEnd: "2024-01-17T19:39:29+00:00"
friendly_name: M1 Incidents

Template sensor code:

# M25 traffic incidents rest sensor updates
  - trigger:
    - platform: state
      entity_id: sensor.m1_incidents
      attribute: item
    sensor:
    - name: "Traffic Alerts To Home"
      unique_id: traffic_alerts_to_home
      availability: "{{ is_state('sensor.m1_incidents', 'OK') }}"
      icon: mdi:highway
      state: >-
        {% set route='m1 northbound' %}
        {% set junctions = '^.*(j21|j20|j19|j18|j17|j16|j15|j14|j13|j12|j11|j10).*' %}
        {% set my_json=state_attr('sensor.m1_incidents', 'item') %}
        {% set counter=0 %}
        {%- if my_json[0] is defined %}
               {%- for i in range(0, my_json | count) -%}
                       {%- if (route in my_json[i].title | lower) and (my_json[i].description | regex_search(junctions, ignorecase=True))
                               and (my_json[i].eventStart | as_datetime < now() < my_json[i].eventEnd | as_datetime) -%}
                              {%- set counter=counter+1 -%}
                       {%- endif -%}
               {%- endfor -%}
        {%- elif my_json.author is defined -%}
               {%- if (route in my_json.title | lower) and (my_json.description | regex_search(junctions, ignorecase=True))
                       and (my_json.eventStart | as_datetime < now() < my_json.eventEnd | as_datetime) -%}
                      {%- set counter=counter+1 -%}
               {%- endif -%}
        {%- endif -%}
        {{ counter | int(0) }}
      attributes:
        # formatted for markdown
        incidents: >-
          {% set route='m1 northbound' %}
          {% set junctions = '^.*(j21|j20|j19|j18|j17|j16|j15|j14|j13|j12|j11|j10).*' %}
          {% set my_json=state_attr('sensor.m1_incidents', 'item') %}
          {%- if my_json[0] is defined %}
                 {%- for i in range(0, my_json | count) -%}
                         {%- if (route in my_json[i].title | lower) and (my_json[i].description | regex_search(junctions, ignorecase=True))
                                 and (my_json[i].eventStart | as_datetime < now() < my_json[i].eventEnd | as_datetime) -%}
                                {{ my_json[i].description | replace('Location : ', 'Location: ') | trim }}</br>
                         {%- endif -%}
                 {%- endfor -%}
          {%- elif my_json.author is defined -%}
                 {%- if (route in my_json.title | lower) and (my_json.description | regex_search(junctions, ignorecase=True))
                         and (my_json.eventStart | as_datetime < now() < my_json.eventEnd | as_datetime) -%}
                        {{ my_json.description | replace('Location : ', 'Location: ') | trim }}</br>
                 {%- endif -%}
          {%- endif -%}
    - name: "Traffic Alerts To Work"
      unique_id: traffic_alerts_to_work
      availability: "{{ is_state('sensor.m1_incidents', 'OK') }}"
      icon: mdi:highway
      state: >-
        {% set route='m1 southbound' %}
        {% set junctions = '^.*(j21|j20|j19|j18|j17|j16|j15|j14|j13|j12|j11|j10).*' %}
        {% set my_json=state_attr('sensor.m1_incidents', 'item') %}
        {% set counter=0 %}
        {%- if my_json[0] is defined -%}
               {%- for i in range(0, my_json | count) -%}
                       {%- if (route in my_json[i].title | lower) and (my_json[i].description | regex_search(junctions, ignorecase=True))
                               and (my_json[i].eventStart | as_datetime < now() < my_json[i].eventEnd | as_datetime) -%}
                              {%- set counter=counter+1 -%}
                       {%- endif -%}
               {%- endfor -%}
        {%- elif my_json.author is defined -%}
               {%- if (route in my_json.title | lower) and (my_json.description | regex_search(junctions, ignorecase=True))
                       and (my_json.eventStart | as_datetime < now() < my_json.eventEnd | as_datetime) -%}
                      {%- set counter=counter+1 -%}
               {%- endif -%}
        {%- endif -%}
        {{ counter | int(0) }}
      attributes:
        # formatted for markdown
        incidents: >-
          {% set route='m1 southbound' %}
          {% set junctions = '^.*(j21|j20|j19|j18|j17|j16|j15|j14|j13|j12|j11|j10).*' %}
          {% set my_json=state_attr('sensor.m1_incidents', 'item') %}
          {%- if my_json[0] is defined %}
                 {%- for i in range(0, my_json | count) -%}
                         {%- if (route in my_json[i].title | lower) and (my_json[i].description | regex_search(junctions, ignorecase=True))
                                 and (my_json[i].eventStart | as_datetime < now() < my_json[i].eventEnd | as_datetime) -%}
                                {{ my_json[i].description | replace('Location : ', 'Location: ') | trim }}</br>
                         {%- endif -%}
                 {%- endfor -%}
          {%- elif my_json.author is defined -%}
                 {%- if (route in my_json.title | lower) and (my_json.description | regex_search(junctions, ignorecase=True))
                         and (my_json.eventStart | as_datetime < now() < my_json.eventEnd | as_datetime) -%}
                        {{ my_json.description | replace('Location : ', 'Location: ') | trim }}</br>
                 {%- endif -%}
          {%- endif -%}

Bizarrely overnight the state has now changed to 0 - however the example above with the incident on J13 should have been displayed in the markdown template but still comes through as 0 for both the to_work & to_home sensors.

incidents: ""
icon: mdi:highway
friendly_name: Traffic Alerts To Home

I can’t see anything untoward in the code.

Regarding the incident reporting - the trigger template ignores events where they are not “current” i.e. the time of triggering is not between the EventStart and EventEnd of the incident.
Could that be a factor here?

In this situation as the rest sensor is clearly working and has data I’d be inclined to take the template sensor state and incidents attribute code and dump them into HA Developer Tools Template section.
That will let you check values of :

  • my_json
  • my_json[i].title
    etc

to establish what’s actually going on.

Thanks Taz, think I found it when evaluating the code.

The search in {% set route=‘m1 southbound’ %} wasn’t producing any events as the xml seemed to have a random output for title “M1 J16 northbound” on one I tested.

I have removed the m1 element of the search to simply using “southbound” and “northbound”. Now events are coming through.

Thanks so much for bearing with me on it. A final few questions and I can start to play with this…

I am not great with JS code, how would I be able to add the county to the template sensor?

I believe this is the code I would need to modify:

{{ my_json[i].description | replace('Location : ', 'Location: ') | trim }}</br>

to add this element also:

my_json[i].county

Not sure how it would be formatted to somehow tag on the Location: Northamptonshire or similar.

Regarding this:

How would I be able to add a tolerance to this, like say instead of " < now()" say within 5 minutes of EventStart & EventEnd. I guess in other words “< now()+3000”

I

Yeh I checked that on my system this morning and have made the same change in my templating - either the formatting of the data has changed from what I originally tested (before wfh full time for a year - now back to hybrid).

For the addition of county you can use my_json[i].county and my_json.county (in the area referencing my_json.description but I’m not understanding what end result you want.
I don’t believe you want to modify the description code but actually just want to add the county element to the output. This is also something best played around with in the Developer Tools Template tool until you get the desired result.

For modifying time I tend to go with timedelta where possible as I find it more readable.
Copy and paste the below code into the Developer Tools Template tool for illustration:

{{ now()  }}
{{ now() - timedelta(hours=1) }}
{{ now() - timedelta(minutes=5) }}
{{ now() - timedelta(seconds=60) }}
{{ now() + timedelta(hours=1, minutes=5, seconds=60) }}

Thanks Taz

I will have a play. The objective I am after is as follows:

M1 Southbound - **0** incidents

* Location: The M1 southbound between junctions J18 and J17 .
Lane Closures : Lane four is closed.
Reason : Broken down vehicle.
Status : Currently Active.
Time To Clear : The event is expected to clear between 10:30 and 10:45 on 18 January 2024.
Return To Normal : Normal traffic conditions are expected between 10:30 and 10:45 on 18 January 2024.
Lanes Closed : There is one of four lanes closed.
County: Northamptonshire

I did try this to no avail as I also want to pass the html link for more info:

      attributes:
        # formatted for markdown
        incidents: >-
          {% set route='southbound' %}
          {% set junctions = '^.*(j17|j16|j15|j15a|j14|j13|j12|j11|j11a|j10|j9|j8|j7|j6a).*' %}
          {% set my_json=state_attr('sensor.m1_incidents', 'item') %}
          {%- if my_json[0] is defined %}
                 {%- for i in range(0, my_json | count) -%}
                         {%- if (route in my_json[i].title | lower) and (my_json[i].description | regex_search(junctions, ignorecase=True))
                                 and (my_json[i].eventStart | as_datetime < now() < my_json[i].eventEnd | as_datetime) -%}
                                {{ my_json[i].description | replace('Location : ', 'Location: ') | trim }}</br>
                         {%- endif -%}
                 {%- endfor -%}
          {%- elif my_json.author is defined -%}
                 {%- if (route in my_json.title | lower) and (my_json.description | regex_search(junctions, ignorecase=True))
                         and (my_json.eventStart | as_datetime < now() < my_json.eventEnd | as_datetime) -%}
                        {{ my_json.description | replace('Location : ', 'Location: ') | trim }}</br>
                 {%- endif -%}
          {%- endif -%}
        county: >-
            {% set my_json=state_attr('sensor.m1_incidents', 'item') %}
            {{ my_json.county }}
        link: >-
            {% set my_json=state_attr('sensor.m1_incidents', 'item') %}
            {{ my_json.link }}

Each attribute / state code block works in isolation. so your county and link attributes as currently done won’t:

  • match route, junctions or event time
  • handle more than one item in the json

You’ll either need to include the county and link within the returned incidents attribute by appending the values to the description data or replicate the incidents code block returning the data (county / link) in place of description.
Of course if you do the latter then in practice it will be difficult to determine which county / link relates to which description block where there are multiple incidents, so personally I’d go with the former.

You should be able to do something like:

 {{ my_json[i].description | replace('Location : ', 'Location: ') | trim }}
 {{ my_json[i].county }}
 {{ my_json[i].link }}</br>

and the equivalent for the single entry my_json.county etc then tidy up formatting with line breaks etc as required.

Thanks Taz, this works. You wouldn’t happen to know the issue with the code below? It simply ignores my title text and html link attempt when testing.

                        County: {{ my_json.county }}
                        <a href=\"{{ my_json.link }}\">More info...</a></br>
                 {%- endif -%}
          {%- endif -%}

Not really able to tell from the snippet provided - and I’m on an isolated network right now so no opportunity to verify with my own HA developer tools

@matt0020190

Just wanted to draw your attention to a correction in the original trigger-based template sensor (#3 in my original post).

I’ve corrected the definition and use of the counter variable to fix retaining the value of the counter for each iteration of the loop.

Thanks very much - sorry missed this reply. Will compare the two versions to see what was changed so I don’t have to start again with the junction config and sensor names.

Cheers