2 * Copyright (c) 2010-2023 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 Gson gson = new Gson();
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;
140 private long logSubscriptionID = 0;
142 public HeliosHandler221(Thing thing) {
147 public void initialize() {
148 logger.debug("Initializing the Helios IP Vario handler for '{}'.", getThing().getUID().toString());
150 ipAddress = (String) getConfig().get(IP_ADDRESS);
151 String username = (String) getConfig().get(USERNAME);
152 String password = (String) getConfig().get(PASSWORD);
154 if (ipAddress != null && !ipAddress.isEmpty() && username != null && !username.isEmpty() && password != null
155 && !password.isEmpty()) {
156 SecureRestClientTrustManager secureRestClientTrustManager = new SecureRestClientTrustManager();
157 SSLContext sslContext = null;
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);
165 if (sslContext != null) {
166 sslContext.init(null, new javax.net.ssl.TrustManager[] { secureRestClientTrustManager }, null);
168 } catch (KeyManagementException e1) {
169 logger.error("An exception occurred while initialising the SSL context : '{}'", e1.getMessage(), e1);
172 heliosClient = ClientBuilder.newBuilder().sslContext(sslContext).hostnameVerifier(new HostnameVerifier() {
174 public boolean verify(String hostname, javax.net.ssl.SSLSession sslSession) {
178 heliosClient.register(new Authenticator(username, password));
180 baseTarget = heliosClient.target(BASE_URI);
181 systemTarget = baseTarget.path(SYSTEM_PATH);
182 logTarget = baseTarget.path(LOG_PATH);
183 switchTarget = baseTarget.path(SWITCH_PATH);
185 Response response = null;
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);
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);
205 JsonObject jsonObject = JsonParser.parseString(response.readEntity(String.class)).getAsJsonObject();
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"));
213 if (jsonObject.get("success").toString().equals("false")) {
214 logger.trace("initialize() Response: {}", jsonObject.get("error"));
218 if (jsonObject.get("success").toString().equals("false")) {
219 RESTError error = gson.fromJson(jsonObject.get("error").toString(), RESTError.class);
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);
229 if (jsonObject.get("success").toString().equals("true")) {
230 if (logJob == null || logJob.isCancelled()) {
231 logJob = scheduler.scheduleWithFixedDelay(logRunnable, 0, 1, TimeUnit.SECONDS);
234 updateStatus(ThingStatus.ONLINE);
236 scheduler.schedule(configureRunnable, 0, TimeUnit.SECONDS);
242 public void dispose() {
243 logger.debug("Disposing the Helios IP Vario handler for '{}'.", getThing().getUID().toString());
245 if (logSubscriptionID != 0) {
249 if (logJob != null && !logJob.isCancelled()) {
254 if (heliosClient != null) {
255 heliosClient.close();
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());
267 if (theChannel != null) {
268 ChannelTypeUID channelType = theChannel.getChannelTypeUID();
269 if (channelType.equals(triggerUID)) {
270 String switchID = channelUID.getId().substring(6);
271 triggerSwitch(switchID);
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);
286 private long subscribe() {
287 if (getThing().getStatus() == ThingStatus.ONLINE) {
288 logTarget = baseTarget.path(LOG_PATH);
290 Response response = null;
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) {
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);
304 if (response != null) {
305 JsonObject jsonObject = JsonParser.parseString(response.readEntity(String.class)).getAsJsonObject();
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()
312 if (jsonObject.get("success").toString().equals("true")) {
313 logger.trace("subscribe() Response: {}", jsonObject.get("result"));
315 if (jsonObject.get("success").toString().equals("false")) {
316 logger.trace("subscribe() Response: {}", jsonObject.get("error"));
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;
327 RESTError error = gson.fromJson(jsonObject.get("error").toString(), RESTError.class);
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);
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);
348 private void unsubscribe() {
349 if (getThing().getStatus() == ThingStatus.ONLINE) {
350 logTarget = baseTarget.path(LOG_PATH);
352 Response response = null;
354 response = logTarget.resolveTemplate("ip", ipAddress).resolveTemplate("cmd", UNSUBSCRIBE)
355 .queryParam("id", logSubscriptionID).request(MediaType.APPLICATION_JSON_TYPE).get();
356 } catch (Exception e) {
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);
366 if (response != null) {
367 JsonObject jsonObject = JsonParser.parseString(response.readEntity(String.class)).getAsJsonObject();
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"));
376 if (jsonObject.get("success").toString().equals("false")) {
377 logger.trace("unsubscribe() Response: {}", jsonObject.get("error"));
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());
385 RESTError error = gson.fromJson(jsonObject.get("error").toString(), RESTError.class);
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);
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);
406 private List<RESTEvent> pullLog(long logSubscriptionID) {
407 if (getThing().getStatus() == ThingStatus.ONLINE && heliosClient != null) {
408 logTarget = baseTarget.path(LOG_PATH);
410 Response response = null;
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);
427 if (response != null) {
428 JsonObject jsonObject = JsonParser.parseString(response.readEntity(String.class)).getAsJsonObject();
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"));
438 if (jsonObject.get("success").toString().equals("false")) {
439 logger.trace("pullLog() Response: {}", jsonObject.get("error"));
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);
450 RESTError error = gson.fromJson(jsonObject.get("error").toString(), RESTError.class);
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);
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);
473 private List<RESTSwitch> getSwitches() {
474 switchTarget = baseTarget.path(SWITCH_PATH);
476 Response response = null;
478 response = switchTarget.resolveTemplate("ip", ipAddress).resolveTemplate("cmd", CAPABILITIES)
479 .request(MediaType.APPLICATION_JSON_TYPE).get();
480 } catch (NullPointerException e) {
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);
489 if (response != null) {
490 JsonObject jsonObject = JsonParser.parseString(response.readEntity(String.class)).getAsJsonObject();
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"));
498 if (jsonObject.get("success").toString().equals("false")) {
499 logger.trace("getSwitches() Response: {}", jsonObject.get("error"));
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);
514 RESTError error = gson.fromJson(jsonObject.get("error").toString(), RESTError.class);
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)) {
520 "The API is not supported by the Helios hardware or current license, or the Authentication method is not set to Basic");
522 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
523 error.code + ":" + error.param + ":" + error.description);
524 scheduler.schedule(resetRunnable, RESET_INTERVAL, TimeUnit.SECONDS);
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);
538 private void triggerSwitch(String id) {
539 if (getThing().getStatus() == ThingStatus.ONLINE) {
540 switchTarget = baseTarget.path(SWITCH_PATH);
542 Response response = null;
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);
555 if (response != null) {
556 JsonObject jsonObject = JsonParser.parseString(response.readEntity(String.class)).getAsJsonObject();
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"));
565 if (jsonObject.get("success").toString().equals("false")) {
566 logger.trace("triggerSwitch() Response: {}", jsonObject.get("error"));
570 if (jsonObject.get("success").toString().equals("true")) {
571 logger.debug("Successfully triggered a switch on the Helios IP Vario '{}'",
572 getThing().getUID().toString());
574 RESTError error = gson.fromJson(jsonObject.get("error").toString(), RESTError.class);
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);
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);
593 private void enableSwitch(String id, boolean flag) {
594 if (getThing().getStatus() == ThingStatus.ONLINE) {
595 switchTarget = baseTarget.path(SWITCH_PATH);
597 Response response = null;
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);
610 if (response != null) {
611 JsonObject jsonObject = JsonParser.parseString(response.readEntity(String.class)).getAsJsonObject();
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()
618 if (jsonObject.get("success").toString().equals("true")) {
619 logger.trace("enableSwitch() Response: {}", jsonObject.get("result"));
621 if (jsonObject.get("success").toString().equals("false")) {
622 logger.trace("enableSwitch() Response: {}", jsonObject.get("error"));
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());
630 RESTError error = gson.fromJson(jsonObject.get("error").toString(), RESTError.class);
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);
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);
649 private List<RESTPort> getPorts() {
650 portTarget = baseTarget.path(PORT_PATH);
652 Response response = null;
654 response = portTarget.resolveTemplate("ip", ipAddress).resolveTemplate("cmd", CAPABILITIES)
655 .request(MediaType.APPLICATION_JSON_TYPE).get();
656 } catch (NullPointerException e) {
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);
665 if (response != null) {
666 JsonObject jsonObject = JsonParser.parseString(response.readEntity(String.class)).getAsJsonObject();
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"));
674 if (jsonObject.get("success").toString().equals("false")) {
675 logger.trace("getPorts() Response: {}", jsonObject.get("error"));
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);
688 RESTError error = gson.fromJson(jsonObject.get("error").toString(), RESTError.class);
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)) {
694 "The API is not supported by the Helios hardware or current license, or the Authentication method is not set to Basic");
696 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
697 error.code + ":" + error.param + ":" + error.description);
698 scheduler.schedule(resetRunnable, RESET_INTERVAL, TimeUnit.SECONDS);
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);
712 protected Runnable resetRunnable = () -> {
713 logger.debug("Resetting the Helios IP Vario handler for '{}'", getThing().getUID());
718 protected Runnable configureRunnable = () -> {
719 logger.debug("Fetching the configuration of the Helios IP Vario '{}' ", getThing().getUID().toString());
721 Response response = null;
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);
733 if (response != null) {
734 JsonObject jsonObject = JsonParser.parseString(response.readEntity(String.class)).getAsJsonObject();
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"));
742 if (jsonObject.get("success").toString().equals("false")) {
743 logger.trace("configureRunnable Response: {}", jsonObject.get("error"));
747 RESTSystemInfo systemInfo = gson.fromJson(jsonObject.get("result").toString(), RESTSystemInfo.class);
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);
759 List<RESTSwitch> switches = getSwitches();
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);
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());
783 List<RESTPort> ports = getPorts();
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);
792 Map<String, String> channelProperties = new HashMap<>();
793 channelProperties.put("type", aPort.type);
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());
804 protected Runnable logRunnable = () -> {
805 if (getThing().getStatus() == ThingStatus.ONLINE) {
806 if (logSubscriptionID == 0) {
807 logSubscriptionID = subscribe();
810 SimpleDateFormat dateFormatter = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss");
812 while (logSubscriptionID != 0) {
814 List<RESTEvent> events = pullLog(logSubscriptionID);
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));
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));
824 switch (event.event) {
826 StringType valueType = new StringType(event.params.get("state").getAsString());
827 updateState(DEVICE_STATE, valueType);
828 updateState(DEVICE_STATE_STAMP, stampType);
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);
837 updateState(AUDIO_LOOP_TEST, UnDefType.UNDEF);
840 updateState(AUDIO_LOOP_TEST_STAMP, stampType);
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);
849 updateState(MOTION, UnDefType.UNDEF);
852 updateState(MOTION_STAMP, stampType);
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);
861 updateState(NOISE, UnDefType.UNDEF);
864 updateState(NOISE_STAMP, stampType);
868 triggerChannel(KEY_PRESSED, event.params.get("key").getAsString());
870 updateState(KEY_PRESSED_STAMP, stampType);
874 triggerChannel(KEY_RELEASED, event.params.get("key").getAsString());
876 updateState(KEY_RELEASED_STAMP, stampType);
880 triggerChannel(CODE, event.params.get("code").getAsString());
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);
887 updateState(CODE_VALID, UnDefType.UNDEF);
890 updateState(CODE_STAMP, stampType);
894 triggerChannel(CARD, event.params.get("uid").getAsString());
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);
901 updateState(CARD_VALID, UnDefType.UNDEF);
904 updateState(CARD_STAMP, stampType);
908 ChannelUID inputChannel = new ChannelUID(getThing().getUID(),
909 "io" + event.params.get("port").getAsString());
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);
916 updateState(inputChannel, UnDefType.UNDEF);
920 case OUTPUTCHANGED: {
921 ChannelUID inputChannel = new ChannelUID(getThing().getUID(),
922 "io" + event.params.get("port").getAsString());
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);
929 updateState(inputChannel, UnDefType.UNDEF);
933 case CALLSTATECHANGED: {
934 StringType valueType = new StringType(event.params.get("state").getAsString());
935 updateState(CALL_STATE, valueType);
937 valueType = new StringType(event.params.get("direction").getAsString());
938 updateState(CALL_DIRECTION, valueType);
940 updateState(CALL_STATE_STAMP, stampType);
943 case REGISTRATIONSTATECHANGED: {
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);
952 updateState(SWITCH_STATE, UnDefType.UNDEF);
955 if (event.params.get("originator") != null) {
956 StringType originatorType = new StringType(
957 event.params.get("originator").getAsString());
958 updateState(SWITCH_STATE_ORIGINATOR, originatorType);
961 DecimalType switchType = new DecimalType(event.params.get("switch").getAsString());
962 updateState(SWITCH_STATE_SWITCH, switchType);
964 updateState(SWITCH_STATE_STAMP, stampType);
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("\"", ""));
978 if (logger.isTraceEnabled()) {
979 logger.trace("No events were retrieved");
982 } catch (Exception e) {
983 logger.error("An exception occurred while processing an event : '{}'", e.getMessage(), e);
989 protected class Authenticator implements ClientRequestFilter {
991 private final String user;
992 private final String password;
994 public Authenticator(String user, String password) {
996 this.password = password;
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);
1006 private String getBasicAuthentication() {
1007 String token = this.user + ":" + this.password;
1008 return "Basic " + Base64.getEncoder().encodeToString(token.getBytes(StandardCharsets.UTF_8));
1012 public class SecureRestClientTrustManager implements X509TrustManager {
1015 public void checkClientTrusted(X509Certificate[] arg0, String arg1) throws CertificateException {
1019 public void checkServerTrusted(X509Certificate[] arg0, String arg1) throws CertificateException {
1023 public X509Certificate[] getAcceptedIssuers() {
1024 return new X509Certificate[0];
1027 public boolean isClientTrusted(X509Certificate[] arg0) {
1031 public boolean isServerTrusted(X509Certificate[] arg0) {