Since every stop is represented by a KVV-provided id, this id has to be figured out via an API call.
-### Example Call for Stop 'Karlsruhe Volkswohnung'
+### Example Call for Stop 'Gottesauer Platz/BGV'
```bash
-# Request
-curl https://live.kvv.de/webapp/stops/byname/Volkswohnung\?key\=[APIKEY]
+export QUERY="gottesauer"
+curl https://www.kvv.de/tunnelEfaDirect.php?action=XSLT_STOPFINDER_REQUEST&name_sf=${QUERY}&outputFormat=JSON&type_sf=any
+```
-# Response
-{"stops":[{"id":"de:8212:72","name":"Karlsruhe Volkswohnung","lat":49.00381654,"lon":8.40393026}]}
+The exact `id` may be extracted from the JSON-encoded reponse. E.g.
+
+```json
+"points": [
+{
+ "usage": "sf",
+ "type": "any",
+ "name": "Karlsruhe, Gottesauer Platz/BGV",
+ "stateless": "7000006",
+ "anyType": "stop",
+ "sort": "2",
+ "quality": "949",
+ "best": "0",
+ "object": "Gottesauer Platz/BGV",
+ "mainLoc": "Karlsruhe",
+ "modes": "1,4,5",
+ "ref": {
+ "id": "7000006",
+ "gid": "de:08212:6",
+ "omc": "8212000",
+ "placeID": "15",
+ "place": "Karlsruhe",
+ "coords": "937855.00000,5723868.00000"
+ }
+}
```
## Channel Configuration
### Things
```things
-Bridge kvv:bridge:1 "Bridge" @ "Wohnzimmer" [ maxTrains="3", updateInterval="10", apiKey="" ] {
- stop gottesauerplatz "Gottesauer Platz/BGV" [ stopId="de:8212:6" ]
+Bridge kvv:bridge:1 "Bridge" @ "Wohnzimmer" [ maxTrains="3", updateInterval="10" ] {
+ stop gottesauerplatz "Gottesauer Platz/BGV" [ stopId="7000006" ]
}
```
import org.eclipse.jdt.annotation.NonNullByDefault;
+import com.google.gson.annotations.SerializedName;
+
/**
- * Represents the result of a call to the KVV api to fetch the next departures at a specific stop.
+ * Represents the result of a call to the KVV API to fetch the next departures at a specific stop.
*
* @author Maximilian Hess - Initial contribution
*
@NonNullByDefault
public class DepartureResult {
- public String stopName;
-
- public List<Departure> departures;
-
- public DepartureResult() {
- this.stopName = "";
- this.departures = new ArrayList<Departure>();
- }
+ @SerializedName(value = "departureList")
+ public List<Departure> departures = new ArrayList<Departure>();
@Override
public String toString() {
- return "DepartureResult [stopName=" + stopName + ", departures=" + departures + "]";
+ return "DepartureResult [departures=" + departures + "]";
}
/**
@NonNullByDefault
public static class Departure {
- public String route = "";
-
- public String destination = "";
-
- public String direction = "";
-
- public String time = "";
+ @SerializedName(value = "servingLine")
+ public Route route = new Route();
- public String vehicleType = "";
+ @SerializedName(value = "countdown")
+ public String eta = "";
- public boolean lowfloor;
+ @Override
+ public String toString() {
+ return "Departure [" + route + ", eta=" + eta + "]";
+ }
+ }
- public boolean realtime;
+ @NonNullByDefault
+ public static class Route {
- public int traction;
+ @SerializedName(value = "number")
+ public String name = "";
- public String stopPosition = "";
+ public String direction = "";
@Override
public String toString() {
- final String timePrefix = (this.time.endsWith("min")) ? " in " : " at ";
- return "Route " + this.route + timePrefix + this.time + " heading to " + this.destination;
+ return "name=" + name + ", direction=" + direction;
}
}
}
public static final List<ThingTypeUID> SUPPORTED_THING_TYPES = Arrays.asList(THING_TYPE_BRIDGE, THING_TYPE_STOP);
/** URL of the KVV API */
- public static final String API_URL = "https://live.kvv.de/webapp";
+ public static final String API_FORMAT = "https://projekte.kvv-efa.de/sl3-alone/XSLT_DM_REQUEST?outputFormat=JSON&coordOutputFormat=WGS84%%5Bdd.ddddd%%5D&depType=stopEvents&locationServerActive=1&mode=direct&name_dm=%s&type_dm=stop&useOnlyStops=1&useRealtime=1&limit=%d";
/** timeout for API calls in seconds */
public static final int TIMEOUT_IN_SECONDS = 10;
@NonNullByDefault
public class KVVBridgeConfig {
- /** maximum number of traines being queried */
+ /** maximum number of trains being queried */
public int maxTrains;
/** the update interval in seconds */
public int updateInterval;
- /** API key of the KVV API */
- public String apiKey;
-
public KVVBridgeConfig() {
- this.maxTrains = 0;
+ this.maxTrains = 1;
this.updateInterval = 10;
- this.apiKey = "";
}
}
@Override
public void initialize() {
- updateStatus(ThingStatus.UNKNOWN);
-
this.config = getConfigAs(KVVBridgeConfig.class);
- if (this.config.apiKey.isEmpty()) {
- updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
- "Failed to get bridge configuration");
- return;
- }
-
updateStatus(ThingStatus.ONLINE);
}
return cr;
}
- final String url = KVVBindingConstants.API_URL + "/departures/bystop/" + stopConfig.stopId + "?key="
- + config.apiKey + "&maxInfos=" + config.maxTrains;
+ final String url = String.format(KVVBindingConstants.API_FORMAT, stopConfig.stopId, config.maxTrains);
String data;
try {
import org.openhab.core.thing.type.ChannelTypeUID;
import org.openhab.core.types.Command;
import org.openhab.core.types.RefreshType;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
/**
* KVVStopHandler represents a stop and holds information about the trains
@NonNullByDefault
public class KVVStopHandler extends BaseThingHandler {
+ private final Logger logger = LoggerFactory.getLogger(KVVStopHandler.class);
+
@Nullable
private ScheduledFuture<?> pollingJob;
final ChannelTypeUID destType = new ChannelTypeUID(this.thing.getBridgeUID().getBindingId(), "destination");
final ChannelTypeUID etaType = new ChannelTypeUID(this.thing.getBridgeUID().getBindingId(), "eta");
+ if (bridgeHandler.getBridgeConfig().maxTrains == 0) {
+ logger.warn("maxTrains is '0', not creating any channels");
+ }
+
final List<Channel> channels = new ArrayList<Channel>();
for (int i = 0; i < bridgeHandler.getBridgeConfig().maxTrains; i++) {
- channels.add(ChannelBuilder.create(new ChannelUID(this.thing.getUID(), "train" + i + "-name"), "String")
- .withType(nameType).build());
- channels.add(ChannelBuilder
- .create(new ChannelUID(this.thing.getUID(), "train" + i + "-destination"), "String")
- .withType(destType).build());
- channels.add(ChannelBuilder.create(new ChannelUID(this.thing.getUID(), "train" + i + "-eta"), "String")
- .withType(etaType).build());
+ ChannelUID c = new ChannelUID(this.thing.getUID(), "train" + i + "-name");
+ channels.add(ChannelBuilder.create(c, "String").withType(nameType).build());
+ logger.debug("Created channel {}", c);
+
+ c = new ChannelUID(this.thing.getUID(), "train" + i + "-destination");
+ channels.add(ChannelBuilder.create(c, "String").withType(destType).build());
+ logger.debug("Created channel {}", c);
+
+ c = new ChannelUID(this.thing.getUID(), "train" + i + "-eta");
+ channels.add(ChannelBuilder.create(c, "String").withType(etaType).build());
+ logger.debug("Created channel {}", c);
}
this.updateThing(this.editThing().withChannels(channels).build());
-
}
this.pollingJob = this.scheduler.scheduleWithFixedDelay(new UpdateTask(bridgeHandler, this.config), 0,
private synchronized void setDepartures(final DepartureResult departures, final int maxTrains) {
int i = 0;
for (; i < departures.departures.size(); i++) {
+ final DepartureResult.Departure departure = departures.departures.get(i);
+
this.updateState(new ChannelUID(this.thing.getUID(), "train" + i + "-name"),
- new StringType(departures.departures.get(i).route));
+ new StringType(departure.route.name));
this.updateState(new ChannelUID(this.thing.getUID(), "train" + i + "-destination"),
- new StringType(departures.departures.get(i).destination));
- String eta = departures.departures.get(i).time;
- if (eta.equals("0")) {
+ new StringType(departure.route.direction));
+ String eta = departure.eta;
+ if ("0".equals(eta)) {
eta += " min";
}
this.updateState(new ChannelUID(this.thing.getUID(), "train" + i + "-eta"), new StringType(eta));
}
+
for (; i < maxTrains; i++) {
this.updateState(new ChannelUID(this.thing.getUID(), "train" + i + "-name"), StringType.EMPTY);
this.updateState(new ChannelUID(this.thing.getUID(), "train" + i + "-destination"), StringType.EMPTY);
# thing types config
-thing-type.config.kvv.bridge.apiKey.label = API key
-thing-type.config.kvv.bridge.apiKey.description = API key of the KVV API
-thing-type.config.kvv.bridge.maxTrains.label = Maximum Trains Listed
+thing-type.config.kvv.bridge.maxTrains.label = Maximum Trains listed
thing-type.config.kvv.bridge.maxTrains.description = Maximum number of trains listed.
thing-type.config.kvv.bridge.updateInterval.label = Update Interval
thing-type.config.kvv.bridge.updateInterval.description = Update interval in seconds.
<description>The KVV bridge connects to the KVV API.</description>
<config-description>
- <parameter name="maxTrains" type="integer" required="true" min="0" max="127">
+ <parameter name="maxTrains" type="integer" min="1" max="127">
<label>Maximum Trains Listed</label>
<description>Maximum number of trains listed.</description>
+ <default>1</default>
</parameter>
- <parameter name="updateInterval" type="integer" required="true" min="1">
+ <parameter name="updateInterval" type="integer" min="1">
<label>Update Interval</label>
<description>Update interval in seconds.</description>
- </parameter>
- <parameter name="apiKey" type="text" required="true">
- <label>API key</label>
- <description>API key of the KVV API</description>
+ <default>10</default>
</parameter>
</config-description>
</bridge-type>