2 * Copyright (c) 2010-2021 Contributors to the openHAB project
4 * See the NOTICE file(s) distributed with this work for additional
7 * This program and the accompanying materials are made available under the
8 * terms of the Eclipse Public License 2.0 which is available at
9 * http://www.eclipse.org/legal/epl-2.0
11 * SPDX-License-Identifier: EPL-2.0
13 package org.openhab.binding.plugwiseha.internal.api.model;
15 import java.time.ZonedDateTime;
16 import java.time.format.DateTimeFormatter;
17 import java.util.Arrays;
18 import java.util.List;
19 import java.util.Optional;
21 import javax.xml.transform.Transformer;
22 import javax.xml.transform.TransformerConfigurationException;
23 import javax.xml.transform.TransformerFactory;
24 import javax.xml.transform.stream.StreamSource;
26 import org.eclipse.jdt.annotation.NonNullByDefault;
27 import org.eclipse.jdt.annotation.Nullable;
28 import org.eclipse.jetty.client.HttpClient;
29 import org.openhab.binding.plugwiseha.internal.api.exception.PlugwiseHAException;
30 import org.openhab.binding.plugwiseha.internal.api.model.dto.ActuatorFunctionality;
31 import org.openhab.binding.plugwiseha.internal.api.model.dto.ActuatorFunctionalityOffsetTemperature;
32 import org.openhab.binding.plugwiseha.internal.api.model.dto.ActuatorFunctionalityRelay;
33 import org.openhab.binding.plugwiseha.internal.api.model.dto.ActuatorFunctionalityThermostat;
34 import org.openhab.binding.plugwiseha.internal.api.model.dto.Appliance;
35 import org.openhab.binding.plugwiseha.internal.api.model.dto.Appliances;
36 import org.openhab.binding.plugwiseha.internal.api.model.dto.DomainObjects;
37 import org.openhab.binding.plugwiseha.internal.api.model.dto.GatewayInfo;
38 import org.openhab.binding.plugwiseha.internal.api.model.dto.Location;
39 import org.openhab.binding.plugwiseha.internal.api.model.dto.Locations;
40 import org.openhab.binding.plugwiseha.internal.api.xml.PlugwiseHAXStream;
41 import org.slf4j.Logger;
42 import org.slf4j.LoggerFactory;
45 * The {@link PlugwiseHAController} class provides the interface to the Plugwise
46 * Home Automation API and stores/caches the object model for use by the various
47 * ThingHandlers of this binding.
49 * @author B. van Wetten - Initial contribution
52 public class PlugwiseHAController {
54 // Private member variables/constants
56 private static final int MAX_AGE_MINUTES_REFRESH = 10;
57 private static final int MAX_AGE_MINUTES_FULL_REFRESH = 30;
58 private static final DateTimeFormatter FORMAT = DateTimeFormatter.RFC_1123_DATE_TIME; // default Date format that
59 // will be used in conversion
61 private final Logger logger = LoggerFactory.getLogger(PlugwiseHAController.class);
63 private final HttpClient httpClient;
64 private final PlugwiseHAXStream xStream;
65 private final Transformer domainObjectsTransformer;
67 private final String host;
68 private final int port;
69 private final String username;
70 private final String smileId;
72 private @Nullable ZonedDateTime gatewayUpdateDateTime;
73 private @Nullable ZonedDateTime gatewayFullUpdateDateTime;
74 private @Nullable DomainObjects domainObjects;
76 public PlugwiseHAController(HttpClient httpClient, String host, int port, String username, String smileId)
77 throws PlugwiseHAException {
78 this.httpClient = httpClient;
81 this.username = username;
82 this.smileId = smileId;
84 this.xStream = new PlugwiseHAXStream();
86 ClassLoader localClassLoader = getClass().getClassLoader();
87 if (localClassLoader != null) {
88 this.domainObjectsTransformer = PlugwiseHAController
89 .setXSLT(new StreamSource(localClassLoader.getResourceAsStream("domain_objects.xslt")));
91 throw new PlugwiseHAException("PlugwiseHAController.domainObjectsTransformer could not be initialized");
97 public void start(Runnable callback) throws PlugwiseHAException {
102 public void refresh() throws PlugwiseHAException {
103 synchronized (this) {
104 this.getUpdatedDomainObjects();
108 // Public API methods
110 public GatewayInfo getGatewayInfo() throws PlugwiseHAException {
111 return getGatewayInfo(false);
114 public GatewayInfo getGatewayInfo(Boolean forceRefresh) throws PlugwiseHAException {
115 GatewayInfo gatewayInfo = null;
116 DomainObjects localDomainObjects = this.domainObjects;
117 if (localDomainObjects != null) {
118 gatewayInfo = localDomainObjects.getGatewayInfo();
121 if (!forceRefresh && gatewayInfo != null) {
122 this.logger.debug("Found Plugwise Home Automation gateway");
125 PlugwiseHAControllerRequest<DomainObjects> request;
127 request = newRequest(DomainObjects.class, this.domainObjectsTransformer);
129 request.setPath("/core/domain_objects");
130 request.addPathParameter("class", "Gateway");
132 DomainObjects domainObjects = executeRequest(request);
133 this.gatewayUpdateDateTime = ZonedDateTime.parse(request.getServerDateTime(), PlugwiseHAController.FORMAT);
135 return mergeDomainObjects(domainObjects).getGatewayInfo();
139 public Appliances getAppliances(Boolean forceRefresh) throws PlugwiseHAException {
140 Appliances appliances = null;
141 DomainObjects localDomainObjects = this.domainObjects;
142 if (localDomainObjects != null) {
143 appliances = localDomainObjects.getAppliances();
146 if (!forceRefresh && appliances != null) {
149 PlugwiseHAControllerRequest<DomainObjects> request;
151 request = newRequest(DomainObjects.class, this.domainObjectsTransformer);
153 request.setPath("/core/domain_objects");
154 request.addPathParameter("class", "Appliance");
156 DomainObjects domainObjects = executeRequest(request);
157 this.gatewayUpdateDateTime = ZonedDateTime.parse(request.getServerDateTime(), PlugwiseHAController.FORMAT);
159 if (!(domainObjects.getAppliances() == null)) {
160 size = domainObjects.getAppliances().size();
162 this.logger.debug("Found {} Plugwise Home Automation appliance(s)", size);
164 return mergeDomainObjects(domainObjects).getAppliances();
168 public @Nullable Appliance getAppliance(String id, Boolean forceRefresh) throws PlugwiseHAException {
169 Appliances appliances = this.getAppliances(forceRefresh);
170 if (!appliances.containsKey(id)) {
171 this.logger.debug("Plugwise Home Automation Appliance with id {} is not known", id);
174 return appliances.get(id);
178 public Locations getLocations(Boolean forceRefresh) throws PlugwiseHAException {
179 Locations locations = null;
180 DomainObjects localDomainObjects = this.domainObjects;
181 if (localDomainObjects != null) {
182 locations = localDomainObjects.getLocations();
185 if (!forceRefresh && locations != null) {
188 PlugwiseHAControllerRequest<DomainObjects> request;
190 request = newRequest(DomainObjects.class, this.domainObjectsTransformer);
192 request.setPath("/core/domain_objects");
193 request.addPathParameter("class", "Location");
195 DomainObjects domainObjects = executeRequest(request);
196 this.gatewayUpdateDateTime = ZonedDateTime.parse(request.getServerDateTime(), PlugwiseHAController.FORMAT);
198 if (!(domainObjects.getLocations() == null)) {
199 size = domainObjects.getLocations().size();
201 this.logger.debug("Found {} Plugwise Home Automation Zone(s)", size);
202 return mergeDomainObjects(domainObjects).getLocations();
206 public @Nullable Location getLocation(String id, Boolean forceRefresh) throws PlugwiseHAException {
207 Locations locations = this.getLocations(forceRefresh);
209 if (!locations.containsKey(id)) {
210 this.logger.debug("Plugwise Home Automation Zone with {} is not known", id);
213 return locations.get(id);
217 public @Nullable DomainObjects getDomainObjects() throws PlugwiseHAException {
218 PlugwiseHAControllerRequest<DomainObjects> request;
220 request = newRequest(DomainObjects.class, this.domainObjectsTransformer);
222 request.setPath("/core/domain_objects");
223 request.addPathParameter("@locale", "en-US");
225 DomainObjects domainObjects = executeRequest(request);
226 this.gatewayUpdateDateTime = ZonedDateTime.parse(request.getServerDateTime(), PlugwiseHAController.FORMAT);
227 this.gatewayFullUpdateDateTime = this.gatewayUpdateDateTime;
229 return mergeDomainObjects(domainObjects);
232 public @Nullable DomainObjects getUpdatedDomainObjects() throws PlugwiseHAException {
233 ZonedDateTime localGatewayUpdateDateTime = this.gatewayUpdateDateTime;
234 ZonedDateTime localGatewayFullUpdateDateTime = this.gatewayFullUpdateDateTime;
235 if (localGatewayUpdateDateTime == null
236 || localGatewayUpdateDateTime.isBefore(ZonedDateTime.now().minusMinutes(MAX_AGE_MINUTES_REFRESH))) {
237 return getDomainObjects();
238 } else if (localGatewayFullUpdateDateTime == null || localGatewayFullUpdateDateTime
239 .isBefore(ZonedDateTime.now().minusMinutes(MAX_AGE_MINUTES_FULL_REFRESH))) {
240 return getDomainObjects();
242 return getUpdatedDomainObjects(localGatewayUpdateDateTime);
246 public @Nullable DomainObjects getUpdatedDomainObjects(ZonedDateTime since) throws PlugwiseHAException {
247 return getUpdatedDomainObjects(since.toEpochSecond());
250 public @Nullable DomainObjects getUpdatedDomainObjects(Long since) throws PlugwiseHAException {
251 PlugwiseHAControllerRequest<DomainObjects> request;
253 request = newRequest(DomainObjects.class, this.domainObjectsTransformer);
255 request.setPath("/core/domain_objects");
256 request.addPathFilter("modified_date", "ge", since);
257 request.addPathFilter("deleted_date", "ge", "0");
258 request.addPathParameter("@memberModifiedDate", since);
259 request.addPathParameter("@locale", "en-US");
261 DomainObjects domainObjects = executeRequest(request);
262 this.gatewayUpdateDateTime = ZonedDateTime.parse(request.getServerDateTime(), PlugwiseHAController.FORMAT);
264 return mergeDomainObjects(domainObjects);
267 public void setLocationThermostat(Location location, Double temperature) throws PlugwiseHAException {
268 PlugwiseHAControllerRequest<Void> request = newRequest(Void.class);
269 Optional<ActuatorFunctionality> thermostat = location.getActuatorFunctionalities().getFunctionalityThermostat();
271 if (thermostat.isPresent()) {
272 request.setPath("/core/locations");
274 request.addPathParameter("id", String.format("%s/thermostat", location.getId()));
275 request.addPathParameter("id", String.format("%s", thermostat.get().getId()));
276 request.setBodyParameter(new ActuatorFunctionalityThermostat(temperature));
278 executeRequest(request);
282 public void setThermostat(Appliance appliance, Double temperature) throws PlugwiseHAException {
283 PlugwiseHAControllerRequest<Void> request = newRequest(Void.class);
284 Optional<ActuatorFunctionality> thermostat = appliance.getActuatorFunctionalities()
285 .getFunctionalityThermostat();
287 if (thermostat.isPresent()) {
288 request.setPath("/core/appliances");
290 request.addPathParameter("id", String.format("%s/thermostat", appliance.getId()));
291 request.addPathParameter("id", String.format("%s", thermostat.get().getId()));
292 request.setBodyParameter(new ActuatorFunctionalityThermostat(temperature));
294 executeRequest(request);
298 public void setOffsetTemperature(Appliance appliance, Double temperature) throws PlugwiseHAException {
299 PlugwiseHAControllerRequest<Void> request = newRequest(Void.class);
300 Optional<ActuatorFunctionality> offsetTemperatureFunctionality = appliance.getActuatorFunctionalities()
301 .getFunctionalityOffsetTemperature();
303 if (offsetTemperatureFunctionality.isPresent()) {
304 request.setPath("/core/appliances");
306 request.addPathParameter("id", String.format("%s/offset", appliance.getId()));
307 request.addPathParameter("id", String.format("%s", offsetTemperatureFunctionality.get().getId()));
308 request.setBodyParameter(new ActuatorFunctionalityOffsetTemperature(temperature));
310 executeRequest(request);
314 public void switchRelay(Appliance appliance, String state) throws PlugwiseHAException {
315 List<String> allowStates = Arrays.asList("on", "off");
316 if (allowStates.contains(state.toLowerCase())) {
317 if (state.toLowerCase().equals("on")) {
318 switchRelayOn(appliance);
320 switchRelayOff(appliance);
325 public void setPreHeating(Location location, Boolean state) throws PlugwiseHAException {
326 PlugwiseHAControllerRequest<Void> request = newRequest(Void.class);
327 Optional<ActuatorFunctionality> thermostat = location.getActuatorFunctionalities().getFunctionalityThermostat();
329 request.setPath("/core/locations");
330 request.addPathParameter("id", String.format("%s/thermostat", location.getId()));
331 request.addPathParameter("id", String.format("%s", thermostat.get().getId()));
332 request.setBodyParameter(new ActuatorFunctionalityThermostat(state));
334 executeRequest(request);
337 public void switchRelayOn(Appliance appliance) throws PlugwiseHAException {
338 PlugwiseHAControllerRequest<Void> request = newRequest(Void.class);
340 request.setPath("/core/appliances");
341 request.addPathParameter("id", String.format("%s/relay", appliance.getId()));
342 request.setBodyParameter(new ActuatorFunctionalityRelay("on"));
344 executeRequest(request);
347 public void switchRelayOff(Appliance appliance) throws PlugwiseHAException {
348 PlugwiseHAControllerRequest<Void> request = newRequest(Void.class);
350 request.setPath("/core/appliances");
351 request.addPathParameter("id", String.format("%s/relay", appliance.getId()));
352 request.setBodyParameter(new ActuatorFunctionalityRelay("off"));
354 executeRequest(request);
357 public void switchRelayLock(Appliance appliance, String state) throws PlugwiseHAException {
358 List<String> allowStates = Arrays.asList("on", "off");
359 if (allowStates.contains(state.toLowerCase())) {
360 if (state.toLowerCase().equals("on")) {
361 switchRelayLockOn(appliance);
363 switchRelayLockOff(appliance);
368 public void switchRelayLockOff(Appliance appliance) throws PlugwiseHAException {
369 PlugwiseHAControllerRequest<Void> request = newRequest(Void.class);
371 request.setPath("/core/appliances");
372 request.addPathParameter("id", String.format("%s/relay", appliance.getId()));
373 request.setBodyParameter(new ActuatorFunctionalityRelay(null, false));
375 executeRequest(request);
378 public void switchRelayLockOn(Appliance appliance) throws PlugwiseHAException {
379 PlugwiseHAControllerRequest<Void> request = newRequest(Void.class);
381 request.setPath("/core/appliances");
382 request.addPathParameter("id", String.format("%s/relay", appliance.getId()));
383 request.setBodyParameter(new ActuatorFunctionalityRelay(null, true));
385 executeRequest(request);
388 public ZonedDateTime ping() throws PlugwiseHAException {
389 PlugwiseHAControllerRequest<Void> request;
391 request = newRequest(Void.class, null);
393 request.setPath("/cache/gateways");
394 request.addPathParameter("ping");
396 executeRequest(request);
398 return ZonedDateTime.parse(request.getServerDateTime(), PlugwiseHAController.FORMAT);
401 // Protected and private methods
403 private static Transformer setXSLT(StreamSource xsltSource) throws PlugwiseHAException {
405 return TransformerFactory.newInstance().newTransformer(xsltSource);
406 } catch (TransformerConfigurationException e) {
407 throw new PlugwiseHAException("Could not create XML transformer", e);
411 private <T> PlugwiseHAControllerRequest<T> newRequest(Class<T> responseType, @Nullable Transformer transformer) {
412 return new PlugwiseHAControllerRequest<T>(responseType, this.xStream, transformer, this.httpClient, this.host,
413 this.port, this.username, this.smileId);
416 private <T> PlugwiseHAControllerRequest<T> newRequest(Class<T> responseType) {
417 return new PlugwiseHAControllerRequest<T>(responseType, this.xStream, null, this.httpClient, this.host,
418 this.port, this.username, this.smileId);
421 @SuppressWarnings("null")
422 private <T> T executeRequest(PlugwiseHAControllerRequest<T> request) throws PlugwiseHAException {
424 result = request.execute();
428 private DomainObjects mergeDomainObjects(@Nullable DomainObjects updatedDomainObjects) {
429 DomainObjects localDomainObjects = this.domainObjects;
430 if (localDomainObjects == null && updatedDomainObjects != null) {
431 return updatedDomainObjects;
432 } else if (localDomainObjects != null && updatedDomainObjects == null) {
433 return localDomainObjects;
434 } else if (localDomainObjects != null && updatedDomainObjects != null) {
435 Appliances appliances = updatedDomainObjects.getAppliances();
436 Locations locations = updatedDomainObjects.getLocations();
438 if (appliances != null) {
439 localDomainObjects.mergeAppliances(appliances);
442 if (locations != null) {
443 localDomainObjects.mergeLocations(locations);
445 return localDomainObjects;
447 return new DomainObjects();