From e6982e71bb93fc18ef34940f248015b69d8fb126 Mon Sep 17 00:00:00 2001 From: Holger Friedrich Date: Sat, 16 Dec 2023 11:37:57 +0100 Subject: [PATCH] [knx] Add discovery service (#16033) * [knx] Add discovery service Signed-off-by: Holger Friedrich --- bundles/org.openhab.binding.knx/README.md | 14 ++ .../discovery/KNXnetDiscoveryService.java | 143 ++++++++++++++++++ .../src/main/resources/OH-INF/thing/ip.xml | 2 + 3 files changed, 159 insertions(+) create mode 100644 bundles/org.openhab.binding.knx/src/main/java/org/openhab/binding/knx/internal/discovery/KNXnetDiscoveryService.java diff --git a/bundles/org.openhab.binding.knx/README.md b/bundles/org.openhab.binding.knx/README.md index d5019aef1d8..c76196e42a2 100644 --- a/bundles/org.openhab.binding.knx/README.md +++ b/bundles/org.openhab.binding.knx/README.md @@ -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 index 00000000000..9b714f5de28 --- /dev/null +++ b/bundles/org.openhab.binding.knx/src/main/java/org/openhab/binding/knx/internal/discovery/KNXnetDiscoveryService.java @@ -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> responses = discovererUdp.getSearchResponses(); + + for (Result r : responses) { + @Nullable + SearchResponse response = r.getResponse(); + if (response == null) { + continue; + } + Map 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()); + } + } +} diff --git a/bundles/org.openhab.binding.knx/src/main/resources/OH-INF/thing/ip.xml b/bundles/org.openhab.binding.knx/src/main/resources/OH-INF/thing/ip.xml index 1e9fcef5503..6a8d7427d25 100644 --- a/bundles/org.openhab.binding.knx/src/main/resources/OH-INF/thing/ip.xml +++ b/bundles/org.openhab.binding.knx/src/main/resources/OH-INF/thing/ip.xml @@ -8,6 +8,8 @@ This is a KNX IP interface or router + serialNumber +