How to make a Snips skill – Part 3 – All About API

30. December 2018 17:45
5 min reading
This post thumbnail

In part one and two we looked at how we can tell Snips what we intend to ask. Now we should know the questions and need an answer from one of the API’s we looked at in Part 1 already. Just as a reminder here are the different parts of this walk trough journey.

Table of Content

  1. Overview
  2. Intents – how to get data for slots and intents
  3. All about API – get the needed public transport data
  4. Data to spoken text – what about Multilanguage
  5. Develop the actions – tell Snips what to do
  6. Put everything together – publish and debug the App and Actions

API – Station Timetable

I decided to use the data from Open-Data-Platform öV Schweiz. Here we need no API key which makes it easier to publish the Skill later on for a general audience.

To be able to answer the intents we use mainly two API calls. One call will give us back all the next available connections from our home station towards any direction. If you live in a major city you might get a lot of answers. We will limit the number of connections to 3. If you are looking for a specific destination you still have the other intents to ask more specifically.

The API Endpoint for this type of question is stationboard. The parameters for this call are:

station - in our case it will be our "home station"
limit - the number of connections we want in return
http://transport.opendata.ch/v1/stationboard?station=Thun&limit=3

Try it out and look at the JSON we get back. There is a lot of information we could tell our user trough TTS with this data. So the next step is probably to think about the form and content of the information we want to read back.

Station Information

In my first version of the Skill I would like to be able to know the following facts:

  • to which destination is the connection?
  • what time is it leaving?
  • from which platform is it leaving (if we have the information)
  • what”s the train/bus number?
  • how many stops does the connection have until the destination (this gives an indication if the connection is a fast train or a regional commuter)

Output

Let”s define what Snips should tell us about those 3 connection:

The next train from Thun is number “S1” and leaves at eleven forty from platform four towards Bern. There are seven stops before the final destination. Another train, number “S2” goes to Interlaken from platform number three at eleven fortyfive with 2 stops. And at eleven fifty we have Bus number “156” to Spiez with 12 stops.

and the same in German language:

Der nächste Zug von Thun ist die “S1” und fährt um elf Uhr vierzig ab Gleis vier nach Bern. Der Zug hält an sieben Haltestellen bis nach Bern. Eine weitere Verbindung ist Zug “S2” nach Interlaken von Gleis drei um elf Uhr fünfundvierzig mit drei Stops. Und Bus “156” fährt um elf Uhr fünfzig nach Spiez mit 12 Haltestellen.

API – Public Transport Connections

For this type of information we provide the origin and destination of the journey we are looking for. The API endpoint is connections with the following parameters:

from - in our case it will be our "home station" or the from_station slot
to - this is the to_station slot we get from the intent
limit - will be 1 since we just want the next connection

The url for this API call is as follows:

http://transport.opendata.ch/v1/connections?from=Thun&to=Bern&limit=1

Connection information

If I”m looking for a specific connection I would like to know some details:

  • when is the connection leaving?
  • how long does the journey take?
  • what time I will arrive at the destination?
  • what platform and train number?
  • how many transfers do I have to do?
  • some more details about the transfers (station, time, platform, train number)

Output

So with this information an answer to a question for a connection from Davos Platz to Lausanne could sound like that:

Your next connection from Davos Platz to Lausanne leaves at 15:26. It has the number RE 1050 on Platform 1. The journey takes 4 hours and 50 minutes, you will arrive in Lausanne at 20:16 . There are 4 transfers: in Klosters Platz – 15:57, number RE 1350 on platform 2; in Landquart – 16:49, number IC 3 on platform 3; in Zürich HB – 18:02, number IC 8 on platform 31 and in Bern – 19:04 number IR 15 on platform 3.

and the same thing again in German language:

Deinen nächste Verbindung von Davos Platz nach Lausanne fährt um 15:26. Es ist die Nummer RE 1050 auf Gleis 1. Die Reise dauert 4 Stunden 50 Minuten, Du wirst um 20:16 in Lausanne ankommen. Du must 4 mal umsteigen: In Klosters Platz um 15:57 mit RE 1350 auf Gleis 2; in Landquart um 16:49 mit IC 3 auf Gleis 3; in Zürich HB um 18:02 mit IC 8 auf Gleis 31 und in Bern um 19:04 mit IR 15 auf Gleis 3.

