How to create a public transport status sensor with REST and templates?

Hi there!

I was looking for a way to extract some text from json. My json comes from a REST transport API for transport situation exchange. A simplified and reduced example json result from a REST call is at the end of this post. What I am hoping for is someone that can give a few hints how to achieve the following examples (I did it already in a few lines of python but struggle to reproduce in templates):

  • extract the “Description” when a “LineRef” nested inside “Affects” matches “SKY:Line:3” in the example json
  • extract the “Description” when a “StopPointRef” nested inside “Affects” matches “NSR:Quay:53203” in the example json
  • “ParticipantRef” also has to match my choice of “SKY” as there are others in the full dataset. So only deviations when ParticipantRef = SKY.

The result is going to be a REST sensor that displays deviations in service on a line or stop.
I am hoping for some help in this forum, thanks.

PS: If especially interested: the REST endpoint is documented here: https://developer.entur.org/pages-real-time-intro, look for SIRI Lite.

{% set value_json = {
    "Siri": {
        "@version": "2.0",
        "@xmlns": "http://www.siri.org.uk/siri",
        "@xmlns:ns2": "http://www.ifopt.org.uk/acsb",
        "@xmlns:ns3": "http://www.ifopt.org.uk/ifopt",
        "@xmlns:ns4": "http://datex2.eu/schema/2_0RC1/2_0",
        "ServiceDelivery": {
            "MoreData": "false",
            "ProducerRef": "ENT",
            "RequestMessageRef": "0193d38b-cb1d-76c6-9e52-c5c1147f9741",
            "ResponseTimestamp": "2024-12-18T16:29:41.307902112+01:00",
            "SituationExchangeDelivery": {
                "@version": "2.0",
                "ResponseTimestamp": "2024-12-18T16:29:41.30793295+01:00",
                "Situations": {
                    "PtSituationElement": [
                        {
                            "Affects": {
                                "Networks": {
                                    "AffectedNetwork": [
                                        {
                                            "AffectedLine": {
                                                "LineRef": "SKY:Line:3",
                                                "Routes": {
                                                    "AffectedRoute": {
                                                        "StopPoints": {
                                                            "AffectedOnly": "true",
                                                            "AffectedStopPoint": [
                                                                {
                                                                    "StopPointRef": "NSR:Quay:53203"
                                                                }

                                                            ]
                                                        }
                                                    }
                                                }
                                            },
                                            "NetworkRef": "SKY:Network:1"
                                        },
                                        {
                                            "AffectedLine": {
                                                "LineRef": "SKY:Line:4",
                                                "Routes": {
                                                    "AffectedRoute": {
                                                        "StopPoints": {
                                                            "AffectedOnly": "true",
                                                            "AffectedStopPoint": [
                                                                {
                                                                    "StopPointRef": "NSR:Quay:53203"
                                                                }
                                                            ]
                                                        }
                                                    }
                                                }
                                            },
                                            "NetworkRef": "SKY:Network:1"
                                        },
                                        {
                                            "AffectedLine": {
                                                "LineRef": "SKY:Line:27",
                                                "Routes": {
                                                    "AffectedRoute": {
                                                        "StopPoints": {
                                                            "AffectedOnly": "true",
                                                            "AffectedStopPoint": [
                                                                {
                                                                    "StopPointRef": "NSR:Quay:53908"
                                                                },
                                                                {
                                                                    "StopPointRef": "NSR:Quay:50177"
                                                                }
                                                            ]
                                                        }
                                                    }
                                                }
                                            },
                                            "NetworkRef": "SKY:Network:1"
                                        },
                                        {
                                            "AffectedLine": {
                                                "LineRef": "SKY:Line:300",
                                                "Routes": {
                                                    "AffectedRoute": {
                                                        "StopPoints": {
                                                            "AffectedOnly": "true",
                                                            "AffectedStopPoint": [
                                                                {
                                                                    "StopPointRef": "NSR:Quay:53030"
                                                                },
                                                                {
                                                                    "StopPointRef": "NSR:Quay:53236"
                                                                },
                                                                {
                                                                    "StopPointRef": "NSR:Quay:53245"
                                                                }
                                                            ]
                                                        }
                                                    }
                                                }
                                            },
                                            "NetworkRef": "SKY:Network:1"
                                        }
                                    ]
                                }
                            },
                            "CreationTime": "2024-12-18T13:58:38.15+01:00",
                            "Description": "Glaskar- og Selviktunnelen er varsla stengd kl. 00.01–05.30 i retning mot Åsane/Knarvik på grunn av vedlikehaldsarbeid. Omkøyring via Ervikvegen kan føre til forseinkingar. Haldeplass Eidsvåg E39 blir ikkje brukt medan vegen er stengt. Bruk haldeplass Eidsvåg i Ervikveien.Linje 27: Haldeplass Tertneskrysset E39 blir ikkje brukt medan vegen er stengd. Bruk haldeplass Tertneskrysset.",
                            "ParticipantRef": "SKY",
                            "Progress": "open",
                            "ReportType": "general",
                            "Severity": "normal",
                            "SituationNumber": "SKY:SituationNumber:TX1197774",
                            "Source": {
                                "SourceType": "directReport"
                            },
                            "Summary": "Glaskar- og Selviktunnelen varsla stengd frå kl. 00.01",
                            "UndefinedReason": null,
                            "ValidityPeriod": {
                                "EndTime": "2024-12-19T05:30:00+01:00",
                                "StartTime": "2024-12-18T23:45:00+01:00"
                            }
                        }

                    ]
                }
            }
        }
    }
} %}

