[loxone] Support for HTTPS websocket connections. (#10185)

Signed-off-by: Pawel Pieczul <pieczul@gmail.com>
This commit is contained in:
Pawel Pieczul
2021-02-25 22:46:04 +01:00
committed by GitHub
parent e86cc6b219
commit 3a19b29662
7 changed files with 98 additions and 12 deletions

View File

@@ -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;
}

View File

@@ -32,6 +32,7 @@ import java.util.concurrent.atomic.AtomicInteger;
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 @@ import com.google.gson.GsonBuilder;
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 class LxServerHandler extends BaseThingHandler implements LxServerHandler
@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 class LxServerHandler extends BaseThingHandler implements LxServerHandler
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 class LxServerHandler extends BaseThingHandler implements LxServerHandler
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 @@ public class LxServerHandler extends BaseThingHandler implements LxServerHandler
Map<LxUuid, LxState> 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 @@ public class LxServerHandler extends BaseThingHandler implements LxServerHandler
* 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 @@ public class LxServerHandler extends BaseThingHandler implements LxServerHandler
// 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");

View File

@@ -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 @@ public class LxWebSocket {
* @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 @@ public class LxWebSocket {
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 @@ public class LxWebSocket {
}
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/")) {

View File

@@ -264,6 +264,8 @@ public class LxControl {
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());
}
}

View File

@@ -39,6 +39,8 @@ public class LxResponse {
public class LxResponseCfgApi {
public String snr;
public String version;
public String key;
public Integer httpsStatus;
}
/**

View File

@@ -39,10 +39,15 @@
<description>Host address or IP of the Loxone Miniserver</description>
</parameter>
<parameter name="port" type="integer" min="1" max="65535" groupName="miniserver">
<label>Port</label>
<description>Web interface port of the Loxone Miniserver</description>
<label>HTTP Port</label>
<description>HTTP Web interface port of the Loxone Miniserver</description>
<default>80</default>
</parameter>
<parameter name="httpsPort" type="integer" min="1" max="65535" groupName="miniserver">
<label>HTTPS Port</label>
<description>HTTPS Web interface port of the Loxone Miniserver</description>
<default>443</default>
</parameter>
<parameter name="authMethod" type="integer" min="0" max="2" groupName="security">
<label>Authorization Method</label>
@@ -71,6 +76,18 @@
<advanced>true</advanced>
</parameter>
<parameter name="webSocketType" type="integer" min="0" max="2" groupName="security">
<label>WebSocket Connection Type</label>
<description>Protocol used to communicate over WebSocket to the Miniserver</description>
<default>0</default>
<options>
<option value="0">Automatic</option>
<option value="1">Force HTTPS</option>
<option value="2">Force HTTP</option>
</options>
<limitToOptions>true</limitToOptions>
<advanced>true</advanced>
</parameter>
<parameter name="firstConDelay" type="integer" min="0" max="120" groupName="timeouts">
<label>First Connection Delay</label>
<description>Time between binding initialization and first connection attempt (seconds, 0-120)</description>