create a nice Python class to handle the requests

I have to confess, I”m an absolute beginner when it comes to programming in Python. I spent most of my programming days (before my actual management job) in Assembler, Pascal, Visual Basic and C#.

I was reading a lot about Python in the last couple of month. Tried out a some samples in Jupyter Notebook (check out my Home Automation Awesome List to get a grasp at some modules and did some tinkering with Data Science libraries. So bear with me if the code I”m about to present is not “high-glossy pytonic”. I”m always happy to learn more and would appreciate I you leave comments in my blog, on the Snips Forum or in GitHub!

Since this write-up is not about Python Basics, I leave you just with the results of my attempt to create a module which handles the API connection and prepares the returned data into a form we can easily use to create the text to be spoken by Snips TTS.

For this first module I got some inspiration from Fabian Affolter, a fellow Swiss guy who does a lot in the Home Assistant community. He made a Home Assistant component to fetch data from the same API to display connections in Home Assistant. His modules are published in GitHub an I took the liberty to copy a couple of concepts.

Module swiss_transport_info

We will put all the functions related to fetch data from the API and translate the data into spoken text into this module. This way we can encapsulate this tasks to a separate class and leave the Snips App code as small and clean as possible.

The first two functions are taking care of the data retrieval from our API. One function takes care of the Timetable for a station, the other one looks up the Connection for the public transports. Check out the full source code on my GitHub Project.

I put the functions into a class called _OpendataTransport. To get the timetable we can call the get_stationboard function:

import swiss_transport_info as sti
odta = sti._OpendataTransport()
odta.get_stationboard("Bern",3)

The return is a dictionary with the needed information of the next 3 connections from Bern:

[{
"departure": "2019-01-12T11:17:00+0100",
"destination": "Unterzollikofen",
"origin": "Bern",
"platform": "22",
"stops": 5,
"transport": "S 9"
},
{
"departure": "2019-01-12T11:19:00+0100",
"destination": "Bern Brünnen Westside",
"origin": "Bern",
"platform": "12",
"stops": 3,
"transport": "S 51"
},
{
"departure": "2019-01-12T11:20:00+0100",
"destination": "Laupen",
"origin": "Bern",
"platform": "7",
"stops": 10,
"transport": "S 2"
}
]

And the other function to get the next connections from Davos Platz to Lausanne:

import swiss_transport_info as sti
odta = sti._OpendataTransport()
odta.get_connections("Davos Platz", "Lausanne")

This will hand us back the following objects:

[{
"arrival": "2019-01-12T16:16:00+0100",
"departure": "2019-01-12T11:26:00+0100",
"duration": "00d04:50:00",
"first_to": "Klosters Platz",
"first_transport": "RE 1034",
"from": "Davos Platz",
"platform": "1",
"to": "Lausanne",
"transfer_count": 4,
"transfers": [{
"departure": "2019-01-12T11:26:00+0100",
"platform": "1",
"station": "Davos Platz",
"transport": "RE 1034"
},
{
"departure": "2019-01-12T11:57:00+0100",
"platform": "2",
"station": "Klosters Platz",
"transport": "RE 1334"
},
{
"departure": "2019-01-12T12:49:00+0100",
"platform": "3",
"station": "Landquart",
"transport": "ICE 10070"
},
{
"departure": "2019-01-12T14:02:00+0100",
"platform": "31",
"station": "Zürich HB",
"transport": "IC 8"
},
{
"departure": "2019-01-12T15:04:00+0100",
"platform": "6",
"station": "Bern",
"transport": "IR 15"
}
]
}]

Ok, with this returned data and the flexibility of the _OpendataTransport class we are ready to dive into the next adventure. Make some “speakable” text out of the data we have.

This will be the topic of the next part – Data to spoken text – what about Multilanguage.