mirror of
https://github.com/openhab/openhab-addons.git
synced 2025-01-10 07:02:02 +01:00
[publictransportswitzerland] Public Transport Switzerland Initial contribution (#8540)
* [publictransportswitzerland] Initital commit Signed-off-by: Jeremy Stucki <dev@jeremystucki.ch> * [publictransportswitzerland] Add json stationboard Signed-off-by: Jeremy Stucki <dev@jeremystucki.ch> * [publictransportswitzerland] Add csv stationboard Signed-off-by: Jeremy Stucki <dev@jeremystucki.ch> * [publictransportswitzerland] Encoding / Data usage Signed-off-by: Jeremy Stucki <dev@jeremystucki.ch> * [publictransportswitzerland] Update readme Signed-off-by: Jeremy Stucki <dev@jeremystucki.ch> * [publictransportswitzerland] Update binding.xml Signed-off-by: Jeremy Stucki <dev@jeremystucki.ch> * [publictransportswitzerland] Remove code owner Signed-off-by: Jeremy Stucki <dev@jeremystucki.ch> * [publictransportswitzerland] Add more detailed thing status Signed-off-by: Jeremy Stucki <dev@jeremystucki.ch> * [publictransportswitzerland] Make field filters a constant Signed-off-by: Jeremy Stucki <dev@jeremystucki.ch> * [publictransportswitzerland] Remove json channel Signed-off-by: Jeremy Stucki <dev@jeremystucki.ch> * [publictransportswitzerland] Stop scheduler on config error Signed-off-by: Jeremy Stucki <dev@jeremystucki.ch> * [publictransportswitzerland] Work on i18n Signed-off-by: Jeremy Stucki <dev@jeremystucki.ch> * [publictransportswitzerland] Remove TODO Signed-off-by: Jeremy Stucki <dev@jeremystucki.ch> * [publictransportswitzerland] Re-order members Signed-off-by: Jeremy Stucki <dev@jeremystucki.ch> * [publictransportswitzerland] Update label Signed-off-by: Jeremy Stucki <dev@jeremystucki.ch> * [publictransportswitzerland] Set status to unknown instead of initializing Signed-off-by: Jeremy Stucki <dev@jeremystucki.ch> * [publictransportswitzerland] Update version Signed-off-by: Jeremy Stucki <dev@jeremystucki.ch> * [publictransportswitzerland] Log api response Signed-off-by: Jeremy Stucki <dev@jeremystucki.ch> * [publictransportswitzerland] Mark CSV advanced Signed-off-by: Jeremy Stucki <dev@jeremystucki.ch> * [publictransportswitzerland] Add error message Signed-off-by: Jeremy Stucki <dev@jeremystucki.ch> * [publictransportswitzerland] Update readme Signed-off-by: Jeremy Stucki <dev@jeremystucki.ch> * [publictransportswitzerland] Move members Signed-off-by: Jeremy Stucki <dev@jeremystucki.ch> * [publictransportswitzerland] Add dynamic channels Signed-off-by: Jeremy Stucki <dev@jeremystucki.ch> * [publictransportswitzerland] Implement dispose Signed-off-by: Jeremy Stucki <dev@jeremystucki.ch> * [publictransportswitzerland] Remove unnecessary translation Signed-off-by: Jeremy Stucki <dev@jeremystucki.ch> * [publictransportswitzerland] Rename csv -> tsv Signed-off-by: Jeremy Stucki <dev@jeremystucki.ch> * [publictransportswitzerland] Update readme Signed-off-by: Jeremy Stucki <dev@jeremystucki.ch> * [publictransportswitzerland] Update train names Signed-off-by: Jeremy Stucki <dev@jeremystucki.ch> * [publictransportswitzerland] Fix markdown table Signed-off-by: Jeremy Stucki <dev@jeremystucki.ch> * [publictransportswitzerland] Use UNDEF Signed-off-by: Jeremy Stucki <dev@jeremystucki.ch> * [publictransportswitzerland] Allow departures without platform Signed-off-by: Jeremy Stucki <dev@jeremystucki.ch> * [publictransportswitzerland] Work on display logic Signed-off-by: Jeremy Stucki <dev@jeremystucki.ch> * [publictransportswitzerland] Use null Signed-off-by: Jeremy Stucki <dev@jeremystucki.ch> * [publictransportswitzerland] Make style checks happy Signed-off-by: Jeremy Stucki <dev@jeremystucki.ch> * [publictransportswitzerland] Implement refresh command Signed-off-by: Jeremy Stucki <dev@jeremystucki.ch> * [publictransportswitzerland] Avoid hitting API limits Signed-off-by: Jeremy Stucki <dev@jeremystucki.ch> * [publictransportswitzerland] Use expiring cache Signed-off-by: Jeremy Stucki <dev@jeremystucki.ch> * [publictransportswitzerland] Update version Signed-off-by: Jeremy Stucki <dev@jeremystucki.ch> * [publictransportswitzerland] Increase channel update interval Signed-off-by: Jeremy Stucki <dev@jeremystucki.ch> * [publictransportswitzerland] Use lower cache expiration time Signed-off-by: Jeremy Stucki <dev@jeremystucki.ch> * [publictransportswitzerland] Avoid glob import Signed-off-by: Jeremy Stucki <dev@jeremystucki.ch> * [publictransportswitzerland] Make compiler happier Signed-off-by: Jeremy Stucki <dev@jeremystucki.ch> * [publictransportswitzerland] Add error explanation Signed-off-by: Jeremy Stucki <dev@jeremystucki.ch> * [publictransportswitzerland] Skip check Signed-off-by: Jeremy Stucki <dev@jeremystucki.ch> * [publictransportswitzerland] Print message instead of stacktrace Signed-off-by: Jeremy Stucki <dev@jeremystucki.ch> * [publictransportswitzerland] Store date format as final field Signed-off-by: Jeremy Stucki <dev@jeremystucki.ch> * [publictransportswitzerland] Cache configuration Signed-off-by: Jeremy Stucki <dev@jeremystucki.ch> * [publictransportswitzerland] Work on exception handling Signed-off-by: Jeremy Stucki <dev@jeremystucki.ch> * [publictransportswitzerland] Update readme Signed-off-by: Jeremy Stucki <dev@jeremystucki.ch> * [publictransportswitzerland] Make style checks happy Signed-off-by: Jeremy Stucki <dev@jeremystucki.ch> * [publictransportswitzerland] Bump version Signed-off-by: Jeremy Stucki <dev@jeremystucki.ch> * [publictransportswitzerland] Work on compiler warnings Signed-off-by: Jeremy Stucki <dev@jeremystucki.ch> * [publictransportswitzerland] Add more detailed introduction Signed-off-by: Jeremy Stucki <dev@jeremystucki.ch> * [publictransportswitzerland] Move tsv into group Signed-off-by: Jeremy Stucki <dev@jeremystucki.ch> * [publictransportswitzerland] Bump version Signed-off-by: Jeremy Stucki <dev@jeremystucki.ch> * [publictransportswitzerland] Update thing description Co-authored-by: Fabian Wolter <github@fabian-wolter.de> Signed-off-by: Jeremy Stucki <dev@jeremystucki.ch> * [publictransportswitzerland] Update thing description Co-authored-by: Fabian Wolter <github@fabian-wolter.de> Signed-off-by: Jeremy Stucki <dev@jeremystucki.ch> * [publictransportswitzerland] Remove explicit channel creation Signed-off-by: Jeremy Stucki <dev@jeremystucki.ch> * [publictransportswitzerland] Run mvn spotless:apply Signed-off-by: Jeremy Stucki <dev@jeremystucki.ch> * [publictransportswitzerland] Migrate to OH3 Signed-off-by: Jeremy Stucki <dev@jeremystucki.ch> * [publictransportswitzerland] Work on OH3 migration Signed-off-by: Jeremy Stucki <dev@jeremystucki.ch> * [publictransportswitzerland] Bump version Signed-off-by: Jeremy Stucki <dev@jeremystucki.ch> * [publictransportswitzerland] Define channel-type for departures Signed-off-by: Jeremy Stucki <dev@jeremystucki.ch> * [publictransportswitzerland] Update copyright notice Signed-off-by: Jeremy Stucki <dev@jeremystucki.ch> * [publictransportswitzerland] Move channel types below thing types Signed-off-by: Jeremy Stucki <dev@jeremystucki.ch> * [publictransportswitzerland] Remove ignored files Signed-off-by: Jeremy Stucki <dev@jeremystucki.ch> * [publictransportswitzerland] Remove author tag Signed-off-by: Jeremy Stucki <dev@jeremystucki.ch> * [publictransportswitzerland] Update feature description Signed-off-by: Jeremy Stucki <dev@jeremystucki.ch> * [publictransportswitzerland] Make linter happy Signed-off-by: Jeremy Stucki <dev@jeremystucki.ch> * [publictransportswitzerland] Update README Signed-off-by: Jeremy Stucki <dev@jeremystucki.ch> * Bump version Signed-off-by: Fabian Wolter <github@fabian-wolter.de> Co-authored-by: Fabian Wolter <github@fabian-wolter.de>
This commit is contained in:
parent
d38f37fa1a
commit
1cae863569
@ -245,6 +245,7 @@
|
||||
/bundles/org.openhab.binding.plugwiseha/ @lsiepel
|
||||
/bundles/org.openhab.binding.powermax/ @lolodomo
|
||||
/bundles/org.openhab.binding.proteusecometer/ @2chilled
|
||||
/bundles/org.openhab.binding.publictransportswitzerland/ @jeremystucki
|
||||
/bundles/org.openhab.binding.pulseaudio/ @peuter
|
||||
/bundles/org.openhab.binding.pushbullet/ @hakan42
|
||||
/bundles/org.openhab.binding.pushover/ @cweitkamp
|
||||
|
@ -1216,6 +1216,11 @@
|
||||
<artifactId>org.openhab.binding.proteusecometer</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.openhab.addons.bundles</groupId>
|
||||
<artifactId>org.openhab.binding.publictransportswitzerland</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.openhab.addons.bundles</groupId>
|
||||
<artifactId>org.openhab.binding.pulseaudio</artifactId>
|
||||
|
@ -0,0 +1,13 @@
|
||||
This content is produced and maintained by the openHAB project.
|
||||
|
||||
* Project home: https://www.openhab.org
|
||||
|
||||
== Declared Project Licenses
|
||||
|
||||
This program and the accompanying materials are made available under the terms
|
||||
of the Eclipse Public License 2.0 which is available at
|
||||
https://www.eclipse.org/legal/epl-2.0/.
|
||||
|
||||
== Source Code
|
||||
|
||||
https://github.com/openhab/openhab-addons
|
@ -0,0 +1,43 @@
|
||||
# Public Transport Switzerland Binding
|
||||
|
||||
Connects to the "Swiss public transport API" to provide real-time public transport information. [Link to the API](https://transport.opendata.ch/)
|
||||
|
||||
For example, here is a station board in HABPanel. (Download [here](https://github.com/StefanieJaeger/HABPanel-departure-board))
|
||||
|
||||
![Departure board in HABPanel](doc/departure_board_habpanel.png)
|
||||
|
||||
## Supported Things
|
||||
|
||||
### Stationboard
|
||||
|
||||
Upcoming departures for a single station. This is what you would usually see displayed at the train station.
|
||||
|
||||
#### Channels
|
||||
|
||||
| channel | type | description |
|
||||
|----------------|--------|----------------------------------------------------------------------------------------------|
|
||||
| departures#n | String | A dynamic channel for each upcoming departure |
|
||||
| tsv (advanced) | String | A tsv which contains the fields:<br />`identifier, departureTime, destination, track, delay` |
|
||||
|
||||
#### UI based Configuration
|
||||
|
||||
`station` is the station name for which to display departures.
|
||||
The name has to be one that is used by the swiss federal railways.
|
||||
Please consult their [website](https://sbb.ch/en).
|
||||
|
||||
#### Textual configuration
|
||||
|
||||
##### Thing
|
||||
```
|
||||
Thing publictransportswitzerland:stationboard:zurich [ station="Zürich HB" ]
|
||||
```
|
||||
|
||||
##### Items
|
||||
```
|
||||
String Next_Departure "Next Departure" { channel="publictransportswitzerland:stationboard:zurich:departures#1" }
|
||||
String Upcoming_Departures_TSV "Upcoming_Departures_TSV" { channel="publictransportswitzerland:stationboard:zurich:tsv" }
|
||||
```
|
||||
|
||||
## Discovery
|
||||
|
||||
This binding does not support auto-discovery.
|
Binary file not shown.
After Width: | Height: | Size: 272 KiB |
@ -0,0 +1,17 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://maven.apache.org/POM/4.0.0"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<parent>
|
||||
<groupId>org.openhab.addons.bundles</groupId>
|
||||
<artifactId>org.openhab.addons.reactor.bundles</artifactId>
|
||||
<version>3.3.0-SNAPSHOT</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>org.openhab.binding.publictransportswitzerland</artifactId>
|
||||
|
||||
<name>openHAB Add-ons :: Bundles :: PublicTransportSwitzerland Binding</name>
|
||||
|
||||
</project>
|
@ -0,0 +1,9 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<features name="org.openhab.binding.publictransportswitzerland-${project.version}" xmlns="http://karaf.apache.org/xmlns/features/v1.4.0">
|
||||
<repository>mvn:org.openhab.core.features.karaf/org.openhab.core.features.karaf.openhab-core/${ohc.version}/xml/features</repository>
|
||||
|
||||
<feature name="openhab-binding-publictransportswitzerland" description="Public Transport Switzerland Binding" version="${project.version}">
|
||||
<feature>openhab-runtime-base</feature>
|
||||
<bundle start-level="80">mvn:org.openhab.addons.bundles/org.openhab.binding.publictransportswitzerland/${project.version}</bundle>
|
||||
</feature>
|
||||
</features>
|
@ -0,0 +1,33 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2021 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.publictransportswitzerland.internal;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.openhab.core.thing.ThingTypeUID;
|
||||
|
||||
/**
|
||||
* The {@link PublicTransportSwitzerlandBindingConstants} class defines common constants, which are
|
||||
* used across the whole binding.
|
||||
*
|
||||
* @author Jeremy Stucki - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class PublicTransportSwitzerlandBindingConstants {
|
||||
|
||||
private static final String BINDING_ID = "publictransportswitzerland";
|
||||
|
||||
// List of all Thing Type UIDs
|
||||
public static final ThingTypeUID THING_TYPE_STATIONBOARD = new ThingTypeUID(BINDING_ID, "stationboard");
|
||||
|
||||
public static final String BASE_URL = "https://transport.opendata.ch/v1/";
|
||||
}
|
@ -0,0 +1,57 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2021 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.publictransportswitzerland.internal;
|
||||
|
||||
import static org.openhab.binding.publictransportswitzerland.internal.PublicTransportSwitzerlandBindingConstants.*;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.Set;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.openhab.binding.publictransportswitzerland.internal.stationboard.PublicTransportSwitzerlandStationboardHandler;
|
||||
import org.openhab.core.thing.Thing;
|
||||
import org.openhab.core.thing.ThingTypeUID;
|
||||
import org.openhab.core.thing.binding.BaseThingHandlerFactory;
|
||||
import org.openhab.core.thing.binding.ThingHandler;
|
||||
import org.openhab.core.thing.binding.ThingHandlerFactory;
|
||||
import org.osgi.service.component.annotations.Component;
|
||||
|
||||
/**
|
||||
* The {@link PublicTransportSwitzerlandHandlerFactory} is responsible for creating things and thing
|
||||
* handlers.
|
||||
*
|
||||
* @author Jeremy Stucki - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
@Component(configurationPid = "binding.publictransportswitzerland", service = ThingHandlerFactory.class)
|
||||
public class PublicTransportSwitzerlandHandlerFactory extends BaseThingHandlerFactory {
|
||||
|
||||
private static final Set<ThingTypeUID> SUPPORTED_THING_TYPES_UIDS = Collections.singleton(THING_TYPE_STATIONBOARD);
|
||||
|
||||
@Override
|
||||
public boolean supportsThingType(ThingTypeUID thingTypeUID) {
|
||||
return SUPPORTED_THING_TYPES_UIDS.contains(thingTypeUID);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected @Nullable ThingHandler createHandler(Thing thing) {
|
||||
ThingTypeUID thingTypeUID = thing.getThingTypeUID();
|
||||
|
||||
if (THING_TYPE_STATIONBOARD.equals(thingTypeUID)) {
|
||||
return new PublicTransportSwitzerlandStationboardHandler(thing);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
@ -0,0 +1,31 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2021 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.publictransportswitzerland.internal.stationboard;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
|
||||
/**
|
||||
* The {@link PublicTransportSwitzerlandStationboardConfiguration} class contains fields mapping thing configuration
|
||||
* parameters.
|
||||
*
|
||||
* @author Jeremy Stucki - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class PublicTransportSwitzerlandStationboardConfiguration {
|
||||
|
||||
/**
|
||||
* The station name
|
||||
*/
|
||||
public @Nullable String station;
|
||||
}
|
@ -0,0 +1,332 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2021 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.publictransportswitzerland.internal.stationboard;
|
||||
|
||||
import static org.openhab.binding.publictransportswitzerland.internal.PublicTransportSwitzerlandBindingConstants.*;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.URLEncoder;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ScheduledFuture;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.openhab.core.cache.ExpiringCache;
|
||||
import org.openhab.core.io.net.http.HttpUtil;
|
||||
import org.openhab.core.library.types.StringType;
|
||||
import org.openhab.core.thing.Channel;
|
||||
import org.openhab.core.thing.ChannelGroupUID;
|
||||
import org.openhab.core.thing.ChannelUID;
|
||||
import org.openhab.core.thing.Thing;
|
||||
import org.openhab.core.thing.ThingStatus;
|
||||
import org.openhab.core.thing.ThingStatusDetail;
|
||||
import org.openhab.core.thing.binding.BaseThingHandler;
|
||||
import org.openhab.core.thing.binding.builder.ChannelBuilder;
|
||||
import org.openhab.core.thing.binding.builder.ThingBuilder;
|
||||
import org.openhab.core.thing.type.ChannelTypeUID;
|
||||
import org.openhab.core.types.Command;
|
||||
import org.openhab.core.types.RefreshType;
|
||||
import org.openhab.core.types.UnDefType;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import com.google.gson.JsonArray;
|
||||
import com.google.gson.JsonElement;
|
||||
import com.google.gson.JsonObject;
|
||||
import com.google.gson.JsonParser;
|
||||
|
||||
/**
|
||||
* The {@link PublicTransportSwitzerlandStationboardHandler} is responsible for handling commands, which are
|
||||
* sent to one of the channels.
|
||||
*
|
||||
* @author Jeremy Stucki - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class PublicTransportSwitzerlandStationboardHandler extends BaseThingHandler {
|
||||
|
||||
// Limit the API response to the necessary fields
|
||||
private static final String FIELD_FILTERS = createFilterForFields("stationboard/to", "stationboard/category",
|
||||
"stationboard/number", "stationboard/stop/departureTimestamp", "stationboard/stop/delay",
|
||||
"stationboard/stop/platform");
|
||||
|
||||
private static final String TSV_CHANNEL = "tsv";
|
||||
|
||||
private final ChannelGroupUID dynamicChannelGroupUID = new ChannelGroupUID(getThing().getUID(), "departures");
|
||||
|
||||
private final Logger logger = LoggerFactory.getLogger(PublicTransportSwitzerlandStationboardHandler.class);
|
||||
|
||||
private final SimpleDateFormat timeFormat = new SimpleDateFormat("HH:mm");
|
||||
|
||||
private @Nullable ScheduledFuture<?> updateChannelsJob;
|
||||
private @Nullable ExpiringCache<@Nullable JsonElement> cache;
|
||||
private @Nullable PublicTransportSwitzerlandStationboardConfiguration configuration;
|
||||
|
||||
public PublicTransportSwitzerlandStationboardHandler(Thing thing) {
|
||||
super(thing);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleCommand(ChannelUID channelUID, Command command) {
|
||||
if (command instanceof RefreshType) {
|
||||
updateChannels();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void initialize() {
|
||||
// Together with the 10 second timeout, this should be less than a minute
|
||||
cache = new ExpiringCache<>(45_000, this::updateData);
|
||||
|
||||
PublicTransportSwitzerlandStationboardConfiguration configuration = getConfigAs(
|
||||
PublicTransportSwitzerlandStationboardConfiguration.class);
|
||||
this.configuration = configuration;
|
||||
|
||||
String configurationError = findConfigurationError(configuration);
|
||||
if (configurationError != null) {
|
||||
stopChannelUpdate();
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, configurationError);
|
||||
} else {
|
||||
updateStatus(ThingStatus.UNKNOWN);
|
||||
startChannelUpdate();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void dispose() {
|
||||
stopChannelUpdate();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleConfigurationUpdate(Map<String, Object> configurationParameters) {
|
||||
super.handleConfigurationUpdate(configurationParameters);
|
||||
|
||||
PublicTransportSwitzerlandStationboardConfiguration configuration = getConfigAs(
|
||||
PublicTransportSwitzerlandStationboardConfiguration.class);
|
||||
this.configuration = configuration;
|
||||
|
||||
ScheduledFuture<?> updateJob = updateChannelsJob;
|
||||
|
||||
String configurationError = findConfigurationError(configuration);
|
||||
if (configurationError != null) {
|
||||
stopChannelUpdate();
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, configurationError);
|
||||
} else if (updateJob == null || updateJob.isCancelled()) {
|
||||
startChannelUpdate();
|
||||
}
|
||||
}
|
||||
|
||||
private @Nullable String findConfigurationError(PublicTransportSwitzerlandStationboardConfiguration configuration) {
|
||||
String station = configuration.station;
|
||||
if (station == null || station.isEmpty()) {
|
||||
return "The station is not set";
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private void startChannelUpdate() {
|
||||
updateChannelsJob = scheduler.scheduleWithFixedDelay(this::updateChannels, 0, 60, TimeUnit.SECONDS);
|
||||
}
|
||||
|
||||
private void stopChannelUpdate() {
|
||||
ScheduledFuture<?> updateJob = updateChannelsJob;
|
||||
|
||||
if (updateJob != null) {
|
||||
updateJob.cancel(true);
|
||||
}
|
||||
}
|
||||
|
||||
public @Nullable JsonElement updateData() {
|
||||
PublicTransportSwitzerlandStationboardConfiguration config = configuration;
|
||||
if (config == null) {
|
||||
logger.warn("Unable to access configuration");
|
||||
return null;
|
||||
}
|
||||
|
||||
String station = config.station;
|
||||
if (station == null) {
|
||||
logger.warn("Station is null");
|
||||
return null;
|
||||
}
|
||||
|
||||
try {
|
||||
String escapedStation = URLEncoder.encode(station, StandardCharsets.UTF_8.name());
|
||||
String requestUrl = BASE_URL + "stationboard?station=" + escapedStation + FIELD_FILTERS;
|
||||
|
||||
String response = HttpUtil.executeUrl("GET", requestUrl, 10_000);
|
||||
logger.debug("Got response from API: {}", response);
|
||||
|
||||
return JsonParser.parseString(response);
|
||||
} catch (IOException e) {
|
||||
logger.warn("Unable to fetch stationboard data: {}", e.getMessage());
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private static String createFilterForFields(String... fields) {
|
||||
return Arrays.stream(fields).map((field) -> "&fields[]=" + field).collect(Collectors.joining());
|
||||
}
|
||||
|
||||
private void updateChannels() {
|
||||
ExpiringCache<@Nullable JsonElement> expiringCache = cache;
|
||||
|
||||
if (expiringCache == null) {
|
||||
logger.warn("Cache is null");
|
||||
return;
|
||||
}
|
||||
|
||||
JsonElement jsonObject = expiringCache.getValue();
|
||||
|
||||
if (jsonObject == null) {
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR);
|
||||
|
||||
updateState(TSV_CHANNEL, UnDefType.UNDEF);
|
||||
|
||||
for (Channel channel : getThing().getChannelsOfGroup(dynamicChannelGroupUID.getId())) {
|
||||
updateState(channel.getUID(), UnDefType.UNDEF);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
updateStatus(ThingStatus.ONLINE);
|
||||
|
||||
JsonArray stationboard = jsonObject.getAsJsonObject().get("stationboard").getAsJsonArray();
|
||||
|
||||
createDynamicChannels(stationboard.size());
|
||||
setUnusedDynamicChannelsToUndef(stationboard.size());
|
||||
|
||||
List<String> tsvRows = new ArrayList<>();
|
||||
|
||||
for (int i = 0; i < stationboard.size(); i++) {
|
||||
JsonElement jsonElement = stationboard.get(i);
|
||||
|
||||
JsonObject departureObject = jsonElement.getAsJsonObject();
|
||||
JsonElement stopElement = departureObject.get("stop");
|
||||
|
||||
if (stopElement == null) {
|
||||
logger.warn("Skipping stationboard item. Stop element is missing from departure object");
|
||||
continue;
|
||||
}
|
||||
|
||||
JsonObject stopObject = stopElement.getAsJsonObject();
|
||||
|
||||
JsonElement categoryElement = departureObject.get("category");
|
||||
JsonElement numberElement = departureObject.get("number");
|
||||
JsonElement destinationElement = departureObject.get("to");
|
||||
JsonElement departureTimeElement = stopObject.get("departureTimestamp");
|
||||
|
||||
if (categoryElement == null || numberElement == null || destinationElement == null
|
||||
|| departureTimeElement == null) {
|
||||
logger.warn("Skipping stationboard item."
|
||||
+ "One of the following is null: category: {}, number: {}, destination: {}, departureTime: {}",
|
||||
categoryElement, numberElement, destinationElement, departureTimeElement);
|
||||
continue;
|
||||
}
|
||||
|
||||
String category = categoryElement.getAsString();
|
||||
String number = numberElement.getAsString();
|
||||
String destination = destinationElement.getAsString();
|
||||
Long departureTime = departureTimeElement.getAsLong();
|
||||
|
||||
String identifier = createIdentifier(category, number);
|
||||
|
||||
String delay = getStringValueOrNull(departureObject.get("delay"));
|
||||
String track = getStringValueOrNull(stopObject.get("platform"));
|
||||
|
||||
updateState(getChannelUIDForPosition(i),
|
||||
new StringType(formatDeparture(identifier, departureTime, destination, track, delay)));
|
||||
tsvRows.add(String.join("\t", identifier, departureTimeElement.toString(), destination, track, delay));
|
||||
}
|
||||
|
||||
updateState(TSV_CHANNEL, new StringType(String.join("\n", tsvRows)));
|
||||
}
|
||||
|
||||
private @Nullable String getStringValueOrNull(@Nullable JsonElement jsonElement) {
|
||||
if (jsonElement == null || jsonElement.isJsonNull()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
String stringValue = jsonElement.getAsString();
|
||||
|
||||
if (stringValue.isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return stringValue;
|
||||
}
|
||||
|
||||
private String formatDeparture(String identifier, Long departureTimestamp, String destination,
|
||||
@Nullable String track, @Nullable String delay) {
|
||||
Date departureDate = new Date(departureTimestamp * 1000);
|
||||
String formattedDate = timeFormat.format(departureDate);
|
||||
|
||||
String result = String.format("%s - %s %s", formattedDate, identifier, destination);
|
||||
|
||||
if (track != null) {
|
||||
result += " - Pl. " + track;
|
||||
}
|
||||
|
||||
if (delay != null) {
|
||||
result += String.format(" (%s' late)", delay);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private String createIdentifier(String category, String number) {
|
||||
// Only show the number for buses
|
||||
if ("B".equals(category)) {
|
||||
return number;
|
||||
}
|
||||
|
||||
// Some weird quirk with the API
|
||||
if (number.startsWith(category)) {
|
||||
return category;
|
||||
}
|
||||
|
||||
return category + number;
|
||||
}
|
||||
|
||||
private void createDynamicChannels(int numberOfChannels) {
|
||||
List<Channel> existingChannels = getThing().getChannelsOfGroup(dynamicChannelGroupUID.getId());
|
||||
|
||||
ThingBuilder thingBuilder = editThing();
|
||||
|
||||
for (int i = existingChannels.size(); i < numberOfChannels; i++) {
|
||||
Channel channel = ChannelBuilder.create(getChannelUIDForPosition(i), "String")
|
||||
.withLabel("Departure " + (i + 1))
|
||||
.withType(new ChannelTypeUID("publictransportswitzerland", "departure")).build();
|
||||
thingBuilder.withChannel(channel);
|
||||
}
|
||||
|
||||
updateThing(thingBuilder.build());
|
||||
}
|
||||
|
||||
private void setUnusedDynamicChannelsToUndef(int amountOfUsedChannels) {
|
||||
getThing().getChannelsOfGroup(dynamicChannelGroupUID.getId()).stream().skip(amountOfUsedChannels)
|
||||
.forEach(channel -> updateState(channel.getUID(), UnDefType.UNDEF));
|
||||
}
|
||||
|
||||
private ChannelUID getChannelUIDForPosition(int position) {
|
||||
return new ChannelUID(dynamicChannelGroupUID, String.valueOf(position + 1));
|
||||
}
|
||||
}
|
@ -0,0 +1,9 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<binding:binding id="publictransportswitzerland" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xmlns:binding="https://openhab.org/schemas/binding/v1.0.0"
|
||||
xsi:schemaLocation="https://openhab.org/schemas/binding/v1.0.0 https://openhab.org/schemas/binding-1.0.0.xsd">
|
||||
|
||||
<name>Public Transport Switzerland Binding</name>
|
||||
<description>Connects to the "Swiss public transport API" to provide real-time public transport information.</description>
|
||||
|
||||
</binding:binding>
|
@ -0,0 +1,33 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<thing:thing-descriptions bindingId="publictransportswitzerland"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xmlns:thing="https://openhab.org/schemas/thing-description/v1.0.0"
|
||||
xsi:schemaLocation="https://openhab.org/schemas/thing-description/v1.0.0 https://openhab.org/schemas/thing-description-1.0.0.xsd">
|
||||
|
||||
<thing-type id="stationboard">
|
||||
<label>Stationboard</label>
|
||||
<description>Upcoming departures for a single station.</description>
|
||||
<channels>
|
||||
<channel typeId="tsv" id="tsv"/>
|
||||
</channels>
|
||||
<config-description>
|
||||
<parameter name="station" type="text" required="true">
|
||||
<label>Station</label>
|
||||
<description>The name of the station</description>
|
||||
</parameter>
|
||||
</config-description>
|
||||
</thing-type>
|
||||
|
||||
<channel-type id="tsv" advanced="true">
|
||||
<item-type>String</item-type>
|
||||
<label>Tab Separated Time Table</label>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="departure">
|
||||
<item-type>String</item-type>
|
||||
<label>Departure</label>
|
||||
<description>A single departure</description>
|
||||
<state readOnly="true" pattern="%s"/>
|
||||
</channel-type>
|
||||
|
||||
</thing:thing-descriptions>
|
@ -277,6 +277,7 @@
|
||||
<module>org.openhab.binding.plugwiseha</module>
|
||||
<module>org.openhab.binding.powermax</module>
|
||||
<module>org.openhab.binding.proteusecometer</module>
|
||||
<module>org.openhab.binding.publictransportswitzerland</module>
|
||||
<module>org.openhab.binding.pulseaudio</module>
|
||||
<module>org.openhab.binding.pushbullet</module>
|
||||
<module>org.openhab.binding.pushover</module>
|
||||
|
Loading…
Reference in New Issue
Block a user