]> git.basschouten.com Git - openhab-addons.git/blob
c46f0a8f09149eb75e3e0061cf7873cd718430ea
[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 setPreHeating(Location location, Boolean state) throws PlugwiseHAException {
326         PlugwiseHAControllerRequest<Void> request = newRequest(Void.class);
327         Optional<ActuatorFunctionality> thermostat = location.getActuatorFunctionalities().getFunctionalityThermostat();
328
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, null, null));
333
334         executeRequest(request);
335     }
336
337     public void setAllowCooling(Location location, Boolean state) throws PlugwiseHAException {
338         PlugwiseHAControllerRequest<Void> request = newRequest(Void.class);
339         Optional<ActuatorFunctionality> thermostat = location.getActuatorFunctionalities().getFunctionalityThermostat();
340
341         request.setPath("/core/locations");
342         request.addPathParameter("id", String.format("%s/thermostat", location.getId()));
343         request.addPathParameter("id", String.format("%s", thermostat.get().getId()));
344         request.setBodyParameter(new ActuatorFunctionalityThermostat(null, state, null));
345
346         executeRequest(request);
347     }
348
349     public void setRegulationControl(Location location, String state) throws PlugwiseHAException {
350         List<String> allowStates = Arrays.asList("active", "passive", "off");
351         if (!allowStates.contains(state.toLowerCase())) {
352             this.logger.warn("Trying to set the regulation control to an invalid state");
353             return;
354         }
355
356         PlugwiseHAControllerRequest<Void> request = newRequest(Void.class);
357         Optional<ActuatorFunctionality> thermostat = location.getActuatorFunctionalities().getFunctionalityThermostat();
358
359         request.setPath("/core/locations");
360         request.addPathParameter("id", String.format("%s/thermostat", location.getId()));
361         request.addPathParameter("id", String.format("%s", thermostat.get().getId()));
362         request.setBodyParameter(new ActuatorFunctionalityThermostat(null, null, state));
363
364         executeRequest(request);
365     }
366
367     public void setRelay(Appliance appliance, Boolean state) throws PlugwiseHAException {
368         PlugwiseHAControllerRequest<Void> request = newRequest(Void.class);
369
370         request.setPath("/core/appliances");
371         request.addPathParameter("id", String.format("%s/relay", appliance.getId()));
372         request.setBodyParameter(new ActuatorFunctionalityRelay(state ? "on" : "off"));
373
374         executeRequest(request);
375     }
376
377     public void setRelayLock(Appliance appliance, Boolean state) throws PlugwiseHAException {
378         PlugwiseHAControllerRequest<Void> request = newRequest(Void.class);
379
380         request.setPath("/core/appliances");
381         request.addPathParameter("id", String.format("%s/relay", appliance.getId()));
382         request.setBodyParameter(new ActuatorFunctionalityRelay(null, state));
383
384         executeRequest(request);
385     }
386
387     public void setPresetScene(Location location, String state) throws PlugwiseHAException {
388         List<String> allowStates = Arrays.asList("home", "asleep", "away", "vacation", "no_frost");
389         if (!allowStates.contains(state.toLowerCase())) {
390             this.logger.warn("Trying to set the preset scene to an invalid state");
391             return;
392         }
393
394         PlugwiseHAControllerRequest<Void> request = newRequest(Void.class);
395
396         request.setPath("/core/locations");
397         request.addPathParameter("id", String.format("%s", location.getId()));
398         request.setBodyParameter(new Location(state));
399
400         executeRequest(request);
401     }
402
403     public ZonedDateTime ping() throws PlugwiseHAException {
404         PlugwiseHAControllerRequest<Void> request;
405
406         request = newRequest(Void.class, null);
407
408         request.setPath("/cache/gateways");
409         request.addPathParameter("ping");
410
411         executeRequest(request);
412
413         return ZonedDateTime.parse(request.getServerDateTime(), PlugwiseHAController.FORMAT);
414     }
415
416     // Protected and private methods
417
418     private static Transformer setXSLT(StreamSource xsltSource) throws PlugwiseHAException {
419         try {
420             return TransformerFactory.newInstance().newTransformer(xsltSource);
421         } catch (TransformerConfigurationException e) {
422             throw new PlugwiseHAException("Could not create XML transformer", e);
423         }
424     }
425
426     private <T> PlugwiseHAControllerRequest<T> newRequest(Class<T> responseType, @Nullable Transformer transformer) {
427         return new PlugwiseHAControllerRequest<T>(responseType, this.xStream, transformer, this.httpClient, this.host,
428                 this.port, this.username, this.smileId);
429     }
430
431     private <T> PlugwiseHAControllerRequest<T> newRequest(Class<T> responseType) {
432         return new PlugwiseHAControllerRequest<T>(responseType, this.xStream, null, this.httpClient, this.host,
433                 this.port, this.username, this.smileId);
434     }
435
436     @SuppressWarnings("null")
437     private <T> T executeRequest(PlugwiseHAControllerRequest<T> request) throws PlugwiseHAException {
438         T result;
439         result = request.execute();
440         return result;
441     }
442
443     private DomainObjects mergeDomainObjects(@Nullable DomainObjects updatedDomainObjects) {
444         DomainObjects localDomainObjects = this.domainObjects;
445         if (localDomainObjects == null && updatedDomainObjects != null) {
446             this.domainObjects = updatedDomainObjects;
447             return updatedDomainObjects;
448         } else if (localDomainObjects != null && updatedDomainObjects == null) {
449             return localDomainObjects;
450         } else if (localDomainObjects != null && updatedDomainObjects != null) {
451             Appliances appliances = updatedDomainObjects.getAppliances();
452             Locations locations = updatedDomainObjects.getLocations();
453
454             if (appliances != null) {
455                 localDomainObjects.mergeAppliances(appliances);
456             }
457
458             if (locations != null) {
459                 localDomainObjects.mergeLocations(locations);
460             }
461             this.domainObjects = localDomainObjects;
462             return localDomainObjects;
463         } else {
464             return new DomainObjects();
465         }
466     }
467 }