National Rail Integration

Thanks for the reports. This has been fixed in the latest commit


Thank you. All working again.

I have an invalid api token error in my config. Has anyone see this error before? Has been running fine for months and now stopped.

Are you running the latest version? (I updated the branch earlier in the morning)

Thanks for the swift response. I will give it a go when I get a sec.

Any plans on releasing this on HACS to make updating a little easier?


After I refreshed the code from the github the problem has been resolved.

Resolved for me too. Thank you for this great integration. Will save me some time in the mornings for sure.

I don’t use HACS. If there is a clear documentation on how to package it why not. Happy to receive a patch as well

I’ve never done it but the guidance for adding your repo to HACS is here. I’d assume it would get a lot of downloads from UK users.


Thanks. I added the descriptor. Don’t have time right now to set a dev instance with HACS.
Let me know if it works (if not I will try to dig deeper when time allows)

1 Like

Awesome, thanks.

Added your repo as a custom one in HACS and installed the integration and a couple of sensor, everything works perfectly. :+1:t2:

Added here too. Thanks.

Working here too. Great job!

Hi, I’m experimenting setting this up with Alex alerts to inform me of my commute before I leave, I’m particulary interested if the train is ontime, late (and by how much) or if cancelled.

I only need to update the data for 1-2 hrs per day for 3 days per week (when I travel), and would like to be able to change the refresh frequency during my these times to say update every 30 seconds, then switch off the updates until the next scheduled commute (or maybe change frequency to update every few hours).

Is it possible to change the update frequency or add the option on a future update?

Hello, I’m having a go at this and have copied the nationalrailuk folder into the custom_components folder in Home Assistant, but please can someone explain where I actually enter the api token?

Search for National Rail UK in HA integrations and add it. Then you enter the key as part of the config flow.


1 Like

Thanks very much! Its obvious once you mention it…

1 Like

I can see that the sensor attributes provide a list of several of the next trains to the destination. I’ve been trying to work out how to create some kind of departure board of all upcoming trains to the destination.

When I reference the ‘trains’ attribute however, I just get a full dump of all the text.

For eg tracking trains from BHM to COV:

“[{‘scheduled’: datetime.datetime(2023, 10, 14, 22, 51, tzinfo=<FixedOffset ‘+01:00’>), ‘expected’: datetime.datetime(2023, 10, 14, 22, 51, tzinfo=<FixedOffset ‘+01:00’>), ‘terminus’: ‘Northampton’, ‘destinations’: [{‘name’: ‘Coventry’, ‘time_at_destination’: datetime.datetime(2023, 10, 14, 23, 21, tzinfo=<FixedOffset ‘+01:00’>)}], ‘platform’: ‘4A’, ‘perturbation’: False}, {‘scheduled’: datetime.datetime(2023, 10, 14, 23, 16, tzinfo=<FixedOffset ‘+01:00’>), ‘expected’: datetime.datetime(2023, 10, 14, 23, 16, tzinfo=<FixedOffset ‘+01:00’>), ‘terminus’: ‘Coventry’, ‘destinations’: [{‘name’: ‘Coventry’, ‘time_at_destination’: datetime.datetime(2023, 10, 14, 23, 44, tzinfo=<FixedOffset ‘+01:00’>)}], ‘platform’: ‘5’, ‘perturbation’: False}, {‘scheduled’: datetime.datetime(2023, 10, 14, 23, 22, tzinfo=<FixedOffset ‘+01:00’>), ‘expected’: ‘Cancelled’, ‘terminus’: ‘Coventry’, ‘destinations’: [{‘name’: ‘Coventry’, ‘time_at_destination’: ‘Cancelled’}], ‘platform’: None, ‘perturbation’: True}]”

Has anyone managed to extract all this very useful information in a readable format?

Edit: Using “{{state_attr(‘sensor.train_schedule_bhm_cov’, ‘trains’)[1] }}”, I was able to get the next train after the current one.

“{‘scheduled’: datetime.datetime(2023, 10, 14, 23, 16, tzinfo=<FixedOffset ‘+01:00’>), ‘expected’: datetime.datetime(2023, 10, 14, 23, 16, tzinfo=<FixedOffset ‘+01:00’>), ‘terminus’: ‘Coventry’, ‘destinations’: [{‘name’: ‘Coventry’, ‘time_at_destination’: datetime.datetime(2023, 10, 14, 23, 44, tzinfo=<FixedOffset ‘+01:00’>)}], ‘platform’: ‘5’, ‘perturbation’: False}”

