]> git.basschouten.com Git - openhab-addons.git/commitdiff
[knx] Add discovery service (#16033)
authorHolger Friedrich <mail@holger-friedrich.de>
Sat, 16 Dec 2023 10:37:57 +0000 (11:37 +0100)
committerGitHub <noreply@github.com>
Sat, 16 Dec 2023 10:37:57 +0000 (11:37 +0100)
* [knx] Add discovery service

Signed-off-by: Holger Friedrich <mail@holger-friedrich.de>
bundles/org.openhab.binding.knx/README.md
bundles/org.openhab.binding.knx/src/main/java/org/openhab/binding/knx/internal/discovery/KNXnetDiscoveryService.java [new file with mode: 0644]
bundles/org.openhab.binding.knx/src/main/resources/OH-INF/thing/ip.xml

index d5019aef1d892a4bc0b45081b2bfcc683a35dfca..c76196e42a28755fba03a6367e2d8425fc0152f7 100644 (file)
@@ -31,6 +31,20 @@ There is an _ip_ bridge to connect to KNX IP Gateways, and a _serial_ bridge for
 The following two bridge types are supported.
 Bridges don't have channels on their own.
 
+### Discovery
+
+KNX IP bridges, i.e. IP interfaces, routers, and knxd instances, are discovered through mulitcast communication in the local network.
+As a KNX setup is typically static, this in only done during startup of the binding.
+Corresponding bridges are added to the inbox.
+Additional configuration might be necessary after adding a bridge.
+
+Note that several items per device might be created, as routers typically support routing and tunneling.
+Make sure you import only one item per device.
+
+Discovery is not available for serial bridges and device Things described below.
+Discovery of IP bridges will not work without further measures if openHAB and the interface run on different network segments,
+as multicast traffic is typically not forwarded.
+
 ### IP Gateway
 
 The IP Gateway is the most commonly used way to connect to the KNX bus. At its base, the _ip_ bridge accepts the following configuration parameters:
diff --git a/bundles/org.openhab.binding.knx/src/main/java/org/openhab/binding/knx/internal/discovery/KNXnetDiscoveryService.java b/bundles/org.openhab.binding.knx/src/main/java/org/openhab/binding/knx/internal/discovery/KNXnetDiscoveryService.java
new file mode 100644 (file)
index 0000000..9b714f5
--- /dev/null
@@ -0,0 +1,143 @@
+/**
+ * Copyright (c) 2010-2023 Contributors to the openHAB project
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ */
+package org.openhab.binding.knx.internal.discovery;
+
+import static org.openhab.binding.knx.internal.KNXBindingConstants.THING_TYPE_IP_BRIDGE;
+
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import java.util.Set;
+import java.util.concurrent.Future;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.eclipse.jdt.annotation.Nullable;
+import org.openhab.core.config.discovery.AbstractDiscoveryService;
+import org.openhab.core.config.discovery.DiscoveryResultBuilder;
+import org.openhab.core.config.discovery.DiscoveryService;
+import org.openhab.core.thing.ThingUID;
+import org.osgi.service.component.annotations.Component;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import tuwien.auto.calimero.knxnetip.Discoverer;
+import tuwien.auto.calimero.knxnetip.Discoverer.Result;
+import tuwien.auto.calimero.knxnetip.servicetype.SearchResponse;
+import tuwien.auto.calimero.knxnetip.util.ServiceFamiliesDIB;
+import tuwien.auto.calimero.knxnetip.util.ServiceFamiliesDIB.ServiceFamily;
+
+/**
+ * Discovers KNXnet/IP interfaces or routers and adds the results to the inbox.
+ * Several items per device might be created, as routers typically support routing and tunneling.
+ * Discovery uses multicast traffic to IP 224.0.23.12, port 3671.
+ *
+ * @implNote Discovery is based on the functionality provided by Calimero library.
+ * @author Holger Friedrich - Initial contribution
+ */
+@Component(service = DiscoveryService.class, configurationPid = "discovery.knx")
+@NonNullByDefault
+public class KNXnetDiscoveryService extends AbstractDiscoveryService {
+    private final Logger logger = LoggerFactory.getLogger(KNXnetDiscoveryService.class);
+
+    private @Nullable Future<?> scanFuture = null;
+
+    public KNXnetDiscoveryService() {
+        super(Set.of(THING_TYPE_IP_BRIDGE), 3, true);
+    }
+
+    @Override
+    protected void startBackgroundDiscovery() {
+        // only start once at startup
+        startScan();
+    }
+
+    @Override
+    protected void stopBackgroundDiscovery() {
+        stopScan();
+    }
+
+    @Override
+    protected void startScan() {
+        if (scanFuture == null) {
+            scanFuture = scheduler.submit(this::startDiscovery);
+        } else {
+            logger.debug("KNXnet/IP background discovery scan in progress");
+        }
+    }
+
+    @Override
+    protected void stopScan() {
+        Future<?> tmpScanFuture = scanFuture;
+        if (tmpScanFuture != null) {
+            tmpScanFuture.cancel(false);
+            scanFuture = null;
+        }
+    }
+
+    private synchronized void startDiscovery() {
+        try {
+            logger.debug("Starting KNXnet/IP discovery scan");
+            Discoverer discovererUdp = new Discoverer(0, false);
+            discovererUdp.startSearch(3, true);
+
+            List<Result<SearchResponse>> responses = discovererUdp.getSearchResponses();
+
+            for (Result<SearchResponse> r : responses) {
+                @Nullable
+                SearchResponse response = r.getResponse();
+                if (response == null) {
+                    continue;
+                }
+                Map<ServiceFamily, Integer> services = response.getServiceFamilies().families();
+
+                if (services.containsKey(ServiceFamiliesDIB.ServiceFamily.Tunneling)
+                        || services.containsKey(ServiceFamiliesDIB.ServiceFamily.Routing)) {
+                    String serial = Objects.toString(response.getDevice().serialNumber()).replace(':', '-');
+
+                    if (logger.isTraceEnabled()) {
+                        logger.trace("Discovered device {}", response);
+                    } else {
+                        logger.debug("Discovered device {}, {}, {}", response.getDevice().getName(), serial,
+                                response.getDevice().getMACAddressString());
+                    }
+
+                    if (services.containsKey(ServiceFamiliesDIB.ServiceFamily.Tunneling)) {
+                        thingDiscovered(DiscoveryResultBuilder.create(new ThingUID(THING_TYPE_IP_BRIDGE, serial))
+                                .withLabel(response.getDevice().getName()).withProperty("serialNumber", serial)
+                                .withProperty("type", "TUNNEL")
+                                .withProperty("ipAddress",
+                                        "" + response.getControlEndpoint().getAddress().getHostAddress())
+                                .withProperty("port", "" + response.getControlEndpoint().getPort())
+                                .withRepresentationProperty("serialNumber").build());
+                    }
+                    if (services.containsKey(ServiceFamiliesDIB.ServiceFamily.Routing)) {
+                        thingDiscovered(DiscoveryResultBuilder.create(new ThingUID(THING_TYPE_IP_BRIDGE, serial))
+                                .withLabel(response.getDevice().getName() + " (router mode)")
+                                .withProperty("serialNumber", serial + "-r").withProperty("type", "ROUTER")
+                                .withProperty("ipAddress", "224.0.23.12")
+                                .withProperty("port", "" + response.getControlEndpoint().getPort())
+                                .withRepresentationProperty("serialNumber").build());
+                    }
+                } else {
+                    logger.trace("Ignoring device {}", response);
+                }
+            }
+            logger.debug("Completed KNXnet/IP discovery scan");
+        } catch (Exception ex) {
+            logger.warn("An error occurred during KNXnet/IP discovery {}", ex.getMessage(), ex);
+        } finally {
+            scanFuture = null;
+            removeOlderResults(getTimestampOfLastScan());
+        }
+    }
+}
index 1e9fcef550302596b3a262d8120869dc76e424d0..6a8d7427d25bb71bf09415bbcc5afbae8b0b37a8 100644 (file)
@@ -8,6 +8,8 @@
                <label>KNX/IP Gateway</label>
                <description>This is a KNX IP interface or router</description>
 
+               <representation-property>serialNumber</representation-property>
+
                <config-description>
                        <parameter-group name="knxsecure">
                                <label>KNX secure</label>