[KVV] Replace underlying API (#13186)

* [KVV] Replace underlying API

The underlying API does no longer work so it is replace. An API key is no longer necessary.
Users need to replace the `stopId` in there station thing configuration. See README.md for details.

Signed-off-by: Maximilian Hess <mail@ne0h.de>
Co-authored-by: Laurent Garnier <lg.hc@free.fr>
This commit is contained in:
Maximilian Heß 2022-09-25 14:29:10 +02:00 committed by GitHub
parent 79309cb746
commit 8ab5f629c3
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 91 additions and 73 deletions

View File

@ -13,14 +13,38 @@ This includes the name of the train, the final destination and the estimated tim
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
@ -40,8 +64,8 @@ Each set consists of the following three channels:
### 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" ]
}
```

View File

@ -17,8 +17,10 @@ import java.util.List;
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
*
@ -26,18 +28,12 @@ import org.eclipse.jdt.annotation.NonNullByDefault;
@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 + "]";
}
/**
@ -49,28 +45,29 @@ public class DepartureResult {
@NonNullByDefault
public static class Departure {
public String route = "";
@SerializedName(value = "servingLine")
public Route route = new Route();
public String destination = "";
public String direction = "";
public String time = "";
public String vehicleType = "";
public boolean lowfloor;
public boolean realtime;
public int traction;
public String stopPosition = "";
@SerializedName(value = "countdown")
public String eta = "";
@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 "Departure [" + route + ", eta=" + eta + "]";
}
}
@NonNullByDefault
public static class Route {
@SerializedName(value = "number")
public String name = "";
public String direction = "";
@Override
public String toString() {
return "name=" + name + ", direction=" + direction;
}
}
}

View File

@ -39,7 +39,7 @@ public class KVVBindingConstants {
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;

View File

@ -23,18 +23,14 @@ import org.eclipse.jdt.annotation.NonNullByDefault;
@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 = "";
}
}

View File

@ -61,15 +61,7 @@ public class KVVBridgeHandler extends BaseBridgeHandler {
@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);
}
@ -91,8 +83,7 @@ public class KVVBridgeHandler extends BaseBridgeHandler {
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 {

View File

@ -32,6 +32,8 @@ import org.openhab.core.thing.binding.builder.ChannelBuilder;
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
@ -42,6 +44,8 @@ import org.openhab.core.types.RefreshType;
@NonNullByDefault
public class KVVStopHandler extends BaseThingHandler {
private final Logger logger = LoggerFactory.getLogger(KVVStopHandler.class);
@Nullable
private ScheduledFuture<?> pollingJob;
@ -91,18 +95,25 @@ public class KVVStopHandler extends BaseThingHandler {
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,
@ -126,16 +137,19 @@ public class KVVStopHandler extends BaseThingHandler {
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);

View File

@ -12,9 +12,7 @@ thing-type.kvv.stop.description = Train stop for KVV Binding.
# 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.

View File

@ -8,17 +8,15 @@
<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>