[plugwiseha] Initial contribution (#9504)

Signed-off-by: Leo Siepel <leosiepel@gmail.com>
This commit is contained in:
lsiepel 2021-05-19 21:53:33 +02:00 committed by GitHub
parent 7d2c8755eb
commit 8202d57965
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
59 changed files with 5677 additions and 0 deletions

View File

@ -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

View File

@ -1126,6 +1126,11 @@
<artifactId>org.openhab.binding.plugwise</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.openhab.addons.bundles</groupId>
<artifactId>org.openhab.binding.plugwiseha</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.openhab.addons.bundles</groupId>
<artifactId>org.openhab.binding.powermax</artifactId>

View File

@ -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

View File

@ -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
}
}
```

View File

@ -0,0 +1,17 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://maven.apache.org/POM/4.0.0"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.openhab.addons.bundles</groupId>
<artifactId>org.openhab.addons.reactor.bundles</artifactId>
<version>3.1.0-SNAPSHOT</version>
</parent>
<artifactId>org.openhab.binding.plugwiseha</artifactId>
<name>openHAB Add-ons :: Bundles :: PlugwiseHA Binding</name>
</project>

View File

@ -0,0 +1,9 @@
<?xml version="1.0" encoding="UTF-8"?>
<features name="org.openhab.binding.plugwiseha-${project.version}" xmlns="http://karaf.apache.org/xmlns/features/v1.4.0">
<repository>mvn:org.openhab.core.features.karaf/org.openhab.core.features.karaf.openhab-core/${ohc.version}/xml/features</repository>
<feature name="openhab-binding-plugwiseha" description="PlugwiseHA Binding" version="${project.version}">
<feature>openhab-runtime-base</feature>
<bundle start-level="80">mvn:org.openhab.addons.bundles/org.openhab.binding.plugwiseha/${project.version}</bundle>
</feature>
</features>

View File

@ -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<ThingTypeUID> 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<ThingTypeUID> 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<String> 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<String> SUPPORTED_APPLIANCE_TYPES = Set.of(APPLIANCE_TYPE_CENTRALHEATINGPUMP,
APPLIANCE_TYPE_THERMOSTATICRADIATORVALUE, APPLIANCE_TYPE_ZONETHERMOSTAT, APPLIANCE_TYPE_HEATERCENTRAL);
// Supported bridges
public static final Set<ThingTypeUID> 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);
}
}

View File

@ -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;
}
}

View File

@ -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);
}
}

View File

@ -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);
}
}

View File

@ -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);
}
}

View File

@ -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);
}
}

View File

@ -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);
}
}

View File

@ -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);
}
}

View File

@ -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);
}
}

View File

@ -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);
}
}

View File

@ -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<DomainObjects> 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<DomainObjects> 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<DomainObjects> 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<DomainObjects> 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<DomainObjects> 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<Void> request = newRequest(Void.class);
Optional<ActuatorFunctionality> 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<Void> request = newRequest(Void.class);
Optional<ActuatorFunctionality> 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<Void> request = newRequest(Void.class);
Optional<ActuatorFunctionality> 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<String> 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<Void> request = newRequest(Void.class);
Optional<ActuatorFunctionality> 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<Void> 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<Void> 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<String> 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<Void> 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<Void> 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<Void> 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 <T> PlugwiseHAControllerRequest<T> newRequest(Class<T> responseType, @Nullable Transformer transformer) {
return new PlugwiseHAControllerRequest<T>(responseType, this.xStream, transformer, this.httpClient, this.host,
this.port, this.username, this.smileId);
}
private <T> PlugwiseHAControllerRequest<T> newRequest(Class<T> responseType) {
return new PlugwiseHAControllerRequest<T>(responseType, this.xStream, null, this.httpClient, this.host,
this.port, this.username, this.smileId);
}
@SuppressWarnings("null")
private <T> T executeRequest(PlugwiseHAControllerRequest<T> 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();
}
}
}

View File

@ -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<T> {
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<T> resultType;
private final @Nullable Transformer transformer;
private Map<String, String> headers = new HashMap<>();
private Map<String, String> queryParameters = new HashMap<>();
private @Nullable Object bodyParameter;
private String serverDateTime = "";
private String path = "/";
// Constructor
<X extends XStream> PlugwiseHAControllerRequest(Class<T> 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<String, String>) null);
}
public void setPath(String path, @Nullable HashMap<String, String> 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<String, String> entry : this.headers.entrySet()) {
request.header(entry.getKey(), entry.getValue());
}
for (Entry<String, String> 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);
}
}

View File

@ -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();
}

View File

@ -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);
}
}

View File

@ -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<ActuatorFunctionality> {
private static final String THERMOSTAT_FUNCTIONALITY = "thermostat";
private static final String OFFSETTEMPERATURE_FUNCTIONALITY = "temperature_offset";
private static final String RELAY_FUNCTIONALITY = "relay";
public Optional<Boolean> getRelayLockState() {
return this.getFunctionalityRelay().flatMap(ActuatorFunctionality::getRelayLockState)
.map(Boolean::parseBoolean);
}
public Optional<Boolean> getPreHeatState() {
return this.getFunctionalityThermostat().flatMap(ActuatorFunctionality::getPreHeatState)
.map(Boolean::parseBoolean);
}
public Optional<ActuatorFunctionality> getFunctionalityThermostat() {
return Optional.ofNullable(this.get(THERMOSTAT_FUNCTIONALITY));
}
public Optional<ActuatorFunctionality> getFunctionalityOffsetTemperature() {
return Optional.ofNullable(this.get(OFFSETTEMPERATURE_FUNCTIONALITY));
}
public Optional<ActuatorFunctionality> getFunctionalityRelay() {
return Optional.ofNullable(this.get(RELAY_FUNCTIONALITY));
}
@Override
public void merge(Map<String, ActuatorFunctionality> 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);
}
}
}
}
}

View File

@ -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<ActuatorFunctionality> {
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<String> getPreHeatState() {
return Optional.ofNullable(preHeat);
}
public Optional<String> 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;
}
}

View File

@ -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;
}
}

View File

@ -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;
}
}

View File

@ -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;
}
}

View File

@ -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() {
}
}

View File

@ -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() {
}
}

View File

@ -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() {
}
}

View File

@ -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<Appliance> {
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<Double> getTemperature() {
return this.pointLogs.getTemperature();
}
public Optional<String> getTemperatureUnit() {
return this.pointLogs.getTemperatureUnit();
}
public Optional<Double> getSetpointTemperature() {
return this.pointLogs.getThermostatTemperature();
}
public Optional<String> getSetpointTemperatureUnit() {
return this.pointLogs.getThermostatTemperatureUnit();
}
public Optional<Double> getOffsetTemperature() {
return this.pointLogs.getOffsetTemperature();
}
public Optional<String> getOffsetTemperatureUnit() {
return this.pointLogs.getOffsetTemperatureUnit();
}
public Optional<Boolean> getRelayState() {
return this.pointLogs.getRelayState();
}
public Optional<Boolean> getRelayLockState() {
return this.actuatorFunctionalities.getRelayLockState();
}
public Optional<Double> getBatteryLevel() {
return this.pointLogs.getBatteryLevel();
}
public Optional<Double> getPowerUsage() {
return this.pointLogs.getPowerUsage();
}
public Optional<Double> getValvePosition() {
return this.pointLogs.getValvePosition();
}
public Optional<Double> getWaterPressure() {
return this.pointLogs.getWaterPressure();
}
public Optional<Boolean> getCHState() {
return this.pointLogs.getCHState();
}
public Optional<Boolean> getCoolingState() {
return this.pointLogs.getCoolingState();
}
public Optional<Double> getIntendedBoilerTemp() {
return this.pointLogs.getIntendedBoilerTemp();
}
public Optional<String> getIntendedBoilerTempUnit() {
return this.pointLogs.getIntendedBoilerTempUnit();
}
public Optional<Boolean> getFlameState() {
return this.pointLogs.getFlameState();
}
public Optional<Boolean> getIntendedHeatingState() {
return this.pointLogs.getIntendedHeatingState();
}
public Optional<Double> getModulationLevel() {
return this.pointLogs.getModulationLevel();
}
public Optional<Double> getOTAppFaultCode() {
return this.pointLogs.getOTAppFaultCode();
}
public Optional<Double> getDHWTemp() {
return this.pointLogs.getDHWTemp();
}
public Optional<String> getDHWTempUnit() {
return this.pointLogs.getDHWTempUnit();
}
public Optional<Double> getOTOEMFaultcode() {
return this.pointLogs.getOTOEMFaultcode();
}
public Optional<Double> getBoilerTemp() {
return this.pointLogs.getBoilerTemp();
}
public Optional<String> getBoilerTempUnit() {
return this.pointLogs.getBoilerTempUnit();
}
public Optional<Double> getDHTSetpoint() {
return this.pointLogs.getDHTSetpoint();
}
public Optional<String> getDHTSetpointUnit() {
return this.pointLogs.getDHTSetpointUnit();
}
public Optional<Double> getMaxBoilerTemp() {
return this.pointLogs.getMaxBoilerTemp();
}
public Optional<String> getMaxBoilerTempUnit() {
return this.pointLogs.getMaxBoilerTempUnit();
}
public Optional<Boolean> getDHWComfortMode() {
return this.pointLogs.getDHWComfortMode();
}
public Optional<Boolean> 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;
}
}

View File

@ -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<Appliance> {
@Override
public void merge(Map<String, Appliance> 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);
}
}
}
}
}

View File

@ -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;
}
}

View File

@ -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;
}

View File

@ -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;
}
}

View File

@ -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<Location> {
private String name;
private String description;
private String type;
private String preset;
@XStreamImplicit(itemFieldName = "appliance")
private List<String> locationAppliances = new ArrayList<String>();
@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<String> 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<Double> getTemperature() {
return this.pointLogs.getTemperature();
}
public Optional<String> getTemperatureUnit() {
return this.pointLogs.getTemperatureUnit();
}
public Optional<Double> getSetpointTemperature() {
return this.pointLogs.getThermostatTemperature();
}
public Optional<String> getSetpointTemperatureUnit() {
return this.pointLogs.getThermostatTemperatureUnit();
}
public Optional<Boolean> 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;
}
}

View File

@ -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<Location> {
@Override
public void merge(Map<String, Location> 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);
}
}
}
}
}

View File

@ -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<Log> {
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<String> getMeasurement() {
return Optional.ofNullable(measurement);
}
public Optional<Boolean> 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<Double> getMeasurementAsDouble() {
try {
if (measurement != null) {
return Optional.of(Double.parseDouble(measurement));
} else {
return Optional.empty();
}
} catch (NumberFormatException e) {
return Optional.empty();
}
}
public Optional<String> 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;
}
}

View File

@ -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<Log> {
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<Boolean> getCoolingState() {
return this.getLog(COOLINGSTATE).map(logEntry -> logEntry.getMeasurementAsBoolean()).orElse(Optional.empty());
}
public Optional<Double> getIntendedBoilerTemp() {
return this.getLog(INTENDEDBOILERTEMP).map(logEntry -> logEntry.getMeasurementAsDouble())
.orElse(Optional.empty());
}
public Optional<String> getIntendedBoilerTempUnit() {
return this.getLog(INTENDEDBOILERTEMP).map(logEntry -> logEntry.getMeasurementUnit()).orElse(Optional.empty());
}
public Optional<Boolean> getFlameState() {
return this.getLog(FLAMESTATE).map(logEntry -> logEntry.getMeasurementAsBoolean()).orElse(Optional.empty());
}
public Optional<Boolean> getIntendedHeatingState() {
return this.getLog(INTENDEDHEATINGSTATE).map(logEntry -> logEntry.getMeasurementAsBoolean())
.orElse(Optional.empty());
}
public Optional<Double> getModulationLevel() {
return this.getLog(MODULATIONLEVEL).map(logEntry -> logEntry.getMeasurementAsDouble()).orElse(Optional.empty());
}
public Optional<Double> getOTAppFaultCode() {
return this.getLog(OTAPPLICATIONFAULTCODE).map(logEntry -> logEntry.getMeasurementAsDouble())
.orElse(Optional.empty());
}
public Optional<Double> getDHWTemp() {
return this.getLog(DHWTEMP).map(logEntry -> logEntry.getMeasurementAsDouble()).orElse(Optional.empty());
}
public Optional<String> getDHWTempUnit() {
return this.getLog(DHWTEMP).map(logEntry -> logEntry.getMeasurementUnit()).orElse(Optional.empty());
}
public Optional<Double> getOTOEMFaultcode() {
return this.getLog(OTOEMFAULTCODE).map(logEntry -> logEntry.getMeasurementAsDouble()).orElse(Optional.empty());
}
public Optional<Double> getBoilerTemp() {
return this.getLog(BOILERTEMP).map(logEntry -> logEntry.getMeasurementAsDouble()).orElse(Optional.empty());
}
public Optional<String> getBoilerTempUnit() {
return this.getLog(BOILERTEMP).map(logEntry -> logEntry.getMeasurementUnit()).orElse(Optional.empty());
}
public Optional<Double> getDHTSetpoint() {
return this.getLog(DHWSETPOINT).map(logEntry -> logEntry.getMeasurementAsDouble()).orElse(Optional.empty());
}
public Optional<String> getDHTSetpointUnit() {
return this.getLog(DHWSETPOINT).map(logEntry -> logEntry.getMeasurementUnit()).orElse(Optional.empty());
}
public Optional<Double> getMaxBoilerTemp() {
return this.getLog(MAXBOILERTEMP).map(logEntry -> logEntry.getMeasurementAsDouble()).orElse(Optional.empty());
}
public Optional<String> getMaxBoilerTempUnit() {
return this.getLog(MAXBOILERTEMP).map(logEntry -> logEntry.getMeasurementUnit()).orElse(Optional.empty());
}
public Optional<Boolean> getDHWComfortMode() {
return this.getLog(DHWCOMFORTMODE).map(logEntry -> logEntry.getMeasurementAsBoolean()).orElse(Optional.empty());
}
public Optional<Double> getTemperature() {
return this.getLog(TEMPERATURE).map(logEntry -> logEntry.getMeasurementAsDouble()).orElse(Optional.empty());
}
public Optional<String> getTemperatureUnit() {
return this.getLog(TEMPERATURE).map(logEntry -> logEntry.getMeasurementUnit()).orElse(Optional.empty());
}
public Optional<Double> getThermostatTemperature() {
return this.getLog(THERMOSTAT).map(logEntry -> logEntry.getMeasurementAsDouble()).orElse(Optional.empty());
}
public Optional<String> getThermostatTemperatureUnit() {
return this.getLog(THERMOSTAT).map(logEntry -> logEntry.getMeasurementUnit()).orElse(Optional.empty());
}
public Optional<Double> getOffsetTemperature() {
return this.getLog(TEMPERATURE_OFFSET).map(logEntry -> logEntry.getMeasurementAsDouble())
.orElse(Optional.empty());
}
public Optional<String> getOffsetTemperatureUnit() {
return this.getLog(TEMPERATURE_OFFSET).map(logEntry -> logEntry.getMeasurementUnit()).orElse(Optional.empty());
}
public Optional<Boolean> getRelayState() {
return this.getLog(RELAY).map(logEntry -> logEntry.getMeasurementAsBoolean()).orElse(Optional.empty());
}
public Optional<Boolean> getDHWState() {
return this.getLog(DHWSTATE).map(logEntry -> logEntry.getMeasurementAsBoolean()).orElse(Optional.empty());
}
public Optional<Boolean> getCHState() {
return this.getLog(CHSTATE).map(logEntry -> logEntry.getMeasurementAsBoolean()).orElse(Optional.empty());
}
public Optional<Double> getValvePosition() {
return this.getLog(VALVE_POSITION).map(logEntry -> logEntry.getMeasurementAsDouble()).orElse(Optional.empty());
}
public Optional<Double> getWaterPressure() {
return this.getLog(WATER_PRESSURE).map(logEntry -> logEntry.getMeasurementAsDouble()).orElse(Optional.empty());
}
public Optional<Double> getBatteryLevel() {
return this.getLog(BATTERY).map(logEntry -> logEntry.getMeasurementAsDouble()).orElse(Optional.empty());
}
public Optional<Double> getPowerUsage() {
return this.getLog(POWER_USAGE).map(logEntry -> logEntry.getMeasurementAsDouble()).orElse(Optional.empty());
}
public Optional<Log> getLog(String logItem) {
return Optional.ofNullable(this.get(logItem));
}
@Override
public void merge(Map<String, Log> 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);
}
}
}
}
}

View File

@ -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<Module> {
@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;
}
}

View File

@ -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<Module> {
@Override
public void merge(Map<String, Module> modules) {
}
}

View File

@ -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;
}
}

View File

@ -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<T extends PlugwiseBaseModel> {
public int compareDateWith(T hasModifiedDate);
public boolean isOlderThan(T hasModifiedDate);
public boolean isNewerThan(T hasModifiedDate);
}

View File

@ -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<T> implements Map<String, T> {
private final Map<String, T> 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<? extends String, ? extends T> m) {
this.map.putAll(m);
}
@Override
public void clear() {
this.map.clear();
}
@Override
public Set<String> keySet() {
return this.map.keySet();
}
@Override
public Collection<T> values() {
return this.map.values();
}
@Override
public Set<Entry<String, T>> entrySet() {
return this.map.entrySet();
}
public abstract void merge(Map<String, T> map);
}

View File

@ -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;
}

View File

@ -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<Service> {
@Override
public void merge(Map<String, Service> services) {
}
}

View File

@ -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;
}
}

View File

@ -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());
}
}

View File

@ -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 + "}";
}
}

View File

@ -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 + "}";
}
}

View File

@ -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<String, Object> 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<String, Object> 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);
}
}
}

View File

@ -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<Appliance, PlugwiseHAThingConfig> {
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<Temperature> 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<Temperature> 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<Dimensionless>(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<Temperature> unit = entity.getOffsetTemperatureUnit().orElse(UNIT_CELSIUS).equals(UNIT_CELSIUS)
? SIUnits.CELSIUS
: ImperialUnits.FAHRENHEIT;
state = new QuantityType<Temperature>(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<Power>(entity.getPowerUsage().get(), Units.WATT);
}
break;
case APPLIANCE_SETPOINT_CHANNEL:
if (entity.getSetpointTemperature().isPresent()) {
Unit<Temperature> unit = entity.getSetpointTemperatureUnit().orElse(UNIT_CELSIUS)
.equals(UNIT_CELSIUS) ? SIUnits.CELSIUS : ImperialUnits.FAHRENHEIT;
state = new QuantityType<Temperature>(entity.getSetpointTemperature().get(), unit);
}
break;
case APPLIANCE_TEMPERATURE_CHANNEL:
if (entity.getTemperature().isPresent()) {
Unit<Temperature> unit = entity.getTemperatureUnit().orElse(UNIT_CELSIUS).equals(UNIT_CELSIUS)
? SIUnits.CELSIUS
: ImperialUnits.FAHRENHEIT;
state = new QuantityType<Temperature>(entity.getTemperature().get(), unit);
}
break;
case APPLIANCE_VALVEPOSITION_CHANNEL:
if (entity.getValvePosition().isPresent()) {
Double valvePosition = entity.getValvePosition().get() * 100;
state = new QuantityType<Dimensionless>(valvePosition.intValue(), Units.PERCENT);
}
break;
case APPLIANCE_WATERPRESSURE_CHANNEL:
if (entity.getWaterPressure().isPresent()) {
Unit<Pressure> unit = HECTO(SIUnits.PASCAL);
state = new QuantityType<Pressure>(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<Temperature> unit = entity.getIntendedBoilerTempUnit().orElse(UNIT_CELSIUS)
.equals(UNIT_CELSIUS) ? SIUnits.CELSIUS : ImperialUnits.FAHRENHEIT;
state = new QuantityType<Temperature>(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<Dimensionless>(modulationLevel.intValue(), Units.PERCENT);
}
break;
case APPLIANCE_OTAPPLICATIONFAULTCODE_CHANNEL:
if (entity.getOTAppFaultCode().isPresent()) {
state = new QuantityType<Dimensionless>(entity.getOTAppFaultCode().get().intValue(), Units.PERCENT);
}
break;
case APPLIANCE_DHWTEMPERATURE_CHANNEL:
if (entity.getDHWTemp().isPresent()) {
Unit<Temperature> unit = entity.getDHWTempUnit().orElse(UNIT_CELSIUS).equals(UNIT_CELSIUS)
? SIUnits.CELSIUS
: ImperialUnits.FAHRENHEIT;
state = new QuantityType<Temperature>(entity.getDHWTemp().get(), unit);
}
break;
case APPLIANCE_OTOEMFAULTCODE_CHANNEL:
if (entity.getOTOEMFaultcode().isPresent()) {
state = new QuantityType<Dimensionless>(entity.getOTOEMFaultcode().get().intValue(), Units.PERCENT);
}
break;
case APPLIANCE_BOILERTEMPERATURE_CHANNEL:
if (entity.getBoilerTemp().isPresent()) {
Unit<Temperature> unit = entity.getBoilerTempUnit().orElse(UNIT_CELSIUS).equals(UNIT_CELSIUS)
? SIUnits.CELSIUS
: ImperialUnits.FAHRENHEIT;
state = new QuantityType<Temperature>(entity.getBoilerTemp().get(), unit);
}
break;
case APPLIANCE_DHWSETPOINT_CHANNEL:
if (entity.getDHTSetpoint().isPresent()) {
Unit<Temperature> unit = entity.getDHTSetpointUnit().orElse(UNIT_CELSIUS).equals(UNIT_CELSIUS)
? SIUnits.CELSIUS
: ImperialUnits.FAHRENHEIT;
state = new QuantityType<Temperature>(entity.getDHTSetpoint().get(), unit);
}
break;
case APPLIANCE_MAXBOILERTEMPERATURE_CHANNEL:
if (entity.getMaxBoilerTemp().isPresent()) {
Unit<Temperature> unit = entity.getMaxBoilerTempUnit().orElse(UNIT_CELSIUS).equals(UNIT_CELSIUS)
? SIUnits.CELSIUS
: ImperialUnits.FAHRENHEIT;
state = new QuantityType<Temperature>(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<Channel> 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<String, String> 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);
}
}

View File

@ -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 <E> entity - the Plugwise Home Automation entity class used by this
* thing handler
* @param <C> config - the Plugwise Home Automation config class used by this
* thing handler
*/
@NonNullByDefault
public abstract class PlugwiseHABaseHandler<E, C extends PlugwiseHAThingConfig> 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);
}
}
}
}
}
}
}
}

View File

@ -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<Class<? extends ThingHandlerService>> 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<PlugwiseHAModel, PlugwiseHAThingConfig>) 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<String, String> 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);
}
}
}

View File

@ -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<Location, PlugwiseHAThingConfig> {
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<Temperature> 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<Boolean> 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<Temperature> unit = entity.getSetpointTemperatureUnit().orElse(UNIT_CELSIUS)
.equals(UNIT_CELSIUS) ? SIUnits.CELSIUS : ImperialUnits.FAHRENHEIT;
state = new QuantityType<Temperature>(entity.getSetpointTemperature().get(), unit);
}
break;
case ZONE_TEMPERATURE_CHANNEL:
if (entity.getTemperature().isPresent()) {
Unit<Temperature> unit = entity.getTemperatureUnit().orElse(UNIT_CELSIUS).equals(UNIT_CELSIUS)
? SIUnits.CELSIUS
: ImperialUnits.FAHRENHEIT;
state = new QuantityType<Temperature>(entity.getTemperature().get(), unit);
}
break;
default:
break;
}
if (state != UnDefType.NULL) {
updateState(channelID, state);
}
}
protected void setLocationProperties() {
if (this.location != null) {
Map<String, String> 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);
}
}
}

View File

@ -0,0 +1,10 @@
<?xml version="1.0" encoding="UTF-8"?>
<binding:binding id="plugwiseha" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:binding="https://openhab.org/schemas/binding/v1.0.0"
xsi:schemaLocation="https://openhab.org/schemas/binding/v1.0.0 https://openhab.org/schemas/binding-1.0.0.xsd">
<name>Plugwise Home Automation Binding</name>
<description>This binding supports the Plugwise Home Automation 'Adam' gateway. It allows users to access temperature
controls of zones defined on the gateway</description>
</binding:binding>

View File

@ -0,0 +1,88 @@
<?xml version="1.0" encoding="UTF-8"?>
<config-description:config-descriptions
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:config-description="https://openhab.org/schemas/config-description/v1.0.0"
xsi:schemaLocation="https://openhab.org/schemas/config-description/v1.0.0 https://openhab.org/schemas/config-description-1.0.0.xsd">
<!-- Bridge -->
<config-description uri="bridge-type:plugwiseha:gateway">
<parameter name="host" type="text" required="true">
<context>network-address</context>
<label>Host</label>
<description>Hostname or IP address of the boiler gateway</description>
<default>adam</default>
</parameter>
<parameter name="username" type="text" required="true">
<label>Username</label>
<description>Adam HA gateway username (default: smile)</description>
<default>smile</default>
<advanced>true</advanced>
</parameter>
<parameter name="smileId" type="text" pattern="[a-zA-Z0-9]{8}" required="true">
<context>password</context>
<label>Smile ID</label>
<description>The Smile ID is the 8 letter code on the sticker on the back of the Adam boiler gateway</description>
</parameter>
<parameter name="refresh" type="integer" min="1" max="120" required="true" unit="s">
<label>Refresh Interval</label>
<unitLabel>seconds</unitLabel>
<description>Refresh interval in seconds</description>
<default>5</default>
<advanced>true</advanced>
</parameter>
</config-description>
<!-- Zone thing -->
<config-description uri="thing-type:plugwiseha:zone">
<parameter name="id" type="text" required="true" readOnly="false">
<label>ID</label>
<description>Location ID for the zone</description>
</parameter>
</config-description>
<config-description uri="thing-type:plugwiseha:appliance_boiler">
<parameter name="id" type="text" required="true" readOnly="false">
<label>ID</label>
<description>Appliance ID</description>
</parameter>
</config-description>
<!-- Appliance: Radiator valve -->
<config-description uri="thing-type:plugwiseha:appliance_valve">
<parameter name="id" type="text" required="true" readOnly="false">
<label>ID</label>
<description>Appliance ID</description>
</parameter>
<parameter name="lowBatteryPercentage" type="integer" min="1" max="50" required="true">
<label>Low Battery Threshold</label>
<unitLabel>%</unitLabel>
<description>Battery charge remaining at which to trigger battery low warning</description>
<default>15</default>
<advanced>true</advanced>
</parameter>
</config-description>
<!-- Appliance: Pump switch -->
<config-description uri="thing-type:plugwiseha:appliance_pump">
<parameter name="id" type="text" required="true" readOnly="false">
<label>ID</label>
<description>Appliance ID</description>
</parameter>
</config-description>
<!-- Appliance: Radiator valve -->
<config-description uri="thing-type:plugwiseha:appliance_thermostat">
<parameter name="id" type="text" required="true" readOnly="false">
<label>ID</label>
<description>Appliance ID</description>
</parameter>
<parameter name="lowBatteryPercentage" type="integer" min="1" max="50" required="true">
<label>Low Battery Threshold</label>
<unitLabel>%</unitLabel>
<description>Battery charge remaining at which to trigger battery low warning</description>
<default>15</default>
<advanced>true</advanced>
</parameter>
</config-description>
</config-description:config-descriptions>

View File

@ -0,0 +1,194 @@
<?xml version="1.0" encoding="UTF-8"?>
<thing:thing-descriptions bindingId="plugwiseha"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:thing="https://openhab.org/schemas/thing-description/v1.0.0"
xsi:schemaLocation="https://openhab.org/schemas/thing-description/v1.0.0 https://openhab.org/schemas/thing-description-1.0.0.xsd">
<channel-type id="setpointTemperature">
<item-type>Number:Temperature</item-type>
<label>Setpoint Temperature</label>
<description>Gets or sets the set point of this zone</description>
<category>heating</category>
<state min="0.0" max="35.0" step="0.5" pattern="%.1f %unit%"/>
</channel-type>
<channel-type id="temperature">
<item-type>Number:Temperature</item-type>
<label>Zone Temperature</label>
<description>Gets the temperature of this zone</description>
<category>heating</category>
<state readOnly="true" pattern="%.1f %unit%"/>
</channel-type>
<channel-type id="offsetTemperature">
<item-type>Number:Temperature</item-type>
<label>Thermostat Temperature Offset</label>
<description>Gets or sets the temperature offset for this thermostat</description>
<category>heating</category>
<state pattern="%.1f %unit%"/>
</channel-type>
<channel-type id="preHeat">
<item-type>Switch</item-type>
<label>Preheat</label>
<description>Switch the preheating of a zone ON or OFF</description>
<category>switch</category>
</channel-type>
<channel-type id="power">
<item-type>Switch</item-type>
<label>Power</label>
<description>Switch the Plugwise Smart plug ON or OFF</description>
<category>switch</category>
</channel-type>
<channel-type id="lock">
<item-type>Switch</item-type>
<label>Lock</label>
<description>Locks the switch state of the Plugwise Smart plug</description>
<category>switch</category>
</channel-type>
<channel-type id="powerUsage">
<item-type>Number:Power</item-type>
<label>Power Usage</label>
<state pattern="%.2f %unit%" readOnly="true"></state>
</channel-type>
<channel-type id="chState">
<item-type>Switch</item-type>
<label>Central Heating Active</label>
<description>Is the boiler active for central heating, On or OFF</description>
<category>switch</category>
<state readOnly="true"/>
</channel-type>
<channel-type id="dhwState">
<item-type>Switch</item-type>
<label>Domestic Hot Water Active</label>
<description>Is the boiler active for domestic hot water, On or OFF</description>
<category>switch</category>
<state readOnly="true"/>
</channel-type>
<channel-type id="coolingState">
<item-type>Switch</item-type>
<label>Cooling State</label>
<description>Is the boiler active for cooling, On or OFF</description>
<category>switch</category>
<state readOnly="true"/>
</channel-type>
<channel-type id="flameState">
<item-type>Switch</item-type>
<label>Flame State</label>
<description>Is the boiler's flame active, On or OFF</description>
<category>switch</category>
<state readOnly="true"/>
</channel-type>
<channel-type id="intendedHeatingState">
<item-type>Switch</item-type>
<label>Intended Heating State</label>
<description>Should the boiler be active for central heating, On or OFF</description>
<category>switch</category>
<state readOnly="true"/>
</channel-type>
<channel-type id="dhwComfortMode">
<item-type>Switch</item-type>
<label>Domestic Hot Water Comfort Mode</label>
<description>Is the boiler's domestic hot water mode set to comfort, On or OFF</description>
<category>switch</category>
<state readOnly="true"/>
</channel-type>
<channel-type id="intendedBoilerTemp">
<item-type>Number:Temperature</item-type>
<label>Intended Boiler Temperature</label>
<description>Gets the intended temperature of this boiler</description>
<category>heating</category>
<state readOnly="true" pattern="%.1f %unit%"/>
</channel-type>
<channel-type id="modulationLevel">
<item-type>Number</item-type>
<label>Modulelation Level</label>
<description>Gets the modulation level of this boiler</description>
<category>heating</category>
<state readOnly="true" pattern="%.0f"/>
</channel-type>
<channel-type id="otAppFaultCode">
<item-type>Number</item-type>
<label>Opentherm Application Faultcode</label>
<description>Gets the Opentherm application fault code of this boiler</description>
<category>heating</category>
<state readOnly="true" pattern="%.0f"/>
</channel-type>
<channel-type id="dhwTemperature">
<item-type>Number:Temperature</item-type>
<label>Domestic Hot Water Temperature</label>
<description>Gets the temperature of the domestic hot water</description>
<category>heating</category>
<state readOnly="true" pattern="%.1f %unit%"/>
</channel-type>
<channel-type id="otOEMFaultCode">
<item-type>Number</item-type>
<label>OEM Fault Code</label>
<description>Gets the OEM fault code of this boiler</description>
<category>heating</category>
<state readOnly="true" pattern="%.0f"/>
</channel-type>
<channel-type id="boilerTemperature">
<item-type>Number:Temperature</item-type>
<label>Boiler Temperature</label>
<description>Gets the temperature of this boiler</description>
<category>heating</category>
<state readOnly="true" pattern="%.1f %unit%"/>
</channel-type>
<channel-type id="dhwSetpoint">
<item-type>Number:Temperature</item-type>
<label>Domestic Hot Water Setpoint Temperature</label>
<description>Gets the temperature of the domestic hot water setpoint</description>
<category>heating</category>
<state readOnly="true" pattern="%.1f %unit%"/>
</channel-type>
<channel-type id="maxBoilerTemperature">
<item-type>Number:Temperature</item-type>
<label>Max Boiler Temperature</label>
<description>Gets the maximum temperature ofthis boiler</description>
<category>heating</category>
<state readOnly="true" pattern="%.1f %unit%"/>
</channel-type>
<channel-type id="waterPressure">
<item-type>Number:Pressure</item-type>
<label>Water Pressure</label>
<description>Gets the water pressure of the boiler</description>
<category>heating</category>
<state readOnly="true" pattern="%.1f %unit%"/>
</channel-type>
<channel-type id="presetScene">
<item-type>String</item-type>
<label>Preset Scene</label>
<description>Gets the preset scene of the zone</description>
<category>heating</category>
<state readOnly="true"/>
</channel-type>
<channel-type id="valvePosition">
<item-type>Number</item-type>
<label>Valve Position</label>
<description>Gets the position of the valve (0% closed, 100% open)</description>
<category>heating</category>
<state readOnly="true" pattern="%.0f"/>
</channel-type>
</thing:thing-descriptions>

View File

@ -0,0 +1,128 @@
<?xml version="1.0" encoding="UTF-8"?>
<thing:thing-descriptions bindingId="plugwiseha"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:thing="https://openhab.org/schemas/thing-description/v1.0.0"
xsi:schemaLocation="https://openhab.org/schemas/thing-description/v1.0.0 https://openhab.org/schemas/thing-description-1.0.0.xsd">
<!-- Bridge -->
<bridge-type id="gateway">
<label>Plugwise Home Automation Bridge</label>
<description>The Plugwise Home Automation Bridge is needed to connect to the Adam boiler gateway</description>
<config-description-ref uri="bridge-type:plugwiseha:gateway"/>
</bridge-type>
<!-- Zone thing -->
<thing-type id="appliance_boiler" listed="true">
<supported-bridge-type-refs>
<bridge-type-ref id="gateway"/>
</supported-bridge-type-refs>
<label>Boiler</label>
<description>A Plugwise Home Automation controlled boiler</description>
<channels>
<channel id="chState" typeId="chState"/>
<channel id="dhwState" typeId="dhwState"/>
<channel id="waterPressure" typeId="waterPressure"/>
<channel id="coolingState" typeId="coolingState"/>
<channel id="flameState" typeId="flameState"/>
<channel id="intendedHeatingState" typeId="intendedHeatingState"/>
<channel id="dhwComfortMode" typeId="dhwComfortMode"/>
<channel id="intendedBoilerTemp" typeId="intendedBoilerTemp"/>
<channel id="modulationLevel" typeId="modulationLevel"/>
<channel id="otAppFaultCode" typeId="otAppFaultCode"/>
<channel id="dhwTemperature" typeId="dhwTemperature"/>
<channel id="otOEMFaultCode" typeId="otOEMFaultCode"/>
<channel id="boilerTemperature" typeId="boilerTemperature"/>
<channel id="dhwSetpoint" typeId="dhwSetpoint"/>
<channel id="maxBoilerTemperature" typeId="maxBoilerTemperature"/>
</channels>
<representation-property>id</representation-property>
<config-description-ref uri="thing-type:plugwiseha:appliance_boiler"/>
</thing-type>
<!-- Zone thing -->
<thing-type id="zone" listed="true">
<supported-bridge-type-refs>
<bridge-type-ref id="gateway"/>
</supported-bridge-type-refs>
<label>Plugwise Zone</label>
<description>A Plugwise Home Automation heating zone</description>
<channels>
<channel id="setpointTemperature" typeId="setpointTemperature"/>
<channel id="temperature" typeId="temperature"/>
<channel id="presetScene" typeId="presetScene"/>
<channel id="preHeat" typeId="preHeat"/>
</channels>
<representation-property>id</representation-property>
<config-description-ref uri="thing-type:plugwiseha:zone"/>
</thing-type>
<!-- Appliance: Radiator valve (Tom) -->
<thing-type id="appliance_valve" listed="true">
<supported-bridge-type-refs>
<bridge-type-ref id="gateway"/>
</supported-bridge-type-refs>
<label>Plugwise Radiator Valve</label>
<description>A Plugwise Home Automation radiator valve</description>
<channels>
<channel id="setpointTemperature" typeId="setpointTemperature"/>
<channel id="temperature" typeId="temperature"/>
<channel id="valvePosition" typeId="valvePosition"/>
</channels>
<representation-property>id</representation-property>
<config-description-ref uri="thing-type:plugwiseha:appliance_valve"/>
</thing-type>
<!-- Appliance: Pump switch (Circle) -->
<thing-type id="appliance_pump" listed="true">
<supported-bridge-type-refs>
<bridge-type-ref id="gateway"/>
</supported-bridge-type-refs>
<label>Central Heating Pump</label>
<description>A Plugwise Home Automation smart plug switch connected to a central heating pump</description>
<channels>
<channel id="power" typeId="power"/>
<channel id="lock" typeId="lock"/>
<channel id="powerUsage" typeId="powerUsage"/>
</channels>
<representation-property>id</representation-property>
<config-description-ref uri="thing-type:plugwiseha:appliance_pump"/>
</thing-type>
<!-- Appliance: Zone thermostat (Lisa) -->
<thing-type id="appliance_thermostat" listed="true">
<supported-bridge-type-refs>
<bridge-type-ref id="gateway"/>
</supported-bridge-type-refs>
<label>Plugwise Room Thermostat</label>
<description>A Plugwise Home Automation room thermostat</description>
<channels>
<channel id="setpointTemperature" typeId="setpointTemperature"/>
<channel id="temperature" typeId="temperature"/>
<channel id="offsetTemperature" typeId="offsetTemperature"/>
</channels>
<representation-property>id</representation-property>
<config-description-ref uri="thing-type:plugwiseha:appliance_thermostat"/>
</thing-type>
</thing:thing-descriptions>

View File

@ -0,0 +1,127 @@
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
<xsl:output method="xml" version="1.0" encoding="UTF-8" indent="yes"/>
<xsl:strip-space elements="*"/>
<!-- modified identity transform -->
<xsl:template match="/domain_objects">
<xsl:element name="{local-name()}">
<xsl:apply-templates select="gateway" />
<xsl:apply-templates select="appliance" />
<xsl:apply-templates select="location" />
<xsl:apply-templates select="module" />
</xsl:element>
</xsl:template>
<xsl:template match="node()">
<!-- prevent duplicate siblings -->
<xsl:if test="count(preceding-sibling::node()[name()=name(current())])=0">
<!-- copy element -->
<xsl:copy>
<xsl:apply-templates select="@*|node()"/>
</xsl:copy>
</xsl:if>
</xsl:template>
<xsl:template match="appliance">
<!-- copy element -->
<xsl:copy>
<xsl:apply-templates select="@*|node()"/>
</xsl:copy>
</xsl:template>
<xsl:template match="location">
<!-- copy element -->
<xsl:copy>
<xsl:apply-templates select="@*|node()"/>
</xsl:copy>
</xsl:template>
<xsl:template match="module">
<!-- copy element -->
<xsl:copy>
<xsl:apply-templates select="protocols/node()[name()='zig_bee_node']"/>
<xsl:apply-templates select="@*|node()[name()!='protocols']"/>
</xsl:copy>
</xsl:template>
<xsl:template match="location/appliances">
<!-- Apply identity transform on child elements of appliances -->
<xsl:for-each select="appliance">
<xsl:copy>
<xsl:value-of select="@id"/>
</xsl:copy>
</xsl:for-each>
</xsl:template>
<xsl:template match="module/services">
<xsl:for-each select="./node()">
<xsl:element name="service">
<xsl:element name="point_log">
<xsl:value-of select="functionalities/point_log/@id"/>
</xsl:element>
<xsl:apply-templates select="@*|node()[name()!='functionalities']"/>
</xsl:element>
</xsl:for-each>
</xsl:template>
<!-- This matches 'appliance/logs' or 'location/logs' -->
<xsl:template match="*[name() = 'location' or name()='appliance']/logs">
<!-- Apply identity transform on child elements of logs -->
<xsl:variable name="meter_id" select="point_log/*[substring(local-name(), string-length(local-name()) - string-length('_meter')+1) = '_meter']/@id"/>
<xsl:apply-templates select="/domain_objects/module/services/*[@id=$meter_id]/../../protocols/zig_bee_node"/>
<xsl:for-each select="point_log">
<xsl:copy>
<xsl:apply-templates select="@*|node()"/>
</xsl:copy>
</xsl:for-each>
</xsl:template>
<xsl:template match="appliance/location">
<!-- Apply identity transform on child elements of location -->
<xsl:copy>
<xsl:value-of select="@id"/>
</xsl:copy>
</xsl:template>
<xsl:template match="logs/point_log/period">
<xsl:element name="measurement_date">
<xsl:value-of select="measurement/@log_date"/>
</xsl:element>
<xsl:element name="measurement">
<xsl:value-of select="measurement/text()"/>
</xsl:element>
</xsl:template>
<xsl:template match="*[name() = 'location' or name()='appliance']/actuator_functionalities">
<xsl:for-each select="./*">
<xsl:element name="actuator_functionality">
<xsl:if test="not(type)">
<xsl:choose>
<xsl:when test="local-name()='relay_functionality'">
<xsl:element name="type">
<xsl:text>relay</xsl:text>
</xsl:element>
</xsl:when>
<xsl:otherwise>
<xsl:element name="type">
<xsl:value-of select="local-name()"/>
</xsl:element>
</xsl:otherwise>
</xsl:choose>
</xsl:if>
<xsl:for-each select=".">
<xsl:apply-templates select="@*|node()"/>
</xsl:for-each>
</xsl:element>
</xsl:for-each>
</xsl:template>
<!-- attributes to elements -->
<xsl:template match="@*">
<xsl:element name="{name()}">
<xsl:value-of select="."/>
</xsl:element>
</xsl:template>
</xsl:stylesheet>

View File

@ -261,6 +261,7 @@
<module>org.openhab.binding.playstation</module>
<module>org.openhab.binding.plclogo</module>
<module>org.openhab.binding.plugwise</module>
<module>org.openhab.binding.plugwiseha</module>
<module>org.openhab.binding.powermax</module>
<module>org.openhab.binding.pulseaudio</module>
<module>org.openhab.binding.pushbullet</module>