diff --git a/bundles/org.openhab.binding.deutschebahn/README.md b/bundles/org.openhab.binding.deutschebahn/README.md
index 4d3141700cd86..23184d523fea2 100644
--- a/bundles/org.openhab.binding.deutschebahn/README.md
+++ b/bundles/org.openhab.binding.deutschebahn/README.md
@@ -1,7 +1,7 @@
-# DeutscheBahn Binding
+# Deutsche Bahn Binding
-The DeutscheBahn Binding provides the latest timetable information for all trains that arrive or depart at a specific train station, including live information for delays and changes in timetable.
-The information are requested from the timetable api of DeutscheBahn developer portal, so you'll need a (free) developer account to use this binding.
+The Deutsche Bahn Binding provides the latest timetable information for all trains that arrive or depart at a specific train station, including live information for delays and changes in timetable.
+The information are requested from the timetable api of Deutsche Bahn developer portal, so you'll need a (free) developer account to use this binding.
## Supported Things
@@ -12,9 +12,9 @@ The information are requested from the timetable api of DeutscheBahn developer p
### Generate Access-Key for timetable API
-To configure a timetable you first need to register at DeutscheBahn developer portal and register for timetable API to get an access key.
+To configure a timetable you first need to register at Deutsche Bahn developer portal and register for timetable API to get an access key.
-1. Go to [DeutscheBahn Developer](https://developer.deutschebahn.com)
+1. Go to [Deutsche Bahn Developer](https://developer.deutschebahn.com)
2. Register new account or login with an existing one
3. If no application is configured yet (check Tab "Meine Anwendungen") create a new application. Only the name is required, any other fields can be left blank.
4. Go to APIs - Timetables v1 (may be displayed on second page)
@@ -37,7 +37,7 @@ In addition you can configure if only arrivals, only departures or all trains sh
| Property | Default | Required | Description |
|-|-|-|-|
-| `accessToken` | | Yes | The access token for the timetable api within the developer portal of DeutscheBahn. |
+| `accessToken` | | Yes | The access token for the timetable api within the developer portal of Deutsche Bahn. |
| `evaNo` | | Yes | The eva nr. of the train station for which the timetable will be requested.|
| `trainFilter` | | Yes | Selects the trains that will be displayed in the timetable. Either only arrivals, only departures or all trains can be displayed. |
diff --git a/bundles/org.openhab.binding.deutschebahn/pom.xml b/bundles/org.openhab.binding.deutschebahn/pom.xml
index de1b8d95d8082..48ddb0006be01 100644
--- a/bundles/org.openhab.binding.deutschebahn/pom.xml
+++ b/bundles/org.openhab.binding.deutschebahn/pom.xml
@@ -12,7 +12,7 @@
org.openhab.binding.deutschebahn
- openHAB Add-ons :: Bundles :: DeutscheBahn Binding
+ openHAB Add-ons :: Bundles :: Deutsche Bahn Binding
diff --git a/bundles/org.openhab.binding.deutschebahn/src/main/java/org/openhab/binding/deutschebahn/internal/DeutscheBahnTimetableHandlerFactory.java b/bundles/org.openhab.binding.deutschebahn/src/main/java/org/openhab/binding/deutschebahn/internal/DeutscheBahnHandlerFactory.java
similarity index 91%
rename from bundles/org.openhab.binding.deutschebahn/src/main/java/org/openhab/binding/deutschebahn/internal/DeutscheBahnTimetableHandlerFactory.java
rename to bundles/org.openhab.binding.deutschebahn/src/main/java/org/openhab/binding/deutschebahn/internal/DeutscheBahnHandlerFactory.java
index 5434a2a732d89..059f6b4dc53df 100644
--- a/bundles/org.openhab.binding.deutschebahn/src/main/java/org/openhab/binding/deutschebahn/internal/DeutscheBahnTimetableHandlerFactory.java
+++ b/bundles/org.openhab.binding.deutschebahn/src/main/java/org/openhab/binding/deutschebahn/internal/DeutscheBahnHandlerFactory.java
@@ -30,13 +30,13 @@
import org.osgi.service.component.annotations.Component;
/**
- * The {@link DeutscheBahnTimetableHandlerFactory} is responsible for creating things and thing handlers.
+ * The {@link DeutscheBahnHandlerFactory} is responsible for creating things and thing handlers.
*
* @author Sönke Küper - Initial contribution
*/
@NonNullByDefault
@Component(configurationPid = "binding.deutschebahn", service = ThingHandlerFactory.class)
-public class DeutscheBahnTimetableHandlerFactory extends BaseThingHandlerFactory {
+public class DeutscheBahnHandlerFactory extends BaseThingHandlerFactory {
private static final Set SUPPORTED_THING_TYPES_UIDS = Set.of(TIMETABLE_TYPE, TRAIN_TYPE);
diff --git a/bundles/org.openhab.binding.deutschebahn/src/main/java/org/openhab/binding/deutschebahn/internal/DeutscheBahnTimetableHandler.java b/bundles/org.openhab.binding.deutschebahn/src/main/java/org/openhab/binding/deutschebahn/internal/DeutscheBahnTimetableHandler.java
index e097bf9d8ec19..616493a999157 100644
--- a/bundles/org.openhab.binding.deutschebahn/src/main/java/org/openhab/binding/deutschebahn/internal/DeutscheBahnTimetableHandler.java
+++ b/bundles/org.openhab.binding.deutschebahn/src/main/java/org/openhab/binding/deutschebahn/internal/DeutscheBahnTimetableHandler.java
@@ -36,6 +36,7 @@
import org.openhab.binding.deutschebahn.internal.timetable.dto.TimetableStop;
import org.openhab.core.io.net.http.HttpUtil;
import org.openhab.core.thing.Bridge;
+import org.openhab.core.thing.Channel;
import org.openhab.core.thing.ChannelUID;
import org.openhab.core.thing.Thing;
import org.openhab.core.thing.ThingStatus;
@@ -44,6 +45,7 @@
import org.openhab.core.thing.binding.BaseBridgeHandler;
import org.openhab.core.thing.binding.ThingHandler;
import org.openhab.core.types.Command;
+import org.openhab.core.types.UnDefType;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.xml.sax.SAXException;
@@ -100,7 +102,6 @@ public int getMaxPosition() {
private @Nullable ScheduledFuture> updateJob;
private final Logger logger = LoggerFactory.getLogger(DeutscheBahnTimetableHandler.class);
- private @Nullable DeutscheBahnTimetableConfiguration config;
private @Nullable TimetableLoader loader;
private TimetablesV1ApiFactory timetablesV1ApiFactory;
@@ -112,7 +113,7 @@ public int getMaxPosition() {
*/
public DeutscheBahnTimetableHandler( //
final Bridge bridge, //
- TimetablesV1ApiFactory timetablesV1ApiFactory, //
+ final TimetablesV1ApiFactory timetablesV1ApiFactory, //
final Supplier currentTimeProvider) {
super(bridge);
this.timetablesV1ApiFactory = timetablesV1ApiFactory;
@@ -120,14 +121,14 @@ public DeutscheBahnTimetableHandler( //
}
private List loadTimetable() {
- if (this.loader == null) {
- this.updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR);
+ final TimetableLoader currentLoader = this.loader;
+ if (currentLoader == null) {
+ this.updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.HANDLER_INITIALIZING_ERROR);
return Collections.emptyList();
}
try {
- @SuppressWarnings("null")
- final List stops = this.loader.getTimetableStops();
+ final List stops = currentLoader.getTimetableStops();
this.updateStatus(ThingStatus.ONLINE);
return stops;
} catch (final IOException e) {
@@ -143,16 +144,14 @@ private List loadTimetable() {
public void handleCommand(final ChannelUID channelUID, final Command command) {
}
- @SuppressWarnings("null")
@Override
public void initialize() {
- this.config = this.getConfigAs(DeutscheBahnTimetableConfiguration.class);
+ final DeutscheBahnTimetableConfiguration config = this.getConfigAs(DeutscheBahnTimetableConfiguration.class);
try {
- final TimetablesV1Api api = this.timetablesV1ApiFactory.create(this.config.accessToken,
- HttpUtil::executeUrl);
+ final TimetablesV1Api api = this.timetablesV1ApiFactory.create(config.accessToken, HttpUtil::executeUrl);
- final TimetableStopFilter stopFilter = this.config.getTimetableStopFilter();
+ final TimetableStopFilter stopFilter = config.getTimetableStopFilter();
final EventType eventSelection = stopFilter == TimetableStopFilter.ARRIVALS ? EventType.ARRIVAL
: EventType.ARRIVAL;
@@ -162,7 +161,7 @@ public void initialize() {
stopFilter, //
eventSelection, //
currentTimeProvider, //
- this.config.evaNo, //
+ config.evaNo, //
1); // will be updated on first call
this.updateStatus(ThingStatus.UNKNOWN);
@@ -173,7 +172,7 @@ public void initialize() {
});
} catch (JAXBException | SAXException | URISyntaxException e) {
this.logger.error("Error initializing api", e);
- this.updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR);
+ this.updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, e.getMessage());
}
}
@@ -221,16 +220,17 @@ private void stopUpdateJob() {
}
}
- @SuppressWarnings("null")
private void updateChannels() {
- if (this.loader == null) {
- this.updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR);
+ final TimetableLoader currentLoader = this.loader;
+ if (currentLoader == null) {
+ this.updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.HANDLER_INITIALIZING_ERROR);
return;
}
final GroupedThings groupedThings = this.groupThingsPerPosition();
- this.loader.setStopCount(groupedThings.getMaxPosition());
+ currentLoader.setStopCount(groupedThings.getMaxPosition());
final List timetableStops = this.loadTimetable();
if (timetableStops.isEmpty()) {
+ updateThingsToUndefined(groupedThings);
return;
}
@@ -238,6 +238,23 @@ private void updateChannels() {
this.updateThings(groupedThings, timetableStops);
}
+ /**
+ * No data was retrieved, so update all channel values to undefined.
+ */
+ private void updateThingsToUndefined(GroupedThings groupedThings) {
+ for (List things : groupedThings.thingsPerPosition.values()) {
+ for (Thing thing : things) {
+ updateChannelsToUndefined(thing);
+ }
+ }
+ }
+
+ private void updateChannelsToUndefined(Thing thing) {
+ for (Channel channel : thing.getChannels()) {
+ this.updateState(channel.getUID(), UnDefType.UNDEF);
+ }
+ }
+
private void updateThings(GroupedThings groupedThings, final List timetableStops) {
int position = 1;
for (final TimetableStop stop : timetableStops) {
@@ -254,6 +271,17 @@ private void updateThings(GroupedThings groupedThings, final List
}
position++;
}
+
+ // Update all things to undefined, for which no data was received.
+ while (position <= groupedThings.getMaxPosition()) {
+ final List thingsAtPosition = groupedThings.getThingsAtPosition(position);
+ if (thingsAtPosition != null) {
+ for (Thing thing : thingsAtPosition) {
+ updateChannelsToUndefined(thing);
+ }
+ }
+ position++;
+ }
}
/**
diff --git a/bundles/org.openhab.binding.deutschebahn/src/main/java/org/openhab/binding/deutschebahn/internal/DeutscheBahnTrainHandler.java b/bundles/org.openhab.binding.deutschebahn/src/main/java/org/openhab/binding/deutschebahn/internal/DeutscheBahnTrainHandler.java
index 4a0a7001080e2..0652c8fd33eb9 100644
--- a/bundles/org.openhab.binding.deutschebahn/src/main/java/org/openhab/binding/deutschebahn/internal/DeutscheBahnTrainHandler.java
+++ b/bundles/org.openhab.binding.deutschebahn/src/main/java/org/openhab/binding/deutschebahn/internal/DeutscheBahnTrainHandler.java
@@ -102,7 +102,7 @@ public void initialize() {
this.updateStatus(ThingStatus.UNKNOWN);
if (this.getBridge() == null) {
- this.updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR);
+ this.updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, "Please select bridge");
return;
}
diff --git a/bundles/org.openhab.binding.deutschebahn/src/main/resources/OH-INF/binding/binding.xml b/bundles/org.openhab.binding.deutschebahn/src/main/resources/OH-INF/binding/binding.xml
index bca9006df6025..7deb3797ee87c 100644
--- a/bundles/org.openhab.binding.deutschebahn/src/main/resources/OH-INF/binding/binding.xml
+++ b/bundles/org.openhab.binding.deutschebahn/src/main/resources/OH-INF/binding/binding.xml
@@ -3,7 +3,7 @@
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">
- DeutscheBahn Binding
+ Deutsche Bahn BindingThis binding provides timetable information for train stations of Deutsche Bahn.
diff --git a/bundles/org.openhab.binding.deutschebahn/src/main/resources/OH-INF/thing/thing-types.xml b/bundles/org.openhab.binding.deutschebahn/src/main/resources/OH-INF/thing/thing-types.xml
index 37039ba73313e..d85a7c028ebac 100644
--- a/bundles/org.openhab.binding.deutschebahn/src/main/resources/OH-INF/thing/thing-types.xml
+++ b/bundles/org.openhab.binding.deutschebahn/src/main/resources/OH-INF/thing/thing-types.xml
@@ -6,24 +6,24 @@
-
- Connection to the timetable API of DeutscheBahn. Provides timetable data that can be displayed using the
+
+ Connection to the timetable API of Deutsche Bahn. Provides timetable data that can be displayed using the
train things.
- Access Token from DeutscheBahn developer portal for accessing the webservice api.
+ Access Token from Deutsche Bahn developer portal for accessing the webservice api.
-
+
evaNo of the station, for which the timetable should be requested. see
https://data.deutschebahn.com/dataset.tags.EVA-Nr..htmltruedepartures
-
+
Selects the trains that will be be displayed in this timetable. If not set only departures will be
provided.
@@ -127,14 +127,14 @@
String
-
+
Provides the filter flags.String
-
+
Provides the type of the trip.
@@ -265,8 +265,9 @@
Switch
- On if the event should not be shown on WBT because travellers are not supposed to enter or exit the train
- at this stop.
+ On if the event should not be shown, because travellers are not supposed to enter or exit the train
+ at
+ this stop.
@@ -287,21 +288,21 @@
String
-
+
Planned distant endpoint.String
-
+
Changed distant endpoint.Number
-
+
distant change
@@ -309,14 +310,14 @@
String
-
+
Planned final station of the train. For arrivals the starting station is returned, for departures the
target station is returned.String
-
+
Returns the planned stations this train came from (for arrivals) or the stations this train will go to
(for departures). Stations will be separated by single dash.
@@ -324,7 +325,7 @@
String
-
+
Changed final station of the train. For arrivals the starting station is returned, for departures the
target station is returned.
@@ -332,7 +333,7 @@
String
-
+
Returns the changed stations this train came from (for arrivals) or the stations this train will go to
(for departures). Stations will be separated by single dash.
diff --git a/bundles/org.openhab.binding.deutschebahn/src/test/java/org/openhab/binding/deutschebahn/internal/DeutscheBahnTimetableHandlerTest.java b/bundles/org.openhab.binding.deutschebahn/src/test/java/org/openhab/binding/deutschebahn/internal/DeutscheBahnTimetableHandlerTest.java
index f311d4f69fc84..5209ad9d283e9 100644
--- a/bundles/org.openhab.binding.deutschebahn/src/test/java/org/openhab/binding/deutschebahn/internal/DeutscheBahnTimetableHandlerTest.java
+++ b/bundles/org.openhab.binding.deutschebahn/src/test/java/org/openhab/binding/deutschebahn/internal/DeutscheBahnTimetableHandlerTest.java
@@ -23,14 +23,22 @@
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.junit.jupiter.api.Test;
import org.openhab.binding.deutschebahn.internal.timetable.TimeproviderStub;
+import org.openhab.binding.deutschebahn.internal.timetable.TimetablesV1ApiFactory;
+import org.openhab.binding.deutschebahn.internal.timetable.TimetablesV1ApiStub;
+import org.openhab.binding.deutschebahn.internal.timetable.TimetablesV1Impl.HttpCallable;
import org.openhab.binding.deutschebahn.internal.timetable.TimetablesV1ImplTestHelper;
+import org.openhab.binding.deutschebahn.internal.timetable.dto.Event;
+import org.openhab.binding.deutschebahn.internal.timetable.dto.Timetable;
import org.openhab.binding.deutschebahn.internal.timetable.dto.TimetableStop;
import org.openhab.core.config.core.Configuration;
import org.openhab.core.thing.Bridge;
+import org.openhab.core.thing.Channel;
import org.openhab.core.thing.Thing;
import org.openhab.core.thing.ThingStatus;
import org.openhab.core.thing.ThingUID;
import org.openhab.core.thing.binding.ThingHandlerCallback;
+import org.openhab.core.types.State;
+import org.openhab.core.types.UnDefType;
/**
* Tests for {@link DeutscheBahnTimetableHandler}.
@@ -68,11 +76,17 @@ private static Bridge mockBridge() {
private DeutscheBahnTimetableHandler createAndInitHandler(final ThingHandlerCallback callback, final Bridge bridge)
throws Exception {
+ return createAndInitHandler(callback, bridge, createApiWithTestdata().getApiFactory());
+ }
+
+ private DeutscheBahnTimetableHandler createAndInitHandler( //
+ final ThingHandlerCallback callback, //
+ final Bridge bridge, //
+ final TimetablesV1ApiFactory apiFactory) throws Exception { //
final TimeproviderStub timeProvider = new TimeproviderStub();
timeProvider.time = new GregorianCalendar(2021, Calendar.AUGUST, 16, 9, 30);
- final DeutscheBahnTimetableHandler handler = new DeutscheBahnTimetableHandler(bridge,
- createApiWithTestdata().getApiFactory(), timeProvider);
+ final DeutscheBahnTimetableHandler handler = new DeutscheBahnTimetableHandler(bridge, apiFactory, timeProvider);
handler.setCallback(callback);
handler.initialize();
return handler;
@@ -104,4 +118,70 @@ private void verifyThingUpdated(final Bridge bridge, int offset, String stopId)
verify(childHandler, timeout(1000))
.updateChannels(argThat((TimetableStop stop) -> stop.getId().equals(stopId)));
}
+
+ @Test
+ public void testUpdateTrainsToUndefinedIfNoDataWasProvided() throws Exception {
+ final Bridge bridge = mockBridge();
+ final ThingHandlerCallback callback = mock(ThingHandlerCallback.class);
+
+ final TimetablesV1ApiStub stubWithError = TimetablesV1ApiStub.createWithException();
+
+ final DeutscheBahnTimetableHandler handler = createAndInitHandler(callback, bridge,
+ (String authToken, HttpCallable httpCallable) -> stubWithError);
+
+ try {
+ verify(callback).statusUpdated(eq(bridge), argThat(arg -> arg.getStatus().equals(ThingStatus.UNKNOWN)));
+ verify(callback, timeout(1000)).statusUpdated(eq(bridge),
+ argThat(arg -> arg.getStatus().equals(ThingStatus.OFFLINE)));
+
+ verifyChannelsUpdatedToUndef(bridge, 0, callback, UnDefType.UNDEF);
+ verifyChannelsUpdatedToUndef(bridge, 1, callback, UnDefType.UNDEF);
+ verifyChannelsUpdatedToUndef(bridge, 2, callback, UnDefType.UNDEF);
+
+ } finally {
+ handler.dispose();
+ }
+ }
+
+ private static void verifyChannelsUpdatedToUndef(Bridge bridge, int offset, ThingHandlerCallback callback,
+ State expectedState) {
+ final Thing thing = bridge.getThings().get(offset);
+ for (Channel channel : thing.getChannels()) {
+ verify(callback).stateUpdated(eq(channel.getUID()), eq(expectedState));
+ }
+ }
+
+ @Test
+ public void testUpdateTrainsToUndefinedIfNotEnoughDataWasProvided() throws Exception {
+ final Bridge bridge = mockBridge();
+ final ThingHandlerCallback callback = mock(ThingHandlerCallback.class);
+
+ // Bridge contains 3 trains, but Timetable contains only 1 items, so two trains has to be updated to undef
+ // value.
+ final Timetable timetable = new Timetable();
+ TimetableStop stop01 = new TimetableStop();
+ stop01.setId("stop01id");
+ Event dp = new Event();
+ dp.setPt("2108161000");
+ stop01.setDp(dp);
+ timetable.getS().add(stop01);
+
+ final TimetablesV1ApiStub stubWithData = TimetablesV1ApiStub.createWithResult(timetable);
+
+ final DeutscheBahnTimetableHandler handler = createAndInitHandler(callback, bridge,
+ (String authToken, HttpCallable httpCallable) -> stubWithData);
+
+ try {
+ verify(callback).statusUpdated(eq(bridge), argThat(arg -> arg.getStatus().equals(ThingStatus.UNKNOWN)));
+ verify(callback, timeout(1000)).statusUpdated(eq(bridge),
+ argThat(arg -> arg.getStatus().equals(ThingStatus.ONLINE)));
+
+ verifyThingUpdated(bridge, 0, stop01.getId());
+ verifyChannelsUpdatedToUndef(bridge, 1, callback, UnDefType.UNDEF);
+ verifyChannelsUpdatedToUndef(bridge, 2, callback, UnDefType.UNDEF);
+
+ } finally {
+ handler.dispose();
+ }
+ }
}
diff --git a/bundles/org.openhab.binding.deutschebahn/src/test/java/org/openhab/binding/deutschebahn/internal/DeutscheBahnTrainHandlerTest.java b/bundles/org.openhab.binding.deutschebahn/src/test/java/org/openhab/binding/deutschebahn/internal/DeutscheBahnTrainHandlerTest.java
index b619d12b17325..627e53d3f5f3d 100644
--- a/bundles/org.openhab.binding.deutschebahn/src/test/java/org/openhab/binding/deutschebahn/internal/DeutscheBahnTrainHandlerTest.java
+++ b/bundles/org.openhab.binding.deutschebahn/src/test/java/org/openhab/binding/deutschebahn/internal/DeutscheBahnTrainHandlerTest.java
@@ -79,6 +79,11 @@ static Thing mockThing(int id) {
.thenReturn(Arrays.asList(arrivalPlannedTime, arrivalLine, arrivalChangedTime));
when(thing.getChannelsOfGroup("departure"))
.thenReturn(Arrays.asList(departurePlannedTime, departurePlannedPlatform, departureTargetStation));
+ when(thing.getChannels()).thenReturn(Arrays.asList( //
+ tripLabelCategory, //
+ arrivalPlannedTime, arrivalLine, arrivalChangedTime, //
+ departurePlannedTime, departurePlannedPlatform, departureTargetStation));
+
return thing;
}
diff --git a/bundles/org.openhab.binding.deutschebahn/src/test/java/org/openhab/binding/deutschebahn/internal/timetable/TimetablesV1ApiStub.java b/bundles/org.openhab.binding.deutschebahn/src/test/java/org/openhab/binding/deutschebahn/internal/timetable/TimetablesV1ApiStub.java
new file mode 100644
index 0000000000000..3b14cdfdc5824
--- /dev/null
+++ b/bundles/org.openhab.binding.deutschebahn/src/test/java/org/openhab/binding/deutschebahn/internal/timetable/TimetablesV1ApiStub.java
@@ -0,0 +1,71 @@
+/**
+ * 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.deutschebahn.internal.timetable;
+
+import java.io.IOException;
+import java.util.Date;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.eclipse.jdt.annotation.Nullable;
+import org.openhab.binding.deutschebahn.internal.timetable.dto.Timetable;
+
+/**
+ * Stub Implementation of {@link TimetablesV1Api}, that may return an preconfigured Timetable or
+ * throws an {@link IOException} if not data has been set.
+ *
+ * @author Sönke Küper - initial contribution
+ */
+@NonNullByDefault
+public final class TimetablesV1ApiStub implements TimetablesV1Api {
+
+ @Nullable
+ private final Timetable result;
+
+ private TimetablesV1ApiStub(@Nullable Timetable result) {
+ this.result = result;
+ }
+
+ /**
+ * Creates an new {@link TimetablesV1ApiStub}, that returns the given result.
+ */
+ public static TimetablesV1ApiStub createWithResult(Timetable timetable) {
+ return new TimetablesV1ApiStub(timetable);
+ }
+
+ /**
+ * Creates an new {@link TimetablesV1ApiStub} that throws an Exception.
+ */
+ public static TimetablesV1ApiStub createWithException() {
+ return new TimetablesV1ApiStub(null);
+ }
+
+ @Override
+ public Timetable getPlan(String evaNo, Date time) throws IOException {
+ final Timetable currentResult = this.result;
+ if (currentResult == null) {
+ throw new IOException("No timetable data is available");
+ } else {
+ return currentResult;
+ }
+ }
+
+ @Override
+ public Timetable getFullChanges(String evaNo) throws IOException {
+ return new Timetable();
+ }
+
+ @Override
+ public Timetable getRecentChanges(String evaNo) throws IOException {
+ return new Timetable();
+ }
+}
diff --git a/bundles/org.openhab.binding.deutschebahn/src/test/resources/timetablesData/plan/8000152/211014/11.xml b/bundles/org.openhab.binding.deutschebahn/src/test/resources/timetablesData/plan/8000152/211014/11.xml
index 2be9ae5017514..52bb29f7e56dd 100644
--- a/bundles/org.openhab.binding.deutschebahn/src/test/resources/timetablesData/plan/8000152/211014/11.xml
+++ b/bundles/org.openhab.binding.deutschebahn/src/test/resources/timetablesData/plan/8000152/211014/11.xml
@@ -1 +1,282 @@
-
\ No newline at end of file
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file