Public Transport with OpenTripPlanner and HABApp

Hi all,

I’ve managed to integrate my local Public Transport in Germany (Bremen) through the OpenTripPlanner and API from the provider (VBN). This small tutorial should then work with small changes for any OpenTripPlanner Public Transport Entity.

My script filters all departures from your stop for certain methods and calculates the departure time in minutes. It gives the next 3 departures in a certain direction.

What you need:

  • HABApp installed (e.g. through the openhabian menu option)
  • python requests and pandas packages installed in the HABApp python virtual environment (tutorial below)
  • URL to your OpenTripPlanner API provider
  • Token for your OpenTripPlanner API provider
  • Find your stop id (for me it is 1:000009014003) by doing a simple GET request to the connection API (see below)

Here is the code for otp_departures.py

#!/usr/bin/env python3
import datetime
import time
import requests
import json
from pandas import json_normalize
import pandas as pd
import HABApp
from HABApp.core.items import Item

class otp_departures(HABApp.Rule):
    def __init__(self):
        super().__init__()

        # Run every minute
        self.run_minutely(self.get_departure_times)
  
        # The item the output will be sent to
        self.output = HABApp.openhab.items.StringItem.get_item('Next3Trams')

    def get_departure_times(self):

        # VBN API Documentation
        # https://www.vbn.de/service/entwicklerinfos/
    
        # example request to VBN API for a full connection, use this to find your stop ID
        # r=requests.get("http://gtfsr.vbn.de/api/routers/connect/plan?arriveBy=false&date=03-09-2021&fromPlace=53.xxxxxx,8.yyyyyy&toPlace=53.xxxxxx,8.yyyyyy&time=13:00:00&mode=WALK,TRANSIT&maxWalkDistance=300", headers={"Authorization":"YOURTOKEN","Host":"gtfsr.vbn.de"})

        # print(r.content) 
        # OTP documentation

        # http://dev.opentripplanner.org/apidoc/1.4.0/resource_IndexAPI.html#resource_IndexAPI_getStoptimesForStop_GET
        # http://docs.opentripplanner.org/en/latest/Basic-Tutorial/

        # My Stop: "Bremen Kirchweg","stopId":"1:000009014003"
        # URL to get stop times: /otp/routers/{routerId}/index/stops/{stopId}/stoptimes
        # Authorization to API: headers={"Authorization":"YOURTOKEN","Host":"gtfsr.vbn.de"})

        # make the GET request
        r=requests.get("http://gtfsr.vbn.de/api/routers/connect/index/stops/1:000009014003/stoptimes?detail=true&timeRange=7200&numberOfDepartures=20", headers={"Authorization":"YOURTOKEN"})

        # convert to json object
        json_obj = json.loads(r.content.decode('utf-8'))

        # normalize json
        df = json_normalize(json_obj,['times']) 

        # Filter out the unwanted destinations
        df = df.query('headsign not in ["Arsten", "BSAG", "Huckelriede"]').reset_index()

        # get current time
        now = datetime.datetime.now()

        # get time difference to UTC
        diff = datetime.datetime.now() - datetime.datetime.utcnow()

        # create column for departure time as readable string
        df['departureTimeDT'] = pd.to_datetime(df['serviceDay']+df['realtimeDeparture'],unit='s')+diff

        # sort entries by realtime arrival time (provided in epoch format)
        df = df.sort_values(by=['departureTimeDT']).reset_index()

        # optional: only keep next X departures
        # df = df.iloc[:3]

        # create column for departure time in HH:MM format
        df['depTimeHHMM'] = df['departureTimeDT'].dt.strftime('%H:%M')

        # create column for departure time in minutes from now
        df['depInMin'] = ((df['departureTimeDT'] - now).dt.seconds/60).astype(int)

        # drop unnecessary columns
        df = df[['headsign','departureTimeDT','depTimeHHMM','depInMin']]

        # create the string with next 3 departures, e.g. "1-11-21"
        next3in1string = (str(df['depInMin'][0]) + "-" + str(df['depInMin'][1]) + "-" + str(df['depInMin'][2]))

        #print(next3in1string)
        # optional: create JSON of the remaining 

        #dfout = df.to_json(orient='index',date_format='iso')
        #print(dfout)
        #return next3in1string
      
        # send the output to OH
        self.output.oh_send_command(next3in1string)

