]> git.basschouten.com Git - openhab-addons.git/blob
2b6e4b721080af6313df37af7e423cb561200df6
[openhab-addons.git] /
1 /**
2  * Copyright (c) 2010-2022 Contributors to the openHAB project
3  *
4  * See the NOTICE file(s) distributed with this work for additional
5  * information.
6  *
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
10  *
11  * SPDX-License-Identifier: EPL-2.0
12  */
13 package org.openhab.binding.plugwiseha.internal.api.model;
14
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;
20
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;
25
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;
43
44 /**
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.
48  * 
49  * @author B. van Wetten - Initial contribution
50  */
51 @NonNullByDefault
52 public class PlugwiseHAController {
53
54     // Private member variables/constants
55
56     private static final int MAX_AGE_MINUTES_FULL_REFRESH = 15;
57     private static final DateTimeFormatter FORMAT = DateTimeFormatter.RFC_1123_DATE_TIME; // default Date format that
58                                                                                           // will be used in conversion
59
60     private final Logger logger = LoggerFactory.getLogger(PlugwiseHAController.class);
61
62     private final HttpClient httpClient;
63     private final PlugwiseHAXStream xStream;
64     private final Transformer domainObjectsTransformer;
65
66     private final String host;
67     private final int port;
68     private final String username;
69     private final String smileId;
70     private final int maxAgeSecondsRefresh;
71
72     private @Nullable ZonedDateTime gatewayUpdateDateTime;
73     private @Nullable ZonedDateTime gatewayFullUpdateDateTime;
74     private @Nullable DomainObjects domainObjects;
75
76     public PlugwiseHAController(HttpClient httpClient, String host, int port, String username, String smileId,
77             int maxAgeSecondsRefresh) throws PlugwiseHAException {
78         this.httpClient = httpClient;
79         this.host = host;
80         this.port = port;
81         this.username = username;
82         this.smileId = smileId;
83         this.maxAgeSecondsRefresh = maxAgeSecondsRefresh;
84
85         this.xStream = new PlugwiseHAXStream();
86
87         ClassLoader localClassLoader = getClass().getClassLoader();
88         if (localClassLoader != null) {
89             this.domainObjectsTransformer = PlugwiseHAController
90                     .setXSLT(new StreamSource(localClassLoader.getResourceAsStream("domain_objects.xslt")));
91         } else {
92             throw new PlugwiseHAException("PlugwiseHAController.domainObjectsTransformer could not be initialized");
93         }
94     }
95
96     // Public methods
97
98     public void start(Runnable callback) throws PlugwiseHAException {
99         refresh();
100         callback.run();
101     }
102
103     public void refresh() throws PlugwiseHAException {
104         synchronized (this) {
105             this.getUpdatedDomainObjects();
106         }
107     }
108
109     // Public API methods
110
111     public GatewayInfo getGatewayInfo() throws PlugwiseHAException {
112         return getGatewayInfo(false);
113     }
114
115     public GatewayInfo getGatewayInfo(Boolean forceRefresh) throws PlugwiseHAException {
116         GatewayInfo gatewayInfo = null;
117         DomainObjects localDomainObjects = this.domainObjects;
118         if (localDomainObjects != null) {
119             gatewayInfo = localDomainObjects.getGatewayInfo();
120         }
121
122         if (!forceRefresh && gatewayInfo != null) {
123             this.logger.debug("Found Plugwise Home Automation gateway");
124             return gatewayInfo;
125         } else {
126             PlugwiseHAControllerRequest<DomainObjects> request;
127
128             request = newRequest(DomainObjects.class, this.domainObjectsTransformer);
129
130             request.setPath("/core/domain_objects");
131             request.addPathParameter("class", "Gateway");
132
133             DomainObjects domainObjects = executeRequest(request);
134             this.gatewayUpdateDateTime = ZonedDateTime.parse(request.getServerDateTime(), PlugwiseHAController.FORMAT);
135
136             return mergeDomainObjects(domainObjects).getGatewayInfo();
137         }
138     }
139
140     public Appliances getAppliances(Boolean forceRefresh) throws PlugwiseHAException {
141         Appliances appliances = null;
142         DomainObjects localDomainObjects = this.domainObjects;
143         if (localDomainObjects != null) {
144             appliances = localDomainObjects.getAppliances();
145         }
146
147         if (!forceRefresh && appliances != null) {
148             return appliances;
149         } else {
150             PlugwiseHAControllerRequest<DomainObjects> request;
151
152             request = newRequest(DomainObjects.class, this.domainObjectsTransformer);
153
154             request.setPath("/core/domain_objects");
155             request.addPathParameter("class", "Appliance");
156
157             DomainObjects domainObjects = executeRequest(request);
158             this.gatewayUpdateDateTime = ZonedDateTime.parse(request.getServerDateTime(), PlugwiseHAController.FORMAT);
159             int size = 0;
160             if (!(domainObjects.getAppliances() == null)) {
161                 size = domainObjects.getAppliances().size();
162             }
163             this.logger.debug("Found {} Plugwise Home Automation appliance(s)", size);
164
165             return mergeDomainObjects(domainObjects).getAppliances();
166         }
167     }
168
169     public @Nullable Appliance getAppliance(String id) throws PlugwiseHAException {
170         Appliances appliances = this.getAppliances(false);
171         if (!appliances.containsKey(id)) {
172             appliances = this.getAppliances(true);
173         }
174
175         if (!appliances.containsKey(id)) {
176             this.logger.debug("Plugwise Home Automation Appliance with id {} is not known", id);
177             return null;
178         } else {
179             return appliances.get(id);
180         }
181     }
182
183     public Locations getLocations(Boolean forceRefresh) throws PlugwiseHAException {
184         Locations locations = null;
185         DomainObjects localDomainObjects = this.domainObjects;
186         if (localDomainObjects != null) {
187             locations = localDomainObjects.getLocations();
188         }
189
190         if (!forceRefresh && locations != null) {
191             return locations;
192         } else {
193             PlugwiseHAControllerRequest<DomainObjects> request;
194
195             request = newRequest(DomainObjects.class, this.domainObjectsTransformer);
196
197             request.setPath("/core/domain_objects");
198             request.addPathParameter("class", "Location");
199
200             DomainObjects domainObjects = executeRequest(request);
201             this.gatewayUpdateDateTime = ZonedDateTime.parse(request.getServerDateTime(), PlugwiseHAController.FORMAT);
202             int size = 0;
203             if (!(domainObjects.getLocations() == null)) {
204                 size = domainObjects.getLocations().size();
205             }
206             this.logger.debug("Found {} Plugwise Home Automation Zone(s)", size);
207             return mergeDomainObjects(domainObjects).getLocations();
208         }
209     }
210
211     public @Nullable Location getLocation(String id) throws PlugwiseHAException {
212         Locations locations = this.getLocations(false);
213         if (!locations.containsKey(id)) {
214             locations = this.getLocations(true);
215         }
216
217         if (!locations.containsKey(id)) {
218             this.logger.debug("Plugwise Home Automation Zone with {} is not known", id);
219             return null;
220         } else {
221             return locations.get(id);
222         }
223     }
224
225     public @Nullable DomainObjects getDomainObjects() throws PlugwiseHAException {
226         PlugwiseHAControllerRequest<DomainObjects> request;
227
228         request = newRequest(DomainObjects.class, this.domainObjectsTransformer);
229
230         request.setPath("/core/domain_objects");
231         request.addPathParameter("@locale", "en-US");
232         DomainObjects domainObjects = executeRequest(request);
233
234         ZonedDateTime serverTime = ZonedDateTime.parse(request.getServerDateTime(), PlugwiseHAController.FORMAT);
235         this.gatewayUpdateDateTime = serverTime;
236         this.gatewayFullUpdateDateTime = serverTime;
237
238         return mergeDomainObjects(domainObjects);
239     }
240
241     public @Nullable DomainObjects getUpdatedDomainObjects() throws PlugwiseHAException {
242         ZonedDateTime localGatewayUpdateDateTime = this.gatewayUpdateDateTime;
243         ZonedDateTime localGatewayFullUpdateDateTime = this.gatewayFullUpdateDateTime;
244
245         if (localGatewayUpdateDateTime == null || localGatewayFullUpdateDateTime == null) {
246             return getDomainObjects();
247         } else if (localGatewayUpdateDateTime.isBefore(ZonedDateTime.now().minusSeconds(maxAgeSecondsRefresh))) {
248             return getUpdatedDomainObjects(localGatewayUpdateDateTime);
249         } else if (localGatewayFullUpdateDateTime
250                 .isBefore(ZonedDateTime.now().minusMinutes(MAX_AGE_MINUTES_FULL_REFRESH))) {
251             return getDomainObjects();
252         } else {
253             return null;
254         }
255     }
256
257     public @Nullable DomainObjects getUpdatedDomainObjects(ZonedDateTime since) throws PlugwiseHAException {
258         return getUpdatedDomainObjects(since.toEpochSecond());
259     }
260
261     public @Nullable DomainObjects getUpdatedDomainObjects(Long since) throws PlugwiseHAException {
262         PlugwiseHAControllerRequest<DomainObjects> request;
263
264         request = newRequest(DomainObjects.class, this.domainObjectsTransformer);
265
266         request.setPath("/core/domain_objects");
267         request.addPathFilter("modified_date", "ge", since);
268         request.addPathFilter("deleted_date", "ge", "0");
269         request.addPathParameter("@memberModifiedDate", since);
270         request.addPathParameter("@locale", "en-US");
271
272         DomainObjects domainObjects = executeRequest(request);
273         this.gatewayUpdateDateTime = ZonedDateTime.parse(request.getServerDateTime(), PlugwiseHAController.FORMAT);
274
275         return mergeDomainObjects(domainObjects);
276     }
277
278     public void setLocationThermostat(Location location, Double temperature) throws PlugwiseHAException {
279         PlugwiseHAControllerRequest<Void> request = newRequest(Void.class);
280         Optional<ActuatorFunctionality> thermostat = location.getActuatorFunctionalities().getFunctionalityThermostat();
281
282         if (thermostat.isPresent()) {
283             request.setPath("/core/locations");
284
285             request.addPathParameter("id", String.format("%s/thermostat", location.getId()));
286             request.addPathParameter("id", String.format("%s", thermostat.get().getId()));
287             request.setBodyParameter(new ActuatorFunctionalityThermostat(temperature));
288
289             executeRequest(request);
290         }
291     }
292
293     public void setThermostat(Appliance appliance, Double temperature) throws PlugwiseHAException {
294         PlugwiseHAControllerRequest<Void> request = newRequest(Void.class);
295         Optional<ActuatorFunctionality> thermostat = appliance.getActuatorFunctionalities()
296                 .getFunctionalityThermostat();
297
298         if (thermostat.isPresent()) {
299             request.setPath("/core/appliances");
300
301             request.addPathParameter("id", String.format("%s/thermostat", appliance.getId()));
302             request.addPathParameter("id", String.format("%s", thermostat.get().getId()));
303             request.setBodyParameter(new ActuatorFunctionalityThermostat(temperature));
304
305             executeRequest(request);
306         }
307     }
308
309     public void setOffsetTemperature(Appliance appliance, Double temperature) throws PlugwiseHAException {
310         PlugwiseHAControllerRequest<Void> request = newRequest(Void.class);
311         Optional<ActuatorFunctionality> offsetTemperatureFunctionality = appliance.getActuatorFunctionalities()
312                 .getFunctionalityOffsetTemperature();
313
314         if (offsetTemperatureFunctionality.isPresent()) {
315             request.setPath("/core/appliances");
316
317             request.addPathParameter("id", String.format("%s/offset", appliance.getId()));
318             request.addPathParameter("id", String.format("%s", offsetTemperatureFunctionality.get().getId()));
319             request.setBodyParameter(new ActuatorFunctionalityOffsetTemperature(temperature));
320
321             executeRequest(request);
322         }
323     }
324
325     public void switchRelay(Appliance appliance, String state) throws PlugwiseHAException {
326         List<String> allowStates = Arrays.asList("on", "off");
327         if (allowStates.contains(state.toLowerCase())) {
328             if (state.toLowerCase().equals("on")) {
329                 switchRelayOn(appliance);
330             } else {
331                 switchRelayOff(appliance);
332             }
333         }
334     }
335
336     public void setPreHeating(Location location, Boolean state) throws PlugwiseHAException {
337         PlugwiseHAControllerRequest<Void> request = newRequest(Void.class);
338         Optional<ActuatorFunctionality> thermostat = location.getActuatorFunctionalities().getFunctionalityThermostat();
339
340         request.setPath("/core/locations");
341         request.addPathParameter("id", String.format("%s/thermostat", location.getId()));
342         request.addPathParameter("id", String.format("%s", thermostat.get().getId()));
343         request.setBodyParameter(new ActuatorFunctionalityThermostat(state));
344
345         executeRequest(request);
346     }
347
348     public void switchRelayOn(Appliance appliance) throws PlugwiseHAException {
349         PlugwiseHAControllerRequest<Void> request = newRequest(Void.class);
350
351         request.setPath("/core/appliances");
352         request.addPathParameter("id", String.format("%s/relay", appliance.getId()));
353         request.setBodyParameter(new ActuatorFunctionalityRelay("on"));
354
355         executeRequest(request);
356     }
357
358     public void switchRelayOff(Appliance appliance) throws PlugwiseHAException {
359         PlugwiseHAControllerRequest<Void> request = newRequest(Void.class);
360
361         request.setPath("/core/appliances");
362         request.addPathParameter("id", String.format("%s/relay", appliance.getId()));
363         request.setBodyParameter(new ActuatorFunctionalityRelay("off"));
364
365         executeRequest(request);
366     }
367
368     public void switchRelayLock(Appliance appliance, String state) throws PlugwiseHAException {
369         List<String> allowStates = Arrays.asList("on", "off");
370         if (allowStates.contains(state.toLowerCase())) {
371             if (state.toLowerCase().equals("on")) {
372                 switchRelayLockOn(appliance);
373             } else {
374                 switchRelayLockOff(appliance);
375             }
376         }
377     }
378
379     public void switchRelayLockOff(Appliance appliance) throws PlugwiseHAException {
380         PlugwiseHAControllerRequest<Void> request = newRequest(Void.class);
381
382         request.setPath("/core/appliances");
383         request.addPathParameter("id", String.format("%s/relay", appliance.getId()));
384         request.setBodyParameter(new ActuatorFunctionalityRelay(null, false));
385
386         executeRequest(request);
387     }
388
389     public void switchRelayLockOn(Appliance appliance) throws PlugwiseHAException {
390         PlugwiseHAControllerRequest<Void> request = newRequest(Void.class);
391
392         request.setPath("/core/appliances");
393         request.addPathParameter("id", String.format("%s/relay", appliance.getId()));
394         request.setBodyParameter(new ActuatorFunctionalityRelay(null, true));
395
396         executeRequest(request);
397     }
398
399     public ZonedDateTime ping() throws PlugwiseHAException {
400         PlugwiseHAControllerRequest<Void> request;
401
402         request = newRequest(Void.class, null);
403
404         request.setPath("/cache/gateways");
405         request.addPathParameter("ping");
406
407         executeRequest(request);
408
409         return ZonedDateTime.parse(request.getServerDateTime(), PlugwiseHAController.FORMAT);
410     }
411
412     // Protected and private methods
413
414     private static Transformer setXSLT(StreamSource xsltSource) throws PlugwiseHAException {
415         try {
416             return TransformerFactory.newInstance().newTransformer(xsltSource);
417         } catch (TransformerConfigurationException e) {
418             throw new PlugwiseHAException("Could not create XML transformer", e);
419         }
420     }
421
422     private <T> PlugwiseHAControllerRequest<T> newRequest(Class<T> responseType, @Nullable Transformer transformer) {
423         return new PlugwiseHAControllerRequest<T>(responseType, this.xStream, transformer, this.httpClient, this.host,
424                 this.port, this.username, this.smileId);
425     }
426
427     private <T> PlugwiseHAControllerRequest<T> newRequest(Class<T> responseType) {
428         return new PlugwiseHAControllerRequest<T>(responseType, this.xStream, null, this.httpClient, this.host,
429                 this.port, this.username, this.smileId);
430     }
431
432     @SuppressWarnings("null")
433     private <T> T executeRequest(PlugwiseHAControllerRequest<T> request) throws PlugwiseHAException {
434         T result;
435         result = request.execute();
436         return result;
437     }
438
439     private DomainObjects mergeDomainObjects(@Nullable DomainObjects updatedDomainObjects) {
440         DomainObjects localDomainObjects = this.domainObjects;
441         if (localDomainObjects == null && updatedDomainObjects != null) {
442             this.domainObjects = updatedDomainObjects;
443             return updatedDomainObjects;
444         } else if (localDomainObjects != null && updatedDomainObjects == null) {
445             return localDomainObjects;
446         } else if (localDomainObjects != null && updatedDomainObjects != null) {
447             Appliances appliances = updatedDomainObjects.getAppliances();
448             Locations locations = updatedDomainObjects.getLocations();
449
450             if (appliances != null) {
451                 localDomainObjects.mergeAppliances(appliances);
452             }
453
454             if (locations != null) {
455                 localDomainObjects.mergeLocations(locations);
456             }
457             this.domainObjects = localDomainObjects;
458             return localDomainObjects;
459         } else {
460             return new DomainObjects();
461         }
462     }
463 }