A Python service to display quasi-realtime UK railway station departure data in a hauntingly familiar fashion. This is designed for use on a Raspberry Pi and an SSD1322-based 256x64 SPI OLED screen.
This project uses the publicly available Transport API, and is strongly inspired by the work of many others. It is structured for use on the balenaCloud platform, which offers a free tier.
Sign up for the Transport API, and generate an app ID and API key (note the free tier has a limit of 1000 requests a day).
These environment variables can be specified using the balenaCloud dashboard, allowing you to set up mutiple signs in one application for different stations.
Key | Default | Description |
---|---|---|
activeTimes |
(none) | Optional. One or more time ranges during which the display will show data. See below for more details. |
blankTimes |
(none) | Optional. One or more time ranges during which the display will be entirely blank. Takes precedence over activeTimes . See below for more details. |
callingAt |
(none) | Optional. A National Rail station code that trains must call at in order to be shown, e.g. BFR . |
departFrom |
(none) | Required. A National Rail station code to show departures from, e.g. STP . |
displayRotation |
0 |
Optional. How much to rotate the display; either 0 or 180 . Useful if your display placement is constrained. |
minDepartureMin |
0 |
Optional. If greater than zero, trains departing in less than this many minutes are not shown. This allows you to hide trains if you can't get to the platform in time to catch them. |
outOfHoursName |
(none) | Optional. Name shown when current time is outside active hours, e.g. London St Pancras . If set to _clock_ , the display will just show the current time. If set to _blank_ then the display will be completely blanked. |
slowStations |
(none) | Optional. If a train calls at a station in this list, it is marked "slow". |
showCallingAt |
true |
Optional. Whether to show "calling at" for the first departure. If false , a fourth departure is shown instead. |
showPlatform |
true |
Optional. Whether to show platform numbers for departures. |
showUpdateCountdown |
false |
Optional. Whether to show a visual indicator of how long is left until the data is refreshed. |
refreshTime |
120 |
Optional. Seconds between data refresh, during active hours. Values will be clamped to the range 15 - 3600, inclusive. |
transportApi_apiKey |
(none) | Required. Transport API key, e.g. feedface8badf00ddecafbaddead . |
transportApi_appId |
(none) | Required. Transport API application ID, e.g. 12345678 . |
TZ |
Europe/London |
Optional. An Olsen timezone name to use. You almost certainly don't want to change this. |
Active and blank times are a comma-separated list of one or more time ranges. A time range consists of an optional day specifier and a start/end time pair. It has the format [ddd:]hh[[:]mm[[:]ss]]-hh[[:]mm[[:]ss]]
:
ddd:
: An optional case-insensitive day (Sun
,Mon
,Tue
,Wed
,Thu
,Fri
,Sat
,weekday
,weekend
,daily
) followed by a colon.hh
: A one- or two-digit hour (0-23) with optional leading zero (00-23).mm
: A two-digit minute (00-59) preceded by an optional colon.ss
: A two-digit second (00-59) preceded by an optional colon.- If the day is missing, it will be treated as
daily
. - If the minutes or seconds are missing, they will be treated as zero.
Examples of valid time ranges:
8-22
= every day, 08:00:00 until 22:00:00.08-22
= every day, 08:00:00 until 22:00:00.23-01
= every day, 23:00:00 until 01:00:00 the following day.weekday:8-22
= every weekday, 08:00:00 until 22:00:00.weekday:08-22
= every weekday, 08:00:00 until 22:00:00.weekend:1030-1545
= 19:00:00 Friday until 02:45:00 Saturday.fri:1930-0245
= 19:00:00 Friday until 02:45:00 Saturday.
It is permitted for time ranges to overlap. A time range does include its start time, but does not include its end time. This means that an active time of 09:00:00-10:00:00
will become inactive at 10:00:00
exactly.
You can be a bit loose with the day specifiers: Tue
, tue
, tues
, tuesday
, and Tuesday
all have the same meaning.
- During active times, the display will update with train data (if available) every
refreshTime
seconds. - During blank times, the display will be completely blank, not even showing the time. No train data will be fetched.
- During out-of-hours times, the display will show out-of-hours content. No train data will be fetched.
- If
outOfHoursName
is set to_clock_
, then only a clock will be displayed. - If
outOfHoursName
is set to_blank_
, then the display will be entirely blank. - If
outOfHoursName
is empty, then it will act as if it was set to the human-readable name of thedepartFrom
station. - If
outOfHoursName
is any other value, a "Welcome to" message will be displayed along with a clock.
- If
If activeTimes
is not given, the display will be updated at all times. If blankTimes
is not given, the display will not blank itself (unless outOfHoursName
is _blank_
).
For example, the configuration:
activeTimes=weekday:0730-0930,1715-1930,weekend:0900-2000
blankTimes=weekday:2100-0700,sat:0700-0800,sat:2200-0800,sun:2200-0700
outOfHoursName=My Home
would turn off the display overnight, and display a clock and "Welcome to My Home" message during the middle of a weekday.
If no station codes are given, all trains from the departure station are shown. If a station code is given, then a train will be shown only if it calls at that station. Note that simply passing through a station without stopping isn't enough.
If a train calls at a station in this list, it is marked as "slow". For example, TEA
would mark any trains calling at Teesside Airport as being "slow". This variable can have multiple stations listed, and any of them will cause the train to be marked as "slow".
There is also an advanced syntax, which looks like SVG=2,PBO=1
. This assigns a slowness penalty of 1 to services calling at Peterborough, and a slowness penalty of 2 to services calling at Stevenage. The highest value from all of the applicable penalties is used. This allows fine distinction between different speeds of service, such as "nonstop", "fast" and "slow". If any station has an explicit penalty, then all must have one. Penalties are in the range 1-9, inclusive.
While this project can use a pygame-based emulated display for local development, it is designed to work with an SSD1322-based 256x64 SPI display, preferably yellow OLED for an authentic look. I have used displays from AliExpress successfully.
The connections for one of these displays to the Raspberry Pi GPIO header are as follows, but it would be a good idea to check the connections with the datasheet of your particilar display before powering on as there's no guarantee yours will match the pinout of mine.
Display pin | Connection | Raspberry Pi pin |
---|---|---|
1 | Ground | 6 (Ground) |
2 | V+ (3.3V) | 1 (3v3 Power) |
4 | D0/SCLK |
23 (BCM11 SCLK ) |
5 | D1/SDIN |
19 (BCM10 MOSI ) |
14 | DC (data/command select) |
18 (BCM24 ) |
15 | RST (reset) |
22 (BCM25 ) |
16 | CS (chip select) |
24 (BCM8 CE0 ) |
First of all, thanks to the team over at Balena who blogged about their implementation, put it on Github, and inspired me to go one better.
Thanks also to Chris Hutchinson who originally started this project, and Blake who made some further improvements.
The fonts used were painstakingly put together by DanielHartUK
and can be found on GitHub at https://github.com/DanielHartUK/Dot-Matrix-Typeface. Huge thanks for making that resource available!