diff --git a/bundles/org.openhab.binding.loxone/README.md b/bundles/org.openhab.binding.loxone/README.md index 37911c24bfa62..37ab59094e673 100644 --- a/bundles/org.openhab.binding.loxone/README.md +++ b/bundles/org.openhab.binding.loxone/README.md @@ -96,6 +96,9 @@ The acquired token will remain active for several weeks following the last succe In case a websocket connection to the Miniserver remains active for the whole duration of the token's life span, the binding will refresh the token one day before token expiration, without the need of providing the password. +In case of connecting to Generation 2 Miniservers, it is possible to establish a secure WebSocket connection over HTTPS protocol. Binding will automatically detect if HTTPS connection is available and will use it. In that case, commands sent to the Miniserver will not be additionally encrypted. When HTTPS is not available, binding will use unsecure HTTP connection and will encrypt each command. + +It is possible to override the communication protocol by setting `webSocketType` configuration parameter. Setting it to 1 will force to always establish HTTPS connection. Setting it to 2 will force to always establish HTTP connection. Default value of 0 means the binding will determine the right protocol in the runtime. A method to enable unrestricted security policy depends on the JRE version and vendor, some examples can be found [here](https://www.petefreitag.com/item/844.cfm) and [here](https://stackoverflow.com/questions/41580489/how-to-install-unlimited-strength-jurisdiction-policy-files). @@ -195,9 +198,10 @@ To define a parameter value in a .things file, please refer to it by parameter's ### Security -| ID | Name | Values | Default | Description | -|--------------|-----------------------|-------------------------------------------------|--------------|-------------------------------------------------------| -| `authMethod` | Authentication method | 0: Automatic
1: Hash-based
2: Token-based | 0: Automatic | A method used to authenticate user in the Miniserver. | +| ID | Name | Values | Default | Description | +|-----------------|-----------------------|-------------------------------------------------|--------------|-------------------------------------------------------| +| `authMethod` | Authentication method | 0: Automatic
1: Hash-based
2: Token-based | 0: Automatic | A method used to authenticate user in the Miniserver. | +| `webSocketType` | WebSocket protocol | 0: Automatic
1: Force HTTPS
2: Force HTTP | 0: Automatic | Communication protocol used for WebSocket connection. | ### Timeouts diff --git a/bundles/org.openhab.binding.loxone/src/main/java/org/openhab/binding/loxone/internal/LxBindingConfiguration.java b/bundles/org.openhab.binding.loxone/src/main/java/org/openhab/binding/loxone/internal/LxBindingConfiguration.java index 1cebb8770567a..5d817bd2f4021 100644 --- a/bundles/org.openhab.binding.loxone/src/main/java/org/openhab/binding/loxone/internal/LxBindingConfiguration.java +++ b/bundles/org.openhab.binding.loxone/src/main/java/org/openhab/binding/loxone/internal/LxBindingConfiguration.java @@ -24,9 +24,13 @@ public class LxBindingConfiguration { */ public String host; /** - * Port of web service of the Miniserver + * Port of HTTP web service of the Miniserver */ public int port; + /** + * Port of HTTPS web service of the Miniserver + */ + public int httpsPort; /** * User name used to log into the Miniserver */ @@ -76,4 +80,8 @@ public class LxBindingConfiguration { * Authentication method (0-auto, 1-hash, 2-token) */ public int authMethod; + /** + * WebSocket connection type (0-auto, 1-HTTPS, 2-HTTP) + */ + public int webSocketType; } diff --git a/bundles/org.openhab.binding.loxone/src/main/java/org/openhab/binding/loxone/internal/LxServerHandler.java b/bundles/org.openhab.binding.loxone/src/main/java/org/openhab/binding/loxone/internal/LxServerHandler.java index aa6b9d58dedc0..17517e2b3faa5 100644 --- a/bundles/org.openhab.binding.loxone/src/main/java/org/openhab/binding/loxone/internal/LxServerHandler.java +++ b/bundles/org.openhab.binding.loxone/src/main/java/org/openhab/binding/loxone/internal/LxServerHandler.java @@ -32,6 +32,7 @@ import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; +import org.eclipse.jetty.util.ssl.SslContextFactory; import org.eclipse.jetty.util.thread.QueuedThreadPool; import org.eclipse.jetty.websocket.client.ClientUpgradeRequest; import org.eclipse.jetty.websocket.client.WebSocketClient; @@ -72,7 +73,7 @@ public class LxServerHandler extends BaseThingHandler implements LxServerHandlerApi { private static final String SOCKET_URL = "/ws/rfc6455"; - private static final String CMD_CFG_API = "jdev/cfg/api"; + private static final String CMD_CFG_API = "jdev/cfg/apiKey"; private static final Gson GSON; @@ -139,6 +140,7 @@ public LxServerHandler(Thing thing, LxDynamicStateDescriptionProvider provider) @Override public void handleCommand(ChannelUID channelUID, Command command) { + logger.debug("[{}] Handle command: channelUID={}, command={}", debugId, channelUID, command); if (command instanceof RefreshType) { updateChannelState(channelUID); return; @@ -146,6 +148,8 @@ public void handleCommand(ChannelUID channelUID, Command command) { try { LxControl control = channels.get(channelUID); if (control != null) { + logger.debug("[{}] Dispatching command to control UUID={}, name={}", debugId, control.getUuid(), + control.getName()); control.handleCommand(channelUID, command); } else { logger.error("[{}] Received command {} for unknown control.", debugId, command); @@ -182,7 +186,7 @@ public void initialize() { jettyThreadPool.setDaemon(true); socket = new LxWebSocket(debugId, this, bindingConfig, host); - wsClient = new WebSocketClient(); + wsClient = new WebSocketClient(new SslContextFactory.Client(true)); wsClient.setExecutor(jettyThreadPool); if (debugId > 1) { reconnectDelay.set(0); @@ -478,8 +482,16 @@ private void updateStateValue(LxStateUpdate update) { Map perStateUuid = states.get(update.getUuid()); if (perStateUuid != null) { perStateUuid.forEach((controlUuid, state) -> { + logger.debug("[{}] State update (UUID={}, value={}) dispatched to control UUID={}, state name={}", + debugId, update.getUuid(), update.getValue(), controlUuid, state.getName()); + state.setStateValue(update.getValue()); }); + if (perStateUuid.size() == 0) { + logger.debug("[{}] State update UUID={} has empty controls table", debugId, update.getUuid()); + } + } else { + logger.debug("[{}] State update UUID={} has no controls table", debugId, update.getUuid()); } } @@ -553,16 +565,36 @@ private boolean connect() { * Try to read CfgApi structure from the miniserver. It contains serial number and firmware version. If it can't * be read this is not a fatal issue, we will assume most recent version running. */ + boolean httpsCapable = false; String message = socket.httpGet(CMD_CFG_API); if (message != null) { LxResponse resp = socket.getResponse(message); if (resp != null) { - socket.setFwVersion(GSON.fromJson(resp.getValueAsString(), LxResponse.LxResponseCfgApi.class).version); + LxResponse.LxResponseCfgApi apiResp = GSON.fromJson(resp.getValueAsString(), + LxResponse.LxResponseCfgApi.class); + if (apiResp != null) { + socket.setFwVersion(apiResp.version); + httpsCapable = apiResp.httpsStatus != null && apiResp.httpsStatus == 1; + } } } else { logger.debug("[{}] Http get failed for API config request.", debugId); } + switch (bindingConfig.webSocketType) { + case 0: + // keep automatically determined option + break; + case 1: + logger.debug("[{}] Forcing HTTPS websocket connection.", debugId); + httpsCapable = true; + break; + case 2: + logger.debug("[{}] Forcing HTTP websocket connection.", debugId); + httpsCapable = false; + break; + } + try { wsClient.start(); @@ -570,7 +602,14 @@ private boolean connect() { // without this zero timeout, jetty will wait 30 seconds for stopping the client to eventually fail // with the timeout it is immediate and all threads end correctly jettyThreadPool.setStopTimeout(0); - URI target = new URI("ws://" + host.getHostAddress() + ":" + bindingConfig.port + SOCKET_URL); + URI target; + if (httpsCapable) { + target = new URI("wss://" + host.getHostAddress() + ":" + bindingConfig.httpsPort + SOCKET_URL); + socket.setHttps(true); + } else { + target = new URI("ws://" + host.getHostAddress() + ":" + bindingConfig.port + SOCKET_URL); + socket.setHttps(false); + } ClientUpgradeRequest request = new ClientUpgradeRequest(); request.setSubProtocols("remotecontrol"); diff --git a/bundles/org.openhab.binding.loxone/src/main/java/org/openhab/binding/loxone/internal/LxWebSocket.java b/bundles/org.openhab.binding.loxone/src/main/java/org/openhab/binding/loxone/internal/LxWebSocket.java index 79867bd910adc..298e53f154579 100644 --- a/bundles/org.openhab.binding.loxone/src/main/java/org/openhab/binding/loxone/internal/LxWebSocket.java +++ b/bundles/org.openhab.binding.loxone/src/main/java/org/openhab/binding/loxone/internal/LxWebSocket.java @@ -78,6 +78,7 @@ public class LxWebSocket { private Session session; private String fwVersion; + private boolean httpsSession = false; private ScheduledFuture timeout; private LxWsBinaryHeader header; private LxWsSecurity security; @@ -455,9 +456,20 @@ void sendKeepAlive() { * @param fwVersion Miniserver firmware version */ void setFwVersion(String fwVersion) { + logger.debug("[{}] Firmware version: {}", debugId, fwVersion); this.fwVersion = fwVersion; } + /** + * Sets information if session is over HTTPS or HTTP protocol + * + * @param httpsSession true when HTTPS session + */ + void setHttps(boolean httpsSession) { + logger.debug("[{}] HTTPS session: {}", debugId, httpsSession); + this.httpsSession = httpsSession; + } + /** * Start a timer to wait for a Miniserver response to an action sent from the binding. * When timer expires, connection is removed and server error is reported. Further connection attempt can be made @@ -536,7 +548,7 @@ private boolean sendCmdNoResp(String command, boolean encrypt) { try { if (session != null) { String encrypted; - if (encrypt) { + if (encrypt && !httpsSession) { encrypted = security.encrypt(command); logger.debug("[{}] Sending encrypted string: {}", debugId, command); logger.debug("[{}] Encrypted: {}", debugId, encrypted); @@ -580,7 +592,9 @@ private void processResponse(String message) { } logger.debug("[{}] Response: {}", debugId, message.trim()); String control = resp.getCommand().trim(); - control = security.decryptControl(control); + if (!httpsSession) { + control = security.decryptControl(control); + } // for some reason the responses to some commands starting with jdev begin with dev, not jdev // this seems to be a bug in the Miniserver if (control.startsWith("dev/")) { diff --git a/bundles/org.openhab.binding.loxone/src/main/java/org/openhab/binding/loxone/internal/controls/LxControl.java b/bundles/org.openhab.binding.loxone/src/main/java/org/openhab/binding/loxone/internal/controls/LxControl.java index 2533b90f8ce38..f16c04596fa3d 100644 --- a/bundles/org.openhab.binding.loxone/src/main/java/org/openhab/binding/loxone/internal/controls/LxControl.java +++ b/bundles/org.openhab.binding.loxone/src/main/java/org/openhab/binding/loxone/internal/controls/LxControl.java @@ -264,6 +264,8 @@ public final void handleCommand(ChannelUID channelId, Command command) throws IO Callbacks c = callbacks.get(channelId); if (c != null && c.commandCallback != null) { c.commandCallback.handleCommand(command); + } else { + logger.debug("Control UUID={} has no command handler", getUuid()); } } diff --git a/bundles/org.openhab.binding.loxone/src/main/java/org/openhab/binding/loxone/internal/types/LxResponse.java b/bundles/org.openhab.binding.loxone/src/main/java/org/openhab/binding/loxone/internal/types/LxResponse.java index 45af9c7da117a..c9e4a37c6223d 100644 --- a/bundles/org.openhab.binding.loxone/src/main/java/org/openhab/binding/loxone/internal/types/LxResponse.java +++ b/bundles/org.openhab.binding.loxone/src/main/java/org/openhab/binding/loxone/internal/types/LxResponse.java @@ -39,6 +39,8 @@ public class LxResponse { public class LxResponseCfgApi { public String snr; public String version; + public String key; + public Integer httpsStatus; } /** diff --git a/bundles/org.openhab.binding.loxone/src/main/resources/OH-INF/thing/thing-types.xml b/bundles/org.openhab.binding.loxone/src/main/resources/OH-INF/thing/thing-types.xml index 52ef9ccf6c210..72330526c8f9f 100644 --- a/bundles/org.openhab.binding.loxone/src/main/resources/OH-INF/thing/thing-types.xml +++ b/bundles/org.openhab.binding.loxone/src/main/resources/OH-INF/thing/thing-types.xml @@ -39,10 +39,15 @@ Host address or IP of the Loxone Miniserver - - Web interface port of the Loxone Miniserver + + HTTP Web interface port of the Loxone Miniserver 80 + + + HTTPS Web interface port of the Loxone Miniserver + 443 + @@ -71,6 +76,18 @@ true + + + Protocol used to communicate over WebSocket to the Miniserver + 0 + + + + + + true + true + Time between binding initialization and first connection attempt (seconds, 0-120)