Dutch Public Transport sensor (ovapi.nl)

No problem, @Pippyn also commited a change in a pull request that fixed the need of quotes around your parameter values like: timing_point_code: 31000227

Before we remove the stop_code and route_code I do have to check whether my own stop here is also listed in the /line JSON with a timing_point_code. If it is, I’ll create a issue for this removal request.

i just did a full readme PR to make this component better for noobs like me, not sure if i did the PR good but here is the page how it should be:

https://github.com/IIIdefconIII/Home-Assisant-Sensor-OvApi/blob/master/README.md

Nice, I did receive a notification :slight_smile: Good job, I’m reviewing it now.

I also was working on the readme, so I’ll have to fix the conflicts first before I can do a merge. I’m still a beginner with git and multiple users.

same here but you can copy paste my github code and screenshots, thats all if you need it raw and dont have access add me on discord username is same as github

1 Like

I’m not sure if it is wise tp remove these… Unless we get an easy way to find the timing_point_code.
Tinking about it. Wouldn’t it be easier if you had to give your stop name and direction and the sensor would self find the neccessairy codes? This would be more user friendly.
I’ll have a look if its possible.

1 Like

cool, anyway the card bugs for me:


EDIT: found out that that bus is also stopping at roelantweg. need to figure out how to use the line filter.

did i messed up? Those busses are different bus stops and street names.

############################################
##         Public Transport OVAPI         ##
############################################
###### SENSORS -----------------------------
###### TRAM 23 -----------------------------
sensor:
  - platform: ovapi
    name: Tram 23
    timing_point_code: '31000227'
#    line_filter: 2, 6
    show_future_departures: 4

###### BUS 140 -----------------------------
  - platform: ovapi
    name: Bus 140
    timing_point_code: '31006183'
    show_future_departures: 4

###### BUS 183 -----------------------------
  - platform: ovapi
    name: Bus 183
    timing_point_code: '31006177'
    show_future_departures: 4

line filter works like this;

############################################
##         Public Transport OVAPI         ##
############################################
###### SENSORS -----------------------------
###### TRAM 23 -----------------------------
sensor:
  - platform: ovapi
    name: Tram 23
    timing_point_code: '31000227'
    line_filter: 23
    show_future_departures: 4

###### BUS 140 -----------------------------
  - platform: ovapi
    name: Bus 140
    timing_point_code: '31006183'
    line_filter: 140
    show_future_departures: 4

###### BUS 183 -----------------------------
  - platform: ovapi
    name: Bus 183
    timing_point_code: '31006177'
    line_filter: 183
    show_future_departures: 4
1 Like

thanks will try

Already tought about integrating this into Home Assistant?

Hi @klaasnicolaas,

Yes we did, but currently my hands are a bit tied at the moment. Maybe in my vacation mid juli :slight_smile:

Recently I got some error in my logs:

2019-05-21 07:19:17 ERROR (MainThread) [homeassistant.helpers.entity] Update for sensor.bushalte_centraal_station_57 fails
Traceback (most recent call last):
  File "/usr/local/lib/python3.7/site-packages/homeassistant/helpers/entity.py", line 220, in async_update_ha_state
    await self.async_device_update()
  File "/usr/local/lib/python3.7/site-packages/homeassistant/helpers/entity.py", line 377, in async_device_update
    await self.hass.async_add_executor_job(self.update)
  File "/usr/local/lib/python3.7/concurrent/futures/thread.py", line 57, in run
    result = self.fn(*self.args, **self.kwargs)
  File "/config/custom_components/ovapi/sensor.py", line 205, in update
    self._json_data.update()
  File "/usr/local/lib/python3.7/site-packages/homeassistant/util/__init__.py", line 224, in wrapper
    result = method(*args, **kwargs)
  File "/config/custom_components/ovapi/sensor.py", line 324, in update
    response.request("GET", "/tpc/" + self._timing_point_code, headers = self._headers)
  File "/usr/local/lib/python3.7/http/client.py", line 1229, in request
    self._send_request(method, url, body, headers, encode_chunked)
  File "/usr/local/lib/python3.7/http/client.py", line 1275, in _send_request
    self.endheaders(body, encode_chunked=encode_chunked)
  File "/usr/local/lib/python3.7/http/client.py", line 1224, in endheaders
    self._send_output(message_body, encode_chunked=encode_chunked)
  File "/usr/local/lib/python3.7/http/client.py", line 1016, in _send_output
    self.send(msg)
  File "/usr/local/lib/python3.7/http/client.py", line 956, in send
    self.connect()
  File "/usr/local/lib/python3.7/http/client.py", line 928, in connect
    (self.host,self.port), self.timeout, self.source_address)
  File "/usr/local/lib/python3.7/socket.py", line 727, in create_connection
    raise err
  File "/usr/local/lib/python3.7/socket.py", line 716, in create_connection
    sock.connect(sa)
