Deutsche Bahn Android Pendler Widget

Als täglicher Bahnfahrer habe ich nach einer Möglichkeit gesucht, mir mit wenig Aufwand die Zugverspätungen und -ausfälle auf meinem Smartphone anzeigen zu lassen. Glücklicherweise ist die Deutsche Bahn im Bereich OpenData unterwegs und bietet eine HTTP API an, die Veränderungen am planmäßigen Verkehr (ganz wichtig: auch Nahverkehr) bereitstellt. Wenn man einmal (bis zum nächsten Fahrplanwechsel) die Zug-IDs ermittelt hat, macht die Timetables API das, was ich brauche. Leider gibt es bisher zu Nachrichten wie „Ein Wagen fehlt“ keine Zuordnung von Message-IDs zu den dahinterstehenden Texten. Auf meine Anfrage diesbezüglich an dbopendata@deutschebahn.com habe ich leider keine Antwort bekommen. Verspätungen und Zugausfälle funktionieren aber prima.

Um eine der APIs der Deutschen Bahn nutzen zu können muss man sich einmalig registrieren und eine API abonnieren. Dabei erhält man einen Zugangstoken der bei allen Anfragen im Authorization-Header enthalten sein muss. Anschließend kann man über eine Online API Konsole Anfragen testen (sehr praktisch) ohne nur eine Zeile Code geschrieben zu haben. Somit habe ich schnell herausgefunden, dass diese API genau meinen Anforderungen entspricht.

Mit der einfachen http-Anfrage (Authorization-Header nicht vergessen)
https://api.deutschebahn.com/timetables/v1/station/{Bahnhofsname}
lässt sich die Kennung eines Bahnhofs herausfinden. Eine Antwort sieht zum Beispiel so aus:

<stations>
  
  <station name="Köln Hbf" eva="8000207" ds100="KK"/>
  
</stations>

Es wird hier immer nur das erste Ergebnis zurückgegeben, wenn Eure Anfrage nicht eindeutig war. Hier verwende ich folgende Funktion damit die API auch mit Sonderzeichen/Umlauten im Bahnhofsnamen umgehen kann:

	public static String ConvertUrl(String param) {
		return param.replaceAll(" ", "%20")
				        .replaceAll("ä", "%C3%A4")
					.replaceAll("Ä", "%C3%84")
				        .replaceAll("ö", "%C3%B6")
					.replaceAll("Ö", "%C3%96")
					.replaceAll("ü", "%C3%BC")
					.replaceAll("Ü", "%C3%9C")
					.replaceAll("ß", "%C3%9F")
					.replaceAll("/", "%2F");

	}

Über die Betriebsstellen API lässt sich eine Suche nach Bahnhöfen besser realisieren. Wenn man nach „Köln“ sucht bekommt man hier ein sehr ausführliches Ergebnis 😉 Relevant sind aber nur die Typen „Bf“, „Hp“ mit Status „in use“.