I’m still trying to figure out how to split the data so I can use it.

Edit 2: Lol figured it out:

“{{state_attr(‘sensor.train_schedule_bhm_cov’, ‘trains’)[1].expected }}” gives:

“2023-10-14 23:16:00+01:00”

Result. Thank you for reading my blog.

Last Edit:

To produce a list of all upcoming trains:

{% set numtrains = state_attr('sensor.train_schedule_bhm_cov', 'trains')|list|length  %} 
Trains to COV in next hour: {{numtrains}}
{% for i in range(0,numtrains) %}
{{state_attr('sensor.train_schedule_bhm_cov', 'trains')[i].expected}}
{% endfor %}

This will output:

Trains to COV in next hour: 6
2023-10-15 16:06:00+01:00
2023-10-15 16:21:00+01:00
2023-10-15 16:33:00+01:00
2023-10-15 17:06:00+01:00
2023-10-15 17:21:00+01:00
2023-10-15 17:33:00+01:00

Just pushed a new release last night that address an odd case where the national rail API returns results that are not actually going to your preferred destination


This might save someone a job if you wish to show the data as follows in a Markdown card. Just amend the 1st line for how many results to show and the 2nd for your sensor:

{% set numrows = 5 %}
{% set data = states.sensor.train_schedule_sot_man %}
{% if data and (data.attributes.trains|length) > 0 %}
<u><b>Next Trains - {{'_')[2] }} to {{'_')[3] }} {% if data.attributes.next_train_expected is not string %}{% set due = as_timestamp(data.attributes.next_train_expected) - as_timestamp(now()) %}{% endif %}{% if due is defined %}({% if due > 600 %}<font color="green">{% else %}<font color="red">{% endif %}{% if due > 60 %}{{ due|timestamp_custom('%M')|int+1 }} mins{% else %}Due now!{% endif %}</font>){% endif %}</b></u>
{% set ns = namespace(count=1) %}
{% for trains in data.attributes.trains %}
{% if ns.count <= numrows %}
<tr><td><b>Destination: </b></td><td>{{ trains.terminus }}</td></tr>
<tr><td><b>Scheduled: </b></td><td>{% if trains.scheduled is not string %}{% if trains.scheduled != trains.expected %}<s>{{ as_timestamp(trains.scheduled)|timestamp_custom('%H:%M') }}</s> {% if trains.expected is not string %}<i><font color="aqua">Expected: {{ as_timestamp(trains.expected)|timestamp_custom('%H:%M') }}</font></i>{% else %}<b>{% if trains.expected == 'Cancelled' %}<font color="red">{% else %}<font color="orange">{% endif %}{{ trains.expected }}</font></b>{% endif %}{% else %}{{ as_timestamp(trains.scheduled)|timestamp_custom('%H:%M') }}{% endif %}{% else %}<b>{% if trains.scheduled == 'Cancelled' %}<font color="red">{% else %}<font color="orange">{% endif %}{{ trains.scheduled }}</font></b>{% endif %}</td></tr>
<tr><td><b>Platform: </b></td><td>{% if trains.platform != none %}{{ trains.platform }}{% elif trains.expected == 'Cancelled' %}<font color="gray">N/A</font>{% else %}<font color="gray">Unknown</font>{% endif %}</td></tr>
<tr><td><b>Arriving: </b></td><td>{% if trains.destinations[0].time_at_destination is not string %}{% if trains.scheduled != trains.expected %}<i><font color="aqua">{% endif %}{{ as_timestamp(trains.destinations[0].time_at_destination)|timestamp_custom('%H:%M') }}{% else %}<font color="gray">{% if trains.destinations[0].time_at_destination == 'Cancelled' %}N/A{% else %}Unknown{% endif %}</font>{% endif %}</td></tr>
{% if ns.count <= (numrows-1) and (ns.count-data.attributes.trains|length) != 0 %}<br>{% endif %}
{% set ns.count = ns.count + 1 %}
{% endif %}
{% endfor %}
{% else %}
<u><b>Next Trains - {{'_')[2] }} to {{'_')[3] }}</b></u>

<i>No more trains!</i>
{% endif %}