Hi again, I have had some moderate success with the rest sensor defined below:

rest:
  - resource: "https://api.entur.io/realtime/v1/rest/sx"
    scan_interval: 600
    sensor:
      - name: situation
        unique_id: 0193d38b-cb1d-76c6-9e52-c5c1147f9741
        value_template: >-
            {%- if value_json['Siri']['ServiceDelivery']['SituationExchangeDelivery']['Situations'].get('PtSituationElement',0)  -%}
              {%- for i in value_json['Siri']['ServiceDelivery']['SituationExchangeDelivery']['Situations']['PtSituationElement'] -%} 
                {%- if i | regex_findall(find='SKY:Line:23') -%}
                     {{ i["Description"] }}
                     {%- break -%}
                {%- endif -%}
              {%- endfor -%}
            {%- endif -%}

It seems to be working, and even better, returns Unknown if there are no deviations on that particular line. I wonder if it could be more robust though as it doesn’t really parse the json, just looks using a regex, and stops after the first success. I’m not even checking if a deviation is still active.

Also, the Description entry can be a “list” with both English and Norwegian alternatives, and that is not handled here. So it might look like this:

                          "Description": [
                                {
                                    "#text": "Avgangen fra Bergkrystallen kl. 09:40 er innstilt mellom Høyenhall kl. 09:50 og Løren kl. 10:19 på grunn av en feil på transportmiddelet.",
                                    "@xml:lang": "NO"
                                },
                                {
                                    "#text": "Departure from Bergkrystallen at 09:40 is cancelled between Høyenhall at 09:50 and Løren at 10:19 due to technical issues",
                                    "@xml:lang": "EN"
                                }
                            ],

The api is outputting invalid json. It seems to be a bug in their system.

EDIT: No it isn’t. Never mind, it’s an issue with the way you’re testing it.

This is the proper json the API will get and this is what you should use for testing. You have to replace null with None if you plan to paste JSON directly into template editor as an object. Otherwise, leave it as a string and use from_json.

{% set value_json = {"Siri":{"@version":"2.0","@xmlns":"http://www.siri.org.uk/siri","@xmlns:ns2":"http://www.ifopt.org.uk/acsb","@xmlns:ns3":"http://www.ifopt.org.uk/ifopt","@xmlns:ns4":"http://datex2.eu/schema/2_0RC1/2_0","ServiceDelivery":{"MoreData":"false","ProducerRef":"ENT","RequestMessageRef":"0193d38b-cb1d-76c6-9e52-c5c1147f9741","ResponseTimestamp":"2024-12-18T16:29:41.307902112+01:00","SituationExchangeDelivery":{"@version":"2.0","ResponseTimestamp":"2024-12-18T16:29:41.30793295+01:00","Situations":{"PtSituationElement":[{"Affects":{"Networks":{"AffectedNetwork":[{"AffectedLine":{"LineRef":"SKY:Line:3","Routes":{"AffectedRoute":{"StopPoints":{"AffectedOnly":"true","AffectedStopPoint":[{"StopPointRef":"NSR:Quay:53203"}]}}}},"NetworkRef":"SKY:Network:1"},{"AffectedLine":{"LineRef":"SKY:Line:4","Routes":{"AffectedRoute":{"StopPoints":{"AffectedOnly":"true","AffectedStopPoint":[{"StopPointRef":"NSR:Quay:53203"}]}}}},"NetworkRef":"SKY:Network:1"},{"AffectedLine":{"LineRef":"SKY:Line:27","Routes":{"AffectedRoute":{"StopPoints":{"AffectedOnly":"true","AffectedStopPoint":[{"StopPointRef":"NSR:Quay:53908"},{"StopPointRef":"NSR:Quay:50177"}]}}}},"NetworkRef":"SKY:Network:1"},{"AffectedLine":{"LineRef":"SKY:Line:300","Routes":{"AffectedRoute":{"StopPoints":{"AffectedOnly":"true","AffectedStopPoint":[{"StopPointRef":"NSR:Quay:53030"},{"StopPointRef":"NSR:Quay:53236"},{"StopPointRef":"NSR:Quay:53245"}]}}}},"NetworkRef":"SKY:Network:1"}]}},"CreationTime":"2024-12-18T13:58:38.15+01:00","Description":"Glaskar- og Selviktunnelen er varsla stengd kl. 00.01–05.30 i retning mot Åsane/Knarvik på grunn av vedlikehaldsarbeid. Omkøyring via Ervikvegen kan føre til forseinkingar. Haldeplass Eidsvåg E39 blir ikkje brukt medan vegen er stengt. Bruk haldeplass Eidsvåg i Ervikveien.Linje 27: Haldeplass Tertneskrysset E39 blir ikkje brukt medan vegen er stengd. Bruk haldeplass Tertneskrysset.","ParticipantRef":"SKY","Progress":"open","ReportType":"general","Severity":"normal","SituationNumber":"SKY:SituationNumber:TX1197774","Source":{"SourceType":"directReport"},"Summary":"Glaskar- og Selviktunnelen varsla stengd frå kl. 00.01","UndefinedReason":None,"ValidityPeriod":{"EndTime":"2024-12-19T05:30:00+01:00","StartTime":"2024-12-18T23:45:00+01:00"}}]}}}}} %}