[
  {
    "abbrev": "IKFT",
    "name": "Köln Flughafen-Tunnel         (Ola/Olsp)",
    "short": "K Flughfn-Tunnel",
    "type": "Fwst",
    "status": "in use",
    "locationCode": "",
    "UIC": "0080",
    "regionId": 3,
    "validFrom": "2008-01-01T01:00:00.000Z",
    "validTill": null,
    "id": 104236,
    "timeTableRelevant": false,
    "borderStation": false
  },
  {
    "abbrev": "IKG",
    "name": "Köln                     (Uw)",
    "short": "Köln",
    "type": "Uw",
    "status": "in use",
    "locationCode": "",
    "UIC": "0080",
    "regionId": 3,
    "validFrom": "2008-01-01T01:00:00.000Z",
    "validTill": null,
    "id": 104231,
    "timeTableRelevant": false,
    "borderStation": false
  },
  {
    "abbrev": "IKGM",
    "name": "Köln öUST ZES",
    "short": "öUST",
    "type": "Fwst",
    "status": "in use",
    "locationCode": "",
    "UIC": "0080",
    "regionId": 3,
    "validFrom": "2005-07-27T01:00:00.000Z",
    "validTill": null,
    "id": 105716,
    "timeTableRelevant": false,
    "borderStation": false
  },
  {
    "abbrev": "IKGU",
    "name": "Köln                               (Urw)",
    "short": "Köln",
    "type": "Urw",
    "status": "planned",
    "locationCode": "",
    "UIC": "0080",
    "regionId": 3,
    "validFrom": "2010-05-05T01:00:00.000Z",
    "validTill": null,
    "id": 107362,
    "timeTableRelevant": false,
    "borderStation": false
  },
  {
    "abbrev": "IKK",
    "name": "Köln                                (Sp)",
    "short": "Köln",
    "type": "Sp",
    "status": "in use",
    "locationCode": "",
    "UIC": "0080",
    "regionId": 3,
    "validFrom": "2003-01-01T01:00:00.000Z",
    "validTill": null,
    "id": 102519,
    "timeTableRelevant": false,
    "borderStation": false
  },
  {
    "abbrev": "IKKM",
    "name": "Köln-Mülheim                        (Uw)",
    "short": "Köln-Mülheim",
    "type": "Uw",
    "status": "in use",
    "locationCode": "",
    "UIC": "0080",
    "regionId": 3,
    "validFrom": "2003-01-01T01:00:00.000Z",
    "validTill": null,
    "id": 102690,
    "timeTableRelevant": false,
    "borderStation": false
  },
  {
    "abbrev": "IKKN",
    "name": "Köln Nullpunkt                     (OLA)",
    "short": "Köln Nullpunkt",
    "type": "Fwst",
    "status": "in use",
    "locationCode": "",
    "UIC": "0080",
    "regionId": 3,
    "validFrom": "2004-06-23T01:00:00.000Z",
    "validTill": null,
    "id": 105444,
    "timeTableRelevant": false,
    "borderStation": false
  },
  {
    "abbrev": "IKKS",
    "name": "Köln Süd                            (Sp)",
    "short": "Köln Süd",
    "type": "Sp",
    "status": "in use",
    "locationCode": "",
    "UIC": "0080",
    "regionId": 3,
    "validFrom": "2003-01-01T01:00:00.000Z",
    "validTill": null,
    "id": 104229,
    "timeTableRelevant": false,
    "borderStation": false
  },
  {
    "abbrev": "IKKWE",
    "name": "Köln Hbf Werke",
    "short": "Köln Hbf Werke",
    "type": "Sp",
    "status": "in use",
    "locationCode": "",
    "UIC": "0080",
    "regionId": 3,
    "validFrom": "2013-12-10T01:00:00.000Z",
    "validTill": null,
    "id": 109050,
    "timeTableRelevant": false,
    "borderStation": false
  },
  {
    "abbrev": "IKKZ",
    "name": "Köln ZES West",
    "short": "Köln         ZES",
    "type": "Zes",
    "status": "in use",
    "locationCode": "",
    "UIC": "0080",
    "regionId": 3,
    "validFrom": "2004-08-26T01:00:00.000Z",
    "validTill": null,
    "id": 105474,
    "timeTableRelevant": false,
    "borderStation": false
  },
  {
    "abbrev": "IKRGT",
    "name": "Köln Röttgen-Tunnel           (Ola/Olsp)",
    "short": "Röttgen-Tunnel",
    "type": "Fwst",
    "status": "in use",
    "locationCode": "",
    "UIC": "0080",
    "regionId": 3,
    "validFrom": "2008-01-01T01:00:00.000Z",
    "validTill": null,
    "id": 104235,
    "timeTableRelevant": false,
    "borderStation": false
  },
  {
    "abbrev": "KBKS",
    "name": "Köln Bruder Klaus Siedlung",
    "short": "Bruder Klaus Sdl",
    "type": "Abzw",
    "status": "in use",
    "locationCode": "DE15684",
    "UIC": "0080",
    "regionId": 3,
    "validFrom": "2008-12-14T01:00:00.000Z",
    "validTill": null,
    "id": 346130,
    "timeTableRelevant": true,
    "borderStation": false
  },
  {
    "abbrev": "KBP",
    "name": "Köln Airport / Businesspark",
    "short": "K Businesspark",
    "type": "Hp",
    "status": "in use",
    "locationCode": "DE15679",
    "UIC": "0080",
    "regionId": 3,
    "validFrom": "2008-12-14T01:00:00.000Z",
    "validTill": null,
    "id": 717298,
    "timeTableRelevant": true,
    "borderStation": false
  },
  {
    "abbrev": "KDZH",
    "name": "Köln-Deutz Hafen",
    "short": "Köln-Deutz Hafen",
    "type": "NE-Bf",
    "status": "in use",
    "locationCode": "",
    "UIC": "0080",
    "regionId": null,
    "validFrom": "2008-12-14T01:00:00.000Z",
    "validTill": null,
    "id": 480103,
    "timeTableRelevant": true,
    "borderStation": false
  },
  {
    "abbrev": "KFKB",
    "name": "Köln/Bonn Flughafen",
    "short": "Köln/Bonn Flughf",
    "type": "Bf",
    "status": "in use",
    "locationCode": "DE15723",
    "UIC": "0080",
    "regionId": 3,
    "validFrom": "2008-12-14T01:00:00.000Z",
    "validTill": null,
    "id": 320036,
    "timeTableRelevant": true,
    "borderStation": false
  },
  {
    "abbrev": "KFKBB",
    "name": "Köln/Bonn Flughafen (Bus)",
    "short": "Köln/B Flugh Bus",
    "type": "Bush",
    "status": "in use",
    "locationCode": "",
    "UIC": "0080",
    "regionId": null,
    "validFrom": "2014-11-25T01:00:00.000Z",
    "validTill": null,
    "id": null,
    "timeTableRelevant": false,
    "borderStation": false
  },
  {
    "abbrev": "KFNO",
    "name": "Köln Flughafen Nordost",
    "short": "Flughf Nordost",
    "type": "Abzw",
    "status": "in use",
    "locationCode": "DE15697",
    "UIC": "0080",
    "regionId": 3,
    "validFrom": "2008-12-14T01:00:00.000Z",
    "validTill": null,
    "id": 320002,
    "timeTableRelevant": true,
    "borderStation": false
  },
  {
    "abbrev": "KFNW",
    "name": "Köln Flughafen Nordwest",
    "short": "Flughf Nordwest",
    "type": "Abzw",
    "status": "in use",
    "locationCode": "DE15698",
    "UIC": "0080",
    "regionId": 3,
    "validFrom": "2008-12-14T01:00:00.000Z",
    "validTill": null,
    "id": 319756,
    "timeTableRelevant": true,
    "borderStation": false
  },
  {
    "abbrev": "KFQ",
    "name": "Köln Quarzwerke",
    "short": "Quarzwerke",
    "type": "NE-Anst",
    "status": "in use",
    "locationCode": "",
    "UIC": "0080",
    "regionId": null,
    "validFrom": "2008-12-14T01:00:00.000Z",
    "validTill": null,
    "id": 480376,
    "timeTableRelevant": true,
    "borderStation": false
  },
  {
    "abbrev": "KGOF",
    "name": "Köln-Godorf",
    "short": "Köln-Godorf",
    "type": "NE-Hp",
    "status": "in use",
    "locationCode": "",
    "UIC": "0080",
    "regionId": null,
    "validFrom": "2008-12-18T01:00:00.000Z",
    "validTill": null,
    "id": 480301,
    "timeTableRelevant": true,
    "borderStation": false
  },
  {
    "abbrev": "KGOH",
    "name": "Köln-Godorf Hafen Degussa",
    "short": "Köln-Godorf Hf D",
    "type": "NE-Bf",
    "status": "in use",
    "locationCode": "",
    "UIC": "0080",
    "regionId": null,
    "validFrom": "2008-12-14T01:00:00.000Z",
    "validTill": null,
    "id": 480343,
    "timeTableRelevant": true,
    "borderStation": false
  },
  {
    "abbrev": "KGUX",
    "name": "Köln-Deutz 2 ESTW",
    "short": "K-Deutz 2 ESTW",
    "type": "BZ",
    "status": "in use",
    "locationCode": "",
    "UIC": "0080",
    "regionId": 3,
    "validFrom": "2008-01-01T01:00:00.000Z",
    "validTill": null,
    "id": 104562,
    "timeTableRelevant": false,
    "borderStation": false
  },
  {
    "abbrev": "KK",
    "name": "Köln Hbf",
    "short": "Köln Hbf",
    "type": "Bf",
    "status": "in use",
    "locationCode": "DE15705",
    "UIC": "0080",
    "regionId": 3,
    "validFrom": "2008-12-14T01:00:00.000Z",
    "validTill": null,
    "id": 29322,
    "timeTableRelevant": true,
    "borderStation": false
  },
  {
    "abbrev": "KK  P",
    "name": "Köln Hbf Breslauer Platz",
    "short": "Köln Hbf BreslPl",
    "type": "Bush",
    "status": "in use",
    "locationCode": "",
    "UIC": "0080",
    "regionId": null,
    "validFrom": "1999-01-01T01:00:00.000Z",
    "validTill": null,
    "id": null,
    "timeTableRelevant": false,
    "borderStation": false
  }
]

