[velux] fix concurrency bugs, other minor issues, update readme.md (replaces #8493) (#8520)

Signed-off-by: Andrew Fiddian-Green <software@whitebear.ch>
This commit is contained in:
Andrew Fiddian-Green
2020-09-28 17:27:46 +01:00
committed by GitHub
parent d5f6bd326c
commit 64a713e74a
23 changed files with 588 additions and 486 deletions

View File

@@ -145,4 +145,7 @@ public class VeluxBindingConstants {
// Critical issues to be reported will use the following message
public static final String LOGGING_CONTACT = "Please report to maintainer: ";
public static final String UNKNOWN_THING_TYPE_ID = "FAILED";
public static final String UNKNOWN_IP_ADDRESS = "xxx.xxx.xxx.xxx";
}

View File

@@ -39,8 +39,8 @@ class JClogin extends Login implements JsonBridgeCommunicationProtocol {
private static final String URL = "/api/v1/auth";
private static final String DESCRIPTION = "authenticate / login";
private static Request request = new Request();
private static Response response = new Response();
private Request request = new Request();
private Response response = new Response();
/*
* Message Objects

View File

@@ -83,19 +83,19 @@ import org.slf4j.LoggerFactory;
class JsonBridgeAPI implements BridgeAPI {
private final Logger logger = LoggerFactory.getLogger(JsonBridgeAPI.class);
private static final GetDeviceStatus GETDEVICESTATUS = new JCgetDeviceStatus();
private static final GetFirmware GETFIRMWARE = new JCgetFirmware();
private static final GetLANConfig GETLANCONFIG = new JCgetLANConfig();
private static final GetProducts GETPRODUCTS = new JCgetProducts();
private static final GetScenes GETSCENES = new JCgetScenes();
private static final GetWLANConfig GETWLANCONFIG = new JCgetWLANConfig();
private static final Login LOGIN = new JClogin();
private static final Logout LOGOUT = new JClogout();
private static final RunProductDiscovery RUNPRODUCTDISCOVERY = new JCrunProductDiscovery();
private static final RunProductIdentification RUNPRODUCTIDENTIFICATION = new JCrunProductIdentification();
private static final RunProductSearch RUNPRODUCTSEARCH = new JCrunProductSearch();
private static final RunScene RUNSCENE = new JCrunScene();
private static final SetSceneVelocity SETSCENEVELOCITY = new JCsetSceneVelocity();
private final GetDeviceStatus jsonGetDeviceStatus = new JCgetDeviceStatus();
private final GetFirmware jsonGetFirmware = new JCgetFirmware();
private final GetLANConfig jsonGetLanConfig = new JCgetLANConfig();
private final GetProducts jsonGetProducts = new JCgetProducts();
private final GetScenes jsonGetScenes = new JCgetScenes();
private final GetWLANConfig jsonGetWLanConfig = new JCgetWLANConfig();
private final Login jsonLogin = new JClogin();
private final Logout jsonLogout = new JClogout();
private final RunProductDiscovery jsonRunProductDiscovery = new JCrunProductDiscovery();
private final RunProductIdentification jsonRunProductIdentification = new JCrunProductIdentification();
private final RunProductSearch jsonRunProductSearch = new JCrunProductSearch();
private final RunScene jsonRunScene = new JCrunScene();
private final SetSceneVelocity jsonSetSceneVelocity = new JCsetSceneVelocity();
/**
* Constructor.
@@ -113,12 +113,12 @@ class JsonBridgeAPI implements BridgeAPI {
@Override
public GetDeviceStatus getDeviceStatus() {
return GETDEVICESTATUS;
return jsonGetDeviceStatus;
}
@Override
public GetFirmware getFirmware() {
return GETFIRMWARE;
return jsonGetFirmware;
}
@Override
@@ -128,7 +128,7 @@ class JsonBridgeAPI implements BridgeAPI {
@Override
public GetLANConfig getLANConfig() {
return GETLANCONFIG;
return jsonGetLanConfig;
}
@Override
@@ -148,27 +148,27 @@ class JsonBridgeAPI implements BridgeAPI {
@Override
public GetProducts getProducts() {
return GETPRODUCTS;
return jsonGetProducts;
}
@Override
public GetScenes getScenes() {
return GETSCENES;
return jsonGetScenes;
}
@Override
public GetWLANConfig getWLANConfig() {
return GETWLANCONFIG;
return jsonGetWLanConfig;
}
@Override
public Login login() {
return LOGIN;
return jsonLogin;
}
@Override
public Logout logout() {
return LOGOUT;
return jsonLogout;
}
@Override
@@ -178,22 +178,22 @@ class JsonBridgeAPI implements BridgeAPI {
@Override
public RunProductDiscovery runProductDiscovery() {
return RUNPRODUCTDISCOVERY;
return jsonRunProductDiscovery;
}
@Override
public RunProductIdentification runProductIdentification() {
return RUNPRODUCTIDENTIFICATION;
return jsonRunProductIdentification;
}
@Override
public RunProductSearch runProductSearch() {
return RUNPRODUCTSEARCH;
return jsonRunProductSearch;
}
@Override
public RunScene runScene() {
return RUNSCENE;
return jsonRunScene;
}
@Override
@@ -203,6 +203,6 @@ class JsonBridgeAPI implements BridgeAPI {
@Override
public SetSceneVelocity setSceneVelocity() {
return SETSCENEVELOCITY;
return jsonSetSceneVelocity;
}
}

View File

@@ -83,25 +83,25 @@ import org.slf4j.LoggerFactory;
class SlipBridgeAPI implements BridgeAPI {
private final Logger logger = LoggerFactory.getLogger(SlipBridgeAPI.class);
private static final GetDeviceStatus GETDEVICESTATUS = new SCgetDeviceStatus();
private static final GetFirmware GETFIRMWARE = new SCgetFirmware();
private static final GetHouseStatus GETHOUSESTATUS = new SCgetHouseStatus();
private static final GetLANConfig GETLANCONFIG = new SCgetLANConfig();
private static final GetProduct GETPRODUCT = new SCgetProduct();
private static final GetProductLimitation GETPRODUCTLIMITATION = new SCgetLimitation();
private static final GetProducts GETPRODUCTS = new SCgetProducts();
private static final GetScenes GETSCENES = new SCgetScenes();
private static final GetWLANConfig GETWLANCONFIG = new SCgetWLANConfig();
private static final Login LOGIN = new SClogin();
private static final Logout LOGOUT = new SClogout();
private static final RunProductCommand RUNPRODUCTCOMMAND = new SCrunProductCommand();
private static final RunProductDiscovery RUNPRODUCTDISCOVERY = new SCrunProductDiscovery();
private static final RunProductIdentification RUNPRODUCTIDENTIFICATION = new SCrunProductIdentification();
private static final RunProductSearch RUNPRODUCTSEARCH = new SCrunProductSearch();
private static final RunScene RUNSCENE = new SCrunScene();
private static final SetHouseStatusMonitor SETHOUSESTATUSMONITOR = new SCsetHouseStatusMonitor();
private static final SetProductLimitation SETPRODUCTLIMITATION = new SCsetLimitation();
private static final SetSceneVelocity SETSCENEVELOCITY = new SCsetSceneVelocity();
private final GetDeviceStatus slipGetDeviceStatus = new SCgetDeviceStatus();
private final GetFirmware slipGetFirmware = new SCgetFirmware();
private final GetHouseStatus slipGetHouseStatus = new SCgetHouseStatus();
private final GetLANConfig slipGetLanConfig = new SCgetLANConfig();
private final GetProduct slipGetProduct = new SCgetProduct();
private final GetProductLimitation slipGetProductLimitation = new SCgetLimitation();
private final GetProducts slipGetProducts = new SCgetProducts();
private final GetScenes slipGetScenes = new SCgetScenes();
private final GetWLANConfig slipGetWLanConfig = new SCgetWLANConfig();
private final Login slipLogin = new SClogin();
private final Logout slipLogout = new SClogout();
private final RunProductCommand slipRunProductCommand = new SCrunProductCommand();
private final RunProductDiscovery slipRunProductDiscovery = new SCrunProductDiscovery();
private final RunProductIdentification slipRunProductIdentification = new SCrunProductIdentification();
private final RunProductSearch slipRunProductSearch = new SCrunProductSearch();
private final RunScene slipRunScene = new SCrunScene();
private final SetHouseStatusMonitor slipSetHouseMonitor = new SCsetHouseStatusMonitor();
private final SetProductLimitation slipSetProductLimitation = new SCsetLimitation();
private final SetSceneVelocity slipSetSceneVelocity = new SCsetSceneVelocity();
/**
* Constructor.
@@ -118,96 +118,96 @@ class SlipBridgeAPI implements BridgeAPI {
@Override
public GetDeviceStatus getDeviceStatus() {
return GETDEVICESTATUS;
return slipGetDeviceStatus;
}
@Override
public GetFirmware getFirmware() {
return GETFIRMWARE;
return slipGetFirmware;
}
@Override
public @Nullable GetHouseStatus getHouseStatus() {
return GETHOUSESTATUS;
return slipGetHouseStatus;
}
@Override
public GetLANConfig getLANConfig() {
return GETLANCONFIG;
return slipGetLanConfig;
}
@Override
public @Nullable GetProduct getProduct() {
return GETPRODUCT;
return slipGetProduct;
}
@Override
public @Nullable GetProductLimitation getProductLimitation() {
return GETPRODUCTLIMITATION;
return slipGetProductLimitation;
}
@Override
public @Nullable SetProductLimitation setProductLimitation() {
return SETPRODUCTLIMITATION;
return slipSetProductLimitation;
}
@Override
public GetProducts getProducts() {
return GETPRODUCTS;
return slipGetProducts;
}
@Override
public GetScenes getScenes() {
return GETSCENES;
return slipGetScenes;
}
@Override
public GetWLANConfig getWLANConfig() {
return GETWLANCONFIG;
return slipGetWLanConfig;
}
@Override
public Login login() {
return LOGIN;
return slipLogin;
}
@Override
public Logout logout() {
return LOGOUT;
return slipLogout;
}
@Override
public @Nullable RunProductCommand runProductCommand() {
return RUNPRODUCTCOMMAND;
return slipRunProductCommand;
}
@Override
public RunProductDiscovery runProductDiscovery() {
return RUNPRODUCTDISCOVERY;
return slipRunProductDiscovery;
}
@Override
public RunProductIdentification runProductIdentification() {
return RUNPRODUCTIDENTIFICATION;
return slipRunProductIdentification;
}
@Override
public RunProductSearch runProductSearch() {
return RUNPRODUCTSEARCH;
return slipRunProductSearch;
}
@Override
public RunScene runScene() {
return RUNSCENE;
return slipRunScene;
}
@Override
public @Nullable SetHouseStatusMonitor setHouseStatusMonitor() {
return SETHOUSESTATUSMONITOR;
return slipSetHouseMonitor;
}
@Override
public SetSceneVelocity setSceneVelocity() {
return SETSCENEVELOCITY;
return slipSetSceneVelocity;
}
}

View File

@@ -193,7 +193,8 @@ public class SlipVeluxBridge extends VeluxBridge {
*/
private synchronized boolean bridgeDirectCommunicate(SlipBridgeCommunicationProtocol communication,
boolean useAuthentication) {
logger.trace("bridgeDirectCommunicate({},{}authenticated) called.", communication.name(),
String host = this.bridgeInstance.veluxBridgeConfiguration().ipAddress;
logger.trace("bridgeDirectCommunicate({},{}authenticated) on {} called.", host, communication.name(),
useAuthentication ? "" : "un");
assert this.bridgeInstance.veluxBridgeConfiguration().protocol.contentEquals("slip");
@@ -213,15 +214,15 @@ public class SlipVeluxBridge extends VeluxBridge {
Threads.findDeadlocked();
}
logger.debug("bridgeDirectCommunicate({},{}authenticated) initiated by {}.", commandString,
logger.debug("bridgeDirectCommunicate({},{}authenticated) on {} initiated by {}.", host, commandString,
useAuthentication ? "" : "un", Thread.currentThread());
boolean success = false;
communication: do {
if (communicationStartInMSecs + COMMUNICATION_TIMEOUT_MSECS < System.currentTimeMillis()) {
logger.warn(
"{} bridgeDirectCommunicate({}): communication handshake failed (unexpected sequence of requests/responses).",
VeluxBindingConstants.BINDING_VALUES_SEPARATOR, communication.name());
"{} bridgeDirectCommunicate({}) on {}: communication handshake failed (unexpected sequence of requests/responses).",
VeluxBindingConstants.BINDING_VALUES_SEPARATOR, communication.name(), host);
break;
}
@@ -234,67 +235,71 @@ public class SlipVeluxBridge extends VeluxBridge {
}
// Normal processing
logger.trace("bridgeDirectCommunicate(): working on request {} with {} bytes of data.", commandString,
data.length);
logger.trace("bridgeDirectCommunicate() on {}: working on request {} with {} bytes of data.", host,
commandString, data.length);
byte[] sendBytes = emptyPacket;
if (Command.get(command) == Command.GW_OPENHAB_RECEIVEONLY) {
logger.trace(
"bridgeDirectCommunicate(): special command: determine whether there is any message waiting.");
"bridgeDirectCommunicate() on {}: special command: determine whether there is any message waiting.",
host);
logger.trace("bridgeDirectCommunicate(): check for a waiting message.");
if (!connection.isMessageAvailable()) {
logger.trace("bridgeDirectCommunicate(): no message waiting, aborting.");
logger.trace("bridgeDirectCommunicate() on {}: no message waiting, aborting.", host);
break communication;
}
logger.trace("bridgeDirectCommunicate(): there is a message waiting.");
logger.trace("bridgeDirectCommunicate() on {}: there is a message waiting.", host);
} else {
SlipEncoding t = new SlipEncoding(command, data);
if (!t.isValid()) {
logger.warn("bridgeDirectCommunicate(): SlipEncoding() failed, aborting.");
logger.warn("bridgeDirectCommunicate() on {}: SlipEncoding() failed, aborting.", host);
break;
}
logger.trace("bridgeDirectCommunicate(): transportEncoding={}.", t.toString());
logger.trace("bridgeDirectCommunicate() on {}: transportEncoding={}.", host, t.toString());
sendBytes = new SlipRFC1055().encode(t.toMessage());
}
do {
if (communicationStartInMSecs + COMMUNICATION_TIMEOUT_MSECS < System.currentTimeMillis()) {
logger.warn("bridgeDirectCommunicate(): receive takes too long. Please report to maintainer.");
logger.warn("bridgeDirectCommunicate() on {}: receive takes too long. Please report to maintainer.",
host);
break communication;
}
byte[] receivedPacket;
try {
if (sendBytes.length > 0) {
logger.trace("bridgeDirectCommunicate(): sending {} bytes.", sendBytes.length);
logger.trace("bridgeDirectCommunicate() on {}: sending {} bytes.", host, sendBytes.length);
if (isProtocolTraceEnabled) {
logger.info("Sending command {}.", commandString);
}
} else {
logger.trace("bridgeDirectCommunicate(): initiating receive-only.");
logger.trace("bridgeDirectCommunicate() on {}: initiating receive-only.", host);
}
// (Optionally) Send and receive packet.
receivedPacket = connection.io(this.bridgeInstance, sendBytes);
// Once being sent, it should never be sent again
sendBytes = emptyPacket;
} catch (Exception e) {
logger.warn("bridgeDirectCommunicate(): connection.io returns {}", e.getMessage());
logger.warn("bridgeDirectCommunicate() on {}: connection.io returns {}", host, e.getMessage());
break communication;
}
logger.trace("bridgeDirectCommunicate(): received packet {}.", new Packet(receivedPacket).toString());
logger.trace("bridgeDirectCommunicate() on {}: received packet {}.", host,
new Packet(receivedPacket).toString());
byte[] response;
try {
response = new SlipRFC1055().decode(receivedPacket);
} catch (ParseException e) {
logger.warn("bridgeDirectCommunicate(): method SlipRFC1055() raised a decoding error: {}.",
e.getMessage());
logger.warn("bridgeDirectCommunicate() on {}: method SlipRFC1055() raised a decoding error: {}.",
host, e.getMessage());
break communication;
}
SlipEncoding tr = new SlipEncoding(response);
if (!tr.isValid()) {
logger.warn("bridgeDirectCommunicate(): method SlipEncoding() raised a decoding error.");
logger.warn("bridgeDirectCommunicate() on {}: method SlipEncoding() raised a decoding error.",
host);
break communication;
}
short responseCommand = tr.getCommand();
byte[] responseData = tr.getData();
logger.debug("bridgeDirectCommunicate(): working on response {} with {} bytes of data.",
logger.debug("bridgeDirectCommunicate() on {}: working on response {} with {} bytes of data.", host,
Command.get(responseCommand).toString(), responseData.length);
if (isProtocolTraceEnabled) {
logger.info("Received answer {}.", Command.get(responseCommand).toString());
@@ -302,63 +307,66 @@ public class SlipVeluxBridge extends VeluxBridge {
// Handle some common (unexpected) answers
switch (Command.get(responseCommand)) {
case GW_NODE_INFORMATION_CHANGED_NTF:
logger.trace("bridgeDirectCommunicate(): received GW_NODE_INFORMATION_CHANGED_NTF.");
logger.trace("bridgeDirectCommunicate(): continue with receiving.");
logger.trace("bridgeDirectCommunicate() on {}: received GW_NODE_INFORMATION_CHANGED_NTF.",
host);
logger.trace("bridgeDirectCommunicate() on {}: continue with receiving.", host);
continue;
case GW_NODE_STATE_POSITION_CHANGED_NTF:
logger.trace(
"bridgeDirectCommunicate(): received GW_NODE_STATE_POSITION_CHANGED_NTF, special processing of this packet.");
"bridgeDirectCommunicate() on {}: received GW_NODE_STATE_POSITION_CHANGED_NTF, special processing of this packet.",
host);
SCgetHouseStatus receiver = new SCgetHouseStatus();
receiver.setResponse(responseCommand, responseData, isSequentialEnforced);
if (receiver.isCommunicationSuccessful()) {
logger.trace("bridgeDirectCommunicate(): existingProducts().update() called.");
logger.trace("bridgeDirectCommunicate() on {}: existingProducts().update() called.", host);
bridgeInstance.existingProducts().update(new ProductBridgeIndex(receiver.getNtfNodeID()),
receiver.getNtfState(), receiver.getNtfCurrentPosition(), receiver.getNtfTarget());
}
logger.trace("bridgeDirectCommunicate(): continue with receiving.");
logger.trace("bridgeDirectCommunicate() on {}: continue with receiving.", host);
continue;
case GW_ERROR_NTF:
switch (responseData[0]) {
case 0:
logger.warn(
"bridgeDirectCommunicate(): received GW_ERROR_NTF on {} (Not further defined error), aborting.",
commandString);
"bridgeDirectCommunicate() on {}: received GW_ERROR_NTF on {} (Not further defined error), aborting.",
host, commandString);
break communication;
case 1:
logger.warn(
"bridgeDirectCommunicate(): received GW_ERROR_NTF (Unknown Command or command is not accepted at this state) on {}, aborting.",
commandString);
"bridgeDirectCommunicate() on {}: received GW_ERROR_NTF (Unknown Command or command is not accepted at this state) on {}, aborting.",
host, commandString);
break communication;
case 2:
logger.warn(
"bridgeDirectCommunicate(): received GW_ERROR_NTF (ERROR on Frame Structure) on {}, aborting.",
commandString);
"bridgeDirectCommunicate() on {}: received GW_ERROR_NTF (ERROR on Frame Structure) on {}, aborting.",
host, commandString);
break communication;
case 7:
logger.trace(
"bridgeDirectCommunicate(): received GW_ERROR_NTF (Busy. Try again later) on {}, retrying.",
commandString);
"bridgeDirectCommunicate() on {}: received GW_ERROR_NTF (Busy. Try again later) on {}, retrying.",
host, commandString);
sendBytes = emptyPacket;
continue;
case 8:
logger.warn(
"bridgeDirectCommunicate(): received GW_ERROR_NTF (Bad system table index) on {}, aborting.",
commandString);
"bridgeDirectCommunicate() on {}: received GW_ERROR_NTF (Bad system table index) on {}, aborting.",
host, commandString);
break communication;
case 12:
logger.warn(
"bridgeDirectCommunicate(): received GW_ERROR_NTF (Not authenticated) on {}, aborting.",
commandString);
"bridgeDirectCommunicate() on {}: received GW_ERROR_NTF (Not authenticated) on {}, aborting.",
host, commandString);
resetAuthentication();
break communication;
default:
logger.warn("bridgeDirectCommunicate(): received GW_ERROR_NTF ({}) on {}, aborting.",
responseData[0], commandString);
logger.warn(
"bridgeDirectCommunicate() on {}: received GW_ERROR_NTF ({}) on {}, aborting.",
host, responseData[0], commandString);
break communication;
}
case GW_ACTIVATION_LOG_UPDATED_NTF:
logger.info("bridgeDirectCommunicate(): received GW_ACTIVATION_LOG_UPDATED_NTF.");
logger.trace("bridgeDirectCommunicate(): continue with receiving.");
logger.info("bridgeDirectCommunicate() on {}: received GW_ACTIVATION_LOG_UPDATED_NTF.", host);
logger.trace("bridgeDirectCommunicate() on {}: continue with receiving.", host);
continue;
case GW_COMMAND_RUN_STATUS_NTF:
@@ -366,19 +374,21 @@ public class SlipVeluxBridge extends VeluxBridge {
case GW_SESSION_FINISHED_NTF:
if (!isSequentialEnforced) {
logger.trace(
"bridgeDirectCommunicate(): response ignored due to activated parallelism, continue with receiving.");
"bridgeDirectCommunicate() on {}: response ignored due to activated parallelism, continue with receiving.",
host);
continue;
}
default:
}
logger.trace("bridgeDirectCommunicate(): passes back command {} and data {}.",
logger.trace("bridgeDirectCommunicate() on {}: passes back command {} and data {}.", host,
new CommandNumber(responseCommand).toString(), new Packet(responseData).toString());
communication.setResponse(responseCommand, responseData, isSequentialEnforced);
} while (!communication.isCommunicationFinished());
success = communication.isCommunicationSuccessful();
} while (false); // communication
logger.debug("bridgeDirectCommunicate({}) returns {}.", commandString, success ? "success" : "failure");
logger.debug("bridgeDirectCommunicate({}) on {}: returns {}.", commandString, host,
success ? "success" : "failure");
return success;
}
}

