diff --git a/CODEOWNERS b/CODEOWNERS
index 76785db776e..48885dfc57a 100644
--- a/CODEOWNERS
+++ b/CODEOWNERS
@@ -168,6 +168,7 @@
/bundles/org.openhab.binding.linky/ @clinique @lolodomo
/bundles/org.openhab.binding.linuxinput/ @t-8ch
/bundles/org.openhab.binding.lirc/ @kabili207
+/bundles/org.openhab.binding.livisismarthome/ @Novanic
/bundles/org.openhab.binding.logreader/ @paulianttila
/bundles/org.openhab.binding.loxone/ @ppieczul
/bundles/org.openhab.binding.luftdateninfo/ @weymann
diff --git a/bom/openhab-addons/pom.xml b/bom/openhab-addons/pom.xml
index 155250edff2..e86608264c2 100644
--- a/bom/openhab-addons/pom.xml
+++ b/bom/openhab-addons/pom.xml
@@ -836,6 +836,11 @@
org.openhab.binding.lirc
${project.version}
+
+ org.openhab.addons.bundles
+ org.openhab.binding.livisismarthome
+ ${project.version}
+
org.openhab.addons.bundles
org.openhab.binding.logreader
diff --git a/bundles/org.openhab.binding.innogysmarthome/README.md b/bundles/org.openhab.binding.innogysmarthome/README.md
index 0869850a580..376b163e98d 100644
--- a/bundles/org.openhab.binding.innogysmarthome/README.md
+++ b/bundles/org.openhab.binding.innogysmarthome/README.md
@@ -3,7 +3,13 @@
The binding integrates the [innogy SmartHome](https://innogy.com/smarthome) system into openHAB.
It uses the official API 1.1 as provided by innogy as cloud service.
As all status updates and commands have to go through the API, a permanent internet connection is required.
-Currently there is no API for a direct communication with the innogy SmartHome Controller (SHC).
+
+*Notice!*
+
+*This binding is deprecated!*
+
+*LIVISI (formally innogy) has implemented a local API on their SHC from Software Version 1.2.XX.XXX.
+Please migrate to the "LIVISI SmartHome Binding" which is using the new local API and requires neither the LIVISI-cloud-servers nor an internet connection.*
## Supported things
diff --git a/bundles/org.openhab.binding.innogysmarthome/src/main/java/org/openhab/binding/innogysmarthome/internal/handler/InnogyBridgeHandler.java b/bundles/org.openhab.binding.innogysmarthome/src/main/java/org/openhab/binding/innogysmarthome/internal/handler/InnogyBridgeHandler.java
index a5a269587a4..de13ff91c27 100644
--- a/bundles/org.openhab.binding.innogysmarthome/src/main/java/org/openhab/binding/innogysmarthome/internal/handler/InnogyBridgeHandler.java
+++ b/bundles/org.openhab.binding.innogysmarthome/src/main/java/org/openhab/binding/innogysmarthome/internal/handler/InnogyBridgeHandler.java
@@ -140,6 +140,9 @@ public class InnogyBridgeHandler extends BaseBridgeHandler
@Override
public void initialize() {
logger.debug("Initializing innogy SmartHome BridgeHandler...");
+ logger.warn(
+ "The innogy SmartHome binding is deprecated and discontinued and will be removed with the next release of OpenHAB! Please migrate to the newer LIVISI SmartHome binding (which uses a local API which requires no backend servers and no internet connection).");
+
final InnogyBridgeConfiguration bridgeConfiguration = getConfigAs(InnogyBridgeConfiguration.class);
if (checkConfig(bridgeConfiguration)) {
this.bridgeConfiguration = bridgeConfiguration;
diff --git a/bundles/org.openhab.binding.livisismarthome/NOTICE b/bundles/org.openhab.binding.livisismarthome/NOTICE
new file mode 100644
index 00000000000..38d625e3492
--- /dev/null
+++ b/bundles/org.openhab.binding.livisismarthome/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.livisismarthome/README.md b/bundles/org.openhab.binding.livisismarthome/README.md
new file mode 100644
index 00000000000..aa51c521af1
--- /dev/null
+++ b/bundles/org.openhab.binding.livisismarthome/README.md
@@ -0,0 +1,214 @@
+# LIVISI SmartHome Binding
+
+The binding integrates the [LIVISI (RWE/innogy) SmartHome](https://www.livisi.de) system into openHAB.
+The binding is the successor of the innogy SmartHome openHAB binding, which was communicating with the LIVISI cloud servers over the Internet.
+
+This binding communicates directly with LIVISI SmartHome Controllers (SHC) and not through the LIVISI cloud services.
+
+On your SHC you need a minimum software version of 1.2.XX.XXX (SHC 2) or 1.914-3.1.XXXX.XX (SHC 1 / classic) with activated "Local SmartHome".
+
+## Supported things
+
+### Bridge
+
+The LIVISI SmartHome Controller (SHC) is the bridge, that provides the central communication with the devices.
+Without the SHC, you cannot communicate with the devices.
+
+### Devices
+
+The following table shows all supported and tested devices and their channels.
+The channels are described in detail in the next chapter.
+
+| Device | Description | Supported channels |
+|--------|--------------------------------------------------------------------------|------------------------------------------------------------------------------------------------------------------------------|
+| SHC | SmartHome Controller (Bridge) | status, cpu, disk, memory (updated by events; SHC classic: Updated every minute) |
+| BRC8 | Basic Remote Controller | button1 ... button8, button1Count ... button8Count, batteryLow |
+| ISC2 | In Wall Smart Controller | button1, button2, button1Count, button2Count, batteryLow |
+| ISD2 | In Wall Smart Dimmer | button1, button2, button1Count, button2Count, dimmer |
+| ISR2 | In Wall Smart Rollershutter | button1, button2, button1Count, button2Count, rollershutter |
+| ISS2 | In Wall Smart Switch | button1, button2, button1Count, button2Count, switch |
+| PSD | Pluggable Smart Dimmer | dimmer |
+| PSS | Pluggable Smart Switch, indoor | switch |
+| PSSO | Pluggable Smart Switch, outdoor | switch |
+| BT-PSS | Bluetooth Pluggable Smart Switch, indoor | switch |
+| RST | Radiator Mounted Smart Thermostat | targetTemperature, currentTemperature, frostWarning, humidity, moldWarning, operationMode, windowReductionActive, batteryLow |
+| RST2 | Radiator Mounted Smart Thermostat (newer two battery version since 2018) | targetTemperature, currentTemperature, frostWarning, humidity, moldWarning, operationMode, windowReductionActive, batteryLow |
+| | VariableActuator | switch |
+| WDS | Window Door Sensor | contact, batteryLow |
+| WMD | Wall Mounted Motion Detector, indoor | motionCount, luminance, batteryLow |
+| WMDO | Wall Mounted Motion Detector, outdoor | motionCount, luminance, batteryLow |
+| WRT | Wall Mounted Room Thermostat | targetTemperature, currentTemperature, frostWarning, humidity, moldWarning, operationMode, windowReductionActive, batteryLow |
+| WSC2 | Wall Mounted Smart Controller | button1, button2, button1Count, button2Count, batteryLow |
+| WSD | Wall Mounted Smoke Detector, old version | smoke, alarm, batteryLow |
+| WSD2 | Wall Mounted Smoke Detector, new version | smoke, alarm, batteryLow |
+
+Powermeter devices
+
+| Device | Description | Supported channels |
+|-----------------|----------------------------------------------------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
+| AnalogMeter | The Analog Meter from the LIVISI EnergyControl product | energyConsumptionMonthKwh, absoluteEnergyConsumption, energyConsumptionMonthEuro, energyConsumptionDayEuro, energyConsumptionDayKwh |
+| GenerationMeter | The Generation Meter from the LIVISI PowerControlSolar product | energyGenerationMonthKwh, totalEnergyGeneration, energyGenerationMonthEuro, energyGenerationDayEuro, energyGenerationDayKwh, powerGenerationWatt |
+| SmartMeter | The Smart Meter from the LIVISI PowerControl product. | energyConsumptionMonthKwh, absoluteEnergyConsumption, energyConsumptionMonthEuro, energyConsumptionDayEuro, energyConsumptionDayKwh, powerConsumptionWatt |
+| Two-Way-Meter | The Two-Way-Meter from the LIVISI PowerControlSolar product | energyMonthKwh, totalEnergy, energyMonthEuro, energyDayEuro, energyDayKwh, energyFeedMonthKwh, totalEnergyFed, energyFeedMonthEuro, energyFeedDayEuro, energyFeedDayKwh, powerWatt |
+
+## Discovery
+
+The bridge (SHC) can not be discovered automatically. It must be added manually (see below under "Configuration").
+
+After the bridge is added, devices are discovered automatically.
+As there is no background discovery implemented at the moment, you have to start the discovery manually.
+However, only devices will appear that are added in the LIVISI SmartHome app before, because the LIVISI Binding does not support the coupling of devices to the bridge.
+
+## Channels
+
+| Channel Type ID | Item Type | Description | Available on thing |
+|-----------------------|---------------|---------------------------------------------------------------------------|-------------------------------------------------------------|
+| alarm | Switch | Switches the alarm (ON/OFF) | WSD, WSD2 |
+| batteryLow | Switch | Indicates, if the battery is low (ON/OFF) | BRC8, ISC2, RST, RST2, WDS, WMD, WMD0, WRT, WSC2, WSD, WSD2 |
+| contact | Contact | Indicates the contact state (OPEN/CLOSED) | WDS |
+| cpu | Number | CPU-Usage of the SHC in percent | SHC (bridge) |
+| dimmer | Dimmer | Allows to dimm a light device | ISD2, PSD |
+| disk | Number | Disk-Usage of the SHC in percent | SHC (bridge) |
+| frostWarning | Switch | active, if the measured temperature is too low (ON/OFF) | RST, RST2, WRT |
+| humidity | Number | Relative humidity in percent | RST, RST2, WRT |
+| button1 | - | Trigger channel for rules, fires with each push | BRC8, ISC2, ISD2, ISR2, ISS2, WSC2 |
+| button2 | - | Trigger channel for rules, fires with each push | BRC8, ISC2, ISD2, ISR2, ISS2, WSC2 |
+| button3 | - | Trigger channel for rules, fires with each push | BRC8 |
+| button4 | - | Trigger channel for rules, fires with each push | BRC8 |
+| button5 | - | Trigger channel for rules, fires with each push | BRC8 |
+| button6 | - | Trigger channel for rules, fires with each push | BRC8 |
+| button7 | - | Trigger channel for rules, fires with each push | BRC8 |
+| button8 | - | Trigger channel for rules, fires with each push | BRC8 |
+| button1Count | Number | Number of button pushes for button 1, increased with each push | BRC8, ISC2, ISD2, ISR2, ISS2, WSC2 |
+| button2Count | Number | Number of button pushes for button 2, increased with each push | BRC8, ISC2, ISD2, ISR2, ISS2, WSC2 |
+| button3Count | Number | Number of button pushes for button 3, increased with each push | BRC8 |
+| button4Count | Number | Number of button pushes for button 4, increased with each push | BRC8 |
+| button5Count | Number | Number of button pushes for button 5, increased with each push | BRC8 |
+| button6Count | Number | Number of button pushes for button 6, increased with each push | BRC8 |
+| button7Count | Number | Number of button pushes for button 7, increased with each push | BRC8 |
+| button8Count | Number | Number of button pushes for button 8, increased with each push | BRC8 |
+| luminance | Number | Indicates the measured luminance in percent | WMD, WMD0 |
+| memory | Number | Memory-Usage of the SHC in percent | SHC (bridge) |
+| moldWarning | Switch | Active, if the measured humidity is too low (ON/OFF) | RST, RST2, WRT |
+| motionCount | Number | Number of detected motions, increases with each detected motion | WMD, WMDO |
+| operationMode | String | The mode of a thermostat (auto/manual) | RST, RST2, WRT |
+| rollershutter | Rollershutter | Controls a roller shutter | ISR2 |
+| targetTemperature | Number | Sets the target temperature in °C (min 6 °C, max 30 °C) | RST, RST2, WRT |
+| smoke | Switch | Indicates, if smoke was detected (ON/OFF) | WSD, WSD2 |
+| status | String | Status of the SHC (ACTIVE/NORMAL, INITIALIZING/REBOOTING or SHUTTINGDOWN) | SHC (bridge) |
+| switch | Switch | A switch to turn the device or variable on/off (ON/OFF) | ISS2, PSS, PSSO, VariableActuator |
+| currentTemperature | Number | Holds the actual temperature in °C | RST, RST2, WRT |
+| windowReductionActive | Switch | Indicates if a linked window is open and temperature reduced (ON/OFF) | RST, RST2, WRT |
+
+The `rollershutter` channel has a `boolean` parameter `invert`.
+It is `false` by default.
+This means `100` on LIVISI is `UP` and `0` is `DOWN`.
+When `invert` is `true` than `0` on LIVISI is `UP` and `100` is `DOWN`.
+
+
+## Triggers
+
+| Trigger Type | Description | Available on thing |
+|---------------|-----------------------------------------------|-------------------------------------|
+| SHORT_PRESSED | Fired when you press a button short | BRC8, ISC2, ISD2, ISR2, ISS2, WSC2 |
+| LONG_PRESSED | Fired when you press a button longer | BRC8, ISC2, ISD2, ISR2, ISS2, WSC2 |
+| PRESSED | Fired when you press a button (short or long) | BRC8, ISC2, ISD2, ISR2, ISS2, WSC2 |
+
+
+## Thing configuration
+
+### Configuring the SmartHome Controller (SHC)
+
+The SmartHome Controller (SHC) can be configured in the UI as follows:
+
+When adding the "LIVISI SmartHome Controller" via the Inbox, you have to define the hostname or local IP address and the password for the local user.
+Save your changes. The SHC should now login and go online.
+
+### Discovering devices
+
+All devices bound to the bridge are found by the discovery service once the SHC is online.
+As device discovery is not implemented as a background service, you should start it manually in the Inbox to find all devices.
+Now you can add all devices from your Inbox as things.
+
+### File based configuration
+
+As an alternative to the automatic discovery process and graphical configuration using the UI, LIVISI things can be configured manually.
+The LIVISI SmartHome Controller (SHC) can be configured using the following syntax:
+
+```
+Bridge livisismarthome:bridge: "Livisi: SmartHome Controller (SHC)" [ host="192.168.0.99", password="SomethingSecret", webSocketIdleTimeout=900]
+```
+
+** *Security warning!**
+The communication between the binding and the SHC is not encrypted and can be traced.
+So be careful and secure your local network from unauthorized access.
+
+All other LIVISI devices can be added using the following syntax:
+
+```
+Thing WDS "" @ "" [ id="" ]
+```
+
+The device ID (e.g. e9a74941a3807b57332214f346fb1129) can be found in the UI inbox, as you find it below all things there in the form `livisismarthome:::` (example: `livisismarthome:WSC2:SMARTHOME01:e9a74941a3807b57332214f346fb1129`).
+
+However, a full example .things configuration look like this:
+
+```
+Bridge livisismarthome:bridge:mybride "LIVISI SmartHome Controller" {
+ Thing ISD2 myDimmer "Dimmer Kitchen" @ "Kitchen" [ id="" ]
+ Thing ISS2 myLightSwitch "Light Livingroom" @ "Livingroom" [ id="" ]
+ Thing PSS myTVSwitch "TV" @ "Livingroom" [ id="" ]
+ Thing RST myHeating "Thermostat Livingroom" @ "Livingroom" [ id="" ]
+ Thing ISR2 myRollerShutter1 "RollerShutter" @ "Livingroom" [ id="" ]
+ Thing ISR2 myRollerShutter2 "RollerShutter (inverted)" @ "Livingroom" [ id="" ] {Type rollershutterActuator : rollershutter [invert=true]}
+ Thing VariableActuator myLivisiVariable "My Variable" [ id="" ]
+ Thing WDS myWindowContact "Window Kitchen" @ "Kitchen" [ id="" ]
+ Thing WMD myMotionSensor "Motion entry" @ "Entry" [ id="" ]
+ Thing WSC2 myPushButton "Pushbutton" @ "Living" [ id="" ]
+ Thing WSD mySmokeDetector "Smoke detector Livingroom" @ "Living" [ id="" ]
+}
+```
+
+## Items configuration
+
+You can then configure your items in your *.items config files as usual, for example:
+
+```
+Contact myWindowContact "Kitchen" {channel="livisismarthome:WDS:mybridge:myWindowContact:contact"}
+Switch myWindowContactBattery "Battery low" {channel="livisismarthome:WDS:mybridge:myWindowContact:batteryLow"}
+Number myHeatingTemp "Bath [%.1f °C]" {channel="livisismarthome:RST:mybridge:myHeating:currentTemperature"}
+Number myHeatingModeTempTarget "Settemp bath [%.1f °C]" {channel="livisismarthome:RST:mybridge:myHeating:targetTemperature"}
+String myHeatingMode "Mode bath [%s]" {channel="livisismarthome:RST:mybridge:myHeating:operationMode"}
+Number myHeatingHumidity "Bath [%.1f %%]" {channel="livisismarthome:RST:mybridge:myHeating:humidity"}
+
+```
+
+## Sitemap configuration
+
+Example:
+
+```
+sitemap default label="Home" {
+ Frame {
+ Text item=myHeatingTemp label="Temperature"
+ Text item=myHeatingHumidity label="Humidity"
+ Switch item=myHeatingMode label="Mode" mappings=[Manu="Manual", Auto="Auto"]
+ Setpoint item=myHeatingModeTempTarget label="Target temperature" minValue=16 maxValue=25 step=1
+ }
+}
+```
+
+## Rules example for push-buttons
+
+Push-buttons provide trigger channels, that can only be used in rules.
+Here is an example rule:
+
+```
+rule "Button triggered rule"
+when
+ Channel 'livisismarthome:WSC2:mybridge:myPushButton:button1' triggered PRESSED
+then
+ // do something...
+ logInfo("testlogger", "Button 1 pressed")
+end
+```
diff --git a/bundles/org.openhab.binding.livisismarthome/pom.xml b/bundles/org.openhab.binding.livisismarthome/pom.xml
new file mode 100644
index 00000000000..61f8ee6db06
--- /dev/null
+++ b/bundles/org.openhab.binding.livisismarthome/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.livisismarthome
+
+ openHAB Add-ons :: Bundles :: LIVISI SmartHome Binding
+
+
diff --git a/bundles/org.openhab.binding.livisismarthome/src/main/feature/feature.xml b/bundles/org.openhab.binding.livisismarthome/src/main/feature/feature.xml
new file mode 100644
index 00000000000..df0f2725bf4
--- /dev/null
+++ b/bundles/org.openhab.binding.livisismarthome/src/main/feature/feature.xml
@@ -0,0 +1,10 @@
+
+
+ mvn:org.openhab.core.features.karaf/org.openhab.core.features.karaf.openhab-core/${ohc.version}/xml/features
+
+
+ openhab-runtime-base
+ openhab-transport-http
+ mvn:org.openhab.addons.bundles/org.openhab.binding.livisismarthome/${project.version}
+
+
diff --git a/bundles/org.openhab.binding.livisismarthome/src/main/java/org/openhab/binding/livisismarthome/internal/LivisiBindingConstants.java b/bundles/org.openhab.binding.livisismarthome/src/main/java/org/openhab/binding/livisismarthome/internal/LivisiBindingConstants.java
new file mode 100644
index 00000000000..8f04408e246
--- /dev/null
+++ b/bundles/org.openhab.binding.livisismarthome/src/main/java/org/openhab/binding/livisismarthome/internal/LivisiBindingConstants.java
@@ -0,0 +1,184 @@
+/**
+ * Copyright (c) 2010-2022 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.livisismarthome.internal;
+
+import java.util.Set;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.openhab.core.thing.ThingTypeUID;
+
+/**
+ * The {@link LivisiBindingConstants} class defines common constants, which are
+ * used across the whole binding.
+ *
+ * @author Oliver Kuhl - Initial contribution
+ * @author Sven Strohschein - Renamed from Innogy to Livisi
+ */
+@NonNullByDefault
+public class LivisiBindingConstants {
+
+ public static final String BINDING_ID = "livisismarthome";
+
+ public static final String USERNAME = "admin";
+
+ public static final long REINITIALIZE_DELAY_SECONDS = 30;
+ public static final long BRIDGE_REFRESH_SECONDS = 60;
+
+ // properties
+ public static final String PROPERTY_ID = "id";
+ public static final String PROPERTY_VERSION = "version";
+ public static final String PROPERTY_LOCATION = "location";
+ public static final String PROPERTY_GEOLOCATION = "geoLocation";
+ public static final String PROPERTY_SOFTWARE_VERSION = "softwareVersion";
+ public static final String PROPERTY_IP_ADDRESS = "ipAddress";
+ public static final String PROPERTY_REGISTRATION_TIME = "registrationTime";
+ public static final String PROPERTY_TIME_OF_ACCEPTANCE = "timeOfAcceptance";
+ public static final String PROPERTY_TIME_OF_DISCOVERY = "timeOfDiscovery";
+ public static final String PROPERTY_BATTERY_POWERED = "batteryPowered";
+ public static final String PROPERTY_DEVICE_TYPE = "deviceType";
+ public static final String PROPERTY_CONFIGURATION_STATE = "configurationState";
+ public static final String PROPERTY_SHC_TYPE = "controllerType";
+ public static final String PROPERTY_TIME_ZONE = "timeZone";
+ public static final String PROPERTY_CURRENT_UTC_OFFSET = "currentUtcOffsetInMinutes";
+ public static final String PROPERTY_PROTOCOL_ID = "protocolId";
+ public static final String PROPERTY_BACKEND_CONNECTION_MONITORED = "backendConnectionMonitored";
+ public static final String PROPERTY_RFCOM_FAILURE_NOTIFICATION = "rfcomFailureNotification";
+ public static final String PROPERTY_DISPLAY_CURRENT_TEMPERATURE = "displayCurrentTemperature";
+ public static final String PROPERTY_METER_ID = "meterId";
+ public static final String PROPERTY_METER_FIRMWARE_VERSION = "meterFirmwareVersion";
+
+ // List of main device types
+ public static final String DEVICE_SHC = "SHC"; // smarthome controller - the bridge
+ public static final String DEVICE_SHCA = "SHCA"; // smarthome controller version 2
+ public static final String DEVICE_PSS = "PSS"; // pluggable smart switch
+ public static final String DEVICE_PSSO = "PSSO"; // pluggable smart switch outdoor
+ public static final String DEVICE_BT_PSS = "BT-PSS"; // Bluetooth pluggable smart switch
+ public static final String DEVICE_VARIABLE_ACTUATOR = "VariableActuator";
+ public static final String DEVICE_RST = "RST"; // radiator mounted smart thermostat
+ public static final String DEVICE_RST2 = "RST2"; // radiator mounted smart thermostat (newer version)
+ public static final String DEVICE_WRT = "WRT"; // wall mounted room thermostat
+ public static final String DEVICE_WDS = "WDS"; // window door sensor
+ public static final String DEVICE_ISS2 = "ISS2"; // inwall smart switch
+ public static final String DEVICE_WSD = "WSD"; // wall mounted smoke detector
+ public static final String DEVICE_WSD2 = "WSD2"; // wall mounted smoke detector
+ public static final String DEVICE_WMD = "WMD"; // wall mounted motion detector indoor
+ public static final String DEVICE_WMDO = "WMDO"; // wall mounted motion detector outdoor
+ public static final String DEVICE_WSC2 = "WSC2"; // wall mounted smart controller (2 buttons)
+ public static final String DEVICE_BRC8 = "BRC8"; // basic remote controller (8 buttons)
+ public static final String DEVICE_ISC2 = "ISC2"; // in wall smart controller (2 buttons)
+ public static final String DEVICE_ISD2 = "ISD2"; // in wall smart dimmer (2 buttons)
+ public static final String DEVICE_ISR2 = "ISR2"; // in wall smart rollershutter (2 buttons)
+ public static final String DEVICE_PSD = "PSD"; // pluggable smart dimmer
+ public static final String DEVICE_ANALOG_METER = "AnalogMeter";
+ public static final String DEVICE_GENERATION_METER = "GenerationMeter";
+ public static final String DEVICE_SMART_METER = "SmartMeter";
+ public static final String DEVICE_TWO_WAY_METER = "TwoWayMeter";
+
+ public static final Set SUPPORTED_DEVICES = Set.of(DEVICE_SHC, DEVICE_SHCA, DEVICE_PSS, DEVICE_PSSO,
+ DEVICE_BT_PSS, DEVICE_VARIABLE_ACTUATOR, DEVICE_RST, DEVICE_RST2, DEVICE_WRT, DEVICE_WDS, DEVICE_ISS2,
+ DEVICE_WSD, DEVICE_WSD2, DEVICE_WMD, DEVICE_WMDO, DEVICE_WSC2, DEVICE_BRC8, DEVICE_ISC2, DEVICE_ISD2,
+ DEVICE_ISR2, DEVICE_PSD, DEVICE_ANALOG_METER, DEVICE_GENERATION_METER, DEVICE_SMART_METER,
+ DEVICE_TWO_WAY_METER);
+
+ public static final Set BATTERY_POWERED_DEVICES = Set.of(DEVICE_ISC2, DEVICE_RST, DEVICE_RST2, DEVICE_WRT,
+ DEVICE_WDS, DEVICE_WSD, DEVICE_WSD2, DEVICE_WMD, DEVICE_WMDO, DEVICE_WSC2, DEVICE_BRC8);
+
+ // List of all Thing Type UIDs
+ public static final ThingTypeUID THING_TYPE_BRIDGE = new ThingTypeUID(BINDING_ID, "bridge");
+ public static final ThingTypeUID THING_TYPE_PSS = new ThingTypeUID(BINDING_ID, DEVICE_PSS);
+ public static final ThingTypeUID THING_TYPE_PSSO = new ThingTypeUID(BINDING_ID, DEVICE_PSSO);
+ public static final ThingTypeUID THING_TYPE_BT_PSS = new ThingTypeUID(BINDING_ID, DEVICE_BT_PSS);
+ public static final ThingTypeUID THING_TYPE_VARIABLE_ACTUATOR = new ThingTypeUID(BINDING_ID,
+ DEVICE_VARIABLE_ACTUATOR);
+ public static final ThingTypeUID THING_TYPE_RST = new ThingTypeUID(BINDING_ID, DEVICE_RST);
+ public static final ThingTypeUID THING_TYPE_RST2 = new ThingTypeUID(BINDING_ID, DEVICE_RST2);
+ public static final ThingTypeUID THING_TYPE_WRT = new ThingTypeUID(BINDING_ID, DEVICE_WRT);
+ public static final ThingTypeUID THING_TYPE_WDS = new ThingTypeUID(BINDING_ID, DEVICE_WDS);
+ public static final ThingTypeUID THING_TYPE_ISS2 = new ThingTypeUID(BINDING_ID, DEVICE_ISS2);
+ public static final ThingTypeUID THING_TYPE_WSD = new ThingTypeUID(BINDING_ID, DEVICE_WSD);
+ public static final ThingTypeUID THING_TYPE_WSD2 = new ThingTypeUID(BINDING_ID, DEVICE_WSD2);
+ public static final ThingTypeUID THING_TYPE_WMD = new ThingTypeUID(BINDING_ID, DEVICE_WMD);
+ public static final ThingTypeUID THING_TYPE_WMDO = new ThingTypeUID(BINDING_ID, DEVICE_WMDO);
+ public static final ThingTypeUID THING_TYPE_WSC2 = new ThingTypeUID(BINDING_ID, DEVICE_WSC2);
+ public static final ThingTypeUID THING_TYPE_BRC8 = new ThingTypeUID(BINDING_ID, DEVICE_BRC8);
+ public static final ThingTypeUID THING_TYPE_ISC2 = new ThingTypeUID(BINDING_ID, DEVICE_ISC2);
+ public static final ThingTypeUID THING_TYPE_ISD2 = new ThingTypeUID(BINDING_ID, DEVICE_ISD2);
+ public static final ThingTypeUID THING_TYPE_ISR2 = new ThingTypeUID(BINDING_ID, DEVICE_ISR2);
+ public static final ThingTypeUID THING_TYPE_PSD = new ThingTypeUID(BINDING_ID, DEVICE_PSD);
+ public static final ThingTypeUID THING_TYPE_ANALOG_METER = new ThingTypeUID(BINDING_ID, DEVICE_ANALOG_METER);
+ public static final ThingTypeUID THING_TYPE_GENERATION_METER = new ThingTypeUID(BINDING_ID,
+ DEVICE_GENERATION_METER);
+ public static final ThingTypeUID THING_TYPE_SMART_METER = new ThingTypeUID(BINDING_ID, DEVICE_SMART_METER);
+ public static final ThingTypeUID THING_TYPE_TWO_WAY_METER = new ThingTypeUID(BINDING_ID, DEVICE_TWO_WAY_METER);
+
+ public static final Set SUPPORTED_DEVICE_THING_TYPES = Set.of(THING_TYPE_PSS, THING_TYPE_PSSO,
+ THING_TYPE_BT_PSS, THING_TYPE_VARIABLE_ACTUATOR, THING_TYPE_RST, THING_TYPE_RST2, THING_TYPE_WRT,
+ THING_TYPE_WDS, THING_TYPE_ISS2, THING_TYPE_WSD, THING_TYPE_WSD2, THING_TYPE_WMD, THING_TYPE_WMDO,
+ THING_TYPE_WSC2, THING_TYPE_BRC8, THING_TYPE_ISC2, THING_TYPE_ISD2, THING_TYPE_ISR2, THING_TYPE_PSD,
+ THING_TYPE_ANALOG_METER, THING_TYPE_GENERATION_METER, THING_TYPE_SMART_METER, THING_TYPE_TWO_WAY_METER);
+
+ public static final Set SUPPORTED_THING_TYPES = Stream
+ .concat(Stream.of(THING_TYPE_BRIDGE), SUPPORTED_DEVICE_THING_TYPES.stream()).collect(Collectors.toSet());
+
+ // List of all Channel ids
+ public static final String CHANNEL_SWITCH = "switch";
+ public static final String CHANNEL_TARGET_TEMPERATURE = "targetTemperature";
+ public static final String CHANNEL_CURRENT_TEMPERATURE = "currentTemperature";
+ public static final String CHANNEL_HUMIDITY = "humidity";
+ public static final String CHANNEL_CONTACT = "contact";
+ public static final String CHANNEL_SMOKE = "smoke";
+ public static final String CHANNEL_ALARM = "alarm";
+ public static final String CHANNEL_MOTION_COUNT = "motionCount";
+ public static final String CHANNEL_LUMINANCE = "luminance";
+ public static final String CHANNEL_OPERATION_MODE = "operationMode";
+ public static final String CHANNEL_FROST_WARNING = "frostWarning";
+ public static final String CHANNEL_MOLD_WARNING = "moldWarning";
+ public static final String CHANNEL_WINDOW_REDUCTION_ACTIVE = "windowReductionActive";
+ public static final String CHANNEL_BUTTON = "button";
+ public static final String CHANNEL_BUTTON_COUNT = "button%dCount";
+ public static final String CHANNEL_DIMMER = "dimmer";
+ public static final String CHANNEL_ROLLERSHUTTER = "rollershutter";
+ public static final String CHANNEL_BATTERY_LOW = "batteryLow";
+ public static final String CHANNEL_ENERGY_CONSUMPTION_MONTH_KWH = "energyConsumptionMonthKwh";
+ public static final String CHANNEL_ABOLUTE_ENERGY_CONSUMPTION = "absoluteEnergyConsumption";
+ public static final String CHANNEL_ENERGY_CONSUMPTION_MONTH_EURO = "energyConsumptionMonthEuro";
+ public static final String CHANNEL_ENERGY_CONSUMPTION_DAY_EURO = "energyConsumptionDayEuro";
+ public static final String CHANNEL_ENERGY_CONSUMPTION_DAY_KWH = "energyConsumptionDayKwh";
+ public static final String CHANNEL_POWER_CONSUMPTION_WATT = "powerConsumptionWatt";
+ public static final String CHANNEL_ENERGY_GENERATION_MONTH_KWH = "energyGenerationMonthKwh";
+ public static final String CHANNEL_TOTAL_ENERGY_GENERATION = "totalEnergyGeneration";
+ public static final String CHANNEL_ENERGY_GENERATION_MONTH_EURO = "energyGenerationMonthEuro";
+ public static final String CHANNEL_ENERGY_GENERATION_DAY_EURO = "energyGenerationDayEuro";
+ public static final String CHANNEL_ENERGY_GENERATION_DAY_KWH = "energyGenerationDayKwh";
+ public static final String CHANNEL_POWER_GENERATION_WATT = "powerGenerationWatt";
+ public static final String CHANNEL_ENERGY_MONTH_KWH = "energyMonthKwh";
+ public static final String CHANNEL_TOTAL_ENERGY = "totalEnergy";
+ public static final String CHANNEL_ENERGY_MONTH_EURO = "energyMonthEuro";
+ public static final String CHANNEL_ENERGY_DAY_EURO = "energyDayEuro";
+ public static final String CHANNEL_ENERGY_DAY_KWH = "energyDayKwh";
+ public static final String CHANNEL_ENERGY_FEED_MONTH_KWH = "energyFeedMonthKwh";
+ public static final String CHANNEL_TOTAL_ENERGY_FED = "totalEnergyFed";
+ public static final String CHANNEL_ENERGY_FEED_MONTH_EURO = "energyFeedMonthEuro";
+ public static final String CHANNEL_ENERGY_FEED_DAY_EURO = "energyFeedDayEuro";
+ public static final String CHANNEL_ENERGY_FEED_DAY_KWH = "energyFeedDayKwh";
+ public static final String CHANNEL_POWER_WATT = "powerWatt";
+ public static final String CHANNEL_CPU = "cpu";
+ public static final String CHANNEL_DISK = "disk";
+ public static final String CHANNEL_MEMORY = "memory";
+ public static final String CHANNEL_OPERATION_STATUS = "status";
+
+ // List of channel parameters
+ public static final String INVERT_CHANNEL_PARAMETER = "invert";
+}
diff --git a/bundles/org.openhab.binding.livisismarthome/src/main/java/org/openhab/binding/livisismarthome/internal/LivisiHandlerFactory.java b/bundles/org.openhab.binding.livisismarthome/src/main/java/org/openhab/binding/livisismarthome/internal/LivisiHandlerFactory.java
new file mode 100644
index 00000000000..2a6f19950e2
--- /dev/null
+++ b/bundles/org.openhab.binding.livisismarthome/src/main/java/org/openhab/binding/livisismarthome/internal/LivisiHandlerFactory.java
@@ -0,0 +1,77 @@
+/**
+ * Copyright (c) 2010-2022 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.livisismarthome.internal;
+
+import static org.openhab.binding.livisismarthome.internal.LivisiBindingConstants.*;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.eclipse.jdt.annotation.Nullable;
+import org.eclipse.jetty.client.HttpClient;
+import org.openhab.binding.livisismarthome.internal.handler.LivisiBridgeHandler;
+import org.openhab.binding.livisismarthome.internal.handler.LivisiDeviceHandler;
+import org.openhab.core.auth.client.oauth2.OAuthFactory;
+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;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * The {@link LivisiHandlerFactory} is responsible for creating things and thing
+ * handlers.
+ *
+ * @author Oliver Kuhl - Initial contribution
+ * @author Hilbrand Bouwkamp - Refactored to use openHAB http and oauth2 libraries
+ * @author Sven Strohschein - Renamed from Innogy to Livisi
+ */
+@Component(service = ThingHandlerFactory.class, configurationPid = "binding.livisismarthome")
+@NonNullByDefault
+public class LivisiHandlerFactory extends BaseThingHandlerFactory implements ThingHandlerFactory {
+
+ private final Logger logger = LoggerFactory.getLogger(LivisiHandlerFactory.class);
+
+ private final OAuthFactory oAuthFactory;
+ private final HttpClient httpClient;
+
+ @Activate
+ public LivisiHandlerFactory(@Reference OAuthFactory oAuthFactory, @Reference HttpClientFactory httpClientFactory) {
+ this.oAuthFactory = oAuthFactory;
+ this.httpClient = httpClientFactory.getCommonHttpClient();
+ }
+
+ @Override
+ public boolean supportsThingType(ThingTypeUID thingTypeUID) {
+ return SUPPORTED_THING_TYPES.contains(thingTypeUID);
+ }
+
+ @Override
+ protected @Nullable ThingHandler createHandler(Thing thing) {
+ ThingTypeUID thingTypeUID = thing.getThingTypeUID();
+ if (THING_TYPE_BRIDGE.equals(thingTypeUID) || SUPPORTED_DEVICE_THING_TYPES.contains(thingTypeUID)) {
+ if (THING_TYPE_BRIDGE.equals(thingTypeUID)) {
+ return new LivisiBridgeHandler((Bridge) thing, oAuthFactory, httpClient);
+ } else if (SUPPORTED_DEVICE_THING_TYPES.contains(thingTypeUID)) {
+ return new LivisiDeviceHandler(thing);
+ }
+ }
+ logger.debug("Unsupported thing {}.", thingTypeUID);
+ return null;
+ }
+}
diff --git a/bundles/org.openhab.binding.livisismarthome/src/main/java/org/openhab/binding/livisismarthome/internal/LivisiWebSocket.java b/bundles/org.openhab.binding.livisismarthome/src/main/java/org/openhab/binding/livisismarthome/internal/LivisiWebSocket.java
new file mode 100644
index 00000000000..d4cad30a16b
--- /dev/null
+++ b/bundles/org.openhab.binding.livisismarthome/src/main/java/org/openhab/binding/livisismarthome/internal/LivisiWebSocket.java
@@ -0,0 +1,189 @@
+/**
+ * Copyright (c) 2010-2022 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.livisismarthome.internal;
+
+import java.io.IOException;
+import java.net.URI;
+import java.util.concurrent.ExecutionException;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.eclipse.jdt.annotation.Nullable;
+import org.eclipse.jetty.client.HttpClient;
+import org.eclipse.jetty.websocket.api.Session;
+import org.eclipse.jetty.websocket.api.StatusCode;
+import org.eclipse.jetty.websocket.api.annotations.OnWebSocketClose;
+import org.eclipse.jetty.websocket.api.annotations.OnWebSocketConnect;
+import org.eclipse.jetty.websocket.api.annotations.OnWebSocketError;
+import org.eclipse.jetty.websocket.api.annotations.OnWebSocketMessage;
+import org.eclipse.jetty.websocket.api.annotations.WebSocket;
+import org.eclipse.jetty.websocket.client.WebSocketClient;
+import org.openhab.binding.livisismarthome.internal.client.exception.WebSocketConnectException;
+import org.openhab.binding.livisismarthome.internal.listener.EventListener;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * The {@link LivisiWebSocket} implements the websocket for receiving constant updates
+ * from the LIVISI SmartHome web service.
+ *
+ * @author Oliver Kuhl - Initial contribution
+ * @author Sven Strohschein - Renamed from Innogy to Livisi
+ */
+@NonNullByDefault
+@WebSocket
+public class LivisiWebSocket {
+
+ private final Logger logger = LoggerFactory.getLogger(LivisiWebSocket.class);
+
+ private final HttpClient httpClient;
+ private final EventListener eventListener;
+ private final URI webSocketURI;
+ private final int maxIdleTimeout;
+
+ private WebSocketClient client;
+ private @Nullable Session session;
+ private boolean closing;
+
+ /**
+ * Constructs the {@link LivisiWebSocket}.
+ *
+ * @param eventListener the responsible
+ * {@link org.openhab.binding.livisismarthome.internal.handler.LivisiBridgeHandler}
+ * @param webSocketURI the {@link URI} of the websocket endpoint
+ * @param maxIdleTimeout max idle timeout
+ */
+ public LivisiWebSocket(HttpClient httpClient, EventListener eventListener, URI webSocketURI, int maxIdleTimeout) {
+ this.httpClient = httpClient;
+ this.eventListener = eventListener;
+ this.webSocketURI = webSocketURI;
+ this.maxIdleTimeout = maxIdleTimeout;
+ this.client = createWebSocketClient();
+ }
+
+ /**
+ * Starts the {@link LivisiWebSocket}.
+ */
+ public synchronized void start() throws WebSocketConnectException {
+ if (client.isStopped()) {
+ client = createWebSocketClient();
+ startWebSocketClient(client);
+ }
+
+ session = connectWebSocket(session);
+ }
+
+ private Session connectWebSocket(@Nullable Session session) throws WebSocketConnectException {
+ closeSession(session);
+ this.session = null;
+
+ logger.debug("Connecting to LIVISI SmartHome WebSocket...");
+ try {
+ return client.connect(this, webSocketURI).get();
+ } catch (IOException | InterruptedException | ExecutionException e) {
+ throw new WebSocketConnectException("The WebSocket couldn't get connected!", e);
+ }
+ }
+
+ /**
+ * Stops the {@link LivisiWebSocket}.
+ */
+ public synchronized void stop() {
+ this.closing = true;
+ if (isRunning()) {
+ closeSession(session);
+ } else {
+ logger.trace("Stopping websocket ignored - was not running.");
+ }
+ session = null;
+ stopWebSocketClient(client);
+ client = createWebSocketClient();
+ }
+
+ private void closeSession(@Nullable Session session) {
+ if (session != null) {
+ logger.debug("Closing session...");
+ session.close();
+ }
+ }
+
+ /**
+ * Return true, if the websocket is running.
+ *
+ * @return true if the websocket is running, otherwise false
+ */
+ public synchronized boolean isRunning() {
+ return isRunning(session);
+ }
+
+ private boolean isRunning(@Nullable Session session) {
+ return session != null && session.isOpen();
+ }
+
+ @OnWebSocketConnect
+ public void onConnect(Session session) {
+ this.closing = false;
+ logger.debug("Connected to LIVISI SmartHome webservice.");
+ logger.trace("LIVISI SmartHome websocket session: {}", session);
+ }
+
+ @OnWebSocketClose
+ public void onClose(int statusCode, String reason) {
+ if (statusCode == StatusCode.NORMAL) {
+ logger.debug("Connection to LIVISI SmartHome webservice was closed normally.");
+ } else if (!closing) {
+ // An additional reconnect attempt is only required when the close/stop wasn't executed by the binding.
+ logger.debug("Connection to LIVISI SmartHome webservice was closed abnormally (code: {}). Reason: {}",
+ statusCode, reason);
+ eventListener.connectionClosed();
+ }
+ }
+
+ @OnWebSocketError
+ public void onError(Throwable cause) {
+ logger.debug("LIVISI SmartHome websocket onError() - {}", cause.getMessage());
+ eventListener.onError(cause);
+ }
+
+ @OnWebSocketMessage
+ public void onMessage(String msg) {
+ logger.debug("LIVISI SmartHome websocket onMessage() - {}", msg);
+ if (closing) {
+ logger.debug("LIVISI SmartHome websocket onMessage() - ignored, WebSocket is closing...");
+ } else {
+ eventListener.onEvent(msg);
+ }
+ }
+
+ WebSocketClient createWebSocketClient() {
+ WebSocketClient client = new WebSocketClient(httpClient);
+ client.setMaxIdleTimeout(maxIdleTimeout);
+ return client;
+ }
+
+ void startWebSocketClient(WebSocketClient client) throws WebSocketConnectException {
+ try {
+ client.start();
+ } catch (Exception e) {
+ throw new WebSocketConnectException("Starting WebSocket failed!", e);
+ }
+ }
+
+ void stopWebSocketClient(WebSocketClient client) {
+ try {
+ client.stop();
+ client.destroy();
+ } catch (Exception e) {
+ logger.debug("Stopping WebSocket failed", e);
+ }
+ }
+}
diff --git a/bundles/org.openhab.binding.livisismarthome/src/main/java/org/openhab/binding/livisismarthome/internal/client/GsonOptional.java b/bundles/org.openhab.binding.livisismarthome/src/main/java/org/openhab/binding/livisismarthome/internal/client/GsonOptional.java
new file mode 100644
index 00000000000..5c65c9a2a3d
--- /dev/null
+++ b/bundles/org.openhab.binding.livisismarthome/src/main/java/org/openhab/binding/livisismarthome/internal/client/GsonOptional.java
@@ -0,0 +1,54 @@
+/**
+ * Copyright (c) 2010-2022 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.livisismarthome.internal.client;
+
+import java.util.Optional;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.eclipse.jdt.annotation.Nullable;
+
+import com.google.gson.Gson;
+import com.google.gson.GsonBuilder;
+import com.google.gson.JsonSyntaxException;
+
+/**
+ * The {@link GsonOptional} supports non-null-compatible methods to use gson.
+ *
+ * @author Sven Strohschein - Initial contribution
+ */
+@NonNullByDefault
+public class GsonOptional {
+
+ /**
+ * date format as used in json in API. Example: 2016-07-11T10:55:52.3863424Z
+ */
+ private static final String DATE_FORMAT = "yyyy-MM-dd'T'HH:mm:ss.SSSZ";
+
+ private final Gson gson;
+
+ public GsonOptional() {
+ gson = new GsonBuilder().setDateFormat(DATE_FORMAT).create();
+ }
+
+ public Optional fromJson(String json, Class clazz) throws JsonSyntaxException {
+ return Optional.ofNullable(fromJsonNullable(json, clazz));
+ }
+
+ public @Nullable T fromJsonNullable(String json, Class clazz) throws JsonSyntaxException {
+ return gson.fromJson(json, clazz);
+ }
+
+ public String toJson(Object src) {
+ return gson.toJson(src);
+ }
+}
diff --git a/bundles/org.openhab.binding.livisismarthome/src/main/java/org/openhab/binding/livisismarthome/internal/client/LivisiClient.java b/bundles/org.openhab.binding.livisismarthome/src/main/java/org/openhab/binding/livisismarthome/internal/client/LivisiClient.java
new file mode 100644
index 00000000000..93aa7650f19
--- /dev/null
+++ b/bundles/org.openhab.binding.livisismarthome/src/main/java/org/openhab/binding/livisismarthome/internal/client/LivisiClient.java
@@ -0,0 +1,424 @@
+/**
+ * Copyright (c) 2010-2022 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.livisismarthome.internal.client;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.io.OutputStream;
+import java.net.HttpURLConnection;
+import java.nio.charset.StandardCharsets;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
+import java.util.Optional;
+import java.util.stream.Collectors;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.eclipse.jdt.annotation.Nullable;
+import org.eclipse.jetty.http.HttpHeader;
+import org.eclipse.jetty.http.HttpMethod;
+import org.eclipse.jetty.http.HttpStatus;
+import org.openhab.binding.livisismarthome.internal.LivisiBindingConstants;
+import org.openhab.binding.livisismarthome.internal.client.api.entity.StatusResponseDTO;
+import org.openhab.binding.livisismarthome.internal.client.api.entity.action.ActionDTO;
+import org.openhab.binding.livisismarthome.internal.client.api.entity.action.ShutterActionDTO;
+import org.openhab.binding.livisismarthome.internal.client.api.entity.action.ShutterActionType;
+import org.openhab.binding.livisismarthome.internal.client.api.entity.action.StateActionSetterDTO;
+import org.openhab.binding.livisismarthome.internal.client.api.entity.capability.CapabilityDTO;
+import org.openhab.binding.livisismarthome.internal.client.api.entity.capability.CapabilityStateDTO;
+import org.openhab.binding.livisismarthome.internal.client.api.entity.device.DeviceDTO;
+import org.openhab.binding.livisismarthome.internal.client.api.entity.device.DeviceStateDTO;
+import org.openhab.binding.livisismarthome.internal.client.api.entity.device.StateDTO;
+import org.openhab.binding.livisismarthome.internal.client.api.entity.error.ErrorResponseDTO;
+import org.openhab.binding.livisismarthome.internal.client.api.entity.location.LocationDTO;
+import org.openhab.binding.livisismarthome.internal.client.api.entity.message.MessageDTO;
+import org.openhab.binding.livisismarthome.internal.client.exception.ApiException;
+import org.openhab.binding.livisismarthome.internal.client.exception.AuthenticationException;
+import org.openhab.binding.livisismarthome.internal.client.exception.ControllerOfflineException;
+import org.openhab.binding.livisismarthome.internal.client.exception.InvalidActionTriggeredException;
+import org.openhab.binding.livisismarthome.internal.client.exception.RemoteAccessNotAllowedException;
+import org.openhab.binding.livisismarthome.internal.client.exception.ServiceUnavailableException;
+import org.openhab.binding.livisismarthome.internal.client.exception.SessionExistsException;
+import org.openhab.binding.livisismarthome.internal.client.exception.SessionNotFoundException;
+import org.openhab.binding.livisismarthome.internal.handler.LivisiBridgeConfiguration;
+import org.openhab.core.auth.client.oauth2.AccessTokenResponse;
+import org.openhab.core.auth.client.oauth2.OAuthClientService;
+import org.openhab.core.auth.client.oauth2.OAuthException;
+import org.openhab.core.auth.client.oauth2.OAuthResponseException;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.google.gson.JsonSyntaxException;
+
+/**
+ * The main client that handles the communication with the LIVISI SmartHome API service.
+ *
+ * @author Oliver Kuhl - Initial contribution
+ * @author Hilbrand Bouwkamp - Refactored to use openHAB http and oauth2 libraries
+ * @author Sven Strohschein - Renamed from Innogy to Livisi and refactored
+ */
+@NonNullByDefault
+public class LivisiClient {
+
+ private final Logger logger = LoggerFactory.getLogger(LivisiClient.class);
+
+ private final GsonOptional gson;
+ private final LivisiBridgeConfiguration bridgeConfiguration;
+ private final OAuthClientService oAuthService;
+ private final URLConnectionFactory connectionFactory;
+
+ public LivisiClient(final LivisiBridgeConfiguration bridgeConfiguration, final OAuthClientService oAuthService,
+ final URLConnectionFactory connectionFactory) {
+ this.bridgeConfiguration = bridgeConfiguration;
+ this.oAuthService = oAuthService;
+ this.connectionFactory = connectionFactory;
+ this.gson = new GsonOptional();
+ }
+
+ /**
+ * Gets the status
+ * As the API returns the details of the SmartHome controller (SHC), the config version is returned.
+ *
+ * @return config version
+ */
+ public String refreshStatus() throws IOException {
+ logger.debug("Get LIVISI SmartHome status...");
+ final Optional status = executeGet(URLCreator.createStatusURL(bridgeConfiguration.host),
+ StatusResponseDTO.class);
+
+ if (status.isPresent()) {
+ String configVersion = status.get().getConfigVersion();
+ logger.debug("LIVISI SmartHome status loaded. Configuration version is {}.", configVersion);
+ return configVersion;
+ }
+ return "";
+ }
+
+ /**
+ * Executes a HTTP GET request with default headers and returns data as object of type T.
+ *
+ * @param url request URL
+ * @param clazz type of data to return
+ * @return response content
+ */
+ private Optional executeGet(final String url, final Class clazz) throws IOException {
+
+ HttpURLConnection connection = createBaseRequest(url, HttpMethod.GET);
+ String responseContent = executeRequest(connection);
+ return gson.fromJson(responseContent, clazz);
+ }
+
+ /**
+ * Executes a HTTP GET request with default headers and returns data as List of type T.
+ *
+ * @param url request URL
+ * @param clazz array type of data to return as list
+ * @return response content (as a List)
+ */
+ private List executeGetList(final String url, final Class clazz) throws IOException {
+ Optional objects = executeGet(url, clazz);
+ if (objects.isPresent()) {
+ return Arrays.asList(objects.get());
+ }
+ return Collections.emptyList();
+ }
+
+ /**
+ * Executes a HTTP POST request with the given {@link ActionDTO} as content.
+ *
+ * @param url request URL
+ * @param action action to execute
+ */
+ private void executePost(final String url, final ActionDTO action) throws IOException {
+ final String json = gson.toJson(action);
+ final byte[] jsonBytes = json.getBytes(StandardCharsets.UTF_8);
+ logger.debug("Action {} JSON: {}", action.getType(), json);
+
+ HttpURLConnection connection = createBaseRequest(url, HttpMethod.POST);
+ connection.setDoOutput(true);
+ connection.setRequestProperty(HttpHeader.CONTENT_LENGTH.asString(), String.valueOf(jsonBytes.length));
+ try (OutputStream outputStream = connection.getOutputStream()) {
+ outputStream.write(jsonBytes);
+ }
+
+ executeRequest(connection);
+ }
+
+ private String executeRequest(HttpURLConnection connection) throws IOException {
+ StringBuilder stringBuilder = new StringBuilder();
+ try (BufferedReader reader = new BufferedReader(new InputStreamReader(connection.getInputStream()))) {
+ String line;
+ while ((line = reader.readLine()) != null) {
+ stringBuilder.append(line);
+ }
+ }
+
+ String responseContent = stringBuilder.toString();
+ logger.trace("RAW-RESPONSE: {}", responseContent);
+ handleResponseErrors(connection, responseContent);
+ return normalizeResponseContent(responseContent);
+ }
+
+ private HttpURLConnection createBaseRequest(String url, HttpMethod httpMethod) throws IOException {
+
+ final AccessTokenResponse accessTokenResponse = getAccessTokenResponse();
+ return connectionFactory.createBaseRequest(url, httpMethod, accessTokenResponse);
+ }
+
+ public AccessTokenResponse getAccessTokenResponse() throws IOException {
+ try {
+ @Nullable
+ final AccessTokenResponse accessTokenResponse = oAuthService.getAccessTokenResponse();
+ if (accessTokenResponse == null || accessTokenResponse.getAccessToken() == null
+ || accessTokenResponse.getAccessToken().isBlank()) {
+ throw new AuthenticationException("No LIVISI SmartHome access token. Is this thing authorized?");
+ }
+ return accessTokenResponse;
+ } catch (OAuthException | OAuthResponseException e) {
+ throw new AuthenticationException("Error fetching access token: " + e.getMessage());
+ }
+ }
+
+ /**
+ * Handles errors from the {@link org.eclipse.jetty.client.api.ContentResponse} and throws the following errors:
+ *
+ * @param connection connection
+ * @param responseContent response content
+ * @throws ControllerOfflineException thrown, if the LIVISI SmartHome controller (SHC) is offline.
+ */
+ private void handleResponseErrors(final HttpURLConnection connection, final String responseContent)
+ throws IOException {
+
+ final int status = connection.getResponseCode();
+ if (HttpStatus.OK_200 == status) {
+ logger.debug("Statuscode is OK: [{}]", connection.getURL());
+ } else if (HttpStatus.SERVICE_UNAVAILABLE_503 == status) {
+ throw new ServiceUnavailableException("LIVISI SmartHome service is unavailable (503).");
+ } else {
+ logger.debug("Statuscode {} is NOT OK: [{}]", status, connection.getURL());
+ String content = normalizeResponseContent(responseContent);
+ try {
+ logger.trace("Response error content: {}", content);
+ final Optional errorOptional = gson.fromJson(content, ErrorResponseDTO.class);
+ if (errorOptional.isPresent()) {
+ ErrorResponseDTO error = errorOptional.get();
+ switch (error.getCode()) {
+ case ErrorResponseDTO.ERR_SESSION_EXISTS:
+ throw new SessionExistsException("Session exists: " + error.getDescription());
+ case ErrorResponseDTO.ERR_SESSION_NOT_FOUND:
+ throw new SessionNotFoundException("Session not found: " + error.getDescription());
+ case ErrorResponseDTO.ERR_CONTROLLER_OFFLINE:
+ throw new ControllerOfflineException("Controller offline: " + error.getDescription());
+ case ErrorResponseDTO.ERR_REMOTE_ACCESS_NOT_ALLOWED:
+ throw new RemoteAccessNotAllowedException(
+ "Remote access not allowed. Access is allowed only from the SHC device network.");
+ case ErrorResponseDTO.ERR_INVALID_ACTION_TRIGGERED:
+ throw new InvalidActionTriggeredException(
+ "Invalid action triggered. Message: " + error.getDescription());
+ }
+ throw new ApiException("Unknown error: " + error);
+ }
+ } catch (final JsonSyntaxException e) {
+ throw new ApiException("Invalid JSON syntax in error response: " + content, e);
+ }
+ }
+ }
+
+ /**
+ * Sets a new state of a SwitchActuator.
+ */
+ public void setSwitchActuatorState(final String capabilityId, final boolean state) throws IOException {
+ executePost(createActionURL(),
+ new StateActionSetterDTO(capabilityId, CapabilityDTO.TYPE_SWITCHACTUATOR, state));
+ }
+
+ /**
+ * Sets the dimmer level of a DimmerActuator.
+ */
+ public void setDimmerActuatorState(final String capabilityId, final int dimLevel) throws IOException {
+ executePost(createActionURL(),
+ new StateActionSetterDTO(capabilityId, CapabilityDTO.TYPE_DIMMERACTUATOR, dimLevel));
+ }
+
+ /**
+ * Sets the roller shutter level of a RollerShutterActuator.
+ */
+ public void setRollerShutterActuatorState(final String capabilityId, final int rollerShutterLevel)
+ throws IOException {
+ executePost(createActionURL(),
+ new StateActionSetterDTO(capabilityId, CapabilityDTO.TYPE_ROLLERSHUTTERACTUATOR, rollerShutterLevel));
+ }
+
+ /**
+ * Starts or stops moving a RollerShutterActuator
+ */
+ public void setRollerShutterAction(final String capabilityId, final ShutterActionType rollerShutterAction)
+ throws IOException {
+ executePost(createActionURL(), new ShutterActionDTO(capabilityId, rollerShutterAction));
+ }
+
+ /**
+ * Sets a new state of a VariableActuator.
+ */
+ public void setVariableActuatorState(final String capabilityId, final boolean state) throws IOException {
+ executePost(createActionURL(),
+ new StateActionSetterDTO(capabilityId, CapabilityDTO.TYPE_VARIABLEACTUATOR, state));
+ }
+
+ /**
+ * Sets the point temperature.
+ */
+ public void setPointTemperatureState(final String capabilityId, final double pointTemperature) throws IOException {
+ executePost(createActionURL(),
+ new StateActionSetterDTO(capabilityId, CapabilityDTO.TYPE_THERMOSTATACTUATOR, pointTemperature));
+ }
+
+ /**
+ * Sets the operation mode to "Auto" or "Manu".
+ */
+ public void setOperationMode(final String capabilityId, final boolean isAutoMode) throws IOException {
+ executePost(createActionURL(),
+ new StateActionSetterDTO(capabilityId, CapabilityDTO.TYPE_THERMOSTATACTUATOR, isAutoMode));
+ }
+
+ /**
+ * Sets the alarm state.
+ */
+ public void setAlarmActuatorState(final String capabilityId, final boolean alarmState) throws IOException {
+ executePost(createActionURL(),
+ new StateActionSetterDTO(capabilityId, CapabilityDTO.TYPE_ALARMACTUATOR, alarmState));
+ }
+
+ /**
+ * Load the device and returns a {@link List} of {@link DeviceDTO}s.
+ * VariableActuators are returned additionally (independent from the device ids),
+ * because VariableActuators are everytime available and never have a device state.
+ *
+ * @param deviceIds Ids of the devices to return
+ * @return List of Devices
+ */
+ public List getDevices(Collection deviceIds) throws IOException {
+ logger.debug("Loading LIVISI SmartHome devices...");
+ List devices = executeGetList(URLCreator.createDevicesURL(bridgeConfiguration.host),
+ DeviceDTO[].class);
+ return devices.stream().filter(d -> isDeviceUsable(d, deviceIds)).collect(Collectors.toList());
+ }
+
+ /**
+ * Loads the {@link DeviceDTO} with the given deviceId.
+ */
+ public Optional getDeviceById(final String deviceId) throws IOException {
+ logger.debug("Loading device with id {}...", deviceId);
+ return executeGet(URLCreator.createDeviceURL(bridgeConfiguration.host, deviceId), DeviceDTO.class);
+ }
+
+ /**
+ * Loads the states for all {@link DeviceDTO}s.
+ */
+ public List getDeviceStates() throws IOException {
+ logger.debug("Loading device states...");
+ return executeGetList(URLCreator.createDeviceStatesURL(bridgeConfiguration.host), DeviceStateDTO[].class);
+ }
+
+ /**
+ * Loads the device state for the given deviceId.
+ */
+ public @Nullable StateDTO getDeviceStateByDeviceId(final String deviceId, final boolean isSHCClassic)
+ throws IOException {
+ logger.debug("Loading device states for device id {}...", deviceId);
+ if (isSHCClassic) {
+ Optional deviceState = executeGet(
+ URLCreator.createDeviceStateURL(bridgeConfiguration.host, deviceId), DeviceStateDTO.class);
+ return deviceState.map(DeviceStateDTO::getState).orElse(null);
+ }
+ return executeGet(URLCreator.createDeviceStateURL(bridgeConfiguration.host, deviceId), StateDTO.class)
+ .orElse(null);
+ }
+
+ /**
+ * Loads the locations and returns a {@link List} of {@link LocationDTO}s.
+ *
+ * @return a List of Devices
+ */
+ public List getLocations() throws IOException {
+ logger.debug("Loading locations...");
+ return executeGetList(URLCreator.createLocationURL(bridgeConfiguration.host), LocationDTO[].class);
+ }
+
+ /**
+ * Loads and returns a {@link List} of {@link CapabilityDTO}s for the given deviceId.
+ *
+ * @param deviceId the id of the {@link DeviceDTO}
+ * @return capabilities of the device
+ */
+ public List getCapabilitiesForDevice(final String deviceId) throws IOException {
+ logger.debug("Loading capabilities for device {}...", deviceId);
+ return executeGetList(URLCreator.createDeviceCapabilitiesURL(bridgeConfiguration.host, deviceId),
+ CapabilityDTO[].class);
+ }
+
+ /**
+ * Loads and returns a {@link List} of all {@link CapabilityDTO}s.
+ */
+ public List getCapabilities() throws IOException {
+ logger.debug("Loading capabilities...");
+ return executeGetList(URLCreator.createCapabilityURL(bridgeConfiguration.host), CapabilityDTO[].class);
+ }
+
+ /**
+ * Loads and returns a {@link List} of all {@link CapabilityDTO}States.
+ */
+ public List getCapabilityStates() throws IOException {
+ logger.debug("Loading capability states...");
+ return executeGetList(URLCreator.createCapabilityStatesURL(bridgeConfiguration.host),
+ CapabilityStateDTO[].class);
+ }
+
+ /**
+ * Returns a {@link List} of all {@link MessageDTO}s.
+ */
+ public List getMessages() throws IOException {
+ logger.debug("Loading messages...");
+ return executeGetList(URLCreator.createMessageURL(bridgeConfiguration.host), MessageDTO[].class);
+ }
+
+ private String createActionURL() {
+ return URLCreator.createActionURL(bridgeConfiguration.host);
+ }
+
+ /**
+ * Decides if a (discovered) device is usable (available and supported).
+ *
+ * @param device device to check
+ * @param activeDeviceIds active device id (devices with an according available device state)
+ * @return true when usable, otherwise false
+ */
+ private static boolean isDeviceUsable(DeviceDTO device, Collection activeDeviceIds) {
+ return activeDeviceIds.contains(device.getId())
+ || LivisiBindingConstants.DEVICE_VARIABLE_ACTUATOR.equals(device.getType());
+ }
+
+ /**
+ * Normalizes the JSON response content.
+ * The LIVISI SmartHome local API returns "[]" for missing objects instead of "null". This method fixes
+ * this issue.
+ *
+ * @param responseContent response
+ * @return normalized response content
+ */
+ private static String normalizeResponseContent(String responseContent) {
+ return responseContent.replace("[]", "null");
+ }
+}
diff --git a/bundles/org.openhab.binding.livisismarthome/src/main/java/org/openhab/binding/livisismarthome/internal/client/URLConnectionFactory.java b/bundles/org.openhab.binding.livisismarthome/src/main/java/org/openhab/binding/livisismarthome/internal/client/URLConnectionFactory.java
new file mode 100644
index 00000000000..c3d36de47ca
--- /dev/null
+++ b/bundles/org.openhab.binding.livisismarthome/src/main/java/org/openhab/binding/livisismarthome/internal/client/URLConnectionFactory.java
@@ -0,0 +1,54 @@
+/**
+ * Copyright (c) 2010-2022 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.livisismarthome.internal.client;
+
+import java.io.IOException;
+import java.net.HttpURLConnection;
+import java.net.URL;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.eclipse.jetty.http.HttpHeader;
+import org.eclipse.jetty.http.HttpMethod;
+import org.openhab.core.auth.client.oauth2.AccessTokenResponse;
+
+/**
+ * The {@link URLConnectionFactory} is responsible for creating requests / connections.
+ * This is useful to avoid real connections in test mode (via replacing it with mocks).
+ *
+ * @author Sven Strohschein - Initial contribution
+ */
+@NonNullByDefault
+public class URLConnectionFactory {
+
+ public static final String CONTENT_TYPE = "application/json";
+ public static final int HTTP_REQUEST_TIMEOUT_MILLISECONDS = 10000;
+
+ private static final String BEARER = "Bearer ";
+
+ public HttpURLConnection createRequest(String url) throws IOException {
+ return (HttpURLConnection) new URL(url).openConnection();
+ }
+
+ public HttpURLConnection createBaseRequest(String url, HttpMethod httpMethod,
+ AccessTokenResponse accessTokenResponse) throws IOException {
+
+ HttpURLConnection urlConnection = createRequest(url);
+ urlConnection.setRequestMethod(httpMethod.asString());
+ urlConnection.setRequestProperty(HttpHeader.ACCEPT.asString(), CONTENT_TYPE);
+ urlConnection.setRequestProperty(HttpHeader.AUTHORIZATION.asString(),
+ BEARER + accessTokenResponse.getAccessToken());
+ urlConnection.setConnectTimeout(HTTP_REQUEST_TIMEOUT_MILLISECONDS);
+ urlConnection.setReadTimeout(HTTP_REQUEST_TIMEOUT_MILLISECONDS);
+ return urlConnection;
+ }
+}
diff --git a/bundles/org.openhab.binding.livisismarthome/src/main/java/org/openhab/binding/livisismarthome/internal/client/URLCreator.java b/bundles/org.openhab.binding.livisismarthome/src/main/java/org/openhab/binding/livisismarthome/internal/client/URLCreator.java
new file mode 100644
index 00000000000..dcd93a05f40
--- /dev/null
+++ b/bundles/org.openhab.binding.livisismarthome/src/main/java/org/openhab/binding/livisismarthome/internal/client/URLCreator.java
@@ -0,0 +1,93 @@
+/**
+ * Copyright (c) 2010-2022 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.livisismarthome.internal.client;
+
+import java.net.URLEncoder;
+import java.nio.charset.StandardCharsets;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+
+/**
+ * The {@link URLCreator} is responsible for creating all required URLs.
+ *
+ * @author Sven Strohschein - Initial contribution
+ */
+@NonNullByDefault
+public final class URLCreator {
+
+ private URLCreator() {
+ }
+
+ public static String createTokenURL(String host) {
+ return createAddress(host) + "/auth/token";
+ }
+
+ public static String createDevicesURL(String host) {
+ return createAddress(host) + "/device";
+ }
+
+ public static String createDeviceStatesURL(String host) {
+ return createAddress(host) + "/device/states";
+ }
+
+ public static String createDeviceURL(String host, String deviceId) {
+ return createAddress(host) + "/device/" + deviceId;
+ }
+
+ public static String createDeviceStateURL(String host, String deviceId) {
+ return createAddress(host) + "/device/" + deviceId + "/state";
+ }
+
+ public static String createDeviceCapabilitiesURL(String host, String deviceId) {
+ return createAddress(host) + "/device/" + deviceId + "/capabilities";
+ }
+
+ public static String createCapabilityURL(String host) {
+ return createAddress(host) + "/capability";
+ }
+
+ public static String createCapabilityStatesURL(String host) {
+ return createAddress(host) + "/capability/states";
+ }
+
+ public static String createActionURL(String host) {
+ return createAddress(host) + "/action";
+ }
+
+ public static String createStatusURL(String host) {
+ return createAddress(host) + "/status";
+ }
+
+ public static String createLocationURL(String host) {
+ return createAddress(host) + "/location";
+ }
+
+ public static String createMessageURL(String host) {
+ return createAddress(host) + "/message";
+ }
+
+ public static String createEventsURL(String host, String token, boolean isClassicController) {
+ final String tokenURLEncoded = URLEncoder.encode(token, StandardCharsets.UTF_8);
+ final String webSocketPort;
+ if (isClassicController) {
+ webSocketPort = "8080";
+ } else {
+ webSocketPort = "9090";
+ }
+ return "ws://" + host + ':' + webSocketPort + "/events?token=" + tokenURLEncoded;
+ }
+
+ private static String createAddress(String host) {
+ return "http://" + host + ":8080";
+ }
+}
diff --git a/bundles/org.openhab.binding.livisismarthome/src/main/java/org/openhab/binding/livisismarthome/internal/client/Util.java b/bundles/org.openhab.binding.livisismarthome/src/main/java/org/openhab/binding/livisismarthome/internal/client/Util.java
new file mode 100644
index 00000000000..9dc887ccd37
--- /dev/null
+++ b/bundles/org.openhab.binding.livisismarthome/src/main/java/org/openhab/binding/livisismarthome/internal/client/Util.java
@@ -0,0 +1,41 @@
+/**
+ * Copyright (c) 2010-2022 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.livisismarthome.internal.client;
+
+import java.time.ZonedDateTime;
+import java.time.format.DateTimeFormatter;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+
+/**
+ * Utility class with commonly used methods.
+ *
+ * @author Oliver Kuhl - Initial contribution
+ *
+ */
+@NonNullByDefault
+public final class Util {
+
+ private Util() {
+ }
+
+ /**
+ * Converts an ISO offset date-time string to a {@link ZonedDateTime}
+ *
+ * @param dateTimeString date-time string
+ * @return {@link ZonedDateTime}
+ */
+ public static ZonedDateTime timeStringToDate(String dateTimeString) {
+ return ZonedDateTime.parse(dateTimeString, DateTimeFormatter.ISO_OFFSET_DATE_TIME);
+ }
+}
diff --git a/bundles/org.openhab.binding.livisismarthome/src/main/java/org/openhab/binding/livisismarthome/internal/client/api/entity/PropertyDTO.java b/bundles/org.openhab.binding.livisismarthome/src/main/java/org/openhab/binding/livisismarthome/internal/client/api/entity/PropertyDTO.java
new file mode 100644
index 00000000000..aaa7f689fd2
--- /dev/null
+++ b/bundles/org.openhab.binding.livisismarthome/src/main/java/org/openhab/binding/livisismarthome/internal/client/api/entity/PropertyDTO.java
@@ -0,0 +1,52 @@
+/**
+ * Copyright (c) 2010-2022 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.livisismarthome.internal.client.api.entity;
+
+/**
+ * Defines a {@link PropertyDTO}, that is a basic key/value structure used for several data types in the LIVISI API.
+ *
+ * @author Oliver Kuhl - Initial contribution
+ */
+public class PropertyDTO {
+
+ private String name;
+ private Object value;
+
+ /**
+ * @return the name
+ */
+ public String getName() {
+ return name;
+ }
+
+ /**
+ * @param name the name to set
+ */
+ public void setName(String name) {
+ this.name = name;
+ }
+
+ /**
+ * @return the value
+ */
+ public Object getValue() {
+ return value;
+ }
+
+ /**
+ * @param value the value to set
+ */
+ public void setValue(Object value) {
+ this.value = value;
+ }
+}
diff --git a/bundles/org.openhab.binding.livisismarthome/src/main/java/org/openhab/binding/livisismarthome/internal/client/api/entity/StatusResponseDTO.java b/bundles/org.openhab.binding.livisismarthome/src/main/java/org/openhab/binding/livisismarthome/internal/client/api/entity/StatusResponseDTO.java
new file mode 100644
index 00000000000..dcb0baa0cc7
--- /dev/null
+++ b/bundles/org.openhab.binding.livisismarthome/src/main/java/org/openhab/binding/livisismarthome/internal/client/api/entity/StatusResponseDTO.java
@@ -0,0 +1,45 @@
+/**
+ * Copyright (c) 2010-2022 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.livisismarthome.internal.client.api.entity;
+
+import org.openhab.binding.livisismarthome.internal.client.api.entity.device.GatewayDTO;
+
+/**
+ * Defines the structure of the status response
+ *
+ * @author Oliver Kuhl - Initial contribution
+ */
+public class StatusResponseDTO {
+
+ /**
+ * The LIVISI SmartHome gateway. Can be null in case there is no registered for the current logged in user.
+ */
+ private GatewayDTO gateway;
+
+ /**
+ * Version of the configuration. Changes each time the configuration was changed via the LIVISI client app.
+ */
+ private String configVersion;
+
+ /**
+ * @return the configuration version
+ */
+ public String getConfigVersion() {
+ // SHC 2 returns a gateway element with the config version.
+ if (gateway != null) {
+ return gateway.getConfigVersion();
+ }
+ // SHC 1 (classic) has no gateway element, the configVersion is returned directly within the response object.
+ return configVersion;
+ }
+}
diff --git a/bundles/org.openhab.binding.livisismarthome/src/main/java/org/openhab/binding/livisismarthome/internal/client/api/entity/action/ActionDTO.java b/bundles/org.openhab.binding.livisismarthome/src/main/java/org/openhab/binding/livisismarthome/internal/client/api/entity/action/ActionDTO.java
new file mode 100644
index 00000000000..59eab61f222
--- /dev/null
+++ b/bundles/org.openhab.binding.livisismarthome/src/main/java/org/openhab/binding/livisismarthome/internal/client/api/entity/action/ActionDTO.java
@@ -0,0 +1,118 @@
+/**
+ * Copyright (c) 2010-2022 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.livisismarthome.internal.client.api.entity.action;
+
+import org.openhab.binding.livisismarthome.internal.client.api.entity.link.LinkDTO;
+
+/**
+ * Implements the Action structure needed to send JSON actions to the LIVISI backend. They are used to e.g. switch the
+ * state of a device.
+ *
+ * @author Oliver Kuhl - Initial contribution
+ */
+public class ActionDTO {
+
+ private static final String NAMESPACE_CORE_RWE = "core.RWE";
+
+ /**
+ * Specifies the type of the action.
+ */
+ private String type;
+
+ /**
+ * Link to the entity supposed to execute the action.
+ */
+ private String target;
+
+ /**
+ * The product (context) that should handle (execute) the action. Defaults to {@link ActionDTO#NAMESPACE_CORE_RWE}.
+ */
+ private String namespace = NAMESPACE_CORE_RWE;
+
+ /**
+ * Dictionary of functions required for the intended execution of the action.
+ */
+ private ActionParamsDTO params;
+
+ /**
+ * Default constructor, used by serialization.
+ */
+ public ActionDTO() {
+ // used by serialization
+ }
+
+ /**
+ * @return the type
+ */
+ public String getType() {
+ return type;
+ }
+
+ /**
+ * @param type the type to set
+ */
+ public void setType(String type) {
+ this.type = type;
+ }
+
+ /**
+ * @return the link to the target capability
+ */
+ public String getTarget() {
+ return target;
+ }
+
+ /**
+ * @param target the link to the target capability to set
+ */
+ public void setTarget(String target) {
+ this.target = target;
+ }
+
+ /**
+ * @return the namespace
+ */
+ public String getNamespace() {
+ return namespace;
+ }
+
+ /**
+ * @param namespace the namespace to set
+ */
+ public void setNamespace(String namespace) {
+ this.namespace = namespace;
+ }
+
+ /**
+ * Sets the link target to the given capability id.
+ *
+ * @param capabilityId String with the 32 character long id
+ */
+ public void setTargetCapabilityById(String capabilityId) {
+ setTarget(LinkDTO.LINK_TYPE_CAPABILITY + capabilityId);
+ }
+
+ /**
+ * @return the params
+ */
+ public ActionParamsDTO getParams() {
+ return params;
+ }
+
+ /**
+ * @param params the params to set
+ */
+ public void setParams(ActionParamsDTO params) {
+ this.params = params;
+ }
+}
diff --git a/bundles/org.openhab.binding.livisismarthome/src/main/java/org/openhab/binding/livisismarthome/internal/client/api/entity/action/ActionParamsDTO.java b/bundles/org.openhab.binding.livisismarthome/src/main/java/org/openhab/binding/livisismarthome/internal/client/api/entity/action/ActionParamsDTO.java
new file mode 100644
index 00000000000..d845c77d018
--- /dev/null
+++ b/bundles/org.openhab.binding.livisismarthome/src/main/java/org/openhab/binding/livisismarthome/internal/client/api/entity/action/ActionParamsDTO.java
@@ -0,0 +1,129 @@
+/**
+ * Copyright (c) 2010-2022 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.livisismarthome.internal.client.api.entity.action;
+
+/**
+ * Defines the {@link ActionParamsDTO} data structure needed to pass parameters within an {@link ActionDTO} to the
+ * Livisi
+ * SmartHome backend.
+ *
+ * @author Oliver Kuhl - Initial contribution
+ */
+public class ActionParamsDTO {
+
+ private BooleanActionParamDTO onState;
+ private BooleanActionParamDTO value;
+ private DoubleActionParamDTO pointTemperature;
+ private IntegerActionParamDTO dimLevel;
+ private IntegerActionParamDTO shutterLevel;
+ private StringActionParamDTO operationMode;
+ private StringActionParamDTO rampDirection;
+
+ /**
+ * @return the onState
+ */
+ public BooleanActionParamDTO getOnState() {
+ return onState;
+ }
+
+ /**
+ * @param state the state to set
+ */
+ public void setOnState(BooleanActionParamDTO state) {
+ this.onState = state;
+ }
+
+ /**
+ * @return the onState
+ */
+ public BooleanActionParamDTO getValue() {
+ return value;
+ }
+
+ /**
+ * @param state the state to set
+ */
+ public void setValue(BooleanActionParamDTO state) {
+ this.value = state;
+ }
+
+ /**
+ * @return the pointTemperature
+ */
+ public DoubleActionParamDTO getPointTemperature() {
+ return pointTemperature;
+ }
+
+ /**
+ * @param pointTemperature the pointTemperature to set
+ */
+ public void setPointTemperature(DoubleActionParamDTO pointTemperature) {
+ this.pointTemperature = pointTemperature;
+ }
+
+ /**
+ * @return the dimLevel
+ */
+ public IntegerActionParamDTO getDimLevel() {
+ return dimLevel;
+ }
+
+ /**
+ * @param dimLevel the dimLevel to set
+ */
+ public void setDimLevel(IntegerActionParamDTO dimLevel) {
+ this.dimLevel = dimLevel;
+ }
+
+ /**
+ * @return the shutterLevel
+ */
+ public IntegerActionParamDTO getShutterLevel() {
+ return shutterLevel;
+ }
+
+ /**
+ * @param shutterLevel the shutterLevel to set
+ */
+ public void setShutterLevel(IntegerActionParamDTO shutterLevel) {
+ this.shutterLevel = shutterLevel;
+ }
+
+ /**
+ * @return the operationMode
+ */
+ public StringActionParamDTO getOperationMode() {
+ return operationMode;
+ }
+
+ /**
+ * @param operationMode the operationMode to set
+ */
+ public void setOperationMode(StringActionParamDTO operationMode) {
+ this.operationMode = operationMode;
+ }
+
+ /**
+ * @return the rampDirection
+ */
+ public StringActionParamDTO getRampDirection() {
+ return rampDirection;
+ }
+
+ /**
+ * @param rampDirection the rampDirection to set
+ */
+ public void setRampDirection(StringActionParamDTO rampDirection) {
+ this.rampDirection = rampDirection;
+ }
+}
diff --git a/bundles/org.openhab.binding.livisismarthome/src/main/java/org/openhab/binding/livisismarthome/internal/client/api/entity/action/BooleanActionParamDTO.java b/bundles/org.openhab.binding.livisismarthome/src/main/java/org/openhab/binding/livisismarthome/internal/client/api/entity/action/BooleanActionParamDTO.java
new file mode 100644
index 00000000000..818f1280315
--- /dev/null
+++ b/bundles/org.openhab.binding.livisismarthome/src/main/java/org/openhab/binding/livisismarthome/internal/client/api/entity/action/BooleanActionParamDTO.java
@@ -0,0 +1,57 @@
+/**
+ * Copyright (c) 2010-2022 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.livisismarthome.internal.client.api.entity.action;
+
+/**
+ * Defines the structure of a {@link BooleanActionParamDTO}.
+ *
+ * @author Oliver Kuhl - Initial contribution
+ */
+public class BooleanActionParamDTO {
+
+ private String type;
+ private boolean value;
+
+ BooleanActionParamDTO(String type, boolean value) {
+ this.type = type;
+ this.value = value;
+ }
+
+ /**
+ * @return the type
+ */
+ public String getType() {
+ return type;
+ }
+
+ /**
+ * @param type the type to set
+ */
+ public void setType(String type) {
+ this.type = type;
+ }
+
+ /**
+ * @return the value
+ */
+ public boolean isValue() {
+ return value;
+ }
+
+ /**
+ * @param value the value to set
+ */
+ public void setValue(boolean value) {
+ this.value = value;
+ }
+}
diff --git a/bundles/org.openhab.binding.livisismarthome/src/main/java/org/openhab/binding/livisismarthome/internal/client/api/entity/action/DoubleActionParamDTO.java b/bundles/org.openhab.binding.livisismarthome/src/main/java/org/openhab/binding/livisismarthome/internal/client/api/entity/action/DoubleActionParamDTO.java
new file mode 100644
index 00000000000..4321765b9c9
--- /dev/null
+++ b/bundles/org.openhab.binding.livisismarthome/src/main/java/org/openhab/binding/livisismarthome/internal/client/api/entity/action/DoubleActionParamDTO.java
@@ -0,0 +1,57 @@
+/**
+ * Copyright (c) 2010-2022 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.livisismarthome.internal.client.api.entity.action;
+
+/**
+ * Defines the structure of a {@link DoubleActionParamDTO}.
+ *
+ * @author Oliver Kuhl - Initial contribution
+ */
+public class DoubleActionParamDTO {
+
+ private String type;
+ private double value;
+
+ DoubleActionParamDTO(String type, double value) {
+ this.type = type;
+ this.value = value;
+ }
+
+ /**
+ * @return the type
+ */
+ public String getType() {
+ return type;
+ }
+
+ /**
+ * @param type the type to set
+ */
+ public void setType(String type) {
+ this.type = type;
+ }
+
+ /**
+ * @return the value
+ */
+ public double isValue() {
+ return value;
+ }
+
+ /**
+ * @param value the value to set
+ */
+ public void setValue(double value) {
+ this.value = value;
+ }
+}
diff --git a/bundles/org.openhab.binding.livisismarthome/src/main/java/org/openhab/binding/livisismarthome/internal/client/api/entity/action/IntegerActionParamDTO.java b/bundles/org.openhab.binding.livisismarthome/src/main/java/org/openhab/binding/livisismarthome/internal/client/api/entity/action/IntegerActionParamDTO.java
new file mode 100644
index 00000000000..f1bc62ecf5a
--- /dev/null
+++ b/bundles/org.openhab.binding.livisismarthome/src/main/java/org/openhab/binding/livisismarthome/internal/client/api/entity/action/IntegerActionParamDTO.java
@@ -0,0 +1,57 @@
+/**
+ * Copyright (c) 2010-2022 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.livisismarthome.internal.client.api.entity.action;
+
+/**
+ * Defines the structure of a {@link IntegerActionParamDTO}.
+ *
+ * @author Oliver Kuhl - Initial contribution
+ */
+public class IntegerActionParamDTO {
+
+ private String type;
+ private int value;
+
+ IntegerActionParamDTO(String type, int value) {
+ this.type = type;
+ this.value = value;
+ }
+
+ /**
+ * @return the type
+ */
+ public String getType() {
+ return type;
+ }
+
+ /**
+ * @param type the type to set
+ */
+ public void setType(String type) {
+ this.type = type;
+ }
+
+ /**
+ * @return the value
+ */
+ public int isValue() {
+ return value;
+ }
+
+ /**
+ * @param value the value to set
+ */
+ public void setValue(int value) {
+ this.value = value;
+ }
+}
diff --git a/bundles/org.openhab.binding.livisismarthome/src/main/java/org/openhab/binding/livisismarthome/internal/client/api/entity/action/ShutterActionDTO.java b/bundles/org.openhab.binding.livisismarthome/src/main/java/org/openhab/binding/livisismarthome/internal/client/api/entity/action/ShutterActionDTO.java
new file mode 100644
index 00000000000..533507c0abb
--- /dev/null
+++ b/bundles/org.openhab.binding.livisismarthome/src/main/java/org/openhab/binding/livisismarthome/internal/client/api/entity/action/ShutterActionDTO.java
@@ -0,0 +1,51 @@
+/**
+ * Copyright (c) 2010-2022 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.livisismarthome.internal.client.api.entity.action;
+
+/**
+ * Special {@link ActionDTO} needed to control shutters.
+ *
+ * @author Marco Mans - Initial contribution
+ */
+public class ShutterActionDTO extends ActionDTO {
+
+ private static final String TYPE_STOP_RAMP = "StopRamp";
+ private static final String TYPE_START_RAMP = "StartRamp";
+ private static final String DIRECTION_RAMP_UP = "RampUp";
+ private static final String DIRECTION_RAMP_DOWN = "RampDown";
+ private static final String CONSTANT = "Constant";
+ private static final String NAMESPACE_COSIP = "CosipDevices.RWE";
+
+ /**
+ * Describes a Shutteraction
+ *
+ * @param capabilityId String of the 32 character capability id
+ * @param action Which action to perform (UP, DOWN, STOP)
+ */
+ public ShutterActionDTO(String capabilityId, ShutterActionType action) {
+ setTargetCapabilityById(capabilityId);
+ setNamespace(NAMESPACE_COSIP);
+ final ActionParamsDTO params = new ActionParamsDTO();
+
+ if (ShutterActionType.STOP.equals(action)) {
+ setType(TYPE_STOP_RAMP);
+ } else if (ShutterActionType.UP.equals(action)) {
+ setType(TYPE_START_RAMP);
+ params.setRampDirection(new StringActionParamDTO(CONSTANT, DIRECTION_RAMP_UP));
+ } else if (ShutterActionType.DOWN.equals(action)) {
+ setType(TYPE_START_RAMP);
+ params.setRampDirection(new StringActionParamDTO(CONSTANT, DIRECTION_RAMP_DOWN));
+ }
+ setParams(params);
+ }
+}
diff --git a/bundles/org.openhab.binding.livisismarthome/src/main/java/org/openhab/binding/livisismarthome/internal/client/api/entity/action/ShutterActionType.java b/bundles/org.openhab.binding.livisismarthome/src/main/java/org/openhab/binding/livisismarthome/internal/client/api/entity/action/ShutterActionType.java
new file mode 100644
index 00000000000..45c1a799c22
--- /dev/null
+++ b/bundles/org.openhab.binding.livisismarthome/src/main/java/org/openhab/binding/livisismarthome/internal/client/api/entity/action/ShutterActionType.java
@@ -0,0 +1,25 @@
+/**
+ * Copyright (c) 2010-2022 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.livisismarthome.internal.client.api.entity.action;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+
+/**
+ * @author Sven Strohschein - Initial contribution
+ */
+@NonNullByDefault
+public enum ShutterActionType {
+ UP,
+ DOWN,
+ STOP
+}
diff --git a/bundles/org.openhab.binding.livisismarthome/src/main/java/org/openhab/binding/livisismarthome/internal/client/api/entity/action/StateActionSetterDTO.java b/bundles/org.openhab.binding.livisismarthome/src/main/java/org/openhab/binding/livisismarthome/internal/client/api/entity/action/StateActionSetterDTO.java
new file mode 100644
index 00000000000..fb417150ecb
--- /dev/null
+++ b/bundles/org.openhab.binding.livisismarthome/src/main/java/org/openhab/binding/livisismarthome/internal/client/api/entity/action/StateActionSetterDTO.java
@@ -0,0 +1,97 @@
+/**
+ * Copyright (c) 2010-2022 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.livisismarthome.internal.client.api.entity.action;
+
+import org.openhab.binding.livisismarthome.internal.client.api.entity.capability.CapabilityDTO;
+import org.openhab.binding.livisismarthome.internal.client.api.entity.capability.CapabilityStateDTO;
+
+/**
+ * Special {@link ActionDTO} needed to set a state of a device.
+ *
+ * @author Oliver Kuhl - Initial contribution
+ */
+public class StateActionSetterDTO extends ActionDTO {
+
+ private static final String CONSTANT = "Constant";
+ private static final String ACTION_TYPE_SETSTATE = "SetState";
+
+ /**
+ * Constructs a new {@link StateActionSetterDTO}.
+ *
+ * @param capabilityId String of the 32 character capability id
+ * @param capabilityType the type of the {@link CapabilityDTO}, {@link CapabilityDTO#TYPE_SWITCHACTUATOR} or
+ * {@link CapabilityDTO#TYPE_VARIABLEACTUATOR}
+ * @param state the new state as boolean (true=on, false=off)
+ */
+ public StateActionSetterDTO(String capabilityId, String capabilityType, boolean state) {
+ setType(ACTION_TYPE_SETSTATE);
+ setTargetCapabilityById(capabilityId);
+ final ActionParamsDTO params = new ActionParamsDTO();
+
+ if (CapabilityDTO.TYPE_SWITCHACTUATOR.equals(capabilityType)) {
+ params.setOnState(new BooleanActionParamDTO(CONSTANT, state));
+ } else if (CapabilityDTO.TYPE_VARIABLEACTUATOR.equals(capabilityType)) {
+ params.setValue(new BooleanActionParamDTO(CONSTANT, state));
+ } else if (CapabilityDTO.TYPE_ALARMACTUATOR.equals(capabilityType)) {
+ params.setOnState(new BooleanActionParamDTO(CONSTANT, state));
+ } else if (CapabilityDTO.TYPE_THERMOSTATACTUATOR.equals(capabilityType)) {
+ final String operationMode;
+ if (state) {
+ operationMode = CapabilityStateDTO.STATE_VALUE_OPERATION_MODE_AUTO;
+ } else {
+ operationMode = CapabilityStateDTO.STATE_VALUE_OPERATION_MODE_MANUAL;
+ }
+ params.setOperationMode(new StringActionParamDTO(CONSTANT, operationMode));
+ }
+ setParams(params);
+ }
+
+ /**
+ * Constructs a new {@link StateActionSetterDTO}.
+ *
+ * @param capabilityId String of the 32 character capability id
+ * @param capabilityType the type of the {@link CapabilityDTO}, {@link CapabilityDTO#TYPE_THERMOSTATACTUATOR}
+ * @param newValue the new double value
+ */
+ public StateActionSetterDTO(String capabilityId, String capabilityType, double newValue) {
+ setType(ACTION_TYPE_SETSTATE);
+ setTargetCapabilityById(capabilityId);
+ final ActionParamsDTO params = new ActionParamsDTO();
+
+ if (CapabilityDTO.TYPE_THERMOSTATACTUATOR.equals(capabilityType)) {
+ params.setPointTemperature(new DoubleActionParamDTO(CONSTANT, newValue));
+ }
+ setParams(params);
+ }
+
+ /**
+ * Constructs a new {@link StateActionSetterDTO}.
+ *
+ * @param capabilityId String of the 32 character capability id
+ * @param capabilityType the type of the {@link CapabilityDTO}, {@link CapabilityDTO#TYPE_DIMMERACTUATOR}
+ * @param newValue the new int value
+ */
+ public StateActionSetterDTO(String capabilityId, String capabilityType, int newValue) {
+ setType(ACTION_TYPE_SETSTATE);
+ setTargetCapabilityById(capabilityId);
+ final ActionParamsDTO params = new ActionParamsDTO();
+
+ if (CapabilityDTO.TYPE_DIMMERACTUATOR.equals(capabilityType)) {
+ params.setDimLevel(new IntegerActionParamDTO(CONSTANT, newValue));
+ } else if (CapabilityDTO.TYPE_ROLLERSHUTTERACTUATOR.equals(capabilityType)) {
+ params.setShutterLevel(new IntegerActionParamDTO(CONSTANT, newValue));
+ }
+
+ setParams(params);
+ }
+}
diff --git a/bundles/org.openhab.binding.livisismarthome/src/main/java/org/openhab/binding/livisismarthome/internal/client/api/entity/action/StringActionParamDTO.java b/bundles/org.openhab.binding.livisismarthome/src/main/java/org/openhab/binding/livisismarthome/internal/client/api/entity/action/StringActionParamDTO.java
new file mode 100644
index 00000000000..e3143a64519
--- /dev/null
+++ b/bundles/org.openhab.binding.livisismarthome/src/main/java/org/openhab/binding/livisismarthome/internal/client/api/entity/action/StringActionParamDTO.java
@@ -0,0 +1,57 @@
+/**
+ * Copyright (c) 2010-2022 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.livisismarthome.internal.client.api.entity.action;
+
+/**
+ * Defines the structure of a {@link StringActionParamDTO}.
+ *
+ * @author Oliver Kuhl - Initial contribution
+ */
+public class StringActionParamDTO {
+
+ private String type;
+ private String value;
+
+ StringActionParamDTO(String type, String value) {
+ this.type = type;
+ this.value = value;
+ }
+
+ /**
+ * @return the type
+ */
+ public String getType() {
+ return type;
+ }
+
+ /**
+ * @param type the type to set
+ */
+ public void setType(String type) {
+ this.type = type;
+ }
+
+ /**
+ * @return the value
+ */
+ public String isValue() {
+ return value;
+ }
+
+ /**
+ * @param value the value to set
+ */
+ public void setValue(String value) {
+ this.value = value;
+ }
+}
diff --git a/bundles/org.openhab.binding.livisismarthome/src/main/java/org/openhab/binding/livisismarthome/internal/client/api/entity/capability/CapabilityConfigDTO.java b/bundles/org.openhab.binding.livisismarthome/src/main/java/org/openhab/binding/livisismarthome/internal/client/api/entity/capability/CapabilityConfigDTO.java
new file mode 100644
index 00000000000..3ba51accafb
--- /dev/null
+++ b/bundles/org.openhab.binding.livisismarthome/src/main/java/org/openhab/binding/livisismarthome/internal/client/api/entity/capability/CapabilityConfigDTO.java
@@ -0,0 +1,639 @@
+/**
+ * Copyright (c) 2010-2022 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.livisismarthome.internal.client.api.entity.capability;
+
+/**
+ * Holds the Capability configuration.
+ *
+ * @author Oliver Kuhl - Initial contribution
+ *
+ */
+public class CapabilityConfigDTO {
+
+ /**
+ * Name of the capability
+ */
+ private String name;
+
+ /**
+ * Specifies if the activity logging is enabled
+ */
+ private Boolean activityLogActive;
+
+ /**
+ * Specifies if the activity logging is enabled
+ */
+ private Integer pushButtons;
+
+ /**
+ * The valve index
+ */
+ private Integer valveIndex;
+
+ /**
+ * The valve type
+ */
+ private String valveType;
+
+ /**
+ * The valve control mode: Heating or Cooling
+ */
+ private String controlMode;
+
+ /**
+ * Dimmer: Programmed on the device as maximum/minimum and not used for UI representation
+ */
+ private Integer technicalMaxValue;
+
+ /**
+ * Dimmer: Programmed on the device as maximum/minimum and not used for UI representation
+ */
+ private Integer technicalMinValue;
+
+ /**
+ * Rollershutter: How long it takes for the shutter to open completely when it's completely closed (in tenth of
+ * second)
+ */
+ private Integer timeFullUp;
+
+ /**
+ * Rollershutter: How long it takes for the shutter to close completely when it's completely open (in tenth of
+ * second)
+ */
+ private Integer timeFullDown;
+
+ /**
+ * Rollershutter: Flag indicating the ISR is in the calibration mode or not.
+ */
+ private Boolean isCalibrating;
+
+ /**
+ * Switchactuator:
+ * "different types of current sensing behavior the ISS can have:
+ * Enabled - Factory default value, current sensing is enabled; (default)
+ * DisabledNormal - Current sensing disabled, uses output 1;
+ * DisabledReversed - Current sensing disabled, uses output 2"
+ */
+ private String sensingBehavior;
+
+ /**
+ * Thermostatactuator: The max temperature
+ */
+ private Double maxTemperature;
+
+ /**
+ * Thermostatactuator: The min temperature
+ */
+ private Double minTemperature;
+
+ /**
+ * Thermostatactuator: Indicating whether the device is locked
+ */
+ private Boolean childLock;
+
+ /**
+ * Thermostatactuator: The window open temperature
+ */
+ private Double windowOpenTemperature;
+
+ /**
+ * Thermostatactuator: default PointTemperature
+ */
+ private String vRCCSetPoint;
+
+ /**
+ * Temperaturesensor: Indicating whether the device has freeze protection activated
+ */
+ private Boolean isFreezeProtectionActivated;
+
+ /**
+ * Temperaturesensor: The freeze protection temperature, default 6 °C
+ */
+ private Double freezeProtection;
+
+ /**
+ * Temperaturesensor: default Temperature
+ */
+ private String vRCCTemperature;
+
+ /**
+ * HumiditySensor: Indicating whether the device has mold protection activated
+ */
+ private Boolean isMoldProtectionActivated;
+
+ /**
+ * HumiditySensor: The humidity mold protection
+ */
+ private Double humidityMoldProtection;
+
+ /**
+ * HumiditySensor: default Humidity
+ */
+ private String vRCCHumidity;
+
+ /**
+ * SirenActuator: Alarm Sound Id
+ */
+ private String alarmSoundId;
+
+ /**
+ * SirenActuator: Notification Sound Id
+ */
+ private String notificationSoundId;
+
+ /**
+ * SirenActuator: Feedback Sound Id
+ */
+ private String feedbackSoundId;
+
+ /**
+ * RoomSetPoint/RoomTemperature/RoomHumidity: List of capability ids, which are linked to the VRCC
+ */
+ private String underlyingCapabilityIds;
+
+ /**
+ * WindowsDoorSensor: Time before the changed status is sent after the window/door is opened (in seconds)
+ */
+ private Integer eventFilterTime;
+
+ /**
+ * Medion ThermostatActuator: Specifies the temperature threshold that will denote a window open event. 0 = window
+ * reduction disabled 1-12 = 1/12 °C, 2/12 °C,…, 12/12 °C
+ */
+ private Integer windowOpenThreshold;
+
+ /**
+ * Medion ThermostatActuator: Duration in minutes for how long after the threshold was overstepped the valve will be
+ * closed (target temperature = OFF). After the set time, the temperature will jump back to the previous set target
+ * temperature.
+ */
+ private Integer windowOpenTimer;
+
+ /**
+ * Medion MotionDetectionSensor sensitivityControl
+ */
+ private Integer sensitivityControl;
+
+ /**
+ * Medion WindowDoorShockSensor: shockDetectorThreshold
+ */
+ private Integer shockDetectorThreshold;
+
+ /**
+ * @return the name
+ */
+ public String getName() {
+ return name;
+ }
+
+ /**
+ * @param name the name to set
+ */
+ public void setName(final String name) {
+ this.name = name;
+ }
+
+ /**
+ * @return the activityLogActive
+ */
+ public Boolean getActivityLogActive() {
+ return activityLogActive;
+ }
+
+ /**
+ * @param activityLogActive the activityLogActive to set
+ */
+ public void setActivityLogActive(final Boolean activityLogActive) {
+ this.activityLogActive = activityLogActive;
+ }
+
+ /**
+ * @return the pushButtons
+ */
+ public Integer getPushButtons() {
+ return pushButtons;
+ }
+
+ /**
+ * @param pushButtons the pushButtons to set
+ */
+ public void setPushButtons(final Integer pushButtons) {
+ this.pushButtons = pushButtons;
+ }
+
+ /**
+ * @return the valveIndex
+ */
+ public Integer getValveIndex() {
+ return valveIndex;
+ }
+
+ /**
+ * @param valveIndex the valveIndex to set
+ */
+ public void setValveIndex(final Integer valveIndex) {
+ this.valveIndex = valveIndex;
+ }
+
+ /**
+ * @return the valveType
+ */
+ public String getValveType() {
+ return valveType;
+ }
+
+ /**
+ * @param valveType the valveType to set
+ */
+ public void setValveType(final String valveType) {
+ this.valveType = valveType;
+ }
+
+ /**
+ * @return the controlMode
+ */
+ public String getControlMode() {
+ return controlMode;
+ }
+
+ /**
+ * @param controlMode the controlMode to set
+ */
+ public void setControlMode(final String controlMode) {
+ this.controlMode = controlMode;
+ }
+
+ /**
+ * @return the technicalMaxValue
+ */
+ public Integer getTechnicalMaxValue() {
+ return technicalMaxValue;
+ }
+
+ /**
+ * @param technicalMaxValue the technicalMaxValue to set
+ */
+ public void setTechnicalMaxValue(final Integer technicalMaxValue) {
+ this.technicalMaxValue = technicalMaxValue;
+ }
+
+ /**
+ * @return the technicalMinValue
+ */
+ public Integer getTechnicalMinValue() {
+ return technicalMinValue;
+ }
+
+ /**
+ * @param technicalMinValue the technicalMinValue to set
+ */
+ public void setTechnicalMinValue(final Integer technicalMinValue) {
+ this.technicalMinValue = technicalMinValue;
+ }
+
+ /**
+ * @return the timeFullUp
+ */
+ public Integer getTimeFullUp() {
+ return timeFullUp;
+ }
+
+ /**
+ * @param timeFullUp the timeFullUp to set
+ */
+ public void setTimeFullUp(final Integer timeFullUp) {
+ this.timeFullUp = timeFullUp;
+ }
+
+ /**
+ * @return the timeFullDown
+ */
+ public Integer getTimeFullDown() {
+ return timeFullDown;
+ }
+
+ /**
+ * @param timeFullDown the timeFullDown to set
+ */
+ public void setTimeFullDown(final Integer timeFullDown) {
+ this.timeFullDown = timeFullDown;
+ }
+
+ /**
+ * @return the isCalibrating
+ */
+ public Boolean getIsCalibrating() {
+ return isCalibrating;
+ }
+
+ /**
+ * @param isCalibrating the isCalibrating to set
+ */
+ public void setIsCalibrating(final Boolean isCalibrating) {
+ this.isCalibrating = isCalibrating;
+ }
+
+ /**
+ * @return the sensingBehavior
+ */
+ public String getSensingBehavior() {
+ return sensingBehavior;
+ }
+
+ /**
+ * @param sensingBehavior the sensingBehavior to set
+ */
+ public void setSensingBehavior(final String sensingBehavior) {
+ this.sensingBehavior = sensingBehavior;
+ }
+
+ /**
+ * @return the maxTemperature
+ */
+ public Double getMaxTemperature() {
+ return maxTemperature;
+ }
+
+ /**
+ * @param maxTemperature the maxTemperature to set
+ */
+ public void setMaxTemperature(final Double maxTemperature) {
+ this.maxTemperature = maxTemperature;
+ }
+
+ /**
+ * @return the minTemperature
+ */
+ public Double getMinTemperature() {
+ return minTemperature;
+ }
+
+ /**
+ * @param minTemperature the minTemperature to set
+ */
+ public void setMinTemperature(final Double minTemperature) {
+ this.minTemperature = minTemperature;
+ }
+
+ /**
+ * @return the childLock
+ */
+ public Boolean getChildLock() {
+ return childLock;
+ }
+
+ /**
+ * @param childLock the childLock to set
+ */
+ public void setChildLock(final Boolean childLock) {
+ this.childLock = childLock;
+ }
+
+ /**
+ * @return the windowOpenTemperature
+ */
+ public Double getWindowOpenTemperature() {
+ return windowOpenTemperature;
+ }
+
+ /**
+ * @param windowOpenTemperature the windowOpenTemperature to set
+ */
+ public void setWindowOpenTemperature(final Double windowOpenTemperature) {
+ this.windowOpenTemperature = windowOpenTemperature;
+ }
+
+ /**
+ * @return the vRCCSetPoint
+ */
+ public String getvRCCSetPoint() {
+ return vRCCSetPoint;
+ }
+
+ /**
+ * @param vRCCSetPoint the vRCCSetPoint to set
+ */
+ public void setvRCCSetPoint(final String vRCCSetPoint) {
+ this.vRCCSetPoint = vRCCSetPoint;
+ }
+
+ /**
+ * @return the isFreezeProtectionActivated
+ */
+ public Boolean getIsFreezeProtectionActivated() {
+ return isFreezeProtectionActivated;
+ }
+
+ /**
+ * @param isFreezeProtectionActivated the isFreezeProtectionActivated to set
+ */
+ public void setIsFreezeProtectionActivated(final Boolean isFreezeProtectionActivated) {
+ this.isFreezeProtectionActivated = isFreezeProtectionActivated;
+ }
+
+ /**
+ * @return the freezeProtection
+ */
+ public Double getFreezeProtection() {
+ return freezeProtection;
+ }
+
+ /**
+ * @param freezeProtection the freezeProtection to set
+ */
+ public void setFreezeProtection(final Double freezeProtection) {
+ this.freezeProtection = freezeProtection;
+ }
+
+ /**
+ * @return the vRCCTemperature
+ */
+ public String getvRCCTemperature() {
+ return vRCCTemperature;
+ }
+
+ /**
+ * @param vRCCTemperature the vRCCTemperature to set
+ */
+ public void setvRCCTemperature(final String vRCCTemperature) {
+ this.vRCCTemperature = vRCCTemperature;
+ }
+
+ /**
+ * @return the isMoldProtectionActivated
+ */
+ public Boolean getIsMoldProtectionActivated() {
+ return isMoldProtectionActivated;
+ }
+
+ /**
+ * @param isMoldProtectionActivated the isMoldProtectionActivated to set
+ */
+ public void setIsMoldProtectionActivated(final Boolean isMoldProtectionActivated) {
+ this.isMoldProtectionActivated = isMoldProtectionActivated;
+ }
+
+ /**
+ * @return the humidityMoldProtection
+ */
+ public Double getHumidityMoldProtection() {
+ return humidityMoldProtection;
+ }
+
+ /**
+ * @param humidityMoldProtection the humidityMoldProtection to set
+ */
+ public void setHumidityMoldProtection(final Double humidityMoldProtection) {
+ this.humidityMoldProtection = humidityMoldProtection;
+ }
+
+ /**
+ * @return the vRCCHumidity
+ */
+ public String getvRCCHumidity() {
+ return vRCCHumidity;
+ }
+
+ /**
+ * @param vRCCHumidity the vRCCHumidity to set
+ */
+ public void setvRCCHumidity(final String vRCCHumidity) {
+ this.vRCCHumidity = vRCCHumidity;
+ }
+
+ /**
+ * @return the alarmSoundId
+ */
+ public String getAlarmSoundId() {
+ return alarmSoundId;
+ }
+
+ /**
+ * @param alarmSoundId the alarmSoundId to set
+ */
+ public void setAlarmSoundId(final String alarmSoundId) {
+ this.alarmSoundId = alarmSoundId;
+ }
+
+ /**
+ * @return the notificationSoundId
+ */
+ public String getNotificationSoundId() {
+ return notificationSoundId;
+ }
+
+ /**
+ * @param notificationSoundId the notificationSoundId to set
+ */
+ public void setNotificationSoundId(final String notificationSoundId) {
+ this.notificationSoundId = notificationSoundId;
+ }
+
+ /**
+ * @return the feedbackSoundId
+ */
+ public String getFeedbackSoundId() {
+ return feedbackSoundId;
+ }
+
+ /**
+ * @param feedbackSoundId the feedbackSoundId to set
+ */
+ public void setFeedbackSoundId(final String feedbackSoundId) {
+ this.feedbackSoundId = feedbackSoundId;
+ }
+
+ /**
+ * @return the underlyingCapabilityIds
+ */
+ public String getUnderlyingCapabilityIds() {
+ return underlyingCapabilityIds;
+ }
+
+ /**
+ * @param underlyingCapabilityIds the underlyingCapabilityIds to set
+ */
+ public void setUnderlyingCapabilityIds(final String underlyingCapabilityIds) {
+ this.underlyingCapabilityIds = underlyingCapabilityIds;
+ }
+
+ /**
+ * @return the eventFilterTime
+ */
+ public Integer getEventFilterTime() {
+ return eventFilterTime;
+ }
+
+ /**
+ * @param eventFilterTime the eventFilterTime to set
+ */
+ public void setEventFilterTime(final Integer eventFilterTime) {
+ this.eventFilterTime = eventFilterTime;
+ }
+
+ /**
+ * @return the windowOpenThreshold
+ */
+ public Integer getWindowOpenThreshold() {
+ return windowOpenThreshold;
+ }
+
+ /**
+ * @param windowOpenThreshold the windowOpenThreshold to set
+ */
+ public void setWindowOpenThreshold(final Integer windowOpenThreshold) {
+ this.windowOpenThreshold = windowOpenThreshold;
+ }
+
+ /**
+ * @return the windowOpenTimer
+ */
+ public Integer getWindowOpenTimer() {
+ return windowOpenTimer;
+ }
+
+ /**
+ * @param windowOpenTimer the windowOpenTimer to set
+ */
+ public void setWindowOpenTimer(final Integer windowOpenTimer) {
+ this.windowOpenTimer = windowOpenTimer;
+ }
+
+ /**
+ * @return the sensitivityControl
+ */
+ public Integer getSensitivityControl() {
+ return sensitivityControl;
+ }
+
+ /**
+ * @param sensitivityControl the sensitivityControl to set
+ */
+ public void setSensitivityControl(final Integer sensitivityControl) {
+ this.sensitivityControl = sensitivityControl;
+ }
+
+ /**
+ * @return the shockDetectorThreshold
+ */
+ public Integer getShockDetectorThreshold() {
+ return shockDetectorThreshold;
+ }
+
+ /**
+ * @param shockDetectorThreshold the shockDetectorThreshold to set
+ */
+ public void setShockDetectorThreshold(final Integer shockDetectorThreshold) {
+ this.shockDetectorThreshold = shockDetectorThreshold;
+ }
+}
diff --git a/bundles/org.openhab.binding.livisismarthome/src/main/java/org/openhab/binding/livisismarthome/internal/client/api/entity/capability/CapabilityDTO.java b/bundles/org.openhab.binding.livisismarthome/src/main/java/org/openhab/binding/livisismarthome/internal/client/api/entity/capability/CapabilityDTO.java
new file mode 100644
index 00000000000..59679253604
--- /dev/null
+++ b/bundles/org.openhab.binding.livisismarthome/src/main/java/org/openhab/binding/livisismarthome/internal/client/api/entity/capability/CapabilityDTO.java
@@ -0,0 +1,331 @@
+/**
+ * Copyright (c) 2010-2022 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.livisismarthome.internal.client.api.entity.capability;
+
+/**
+ * Defines the structure of a {@link CapabilityDTO}. A capability is a specific functionality of a device like a
+ * temperature sensor.
+ *
+ * @author Oliver Kuhl - Initial contribution
+ */
+public class CapabilityDTO {
+
+ /** capability types */
+ public static final String TYPE_SWITCHACTUATOR = "SwitchActuator";
+ public static final String TYPE_VARIABLEACTUATOR = "BooleanStateActuator";
+ public static final String TYPE_THERMOSTATACTUATOR = "ThermostatActuator";
+ public static final String TYPE_TEMPERATURESENSOR = "TemperatureSensor";
+ public static final String TYPE_HUMIDITYSENSOR = "HumiditySensor";
+ public static final String TYPE_WINDOWDOORSENSOR = "WindowDoorSensor";
+ public static final String TYPE_SMOKEDETECTORSENSOR = "SmokeDetectorSensor";
+ public static final String TYPE_ALARMACTUATOR = "AlarmActuator";
+ public static final String TYPE_MOTIONDETECTIONSENSOR = "MotionDetectionSensor";
+ public static final String TYPE_LUMINANCESENSOR = "LuminanceSensor";
+ public static final String TYPE_PUSHBUTTONSENSOR = "PushButtonSensor";
+ public static final String TYPE_DIMMERACTUATOR = "DimmerActuator";
+ public static final String TYPE_ROLLERSHUTTERACTUATOR = "RollerShutterActuator";
+ public static final String TYPE_ENERGYCONSUMPTIONSENSOR = "EnergyConsumptionSensor";
+ public static final String TYPE_POWERCONSUMPTIONSENSOR = "PowerConsumptionSensor";
+ public static final String TYPE_GENERATIONMETERENERGYSENSOR = "GenerationMeterEnergySensor";
+ public static final String TYPE_GENERATIONMETERPOWERCONSUMPTIONSENSOR = "GenerationMeterPowerConsumptionSensor";
+ public static final String TYPE_TWOWAYMETERENERGYCONSUMPTIONSENSOR = "TwoWayMeterEnergyConsumptionSensor";
+ public static final String TYPE_TWOWAYMETERENERGYFEEDSENSOR = "TwoWayMeterEnergyFeedSensor";
+ public static final String TYPE_TWOWAYMETERPOWERCONSUMPTIONSENSOR = "TwoWayMeterPowerConsumptionSensor";
+
+ /**
+ * Unique id for the Capability.
+ */
+ private String id;
+
+ /**
+ * Type of the capability – must be unique per device, since the device links to the capability via the type.
+ */
+ private String type;
+
+ /**
+ * Contains the link to the parent device, which offers the capability.
+ */
+ private String device;
+
+ /**
+ * This represents a container of all configuration properties.
+ */
+ private CapabilityConfigDTO config;
+
+ private CapabilityStateDTO capabilityState;
+
+ public String getId() {
+ return id;
+ }
+
+ public void setId(String id) {
+ this.id = id;
+ }
+
+ public String getType() {
+ return type;
+ }
+
+ public void setType(String type) {
+ this.type = type;
+ }
+
+ public String getDeviceLink() {
+ return device;
+ }
+
+ public void setDeviceLink(String deviceLink) {
+ this.device = deviceLink;
+ }
+
+ public CapabilityConfigDTO getConfig() {
+ return config;
+ }
+
+ public void setConfig(CapabilityConfigDTO config) {
+ this.config = config;
+ }
+
+ /**
+ * Returns the {@link CapabilityStateDTO}. Only available, if capability has a state. Better check with
+ * {@link CapabilityDTO#hasState()} first!
+ *
+ * @return the capabilityState or null
+ */
+ public CapabilityStateDTO getCapabilityState() {
+ return capabilityState;
+ }
+
+ /**
+ * @param capabilityState the capabilityState to set
+ */
+ public void setCapabilityState(CapabilityStateDTO capabilityState) {
+ this.capabilityState = capabilityState;
+ }
+
+ /**
+ * Returns, if the capability has a state. Not all capabilities have a state.
+ *
+ * @return true if the capability has a state, otherwise false
+ */
+ public boolean hasState() {
+ return (capabilityState != null) && (capabilityState.getState() != null);
+ }
+
+ /**
+ * Returns the name of the {@link CapabilityDTO}.
+ *
+ * @return capability name
+ */
+ public String getName() {
+ return getConfig().getName();
+ }
+
+ /**
+ * Returns, if the activity log is active for the {@link CapabilityDTO}.
+ *
+ * @return boolean or null, if the {@link CapabilityDTO} does not have this property.
+ */
+ public boolean getActivityLogActive() {
+ return getConfig().getActivityLogActive();
+ }
+
+ /**
+ * Returns the number of pushbuttons for the {@link CapabilityDTO}.
+ *
+ * @return int or null, if the {@link CapabilityDTO} does not have this property.
+ */
+ public int getPushButtons() {
+ return getConfig().getPushButtons();
+ }
+
+ /**
+ * Returns true, if the {@link CapabilityDTO} is of type VariableActuator.
+ *
+ * @return true if it is a VariableActuator, otherwise false
+ */
+ public boolean isTypeVariableActuator() {
+ return TYPE_VARIABLEACTUATOR.equals(getType());
+ }
+
+ /**
+ * Returns true, if the {@link CapabilityDTO} is of type SwitchActuator.
+ *
+ * @return true if it is a SwitchActuator, otherwise false
+ */
+ public boolean isTypeSwitchActuator() {
+ return TYPE_SWITCHACTUATOR.equals(getType());
+ }
+
+ /**
+ * Returns true, if the {@link CapabilityDTO} is of type ThermostatActuator.
+ *
+ * @return true if it is a SwitchActuator, otherwise false
+ */
+ public boolean isTypeThermostatActuator() {
+ return TYPE_THERMOSTATACTUATOR.equals(getType());
+ }
+
+ /**
+ * Returns true, if the {@link CapabilityDTO} is of type TemperatureSensor.
+ *
+ * @return true if it is a TemperatureSensor, otherwise false
+ */
+ public boolean isTypeTemperatureSensor() {
+ return TYPE_TEMPERATURESENSOR.equals(getType());
+ }
+
+ /**
+ * Returns true, if the {@link CapabilityDTO} is of type HumiditySensor.
+ *
+ * @return true if it is a HumiditySensor, otherwise false
+ */
+ public boolean isTypeHumiditySensor() {
+ return TYPE_HUMIDITYSENSOR.equals(getType());
+ }
+
+ /**
+ * Returns true, if the {@link CapabilityDTO} is of type WindowDoorSensor.
+ *
+ * @return true if it is a WindowDoorSensor, otherwise false
+ */
+ public boolean isTypeWindowDoorSensor() {
+ return TYPE_WINDOWDOORSENSOR.equals(getType());
+ }
+
+ /**
+ * Returns true, if the {@link CapabilityDTO} is of type SmokeDetectorSensor.
+ *
+ * @return true if it is a SmokeDetector, otherwise false
+ */
+ public boolean isTypeSmokeDetectorSensor() {
+ return TYPE_SMOKEDETECTORSENSOR.equals(getType());
+ }
+
+ /**
+ * Returns true, if the {@link CapabilityDTO} is of type AlarmActuator.
+ *
+ * @return true if it is an AlarmActuator, otherwise false
+ */
+ public boolean isTypeAlarmActuator() {
+ return TYPE_ALARMACTUATOR.equals(getType());
+ }
+
+ /**
+ * Returns true, if the {@link CapabilityDTO} is of type MotionDetectionSensor.
+ *
+ * @return true if it is a MotionDetectionSensor, otherwise false
+ */
+ public boolean isTypeMotionDetectionSensor() {
+ return TYPE_MOTIONDETECTIONSENSOR.equals(getType());
+ }
+
+ /**
+ * Returns true, if the {@link CapabilityDTO} is of type LuminanceSensor.
+ *
+ * @return true if it is a LuminanceSensor, otherwise false
+ */
+ public boolean isTypeLuminanceSensor() {
+ return TYPE_LUMINANCESENSOR.equals(getType());
+ }
+
+ /**
+ * Returns true, if the {@link CapabilityDTO} is of type PushButtonSensor.
+ *
+ * @return true if it is a PushButtonSensor, otherwise false
+ */
+ public boolean isTypePushButtonSensor() {
+ return TYPE_PUSHBUTTONSENSOR.equals(getType());
+ }
+
+ /**
+ * Returns true, if the {@link CapabilityDTO} is of type DimmerActuator.
+ *
+ * @return true if it is a DimmerActuator, otherwise false
+ */
+ public boolean isTypeDimmerActuator() {
+ return TYPE_DIMMERACTUATOR.equals(getType());
+ }
+
+ /**
+ * Returns true, if the {@link CapabilityDTO} is of type RollerShutterActuator.
+ *
+ * @return true if it is a RollerShutterActuator, otherwise false
+ */
+ public boolean isTypeRollerShutterActuator() {
+ return TYPE_ROLLERSHUTTERACTUATOR.equals(getType());
+ }
+
+ /**
+ * Returns true, if the {@link CapabilityDTO} is of type EnergyConsumptionSensor.
+ *
+ * @return true if it is a EnergyConsumptionSensor, otherwise false
+ */
+ public boolean isTypeEnergyConsumptionSensor() {
+ return TYPE_ENERGYCONSUMPTIONSENSOR.equals(getType());
+ }
+
+ /**
+ * Returns true, if the {@link CapabilityDTO} is of type PowerConsumptionSensor.
+ *
+ * @return true if it is a PowerConsumptionSensor, otherwise false
+ */
+ public boolean isTypePowerConsumptionSensor() {
+ return TYPE_POWERCONSUMPTIONSENSOR.equals(getType());
+ }
+
+ /**
+ * Returns true, if the {@link CapabilityDTO} is of type GenerationMeterEnergySensor.
+ *
+ * @return true if it is a GenerationMeterEnergySensor, otherwise false
+ */
+ public boolean isTypeGenerationMeterEnergySensor() {
+ return TYPE_GENERATIONMETERENERGYSENSOR.equals(getType());
+ }
+
+ /**
+ * Returns true, if the {@link CapabilityDTO} is of type GenerationMeterPowerConsumptionSensor.
+ *
+ * @return true if it is a GenerationMeterPowerConsumptionSensor, otherwise false
+ */
+ public boolean isTypeGenerationMeterPowerConsumptionSensor() {
+ return TYPE_GENERATIONMETERPOWERCONSUMPTIONSENSOR.equals(getType());
+ }
+
+ /**
+ * Returns true, if the {@link CapabilityDTO} is of type TwoWayMeterEnergyConsumptionSensor.
+ *
+ * @return true if it is a TwoWayMeterEnergyConsumptionSensor, otherwise false
+ */
+ public boolean isTypeTwoWayMeterEnergyConsumptionSensor() {
+ return TYPE_TWOWAYMETERENERGYCONSUMPTIONSENSOR.equals(getType());
+ }
+
+ /**
+ * Returns true, if the {@link CapabilityDTO} is of type TwoWayMeterEnergyFeedSensor.
+ *
+ * @return true if it is a TwoWayMeterEnergyFeedSensor, otherwise false
+ */
+ public boolean isTypeTwoWayMeterEnergyFeedSensor() {
+ return TYPE_TWOWAYMETERENERGYFEEDSENSOR.equals(getType());
+ }
+
+ /**
+ * Returns true, if the {@link CapabilityDTO} is of type TwoWayMeterPowerConsumptionSensor.
+ *
+ * @return true if it is a TwoWayMeterPowerConsumptionSensor, otherwise false
+ */
+ public boolean isTypeTwoWayMeterPowerConsumptionSensor() {
+ return TYPE_TWOWAYMETERPOWERCONSUMPTIONSENSOR.equals(getType());
+ }
+}
diff --git a/bundles/org.openhab.binding.livisismarthome/src/main/java/org/openhab/binding/livisismarthome/internal/client/api/entity/capability/CapabilityStateDTO.java b/bundles/org.openhab.binding.livisismarthome/src/main/java/org/openhab/binding/livisismarthome/internal/client/api/entity/capability/CapabilityStateDTO.java
new file mode 100644
index 00000000000..7f9f1a6fa31
--- /dev/null
+++ b/bundles/org.openhab.binding.livisismarthome/src/main/java/org/openhab/binding/livisismarthome/internal/client/api/entity/capability/CapabilityStateDTO.java
@@ -0,0 +1,413 @@
+/**
+ * Copyright (c) 2010-2022 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.livisismarthome.internal.client.api.entity.capability;
+
+/**
+ * Defines the {@link CapabilityStateDTO}, that holds the state of a {@link CapabilityDTO}, e.g. a temperature.
+ *
+ * @author Oliver Kuhl - Initial contribution
+ */
+public class CapabilityStateDTO {
+
+ public static final String STATE_VALUE_OPERATION_MODE_AUTO = "Auto";
+ public static final String STATE_VALUE_OPERATION_MODE_MANUAL = "Manu";
+
+ /**
+ * id of the {@link CapabilityDTO}
+ */
+ private String id;
+
+ /**
+ * class containing all states
+ */
+ private StateDTO state;
+
+ public CapabilityStateDTO() {
+ state = new StateDTO();
+ }
+
+ /**
+ * @return the id
+ */
+ public String getId() {
+ return id;
+ }
+
+ /**
+ * @param id the id to set
+ */
+ public void setId(final String id) {
+ this.id = id;
+ }
+
+ /**
+ * @return the state
+ */
+ public StateDTO getState() {
+ return state;
+ }
+
+ /**
+ * @param state the state to set
+ */
+ public void setState(final StateDTO state) {
+ this.state = state;
+ }
+
+ public Boolean getVariableActuatorState() {
+ return getState().getValueState().getValue();
+ }
+
+ public void setVariableActuatorState(final Boolean on) {
+ getState().getValueState().setValue(on);
+ }
+
+ public Boolean getSwitchActuatorState() {
+ return getState().getOnState().getValue();
+ }
+
+ public void setSwitchActuatorState(final Boolean on) {
+ getState().getOnState().setValue(on);
+ }
+
+ public Double getTemperatureSensorTemperatureState() {
+ return getState().getTemperatureState().getValue();
+ }
+
+ public void setTemperatureSensorTemperatureState(final Double temperature) {
+ getState().getTemperatureState().setValue(temperature);
+ }
+
+ public Boolean getTemperatureSensorFrostWarningState() {
+ return getState().getFrostWarningState().getValue();
+ }
+
+ public void setTemperatureSensorFrostWarningState(final Boolean frostWarning) {
+ getState().getFrostWarningState().setValue(frostWarning);
+ }
+
+ public Double getThermostatActuatorPointTemperatureState() {
+ return getState().getPointTemperatureState().getValue();
+ }
+
+ public void setThermostatActuatorPointTemperatureState(final Double pointTemperature) {
+ getState().getPointTemperatureState().setValue(pointTemperature);
+ }
+
+ public String getThermostatActuatorOperationModeState() {
+ return getState().getOperationModeState().getValue();
+ }
+
+ public void setThermostatActuatorOperationModeState(final String operationMode) {
+ if (STATE_VALUE_OPERATION_MODE_MANUAL.equals(operationMode)) {
+ getState().getOperationModeState().setValue(STATE_VALUE_OPERATION_MODE_MANUAL);
+ } else {
+ getState().getOperationModeState().setValue(STATE_VALUE_OPERATION_MODE_AUTO);
+ }
+ }
+
+ public Boolean getThermostatActuatorWindowReductionActiveState() {
+ return getState().getWindowReductionActiveState().getValue();
+ }
+
+ public void setThermostatActuatorWindowReductionActiveState(final Boolean windowReductionActive) {
+ getState().getWindowReductionActiveState().setValue(windowReductionActive);
+ }
+
+ public Double getHumiditySensorHumidityState() {
+ return getState().getHumidityState().getValue();
+ }
+
+ public void setHumiditySensorHumidityState(final Double humidity) {
+ getState().getHumidityState().setValue(humidity);
+ }
+
+ public Boolean getHumiditySensorMoldWarningState() {
+ return getState().getMoldWarningState().getValue();
+ }
+
+ public void setHumiditySensorMoldWarningState(final Boolean moldWarning) {
+ getState().getMoldWarningState().setValue(moldWarning);
+ }
+
+ public Boolean getWindowDoorSensorState() {
+ return getState().getIsOpenState().getValue();
+ }
+
+ public void setWindowDoorSensorState(final Boolean open) {
+ getState().getIsOpenState().setValue(open);
+ }
+
+ public Boolean getSmokeDetectorSensorState() {
+ return getState().getIsSmokeAlarmState().getValue();
+ }
+
+ public void setSmokeDetectorSensorState(final Boolean on) {
+ getState().getIsSmokeAlarmState().setValue(on);
+ }
+
+ public Boolean getAlarmActuatorState() {
+ return getState().getOnState().getValue();
+ }
+
+ public void setAlarmActuatorState(final Boolean on) {
+ getState().getOnState().setValue(on);
+ }
+
+ public Integer getMotionDetectionSensorState() {
+ return getState().getMotionDetectedCountState().getValue();
+ }
+
+ public void setMotionDetectionSensorState(final Integer numberOfMotions) {
+ getState().getMotionDetectedCountState().setValue(numberOfMotions);
+ }
+
+ public Double getLuminanceSensorState() {
+ return getState().getLuminanceState().getValue();
+ }
+
+ public void setLuminanceSensorState(final Double luminance) {
+ getState().getLuminanceState().setValue(luminance);
+ }
+
+ public Integer getPushButtonSensorCounterState() {
+ return getState().getLastKeyPressCounterState().getValue();
+ }
+
+ public void setPushButtonSensorCounterState(final Integer numberOfPresses) {
+ getState().getLastKeyPressCounterState().setValue(numberOfPresses);
+ }
+
+ public Integer getPushButtonSensorButtonIndexState() {
+ return getState().getLastPressedButtonIndex().getValue();
+ }
+
+ public void setPushButtonSensorButtonIndexState(final Integer buttonIndex) {
+ getState().getLastPressedButtonIndex().setValue(buttonIndex);
+ }
+
+ public String getPushButtonSensorButtonIndexType() {
+ return getState().getLastKeyPressType().getValue();
+ }
+
+ public void setPushButtonSensorButtonIndexType(String lastKeyPressType) {
+ getState().getLastKeyPressType().setValue(lastKeyPressType);
+ }
+
+ public Integer getDimmerActuatorState() {
+ return getState().getDimLevelState().getValue();
+ }
+
+ public void setDimmerActuatorState(final Integer DimLevel) {
+ getState().getDimLevelState().setValue(DimLevel);
+ }
+
+ public Integer getRollerShutterActuatorState() {
+ return getState().getShutterLevelState().getValue();
+ }
+
+ public void setRollerShutterActuatorState(final Integer rollerShutterLevel) {
+ getState().getShutterLevelState().setValue(rollerShutterLevel);
+ }
+
+ // ENERGY CONSUMPTION SENSOR
+ public Double getEnergyConsumptionSensorEnergyConsumptionMonthKWhState() {
+ return getState().getEnergyConsumptionMonthKWhState().getValue();
+ }
+
+ public void setEnergyConsumptionSensorEnergyConsumptionMonthKWhState(final Double state) {
+ getState().getEnergyConsumptionMonthKWhState().setValue(state);
+ }
+
+ public Double getEnergyConsumptionSensorAbsoluteEnergyConsumptionState() {
+ return getState().getAbsoluteEnergyConsumptionState().getValue();
+ }
+
+ public void setEnergyConsumptionSensorAbsoluteEnergyConsumptionState(final Double state) {
+ getState().getAbsoluteEnergyConsumptionState().setValue(state);
+ }
+
+ public Double getEnergyConsumptionSensorEnergyConsumptionMonthEuroState() {
+ return getState().getEnergyConsumptionMonthEuroState().getValue();
+ }
+
+ public void setEnergyConsumptionSensorEnergyConsumptionMonthEuroState(final Double state) {
+ getState().getEnergyConsumptionMonthEuroState().setValue(state);
+ }
+
+ public Double getEnergyConsumptionSensorEnergyConsumptionDayEuroState() {
+ return getState().getEnergyConsumptionDayEuroState().getValue();
+ }
+
+ public void setEnergyConsumptionSensorEnergyConsumptionDayEuroState(final Double state) {
+ getState().getEnergyConsumptionDayEuroState().setValue(state);
+ }
+
+ public Double getEnergyConsumptionSensorEnergyConsumptionDayKWhState() {
+ return getState().getEnergyConsumptionDayKWhState().getValue();
+ }
+
+ public void setEnergyConsumptionSensorEnergyConsumptionDayKWhState(final Double state) {
+ getState().getEnergyConsumptionDayKWhState().setValue(state);
+ }
+
+ // POWER CONSUMPTION SENSOR
+ public Double getPowerConsumptionSensorPowerConsumptionWattState() {
+ return getState().getPowerConsumptionWattState().getValue();
+ }
+
+ public void setPowerConsumptionSensorPowerConsumptionWattState(final Double state) {
+ getState().getPowerConsumptionWattState().setValue(state);
+ }
+
+ // GENERATION METER ENGERY SENSOR
+ public Double getGenerationMeterEnergySensorEnergyPerMonthInKWhState() {
+ return getState().getEnergyPerMonthInKWhState().getValue();
+ }
+
+ public void setGenerationMeterEnergySensorEnergyPerMonthInKWhState(final Double state) {
+ getState().getEnergyPerMonthInKWhState().setValue(state);
+ }
+
+ public Double getGenerationMeterEnergySensorTotalEnergyState() {
+ return getState().getTotalEnergyState().getValue();
+ }
+
+ public void setGenerationMeterEnergySensorTotalEnergyState(final Double state) {
+ getState().getTotalEnergyState().setValue(state);
+ }
+
+ public Double getGenerationMeterEnergySensorEnergyPerMonthInEuroState() {
+ return getState().getEnergyPerMonthInEuroState().getValue();
+ }
+
+ public void setGenerationMeterEnergySensorEnergyPerMonthInEuroState(final Double state) {
+ getState().getEnergyPerMonthInEuroState().setValue(state);
+ }
+
+ public Double getGenerationMeterEnergySensorEnergyPerDayInEuroState() {
+ return getState().getEnergyPerDayInEuroState().getValue();
+ }
+
+ public void setGenerationMeterEnergySensorEnergyPerDayInEuroState(final Double state) {
+ getState().getEnergyPerDayInEuroState().setValue(state);
+ }
+
+ public Double getGenerationMeterEnergySensorEnergyPerDayInKWhState() {
+ return getState().getEnergyPerDayInKWhState().getValue();
+ }
+
+ public void setGenerationMeterEnergySensorEnergyPerDayInKWhState(final Double state) {
+ getState().getEnergyPerDayInKWhState().setValue(state);
+ }
+
+ // GENERATION METER POWER CONSUMPTION SENSOR
+ public Double getGenerationMeterPowerConsumptionSensorPowerInWattState() {
+ return getState().getPowerInWattState().getValue();
+ }
+
+ public void setGenerationMeterPowerConsumptionSensorPowerInWattState(final Double state) {
+ getState().getPowerInWattState().setValue(state);
+ }
+
+ // TWO WAY METER ENERGY CONSUMPTION SENSOR
+ public Double getTwoWayMeterEnergyConsumptionSensorEnergyPerMonthInKWhState() {
+ return getState().getEnergyPerMonthInKWhState().getValue();
+ }
+
+ public void setTwoWayMeterEnergyConsumptionSensorEnergyPerMonthInKWhState(final Double state) {
+ getState().getEnergyPerMonthInKWhState().setValue(state);
+ }
+
+ public Double getTwoWayMeterEnergyConsumptionSensorTotalEnergyState() {
+ return getState().getTotalEnergyState().getValue();
+ }
+
+ public void setTwoWayMeterEnergyConsumptionSensorTotalEnergyState(final Double state) {
+ getState().getTotalEnergyState().setValue(state);
+ }
+
+ public Double getTwoWayMeterEnergyConsumptionSensorEnergyPerMonthInEuroState() {
+ return getState().getEnergyPerMonthInEuroState().getValue();
+ }
+
+ public void setTwoWayMeterEnergyConsumptionSensorEnergyPerMonthInEuroState(final Double state) {
+ getState().getEnergyPerMonthInEuroState().setValue(state);
+ }
+
+ public Double getTwoWayMeterEnergyConsumptionSensorEnergyPerDayInEuroState() {
+ return getState().getEnergyPerDayInEuroState().getValue();
+ }
+
+ public void setTwoWayMeterEnergyConsumptionSensorEnergyPerDayInEuroState(final Double state) {
+ getState().getEnergyPerDayInEuroState().setValue(state);
+ }
+
+ public Double getTwoWayMeterEnergyConsumptionSensorEnergyPerDayInKWhState() {
+ return getState().getEnergyPerDayInKWhState().getValue();
+ }
+
+ public void setTwoWayMeterEnergyConsumptionSensorEnergyPerDayInKWhState(final Double state) {
+ getState().getEnergyPerDayInKWhState().setValue(state);
+ }
+
+ // TWO WAY METER ENERGY FEED SENSOR
+ public Double getTwoWayMeterEnergyFeedSensorEnergyPerMonthInKWhState() {
+ return getState().getEnergyPerMonthInKWhState().getValue();
+ }
+
+ public void setTwoWayMeterEnergyFeedSensorEnergyPerMonthInKWhState(final Double state) {
+ getState().getEnergyPerMonthInKWhState().setValue(state);
+ }
+
+ public Double getTwoWayMeterEnergyFeedSensorTotalEnergyState() {
+ return getState().getTotalEnergyState().getValue();
+ }
+
+ public void setTwoWayMeterEnergyFeedSensorTotalEnergyState(final Double state) {
+ getState().getTotalEnergyState().setValue(state);
+ }
+
+ public Double getTwoWayMeterEnergyFeedSensorEnergyPerMonthInEuroState() {
+ return getState().getEnergyPerMonthInEuroState().getValue();
+ }
+
+ public void setTwoWayMeterEnergyFeedSensorEnergyPerMonthInEuroState(final Double state) {
+ getState().getEnergyPerMonthInEuroState().setValue(state);
+ }
+
+ public Double getTwoWayMeterEnergyFeedSensorEnergyPerDayInEuroState() {
+ return getState().getEnergyPerDayInEuroState().getValue();
+ }
+
+ public void setTwoWayMeterEnergyFeedSensorEnergyPerDayInEuroState(final Double state) {
+ getState().getEnergyPerDayInEuroState().setValue(state);
+ }
+
+ public Double getTwoWayMeterEnergyFeedSensorEnergyPerDayInKWhState() {
+ return getState().getEnergyPerDayInKWhState().getValue();
+ }
+
+ public void setTwoWayMeterEnergyFeedSensorEnergyPerDayInKWhState(final Double state) {
+ getState().getEnergyPerDayInKWhState().setValue(state);
+ }
+
+ // TWO WAY METER POWER CONSUMPTION SENSOR
+ public Double getTwoWayMeterPowerConsumptionSensorPowerInWattState() {
+ return getState().getPowerInWattState().getValue();
+ }
+
+ public void setTwoWayMeterPowerConsumptionSensorPowerInWattState(final Double state) {
+ getState().getPowerInWattState().setValue(state);
+ }
+}
diff --git a/bundles/org.openhab.binding.livisismarthome/src/main/java/org/openhab/binding/livisismarthome/internal/client/api/entity/capability/StateDTO.java b/bundles/org.openhab.binding.livisismarthome/src/main/java/org/openhab/binding/livisismarthome/internal/client/api/entity/capability/StateDTO.java
new file mode 100644
index 00000000000..aa59bc54369
--- /dev/null
+++ b/bundles/org.openhab.binding.livisismarthome/src/main/java/org/openhab/binding/livisismarthome/internal/client/api/entity/capability/StateDTO.java
@@ -0,0 +1,803 @@
+/**
+ * Copyright (c) 2010-2022 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.livisismarthome.internal.client.api.entity.capability;
+
+import org.openhab.binding.livisismarthome.internal.client.api.entity.state.BooleanStateDTO;
+import org.openhab.binding.livisismarthome.internal.client.api.entity.state.DateTimeStateDTO;
+import org.openhab.binding.livisismarthome.internal.client.api.entity.state.DoubleStateDTO;
+import org.openhab.binding.livisismarthome.internal.client.api.entity.state.IntegerStateDTO;
+import org.openhab.binding.livisismarthome.internal.client.api.entity.state.StringStateDTO;
+
+import com.google.gson.annotations.SerializedName;
+
+/**
+ * Holds the Capability state.
+ *
+ * @author Oliver Kuhl - Initial contribution
+ *
+ */
+public class StateDTO {
+
+ @SerializedName("absoluteEnergyConsumption")
+ private DoubleStateDTO absoluteEnergyConsumptionState;
+
+ @SerializedName("activeChannel")
+ private StringStateDTO activeChannelState;
+
+ @SerializedName("dimLevel")
+ private IntegerStateDTO dimLevelState;
+
+ @SerializedName("energyConsumptionDayEuro")
+ private DoubleStateDTO energyConsumptionDayEuroState;
+
+ @SerializedName("energyConsumptionDayKWh")
+ private DoubleStateDTO energyConsumptionDayKWhState;
+
+ @SerializedName("energyConsumptionMonthEuro")
+ private DoubleStateDTO energyConsumptionMonthEuroState;
+
+ @SerializedName("energyConsumptionMonthKWh")
+ private DoubleStateDTO energyConsumptionMonthKWhState;
+
+ @SerializedName("energyPerDayInEuro")
+ private DoubleStateDTO energyPerDayInEuroState;
+
+ @SerializedName("energyPerDayInKWh")
+ private DoubleStateDTO energyPerDayInKWhState;
+
+ @SerializedName("energyPerMonthInEuro")
+ private DoubleStateDTO energyPerMonthInEuroState;
+
+ @SerializedName("energyPerMonthInKWh")
+ private DoubleStateDTO energyPerMonthInKWhState;
+
+ @SerializedName("frostWarning")
+ private BooleanStateDTO frostWarningState;
+
+ @SerializedName("humidity")
+ private DoubleStateDTO humidityState;
+
+ @SerializedName("isDay")
+ private BooleanStateDTO isDayState;
+
+ @SerializedName("isOn")
+ private BooleanStateDTO isOnState;
+
+ @SerializedName("isOpen")
+ private BooleanStateDTO isOpenState;
+
+ @SerializedName("isSmokeAlarm")
+ private BooleanStateDTO isSmokeAlarmState;
+
+ @SerializedName("lastKeyPressCounter")
+ private IntegerStateDTO lastKeyPressCounterState;
+
+ @SerializedName("lastPressedButtonIndex")
+ private IntegerStateDTO lastPressedButtonIndex;
+
+ private StringStateDTO lastPressedButtonIndexState;
+
+ @SerializedName("luminance")
+ private DoubleStateDTO luminanceState;
+
+ @SerializedName("moldWarning")
+ private BooleanStateDTO moldWarningState;
+
+ @SerializedName("motionDetectedCount")
+ private IntegerStateDTO motionDetectedCountState;
+
+ @SerializedName("nextSunrise")
+ private DateTimeStateDTO nextSunrise;
+
+ @SerializedName("nextSunset")
+ private DateTimeStateDTO nextSunsetState;
+
+ @SerializedName("nextTimeEvent")
+ private DateTimeStateDTO nextTimeEventState;
+
+ @SerializedName("onState")
+ private BooleanStateDTO onState;
+
+ @SerializedName("operationMode")
+ private StringStateDTO operationModeState;
+
+ @SerializedName("pointTemperature")
+ private DoubleStateDTO pointTemperatureState;
+
+ @SerializedName("powerConsumptionWatt")
+ private DoubleStateDTO powerConsumptionWattState;
+
+ @SerializedName("powerInWatt")
+ private DoubleStateDTO powerInWattState;
+
+ @SerializedName("shutterLevel")
+ private IntegerStateDTO shutterLevelState;
+
+ @SerializedName("supplyValueInCubicMetterPerDay")
+ private DoubleStateDTO supplyValueInCubicMetterPerDayState;
+
+ @SerializedName("supplyValueInCubicMetterPerMonth")
+ private DoubleStateDTO supplyValueInCubicMetterPerMonthState;
+
+ @SerializedName("supplyValueInCurrencyPerDay")
+ private DoubleStateDTO supplyValueInCurrencyPerDayState;
+
+ @SerializedName("supplyValueInCurrencyPerMonth")
+ private DoubleStateDTO supplyValueInCurrencyPerMonthState;
+
+ @SerializedName("supplyValueInLitrePerDay")
+ private DoubleStateDTO supplyValueInLitrePerDayState;
+
+ @SerializedName("supplyValueInLitrePerMonth")
+ private DoubleStateDTO supplyValueInLitrePerMonthState;
+
+ @SerializedName("temperature")
+ private DoubleStateDTO temperatureState;
+
+ @SerializedName("totalEnergy")
+ private DoubleStateDTO totalEnergyState;
+
+ @SerializedName("value")
+ private BooleanStateDTO valueState;
+
+ @SerializedName("valvePosition")
+ private BooleanStateDTO valvePositionState;
+
+ @SerializedName("windowReductionActive")
+ private BooleanStateDTO windowReductionActiveState;
+
+ public StateDTO() {
+ absoluteEnergyConsumptionState = new DoubleStateDTO();
+ activeChannelState = new StringStateDTO();
+ dimLevelState = new IntegerStateDTO();
+ energyConsumptionDayEuroState = new DoubleStateDTO();
+ energyConsumptionDayKWhState = new DoubleStateDTO();
+ energyConsumptionMonthEuroState = new DoubleStateDTO();
+ energyConsumptionMonthKWhState = new DoubleStateDTO();
+ energyPerDayInEuroState = new DoubleStateDTO();
+ energyPerDayInKWhState = new DoubleStateDTO();
+ energyPerMonthInEuroState = new DoubleStateDTO();
+ energyPerMonthInKWhState = new DoubleStateDTO();
+ frostWarningState = new BooleanStateDTO();
+ humidityState = new DoubleStateDTO();
+ isDayState = new BooleanStateDTO();
+ isOnState = new BooleanStateDTO();
+ isOpenState = new BooleanStateDTO();
+ isSmokeAlarmState = new BooleanStateDTO();
+ lastKeyPressCounterState = new IntegerStateDTO();
+ lastPressedButtonIndex = new IntegerStateDTO();
+ lastPressedButtonIndexState = new StringStateDTO();
+ luminanceState = new DoubleStateDTO();
+ moldWarningState = new BooleanStateDTO();
+ motionDetectedCountState = new IntegerStateDTO();
+ nextSunrise = new DateTimeStateDTO();
+ nextSunsetState = new DateTimeStateDTO();
+ nextTimeEventState = new DateTimeStateDTO();
+ onState = new BooleanStateDTO();
+ operationModeState = new StringStateDTO();
+ pointTemperatureState = new DoubleStateDTO();
+ powerConsumptionWattState = new DoubleStateDTO();
+ powerInWattState = new DoubleStateDTO();
+ shutterLevelState = new IntegerStateDTO();
+ supplyValueInCubicMetterPerDayState = new DoubleStateDTO();
+ supplyValueInCubicMetterPerMonthState = new DoubleStateDTO();
+ supplyValueInCurrencyPerDayState = new DoubleStateDTO();
+ supplyValueInCurrencyPerMonthState = new DoubleStateDTO();
+ supplyValueInLitrePerDayState = new DoubleStateDTO();
+ supplyValueInLitrePerMonthState = new DoubleStateDTO();
+ temperatureState = new DoubleStateDTO();
+ totalEnergyState = new DoubleStateDTO();
+ valueState = new BooleanStateDTO();
+ valvePositionState = new BooleanStateDTO();
+ windowReductionActiveState = new BooleanStateDTO();
+ }
+
+ /**
+ * @return the absoluteEnergyConsumptionState
+ */
+ public DoubleStateDTO getAbsoluteEnergyConsumptionState() {
+ return absoluteEnergyConsumptionState;
+ }
+
+ /**
+ * @param state the absoluteEnergyConsumptionState to set
+ */
+ public void setAbsoluteEnergyConsumptionState(DoubleStateDTO state) {
+ this.absoluteEnergyConsumptionState = state;
+ }
+
+ /**
+ * @return the activeChannelState
+ */
+ public StringStateDTO getActiveChannelState() {
+ return activeChannelState;
+ }
+
+ /**
+ * @param state the activeChannelState to set
+ */
+ public void setActiveChannelState(StringStateDTO state) {
+ this.activeChannelState = state;
+ }
+
+ /**
+ * @return the dimLevelState
+ */
+ public IntegerStateDTO getDimLevelState() {
+ return dimLevelState;
+ }
+
+ /**
+ * @param state the dimLevelState to set
+ */
+ public void setDimLevelState(IntegerStateDTO state) {
+ this.dimLevelState = state;
+ }
+
+ /**
+ * @return the energyConsumptionDayEuroState
+ */
+ public DoubleStateDTO getEnergyConsumptionDayEuroState() {
+ return energyConsumptionDayEuroState;
+ }
+
+ /**
+ * @param state the energyConsumptionDayEuroState to set
+ */
+ public void setEnergyConsumptionDayEuroState(DoubleStateDTO state) {
+ this.energyConsumptionDayEuroState = state;
+ }
+
+ /**
+ * @return the energyConsumptionDayKWhState
+ */
+ public DoubleStateDTO getEnergyConsumptionDayKWhState() {
+ return energyConsumptionDayKWhState;
+ }
+
+ /**
+ * @param state the energyConsumptionDayKWhState to set
+ */
+ public void setEnergyConsumptionDayKWhState(DoubleStateDTO state) {
+ this.energyConsumptionDayKWhState = state;
+ }
+
+ /**
+ * @return the energyConsumptionMonthEuroState
+ */
+ public DoubleStateDTO getEnergyConsumptionMonthEuroState() {
+ return energyConsumptionMonthEuroState;
+ }
+
+ /**
+ * @param state the energyConsumptionMonthEuroState to set
+ */
+ public void setEnergyConsumptionMonthEuroState(DoubleStateDTO state) {
+ this.energyConsumptionMonthEuroState = state;
+ }
+
+ /**
+ * @return the energyConsumptionMonthKWhState
+ */
+ public DoubleStateDTO getEnergyConsumptionMonthKWhState() {
+ return energyConsumptionMonthKWhState;
+ }
+
+ /**
+ * @param state the energyConsumptionMonthKWhState to set
+ */
+ public void setEnergyConsumptionMonthKWhState(DoubleStateDTO state) {
+ this.energyConsumptionMonthKWhState = state;
+ }
+
+ /**
+ * @return the energyPerDayInEuroState
+ */
+ public DoubleStateDTO getEnergyPerDayInEuroState() {
+ return energyPerDayInEuroState;
+ }
+
+ /**
+ * @param state the energyPerDayInEuroState to set
+ */
+ public void setEnergyPerDayInEuroState(DoubleStateDTO state) {
+ this.energyPerDayInEuroState = state;
+ }
+
+ /**
+ * @return the energyPerDayInKWhState
+ */
+ public DoubleStateDTO getEnergyPerDayInKWhState() {
+ return energyPerDayInKWhState;
+ }
+
+ /**
+ * @param state the energyPerDayInKWhState to set
+ */
+ public void setEnergyPerDayInKWhState(DoubleStateDTO state) {
+ this.energyPerDayInKWhState = state;
+ }
+
+ /**
+ * @return the energyPerMonthInEuroState
+ */
+ public DoubleStateDTO getEnergyPerMonthInEuroState() {
+ return energyPerMonthInEuroState;
+ }
+
+ /**
+ * @param state the energyPerMonthInEuroState to set
+ */
+ public void setEnergyPerMonthInEuroState(DoubleStateDTO state) {
+ this.energyPerMonthInEuroState = state;
+ }
+
+ /**
+ * @return the energyPerMonthInKWhState
+ */
+ public DoubleStateDTO getEnergyPerMonthInKWhState() {
+ return energyPerMonthInKWhState;
+ }
+
+ /**
+ * @param state the energyPerMonthInKWhState to set
+ */
+ public void setEnergyPerMonthInKWhState(DoubleStateDTO state) {
+ this.energyPerMonthInKWhState = state;
+ }
+
+ /**
+ * @return the frostWarningState
+ */
+ public BooleanStateDTO getFrostWarningState() {
+ return frostWarningState;
+ }
+
+ /**
+ * @param state the frostWarningState to set
+ */
+ public void setFrostWarningState(BooleanStateDTO state) {
+ this.frostWarningState = state;
+ }
+
+ /**
+ * @return the humidityState
+ */
+ public DoubleStateDTO getHumidityState() {
+ return humidityState;
+ }
+
+ /**
+ * @param state the humidityState to set
+ */
+ public void setHumidityState(DoubleStateDTO state) {
+ this.humidityState = state;
+ }
+
+ /**
+ * @return the isDayState
+ */
+ public BooleanStateDTO getIsDayState() {
+ return isDayState;
+ }
+
+ /**
+ * @param state the isDayState to set
+ */
+ public void setIsDayState(BooleanStateDTO state) {
+ this.isDayState = state;
+ }
+
+ /**
+ * @return the isOnState
+ */
+ public BooleanStateDTO getIsOnState() {
+ return isOnState;
+ }
+
+ /**
+ * @param state the isOnState to set
+ */
+ public void setIsOnState(BooleanStateDTO state) {
+ this.isOnState = state;
+ }
+
+ /**
+ * @return the isOpenState
+ */
+ public BooleanStateDTO getIsOpenState() {
+ return isOpenState;
+ }
+
+ /**
+ * @param state the isOpenState to set
+ */
+ public void setIsOpenState(BooleanStateDTO state) {
+ this.isOpenState = state;
+ }
+
+ /**
+ * @return the isSmokeAlarmState
+ */
+ public BooleanStateDTO getIsSmokeAlarmState() {
+ return isSmokeAlarmState;
+ }
+
+ /**
+ * @param state the isSmokeAlarmState to set
+ */
+ public void setIsSmokeAlarmState(BooleanStateDTO state) {
+ this.isSmokeAlarmState = state;
+ }
+
+ /**
+ * @return the lastKeyPressCounterState
+ */
+ public IntegerStateDTO getLastKeyPressCounterState() {
+ return lastKeyPressCounterState;
+ }
+
+ /**
+ * @param state the lastKeyPressCounterState to set
+ */
+ public void setLastKeyPressCounterState(IntegerStateDTO state) {
+ this.lastKeyPressCounterState = state;
+ }
+
+ /**
+ * @return the lastPressedButtonIndex
+ */
+ public IntegerStateDTO getLastPressedButtonIndex() {
+ return lastPressedButtonIndex;
+ }
+
+ /**
+ * @param state the lastPressedButtonIndex to set
+ */
+ public void setLastPressedButtonIndex(IntegerStateDTO state) {
+ this.lastPressedButtonIndex = state;
+ }
+
+ public StringStateDTO getLastKeyPressType() {
+ if (lastPressedButtonIndexState == null) {
+ lastPressedButtonIndexState = new StringStateDTO();
+ }
+ return lastPressedButtonIndexState;
+ }
+
+ public void setLastKeyPressType(StringStateDTO lastPressedButtonIndexState) {
+ this.lastPressedButtonIndexState = lastPressedButtonIndexState;
+ }
+
+ /**
+ * @return the luminanceState
+ */
+ public DoubleStateDTO getLuminanceState() {
+ return luminanceState;
+ }
+
+ /**
+ * @param state the luminanceState to set
+ */
+ public void setLuminanceState(DoubleStateDTO state) {
+ this.luminanceState = state;
+ }
+
+ /**
+ * @return the moldWarningState
+ */
+ public BooleanStateDTO getMoldWarningState() {
+ return moldWarningState;
+ }
+
+ /**
+ * @param state the moldWarningState to set
+ */
+ public void setMoldWarningState(BooleanStateDTO state) {
+ this.moldWarningState = state;
+ }
+
+ /**
+ * @return the motionDetectedCountState
+ */
+ public IntegerStateDTO getMotionDetectedCountState() {
+ return motionDetectedCountState;
+ }
+
+ /**
+ * @param state the motionDetectedCountState to set
+ */
+ public void setMotionDetectedCountState(IntegerStateDTO state) {
+ this.motionDetectedCountState = state;
+ }
+
+ /**
+ * @return the nextSunrise
+ */
+ public DateTimeStateDTO getNextSunrise() {
+ return nextSunrise;
+ }
+
+ /**
+ * @param state the nextSunrise to set
+ */
+ public void setNextSunrise(DateTimeStateDTO state) {
+ this.nextSunrise = state;
+ }
+
+ /**
+ * @return the nextSunsetState
+ */
+ public DateTimeStateDTO getNextSunsetState() {
+ return nextSunsetState;
+ }
+
+ /**
+ * @param state the nextSunsetState to set
+ */
+ public void setNextSunsetState(DateTimeStateDTO state) {
+ this.nextSunsetState = state;
+ }
+
+ /**
+ * @return the nextTimeEventState
+ */
+ public DateTimeStateDTO getNextTimeEventState() {
+ return nextTimeEventState;
+ }
+
+ /**
+ * @param state the nextTimeEventState to set
+ */
+ public void setNextTimeEventState(DateTimeStateDTO state) {
+ this.nextTimeEventState = state;
+ }
+
+ /**
+ * @return the onState
+ */
+ public BooleanStateDTO getOnState() {
+ return onState;
+ }
+
+ /**
+ * @param state the onState to set
+ */
+ public void setOnState(BooleanStateDTO state) {
+ this.onState = state;
+ }
+
+ /**
+ * @return the operationModeState
+ */
+ public StringStateDTO getOperationModeState() {
+ return operationModeState;
+ }
+
+ /**
+ * @param state the operationModeState to set
+ */
+ public void setOperationModeState(StringStateDTO state) {
+ this.operationModeState = state;
+ }
+
+ /**
+ * @return the pointTemperatureState
+ */
+ public DoubleStateDTO getPointTemperatureState() {
+ return pointTemperatureState;
+ }
+
+ /**
+ * @param state the pointTemperatureState to set
+ */
+ public void setPointTemperatureState(DoubleStateDTO state) {
+ this.pointTemperatureState = state;
+ }
+
+ /**
+ * @return the powerConsumptionWattState
+ */
+ public DoubleStateDTO getPowerConsumptionWattState() {
+ return powerConsumptionWattState;
+ }
+
+ /**
+ * @param state the powerConsumptionWattState to set
+ */
+ public void setPowerConsumptionWattState(DoubleStateDTO state) {
+ this.powerConsumptionWattState = state;
+ }
+
+ /**
+ * @return the powerInWattState
+ */
+ public DoubleStateDTO getPowerInWattState() {
+ return powerInWattState;
+ }
+
+ /**
+ * @param state the powerInWattState to set
+ */
+ public void setPowerInWattState(DoubleStateDTO state) {
+ this.powerInWattState = state;
+ }
+
+ /**
+ * @return the shutterLevelState
+ */
+ public IntegerStateDTO getShutterLevelState() {
+ return shutterLevelState;
+ }
+
+ /**
+ * @param state the shutterLevelState to set
+ */
+ public void setShutterLevelState(IntegerStateDTO state) {
+ this.shutterLevelState = state;
+ }
+
+ /**
+ * @return the supplyValueInCubicMetterPerDayState
+ */
+ public DoubleStateDTO getSupplyValueInCubicMetterPerDayState() {
+ return supplyValueInCubicMetterPerDayState;
+ }
+
+ /**
+ * @param state the supplyValueInCubicMetterPerDayState to set
+ */
+ public void setSupplyValueInCubicMetterPerDayState(DoubleStateDTO state) {
+ this.supplyValueInCubicMetterPerDayState = state;
+ }
+
+ /**
+ * @return the supplyValueInCubicMetterPerMonthState
+ */
+ public DoubleStateDTO getSupplyValueInCubicMetterPerMonthState() {
+ return supplyValueInCubicMetterPerMonthState;
+ }
+
+ /**
+ * @param state the supplyValueInCubicMetterPerMonthState to set
+ */
+ public void setSupplyValueInCubicMetterPerMonthState(DoubleStateDTO state) {
+ this.supplyValueInCubicMetterPerMonthState = state;
+ }
+
+ /**
+ * @return the supplyValueInCurrencyPerDayState
+ */
+ public DoubleStateDTO getSupplyValueInCurrencyPerDayState() {
+ return supplyValueInCurrencyPerDayState;
+ }
+
+ /**
+ * @param state the supplyValueInCurrencyPerDayState to set
+ */
+ public void setSupplyValueInCurrencyPerDayState(DoubleStateDTO state) {
+ this.supplyValueInCurrencyPerDayState = state;
+ }
+
+ /**
+ * @return the supplyValueInCurrencyPerMonthState
+ */
+ public DoubleStateDTO getSupplyValueInCurrencyPerMonthState() {
+ return supplyValueInCurrencyPerMonthState;
+ }
+
+ /**
+ * @param state the supplyValueInCurrencyPerMonthState to set
+ */
+ public void setSupplyValueInCurrencyPerMonthState(DoubleStateDTO state) {
+ this.supplyValueInCurrencyPerMonthState = state;
+ }
+
+ /**
+ * @return the supplyValueInLitrePerDayState
+ */
+ public DoubleStateDTO getSupplyValueInLitrePerDayState() {
+ return supplyValueInLitrePerDayState;
+ }
+
+ /**
+ * @param state the supplyValueInLitrePerDayState to set
+ */
+ public void setSupplyValueInLitrePerDayState(DoubleStateDTO state) {
+ this.supplyValueInLitrePerDayState = state;
+ }
+
+ /**
+ * @return the supplyValueInLitrePerMonthState
+ */
+ public DoubleStateDTO getSupplyValueInLitrePerMonthState() {
+ return supplyValueInLitrePerMonthState;
+ }
+
+ /**
+ * @param state the supplyValueInLitrePerMonthState to set
+ */
+ public void setSupplyValueInLitrePerMonthState(DoubleStateDTO state) {
+ this.supplyValueInLitrePerMonthState = state;
+ }
+
+ /**
+ * @return the temperatureState
+ */
+ public DoubleStateDTO getTemperatureState() {
+ return temperatureState;
+ }
+
+ /**
+ * @param state the temperatureState to set
+ */
+ public void setTemperatureState(DoubleStateDTO state) {
+ this.temperatureState = state;
+ }
+
+ /**
+ * @return the totalEnergyState
+ */
+ public DoubleStateDTO getTotalEnergyState() {
+ return totalEnergyState;
+ }
+
+ /**
+ * @param state the totalEnergyState to set
+ */
+ public void setTotalEnergyState(DoubleStateDTO state) {
+ this.totalEnergyState = state;
+ }
+
+ /**
+ * @return the valueState
+ */
+ public BooleanStateDTO getValueState() {
+ return valueState;
+ }
+
+ /**
+ * @param state the valueState to set
+ */
+ public void setValueState(BooleanStateDTO state) {
+ this.valueState = state;
+ }
+
+ /**
+ * @return the valvePositionState
+ */
+ public BooleanStateDTO getValvePositionState() {
+ return valvePositionState;
+ }
+
+ /**
+ * @param state the valvePositionState to set
+ */
+ public void setValvePositionState(BooleanStateDTO state) {
+ this.valvePositionState = state;
+ }
+
+ /**
+ * @return the windowReductionActiveState
+ */
+ public BooleanStateDTO getWindowReductionActiveState() {
+ return windowReductionActiveState;
+ }
+
+ /**
+ * @param state the windowReductionActiveState to set
+ */
+ public void setWindowReductionActiveState(BooleanStateDTO state) {
+ this.windowReductionActiveState = state;
+ }
+}
diff --git a/bundles/org.openhab.binding.livisismarthome/src/main/java/org/openhab/binding/livisismarthome/internal/client/api/entity/device/DeviceConfigDTO.java b/bundles/org.openhab.binding.livisismarthome/src/main/java/org/openhab/binding/livisismarthome/internal/client/api/entity/device/DeviceConfigDTO.java
new file mode 100644
index 00000000000..c90ff5370f5
--- /dev/null
+++ b/bundles/org.openhab.binding.livisismarthome/src/main/java/org/openhab/binding/livisismarthome/internal/client/api/entity/device/DeviceConfigDTO.java
@@ -0,0 +1,390 @@
+/**
+ * Copyright (c) 2010-2022 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.livisismarthome.internal.client.api.entity.device;
+
+import java.time.ZonedDateTime;
+
+import org.openhab.binding.livisismarthome.internal.client.Util;
+
+import com.google.gson.annotations.SerializedName;
+
+/**
+ * Holds the configuration of the Device.
+ *
+ * @author Oliver Kuhl - Initial contribution
+ *
+ */
+public class DeviceConfigDTO {
+
+ private String name;
+ private String protocolId;
+ private String timeOfAcceptance;
+ private String timeOfDiscovery;
+ private String hardwareVersion;
+ private String softwareVersion;
+ private String firmwareVersion;
+ private String hostName;
+ private boolean activityLogEnabled;
+ private String configurationState;
+ @SerializedName("IPAddress")
+ private String ipAddress;
+ @SerializedName("MACAddress")
+ private String macAddress;
+ private String registrationTime;
+ private String timeZone;
+ private String shcType;
+ private String geoLocation;
+ private Double currentUTCOffset;
+ private Boolean backendConnectionMonitored;
+ @SerializedName("RFCommFailureNotification")
+ private Boolean rfCommFailureNotification;
+ private String displayCurrentTemperature;
+ private String underlyingDeviceIds;
+ private String meterId;
+ private String meterFirmwareVersion;
+
+ /**
+ * @return the name
+ */
+ public String getName() {
+ return name;
+ }
+
+ /**
+ * @param name the name to set
+ */
+ public void setName(String name) {
+ this.name = name;
+ }
+
+ /**
+ * @return the protocolId
+ */
+ public String getProtocolId() {
+ return protocolId;
+ }
+
+ /**
+ * @param protocolId the protocolId to set
+ */
+ public void setProtocolId(String protocolId) {
+ this.protocolId = protocolId;
+ }
+
+ /**
+ * Returns the time, when the {@link DeviceDTO} was added to the SHC configuration.
+ *
+ * @return time of acceptance
+ */
+ public ZonedDateTime getTimeOfAcceptance() {
+ if (timeOfAcceptance == null) {
+ return null;
+ }
+ return Util.timeStringToDate(timeOfAcceptance);
+ }
+
+ /**
+ * @param timeOfAcceptance the timeOfAcceptance to set
+ */
+ public void setTimeOfAcceptance(String timeOfAcceptance) {
+ this.timeOfAcceptance = timeOfAcceptance;
+ }
+
+ /**
+ * Returns the time, when the {@link DeviceDTO} was discovered by the SHC.
+ *
+ * @return time of discovery
+ */
+ public ZonedDateTime getTimeOfDiscovery() {
+ if (timeOfDiscovery == null) {
+ return null;
+ }
+ return Util.timeStringToDate(timeOfDiscovery);
+ }
+
+ /**
+ * @param timeOfDiscovery the timeOfDiscovery to set
+ */
+ public void setTimeOfDiscovery(String timeOfDiscovery) {
+ this.timeOfDiscovery = timeOfDiscovery;
+ }
+
+ /**
+ * @return the hardwareVersion
+ */
+ public String getHardwareVersion() {
+ return hardwareVersion;
+ }
+
+ /**
+ * @param hardwareVersion the hardwareVersion to set
+ */
+ public void setHardwareVersion(String hardwareVersion) {
+ this.hardwareVersion = hardwareVersion;
+ }
+
+ /**
+ * @return the softwareVersion
+ */
+ public String getSoftwareVersion() {
+ return softwareVersion;
+ }
+
+ /**
+ * @param softwareVersion the softwareVersion to set
+ */
+ public void setSoftwareVersion(String softwareVersion) {
+ this.softwareVersion = softwareVersion;
+ }
+
+ /**
+ * @return the firmwareVersion
+ */
+ public String getFirmwareVersion() {
+ return firmwareVersion;
+ }
+
+ /**
+ * @param firmwareVersion the firmwareVersion to set
+ */
+ public void setFirmwareVersion(String firmwareVersion) {
+ this.firmwareVersion = firmwareVersion;
+ }
+
+ /**
+ * @return the hostName
+ */
+ public String getHostName() {
+ return hostName;
+ }
+
+ /**
+ * @param hostName the hostName to set
+ */
+ public void setHostName(String hostName) {
+ this.hostName = hostName;
+ }
+
+ /**
+ * @return the activityLogEnabled
+ */
+ public boolean isActivityLogEnabled() {
+ return activityLogEnabled;
+ }
+
+ /**
+ * @param activityLogEnabled the activityLogEnabled to set
+ */
+ public void setActivityLogEnabled(boolean activityLogEnabled) {
+ this.activityLogEnabled = activityLogEnabled;
+ }
+
+ /**
+ * @return the configurationState
+ */
+ public String getConfigurationState() {
+ return configurationState;
+ }
+
+ /**
+ * @param configurationState the configurationState to set
+ */
+ public void setConfigurationState(String configurationState) {
+ this.configurationState = configurationState;
+ }
+
+ /**
+ * @return the iPAddress
+ */
+ public String getIPAddress() {
+ return ipAddress;
+ }
+
+ /**
+ * @param ipAddress the ipAddress to set
+ */
+ public void setIPAddress(String ipAddress) {
+ this.ipAddress = ipAddress;
+ }
+
+ /**
+ * @return the mACAddress
+ */
+ public String getMACAddress() {
+ return macAddress;
+ }
+
+ /**
+ * @param mACAddress the mACAddress to set
+ */
+ public void setMACAddress(String mACAddress) {
+ this.macAddress = mACAddress;
+ }
+
+ /**
+ * @return the registrationTime
+ */
+ public ZonedDateTime getRegistrationTime() {
+ if (registrationTime == null) {
+ return null;
+ }
+ return Util.timeStringToDate(registrationTime);
+ }
+
+ /**
+ * @param registrationTime the registrationTime to set
+ */
+ public void setRegistrationTime(String registrationTime) {
+ this.registrationTime = registrationTime;
+ }
+
+ /**
+ * @return the timeZone
+ */
+ public String getTimeZone() {
+ return timeZone;
+ }
+
+ /**
+ * @param timeZone the timeZone to set
+ */
+ public void setTimeZone(String timeZone) {
+ this.timeZone = timeZone;
+ }
+
+ /**
+ * @return the shcType
+ */
+ public String getShcType() {
+ return shcType;
+ }
+
+ /**
+ * @param shcType the shcType to set
+ */
+ public void setShcType(String shcType) {
+ this.shcType = shcType;
+ }
+
+ /**
+ * @return the geoLocation
+ */
+ public String getGeoLocation() {
+ return geoLocation;
+ }
+
+ /**
+ * @param geoLocation the geoLocation to set
+ */
+ public void setGeoLocation(String geoLocation) {
+ this.geoLocation = geoLocation;
+ }
+
+ /**
+ * @return the currentUTCOffset
+ */
+ public Double getCurrentUTCOffset() {
+ return currentUTCOffset;
+ }
+
+ /**
+ * @param currentUTCOffset the currentUTCOffset to set
+ */
+ public void setCurrentUTCOffset(Double currentUTCOffset) {
+ this.currentUTCOffset = currentUTCOffset;
+ }
+
+ /**
+ * @return the backendConnectionMonitored
+ */
+ public Boolean getBackendConnectionMonitored() {
+ return backendConnectionMonitored;
+ }
+
+ /**
+ * @param backendConnectionMonitored the backendConnectionMonitored to set
+ */
+ public void setBackendConnectionMonitored(Boolean backendConnectionMonitored) {
+ this.backendConnectionMonitored = backendConnectionMonitored;
+ }
+
+ /**
+ * @return the rFCommFailureNotification
+ */
+ public Boolean getRFCommFailureNotification() {
+ return rfCommFailureNotification;
+ }
+
+ /**
+ * @param rFCommFailureNotification the rFCommFailureNotification to set
+ */
+ public void setRFCommFailureNotification(Boolean rFCommFailureNotification) {
+ rfCommFailureNotification = rFCommFailureNotification;
+ }
+
+ /**
+ * @return the displayCurrentTemperature
+ */
+ public String getDisplayCurrentTemperature() {
+ return displayCurrentTemperature;
+ }
+
+ /**
+ * @param displayCurrentTemperature the displayCurrentTemperature to set
+ */
+ public void setDisplayCurrentTemperature(String displayCurrentTemperature) {
+ this.displayCurrentTemperature = displayCurrentTemperature;
+ }
+
+ /**
+ * @return the underlyingDeviceIds
+ */
+ public String getUnderlyingDeviceIds() {
+ return underlyingDeviceIds;
+ }
+
+ /**
+ * @param underlyingDeviceIds the underlyingDeviceIds to set
+ */
+ public void setUnderlyingDeviceIds(String underlyingDeviceIds) {
+ this.underlyingDeviceIds = underlyingDeviceIds;
+ }
+
+ /**
+ * @return the meterId
+ */
+ public String getMeterId() {
+ return meterId;
+ }
+
+ /**
+ * @param meterId the meterId to set
+ */
+ public void setMeterId(String meterId) {
+ this.meterId = meterId;
+ }
+
+ /**
+ * @return the meterFirmwareVersion
+ */
+ public String getMeterFirmwareVersion() {
+ return meterFirmwareVersion;
+ }
+
+ /**
+ * @param meterFirmwareVersion the meterFirmwareVersion to set
+ */
+ public void setMeterFirmwareVersion(String meterFirmwareVersion) {
+ this.meterFirmwareVersion = meterFirmwareVersion;
+ }
+}
diff --git a/bundles/org.openhab.binding.livisismarthome/src/main/java/org/openhab/binding/livisismarthome/internal/client/api/entity/device/DeviceDTO.java b/bundles/org.openhab.binding.livisismarthome/src/main/java/org/openhab/binding/livisismarthome/internal/client/api/entity/device/DeviceDTO.java
new file mode 100644
index 00000000000..f67eaf4a963
--- /dev/null
+++ b/bundles/org.openhab.binding.livisismarthome/src/main/java/org/openhab/binding/livisismarthome/internal/client/api/entity/device/DeviceDTO.java
@@ -0,0 +1,523 @@
+/**
+ * Copyright (c) 2010-2022 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.livisismarthome.internal.client.api.entity.device;
+
+import static org.openhab.binding.livisismarthome.internal.LivisiBindingConstants.*;
+
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+
+import org.eclipse.jdt.annotation.NonNull;
+import org.openhab.binding.livisismarthome.internal.client.api.entity.capability.CapabilityDTO;
+import org.openhab.binding.livisismarthome.internal.client.api.entity.location.LocationDTO;
+import org.openhab.binding.livisismarthome.internal.client.api.entity.message.MessageDTO;
+
+import com.google.gson.annotations.SerializedName;
+
+/**
+ * Defines the structure of a {@link DeviceDTO}.
+ *
+ * @author Oliver Kuhl - Initial contribution
+ */
+public class DeviceDTO {
+
+ private static final String PROTOCOL_ID_COSIP = "Cosip";
+ private static final String PROTOCOL_ID_VIRTUAL = "Virtual";
+ private static final String PROTOCOL_ID_WMBUS = "wMBus";
+
+ /**
+ * Unique id for the device, always available in model.
+ */
+ private String id;
+
+ /**
+ * Identifier of the manufacturer, always available in model
+ */
+ private String manufacturer;
+
+ /**
+ * Version number of the device for the domain model.
+ *
+ * If the functionality of the device changes, the version must
+ * be increased to indicate that there are new or changed attributes
+ * of the device. Always available in model.
+ */
+ private String version;
+
+ /**
+ * Defines the product, which is used as an identifier for selecting the
+ * right add-in to support the functionality of the device.
+ * Remark: one add-in can support multiple devices, e.g.
+ * core.RWE, which supports all RWE hardware devices (also referred to as core devices).
+ * Always available in model.
+ */
+ private String product;
+
+ /**
+ * Device number or id like SGTIN given by the manufacturer. Optional.
+ */
+ @SerializedName("serialnumber")
+ private String serialNumber;
+
+ /**
+ * Specifies the type of the device, which is defined by the manufacturer. The triple of device type, manufacturer
+ * and the version must be unique.
+ * Always available in model.
+ */
+ private String type;
+
+ private DeviceConfigDTO config;
+
+ private List capabilities;
+
+ private Map capabilityMap;
+
+ private DeviceStateDTO deviceState;
+
+ /*
+ * The tag container can contain any number of properties for grouping of the devices in the client, e.g. category
+ * of device like “security related”. The tags can be freely chosen by the client and will not be considered by the
+ * system for any business logic.
+ *
+ * Optional.
+ *
+ * @Key("tags")
+ * private List tagList;
+ */
+
+ /**
+ * The location contains the link to the location of the device. Optional.
+ */
+ @SerializedName("location")
+ private String locationLink;
+
+ private transient LocationDTO location;
+
+ private List messageList;
+
+ private boolean lowBattery;
+
+ /**
+ * Stores, if the {@link DeviceDTO} is battery powered.
+ */
+ private boolean batteryPowered = false;
+
+ /**
+ * @return the id
+ */
+ public String getId() {
+ return id;
+ }
+
+ /**
+ * @param id the id to set
+ */
+ public void setId(String id) {
+ this.id = id;
+ }
+
+ /**
+ * @return the manufacturer
+ */
+ public String getManufacturer() {
+ return manufacturer;
+ }
+
+ /**
+ * @param manufacturer the manufacturer to set
+ */
+ public void setManufacturer(String manufacturer) {
+ this.manufacturer = manufacturer;
+ }
+
+ /**
+ * @return the version
+ */
+ public String getVersion() {
+ return version;
+ }
+
+ /**
+ * @param version the version to set
+ */
+ public void setVersion(String version) {
+ this.version = version;
+ }
+
+ /**
+ * @return the product
+ */
+ public String getProduct() {
+ return product;
+ }
+
+ /**
+ * @param product the product to set
+ */
+ public void setProduct(String product) {
+ this.product = product;
+ }
+
+ /**
+ * @return the serialnumber
+ */
+ public String getSerialNumber() {
+ return serialNumber;
+ }
+
+ /**
+ * @param serialNumber the serialnumber to set
+ */
+ public void setSerialNumber(String serialNumber) {
+ this.serialNumber = serialNumber;
+ }
+
+ /**
+ * Returns true, if the {@link DeviceDTO} has a serial number.
+ *
+ * @return true if the device has serial number, otherwise false
+ */
+ public boolean hasSerialNumber() {
+ return serialNumber != null && !serialNumber.isEmpty();
+ }
+
+ /**
+ * @return the type
+ */
+ public String getType() {
+ return type;
+ }
+
+ /**
+ * @param type the type to set
+ */
+ public void setType(String type) {
+ this.type = type;
+ }
+
+ /**
+ * @return the config
+ */
+ public DeviceConfigDTO getConfig() {
+ return config;
+ }
+
+ /**
+ * @param config the config to set
+ */
+ public void setConfig(DeviceConfigDTO config) {
+ this.config = config;
+ }
+
+ /**
+ * Returns the {@link DeviceStateDTO}. Only available, if device has a state. Better check with
+ * {@link DeviceDTO#hasDeviceState()} first!
+ *
+ * @return the entityState or null
+ */
+ public DeviceStateDTO getDeviceState() {
+ return deviceState;
+ }
+
+ /**
+ * @param deviceState the deviceState to set
+ */
+ public void setDeviceState(DeviceStateDTO deviceState) {
+ this.deviceState = deviceState;
+ }
+
+ /**
+ * Returns, if the {@link DeviceDTO} has a state. Not all {@link DeviceDTO}s have a state.
+ *
+ * @return true if the device has a device state, otherwise false
+ */
+ public boolean hasDeviceState() {
+ return deviceState != null;
+ }
+
+ /**
+ * @return the capabilityList
+ */
+ public List getCapabilities() {
+ return Objects.requireNonNullElse(capabilities, Collections.emptyList());
+ }
+
+ /**
+ * @param capabilityList the capabilityList to set
+ */
+ public void setCapabilities(List capabilityList) {
+ this.capabilities = capabilityList;
+ }
+
+ /**
+ * @param capabilityMap the capabilityMap to set
+ */
+ public void setCapabilityMap(Map capabilityMap) {
+ this.capabilityMap = capabilityMap;
+ }
+
+ /**
+ * @return the capabilityMap
+ */
+ public Map getCapabilityMap() {
+ if (capabilityMap != null) {
+ return capabilityMap;
+ }
+ return Collections.emptyMap();
+ }
+
+ /**
+ * Returns the {@link CapabilityDTO} with the given id.
+ *
+ * @param id capability id
+ * @return capability
+ */
+ public CapabilityDTO getCapabilityWithId(String id) {
+ return this.capabilityMap.get(id);
+ }
+
+ /**
+ * @return the locationLink
+ */
+ public String getLocationLink() {
+ return locationLink;
+ }
+
+ /**
+ * @param locationLink the locationList to set
+ */
+ public void setLocation(String locationLink) {
+ this.locationLink = locationLink;
+ }
+
+ /**
+ * Returns the id of the {@link LocationDTO}
+ *
+ * @return location id
+ */
+ public String getLocationId() {
+ if (locationLink != null) {
+ return locationLink.replace("/location/", "");
+ }
+ return null;
+ }
+
+ /**
+ * Returns the {@link LocationDTO} of the {@link DeviceDTO}. Better check with {@link DeviceDTO#hasLocation()}
+ * first, as not
+ * all devices have one.
+ *
+ * @return the location
+ */
+ public LocationDTO getLocation() {
+ return location;
+ }
+
+ /**
+ * @param location the location to set
+ */
+ public void setLocation(LocationDTO location) {
+ this.location = location;
+ }
+
+ /**
+ * Returns, if the {@link DeviceDTO} has a {@link LocationDTO}. Not all devices have a {@link LocationDTO}...
+ *
+ * @return boolean true, if a {@link LocationDTO} is set, else false
+ */
+ public boolean hasLocation() {
+ return location != null;
+ }
+
+ public @NonNull String getLocationName() {
+ LocationDTO location = getLocation();
+ if (location != null && location.getName() != null) {
+ return location.getName();
+ }
+ return "";
+ }
+
+ /**
+ * @return the messageList
+ */
+ public List getMessageList() {
+ if (messageList != null) {
+ return messageList;
+ }
+ return Collections.emptyList();
+ }
+
+ /**
+ * @param messageList the messageList to set
+ */
+ public void setMessageList(List messageList) {
+ this.messageList = messageList;
+ applyMessageList(messageList);
+ }
+
+ private void applyMessageList(List messageList) {
+ if (messageList != null && !messageList.isEmpty()) {
+ boolean isUnreachableMessageFound = false;
+ boolean isLowBatteryMessageFound = false;
+ for (final MessageDTO message : messageList) {
+ switch (message.getType()) {
+ case MessageDTO.TYPE_DEVICE_UNREACHABLE:
+ isUnreachableMessageFound = true;
+ break;
+ case MessageDTO.TYPE_DEVICE_LOW_BATTERY:
+ isLowBatteryMessageFound = true;
+ break;
+ }
+ }
+ if (isUnreachableMessageFound) {
+ setReachable(false); // overwrite only when there is a corresponding message (to keep the state of the
+ // API in other cases)
+ }
+ if (isLowBatteryMessageFound) {
+ setLowBattery(true); // overwrite only when there is a corresponding message (to keep the state of the
+ // API in other cases)
+ }
+ }
+ }
+
+ /**
+ * Sets if the {@link DeviceDTO} is reachable;
+ *
+ * @param isReachable reachable (boolean)
+ */
+ private void setReachable(boolean isReachable) {
+ if (getDeviceState().hasIsReachableState()) {
+ getDeviceState().setReachable(isReachable);
+ }
+ }
+
+ /**
+ * Returns if the {@link DeviceDTO} is reachable.
+ *
+ * @return reachable (boolean)
+ */
+ public Boolean isReachable() {
+ if (hasDeviceState() && getDeviceState().hasIsReachableState()) {
+ return getDeviceState().isReachable();
+ }
+ return null;
+ }
+
+ /**
+ * Sets the low battery state for the {@link DeviceDTO}.
+ *
+ * @param isBatteryLow true if the battery is low, otherwise false
+ */
+ public void setLowBattery(boolean isBatteryLow) {
+ this.lowBattery = isBatteryLow;
+ }
+
+ /**
+ * Returns true if the {@link DeviceDTO} has a low battery warning. Only available on battery devices.
+ *
+ * @return true if the battery is low, otherwise false
+ */
+ public boolean hasLowBattery() {
+ return lowBattery;
+ }
+
+ /**
+ * Returns true, if the {@link DeviceDTO} is battery powered.
+ *
+ * @return true if the device is battery powered, otherwise false
+ */
+ public boolean isBatteryPowered() {
+ return batteryPowered;
+ }
+
+ /**
+ * Sets if the device is battery powered.
+ *
+ * @param isBatteryPowerd true if the device is battery powered, otherwise false
+ */
+ public void setIsBatteryPowered(boolean isBatteryPowerd) {
+ batteryPowered = isBatteryPowerd;
+ }
+
+ /**
+ * Returns true, if the {@link DeviceDTO} has {@link MessageDTO}s.
+ *
+ * @return true if messages accoring the device are available, otherwise false
+ */
+ public boolean hasMessages() {
+ return (messageList != null && !messageList.isEmpty());
+ }
+
+ /**
+ * Returns true if the device is a SmartHomeController (SHC).
+ *
+ * @return true if the device is a SmartHomeController (SHC) otherwise false
+ */
+ public boolean isController() {
+ return isClassicController() || DEVICE_SHCA.equals(type);
+ }
+
+ /**
+ * Returns true if the device is a classic controller (SHC, before Gen 2.).
+ *
+ * @return true if it is a classic controller, otherwise false
+ */
+ public boolean isClassicController() {
+ return DEVICE_SHC.equals(type);
+ }
+
+ /**
+ * Returns true, if the {@link DeviceDTO} is a virtual device (e.g. a VariableActuator).
+ *
+ * @return true if it is a virtual device, otherwise false
+ */
+ public boolean isVirtualDevice() {
+ return PROTOCOL_ID_VIRTUAL.equals(getConfig().getProtocolId());
+ }
+
+ /**
+ * Returns true, if the {@link DeviceDTO} is a radio device.
+ *
+ * @return true if it is a radio device, otherwise false
+ */
+ public boolean isRadioDevice() {
+ return isCoSipDevice() || isWMBusDevice();
+ }
+
+ /**
+ * Returns true, if the {@link DeviceDTO} is a CoSip device.
+ *
+ * @return true if it is a CoSip device, otherwise false
+ */
+ public boolean isCoSipDevice() {
+ return PROTOCOL_ID_COSIP.equals(getConfig().getProtocolId());
+ }
+
+ /**
+ * Returns true, if the {@link DeviceDTO} is a W-Mbus device.
+ *
+ * @return true if it is a W-Mbus device, otherwise false
+ */
+ public boolean isWMBusDevice() {
+ return PROTOCOL_ID_WMBUS.equals(getConfig().getProtocolId());
+ }
+
+ @Override
+ public String toString() {
+ return "Device [" + "id=" + getId() + " manufacturer=" + getManufacturer() + " version=" + getVersion()
+ + " product=" + getProduct() + " serialnumber=" + getSerialNumber() + " type=" + getType() + " name="
+ + getConfig().getName() + "]";
+ }
+}
diff --git a/bundles/org.openhab.binding.livisismarthome/src/main/java/org/openhab/binding/livisismarthome/internal/client/api/entity/device/DeviceStateDTO.java b/bundles/org.openhab.binding.livisismarthome/src/main/java/org/openhab/binding/livisismarthome/internal/client/api/entity/device/DeviceStateDTO.java
new file mode 100644
index 00000000000..7eca6f7ae75
--- /dev/null
+++ b/bundles/org.openhab.binding.livisismarthome/src/main/java/org/openhab/binding/livisismarthome/internal/client/api/entity/device/DeviceStateDTO.java
@@ -0,0 +1,150 @@
+/**
+ * Copyright (c) 2010-2022 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.livisismarthome.internal.client.api.entity.device;
+
+import java.util.HashMap;
+
+import org.openhab.binding.livisismarthome.internal.client.api.entity.PropertyDTO;
+
+/**
+ * Defines the {@link DeviceStateDTO}, e.g. if the device is reachable.
+ *
+ * @author Oliver Kuhl - Initial contribution
+ */
+public class DeviceStateDTO {
+
+ private static final String DEVICE_UPDATE_STATE_UPTODATE = "UpToDate";
+
+ private String id;
+ private StateDTO state;
+ private HashMap stateMap;
+
+ public DeviceStateDTO() {
+ state = new StateDTO();
+ stateMap = new HashMap<>();
+ }
+
+ /**
+ * @return the id
+ */
+ public String getId() {
+ return id;
+ }
+
+ /**
+ * @param id the id to set
+ */
+ public void setId(String id) {
+ this.id = id;
+ }
+
+ /**
+ * @return the state
+ */
+ public StateDTO getState() {
+ return state;
+ }
+
+ /**
+ * @param state the state to set
+ */
+ public void setState(StateDTO state) {
+ this.state = state;
+ }
+
+ /**
+ * Returns true if the device is reachable, false otherwise.
+ *
+ * @return true or false for "reachable" {@link DeviceDTO}s, else null.
+ */
+ public Boolean isReachable() {
+ return getState().getIsReachable().getValue();
+ }
+
+ /**
+ * Returns if the {@link StateDTO} "isReachable" is available for the current {@link DeviceDTO}.
+ *
+ * @return true if the reachable state is available, otherwise false
+ */
+ public Boolean hasIsReachableState() {
+ return getState().getIsReachable() != null;
+ }
+
+ /**
+ * Sets if the {@link DeviceDTO} is reachable.
+ *
+ * @param isReachable reachable (boolean)
+ */
+ public void setReachable(boolean isReachable) {
+ getState().getIsReachable().setValue(isReachable);
+ }
+
+ /**
+ * Returns the configuration state of the device.
+ *
+ * @return the configuration state
+ */
+ public String getDeviceConfigurationState() {
+ return getState().getDeviceConfigurationState().getValue();
+ }
+
+ /**
+ * Returns the device inclusion state.
+ *
+ * @return the device inclusion state
+ */
+ public String getDeviceInclusionState() {
+ return getState().getDeviceInclusionState().getValue();
+ }
+
+ /**
+ * @return the stateMap
+ */
+ public HashMap getStateMap() {
+ return stateMap;
+ }
+
+ /**
+ * @param stateMap the stateMap to set
+ */
+ public void setStateMap(HashMap stateMap) {
+ this.stateMap = stateMap;
+ }
+
+ /**
+ * Return the update state of the {@link DeviceDTO}.
+ *
+ * @return the update state
+ */
+ public String getDeviceUpdateState() {
+ return getState().getUpdateState().getValue();
+ }
+
+ /**
+ * Returns true if the {@link DeviceDTO} is up to date.
+ *
+ * @return true, if the deviceUpdateState is "UpToDate"
+ */
+ public Boolean deviceIsUpToDate() {
+ return DEVICE_UPDATE_STATE_UPTODATE.equals(getDeviceUpdateState());
+ }
+
+ /**
+ * Returns the firmware version of the {@link DeviceDTO}.
+ *
+ * @return the firmware version
+ */
+ public String getFirmwareVersion() {
+ return getState().getFirmwareVersion().getValue();
+ }
+}
diff --git a/bundles/org.openhab.binding.livisismarthome/src/main/java/org/openhab/binding/livisismarthome/internal/client/api/entity/device/GatewayDTO.java b/bundles/org.openhab.binding.livisismarthome/src/main/java/org/openhab/binding/livisismarthome/internal/client/api/entity/device/GatewayDTO.java
new file mode 100644
index 00000000000..bc687da91f7
--- /dev/null
+++ b/bundles/org.openhab.binding.livisismarthome/src/main/java/org/openhab/binding/livisismarthome/internal/client/api/entity/device/GatewayDTO.java
@@ -0,0 +1,40 @@
+/**
+ * Copyright (c) 2010-2022 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.livisismarthome.internal.client.api.entity.device;
+
+/**
+ * Defines the {@link GatewayDTO} structure.
+ *
+ * @author Oliver Kuhl - Initial contribution
+ */
+public class GatewayDTO {
+
+ /**
+ * Version of the configuration. Changes each time the configuration was changed via the LIVISI client app.
+ */
+ private String configVersion;
+
+ /**
+ * @return the configuration version
+ */
+ public String getConfigVersion() {
+ return configVersion;
+ }
+
+ /**
+ * @param configVersion the config version to set
+ */
+ public void setConfigVersion(String configVersion) {
+ this.configVersion = configVersion;
+ }
+}
diff --git a/bundles/org.openhab.binding.livisismarthome/src/main/java/org/openhab/binding/livisismarthome/internal/client/api/entity/device/StateDTO.java b/bundles/org.openhab.binding.livisismarthome/src/main/java/org/openhab/binding/livisismarthome/internal/client/api/entity/device/StateDTO.java
new file mode 100644
index 00000000000..b5e0a7e96f7
--- /dev/null
+++ b/bundles/org.openhab.binding.livisismarthome/src/main/java/org/openhab/binding/livisismarthome/internal/client/api/entity/device/StateDTO.java
@@ -0,0 +1,473 @@
+/**
+ * Copyright (c) 2010-2022 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.livisismarthome.internal.client.api.entity.device;
+
+import org.openhab.binding.livisismarthome.internal.client.api.entity.state.BooleanStateDTO;
+import org.openhab.binding.livisismarthome.internal.client.api.entity.state.DateTimeStateDTO;
+import org.openhab.binding.livisismarthome.internal.client.api.entity.state.DoubleStateDTO;
+import org.openhab.binding.livisismarthome.internal.client.api.entity.state.IntegerStateDTO;
+import org.openhab.binding.livisismarthome.internal.client.api.entity.state.StringStateDTO;
+
+import com.google.gson.annotations.SerializedName;
+
+/**
+ * Holds the state of the Device.
+ *
+ * @author Oliver Kuhl - Initial contribution
+ *
+ */
+public class StateDTO {
+ /** Standard device states */
+ @SerializedName("deviceInclusionState")
+ private StringStateDTO deviceInclusionState;
+ @SerializedName("deviceConfigurationState")
+ private StringStateDTO deviceConfigurationState;
+ private BooleanStateDTO isReachable;
+ private StringStateDTO updateState;
+ private StringStateDTO firmwareVersion;
+ @SerializedName("WHRating")
+ private DoubleStateDTO wHRating;
+
+ /** SHC device states */
+ // Removed updateAvailable because it is different between version 1 and 2 devices and not used anyway
+ // Related to openhab-addons #6613
+ // private StringState updateAvailable
+ private DateTimeStateDTO lastReboot;
+ private DoubleStateDTO memoryLoad;
+ @SerializedName("CPULoad")
+ private DoubleStateDTO cpuLoad;
+ @SerializedName("LBDongleAttached")
+ private BooleanStateDTO lBDongleAttached;
+ @SerializedName("MBusDongleAttached")
+ private BooleanStateDTO mBusDongleAttached;
+ private IntegerStateDTO configVersion;
+ @SerializedName("OSState")
+ private StringStateDTO osState;
+ private IntegerStateDTO wifiSignalStrength;
+ private StringStateDTO ethIpAddress;
+ private StringStateDTO wifiIpAddress;
+ private StringStateDTO ethMacAddress;
+ private StringStateDTO wifiMacAddress;
+ private StringStateDTO inUseAdapter;
+ private BooleanStateDTO discoveryActive;
+ private StringStateDTO operationStatus;
+ private DoubleStateDTO currentUtcOffset;
+ private DoubleStateDTO cpuUsage;
+ private DoubleStateDTO diskUsage;
+ private DoubleStateDTO memoryUsage;
+
+ public StateDTO() {
+ deviceInclusionState = new StringStateDTO();
+ deviceConfigurationState = new StringStateDTO();
+ isReachable = new BooleanStateDTO();
+ updateState = new StringStateDTO();
+ firmwareVersion = new StringStateDTO();
+ wHRating = new DoubleStateDTO();
+ lastReboot = new DateTimeStateDTO();
+ memoryLoad = new DoubleStateDTO();
+ cpuLoad = new DoubleStateDTO();
+ lBDongleAttached = new BooleanStateDTO();
+ mBusDongleAttached = new BooleanStateDTO();
+ configVersion = new IntegerStateDTO();
+ osState = new StringStateDTO();
+ wifiSignalStrength = new IntegerStateDTO();
+ ethIpAddress = new StringStateDTO();
+ wifiIpAddress = new StringStateDTO();
+ ethMacAddress = new StringStateDTO();
+ wifiMacAddress = new StringStateDTO();
+ inUseAdapter = new StringStateDTO();
+ discoveryActive = new BooleanStateDTO();
+ operationStatus = new StringStateDTO();
+ currentUtcOffset = new DoubleStateDTO();
+ cpuUsage = new DoubleStateDTO();
+ diskUsage = new DoubleStateDTO();
+ memoryUsage = new DoubleStateDTO();
+ }
+
+ /**
+ * @return the deviceInclusionState
+ */
+ public StringStateDTO getDeviceInclusionState() {
+ return deviceInclusionState;
+ }
+
+ /**
+ * @param deviceInclusionState the deviceInclusionState to set
+ */
+ public void setDeviceInclusionState(StringStateDTO deviceInclusionState) {
+ this.deviceInclusionState = deviceInclusionState;
+ }
+
+ /**
+ * @return the deviceConfigurationState
+ */
+ public StringStateDTO getDeviceConfigurationState() {
+ return deviceConfigurationState;
+ }
+
+ /**
+ * @param deviceConfigurationState the deviceConfigurationState to set
+ */
+ public void setDeviceConfigurationState(StringStateDTO deviceConfigurationState) {
+ this.deviceConfigurationState = deviceConfigurationState;
+ }
+
+ /**
+ * @return the isReachable
+ */
+ public BooleanStateDTO getIsReachable() {
+ return isReachable;
+ }
+
+ /**
+ * @param isReachable the isReachable to set
+ */
+ public void setIsReachable(BooleanStateDTO isReachable) {
+ this.isReachable = isReachable;
+ }
+
+ /**
+ * @return the updateState
+ */
+ public StringStateDTO getUpdateState() {
+ return updateState;
+ }
+
+ /**
+ * @param updateState the updateState to set
+ */
+ public void setUpdateState(StringStateDTO updateState) {
+ this.updateState = updateState;
+ }
+
+ /**
+ * @return the firmwareVersion
+ */
+ public StringStateDTO getFirmwareVersion() {
+ return firmwareVersion;
+ }
+
+ /**
+ * @param firmwareVersion the firmwareVersion to set
+ */
+ public void setFirmwareVersion(StringStateDTO firmwareVersion) {
+ this.firmwareVersion = firmwareVersion;
+ }
+
+ /**
+ * @return the wHRating
+ */
+ public DoubleStateDTO getWHRating() {
+ return wHRating;
+ }
+
+ /**
+ * @param wHRating the wHRating to set
+ */
+ public void setWHRating(DoubleStateDTO wHRating) {
+ this.wHRating = wHRating;
+ }
+
+ /**
+ * @return the lastReboot
+ */
+ public DateTimeStateDTO getLastReboot() {
+ return lastReboot;
+ }
+
+ /**
+ * @param lastReboot the lastReboot to set
+ */
+ public void setLastReboot(DateTimeStateDTO lastReboot) {
+ this.lastReboot = lastReboot;
+ }
+
+ /**
+ * @return the memoryLoad
+ */
+ public DoubleStateDTO getMemoryLoad() {
+ return memoryLoad;
+ }
+
+ /**
+ * @param memoryLoad the memoryLoad to set
+ */
+ public void setMemoryLoad(DoubleStateDTO memoryLoad) {
+ this.memoryLoad = memoryLoad;
+ }
+
+ /**
+ * @return the cPULoad
+ */
+ public DoubleStateDTO getCPULoad() {
+ return cpuLoad;
+ }
+
+ /**
+ * @param cpuLoad the cPULoad to set
+ */
+ public void setCPULoad(DoubleStateDTO cpuLoad) {
+ this.cpuLoad = cpuLoad;
+ }
+
+ /**
+ * @return the lBDongleAttached
+ */
+ public BooleanStateDTO getLBDongleAttached() {
+ return lBDongleAttached;
+ }
+
+ /**
+ * @param lBDongleAttached the lBDongleAttached to set
+ */
+ public void setLBDongleAttached(BooleanStateDTO lBDongleAttached) {
+ this.lBDongleAttached = lBDongleAttached;
+ }
+
+ /**
+ * @return the mBusDongleAttached
+ */
+ public BooleanStateDTO getMBusDongleAttached() {
+ return mBusDongleAttached;
+ }
+
+ /**
+ * @param mBusDongleAttached the mBusDongleAttached to set
+ */
+ public void setMBusDongleAttached(BooleanStateDTO mBusDongleAttached) {
+ this.mBusDongleAttached = mBusDongleAttached;
+ }
+
+ /**
+ * @return the configVersion
+ */
+ public IntegerStateDTO getConfigVersion() {
+ return configVersion;
+ }
+
+ /**
+ * @param configVersion the configVersion to set
+ */
+ public void setConfigVersion(IntegerStateDTO configVersion) {
+ this.configVersion = configVersion;
+ }
+
+ /**
+ * @return the oSState
+ */
+ public StringStateDTO getOSState() {
+ return osState;
+ }
+
+ /**
+ * @param osState the oSState to set
+ */
+ public void setOSState(StringStateDTO osState) {
+ this.osState = osState;
+ }
+
+ /**
+ * @return the wifiSignalStrength
+ */
+ public IntegerStateDTO getWifiSignalStrength() {
+ return wifiSignalStrength;
+ }
+
+ /**
+ * @param wifiSignalStrength the wifiSignalStrength to set
+ */
+ public void setWifiSignalStrength(IntegerStateDTO wifiSignalStrength) {
+ this.wifiSignalStrength = wifiSignalStrength;
+ }
+
+ /**
+ * @return the ethIpAddress
+ */
+ public StringStateDTO getEthIpAddress() {
+ return ethIpAddress;
+ }
+
+ /**
+ * @param ethIpAddress the ethIpAddress to set
+ */
+ public void setEthIpAddress(StringStateDTO ethIpAddress) {
+ this.ethIpAddress = ethIpAddress;
+ }
+
+ /**
+ * @return the wifiIpAddress
+ */
+ public StringStateDTO getWifiIpAddress() {
+ return wifiIpAddress;
+ }
+
+ /**
+ * @param wifiIpAddress the wifiIpAddress to set
+ */
+ public void setWifiIpAddress(StringStateDTO wifiIpAddress) {
+ this.wifiIpAddress = wifiIpAddress;
+ }
+
+ /**
+ * @return the ethMacAddress
+ */
+ public StringStateDTO getEthMacAddress() {
+ return ethMacAddress;
+ }
+
+ /**
+ * @param ethMacAddress the ethMacAddress to set
+ */
+ public void setEthMacAddress(StringStateDTO ethMacAddress) {
+ this.ethMacAddress = ethMacAddress;
+ }
+
+ /**
+ * @return the wifiMacAddress
+ */
+ public StringStateDTO getWifiMacAddress() {
+ return wifiMacAddress;
+ }
+
+ /**
+ * @param wifiMacAddress the wifiMacAddress to set
+ */
+ public void setWifiMacAddress(StringStateDTO wifiMacAddress) {
+ this.wifiMacAddress = wifiMacAddress;
+ }
+
+ /**
+ * @return the inUseAdapter
+ */
+ public StringStateDTO getInUseAdapter() {
+ return inUseAdapter;
+ }
+
+ /**
+ * @param inUseAdapter the inUseAdapter to set
+ */
+ public void setInUseAdapter(StringStateDTO inUseAdapter) {
+ this.inUseAdapter = inUseAdapter;
+ }
+
+ /**
+ * @return the discoveryActive
+ */
+ public BooleanStateDTO getDiscoveryActive() {
+ return discoveryActive;
+ }
+
+ /**
+ * @param discoveryActive the discoveryActive to set
+ */
+ public void setDiscoveryActive(BooleanStateDTO discoveryActive) {
+ this.discoveryActive = discoveryActive;
+ }
+
+ /**
+ * @return the operationStatus
+ */
+ public StringStateDTO getOperationStatus() {
+ return operationStatus;
+ }
+
+ /**
+ * @param operationStatus the operationStatus to set
+ */
+ public void setOperationStatus(StringStateDTO operationStatus) {
+ this.operationStatus = operationStatus;
+ }
+
+ /**
+ * @return the operationStatus
+ */
+ public StringStateDTO getOperationStatus(boolean isSHCClassic) {
+ if (isSHCClassic) {
+ return getOSState();
+ }
+ return getOperationStatus();
+ }
+
+ /**
+ * @return the currentUtcOffset
+ */
+ public DoubleStateDTO getCurrentUtcOffset() {
+ return currentUtcOffset;
+ }
+
+ /**
+ * @param currentUtcOffset the currentUtcOffset to set
+ */
+ public void setCurrentUtcOffset(DoubleStateDTO currentUtcOffset) {
+ this.currentUtcOffset = currentUtcOffset;
+ }
+
+ /**
+ * @return the cpuUsage
+ */
+ public DoubleStateDTO getCpuUsage() {
+ return cpuUsage;
+ }
+
+ /**
+ * @param cpuUsage the cpuUsage to set
+ */
+ public void setCpuUsage(DoubleStateDTO cpuUsage) {
+ this.cpuUsage = cpuUsage;
+ }
+
+ public DoubleStateDTO getCpuUsage(boolean isSHCClassic) {
+ if (isSHCClassic) {
+ return getCPULoad();
+ }
+ return getCpuUsage();
+ }
+
+ /**
+ * @return the diskUsage
+ */
+ public DoubleStateDTO getDiskUsage() {
+ return diskUsage;
+ }
+
+ /**
+ * @param diskUsage the diskUsage to set
+ */
+ public void setDiskUsage(DoubleStateDTO diskUsage) {
+ this.diskUsage = diskUsage;
+ }
+
+ /**
+ * @return the memoryUsage
+ */
+ public DoubleStateDTO getMemoryUsage() {
+ return memoryUsage;
+ }
+
+ /**
+ * @param memoryUsage the memoryUsage to set
+ */
+ public void setMemoryUsage(DoubleStateDTO memoryUsage) {
+ this.memoryUsage = memoryUsage;
+ }
+
+ /**
+ * @return the memoryUsage
+ */
+ public DoubleStateDTO getMemoryUsage(boolean isSHCClassic) {
+ if (isSHCClassic) {
+ return getMemoryLoad();
+ }
+ return getMemoryUsage();
+ }
+}
diff --git a/bundles/org.openhab.binding.livisismarthome/src/main/java/org/openhab/binding/livisismarthome/internal/client/api/entity/error/ErrorResponseDTO.java b/bundles/org.openhab.binding.livisismarthome/src/main/java/org/openhab/binding/livisismarthome/internal/client/api/entity/error/ErrorResponseDTO.java
new file mode 100644
index 00000000000..cb880f594f9
--- /dev/null
+++ b/bundles/org.openhab.binding.livisismarthome/src/main/java/org/openhab/binding/livisismarthome/internal/client/api/entity/error/ErrorResponseDTO.java
@@ -0,0 +1,141 @@
+/**
+ * Copyright (c) 2010-2022 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.livisismarthome.internal.client.api.entity.error;
+
+import java.util.List;
+
+import com.google.gson.annotations.SerializedName;
+
+/**
+ * Error response object from the LIVISI SmartHome api.
+ *
+ * @author Oliver Kuhl - Initial contribution
+ */
+public class ErrorResponseDTO {
+
+ // General errors
+ public static final int ERR_UNKNOWN = 1000;
+ public static final int ERR_SERVICE_UNAVAILABLE = 1001;
+ public static final int ERR_SERVICE_TIMEOUT = 1002;
+ public static final int ERR_INTERNAL_API_ERROR = 1003;
+ public static final int ERR_INVALID_SHC_OPERATION = 1004;
+ public static final int ERR_MISSING_ARGUMENT_OR_WRONG_VALUE = 1005;
+ public static final int ERR_SERVICE_TOO_BUSY = 1006;
+
+ // Authentication and authorization errors
+ public static final int ERR_UNKNOWN_AUTHENTICATION_ERROR = 2000;
+ public static final int ERR_ACCESS_NOT_ALLOWED = 2001;
+ public static final int ERR_INVALID_TOKEN_REQUEST = 2002;
+ public static final int ERR_INVALID_CLIENT_CREDENTIALS = 2003;
+ public static final int ERR_INVALID_TOKEN_SIGNATURE = 2004;
+ public static final int ERR_SESSION_INITIALIZATION_FAILED = 2005;
+ public static final int ERR_SESSION_EXISTS = 2006;
+ public static final int ERR_TOKEN_EXPIRED = 2007;
+ public static final int ERR_LOGIN_FROM_DIFFERENT_CLIENT = 2008;
+ public static final int ERR_INVALID_USER_CREDENTIALS = 2009;
+ public static final int ERR_REMOTE_ACCESS_NOT_ALLOWED = 2010;
+ public static final int ERR_INSUFFICIENT_PERMISSIONS = 2011;
+ public static final int ERR_SESSION_NOT_FOUND = 2012;
+ public static final int ERR_ACCOUNT_TEMPORARY_LOCKED = 2013;
+
+ // Entities
+ public static final int ERR_ENTITY_DOES_NOT_EXIST = 3000;
+ public static final int ERR_INVALID_REQUEST_CONTENT = 3001;
+ public static final int ERR_NO_CHANGE_PERFORMED = 3002;
+ public static final int ERR_ENTITY_ALREADY_EXISTS = 3003;
+ public static final int ERR_INVALID_INTERACTION = 3004;
+
+ // Products
+ public static final int ERR_PREMIUM_SERVICE_CANNOT_BE_ENABLED_DIRECTLY = 3500;
+ public static final int ERR_CANNOT_REMOVE_A_PRODUCT_THAT_WAS_PAID = 3501;
+
+ // Actions
+ public static final int ERR_INVALID_ACTION_TRIGGERED = 4000;
+ public static final int ERR_INVALID_PARAMETER = 4001;
+ public static final int ERR_TRIGGER_ACTION_NOT_ALLOWED = 4002;
+ public static final int ERR_UNSUPPORTED_ACTION_TYPE = 4003;
+
+ // Configuration
+ public static final int ERR_ERROR_UPDATING_CONFIG = 5000;
+ public static final int ERR_CONFIG_LOCKED_BY_OTHER_PROCESS = 5001;
+ public static final int ERR_COMMUNICATION_WITH_SHC_FAILED = 5002;
+ public static final int ERR_LATEST_TERMS_AND_CONDITIONS_NOT_ACCEPTED_BY_USER = 5003;
+ public static final int ERR_ONE_SHC_ALREADY_REGISTERED = 5004;
+ public static final int ERR_USER_HAS_NO_REGISTERED_SHC = 5005;
+ public static final int ERR_CONTROLLER_OFFLINE = 5006;
+ public static final int ERR_REGISTRATION_FAILURE = 5009;
+
+ // Smart codes
+ public static final int ERR_SMARTCODE_REQUEST_NOT_ALLOWED = 6000;
+ public static final int ERR_SMARTCODE_CANNOT_BE_REDEEMED = 6001;
+ public static final int ERR_RESTRICTED_ACCESS = 6002;
+
+ @SerializedName("errorcode")
+ private int code;
+ @SerializedName("description")
+ private String description;
+ @SerializedName("messages")
+ private List messages;
+
+ /**
+ * @return the error code
+ */
+ public int getCode() {
+ return code;
+ }
+
+ /**
+ * @param code the error code to set
+ */
+ public void setCode(int code) {
+ this.code = code;
+ }
+
+ /**
+ * @return the description
+ */
+ public String getDescription() {
+ return description;
+ }
+
+ /**
+ * @param description the description to set
+ */
+ public void setDescription(String description) {
+ this.description = description;
+ }
+
+ /**
+ * @return the messages
+ */
+ public List getMessages() {
+ return messages;
+ }
+
+ /**
+ * @param messages the messages to set
+ */
+ public void setMessages(List messages) {
+ this.messages = messages;
+ }
+
+ @Override
+ public String toString() {
+ String stringRepresentation = "ErrorResponse [code=" + code + ", description=" + description;
+ if (messages != null) {
+ stringRepresentation += ", messages=" + messages.toString();
+ }
+ stringRepresentation += "]";
+ return stringRepresentation;
+ }
+}
diff --git a/bundles/org.openhab.binding.livisismarthome/src/main/java/org/openhab/binding/livisismarthome/internal/client/api/entity/event/BaseEventDTO.java b/bundles/org.openhab.binding.livisismarthome/src/main/java/org/openhab/binding/livisismarthome/internal/client/api/entity/event/BaseEventDTO.java
new file mode 100644
index 00000000000..65f0d174449
--- /dev/null
+++ b/bundles/org.openhab.binding.livisismarthome/src/main/java/org/openhab/binding/livisismarthome/internal/client/api/entity/event/BaseEventDTO.java
@@ -0,0 +1,142 @@
+/**
+ * Copyright (c) 2010-2022 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.livisismarthome.internal.client.api.entity.event;
+
+import java.util.Collections;
+import java.util.Set;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+
+/**
+ * @author Oliver Kuhl - Initial contribution
+ */
+public class BaseEventDTO {
+
+ public static final String TYPE_STATE_CHANGED = "StateChanged";// "device/SHC.RWE/1.0/event/StateChanged";
+ public static final String TYPE_NEW_MESSAGE_RECEIVED = "NewMessageReceived"; // "device/SHC.RWE/1.0/event/NewMessageReceived";
+ public static final String TYPE_MESSAGE_CREATED = "MessageCreated";
+ public static final String TYPE_MESSAGE_DELETED = "MessageDeleted"; // "device/SHC.RWE/1.0/event/MessageDeleted";
+ public static final String TYPE_DISCONNECT = "Disconnect"; // "/event/Disconnect";
+ public static final String TYPE_CONFIGURATION_CHANGED = "ConfigurationChanged"; // "device/SHC.RWE/1.0/event/ConfigChanged";
+ public static final String TYPE_CONTROLLER_CONNECTIVITY_CHANGED = "/event/ControllerConnectivityChanged"; // "device/SHC.RWE/1.0/event/ControllerConnectivityChanged";
+ public static final String TYPE_BUTTON_PRESSED = "ButtonPressed";
+
+ public static final Set SUPPORTED_EVENT_TYPES = Collections
+ .unmodifiableSet(Stream.of(TYPE_STATE_CHANGED, TYPE_NEW_MESSAGE_RECEIVED, TYPE_MESSAGE_CREATED,
+ TYPE_MESSAGE_DELETED, TYPE_DISCONNECT, TYPE_CONFIGURATION_CHANGED,
+ TYPE_CONTROLLER_CONNECTIVITY_CHANGED, TYPE_BUTTON_PRESSED).collect(Collectors.toSet()));
+
+ /**
+ * The event sequence number – the gateway keeps track and adds a sequence number to each event for the client to
+ * identify order and missing events
+ */
+ private Integer sequenceNumber;
+
+ /**
+ * Specifies the type of the event. The type must be the full path to uniquely reference the event definition.
+ * Always available.
+ */
+ private String type;
+
+ /**
+ * Date and time when the event occurred in the system. Always available.
+ */
+ private String timestamp;
+
+ /**
+ * @return the sequenceNumber
+ */
+ public Integer getSequenceNumber() {
+ return sequenceNumber;
+ }
+
+ /**
+ * @return the timestamp
+ */
+ public String getTimestamp() {
+ return timestamp;
+ }
+
+ /**
+ * @return the type
+ */
+ public String getType() {
+ return type;
+ }
+
+ /**
+ * @param sequenceNumber the sequenceNumber to set
+ */
+ public void setSequenceNumber(Integer sequenceNumber) {
+ this.sequenceNumber = sequenceNumber;
+ }
+
+ /**
+ * @param timestamp the timestamp to set
+ */
+ public void setTimestamp(String timestamp) {
+ this.timestamp = timestamp;
+ }
+
+ /**
+ * @param type the type to set
+ */
+ public void setType(String type) {
+ this.type = type;
+ }
+
+ /**
+ * Returns true, if the {@link EventDTO} is a ConfigChanged event.
+ *
+ * @return true if it is a ConfigChanged event, otherwise false
+ */
+ public boolean isConfigChangedEvent() {
+ return TYPE_CONFIGURATION_CHANGED.equals(getType());
+ }
+
+ /**
+ * Returns true, if the {@link EventDTO} is a Disconnect event.
+ *
+ * @return true if it is a Disconnect event, otherwise false
+ */
+ public boolean isDisconnectedEvent() {
+ return TYPE_DISCONNECT.equals(getType());
+ }
+
+ /**
+ * Returns true, if the {@link EventDTO} is a MessageDeletedEvent.
+ *
+ * @return true if it is a MessageDeleted event, otherwise false
+ */
+ public boolean isMessageDeletedEvent() {
+ return TYPE_MESSAGE_DELETED.equals(getType());
+ }
+
+ /**
+ * Returns true, if the {@link EventDTO} is a NewMessageReceivedEvent.
+ *
+ * @return true if it is a MessageReceived event, otherwise false
+ */
+ public boolean isNewMessageReceivedEvent() {
+ return TYPE_NEW_MESSAGE_RECEIVED.equals(getType());
+ }
+
+ /**
+ * Returns true, if the {@link EventDTO} is a StateChangedEvent.
+ *
+ * @return true if it is a StateChanged event, otherwise false
+ */
+ public boolean isStateChangedEvent() {
+ return TYPE_STATE_CHANGED.equals(getType());
+ }
+}
diff --git a/bundles/org.openhab.binding.livisismarthome/src/main/java/org/openhab/binding/livisismarthome/internal/client/api/entity/event/EventDTO.java b/bundles/org.openhab.binding.livisismarthome/src/main/java/org/openhab/binding/livisismarthome/internal/client/api/entity/event/EventDTO.java
new file mode 100644
index 00000000000..08c23d37a24
--- /dev/null
+++ b/bundles/org.openhab.binding.livisismarthome/src/main/java/org/openhab/binding/livisismarthome/internal/client/api/entity/event/EventDTO.java
@@ -0,0 +1,204 @@
+/**
+ * Copyright (c) 2010-2022 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.livisismarthome.internal.client.api.entity.event;
+
+import org.openhab.binding.livisismarthome.internal.client.api.entity.link.LinkDTO;
+
+/**
+ * Defines the {@link EventDTO}, which is sent by the LIVISI websocket to inform the clients about changes.
+ *
+ * @author Oliver Kuhl - Initial contribution
+ */
+public class EventDTO extends BaseEventDTO {
+
+ /**
+ * Reference to the associated entity (instance or metadata) for the given event. Always available.
+ */
+ private String source;
+
+ /**
+ * The product (context) that generated the event.
+ */
+ private String namespace;
+
+ /**
+ * This container includes only properties, e.g. for the changed state properties. If there is other data than
+ * properties to be transported, the data container will be used.
+ * Optional.
+ */
+ private EventPropertiesDTO properties;
+
+ /**
+ * Data for the event, The data container can contain any type of entity dependent on the event type. For example,
+ * the DeviceFound events contains the entire Device entity rather than selected properties.
+ * Optional.
+ */
+ private EventDataDTO data;
+
+ /**
+ * @return the link to the source
+ */
+ public String getSource() {
+ return source;
+ }
+
+ /**
+ * @param source the link to the source to set
+ */
+ public void setSource(String source) {
+ this.source = source;
+ }
+
+ /**
+ * @return the namespace
+ */
+ public String getNamespace() {
+ return namespace;
+ }
+
+ /**
+ * @param namespace the namespace to set
+ */
+ public void setNamespace(String namespace) {
+ this.namespace = namespace;
+ }
+
+ /**
+ * @return the properties
+ */
+ public EventPropertiesDTO getProperties() {
+ return properties;
+ }
+
+ /**
+ * @param properties the properties to set
+ */
+ public void setProperties(EventPropertiesDTO properties) {
+ this.properties = properties;
+ }
+
+ /**
+ * @return the dataList
+ */
+ public EventDataDTO getData() {
+ return data;
+ }
+
+ /**
+ * @param data the data to set
+ */
+ public void setData(EventDataDTO data) {
+ this.data = data;
+ }
+
+ /**
+ * Returns the id of the link or null, if there is no link or the link does not have an id.
+ *
+ * @return String the id of the link or null
+ */
+ public String getSourceId() {
+ final String linkType = getSourceLinkType();
+ if (linkType != null && !LinkDTO.LINK_TYPE_UNKNOWN.equals(linkType)
+ && !LinkDTO.LINK_TYPE_SHC.equals(linkType)) {
+ if (source != null) {
+ String sourceId = source.replace(linkType, "");
+ sourceId = sourceId.replace("-", "");
+ return sourceId;
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Returns the Type of the {@link LinkDTO} in the {@link EventDTO}.
+ *
+ * @return link type
+ */
+ public String getSourceLinkType() {
+ if (source != null) {
+ return LinkDTO.getLinkType(source);
+ }
+ return null;
+ }
+
+ /**
+ * Returns true, if the {@link LinkDTO} points to a
+ * {@link org.openhab.binding.livisismarthome.internal.client.api.entity.capability.CapabilityDTO}.
+ *
+ * @return true if the link points to a capability, otherwise false
+ */
+ public Boolean isLinkedtoCapability() {
+ return source != null && LinkDTO.isTypeCapability(source);
+ }
+
+ /**
+ * Returns true, if the {@link LinkDTO} points to a
+ * {@link org.openhab.binding.livisismarthome.internal.client.api.entity.device.DeviceDTO}.
+ *
+ * @return true if the link points to a device, otherwise false
+ */
+ public Boolean isLinkedtoDevice() {
+ return source != null && LinkDTO.isTypeDevice(source);
+ }
+
+ /**
+ * Returns true, if the {@link LinkDTO} points to a
+ * {@link org.openhab.binding.livisismarthome.internal.client.api.entity.message.MessageDTO}.
+ *
+ * @return true if the link points to a message, otherwise false
+ */
+ public Boolean isLinkedtoMessage() {
+ return source != null && LinkDTO.isTypeMessage(source);
+ }
+
+ /**
+ * Returns true, if the {@link LinkDTO} points to the SHC
+ * {@link org.openhab.binding.livisismarthome.internal.client.api.entity.device.DeviceDTO}.
+ *
+ * @return true if the link points to a SHC bridge device, otherwise false
+ */
+ public Boolean isLinkedtoSHC() {
+ return source != null && LinkDTO.isTypeSHC(source);
+ }
+
+ /**
+ * Returns true, if it is a button pressed event. Otherwise false.
+ * It is a button pressed event when button index is set (LastPressedButtonIndex is set too).
+ *
+ * @return true if it is a button pressed event, otherwise false
+ */
+ public boolean isButtonPressedEvent() {
+ final Integer buttonIndex = getProperties().getKeyPressButtonIndex();
+ return buttonIndex != null;
+ }
+
+ /**
+ * Returns the configurationVersion or null, if this
+ * {@link org.openhab.binding.livisismarthome.internal.client.api.entity.PropertyDTO} is not available in the event.
+ *
+ * @return configuration version
+ */
+ public Integer getConfigurationVersion() {
+ return getData().getConfigVersion();
+ }
+
+ /**
+ * Returns the isConnected {@link org.openhab.binding.livisismarthome.internal.client.api.entity.PropertyDTO} value.
+ * Only available for event of type ControllerConnectivityChanged
+ *
+ * @return {@link Boolean} or null
, if not available
+ */
+ public Boolean getIsConnected() {
+ return getProperties().getIsConnected();
+ }
+}
diff --git a/bundles/org.openhab.binding.livisismarthome/src/main/java/org/openhab/binding/livisismarthome/internal/client/api/entity/event/EventDataDTO.java b/bundles/org.openhab.binding.livisismarthome/src/main/java/org/openhab/binding/livisismarthome/internal/client/api/entity/event/EventDataDTO.java
new file mode 100644
index 00000000000..cc232117125
--- /dev/null
+++ b/bundles/org.openhab.binding.livisismarthome/src/main/java/org/openhab/binding/livisismarthome/internal/client/api/entity/event/EventDataDTO.java
@@ -0,0 +1,45 @@
+/**
+ * Copyright (c) 2010-2022 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.livisismarthome.internal.client.api.entity.event;
+
+/**
+ * @author Oliver Kuhl - Initial contribution
+ *
+ */
+public class EventDataDTO {
+
+ private Integer configVersion;
+ private String id;
+
+ /**
+ * @return the configVersion
+ */
+ public Integer getConfigVersion() {
+ return configVersion;
+ }
+
+ /**
+ * @param configVersion the configVersion to set
+ */
+ public void setConfigVersion(Integer configVersion) {
+ this.configVersion = configVersion;
+ }
+
+ public String getId() {
+ return id;
+ }
+
+ public void setId(String id) {
+ this.id = id;
+ }
+}
diff --git a/bundles/org.openhab.binding.livisismarthome/src/main/java/org/openhab/binding/livisismarthome/internal/client/api/entity/event/EventPropertiesDTO.java b/bundles/org.openhab.binding.livisismarthome/src/main/java/org/openhab/binding/livisismarthome/internal/client/api/entity/event/EventPropertiesDTO.java
new file mode 100644
index 00000000000..e811af19422
--- /dev/null
+++ b/bundles/org.openhab.binding.livisismarthome/src/main/java/org/openhab/binding/livisismarthome/internal/client/api/entity/event/EventPropertiesDTO.java
@@ -0,0 +1,641 @@
+/**
+ * Copyright (c) 2010-2022 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.livisismarthome.internal.client.api.entity.event;
+
+import com.google.gson.annotations.SerializedName;
+
+/**
+ * @author Oliver Kuhl - Initial contribution
+ */
+public class EventPropertiesDTO {
+
+ /** SHC Properties **/
+ private Integer configVersion;
+ private Boolean isConnected;
+
+ /** Writable capability properties **/
+ private Integer dimLevel;
+ private Boolean onState;
+ private String operationMode;
+ private String operationStatus;
+ private Double pointTemperature;
+ private Integer shutterLevel;
+ private Boolean value;
+
+ /** readable capability properties **/
+ private Double absoluteEnergyConsumption;
+ private Double energyConsumptionDayEuro;
+ private Double energyConsumptionDayKWh;
+ private Double energyConsumptionMonthEuro;
+ private Double energyConsumptionMonthKWh;
+ private Double energyPerDayInEuro;
+ private Double energyPerDayInKWh;
+ private Double energyPerMonthInEuro;
+ private Double energyPerMonthInKWh;
+ private Boolean frostWarning;
+ private Double humidity;
+ private Boolean isReachable;
+ private Boolean isOpen;
+ private Boolean isSmokeAlarm;
+ @SerializedName("type")
+ private String keyPressType;
+ @SerializedName("index")
+ private Integer keyPressButtonIndex;
+ private Integer keyPressCounter;
+ @SerializedName("lastPressedButtonIndex")
+ private Integer lastKeyPressButtonIndex;
+ private Integer lastKeyPressCounter;
+ private Double luminance;
+ private Boolean moldWarning;
+ private Integer motionDetectedCount;
+ private Double powerConsumptionWatt;
+ private Double powerInWatt;
+ private Double temperature;
+ private Double totalEnergy;
+ private Boolean windowReductionActive;
+ private Double cpuUsage;
+ private Double diskUsage;
+ private Double memoryUsage;
+ @SerializedName("CPULoad")
+ private Double cpuLoad;
+ private Double memoryLoad;
+ @SerializedName("OSState")
+ private String osState;
+
+ /**
+ * @return the configurationVersion
+ */
+ public Integer getConfigVersion() {
+ return configVersion;
+ }
+
+ /**
+ * @param configVersion the configurationVersion to set
+ */
+ public void setConfigVersion(final Integer configVersion) {
+ this.configVersion = configVersion;
+ }
+
+ /**
+ * @return the isConnected
+ */
+ public Boolean getIsConnected() {
+ return isConnected;
+ }
+
+ /**
+ * @param isConnected the isConnected to set
+ */
+ public void setIsConnected(final Boolean isConnected) {
+ this.isConnected = isConnected;
+ }
+
+ /**
+ * @return the dimLevel
+ */
+ public Integer getDimLevel() {
+ return dimLevel;
+ }
+
+ /**
+ * @param dimLevel the dimLevel to set
+ */
+ public void setDimLevel(final Integer dimLevel) {
+ this.dimLevel = dimLevel;
+ }
+
+ /**
+ * @return the onState
+ */
+ public Boolean getOnState() {
+ return onState;
+ }
+
+ /**
+ * @param onState the onState to set
+ */
+ public void setOnState(final Boolean onState) {
+ this.onState = onState;
+ }
+
+ /**
+ * @return the operationMode
+ */
+ public String getOperationMode() {
+ return operationMode;
+ }
+
+ /**
+ * @param operationMode the operationMode to set
+ */
+ public void setOperationMode(final String operationMode) {
+ this.operationMode = operationMode;
+ }
+
+ /**
+ * @return the operationStatus
+ */
+ public String getOperationStatus() {
+ return operationStatus;
+ }
+
+ /**
+ * @param operationStatus the operationStatus to set
+ */
+ public void setOperationStatus(final String operationStatus) {
+ this.operationStatus = operationStatus;
+ }
+
+ /**
+ * @return the pointTemperature
+ */
+ public Double getPointTemperature() {
+ return pointTemperature;
+ }
+
+ /**
+ * @param pointTemperature the pointTemperature to set
+ */
+ public void setPointTemperature(final Double pointTemperature) {
+ this.pointTemperature = pointTemperature;
+ }
+
+ /**
+ * @return the shutterLevel
+ */
+ public Integer getShutterLevel() {
+ return shutterLevel;
+ }
+
+ /**
+ * @param shutterLevel the shutterLevel to set
+ */
+ public void setShutterLevel(final Integer shutterLevel) {
+ this.shutterLevel = shutterLevel;
+ }
+
+ /**
+ * @return the value
+ */
+ public Boolean getValue() {
+ return value;
+ }
+
+ /**
+ * @param value the value to set
+ */
+ public void setValue(final Boolean value) {
+ this.value = value;
+ }
+
+ /**
+ * @return the absoluteEnergyConsumption
+ */
+ public Double getAbsoluteEnergyConsumption() {
+ return absoluteEnergyConsumption;
+ }
+
+ /**
+ * @param absoluteEnergyConsumption the absoluteEnergyConsumption to set
+ */
+ public void setAbsoluteEnergyConsumption(final Double absoluteEnergyConsumption) {
+ this.absoluteEnergyConsumption = absoluteEnergyConsumption;
+ }
+
+ /**
+ * @return the energyConsumptionDayEuro
+ */
+ public Double getEnergyConsumptionDayEuro() {
+ return energyConsumptionDayEuro;
+ }
+
+ /**
+ * @param energyConsumptionDayEuro the energyConsumptionDayEuro to set
+ */
+ public void setEnergyConsumptionDayEuro(final Double energyConsumptionDayEuro) {
+ this.energyConsumptionDayEuro = energyConsumptionDayEuro;
+ }
+
+ /**
+ * @return the energyConsumptionDayKWh
+ */
+ public Double getEnergyConsumptionDayKWh() {
+ return energyConsumptionDayKWh;
+ }
+
+ /**
+ * @param energyConsumptionDayKWh the energyConsumptionDayKWh to set
+ */
+ public void setEnergyConsumptionDayKWh(final Double energyConsumptionDayKWh) {
+ this.energyConsumptionDayKWh = energyConsumptionDayKWh;
+ }
+
+ /**
+ * @return the energyConsumptionMonthEuro
+ */
+ public Double getEnergyConsumptionMonthEuro() {
+ return energyConsumptionMonthEuro;
+ }
+
+ /**
+ * @param energyConsumptionMonthEuro the energyConsumptionMonthEuro to set
+ */
+ public void setEnergyConsumptionMonthEuro(final Double energyConsumptionMonthEuro) {
+ this.energyConsumptionMonthEuro = energyConsumptionMonthEuro;
+ }
+
+ /**
+ * @return the energyConsumptionMonthKWh
+ */
+ public Double getEnergyConsumptionMonthKWh() {
+ return energyConsumptionMonthKWh;
+ }
+
+ /**
+ * @param energyConsumptionMonthKWh the energyConsumptionMonthKWh to set
+ */
+ public void setEnergyConsumptionMonthKWh(final Double energyConsumptionMonthKWh) {
+ this.energyConsumptionMonthKWh = energyConsumptionMonthKWh;
+ }
+
+ /**
+ * @return the energyPerDayInEuro
+ */
+ public Double getEnergyPerDayInEuro() {
+ return energyPerDayInEuro;
+ }
+
+ /**
+ * @param energyPerDayInEuro the energyPerDayInEuro to set
+ */
+ public void setEnergyPerDayInEuro(final Double energyPerDayInEuro) {
+ this.energyPerDayInEuro = energyPerDayInEuro;
+ }
+
+ /**
+ * @return the energyPerDayInKWh
+ */
+ public Double getEnergyPerDayInKWh() {
+ return energyPerDayInKWh;
+ }
+
+ /**
+ * @param energyPerDayInKWh the energyPerDayInKWh to set
+ */
+ public void setEnergyPerDayInKWh(final Double energyPerDayInKWh) {
+ this.energyPerDayInKWh = energyPerDayInKWh;
+ }
+
+ /**
+ * @return the energyPerMonthInEuro
+ */
+ public Double getEnergyPerMonthInEuro() {
+ return energyPerMonthInEuro;
+ }
+
+ /**
+ * @param energyPerMonthInEuro the energyPerMonthInEuro to set
+ */
+ public void setEnergyPerMonthInEuro(final Double energyPerMonthInEuro) {
+ this.energyPerMonthInEuro = energyPerMonthInEuro;
+ }
+
+ /**
+ * @return the energyPerMonthInKWh
+ */
+ public Double getEnergyPerMonthInKWh() {
+ return energyPerMonthInKWh;
+ }
+
+ /**
+ * @param energyPerMonthInKWh the energyPerMonthInKWh to set
+ */
+ public void setEnergyPerMonthInKWh(final Double energyPerMonthInKWh) {
+ this.energyPerMonthInKWh = energyPerMonthInKWh;
+ }
+
+ /**
+ * @return the frostWarning
+ */
+ public Boolean getFrostWarning() {
+ return frostWarning;
+ }
+
+ /**
+ * @param frostWarning the frostWarning to set
+ */
+ public void setFrostWarning(final Boolean frostWarning) {
+ this.frostWarning = frostWarning;
+ }
+
+ /**
+ * @return the humidity
+ */
+ public Double getHumidity() {
+ return humidity;
+ }
+
+ /**
+ * @param humidity the humidity to set
+ */
+ public void setHumidity(final Double humidity) {
+ this.humidity = humidity;
+ }
+
+ /**
+ * @return if the device is reachable
+ */
+ public Boolean getReachable() {
+ return isReachable;
+ }
+
+ /**
+ * @param reachable if the device is reachable
+ */
+ public void setReachable(Boolean reachable) {
+ isReachable = reachable;
+ }
+
+ /**
+ * @return the isOpen
+ */
+ public Boolean getIsOpen() {
+ return isOpen;
+ }
+
+ /**
+ * @param isOpen the isOpen to set
+ */
+ public void setIsOpen(final Boolean isOpen) {
+ this.isOpen = isOpen;
+ }
+
+ /**
+ * @return the isSmokeAlarm
+ */
+ public Boolean getIsSmokeAlarm() {
+ return isSmokeAlarm;
+ }
+
+ /**
+ * @param isSmokeAlarm the isSmokeAlarm to set
+ */
+ public void setIsSmokeAlarm(final Boolean isSmokeAlarm) {
+ this.isSmokeAlarm = isSmokeAlarm;
+ }
+
+ public String getKeyPressType() {
+ return keyPressType;
+ }
+
+ public void setKeyPressType(final String keyPressType) {
+ this.keyPressType = keyPressType;
+ }
+
+ public Integer getKeyPressButtonIndex() {
+ return keyPressButtonIndex;
+ }
+
+ public void setKeyPressButtonIndex(final Integer keyPressButtonIndex) {
+ this.keyPressButtonIndex = keyPressButtonIndex;
+ }
+
+ public Integer getKeyPressCounter() {
+ return keyPressCounter;
+ }
+
+ public void setKeyPressCounter(final Integer keyPressCounter) {
+ this.keyPressCounter = keyPressCounter;
+ }
+
+ public Integer getLastKeyPressButtonIndex() {
+ return lastKeyPressButtonIndex;
+ }
+
+ public void setLastKeyPressButtonIndex(final Integer lastKeyPressButtonIndex) {
+ this.lastKeyPressButtonIndex = lastKeyPressButtonIndex;
+ }
+
+ public Integer getLastKeyPressCounter() {
+ return lastKeyPressCounter;
+ }
+
+ public void setLastKeyPressCounter(final Integer lastKeyPressCounter) {
+ this.lastKeyPressCounter = lastKeyPressCounter;
+ }
+
+ /**
+ * @return the luminance
+ */
+ public Double getLuminance() {
+ return luminance;
+ }
+
+ /**
+ * @param luminance the luminance to set
+ */
+ public void setLuminance(final Double luminance) {
+ this.luminance = luminance;
+ }
+
+ /**
+ * @return the moldWarning
+ */
+ public Boolean getMoldWarning() {
+ return moldWarning;
+ }
+
+ /**
+ * @param moldWarning the moldWarning to set
+ */
+ public void setMoldWarning(final Boolean moldWarning) {
+ this.moldWarning = moldWarning;
+ }
+
+ /**
+ * @return the motionDetectedCount
+ */
+ public Integer getMotionDetectedCount() {
+ return motionDetectedCount;
+ }
+
+ /**
+ * @param motionDetectedCount the motionDetectedCount to set
+ */
+ public void setMotionDetectedCount(final Integer motionDetectedCount) {
+ this.motionDetectedCount = motionDetectedCount;
+ }
+
+ /**
+ * @return the powerConsumptionWatt
+ */
+ public Double getPowerConsumptionWatt() {
+ return powerConsumptionWatt;
+ }
+
+ /**
+ * @param powerConsumptionWatt the powerConsumptionWatt to set
+ */
+ public void setPowerConsumptionWatt(final Double powerConsumptionWatt) {
+ this.powerConsumptionWatt = powerConsumptionWatt;
+ }
+
+ /**
+ * @return the powerInWatt
+ */
+ public Double getPowerInWatt() {
+ return powerInWatt;
+ }
+
+ /**
+ * @param powerInWatt the powerInWatt to set
+ */
+ public void setPowerInWatt(final Double powerInWatt) {
+ this.powerInWatt = powerInWatt;
+ }
+
+ /**
+ * @return the temperature
+ */
+ public Double getTemperature() {
+ return temperature;
+ }
+
+ /**
+ * @param temperature the temperature to set
+ */
+ public void setTemperature(final Double temperature) {
+ this.temperature = temperature;
+ }
+
+ /**
+ * @return the totalEnergy
+ */
+ public Double getTotalEnergy() {
+ return totalEnergy;
+ }
+
+ /**
+ * @param totalEnergy the totalEnergy to set
+ */
+ public void setTotalEnergy(final Double totalEnergy) {
+ this.totalEnergy = totalEnergy;
+ }
+
+ /**
+ * @return the windowReductionActive
+ */
+ public Boolean getWindowReductionActive() {
+ return windowReductionActive;
+ }
+
+ /**
+ * @param windowReductionActive the windowReductionActive to set
+ */
+ public void setWindowReductionActive(final Boolean windowReductionActive) {
+ this.windowReductionActive = windowReductionActive;
+ }
+
+ /**
+ * @param cpuUsage the cpuUsage to set
+ */
+ public void setCpuUsage(final Double cpuUsage) {
+ this.cpuUsage = cpuUsage;
+ }
+
+ /**
+ * @return the cpuUsage
+ */
+ public Double getCpuUsage() {
+ return cpuUsage;
+ }
+
+ /**
+ * @param diskUsage the diskUsage to set
+ */
+ public void setDiskUsage(final Double diskUsage) {
+ this.diskUsage = diskUsage;
+ }
+
+ /**
+ * @return the diskUsage
+ */
+ public Double getDiskUsage() {
+ return diskUsage;
+ }
+
+ /**
+ * @param memoryUsage the memoryUsage to set
+ */
+ public void setMemoryUsage(final Double memoryUsage) {
+ this.memoryUsage = memoryUsage;
+ }
+
+ /**
+ * @return the memoryUsage
+ */
+ public Double getMemoryUsage() {
+ return memoryUsage;
+ }
+
+ public Double getCPULoad() {
+ return cpuLoad;
+ }
+
+ public void setCPULoad(Double cpuLoad) {
+ this.cpuLoad = cpuLoad;
+ }
+
+ public Double getCpuUsage(boolean isSHCClassic) {
+ if (isSHCClassic) {
+ return getCPULoad();
+ }
+ return getCpuUsage();
+ }
+
+ public Double getMemoryLoad() {
+ return memoryLoad;
+ }
+
+ public void setMemoryLoad(Double memoryLoad) {
+ this.memoryLoad = memoryLoad;
+ }
+
+ /**
+ * @return the memoryUsage
+ */
+ public Double getMemoryUsage(boolean isSHCClassic) {
+ if (isSHCClassic) {
+ return getMemoryLoad();
+ }
+ return getMemoryUsage();
+ }
+
+ public String getOSState() {
+ return osState;
+ }
+
+ public void setOSState(String osState) {
+ this.osState = osState;
+ }
+
+ /**
+ * @return the operationStatus
+ */
+ public String getOperationStatus(boolean isSHCClassic) {
+ if (isSHCClassic) {
+ return getOSState();
+ }
+ return getOperationStatus();
+ }
+}
diff --git a/bundles/org.openhab.binding.livisismarthome/src/main/java/org/openhab/binding/livisismarthome/internal/client/api/entity/event/MessageEventDTO.java b/bundles/org.openhab.binding.livisismarthome/src/main/java/org/openhab/binding/livisismarthome/internal/client/api/entity/event/MessageEventDTO.java
new file mode 100644
index 00000000000..6f70579dbdc
--- /dev/null
+++ b/bundles/org.openhab.binding.livisismarthome/src/main/java/org/openhab/binding/livisismarthome/internal/client/api/entity/event/MessageEventDTO.java
@@ -0,0 +1,115 @@
+/**
+ * Copyright (c) 2010-2022 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.livisismarthome.internal.client.api.entity.event;
+
+import org.openhab.binding.livisismarthome.internal.client.api.entity.link.LinkDTO;
+import org.openhab.binding.livisismarthome.internal.client.api.entity.message.MessageDTO;
+
+/**
+ * Defines the {@link EventDTO}, which is sent by the LIVISI websocket to inform the clients about changes.
+ *
+ * @author Oliver Kuhl - Initial contribution
+ */
+public class MessageEventDTO extends BaseEventDTO {
+
+ /**
+ * Reference to the associated entity (instance or metadata) for the given event. Always available.
+ */
+ private String source;
+
+ /**
+ * The product (context) that generated the event.
+ */
+ private String namespace;
+
+ /**
+ * Data for the event, The data container can contain any type of entity dependent on the event type. For example,
+ * the DeviceFound events contains the entire Device entity rather than selected properties.
+ * Optional.
+ */
+ private MessageDTO data;
+
+ /**
+ * @return the link to the source
+ */
+ public String getSource() {
+ return source;
+ }
+
+ /**
+ * @param source the link to the source to set
+ */
+ public void setSource(String source) {
+ this.source = source;
+ }
+
+ /**
+ * @return the namespace
+ */
+ public String getNamespace() {
+ return namespace;
+ }
+
+ /**
+ * @param namespace the namespace to set
+ */
+ public void setNamespace(String namespace) {
+ this.namespace = namespace;
+ }
+
+ /**
+ * @return the dataList
+ */
+ public MessageDTO getData() {
+ return data;
+ }
+
+ /**
+ * @param data the data to set
+ */
+ public void setData(MessageDTO data) {
+ this.data = data;
+ }
+
+ public MessageDTO getMessage() {
+ return data;
+ }
+
+ /**
+ * Returns the id of the link or null, if there is no link or the link does not have an id.
+ *
+ * @return String the id of the link or null
+ */
+ public String getSourceId() {
+ if (source != null) {
+ final String linkType = getSourceLinkType();
+ if (linkType != null && !LinkDTO.LINK_TYPE_UNKNOWN.equals(linkType)
+ && !LinkDTO.LINK_TYPE_SHC.equals(linkType)) {
+ return source.replace(linkType, "");
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Returns the Type of the {@link LinkDTO} in the {@link EventDTO}.
+ *
+ * @return type of the {@link LinkDTO}
+ */
+ private String getSourceLinkType() {
+ if (source != null) {
+ return LinkDTO.getLinkType(source);
+ }
+ return null;
+ }
+}
diff --git a/bundles/org.openhab.binding.livisismarthome/src/main/java/org/openhab/binding/livisismarthome/internal/client/api/entity/link/LinkDTO.java b/bundles/org.openhab.binding.livisismarthome/src/main/java/org/openhab/binding/livisismarthome/internal/client/api/entity/link/LinkDTO.java
new file mode 100644
index 00000000000..03361115e02
--- /dev/null
+++ b/bundles/org.openhab.binding.livisismarthome/src/main/java/org/openhab/binding/livisismarthome/internal/client/api/entity/link/LinkDTO.java
@@ -0,0 +1,113 @@
+/**
+ * Copyright (c) 2010-2022 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.livisismarthome.internal.client.api.entity.link;
+
+/**
+ * Defines the data structure for a {@link LinkDTO}. This is the basic component used to link different data types in
+ * the
+ * LIVISI API.
+ *
+ * @author Oliver Kuhl - Initial contribution
+ */
+public class LinkDTO {
+
+ public static final String LINK_TYPE_CAPABILITY = "/capability/";
+ public static final String LINK_TYPE_DEVICE = "/device/";
+ public static final String LINK_TYPE_MESSAGE = "/message/";
+ public static final String LINK_TYPE_SHC = "/desc/device/SHC.RWE/";
+ public static final String LINK_TYPE_UNKNOWN = "unknown";
+
+ /**
+ * Returns the Type of the {@link LinkDTO}.
+ *
+ * @return {@link #LINK_TYPE_CAPABILITY},{@link #LINK_TYPE_DEVICE}, {@link #LINK_TYPE_MESSAGE},
+ * {@link #LINK_TYPE_SHC} or {@link #LINK_TYPE_UNKNOWN}
+ */
+ public static String getLinkType(String link) {
+ if (link.startsWith(LINK_TYPE_CAPABILITY)) {
+ return LINK_TYPE_CAPABILITY;
+ } else if (link.startsWith(LINK_TYPE_DEVICE)) {
+ return LINK_TYPE_DEVICE;
+ } else if (link.startsWith(LINK_TYPE_MESSAGE)) {
+ return LINK_TYPE_MESSAGE;
+ } else if (link.startsWith(LINK_TYPE_SHC)) {
+ return LINK_TYPE_SHC;
+ } else {
+ return LINK_TYPE_UNKNOWN;
+ }
+ }
+
+ /**
+ * Returns the id of the {@link LinkDTO} or null, if the link does not have an id or even no value.
+ *
+ * @return String the id of the link or null
+ */
+ public static String getId(String link) {
+ if (link != null) {
+ final String linkType = getLinkType(link);
+ if (linkType != null && !LinkDTO.LINK_TYPE_UNKNOWN.equals(linkType)
+ && !LinkDTO.LINK_TYPE_SHC.equals(linkType)) {
+ return link.replace(linkType, "");
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Returns true, if the {@link LinkDTO} points to a
+ * {@link org.openhab.binding.livisismarthome.internal.client.api.entity.capability.CapabilityDTO}.
+ *
+ * @return true if the link points to a capability, otherwise false
+ */
+ public static boolean isTypeCapability(String link) {
+ return LINK_TYPE_CAPABILITY.equals(LinkDTO.getLinkType(link));
+ }
+
+ /**
+ * Returns true, if the {@link LinkDTO} points to a
+ * {@link org.openhab.binding.livisismarthome.internal.client.api.entity.device.DeviceDTO}.
+ *
+ * @return true if the link points to a device, otherwise false
+ */
+ public static boolean isTypeDevice(String link) {
+ return LINK_TYPE_DEVICE.equals(LinkDTO.getLinkType(link));
+ }
+
+ /**
+ * Returns true, if the {@link LinkDTO} points to a
+ * {@link org.openhab.binding.livisismarthome.internal.client.api.entity.message.MessageDTO}.
+ *
+ * @return true if the link points to a message, otherwise false
+ */
+ public static boolean isTypeMessage(String link) {
+ return LINK_TYPE_MESSAGE.equals(LinkDTO.getLinkType(link));
+ }
+
+ /**
+ * Returns true, if the {@link LinkDTO} points to a SHC.
+ *
+ * @return true if the link points to a SHC bridge device, otherwise false
+ */
+ public static boolean isTypeSHC(String link) {
+ return LINK_TYPE_SHC.equals(LinkDTO.getLinkType(link));
+ }
+
+ /**
+ * Returns true, if the {@link LinkDTO} points to something unknown.
+ *
+ * @return true if the link points to something unknown, otherwise false
+ */
+ public static boolean isTypeUnknown(String link) {
+ return LINK_TYPE_UNKNOWN.equals(LinkDTO.getLinkType(link));
+ }
+}
diff --git a/bundles/org.openhab.binding.livisismarthome/src/main/java/org/openhab/binding/livisismarthome/internal/client/api/entity/location/LocationConfigDTO.java b/bundles/org.openhab.binding.livisismarthome/src/main/java/org/openhab/binding/livisismarthome/internal/client/api/entity/location/LocationConfigDTO.java
new file mode 100644
index 00000000000..60a67114761
--- /dev/null
+++ b/bundles/org.openhab.binding.livisismarthome/src/main/java/org/openhab/binding/livisismarthome/internal/client/api/entity/location/LocationConfigDTO.java
@@ -0,0 +1,59 @@
+/**
+ * Copyright (c) 2010-2022 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.livisismarthome.internal.client.api.entity.location;
+
+/**
+ * Defines the structure of the configuration of a {@link LocationDTO}.
+ *
+ * @author Oliver Kuhl - Initial contribution
+ */
+public class LocationConfigDTO {
+
+ /**
+ * Name of the {@link LocationDTO}
+ */
+ private String name;
+
+ /**
+ * Type of the {@link LocationDTO}
+ */
+ private String type;
+
+ /**
+ * @return the name
+ */
+ public String getName() {
+ return name;
+ }
+
+ /**
+ * @param name the name to set
+ */
+ public void setName(String name) {
+ this.name = name;
+ }
+
+ /**
+ * @return the type
+ */
+ public String getType() {
+ return type;
+ }
+
+ /**
+ * @param type the type to set
+ */
+ public void setType(String type) {
+ this.type = type;
+ }
+}
diff --git a/bundles/org.openhab.binding.livisismarthome/src/main/java/org/openhab/binding/livisismarthome/internal/client/api/entity/location/LocationDTO.java b/bundles/org.openhab.binding.livisismarthome/src/main/java/org/openhab/binding/livisismarthome/internal/client/api/entity/location/LocationDTO.java
new file mode 100644
index 00000000000..55441b86e9a
--- /dev/null
+++ b/bundles/org.openhab.binding.livisismarthome/src/main/java/org/openhab/binding/livisismarthome/internal/client/api/entity/location/LocationDTO.java
@@ -0,0 +1,73 @@
+/**
+ * Copyright (c) 2010-2022 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.livisismarthome.internal.client.api.entity.location;
+
+/**
+ * Defines a {@link LocationDTO} structure.
+ *
+ * @author Oliver Kuhl - Initial contribution
+ */
+public class LocationDTO {
+
+ /**
+ * Identifier of the location – must be unique.
+ */
+ private String id;
+
+ /**
+ * Configuration properties of the {@link LocationDTO}.
+ */
+ private LocationConfigDTO config;
+
+ /**
+ * @return the id
+ */
+ public String getId() {
+ return id;
+ }
+
+ /**
+ * @param id the id to set
+ */
+ public void setId(String id) {
+ this.id = id;
+ }
+
+ /**
+ * @return the config
+ */
+ public LocationConfigDTO getConfig() {
+ return config;
+ }
+
+ /**
+ * @param config the config to set
+ */
+ public void setConfig(LocationConfigDTO config) {
+ this.config = config;
+ }
+
+ /**
+ * @return the location name
+ */
+ public String getName() {
+ return getConfig().getName();
+ }
+
+ /**
+ * @return the location type
+ */
+ public String getType() {
+ return getConfig().getType();
+ }
+}
diff --git a/bundles/org.openhab.binding.livisismarthome/src/main/java/org/openhab/binding/livisismarthome/internal/client/api/entity/message/MessageDTO.java b/bundles/org.openhab.binding.livisismarthome/src/main/java/org/openhab/binding/livisismarthome/internal/client/api/entity/message/MessageDTO.java
new file mode 100644
index 00000000000..ef3e87d8e4a
--- /dev/null
+++ b/bundles/org.openhab.binding.livisismarthome/src/main/java/org/openhab/binding/livisismarthome/internal/client/api/entity/message/MessageDTO.java
@@ -0,0 +1,210 @@
+/**
+ * Copyright (c) 2010-2022 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.livisismarthome.internal.client.api.entity.message;
+
+import java.util.List;
+
+import com.google.gson.annotations.SerializedName;
+
+/**
+ * Defines the structure of a {@link MessageDTO}. Messages are part of the LIVISI SmartHome system and besides other
+ * things
+ * are used
+ * to raise battery warnings.
+ *
+ * @author Oliver Kuhl - Initial contribution
+ */
+public class MessageDTO {
+
+ /** device related messages */
+ public static final String TYPE_DEVICE_UNREACHABLE = "DeviceUnreachable";
+ public static final String TYPE_DEVICE_LOW_BATTERY = "DeviceLowBattery";
+
+ /**
+ * Identifier of the message – must be unique.
+ */
+ private String id;
+
+ /**
+ * Specifies the type of the message.
+ */
+ private String type;
+
+ /**
+ * Defines whether the message has been viewed by a user.
+ */
+ @SerializedName("read")
+ private boolean isRead;
+
+ /**
+ * Defines whether it is an alert or a message, default is message.
+ */
+ @SerializedName("class")
+ private String messageClass;
+
+ /**
+ * Timestamp when the message was created.
+ *
+ * Optional.
+ */
+ private String timestamp;
+
+ /**
+ * Reference to the underlying devices, which the message relates to.
+ *
+ * Optional.
+ */
+ private List devices;
+
+ /**
+ * Container for all parameters of the message. The parameters are contained in Property entities.
+ *
+ * Optional.
+ */
+ private MessagePropertiesDTO properties;
+
+ /**
+ * The product (context) that generated the message.
+ */
+ private String namespace;
+
+ /**
+ * @return the id
+ */
+ public String getId() {
+ return id;
+ }
+
+ /**
+ * @param id the id to set
+ */
+ public void setId(String id) {
+ this.id = id;
+ }
+
+ /**
+ * @return the type
+ */
+ public String getType() {
+ return type;
+ }
+
+ /**
+ * @param type the type to set
+ */
+ public void setType(String type) {
+ this.type = type;
+ }
+
+ /**
+ * @return the messageClass
+ */
+ public String getMessageClass() {
+ return messageClass;
+ }
+
+ /**
+ * @param messageClass the messageClass to set
+ */
+ public void setMessageClass(String messageClass) {
+ this.messageClass = messageClass;
+ }
+
+ /**
+ * @return the timestamp
+ */
+ public String getTimestamp() {
+ return timestamp;
+ }
+
+ /**
+ * @param timestamp the timestamp to set
+ */
+ public void setTimestamp(String timestamp) {
+ this.timestamp = timestamp;
+ }
+
+ /**
+ * @return the isRead
+ */
+ public boolean isRead() {
+ return isRead;
+ }
+
+ /**
+ * @param isRead the isRead to set
+ */
+ public void setRead(boolean isRead) {
+ this.isRead = isRead;
+ }
+
+ /**
+ * @return the devices
+ */
+ public List getDevices() {
+ return devices;
+ }
+
+ /**
+ * @param devices the devices to set
+ */
+ public void setDevices(List devices) {
+ this.devices = devices;
+ }
+
+ /**
+ * @return the dataPropertyList
+ */
+ public MessagePropertiesDTO getProperties() {
+ return properties;
+ }
+
+ /**
+ * @param properties the dataPropertyList to set
+ */
+ public void setProperties(MessagePropertiesDTO properties) {
+ this.properties = properties;
+ }
+
+ /**
+ * @return the namespace
+ */
+ public String getNamespace() {
+ return namespace;
+ }
+
+ /**
+ * @param namespace the namespace to set
+ */
+ public void setNamespace(String namespace) {
+ this.namespace = namespace;
+ }
+
+ /**
+ * Returns true, if the message is of type "DeviceUnreachable".
+ *
+ * @return true if the message is of type "DeviceUnreachable", otherwise false
+ */
+ public boolean isTypeDeviceUnreachable() {
+ return TYPE_DEVICE_UNREACHABLE.equals(type);
+ }
+
+ /**
+ * Returns true, if the message is of type "DeviceLowBattery".
+ *
+ * @return true if the message is of type "DeviceLowBattery", otherwise false
+ */
+ public boolean isTypeDeviceLowBattery() {
+ return TYPE_DEVICE_LOW_BATTERY.equals(type);
+ }
+}
diff --git a/bundles/org.openhab.binding.livisismarthome/src/main/java/org/openhab/binding/livisismarthome/internal/client/api/entity/message/MessagePropertiesDTO.java b/bundles/org.openhab.binding.livisismarthome/src/main/java/org/openhab/binding/livisismarthome/internal/client/api/entity/message/MessagePropertiesDTO.java
new file mode 100644
index 00000000000..2986ee8133a
--- /dev/null
+++ b/bundles/org.openhab.binding.livisismarthome/src/main/java/org/openhab/binding/livisismarthome/internal/client/api/entity/message/MessagePropertiesDTO.java
@@ -0,0 +1,79 @@
+/**
+ * Copyright (c) 2010-2022 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.livisismarthome.internal.client.api.entity.message;
+
+/**
+ * @author Oliver Kuhl - Initial contribution
+ *
+ */
+public class MessagePropertiesDTO {
+
+ /**
+ * Name of the referenced {@link org.openhab.binding.livisismarthome.internal.client.api.entity.device.DeviceDTO}
+ */
+ private String deviceName;
+
+ /**
+ * Serialnumber of the referenced
+ * {@link org.openhab.binding.livisismarthome.internal.client.api.entity.device.DeviceDTO}
+ */
+ private String serialNumber;
+
+ /**
+ * Locationname of the referenced
+ * {@link org.openhab.binding.livisismarthome.internal.client.api.entity.device.DeviceDTO}
+ */
+ private String locationName;
+
+ /**
+ * @return the deviceName
+ */
+ public String getDeviceName() {
+ return deviceName;
+ }
+
+ /**
+ * @param deviceName the deviceName to set
+ */
+ public void setDeviceName(String deviceName) {
+ this.deviceName = deviceName;
+ }
+
+ /**
+ * @return the serialNumber
+ */
+ public String getSerialNumber() {
+ return serialNumber;
+ }
+
+ /**
+ * @param serialNumber the serialNumber to set
+ */
+ public void setSerialNumber(String serialNumber) {
+ this.serialNumber = serialNumber;
+ }
+
+ /**
+ * @return the locationName
+ */
+ public String getLocationName() {
+ return locationName;
+ }
+
+ /**
+ * @param locationName the locationName to set
+ */
+ public void setLocationName(String locationName) {
+ this.locationName = locationName;
+ }
+}
diff --git a/bundles/org.openhab.binding.livisismarthome/src/main/java/org/openhab/binding/livisismarthome/internal/client/api/entity/state/BaseStateDTO.java b/bundles/org.openhab.binding.livisismarthome/src/main/java/org/openhab/binding/livisismarthome/internal/client/api/entity/state/BaseStateDTO.java
new file mode 100644
index 00000000000..70ee2016758
--- /dev/null
+++ b/bundles/org.openhab.binding.livisismarthome/src/main/java/org/openhab/binding/livisismarthome/internal/client/api/entity/state/BaseStateDTO.java
@@ -0,0 +1,35 @@
+/**
+ * Copyright (c) 2010-2022 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.livisismarthome.internal.client.api.entity.state;
+
+/**
+ * @author Oliver Kuhl - Initial contribution
+ */
+public abstract class BaseStateDTO {
+
+ private String lastChanged;
+
+ /**
+ * @return the lastChanged
+ */
+ public String getLastChanged() {
+ return lastChanged;
+ }
+
+ /**
+ * @param lastChanged the lastChanged to set
+ */
+ public void setLastChanged(String lastChanged) {
+ this.lastChanged = lastChanged;
+ }
+}
diff --git a/bundles/org.openhab.binding.livisismarthome/src/main/java/org/openhab/binding/livisismarthome/internal/client/api/entity/state/BooleanStateDTO.java b/bundles/org.openhab.binding.livisismarthome/src/main/java/org/openhab/binding/livisismarthome/internal/client/api/entity/state/BooleanStateDTO.java
new file mode 100644
index 00000000000..8a7991d5bf1
--- /dev/null
+++ b/bundles/org.openhab.binding.livisismarthome/src/main/java/org/openhab/binding/livisismarthome/internal/client/api/entity/state/BooleanStateDTO.java
@@ -0,0 +1,35 @@
+/**
+ * Copyright (c) 2010-2022 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.livisismarthome.internal.client.api.entity.state;
+
+/**
+ * @author Oliver Kuhl - Initial contribution
+ */
+public class BooleanStateDTO extends BaseStateDTO {
+
+ private Boolean value;
+
+ /**
+ * @return the value
+ */
+ public Boolean getValue() {
+ return value;
+ }
+
+ /**
+ * @param value the value to set
+ */
+ public void setValue(Boolean value) {
+ this.value = value;
+ }
+}
diff --git a/bundles/org.openhab.binding.livisismarthome/src/main/java/org/openhab/binding/livisismarthome/internal/client/api/entity/state/DateTimeStateDTO.java b/bundles/org.openhab.binding.livisismarthome/src/main/java/org/openhab/binding/livisismarthome/internal/client/api/entity/state/DateTimeStateDTO.java
new file mode 100644
index 00000000000..18a79c83dfa
--- /dev/null
+++ b/bundles/org.openhab.binding.livisismarthome/src/main/java/org/openhab/binding/livisismarthome/internal/client/api/entity/state/DateTimeStateDTO.java
@@ -0,0 +1,20 @@
+/**
+ * Copyright (c) 2010-2022 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.livisismarthome.internal.client.api.entity.state;
+
+/**
+ * @author Oliver Kuhl - Initial contribution
+ */
+public class DateTimeStateDTO extends StringStateDTO {
+
+}
diff --git a/bundles/org.openhab.binding.livisismarthome/src/main/java/org/openhab/binding/livisismarthome/internal/client/api/entity/state/DoubleStateDTO.java b/bundles/org.openhab.binding.livisismarthome/src/main/java/org/openhab/binding/livisismarthome/internal/client/api/entity/state/DoubleStateDTO.java
new file mode 100644
index 00000000000..6d30185dbab
--- /dev/null
+++ b/bundles/org.openhab.binding.livisismarthome/src/main/java/org/openhab/binding/livisismarthome/internal/client/api/entity/state/DoubleStateDTO.java
@@ -0,0 +1,35 @@
+/**
+ * Copyright (c) 2010-2022 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.livisismarthome.internal.client.api.entity.state;
+
+/**
+ * @author Oliver Kuhl - Initial contribution
+ */
+public class DoubleStateDTO extends BaseStateDTO {
+
+ private Double value;
+
+ /**
+ * @return the value
+ */
+ public Double getValue() {
+ return value;
+ }
+
+ /**
+ * @param value the value to set
+ */
+ public void setValue(Double value) {
+ this.value = value;
+ }
+}
diff --git a/bundles/org.openhab.binding.livisismarthome/src/main/java/org/openhab/binding/livisismarthome/internal/client/api/entity/state/IntegerStateDTO.java b/bundles/org.openhab.binding.livisismarthome/src/main/java/org/openhab/binding/livisismarthome/internal/client/api/entity/state/IntegerStateDTO.java
new file mode 100644
index 00000000000..e03bb2554c0
--- /dev/null
+++ b/bundles/org.openhab.binding.livisismarthome/src/main/java/org/openhab/binding/livisismarthome/internal/client/api/entity/state/IntegerStateDTO.java
@@ -0,0 +1,35 @@
+/**
+ * Copyright (c) 2010-2022 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.livisismarthome.internal.client.api.entity.state;
+
+/**
+ * @author Oliver Kuhl - Initial contribution
+ */
+public class IntegerStateDTO extends BaseStateDTO {
+
+ private Integer value;
+
+ /**
+ * @return the value
+ */
+ public Integer getValue() {
+ return value;
+ }
+
+ /**
+ * @param value the value to set
+ */
+ public void setValue(Integer value) {
+ this.value = value;
+ }
+}
diff --git a/bundles/org.openhab.binding.livisismarthome/src/main/java/org/openhab/binding/livisismarthome/internal/client/api/entity/state/StringStateDTO.java b/bundles/org.openhab.binding.livisismarthome/src/main/java/org/openhab/binding/livisismarthome/internal/client/api/entity/state/StringStateDTO.java
new file mode 100644
index 00000000000..6abf2b0c6ab
--- /dev/null
+++ b/bundles/org.openhab.binding.livisismarthome/src/main/java/org/openhab/binding/livisismarthome/internal/client/api/entity/state/StringStateDTO.java
@@ -0,0 +1,35 @@
+/**
+ * Copyright (c) 2010-2022 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.livisismarthome.internal.client.api.entity.state;
+
+/**
+ * @author Oliver Kuhl - Initial contribution
+ */
+public class StringStateDTO extends BaseStateDTO {
+
+ private String value;
+
+ /**
+ * @return the value
+ */
+ public String getValue() {
+ return value;
+ }
+
+ /**
+ * @param value the value to set
+ */
+ public void setValue(String value) {
+ this.value = value;
+ }
+}
diff --git a/bundles/org.openhab.binding.livisismarthome/src/main/java/org/openhab/binding/livisismarthome/internal/client/exception/ApiException.java b/bundles/org.openhab.binding.livisismarthome/src/main/java/org/openhab/binding/livisismarthome/internal/client/exception/ApiException.java
new file mode 100644
index 00000000000..d214508dae1
--- /dev/null
+++ b/bundles/org.openhab.binding.livisismarthome/src/main/java/org/openhab/binding/livisismarthome/internal/client/exception/ApiException.java
@@ -0,0 +1,36 @@
+/**
+ * Copyright (c) 2010-2022 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.livisismarthome.internal.client.exception;
+
+import java.io.IOException;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+
+/**
+ * Thrown, when the LIVISI SmartHome Client API returns an unknown error.
+ *
+ * @author Oliver Kuhl - Initial contribution
+ */
+@NonNullByDefault
+public class ApiException extends IOException {
+
+ private static final long serialVersionUID = -3581569381976159265L;
+
+ public ApiException(String message) {
+ super(message);
+ }
+
+ public ApiException(String message, Throwable cause) {
+ super(message, cause);
+ }
+}
diff --git a/bundles/org.openhab.binding.livisismarthome/src/main/java/org/openhab/binding/livisismarthome/internal/client/exception/AuthenticationException.java b/bundles/org.openhab.binding.livisismarthome/src/main/java/org/openhab/binding/livisismarthome/internal/client/exception/AuthenticationException.java
new file mode 100644
index 00000000000..fb149171118
--- /dev/null
+++ b/bundles/org.openhab.binding.livisismarthome/src/main/java/org/openhab/binding/livisismarthome/internal/client/exception/AuthenticationException.java
@@ -0,0 +1,31 @@
+/**
+ * Copyright (c) 2010-2022 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.livisismarthome.internal.client.exception;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+
+/**
+ * Thrown, if an authentication error is given.
+ *
+ * @author Hilbrand Bouwkamp - Initial contribution
+ *
+ */
+@NonNullByDefault
+public class AuthenticationException extends ApiException {
+
+ private static final long serialVersionUID = 720872317620845246L;
+
+ public AuthenticationException(String message) {
+ super(message);
+ }
+}
diff --git a/bundles/org.openhab.binding.livisismarthome/src/main/java/org/openhab/binding/livisismarthome/internal/client/exception/ControllerOfflineException.java b/bundles/org.openhab.binding.livisismarthome/src/main/java/org/openhab/binding/livisismarthome/internal/client/exception/ControllerOfflineException.java
new file mode 100644
index 00000000000..ba7a4a3edb8
--- /dev/null
+++ b/bundles/org.openhab.binding.livisismarthome/src/main/java/org/openhab/binding/livisismarthome/internal/client/exception/ControllerOfflineException.java
@@ -0,0 +1,33 @@
+/**
+ * Copyright (c) 2010-2022 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.livisismarthome.internal.client.exception;
+
+import java.io.IOException;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+
+/**
+ * Thrown, if the LIVISI SmartHome controller (SHC) is offline.
+ *
+ * @author Oliver Kuhl - Initial contribution
+ *
+ */
+@NonNullByDefault
+public class ControllerOfflineException extends IOException {
+
+ private static final long serialVersionUID = 2851756294511651529L;
+
+ public ControllerOfflineException(String message) {
+ super(message);
+ }
+}
diff --git a/bundles/org.openhab.binding.livisismarthome/src/main/java/org/openhab/binding/livisismarthome/internal/client/exception/InvalidActionTriggeredException.java b/bundles/org.openhab.binding.livisismarthome/src/main/java/org/openhab/binding/livisismarthome/internal/client/exception/InvalidActionTriggeredException.java
new file mode 100644
index 00000000000..190770f4076
--- /dev/null
+++ b/bundles/org.openhab.binding.livisismarthome/src/main/java/org/openhab/binding/livisismarthome/internal/client/exception/InvalidActionTriggeredException.java
@@ -0,0 +1,31 @@
+/**
+ * Copyright (c) 2010-2022 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.livisismarthome.internal.client.exception;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+
+/**
+ * Thrown, if an action was called with invalid parameters.
+ *
+ * @author Oliver Kuhl - Initial contribution
+ *
+ */
+@NonNullByDefault
+public class InvalidActionTriggeredException extends ApiException {
+
+ private static final long serialVersionUID = 5320848072133493770L;
+
+ public InvalidActionTriggeredException(String message) {
+ super(message);
+ }
+}
diff --git a/bundles/org.openhab.binding.livisismarthome/src/main/java/org/openhab/binding/livisismarthome/internal/client/exception/RemoteAccessNotAllowedException.java b/bundles/org.openhab.binding.livisismarthome/src/main/java/org/openhab/binding/livisismarthome/internal/client/exception/RemoteAccessNotAllowedException.java
new file mode 100644
index 00000000000..d197fa0efc4
--- /dev/null
+++ b/bundles/org.openhab.binding.livisismarthome/src/main/java/org/openhab/binding/livisismarthome/internal/client/exception/RemoteAccessNotAllowedException.java
@@ -0,0 +1,31 @@
+/**
+ * Copyright (c) 2010-2022 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.livisismarthome.internal.client.exception;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+
+/**
+ * Thrown, when the authorization fails with a "remote access not allowed" error.
+ *
+ * @author Oliver Kuhl - Initial contribution
+ *
+ */
+@NonNullByDefault
+public class RemoteAccessNotAllowedException extends ApiException {
+
+ private static final long serialVersionUID = 3399664327165581656L;
+
+ public RemoteAccessNotAllowedException(String message) {
+ super(message);
+ }
+}
diff --git a/bundles/org.openhab.binding.livisismarthome/src/main/java/org/openhab/binding/livisismarthome/internal/client/exception/ServiceUnavailableException.java b/bundles/org.openhab.binding.livisismarthome/src/main/java/org/openhab/binding/livisismarthome/internal/client/exception/ServiceUnavailableException.java
new file mode 100644
index 00000000000..3d5c357a0f3
--- /dev/null
+++ b/bundles/org.openhab.binding.livisismarthome/src/main/java/org/openhab/binding/livisismarthome/internal/client/exception/ServiceUnavailableException.java
@@ -0,0 +1,31 @@
+/**
+ * Copyright (c) 2010-2022 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.livisismarthome.internal.client.exception;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+
+/**
+ * Thrown, if the LIVISI service is unavailable (HTTP response 503).
+ *
+ * @author Oliver Kuhl - Initial contribution
+ *
+ */
+@NonNullByDefault
+public class ServiceUnavailableException extends ApiException {
+
+ private static final long serialVersionUID = -9148687420729079329L;
+
+ public ServiceUnavailableException(String message) {
+ super(message);
+ }
+}
diff --git a/bundles/org.openhab.binding.livisismarthome/src/main/java/org/openhab/binding/livisismarthome/internal/client/exception/SessionExistsException.java b/bundles/org.openhab.binding.livisismarthome/src/main/java/org/openhab/binding/livisismarthome/internal/client/exception/SessionExistsException.java
new file mode 100644
index 00000000000..def5baf0525
--- /dev/null
+++ b/bundles/org.openhab.binding.livisismarthome/src/main/java/org/openhab/binding/livisismarthome/internal/client/exception/SessionExistsException.java
@@ -0,0 +1,31 @@
+/**
+ * Copyright (c) 2010-2022 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.livisismarthome.internal.client.exception;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+
+/**
+ * Thrown, when a session already exists while initializing a new session.
+ *
+ * @author Oliver Kuhl - Initial contribution
+ *
+ */
+@NonNullByDefault
+public class SessionExistsException extends ApiException {
+
+ private static final long serialVersionUID = -5497104376650832564L;
+
+ public SessionExistsException(String message) {
+ super(message);
+ }
+}
diff --git a/bundles/org.openhab.binding.livisismarthome/src/main/java/org/openhab/binding/livisismarthome/internal/client/exception/SessionNotFoundException.java b/bundles/org.openhab.binding.livisismarthome/src/main/java/org/openhab/binding/livisismarthome/internal/client/exception/SessionNotFoundException.java
new file mode 100644
index 00000000000..ab3e4915c1c
--- /dev/null
+++ b/bundles/org.openhab.binding.livisismarthome/src/main/java/org/openhab/binding/livisismarthome/internal/client/exception/SessionNotFoundException.java
@@ -0,0 +1,31 @@
+/**
+ * Copyright (c) 2010-2022 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.livisismarthome.internal.client.exception;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+
+/**
+ * Thrown, if the session is not initialized or disconnected.
+ *
+ * @author Oliver Kuhl - Initial contribution
+ *
+ */
+@NonNullByDefault
+public class SessionNotFoundException extends ApiException {
+
+ private static final long serialVersionUID = -2621323485648025577L;
+
+ public SessionNotFoundException(String message) {
+ super(message);
+ }
+}
diff --git a/bundles/org.openhab.binding.livisismarthome/src/main/java/org/openhab/binding/livisismarthome/internal/client/exception/WebSocketConnectException.java b/bundles/org.openhab.binding.livisismarthome/src/main/java/org/openhab/binding/livisismarthome/internal/client/exception/WebSocketConnectException.java
new file mode 100644
index 00000000000..0c95ee58095
--- /dev/null
+++ b/bundles/org.openhab.binding.livisismarthome/src/main/java/org/openhab/binding/livisismarthome/internal/client/exception/WebSocketConnectException.java
@@ -0,0 +1,32 @@
+/**
+ * Copyright (c) 2010-2022 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.livisismarthome.internal.client.exception;
+
+import java.io.IOException;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+
+/**
+ * Thrown, if the WebSocket couldn't get started / connected.
+ *
+ * @author Sven Strohschein - Initial contribution
+ */
+@NonNullByDefault
+public class WebSocketConnectException extends IOException {
+
+ private static final long serialVersionUID = -5594715669510573378L;
+
+ public WebSocketConnectException(String message, Throwable cause) {
+ super(message, cause);
+ }
+}
diff --git a/bundles/org.openhab.binding.livisismarthome/src/main/java/org/openhab/binding/livisismarthome/internal/discovery/LivisiDeviceDiscoveryService.java b/bundles/org.openhab.binding.livisismarthome/src/main/java/org/openhab/binding/livisismarthome/internal/discovery/LivisiDeviceDiscoveryService.java
new file mode 100644
index 00000000000..271c0272b63
--- /dev/null
+++ b/bundles/org.openhab.binding.livisismarthome/src/main/java/org/openhab/binding/livisismarthome/internal/discovery/LivisiDeviceDiscoveryService.java
@@ -0,0 +1,172 @@
+/**
+ * Copyright (c) 2010-2022 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.livisismarthome.internal.discovery;
+
+import static org.openhab.binding.livisismarthome.internal.LivisiBindingConstants.PROPERTY_ID;
+
+import java.util.Date;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Optional;
+import java.util.Set;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.eclipse.jdt.annotation.Nullable;
+import org.openhab.binding.livisismarthome.internal.LivisiBindingConstants;
+import org.openhab.binding.livisismarthome.internal.client.api.entity.device.DeviceDTO;
+import org.openhab.binding.livisismarthome.internal.handler.LivisiBridgeHandler;
+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.config.discovery.DiscoveryService;
+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 LivisiDeviceDiscoveryService} is responsible for discovering new devices.
+ *
+ * @author Oliver Kuhl - Initial contribution
+ * @author Sven Strohschein - Renamed from Innogy to Livisi
+ */
+@NonNullByDefault
+public class LivisiDeviceDiscoveryService extends AbstractDiscoveryService
+ implements DiscoveryService, ThingHandlerService {
+
+ private static final int SEARCH_TIME_SECONDS = 60;
+
+ private final Logger logger = LoggerFactory.getLogger(LivisiDeviceDiscoveryService.class);
+
+ private @Nullable LivisiBridgeHandler bridgeHandler;
+
+ /**
+ * Construct an {@link LivisiDeviceDiscoveryService}.
+ */
+ public LivisiDeviceDiscoveryService() {
+ super(SEARCH_TIME_SECONDS);
+ }
+
+ /**
+ * Deactivates the {@link LivisiDeviceDiscoveryService} by unregistering it as
+ * {@link org.openhab.binding.livisismarthome.internal.listener.DeviceStatusListener} on the
+ * {@link LivisiBridgeHandler}. Older discovery results will be removed.
+ *
+ * @see org.openhab.core.config.discovery.AbstractDiscoveryService#deactivate()
+ */
+ @Override
+ public void deactivate() {
+ removeOlderResults(new Date().getTime());
+ }
+
+ @Override
+ public Set getSupportedThingTypes() {
+ return LivisiBindingConstants.SUPPORTED_DEVICE_THING_TYPES;
+ }
+
+ @Override
+ protected void startScan() {
+ logger.debug("SCAN for new LIVISI SmartHome devices started...");
+ final LivisiBridgeHandler bridgeHandlerNonNullable = bridgeHandler;
+ if (bridgeHandlerNonNullable != null) {
+ for (final DeviceDTO d : bridgeHandlerNonNullable.loadDevices()) {
+ onDeviceAdded(d);
+ }
+ }
+ }
+
+ @Override
+ protected synchronized void stopScan() {
+ super.stopScan();
+ removeOlderResults(getTimestampOfLastScan());
+ }
+
+ public void onDeviceAdded(DeviceDTO device) {
+ final LivisiBridgeHandler bridgeHandlerNonNullable = bridgeHandler;
+ if (bridgeHandlerNonNullable != null) {
+ final ThingUID bridgeUID = bridgeHandlerNonNullable.getThing().getUID();
+ final Optional thingUID = getThingUID(bridgeUID, device);
+ final Optional thingTypeUID = getThingTypeUID(device);
+
+ if (thingUID.isPresent() && thingTypeUID.isPresent()) {
+ String name = device.getConfig().getName();
+ if (name.isEmpty()) {
+ name = device.getSerialNumber();
+ }
+
+ final Map properties = new HashMap<>();
+ properties.put(PROPERTY_ID, device.getId());
+
+ final String label;
+ if (device.hasLocation()) {
+ label = device.getType() + ": " + name + " (" + device.getLocation().getName() + ")";
+ } else {
+ label = device.getType() + ": " + name;
+ }
+
+ final DiscoveryResult discoveryResult = DiscoveryResultBuilder.create(thingUID.get())
+ .withThingType(thingTypeUID.get()).withProperties(properties).withBridge(bridgeUID)
+ .withRepresentationProperty(PROPERTY_ID).withLabel(label).build();
+
+ thingDiscovered(discoveryResult);
+ } else {
+ logger.debug("Discovered unsupported device of type '{}' and name '{}' with id {}", device.getType(),
+ device.getConfig().getName(), device.getId());
+ }
+ }
+ }
+
+ /**
+ * Returns the {@link ThingUID} for the given {@link DeviceDTO} or empty, if the device type is not available.
+ *
+ * @param bridgeUID bridge
+ * @param device device
+ * @return {@link ThingUID} for the given {@link DeviceDTO} or empty
+ */
+ private Optional getThingUID(ThingUID bridgeUID, DeviceDTO device) {
+ final Optional thingTypeUID = getThingTypeUID(device);
+
+ if (thingTypeUID.isPresent() && getSupportedThingTypes().contains(thingTypeUID.get())) {
+ return Optional.of(new ThingUID(thingTypeUID.get(), bridgeUID, device.getId()));
+ }
+ return Optional.empty();
+ }
+
+ /**
+ * Returns a {@link ThingTypeUID} for the given {@link DeviceDTO} or empty, if the device type is not available.
+ *
+ * @param device device
+ * @return {@link ThingTypeUID} for the given {@link DeviceDTO} or empty
+ */
+ private Optional getThingTypeUID(DeviceDTO device) {
+ final String thingTypeId = device.getType();
+ if (thingTypeId != null) {
+ return Optional.of(new ThingTypeUID(LivisiBindingConstants.BINDING_ID, thingTypeId));
+ }
+ return Optional.empty();
+ }
+
+ @Override
+ public void setThingHandler(@Nullable ThingHandler handler) {
+ if (handler instanceof LivisiBridgeHandler) {
+ bridgeHandler = (LivisiBridgeHandler) handler;
+ }
+ }
+
+ @Override
+ public @Nullable ThingHandler getThingHandler() {
+ return bridgeHandler;
+ }
+}
diff --git a/bundles/org.openhab.binding.livisismarthome/src/main/java/org/openhab/binding/livisismarthome/internal/handler/LivisiBridgeConfiguration.java b/bundles/org.openhab.binding.livisismarthome/src/main/java/org/openhab/binding/livisismarthome/internal/handler/LivisiBridgeConfiguration.java
new file mode 100644
index 00000000000..e9a5797ed03
--- /dev/null
+++ b/bundles/org.openhab.binding.livisismarthome/src/main/java/org/openhab/binding/livisismarthome/internal/handler/LivisiBridgeConfiguration.java
@@ -0,0 +1,27 @@
+/**
+ * Copyright (c) 2010-2022 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.livisismarthome.internal.handler;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+
+/**
+ * @author Hilbrand Bouwkamp - Initial contribution
+ * @author Sven Strohschein - Renamed from Innogy to Livisi
+ */
+@NonNullByDefault
+public class LivisiBridgeConfiguration {
+
+ public String host = "";
+ public String password = "";
+ public int webSocketIdleTimeout = 900;
+}
diff --git a/bundles/org.openhab.binding.livisismarthome/src/main/java/org/openhab/binding/livisismarthome/internal/handler/LivisiBridgeHandler.java b/bundles/org.openhab.binding.livisismarthome/src/main/java/org/openhab/binding/livisismarthome/internal/handler/LivisiBridgeHandler.java
new file mode 100644
index 00000000000..176371de0be
--- /dev/null
+++ b/bundles/org.openhab.binding.livisismarthome/src/main/java/org/openhab/binding/livisismarthome/internal/handler/LivisiBridgeHandler.java
@@ -0,0 +1,924 @@
+/**
+ * Copyright (c) 2010-2022 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.livisismarthome.internal.handler;
+
+import static org.openhab.binding.livisismarthome.internal.LivisiBindingConstants.*;
+
+import java.io.IOException;
+import java.net.SocketTimeoutException;
+import java.net.URI;
+import java.time.format.DateTimeFormatter;
+import java.time.format.FormatStyle;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Map;
+import java.util.Objects;
+import java.util.Optional;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ExecutionException;
+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.openhab.binding.livisismarthome.internal.LivisiBindingConstants;
+import org.openhab.binding.livisismarthome.internal.LivisiWebSocket;
+import org.openhab.binding.livisismarthome.internal.client.GsonOptional;
+import org.openhab.binding.livisismarthome.internal.client.LivisiClient;
+import org.openhab.binding.livisismarthome.internal.client.URLConnectionFactory;
+import org.openhab.binding.livisismarthome.internal.client.URLCreator;
+import org.openhab.binding.livisismarthome.internal.client.api.entity.action.ShutterActionType;
+import org.openhab.binding.livisismarthome.internal.client.api.entity.capability.CapabilityDTO;
+import org.openhab.binding.livisismarthome.internal.client.api.entity.device.DeviceConfigDTO;
+import org.openhab.binding.livisismarthome.internal.client.api.entity.device.DeviceDTO;
+import org.openhab.binding.livisismarthome.internal.client.api.entity.device.DeviceStateDTO;
+import org.openhab.binding.livisismarthome.internal.client.api.entity.event.BaseEventDTO;
+import org.openhab.binding.livisismarthome.internal.client.api.entity.event.EventDTO;
+import org.openhab.binding.livisismarthome.internal.client.api.entity.event.MessageEventDTO;
+import org.openhab.binding.livisismarthome.internal.client.api.entity.link.LinkDTO;
+import org.openhab.binding.livisismarthome.internal.client.api.entity.message.MessageDTO;
+import org.openhab.binding.livisismarthome.internal.client.exception.ApiException;
+import org.openhab.binding.livisismarthome.internal.client.exception.AuthenticationException;
+import org.openhab.binding.livisismarthome.internal.client.exception.ControllerOfflineException;
+import org.openhab.binding.livisismarthome.internal.client.exception.InvalidActionTriggeredException;
+import org.openhab.binding.livisismarthome.internal.client.exception.RemoteAccessNotAllowedException;
+import org.openhab.binding.livisismarthome.internal.client.exception.SessionExistsException;
+import org.openhab.binding.livisismarthome.internal.discovery.LivisiDeviceDiscoveryService;
+import org.openhab.binding.livisismarthome.internal.listener.DeviceStatusListener;
+import org.openhab.binding.livisismarthome.internal.listener.EventListener;
+import org.openhab.binding.livisismarthome.internal.manager.DeviceStructureManager;
+import org.openhab.binding.livisismarthome.internal.manager.FullDeviceManager;
+import org.openhab.core.auth.client.oauth2.AccessTokenRefreshListener;
+import org.openhab.core.auth.client.oauth2.AccessTokenResponse;
+import org.openhab.core.auth.client.oauth2.OAuthClientService;
+import org.openhab.core.auth.client.oauth2.OAuthException;
+import org.openhab.core.auth.client.oauth2.OAuthFactory;
+import org.openhab.core.auth.client.oauth2.OAuthResponseException;
+import org.openhab.core.library.types.QuantityType;
+import org.openhab.core.library.types.StringType;
+import org.openhab.core.library.unit.Units;
+import org.openhab.core.thing.Bridge;
+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.BaseBridgeHandler;
+import org.openhab.core.thing.binding.ThingHandlerService;
+import org.openhab.core.types.Command;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * The {@link LivisiBridgeHandler} is responsible for handling the LIVISI SmartHome controller including the connection
+ * to the LIVISI SmartHome backend for all communications with the LIVISI SmartHome {@link DeviceDTO}s.
+ *
+ * It implements the {@link AccessTokenRefreshListener} to handle updates of the oauth2 tokens and the
+ * {@link EventListener} to handle {@link EventDTO}s, that are received by the {@link LivisiWebSocket}.
+ *
+ * The {@link DeviceDTO}s are organized by the {@link DeviceStructureManager}, which is also responsible for the
+ * connection
+ * to the LIVISI SmartHome webservice via the {@link LivisiClient}.
+ *
+ * @author Oliver Kuhl - Initial contribution
+ * @author Hilbrand Bouwkamp - Refactored to use openHAB http and oauth2 libraries
+ * @author Sven Strohschein - Renamed from Innogy to Livisi
+ */
+@NonNullByDefault
+public class LivisiBridgeHandler extends BaseBridgeHandler
+ implements AccessTokenRefreshListener, EventListener, DeviceStatusListener {
+
+ private final Logger logger = LoggerFactory.getLogger(LivisiBridgeHandler.class);
+ private final GsonOptional gson = new GsonOptional();
+ private final Object lock = new Object();
+ private final Map deviceStatusListeners;
+ private final OAuthFactory oAuthFactory;
+ private final HttpClient httpClient;
+
+ private @NonNullByDefault({}) LivisiClient client;
+ private @Nullable LivisiWebSocket webSocket;
+ private @NonNullByDefault({}) DeviceStructureManager deviceStructMan;
+ private @Nullable String bridgeId;
+ private @Nullable ScheduledFuture> reInitJob;
+ private @Nullable ScheduledFuture> bridgeRefreshJob;
+ private @NonNullByDefault({}) LivisiBridgeConfiguration bridgeConfiguration;
+ private @NonNullByDefault({}) OAuthClientService oAuthService;
+ private String configVersion = "";
+
+ /**
+ * Constructs a new {@link LivisiBridgeHandler}.
+ *
+ * @param bridge Bridge thing to be used by this handler
+ * @param oAuthFactory Factory class to get OAuth2 service
+ * @param httpClient httpclient instance
+ */
+ public LivisiBridgeHandler(final Bridge bridge, final OAuthFactory oAuthFactory, final HttpClient httpClient) {
+ super(bridge);
+ this.oAuthFactory = oAuthFactory;
+ this.httpClient = httpClient;
+ deviceStatusListeners = new ConcurrentHashMap<>();
+ }
+
+ @Override
+ public void handleCommand(final ChannelUID channelUID, final Command command) {
+ // not needed
+ }
+
+ @Override
+ public Collection> getServices() {
+ return Collections.singleton(LivisiDeviceDiscoveryService.class);
+ }
+
+ @Override
+ public void initialize() {
+ logger.debug("Initializing LIVISI SmartHome BridgeHandler...");
+ bridgeConfiguration = getConfigAs(LivisiBridgeConfiguration.class);
+ updateStatus(ThingStatus.UNKNOWN);
+ initializeClient();
+ }
+
+ /**
+ * Initializes the services and LivisiClient.
+ */
+ private void initializeClient() {
+ String tokenURL = URLCreator.createTokenURL(bridgeConfiguration.host);
+ oAuthService = oAuthFactory.createOAuthClientService(thing.getUID().getAsString(), tokenURL, tokenURL,
+ "clientId", "clientPass", null, true);
+ client = createClient(oAuthService);
+ deviceStructMan = new DeviceStructureManager(createFullDeviceManager(client));
+ oAuthService.addAccessTokenRefreshListener(this);
+
+ getScheduler().schedule(() -> {
+ try {
+ requestAccessToken();
+
+ scheduleRestartClient(false);
+ } catch (IOException | OAuthException | OAuthResponseException e) {
+ logger.debug("Error fetching access tokens. Please check your credentials. Detail: {}", e.getMessage());
+ updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, "@text/error.connect");
+ }
+ }, 0, TimeUnit.SECONDS);
+ }
+
+ /**
+ * Initializes the client and connects to the LIVISI SmartHome service via Client API. Based on the provided
+ * configuration while constructing {@link LivisiClient}, the given oauth2 access and refresh tokens are
+ * used or - if not yet available - new tokens are fetched from the service using the provided auth code.
+ */
+ private void startClient() {
+ logger.debug("Initializing LIVISI SmartHome client...");
+ boolean isSuccessfullyRefreshed = refreshDevices();
+ if (isSuccessfullyRefreshed) {
+ Optional bridgeDeviceOptional = getBridgeDevice();
+ if (bridgeDeviceOptional.isPresent()) {
+ DeviceDTO bridgeDevice = bridgeDeviceOptional.get();
+ bridgeId = bridgeDevice.getId();
+ setBridgeProperties(bridgeDevice);
+
+ registerDeviceStatusListener(bridgeDevice.getId(), this);
+ onDeviceStateChanged(bridgeDevice); // initialize channels
+ scheduleBridgeRefreshJob(bridgeDevice);
+
+ startWebSocket(bridgeDevice);
+ } else {
+ logger.debug("Failed to get bridge device, re-scheduling startClient.");
+ scheduleRestartClient(true);
+ }
+ }
+ }
+
+ private boolean refreshDevices() {
+ try {
+ configVersion = client.refreshStatus();
+ deviceStructMan.refreshDevices();
+ return true;
+ } catch (IOException e) {
+ if (handleClientException(e)) {
+ // If exception could not be handled properly it's no use to continue so we won't continue start
+ logger.debug("Error initializing LIVISI SmartHome client.", e);
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Start the websocket connection for receiving permanent update {@link EventDTO}s from the LIVISI API.
+ */
+ private void startWebSocket(DeviceDTO bridgeDevice) {
+ try {
+ stopWebSocket();
+
+ logger.debug("Starting LIVISI SmartHome websocket.");
+ webSocket = createAndStartWebSocket(bridgeDevice);
+ updateStatus(ThingStatus.ONLINE);
+ } catch (final IOException e) {
+ logger.warn("Error starting websocket.", e);
+ handleClientException(e);
+ }
+ }
+
+ private void stopWebSocket() {
+ LivisiWebSocket webSocket = this.webSocket;
+ if (webSocket != null && webSocket.isRunning()) {
+ logger.debug("Stopping LIVISI SmartHome websocket.");
+ webSocket.stop();
+ this.webSocket = null;
+ }
+ }
+
+ @Nullable
+ LivisiWebSocket createAndStartWebSocket(DeviceDTO bridgeDevice) throws IOException {
+ final Optional accessToken = getAccessToken(client);
+ if (accessToken.isEmpty()) {
+ return null;
+ }
+
+ final String webSocketUrl = URLCreator.createEventsURL(bridgeConfiguration.host, accessToken.get(),
+ bridgeDevice.isClassicController());
+
+ logger.debug("WebSocket URL: {}...{}", webSocketUrl.substring(0, 70),
+ webSocketUrl.substring(webSocketUrl.length() - 10));
+
+ LivisiWebSocket webSocket = new LivisiWebSocket(httpClient, this, URI.create(webSocketUrl),
+ bridgeConfiguration.webSocketIdleTimeout * 1000);
+ webSocket.start();
+ return webSocket;
+ }
+
+ private static Optional getAccessToken(LivisiClient client) throws IOException {
+ return Optional.of(client.getAccessTokenResponse().getAccessToken());
+ }
+
+ @Override
+ public void onAccessTokenResponse(final AccessTokenResponse credential) {
+ scheduleRestartClient(true);
+ }
+
+ /**
+ * Schedules a re-initialization in the given future.
+ *
+ * @param delayed when it is scheduled delayed, it starts with a delay of
+ * {@link org.openhab.binding.livisismarthome.internal.LivisiBindingConstants#REINITIALIZE_DELAY_SECONDS}
+ * seconds,
+ * otherwise it starts directly
+ */
+ private synchronized void scheduleRestartClient(final boolean delayed) {
+ final ScheduledFuture> reInitJobLocal = this.reInitJob;
+ if (reInitJobLocal == null || !isAlreadyScheduled(reInitJobLocal)) {
+ long delaySeconds = 0;
+ if (delayed) {
+ delaySeconds = REINITIALIZE_DELAY_SECONDS;
+ }
+ logger.debug("Scheduling reinitialize in {} delaySeconds.", delaySeconds);
+ this.reInitJob = getScheduler().schedule(this::startClient, delaySeconds, TimeUnit.SECONDS);
+ }
+ }
+
+ /**
+ * Starts a refresh job for the bridge channels, because the SHC 1 (classic) doesn't send events
+ * for cpu, memory, disc or operation state changes.
+ * The refresh job is only executed for SHC 1 (classic) bridges, newer bridges like SHC 2 do send events.
+ */
+ private void scheduleBridgeRefreshJob(DeviceDTO bridgeDevice) {
+ if (bridgeDevice.isClassicController()) {
+ final ScheduledFuture> bridgeRefreshJobLocal = this.bridgeRefreshJob;
+ if (bridgeRefreshJobLocal == null || !isAlreadyScheduled(bridgeRefreshJobLocal)) {
+ logger.debug("Scheduling bridge refresh job with an interval of {} seconds.", BRIDGE_REFRESH_SECONDS);
+
+ this.bridgeRefreshJob = getScheduler().scheduleWithFixedDelay(() -> {
+ logger.debug("Refreshing bridge");
+
+ refreshBridgeState();
+ onDeviceStateChanged(bridgeDevice);
+ }, BRIDGE_REFRESH_SECONDS, BRIDGE_REFRESH_SECONDS, TimeUnit.SECONDS);
+ }
+ }
+ }
+
+ private void setBridgeProperties(final DeviceDTO bridgeDevice) {
+ final DeviceConfigDTO config = bridgeDevice.getConfig();
+
+ logger.debug("Setting Bridge Device Properties for Bridge of type '{}' with ID '{}'", config.getName(),
+ bridgeDevice.getId());
+ final Map properties = editProperties();
+
+ setPropertyIfPresent(Thing.PROPERTY_VENDOR, bridgeDevice.getManufacturer(), properties);
+ setPropertyIfPresent(Thing.PROPERTY_SERIAL_NUMBER, bridgeDevice.getSerialNumber(), properties);
+ setPropertyIfPresent(PROPERTY_ID, bridgeDevice.getId(), properties);
+ setPropertyIfPresent(Thing.PROPERTY_FIRMWARE_VERSION, config.getFirmwareVersion(), properties);
+ setPropertyIfPresent(Thing.PROPERTY_HARDWARE_VERSION, config.getHardwareVersion(), properties);
+ setPropertyIfPresent(PROPERTY_SOFTWARE_VERSION, config.getSoftwareVersion(), properties);
+ setPropertyIfPresent(PROPERTY_IP_ADDRESS, config.getIPAddress(), properties);
+ setPropertyIfPresent(Thing.PROPERTY_MAC_ADDRESS, config.getMACAddress(), properties);
+ if (config.getRegistrationTime() != null) {
+ properties.put(PROPERTY_REGISTRATION_TIME,
+ config.getRegistrationTime().format(DateTimeFormatter.ofLocalizedDateTime(FormatStyle.MEDIUM)));
+ }
+ setPropertyIfPresent(PROPERTY_CONFIGURATION_STATE, config.getConfigurationState(), properties);
+ setPropertyIfPresent(PROPERTY_SHC_TYPE, bridgeDevice.getType(), properties);
+ setPropertyIfPresent(PROPERTY_TIME_ZONE, config.getTimeZone(), properties);
+ setPropertyIfPresent(PROPERTY_PROTOCOL_ID, config.getProtocolId(), properties);
+ setPropertyIfPresent(PROPERTY_GEOLOCATION, config.getGeoLocation(), properties);
+ setPropertyIfPresent(PROPERTY_CURRENT_UTC_OFFSET, config.getCurrentUTCOffset(), properties);
+ setPropertyIfPresent(PROPERTY_BACKEND_CONNECTION_MONITORED, config.getBackendConnectionMonitored(), properties);
+ setPropertyIfPresent(PROPERTY_RFCOM_FAILURE_NOTIFICATION, config.getRFCommFailureNotification(), properties);
+ updateProperties(properties);
+ }
+
+ private void setPropertyIfPresent(final String key, final @Nullable Object data,
+ final Map properties) {
+ if (data != null) {
+ properties.put(key, data.toString());
+ }
+ }
+
+ @Override
+ public void dispose() {
+ logger.debug("Disposing LIVISI SmartHome bridge handler '{}'", getThing().getUID().getId());
+ unregisterDeviceStatusListener(bridgeId);
+ cancelJobs();
+ stopWebSocket();
+ client = null;
+ deviceStructMan = null;
+
+ super.dispose();
+ logger.debug("LIVISI SmartHome bridge handler shut down.");
+ }
+
+ private synchronized void cancelJobs() {
+ if (cancelJob(reInitJob)) {
+ reInitJob = null;
+ }
+ if (cancelJob(bridgeRefreshJob)) {
+ bridgeRefreshJob = null;
+ }
+ }
+
+ private static boolean cancelJob(@Nullable ScheduledFuture> job) {
+ if (job != null) {
+ job.cancel(true);
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ * Registers a {@link DeviceStatusListener}.
+ *
+ * @param deviceStatusListener listener
+ */
+ public void registerDeviceStatusListener(final String deviceId, final DeviceStatusListener deviceStatusListener) {
+ deviceStatusListeners.putIfAbsent(deviceId, deviceStatusListener);
+ }
+
+ /**
+ * Unregisters a {@link DeviceStatusListener}.
+ *
+ * @param deviceId id of the device to which the listener is registered
+ */
+ public void unregisterDeviceStatusListener(@Nullable final String deviceId) {
+ if (deviceId != null) {
+ deviceStatusListeners.remove(deviceId);
+ }
+ }
+
+ /**
+ * Loads a Collection of {@link DeviceDTO}s from the bridge and returns them.
+ *
+ * @return a Collection of {@link DeviceDTO}s
+ */
+ public Collection loadDevices() {
+ return deviceStructMan.getDeviceList();
+ }
+
+ public boolean isSHCClassic() {
+ return getBridgeDevice().filter(DeviceDTO::isClassicController).isPresent();
+ }
+
+ /**
+ * Returns the bridge {@link DeviceDTO}.
+ *
+ * @return bridge {@link DeviceDTO}
+ */
+ private Optional getBridgeDevice() {
+ return deviceStructMan.getBridgeDevice();
+ }
+
+ /**
+ * Returns the {@link DeviceDTO} with the given deviceId.
+ *
+ * @param deviceId device id
+ * @return {@link DeviceDTO} or null, if it does not exist or no {@link DeviceStructureManager} is available
+ */
+ public Optional getDeviceById(final String deviceId) {
+ return deviceStructMan.getDeviceById(deviceId);
+ }
+
+ private void refreshBridgeState() {
+ Optional bridgeOptional = getBridgeDevice();
+ if (bridgeOptional.isPresent()) {
+ try {
+ DeviceDTO bridgeDevice = bridgeOptional.get();
+
+ DeviceStateDTO deviceState = new DeviceStateDTO();
+ deviceState.setId(bridgeDevice.getId());
+ deviceState.setState(client.getDeviceStateByDeviceId(bridgeDevice.getId(), isSHCClassic()));
+ bridgeDevice.setDeviceState(deviceState);
+ } catch (IOException e) {
+ logger.debug("Exception occurred on reloading bridge", e);
+ }
+ }
+ }
+
+ /**
+ * Refreshes the {@link DeviceDTO} with the given id, by reloading the full device from the LIVISI webservice.
+ *
+ * @param deviceId device id
+ * @return the {@link DeviceDTO} or null, if it does not exist or no {@link DeviceStructureManager} is available
+ */
+ public Optional refreshDevice(final String deviceId) {
+ try {
+ return deviceStructMan.refreshDevice(deviceId, isSHCClassic());
+ } catch (IOException e) {
+ handleClientException(e);
+ }
+ return Optional.empty();
+ }
+
+ @Override
+ public void onDeviceStateChanged(final DeviceDTO bridgeDevice) {
+ synchronized (this.lock) {
+ // DEVICE STATES
+ if (bridgeDevice.hasDeviceState()) {
+ final boolean isSHCClassic = bridgeDevice.isClassicController();
+ final Double cpuUsage = bridgeDevice.getDeviceState().getState().getCpuUsage(isSHCClassic).getValue();
+ if (cpuUsage != null) {
+ logger.debug("-> CPU usage state: {}", cpuUsage);
+ updateState(CHANNEL_CPU, QuantityType.valueOf(cpuUsage, Units.PERCENT));
+ }
+ final Double diskUsage = bridgeDevice.getDeviceState().getState().getDiskUsage().getValue();
+ if (diskUsage != null) {
+ logger.debug("-> Disk usage state: {}", diskUsage);
+ updateState(CHANNEL_DISK, QuantityType.valueOf(diskUsage, Units.PERCENT));
+ }
+ final Double memoryUsage = bridgeDevice.getDeviceState().getState().getMemoryUsage(isSHCClassic)
+ .getValue();
+ if (memoryUsage != null) {
+ logger.debug("-> Memory usage state: {}", memoryUsage);
+ updateState(CHANNEL_MEMORY, QuantityType.valueOf(memoryUsage, Units.PERCENT));
+ }
+ String operationStatus = bridgeDevice.getDeviceState().getState().getOperationStatus(isSHCClassic)
+ .getValue();
+ if (operationStatus != null) {
+ logger.debug("-> Operation status: {}", operationStatus);
+ updateState(CHANNEL_OPERATION_STATUS, new StringType(operationStatus.toUpperCase()));
+ }
+ }
+ }
+ }
+
+ @Override
+ public void onDeviceStateChanged(final DeviceDTO bridgeDevice, final EventDTO event) {
+ synchronized (this.lock) {
+ if (event.isLinkedtoDevice()) {
+ final boolean isSHCClassic = bridgeDevice.isClassicController();
+ bridgeDevice.getDeviceState().getState().getOperationStatus(isSHCClassic)
+ .setValue(event.getProperties().getOperationStatus(isSHCClassic));
+ bridgeDevice.getDeviceState().getState().getCpuUsage(isSHCClassic)
+ .setValue(event.getProperties().getCpuUsage(isSHCClassic));
+ bridgeDevice.getDeviceState().getState().getDiskUsage().setValue(event.getProperties().getDiskUsage());
+ bridgeDevice.getDeviceState().getState().getMemoryUsage(isSHCClassic)
+ .setValue(event.getProperties().getMemoryUsage(isSHCClassic));
+ onDeviceStateChanged(bridgeDevice);
+ }
+ }
+ }
+
+ @Override
+ public void onEvent(final String msg) {
+ logger.trace("onEvent called. Msg: {}", msg);
+
+ try {
+ final Optional eventOptional = parseEvent(msg);
+ if (eventOptional.isPresent()) {
+ EventDTO event = eventOptional.get();
+ switch (event.getType()) {
+ case BaseEventDTO.TYPE_STATE_CHANGED:
+ case BaseEventDTO.TYPE_BUTTON_PRESSED:
+ handleStateChangedEvent(event);
+ break;
+ case BaseEventDTO.TYPE_DISCONNECT:
+ logger.debug("Websocket disconnected.");
+ scheduleRestartClient(true);
+ break;
+ case BaseEventDTO.TYPE_CONFIGURATION_CHANGED:
+ handleConfigurationChangedEvent(event);
+ break;
+ case BaseEventDTO.TYPE_CONTROLLER_CONNECTIVITY_CHANGED:
+ handleControllerConnectivityChangedEvent(event);
+ break;
+ case BaseEventDTO.TYPE_NEW_MESSAGE_RECEIVED:
+ case BaseEventDTO.TYPE_MESSAGE_CREATED:
+ final Optional messageEvent = gson.fromJson(msg, MessageEventDTO.class);
+ if (messageEvent.isPresent()) {
+ handleNewMessageReceivedEvent(Objects.requireNonNull(messageEvent.get()));
+ }
+ break;
+ case BaseEventDTO.TYPE_MESSAGE_DELETED:
+ handleMessageDeletedEvent(event);
+ break;
+ default:
+ logger.debug("Unsupported event type {}.", event.getType());
+ break;
+ }
+ }
+ } catch (IOException | RuntimeException e) {
+ logger.debug("Error with Event: {}", e.getMessage(), e);
+ handleClientException(e);
+ }
+ }
+
+ @Override
+ public void onError(final Throwable cause) {
+ if (cause instanceof Exception) {
+ handleClientException((Exception) cause);
+ }
+ }
+
+ /**
+ * Handles the event that occurs, when the state of a device (like reachability) or a capability (like a temperature
+ * value) has changed.
+ *
+ * @param event event
+ */
+ private void handleStateChangedEvent(final EventDTO event) throws IOException {
+
+ // CAPABILITY
+ if (event.isLinkedtoCapability()) {
+ logger.trace("Event is linked to capability");
+ final Optional device = deviceStructMan.getDeviceByCapabilityId(event.getSourceId());
+ notifyDeviceStatusListeners(device, event);
+
+ // DEVICE
+ } else if (event.isLinkedtoDevice()) {
+ logger.trace("Event is linked to device");
+ final String sourceId = event.getSourceId();
+
+ final Optional bridgeDevice = deviceStructMan.getBridgeDevice();
+ final Optional device;
+ if (bridgeDevice.isPresent() && !sourceId.equals(bridgeDevice.get().getId())) {
+ device = deviceStructMan.refreshDevice(sourceId, isSHCClassic());
+ } else {
+ device = deviceStructMan.getDeviceById(sourceId);
+ }
+ notifyDeviceStatusListeners(device, event);
+
+ } else {
+ logger.debug("link type {} not supported (yet?)", event.getSourceLinkType());
+ }
+ }
+
+ /**
+ * Handles the event that occurs, when the connectivity of the bridge has changed.
+ *
+ * @param event event
+ */
+ private void handleControllerConnectivityChangedEvent(final EventDTO event) throws IOException {
+
+ final Boolean connected = event.getIsConnected();
+ if (connected != null) {
+ final ThingStatus thingStatus;
+ if (connected) {
+ deviceStructMan.refreshDevices();
+ thingStatus = ThingStatus.ONLINE;
+ updateStatus(thingStatus);
+ } else {
+ thingStatus = ThingStatus.OFFLINE;
+ }
+ logger.debug("SmartHome Controller connectivity changed to {} by {} event.", thingStatus,
+ BaseEventDTO.TYPE_CONTROLLER_CONNECTIVITY_CHANGED);
+ } else {
+ logger.debug("isConnected property missing in {} event (returned null)!",
+ BaseEventDTO.TYPE_CONTROLLER_CONNECTIVITY_CHANGED);
+ }
+ }
+
+ /**
+ * Handles the event that occurs, when a new message was received. Currently only handles low battery messages.
+ *
+ * @param event event
+ */
+ private void handleNewMessageReceivedEvent(final MessageEventDTO event) throws IOException {
+
+ final MessageDTO message = event.getMessage();
+ if (logger.isTraceEnabled()) {
+ logger.trace("Message: {}", gson.toJson(message));
+ logger.trace("Messagetype: {}", message.getType());
+ }
+ if (MessageDTO.TYPE_DEVICE_LOW_BATTERY.equals(message.getType()) && message.getDevices() != null) {
+ for (final String link : message.getDevices()) {
+ final Optional device = deviceStructMan.refreshDevice(LinkDTO.getId(link), isSHCClassic());
+ notifyDeviceStatusListener(event.getSourceId(), device);
+ }
+ } else {
+ logger.debug("Message received event not yet implemented for Messagetype {}.", message.getType());
+ }
+ }
+
+ /**
+ * Handle the event that occurs, when a message was deleted. In case of a low battery message this means, that the
+ * device is back to normal. Currently, only messages linked to devices are handled by refreshing the device data
+ * and informing the {@link LivisiDeviceHandler} about the changed device.
+ *
+ * @param event event
+ */
+ private void handleMessageDeletedEvent(final EventDTO event) throws IOException {
+
+ final String messageId = event.getData().getId();
+ logger.debug("handleMessageDeletedEvent with messageId '{}'", messageId);
+
+ Optional device = deviceStructMan.getDeviceWithMessageId(messageId);
+ if (device.isPresent()) {
+ String id = device.get().getId();
+ Optional deviceRefreshed = deviceStructMan.refreshDevice(id, isSHCClassic());
+ notifyDeviceStatusListener(event.getSourceId(), deviceRefreshed);
+ } else {
+ logger.debug("No device found with message id {}.", messageId);
+ }
+ }
+
+ private void handleConfigurationChangedEvent(EventDTO event) {
+ if (configVersion.equals(event.getConfigurationVersion().toString())) {
+ logger.debug("Ignored configuration changed event with version '{}' as current version is '{}' the same.",
+ event.getConfigurationVersion(), configVersion);
+ } else {
+ logger.info("Configuration changed from version {} to {}. Restarting LIVISI SmartHome binding...",
+ configVersion, event.getConfigurationVersion());
+ scheduleRestartClient(false);
+ }
+ }
+
+ private void notifyDeviceStatusListener(String deviceId, Optional device) {
+ if (device.isPresent()) {
+ DeviceStatusListener deviceStatusListener = deviceStatusListeners.get(device.get().getId());
+ if (deviceStatusListener != null) {
+ deviceStatusListener.onDeviceStateChanged(device.get());
+ } else {
+ logger.debug("No device status listener registered for device {}.", deviceId);
+ }
+ } else {
+ logger.debug("Unknown/unsupported device {}.", deviceId);
+ }
+ }
+
+ private void notifyDeviceStatusListeners(Optional device, EventDTO event) {
+ String sourceId = event.getSourceId();
+ if (device.isPresent()) {
+ DeviceStatusListener deviceStatusListener = deviceStatusListeners.get(device.get().getId());
+ if (deviceStatusListener != null) {
+ deviceStatusListener.onDeviceStateChanged(device.get(), event);
+ } else {
+ logger.debug("No device status listener registered for device / capability {}.", sourceId);
+ }
+ } else {
+ logger.debug("Unknown/unsupported device / capability {}.", sourceId);
+ }
+ }
+
+ @Override
+ public void connectionClosed() {
+ scheduleRestartClient(true);
+ }
+
+ /**
+ * Sends the command to switch the {@link DeviceDTO} with the given id to the new state. Is called by the
+ * {@link LivisiDeviceHandler} for switch devices like the VariableActuator, PSS, PSSO or ISS2.
+ *
+ * @param deviceId device id
+ * @param state state (boolean)
+ */
+ public void commandSwitchDevice(final String deviceId, final boolean state) {
+ // VariableActuator
+ Optional device = deviceStructMan.getDeviceById(deviceId);
+ if (device.isPresent()) {
+ final String deviceType = device.get().getType();
+ if (DEVICE_VARIABLE_ACTUATOR.equals(deviceType)) {
+ executeCommand(deviceId, CapabilityDTO.TYPE_VARIABLEACTUATOR,
+ (capabilityId) -> client.setVariableActuatorState(capabilityId, state));
+ // PSS / PSSO / ISS2 / BT-PSS
+ } else if (DEVICE_PSS.equals(deviceType) || DEVICE_PSSO.equals(deviceType) || DEVICE_ISS2.equals(deviceType)
+ || DEVICE_BT_PSS.equals((deviceType))) {
+ executeCommand(deviceId, CapabilityDTO.TYPE_SWITCHACTUATOR,
+ (capabilityId) -> client.setSwitchActuatorState(capabilityId, state));
+ }
+ } else {
+ logger.debug("No device with id {} could get found!", deviceId);
+ }
+ }
+
+ /**
+ * Sends the command to update the point temperature of the {@link DeviceDTO} with the given deviceId. Is called by
+ * the
+ * {@link LivisiDeviceHandler} for thermostat {@link DeviceDTO}s like RST or WRT.
+ *
+ * @param deviceId device id
+ * @param pointTemperature point temperature
+ */
+ public void commandUpdatePointTemperature(final String deviceId, final double pointTemperature) {
+ executeCommand(deviceId, CapabilityDTO.TYPE_THERMOSTATACTUATOR,
+ (capabilityId) -> client.setPointTemperatureState(capabilityId, pointTemperature));
+ }
+
+ /**
+ * Sends the command to turn the alarm of the {@link DeviceDTO} with the given id on or off. Is called by the
+ * {@link LivisiDeviceHandler} for smoke detector {@link DeviceDTO}s like WSD or WSD2.
+ *
+ * @param deviceId device id
+ * @param alarmState alarm state (boolean)
+ */
+ public void commandSwitchAlarm(final String deviceId, final boolean alarmState) {
+ executeCommand(deviceId, CapabilityDTO.TYPE_ALARMACTUATOR,
+ (capabilityId) -> client.setAlarmActuatorState(capabilityId, alarmState));
+ }
+
+ /**
+ * Sends the command to set the operation mode of the {@link DeviceDTO} with the given deviceId to auto (or manual,
+ * if
+ * false). Is called by the {@link LivisiDeviceHandler} for thermostat {@link DeviceDTO}s like RST.
+ *
+ * @param deviceId device id
+ * @param isAutoMode true activates the automatic mode, false the manual mode.
+ */
+ public void commandSetOperationMode(final String deviceId, final boolean isAutoMode) {
+ executeCommand(deviceId, CapabilityDTO.TYPE_THERMOSTATACTUATOR,
+ (capabilityId) -> client.setOperationMode(capabilityId, isAutoMode));
+ }
+
+ /**
+ * Sends the command to set the dimm level of the {@link DeviceDTO} with the given id. Is called by the
+ * {@link LivisiDeviceHandler} for {@link DeviceDTO}s like ISD2 or PSD.
+ *
+ * @param deviceId device id
+ * @param dimLevel dim level
+ */
+ public void commandSetDimLevel(final String deviceId, final int dimLevel) {
+ executeCommand(deviceId, CapabilityDTO.TYPE_DIMMERACTUATOR,
+ (capabilityId) -> client.setDimmerActuatorState(capabilityId, dimLevel));
+ }
+
+ /**
+ * Sends the command to set the rollershutter level of the {@link DeviceDTO} with the given id. Is called by the
+ * {@link LivisiDeviceHandler} for {@link DeviceDTO}s like ISR2.
+ *
+ * @param deviceId device id
+ * @param rollerShutterLevel roller shutter level
+ */
+ public void commandSetRollerShutterLevel(final String deviceId, final int rollerShutterLevel) {
+ executeCommand(deviceId, CapabilityDTO.TYPE_ROLLERSHUTTERACTUATOR,
+ (capabilityId) -> client.setRollerShutterActuatorState(capabilityId, rollerShutterLevel));
+ }
+
+ /**
+ * Sends the command to start or stop moving the rollershutter (ISR2) in a specified direction
+ *
+ * @param deviceId device id
+ * @param action action
+ */
+ public void commandSetRollerShutterStop(final String deviceId, final ShutterActionType action) {
+ executeCommand(deviceId, CapabilityDTO.TYPE_ROLLERSHUTTERACTUATOR,
+ (capabilityId) -> client.setRollerShutterAction(capabilityId, action));
+ }
+
+ private void executeCommand(final String deviceId, final String capabilityType,
+ final CommandExecutor commandExecutor) {
+ try {
+ final Optional capabilityId = deviceStructMan.getCapabilityId(deviceId, capabilityType);
+ if (capabilityId.isPresent()) {
+ commandExecutor.executeCommand(capabilityId.get());
+ }
+ } catch (IOException e) {
+ handleClientException(e);
+ }
+ }
+
+ ScheduledExecutorService getScheduler() {
+ return scheduler;
+ }
+
+ FullDeviceManager createFullDeviceManager(LivisiClient client) {
+ return new FullDeviceManager(client);
+ }
+
+ LivisiClient createClient(final OAuthClientService oAuthService) {
+ return new LivisiClient(bridgeConfiguration, oAuthService, new URLConnectionFactory());
+ }
+
+ /**
+ * Handles all Exceptions of the client communication. For minor "errors" like an already existing session, it
+ * returns true to inform the binding to continue running. In other cases it may e.g. schedule a reinitialization of
+ * the binding.
+ *
+ * @param e the Exception
+ * @return boolean true, if binding should continue.
+ */
+ private boolean handleClientException(final Exception e) {
+ boolean isReinitialize = true;
+ if (e instanceof SessionExistsException) {
+ logger.debug("Session already exists. Continuing...");
+ isReinitialize = false;
+ } else if (e instanceof InvalidActionTriggeredException) {
+ logger.debug("Error triggering action: {}", e.getMessage());
+ isReinitialize = false;
+ } else if (e instanceof RemoteAccessNotAllowedException) {
+ // Remote access not allowed (usually by IP address change)
+ logger.debug("Remote access not allowed. Dropping access token and reinitializing binding...");
+ refreshAccessToken();
+ } else if (e instanceof ControllerOfflineException) {
+ logger.debug("LIVISI SmartHome Controller is offline.");
+ updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, e.getMessage());
+ } else if (e instanceof AuthenticationException) {
+ logger.debug("OAuthenticaton error, refreshing tokens: {}", e.getMessage());
+ refreshAccessToken();
+ updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, e.getMessage());
+ } else if (e instanceof ApiException) {
+ logger.warn("Unexpected API error: {}", e.getMessage());
+ logger.debug("Unexpected API error", e);
+ updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, e.getMessage());
+ } else if (e instanceof TimeoutException) {
+ logger.debug("WebSocket timeout: {}", e.getMessage());
+ } else if (e instanceof SocketTimeoutException) {
+ logger.debug("Socket timeout: {}", e.getMessage());
+ updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, e.getMessage());
+ } else if (e instanceof IOException) {
+ logger.debug("IOException occurred", e);
+ updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, e.getMessage());
+ } else if (e instanceof InterruptedException) {
+ isReinitialize = false;
+ Thread.currentThread().interrupt();
+ } else if (e instanceof ExecutionException) {
+ logger.debug("ExecutionException occurred", e);
+ updateStatus(ThingStatus.OFFLINE);
+ } else {
+ logger.debug("Unknown exception", e);
+ updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.NONE, e.getMessage());
+ }
+ if (isReinitialize) {
+ scheduleRestartClient(true);
+ }
+ return isReinitialize;
+ }
+
+ private void refreshAccessToken() {
+ try {
+ requestAccessToken();
+ } catch (IOException | OAuthException | OAuthResponseException e) {
+ logger.debug("Could not refresh tokens", e);
+ }
+ }
+
+ private void requestAccessToken() throws OAuthException, IOException, OAuthResponseException {
+ oAuthService.getAccessTokenByResourceOwnerPasswordCredentials(LivisiBindingConstants.USERNAME,
+ bridgeConfiguration.password, null);
+ }
+
+ private Optional parseEvent(final String msg) {
+ final Optional baseEventOptional = gson.fromJson(msg, BaseEventDTO.class);
+ if (baseEventOptional.isPresent()) {
+ BaseEventDTO baseEvent = baseEventOptional.get();
+ logger.debug("Event no {} found. Type: {}", baseEvent.getSequenceNumber(), baseEvent.getType());
+ if (BaseEventDTO.SUPPORTED_EVENT_TYPES.contains(baseEvent.getType())) {
+ return gson.fromJson(msg, EventDTO.class);
+ }
+ logger.debug("Event type {} not supported. Skipping...", baseEvent.getType());
+ }
+ return Optional.empty();
+ }
+
+ /**
+ * Checks if the job is already (re-)scheduled.
+ *
+ * @param job job to check
+ * @return true, when the job is already (re-)scheduled, otherwise false
+ */
+ private static boolean isAlreadyScheduled(ScheduledFuture> job) {
+ return job.getDelay(TimeUnit.SECONDS) > 0;
+ }
+
+ @FunctionalInterface
+ private interface CommandExecutor {
+
+ void executeCommand(String capabilityId) throws IOException;
+ }
+}
diff --git a/bundles/org.openhab.binding.livisismarthome/src/main/java/org/openhab/binding/livisismarthome/internal/handler/LivisiDeviceHandler.java b/bundles/org.openhab.binding.livisismarthome/src/main/java/org/openhab/binding/livisismarthome/internal/handler/LivisiDeviceHandler.java
new file mode 100644
index 00000000000..f5a6bb5c3db
--- /dev/null
+++ b/bundles/org.openhab.binding.livisismarthome/src/main/java/org/openhab/binding/livisismarthome/internal/handler/LivisiDeviceHandler.java
@@ -0,0 +1,1027 @@
+/**
+ * Copyright (c) 2010-2022 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.livisismarthome.internal.handler;
+
+import static org.openhab.binding.livisismarthome.internal.LivisiBindingConstants.*;
+
+import java.time.format.DateTimeFormatter;
+import java.time.format.FormatStyle;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.Optional;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.eclipse.jdt.annotation.Nullable;
+import org.openhab.binding.livisismarthome.internal.client.api.entity.action.ShutterActionType;
+import org.openhab.binding.livisismarthome.internal.client.api.entity.capability.CapabilityDTO;
+import org.openhab.binding.livisismarthome.internal.client.api.entity.capability.CapabilityStateDTO;
+import org.openhab.binding.livisismarthome.internal.client.api.entity.device.DeviceDTO;
+import org.openhab.binding.livisismarthome.internal.client.api.entity.event.EventDTO;
+import org.openhab.binding.livisismarthome.internal.client.api.entity.event.EventPropertiesDTO;
+import org.openhab.binding.livisismarthome.internal.listener.DeviceStatusListener;
+import org.openhab.core.library.types.DecimalType;
+import org.openhab.core.library.types.OnOffType;
+import org.openhab.core.library.types.OpenClosedType;
+import org.openhab.core.library.types.PercentType;
+import org.openhab.core.library.types.QuantityType;
+import org.openhab.core.library.types.StopMoveType;
+import org.openhab.core.library.types.StringType;
+import org.openhab.core.library.types.UpDownType;
+import org.openhab.core.library.unit.SIUnits;
+import org.openhab.core.library.unit.Units;
+import org.openhab.core.thing.Bridge;
+import org.openhab.core.thing.Channel;
+import org.openhab.core.thing.ChannelUID;
+import org.openhab.core.thing.CommonTriggerEvents;
+import org.openhab.core.thing.Thing;
+import org.openhab.core.thing.ThingStatus;
+import org.openhab.core.thing.ThingStatusDetail;
+import org.openhab.core.thing.ThingStatusInfo;
+import org.openhab.core.thing.binding.BaseThingHandler;
+import org.openhab.core.thing.binding.ThingHandler;
+import org.openhab.core.types.Command;
+import org.openhab.core.types.RefreshType;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * The {@link LivisiDeviceHandler} is responsible for handling the {@link DeviceDTO}s and their commands, which are
+ * sent to one of the channels.
+ *
+ * @author Oliver Kuhl - Initial contribution
+ * @author Sven Strohschein - Renamed from Innogy to Livisi
+ */
+@NonNullByDefault
+public class LivisiDeviceHandler extends BaseThingHandler implements DeviceStatusListener {
+
+ private static final int MIN_TEMPERATURE_CELSIUS = 6;
+ private static final int MAX_TEMPERATURE_CELSIUS = 30;
+ private static final String LONG_PRESS = "LongPress";
+ private static final String SHORT_PRESS = "ShortPress";
+
+ private final Logger logger = LoggerFactory.getLogger(LivisiDeviceHandler.class);
+ private final Object lock = new Object();
+
+ private String deviceId = "";
+ private @Nullable LivisiBridgeHandler bridgeHandler;
+
+ /**
+ * Constructs a new {@link LivisiDeviceHandler} for the given {@link Thing}.
+ *
+ * @param thing device thing
+ */
+ public LivisiDeviceHandler(final Thing thing) {
+ super(thing);
+ }
+
+ @Override
+ public void handleCommand(final ChannelUID channelUID, final Command command) {
+ logger.debug("handleCommand called for channel '{}' of type '{}' with command '{}'", channelUID,
+ getThing().getThingTypeUID().getId(), command);
+
+ if (ThingStatus.ONLINE.equals(getThing().getStatus())) {
+ final Optional bridgeHandlerOptional = getBridgeHandler();
+ if (bridgeHandlerOptional.isPresent()) {
+ LivisiBridgeHandler bridgeHandler = bridgeHandlerOptional.get();
+ if (command instanceof RefreshType) {
+ final Optional device = bridgeHandler.getDeviceById(deviceId);
+ device.ifPresent(this::onDeviceStateChanged);
+ } else {
+ executeCommand(channelUID, command, bridgeHandler);
+ }
+ } else {
+ logger.warn("BridgeHandler not found. Cannot handle command without bridge.");
+ }
+ } else {
+ logger.debug("Cannot handle command - thing is not online. Command ignored.");
+ }
+ }
+
+ private void executeCommand(ChannelUID channelUID, Command command, LivisiBridgeHandler bridgeHandler) {
+ if (CHANNEL_SWITCH.equals(channelUID.getId())) {
+ commandSwitchDevice(command, bridgeHandler);
+ } else if (CHANNEL_DIMMER.equals(channelUID.getId())) {
+ commandSetDimLevel(command, bridgeHandler);
+ } else if (CHANNEL_ROLLERSHUTTER.equals(channelUID.getId())) {
+ commandRollerShutter(command, bridgeHandler);
+ } else if (CHANNEL_TARGET_TEMPERATURE.equals(channelUID.getId())) {
+ commandUpdatePointTemperature(command, bridgeHandler);
+ } else if (CHANNEL_OPERATION_MODE.equals(channelUID.getId())) {
+ commandSetOperationMode(command, bridgeHandler);
+ } else if (CHANNEL_ALARM.equals(channelUID.getId())) {
+ commandSwitchAlarm(command, bridgeHandler);
+ } else {
+ logger.debug("UNSUPPORTED channel {} for device {}.", channelUID.getId(), deviceId);
+ }
+ }
+
+ private void commandSwitchDevice(Command command, LivisiBridgeHandler bridgeHandler) {
+ if (command instanceof OnOffType) {
+ bridgeHandler.commandSwitchDevice(deviceId, OnOffType.ON.equals(command));
+ }
+ }
+
+ private void commandSetDimLevel(Command command, LivisiBridgeHandler bridgeHandler) {
+ if (command instanceof DecimalType) {
+ final DecimalType dimLevel = (DecimalType) command;
+ bridgeHandler.commandSetDimLevel(deviceId, dimLevel.intValue());
+ } else if (command instanceof OnOffType) {
+ if (OnOffType.ON.equals(command)) {
+ bridgeHandler.commandSetDimLevel(deviceId, 100);
+ } else {
+ bridgeHandler.commandSetDimLevel(deviceId, 0);
+ }
+ }
+ }
+
+ private void commandRollerShutter(Command command, LivisiBridgeHandler bridgeHandler) {
+ if (command instanceof DecimalType) {
+ final DecimalType rollerShutterLevel = (DecimalType) command;
+ bridgeHandler.commandSetRollerShutterLevel(deviceId,
+ invertRollerShutterValueIfConfigured(rollerShutterLevel.intValue()));
+ } else if (command instanceof OnOffType) {
+ if (OnOffType.ON.equals(command)) {
+ bridgeHandler.commandSetRollerShutterStop(deviceId, ShutterActionType.DOWN);
+ } else {
+ bridgeHandler.commandSetRollerShutterStop(deviceId, ShutterActionType.UP);
+ }
+ } else if (command instanceof UpDownType) {
+ if (UpDownType.DOWN.equals(command)) {
+ bridgeHandler.commandSetRollerShutterStop(deviceId, ShutterActionType.DOWN);
+ } else {
+ bridgeHandler.commandSetRollerShutterStop(deviceId, ShutterActionType.UP);
+ }
+ } else if (command instanceof StopMoveType) {
+ if (StopMoveType.STOP.equals(command)) {
+ bridgeHandler.commandSetRollerShutterStop(deviceId, ShutterActionType.STOP);
+ }
+ }
+ }
+
+ private void commandUpdatePointTemperature(Command command, LivisiBridgeHandler bridgeHandler) {
+ if (command instanceof QuantityType) {
+ final QuantityType> pointTemperatureCommand = ((QuantityType>) command).toUnit(SIUnits.CELSIUS);
+ if (pointTemperatureCommand != null) {
+ commandUpdatePointTemperature(pointTemperatureCommand.doubleValue(), bridgeHandler);
+ }
+ } else if (command instanceof DecimalType) {
+ commandUpdatePointTemperature(((DecimalType) command).doubleValue(), bridgeHandler);
+ }
+ }
+
+ private void commandUpdatePointTemperature(double pointTemperature, LivisiBridgeHandler bridgeHandler) {
+ if (pointTemperature < MIN_TEMPERATURE_CELSIUS) {
+ pointTemperature = MIN_TEMPERATURE_CELSIUS;
+ logger.debug(
+ "pointTemperature set to value {} (instead of value '{}'), because it is the minimal possible value!",
+ MIN_TEMPERATURE_CELSIUS, pointTemperature);
+ } else if (pointTemperature > MAX_TEMPERATURE_CELSIUS) {
+ pointTemperature = MAX_TEMPERATURE_CELSIUS;
+ logger.debug(
+ "pointTemperature set to value {} (instead of value '{}'), because it is the maximal possible value!",
+ MAX_TEMPERATURE_CELSIUS, pointTemperature);
+ }
+ bridgeHandler.commandUpdatePointTemperature(deviceId, pointTemperature);
+ }
+
+ private void commandSetOperationMode(Command command, LivisiBridgeHandler bridgeHandler) {
+ if (command instanceof StringType) {
+ final String autoModeCommand = command.toString();
+
+ if (CapabilityStateDTO.STATE_VALUE_OPERATION_MODE_AUTO.equals(autoModeCommand)) {
+ bridgeHandler.commandSetOperationMode(deviceId, true);
+ } else if (CapabilityStateDTO.STATE_VALUE_OPERATION_MODE_MANUAL.equals(autoModeCommand)) {
+ bridgeHandler.commandSetOperationMode(deviceId, false);
+ } else {
+ logger.warn("Could not set operationMode. Invalid value '{}'! Only '{}' or '{}' allowed.",
+ autoModeCommand, CapabilityStateDTO.STATE_VALUE_OPERATION_MODE_AUTO,
+ CapabilityStateDTO.STATE_VALUE_OPERATION_MODE_MANUAL);
+ }
+ }
+ }
+
+ private void commandSwitchAlarm(Command command, LivisiBridgeHandler bridgeHandler) {
+ if (command instanceof OnOffType) {
+ bridgeHandler.commandSwitchAlarm(deviceId, OnOffType.ON.equals(command));
+ }
+ }
+
+ @Override
+ public void initialize() {
+ logger.debug("Initializing LIVISI SmartHome device handler.");
+ initializeThing(isBridgeOnline());
+ }
+
+ @Override
+ public void dispose() {
+ unregisterListeners(bridgeHandler, deviceId);
+ }
+
+ private static void unregisterListeners(@Nullable LivisiBridgeHandler bridgeHandler, String deviceId) {
+ if (bridgeHandler != null) {
+ bridgeHandler.unregisterDeviceStatusListener(deviceId);
+ }
+ }
+
+ @Override
+ public void bridgeStatusChanged(final ThingStatusInfo bridgeStatusInfo) {
+ logger.debug("bridgeStatusChanged {}", bridgeStatusInfo);
+ initializeThing(ThingStatus.ONLINE == bridgeStatusInfo.getStatus());
+ }
+
+ /**
+ * Initializes the {@link Thing} corresponding to the given status of the bridge.
+ *
+ * @param isBridgeOnline true if the bridge thing is online, otherwise false
+ */
+ private void initializeThing(final boolean isBridgeOnline) {
+ logger.debug("initializeThing thing {} bridge online status: {}", getThing().getUID(), isBridgeOnline);
+ final String configDeviceId = (String) getConfig().get(PROPERTY_ID);
+ if (configDeviceId != null) {
+ deviceId = configDeviceId;
+
+ Optional bridgeHandler = registerAtBridgeHandler();
+ if (bridgeHandler.isPresent()) {
+ if (isBridgeOnline) {
+ initializeProperties();
+
+ Optional deviceOptional = getDevice();
+ if (deviceOptional.isPresent()) {
+ DeviceDTO device = deviceOptional.get();
+ if (device.isReachable() != null && !device.isReachable()) {
+ updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
+ "@text/error.notReachable");
+ } else {
+ updateStatus(ThingStatus.ONLINE);
+ }
+ } else {
+ updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.GONE, "@text/error.deviceNotFound");
+ }
+ } else {
+ updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.BRIDGE_OFFLINE);
+ }
+ } else {
+ updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
+ "@text/error.bridgeHandlerMissing");
+ }
+ } else {
+ updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, "@text/error.deviceIdUnknown");
+ }
+ }
+
+ /**
+ * Initializes all properties of the {@link DeviceDTO}, like vendor, serialnumber etc.
+ */
+ private void initializeProperties() {
+ synchronized (this.lock) {
+ final Optional deviceOptional = getDevice();
+ if (deviceOptional.isPresent()) {
+ DeviceDTO device = deviceOptional.get();
+
+ final Map properties = editProperties();
+ properties.put(PROPERTY_ID, device.getId());
+ properties.put(PROPERTY_PROTOCOL_ID, device.getConfig().getProtocolId());
+ if (device.hasSerialNumber()) {
+ properties.put(Thing.PROPERTY_SERIAL_NUMBER, device.getSerialNumber());
+ }
+ properties.put(Thing.PROPERTY_VENDOR, device.getManufacturer());
+ properties.put(PROPERTY_VERSION, device.getVersion());
+ if (device.hasLocation()) {
+ properties.put(PROPERTY_LOCATION, device.getLocation().getName());
+ }
+ if (device.isBatteryPowered()) {
+ properties.put(PROPERTY_BATTERY_POWERED, "yes");
+ } else {
+ properties.put(PROPERTY_BATTERY_POWERED, "no");
+ }
+ if (device.isController()) {
+ properties.put(PROPERTY_DEVICE_TYPE, "Controller");
+ } else if (device.isVirtualDevice()) {
+ properties.put(PROPERTY_DEVICE_TYPE, "Virtual");
+ } else if (device.isRadioDevice()) {
+ properties.put(PROPERTY_DEVICE_TYPE, "Radio");
+ }
+
+ // Thermostat
+ if (DEVICE_RST.equals(device.getType()) || DEVICE_RST2.equals(device.getType())
+ || DEVICE_WRT.equals(device.getType())) {
+ properties.put(PROPERTY_DISPLAY_CURRENT_TEMPERATURE,
+ device.getConfig().getDisplayCurrentTemperature());
+ }
+
+ // Meter
+ if (DEVICE_ANALOG_METER.equals(device.getType()) || DEVICE_GENERATION_METER.equals(device.getType())
+ || DEVICE_SMART_METER.equals(device.getType())
+ || DEVICE_TWO_WAY_METER.equals(device.getType())) {
+ properties.put(PROPERTY_METER_ID, device.getConfig().getMeterId());
+ properties.put(PROPERTY_METER_FIRMWARE_VERSION, device.getConfig().getMeterFirmwareVersion());
+ }
+
+ if (device.getConfig().getTimeOfAcceptance() != null) {
+ properties.put(PROPERTY_TIME_OF_ACCEPTANCE, device.getConfig().getTimeOfAcceptance()
+ .format(DateTimeFormatter.ofLocalizedDateTime(FormatStyle.MEDIUM)));
+ }
+ if (device.getConfig().getTimeOfDiscovery() != null) {
+ properties.put(PROPERTY_TIME_OF_DISCOVERY, device.getConfig().getTimeOfDiscovery()
+ .format(DateTimeFormatter.ofLocalizedDateTime(FormatStyle.MEDIUM)));
+ }
+
+ updateProperties(properties);
+
+ onDeviceStateChanged(device);
+ } else {
+ logger.debug("initializeProperties: The device with id {} isn't found", deviceId);
+ }
+ }
+ }
+
+ @Override
+ public void onDeviceStateChanged(final DeviceDTO device) {
+ synchronized (this.lock) {
+ updateChannels(device);
+ }
+ }
+
+ @Override
+ public void onDeviceStateChanged(final DeviceDTO device, final EventDTO event) {
+ synchronized (this.lock) {
+ if (event.isLinkedtoCapability()) {
+ final String linkedCapabilityId = event.getSourceId();
+
+ CapabilityDTO capability = device.getCapabilityMap().get(linkedCapabilityId);
+ if (capability != null) {
+ logger.trace("Loaded Capability {}, {} with id {}, device {} from device id {}",
+ capability.getType(), capability.getName(), capability.getId(), capability.getDeviceLink(),
+ device.getId());
+
+ if (capability.hasState()) {
+ boolean deviceChanged = updateDevice(event, capability);
+ if (deviceChanged) {
+ updateChannels(device);
+ }
+ } else {
+ logger.debug("Capability {} has no state (yet?) - refreshing device.", capability.getName());
+
+ Optional deviceOptional = refreshDevice(linkedCapabilityId);
+ deviceOptional.ifPresent(this::updateChannels);
+ }
+ }
+ } else if (event.isLinkedtoDevice()) {
+ if (device.hasDeviceState()) {
+ updateChannels(device);
+ } else {
+ logger.debug("Device {}/{} has no state.", device.getConfig().getName(), device.getId());
+ }
+ }
+ }
+ }
+
+ private Optional refreshDevice(String linkedCapabilityId) {
+ Optional bridgeHandler = registerAtBridgeHandler();
+ Optional deviceOptional = bridgeHandler.flatMap(bh -> bh.refreshDevice(deviceId));
+ if (deviceOptional.isPresent()) {
+ DeviceDTO device = deviceOptional.get();
+ CapabilityDTO capability = device.getCapabilityMap().get(linkedCapabilityId);
+ if (capability != null && capability.hasState()) {
+ return Optional.of(device);
+ }
+ }
+ return Optional.empty();
+ }
+
+ private boolean updateDevice(EventDTO event, CapabilityDTO capability) {
+ CapabilityStateDTO capabilityState = capability.getCapabilityState();
+
+ // VariableActuator
+ if (capability.isTypeVariableActuator()) {
+ capabilityState.setVariableActuatorState(event.getProperties().getValue());
+
+ // SwitchActuator
+ } else if (capability.isTypeSwitchActuator()) {
+ capabilityState.setSwitchActuatorState(event.getProperties().getOnState());
+
+ // DimmerActuator
+ } else if (capability.isTypeDimmerActuator()) {
+ capabilityState.setDimmerActuatorState(event.getProperties().getDimLevel());
+
+ // RollerShutterActuator
+ } else if (capability.isTypeRollerShutterActuator()) {
+ capabilityState.setRollerShutterActuatorState(event.getProperties().getShutterLevel());
+
+ // TemperatureSensor
+ } else if (capability.isTypeTemperatureSensor()) {
+ // when values are changed, they come with separate events
+ // values should only updated when they are not null
+ final Double temperature = event.getProperties().getTemperature();
+ final Boolean frostWarning = event.getProperties().getFrostWarning();
+ if (temperature != null) {
+ capabilityState.setTemperatureSensorTemperatureState(temperature);
+ }
+ if (frostWarning != null) {
+ capabilityState.setTemperatureSensorFrostWarningState(frostWarning);
+ }
+
+ // ThermostatActuator
+ } else if (capability.isTypeThermostatActuator()) {
+ // when values are changed, they come with separate events
+ // values should only updated when they are not null
+
+ final Double pointTemperature = event.getProperties().getPointTemperature();
+ final String operationMode = event.getProperties().getOperationMode();
+ final Boolean windowReductionActive = event.getProperties().getWindowReductionActive();
+
+ if (pointTemperature != null) {
+ capabilityState.setThermostatActuatorPointTemperatureState(pointTemperature);
+ }
+ if (operationMode != null) {
+ capabilityState.setThermostatActuatorOperationModeState(operationMode);
+ }
+ if (windowReductionActive != null) {
+ capabilityState.setThermostatActuatorWindowReductionActiveState(windowReductionActive);
+ }
+
+ // HumiditySensor
+ } else if (capability.isTypeHumiditySensor()) {
+ // when values are changed, they come with separate events
+ // values should only updated when they are not null
+ final Double humidity = event.getProperties().getHumidity();
+ final Boolean moldWarning = event.getProperties().getMoldWarning();
+ if (humidity != null) {
+ capabilityState.setHumiditySensorHumidityState(humidity);
+ }
+ if (moldWarning != null) {
+ capabilityState.setHumiditySensorMoldWarningState(moldWarning);
+ }
+
+ // WindowDoorSensor
+ } else if (capability.isTypeWindowDoorSensor()) {
+ capabilityState.setWindowDoorSensorState(event.getProperties().getIsOpen());
+
+ // SmokeDetectorSensor
+ } else if (capability.isTypeSmokeDetectorSensor()) {
+ capabilityState.setSmokeDetectorSensorState(event.getProperties().getIsSmokeAlarm());
+
+ // AlarmActuator
+ } else if (capability.isTypeAlarmActuator()) {
+ capabilityState.setAlarmActuatorState(event.getProperties().getOnState());
+
+ // MotionDetectionSensor
+ } else if (capability.isTypeMotionDetectionSensor()) {
+ capabilityState.setMotionDetectionSensorState(event.getProperties().getMotionDetectedCount());
+
+ // LuminanceSensor
+ } else if (capability.isTypeLuminanceSensor()) {
+ capabilityState.setLuminanceSensorState(event.getProperties().getLuminance());
+
+ // PushButtonSensor
+ } else if (capability.isTypePushButtonSensor()) {
+ if (event.isButtonPressedEvent()) {
+ // Some devices send both StateChanged and ButtonPressed. But only the ButtonPressed should be handled,
+ // therefore it is checked for button pressed event (button index is set).
+ EventPropertiesDTO properties = event.getProperties();
+ capabilityState.setPushButtonSensorButtonIndexState(properties.getKeyPressButtonIndex());
+ capabilityState.setPushButtonSensorButtonIndexType(properties.getKeyPressType());
+ capabilityState.setPushButtonSensorCounterState(properties.getKeyPressCounter());
+ }
+
+ // EnergyConsumptionSensor
+ } else if (capability.isTypeEnergyConsumptionSensor()) {
+ capabilityState.setEnergyConsumptionSensorEnergyConsumptionMonthKWhState(
+ event.getProperties().getEnergyConsumptionMonthKWh());
+ capabilityState.setEnergyConsumptionSensorAbsoluteEnergyConsumptionState(
+ event.getProperties().getAbsoluteEnergyConsumption());
+ capabilityState.setEnergyConsumptionSensorEnergyConsumptionMonthEuroState(
+ event.getProperties().getEnergyConsumptionMonthEuro());
+ capabilityState.setEnergyConsumptionSensorEnergyConsumptionDayEuroState(
+ event.getProperties().getEnergyConsumptionDayEuro());
+ capabilityState.setEnergyConsumptionSensorEnergyConsumptionDayKWhState(
+ event.getProperties().getEnergyConsumptionDayKWh());
+
+ // PowerConsumptionSensor
+ } else if (capability.isTypePowerConsumptionSensor()) {
+ capabilityState.setPowerConsumptionSensorPowerConsumptionWattState(
+ event.getProperties().getPowerConsumptionWatt());
+
+ // GenerationMeterEnergySensor
+ } else if (capability.isTypeGenerationMeterEnergySensor()) {
+ capabilityState.setGenerationMeterEnergySensorEnergyPerMonthInKWhState(
+ event.getProperties().getEnergyPerMonthInKWh());
+ capabilityState.setGenerationMeterEnergySensorTotalEnergyState(event.getProperties().getTotalEnergy());
+ capabilityState.setGenerationMeterEnergySensorEnergyPerMonthInEuroState(
+ event.getProperties().getEnergyPerMonthInEuro());
+ capabilityState.setGenerationMeterEnergySensorEnergyPerDayInEuroState(
+ event.getProperties().getEnergyPerDayInEuro());
+ capabilityState
+ .setGenerationMeterEnergySensorEnergyPerDayInKWhState(event.getProperties().getEnergyPerDayInKWh());
+
+ // GenerationMeterPowerConsumptionSensor
+ } else if (capability.isTypeGenerationMeterPowerConsumptionSensor()) {
+ capabilityState
+ .setGenerationMeterPowerConsumptionSensorPowerInWattState(event.getProperties().getPowerInWatt());
+
+ // TwoWayMeterEnergyConsumptionSensor
+ } else if (capability.isTypeTwoWayMeterEnergyConsumptionSensor()) {
+ capabilityState.setTwoWayMeterEnergyConsumptionSensorEnergyPerMonthInKWhState(
+ event.getProperties().getEnergyPerMonthInKWh());
+ capabilityState
+ .setTwoWayMeterEnergyConsumptionSensorTotalEnergyState(event.getProperties().getTotalEnergy());
+ capabilityState.setTwoWayMeterEnergyConsumptionSensorEnergyPerMonthInEuroState(
+ event.getProperties().getEnergyPerMonthInEuro());
+ capabilityState.setTwoWayMeterEnergyConsumptionSensorEnergyPerDayInEuroState(
+ event.getProperties().getEnergyPerDayInEuro());
+ capabilityState.setTwoWayMeterEnergyConsumptionSensorEnergyPerDayInKWhState(
+ event.getProperties().getEnergyPerDayInKWh());
+
+ // TwoWayMeterEnergyFeedSensor
+ } else if (capability.isTypeTwoWayMeterEnergyFeedSensor()) {
+ capabilityState.setTwoWayMeterEnergyFeedSensorEnergyPerMonthInKWhState(
+ event.getProperties().getEnergyPerMonthInKWh());
+ capabilityState.setTwoWayMeterEnergyFeedSensorTotalEnergyState(event.getProperties().getTotalEnergy());
+ capabilityState.setTwoWayMeterEnergyFeedSensorEnergyPerMonthInEuroState(
+ event.getProperties().getEnergyPerMonthInEuro());
+ capabilityState.setTwoWayMeterEnergyFeedSensorEnergyPerDayInEuroState(
+ event.getProperties().getEnergyPerDayInEuro());
+ capabilityState
+ .setTwoWayMeterEnergyFeedSensorEnergyPerDayInKWhState(event.getProperties().getEnergyPerDayInKWh());
+
+ // TwoWayMeterPowerConsumptionSensor
+ } else if (capability.isTypeTwoWayMeterPowerConsumptionSensor()) {
+ capabilityState
+ .setTwoWayMeterPowerConsumptionSensorPowerInWattState(event.getProperties().getPowerInWatt());
+
+ } else {
+ logger.debug("Unsupported capability type {}.", capability.getType());
+ return false;
+ }
+ return true;
+ }
+
+ private void updateChannels(DeviceDTO device) {
+ // DEVICE STATES
+ final boolean isReachable = updateStatus(device);
+
+ if (isReachable) {
+ updateDeviceChannels(device);
+
+ // CAPABILITY STATES
+ for (final Entry entry : device.getCapabilityMap().entrySet()) {
+ final CapabilityDTO capability = entry.getValue();
+
+ logger.debug("->capability:{} ({}/{})", capability.getId(), capability.getType(), capability.getName());
+
+ if (capability.hasState()) {
+ updateCapabilityChannels(device, capability);
+ } else {
+ logger.debug("Capability not available for device {} ({})", device.getConfig().getName(),
+ device.getType());
+ }
+ }
+ }
+ }
+
+ private boolean updateStatus(DeviceDTO device) {
+ Boolean reachable = device.isReachable();
+ if (reachable != null) {
+ if (reachable) {
+ updateStatus(ThingStatus.ONLINE);
+ } else {
+ updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, "@text/error.notReachable");
+ return false;
+ }
+ }
+ return true;
+ }
+
+ private void updateDeviceChannels(DeviceDTO device) {
+ if (device.isBatteryPowered()) {
+ updateState(CHANNEL_BATTERY_LOW, OnOffType.from(device.hasLowBattery()));
+ }
+ }
+
+ private void updateCapabilityChannels(DeviceDTO device, CapabilityDTO capability) {
+ switch (capability.getType()) {
+ case CapabilityDTO.TYPE_VARIABLEACTUATOR:
+ updateVariableActuatorChannels(capability);
+ break;
+ case CapabilityDTO.TYPE_SWITCHACTUATOR:
+ updateSwitchActuatorChannels(capability);
+ break;
+ case CapabilityDTO.TYPE_DIMMERACTUATOR:
+ updateDimmerActuatorChannels(capability);
+ break;
+ case CapabilityDTO.TYPE_ROLLERSHUTTERACTUATOR:
+ updateRollerShutterActuatorChannels(capability);
+ break;
+ case CapabilityDTO.TYPE_TEMPERATURESENSOR:
+ updateTemperatureSensorChannels(capability);
+ break;
+ case CapabilityDTO.TYPE_THERMOSTATACTUATOR:
+ updateThermostatActuatorChannels(device, capability);
+ break;
+ case CapabilityDTO.TYPE_HUMIDITYSENSOR:
+ updateHumiditySensorChannels(capability);
+ break;
+ case CapabilityDTO.TYPE_WINDOWDOORSENSOR:
+ updateWindowDoorSensorChannels(capability);
+ break;
+ case CapabilityDTO.TYPE_SMOKEDETECTORSENSOR:
+ updateSmokeDetectorChannels(capability);
+ break;
+ case CapabilityDTO.TYPE_ALARMACTUATOR:
+ updateAlarmActuatorChannels(capability);
+ break;
+ case CapabilityDTO.TYPE_MOTIONDETECTIONSENSOR:
+ updateMotionDetectionSensorChannels(capability);
+ break;
+ case CapabilityDTO.TYPE_LUMINANCESENSOR:
+ updateLuminanceSensorChannels(capability);
+ break;
+ case CapabilityDTO.TYPE_PUSHBUTTONSENSOR:
+ updatePushButtonSensorChannels(capability);
+ break;
+ case CapabilityDTO.TYPE_ENERGYCONSUMPTIONSENSOR:
+ updateEnergyConsumptionSensorChannels(capability);
+ break;
+ case CapabilityDTO.TYPE_POWERCONSUMPTIONSENSOR:
+ updateStateForEnergyChannelWatt(CHANNEL_POWER_CONSUMPTION_WATT,
+ capability.getCapabilityState().getPowerConsumptionSensorPowerConsumptionWattState(),
+ capability);
+ break;
+ case CapabilityDTO.TYPE_GENERATIONMETERENERGYSENSOR:
+ updateGenerationMeterEnergySensorChannels(capability);
+ break;
+ case CapabilityDTO.TYPE_GENERATIONMETERPOWERCONSUMPTIONSENSOR:
+ updateStateForEnergyChannelWatt(CHANNEL_POWER_GENERATION_WATT,
+ capability.getCapabilityState().getGenerationMeterPowerConsumptionSensorPowerInWattState(),
+ capability);
+ break;
+ case CapabilityDTO.TYPE_TWOWAYMETERENERGYCONSUMPTIONSENSOR:
+ updateTwoWayMeterEnergyConsumptionSensorChannels(capability);
+ break;
+ case CapabilityDTO.TYPE_TWOWAYMETERENERGYFEEDSENSOR:
+ updateTwoWayMeterEnergyFeedSensorChannels(capability);
+ break;
+ case CapabilityDTO.TYPE_TWOWAYMETERPOWERCONSUMPTIONSENSOR:
+ updateStateForEnergyChannelWatt(CHANNEL_POWER_WATT,
+ capability.getCapabilityState().getTwoWayMeterPowerConsumptionSensorPowerInWattState(),
+ capability);
+ break;
+ default:
+ logger.debug("Unsupported capability type {}.", capability.getType());
+ break;
+ }
+ }
+
+ private void updateVariableActuatorChannels(CapabilityDTO capability) {
+ final Boolean variableActuatorState = capability.getCapabilityState().getVariableActuatorState();
+ if (variableActuatorState != null) {
+ updateState(CHANNEL_SWITCH, OnOffType.from(variableActuatorState));
+ } else {
+ logStateNull(capability);
+ }
+ }
+
+ private void updateSwitchActuatorChannels(CapabilityDTO capability) {
+ final Boolean switchActuatorState = capability.getCapabilityState().getSwitchActuatorState();
+ if (switchActuatorState != null) {
+ updateState(CHANNEL_SWITCH, OnOffType.from(switchActuatorState));
+ } else {
+ logStateNull(capability);
+ }
+ }
+
+ private void updateDimmerActuatorChannels(CapabilityDTO capability) {
+ final Integer dimLevel = capability.getCapabilityState().getDimmerActuatorState();
+ if (dimLevel != null) {
+ logger.debug("Dimlevel state {}", dimLevel);
+ updateState(CHANNEL_DIMMER, new PercentType(dimLevel));
+ } else {
+ logStateNull(capability);
+ }
+ }
+
+ private void updateRollerShutterActuatorChannels(CapabilityDTO capability) {
+ Integer rollerShutterLevel = capability.getCapabilityState().getRollerShutterActuatorState();
+ if (rollerShutterLevel != null) {
+ rollerShutterLevel = invertRollerShutterValueIfConfigured(rollerShutterLevel);
+ logger.debug("RollerShutterlevel state {}", rollerShutterLevel);
+ updateState(CHANNEL_ROLLERSHUTTER, new PercentType(rollerShutterLevel));
+ } else {
+ logStateNull(capability);
+ }
+ }
+
+ private void updateTemperatureSensorChannels(CapabilityDTO capability) {
+ // temperature
+ final Double temperature = capability.getCapabilityState().getTemperatureSensorTemperatureState();
+ if (temperature != null) {
+ logger.debug("-> Temperature sensor state: {}", temperature);
+ updateState(CHANNEL_CURRENT_TEMPERATURE, QuantityType.valueOf(temperature, SIUnits.CELSIUS));
+ } else {
+ logStateNull(capability);
+ }
+
+ // frost warning
+ final Boolean frostWarning = capability.getCapabilityState().getTemperatureSensorFrostWarningState();
+ if (frostWarning != null) {
+ updateState(CHANNEL_FROST_WARNING, OnOffType.from(frostWarning));
+ } else {
+ logStateNull(capability);
+ }
+ }
+
+ private void updateThermostatActuatorChannels(DeviceDTO device, CapabilityDTO capability) {
+ // point temperature
+ final Double pointTemperature = capability.getCapabilityState().getThermostatActuatorPointTemperatureState();
+ if (pointTemperature != null) {
+ logger.debug("Update CHANNEL_SET_TEMPERATURE: state:{} (DeviceName {}, Capab-ID:{})", pointTemperature,
+ device.getConfig().getName(), capability.getId());
+ updateState(CHANNEL_TARGET_TEMPERATURE, QuantityType.valueOf(pointTemperature, SIUnits.CELSIUS));
+ } else {
+ logStateNull(capability);
+ }
+
+ // operation mode
+ final String operationMode = capability.getCapabilityState().getThermostatActuatorOperationModeState();
+ if (operationMode != null) {
+ updateState(CHANNEL_OPERATION_MODE, new StringType(operationMode));
+ } else {
+ logStateNull(capability);
+ }
+
+ // window reduction active
+ final Boolean windowReductionActive = capability.getCapabilityState()
+ .getThermostatActuatorWindowReductionActiveState();
+ if (windowReductionActive != null) {
+ updateState(CHANNEL_WINDOW_REDUCTION_ACTIVE, OnOffType.from(windowReductionActive));
+ } else {
+ logStateNull(capability);
+ }
+ }
+
+ private void updateHumiditySensorChannels(CapabilityDTO capability) {
+ // humidity
+ final Double humidity = capability.getCapabilityState().getHumiditySensorHumidityState();
+ if (humidity != null) {
+ updateState(CHANNEL_HUMIDITY, QuantityType.valueOf(humidity, Units.PERCENT));
+ } else {
+ logStateNull(capability);
+ }
+
+ // mold warning
+ final Boolean moldWarning = capability.getCapabilityState().getHumiditySensorMoldWarningState();
+ if (moldWarning != null) {
+ updateState(CHANNEL_MOLD_WARNING, OnOffType.from(moldWarning));
+ } else {
+ logStateNull(capability);
+ }
+ }
+
+ private void updateWindowDoorSensorChannels(CapabilityDTO capability) {
+ final Boolean contactState = capability.getCapabilityState().getWindowDoorSensorState();
+ if (contactState != null) {
+ updateState(CHANNEL_CONTACT, toOpenClosedType(contactState));
+ } else {
+ logStateNull(capability);
+ }
+ }
+
+ private void updateSmokeDetectorChannels(CapabilityDTO capability) {
+ final Boolean smokeState = capability.getCapabilityState().getSmokeDetectorSensorState();
+ if (smokeState != null) {
+ updateState(CHANNEL_SMOKE, OnOffType.from(smokeState));
+ } else {
+ logStateNull(capability);
+ }
+ }
+
+ private void updateAlarmActuatorChannels(CapabilityDTO capability) {
+ final Boolean alarmState = capability.getCapabilityState().getAlarmActuatorState();
+ if (alarmState != null) {
+ updateState(CHANNEL_ALARM, OnOffType.from(alarmState));
+ } else {
+ logStateNull(capability);
+ }
+ }
+
+ private void updateMotionDetectionSensorChannels(CapabilityDTO capability) {
+ final Integer motionCount = capability.getCapabilityState().getMotionDetectionSensorState();
+ if (motionCount != null) {
+ logger.debug("Motion state {} -> count {}", motionCount, motionCount);
+ updateState(CHANNEL_MOTION_COUNT, new DecimalType(motionCount));
+ } else {
+ logStateNull(capability);
+ }
+ }
+
+ private void updateLuminanceSensorChannels(CapabilityDTO capability) {
+ final Double luminance = capability.getCapabilityState().getLuminanceSensorState();
+ if (luminance != null) {
+ updateState(CHANNEL_LUMINANCE, QuantityType.valueOf(luminance, Units.PERCENT));
+ } else {
+ logStateNull(capability);
+ }
+ }
+
+ private void updatePushButtonSensorChannels(CapabilityDTO capability) {
+ final Integer pushCount = capability.getCapabilityState().getPushButtonSensorCounterState();
+ final Integer buttonIndex = capability.getCapabilityState().getPushButtonSensorButtonIndexState();
+ final String type = capability.getCapabilityState().getPushButtonSensorButtonIndexType();
+ logger.debug("Pushbutton index {}, count {}, type {}", buttonIndex, pushCount, type);
+ if (buttonIndex != null && pushCount != null) {
+ if (buttonIndex >= 0 && buttonIndex <= 7) {
+ final int channelIndex = buttonIndex + 1;
+ if (type != null) {
+ if (SHORT_PRESS.equals(type)) {
+ triggerChannel(CHANNEL_BUTTON + channelIndex, CommonTriggerEvents.SHORT_PRESSED);
+ } else if (LONG_PRESS.equals(type)) {
+ triggerChannel(CHANNEL_BUTTON + channelIndex, CommonTriggerEvents.LONG_PRESSED);
+ }
+ }
+ triggerChannel(CHANNEL_BUTTON + channelIndex, CommonTriggerEvents.PRESSED);
+ updateState(String.format(CHANNEL_BUTTON_COUNT, channelIndex), new DecimalType(pushCount));
+ }
+ // Button handled so remove state to avoid re-trigger.
+ capability.getCapabilityState().setPushButtonSensorButtonIndexState(null);
+ capability.getCapabilityState().setPushButtonSensorButtonIndexType(null);
+ } else {
+ logStateNull(capability);
+ }
+ }
+
+ private void updateEnergyConsumptionSensorChannels(CapabilityDTO capability) {
+ updateStateForEnergyChannelKiloWattHour(CHANNEL_ENERGY_CONSUMPTION_MONTH_KWH,
+ capability.getCapabilityState().getEnergyConsumptionSensorEnergyConsumptionMonthKWhState(), capability);
+ updateStateForEnergyChannelKiloWattHour(CHANNEL_ABOLUTE_ENERGY_CONSUMPTION,
+ capability.getCapabilityState().getEnergyConsumptionSensorAbsoluteEnergyConsumptionState(), capability);
+ updateStateForEnergyChannelEuro(CHANNEL_ENERGY_CONSUMPTION_MONTH_EURO,
+ capability.getCapabilityState().getEnergyConsumptionSensorEnergyConsumptionMonthEuroState(),
+ capability);
+ updateStateForEnergyChannelEuro(CHANNEL_ENERGY_CONSUMPTION_DAY_EURO,
+ capability.getCapabilityState().getEnergyConsumptionSensorEnergyConsumptionDayEuroState(), capability);
+ updateStateForEnergyChannelKiloWattHour(CHANNEL_ENERGY_CONSUMPTION_DAY_KWH,
+ capability.getCapabilityState().getEnergyConsumptionSensorEnergyConsumptionDayKWhState(), capability);
+ }
+
+ private void updateGenerationMeterEnergySensorChannels(CapabilityDTO capability) {
+ updateStateForEnergyChannelKiloWattHour(CHANNEL_ENERGY_GENERATION_MONTH_KWH,
+ capability.getCapabilityState().getGenerationMeterEnergySensorEnergyPerMonthInKWhState(), capability);
+ updateStateForEnergyChannelKiloWattHour(CHANNEL_TOTAL_ENERGY_GENERATION,
+ capability.getCapabilityState().getGenerationMeterEnergySensorTotalEnergyState(), capability);
+ updateStateForEnergyChannelEuro(CHANNEL_ENERGY_GENERATION_MONTH_EURO,
+ capability.getCapabilityState().getGenerationMeterEnergySensorEnergyPerMonthInEuroState(), capability);
+ updateStateForEnergyChannelEuro(CHANNEL_ENERGY_GENERATION_DAY_EURO,
+ capability.getCapabilityState().getGenerationMeterEnergySensorEnergyPerDayInEuroState(), capability);
+ updateStateForEnergyChannelKiloWattHour(CHANNEL_ENERGY_GENERATION_DAY_KWH,
+ capability.getCapabilityState().getGenerationMeterEnergySensorEnergyPerDayInKWhState(), capability);
+ }
+
+ private void updateTwoWayMeterEnergyConsumptionSensorChannels(CapabilityDTO capability) {
+ updateStateForEnergyChannelKiloWattHour(CHANNEL_ENERGY_MONTH_KWH,
+ capability.getCapabilityState().getTwoWayMeterEnergyConsumptionSensorEnergyPerMonthInKWhState(),
+ capability);
+ updateStateForEnergyChannelKiloWattHour(CHANNEL_TOTAL_ENERGY,
+ capability.getCapabilityState().getTwoWayMeterEnergyConsumptionSensorTotalEnergyState(), capability);
+ updateStateForEnergyChannelEuro(CHANNEL_ENERGY_MONTH_EURO,
+ capability.getCapabilityState().getTwoWayMeterEnergyConsumptionSensorEnergyPerMonthInEuroState(),
+ capability);
+ updateStateForEnergyChannelEuro(CHANNEL_ENERGY_DAY_EURO,
+ capability.getCapabilityState().getTwoWayMeterEnergyConsumptionSensorEnergyPerDayInEuroState(),
+ capability);
+ updateStateForEnergyChannelKiloWattHour(CHANNEL_ENERGY_DAY_KWH,
+ capability.getCapabilityState().getTwoWayMeterEnergyConsumptionSensorEnergyPerDayInKWhState(),
+ capability);
+ }
+
+ private void updateTwoWayMeterEnergyFeedSensorChannels(CapabilityDTO capability) {
+ updateStateForEnergyChannelKiloWattHour(CHANNEL_ENERGY_FEED_MONTH_KWH,
+ capability.getCapabilityState().getTwoWayMeterEnergyFeedSensorEnergyPerMonthInKWhState(), capability);
+ updateStateForEnergyChannelKiloWattHour(CHANNEL_TOTAL_ENERGY_FED,
+ capability.getCapabilityState().getTwoWayMeterEnergyFeedSensorTotalEnergyState(), capability);
+ updateStateForEnergyChannelEuro(CHANNEL_ENERGY_FEED_MONTH_EURO,
+ capability.getCapabilityState().getTwoWayMeterEnergyFeedSensorEnergyPerMonthInEuroState(), capability);
+ updateStateForEnergyChannelEuro(CHANNEL_ENERGY_FEED_DAY_EURO,
+ capability.getCapabilityState().getTwoWayMeterEnergyFeedSensorEnergyPerDayInEuroState(), capability);
+ updateStateForEnergyChannelKiloWattHour(CHANNEL_ENERGY_FEED_DAY_KWH,
+ capability.getCapabilityState().getTwoWayMeterEnergyFeedSensorEnergyPerDayInKWhState(), capability);
+ }
+
+ private void updateStateForEnergyChannelEuro(final String channelId, @Nullable final Double state,
+ final CapabilityDTO capability) {
+ if (state != null) {
+ updateState(channelId, new DecimalType(state));
+ } else {
+ logStateNull(capability);
+ }
+ }
+
+ private void updateStateForEnergyChannelWatt(final String channelId, @Nullable final Double state,
+ final CapabilityDTO capability) {
+ if (state != null) {
+ updateState(channelId, QuantityType.valueOf(state, Units.WATT));
+ } else {
+ logStateNull(capability);
+ }
+ }
+
+ private void updateStateForEnergyChannelKiloWattHour(final String channelId, @Nullable final Double state,
+ final CapabilityDTO capability) {
+ if (state != null) {
+ updateState(channelId, QuantityType.valueOf(state, Units.KILOWATT_HOUR));
+ } else {
+ logStateNull(capability);
+ }
+ }
+
+ /**
+ * Returns the inverted value. Currently only rollershutter channels are supported.
+ *
+ * @param value value to become inverted
+ * @return the value or the inverted value
+ */
+ private int invertRollerShutterValueIfConfigured(final int value) {
+ @Nullable
+ final Channel channel = getThing().getChannel(CHANNEL_ROLLERSHUTTER);
+ if (channel == null) {
+ logger.debug("Channel {} was null! Value not inverted.", CHANNEL_ROLLERSHUTTER);
+ return value;
+ }
+ final Boolean invert = (Boolean) channel.getConfiguration().get(INVERT_CHANNEL_PARAMETER);
+ if (invert != null && invert) {
+ return value;
+ }
+ return 100 - value;
+ }
+
+ /**
+ * Returns the {@link DeviceDTO} associated with this {@link LivisiDeviceHandler} (referenced by the
+ * {@link LivisiDeviceHandler#deviceId}).
+ *
+ * @return the {@link DeviceDTO} or null, if not found or no {@link LivisiBridgeHandler} is available
+ */
+ private Optional getDevice() {
+ return getBridgeHandler().flatMap(bridgeHandler -> bridgeHandler.getDeviceById(deviceId));
+ }
+
+ private Optional registerAtBridgeHandler() {
+ synchronized (this.lock) {
+ if (this.bridgeHandler == null) {
+ @Nullable
+ final Bridge bridge = getBridge();
+ if (bridge == null) {
+ return Optional.empty();
+ }
+ @Nullable
+ final ThingHandler handler = bridge.getHandler();
+ if (handler instanceof LivisiBridgeHandler) {
+ LivisiBridgeHandler bridgeHandler = (LivisiBridgeHandler) handler;
+ bridgeHandler.registerDeviceStatusListener(deviceId, this);
+ this.bridgeHandler = bridgeHandler;
+ } else {
+ return Optional.empty(); // also called when the handler is NULL
+ }
+ }
+ return getBridgeHandler();
+ }
+ }
+
+ /**
+ * Returns the LIVISI bridge handler.
+ *
+ * @return the {@link LivisiBridgeHandler} or null
+ */
+ private Optional getBridgeHandler() {
+ return Optional.ofNullable(this.bridgeHandler);
+ }
+
+ private boolean isBridgeOnline() {
+ @Nullable
+ Bridge bridge = getBridge();
+ if (bridge != null) {
+ return ThingStatus.ONLINE == bridge.getStatus();
+ }
+ return false;
+ }
+
+ private void logStateNull(CapabilityDTO capability) {
+ logger.debug("State for {} is STILL NULL!! cstate-id: {}, capability-id: {}", capability.getType(),
+ capability.getCapabilityState().getId(), capability.getId());
+ }
+
+ private static OpenClosedType toOpenClosedType(boolean isOpen) {
+ if (isOpen) {
+ return OpenClosedType.OPEN;
+ }
+ return OpenClosedType.CLOSED;
+ }
+}
diff --git a/bundles/org.openhab.binding.livisismarthome/src/main/java/org/openhab/binding/livisismarthome/internal/listener/DeviceStatusListener.java b/bundles/org.openhab.binding.livisismarthome/src/main/java/org/openhab/binding/livisismarthome/internal/listener/DeviceStatusListener.java
new file mode 100644
index 00000000000..e8a1537a051
--- /dev/null
+++ b/bundles/org.openhab.binding.livisismarthome/src/main/java/org/openhab/binding/livisismarthome/internal/listener/DeviceStatusListener.java
@@ -0,0 +1,41 @@
+/**
+ * Copyright (c) 2010-2022 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.livisismarthome.internal.listener;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.openhab.binding.livisismarthome.internal.client.api.entity.device.DeviceDTO;
+import org.openhab.binding.livisismarthome.internal.client.api.entity.event.EventDTO;
+
+/**
+ * The {@link DeviceStatusListener} is called, when {@link DeviceDTO}s are added, removed or changed.
+ *
+ * @author Oliver Kuhl - Initial contribution
+ */
+@NonNullByDefault
+public interface DeviceStatusListener {
+
+ /**
+ * This method is called whenever the state of the given {@link DeviceDTO} has changed.
+ *
+ * @param device The device which received the state update.
+ */
+ void onDeviceStateChanged(DeviceDTO device);
+
+ /**
+ * This method is called whenever the state of a {@link DeviceDTO} is changed by the given {@link EventDTO}.
+ *
+ * @param device device
+ * @param event event
+ */
+ void onDeviceStateChanged(DeviceDTO device, EventDTO event);
+}
diff --git a/bundles/org.openhab.binding.livisismarthome/src/main/java/org/openhab/binding/livisismarthome/internal/listener/EventListener.java b/bundles/org.openhab.binding.livisismarthome/src/main/java/org/openhab/binding/livisismarthome/internal/listener/EventListener.java
new file mode 100644
index 00000000000..76f558dfbef
--- /dev/null
+++ b/bundles/org.openhab.binding.livisismarthome/src/main/java/org/openhab/binding/livisismarthome/internal/listener/EventListener.java
@@ -0,0 +1,46 @@
+/**
+ * Copyright (c) 2010-2022 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.livisismarthome.internal.listener;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+
+/**
+ * The {@link EventListener} is called by the {@link org.openhab.binding.livisismarthome.internal.LivisiWebSocket} on
+ * new Events and if the {@link org.openhab.binding.livisismarthome.internal.LivisiWebSocket}
+ * closed the connection.
+ *
+ * @author Oliver Kuhl - Initial contribution
+ */
+@NonNullByDefault
+public interface EventListener {
+
+ /**
+ * This method is called, whenever a new event comes from the LIVISI SmartHome service (like a device change for
+ * example).
+ *
+ * @param msg message
+ */
+ void onEvent(String msg);
+
+ /**
+ * This method is called when the LIVISI SmartHome websocket services throws an onError.
+ *
+ * @param cause cause / throwable
+ */
+ void onError(Throwable cause);
+
+ /**
+ * This method is called, when the evenRunner stops abnormally (statuscode <> 1000).
+ */
+ void connectionClosed();
+}
diff --git a/bundles/org.openhab.binding.livisismarthome/src/main/java/org/openhab/binding/livisismarthome/internal/manager/DeviceStructureManager.java b/bundles/org.openhab.binding.livisismarthome/src/main/java/org/openhab/binding/livisismarthome/internal/manager/DeviceStructureManager.java
new file mode 100644
index 00000000000..939d236689d
--- /dev/null
+++ b/bundles/org.openhab.binding.livisismarthome/src/main/java/org/openhab/binding/livisismarthome/internal/manager/DeviceStructureManager.java
@@ -0,0 +1,229 @@
+/**
+ * Copyright (c) 2010-2022 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.livisismarthome.internal.manager;
+
+import java.io.IOException;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Optional;
+import java.util.concurrent.ConcurrentHashMap;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.openhab.binding.livisismarthome.internal.LivisiBindingConstants;
+import org.openhab.binding.livisismarthome.internal.client.api.entity.capability.CapabilityDTO;
+import org.openhab.binding.livisismarthome.internal.client.api.entity.device.DeviceDTO;
+import org.openhab.binding.livisismarthome.internal.client.api.entity.link.LinkDTO;
+import org.openhab.binding.livisismarthome.internal.client.api.entity.message.MessageDTO;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Manages the structure of the {@link DeviceDTO}s and the calls to the
+ * {@link org.openhab.binding.livisismarthome.internal.client.LivisiClient} to load the {@link DeviceDTO}
+ * data from the LIVISI SmartHome web service.
+ *
+ * @author Oliver Kuhl - Initial contribution
+ *
+ */
+@NonNullByDefault
+public class DeviceStructureManager {
+
+ private final Logger logger = LoggerFactory.getLogger(DeviceStructureManager.class);
+
+ private final FullDeviceManager deviceManager;
+ private final Map deviceMap;
+ private final Map capabilityIdToDeviceMap;
+ private String bridgeDeviceId = "";
+
+ /**
+ * Constructs the {@link DeviceStructureManager}.
+ *
+ * @param deviceManager the {@link FullDeviceManager}
+ */
+ public DeviceStructureManager(FullDeviceManager deviceManager) {
+ this.deviceManager = deviceManager;
+ deviceMap = Collections.synchronizedMap(new HashMap<>());
+ capabilityIdToDeviceMap = new ConcurrentHashMap<>();
+ }
+
+ /**
+ * Returns the {@link #deviceMap}, a map with the device id and the device.
+ *
+ * @return map of device id and device
+ */
+ public Map getDeviceMap() {
+ return deviceMap;
+ }
+
+ /**
+ * Loads all device data from the bridge and stores the {@link DeviceDTO}s and their states in the
+ * {@link DeviceStructureManager}.
+ */
+ public void refreshDevices() throws IOException {
+ deviceMap.clear();
+ capabilityIdToDeviceMap.clear();
+ List devices = deviceManager.getFullDevices();
+ for (DeviceDTO d : devices) {
+ handleRefreshedDevice(d);
+ }
+ }
+
+ /**
+ * Refreshs the {@link DeviceDTO} with the given id and stores it in the {@link DeviceStructureManager}.
+ *
+ * @param deviceId device id
+ */
+ public Optional refreshDevice(final String deviceId, final boolean isSHCClassic) throws IOException {
+ logger.trace("Refreshing Device with id '{}'", deviceId);
+ Optional device = deviceManager.getFullDeviceById(deviceId, isSHCClassic);
+ device.ifPresent(this::handleRefreshedDevice);
+ return device;
+ }
+
+ /**
+ * Stores the newly refreshed {@link DeviceDTO} in the {@link DeviceStructureManager} structure and logs the
+ * {@link DeviceDTO}s details and state, if the debug logging is enabled.
+ *
+ * @param d the {@link DeviceDTO}
+ */
+ private void handleRefreshedDevice(DeviceDTO d) {
+ if (LivisiBindingConstants.SUPPORTED_DEVICES.contains(d.getType())) {
+ addDeviceToStructure(d);
+
+ if (d.isController()) {
+ bridgeDeviceId = d.getId();
+ }
+ } else {
+ logger.debug("Device {}:'{}' by {} ({}) ignored - UNSUPPORTED.", d.getType(), d.getConfig().getName(),
+ d.getManufacturer(), d.getId());
+ logger.debug("====================================");
+ }
+ }
+
+ /**
+ * Adds the {@link DeviceDTO} to the structure.
+ *
+ * @param device device
+ */
+ private void addDeviceToStructure(DeviceDTO device) {
+ if (device.getId() != null) {
+ getDeviceMap().put(device.getId(), device);
+ }
+
+ for (String capability : device.getCapabilities()) {
+ capabilityIdToDeviceMap.put(LinkDTO.getId(capability), device);
+ }
+
+ logDeviceLoaded(device);
+ }
+
+ /**
+ * Returns the {@link DeviceDTO} with the given id.
+ *
+ * @param id device id
+ * @return the {@link DeviceDTO} or null, if it does not exist
+ */
+ public Optional getDeviceById(String id) {
+ logger.debug("getDeviceById {}:{}", id, getDeviceMap().containsKey(id));
+ return Optional.ofNullable(getDeviceMap().get(id));
+ }
+
+ /**
+ * Returns the {@link DeviceDTO}, that provides the given capability.
+ *
+ * @param capabilityId capability id
+ * @return {@link DeviceDTO} or null
+ */
+ public Optional getDeviceByCapabilityId(String capabilityId) {
+ return Optional.ofNullable(capabilityIdToDeviceMap.get(capabilityId));
+ }
+
+ /**
+ * Returns the bridge {@link DeviceDTO}.
+ *
+ * @return bridge device
+ */
+ public Optional getBridgeDevice() {
+ return Optional.ofNullable(getDeviceMap().get(bridgeDeviceId));
+ }
+
+ /**
+ * Returns a {@link Collection} of all {@link DeviceDTO}s handled by the {@link DeviceStructureManager}.
+ *
+ * @return devices
+ */
+ public Collection getDeviceList() {
+ return Collections.unmodifiableCollection(getDeviceMap().values());
+ }
+
+ /**
+ * Returns the {@link DeviceDTO}, that has the {@link MessageDTO} with the given messageId.
+ *
+ * @param messageId the id of the {@link MessageDTO}
+ * @return the {@link DeviceDTO} or null if none found
+ */
+ public Optional getDeviceWithMessageId(String messageId) {
+ logger.trace("Getting Device with MessageId '{}'", messageId);
+ for (DeviceDTO d : getDeviceMap().values()) {
+ if (d.hasMessages()) {
+ for (MessageDTO m : d.getMessageList()) {
+ if (messageId.equals(m.getId())) {
+ return Optional.of(d);
+ }
+ }
+ }
+ }
+ return Optional.empty();
+ }
+
+ /**
+ * Returns the id of the {@link CapabilityDTO} for {@link DeviceDTO} with the given id and the given capabilityType.
+ *
+ * @param deviceId device id
+ * @param capabilityType capability type
+ * @return the id of the found {@link CapabilityDTO} or null
+ */
+ public Optional getCapabilityId(String deviceId, String capabilityType) {
+ DeviceDTO device = getDeviceMap().get(deviceId);
+ if (device != null) {
+ for (CapabilityDTO c : device.getCapabilityMap().values()) {
+ if (c.getType().equals(capabilityType)) {
+ return Optional.of(c.getId());
+ }
+ }
+ }
+ return Optional.empty();
+ }
+
+ private void logDeviceLoaded(DeviceDTO device) {
+ if (logger.isDebugEnabled()) {
+ String location = device.getLocationName();
+ logger.debug("Device {}:'{}@{}' by {} ({}) loaded.", device.getType(), device.getConfig().getName(),
+ location, device.getManufacturer(), device.getId());
+ for (CapabilityDTO c : device.getCapabilityMap().values()) {
+ logger.debug("> CAP: {}/{} ({})", c.getType(), c.getName(), c.getId());
+ if (device.isRadioDevice() && device.isReachable() != null && !device.isReachable()) {
+ logger.debug(">> CAP-State: unknown (device NOT REACHABLE).");
+ } else {
+ if (!c.hasState()) {
+ logger.debug(">> CAP-State: unknown (NULL)");
+ }
+ }
+ }
+ logger.debug("====================================");
+ }
+ }
+}
diff --git a/bundles/org.openhab.binding.livisismarthome/src/main/java/org/openhab/binding/livisismarthome/internal/manager/FullDeviceManager.java b/bundles/org.openhab.binding.livisismarthome/src/main/java/org/openhab/binding/livisismarthome/internal/manager/FullDeviceManager.java
new file mode 100644
index 00000000000..0829b862016
--- /dev/null
+++ b/bundles/org.openhab.binding.livisismarthome/src/main/java/org/openhab/binding/livisismarthome/internal/manager/FullDeviceManager.java
@@ -0,0 +1,229 @@
+/**
+ * Copyright (c) 2010-2022 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.livisismarthome.internal.manager;
+
+import static org.openhab.binding.livisismarthome.internal.LivisiBindingConstants.BATTERY_POWERED_DEVICES;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import java.util.Optional;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.eclipse.jdt.annotation.Nullable;
+import org.openhab.binding.livisismarthome.internal.client.LivisiClient;
+import org.openhab.binding.livisismarthome.internal.client.api.entity.capability.CapabilityDTO;
+import org.openhab.binding.livisismarthome.internal.client.api.entity.capability.CapabilityStateDTO;
+import org.openhab.binding.livisismarthome.internal.client.api.entity.device.DeviceDTO;
+import org.openhab.binding.livisismarthome.internal.client.api.entity.device.DeviceStateDTO;
+import org.openhab.binding.livisismarthome.internal.client.api.entity.link.LinkDTO;
+import org.openhab.binding.livisismarthome.internal.client.api.entity.location.LocationDTO;
+import org.openhab.binding.livisismarthome.internal.client.api.entity.message.MessageDTO;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * @author Sven Strohschein - Initial contribution
+ *
+ * (created by refactoring the LivisiClient class)
+ */
+@NonNullByDefault
+public class FullDeviceManager {
+
+ private final Logger logger = LoggerFactory.getLogger(FullDeviceManager.class);
+
+ private final LivisiClient client;
+
+ public FullDeviceManager(LivisiClient client) {
+ this.client = client;
+ }
+
+ /**
+ * Returns a {@link List} of all {@link DeviceDTO}s with the full configuration details, {@link CapabilityDTO}s and
+ * states. Calling this may take a while...
+ */
+ public List getFullDevices() throws IOException {
+
+ final Map locationMap = createLocationMap(client);
+ final Map capabilityMap = createCapabilityMap(client);
+ final Map deviceStateMap = createDeviceStateMap(client);
+ final Map> messageMap = createMessageMap(client);
+
+ final List deviceList = client.getDevices(deviceStateMap.keySet());
+ for (final DeviceDTO device : deviceList) {
+ final String deviceId = device.getId();
+ initializeDevice(device, deviceStateMap.get(deviceId), locationMap, capabilityMap,
+ getMessageList(device, messageMap));
+ }
+ return deviceList;
+ }
+
+ /**
+ * Returns the {@link DeviceDTO} with the given deviceId with full configuration details, {@link CapabilityDTO}s and
+ * states. Calling this may take a little bit longer...
+ */
+ public Optional getFullDeviceById(final String deviceId, final boolean isSHCClassic) throws IOException {
+ final Map locationMap = createLocationMap(client);
+ final Map capabilityMap = createCapabilityMap(deviceId, client);
+ final List messageMap = createMessageMap(deviceId, client);
+
+ final Optional device = client.getDeviceById(deviceId);
+ if (device.isPresent()) {
+ final DeviceStateDTO deviceState = new DeviceStateDTO();
+ deviceState.setId(deviceId);
+ deviceState.setState(client.getDeviceStateByDeviceId(deviceId, isSHCClassic));
+
+ initializeDevice(device.get(), deviceState, locationMap, capabilityMap, messageMap);
+ }
+ return device;
+ }
+
+ private void initializeDevice(DeviceDTO device, @Nullable DeviceStateDTO deviceState,
+ Map locationMap, Map capabilityMap,
+ List messageList) {
+
+ device.setDeviceState(deviceState);
+
+ if (isBatteryPowered(device)) {
+ device.setIsBatteryPowered(true);
+ }
+
+ device.setLocation(locationMap.get(device.getLocationId()));
+
+ device.setCapabilityMap(createDeviceCapabilityMap(device, capabilityMap));
+
+ device.setMessageList(messageList);
+ }
+
+ private static boolean isBatteryPowered(DeviceDTO device) {
+ return BATTERY_POWERED_DEVICES.contains(device.getType());
+ }
+
+ private List getMessageList(DeviceDTO device, Map> messageMap) {
+ return Objects.requireNonNullElse(messageMap.get(device.getId()), Collections.emptyList());
+ }
+
+ private static Map createLocationMap(LivisiClient client) throws IOException {
+ final List locationList = client.getLocations();
+ final Map locationMap = new HashMap<>(locationList.size());
+ for (final LocationDTO location : locationList) {
+ locationMap.put(location.getId(), location);
+ }
+ return locationMap;
+ }
+
+ private static Map createCapabilityStateMap(LivisiClient client) throws IOException {
+ final List capabilityStateList = client.getCapabilityStates();
+ final Map capabilityStateMap = new HashMap<>(capabilityStateList.size());
+ for (final CapabilityStateDTO capabilityState : capabilityStateList) {
+ capabilityStateMap.put(capabilityState.getId(), capabilityState);
+ }
+ return capabilityStateMap;
+ }
+
+ private static Map createCapabilityMap(LivisiClient client) throws IOException {
+
+ final Map capabilityStateMap = createCapabilityStateMap(client);
+ final List capabilityList = client.getCapabilities();
+
+ return initializeCapabilities(capabilityStateMap, capabilityList);
+ }
+
+ private static Map createCapabilityMap(String deviceId, LivisiClient client)
+ throws IOException {
+
+ final Map capabilityStateMap = createCapabilityStateMap(client);
+ final List capabilityList = client.getCapabilitiesForDevice(deviceId);
+
+ return initializeCapabilities(capabilityStateMap, capabilityList);
+ }
+
+ private static Map initializeCapabilities(Map capabilityStateMap,
+ List capabilityList) {
+ final Map capabilityMap = new HashMap<>(capabilityList.size());
+ for (final CapabilityDTO capability : capabilityList) {
+ String capabilityId = capability.getId();
+
+ CapabilityStateDTO capabilityState = capabilityStateMap.get(capabilityId);
+ capability.setCapabilityState(capabilityState);
+
+ capabilityMap.put(capabilityId, capability);
+ }
+ return capabilityMap;
+ }
+
+ private static Map createDeviceCapabilityMap(DeviceDTO device,
+ Map capabilityMap) {
+
+ final HashMap deviceCapabilityMap = new HashMap<>();
+ for (final String capabilityValue : device.getCapabilities()) {
+ final CapabilityDTO capability = capabilityMap.get(LinkDTO.getId(capabilityValue));
+ if (capability != null) {
+ final String capabilityId = capability.getId();
+ deviceCapabilityMap.put(capabilityId, capability);
+ }
+ }
+ return deviceCapabilityMap;
+ }
+
+ private static Map createDeviceStateMap(LivisiClient client) throws IOException {
+ final List deviceStateList = client.getDeviceStates();
+ final Map deviceStateMap = new HashMap<>(deviceStateList.size());
+ for (final DeviceStateDTO deviceState : deviceStateList) {
+ deviceStateMap.put(deviceState.getId(), deviceState);
+ }
+ return deviceStateMap;
+ }
+
+ private List createMessageMap(String deviceId, LivisiClient client) throws IOException {
+ final List messages = client.getMessages();
+ final List messageList = new ArrayList<>();
+ final String deviceIdPath = "/device/" + deviceId;
+
+ for (final MessageDTO message : messages) {
+ logger.trace("Message Type {} with ID {}", message.getType(), message.getId());
+ if (message.getDevices() != null && !message.getDevices().isEmpty()) {
+ for (final String li : message.getDevices()) {
+ if (deviceIdPath.equals(li)) {
+ messageList.add(message);
+ }
+ }
+ }
+ }
+ return messageList;
+ }
+
+ private static Map> createMessageMap(LivisiClient client) throws IOException {
+ final List messageList = client.getMessages();
+ final Map> deviceMessageMap = new HashMap<>();
+ for (final MessageDTO message : messageList) {
+ if (message.getDevices() != null && !message.getDevices().isEmpty()) {
+ final String deviceId = message.getDevices().get(0).replace("/device/", "");
+
+ // could get optimized with computeIfAbsent, but the non-null checks doesn't understand that and
+ // produces compiler warnings...
+ List ml = deviceMessageMap.get(deviceId);
+ if (ml == null) {
+ ml = new ArrayList<>();
+ deviceMessageMap.put(deviceId, ml);
+ }
+ ml.add(message);
+ }
+ }
+ return deviceMessageMap;
+ }
+}
diff --git a/bundles/org.openhab.binding.livisismarthome/src/main/resources/OH-INF/binding/binding.xml b/bundles/org.openhab.binding.livisismarthome/src/main/resources/OH-INF/binding/binding.xml
new file mode 100644
index 00000000000..0d40073d580
--- /dev/null
+++ b/bundles/org.openhab.binding.livisismarthome/src/main/resources/OH-INF/binding/binding.xml
@@ -0,0 +1,9 @@
+
+
+
+ LIVISI SmartHome Binding
+ The LIVISI SmartHome binding integrates your LIVISI SmartHome system.
+
+
diff --git a/bundles/org.openhab.binding.livisismarthome/src/main/resources/OH-INF/config/config.xml b/bundles/org.openhab.binding.livisismarthome/src/main/resources/OH-INF/config/config.xml
new file mode 100644
index 00000000000..324d73583b8
--- /dev/null
+++ b/bundles/org.openhab.binding.livisismarthome/src/main/resources/OH-INF/config/config.xml
@@ -0,0 +1,15 @@
+
+
+
+
+
+
+ The identifier uniquely identifies this device.
+
+
+
+
diff --git a/bundles/org.openhab.binding.livisismarthome/src/main/resources/OH-INF/i18n/livisismarthome.properties b/bundles/org.openhab.binding.livisismarthome/src/main/resources/OH-INF/i18n/livisismarthome.properties
new file mode 100644
index 00000000000..264cfb5c9af
--- /dev/null
+++ b/bundles/org.openhab.binding.livisismarthome/src/main/resources/OH-INF/i18n/livisismarthome.properties
@@ -0,0 +1,161 @@
+# binding
+
+binding.livisismarthome.name = LIVISI SmartHome Binding
+binding.livisismarthome.description = The LIVISI SmartHome binding integrates your LIVISI SmartHome system.
+
+# common messages
+
+error.connect = Can't connect to LIVISI SmartHome service! Please check your credentials.
+error.notReachable = Device not reachable
+error.deviceNotFound = Device not found in LIVISI SmartHome config. Was it removed?
+error.deviceIdUnknown = Unknown device id
+error.bridgeHandlerMissing = The bridge or bridge handler is missing. Isn't it configured at the thing?
+
+# thing types
+
+thing-type.livisismarthome.AnalogMeter.label = Analog Meter
+thing-type.livisismarthome.AnalogMeter.description = The Analog Meter from the EnergyControl product.
+thing-type.livisismarthome.BRC8.label = Basic Remote Controller (BRC8)
+thing-type.livisismarthome.BRC8.description = A battery powered remote controller with eight push buttons.
+thing-type.livisismarthome.BT-PSS.label = Bluetooth Pluggable Smart Switch (BT-PSS)
+thing-type.livisismarthome.BT-PSS.description = A pluggable switch that can be switched on and off and which can measure the current energy consumption.
+thing-type.livisismarthome.GenerationMeter.label = Generation Meter
+thing-type.livisismarthome.GenerationMeter.description = The Generation Meter from the PowerControlSolar product, that measures the generated energy.
+thing-type.livisismarthome.ISC2.label = In Wall Smart Controller (ISC2)
+thing-type.livisismarthome.ISC2.description = A smart controller with two push buttons.
+thing-type.livisismarthome.ISD2.label = In Wall Smart Dimmer (ISD2)
+thing-type.livisismarthome.ISD2.description = An in wall dimmer with push buttons.
+thing-type.livisismarthome.ISR2.label = In Wall Smart Roller Shutter (ISR2)
+thing-type.livisismarthome.ISR2.description = An in wall rollershutter with two push buttons.
+thing-type.livisismarthome.ISS2.label = In Wall Smart Switch (ISS2)
+thing-type.livisismarthome.ISS2.description = An in wall switch with push buttons that can be switched on and off.
+thing-type.livisismarthome.PSD.label = Pluggable Smart Dimmer (PSD)
+thing-type.livisismarthome.PSD.description = A pluggable dimmer.
+thing-type.livisismarthome.PSS.label = Pluggable Smart Switch (PSS)
+thing-type.livisismarthome.PSS.description = A pluggable switch that can be switched on and off.
+thing-type.livisismarthome.PSSO.label = Pluggable Smart Switch Outdoor (PSSO)
+thing-type.livisismarthome.PSSO.description = A pluggable outdoor switch that can be switched on and off.
+thing-type.livisismarthome.RST.label = Radiator Mounted Smart Thermostat (RST)
+thing-type.livisismarthome.RST.description = A thermostat, that supports setting the temperature and measuring the current temperature and humidity.
+thing-type.livisismarthome.RST2.label = Radiator Mounted Smart Thermostat (RST2)
+thing-type.livisismarthome.RST2.description = A thermostat, that supports setting the temperature and measuring the current temperature and humidity (2 battery version since 2018)
+thing-type.livisismarthome.SmartMeter.label = Smart Meter
+thing-type.livisismarthome.SmartMeter.description = The Smart Meter from the PowerControl product.
+thing-type.livisismarthome.TwoWayMeter.label = Two-Way-Meter
+thing-type.livisismarthome.TwoWayMeter.description = The Two-Way-Meter from the PowerControlSolar product.
+thing-type.livisismarthome.VariableActuator.label = Variable Actuator
+thing-type.livisismarthome.VariableActuator.description = A variable from the LIVISI SmartHome System that can be switched on and off.
+thing-type.livisismarthome.WDS.label = Wall Mounted Door/Window Sensor (WDS)
+thing-type.livisismarthome.WDS.description = A window/door sensor, that provides the open/close state.
+thing-type.livisismarthome.WMD.label = Wall Mounted Motion Detector Indoor (WMD)
+thing-type.livisismarthome.WMD.description = A battery powered motion detector, that also measures luminance.
+thing-type.livisismarthome.WMDO.label = Wall Mounted Motion Detector Outdoor (WMDO)
+thing-type.livisismarthome.WMDO.description = A battery powered motion detector for outdoors, that also measures luminance.
+thing-type.livisismarthome.WRT.label = Wall Mounted Room Thermostat (WRT)
+thing-type.livisismarthome.WRT.description = A wall thermostat, that supports setting the temperature and measuring the current temperature and humidity.
+thing-type.livisismarthome.WSC2.label = Wall Mounted Smart Controller (WSC2)
+thing-type.livisismarthome.WSC2.description = A battery powered smart controller with two push buttons.
+thing-type.livisismarthome.WSD.label = Wall Mounted Smoke Detector (WSD)
+thing-type.livisismarthome.WSD.description = A battery powered smoke detector sensor with integrated alarm (first version).
+thing-type.livisismarthome.WSD2.label = Wall Mounted Smoke Detector (WSD2)
+thing-type.livisismarthome.WSD2.description = A battery powered smoke detector sensor with integrated alarm (2nd version with long-life battery).
+thing-type.livisismarthome.bridge.label = LIVISI SmartHome Controller
+thing-type.livisismarthome.bridge.description = The LIVISI SmartHome Controller (SHC) is the bridge for the LIVISI SmartHome System.
+
+# thing types config
+
+thing-type.config.livisismarthome.bridge.group.advanced.label = Advanced Configuration
+thing-type.config.livisismarthome.bridge.group.advanced.description = Advanced parameters, for special tweaking only.
+thing-type.config.livisismarthome.bridge.group.connection.label = Connection
+thing-type.config.livisismarthome.bridge.group.connection.description = Parameters for connecting to LIVISI SmartHome Controller (SHC)
+thing-type.config.livisismarthome.bridge.webSocketIdleTimeout.label = WebSocket Idle Timeout in Seconds
+thing-type.config.livisismarthome.bridge.webSocketIdleTimeout.description = The WebSocket is the connection to the LIVISI service that listens to status updates. If no data is received over the websocket connection for the given time, the websocket will reconnect. 0 will disable the idle timeout. Default is 900 seconds (15 minutes).
+thing-type.config.livisismarthome.config.id.label = ID
+thing-type.config.livisismarthome.config.id.description = The identifier uniquely identifies this device.
+
+# channel types
+channel-type.livisismarthome.absoluteEnergyConsumption.label = Total Consumption
+channel-type.livisismarthome.absoluteEnergyConsumption.description = The absolute Energy consumption
+channel-type.livisismarthome.alarmActuator.label = Alarm
+channel-type.livisismarthome.alarmActuator.description = Switches the alarm on/off
+channel-type.livisismarthome.booleanStateActuator.label = Switch
+channel-type.livisismarthome.booleanStateActuator.description = Switches the state on/off
+channel-type.livisismarthome.cpuUsage.label = CPU Usage
+channel-type.livisismarthome.cpuUsage.description = The CPU usage of SHC
+channel-type.livisismarthome.dimmerActuator.label = Dimmer
+channel-type.livisismarthome.dimmerActuator.description = Dimms the connected light (percent)
+channel-type.livisismarthome.diskUsage.label = Disk Usage
+channel-type.livisismarthome.diskUsage.description = The disk usage of SHC
+channel-type.livisismarthome.energyConsumptionDayEuro.label = Consumption Costs (day)
+channel-type.livisismarthome.energyConsumptionDayEuro.description = The energy consumption per day (Euro)
+channel-type.livisismarthome.energyConsumptionDayKwh.label = Consumption (day)
+channel-type.livisismarthome.energyConsumptionDayKwh.description = The energy consumption per day (kWh)
+channel-type.livisismarthome.energyConsumptionMonthEuro.label = Consumption Costs (month)
+channel-type.livisismarthome.energyConsumptionMonthEuro.description = The energy consumption per month (Euro)
+channel-type.livisismarthome.energyConsumptionMonthKwh.label = Consumption (month)
+channel-type.livisismarthome.energyConsumptionMonthKwh.description = The energy consumption per month (kWh)
+channel-type.livisismarthome.energyFeedDayEuro.label = Feed Income (day)
+channel-type.livisismarthome.energyFeedDayEuro.description = The energy feed per day (Euro)
+channel-type.livisismarthome.energyFeedDayKwh.label = Feed (day)
+channel-type.livisismarthome.energyFeedDayKwh.description = The energy feed per day (kWh)
+channel-type.livisismarthome.energyFeedMonthEuro.label = Feed Income (month)
+channel-type.livisismarthome.energyFeedMonthEuro.description = The energy feed per month (Euro)
+channel-type.livisismarthome.energyFeedMonthKwh.label = Feed (month)
+channel-type.livisismarthome.energyFeedMonthKwh.description = The energy feed per month (kWh)
+channel-type.livisismarthome.energyGenerationDayEuro.label = Generation Value (day)
+channel-type.livisismarthome.energyGenerationDayEuro.description = The energy generation per day (Euro)
+channel-type.livisismarthome.energyGenerationDayKwh.label = Generation (day)
+channel-type.livisismarthome.energyGenerationDayKwh.description = The energy generation per day (kWh)
+channel-type.livisismarthome.energyGenerationMonthEuro.label = Generation Value (month)
+channel-type.livisismarthome.energyGenerationMonthEuro.description = The energy generation per month (Euro)
+channel-type.livisismarthome.energyGenerationMonthKwh.label = Generation (month)
+channel-type.livisismarthome.energyGenerationMonthKwh.description = The energy generation per month (kWh)
+channel-type.livisismarthome.humiditySensorHumidity.label = Actual Humidity
+channel-type.livisismarthome.humiditySensorHumidity.description = Actual measured room humidity (percent)
+channel-type.livisismarthome.humiditySensorMoldWarning.label = Mold Warning
+channel-type.livisismarthome.humiditySensorMoldWarning.description = Active, if humidity is over a threshold (configured in LIVISI app)
+channel-type.livisismarthome.luminanceSensor.label = Luminance
+channel-type.livisismarthome.luminanceSensor.description = Shows the detected luminance (percent)
+channel-type.livisismarthome.memoryUsage.label = Memory Usage
+channel-type.livisismarthome.memoryUsage.description = The memory usage of SHC (percent)
+channel-type.livisismarthome.motionDetectionSensor.label = Motion Count
+channel-type.livisismarthome.motionDetectionSensor.description = The count of detected motions
+channel-type.livisismarthome.operationStatus.label = Operation Status
+channel-type.livisismarthome.operationStatus.description = The operation status of SHC-A
+channel-type.livisismarthome.powerConsumptionWatt.label = Current Power Consumption
+channel-type.livisismarthome.powerConsumptionWatt.description = The current power consumption (Watt)
+channel-type.livisismarthome.powerGenerationWatt.label = Current Power Generation
+channel-type.livisismarthome.powerGenerationWatt.description = The current power generation (Watt)
+channel-type.livisismarthome.pushButtonCounter.label = Button Pushed Count
+channel-type.livisismarthome.pushButtonCounter.description = The count of button pushes.
+channel-type.livisismarthome.rollerShutterActuator.label = Blinds Position
+channel-type.livisismarthome.rollerShutterActuator.description = Controls the blinds (percent)
+channel-type.livisismarthome.smokeDetectorSensor.label = Smoke
+channel-type.livisismarthome.smokeDetectorSensor.description = Shows if smoke was detected (on/off)
+channel-type.livisismarthome.switchActuator.label = Switch
+channel-type.livisismarthome.switchActuator.description = Switches the current (on/off)
+channel-type.livisismarthome.temperatureSensorFrostWarning.label = Frost Warning
+channel-type.livisismarthome.temperatureSensorFrostWarning.description = Warns, if temperature drop below a threshold (configured in LIVISI app)
+channel-type.livisismarthome.temperatureSensorTemperature.label = Actual Temperature
+channel-type.livisismarthome.temperatureSensorTemperature.description = Actual measured room temperature
+channel-type.livisismarthome.thermostatActuatorOperationMode.label = Operation Mode
+channel-type.livisismarthome.thermostatActuatorOperationMode.description = Thermostat operation mode (manual/auto)
+channel-type.livisismarthome.thermostatActuatorOperationMode.state.option.Auto = auto
+channel-type.livisismarthome.thermostatActuatorOperationMode.state.option.Manu = manual
+channel-type.livisismarthome.thermostatActuatorPointTemperature.label = Target Temperature
+channel-type.livisismarthome.thermostatActuatorPointTemperature.description = Thermostat target temperature (6C to 30C)
+channel-type.livisismarthome.thermostatActuatorWindowReductionActive.label = Window Reduction Active
+channel-type.livisismarthome.thermostatActuatorWindowReductionActive.description = Thermostat temperature reduced, if window is open.
+channel-type.livisismarthome.totalEnergyConsumption.label = Total Consumption
+channel-type.livisismarthome.totalEnergyConsumption.description = The total Energy consumption
+channel-type.livisismarthome.totalEnergyFed.label = Total Fed
+channel-type.livisismarthome.totalEnergyFed.description = The total Energy fed
+channel-type.livisismarthome.totalEnergyGeneration.label = Total Generation
+channel-type.livisismarthome.totalEnergyGeneration.description = The total Energy generation
+channel-type.livisismarthome.windowDoorSensor.label = Contact
+channel-type.livisismarthome.windowDoorSensor.description = Shows the open/close state
+
+# channel types config
+
+channel-type.config.livisismarthome.RollerShutterActuator.invert.label = Invert Position
+channel-type.config.livisismarthome.RollerShutterActuator.invert.description = By default the LIVISI value for OpenHAB is inverted so that "top" in LIVISI is also "top" in OpenHAB. If this is not desired, this inversion can be canceled again by activating this option.
diff --git a/bundles/org.openhab.binding.livisismarthome/src/main/resources/OH-INF/i18n/livisismarthome_de.properties b/bundles/org.openhab.binding.livisismarthome/src/main/resources/OH-INF/i18n/livisismarthome_de.properties
new file mode 100644
index 00000000000..1e7d3447073
--- /dev/null
+++ b/bundles/org.openhab.binding.livisismarthome/src/main/resources/OH-INF/i18n/livisismarthome_de.properties
@@ -0,0 +1,164 @@
+# binding
+
+binding.livisismarthome.name = LIVISI SmartHome Binding
+binding.livisismarthome.description = Das LIVISI SmartHome Binding verbindet dein LIVISI SmartHome System mit openHAB.
+
+# common messages
+
+error.connect = Konnte keine Verbindung zum LIVISI SmartHome - Service herstellen! Bitte berprfen Sie die Zugangsdaten.
+error.notReachable = Gert nicht erreichbar
+error.deviceNotFound = Das Gert wurde nicht in der LIVISI SmartHome Konfiguration gefunden. Wurde es entfernt?
+error.deviceIdUnknown = Unbekannte device id
+error.bridgeHandlerMissing = Das Bridge-Thing oder der Bridge-Handler wurden nicht gefunden. Ist die Bridge nicht am Gert konfiguriert?
+
+# thing types
+
+thing-type.livisismarthome.bridge.label = LIVISI SmartHome Zentrale
+thing-type.livisismarthome.bridge.description = Der LIVISI SmartHome Controller (SHC) ist die Zentrale fr das LIVISI SmartHome System.
+thing-type.livisismarthome.AnalogMeter.label = Analoger Zhler (Analog Meter)
+thing-type.livisismarthome.AnalogMeter.description = Ein konventioneller Zhler / Ferraris-Zhler
+thing-type.livisismarthome.BT-PSS.label = Zwischenstecker (Bluetooth) (BT-PSS)
+thing-type.livisismarthome.BT-PSS.description = Ein Zwischenstecker von Medion zum Schalten mit Energieverbrauchsmessung
+thing-type.livisismarthome.BRC8.label = Funkfernbedienung (BRC8)
+thing-type.livisismarthome.BRC8.description = Eine batteriebetriebene Fernbedienung mit 8 Tasten.
+thing-type.livisismarthome.GenerationMeter.label = Erzeugungs-Zhler (Generation Meter)
+thing-type.livisismarthome.GenerationMeter.description = Ein Erzeugungs-Zhler (fr PV-Anlagen)
+thing-type.livisismarthome.ISC2.label = Unterputzsender (ISC2)
+thing-type.livisismarthome.ISC2.description = Ein batteriebetriebener Unterputzsender mit zwei Tasten.
+thing-type.livisismarthome.ISD2.label = Unterputzdimmer (ISD2)
+thing-type.livisismarthome.ISD2.description = Ein Unterputzdimmer mit zwei Tasten.
+thing-type.livisismarthome.ISR2.label = Unterputz Rollladensteuerung (ISR2)
+thing-type.livisismarthome.ISR2.description = Eine Unterputz Rollladensteuerung mit zwei Tasten.
+thing-type.livisismarthome.ISS2.label = Unterputz Lichtschalter (ISS2)
+thing-type.livisismarthome.ISS2.description = Ein Unterputz Lichtschalter mit zwei Tasten.
+thing-type.livisismarthome.PSD.label = Zwischenstecker dimmbar (PSD)
+thing-type.livisismarthome.PSD.description = Ein dimmbarer Zwischenstecker.
+thing-type.livisismarthome.PSS.label = Zwischenstecker innen (PSS)
+thing-type.livisismarthome.PSS.description = Ein Zwischenstecker fr innen, der ein- und ausgeschaltet werden kann.
+thing-type.livisismarthome.PSSO.label = Zwischenstecker auen (PSSO)
+thing-type.livisismarthome.PSSO.description = Ein Zwischenstecker fr den Auenbereich, der ein- und ausgeschaltet werden kann.
+thing-type.livisismarthome.RST.label = Heizkrperthermostat (RST)
+thing-type.livisismarthome.RST.description = Das Thermostat untersttzt das Einstellen der Temperatur sowie das Messen der aktuellen Temperatur und Luftfeuchtigkeit.
+thing-type.livisismarthome.RST2.label = Heizkrperthermostat (RST2)
+thing-type.livisismarthome.RST2.description = Das Thermostat untersttzt das Einstellen der Temperatur sowie das Messen der aktuellen Temperatur und Luftfeuchtigkeit (Version mit 2 Batterien seit 2018).
+thing-type.livisismarthome.SmartMeter.label = Elektronischer Zhler (Smart Meter)
+thing-type.livisismarthome.SmartMeter.description = Ein elektronischer Zhler
+thing-type.livisismarthome.TwoWayMeter.label = Zweiwege-Zhler (Two-Way-Meter)
+thing-type.livisismarthome.TwoWayMeter.description = Ein Zweiwege-Zhler fr PV-Anlagen mit Einspeisung ins ffentliche Netz
+thing-type.livisismarthome.VariableActuator.label = Zustandsvariable
+thing-type.livisismarthome.VariableActuator.description = Eine Zustandsvariable des LIVISI SmartHome Systems, die ein- oder ausgeschaltet werden kann.
+thing-type.livisismarthome.WDS.label = Fenster-/Trkontakt (WDS)
+thing-type.livisismarthome.WDS.description = Ein Fenster-/Trkontakt, der offen oder geschlossen anzeigt.
+thing-type.livisismarthome.WMD.label = Bewegungsmelder (WMD)
+thing-type.livisismarthome.WMD.description = Ein batteriebetriebener Bewegungsmelder, der zustzlich die Helligkeit misst.
+thing-type.livisismarthome.WMDO.label = Bewegungsmelder auen (WMDO)
+thing-type.livisismarthome.WMDO.description = Ein batteriebetriebener Bewegungsmelder fr den Auenbereich, der zustzlich die Helligkeit misst.
+thing-type.livisismarthome.WRT.label = Raumthermostat (WRT)
+thing-type.livisismarthome.WRT.description = Ein Raumthermostat zur Wandbefestigung, das das Einstellen der Temperatur sowie das Messen der aktuellen Temperatur und Luftfeuchtigkeit untersttzt.
+thing-type.livisismarthome.WSC2.label = Wandsender (WSC2)
+thing-type.livisismarthome.WSC2.description = Ein batteriebetriebener Wandsender mit zwei Tasten.
+thing-type.livisismarthome.WSD.label = Rauchmelder (WSD)
+thing-type.livisismarthome.WSD.description = Ein batteriebetriebener Rauchmelder mit integriertem Alarm (erste Version).
+thing-type.livisismarthome.WSD2.label = Rauchmelder (WSD2)
+thing-type.livisismarthome.WSD2.description = Ein batteriebetriebener Rauchmelder mit integriertem Alarm (zweite Version mit long-life Batterie).
+
+# thing types config
+
+thing-type.config.livisismarthome.bridge.group.advanced.label = Erweiterte Konfiguration
+thing-type.config.livisismarthome.bridge.group.advanced.description = Erweiterte Parameter fr spezielle Anpassungen.
+thing-type.config.livisismarthome.bridge.group.connection.label = Verbindung
+thing-type.config.livisismarthome.bridge.group.connection.description = Parameter zur Verbindung mit dem LIVISI SmartHome Controller (SHC)
+thing-type.config.livisismarthome.bridge.webSocketIdleTimeout.label = WebSocket idle timeout in Sekunden
+thing-type.config.livisismarthome.bridge.webSocketIdleTimeout.description = Der WebSocket hlt die Verbindung zum LIVISI Webservice und wartet auf Statusaktualisierungen. Wenn fr die angegebene Dauer keine Daten ber den WebSocket empfangen werden, wird die Verbindung neu aufgebaut. 0 deaktiviert den idle timeout. Standard ist 900 Sekunden (15 Minuten).
+thing-type.config.livisismarthome.bridge.host.label = Host
+thing-type.config.livisismarthome.bridge.host.description = Der Hostname oder die lokale IP-Adresse der LIVISI SmartHome Zentrale (SHC)
+thing-type.config.livisismarthome.bridge.password.label = Passwort
+thing-type.config.livisismarthome.bridge.password.description = Das Passwort fr den Zugriff auf die lokale API der LIVISI Smarthome Zentrale (SHC)
+thing-type.config.livisismarthome.config.id.label = ID
+thing-type.config.livisismarthome.config.id.description = ID zur eindeutigen Gerte-Identifizierung
+
+# channel types
+
+channel-type.livisismarthome.absoluteEnergyConsumption.label = Gesamtverbrauch
+channel-type.livisismarthome.absoluteEnergyConsumption.description = Gesamtenergieverbrauch (kWh)
+channel-type.livisismarthome.alarmActuator.label = Alarmton
+channel-type.livisismarthome.alarmActuator.description = Schaltet den Alarmton (an/aus)
+channel-type.livisismarthome.booleanStateActuator.label = Status
+channel-type.livisismarthome.booleanStateActuator.description = Schaltet den Zustand (an/aus)
+channel-type.livisismarthome.cpuUsage.label = CPU-Last
+channel-type.livisismarthome.cpuUsage.description = CPU-Last des SHC (in Prozent)
+channel-type.livisismarthome.dimmerActuator.label = Dimmer
+channel-type.livisismarthome.dimmerActuator.description = Dimmt das verbundene Licht (in Prozent)
+channel-type.livisismarthome.diskUsage.label = Festplatten-Belegung
+channel-type.livisismarthome.diskUsage.description = Festplatte des SHC belegt (in Prozent)
+channel-type.livisismarthome.energyConsumptionDayEuro.label = Verbrauchkosten pro Tag
+channel-type.livisismarthome.energyConsumptionDayEuro.description = Energieverbrauchskosten pro Tag (Euro)
+channel-type.livisismarthome.energyConsumptionDayKwh.label = Verbrauch pro Tag
+channel-type.livisismarthome.energyConsumptionDayKwh.description = Energieverbrauch pro Tag (kWh)
+channel-type.livisismarthome.energyConsumptionMonthEuro.label = Verbrauchskosten pro Monat
+channel-type.livisismarthome.energyConsumptionMonthEuro.description = Energieverbrauchskosten pro Monat (Euro)
+channel-type.livisismarthome.energyConsumptionMonthKwh.label = Verbrauch pro Monat
+channel-type.livisismarthome.energyConsumptionMonthKwh.description = Energieverbrauch pro Monat (kWh)
+channel-type.livisismarthome.energyFeedDayEuro.label = Einspeise-Vergtung pro Tag
+channel-type.livisismarthome.energyFeedDayEuro.description = Einspeise-Vergtung pro Tag (Euro)
+channel-type.livisismarthome.energyFeedDayKwh.label = Einspeisung pro Tag
+channel-type.livisismarthome.energyFeedDayKwh.description = Energie-Einspeisung pro Tag (kWh)
+channel-type.livisismarthome.energyFeedMonthEuro.label = Einspeise-Vergtung pro Monat
+channel-type.livisismarthome.energyFeedMonthEuro.description = Einspeise-Vergtung pro Monat (Euro)
+channel-type.livisismarthome.energyFeedMonthKwh.label = Einspeisung pro Monat
+channel-type.livisismarthome.energyFeedMonthKwh.description = Energie-Einspeisung pro Monat (kWh)
+channel-type.livisismarthome.energyGenerationDayEuro.label = Erzeugungswert pro Tag
+channel-type.livisismarthome.energyGenerationDayEuro.description = Energie-Erzeugungswert pro Tag (Euro)
+channel-type.livisismarthome.energyGenerationDayKwh.label = Erzeugung pro Tag
+channel-type.livisismarthome.energyGenerationDayKwh.description = Energie-Erzeugung pro Tag (kWh)
+channel-type.livisismarthome.energyGenerationMonthEuro.label = Erzeugungswert pro Monat
+channel-type.livisismarthome.energyGenerationMonthEuro.description = Energie-Erzeugungswert pro Monat (Euro)
+channel-type.livisismarthome.energyGenerationMonthKwh.label = Erzeugung pro Monat
+channel-type.livisismarthome.energyGenerationMonthKwh.description = Energie-Erzeugung pro Monat (kWh)
+channel-type.livisismarthome.humiditySensorHumidity.label = Luftfeuchtigkeit
+channel-type.livisismarthome.humiditySensorHumidity.description = Aktuell gemessene Luftfeuchtigkeit (in Prozent)
+channel-type.livisismarthome.humiditySensorMoldWarning.label = Schimmelwarnung
+channel-type.livisismarthome.humiditySensorMoldWarning.description = Warnt bei berschreiten der in der LIVISI-App konfigurierten maximalen Feuchtigkeit (an/aus)
+channel-type.livisismarthome.luminanceSensor.label = Helligkeit
+channel-type.livisismarthome.luminanceSensor.description = Gemessene Helligkeit (in Prozent)
+channel-type.livisismarthome.memoryUsage.label = Hauptspeicher-Belegung
+channel-type.livisismarthome.memoryUsage.description = Hauptspeicher des SHC belegt (in Prozent)
+channel-type.livisismarthome.motionDetectionSensor.label = Bewegungszhler
+channel-type.livisismarthome.motionDetectionSensor.description = Anzahl der erkannten Bewegungen
+channel-type.livisismarthome.operationStatus.label = Betriebssstatus
+channel-type.livisismarthome.operationStatus.description = Betriebsstatus des SHC
+channel-type.livisismarthome.powerConsumptionWatt.label = Aktueller Verbrauch
+channel-type.livisismarthome.powerConsumptionWatt.description = Aktueller Energieverbrauch (Watt)
+channel-type.livisismarthome.powerGenerationWatt.label = Aktuelle Erzeugung
+channel-type.livisismarthome.powerGenerationWatt.description = Aktuelle Energieerzeuzung (Watt)
+channel-type.livisismarthome.pushButtonCounter.label = Tastendruck-Zhler
+channel-type.livisismarthome.pushButtonCounter.description = Anzahl der Tastenbettigungen
+channel-type.livisismarthome.rollerShutterActuator.label = Rollladenposition
+channel-type.livisismarthome.rollerShutterActuator.description = Steuert die Rollladen (in Prozent)
+channel-type.livisismarthome.smokeDetectorSensor.label = Rauchalarm
+channel-type.livisismarthome.smokeDetectorSensor.description = Meldet bei Raucherkennung (an/aus)
+channel-type.livisismarthome.switchActuator.label = Schalter
+channel-type.livisismarthome.switchActuator.description = Schaltet den Strom (an/aus)
+channel-type.livisismarthome.thermostatActuatorOperationMode.label = Betriebsmodus
+channel-type.livisismarthome.thermostatActuatorOperationMode.description = Betriebsmodus des Thermostats (manuell/automatisch)
+channel-type.livisismarthome.thermostatActuatorPointTemperature.label = Solltemperatur
+channel-type.livisismarthome.thermostatActuatorPointTemperature.description = Temperaturvorgabe fr das Thermostat (6C - 30C)
+channel-type.livisismarthome.thermostatActuatorWindowReductionActive.label = Fensterabsenkung aktiv
+channel-type.livisismarthome.thermostatActuatorWindowReductionActive.description = Absenkung der Solltemperatur bei offenem Fenster (an/aus)
+channel-type.livisismarthome.temperatureSensorFrostWarning.label = Frostwarnung
+channel-type.livisismarthome.temperatureSensorFrostWarning.description = Warnt beim Unterschreiten der in der LIVISI-App konfigurierten Mindesttemperatur (an/aus)
+channel-type.livisismarthome.temperatureSensorTemperature.label = Aktuelle Temperatur
+channel-type.livisismarthome.temperatureSensorTemperature.description = Aktuell gemessene Raumtemperatur (C)
+channel-type.livisismarthome.totalEnergyConsumption.label = Gesamtverbrauch
+channel-type.livisismarthome.totalEnergyConsumption.description = Energie-Gesamtverbrauch (kWh)
+channel-type.livisismarthome.totalEnergyFed.label = Gesamteinspeisung
+channel-type.livisismarthome.totalEnergyFed.description = Energie-Gesamteinspeisung (kWh)
+channel-type.livisismarthome.totalEnergyGeneration.label = Gesamterzeugung
+channel-type.livisismarthome.totalEnergyGeneration.description = Energie-Gesamterzeugung (kWh)
+channel-type.livisismarthome.windowDoorSensor.label = Tr/Fensterstatus
+channel-type.livisismarthome.windowDoorSensor.description = Zeigt den Status (offen/geschlossen)
+
+# channel types config
+
+channel-type.config.livisismarthome.RollerShutterActuator.invert.label = Invertierte Position
+channel-type.config.livisismarthome.RollerShutterActuator.invert.description = Standardmig wird der LIVISI-Wert fr OpenHAB invertiert, damit "Oben" in LIVISI auch "Oben" in OpenHAB ist. Wenn das nicht gewnscht wird, kann diese Invertierung durch Aktivieren dieser Option wieder aufgehoben werden.
diff --git a/bundles/org.openhab.binding.livisismarthome/src/main/resources/OH-INF/thing/AnalogMeter.xml b/bundles/org.openhab.binding.livisismarthome/src/main/resources/OH-INF/thing/AnalogMeter.xml
new file mode 100644
index 00000000000..f881f03d1ea
--- /dev/null
+++ b/bundles/org.openhab.binding.livisismarthome/src/main/resources/OH-INF/thing/AnalogMeter.xml
@@ -0,0 +1,27 @@
+
+
+
+
+
+
+
+
+
+ The Analog Meter from the LIVISI EnergyControl product.
+
+
+
+
+
+
+
+
+
+ id
+
+
+
+
diff --git a/bundles/org.openhab.binding.livisismarthome/src/main/resources/OH-INF/thing/BRC8.xml b/bundles/org.openhab.binding.livisismarthome/src/main/resources/OH-INF/thing/BRC8.xml
new file mode 100644
index 00000000000..2836814d44e
--- /dev/null
+++ b/bundles/org.openhab.binding.livisismarthome/src/main/resources/OH-INF/thing/BRC8.xml
@@ -0,0 +1,39 @@
+
+
+
+
+
+
+
+
+
+ A battery powered remote controller with eight push buttons.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ id
+
+
+
+
diff --git a/bundles/org.openhab.binding.livisismarthome/src/main/resources/OH-INF/thing/BT-PSS.xml b/bundles/org.openhab.binding.livisismarthome/src/main/resources/OH-INF/thing/BT-PSS.xml
new file mode 100644
index 00000000000..4f9a8c898bd
--- /dev/null
+++ b/bundles/org.openhab.binding.livisismarthome/src/main/resources/OH-INF/thing/BT-PSS.xml
@@ -0,0 +1,23 @@
+
+
+
+
+
+
+
+
+
+ A pluggable switch that can be switched on and off and which can measure the current energy consumption.
+
+
+
+
+
+ id
+
+
+
+
diff --git a/bundles/org.openhab.binding.livisismarthome/src/main/resources/OH-INF/thing/GenerationMeter.xml b/bundles/org.openhab.binding.livisismarthome/src/main/resources/OH-INF/thing/GenerationMeter.xml
new file mode 100644
index 00000000000..73478ca04bc
--- /dev/null
+++ b/bundles/org.openhab.binding.livisismarthome/src/main/resources/OH-INF/thing/GenerationMeter.xml
@@ -0,0 +1,28 @@
+
+
+
+
+
+
+
+
+
+ The Generation Meter from the LIVISI PowerControlSolar product, that measures the generated energy.
+
+
+
+
+
+
+
+
+
+
+ id
+
+
+
+
diff --git a/bundles/org.openhab.binding.livisismarthome/src/main/resources/OH-INF/thing/ISC2.xml b/bundles/org.openhab.binding.livisismarthome/src/main/resources/OH-INF/thing/ISC2.xml
new file mode 100644
index 00000000000..27e2e27da1f
--- /dev/null
+++ b/bundles/org.openhab.binding.livisismarthome/src/main/resources/OH-INF/thing/ISC2.xml
@@ -0,0 +1,27 @@
+
+
+
+
+
+
+
+
+
+ A smart controller with two push buttons.
+
+
+
+
+
+
+
+
+
+ id
+
+
+
+
diff --git a/bundles/org.openhab.binding.livisismarthome/src/main/resources/OH-INF/thing/ISD2.xml b/bundles/org.openhab.binding.livisismarthome/src/main/resources/OH-INF/thing/ISD2.xml
new file mode 100644
index 00000000000..ec742051b46
--- /dev/null
+++ b/bundles/org.openhab.binding.livisismarthome/src/main/resources/OH-INF/thing/ISD2.xml
@@ -0,0 +1,27 @@
+
+
+
+
+
+
+
+
+
+ An in wall dimmer with push buttons.
+
+
+
+
+
+
+
+
+
+ id
+
+
+
+
diff --git a/bundles/org.openhab.binding.livisismarthome/src/main/resources/OH-INF/thing/ISR2.xml b/bundles/org.openhab.binding.livisismarthome/src/main/resources/OH-INF/thing/ISR2.xml
new file mode 100644
index 00000000000..afe601a9459
--- /dev/null
+++ b/bundles/org.openhab.binding.livisismarthome/src/main/resources/OH-INF/thing/ISR2.xml
@@ -0,0 +1,27 @@
+
+
+
+
+
+
+
+
+
+ An in wall rollershutter with two push buttons.
+
+
+
+
+
+
+
+
+
+ id
+
+
+
+
diff --git a/bundles/org.openhab.binding.livisismarthome/src/main/resources/OH-INF/thing/ISS2.xml b/bundles/org.openhab.binding.livisismarthome/src/main/resources/OH-INF/thing/ISS2.xml
new file mode 100644
index 00000000000..bdbbc53f6d2
--- /dev/null
+++ b/bundles/org.openhab.binding.livisismarthome/src/main/resources/OH-INF/thing/ISS2.xml
@@ -0,0 +1,27 @@
+
+
+
+
+
+
+
+
+
+ An in wall switch with push buttons that can be switched on and off.
+
+
+
+
+
+
+
+
+
+ id
+
+
+
+
diff --git a/bundles/org.openhab.binding.livisismarthome/src/main/resources/OH-INF/thing/PSD.xml b/bundles/org.openhab.binding.livisismarthome/src/main/resources/OH-INF/thing/PSD.xml
new file mode 100644
index 00000000000..68a37fc9603
--- /dev/null
+++ b/bundles/org.openhab.binding.livisismarthome/src/main/resources/OH-INF/thing/PSD.xml
@@ -0,0 +1,23 @@
+
+
+
+
+
+
+
+
+
+ A pluggable dimmer.
+
+
+
+
+
+ id
+
+
+
+
diff --git a/bundles/org.openhab.binding.livisismarthome/src/main/resources/OH-INF/thing/PSS.xml b/bundles/org.openhab.binding.livisismarthome/src/main/resources/OH-INF/thing/PSS.xml
new file mode 100644
index 00000000000..cd992452d35
--- /dev/null
+++ b/bundles/org.openhab.binding.livisismarthome/src/main/resources/OH-INF/thing/PSS.xml
@@ -0,0 +1,23 @@
+
+
+
+
+
+
+
+
+
+ A pluggable switch that can be switched on and off.
+
+
+
+
+
+ id
+
+
+
+
diff --git a/bundles/org.openhab.binding.livisismarthome/src/main/resources/OH-INF/thing/PSSO.xml b/bundles/org.openhab.binding.livisismarthome/src/main/resources/OH-INF/thing/PSSO.xml
new file mode 100644
index 00000000000..9c872978604
--- /dev/null
+++ b/bundles/org.openhab.binding.livisismarthome/src/main/resources/OH-INF/thing/PSSO.xml
@@ -0,0 +1,23 @@
+
+
+
+
+
+
+
+
+
+ A pluggable outdoor switch that can be switched on and off.
+
+
+
+
+
+ id
+
+
+
+
diff --git a/bundles/org.openhab.binding.livisismarthome/src/main/resources/OH-INF/thing/RST.xml b/bundles/org.openhab.binding.livisismarthome/src/main/resources/OH-INF/thing/RST.xml
new file mode 100644
index 00000000000..0d37b97192a
--- /dev/null
+++ b/bundles/org.openhab.binding.livisismarthome/src/main/resources/OH-INF/thing/RST.xml
@@ -0,0 +1,30 @@
+
+
+
+
+
+
+
+
+
+ A thermostat, that supports setting the temperature and measuring the current temperature and humidity.
+
+
+
+
+
+
+
+
+
+
+
+
+ id
+
+
+
+
diff --git a/bundles/org.openhab.binding.livisismarthome/src/main/resources/OH-INF/thing/RST2.xml b/bundles/org.openhab.binding.livisismarthome/src/main/resources/OH-INF/thing/RST2.xml
new file mode 100644
index 00000000000..f49e8c99beb
--- /dev/null
+++ b/bundles/org.openhab.binding.livisismarthome/src/main/resources/OH-INF/thing/RST2.xml
@@ -0,0 +1,31 @@
+
+
+
+
+
+
+
+
+
+ A thermostat, that supports setting the temperature and measuring the current temperature and humidity (2
+ battery version since 2018)
+
+
+
+
+
+
+
+
+
+
+
+
+ id
+
+
+
+
diff --git a/bundles/org.openhab.binding.livisismarthome/src/main/resources/OH-INF/thing/SmartMeter.xml b/bundles/org.openhab.binding.livisismarthome/src/main/resources/OH-INF/thing/SmartMeter.xml
new file mode 100644
index 00000000000..4a96564abe4
--- /dev/null
+++ b/bundles/org.openhab.binding.livisismarthome/src/main/resources/OH-INF/thing/SmartMeter.xml
@@ -0,0 +1,28 @@
+
+
+
+
+
+
+
+
+
+ The Smart Meter from the LIVISI PowerControl product.
+
+
+
+
+
+
+
+
+
+
+ id
+
+
+
+
diff --git a/bundles/org.openhab.binding.livisismarthome/src/main/resources/OH-INF/thing/TwoWayMeter.xml b/bundles/org.openhab.binding.livisismarthome/src/main/resources/OH-INF/thing/TwoWayMeter.xml
new file mode 100644
index 00000000000..300b6080614
--- /dev/null
+++ b/bundles/org.openhab.binding.livisismarthome/src/main/resources/OH-INF/thing/TwoWayMeter.xml
@@ -0,0 +1,33 @@
+
+
+
+
+
+
+
+
+
+ The Two-Way-Meter from the LIVISI PowerControlSolar product.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ id
+
+
+
+
diff --git a/bundles/org.openhab.binding.livisismarthome/src/main/resources/OH-INF/thing/VariableActuator.xml b/bundles/org.openhab.binding.livisismarthome/src/main/resources/OH-INF/thing/VariableActuator.xml
new file mode 100644
index 00000000000..95ff678ad8f
--- /dev/null
+++ b/bundles/org.openhab.binding.livisismarthome/src/main/resources/OH-INF/thing/VariableActuator.xml
@@ -0,0 +1,23 @@
+
+
+
+
+
+
+
+
+
+ A variable from the LIVISI SmartHome system that can be switched on and off.
+
+
+
+
+
+ id
+
+
+
+
diff --git a/bundles/org.openhab.binding.livisismarthome/src/main/resources/OH-INF/thing/WDS.xml b/bundles/org.openhab.binding.livisismarthome/src/main/resources/OH-INF/thing/WDS.xml
new file mode 100644
index 00000000000..bbefd257aab
--- /dev/null
+++ b/bundles/org.openhab.binding.livisismarthome/src/main/resources/OH-INF/thing/WDS.xml
@@ -0,0 +1,24 @@
+
+
+
+
+
+
+
+
+
+ A window/door sensor, that provides the open/close state.
+
+
+
+
+
+
+ id
+
+
+
+
diff --git a/bundles/org.openhab.binding.livisismarthome/src/main/resources/OH-INF/thing/WMD.xml b/bundles/org.openhab.binding.livisismarthome/src/main/resources/OH-INF/thing/WMD.xml
new file mode 100644
index 00000000000..1d812f89415
--- /dev/null
+++ b/bundles/org.openhab.binding.livisismarthome/src/main/resources/OH-INF/thing/WMD.xml
@@ -0,0 +1,25 @@
+
+
+
+
+
+
+
+
+
+ A battery powered motion detector, that also measures luminance.
+
+
+
+
+
+
+
+ id
+
+
+
+
diff --git a/bundles/org.openhab.binding.livisismarthome/src/main/resources/OH-INF/thing/WMDO.xml b/bundles/org.openhab.binding.livisismarthome/src/main/resources/OH-INF/thing/WMDO.xml
new file mode 100644
index 00000000000..7e62d85ab3a
--- /dev/null
+++ b/bundles/org.openhab.binding.livisismarthome/src/main/resources/OH-INF/thing/WMDO.xml
@@ -0,0 +1,25 @@
+
+
+
+
+
+
+
+
+
+ A battery powered motion detector for outdoors, that also measures luminance.
+
+
+
+
+
+
+
+ id
+
+
+
+
diff --git a/bundles/org.openhab.binding.livisismarthome/src/main/resources/OH-INF/thing/WRT.xml b/bundles/org.openhab.binding.livisismarthome/src/main/resources/OH-INF/thing/WRT.xml
new file mode 100644
index 00000000000..f36e910e4ed
--- /dev/null
+++ b/bundles/org.openhab.binding.livisismarthome/src/main/resources/OH-INF/thing/WRT.xml
@@ -0,0 +1,31 @@
+
+
+
+
+
+
+
+
+
+ A wall thermostat, that supports setting the temperature and measuring the current temperature and
+ humidity.
+
+
+
+
+
+
+
+
+
+
+
+
+ id
+
+
+
+
diff --git a/bundles/org.openhab.binding.livisismarthome/src/main/resources/OH-INF/thing/WSC2.xml b/bundles/org.openhab.binding.livisismarthome/src/main/resources/OH-INF/thing/WSC2.xml
new file mode 100644
index 00000000000..51e32ed894f
--- /dev/null
+++ b/bundles/org.openhab.binding.livisismarthome/src/main/resources/OH-INF/thing/WSC2.xml
@@ -0,0 +1,27 @@
+
+
+
+
+
+
+
+
+
+ A battery powered smart controller with two push buttons.
+
+
+
+
+
+
+
+
+
+ id
+
+
+
+
diff --git a/bundles/org.openhab.binding.livisismarthome/src/main/resources/OH-INF/thing/WSD.xml b/bundles/org.openhab.binding.livisismarthome/src/main/resources/OH-INF/thing/WSD.xml
new file mode 100644
index 00000000000..04e89cecfd1
--- /dev/null
+++ b/bundles/org.openhab.binding.livisismarthome/src/main/resources/OH-INF/thing/WSD.xml
@@ -0,0 +1,25 @@
+
+
+
+
+
+
+
+
+
+ A battery powered smoke detector sensor with integrated alarm (first version).
+
+
+
+
+
+
+
+ id
+
+
+
+
diff --git a/bundles/org.openhab.binding.livisismarthome/src/main/resources/OH-INF/thing/WSD2.xml b/bundles/org.openhab.binding.livisismarthome/src/main/resources/OH-INF/thing/WSD2.xml
new file mode 100644
index 00000000000..ba87f0e0182
--- /dev/null
+++ b/bundles/org.openhab.binding.livisismarthome/src/main/resources/OH-INF/thing/WSD2.xml
@@ -0,0 +1,25 @@
+
+
+
+
+
+
+
+
+
+ A battery powered smoke detector sensor with integrated alarm (2nd version with long-life battery).
+
+
+
+
+
+
+
+ id
+
+
+
+
diff --git a/bundles/org.openhab.binding.livisismarthome/src/main/resources/OH-INF/thing/bridge.xml b/bundles/org.openhab.binding.livisismarthome/src/main/resources/OH-INF/thing/bridge.xml
new file mode 100644
index 00000000000..27f5281d184
--- /dev/null
+++ b/bundles/org.openhab.binding.livisismarthome/src/main/resources/OH-INF/thing/bridge.xml
@@ -0,0 +1,52 @@
+
+
+
+
+
+ The LIVISI SmartHome Controller (SHC) is the bridge for the LIVISI SmartHome System.
+
+
+
+
+
+
+
+
+ id
+
+
+
+
+ Parameters for connecting to LIVISI SmartHome Controller (SHC)
+
+
+
+ Advanced parameters, for special tweaking only.
+ true
+
+
+
+
+ The hostname or local IP address of the LIVISI SmartHome Controller (SHC)
+
+
+
+ Password for the local API of LIVISI SmartHome
+ password
+
+
+ seconds
+
+ The WebSocket is the connection to the LIVISI service that listens to status updates. If no data is
+ received over the WebSocket connection for the given time, the WebSocket will reconnect. 0 will disable the idle
+ timeout. Default is 900 seconds (15 minutes).
+ 900
+ true
+
+
+
+
+
diff --git a/bundles/org.openhab.binding.livisismarthome/src/main/resources/OH-INF/thing/channels.xml b/bundles/org.openhab.binding.livisismarthome/src/main/resources/OH-INF/thing/channels.xml
new file mode 100644
index 00000000000..f3b5da03673
--- /dev/null
+++ b/bundles/org.openhab.binding.livisismarthome/src/main/resources/OH-INF/thing/channels.xml
@@ -0,0 +1,368 @@
+
+
+
+
+
+ Switch
+
+ Switches the current on/off
+ Switch
+
+
+
+
+ Dimmer
+
+ Dims the connected light
+ Lightbulb
+
+
+
+
+ Rollershutter
+
+ Controls the blinds
+ Blinds
+
+
+
+ When invert is true than 0 on LIVISI is UP and 100 is DOWN
+
+
+
+
+
+
+ Switch
+
+ Switches the state on/off
+ Switch
+
+
+
+
+ Number:Temperature
+
+ Thermostat target temperature
+ Temperature
+
+ Setpoint
+ Temperature
+
+
+
+
+ String
+
+ Thermostat operation mode (manual/auto)
+ Settings
+
+
+
+
+
+
+
+
+ Switch
+
+ Thermostat temperature reduced, if window is open.
+ Temperature
+
+
+
+
+
+ Number:Temperature
+
+ Actual measured room temperature
+ Temperature
+
+ Measurement
+ Temperature
+
+
+
+
+ Switch
+
+ Warns, if temperature drop below a threshold (configured in LIVISI app)
+ Temperature
+
+
+
+
+
+ Number:Dimensionless
+
+ Actual measured room humidity
+ Humidity
+
+ Measurement
+ Humidity
+
+
+
+
+ Switch
+
+ Active, if humidity is over a threshold (configured in LIVISI app)
+ Humidity
+
+
+
+
+
+ Contact
+
+ Shows the open/close state
+ Contact
+
+
+
+
+
+ Switch
+
+ Shows if smoke was detected
+ Smoke
+
+
+
+
+
+ Switch
+
+ Switches the alarm on/off
+ Alarm
+
+
+
+
+ Number
+
+ The count of detected motions
+ Motion
+
+
+
+
+
+ Number:Dimensionless
+
+ Shows the detected luminance in percent
+ Sun
+
+
+
+
+
+ Number
+
+ The count of button pushes.
+ Count
+
+
+
+
+
+ Number:Energy
+
+ The energy consumption per month in kWh
+ Energy
+
+
+
+
+
+ Number:Energy
+
+ The absolute energy consumption
+ Energy
+
+
+
+
+
+ Number
+
+ The energy consumption per month in Euro
+ Energy
+
+
+
+
+
+ Number
+
+ The energy consumption per day in Euro
+ Energy
+
+
+
+
+
+ Number:Energy
+
+ The energy consumption per day in kWh
+ Energy
+
+
+
+
+
+ Number:Power
+
+ The current power consumption in Watt
+ Energy
+
+
+
+
+
+ Number:Energy
+
+ The energy generation per month in kWh
+ Energy
+
+
+
+
+
+ Number:Energy
+
+ The total energy generation
+ Energy
+
+
+
+
+
+ Number
+
+ The energy generation per month in Euro
+ Energy
+
+
+
+
+
+ Number
+
+ The energy generation per day in Euro
+ Energy
+
+
+
+
+
+ Number:Energy
+
+ The energy generation per day in kWh
+ Energy
+
+
+
+
+
+ Number:Power
+
+ The current power generation in Watt
+ Energy
+
+
+
+
+
+ Number:Energy
+
+ The total energy consumption
+ Energy
+
+
+
+
+
+ Number:Energy
+
+ The energy feed per month in kWh
+ Energy
+
+
+
+
+
+ Number:Energy
+
+ The total energy fed
+ Energy
+
+
+
+
+
+ Number
+
+ The energy feed per month in Euro
+ Energy
+
+
+
+
+
+ Number
+
+ The energy feed per day in Euro
+ Energy
+
+
+
+
+
+ Number:Energy
+
+ The energy feed per day in kWh
+ Energy
+
+
+
+
+
+ Number:Dimensionless
+
+ The CPU usage of the SHC in percent
+ Line
+
+
+
+
+
+ Number:Dimensionless
+
+ The disk usage of the SHC
+ Line
+
+
+
+
+
+ Number:Dimensionless
+
+ The memory usage of the SHC in percent
+ Line
+
+
+
+
+
+ String
+
+ The operation status of SHC-A
+ Settings
+
+
+
+
diff --git a/bundles/org.openhab.binding.livisismarthome/src/test/java/org/openhab/binding/livisismarthome/internal/LivisiWebSocketTest.java b/bundles/org.openhab.binding.livisismarthome/src/test/java/org/openhab/binding/livisismarthome/internal/LivisiWebSocketTest.java
new file mode 100644
index 00000000000..04a593f9f27
--- /dev/null
+++ b/bundles/org.openhab.binding.livisismarthome/src/test/java/org/openhab/binding/livisismarthome/internal/LivisiWebSocketTest.java
@@ -0,0 +1,259 @@
+/**
+ * Copyright (c) 2010-2022 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.livisismarthome.internal;
+
+import static org.junit.jupiter.api.Assertions.*;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.*;
+
+import java.net.URI;
+import java.util.concurrent.Future;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.eclipse.jetty.client.HttpClient;
+import org.eclipse.jetty.websocket.api.Session;
+import org.eclipse.jetty.websocket.api.StatusCode;
+import org.eclipse.jetty.websocket.client.WebSocketClient;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.openhab.binding.livisismarthome.internal.listener.EventListener;
+
+/**
+ * @author Sven Strohschein - Initial contribution
+ */
+@NonNullByDefault
+public class LivisiWebSocketTest {
+
+ private @NonNullByDefault({}) LivisiWebSocketAccessible webSocket;
+ private @NonNullByDefault({}) EventListenerDummy eventListener;
+ private @NonNullByDefault({}) WebSocketClient webSocketClientMock;
+ private @NonNullByDefault({}) Session sessionMock;
+
+ @BeforeEach
+ @SuppressWarnings("unchecked")
+ public void before() throws Exception {
+ sessionMock = mock(Session.class);
+
+ Future futureMock = mock(Future.class);
+ when(futureMock.get()).thenReturn(sessionMock);
+
+ webSocketClientMock = mock(WebSocketClient.class);
+ when(webSocketClientMock.connect(any(), any())).thenReturn(futureMock);
+
+ HttpClient httpClientMock = mock(HttpClient.class);
+
+ eventListener = new EventListenerDummy();
+ webSocket = new LivisiWebSocketAccessible(httpClientMock, eventListener, new URI(""), 1000);
+ }
+
+ @Test
+ public void testStart() throws Exception {
+ startWebSocket();
+
+ assertTrue(webSocket.isRunning());
+ }
+
+ @Test
+ public void testStop() throws Exception {
+ startWebSocket();
+
+ webSocket.stop();
+
+ assertFalse(webSocket.isRunning());
+ }
+
+ @Test
+ public void testOnCloseAfterStop() throws Exception {
+ startWebSocket();
+
+ assertFalse(eventListener.isOnEventCalled());
+ assertFalse(eventListener.isOnErrorCalled());
+ assertFalse(eventListener.isConnectionClosedCalled());
+
+ webSocket.stop();
+ webSocket.onClose(StatusCode.ABNORMAL, "Test");
+
+ assertFalse(eventListener.isOnEventCalled());
+ assertFalse(eventListener.isOnErrorCalled());
+ // stop() itself causes a (abnormal) close event, that shouldn't get noticed
+ // (otherwise it would cause a reconnect event which would lead to an infinite loop ...)
+ assertFalse(eventListener.isConnectionClosedCalled());
+ }
+
+ @Test
+ public void testOnCloseAfterRestart() throws Exception {
+ startWebSocket();
+
+ assertFalse(eventListener.isOnEventCalled());
+ assertFalse(eventListener.isOnErrorCalled());
+ assertFalse(eventListener.isConnectionClosedCalled());
+
+ webSocket.stop();
+ webSocket.onClose(StatusCode.ABNORMAL, "Test");
+
+ assertFalse(eventListener.isOnEventCalled());
+ assertFalse(eventListener.isOnErrorCalled());
+ // stop() itself causes a (abnormal) close event, that shouldn't get noticed
+ // (otherwise it would cause a reconnect event which would lead to an infinite loop ...)
+ assertFalse(eventListener.isConnectionClosedCalled());
+
+ startWebSocket();
+
+ webSocket.onClose(StatusCode.ABNORMAL, "Test");
+
+ assertFalse(eventListener.isOnEventCalled());
+ assertFalse(eventListener.isOnErrorCalled());
+ // A close event after a restart of the web socket should get recognized again
+ assertTrue(eventListener.isConnectionClosedCalled());
+ }
+
+ @Test
+ public void testOnCloseAbnormal() throws Exception {
+ startWebSocket();
+
+ assertFalse(eventListener.isOnEventCalled());
+ assertFalse(eventListener.isOnErrorCalled());
+ assertFalse(eventListener.isConnectionClosedCalled());
+
+ webSocket.onClose(StatusCode.ABNORMAL, "Test");
+
+ assertFalse(eventListener.isOnEventCalled());
+ assertFalse(eventListener.isOnErrorCalled());
+ assertTrue(eventListener.isConnectionClosedCalled());
+ }
+
+ @Test
+ public void testOnCloseNormal() throws Exception {
+ startWebSocket();
+
+ assertFalse(eventListener.isOnEventCalled());
+ assertFalse(eventListener.isOnErrorCalled());
+ assertFalse(eventListener.isConnectionClosedCalled());
+
+ webSocket.onClose(StatusCode.NORMAL, "Test");
+
+ assertFalse(eventListener.isOnEventCalled());
+ assertFalse(eventListener.isOnErrorCalled());
+ // Nothing should get noticed when a normal close is executed (for example by stopping OpenHAB)
+ assertFalse(eventListener.isConnectionClosedCalled());
+ }
+
+ @Test
+ public void testOnMessage() throws Exception {
+ startWebSocket();
+
+ assertFalse(eventListener.isOnEventCalled());
+ assertFalse(eventListener.isOnErrorCalled());
+ assertFalse(eventListener.isConnectionClosedCalled());
+
+ webSocket.onMessage("Test-Message");
+
+ assertTrue(eventListener.isOnEventCalled());
+ assertFalse(eventListener.isOnErrorCalled());
+ assertFalse(eventListener.isConnectionClosedCalled());
+ }
+
+ @Test
+ public void testOnMessageAfterStop() throws Exception {
+ startWebSocket();
+
+ assertFalse(eventListener.isOnEventCalled());
+ assertFalse(eventListener.isOnErrorCalled());
+ assertFalse(eventListener.isConnectionClosedCalled());
+
+ webSocket.stop();
+ webSocket.onClose(StatusCode.ABNORMAL, "Test");
+ webSocket.onMessage("Test-Message");
+
+ assertFalse(eventListener.isOnEventCalled());
+ assertFalse(eventListener.isOnErrorCalled());
+ assertFalse(eventListener.isConnectionClosedCalled());
+ }
+
+ @Test
+ public void testOnError() throws Exception {
+ startWebSocket();
+
+ assertFalse(eventListener.isOnEventCalled());
+ assertFalse(eventListener.isOnErrorCalled());
+ assertFalse(eventListener.isConnectionClosedCalled());
+
+ webSocket.onError(new RuntimeException("Test-Exception"));
+
+ assertFalse(eventListener.isOnEventCalled());
+ assertTrue(eventListener.isOnErrorCalled());
+ assertFalse(eventListener.isConnectionClosedCalled());
+ }
+
+ private void startWebSocket() throws Exception {
+ webSocket.start();
+ when(sessionMock.isOpen()).thenReturn(true);
+
+ webSocket.onConnect(sessionMock);
+ }
+
+ private class LivisiWebSocketAccessible extends LivisiWebSocket {
+
+ private LivisiWebSocketAccessible(HttpClient httpClient, EventListener eventListener, URI webSocketURI,
+ int maxIdleTimeout) {
+ super(httpClient, eventListener, webSocketURI, maxIdleTimeout);
+ }
+
+ @Override
+ WebSocketClient createWebSocketClient() {
+ return webSocketClientMock;
+ }
+
+ @Override
+ void startWebSocketClient(WebSocketClient client) {
+ }
+
+ @Override
+ void stopWebSocketClient(WebSocketClient client) {
+ }
+ }
+
+ private static class EventListenerDummy implements EventListener {
+
+ private boolean isOnEventCalled;
+ private boolean isOnErrorCalled;
+ private boolean isConnectionClosedCalled;
+
+ @Override
+ public void onEvent(String msg) {
+ isOnEventCalled = true;
+ }
+
+ @Override
+ public void onError(Throwable cause) {
+ isOnErrorCalled = true;
+ }
+
+ @Override
+ public void connectionClosed() {
+ isConnectionClosedCalled = true;
+ }
+
+ public boolean isOnEventCalled() {
+ return isOnEventCalled;
+ }
+
+ public boolean isOnErrorCalled() {
+ return isOnErrorCalled;
+ }
+
+ public boolean isConnectionClosedCalled() {
+ return isConnectionClosedCalled;
+ }
+ }
+}
diff --git a/bundles/org.openhab.binding.livisismarthome/src/test/java/org/openhab/binding/livisismarthome/internal/client/LivisiClientTest.java b/bundles/org.openhab.binding.livisismarthome/src/test/java/org/openhab/binding/livisismarthome/internal/client/LivisiClientTest.java
new file mode 100644
index 00000000000..1723af508ea
--- /dev/null
+++ b/bundles/org.openhab.binding.livisismarthome/src/test/java/org/openhab/binding/livisismarthome/internal/client/LivisiClientTest.java
@@ -0,0 +1,141 @@
+/**
+ * Copyright (c) 2010-2022 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.livisismarthome.internal.client;
+
+import static org.junit.jupiter.api.Assertions.*;
+import static org.mockito.Mockito.*;
+
+import java.io.ByteArrayInputStream;
+import java.net.HttpURLConnection;
+import java.nio.charset.StandardCharsets;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.eclipse.jetty.client.api.ContentResponse;
+import org.eclipse.jetty.http.HttpStatus;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.openhab.binding.livisismarthome.internal.client.api.entity.capability.CapabilityStateDTO;
+import org.openhab.binding.livisismarthome.internal.handler.LivisiBridgeConfiguration;
+import org.openhab.core.auth.client.oauth2.AccessTokenResponse;
+import org.openhab.core.auth.client.oauth2.OAuthClientService;
+
+/**
+ * @author Sven Strohschein - Initial contribution
+ */
+@NonNullByDefault
+public class LivisiClientTest {
+
+ private static final String STATUS_URL = "http://127.0.0.1:8080/status";
+ private static final String DEVICES_URL = "http://127.0.0.1:8080/device";
+ private static final String CAPABILITY_STATES_URL = "http://127.0.0.1:8080/capability/states";
+
+ private @NonNullByDefault({}) LivisiClient client;
+ private @NonNullByDefault({}) URLConnectionFactory connectionFactoryMock;
+
+ @BeforeEach
+ public void before() throws Exception {
+ AccessTokenResponse accessTokenResponse = new AccessTokenResponse();
+ accessTokenResponse.setAccessToken("accessToken");
+
+ OAuthClientService oAuthClientMock = mock(OAuthClientService.class);
+ when(oAuthClientMock.getAccessTokenResponse()).thenReturn(accessTokenResponse);
+
+ connectionFactoryMock = mock(URLConnectionFactory.class);
+
+ LivisiBridgeConfiguration bridgeConfiguration = new LivisiBridgeConfiguration();
+ bridgeConfiguration.host = "127.0.0.1";
+ client = new LivisiClient(bridgeConfiguration, oAuthClientMock, connectionFactoryMock);
+ }
+
+ @Test
+ public void testRefreshStatusSHC1() throws Exception {
+ mockRequest(STATUS_URL,
+ "{\"serialNumber\":\"123\",\"connected\":true,\"appVersion\":\"3.1.1025.0\",\"osVersion\":\"1.914\",\"configVersion\":\"1200\",\"controllerType\":\"Classic\"}");
+ assertEquals("1200", client.refreshStatus());
+ }
+
+ @Test
+ public void testRefreshStatusSHC2() throws Exception {
+ mockRequest(STATUS_URL,
+ "{\"gateway\": {\"serialNumber\": \"123\","
+ + "\"appVersion\": \"1.2.37.430\",\"osVersion\": \"8.17\",\"configVersion\": 1200,"
+ + "\"operationStatus\": \"active\",\"network\": "
+ + "{\"ethCableAttached\": true,\"inUseAdapter\": \"eth\",\"hotspotActive\": false,"
+ + "\"wpsActive\": false,\"backendAvailable\": true,\"ethMacAddress\": "
+ + "[{\"id\": \"456\",\"config\": {\"name\": \"Arbeitszimmer\",\"type\": \"Other\"},"
+ + "\"desc\": \"/desc/location\"}]}}}");
+ assertEquals("1200", client.refreshStatus());
+ }
+
+ @Test
+ public void testGetDevices() throws Exception {
+ mockRequest(DEVICES_URL, "[ { id: 123 }, { id: 789, type: 'VariableActuator' } ]");
+ assertEquals(2, client.getDevices(Arrays.asList("123", "456")).size());
+ }
+
+ @Test
+ public void testGetDevicesNoDeviceIds() throws Exception {
+ mockRequest(DEVICES_URL, "[ { id: 123 } ]");
+ assertEquals(0, client.getDevices(Collections.emptyList()).size());
+ }
+
+ @Test
+ public void testGetDevicesFalseDeviceIds() throws Exception {
+ mockRequest(DEVICES_URL, "[ { id: 789 }]");
+ assertEquals(0, client.getDevices(Arrays.asList("123", "456")).size());
+ }
+
+ @Test
+ public void testGetDevicesNoDevicesNoDeviceIds() throws Exception {
+ mockRequest(DEVICES_URL, "[]");
+ assertEquals(0, client.getDevices(Collections.emptyList()).size());
+ }
+
+ @Test
+ public void testGetDevicesNoDevicesDeviceIds() throws Exception {
+ mockRequest(DEVICES_URL, "[]");
+ assertEquals(0, client.getDevices(Arrays.asList("123", "456")).size());
+ }
+
+ @Test
+ public void testGetCapabilityStates() throws Exception {
+ mockRequest(CAPABILITY_STATES_URL,
+ "[{\"id\":\"123\",\"state\":{\"isOpen\":{\"value\":false,\"lastChanged\":\"2022-03-12T20:54:50.6930000Z\"}}},{\"id\":\"456\",\"state\":{\"isOpen\":{\"value\":false,\"lastChanged\":\"2022-03-13T13:48:36.6830000Z\"}}},{\"id\":\"789\",\"state\":{\"isOpen\":{\"value\":true,\"lastChanged\":\"2022-03-13T13:48:36.6830000Z\"}}}]");
+ assertEquals(3, client.getCapabilityStates().size());
+ }
+
+ @Test
+ public void testGetCapabilityStatesStateNULL() throws Exception {
+ mockRequest(CAPABILITY_STATES_URL,
+ "[{\"id\":\"123\",\"state\":{\"isOpen\":{\"value\":false,\"lastChanged\":\"2022-03-12T20:54:50.6930000Z\"}}},{\"id\":\"456\",\"state\":[]},{\"id\":\"789\",\"state\":[]}]");
+ List capabilityStates = client.getCapabilityStates();
+ assertEquals(3, capabilityStates.size());
+ }
+
+ private void mockRequest(String url, String responseContent) throws Exception {
+ ContentResponse response = mock(ContentResponse.class);
+ when(response.getStatus()).thenReturn(HttpStatus.OK_200);
+ when(response.getContentAsString()).thenReturn(responseContent);
+
+ HttpURLConnection connectionMock = mock(HttpURLConnection.class);
+ when(connectionMock.getResponseCode()).thenReturn(HttpStatus.OK_200);
+ when(connectionMock.getInputStream())
+ .thenReturn(new ByteArrayInputStream(responseContent.getBytes(StandardCharsets.UTF_8)));
+
+ when(connectionFactoryMock.createRequest(eq(url))).thenReturn(connectionMock);
+ when(connectionFactoryMock.createBaseRequest(eq(url), any(), any())).thenReturn(connectionMock);
+ }
+}
diff --git a/bundles/org.openhab.binding.livisismarthome/src/test/java/org/openhab/binding/livisismarthome/internal/client/URLCreatorTest.java b/bundles/org.openhab.binding.livisismarthome/src/test/java/org/openhab/binding/livisismarthome/internal/client/URLCreatorTest.java
new file mode 100644
index 00000000000..b5c1ea6db8d
--- /dev/null
+++ b/bundles/org.openhab.binding.livisismarthome/src/test/java/org/openhab/binding/livisismarthome/internal/client/URLCreatorTest.java
@@ -0,0 +1,44 @@
+/**
+ * Copyright (c) 2010-2022 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.livisismarthome.internal.client;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.junit.jupiter.api.Test;
+
+/**
+ * @author Sven Strohschein - Initial contribution
+ */
+@NonNullByDefault
+public class URLCreatorTest {
+
+ @Test
+ public void testCreateEventsURL() {
+ String url = URLCreator.createEventsURL("localhost", "token+123/456", false);
+ // The token should be URL encoded
+ assertEquals("ws://localhost:9090/events?token=token%2B123%2F456", url);
+ }
+
+ @Test
+ public void testCreateEventsURL_ClassicController() {
+ String url = URLCreator.createEventsURL("localhost", "token123", true);
+ assertEquals("ws://localhost:8080/events?token=token123", url);
+ }
+
+ @Test
+ public void testCreateEventsURL_Gen2Controller() {
+ String url = URLCreator.createEventsURL("localhost", "token123", false);
+ assertEquals("ws://localhost:9090/events?token=token123", url);
+ }
+}
diff --git a/bundles/org.openhab.binding.livisismarthome/src/test/java/org/openhab/binding/livisismarthome/internal/client/entity/device/DeviceDTOTest.java b/bundles/org.openhab.binding.livisismarthome/src/test/java/org/openhab/binding/livisismarthome/internal/client/entity/device/DeviceDTOTest.java
new file mode 100644
index 00000000000..bdde9838430
--- /dev/null
+++ b/bundles/org.openhab.binding.livisismarthome/src/test/java/org/openhab/binding/livisismarthome/internal/client/entity/device/DeviceDTOTest.java
@@ -0,0 +1,199 @@
+/**
+ * Copyright (c) 2010-2022 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.livisismarthome.internal.client.entity.device;
+
+import static org.junit.jupiter.api.Assertions.*;
+
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.junit.jupiter.api.Test;
+import org.openhab.binding.livisismarthome.internal.client.api.entity.device.DeviceDTO;
+import org.openhab.binding.livisismarthome.internal.client.api.entity.device.DeviceStateDTO;
+import org.openhab.binding.livisismarthome.internal.client.api.entity.device.StateDTO;
+import org.openhab.binding.livisismarthome.internal.client.api.entity.message.MessageDTO;
+import org.openhab.binding.livisismarthome.internal.client.api.entity.state.BooleanStateDTO;
+
+/**
+ * @author Sven Strohschein - Initial contribution
+ */
+@NonNullByDefault
+public class DeviceDTOTest {
+
+ @Test
+ public void testSetMessageListLowBatteryMessage() {
+ DeviceDTO device = createDevice();
+
+ assertTrue(device.isReachable());
+ assertFalse(device.hasLowBattery());
+
+ device.setMessageList(Collections.singletonList(createMessage(MessageDTO.TYPE_DEVICE_LOW_BATTERY)));
+
+ assertTrue(device.isReachable());
+ assertTrue(device.hasLowBattery());
+ }
+
+ @Test
+ public void testSetMessageListUnreachableMessage() {
+ DeviceDTO device = createDevice();
+
+ assertTrue(device.isReachable());
+ assertFalse(device.hasLowBattery());
+
+ device.setMessageList(Collections.singletonList(createMessage(MessageDTO.TYPE_DEVICE_UNREACHABLE)));
+
+ assertFalse(device.isReachable());
+ assertFalse(device.hasLowBattery());
+ }
+
+ @Test
+ public void testSetMessageListResetByEmpty() {
+ DeviceDTO device = createDevice();
+
+ assertTrue(device.getMessageList().isEmpty());
+ assertTrue(device.isReachable());
+ assertFalse(device.hasLowBattery());
+
+ List messages = Arrays.asList(createMessage(MessageDTO.TYPE_DEVICE_LOW_BATTERY),
+ createMessage(MessageDTO.TYPE_DEVICE_UNREACHABLE));
+ device.setMessageList(messages);
+
+ assertEquals(messages, device.getMessageList());
+ assertFalse(device.isReachable());
+ assertTrue(device.hasLowBattery());
+
+ device.setMessageList(Collections.emptyList());
+
+ // Nothing should get changed.
+ // New messages are only set in real-life when the device is refreshed with new data of the API.
+ // Therefore the data of the API should be kept / not overwritten when no corresponding messages are available.
+ assertEquals(Collections.emptyList(), device.getMessageList());
+ assertFalse(device.isReachable());
+ assertTrue(device.hasLowBattery());
+ }
+
+ @Test
+ public void testSetMessageListResetByNULL() {
+ DeviceDTO device = createDevice();
+
+ assertTrue(device.getMessageList().isEmpty());
+ assertTrue(device.isReachable());
+ assertFalse(device.hasLowBattery());
+
+ List messages = Arrays.asList(createMessage(MessageDTO.TYPE_DEVICE_LOW_BATTERY),
+ createMessage(MessageDTO.TYPE_DEVICE_UNREACHABLE));
+ device.setMessageList(messages);
+
+ assertEquals(messages, device.getMessageList());
+ assertFalse(device.isReachable());
+ assertTrue(device.hasLowBattery());
+
+ device.setMessageList(null);
+
+ // Nothing should get changed.
+ // New messages are only set in real-life when the device is refreshed with new data of the API.
+ // Therefore the data of the API should be kept / not overwritten when no corresponding messages are available.
+ assertTrue(device.getMessageList().isEmpty());
+ assertFalse(device.isReachable());
+ assertTrue(device.hasLowBattery());
+ }
+
+ @Test
+ public void testSetMessageListResetByUnimportantMessage() {
+ DeviceDTO device = createDevice();
+
+ assertTrue(device.getMessageList().isEmpty());
+ assertTrue(device.isReachable());
+ assertFalse(device.hasLowBattery());
+
+ List messages = Arrays.asList(createMessage(MessageDTO.TYPE_DEVICE_LOW_BATTERY),
+ createMessage(MessageDTO.TYPE_DEVICE_UNREACHABLE));
+ device.setMessageList(messages);
+
+ assertEquals(messages, device.getMessageList());
+ assertFalse(device.isReachable());
+ assertTrue(device.hasLowBattery());
+
+ messages = Collections.singletonList(createMessage("UNKNOWN"));
+ device.setMessageList(messages);
+
+ // Nothing should get changed.
+ // New messages are only set in real-life when the device is refreshed with new data of the API.
+ // Therefore the data of the API should be kept / not overwritten when no corresponding messages are available.
+ assertEquals(messages, device.getMessageList());
+ assertFalse(device.isReachable());
+ assertTrue(device.hasLowBattery());
+ }
+
+ @Test
+ public void testSetMessageListUnimportantMessage() {
+ DeviceDTO device = createDevice();
+
+ assertTrue(device.isReachable());
+ assertFalse(device.hasLowBattery());
+
+ device.setMessageList(Collections.singletonList(createMessage("UNKNOWN")));
+
+ assertTrue(device.isReachable());
+ assertFalse(device.hasLowBattery());
+ }
+
+ private MessageDTO createMessage(String messageType) {
+ MessageDTO message = new MessageDTO();
+ message.setType(messageType);
+ return message;
+ }
+
+ @Test
+ public void testSetMessageListNULL() {
+ DeviceDTO device = createDevice();
+
+ assertTrue(device.isReachable());
+ assertFalse(device.hasLowBattery());
+
+ device.setMessageList(null);
+
+ assertTrue(device.isReachable());
+ assertFalse(device.hasLowBattery());
+ }
+
+ @Test
+ public void testSetMessageListEmpty() {
+ DeviceDTO device = createDevice();
+
+ assertTrue(device.isReachable());
+ assertFalse(device.hasLowBattery());
+
+ device.setMessageList(Collections.emptyList());
+
+ assertTrue(device.isReachable());
+ assertFalse(device.hasLowBattery());
+ }
+
+ private static DeviceDTO createDevice() {
+ BooleanStateDTO isReachableState = new BooleanStateDTO();
+ isReachableState.setValue(true);
+
+ StateDTO state = new StateDTO();
+ state.setIsReachable(isReachableState);
+
+ DeviceStateDTO deviceState = new DeviceStateDTO();
+ deviceState.setState(state);
+
+ DeviceDTO device = new DeviceDTO();
+ device.setDeviceState(deviceState);
+ return device;
+ }
+}
diff --git a/bundles/org.openhab.binding.livisismarthome/src/test/java/org/openhab/binding/livisismarthome/internal/handler/LivisiBridgeHandlerTest.java b/bundles/org.openhab.binding.livisismarthome/src/test/java/org/openhab/binding/livisismarthome/internal/handler/LivisiBridgeHandlerTest.java
new file mode 100644
index 00000000000..632833c8a8b
--- /dev/null
+++ b/bundles/org.openhab.binding.livisismarthome/src/test/java/org/openhab/binding/livisismarthome/internal/handler/LivisiBridgeHandlerTest.java
@@ -0,0 +1,416 @@
+/**
+ * Copyright (c) 2010-2022 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.livisismarthome.internal.handler;
+
+import static org.junit.jupiter.api.Assertions.*;
+import static org.mockito.Mockito.*;
+import static org.openhab.binding.livisismarthome.internal.LivisiBindingConstants.*;
+import static org.openhab.binding.livisismarthome.internal.client.api.entity.link.LinkDTO.LINK_TYPE_DEVICE;
+
+import java.io.IOException;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.LinkedHashMap;
+import java.util.Map;
+import java.util.concurrent.ScheduledExecutorService;
+import java.util.concurrent.ScheduledFuture;
+import java.util.function.Consumer;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.eclipse.jdt.annotation.Nullable;
+import org.eclipse.jetty.client.HttpClient;
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.openhab.binding.livisismarthome.internal.LivisiWebSocket;
+import org.openhab.binding.livisismarthome.internal.client.LivisiClient;
+import org.openhab.binding.livisismarthome.internal.client.api.entity.device.DeviceConfigDTO;
+import org.openhab.binding.livisismarthome.internal.client.api.entity.device.DeviceDTO;
+import org.openhab.binding.livisismarthome.internal.client.api.entity.device.DeviceStateDTO;
+import org.openhab.binding.livisismarthome.internal.client.api.entity.device.StateDTO;
+import org.openhab.binding.livisismarthome.internal.client.api.entity.event.EventDTO;
+import org.openhab.binding.livisismarthome.internal.client.api.entity.event.EventPropertiesDTO;
+import org.openhab.binding.livisismarthome.internal.client.api.entity.state.DoubleStateDTO;
+import org.openhab.binding.livisismarthome.internal.client.api.entity.state.StringStateDTO;
+import org.openhab.binding.livisismarthome.internal.client.exception.WebSocketConnectException;
+import org.openhab.binding.livisismarthome.internal.manager.FullDeviceManager;
+import org.openhab.core.auth.client.oauth2.OAuthClientService;
+import org.openhab.core.auth.client.oauth2.OAuthFactory;
+import org.openhab.core.config.core.Configuration;
+import org.openhab.core.library.types.QuantityType;
+import org.openhab.core.library.types.StringType;
+import org.openhab.core.library.unit.Units;
+import org.openhab.core.thing.Bridge;
+import org.openhab.core.thing.ThingUID;
+import org.openhab.core.thing.binding.ThingHandlerCallback;
+import org.openhab.core.types.State;
+import org.slf4j.LoggerFactory;
+
+import ch.qos.logback.classic.Level;
+import ch.qos.logback.classic.Logger;
+
+/**
+ * @author Sven Strohschein - Initial contribution
+ */
+@NonNullByDefault
+public class LivisiBridgeHandlerTest {
+
+ private static final int MAXIMUM_RETRY_EXECUTIONS = 10;
+
+ private @NonNullByDefault({}) LivisiBridgeHandlerAccessible bridgeHandler;
+ private @NonNullByDefault({}) Bridge bridgeMock;
+ private @NonNullByDefault({}) LivisiWebSocket webSocketMock;
+ private @NonNullByDefault({}) Map updatedChannels;
+
+ private @NonNullByDefault({}) Level previousLoggingLevel;
+
+ @BeforeEach
+ public void before() throws Exception {
+ Logger logger = (Logger) LoggerFactory.getLogger(LivisiBridgeHandler.class);
+ previousLoggingLevel = logger.getLevel();
+ logger.setLevel(Level.OFF); // avoid (test) exception logs which occur within these reconnect tests
+
+ updatedChannels = new LinkedHashMap<>();
+
+ bridgeMock = mock(Bridge.class);
+ when(bridgeMock.getUID()).thenReturn(new ThingUID("livisismarthome", "bridge"));
+
+ webSocketMock = mock(LivisiWebSocket.class);
+
+ OAuthClientService oAuthService = mock(OAuthClientService.class);
+
+ OAuthFactory oAuthFactoryMock = mock(OAuthFactory.class);
+ when(oAuthFactoryMock.createOAuthClientService(any(), any(), any(), any(), any(), any(), any()))
+ .thenReturn(oAuthService);
+
+ HttpClient httpClientMock = mock(HttpClient.class);
+
+ bridgeHandler = new LivisiBridgeHandlerAccessible(bridgeMock, oAuthFactoryMock, httpClientMock);
+ bridgeHandler.setCallback(mock(ThingHandlerCallback.class));
+ }
+
+ @AfterEach
+ public void after() {
+ Logger logger = (Logger) LoggerFactory.getLogger(LivisiBridgeHandler.class);
+ logger.setLevel(previousLoggingLevel);
+ }
+
+ @Test
+ public void testInitialize() throws Exception {
+ Configuration bridgeConfig = new Configuration();
+
+ when(bridgeMock.getConfiguration()).thenReturn(bridgeConfig);
+
+ bridgeHandler.initialize();
+
+ verify(webSocketMock).start();
+ assertEquals(2, bridgeHandler.getDirectExecutionCount());
+ }
+
+ @Test
+ public void testInitializeErrorOnStartingWebSocket() throws Exception {
+ Configuration bridgeConfig = new Configuration();
+
+ when(bridgeMock.getConfiguration()).thenReturn(bridgeConfig);
+
+ doThrow(new WebSocketConnectException("Test-Exception", new IOException())).when(webSocketMock).start();
+
+ bridgeHandler.initialize();
+
+ verify(webSocketMock, times(MAXIMUM_RETRY_EXECUTIONS - 1)).start();
+ assertEquals(2, bridgeHandler.getDirectExecutionCount()); // only the first execution should be without a delay
+ }
+
+ @Test
+ public void testConnectionClosed() throws Exception {
+ Configuration bridgeConfig = new Configuration();
+
+ when(bridgeMock.getConfiguration()).thenReturn(bridgeConfig);
+
+ bridgeHandler.initialize();
+
+ verify(webSocketMock).start();
+ assertEquals(2, bridgeHandler.getDirectExecutionCount());
+
+ bridgeHandler.connectionClosed();
+
+ verify(webSocketMock, times(2)).start(); // automatically restarted (with a delay)
+ assertEquals(2, bridgeHandler.getDirectExecutionCount());
+
+ bridgeHandler.connectionClosed();
+
+ verify(webSocketMock, times(3)).start(); // automatically restarted (with a delay)
+ assertEquals(2, bridgeHandler.getDirectExecutionCount());
+ }
+
+ @Test
+ public void testConnectionClosedReconnectNotPossible() throws Exception {
+ Configuration bridgeConfig = new Configuration();
+
+ when(bridgeMock.getConfiguration()).thenReturn(bridgeConfig);
+
+ bridgeHandler.initialize();
+
+ verify(webSocketMock).start();
+ assertEquals(2, bridgeHandler.getDirectExecutionCount());
+
+ doThrow(new WebSocketConnectException("Connection refused", new IOException())).when(webSocketMock).start();
+
+ bridgeHandler.connectionClosed();
+
+ verify(webSocketMock, times(MAXIMUM_RETRY_EXECUTIONS - 1)).start(); // automatic reconnect attempts (with a
+ // delay)
+ assertEquals(2, bridgeHandler.getDirectExecutionCount());
+ }
+
+ @Test
+ public void testOnEventDisconnect() throws Exception {
+ final String disconnectEventJSON = "{ type: \"Disconnect\" }";
+
+ Configuration bridgeConfig = new Configuration();
+
+ when(bridgeMock.getConfiguration()).thenReturn(bridgeConfig);
+
+ bridgeHandler.initialize();
+
+ verify(webSocketMock).start();
+ assertEquals(2, bridgeHandler.getDirectExecutionCount());
+
+ bridgeHandler.onEvent(disconnectEventJSON);
+
+ verify(webSocketMock, times(2)).start(); // automatically restarted (with a delay)
+ assertEquals(2, bridgeHandler.getDirectExecutionCount());
+
+ bridgeHandler.onEvent(disconnectEventJSON);
+
+ verify(webSocketMock, times(3)).start(); // automatically restarted (with a delay)
+ assertEquals(2, bridgeHandler.getDirectExecutionCount());
+ }
+
+ @Test
+ public void testOnDeviceStateChanged_SHC_Classic() {
+ DeviceDTO bridgeDevice = createBridgeDevice(true);
+
+ StateDTO state = new StateDTO();
+ state.setCPULoad(doubleState(30.5));
+ state.setMemoryLoad(doubleState(60.5));
+ state.setDiskUsage(doubleState(70.5));
+ state.setOSState(stringState("Normal"));
+
+ DeviceStateDTO deviceState = new DeviceStateDTO();
+ deviceState.setState(state);
+ bridgeDevice.setDeviceState(deviceState);
+
+ bridgeHandler.onDeviceStateChanged(bridgeDevice);
+
+ assertTrue(isChannelUpdated(CHANNEL_CPU, QuantityType.valueOf(30.5, Units.PERCENT)));
+ assertTrue(isChannelUpdated(CHANNEL_MEMORY, QuantityType.valueOf(60.5, Units.PERCENT)));
+ assertTrue(isChannelUpdated(CHANNEL_DISK, QuantityType.valueOf(70.5, Units.PERCENT)));
+ assertTrue(isChannelUpdated(CHANNEL_OPERATION_STATUS, StringType.valueOf("NORMAL")));
+ }
+
+ @Test
+ public void testOnDeviceStateChanged_SHCA() {
+ DeviceDTO bridgeDevice = createBridgeDevice(false);
+
+ StateDTO state = new StateDTO();
+ state.setCpuUsage(doubleState(30.5));
+ state.setMemoryUsage(doubleState(60.5));
+ state.setDiskUsage(doubleState(70.5));
+ state.setOperationStatus(stringState("active"));
+
+ DeviceStateDTO deviceState = new DeviceStateDTO();
+ deviceState.setState(state);
+ bridgeDevice.setDeviceState(deviceState);
+
+ bridgeHandler.onDeviceStateChanged(bridgeDevice);
+
+ assertTrue(isChannelUpdated(CHANNEL_CPU, QuantityType.valueOf(30.5, Units.PERCENT)));
+ assertTrue(isChannelUpdated(CHANNEL_MEMORY, QuantityType.valueOf(60.5, Units.PERCENT)));
+ assertTrue(isChannelUpdated(CHANNEL_DISK, QuantityType.valueOf(70.5, Units.PERCENT)));
+ assertTrue(isChannelUpdated(CHANNEL_OPERATION_STATUS, StringType.valueOf("ACTIVE")));
+ }
+
+ @Test
+ public void testOnDeviceStateChanged_Event_SHC_Classic() {
+ DeviceDTO bridgeDevice = createBridgeDevice(true);
+
+ // Example SHC-Classic-Event
+ // {"sequenceNumber":709,"type":"StateChanged","namespace":"core.RWE","timestamp":"2022-03-17T11:13:20.1100000Z","source":"/device/812ae7233697408378943e5d943a450x","properties":{"updateAvailable":"","lastReboot":"2022-03-17T
+ // 10:10:14.3530000Z","MBusDongleAttached":false,"LBDongleAttached":false,"configVersion":649,"discoveryActive":false,"IPAddress":"192.168.178.12","currentUTCOffset":
+ // 60,"productsHash":"xy","OSState":"Normal","memoryLoad":67,"CPULoad":39,"diskUsage":20}}
+
+ EventDTO event = createDeviceEvent(c -> {
+ c.setOSState("Normal");
+ c.setCPULoad(39.5);
+ c.setMemoryLoad(67.5);
+ c.setDiskUsage(20.5);
+ });
+
+ StateDTO state = new StateDTO();
+ state.setCPULoad(doubleState(30.5));
+ state.setMemoryLoad(doubleState(60.5));
+ state.setDiskUsage(doubleState(70.5));
+ state.setOSState(stringState("active"));
+
+ DeviceStateDTO deviceState = new DeviceStateDTO();
+ deviceState.setState(state);
+ bridgeDevice.setDeviceState(deviceState);
+
+ bridgeHandler.onDeviceStateChanged(bridgeDevice, event);
+
+ assertTrue(isChannelUpdated(CHANNEL_CPU, QuantityType.valueOf(39.5, Units.PERCENT)));
+ assertTrue(isChannelUpdated(CHANNEL_MEMORY, QuantityType.valueOf(67.5, Units.PERCENT)));
+ assertTrue(isChannelUpdated(CHANNEL_DISK, QuantityType.valueOf(20.5, Units.PERCENT)));
+ assertTrue(isChannelUpdated(CHANNEL_OPERATION_STATUS, StringType.valueOf("NORMAL")));
+ }
+
+ @Test
+ public void testOnDeviceStateChanged_Event_SHCA() {
+ DeviceDTO bridgeDevice = createBridgeDevice(false);
+
+ EventDTO event = createDeviceEvent(c -> {
+ c.setOperationStatus("active");
+ c.setCpuUsage(39.5);
+ c.setMemoryUsage(67.5);
+ c.setDiskUsage(20.5);
+ });
+
+ StateDTO state = new StateDTO();
+ state.setCpuUsage(doubleState(30.5));
+ state.setMemoryUsage(doubleState(60.5));
+ state.setDiskUsage(doubleState(70.5));
+ state.setOperationStatus(stringState("shuttingdown"));
+
+ DeviceStateDTO deviceState = new DeviceStateDTO();
+ deviceState.setState(state);
+ bridgeDevice.setDeviceState(deviceState);
+
+ bridgeHandler.onDeviceStateChanged(bridgeDevice, event);
+
+ assertTrue(isChannelUpdated(CHANNEL_CPU, QuantityType.valueOf(39.5, Units.PERCENT)));
+ assertTrue(isChannelUpdated(CHANNEL_MEMORY, QuantityType.valueOf(67.5, Units.PERCENT)));
+ assertTrue(isChannelUpdated(CHANNEL_DISK, QuantityType.valueOf(20.5, Units.PERCENT)));
+ assertTrue(isChannelUpdated(CHANNEL_OPERATION_STATUS, StringType.valueOf("ACTIVE")));
+ }
+
+ private static DoubleStateDTO doubleState(double value) {
+ DoubleStateDTO state = new DoubleStateDTO();
+ state.setValue(value);
+ return state;
+ }
+
+ private static StringStateDTO stringState(String value) {
+ StringStateDTO state = new StringStateDTO();
+ state.setValue(value);
+ return state;
+ }
+
+ private static DeviceDTO createBridgeDevice(boolean isSHCClassic) {
+ DeviceDTO device = new DeviceDTO();
+ device.setId("id");
+ device.setConfig(new DeviceConfigDTO());
+ device.setCapabilityMap(new HashMap<>());
+ if (isSHCClassic) {
+ device.setType(DEVICE_SHC);
+ } else {
+ device.setType(DEVICE_SHCA);
+ }
+ return device;
+ }
+
+ private boolean isChannelUpdated(String channelUID, State expectedState) {
+ State state = updatedChannels.get(channelUID);
+ return expectedState.equals(state);
+ }
+
+ private static EventDTO createDeviceEvent(Consumer eventPropertiesConsumer) {
+ EventPropertiesDTO eventProperties = new EventPropertiesDTO();
+ eventPropertiesConsumer.accept(eventProperties);
+
+ EventDTO event = new EventDTO();
+ event.setSource(LINK_TYPE_DEVICE + "deviceId");
+ event.setProperties(eventProperties);
+ return event;
+ }
+
+ private class LivisiBridgeHandlerAccessible extends LivisiBridgeHandler {
+
+ private final LivisiClient livisiClientMock;
+ private final FullDeviceManager fullDeviceManagerMock;
+ private final ScheduledExecutorService schedulerMock;
+ private int executionCount;
+ private int directExecutionCount;
+
+ private LivisiBridgeHandlerAccessible(Bridge bridge, OAuthFactory oAuthFactory, HttpClient httpClient)
+ throws Exception {
+ super(bridge, oAuthFactory, httpClient);
+
+ DeviceDTO bridgeDevice = new DeviceDTO();
+ bridgeDevice.setId("bridgeId");
+ bridgeDevice.setType(DEVICE_SHC);
+ bridgeDevice.setConfig(new DeviceConfigDTO());
+
+ livisiClientMock = mock(LivisiClient.class);
+ fullDeviceManagerMock = mock(FullDeviceManager.class);
+ when(fullDeviceManagerMock.getFullDevices()).thenReturn(Collections.singletonList(bridgeDevice));
+
+ schedulerMock = mock(ScheduledExecutorService.class);
+
+ doAnswer(invocationOnMock -> {
+ if (executionCount < MAXIMUM_RETRY_EXECUTIONS) {
+ executionCount++;
+ long seconds = invocationOnMock.getArgument(1);
+ if (seconds <= 0) {
+ directExecutionCount++;
+ }
+
+ invocationOnMock.getArgument(0, Runnable.class).run();
+ }
+ return mock(ScheduledFuture.class);
+ }).when(schedulerMock).schedule(any(Runnable.class), anyLong(), any());
+ }
+
+ public int getDirectExecutionCount() {
+ return directExecutionCount;
+ }
+
+ @Override
+ FullDeviceManager createFullDeviceManager(LivisiClient client) {
+ return fullDeviceManagerMock;
+ }
+
+ @Override
+ LivisiClient createClient(OAuthClientService oAuthService) {
+ return livisiClientMock;
+ }
+
+ @Override
+ @Nullable
+ LivisiWebSocket createAndStartWebSocket(DeviceDTO bridgeDevice) throws WebSocketConnectException {
+ webSocketMock.start();
+ return webSocketMock;
+ }
+
+ @Override
+ ScheduledExecutorService getScheduler() {
+ return schedulerMock;
+ }
+
+ @Override
+ protected void updateState(String channelID, State state) {
+ super.updateState(channelID, state);
+ updatedChannels.put(channelID, state);
+ }
+ }
+}
diff --git a/bundles/org.openhab.binding.livisismarthome/src/test/java/org/openhab/binding/livisismarthome/internal/handler/LivisiDeviceHandlerTest.java b/bundles/org.openhab.binding.livisismarthome/src/test/java/org/openhab/binding/livisismarthome/internal/handler/LivisiDeviceHandlerTest.java
new file mode 100644
index 00000000000..a10b6da286e
--- /dev/null
+++ b/bundles/org.openhab.binding.livisismarthome/src/test/java/org/openhab/binding/livisismarthome/internal/handler/LivisiDeviceHandlerTest.java
@@ -0,0 +1,1875 @@
+/**
+ * Copyright (c) 2010-2022 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.livisismarthome.internal.handler;
+
+import static org.junit.jupiter.api.Assertions.*;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.*;
+import static org.openhab.binding.livisismarthome.internal.LivisiBindingConstants.*;
+import static org.openhab.binding.livisismarthome.internal.client.api.entity.capability.CapabilityStateDTO.STATE_VALUE_OPERATION_MODE_AUTO;
+import static org.openhab.binding.livisismarthome.internal.client.api.entity.capability.CapabilityStateDTO.STATE_VALUE_OPERATION_MODE_MANUAL;
+import static org.openhab.binding.livisismarthome.internal.client.api.entity.link.LinkDTO.LINK_TYPE_CAPABILITY;
+import static org.openhab.binding.livisismarthome.internal.client.api.entity.link.LinkDTO.LINK_TYPE_DEVICE;
+
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.LinkedHashMap;
+import java.util.LinkedHashSet;
+import java.util.Map;
+import java.util.Objects;
+import java.util.Optional;
+import java.util.Set;
+import java.util.function.Consumer;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.eclipse.jdt.annotation.Nullable;
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.openhab.binding.livisismarthome.internal.client.api.entity.action.ShutterActionType;
+import org.openhab.binding.livisismarthome.internal.client.api.entity.capability.CapabilityConfigDTO;
+import org.openhab.binding.livisismarthome.internal.client.api.entity.capability.CapabilityDTO;
+import org.openhab.binding.livisismarthome.internal.client.api.entity.capability.CapabilityStateDTO;
+import org.openhab.binding.livisismarthome.internal.client.api.entity.device.DeviceConfigDTO;
+import org.openhab.binding.livisismarthome.internal.client.api.entity.device.DeviceDTO;
+import org.openhab.binding.livisismarthome.internal.client.api.entity.device.DeviceStateDTO;
+import org.openhab.binding.livisismarthome.internal.client.api.entity.event.EventDTO;
+import org.openhab.binding.livisismarthome.internal.client.api.entity.event.EventPropertiesDTO;
+import org.openhab.core.config.core.Configuration;
+import org.openhab.core.library.types.DecimalType;
+import org.openhab.core.library.types.OnOffType;
+import org.openhab.core.library.types.OpenClosedType;
+import org.openhab.core.library.types.QuantityType;
+import org.openhab.core.library.types.StopMoveType;
+import org.openhab.core.library.types.StringType;
+import org.openhab.core.library.types.UpDownType;
+import org.openhab.core.library.unit.SIUnits;
+import org.openhab.core.library.unit.Units;
+import org.openhab.core.thing.Bridge;
+import org.openhab.core.thing.Channel;
+import org.openhab.core.thing.ChannelUID;
+import org.openhab.core.thing.CommonTriggerEvents;
+import org.openhab.core.thing.Thing;
+import org.openhab.core.thing.ThingStatus;
+import org.openhab.core.thing.ThingStatusDetail;
+import org.openhab.core.thing.ThingStatusInfo;
+import org.openhab.core.thing.ThingTypeUID;
+import org.openhab.core.thing.ThingUID;
+import org.openhab.core.thing.binding.ThingHandlerCallback;
+import org.openhab.core.thing.binding.builder.ChannelBuilder;
+import org.openhab.core.thing.binding.builder.ThingStatusInfoBuilder;
+import org.openhab.core.types.State;
+import org.slf4j.LoggerFactory;
+
+import ch.qos.logback.classic.Level;
+import ch.qos.logback.classic.Logger;
+
+/**
+ * @author Sven Strohschein - Initial contribution
+ */
+@NonNullByDefault
+public class LivisiDeviceHandlerTest {
+
+ private @NonNullByDefault({}) LivisiBridgeHandler bridgeHandlerMock;
+ private @NonNullByDefault({}) ThingStatusInfo thingStatusInfo;
+ private @NonNullByDefault({}) Map updatedChannels;
+ private @NonNullByDefault({}) Set triggeredChannels;
+
+ private @NonNullByDefault({}) Level previousLoggingLevel;
+
+ @BeforeEach
+ public void before() {
+ bridgeHandlerMock = mock(LivisiBridgeHandler.class);
+ thingStatusInfo = ThingStatusInfoBuilder.create(ThingStatus.UNINITIALIZED, ThingStatusDetail.NONE).build();
+ updatedChannels = new LinkedHashMap<>();
+ triggeredChannels = new LinkedHashSet<>();
+
+ Logger logger = (Logger) LoggerFactory.getLogger(LivisiDeviceHandler.class);
+ previousLoggingLevel = logger.getLevel();
+ logger.setLevel(Level.OFF); // avoid log messages (which would be logged to the console)
+ }
+
+ @AfterEach
+ public void after() {
+ Logger logger = (Logger) LoggerFactory.getLogger(LivisiDeviceHandler.class);
+ logger.setLevel(previousLoggingLevel);
+ }
+
+ @Test
+ public void testInitialize() {
+ LivisiDeviceHandler deviceHandler = createDeviceHandler(createDevice());
+ assertEquals(ThingStatus.ONLINE, deviceHandler.getThing().getStatus());
+ }
+
+ @Test
+ public void testOnDeviceStateChanged() {
+ DeviceDTO device = createDevice();
+ LivisiDeviceHandler deviceHandler = createDeviceHandler(device);
+
+ deviceHandler.onDeviceStateChanged(device);
+ }
+
+ @Test
+ public void testOnDeviceStateChanged_IsReachable() {
+ DeviceStateDTO deviceState = new DeviceStateDTO();
+ deviceState.setReachable(true);
+
+ DeviceDTO device = createDevice();
+ device.setDeviceState(deviceState);
+
+ LivisiDeviceHandler deviceHandler = createDeviceHandler(device);
+
+ deviceHandler.onDeviceStateChanged(device);
+ assertEquals(ThingStatus.ONLINE, deviceHandler.getThing().getStatus());
+ assertEquals(ThingStatusDetail.NONE, deviceHandler.getThing().getStatusInfo().getStatusDetail());
+ }
+
+ @Test
+ public void testOnDeviceStateChanged_IsNotReachable() {
+ DeviceStateDTO deviceState = new DeviceStateDTO();
+ deviceState.setReachable(false);
+
+ DeviceDTO device = createDevice();
+ device.setDeviceState(deviceState);
+
+ LivisiDeviceHandler deviceHandler = createDeviceHandler(device);
+
+ deviceHandler.onDeviceStateChanged(device);
+ assertEquals(ThingStatus.OFFLINE, deviceHandler.getThing().getStatus());
+ assertEquals(ThingStatusDetail.COMMUNICATION_ERROR, deviceHandler.getThing().getStatusInfo().getStatusDetail());
+ }
+
+ @Test
+ public void testOnDeviceStateChanged_IsReachable_VariableActuator() {
+ DeviceStateDTO deviceState = new DeviceStateDTO();
+ deviceState.setReachable(true);
+
+ DeviceDTO device = createDevice();
+ device.setType(DEVICE_VARIABLE_ACTUATOR);
+ device.setDeviceState(deviceState);
+
+ LivisiDeviceHandler deviceHandler = createDeviceHandler(device);
+
+ deviceHandler.onDeviceStateChanged(device);
+ assertEquals(ThingStatus.ONLINE, deviceHandler.getThing().getStatus());
+ assertEquals(ThingStatusDetail.NONE, deviceHandler.getThing().getStatusInfo().getStatusDetail());
+ }
+
+ @Test
+ public void testOnDeviceStateChanged_LowBattery() {
+ DeviceDTO device = createDevice();
+ device.setIsBatteryPowered(true);
+ device.setLowBattery(true);
+
+ LivisiDeviceHandler deviceHandler = createDeviceHandler(device);
+
+ deviceHandler.onDeviceStateChanged(device);
+ assertTrue(isChannelUpdated(CHANNEL_BATTERY_LOW, OnOffType.ON));
+ }
+
+ @Test
+ public void testOnDeviceStateChanged_NoLowBattery() {
+ DeviceDTO device = createDevice();
+ device.setIsBatteryPowered(true);
+ device.setLowBattery(false);
+
+ LivisiDeviceHandler deviceHandler = createDeviceHandler(device);
+
+ deviceHandler.onDeviceStateChanged(device);
+ assertTrue(isChannelUpdated(CHANNEL_BATTERY_LOW, OnOffType.OFF));
+ }
+
+ @Test
+ public void testOnDeviceStateChanged_NotBatteryPowered() {
+ DeviceDTO device = createDevice();
+ device.setIsBatteryPowered(false);
+
+ LivisiDeviceHandler deviceHandler = createDeviceHandler(device);
+
+ deviceHandler.onDeviceStateChanged(device);
+ assertFalse(isChannelUpdated(CHANNEL_BATTERY_LOW));
+ }
+
+ @Test
+ public void testOnDeviceStateChanged_VariableActuator_On() {
+ DeviceDTO device = createDevice();
+ addCapabilityToDevice(CapabilityDTO.TYPE_VARIABLEACTUATOR, c -> c.setVariableActuatorState(true), device);
+
+ LivisiDeviceHandler deviceHandler = createDeviceHandler(device);
+
+ deviceHandler.onDeviceStateChanged(device);
+ assertTrue(isChannelUpdated(CHANNEL_SWITCH, OnOffType.ON));
+ }
+
+ @Test
+ public void testOnDeviceStateChanged_VariableActuator_Off() {
+ DeviceDTO device = createDevice();
+ addCapabilityToDevice(CapabilityDTO.TYPE_VARIABLEACTUATOR, c -> c.setVariableActuatorState(false), device);
+
+ LivisiDeviceHandler deviceHandler = createDeviceHandler(device);
+
+ deviceHandler.onDeviceStateChanged(device);
+ assertTrue(isChannelUpdated(CHANNEL_SWITCH, OnOffType.OFF));
+ }
+
+ @Test
+ public void testOnDeviceStateChanged_VariableActuator_EmptyState() {
+ DeviceDTO device = createDevice();
+ addCapabilityToDevice(CapabilityDTO.TYPE_VARIABLEACTUATOR, null, device);
+
+ LivisiDeviceHandler deviceHandler = createDeviceHandler(device);
+
+ deviceHandler.onDeviceStateChanged(device);
+ assertFalse(isChannelUpdated(CHANNEL_SWITCH));
+ }
+
+ @Test
+ public void testOnDeviceStateChanged_TemperatureSensor_FrostWarning_On() {
+ DeviceDTO device = createDevice();
+ addCapabilityToDevice(CapabilityDTO.TYPE_TEMPERATURESENSOR, c -> {
+ c.setTemperatureSensorTemperatureState(21.5);
+ c.setTemperatureSensorFrostWarningState(true);
+ }, device);
+
+ LivisiDeviceHandler deviceHandler = createDeviceHandler(device);
+
+ deviceHandler.onDeviceStateChanged(device);
+ assertTrue(isChannelUpdated(CHANNEL_CURRENT_TEMPERATURE, QuantityType.valueOf(21.5, SIUnits.CELSIUS)));
+ assertTrue(isChannelUpdated(CHANNEL_FROST_WARNING, OnOffType.ON));
+ }
+
+ @Test
+ public void testOnDeviceStateChanged_TemperatureSensor_FrostWarning_Off() {
+ DeviceDTO device = createDevice();
+ addCapabilityToDevice(CapabilityDTO.TYPE_TEMPERATURESENSOR, c -> {
+ c.setTemperatureSensorTemperatureState(21.5);
+ c.setTemperatureSensorFrostWarningState(false);
+ }, device);
+
+ LivisiDeviceHandler deviceHandler = createDeviceHandler(device);
+
+ deviceHandler.onDeviceStateChanged(device);
+ assertTrue(isChannelUpdated(CHANNEL_CURRENT_TEMPERATURE, QuantityType.valueOf(21.5, SIUnits.CELSIUS)));
+ assertTrue(isChannelUpdated(CHANNEL_FROST_WARNING, OnOffType.OFF));
+ }
+
+ @Test
+ public void testOnDeviceStateChanged_TemperatureSensor_EmptyState() {
+ DeviceDTO device = createDevice();
+ addCapabilityToDevice(CapabilityDTO.TYPE_TEMPERATURESENSOR, null, device);
+
+ LivisiDeviceHandler deviceHandler = createDeviceHandler(device);
+
+ deviceHandler.onDeviceStateChanged(device);
+ assertFalse(isChannelUpdated(CHANNEL_CURRENT_TEMPERATURE));
+ assertFalse(isChannelUpdated(CHANNEL_FROST_WARNING));
+ }
+
+ @Test
+ public void testOnDeviceStateChanged_ThermostatActuator_WindowReduction_On() {
+ DeviceDTO device = createDevice();
+ addCapabilityToDevice(CapabilityDTO.TYPE_THERMOSTATACTUATOR, c -> {
+ c.setThermostatActuatorPointTemperatureState(21.5);
+ c.setThermostatActuatorOperationModeState(STATE_VALUE_OPERATION_MODE_AUTO);
+ c.setThermostatActuatorWindowReductionActiveState(true);
+ }, device);
+
+ LivisiDeviceHandler deviceHandler = createDeviceHandler(device);
+
+ deviceHandler.onDeviceStateChanged(device);
+ assertTrue(isChannelUpdated(CHANNEL_TARGET_TEMPERATURE, QuantityType.valueOf(21.5, SIUnits.CELSIUS)));
+ assertTrue(isChannelUpdated(CHANNEL_OPERATION_MODE, new StringType(STATE_VALUE_OPERATION_MODE_AUTO)));
+ assertTrue(isChannelUpdated(CHANNEL_WINDOW_REDUCTION_ACTIVE, OnOffType.ON));
+ }
+
+ @Test
+ public void testOnDeviceStateChanged_ThermostatActuator_WindowReduction_Off() {
+ DeviceDTO device = createDevice();
+ addCapabilityToDevice(CapabilityDTO.TYPE_THERMOSTATACTUATOR, c -> {
+ c.setThermostatActuatorPointTemperatureState(21.5);
+ c.setThermostatActuatorOperationModeState(STATE_VALUE_OPERATION_MODE_MANUAL);
+ c.setThermostatActuatorWindowReductionActiveState(false);
+ }, device);
+
+ LivisiDeviceHandler deviceHandler = createDeviceHandler(device);
+
+ deviceHandler.onDeviceStateChanged(device);
+ assertTrue(isChannelUpdated(CHANNEL_TARGET_TEMPERATURE, QuantityType.valueOf(21.5, SIUnits.CELSIUS)));
+ assertTrue(isChannelUpdated(CHANNEL_OPERATION_MODE, new StringType(STATE_VALUE_OPERATION_MODE_MANUAL)));
+ assertTrue(isChannelUpdated(CHANNEL_WINDOW_REDUCTION_ACTIVE, OnOffType.OFF));
+ }
+
+ @Test
+ public void testOnDeviceStateChanged_ThermostatActuator_EmptyState() {
+ DeviceDTO device = createDevice();
+ addCapabilityToDevice(CapabilityDTO.TYPE_THERMOSTATACTUATOR, null, device);
+
+ LivisiDeviceHandler deviceHandler = createDeviceHandler(device);
+
+ deviceHandler.onDeviceStateChanged(device);
+ assertFalse(isChannelUpdated(CHANNEL_TARGET_TEMPERATURE));
+ assertFalse(isChannelUpdated(CHANNEL_OPERATION_MODE));
+ assertFalse(isChannelUpdated(CHANNEL_WINDOW_REDUCTION_ACTIVE));
+ }
+
+ @Test
+ public void testOnDeviceStateChanged_HumiditySensor_MoldWarning_On() {
+ DeviceDTO device = createDevice();
+ addCapabilityToDevice(CapabilityDTO.TYPE_HUMIDITYSENSOR, c -> {
+ c.setHumiditySensorHumidityState(35.5);
+ c.setHumiditySensorMoldWarningState(true);
+ }, device);
+
+ LivisiDeviceHandler deviceHandler = createDeviceHandler(device);
+
+ deviceHandler.onDeviceStateChanged(device);
+ assertTrue(isChannelUpdated(CHANNEL_HUMIDITY, QuantityType.valueOf(35.5, Units.PERCENT)));
+ assertTrue(isChannelUpdated(CHANNEL_MOLD_WARNING, OnOffType.ON));
+ }
+
+ @Test
+ public void testOnDeviceStateChanged_HumiditySensor_MoldWarning_Off() {
+ DeviceDTO device = createDevice();
+ addCapabilityToDevice(CapabilityDTO.TYPE_HUMIDITYSENSOR, c -> {
+ c.setHumiditySensorHumidityState(35.5);
+ c.setHumiditySensorMoldWarningState(false);
+ }, device);
+
+ LivisiDeviceHandler deviceHandler = createDeviceHandler(device);
+
+ deviceHandler.onDeviceStateChanged(device);
+ assertTrue(isChannelUpdated(CHANNEL_HUMIDITY, QuantityType.valueOf(35.5, Units.PERCENT)));
+ assertTrue(isChannelUpdated(CHANNEL_MOLD_WARNING, OnOffType.OFF));
+ }
+
+ @Test
+ public void testOnDeviceStateChanged_HumiditySensor_EmptyState() {
+ DeviceDTO device = createDevice();
+ addCapabilityToDevice(CapabilityDTO.TYPE_HUMIDITYSENSOR, null, device);
+
+ LivisiDeviceHandler deviceHandler = createDeviceHandler(device);
+
+ deviceHandler.onDeviceStateChanged(device);
+ assertFalse(isChannelUpdated(CHANNEL_HUMIDITY));
+ assertFalse(isChannelUpdated(CHANNEL_MOLD_WARNING));
+ }
+
+ @Test
+ public void testOnDeviceStateChanged_WindowDoorSensor_Open() {
+ DeviceDTO device = createDevice();
+ addCapabilityToDevice(CapabilityDTO.TYPE_WINDOWDOORSENSOR, c -> c.setWindowDoorSensorState(true), device);
+
+ LivisiDeviceHandler deviceHandler = createDeviceHandler(device);
+
+ deviceHandler.onDeviceStateChanged(device);
+ assertTrue(isChannelUpdated(CHANNEL_CONTACT, OpenClosedType.OPEN));
+ }
+
+ @Test
+ public void testOnDeviceStateChanged_WindowDoorSensor_Closed() {
+ DeviceDTO device = createDevice();
+ addCapabilityToDevice(CapabilityDTO.TYPE_WINDOWDOORSENSOR, c -> c.setWindowDoorSensorState(false), device);
+
+ LivisiDeviceHandler deviceHandler = createDeviceHandler(device);
+
+ deviceHandler.onDeviceStateChanged(device);
+ assertTrue(isChannelUpdated(CHANNEL_CONTACT, OpenClosedType.CLOSED));
+ }
+
+ @Test
+ public void testOnDeviceStateChanged_WindowDoorSensor_EmptyState() {
+ DeviceDTO device = createDevice();
+ addCapabilityToDevice(CapabilityDTO.TYPE_WINDOWDOORSENSOR, null, device);
+
+ LivisiDeviceHandler deviceHandler = createDeviceHandler(device);
+
+ deviceHandler.onDeviceStateChanged(device);
+ assertFalse(isChannelUpdated(CHANNEL_CONTACT));
+ }
+
+ @Test
+ public void testOnDeviceStateChanged_SmokeDetectorSensor_On() {
+ DeviceDTO device = createDevice();
+ addCapabilityToDevice(CapabilityDTO.TYPE_SMOKEDETECTORSENSOR, c -> c.setSmokeDetectorSensorState(true), device);
+
+ LivisiDeviceHandler deviceHandler = createDeviceHandler(device);
+
+ deviceHandler.onDeviceStateChanged(device);
+ assertTrue(isChannelUpdated(CHANNEL_SMOKE, OnOffType.ON));
+ }
+
+ @Test
+ public void testOnDeviceStateChanged_SmokeDetectorSensor_Off() {
+ DeviceDTO device = createDevice();
+ addCapabilityToDevice(CapabilityDTO.TYPE_SMOKEDETECTORSENSOR, c -> c.setSmokeDetectorSensorState(false),
+ device);
+
+ LivisiDeviceHandler deviceHandler = createDeviceHandler(device);
+
+ deviceHandler.onDeviceStateChanged(device);
+ assertTrue(isChannelUpdated(CHANNEL_SMOKE, OnOffType.OFF));
+ }
+
+ @Test
+ public void testOnDeviceStateChanged_SmokeDetectorSensor_EmptyState() {
+ DeviceDTO device = createDevice();
+ addCapabilityToDevice(CapabilityDTO.TYPE_SMOKEDETECTORSENSOR, null, device);
+
+ LivisiDeviceHandler deviceHandler = createDeviceHandler(device);
+
+ deviceHandler.onDeviceStateChanged(device);
+ assertFalse(isChannelUpdated(CHANNEL_SMOKE));
+ }
+
+ @Test
+ public void testOnDeviceStateChanged_AlarmActuator_On() {
+ DeviceDTO device = createDevice();
+ addCapabilityToDevice(CapabilityDTO.TYPE_ALARMACTUATOR, c -> c.setAlarmActuatorState(true), device);
+
+ LivisiDeviceHandler deviceHandler = createDeviceHandler(device);
+
+ deviceHandler.onDeviceStateChanged(device);
+ assertTrue(isChannelUpdated(CHANNEL_ALARM, OnOffType.ON));
+ }
+
+ @Test
+ public void testOnDeviceStateChanged_AlarmActuator_Off() {
+ DeviceDTO device = createDevice();
+ addCapabilityToDevice(CapabilityDTO.TYPE_ALARMACTUATOR, c -> c.setAlarmActuatorState(false), device);
+
+ LivisiDeviceHandler deviceHandler = createDeviceHandler(device);
+
+ deviceHandler.onDeviceStateChanged(device);
+ assertTrue(isChannelUpdated(CHANNEL_ALARM, OnOffType.OFF));
+ }
+
+ @Test
+ public void testOnDeviceStateChanged_AlarmActuator_EmptyState() {
+ DeviceDTO device = createDevice();
+ addCapabilityToDevice(CapabilityDTO.TYPE_ALARMACTUATOR, null, device);
+
+ LivisiDeviceHandler deviceHandler = createDeviceHandler(device);
+
+ deviceHandler.onDeviceStateChanged(device);
+ assertFalse(isChannelUpdated(CHANNEL_ALARM));
+ }
+
+ @Test
+ public void testOnDeviceStateChanged_SwitchActuator_On() {
+ DeviceDTO device = createDevice();
+ addCapabilityToDevice(CapabilityDTO.TYPE_SWITCHACTUATOR, c -> c.setSwitchActuatorState(true), device);
+
+ LivisiDeviceHandler deviceHandler = createDeviceHandler(device);
+
+ deviceHandler.onDeviceStateChanged(device);
+ assertTrue(isChannelUpdated(CHANNEL_SWITCH, OnOffType.ON));
+ }
+
+ @Test
+ public void testOnDeviceStateChanged_SwitchActuator_Off() {
+ DeviceDTO device = createDevice();
+ addCapabilityToDevice(CapabilityDTO.TYPE_SWITCHACTUATOR, c -> c.setSwitchActuatorState(false), device);
+
+ LivisiDeviceHandler deviceHandler = createDeviceHandler(device);
+
+ deviceHandler.onDeviceStateChanged(device);
+ assertTrue(isChannelUpdated(CHANNEL_SWITCH, OnOffType.OFF));
+ }
+
+ @Test
+ public void testOnDeviceStateChanged_SwitchActuator_EmptyState() {
+ DeviceDTO device = createDevice();
+ addCapabilityToDevice(CapabilityDTO.TYPE_SWITCHACTUATOR, null, device);
+
+ LivisiDeviceHandler deviceHandler = createDeviceHandler(device);
+
+ deviceHandler.onDeviceStateChanged(device);
+ assertFalse(isChannelUpdated(CHANNEL_SWITCH));
+ }
+
+ @Test
+ public void testOnDeviceStateChanged_DimmerActuator() {
+ DeviceDTO device = createDevice();
+ addCapabilityToDevice(CapabilityDTO.TYPE_DIMMERACTUATOR, c -> c.setDimmerActuatorState(50), device);
+
+ LivisiDeviceHandler deviceHandler = createDeviceHandler(device);
+
+ deviceHandler.onDeviceStateChanged(device);
+ assertTrue(isChannelUpdated(CHANNEL_DIMMER, new DecimalType(50)));
+ }
+
+ @Test
+ public void testOnDeviceStateChanged_DimmerActuator_EmptyState() {
+ DeviceDTO device = createDevice();
+ addCapabilityToDevice(CapabilityDTO.TYPE_DIMMERACTUATOR, null, device);
+
+ LivisiDeviceHandler deviceHandler = createDeviceHandler(device);
+
+ deviceHandler.onDeviceStateChanged(device);
+ assertFalse(isChannelUpdated(CHANNEL_DIMMER));
+ }
+
+ @Test
+ public void testOnDeviceStateChanged_RollerShutterActuator() {
+ DeviceDTO device = createDevice();
+ addCapabilityToDevice(CapabilityDTO.TYPE_ROLLERSHUTTERACTUATOR, c -> c.setRollerShutterActuatorState(40),
+ device);
+
+ LivisiDeviceHandler deviceHandler = createDeviceHandler(device);
+
+ deviceHandler.onDeviceStateChanged(device);
+ assertTrue(isChannelUpdated(CHANNEL_ROLLERSHUTTER, new DecimalType(40)));
+ }
+
+ @Test
+ public void testOnDeviceStateChanged_RollerShutterActuator_Invert_True() {
+ DeviceDTO device = createDevice();
+ addCapabilityToDevice(CapabilityDTO.TYPE_ROLLERSHUTTERACTUATOR, c -> c.setRollerShutterActuatorState(40),
+ device);
+
+ LivisiDeviceHandler deviceHandler = createDeviceHandler(device);
+
+ Channel rollerShutterChannelMock = createRollerShutterChannelMock(true);
+
+ Thing thingMock = deviceHandler.getThing();
+ when(thingMock.getChannel(CHANNEL_ROLLERSHUTTER)).thenReturn(rollerShutterChannelMock);
+
+ deviceHandler.onDeviceStateChanged(device);
+ assertTrue(isChannelUpdated(CHANNEL_ROLLERSHUTTER, new DecimalType(40)));
+ assertFalse(isChannelUpdated(CHANNEL_ROLLERSHUTTER, new DecimalType(60)));
+ }
+
+ @Test
+ public void testOnDeviceStateChanged_RollerShutterActuator_Invert_False() {
+ DeviceDTO device = createDevice();
+ addCapabilityToDevice(CapabilityDTO.TYPE_ROLLERSHUTTERACTUATOR, c -> c.setRollerShutterActuatorState(40),
+ device);
+
+ LivisiDeviceHandler deviceHandler = createDeviceHandler(device);
+
+ Channel rollerShutterChannelMock = createRollerShutterChannelMock(false);
+
+ Thing thingMock = deviceHandler.getThing();
+ when(thingMock.getChannel(CHANNEL_ROLLERSHUTTER)).thenReturn(rollerShutterChannelMock);
+
+ deviceHandler.onDeviceStateChanged(device);
+ assertFalse(isChannelUpdated(CHANNEL_ROLLERSHUTTER, new DecimalType(40)));
+ assertTrue(isChannelUpdated(CHANNEL_ROLLERSHUTTER, new DecimalType(60)));
+ }
+
+ @Test
+ public void testOnDeviceStateChanged_RollerShutterActuator_EmptyState() {
+ DeviceDTO device = createDevice();
+ addCapabilityToDevice(CapabilityDTO.TYPE_ROLLERSHUTTERACTUATOR, null, device);
+
+ LivisiDeviceHandler deviceHandler = createDeviceHandler(device);
+
+ deviceHandler.onDeviceStateChanged(device);
+ assertFalse(isChannelUpdated(CHANNEL_ROLLERSHUTTER));
+ }
+
+ @Test
+ public void testOnDeviceStateChanged_MotionDetectionSensor() {
+ DeviceDTO device = createDevice();
+ addCapabilityToDevice(CapabilityDTO.TYPE_MOTIONDETECTIONSENSOR, c -> c.setMotionDetectionSensorState(50),
+ device);
+
+ LivisiDeviceHandler deviceHandler = createDeviceHandler(device);
+
+ deviceHandler.onDeviceStateChanged(device);
+ assertTrue(isChannelUpdated(CHANNEL_MOTION_COUNT, new DecimalType(50)));
+ }
+
+ @Test
+ public void testOnDeviceStateChanged_MotionDetectionSensor_EmptyState() {
+ DeviceDTO device = createDevice();
+ addCapabilityToDevice(CapabilityDTO.TYPE_MOTIONDETECTIONSENSOR, null, device);
+
+ LivisiDeviceHandler deviceHandler = createDeviceHandler(device);
+
+ deviceHandler.onDeviceStateChanged(device);
+ assertFalse(isChannelUpdated(CHANNEL_MOTION_COUNT));
+ }
+
+ @Test
+ public void testOnDeviceStateChanged_LuminanceSensor() {
+ DeviceDTO device = createDevice();
+ addCapabilityToDevice(CapabilityDTO.TYPE_LUMINANCESENSOR, c -> c.setLuminanceSensorState(50.1), device);
+
+ LivisiDeviceHandler deviceHandler = createDeviceHandler(device);
+
+ deviceHandler.onDeviceStateChanged(device);
+ assertTrue(isChannelUpdated(CHANNEL_LUMINANCE, QuantityType.valueOf(50.1, Units.PERCENT)));
+ }
+
+ @Test
+ public void testOnDeviceStateChanged_LuminanceSensor_EmptyState() {
+ DeviceDTO device = createDevice();
+ addCapabilityToDevice(CapabilityDTO.TYPE_LUMINANCESENSOR, null, device);
+
+ LivisiDeviceHandler deviceHandler = createDeviceHandler(device);
+
+ deviceHandler.onDeviceStateChanged(device);
+ assertFalse(isChannelUpdated(CHANNEL_LUMINANCE));
+ }
+
+ @Test
+ public void testOnDeviceStateChanged_EnergyConsumptionSensor() {
+ DeviceDTO device = createDevice();
+ addCapabilityToDevice(CapabilityDTO.TYPE_ENERGYCONSUMPTIONSENSOR, c -> {
+ c.setEnergyConsumptionSensorEnergyConsumptionMonthKWhState(201.51);
+ c.setEnergyConsumptionSensorAbsoluteEnergyConsumptionState(5500.51);
+ c.setEnergyConsumptionSensorEnergyConsumptionMonthEuroState(80.32);
+ c.setEnergyConsumptionSensorEnergyConsumptionDayEuroState(3.72);
+ c.setEnergyConsumptionSensorEnergyConsumptionDayKWhState(8.71);
+ }, device);
+
+ LivisiDeviceHandler deviceHandler = createDeviceHandler(device);
+
+ deviceHandler.onDeviceStateChanged(device);
+ assertTrue(isChannelUpdated(CHANNEL_ENERGY_CONSUMPTION_MONTH_KWH,
+ QuantityType.valueOf(201.51, Units.KILOWATT_HOUR)));
+ assertTrue(isChannelUpdated(CHANNEL_ABOLUTE_ENERGY_CONSUMPTION,
+ QuantityType.valueOf(5500.51, Units.KILOWATT_HOUR)));
+ assertTrue(isChannelUpdated(CHANNEL_ENERGY_CONSUMPTION_MONTH_EURO, new DecimalType(80.32)));
+ assertTrue(isChannelUpdated(CHANNEL_ENERGY_CONSUMPTION_DAY_EURO, new DecimalType(3.72)));
+ assertTrue(
+ isChannelUpdated(CHANNEL_ENERGY_CONSUMPTION_DAY_KWH, QuantityType.valueOf(8.71, Units.KILOWATT_HOUR)));
+ }
+
+ @Test
+ public void testOnDeviceStateChanged_EnergyConsumptionSensor_EmptyState() {
+ DeviceDTO device = createDevice();
+ addCapabilityToDevice(CapabilityDTO.TYPE_ENERGYCONSUMPTIONSENSOR, null, device);
+
+ LivisiDeviceHandler deviceHandler = createDeviceHandler(device);
+
+ deviceHandler.onDeviceStateChanged(device);
+ assertFalse(isChannelUpdated(CHANNEL_ENERGY_CONSUMPTION_MONTH_KWH));
+ assertFalse(isChannelUpdated(CHANNEL_ABOLUTE_ENERGY_CONSUMPTION));
+ assertFalse(isChannelUpdated(CHANNEL_ENERGY_CONSUMPTION_MONTH_EURO));
+ assertFalse(isChannelUpdated(CHANNEL_ENERGY_CONSUMPTION_DAY_EURO));
+ assertFalse(isChannelUpdated(CHANNEL_ENERGY_CONSUMPTION_DAY_KWH));
+ }
+
+ @Test
+ public void testOnDeviceStateChanged_PowerConsumptionSensor() {
+ DeviceDTO device = createDevice();
+ addCapabilityToDevice(CapabilityDTO.TYPE_POWERCONSUMPTIONSENSOR,
+ c -> c.setPowerConsumptionSensorPowerConsumptionWattState(350.5), device);
+
+ LivisiDeviceHandler deviceHandler = createDeviceHandler(device);
+
+ deviceHandler.onDeviceStateChanged(device);
+ assertTrue(isChannelUpdated(CHANNEL_POWER_CONSUMPTION_WATT, QuantityType.valueOf(350.5, Units.WATT)));
+ }
+
+ @Test
+ public void testOnDeviceStateChanged_PowerConsumptionSensor_EmptyState() {
+ DeviceDTO device = createDevice();
+ addCapabilityToDevice(CapabilityDTO.TYPE_POWERCONSUMPTIONSENSOR, null, device);
+
+ LivisiDeviceHandler deviceHandler = createDeviceHandler(device);
+
+ deviceHandler.onDeviceStateChanged(device);
+ assertFalse(isChannelUpdated(CHANNEL_POWER_CONSUMPTION_WATT));
+ }
+
+ @Test
+ public void testOnDeviceStateChanged_GenerationMeterEnergySensor() {
+ DeviceDTO device = createDevice();
+ addCapabilityToDevice(CapabilityDTO.TYPE_GENERATIONMETERENERGYSENSOR, c -> {
+ c.setGenerationMeterEnergySensorEnergyPerMonthInKWhState(201.51);
+ c.setGenerationMeterEnergySensorTotalEnergyState(5500.51);
+ c.setGenerationMeterEnergySensorEnergyPerMonthInEuroState(80.32);
+ c.setGenerationMeterEnergySensorEnergyPerDayInEuroState(3.72);
+ c.setGenerationMeterEnergySensorEnergyPerDayInKWhState(8.71);
+ }, device);
+
+ LivisiDeviceHandler deviceHandler = createDeviceHandler(device);
+
+ deviceHandler.onDeviceStateChanged(device);
+ assertTrue(isChannelUpdated(CHANNEL_ENERGY_GENERATION_MONTH_KWH,
+ QuantityType.valueOf(201.51, Units.KILOWATT_HOUR)));
+ assertTrue(
+ isChannelUpdated(CHANNEL_TOTAL_ENERGY_GENERATION, QuantityType.valueOf(5500.51, Units.KILOWATT_HOUR)));
+ assertTrue(isChannelUpdated(CHANNEL_ENERGY_GENERATION_MONTH_EURO, new DecimalType(80.32)));
+ assertTrue(isChannelUpdated(CHANNEL_ENERGY_GENERATION_DAY_EURO, new DecimalType(3.72)));
+ assertTrue(
+ isChannelUpdated(CHANNEL_ENERGY_GENERATION_DAY_KWH, QuantityType.valueOf(8.71, Units.KILOWATT_HOUR)));
+ }
+
+ @Test
+ public void testOnDeviceStateChanged_GenerationMeterEnergySensor_EmptyState() {
+ DeviceDTO device = createDevice();
+ addCapabilityToDevice(CapabilityDTO.TYPE_GENERATIONMETERENERGYSENSOR, null, device);
+
+ LivisiDeviceHandler deviceHandler = createDeviceHandler(device);
+
+ deviceHandler.onDeviceStateChanged(device);
+ assertFalse(isChannelUpdated(CHANNEL_ENERGY_GENERATION_MONTH_KWH));
+ assertFalse(isChannelUpdated(CHANNEL_TOTAL_ENERGY_GENERATION));
+ assertFalse(isChannelUpdated(CHANNEL_ENERGY_GENERATION_MONTH_EURO));
+ assertFalse(isChannelUpdated(CHANNEL_ENERGY_GENERATION_DAY_EURO));
+ assertFalse(isChannelUpdated(CHANNEL_ENERGY_GENERATION_DAY_KWH));
+ }
+
+ @Test
+ public void testOnDeviceStateChanged_GenerationMeterPowerConsumptionSensor() {
+ DeviceDTO device = createDevice();
+ addCapabilityToDevice(CapabilityDTO.TYPE_GENERATIONMETERPOWERCONSUMPTIONSENSOR,
+ c -> c.setGenerationMeterPowerConsumptionSensorPowerInWattState(350.5), device);
+
+ LivisiDeviceHandler deviceHandler = createDeviceHandler(device);
+
+ deviceHandler.onDeviceStateChanged(device);
+ assertTrue(isChannelUpdated(CHANNEL_POWER_GENERATION_WATT, QuantityType.valueOf(350.5, Units.WATT)));
+ }
+
+ @Test
+ public void testOnDeviceStateChanged_GenerationMeterPowerConsumptionSensor_EmptyState() {
+ DeviceDTO device = createDevice();
+ addCapabilityToDevice(CapabilityDTO.TYPE_GENERATIONMETERPOWERCONSUMPTIONSENSOR, null, device);
+
+ LivisiDeviceHandler deviceHandler = createDeviceHandler(device);
+
+ deviceHandler.onDeviceStateChanged(device);
+ assertFalse(isChannelUpdated(CHANNEL_POWER_GENERATION_WATT));
+ }
+
+ @Test
+ public void testOnDeviceStateChanged_TwoWayMeterEnergyConsumptionSensor() {
+ DeviceDTO device = createDevice();
+ addCapabilityToDevice(CapabilityDTO.TYPE_TWOWAYMETERENERGYCONSUMPTIONSENSOR, c -> {
+ c.setTwoWayMeterEnergyConsumptionSensorEnergyPerMonthInKWhState(201.51);
+ c.setTwoWayMeterEnergyConsumptionSensorTotalEnergyState(5500.51);
+ c.setTwoWayMeterEnergyConsumptionSensorEnergyPerMonthInEuroState(80.32);
+ c.setTwoWayMeterEnergyConsumptionSensorEnergyPerDayInEuroState(3.72);
+ c.setTwoWayMeterEnergyConsumptionSensorEnergyPerDayInKWhState(8.71);
+ }, device);
+
+ LivisiDeviceHandler deviceHandler = createDeviceHandler(device);
+
+ deviceHandler.onDeviceStateChanged(device);
+ assertTrue(isChannelUpdated(CHANNEL_ENERGY_MONTH_KWH, QuantityType.valueOf(201.51, Units.KILOWATT_HOUR)));
+ assertTrue(isChannelUpdated(CHANNEL_TOTAL_ENERGY, QuantityType.valueOf(5500.51, Units.KILOWATT_HOUR)));
+ assertTrue(isChannelUpdated(CHANNEL_ENERGY_MONTH_EURO, new DecimalType(80.32)));
+ assertTrue(isChannelUpdated(CHANNEL_ENERGY_DAY_EURO, new DecimalType(3.72)));
+ assertTrue(isChannelUpdated(CHANNEL_ENERGY_DAY_KWH, QuantityType.valueOf(8.71, Units.KILOWATT_HOUR)));
+ }
+
+ @Test
+ public void testOnDeviceStateChanged_TwoWayMeterEnergyConsumptionSensor_EmptyState() {
+ DeviceDTO device = createDevice();
+ addCapabilityToDevice(CapabilityDTO.TYPE_TWOWAYMETERENERGYCONSUMPTIONSENSOR, null, device);
+
+ LivisiDeviceHandler deviceHandler = createDeviceHandler(device);
+
+ deviceHandler.onDeviceStateChanged(device);
+ assertFalse(isChannelUpdated(CHANNEL_ENERGY_MONTH_KWH));
+ assertFalse(isChannelUpdated(CHANNEL_TOTAL_ENERGY));
+ assertFalse(isChannelUpdated(CHANNEL_ENERGY_MONTH_EURO));
+ assertFalse(isChannelUpdated(CHANNEL_ENERGY_DAY_EURO));
+ assertFalse(isChannelUpdated(CHANNEL_ENERGY_DAY_KWH));
+ }
+
+ @Test
+ public void testOnDeviceStateChanged_TwoWayMeterEnergyFeedSensor() {
+ DeviceDTO device = createDevice();
+ addCapabilityToDevice(CapabilityDTO.TYPE_TWOWAYMETERENERGYFEEDSENSOR, c -> {
+ c.setTwoWayMeterEnergyFeedSensorEnergyPerMonthInKWhState(201.51);
+ c.setTwoWayMeterEnergyFeedSensorTotalEnergyState(5500.51);
+ c.setTwoWayMeterEnergyFeedSensorEnergyPerMonthInEuroState(80.32);
+ c.setTwoWayMeterEnergyFeedSensorEnergyPerDayInEuroState(3.72);
+ c.setTwoWayMeterEnergyFeedSensorEnergyPerDayInKWhState(8.71);
+ }, device);
+
+ LivisiDeviceHandler deviceHandler = createDeviceHandler(device);
+
+ deviceHandler.onDeviceStateChanged(device);
+ assertTrue(isChannelUpdated(CHANNEL_ENERGY_FEED_MONTH_KWH, QuantityType.valueOf(201.51, Units.KILOWATT_HOUR)));
+ assertTrue(isChannelUpdated(CHANNEL_TOTAL_ENERGY_FED, QuantityType.valueOf(5500.51, Units.KILOWATT_HOUR)));
+ assertTrue(isChannelUpdated(CHANNEL_ENERGY_FEED_MONTH_EURO, new DecimalType(80.32)));
+ assertTrue(isChannelUpdated(CHANNEL_ENERGY_FEED_DAY_EURO, new DecimalType(3.72)));
+ assertTrue(isChannelUpdated(CHANNEL_ENERGY_FEED_DAY_KWH, QuantityType.valueOf(8.71, Units.KILOWATT_HOUR)));
+ }
+
+ @Test
+ public void testOnDeviceStateChanged_TwoWayMeterEnergyFeedSensor_EmptyState() {
+ DeviceDTO device = createDevice();
+ addCapabilityToDevice(CapabilityDTO.TYPE_TWOWAYMETERENERGYFEEDSENSOR, null, device);
+
+ LivisiDeviceHandler deviceHandler = createDeviceHandler(device);
+
+ deviceHandler.onDeviceStateChanged(device);
+ assertFalse(isChannelUpdated(CHANNEL_ENERGY_FEED_MONTH_KWH));
+ assertFalse(isChannelUpdated(CHANNEL_TOTAL_ENERGY_FED));
+ assertFalse(isChannelUpdated(CHANNEL_ENERGY_FEED_MONTH_EURO));
+ assertFalse(isChannelUpdated(CHANNEL_ENERGY_FEED_DAY_EURO));
+ assertFalse(isChannelUpdated(CHANNEL_ENERGY_FEED_DAY_KWH));
+ }
+
+ @Test
+ public void testOnDeviceStateChanged_TwoWayMeterPowerConsumptionSensor() {
+ DeviceDTO device = createDevice();
+ addCapabilityToDevice(CapabilityDTO.TYPE_TWOWAYMETERPOWERCONSUMPTIONSENSOR,
+ c -> c.setTwoWayMeterPowerConsumptionSensorPowerInWattState(350.5), device);
+
+ LivisiDeviceHandler deviceHandler = createDeviceHandler(device);
+
+ deviceHandler.onDeviceStateChanged(device);
+ assertTrue(isChannelUpdated(CHANNEL_POWER_WATT, QuantityType.valueOf(350.5, Units.WATT)));
+ }
+
+ @Test
+ public void testOnDeviceStateChanged_TwoWayMeterPowerConsumptionSensor_EmptyState() {
+ DeviceDTO device = createDevice();
+ addCapabilityToDevice(CapabilityDTO.TYPE_TWOWAYMETERPOWERCONSUMPTIONSENSOR, null, device);
+
+ LivisiDeviceHandler deviceHandler = createDeviceHandler(device);
+
+ deviceHandler.onDeviceStateChanged(device);
+ assertFalse(isChannelUpdated(CHANNEL_POWER_WATT));
+ }
+
+ @Test
+ public void testOnDeviceStateChanged_PushButtonSensor_Button1_ShortPress() {
+ DeviceDTO device = createDevice();
+ addCapabilityToDevice(CapabilityDTO.TYPE_PUSHBUTTONSENSOR, c -> {
+ c.setPushButtonSensorCounterState(10);
+ c.setPushButtonSensorButtonIndexState(0);
+ c.setPushButtonSensorButtonIndexType("ShortPress");
+ }, device);
+
+ LivisiDeviceHandler deviceHandler = createDeviceHandler(device);
+
+ deviceHandler.onDeviceStateChanged(device);
+ assertTrue(isChannelUpdated("button1Count", new DecimalType(10)));
+ assertTrue(isChannelTriggered("button1", CommonTriggerEvents.SHORT_PRESSED));
+ assertFalse(isChannelTriggered("button1", CommonTriggerEvents.LONG_PRESSED));
+ }
+
+ @Test
+ public void testOnDeviceStateChanged_PushButtonSensor_Button1_LongPress() {
+ DeviceDTO device = createDevice();
+ addCapabilityToDevice(CapabilityDTO.TYPE_PUSHBUTTONSENSOR, c -> {
+ c.setPushButtonSensorCounterState(10);
+ c.setPushButtonSensorButtonIndexState(0);
+ c.setPushButtonSensorButtonIndexType("LongPress");
+ }, device);
+
+ LivisiDeviceHandler deviceHandler = createDeviceHandler(device);
+
+ deviceHandler.onDeviceStateChanged(device);
+ assertTrue(isChannelUpdated("button1Count", new DecimalType(10)));
+ assertFalse(isChannelTriggered("button1", CommonTriggerEvents.SHORT_PRESSED));
+ assertTrue(isChannelTriggered("button1", CommonTriggerEvents.LONG_PRESSED));
+ }
+
+ @Test
+ public void testOnDeviceStateChanged_PushButtonSensor_Button2_ShortPress() {
+ DeviceDTO device = createDevice();
+ addCapabilityToDevice(CapabilityDTO.TYPE_PUSHBUTTONSENSOR, c -> {
+ c.setPushButtonSensorCounterState(10);
+ c.setPushButtonSensorButtonIndexState(1);
+ c.setPushButtonSensorButtonIndexType("ShortPress");
+ }, device);
+
+ LivisiDeviceHandler deviceHandler = createDeviceHandler(device);
+
+ deviceHandler.onDeviceStateChanged(device);
+ assertTrue(isChannelUpdated("button2Count", new DecimalType(10)));
+ assertTrue(isChannelTriggered("button2", CommonTriggerEvents.SHORT_PRESSED));
+ assertFalse(isChannelTriggered("button2", CommonTriggerEvents.LONG_PRESSED));
+ }
+
+ @Test
+ public void testOnDeviceStateChanged_PushButtonSensor_Button2_LongPress() {
+ DeviceDTO device = createDevice();
+ addCapabilityToDevice(CapabilityDTO.TYPE_PUSHBUTTONSENSOR, c -> {
+ c.setPushButtonSensorCounterState(10);
+ c.setPushButtonSensorButtonIndexState(1);
+ c.setPushButtonSensorButtonIndexType("LongPress");
+ }, device);
+
+ LivisiDeviceHandler deviceHandler = createDeviceHandler(device);
+
+ deviceHandler.onDeviceStateChanged(device);
+ assertTrue(isChannelUpdated("button2Count", new DecimalType(10)));
+ assertFalse(isChannelTriggered("button2", CommonTriggerEvents.SHORT_PRESSED));
+ assertTrue(isChannelTriggered("button2", CommonTriggerEvents.LONG_PRESSED));
+ }
+
+ @Test
+ public void testOnDeviceStateChanged_PushButtonSensor_EmptyState() {
+ DeviceDTO device = createDevice();
+ addCapabilityToDevice(CapabilityDTO.TYPE_PUSHBUTTONSENSOR, null, device);
+
+ LivisiDeviceHandler deviceHandler = createDeviceHandler(device);
+
+ deviceHandler.onDeviceStateChanged(device);
+ assertFalse(isChannelUpdated(CHANNEL_BUTTON_COUNT));
+ assertFalse(isChannelUpdated("button1Count"));
+ assertFalse(isChannelTriggered("button1", CommonTriggerEvents.SHORT_PRESSED));
+ assertFalse(isChannelTriggered("button1", CommonTriggerEvents.LONG_PRESSED));
+ }
+
+ @Test
+ public void testOnDeviceStateChanged_Event_LinkedToDevice() {
+ DeviceStateDTO deviceState = new DeviceStateDTO();
+ deviceState.setReachable(true);
+
+ DeviceDTO device = createDevice();
+ device.setDeviceState(deviceState);
+
+ LivisiDeviceHandler deviceHandler = createDeviceHandler(device);
+
+ EventDTO event = new EventDTO();
+ event.setSource(LINK_TYPE_DEVICE);
+
+ deviceHandler.onDeviceStateChanged(device, event);
+ assertEquals(ThingStatus.ONLINE, deviceHandler.getThing().getStatus());
+ assertEquals(ThingStatusDetail.NONE, deviceHandler.getThing().getStatusInfo().getStatusDetail());
+ }
+
+ @Test
+ public void testOnDeviceStateChanged_Event_LinkedToCapability_WithoutState() {
+ DeviceDTO device = createDevice();
+
+ CapabilityConfigDTO capabilityConfig = new CapabilityConfigDTO();
+ capabilityConfig.setName("capabilityName");
+
+ final String capabilityId = "capabilityId";
+ CapabilityDTO capability = new CapabilityDTO();
+ capability.setId(capabilityId);
+ capability.setType(CapabilityDTO.TYPE_SWITCHACTUATOR);
+ capability.setConfig(capabilityConfig);
+ device.getCapabilityMap().put(capabilityId, capability);
+
+ DeviceDTO refreshedDevice = createDevice();
+
+ CapabilityStateDTO capabilityState = new CapabilityStateDTO();
+ capabilityState.setSwitchActuatorState(true);
+
+ CapabilityDTO refreshedCapability = new CapabilityDTO();
+ refreshedCapability.setId(capabilityId);
+ refreshedCapability.setType(CapabilityDTO.TYPE_SWITCHACTUATOR);
+ refreshedCapability.setConfig(capabilityConfig);
+ refreshedCapability.setCapabilityState(capabilityState);
+ refreshedDevice.getCapabilityMap().put(capabilityId, refreshedCapability);
+
+ LivisiDeviceHandler deviceHandler = createDeviceHandler(device);
+ when(bridgeHandlerMock.refreshDevice(any())).thenReturn(Optional.of(refreshedDevice));
+
+ EventDTO event = createCapabilityEvent(c -> c.setOnState(true));
+
+ deviceHandler.onDeviceStateChanged(device, event);
+ assertTrue(isChannelUpdated(CHANNEL_SWITCH, OnOffType.ON));
+ }
+
+ @Test
+ public void testOnDeviceStateChanged_Event_LinkedToCapability_WithoutStateAlsoAfterRefresh() {
+ DeviceDTO device = createDevice();
+
+ CapabilityConfigDTO capabilityConfig = new CapabilityConfigDTO();
+ capabilityConfig.setName("capabilityName");
+
+ final String capabilityId = "capabilityId";
+ CapabilityDTO capability = new CapabilityDTO();
+ capability.setId(capabilityId);
+ capability.setType(CapabilityDTO.TYPE_SWITCHACTUATOR);
+ capability.setConfig(capabilityConfig);
+ device.getCapabilityMap().put(capabilityId, capability);
+
+ DeviceDTO refreshedDevice = createDevice();
+
+ CapabilityDTO refreshedCapability = new CapabilityDTO();
+ refreshedCapability.setId(capabilityId);
+ refreshedCapability.setType(CapabilityDTO.TYPE_SWITCHACTUATOR);
+ refreshedCapability.setConfig(capabilityConfig);
+ refreshedDevice.getCapabilityMap().put(capabilityId, refreshedCapability);
+
+ LivisiDeviceHandler deviceHandler = createDeviceHandler(device);
+ when(bridgeHandlerMock.refreshDevice(any())).thenReturn(Optional.of(refreshedDevice));
+
+ EventDTO event = createCapabilityEvent(c -> c.setOnState(true));
+
+ deviceHandler.onDeviceStateChanged(device, event);
+ // Channels should only get updated when the device have a state.
+ assertFalse(isChannelUpdated(CHANNEL_SWITCH));
+ }
+
+ @Test
+ public void testOnDeviceStateChanged_Event_VariableActuator() {
+ DeviceDTO device = createDevice();
+ addCapabilityToDevice(CapabilityDTO.TYPE_VARIABLEACTUATOR, null, device);
+
+ LivisiDeviceHandler deviceHandler = createDeviceHandler(device);
+
+ EventDTO event = createCapabilityEvent(c -> c.setValue(true));
+
+ deviceHandler.onDeviceStateChanged(device, event);
+ assertTrue(isChannelUpdated(CHANNEL_SWITCH, OnOffType.ON));
+ }
+
+ @Test
+ public void testOnDeviceStateChanged_Event_SwitchActuator() {
+ DeviceDTO device = createDevice();
+ addCapabilityToDevice(CapabilityDTO.TYPE_SWITCHACTUATOR, null, device);
+
+ LivisiDeviceHandler deviceHandler = createDeviceHandler(device);
+
+ EventDTO event = createCapabilityEvent(c -> c.setOnState(true));
+
+ deviceHandler.onDeviceStateChanged(device, event);
+ assertTrue(isChannelUpdated(CHANNEL_SWITCH, OnOffType.ON));
+ }
+
+ @Test
+ public void testOnDeviceStateChanged_Event_DimmerActuator() {
+ DeviceDTO device = createDevice();
+ addCapabilityToDevice(CapabilityDTO.TYPE_DIMMERACTUATOR, null, device);
+
+ LivisiDeviceHandler deviceHandler = createDeviceHandler(device);
+
+ EventDTO event = createCapabilityEvent(c -> c.setDimLevel(50));
+
+ deviceHandler.onDeviceStateChanged(device, event);
+ assertTrue(isChannelUpdated(CHANNEL_DIMMER, new DecimalType(50)));
+ }
+
+ @Test
+ public void testOnDeviceStateChanged_Event_RollerShutterActuator() {
+ DeviceDTO device = createDevice();
+ addCapabilityToDevice(CapabilityDTO.TYPE_ROLLERSHUTTERACTUATOR, null, device);
+
+ LivisiDeviceHandler deviceHandler = createDeviceHandler(device);
+
+ EventDTO event = createCapabilityEvent(c -> c.setShutterLevel(50));
+
+ deviceHandler.onDeviceStateChanged(device, event);
+ assertTrue(isChannelUpdated(CHANNEL_ROLLERSHUTTER, new DecimalType(50)));
+ }
+
+ @Test
+ public void testOnDeviceStateChanged_Event_RollerShutter_PushButtonSensor() {
+ DeviceDTO device = createDevice();
+ addCapabilityToDevice(CapabilityDTO.TYPE_ROLLERSHUTTERACTUATOR, null, device);
+ addCapabilityToDevice(CapabilityDTO.TYPE_PUSHBUTTONSENSOR, null, device);
+
+ LivisiDeviceHandler deviceHandler = createDeviceHandler(device);
+
+ EventDTO event = createCapabilityEvent(c -> {
+ c.setKeyPressCounter(10);
+ c.setKeyPressButtonIndex(0);
+ c.setKeyPressType("ShortPress");
+ });
+ event.setType("ButtonPressed");
+ event.setNamespace("CosipDevices.RWE");
+
+ deviceHandler.onDeviceStateChanged(device, event);
+ assertTrue(isChannelUpdated("button1Count", new DecimalType(10)));
+ assertTrue(isChannelTriggered("button1", CommonTriggerEvents.SHORT_PRESSED));
+ assertFalse(isChannelTriggered("button1", CommonTriggerEvents.LONG_PRESSED));
+ }
+
+ @Test
+ public void testOnDeviceStateChanged_Event_TemperatureSensor() {
+ DeviceDTO device = createDevice();
+ addCapabilityToDevice(CapabilityDTO.TYPE_TEMPERATURESENSOR, null, device);
+
+ LivisiDeviceHandler deviceHandler = createDeviceHandler(device);
+
+ EventDTO event = createCapabilityEvent(c -> {
+ c.setTemperature(21.5);
+ c.setFrostWarning(true);
+ });
+
+ deviceHandler.onDeviceStateChanged(device, event);
+ assertTrue(isChannelUpdated(CHANNEL_CURRENT_TEMPERATURE, QuantityType.valueOf(21.5, SIUnits.CELSIUS)));
+ assertTrue(isChannelUpdated(CHANNEL_FROST_WARNING, OnOffType.ON));
+ }
+
+ @Test
+ public void testOnDeviceStateChanged_Event_ThermostatSensor() {
+ DeviceDTO device = createDevice();
+ addCapabilityToDevice(CapabilityDTO.TYPE_THERMOSTATACTUATOR, null, device);
+
+ LivisiDeviceHandler deviceHandler = createDeviceHandler(device);
+
+ EventDTO event = createCapabilityEvent(c -> {
+ c.setPointTemperature(21.5);
+ c.setOperationMode(STATE_VALUE_OPERATION_MODE_AUTO);
+ c.setWindowReductionActive(true);
+ });
+
+ deviceHandler.onDeviceStateChanged(device, event);
+ assertTrue(isChannelUpdated(CHANNEL_TARGET_TEMPERATURE, QuantityType.valueOf(21.5, SIUnits.CELSIUS)));
+ assertTrue(isChannelUpdated(CHANNEL_OPERATION_MODE, new StringType(STATE_VALUE_OPERATION_MODE_AUTO)));
+ assertTrue(isChannelUpdated(CHANNEL_WINDOW_REDUCTION_ACTIVE, OnOffType.ON));
+ }
+
+ @Test
+ public void testOnDeviceStateChanged_Event_HumiditySensor() {
+ DeviceDTO device = createDevice();
+ addCapabilityToDevice(CapabilityDTO.TYPE_HUMIDITYSENSOR, null, device);
+
+ LivisiDeviceHandler deviceHandler = createDeviceHandler(device);
+
+ EventDTO event = createCapabilityEvent(c -> {
+ c.setHumidity(35.5);
+ c.setMoldWarning(true);
+ });
+
+ deviceHandler.onDeviceStateChanged(device, event);
+ assertTrue(isChannelUpdated(CHANNEL_HUMIDITY, QuantityType.valueOf(35.5, Units.PERCENT)));
+ assertTrue(isChannelUpdated(CHANNEL_MOLD_WARNING, OnOffType.ON));
+ }
+
+ @Test
+ public void testOnDeviceStateChanged_Event_WindowDoorSensor() {
+ DeviceDTO device = createDevice();
+ addCapabilityToDevice(CapabilityDTO.TYPE_WINDOWDOORSENSOR, null, device);
+
+ LivisiDeviceHandler deviceHandler = createDeviceHandler(device);
+
+ EventDTO event = createCapabilityEvent(c -> c.setIsOpen(true));
+
+ deviceHandler.onDeviceStateChanged(device, event);
+ assertTrue(isChannelUpdated(CHANNEL_CONTACT, OpenClosedType.OPEN));
+ }
+
+ @Test
+ public void testOnDeviceStateChanged_Event_SmokeDetectorSensor() {
+ DeviceDTO device = createDevice();
+ addCapabilityToDevice(CapabilityDTO.TYPE_SMOKEDETECTORSENSOR, null, device);
+
+ LivisiDeviceHandler deviceHandler = createDeviceHandler(device);
+
+ EventDTO event = createCapabilityEvent(c -> c.setIsSmokeAlarm(true));
+
+ deviceHandler.onDeviceStateChanged(device, event);
+ assertTrue(isChannelUpdated(CHANNEL_SMOKE, OnOffType.ON));
+ }
+
+ @Test
+ public void testOnDeviceStateChanged_Event_AlarmActuator() {
+ DeviceDTO device = createDevice();
+ addCapabilityToDevice(CapabilityDTO.TYPE_ALARMACTUATOR, null, device);
+
+ LivisiDeviceHandler deviceHandler = createDeviceHandler(device);
+
+ EventDTO event = createCapabilityEvent(c -> c.setOnState(true));
+
+ deviceHandler.onDeviceStateChanged(device, event);
+ assertTrue(isChannelUpdated(CHANNEL_ALARM, OnOffType.ON));
+ }
+
+ @Test
+ public void testOnDeviceStateChanged_Event_MotionDetectionSensor() {
+ DeviceDTO device = createDevice();
+ addCapabilityToDevice(CapabilityDTO.TYPE_MOTIONDETECTIONSENSOR, null, device);
+
+ LivisiDeviceHandler deviceHandler = createDeviceHandler(device);
+
+ EventDTO event = createCapabilityEvent(c -> c.setMotionDetectedCount(50));
+
+ deviceHandler.onDeviceStateChanged(device, event);
+ assertTrue(isChannelUpdated(CHANNEL_MOTION_COUNT, new DecimalType(50)));
+ }
+
+ @Test
+ public void testOnDeviceStateChanged_Event_LuminanceSensor() {
+ DeviceDTO device = createDevice();
+ addCapabilityToDevice(CapabilityDTO.TYPE_LUMINANCESENSOR, null, device);
+
+ LivisiDeviceHandler deviceHandler = createDeviceHandler(device);
+
+ EventDTO event = createCapabilityEvent(c -> c.setLuminance(50.1));
+
+ deviceHandler.onDeviceStateChanged(device, event);
+ assertTrue(isChannelUpdated(CHANNEL_LUMINANCE, QuantityType.valueOf(50.1, Units.PERCENT)));
+ }
+
+ @Test
+ public void testOnDeviceStateChanged_Event_PushButtonSensor() {
+ DeviceDTO device = createDevice();
+ addCapabilityToDevice(CapabilityDTO.TYPE_PUSHBUTTONSENSOR, null, device);
+
+ LivisiDeviceHandler deviceHandler = createDeviceHandler(device);
+
+ EventDTO event = createCapabilityEvent(c -> {
+ c.setKeyPressCounter(10);
+ c.setKeyPressButtonIndex(0);
+ c.setKeyPressType("ShortPress");
+ });
+
+ deviceHandler.onDeviceStateChanged(device, event);
+ assertTrue(isChannelUpdated("button1Count", new DecimalType(10)));
+ assertTrue(isChannelTriggered("button1", CommonTriggerEvents.PRESSED));
+ assertTrue(isChannelTriggered("button1", CommonTriggerEvents.SHORT_PRESSED));
+ assertFalse(isChannelTriggered("button1", CommonTriggerEvents.LONG_PRESSED));
+ assertFalse(isChannelTriggered("button2", CommonTriggerEvents.PRESSED));
+ }
+
+ @Test
+ public void testOnDeviceStateChanged_StateChangedEvent_PushButtonSensor_SHC_Classic() {
+ when(bridgeHandlerMock.isSHCClassic()).thenReturn(true);
+
+ DeviceDTO device = createDevice();
+ addCapabilityToDevice(CapabilityDTO.TYPE_PUSHBUTTONSENSOR, null, device);
+
+ LivisiDeviceHandler deviceHandler = createDeviceHandler(device);
+
+ // SHC Classic sends only StateChanged events with this information
+ EventDTO event = createCapabilityEvent(c -> {
+ c.setLastKeyPressButtonIndex(0);
+ c.setLastKeyPressCounter(10);
+ });
+
+ // Nothing should get processed, because it should only react on the more detailed ButtonPressed events
+ deviceHandler.onDeviceStateChanged(device, event);
+ assertFalse(isChannelUpdated("button1Count", new DecimalType(10)));
+ assertFalse(isChannelTriggered("button1", CommonTriggerEvents.PRESSED));
+ assertFalse(isChannelTriggered("button1", CommonTriggerEvents.SHORT_PRESSED)); // not available for SHC Classic
+ assertFalse(isChannelTriggered("button1", CommonTriggerEvents.LONG_PRESSED)); // not available for SHC Classic
+ assertFalse(isChannelTriggered("button2", CommonTriggerEvents.PRESSED));
+ }
+
+ @Test
+ public void testOnDeviceStateChanged_StateChangedEvent_PushButtonSensor_SHCA() {
+ when(bridgeHandlerMock.isSHCClassic()).thenReturn(false);
+
+ DeviceDTO device = createDevice();
+ addCapabilityToDevice(CapabilityDTO.TYPE_PUSHBUTTONSENSOR, null, device);
+
+ LivisiDeviceHandler deviceHandler = createDeviceHandler(device);
+
+ // StateChanged events only have with this information
+ EventDTO event = createCapabilityEvent(c -> {
+ c.setLastKeyPressButtonIndex(0);
+ c.setLastKeyPressCounter(10);
+ });
+
+ // Nothing should get processed, because it should only react on the more detailed ButtonPressed events
+ deviceHandler.onDeviceStateChanged(device, event);
+ assertFalse(isChannelUpdated("button1Count", new DecimalType(10)));
+ assertFalse(isChannelTriggered("button1", CommonTriggerEvents.PRESSED));
+ assertFalse(isChannelTriggered("button1", CommonTriggerEvents.SHORT_PRESSED));
+ assertFalse(isChannelTriggered("button1", CommonTriggerEvents.LONG_PRESSED));
+ assertFalse(isChannelTriggered("button2", CommonTriggerEvents.PRESSED));
+ }
+
+ @Test
+ public void testOnDeviceStateChanged_Event_EnergyConsumptionSensor() {
+ DeviceDTO device = createDevice();
+ addCapabilityToDevice(CapabilityDTO.TYPE_ENERGYCONSUMPTIONSENSOR, null, device);
+
+ LivisiDeviceHandler deviceHandler = createDeviceHandler(device);
+
+ EventDTO event = createCapabilityEvent(c -> {
+ c.setEnergyConsumptionMonthKWh(201.51);
+ c.setAbsoluteEnergyConsumption(5500.51);
+ c.setEnergyConsumptionMonthEuro(80.32);
+ c.setEnergyConsumptionDayEuro(3.72);
+ c.setEnergyConsumptionDayKWh(8.71);
+ });
+
+ deviceHandler.onDeviceStateChanged(device, event);
+ assertTrue(isChannelUpdated(CHANNEL_ENERGY_CONSUMPTION_MONTH_KWH,
+ QuantityType.valueOf(201.51, Units.KILOWATT_HOUR)));
+ assertTrue(isChannelUpdated(CHANNEL_ABOLUTE_ENERGY_CONSUMPTION,
+ QuantityType.valueOf(5500.51, Units.KILOWATT_HOUR)));
+ assertTrue(isChannelUpdated(CHANNEL_ENERGY_CONSUMPTION_MONTH_EURO, new DecimalType(80.32)));
+ assertTrue(isChannelUpdated(CHANNEL_ENERGY_CONSUMPTION_DAY_EURO, new DecimalType(3.72)));
+ assertTrue(
+ isChannelUpdated(CHANNEL_ENERGY_CONSUMPTION_DAY_KWH, QuantityType.valueOf(8.71, Units.KILOWATT_HOUR)));
+ }
+
+ @Test
+ public void testOnDeviceStateChanged_Event_PowerConsumptionSensor() {
+ DeviceDTO device = createDevice();
+ addCapabilityToDevice(CapabilityDTO.TYPE_POWERCONSUMPTIONSENSOR, null, device);
+
+ LivisiDeviceHandler deviceHandler = createDeviceHandler(device);
+
+ EventDTO event = createCapabilityEvent(c -> c.setPowerConsumptionWatt(350.5));
+
+ deviceHandler.onDeviceStateChanged(device, event);
+ assertTrue(isChannelUpdated(CHANNEL_POWER_CONSUMPTION_WATT, QuantityType.valueOf(350.5, Units.WATT)));
+ }
+
+ @Test
+ public void testOnDeviceStateChanged_Event_GenerationMeterEnergySensor() {
+ DeviceDTO device = createDevice();
+ addCapabilityToDevice(CapabilityDTO.TYPE_GENERATIONMETERENERGYSENSOR, null, device);
+
+ LivisiDeviceHandler deviceHandler = createDeviceHandler(device);
+
+ EventDTO event = createCapabilityEvent(c -> {
+ c.setEnergyPerMonthInKWh(201.51);
+ c.setTotalEnergy(5500.51);
+ c.setEnergyPerMonthInEuro(80.32);
+ c.setEnergyPerDayInEuro(3.72);
+ c.setEnergyPerDayInKWh(8.71);
+ });
+
+ deviceHandler.onDeviceStateChanged(device, event);
+ assertTrue(isChannelUpdated(CHANNEL_ENERGY_GENERATION_MONTH_KWH,
+ QuantityType.valueOf(201.51, Units.KILOWATT_HOUR)));
+ assertTrue(
+ isChannelUpdated(CHANNEL_TOTAL_ENERGY_GENERATION, QuantityType.valueOf(5500.51, Units.KILOWATT_HOUR)));
+ assertTrue(isChannelUpdated(CHANNEL_ENERGY_GENERATION_MONTH_EURO, new DecimalType(80.32)));
+ assertTrue(isChannelUpdated(CHANNEL_ENERGY_GENERATION_DAY_EURO, new DecimalType(3.72)));
+ assertTrue(
+ isChannelUpdated(CHANNEL_ENERGY_GENERATION_DAY_KWH, QuantityType.valueOf(8.71, Units.KILOWATT_HOUR)));
+ }
+
+ @Test
+ public void testOnDeviceStateChanged_Event_GenerationMeterPowerConsumptionSensor() {
+ DeviceDTO device = createDevice();
+ addCapabilityToDevice(CapabilityDTO.TYPE_GENERATIONMETERPOWERCONSUMPTIONSENSOR, null, device);
+
+ LivisiDeviceHandler deviceHandler = createDeviceHandler(device);
+
+ EventDTO event = createCapabilityEvent(c -> c.setPowerInWatt(350.5));
+
+ deviceHandler.onDeviceStateChanged(device, event);
+ assertTrue(isChannelUpdated(CHANNEL_POWER_GENERATION_WATT, QuantityType.valueOf(350.5, Units.WATT)));
+ }
+
+ @Test
+ public void testOnDeviceStateChanged_Event_TwoWayMeterEnergyConsumptionSensor() {
+ DeviceDTO device = createDevice();
+ addCapabilityToDevice(CapabilityDTO.TYPE_TWOWAYMETERENERGYCONSUMPTIONSENSOR, null, device);
+
+ LivisiDeviceHandler deviceHandler = createDeviceHandler(device);
+
+ EventDTO event = createCapabilityEvent(c -> {
+ c.setEnergyPerMonthInKWh(201.51);
+ c.setTotalEnergy(5500.51);
+ c.setEnergyPerMonthInEuro(80.32);
+ c.setEnergyPerDayInEuro(3.72);
+ c.setEnergyPerDayInKWh(8.71);
+ });
+
+ deviceHandler.onDeviceStateChanged(device, event);
+ assertTrue(isChannelUpdated(CHANNEL_ENERGY_MONTH_KWH, QuantityType.valueOf(201.51, Units.KILOWATT_HOUR)));
+ assertTrue(isChannelUpdated(CHANNEL_TOTAL_ENERGY, QuantityType.valueOf(5500.51, Units.KILOWATT_HOUR)));
+ assertTrue(isChannelUpdated(CHANNEL_ENERGY_MONTH_EURO, new DecimalType(80.32)));
+ assertTrue(isChannelUpdated(CHANNEL_ENERGY_DAY_EURO, new DecimalType(3.72)));
+ assertTrue(isChannelUpdated(CHANNEL_ENERGY_DAY_KWH, QuantityType.valueOf(8.71, Units.KILOWATT_HOUR)));
+ }
+
+ @Test
+ public void testOnDeviceStateChanged_Event_TwoWayMeterEnergyFeedSensor() {
+ DeviceDTO device = createDevice();
+ addCapabilityToDevice(CapabilityDTO.TYPE_TWOWAYMETERENERGYFEEDSENSOR, null, device);
+
+ LivisiDeviceHandler deviceHandler = createDeviceHandler(device);
+
+ EventDTO event = createCapabilityEvent(c -> {
+ c.setEnergyPerMonthInKWh(201.51);
+ c.setTotalEnergy(5500.51);
+ c.setEnergyPerMonthInEuro(80.32);
+ c.setEnergyPerDayInEuro(3.72);
+ c.setEnergyPerDayInKWh(8.71);
+ });
+
+ deviceHandler.onDeviceStateChanged(device, event);
+ assertTrue(isChannelUpdated(CHANNEL_ENERGY_FEED_MONTH_KWH, QuantityType.valueOf(201.51, Units.KILOWATT_HOUR)));
+ assertTrue(isChannelUpdated(CHANNEL_TOTAL_ENERGY_FED, QuantityType.valueOf(5500.51, Units.KILOWATT_HOUR)));
+ assertTrue(isChannelUpdated(CHANNEL_ENERGY_FEED_MONTH_EURO, new DecimalType(80.32)));
+ assertTrue(isChannelUpdated(CHANNEL_ENERGY_FEED_DAY_EURO, new DecimalType(3.72)));
+ assertTrue(isChannelUpdated(CHANNEL_ENERGY_FEED_DAY_KWH, QuantityType.valueOf(8.71, Units.KILOWATT_HOUR)));
+ }
+
+ @Test
+ public void testOnDeviceStateChanged_Event_TwoWayMeterPowerConsumptionSensor() {
+ DeviceDTO device = createDevice();
+ addCapabilityToDevice(CapabilityDTO.TYPE_TWOWAYMETERPOWERCONSUMPTIONSENSOR, null, device);
+
+ LivisiDeviceHandler deviceHandler = createDeviceHandler(device);
+
+ EventDTO event = createCapabilityEvent(c -> c.setPowerInWatt(350.5));
+
+ deviceHandler.onDeviceStateChanged(device, event);
+ assertTrue(isChannelUpdated(CHANNEL_POWER_WATT, QuantityType.valueOf(350.5, Units.WATT)));
+ }
+
+ @Test
+ public void testHandleCommand_UnsupportedChannel() {
+ DeviceDTO device = createDevice();
+
+ ChannelUID channelMock = createChannel(CHANNEL_CONTACT);
+
+ LivisiDeviceHandler deviceHandler = createDeviceHandler(device);
+
+ deviceHandler.handleCommand(channelMock, OpenClosedType.OPEN);
+
+ verify(bridgeHandlerMock, never()).commandSetRollerShutterLevel(any(), anyInt());
+ verify(bridgeHandlerMock, never()).commandSwitchDevice(any(), anyBoolean());
+ verify(bridgeHandlerMock, never()).commandUpdatePointTemperature(any(), anyDouble());
+ verify(bridgeHandlerMock, never()).commandSwitchAlarm(any(), anyBoolean());
+ verify(bridgeHandlerMock, never()).commandSetOperationMode(any(), anyBoolean());
+ verify(bridgeHandlerMock, never()).commandSetDimLevel(any(), anyInt());
+ verify(bridgeHandlerMock, never()).commandSetRollerShutterLevel(any(), anyInt());
+ verify(bridgeHandlerMock, never()).commandSetRollerShutterStop(any(), any());
+ }
+
+ @Test
+ public void testHandleCommand_CommandSwitchDevice_On() {
+ DeviceDTO device = createDevice();
+
+ ChannelUID channelMock = createChannel(CHANNEL_SWITCH);
+
+ LivisiDeviceHandler deviceHandler = createDeviceHandler(device);
+ deviceHandler.handleCommand(channelMock, OnOffType.ON);
+
+ verify(bridgeHandlerMock).commandSwitchDevice(device.getId(), true);
+ }
+
+ @Test
+ public void testHandleCommand_CommandSwitchDevice_Off() {
+ DeviceDTO device = createDevice();
+
+ ChannelUID channelMock = createChannel(CHANNEL_SWITCH);
+
+ LivisiDeviceHandler deviceHandler = createDeviceHandler(device);
+ deviceHandler.handleCommand(channelMock, OnOffType.OFF);
+
+ verify(bridgeHandlerMock).commandSwitchDevice(device.getId(), false);
+ }
+
+ @Test
+ public void testHandleCommand_CommandSwitchAlarm_On() {
+ DeviceDTO device = createDevice();
+
+ ChannelUID channelMock = createChannel(CHANNEL_ALARM);
+
+ LivisiDeviceHandler deviceHandler = createDeviceHandler(device);
+ deviceHandler.handleCommand(channelMock, OnOffType.ON);
+
+ verify(bridgeHandlerMock).commandSwitchAlarm(device.getId(), true);
+ }
+
+ @Test
+ public void testHandleCommand_CommandSwitchAlarm_Off() {
+ DeviceDTO device = createDevice();
+
+ ChannelUID channelMock = createChannel(CHANNEL_ALARM);
+
+ LivisiDeviceHandler deviceHandler = createDeviceHandler(device);
+ deviceHandler.handleCommand(channelMock, OnOffType.OFF);
+
+ verify(bridgeHandlerMock).commandSwitchAlarm(device.getId(), false);
+ }
+
+ @Test
+ public void testHandleCommand_CommandSetDimLevel_On() {
+ DeviceDTO device = createDevice();
+
+ ChannelUID channelMock = createChannel(CHANNEL_DIMMER);
+
+ LivisiDeviceHandler deviceHandler = createDeviceHandler(device);
+ deviceHandler.handleCommand(channelMock, OnOffType.ON);
+
+ verify(bridgeHandlerMock).commandSetDimLevel(device.getId(), 100);
+ }
+
+ @Test
+ public void testHandleCommand_CommandSetDimLevel_Off() {
+ DeviceDTO device = createDevice();
+
+ ChannelUID channelMock = createChannel(CHANNEL_DIMMER);
+
+ LivisiDeviceHandler deviceHandler = createDeviceHandler(device);
+ deviceHandler.handleCommand(channelMock, OnOffType.OFF);
+
+ verify(bridgeHandlerMock).commandSetDimLevel(device.getId(), 0);
+ }
+
+ @Test
+ public void testHandleCommand_CommandSetDimLevel_DecimalType() {
+ DeviceDTO device = createDevice();
+
+ ChannelUID channelMock = createChannel(CHANNEL_DIMMER);
+
+ LivisiDeviceHandler deviceHandler = createDeviceHandler(device);
+ deviceHandler.handleCommand(channelMock, DecimalType.valueOf("30"));
+
+ verify(bridgeHandlerMock).commandSetDimLevel(device.getId(), 30);
+ }
+
+ @Test
+ public void testHandleCommand_CommandSetOperationMode_Auto() {
+ DeviceDTO device = createDevice();
+
+ ChannelUID channelMock = createChannel(CHANNEL_OPERATION_MODE);
+
+ LivisiDeviceHandler deviceHandler = createDeviceHandler(device);
+ deviceHandler.handleCommand(channelMock, StringType.valueOf(STATE_VALUE_OPERATION_MODE_AUTO));
+
+ verify(bridgeHandlerMock).commandSetOperationMode(device.getId(), true);
+ }
+
+ @Test
+ public void testHandleCommand_CommandSetOperationMode_Manual() {
+ DeviceDTO device = createDevice();
+
+ ChannelUID channelMock = createChannel(CHANNEL_OPERATION_MODE);
+
+ LivisiDeviceHandler deviceHandler = createDeviceHandler(device);
+ deviceHandler.handleCommand(channelMock, StringType.valueOf(STATE_VALUE_OPERATION_MODE_MANUAL));
+
+ verify(bridgeHandlerMock).commandSetOperationMode(device.getId(), false);
+ }
+
+ @Test
+ public void testHandleCommand_CommandSetOperationMode_Unknown() {
+ DeviceDTO device = createDevice();
+
+ ChannelUID channelMock = createChannel(CHANNEL_OPERATION_MODE);
+
+ LivisiDeviceHandler deviceHandler = createDeviceHandler(device);
+ deviceHandler.handleCommand(channelMock, StringType.valueOf("Unknown"));
+
+ verify(bridgeHandlerMock, never()).commandSetOperationMode(any(), anyBoolean());
+ }
+
+ @Test
+ public void testHandleCommand_CommandUpdatePointTemperature_QuantityType() {
+ DeviceDTO device = createDevice();
+
+ ChannelUID channelMock = createChannel(CHANNEL_TARGET_TEMPERATURE);
+
+ LivisiDeviceHandler deviceHandler = createDeviceHandler(device);
+ deviceHandler.handleCommand(channelMock, QuantityType.valueOf(20.0, SIUnits.CELSIUS));
+
+ verify(bridgeHandlerMock).commandUpdatePointTemperature(device.getId(), 20.0);
+ }
+
+ @Test
+ public void testHandleCommand_CommandUpdatePointTemperature_DecimalType() {
+ DeviceDTO device = createDevice();
+
+ ChannelUID channelMock = createChannel(CHANNEL_TARGET_TEMPERATURE);
+
+ LivisiDeviceHandler deviceHandler = createDeviceHandler(device);
+ deviceHandler.handleCommand(channelMock, DecimalType.valueOf("20"));
+
+ verify(bridgeHandlerMock).commandUpdatePointTemperature(device.getId(), 20.0);
+ }
+
+ @Test
+ public void testHandleCommand_CommandUpdatePointTemperature_MinTemperature() {
+ DeviceDTO device = createDevice();
+
+ ChannelUID channelMock = createChannel(CHANNEL_TARGET_TEMPERATURE);
+
+ LivisiDeviceHandler deviceHandler = createDeviceHandler(device);
+ deviceHandler.handleCommand(channelMock, DecimalType.valueOf("0"));
+
+ // 0 (and everything below the min temperature 6.0 should be set to the min temperature 6.0)
+ verify(bridgeHandlerMock).commandUpdatePointTemperature(device.getId(), 6.0);
+ }
+
+ @Test
+ public void testHandleCommand_CommandUpdatePointTemperature_MaxTemperature() {
+ DeviceDTO device = createDevice();
+
+ ChannelUID channelMock = createChannel(CHANNEL_TARGET_TEMPERATURE);
+
+ LivisiDeviceHandler deviceHandler = createDeviceHandler(device);
+ deviceHandler.handleCommand(channelMock, DecimalType.valueOf("40"));
+
+ // 0 (and everything below the min temperature 30.0 should be set to the min temperature 30.0)
+ verify(bridgeHandlerMock).commandUpdatePointTemperature(device.getId(), 30.0);
+ }
+
+ @Test
+ public void testHandleCommand_CommandRollerShutter_Up() {
+ DeviceDTO device = createDevice();
+
+ ChannelUID channelMock = createChannel(CHANNEL_ROLLERSHUTTER);
+
+ LivisiDeviceHandler deviceHandler = createDeviceHandler(device);
+ deviceHandler.handleCommand(channelMock, UpDownType.UP);
+
+ verify(bridgeHandlerMock).commandSetRollerShutterStop(device.getId(), ShutterActionType.UP);
+ }
+
+ @Test
+ public void testHandleCommand_CommandRollerShutter_Down() {
+ DeviceDTO device = createDevice();
+
+ ChannelUID channelMock = createChannel(CHANNEL_ROLLERSHUTTER);
+
+ LivisiDeviceHandler deviceHandler = createDeviceHandler(device);
+ deviceHandler.handleCommand(channelMock, UpDownType.DOWN);
+
+ verify(bridgeHandlerMock).commandSetRollerShutterStop(device.getId(), ShutterActionType.DOWN);
+ }
+
+ @Test
+ public void testHandleCommand_CommandRollerShutter_On() {
+ DeviceDTO device = createDevice();
+
+ ChannelUID channelMock = createChannel(CHANNEL_ROLLERSHUTTER);
+
+ LivisiDeviceHandler deviceHandler = createDeviceHandler(device);
+ deviceHandler.handleCommand(channelMock, OnOffType.ON);
+
+ verify(bridgeHandlerMock).commandSetRollerShutterStop(device.getId(), ShutterActionType.DOWN);
+ }
+
+ @Test
+ public void testHandleCommand_CommandRollerShutter_Off() {
+ DeviceDTO device = createDevice();
+
+ ChannelUID channelMock = createChannel(CHANNEL_ROLLERSHUTTER);
+
+ LivisiDeviceHandler deviceHandler = createDeviceHandler(device);
+ deviceHandler.handleCommand(channelMock, OnOffType.OFF);
+
+ verify(bridgeHandlerMock).commandSetRollerShutterStop(device.getId(), ShutterActionType.UP);
+ }
+
+ @Test
+ public void testHandleCommand_CommandRollerShutter_Stop() {
+ DeviceDTO device = createDevice();
+
+ ChannelUID channelMock = createChannel(CHANNEL_ROLLERSHUTTER);
+
+ LivisiDeviceHandler deviceHandler = createDeviceHandler(device);
+ deviceHandler.handleCommand(channelMock, StopMoveType.STOP);
+
+ verify(bridgeHandlerMock).commandSetRollerShutterStop(device.getId(), ShutterActionType.STOP);
+ }
+
+ @Test
+ public void testHandleCommand_CommandRollerShutter_DecimalType() {
+ DeviceDTO device = createDevice();
+
+ ChannelUID channelMock = createChannel(CHANNEL_ROLLERSHUTTER);
+
+ LivisiDeviceHandler deviceHandler = createDeviceHandler(device);
+ deviceHandler.handleCommand(channelMock, DecimalType.valueOf("30"));
+
+ verify(bridgeHandlerMock).commandSetRollerShutterLevel(device.getId(), 30);
+ }
+
+ @Test
+ public void testHandleCommand_CommandRollerShutter_DecimalType_Inverted() {
+ DeviceDTO device = createDevice();
+
+ Channel rollerShutterChannelMock = createRollerShutterChannelMock(true);
+
+ LivisiDeviceHandler deviceHandler = createDeviceHandler(device);
+
+ Thing thingMock = deviceHandler.getThing();
+ when(thingMock.getChannel(CHANNEL_ROLLERSHUTTER)).thenReturn(rollerShutterChannelMock);
+
+ ChannelUID channelMock = createChannel(CHANNEL_ROLLERSHUTTER);
+
+ deviceHandler.handleCommand(channelMock, DecimalType.valueOf("30"));
+
+ verify(bridgeHandlerMock).commandSetRollerShutterLevel(device.getId(), 30);
+ }
+
+ @Test
+ public void testHandleCommand_CommandRollerShutter_DecimalType_NotInverted() {
+ DeviceDTO device = createDevice();
+
+ Channel rollerShutterChannelMock = createRollerShutterChannelMock(false);
+
+ LivisiDeviceHandler deviceHandler = createDeviceHandler(device);
+
+ Thing thingMock = deviceHandler.getThing();
+ when(thingMock.getChannel(CHANNEL_ROLLERSHUTTER)).thenReturn(rollerShutterChannelMock);
+
+ ChannelUID channelMock = createChannel(CHANNEL_ROLLERSHUTTER);
+
+ deviceHandler.handleCommand(channelMock, DecimalType.valueOf("30"));
+
+ verify(bridgeHandlerMock).commandSetRollerShutterLevel(device.getId(), 70);
+ }
+
+ private LivisiDeviceHandler createDeviceHandler(DeviceDTO device) {
+ when(bridgeHandlerMock.getDeviceById(any())).thenReturn(Optional.of(device));
+
+ ThingUID bridgeThingUID = new ThingUID(THING_TYPE_BRIDGE, "bridgeId");
+ Bridge bridgeMock = mock(Bridge.class);
+ when(bridgeMock.getHandler()).thenReturn(bridgeHandlerMock);
+ when(bridgeMock.getStatus()).thenReturn(ThingStatus.ONLINE);
+ when(bridgeMock.getStatusInfo())
+ .thenReturn(new ThingStatusInfo(ThingStatus.ONLINE, ThingStatusDetail.NONE, null));
+
+ ThingTypeUID thingTypeUID = THING_TYPE_RST2;
+ ThingUID thingUID = new ThingUID(thingTypeUID, device.getId());
+
+ Configuration thingConfiguration = new Configuration();
+ thingConfiguration.setProperties(Collections.singletonMap(PROPERTY_ID, device.getId()));
+
+ Thing thingMock = mock(Thing.class);
+ when(thingMock.getBridgeUID()).thenReturn(bridgeThingUID);
+ when(thingMock.getConfiguration()).thenReturn(thingConfiguration);
+ when(thingMock.getUID()).thenReturn(thingUID);
+ when(thingMock.getThingTypeUID()).thenReturn(thingTypeUID);
+ doAnswer(invocation -> {
+ thingStatusInfo = invocation.getArgument(0, ThingStatusInfo.class);
+ return null;
+ }).when(thingMock).setStatusInfo(any());
+ when(thingMock.getStatusInfo()).thenAnswer(invocation -> thingStatusInfo);
+ when(thingMock.getStatus()).thenAnswer(invocation -> thingStatusInfo.getStatus());
+
+ LivisiDeviceHandler deviceHandler = new LivisiDeviceHandler(thingMock);
+ ThingHandlerCallback callbackMock = createCallbackMock(bridgeMock);
+ deviceHandler.setCallback(callbackMock);
+
+ deviceHandler.initialize();
+
+ return deviceHandler;
+ }
+
+ private static DeviceDTO createDevice() {
+ DeviceDTO device = new DeviceDTO();
+ device.setId("id");
+ device.setConfig(new DeviceConfigDTO());
+ device.setCapabilityMap(new HashMap<>());
+ return device;
+ }
+
+ private static ChannelUID createChannel(String channelId) {
+ ChannelUID channelMock = mock(ChannelUID.class);
+ when(channelMock.getId()).thenReturn(channelId);
+ return channelMock;
+ }
+
+ private static void addCapabilityToDevice(String capabilityType,
+ @Nullable Consumer capabilityStateConsumer, DeviceDTO device) {
+ CapabilityConfigDTO capabilityConfig = new CapabilityConfigDTO();
+ capabilityConfig.setName("capabilityName");
+
+ CapabilityStateDTO capabilityState = new CapabilityStateDTO();
+ if (capabilityStateConsumer != null) {
+ capabilityStateConsumer.accept(capabilityState);
+ }
+
+ final String capabilityId = "capabilityId";
+ CapabilityDTO capability = new CapabilityDTO();
+ capability.setId(capabilityId);
+ capability.setType(capabilityType);
+ capability.setConfig(capabilityConfig);
+ capability.setCapabilityState(capabilityState);
+ device.getCapabilityMap().put(capabilityId, capability);
+ }
+
+ private static EventDTO createCapabilityEvent(Consumer eventPropertiesConsumer) {
+ EventPropertiesDTO eventProperties = new EventPropertiesDTO();
+ eventPropertiesConsumer.accept(eventProperties);
+
+ EventDTO event = new EventDTO();
+ event.setSource(LINK_TYPE_CAPABILITY + "capabilityId");
+ event.setProperties(eventProperties);
+ return event;
+ }
+
+ private ThingHandlerCallback createCallbackMock(Bridge bridge) {
+ ThingHandlerCallback callback = mock(ThingHandlerCallback.class);
+ when(callback.getBridge(any())).thenReturn(bridge);
+ when(callback.createChannelBuilders(any(), any())).thenReturn(Collections.emptyList());
+
+ doAnswer(invocation -> {
+ ChannelUID channelUID = invocation.getArgument(0, ChannelUID.class);
+ State state = invocation.getArgument(1, State.class);
+ updatedChannels.put(channelUID.getId(), state);
+ return null;
+ }).when(callback).stateUpdated(any(), any());
+
+ doAnswer(invocation -> {
+ Thing thing = invocation.getArgument(0, Thing.class);
+ ThingStatusInfo thingStatusInfo = invocation.getArgument(1, ThingStatusInfo.class);
+ thing.setStatusInfo(thingStatusInfo);
+ return null;
+ }).when(callback).statusUpdated(any(), any());
+
+ doAnswer(invocation -> {
+ ChannelUID channelUID = invocation.getArgument(1, ChannelUID.class);
+ String value = invocation.getArgument(2, String.class);
+ triggeredChannels.add(new TriggeredEvent(channelUID.getId(), value));
+ return null;
+ }).when(callback).channelTriggered(any(), any(), any());
+
+ doAnswer(invocation -> {
+ ChannelUID channelUID = invocation.getArgument(0, ChannelUID.class);
+ return ChannelBuilder.create(channelUID);
+ }).when(callback).createChannelBuilder(any(), any());
+
+ doAnswer(invocation -> {
+ ChannelUID channelUID = invocation.getArgument(1, ChannelUID.class);
+ return ChannelBuilder.create(channelUID);
+ }).when(callback).editChannel(any(), any());
+
+ return callback;
+ }
+
+ private boolean isChannelUpdated(String channelUID) {
+ return updatedChannels.containsKey(channelUID);
+ }
+
+ private boolean isChannelUpdated(String channelUID, State expectedState) {
+ State state = updatedChannels.get(channelUID);
+ return expectedState.equals(state);
+ }
+
+ private boolean isChannelTriggered(String channelUID, String expectedTriggerValue) {
+ return triggeredChannels.contains(new TriggeredEvent(channelUID, expectedTriggerValue));
+ }
+
+ private static Channel createRollerShutterChannelMock(boolean isInvert) {
+ Map rollerShutterChannelProperties = new HashMap<>();
+ rollerShutterChannelProperties.put(INVERT_CHANNEL_PARAMETER, isInvert);
+ Configuration rollerShutterChannelConfiguration = new Configuration(rollerShutterChannelProperties);
+
+ Channel rollerShutterChannelMock = mock(Channel.class);
+ when(rollerShutterChannelMock.getConfiguration()).thenReturn(rollerShutterChannelConfiguration);
+ return rollerShutterChannelMock;
+ }
+
+ private static class TriggeredEvent {
+
+ private final String channelUID;
+ private final String triggerValue;
+
+ public TriggeredEvent(String channelUID, String triggerValue) {
+ this.channelUID = channelUID;
+ this.triggerValue = triggerValue;
+ }
+
+ @Override
+ public boolean equals(@Nullable Object o) {
+ if (this == o)
+ return true;
+ if (o == null || getClass() != o.getClass())
+ return false;
+ TriggeredEvent that = (TriggeredEvent) o;
+ return channelUID.equals(that.channelUID) && triggerValue.equals(that.triggerValue);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(channelUID, triggerValue);
+ }
+ }
+}
diff --git a/bundles/org.openhab.binding.livisismarthome/src/test/resources/org/openhab/binding/livisismarthome/internal/client/entity/device/SHC2state.json b/bundles/org.openhab.binding.livisismarthome/src/test/resources/org/openhab/binding/livisismarthome/internal/client/entity/device/SHC2state.json
new file mode 100644
index 00000000000..2e80a937f9c
--- /dev/null
+++ b/bundles/org.openhab.binding.livisismarthome/src/test/resources/org/openhab/binding/livisismarthome/internal/client/entity/device/SHC2state.json
@@ -0,0 +1,80 @@
+{
+ "configVersion": {
+ "value": 38,
+ "lastChanged": "2019-07-13T08:34:35.627766Z"
+ },
+ "cpuUsage": {
+ "value": 0.80912222750515084,
+ "lastChanged": "2019-07-22T19:35:06.080964Z"
+ },
+ "currentUtcOffset": {
+ "value": 120,
+ "lastChanged": "1970-01-01T00:00:00Z"
+ },
+ "deviceConfigurationState": {
+ "value": "Complete",
+ "lastChanged": "1970-01-01T00:00:00Z"
+ },
+ "deviceInclusionState": {
+ "value": "Included",
+ "lastChanged": "2019-02-07T19:47:50.2148Z"
+ },
+ "discoveryActive": {
+ "value": false,
+ "lastChanged": "2019-07-13T07:19:47.187338Z"
+ },
+ "diskUsage": {
+ "value": 57.358010125846029,
+ "lastChanged": "2019-07-22T19:35:06.081238Z"
+ },
+ "ethIpAddress": {
+ "value": "192.168.0.42",
+ "lastChanged": "1970-01-01T00:00:00Z"
+ },
+ "ethMacAddress": {
+ "value": "94:c6:91:a1:93:b2",
+ "lastChanged": "1970-01-01T00:00:00Z"
+ },
+ "inUseAdapter": {
+ "value": "eth",
+ "lastChanged": "1970-01-01T00:00:00Z"
+ },
+ "isReachable": {
+ "lastChanged": "1970-01-01T00:00:00Z"
+ },
+ "lastReboot": {
+ "value": "2019-07-11T22:15:02Z",
+ "lastChanged": "1970-01-01T00:00:00Z"
+ },
+ "lbDongleAttached": {
+ "value": false,
+ "lastChanged": "1970-01-01T00:00:00Z"
+ },
+ "memoryUsage": {
+ "value": 29.485105729836945,
+ "lastChanged": "2019-07-22T19:35:06.081461Z"
+ },
+ "operationStatus": {
+ "value": "active",
+ "lastChanged": "2019-07-11T22:15:35.284088Z"
+ },
+ "updateAvailable": {
+ "lastChanged": "1970-01-01T00:00:00Z"
+ },
+ "wMBusDongleAttached": {
+ "value": false,
+ "lastChanged": "1970-01-01T00:00:00Z"
+ },
+ "wifiIpAddress": {
+ "value": "",
+ "lastChanged": "1970-01-01T00:00:00Z"
+ },
+ "wifiMacAddress": {
+ "value": "40:9f:38:3d:b5:7d",
+ "lastChanged": "1970-01-01T00:00:00Z"
+ },
+ "wifiSignalStrength": {
+ "value": 0,
+ "lastChanged": "1970-01-01T00:00:00Z"
+ }
+}
diff --git a/bundles/org.openhab.binding.livisismarthome/src/test/resources/org/openhab/binding/livisismarthome/internal/client/entity/device/SHCstate.json b/bundles/org.openhab.binding.livisismarthome/src/test/resources/org/openhab/binding/livisismarthome/internal/client/entity/device/SHCstate.json
new file mode 100644
index 00000000000..4636e109bee
--- /dev/null
+++ b/bundles/org.openhab.binding.livisismarthome/src/test/resources/org/openhab/binding/livisismarthome/internal/client/entity/device/SHCstate.json
@@ -0,0 +1,34 @@
+{
+ "updateAvailable": {
+ "value": "",
+ "lastChanged": "2019-06-20T00:15:32.766Z"
+ },
+ "lastReboot": {
+ "value": "2019-06-20T00:14:10.477Z",
+ "lastChanged": "2019-06-20T00:15:32.767Z"
+ },
+ "MBusDongleAttached": {
+ "value": false,
+ "lastChanged": "2019-06-20T00:15:32.875Z"
+ },
+ "LBDongleAttached": {
+ "value": false,
+ "lastChanged": "2019-06-20T00:15:32.876Z"
+ },
+ "configVersion": {
+ "value": 294,
+ "lastChanged": "2019-07-16T16:30:14.498Z"
+ },
+ "OSState": {
+ "value": "Normal",
+ "lastChanged": "2019-06-20T01:15:32.961Z"
+ },
+ "memoryLoad": {
+ "value": 63,
+ "lastChanged": "2019-07-22T19:47:43.737Z"
+ },
+ "CPULoad": {
+ "value": 12,
+ "lastChanged": "2019-07-22T19:47:43.737Z"
+ }
+}
diff --git a/bundles/org.openhab.binding.livisismarthome/src/test/resources/org/openhab/binding/livisismarthome/internal/client/entity/device/debug.json b/bundles/org.openhab.binding.livisismarthome/src/test/resources/org/openhab/binding/livisismarthome/internal/client/entity/device/debug.json
new file mode 100644
index 00000000000..af15df93e5d
--- /dev/null
+++ b/bundles/org.openhab.binding.livisismarthome/src/test/resources/org/openhab/binding/livisismarthome/internal/client/entity/device/debug.json
@@ -0,0 +1,15 @@
+{
+ "sequenceNumber": -1,
+ "type": "StateChanged",
+ "desc": "/desc/event/StateChanged",
+ "namespace": "core.RWE",
+ "timestamp": "2019-06-29T21:16:05.1550000Z",
+ "source": "/device/72e753b09fd44a118997bc615351cabd",
+ "properties": {
+ "isReachable": false,
+ "deviceConfigurationState": "Complete",
+ "deviceInclusionState": "Included",
+ "updateState": "UpToDate",
+ "firmwareVersion": "1.6"
+ }
+}
diff --git a/bundles/org.openhab.binding.livisismarthome/src/test/resources/org/openhab/binding/livisismarthome/internal/client/entity/event/MessageCreatedEventBatteryLow.json b/bundles/org.openhab.binding.livisismarthome/src/test/resources/org/openhab/binding/livisismarthome/internal/client/entity/event/MessageCreatedEventBatteryLow.json
new file mode 100644
index 00000000000..5871d210863
--- /dev/null
+++ b/bundles/org.openhab.binding.livisismarthome/src/test/resources/org/openhab/binding/livisismarthome/internal/client/entity/event/MessageCreatedEventBatteryLow.json
@@ -0,0 +1,46 @@
+{
+ "sequenceNumber": -1,
+ "type": "MessageCreated",
+ "desc": "/desc/event/MessageCreated",
+ "namespace": "core.RWE",
+ "timestamp": "2019-07-07T18:44:07.6280000Z",
+ "source": "/desc/device/SHC.RWE/1.0",
+ "data": {
+ "id": "c5c3128810524820b6071b00e80dfd23",
+ "type": "DeviceLowBattery",
+ "read": false,
+ "class": "Alert",
+ "timestamp": "2019-07-07T18:44:07.583Z",
+ "devices": ["/device/1b77a62c18c9423f871251704faec45f"],
+ "properties": {
+ "deviceName": "Fernbedienung",
+ "serialNumber": "914140024103",
+ "locationName": "Arbeitszimmer"
+ },
+ "namespace": "core.RWE"
+ }
+}
+
+{
+ "sequenceNumber": -1,
+ "type": "MessageCreated",
+ "desc": "/desc/event/MessageCreated",
+ "namespace": "core.RWE",
+ "timestamp": "2019-07-07T18:41:47.2970000Z",
+ "source": "/desc/device/SHC.RWE/1.0",
+ "data": {
+ "id": "6e5ce2290cd247208f95a5b53736958b",
+ "type": "DeviceLowBattery",
+ "read": false,
+ "class": "Alert",
+ "timestamp": "2019-07-07T18:41:47.232Z",
+ "devices": ["/device/fe51785319854f36a621d0b4f8ea0e25"],
+ "properties": {
+ "deviceName": "Heizkörperthermostat",
+ "serialNumber": "914110165056",
+ "locationName": "Bad"
+ },
+ "namespace": "core.RWE"
+ }
+}
+
diff --git a/bundles/org.openhab.binding.livisismarthome/src/test/resources/org/openhab/binding/livisismarthome/internal/client/entity/event/MessageDeletedEvent.json b/bundles/org.openhab.binding.livisismarthome/src/test/resources/org/openhab/binding/livisismarthome/internal/client/entity/event/MessageDeletedEvent.json
new file mode 100644
index 00000000000..bbfb5445242
--- /dev/null
+++ b/bundles/org.openhab.binding.livisismarthome/src/test/resources/org/openhab/binding/livisismarthome/internal/client/entity/event/MessageDeletedEvent.json
@@ -0,0 +1,8 @@
+{
+ "sequenceNumber": -1,
+ "type": "MessageDeleted",
+ "desc": "/desc/event/MessageDeleted",
+ "namespace": "core.RWE",
+ "timestamp": "2019-07-07T19:15:39.2100000Z",
+ "data": { "id": "6e5ce2290cd247208f95a5b53736958b" }
+}
diff --git a/bundles/org.openhab.binding.livisismarthome/src/test/resources/org/openhab/binding/livisismarthome/internal/client/entity/eventCapabilityChanged.json b/bundles/org.openhab.binding.livisismarthome/src/test/resources/org/openhab/binding/livisismarthome/internal/client/entity/eventCapabilityChanged.json
new file mode 100644
index 00000000000..447ac947506
--- /dev/null
+++ b/bundles/org.openhab.binding.livisismarthome/src/test/resources/org/openhab/binding/livisismarthome/internal/client/entity/eventCapabilityChanged.json
@@ -0,0 +1,11 @@
+{
+ "sequenceNumber": -1,
+ "type": "StateChanged",
+ "desc": "/desc/event/StateChanged",
+ "namespace": "core.RWE",
+ "timestamp": "2019-03-10T17:34:56.2400000Z",
+ "source": "/capability/3f268584b95c40df93b67d6c64957846",
+ "properties": {
+ "onState": true
+ }
+}
\ No newline at end of file
diff --git a/bundles/org.openhab.binding.livisismarthome/src/test/resources/org/openhab/binding/livisismarthome/internal/client/entity/eventStateChangedDevice.json b/bundles/org.openhab.binding.livisismarthome/src/test/resources/org/openhab/binding/livisismarthome/internal/client/entity/eventStateChangedDevice.json
new file mode 100644
index 00000000000..b0c4fd7208d
--- /dev/null
+++ b/bundles/org.openhab.binding.livisismarthome/src/test/resources/org/openhab/binding/livisismarthome/internal/client/entity/eventStateChangedDevice.json
@@ -0,0 +1,11 @@
+{
+ "sequenceNumber": -1,
+ "type": "StateChanged",
+ "desc": "/desc/event/StateChanged",
+ "namespace": "core.RWE",
+ "timestamp": "2019-03-10T20:49:25.4940000Z",
+ "source": "/device/b487e440f3e743649477fbbb64f8d55a",
+ "properties": {
+ "deviceInclusionState": "Included"
+ }
+}
diff --git a/bundles/org.openhab.binding.livisismarthome/src/test/resources/org/openhab/binding/livisismarthome/internal/client/entity/eventUserDataChanged.json b/bundles/org.openhab.binding.livisismarthome/src/test/resources/org/openhab/binding/livisismarthome/internal/client/entity/eventUserDataChanged.json
new file mode 100644
index 00000000000..8dc5979dc0d
--- /dev/null
+++ b/bundles/org.openhab.binding.livisismarthome/src/test/resources/org/openhab/binding/livisismarthome/internal/client/entity/eventUserDataChanged.json
@@ -0,0 +1,15 @@
+{
+ "sequenceNumber": 0,
+ "type": "/event/UserDataChanged",
+ "timestamp": "2019-02-25T20:18:20.5600109Z",
+ "data": [
+ {
+ "key": "StatesId",
+ "partition": "HomepageDeviceVisibility"
+ },
+ {
+ "key": "UserData",
+ "partition": "Version"
+ }
+ ]
+}
\ No newline at end of file
diff --git a/bundles/org.openhab.binding.livisismarthome/src/test/resources/org/openhab/binding/livisismarthome/internal/client/entity/getdevices.json b/bundles/org.openhab.binding.livisismarthome/src/test/resources/org/openhab/binding/livisismarthome/internal/client/entity/getdevices.json
new file mode 100644
index 00000000000..e59f80853c1
--- /dev/null
+++ b/bundles/org.openhab.binding.livisismarthome/src/test/resources/org/openhab/binding/livisismarthome/internal/client/entity/getdevices.json
@@ -0,0 +1,632 @@
+[
+ {
+ "id": "b0f441a410f8465fbede93a7363a4339",
+ "manufacturer": "RWE",
+ "version": "1.0",
+ "product": "core.RWE",
+ "serialNumber": "914100007996",
+ "type": "SHC",
+ "config": {
+ "name": "Smart Home Controller",
+ "protocolId": "Virtual",
+ "timeOfAcceptance": "2016-07-11T10:55:52.3863424Z",
+ "timeOfDiscovery": "2016-07-11T10:55:52.3863424Z",
+ "hardwareVersion": "00.02",
+ "softwareVersion": "2.1.0.66",
+ "firmwareVersion": "1.913",
+ "hostName": "SMARTHOME06",
+ "activityLogEnabled": true,
+ "configurationState": "Complete",
+ "geoLocation": "50.8301066,6.901272800000015",
+ "timeZone": "W. Europe Standard Time",
+ "currentUTCOffset": 60.0,
+ "IPAddress": "192.168.0.125",
+ "MACAddress": "00-1a-22-00-46-ae",
+ "shcType": "0",
+ "backendConnectionMonitored": true,
+ "RFCommFailureNotification": false,
+ "postCode": "50321",
+ "city": "Brühl",
+ "street": "Hermannstraße",
+ "houseNumber": "13",
+ "country": "Deutschland",
+ "householdType": "House",
+ "numberOfPersons": 0.0,
+ "numberOfFloors": 0.0,
+ "livingArea": 0.0,
+ "registrationTime": "2016-07-11T10:55:52.3863424Z"
+ },
+ "capabilities": [
+ "/capability/d43d0a536110445fa040f06d25d25254",
+ "/capability/edfd5fa4d18e4c4994935bf5905a262e"
+ ]
+ },
+ {
+ "id": "970da72e3d5847ac941b889e5e6e8b3a",
+ "manufacturer": "RWE",
+ "version": "1.0",
+ "product": "core.RWE",
+ "serialNumber": "",
+ "type": "NotificationSender",
+ "config": {
+ "name": "Notification Sender",
+ "protocolId": "Virtual",
+ "timeOfAcceptance": "2016-07-11T10:55:52.3863424Z",
+ "timeOfDiscovery": "2016-07-11T10:55:52.3863424Z"
+ },
+ "capabilities": [
+ "/capability/fab663ad6dba4e0b92888bfb17dc3c1e",
+ "/capability/84e766ad0f99445f99c810c47547cbb6",
+ "/capability/098c77c87e5c42db9d39b616dcf6203d"
+ ]
+ },
+ {
+ "id": "155f38e4ee2047c0b2246133b1de96a0",
+ "manufacturer": "RWE",
+ "version": "2.0",
+ "product": "VariableActuator.RWE",
+ "serialNumber": "155f38e4ee2047c0b2246133b1de96a0",
+ "type": "VariableActuator",
+ "config": {
+ "name": "Zuhause",
+ "protocolId": "Virtual",
+ "timeOfAcceptance": "2016-07-11T10:59:57.844Z",
+ "timeOfDiscovery": "2016-07-11T10:56:19.262Z"
+ },
+ "tags": {
+ "internalStateId": "HomeAway"
+ },
+ "capabilities": [
+ "/capability/2d7e58bca7fc40148e77cb4df6daef90"
+ ]
+ },
+ {
+ "id": "b19d0f5794ee4a2b8ecadbf2ea470c31",
+ "manufacturer": "RWE",
+ "version": "2.0",
+ "product": "VariableActuator.RWE",
+ "serialNumber": "b19d0f5794ee4a2b8ecadbf2ea470c31",
+ "type": "VariableActuator",
+ "config": {
+ "name": "Urlaub",
+ "protocolId": "Virtual",
+ "timeOfAcceptance": "2016-07-11T11:00:02.191Z",
+ "timeOfDiscovery": "2016-07-11T11:00:00.039Z"
+ },
+ "tags": {
+ "internalStateId": "Vacation"
+ },
+ "capabilities": [
+ "/capability/aeda6003eb124b19be5ff644ca59fe40"
+ ]
+ },
+ {
+ "id": "32214feb11a74949b573346f1a380729",
+ "manufacturer": "RWE",
+ "version": "1.0",
+ "product": "core.RWE",
+ "serialNumber": "914130092370",
+ "type": "WSC2",
+ "config": {
+ "name": "Wandsender Test",
+ "protocolId": "Cosip",
+ "timeOfAcceptance": "2016-07-11T11:22:18.087Z",
+ "timeOfDiscovery": "2016-07-11T11:20:49.192Z"
+ },
+ "tags": {
+ "typeCategory": "TCSwitchIdTag",
+ "type": "TTwoButtonSwitchIdTag"
+ },
+ "capabilities": [
+ "/capability/2dca5b6a3e5a419d8f6f4a8f05ed8310"
+ ],
+ "location": "/location/bd67af2ca266487bb97b7d08b3468c2f"
+ },
+ {
+ "id": "d68abf56664c4607a2e01d66be1fb727",
+ "manufacturer": "RWE",
+ "version": "1.0",
+ "product": "core.RWE",
+ "serialNumber": "914120076611",
+ "type": "PSS",
+ "config": {
+ "name": "Fernseher",
+ "protocolId": "Cosip",
+ "timeOfAcceptance": "2016-08-21T13:28:58.508Z",
+ "timeOfDiscovery": "2016-08-21T13:28:34.606Z"
+ },
+ "tags": {
+ "typeCategory": "TCEntertainmentId",
+ "type": "TTVId"
+ },
+ "capabilities": [
+ "/capability/3f268584b95c40df93b67d6c64957846"
+ ],
+ "location": "/location/30858fcbea3f4833b4e7b3143ea872f7"
+ },
+ {
+ "id": "18b546218e90484796e3d8e0786d0256",
+ "manufacturer": "RWE",
+ "version": "1.0",
+ "product": "core.RWE",
+ "serialNumber": "914130043917",
+ "type": "WSC2",
+ "config": {
+ "name": "Wandsender Bett",
+ "protocolId": "Cosip",
+ "timeOfAcceptance": "2016-09-04T11:32:50.85Z",
+ "timeOfDiscovery": "2016-09-04T11:32:26.982Z"
+ },
+ "tags": {
+ "typeCategory": "TCSwitchIdTag",
+ "type": "TTwoButtonSwitchIdTag"
+ },
+ "capabilities": [
+ "/capability/4110238f56cb4cb7ba211b0d8c9b6557"
+ ],
+ "location": "/location/30858fcbea3f4833b4e7b3143ea872f7"
+ },
+ {
+ "id": "e9b272c83d9a488abccb0d404771b6c6",
+ "manufacturer": "RWE",
+ "version": "1.1",
+ "product": "core.RWE",
+ "serialNumber": "914110059496",
+ "type": "RST",
+ "config": {
+ "name": "Heizkörperthermostat",
+ "protocolId": "Cosip",
+ "timeOfAcceptance": "2016-09-29T18:27:38.094Z",
+ "timeOfDiscovery": "2016-09-29T18:27:11.102Z",
+ "displayCurrentTemperature": "TargetTemperature"
+ },
+ "tags": {
+ "typeCategory": "TCHeatingId",
+ "type": "TRadiatorThermostateIdTag"
+ },
+ "capabilities": [
+ "/capability/4afde71507ba4144bb81fbdc82a5fe7e",
+ "/capability/a29a7a77fc05484780eae1f8b47e62b3",
+ "/capability/a4e1d52096c54a2ebd018e9a1338f392"
+ ],
+ "location": "/location/b09e4978ce8c4be9ad4afcdf6cc09005"
+ },
+ {
+ "id": "07332bf0dd0e42508c66c846e1e1a8ad",
+ "manufacturer": "RWE",
+ "version": "2.0",
+ "product": "SunriseSunsetSensor.RWE",
+ "serialNumber": "07332bf0dd0e42508c66c846e1e1a8ad",
+ "type": "SunriseSunsetSensor",
+ "config": {
+ "name": "Sunrise Sunset Sensor",
+ "protocolId": "Virtual",
+ "timeOfAcceptance": "2016-09-29T21:51:39.553Z",
+ "timeOfDiscovery": "2016-09-29T21:51:33.965Z"
+ },
+ "capabilities": [
+ "/capability/64bf06b56b5e4fc49292985c6bb48bea"
+ ]
+ },
+ {
+ "id": "6de7890d580649aeafd66043b2102a12",
+ "manufacturer": "RWE",
+ "version": "1.0",
+ "product": "core.RWE",
+ "serialNumber": "921430037295",
+ "type": "WDS",
+ "config": {
+ "name": "Balkon",
+ "protocolId": "Cosip",
+ "timeOfAcceptance": "2016-11-20T21:19:41.098Z",
+ "timeOfDiscovery": "2016-11-20T21:19:25.775Z"
+ },
+ "tags": {
+ "typeCategory": "TCDoorId",
+ "type": "TBalconyDoorId"
+ },
+ "capabilities": [
+ "/capability/3655816c7413451db58b9c07e473540b"
+ ],
+ "location": "/location/e397695c607846379c76068beac379af"
+ },
+ {
+ "id": "0f16337ac685415986a274376b601450",
+ "manufacturer": "RWE",
+ "version": "1.0",
+ "product": "core.RWE",
+ "serialNumber": "914150006603",
+ "type": "WMD",
+ "config": {
+ "name": "Bewegungsmelder",
+ "protocolId": "Cosip",
+ "timeOfAcceptance": "2016-11-04T22:13:28.403Z",
+ "timeOfDiscovery": "2016-11-04T22:12:54.536Z"
+ },
+ "tags": {
+ "typeCategory": "TCMotionIdTag",
+ "type": "TMotionDetectorIdTag"
+ },
+ "capabilities": [
+ "/capability/5d41af178a664418ad8ee10b5d906d98",
+ "/capability/2684a9d13ac9410b94eae134ed39826f"
+ ],
+ "location": "/location/26f2f33fbe7a4e6abddfc1512a65e4e0"
+ },
+ {
+ "id": "baed452016404d1f8c7d6bf5525fc224",
+ "manufacturer": "RWE",
+ "version": "1.1",
+ "product": "core.RWE",
+ "serialNumber": "914210002474",
+ "type": "ISS2",
+ "config": {
+ "name": "Deckenleuchte",
+ "protocolId": "Cosip",
+ "timeOfAcceptance": "2016-11-04T23:33:57.461Z",
+ "timeOfDiscovery": "2016-11-04T23:33:25.954Z"
+ },
+ "tags": {
+ "typeCategory": "TCLightId",
+ "type": "TCeilingLightId"
+ },
+ "capabilities": [
+ "/capability/7efeaa246c704713beda52abdd741d45",
+ "/capability/fb8a5e34c3c54ef998579d44bb57b198"
+ ],
+ "location": "/location/26f2f33fbe7a4e6abddfc1512a65e4e0"
+ },
+ {
+ "id": "76e68e066e874b9da3403223cbf4b048",
+ "manufacturer": "RWE",
+ "version": "1.0",
+ "product": "core.RWE",
+ "serialNumber": "IRW0020883",
+ "type": "WSD",
+ "config": {
+ "name": "Rauchmelder",
+ "protocolId": "Cosip",
+ "timeOfAcceptance": "2016-11-20T21:34:39.905Z",
+ "timeOfDiscovery": "2016-11-20T21:34:34.388Z"
+ },
+ "tags": {
+ "typeCategory": "TSmokeDetectorIdTag",
+ "type": "TOpticalSmokeDetectorIdTag"
+ },
+ "capabilities": [
+ "/capability/aa25dfc37fee4b11978a7c6f76fd2c19",
+ "/capability/56ba36531e4a485ea68a12539fb4cf5e"
+ ],
+ "location": "/location/e397695c607846379c76068beac379af"
+ },
+ {
+ "id": "fd6830f0066f4283b5fc7ba1c8f43071",
+ "manufacturer": "RWE",
+ "version": "1.1",
+ "product": "core.RWE",
+ "serialNumber": "914210010378",
+ "type": "ISS2",
+ "config": {
+ "name": "Balkon",
+ "protocolId": "Cosip",
+ "timeOfAcceptance": "2016-12-07T07:37:21.649Z",
+ "timeOfDiscovery": "2016-12-07T07:36:46.033Z"
+ },
+ "tags": {
+ "typeCategory": "TCLightId",
+ "type": "TOutsideLightId"
+ },
+ "capabilities": [
+ "/capability/3f2bb031cc1c447aa849e930c7bd4c02",
+ "/capability/5af9f6896e114051b9e3423cf0aa7f20"
+ ],
+ "location": "/location/e397695c607846379c76068beac379af"
+ },
+ {
+ "id": "a8282a7de8ff4b19958051814f8d8ad6",
+ "manufacturer": "RWE",
+ "version": "1.1",
+ "product": "core.RWE",
+ "serialNumber": "914110012419",
+ "type": "RST",
+ "config": {
+ "name": "Heizkörperthermostat",
+ "protocolId": "Cosip",
+ "timeOfAcceptance": "2016-12-07T21:36:41.022Z",
+ "timeOfDiscovery": "2016-12-07T21:36:16.215Z",
+ "displayCurrentTemperature": "TargetTemperature"
+ },
+ "tags": {
+ "typeCategory": "TCHeatingId",
+ "type": "TRadiatorThermostateIdTag"
+ },
+ "capabilities": [
+ "/capability/00a711783dbc4ea0960f923af5c45ad5",
+ "/capability/c8baa78d382649b3995c1eb504469ff4",
+ "/capability/2594a0af3293424e82e19ca64bc84f01"
+ ],
+ "location": "/location/fb7593aa398a47508f2072d593cd1bfa"
+ },
+ {
+ "id": "728baded2f4445a6926e7512cd03e6d3",
+ "manufacturer": "RWE",
+ "version": "1.0",
+ "product": "core.RWE",
+ "serialNumber": "fb7593aa398a47508f2072d593cd1bfa",
+ "type": "VRCC",
+ "config": {
+ "name": "Raumklima",
+ "protocolId": "Virtual",
+ "timeOfAcceptance": "2016-12-07T21:36:41.372Z",
+ "timeOfDiscovery": "2016-12-07T21:36:41.372Z",
+ "underlyingDeviceIds": "a8282a7de8ff4b19958051814f8d8ad6"
+ },
+ "capabilities": [
+ "/capability/37a02890ab9f44bb97b403fb01326a46",
+ "/capability/27afb854c4074ecbace3857380ef2a95",
+ "/capability/b636bed0634044428e29c4a768425798"
+ ],
+ "location": "/location/fb7593aa398a47508f2072d593cd1bfa"
+ },
+ {
+ "id": "4337389c9c4a416dbeacd826f85edc2a",
+ "manufacturer": "RWE",
+ "version": "1.0",
+ "product": "core.RWE",
+ "serialNumber": "921430017792",
+ "type": "WDS",
+ "config": {
+ "name": "Dachfenster",
+ "protocolId": "Cosip",
+ "timeOfAcceptance": "2016-12-07T21:41:08.229Z",
+ "timeOfDiscovery": "2016-12-07T21:40:44.48Z"
+ },
+ "tags": {
+ "typeCategory": "TCWindowId",
+ "type": "TRoofWindowId"
+ },
+ "capabilities": [
+ "/capability/e60a20b2287d4aa690aaffae6d08aa20"
+ ],
+ "location": "/location/fb7593aa398a47508f2072d593cd1bfa"
+ },
+ {
+ "id": "452b3515364d45c49285b99484acde28",
+ "manufacturer": "RWE",
+ "version": "1.0",
+ "product": "core.RWE",
+ "serialNumber": "b09e4978ce8c4be9ad4afcdf6cc09005",
+ "type": "VRCC",
+ "config": {
+ "name": "Raumklima",
+ "protocolId": "Virtual",
+ "timeOfAcceptance": "2016-12-07T21:48:09.505Z",
+ "timeOfDiscovery": "2016-12-07T21:48:09.505Z",
+ "underlyingDeviceIds": "e9b272c83d9a488abccb0d404771b6c6"
+ },
+ "capabilities": [
+ "/capability/a50fba7718ef4fc4be89869e1ce129be",
+ "/capability/67b3bf1bf0da46ca8833debba71a375d",
+ "/capability/1f9a66910ee24b3085e53aeda7e5a4cd"
+ ],
+ "location": "/location/b09e4978ce8c4be9ad4afcdf6cc09005"
+ },
+ {
+ "id": "72e753b09fd44a118997bc615351cabd",
+ "manufacturer": "RWE",
+ "version": "1.0",
+ "product": "core.RWE",
+ "serialNumber": "914120073945",
+ "type": "PSS",
+ "config": {
+ "name": "Fernseher",
+ "protocolId": "Cosip",
+ "timeOfAcceptance": "2016-12-14T19:47:03.674Z",
+ "timeOfDiscovery": "2016-12-14T19:46:46.387Z"
+ },
+ "tags": {
+ "typeCategory": "TCEntertainmentId",
+ "type": "TTVId"
+ },
+ "capabilities": [
+ "/capability/43e345ee14e94996a04972267b5d0489"
+ ],
+ "location": "/location/fb7593aa398a47508f2072d593cd1bfa"
+ },
+ {
+ "id": "cb8f20e7dfa34f76956e4c797dc4c96a",
+ "manufacturer": "RWE",
+ "version": "1.0",
+ "product": "core.RWE",
+ "serialNumber": "914130043726",
+ "type": "WSC2",
+ "config": {
+ "name": "Wandsender",
+ "protocolId": "Cosip",
+ "timeOfAcceptance": "2016-12-14T20:06:16.502Z",
+ "timeOfDiscovery": "2016-12-14T20:06:03.372Z"
+ },
+ "tags": {
+ "typeCategory": "TCSwitchIdTag",
+ "type": "TTwoButtonSwitchIdTag"
+ },
+ "capabilities": [
+ "/capability/41468ab7824b443ea493f12bd87653e0"
+ ],
+ "location": "/location/fb7593aa398a47508f2072d593cd1bfa"
+ },
+ {
+ "id": "4e79eb7a18cf4a3a94d6b26c6ebdb8cb",
+ "manufacturer": "RWE",
+ "version": "1.0",
+ "product": "core.RWE",
+ "serialNumber": "921430016160",
+ "type": "WDS",
+ "config": {
+ "name": "Dachfenster",
+ "protocolId": "Cosip",
+ "timeOfAcceptance": "2016-12-14T20:14:56.433Z",
+ "timeOfDiscovery": "2016-12-14T20:14:34.906Z"
+ },
+ "tags": {
+ "typeCategory": "TCWindowId",
+ "type": "TRoofWindowId"
+ },
+ "capabilities": [
+ "/capability/3bb24f7906c043d2ae77bb2ec9f7d2fe"
+ ],
+ "location": "/location/24442676b2ea406ea671df22068cc02c"
+ },
+ {
+ "id": "fe51785319854f36a621d0b4f8ea0e25",
+ "manufacturer": "RWE",
+ "version": "1.1",
+ "product": "core.RWE",
+ "serialNumber": "914110165056",
+ "type": "RST",
+ "config": {
+ "name": "Heizkörperthermostat",
+ "protocolId": "Cosip",
+ "timeOfAcceptance": "2016-12-14T20:22:41.463Z",
+ "timeOfDiscovery": "2016-12-14T20:21:49.681Z",
+ "displayCurrentTemperature": "TargetTemperature"
+ },
+ "tags": {
+ "typeCategory": "TCHeatingId",
+ "type": "TRadiatorThermostateIdTag"
+ },
+ "capabilities": [
+ "/capability/5a0f2df3f7064cdbb71c2d6340c985ad",
+ "/capability/8ff9ea5c3233434ba87426e0a23b245e",
+ "/capability/7b98d33e5a15404686b2cbc99f725d01"
+ ],
+ "location": "/location/24442676b2ea406ea671df22068cc02c"
+ },
+ {
+ "id": "9756c6b4dcb14fa391e1748d35e286fb",
+ "manufacturer": "RWE",
+ "version": "1.0",
+ "product": "core.RWE",
+ "serialNumber": "24442676b2ea406ea671df22068cc02c",
+ "type": "VRCC",
+ "config": {
+ "name": "Raumklima",
+ "protocolId": "Virtual",
+ "timeOfAcceptance": "2016-12-14T20:22:41.776Z",
+ "timeOfDiscovery": "2016-12-14T20:22:41.777Z",
+ "underlyingDeviceIds": "fe51785319854f36a621d0b4f8ea0e25"
+ },
+ "capabilities": [
+ "/capability/f8d4593186694df4a86c325fc914596b",
+ "/capability/0949f28423864bebb6f0ab64a99fa8a1",
+ "/capability/6db429654212467a8661c1dea714c942"
+ ],
+ "location": "/location/24442676b2ea406ea671df22068cc02c"
+ },
+ {
+ "id": "9fdeb547d2ca4dd0b28631985d525131",
+ "manufacturer": "RWE",
+ "version": "1.0",
+ "product": "core.RWE",
+ "serialNumber": "921430019747",
+ "type": "WDS",
+ "config": {
+ "name": "Dachfenster",
+ "protocolId": "Cosip",
+ "timeOfAcceptance": "2016-12-14T20:27:14.769Z",
+ "timeOfDiscovery": "2016-12-14T20:27:07.685Z"
+ },
+ "tags": {
+ "typeCategory": "TCWindowId",
+ "type": "TRoofWindowId"
+ },
+ "capabilities": [
+ "/capability/8f0e726da08e438fa2a7b8d479ffb37d"
+ ],
+ "location": "/location/30858fcbea3f4833b4e7b3143ea872f7"
+ },
+ {
+ "id": "e28b3b1d02db474aaf1fd910ce67e67e",
+ "manufacturer": "RWE",
+ "version": "1.0",
+ "product": "core.RWE",
+ "serialNumber": "921430037338",
+ "type": "WDS",
+ "config": {
+ "name": "Dachfenster",
+ "protocolId": "Cosip",
+ "timeOfAcceptance": "2016-12-14T20:29:34.071Z",
+ "timeOfDiscovery": "2016-12-14T20:29:08.665Z"
+ },
+ "tags": {
+ "typeCategory": "TCWindowId",
+ "type": "TRoofWindowId"
+ },
+ "capabilities": [
+ "/capability/a665756005e849c6abb8061d4a848eb8"
+ ],
+ "location": "/location/b09e4978ce8c4be9ad4afcdf6cc09005"
+ },
+ {
+ "id": "5b1fe837a6fa4e6587fab1b260adc288",
+ "manufacturer": "RWE",
+ "version": "1.0",
+ "product": "core.RWE",
+ "serialNumber": "914220020108",
+ "type": "ISD2",
+ "config": {
+ "name": "Deckenleuchte",
+ "protocolId": "Cosip",
+ "timeOfAcceptance": "2016-12-28T18:46:59.916Z",
+ "timeOfDiscovery": "2016-12-28T18:46:39.096Z"
+ },
+ "tags": {
+ "typeCategory": "TCLightId",
+ "type": "TCeilingLightId"
+ },
+ "capabilities": [
+ "/capability/0fbb96bde9e24517b5d37d9932b4e24e",
+ "/capability/9a97159073f94470bcb30e4706c0a441"
+ ],
+ "location": "/location/26f2f33fbe7a4e6abddfc1512a65e4e0"
+ },
+ {
+ "id": "f78e30ef7098480cbfa096d363085825",
+ "manufacturer": "RWE",
+ "version": "1.0",
+ "product": "core.RWE",
+ "serialNumber": "921430037314",
+ "type": "WDS",
+ "config": {
+ "name": "Dachfenster",
+ "protocolId": "Cosip",
+ "timeOfAcceptance": "2017-05-13T10:16:54.232Z",
+ "timeOfDiscovery": "2017-05-13T10:16:35.025Z"
+ },
+ "tags": {
+ "typeCategory": "TCWindowId",
+ "type": "TRoofWindowId"
+ },
+ "capabilities": [
+ "/capability/7bc3e21536e64f2d847a09d00b1f28ce"
+ ],
+ "location": "/location/d4a54173ba104bec9734e822e303f4a2"
+ },
+ {
+ "id": "949a1b6b254b49e0a1ba593ce7e88f36",
+ "manufacturer": "RWE",
+ "version": "2.0",
+ "product": "VariableActuator.RWE",
+ "serialNumber": "949a1b6b254b49e0a1ba593ce7e88f36",
+ "type": "VariableActuator",
+ "config": {
+ "name": "Sex",
+ "protocolId": "Virtual",
+ "timeOfAcceptance": "2017-08-28T20:01:49.095Z",
+ "timeOfDiscovery": "2017-08-20T13:39:53.963Z"
+ },
+ "capabilities": [
+ "/capability/a38daa9e72084ca9950c1f353aeb8ef3"
+ ]
+ }
+]
diff --git a/bundles/org.openhab.binding.livisismarthome/src/test/resources/org/openhab/binding/livisismarthome/internal/client/entity/message_configChanged.json b/bundles/org.openhab.binding.livisismarthome/src/test/resources/org/openhab/binding/livisismarthome/internal/client/entity/message_configChanged.json
new file mode 100644
index 00000000000..33d8f115c37
--- /dev/null
+++ b/bundles/org.openhab.binding.livisismarthome/src/test/resources/org/openhab/binding/livisismarthome/internal/client/entity/message_configChanged.json
@@ -0,0 +1,45 @@
+{
+ "sequenceNumber": -1,
+ "type": "ConfigurationChanged",
+ "desc": "/desc/event/ConfigurationChanged",
+ "namespace": "core.RWE",
+ "timestamp": "2019-02-25T19:53:17.1960000Z",
+ "source": "/desc/device/SHC.RWE/1.0",
+ "data": {
+ "devices": [
+ {
+ "id": "0ba965875d37416a935b6552cd6929d9",
+ "manufacturer": "RWE",
+ "version": "2.0",
+ "product": "VariableActuator.RWE",
+ "serialNumber": "0ba965875d37416a935b6552cd6929d9",
+ "type": "VariableActuator",
+ "config": {
+ "name": "Test",
+ "protocolId": "Virtual",
+ "timeOfAcceptance": "2019-02-25T19:53:16.183Z",
+ "timeOfDiscovery": "2019-01-30T01:59:52.451Z"
+ },
+ "capabilities": [
+ "/capability/692d923c583346e2a358db8e32b3c505"
+ ]
+ }
+ ],
+ "locations": [],
+ "capabilities": [
+ {
+ "id": "692d923c583346e2a358db8e32b3c505",
+ "type": "booleanStateActuator",
+ "device": "/device/0ba965875d37416a935b6552cd6929d9",
+ "config": {
+ "name": "Boolean State Actuator",
+ "activityLogActive": true
+ }
+ }
+ ],
+ "interactions": [],
+ "homeSetups": [],
+ "deleted": [],
+ "configVersion": 246
+ }
+}
\ No newline at end of file
diff --git a/bundles/pom.xml b/bundles/pom.xml
index 4d439c90a20..9fc95eb855a 100644
--- a/bundles/pom.xml
+++ b/bundles/pom.xml
@@ -201,6 +201,7 @@
org.openhab.binding.linky
org.openhab.binding.linuxinput
org.openhab.binding.lirc
+ org.openhab.binding.livisismarthome
org.openhab.binding.logreader
org.openhab.binding.loxone
org.openhab.binding.luftdateninfo