]> git.basschouten.com Git - openhab-addons.git/blob
16c3e3ff0016e6659defb5e2f3aeabfa28971078
[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_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
60
61     private final Logger logger = LoggerFactory.getLogger(PlugwiseHAController.class);
62
63     private final HttpClient httpClient;
64     private final PlugwiseHAXStream xStream;
65     private final Transformer domainObjectsTransformer;
66
67     private final String host;
68     private final int port;
69     private final String username;
70     private final String smileId;
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             throws PlugwiseHAException {
78         this.httpClient = httpClient;
79         this.host = host;
80         this.port = port;
81         this.username = username;
82         this.smileId = smileId;
83
84         this.xStream = new PlugwiseHAXStream();
85
86         ClassLoader localClassLoader = getClass().getClassLoader();
87         if (localClassLoader != null) {
88             this.domainObjectsTransformer = PlugwiseHAController
89                     .setXSLT(new StreamSource(localClassLoader.getResourceAsStream("domain_objects.xslt")));
90         } else {
91             throw new PlugwiseHAException("PlugwiseHAController.domainObjectsTransformer could not be initialized");
92         }
93     }
94
95     // Public methods
96
97     public void start(Runnable callback) throws PlugwiseHAException {
98         refresh();
99         callback.run();
100     }
101
102     public void refresh() throws PlugwiseHAException {
103         synchronized (this) {
104             this.getUpdatedDomainObjects();
105         }
106     }
107
108     // Public API methods
109
110     public GatewayInfo getGatewayInfo() throws PlugwiseHAException {
111         return getGatewayInfo(false);
112     }
113
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();
119         }
120
121         if (!forceRefresh && gatewayInfo != null) {
122             this.logger.debug("Found Plugwise Home Automation gateway");
123             return gatewayInfo;
124         } else {
125             PlugwiseHAControllerRequest<DomainObjects> request;
126
127             request = newRequest(DomainObjects.class, this.domainObjectsTransformer);
128
129             request.setPath("/core/domain_objects");
130             request.addPathParameter("class", "Gateway");
131
132             DomainObjects domainObjects = executeRequest(request);
133             this.gatewayUpdateDateTime = ZonedDateTime.parse(request.getServerDateTime(), PlugwiseHAController.FORMAT);
134
135             return mergeDomainObjects(domainObjects).getGatewayInfo();
136         }
137     }
138
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();
144         }
145
146         if (!forceRefresh && appliances != null) {
147             return appliances;
148         } else {
149             PlugwiseHAControllerRequest<DomainObjects> request;
150
151             request = newRequest(DomainObjects.class, this.domainObjectsTransformer);
152
153             request.setPath("/core/domain_objects");
154             request.addPathParameter("class", "Appliance");
155
156             DomainObjects domainObjects = executeRequest(request);
157             this.gatewayUpdateDateTime = ZonedDateTime.parse(request.getServerDateTime(), PlugwiseHAController.FORMAT);
158             int size = 0;
159             if (!(domainObjects.getAppliances() == null)) {
160                 size = domainObjects.getAppliances().size();
161             }
162             this.logger.debug("Found {} Plugwise Home Automation appliance(s)", size);
163
164             return mergeDomainObjects(domainObjects).getAppliances();
165         }
166     }
167
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);
172             return null;
173         } else {
174             return appliances.get(id);
175         }
176     }
177
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();
183         }
184
185         if (!forceRefresh && locations != null) {
186             return locations;
187         } else {
188             PlugwiseHAControllerRequest<DomainObjects> request;
189
190             request = newRequest(DomainObjects.class, this.domainObjectsTransformer);
191
192             request.setPath("/core/domain_objects");
193             request.addPathParameter("class", "Location");
194
195             DomainObjects domainObjects = executeRequest(request);
196             this.gatewayUpdateDateTime = ZonedDateTime.parse(request.getServerDateTime(), PlugwiseHAController.FORMAT);
197             int size = 0;
198             if (!(domainObjects.getLocations() == null)) {
199                 size = domainObjects.getLocations().size();
200             }
201             this.logger.debug("Found {} Plugwise Home Automation Zone(s)", size);
202             return mergeDomainObjects(domainObjects).getLocations();
203         }
204     }
205
206     public @Nullable Location getLocation(String id, Boolean forceRefresh) throws PlugwiseHAException {
207         Locations locations = this.getLocations(forceRefresh);
208
209         if (!locations.containsKey(id)) {
210             this.logger.debug("Plugwise Home Automation Zone with {} is not known", id);
211             return null;
212         } else {
213             return locations.get(id);
214         }
215     }
216
217     public @Nullable DomainObjects getDomainObjects() throws PlugwiseHAException {
218         PlugwiseHAControllerRequest<DomainObjects> request;
219
220         request = newRequest(DomainObjects.class, this.domainObjectsTransformer);
221
222         request.setPath("/core/domain_objects");
223         request.addPathParameter("@locale", "en-US");
224
225         DomainObjects domainObjects = executeRequest(request);
226         this.gatewayUpdateDateTime = ZonedDateTime.parse(request.getServerDateTime(), PlugwiseHAController.FORMAT);
227         this.gatewayFullUpdateDateTime = this.gatewayUpdateDateTime;
228
229         return mergeDomainObjects(domainObjects);
230     }
231
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();
241         } else {
242             return getUpdatedDomainObjects(localGatewayUpdateDateTime);
243         }
244     }
245
246     public @Nullable DomainObjects getUpdatedDomainObjects(ZonedDateTime since) throws PlugwiseHAException {
247         return getUpdatedDomainObjects(since.toEpochSecond());
248     }
249
250     public @Nullable DomainObjects getUpdatedDomainObjects(Long since) throws PlugwiseHAException {
251         PlugwiseHAControllerRequest<DomainObjects> request;
252
253         request = newRequest(DomainObjects.class, this.domainObjectsTransformer);
254
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");
260
261         DomainObjects domainObjects = executeRequest(request);
262         this.gatewayUpdateDateTime = ZonedDateTime.parse(request.getServerDateTime(), PlugwiseHAController.FORMAT);
263
264         return mergeDomainObjects(domainObjects);
265     }
266
267     public void setLocationThermostat(Location location, Double temperature) throws PlugwiseHAException {
268         PlugwiseHAControllerRequest<Void> request = newRequest(Void.class);
269         Optional<ActuatorFunctionality> thermostat = location.getActuatorFunctionalities().getFunctionalityThermostat();
270
271         if (thermostat.isPresent()) {
272             request.setPath("/core/locations");
273
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));
277
278             executeRequest(request);
279         }
280     }
281
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();
286
287         if (thermostat.isPresent()) {
288             request.setPath("/core/appliances");
289
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));
293
294             executeRequest(request);
295         }
296     }
297
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();
302
303         if (offsetTemperatureFunctionality.isPresent()) {
304             request.setPath("/core/appliances");
305
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));
309
310             executeRequest(request);
311         }
312     }
313
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);
319             } else {
320                 switchRelayOff(appliance);
321             }
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));
333
334         executeRequest(request);
335     }
336
337     public void switchRelayOn(Appliance appliance) throws PlugwiseHAException {
338         PlugwiseHAControllerRequest<Void> request = newRequest(Void.class);
339
340         request.setPath("/core/appliances");
341         request.addPathParameter("id", String.format("%s/relay", appliance.getId()));
342         request.setBodyParameter(new ActuatorFunctionalityRelay("on"));
343
344         executeRequest(request);
345     }
346
347     public void switchRelayOff(Appliance appliance) throws PlugwiseHAException {
348         PlugwiseHAControllerRequest<Void> request = newRequest(Void.class);
349
350         request.setPath("/core/appliances");
351         request.addPathParameter("id", String.format("%s/relay", appliance.getId()));
352         request.setBodyParameter(new ActuatorFunctionalityRelay("off"));
353
354         executeRequest(request);
355     }
356
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);
362             } else {
363                 switchRelayLockOff(appliance);
364             }
365         }
366     }
367
368     public void switchRelayLockOff(Appliance appliance) throws PlugwiseHAException {
369         PlugwiseHAControllerRequest<Void> request = newRequest(Void.class);
370
371         request.setPath("/core/appliances");
372         request.addPathParameter("id", String.format("%s/relay", appliance.getId()));
373         request.setBodyParameter(new ActuatorFunctionalityRelay(null, false));
374
375         executeRequest(request);
376     }
377
378     public void switchRelayLockOn(Appliance appliance) throws PlugwiseHAException {
379         PlugwiseHAControllerRequest<Void> request = newRequest(Void.class);
380
381         request.setPath("/core/appliances");
382         request.addPathParameter("id", String.format("%s/relay", appliance.getId()));
383         request.setBodyParameter(new ActuatorFunctionalityRelay(null, true));
384
385         executeRequest(request);
386     }
387
388     public ZonedDateTime ping() throws PlugwiseHAException {
389         PlugwiseHAControllerRequest<Void> request;
390
391         request = newRequest(Void.class, null);
392
393         request.setPath("/cache/gateways");
394         request.addPathParameter("ping");
395
396         executeRequest(request);
397
398         return ZonedDateTime.parse(request.getServerDateTime(), PlugwiseHAController.FORMAT);
399     }
400
401     // Protected and private methods
402
403     private static Transformer setXSLT(StreamSource xsltSource) throws PlugwiseHAException {
404         try {
405             return TransformerFactory.newInstance().newTransformer(xsltSource);
406         } catch (TransformerConfigurationException e) {
407             throw new PlugwiseHAException("Could not create XML transformer", e);
408         }
409     }
410
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);
414     }
415
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);
419     }
420
421     @SuppressWarnings("null")
422     private <T> T executeRequest(PlugwiseHAControllerRequest<T> request) throws PlugwiseHAException {
423         T result;
424         result = request.execute();
425         return result;
426     }
427
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();
437
438             if (appliances != null) {
439                 localDomainObjects.mergeAppliances(appliances);
440             }
441
442             if (locations != null) {
443                 localDomainObjects.mergeLocations(locations);
444             }
445             return localDomainObjects;
446         } else {
447             return new DomainObjects();
448         }
449     }
450 }