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