diff --git a/bundles/org.openhab.binding.homematic/README.md b/bundles/org.openhab.binding.homematic/README.md index 1dd7842f7fc8..26ff2b8f85bf 100644 --- a/bundles/org.openhab.binding.homematic/README.md +++ b/bundles/org.openhab.binding.homematic/README.md @@ -157,6 +157,13 @@ The port number of the HMIP server (default = 2010) - **cuxdPort** The port number of the CUxD daemon (default = 8701) +- **groupPort** +The port number of the Group daemon (default = 9292) + +- **callbackRegTimeout** +Maximum time in seconds for callback registration in the Homematic gateway (default = 120s). +For a CCU2, the value may need to be increased to 180s. + - **installModeDuration** Time in seconds that the controller will be in install mode when a device discovery is initiated (default = 60) @@ -676,6 +683,13 @@ Examples: HmIP-BROLL, HmIP-FROLL, HmIP-BBL, HmIP-FBL and HmIP-DRBLI4 | openHAB | 0% | 100% | | CCU | 100% | 0% | +** The binding does not receive any status changes from the Homematic gateway** + +First of all, make sure that none of the ports needed to receive status changes from the gateway are blocked by firewall settings. + +If the computer running openHAB has more than one IP address, a wrong one may have been set as receiver for status changes. +In this case change the setting for `callbackHost` to the correct address. + ### Debugging and Tracing If you want to see what's going on in the binding, switch the log level to DEBUG in the Karaf console diff --git a/bundles/org.openhab.binding.homematic/src/main/java/org/openhab/binding/homematic/internal/HomematicBindingConstants.java b/bundles/org.openhab.binding.homematic/src/main/java/org/openhab/binding/homematic/internal/HomematicBindingConstants.java index a17dd71cc18a..10118362983f 100644 --- a/bundles/org.openhab.binding.homematic/src/main/java/org/openhab/binding/homematic/internal/HomematicBindingConstants.java +++ b/bundles/org.openhab.binding.homematic/src/main/java/org/openhab/binding/homematic/internal/HomematicBindingConstants.java @@ -24,6 +24,7 @@ public class HomematicBindingConstants { public static final String BINDING_ID = "homematic"; + public static final String GATEWAY_POOL_NAME = "homematicGateway"; public static final ThingTypeUID THING_TYPE_BRIDGE = new ThingTypeUID(BINDING_ID, "bridge"); public static final String CONFIG_DESCRIPTION_URI_CHANNEL = "channel-type:homematic:config"; diff --git a/bundles/org.openhab.binding.homematic/src/main/java/org/openhab/binding/homematic/internal/common/HomematicConfig.java b/bundles/org.openhab.binding.homematic/src/main/java/org/openhab/binding/homematic/internal/common/HomematicConfig.java index 9936668541d1..4d20f99cdbae 100644 --- a/bundles/org.openhab.binding.homematic/src/main/java/org/openhab/binding/homematic/internal/common/HomematicConfig.java +++ b/bundles/org.openhab.binding.homematic/src/main/java/org/openhab/binding/homematic/internal/common/HomematicConfig.java @@ -58,6 +58,8 @@ public class HomematicConfig { private int bufferSize = 2048; private HmGatewayInfo gatewayInfo; + private int callbackRegistrationRetries; + private int callbackRegTimeout; /** * Returns the Homematic gateway address. @@ -115,6 +117,20 @@ public void setBinCallbackPort(int binCallbackPort) { this.binCallbackPort = binCallbackPort; } + /** + * Sets timeout for callback registration. + */ + public void setCallbackRegTimeout(int timeout) { + this.callbackRegTimeout = timeout; + } + + /** + * Returns timeout for callback registrations. + */ + public int getCallbackRegTimeout() { + return callbackRegTimeout; + } + /** * Returns the HmGatewayInfo. */ diff --git a/bundles/org.openhab.binding.homematic/src/main/java/org/openhab/binding/homematic/internal/communicator/AbstractHomematicGateway.java b/bundles/org.openhab.binding.homematic/src/main/java/org/openhab/binding/homematic/internal/communicator/AbstractHomematicGateway.java index d834ec1f89f7..aba9227ab1ba 100644 --- a/bundles/org.openhab.binding.homematic/src/main/java/org/openhab/binding/homematic/internal/communicator/AbstractHomematicGateway.java +++ b/bundles/org.openhab.binding.homematic/src/main/java/org/openhab/binding/homematic/internal/communicator/AbstractHomematicGateway.java @@ -12,6 +12,7 @@ */ package org.openhab.binding.homematic.internal.communicator; +import static org.openhab.binding.homematic.internal.HomematicBindingConstants.*; import static org.openhab.binding.homematic.internal.misc.HomematicConstants.*; import java.io.IOException; @@ -87,8 +88,8 @@ public abstract class AbstractHomematicGateway implements RpcEventListener, HomematicGateway, VirtualGateway { private final Logger logger = LoggerFactory.getLogger(AbstractHomematicGateway.class); public static final double DEFAULT_DISABLE_DELAY = 2.0; + private static final long RESTART_DELAY = 30; private static final long CONNECTION_TRACKER_INTERVAL_SECONDS = 15; - private static final String GATEWAY_POOL_NAME = "homematicGateway"; private final Map> rpcClients = new HashMap<>(); private final Map rpcServers = new HashMap<>(); @@ -189,6 +190,7 @@ public void initialize() throws IOException { logger.debug("Used Homematic transfer modes: {}", sb.toString()); startClients(); startServers(); + registerCallbacks(); if (!config.getGatewayInfo().isHomegear()) { // delay the newDevice event handling at startup, reduces some API calls @@ -211,7 +213,7 @@ public void dispose() { stopWatchdogs(); sendDelayedExecutor.stop(); receiveDelayedExecutor.stop(); - stopServers(); + stopServers(true); stopClients(); devices.clear(); echoEvents.clear(); @@ -253,25 +255,29 @@ private synchronized void startServers() throws IOException { rpcServer.start(); } } + } + + private void registerCallbacks() throws IOException { for (HmInterface hmInterface : availableInterfaces.keySet()) { - getRpcClient(hmInterface).init(hmInterface, hmInterface.toString() + "-" + id); + getRpcClient(hmInterface).init(hmInterface); } } /** * Stops the Homematic RPC server. */ - private synchronized void stopServers() { - for (HmInterface hmInterface : availableInterfaces.keySet()) { - try { - getRpcClient(hmInterface).release(hmInterface); - } catch (IOException ex) { - // recoverable exception, therefore only debug - logger.debug("Unable to release the connection to the gateway with id '{}': {}", id, ex.getMessage(), - ex); + private synchronized void stopServers(boolean releaseConnection) { + if (releaseConnection) { + for (HmInterface hmInterface : availableInterfaces.keySet()) { + try { + getRpcClient(hmInterface).release(hmInterface); + } catch (IOException ex) { + // recoverable exception, therefore only debug + logger.debug("Unable to release the connection to the gateway with id '{}': {}", id, + ex.getMessage(), ex); + } } } - for (TransferMode mode : rpcServers.keySet()) { rpcServers.get(mode).shutdown(); } @@ -896,10 +902,14 @@ private int translateFlags(boolean reset, boolean force, boolean defer) { */ private class ConnectionTrackerThread implements Runnable { private boolean connectionLost; + private boolean reStartPending = false; @Override public void run() { try { + if (reStartPending) { + return; + } ListBidcosInterfacesParser parser = getRpcClient(getDefaultInterface()) .listBidcosInterfaces(getDefaultInterface()); Integer dutyCycleRatio = parser.getDutyCycleRatio(); @@ -920,6 +930,11 @@ private void connectionConfirmed() { if (connectionLost) { connectionLost = false; logger.info("Connection resumed on gateway '{}'", id); + try { + registerCallbacks(); + } catch (IOException e) { + logger.warn("Connection only partially restored. It is recommended to restart the binding"); + } gatewayAdapter.onConnectionResumed(); } } @@ -930,10 +945,19 @@ private void handleInvalidConnection(String cause) throws IOException { logger.warn("Connection lost on gateway '{}', cause: \"{}\"", id, cause); gatewayAdapter.onConnectionLost(); } - stopServers(); + stopServers(false); stopClients(); - startClients(); - startServers(); + reStartPending = true; + logger.debug("Waiting {}s until restart attempt", RESTART_DELAY); + scheduler.schedule(() -> { + try { + startClients(); + startServers(); + } catch (IOException e) { + logger.debug("Restart failed: {}", e.getMessage()); + } + reStartPending = false; + }, RESTART_DELAY, TimeUnit.SECONDS); } } } diff --git a/bundles/org.openhab.binding.homematic/src/main/java/org/openhab/binding/homematic/internal/communicator/client/BinRpcClient.java b/bundles/org.openhab.binding.homematic/src/main/java/org/openhab/binding/homematic/internal/communicator/client/BinRpcClient.java index b814b91d897f..f3c01c3fca67 100644 --- a/bundles/org.openhab.binding.homematic/src/main/java/org/openhab/binding/homematic/internal/communicator/client/BinRpcClient.java +++ b/bundles/org.openhab.binding.homematic/src/main/java/org/openhab/binding/homematic/internal/communicator/client/BinRpcClient.java @@ -40,6 +40,7 @@ public BinRpcClient(HomematicConfig config) { @Override public void dispose() { + super.dispose(); socketHandler.flush(); } @@ -54,8 +55,8 @@ protected String getRpcCallbackUrl() { } @Override - public void init(HmInterface hmInterface, String clientId) throws IOException { - super.init(hmInterface, clientId); + public void init(HmInterface hmInterface) throws IOException { + super.init(hmInterface); socketHandler.removeSocket(config.getRpcPort(hmInterface)); } diff --git a/bundles/org.openhab.binding.homematic/src/main/java/org/openhab/binding/homematic/internal/communicator/client/RpcClient.java b/bundles/org.openhab.binding.homematic/src/main/java/org/openhab/binding/homematic/internal/communicator/client/RpcClient.java index 4f2fab2c823c..e0e05109da67 100644 --- a/bundles/org.openhab.binding.homematic/src/main/java/org/openhab/binding/homematic/internal/communicator/client/RpcClient.java +++ b/bundles/org.openhab.binding.homematic/src/main/java/org/openhab/binding/homematic/internal/communicator/client/RpcClient.java @@ -19,6 +19,13 @@ import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.UUID; +import java.util.concurrent.CancellationException; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.ScheduledFuture; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; import org.openhab.binding.homematic.internal.HomematicBindingConstants; import org.openhab.binding.homematic.internal.common.HomematicConfig; @@ -41,6 +48,7 @@ import org.openhab.binding.homematic.internal.model.HmInterface; import org.openhab.binding.homematic.internal.model.HmParamsetType; import org.openhab.binding.homematic.internal.model.HmRssiInfo; +import org.openhab.core.common.ThreadPoolManager; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -53,18 +61,18 @@ public abstract class RpcClient { private final Logger logger = LoggerFactory.getLogger(RpcClient.class); protected static final int MAX_RPC_RETRY = 3; protected static final int RESP_BUFFER_SIZE = 8192; + private static final int INITIAL_CALLBACK_REG_DELAY = 20; // 20 s before first attempt + private static final int CALLBACK_REG_DELAY = 10; // 10 s between two attempts protected HomematicConfig config; + private String thisUID = UUID.randomUUID().toString(); + private ScheduledFuture future = null; + private int attempt; public RpcClient(HomematicConfig config) { this.config = config; } - /** - * Disposes the client. - */ - public abstract void dispose(); - /** * Returns a RpcRequest for this client. */ @@ -83,14 +91,48 @@ public RpcClient(HomematicConfig config) { /** * Register a callback for the specified interface where the Homematic gateway can send its events. */ - public void init(HmInterface hmInterface, String clientId) throws IOException { + public void init(HmInterface hmInterface) throws IOException { + ScheduledExecutorService scheduler = ThreadPoolManager.getScheduledPool(GATEWAY_POOL_NAME); RpcRequest request = createRpcRequest("init"); request.addArg(getRpcCallbackUrl()); - request.addArg(clientId); + request.addArg(thisUID); if (config.getGatewayInfo().isHomegear()) { request.addArg(Integer.valueOf(0x22)); } - sendMessage(config.getRpcPort(hmInterface), request); + logger.debug("Register callback for interface {}", hmInterface.getName()); + try { + attempt = 1; + sendMessage(config.getRpcPort(hmInterface), request); // first attempt without delay + } catch (IOException e) { + future = scheduler.scheduleWithFixedDelay(() -> { + logger.debug("Register callback for interface {}, attempt {}", hmInterface.getName(), ++attempt); + try { + sendMessage(config.getRpcPort(hmInterface), request); + future.cancel(true); + } catch (IOException ex) { + // Ignore, retry + } + }, INITIAL_CALLBACK_REG_DELAY, CALLBACK_REG_DELAY, TimeUnit.SECONDS); + try { + future.get(config.getCallbackRegTimeout(), TimeUnit.SECONDS); + } catch (CancellationException e1) { + logger.debug("Callback for interface {} successfully registered", hmInterface.getName()); + } catch (InterruptedException | ExecutionException e1) { + throw new IOException("Callback reg. thread interrupted", e1); + } catch (TimeoutException e1) { + logger.error("Callback registration for interface {} timed out", hmInterface.getName()); + throw new IOException("Unable to reconnect in time"); + } + future = null; + } + } + + /** + * Disposes the client. + */ + public void dispose() { + if (future != null) + future.cancel(true); } /** diff --git a/bundles/org.openhab.binding.homematic/src/main/java/org/openhab/binding/homematic/internal/communicator/client/XmlRpcClient.java b/bundles/org.openhab.binding.homematic/src/main/java/org/openhab/binding/homematic/internal/communicator/client/XmlRpcClient.java index 3d82937eb450..42e94154005c 100644 --- a/bundles/org.openhab.binding.homematic/src/main/java/org/openhab/binding/homematic/internal/communicator/client/XmlRpcClient.java +++ b/bundles/org.openhab.binding.homematic/src/main/java/org/openhab/binding/homematic/internal/communicator/client/XmlRpcClient.java @@ -49,10 +49,6 @@ public XmlRpcClient(HomematicConfig config, HttpClient httpClient) throws IOExce this.httpClient = httpClient; } - @Override - public void dispose() { - } - @Override public RpcRequest createRpcRequest(String methodName) { return new XmlRpcRequest(methodName); @@ -87,10 +83,13 @@ protected synchronized Object[] sendMessage(int port, RpcRequest request throw new IOException(ex); } catch (IOException ex) { reason = ex; - if ("init".equals(request.getMethodName())) { // no retries for "init" request + // no retries for "init" request or if connection is refused + if ("init".equals(request.getMethodName()) + || ex.getCause() != null && ex.getCause() instanceof ExecutionException) { break; } - logger.debug("XmlRpcMessage failed, sending message again {}/{}", rpcRetryCounter, MAX_RPC_RETRY); + logger.debug("XmlRpcMessage failed({}), sending message again {}/{}", ex.getMessage(), rpcRetryCounter, + MAX_RPC_RETRY); } } throw reason; diff --git a/bundles/org.openhab.binding.homematic/src/main/resources/OH-INF/thing/bridge.xml b/bundles/org.openhab.binding.homematic/src/main/resources/OH-INF/thing/bridge.xml index 468b5aa67042..343098ea1d90 100644 --- a/bundles/org.openhab.binding.homematic/src/main/resources/OH-INF/thing/bridge.xml +++ b/bundles/org.openhab.binding.homematic/src/main/resources/OH-INF/thing/bridge.xml @@ -95,6 +95,12 @@ true 9292 + + + Maximum time in seconds for callback registration in the Homematic gateway. + true + 120 + Time in seconds that the controller will be in install mode when a device discovery is initiated