Über die hier erhaltene Property „abbrev“ kann man in der Timetable-API die EVA Bezeichnung eines Bahnhofs ermitteln, die man für die weiteren Anfragen benötigt.

Mit dem Wert dieser Property kann auch über oben beschriebene Anfrage (https://api.deutschebahn.com/timetables/v1/station/{Bahnhofsname}) gesucht werden – mit eindeutigem Ergebnis.

Mit der Anfrage
https://api.deutschebahn.com/timetables/v1/plan/{evaNo}/{date}/{hour}
lassen sich zu Zugverbindungen die jeweiligen IDs ermitteln. Leider bekommt man hier nur ein Ergebnis wenn die angegebene date-hour Kombination max. 13 Stunden in der Vergangenheit und 18 Stunden in der Zukunft liegt.

Nun kann man sich mit folgender Anfrage
https://api.deutschebahn.com/timetables/v1/fchg/{evaNo}
die Änderungen an einem bestimmten Bahnhof ausgeben lassen. Hier werden Änderungen zu allen Zugverbindungen angezeigt, daher muss man hier mit der vorher ermittelten ID der Zugverbindung die relevanten Infos herausfiltern. Diese Anfrage wird ab 20 Minuten vor der planmäßigen Abfahrt bis zur planmäßigen Abfahrt + bisher bekannte Verspätung minütlich gestellt um immer auf dem aktuellen Stand zu bleiben.

Für den Kölner Hauptbahnhof sieht eine Antwort zum Beispiel so aus:

<timetable station="K&#246;ln Hbf" eva="8000207">
  
  <s id="7069447941423545793-1804072224-29" eva="8000207">
        <m id="r57683s" t="r" from="1804071444" to="1804072015" ts="1804071444"/>
        <ar ct="1804072347" l="11"/>
        <dp ct="1804072348" l="11"/>
  </s>
  
  
  <s id="-171995145270321019-1804072216-12" eva="8000207">
        <ar ct="1804072347" l="5">
              <m id="r28769625" t="d" c="35" ts="1804072206"/>
              <m id="r28770034" t="d" c="35" ts="1804072228"/>
        </ar>
        <dp ct="1804072350" l="5">
              <m id="r28769625" t="d" c="35" ts="1804072206"/>
              <m id="r28770034" t="d" c="35" ts="1804072228"/>
        </dp>
  </s>
  
  
  <s id="-3073949015818699333-1804072110-15" eva="8000207">
        <ar ct="1804072237" l="9"/>
  </s>
  
  
  <s id="4065472633490480606-1804072217-13" eva="8000207">
        <ar ct="1804072259" l="12">
              <m id="r28761300" t="q" c="82" ts="1804071743"/>
        </ar>
        <dp ct="1804072300" l="12">
              <m id="r28761300" t="q" c="82" ts="1804071743"/>
        </dp>
  </s>
  
  
  <s id="6047904296347569207-1804072124-29" eva="8000207">
        <m id="r57683s" t="r" from="1804071444" to="1804072015" ts="1804071444"/>
        <ar ct="1804072305" l="11">
              <m id="r28769131" t="d" c="99" ts="1804072142"/>
        </ar>
        <dp ct="1804072306" l="11">
              <m id="r28769131" t="d" c="99" ts="1804072142"/>
        </dp>
  </s>
</timetable>

Eine Dokumentation der Daten erhält man zur API.

In meinem Widget kann ich letztendlich nach Bahnhöfen suchen und mir dort Verbindungen zu „Meine Verbindungen“ hinzufügen. Das Widget zeigt nur Verbindungen die in einem Zeitfenster von +-4 Stunden zur aktuellen Uhrzeit liegen. Somit werden mir morgens nur die Hin-Verbindungen und nachmittags nur die Rück-Verbindungen angezeigt. Beträgt die Verspätung mehr als 4 Minuten, wird die Verspätung rot gekennzeichnet. Fällt ein Zug aus, wird der Eintrag rot durchgestrichen. Hat sich das Gleis geändert, wird der neue Wert in rot dargestellt.

Euch hier Codeausschnitte zu präsentieren macht nicht viel Sinn, da man ohne meine verwendeten Klassen schlecht durchblicken würde und die würden den Rahmen des Blog-Eintrags definitiv sprengen. Die App ist bis jetzt noch nicht im Play Store. Hier noch ein paar Screenshots aus der App:

  • Widget zeigt Abfahrtszeit(+Verspätung), Zugbezeichnung, Gleis und Uhrzeit der letzten Aktualisierung
Categories: Android