socket.timeout: timed out

Hi Klaas,

Can you check the repro, I had to make some changes to the code to support the latest changes of Home Assistant :slight_smile:

I’ll look at it, isn’t it useful to work with releases in Github by the way?

Has anyone created a custom lovelace card for OVAPI or know of any? Any link to a repo is appreciated.

Could you share your OV API setup with me? I think we live in the same street (Koninginneweg) :stuck_out_tongue_winking_eye:

Thanks for the plugin. I was looking for something like this.
I Was testing your plugin, got it working, but I don’t understand how you work with the delay times. Sometimes the delay is presented as hh:mm + d. Sometimes as hh:mm (calculated time based upon delay).

Shouldn’t this statement
self._state = stops_list[self._sensor_number]["ExpectedArrivalTime"].strftime('%H:%M')
be replaced by

self._state = stops_list[self._sensor_number]["TargetDepartureTime"].strftime('%H:%M') +" + " + str(next_stops_list[counter]["Delay"]) + "m")

@wimd68

Did you have difficulty installing it?
I believe I have installed it, but when I add the following code to my configuration.yam (to test it out) - it accepts it, but I can not find the sensor :frowning: - not in Lovelace UI or Developer tools.

It seems to be an old sensor /integration with minimal interest these day, unfortunately.

Hope to hear,
Neil

sensor:
  - platform: dutch_public_transport_api
    name: Amsterdam naar Vlissingen        # (required)
    station: station-amsterdam-centraal    # (required)
    destination: Vlissingen                # (required)
    show_future_departures: 2              # (optional)

Hello folks,
I have also tried with no avail to use this integration.
Not sure what went wrong but my Home Assistant would not restart propperly anymore.

I have then written my own rest yaml code and this works for me. However it is not as simple as having a config file. You would need to adapt my implementation with the timingpointcodes that apply to your needs.

# OVAPI - public transport information
# Utrecht Neude Busstation
- resource: "https://v0.ovapi.nl/tpc/50000360"
  scan_interval: 10

  sensor:
    - name: "utr_neude_debilt_bus1"
      value_template: >
        {% set busdata = value_json["50000360"]["Passes"].items() | list %}

        {% set ns = namespace(count = 1) %}
        {% set data = namespace(trips=[]) %}
        {% for x in busdata -%}
          {%- if x[1]["LinePublicNumber"] == "77" and (x[1]["TripStopStatus"] != "CANCEL") and (x[1]["TripStopStatus"] != "PASSED") and (x[1]["TripStopStatus"] != "ARRIVED") %}
          {% set datapoint = ns.count %}{% set line = x[1]["LinePublicNumber"] %}{% set destination = x[1]["DestinationName50"] %}{% set ttd = (((as_timestamp(x[1]["ExpectedDepartureTime"]) - as_timestamp(now())) | round) / 60) | round(0) %}{% set status = x[1]["TripStopStatus"] %}{% set expected = x[1]["ExpectedDepartureTime"] %}{% set data.trips = data.trips + [(ttd, line, destination, status, expected)] %}{% set ns.count = ns.count + 1 %}{% endif %}{%- endfor %}

        {% set busses = data.trips | sort(0) %}
        {% set bus1 = "nodata" %}
        {% set bus2 = "nodata" %}
        {% set bus3 = "nodata" %}

        {% if (data.trips | count) <1 %}
        {% elif (data.trips | count) <2 %}
        {% set bus1 = busses[0] %}
        {% elif (data.trips | count) <3 %}
        {% set bus1 = busses[0] %}
        {% set bus2 = busses[1] %}
        {% else %}
        {% set bus1 = busses[0] %}
        {% set bus2 = busses[1] %}
        {% set bus3 = busses[2] %}
        {% endif %}

        {{ bus1 }}

    - name: "utr_neude_debilt_bus2"
      value_template: >
        {% set busdata = value_json["50000360"]["Passes"].items() | list %}

        {% set ns = namespace(count = 1) %}
        {% set data = namespace(trips=[]) %}
        {% for x in busdata -%}
          {%- if x[1]["LinePublicNumber"] == "77" and (x[1]["TripStopStatus"] != "CANCEL") and (x[1]["TripStopStatus"] != "PASSED") and (x[1]["TripStopStatus"] != "ARRIVED") %}
          {% set datapoint = ns.count %}{% set line = x[1]["LinePublicNumber"] %}{% set destination = x[1]["DestinationName50"] %}{% set ttd = (((as_timestamp(x[1]["ExpectedDepartureTime"]) - as_timestamp(now())) | round) / 60) | round(0) %}{% set status = x[1]["TripStopStatus"] %}{% set expected = x[1]["ExpectedDepartureTime"] %}{% set data.trips = data.trips + [(ttd, line, destination, status, expected)] %}{% set ns.count = ns.count + 1 %}{% endif %}{%- endfor %}

        {% set busses = data.trips | sort(0) %}
        {% set bus1 = "nodata" %}
        {% set bus2 = "nodata" %}
        {% set bus3 = "nodata" %}

        {% if (data.trips | count) <1 %}
        {% elif (data.trips | count) <2 %}
        {% set bus1 = busses[0] %}
        {% elif (data.trips | count) <3 %}
        {% set bus1 = busses[0] %}
        {% set bus2 = busses[1] %}
        {% else %}
        {% set bus1 = busses[0] %}
        {% set bus2 = busses[1] %}
        {% set bus3 = busses[2] %}
        {% endif %}

        {{ bus2 }}

    - name: "utr_neude_debilt_bus3"
      value_template: >
        {% set busdata = value_json["50000360"]["Passes"].items() | list %}

        {% set ns = namespace(count = 1) %}
        {% set data = namespace(trips=[]) %}
        {% for x in busdata -%}
          {%- if x[1]["LinePublicNumber"] == "77" and (x[1]["TripStopStatus"] != "CANCEL") and (x[1]["TripStopStatus"] != "PASSED") and (x[1]["TripStopStatus"] != "ARRIVED") %}
          {% set datapoint = ns.count %}{% set line = x[1]["LinePublicNumber"] %}{% set destination = x[1]["DestinationName50"] %}{% set ttd = (((as_timestamp(x[1]["ExpectedDepartureTime"]) - as_timestamp(now())) | round) / 60) | round(0) %}{% set status = x[1]["TripStopStatus"] %}{% set expected = x[1]["ExpectedDepartureTime"] %}{% set data.trips = data.trips + [(ttd, line, destination, status, expected)] %}{% set ns.count = ns.count + 1 %}{% endif %}{%- endfor %}

        {% set busses = data.trips | sort(0) %}
        {% set bus1 = "nodata" %}
        {% set bus2 = "nodata" %}
        {% set bus3 = "nodata" %}

        {% if (data.trips | count) <1 %}
        {% elif (data.trips | count) <2 %}
        {% set bus1 = busses[0] %}
        {% elif (data.trips | count) <3 %}
        {% set bus1 = busses[0] %}
        {% set bus2 = busses[1] %}
        {% else %}
        {% set bus1 = busses[0] %}
        {% set bus2 = busses[1] %}
        {% set bus3 = busses[2] %}
        {% endif %}

        {{ bus3 }}