otp_departures()

How to install packages in HABApp:

Make sure the HABApp venv directory is writable:

/opt/habapp $ ls -l

should say:

total 20
drwxrwxrwx 2 root root 4096 Feb  1 16:23 bin
drwxrwxrwx 2 root root 4096 Feb  1 16:22 include
drwxrwxrwx 3 root root 4096 Feb  1 16:22 lib
-rwxrwxrwx 1 root root   69 Mar  9 13:54 pyvenv.cfg
drwxrwxrwx 3 root root 4096 Feb  1 16:22 share

in case not, perform:

sudo chmod -R u=rwx,g=rwx,o=rwx /opt/habapp/

Then:

sudo systemctl stop habapp.service

cd /opt/habapp
source bin/activate
pip install requests
pip install pandas
deactivate

sudo systemctl restart habapp.service

Now you should be set!

I’m open to see your improvements and ideas or solutions for other providers!

3 Likes

Hi

I found this post in trying to make some similar function for my public transport in Sweden. We have a API that sends out all planned trips in a .zip file (gtfs format) and also all realtime data is availible via api in protobuf format (GTFS RT). Have anyone been abla to parse protobuf to any readable and processable text for Openhab I would like to monitor when trains pass the previous stop so I can walk to the station.

Any help would be appreciated.

A tutorial for a variety of programing languages is available here: Tutorials  |  Protocol Buffers  |  Google Developers

Thanks for your information, however I’m even strugeling witg proof of concept for vonverting pb file to text just to see what kind of data is there.

I think what you would like to do is to access the GTFS RT data as this includes most up to date information like a train is delayed or a train connection is cancelled.
According to Trafiklab GTFS Sweden REST API | ProgrammableWeb you first need to get an API key which can be ordered at https://www.trafiklab.se/ to get access to RT data.

There is an example of what is returned by the RT API at The protobuf file format | Trafiklab .
You may also search on github.com in case you find something ready to use or re-use to start with like e.g. GitHub - andersekbom/pysl: Python module for accessing Stockholm public transport real-time data.

with regard to the static zip it looks like it is regularly update and you find an example at: http://peatus.ee/gtfs/gtfs.zip
extracting the files and checking their header we see:

==> agency.txt <==
agency_id,agency_name,agency_url,agency_timezone,agency_phone,agency_lang

==> calendar_dates.txt <==
service_id,date,exception_type

==> calendar.txt <==
service_id,monday,tuesday,wednesday,thursday,friday,saturday,sunday,start_date,end_date

==> fare_attributes.txt <==
fare_id,price,currency_type,payment_method,transfers,agency_id

==> fare_rules.txt <==
fare_id,route_id,origin_id,destination_id

==> feed_info.txt <==
feed_id,feed_publisher_name,feed_publisher_url,feed_lang

==> routes.txt <==
route_id,agency_id,route_short_name,route_long_name,route_type,route_color,competent_authority,route_desc

==> shapes.txt <==
shape_id,shape_pt_lat,shape_pt_lon,shape_pt_sequence

==> stops.txt <==
stop_id,stop_code,stop_name,stop_lat,stop_lon,zone_id,alias,stop_area,stop_desc,lest_x,lest_y,zone_name,authority

==> stop_times.txt <==
trip_id,arrival_time,departure_time,stop_id,stop_sequence,pickup_type,drop_off_type

==> trips.txt <==
route_id,service_id,trip_id,trip_headsign,trip_long_name,direction_code,shape_id,wheelchair_accessible

This “looks like” some kind of db tables.
You just need to find the relations between the tables but that shouldn’t be a big problem.

Thanks for even more information, I’m apssed that stage that I can save API output as file an then need to make the protobuf file into something more vieweable. I will keep on searching on the internet and see what I can find.