Payload Logo
Saturday

Home Assistant: EP3 Stockholms Lokaltrafik (SL) voice enabled departures

Author

Norbert TakΓ‘cs

Date Published

The idea came upon to me one day when I was running to the bus and had to check when the next bus was going, knowing that fiddling with my phone, or any other screen make me certainly miss the bus. If I could just ask my voice assistant as I scramble around to get ready it would solve all my problems. At least in this scenario.


This script has been adjusted over the years of using it. Asking the voice assistant "When is the next bus?" results in an announcement which states over my voice assistant the following:

next bus leaves in 28, and 58 minutes, and to the mall it leaves in, 16, and 46 minutes

It picks up the two lines that are near my apartment and crafts a human understandable message.

🚌 SL Stockholms lokaltrafik API

I cant take for-granted that I live in a city which has an open API for its public transport. This couldn't be possible without it. This is a great use of our tax money.

Open data rules because:

  • developers get to build on top of the data for free (like this article)
  • more eyes allow for issues and errors in API-s being caught faster. As opposed to Security through obscurity

🟦 Home assistant setup

The two add-ons that I used can be both installed from HACS, just make sure to install the newest pre-release versions wherever possible. In my case this worked with

hacs addon showcase

πŸ—ƒοΈ HASL sensor 3.2.0b2+

This project uses the new versions of the hasl-sensor it can be found on HACS just make sure to install the pre-release version. Since at the time of writing, that is the only version that works.

To use it we will need a key from developer.trafiklab.se, api we need is SL Realtidsinformation 4, grab a copy of it.

Add a new HASL integration in the Settings -> Devices & Services -> Add Integration

Fill in the UI the API key, location, select departures. Set the directions between 0,1,2 I left mine on default 0

This will create a sensor which contains the information about the upcoming departures.

🎴 HASL departure card 3.4.1+

card showcasing the departure card

For the UI I used the updated lovelace-hasl-departure-card.

Setting it up was easy, just add to lovelace and choose the sensor you created earlier.

πŸŸ₯ Node-red setup overview

node red showing the flow of the components described

The only extra package I had to install was node-red-contrib-home-assistant-websocket in node-red. Make sure to connect it to your home assistant instance.

The node-s are laid out above on the screenshot and their breakdown is below

▢️ Triggering the flow with button-node

Home assistant button node, this creates a button in home assistant. Add the timestamp node also which can be used to trigger the flow from within node-red.

The created button entity in home assistant will be used to by Alexa to trigger the flow. Id of the button can be adjusted.

Don't forget to tie this button to an action in your voice assistant.

πŸ“ƒ Payload to message with function-node

Function nodes are built into node-red and run javascript, useful for manipulating objects/arrays.

The node-red setup is rather easy,

1const payload = msg.data.attributes.departures
2
3const buses = payload.map((bus) => {
4 // Create a Date object from the specific date string
5 const specificDate = new Date(bus.scheduled);
6
7 // Get the current date and time
8 const currentDate = new Date();
9
10 // Calculate the difference in milliseconds
11 const differenceInMs = specificDate.getTime() - currentDate.getTime();
12
13 // Convert milliseconds to minutes
14 const differenceInMinutes = Math.floor(differenceInMs / (1000 * 60));
15
16 return {...bus, timeLeft: differenceInMinutes}
17
18}).filter((bus) => bus.timeLeft > 0 )
19
20const bus401 = buses.filter(bus => bus.line.id === 401)
21const bus402 = buses.filter(bus => bus.line.id === 402)
22
23let getMessage = () => {
24 let base = ""
25
26 if (bus401[0]?.timeLeft){
27 base = base + `leaves in ${bus401[0].timeLeft}`
28 }
29
30 if (bus401[1]?.timeLeft){
31 base = base + `, and ${bus401[1].timeLeft} minutes`
32 }
33
34 if (bus402[0]?.timeLeft){
35 if (base) {
36 base = base + `, and `
37 }
38 base = base + `to the mall it leaves in, ${bus402[0].timeLeft}`
39 }
40
41 if (bus402[1]?.timeLeft){
42 base = base + `, and ${bus402[1].timeLeft} minutes`
43 }
44
45 return base
46}
47
48return { message: getMessage()}

The script is rather crude. It does the following:

  • filters hardcoded 2 busses, 401,402 in my case from the payload
  • calculates each bus how much time they have left and saves this in timeLeft property
  • removes the busses which have no timeLeft anymore
  • picks out first 2 busses from each bus number
  • crafts the string message for the next node

I can recommend using node.error() to log parts of the code for debugging. These messages end up in the debug tab of node-red.

debug log for node red

πŸ“£ Message to the Voice assistant with action node

action node setup

Once again we are using a node from the home assistant palette. Action node -> Action -> tts.cloud_say with the following Jinja command

1{"message":msg.message, "entity_id": "media_player.dining_room"}

Run the full script either by triggering it from the timestamp node or by asking your voice assistant and get greeted by the following message over your speaker

next bus leaves in 28, and 58 minutes, and to the mall it leaves in, 16, and 46 minutes

Join the Discussion on github