diff --git a/CODEOWNERS b/CODEOWNERS
index c09e2b9f1..a3f827da5 100644
--- a/CODEOWNERS
+++ b/CODEOWNERS
@@ -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
diff --git a/bom/openhab-addons/pom.xml b/bom/openhab-addons/pom.xml
index 8e2a9ade6..2a71e5b30 100644
--- a/bom/openhab-addons/pom.xml
+++ b/bom/openhab-addons/pom.xml
@@ -1216,6 +1216,11 @@
org.openhab.binding.proteusecometer
${project.version}
+
+ org.openhab.addons.bundles
+ org.openhab.binding.publictransportswitzerland
+ ${project.version}
+
org.openhab.addons.bundles
org.openhab.binding.pulseaudio
diff --git a/bundles/org.openhab.binding.publictransportswitzerland/NOTICE b/bundles/org.openhab.binding.publictransportswitzerland/NOTICE
new file mode 100644
index 000000000..38d625e34
--- /dev/null
+++ b/bundles/org.openhab.binding.publictransportswitzerland/NOTICE
@@ -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
diff --git a/bundles/org.openhab.binding.publictransportswitzerland/README.md b/bundles/org.openhab.binding.publictransportswitzerland/README.md
new file mode 100644
index 000000000..e8b601cf4
--- /dev/null
+++ b/bundles/org.openhab.binding.publictransportswitzerland/README.md
@@ -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:
`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.
diff --git a/bundles/org.openhab.binding.publictransportswitzerland/doc/departure_board_habpanel.png b/bundles/org.openhab.binding.publictransportswitzerland/doc/departure_board_habpanel.png
new file mode 100644
index 000000000..233a3b9bd
Binary files /dev/null and b/bundles/org.openhab.binding.publictransportswitzerland/doc/departure_board_habpanel.png differ
diff --git a/bundles/org.openhab.binding.publictransportswitzerland/pom.xml b/bundles/org.openhab.binding.publictransportswitzerland/pom.xml
new file mode 100644
index 000000000..7392a0d35
--- /dev/null
+++ b/bundles/org.openhab.binding.publictransportswitzerland/pom.xml
@@ -0,0 +1,17 @@
+
+
+
+ 4.0.0
+
+
+ org.openhab.addons.bundles
+ org.openhab.addons.reactor.bundles
+ 3.3.0-SNAPSHOT
+
+
+ org.openhab.binding.publictransportswitzerland
+
+ openHAB Add-ons :: Bundles :: PublicTransportSwitzerland Binding
+
+
diff --git a/bundles/org.openhab.binding.publictransportswitzerland/src/main/feature/feature.xml b/bundles/org.openhab.binding.publictransportswitzerland/src/main/feature/feature.xml
new file mode 100644
index 000000000..38b894ebc
--- /dev/null
+++ b/bundles/org.openhab.binding.publictransportswitzerland/src/main/feature/feature.xml
@@ -0,0 +1,9 @@
+
+
+ mvn:org.openhab.core.features.karaf/org.openhab.core.features.karaf.openhab-core/${ohc.version}/xml/features
+
+
+ openhab-runtime-base
+ mvn:org.openhab.addons.bundles/org.openhab.binding.publictransportswitzerland/${project.version}
+
+
diff --git a/bundles/org.openhab.binding.publictransportswitzerland/src/main/java/org/openhab/binding/publictransportswitzerland/internal/PublicTransportSwitzerlandBindingConstants.java b/bundles/org.openhab.binding.publictransportswitzerland/src/main/java/org/openhab/binding/publictransportswitzerland/internal/PublicTransportSwitzerlandBindingConstants.java
new file mode 100644
index 000000000..a66660465
--- /dev/null
+++ b/bundles/org.openhab.binding.publictransportswitzerland/src/main/java/org/openhab/binding/publictransportswitzerland/internal/PublicTransportSwitzerlandBindingConstants.java
@@ -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/";
+}
diff --git a/bundles/org.openhab.binding.publictransportswitzerland/src/main/java/org/openhab/binding/publictransportswitzerland/internal/PublicTransportSwitzerlandHandlerFactory.java b/bundles/org.openhab.binding.publictransportswitzerland/src/main/java/org/openhab/binding/publictransportswitzerland/internal/PublicTransportSwitzerlandHandlerFactory.java
new file mode 100644
index 000000000..b6978827e
--- /dev/null
+++ b/bundles/org.openhab.binding.publictransportswitzerland/src/main/java/org/openhab/binding/publictransportswitzerland/internal/PublicTransportSwitzerlandHandlerFactory.java
@@ -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 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;
+ }
+}
diff --git a/bundles/org.openhab.binding.publictransportswitzerland/src/main/java/org/openhab/binding/publictransportswitzerland/internal/stationboard/PublicTransportSwitzerlandStationboardConfiguration.java b/bundles/org.openhab.binding.publictransportswitzerland/src/main/java/org/openhab/binding/publictransportswitzerland/internal/stationboard/PublicTransportSwitzerlandStationboardConfiguration.java
new file mode 100644
index 000000000..565c0f694
--- /dev/null
+++ b/bundles/org.openhab.binding.publictransportswitzerland/src/main/java/org/openhab/binding/publictransportswitzerland/internal/stationboard/PublicTransportSwitzerlandStationboardConfiguration.java
@@ -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;
+}
diff --git a/bundles/org.openhab.binding.publictransportswitzerland/src/main/java/org/openhab/binding/publictransportswitzerland/internal/stationboard/PublicTransportSwitzerlandStationboardHandler.java b/bundles/org.openhab.binding.publictransportswitzerland/src/main/java/org/openhab/binding/publictransportswitzerland/internal/stationboard/PublicTransportSwitzerlandStationboardHandler.java
new file mode 100644
index 000000000..37c95d42f
--- /dev/null
+++ b/bundles/org.openhab.binding.publictransportswitzerland/src/main/java/org/openhab/binding/publictransportswitzerland/internal/stationboard/PublicTransportSwitzerlandStationboardHandler.java
@@ -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 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 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 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));
+ }
+}
diff --git a/bundles/org.openhab.binding.publictransportswitzerland/src/main/resources/OH-INF/binding/binding.xml b/bundles/org.openhab.binding.publictransportswitzerland/src/main/resources/OH-INF/binding/binding.xml
new file mode 100644
index 000000000..ab321cce0
--- /dev/null
+++ b/bundles/org.openhab.binding.publictransportswitzerland/src/main/resources/OH-INF/binding/binding.xml
@@ -0,0 +1,9 @@
+
+
+
+ Public Transport Switzerland Binding
+ Connects to the "Swiss public transport API" to provide real-time public transport information.
+
+
diff --git a/bundles/org.openhab.binding.publictransportswitzerland/src/main/resources/OH-INF/thing/stationboard.xml b/bundles/org.openhab.binding.publictransportswitzerland/src/main/resources/OH-INF/thing/stationboard.xml
new file mode 100644
index 000000000..d2b417d3a
--- /dev/null
+++ b/bundles/org.openhab.binding.publictransportswitzerland/src/main/resources/OH-INF/thing/stationboard.xml
@@ -0,0 +1,33 @@
+
+
+
+
+
+ Upcoming departures for a single station.
+
+
+
+
+
+
+ The name of the station
+
+
+
+
+
+ String
+
+
+
+
+ String
+
+ A single departure
+
+
+
+
diff --git a/bundles/pom.xml b/bundles/pom.xml
index 62cc68f53..a633b89ee 100644
--- a/bundles/pom.xml
+++ b/bundles/pom.xml
@@ -277,6 +277,7 @@
org.openhab.binding.plugwiseha
org.openhab.binding.powermax
org.openhab.binding.proteusecometer
+ org.openhab.binding.publictransportswitzerland
org.openhab.binding.pulseaudio
org.openhab.binding.pushbullet
org.openhab.binding.pushover