diff --git a/CODEOWNERS b/CODEOWNERS index 1382030a92552..08b0ca2ba9970 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -223,6 +223,7 @@ /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 diff --git a/bundles/org.openhab.binding.qbus/NOTICE b/bundles/org.openhab.binding.qbus/NOTICE new file mode 100644 index 0000000000000..38d625e349232 --- /dev/null +++ b/bundles/org.openhab.binding.qbus/NOTICE @@ -0,0 +1,13 @@ +This content is produced and maintained by the openHAB project. + +* Project home: https://www.openhab.org + +== Declared Project Licenses + +This program and the accompanying materials are made available under the terms +of the Eclipse Public License 2.0 which is available at +https://www.eclipse.org/legal/epl-2.0/. + +== Source Code + +https://github.com/openhab/openhab-addons diff --git a/bundles/org.openhab.binding.qbus/README.md b/bundles/org.openhab.binding.qbus/README.md new file mode 100644 index 0000000000000..50b5bedccdf19 --- /dev/null +++ b/bundles/org.openhab.binding.qbus/README.md @@ -0,0 +1,110 @@ +# Qbus Binding + +![Qbus Logo](doc/Logo.JPG) + +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 [ "Lighting" ] {channel="qbus:dimmer:CTD007841:1:brightness"} +Switch Toonzaal230V {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 {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. + diff --git a/bundles/org.openhab.binding.qbus/doc/Logo.JPG b/bundles/org.openhab.binding.qbus/doc/Logo.JPG new file mode 100644 index 0000000000000..93f10e4de88ff Binary files /dev/null and b/bundles/org.openhab.binding.qbus/doc/Logo.JPG differ diff --git a/bundles/org.openhab.binding.qbus/pom.xml b/bundles/org.openhab.binding.qbus/pom.xml new file mode 100644 index 0000000000000..c611bfc48f111 --- /dev/null +++ b/bundles/org.openhab.binding.qbus/pom.xml @@ -0,0 +1,17 @@ + + + + 4.0.0 + + + org.openhab.addons.bundles + org.openhab.addons.reactor.bundles + 3.1.0-SNAPSHOT + + + org.openhab.binding.qbus + + openHAB Add-ons :: Bundles :: Qbus Binding + + diff --git a/bundles/org.openhab.binding.qbus/src/main/feature/feature.xml b/bundles/org.openhab.binding.qbus/src/main/feature/feature.xml new file mode 100644 index 0000000000000..fcae4ee3aee20 --- /dev/null +++ b/bundles/org.openhab.binding.qbus/src/main/feature/feature.xml @@ -0,0 +1,23 @@ + + + + mvn:org.openhab.core.features.karaf/org.openhab.core.features.karaf.openhab-core/${ohc.version}/xml/features + + + openhab-runtime-base + mvn:org.openhab.addons.bundles/org.openhab.binding.qbus/${project.version} + + diff --git a/bundles/org.openhab.binding.qbus/src/main/java/org/openhab/binding/qbus/internal/QbusBindingConstants.java b/bundles/org.openhab.binding.qbus/src/main/java/org/openhab/binding/qbus/internal/QbusBindingConstants.java new file mode 100644 index 0000000000000..57b0cf5575a78 --- /dev/null +++ b/bundles/org.openhab.binding.qbus/src/main/java/org/openhab/binding/qbus/internal/QbusBindingConstants.java @@ -0,0 +1,84 @@ +/** + * 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 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 SCENE_THING_TYPES_UIDS = Set.of(THING_TYPE_SCENE); + public static final Set CO2_THING_TYPES_UIDS = Set.of(THING_TYPE_CO2); + public static final Set ROLLERSHUTTER_THING_TYPES_UIDS = Set.of(THING_TYPE_ROLLERSHUTTER); + public static final Set ROLLERSHUTTER_SLATS_THING_TYPES_UIDS = Set.of(THING_TYPE_ROLLERSHUTTER_SLATS); + public static final Set BISTABIEL_THING_TYPES_UIDS = Set.of(THING_TYPE_ON_OFF_LIGHT); + public static final Set THERMOSTAT_THING_TYPES_UIDS = Set.of(THING_TYPE_THERMOSTAT); + public static final Set DIMMER_THING_TYPES_UIDS = Set.of(THING_TYPE_ON_OFF_LIGHT, + THING_TYPE_DIMMABLE_LIGHT); + + public static final Set 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"; +} diff --git a/bundles/org.openhab.binding.qbus/src/main/java/org/openhab/binding/qbus/internal/QbusBridgeHandler.java b/bundles/org.openhab.binding.qbus/src/main/java/org/openhab/binding/qbus/internal/QbusBridgeHandler.java new file mode 100644 index 0000000000000..2f625669ae348 --- /dev/null +++ b/bundles/org.openhab.binding.qbus/src/main/java/org/openhab/binding/qbus/internal/QbusBridgeHandler.java @@ -0,0 +1,359 @@ +/** + * 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) { + } +} diff --git a/bundles/org.openhab.binding.qbus/src/main/java/org/openhab/binding/qbus/internal/QbusConfiguration.java b/bundles/org.openhab.binding.qbus/src/main/java/org/openhab/binding/qbus/internal/QbusConfiguration.java new file mode 100644 index 0000000000000..6a68c94b10783 --- /dev/null +++ b/bundles/org.openhab.binding.qbus/src/main/java/org/openhab/binding/qbus/internal/QbusConfiguration.java @@ -0,0 +1,31 @@ +/** + * Copyright (c) 2010-2021 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ + +package org.openhab.binding.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; +} diff --git a/bundles/org.openhab.binding.qbus/src/main/java/org/openhab/binding/qbus/internal/QbusHandlerFactory.java b/bundles/org.openhab.binding.qbus/src/main/java/org/openhab/binding/qbus/internal/QbusHandlerFactory.java new file mode 100644 index 0000000000000..6de139b1e6b84 --- /dev/null +++ b/bundles/org.openhab.binding.qbus/src/main/java/org/openhab/binding/qbus/internal/QbusHandlerFactory.java @@ -0,0 +1,72 @@ +/** + * 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; + } +} diff --git a/bundles/org.openhab.binding.qbus/src/main/java/org/openhab/binding/qbus/internal/handler/QbusBistabielHandler.java b/bundles/org.openhab.binding.qbus/src/main/java/org/openhab/binding/qbus/internal/handler/QbusBistabielHandler.java new file mode 100644 index 0000000000000..76f0d79e9afa5 --- /dev/null +++ b/bundles/org.openhab.binding.qbus/src/main/java/org/openhab/binding/qbus/internal/handler/QbusBistabielHandler.java @@ -0,0 +1,244 @@ +/** + * 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 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 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; + } + } +} diff --git a/bundles/org.openhab.binding.qbus/src/main/java/org/openhab/binding/qbus/internal/handler/QbusCO2Handler.java b/bundles/org.openhab.binding.qbus/src/main/java/org/openhab/binding/qbus/internal/handler/QbusCO2Handler.java new file mode 100644 index 0000000000000..5b191b4e8557e --- /dev/null +++ b/bundles/org.openhab.binding.qbus/src/main/java/org/openhab/binding/qbus/internal/handler/QbusCO2Handler.java @@ -0,0 +1,197 @@ +/** + * 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 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 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; + } + } +} diff --git a/bundles/org.openhab.binding.qbus/src/main/java/org/openhab/binding/qbus/internal/handler/QbusDimmerHandler.java b/bundles/org.openhab.binding.qbus/src/main/java/org/openhab/binding/qbus/internal/handler/QbusDimmerHandler.java new file mode 100644 index 0000000000000..fc91c6a3f2984 --- /dev/null +++ b/bundles/org.openhab.binding.qbus/src/main/java/org/openhab/binding/qbus/internal/handler/QbusDimmerHandler.java @@ -0,0 +1,310 @@ +/** + * 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 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 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; + } + } +} diff --git a/bundles/org.openhab.binding.qbus/src/main/java/org/openhab/binding/qbus/internal/handler/QbusGlobalHandler.java b/bundles/org.openhab.binding.qbus/src/main/java/org/openhab/binding/qbus/internal/handler/QbusGlobalHandler.java new file mode 100644 index 0000000000000..068813c899b5d --- /dev/null +++ b/bundles/org.openhab.binding.qbus/src/main/java/org/openhab/binding/qbus/internal/handler/QbusGlobalHandler.java @@ -0,0 +1,114 @@ +/** + * 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); + } +} diff --git a/bundles/org.openhab.binding.qbus/src/main/java/org/openhab/binding/qbus/internal/handler/QbusRolHandler.java b/bundles/org.openhab.binding.qbus/src/main/java/org/openhab/binding/qbus/internal/handler/QbusRolHandler.java new file mode 100644 index 0000000000000..0a1a532f11347 --- /dev/null +++ b/bundles/org.openhab.binding.qbus/src/main/java/org/openhab/binding/qbus/internal/handler/QbusRolHandler.java @@ -0,0 +1,333 @@ +/** + * 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 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 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; + } + } +} diff --git a/bundles/org.openhab.binding.qbus/src/main/java/org/openhab/binding/qbus/internal/handler/QbusSceneHandler.java b/bundles/org.openhab.binding.qbus/src/main/java/org/openhab/binding/qbus/internal/handler/QbusSceneHandler.java new file mode 100644 index 0000000000000..fc0381fd4af06 --- /dev/null +++ b/bundles/org.openhab.binding.qbus/src/main/java/org/openhab/binding/qbus/internal/handler/QbusSceneHandler.java @@ -0,0 +1,217 @@ +/** + * 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 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 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; + } + } +} diff --git a/bundles/org.openhab.binding.qbus/src/main/java/org/openhab/binding/qbus/internal/handler/QbusThermostatHandler.java b/bundles/org.openhab.binding.qbus/src/main/java/org/openhab/binding/qbus/internal/handler/QbusThermostatHandler.java new file mode 100644 index 0000000000000..cbef42e775eb3 --- /dev/null +++ b/bundles/org.openhab.binding.qbus/src/main/java/org/openhab/binding/qbus/internal/handler/QbusThermostatHandler.java @@ -0,0 +1,295 @@ +/** + * 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 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 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; + } + } +} diff --git a/bundles/org.openhab.binding.qbus/src/main/java/org/openhab/binding/qbus/internal/handler/QbusThingsConfig.java b/bundles/org.openhab.binding.qbus/src/main/java/org/openhab/binding/qbus/internal/handler/QbusThingsConfig.java new file mode 100644 index 0000000000000..56de0cb64d8f4 --- /dev/null +++ b/bundles/org.openhab.binding.qbus/src/main/java/org/openhab/binding/qbus/internal/handler/QbusThingsConfig.java @@ -0,0 +1,32 @@ +/** + * 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; +} diff --git a/bundles/org.openhab.binding.qbus/src/main/java/org/openhab/binding/qbus/internal/protocol/QbusBistabiel.java b/bundles/org.openhab.binding.qbus/src/main/java/org/openhab/binding/qbus/internal/protocol/QbusBistabiel.java new file mode 100644 index 0000000000000..a55ec87431c69 --- /dev/null +++ b/bundles/org.openhab.binding.qbus/src/main/java/org/openhab/binding/qbus/internal/protocol/QbusBistabiel.java @@ -0,0 +1,101 @@ +/** + * 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); + } + } +} diff --git a/bundles/org.openhab.binding.qbus/src/main/java/org/openhab/binding/qbus/internal/protocol/QbusCO2.java b/bundles/org.openhab.binding.qbus/src/main/java/org/openhab/binding/qbus/internal/protocol/QbusCO2.java new file mode 100644 index 0000000000000..88d0f404925c8 --- /dev/null +++ b/bundles/org.openhab.binding.qbus/src/main/java/org/openhab/binding/qbus/internal/protocol/QbusCO2.java @@ -0,0 +1,65 @@ +/** + * 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); + } + } +} diff --git a/bundles/org.openhab.binding.qbus/src/main/java/org/openhab/binding/qbus/internal/protocol/QbusCommunication.java b/bundles/org.openhab.binding.qbus/src/main/java/org/openhab/binding/qbus/internal/protocol/QbusCommunication.java new file mode 100644 index 0000000000000..e07032d2ebd6a --- /dev/null +++ b/bundles/org.openhab.binding.qbus/src/main/java/org/openhab/binding/qbus/internal/protocol/QbusCommunication.java @@ -0,0 +1,796 @@ +/** + * 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: + *
    + *
  • Start and stop TCP socket connection with Qbus Server. + *
  • Read all the outputs and their status from the Qbus Controller. + *
  • Execute Qbus commands. + *
  • Listen to events from Qbus. + *
