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