My TimingPointCode of interest is: 50000360
Line Number I am interested in is: 77
I only want to see the next 3 departure times.
I also exclude departure records that contain CANCEL, PASS or ARRIVED as these do not add value if I want to see when the next bus will depart.

If you can reuse my code, feel free.

Here is a UI visualisation I use for these results:

type: horizontal-stack
cards:
  - type: vertical-stack
    cards:
      - type: custom:mushroom-title-card
        title: Schedule 77 Neude
      - type: vertical-stack
        cards:
          - type: custom:mushroom-template-card
            primary: >-
              {% if states("sensor.utr_neude_debilt_bus1") == "nodata" %}"No
              Data Available"{% else %}Next Bus in {{
              states("sensor.utr_neude_debilt_bus1").replace("(",
              "").replace(")", "").replace("'", "").split(',')[0] }} minutes{%
              endif %}
            secondary: >-
              {% if states("sensor.utr_neude_debilt_bus1") == "nodata" %}"No
              Data Available"{% else %}{{
              states("sensor.utr_neude_debilt_bus1").replace("(",
              "").replace(")", "").replace("'", "").split(',')[1] }} {{
              states("sensor.utr_neude_debilt_bus1").replace("(",
              "").replace(")", "").replace("'", "").split(',')[2] }}

              {{ states("sensor.utr_neude_debilt_bus1").replace("(",
              "").replace(")", "").replace("'", "").split(',')[3] }}{% endif %}
            icon: mdi:bus-clock
            multiline_secondary: true
            entity: sensor.utr_neude_debilt_bus1
            icon_color: >-
              {% if states("sensor.utr_neude_debilt_bus1") == "nodata" %}grey{%
              elif states("sensor.utr_neude_debilt_bus1").replace("(",
              "").replace(")", "").replace("'", "").split(',')[3] | trim ==
              "DRIVING" %}green{% else %}blue{% endif %}
            tap_action:
              action: more-info
            hold_action:
              action: none
            double_tap_action:
              action: none
          - type: custom:mushroom-template-card
            primary: >-
              {% if states("sensor.utr_neude_debilt_bus2") == "nodata" %}"No
              Data Available"{% else %}Bus in {{
              states("sensor.utr_neude_debilt_bus2").replace("(",
              "").replace(")", "").replace("'", "").split(',')[0] }} minutes{%
              endif %}
            secondary: >-
              {% if states("sensor.utr_neude_debilt_bus2") == "nodata" %}"No
              Data Available"{% else %}{{
              states("sensor.utr_neude_debilt_bus2").replace("(",
              "").replace(")", "").replace("'", "").split(',')[1] }} {{
              states("sensor.utr_neude_debilt_bus1").replace("(",
              "").replace(")", "").replace("'", "").split(',')[2] }}

              {{ states("sensor.utr_neude_debilt_bus2").replace("(",
              "").replace(")", "").replace("'", "").split(',')[3] }}{% endif %}
            icon: mdi:bus-clock
            multiline_secondary: true
            entity: sensor.utr_neude_debilt_bus2
            icon_color: >-
              {% if states("sensor.utr_neude_debilt_bus2") == "nodata" %}grey{%
              elif states("sensor.utr_neude_debilt_bus2").replace("(",
              "").replace(")", "").replace("'", "").split(',')[3] | trim ==
              "DRIVING" %}green{% else %}blue{% endif %}
            tap_action:
              action: more-info
            hold_action:
              action: none
            double_tap_action:
              action: none
          - type: custom:mushroom-template-card
            primary: >-
              {% if states("sensor.utr_neude_debilt_bus3") == "nodata" %}"No
              Data Available"{% else %}Bus in {{
              states("sensor.utr_neude_debilt_bus3").replace("(",
              "").replace(")", "").replace("'", "").split(',')[0] }} minutes{%
              endif %}
            secondary: >-
              {% if states("sensor.utr_neude_debilt_bus3") == "nodata" %}"No
              Data Available"{% else %}{{
              states("sensor.utr_neude_debilt_bus3").replace("(",
              "").replace(")", "").replace("'", "").split(',')[1] }} {{
              states("sensor.utr_neude_debilt_bus1").replace("(",
              "").replace(")", "").replace("'", "").split(',')[2] }}

              {{ states("sensor.utr_neude_debilt_bus3").replace("(",
              "").replace(")", "").replace("'", "").split(',')[3] }}{% endif %}
            icon: mdi:bus-clock
            multiline_secondary: true
            entity: sensor.utr_neude_debilt_bus3
            icon_color: >-
              {% if states("sensor.utr_neude_debilt_bus3") == "nodata" %}grey{%
              elif states("sensor.utr_neude_debilt_bus3").replace("(",
              "").replace(")", "").replace("'", "").split(',')[3] | trim ==
              "DRIVING" %}green{% else %}blue{% endif %}
            tap_action:
              action: more-info
            hold_action:
              action: none
            double_tap_action:
              action: none

