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.ProcessingException;
38 import javax.ws.rs.client.Client;
39 import javax.ws.rs.client.ClientBuilder;
40 import javax.ws.rs.client.ClientRequestContext;
41 import javax.ws.rs.client.ClientRequestFilter;
42 import javax.ws.rs.client.WebTarget;
43 import javax.ws.rs.core.MediaType;
44 import javax.ws.rs.core.MultivaluedMap;
45 import javax.ws.rs.core.Response;
47 import org.openhab.binding.helios.internal.ws.rest.RESTError;
48 import org.openhab.binding.helios.internal.ws.rest.RESTEvent;
49 import org.openhab.binding.helios.internal.ws.rest.RESTPort;
50 import org.openhab.binding.helios.internal.ws.rest.RESTSubscribeResponse;
51 import org.openhab.binding.helios.internal.ws.rest.RESTSwitch;
52 import org.openhab.binding.helios.internal.ws.rest.RESTSystemInfo;
53 import org.openhab.core.library.types.DateTimeType;
54 import org.openhab.core.library.types.DecimalType;
55 import org.openhab.core.library.types.OnOffType;
56 import org.openhab.core.library.types.StringType;
57 import org.openhab.core.thing.Channel;
58 import org.openhab.core.thing.ChannelUID;
59 import org.openhab.core.thing.Thing;
60 import org.openhab.core.thing.ThingStatus;
61 import org.openhab.core.thing.ThingStatusDetail;
62 import org.openhab.core.thing.binding.BaseThingHandler;
63 import org.openhab.core.thing.binding.builder.ChannelBuilder;
64 import org.openhab.core.thing.binding.builder.ThingBuilder;
65 import org.openhab.core.thing.type.ChannelTypeUID;
66 import org.openhab.core.types.Command;
67 import org.openhab.core.types.RefreshType;
68 import org.openhab.core.types.UnDefType;
69 import org.slf4j.Logger;
70 import org.slf4j.LoggerFactory;
72 import com.google.gson.Gson;
73 import com.google.gson.JsonElement;
74 import com.google.gson.JsonObject;
75 import com.google.gson.JsonParser;
78 * The {@link HeliosHandler221} is responsible for handling commands, which are
79 * sent to one of the channels.
81 * @author Karel Goderis - Initial contribution
84 public class HeliosHandler221 extends BaseThingHandler {
86 private final Logger logger = LoggerFactory.getLogger(HeliosHandler221.class);
88 // List of Configuration constants
89 public static final String IP_ADDRESS = "ipAddress";
90 public static final String USERNAME = "username";
91 public static final String PASSWORD = "password";
93 // List of all REST API URI, commands, and JSON constants
94 public static final String BASE_URI = "https://{ip}/api/";
95 public static final String SYSTEM_PATH = "system/{cmd}";
96 public static final String FIRMWARE_PATH = "firmware/{cmd}";
97 public static final String LOG_PATH = "log/{cmd}";
98 public static final String SWITCH_PATH = "switch/{cmd}";
99 public static final String PORT_PATH = "io/{cmd}";
101 public static final String INFO = "info";
102 public static final String STATUS = "status";
104 public static final String SUBSCRIBE = "subscribe";
105 public static final String UNSUBSCRIBE = "unsubscribe";
106 public static final String PULL = "pull";
107 public static final String CAPABILITIES = "caps";
108 public static final String CONTROL = "ctrl";
110 public static final String DEVICESTATE = "DeviceState";
111 public static final String AUDIOLOOPTEST = "AudioLoopTest";
112 public static final String MOTIONDETECTED = "MotionDetected";
113 public static final String NOISEDETECTED = "NoiseDetected";
114 public static final String KEYPRESSED = "KeyPressed";
115 public static final String KEYRELEASED = "KeyReleased";
116 public static final String CODEENTERED = "CodeEntered";
117 public static final String CARDENTERED = "CardEntered";
118 public static final String INPUTCHANGED = "InputChanged";
119 public static final String OUTPUTCHANGED = "OutputChanged";
120 public static final String CALLSTATECHANGED = "CallStateChanged";
121 public static final String REGISTRATIONSTATECHANGED = "RegistrationStateChanged";
122 public static final String SWITCHSTATECHANGED = "SwitchStateChanged";
124 // REST Client API variables
125 private Client heliosClient;
126 private final ClientBuilder heliosClientBuilder;
127 private WebTarget baseTarget;
128 private WebTarget systemTarget;
129 private WebTarget logTarget;
130 private WebTarget switchTarget;
131 private WebTarget portTarget;
132 private String ipAddress;
135 private Gson gson = new Gson();
137 private ScheduledFuture<?> logJob;
138 private static final long RESET_INTERVAL = 15;
139 private static final long HELIOS_DURATION = 120;
140 private static final long HELIOS_PULL_DURATION = 10;
142 private long logSubscriptionID = 0;
144 public HeliosHandler221(Thing thing, ClientBuilder heliosClientBuilder) {
146 this.heliosClientBuilder = heliosClientBuilder;
150 public void initialize() {
151 logger.debug("Initializing the Helios IP Vario handler for '{}'.", getThing().getUID().toString());
153 ipAddress = (String) getConfig().get(IP_ADDRESS);
154 String username = (String) getConfig().get(USERNAME);
155 String password = (String) getConfig().get(PASSWORD);
157 if (ipAddress != null && !ipAddress.isEmpty() && username != null && !username.isEmpty() && password != null
158 && !password.isEmpty()) {
159 SecureRestClientTrustManager secureRestClientTrustManager = new SecureRestClientTrustManager();
160 SSLContext sslContext = null;
162 sslContext = SSLContext.getInstance("SSL");
163 } catch (NoSuchAlgorithmException e1) {
164 logger.error("An exception occurred while requesting the SSL encryption algorithm : '{}'",
165 e1.getMessage(), e1);
168 if (sslContext != null) {
169 sslContext.init(null, new javax.net.ssl.TrustManager[] { secureRestClientTrustManager }, null);
171 } catch (KeyManagementException e1) {
172 logger.error("An exception occurred while initialising the SSL context : '{}'", e1.getMessage(), e1);
175 heliosClient = heliosClientBuilder.sslContext(sslContext).hostnameVerifier(new HostnameVerifier() {
177 public boolean verify(String hostname, javax.net.ssl.SSLSession sslSession) {
181 heliosClient.register(new Authenticator(username, password));
183 baseTarget = heliosClient.target(BASE_URI.replace("{ip}", ipAddress));
184 systemTarget = baseTarget.path(SYSTEM_PATH);
185 logTarget = baseTarget.path(LOG_PATH);
186 switchTarget = baseTarget.path(SWITCH_PATH);
188 Response response = null;
190 response = systemTarget.resolveTemplate("cmd", INFO).request(MediaType.APPLICATION_JSON_TYPE).get();
191 } catch (ProcessingException e) {
192 logger.debug("An exception occurred while fetching system info of the Helios IP Vario '{}' : '{}'",
193 getThing().getUID().toString(), e.getMessage(), e);
194 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, e.getMessage());
195 scheduler.schedule(resetRunnable, RESET_INTERVAL, TimeUnit.SECONDS);
199 if (response == null) {
200 logger.debug("There is a configuration problem for the Helios IP Vario '{}'",
201 getThing().getUID().toString());
202 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR);
203 scheduler.schedule(resetRunnable, RESET_INTERVAL, TimeUnit.SECONDS);
207 JsonObject jsonObject = JsonParser.parseString(response.readEntity(String.class)).getAsJsonObject();
209 if (logger.isTraceEnabled()) {
210 logger.trace("initialize() Request : {}", systemTarget.resolveTemplate("ip", ipAddress)
211 .resolveTemplate("cmd", INFO).getUri().toASCIIString());
212 if (jsonObject.get("success").toString().equals("true")) {
213 logger.trace("initialize() Response: {}", jsonObject.get("result"));
215 if (jsonObject.get("success").toString().equals("false")) {
216 logger.trace("initialize() Response: {}", jsonObject.get("error"));
220 if (jsonObject.get("success").toString().equals("false")) {
221 RESTError error = gson.fromJson(jsonObject.get("error").toString(), RESTError.class);
223 "An error occurred while communicating with the Helios IP Vario '{}': code '{}', param '{}' : '{}'",
224 new Object[] { getThing().getUID().toString(), error.code, error.param, error.description });
225 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
226 error.code + ":" + error.param + ":" + error.description);
227 scheduler.schedule(resetRunnable, RESET_INTERVAL, TimeUnit.SECONDS);
231 if (jsonObject.get("success").toString().equals("true")) {
232 if (logJob == null || logJob.isCancelled()) {
233 logJob = scheduler.scheduleWithFixedDelay(logRunnable, 0, 1, TimeUnit.SECONDS);
236 updateStatus(ThingStatus.ONLINE);
238 scheduler.schedule(configureRunnable, 0, TimeUnit.SECONDS);
244 public void dispose() {
245 logger.debug("Disposing the Helios IP Vario handler for '{}'.", getThing().getUID().toString());
249 private void tearDown() {
250 logger.debug("Tearing down the Helios IP Vario handler for '{}'.", getThing().getUID().toString());
252 if (logSubscriptionID != 0) {
256 if (logJob != null && !logJob.isCancelled()) {
261 if (heliosClient != null) {
262 heliosClient.close();
268 public void handleCommand(ChannelUID channelUID, Command command) {
269 if (!(command instanceof RefreshType)) {
270 ChannelTypeUID triggerUID = new ChannelTypeUID(BINDING_ID, SWITCH_TRIGGER);
271 ChannelTypeUID enablerUID = new ChannelTypeUID(BINDING_ID, SWITCH_ENABLER);
272 Channel theChannel = getThing().getChannel(channelUID.getId());
274 if (theChannel != null) {
275 ChannelTypeUID channelType = theChannel.getChannelTypeUID();
276 if (channelType.equals(triggerUID)) {
277 String switchID = channelUID.getId().substring(6);
278 triggerSwitch(switchID);
281 if (channelType.equals(enablerUID)) {
282 String switchID = channelUID.getId().substring(6, channelUID.getId().lastIndexOf("active"));
283 if (command instanceof OnOffType && command == OnOffType.OFF) {
284 enableSwitch(switchID, false);
285 } else if (command instanceof OnOffType && command == OnOffType.ON) {
286 enableSwitch(switchID, true);
293 private long subscribe() {
294 if (getThing().getStatus() == ThingStatus.ONLINE) {
295 logTarget = baseTarget.path(LOG_PATH);
297 Response response = null;
299 response = logTarget.resolveTemplate("ip", ipAddress).resolveTemplate("cmd", SUBSCRIBE)
300 .queryParam("include", "new").queryParam("duration", HELIOS_DURATION)
301 .request(MediaType.APPLICATION_JSON_TYPE).get();
302 } catch (NullPointerException e) {
304 "An exception occurred while subscribing to the log entries of the Helios IP Vario '{}' : '{}'",
305 getThing().getUID().toString(), e.getMessage(), e);
306 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, e.getMessage());
307 scheduler.schedule(resetRunnable, RESET_INTERVAL, TimeUnit.SECONDS);
311 if (response != null) {
312 JsonObject jsonObject = JsonParser.parseString(response.readEntity(String.class)).getAsJsonObject();
314 if (logger.isTraceEnabled()) {
315 logger.trace("subscribe() Request : {}",
316 logTarget.resolveTemplate("ip", ipAddress).resolveTemplate("cmd", SUBSCRIBE)
317 .queryParam("include", "new").queryParam("duration", HELIOS_DURATION).getUri()
319 if (jsonObject.get("success").toString().equals("true")) {
320 logger.trace("subscribe() Response: {}", jsonObject.get("result"));
322 if (jsonObject.get("success").toString().equals("false")) {
323 logger.trace("subscribe() Response: {}", jsonObject.get("error"));
327 if (jsonObject.get("success").toString().equals("true")) {
328 RESTSubscribeResponse subscribeResponse = gson.fromJson(jsonObject.get("result").toString(),
329 RESTSubscribeResponse.class);
330 logger.debug("The subscription id to pull logs from the Helios IP Vario '{}' is '{}'",
331 getThing().getUID().toString(), subscribeResponse.id);
332 return subscribeResponse.id;
334 RESTError error = gson.fromJson(jsonObject.get("error").toString(), RESTError.class);
336 "An error occurred while communicating with the Helios IP Vario '{}': code '{}', param '{}' : '{}'",
337 getThing().getUID().toString(), error.code, error.param, error.description);
338 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
339 error.code + ":" + error.param + ":" + error.description);
340 scheduler.schedule(resetRunnable, RESET_INTERVAL, TimeUnit.SECONDS);
344 logger.debug("An error occurred while subscribing to the log entries of the Helios IP Vario '{}'",
345 getThing().getUID().toString());
346 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR);
347 scheduler.schedule(resetRunnable, RESET_INTERVAL, TimeUnit.SECONDS);
355 private void unsubscribe() {
356 if (getThing().getStatus() == ThingStatus.ONLINE) {
357 logTarget = baseTarget.path(LOG_PATH);
359 Response response = null;
361 response = logTarget.resolveTemplate("ip", ipAddress).resolveTemplate("cmd", UNSUBSCRIBE)
362 .queryParam("id", logSubscriptionID).request(MediaType.APPLICATION_JSON_TYPE).get();
363 } catch (Exception e) {
365 "An exception occurred while unsubscribing from the log entries of the Helios IP Vario '{}' : {}",
366 getThing().getUID().toString(), e.getMessage(), e);
367 logSubscriptionID = 0;
368 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, e.getMessage());
369 scheduler.schedule(resetRunnable, RESET_INTERVAL, TimeUnit.SECONDS);
373 if (response != null) {
374 JsonObject jsonObject = JsonParser.parseString(response.readEntity(String.class)).getAsJsonObject();
376 if (logger.isTraceEnabled()) {
377 logger.trace("unsubscribe() Request : {}",
378 logTarget.resolveTemplate("ip", ipAddress).resolveTemplate("cmd", UNSUBSCRIBE)
379 .queryParam("id", logSubscriptionID).getUri().toASCIIString());
380 if (jsonObject.get("success").toString().equals("true")) {
381 logger.trace("unsubscribe() Response: {}", jsonObject.get("result"));
383 if (jsonObject.get("success").toString().equals("false")) {
384 logger.trace("unsubscribe() Response: {}", jsonObject.get("error"));
388 if (jsonObject.get("success").toString().equals("true")) {
389 logger.debug("Successfully unsubscribed from the log entries of the Helios IP Vario '{}'",
390 getThing().getUID().toString());
392 RESTError error = gson.fromJson(jsonObject.get("error").toString(), RESTError.class);
394 "An error occurred while communicating with the Helios IP Vario '{}' : code '{}', param '{}' : '{}'",
395 getThing().getUID().toString(), error.code, error.param, error.description);
396 logSubscriptionID = 0;
397 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
398 error.code + ":" + error.param + ":" + error.description);
399 scheduler.schedule(resetRunnable, RESET_INTERVAL, TimeUnit.SECONDS);
403 logger.debug("An error occurred while unsubscribing from the log entries of the Helios IP Vario '{}'",
404 getThing().getUID().toString());
405 logSubscriptionID = 0;
406 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR);
407 scheduler.schedule(resetRunnable, RESET_INTERVAL, TimeUnit.SECONDS);
413 private List<RESTEvent> pullLog(long logSubscriptionID) {
414 if (getThing().getStatus() == ThingStatus.ONLINE && heliosClient != null) {
415 logTarget = baseTarget.path(LOG_PATH);
417 Response response = null;
419 long now = System.currentTimeMillis();
420 response = logTarget.resolveTemplate("ip", ipAddress).resolveTemplate("cmd", PULL)
421 .queryParam("id", logSubscriptionID).queryParam("timeout", HELIOS_PULL_DURATION)
422 .request(MediaType.APPLICATION_JSON_TYPE).get();
423 logger.trace("Pulled logs in {} millseconds from {}", System.currentTimeMillis() - now,
424 getThing().getUID());
425 } catch (NullPointerException e) {
426 logger.debug("An exception occurred while pulling log entries from the Helios IP Vario '{}' : '{}'",
427 getThing().getUID().toString(), e.getMessage(), e);
428 this.logSubscriptionID = 0;
429 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, e.getMessage());
430 scheduler.schedule(resetRunnable, RESET_INTERVAL, TimeUnit.SECONDS);
434 if (response != null) {
435 JsonObject jsonObject = JsonParser.parseString(response.readEntity(String.class)).getAsJsonObject();
437 if (logger.isTraceEnabled()) {
438 logger.trace("pullLog() Request : {}",
439 logTarget.resolveTemplate("ip", ipAddress).resolveTemplate("cmd", PULL)
440 .queryParam("id", logSubscriptionID).queryParam("timeout", HELIOS_PULL_DURATION)
441 .getUri().toASCIIString());
442 if (jsonObject.get("success").toString().equals("true")) {
443 logger.trace("pullLog() Response: {}", jsonObject.get("result"));
445 if (jsonObject.get("success").toString().equals("false")) {
446 logger.trace("pullLog() Response: {}", jsonObject.get("error"));
450 if (jsonObject.get("success").toString().equals("true")) {
451 logger.trace("Successfully pulled log entries from the Helios IP Vario '{}'",
452 getThing().getUID().toString());
453 JsonObject js = (JsonObject) jsonObject.get("result");
454 RESTEvent[] eventArray = gson.fromJson(js.getAsJsonArray("events"), RESTEvent[].class);
455 return Arrays.asList(eventArray);
457 RESTError error = gson.fromJson(jsonObject.get("error").toString(), RESTError.class);
459 "An error occurred while communicating with the Helios IP Vario '{}' : code '{}', param '{}' : '{}'",
460 getThing().getUID().toString(), error.code, error.param, error.description);
461 this.logSubscriptionID = 0;
462 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
463 error.code + ":" + error.param + ":" + error.description);
464 scheduler.schedule(resetRunnable, RESET_INTERVAL, TimeUnit.SECONDS);
468 logger.debug("An error occurred while polling log entries from the Helios IP Vario '{}'",
469 getThing().getUID().toString());
470 this.logSubscriptionID = 0;
471 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR);
472 scheduler.schedule(resetRunnable, RESET_INTERVAL, TimeUnit.SECONDS);
480 private List<RESTSwitch> getSwitches() {
481 switchTarget = baseTarget.path(SWITCH_PATH);
483 Response response = null;
485 response = switchTarget.resolveTemplate("ip", ipAddress).resolveTemplate("cmd", CAPABILITIES)
486 .request(MediaType.APPLICATION_JSON_TYPE).get();
487 } catch (NullPointerException e) {
489 "An exception occurred while requesting switch capabilities from the Helios IP Vario '{}' : '{}'",
490 getThing().getUID().toString(), e.getMessage(), e);
491 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, e.getMessage());
492 scheduler.schedule(resetRunnable, RESET_INTERVAL, TimeUnit.SECONDS);
496 if (response != null) {
497 JsonObject jsonObject = JsonParser.parseString(response.readEntity(String.class)).getAsJsonObject();
499 if (logger.isTraceEnabled()) {
500 logger.trace("getSwitches() Request : {}", switchTarget.resolveTemplate("ip", ipAddress)
501 .resolveTemplate("cmd", CAPABILITIES).getUri().toASCIIString());
502 if (jsonObject.get("success").toString().equals("true")) {
503 logger.trace("getSwitches() Response: {}", jsonObject.get("result"));
505 if (jsonObject.get("success").toString().equals("false")) {
506 logger.trace("getSwitches() Response: {}", jsonObject.get("error"));
510 if (jsonObject.get("success").toString().equals("true")) {
511 logger.debug("Successfully requested switch capabilities from the Helios IP Vario '{}'",
512 getThing().getUID().toString());
513 String result = jsonObject.get("result").toString();
514 result = result.replace("switch", "id");
515 JsonObject js = JsonParser.parseString(result).getAsJsonObject();
516 RESTSwitch[] switchArray = gson.fromJson(js.getAsJsonArray("ides"), RESTSwitch[].class);
517 if (switchArray != null) {
518 return Arrays.asList(switchArray);
521 RESTError error = gson.fromJson(jsonObject.get("error").toString(), RESTError.class);
523 "An error occurred while communicating with the Helios IP Vario '{}' : code '{}', param '{}' : '{}'",
524 getThing().getUID().toString(), error.code, error.param, error.description);
525 if ("8".equals(error.code)) {
527 "The API is not supported by the Helios hardware or current license, or the Authentication method is not set to Basic");
529 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
530 error.code + ":" + error.param + ":" + error.description);
531 scheduler.schedule(resetRunnable, RESET_INTERVAL, TimeUnit.SECONDS);
536 logger.debug("An error occurred while requesting switch capabilities from the Helios IP Vario '{}'",
537 getThing().getUID().toString());
538 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR);
539 scheduler.schedule(resetRunnable, RESET_INTERVAL, TimeUnit.SECONDS);
545 private void triggerSwitch(String id) {
546 if (getThing().getStatus() == ThingStatus.ONLINE) {
547 switchTarget = baseTarget.path(SWITCH_PATH);
549 Response response = null;
551 response = switchTarget.resolveTemplate("ip", ipAddress).resolveTemplate("cmd", CONTROL)
552 .queryParam("switch", id).queryParam("action", "trigger")
553 .request(MediaType.APPLICATION_JSON_TYPE).get();
554 } catch (NullPointerException e) {
555 logger.debug("An exception occurred while triggering a switch on the Helios IP Vario '{}' : '{}'",
556 getThing().getUID().toString(), e.getMessage(), e);
557 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, e.getMessage());
558 scheduler.schedule(resetRunnable, RESET_INTERVAL, TimeUnit.SECONDS);
562 if (response != null) {
563 JsonObject jsonObject = JsonParser.parseString(response.readEntity(String.class)).getAsJsonObject();
565 if (logger.isTraceEnabled()) {
566 logger.trace("triggerSwitch() Request : {}",
567 switchTarget.resolveTemplate("ip", ipAddress).resolveTemplate("cmd", CONTROL)
568 .queryParam("switch", id).queryParam("action", "trigger").getUri().toASCIIString());
569 if (jsonObject.get("success").toString().equals("true")) {
570 logger.trace("triggerSwitch() Response: {}", jsonObject.get("result"));
572 if (jsonObject.get("success").toString().equals("false")) {
573 logger.trace("triggerSwitch() Response: {}", jsonObject.get("error"));
577 if (jsonObject.get("success").toString().equals("true")) {
578 logger.debug("Successfully triggered a switch on the Helios IP Vario '{}'",
579 getThing().getUID().toString());
581 RESTError error = gson.fromJson(jsonObject.get("error").toString(), RESTError.class);
583 "An error occurred while communicating with the Helios IP Vario '{}' : code '{}', param '{}' : '{}'",
584 getThing().getUID().toString(), error.code, error.param, error.description);
585 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
586 error.code + ":" + error.param + ":" + error.description);
587 scheduler.schedule(resetRunnable, RESET_INTERVAL, TimeUnit.SECONDS);
591 logger.warn("An error occurred while triggering a switch on the Helios IP Vario '{}'",
592 getThing().getUID().toString());
593 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR);
594 scheduler.schedule(resetRunnable, RESET_INTERVAL, TimeUnit.SECONDS);
600 private void enableSwitch(String id, boolean flag) {
601 if (getThing().getStatus() == ThingStatus.ONLINE) {
602 switchTarget = baseTarget.path(SWITCH_PATH);
604 Response response = null;
606 response = switchTarget.resolveTemplate("ip", ipAddress).resolveTemplate("cmd", CONTROL)
607 .queryParam("switch", id).queryParam("action", flag ? "on" : "off")
608 .request(MediaType.APPLICATION_JSON_TYPE).get();
609 } catch (NullPointerException e) {
610 logger.error("An exception occurred while dis/enabling a switch on the Helios IP Vario '{}' : '{}'",
611 getThing().getUID().toString(), e.getMessage(), e);
612 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, e.getMessage());
613 scheduler.schedule(resetRunnable, RESET_INTERVAL, TimeUnit.SECONDS);
617 if (response != null) {
618 JsonObject jsonObject = JsonParser.parseString(response.readEntity(String.class)).getAsJsonObject();
620 if (logger.isTraceEnabled()) {
621 logger.trace("enableSwitch() Request : {}",
622 switchTarget.resolveTemplate("ip", ipAddress).resolveTemplate("cmd", CONTROL)
623 .queryParam("switch", id).queryParam("action", flag ? "on" : "off").getUri()
625 if (jsonObject.get("success").toString().equals("true")) {
626 logger.trace("enableSwitch() Response: {}", jsonObject.get("result"));
628 if (jsonObject.get("success").toString().equals("false")) {
629 logger.trace("enableSwitch() Response: {}", jsonObject.get("error"));
633 if (jsonObject.get("success").toString().equals("true")) {
634 logger.debug("Successfully dis/enabled a switch on the Helios IP Vario '{}'",
635 getThing().getUID().toString());
637 RESTError error = gson.fromJson(jsonObject.get("error").toString(), RESTError.class);
639 "An error occurred while communicating with the Helios IP Vario '{}': code '{}', param '{}' : '{}'",
640 getThing().getUID().toString(), error.code, error.param, error.description);
641 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
642 error.code + ":" + error.param + ":" + error.description);
643 scheduler.schedule(resetRunnable, RESET_INTERVAL, TimeUnit.SECONDS);
647 logger.warn("An error occurred while dis/enabling a switch on the Helios IP Vario '{}'",
648 getThing().getUID().toString());
649 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR);
650 scheduler.schedule(resetRunnable, RESET_INTERVAL, TimeUnit.SECONDS);
656 private List<RESTPort> getPorts() {
657 portTarget = baseTarget.path(PORT_PATH);
659 Response response = null;
661 response = portTarget.resolveTemplate("ip", ipAddress).resolveTemplate("cmd", CAPABILITIES)
662 .request(MediaType.APPLICATION_JSON_TYPE).get();
663 } catch (NullPointerException e) {
665 "An exception occurred while requesting port capabilities from the Helios IP Vario '{}' : '{}'",
666 getThing().getUID().toString(), e.getMessage(), e);
667 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, e.getMessage());
668 scheduler.schedule(resetRunnable, RESET_INTERVAL, TimeUnit.SECONDS);
672 if (response != null) {
673 JsonObject jsonObject = JsonParser.parseString(response.readEntity(String.class)).getAsJsonObject();
675 if (logger.isTraceEnabled()) {
676 logger.trace("getPorts() Request : {}", portTarget.resolveTemplate("ip", ipAddress)
677 .resolveTemplate("cmd", CAPABILITIES).getUri().toASCIIString());
678 if (jsonObject.get("success").toString().equals("true")) {
679 logger.trace("getPorts() Response: {}", jsonObject.get("result"));
681 if (jsonObject.get("success").toString().equals("false")) {
682 logger.trace("getPorts() Response: {}", jsonObject.get("error"));
686 if (jsonObject.get("success").toString().equals("true")) {
687 logger.debug("Successfully requested port capabilities from the Helios IP Vario '{}'",
688 getThing().getUID().toString());
689 JsonObject js = (JsonObject) jsonObject.get("result");
690 RESTPort[] portArray = gson.fromJson(js.getAsJsonArray("ports"), RESTPort[].class);
691 if (portArray != null) {
692 return Arrays.asList(portArray);
695 RESTError error = gson.fromJson(jsonObject.get("error").toString(), RESTError.class);
697 "An error occurred while communicating with the Helios IP Vario '{}': code '{}', param '{}' : '{}'",
698 getThing().getUID().toString(), error.code, error.param, error.description);
699 if ("8".equals(error.code)) {
701 "The API is not supported by the Helios hardware or current license, or the Authentication method is not set to Basic");
703 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
704 error.code + ":" + error.param + ":" + error.description);
705 scheduler.schedule(resetRunnable, RESET_INTERVAL, TimeUnit.SECONDS);
710 logger.warn("An error occurred while requesting port capabilities from the Helios IP Vario '{}'",
711 getThing().getUID().toString());
712 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR);
713 scheduler.schedule(resetRunnable, RESET_INTERVAL, TimeUnit.SECONDS);
719 protected Runnable resetRunnable = () -> {
720 logger.debug("Resetting the Helios IP Vario handler for '{}'", getThing().getUID());
725 protected Runnable configureRunnable = () -> {
726 logger.debug("Fetching the configuration of the Helios IP Vario '{}' ", getThing().getUID().toString());
728 Response response = null;
730 response = systemTarget.resolveTemplate("ip", ipAddress).resolveTemplate("cmd", INFO)
731 .request(MediaType.APPLICATION_JSON_TYPE).get();
732 } catch (NullPointerException e) {
733 logger.error("An exception occurred while fetching system info of the Helios IP Vario '{}' : '{}'",
734 getThing().getUID().toString(), e.getMessage(), e);
735 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR);
736 scheduler.schedule(resetRunnable, RESET_INTERVAL, TimeUnit.SECONDS);
740 if (response != null) {
741 JsonObject jsonObject = JsonParser.parseString(response.readEntity(String.class)).getAsJsonObject();
743 if (logger.isTraceEnabled()) {
744 logger.trace("configureRunnable Request : {}", systemTarget.resolveTemplate("ip", ipAddress)
745 .resolveTemplate("cmd", INFO).getUri().toASCIIString());
746 if (jsonObject.get("success").toString().equals("true")) {
747 logger.trace("configureRunnable Response: {}", jsonObject.get("result"));
749 if (jsonObject.get("success").toString().equals("false")) {
750 logger.trace("configureRunnable Response: {}", jsonObject.get("error"));
754 RESTSystemInfo systemInfo = gson.fromJson(jsonObject.get("result").toString(), RESTSystemInfo.class);
756 Map<String, String> properties = editProperties();
757 properties.put(VARIANT, systemInfo.variant);
758 properties.put(SERIAL_NUMBER, systemInfo.serialNumber);
759 properties.put(HW_VERSION, systemInfo.hwVersion);
760 properties.put(SW_VERSION, systemInfo.swVersion);
761 properties.put(BUILD_TYPE, systemInfo.buildType);
762 properties.put(DEVICE_NAME, systemInfo.deviceName);
763 updateProperties(properties);
766 List<RESTSwitch> switches = getSwitches();
768 if (switches != null) {
769 for (RESTSwitch aSwitch : switches) {
770 if (aSwitch.enabled.equals("true")) {
771 logger.debug("Adding a channel to the Helios IP Vario '{}' for the switch with id '{}'",
772 getThing().getUID().toString(), aSwitch.id);
773 ThingBuilder thingBuilder = editThing();
774 ChannelTypeUID enablerUID = new ChannelTypeUID(BINDING_ID, SWITCH_ENABLER);
775 ChannelTypeUID triggerUID = new ChannelTypeUID(BINDING_ID, SWITCH_TRIGGER);
776 ChannelUID activeSwitchChannelUID = new ChannelUID(getThing().getUID(),
777 "switch" + aSwitch.id + "active");
778 ChannelUID switchChannelUID = new ChannelUID(getThing().getUID(), "switch" + aSwitch.id);
780 if (this.getThing().getChannel(activeSwitchChannelUID) == null) {
782 "Adding a channel with id '{}' to the Helios IP Vario '{}' for the switch with id '{}'",
783 activeSwitchChannelUID, getThing().getUID().toString(), aSwitch.id);
784 Channel channel = ChannelBuilder.create(activeSwitchChannelUID, "Switch").withType(enablerUID)
786 thingBuilder.withChannel(channel);
788 if (this.getThing().getChannel(switchChannelUID) == null) {
790 "Adding a channel with id '{}' to the Helios IP Vario '{}' for the switch with id '{}'",
791 switchChannelUID, getThing().getUID().toString(), aSwitch.id);
792 Channel channel = ChannelBuilder.create(switchChannelUID, "Switch").withType(triggerUID)
794 thingBuilder.withChannel(channel);
796 updateThing(thingBuilder.build());
801 List<RESTPort> ports = getPorts();
804 for (RESTPort aPort : ports) {
805 logger.debug("Adding a channel to the Helios IP Vario '{}' for the IO port with id '{}'",
806 getThing().getUID().toString(), aPort.port);
807 ThingBuilder thingBuilder = editThing();
808 ChannelTypeUID triggerUID = new ChannelTypeUID(BINDING_ID, IO_TRIGGER);
809 ChannelUID ioChannelUID = new ChannelUID(getThing().getUID(), "io" + aPort.port);
811 Map<String, String> channelProperties = new HashMap<>();
812 channelProperties.put("type", aPort.type);
814 if (this.getThing().getChannel(ioChannelUID) == null) {
816 "Adding a channel with id '{}' to the Helios IP Vario '{}' for the switch with id '{}'",
817 ioChannelUID.getId(), getThing().getUID().toString(), aPort.port);
818 Channel channel = ChannelBuilder.create(ioChannelUID, "Switch").withType(triggerUID)
819 .withProperties(channelProperties).build();
820 thingBuilder.withChannel(channel);
822 updateThing(thingBuilder.build());
827 protected Runnable logRunnable = () -> {
828 if (getThing().getStatus() == ThingStatus.ONLINE) {
829 if (logSubscriptionID == 0) {
830 logSubscriptionID = subscribe();
833 SimpleDateFormat dateFormatter = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss");
835 while (logSubscriptionID != 0) {
837 List<RESTEvent> events = pullLog(logSubscriptionID);
839 if (events != null) {
840 for (RESTEvent event : events) {
841 Date date = new Date(Long.valueOf(event.utcTime));
842 DateTimeType stampType = new DateTimeType(dateFormatter.format(date));
844 logger.debug("Received the event for Helios IP Vario '{}' with ID '{}' of type '{}' on {}",
845 getThing().getUID().toString(), event.id, event.event, dateFormatter.format(date));
847 switch (event.event) {
849 StringType valueType = new StringType(event.params.get("state").getAsString());
850 updateState(DEVICE_STATE, valueType);
851 updateState(DEVICE_STATE_STAMP, stampType);
854 case AUDIOLOOPTEST: {
855 if (event.params.get("result").getAsString().equals("passed")) {
856 updateState(AUDIO_LOOP_TEST, OnOffType.ON);
857 } else if (event.params.get("result").getAsString().equals("failed")) {
858 updateState(AUDIO_LOOP_TEST, OnOffType.OFF);
860 updateState(AUDIO_LOOP_TEST, UnDefType.UNDEF);
863 updateState(AUDIO_LOOP_TEST_STAMP, stampType);
866 case MOTIONDETECTED: {
867 if (event.params.get("state").getAsString().equals("in")) {
868 updateState(MOTION, OnOffType.ON);
869 } else if (event.params.get("state").getAsString().equals("out")) {
870 updateState(MOTION, OnOffType.OFF);
872 updateState(MOTION, UnDefType.UNDEF);
875 updateState(MOTION_STAMP, stampType);
878 case NOISEDETECTED: {
879 if (event.params.get("state").getAsString().equals("in")) {
880 updateState(NOISE, OnOffType.ON);
881 } else if (event.params.get("state").getAsString().equals("out")) {
882 updateState(NOISE, OnOffType.OFF);
884 updateState(NOISE, UnDefType.UNDEF);
887 updateState(NOISE_STAMP, stampType);
891 triggerChannel(KEY_PRESSED, event.params.get("key").getAsString());
893 updateState(KEY_PRESSED_STAMP, stampType);
897 triggerChannel(KEY_RELEASED, event.params.get("key").getAsString());
899 updateState(KEY_RELEASED_STAMP, stampType);
903 triggerChannel(CODE, event.params.get("code").getAsString());
905 if (event.params.get("valid").getAsString().equals("true")) {
906 updateState(CODE_VALID, OnOffType.ON);
907 } else if (event.params.get("valid").getAsString().equals("false")) {
908 updateState(CODE_VALID, OnOffType.OFF);
910 updateState(CODE_VALID, UnDefType.UNDEF);
913 updateState(CODE_STAMP, stampType);
917 triggerChannel(CARD, event.params.get("uid").getAsString());
919 if (event.params.get("valid").getAsString().equals("true")) {
920 updateState(CARD_VALID, OnOffType.ON);
921 } else if (event.params.get("valid").getAsString().equals("false")) {
922 updateState(CARD_VALID, OnOffType.OFF);
924 updateState(CARD_VALID, UnDefType.UNDEF);
927 updateState(CARD_STAMP, stampType);
931 ChannelUID inputChannel = new ChannelUID(getThing().getUID(),
932 "io" + event.params.get("port").getAsString());
934 if (event.params.get("state").getAsString().equals("true")) {
935 updateState(inputChannel, OnOffType.ON);
936 } else if (event.params.get("state").getAsString().equals("false")) {
937 updateState(inputChannel, OnOffType.OFF);
939 updateState(inputChannel, UnDefType.UNDEF);
943 case OUTPUTCHANGED: {
944 ChannelUID inputChannel = new ChannelUID(getThing().getUID(),
945 "io" + event.params.get("port").getAsString());
947 if (event.params.get("state").getAsString().equals("true")) {
948 updateState(inputChannel, OnOffType.ON);
949 } else if (event.params.get("state").getAsString().equals("false")) {
950 updateState(inputChannel, OnOffType.OFF);
952 updateState(inputChannel, UnDefType.UNDEF);
956 case CALLSTATECHANGED: {
957 StringType valueType = new StringType(event.params.get("state").getAsString());
958 updateState(CALL_STATE, valueType);
960 valueType = new StringType(event.params.get("direction").getAsString());
961 updateState(CALL_DIRECTION, valueType);
963 updateState(CALL_STATE_STAMP, stampType);
966 case REGISTRATIONSTATECHANGED: {
969 case SWITCHSTATECHANGED: {
970 if (event.params.get("state").getAsString().equals("true")) {
971 updateState(SWITCH_STATE, OnOffType.ON);
972 } else if (event.params.get("state").getAsString().equals("false")) {
973 updateState(SWITCH_STATE, OnOffType.OFF);
975 updateState(SWITCH_STATE, UnDefType.UNDEF);
978 if (event.params.get("originator") != null) {
979 StringType originatorType = new StringType(
980 event.params.get("originator").getAsString());
981 updateState(SWITCH_STATE_ORIGINATOR, originatorType);
984 DecimalType switchType = new DecimalType(event.params.get("switch").getAsString());
985 updateState(SWITCH_STATE_SWITCH, switchType);
987 updateState(SWITCH_STATE_STAMP, stampType);
991 logger.debug("Unrecognised event type : '{}'", event.event);
992 Set<Map.Entry<String, JsonElement>> entrySet = event.params.entrySet();
993 for (Map.Entry<String, JsonElement> entry : entrySet) {
994 logger.debug("Key '{}', Value '{}'", entry.getKey(),
995 event.params.get(entry.getKey()).getAsString().replace("\"", ""));
1001 if (logger.isTraceEnabled()) {
1002 logger.trace("No events were retrieved");
1005 } catch (ProcessingException e) {
1006 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, e.getMessage());
1007 logger.trace("An underlying exception forced the Helios IP Vario to go offline : '{}'",
1009 scheduler.schedule(resetRunnable, RESET_INTERVAL, TimeUnit.SECONDS);
1010 } catch (Exception e) {
1011 logger.error("An exception occurred while processing an event : '{}'", e.getMessage(), e);
1017 protected class Authenticator implements ClientRequestFilter {
1019 private final String user;
1020 private final String password;
1022 public Authenticator(String user, String password) {
1024 this.password = password;
1028 public void filter(ClientRequestContext requestContext) throws IOException {
1029 MultivaluedMap<String, Object> headers = requestContext.getHeaders();
1030 final String basicAuthentication = getBasicAuthentication();
1031 headers.add("Authorization", basicAuthentication);
1034 private String getBasicAuthentication() {
1035 String token = this.user + ":" + this.password;
1036 return "Basic " + Base64.getEncoder().encodeToString(token.getBytes(StandardCharsets.UTF_8));
1040 public class SecureRestClientTrustManager implements X509TrustManager {
1043 public void checkClientTrusted(X509Certificate[] arg0, String arg1) throws CertificateException {
1047 public void checkServerTrusted(X509Certificate[] arg0, String arg1) throws CertificateException {
1051 public X509Certificate[] getAcceptedIssuers() {
1052 return new X509Certificate[0];
1055 public boolean isClientTrusted(X509Certificate[] arg0) {
1059 public boolean isServerTrusted(X509Certificate[] arg0) {