diff --git a/CODEOWNERS b/CODEOWNERS
index a9bb24764bb51..1913885694c0e 100644
--- a/CODEOWNERS
+++ b/CODEOWNERS
@@ -326,6 +326,7 @@
/bundles/org.openhab.binding.windcentrale/ @marcelrv
/bundles/org.openhab.binding.wlanthermo/ @CSchlipp
/bundles/org.openhab.binding.wled/ @Skinah
+/bundles/org.openhab.binding.wolfsmartset/ @BoBiene
/bundles/org.openhab.binding.xmltv/ @clinique
/bundles/org.openhab.binding.xmppclient/ @pavel-gololobov
/bundles/org.openhab.binding.yamahareceiver/ @davidgraeff @zarusz
diff --git a/bundles/org.openhab.binding.wolfsmartset/NOTICE b/bundles/org.openhab.binding.wolfsmartset/NOTICE
new file mode 100644
index 0000000000000..38d625e349232
--- /dev/null
+++ b/bundles/org.openhab.binding.wolfsmartset/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.wolfsmartset/README.md b/bundles/org.openhab.binding.wolfsmartset/README.md
new file mode 100644
index 0000000000000..ffd0a43d2645c
--- /dev/null
+++ b/bundles/org.openhab.binding.wolfsmartset/README.md
@@ -0,0 +1,149 @@
+# Wolf Smartset Binding
+
+This binding communicates with the www.wolf-smartset.de API and provides values readonly.
+Wolf systems are connected with official gateways (Wolf Link Home or Wolf Link Pro) https://www.wolf.eu/produkte/smarthome/
+
+## Supported Things
+
+- Account (``thing-type:wolfsmartset:account``)
+ * holding the credentials to connect to the wolf-smartset online portal.
+- System (``thing-type:wolfsmartset:system``)
+ * represents one wolf system connected to the wolf-smartset online portal.
+- Unit (``thing-type:wolfsmartset:unit``)
+ * unit is a part of the system with values and parameter
+
+## Discovery
+
+- System things (bridge) are discovered after Account thing (bridge) is set up
+- Unit things are discovered after System things are set up
+
+## Thing Configuration
+
+### Account (bridge)
+
+The account thing holds the credentials to connect to the wolf-smartset online portal.
+
+| Parameter | Type | Defaut | Description |
+|-----------------|---------|----------|---------------------------------------------------------------------|
+| username | text | | username to authenticate to www.wolf-smartset.de |
+| password | text | | password to authenticate to www.wolf-smartset.de |
+| refreshIntervalStructure | integer | 10 | Specifies the refresh interval to refresh the Structure in minutes |
+| refreshIntervalValues | integer | 15 | Specifies time in seconds to refresh values |
+| discoveryEnabled | boolean | true | disable the Thing discovery |
+
+### System (bridge)
+
+The system thing represents one wolf system connected via a WOLF Link home or a WOLF Link pro to the wolf-smartset online portal.
+You have access to your own or to shared systems.
+
+| Parameter | Type | Defaut | Description |
+|-----------------|---------|----------|---------------------------------------------------------------------|
+| systemId | integer | | System ID assigned to the system by WolfSmartset |
+
+### Unit
+
+A system is divided into different units.
+In the wolf-smartset portal, the system has an "Expert" section, each submenu item within the "Expert" section has multiple tabs.
+Each of these tabs is treated as one unit.
+
+| Parameter | Type | Defaut | Description |
+|-----------------|---------|----------|---------------------------------------------------------------------|
+| unitId | integer | | The BundleId assigned to the unit by WolfSmartset |
+
+## Tested WOLF-Devices
+
+| WOLF Equipment | openhab Version | Used gateway |
+|-------------------|-----------------|---------------|
+| CSZ (CGB and SM1) | 3.1 | WOLF Link Pro |
+| CGB-2 | 3.1 | WOLF Link home|
+
+
+## Channels
+
+| channel | type | description |
+|----------|--------|------------------------------|
+| number | Number | a generic number |
+| contact | Contact | a generic contact |
+| temperature | Number:Temperature | a generic temperature |
+| string | String | a generic String |
+| datetime | DateTime | a generic DateTime |
+
+## Full Example
+
+### Things
+
+````
+Bridge wolfsmartset:account:account "Wolf Smartset Account" [ username="User", password="Password" ] {
+ Bridge system 32122305166 "WolfSmartset System CSZ" [ systemId="32122305166" ] {
+ Thing unitId uinit0 "CSZ Heizgerät" [ unitId="unit0" ] {
+ }
+ }
+}
+````
+_You need to use the corrosponding systemId and unitId returned by the discovery_
+
+### Items
+
+````
+"Number CSZHeizgerat_Raumtemperatur "Raumtemperatur" { channel="wolfsmartset:unit:account:32122305166:uinit0:1000900000"}
+Number CSZHeizgerat_Flamme "Flamme" { channel="wolfsmartset:unit:account:32122305166:uinit0:1000900001"}
+Number CSZHeizgerat_AnalogeFernbedienung "Analoge Fernbedienung" { channel="wolfsmartset:unit:account:32122305166:uinit0:1000900002"}
+Number CSZHeizgerat_Raumsolltemperatur "Raumsolltemperatur" { channel="wolfsmartset:unit:account:32122305166:uinit0:1000900003"}
+Number CSZHeizgerat_AusgangA1 "Ausgang A1" { channel="wolfsmartset:unit:account:32122305166:uinit0:1000900004"}
+String CSZHeizgerat_ZeitprogrammdirekterHeizkreis "Zeitprogramm direkter Heizkreis" { channel="wolfsmartset:unit:account:32122305166:uinit0:1000900005"}
+Number CSZHeizgerat_Ventil1 "Ventil 1" { channel="wolfsmartset:unit:account:32122305166:uinit0:1000900006"}
+Number CSZHeizgerat_Ventil2 "Ventil 2" { channel="wolfsmartset:unit:account:32122305166:uinit0:1000900007"}
+Number CSZHeizgerat_WiSoUmschaltung "Wi/So Umschaltung" { channel="wolfsmartset:unit:account:32122305166:uinit0:1000900008"}
+Number CSZHeizgerat_Tagtemperatur "Tagtemperatur" { channel="wolfsmartset:unit:account:32122305166:uinit0:1000900009"}
+Number CSZHeizgerat_PWMPumpe "PWM Pumpe" { channel="wolfsmartset:unit:account:32122305166:uinit0:10009000010"}
+Number CSZHeizgerat_Speichersolltemperatur "Speichersolltemperatur" { channel="wolfsmartset:unit:account:32122305166:uinit0:10009000011"}
+Number CSZHeizgerat_Heizkurve "Heizkurve" { channel="wolfsmartset:unit:account:32122305166:uinit0:10009000012"}
+Number CSZHeizgerat_Raumeinfluss "Raumeinfluss" { channel="wolfsmartset:unit:account:32122305166:uinit0:10009000013"}
+Number CSZHeizgerat_TWVorlauf "TW-Vorlauf" { channel="wolfsmartset:unit:account:32122305166:uinit0:10009000014"}
+Number CSZHeizgerat_Spartemperatur "Spartemperatur" { channel="wolfsmartset:unit:account:32122305166:uinit0:10009000015"}
+Number CSZHeizgerat_Geblase "Gebläse" { channel="wolfsmartset:unit:account:32122305166:uinit0:10009000016"}
+Number CSZHeizgerat_Vorlaufsolltemperatur "Vorlaufsolltemperatur" { channel="wolfsmartset:unit:account:32122305166:uinit0:10009000017"}
+Group CSZHeizgerat "CSZ Heizgerät" { channel="wolfsmartset:unit:account:32122305166:uinit0:10009000018"}
+Number CSZHeizgerat_ECOABS "ECO/ABS" { channel="wolfsmartset:unit:account:32122305166:uinit0:10009000019"}
+Number CSZHeizgerat_Netzbetriebstunden "Netzbetriebstunden" { channel="wolfsmartset:unit:account:32122305166:uinit0:10009000020"}
+Number CSZHeizgerat_TWAbgas "TW-Abgas" { channel="wolfsmartset:unit:account:32122305166:uinit0:10009000021"}
+Number CSZHeizgerat_HGStatus "HG Status" { channel="wolfsmartset:unit:account:32122305166:uinit0:10009000022"}
+Number CSZHeizgerat_EingangE1 "Eingang E1" { channel="wolfsmartset:unit:account:32122305166:uinit0:10009000023"}"
+````
+
+## Supported Heating-Devices
+
+All devices able to be connected to www.wolf-smartset.de
+
+### Related Documentation from WOLF
+
+https://www.wolf.eu/fileadmin/Wolf_Daten/Dokumente/FAQ/3065655_201711.pdf
+
+| Heating system | WOLF Link home | WOLF Link pro |
+|-------------------------------------------|-----------------------|--------------------|
+| Gas condensing boiler CGB-2, CGW-2, CGS-2 | ✅ | ✅ |
+| Oil condensing boiler TOB | ✅ | ✅ |
+| MGK-2 gas condensing boiler | ✅ | ✅ |
+| split air/water heat pump BWL-1S | ✅ | ✅ |
+| Oil condensing boiler COB | | ✅ |
+| gas condensing boiler MGK | | ✅ |
+| Gas condensing boilers CGB, CGW, CGS, FGB | | ✅ |
+| Gas condensing boilers CGG-2, CGU-2 | | ✅ |
+| Boiler controls R2, R3, R21 | | ✅ |
+| Monobloc heat pumps BWW-1, BWL-1, BWS-1 | | ✅ |
+| mixer module MM, MM-2 | ⬜ | ✅ |
+| cascade module KM, KM-2 | ⬜ | ✅ |
+| solar modules SM1, SM1-2, SM-2, SM2-2 | ⬜ | ✅ |
+| Comfort apartment ventilation CWL Excellent | ⬜ | ✅ |
+| Air handling units KG Top, CKL Pool``*`` | | ✅ |
+| Air handling units CKL, CFL, CRL``*`` | | ✅ |
+| Combined heat and power units | | ✅ |
+
+
+Note:
+
+⬜ possible in connection with a WOLF Link home compatible heater,
+full functionality only for devices with current software version.
+
+``*`` Modbus interface required in the device,
+Special programming cannot be mapped.
diff --git a/bundles/org.openhab.binding.wolfsmartset/pom.xml b/bundles/org.openhab.binding.wolfsmartset/pom.xml
new file mode 100644
index 0000000000000..bfaa738634bda
--- /dev/null
+++ b/bundles/org.openhab.binding.wolfsmartset/pom.xml
@@ -0,0 +1,17 @@
+
+
+
+ 4.0.0
+
+
+ org.openhab.addons.bundles
+ org.openhab.addons.reactor.bundles
+ 3.2.0-SNAPSHOT
+
+
+ org.openhab.binding.wolfsmartset
+
+ openHAB Add-ons :: Bundles :: WolfSmartset Binding
+
+
diff --git a/bundles/org.openhab.binding.wolfsmartset/src/main/feature/feature.xml b/bundles/org.openhab.binding.wolfsmartset/src/main/feature/feature.xml
new file mode 100644
index 0000000000000..ba34a81e8ab63
--- /dev/null
+++ b/bundles/org.openhab.binding.wolfsmartset/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.wolfsmartset/${project.version}
+
+
diff --git a/bundles/org.openhab.binding.wolfsmartset/src/main/java/org/openhab/binding/wolfsmartset/internal/WolfSmartsetBindingConstants.java b/bundles/org.openhab.binding.wolfsmartset/src/main/java/org/openhab/binding/wolfsmartset/internal/WolfSmartsetBindingConstants.java
new file mode 100644
index 0000000000000..61765b9c7acc9
--- /dev/null
+++ b/bundles/org.openhab.binding.wolfsmartset/src/main/java/org/openhab/binding/wolfsmartset/internal/WolfSmartsetBindingConstants.java
@@ -0,0 +1,91 @@
+/**
+ * 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.wolfsmartset.internal;
+
+import java.util.Collections;
+import java.util.List;
+import java.util.Set;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.openhab.binding.wolfsmartset.internal.dto.GetSystemListDTO;
+import org.openhab.binding.wolfsmartset.internal.dto.SubMenuEntryWithMenuItemTabView;
+import org.openhab.core.thing.ThingTypeUID;
+
+/**
+ * The {@link WolfSmartsetBindingConstants} class defines common constants that are
+ * used across the whole binding.
+ *
+ * @author Bo Biene - Initial contribution
+ */
+@NonNullByDefault
+public class WolfSmartsetBindingConstants {
+
+ public static final String BINDING_ID = "wolfsmartset";
+
+ // Account bridge
+ public static final String THING_TYPE_ACCOUNT = "account";
+ public static final ThingTypeUID UID_ACCOUNT_BRIDGE = new ThingTypeUID(BINDING_ID, THING_TYPE_ACCOUNT);
+ public static final Set SUPPORTED_ACCOUNT_BRIDGE_THING_TYPES_UIDS = Collections
+ .unmodifiableSet(Stream.of(UID_ACCOUNT_BRIDGE).collect(Collectors.toSet()));
+
+ // System bridge
+ public static final String THING_TYPE_SYSTEM = "system";
+ public static final ThingTypeUID UID_SYSTEM_BRIDGE = new ThingTypeUID(BINDING_ID, THING_TYPE_SYSTEM);
+ public static final Set SUPPORTED_SYSTEM_BRIDGE_THING_TYPES_UIDS = Collections
+ .unmodifiableSet(Stream.of(UID_SYSTEM_BRIDGE).collect(Collectors.toSet()));
+
+ // unit thing
+ public static final String THING_TYPE_UNIT = "unit";
+ public static final ThingTypeUID UID_UNIT_THING = new ThingTypeUID(BINDING_ID, THING_TYPE_UNIT);
+ public static final Set SUPPORTED_UNIT_THING_TYPES_UIDS = Collections
+ .unmodifiableSet(Stream.of(UID_UNIT_THING).collect(Collectors.toSet()));
+
+ // Collection of system and unit thing types
+ public static final Set SUPPORTED_SYSTEM_AND_UNIT_THING_TYPES_UIDS = Stream
+ .concat(SUPPORTED_SYSTEM_BRIDGE_THING_TYPES_UIDS.stream(), SUPPORTED_UNIT_THING_TYPES_UIDS.stream())
+ .collect(Collectors.toSet());
+
+ // Collection of all supported thing types
+ public static final Set SUPPORTED_THING_TYPES_UIDS = Collections.unmodifiableSet(
+ Stream.of(UID_ACCOUNT_BRIDGE, UID_SYSTEM_BRIDGE, UID_UNIT_THING).collect(Collectors.toSet()));
+
+ // System Properties
+ public static final String THING_PROPERTY_GATEWAY_ID = "GatewayId";
+ public static final String THING_PROPERTY_GATEWAY_USERNAME = "GatewayUsername";
+ public static final String THING_PROPERTY_INSTALLATION_DATE = "InstallationDate";
+ public static final String THING_PROPERTY_LOCATION = "Location";
+ public static final String THING_PROPERTY_OPERATOR_NAME = "OperatorName";
+ public static final String THING_PROPERTY_USERNAME_OWNER = "UserNameOwner";
+ public static final String THING_PROPERTY_ACCESSLEVEL = "AccessLevel";
+
+ public static final String CH_TEMPERATURE = "temperature";
+ public static final String CH_PRESSURE = "barometric-pressure";
+ public static final String CH_STRING = "string";
+ public static final String CH_CONTACT = "contact";
+ public static final String CH_NUMBER = "number";
+ public static final String CH_DATETIME = "datetime";
+
+ // Background discovery frequency
+ public static final int DISCOVERY_INTERVAL_SECONDS = 300;
+ public static final int DISCOVERY_INITIAL_DELAY_SECONDS = 10;
+
+ // System bridge and remote unit thing config parameters
+ public static final String CONFIG_SYSTEM_ID = "systemId";
+ public static final String CONFIG_UNIT_ID = "unitId";
+
+ public static final List EMPTY_UNITS = Collections
+ . emptyList();
+ public static final List EMPTY_SYSTEMS = Collections. emptyList();
+}
diff --git a/bundles/org.openhab.binding.wolfsmartset/src/main/java/org/openhab/binding/wolfsmartset/internal/WolfSmartsetHandlerFactory.java b/bundles/org.openhab.binding.wolfsmartset/src/main/java/org/openhab/binding/wolfsmartset/internal/WolfSmartsetHandlerFactory.java
new file mode 100644
index 0000000000000..5d892a9315fdd
--- /dev/null
+++ b/bundles/org.openhab.binding.wolfsmartset/src/main/java/org/openhab/binding/wolfsmartset/internal/WolfSmartsetHandlerFactory.java
@@ -0,0 +1,70 @@
+/**
+ * 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.wolfsmartset.internal;
+
+import static org.openhab.binding.wolfsmartset.internal.WolfSmartsetBindingConstants.*;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.eclipse.jdt.annotation.Nullable;
+import org.eclipse.jetty.client.HttpClient;
+import org.openhab.binding.wolfsmartset.internal.handler.WolfSmartsetAccountBridgeHandler;
+import org.openhab.binding.wolfsmartset.internal.handler.WolfSmartsetSystemBridgeHandler;
+import org.openhab.binding.wolfsmartset.internal.handler.WolfSmartsetUnitThingHandler;
+import org.openhab.core.io.net.http.HttpClientFactory;
+import org.openhab.core.thing.Bridge;
+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.Activate;
+import org.osgi.service.component.annotations.Component;
+import org.osgi.service.component.annotations.Reference;
+
+/**
+ * The {@link WolfSmartsetHandlerFactory} is responsible for creating thing handlers
+ * for the account bridge, system bridge, and unit thing.
+ *
+ * @author Bo Biene - Initial contribution
+ */
+@NonNullByDefault
+@Component(configurationPid = "binding.wolfsmartset", service = ThingHandlerFactory.class)
+public class WolfSmartsetHandlerFactory extends BaseThingHandlerFactory {
+ private final HttpClient httpClient;
+
+ @Activate
+ public WolfSmartsetHandlerFactory(@Reference HttpClientFactory httpClientFactory) {
+ this.httpClient = httpClientFactory.getCommonHttpClient();
+ }
+
+ @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 (SUPPORTED_ACCOUNT_BRIDGE_THING_TYPES_UIDS.contains(thingTypeUID)) {
+ return new WolfSmartsetAccountBridgeHandler((Bridge) thing, httpClient);
+ }
+ if (SUPPORTED_SYSTEM_BRIDGE_THING_TYPES_UIDS.contains(thingTypeUID)) {
+ return new WolfSmartsetSystemBridgeHandler((Bridge) thing);
+ }
+ if (SUPPORTED_UNIT_THING_TYPES_UIDS.contains(thingTypeUID)) {
+ return new WolfSmartsetUnitThingHandler(thing);
+ }
+ return null;
+ }
+}
diff --git a/bundles/org.openhab.binding.wolfsmartset/src/main/java/org/openhab/binding/wolfsmartset/internal/api/WolfSmartsetApi.java b/bundles/org.openhab.binding.wolfsmartset/src/main/java/org/openhab/binding/wolfsmartset/internal/api/WolfSmartsetApi.java
new file mode 100644
index 0000000000000..6e85130b94df5
--- /dev/null
+++ b/bundles/org.openhab.binding.wolfsmartset/src/main/java/org/openhab/binding/wolfsmartset/internal/api/WolfSmartsetApi.java
@@ -0,0 +1,610 @@
+/**
+ * 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.wolfsmartset.internal.api;
+
+import java.net.URLEncoder;
+import java.nio.charset.StandardCharsets;
+import java.time.Instant;
+import java.time.LocalDateTime;
+import java.time.format.DateTimeFormatter;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.concurrent.CancellationException;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.LinkedBlockingQueue;
+import java.util.concurrent.RejectedExecutionException;
+import java.util.concurrent.ScheduledExecutorService;
+import java.util.concurrent.ScheduledFuture;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.eclipse.jdt.annotation.Nullable;
+import org.eclipse.jetty.client.HttpClient;
+import org.eclipse.jetty.client.HttpResponseException;
+import org.eclipse.jetty.client.api.ContentResponse;
+import org.eclipse.jetty.client.api.Request;
+import org.eclipse.jetty.client.util.StringContentProvider;
+import org.eclipse.jetty.http.HttpHeader;
+import org.eclipse.jetty.http.HttpMethod;
+import org.eclipse.jetty.http.HttpStatus;
+import org.openhab.binding.wolfsmartset.internal.dto.CreateSession2DTO;
+import org.openhab.binding.wolfsmartset.internal.dto.GetGuiDescriptionForGatewayDTO;
+import org.openhab.binding.wolfsmartset.internal.dto.GetParameterValuesDTO;
+import org.openhab.binding.wolfsmartset.internal.dto.GetSystemListDTO;
+import org.openhab.binding.wolfsmartset.internal.dto.GetSystemStateListDTO;
+import org.openhab.binding.wolfsmartset.internal.dto.LoginResponseDTO;
+import org.openhab.binding.wolfsmartset.internal.dto.ReadFaultMessagesDTO;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.google.gson.Gson;
+import com.google.gson.GsonBuilder;
+import com.google.gson.JsonArray;
+import com.google.gson.JsonElement;
+import com.google.gson.JsonObject;
+import com.google.gson.JsonParseException;
+import com.google.gson.JsonSyntaxException;
+
+/**
+ * The {@link WolfSmartsetCloudConnector} class is used for connecting to the Wolf Smartset cloud service
+ *
+ * @author Bo Biene - Initial contribution
+ */
+@NonNullByDefault
+public class WolfSmartsetApi {
+ private static final int MAX_QUEUE_SIZE = 1000; // maximum queue size
+ private static final int REQUEST_TIMEOUT_SECONDS = 10;
+
+ private static final DateTimeFormatter SESSION_TIME_STAMP = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
+ private static final String WOLF_API_URL = "https://www.wolf-smartset.com/portal/";
+
+ private Instant blockRequestsUntil = Instant.now();
+ private String username;
+ private String password;
+ private String serviceToken = "";
+ private @Nullable CreateSession2DTO session = null;
+ private int loginFailedCounter = 0;
+ private HttpClient httpClient;
+ private int delay = 500; // in ms
+ private final ScheduledExecutorService scheduler;
+ private final Gson gson = new GsonBuilder().serializeNulls().create();
+ private final LinkedBlockingQueue requestQueue = new LinkedBlockingQueue<>(MAX_QUEUE_SIZE);
+ private @Nullable ScheduledFuture> processJob;
+
+ private final Logger logger = LoggerFactory.getLogger(WolfSmartsetApi.class);
+
+ public WolfSmartsetApi(String username, String password, HttpClient httpClient, ScheduledExecutorService scheduler)
+ throws WolfSmartsetCloudException {
+ this.username = username;
+ this.password = password;
+ this.httpClient = httpClient;
+ this.scheduler = scheduler;
+ if (!checkCredentials()) {
+ throw new WolfSmartsetCloudException("username or password can't be empty");
+ }
+ }
+
+ /**
+ * Validate Login to wolf smartset. Returns true if valid token is available, otherwise tries to authenticate with
+ * wolf smartset portal
+ */
+ public synchronized boolean login() {
+ if (!checkCredentials()) {
+ return false;
+ }
+ if (!serviceToken.isEmpty()) {
+ return true;
+ }
+ logger.debug("Wolf Smartset login with username {}", username);
+ try {
+ loginRequest();
+ loginFailedCounter = 0;
+ this.session = getCreateSession();
+ if (this.session != null) {
+ logger.debug("login successful, browserSessionId {}", session.getBrowserSessionId());
+ return true;
+ } else {
+ loginFailedCounter++;
+ this.session = null;
+ logger.trace("Login succeeded but failed to create session {}", loginFailedCounter);
+ return false;
+ }
+
+ } catch (WolfSmartsetCloudException e) {
+ logger.debug("Error logging on to Wolf Smartset ({}): {}", loginFailedCounter, e.getMessage());
+ loginFailedCounter++;
+ serviceToken = "";
+ loginFailedCounterCheck();
+ return false;
+ }
+ }
+
+ /**
+ * Request the systems available for the authenticated account
+ *
+ * @return a list of the available systems
+ */
+ public List getSystems() {
+ final String response = getSystemString();
+ List devicesList = new ArrayList<>();
+ try {
+ GetSystemListDTO[] cdl = gson.fromJson(response, GetSystemListDTO[].class);
+ if (cdl != null) {
+ for (GetSystemListDTO system : cdl) {
+ devicesList.add(system);
+ }
+ }
+ } catch (JsonSyntaxException | IllegalStateException | ClassCastException e) {
+ loginFailedCounter++;
+ logger.warn("Error while parsing devices: {}", e.getMessage());
+ }
+ return devicesList;
+ }
+
+ /**
+ * Request the description of the given system
+ *
+ * @param systemId the id of the system
+ * @param gatewayId the id of the gateway the system relates to
+ * @return dto describing the requested system
+ */
+ public @Nullable GetGuiDescriptionForGatewayDTO getSystemDescription(Integer systemId, Integer gatewayId) {
+ final String response = getSystemDescriptionString(systemId, gatewayId);
+ GetGuiDescriptionForGatewayDTO deviceDescription = null;
+ try {
+ deviceDescription = gson.fromJson(response, GetGuiDescriptionForGatewayDTO.class);
+ } catch (JsonSyntaxException | IllegalStateException | ClassCastException e) {
+ loginFailedCounter++;
+ logger.warn("Error while parsing device descriptions: {}", e.getMessage());
+ }
+ return deviceDescription;
+ }
+
+ /**
+ * Request the system state of the given systems
+ *
+ * @param systems a list of {@link GetSystemListDTO}
+ * @return the {@link GetSystemStateListDTO} descibing the state of the given {@link GetSystemListDTO} items
+ */
+ public @Nullable GetSystemStateListDTO @Nullable [] getSystemState(Collection<@Nullable GetSystemListDTO> systems) {
+ final String response = getSystemStateString(systems);
+ GetSystemStateListDTO[] systemState = null;
+ try {
+ systemState = gson.fromJson(response, GetSystemStateListDTO[].class);
+ } catch (JsonSyntaxException | IllegalStateException | ClassCastException e) {
+ loginFailedCounter++;
+ logger.warn("Error while parsing device descriptions: {}", e.getMessage());
+ }
+ if (systemState != null && systemState.length >= 1) {
+ return systemState;
+ } else {
+ return null;
+ }
+ }
+
+ /**
+ * Request the fault messages of the given system
+ *
+ * @param systemId the id of the system
+ * @param gatewayId the id of the gateway the system relates to
+ * @return {@link ReadFaultMessagesDTO} containing the faultmessages
+ */
+ public @Nullable ReadFaultMessagesDTO getFaultMessages(Integer systemId, Integer gatewayId) {
+ final String response = getFaultMessagesString(systemId, gatewayId);
+ ReadFaultMessagesDTO faultMessages = null;
+ try {
+ faultMessages = gson.fromJson(response, ReadFaultMessagesDTO.class);
+ } catch (JsonSyntaxException | IllegalStateException | ClassCastException e) {
+ loginFailedCounter++;
+ logger.warn("Error while parsing faultmessages: {}", e.getMessage());
+ }
+ return faultMessages;
+ }
+
+ /**
+ * Request the current values for a unit associated with the given system.
+ * if lastAccess is not null, only value changes newer than the given timestamp are returned
+ *
+ * @param systemId the id of the system
+ * @param gatewayId the id of the gateway the system relates to
+ * @param bundleId the id of the Unit
+ * @param valueIdList list of the values to request
+ * @param lastAccess timestamp of the last valid value request
+ * @return {@link GetParameterValuesDTO} containing the requested values
+ */
+ public @Nullable GetParameterValuesDTO getGetParameterValues(Integer systemId, Integer gatewayId, Long bundleId,
+ List valueIdList, @Nullable Instant lastAccess) {
+ final String response = getGetParameterValuesString(systemId, gatewayId, bundleId, valueIdList, lastAccess);
+ GetParameterValuesDTO parameterValues = null;
+ try {
+ parameterValues = gson.fromJson(response, GetParameterValuesDTO.class);
+ } catch (JsonSyntaxException | IllegalStateException | ClassCastException e) {
+ loginFailedCounter++;
+ logger.warn("Error while parsing device parameter values: {}", e.getMessage());
+ }
+ return parameterValues;
+ }
+
+ public void stopRequestQueue() {
+ try {
+ stopProcessJob();
+ requestQueue.forEach(queueEntry -> queueEntry.future.completeExceptionally(new CancellationException()));
+ } catch (Exception e) {
+ logger.debug("Error stopping request queue background processing:{}", e.getMessage(), e);
+ }
+ }
+
+ /**
+ * Set a new delay
+ *
+ * @param delay in ms between to requests
+ */
+ private void setDelay(int delay) {
+ if (delay < 0) {
+ throw new IllegalArgumentException("Delay needs to be larger or equal to zero");
+ }
+ this.delay = delay;
+ stopProcessJob();
+ if (delay != 0) {
+ processJob = scheduler.scheduleWithFixedDelay(() -> processQueue(), 0, delay, TimeUnit.MILLISECONDS);
+ }
+ }
+
+ private boolean checkCredentials() {
+ if (username.trim().isEmpty() || password.trim().isEmpty()) {
+ logger.debug("Wolf Smartset: username or password missing.");
+ return false;
+ }
+ return true;
+ }
+
+ private String getCreateSessionString() {
+ String resp = "";
+ try {
+ JsonObject json = new JsonObject();
+ json.addProperty("Timestamp", SESSION_TIME_STAMP.format(LocalDateTime.now()));
+ resp = requestPOST("api/portal/CreateSession2", json).get();
+ logger.trace("api/portal/CreateSession2 response: {}", resp);
+ } catch (InterruptedException | ExecutionException e) {
+ logger.warn("getSystemStateString failed with {}: {}", e.getCause(), e.getMessage());
+ loginFailedCounter++;
+ } catch (WolfSmartsetCloudException e) {
+ logger.debug("getSystemStateString failed with {}: {}", e.getCause(), e.getMessage());
+ loginFailedCounter++;
+ }
+
+ return resp;
+ }
+
+ private @Nullable CreateSession2DTO getCreateSession() {
+ final String response = getCreateSessionString();
+ CreateSession2DTO session = null;
+ try {
+ session = gson.fromJson(response, CreateSession2DTO.class);
+ } catch (JsonSyntaxException | IllegalStateException | ClassCastException e) {
+ loginFailedCounter++;
+ logger.warn("getCreateSession failed with {}: {}", e.getCause(), e.getMessage());
+ }
+ return session;
+ }
+
+ private String getSystemString() {
+ String resp = "";
+ try {
+ resp = requestGET("api/portal/GetSystemList").get();
+ logger.trace("api/portal/GetSystemList response: {}", resp);
+ } catch (InterruptedException | ExecutionException | WolfSmartsetCloudException e) {
+ logger.warn("getSystemString failed with {}: {}", e.getCause(), e.getMessage());
+ loginFailedCounter++;
+ }
+ return resp;
+ }
+
+ private String getSystemDescriptionString(Integer systemId, Integer gatewayId) {
+ String resp = "";
+ try {
+ Map params = new HashMap();
+ params.put("SystemId", systemId.toString());
+ params.put("GatewayId", gatewayId.toString());
+ resp = requestGET("api/portal/GetGuiDescriptionForGateway", params).get();
+ logger.trace("api/portal/GetGuiDescriptionForGateway response: {}", resp);
+ } catch (InterruptedException | ExecutionException | WolfSmartsetCloudException e) {
+ logger.warn("getSystemDescriptionString failed with {}: {}", e.getCause(), e.getMessage());
+ loginFailedCounter++;
+ }
+ return resp;
+ }
+
+ private String getSystemStateString(Collection<@Nullable GetSystemListDTO> systems) {
+ String resp = "";
+ try {
+ JsonArray jsonSystemList = new JsonArray();
+
+ for (@Nullable
+ GetSystemListDTO system : systems) {
+ if (system != null) {
+ JsonObject jsonSystem = new JsonObject();
+ jsonSystem.addProperty("SystemId", system.getId());
+ jsonSystem.addProperty("GatewayId", system.getGatewayId());
+
+ if (system.getSystemShareId() != null) {
+ jsonSystem.addProperty("SystemShareId", system.getSystemShareId());
+ }
+ jsonSystemList.add(jsonSystem);
+ }
+ }
+
+ JsonObject json = new JsonObject();
+
+ json.add("SystemList", jsonSystemList);
+ resp = requestPOST("api/portal/GetSystemStateList", json).get();
+ logger.trace("api/portal/GetSystemStateList response: {}", resp);
+ } catch (InterruptedException | ExecutionException | WolfSmartsetCloudException e) {
+ logger.warn("getSystemStateString failed with {}: {}", e.getCause(), e.getMessage());
+ loginFailedCounter++;
+ }
+ return resp;
+ }
+
+ private String getFaultMessagesString(Integer systemId, Integer gatewayId) {
+ String resp = "";
+ try {
+ JsonObject json = new JsonObject();
+
+ json.addProperty("SystemId", systemId);
+ json.addProperty("GatewayId", gatewayId);
+ resp = requestPOST("api/portal/ReadFaultMessages", json).get();
+ logger.trace("api/portal/ReadFaultMessages response: {}", resp);
+ } catch (InterruptedException | ExecutionException | WolfSmartsetCloudException e) {
+ logger.warn("getFaultMessagesString failed with {}: {}", e.getCause(), e.getMessage());
+ loginFailedCounter++;
+ }
+ return resp;
+ }
+
+ private String getGetParameterValuesString(Integer systemId, Integer gatewayId, Long bundleId,
+ List valueIdList, @Nullable Instant lastAccess) {
+ String resp = "";
+ try {
+ JsonObject json = new JsonObject();
+ json.addProperty("SystemId", systemId);
+ json.addProperty("GatewayId", gatewayId);
+ json.addProperty("BundleId", bundleId);
+ json.addProperty("IsSubBundle", false);
+ json.add("ValueIdList", gson.toJsonTree(valueIdList));
+ if (lastAccess != null) {
+ json.addProperty("LastAccess", DateTimeFormatter.ISO_INSTANT.format(lastAccess));
+ } else {
+ json.addProperty("LastAccess", (String) null);
+ }
+ json.addProperty("GuiIdChanged", false);
+ if (session != null) {
+ json.addProperty("SessionId", session.getBrowserSessionId());
+ }
+ resp = requestPOST("api/portal/GetParameterValues", json).get();
+ logger.trace("api/portal/GetParameterValues response: {}", resp);
+ } catch (InterruptedException | ExecutionException | WolfSmartsetCloudException e) {
+ logger.warn("getGetParameterValuesString failed with {}: {}", e.getCause(), e.getMessage());
+ loginFailedCounter++;
+ }
+ return resp;
+ }
+
+ private CompletableFuture requestGET(String url) throws WolfSmartsetCloudException {
+ return requestGET(url, new HashMap());
+ }
+
+ private CompletableFuture requestGET(String url, Map params)
+ throws WolfSmartsetCloudException {
+ return rateLimtedRequest(() -> {
+ if (this.serviceToken.isEmpty()) {
+ throw new WolfSmartsetCloudException("Cannot execute request. service token missing");
+ }
+ loginFailedCounterCheck();
+
+ var requestUrl = WOLF_API_URL + url;
+ Request request = httpClient.newRequest(requestUrl).timeout(REQUEST_TIMEOUT_SECONDS, TimeUnit.SECONDS);
+
+ // using HTTP GET with ContentType application/x-www-form-urlencoded like the iOS App does
+ request.header(HttpHeader.AUTHORIZATION, serviceToken);
+ request.method(HttpMethod.GET);
+ request.header(HttpHeader.CONTENT_TYPE, "application/x-www-form-urlencoded");
+
+ for (Entry entry : params.entrySet()) {
+ logger.debug("Send request param: {}={} to {}", entry.getKey(), entry.getValue().toString(), url);
+ request.param(entry.getKey(), entry.getValue());
+ }
+
+ return request;
+ });
+ }
+
+ private CompletableFuture requestPOST(String url, JsonElement json) throws WolfSmartsetCloudException {
+ return rateLimtedRequest(() -> {
+ if (this.serviceToken.isEmpty()) {
+ throw new WolfSmartsetCloudException("Cannot execute request. service token missing");
+ }
+ loginFailedCounterCheck();
+
+ var request = createPOSTRequest(url, json);
+ request.header(HttpHeader.AUTHORIZATION, serviceToken);
+ return request;
+ });
+ }
+
+ private Request createPOSTRequest(String url, JsonElement json) {
+ var requestUrl = WOLF_API_URL + url;
+ Request request = httpClient.newRequest(requestUrl).timeout(REQUEST_TIMEOUT_SECONDS, TimeUnit.SECONDS);
+
+ request.header(HttpHeader.ACCEPT, "application/json");
+ request.header(HttpHeader.CONTENT_TYPE, "application/json");
+ request.method(HttpMethod.POST);
+
+ request.content(new StringContentProvider(json.toString()), "application/json");
+ return request;
+ }
+
+ private CompletableFuture rateLimtedRequest(SupplyRequestFunctionalInterface buildRequest) {
+ // if no delay is set, return a completed CompletableFuture
+ CompletableFuture future = new CompletableFuture<>();
+ RequestQueueEntry queueEntry = new RequestQueueEntry(buildRequest, future);
+
+ if (delay == 0) {
+ queueEntry.completeFuture((r) -> this.getResponse(r));
+ } else {
+ if (!requestQueue.offer(queueEntry)) {
+ future.completeExceptionally(new RejectedExecutionException("Maximum queue size exceeded."));
+ }
+ }
+ return future;
+ }
+
+ private void stopProcessJob() {
+ ScheduledFuture> processJob = this.processJob;
+ if (processJob != null) {
+ processJob.cancel(false);
+ this.processJob = null;
+ }
+ }
+
+ private void processQueue() {
+ // No new Requests until blockRequestsUntil, is set when recieved HttpStatus.TOO_MANY_REQUESTS_429
+ if (blockRequestsUntil.isBefore(Instant.now())) {
+ RequestQueueEntry queueEntry = requestQueue.poll();
+ if (queueEntry != null) {
+ queueEntry.completeFuture((r) -> this.getResponse(r));
+ }
+ }
+ }
+
+ @FunctionalInterface
+ interface SupplyRequestFunctionalInterface {
+ Request get() throws WolfSmartsetCloudException;
+ }
+
+ @FunctionalInterface
+ interface GetResponseFunctionalInterface {
+ String get(Request request) throws WolfSmartsetCloudException;
+ }
+
+ private String getResponse(Request request) throws WolfSmartsetCloudException {
+ try {
+ logger.debug("execute request {} {}", request.getMethod(), request.getURI());
+ final ContentResponse response = request.send();
+ if (response.getStatus() == HttpStatus.NOT_FOUND_404) {
+ throw new WolfSmartsetCloudException("Invalid request, not found " + request.getURI());
+ } else if (response.getStatus() == HttpStatus.TOO_MANY_REQUESTS_429) {
+ blockRequestsUntil = Instant.now().plusSeconds(30);
+ throw new WolfSmartsetCloudException("Error too many requests: " + response.getContentAsString());
+ } else if (response.getStatus() >= HttpStatus.BAD_REQUEST_400
+ && response.getStatus() < HttpStatus.INTERNAL_SERVER_ERROR_500) {
+ this.serviceToken = "";
+ logger.debug("Status {} while executing request to {} :{}", response.getStatus(), request.getURI(),
+ response.getContentAsString());
+ } else {
+ return response.getContentAsString();
+ }
+ } catch (HttpResponseException e) {
+ serviceToken = "";
+ logger.debug("Error while executing request to {} :{}", request.getURI(), e.getMessage());
+ loginFailedCounter++;
+ } catch (InterruptedException | TimeoutException | ExecutionException /* | IOException */ e) {
+ logger.debug("Error while executing request to {} :{}", request.getURI(), e.getMessage());
+ loginFailedCounter++;
+ }
+ return "";
+ }
+
+ void loginFailedCounterCheck() {
+ if (loginFailedCounter > 10) {
+ logger.debug("Repeated errors logging on to Wolf Smartset");
+ serviceToken = "";
+ loginFailedCounter = 0;
+ }
+ }
+
+ protected void loginRequest() throws WolfSmartsetCloudException {
+ try {
+ setDelay(delay);
+ logger.trace("Wolf Smartset Login");
+
+ String url = WOLF_API_URL + "connect/token";
+ Request request = httpClient.POST(url).timeout(REQUEST_TIMEOUT_SECONDS, TimeUnit.SECONDS);
+ request.header(HttpHeader.CONTENT_TYPE, "application/x-www-form-urlencoded");
+
+ // Building Request body exacly the way the iOS App did this
+ var encodedUser = URLEncoder.encode(username, StandardCharsets.UTF_8);
+ var encodedPassword = URLEncoder.encode(password, StandardCharsets.UTF_8);
+ var authRequestBody = "grant_type=password&username=" + encodedUser + "&password=" + encodedPassword;
+
+ request.content(new StringContentProvider("application/x-www-form-urlencoded", authRequestBody,
+ StandardCharsets.UTF_8));
+
+ final ContentResponse response;
+ response = request.send();
+
+ final String content = response.getContentAsString();
+ logger.trace("Wolf smartset Login response= {}", response);
+ logger.trace("Wolf smartset Login content= {}", content);
+
+ switch (response.getStatus()) {
+ case HttpStatus.FORBIDDEN_403:
+ throw new WolfSmartsetCloudException(
+ "Access denied. Did you set the correct password and/or username?");
+ case HttpStatus.OK_200:
+ LoginResponseDTO jsonResp = gson.fromJson(content, LoginResponseDTO.class);
+ if (jsonResp == null) {
+ throw new WolfSmartsetCloudException("Error getting logon details: " + content);
+ }
+
+ serviceToken = jsonResp.getTokenType() + " " + jsonResp.getAccessToken();
+
+ logger.trace("Wolf Smartset login scope = {}", jsonResp.getScope());
+ logger.trace("Wolf Smartset login expiresIn = {}", jsonResp.getExpiresIn());
+ logger.trace("Wolf Smartset login tokenType = {}", jsonResp.getTokenType());
+ return;
+ default:
+ logger.trace("request returned status '{}', reason: {}, content = {}", response.getStatus(),
+ response.getReason(), response.getContentAsString());
+ throw new WolfSmartsetCloudException(response.getStatus() + response.getReason());
+ }
+ } catch (InterruptedException | TimeoutException | ExecutionException | JsonParseException e) {
+ throw new WolfSmartsetCloudException("Cannot logon to Wolf Smartset cloud: " + e.getMessage(), e);
+ }
+ }
+
+ private static class RequestQueueEntry {
+ private SupplyRequestFunctionalInterface buildRequest;
+ private CompletableFuture future;
+
+ public RequestQueueEntry(SupplyRequestFunctionalInterface buildRequest, CompletableFuture future) {
+ this.buildRequest = buildRequest;
+ this.future = future;
+ }
+
+ public void completeFuture(GetResponseFunctionalInterface getResponse) {
+ try {
+ String response = getResponse.get(this.buildRequest.get());
+ future.complete(response);
+ } catch (WolfSmartsetCloudException e) {
+ future.completeExceptionally(e);
+ }
+ }
+ }
+}
diff --git a/bundles/org.openhab.binding.wolfsmartset/src/main/java/org/openhab/binding/wolfsmartset/internal/api/WolfSmartsetCloudException.java b/bundles/org.openhab.binding.wolfsmartset/src/main/java/org/openhab/binding/wolfsmartset/internal/api/WolfSmartsetCloudException.java
new file mode 100644
index 0000000000000..010f48fda2759
--- /dev/null
+++ b/bundles/org.openhab.binding.wolfsmartset/src/main/java/org/openhab/binding/wolfsmartset/internal/api/WolfSmartsetCloudException.java
@@ -0,0 +1,41 @@
+/**
+ * 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.wolfsmartset.internal.api;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.eclipse.jdt.annotation.Nullable;
+
+/**
+ * Will be thrown for cloud errors
+ *
+ * @author Bo Biene - Initial contribution
+ */
+@NonNullByDefault
+public class WolfSmartsetCloudException extends Exception {
+ /**
+ * required variable to avoid IncorrectMultilineIndexException warning
+ */
+ private static final Long serialVersionUID = -1280858607995252321L;
+
+ public WolfSmartsetCloudException() {
+ super();
+ }
+
+ public WolfSmartsetCloudException(@Nullable String message) {
+ super(message);
+ }
+
+ public WolfSmartsetCloudException(@Nullable String message, @Nullable Exception e) {
+ super(message, e);
+ }
+}
diff --git a/bundles/org.openhab.binding.wolfsmartset/src/main/java/org/openhab/binding/wolfsmartset/internal/config/WolfSmartsetAccountConfiguration.java b/bundles/org.openhab.binding.wolfsmartset/src/main/java/org/openhab/binding/wolfsmartset/internal/config/WolfSmartsetAccountConfiguration.java
new file mode 100644
index 0000000000000..1f23a0a6abc31
--- /dev/null
+++ b/bundles/org.openhab.binding.wolfsmartset/src/main/java/org/openhab/binding/wolfsmartset/internal/config/WolfSmartsetAccountConfiguration.java
@@ -0,0 +1,43 @@
+/**
+ * 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.wolfsmartset.internal.config;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.eclipse.jdt.annotation.Nullable;
+
+/**
+ * The {@link WolfSmartsetAccountConfiguration} class contains fields mapping
+ * to the account thing configuration parameters.
+ *
+ * @author Bo Biene - Initial contribution
+ */
+@NonNullByDefault
+public class WolfSmartsetAccountConfiguration {
+
+ public @Nullable String username = "";
+ public @Nullable String password = "";
+ /**
+ * Time in seconds between information refresh
+ */
+ public @Nullable Integer refreshIntervalStructure;
+
+ /**
+ * Time in seconds to wait after successful update, command or action before refresh
+ */
+ public @Nullable Integer refreshIntervalValues;
+
+ /*
+ * Enable/disable automatic discovery
+ */
+ public @Nullable Boolean discoveryEnabled;
+}
diff --git a/bundles/org.openhab.binding.wolfsmartset/src/main/java/org/openhab/binding/wolfsmartset/internal/config/WolfSmartsetSystemConfiguration.java b/bundles/org.openhab.binding.wolfsmartset/src/main/java/org/openhab/binding/wolfsmartset/internal/config/WolfSmartsetSystemConfiguration.java
new file mode 100644
index 0000000000000..bd852af3f1ea1
--- /dev/null
+++ b/bundles/org.openhab.binding.wolfsmartset/src/main/java/org/openhab/binding/wolfsmartset/internal/config/WolfSmartsetSystemConfiguration.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.wolfsmartset.internal.config;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.eclipse.jdt.annotation.Nullable;
+
+/**
+ * The {@link WolfSmartsetSystemConfiguration} class contains fields mapping
+ * to the system thing configuration parameters.
+ *
+ * @author Bo Biene - Initial contribution
+ */
+@NonNullByDefault
+public class WolfSmartsetSystemConfiguration {
+
+ /**
+ * System ID assigned by WolfSmartset
+ */
+ public @Nullable String systemId;
+}
diff --git a/bundles/org.openhab.binding.wolfsmartset/src/main/java/org/openhab/binding/wolfsmartset/internal/config/WolfSmartsetUnitConfiguration.java b/bundles/org.openhab.binding.wolfsmartset/src/main/java/org/openhab/binding/wolfsmartset/internal/config/WolfSmartsetUnitConfiguration.java
new file mode 100644
index 0000000000000..3135a1fa7031c
--- /dev/null
+++ b/bundles/org.openhab.binding.wolfsmartset/src/main/java/org/openhab/binding/wolfsmartset/internal/config/WolfSmartsetUnitConfiguration.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.wolfsmartset.internal.config;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.eclipse.jdt.annotation.Nullable;
+
+/**
+ * The {@link WolfSmartsetUnitConfiguration} class contains fields mapping
+ * to the unit thing configuration parameters.
+ *
+ * @author Bo Biene - Initial contribution
+ */
+@NonNullByDefault
+public class WolfSmartsetUnitConfiguration {
+
+ /**
+ * Id of remote unit
+ */
+ public @Nullable String unitId;
+}
diff --git a/bundles/org.openhab.binding.wolfsmartset/src/main/java/org/openhab/binding/wolfsmartset/internal/discovery/WolfSmartsetAccountDiscoveryService.java b/bundles/org.openhab.binding.wolfsmartset/src/main/java/org/openhab/binding/wolfsmartset/internal/discovery/WolfSmartsetAccountDiscoveryService.java
new file mode 100644
index 0000000000000..41a5f31c632b4
--- /dev/null
+++ b/bundles/org.openhab.binding.wolfsmartset/src/main/java/org/openhab/binding/wolfsmartset/internal/discovery/WolfSmartsetAccountDiscoveryService.java
@@ -0,0 +1,154 @@
+/**
+ * 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.wolfsmartset.internal.discovery;
+
+import static org.openhab.binding.wolfsmartset.internal.WolfSmartsetBindingConstants.*;
+
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.Future;
+import java.util.concurrent.TimeUnit;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.eclipse.jdt.annotation.Nullable;
+import org.openhab.binding.wolfsmartset.internal.dto.GetSystemListDTO;
+import org.openhab.binding.wolfsmartset.internal.handler.WolfSmartsetAccountBridgeHandler;
+import org.openhab.core.config.discovery.AbstractDiscoveryService;
+import org.openhab.core.config.discovery.DiscoveryResult;
+import org.openhab.core.config.discovery.DiscoveryResultBuilder;
+import org.openhab.core.thing.ThingStatus;
+import org.openhab.core.thing.ThingTypeUID;
+import org.openhab.core.thing.ThingUID;
+import org.openhab.core.thing.binding.ThingHandler;
+import org.openhab.core.thing.binding.ThingHandlerService;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * The {@link WolfSmartsetAccountDiscoveryService} is responsible for discovering the WolfSmartset
+ * systems that are associated with the WolfSmartset Account
+ *
+ * @author Bo Biene - Initial contribution
+ */
+@NonNullByDefault
+public class WolfSmartsetAccountDiscoveryService extends AbstractDiscoveryService implements ThingHandlerService {
+
+ private final Logger logger = LoggerFactory.getLogger(WolfSmartsetAccountDiscoveryService.class);
+
+ private @NonNullByDefault({}) WolfSmartsetAccountBridgeHandler bridgeHandler;
+
+ private @Nullable Future> discoveryJob;
+
+ public WolfSmartsetAccountDiscoveryService() {
+ super(SUPPORTED_SYSTEM_AND_UNIT_THING_TYPES_UIDS, 8, true);
+ }
+
+ @Override
+ public void setThingHandler(@Nullable ThingHandler handler) {
+ if (handler instanceof WolfSmartsetAccountBridgeHandler) {
+ this.bridgeHandler = (WolfSmartsetAccountBridgeHandler) handler;
+ }
+ }
+
+ @Override
+ public @Nullable ThingHandler getThingHandler() {
+ return bridgeHandler;
+ }
+
+ @Override
+ public void activate() {
+ super.activate(null);
+ }
+
+ @Override
+ public void deactivate() {
+ super.deactivate();
+ }
+
+ @Override
+ public Set getSupportedThingTypes() {
+ return SUPPORTED_SYSTEM_AND_UNIT_THING_TYPES_UIDS;
+ }
+
+ @Override
+ protected void startBackgroundDiscovery() {
+ logger.debug("WolfSmartsetDiscovery: Starting background discovery job");
+
+ Future> localDiscoveryJob = discoveryJob;
+ if (localDiscoveryJob == null || localDiscoveryJob.isCancelled()) {
+ discoveryJob = scheduler.scheduleWithFixedDelay(this::backgroundDiscover, DISCOVERY_INITIAL_DELAY_SECONDS,
+ DISCOVERY_INTERVAL_SECONDS, TimeUnit.SECONDS);
+ }
+ }
+
+ @Override
+ protected void stopBackgroundDiscovery() {
+ logger.debug("WolfSmartsetDiscovery: Stopping background discovery job");
+ Future> localDiscoveryJob = discoveryJob;
+ if (localDiscoveryJob != null) {
+ localDiscoveryJob.cancel(true);
+ discoveryJob = null;
+ }
+ }
+
+ @Override
+ public void startScan() {
+ logger.debug("WolfSmartsetDiscovery: Starting discovery scan");
+ discover();
+ }
+
+ private void backgroundDiscover() {
+ if (!bridgeHandler.isBackgroundDiscoveryEnabled()) {
+ return;
+ }
+ discover();
+ }
+
+ private void discover() {
+ if (bridgeHandler.getThing().getStatus() != ThingStatus.ONLINE) {
+ logger.debug("WolfSmartsetDiscovery: Skipping discovery because Account Bridge thing is not ONLINE");
+ return;
+ }
+ logger.debug("WolfSmartsetDiscovery: Discovering WolfSmartset devices");
+ discoverSystems();
+ }
+
+ private synchronized void discoverSystems() {
+ logger.debug("WolfSmartsetDiscovery: Discovering systems");
+ var registeredSytems = bridgeHandler.getRegisteredSystems();
+ if (registeredSytems != null) {
+ for (GetSystemListDTO system : registeredSytems) {
+ String name = system.getName();
+ String identifier = null;
+ if (system.getId() != null) {
+ identifier = system.getId().toString();
+ }
+ if (identifier != null && name != null) {
+ ThingUID thingUID = new ThingUID(UID_SYSTEM_BRIDGE, bridgeHandler.getThing().getUID(), identifier);
+ thingDiscovered(createSystemDiscoveryResult(thingUID, identifier, name));
+ logger.debug("WolfSmartsetDiscovery: System '{}' and name '{}' added with UID '{}'", identifier,
+ name, thingUID);
+ }
+ }
+ }
+ }
+
+ private DiscoveryResult createSystemDiscoveryResult(ThingUID systemUID, String identifier, String name) {
+ Map properties = new HashMap<>();
+ properties.put(CONFIG_SYSTEM_ID, identifier);
+ return DiscoveryResultBuilder.create(systemUID).withProperties(properties)
+ .withRepresentationProperty(CONFIG_SYSTEM_ID).withBridge(bridgeHandler.getThing().getUID())
+ .withLabel(String.format("WolfSmartset System %s", name)).build();
+ }
+}
diff --git a/bundles/org.openhab.binding.wolfsmartset/src/main/java/org/openhab/binding/wolfsmartset/internal/discovery/WolfSmartsetSystemDiscoveryService.java b/bundles/org.openhab.binding.wolfsmartset/src/main/java/org/openhab/binding/wolfsmartset/internal/discovery/WolfSmartsetSystemDiscoveryService.java
new file mode 100644
index 0000000000000..dda2cb90c0b04
--- /dev/null
+++ b/bundles/org.openhab.binding.wolfsmartset/src/main/java/org/openhab/binding/wolfsmartset/internal/discovery/WolfSmartsetSystemDiscoveryService.java
@@ -0,0 +1,161 @@
+/**
+ * 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.wolfsmartset.internal.discovery;
+
+import static org.openhab.binding.wolfsmartset.internal.WolfSmartsetBindingConstants.*;
+
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.Future;
+import java.util.concurrent.TimeUnit;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.eclipse.jdt.annotation.Nullable;
+import org.openhab.binding.wolfsmartset.internal.dto.GetSystemListDTO;
+import org.openhab.binding.wolfsmartset.internal.dto.SubMenuEntryWithMenuItemTabView;
+import org.openhab.binding.wolfsmartset.internal.handler.WolfSmartsetSystemBridgeHandler;
+import org.openhab.core.config.discovery.AbstractDiscoveryService;
+import org.openhab.core.config.discovery.DiscoveryResult;
+import org.openhab.core.config.discovery.DiscoveryResultBuilder;
+import org.openhab.core.thing.ThingStatus;
+import org.openhab.core.thing.ThingTypeUID;
+import org.openhab.core.thing.ThingUID;
+import org.openhab.core.thing.binding.ThingHandler;
+import org.openhab.core.thing.binding.ThingHandlerService;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * The {@link WolfSmartsetAccountDiscoveryService} is responsible for discovering the WolfSmartset Units
+ * that are associated with the WolfSmartset System
+ *
+ * @author Bo Biene - Initial contribution
+ */
+@NonNullByDefault
+public class WolfSmartsetSystemDiscoveryService extends AbstractDiscoveryService implements ThingHandlerService {
+
+ private final Logger logger = LoggerFactory.getLogger(WolfSmartsetSystemDiscoveryService.class);
+
+ private @NonNullByDefault({}) WolfSmartsetSystemBridgeHandler bridgeHandler;
+
+ private @Nullable Future> discoveryJob;
+
+ public WolfSmartsetSystemDiscoveryService() {
+ super(SUPPORTED_SYSTEM_AND_UNIT_THING_TYPES_UIDS, 8, true);
+ }
+
+ @Override
+ public void setThingHandler(@Nullable ThingHandler handler) {
+ if (handler instanceof WolfSmartsetSystemBridgeHandler) {
+ this.bridgeHandler = (WolfSmartsetSystemBridgeHandler) handler;
+ }
+ }
+
+ @Override
+ public @Nullable ThingHandler getThingHandler() {
+ return bridgeHandler;
+ }
+
+ @Override
+ public void activate() {
+ super.activate(null);
+ }
+
+ @Override
+ public void deactivate() {
+ super.deactivate();
+ }
+
+ @Override
+ public Set getSupportedThingTypes() {
+ return SUPPORTED_SYSTEM_AND_UNIT_THING_TYPES_UIDS;
+ }
+
+ @Override
+ protected void startBackgroundDiscovery() {
+ logger.debug("WolfSmartsetSystemDiscovery: Starting background discovery job");
+ Future> localDiscoveryJob = discoveryJob;
+ if (localDiscoveryJob == null || localDiscoveryJob.isCancelled()) {
+ discoveryJob = scheduler.scheduleWithFixedDelay(() -> this.backgroundDiscover(),
+ DISCOVERY_INITIAL_DELAY_SECONDS, DISCOVERY_INTERVAL_SECONDS, TimeUnit.SECONDS);
+ }
+ }
+
+ @Override
+ protected void stopBackgroundDiscovery() {
+ logger.debug("WolfSmartsetSystemDiscovery: Stopping background discovery job");
+ Future> localDiscoveryJob = discoveryJob;
+ if (localDiscoveryJob != null) {
+ localDiscoveryJob.cancel(true);
+ discoveryJob = null;
+ }
+ }
+
+ @Override
+ public void startScan() {
+ logger.debug("WolfSmartsetSystemDiscovery: Starting discovery scan");
+ discover();
+ }
+
+ private void backgroundDiscover() {
+ var accountBridgeHandler = bridgeHandler.getAccountBridgeHandler();
+ if (accountBridgeHandler == null || !accountBridgeHandler.isBackgroundDiscoveryEnabled()) {
+ return;
+ }
+ discover();
+ }
+
+ private void discover() {
+ if (bridgeHandler.getThing().getStatus() != ThingStatus.ONLINE) {
+ logger.debug("WolfSmartsetSystemDiscovery: Skipping discovery because Account Bridge thing is not ONLINE");
+ return;
+ }
+ logger.debug("WolfSmartsetSystemDiscovery: Discovering WolfSmartset devices");
+ discoverUnits();
+ }
+
+ private synchronized void discoverUnits() {
+ if (this.bridgeHandler != null) {
+ String systemId = this.bridgeHandler.getSystemId();
+ var systemConfig = this.bridgeHandler.getSystemConfig();
+ if (systemConfig != null) {
+ logger.debug("WolfSmartsetSystemDiscovery: Discovering units for system '{}' (id {})",
+ systemConfig.getName(), systemId);
+ for (var unit : this.bridgeHandler.getUnits()) {
+ ThingUID bridgeUID = this.bridgeHandler.getThing().getUID();
+ ThingUID unitUID = new ThingUID(UID_UNIT_THING, bridgeUID,
+ unit.menuItemTabViewDTO.bundleId.toString());
+ thingDiscovered(createUnitDiscoveryResult(unitUID, bridgeUID, systemConfig, unit));
+ logger.debug(
+ "WolfSmartsetSystemDiscovery: Unit for '{}' with id '{}' and name '{}' added with UID '{}'",
+ systemId, unit.menuItemTabViewDTO.bundleId, unit.menuItemTabViewDTO.tabName, unitUID);
+ }
+ }
+ }
+ }
+
+ private DiscoveryResult createUnitDiscoveryResult(ThingUID unitUID, ThingUID bridgeUID,
+ GetSystemListDTO systemConfig, SubMenuEntryWithMenuItemTabView unit) {
+ Map properties = new HashMap<>();
+ properties.put(CONFIG_UNIT_ID, unit.menuItemTabViewDTO.bundleId.toString());
+ var tabName = unit.menuItemTabViewDTO.tabName;
+ var menuName = unit.subMenuEntryDTO.getName();
+ tabName = tabName.isEmpty() || tabName.equalsIgnoreCase("NULL") || menuName.equalsIgnoreCase(tabName) ? ""
+ : "-" + tabName;
+
+ return DiscoveryResultBuilder.create(unitUID).withProperties(properties)
+ .withRepresentationProperty(CONFIG_UNIT_ID).withBridge(bridgeUID)
+ .withLabel(String.format("%s %s%s", systemConfig.getName(), menuName, tabName)).build();
+ }
+}
diff --git a/bundles/org.openhab.binding.wolfsmartset/src/main/java/org/openhab/binding/wolfsmartset/internal/dto/CreateSession2DTO.java b/bundles/org.openhab.binding.wolfsmartset/src/main/java/org/openhab/binding/wolfsmartset/internal/dto/CreateSession2DTO.java
new file mode 100644
index 0000000000000..0d13b4bc1eb05
--- /dev/null
+++ b/bundles/org.openhab.binding.wolfsmartset/src/main/java/org/openhab/binding/wolfsmartset/internal/dto/CreateSession2DTO.java
@@ -0,0 +1,83 @@
+/**
+ * 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.wolfsmartset.internal.dto;
+
+import javax.annotation.Generated;
+
+import com.google.gson.annotations.Expose;
+import com.google.gson.annotations.SerializedName;
+
+/**
+ * generated with https://www.jsonschema2pojo.org/
+ *
+ * @author Bo Biene - Initial contribution
+ */
+@Generated("jsonschema2pojo")
+public class CreateSession2DTO {
+
+ @SerializedName("CultureInfoCode")
+ @Expose
+ private String cultureInfoCode;
+ @SerializedName("IsPasswordReset")
+ @Expose
+ private Boolean isPasswordReset;
+ @SerializedName("IsProfessional")
+ @Expose
+ private Boolean isProfessional;
+ @SerializedName("IsProfessionalPasswordReset")
+ @Expose
+ private Boolean isProfessionalPasswordReset;
+ @SerializedName("BrowserSessionId")
+ @Expose
+ private Integer browserSessionId;
+
+ public String getCultureInfoCode() {
+ return cultureInfoCode;
+ }
+
+ public void setCultureInfoCode(String cultureInfoCode) {
+ this.cultureInfoCode = cultureInfoCode;
+ }
+
+ public Boolean getIsPasswordReset() {
+ return isPasswordReset;
+ }
+
+ public void setIsPasswordReset(Boolean isPasswordReset) {
+ this.isPasswordReset = isPasswordReset;
+ }
+
+ public Boolean getIsProfessional() {
+ return isProfessional;
+ }
+
+ public void setIsProfessional(Boolean isProfessional) {
+ this.isProfessional = isProfessional;
+ }
+
+ public Boolean getIsProfessionalPasswordReset() {
+ return isProfessionalPasswordReset;
+ }
+
+ public void setIsProfessionalPasswordReset(Boolean isProfessionalPasswordReset) {
+ this.isProfessionalPasswordReset = isProfessionalPasswordReset;
+ }
+
+ public Integer getBrowserSessionId() {
+ return browserSessionId;
+ }
+
+ public void setBrowserSessionId(Integer browserSessionId) {
+ this.browserSessionId = browserSessionId;
+ }
+}
diff --git a/bundles/org.openhab.binding.wolfsmartset/src/main/java/org/openhab/binding/wolfsmartset/internal/dto/CurrentMessageDTO.java b/bundles/org.openhab.binding.wolfsmartset/src/main/java/org/openhab/binding/wolfsmartset/internal/dto/CurrentMessageDTO.java
new file mode 100644
index 0000000000000..9868a3ac54e65
--- /dev/null
+++ b/bundles/org.openhab.binding.wolfsmartset/src/main/java/org/openhab/binding/wolfsmartset/internal/dto/CurrentMessageDTO.java
@@ -0,0 +1,105 @@
+/**
+ * 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.wolfsmartset.internal.dto;
+
+import javax.annotation.Generated;
+
+import com.google.gson.annotations.Expose;
+import com.google.gson.annotations.SerializedName;
+
+/**
+ * generated with https://www.jsonschema2pojo.org/
+ *
+ * @author Bo Biene - Initial contribution
+ */
+@Generated("jsonschema2pojo")
+public class CurrentMessageDTO {
+
+ @SerializedName("Id")
+ @Expose
+ private Integer id;
+ @SerializedName("ErrorCode")
+ @Expose
+ private Integer errorCode;
+ @SerializedName("Description")
+ @Expose
+ private String description;
+ @SerializedName("OccurTimeLocal")
+ @Expose
+ private String occurTimeLocal;
+ @SerializedName("Active")
+ @Expose
+ private Boolean active;
+ @SerializedName("Index")
+ @Expose
+ private Integer index;
+ @SerializedName("Device")
+ @Expose
+ private String device;
+
+ public Integer getId() {
+ return id;
+ }
+
+ public void setId(Integer id) {
+ this.id = id;
+ }
+
+ public Integer getErrorCode() {
+ return errorCode;
+ }
+
+ public void setErrorCode(Integer errorCode) {
+ this.errorCode = errorCode;
+ }
+
+ public String getDescription() {
+ return description;
+ }
+
+ public void setDescription(String description) {
+ this.description = description;
+ }
+
+ public String getOccurTimeLocal() {
+ return occurTimeLocal;
+ }
+
+ public void setOccurTimeLocal(String occurTimeLocal) {
+ this.occurTimeLocal = occurTimeLocal;
+ }
+
+ public Boolean getActive() {
+ return active;
+ }
+
+ public void setActive(Boolean active) {
+ this.active = active;
+ }
+
+ public Integer getIndex() {
+ return index;
+ }
+
+ public void setIndex(Integer index) {
+ this.index = index;
+ }
+
+ public String getDevice() {
+ return device;
+ }
+
+ public void setDevice(String device) {
+ this.device = device;
+ }
+}
diff --git a/bundles/org.openhab.binding.wolfsmartset/src/main/java/org/openhab/binding/wolfsmartset/internal/dto/GatewayStateDTO.java b/bundles/org.openhab.binding.wolfsmartset/src/main/java/org/openhab/binding/wolfsmartset/internal/dto/GatewayStateDTO.java
new file mode 100644
index 0000000000000..f511644cb5497
--- /dev/null
+++ b/bundles/org.openhab.binding.wolfsmartset/src/main/java/org/openhab/binding/wolfsmartset/internal/dto/GatewayStateDTO.java
@@ -0,0 +1,105 @@
+/**
+ * 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.wolfsmartset.internal.dto;
+
+import javax.annotation.Generated;
+
+import com.google.gson.annotations.Expose;
+import com.google.gson.annotations.SerializedName;
+
+/**
+ * generated with https://www.jsonschema2pojo.org/
+ *
+ * @author Bo Biene - Initial contribution
+ */
+@Generated("jsonschema2pojo")
+public class GatewayStateDTO {
+
+ @SerializedName("GatewayId")
+ @Expose
+ private Integer gatewayId;
+ @SerializedName("IsOnline")
+ @Expose
+ private Boolean isOnline;
+ @SerializedName("GatewayOfflineCause")
+ @Expose
+ private Integer gatewayOfflineCause;
+ @SerializedName("IsLocked")
+ @Expose
+ private Boolean isLocked;
+ @SerializedName("IsDeleted")
+ @Expose
+ private Boolean isDeleted;
+ @SerializedName("IsBusy")
+ @Expose
+ private Boolean isBusy;
+ @SerializedName("ImageName")
+ @Expose
+ private String imageName;
+
+ public Integer getGatewayId() {
+ return gatewayId;
+ }
+
+ public void setGatewayId(Integer gatewayId) {
+ this.gatewayId = gatewayId;
+ }
+
+ public Boolean getIsOnline() {
+ return isOnline;
+ }
+
+ public void setIsOnline(Boolean isOnline) {
+ this.isOnline = isOnline;
+ }
+
+ public Integer getGatewayOfflineCause() {
+ return gatewayOfflineCause;
+ }
+
+ public void setGatewayOfflineCause(Integer gatewayOfflineCause) {
+ this.gatewayOfflineCause = gatewayOfflineCause;
+ }
+
+ public Boolean getIsLocked() {
+ return isLocked;
+ }
+
+ public void setIsLocked(Boolean isLocked) {
+ this.isLocked = isLocked;
+ }
+
+ public Boolean getIsDeleted() {
+ return isDeleted;
+ }
+
+ public void setIsDeleted(Boolean isDeleted) {
+ this.isDeleted = isDeleted;
+ }
+
+ public Boolean getIsBusy() {
+ return isBusy;
+ }
+
+ public void setIsBusy(Boolean isBusy) {
+ this.isBusy = isBusy;
+ }
+
+ public String getImageName() {
+ return imageName;
+ }
+
+ public void setImageName(String imageName) {
+ this.imageName = imageName;
+ }
+}
diff --git a/bundles/org.openhab.binding.wolfsmartset/src/main/java/org/openhab/binding/wolfsmartset/internal/dto/GetGuiDescriptionForGatewayDTO.java b/bundles/org.openhab.binding.wolfsmartset/src/main/java/org/openhab/binding/wolfsmartset/internal/dto/GetGuiDescriptionForGatewayDTO.java
new file mode 100644
index 0000000000000..1c5ce124a625a
--- /dev/null
+++ b/bundles/org.openhab.binding.wolfsmartset/src/main/java/org/openhab/binding/wolfsmartset/internal/dto/GetGuiDescriptionForGatewayDTO.java
@@ -0,0 +1,63 @@
+/**
+ * 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.wolfsmartset.internal.dto;
+
+import java.util.List;
+
+import javax.annotation.Generated;
+
+import com.google.gson.annotations.Expose;
+import com.google.gson.annotations.SerializedName;
+
+/**
+ * generated with https://www.jsonschema2pojo.org/
+ *
+ * @author Bo Biene - Initial contribution
+ */
+@Generated("jsonschema2pojo")
+public class GetGuiDescriptionForGatewayDTO {
+
+ @SerializedName("MenuItems")
+ @Expose
+ private List menuItems = null;
+ @SerializedName("DynFaultMessageDevices")
+ @Expose
+ private List