[loxone] Support for HTTPS websocket connections. (#10185)
Signed-off-by: Pawel Pieczul <pieczul@gmail.com>
This commit is contained in:
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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");
|
||||
|
||||
|
||||
@@ -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/")) {
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -39,6 +39,8 @@ public class LxResponse {
|
||||
public class LxResponseCfgApi {
|
||||
public String snr;
|
||||
public String version;
|
||||
public String key;
|
||||
public Integer httpsStatus;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -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>
|
||||
|
||||
Reference in New Issue
Block a user