2 * Copyright (c) 2010-2021 Contributors to the openHAB project
4 * See the NOTICE file(s) distributed with this work for additional
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
11 * SPDX-License-Identifier: EPL-2.0
13 package org.openhab.binding.helios.internal.handler;
15 import static org.openhab.binding.helios.internal.HeliosBindingConstants.*;
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;
31 import java.util.concurrent.ScheduledFuture;
32 import java.util.concurrent.TimeUnit;
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;
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;
71 import com.google.gson.Gson;
72 import com.google.gson.JsonElement;
73 import com.google.gson.JsonObject;
74 import com.google.gson.JsonParser;
77 * The {@link HeliosHandler221} is responsible for handling commands, which are
78 * sent to one of the channels.
80 * @author Karel Goderis - Initial contribution
83 public class HeliosHandler221 extends BaseThingHandler {
85 private final Logger logger = LoggerFactory.getLogger(HeliosHandler221.class);
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";
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}";
100 public static final String INFO = "info";
101 public static final String STATUS = "status";
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";
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";
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;
133 private JsonParser parser = new JsonParser();
134 private Gson gson = new Gson();
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;
141 private long logSubscriptionID = 0;
143 public HeliosHandler221(Thing thing) {
148 public void initialize() {
149 logger.debug("Initializing the Helios IP Vario handler for '{}'.", getThing().getUID().toString());
151 ipAddress = (String) getConfig().get(IP_ADDRESS);
152 String username = (String) getConfig().get(USERNAME);
153 String password = (String) getConfig().get(PASSWORD);
155 if (ipAddress != null && !ipAddress.isEmpty() && username != null && !username.isEmpty() && password != null
156 && !password.isEmpty()) {
157 SecureRestClientTrustManager secureRestClientTrustManager = new SecureRestClientTrustManager();
158 SSLContext sslContext = null;
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);
166 if (sslContext != null) {
167 sslContext.init(null, new javax.net.ssl.TrustManager[] { secureRestClientTrustManager }, null);
169 } catch (KeyManagementException e1) {
170 logger.error("An exception occurred while initialising the SSL context : '{}'", e1.getMessage(), e1);
173 heliosClient = ClientBuilder.newBuilder().sslContext(sslContext).hostnameVerifier(new HostnameVerifier() {
175 public boolean verify(String hostname, javax.net.ssl.SSLSession sslSession) {
179 heliosClient.register(new Authenticator(username, password));
181 baseTarget = heliosClient.target(BASE_URI);
182 systemTarget = baseTarget.path(SYSTEM_PATH);
183 logTarget = baseTarget.path(LOG_PATH);
184 switchTarget = baseTarget.path(SWITCH_PATH);
186 Response response = null;
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);
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);
206 JsonObject jsonObject = parser.parse(response.readEntity(String.class)).getAsJsonObject();
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"));
214 if (jsonObject.get("success").toString().equals("false")) {
215 logger.trace("initialize() Response: {}", jsonObject.get("error"));
219 if (jsonObject.get("success").toString().equals("false")) {
220 RESTError error = gson.fromJson(jsonObject.get("error").toString(), RESTError.class);
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);
230 if (jsonObject.get("success").toString().equals("true")) {
231 if (logJob == null || logJob.isCancelled()) {
232 logJob = scheduler.scheduleWithFixedDelay(logRunnable, 0, 1, TimeUnit.SECONDS);
235 updateStatus(ThingStatus.ONLINE);
237 scheduler.schedule(configureRunnable, 0, TimeUnit.SECONDS);
243 public void dispose() {
244 logger.debug("Disposing the Helios IP Vario handler for '{}'.", getThing().getUID().toString());
246 if (logSubscriptionID != 0) {
250 if (logJob != null && !logJob.isCancelled()) {
255 if (heliosClient != null) {
256 heliosClient.close();
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());
268 if (theChannel != null) {
269 ChannelTypeUID channelType = theChannel.getChannelTypeUID();
270 if (channelType.equals(triggerUID)) {
271 String switchID = channelUID.getId().substring(6);
272 triggerSwitch(switchID);
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);
287 private long subscribe() {
288 if (getThing().getStatus() == ThingStatus.ONLINE) {
289 logTarget = baseTarget.path(LOG_PATH);
291 Response response = null;
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) {
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);
305 if (response != null) {
306 JsonObject jsonObject = parser.parse(response.readEntity(String.class)).getAsJsonObject();
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()
313 if (jsonObject.get("success").toString().equals("true")) {
314 logger.trace("subscribe() Response: {}", jsonObject.get("result"));
316 if (jsonObject.get("success").toString().equals("false")) {
317 logger.trace("subscribe() Response: {}", jsonObject.get("error"));
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;
328 RESTError error = gson.fromJson(jsonObject.get("error").toString(), RESTError.class);
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);
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);
349 private void unsubscribe() {
350 if (getThing().getStatus() == ThingStatus.ONLINE) {
351 logTarget = baseTarget.path(LOG_PATH);
353 Response response = null;
355 response = logTarget.resolveTemplate("ip", ipAddress).resolveTemplate("cmd", UNSUBSCRIBE)
356 .queryParam("id", logSubscriptionID).request(MediaType.APPLICATION_JSON_TYPE).get();
357 } catch (Exception e) {
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);
367 if (response != null) {
368 JsonObject jsonObject = parser.parse(response.readEntity(String.class)).getAsJsonObject();
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"));
377 if (jsonObject.get("success").toString().equals("false")) {
378 logger.trace("unsubscribe() Response: {}", jsonObject.get("error"));
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());
386 RESTError error = gson.fromJson(jsonObject.get("error").toString(), RESTError.class);
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);
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);
407 private List<RESTEvent> pullLog(long logSubscriptionID) {
408 if (getThing().getStatus() == ThingStatus.ONLINE && heliosClient != null) {
409 logTarget = baseTarget.path(LOG_PATH);
411 Response response = null;
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);
428 if (response != null) {
429 JsonObject jsonObject = parser.parse(response.readEntity(String.class)).getAsJsonObject();
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"));
439 if (jsonObject.get("success").toString().equals("false")) {
440 logger.trace("pullLog() Response: {}", jsonObject.get("error"));
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);
451 RESTError error = gson.fromJson(jsonObject.get("error").toString(), RESTError.class);
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);
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);
474 private List<RESTSwitch> getSwitches() {
475 switchTarget = baseTarget.path(SWITCH_PATH);
477 Response response = null;
479 response = switchTarget.resolveTemplate("ip", ipAddress).resolveTemplate("cmd", CAPABILITIES)
480 .request(MediaType.APPLICATION_JSON_TYPE).get();
481 } catch (NullPointerException e) {
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);
490 if (response != null) {
491 JsonObject jsonObject = parser.parse(response.readEntity(String.class)).getAsJsonObject();
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"));
499 if (jsonObject.get("success").toString().equals("false")) {
500 logger.trace("getSwitches() Response: {}", jsonObject.get("error"));
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);
515 RESTError error = gson.fromJson(jsonObject.get("error").toString(), RESTError.class);
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)) {
521 "The API is not supported by the Helios hardware or current license, or the Authentication method is not set to Basic");
523 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
524 error.code + ":" + error.param + ":" + error.description);
525 scheduler.schedule(resetRunnable, RESET_INTERVAL, TimeUnit.SECONDS);
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);
539 private void triggerSwitch(String id) {
540 if (getThing().getStatus() == ThingStatus.ONLINE) {
541 switchTarget = baseTarget.path(SWITCH_PATH);
543 Response response = null;
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);
556 if (response != null) {
557 JsonObject jsonObject = parser.parse(response.readEntity(String.class)).getAsJsonObject();
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"));
566 if (jsonObject.get("success").toString().equals("false")) {
567 logger.trace("triggerSwitch() Response: {}", jsonObject.get("error"));
571 if (jsonObject.get("success").toString().equals("true")) {
572 logger.debug("Successfully triggered a switch on the Helios IP Vario '{}'",
573 getThing().getUID().toString());
575 RESTError error = gson.fromJson(jsonObject.get("error").toString(), RESTError.class);
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);
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);
594 private void enableSwitch(String id, boolean flag) {
595 if (getThing().getStatus() == ThingStatus.ONLINE) {
596 switchTarget = baseTarget.path(SWITCH_PATH);
598 Response response = null;
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);
611 if (response != null) {
612 JsonObject jsonObject = parser.parse(response.readEntity(String.class)).getAsJsonObject();
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()
619 if (jsonObject.get("success").toString().equals("true")) {
620 logger.trace("enableSwitch() Response: {}", jsonObject.get("result"));
622 if (jsonObject.get("success").toString().equals("false")) {
623 logger.trace("enableSwitch() Response: {}", jsonObject.get("error"));
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());
631 RESTError error = gson.fromJson(jsonObject.get("error").toString(), RESTError.class);
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);
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);
650 private List<RESTPort> getPorts() {
651 portTarget = baseTarget.path(PORT_PATH);
653 Response response = null;
655 response = portTarget.resolveTemplate("ip", ipAddress).resolveTemplate("cmd", CAPABILITIES)
656 .request(MediaType.APPLICATION_JSON_TYPE).get();
657 } catch (NullPointerException e) {
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);
666 if (response != null) {
667 JsonObject jsonObject = parser.parse(response.readEntity(String.class)).getAsJsonObject();
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"));
675 if (jsonObject.get("success").toString().equals("false")) {
676 logger.trace("getPorts() Response: {}", jsonObject.get("error"));
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);
689 RESTError error = gson.fromJson(jsonObject.get("error").toString(), RESTError.class);
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)) {
695 "The API is not supported by the Helios hardware or current license, or the Authentication method is not set to Basic");
697 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
698 error.code + ":" + error.param + ":" + error.description);
699 scheduler.schedule(resetRunnable, RESET_INTERVAL, TimeUnit.SECONDS);
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);
713 protected Runnable resetRunnable = () -> {
714 logger.debug("Resetting the Helios IP Vario handler for '{}'", getThing().getUID());
719 protected Runnable configureRunnable = () -> {
720 logger.debug("Fetching the configuration of the Helios IP Vario '{}' ", getThing().getUID().toString());
722 Response response = null;
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);
734 if (response != null) {
735 JsonObject jsonObject = parser.parse(response.readEntity(String.class)).getAsJsonObject();
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"));
743 if (jsonObject.get("success").toString().equals("false")) {
744 logger.trace("configureRunnable Response: {}", jsonObject.get("error"));
748 RESTSystemInfo systemInfo = gson.fromJson(jsonObject.get("result").toString(), RESTSystemInfo.class);
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);
760 List<RESTSwitch> switches = getSwitches();
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);
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());
784 List<RESTPort> ports = getPorts();
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);
793 Map<String, String> channelProperties = new HashMap<>();
794 channelProperties.put("type", aPort.type);
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());
805 protected Runnable logRunnable = () -> {
806 if (getThing().getStatus() == ThingStatus.ONLINE) {
807 if (logSubscriptionID == 0) {
808 logSubscriptionID = subscribe();
811 SimpleDateFormat dateFormatter = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss");
813 while (logSubscriptionID != 0) {
815 List<RESTEvent> events = pullLog(logSubscriptionID);
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));
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));
825 switch (event.event) {
827 StringType valueType = new StringType(event.params.get("state").getAsString());
828 updateState(DEVICE_STATE, valueType);
829 updateState(DEVICE_STATE_STAMP, stampType);
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);
838 updateState(AUDIO_LOOP_TEST, UnDefType.UNDEF);
841 updateState(AUDIO_LOOP_TEST_STAMP, stampType);
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);
850 updateState(MOTION, UnDefType.UNDEF);
853 updateState(MOTION_STAMP, stampType);
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);
862 updateState(NOISE, UnDefType.UNDEF);
865 updateState(NOISE_STAMP, stampType);
869 triggerChannel(KEY_PRESSED, event.params.get("key").getAsString());
871 updateState(KEY_PRESSED_STAMP, stampType);
875 triggerChannel(KEY_RELEASED, event.params.get("key").getAsString());
877 updateState(KEY_RELEASED_STAMP, stampType);
881 triggerChannel(CODE, event.params.get("code").getAsString());
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);
888 updateState(CODE_VALID, UnDefType.UNDEF);
891 updateState(CODE_STAMP, stampType);
895 triggerChannel(CARD, event.params.get("uid").getAsString());
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);
902 updateState(CARD_VALID, UnDefType.UNDEF);
905 updateState(CARD_STAMP, stampType);
909 ChannelUID inputChannel = new ChannelUID(getThing().getUID(),
910 "io" + event.params.get("port").getAsString());
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);
917 updateState(inputChannel, UnDefType.UNDEF);
921 case OUTPUTCHANGED: {
922 ChannelUID inputChannel = new ChannelUID(getThing().getUID(),
923 "io" + event.params.get("port").getAsString());
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);
930 updateState(inputChannel, UnDefType.UNDEF);
934 case CALLSTATECHANGED: {
935 StringType valueType = new StringType(event.params.get("state").getAsString());
936 updateState(CALL_STATE, valueType);
938 valueType = new StringType(event.params.get("direction").getAsString());
939 updateState(CALL_DIRECTION, valueType);
941 updateState(CALL_STATE_STAMP, stampType);
944 case REGISTRATIONSTATECHANGED: {
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);
953 updateState(SWITCH_STATE, UnDefType.UNDEF);
956 if (event.params.get("originator") != null) {
957 StringType originatorType = new StringType(
958 event.params.get("originator").getAsString());
959 updateState(SWITCH_STATE_ORIGINATOR, originatorType);
962 DecimalType switchType = new DecimalType(event.params.get("switch").getAsString());
963 updateState(SWITCH_STATE_SWITCH, switchType);
965 updateState(SWITCH_STATE_STAMP, stampType);
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("\"", ""));
979 if (logger.isTraceEnabled()) {
980 logger.trace("No events were retrieved");
983 } catch (Exception e) {
984 logger.error("An exception occurred while processing an event : '{}'", e.getMessage(), e);
990 protected class Authenticator implements ClientRequestFilter {
992 private final String user;
993 private final String password;
995 public Authenticator(String user, String password) {
997 this.password = password;
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);
1007 private String getBasicAuthentication() {
1008 String token = this.user + ":" + this.password;
1009 return "Basic " + Base64.getEncoder().encodeToString(token.getBytes(StandardCharsets.UTF_8));
1013 public class SecureRestClientTrustManager implements X509TrustManager {
1016 public void checkClientTrusted(X509Certificate[] arg0, String arg1) throws CertificateException {
1020 public void checkServerTrusted(X509Certificate[] arg0, String arg1) throws CertificateException {
1024 public X509Certificate[] getAcceptedIssuers() {
1025 return new X509Certificate[0];
1028 public boolean isClientTrusted(X509Certificate[] arg0) {
1032 public boolean isServerTrusted(X509Certificate[] arg0) {