Bon Chance and happy coding

In case this helps someone, I’ve used this new “badges” system to add a little badge with the time of the next bus and the next ferry. it looks like this:

The code is as follows:

  - platform: rest
    name: Next Bus 48 Departure
    resource: "http://v0.ovapi.nl/tpc/30005093"
    value_template: >
      {% set ns = namespace(earliest_time="2070-01-01T00:00:01") %}
      {% for key, pass_data in value_json['30005093']['Passes'].items() %}
        {% if pass_data['ExpectedDepartureTime'] < ns.earliest_time %}
          {% set ns.earliest_time = pass_data['ExpectedDepartureTime'] %}
        {% endif %}
      {% endfor %}
      {{ ns.earliest_time }}
    scan_interval: 180
  - platform: rest
    name: Next Ferry NDSM Departure
    resource: "http://v0.ovapi.nl/tpc/30009900"
    value_template: >
      {% set ns = namespace(earliest_time="2070-01-01T00:00:01") %}
      {% for key, pass_data in value_json['30009900']['Passes'].items() %}
        {% if pass_data['ExpectedDepartureTime'] < ns.earliest_time and pass_data['DestinationCode'
          {% set ns.earliest_time = pass_data['ExpectedDepartureTime'] %}
        {% endif %}
      {% endfor %}
      {{ ns.earliest_time }}
    scan_interval: 180
  - platform: template
    sensors:
      next_ferry_ndsm_departure_formatted:
        friendly_name: "Next Ferry NDSM Departure Time"
        value_template: >
          {{ states('sensor.next_ferry_ndsm_departure')[11:16] }}
  - platform: template
    sensors:
      next_bus_48_departure_formatted:
        friendly_name: "Next Bus 48 Departure Time"
        value_template: >
          {{ states('sensor.next_bus_48_departure')[11:16] }}
4 Likes

am trying to use this but i can;'t find my busstop in the json file.
am looking for, Burgemeester Kasteleinweg, Aalsmeer.

and then the line to haarlem station 340

can’t find anything in Aalsmeer even