[knx] Add discovery service (#16033)

* [knx] Add discovery service

Signed-off-by: Holger Friedrich <mail@holger-friedrich.de>
This commit is contained in:
Holger Friedrich 2023-12-16 11:37:57 +01:00 committed by GitHub
parent 157d3d0141
commit e6982e71bb
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 159 additions and 0 deletions

View 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:

View File

@ -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());
}
}
}

View 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>