[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
59 changed files with 5677 additions and 0 deletions

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>