View File

@@ -16,6 +16,7 @@ import java.io.IOException;
import java.net.ConnectException;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.openhab.binding.velux.internal.VeluxBindingConstants;
import org.openhab.binding.velux.internal.bridge.VeluxBridgeInstance;
import org.openhab.binding.velux.internal.bridge.slip.utils.Packet;
import org.slf4j.Logger;
@@ -59,6 +60,8 @@ public class Connection {
*/
private SSLconnection connectivity = SSLconnection.UNKNOWN;
private String host = VeluxBindingConstants.UNKNOWN_IP_ADDRESS;
/*
* **************************
* ***** Public Methods *****
@@ -75,7 +78,7 @@ public class Connection {
*/
public synchronized byte[] io(VeluxBridgeInstance bridgeInstance, byte[] request)
throws ConnectException, IOException {
logger.trace("io() called.");
logger.trace("io() on {}: called.", host);
lastCommunicationInMSecs = System.currentTimeMillis();
@@ -88,18 +91,18 @@ public class Connection {
if (!connectivity.isReady()) {
try {
// From configuration
String host = bridgeInstance.veluxBridgeConfiguration().ipAddress;
host = bridgeInstance.veluxBridgeConfiguration().ipAddress;
int port = bridgeInstance.veluxBridgeConfiguration().tcpPort;
int timeoutMsecs = bridgeInstance.veluxBridgeConfiguration().timeoutMsecs;
logger.trace("io(): connecting to {}:{}.", host, port);
logger.trace("io() on {}: connecting to port {}", host, port);
connectivity = new SSLconnection(host, port);
connectivity.setTimeout(timeoutMsecs);
} catch (ConnectException ce) {
throw new ConnectException(String
.format("raised a non-recoverable error during connection setup: %s", ce.getMessage()));
} catch (IOException e) {
logger.warn("io(): raised an error during connection setup: {}.", e.getMessage());
logger.warn("io() on {}: raised an error during connection setup: {}.", host, e.getMessage());
lastIOE = new IOException(String.format("error during connection setup: %s.", e.getMessage()));
continue;
}
@@ -107,65 +110,68 @@ public class Connection {
if (request.length > 0) {
try {
if (logger.isTraceEnabled()) {
logger.trace("io(): sending packet with {} bytes: {}", request.length, new Packet(request));
logger.trace("io() on {}: sending packet with {} bytes: {}", host, request.length,
new Packet(request));
} else {
logger.debug("io(): sending packet of size {}.", request.length);
logger.debug("io() on {}: sending packet of size {}.", host, request.length);
}
if (connectivity.isReady()) {
connectivity.send(request);
}
} catch (IOException e) {
logger.info("io(): raised an error during sending: {}.", e.getMessage());
logger.info("io() on {}: raised an error during sending: {}.", host, e.getMessage());
break;
}
// Give the bridge some time to breathe
if (bridgeInstance.veluxBridgeConfiguration().timeoutMsecs > 0) {
logger.trace("io(): wait time {} msecs.",
logger.trace("io() on {}: wait time {} msecs.", host,
bridgeInstance.veluxBridgeConfiguration().timeoutMsecs);
try {
Thread.sleep(bridgeInstance.veluxBridgeConfiguration().timeoutMsecs);
} catch (InterruptedException ie) {
logger.trace("io() wait interrupted.");
logger.trace("io() on {}: wait interrupted.", host);
}
}
}
byte[] packet = new byte[0];
logger.trace("io(): receiving bytes.");
logger.trace("io() on {}: receiving bytes.", host);
if (connectivity.isReady()) {
packet = connectivity.receive();
}
if (logger.isTraceEnabled()) {
logger.trace("io(): received packet with {} bytes: {}", packet.length, new Packet(packet));
logger.trace("io() on {}: received packet with {} bytes: {}", host, packet.length,
new Packet(packet));
} else {
logger.debug("io(): received packet with {} bytes.", packet.length);
logger.debug("io() on {}: received packet with {} bytes.", host, packet.length);
}
lastSuccessfulCommunicationInMSecs = System.currentTimeMillis();
lastCommunicationInMSecs = lastSuccessfulCommunicationInMSecs;
logger.trace("io() finished.");
logger.trace("io() on {}: finished.", host);
return packet;
} catch (IOException ioe) {
logger.info("io(): Exception occurred during I/O: {}.", ioe.getMessage());
logger.info("io() on {}: Exception occurred during I/O: {}.", host, ioe.getMessage());
lastIOE = ioe;
// Error Retries with Exponential Backoff
long waitTime = ((long) Math.pow(2, retryCount)
* bridgeInstance.veluxBridgeConfiguration().timeoutMsecs);
logger.trace("io(): wait time {} msecs.", waitTime);
logger.trace("io() on {}: wait time {} msecs.", host, waitTime);
try {
Thread.sleep(waitTime);
} catch (InterruptedException ie) {
logger.trace("io(): wait interrupted.");
logger.trace("io() on {}: wait interrupted.", host);
}
}
} while (retryCount++ < bridgeInstance.veluxBridgeConfiguration().retries);
if (retryCount >= bridgeInstance.veluxBridgeConfiguration().retries) {
logger.info("io(): socket I/O failed {} times.", bridgeInstance.veluxBridgeConfiguration().retries);
logger.info("io() on {}: socket I/O failed {} times.", host,
bridgeInstance.veluxBridgeConfiguration().retries);
}
logger.trace("io(): shutting down connection.");
logger.trace("io() on {}: shutting down connection.", host);
if (connectivity.isReady()) {
connectivity.close();
}
logger.trace("io() finishes with failure by throwing exception.");
logger.trace("io() on {}: finishes with failure by throwing exception.", host);
throw lastIOE;
}
@@ -175,7 +181,7 @@ public class Connection {
* @return state as boolean.
*/
public boolean isAlive() {
logger.trace("isAlive(): called.");
logger.trace("isAlive() on {}: called.", host);
return connectivity.isReady();
}
@@ -185,17 +191,17 @@ public class Connection {
* @return state as boolean.
*/
public synchronized boolean isMessageAvailable() {
logger.trace("isMessageAvailable(): called.");
logger.trace("isMessageAvailable() on {}: called.", host);
try {
if ((connectivity.isReady()) && (connectivity.available())) {
logger.trace("isMessageAvailable(): there is a message waiting.");
logger.trace("isMessageAvailable() on {}: there is a message waiting.", host);
return true;
}
} catch (IOException e) {
logger.trace("isMessageAvailable(): lost connection due to {}.", e.getMessage());
logger.trace("isMessageAvailable() on {}: lost connection due to {}.", host, e.getMessage());
resetConnection();
}
logger.trace("isMessageAvailable(): no message waiting.");
logger.trace("isMessageAvailable() on {}: no message waiting.", host);
return false;
}
@@ -223,18 +229,12 @@ public class Connection {
* Resets an open connectivity by closing the socket and resetting the authentication information.
*/
public synchronized void resetConnection() {
logger.trace("resetConnection() called.");
if (connectivity.isReady()) {
logger.trace("resetConnection(): shutting down connection.");
try {
if (connectivity.isReady()) {
connectivity.close();
}
} catch (IOException e) {
logger.info("resetConnection(): raised an error during connection close: {}.", e.getMessage());
}
logger.trace("resetConnection(): clearing authentication token.");
logger.trace("resetConnection() on {}: called.", host);
try {
connectivity.close();
} catch (IOException e) {
logger.info("resetConnection() on {}: raised an error during connection close: {}.", host, e.getMessage());
}
logger.trace("resetConnection() done.");
logger.trace("resetConnection() on {}: done.", host);
}
}

View File

@@ -155,23 +155,43 @@ class SSLconnection {
/**
* Method to pass a message towards the bridge.
* This method gets called when we are initiating a new SLIP transaction.
* <p>
* Note that DataOutputStream and DataInputStream are buffered I/O's. The SLIP protocol requires that prior requests
* should have been fully sent over the socket, and their responses should have been fully read from the buffer
* before the next request is initiated. i.e. Both read and write buffers should already be empty. Nevertheless,
* just in case, we do the following..
* <p>
* 1) Flush from the read buffer any orphan response data that may have been left over from prior transactions, and
* 2) Flush the write buffer directly to the socket to ensure that any exceptions are raised immediately, and the
* KLF starts work immediately
*
* @param packet as Array of bytes to be transmitted towards the bridge via the established connection.
* @throws java.io.IOException in case of a communication I/O failure.
* @throws java.io.IOException in case of a communication I/O failure, and sets 'ready' = false
*/
@SuppressWarnings("null")
synchronized void send(byte[] packet) throws IOException {
logger.trace("send() called, writing {} bytes.", packet.length);
if (!ready || (dOut == null)) {
throw new IOException();
}
dOut.write(packet, 0, packet.length);
if (logger.isTraceEnabled()) {
StringBuilder sb = new StringBuilder();
for (byte b : packet) {
sb.append(String.format("%02X ", b));
try {
if (!ready || (dOut == null) || (dIn == null)) {
throw new IOException();
}
logger.trace("send() finished after having send {} bytes: {}", packet.length, sb.toString());
// flush the read buffer if (exceptionally) there is orphan response data in it
flushReadBufffer();
// copy packet data to the write buffer
dOut.write(packet, 0, packet.length);
// force the write buffer data to be written to the socket
dOut.flush();
if (logger.isTraceEnabled()) {
StringBuilder sb = new StringBuilder();
for (byte b : packet) {
sb.append(String.format("%02X ", b));
}
logger.trace("send() finished after having send {} bytes: {}", packet.length, sb.toString());
}
} catch (IOException e) {
ready = false;
throw e;
}
}
@@ -196,26 +216,31 @@ class SSLconnection {
* Method to get a message from the bridge.
*
* @return <b>packet</b> as Array of bytes as received from the bridge via the established connection.
* @throws java.io.IOException in case of a communication I/O failure.
* @throws java.io.IOException in case of a communication I/O failure, and sets 'ready' = false
*/
synchronized byte[] receive() throws IOException {
logger.trace("receive() called.");
if (!ready || (dIn == null)) {
throw new IOException();
}
byte[] message = new byte[CONNECTION_BUFFER_SIZE];
@SuppressWarnings("null")
int messageLength = dIn.read(message, 0, message.length, ioTimeoutMSecs);
byte[] packet = new byte[messageLength];
System.arraycopy(message, 0, packet, 0, messageLength);
if (logger.isTraceEnabled()) {
StringBuilder sb = new StringBuilder();
for (byte b : packet) {
sb.append(String.format("%02X ", b));
try {
if (!ready || (dIn == null)) {
throw new IOException();
}
logger.trace("receive() finished after having read {} bytes: {}", messageLength, sb.toString());
byte[] message = new byte[CONNECTION_BUFFER_SIZE];
@SuppressWarnings("null")
int messageLength = dIn.read(message, 0, message.length, ioTimeoutMSecs);
byte[] packet = new byte[messageLength];
System.arraycopy(message, 0, packet, 0, messageLength);
if (logger.isTraceEnabled()) {
StringBuilder sb = new StringBuilder();
for (byte b : packet) {
sb.append(String.format("%02X ", b));
}
logger.trace("receive() finished after having read {} bytes: {}", messageLength, sb.toString());
}
return packet;
} catch (IOException e) {
ready = false;
throw e;
}
return packet;
}
/**
@@ -231,16 +256,19 @@ class SSLconnection {
DataInputStreamWithTimeout dInX = dIn;
if (dInX != null) {
dInX.close();
dIn = null;
}
// Just for avoidance of Potential null pointer access
DataOutputStream dOutX = dOut;
if (dOutX != null) {
dOutX.close();
dOut = null;
}
// Just for avoidance of Potential null pointer access
SSLSocket socketX = socket;
if (socketX != null) {
socketX.close();
socket = null;
}
logger.trace("close() finished.");
}
@@ -254,4 +282,32 @@ class SSLconnection {
logger.debug("setTimeout() set timeout to {} milliseconds.", timeoutMSecs);
ioTimeoutMSecs = timeoutMSecs;
}
/**
* Method to flush the input buffer.
*
* @throws java.io.IOException in case of a communication I/O failure.
*/
private void flushReadBufffer() throws IOException {
logger.trace("flushReadBuffer() called.");
DataInputStreamWithTimeout dInX = dIn;
if (!ready || (dInX == null)) {
throw new IOException();
}
int byteCount = dInX.available();
if (byteCount > 0) {
byte[] byteArray = new byte[byteCount];
dInX.readFully(byteArray);
if (logger.isTraceEnabled()) {
StringBuilder stringBuilder = new StringBuilder();
for (byte currByte : byteArray) {
stringBuilder.append(String.format("%02X ", currByte));
}
logger.trace("flushReadBuffer(): discarded {} unexpected bytes in the input buffer: {}", byteCount,
stringBuilder.toString());
} else {
logger.warn("flushReadBuffer(): discarded {} unexpected bytes in the input buffer", byteCount);
}
}
}
}

View File

@@ -64,9 +64,9 @@ public class KLF200Response {
*/
public static void errorLogging(Logger logger, short responseCommand) {
logger.trace("setResponse(): cannot handle response {} ({}).", Command.get(responseCommand).toString(),
responseCommand);
new CommandNumber(responseCommand).toString());
logger.warn("Gateway response {} ({}) cannot be handled at this point of interaction.",
Command.get(responseCommand).toString(), responseCommand);
Command.get(responseCommand).toString(), new CommandNumber(responseCommand).toString());
}
/**
@@ -122,10 +122,10 @@ public class KLF200Response {
* @return <b>check4matchingAnyID</b> of type boolean which signals the equality.
*/
private static boolean check4matchingAnyID(Logger logger, String idName, int requestID, int responseID) {
logger.trace("check4matchingAnyID() called for request{} {} and response{} {}.", idName, requestID, idName,
logger.trace("check4matchingAnyID() called for request {} {} and response {} {}.", idName, requestID, idName,
responseID);
if (requestID != responseID) {
logger.warn("Gateway response with {} {} unexpected as query asked for {} {}.", idName, requestID, idName,
logger.warn("Gateway query for {} {} received unexpected response of {} {}.", idName, requestID, idName,
responseID);
return false;
}

View File

@@ -47,8 +47,8 @@ public class SlipEncoding {
private final Logger logger = LoggerFactory.getLogger(SlipEncoding.class);
private static final byte PROTOCOL_ID = 0;
private static boolean encodingValid = false;
private static byte[] message = new byte[0];
private boolean encodingValid = false;
private byte[] message = new byte[0];
/**
* Builds a message based on command and parameters.

View File

@@ -14,8 +14,10 @@ package org.openhab.binding.velux.internal.discovery;
import static org.openhab.binding.velux.internal.VeluxBindingConstants.*;
import java.util.HashSet;
import java.util.Set;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.openhab.binding.velux.internal.VeluxBindingConstants;
import org.openhab.binding.velux.internal.VeluxBindingProperties;
import org.openhab.binding.velux.internal.handler.VeluxBridgeHandler;
@@ -60,7 +62,7 @@ public class VeluxDiscoveryService extends AbstractDiscoveryService implements R
private @NonNullByDefault({}) LocaleProvider localeProvider;
private @NonNullByDefault({}) TranslationProvider i18nProvider;
private Localization localization = Localization.UNKNOWN;
private static @Nullable VeluxBridgeHandler bridgeHandler = null;
private final Set<VeluxBridgeHandler> bridgeHandlers = new HashSet<>();
// Private
@@ -102,21 +104,11 @@ public class VeluxDiscoveryService extends AbstractDiscoveryService implements R
* Initializes the {@link VeluxDiscoveryService} with a reference to the well-prepared environment with a
* {@link VeluxBridgeHandler}.
*
* @param bridge Initialized Velux bridge handler.
* @param localizationHandler Initialized localization handler.
*/
public VeluxDiscoveryService(VeluxBridgeHandler bridge, Localization localizationHandler) {
public VeluxDiscoveryService(Localization localizationHandler) {
super(VeluxBindingConstants.SUPPORTED_THINGS_ITEMS, DISCOVER_TIMEOUT_SECONDS);
logger.trace("VeluxDiscoveryService(bridge={},locale={},i18n={}) just initialized.", bridge, localeProvider,
i18nProvider);
if (bridgeHandler == null) {
logger.trace("VeluxDiscoveryService(): registering bridge {} for lateron use for Discovery.", bridge);
} else if (!bridge.equals(bridgeHandler)) {
logger.trace("VeluxDiscoveryService(): replacing already registered bridge {} by {}.", bridgeHandler,
bridge);
}
bridgeHandler = bridge;
logger.trace("VeluxDiscoveryService(locale={},i18n={}) just initialized.", localeProvider, i18nProvider);
localization = localizationHandler;
}
@@ -126,16 +118,14 @@ public class VeluxDiscoveryService extends AbstractDiscoveryService implements R
* Initializes the {@link VeluxDiscoveryService} with a reference to the well-prepared environment with a
* {@link VeluxBridgeHandler}.
*
* @param bridge Initialized Velux bridge handler.
* @param locationProvider Provider for a location.
* @param localeProvider Provider for a locale.
* @param i18nProvider Provider for the internationalization.
*/
public VeluxDiscoveryService(VeluxBridgeHandler bridge, LocationProvider locationProvider,
LocaleProvider localeProvider, TranslationProvider i18nProvider) {
this(bridge, new Localization(localeProvider, i18nProvider));
logger.trace("VeluxDiscoveryService(bridge={},locale={},i18n={}) finished.", bridge, localeProvider,
i18nProvider);
public VeluxDiscoveryService(LocationProvider locationProvider, LocaleProvider localeProvider,
TranslationProvider i18nProvider) {
this(new Localization(localeProvider, i18nProvider));
logger.trace("VeluxDiscoveryService(locale={},i18n={}) finished.", localeProvider, i18nProvider);
}
@Override
@@ -157,8 +147,8 @@ public class VeluxDiscoveryService extends AbstractDiscoveryService implements R
logger.debug("startScan(): registering new thing {}.", discoveryResult);
thingDiscovered(discoveryResult);
if (bridgeHandler == null) {
logger.debug("startScan(): VeluxDiscoveryService cannot proceed due to missing Velux bridge.");
if (bridgeHandlers.isEmpty()) {
logger.debug("startScan(): VeluxDiscoveryService cannot proceed due to missing Velux bridge(s).");
} else {
logger.debug("startScan(): Starting Velux discovery scan for scenes and actuators.");
discoverScenes();
@@ -185,29 +175,25 @@ public class VeluxDiscoveryService extends AbstractDiscoveryService implements R
*/
private void discoverScenes() {
logger.trace("discoverScenes() called.");
// Just for avoidance of Potential null pointer access
VeluxBridgeHandler bridgeHandlerX = bridgeHandler;
if (bridgeHandlerX == null) {
logger.debug("discoverScenes(): VeluxDiscoveryService.bridgeHandler not initialized, aborting discovery.");
return;
}
ThingUID bridgeUID = bridgeHandlerX.getThing().getUID();
logger.debug("discoverScenes(): discovering all scenes.");
for (VeluxScene scene : bridgeHandlerX.existingScenes().values()) {
String sceneName = scene.getName().toString();
logger.trace("discoverScenes(): found scene {}.", sceneName);
for (VeluxBridgeHandler bridgeHandlerX : bridgeHandlers) {
ThingUID bridgeUID = bridgeHandlerX.getThing().getUID();
logger.debug("discoverScenes(): discovering all scenes on bridge {}.", bridgeUID);
for (VeluxScene scene : bridgeHandlerX.existingScenes().values()) {
String sceneName = scene.getName().toString();
logger.trace("discoverScenes(): found scene {}.", sceneName);
String label = sceneName.replaceAll("\\P{Alnum}", "_");
logger.trace("discoverScenes(): using label {}.", label);
String label = sceneName.replaceAll("\\P{Alnum}", "_");
logger.trace("discoverScenes(): using label {}.", label);
ThingTypeUID thingTypeUID = THING_TYPE_VELUX_SCENE;
ThingUID thingUID = new ThingUID(thingTypeUID, bridgeUID, label);
DiscoveryResult discoveryResult = DiscoveryResultBuilder.create(thingUID).withThingType(thingTypeUID)
.withProperty(VeluxBindingProperties.PROPERTY_SCENE_NAME, sceneName)
.withRepresentationProperty(VeluxBindingProperties.PROPERTY_SCENE_NAME).withBridge(bridgeUID)
.withLabel(label).build();
logger.debug("discoverScenes(): registering new thing {}.", discoveryResult);
thingDiscovered(discoveryResult);
ThingTypeUID thingTypeUID = THING_TYPE_VELUX_SCENE;
ThingUID thingUID = new ThingUID(thingTypeUID, bridgeUID, label);
DiscoveryResult discoveryResult = DiscoveryResultBuilder.create(thingUID).withThingType(thingTypeUID)
.withProperty(VeluxBindingProperties.PROPERTY_SCENE_NAME, sceneName)
.withRepresentationProperty(VeluxBindingProperties.PROPERTY_SCENE_NAME).withBridge(bridgeUID)
.withLabel(label).build();
logger.debug("discoverScenes(): registering new thing {}.", discoveryResult);
thingDiscovered(discoveryResult);
}
}
logger.trace("discoverScenes() finished.");
}
@@ -217,56 +203,87 @@ public class VeluxDiscoveryService extends AbstractDiscoveryService implements R
*/
private void discoverProducts() {
logger.trace("discoverProducts() called.");
// Just for avoidance of Potential null pointer access
VeluxBridgeHandler bridgeHandlerX = bridgeHandler;
if (bridgeHandlerX == null) {
logger.debug("discoverScenes() VeluxDiscoveryService.bridgeHandlerR not initialized, aborting discovery.");
return;
}
ThingUID bridgeUID = bridgeHandlerX.getThing().getUID();
logger.debug("discoverProducts(): discovering all actuators.");
for (VeluxProduct product : bridgeHandlerX.existingProducts().values()) {
String serialNumber = product.getSerialNumber();
String actuatorName = product.getProductName().toString();
logger.trace("discoverProducts() found actuator {} (name {}).", serialNumber, actuatorName);
String identifier;
if (serialNumber.equals(VeluxProductSerialNo.UNKNOWN)) {
identifier = actuatorName;
} else {
identifier = serialNumber;
for (VeluxBridgeHandler bridgeHandlerX : bridgeHandlers) {
ThingUID bridgeUID = bridgeHandlerX.getThing().getUID();
logger.debug("discoverProducts(): discovering all actuators on bridge {}.", bridgeUID);
for (VeluxProduct product : bridgeHandlerX.existingProducts().values()) {
String serialNumber = product.getSerialNumber();
String actuatorName = product.getProductName().toString();
logger.trace("discoverProducts() found actuator {} (name {}).", serialNumber, actuatorName);
String identifier;
if (serialNumber.equals(VeluxProductSerialNo.UNKNOWN)) {
identifier = actuatorName;
} else {
identifier = serialNumber;
}
String label = actuatorName.replaceAll("\\P{Alnum}", "_");
logger.trace("discoverProducts(): using label {}.", label);
ThingTypeUID thingTypeUID;
boolean isInverted = false;
logger.trace("discoverProducts() dealing with {} (type {}).", product, product.getProductType());
switch (product.getProductType()) {
case SLIDER_WINDOW:
logger.trace("discoverProducts(): creating window.");
thingTypeUID = THING_TYPE_VELUX_WINDOW;
isInverted = true;
break;
case SLIDER_SHUTTER:
logger.trace("discoverProducts(): creating rollershutter.");
thingTypeUID = THING_TYPE_VELUX_ROLLERSHUTTER;
break;
default:
logger.trace("discoverProducts(): creating actuator.");
thingTypeUID = THING_TYPE_VELUX_ACTUATOR;
}
ThingUID thingUID = new ThingUID(thingTypeUID, bridgeUID, label);
DiscoveryResult discoveryResult = DiscoveryResultBuilder.create(thingUID).withThingType(thingTypeUID)
.withProperty(VeluxBindingProperties.CONFIG_ACTUATOR_SERIALNUMBER, identifier)
.withProperty(VeluxBindingProperties.PROPERTY_ACTUATOR_NAME, actuatorName)
.withProperty(VeluxBindingProperties.PROPERTY_ACTUATOR_INVERTED, isInverted)
.withRepresentationProperty(VeluxBindingProperties.CONFIG_ACTUATOR_SERIALNUMBER)
.withBridge(bridgeUID).withLabel(actuatorName).build();
logger.debug("discoverProducts(): registering new thing {}.", discoveryResult);
thingDiscovered(discoveryResult);
}
String label = actuatorName.replaceAll("\\P{Alnum}", "_");
logger.trace("discoverProducts(): using label {}.", label);
ThingTypeUID thingTypeUID;
boolean isInverted = false;
logger.trace("discoverProducts() dealing with {} (type {}).", product, product.getProductType());
switch (product.getProductType()) {
case SLIDER_WINDOW:
logger.trace("discoverProducts(): creating window.");
thingTypeUID = THING_TYPE_VELUX_WINDOW;
isInverted = true;
break;
case SLIDER_SHUTTER:
logger.trace("discoverProducts(): creating rollershutter.");
thingTypeUID = THING_TYPE_VELUX_ROLLERSHUTTER;
break;
default:
logger.trace("discoverProducts(): creating actuator.");
thingTypeUID = THING_TYPE_VELUX_ACTUATOR;
}
ThingUID thingUID = new ThingUID(thingTypeUID, bridgeUID, label);
DiscoveryResult discoveryResult = DiscoveryResultBuilder.create(thingUID).withThingType(thingTypeUID)
.withProperty(VeluxBindingProperties.CONFIG_ACTUATOR_SERIALNUMBER, identifier)
.withProperty(VeluxBindingProperties.PROPERTY_ACTUATOR_NAME, actuatorName)
.withProperty(VeluxBindingProperties.PROPERTY_ACTUATOR_INVERTED, isInverted)
.withRepresentationProperty(VeluxBindingProperties.CONFIG_ACTUATOR_SERIALNUMBER)
.withBridge(bridgeUID).withLabel(actuatorName).build();
logger.debug("discoverProducts(): registering new thing {}.", discoveryResult);
thingDiscovered(discoveryResult);
}
logger.trace("discoverProducts() finished.");
}
/**
* Add a {@link VeluxBridgeHandler} to the {@link VeluxDiscoveryService}
*
* @param bridge Velux bridge handler.
* @return true if the bridge was added, or false if it was already present
*/
public boolean addBridge(VeluxBridgeHandler bridge) {
if (!bridgeHandlers.contains(bridge)) {
logger.trace("VeluxDiscoveryService(): registering bridge {} for discovery.", bridge);
bridgeHandlers.add(bridge);
return true;
}
logger.trace("VeluxDiscoveryService(): bridge {} already registered for discovery.", bridge);
return false;
}
/**
* Remove a {@link VeluxBridgeHandler} from the {@link VeluxDiscoveryService}
*
* @param bridge Velux bridge handler.
* @return true if the bridge was removed, or false if it was not present
*/
public boolean removeBridge(VeluxBridgeHandler bridge) {
return bridgeHandlers.remove(bridge);
}
/**
* Check if the {@link VeluxDiscoveryService} list of {@link VeluxBridgeHandler} is empty
*
* @return true if empty
*/
public boolean isEmpty() {
return bridgeHandlers.isEmpty();
}
}

View File

@@ -12,10 +12,8 @@
*/
package org.openhab.binding.velux.internal.factory;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Hashtable;
import java.util.Map;
import java.util.Set;
import org.eclipse.jdt.annotation.NonNullByDefault;
@@ -32,7 +30,6 @@ import org.openhab.core.i18n.TranslationProvider;
import org.openhab.core.thing.Bridge;
import org.openhab.core.thing.Thing;
import org.openhab.core.thing.ThingTypeUID;
import org.openhab.core.thing.ThingUID;
import org.openhab.core.thing.binding.BaseThingHandlerFactory;
import org.openhab.core.thing.binding.ThingHandler;
import org.openhab.core.thing.binding.ThingHandlerFactory;
@@ -56,7 +53,8 @@ public class VeluxHandlerFactory extends BaseThingHandlerFactory {
// Class internal
private final Map<ThingUID, ServiceRegistration<?>> discoveryServiceRegistrations = new HashMap<>();
private @Nullable ServiceRegistration<?> discoveryServiceRegistration = null;
private @Nullable VeluxDiscoveryService discoveryService = null;
private Set<VeluxBindingHandler> veluxBindingHandlers = new HashSet<>();
private Set<VeluxBridgeHandler> veluxBridgeHandlers = new HashSet<>();
@@ -69,20 +67,25 @@ public class VeluxHandlerFactory extends BaseThingHandlerFactory {
// Private
private void registerDeviceDiscoveryService(VeluxBridgeHandler bridgeHandler) {
VeluxDiscoveryService discoveryService = new VeluxDiscoveryService(bridgeHandler, localization);
ServiceRegistration<?> discoveryServiceReg = bundleContext.registerService(DiscoveryService.class.getName(),
discoveryService, new Hashtable<>());
discoveryServiceRegistrations.put(bridgeHandler.getThing().getUID(), discoveryServiceReg);
logger.trace("registerDeviceDiscoveryService({}) called.", bridgeHandler);
boolean createNew = (discoveryService == null);
if (createNew) {
discoveryService = new VeluxDiscoveryService(localization);
}
discoveryService.addBridge(bridgeHandler);
if (createNew) {
discoveryServiceRegistration = bundleContext.registerService(DiscoveryService.class.getName(),
discoveryService, new Hashtable<>());
}
}
// Even if the compiler tells, that the value of <remove> cannot be null, it is possible!
// Therefore a @SuppressWarnings("null") is needed to suppress the warning
@SuppressWarnings("null")
private synchronized void unregisterDeviceDiscoveryService(ThingUID thingUID) {
logger.trace("unregisterDeviceDiscoveryService({}) called.", thingUID);
ServiceRegistration<?> remove = discoveryServiceRegistrations.remove(thingUID);
if (remove != null) {
remove.unregister();
private synchronized void unregisterDeviceDiscoveryService(VeluxBridgeHandler bridgeHandler) {
logger.trace("unregisterDeviceDiscoveryService({}) called.", bridgeHandler);
if (discoveryService != null) {
discoveryService.removeBridge(bridgeHandler);
if (discoveryService.isEmpty()) {
discoveryServiceRegistration.unregister();
}
}
}
@@ -191,7 +194,7 @@ public class VeluxHandlerFactory extends BaseThingHandlerFactory {
if (thingHandler instanceof VeluxBridgeHandler) {
logger.trace("removeHandler() removing bridge '{}'.", thingHandler.toString());
veluxBridgeHandlers.remove(thingHandler);
unregisterDeviceDiscoveryService(thingHandler.getThing().getUID());
unregisterDeviceDiscoveryService((VeluxBridgeHandler) thingHandler);
} else
// Handle removal of Things behind the Bridge
if (thingHandler instanceof VeluxHandler) {

View File

@@ -19,6 +19,7 @@ import org.openhab.binding.velux.internal.VeluxItemType;
import org.openhab.binding.velux.internal.bridge.VeluxBridgeWLANConfig;
import org.openhab.binding.velux.internal.handler.utils.StateUtils;
import org.openhab.binding.velux.internal.handler.utils.ThingProperty;
import org.openhab.core.library.types.StringType;
import org.openhab.core.thing.ChannelUID;
import org.openhab.core.types.State;
import org.slf4j.Logger;
@@ -69,16 +70,15 @@ final class ChannelBridgeWLANconfig extends ChannelHandlerTemplate {
if (thisBridgeHandler.bridgeParameters.wlanConfig.isRetrieved) {
VeluxItemType itemType = VeluxItemType.getByThingAndChannel(thisBridgeHandler.thingTypeUIDOf(channelUID),
channelUID.getId());
String msg = thisBridgeHandler.localization.getText("config.velux.bridge.unAvailable");
switch (itemType) {
case BRIDGE_WLANSSID:
newState = StateUtils.createState(thisBridgeHandler.bridgeParameters.lanConfig.openHABipAddress);
ThingProperty.setValue(thisBridgeHandler, VeluxBindingConstants.PROPERTY_BRIDGE_WLANSSID,
thisBridgeHandler.bridgeParameters.wlanConfig.openHABwlanSSID.toString());
newState = StateUtils.createState(new StringType(msg));
ThingProperty.setValue(thisBridgeHandler, VeluxBindingConstants.PROPERTY_BRIDGE_WLANSSID, msg);
break;
case BRIDGE_WLANPASSWORD:
newState = StateUtils.createState(thisBridgeHandler.bridgeParameters.lanConfig.openHABsubnetMask);
ThingProperty.setValue(thisBridgeHandler, VeluxBindingConstants.PROPERTY_BRIDGE_WLANPASSWORD,
thisBridgeHandler.bridgeParameters.wlanConfig.openHABwlanPassword.toString());
newState = StateUtils.createState(new StringType(msg));
ThingProperty.setValue(thisBridgeHandler, VeluxBindingConstants.PROPERTY_BRIDGE_WLANPASSWORD, msg);
break;
default:
}

View File

@@ -13,6 +13,7 @@
package org.openhab.binding.velux.internal.handler;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.openhab.binding.velux.internal.VeluxBindingConstants;
import org.openhab.binding.velux.internal.VeluxBindingProperties;
import org.openhab.binding.velux.internal.VeluxItemType;
import org.openhab.binding.velux.internal.handler.utils.ExtendedBaseThingHandler;
@@ -20,6 +21,7 @@ import org.openhab.binding.velux.internal.handler.utils.StateUtils;
import org.openhab.binding.velux.internal.handler.utils.ThingProperty;
import org.openhab.binding.velux.internal.utils.Localization;
import org.openhab.binding.velux.internal.utils.ManifestInformation;
import org.openhab.core.common.AbstractUID;
import org.openhab.core.thing.Channel;
import org.openhab.core.thing.ChannelUID;
import org.openhab.core.thing.Thing;
@@ -82,9 +84,13 @@ public class VeluxBindingHandler extends ExtendedBaseThingHandler {
* @param channelUID for type {@link ChannelUID}.
* @return thingTypeUID of type {@link ThingTypeUID}.
*/
@SuppressWarnings("deprecation")
private ThingTypeUID thingTypeUIDOf(ChannelUID channelUID) {
return channelUID.getThingUID().getThingTypeUID();
String[] segments = channelUID.getAsString().split(AbstractUID.SEPARATOR);
if (segments.length > 1) {
return new ThingTypeUID(segments[0], segments[1]);
}
logger.warn("thingTypeUIDOf({}) failed.", channelUID);
return new ThingTypeUID(VeluxBindingConstants.BINDING_ID, VeluxBindingConstants.UNKNOWN_THING_TYPE_ID);
}
/**

View File

@@ -49,6 +49,7 @@ import org.openhab.binding.velux.internal.things.VeluxProduct;
import org.openhab.binding.velux.internal.things.VeluxProduct.ProductBridgeIndex;
import org.openhab.binding.velux.internal.things.VeluxProductPosition;
import org.openhab.binding.velux.internal.utils.Localization;
import org.openhab.core.common.AbstractUID;
import org.openhab.core.common.ThreadPoolManager;
import org.openhab.core.library.types.DecimalType;
import org.openhab.core.library.types.OnOffType;
@@ -187,9 +188,13 @@ public class VeluxBridgeHandler extends ExtendedBaseBridgeHandler implements Vel
* @param channelUID for type {@link ChannelUID}.
* @return thingTypeUID of type {@link ThingTypeUID}.
*/
@SuppressWarnings("deprecation")
ThingTypeUID thingTypeUIDOf(ChannelUID channelUID) {
return channelUID.getThingUID().getThingTypeUID();
String[] segments = channelUID.getAsString().split(AbstractUID.SEPARATOR);
if (segments.length > 1) {
return new ThingTypeUID(segments[0], segments[1]);
}
logger.warn("thingTypeUIDOf({}) failed.", channelUID);
return new ThingTypeUID(VeluxBindingConstants.BINDING_ID, VeluxBindingConstants.UNKNOWN_THING_TYPE_ID);
}
// Objects and Methods for interface VeluxBridgeInstance

View File

@@ -129,7 +129,7 @@ public class VeluxKLFAPI {
UNDEFTYPE((short) -1, "Unknown command."),
// Special item: Shutdown of the connection
GW_OPENHAB_CLOSE((short) -2, "openHAB connection shutdown command."),
// Special item: Shutdown of the connection
// Special item: Empty Command (used to send nothing and process an incoming message only)
GW_OPENHAB_RECEIVEONLY((short) -3, "openHAB receive command."),
// Velux specific commands
GW_ERROR_NTF((short) 0x0000, "Provides information on what triggered the error."),

View File

@@ -68,6 +68,7 @@ config.velux.bridge.isSequentialEnforced.label = Enforce Sequential Mode
config.velux.bridge.isSequentialEnforced.description = Enforce Sequential Actuator Control. Determine the mode of operation for long-time actions like running commands or activation of scenes. However the parallelism disables the in-depth protocol handshake processing which does not affect or limit any functionalities.
config.velux.bridge.isProtocolTraceEnabled.label = Enable Protocol Trace
config.velux.bridge.isProtocolTraceEnabled.description = Provide KLF200 protocol details.
config.velux.bridge.unAvailable = Unavailable
#
config.velux.thing.scene.sceneName.label = Scene Name
config.velux.thing.scene.sceneName.description = Name of the scene to be handled.

View File

@@ -69,6 +69,7 @@ config.velux.bridge.isSequentialEnforced.label = Sequentielles Modus
config.velux.bridge.isSequentialEnforced.description = Erzwingt den sequentiellen Modus von Operationen, insbesondere von länger andauernden Aktionen, wie der Aktivierung von Szenen.
config.velux.bridge.isProtocolTraceEnabled.label = Protokolleinblick
config.velux.bridge.isProtocolTraceEnabled.description = Aktiviert KLF200 Protokolldetails.
config.velux.bridge.unAvailable = Nicht verfügbar
#
config.velux.thing.scene.sceneName.label = Scenenname
config.velux.thing.scene.sceneName.description = Name der Szene, wie sie auf dem Kopplungselements definiert wurde.

View File

@@ -65,6 +65,7 @@ config.velux.bridge.isSequentialEnforced.label = Håndhæv sekventiel tilstand
config.velux.bridge.isSequentialEnforced.description = Håndhæv sekventiel aktuatorstyring. Bestem driftsmåden for handlinger i lang tid som at køre kommandoer eller aktivering af scener. Paralleliteten deaktiverer imidlertid den dybdegående protokolhåndtryksbehandling, som ikke påvirker eller begrænser nogen funktionaliteter.
config.velux.bridge.isProtocolTraceEnabled.label = Protocol Insight
config.velux.bridge.isProtocolTraceEnabled.description = Aktiverer KLF200-protokoldetaljer.
config.velux.bridge.unAvailable = Utilgængelig
#
config.velux.thing.scene.sceneName.label = Scenavn
config.velux.thing.scene.sceneName.description = Navn på den scene, der skal håndteres.

View File

@@ -65,6 +65,7 @@ config.velux.bridge.isSequentialEnforced.label = Opeenvolgende modus afdwingen
config.velux.bridge.isSequentialEnforced.description = Handhaving van sequentiële actuatorcontrole. Bepaal de werkingsmodus voor langdurige acties zoals het uitvoeren van opdrachten of het activeren van scènes. Het parallellisme schakelt echter de diepgaande protocolhandshake-verwerking uit die geen enkele functionaliteit beïnvloedt of beperkt.
config.velux.bridge.isProtocolTraceEnabled.label = Protocol Insight
config.velux.bridge.isProtocolTraceEnabled.description = Schakelt KLF200-protocoldetails in.
config.velux.bridge.unAvailable = Niet beschikbaar
#
config.velux.thing.scene.sceneName.label = Scènenaam
config.velux.thing.scene.sceneName.description = Naam van de te behandelen scène.