Real Time Trains API
Mark Elvers
2 min read

Categories

  • OCaml

Tags

  • tunbury.org

After the Heathrow substation electrical fire, I found myself in Manchester with a long train ride ahead. Checking on Real Time Trains for the schedule I noticed that they had an API. With time to spare, I registered for an account and downloaded the sample code from ocaml-cohttp.

The API account details uses HTTP basic authentication which is added via the HTTP header:

  let headers = Cohttp.Header.init () in
  let headers =
    Cohttp.Header.add_authorization headers (`Basic (user, password))

The response from the API can be converted to JSON using Yojson.

let json =
      Eio.Buf_read.(parse_exn take_all) body ~max_size:max_int
      |> Yojson.Safe.from_string

The JSON field can be read using the Util functions. For example, Yojson.Basic.Util.member "services" json will read the services entry. Elements can be converted to lists with Yojson.Basic.Util.to_list. After a bit of hacking this turned out to be quite tedious to code.

As an alternative, I decided to use ppx_deriving_yojson.runtime. I described the JSON blocks as OCaml types, e.g. station as below.

type station = {
  tiploc : string;
  description : string;
  workingTime : string;
  publicTime : string;
}
[@@deriving yojson]

The preprocessor automatically generates two functions:station_of_json and station_to_json which handle the conversion.

The only negative on this approach is that RTT doesn’t emit empty JSON fields, so they need to be flagged as possibly missing and a default value provided. For example, realtimeArrivalNextDay is not emitted unless the value is true.

  realtimeArrivalNextDay : (bool[@default false]);

Now once the JSON has been received we can just convert it to OCaml types very easily:

    match reply_of_yojson json with
    | Ok reply ->
       (* Use reply.services *)
    | Error err -> Printf.printf "Error %s\n" err

My work in progress code is available on GitHub

dune exec --release -- rtt --user USER --pass PASS --station RTR
rtt: [DEBUG] received 3923 bytes of body
rtt: [DEBUG] received 4096 bytes of body
rtt: [DEBUG] received 4096 bytes of body
rtt: [DEBUG] received 4096 bytes of body
rtt: [DEBUG] received 1236 bytes of body
rtt: [DEBUG] end of inbound body
2025-03-23 2132 W16178 1C69 1 Ramsgate St Pancras International
2025-03-23 2132 W25888 9P59 2 Plumstead Rainham (Kent)
2025-03-23 2136 J00119 1U28 2 London Victoria Ramsgate
2025-03-23 2144 W25927 9P86 1 Rainham (Kent) Plumstead
2025-03-23 2157 W16899 1C66 2 St Pancras International Ramsgate
2025-03-23 2202 W25894 9P61 2 Plumstead Rainham (Kent)
2025-03-23 2210 J26398 1U80 1 Ramsgate London Victoria
2025-03-23 2214 W25916 9P70 1 Rainham (Kent) Plumstead
2025-03-23 2232 W16910 1C73 1 Ramsgate St Pancras International
2025-03-23 2232 W25900 9P63 2 Plumstead Rainham (Kent)
2025-03-23 2236 J00121 1U30 2 London Victoria Ramsgate
2025-03-23 2244 W25277 9A92 1 Rainham (Kent) Dartford
2025-03-23 2257 W16450 1F70 2 St Pancras International Faversham
2025-03-23 2302 W25906 9P65 2 Plumstead Rainham (Kent)
2025-03-23 2314 W25283 9A94 1 Rainham (Kent) Dartford
2025-03-23 2318 J00155 1U82 1 Ramsgate London Victoria
2025-03-23 2332 W25912 9P67 2 Plumstead Gillingham (Kent)
2025-03-23 2336 J00123 1U32 2 London Victoria Ramsgate
2025-03-23 2344 W25289 9A96 1 Rainham (Kent) Dartford
2025-03-23 2357 W16475 1F74 2 St Pancras International Faversham
2025-03-23 0002 W25915 9P69 2 Plumstead Gillingham (Kent)
2025-03-23 0041 J26381 1Z34 2 London Victoria Faversham