+ * + * 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> outputs = new ArrayList<>(); + private final Map bistabiel = new HashMap<>(); + private final Map scene = new HashMap<>(); + private final Map dimmer = new HashMap<>(); + private final Map rol = new HashMap<>(); + private final Map thermostat = new HashMap<>(); + private final Map 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. + *
    + *
  • Connect to Qbus server + *
  • Requests outputs + *
  • Start listener + *
+ * + * @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. + *

+ * 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. + *

    + *
  • Get request & update states for Bistabiel/Timers/Intervals/Mono outputs + *
  • Get request & update states for the Scenes + *
  • Get request & update states for Dimmers 1T and 2T + *
  • Get request & update states for Shutters + *
  • Get request & update states for Thermostats + *
  • Get request & update states for CO2 + *
+ * + * @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 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 getBistabiel() { + return this.bistabiel; + } + + /** + * Return all Scenes in the Qbus Controller + * + * @return + */ + public Map getScene() { + return this.scene; + } + + /** + * Return all Dimmers outputs in the Qbus Controller. + * + * @return + */ + public Map getDimmer() { + return this.dimmer; + } + + /** + * Return all rollershutter/screen outputs in the Qbus Controller. + * + * @return + */ + public Map getRol() { + return this.rol; + } + + /** + * Return all Thermostats outputs in the Qbus Controller. + * + * @return + */ + public Map getThermostat() { + return this.thermostat; + } + + /** + * Return all CO2 outputs in the Qbus Controller. + * + * @return + */ + public Map 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) { + } +} diff --git a/bundles/org.openhab.binding.qbus/src/main/java/org/openhab/binding/qbus/internal/protocol/QbusDimmer.java b/bundles/org.openhab.binding.qbus/src/main/java/org/openhab/binding/qbus/internal/protocol/QbusDimmer.java new file mode 100644 index 0000000000000..a7d61973775a4 --- /dev/null +++ b/bundles/org.openhab.binding.qbus/src/main/java/org/openhab/binding/qbus/internal/protocol/QbusDimmer.java @@ -0,0 +1,112 @@ +/** + * 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); + } + } +} diff --git a/bundles/org.openhab.binding.qbus/src/main/java/org/openhab/binding/qbus/internal/protocol/QbusMessageBase.java b/bundles/org.openhab.binding.qbus/src/main/java/org/openhab/binding/qbus/internal/protocol/QbusMessageBase.java new file mode 100644 index 0000000000000..5c75d1cb7b196 --- /dev/null +++ b/bundles/org.openhab.binding.qbus/src/main/java/org/openhab/binding/qbus/internal/protocol/QbusMessageBase.java @@ -0,0 +1,121 @@ +/** + * 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}. + *

+ * + * @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; + } +} diff --git a/bundles/org.openhab.binding.qbus/src/main/java/org/openhab/binding/qbus/internal/protocol/QbusMessageCmd.java b/bundles/org.openhab.binding.qbus/src/main/java/org/openhab/binding/qbus/internal/protocol/QbusMessageCmd.java new file mode 100644 index 0000000000000..f7e7d031e1684 --- /dev/null +++ b/bundles/org.openhab.binding.qbus/src/main/java/org/openhab/binding/qbus/internal/protocol/QbusMessageCmd.java @@ -0,0 +1,62 @@ +/** + * 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}. + *

+ * Example: {"cmd":"executebistabiel","id":1,"value1":0} + * + * @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; + } +} diff --git a/bundles/org.openhab.binding.qbus/src/main/java/org/openhab/binding/qbus/internal/protocol/QbusMessageDeserializer.java b/bundles/org.openhab.binding.qbus/src/main/java/org/openhab/binding/qbus/internal/protocol/QbusMessageDeserializer.java new file mode 100644 index 0000000000000..2d06f72cb39c0 --- /dev/null +++ b/bundles/org.openhab.binding.qbus/src/main/java/org/openhab/binding/qbus/internal/protocol/QbusMessageDeserializer.java @@ -0,0 +1,157 @@ +/** + * 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 { + + @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> outputsList = new ArrayList<>(); + for (int i = 0; i < jsonOutputsArray.size(); i++) { + JsonObject jsonOutputsObject = jsonOutputsArray.get(i).getAsJsonObject(); + + Map outputs = new HashMap<>(); + for (Entry 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()); + } + } +} diff --git a/bundles/org.openhab.binding.qbus/src/main/java/org/openhab/binding/qbus/internal/protocol/QbusMessageListMap.java b/bundles/org.openhab.binding.qbus/src/main/java/org/openhab/binding/qbus/internal/protocol/QbusMessageListMap.java new file mode 100644 index 0000000000000..01fefbd74ba87 --- /dev/null +++ b/bundles/org.openhab.binding.qbus/src/main/java/org/openhab/binding/qbus/internal/protocol/QbusMessageListMap.java @@ -0,0 +1,41 @@ +/** + * Copyright (c) 2010-2021 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.binding.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}. + *

+ * + * @author Koen Schockaert - Initial Contribution + */ + +@NonNullByDefault +class QbusMessageListMap extends QbusMessageBase { + + private List> outputs = new ArrayList<>(); + + List> getOutputs() { + return this.outputs; + } + + void setOutputs(List> outputs) { + this.outputs = outputs; + } +} diff --git a/bundles/org.openhab.binding.qbus/src/main/java/org/openhab/binding/qbus/internal/protocol/QbusMessageMap.java b/bundles/org.openhab.binding.qbus/src/main/java/org/openhab/binding/qbus/internal/protocol/QbusMessageMap.java new file mode 100644 index 0000000000000..097d0841429ca --- /dev/null +++ b/bundles/org.openhab.binding.qbus/src/main/java/org/openhab/binding/qbus/internal/protocol/QbusMessageMap.java @@ -0,0 +1,40 @@ +/** + * 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}. + *

+ * + * @author Koen Schockaert - Initial Contribution + */ + +@NonNullByDefault +class QbusMessageMap extends QbusMessageBase { + + private Map outputs = new HashMap<>(); + + Map getData() { + return this.outputs; + } + + void setOutputs(Map outputs) { + this.outputs = outputs; + } +} diff --git a/bundles/org.openhab.binding.qbus/src/main/java/org/openhab/binding/qbus/internal/protocol/QbusRol.java b/bundles/org.openhab.binding.qbus/src/main/java/org/openhab/binding/qbus/internal/protocol/QbusRol.java new file mode 100644 index 0000000000000..4358e7e27176b --- /dev/null +++ b/bundles/org.openhab.binding.qbus/src/main/java/org/openhab/binding/qbus/internal/protocol/QbusRol.java @@ -0,0 +1,138 @@ +/** + * 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); + } + } +} diff --git a/bundles/org.openhab.binding.qbus/src/main/java/org/openhab/binding/qbus/internal/protocol/QbusScene.java b/bundles/org.openhab.binding.qbus/src/main/java/org/openhab/binding/qbus/internal/protocol/QbusScene.java new file mode 100644 index 0000000000000..bc5fac0c40726 --- /dev/null +++ b/bundles/org.openhab.binding.qbus/src/main/java/org/openhab/binding/qbus/internal/protocol/QbusScene.java @@ -0,0 +1,88 @@ +/** + * 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); + } + } +} diff --git a/bundles/org.openhab.binding.qbus/src/main/java/org/openhab/binding/qbus/internal/protocol/QbusThermostat.java b/bundles/org.openhab.binding.qbus/src/main/java/org/openhab/binding/qbus/internal/protocol/QbusThermostat.java new file mode 100644 index 0000000000000..493d6b6b548b5 --- /dev/null +++ b/bundles/org.openhab.binding.qbus/src/main/java/org/openhab/binding/qbus/internal/protocol/QbusThermostat.java @@ -0,0 +1,142 @@ +/** + * 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); + } + } +} diff --git a/bundles/org.openhab.binding.qbus/src/main/resources/OH-INF/binding/binding.xml b/bundles/org.openhab.binding.qbus/src/main/resources/OH-INF/binding/binding.xml new file mode 100644 index 0000000000000..2457b9d1b1a7d --- /dev/null +++ b/bundles/org.openhab.binding.qbus/src/main/resources/OH-INF/binding/binding.xml @@ -0,0 +1,10 @@ + + + + Qbus Binding + 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) + + diff --git a/bundles/org.openhab.binding.qbus/src/main/resources/OH-INF/i18n/qbus_nl.properties b/bundles/org.openhab.binding.qbus/src/main/resources/OH-INF/i18n/qbus_nl.properties new file mode 100644 index 0000000000000..c1e57f9cd1066 --- /dev/null +++ b/bundles/org.openhab.binding.qbus/src/main/resources/OH-INF/i18n/qbus_nl.properties @@ -0,0 +1,85 @@ +# 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 + diff --git a/bundles/org.openhab.binding.qbus/src/main/resources/OH-INF/thing/thing-types.xml b/bundles/org.openhab.binding.qbus/src/main/resources/OH-INF/thing/thing-types.xml new file mode 100644 index 0000000000000..0b8a00cd906b3 --- /dev/null +++ b/bundles/org.openhab.binding.qbus/src/main/resources/OH-INF/thing/thing-types.xml @@ -0,0 +1,231 @@ + + + + + + This bridge represents a Qbus client + + + + IP address or hostname of Qbus server, usually 'localhost' + localhost + network-address + + + + Serial number of the CTD controller + + + + Port to communicate with Qbus server, default 8447 + 8447 + true + + + + Time to check communication with Qbus Server (min), default 10. If set to 0 or left empty, no refresh + will be scheduled + 10 + true + + + + + + + + + + Bistabiel-Mono-Timer-Interval Output + + + + + + + Qbus Bistabiel ID + + + + + + + + + + Qbus Scene + + + + + + + Qbus Scene ID + + + + + + + + + + Qbus CO2 + + + + + + + Qbus CO2 ID + + + + + + + + + + Qbus Dimmer Output + + + + + + + Qbus Dimmer ID + + + + Step value used for increase/decrease of dimmer brightness, default 10% + 10 + true + + + + + + + + + + Qbus shutter (ROL02P) control + + + + + + + Qbus Rol Id + + + + + + + + + + Qbus shutter with slats control + + + + + + + + Qbus Rol Id + + + + + + + + + + Qbus Thermostat + + + + + + + + + Qbus Thermostat ID + + + + + + Switch + + Scene Control for Qbus + Scene + + + + Number:Temperature + + Temperature Measured by Thermostat + Temperature + + CurrentTemperature + + + + + + Number:Temperature + + Setpoint Temperature of Thermostat + Temperature + + TargetTemperature + + + + + + Number + + Thermostat Mode + Number + + + + + + + + + + + + + Number + + CO2 value for Qbus + CO2 + + + + Rollershutter + + Rollershutter Control for Qbus + Blinds + + + + Dimmer + + Slatcontrol for Qbus + Blinds + + + diff --git a/bundles/pom.xml b/bundles/pom.xml index a9988b81141d1..4dcdaf23842cc 100644 --- a/bundles/pom.xml +++ b/bundles/pom.xml @@ -498,6 +498,8 @@ org.openhab.binding.pushover + org.openhab.binding.qbus + org.openhab.binding.radiothermostat org.openhab.binding.regoheatpump