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