From 8202d579650eabb179c987b27d96707b423ce0eb Mon Sep 17 00:00:00 2001 From: lsiepel Date: Wed, 19 May 2021 21:53:33 +0200 Subject: [PATCH] [plugwiseha] Initial contribution (#9504) Signed-off-by: Leo Siepel --- CODEOWNERS | 1 + bom/openhab-addons/pom.xml | 5 + bundles/org.openhab.binding.plugwiseha/NOTICE | 13 + .../org.openhab.binding.plugwiseha/README.md | 215 ++++++++ .../org.openhab.binding.plugwiseha/pom.xml | 17 + .../src/main/feature/feature.xml | 9 + .../internal/PlugwiseHABindingConstants.java | 165 ++++++ .../internal/PlugwiseHAHandlerFactory.java | 116 ++++ .../PlugwiseHABadRequestException.java | 39 ++ .../PlugwiseHACommunicationException.java | 33 ++ .../api/exception/PlugwiseHAException.java | 40 ++ .../PlugwiseHAForbiddenException.java | 41 ++ .../PlugwiseHAInvalidHostException.java | 41 ++ .../PlugwiseHANotAuthorizedException.java | 40 ++ .../exception/PlugwiseHATimeoutException.java | 40 ++ .../PlugwiseHAUnauthorizedException.java | 40 ++ .../api/model/PlugwiseHAController.java | 450 ++++++++++++++++ .../model/PlugwiseHAControllerRequest.java | 286 ++++++++++ .../internal/api/model/PlugwiseHAModel.java | 28 + .../model/converter/DateTimeConverter.java | 65 +++ .../model/dto/ActuatorFunctionalities.java | 73 +++ .../api/model/dto/ActuatorFunctionality.java | 111 ++++ ...ctuatorFunctionalityOffsetTemperature.java | 29 + .../model/dto/ActuatorFunctionalityRelay.java | 36 ++ .../dto/ActuatorFunctionalityThermostat.java | 38 ++ .../dto/ActuatorFunctionalityThreshold.java | 25 + .../model/dto/ActuatorFunctionalityTimer.java | 25 + .../dto/ActuatorFunctionalityToggle.java | 25 + .../internal/api/model/dto/Appliance.java | 256 +++++++++ .../internal/api/model/dto/Appliances.java | 55 ++ .../internal/api/model/dto/DomainObjects.java | 64 +++ .../api/model/dto/GatewayEnvironment.java | 28 + .../internal/api/model/dto/GatewayInfo.java | 120 +++++ .../internal/api/model/dto/Location.java | 137 +++++ .../internal/api/model/dto/Locations.java | 55 ++ .../internal/api/model/dto/Log.java | 115 ++++ .../internal/api/model/dto/Logs.java | 194 +++++++ .../internal/api/model/dto/Module.java | 90 ++++ .../internal/api/model/dto/Modules.java | 30 ++ .../api/model/dto/PlugwiseBaseModel.java | 60 +++ .../api/model/dto/PlugwiseComparableDate.java | 24 + .../api/model/dto/PlugwiseHACollection.java | 88 ++++ .../internal/api/model/dto/Service.java | 30 ++ .../internal/api/model/dto/Services.java | 30 ++ .../internal/api/model/dto/ZigBeeNode.java | 52 ++ .../internal/api/xml/PlugwiseHAXStream.java | 110 ++++ .../config/PlugwiseHABridgeThingConfig.java | 66 +++ .../config/PlugwiseHAThingConfig.java | 52 ++ .../discovery/PlugwiseHADiscoveryService.java | 212 ++++++++ .../handler/PlugwiseHAApplianceHandler.java | 496 ++++++++++++++++++ .../handler/PlugwiseHABaseHandler.java | 245 +++++++++ .../handler/PlugwiseHABridgeHandler.java | 252 +++++++++ .../handler/PlugwiseHAZoneHandler.java | 222 ++++++++ .../main/resources/OH-INF/binding/binding.xml | 10 + .../main/resources/OH-INF/config/config.xml | 88 ++++ .../main/resources/OH-INF/thing/channels.xml | 194 +++++++ .../resources/OH-INF/thing/thing-types.xml | 128 +++++ .../src/main/resources/domain_objects.xslt | 127 +++++ bundles/pom.xml | 1 + 59 files changed, 5677 insertions(+) create mode 100644 bundles/org.openhab.binding.plugwiseha/NOTICE create mode 100644 bundles/org.openhab.binding.plugwiseha/README.md create mode 100644 bundles/org.openhab.binding.plugwiseha/pom.xml create mode 100644 bundles/org.openhab.binding.plugwiseha/src/main/feature/feature.xml create mode 100644 bundles/org.openhab.binding.plugwiseha/src/main/java/org/openhab/binding/plugwiseha/internal/PlugwiseHABindingConstants.java create mode 100644 bundles/org.openhab.binding.plugwiseha/src/main/java/org/openhab/binding/plugwiseha/internal/PlugwiseHAHandlerFactory.java create mode 100644 bundles/org.openhab.binding.plugwiseha/src/main/java/org/openhab/binding/plugwiseha/internal/api/exception/PlugwiseHABadRequestException.java create mode 100644 bundles/org.openhab.binding.plugwiseha/src/main/java/org/openhab/binding/plugwiseha/internal/api/exception/PlugwiseHACommunicationException.java create mode 100644 bundles/org.openhab.binding.plugwiseha/src/main/java/org/openhab/binding/plugwiseha/internal/api/exception/PlugwiseHAException.java create mode 100644 bundles/org.openhab.binding.plugwiseha/src/main/java/org/openhab/binding/plugwiseha/internal/api/exception/PlugwiseHAForbiddenException.java create mode 100644 bundles/org.openhab.binding.plugwiseha/src/main/java/org/openhab/binding/plugwiseha/internal/api/exception/PlugwiseHAInvalidHostException.java create mode 100644 bundles/org.openhab.binding.plugwiseha/src/main/java/org/openhab/binding/plugwiseha/internal/api/exception/PlugwiseHANotAuthorizedException.java create mode 100644 bundles/org.openhab.binding.plugwiseha/src/main/java/org/openhab/binding/plugwiseha/internal/api/exception/PlugwiseHATimeoutException.java create mode 100644 bundles/org.openhab.binding.plugwiseha/src/main/java/org/openhab/binding/plugwiseha/internal/api/exception/PlugwiseHAUnauthorizedException.java create mode 100644 bundles/org.openhab.binding.plugwiseha/src/main/java/org/openhab/binding/plugwiseha/internal/api/model/PlugwiseHAController.java create mode 100644 bundles/org.openhab.binding.plugwiseha/src/main/java/org/openhab/binding/plugwiseha/internal/api/model/PlugwiseHAControllerRequest.java create mode 100644 bundles/org.openhab.binding.plugwiseha/src/main/java/org/openhab/binding/plugwiseha/internal/api/model/PlugwiseHAModel.java create mode 100644 bundles/org.openhab.binding.plugwiseha/src/main/java/org/openhab/binding/plugwiseha/internal/api/model/converter/DateTimeConverter.java create mode 100644 bundles/org.openhab.binding.plugwiseha/src/main/java/org/openhab/binding/plugwiseha/internal/api/model/dto/ActuatorFunctionalities.java create mode 100644 bundles/org.openhab.binding.plugwiseha/src/main/java/org/openhab/binding/plugwiseha/internal/api/model/dto/ActuatorFunctionality.java create mode 100644 bundles/org.openhab.binding.plugwiseha/src/main/java/org/openhab/binding/plugwiseha/internal/api/model/dto/ActuatorFunctionalityOffsetTemperature.java create mode 100644 bundles/org.openhab.binding.plugwiseha/src/main/java/org/openhab/binding/plugwiseha/internal/api/model/dto/ActuatorFunctionalityRelay.java create mode 100644 bundles/org.openhab.binding.plugwiseha/src/main/java/org/openhab/binding/plugwiseha/internal/api/model/dto/ActuatorFunctionalityThermostat.java create mode 100644 bundles/org.openhab.binding.plugwiseha/src/main/java/org/openhab/binding/plugwiseha/internal/api/model/dto/ActuatorFunctionalityThreshold.java create mode 100644 bundles/org.openhab.binding.plugwiseha/src/main/java/org/openhab/binding/plugwiseha/internal/api/model/dto/ActuatorFunctionalityTimer.java create mode 100644 bundles/org.openhab.binding.plugwiseha/src/main/java/org/openhab/binding/plugwiseha/internal/api/model/dto/ActuatorFunctionalityToggle.java create mode 100644 bundles/org.openhab.binding.plugwiseha/src/main/java/org/openhab/binding/plugwiseha/internal/api/model/dto/Appliance.java create mode 100644 bundles/org.openhab.binding.plugwiseha/src/main/java/org/openhab/binding/plugwiseha/internal/api/model/dto/Appliances.java create mode 100644 bundles/org.openhab.binding.plugwiseha/src/main/java/org/openhab/binding/plugwiseha/internal/api/model/dto/DomainObjects.java create mode 100644 bundles/org.openhab.binding.plugwiseha/src/main/java/org/openhab/binding/plugwiseha/internal/api/model/dto/GatewayEnvironment.java create mode 100644 bundles/org.openhab.binding.plugwiseha/src/main/java/org/openhab/binding/plugwiseha/internal/api/model/dto/GatewayInfo.java create mode 100644 bundles/org.openhab.binding.plugwiseha/src/main/java/org/openhab/binding/plugwiseha/internal/api/model/dto/Location.java create mode 100644 bundles/org.openhab.binding.plugwiseha/src/main/java/org/openhab/binding/plugwiseha/internal/api/model/dto/Locations.java create mode 100644 bundles/org.openhab.binding.plugwiseha/src/main/java/org/openhab/binding/plugwiseha/internal/api/model/dto/Log.java create mode 100644 bundles/org.openhab.binding.plugwiseha/src/main/java/org/openhab/binding/plugwiseha/internal/api/model/dto/Logs.java create mode 100644 bundles/org.openhab.binding.plugwiseha/src/main/java/org/openhab/binding/plugwiseha/internal/api/model/dto/Module.java create mode 100644 bundles/org.openhab.binding.plugwiseha/src/main/java/org/openhab/binding/plugwiseha/internal/api/model/dto/Modules.java create mode 100644 bundles/org.openhab.binding.plugwiseha/src/main/java/org/openhab/binding/plugwiseha/internal/api/model/dto/PlugwiseBaseModel.java create mode 100644 bundles/org.openhab.binding.plugwiseha/src/main/java/org/openhab/binding/plugwiseha/internal/api/model/dto/PlugwiseComparableDate.java create mode 100644 bundles/org.openhab.binding.plugwiseha/src/main/java/org/openhab/binding/plugwiseha/internal/api/model/dto/PlugwiseHACollection.java create mode 100644 bundles/org.openhab.binding.plugwiseha/src/main/java/org/openhab/binding/plugwiseha/internal/api/model/dto/Service.java create mode 100644 bundles/org.openhab.binding.plugwiseha/src/main/java/org/openhab/binding/plugwiseha/internal/api/model/dto/Services.java create mode 100644 bundles/org.openhab.binding.plugwiseha/src/main/java/org/openhab/binding/plugwiseha/internal/api/model/dto/ZigBeeNode.java create mode 100644 bundles/org.openhab.binding.plugwiseha/src/main/java/org/openhab/binding/plugwiseha/internal/api/xml/PlugwiseHAXStream.java create mode 100644 bundles/org.openhab.binding.plugwiseha/src/main/java/org/openhab/binding/plugwiseha/internal/config/PlugwiseHABridgeThingConfig.java create mode 100644 bundles/org.openhab.binding.plugwiseha/src/main/java/org/openhab/binding/plugwiseha/internal/config/PlugwiseHAThingConfig.java create mode 100644 bundles/org.openhab.binding.plugwiseha/src/main/java/org/openhab/binding/plugwiseha/internal/discovery/PlugwiseHADiscoveryService.java create mode 100644 bundles/org.openhab.binding.plugwiseha/src/main/java/org/openhab/binding/plugwiseha/internal/handler/PlugwiseHAApplianceHandler.java create mode 100644 bundles/org.openhab.binding.plugwiseha/src/main/java/org/openhab/binding/plugwiseha/internal/handler/PlugwiseHABaseHandler.java create mode 100644 bundles/org.openhab.binding.plugwiseha/src/main/java/org/openhab/binding/plugwiseha/internal/handler/PlugwiseHABridgeHandler.java create mode 100644 bundles/org.openhab.binding.plugwiseha/src/main/java/org/openhab/binding/plugwiseha/internal/handler/PlugwiseHAZoneHandler.java create mode 100644 bundles/org.openhab.binding.plugwiseha/src/main/resources/OH-INF/binding/binding.xml create mode 100644 bundles/org.openhab.binding.plugwiseha/src/main/resources/OH-INF/config/config.xml create mode 100644 bundles/org.openhab.binding.plugwiseha/src/main/resources/OH-INF/thing/channels.xml create mode 100644 bundles/org.openhab.binding.plugwiseha/src/main/resources/OH-INF/thing/thing-types.xml create mode 100644 bundles/org.openhab.binding.plugwiseha/src/main/resources/domain_objects.xslt diff --git a/CODEOWNERS b/CODEOWNERS index 16a0fb25e1b..ce4bd7ea6d4 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -229,6 +229,7 @@ /bundles/org.openhab.binding.playstation/ @FluBBaOfWard /bundles/org.openhab.binding.plclogo/ @falkena /bundles/org.openhab.binding.plugwise/ @wborn +/bundles/org.openhab.binding.plugwiseha/ @lsiepel /bundles/org.openhab.binding.powermax/ @lolodomo /bundles/org.openhab.binding.pulseaudio/ @peuter /bundles/org.openhab.binding.pushbullet/ @hakan42 diff --git a/bom/openhab-addons/pom.xml b/bom/openhab-addons/pom.xml index 57f14d56c82..be408410734 100644 --- a/bom/openhab-addons/pom.xml +++ b/bom/openhab-addons/pom.xml @@ -1126,6 +1126,11 @@ org.openhab.binding.plugwise ${project.version} + + org.openhab.addons.bundles + org.openhab.binding.plugwiseha + ${project.version} + org.openhab.addons.bundles org.openhab.binding.powermax diff --git a/bundles/org.openhab.binding.plugwiseha/NOTICE b/bundles/org.openhab.binding.plugwiseha/NOTICE new file mode 100644 index 00000000000..4c20ef446c1 --- /dev/null +++ b/bundles/org.openhab.binding.plugwiseha/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/openhab2-addons diff --git a/bundles/org.openhab.binding.plugwiseha/README.md b/bundles/org.openhab.binding.plugwiseha/README.md new file mode 100644 index 00000000000..f566e973662 --- /dev/null +++ b/bundles/org.openhab.binding.plugwiseha/README.md @@ -0,0 +1,215 @@ +# PlugwiseHA Binding + +The Plugwise Home Automation binding adds support to openHAB for the [Plugwise Home Automation ecosystem](https://www.plugwise.com/en_US/adam_zone_control). +This system is built around a gateway from Plugwise called the 'Adam' which incorporates a ZigBee controller to manage thermostatic radiator valves, room thermostats, floor heating pumps, et cetera. + +Users can manage and control this system either via a web app or a mobile phone app developed by Plugwise. +The (web) app allows users to define heating zone's (e.g. rooms) and add radiator valves to those rooms to manage and control their heating irrespective of other rooms. + +Using the Plugwise Home Automation binding you can incorporate the management of these devices and heating zones into openHAB. +The binding uses the same RESTfull API that both the mobile phone app and the web app use. + +The binding requires users to have a working Plugwise Home Automation setup consisting of at least 1 gateway device (the 'Adam') and preferably 1 radiator valve as a bare minimum. +The 'Adam' (from hereon called the gateway) needs to be accessible from the openHAB instance via a TCP/IP connection. + +## Supported Things + +| Device Type | Description | Thing Type | +|----------------------------------------------------------|--------------------------------------------------------------------------------------------------------------------|----------------------| +| - | A Plugwise heating zone configured with at least 1 of the devices below | zone | +| [Adam](https://www.plugwise.com/en_US/products/adam-ha) | The Plugwise Home Automation Bridge is needed to connect to the Adam boiler gateway | gateway | +| [Tom](https://www.plugwise.com/en_US/products/tom) | A Plugwise Home Automation radiator valve | appliance_valve | +| [Floor](https://www.plugwise.com/en_US/products/floor) | A Plugwise Home Automation radiator valve specifically used for floor heating | appliance_valve | +| [Circle](https://www.plugwise.com/en_US/products/circle) | A power outlet plug that provides energy measurement and switching control of appliances (e.g. floor heating pump) | appliance_pump | +| [Lisa](https://www.plugwise.com/en_US/products/lisa) | A room thermostat (also supports the 'Anna' room thermostat) | appliance_thermostat | +| [Boiler] | A central boiler used for heating and/or domestic hot water | appliance_boiler | + + + +## Discovery + +After setting up the Plugwise Home Automation bridge you can start a manual scan to find all devices registered on the gateway. +You can also manually add things by entering the corresponding device id as a configuration parameter. +The device IDs can be found be enabling TRACE logging in the Karaf console. + +## Thing Configuration + +You must define a Plugwise Home Automation gateway (Bridge) before defining zones or appliances (Things) for this binding to work. + +#### Plugwise Home Automation gateway (Bridge): + +| Parameter | Description | Config | Default | +| --------- | ----------------------------------------------------------------------- | -------- | ------- | +| host | The IP address or hostname of the Adam HA gateway | Required | 'adam' | +| username | The username for the Adam HA gateway | Optional | 'smile' | +| smileID | The 8 letter code on the sticker on the back of the Adam boiler gateway | Required | - | +| refresh | The refresh interval in seconds | Optional | 15 | + +#### Plugwise Home Automation zone (`zone`): + +| Parameter | Description | Config | Default | +| --------- | ------------------------- | -------- | ------- | +| id | The unique ID of the zone | Required | - | + +#### Plugwise Home Automation appliance (`appliance_valve`): + +| Parameter | Description | Config | Default | +| -------------------- | ------------------------------------------------------------------------------------------------------------------ | -------- | ------- | +| id | The unique ID of the radiator valve appliance | Required | - | +| lowBatteryPercentage | Battery charge remaining at which to trigger battery low warning. (*Only applicable for battery operated devices*) | Optional | 15 | + +#### Plugwise Home Automation appliance (`appliance_thermostat`): + +| Parameter | Description | Config | Default | +| -------------------- | ------------------------------------------------------------------------------------------------------------------ | -------- | ------- | +| id | The unique ID of the room thermostat appliance | Required | - | +| lowBatteryPercentage | Battery charge remaining at which to trigger battery low warning. (*Only applicable for battery operated devices*) | Optional | 15 | + + +#### Plugwise Home Automation appliance (`appliance_pump`): + +| Parameter | Description | Config | Default | +| --------- | ----------------------------------- | -------- | ------- | +| id | The unique ID of the pump appliance | Required | - | + +#### Plugwise Home Automation boiler (`appliance_boiler`): + +| Parameter | Description | Config | Default | +| --------- | --------------------------- | -------- | ------- | +| id | The unique ID of the boiler | Required | - | + +## Channels + +| channel | type | Read-only? | description | +|----------------------|--------------------|------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| temperature | Number:Temperature | Yes | The temperature of an appliance that supports the thermostat functionality | +| setpointTemperature | Number:Temperature | No | The setpoint temperature (read/write) of an appliance that supports the thermostat functionality | +| power | Switch | No | Toggle an appliance ON/OFF that supports the relay functionality | +| lock | Switch | No | Toggle an appliance lock ON/OFF that supports the relay functionality.(*When the lock is ON the gateway will not automatically control the corresponding relay switch depending on thermostat mode*) | +| powerUsage | Number:Power | Yes | The current power usage in Watts of an appliance that supports this | +| batteryLevel | Number | Yes | The current battery level of an appliance that is battery operated | +| batteryLevelLow | Switch | Yes | Switches ON when the battery level of an appliance that is battery operated drops below a certain threshold | +| chState | Switch | Yes | The current central heating state of the boiler | +| dhwState | Switch | Yes | The current domestic hot water state of the boiler | +| waterPressure | Number:Pressure | Yes | The current water pressure of the boiler | +| presetScene | String | Yes | The current active scene for the zone | +| valvePosition | Number | Yes | The current position of the valve | +| preHeat | Switch | Yes | Toggle the pre heating of a zone ON/OFF | +| coolingState | Switch | Yes | The current cooling state of the boiler | +| intendedBoilerTemp | Number:Temperature | Yes | The intended boiler temperature | +| flameState | Switch | Yes | The flame state of the boiler | +| intendedHeatingState | Switch | Yes | The intended heating state of the boiler | +| modulationLevel | Number | Yes | The current modulation level of the boiler | +| otAppFaultCode | Number | Yes | The Opentherm application fault code of the boiler | +| dhwTemperature | Number:Temperature | Yes | The current central heating state of the boiler | +| otOEMFaultCode | Number | Yes | The Opentherm OEM fault code of the boiler | +| boilerTemperature | Number:Temperature | Yes | The current temperature of the boiler | +| dhwSetpoint | Number:Temperature | Yes | The domestic hot water setpoint | +| maxBoilerTemperature | Number:Temperature | Yes | The maximum temperature of the boiler | +| dhwComfortMode | Switch | Yes | The domestic hot water confortmode | + | + + +## Full Example + +**things/plugwiseha.things** + +``` +Bridge plugwiseha:gateway:home "Plugwise Home Automation Gateway" [ smileId="abcdefgh" ] { + Thing zone living_room_zone "Living room" [ id="$device_id" ] + Thing appliance_valve living_room_radiator "Living room radiator valve" [ id="$device_id" ] + Thing appliance_thermostat living_room_thermostat "Living room thermostat" [ id="$device_id" ] + Thing appliance_pump living_room_pump "Floor heating pump" [ id="$device_id" ] + Thing appliance_boiler main_boiler "Main boiler" [ id="$device_id" ] +} +``` + +Replace `$device_id` accordingly. + +**items/plugwiseha.items** + +``` +Number:Temperature living_room_zone_temperature "Zone temperature" {channel="plugwiseha:zone:home:living_room_zone:temperature"} +Number:Temperature living_room_zone_temperature_setpoint "Zone temperature setpoint" {channel="plugwiseha:zone:home:living_room_zone:setpointTemperature"} +Number:Temperature living_room_zone_preset_scene "Zone preset scene" {channel="plugwiseha:zone:home:living_room_zone:presetScene"} +Switch living_room_zone_preheat "Zone preheat enabled" {channel="plugwiseha:zone:home:living_room_zone:preHeat"} + +Number:Temperature living_room_radiator_temperature "Radiator valve temperature" {channel="plugwiseha:appliance_valve:home:living_room_radiator:temperature"} +Number:Temperature living_room_radiator_temperature_setpoint "Radiator valve temperature setpoint" {channel="plugwiseha:appliance_valve:home:living_room_radiator:setpointTemperature"} +Number living_room_radiator_valve_position "Radiator valve position" {channel="plugwiseha:appliance_valve:home:living_room_radiator:valvePosition"} + +Number:Temperature living_room_thermostat_temperature "Room thermostat temperature" {channel="plugwiseha:appliance_valve:home:living_room_thermostat:temperature"} +Number:Temperature living_room_thermostat_temperature_setpoint "Room thermostat temperature setpoint" {channel="plugwiseha:appliance_valve:home:living_room_thermostat:setpointTemperature"} +Number:Temperature living_room_thermostat_temperature_offset "Room thermostat temperature offset" {channel="plugwiseha:appliance_valve:home:living_room_thermostat:offsetTemperature"} + +Switch living_room_pump_power "Floor heating pump power" {channel="plugwiseha:appliance_pump:home:living_room_pump:power"} +Switch living_room_pump_lock "Floor heating pump lock [MAP:(plugwiseha.map):%s]" {channel="plugwiseha:appliance_pump:home:living_room_pump:lock"} +Number:Power living_room_pump_power_usage "Floor heating pump power [%0.2fW]" {channel="plugwiseha:appliance_pump:home:living_room_pump:powerUsage"} + +Number:Pressure main_boiler_waterpressure "Waterpressure" { channel="plugwiseha:appliance_boiler:home:main_boiler:waterPressure"} +Switch main_boiler_chState "Heating active" { channel="plugwiseha:appliance_boiler:home:main_boiler:chActive"} +Switch main_boiler_dhwState "Domestic hot water active" { channel="plugwiseha:appliance_boiler:home:main_boiler:dhwActive"} + +Switch main_boiler_coolingState "Cooling state" { channel="plugwiseha:appliance_boiler:home:main_boiler:coolingState"} +Number:Temperature main_boiler_intendedBoilerTemp "Intended boiler temperature" {channel="plugwiseha:appliance_boiler:home:living_room_thermostat:intendedBoilerTemp"} +Switch main_boiler_flameState "Flame state" { channel="plugwiseha:appliance_boiler:home:main_boiler:flameState"} +Switch main_boiler_intendedHeatingState "Intended heating state" { channel="plugwiseha:appliance_boiler:home:main_boiler:intendedHeatingState"} +Number main_boiler_modulationLevel "Modulation level" {channel="plugwiseha:appliance_boiler:home:living_room_radiator:modulationLevel"} +Number main_boiler_otAppFaultCode "Opentherm app. faultcode" {channel="plugwiseha:appliance_boiler:home:living_room_radiator:otAppFaultCode"} +Number:Temperature main_boiler_dhwTemperature "DHW temperature" {channel="plugwiseha:appliance_boiler:home:living_room_thermostat:dhwTemperature"} +Number main_boiler_otOEMFaultCode "Opentherm OEM faultcode" {channel="plugwiseha:appliance_boiler:home:living_room_radiator:otOEMFaultCode"} +Number:Temperature main_boiler_boilerTemperature "Boiler temperature" {channel="plugwiseha:appliance_boiler:home:living_room_thermostat:boilerTemperature"} +Number:Temperature main_boiler_dhwSetpoint "DHW setpoint" {channel="plugwiseha:appliance_boiler:home:living_room_thermostat:dhwSetpoint"} +Number:Temperature main_boiler_maxBoilerTemperature "Max. boiler temperature" {channel="plugwiseha:appliance_boiler:home:living_room_thermostat:maxBoilerTemperature"} +Switch main_boiler_dhwComfortMode "DHW comfort mode" { channel="plugwiseha:appliance_boiler:home:main_boiler:dhwComfortMode"} +``` + +**transform/plugwiseha.map** + +``` +ON=Locked +OFF=Unlocked +``` + +**sitemaps/plugwiseha.sitemap** + +``` +sitemap plugwiseha label="PlugwiseHA Binding" +{ + Frame { + Text item=living_room_zone_temperature + Setpoint item=living_room_zone_temperature_setpoint label="Living room [%.1f °C]" minValue=5.0 maxValue=25 step=0.5 + Text item=living_room_zone_presetScene + Switch item=living_room_zone_preheat + + Text item=living_room_radiator_temperature + Setpoint item=living_room_radiator_temperature_setpoint label="Living room [%.1f °C]" minValue=5.0 maxValue=25 step=0.5 + Text item=living_room_radiator_valve_position + + Text item=living_room_thermostat_temperature + Setpoint item=living_room_thermostat_temperature_setpoint label="Living room [%.1f °C]" minValue=5.0 maxValue=25 step=0.5 + Setpoint item=living_room_thermostat_temperature_offset label="Living room offset [%.1f °C]" minValue=-5.0 maxValue=5 step=0.5 + + Number item=living_room_pump_power_usage + Switch item=living_room_pump_power + Switch item=living_room_pump_lock + + Number item=main_boiler_waterpressure + Switch item=main_boiler_chState + Switch item=main_boiler_dhwState + + Switch item=main_boiler_coolingState + Number item=main_boiler_intendedBoilerTemp + Switch item=main_boiler_flameState + Switch item=main_boiler_intendedHeatingState + Number item=main_boiler_modulationLevel + Number item=main_boiler_otAppFaultCode + Number item=main_boiler_dhwTemperature + Number item=main_boiler_otOEMFaultCode + Number item=main_boiler_boilerTemperature + Number item=main_boiler_dhwSetpoint + Number item=main_boiler_maxBoilerTemperature + Switch item=main_boiler_dhwComfortMode + } +} +``` diff --git a/bundles/org.openhab.binding.plugwiseha/pom.xml b/bundles/org.openhab.binding.plugwiseha/pom.xml new file mode 100644 index 00000000000..9c5ed6edac2 --- /dev/null +++ b/bundles/org.openhab.binding.plugwiseha/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.plugwiseha + + openHAB Add-ons :: Bundles :: PlugwiseHA Binding + + diff --git a/bundles/org.openhab.binding.plugwiseha/src/main/feature/feature.xml b/bundles/org.openhab.binding.plugwiseha/src/main/feature/feature.xml new file mode 100644 index 00000000000..b7d6c84ffb9 --- /dev/null +++ b/bundles/org.openhab.binding.plugwiseha/src/main/feature/feature.xml @@ -0,0 +1,9 @@ + + + mvn:org.openhab.core.features.karaf/org.openhab.core.features.karaf.openhab-core/${ohc.version}/xml/features + + + openhab-runtime-base + mvn:org.openhab.addons.bundles/org.openhab.binding.plugwiseha/${project.version} + + diff --git a/bundles/org.openhab.binding.plugwiseha/src/main/java/org/openhab/binding/plugwiseha/internal/PlugwiseHABindingConstants.java b/bundles/org.openhab.binding.plugwiseha/src/main/java/org/openhab/binding/plugwiseha/internal/PlugwiseHABindingConstants.java new file mode 100644 index 00000000000..cd7e9e6892a --- /dev/null +++ b/bundles/org.openhab.binding.plugwiseha/src/main/java/org/openhab/binding/plugwiseha/internal/PlugwiseHABindingConstants.java @@ -0,0 +1,165 @@ +/** + * 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.plugwiseha.internal; + +import java.util.Set; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.openhab.core.thing.ThingTypeUID; +import org.openhab.core.thing.type.ChannelTypeUID; + +/** + * The {@link PlugwiseHABindingConstants} class defines common constants, which + * are used across the whole binding. + * + * @author Bas van Wetten - Initial contribution + * @author Leo Siepel - finish initial contribution + */ +@NonNullByDefault +public class PlugwiseHABindingConstants { + + public static final String BINDING_ID = "plugwiseha"; + + // List of PlugwiseHA services, related urls, information + + public static final String PLUGWISEHA_API_URL = "http://%s"; + public static final String PLUGWISEHA_API_APPLIANCES_URL = PLUGWISEHA_API_URL + "/core/appliances"; + public static final String PLUGWISEHA_API_APPLIANCE_URL = PLUGWISEHA_API_URL + "/core/appliances;id=%s"; + public static final String PLUGWISEHA_API_LOCATIONS_URL = PLUGWISEHA_API_URL + "/core/locations"; + public static final String PLUGWISEHA_API_LOCATION_URL = PLUGWISEHA_API_URL + "/core/locations;id=%s"; + + // Bridge + public static final ThingTypeUID THING_TYPE_GATEWAY = new ThingTypeUID(BINDING_ID, "gateway"); + + // List of all Thing Type UIDs + public static final ThingTypeUID THING_TYPE_ZONE = new ThingTypeUID(BINDING_ID, "zone"); + public static final ThingTypeUID THING_TYPE_APPLIANCE_VALVE = new ThingTypeUID(BINDING_ID, "appliance_valve"); + public static final ThingTypeUID THING_TYPE_APPLIANCE_PUMP = new ThingTypeUID(BINDING_ID, "appliance_pump"); + public static final ThingTypeUID THING_TYPE_APPLIANCE_THERMOSTAT = new ThingTypeUID(BINDING_ID, + "appliance_thermostat"); + public static final ThingTypeUID THING_TYPE_APPLIANCE_BOILER = new ThingTypeUID(BINDING_ID, "appliance_boiler"); + + // List of channel Type UIDs + public static final ChannelTypeUID CHANNEL_TYPE_BATTERYLEVEL = new ChannelTypeUID("system:battery-level"); + public static final ChannelTypeUID CHANNEL_TYPE_BATTERYLEVELLOW = new ChannelTypeUID("system:low-battery"); + + // Empty set + public static final Set SUPPORTED_INTERFACE_TYPES_UIDS_EMPTY = Set.of(); + + // List of all Gateway configuration properties + public static final String GATEWAY_CONFIG_HOST = "host"; + public static final String GATEWAY_CONFIG_USERNAME = "username"; + public static final String GATEWAY_CONFIG_SMILEID = "smileId"; + public static final String GATEWAY_CONFIG_REFRESH = "refresh"; + + // List of all Zone configuration properties + public static final String ZONE_CONFIG_ID = "id"; + public static final String ZONE_CONFIG_NAME = "zoneName"; + + // List of all Appliance configuration properties + public static final String APPLIANCE_CONFIG_ID = "id"; + public static final String APPLIANCE_CONFIG_NAME = "applianceName"; + public static final String APPLIANCE_CONFIG_LOWBATTERY = "lowBatteryPercentage"; + + // List of all Appliance properties + public static final String APPLIANCE_PROPERTY_DESCRIPTION = "description"; + public static final String APPLIANCE_PROPERTY_TYPE = "type"; + public static final String APPLIANCE_PROPERTY_FUNCTIONALITIES = "functionalities"; + public static final String APPLIANCE_PROPERTY_ZB_TYPE = "zigbee type"; + public static final String APPLIANCE_PROPERTY_ZB_REACHABLE = "zigbee reachable"; + public static final String APPLIANCE_PROPERTY_ZB_POWERSOURCE = "zigboo power source"; + + // List of all Location properties + public static final String LOCATION_PROPERTY_DESCRIPTION = "description"; + public static final String LOCATION_PROPERTY_TYPE = "type"; + public static final String LOCATION_PROPERTY_FUNCTIONALITIES = "functionalities"; + + // List of all Channel IDs + public static final String ZONE_SETPOINT_CHANNEL = "setpointTemperature"; + public static final String ZONE_TEMPERATURE_CHANNEL = "temperature"; + public static final String ZONE_PRESETSCENE_CHANNEL = "presetScene"; + public static final String ZONE_PREHEAT_CHANNEL = "preHeat"; + + public static final String APPLIANCE_SETPOINT_CHANNEL = "setpointTemperature"; + public static final String APPLIANCE_TEMPERATURE_CHANNEL = "temperature"; + public static final String APPLIANCE_BATTERYLEVEL_CHANNEL = "batteryLevel"; + public static final String APPLIANCE_BATTERYLEVELLOW_CHANNEL = "batteryLevelLow"; + public static final String APPLIANCE_POWER_USAGE_CHANNEL = "powerUsage"; + public static final String APPLIANCE_POWER_CHANNEL = "power"; + public static final String APPLIANCE_LOCK_CHANNEL = "lock"; + public static final String APPLIANCE_WATERPRESSURE_CHANNEL = "waterPressure"; + public static final String APPLIANCE_DHWSTATE_CHANNEL = "dhwState"; + public static final String APPLIANCE_CHSTATE_CHANNEL = "chState"; + public static final String APPLIANCE_OFFSET_CHANNEL = "offsetTemperature"; + public static final String APPLIANCE_VALVEPOSITION_CHANNEL = "valvePosition"; + public static final String APPLIANCE_COOLINGSTATE_CHANNEL = "coolingState"; + public static final String APPLIANCE_INTENDEDBOILERTEMP_CHANNEL = "intendedBoilerTemp"; + public static final String APPLIANCE_FLAMESTATE_CHANNEL = "flameState"; + public static final String APPLIANCE_INTENDEDHEATINGSTATE_CHANNEL = "intendedHeatingState"; + public static final String APPLIANCE_MODULATIONLEVEL_CHANNEL = "modulationLevel"; + public static final String APPLIANCE_OTAPPLICATIONFAULTCODE_CHANNEL = "otAppFaultCode"; + public static final String APPLIANCE_DHWTEMPERATURE_CHANNEL = "dhwTemperature"; + public static final String APPLIANCE_OTOEMFAULTCODE_CHANNEL = "otOEMFaultCode"; + public static final String APPLIANCE_BOILERTEMPERATURE_CHANNEL = "boilerTemperature"; + public static final String APPLIANCE_DHWSETPOINT_CHANNEL = "dhwSetpoint"; + public static final String APPLIANCE_MAXBOILERTEMPERATURE_CHANNEL = "maxBoilerTemperature"; + public static final String APPLIANCE_DHWCOMFORTMODE_CHANNEL = "dhwComfortMode"; + + // List of all Appliance Types + public static final String APPLIANCE_TYPE_THERMOSTAT = "thermostat"; + public static final String APPLIANCE_TYPE_GATEWAY = "gateway"; + public static final String APPLIANCE_TYPE_CENTRALHEATINGPUMP = "central_heating_pump"; + public static final String APPLIANCE_TYPE_OPENTHERMGATEWAY = "open_therm_gateway"; + public static final String APPLIANCE_TYPE_ZONETHERMOSTAT = "zone_thermostat"; + public static final String APPLIANCE_TYPE_HEATERCENTRAL = "heater_central"; + public static final String APPLIANCE_TYPE_THERMOSTATICRADIATORVALUE = "thermostatic_radiator_valve"; + + // List of Plugwise Maesure Units + public static final String UNIT_CELSIUS = "C"; + + // Supported things + public static final Set SUPPORTED_THING_TYPES_UIDS = Set.of(THING_TYPE_ZONE, + THING_TYPE_APPLIANCE_VALVE, THING_TYPE_APPLIANCE_PUMP, THING_TYPE_APPLIANCE_BOILER); + + // Appliance types known to binding + public static final Set KNOWN_APPLIANCE_TYPES = Set.of(APPLIANCE_TYPE_THERMOSTAT, APPLIANCE_TYPE_GATEWAY, + APPLIANCE_TYPE_CENTRALHEATINGPUMP, APPLIANCE_TYPE_OPENTHERMGATEWAY, APPLIANCE_TYPE_ZONETHERMOSTAT, + APPLIANCE_TYPE_HEATERCENTRAL, APPLIANCE_TYPE_THERMOSTATICRADIATORVALUE); + + public static final Set SUPPORTED_APPLIANCE_TYPES = Set.of(APPLIANCE_TYPE_CENTRALHEATINGPUMP, + APPLIANCE_TYPE_THERMOSTATICRADIATORVALUE, APPLIANCE_TYPE_ZONETHERMOSTAT, APPLIANCE_TYPE_HEATERCENTRAL); + + // Supported bridges + public static final Set SUPPORTED_BRIDGE_TYPES_UIDS = Set.of(THING_TYPE_GATEWAY); + + // Getters & Setters + public static String getApiUrl(String host) { + return String.format(PLUGWISEHA_API_URL, host); + } + + public static String getAppliancesUrl(String host) { + return String.format(PLUGWISEHA_API_APPLIANCES_URL, host); + } + + public static String getApplianceUrl(String host, String applianceId) { + return String.format(PLUGWISEHA_API_APPLIANCE_URL, host, applianceId); + } + + public static String getLocationsUrl(String host) { + return String.format(PLUGWISEHA_API_LOCATIONS_URL, host); + } + + public static String getLocationUrl(String host, String locationId) { + return String.format(PLUGWISEHA_API_LOCATION_URL, host, locationId); + } +} diff --git a/bundles/org.openhab.binding.plugwiseha/src/main/java/org/openhab/binding/plugwiseha/internal/PlugwiseHAHandlerFactory.java b/bundles/org.openhab.binding.plugwiseha/src/main/java/org/openhab/binding/plugwiseha/internal/PlugwiseHAHandlerFactory.java new file mode 100644 index 00000000000..2d8a8d51c56 --- /dev/null +++ b/bundles/org.openhab.binding.plugwiseha/src/main/java/org/openhab/binding/plugwiseha/internal/PlugwiseHAHandlerFactory.java @@ -0,0 +1,116 @@ +/** + * 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.plugwiseha.internal; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; +import org.eclipse.jetty.client.HttpClient; +import org.openhab.binding.plugwiseha.internal.handler.PlugwiseHAApplianceHandler; +import org.openhab.binding.plugwiseha.internal.handler.PlugwiseHABridgeHandler; +import org.openhab.binding.plugwiseha.internal.handler.PlugwiseHAZoneHandler; +import org.openhab.core.config.core.Configuration; +import org.openhab.core.io.net.http.HttpClientFactory; +import org.openhab.core.thing.Bridge; +import org.openhab.core.thing.Thing; +import org.openhab.core.thing.ThingTypeUID; +import org.openhab.core.thing.ThingUID; +import org.openhab.core.thing.binding.BaseThingHandlerFactory; +import org.openhab.core.thing.binding.ThingHandler; +import org.openhab.core.thing.binding.ThingHandlerFactory; +import org.osgi.service.component.annotations.Activate; +import org.osgi.service.component.annotations.Component; +import org.osgi.service.component.annotations.Reference; + +/** + * The {@link PlugwiseHAHandlerFactory} is responsible for creating things and + * thing handlers. + * + * @author Bas van Wetten - Initial contribution + * @author Leo Siepel - finish initial contribution + * + */ +@NonNullByDefault +@Component(service = ThingHandlerFactory.class, configurationPid = "binding.plugwiseha") +public class PlugwiseHAHandlerFactory extends BaseThingHandlerFactory { + + private final HttpClient httpClient; + + // Constructor + + @Activate + public PlugwiseHAHandlerFactory(@Reference final HttpClientFactory httpClientFactory) { + this.httpClient = httpClientFactory.getCommonHttpClient(); + } + + // Public methods + + /** + * Returns whether the handler is able to create a thing or register a thing + * handler for the given type. + * + * @param thingTypeUID the thing type UID + * @return true, if the handler supports the thing type, false otherwise + */ + @Override + public boolean supportsThingType(ThingTypeUID thingTypeUID) { + return (PlugwiseHABridgeHandler.supportsThingType(thingTypeUID) + || PlugwiseHAZoneHandler.supportsThingType(thingTypeUID)) + || PlugwiseHAApplianceHandler.supportsThingType(thingTypeUID); + } + + /** + * Creates a thing for given arguments. + * + * @param thingTypeUID thing type uid (not null) + * @param configuration configuration + * @param thingUID thing uid, which can be null + * @param bridgeUID bridge uid, which can be null + * @return created thing + */ + @Override + public @Nullable Thing createThing(ThingTypeUID thingTypeUID, Configuration configuration, + @Nullable ThingUID thingUID, @Nullable ThingUID bridgeUID) { + if (PlugwiseHABridgeHandler.supportsThingType(thingTypeUID)) { + return super.createThing(thingTypeUID, configuration, thingUID, null); + } else if (PlugwiseHAZoneHandler.supportsThingType(thingTypeUID)) { + return super.createThing(thingTypeUID, configuration, thingUID, bridgeUID); + } else if (PlugwiseHAApplianceHandler.supportsThingType(thingTypeUID)) { + return super.createThing(thingTypeUID, configuration, thingUID, bridgeUID); + } + + throw new IllegalArgumentException( + "The thing type " + thingTypeUID + " is not supported by the plugwiseha binding."); + } + + // Protected and private methods + + /** + * Creates a {@link ThingHandler} for the given thing. + * + * @param thing the thing + * @return thing the created handler + */ + @Override + protected @Nullable ThingHandler createHandler(Thing thing) { + ThingTypeUID thingTypeUID = thing.getThingTypeUID(); + + if (PlugwiseHABridgeHandler.supportsThingType(thingTypeUID)) { + return new PlugwiseHABridgeHandler((Bridge) thing, this.httpClient); + } else if (PlugwiseHAZoneHandler.supportsThingType(thingTypeUID)) { + return new PlugwiseHAZoneHandler(thing); + } else if (PlugwiseHAApplianceHandler.supportsThingType(thingTypeUID)) { + return new PlugwiseHAApplianceHandler(thing); + } + return null; + } +} diff --git a/bundles/org.openhab.binding.plugwiseha/src/main/java/org/openhab/binding/plugwiseha/internal/api/exception/PlugwiseHABadRequestException.java b/bundles/org.openhab.binding.plugwiseha/src/main/java/org/openhab/binding/plugwiseha/internal/api/exception/PlugwiseHABadRequestException.java new file mode 100644 index 00000000000..19a70b2c52b --- /dev/null +++ b/bundles/org.openhab.binding.plugwiseha/src/main/java/org/openhab/binding/plugwiseha/internal/api/exception/PlugwiseHABadRequestException.java @@ -0,0 +1,39 @@ +/** + * 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.plugwiseha.internal.api.exception; + +import org.eclipse.jdt.annotation.NonNullByDefault; + +/** + * The {@link PlugwiseHABadRequestException} represents a binding specific {@link Exception}. + * + * @author Bas van Wetten - Initial contribution + * @author Leo Siepel - finish initial contribution + */ +@NonNullByDefault +public class PlugwiseHABadRequestException extends PlugwiseHAException { + + private static final long serialVersionUID = 1L; + + public PlugwiseHABadRequestException(String message) { + super(message); + } + + public PlugwiseHABadRequestException(String message, Throwable cause) { + super(message, cause); + } + + public PlugwiseHABadRequestException(Throwable cause) { + super(cause); + } +} diff --git a/bundles/org.openhab.binding.plugwiseha/src/main/java/org/openhab/binding/plugwiseha/internal/api/exception/PlugwiseHACommunicationException.java b/bundles/org.openhab.binding.plugwiseha/src/main/java/org/openhab/binding/plugwiseha/internal/api/exception/PlugwiseHACommunicationException.java new file mode 100644 index 00000000000..699f6c0ae29 --- /dev/null +++ b/bundles/org.openhab.binding.plugwiseha/src/main/java/org/openhab/binding/plugwiseha/internal/api/exception/PlugwiseHACommunicationException.java @@ -0,0 +1,33 @@ +/** + * Copyright (c) 2010-2021 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.binding.plugwiseha.internal.api.exception; + +import org.eclipse.jdt.annotation.NonNullByDefault; + +/** + * The {@link PlugwiseHACommunicationException} represents a binding specific {@link Exception}. + * + * @author Bas van Wetten - Initial contribution + * @author Leo Siepel - finish initial contribution + * + */ + +@NonNullByDefault +public class PlugwiseHACommunicationException extends PlugwiseHAException { + + private static final long serialVersionUID = 1L; + + public PlugwiseHACommunicationException(Throwable cause) { + super(cause); + } +} diff --git a/bundles/org.openhab.binding.plugwiseha/src/main/java/org/openhab/binding/plugwiseha/internal/api/exception/PlugwiseHAException.java b/bundles/org.openhab.binding.plugwiseha/src/main/java/org/openhab/binding/plugwiseha/internal/api/exception/PlugwiseHAException.java new file mode 100644 index 00000000000..a1d76e49a85 --- /dev/null +++ b/bundles/org.openhab.binding.plugwiseha/src/main/java/org/openhab/binding/plugwiseha/internal/api/exception/PlugwiseHAException.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.plugwiseha.internal.api.exception; + +import org.eclipse.jdt.annotation.NonNullByDefault; + +/** + * The {@link PlugwiseHAException} represents a binding specific {@link Exception}. + * + * @author Bas van Wetten - Initial contribution + * @author Leo Siepel - finish initial contribution + * + */ +@NonNullByDefault +public class PlugwiseHAException extends Exception { + + private static final long serialVersionUID = 1L; + + public PlugwiseHAException(String message) { + super(message); + } + + public PlugwiseHAException(String message, Throwable cause) { + super(message, cause); + } + + public PlugwiseHAException(Throwable cause) { + super(cause); + } +} diff --git a/bundles/org.openhab.binding.plugwiseha/src/main/java/org/openhab/binding/plugwiseha/internal/api/exception/PlugwiseHAForbiddenException.java b/bundles/org.openhab.binding.plugwiseha/src/main/java/org/openhab/binding/plugwiseha/internal/api/exception/PlugwiseHAForbiddenException.java new file mode 100644 index 00000000000..68b9af19f0c --- /dev/null +++ b/bundles/org.openhab.binding.plugwiseha/src/main/java/org/openhab/binding/plugwiseha/internal/api/exception/PlugwiseHAForbiddenException.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.plugwiseha.internal.api.exception; + +import org.eclipse.jdt.annotation.NonNullByDefault; + +/** + * The {@link PlugwiseHAForbiddenException} signals the controller denied a request due to invalid credentials. + * + * @author Bas van Wetten - Initial contribution + * @author Leo Siepel - finish initial contribution + * + */ + +@NonNullByDefault +public class PlugwiseHAForbiddenException extends PlugwiseHAException { + + private static final long serialVersionUID = 1L; + + public PlugwiseHAForbiddenException(String message) { + super(message); + } + + public PlugwiseHAForbiddenException(String message, Throwable cause) { + super(message, cause); + } + + public PlugwiseHAForbiddenException(Throwable cause) { + super(cause); + } +} diff --git a/bundles/org.openhab.binding.plugwiseha/src/main/java/org/openhab/binding/plugwiseha/internal/api/exception/PlugwiseHAInvalidHostException.java b/bundles/org.openhab.binding.plugwiseha/src/main/java/org/openhab/binding/plugwiseha/internal/api/exception/PlugwiseHAInvalidHostException.java new file mode 100644 index 00000000000..f56fdeb729d --- /dev/null +++ b/bundles/org.openhab.binding.plugwiseha/src/main/java/org/openhab/binding/plugwiseha/internal/api/exception/PlugwiseHAInvalidHostException.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.plugwiseha.internal.api.exception; + +import org.eclipse.jdt.annotation.NonNullByDefault; + +/** + * The {@link PlugwiseHAInvalidHostException} signals there was a problem with the hostname of the controller. + * + * @author Bas van Wetten - Initial contribution + * @author Leo Siepel - finish initial contribution + * + */ + +@NonNullByDefault +public class PlugwiseHAInvalidHostException extends PlugwiseHAException { + + private static final long serialVersionUID = 1L; + + public PlugwiseHAInvalidHostException(String message) { + super(message); + } + + public PlugwiseHAInvalidHostException(String message, Throwable cause) { + super(message, cause); + } + + public PlugwiseHAInvalidHostException(Throwable cause) { + super(cause); + } +} diff --git a/bundles/org.openhab.binding.plugwiseha/src/main/java/org/openhab/binding/plugwiseha/internal/api/exception/PlugwiseHANotAuthorizedException.java b/bundles/org.openhab.binding.plugwiseha/src/main/java/org/openhab/binding/plugwiseha/internal/api/exception/PlugwiseHANotAuthorizedException.java new file mode 100644 index 00000000000..9bd004e5e9d --- /dev/null +++ b/bundles/org.openhab.binding.plugwiseha/src/main/java/org/openhab/binding/plugwiseha/internal/api/exception/PlugwiseHANotAuthorizedException.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.plugwiseha.internal.api.exception; + +import org.eclipse.jdt.annotation.NonNullByDefault; + +/** + * The {@link PlugwiseHANotAuthorizedException} signals the controller denied a request due to invalid credentials. + * + * @author Bas van Wetten - Initial contribution + * @author Leo Siepel - finish initial contribution + * + */ +@NonNullByDefault +public class PlugwiseHANotAuthorizedException extends PlugwiseHAException { + + private static final long serialVersionUID = 1L; + + public PlugwiseHANotAuthorizedException(String message) { + super(message); + } + + public PlugwiseHANotAuthorizedException(String message, Throwable cause) { + super(message, cause); + } + + public PlugwiseHANotAuthorizedException(Throwable cause) { + super(cause); + } +} diff --git a/bundles/org.openhab.binding.plugwiseha/src/main/java/org/openhab/binding/plugwiseha/internal/api/exception/PlugwiseHATimeoutException.java b/bundles/org.openhab.binding.plugwiseha/src/main/java/org/openhab/binding/plugwiseha/internal/api/exception/PlugwiseHATimeoutException.java new file mode 100644 index 00000000000..c50dfe10e01 --- /dev/null +++ b/bundles/org.openhab.binding.plugwiseha/src/main/java/org/openhab/binding/plugwiseha/internal/api/exception/PlugwiseHATimeoutException.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.plugwiseha.internal.api.exception; + +import org.eclipse.jdt.annotation.NonNullByDefault; + +/** + * The {@link PlugwiseHATimeoutException} represents a binding specific {@link Exception}. + * + * @author Bas van Wetten - Initial contribution + * @author Leo Siepel - finish initial contribution + */ + +@NonNullByDefault +public class PlugwiseHATimeoutException extends PlugwiseHAException { + + private static final long serialVersionUID = 1L; + + public PlugwiseHATimeoutException(String message) { + super(message); + } + + public PlugwiseHATimeoutException(String message, Throwable cause) { + super(message, cause); + } + + public PlugwiseHATimeoutException(Throwable cause) { + super(cause); + } +} diff --git a/bundles/org.openhab.binding.plugwiseha/src/main/java/org/openhab/binding/plugwiseha/internal/api/exception/PlugwiseHAUnauthorizedException.java b/bundles/org.openhab.binding.plugwiseha/src/main/java/org/openhab/binding/plugwiseha/internal/api/exception/PlugwiseHAUnauthorizedException.java new file mode 100644 index 00000000000..56266d58dc2 --- /dev/null +++ b/bundles/org.openhab.binding.plugwiseha/src/main/java/org/openhab/binding/plugwiseha/internal/api/exception/PlugwiseHAUnauthorizedException.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.plugwiseha.internal.api.exception; + +import org.eclipse.jdt.annotation.NonNullByDefault; + +/** + * The {@link PlugwiseHAUnauthorizedException} represents a binding specific {@link Exception}. + * + * @author Bas van Wetten - Initial contribution + * @author Leo Siepel - finish initial contribution + */ + +@NonNullByDefault +public class PlugwiseHAUnauthorizedException extends PlugwiseHAException { + + private static final long serialVersionUID = 1L; + + public PlugwiseHAUnauthorizedException(String message) { + super(message); + } + + public PlugwiseHAUnauthorizedException(String message, Throwable cause) { + super(message, cause); + } + + public PlugwiseHAUnauthorizedException(Throwable cause) { + super(cause); + } +} diff --git a/bundles/org.openhab.binding.plugwiseha/src/main/java/org/openhab/binding/plugwiseha/internal/api/model/PlugwiseHAController.java b/bundles/org.openhab.binding.plugwiseha/src/main/java/org/openhab/binding/plugwiseha/internal/api/model/PlugwiseHAController.java new file mode 100644 index 00000000000..d3bef86f022 --- /dev/null +++ b/bundles/org.openhab.binding.plugwiseha/src/main/java/org/openhab/binding/plugwiseha/internal/api/model/PlugwiseHAController.java @@ -0,0 +1,450 @@ +/** + * 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.plugwiseha.internal.api.model; + +import java.time.ZonedDateTime; +import java.time.format.DateTimeFormatter; +import java.util.Arrays; +import java.util.List; +import java.util.Optional; + +import javax.xml.transform.Transformer; +import javax.xml.transform.TransformerConfigurationException; +import javax.xml.transform.TransformerFactory; +import javax.xml.transform.stream.StreamSource; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; +import org.eclipse.jetty.client.HttpClient; +import org.openhab.binding.plugwiseha.internal.api.exception.PlugwiseHAException; +import org.openhab.binding.plugwiseha.internal.api.model.dto.ActuatorFunctionality; +import org.openhab.binding.plugwiseha.internal.api.model.dto.ActuatorFunctionalityOffsetTemperature; +import org.openhab.binding.plugwiseha.internal.api.model.dto.ActuatorFunctionalityRelay; +import org.openhab.binding.plugwiseha.internal.api.model.dto.ActuatorFunctionalityThermostat; +import org.openhab.binding.plugwiseha.internal.api.model.dto.Appliance; +import org.openhab.binding.plugwiseha.internal.api.model.dto.Appliances; +import org.openhab.binding.plugwiseha.internal.api.model.dto.DomainObjects; +import org.openhab.binding.plugwiseha.internal.api.model.dto.GatewayInfo; +import org.openhab.binding.plugwiseha.internal.api.model.dto.Location; +import org.openhab.binding.plugwiseha.internal.api.model.dto.Locations; +import org.openhab.binding.plugwiseha.internal.api.xml.PlugwiseHAXStream; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * The {@link PlugwiseHAController} class provides the interface to the Plugwise + * Home Automation API and stores/caches the object model for use by the various + * ThingHandlers of this binding. + * + * @author B. van Wetten - Initial contribution + */ +@NonNullByDefault +public class PlugwiseHAController { + + // Private member variables/constants + + private static final int MAX_AGE_MINUTES_REFRESH = 10; + private static final int MAX_AGE_MINUTES_FULL_REFRESH = 30; + private static final DateTimeFormatter FORMAT = DateTimeFormatter.RFC_1123_DATE_TIME; // default Date format that + // will be used in conversion + + private final Logger logger = LoggerFactory.getLogger(PlugwiseHAController.class); + + private final HttpClient httpClient; + private final PlugwiseHAXStream xStream; + private final Transformer domainObjectsTransformer; + + private final String host; + private final int port; + private final String username; + private final String smileId; + + private @Nullable ZonedDateTime gatewayUpdateDateTime; + private @Nullable ZonedDateTime gatewayFullUpdateDateTime; + private @Nullable DomainObjects domainObjects; + + public PlugwiseHAController(HttpClient httpClient, String host, int port, String username, String smileId) + throws PlugwiseHAException { + this.httpClient = httpClient; + this.host = host; + this.port = port; + this.username = username; + this.smileId = smileId; + + this.xStream = new PlugwiseHAXStream(); + + ClassLoader localClassLoader = getClass().getClassLoader(); + if (localClassLoader != null) { + this.domainObjectsTransformer = PlugwiseHAController + .setXSLT(new StreamSource(localClassLoader.getResourceAsStream("domain_objects.xslt"))); + } else { + throw new PlugwiseHAException("PlugwiseHAController.domainObjectsTransformer could not be initialized"); + } + } + + // Public methods + + public void start(Runnable callback) throws PlugwiseHAException { + refresh(); + callback.run(); + } + + public void refresh() throws PlugwiseHAException { + synchronized (this) { + this.getUpdatedDomainObjects(); + } + } + + // Public API methods + + public GatewayInfo getGatewayInfo() throws PlugwiseHAException { + return getGatewayInfo(false); + } + + public GatewayInfo getGatewayInfo(Boolean forceRefresh) throws PlugwiseHAException { + GatewayInfo gatewayInfo = null; + DomainObjects localDomainObjects = this.domainObjects; + if (localDomainObjects != null) { + gatewayInfo = localDomainObjects.getGatewayInfo(); + } + + if (!forceRefresh && gatewayInfo != null) { + this.logger.debug("Found Plugwise Home Automation gateway"); + return gatewayInfo; + } else { + PlugwiseHAControllerRequest request; + + request = newRequest(DomainObjects.class, this.domainObjectsTransformer); + + request.setPath("/core/domain_objects"); + request.addPathParameter("class", "Gateway"); + + DomainObjects domainObjects = executeRequest(request); + this.gatewayUpdateDateTime = ZonedDateTime.parse(request.getServerDateTime(), PlugwiseHAController.FORMAT); + + return mergeDomainObjects(domainObjects).getGatewayInfo(); + } + } + + public Appliances getAppliances(Boolean forceRefresh) throws PlugwiseHAException { + Appliances appliances = null; + DomainObjects localDomainObjects = this.domainObjects; + if (localDomainObjects != null) { + appliances = localDomainObjects.getAppliances(); + } + + if (!forceRefresh && appliances != null) { + return appliances; + } else { + PlugwiseHAControllerRequest request; + + request = newRequest(DomainObjects.class, this.domainObjectsTransformer); + + request.setPath("/core/domain_objects"); + request.addPathParameter("class", "Appliance"); + + DomainObjects domainObjects = executeRequest(request); + this.gatewayUpdateDateTime = ZonedDateTime.parse(request.getServerDateTime(), PlugwiseHAController.FORMAT); + int size = 0; + if (!(domainObjects.getAppliances() == null)) { + size = domainObjects.getAppliances().size(); + } + this.logger.debug("Found {} Plugwise Home Automation appliance(s)", size); + + return mergeDomainObjects(domainObjects).getAppliances(); + } + } + + public @Nullable Appliance getAppliance(String id, Boolean forceRefresh) throws PlugwiseHAException { + Appliances appliances = this.getAppliances(forceRefresh); + if (!appliances.containsKey(id)) { + this.logger.debug("Plugwise Home Automation Appliance with id {} is not known", id); + return null; + } else { + return appliances.get(id); + } + } + + public Locations getLocations(Boolean forceRefresh) throws PlugwiseHAException { + Locations locations = null; + DomainObjects localDomainObjects = this.domainObjects; + if (localDomainObjects != null) { + locations = localDomainObjects.getLocations(); + } + + if (!forceRefresh && locations != null) { + return locations; + } else { + PlugwiseHAControllerRequest request; + + request = newRequest(DomainObjects.class, this.domainObjectsTransformer); + + request.setPath("/core/domain_objects"); + request.addPathParameter("class", "Location"); + + DomainObjects domainObjects = executeRequest(request); + this.gatewayUpdateDateTime = ZonedDateTime.parse(request.getServerDateTime(), PlugwiseHAController.FORMAT); + int size = 0; + if (!(domainObjects.getLocations() == null)) { + size = domainObjects.getLocations().size(); + } + this.logger.debug("Found {} Plugwise Home Automation Zone(s)", size); + return mergeDomainObjects(domainObjects).getLocations(); + } + } + + public @Nullable Location getLocation(String id, Boolean forceRefresh) throws PlugwiseHAException { + Locations locations = this.getLocations(forceRefresh); + + if (!locations.containsKey(id)) { + this.logger.debug("Plugwise Home Automation Zone with {} is not known", id); + return null; + } else { + return locations.get(id); + } + } + + public @Nullable DomainObjects getDomainObjects() throws PlugwiseHAException { + PlugwiseHAControllerRequest request; + + request = newRequest(DomainObjects.class, this.domainObjectsTransformer); + + request.setPath("/core/domain_objects"); + request.addPathParameter("@locale", "en-US"); + + DomainObjects domainObjects = executeRequest(request); + this.gatewayUpdateDateTime = ZonedDateTime.parse(request.getServerDateTime(), PlugwiseHAController.FORMAT); + this.gatewayFullUpdateDateTime = this.gatewayUpdateDateTime; + + return mergeDomainObjects(domainObjects); + } + + public @Nullable DomainObjects getUpdatedDomainObjects() throws PlugwiseHAException { + ZonedDateTime localGatewayUpdateDateTime = this.gatewayUpdateDateTime; + ZonedDateTime localGatewayFullUpdateDateTime = this.gatewayFullUpdateDateTime; + if (localGatewayUpdateDateTime == null + || localGatewayUpdateDateTime.isBefore(ZonedDateTime.now().minusMinutes(MAX_AGE_MINUTES_REFRESH))) { + return getDomainObjects(); + } else if (localGatewayFullUpdateDateTime == null || localGatewayFullUpdateDateTime + .isBefore(ZonedDateTime.now().minusMinutes(MAX_AGE_MINUTES_FULL_REFRESH))) { + return getDomainObjects(); + } else { + return getUpdatedDomainObjects(localGatewayUpdateDateTime); + } + } + + public @Nullable DomainObjects getUpdatedDomainObjects(ZonedDateTime since) throws PlugwiseHAException { + return getUpdatedDomainObjects(since.toEpochSecond()); + } + + public @Nullable DomainObjects getUpdatedDomainObjects(Long since) throws PlugwiseHAException { + PlugwiseHAControllerRequest request; + + request = newRequest(DomainObjects.class, this.domainObjectsTransformer); + + request.setPath("/core/domain_objects"); + request.addPathFilter("modified_date", "ge", since); + request.addPathFilter("deleted_date", "ge", "0"); + request.addPathParameter("@memberModifiedDate", since); + request.addPathParameter("@locale", "en-US"); + + DomainObjects domainObjects = executeRequest(request); + this.gatewayUpdateDateTime = ZonedDateTime.parse(request.getServerDateTime(), PlugwiseHAController.FORMAT); + + return mergeDomainObjects(domainObjects); + } + + public void setLocationThermostat(Location location, Double temperature) throws PlugwiseHAException { + PlugwiseHAControllerRequest request = newRequest(Void.class); + Optional thermostat = location.getActuatorFunctionalities().getFunctionalityThermostat(); + + if (thermostat.isPresent()) { + request.setPath("/core/locations"); + + request.addPathParameter("id", String.format("%s/thermostat", location.getId())); + request.addPathParameter("id", String.format("%s", thermostat.get().getId())); + request.setBodyParameter(new ActuatorFunctionalityThermostat(temperature)); + + executeRequest(request); + } + } + + public void setThermostat(Appliance appliance, Double temperature) throws PlugwiseHAException { + PlugwiseHAControllerRequest request = newRequest(Void.class); + Optional thermostat = appliance.getActuatorFunctionalities() + .getFunctionalityThermostat(); + + if (thermostat.isPresent()) { + request.setPath("/core/appliances"); + + request.addPathParameter("id", String.format("%s/thermostat", appliance.getId())); + request.addPathParameter("id", String.format("%s", thermostat.get().getId())); + request.setBodyParameter(new ActuatorFunctionalityThermostat(temperature)); + + executeRequest(request); + } + } + + public void setOffsetTemperature(Appliance appliance, Double temperature) throws PlugwiseHAException { + PlugwiseHAControllerRequest request = newRequest(Void.class); + Optional offsetTemperatureFunctionality = appliance.getActuatorFunctionalities() + .getFunctionalityOffsetTemperature(); + + if (offsetTemperatureFunctionality.isPresent()) { + request.setPath("/core/appliances"); + + request.addPathParameter("id", String.format("%s/offset", appliance.getId())); + request.addPathParameter("id", String.format("%s", offsetTemperatureFunctionality.get().getId())); + request.setBodyParameter(new ActuatorFunctionalityOffsetTemperature(temperature)); + + executeRequest(request); + } + } + + public void switchRelay(Appliance appliance, String state) throws PlugwiseHAException { + List allowStates = Arrays.asList("on", "off"); + if (allowStates.contains(state.toLowerCase())) { + if (state.toLowerCase().equals("on")) { + switchRelayOn(appliance); + } else { + switchRelayOff(appliance); + } + } + } + + public void setPreHeating(Location location, Boolean state) throws PlugwiseHAException { + PlugwiseHAControllerRequest request = newRequest(Void.class); + Optional thermostat = location.getActuatorFunctionalities().getFunctionalityThermostat(); + + request.setPath("/core/locations"); + request.addPathParameter("id", String.format("%s/thermostat", location.getId())); + request.addPathParameter("id", String.format("%s", thermostat.get().getId())); + request.setBodyParameter(new ActuatorFunctionalityThermostat(state)); + + executeRequest(request); + } + + public void switchRelayOn(Appliance appliance) throws PlugwiseHAException { + PlugwiseHAControllerRequest request = newRequest(Void.class); + + request.setPath("/core/appliances"); + request.addPathParameter("id", String.format("%s/relay", appliance.getId())); + request.setBodyParameter(new ActuatorFunctionalityRelay("on")); + + executeRequest(request); + } + + public void switchRelayOff(Appliance appliance) throws PlugwiseHAException { + PlugwiseHAControllerRequest request = newRequest(Void.class); + + request.setPath("/core/appliances"); + request.addPathParameter("id", String.format("%s/relay", appliance.getId())); + request.setBodyParameter(new ActuatorFunctionalityRelay("off")); + + executeRequest(request); + } + + public void switchRelayLock(Appliance appliance, String state) throws PlugwiseHAException { + List allowStates = Arrays.asList("on", "off"); + if (allowStates.contains(state.toLowerCase())) { + if (state.toLowerCase().equals("on")) { + switchRelayLockOn(appliance); + } else { + switchRelayLockOff(appliance); + } + } + } + + public void switchRelayLockOff(Appliance appliance) throws PlugwiseHAException { + PlugwiseHAControllerRequest request = newRequest(Void.class); + + request.setPath("/core/appliances"); + request.addPathParameter("id", String.format("%s/relay", appliance.getId())); + request.setBodyParameter(new ActuatorFunctionalityRelay(null, false)); + + executeRequest(request); + } + + public void switchRelayLockOn(Appliance appliance) throws PlugwiseHAException { + PlugwiseHAControllerRequest request = newRequest(Void.class); + + request.setPath("/core/appliances"); + request.addPathParameter("id", String.format("%s/relay", appliance.getId())); + request.setBodyParameter(new ActuatorFunctionalityRelay(null, true)); + + executeRequest(request); + } + + public ZonedDateTime ping() throws PlugwiseHAException { + PlugwiseHAControllerRequest request; + + request = newRequest(Void.class, null); + + request.setPath("/cache/gateways"); + request.addPathParameter("ping"); + + executeRequest(request); + + return ZonedDateTime.parse(request.getServerDateTime(), PlugwiseHAController.FORMAT); + } + + // Protected and private methods + + private static Transformer setXSLT(StreamSource xsltSource) throws PlugwiseHAException { + try { + return TransformerFactory.newInstance().newTransformer(xsltSource); + } catch (TransformerConfigurationException e) { + throw new PlugwiseHAException("Could not create XML transformer", e); + } + } + + private PlugwiseHAControllerRequest newRequest(Class responseType, @Nullable Transformer transformer) { + return new PlugwiseHAControllerRequest(responseType, this.xStream, transformer, this.httpClient, this.host, + this.port, this.username, this.smileId); + } + + private PlugwiseHAControllerRequest newRequest(Class responseType) { + return new PlugwiseHAControllerRequest(responseType, this.xStream, null, this.httpClient, this.host, + this.port, this.username, this.smileId); + } + + @SuppressWarnings("null") + private T executeRequest(PlugwiseHAControllerRequest request) throws PlugwiseHAException { + T result; + result = request.execute(); + return result; + } + + private DomainObjects mergeDomainObjects(@Nullable DomainObjects updatedDomainObjects) { + DomainObjects localDomainObjects = this.domainObjects; + if (localDomainObjects == null && updatedDomainObjects != null) { + return updatedDomainObjects; + } else if (localDomainObjects != null && updatedDomainObjects == null) { + return localDomainObjects; + } else if (localDomainObjects != null && updatedDomainObjects != null) { + Appliances appliances = updatedDomainObjects.getAppliances(); + Locations locations = updatedDomainObjects.getLocations(); + + if (appliances != null) { + localDomainObjects.mergeAppliances(appliances); + } + + if (locations != null) { + localDomainObjects.mergeLocations(locations); + } + return localDomainObjects; + } else { + return new DomainObjects(); + } + } +} diff --git a/bundles/org.openhab.binding.plugwiseha/src/main/java/org/openhab/binding/plugwiseha/internal/api/model/PlugwiseHAControllerRequest.java b/bundles/org.openhab.binding.plugwiseha/src/main/java/org/openhab/binding/plugwiseha/internal/api/model/PlugwiseHAControllerRequest.java new file mode 100644 index 00000000000..f8d047fc831 --- /dev/null +++ b/bundles/org.openhab.binding.plugwiseha/src/main/java/org/openhab/binding/plugwiseha/internal/api/model/PlugwiseHAControllerRequest.java @@ -0,0 +1,286 @@ +/** + * 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.plugwiseha.internal.api.model; + +import java.io.StringReader; +import java.io.StringWriter; +import java.net.ConnectException; +import java.net.SocketTimeoutException; +import java.net.UnknownHostException; +import java.nio.charset.StandardCharsets; +import java.util.Base64; +import java.util.HashMap; +import java.util.Map; +import java.util.Map.Entry; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; +import java.util.stream.Collectors; + +import javax.xml.transform.Transformer; +import javax.xml.transform.TransformerException; +import javax.xml.transform.stream.StreamResult; +import javax.xml.transform.stream.StreamSource; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; +import org.eclipse.jetty.client.HttpClient; +import org.eclipse.jetty.client.api.ContentProvider; +import org.eclipse.jetty.client.api.ContentResponse; +import org.eclipse.jetty.client.api.Request; +import org.eclipse.jetty.client.util.StringContentProvider; +import org.eclipse.jetty.http.HttpHeader; +import org.eclipse.jetty.http.HttpMethod; +import org.eclipse.jetty.http.HttpScheme; +import org.eclipse.jetty.http.HttpStatus; +import org.eclipse.jetty.http.HttpURI; +import org.eclipse.jetty.http.MimeTypes; +import org.openhab.binding.plugwiseha.internal.api.exception.PlugwiseHABadRequestException; +import org.openhab.binding.plugwiseha.internal.api.exception.PlugwiseHAException; +import org.openhab.binding.plugwiseha.internal.api.exception.PlugwiseHAForbiddenException; +import org.openhab.binding.plugwiseha.internal.api.exception.PlugwiseHATimeoutException; +import org.openhab.binding.plugwiseha.internal.api.exception.PlugwiseHAUnauthorizedException; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.thoughtworks.xstream.XStream; + +/** + * The {@link PlugwiseHAControllerRequest} class is a utility class to create + * API requests to the Plugwise Home Automation controller and to deserialize + * incoming XML into the appropriate model objects to be used by the {@link + * PlugwiseHAController}. + * + * @author B. van Wetten - Initial contribution + */ +@NonNullByDefault +public class PlugwiseHAControllerRequest { + + private static final String CONTENT_TYPE_TEXT_XML = MimeTypes.Type.TEXT_XML_8859_1.toString(); + private static final long TIMEOUT_SECONDS = 5; + + private final Logger logger = LoggerFactory.getLogger(PlugwiseHAControllerRequest.class); + private final XStream xStream; + private final HttpClient httpClient; + private final String host; + private final int port; + private final Class resultType; + private final @Nullable Transformer transformer; + + private Map headers = new HashMap<>(); + private Map queryParameters = new HashMap<>(); + private @Nullable Object bodyParameter; + private String serverDateTime = ""; + private String path = "/"; + + // Constructor + + PlugwiseHAControllerRequest(Class resultType, X xStream, @Nullable Transformer transformer, + HttpClient httpClient, String host, int port, String username, String password) { + this.resultType = resultType; + this.xStream = xStream; + this.transformer = transformer; + this.httpClient = httpClient; + this.host = host; + this.port = port; + + setHeader(HttpHeader.ACCEPT.toString(), CONTENT_TYPE_TEXT_XML); + + // Create Basic Auth header if username and password are supplied + if (!username.isBlank() && !password.isBlank()) { + setHeader(HttpHeader.AUTHORIZATION.toString(), "Basic " + Base64.getEncoder() + .encodeToString(String.format("%s:%s", username, password).getBytes(StandardCharsets.UTF_8))); + } + } + + // Public methods + + public void setPath(String path) { + this.setPath(path, (HashMap) null); + } + + public void setPath(String path, @Nullable HashMap pathParameters) { + this.path = path; + + if (pathParameters != null) { + this.path += pathParameters.entrySet().stream().map(Object::toString).collect(Collectors.joining(";")); + } + } + + public void setHeader(String key, Object value) { + this.headers.put(key, String.valueOf(value)); + } + + public void addPathParameter(String key) { + this.path += String.format(";%s", key); + } + + public void addPathParameter(String key, Object value) { + this.path += String.format(";%s=%s", key, value); + } + + public void addPathFilter(String key, String operator, Object value) { + this.path += String.format(";%s:%s:%s", key, operator, value); + } + + public void setQueryParameter(String key, Object value) { + this.queryParameters.put(key, String.valueOf(value)); + } + + public void setBodyParameter(Object body) { + this.bodyParameter = body; + } + + public String getServerDateTime() { + return this.serverDateTime; + } + + @SuppressWarnings("unchecked") + public @Nullable T execute() throws PlugwiseHAException { + T result; + String xml = getContent(); + + if (String.class.equals(resultType)) { + if (this.transformer != null) { + result = (T) this.transformXML(xml); + } else { + result = (T) xml; + } + } else if (!Void.class.equals(resultType)) { + if (this.transformer != null) { + result = (T) this.xStream.fromXML(this.transformXML(xml)); + } else { + result = (T) this.xStream.fromXML(xml); + } + } else { + return null; + } + + return result; + } + + // Protected and private methods + + private String transformXML(String xml) throws PlugwiseHAException { + StringReader input = new StringReader(xml); + StringWriter output = new StringWriter(); + Transformer localTransformer = this.transformer; + if (localTransformer != null) { + try { + localTransformer.transform(new StreamSource(input), new StreamResult(output)); + } catch (TransformerException e) { + logger.debug("Could not apply XML stylesheet", e); + throw new PlugwiseHAException("Could not apply XML stylesheet", e); + } + } else { + throw new PlugwiseHAException("Could not transform XML stylesheet, the transformer is null"); + } + + return output.toString(); + } + + private String getContent() throws PlugwiseHAException { + String content; + ContentResponse response; + + try { + response = getContentResponse(); + } catch (PlugwiseHATimeoutException e) { + // Retry + response = getContentResponse(); + } + + int status = response.getStatus(); + switch (status) { + case HttpStatus.OK_200: + case HttpStatus.ACCEPTED_202: + content = response.getContentAsString(); + if (logger.isTraceEnabled()) { + logger.trace("<< {} {} \n{}", status, HttpStatus.getMessage(status), content); + } + break; + case HttpStatus.BAD_REQUEST_400: + throw new PlugwiseHABadRequestException("Bad request"); + case HttpStatus.UNAUTHORIZED_401: + throw new PlugwiseHAUnauthorizedException("Unauthorized"); + case HttpStatus.FORBIDDEN_403: + throw new PlugwiseHAForbiddenException("Forbidden"); + default: + throw new PlugwiseHAException("Unknown HTTP status code " + status + " returned by the controller"); + } + + this.serverDateTime = response.getHeaders().get("Date"); + + return content; + } + + private ContentResponse getContentResponse() throws PlugwiseHAException { + Request request = newRequest(); + ContentResponse response; + + if (logger.isTraceEnabled()) { + logger.trace(">> {} {}", request.getMethod(), request.getURI()); + } + + try { + response = request.send(); + } catch (TimeoutException | InterruptedException e) { + throw new PlugwiseHATimeoutException(e); + } catch (ExecutionException e) { + // Unwrap the cause and try to cleanly handle it + Throwable cause = e.getCause(); + if (cause instanceof UnknownHostException) { + // Invalid hostname + throw new PlugwiseHAException(cause); + } else if (cause instanceof ConnectException) { + // Cannot connect + throw new PlugwiseHAException(cause); + } else if (cause instanceof SocketTimeoutException) { + throw new PlugwiseHATimeoutException(cause); + } else if (cause == null) { + // Unable to unwrap + throw new PlugwiseHAException(e); + } else { + // Catch all + throw new PlugwiseHAException(cause); + } + } + return response; + } + + private Request newRequest() { + HttpMethod method = bodyParameter == null ? HttpMethod.GET : HttpMethod.PUT; + HttpURI uri = new HttpURI(HttpScheme.HTTP.asString(), this.host, this.port, this.path); + Request request = httpClient.newRequest(uri.toString()).timeout(TIMEOUT_SECONDS, TimeUnit.SECONDS) + .method(method); + + for (Entry entry : this.headers.entrySet()) { + request.header(entry.getKey(), entry.getValue()); + } + + for (Entry entry : this.queryParameters.entrySet()) { + request.param(entry.getKey(), entry.getValue()); + } + + if (this.bodyParameter != null) { + String xmlBody = getRequestBodyAsXml(); + ContentProvider content = new StringContentProvider(CONTENT_TYPE_TEXT_XML, xmlBody, StandardCharsets.UTF_8); + request = request.content(content); + } + return request; + } + + private String getRequestBodyAsXml() { + return this.xStream.toXML(this.bodyParameter); + } +} diff --git a/bundles/org.openhab.binding.plugwiseha/src/main/java/org/openhab/binding/plugwiseha/internal/api/model/PlugwiseHAModel.java b/bundles/org.openhab.binding.plugwiseha/src/main/java/org/openhab/binding/plugwiseha/internal/api/model/PlugwiseHAModel.java new file mode 100644 index 00000000000..50fd784a1e2 --- /dev/null +++ b/bundles/org.openhab.binding.plugwiseha/src/main/java/org/openhab/binding/plugwiseha/internal/api/model/PlugwiseHAModel.java @@ -0,0 +1,28 @@ +/** + * 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.plugwiseha.internal.api.model; + +import org.eclipse.jdt.annotation.NonNullByDefault; + +/** + * The {@link PlugwiseHAModel} interface describes common + * methods that need to be implemented by any object model class. + * + * @author B. van Wetten - Initial contribution + */ + +@NonNullByDefault +public interface PlugwiseHAModel { + + public abstract boolean isBatteryOperated(); +} diff --git a/bundles/org.openhab.binding.plugwiseha/src/main/java/org/openhab/binding/plugwiseha/internal/api/model/converter/DateTimeConverter.java b/bundles/org.openhab.binding.plugwiseha/src/main/java/org/openhab/binding/plugwiseha/internal/api/model/converter/DateTimeConverter.java new file mode 100644 index 00000000000..03599214dc1 --- /dev/null +++ b/bundles/org.openhab.binding.plugwiseha/src/main/java/org/openhab/binding/plugwiseha/internal/api/model/converter/DateTimeConverter.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.plugwiseha.internal.api.model.converter; + +import java.time.ZonedDateTime; +import java.time.format.DateTimeFormatter; +import java.time.format.DateTimeParseException; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.thoughtworks.xstream.converters.basic.AbstractSingleValueConverter; + +/** + * The {@link DateTimeConverter} provides a SingleValueConverter for use by XStream when converting + * XML documents with a zoned date/time field. + * + * @author B. van Wetten - Initial contribution + */ + +@NonNullByDefault +public class DateTimeConverter extends AbstractSingleValueConverter { + + private final Logger logger = LoggerFactory.getLogger(DateTimeConverter.class); + private static final DateTimeFormatter FORMAT = DateTimeFormatter.ISO_OFFSET_DATE_TIME; // default Date format that + + @Override + public boolean canConvert(@Nullable @SuppressWarnings("rawtypes") Class type) { + if (type == null) { + return false; + } + return ZonedDateTime.class.isAssignableFrom(type); + } + + @Override + public @Nullable ZonedDateTime fromString(@Nullable String str) { + if (str == null || str.isBlank()) { + return null; + } + + try { + ZonedDateTime dateTime = ZonedDateTime.parse(str, DateTimeConverter.FORMAT); + return dateTime; + } catch (DateTimeParseException e) { + logger.debug("Invalid datetime format in {}", str); + return null; + } + } + + public String toString(ZonedDateTime dateTime) { + return dateTime.format(DateTimeConverter.FORMAT); + } +} diff --git a/bundles/org.openhab.binding.plugwiseha/src/main/java/org/openhab/binding/plugwiseha/internal/api/model/dto/ActuatorFunctionalities.java b/bundles/org.openhab.binding.plugwiseha/src/main/java/org/openhab/binding/plugwiseha/internal/api/model/dto/ActuatorFunctionalities.java new file mode 100644 index 00000000000..80e55b0e4ae --- /dev/null +++ b/bundles/org.openhab.binding.plugwiseha/src/main/java/org/openhab/binding/plugwiseha/internal/api/model/dto/ActuatorFunctionalities.java @@ -0,0 +1,73 @@ +/** + * 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.plugwiseha.internal.api.model.dto; + +import java.util.Map; +import java.util.Optional; + +/** + * The {@link ActuatorFunctionalities} class is an object model class that + * mirrors the XML structure provided by the Plugwise Home Automation controller + * for the collection of actuator functionalities. (e.g. 'offset', 'relay', et + * cetera). It extends the {@link CustomCollection} class. + * + * @author B. van Wetten - Initial contribution + */ + +public class ActuatorFunctionalities extends PlugwiseHACollection { + + private static final String THERMOSTAT_FUNCTIONALITY = "thermostat"; + private static final String OFFSETTEMPERATURE_FUNCTIONALITY = "temperature_offset"; + private static final String RELAY_FUNCTIONALITY = "relay"; + + public Optional getRelayLockState() { + return this.getFunctionalityRelay().flatMap(ActuatorFunctionality::getRelayLockState) + .map(Boolean::parseBoolean); + } + + public Optional getPreHeatState() { + return this.getFunctionalityThermostat().flatMap(ActuatorFunctionality::getPreHeatState) + .map(Boolean::parseBoolean); + } + + public Optional getFunctionalityThermostat() { + return Optional.ofNullable(this.get(THERMOSTAT_FUNCTIONALITY)); + } + + public Optional getFunctionalityOffsetTemperature() { + return Optional.ofNullable(this.get(OFFSETTEMPERATURE_FUNCTIONALITY)); + } + + public Optional getFunctionalityRelay() { + return Optional.ofNullable(this.get(RELAY_FUNCTIONALITY)); + } + + @Override + public void merge(Map actuatorFunctionalities) { + if (actuatorFunctionalities != null) { + for (ActuatorFunctionality actuatorFunctionality : actuatorFunctionalities.values()) { + String type = actuatorFunctionality.getType(); + ActuatorFunctionality originalActuatorFunctionality = this.get(type); + + Boolean originalIsOlder = false; + if (originalActuatorFunctionality != null) { + originalIsOlder = originalActuatorFunctionality.isOlderThan(actuatorFunctionality); + } + + if (originalActuatorFunctionality == null || originalIsOlder) { + this.put(type, actuatorFunctionality); + } + } + } + } +} diff --git a/bundles/org.openhab.binding.plugwiseha/src/main/java/org/openhab/binding/plugwiseha/internal/api/model/dto/ActuatorFunctionality.java b/bundles/org.openhab.binding.plugwiseha/src/main/java/org/openhab/binding/plugwiseha/internal/api/model/dto/ActuatorFunctionality.java new file mode 100644 index 00000000000..2dac9c760b4 --- /dev/null +++ b/bundles/org.openhab.binding.plugwiseha/src/main/java/org/openhab/binding/plugwiseha/internal/api/model/dto/ActuatorFunctionality.java @@ -0,0 +1,111 @@ +/** + * 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.plugwiseha.internal.api.model.dto; + +import java.time.ZonedDateTime; +import java.util.Optional; + +import com.thoughtworks.xstream.annotations.XStreamAlias; + +/** + * The {@link ActuatorFunctionality} class is an object model class that mirrors + * the XML structure provided by the Plugwise Home Automation controller for the + * any actuator functionality. It implements the {@link PlugwiseComparableDate} + * interface and extends the abstract class {@link PlugwiseBaseModel}. + * + * @author B. van Wetten - Initial contribution + * @author Leo Siepel - finish initial contribution + */ +@XStreamAlias("actuator_functionality") +public class ActuatorFunctionality extends PlugwiseBaseModel implements PlugwiseComparableDate { + + private String type; + private String duration; + private String setpoint; + private String resolution; + private String lock; + + @XStreamAlias("preheating_allowed") + private String preHeat; + + @XStreamAlias("lower_bound") + private String lowerBound; + + @XStreamAlias("upper_bound") + private String upperBound; + + @XStreamAlias("updated_date") + private ZonedDateTime updatedDate; + + public String getType() { + return type; + } + + public String getDuration() { + return duration; + } + + public String getSetpoint() { + return setpoint; + } + + public String getResolution() { + return resolution; + } + + public String getLowerBound() { + return lowerBound; + } + + public String getUpperBound() { + return upperBound; + } + + public ZonedDateTime getUpdatedDate() { + return updatedDate; + } + + public Optional getPreHeatState() { + return Optional.ofNullable(preHeat); + } + + public Optional getRelayLockState() { + return Optional.ofNullable(lock); + } + + @Override + public int compareDateWith(ActuatorFunctionality compareTo) { + if (compareTo == null) { + return -1; + } + ZonedDateTime compareToDate = compareTo.getModifiedDate(); + ZonedDateTime compareFromDate = this.getModifiedDate(); + if (compareFromDate == null) { + return -1; + } else if (compareToDate == null) { + return 1; + } else { + return compareFromDate.compareTo(compareToDate); + } + } + + @Override + public boolean isNewerThan(ActuatorFunctionality hasModifiedDate) { + return compareDateWith(hasModifiedDate) > 0; + } + + @Override + public boolean isOlderThan(ActuatorFunctionality hasModifiedDate) { + return compareDateWith(hasModifiedDate) < 0; + } +} diff --git a/bundles/org.openhab.binding.plugwiseha/src/main/java/org/openhab/binding/plugwiseha/internal/api/model/dto/ActuatorFunctionalityOffsetTemperature.java b/bundles/org.openhab.binding.plugwiseha/src/main/java/org/openhab/binding/plugwiseha/internal/api/model/dto/ActuatorFunctionalityOffsetTemperature.java new file mode 100644 index 00000000000..15169c419ac --- /dev/null +++ b/bundles/org.openhab.binding.plugwiseha/src/main/java/org/openhab/binding/plugwiseha/internal/api/model/dto/ActuatorFunctionalityOffsetTemperature.java @@ -0,0 +1,29 @@ +/** + * 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.plugwiseha.internal.api.model.dto; + +import com.thoughtworks.xstream.annotations.XStreamAlias; + +/** + * @author B. van Wetten - Initial contribution + */ +@XStreamAlias("offset_functionality") +public class ActuatorFunctionalityOffsetTemperature extends ActuatorFunctionality { + + @SuppressWarnings("unused") + private Double offset; + + public ActuatorFunctionalityOffsetTemperature(Double temperature) { + this.offset = temperature; + } +} diff --git a/bundles/org.openhab.binding.plugwiseha/src/main/java/org/openhab/binding/plugwiseha/internal/api/model/dto/ActuatorFunctionalityRelay.java b/bundles/org.openhab.binding.plugwiseha/src/main/java/org/openhab/binding/plugwiseha/internal/api/model/dto/ActuatorFunctionalityRelay.java new file mode 100644 index 00000000000..aa0e7152343 --- /dev/null +++ b/bundles/org.openhab.binding.plugwiseha/src/main/java/org/openhab/binding/plugwiseha/internal/api/model/dto/ActuatorFunctionalityRelay.java @@ -0,0 +1,36 @@ +/** + * 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.plugwiseha.internal.api.model.dto; + +import com.thoughtworks.xstream.annotations.XStreamAlias; + +/** + * @author B. van Wetten - Initial contribution + */ +@XStreamAlias("relay_functionality") +public class ActuatorFunctionalityRelay extends ActuatorFunctionality { + + @SuppressWarnings("unused") + private String state; + @SuppressWarnings("unused") + private Boolean lock; + + public ActuatorFunctionalityRelay(String state) { + this.state = state; + } + + public ActuatorFunctionalityRelay(String state, Boolean lock) { + this.state = state; + this.lock = lock; + } +} diff --git a/bundles/org.openhab.binding.plugwiseha/src/main/java/org/openhab/binding/plugwiseha/internal/api/model/dto/ActuatorFunctionalityThermostat.java b/bundles/org.openhab.binding.plugwiseha/src/main/java/org/openhab/binding/plugwiseha/internal/api/model/dto/ActuatorFunctionalityThermostat.java new file mode 100644 index 00000000000..638df4f00d5 --- /dev/null +++ b/bundles/org.openhab.binding.plugwiseha/src/main/java/org/openhab/binding/plugwiseha/internal/api/model/dto/ActuatorFunctionalityThermostat.java @@ -0,0 +1,38 @@ +/** + * 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.plugwiseha.internal.api.model.dto; + +import com.thoughtworks.xstream.annotations.XStreamAlias; + +/** + * @author B. van Wetten - Initial contribution + * @author Leo Siepel - finish initial contribution + */ +@XStreamAlias("thermostat_functionality") +public class ActuatorFunctionalityThermostat extends ActuatorFunctionality { + + @SuppressWarnings("unused") + private Double setpoint; + + @SuppressWarnings("unused") + @XStreamAlias("preheating_allowed") + private Boolean preheatingAllowed; + + public ActuatorFunctionalityThermostat(Double temperature) { + this.setpoint = temperature; + } + + public ActuatorFunctionalityThermostat(Boolean preheatingAllowed) { + this.preheatingAllowed = preheatingAllowed; + } +} diff --git a/bundles/org.openhab.binding.plugwiseha/src/main/java/org/openhab/binding/plugwiseha/internal/api/model/dto/ActuatorFunctionalityThreshold.java b/bundles/org.openhab.binding.plugwiseha/src/main/java/org/openhab/binding/plugwiseha/internal/api/model/dto/ActuatorFunctionalityThreshold.java new file mode 100644 index 00000000000..ee107576b17 --- /dev/null +++ b/bundles/org.openhab.binding.plugwiseha/src/main/java/org/openhab/binding/plugwiseha/internal/api/model/dto/ActuatorFunctionalityThreshold.java @@ -0,0 +1,25 @@ +/** + * 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.plugwiseha.internal.api.model.dto; + +import com.thoughtworks.xstream.annotations.XStreamAlias; + +/** + * @author B. van Wetten - Initial contribution + */ +@XStreamAlias("threshold_functionality") +public class ActuatorFunctionalityThreshold extends ActuatorFunctionality { + + public ActuatorFunctionalityThreshold() { + } +} diff --git a/bundles/org.openhab.binding.plugwiseha/src/main/java/org/openhab/binding/plugwiseha/internal/api/model/dto/ActuatorFunctionalityTimer.java b/bundles/org.openhab.binding.plugwiseha/src/main/java/org/openhab/binding/plugwiseha/internal/api/model/dto/ActuatorFunctionalityTimer.java new file mode 100644 index 00000000000..822f2e9b1c2 --- /dev/null +++ b/bundles/org.openhab.binding.plugwiseha/src/main/java/org/openhab/binding/plugwiseha/internal/api/model/dto/ActuatorFunctionalityTimer.java @@ -0,0 +1,25 @@ +/** + * 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.plugwiseha.internal.api.model.dto; + +import com.thoughtworks.xstream.annotations.XStreamAlias; + +/** + * @author B. van Wetten - Initial contribution + */ +@XStreamAlias("timer_functionality") +public class ActuatorFunctionalityTimer extends ActuatorFunctionality { + + public ActuatorFunctionalityTimer() { + } +} diff --git a/bundles/org.openhab.binding.plugwiseha/src/main/java/org/openhab/binding/plugwiseha/internal/api/model/dto/ActuatorFunctionalityToggle.java b/bundles/org.openhab.binding.plugwiseha/src/main/java/org/openhab/binding/plugwiseha/internal/api/model/dto/ActuatorFunctionalityToggle.java new file mode 100644 index 00000000000..e2e3b6ecd6a --- /dev/null +++ b/bundles/org.openhab.binding.plugwiseha/src/main/java/org/openhab/binding/plugwiseha/internal/api/model/dto/ActuatorFunctionalityToggle.java @@ -0,0 +1,25 @@ +/** + * 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.plugwiseha.internal.api.model.dto; + +import com.thoughtworks.xstream.annotations.XStreamAlias; + +/** + * @author B. van Wetten - Initial contribution + */ +@XStreamAlias("toggle_functionality") +public class ActuatorFunctionalityToggle extends ActuatorFunctionality { + + public ActuatorFunctionalityToggle() { + } +} diff --git a/bundles/org.openhab.binding.plugwiseha/src/main/java/org/openhab/binding/plugwiseha/internal/api/model/dto/Appliance.java b/bundles/org.openhab.binding.plugwiseha/src/main/java/org/openhab/binding/plugwiseha/internal/api/model/dto/Appliance.java new file mode 100644 index 00000000000..c4a4bdca762 --- /dev/null +++ b/bundles/org.openhab.binding.plugwiseha/src/main/java/org/openhab/binding/plugwiseha/internal/api/model/dto/Appliance.java @@ -0,0 +1,256 @@ +/** + * 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.plugwiseha.internal.api.model.dto; + +import java.time.ZonedDateTime; +import java.util.Optional; + +import com.thoughtworks.xstream.annotations.XStreamAlias; +import com.thoughtworks.xstream.annotations.XStreamImplicit; + +/** + * The {@link Appliance} class is an object model class that + * mirrors the XML structure provided by the Plugwise Home Automation + * controller for a Plugwise appliance. + * It implements the {@link PlugwiseComparableDate} interface and + * extends the abstract class {@link PlugwiseBaseModel}. + * + * @author B. van Wetten - Initial contribution + * @author Leo Siepel - finish initial contribution + */ +@XStreamAlias("appliance") +public class Appliance extends PlugwiseBaseModel implements PlugwiseComparableDate { + + private String name; + private String description; + private String type; + private String location; + + @XStreamAlias("module") + private Module module; + + @XStreamAlias("zig_bee_node") + private ZigBeeNode zigbeeNode; + + @XStreamImplicit(itemFieldName = "point_log", keyFieldName = "type") + private Logs pointLogs; + + @XStreamImplicit(itemFieldName = "actuator_functionality", keyFieldName = "type") + private ActuatorFunctionalities actuatorFunctionalities; + + public String getName() { + return name; + } + + public String getDescription() { + return description; + } + + public String getType() { + return type; + } + + public String getLocation() { + return location; + } + + public ZigBeeNode getZigbeeNode() { + if (zigbeeNode == null) { + zigbeeNode = new ZigBeeNode(); + } + return zigbeeNode; + } + + public Module getModule() { + if (module == null) { + module = new Module(); + } + return module; + } + + public Logs getPointLogs() { + if (pointLogs == null) { + pointLogs = new Logs(); + } + return pointLogs; + } + + public ActuatorFunctionalities getActuatorFunctionalities() { + if (actuatorFunctionalities == null) { + actuatorFunctionalities = new ActuatorFunctionalities(); + } + return actuatorFunctionalities; + } + + public Optional getTemperature() { + return this.pointLogs.getTemperature(); + } + + public Optional getTemperatureUnit() { + return this.pointLogs.getTemperatureUnit(); + } + + public Optional getSetpointTemperature() { + return this.pointLogs.getThermostatTemperature(); + } + + public Optional getSetpointTemperatureUnit() { + return this.pointLogs.getThermostatTemperatureUnit(); + } + + public Optional getOffsetTemperature() { + return this.pointLogs.getOffsetTemperature(); + } + + public Optional getOffsetTemperatureUnit() { + return this.pointLogs.getOffsetTemperatureUnit(); + } + + public Optional getRelayState() { + return this.pointLogs.getRelayState(); + } + + public Optional getRelayLockState() { + return this.actuatorFunctionalities.getRelayLockState(); + } + + public Optional getBatteryLevel() { + return this.pointLogs.getBatteryLevel(); + } + + public Optional getPowerUsage() { + return this.pointLogs.getPowerUsage(); + } + + public Optional getValvePosition() { + return this.pointLogs.getValvePosition(); + } + + public Optional getWaterPressure() { + return this.pointLogs.getWaterPressure(); + } + + public Optional getCHState() { + return this.pointLogs.getCHState(); + } + + public Optional getCoolingState() { + return this.pointLogs.getCoolingState(); + } + + public Optional getIntendedBoilerTemp() { + return this.pointLogs.getIntendedBoilerTemp(); + } + + public Optional getIntendedBoilerTempUnit() { + return this.pointLogs.getIntendedBoilerTempUnit(); + } + + public Optional getFlameState() { + return this.pointLogs.getFlameState(); + } + + public Optional getIntendedHeatingState() { + return this.pointLogs.getIntendedHeatingState(); + } + + public Optional getModulationLevel() { + return this.pointLogs.getModulationLevel(); + } + + public Optional getOTAppFaultCode() { + return this.pointLogs.getOTAppFaultCode(); + } + + public Optional getDHWTemp() { + return this.pointLogs.getDHWTemp(); + } + + public Optional getDHWTempUnit() { + return this.pointLogs.getDHWTempUnit(); + } + + public Optional getOTOEMFaultcode() { + return this.pointLogs.getOTOEMFaultcode(); + } + + public Optional getBoilerTemp() { + return this.pointLogs.getBoilerTemp(); + } + + public Optional getBoilerTempUnit() { + return this.pointLogs.getBoilerTempUnit(); + } + + public Optional getDHTSetpoint() { + return this.pointLogs.getDHTSetpoint(); + } + + public Optional getDHTSetpointUnit() { + return this.pointLogs.getDHTSetpointUnit(); + } + + public Optional getMaxBoilerTemp() { + return this.pointLogs.getMaxBoilerTemp(); + } + + public Optional getMaxBoilerTempUnit() { + return this.pointLogs.getMaxBoilerTempUnit(); + } + + public Optional getDHWComfortMode() { + return this.pointLogs.getDHWComfortMode(); + } + + public Optional getDHWState() { + return this.pointLogs.getDHWState(); + } + + public boolean isZigbeeDevice() { + return (this.zigbeeNode instanceof ZigBeeNode); + } + + public boolean isBatteryOperated() { + if (this.zigbeeNode instanceof ZigBeeNode) { + return this.zigbeeNode.getPowerSource().equals("battery") && this.getBatteryLevel().isPresent(); + } else { + return false; + } + } + + @Override + public int compareDateWith(Appliance compareTo) { + if (compareTo == null) { + return -1; + } + ZonedDateTime compareToDate = compareTo.getModifiedDate(); + ZonedDateTime compareFromDate = this.getModifiedDate(); + if (compareFromDate == null) { + return -1; + } else if (compareToDate == null) { + return 1; + } else { + return compareFromDate.compareTo(compareToDate); + } + } + + @Override + public boolean isNewerThan(Appliance hasModifiedDate) { + return compareDateWith(hasModifiedDate) > 0; + } + + @Override + public boolean isOlderThan(Appliance hasModifiedDate) { + return compareDateWith(hasModifiedDate) < 0; + } +} diff --git a/bundles/org.openhab.binding.plugwiseha/src/main/java/org/openhab/binding/plugwiseha/internal/api/model/dto/Appliances.java b/bundles/org.openhab.binding.plugwiseha/src/main/java/org/openhab/binding/plugwiseha/internal/api/model/dto/Appliances.java new file mode 100644 index 00000000000..3ab695dd4e8 --- /dev/null +++ b/bundles/org.openhab.binding.plugwiseha/src/main/java/org/openhab/binding/plugwiseha/internal/api/model/dto/Appliances.java @@ -0,0 +1,55 @@ +/** + * 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.plugwiseha.internal.api.model.dto; + +import java.util.Map; + +/** + * The {@link Appliances} class is an object model class that mirrors the XML + * structure provided by the Plugwise Home Automation controller for the + * collection of appliances. It extends the {@link PlugwiseHACollection} class. + * + * @author B. van Wetten - Initial contribution + */ +public class Appliances extends PlugwiseHACollection { + + @Override + public void merge(Map appliancesToMerge) { + if (appliancesToMerge != null) { + for (Appliance applianceToMerge : appliancesToMerge.values()) { + String id = applianceToMerge.getId(); + Appliance originalAppliance = this.get(id); + + Boolean originalApplianceIsOlder = false; + if (originalAppliance != null) { + originalApplianceIsOlder = originalAppliance.isOlderThan(applianceToMerge); + } + + if (originalAppliance != null && originalApplianceIsOlder) { + Logs updatedPointLogs = applianceToMerge.getPointLogs(); + if (updatedPointLogs != null) { + updatedPointLogs.merge(originalAppliance.getPointLogs()); + } + + ActuatorFunctionalities updatedActuatorFunctionalities = applianceToMerge + .getActuatorFunctionalities(); + if (updatedActuatorFunctionalities != null) { + updatedActuatorFunctionalities.merge(originalAppliance.getActuatorFunctionalities()); + } + + this.put(id, applianceToMerge); + } + } + } + } +} diff --git a/bundles/org.openhab.binding.plugwiseha/src/main/java/org/openhab/binding/plugwiseha/internal/api/model/dto/DomainObjects.java b/bundles/org.openhab.binding.plugwiseha/src/main/java/org/openhab/binding/plugwiseha/internal/api/model/dto/DomainObjects.java new file mode 100644 index 00000000000..e8371ae4c70 --- /dev/null +++ b/bundles/org.openhab.binding.plugwiseha/src/main/java/org/openhab/binding/plugwiseha/internal/api/model/dto/DomainObjects.java @@ -0,0 +1,64 @@ +/** + * 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.plugwiseha.internal.api.model.dto; + +import com.thoughtworks.xstream.annotations.XStreamAlias; +import com.thoughtworks.xstream.annotations.XStreamImplicit; + +/** + * @author B. van Wetten - Initial contribution + */ +@XStreamAlias("domain_objects") +public class DomainObjects { + + @XStreamAlias("gateway") + private GatewayInfo gatewayInfo; + + @XStreamImplicit(itemFieldName = "appliance", keyFieldName = "id") + private Appliances appliances = new Appliances(); + + @XStreamImplicit(itemFieldName = "location", keyFieldName = "id") + private Locations locations = new Locations(); + + @SuppressWarnings("unused") + @XStreamImplicit(itemFieldName = "module", keyFieldName = "id") + private Modules modules = new Modules(); + + public GatewayInfo getGatewayInfo() { + return gatewayInfo; + } + + public Appliances getAppliances() { + return appliances; + } + + public Locations getLocations() { + return locations; + } + + public Appliances mergeAppliances(Appliances updatedAppliances) { + if (updatedAppliances != null) { + this.appliances.merge(updatedAppliances); + } + + return this.appliances; + } + + public Locations mergeLocations(Locations updatedLocations) { + if (updatedLocations != null) { + this.locations.merge(updatedLocations); + } + + return this.locations; + } +} diff --git a/bundles/org.openhab.binding.plugwiseha/src/main/java/org/openhab/binding/plugwiseha/internal/api/model/dto/GatewayEnvironment.java b/bundles/org.openhab.binding.plugwiseha/src/main/java/org/openhab/binding/plugwiseha/internal/api/model/dto/GatewayEnvironment.java new file mode 100644 index 00000000000..1ceb7e9ddea --- /dev/null +++ b/bundles/org.openhab.binding.plugwiseha/src/main/java/org/openhab/binding/plugwiseha/internal/api/model/dto/GatewayEnvironment.java @@ -0,0 +1,28 @@ +/** + * 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.plugwiseha.internal.api.model.dto; + +import com.thoughtworks.xstream.annotations.XStreamAlias; + +/** + * @author B. van Wetten - Initial contribution + */ +@XStreamAlias("gateway_environment") +@SuppressWarnings("unused") +public class GatewayEnvironment extends PlugwiseBaseModel { + private String city; + private String country; + private String currency; + private String latitude; + private String longitude; +} diff --git a/bundles/org.openhab.binding.plugwiseha/src/main/java/org/openhab/binding/plugwiseha/internal/api/model/dto/GatewayInfo.java b/bundles/org.openhab.binding.plugwiseha/src/main/java/org/openhab/binding/plugwiseha/internal/api/model/dto/GatewayInfo.java new file mode 100644 index 00000000000..21906584fad --- /dev/null +++ b/bundles/org.openhab.binding.plugwiseha/src/main/java/org/openhab/binding/plugwiseha/internal/api/model/dto/GatewayInfo.java @@ -0,0 +1,120 @@ +/** + * 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.plugwiseha.internal.api.model.dto; + +import java.time.ZonedDateTime; + +import com.thoughtworks.xstream.annotations.XStreamAlias; + +/** + * @author B. van Wetten - Initial contribution + */ +@XStreamAlias("gateway") +public class GatewayInfo extends PlugwiseBaseModel { + + private String name; + private String description; + private String hostname; + private String timezone; + private ZonedDateTime time; + + @XStreamAlias("gateway_environment") + private GatewayEnvironment gatewayEnvironment; + + @XStreamAlias("vendor_name") + private String vendorName; + + @XStreamAlias("vendor_model") + private String vendorModel; + + @XStreamAlias("hardware_version") + private String hardwareVersion; + + @XStreamAlias("firmware_version") + private String firmwareVersion; + + @XStreamAlias("mac_address") + private String macAddress; + + @XStreamAlias("lan_ip") + private String lanIp; + + @XStreamAlias("wifi_ip") + private String wifiIp; + + @XStreamAlias("last_reset_date") + private ZonedDateTime lastResetDate; + + @XStreamAlias("last_boot_date") + private ZonedDateTime lastBootDate; + + public ZonedDateTime getTime() { + return time; + } + + public String getName() { + return name; + } + + public String getDescription() { + return description; + } + + public String getHostname() { + return hostname; + } + + public String getTimezone() { + return timezone; + } + + public GatewayEnvironment getGatewayEnvironment() { + return gatewayEnvironment; + } + + public String getVendorName() { + return vendorName; + } + + public String getVendorModel() { + return vendorModel; + } + + public String getHardwareVersion() { + return hardwareVersion; + } + + public String getFirmwareVersion() { + return firmwareVersion; + } + + public String getMacAddress() { + return macAddress; + } + + public String getLanIp() { + return lanIp; + } + + public String getWifiIp() { + return wifiIp; + } + + public ZonedDateTime getLastResetDate() { + return lastResetDate; + } + + public ZonedDateTime getLastBootDate() { + return lastBootDate; + } +} diff --git a/bundles/org.openhab.binding.plugwiseha/src/main/java/org/openhab/binding/plugwiseha/internal/api/model/dto/Location.java b/bundles/org.openhab.binding.plugwiseha/src/main/java/org/openhab/binding/plugwiseha/internal/api/model/dto/Location.java new file mode 100644 index 00000000000..75b2291e4b9 --- /dev/null +++ b/bundles/org.openhab.binding.plugwiseha/src/main/java/org/openhab/binding/plugwiseha/internal/api/model/dto/Location.java @@ -0,0 +1,137 @@ +/** + * 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.plugwiseha.internal.api.model.dto; + +import java.time.ZonedDateTime; +import java.util.ArrayList; +import java.util.List; +import java.util.Optional; + +import com.thoughtworks.xstream.annotations.XStreamAlias; +import com.thoughtworks.xstream.annotations.XStreamImplicit; + +/** + * The {@link Location} class is an object model class that + * mirrors the XML structure provided by the Plugwise Home Automation + * controller for a Plugwise zone/location. + * It implements the {@link PlugwiseComparableDate} interface and + * extends the abstract class {@link PlugwiseBaseModel}. + * + * @author B. van Wetten - Initial contribution + * @author Leo Siepel - finish initial contribution + */ +@XStreamAlias("location") +public class Location extends PlugwiseBaseModel implements PlugwiseComparableDate { + + private String name; + private String description; + private String type; + private String preset; + + @XStreamImplicit(itemFieldName = "appliance") + private List locationAppliances = new ArrayList(); + + @XStreamImplicit(itemFieldName = "point_log", keyFieldName = "type") + private Logs pointLogs; + + @XStreamImplicit(itemFieldName = "actuator_functionality", keyFieldName = "type") + private ActuatorFunctionalities actuatorFunctionalities; + + public String getName() { + return name; + } + + public String getDescription() { + return description; + } + + public String getType() { + return type; + } + + public String getPreset() { + return preset; + } + + public List getLocationAppliances() { + return locationAppliances; + } + + public Logs getPointLogs() { + if (pointLogs == null) { + pointLogs = new Logs(); + } + return pointLogs; + } + + public ActuatorFunctionalities getActuatorFunctionalities() { + if (actuatorFunctionalities == null) { + actuatorFunctionalities = new ActuatorFunctionalities(); + } + return actuatorFunctionalities; + } + + public Optional getTemperature() { + return this.pointLogs.getTemperature(); + } + + public Optional getTemperatureUnit() { + return this.pointLogs.getTemperatureUnit(); + } + + public Optional getSetpointTemperature() { + return this.pointLogs.getThermostatTemperature(); + } + + public Optional getSetpointTemperatureUnit() { + return this.pointLogs.getThermostatTemperatureUnit(); + } + + public Optional getPreHeatState() { + return this.actuatorFunctionalities.getPreHeatState(); + } + + public int applianceCount() { + if (this.locationAppliances == null) { + return 0; + } else { + return this.locationAppliances.size(); + } + } + + @Override + public int compareDateWith(Location compareTo) { + if (compareTo == null) { + return -1; + } + ZonedDateTime compareToDate = compareTo.getModifiedDate(); + ZonedDateTime compareFromDate = this.getModifiedDate(); + if (compareFromDate == null) { + return -1; + } else if (compareToDate == null) { + return 1; + } else { + return compareFromDate.compareTo(compareToDate); + } + } + + @Override + public boolean isNewerThan(Location hasModifiedDate) { + return compareDateWith(hasModifiedDate) > 0; + } + + @Override + public boolean isOlderThan(Location hasModifiedDate) { + return compareDateWith(hasModifiedDate) < 0; + } +} diff --git a/bundles/org.openhab.binding.plugwiseha/src/main/java/org/openhab/binding/plugwiseha/internal/api/model/dto/Locations.java b/bundles/org.openhab.binding.plugwiseha/src/main/java/org/openhab/binding/plugwiseha/internal/api/model/dto/Locations.java new file mode 100644 index 00000000000..8858c1593bf --- /dev/null +++ b/bundles/org.openhab.binding.plugwiseha/src/main/java/org/openhab/binding/plugwiseha/internal/api/model/dto/Locations.java @@ -0,0 +1,55 @@ +/** + * 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.plugwiseha.internal.api.model.dto; + +import java.util.Map; + +/** + * The {@link Locations} class is an object model class that mirrors the XML + * structure provided by the Plugwise Home Automation controller for the + * collection of Plugwise locations/zones. It extends the + * {@link PlugwiseHACollection} class. + * + * @author B. van Wetten - Initial contribution + */ +public class Locations extends PlugwiseHACollection { + + @Override + public void merge(Map locations) { + if (locations != null) { + for (Location location : locations.values()) { + String id = location.getId(); + Location originalLocation = this.get(id); + + Boolean originalLocationIsOlder = false; + if (originalLocation != null) { + originalLocationIsOlder = originalLocation.isOlderThan(location); + } + + if (originalLocation != null && originalLocationIsOlder) { + Logs updatedPointLogs = location.getPointLogs(); + if (updatedPointLogs != null) { + updatedPointLogs.merge(originalLocation.getPointLogs()); + } + + ActuatorFunctionalities updatedActuatorFunctionalities = location.getActuatorFunctionalities(); + if (updatedActuatorFunctionalities != null) { + updatedActuatorFunctionalities.merge(originalLocation.getActuatorFunctionalities()); + } + + this.put(id, location); + } + } + } + } +} diff --git a/bundles/org.openhab.binding.plugwiseha/src/main/java/org/openhab/binding/plugwiseha/internal/api/model/dto/Log.java b/bundles/org.openhab.binding.plugwiseha/src/main/java/org/openhab/binding/plugwiseha/internal/api/model/dto/Log.java new file mode 100644 index 00000000000..0b87b0076bb --- /dev/null +++ b/bundles/org.openhab.binding.plugwiseha/src/main/java/org/openhab/binding/plugwiseha/internal/api/model/dto/Log.java @@ -0,0 +1,115 @@ +/** + * 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.plugwiseha.internal.api.model.dto; + +import java.time.ZonedDateTime; +import java.util.Optional; + +import com.thoughtworks.xstream.annotations.XStreamAlias; + +/** + * @author B. van Wetten - Initial contribution + * @author Leo Siepel - finish initial contribution + */ +@XStreamAlias("point_log") +public class Log extends PlugwiseBaseModel implements PlugwiseComparableDate { + + private String type; + + private String unit; + + private String measurement; + + @XStreamAlias("measurement_date") + private ZonedDateTime measurementDate; + + @XStreamAlias("updated_date") + private ZonedDateTime updatedDate; + + public String getType() { + return type; + } + + public String getUnit() { + return unit; + } + + public Optional getMeasurement() { + return Optional.ofNullable(measurement); + } + + public Optional getMeasurementAsBoolean() { + if (measurement != null) { + switch (measurement.toLowerCase()) { + case "on": + return Optional.of(true); + case "off": + return Optional.of(false); + default: + return Optional.empty(); + } + } else { + return Optional.empty(); + } + } + + public Optional getMeasurementAsDouble() { + try { + if (measurement != null) { + return Optional.of(Double.parseDouble(measurement)); + } else { + return Optional.empty(); + } + } catch (NumberFormatException e) { + return Optional.empty(); + } + } + + public Optional getMeasurementUnit() { + return Optional.ofNullable(unit); + } + + public ZonedDateTime getMeasurementDate() { + return measurementDate; + } + + public ZonedDateTime getUpdatedDate() { + return updatedDate; + } + + @Override + public int compareDateWith(Log compareTo) { + if (compareTo == null) { + return -1; + } + ZonedDateTime compareToDate = compareTo.getMeasurementDate(); + ZonedDateTime compareFromDate = this.getMeasurementDate(); + if (compareFromDate == null) { + return -1; + } else if (compareToDate == null) { + return 1; + } else { + return compareFromDate.compareTo(compareToDate); + } + } + + @Override + public boolean isNewerThan(Log hasModifiedDate) { + return compareDateWith(hasModifiedDate) > 0; + } + + @Override + public boolean isOlderThan(Log hasModifiedDate) { + return compareDateWith(hasModifiedDate) < 0; + } +} diff --git a/bundles/org.openhab.binding.plugwiseha/src/main/java/org/openhab/binding/plugwiseha/internal/api/model/dto/Logs.java b/bundles/org.openhab.binding.plugwiseha/src/main/java/org/openhab/binding/plugwiseha/internal/api/model/dto/Logs.java new file mode 100644 index 00000000000..205254431aa --- /dev/null +++ b/bundles/org.openhab.binding.plugwiseha/src/main/java/org/openhab/binding/plugwiseha/internal/api/model/dto/Logs.java @@ -0,0 +1,194 @@ +/** + * 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.plugwiseha.internal.api.model.dto; + +import java.util.Map; +import java.util.Optional; + +/** + * The {@link Logs} class is an object model class that + * mirrors the XML structure provided by the Plugwise Home Automation + * controller for the collection of logs. + * It extends the {@link PlugwiseHACollection} class. + * + * @author B. van Wetten - Initial contribution + */ +public class Logs extends PlugwiseHACollection { + + private static final String THERMOSTAT = "thermostat"; + private static final String TEMPERATURE = "temperature"; + private static final String TEMPERATURE_OFFSET = "temperature_offset"; + private static final String BATTERY = "battery"; + private static final String POWER_USAGE = "electricity_consumed"; + private static final String RELAY = "relay"; + private static final String DHWSTATE = "domestic_hot_water_state"; + private static final String COOLINGSTATE = "cooling_state"; + private static final String INTENDEDBOILERTEMP = "intended_boiler_temperature"; + private static final String FLAMESTATE = "flame_state"; + private static final String INTENDEDHEATINGSTATE = "intended_central_heating_state"; + private static final String MODULATIONLEVEL = "modulation_level"; + private static final String OTAPPLICATIONFAULTCODE = "open_therm_application_specific_fault_code"; + private static final String DHWTEMP = "domestic_hot_water_temperature"; + private static final String OTOEMFAULTCODE = "open_therm_oem_fault_code"; + private static final String BOILERTEMP = "boiler_temperature"; + private static final String DHWSETPOINT = "domestic_hot_water_setpoint"; + private static final String MAXBOILERTEMP = "maximum_boiler_temperature"; + private static final String DHWCOMFORTMODE = "domestic_hot_water_comfort_mode"; + private static final String CHSTATE = "central_heating_state"; + private static final String VALVE_POSITION = "valve_position"; + private static final String WATER_PRESSURE = "central_heater_water_pressure"; + + public Optional getCoolingState() { + return this.getLog(COOLINGSTATE).map(logEntry -> logEntry.getMeasurementAsBoolean()).orElse(Optional.empty()); + } + + public Optional getIntendedBoilerTemp() { + return this.getLog(INTENDEDBOILERTEMP).map(logEntry -> logEntry.getMeasurementAsDouble()) + .orElse(Optional.empty()); + } + + public Optional getIntendedBoilerTempUnit() { + return this.getLog(INTENDEDBOILERTEMP).map(logEntry -> logEntry.getMeasurementUnit()).orElse(Optional.empty()); + } + + public Optional getFlameState() { + return this.getLog(FLAMESTATE).map(logEntry -> logEntry.getMeasurementAsBoolean()).orElse(Optional.empty()); + } + + public Optional getIntendedHeatingState() { + return this.getLog(INTENDEDHEATINGSTATE).map(logEntry -> logEntry.getMeasurementAsBoolean()) + .orElse(Optional.empty()); + } + + public Optional getModulationLevel() { + return this.getLog(MODULATIONLEVEL).map(logEntry -> logEntry.getMeasurementAsDouble()).orElse(Optional.empty()); + } + + public Optional getOTAppFaultCode() { + return this.getLog(OTAPPLICATIONFAULTCODE).map(logEntry -> logEntry.getMeasurementAsDouble()) + .orElse(Optional.empty()); + } + + public Optional getDHWTemp() { + return this.getLog(DHWTEMP).map(logEntry -> logEntry.getMeasurementAsDouble()).orElse(Optional.empty()); + } + + public Optional getDHWTempUnit() { + return this.getLog(DHWTEMP).map(logEntry -> logEntry.getMeasurementUnit()).orElse(Optional.empty()); + } + + public Optional getOTOEMFaultcode() { + return this.getLog(OTOEMFAULTCODE).map(logEntry -> logEntry.getMeasurementAsDouble()).orElse(Optional.empty()); + } + + public Optional getBoilerTemp() { + return this.getLog(BOILERTEMP).map(logEntry -> logEntry.getMeasurementAsDouble()).orElse(Optional.empty()); + } + + public Optional getBoilerTempUnit() { + return this.getLog(BOILERTEMP).map(logEntry -> logEntry.getMeasurementUnit()).orElse(Optional.empty()); + } + + public Optional getDHTSetpoint() { + return this.getLog(DHWSETPOINT).map(logEntry -> logEntry.getMeasurementAsDouble()).orElse(Optional.empty()); + } + + public Optional getDHTSetpointUnit() { + return this.getLog(DHWSETPOINT).map(logEntry -> logEntry.getMeasurementUnit()).orElse(Optional.empty()); + } + + public Optional getMaxBoilerTemp() { + return this.getLog(MAXBOILERTEMP).map(logEntry -> logEntry.getMeasurementAsDouble()).orElse(Optional.empty()); + } + + public Optional getMaxBoilerTempUnit() { + return this.getLog(MAXBOILERTEMP).map(logEntry -> logEntry.getMeasurementUnit()).orElse(Optional.empty()); + } + + public Optional getDHWComfortMode() { + return this.getLog(DHWCOMFORTMODE).map(logEntry -> logEntry.getMeasurementAsBoolean()).orElse(Optional.empty()); + } + + public Optional getTemperature() { + return this.getLog(TEMPERATURE).map(logEntry -> logEntry.getMeasurementAsDouble()).orElse(Optional.empty()); + } + + public Optional getTemperatureUnit() { + return this.getLog(TEMPERATURE).map(logEntry -> logEntry.getMeasurementUnit()).orElse(Optional.empty()); + } + + public Optional getThermostatTemperature() { + return this.getLog(THERMOSTAT).map(logEntry -> logEntry.getMeasurementAsDouble()).orElse(Optional.empty()); + } + + public Optional getThermostatTemperatureUnit() { + return this.getLog(THERMOSTAT).map(logEntry -> logEntry.getMeasurementUnit()).orElse(Optional.empty()); + } + + public Optional getOffsetTemperature() { + return this.getLog(TEMPERATURE_OFFSET).map(logEntry -> logEntry.getMeasurementAsDouble()) + .orElse(Optional.empty()); + } + + public Optional getOffsetTemperatureUnit() { + return this.getLog(TEMPERATURE_OFFSET).map(logEntry -> logEntry.getMeasurementUnit()).orElse(Optional.empty()); + } + + public Optional getRelayState() { + return this.getLog(RELAY).map(logEntry -> logEntry.getMeasurementAsBoolean()).orElse(Optional.empty()); + } + + public Optional getDHWState() { + return this.getLog(DHWSTATE).map(logEntry -> logEntry.getMeasurementAsBoolean()).orElse(Optional.empty()); + } + + public Optional getCHState() { + return this.getLog(CHSTATE).map(logEntry -> logEntry.getMeasurementAsBoolean()).orElse(Optional.empty()); + } + + public Optional getValvePosition() { + return this.getLog(VALVE_POSITION).map(logEntry -> logEntry.getMeasurementAsDouble()).orElse(Optional.empty()); + } + + public Optional getWaterPressure() { + return this.getLog(WATER_PRESSURE).map(logEntry -> logEntry.getMeasurementAsDouble()).orElse(Optional.empty()); + } + + public Optional getBatteryLevel() { + return this.getLog(BATTERY).map(logEntry -> logEntry.getMeasurementAsDouble()).orElse(Optional.empty()); + } + + public Optional getPowerUsage() { + return this.getLog(POWER_USAGE).map(logEntry -> logEntry.getMeasurementAsDouble()).orElse(Optional.empty()); + } + + public Optional getLog(String logItem) { + return Optional.ofNullable(this.get(logItem)); + } + + @Override + public void merge(Map logsToMerge) { + if (logsToMerge != null) { + for (Log logToMerge : logsToMerge.values()) { + String type = logToMerge.getType(); + Log originalLog = this.get(type); + + if (originalLog == null || originalLog.isOlderThan(logToMerge)) { + this.put(type, logToMerge); + } else { + this.put(type, originalLog); + } + } + } + } +} diff --git a/bundles/org.openhab.binding.plugwiseha/src/main/java/org/openhab/binding/plugwiseha/internal/api/model/dto/Module.java b/bundles/org.openhab.binding.plugwiseha/src/main/java/org/openhab/binding/plugwiseha/internal/api/model/dto/Module.java new file mode 100644 index 00000000000..fa4ca6b0b06 --- /dev/null +++ b/bundles/org.openhab.binding.plugwiseha/src/main/java/org/openhab/binding/plugwiseha/internal/api/model/dto/Module.java @@ -0,0 +1,90 @@ +/** + * 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.plugwiseha.internal.api.model.dto; + +import java.time.ZonedDateTime; + +import com.thoughtworks.xstream.annotations.XStreamAlias; +import com.thoughtworks.xstream.annotations.XStreamImplicit; + +/** + * The {@link Module} class is an object model class that + * mirrors the XML structure provided by the Plugwise Home Automation + * controller for a Plugwise module. + * It implements the {@link PlugwiseComparableDate} interface and + * extends the abstract class {@link PlugwiseBaseModel}. + * + * @author B. van Wetten - Initial contribution + * @author Leo Siepel - finish initial contribution + */ +@XStreamAlias("module") +public class Module extends PlugwiseBaseModel implements PlugwiseComparableDate { + + @SuppressWarnings("unused") + @XStreamImplicit(itemFieldName = "service", keyFieldName = "id") + private Services services; + + @XStreamAlias("vendor_name") + private String vendorName; + + @XStreamAlias("vendor_model") + private String vendorModel; + + @XStreamAlias("hardware_version") + private String hardwareVersion; + + @XStreamAlias("firmware_version") + private String firmwareVersion; + + public String getVendorName() { + return vendorName; + } + + public String getVendorModel() { + return vendorModel; + } + + public String getHardwareVersion() { + return hardwareVersion; + } + + public String getFirmwareVersion() { + return firmwareVersion; + } + + @Override + public int compareDateWith(Module compareTo) { + if (compareTo == null) { + return -1; + } + ZonedDateTime compareToDate = compareTo.getModifiedDate(); + ZonedDateTime compareFromDate = this.getModifiedDate(); + if (compareFromDate == null) { + return -1; + } else if (compareToDate == null) { + return 1; + } else { + return compareFromDate.compareTo(compareToDate); + } + } + + @Override + public boolean isNewerThan(Module hasModifiedDate) { + return compareDateWith(hasModifiedDate) > 0; + } + + @Override + public boolean isOlderThan(Module hasModifiedDate) { + return compareDateWith(hasModifiedDate) < 0; + } +} diff --git a/bundles/org.openhab.binding.plugwiseha/src/main/java/org/openhab/binding/plugwiseha/internal/api/model/dto/Modules.java b/bundles/org.openhab.binding.plugwiseha/src/main/java/org/openhab/binding/plugwiseha/internal/api/model/dto/Modules.java new file mode 100644 index 00000000000..29eb21b924d --- /dev/null +++ b/bundles/org.openhab.binding.plugwiseha/src/main/java/org/openhab/binding/plugwiseha/internal/api/model/dto/Modules.java @@ -0,0 +1,30 @@ +/** + * 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.plugwiseha.internal.api.model.dto; + +import java.util.Map; + +/** + * The {@link Modules} class is an object model class that + * mirrors the XML structure provided by the Plugwise Home Automation + * controller for the collection of modules. + * It extends the {@link PlugwiseHACollection} class. + * + * @author B. van Wetten - Initial contribution + */ +public class Modules extends PlugwiseHACollection { + + @Override + public void merge(Map modules) { + } +} diff --git a/bundles/org.openhab.binding.plugwiseha/src/main/java/org/openhab/binding/plugwiseha/internal/api/model/dto/PlugwiseBaseModel.java b/bundles/org.openhab.binding.plugwiseha/src/main/java/org/openhab/binding/plugwiseha/internal/api/model/dto/PlugwiseBaseModel.java new file mode 100644 index 00000000000..dbf0adbd487 --- /dev/null +++ b/bundles/org.openhab.binding.plugwiseha/src/main/java/org/openhab/binding/plugwiseha/internal/api/model/dto/PlugwiseBaseModel.java @@ -0,0 +1,60 @@ +/** + * 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.plugwiseha.internal.api.model.dto; + +import java.time.ZonedDateTime; + +import com.thoughtworks.xstream.annotations.XStreamAlias; + +/** + * The {@link PlugwiseBaseModel} abstract class contains + * methods and properties that similar for all object model classes. + * + * @author B. van Wetten - Initial contribution + */ +public abstract class PlugwiseBaseModel { + + private String id; + + @XStreamAlias("created_date") + private ZonedDateTime createdDate; + + @XStreamAlias("modified_date") + private ZonedDateTime modifiedDate; + + @XStreamAlias("updated_date") + private ZonedDateTime updateDate; + + @XStreamAlias("deleted_date") + private ZonedDateTime deletedDate; + + public String getId() { + return id; + } + + public ZonedDateTime getCreatedDate() { + return createdDate; + } + + public ZonedDateTime getModifiedDate() { + return modifiedDate; + } + + public ZonedDateTime getUpdatedDate() { + return updateDate; + } + + public ZonedDateTime getDeletedDate() { + return deletedDate; + } +} diff --git a/bundles/org.openhab.binding.plugwiseha/src/main/java/org/openhab/binding/plugwiseha/internal/api/model/dto/PlugwiseComparableDate.java b/bundles/org.openhab.binding.plugwiseha/src/main/java/org/openhab/binding/plugwiseha/internal/api/model/dto/PlugwiseComparableDate.java new file mode 100644 index 00000000000..19ec253f7e3 --- /dev/null +++ b/bundles/org.openhab.binding.plugwiseha/src/main/java/org/openhab/binding/plugwiseha/internal/api/model/dto/PlugwiseComparableDate.java @@ -0,0 +1,24 @@ +/** + * 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.plugwiseha.internal.api.model.dto; + +/** + * @author B. van Wetten - Initial contribution + */ +public interface PlugwiseComparableDate { + public int compareDateWith(T hasModifiedDate); + + public boolean isOlderThan(T hasModifiedDate); + + public boolean isNewerThan(T hasModifiedDate); +} diff --git a/bundles/org.openhab.binding.plugwiseha/src/main/java/org/openhab/binding/plugwiseha/internal/api/model/dto/PlugwiseHACollection.java b/bundles/org.openhab.binding.plugwiseha/src/main/java/org/openhab/binding/plugwiseha/internal/api/model/dto/PlugwiseHACollection.java new file mode 100644 index 00000000000..dacebe719c6 --- /dev/null +++ b/bundles/org.openhab.binding.plugwiseha/src/main/java/org/openhab/binding/plugwiseha/internal/api/model/dto/PlugwiseHACollection.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.plugwiseha.internal.api.model.dto; + +import java.util.Collection; +import java.util.HashMap; +import java.util.Map; +import java.util.Set; + +/** + * @author B. van Wetten - Initial contribution + */ +public abstract class PlugwiseHACollection implements Map { + + private final Map map = new HashMap<>(); + + @Override + public int size() { + return this.map.size(); + } + + @Override + public boolean isEmpty() { + return this.map.isEmpty(); + } + + @Override + public boolean containsKey(Object key) { + return this.map.containsKey(key); + } + + @Override + public boolean containsValue(Object value) { + return this.map.containsValue(value); + } + + @Override + public T get(Object key) { + return this.map.get(key); + } + + @Override + public T put(String key, T value) { + return this.map.put(key, value); + } + + @Override + public T remove(Object key) { + return this.map.remove(key); + } + + @Override + public void putAll(Map m) { + this.map.putAll(m); + } + + @Override + public void clear() { + this.map.clear(); + } + + @Override + public Set keySet() { + return this.map.keySet(); + } + + @Override + public Collection values() { + return this.map.values(); + } + + @Override + public Set> entrySet() { + return this.map.entrySet(); + } + + public abstract void merge(Map map); +} diff --git a/bundles/org.openhab.binding.plugwiseha/src/main/java/org/openhab/binding/plugwiseha/internal/api/model/dto/Service.java b/bundles/org.openhab.binding.plugwiseha/src/main/java/org/openhab/binding/plugwiseha/internal/api/model/dto/Service.java new file mode 100644 index 00000000000..f6e68b2df2a --- /dev/null +++ b/bundles/org.openhab.binding.plugwiseha/src/main/java/org/openhab/binding/plugwiseha/internal/api/model/dto/Service.java @@ -0,0 +1,30 @@ +/** + * 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.plugwiseha.internal.api.model.dto; + +import com.thoughtworks.xstream.annotations.XStreamAlias; + +/** + * @author B. van Wetten - Initial contribution + */ +@XStreamAlias("service") +public class Service extends PlugwiseBaseModel { + + @SuppressWarnings("unused") + @XStreamAlias("log_type") + private String logType; + + @SuppressWarnings("unused") + @XStreamAlias("point_log") + private String pointLogId; +} diff --git a/bundles/org.openhab.binding.plugwiseha/src/main/java/org/openhab/binding/plugwiseha/internal/api/model/dto/Services.java b/bundles/org.openhab.binding.plugwiseha/src/main/java/org/openhab/binding/plugwiseha/internal/api/model/dto/Services.java new file mode 100644 index 00000000000..4de9f70c540 --- /dev/null +++ b/bundles/org.openhab.binding.plugwiseha/src/main/java/org/openhab/binding/plugwiseha/internal/api/model/dto/Services.java @@ -0,0 +1,30 @@ +/** + * 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.plugwiseha.internal.api.model.dto; + +import java.util.Map; + +/** + * The {@link Services} class is an object model class that + * mirrors the XML structure provided by the Plugwise Home Automation + * controller for the collection of module services. + * It extends the {@link PlugwiseHACollection} class. + * + * @author B. van Wetten - Initial contribution + */ +public class Services extends PlugwiseHACollection { + + @Override + public void merge(Map services) { + } +} diff --git a/bundles/org.openhab.binding.plugwiseha/src/main/java/org/openhab/binding/plugwiseha/internal/api/model/dto/ZigBeeNode.java b/bundles/org.openhab.binding.plugwiseha/src/main/java/org/openhab/binding/plugwiseha/internal/api/model/dto/ZigBeeNode.java new file mode 100644 index 00000000000..33f1aafd38f --- /dev/null +++ b/bundles/org.openhab.binding.plugwiseha/src/main/java/org/openhab/binding/plugwiseha/internal/api/model/dto/ZigBeeNode.java @@ -0,0 +1,52 @@ +/** + * 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.plugwiseha.internal.api.model.dto; + +import com.thoughtworks.xstream.annotations.XStreamAlias; + +/** + * The {@link ZigBeeNode} class is an object model class that + * mirrors the XML structure provided by the Plugwise Home Automation + * controller for a Plugwise ZigBeeNode. + * It extends the abstract class {@link PlugwiseBaseModel}. + * + * @author B. van Wetten - Initial contribution + */ +@XStreamAlias("ZigBeeNode") +public class ZigBeeNode extends PlugwiseBaseModel { + + private String type; + private String reachable; + + @XStreamAlias("power_source") + private String powerSource; + + @XStreamAlias("mac_address") + private String macAddress; + + public String getType() { + return type; + } + + public String getReachable() { + return reachable; + } + + public String getPowerSource() { + return powerSource; + } + + public String getMacAddress() { + return macAddress; + } +} diff --git a/bundles/org.openhab.binding.plugwiseha/src/main/java/org/openhab/binding/plugwiseha/internal/api/xml/PlugwiseHAXStream.java b/bundles/org.openhab.binding.plugwiseha/src/main/java/org/openhab/binding/plugwiseha/internal/api/xml/PlugwiseHAXStream.java new file mode 100644 index 00000000000..e60eefda028 --- /dev/null +++ b/bundles/org.openhab.binding.plugwiseha/src/main/java/org/openhab/binding/plugwiseha/internal/api/xml/PlugwiseHAXStream.java @@ -0,0 +1,110 @@ +/** + * 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.plugwiseha.internal.api.xml; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.openhab.binding.plugwiseha.internal.api.model.converter.DateTimeConverter; +import org.openhab.binding.plugwiseha.internal.api.model.dto.ActuatorFunctionalities; +import org.openhab.binding.plugwiseha.internal.api.model.dto.ActuatorFunctionality; +import org.openhab.binding.plugwiseha.internal.api.model.dto.ActuatorFunctionalityOffsetTemperature; +import org.openhab.binding.plugwiseha.internal.api.model.dto.ActuatorFunctionalityRelay; +import org.openhab.binding.plugwiseha.internal.api.model.dto.ActuatorFunctionalityThermostat; +import org.openhab.binding.plugwiseha.internal.api.model.dto.ActuatorFunctionalityThreshold; +import org.openhab.binding.plugwiseha.internal.api.model.dto.ActuatorFunctionalityTimer; +import org.openhab.binding.plugwiseha.internal.api.model.dto.ActuatorFunctionalityToggle; +import org.openhab.binding.plugwiseha.internal.api.model.dto.Appliance; +import org.openhab.binding.plugwiseha.internal.api.model.dto.Appliances; +import org.openhab.binding.plugwiseha.internal.api.model.dto.DomainObjects; +import org.openhab.binding.plugwiseha.internal.api.model.dto.GatewayEnvironment; +import org.openhab.binding.plugwiseha.internal.api.model.dto.GatewayInfo; +import org.openhab.binding.plugwiseha.internal.api.model.dto.Location; +import org.openhab.binding.plugwiseha.internal.api.model.dto.Locations; +import org.openhab.binding.plugwiseha.internal.api.model.dto.Log; +import org.openhab.binding.plugwiseha.internal.api.model.dto.Logs; +import org.openhab.binding.plugwiseha.internal.api.model.dto.Module; +import org.openhab.binding.plugwiseha.internal.api.model.dto.Modules; +import org.openhab.binding.plugwiseha.internal.api.model.dto.Service; +import org.openhab.binding.plugwiseha.internal.api.model.dto.Services; +import org.openhab.binding.plugwiseha.internal.api.model.dto.ZigBeeNode; + +import com.thoughtworks.xstream.XStream; +import com.thoughtworks.xstream.io.xml.StaxDriver; +import com.thoughtworks.xstream.io.xml.XmlFriendlyNameCoder; +import com.thoughtworks.xstream.security.NoTypePermission; +import com.thoughtworks.xstream.security.NullPermission; + +/** + * The {@link PlugwiseHAXStream} class is a utility class that wraps an XStream + * object and provide additional functionality specific to the PlugwiseHA + * binding. It automatically load the correct converter classes and processes + * the XStream annotions used by the object classes. + * + * @author B. van Wetten - Initial contribution + */ + +@NonNullByDefault +public class PlugwiseHAXStream extends XStream { + + private static XmlFriendlyNameCoder customCoder = new XmlFriendlyNameCoder("_-", "_"); + + public PlugwiseHAXStream() { + super(new StaxDriver(PlugwiseHAXStream.customCoder)); + + initialize(); + } + + // Protected methods + + @SuppressWarnings("rawtypes") + protected void allowClass(Class clz) { + this.processAnnotations(clz); + this.allowTypeHierarchy(clz); + } + + protected void initialize() { + // Configure XStream + this.ignoreUnknownElements(); + this.setClassLoader(getClass().getClassLoader()); + + // Clear out existing + this.addPermission(NoTypePermission.NONE); + this.addPermission(NullPermission.NULL); + + // Whitelist classes + this.allowClass(GatewayInfo.class); + this.allowClass(GatewayEnvironment.class); + this.allowClass(Appliances.class); + this.allowClass(Appliance.class); + this.allowClass(Modules.class); + this.allowClass(Module.class); + this.allowClass(Locations.class); + this.allowClass(Location.class); + this.allowClass(Logs.class); + this.allowClass(Log.class); + this.allowClass(Services.class); + this.allowClass(Service.class); + this.allowClass(ZigBeeNode.class); + this.allowClass(ActuatorFunctionalities.class); + this.allowClass(ActuatorFunctionality.class); + this.allowClass(ActuatorFunctionalityThermostat.class); + this.allowClass(ActuatorFunctionalityOffsetTemperature.class); + this.allowClass(ActuatorFunctionalityRelay.class); + this.allowClass(ActuatorFunctionalityTimer.class); + this.allowClass(ActuatorFunctionalityThreshold.class); + this.allowClass(ActuatorFunctionalityToggle.class); + this.allowClass(DomainObjects.class); + + // Register custom converters + this.registerConverter(new DateTimeConverter()); + } +} diff --git a/bundles/org.openhab.binding.plugwiseha/src/main/java/org/openhab/binding/plugwiseha/internal/config/PlugwiseHABridgeThingConfig.java b/bundles/org.openhab.binding.plugwiseha/src/main/java/org/openhab/binding/plugwiseha/internal/config/PlugwiseHABridgeThingConfig.java new file mode 100644 index 00000000000..2be9fb59f4b --- /dev/null +++ b/bundles/org.openhab.binding.plugwiseha/src/main/java/org/openhab/binding/plugwiseha/internal/config/PlugwiseHABridgeThingConfig.java @@ -0,0 +1,66 @@ +/** + * 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.plugwiseha.internal.config; + +import org.eclipse.jdt.annotation.NonNullByDefault; + +/** + * The {@link PlugwiseHABridgeThingConfig} encapsulates all the configuration options for an instance of the + * {@link PlugwiseHABridgeHandler}. + * + * @author Bas van Wetten - Initial contribution + * @author Leo Siepel - finish initial contribution + */ +@NonNullByDefault +public class PlugwiseHABridgeThingConfig { + + private String host = "adam"; + + private int port = 80; + + private String username = "smile"; + + private String smileId = ""; + + private int refresh = 15; + + public String getHost() { + return host; + } + + public int getPort() { + return port; + } + + public String getUsername() { + return username; + } + + public String getsmileId() { + return smileId; + } + + public int getRefresh() { + return refresh; + } + + public boolean isValid() { + return !host.isBlank() && !username.isBlank() && !smileId.isBlank(); + } + + @Override + public String toString() { + return "PlugwiseHABridgeThingConfig{host = " + host + ", port = " + port + ", username = " + username + + ", smileId = *****, refresh = " + refresh + "}"; + } +} diff --git a/bundles/org.openhab.binding.plugwiseha/src/main/java/org/openhab/binding/plugwiseha/internal/config/PlugwiseHAThingConfig.java b/bundles/org.openhab.binding.plugwiseha/src/main/java/org/openhab/binding/plugwiseha/internal/config/PlugwiseHAThingConfig.java new file mode 100644 index 00000000000..fabda3f727d --- /dev/null +++ b/bundles/org.openhab.binding.plugwiseha/src/main/java/org/openhab/binding/plugwiseha/internal/config/PlugwiseHAThingConfig.java @@ -0,0 +1,52 @@ +/** + * 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.plugwiseha.internal.config; + +import org.eclipse.jdt.annotation.NonNullByDefault; + +/** + * The {@link PlugwiseHAThingConfig} encapsulates the configuration options for + * an instance of the {@link PlugwiseHAApplianceHandler} and the + * {@link PlugwiseHAZoneHandler} + * + * @author Bas van Wetten - Initial contribution + * @author Leo Siepel - finish initial contribution + */ +@NonNullByDefault +public class PlugwiseHAThingConfig { + + private String id = ""; + + private int lowBatteryPercentage = 15; + + // Getters + + public String getId() { + return id; + } + + public int getLowBatteryPercentage() { + return this.lowBatteryPercentage; + } + + // Member methods + + public boolean isValid() { + return !id.isBlank() && lowBatteryPercentage > 0 && lowBatteryPercentage < 100; + } + + @Override + public String toString() { + return "PlugwiseHAThingConfig{id = " + id + ", lowBatteryPercentage = " + lowBatteryPercentage + "}"; + } +} diff --git a/bundles/org.openhab.binding.plugwiseha/src/main/java/org/openhab/binding/plugwiseha/internal/discovery/PlugwiseHADiscoveryService.java b/bundles/org.openhab.binding.plugwiseha/src/main/java/org/openhab/binding/plugwiseha/internal/discovery/PlugwiseHADiscoveryService.java new file mode 100644 index 00000000000..f1564890e2b --- /dev/null +++ b/bundles/org.openhab.binding.plugwiseha/src/main/java/org/openhab/binding/plugwiseha/internal/discovery/PlugwiseHADiscoveryService.java @@ -0,0 +1,212 @@ +/** + * 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.plugwiseha.internal.discovery; + +import static org.openhab.binding.plugwiseha.internal.PlugwiseHABindingConstants.*; + +import java.util.HashMap; +import java.util.Map; +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.plugwiseha.internal.PlugwiseHABindingConstants; +import org.openhab.binding.plugwiseha.internal.api.exception.PlugwiseHAException; +import org.openhab.binding.plugwiseha.internal.api.model.PlugwiseHAController; +import org.openhab.binding.plugwiseha.internal.api.model.dto.Appliance; +import org.openhab.binding.plugwiseha.internal.api.model.dto.DomainObjects; +import org.openhab.binding.plugwiseha.internal.api.model.dto.Location; +import org.openhab.binding.plugwiseha.internal.handler.PlugwiseHABridgeHandler; +import org.openhab.core.config.discovery.AbstractDiscoveryService; +import org.openhab.core.config.discovery.DiscoveryResult; +import org.openhab.core.config.discovery.DiscoveryResultBuilder; +import org.openhab.core.thing.ThingUID; +import org.openhab.core.thing.binding.ThingHandler; +import org.openhab.core.thing.binding.ThingHandlerService; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * The {@link PlugwiseHADiscoveryService} class is capable of discovering the + * available data from the Plugwise Home Automation gateway + * + * @author Bas van Wetten - Initial contribution + * @author Leo Siepel - finish initial contribution + */ +@NonNullByDefault +public class PlugwiseHADiscoveryService extends AbstractDiscoveryService implements ThingHandlerService { + + private final Logger logger = LoggerFactory.getLogger(PlugwiseHADiscoveryService.class); + private static final int TIMEOUT_SECONDS = 5; + private static final int REFRESH_SECONDS = 600; + private @Nullable PlugwiseHABridgeHandler bridgeHandler; + private @Nullable ScheduledFuture discoveryFuture; + + public PlugwiseHADiscoveryService() { + super(SUPPORTED_THING_TYPES_UIDS, TIMEOUT_SECONDS, true); + } + + @Override + protected synchronized void startScan() { + try { + discoverDomainObjects(); + } catch (PlugwiseHAException e) { + // Ignore silently + } + } + + @Override + protected synchronized void stopScan() { + super.stopScan(); + removeOlderResults(getTimestampOfLastScan()); + } + + @Override + protected void startBackgroundDiscovery() { + logger.debug("Start Plugwise Home Automation background discovery"); + + ScheduledFuture localDiscoveryFuture = discoveryFuture; + if (localDiscoveryFuture == null || localDiscoveryFuture.isCancelled()) { + discoveryFuture = scheduler.scheduleWithFixedDelay(this::startScan, 30, REFRESH_SECONDS, TimeUnit.SECONDS); + } + } + + @Override + protected void stopBackgroundDiscovery() { + logger.debug("Stopping Plugwise Home Automation background discovery"); + + ScheduledFuture localDiscoveryFuture = discoveryFuture; + if (localDiscoveryFuture != null) { + if (!localDiscoveryFuture.isCancelled()) { + localDiscoveryFuture.cancel(true); + localDiscoveryFuture = null; + } + } + } + + @Override + public void deactivate() { + super.deactivate(); + } + + @Override + public void setThingHandler(@Nullable ThingHandler handler) { + if (handler instanceof PlugwiseHABridgeHandler) { + bridgeHandler = (PlugwiseHABridgeHandler) handler; + } + } + + @Override + public @Nullable ThingHandler getThingHandler() { + return bridgeHandler; + } + + private void discoverDomainObjects() throws PlugwiseHAException { + PlugwiseHAController controller = null; + PlugwiseHABridgeHandler localBridgeHandler = this.bridgeHandler; + if (localBridgeHandler != null) { + controller = localBridgeHandler.getController(); + } + + if (controller != null) { + DomainObjects domainObjects = controller.getDomainObjects(); + + if (domainObjects != null) { + for (Location location : domainObjects.getLocations().values()) { + // Only add locations with at least 1 appliance (this ignores the 'root' (home) + // location which is the parent of all other locations.) + if (location.applianceCount() > 0) { + locationDiscovery(location); + } + } + + for (Appliance appliance : domainObjects.getAppliances().values()) { + // Only add appliances that are required/supported for this binding + if (PlugwiseHABindingConstants.SUPPORTED_APPLIANCE_TYPES.contains(appliance.getType())) { + applianceDiscovery(appliance); + } + } + } + } + } + + private void applianceDiscovery(Appliance appliance) { + String applianceId = appliance.getId(); + String applianceName = appliance.getName(); + String applianceType = appliance.getType(); + + PlugwiseHABridgeHandler localBridgeHandler = this.bridgeHandler; + if (localBridgeHandler != null) { + ThingUID bridgeUID = localBridgeHandler.getThing().getUID(); + + ThingUID uid; + + Map configProperties = new HashMap<>(); + + configProperties.put(APPLIANCE_CONFIG_ID, applianceId); + + switch (applianceType) { + case "thermostatic_radiator_valve": + uid = new ThingUID(PlugwiseHABindingConstants.THING_TYPE_APPLIANCE_VALVE, bridgeUID, applianceId); + configProperties.put(APPLIANCE_CONFIG_LOWBATTERY, 15); + break; + case "central_heating_pump": + uid = new ThingUID(PlugwiseHABindingConstants.THING_TYPE_APPLIANCE_PUMP, bridgeUID, applianceId); + break; + case "heater_central": + uid = new ThingUID(PlugwiseHABindingConstants.THING_TYPE_APPLIANCE_BOILER, bridgeUID, applianceId); + break; + case "zone_thermostat": + uid = new ThingUID(PlugwiseHABindingConstants.THING_TYPE_APPLIANCE_THERMOSTAT, bridgeUID, + applianceId); + configProperties.put(APPLIANCE_CONFIG_LOWBATTERY, 15); + break; + default: + return; + } + + DiscoveryResult discoveryResult = DiscoveryResultBuilder.create(uid).withBridge(bridgeUID) + .withLabel(applianceName).withProperties(configProperties) + .withRepresentationProperty(APPLIANCE_CONFIG_ID).build(); + + thingDiscovered(discoveryResult); + + logger.debug("Discovered plugwise appliance type '{}' with name '{}' with id {} ({})", applianceType, + applianceName, applianceId, uid); + } + } + + private void locationDiscovery(Location location) { + String locationId = location.getId(); + String locationName = location.getName(); + + PlugwiseHABridgeHandler localBridgeHandler = this.bridgeHandler; + if (localBridgeHandler != null) { + ThingUID bridgeUID = localBridgeHandler.getThing().getUID(); + ThingUID uid = new ThingUID(PlugwiseHABindingConstants.THING_TYPE_ZONE, bridgeUID, locationId); + + Map configProperties = new HashMap<>(); + + configProperties.put(ZONE_CONFIG_ID, locationId); + + DiscoveryResult discoveryResult = DiscoveryResultBuilder.create(uid).withBridge(bridgeUID) + .withLabel(locationName).withRepresentationProperty(ZONE_CONFIG_ID).withProperties(configProperties) + .build(); + + thingDiscovered(discoveryResult); + + logger.debug("Discovered plugwise zone '{}' with id {} ({})", locationName, locationId, uid); + } + } +} diff --git a/bundles/org.openhab.binding.plugwiseha/src/main/java/org/openhab/binding/plugwiseha/internal/handler/PlugwiseHAApplianceHandler.java b/bundles/org.openhab.binding.plugwiseha/src/main/java/org/openhab/binding/plugwiseha/internal/handler/PlugwiseHAApplianceHandler.java new file mode 100644 index 00000000000..a93d4653a4f --- /dev/null +++ b/bundles/org.openhab.binding.plugwiseha/src/main/java/org/openhab/binding/plugwiseha/internal/handler/PlugwiseHAApplianceHandler.java @@ -0,0 +1,496 @@ +/** + * 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.plugwiseha.internal.handler; + +import static org.openhab.binding.plugwiseha.internal.PlugwiseHABindingConstants.*; +import static org.openhab.core.library.unit.MetricPrefix.*; +import static org.openhab.core.thing.ThingStatus.*; +import static org.openhab.core.thing.ThingStatusDetail.BRIDGE_OFFLINE; +import static org.openhab.core.thing.ThingStatusDetail.COMMUNICATION_ERROR; +import static org.openhab.core.thing.ThingStatusDetail.CONFIGURATION_ERROR; + +import java.util.List; +import java.util.Map; + +import javax.measure.Unit; +import javax.measure.quantity.Dimensionless; +import javax.measure.quantity.Power; +import javax.measure.quantity.Pressure; +import javax.measure.quantity.Temperature; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; +import org.openhab.binding.plugwiseha.internal.PlugwiseHABindingConstants; +import org.openhab.binding.plugwiseha.internal.api.exception.PlugwiseHAException; +import org.openhab.binding.plugwiseha.internal.api.model.PlugwiseHAController; +import org.openhab.binding.plugwiseha.internal.api.model.dto.Appliance; +import org.openhab.binding.plugwiseha.internal.config.PlugwiseHAThingConfig; +import org.openhab.core.library.types.OnOffType; +import org.openhab.core.library.types.QuantityType; +import org.openhab.core.library.unit.ImperialUnits; +import org.openhab.core.library.unit.SIUnits; +import org.openhab.core.library.unit.Units; +import org.openhab.core.thing.Channel; +import org.openhab.core.thing.ChannelUID; +import org.openhab.core.thing.Thing; +import org.openhab.core.thing.ThingTypeUID; +import org.openhab.core.thing.binding.builder.ChannelBuilder; +import org.openhab.core.thing.binding.builder.ThingBuilder; +import org.openhab.core.thing.type.ChannelKind; +import org.openhab.core.types.Command; +import org.openhab.core.types.State; +import org.openhab.core.types.UnDefType; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * The {@link PlugwiseHAApplianceHandler} class is responsible for handling + * commands and status updates for the Plugwise Home Automation appliances. + * Extends @{link PlugwiseHABaseHandler} + * + * @author Bas van Wetten - Initial contribution + * @author Leo Siepel - finish initial contribution + * + */ +@NonNullByDefault +public class PlugwiseHAApplianceHandler extends PlugwiseHABaseHandler { + + private @Nullable Appliance appliance; + private final Logger logger = LoggerFactory.getLogger(PlugwiseHAApplianceHandler.class); + + // Constructor + + public PlugwiseHAApplianceHandler(Thing thing) { + super(thing); + } + + public static boolean supportsThingType(ThingTypeUID thingTypeUID) { + return PlugwiseHABindingConstants.THING_TYPE_APPLIANCE_VALVE.equals(thingTypeUID) + || PlugwiseHABindingConstants.THING_TYPE_APPLIANCE_PUMP.equals(thingTypeUID) + || PlugwiseHABindingConstants.THING_TYPE_APPLIANCE_BOILER.equals(thingTypeUID) + || PlugwiseHABindingConstants.THING_TYPE_APPLIANCE_THERMOSTAT.equals(thingTypeUID); + } + + // Overrides + + @Override + protected synchronized void initialize(PlugwiseHAThingConfig config, PlugwiseHABridgeHandler bridgeHandler) { + if (thing.getStatus() == INITIALIZING) { + logger.debug("Initializing Plugwise Home Automation appliance handler with config = {}", config); + if (!config.isValid()) { + updateStatus(OFFLINE, CONFIGURATION_ERROR, + "Invalid configuration for Plugwise Home Automation appliance handler."); + return; + } + + try { + PlugwiseHAController controller = bridgeHandler.getController(); + if (controller != null) { + this.appliance = getEntity(controller, true); + Appliance localAppliance = this.appliance; + if (localAppliance != null) { + if (localAppliance.isBatteryOperated()) { + addBatteryChannels(); + } + setApplianceProperties(); + updateStatus(ONLINE); + } else { + updateStatus(OFFLINE); + } + } else { + updateStatus(OFFLINE, BRIDGE_OFFLINE); + } + } catch (PlugwiseHAException e) { + updateStatus(OFFLINE, COMMUNICATION_ERROR, e.getMessage()); + } + } + } + + @Override + protected @Nullable Appliance getEntity(PlugwiseHAController controller, Boolean forceRefresh) + throws PlugwiseHAException { + PlugwiseHAThingConfig config = getPlugwiseThingConfig(); + Appliance appliance = controller.getAppliance(config.getId(), forceRefresh); + + return appliance; + } + + @Override + protected void handleCommand(Appliance entity, ChannelUID channelUID, Command command) throws PlugwiseHAException { + String channelID = channelUID.getIdWithoutGroup(); + + PlugwiseHABridgeHandler bridge = this.getPlugwiseHABridge(); + if (bridge == null) { + return; + } + + PlugwiseHAController controller = bridge.getController(); + if (controller == null) { + return; + } + + switch (channelID) { + case APPLIANCE_LOCK_CHANNEL: + if (command instanceof OnOffType) { + try { + if (command == OnOffType.ON) { + controller.switchRelayLockOn(entity); + } else { + controller.switchRelayLockOff(entity); + } + } catch (PlugwiseHAException e) { + logger.warn("Unable to switch relay lock {} for appliance '{}'", (State) command, + entity.getName()); + } + } + break; + case APPLIANCE_OFFSET_CHANNEL: + if (command instanceof QuantityType) { + Unit unit = entity.getOffsetTemperatureUnit().orElse(UNIT_CELSIUS).equals(UNIT_CELSIUS) + ? SIUnits.CELSIUS + : ImperialUnits.FAHRENHEIT; + QuantityType state = ((QuantityType) command).toUnit(unit); + + if (state != null) { + try { + controller.setOffsetTemperature(entity, state.doubleValue()); + } catch (PlugwiseHAException e) { + logger.warn("Unable to update setpoint for zone '{}': {} -> {}", entity.getName(), + entity.getSetpointTemperature().orElse(null), state.doubleValue()); + } + } + } + break; + case APPLIANCE_POWER_CHANNEL: + if (command instanceof OnOffType) { + try { + if (command == OnOffType.ON) { + controller.switchRelayOn(entity); + } else { + controller.switchRelayOff(entity); + } + } catch (PlugwiseHAException e) { + logger.warn("Unable to switch relay {} for appliance '{}'", (State) command, entity.getName()); + } + } + break; + case APPLIANCE_SETPOINT_CHANNEL: + if (command instanceof QuantityType) { + Unit unit = entity.getSetpointTemperatureUnit().orElse(UNIT_CELSIUS) + .equals(UNIT_CELSIUS) ? SIUnits.CELSIUS : ImperialUnits.FAHRENHEIT; + QuantityType state = ((QuantityType) command).toUnit(unit); + + if (state != null) { + try { + controller.setThermostat(entity, state.doubleValue()); + } catch (PlugwiseHAException e) { + logger.warn("Unable to update setpoint for appliance '{}': {} -> {}", entity.getName(), + entity.getSetpointTemperature().orElse(null), state.doubleValue()); + } + } + } + break; + default: + logger.warn("Ignoring unsupported command = {} for channel = {}", command, channelUID); + } + } + + private State getDefaultState(String channelID) { + State state = UnDefType.NULL; + switch (channelID) { + case APPLIANCE_BATTERYLEVEL_CHANNEL: + case APPLIANCE_CHSTATE_CHANNEL: + case APPLIANCE_DHWSTATE_CHANNEL: + case APPLIANCE_COOLINGSTATE_CHANNEL: + case APPLIANCE_INTENDEDBOILERTEMP_CHANNEL: + case APPLIANCE_FLAMESTATE_CHANNEL: + case APPLIANCE_INTENDEDHEATINGSTATE_CHANNEL: + case APPLIANCE_MODULATIONLEVEL_CHANNEL: + case APPLIANCE_OTAPPLICATIONFAULTCODE_CHANNEL: + case APPLIANCE_DHWTEMPERATURE_CHANNEL: + case APPLIANCE_OTOEMFAULTCODE_CHANNEL: + case APPLIANCE_BOILERTEMPERATURE_CHANNEL: + case APPLIANCE_DHWSETPOINT_CHANNEL: + case APPLIANCE_MAXBOILERTEMPERATURE_CHANNEL: + case APPLIANCE_DHWCOMFORTMODE_CHANNEL: + case APPLIANCE_OFFSET_CHANNEL: + case APPLIANCE_POWER_USAGE_CHANNEL: + case APPLIANCE_SETPOINT_CHANNEL: + case APPLIANCE_TEMPERATURE_CHANNEL: + case APPLIANCE_VALVEPOSITION_CHANNEL: + case APPLIANCE_WATERPRESSURE_CHANNEL: + state = UnDefType.NULL; + break; + case APPLIANCE_BATTERYLEVELLOW_CHANNEL: + case APPLIANCE_LOCK_CHANNEL: + case APPLIANCE_POWER_CHANNEL: + state = UnDefType.UNDEF; + break; + } + return state; + } + + @Override + protected void refreshChannel(Appliance entity, ChannelUID channelUID) { + String channelID = channelUID.getIdWithoutGroup(); + State state = getDefaultState(channelID); + PlugwiseHAThingConfig config = getPlugwiseThingConfig(); + + switch (channelID) { + case APPLIANCE_BATTERYLEVEL_CHANNEL: { + Double batteryLevel = entity.getBatteryLevel().orElse(null); + + if (batteryLevel != null) { + batteryLevel = batteryLevel * 100; + state = new QuantityType(batteryLevel.intValue(), Units.PERCENT); + if (batteryLevel <= config.getLowBatteryPercentage()) { + updateState(APPLIANCE_BATTERYLEVELLOW_CHANNEL, OnOffType.ON); + } else { + updateState(APPLIANCE_BATTERYLEVELLOW_CHANNEL, OnOffType.OFF); + } + } + break; + } + case APPLIANCE_BATTERYLEVELLOW_CHANNEL: { + Double batteryLevel = entity.getBatteryLevel().orElse(null); + + if (batteryLevel != null) { + batteryLevel *= 100; + if (batteryLevel <= config.getLowBatteryPercentage()) { + state = OnOffType.ON; + } else { + state = OnOffType.OFF; + } + } + break; + } + case APPLIANCE_CHSTATE_CHANNEL: + if (entity.getCHState().isPresent()) { + state = OnOffType.from(entity.getCHState().get()); + } + break; + case APPLIANCE_DHWSTATE_CHANNEL: + if (entity.getDHWState().isPresent()) { + state = OnOffType.from(entity.getDHWState().get()); + } + break; + case APPLIANCE_LOCK_CHANNEL: + Boolean relayLockState = entity.getRelayLockState().orElse(null); + if (relayLockState != null) { + state = OnOffType.from(relayLockState); + } + break; + case APPLIANCE_OFFSET_CHANNEL: + if (entity.getOffsetTemperature().isPresent()) { + Unit unit = entity.getOffsetTemperatureUnit().orElse(UNIT_CELSIUS).equals(UNIT_CELSIUS) + ? SIUnits.CELSIUS + : ImperialUnits.FAHRENHEIT; + state = new QuantityType(entity.getOffsetTemperature().get(), unit); + } + break; + case APPLIANCE_POWER_CHANNEL: + if (entity.getRelayState().isPresent()) { + state = OnOffType.from(entity.getRelayState().get()); + } + break; + case APPLIANCE_POWER_USAGE_CHANNEL: + if (entity.getPowerUsage().isPresent()) { + state = new QuantityType(entity.getPowerUsage().get(), Units.WATT); + } + break; + case APPLIANCE_SETPOINT_CHANNEL: + if (entity.getSetpointTemperature().isPresent()) { + Unit unit = entity.getSetpointTemperatureUnit().orElse(UNIT_CELSIUS) + .equals(UNIT_CELSIUS) ? SIUnits.CELSIUS : ImperialUnits.FAHRENHEIT; + state = new QuantityType(entity.getSetpointTemperature().get(), unit); + } + break; + case APPLIANCE_TEMPERATURE_CHANNEL: + if (entity.getTemperature().isPresent()) { + Unit unit = entity.getTemperatureUnit().orElse(UNIT_CELSIUS).equals(UNIT_CELSIUS) + ? SIUnits.CELSIUS + : ImperialUnits.FAHRENHEIT; + state = new QuantityType(entity.getTemperature().get(), unit); + } + break; + case APPLIANCE_VALVEPOSITION_CHANNEL: + if (entity.getValvePosition().isPresent()) { + Double valvePosition = entity.getValvePosition().get() * 100; + state = new QuantityType(valvePosition.intValue(), Units.PERCENT); + } + break; + case APPLIANCE_WATERPRESSURE_CHANNEL: + if (entity.getWaterPressure().isPresent()) { + Unit unit = HECTO(SIUnits.PASCAL); + state = new QuantityType(entity.getWaterPressure().get(), unit); + } + break; + case APPLIANCE_COOLINGSTATE_CHANNEL: + if (entity.getCoolingState().isPresent()) { + state = OnOffType.from(entity.getCoolingState().get()); + } + break; + case APPLIANCE_INTENDEDBOILERTEMP_CHANNEL: + if (entity.getIntendedBoilerTemp().isPresent()) { + Unit unit = entity.getIntendedBoilerTempUnit().orElse(UNIT_CELSIUS) + .equals(UNIT_CELSIUS) ? SIUnits.CELSIUS : ImperialUnits.FAHRENHEIT; + state = new QuantityType(entity.getIntendedBoilerTemp().get(), unit); + } + break; + case APPLIANCE_FLAMESTATE_CHANNEL: + if (entity.getFlameState().isPresent()) { + state = OnOffType.from(entity.getFlameState().get()); + } + break; + case APPLIANCE_INTENDEDHEATINGSTATE_CHANNEL: + if (entity.getIntendedHeatingState().isPresent()) { + state = OnOffType.from(entity.getIntendedHeatingState().get()); + } + break; + case APPLIANCE_MODULATIONLEVEL_CHANNEL: + if (entity.getModulationLevel().isPresent()) { + Double modulationLevel = entity.getModulationLevel().get() * 100; + state = new QuantityType(modulationLevel.intValue(), Units.PERCENT); + } + break; + case APPLIANCE_OTAPPLICATIONFAULTCODE_CHANNEL: + if (entity.getOTAppFaultCode().isPresent()) { + state = new QuantityType(entity.getOTAppFaultCode().get().intValue(), Units.PERCENT); + } + break; + case APPLIANCE_DHWTEMPERATURE_CHANNEL: + if (entity.getDHWTemp().isPresent()) { + Unit unit = entity.getDHWTempUnit().orElse(UNIT_CELSIUS).equals(UNIT_CELSIUS) + ? SIUnits.CELSIUS + : ImperialUnits.FAHRENHEIT; + state = new QuantityType(entity.getDHWTemp().get(), unit); + } + break; + case APPLIANCE_OTOEMFAULTCODE_CHANNEL: + if (entity.getOTOEMFaultcode().isPresent()) { + state = new QuantityType(entity.getOTOEMFaultcode().get().intValue(), Units.PERCENT); + } + break; + case APPLIANCE_BOILERTEMPERATURE_CHANNEL: + if (entity.getBoilerTemp().isPresent()) { + Unit unit = entity.getBoilerTempUnit().orElse(UNIT_CELSIUS).equals(UNIT_CELSIUS) + ? SIUnits.CELSIUS + : ImperialUnits.FAHRENHEIT; + state = new QuantityType(entity.getBoilerTemp().get(), unit); + } + break; + case APPLIANCE_DHWSETPOINT_CHANNEL: + if (entity.getDHTSetpoint().isPresent()) { + Unit unit = entity.getDHTSetpointUnit().orElse(UNIT_CELSIUS).equals(UNIT_CELSIUS) + ? SIUnits.CELSIUS + : ImperialUnits.FAHRENHEIT; + state = new QuantityType(entity.getDHTSetpoint().get(), unit); + } + break; + case APPLIANCE_MAXBOILERTEMPERATURE_CHANNEL: + if (entity.getMaxBoilerTemp().isPresent()) { + Unit unit = entity.getMaxBoilerTempUnit().orElse(UNIT_CELSIUS).equals(UNIT_CELSIUS) + ? SIUnits.CELSIUS + : ImperialUnits.FAHRENHEIT; + state = new QuantityType(entity.getMaxBoilerTemp().get(), unit); + } + break; + case APPLIANCE_DHWCOMFORTMODE_CHANNEL: + if (entity.getDHWComfortMode().isPresent()) { + state = OnOffType.from(entity.getDHWComfortMode().get()); + } + break; + default: + break; + } + + if (state != UnDefType.NULL) { + updateState(channelID, state); + } + } + + protected synchronized void addBatteryChannels() { + logger.debug("Battery operated appliance: {} detected: adding 'Battery level' and 'Battery low level' channels", + thing.getLabel()); + + ChannelUID channelUIDBatteryLevel = new ChannelUID(getThing().getUID(), APPLIANCE_BATTERYLEVEL_CHANNEL); + ChannelUID channelUIDBatteryLevelLow = new ChannelUID(getThing().getUID(), APPLIANCE_BATTERYLEVELLOW_CHANNEL); + + boolean channelBatteryLevelExists = false; + boolean channelBatteryLowExists = false; + + List channels = getThing().getChannels(); + for (Channel channel : channels) { + if (channel.getUID().equals(channelUIDBatteryLevel)) { + channelBatteryLevelExists = true; + } else if (channel.getUID().equals(channelUIDBatteryLevelLow)) { + channelBatteryLowExists = true; + } + if (channelBatteryLevelExists && channelBatteryLowExists) { + break; + } + } + + if (!channelBatteryLevelExists) { + ThingBuilder thingBuilder = editThing(); + + Channel channelBatteryLevel = ChannelBuilder.create(channelUIDBatteryLevel, "Number") + .withType(CHANNEL_TYPE_BATTERYLEVEL).withKind(ChannelKind.STATE).withLabel("Battery Level") + .withDescription("Represents the battery level as a percentage (0-100%)").build(); + + thingBuilder.withChannel(channelBatteryLevel); + + updateThing(thingBuilder.build()); + } + + if (!channelBatteryLowExists) { + ThingBuilder thingBuilder = editThing(); + + Channel channelBatteryLow = ChannelBuilder.create(channelUIDBatteryLevelLow, "Switch") + .withType(CHANNEL_TYPE_BATTERYLEVELLOW).withKind(ChannelKind.STATE).withLabel("Battery Low Level") + .withDescription("Switches ON when battery level gets below threshold level").build(); + + thingBuilder.withChannel(channelBatteryLow); + + updateThing(thingBuilder.build()); + } + } + + protected void setApplianceProperties() { + Map properties = editProperties(); + logger.debug("Setting thing properties to {}", thing.getLabel()); + Appliance localAppliance = this.appliance; + if (localAppliance != null) { + properties.put(PlugwiseHABindingConstants.APPLIANCE_PROPERTY_DESCRIPTION, localAppliance.getDescription()); + properties.put(PlugwiseHABindingConstants.APPLIANCE_PROPERTY_TYPE, localAppliance.getType()); + properties.put(PlugwiseHABindingConstants.APPLIANCE_PROPERTY_FUNCTIONALITIES, + String.join(", ", localAppliance.getActuatorFunctionalities().keySet())); + + if (localAppliance.isZigbeeDevice()) { + properties.put(PlugwiseHABindingConstants.APPLIANCE_PROPERTY_ZB_TYPE, + localAppliance.getZigbeeNode().getType()); + properties.put(PlugwiseHABindingConstants.APPLIANCE_PROPERTY_ZB_REACHABLE, + localAppliance.getZigbeeNode().getReachable()); + properties.put(PlugwiseHABindingConstants.APPLIANCE_PROPERTY_ZB_POWERSOURCE, + localAppliance.getZigbeeNode().getPowerSource()); + properties.put(Thing.PROPERTY_MAC_ADDRESS, localAppliance.getZigbeeNode().getMacAddress()); + } + + properties.put(Thing.PROPERTY_FIRMWARE_VERSION, localAppliance.getModule().getFirmwareVersion()); + properties.put(Thing.PROPERTY_HARDWARE_VERSION, localAppliance.getModule().getHardwareVersion()); + properties.put(Thing.PROPERTY_VENDOR, localAppliance.getModule().getVendorName()); + properties.put(Thing.PROPERTY_MODEL_ID, localAppliance.getModule().getVendorModel()); + } + updateProperties(properties); + } +} diff --git a/bundles/org.openhab.binding.plugwiseha/src/main/java/org/openhab/binding/plugwiseha/internal/handler/PlugwiseHABaseHandler.java b/bundles/org.openhab.binding.plugwiseha/src/main/java/org/openhab/binding/plugwiseha/internal/handler/PlugwiseHABaseHandler.java new file mode 100644 index 00000000000..be4794559e4 --- /dev/null +++ b/bundles/org.openhab.binding.plugwiseha/src/main/java/org/openhab/binding/plugwiseha/internal/handler/PlugwiseHABaseHandler.java @@ -0,0 +1,245 @@ +/** + * 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.plugwiseha.internal.handler; + +import static org.openhab.core.thing.ThingStatus.*; + +import java.lang.reflect.ParameterizedType; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; +import org.openhab.binding.plugwiseha.internal.api.exception.PlugwiseHAException; +import org.openhab.binding.plugwiseha.internal.api.model.PlugwiseHAController; +import org.openhab.binding.plugwiseha.internal.config.PlugwiseHAThingConfig; +import org.openhab.core.thing.Bridge; +import org.openhab.core.thing.Channel; +import org.openhab.core.thing.ChannelUID; +import org.openhab.core.thing.Thing; +import org.openhab.core.thing.ThingStatus; +import org.openhab.core.thing.ThingStatusDetail; +import org.openhab.core.thing.ThingStatusInfo; +import org.openhab.core.thing.binding.BaseThingHandler; +import org.openhab.core.types.Command; +import org.openhab.core.types.RefreshType; +import org.openhab.core.types.UnDefType; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * The {@link PlugwiseHABaseHandler} abstract class provides common methods and + * properties for the ThingHandlers of this binding. Extends @{link + * BaseThingHandler} + * + * @author Bas van Wetten - Initial contribution + * @author Leo Siepel - finish initial contribution + * + * @param entity - the Plugwise Home Automation entity class used by this + * thing handler + * @param config - the Plugwise Home Automation config class used by this + * thing handler + */ + +@NonNullByDefault +public abstract class PlugwiseHABaseHandler extends BaseThingHandler { + + protected static final String STATUS_DESCRIPTION_COMMUNICATION_ERROR = "Error communicating with the Plugwise Home Automation controller"; + + protected final Logger logger = LoggerFactory.getLogger(PlugwiseHABaseHandler.class); + + private Class clazz; + + // Constructor + @SuppressWarnings("null") + public PlugwiseHABaseHandler(Thing thing) { + super(thing); + clazz = (Class) (((ParameterizedType) getClass().getGenericSuperclass()).getActualTypeArguments()[1]); + } + + // Abstract methods + + /** + * Initializes the Plugwise Entity that this class handles. + * + * @param config the thing configuration + * @param bridge the bridge that this thing is part of + */ + protected abstract void initialize(C config, PlugwiseHABridgeHandler bridge); + + /** + * Get the Plugwise Entity that belongs to this ThingHandler + * + * @param controller the controller for this ThingHandler + * @param forceRefresh indicated if the entity should be refreshed from the Plugwise API + */ + protected abstract @Nullable E getEntity(PlugwiseHAController controller, Boolean forceRefresh) + throws PlugwiseHAException; + + /** + * Handles a {@link RefreshType} command for a given channel. + * + * @param entity the Plugwise Entity + * @param channelUID the channel uid the command is for + */ + protected abstract void refreshChannel(E entity, ChannelUID channelUID); + + /** + * Handles a command for a given channel. + * + * @param entity the Plugwise Entity + * @param channelUID the channel uid the command is for + * @param command the command + */ + protected abstract void handleCommand(E entity, ChannelUID channelUID, Command command) throws PlugwiseHAException; + + // Overrides + + @Override + public void initialize() { + C config = getPlugwiseThingConfig(); + + if (checkConfig(config)) { + Bridge bridge = getBridge(); + if (bridge == null || bridge.getHandler() == null + || !(bridge.getHandler() instanceof PlugwiseHABridgeHandler)) { + updateStatus(OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, + "You must choose a Plugwise Home Automation bridge for this thing."); + return; + } + + if (bridge.getStatus() == OFFLINE) { + updateStatus(OFFLINE, ThingStatusDetail.BRIDGE_OFFLINE, + "The Plugwise Home Automation bridge is currently offline."); + } + + PlugwiseHABridgeHandler bridgeHandler = (PlugwiseHABridgeHandler) bridge.getHandler(); + if (bridgeHandler != null) { + initialize(config, bridgeHandler); + } + } else { + logger.debug("Invalid config for Plugwise Home Automation thing handler with config = {}", config); + } + } + + @Override + public final void handleCommand(ChannelUID channelUID, Command command) { + logger.debug("Handling command = {} for channel = {}", command, channelUID); + + if (getThing().getStatus() == ONLINE) { + PlugwiseHAController controller = getController(); + if (controller != null) { + try { + @Nullable + E entity = getEntity(controller, false); + if (entity != null) { + if (this.isLinked(channelUID)) { + if (command instanceof RefreshType) { + refreshChannel(entity, channelUID); + } else { + handleCommand(entity, channelUID, command); + } + } + } + } catch (PlugwiseHAException e) { + logger.warn("Unexpected error handling command = {} for channel = {} : {}", command, channelUID, + e.getMessage()); + } + } + } + } + + // Public member methods + + public @Nullable PlugwiseHABridgeHandler getPlugwiseHABridge() { + Bridge bridge = this.getBridge(); + if (bridge != null) { + return (PlugwiseHABridgeHandler) bridge.getHandler(); + } + + return null; + } + + @SuppressWarnings("unchecked") + public C getPlugwiseThingConfig() { + return (C) getConfigAs(clazz); + } + + // Private & protected methods + + private @Nullable PlugwiseHAController getController() { + PlugwiseHABridgeHandler bridgeHandler = getPlugwiseHABridge(); + + if (bridgeHandler != null) { + return bridgeHandler.getController(); + } + + return null; + } + + /** + * Checks the configuration for validity, result is reflected in the status of + * the Thing + */ + private boolean checkConfig(C config) { + if (!config.isValid()) { + updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, + "Configuration is missing or corrupted"); + return false; + } else { + return true; + } + } + + @Override + public void bridgeStatusChanged(ThingStatusInfo bridgeStatusInfo) { + super.bridgeStatusChanged(bridgeStatusInfo); + if (bridgeStatusInfo.getStatus() == ThingStatus.OFFLINE) { + setLinkedChannelsUndef(); + } + } + + private void setLinkedChannelsUndef() { + for (Channel channel : getThing().getChannels()) { + ChannelUID channelUID = channel.getUID(); + if (this.isLinked(channelUID)) { + updateState(channelUID, UnDefType.UNDEF); + } + } + } + + protected final void refresh() { + PlugwiseHABridgeHandler bridgeHandler = getPlugwiseHABridge(); + if (bridgeHandler != null) { + if (bridgeHandler.getThing().getStatusInfo().getStatus() == ThingStatus.ONLINE) { + PlugwiseHAController controller = getController(); + if (controller != null) { + @Nullable + E entity = null; + try { + entity = getEntity(controller, false); + } catch (PlugwiseHAException e) { + updateStatus(OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, e.getMessage()); + setLinkedChannelsUndef(); + } + if (entity != null) { + for (Channel channel : getThing().getChannels()) { + ChannelUID channelUID = channel.getUID(); + if (this.isLinked(channelUID)) { + refreshChannel(entity, channelUID); + } + } + } + } + } + } + } +} diff --git a/bundles/org.openhab.binding.plugwiseha/src/main/java/org/openhab/binding/plugwiseha/internal/handler/PlugwiseHABridgeHandler.java b/bundles/org.openhab.binding.plugwiseha/src/main/java/org/openhab/binding/plugwiseha/internal/handler/PlugwiseHABridgeHandler.java new file mode 100644 index 00000000000..a2cc48c7bc1 --- /dev/null +++ b/bundles/org.openhab.binding.plugwiseha/src/main/java/org/openhab/binding/plugwiseha/internal/handler/PlugwiseHABridgeHandler.java @@ -0,0 +1,252 @@ +/** + * 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.plugwiseha.internal.handler; + +import static org.openhab.binding.plugwiseha.internal.PlugwiseHABindingConstants.*; +import static org.openhab.core.thing.ThingStatus.OFFLINE; +import static org.openhab.core.thing.ThingStatus.ONLINE; +import static org.openhab.core.thing.ThingStatusDetail.*; + +import java.util.Collection; +import java.util.Collections; +import java.util.Map; +import java.util.concurrent.ScheduledFuture; +import java.util.concurrent.TimeUnit; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; +import org.eclipse.jetty.client.HttpClient; +import org.openhab.binding.plugwiseha.internal.api.exception.PlugwiseHACommunicationException; +import org.openhab.binding.plugwiseha.internal.api.exception.PlugwiseHAException; +import org.openhab.binding.plugwiseha.internal.api.exception.PlugwiseHAInvalidHostException; +import org.openhab.binding.plugwiseha.internal.api.exception.PlugwiseHANotAuthorizedException; +import org.openhab.binding.plugwiseha.internal.api.exception.PlugwiseHATimeoutException; +import org.openhab.binding.plugwiseha.internal.api.exception.PlugwiseHAUnauthorizedException; +import org.openhab.binding.plugwiseha.internal.api.model.PlugwiseHAController; +import org.openhab.binding.plugwiseha.internal.api.model.PlugwiseHAModel; +import org.openhab.binding.plugwiseha.internal.api.model.dto.GatewayInfo; +import org.openhab.binding.plugwiseha.internal.config.PlugwiseHABridgeThingConfig; +import org.openhab.binding.plugwiseha.internal.config.PlugwiseHAThingConfig; +import org.openhab.binding.plugwiseha.internal.discovery.PlugwiseHADiscoveryService; +import org.openhab.core.thing.Bridge; +import org.openhab.core.thing.ChannelUID; +import org.openhab.core.thing.Thing; +import org.openhab.core.thing.ThingStatus; +import org.openhab.core.thing.ThingTypeUID; +import org.openhab.core.thing.binding.BaseBridgeHandler; +import org.openhab.core.thing.binding.ThingHandler; +import org.openhab.core.thing.binding.ThingHandlerService; +import org.openhab.core.types.Command; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * The {@link PlugwiseHABridgeHandler} class is responsible for handling + * commands and status updates for the Plugwise Home Automation bridge. + * Extends @{link BaseBridgeHandler} + * + * @author Bas van Wetten - Initial contribution + * @author Leo Siepel - finish initial contribution + * + */ + +@NonNullByDefault +public class PlugwiseHABridgeHandler extends BaseBridgeHandler { + + // Private Static error messages + + private static final String STATUS_DESCRIPTION_COMMUNICATION_ERROR = "Error communicating with the Plugwise Home Automation controller"; + private static final String STATUS_DESCRIPTION_TIMEOUT = "Communication timeout while communicating with the Plugwise Home Automation controller"; + private static final String STATUS_DESCRIPTION_CONFIGURATION_ERROR = "Invalid or missing configuration"; + private static final String STATUS_DESCRIPTION_INVALID_CREDENTIALS = "Invalid username and/or password - please double-check your configuration"; + private static final String STATUS_DESCRIPTION_INVALID_HOSTNAME = "Invalid hostname - please double-check your configuration"; + + // Private member variables/constants + private @Nullable ScheduledFuture refreshJob; + private @Nullable volatile PlugwiseHAController controller; + + private final HttpClient httpClient; + private final Logger logger = LoggerFactory.getLogger(PlugwiseHABridgeHandler.class); + + // Constructor + + public PlugwiseHABridgeHandler(Bridge bridge, HttpClient httpClient) { + super(bridge); + this.httpClient = httpClient; + } + + // Public methods + + @Override + public void initialize() { + PlugwiseHABridgeThingConfig bridgeConfig = getConfigAs(PlugwiseHABridgeThingConfig.class); + + if (this.checkConfig(bridgeConfig)) { + logger.debug("Initializing the Plugwise Home Automation bridge handler with config = {}", bridgeConfig); + try { + this.controller = new PlugwiseHAController(httpClient, bridgeConfig.getHost(), bridgeConfig.getPort(), + bridgeConfig.getUsername(), bridgeConfig.getsmileId()); + scheduleRefreshJob(bridgeConfig); + } catch (PlugwiseHAException e) { + updateStatus(OFFLINE, CONFIGURATION_ERROR, e.getMessage()); + } + } else { + logger.warn("Invalid config for the Plugwise Home Automation bridge handler with config = {}", + bridgeConfig); + } + } + + @Override + public Collection> getServices() { + return Collections.singleton(PlugwiseHADiscoveryService.class); + } + + @Override + public void handleCommand(ChannelUID channelUID, Command command) { + this.logger.warn( + "Ignoring command = {} for channel = {} - this channel for the Plugwise Home Automation binding is read-only!", + command, channelUID); + } + + @Override + public void dispose() { + cancelRefreshJob(); + if (this.controller != null) { + this.controller = null; + } + } + + public static boolean supportsThingType(ThingTypeUID thingTypeUID) { + return SUPPORTED_BRIDGE_TYPES_UIDS.contains(thingTypeUID); + } + + // Getters & setters + + public @Nullable PlugwiseHAController getController() { + return this.controller; + } + + // Protected and private methods + + /** + * Checks the configuration for validity, result is reflected in the status of + * the Thing + */ + private boolean checkConfig(PlugwiseHABridgeThingConfig bridgeConfig) { + if (!bridgeConfig.isValid()) { + updateStatus(OFFLINE, CONFIGURATION_ERROR, STATUS_DESCRIPTION_CONFIGURATION_ERROR); + return false; + } else { + return true; + } + } + + private void scheduleRefreshJob(PlugwiseHABridgeThingConfig bridgeConfig) { + synchronized (this) { + if (this.refreshJob == null) { + logger.debug("Scheduling refresh job every {}s", bridgeConfig.getRefresh()); + this.refreshJob = scheduler.scheduleWithFixedDelay(this::run, 0, bridgeConfig.getRefresh(), + TimeUnit.SECONDS); + } + } + } + + private void run() { + try { + logger.trace("Executing refresh job"); + refresh(); + + if (super.thing.getStatus() == ThingStatus.INITIALIZING) { + setBridgeProperties(); + } + + } catch (PlugwiseHAInvalidHostException e) { + updateStatus(OFFLINE, CONFIGURATION_ERROR, STATUS_DESCRIPTION_INVALID_HOSTNAME); + } catch (PlugwiseHAUnauthorizedException | PlugwiseHANotAuthorizedException e) { + updateStatus(OFFLINE, CONFIGURATION_ERROR, STATUS_DESCRIPTION_INVALID_CREDENTIALS); + } catch (PlugwiseHACommunicationException e) { + updateStatus(OFFLINE, COMMUNICATION_ERROR, STATUS_DESCRIPTION_COMMUNICATION_ERROR); + } catch (PlugwiseHATimeoutException e) { + updateStatus(OFFLINE, COMMUNICATION_ERROR, STATUS_DESCRIPTION_TIMEOUT); + } catch (PlugwiseHAException e) { + updateStatus(OFFLINE, COMMUNICATION_ERROR, e.getMessage()); + } catch (RuntimeException e) { + updateStatus(OFFLINE, COMMUNICATION_ERROR, e.getMessage()); + } + } + + @SuppressWarnings("unchecked") + private void refresh() throws PlugwiseHAException { + if (this.getController() != null) { + logger.debug("Refreshing the Plugwise Home Automation Controller {}", getThing().getUID()); + + PlugwiseHAController controller = this.getController(); + if (controller != null) { + controller.refresh(); + updateStatus(ONLINE); + } + + getThing().getThings().forEach((thing) -> { + ThingHandler thingHandler = thing.getHandler(); + if (thingHandler instanceof PlugwiseHABaseHandler) { + ((PlugwiseHABaseHandler) thingHandler).refresh(); + } + }); + } + } + + @SuppressWarnings("null") + private void cancelRefreshJob() { + synchronized (this) { + if (this.refreshJob != null) { + logger.debug("Cancelling refresh job"); + this.refreshJob.cancel(true); + this.refreshJob = null; + } + } + } + + protected void setBridgeProperties() { + logger.debug("Setting bridge properties"); + try { + PlugwiseHAController controller = this.getController(); + GatewayInfo localGatewayInfo = null; + if (controller != null) { + localGatewayInfo = controller.getGatewayInfo(); + } + + if (localGatewayInfo != null) { + Map properties = editProperties(); + if (localGatewayInfo.getFirmwareVersion() != null) { + properties.put(Thing.PROPERTY_FIRMWARE_VERSION, localGatewayInfo.getFirmwareVersion()); + } + if (localGatewayInfo.getHardwareVersion() != null) { + properties.put(Thing.PROPERTY_HARDWARE_VERSION, localGatewayInfo.getHardwareVersion()); + } + if (localGatewayInfo.getMacAddress() != null) { + properties.put(Thing.PROPERTY_MAC_ADDRESS, localGatewayInfo.getMacAddress()); + } + if (localGatewayInfo.getVendorName() != null) { + properties.put(Thing.PROPERTY_VENDOR, localGatewayInfo.getVendorName()); + } + if (localGatewayInfo.getVendorModel() != null) { + properties.put(Thing.PROPERTY_MODEL_ID, localGatewayInfo.getVendorModel()); + } + + updateProperties(properties); + } + } catch (PlugwiseHAException e) { + updateStatus(OFFLINE, COMMUNICATION_ERROR, STATUS_DESCRIPTION_COMMUNICATION_ERROR); + } + } +} diff --git a/bundles/org.openhab.binding.plugwiseha/src/main/java/org/openhab/binding/plugwiseha/internal/handler/PlugwiseHAZoneHandler.java b/bundles/org.openhab.binding.plugwiseha/src/main/java/org/openhab/binding/plugwiseha/internal/handler/PlugwiseHAZoneHandler.java new file mode 100644 index 00000000000..cfc0a2c4b71 --- /dev/null +++ b/bundles/org.openhab.binding.plugwiseha/src/main/java/org/openhab/binding/plugwiseha/internal/handler/PlugwiseHAZoneHandler.java @@ -0,0 +1,222 @@ +/** + * 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.plugwiseha.internal.handler; + +import static org.openhab.binding.plugwiseha.internal.PlugwiseHABindingConstants.*; +import static org.openhab.core.thing.ThingStatus.*; +import static org.openhab.core.thing.ThingStatusDetail.BRIDGE_OFFLINE; +import static org.openhab.core.thing.ThingStatusDetail.COMMUNICATION_ERROR; +import static org.openhab.core.thing.ThingStatusDetail.CONFIGURATION_ERROR; + +import java.util.Map; +import java.util.Optional; + +import javax.measure.Unit; +import javax.measure.quantity.Temperature; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; +import org.openhab.binding.plugwiseha.internal.PlugwiseHABindingConstants; +import org.openhab.binding.plugwiseha.internal.api.exception.PlugwiseHAException; +import org.openhab.binding.plugwiseha.internal.api.model.PlugwiseHAController; +import org.openhab.binding.plugwiseha.internal.api.model.dto.Location; +import org.openhab.binding.plugwiseha.internal.config.PlugwiseHAThingConfig; +import org.openhab.core.library.types.OnOffType; +import org.openhab.core.library.types.QuantityType; +import org.openhab.core.library.types.StringType; +import org.openhab.core.library.unit.ImperialUnits; +import org.openhab.core.library.unit.SIUnits; +import org.openhab.core.thing.ChannelUID; +import org.openhab.core.thing.Thing; +import org.openhab.core.thing.ThingTypeUID; +import org.openhab.core.types.Command; +import org.openhab.core.types.State; +import org.openhab.core.types.UnDefType; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * The {@link PlugwiseHAZoneHandler} class is responsible for handling commands + * and status updates for the Plugwise Home Automation zones/locations. + * Extends @{link PlugwiseHABaseHandler} + * + * @author Bas van Wetten - Initial contribution + * @author Leo Siepel - finish initial contribution + * + */ + +@NonNullByDefault +public class PlugwiseHAZoneHandler extends PlugwiseHABaseHandler { + + private @Nullable Location location; + private final Logger logger = LoggerFactory.getLogger(PlugwiseHAZoneHandler.class); + + // Constructor + + public PlugwiseHAZoneHandler(Thing thing) { + super(thing); + } + + public static boolean supportsThingType(ThingTypeUID thingTypeUID) { + return PlugwiseHABindingConstants.THING_TYPE_ZONE.equals(thingTypeUID); + } + + // Overrides + + @Override + protected synchronized void initialize(PlugwiseHAThingConfig config, PlugwiseHABridgeHandler bridgeHandler) { + if (thing.getStatus() == INITIALIZING) { + logger.debug("Initializing Plugwise Home Automation zone handler with config = {}", config); + if (!config.isValid()) { + updateStatus(OFFLINE, CONFIGURATION_ERROR, + "Invalid configuration for Plugwise Home Automation zone handler."); + return; + } + + try { + PlugwiseHAController controller = bridgeHandler.getController(); + if (controller != null) { + this.location = getEntity(controller, true); + if (this.location != null) { + setLocationProperties(); + updateStatus(ONLINE); + } else { + updateStatus(OFFLINE); + } + } else { + updateStatus(OFFLINE, BRIDGE_OFFLINE); + } + } catch (PlugwiseHAException e) { + updateStatus(OFFLINE, COMMUNICATION_ERROR, e.getMessage()); + } + } + } + + @Override + protected @Nullable Location getEntity(PlugwiseHAController controller, Boolean forceRefresh) + throws PlugwiseHAException { + PlugwiseHAThingConfig config = getPlugwiseThingConfig(); + Location location = controller.getLocation(config.getId(), forceRefresh); + + return location; + } + + @Override + protected void handleCommand(Location entity, ChannelUID channelUID, Command command) throws PlugwiseHAException { + String channelID = channelUID.getIdWithoutGroup(); + PlugwiseHABridgeHandler bridge = this.getPlugwiseHABridge(); + if (bridge != null) { + PlugwiseHAController controller = bridge.getController(); + if (controller != null) { + switch (channelID) { + case ZONE_SETPOINT_CHANNEL: + if (command instanceof QuantityType) { + Unit unit = entity.getSetpointTemperatureUnit().orElse(UNIT_CELSIUS) + .equals(UNIT_CELSIUS) ? SIUnits.CELSIUS : ImperialUnits.FAHRENHEIT; + QuantityType state = ((QuantityType) command).toUnit(unit); + if (state != null) { + try { + controller.setLocationThermostat(entity, state.doubleValue()); + } catch (PlugwiseHAException e) { + logger.warn("Unable to update setpoint for zone '{}': {} -> {}", entity.getName(), + entity.getSetpointTemperature().orElse(null), state.doubleValue()); + } + } + } + break; + case ZONE_PREHEAT_CHANNEL: + if (command instanceof OnOffType) { + try { + controller.setPreHeating(entity, command == OnOffType.ON); + } catch (PlugwiseHAException e) { + logger.warn("Unable to switch zone pre heating {} for zone '{}'", (State) command, + entity.getName()); + } + } + break; + default: + logger.warn("Ignoring unsupported command = {} for channel = {}", command, channelUID); + } + } + } + } + + private State getDefaultState(String channelID) { + State state = UnDefType.NULL; + switch (channelID) { + case ZONE_PREHEAT_CHANNEL: + case ZONE_PRESETSCENE_CHANNEL: + case ZONE_SETPOINT_CHANNEL: + case ZONE_TEMPERATURE_CHANNEL: + state = UnDefType.NULL; + break; + } + return state; + } + + @Override + protected void refreshChannel(Location entity, ChannelUID channelUID) { + String channelID = channelUID.getIdWithoutGroup(); + State state = getDefaultState(channelID); + + switch (channelID) { + case ZONE_PREHEAT_CHANNEL: + Optional preHeatState = entity.getPreHeatState(); + if (preHeatState.isPresent()) { + state = OnOffType.from(preHeatState.get()); + } + break; + case ZONE_PRESETSCENE_CHANNEL: + state = new StringType(entity.getPreset()); + break; + case ZONE_SETPOINT_CHANNEL: + if (entity.getSetpointTemperature().isPresent()) { + Unit unit = entity.getSetpointTemperatureUnit().orElse(UNIT_CELSIUS) + .equals(UNIT_CELSIUS) ? SIUnits.CELSIUS : ImperialUnits.FAHRENHEIT; + state = new QuantityType(entity.getSetpointTemperature().get(), unit); + } + break; + case ZONE_TEMPERATURE_CHANNEL: + if (entity.getTemperature().isPresent()) { + Unit unit = entity.getTemperatureUnit().orElse(UNIT_CELSIUS).equals(UNIT_CELSIUS) + ? SIUnits.CELSIUS + : ImperialUnits.FAHRENHEIT; + state = new QuantityType(entity.getTemperature().get(), unit); + } + break; + default: + break; + } + + if (state != UnDefType.NULL) { + updateState(channelID, state); + } + } + + protected void setLocationProperties() { + if (this.location != null) { + Map properties = editProperties(); + + Location localLocation = this.location; + if (localLocation != null) { + properties.put(PlugwiseHABindingConstants.LOCATION_PROPERTY_DESCRIPTION, + localLocation.getDescription()); + properties.put(PlugwiseHABindingConstants.LOCATION_PROPERTY_TYPE, localLocation.getType()); + properties.put(PlugwiseHABindingConstants.LOCATION_PROPERTY_FUNCTIONALITIES, + String.join(", ", localLocation.getActuatorFunctionalities().keySet())); + } + + updateProperties(properties); + } + } +} diff --git a/bundles/org.openhab.binding.plugwiseha/src/main/resources/OH-INF/binding/binding.xml b/bundles/org.openhab.binding.plugwiseha/src/main/resources/OH-INF/binding/binding.xml new file mode 100644 index 00000000000..ffb50a37b57 --- /dev/null +++ b/bundles/org.openhab.binding.plugwiseha/src/main/resources/OH-INF/binding/binding.xml @@ -0,0 +1,10 @@ + + + + Plugwise Home Automation Binding + This binding supports the Plugwise Home Automation 'Adam' gateway. It allows users to access temperature + controls of zones defined on the gateway + + diff --git a/bundles/org.openhab.binding.plugwiseha/src/main/resources/OH-INF/config/config.xml b/bundles/org.openhab.binding.plugwiseha/src/main/resources/OH-INF/config/config.xml new file mode 100644 index 00000000000..712cbce465c --- /dev/null +++ b/bundles/org.openhab.binding.plugwiseha/src/main/resources/OH-INF/config/config.xml @@ -0,0 +1,88 @@ + + + + + + + network-address + + Hostname or IP address of the boiler gateway + adam + + + + Adam HA gateway username (default: smile) + smile + true + + + password + + The Smile ID is the 8 letter code on the sticker on the back of the Adam boiler gateway + + + + seconds + Refresh interval in seconds + 5 + true + + + + + + + + Location ID for the zone + + + + + + + Appliance ID + + + + + + + + Appliance ID + + + + % + Battery charge remaining at which to trigger battery low warning + 15 + true + + + + + + + + Appliance ID + + + + + + + + Appliance ID + + + + % + Battery charge remaining at which to trigger battery low warning + 15 + true + + + + diff --git a/bundles/org.openhab.binding.plugwiseha/src/main/resources/OH-INF/thing/channels.xml b/bundles/org.openhab.binding.plugwiseha/src/main/resources/OH-INF/thing/channels.xml new file mode 100644 index 00000000000..7f57acc9717 --- /dev/null +++ b/bundles/org.openhab.binding.plugwiseha/src/main/resources/OH-INF/thing/channels.xml @@ -0,0 +1,194 @@ + + + + + Number:Temperature + + Gets or sets the set point of this zone + heating + + + + + Number:Temperature + + Gets the temperature of this zone + heating + + + + + Number:Temperature + + Gets or sets the temperature offset for this thermostat + heating + + + + + Switch + + Switch the preheating of a zone ON or OFF + switch + + + + Switch + + Switch the Plugwise Smart plug ON or OFF + switch + + + + Switch + + Locks the switch state of the Plugwise Smart plug + switch + + + + Number:Power + + + + + + Switch + + Is the boiler active for central heating, On or OFF + switch + + + + + Switch + + Is the boiler active for domestic hot water, On or OFF + switch + + + + + Switch + + Is the boiler active for cooling, On or OFF + switch + + + + + Switch + + Is the boiler's flame active, On or OFF + switch + + + + + Switch + + Should the boiler be active for central heating, On or OFF + switch + + + + + Switch + + Is the boiler's domestic hot water mode set to comfort, On or OFF + switch + + + + + Number:Temperature + + Gets the intended temperature of this boiler + heating + + + + + Number + + Gets the modulation level of this boiler + heating + + + + + Number + + Gets the Opentherm application fault code of this boiler + heating + + + + + Number:Temperature + + Gets the temperature of the domestic hot water + heating + + + + + Number + + Gets the OEM fault code of this boiler + heating + + + + + Number:Temperature + + Gets the temperature of this boiler + heating + + + + + Number:Temperature + + Gets the temperature of the domestic hot water setpoint + heating + + + + + Number:Temperature + + Gets the maximum temperature ofthis boiler + heating + + + + + Number:Pressure + + Gets the water pressure of the boiler + heating + + + + + String + + Gets the preset scene of the zone + heating + + + + + Number + + Gets the position of the valve (0% closed, 100% open) + heating + + + + diff --git a/bundles/org.openhab.binding.plugwiseha/src/main/resources/OH-INF/thing/thing-types.xml b/bundles/org.openhab.binding.plugwiseha/src/main/resources/OH-INF/thing/thing-types.xml new file mode 100644 index 00000000000..11d3b11e4b3 --- /dev/null +++ b/bundles/org.openhab.binding.plugwiseha/src/main/resources/OH-INF/thing/thing-types.xml @@ -0,0 +1,128 @@ + + + + + + + The Plugwise Home Automation Bridge is needed to connect to the Adam boiler gateway + + + + + + + + + + + + A Plugwise Home Automation controlled boiler + + + + + + + + + + + + + + + + + + + + id + + + + + + + + + + + + A Plugwise Home Automation heating zone + + + + + + + + + id + + + + + + + + + + + + A Plugwise Home Automation radiator valve + + + + + + + + id + + + + + + + + + + + + A Plugwise Home Automation smart plug switch connected to a central heating pump + + + + + + + + id + + + + + + + + + + + + A Plugwise Home Automation room thermostat + + + + + + + + id + + + + + diff --git a/bundles/org.openhab.binding.plugwiseha/src/main/resources/domain_objects.xslt b/bundles/org.openhab.binding.plugwiseha/src/main/resources/domain_objects.xslt new file mode 100644 index 00000000000..8b1be1382bc --- /dev/null +++ b/bundles/org.openhab.binding.plugwiseha/src/main/resources/domain_objects.xslt @@ -0,0 +1,127 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + relay + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/bundles/pom.xml b/bundles/pom.xml index dbc247649ef..55732ad1a6f 100644 --- a/bundles/pom.xml +++ b/bundles/pom.xml @@ -261,6 +261,7 @@ org.openhab.binding.playstation org.openhab.binding.plclogo org.openhab.binding.plugwise + org.openhab.binding.plugwiseha org.openhab.binding.powermax org.openhab.binding.pulseaudio org.openhab.binding.pushbullet