/bundles/org.openhab.binding.pulseaudio/ @peuter
/bundles/org.openhab.binding.pushbullet/ @hakan42
/bundles/org.openhab.binding.pushover/ @cweitkamp
+/bundles/org.openhab.binding.qbus/ @QbusKoen
/bundles/org.openhab.binding.radiothermostat/ @mlobstein
/bundles/org.openhab.binding.regoheatpump/ @crnjan
/bundles/org.openhab.binding.revogi/ @andibraeu
--- /dev/null
+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
--- /dev/null
+# Qbus Binding
+
+
+
+This binding for [Qbus](https://qbus.be) communicates with all controllers of the Qbus home automation system.
+
+We also host a site which contains a [manual](https://manualoh.schockaert.tk/) where you can find lots of information to set up openHAB with Qbus client and server (for the moment only in Dutch).
+
+The controllers can not communicate directly with openHAB, therefore we developed a client/server application which you must install prior to enable this binding.
+More information can be found here:
+[Qbus Client/Server](https://github.com/QbusKoen/QbusClientServer-Installer)
+
+With this binding you can control and read almost every output from the Qbus system.
+
+## Supported Things
+
+The following things are supported by the Qbus binding:
+
+- `dimmer`: Dimmer 1 button, 2 button and clc
+- `onOff`: Bistabiel, Timer1-3, Interval
+- `thermostats`: Thermostats - normal and PID
+- `scene`: Scenes
+- `co2`: CO2
+- `rollershutter`: Rollershutter
+- `rollershutter_slats`: Rollerhutter with slats
+
+For now the following Qbus things are not yet supported but will come:
+
+- DMX
+- Timer 4 & 5
+- HVAC
+- Humidity
+- Renson
+- Duco
+- Kinetura
+- Energy monitor
+- Weather station
+
+
+## Discovery
+
+The discovery service is not yet implemented but the System Manager III software of Qbus generates things and item files from the programming, which you can use directly in openHAB.
+
+## Bridge configuration
+
+```
+Bridge qbus:bridge:CTD001122 [ addr="localhost", sn="001122", port=8447, serverCheck=10 ] {
+...
+}
+```
+
+
+
+| Property | Default | Required | Description |
+| ------------- | --------- | -------- | ------------------------------------------------------------------------------------------------------------------------------------ |
+| `addr` | localhost | YES | The ip address of the machine where the Qbus Server runs |
+| `sn` | | YES | The serial number of your controller |
+| `port` | 8447 | YES | The communication port of the client/server |
+| `serverCheck` | 10 | NO | Refresh time - After x minutes there will be a check if server is still running and if client is still connected. If not - reconnect |
+
+
+
+## Things configuration
+
+| Thing Type ID | Channel Name | Read only | description |
+| --------------------- | ------------- | --------- | ------------------------------------------------------ |
+| `onOff` | switch | No | This is the channel for Bistable, Timers and Intervals |
+| `dimmer` | brightness | No | This is the channel for Dimmers 1&2 buttons and CLC |
+| `scene` | Switch | No | This is the channel for scenes |
+| `co2` | co2 | Yes | This is the channel for CO2 sensors |
+| `rollershutter` | rollershutter | No | This is the channel for rollershutters |
+| `rollershutter_slats` | rollershutter | No | This is the channel for rollershutters with slats |
+| `thermostat` | setpoint | No | This is the channel for thermostats setpoint |
+| `thermostat` | measured | Yes | This is the channel for thermostats currenttemp |
+| `thermostat` | mode | No | This is the channel for thermostats mode |
+
+
+## Full Example
+
+### Things
+
+```
+Bridge qbus:bridge:CTD001122 [ addr="localhost", sn="001122", port=8447, serverCheck=10 ] {
+ dimmer 1 "ToonzaalLED" [ dimmerId=100 ]
+ onOff 30 "Toonzaal230V" [ bistabielId=76 ]
+ thermostat 50 "Service" [ thermostatId=99 ]
+ scene 70 "Disco" [ sceneId=36 ]
+ co2 100 "Productie" [ co2Id=26 ]
+ rollershutter 120 "Roller1" [ rolId=268 ]
+ rollershutter_slats 121 "Roller2" [ rolId=264 ]
+}
+```
+
+### Items
+
+```
+Dimmer ToonzaalLED <light> [ "Lighting" ] {channel="qbus:dimmer:CTD007841:1:brightness"}
+Switch Toonzaal230V <light> {channel="qbus:onOff:CTD007841:30:switch"}
+Number:Temperature ServiceSP"[%.1f %unit%]" (GroepThermostaten) {channel="qbus:thermostat:CTD007841:50:setpoint"}
+Number:Temperature ServiceCT"[%.1f %unit%]" (GroepThermostaten) {channel="qbus:thermostat:CTD007841:50:measured"}
+Number ServiceMode (GroepThermostaten) {channel="qbus:thermostat:CTD007841:50:mode",ihc="0x33c311" , autoupdate="true"}
+Switch Disco <light> {channel="qbus:scene:CTD007841:36:scene"}
+Number ProductieCO2 {channel="qbus:co2:CTD007841:100:co2"}
+Rollershutter Roller1 {channel="qbus:rollershutter:CTD007841:120:rollershutter"}
+Rollershutter Roller2 {channel="qbus:rollershutter_slats:CTD007841:121:rollershutter"}
+Dimmer Roller2_slats {channel="qbus:rollershutter_slats:CTD007841:121:slats"}
+```
+
+This is the link to the [Qbus forum](https://qbusforum.be). This forum is mainly in dutch and you can find a lot of information about the pre testings of this binding and offers a way to communicate with other users.
+
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://maven.apache.org/POM/4.0.0"
+ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+
+ <modelVersion>4.0.0</modelVersion>
+
+ <parent>
+ <groupId>org.openhab.addons.bundles</groupId>
+ <artifactId>org.openhab.addons.reactor.bundles</artifactId>
+ <version>3.1.0-SNAPSHOT</version>
+ </parent>
+
+ <artifactId>org.openhab.binding.qbus</artifactId>
+
+ <name>openHAB Add-ons :: Bundles :: Qbus Binding</name>
+
+</project>
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+
+ Copyright (c) 2010-2020 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
+
+-->
+<features name="org.openhab.binding.qbus-${project.version}" xmlns="http://karaf.apache.org/xmlns/features/v1.4.0">
+ <repository>mvn:org.openhab.core.features.karaf/org.openhab.core.features.karaf.openhab-core/${ohc.version}/xml/features</repository>
+
+ <feature name="openhab-binding-qbus" description="Qbus Binding" version="${project.version}">
+ <feature>openhab-runtime-base</feature>
+ <bundle start-level="80">mvn:org.openhab.addons.bundles/org.openhab.binding.qbus/${project.version}</bundle>
+ </feature>
+</features>
--- /dev/null
+/**
+ * Copyright (c) 2010-2021 Contributors to the openHAB project
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ */
+package org.openhab.binding.qbus.internal;
+
+import java.util.Collections;
+import java.util.Set;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.openhab.core.thing.ThingTypeUID;
+
+/**
+ * The {@link QbusBindingConstants} class defines common constants, which are
+ * used across the whole binding.
+ *
+ * @author Koen Schockaert - Initial contribution
+ */
+@NonNullByDefault
+public class QbusBindingConstants {
+
+ private static final String BINDING_ID = "qbus";
+
+ // bridge
+ public static final ThingTypeUID BRIDGE_THING_TYPE = new ThingTypeUID(BINDING_ID, "bridge");
+ public static final Set<ThingTypeUID> BRIDGE_THING_TYPES_UIDS = Collections.singleton(BRIDGE_THING_TYPE);
+ // Bridge config properties
+ public static final String CONFIG_HOST_NAME = "addr";
+ public static final String CONFIG_PORT = "port";
+ public static final String CONFIG_SN = "sn";
+ public static final String CONFIG_SERVERCHECK = "serverCheck";
+
+ // generic thing types
+ public static final ThingTypeUID THING_TYPE_CO2 = new ThingTypeUID(BINDING_ID, "co2");
+ public static final ThingTypeUID THING_TYPE_SCENE = new ThingTypeUID(BINDING_ID, "scene");
+ public static final ThingTypeUID THING_TYPE_ON_OFF_LIGHT = new ThingTypeUID(BINDING_ID, "onOff");
+ public static final ThingTypeUID THING_TYPE_DIMMABLE_LIGHT = new ThingTypeUID(BINDING_ID, "dimmer");
+ public static final ThingTypeUID THING_TYPE_ROLLERSHUTTER = new ThingTypeUID(BINDING_ID, "rollershutter");
+ public static final ThingTypeUID THING_TYPE_ROLLERSHUTTER_SLATS = new ThingTypeUID(BINDING_ID,
+ "rollershutter_slats");
+ public static final ThingTypeUID THING_TYPE_THERMOSTAT = new ThingTypeUID(BINDING_ID, "thermostat");
+
+ // List of all Thing Type UIDs
+ public static final Set<ThingTypeUID> SCENE_THING_TYPES_UIDS = Set.of(THING_TYPE_SCENE);
+ public static final Set<ThingTypeUID> CO2_THING_TYPES_UIDS = Set.of(THING_TYPE_CO2);
+ public static final Set<ThingTypeUID> ROLLERSHUTTER_THING_TYPES_UIDS = Set.of(THING_TYPE_ROLLERSHUTTER);
+ public static final Set<ThingTypeUID> ROLLERSHUTTER_SLATS_THING_TYPES_UIDS = Set.of(THING_TYPE_ROLLERSHUTTER_SLATS);
+ public static final Set<ThingTypeUID> BISTABIEL_THING_TYPES_UIDS = Set.of(THING_TYPE_ON_OFF_LIGHT);
+ public static final Set<ThingTypeUID> THERMOSTAT_THING_TYPES_UIDS = Set.of(THING_TYPE_THERMOSTAT);
+ public static final Set<ThingTypeUID> DIMMER_THING_TYPES_UIDS = Set.of(THING_TYPE_ON_OFF_LIGHT,
+ THING_TYPE_DIMMABLE_LIGHT);
+
+ public static final Set<ThingTypeUID> SUPPORTED_THING_TYPES_UIDS = Set.of(THING_TYPE_ON_OFF_LIGHT,
+ THING_TYPE_DIMMABLE_LIGHT, THING_TYPE_THERMOSTAT, THING_TYPE_SCENE, THING_TYPE_CO2,
+ THING_TYPE_ROLLERSHUTTER, THING_TYPE_ROLLERSHUTTER_SLATS);
+
+ // List of all Channel ids
+ public static final String CHANNEL_SWITCH = "switch";
+ public static final String CHANNEL_SCENE = "scene";
+ public static final String CHANNEL_BRIGHTNESS = "brightness";
+ public static final String CHANNEL_MEASURED = "measured";
+ public static final String CHANNEL_SETPOINT = "setpoint";
+ public static final String CHANNEL_MODE = "mode";
+ public static final String CHANNEL_CO2 = "co2";
+ public static final String CHANNEL_ROLLERSHUTTER = "rollershutter";
+ public static final String CHANNEL_SLATS = "slats";
+
+ // Thing config properties
+ public static final String CONFIG_BISTABIEL_ID = "bistabielId";
+ public static final String CONFIG_DIMMER_ID = "dimmerId";
+ public static final String CONFIG_THERMOSTAT_ID = "thermostatId";
+ public static final String CONFIG_SCENE_ID = "sceneId";
+ public static final String CONFIG_CO2_ID = "co2Id";
+ public static final String CONFIG_ROLLERSHUTTER_ID = "rolId";
+ public static final String CONFIG_STEP_VALUE = "step";
+}
--- /dev/null
+/**
+ * Copyright (c) 2010-2021 Contributors to the openHAB project
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ */
+
+package org.openhab.binding.qbus.internal;
+
+import java.io.IOException;
+import java.util.concurrent.ScheduledFuture;
+import java.util.concurrent.TimeUnit;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.eclipse.jdt.annotation.Nullable;
+import org.openhab.binding.qbus.internal.protocol.QbusCommunication;
+import org.openhab.core.thing.Bridge;
+import org.openhab.core.thing.ChannelUID;
+import org.openhab.core.thing.ThingStatus;
+import org.openhab.core.thing.ThingStatusDetail;
+import org.openhab.core.thing.ThingStatusInfo;
+import org.openhab.core.thing.binding.BaseBridgeHandler;
+import org.openhab.core.types.Command;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * {@link QbusBridgeHandler} is the handler for a Qbus controller
+ *
+ * @author Koen Schockaert - Initial Contribution
+ */
+
+@NonNullByDefault
+public class QbusBridgeHandler extends BaseBridgeHandler {
+
+ private @Nullable QbusCommunication qbusComm;
+
+ protected @Nullable QbusConfiguration bridgeConfig = new QbusConfiguration();
+
+ private @Nullable ScheduledFuture<?> refreshTimer;
+
+ private final Logger logger = LoggerFactory.getLogger(QbusBridgeHandler.class);
+
+ public QbusBridgeHandler(Bridge Bridge) {
+ super(Bridge);
+ }
+
+ /**
+ * Initialize the bridge
+ */
+ @Override
+ public void initialize() {
+ Integer serverCheck = getServerCheck();
+
+ readConfig();
+
+ createCommunicationObject();
+
+ if (serverCheck != null) {
+ this.setupRefreshTimer(serverCheck);
+ }
+ }
+
+ /**
+ * Sets the Bridge call back
+ */
+ private void setBridgeCallBack() {
+ QbusCommunication qbusCommunication = getQbusCommunication();
+ if (qbusCommunication != null) {
+ qbusCommunication.setBridgeCallBack(this);
+ }
+ }
+
+ /**
+ * Create communication object to Qbus server and start communication.
+ *
+ * @param addr : IP address of Qbus server
+ * @param port : Communication port of QbusServer
+ */
+ private void createCommunicationObject() {
+ scheduler.submit(() -> {
+
+ setQbusCommunication(new QbusCommunication(thing));
+
+ QbusCommunication qbusCommunication = getQbusCommunication();
+
+ setBridgeCallBack();
+
+ Integer serverCheck = getServerCheck();
+ String sn = getSn();
+ if (serverCheck != null) {
+ if (sn != null) {
+ if (qbusCommunication != null) {
+ try {
+ qbusCommunication.startCommunication();
+ } catch (InterruptedException e) {
+ String msg = e.getMessage();
+ bridgeOffline(ThingStatusDetail.COMMUNICATION_ERROR,
+ "Communication wit Qbus server could not be established, will try to reconnect every "
+ + serverCheck + " minutes. InterruptedException: " + msg);
+ return;
+ } catch (IOException e) {
+ String msg = e.getMessage();
+ bridgeOffline(ThingStatusDetail.COMMUNICATION_ERROR,
+ "Communication wit Qbus server could not be established, will try to reconnect every "
+ + serverCheck + " minutes. IOException: " + msg);
+ return;
+ }
+
+ if (!qbusCommunication.communicationActive()) {
+ bridgeOffline(ThingStatusDetail.COMMUNICATION_ERROR,
+ "No communication with Qbus Server, will try to reconnect every " + serverCheck
+ + " minutes");
+ return;
+ }
+
+ if (!qbusCommunication.clientConnected()) {
+ bridgePending("Waiting for Qbus client to come online");
+ return;
+ }
+
+ }
+ }
+ }
+ });
+ }
+
+ /**
+ * Updates offline status off the Bridge when an error occurs.
+ *
+ * @param status
+ * @param detail
+ * @param message
+ */
+ public void bridgeOffline(ThingStatusDetail detail, String message) {
+ updateStatus(ThingStatus.OFFLINE, detail, message);
+ }
+
+ /**
+ * Updates pending status off the Bridge (usualay when Qbus client id not connected)
+ *
+ * @param message
+ */
+ public void bridgePending(String message) {
+ updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.HANDLER_CONFIGURATION_PENDING, message);
+ }
+
+ /**
+ * Put bridge online when error in communication resolved.
+ */
+ public void bridgeOnline() {
+ updateStatus(ThingStatus.ONLINE);
+ }
+
+ /**
+ * Initializes a timer that check the communication with Qbus server/client and tries to re-establish communication.
+ *
+ * @param refreshInterval Time before refresh in minutes.
+ */
+ private void setupRefreshTimer(int refreshInterval) {
+ ScheduledFuture<?> timer = refreshTimer;
+
+ if (timer != null) {
+ timer.cancel(true);
+ refreshTimer = null;
+ }
+
+ if (refreshInterval == 0) {
+ return;
+ }
+
+ refreshTimer = scheduler.scheduleWithFixedDelay(() -> {
+ QbusCommunication comm = getCommunication();
+ Integer serverCheck = getServerCheck();
+
+ if (comm != null) {
+ if (serverCheck != null) {
+ if (!comm.communicationActive()) {
+ // Disconnected from Qbus Server, restart communication
+ try {
+ comm.startCommunication();
+ } catch (InterruptedException e) {
+ String msg = e.getMessage();
+ bridgeOffline(ThingStatusDetail.COMMUNICATION_ERROR,
+ "Communication wit Qbus server could not be established, will try to reconnect every "
+ + serverCheck + " minutes. InterruptedException: " + msg);
+ } catch (IOException e) {
+ String msg = e.getMessage();
+ bridgeOffline(ThingStatusDetail.COMMUNICATION_ERROR,
+ "Communication wit Qbus server could not be established, will try to reconnect every "
+ + serverCheck + " minutes. IOException: " + msg);
+ }
+ }
+ }
+ }
+ }, refreshInterval, refreshInterval, TimeUnit.MINUTES);
+ }
+
+ /**
+ * Disposes the Bridge and stops communication with the Qbus server
+ */
+ @Override
+ public void dispose() {
+ ScheduledFuture<?> timer = refreshTimer;
+ if (timer != null) {
+ timer.cancel(true);
+ }
+
+ refreshTimer = null;
+
+ QbusCommunication comm = getCommunication();
+
+ if (comm != null) {
+ try {
+ comm.stopCommunication();
+ } catch (IOException e) {
+ String message = e.toString();
+ logger.debug("Error on stopping communication.{} ", message);
+ }
+ }
+
+ comm = null;
+ }
+
+ /**
+ * Reconnect to Qbus server if controller is offline
+ */
+ public void ctdOffline() {
+ bridgePending("Waiting for CTD connection");
+ }
+
+ /**
+ * Get BridgeCommunication
+ *
+ * @return BridgeCommunication
+ */
+ public @Nullable QbusCommunication getQbusCommunication() {
+ if (this.qbusComm != null) {
+ return this.qbusComm;
+ } else {
+ return null;
+ }
+ }
+
+ /**
+ * Sets BridgeCommunication
+ *
+ * @param BridgeCommunication
+ */
+ void setQbusCommunication(QbusCommunication comm) {
+ this.qbusComm = comm;
+ }
+
+ /**
+ * Gets the status off the Bridge
+ *
+ * @return
+ */
+ public ThingStatus getStatus() {
+ return thing.getStatus();
+ }
+
+ /**
+ * Gets the status off the Bridge
+ *
+ * @return
+ */
+ public ThingStatusDetail getStatusDetails() {
+ ThingStatusInfo status = thing.getStatusInfo();
+ ThingStatusDetail detail = status.getStatusDetail();
+ return detail;
+ }
+
+ /**
+ * Sets the configuration parameters
+ */
+ protected void readConfig() {
+ bridgeConfig = getConfig().as(QbusConfiguration.class);
+ }
+
+ /**
+ * Get the Qbus communication object.
+ *
+ * @return Qbus communication object
+ */
+ public @Nullable QbusCommunication getCommunication() {
+ return this.qbusComm;
+ }
+
+ /**
+ * Get the ip address of the Qbus server.
+ *
+ * @return the ip address
+ */
+ public @Nullable String getAddress() {
+ QbusConfiguration localConfig = this.bridgeConfig;
+
+ if (localConfig != null) {
+ return localConfig.addr;
+ } else {
+ return null;
+ }
+ }
+
+ /**
+ * Get the listening port of the Qbus server.
+ *
+ * @return
+ */
+ public @Nullable Integer getPort() {
+ QbusConfiguration localConfig = this.bridgeConfig;
+
+ if (localConfig != null) {
+ return localConfig.port;
+ } else {
+ return null;
+ }
+ }
+
+ /**
+ * Get the serial nr of the Qbus server.
+ *
+ * @return the serial nr of the controller
+ */
+ public @Nullable String getSn() {
+ QbusConfiguration localConfig = this.bridgeConfig;
+
+ if (localConfig != null) {
+ return localConfig.sn;
+ } else {
+ return null;
+ }
+ }
+
+ /**
+ * Get the refresh interval.
+ *
+ * @return the refresh interval
+ */
+ public @Nullable Integer getServerCheck() {
+ QbusConfiguration localConfig = this.bridgeConfig;
+
+ if (localConfig != null) {
+ return localConfig.serverCheck;
+ } else {
+ return null;
+ }
+ }
+
+ @Override
+ public void handleCommand(ChannelUID channelUID, Command command) {
+ }
+}
--- /dev/null
+/**
+ * Copyright (c) 2010-2021 Contributors to the openHAB project
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ */
+
+package org.openhab.binding.qbus.internal;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.eclipse.jdt.annotation.Nullable;
+
+/**
+ * Class {@link QbusConfiguration} Configuration Class
+ *
+ * @author Koen Schockaert - Initial Contribution
+ */
+
+@NonNullByDefault
+public class QbusConfiguration {
+ public @Nullable String addr;
+ public @Nullable Integer port;
+ public @Nullable String sn;
+ public @Nullable Integer serverCheck;
+}
--- /dev/null
+/**
+ * Copyright (c) 2010-2021 Contributors to the openHAB project
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ */
+package org.openhab.binding.qbus.internal;
+
+import static org.openhab.binding.qbus.internal.QbusBindingConstants.*;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.eclipse.jdt.annotation.Nullable;
+import org.openhab.binding.qbus.internal.handler.QbusBistabielHandler;
+import org.openhab.binding.qbus.internal.handler.QbusCO2Handler;
+import org.openhab.binding.qbus.internal.handler.QbusDimmerHandler;
+import org.openhab.binding.qbus.internal.handler.QbusRolHandler;
+import org.openhab.binding.qbus.internal.handler.QbusSceneHandler;
+import org.openhab.binding.qbus.internal.handler.QbusThermostatHandler;
+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.Component;
+
+/**
+ * The {@link qbusHandlerFactory} is responsible for creating things and thing
+ * handlers.
+ *
+ * @author Koen Schockaert - Initial Contribution
+ */
+
+@Component(service = ThingHandlerFactory.class, configurationPid = "binding.qbus")
+@NonNullByDefault
+public class QbusHandlerFactory extends BaseThingHandlerFactory {
+
+ @Override
+ public boolean supportsThingType(ThingTypeUID thingTypeUID) {
+ return SUPPORTED_THING_TYPES_UIDS.contains(thingTypeUID) || BRIDGE_THING_TYPES_UIDS.contains(thingTypeUID);
+ }
+
+ @Override
+ protected @Nullable ThingHandler createHandler(Thing thing) {
+ if (BRIDGE_THING_TYPES_UIDS.contains(thing.getThingTypeUID())) {
+ QbusBridgeHandler handler = new QbusBridgeHandler((Bridge) thing);
+ return handler;
+ } else if (SCENE_THING_TYPES_UIDS.contains(thing.getThingTypeUID())) {
+ return new QbusSceneHandler(thing);
+ } else if (BISTABIEL_THING_TYPES_UIDS.contains(thing.getThingTypeUID())) {
+ return new QbusBistabielHandler(thing);
+ } else if (THERMOSTAT_THING_TYPES_UIDS.contains(thing.getThingTypeUID())) {
+ return new QbusThermostatHandler(thing);
+ } else if (DIMMER_THING_TYPES_UIDS.contains(thing.getThingTypeUID())) {
+ return new QbusDimmerHandler(thing);
+ } else if (CO2_THING_TYPES_UIDS.contains(thing.getThingTypeUID())) {
+ return new QbusCO2Handler(thing);
+ } else if (ROLLERSHUTTER_THING_TYPES_UIDS.contains(thing.getThingTypeUID())) {
+ return new QbusRolHandler(thing);
+ } else if (ROLLERSHUTTER_SLATS_THING_TYPES_UIDS.contains(thing.getThingTypeUID())) {
+ return new QbusRolHandler(thing);
+ }
+
+ return null;
+ }
+}
--- /dev/null
+/**
+ * Copyright (c) 2010-2021 Contributors to the openHAB project
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ */
+package org.openhab.binding.qbus.internal.handler;
+
+import static org.openhab.binding.qbus.internal.QbusBindingConstants.CHANNEL_SWITCH;
+import static org.openhab.core.types.RefreshType.REFRESH;
+
+import java.io.IOException;
+import java.util.Map;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.eclipse.jdt.annotation.Nullable;
+import org.openhab.binding.qbus.internal.QbusBridgeHandler;
+import org.openhab.binding.qbus.internal.protocol.QbusBistabiel;
+import org.openhab.binding.qbus.internal.protocol.QbusCommunication;
+import org.openhab.core.library.types.OnOffType;
+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.types.Command;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * The {@link QbusBistabielHandler} is responsible for handling the Bistable outputs of Qbus
+ *
+ * @author Koen Schockaert - Initial Contribution
+ */
+
+@NonNullByDefault
+public class QbusBistabielHandler extends QbusGlobalHandler {
+
+ private final Logger logger = LoggerFactory.getLogger(QbusBistabielHandler.class);
+
+ protected @Nullable QbusThingsConfig bistabielConfig = new QbusThingsConfig();
+
+ private @Nullable Integer bistabielId;
+
+ private @Nullable String sn;
+
+ public QbusBistabielHandler(Thing thing) {
+ super(thing);
+ }
+
+ /**
+ * Main initialization
+ */
+ @Override
+ public void initialize() {
+ readConfig();
+
+ this.bistabielId = getId();
+
+ setSN();
+
+ scheduler.submit(() -> {
+ QbusCommunication controllerComm;
+
+ if (this.bistabielId != null) {
+ controllerComm = getCommunication("Bistabiel", this.bistabielId);
+ } else {
+ thingOffline(ThingStatusDetail.CONFIGURATION_ERROR, "ID for BISTABIEL no set! " + this.bistabielId);
+ return;
+ }
+
+ if (controllerComm == null) {
+ thingOffline(ThingStatusDetail.CONFIGURATION_ERROR,
+ "ID for BISTABIEL not known in controller " + this.bistabielId);
+ return;
+ }
+
+ Map<Integer, QbusBistabiel> bistabielCommLocal = controllerComm.getBistabiel();
+
+ QbusBistabiel outputLocal = bistabielCommLocal.get(this.bistabielId);
+
+ if (outputLocal == null) {
+ thingOffline(ThingStatusDetail.CONFIGURATION_ERROR,
+ "Bridge could not initialize BISTABIEL ID " + this.bistabielId);
+ return;
+ }
+
+ outputLocal.setThingHandler(this);
+ handleStateUpdate(outputLocal);
+
+ QbusBridgeHandler qBridgeHandler = getBridgeHandler("Bistabiel", this.bistabielId);
+
+ if (qBridgeHandler != null) {
+ if (qBridgeHandler.getStatus() == ThingStatus.ONLINE) {
+ updateStatus(ThingStatus.ONLINE);
+ } else {
+ updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.BRIDGE_OFFLINE,
+ "Bridge offline for BISTABIEL ID " + this.bistabielId);
+ }
+ }
+ });
+ }
+
+ /**
+ * Handle the status update from the bistabiel
+ */
+ @Override
+ public void handleCommand(ChannelUID channelUID, Command command) {
+ QbusCommunication qComm = getCommunication("Bistabiel", this.bistabielId);
+
+ if (qComm == null) {
+ thingOffline(ThingStatusDetail.CONFIGURATION_ERROR,
+ "ID for BISTABIEL not known in controller " + this.bistabielId);
+ return;
+ } else {
+ Map<Integer, QbusBistabiel> bistabielComm = qComm.getBistabiel();
+
+ QbusBistabiel qBistabiel = bistabielComm.get(this.bistabielId);
+
+ if (qBistabiel == null) {
+ thingOffline(ThingStatusDetail.CONFIGURATION_ERROR,
+ "ID for BISTABIEL not known in controller " + this.bistabielId);
+ return;
+ } else {
+ scheduler.submit(() -> {
+ if (!qComm.communicationActive()) {
+ restartCommunication(qComm, "Bistabiel", this.bistabielId);
+ }
+
+ if (qComm.communicationActive()) {
+ if (command == REFRESH) {
+ handleStateUpdate(qBistabiel);
+ return;
+ }
+
+ switch (channelUID.getId()) {
+ case CHANNEL_SWITCH:
+ try {
+ handleSwitchCommand(qBistabiel, command);
+ } catch (IOException e) {
+ String message = e.getMessage();
+ logger.warn("Error on executing Switch for bistabiel ID {}. IOException: {}",
+ this.bistabielId, message);
+ } catch (InterruptedException e) {
+ String message = e.getMessage();
+ logger.warn(
+ "Error on executing Switch for bistabiel ID {}. Interruptedexception {}",
+ this.bistabielId, message);
+ }
+ break;
+
+ default:
+ thingOffline(ThingStatusDetail.COMMUNICATION_ERROR,
+ "Unknown Channel " + channelUID.getId());
+ }
+ }
+ });
+ }
+ }
+ }
+
+ /**
+ * Executes the switch command
+ *
+ * @throws IOException
+ * @throws InterruptedException
+ */
+ private void handleSwitchCommand(QbusBistabiel qBistabiel, Command command)
+ throws InterruptedException, IOException {
+ String snr = getSN();
+ if (snr != null) {
+ if (command instanceof OnOffType) {
+ if (command == OnOffType.OFF) {
+ qBistabiel.execute(0, snr);
+ } else {
+ qBistabiel.execute(100, snr);
+ }
+ } else {
+ thingOffline(ThingStatusDetail.CONFIGURATION_ERROR,
+ "No serial number configured for BISTABIEL " + this.bistabielId);
+ }
+ }
+ }
+
+ /**
+ * Method to update state of channel, called from Qbus Bistabiel.
+ *
+ * @param qBistabiel
+ */
+ public void handleStateUpdate(QbusBistabiel qBistabiel) {
+ Integer bistabielState = qBistabiel.getState();
+ if (bistabielState != null) {
+ updateState(CHANNEL_SWITCH, (bistabielState == 0) ? OnOffType.OFF : OnOffType.ON);
+ }
+ }
+
+ /**
+ * Returns the serial number of the controller
+ *
+ * @return the serial nr
+ */
+ public @Nullable String getSN() {
+ return sn;
+ }
+
+ /**
+ * Sets the serial number of the controller
+ */
+ public void setSN() {
+ QbusBridgeHandler qBridgeHandler = getBridgeHandler("Bistabiel", this.bistabielId);
+ if (qBridgeHandler == null) {
+ thingOffline(ThingStatusDetail.COMMUNICATION_ERROR,
+ "No communication with Qbus Bridge for BISTABIEL " + this.bistabielId);
+ return;
+ }
+ sn = qBridgeHandler.getSn();
+ }
+
+ /**
+ * Read the configuration
+ */
+ protected synchronized void readConfig() {
+ bistabielConfig = getConfig().as(QbusThingsConfig.class);
+ }
+
+ /**
+ * Returns the Id from the configuration
+ *
+ * @return outputId
+ */
+ public @Nullable Integer getId() {
+ QbusThingsConfig localConfig = bistabielConfig;
+ if (localConfig != null) {
+ return localConfig.bistabielId;
+ } else {
+ return null;
+ }
+ }
+}
--- /dev/null
+/**
+ * Copyright (c) 2010-2021 Contributors to the openHAB project
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ */
+package org.openhab.binding.qbus.internal.handler;
+
+import static org.openhab.binding.qbus.internal.QbusBindingConstants.CHANNEL_CO2;
+import static org.openhab.core.types.RefreshType.REFRESH;
+
+import java.util.Map;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.eclipse.jdt.annotation.Nullable;
+import org.openhab.binding.qbus.internal.QbusBridgeHandler;
+import org.openhab.binding.qbus.internal.protocol.QbusCO2;
+import org.openhab.binding.qbus.internal.protocol.QbusCommunication;
+import org.openhab.core.library.types.DecimalType;
+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.types.Command;
+
+/**
+ * The {@link QbusCO2Handler} is responsible for handling commands, which are
+ * sent to one of the channels.
+ *
+ * @author Koen Schockaert - Initial Contribution
+ */
+
+@NonNullByDefault
+public class QbusCO2Handler extends QbusGlobalHandler {
+ protected @Nullable QbusThingsConfig config;
+
+ protected @Nullable QbusThingsConfig co2Config = new QbusThingsConfig();
+
+ private @Nullable Integer co2Id;
+
+ private @Nullable String sn;
+
+ public QbusCO2Handler(Thing thing) {
+ super(thing);
+ }
+
+ /**
+ * Main initialization
+ */
+ @Override
+ public void initialize() {
+ readConfig();
+
+ this.co2Id = getId();
+
+ setSN();
+
+ scheduler.submit(() -> {
+ QbusCommunication controllerComm;
+
+ if (this.co2Id != null) {
+ controllerComm = getCommunication("CO2", this.co2Id);
+ } else {
+ thingOffline(ThingStatusDetail.CONFIGURATION_ERROR, "ID for CO2 no set! " + this.co2Id);
+ return;
+ }
+
+ if (controllerComm == null) {
+ thingOffline(ThingStatusDetail.CONFIGURATION_ERROR, "ID for CO2 not known in controller " + this.co2Id);
+ return;
+ }
+
+ Map<Integer, QbusCO2> co2CommLocal = controllerComm.getCo2();
+
+ QbusCO2 outputLocal = co2CommLocal.get(this.co2Id);
+
+ if (outputLocal == null) {
+ thingOffline(ThingStatusDetail.CONFIGURATION_ERROR, "Bridge could not initialize CO2 ID " + this.co2Id);
+ return;
+ }
+
+ outputLocal.setThingHandler(this);
+ handleStateUpdate(outputLocal);
+
+ QbusBridgeHandler qBridgeHandler = getBridgeHandler("CO2", this.co2Id);
+
+ if (qBridgeHandler != null) {
+ if (qBridgeHandler.getStatus() == ThingStatus.ONLINE) {
+ updateStatus(ThingStatus.ONLINE);
+ } else {
+ updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.BRIDGE_OFFLINE,
+ "Bridge offline for CO2 ID " + this.co2Id);
+ }
+ }
+ });
+ }
+
+ /**
+ * Handle the status update from the thing
+ */
+ @Override
+ public void handleCommand(ChannelUID channelUID, Command command) {
+ QbusCommunication qComm = getCommunication("CO2", this.co2Id);
+
+ if (qComm == null) {
+ thingOffline(ThingStatusDetail.CONFIGURATION_ERROR, "ID for CO2 not known in controller " + this.co2Id);
+ return;
+ } else {
+ Map<Integer, QbusCO2> co2Comm = qComm.getCo2();
+
+ QbusCO2 qCo2 = co2Comm.get(this.co2Id);
+
+ if (qCo2 == null) {
+ thingOffline(ThingStatusDetail.CONFIGURATION_ERROR, "ID for CO2 not known in controller " + this.co2Id);
+ return;
+ } else {
+ scheduler.submit(() -> {
+ if (!qComm.communicationActive()) {
+ restartCommunication(qComm, "CO2", this.co2Id);
+ }
+
+ if (qComm.communicationActive()) {
+ if (command == REFRESH) {
+ handleStateUpdate(qCo2);
+ return;
+ }
+
+ switch (channelUID.getId()) {
+ default:
+ thingOffline(ThingStatusDetail.COMMUNICATION_ERROR,
+ "Unknown Channel " + channelUID.getId());
+ }
+ }
+ });
+ }
+ }
+ }
+
+ /**
+ * Method to update state of channel, called from Qbus CO2.
+ */
+ public void handleStateUpdate(QbusCO2 qCo2) {
+ Integer co2State = qCo2.getState();
+ if (co2State != null) {
+ updateState(CHANNEL_CO2, new DecimalType(co2State));
+ }
+ }
+
+ /**
+ * Returns the serial number of the controller
+ *
+ * @return the serial nr
+ */
+ public @Nullable String getSN() {
+ return sn;
+ }
+
+ /**
+ * Sets the serial number of the controller
+ */
+ public void setSN() {
+ QbusBridgeHandler qBridgeHandler = getBridgeHandler("CO2", this.co2Id);
+ if (qBridgeHandler == null) {
+ thingOffline(ThingStatusDetail.COMMUNICATION_ERROR,
+ "No communication with Qbus Bridge for CO2 " + this.co2Id);
+ return;
+ }
+ sn = qBridgeHandler.getSn();
+ }
+
+ /**
+ * Read the configuration
+ */
+ protected synchronized void readConfig() {
+ co2Config = getConfig().as(QbusThingsConfig.class);
+ }
+
+ /**
+ * Returns the Id from the configuration
+ *
+ * @return outputId
+ */
+ public @Nullable Integer getId() {
+ QbusThingsConfig localConfig = this.co2Config;
+ if (localConfig != null) {
+ return localConfig.co2Id;
+ } else {
+ return null;
+ }
+ }
+}
--- /dev/null
+/**
+ * Copyright (c) 2010-2021 Contributors to the openHAB project
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ */
+package org.openhab.binding.qbus.internal.handler;
+
+import static org.openhab.binding.qbus.internal.QbusBindingConstants.*;
+import static org.openhab.core.types.RefreshType.REFRESH;
+
+import java.io.IOException;
+import java.util.Map;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.eclipse.jdt.annotation.Nullable;
+import org.openhab.binding.qbus.internal.QbusBridgeHandler;
+import org.openhab.binding.qbus.internal.protocol.QbusCommunication;
+import org.openhab.binding.qbus.internal.protocol.QbusDimmer;
+import org.openhab.core.library.types.IncreaseDecreaseType;
+import org.openhab.core.library.types.OnOffType;
+import org.openhab.core.library.types.PercentType;
+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.types.Command;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * The {@link QbusDimmerHandler} is responsible for handling the dimmable outputs of Qbus
+ *
+ * @author Koen Schockaert - Initial Contribution
+ */
+
+@NonNullByDefault
+public class QbusDimmerHandler extends QbusGlobalHandler {
+
+ private final Logger logger = LoggerFactory.getLogger(QbusDimmerHandler.class);
+
+ protected @Nullable QbusThingsConfig dimmerConfig = new QbusThingsConfig();
+
+ private @Nullable Integer dimmerId;
+
+ private @Nullable String sn;
+
+ public QbusDimmerHandler(Thing thing) {
+ super(thing);
+ }
+
+ /**
+ * Main initialization
+ */
+ @Override
+ public void initialize() {
+ readConfig();
+
+ this.dimmerId = getId();
+
+ setSN();
+
+ scheduler.submit(() -> {
+ QbusCommunication controllerComm;
+
+ if (this.dimmerId != null) {
+ controllerComm = getCommunication("Dimmer", this.dimmerId);
+ } else {
+ thingOffline(ThingStatusDetail.CONFIGURATION_ERROR, "ID for DIMMER no set! " + this.dimmerId);
+ return;
+ }
+
+ if (controllerComm == null) {
+ thingOffline(ThingStatusDetail.CONFIGURATION_ERROR,
+ "ID for DIMMER not known in controller " + this.dimmerId);
+ return;
+ }
+
+ Map<Integer, QbusDimmer> dimmerCommLocal = controllerComm.getDimmer();
+
+ QbusDimmer outputLocal = dimmerCommLocal.get(this.dimmerId);
+
+ if (outputLocal == null) {
+ thingOffline(ThingStatusDetail.CONFIGURATION_ERROR,
+ "Bridge could not initialize DIMMER ID " + this.dimmerId);
+ return;
+ }
+
+ outputLocal.setThingHandler(this);
+ handleStateUpdate(outputLocal);
+
+ QbusBridgeHandler qBridgeHandler = getBridgeHandler("Dimmer", this.dimmerId);
+
+ if (qBridgeHandler != null) {
+ if (qBridgeHandler.getStatus() == ThingStatus.ONLINE) {
+ updateStatus(ThingStatus.ONLINE);
+ } else {
+ updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.BRIDGE_OFFLINE,
+ "Bridge offline for DIMMER ID " + this.dimmerId);
+ }
+ }
+ });
+ }
+
+ /**
+ * Handle the status update from the dimmer
+ */
+ @Override
+ public void handleCommand(ChannelUID channelUID, Command command) {
+ QbusCommunication qComm = getCommunication("Dimmer", this.dimmerId);
+
+ if (qComm == null) {
+ thingOffline(ThingStatusDetail.CONFIGURATION_ERROR,
+ "ID for DIMMER not known in controller " + this.dimmerId);
+ return;
+ } else {
+ Map<Integer, QbusDimmer> dimmerComm = qComm.getDimmer();
+
+ QbusDimmer qDimmer = dimmerComm.get(this.dimmerId);
+
+ if (qDimmer == null) {
+ thingOffline(ThingStatusDetail.CONFIGURATION_ERROR,
+ "ID for DIMMER not known in controller " + this.dimmerId);
+ return;
+ } else {
+ scheduler.submit(() -> {
+ if (!qComm.communicationActive()) {
+ restartCommunication(qComm, "Dimmer", this.dimmerId);
+ }
+
+ if (qComm.communicationActive()) {
+ if (command == REFRESH) {
+ handleStateUpdate(qDimmer);
+ return;
+ }
+
+ switch (channelUID.getId()) {
+ case CHANNEL_SWITCH:
+ try {
+ handleSwitchCommand(qDimmer, command);
+ } catch (IOException e) {
+ String message = e.getMessage();
+ logger.warn("Error on executing Switch for dimmer ID {}. IOException: {}",
+ this.dimmerId, message);
+ } catch (InterruptedException e) {
+ String message = e.getMessage();
+ logger.warn("Error on executing Switch for dimmer ID {}. Interruptedexception {}",
+ this.dimmerId, message);
+ }
+ break;
+
+ case CHANNEL_BRIGHTNESS:
+ try {
+ handleBrightnessCommand(qDimmer, command);
+ } catch (IOException e) {
+ String message = e.getMessage();
+ logger.warn("Error on executing Brightness for dimmer ID {}. IOException: {}",
+ this.dimmerId, message);
+ } catch (InterruptedException e) {
+ String message = e.getMessage();
+ logger.warn(
+ "Error on executing Brightness for dimmer ID {}. Interruptedexception {}",
+ this.dimmerId, message);
+ }
+ break;
+
+ default:
+ thingOffline(ThingStatusDetail.COMMUNICATION_ERROR,
+ "Unknown Channel " + channelUID.getId());
+ }
+ }
+ });
+ }
+ }
+ }
+
+ /**
+ * Executes the switch command
+ *
+ * @throws IOException
+ * @throws InterruptedException
+ */
+ private void handleSwitchCommand(QbusDimmer qDimmer, Command command) throws InterruptedException, IOException {
+ if (command instanceof OnOffType) {
+ String snr = getSN();
+ if (snr != null) {
+ if (command == OnOffType.OFF) {
+ qDimmer.execute(0, snr);
+ } else {
+ qDimmer.execute(1000, snr);
+ }
+ } else {
+ thingOffline(ThingStatusDetail.CONFIGURATION_ERROR,
+ "No serial number configured for DIMMER " + this.dimmerId);
+ }
+ }
+ }
+
+ /**
+ * Executes the brightness command
+ *
+ * @throws IOException
+ * @throws InterruptedException
+ */
+ private void handleBrightnessCommand(QbusDimmer qDimmer, Command command) throws InterruptedException, IOException {
+ String snr = getSN();
+
+ if (snr == null) {
+ thingOffline(ThingStatusDetail.CONFIGURATION_ERROR,
+ "No serial number configured for DIMMER " + this.dimmerId);
+ return;
+ } else {
+ if (command instanceof OnOffType) {
+ if (command == OnOffType.OFF) {
+ qDimmer.execute(0, snr);
+ } else {
+ qDimmer.execute(100, snr);
+ }
+ } else if (command instanceof IncreaseDecreaseType) {
+ int stepValue = ((Number) getConfig().get(CONFIG_STEP_VALUE)).intValue();
+ Integer currentValue = qDimmer.getState();
+ Integer newValue;
+ Integer sendvalue;
+ if (currentValue != null) {
+ if (command == IncreaseDecreaseType.INCREASE) {
+ newValue = currentValue + stepValue;
+ // round down to step multiple
+ newValue = newValue - newValue % stepValue;
+ sendvalue = newValue > 100 ? 100 : newValue;
+ qDimmer.execute(sendvalue, snr);
+ } else {
+ newValue = currentValue - stepValue;
+ // round up to step multiple
+ newValue = newValue + newValue % stepValue;
+ sendvalue = newValue < 0 ? 0 : newValue;
+ qDimmer.execute(sendvalue, snr);
+ }
+ }
+ } else if (command instanceof PercentType) {
+ int percentToInt = ((PercentType) command).intValue();
+ if (command == PercentType.ZERO) {
+ qDimmer.execute(0, snr);
+ } else {
+ qDimmer.execute(percentToInt, snr);
+ }
+ }
+ }
+ }
+
+ /**
+ * Method to update state of channel, called from Qbus Dimmer.
+ *
+ * @param qDimmer
+ */
+ public void handleStateUpdate(QbusDimmer qDimmer) {
+ Integer dimmerState = qDimmer.getState();
+ if (dimmerState != null) {
+ updateState(CHANNEL_BRIGHTNESS, new PercentType(dimmerState));
+ }
+ }
+
+ /**
+ * Returns the serial number of the controller
+ *
+ * @return the serial number
+ */
+ public @Nullable String getSN() {
+ return sn;
+ }
+
+ /**
+ * Sets the serial number of the controller
+ */
+ public void setSN() {
+ QbusBridgeHandler qBridgeHandler = getBridgeHandler("Dimmer", this.dimmerId);
+ if (qBridgeHandler == null) {
+ thingOffline(ThingStatusDetail.COMMUNICATION_ERROR,
+ "No communication with Qbus Bridge for DIMMER " + this.dimmerId);
+ return;
+ }
+ this.sn = qBridgeHandler.getSn();
+ }
+
+ /**
+ * Read the configuration
+ */
+ protected synchronized void readConfig() {
+ dimmerConfig = getConfig().as(QbusThingsConfig.class);
+ }
+
+ /**
+ * Returns the Id from the configuration
+ *
+ * @return outputId
+ */
+ public @Nullable Integer getId() {
+ QbusThingsConfig localConfig = dimmerConfig;
+ if (localConfig != null) {
+ return localConfig.dimmerId;
+ } else {
+ return null;
+ }
+ }
+}
--- /dev/null
+/**
+ * Copyright (c) 2010-2021 Contributors to the openHAB project
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ */
+package org.openhab.binding.qbus.internal.handler;
+
+import java.io.IOException;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.eclipse.jdt.annotation.Nullable;
+import org.openhab.binding.qbus.internal.QbusBridgeHandler;
+import org.openhab.binding.qbus.internal.protocol.QbusCommunication;
+import org.openhab.core.thing.Bridge;
+import org.openhab.core.thing.Thing;
+import org.openhab.core.thing.ThingStatus;
+import org.openhab.core.thing.ThingStatusDetail;
+import org.openhab.core.thing.binding.BaseThingHandler;
+
+/**
+ * The {@link QbusGlobalHandler} is used in other handlers, to share the functions.
+ *
+ * @author Koen Schockaert - Initial Contribution
+ */
+
+@NonNullByDefault
+public abstract class QbusGlobalHandler extends BaseThingHandler {
+
+ public QbusGlobalHandler(Thing thing) {
+ super(thing);
+ }
+
+ /**
+ * Get Bridge communication
+ *
+ * @param type
+ * @param globalId
+ * @return
+ */
+ public @Nullable QbusCommunication getCommunication(String type, @Nullable Integer globalId) {
+ QbusBridgeHandler qBridgeHandler = null;
+ if (globalId != null) {
+ qBridgeHandler = getBridgeHandler(type, globalId);
+ }
+
+ if (qBridgeHandler == null) {
+ updateStatus(ThingStatus.UNKNOWN, ThingStatusDetail.BRIDGE_UNINITIALIZED,
+ "No bridge handler initialized for " + type + " with id " + globalId + ".");
+ return null;
+ }
+ QbusCommunication qComm = qBridgeHandler.getCommunication();
+ return qComm;
+ }
+
+ /**
+ * Get the Bridge handler
+ *
+ * @param type
+ * @param globalId
+ * @return
+ */
+ public @Nullable QbusBridgeHandler getBridgeHandler(String type, @Nullable Integer globalId) {
+ Bridge qBridge = getBridge();
+ if (qBridge == null) {
+ updateStatus(ThingStatus.UNKNOWN, ThingStatusDetail.BRIDGE_UNINITIALIZED,
+ "No bridge initialized for " + type + " with ID " + globalId);
+ return null;
+ }
+ QbusBridgeHandler qBridgeHandler = (QbusBridgeHandler) qBridge.getHandler();
+ return qBridgeHandler;
+ }
+
+ /**
+ *
+ * @param qComm
+ * @param type
+ * @param globalId
+ */
+ public void restartCommunication(QbusCommunication qComm, String type, @Nullable Integer globalId) {
+ try {
+ qComm.restartCommunication();
+ } catch (InterruptedException e) {
+ String message = e.toString();
+ updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, message);
+ } catch (IOException e) {
+ String message = e.toString();
+ updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, message);
+ }
+
+ QbusBridgeHandler qBridgeHandler = getBridgeHandler(type, globalId);
+
+ if (qBridgeHandler != null && qComm.communicationActive()) {
+ qBridgeHandler.bridgeOnline();
+ } else {
+ thingOffline(ThingStatusDetail.COMMUNICATION_ERROR, "Communication socket error");
+ }
+ }
+
+ /**
+ * Put thing offline
+ *
+ * @param message
+ */
+ public void thingOffline(ThingStatusDetail detail, String message) {
+ updateStatus(ThingStatus.OFFLINE, detail, message);
+ }
+}
--- /dev/null
+/**
+ * Copyright (c) 2010-2021 Contributors to the openHAB project
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ */
+package org.openhab.binding.qbus.internal.handler;
+
+import static org.openhab.binding.qbus.internal.QbusBindingConstants.*;
+import static org.openhab.core.library.types.UpDownType.DOWN;
+import static org.openhab.core.types.RefreshType.REFRESH;
+
+import java.io.IOException;
+import java.util.Map;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.eclipse.jdt.annotation.Nullable;
+import org.openhab.binding.qbus.internal.QbusBridgeHandler;
+import org.openhab.binding.qbus.internal.protocol.QbusCommunication;
+import org.openhab.binding.qbus.internal.protocol.QbusRol;
+import org.openhab.core.library.types.IncreaseDecreaseType;
+import org.openhab.core.library.types.PercentType;
+import org.openhab.core.library.types.UpDownType;
+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.types.Command;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * The {@link QbusRolHandler} is responsible for handling commands, which are
+ * sent to one of the channels.
+ *
+ * @author Koen Schockaert - Initial Contribution
+ */
+
+@NonNullByDefault
+public class QbusRolHandler extends QbusGlobalHandler {
+
+ private final Logger logger = LoggerFactory.getLogger(QbusRolHandler.class);
+
+ protected @Nullable QbusThingsConfig rolConfig = new QbusThingsConfig();
+
+ private @Nullable Integer rolId;
+
+ private @Nullable String sn;
+
+ public QbusRolHandler(Thing thing) {
+ super(thing);
+ }
+
+ /**
+ * Main initialization
+ */
+ @Override
+ public void initialize() {
+ readConfig();
+
+ this.rolId = getId();
+
+ setSN();
+
+ scheduler.submit(() -> {
+ QbusCommunication controllerComm;
+
+ if (this.rolId != null) {
+ controllerComm = getCommunication("Screen/Store", this.rolId);
+ } else {
+ thingOffline(ThingStatusDetail.CONFIGURATION_ERROR, "ID for Screen/Store no set! " + this.rolId);
+ return;
+ }
+
+ if (controllerComm == null) {
+ thingOffline(ThingStatusDetail.CONFIGURATION_ERROR,
+ "ID for Screen/Store not known in controller " + this.rolId);
+ return;
+ }
+
+ Map<Integer, QbusRol> rolCommLocal = controllerComm.getRol();
+
+ QbusRol outputLocal = rolCommLocal.get(this.rolId);
+
+ if (outputLocal == null) {
+ thingOffline(ThingStatusDetail.CONFIGURATION_ERROR,
+ "Bridge could not initialize Screen/Store ID " + this.rolId);
+ return;
+ }
+
+ outputLocal.setThingHandler(this);
+ handleStateUpdate(outputLocal);
+
+ QbusBridgeHandler qBridgeHandler = getBridgeHandler("Screen/Store", this.rolId);
+
+ if (qBridgeHandler != null) {
+ if (qBridgeHandler.getStatus() == ThingStatus.ONLINE) {
+ updateStatus(ThingStatus.ONLINE);
+ } else {
+ updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.BRIDGE_OFFLINE,
+ "Bridge offline for SCREEN/STORE ID " + this.rolId);
+ }
+ }
+ });
+ }
+
+ /**
+ * Handle the status update from the thing
+ */
+ @Override
+ public void handleCommand(ChannelUID channelUID, Command command) {
+ QbusCommunication qComm = getCommunication("Screen/Store", this.rolId);
+
+ if (qComm == null) {
+ thingOffline(ThingStatusDetail.CONFIGURATION_ERROR,
+ "ID for ROLLERSHUTTER/SCREEN not known in controller " + this.rolId);
+ return;
+ } else {
+ Map<Integer, QbusRol> rolComm = qComm.getRol();
+
+ QbusRol qRol = rolComm.get(this.rolId);
+
+ if (qRol == null) {
+ thingOffline(ThingStatusDetail.CONFIGURATION_ERROR,
+ "ID for ROLLERSHUTTER/SCREEN not known in controller " + this.rolId);
+ return;
+ } else {
+ scheduler.submit(() -> {
+ if (!qComm.communicationActive()) {
+ restartCommunication(qComm, "Screen/Store", this.rolId);
+ }
+
+ if (qComm.communicationActive()) {
+ if (command == REFRESH) {
+ handleStateUpdate(qRol);
+ return;
+ }
+
+ switch (channelUID.getId()) {
+ case CHANNEL_ROLLERSHUTTER:
+ try {
+ handleScreenposCommand(qRol, command);
+ } catch (IOException e) {
+ String message = e.getMessage();
+ logger.warn("Error on executing Rollershutter for screen ID {}. IOException: {}",
+ this.rolId, message);
+ } catch (InterruptedException e) {
+ String message = e.toString();
+ logger.warn(
+ "Error on executing Rollershutter for screen ID {}. Interruptedexception {}",
+ this.rolId, message);
+ }
+ break;
+
+ case CHANNEL_SLATS:
+ try {
+ handleSlatsposCommand(qRol, command);
+ } catch (IOException e) {
+ String message = e.getMessage();
+ logger.warn("Error on executing Slats for screen ID {}. IOException: {}",
+ this.rolId, message);
+ } catch (InterruptedException e) {
+ String message = e.toString();
+ logger.warn("Error on executing Slats for screen ID {}. Interruptedexception {}",
+ this.rolId, message);
+ }
+ break;
+ }
+ }
+ });
+ }
+ }
+ }
+
+ /**
+ * Executes the command for screen up/down position
+ *
+ * @throws IOException
+ * @throws InterruptedException
+ */
+ private void handleScreenposCommand(QbusRol qRol, Command command) throws InterruptedException, IOException {
+ String snr = getSN();
+ if (snr != null) {
+ if (command instanceof UpDownType) {
+ UpDownType upDown = (UpDownType) command;
+ if (upDown == DOWN) {
+ qRol.execute(0, snr);
+ } else {
+ qRol.execute(100, snr);
+ }
+ } else if (command instanceof IncreaseDecreaseType) {
+ IncreaseDecreaseType inc = (IncreaseDecreaseType) command;
+ int stepValue = ((Number) getConfig().get(CONFIG_STEP_VALUE)).intValue();
+ Integer currentValue = qRol.getState();
+ int newValue;
+ int sendValue;
+ if (currentValue != null) {
+ if (inc == IncreaseDecreaseType.INCREASE) {
+ newValue = currentValue + stepValue;
+ // round down to step multiple
+ newValue = newValue - newValue % stepValue;
+ sendValue = newValue > 100 ? 100 : newValue;
+ qRol.execute(sendValue, snr);
+ } else {
+ newValue = currentValue - stepValue;
+ // round up to step multiple
+ newValue = newValue + newValue % stepValue;
+ sendValue = newValue > 100 ? 100 : newValue;
+ qRol.execute(sendValue, snr);
+ }
+ }
+ } else if (command instanceof PercentType) {
+ PercentType p = (PercentType) command;
+ int pp = p.intValue();
+ if (p == PercentType.ZERO) {
+ qRol.execute(0, snr);
+ } else {
+ qRol.execute(pp, snr);
+ }
+ }
+ }
+ }
+
+ /**
+ * Executes the command for screen slats position
+ *
+ * @throws IOException
+ * @throws InterruptedException
+ */
+ private void handleSlatsposCommand(QbusRol qRol, Command command) throws InterruptedException, IOException {
+ String snr = getSN();
+ if (snr != null) {
+ if (command instanceof UpDownType) {
+ if (command == DOWN) {
+ qRol.executeSlats(0, snr);
+ } else {
+ qRol.executeSlats(100, snr);
+ }
+ } else if (command instanceof IncreaseDecreaseType) {
+ int stepValue = ((Number) getConfig().get(CONFIG_STEP_VALUE)).intValue();
+ Integer currentValue = qRol.getState();
+ int newValue;
+ int sendValue;
+ if (currentValue != null) {
+ if (command == IncreaseDecreaseType.INCREASE) {
+ newValue = currentValue + stepValue;
+ // round down to step multiple
+ newValue = newValue - newValue % stepValue;
+ sendValue = newValue > 100 ? 100 : newValue;
+ qRol.executeSlats(sendValue, snr);
+ } else {
+ newValue = currentValue - stepValue;
+ // round up to step multiple
+ newValue = newValue + newValue % stepValue;
+ sendValue = newValue > 100 ? 100 : newValue;
+ qRol.executeSlats(sendValue, snr);
+ }
+ }
+ } else if (command instanceof PercentType) {
+ int percentToInt = ((PercentType) command).intValue();
+ if (command == PercentType.ZERO) {
+ qRol.executeSlats(0, snr);
+ } else {
+ qRol.executeSlats(percentToInt, snr);
+ }
+ }
+ }
+ }
+
+ /**
+ * Method to update state of channel, called from Qbus Screen/Store.
+ */
+ public void handleStateUpdate(QbusRol qRol) {
+ Integer rolState = qRol.getState();
+ Integer slatState = qRol.getStateSlats();
+
+ if (rolState != null) {
+ updateState(CHANNEL_ROLLERSHUTTER, new PercentType(rolState));
+ }
+ if (slatState != null) {
+ updateState(CHANNEL_SLATS, new PercentType(slatState));
+ }
+ }
+
+ /**
+ * Returns the serial number of the controller
+ *
+ * @return the serial nr
+ */
+ public @Nullable String getSN() {
+ return sn;
+ }
+
+ /**
+ * Sets the serial number of the controller
+ */
+ public void setSN() {
+ QbusBridgeHandler qBridgeHandler = getBridgeHandler("Screen/Store", this.rolId);
+ if (qBridgeHandler == null) {
+ thingOffline(ThingStatusDetail.COMMUNICATION_ERROR,
+ "No communication with Qbus Bridge for ROLLERSHUTTER/SCREEN " + this.rolId);
+ return;
+ }
+ sn = qBridgeHandler.getSn();
+ }
+
+ /**
+ * Read the configuration
+ */
+ protected synchronized void readConfig() {
+ rolConfig = getConfig().as(QbusThingsConfig.class);
+ }
+
+ /**
+ * Returns the Id from the configuration
+ *
+ * @return outputId
+ */
+ public @Nullable Integer getId() {
+ QbusThingsConfig localConfig = rolConfig;
+ if (localConfig != null) {
+ return localConfig.rolId;
+ } else {
+ return null;
+ }
+ }
+}
--- /dev/null
+/**
+ * Copyright (c) 2010-2021 Contributors to the openHAB project
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ */
+package org.openhab.binding.qbus.internal.handler;
+
+import static org.openhab.binding.qbus.internal.QbusBindingConstants.CHANNEL_SCENE;
+
+import java.io.IOException;
+import java.util.Map;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.eclipse.jdt.annotation.Nullable;
+import org.openhab.binding.qbus.internal.QbusBridgeHandler;
+import org.openhab.binding.qbus.internal.protocol.QbusCommunication;
+import org.openhab.binding.qbus.internal.protocol.QbusScene;
+import org.openhab.core.library.types.OnOffType;
+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.types.Command;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * The {@link QbusSceneHandler} is responsible for handling commands, which are
+ * sent to one of the channels.
+ *
+ * @author Koen Schockaert - Initial Contribution
+ */
+
+@NonNullByDefault
+public class QbusSceneHandler extends QbusGlobalHandler {
+
+ private final Logger logger = LoggerFactory.getLogger(QbusSceneHandler.class);
+
+ protected @Nullable QbusThingsConfig sceneConfig = new QbusThingsConfig();
+
+ private @Nullable Integer sceneId;
+
+ private @Nullable String sn;
+
+ public QbusSceneHandler(Thing thing) {
+ super(thing);
+ }
+
+ /**
+ * Main initialization
+ */
+ @Override
+ public void initialize() {
+ readConfig();
+
+ this.sceneId = getId();
+
+ setSN();
+
+ scheduler.submit(() -> {
+ QbusCommunication controllerComm;
+
+ if (this.sceneId != null) {
+ controllerComm = getCommunication("Scene", this.sceneId);
+ } else {
+ thingOffline(ThingStatusDetail.CONFIGURATION_ERROR, "ID for SCENE no set! " + this.sceneId);
+ return;
+ }
+
+ if (controllerComm == null) {
+ thingOffline(ThingStatusDetail.CONFIGURATION_ERROR,
+ "ID for SCENE not known in controller " + this.sceneId);
+ return;
+ }
+
+ Map<Integer, QbusScene> sceneCommLocal = controllerComm.getScene();
+
+ QbusScene outputLocal = sceneCommLocal.get(this.sceneId);
+
+ if (outputLocal == null) {
+ thingOffline(ThingStatusDetail.CONFIGURATION_ERROR,
+ "Bridge could not initialize SCENE ID " + this.sceneId);
+ return;
+ }
+
+ outputLocal.setThingHandler(this);
+
+ QbusBridgeHandler qBridgeHandler = getBridgeHandler("Scene", this.sceneId);
+
+ if ((qBridgeHandler != null) && (qBridgeHandler.getStatus() == ThingStatus.ONLINE)) {
+ updateStatus(ThingStatus.ONLINE);
+ } else {
+ thingOffline(ThingStatusDetail.COMMUNICATION_ERROR, "Bridge offline for SCENE ID " + this.sceneId);
+ }
+ });
+ }
+
+ /**
+ * Handle the status update from the thing
+ */
+ @Override
+ public void handleCommand(ChannelUID channelUID, Command command) {
+ QbusCommunication qComm = getCommunication("Scene", this.sceneId);
+
+ if (qComm == null) {
+ thingOffline(ThingStatusDetail.CONFIGURATION_ERROR, "ID for SCENE not known in controller " + this.sceneId);
+ return;
+ } else {
+ Map<Integer, QbusScene> sceneComm = qComm.getScene();
+ QbusScene qScene = sceneComm.get(this.sceneId);
+
+ if (qScene == null) {
+ thingOffline(ThingStatusDetail.CONFIGURATION_ERROR,
+ "ID for SCENE not known in controller " + this.sceneId);
+ return;
+ } else {
+ scheduler.submit(() -> {
+ if (!qComm.communicationActive()) {
+ restartCommunication(qComm, "Scene", this.sceneId);
+ }
+
+ if (qComm.communicationActive()) {
+ switch (channelUID.getId()) {
+ case CHANNEL_SCENE:
+ try {
+ handleSwitchCommand(qScene, channelUID, command);
+ } catch (IOException e) {
+ String message = e.getMessage();
+ logger.warn("Error on executing Scene for scene ID {}. IOException: {}",
+ this.sceneId, message);
+ } catch (InterruptedException e) {
+ String message = e.getMessage();
+ logger.warn("Error on executing Scene for scene ID {}. Interruptedexception {}",
+ this.sceneId, message);
+ }
+ break;
+
+ default:
+ thingOffline(ThingStatusDetail.COMMUNICATION_ERROR,
+ "Unknown Channel " + channelUID.getId());
+ }
+ }
+ });
+ }
+ }
+ }
+
+ /**
+ * Executes the scene command
+ *
+ * @throws IOException
+ * @throws InterruptedException
+ */
+ void handleSwitchCommand(QbusScene qScene, ChannelUID channelUID, Command command)
+ throws InterruptedException, IOException {
+ String snr = getSN();
+ if (snr != null) {
+ if (command instanceof OnOffType) {
+ if (command == OnOffType.OFF) {
+ qScene.execute(0, snr);
+ } else {
+ qScene.execute(100, snr);
+ }
+ }
+ }
+ }
+
+ /**
+ * Returns the serial number of the controller
+ *
+ * @return the serial nr
+ */
+ public @Nullable String getSN() {
+ return sn;
+ }
+
+ /**
+ * Sets the serial number of the controller
+ */
+ public void setSN() {
+ QbusBridgeHandler qBridgeHandler = getBridgeHandler("Scene", this.sceneId);
+ if (qBridgeHandler == null) {
+ thingOffline(ThingStatusDetail.COMMUNICATION_ERROR,
+ "No communication with Qbus Bridge for SCENE " + this.sceneId);
+ return;
+ }
+ sn = qBridgeHandler.getSn();
+ }
+
+ /**
+ * Read the configuration
+ */
+ protected synchronized void readConfig() {
+ sceneConfig = getConfig().as(QbusThingsConfig.class);
+ }
+
+ /**
+ * Returns the Id from the configuration
+ *
+ * @return outputId
+ */
+ public @Nullable Integer getId() {
+ QbusThingsConfig localConfig = sceneConfig;
+ if (localConfig != null) {
+ return localConfig.sceneId;
+ } else {
+ return null;
+ }
+ }
+}
--- /dev/null
+/**
+ * Copyright (c) 2010-2021 Contributors to the openHAB project
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ */
+package org.openhab.binding.qbus.internal.handler;
+
+import static org.openhab.binding.qbus.internal.QbusBindingConstants.*;
+import static org.openhab.core.library.unit.SIUnits.CELSIUS;
+import static org.openhab.core.types.RefreshType.REFRESH;
+
+import java.io.IOException;
+import java.util.Map;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.eclipse.jdt.annotation.Nullable;
+import org.openhab.binding.qbus.internal.QbusBridgeHandler;
+import org.openhab.binding.qbus.internal.protocol.QbusCommunication;
+import org.openhab.binding.qbus.internal.protocol.QbusThermostat;
+import org.openhab.core.library.types.DecimalType;
+import org.openhab.core.library.types.QuantityType;
+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.types.Command;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * The {@link QbusThermostatHandler} is responsible for handling the Thermostat outputs of Qbus
+ *
+ * @author Koen Schockaert - Initial Contribution
+ */
+
+@NonNullByDefault
+public class QbusThermostatHandler extends QbusGlobalHandler {
+
+ private final Logger logger = LoggerFactory.getLogger(QbusThermostatHandler.class);
+
+ protected @Nullable QbusThingsConfig thermostatConfig = new QbusThingsConfig();
+
+ private @Nullable Integer thermostatId;
+
+ private @Nullable String sn;
+
+ public QbusThermostatHandler(Thing thing) {
+ super(thing);
+ }
+
+ /**
+ * Main initialization
+ */
+ @Override
+ public void initialize() {
+ readConfig();
+
+ this.thermostatId = getId();
+
+ setSN();
+
+ scheduler.submit(() -> {
+ QbusCommunication controllerComm;
+
+ if (this.thermostatId != null) {
+ controllerComm = getCommunication("Thermostat", this.thermostatId);
+ } else {
+ thingOffline(ThingStatusDetail.CONFIGURATION_ERROR, "ID for THERMOSTAT no set! " + this.thermostatId);
+ return;
+ }
+
+ if (controllerComm == null) {
+ thingOffline(ThingStatusDetail.CONFIGURATION_ERROR,
+ "ID for THERMOSTAT not known in controller " + this.thermostatId);
+ return;
+ }
+
+ Map<Integer, QbusThermostat> thermostatlCommLocal = controllerComm.getThermostat();
+
+ QbusThermostat outputLocal = thermostatlCommLocal.get(this.thermostatId);
+
+ if (outputLocal == null) {
+ thingOffline(ThingStatusDetail.CONFIGURATION_ERROR,
+ "Bridge could not initialize THERMOSTAT ID " + this.thermostatId);
+ return;
+ }
+
+ outputLocal.setThingHandler(this);
+ handleStateUpdate(outputLocal);
+
+ QbusBridgeHandler qBridgeHandler = getBridgeHandler("Thermostat", this.thermostatId);
+
+ if (qBridgeHandler != null) {
+ if (qBridgeHandler.getStatus() == ThingStatus.ONLINE) {
+ updateStatus(ThingStatus.ONLINE);
+ } else {
+ updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.BRIDGE_OFFLINE,
+ "Bridge offline for THERMOSTAT ID " + this.thermostatId);
+ }
+ }
+ });
+ }
+
+ /**
+ * Handle the status update from the thermostat
+ */
+ @Override
+ public void handleCommand(ChannelUID channelUID, Command command) {
+ QbusCommunication qComm = getCommunication("Thermostat", this.thermostatId);
+
+ if (qComm == null) {
+ thingOffline(ThingStatusDetail.CONFIGURATION_ERROR,
+ "ID for THERMOSTAT not known in controller " + this.thermostatId);
+ return;
+ } else {
+ Map<Integer, QbusThermostat> thermostatComm = qComm.getThermostat();
+
+ QbusThermostat qThermostat = thermostatComm.get(this.thermostatId);
+
+ if (qThermostat == null) {
+ thingOffline(ThingStatusDetail.CONFIGURATION_ERROR,
+ "ID for THERMOSTAT not known in controller " + this.thermostatId);
+ return;
+ } else {
+ scheduler.submit(() -> {
+ if (!qComm.communicationActive()) {
+ restartCommunication(qComm, "Thermostat", this.thermostatId);
+ }
+
+ if (qComm.communicationActive()) {
+ if (command == REFRESH) {
+ handleStateUpdate(qThermostat);
+ return;
+ }
+
+ switch (channelUID.getId()) {
+ case CHANNEL_MODE:
+ try {
+ handleModeCommand(qThermostat, command);
+ } catch (IOException e) {
+ String message = e.getMessage();
+ logger.warn("Error on executing Mode for thermostat ID {}. IOException: {} ",
+ this.thermostatId, message);
+ } catch (InterruptedException e) {
+ String message = e.getMessage();
+ logger.warn(
+ "Error on executing Mode for thermostat ID {}. Interruptedexception {} ",
+ this.thermostatId, message);
+ }
+ break;
+
+ case CHANNEL_SETPOINT:
+ try {
+ handleSetpointCommand(qThermostat, command);
+ } catch (IOException e) {
+ String message = e.getMessage();
+ logger.warn("Error on executing Setpoint for thermostat ID {}. IOException: {} ",
+ this.thermostatId, message);
+ } catch (InterruptedException e) {
+ String message = e.getMessage();
+ logger.warn(
+ "Error on executing Setpoint for thermostat ID {}. Interruptedexception {} ",
+ this.thermostatId, message);
+ }
+ break;
+
+ default:
+ thingOffline(ThingStatusDetail.COMMUNICATION_ERROR,
+ "Unknown Channel " + channelUID.getId());
+ }
+ }
+ });
+ }
+ }
+ }
+
+ /**
+ * Executes the Mode command
+ *
+ * @param qThermostat
+ * @param command
+ * @param snr
+ * @throws InterruptedException
+ * @throws IOException
+ */
+ private void handleModeCommand(QbusThermostat qThermostat, Command command)
+ throws InterruptedException, IOException {
+ String snr = getSN();
+ if (snr != null) {
+ if (command instanceof DecimalType) {
+ int mode = ((DecimalType) command).intValue();
+ qThermostat.executeMode(mode, snr);
+ }
+ }
+ }
+
+ /**
+ * Executes the Setpoint command
+ *
+ * @param qThermostat
+ * @param command
+ * @param snr
+ * @throws InterruptedException
+ * @throws IOException
+ */
+ private void handleSetpointCommand(QbusThermostat qThermostat, Command command)
+ throws InterruptedException, IOException {
+ String snr = getSN();
+ if (snr != null) {
+ if (command instanceof QuantityType<?>) {
+ QuantityType<?> s = (QuantityType<?>) command;
+ double sp = s.doubleValue();
+ QuantityType<?> spCelcius = s.toUnit(CELSIUS);
+
+ if (spCelcius != null) {
+ qThermostat.executeSetpoint(sp, snr);
+ } else {
+ logger.warn("Could not set setpoint for thermostat (conversion failed) {}", this.thermostatId);
+ }
+ }
+ }
+ }
+
+ /**
+ * Method to update state of all channels, called from Qbus thermostat.
+ *
+ * @param qThermostat
+ */
+ public void handleStateUpdate(QbusThermostat qThermostat) {
+ Double measured = qThermostat.getMeasured();
+ if (measured != null) {
+ updateState(CHANNEL_MEASURED, new QuantityType<>(measured, CELSIUS));
+ }
+
+ Double setpoint = qThermostat.getSetpoint();
+ if (setpoint != null) {
+ updateState(CHANNEL_SETPOINT, new QuantityType<>(setpoint, CELSIUS));
+ }
+
+ Integer mode = qThermostat.getMode();
+ if (mode != null) {
+ updateState(CHANNEL_MODE, new DecimalType(mode));
+ }
+ }
+
+ /**
+ * Returns the serial number of the controller
+ *
+ * @return the serial nr
+ */
+ public @Nullable String getSN() {
+ return sn;
+ }
+
+ /**
+ * Sets the serial number of the controller
+ */
+ public void setSN() {
+ QbusBridgeHandler qBridgeHandler = getBridgeHandler("Thermostsat", this.thermostatId);
+ if (qBridgeHandler == null) {
+ thingOffline(ThingStatusDetail.COMMUNICATION_ERROR,
+ "No communication with Qbus Bridge for THERMOSTAT " + this.thermostatId);
+ return;
+ }
+ sn = qBridgeHandler.getSn();
+ }
+
+ /**
+ * Read the configuration
+ */
+ protected synchronized void readConfig() {
+ thermostatConfig = getConfig().as(QbusThingsConfig.class);
+ }
+
+ /**
+ * Returns the Id from the configuration
+ *
+ * @return outputId
+ */
+ public @Nullable Integer getId() {
+ QbusThingsConfig localConfig = thermostatConfig;
+ if (localConfig != null) {
+ return localConfig.thermostatId;
+ } else {
+ return null;
+ }
+ }
+}
--- /dev/null
+/**
+ * Copyright (c) 2010-2021 Contributors to the openHAB project
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ */
+package org.openhab.binding.qbus.internal.handler;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.eclipse.jdt.annotation.Nullable;
+
+/**
+ * The {@link QbusThingsConfig} is responible for handling configurations for all things
+ *
+ * @author Koen Schockaert - Initial Contribution
+ */
+
+@NonNullByDefault
+public class QbusThingsConfig {
+ public @Nullable Integer bistabielId;
+ public @Nullable Integer dimmerId;
+ public @Nullable Integer co2Id;
+ public @Nullable Integer rolId;
+ public @Nullable Integer sceneId;
+ public @Nullable Integer thermostatId;
+}
--- /dev/null
+/**
+ * Copyright (c) 2010-2021 Contributors to the openHAB project
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ */
+package org.openhab.binding.qbus.internal.protocol;
+
+import java.io.IOException;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.eclipse.jdt.annotation.Nullable;
+import org.openhab.binding.qbus.internal.handler.QbusBistabielHandler;
+
+/**
+ * The {@link QbusBistabiel} class represents the Qbus BISTABIEL output.
+ *
+ * @author Koen Schockaert - Initial Contribution
+ */
+
+@NonNullByDefault
+public final class QbusBistabiel {
+
+ private @Nullable QbusCommunication qComm;
+
+ private Integer id;
+
+ private @Nullable Integer state;
+
+ private @Nullable QbusBistabielHandler thingHandler;
+
+ QbusBistabiel(Integer id) {
+ this.id = id;
+ }
+
+ /**
+ * This method should be called if the ThingHandler for the thing corresponding to this bistabiel is initialized.
+ * It keeps a record of the thing handler in this object so the thing can be updated when
+ * the bistable output receives an update from the Qbus client.
+ *
+ * @param handler
+ */
+ public void setThingHandler(QbusBistabielHandler handler) {
+ this.thingHandler = handler;
+ }
+
+ /**
+ * This method sets a pointer to the qComm BISTABIEL of class {@link QbusCommuncation}.
+ * This is then used to be able to call back the sendCommand method in this class to send a command to the
+ * Qbus client.
+ *
+ * @param qComm
+ */
+ public void setQComm(QbusCommunication qComm) {
+ this.qComm = qComm;
+ }
+
+ /**
+ * Update the value of the Bistabiel.
+ *
+ * @param state
+ */
+ void updateState(@Nullable Integer state) {
+ this.state = state;
+ QbusBistabielHandler handler = this.thingHandler;
+ if (handler != null) {
+ handler.handleStateUpdate(this);
+ }
+ }
+
+ /**
+ * Get the value of the Bistabiel.
+ *
+ * @return
+ */
+ public @Nullable Integer getState() {
+ return this.state;
+ }
+
+ /**
+ * Sends Bistabiel state to Qbus.
+ *
+ * @param value
+ * @param sn
+ * @throws InterruptedException
+ * @throws IOException
+ */
+ public void execute(int value, String sn) throws InterruptedException, IOException {
+ QbusMessageCmd qCmd = new QbusMessageCmd(sn, "executeBistabiel").withId(this.id).withState(value);
+ QbusCommunication comm = this.qComm;
+ if (comm != null) {
+ comm.sendMessage(qCmd);
+ }
+ }
+}
--- /dev/null
+/**
+ * Copyright (c) 2010-2021 Contributors to the openHAB project
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ */
+
+package org.openhab.binding.qbus.internal.protocol;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.eclipse.jdt.annotation.Nullable;
+import org.openhab.binding.qbus.internal.handler.QbusCO2Handler;
+
+/**
+ * The {@link QbusCO2} class represents the action Qbus CO2 output.
+ *
+ * @author Koen Schockaert - Initial Contribution
+ */
+
+@NonNullByDefault
+public final class QbusCO2 {
+
+ private @Nullable Integer state;
+
+ private @Nullable QbusCO2Handler thingHandler;
+
+ /**
+ * This method should be called if the ThingHandler for the thing corresponding to this CO2 is initialized.
+ * It keeps a record of the thing handler in this object so the thing can be updated when
+ * the CO2 output receives an update from the Qbus IP-interface.
+ *
+ * @param handler
+ */
+ public void setThingHandler(QbusCO2Handler handler) {
+ this.thingHandler = handler;
+ }
+
+ /**
+ * Get state of CO2.
+ *
+ * @return CO2 state
+ */
+ public @Nullable Integer getState() {
+ return this.state;
+ }
+
+ /**
+ * Update the value of the CO2.
+ *
+ * @param CO2 value
+ */
+ void updateState(@Nullable Integer state) {
+ this.state = state;
+ QbusCO2Handler handler = this.thingHandler;
+ if (handler != null) {
+ handler.handleStateUpdate(this);
+ }
+ }
+}
--- /dev/null
+/**
+ * Copyright (c) 2010-2021 Contributors to the openHAB project
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ */
+package org.openhab.binding.qbus.internal.protocol;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.io.PrintWriter;
+import java.net.InetAddress;
+import java.net.Socket;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.TimeUnit;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.eclipse.jdt.annotation.Nullable;
+import org.openhab.binding.qbus.internal.QbusBridgeHandler;
+import org.openhab.core.common.NamedThreadFactory;
+import org.openhab.core.thing.ChannelUID;
+import org.openhab.core.thing.Thing;
+import org.openhab.core.thing.ThingStatusDetail;
+import org.openhab.core.thing.binding.BaseThingHandler;
+import org.openhab.core.types.Command;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.google.gson.Gson;
+import com.google.gson.GsonBuilder;
+import com.google.gson.JsonParseException;
+
+/**
+ * The {@link QbusCommunication} class is able to do the following tasks with Qbus
+ * CTD controllers:
+ * <ul>
+ * <li>Start and stop TCP socket connection with Qbus Server.
+ * <li>Read all the outputs and their status from the Qbus Controller.
+ * <li>Execute Qbus commands.
+ * <li>Listen to events from Qbus.
+ * </ul>
+ *
+ * A class instance is instantiated from the {@link QbusBridgeHandler} class initialization.
+ *
+ * @author Koen Schockaert - Initial Contribution
+ */
+
+@NonNullByDefault
+public final class QbusCommunication extends BaseThingHandler {
+
+ private final Logger logger = LoggerFactory.getLogger(QbusCommunication.class);
+
+ private @Nullable Socket qSocket;
+ private @Nullable PrintWriter qOut;
+ private @Nullable BufferedReader qIn;
+
+ private boolean listenerStopped;
+ private boolean qbusListenerRunning;
+
+ private Gson gsonOut = new Gson();
+ private Gson gsonIn;
+
+ private @Nullable String ctd;
+ private boolean ctdConnected;
+
+ private List<Map<String, String>> outputs = new ArrayList<>();
+ private final Map<Integer, QbusBistabiel> bistabiel = new HashMap<>();
+ private final Map<Integer, QbusScene> scene = new HashMap<>();
+ private final Map<Integer, QbusDimmer> dimmer = new HashMap<>();
+ private final Map<Integer, QbusRol> rol = new HashMap<>();
+ private final Map<Integer, QbusThermostat> thermostat = new HashMap<>();
+ private final Map<Integer, QbusCO2> co2 = new HashMap<>();
+
+ private final ExecutorService threadExecutor = Executors
+ .newSingleThreadExecutor(new NamedThreadFactory(getThing().getUID().getAsString(), true));
+
+ private @Nullable QbusBridgeHandler bridgeCallBack;
+
+ public QbusCommunication(Thing thing) {
+ super(thing);
+ GsonBuilder gsonBuilder = new GsonBuilder();
+ gsonBuilder.registerTypeAdapter(QbusMessageBase.class, new QbusMessageDeserializer());
+ gsonIn = gsonBuilder.create();
+ }
+
+ /**
+ * Starts main communication thread.
+ * <ul>
+ * <li>Connect to Qbus server
+ * <li>Requests outputs
+ * <li>Start listener
+ * </ul>
+ *
+ * @throws IOException
+ * @throws InterruptedException
+ */
+ public synchronized void startCommunication() throws IOException, InterruptedException {
+ QbusBridgeHandler handler = bridgeCallBack;
+ ctdConnected = false;
+
+ if (qbusListenerRunning) {
+ throw new IOException("Previous listening thread is still active.");
+ }
+
+ if (handler == null) {
+ throw new IOException("No Bridge handler initialised.");
+ }
+
+ InetAddress addr = InetAddress.getByName(handler.getAddress());
+ Integer port = handler.getPort();
+
+ if (port != null) {
+ Socket socket = new Socket(addr, port);
+ qSocket = socket;
+ qOut = new PrintWriter(socket.getOutputStream(), true);
+ qIn = new BufferedReader(new InputStreamReader(socket.getInputStream()));
+ } else {
+ return;
+ }
+
+ setSN();
+ getSN();
+
+ // Connect to Qbus server
+ connect();
+
+ // Then start thread to listen to incoming updates from Qbus.
+ threadExecutor.execute(() -> {
+ try {
+ qbusListener();
+ } catch (IOException e) {
+ String msg = e.getMessage();
+ logger.warn("Could not start listening thread, IOException: {}", msg);
+ } catch (InterruptedException e) {
+ String msg = e.getMessage();
+ logger.warn("Could not start listening thread, InterruptedException: {}", msg);
+ }
+ });
+
+ if (!ctdConnected) {
+ handler.bridgePending("Waiting for CTD to come online...");
+ }
+ }
+
+ /**
+ * Cleanup socket when the communication with Qbus Server is closed.
+ *
+ * @throws IOException
+ *
+ */
+ public synchronized void stopCommunication() throws IOException {
+ listenerStopped = true;
+
+ Socket socket = qSocket;
+
+ if (socket != null) {
+ try {
+ socket.close();
+ } catch (IOException ignore) {
+ // ignore IO Error when trying to close the socket if the intention is to close it anyway
+ }
+ }
+
+ BufferedReader reader = this.qIn;
+ if (reader != null) {
+ reader.close();
+ }
+
+ PrintWriter writer = this.qOut;
+ if (writer != null) {
+ writer.close();
+ }
+
+ qSocket = null;
+ qbusListenerRunning = false;
+ ctdConnected = false;
+
+ logger.trace("Communication stopped from thread {}", Thread.currentThread().getId());
+ }
+
+ /**
+ * Close and restart communication with Qbus Server.
+ *
+ * @throws InterruptedException
+ * @throws IOException
+ */
+ public synchronized void restartCommunication() throws InterruptedException, IOException {
+ stopCommunication();
+
+ startCommunication();
+ }
+
+ /**
+ * Thread that handles incoming messages from Qbus client.
+ * <p>
+ * The thread listens to the TCP socket opened at instantiation of the {@link QbusCommunication} class
+ * and interprets all incomming json messages. It triggers state updates for active channels linked to the
+ * Qbus outputs. It is started after initialization of the communication.
+ *
+ * @return
+ * @throws IOException
+ * @throws InterruptedException
+ *
+ *
+ */
+ private void qbusListener() throws IOException, InterruptedException {
+ String qMessage;
+
+ listenerStopped = false;
+ qbusListenerRunning = true;
+
+ BufferedReader reader = this.qIn;
+
+ if (reader == null) {
+ throw new IOException("Bufferreader for incoming messages not initialized.");
+ }
+
+ try {
+ while (!Thread.currentThread().isInterrupted() && ((qMessage = reader.readLine()) != null)) {
+ readMessage(qMessage);
+
+ }
+ } catch (IOException e) {
+ if (!listenerStopped) {
+ qbusListenerRunning = false;
+ // the IO has stopped working, so we need to close cleanly and try to restart
+ restartCommunication();
+ return;
+ }
+ } finally {
+ qbusListenerRunning = false;
+ }
+
+ if (!listenerStopped) {
+ qbusListenerRunning = false;
+
+ QbusBridgeHandler handler = bridgeCallBack;
+
+ if (handler != null) {
+ ctdConnected = false;
+ handler.bridgeOffline(ThingStatusDetail.COMMUNICATION_ERROR, "No communication with Qbus server");
+ }
+ }
+
+ qbusListenerRunning = false;
+ logger.trace("Event listener thread stopped on thread {}", Thread.currentThread().getId());
+ };
+
+ /**
+ * Called by other methods to send json data to Qbus.
+ *
+ * @param qMessage
+ * @throws InterruptedException
+ * @throws IOException
+ */
+ synchronized void sendMessage(Object qMessage) throws InterruptedException, IOException {
+ PrintWriter writer = qOut;
+ String json = gsonOut.toJson(qMessage);
+
+ if (writer != null) {
+ writer.println(json);
+ // Delay after sending data to improve scene execution
+ TimeUnit.MILLISECONDS.sleep(250);
+ }
+
+ if ((writer == null) || (writer.checkError())) {
+ logger.warn("Error sending message, trying to restart communication");
+
+ restartCommunication();
+
+ // retry sending after restart
+ writer = qOut;
+ if (writer != null) {
+ writer.println(json);
+ }
+ if ((writer == null) || (writer.checkError())) {
+ logger.warn("Error resending message");
+
+ }
+ }
+ }
+
+ /**
+ * Method that interprets all feedback from Qbus Server application and calls appropriate handling methods.
+ * <ul>
+ * <li>Get request & update states for Bistabiel/Timers/Intervals/Mono outputs
+ * <li>Get request & update states for the Scenes
+ * <li>Get request & update states for Dimmers 1T and 2T
+ * <li>Get request & update states for Shutters
+ * <li>Get request & update states for Thermostats
+ * <li>Get request & update states for CO2
+ * </ul>
+ *
+ * @param qMessage message read from Qbus.
+ * @throws InterruptedException
+ * @throws IOException
+ *
+ */
+ private void readMessage(String qMessage) {
+ String sn = null;
+ String cmd = "";
+ String ctd = null;
+ Integer id = null;
+ Integer state = null;
+ Integer mode = null;
+ Double setpoint = null;
+ Double measured = null;
+ Integer slats = null;
+
+ QbusMessageBase qMessageGson;
+ try {
+ qMessageGson = gsonIn.fromJson(qMessage, QbusMessageBase.class);
+
+ if (qMessageGson != null) {
+ ctd = qMessageGson.getSn();
+ cmd = qMessageGson.getCmd();
+ id = qMessageGson.getId();
+ state = qMessageGson.getState();
+ mode = qMessageGson.getMode();
+ setpoint = qMessageGson.getSetPoint();
+ measured = qMessageGson.getMeasured();
+ slats = qMessageGson.getSlatState();
+ }
+ } catch (JsonParseException e) {
+ String msg = e.getMessage();
+ logger.trace("Not acted on unsupported json {} : {}", qMessage, msg);
+ return;
+ }
+
+ QbusBridgeHandler handler = bridgeCallBack;
+
+ if (handler != null) {
+ sn = handler.getSn();
+ }
+
+ if (sn != null && ctd != null) {
+ try {
+ if (sn.equals(ctd) && qMessageGson != null) { // Check if commands are for this Bridge
+ // Handle all outputs from Qbus
+ if ("returnOutputs".equals(cmd)) {
+ outputs = ((QbusMessageListMap) qMessageGson).getOutputs();
+
+ for (Map<String, String> ctdOutputs : outputs) {
+
+ String ctdType = ctdOutputs.get("type");
+ String ctdIdStr = ctdOutputs.get("id");
+ Integer ctdId = null;
+
+ if (ctdIdStr != null) {
+ ctdId = Integer.parseInt(ctdIdStr);
+ } else {
+ return;
+ }
+
+ if (ctdType != null) {
+ String ctdState = ctdOutputs.get("state");
+ String ctdMmode = ctdOutputs.get("regime");
+ String ctdSetpoint = ctdOutputs.get("setpoint");
+ String ctdMeasured = ctdOutputs.get("measured");
+ String ctdSlats = ctdOutputs.get("slats");
+
+ Integer ctdStateI = null;
+ if (ctdState != null) {
+ ctdStateI = Integer.parseInt(ctdState);
+ }
+
+ Integer ctdSlatsI = null;
+ if (ctdSlats != null) {
+ ctdSlatsI = Integer.parseInt(ctdSlats);
+ }
+
+ Integer ctdMmodeI = null;
+ if (ctdMmode != null) {
+ ctdMmodeI = Integer.parseInt(ctdMmode);
+ }
+
+ Double ctdSetpointD = null;
+ if (ctdSetpoint != null) {
+ ctdSetpointD = Double.parseDouble(ctdSetpoint);
+ }
+
+ Double ctdMeasuredD = null;
+ if (ctdMeasured != null) {
+ ctdMeasuredD = Double.parseDouble(ctdMeasured);
+ }
+
+ if (ctdState != null) {
+ if (ctdType.equals("bistabiel")) {
+ QbusBistabiel output = new QbusBistabiel(ctdId);
+ if (!bistabiel.containsKey(ctdId)) {
+ output.setQComm(this);
+ output.updateState(ctdStateI);
+ bistabiel.put(ctdId, output);
+ } else {
+ output.updateState(ctdStateI);
+ }
+ } else if (ctdType.equals("dimmer")) {
+ QbusDimmer output = new QbusDimmer(ctdId);
+ if (!dimmer.containsKey(ctdId)) {
+ output.setQComm(this);
+ output.updateState(ctdStateI);
+ dimmer.put(ctdId, output);
+ } else {
+ output.updateState(ctdStateI);
+ }
+ } else if (ctdType.equals("CO2")) {
+ QbusCO2 output = new QbusCO2();
+ if (!co2.containsKey(ctdId)) {
+ output.updateState(ctdStateI);
+ co2.put(ctdId, output);
+ } else {
+ output.updateState(ctdStateI);
+ }
+ } else if (ctdType.equals("scene")) {
+ QbusScene output = new QbusScene(ctdId);
+ if (!scene.containsKey(ctdId)) {
+ output.setQComm(this);
+ scene.put(ctdId, output);
+ }
+ } else if (ctdType.equals("rol")) {
+ QbusRol output = new QbusRol(ctdId);
+ if (!rol.containsKey(ctdId)) {
+ output.setQComm(this);
+ output.updateState(ctdStateI);
+ if (ctdSlats != null) {
+ output.updateSlats(ctdSlatsI);
+ }
+ rol.put(ctdId, output);
+ } else {
+ output.updateState(ctdStateI);
+ if (ctdSlats != null) {
+ output.updateSlats(ctdSlatsI);
+ }
+ }
+ }
+ } else if (ctdMeasuredD != null && ctdSetpointD != null && ctdMmodeI != null) {
+ if (ctdType.equals("thermostat")) {
+ QbusThermostat output = new QbusThermostat(ctdId);
+ if (!thermostat.containsKey(ctdId)) {
+ output.setQComm(this);
+ output.updateState(ctdMeasuredD, ctdSetpointD, ctdMmodeI);
+ thermostat.put(ctdId, output);
+ } else {
+ output.updateState(ctdMeasuredD, ctdSetpointD, ctdMmodeI);
+ }
+ }
+ }
+ }
+ }
+ // Handle update commands from Qbus
+ } else if ("updateBistabiel".equals(cmd)) {
+ if (id != null && state != null) {
+ updateBistabiel(id, state);
+ }
+ } else if ("updateDimmer".equals(cmd)) {
+ if (id != null && state != null) {
+ updateDimmer(id, state);
+ }
+ } else if ("updateDimmer".equals(cmd)) {
+ if (id != null && state != null) {
+ updateDimmer(id, state);
+ }
+ } else if ("updateCo2".equals(cmd)) {
+ if (id != null && state != null) {
+ updateCO2(id, state);
+ }
+ } else if ("updateThermostat".equals(cmd)) {
+ if (id != null && measured != null && setpoint != null && mode != null) {
+ updateThermostat(id, mode, setpoint, measured);
+ }
+ } else if ("updateRol02p".equals(cmd)) {
+ if (id != null && state != null) {
+ updateRol(id, state);
+ }
+ } else if ("updateRol02pSlat".equals(cmd)) {
+ if (id != null && state != null && slats != null) {
+ updateRolSlats(id, state, slats);
+ }
+ // Incomming commands from Qbus server to verify the client connection
+ } else if ("noconnection".equals(cmd)) {
+ eventDisconnect();
+ } else if ("connected".equals(cmd)) {
+ // threadExecutor.execute(() -> {
+ try {
+ requestOutputs();
+ } catch (InterruptedException e) {
+ String msg = e.getMessage();
+ logger.warn("Could not request outputs. InterruptedException: {}", msg);
+ } catch (IOException e) {
+ String msg = e.getMessage();
+ logger.warn("Could not request outputs. IOException: {}", msg);
+ }
+ }
+ }
+ } catch (JsonParseException e) {
+ String msg = e.getMessage();
+ logger.warn("Not acted on unsupported json {}, {}", qMessage, msg);
+ }
+ }
+ }
+
+ /**
+ * Initialize the communication object
+ */
+ @Override
+ public void initialize() {
+ }
+
+ /**
+ * Initial connection to Qbus Server to open a communication channel
+ *
+ * @throws InterruptedException
+ * @throws IOException
+ */
+ private void connect() throws InterruptedException, IOException {
+ String snr = getSN();
+
+ if (snr != null) {
+ QbusMessageCmd qCmd = new QbusMessageCmd(snr, "openHAB");
+
+ sendMessage(qCmd);
+
+ BufferedReader reader = qIn;
+
+ if (reader == null) {
+ throw new IOException("Cannot read from socket, reader not connected.");
+ }
+ readMessage(reader.readLine());
+
+ } else {
+ QbusBridgeHandler handler = bridgeCallBack;
+ if (handler != null) {
+ handler.bridgeOffline(ThingStatusDetail.CONFIGURATION_ERROR, "No serial nr defined");
+ }
+ }
+ }
+
+ /**
+ * Send a request for all available outputs and initializes them via readMessage
+ *
+ * @throws InterruptedException
+ * @throws IOException
+ */
+ private void requestOutputs() throws InterruptedException, IOException {
+ String snr = getSN();
+ QbusBridgeHandler handler = bridgeCallBack;
+
+ if (snr != null) {
+ QbusMessageCmd qCmd = new QbusMessageCmd(snr, "all");
+ sendMessage(qCmd);
+
+ BufferedReader reader = qIn;
+ if (reader == null) {
+ throw new IOException("Cannot read from socket, reader not connected.");
+ }
+ readMessage(reader.readLine());
+ ctdConnected = true;
+
+ if (handler != null) {
+ handler.bridgeOnline();
+ }
+
+ } else {
+ if (handler != null) {
+ handler.bridgeOffline(ThingStatusDetail.CONFIGURATION_ERROR, "No serial nr defined");
+ }
+ }
+ }
+
+ /**
+ * Event on incoming Bistabiel/Timer/Mono/Interval updates
+ *
+ * @param id
+ * @param state
+ */
+ private void updateBistabiel(Integer id, Integer state) {
+ QbusBistabiel qBistabiel = this.bistabiel.get(id);
+
+ if (qBistabiel != null) {
+ qBistabiel.updateState(state);
+ } else {
+ logger.trace("Bistabiel in controller not known {}", id);
+ }
+ }
+
+ /**
+ * Event on incoming Dimmer updates
+ *
+ * @param id
+ * @param state
+ */
+ private void updateDimmer(Integer id, Integer state) {
+ QbusDimmer qDimmer = this.dimmer.get(id);
+
+ if (qDimmer != null) {
+ qDimmer.updateState(state);
+ } else {
+ logger.trace("Dimmer in controller not known {}", id);
+ }
+ }
+
+ /**
+ * Event on incoming thermostat updates
+ *
+ * @param id
+ * @param mode
+ * @param sp
+ * @param ct
+ */
+ private void updateThermostat(Integer id, int mode, double sp, double ct) {
+ QbusThermostat qThermostat = this.thermostat.get(id);
+
+ if (qThermostat != null) {
+ qThermostat.updateState(ct, sp, mode);
+ } else {
+ logger.trace("Thermostat in controller not known {}", id);
+ }
+ }
+
+ /**
+ * Event on incoming CO2 updates
+ *
+ * @param id
+ * @param state
+ */
+ private void updateCO2(Integer id, Integer state) {
+ QbusCO2 qCO2 = this.co2.get(id);
+
+ if (qCO2 != null) {
+ qCO2.updateState(state);
+ } else {
+ logger.trace("CO2 in controller not known {}", id);
+ }
+ }
+
+ /**
+ * Event on incoming screen updates
+ *
+ * @param id
+ * @param state
+ */
+ private void updateRol(Integer id, Integer state) {
+ QbusRol qRol = this.rol.get(id);
+
+ if (qRol != null) {
+ qRol.updateState(state);
+ } else {
+ logger.trace("ROL02P in controller not known {}", id);
+ }
+ }
+
+ /**
+ * Event on incoming screen with slats updates
+ *
+ * @param id
+ * @param state
+ * @param slats
+ */
+ private void updateRolSlats(Integer id, Integer state, Integer slats) {
+ QbusRol qRol = this.rol.get(id);
+
+ if (qRol != null) {
+ qRol.updateState(state);
+ qRol.updateSlats(slats);
+ } else {
+ logger.trace("ROL02P with slats in controller not known {}", id);
+ }
+ }
+
+ /**
+ * Put Bridge offline when there is no connection from the QbusClient
+ *
+ */
+ private void eventDisconnect() {
+ QbusBridgeHandler handler = bridgeCallBack;
+
+ if (handler != null) {
+ handler.bridgePending("Waiting for CTD connection");
+ }
+ }
+
+ /**
+ * Return all Bistabiel/Timers/Mono/Intervals in the Qbus Controller.
+ *
+ * @return
+ */
+ public Map<Integer, QbusBistabiel> getBistabiel() {
+ return this.bistabiel;
+ }
+
+ /**
+ * Return all Scenes in the Qbus Controller
+ *
+ * @return
+ */
+ public Map<Integer, QbusScene> getScene() {
+ return this.scene;
+ }
+
+ /**
+ * Return all Dimmers outputs in the Qbus Controller.
+ *
+ * @return
+ */
+ public Map<Integer, QbusDimmer> getDimmer() {
+ return this.dimmer;
+ }
+
+ /**
+ * Return all rollershutter/screen outputs in the Qbus Controller.
+ *
+ * @return
+ */
+ public Map<Integer, QbusRol> getRol() {
+ return this.rol;
+ }
+
+ /**
+ * Return all Thermostats outputs in the Qbus Controller.
+ *
+ * @return
+ */
+ public Map<Integer, QbusThermostat> getThermostat() {
+ return this.thermostat;
+ }
+
+ /**
+ * Return all CO2 outputs in the Qbus Controller.
+ *
+ * @return
+ */
+ public Map<Integer, QbusCO2> getCo2() {
+ return this.co2;
+ }
+
+ /**
+ * Method to check if communication with Qbus Server is active
+ *
+ * @return True if active
+ */
+ public boolean communicationActive() {
+ return qSocket != null;
+ }
+
+ /**
+ * Method to check if communication with Qbus Client is active
+ *
+ * @return True if active
+ */
+ public boolean clientConnected() {
+ return ctdConnected;
+ }
+
+ /**
+ * @param bridgeCallBack the bridgeCallBack to set
+ */
+ public void setBridgeCallBack(QbusBridgeHandler bridgeCallBack) {
+ this.bridgeCallBack = bridgeCallBack;
+ }
+
+ /**
+ * Get the serial number of the CTD as configured in the Bridge.
+ *
+ * @return serial number of controller
+ */
+ public @Nullable String getSN() {
+ return this.ctd;
+ }
+
+ /**
+ * Sets the serial number of the CTD, as configured in the Bridge.
+ */
+ public void setSN() {
+ QbusBridgeHandler qBridgeHandler = bridgeCallBack;
+
+ if (qBridgeHandler != null) {
+ this.ctd = qBridgeHandler.getSn();
+ }
+ }
+
+ @Override
+ public void handleCommand(ChannelUID channelUID, Command command) {
+ }
+}
--- /dev/null
+/**
+ * Copyright (c) 2010-2021 Contributors to the openHAB project
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ */
+package org.openhab.binding.qbus.internal.protocol;
+
+import java.io.IOException;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.eclipse.jdt.annotation.Nullable;
+import org.openhab.binding.qbus.internal.handler.QbusDimmerHandler;
+
+/**
+ * The {@link QbusDimmer} class represents the action Qbus Dimmer output.
+ *
+ * @author Koen Schockaert - Initial Contribution
+ */
+
+@NonNullByDefault
+public final class QbusDimmer {
+
+ private @Nullable QbusCommunication qComm;
+
+ private Integer id;
+
+ private @Nullable Integer state;
+
+ private @Nullable QbusDimmerHandler thingHandler;
+
+ QbusDimmer(Integer id) {
+ this.id = id;
+ }
+
+ /**
+ * This method should be called if the ThingHandler for the thing corresponding to this dimmer is initialized.
+ * It keeps a record of the thing handler in this object so the thing can be updated when
+ * the dimmer receives an update from the Qbus client.
+ *
+ * @param handler
+ */
+ public void setThingHandler(QbusDimmerHandler handler) {
+ this.thingHandler = handler;
+ }
+
+ /**
+ * This method sets a pointer to the qComm Dimmer of class {@link QbusCommuncation}.
+ * This is then used to be able to call back the sendCommand method in this class to send a command to the
+ * Qbus client.
+ *
+ * @param qComm
+ */
+ public void setQComm(QbusCommunication qComm) {
+ this.qComm = qComm;
+ }
+
+ /**
+ * Update the value of the dimmer
+ *
+ * @param state
+ */
+ public void updateState(@Nullable Integer state) {
+ this.state = state;
+ QbusDimmerHandler handler = this.thingHandler;
+ if (handler != null) {
+ handler.handleStateUpdate(this);
+ }
+ }
+
+ /**
+ * Get the state of dimmer.
+ *
+ * @return dimmer state
+ */
+ public @Nullable Integer getState() {
+ return this.state;
+ }
+
+ /**
+ * Sets the state of Dimmer.
+ *
+ * @param dimmer state
+ */
+ void setState(int state) {
+ this.state = state;
+ QbusDimmerHandler handler = thingHandler;
+ if (handler != null) {
+ handler.handleStateUpdate(this);
+ }
+ }
+
+ /**
+ * Sends the dimmer state to Qbus.
+ *
+ * @throws IOException
+ * @throws InterruptedException
+ */
+ public void execute(int percent, String sn) throws InterruptedException, IOException {
+ QbusMessageCmd qCmd = new QbusMessageCmd(sn, "executeDimmer").withId(this.id).withState(percent);
+ QbusCommunication comm = this.qComm;
+ if (comm != null) {
+ comm.sendMessage(qCmd);
+ }
+ }
+}
--- /dev/null
+/**
+ * Copyright (c) 2010-2021 Contributors to the openHAB project
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ */
+package org.openhab.binding.qbus.internal.protocol;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.eclipse.jdt.annotation.Nullable;
+
+/**
+ * Class {@link QbusMessageBase} used as base class for output from gson for cmd or event feedback from the Qbus server.
+ * This class only contains the common base fields required for the deserializer
+ * {@link QbusMessageDeserializer} to select the specific formats implemented in {@link QbusMessageMap},
+ * {@link QbusMessageListMap}, {@link QbusMessageCmd}.
+ * <p>
+ *
+ * @author Koen Schockaert - Initial Contribution
+ */
+
+@NonNullByDefault
+abstract class QbusMessageBase {
+
+ private @Nullable String ctd;
+ protected @Nullable String cmd;
+ protected @Nullable String type;
+ protected @Nullable Integer id;
+ protected @Nullable Integer state;
+ protected @Nullable Integer mode;
+ protected @Nullable Double setpoint;
+ protected @Nullable Double measured;
+ protected @Nullable Integer slatState;
+
+ @Nullable
+ String getSn() {
+ return this.ctd;
+ }
+
+ void setSn(String ctd) {
+ this.ctd = ctd;
+ }
+
+ @Nullable
+ String getCmd() {
+ return this.cmd;
+ }
+
+ void setCmd(String cmd) {
+ this.cmd = cmd;
+ }
+
+ @Nullable
+ public Integer getId() {
+ return id;
+ }
+
+ public void setType(String type) {
+ this.type = type;
+ }
+
+ @Nullable
+ public String getType() {
+ return type;
+ }
+
+ public void setId(Integer id) {
+ this.id = id;
+ }
+
+ @Nullable
+ public Integer getState() {
+ return state;
+ }
+
+ public void setState(int state) {
+ this.state = state;
+ }
+
+ @Nullable
+ public Integer getMode() {
+ return mode;
+ }
+
+ public void setMode(int mode) {
+ this.mode = mode;
+ }
+
+ @Nullable
+ public Double getSetPoint() {
+ return setpoint;
+ }
+
+ public void setSetPoint(Double setpoint) {
+ this.setpoint = setpoint;
+ }
+
+ @Nullable
+ public Double getMeasured() {
+ return measured;
+ }
+
+ public void setMeasured(Double measured) {
+ this.measured = measured;
+ }
+
+ @Nullable
+ public Integer getSlatState() {
+ return slatState;
+ }
+
+ public void setSlatState(int slatState) {
+ this.slatState = slatState;
+ }
+}
--- /dev/null
+/**
+ * Copyright (c) 2010-2021 Contributors to the openHAB project
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ */
+package org.openhab.binding.qbus.internal.protocol;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+
+/**
+ * Class {@link QbusMessageCmd} used as input to gson to send commands to Qbus. Extends
+ * {@link QbusMessageBase}.
+ * <p>
+ * Example: <code>{"cmd":"executebistabiel","id":1,"value1":0}</code>
+ *
+ * @author Koen Schockaert - Initial Contribution
+ */
+
+@NonNullByDefault
+class QbusMessageCmd extends QbusMessageBase {
+
+ QbusMessageCmd(String CTD) {
+ super.setSn(CTD);
+ }
+
+ QbusMessageCmd(String CTD, String cmd) {
+ this(CTD);
+ this.cmd = cmd;
+ }
+
+ QbusMessageCmd withId(Integer id) {
+ this.setId(id);
+ return this;
+ }
+
+ QbusMessageCmd withState(int state) {
+ this.setState(state);
+ return this;
+ }
+
+ QbusMessageCmd withMode(int mode) {
+ this.setMode(mode);
+ return this;
+ }
+
+ QbusMessageCmd withSetPoint(Double setpoint) {
+ this.setSetPoint(setpoint);
+ return this;
+ }
+
+ QbusMessageCmd withSlatState(int slatState) {
+ this.setSlatState(slatState);
+ return this;
+ }
+}
--- /dev/null
+/**
+ * Copyright (c) 2010-2021 Contributors to the openHAB project
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ */
+package org.openhab.binding.qbus.internal.protocol;
+
+import java.lang.reflect.Type;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.eclipse.jdt.annotation.Nullable;
+
+import com.google.gson.JsonArray;
+import com.google.gson.JsonDeserializationContext;
+import com.google.gson.JsonDeserializer;
+import com.google.gson.JsonElement;
+import com.google.gson.JsonObject;
+import com.google.gson.JsonParseException;
+
+/**
+ * Class {@link QbusMessageDeserializer} deserializes all json messages from Qbus. Various json
+ * message formats are supported. The format is selected based on the content of the cmd and event json objects.
+ *
+ * @author Koen Schockaert - Initial Contribution
+ *
+ */
+
+@NonNullByDefault
+class QbusMessageDeserializer implements JsonDeserializer<QbusMessageBase> {
+
+ @Override
+ public @Nullable QbusMessageBase deserialize(final JsonElement json, final Type typeOfT,
+ final JsonDeserializationContext context) throws JsonParseException {
+ final JsonObject jsonObject = json.getAsJsonObject();
+
+ String ctd = null;
+ String cmd = null;
+ Integer id = null;
+ Integer state = null;
+ Integer mode = null;
+ Double measured = null;
+ Double setpoint = null;
+ Integer slats = null;
+
+ QbusMessageBase message = null;
+
+ JsonElement jsonOutputs = null;
+ try {
+ if (jsonObject.has("CTD")) {
+ ctd = jsonObject.get("CTD").getAsString();
+ }
+
+ if (jsonObject.has("cmd")) {
+ cmd = jsonObject.get("cmd").getAsString();
+ }
+
+ if (jsonObject.has("id")) {
+ id = jsonObject.get("id").getAsInt();
+ }
+
+ if (jsonObject.has("state")) {
+ state = jsonObject.get("state").getAsInt();
+ }
+
+ if (jsonObject.has("mode")) {
+ mode = jsonObject.get("mode").getAsInt();
+ }
+
+ if (jsonObject.has("measured")) {
+ measured = jsonObject.get("measured").getAsDouble();
+ }
+
+ if (jsonObject.has("setpoint")) {
+ setpoint = jsonObject.get("setpoint").getAsDouble();
+ }
+
+ if (jsonObject.has("slats")) {
+ slats = jsonObject.get("slats").getAsInt();
+ }
+
+ if (jsonObject.has("outputs")) {
+ jsonOutputs = jsonObject.get("outputs");
+
+ }
+
+ if (ctd != null && cmd != null) {
+ if (jsonOutputs != null) {
+ if (jsonOutputs.isJsonArray()) {
+ JsonArray jsonOutputsArray = jsonOutputs.getAsJsonArray();
+ message = new QbusMessageListMap();
+ message.setCmd(cmd);
+ message.setSn(ctd);
+
+ List<Map<String, String>> outputsList = new ArrayList<>();
+ for (int i = 0; i < jsonOutputsArray.size(); i++) {
+ JsonObject jsonOutputsObject = jsonOutputsArray.get(i).getAsJsonObject();
+
+ Map<String, String> outputs = new HashMap<>();
+ for (Entry<String, JsonElement> entry : jsonOutputsObject.entrySet()) {
+ outputs.put(entry.getKey(), entry.getValue().getAsString());
+ }
+ outputsList.add(outputs);
+ }
+ ((QbusMessageListMap) message).setOutputs(outputsList);
+ }
+
+ } else {
+ message = new QbusMessageMap();
+
+ message.setCmd(cmd);
+ message.setSn(ctd);
+
+ if (id != null) {
+ message.setId(id);
+ }
+
+ if (state != null) {
+ message.setState(state);
+ }
+
+ if (slats != null) {
+ message.setSlatState(slats);
+ }
+
+ if (mode != null) {
+ message.setMode(mode);
+ }
+
+ if (measured != null) {
+ message.setMeasured(measured);
+ }
+
+ if (setpoint != null) {
+ message.setSetPoint(setpoint);
+ }
+
+ }
+ }
+ return message;
+ } catch (IllegalStateException e) {
+ String mess = e.getMessage();
+ throw new JsonParseException("Unexpected Json format " + mess + " for " + jsonObject.toString());
+ }
+ }
+}
--- /dev/null
+/**
+ * Copyright (c) 2010-2021 Contributors to the openHAB project
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ */
+package org.openhab.binding.qbus.internal.protocol;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+
+/**
+ * Class {@link QbusMessageListMap} used as output from gson for cmd or event feedback from Qbus where the
+ * data part is enclosed by [] and contains a list of json strings. Extends {@link QbusMessageBase}.
+ * <p>
+ *
+ * @author Koen Schockaert - Initial Contribution
+ */
+
+@NonNullByDefault
+class QbusMessageListMap extends QbusMessageBase {
+
+ private List<Map<String, String>> outputs = new ArrayList<>();
+
+ List<Map<String, String>> getOutputs() {
+ return this.outputs;
+ }
+
+ void setOutputs(List<Map<String, String>> outputs) {
+ this.outputs = outputs;
+ }
+}
--- /dev/null
+/**
+ * Copyright (c) 2010-2021 Contributors to the openHAB project
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ */
+package org.openhab.binding.qbus.internal.protocol;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+
+/**
+ * Class {@link QbusMessageMap} used as output from gson for cmd or event feedback from Qbus where the
+ * data part is a simple json string. Extends {@link QbusMessageBase}.
+ * <p>
+ *
+ * @author Koen Schockaert - Initial Contribution
+ */
+
+@NonNullByDefault
+class QbusMessageMap extends QbusMessageBase {
+
+ private Map<String, String> outputs = new HashMap<>();
+
+ Map<String, String> getData() {
+ return this.outputs;
+ }
+
+ void setOutputs(Map<String, String> outputs) {
+ this.outputs = outputs;
+ }
+}
--- /dev/null
+/**
+ * Copyright (c) 2010-2021 Contributors to the openHAB project
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ */
+package org.openhab.binding.qbus.internal.protocol;
+
+import java.io.IOException;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.eclipse.jdt.annotation.Nullable;
+import org.openhab.binding.qbus.internal.handler.QbusRolHandler;
+
+/**
+ * The {@link QbusRol} class represents the action Qbus Shutter/Slats output.
+ *
+ * @author Koen Schockaert - Initial Contribution
+ */
+
+@NonNullByDefault
+public final class QbusRol {
+
+ private @Nullable QbusCommunication qComm;
+
+ private Integer id;
+
+ private @Nullable Integer state;
+
+ private @Nullable Integer slats;
+
+ private @Nullable QbusRolHandler thingHandler;
+
+ QbusRol(Integer id) {
+ this.id = id;
+ }
+
+ /**
+ * This method should be called if the ThingHandler for the thing corresponding to this Shutter/Slats is
+ * initialized.
+ * It keeps a record of the thing handler in this object so the thing can be updated when
+ * the shutter/slat receives an update from the Qbus client.
+ *
+ * @param qbusRolHandler
+ */
+ public void setThingHandler(QbusRolHandler qbusRolHandler) {
+ this.thingHandler = qbusRolHandler;
+ }
+
+ /**
+ * This method sets a pointer to the qComm Shutter/Slats of class {@link QbusCommuncation}.
+ * This is then used to be able to call back the sendCommand method in this class to send a command to the
+ * Qbus IP-interface when..
+ *
+ * @param qComm
+ */
+ public void setQComm(QbusCommunication qComm) {
+ this.qComm = qComm;
+ }
+
+ /**
+ * Update the value of the Shutter.
+ *
+ * @param Shutter value
+ */
+ public void updateState(@Nullable Integer state) {
+ this.state = state;
+ QbusRolHandler handler = this.thingHandler;
+ if (handler != null) {
+ handler.handleStateUpdate(this);
+ }
+ }
+
+ /**
+ * Update the value of the Slats.
+ *
+ * @param Slat value
+ */
+ public void updateSlats(@Nullable Integer Slats) {
+ this.slats = Slats;
+ QbusRolHandler handler = this.thingHandler;
+ if (handler != null) {
+ handler.handleStateUpdate(this);
+ }
+ }
+
+ /**
+ * Get the value of the Shutter.
+ *
+ * @return shutter value
+ */
+ public @Nullable Integer getState() {
+ return this.state;
+ }
+
+ /**
+ * Get the value of the Slats.
+ *
+ * @return slats value
+ */
+ public @Nullable Integer getStateSlats() {
+ return this.slats;
+ }
+
+ /**
+ * Sends shutter state to Qbus.
+ *
+ * @throws IOException
+ * @throws InterruptedException
+ */
+ public void execute(int value, String sn) throws InterruptedException, IOException {
+ QbusMessageCmd qCmd = new QbusMessageCmd(sn, "executeStore").withId(this.id).withState(value);
+ QbusCommunication comm = qComm;
+ if (comm != null) {
+ comm.sendMessage(qCmd);
+ }
+ }
+
+ /**
+ * Sends slats state to Qbus.
+ *
+ * @throws IOException
+ * @throws InterruptedException
+ */
+ public void executeSlats(int value, String sn) throws InterruptedException, IOException {
+ QbusMessageCmd qCmd = new QbusMessageCmd(sn, "executeSlats").withId(this.id).withState(value);
+ QbusCommunication comm = qComm;
+ if (comm != null) {
+ comm.sendMessage(qCmd);
+ }
+ }
+}
--- /dev/null
+/**
+ * Copyright (c) 2010-2021 Contributors to the openHAB project
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ */
+package org.openhab.binding.qbus.internal.protocol;
+
+import java.io.IOException;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.eclipse.jdt.annotation.Nullable;
+import org.openhab.binding.qbus.internal.handler.QbusSceneHandler;
+
+/**
+ * The {@link QbusScene} class represents the action Qbus Scene output.
+ *
+ * @author Koen Schockaert - Initial Contribution
+ */
+
+@NonNullByDefault
+public final class QbusScene {
+
+ private @Nullable QbusCommunication qComm;
+
+ public @Nullable QbusSceneHandler thingHandler;
+
+ private @Nullable Integer state;
+
+ private Integer id;
+
+ QbusScene(Integer id) {
+ this.id = id;
+ }
+
+ /**
+ * This method should be called if the ThingHandler for the thing corresponding to this scene is initialized.
+ * It keeps a record of the thing handler in this object so the thing can be updated when
+ * the scene output receives an update from the Qbus client.
+ *
+ * @param handler
+ */
+ public void setThingHandler(QbusSceneHandler handler) {
+ this.thingHandler = handler;
+ }
+
+ /**
+ * This method sets a pointer to the qComm SCENE of class {@link QbusCommuncation}.
+ * This is then used to be able to call back the sendCommand method in this class to send a command to the
+ * Qbus client.
+ *
+ * @param qComm
+ */
+ public void setQComm(QbusCommunication qComm) {
+ this.qComm = qComm;
+ }
+
+ /**
+ * Get the value of the Scene.
+ *
+ * @return Scene value
+ */
+ public @Nullable Integer getState() {
+ return this.state;
+ }
+
+ /**
+ * Sends Scene state to Qbus.
+ *
+ * @param value
+ * @param sn
+ * @throws InterruptedException
+ * @throws IOException
+ */
+ public void execute(int value, String sn) throws InterruptedException, IOException {
+ QbusMessageCmd qCmd = new QbusMessageCmd(sn, "executeScene").withId(this.id).withState(value);
+ QbusCommunication comm = qComm;
+ if (comm != null) {
+ comm.sendMessage(qCmd);
+ }
+ }
+}
--- /dev/null
+/**
+ * Copyright (c) 2010-2021 Contributors to the openHAB project
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ */
+package org.openhab.binding.qbus.internal.protocol;
+
+import java.io.IOException;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.eclipse.jdt.annotation.Nullable;
+import org.openhab.binding.qbus.internal.handler.QbusThermostatHandler;
+
+/**
+ * The {@link QbusThermostat} class represents the thermostat Qbus communication object. It contains all
+ * fields representing a Qbus thermostat and has methods to set the thermostat mode and setpoint in Qbus and
+ * receive thermostat updates.
+ *
+ * @author Koen Schockaert - Initial Contribution
+ */
+
+@NonNullByDefault
+public final class QbusThermostat {
+
+ private @Nullable QbusCommunication qComm;
+
+ private Integer id;
+ private double measured = 0.0;
+ private double setpoint = 0.0;
+ private @Nullable Integer mode;
+
+ private @Nullable QbusThermostatHandler thingHandler;
+
+ QbusThermostat(Integer id) {
+ this.id = id;
+ }
+
+ /**
+ * This method should be called if the ThingHandler for the thing corresponding to the termostat is initialized.
+ * It keeps a record of the thing handler in this object so the thing can be updated when
+ * the thermostat receives an update from the Qbus client.
+ *
+ * @param handler
+ */
+ public void setThingHandler(QbusThermostatHandler handler) {
+ this.thingHandler = handler;
+ }
+
+ /**
+ * This method sets a pointer to the qComm THERMOSTAT of class {@link QbusCommuncation}.
+ * This is then used to be able to call back the sendCommand method in this class to send a command to the
+ * Qbus client.
+ *
+ * @param qComm
+ */
+ public void setQComm(QbusCommunication qComm) {
+ this.qComm = qComm;
+ }
+
+ /**
+ * Update all values of the Thermostat
+ *
+ * @param measured current temperature in 1°C multiples
+ * @param setpoint the setpoint temperature in 1°C multiples
+ * @param mode 0="Manual", 1="Freeze", 2="Economic", 3="Comfort", 4="Night"
+ */
+ public void updateState(Double measured, Double setpoint, Integer mode) {
+ this.measured = measured;
+ this.setpoint = setpoint;
+ this.mode = mode;
+
+ QbusThermostatHandler handler = this.thingHandler;
+ if (handler != null) {
+ handler.handleStateUpdate(this);
+ }
+ }
+
+ /**
+ * Get measured temperature of the Thermostat.
+ *
+ * @return measured temperature in 0.5°C multiples
+ */
+ public @Nullable Double getMeasured() {
+ return this.measured;
+ }
+
+ /**
+ * Get setpoint temperature of the Thermostat.
+ *
+ * @return the setpoint temperature in 0.5°C multiples
+ */
+ public @Nullable Double getSetpoint() {
+ return this.setpoint;
+ }
+
+ /**
+ * Get the Thermostat mode.
+ *
+ * @return the mode: 0="Manual", 1="Freeze", 2="Economic", 3="Comfort", 4="Night"
+ */
+ public @Nullable Integer getMode() {
+ return this.mode;
+ }
+
+ /**
+ * Sends Thermostat mode to Qbus.
+ *
+ * @param mode
+ * @param sn
+ * @throws InterruptedException
+ * @throws IOException
+ */
+ public void executeMode(int mode, String sn) throws InterruptedException, IOException {
+ QbusMessageCmd qCmd = new QbusMessageCmd(sn, "executeThermostat").withId(this.id).withMode(mode);
+ QbusCommunication comm = this.qComm;
+ if (comm != null) {
+ comm.sendMessage(qCmd);
+ }
+ }
+
+ /**
+ * Sends Thermostat setpoint to Qbus.
+ *
+ * @param setpoint
+ * @throws IOException
+ * @throws InterruptedException
+ */
+ public void executeSetpoint(double setpoint, String sn) throws InterruptedException, IOException {
+ QbusMessageCmd qCmd = new QbusMessageCmd(sn, "executeThermostat").withId(this.id).withSetPoint(setpoint);
+ QbusCommunication comm = this.qComm;
+ if (comm != null) {
+ comm.sendMessage(qCmd);
+ }
+ }
+}
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+<binding:binding id="qbus" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xmlns:binding="https://openhab.org/schemas/binding/v1.0.0"
+ xsi:schemaLocation="https://openhab.org/schemas/binding/v1.0.0 https://openhab.org/schemas/binding-1.0.0.xsd">
+
+ <name>Qbus Binding</name>
+ <description>This is the binding for the Qbus home automation system. Qbus is a system made and developed in Belgium
+ (https://www.qbus.be/nl-nl)</description>
+
+</binding:binding>
--- /dev/null
+# binding
+binding.qbus.name = Qbus Binding
+binding.qbus.description = Deze binding maakt via een server applicate verbinding met de Qbus controller.
+
+# thing types
+thing-type.qbus.bridge.label = Qbus Bridge
+thing-type.qbus.bridge.description = De Qbus Bridge Maakt Verbinding Met de Qbus Server.
+thing-type.config.qbus.bridge.addr.label = IP Adres of Host Naam
+thing-type.config.qbus.bridge.addr.description = IP adres van de Qbus server, meestal 'localhost'
+thing-type.config.qbus.bridge.sn.label = Serienummer van de Controller
+thing-type.config.qbus.bridge.sn.description = Serienummer van de CTD controller
+thing-type.config.qbus.bridge.port.label = Poort
+thing-type.config.qbus.bridge.port.description = Communicatiepoort van de Qbus server (standaard: 8447)
+thing-type.config.qbus.bridge.serverCheck.label = Server Connectie
+thing-type.config.qbus.bridge.serverCheck.description = Ingestelde timer, bij het verlopen van de timer zal de communicatie met de Qbus server gecontroleerd worden en indien nodig herstart.
+
+thing-type.qbus.onOff.label = Aan/uit
+thing-type.qbus.onOff.description = Alle Bistabiel-Mono-Timer-Interval uitgangen
+thing-type.config.qbus.onOff.bistabielId.label = Qbus ID
+thing-type.config.qbus.onOff.bistabielId.description = Identificatienummer van de uitgang (zie SMIII)
+
+thing-type.qbus.scene.label = Sfeer
+thing-type.qbus.scene.description = Alle sferen
+thing-type.config.qbus.scene.sceneId.label = Qbus ID
+thing-type.config.qbus.scene.sceneId.description = Identificatienummer van de sfeer (zie SMIII)
+
+thing-type.qbus.co2.label = CO2
+thing-type.qbus.co2.description = Alle CO2 Uitgangen
+thing-type.config.qbus.co2.co2Id.label = Qbus ID
+thing-type.config.qbus.co2.co2Id.description = Identificatienummer van de uitgang (zie SMIII)
+
+thing-type.qbus.dimmer.label = Dimmer
+thing-type.qbus.dimmer.description = Alle Dimbare Uitgangen
+thing-type.config.qbus.dimmer.dimmerId.label = Qbus ID
+thing-type.config.qbus.dimmer.dimmerId.description = Identificatienummer van de uitgang (zie SMIII)
+thing-type.config.qbus.dimmer.step.label = Stappenwaarde
+thing-type.config.qbus.dimmer.step.description = Waarde gebruikt voor het dimmen in stappen (standaard 10%)
+
+thing-type.qbus.rollershutter.label = Rolluik
+thing-type.qbus.rollershutter.description = Alle Rolluik (ROL02P) Uitgangen
+thing-type.config.qbus.rollershutter.rolId.label = Qbus ID
+thing-type.config.qbus.rollershutter.rolId.description = Identificatienummer van de uitgang (zie SMIII)
+
+thing-type.qbus.rollershutter_slats.label = Rolluik (met lamellen)
+thing-type.qbus.rollershutter_slats.description = Alle schermen met lamellen (ROL02P) uitgang
+thing-type.config.qbus.rollershutter_slats.rolId.label = Qbus ID
+thing-type.config.qbus.rollershutter_slats.rolId.description = Identificatienummer van de uitgang (zie SMIII)
+
+thing-type.qbus.thermostat.label = Thermostaat
+thing-type.qbus.thermostat.description = Alle thermostaten
+thing-type.config.qbus.thermostat.thermostatId.label = Qbus ID
+thing-type.config.qbus.thermostat.thermostatId.description = Identificatienummer van de uitgang (zie SMIII)
+
+channel-type.qbus.scene.label = Sfeer
+channel-type.qbus.scene.description = Bediening van de sfeer
+
+channel-type.qbus.co2.label = CO2
+channel-type.qbus.co2.description = Uitlezing van de CO2 waarde
+
+channel-type.qbus.switch.label = Schakelaar
+channel-type.qbus.switch.description = Schakelaar bediening van de uitgangen
+
+channel-type.qbus.brightness.label = Helderheid
+channel-type.qbus.brightness.description = Helderheid bediening van de uitgangen
+
+channel-type.qbus.measured.label = Gemeten Temperatuur
+channel-type.qbus.measured.description = Uitlezing van de gemeten Temperatuur
+
+channel-type.qbus.setpoint.label = Ingestelde Temperatuur
+channel-type.qbus.setpoint.description = Ingestelde temperatuur bediening van de uitgangen
+
+channel-type.qbus.mode.label = Ingesteld Regime
+channel-type.qbus.mode.description = Regime bediening van de uitgangen
+channel-type.qbus.mode.state.option.0 = Manueel
+channel-type.qbus.mode.state.option.1 = Vorst
+channel-type.qbus.mode.state.option.2 = Economisch
+channel-type.qbus.mode.state.option.3 = Comfort
+channel-type.qbus.mode.state.option.4 = Nacht
+
+channel-type.qbus.rollershutter.label = Rolluik Bediening
+channel-type.qbus.rollershutter.description = Rolluik bediening van de uitgangen
+
+channel-type.qbus.slats.label = Lamellen Bediening
+channel-type.qbus.slats.description = Lamellen bediening van de uitgangen
+
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+<thing:thing-descriptions bindingId="qbus"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xmlns:thing="https://openhab.org/schemas/thing-description/v1.0.0"
+ xsi:schemaLocation="https://openhab.org/schemas/thing-description/v1.0.0 https://openhab.org/schemas/thing-description-1.0.0.xsd">
+
+ <bridge-type id="bridge">
+ <label>Qbus Bridge</label>
+ <description>This bridge represents a Qbus client</description>
+ <config-description>
+ <parameter name="addr" type="text" required="true">
+ <label>Hostname</label>
+ <description>IP address or hostname of Qbus server, usually 'localhost'</description>
+ <default>localhost</default>
+ <context>network-address</context>
+ </parameter>
+ <parameter name="sn" type="text" required="true">
+ <label>Serial Number</label>
+ <description>Serial number of the CTD controller</description>
+ </parameter>
+ <parameter name="port" type="integer" required="false">
+ <label>Bridge Port</label>
+ <description>Port to communicate with Qbus server, default 8447</description>
+ <default>8447</default>
+ <advanced>true</advanced>
+ </parameter>
+ <parameter name="serverCheck" type="integer" required="false" unit="min" min="1">
+ <label>Server Check</label>
+ <description>Time to check communication with Qbus Server (min), default 10. If set to 0 or left empty, no refresh
+ will be scheduled</description>
+ <default>10</default>
+ <advanced>true</advanced>
+ </parameter>
+ </config-description>
+ </bridge-type>
+
+ <thing-type id="onOff">
+ <supported-bridge-type-refs>
+ <bridge-type-ref id="bridge"/>
+ </supported-bridge-type-refs>
+ <label>Switch</label>
+ <description>Bistabiel-Mono-Timer-Interval Output</description>
+ <channels>
+ <channel id="switch" typeId="system.power"/>
+ </channels>
+ <config-description>
+ <parameter name="bistabielId" type="integer" required="true">
+ <label>Qbus ID</label>
+ <description>Qbus Bistabiel ID</description>
+ </parameter>
+ </config-description>
+ </thing-type>
+
+ <thing-type id="scene">
+ <supported-bridge-type-refs>
+ <bridge-type-ref id="bridge"/>
+ </supported-bridge-type-refs>
+ <label>Scene</label>
+ <description>Qbus Scene</description>
+ <channels>
+ <channel id="scene" typeId="scene"/>
+ </channels>
+ <config-description>
+ <parameter name="sceneId" type="integer" required="true">
+ <label>Qbus Scene ID</label>
+ <description>Qbus Scene ID</description>
+ </parameter>
+ </config-description>
+ </thing-type>
+
+ <thing-type id="co2">
+ <supported-bridge-type-refs>
+ <bridge-type-ref id="bridge"/>
+ </supported-bridge-type-refs>
+ <label>CO2</label>
+ <description>Qbus CO2</description>
+ <channels>
+ <channel id="co2" typeId="co2"/>
+ </channels>
+ <config-description>
+ <parameter name="co2Id" type="integer" required="true">
+ <label>Qbus CO2 ID</label>
+ <description>Qbus CO2 ID</description>
+ </parameter>
+ </config-description>
+ </thing-type>
+
+ <thing-type id="dimmer">
+ <supported-bridge-type-refs>
+ <bridge-type-ref id="bridge"/>
+ </supported-bridge-type-refs>
+ <label>Dimmer</label>
+ <description>Qbus Dimmer Output</description>
+ <channels>
+ <channel id="brightness" typeId="system.brightness"/>
+ </channels>
+ <config-description>
+ <parameter name="dimmerId" type="integer" required="true">
+ <label>Output ID</label>
+ <description>Qbus Dimmer ID</description>
+ </parameter>
+ <parameter name="step" type="integer" required="true">
+ <label>Step Value</label>
+ <description>Step value used for increase/decrease of dimmer brightness, default 10%</description>
+ <default>10</default>
+ <advanced>true</advanced>
+ </parameter>
+ </config-description>
+ </thing-type>
+
+ <thing-type id="rollershutter">
+ <supported-bridge-type-refs>
+ <bridge-type-ref id="bridge"/>
+ </supported-bridge-type-refs>
+ <label>RollerShutter</label>
+ <description>Qbus shutter (ROL02P) control</description>
+ <channels>
+ <channel id="rollershutter" typeId="rollershutter"/>
+ </channels>
+ <config-description>
+ <parameter name="rolId" type="integer" required="true">
+ <label>Rol ID</label>
+ <description>Qbus Rol Id</description>
+ </parameter>
+ </config-description>
+ </thing-type>
+
+ <thing-type id="rollershutter_slats">
+ <supported-bridge-type-refs>
+ <bridge-type-ref id="bridge"/>
+ </supported-bridge-type-refs>
+ <label>RollerShutter (With Slats)</label>
+ <description>Qbus shutter with slats control</description>
+ <channels>
+ <channel id="rollershutter" typeId="rollershutter"/>
+ <channel id="slats" typeId="slats"/>
+ </channels>
+ <config-description>
+ <parameter name="rolId" type="integer" required="true">
+ <label>Rol ID</label>
+ <description>Qbus Rol Id</description>
+ </parameter>
+ </config-description>
+ </thing-type>
+
+ <thing-type id="thermostat">
+ <supported-bridge-type-refs>
+ <bridge-type-ref id="bridge"/>
+ </supported-bridge-type-refs>
+ <label>Thermostat</label>
+ <description>Qbus Thermostat</description>
+ <channels>
+ <channel id="measured" typeId="measured"/>
+ <channel id="mode" typeId="mode"/>
+ <channel id="setpoint" typeId="setpoint"/>
+ </channels>
+ <config-description>
+ <parameter name="thermostatId" type="integer" required="true">
+ <label>Thermostat ID</label>
+ <description>Qbus Thermostat ID</description>
+ </parameter>
+ </config-description>
+ </thing-type>
+
+ <channel-type id="scene">
+ <item-type>Switch</item-type>
+ <label>Scene</label>
+ <description>Scene Control for Qbus</description>
+ <category>Scene</category>
+ </channel-type>
+
+ <channel-type id="measured">
+ <item-type>Number:Temperature</item-type>
+ <label>Measured</label>
+ <description>Temperature Measured by Thermostat</description>
+ <category>Temperature</category>
+ <tags>
+ <tag>CurrentTemperature</tag>
+ </tags>
+ <state readOnly="true" pattern="%.1f %unit%"/>
+ </channel-type>
+
+ <channel-type id="setpoint">
+ <item-type>Number:Temperature</item-type>
+ <label>Setpoint</label>
+ <description>Setpoint Temperature of Thermostat</description>
+ <category>Temperature</category>
+ <tags>
+ <tag>TargetTemperature</tag>
+ </tags>
+ <state min="0" max="100" step="0.5" pattern="%.1f %unit%"/>
+ </channel-type>
+
+ <channel-type id="mode">
+ <item-type>Number</item-type>
+ <label>Mode</label>
+ <description>Thermostat Mode</description>
+ <category>Number</category>
+ <state>
+ <options>
+ <option value="0">Manual</option>
+ <option value="1">Freeze</option>
+ <option value="2">Economic</option>
+ <option value="3">Comfort</option>
+ <option value="4">Night</option>
+ </options>
+ </state>
+ </channel-type>
+
+ <channel-type id="co2">
+ <item-type>Number</item-type>
+ <label>CO2</label>
+ <description>CO2 value for Qbus</description>
+ <category>CO2</category>
+ </channel-type>
+
+ <channel-type id="rollershutter">
+ <item-type>Rollershutter</item-type>
+ <label>Rollershutter</label>
+ <description>Rollershutter Control for Qbus</description>
+ <category>Blinds</category>
+ </channel-type>
+
+ <channel-type id="slats">
+ <item-type>Dimmer</item-type>
+ <label>Slatcontrol</label>
+ <description>Slatcontrol for Qbus</description>
+ <category>Blinds</category>
+ </channel-type>
+
+</thing:thing-descriptions>
<module>org.openhab.binding.pulseaudio</module>
<module>org.openhab.binding.pushbullet</module>
<module>org.openhab.binding.pushover</module>
+ <module>org.openhab.binding.qbus</module>
<module>org.openhab.binding.radiothermostat</module>
<module>org.openhab.binding.regoheatpump</module>
<module>org.openhab.binding.revogi</module>