Then your template is just (assuming value_json is supplied)

{% set element = value_json.Siri.ServiceDelivery.SituationExchangeDelivery.Situations.get('PtSituationElement', []) | first | default({}) %}
{{ element.get('Description', "No Description") }}

Thanks for that @petro. I need to look deeper into the structure though. This is what I have so far but I hope to get a bit of a hint to help with the templating. See below, I really want to get the description for a specific bus line (or even specific stop). I couldn’t paste enough of the json in here so it needs to be fetched from EnTur. Ideally, in this case, I would want to check if 'LineRef' == 'SKY:Line:40' and not just a simple regex. LineRef comes in at different levels depending on the deviation.

{% set value_json = {"Siri": ... *paste from "https://api.entur.io/realtime/v1/rest/sx"* ... %}

{% set elements = value_json.Siri.ServiceDelivery.SituationExchangeDelivery.Situations.get('PtSituationElement', []) | default({}) %}
  
{%- set lookfor = 'SKY:Line:40' -%}
{%- if elements %}
  {%- for element in elements %} 
    {%- set descr = element.get('Description', "No Description") %}
    {%- if element | regex_findall(find=lookfor) and element["Progress"] == "open"%}
    {{ 
         descr }}
         {%- break -%}
    {%- endif -%}
  {%- endfor -%}
{%- endif -%}

Actually this isn’t going to work.
The Description is too large to be the state (errors in log say so). I was hoping to avoid writing an integration :frowning:

You don’t need to make a custom integration. Put the description in an attribute. Also, when you reply to people, don’t reply to the thread, reply to comments. Otherwise people do not get notifications that you responded.

Anyways, here’s a template to get all open progress descriptions for line 'SKY:Line:40'

{% set value_json = {"Siri":{"@version":"2.0","@xmlns":"http://www.siri.org.uk/siri","@xmlns:ns2":"http://www.ifopt.org.uk/acsb","@xmlns:ns3":"http://www.ifopt.org.uk/ifopt","@xmlns:ns4":"http://datex2.eu/schema/2_0RC1/2_0","ServiceDelivery":{"MoreData":"false","ProducerRef":"ENT","RequestMessageRef":"0193d38b-cb1d-76c6-9e52-c5c1147f9741","ResponseTimestamp":"2024-12-18T16:29:41.307902112+01:00","SituationExchangeDelivery":{"@version":"2.0","ResponseTimestamp":"2024-12-18T16:29:41.30793295+01:00","Situations":{"PtSituationElement":[{"Affects":{"Networks":{"AffectedNetwork":[{"AffectedLine":{"LineRef":"SKY:Line:3","Routes":{"AffectedRoute":{"StopPoints":{"AffectedOnly":"true","AffectedStopPoint":[{"StopPointRef":"NSR:Quay:53203"}]}}}},"NetworkRef":"SKY:Network:1"},{"AffectedLine":{"LineRef":"SKY:Line:4","Routes":{"AffectedRoute":{"StopPoints":{"AffectedOnly":"true","AffectedStopPoint":[{"StopPointRef":"NSR:Quay:53203"}]}}}},"NetworkRef":"SKY:Network:1"},{"AffectedLine":{"LineRef":"SKY:Line:27","Routes":{"AffectedRoute":{"StopPoints":{"AffectedOnly":"true","AffectedStopPoint":[{"StopPointRef":"NSR:Quay:53908"},{"StopPointRef":"NSR:Quay:50177"}]}}}},"NetworkRef":"SKY:Network:1"},{"AffectedLine":{"LineRef":"SKY:Line:300","Routes":{"AffectedRoute":{"StopPoints":{"AffectedOnly":"true","AffectedStopPoint":[{"StopPointRef":"NSR:Quay:53030"},{"StopPointRef":"NSR:Quay:53236"},{"StopPointRef":"NSR:Quay:53245"}]}}}},"NetworkRef":"SKY:Network:1"}]}},"CreationTime":"2024-12-18T13:58:38.15+01:00","Description":"Glaskar- og Selviktunnelen er varsla stengd kl. 00.01–05.30 i retning mot Åsane/Knarvik på grunn av vedlikehaldsarbeid. Omkøyring via Ervikvegen kan føre til forseinkingar. Haldeplass Eidsvåg E39 blir ikkje brukt medan vegen er stengt. Bruk haldeplass Eidsvåg i Ervikveien.Linje 27: Haldeplass Tertneskrysset E39 blir ikkje brukt medan vegen er stengd. Bruk haldeplass Tertneskrysset.","ParticipantRef":"SKY","Progress":"open","ReportType":"general","Severity":"normal","SituationNumber":"SKY:SituationNumber:TX1197774","Source":{"SourceType":"directReport"},"Summary":"Glaskar- og Selviktunnelen varsla stengd frå kl. 00.01","UndefinedReason":None,"ValidityPeriod":{"EndTime":"2024-12-19T05:30:00+01:00","StartTime":"2024-12-18T23:45:00+01:00"}}]}}}}} %}
{% set ns = namespace(items=[]) %}
{% for element in value_json.Siri.ServiceDelivery.SituationExchangeDelivery.Situations.get('PtSituationElement', []) | selectattr('Progress', 'eq', 'open') %}
  {% set lines = element.Affects.Networks.AffectedNetwork | map(attribute='AffectedLine.LineRef')  %}
  {% if 'SKY:Line:40' in lines %}
    {% set ns.items = ns.items + [ element.Description ] %}
  {% endif %}
{% endfor %}
{{ ns.items | join(' ') }}

remove the value_json line when putting it in the template entity and replace it with an attribute from your rest sensor that has the rest response.

Ok, thanks, a working example like this is just what I was looking for. I still have a job to do, not all line entries come under eg AffectedNetwork.

But I hope I can figure something out now thanks.

I think I will have to kick this in to touch for a while and try a different approach another time. Something is going wrong when the rest service reads the endpoint. Sometimes value is rendered as XML (line_3 config), and sometimes as JSON. Very strange. Consequently value_json isnt always set so trying and testing different things is a nightmare.

Also, I couldn’t get all of the parts of your template to work, in particular when the map(attribute… part is present, there is an error.

UndefinedError: 'str object' has no attribute 'AffectedLine'

This is my rest sensor(s), “line_40” isn’t actually working but this is as far as I have got.

rest:
  - resource: "https://api.entur.io/realtime/v1/rest/sx?datasetId=SKY"
    scan_interval: 120
    sensor:
      - name: line_40
        unique_id: 0193d38b-cb1d-76c6-9e52-c5c1147f9741-1
        value_template: >
            {%- set ns = namespace(items=[]) %}
            {%- for element in value_json.Siri.ServiceDelivery.SituationExchangeDelivery.Situations.get('PtSituationElement', []) | selectattr('Progress', 'eq', 'open') %}
              {%- set lines = element.Affects.Networks.AffectedNetwork.AffectedLine.LineRef  %}
              {%- if 'SKY:Line:964' in lines %}
                {%- set ns.items = ns.items + [ element.Description ] %}
              {%- endif %}
            {%- endfor %}
            {{ ns.items | join(' ') | truncate }}
      - name: line_3
        unique_id: 0193d38b-cb1d-76c6-9e52-c5c1147f9741-2
        value_template: >
            {{ value[:250] }}

Is that error coming from you testing the API in the template editor or from the actual rest entity?

I have it in the template editor. Havent tried in the sensor because of the other errors.

Ok, you’re copying it wrong into the template editor. It’s not working for you because null is in the json you’re testing with. You need to replace null with None.

That’s why that area of your data is a string.

This is what I am using when testing in the template editor. No null or None. I tried some other things too.

I don’t understand why value in my rest sensor is sometimes xml and sometimes json either. Unless there is something funny happening with the REST service, or even a bug in HA. I will try to test with some python code after Christmas to see if the service is working properly.

Thx

{%- set value_json = {"Siri":{"@version":"2.0","@xmlns":"http://www.siri.org.uk/siri","@xmlns:ns2":"http://www.ifopt.org.uk/acsb","@xmlns:ns3":"http://www.ifopt.org.uk/ifopt","@xmlns:ns4":"http://datex2.eu/schema/2_0RC1/2_0","ServiceDelivery":{"MoreData":"false","ProducerRef":"ENT","ResponseTimestamp":"2024-12-20T13:52:08.898642154+01:00","SituationExchangeDelivery":{"@version":"2.0","ResponseTimestamp":"2024-12-20T13:52:08.898646901+01:00","Situations":{"PtSituationElement":[{"Affects":{"Networks":{"AffectedNetwork":{"AffectedLine":{"LineRef":"SKY:Line:964"},"NetworkRef":"SKY:Network:1"}}},"CreationTime":"2024-11-20T07:13:44.36+01:00","Description":"Langebrua på Voss er stengd for gjennomkøyring på grunn av vegarbeid i Uttrågata.  Medan vegen er stengd får linja omkøyring via Strandavegen mellom Voss og Skjerpe/Motræet. Linja køyrer ikkje om Hardangervegen når Langebrua er stengd, og haldeplassane Langebrua, Voss kommunikasjon, Esso Hardangervegen, Fredheim og Vetleflaten er ikkje i bruk.Les meir om endringane og sjå ny rutetabell for linja på skyss.no/avvik.","Language":"en","ParticipantRef":"SKY","Progress":"open","ReportType":"general","Severity":"normal","SituationNumber":"SKY:SituationNumber:TX1194607","Source":{"SourceType":"directReport"},"Summary":"Omkøyring på grunn av stengd veg","ValidityPeriod":{"StartTime":"2024-11-14T08:30:00+01:00"}},{"Affects":{"Networks":{"AffectedNetwork":{"AffectedLine":{"LineRef":"SKY:Line:460","Routes":{"AffectedRoute":{"StopPoints":{"AffectedOnly":"true","AffectedStopPoint":[{"StopPointRef":"NSR:Quay:52077"},{"StopPointRef":"NSR:Quay:52071"},{"StopPointRef":"NSR:Quay:52061"},{"StopPointRef":"NSR:Quay:52054"},{"StopPointRef":"NSR:Quay:52044"},{"StopPointRef":"NSR:Quay:51740"},{"StopPointRef":"NSR:Quay:51737"},{"StopPointRef":"NSR:Quay:51730"},{"StopPointRef":"NSR:Quay:51720"},{"StopPointRef":"NSR:Quay:51714"},{"StopPointRef":"NSR:Quay:51707"},{"StopPointRef":"NSR:Quay:51700"},{"StopPointRef":"NSR:Quay:51692"},{"StopPointRef":"NSR:Quay:52646"},{"StopPointRef":"NSR:Quay:52636"},{"StopPointRef":"NSR:Quay:52625"},{"StopPointRef":"NSR:Quay:52615"},{"StopPointRef":"NSR:Quay:52605"},{"StopPointRef":"NSR:Quay:52597"},{"StopPointRef":"NSR:Quay:52590"},{"StopPointRef":"NSR:Quay:52577"}]}}}},"NetworkRef":"SKY:Network:1"}}},"CreationTime":"2024-06-26T08:14:51.79+02:00","Description":"Omkøyring via Kolltveitunnelen i retning mot Straume då Bildøyvegen er stengd. Haldeplassane Døsjeskiftet og Bildøy bru blir ikkje brukt medan vegen er stengd. Sjå meir informasjon om stenginga på skyss.no/avvik.","Language":"en","ParticipantRef":"SKY","Progress":"open","ReportType":"general","Severity":"normal","SituationNumber":"SKY:SituationNumber:TX1180107","Source":{"SourceType":"directReport"},"Summary":"Omkøyring på grunn av stengd veg","ValidityPeriod":{"StartTime":"2024-06-10T08:50:00+02:00"}},{"Affects":{"Networks":{"AffectedNetwork":{"AffectedLine":{"LineRef":"SKY:Line:1033"},"NetworkRef":"SKY:Network:1"}}},"CreationTime":"2024-12-20T06:11:18.83+01:00","Description":"Ferjesambandet Jektavik–Nordhuglo–Hodnanes innstilt på grunn av beredskapsoppdrag i sambandet i løpet av natta. Første tur i sambandet fredag 20. desember har avgang kl. 11.20 frå Jektavik.","Language":"en","ParticipantRef":"SKY","Progress":"closed","ReportType":"general","Severity":"normal","SituationNumber":"SKY:SituationNumber:TX1197967","Source":{"SourceType":"directReport"},"Summary":"Jektavik–Nordhuglo–Hodnanes innstilt til kl. 11.20","ValidityPeriod":{"EndTime":"2024-12-20T15:28:26.998+01:00","StartTime":"2024-12-20T06:11:00+01:00"}},{"Affects":{"Networks":{"AffectedNetwork":{"AffectedLine":{"LineRef":"SKY:Line:982","Routes":{"AffectedRoute":{"StopPoints":{"AffectedOnly":"true","AffectedStopPoint":{"StopPointRef":"NSR:Quay:52900"}}}}},"NetworkRef":"SKY:Network:1"}}},"CreationTime":"2024-07-02T11:09:39.63+02:00","Description":"Haldeplass Brattabø, i retning mot Flatabø, er stengd frå måndag 24. juni og inntil vidare på grunn av vegarbeid.","Language":"en","ParticipantRef":"SKY","Progress":"open","ReportType":"general","Severity":"normal","SituationNumber":"SKY:SituationNumber:TX1180735","Source":{"SourceType":"directReport"},"Summary":"Haldeplass Brattabø ikkje i bruk","ValidityPeriod":{"StartTime":"2024-06-24T04:00:00+02:00"}}]}}}}} %}
{%- set ns = namespace(items=[]) %}
{%- for element in value_json.Siri.ServiceDelivery.SituationExchangeDelivery.Situations.get('PtSituationElement', []) | selectattr('Progress', 'eq', 'open') %}
  {% set lines = element.Affects.Networks.AffectedNetwork | map(attribute='AffectedLine.LineRef')  %}
  {%- if 'SKY:Line:964' in lines %}
    {%- set ns.items = ns.items + [ element.Description ] %}
  {%- endif %}
{%- endfor %}
{{ ns.items | join(' ') | truncate }}

image

I have had another look at this and worked my way through the spec for SIRI SX messages as well as made a working python example. The result of AffectedNetworks can be a dict or a list of dicts so needed special handling.

Here’s a rest example with a template that is working. The first line_x should always be “Unknown”. The second will probably give a message.
Not very elegant I know, I’d like to do something about that, I tried to make a macro, but failed :frowning:

rest:
  - resource: "https://api.entur.io/realtime/v1/rest/sx?datasetId=SKY"
    headers:
        User-Agent: Home Assistant
        Content-Type: application/xml
    scan_interval: 120
    sensor:
      - name: line_x
        unique_id: 0193d38b-cb1d-76c6-9e52-c5c1147f9741-41
        value_template: >
            {%- set look_for = 'does not exist' %}
            {%- set ns = namespace(items=[]) %}
            {%- for element in value_json.Siri.ServiceDelivery.SituationExchangeDelivery.Situations.get('PtSituationElement', []) | selectattr('Progress', 'eq', 'open') %}
              {%- set summary = element.get('Summary') %}
              {%- set description = element.get('Description') %}
              {%- set affects = element.get('Affects') %}
              {%- set networks = affects.get('Networks') %}
              {%- if networks %}
                {%- set affected_networks = networks.get('AffectedNetwork') %}
                {%- if affected_networks is mapping %}
                  {%- set affected_line = affected_networks.get('AffectedLine')  %}
                  {%- set line_ref = affected_line.get('LineRef') %}
                  {%- if look_for == line_ref %}
                    {%- set ns.items = ns.items + [ summary ] %}
                    {%- set found = 1 %}
                    {%- set found = found + 1 %}
                  {%- endif %}
                {%- elif affected_networks is list %}
                  {%-for an in affected_networks %}
                    {%- set affected_line = an.get('AffectedLine')  %}
                    {%- set line_ref = affected_line.get('LineRef') %}
                    {%- if look_for == line_ref %}
                      {%- set ns.items = ns.items + [ summary ] %}
                    {%- endif %}
                  {%- endfor %}
                {%- endif %}
              {%- endif %}
            {%- endfor %}
            {%- if ns.items | length >= 1 %}
            {{ ns.items | join(' ') }}
            {%- else %}
            unknown
            {%- endif %}
      - name: line_y
        unique_id: 0193d38b-cb1d-76c6-9e52-c5c1147f9741-3
        value_template: >
            {%- set look_for = 'SKY:Line:460' %}
            {%- set ns = namespace(items=[]) %}
            {%- for element in value_json.Siri.ServiceDelivery.SituationExchangeDelivery.Situations.get('PtSituationElement', []) | selectattr('Progress', 'eq', 'open') %}
              {%- set summary = element.get('Summary') %}
              {%- set description = element.get('Description') %}
              {%- set affects = element.get('Affects') %}
              {%- set networks = affects.get('Networks') %}
              {%- if networks %}
                {%- set affected_networks = networks.get('AffectedNetwork') %}
                {%- if affected_networks is mapping %}
                  {%- set affected_line = affected_networks.get('AffectedLine')  %}
                  {%- set line_ref = affected_line.get('LineRef') %}
                  {%- if look_for == line_ref %}
                    {%- set ns.items = ns.items + [ summary ] %}
                    {%- set found = 1 %}
                    {%- set found = found + 1 %}
                  {%- endif %}
                {%- elif affected_networks is list %}
                  {%-for an in affected_networks %}
                    {%- set affected_line = an.get('AffectedLine')  %}
                    {%- set line_ref = affected_line.get('LineRef') %}
                    {%- if look_for == line_ref %}
                      {%- set ns.items = ns.items + [ summary ] %}
                    {%- endif %}
                  {%- endfor %}
                {%- endif %}
              {%- endif %}
            {%- endfor %}
            {%- if ns.items | length >= 1 %}
            {{ ns.items | join(' ') }}
            {%- else %}
            unknown
            {%- endif %}

Things I’d like to be able to do next:

  • make a (more elegant) macro that works
  • stuff the description variable in to an attribute.

Dunno how you attempted to make the macro, but any macro defined inside a template will only ever work inside of that one template. If you want a macro to be usable across several templates, you have to put it in the custom_templates folder. Also a macro can only ever return a string, so for any more complex data types you will need to pass them as JSON using the to_json / from_json filters.

EDIT: I fixed this now - figured out from the template editor that its endmacro not end_macro.

Put the macro below in /homeassistant/custom_templates/skyss_siri_sx.jinja
Then in the rest template:

rest:
  - resource: "https://api.entur.io/realtime/v1/rest/sx?datasetId=SKY"
    headers:
        User-Agent: Home Assistant
        Content-Type: application/xml
    scan_interval: 120
    sensor:
      - name: line_x
        unique_id: 0193d38b-cb1d-76c6-9e52-c5c1147f9741-41
        value_template: >
            {% from 'skyss_siri_sx.jinja' import skyss_siri_sx %}
            {{ skyss_siri_sx('SKY:Line:1', value_json) }}

I would still like to be able to add “description” as an attribute to the rest sensor.


That’s what I tried, and need more time to figure out why it wouldn’t work.


{% macro skyss_siri_sx(line_deviation, value_json) %}
{%- set ns = namespace(items=[]) %}
{%- set look_for = line_deviation %}
{%- for element in value_json.Siri.ServiceDelivery.SituationExchangeDelivery.Situations.get('PtSituationElement', []) | selectattr('Progress', 'eq', 'open') %}
  {%- set summary = element.get('Summary') %}
  {%- set description = element.get('Description') %}
  {%- set affects = element.get('Affects') %}
  {%- set networks = affects.get('Networks') %}
  {%- if networks %}
    {%- set affected_networks = networks.get('AffectedNetwork') %}
    {%- if affected_networks is mapping %}
      {%- set affected_line = affected_networks.get('AffectedLine')  %}
      {%- set line_ref = affected_line.get('LineRef') %}
      {%- if look_for == line_ref %}
        {%- set ns.items = ns.items + [ summary ] %}
      {%- endif %}
    {%- elif affected_networks is list %}
      {%-for an in affected_networks %}
        {%- set affected_line = an.get('AffectedLine')  %}
        {%- set line_ref = affected_line.get('LineRef') %}
        {%- if look_for == line_ref %}
          {%- set ns.items = ns.items + [ summary ] %}
        {%- endif %}
      {%- endfor %}
    {%- endif %}
  {%- endif %}
{%- endfor %}
{%- if ns.items | length >= 1 %}
{{ ns.items | join(' ') }}
{%- else %}
unknown
{%- endif %}
{% endmacro %}

Hi! I have this working reliably now. See my answer in the thread.

I had some issues where the returned data would switch between json and xml. By requesting only xml and not relying on any default this is now solved.

The implementation uses “Summary” from the value_json as the spec for the transport situation exchange format says this will not exceed 160 characters, and will therefore fit as the entity state. It would though be of benefit if the longer “Description” was available as an attribute to the sensor as then a more_info action could show this. I have figured out a way to set the json_attributes_path by calculating the JSON path in my template macro. The problem is that the rest sensor doesn’t support templating the json_attributes_path setting so I cant apply it and haven’t found any other way to dynamically apply a value to the path. JSON expressions cannot be used in this case either as the data is nested in different ways depending on the information. I have tested that the calculated json path is working in a hardcoded test.

I have created a feature request for templating of json_attributes_path:

A future templating of json_attributes_path would be of benefit here.

json_attributes_path supports json path which itself is a language. It’s very likely you can write something that works for this without needing to template it.

Unfortunately I did try but not find a suitable expression. The requirement would be to return the selected item of PtSituationElement where any nested LineRef under Affects (and they turn up at different levels) matches.

{
    "Siri": {
        "ServiceDelivery": {
            "SituationExchangeDelivery": {
                "Situations": {
                    "PtSituationElement": [
                        {
                            "Affects": {
                                "Networks": {
                                    "AffectedNetwork": {
                                        "AffectedLine": {
                                            "LineRef": "SKY:Line:964"
                                        },
                                        "NetworkRef": "SKY:Network:1"
                                    }
                                }
                            },
                            "CreationTime": "2024-11-20T07:13:44.36+01:00",
                            "Description": "Langebrua på Voss er stengd for gjennomkøyring på grunn av vegarbeid i Uttrågata.  Medan vegen er stengd får linja omkøyring via Strandavegen mellom Voss og Skjerpe/Motræet. Linja køyrer ikkje om Hardangervegen når Langebrua er stengd, og haldeplassane Langebrua, Voss kommunikasjon, Esso Hardangervegen, Fredheim og Vetleflaten er ikkje i bruk.Les meir om endringane og sjå ny rutetabell for linja på skyss.no/avvik.",
                            "Summary": "Omkøyring på grunn av stengd veg"
                        }
                    ]
                }
            }
        }
    }
}
      

EDIT: Updated example with parameterized script.

I have this working now. Finally! What this configuration does is retrieves any deviation for the specified bus or light rail line as announced by the Norwegian national travel service ENTUR. I am using SKYSS (SKY in the url) which is the local transport operator for where I live. It should also work with other operators, but would need testing.

I will probably tweak the state to be one of “Normal operation” and “Deviation” instead of the current summary text, but the heavy lifting is done.

This is how:

  1. You will need the template macro skyss_siri_sx_dict_from_json.jinja in the custom_templates folder. This creates a json serialized dict with all the information:
{%- macro skyss_siri_sx_dict_from_json(line_deviation, value_json) %}
{%- set ns = namespace(items=[], summary="", description="") %}
{%- set look_for = line_deviation %}
{%- for sed in value_json.Siri.ServiceDelivery.get('SituationExchangeDelivery') %}
{%    set situations = sed.get('Situations') %}
{%    for element in situations.get('PtSituationElement', [] )| selectattr('Progress', 'eq', 'OPEN')  %}
{%-     set summary = element.get('Summary')[0].get('value') %}
{%-     set description = element.get('Description')[0].get('value') %}
{%-     set affects = element.get('Affects') %}
{%-     set networks = affects.get('Networks') %}
{%-     if networks %}
{%-       set affected_networks = networks.get('AffectedNetwork') %}
{%-       for an in affected_networks %}
{%-         set affected_line = an.get('AffectedLine')  %}
{%-         set line_ref = affected_line[0].get('LineRef').get('value') %}
{%-         if look_for == line_ref %}
{%-           set ns.items = ns.items + [ summary ] %}
{%-           set ns.description = ns.description + description %}
{%-         endif %}
{%-       endfor %}
{%-     endif %}
{%-   endfor %}
{%- endfor %}
{%- if ns.items | length >= 1 %}
{%-   set ns.summary = ns.items | join(' ') %}
{%- else %}
{%-   set ns.summary = "unknown" %}
{%- endif %}
{%- set kvps = [('items', ns.items), ('summary', ns.summary), ('description', ns.description) ] %}
{%- set d = dict.from_keys(kvps) %}
{{- d|to_json }}
{%- endmacro %}
  1. create a rest_command entry in configuration.yaml. This calls the situation exchange API and gets all the data for the SKY operator.
rest_command:
    skyss_sx: 
        url: https://api.entur.io/realtime/v1/rest/sx?datasetId=SKY
        method: GET
        headers:
            User-Agent: Home Assistant
            Content-Type: application/json
  1. Create a Home Assistant script that looks like this (this will return the serialized dict to the template sensor):
alias: skyss_sx
description: Call the SIRI SX Rest API for Skyss
sequence:
  - action: rest_command.skyss_sx
    metadata: {}
    data: {}
    response_variable: skyss_avvik
  - variables:
      sx_dict_var: >
        {% from 'skyss_siri_sx_dict_from_json.jinja' import
        skyss_siri_sx_dict_from_json %} {{
        skyss_siri_sx_dict_from_json(line_deviation,
        skyss_avvik.content)|from_json }}
    enabled: true
  - stop: retrieved deviation
    response_variable: sx_dict_var
  1. create a template trigger sensor in configuration.yaml. SKY:Line:990 below is the line I have used for testing and needs to be set correctly for the line you are interested in (I am actually interested in SKY:Line:1 but that is functioning today so can’t test with that!)
template:
  - trigger:
      - platform: time_pattern
        minutes: /1
    action: 
      - service: script.skyss_sx
        data:
            line_deviation: 'SKY:Line:990'
        response_variable: sx_dict_var
    sensor:
      - name: line_bybanen_avvik
        unique_id: line_bybanen_avvik
        state: "{{ sx_dict_var.get('summary') }}"
        attributes:
            summary:  "{{ sx_dict_var.get('summary') }}"
            description: "{{ sx_dict_var.get('description') }}"