DB Journeys

Since I tend to work with hardware and actually like my colleagues, I commute to work at least some days. And while it has some downsides, going by train effectively “recovers” some of that commute time, as it allows me to use the time, instead of concentrating on the road or other people.

Sadly, one of the inconveniences of trains is their schedule. And while it won’t wait for me, I’ve had to wait for delayed trains before. Or worse, wait for the next train in the schedule, because mine was cancelled. To combat this, I want to see the current expected departure times before I leave for the train.

And while the app provides this feature, I had a different plan.

The Plan

The premise of all my automation: I’m incredibly lazy. And while I could open the app on my phone and select check the trains, that’s effort. So I need my environment to push that information onto me.

There’s two existing solutions in my home that guide the decision about how to do this.

  • Home Assistant: The home automation solution of my choice
  • OpenHASP: A very simple detached display solution that integrates with HA.

With these two already in place, it’s a short 2 step plan:

  1. Pull information about the upcoming valid trains from Deutsche Bahn.
  2. Add this information to the display

And while it can be fun and valuable guidance to start with the final design, my approach to programming naturally guides me to figuring out the API first, then worry about the interface when I can populate it with real data.

DB

While I thought finding the correct API should be the easiest part, I didn’t consider German bureaucracy and DB being “old” when I made that judgement.

DB API Marketplace

DB hosts an API Marketplace on their own site. It requires users to have a developer account and generally only allow s a limited number of API calls per timeframe. And while it’s annoying to make users into developers, the developer login type credentials isn’t uncommon in HA. And the volume limits would be large enough for my usecase.

So, after looking through the market place, I found the perfect solution. RIS::Journeys. Just what I need. An easy way to query DB for the journeys from one station to another. I only need to “buy” the free packag and….

There’s no free version of this. There isn’t even a paid version of this. The API is only available to “Vertriebspartner”. So, I’m out of luck..?

Can’t be this bad, other providers integrate with DB as well, and some of them are free (as in beer and freedom).

db.transport.rest

After some looking around, I found The Solution. Some kind soul hosts a bridge between http REST calls and a hafas API that DB uses for their own clients. While I could just interface with the API further down the line, this server allows 100 calls per minute without any authentication, or other unique IDs.

And it exposes the exact endpoints I need. /journey to query for the journeys. And /stations to allow easy lookup of relevant stations.

With this API available, it’s now time to let HA know about my trains.

HomeAssistant

Since I already run HomeAssistant for my smart home needs, and got dashboards based on the collected data it was my obvious choice for collecting cloud data locally.

While the API is very simple, there’s a certain amount of complexity in HA and its component interface.

After some de-tour trying to follow the official (and some unofficial) guides on how to write a new component, I was pointed at an existing component.

It’s easy

So I scratched the previous attempt and coppied trafikverket_train as base for my new development. With this in place, I only had to clean up some variable and class names to reference DB instead of Traefikverket and replace the API calls. The result looks like this and is available on github.

Boom, I have an entity with the next possible train from my home to work. And while the next train is generally the best train, it’s not enough. For multiple reasons.

Not so easy

By default, the integration kept the same “next” train around, even when it already departed. At times for multiple minutes.

And while this could be avoided by a mixture of having HA poll for data on a timer, and actively asking it to update push updates when a train leaves, it’s generally beneficial to see both the next train, and be aware of other options either way.

Luckily, it’s easy to add the next N trains as well. The /journey endpoint already returns 3 journeys by default. So I simply extended the component to provide 3 devices, one for each upcoming journey:

  • Next
  • The one after
  • The one after that

And while the earliest can still become outdated by a couple minutes, it’s not a problem anymore, as the next two will still be viable options.

All that’s left, is to take the information out of HA and push it into my face every morning.

OpenHASP

I already had a vastly underutilized display in my home. It’s a Sunton 8048 I run OpenHASP on.

Previously it only displayed an overview of the weather forecast. This was essentially the “Hello World” for OpenHASP on this device. But it’s placed perfectly in my line of sight when I get out of my bedroom or put on jackets or shoes to go outside.

And while I like this setup, it also partially guides the design. It is somewhat tedious to work with, and the layouting is very manual. Nothing close to the markup based text formatting we are used to from the web, or modern desktop frameworks.

Thus, the provided information will be a bit spartanic, and reduced to the few highest impact variables.

  • Departure time
  • Travel time
  • Number of legs

This just needs a couple of lines in my layout. One of these blocks per journey

{"page":1,"id":120,"obj":"obj","parentid":102, "x":5, "y":0, "w":280, "h":32, "click":0}
{"page":1,"id":121,"obj":"label","x":2,"y":0,"w":180,"h":30,"align":"left","text":"06:00","parentid":120,"click":0, "text_font":24}
{"page":1,"id":122,"obj":"label","x":170,"y":0,"w":75,"h":30,"align":"center","text":"1h22m","parentid":120,"pad_top":-2,"text_font":24,"click":0}
{"page":1,"id":123,"obj":"label","x":245,"y":0,"w":32,"h":32,"align":"right","text":"1","parentid":120,"text_font":24,"click":0}

And on the other end, one of these blocks per journey in the OpenHASP HA integration config:

  - obj: "p1b141" # Train Next Departure
    properties:
      "text": >
        {{((states("sensor.$home_$work_2_timestamp")) | as_datetime | as_local).strftime("%H:%M")}}

  - obj: "p1b142" # Train Next Duration
    properties:
      "text": >
        {{states("sensor.$home_$worke_2_duration")|int  | timestamp_custom("%Hh%Mm", false)}}

  - obj: "p1b143" # Train Next Legs
    properties:
      "text": >
        {{states("sensor.$home_$work_2_data_size")}}