]> git.basschouten.com Git - openhab-addons.git/blob
13792f3e9892ff9e0e9c9a46aa490e0eae2d5986
[openhab-addons.git] /
1 /**
2  * Copyright (c) 2010-2021 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.haywardomnilogic.internal.handler;
14
15 import java.io.StringReader;
16 import java.util.ArrayList;
17 import java.util.Collection;
18 import java.util.Collections;
19 import java.util.List;
20 import java.util.Map;
21 import java.util.concurrent.ExecutionException;
22 import java.util.concurrent.ScheduledFuture;
23 import java.util.concurrent.TimeUnit;
24 import java.util.concurrent.TimeoutException;
25
26 import javax.xml.xpath.XPath;
27 import javax.xml.xpath.XPathConstants;
28 import javax.xml.xpath.XPathExpressionException;
29 import javax.xml.xpath.XPathFactory;
30
31 import org.eclipse.jdt.annotation.NonNullByDefault;
32 import org.eclipse.jdt.annotation.Nullable;
33 import org.eclipse.jetty.client.HttpClient;
34 import org.eclipse.jetty.client.api.ContentResponse;
35 import org.eclipse.jetty.client.api.Request;
36 import org.eclipse.jetty.client.util.StringContentProvider;
37 import org.eclipse.jetty.http.HttpHeader;
38 import org.eclipse.jetty.http.HttpMethod;
39 import org.eclipse.jetty.http.HttpVersion;
40 import org.openhab.binding.haywardomnilogic.internal.HaywardAccount;
41 import org.openhab.binding.haywardomnilogic.internal.HaywardBindingConstants;
42 import org.openhab.binding.haywardomnilogic.internal.HaywardException;
43 import org.openhab.binding.haywardomnilogic.internal.HaywardThingHandler;
44 import org.openhab.binding.haywardomnilogic.internal.HaywardTypeToRequest;
45 import org.openhab.binding.haywardomnilogic.internal.config.HaywardConfig;
46 import org.openhab.binding.haywardomnilogic.internal.discovery.HaywardDiscoveryService;
47 import org.openhab.core.library.types.OnOffType;
48 import org.openhab.core.thing.Bridge;
49 import org.openhab.core.thing.ChannelUID;
50 import org.openhab.core.thing.Thing;
51 import org.openhab.core.thing.ThingStatus;
52 import org.openhab.core.thing.ThingStatusDetail;
53 import org.openhab.core.thing.binding.BaseBridgeHandler;
54 import org.openhab.core.thing.binding.ThingHandlerService;
55 import org.openhab.core.types.Command;
56 import org.slf4j.Logger;
57 import org.slf4j.LoggerFactory;
58 import org.w3c.dom.NodeList;
59 import org.xml.sax.InputSource;
60
61 /**
62  * The {@link HaywardBridgeHandler} is responsible for handling commands, which are
63  * sent to one of the channels.
64  *
65  * @author Matt Myers - Initial contribution
66  */
67
68 @NonNullByDefault
69 public class HaywardBridgeHandler extends BaseBridgeHandler {
70     private final Logger logger = LoggerFactory.getLogger(HaywardBridgeHandler.class);
71     private final HttpClient httpClient;
72     private @Nullable ScheduledFuture<?> initializeFuture;
73     private @Nullable ScheduledFuture<?> pollTelemetryFuture;
74     private @Nullable ScheduledFuture<?> pollAlarmsFuture;
75     private int commFailureCount;
76     public HaywardConfig config = getConfig().as(HaywardConfig.class);
77     public HaywardAccount account = getConfig().as(HaywardAccount.class);
78
79     @Override
80     public Collection<Class<? extends ThingHandlerService>> getServices() {
81         return Collections.singleton(HaywardDiscoveryService.class);
82     }
83
84     public HaywardBridgeHandler(Bridge bridge, HttpClient httpClient) {
85         super(bridge);
86         this.httpClient = httpClient;
87     }
88
89     @Override
90     public void handleCommand(ChannelUID channelUID, Command command) {
91     }
92
93     @Override
94     public void dispose() {
95         clearPolling(initializeFuture);
96         clearPolling(pollTelemetryFuture);
97         clearPolling(pollAlarmsFuture);
98         logger.trace("Hayward polling cancelled");
99         super.dispose();
100     }
101
102     @Override
103     public void initialize() {
104         initializeFuture = scheduler.schedule(this::scheduledInitialize, 1, TimeUnit.SECONDS);
105         return;
106     }
107
108     public void scheduledInitialize() {
109         config = getConfigAs(HaywardConfig.class);
110
111         try {
112             clearPolling(pollTelemetryFuture);
113             clearPolling(pollAlarmsFuture);
114
115             if (!(login())) {
116                 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
117                         "Unable to Login to Hayward's server");
118                 clearPolling(pollTelemetryFuture);
119                 clearPolling(pollAlarmsFuture);
120                 commFailureCount = 50;
121                 initPolling(60);
122                 return;
123             }
124
125             if (!(getSiteList())) {
126                 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
127                         "Unable to getMSP from Hayward's server");
128                 clearPolling(pollTelemetryFuture);
129                 clearPolling(pollAlarmsFuture);
130                 commFailureCount = 50;
131                 initPolling(60);
132                 return;
133             }
134
135             if (!(mspConfigUnits())) {
136                 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
137                         "Unable to getMSPConfigUnits from Hayward's server");
138                 clearPolling(pollTelemetryFuture);
139                 clearPolling(pollAlarmsFuture);
140                 commFailureCount = 50;
141                 initPolling(60);
142                 return;
143             }
144
145             if (this.thing.getStatus() != ThingStatus.ONLINE) {
146                 updateStatus(ThingStatus.ONLINE);
147             }
148
149             logger.debug("Succesfully opened connection to Hayward's server: {} Username:{}", config.endpointUrl,
150                     config.username);
151
152             initPolling(0);
153             logger.trace("Hayward Telemetry polling scheduled");
154
155             if (config.alarmPollTime > 0) {
156                 initAlarmPolling(1);
157             }
158         } catch (HaywardException e) {
159             updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.HANDLER_INITIALIZING_ERROR,
160                     "scheduledInitialize exception: " + e.getMessage());
161             clearPolling(pollTelemetryFuture);
162             clearPolling(pollAlarmsFuture);
163             commFailureCount = 50;
164             initPolling(60);
165             return;
166         } catch (InterruptedException e) {
167             return;
168         }
169     }
170
171     public synchronized boolean login() throws HaywardException, InterruptedException {
172         String xmlResponse;
173         String status;
174
175         // *****Login to Hayward server
176         String urlParameters = "<?xml version=\"1.0\" encoding=\"utf-8\"?><Request>" + "<Name>Login</Name><Parameters>"
177                 + "<Parameter name=\"UserName\" dataType=\"String\">" + config.username + "</Parameter>"
178                 + "<Parameter name=\"Password\" dataType=\"String\">" + config.password + "</Parameter>"
179                 + "</Parameters></Request>";
180
181         xmlResponse = httpXmlResponse(urlParameters);
182
183         if (xmlResponse.isEmpty()) {
184             return false;
185         }
186
187         status = evaluateXPath("/Response/Parameters//Parameter[@name='Status']/text()", xmlResponse).get(0);
188
189         if (!("0".equals(status))) {
190             logger.debug("Hayward Connection thing: Login XML response: {}", xmlResponse);
191             return false;
192         }
193
194         account.token = evaluateXPath("/Response/Parameters//Parameter[@name='Token']/text()", xmlResponse).get(0);
195         account.userID = evaluateXPath("/Response/Parameters//Parameter[@name='UserID']/text()", xmlResponse).get(0);
196         return true;
197     }
198
199     public synchronized boolean getApiDef() throws HaywardException, InterruptedException {
200         String xmlResponse;
201
202         // *****getConfig from Hayward server
203         String urlParameters = "<?xml version=\"1.0\" encoding=\"utf-8\"?><Request><Name>GetAPIDef</Name><Parameters>"
204                 + "<Parameter name=\"Token\" dataType=\"String\">" + account.token + "</Parameter>"
205                 + "<Parameter name=\"MspSystemID\" dataType=\"int\">" + account.mspSystemID + "</Parameter>;"
206                 + "<Parameter name=\"Version\" dataType=\"string\">0.4</Parameter >\r\n"
207                 + "<Parameter name=\"Language\" dataType=\"string\">en</Parameter >\r\n" + "</Parameters></Request>";
208
209         xmlResponse = httpXmlResponse(urlParameters);
210
211         if (xmlResponse.isEmpty()) {
212             logger.debug("Hayward Connection thing: Login XML response was null");
213             return false;
214         }
215         return true;
216     }
217
218     public synchronized boolean getSiteList() throws HaywardException, InterruptedException {
219         String xmlResponse;
220         String status;
221
222         // *****Get MSP
223         String urlParameters = "<?xml version=\"1.0\" encoding=\"utf-8\"?><Request><Name>GetSiteList</Name><Parameters>"
224                 + "<Parameter name=\"Token\" dataType=\"String\">" + account.token
225                 + "</Parameter><Parameter name=\"UserID\" dataType=\"String\">" + account.userID
226                 + "</Parameter></Parameters></Request>";
227
228         xmlResponse = httpXmlResponse(urlParameters);
229
230         if (xmlResponse.isEmpty()) {
231             logger.debug("Hayward Connection thing: getSiteList XML response was null");
232             return false;
233         }
234
235         status = evaluateXPath("/Response/Parameters//Parameter[@name='Status']/text()", xmlResponse).get(0);
236
237         if (!("0".equals(status))) {
238             logger.debug("Hayward Connection thing: getSiteList XML response: {}", xmlResponse);
239             return false;
240         }
241
242         account.mspSystemID = evaluateXPath("/Response/Parameters/Parameter/Item//Property[@name='MspSystemID']/text()",
243                 xmlResponse).get(0);
244         account.backyardName = evaluateXPath(
245                 "/Response/Parameters/Parameter/Item//Property[@name='BackyardName']/text()", xmlResponse).get(0);
246         account.address = evaluateXPath("/Response/Parameters/Parameter/Item//Property[@name='Address']/text()",
247                 xmlResponse).get(0);
248         return true;
249     }
250
251     public synchronized String getMspConfig() throws HaywardException, InterruptedException {
252         // *****getMspConfig from Hayward server
253         String urlParameters = "<?xml version=\"1.0\" encoding=\"utf-8\"?><Request><Name>GetMspConfigFile</Name><Parameters>"
254                 + "<Parameter name=\"Token\" dataType=\"String\">" + account.token + "</Parameter>"
255                 + "<Parameter name=\"MspSystemID\" dataType=\"int\">" + account.mspSystemID
256                 + "</Parameter><Parameter name=\"Version\" dataType=\"string\">0</Parameter>\r\n"
257                 + "</Parameters></Request>";
258
259         String xmlResponse = httpXmlResponse(urlParameters);
260
261         // Debug: Inject xml file for testing
262         // String path =
263         // "C:/Users/Controls/openhab-2-5-x/git/openhab-addons/bundles/org.openhab.binding.haywardomnilogic/getConfig.xml";
264         // xmlResponse = new String(Files.readAllBytes(Paths.get(path)));
265
266         if (xmlResponse.isEmpty()) {
267             logger.debug("Hayward Connection thing: requestConfig XML response was null");
268             return "Fail";
269         }
270
271         if (evaluateXPath("//Backyard/Name/text()", xmlResponse).isEmpty()) {
272             logger.debug("Hayward Connection thing: requestConfiguration XML response: {}", xmlResponse);
273             return "Fail";
274         }
275         return xmlResponse;
276     }
277
278     public synchronized boolean mspConfigUnits() throws HaywardException, InterruptedException {
279         List<String> property1 = new ArrayList<>();
280         List<String> property2 = new ArrayList<>();
281
282         String xmlResponse = getMspConfig();
283
284         // Get Units (Standard, Metric)
285         property1 = evaluateXPath("//System/Units/text()", xmlResponse);
286         account.units = property1.get(0);
287
288         // Get Variable Speed Pump Units (percent, RPM)
289         property2 = evaluateXPath("//System/Msp-Vsp-Speed-Format/text()", xmlResponse);
290         account.vspSpeedFormat = property2.get(0);
291
292         return true;
293     }
294
295     public synchronized boolean getTelemetryData() throws HaywardException, InterruptedException {
296         // *****getTelemetry from Hayward server
297         String urlParameters = "<?xml version=\"1.0\" encoding=\"utf-8\"?><Request><Name>GetTelemetryData</Name><Parameters>"
298                 + "<Parameter name=\"Token\" dataType=\"String\">" + account.token + "</Parameter>"
299                 + "<Parameter name=\"MspSystemID\" dataType=\"int\">" + account.mspSystemID
300                 + "</Parameter></Parameters></Request>";
301
302         String xmlResponse = httpXmlResponse(urlParameters);
303
304         if (xmlResponse.isEmpty()) {
305             logger.debug("Hayward Connection thing: getTelemetry XML response was null");
306             return false;
307         }
308
309         if (!evaluateXPath("/Response/Parameters//Parameter[@name='StatusMessage']/text()", xmlResponse).isEmpty()) {
310             logger.debug("Hayward Connection thing: getTelemetry XML response: {}", xmlResponse);
311             return false;
312         }
313
314         for (Thing thing : getThing().getThings()) {
315             if (thing.getHandler() instanceof HaywardThingHandler) {
316                 HaywardThingHandler handler = (HaywardThingHandler) thing.getHandler();
317                 if (handler != null) {
318                     handler.getTelemetry(xmlResponse);
319                 }
320             }
321         }
322         return true;
323     }
324
325     public synchronized boolean getAlarmList() throws HaywardException {
326         for (Thing thing : getThing().getThings()) {
327             Map<String, String> properties = thing.getProperties();
328             if ("BACKYARD".equals(properties.get(HaywardBindingConstants.PROPERTY_TYPE))) {
329                 HaywardBackyardHandler handler = (HaywardBackyardHandler) thing.getHandler();
330                 if (handler != null) {
331                     String systemID = properties.get(HaywardBindingConstants.PROPERTY_SYSTEM_ID);
332                     if (systemID != null) {
333                         return handler.getAlarmList(systemID);
334                     }
335                 }
336             }
337         }
338         return false;
339     }
340
341     private synchronized void initPolling(int initalDelay) {
342         pollTelemetryFuture = scheduler.scheduleWithFixedDelay(() -> {
343             try {
344                 if (commFailureCount >= 5) {
345                     commFailureCount = 0;
346                     clearPolling(pollTelemetryFuture);
347                     clearPolling(pollAlarmsFuture);
348                     initialize();
349                     return;
350                 }
351                 if (!(getTelemetryData())) {
352                     commFailureCount++;
353                     return;
354                 }
355                 updateStatus(ThingStatus.ONLINE);
356             } catch (HaywardException e) {
357                 logger.debug("Hayward Connection thing: Exception during poll: {}", e.getMessage());
358             } catch (InterruptedException e) {
359                 return;
360             }
361         }, initalDelay, config.telemetryPollTime, TimeUnit.SECONDS);
362         return;
363     }
364
365     private synchronized void initAlarmPolling(int initalDelay) {
366         pollAlarmsFuture = scheduler.scheduleWithFixedDelay(() -> {
367             try {
368                 getAlarmList();
369             } catch (HaywardException e) {
370                 logger.debug("Hayward Connection thing: Exception during poll: {}", e.getMessage());
371             }
372         }, initalDelay, config.alarmPollTime, TimeUnit.SECONDS);
373     }
374
375     private void clearPolling(@Nullable ScheduledFuture<?> pollJob) {
376         if (pollJob != null) {
377             pollJob.cancel(false);
378         }
379     }
380
381     @Nullable
382     Thing getThingForType(HaywardTypeToRequest type, int num) {
383         for (Thing thing : getThing().getThings()) {
384             Map<String, String> properties = thing.getProperties();
385             if (Integer.toString(num).equals(properties.get(HaywardBindingConstants.PROPERTY_SYSTEM_ID))) {
386                 if (type.toString().equals(properties.get(HaywardBindingConstants.PROPERTY_TYPE))) {
387                     return thing;
388                 }
389             }
390         }
391         return null;
392     }
393
394     public List<String> evaluateXPath(String xpathExp, String xmlResponse) {
395         List<String> values = new ArrayList<>();
396         try {
397             InputSource inputXML = new InputSource(new StringReader(xmlResponse));
398             XPath xPath = XPathFactory.newInstance().newXPath();
399             NodeList nodes = (NodeList) xPath.evaluate(xpathExp, inputXML, XPathConstants.NODESET);
400
401             for (int i = 0; i < nodes.getLength(); i++) {
402                 values.add(nodes.item(i).getNodeValue());
403             }
404         } catch (XPathExpressionException e) {
405             logger.warn("XPathExpression exception: {}", e.getMessage());
406         }
407         return values;
408     }
409
410     private Request sendRequestBuilder(String url, HttpMethod method) {
411         return this.httpClient.newRequest(url).agent("NextGenForIPhone/16565 CFNetwork/887 Darwin/17.0.0")
412                 .method(method).header(HttpHeader.ACCEPT_LANGUAGE, "en-us").header(HttpHeader.ACCEPT, "*/*")
413                 .header(HttpHeader.ACCEPT_ENCODING, "gzip, deflate").version(HttpVersion.HTTP_1_1)
414                 .header(HttpHeader.CONNECTION, "keep-alive").header(HttpHeader.HOST, "www.haywardomnilogic.com:80")
415                 .timeout(10, TimeUnit.SECONDS);
416     }
417
418     public synchronized String httpXmlResponse(String urlParameters) throws HaywardException, InterruptedException {
419         String urlParameterslength = Integer.toString(urlParameters.length());
420         String statusMessage;
421
422         try {
423             ContentResponse httpResponse = sendRequestBuilder(config.endpointUrl, HttpMethod.POST)
424                     .content(new StringContentProvider(urlParameters), "text/xml; charset=utf-8")
425                     .header(HttpHeader.CONTENT_LENGTH, urlParameterslength).send();
426
427             int status = httpResponse.getStatus();
428             String xmlResponse = httpResponse.getContentAsString();
429
430             if (status == 200) {
431                 List<String> statusMessages = evaluateXPath(
432                         "/Response/Parameters//Parameter[@name='StatusMessage']/text()", xmlResponse);
433                 if (!(statusMessages.isEmpty())) {
434                     statusMessage = statusMessages.get(0);
435                 } else {
436                     statusMessage = httpResponse.getReason();
437                 }
438
439                 if (logger.isTraceEnabled()) {
440                     logger.trace("Hayward Connection thing:  {} Hayward http command: {}", getCallingMethod(),
441                             urlParameters);
442                     logger.trace("Hayward Connection thing:  {} Hayward http response: {} {}", getCallingMethod(),
443                             statusMessage, xmlResponse);
444                 }
445                 return xmlResponse;
446             } else {
447                 if (logger.isDebugEnabled()) {
448                     logger.debug("Hayward Connection thing:  {} Hayward http command: {}", getCallingMethod(),
449                             urlParameters);
450                     logger.debug("Hayward Connection thing:  {} Hayward http response: {} {}", getCallingMethod(),
451                             status, xmlResponse);
452                 }
453                 return "";
454             }
455         } catch (ExecutionException e) {
456             updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
457                     "Unable to resolve host.  Check Hayward hostname and your internet connection. " + e);
458             return "";
459         } catch (TimeoutException e) {
460             updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
461                     "Connection Timeout.  Check Hayward hostname and your internet connection. " + e);
462             return "";
463         }
464     }
465
466     private String getCallingMethod() {
467         StackTraceElement[] stacktrace = Thread.currentThread().getStackTrace();
468         StackTraceElement e = stacktrace[3];
469         return e.getMethodName();
470     }
471
472     public int convertCommand(Command command) {
473         if (command == OnOffType.ON) {
474             return 1;
475         } else {
476             return 0;
477         }
478     }
479 }