]> git.basschouten.com Git - openhab-addons.git/blob
cca5cbf13353aeeafa36b9a76d2a89b69ac04c71
[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.helios.internal.handler;
14
15 import static org.openhab.binding.helios.internal.HeliosBindingConstants.*;
16
17 import java.io.IOException;
18 import java.nio.charset.StandardCharsets;
19 import java.security.KeyManagementException;
20 import java.security.NoSuchAlgorithmException;
21 import java.security.cert.CertificateException;
22 import java.security.cert.X509Certificate;
23 import java.text.SimpleDateFormat;
24 import java.util.Arrays;
25 import java.util.Base64;
26 import java.util.Date;
27 import java.util.HashMap;
28 import java.util.List;
29 import java.util.Map;
30 import java.util.Set;
31 import java.util.concurrent.ScheduledFuture;
32 import java.util.concurrent.TimeUnit;
33
34 import javax.net.ssl.HostnameVerifier;
35 import javax.net.ssl.SSLContext;
36 import javax.net.ssl.X509TrustManager;
37 import javax.ws.rs.client.Client;
38 import javax.ws.rs.client.ClientBuilder;
39 import javax.ws.rs.client.ClientRequestContext;
40 import javax.ws.rs.client.ClientRequestFilter;
41 import javax.ws.rs.client.WebTarget;
42 import javax.ws.rs.core.MediaType;
43 import javax.ws.rs.core.MultivaluedMap;
44 import javax.ws.rs.core.Response;
45
46 import org.openhab.binding.helios.internal.ws.rest.RESTError;
47 import org.openhab.binding.helios.internal.ws.rest.RESTEvent;
48 import org.openhab.binding.helios.internal.ws.rest.RESTPort;
49 import org.openhab.binding.helios.internal.ws.rest.RESTSubscribeResponse;
50 import org.openhab.binding.helios.internal.ws.rest.RESTSwitch;
51 import org.openhab.binding.helios.internal.ws.rest.RESTSystemInfo;
52 import org.openhab.core.library.types.DateTimeType;
53 import org.openhab.core.library.types.DecimalType;
54 import org.openhab.core.library.types.OnOffType;
55 import org.openhab.core.library.types.StringType;
56 import org.openhab.core.thing.Channel;
57 import org.openhab.core.thing.ChannelUID;
58 import org.openhab.core.thing.Thing;
59 import org.openhab.core.thing.ThingStatus;
60 import org.openhab.core.thing.ThingStatusDetail;
61 import org.openhab.core.thing.binding.BaseThingHandler;
62 import org.openhab.core.thing.binding.builder.ChannelBuilder;
63 import org.openhab.core.thing.binding.builder.ThingBuilder;
64 import org.openhab.core.thing.type.ChannelTypeUID;
65 import org.openhab.core.types.Command;
66 import org.openhab.core.types.RefreshType;
67 import org.openhab.core.types.UnDefType;
68 import org.slf4j.Logger;
69 import org.slf4j.LoggerFactory;
70
71 import com.google.gson.Gson;
72 import com.google.gson.JsonElement;
73 import com.google.gson.JsonObject;
74 import com.google.gson.JsonParser;
75
76 /**
77  * The {@link HeliosHandler221} is responsible for handling commands, which are
78  * sent to one of the channels.
79  *
80  * @author Karel Goderis - Initial contribution
81  */
82
83 public class HeliosHandler221 extends BaseThingHandler {
84
85     private final Logger logger = LoggerFactory.getLogger(HeliosHandler221.class);
86
87     // List of Configuration constants
88     public static final String IP_ADDRESS = "ipAddress";
89     public static final String USERNAME = "username";
90     public static final String PASSWORD = "password";
91
92     // List of all REST API URI, commands, and JSON constants
93     public static final String BASE_URI = "https://{ip}/api/";
94     public static final String SYSTEM_PATH = "system/{cmd}";
95     public static final String FIRMWARE_PATH = "firmware/{cmd}";
96     public static final String LOG_PATH = "log/{cmd}";
97     public static final String SWITCH_PATH = "switch/{cmd}";
98     public static final String PORT_PATH = "io/{cmd}";
99
100     public static final String INFO = "info";
101     public static final String STATUS = "status";
102
103     public static final String SUBSCRIBE = "subscribe";
104     public static final String UNSUBSCRIBE = "unsubscribe";
105     public static final String PULL = "pull";
106     public static final String CAPABILITIES = "caps";
107     public static final String CONTROL = "ctrl";
108
109     public static final String DEVICESTATE = "DeviceState";
110     public static final String AUDIOLOOPTEST = "AudioLoopTest";
111     public static final String MOTIONDETECTED = "MotionDetected";
112     public static final String NOISEDETECTED = "NoiseDetected";
113     public static final String KEYPRESSED = "KeyPressed";
114     public static final String KEYRELEASED = "KeyReleased";
115     public static final String CODEENTERED = "CodeEntered";
116     public static final String CARDENTERED = "CardEntered";
117     public static final String INPUTCHANGED = "InputChanged";
118     public static final String OUTPUTCHANGED = "OutputChanged";
119     public static final String CALLSTATECHANGED = "CallStateChanged";
120     public static final String REGISTRATIONSTATECHANGED = "RegistrationStateChanged";
121     public static final String SWITCHSTATECHANGED = "SwitchStateChanged";
122
123     // REST Client API variables
124     private Client heliosClient;
125     private WebTarget baseTarget;
126     private WebTarget systemTarget;
127     private WebTarget logTarget;
128     private WebTarget switchTarget;
129     private WebTarget portTarget;
130     private String ipAddress;
131
132     // JSON variables
133     private JsonParser parser = new JsonParser();
134     private Gson gson = new Gson();
135
136     private ScheduledFuture<?> logJob;
137     private static final long RESET_INTERVAL = 15;
138     private static final long HELIOS_DURATION = 120;
139     private static final long HELIOS_PULL_DURATION = 10;
140
141     private long logSubscriptionID = 0;
142
143     public HeliosHandler221(Thing thing) {
144         super(thing);
145     }
146
147     @Override
148     public void initialize() {
149         logger.debug("Initializing the Helios IP Vario handler for '{}'.", getThing().getUID().toString());
150
151         ipAddress = (String) getConfig().get(IP_ADDRESS);
152         String username = (String) getConfig().get(USERNAME);
153         String password = (String) getConfig().get(PASSWORD);
154
155         if (ipAddress != null && !ipAddress.isEmpty() && username != null && !username.isEmpty() && password != null
156                 && !password.isEmpty()) {
157             SecureRestClientTrustManager secureRestClientTrustManager = new SecureRestClientTrustManager();
158             SSLContext sslContext = null;
159             try {
160                 sslContext = SSLContext.getInstance("SSL");
161             } catch (NoSuchAlgorithmException e1) {
162                 logger.error("An exception occurred while requesting the SSL encryption algorithm : '{}'",
163                         e1.getMessage(), e1);
164             }
165             try {
166                 if (sslContext != null) {
167                     sslContext.init(null, new javax.net.ssl.TrustManager[] { secureRestClientTrustManager }, null);
168                 }
169             } catch (KeyManagementException e1) {
170                 logger.error("An exception occurred while initialising the SSL context : '{}'", e1.getMessage(), e1);
171             }
172
173             heliosClient = ClientBuilder.newBuilder().sslContext(sslContext).hostnameVerifier(new HostnameVerifier() {
174                 @Override
175                 public boolean verify(String hostname, javax.net.ssl.SSLSession sslSession) {
176                     return true;
177                 }
178             }).build();
179             heliosClient.register(new Authenticator(username, password));
180
181             baseTarget = heliosClient.target(BASE_URI);
182             systemTarget = baseTarget.path(SYSTEM_PATH);
183             logTarget = baseTarget.path(LOG_PATH);
184             switchTarget = baseTarget.path(SWITCH_PATH);
185
186             Response response = null;
187             try {
188                 response = systemTarget.resolveTemplate("ip", ipAddress).resolveTemplate("cmd", INFO)
189                         .request(MediaType.APPLICATION_JSON_TYPE).get();
190             } catch (NullPointerException e) {
191                 logger.debug("An exception occurred while fetching system info of the Helios IP Vario '{}' : '{}'",
192                         getThing().getUID().toString(), e.getMessage(), e);
193                 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, e.getMessage());
194                 scheduler.schedule(resetRunnable, RESET_INTERVAL, TimeUnit.SECONDS);
195                 return;
196             }
197
198             if (response == null) {
199                 logger.debug("There is a configuration problem for the Helios IP Vario '{}'",
200                         getThing().getUID().toString());
201                 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR);
202                 scheduler.schedule(resetRunnable, RESET_INTERVAL, TimeUnit.SECONDS);
203                 return;
204             }
205
206             JsonObject jsonObject = parser.parse(response.readEntity(String.class)).getAsJsonObject();
207
208             if (logger.isTraceEnabled()) {
209                 logger.trace("initialize() Request : {}", systemTarget.resolveTemplate("ip", ipAddress)
210                         .resolveTemplate("cmd", INFO).getUri().toASCIIString());
211                 if (jsonObject.get("success").toString().equals("true")) {
212                     logger.trace("initialize() Response: {}", jsonObject.get("result"));
213                 }
214                 if (jsonObject.get("success").toString().equals("false")) {
215                     logger.trace("initialize() Response: {}", jsonObject.get("error"));
216                 }
217             }
218
219             if (jsonObject.get("success").toString().equals("false")) {
220                 RESTError error = gson.fromJson(jsonObject.get("error").toString(), RESTError.class);
221                 logger.debug(
222                         "An error occurred while communicating with the Helios IP Vario '{}': code '{}', param '{}' : '{}'",
223                         new Object[] { getThing().getUID().toString(), error.code, error.param, error.description });
224                 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
225                         error.code + ":" + error.param + ":" + error.description);
226                 scheduler.schedule(resetRunnable, RESET_INTERVAL, TimeUnit.SECONDS);
227                 return;
228             }
229
230             if (jsonObject.get("success").toString().equals("true")) {
231                 if (logJob == null || logJob.isCancelled()) {
232                     logJob = scheduler.scheduleWithFixedDelay(logRunnable, 0, 1, TimeUnit.SECONDS);
233                 }
234
235                 updateStatus(ThingStatus.ONLINE);
236
237                 scheduler.schedule(configureRunnable, 0, TimeUnit.SECONDS);
238             }
239         }
240     }
241
242     @Override
243     public void dispose() {
244         logger.debug("Disposing the Helios IP Vario handler for '{}'.", getThing().getUID().toString());
245
246         if (logSubscriptionID != 0) {
247             unsubscribe();
248         }
249
250         if (logJob != null && !logJob.isCancelled()) {
251             logJob.cancel(true);
252             logJob = null;
253         }
254
255         if (heliosClient != null) {
256             heliosClient.close();
257             heliosClient = null;
258         }
259     }
260
261     @Override
262     public void handleCommand(ChannelUID channelUID, Command command) {
263         if (!(command instanceof RefreshType)) {
264             ChannelTypeUID triggerUID = new ChannelTypeUID(BINDING_ID, SWITCH_TRIGGER);
265             ChannelTypeUID enablerUID = new ChannelTypeUID(BINDING_ID, SWITCH_ENABLER);
266             Channel theChannel = getThing().getChannel(channelUID.getId());
267
268             if (theChannel != null) {
269                 ChannelTypeUID channelType = theChannel.getChannelTypeUID();
270                 if (channelType.equals(triggerUID)) {
271                     String switchID = channelUID.getId().substring(6);
272                     triggerSwitch(switchID);
273                 }
274
275                 if (channelType.equals(enablerUID)) {
276                     String switchID = channelUID.getId().substring(6, channelUID.getId().lastIndexOf("active"));
277                     if (command instanceof OnOffType && command == OnOffType.OFF) {
278                         enableSwitch(switchID, false);
279                     } else if (command instanceof OnOffType && command == OnOffType.ON) {
280                         enableSwitch(switchID, true);
281                     }
282                 }
283             }
284         }
285     }
286
287     private long subscribe() {
288         if (getThing().getStatus() == ThingStatus.ONLINE) {
289             logTarget = baseTarget.path(LOG_PATH);
290
291             Response response = null;
292             try {
293                 response = logTarget.resolveTemplate("ip", ipAddress).resolveTemplate("cmd", SUBSCRIBE)
294                         .queryParam("include", "new").queryParam("duration", HELIOS_DURATION)
295                         .request(MediaType.APPLICATION_JSON_TYPE).get();
296             } catch (NullPointerException e) {
297                 logger.debug(
298                         "An exception occurred while subscribing to the log entries of the Helios IP Vario '{}' : '{}'",
299                         getThing().getUID().toString(), e.getMessage(), e);
300                 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, e.getMessage());
301                 scheduler.schedule(resetRunnable, RESET_INTERVAL, TimeUnit.SECONDS);
302                 return 0;
303             }
304
305             if (response != null) {
306                 JsonObject jsonObject = parser.parse(response.readEntity(String.class)).getAsJsonObject();
307
308                 if (logger.isTraceEnabled()) {
309                     logger.trace("subscribe() Request : {}",
310                             logTarget.resolveTemplate("ip", ipAddress).resolveTemplate("cmd", SUBSCRIBE)
311                                     .queryParam("include", "new").queryParam("duration", HELIOS_DURATION).getUri()
312                                     .toASCIIString());
313                     if (jsonObject.get("success").toString().equals("true")) {
314                         logger.trace("subscribe() Response: {}", jsonObject.get("result"));
315                     }
316                     if (jsonObject.get("success").toString().equals("false")) {
317                         logger.trace("subscribe() Response: {}", jsonObject.get("error"));
318                     }
319                 }
320
321                 if (jsonObject.get("success").toString().equals("true")) {
322                     RESTSubscribeResponse subscribeResponse = gson.fromJson(jsonObject.get("result").toString(),
323                             RESTSubscribeResponse.class);
324                     logger.debug("The subscription id to pull logs from the Helios IP Vario '{}' is '{}'",
325                             getThing().getUID().toString(), subscribeResponse.id);
326                     return subscribeResponse.id;
327                 } else {
328                     RESTError error = gson.fromJson(jsonObject.get("error").toString(), RESTError.class);
329                     logger.debug(
330                             "An error occurred while communicating with the Helios IP Vario '{}': code '{}', param '{}' : '{}'",
331                             getThing().getUID().toString(), error.code, error.param, error.description);
332                     updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
333                             error.code + ":" + error.param + ":" + error.description);
334                     scheduler.schedule(resetRunnable, RESET_INTERVAL, TimeUnit.SECONDS);
335                     return 0;
336                 }
337             } else {
338                 logger.debug("An error occurred while subscribing to the log entries of the Helios IP Vario '{}'",
339                         getThing().getUID().toString());
340                 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR);
341                 scheduler.schedule(resetRunnable, RESET_INTERVAL, TimeUnit.SECONDS);
342                 return 0;
343             }
344         }
345
346         return 0;
347     }
348
349     private void unsubscribe() {
350         if (getThing().getStatus() == ThingStatus.ONLINE) {
351             logTarget = baseTarget.path(LOG_PATH);
352
353             Response response = null;
354             try {
355                 response = logTarget.resolveTemplate("ip", ipAddress).resolveTemplate("cmd", UNSUBSCRIBE)
356                         .queryParam("id", logSubscriptionID).request(MediaType.APPLICATION_JSON_TYPE).get();
357             } catch (Exception e) {
358                 logger.debug(
359                         "An exception occurred while unsubscribing from the log entries of the Helios IP Vario '{}' : {}",
360                         getThing().getUID().toString(), e.getMessage(), e);
361                 logSubscriptionID = 0;
362                 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, e.getMessage());
363                 scheduler.schedule(resetRunnable, RESET_INTERVAL, TimeUnit.SECONDS);
364                 return;
365             }
366
367             if (response != null) {
368                 JsonObject jsonObject = parser.parse(response.readEntity(String.class)).getAsJsonObject();
369
370                 if (logger.isTraceEnabled()) {
371                     logger.trace("unsubscribe() Request : {}",
372                             logTarget.resolveTemplate("ip", ipAddress).resolveTemplate("cmd", UNSUBSCRIBE)
373                                     .queryParam("id", logSubscriptionID).getUri().toASCIIString());
374                     if (jsonObject.get("success").toString().equals("true")) {
375                         logger.trace("unsubscribe() Response: {}", jsonObject.get("result"));
376                     }
377                     if (jsonObject.get("success").toString().equals("false")) {
378                         logger.trace("unsubscribe() Response: {}", jsonObject.get("error"));
379                     }
380                 }
381
382                 if (jsonObject.get("success").toString().equals("true")) {
383                     logger.debug("Successfully unsubscribed from the log entries of the Helios IP Vario '{}'",
384                             getThing().getUID().toString());
385                 } else {
386                     RESTError error = gson.fromJson(jsonObject.get("error").toString(), RESTError.class);
387                     logger.debug(
388                             "An error occurred while communicating with the Helios IP Vario '{}' : code '{}', param '{}' : '{}'",
389                             getThing().getUID().toString(), error.code, error.param, error.description);
390                     logSubscriptionID = 0;
391                     updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
392                             error.code + ":" + error.param + ":" + error.description);
393                     scheduler.schedule(resetRunnable, RESET_INTERVAL, TimeUnit.SECONDS);
394                     return;
395                 }
396             } else {
397                 logger.debug("An error occurred while unsubscribing from the log entries of the Helios IP Vario '{}'",
398                         getThing().getUID().toString());
399                 logSubscriptionID = 0;
400                 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR);
401                 scheduler.schedule(resetRunnable, RESET_INTERVAL, TimeUnit.SECONDS);
402                 return;
403             }
404         }
405     }
406
407     private List<RESTEvent> pullLog(long logSubscriptionID) {
408         if (getThing().getStatus() == ThingStatus.ONLINE && heliosClient != null) {
409             logTarget = baseTarget.path(LOG_PATH);
410
411             Response response = null;
412             try {
413                 long now = System.currentTimeMillis();
414                 response = logTarget.resolveTemplate("ip", ipAddress).resolveTemplate("cmd", PULL)
415                         .queryParam("id", logSubscriptionID).queryParam("timeout", HELIOS_PULL_DURATION)
416                         .request(MediaType.APPLICATION_JSON_TYPE).get();
417                 logger.trace("Pulled logs in {} millseconds from {}", System.currentTimeMillis() - now,
418                         getThing().getUID());
419             } catch (NullPointerException e) {
420                 logger.debug("An exception occurred while pulling log entries from the Helios IP Vario '{}' : '{}'",
421                         getThing().getUID().toString(), e.getMessage(), e);
422                 this.logSubscriptionID = 0;
423                 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, e.getMessage());
424                 scheduler.schedule(resetRunnable, RESET_INTERVAL, TimeUnit.SECONDS);
425                 return null;
426             }
427
428             if (response != null) {
429                 JsonObject jsonObject = parser.parse(response.readEntity(String.class)).getAsJsonObject();
430
431                 if (logger.isTraceEnabled()) {
432                     logger.trace("pullLog() Request : {}",
433                             logTarget.resolveTemplate("ip", ipAddress).resolveTemplate("cmd", PULL)
434                                     .queryParam("id", logSubscriptionID).queryParam("timeout", HELIOS_PULL_DURATION)
435                                     .getUri().toASCIIString());
436                     if (jsonObject.get("success").toString().equals("true")) {
437                         logger.trace("pullLog() Response: {}", jsonObject.get("result"));
438                     }
439                     if (jsonObject.get("success").toString().equals("false")) {
440                         logger.trace("pullLog() Response: {}", jsonObject.get("error"));
441                     }
442                 }
443
444                 if (jsonObject.get("success").toString().equals("true")) {
445                     logger.trace("Successfully pulled log entries from the Helios IP Vario '{}'",
446                             getThing().getUID().toString());
447                     JsonObject js = (JsonObject) jsonObject.get("result");
448                     RESTEvent[] eventArray = gson.fromJson(js.getAsJsonArray("events"), RESTEvent[].class);
449                     return Arrays.asList(eventArray);
450                 } else {
451                     RESTError error = gson.fromJson(jsonObject.get("error").toString(), RESTError.class);
452                     logger.debug(
453                             "An error occurred while communicating with the Helios IP Vario '{}' : code '{}', param '{}' : '{}'",
454                             getThing().getUID().toString(), error.code, error.param, error.description);
455                     this.logSubscriptionID = 0;
456                     updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
457                             error.code + ":" + error.param + ":" + error.description);
458                     scheduler.schedule(resetRunnable, RESET_INTERVAL, TimeUnit.SECONDS);
459                     return null;
460                 }
461             } else {
462                 logger.debug("An error occurred while polling log entries from the Helios IP Vario '{}'",
463                         getThing().getUID().toString());
464                 this.logSubscriptionID = 0;
465                 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR);
466                 scheduler.schedule(resetRunnable, RESET_INTERVAL, TimeUnit.SECONDS);
467                 return null;
468             }
469         }
470
471         return null;
472     }
473
474     private List<RESTSwitch> getSwitches() {
475         switchTarget = baseTarget.path(SWITCH_PATH);
476
477         Response response = null;
478         try {
479             response = switchTarget.resolveTemplate("ip", ipAddress).resolveTemplate("cmd", CAPABILITIES)
480                     .request(MediaType.APPLICATION_JSON_TYPE).get();
481         } catch (NullPointerException e) {
482             logger.debug(
483                     "An exception occurred while requesting switch capabilities from the Helios IP Vario '{}' : '{}'",
484                     getThing().getUID().toString(), e.getMessage(), e);
485             updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, e.getMessage());
486             scheduler.schedule(resetRunnable, RESET_INTERVAL, TimeUnit.SECONDS);
487             return null;
488         }
489
490         if (response != null) {
491             JsonObject jsonObject = parser.parse(response.readEntity(String.class)).getAsJsonObject();
492
493             if (logger.isTraceEnabled()) {
494                 logger.trace("getSwitches() Request : {}", switchTarget.resolveTemplate("ip", ipAddress)
495                         .resolveTemplate("cmd", CAPABILITIES).getUri().toASCIIString());
496                 if (jsonObject.get("success").toString().equals("true")) {
497                     logger.trace("getSwitches() Response: {}", jsonObject.get("result"));
498                 }
499                 if (jsonObject.get("success").toString().equals("false")) {
500                     logger.trace("getSwitches() Response: {}", jsonObject.get("error"));
501                 }
502             }
503
504             if (jsonObject.get("success").toString().equals("true")) {
505                 logger.debug("Successfully requested switch capabilities from the Helios IP Vario '{}'",
506                         getThing().getUID().toString());
507                 String result = jsonObject.get("result").toString();
508                 result = result.replace("switch", "id");
509                 JsonObject js = parser.parse(result).getAsJsonObject();
510                 RESTSwitch[] switchArray = gson.fromJson(js.getAsJsonArray("ides"), RESTSwitch[].class);
511                 if (switchArray != null) {
512                     return Arrays.asList(switchArray);
513                 }
514             } else {
515                 RESTError error = gson.fromJson(jsonObject.get("error").toString(), RESTError.class);
516                 logger.debug(
517                         "An error occurred while communicating with the Helios IP Vario '{}' : code '{}', param '{}' : '{}'",
518                         getThing().getUID().toString(), error.code, error.param, error.description);
519                 if ("8".equals(error.code)) {
520                     logger.debug(
521                             "The API is not supported by the Helios hardware or current license, or the Authentication method is not set to Basic");
522                 } else {
523                     updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
524                             error.code + ":" + error.param + ":" + error.description);
525                     scheduler.schedule(resetRunnable, RESET_INTERVAL, TimeUnit.SECONDS);
526                 }
527                 return null;
528             }
529         } else {
530             logger.debug("An error occurred while requesting switch capabilities from the Helios IP Vario '{}'",
531                     getThing().getUID().toString());
532             updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR);
533             scheduler.schedule(resetRunnable, RESET_INTERVAL, TimeUnit.SECONDS);
534         }
535
536         return null;
537     }
538
539     private void triggerSwitch(String id) {
540         if (getThing().getStatus() == ThingStatus.ONLINE) {
541             switchTarget = baseTarget.path(SWITCH_PATH);
542
543             Response response = null;
544             try {
545                 response = switchTarget.resolveTemplate("ip", ipAddress).resolveTemplate("cmd", CONTROL)
546                         .queryParam("switch", id).queryParam("action", "trigger")
547                         .request(MediaType.APPLICATION_JSON_TYPE).get();
548             } catch (NullPointerException e) {
549                 logger.debug("An exception occurred while triggering a switch  on the Helios IP Vario '{}' : '{}'",
550                         getThing().getUID().toString(), e.getMessage(), e);
551                 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, e.getMessage());
552                 scheduler.schedule(resetRunnable, RESET_INTERVAL, TimeUnit.SECONDS);
553                 return;
554             }
555
556             if (response != null) {
557                 JsonObject jsonObject = parser.parse(response.readEntity(String.class)).getAsJsonObject();
558
559                 if (logger.isTraceEnabled()) {
560                     logger.trace("triggerSwitch() Request : {}",
561                             switchTarget.resolveTemplate("ip", ipAddress).resolveTemplate("cmd", CONTROL)
562                                     .queryParam("switch", id).queryParam("action", "trigger").getUri().toASCIIString());
563                     if (jsonObject.get("success").toString().equals("true")) {
564                         logger.trace("triggerSwitch() Response: {}", jsonObject.get("result"));
565                     }
566                     if (jsonObject.get("success").toString().equals("false")) {
567                         logger.trace("triggerSwitch() Response: {}", jsonObject.get("error"));
568                     }
569                 }
570
571                 if (jsonObject.get("success").toString().equals("true")) {
572                     logger.debug("Successfully triggered a switch on the Helios IP Vario '{}'",
573                             getThing().getUID().toString());
574                 } else {
575                     RESTError error = gson.fromJson(jsonObject.get("error").toString(), RESTError.class);
576                     logger.error(
577                             "An error occurred while communicating with the Helios IP Vario '{}' : code '{}', param '{}' : '{}'",
578                             getThing().getUID().toString(), error.code, error.param, error.description);
579                     updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
580                             error.code + ":" + error.param + ":" + error.description);
581                     scheduler.schedule(resetRunnable, RESET_INTERVAL, TimeUnit.SECONDS);
582                     return;
583                 }
584             } else {
585                 logger.warn("An error occurred while triggering a switch on the Helios IP Vario '{}'",
586                         getThing().getUID().toString());
587                 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR);
588                 scheduler.schedule(resetRunnable, RESET_INTERVAL, TimeUnit.SECONDS);
589                 return;
590             }
591         }
592     }
593
594     private void enableSwitch(String id, boolean flag) {
595         if (getThing().getStatus() == ThingStatus.ONLINE) {
596             switchTarget = baseTarget.path(SWITCH_PATH);
597
598             Response response = null;
599             try {
600                 response = switchTarget.resolveTemplate("ip", ipAddress).resolveTemplate("cmd", CONTROL)
601                         .queryParam("switch", id).queryParam("action", flag ? "on" : "off")
602                         .request(MediaType.APPLICATION_JSON_TYPE).get();
603             } catch (NullPointerException e) {
604                 logger.error("An exception occurred while dis/enabling a switch  on the Helios IP Vario '{}' : '{}'",
605                         getThing().getUID().toString(), e.getMessage(), e);
606                 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, e.getMessage());
607                 scheduler.schedule(resetRunnable, RESET_INTERVAL, TimeUnit.SECONDS);
608                 return;
609             }
610
611             if (response != null) {
612                 JsonObject jsonObject = parser.parse(response.readEntity(String.class)).getAsJsonObject();
613
614                 if (logger.isTraceEnabled()) {
615                     logger.trace("enableSwitch() Request : {}",
616                             switchTarget.resolveTemplate("ip", ipAddress).resolveTemplate("cmd", CONTROL)
617                                     .queryParam("switch", id).queryParam("action", flag ? "on" : "off").getUri()
618                                     .toASCIIString());
619                     if (jsonObject.get("success").toString().equals("true")) {
620                         logger.trace("enableSwitch() Response: {}", jsonObject.get("result"));
621                     }
622                     if (jsonObject.get("success").toString().equals("false")) {
623                         logger.trace("enableSwitch() Response: {}", jsonObject.get("error"));
624                     }
625                 }
626
627                 if (jsonObject.get("success").toString().equals("true")) {
628                     logger.debug("Successfully dis/enabled a  switch on the Helios IP Vario '{}'",
629                             getThing().getUID().toString());
630                 } else {
631                     RESTError error = gson.fromJson(jsonObject.get("error").toString(), RESTError.class);
632                     logger.error(
633                             "An error occurred while communicating with the Helios IP Vario '{}': code '{}', param '{}' : '{}'",
634                             getThing().getUID().toString(), error.code, error.param, error.description);
635                     updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
636                             error.code + ":" + error.param + ":" + error.description);
637                     scheduler.schedule(resetRunnable, RESET_INTERVAL, TimeUnit.SECONDS);
638                     return;
639                 }
640             } else {
641                 logger.warn("An error occurred while dis/enabling a switch on the Helios IP Vario '{}'",
642                         getThing().getUID().toString());
643                 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR);
644                 scheduler.schedule(resetRunnable, RESET_INTERVAL, TimeUnit.SECONDS);
645                 return;
646             }
647         }
648     }
649
650     private List<RESTPort> getPorts() {
651         portTarget = baseTarget.path(PORT_PATH);
652
653         Response response = null;
654         try {
655             response = portTarget.resolveTemplate("ip", ipAddress).resolveTemplate("cmd", CAPABILITIES)
656                     .request(MediaType.APPLICATION_JSON_TYPE).get();
657         } catch (NullPointerException e) {
658             logger.error(
659                     "An exception occurred while requesting port capabilities from the Helios IP Vario '{}' : '{}'",
660                     getThing().getUID().toString(), e.getMessage(), e);
661             updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, e.getMessage());
662             scheduler.schedule(resetRunnable, RESET_INTERVAL, TimeUnit.SECONDS);
663             return null;
664         }
665
666         if (response != null) {
667             JsonObject jsonObject = parser.parse(response.readEntity(String.class)).getAsJsonObject();
668
669             if (logger.isTraceEnabled()) {
670                 logger.trace("getPorts() Request : {}", portTarget.resolveTemplate("ip", ipAddress)
671                         .resolveTemplate("cmd", CAPABILITIES).getUri().toASCIIString());
672                 if (jsonObject.get("success").toString().equals("true")) {
673                     logger.trace("getPorts() Response: {}", jsonObject.get("result"));
674                 }
675                 if (jsonObject.get("success").toString().equals("false")) {
676                     logger.trace("getPorts() Response: {}", jsonObject.get("error"));
677                 }
678             }
679
680             if (jsonObject.get("success").toString().equals("true")) {
681                 logger.debug("Successfully requested port capabilities from the Helios IP Vario '{}'",
682                         getThing().getUID().toString());
683                 JsonObject js = (JsonObject) jsonObject.get("result");
684                 RESTPort[] portArray = gson.fromJson(js.getAsJsonArray("ports"), RESTPort[].class);
685                 if (portArray != null) {
686                     return Arrays.asList(portArray);
687                 }
688             } else {
689                 RESTError error = gson.fromJson(jsonObject.get("error").toString(), RESTError.class);
690                 logger.error(
691                         "An error occurred while communicating with the Helios IP Vario '{}': code '{}', param '{}' : '{}'",
692                         getThing().getUID().toString(), error.code, error.param, error.description);
693                 if ("8".equals(error.code)) {
694                     logger.debug(
695                             "The API is not supported by the Helios hardware or current license, or the Authentication method is not set to Basic");
696                 } else {
697                     updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
698                             error.code + ":" + error.param + ":" + error.description);
699                     scheduler.schedule(resetRunnable, RESET_INTERVAL, TimeUnit.SECONDS);
700                 }
701                 return null;
702             }
703         } else {
704             logger.warn("An error occurred while requesting port capabilities from the Helios IP Vario '{}'",
705                     getThing().getUID().toString());
706             updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR);
707             scheduler.schedule(resetRunnable, RESET_INTERVAL, TimeUnit.SECONDS);
708         }
709
710         return null;
711     }
712
713     protected Runnable resetRunnable = () -> {
714         logger.debug("Resetting the Helios IP Vario handler for '{}'", getThing().getUID());
715         dispose();
716         initialize();
717     };
718
719     protected Runnable configureRunnable = () -> {
720         logger.debug("Fetching the configuration of the Helios IP Vario '{}' ", getThing().getUID().toString());
721
722         Response response = null;
723         try {
724             response = systemTarget.resolveTemplate("ip", ipAddress).resolveTemplate("cmd", INFO)
725                     .request(MediaType.APPLICATION_JSON_TYPE).get();
726         } catch (NullPointerException e) {
727             logger.error("An exception occurred while fetching system info of the Helios IP Vario '{}' : '{}'",
728                     getThing().getUID().toString(), e.getMessage(), e);
729             updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR);
730             scheduler.schedule(resetRunnable, RESET_INTERVAL, TimeUnit.SECONDS);
731             return;
732         }
733
734         if (response != null) {
735             JsonObject jsonObject = parser.parse(response.readEntity(String.class)).getAsJsonObject();
736
737             if (logger.isTraceEnabled()) {
738                 logger.trace("configureRunnable Request : {}", systemTarget.resolveTemplate("ip", ipAddress)
739                         .resolveTemplate("cmd", INFO).getUri().toASCIIString());
740                 if (jsonObject.get("success").toString().equals("true")) {
741                     logger.trace("configureRunnable Response: {}", jsonObject.get("result"));
742                 }
743                 if (jsonObject.get("success").toString().equals("false")) {
744                     logger.trace("configureRunnable Response: {}", jsonObject.get("error"));
745                 }
746             }
747
748             RESTSystemInfo systemInfo = gson.fromJson(jsonObject.get("result").toString(), RESTSystemInfo.class);
749
750             Map<String, String> properties = editProperties();
751             properties.put(VARIANT, systemInfo.variant);
752             properties.put(SERIAL_NUMBER, systemInfo.serialNumber);
753             properties.put(HW_VERSION, systemInfo.hwVersion);
754             properties.put(SW_VERSION, systemInfo.swVersion);
755             properties.put(BUILD_TYPE, systemInfo.buildType);
756             properties.put(DEVICE_NAME, systemInfo.deviceName);
757             updateProperties(properties);
758         }
759
760         List<RESTSwitch> switches = getSwitches();
761
762         if (switches != null) {
763             for (RESTSwitch aSwitch : switches) {
764                 if (aSwitch.enabled.equals("true")) {
765                     logger.debug("Adding a channel to the Helios IP Vario '{}' for the switch with id '{}'",
766                             getThing().getUID().toString(), aSwitch.id);
767                     ThingBuilder thingBuilder = editThing();
768                     ChannelTypeUID enablerUID = new ChannelTypeUID(BINDING_ID, SWITCH_ENABLER);
769                     ChannelTypeUID triggerUID = new ChannelTypeUID(BINDING_ID, SWITCH_TRIGGER);
770
771                     Channel channel = ChannelBuilder
772                             .create(new ChannelUID(getThing().getUID(), "switch" + aSwitch.id + "active"), "Switch")
773                             .withType(enablerUID).build();
774                     thingBuilder.withChannel(channel);
775                     channel = ChannelBuilder
776                             .create(new ChannelUID(getThing().getUID(), "switch" + aSwitch.id), "Switch")
777                             .withType(triggerUID).build();
778                     thingBuilder.withChannel(channel);
779                     updateThing(thingBuilder.build());
780                 }
781             }
782         }
783
784         List<RESTPort> ports = getPorts();
785
786         if (ports != null) {
787             for (RESTPort aPort : ports) {
788                 logger.debug("Adding a channel to the Helios IP Vario '{}' for the IO port with id '{}'",
789                         getThing().getUID().toString(), aPort.port);
790                 ThingBuilder thingBuilder = editThing();
791                 ChannelTypeUID triggerUID = new ChannelTypeUID(BINDING_ID, IO_TRIGGER);
792
793                 Map<String, String> channelProperties = new HashMap<>();
794                 channelProperties.put("type", aPort.type);
795
796                 Channel channel = ChannelBuilder
797                         .create(new ChannelUID(getThing().getUID(), "io" + aPort.port), "Switch").withType(triggerUID)
798                         .withProperties(channelProperties).build();
799                 thingBuilder.withChannel(channel);
800                 updateThing(thingBuilder.build());
801             }
802         }
803     };
804
805     protected Runnable logRunnable = () -> {
806         if (getThing().getStatus() == ThingStatus.ONLINE) {
807             if (logSubscriptionID == 0) {
808                 logSubscriptionID = subscribe();
809             }
810
811             SimpleDateFormat dateFormatter = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss");
812
813             while (logSubscriptionID != 0) {
814                 try {
815                     List<RESTEvent> events = pullLog(logSubscriptionID);
816
817                     if (events != null) {
818                         for (RESTEvent event : events) {
819                             Date date = new Date(Long.valueOf(event.utcTime));
820                             DateTimeType stampType = new DateTimeType(dateFormatter.format(date));
821
822                             logger.debug("Received the event for Helios IP Vario '{}' with ID '{}' of type '{}' on {}",
823                                     getThing().getUID().toString(), event.id, event.event, dateFormatter.format(date));
824
825                             switch (event.event) {
826                                 case DEVICESTATE: {
827                                     StringType valueType = new StringType(event.params.get("state").getAsString());
828                                     updateState(DEVICE_STATE, valueType);
829                                     updateState(DEVICE_STATE_STAMP, stampType);
830                                     break;
831                                 }
832                                 case AUDIOLOOPTEST: {
833                                     if (event.params.get("result").getAsString().equals("passed")) {
834                                         updateState(AUDIO_LOOP_TEST, OnOffType.ON);
835                                     } else if (event.params.get("result").getAsString().equals("failed")) {
836                                         updateState(AUDIO_LOOP_TEST, OnOffType.OFF);
837                                     } else {
838                                         updateState(AUDIO_LOOP_TEST, UnDefType.UNDEF);
839                                     }
840
841                                     updateState(AUDIO_LOOP_TEST_STAMP, stampType);
842                                     break;
843                                 }
844                                 case MOTIONDETECTED: {
845                                     if (event.params.get("state").getAsString().equals("in")) {
846                                         updateState(MOTION, OnOffType.ON);
847                                     } else if (event.params.get("state").getAsString().equals("out")) {
848                                         updateState(MOTION, OnOffType.OFF);
849                                     } else {
850                                         updateState(MOTION, UnDefType.UNDEF);
851                                     }
852
853                                     updateState(MOTION_STAMP, stampType);
854                                     break;
855                                 }
856                                 case NOISEDETECTED: {
857                                     if (event.params.get("state").getAsString().equals("in")) {
858                                         updateState(NOISE, OnOffType.ON);
859                                     } else if (event.params.get("state").getAsString().equals("out")) {
860                                         updateState(NOISE, OnOffType.OFF);
861                                     } else {
862                                         updateState(NOISE, UnDefType.UNDEF);
863                                     }
864
865                                     updateState(NOISE_STAMP, stampType);
866                                     break;
867                                 }
868                                 case KEYPRESSED: {
869                                     triggerChannel(KEY_PRESSED, event.params.get("key").getAsString());
870
871                                     updateState(KEY_PRESSED_STAMP, stampType);
872                                     break;
873                                 }
874                                 case KEYRELEASED: {
875                                     triggerChannel(KEY_RELEASED, event.params.get("key").getAsString());
876
877                                     updateState(KEY_RELEASED_STAMP, stampType);
878                                     break;
879                                 }
880                                 case CODEENTERED: {
881                                     triggerChannel(CODE, event.params.get("code").getAsString());
882
883                                     if (event.params.get("valid").getAsString().equals("true")) {
884                                         updateState(CODE_VALID, OnOffType.ON);
885                                     } else if (event.params.get("valid").getAsString().equals("false")) {
886                                         updateState(CODE_VALID, OnOffType.OFF);
887                                     } else {
888                                         updateState(CODE_VALID, UnDefType.UNDEF);
889                                     }
890
891                                     updateState(CODE_STAMP, stampType);
892                                     break;
893                                 }
894                                 case CARDENTERED: {
895                                     triggerChannel(CARD, event.params.get("uid").getAsString());
896
897                                     if (event.params.get("valid").getAsString().equals("true")) {
898                                         updateState(CARD_VALID, OnOffType.ON);
899                                     } else if (event.params.get("valid").getAsString().equals("false")) {
900                                         updateState(CARD_VALID, OnOffType.OFF);
901                                     } else {
902                                         updateState(CARD_VALID, UnDefType.UNDEF);
903                                     }
904
905                                     updateState(CARD_STAMP, stampType);
906                                     break;
907                                 }
908                                 case INPUTCHANGED: {
909                                     ChannelUID inputChannel = new ChannelUID(getThing().getUID(),
910                                             "io" + event.params.get("port").getAsString());
911
912                                     if (event.params.get("state").getAsString().equals("true")) {
913                                         updateState(inputChannel, OnOffType.ON);
914                                     } else if (event.params.get("state").getAsString().equals("false")) {
915                                         updateState(inputChannel, OnOffType.OFF);
916                                     } else {
917                                         updateState(inputChannel, UnDefType.UNDEF);
918                                     }
919                                     break;
920                                 }
921                                 case OUTPUTCHANGED: {
922                                     ChannelUID inputChannel = new ChannelUID(getThing().getUID(),
923                                             "io" + event.params.get("port").getAsString());
924
925                                     if (event.params.get("state").getAsString().equals("true")) {
926                                         updateState(inputChannel, OnOffType.ON);
927                                     } else if (event.params.get("state").getAsString().equals("false")) {
928                                         updateState(inputChannel, OnOffType.OFF);
929                                     } else {
930                                         updateState(inputChannel, UnDefType.UNDEF);
931                                     }
932                                     break;
933                                 }
934                                 case CALLSTATECHANGED: {
935                                     StringType valueType = new StringType(event.params.get("state").getAsString());
936                                     updateState(CALL_STATE, valueType);
937
938                                     valueType = new StringType(event.params.get("direction").getAsString());
939                                     updateState(CALL_DIRECTION, valueType);
940
941                                     updateState(CALL_STATE_STAMP, stampType);
942                                     break;
943                                 }
944                                 case REGISTRATIONSTATECHANGED: {
945                                     break;
946                                 }
947                                 case SWITCHSTATECHANGED: {
948                                     if (event.params.get("state").getAsString().equals("true")) {
949                                         updateState(SWITCH_STATE, OnOffType.ON);
950                                     } else if (event.params.get("state").getAsString().equals("false")) {
951                                         updateState(SWITCH_STATE, OnOffType.OFF);
952                                     } else {
953                                         updateState(SWITCH_STATE, UnDefType.UNDEF);
954                                     }
955
956                                     if (event.params.get("originator") != null) {
957                                         StringType originatorType = new StringType(
958                                                 event.params.get("originator").getAsString());
959                                         updateState(SWITCH_STATE_ORIGINATOR, originatorType);
960                                     }
961
962                                     DecimalType switchType = new DecimalType(event.params.get("switch").getAsString());
963                                     updateState(SWITCH_STATE_SWITCH, switchType);
964
965                                     updateState(SWITCH_STATE_STAMP, stampType);
966                                     break;
967                                 }
968                                 default: {
969                                     logger.debug("Unrecognised event type : '{}'", event.event);
970                                     Set<Map.Entry<String, JsonElement>> entrySet = event.params.entrySet();
971                                     for (Map.Entry<String, JsonElement> entry : entrySet) {
972                                         logger.debug("Key '{}', Value '{}'", entry.getKey(),
973                                                 event.params.get(entry.getKey()).getAsString().replace("\"", ""));
974                                     }
975                                 }
976                             }
977                         }
978                     } else {
979                         if (logger.isTraceEnabled()) {
980                             logger.trace("No events were retrieved");
981                         }
982                     }
983                 } catch (Exception e) {
984                     logger.error("An exception occurred while processing an event : '{}'", e.getMessage(), e);
985                 }
986             }
987         }
988     };
989
990     protected class Authenticator implements ClientRequestFilter {
991
992         private final String user;
993         private final String password;
994
995         public Authenticator(String user, String password) {
996             this.user = user;
997             this.password = password;
998         }
999
1000         @Override
1001         public void filter(ClientRequestContext requestContext) throws IOException {
1002             MultivaluedMap<String, Object> headers = requestContext.getHeaders();
1003             final String basicAuthentication = getBasicAuthentication();
1004             headers.add("Authorization", basicAuthentication);
1005         }
1006
1007         private String getBasicAuthentication() {
1008             String token = this.user + ":" + this.password;
1009             return "Basic " + Base64.getEncoder().encodeToString(token.getBytes(StandardCharsets.UTF_8));
1010         }
1011     }
1012
1013     public class SecureRestClientTrustManager implements X509TrustManager {
1014
1015         @Override
1016         public void checkClientTrusted(X509Certificate[] arg0, String arg1) throws CertificateException {
1017         }
1018
1019         @Override
1020         public void checkServerTrusted(X509Certificate[] arg0, String arg1) throws CertificateException {
1021         }
1022
1023         @Override
1024         public X509Certificate[] getAcceptedIssuers() {
1025             return new X509Certificate[0];
1026         }
1027
1028         public boolean isClientTrusted(X509Certificate[] arg0) {
1029             return true;
1030         }
1031
1032         public boolean isServerTrusted(X509Certificate[] arg0) {
1033             return true;
1034         }
1035     }
1036 }