diff --git a/bom/openhab-core/pom.xml b/bom/openhab-core/pom.xml
index 8346e8dd549..0b7cd97eea3 100644
--- a/bom/openhab-core/pom.xml
+++ b/bom/openhab-core/pom.xml
@@ -310,6 +310,12 @@
${project.version}
compile
+
+ org.openhab.core.bundles
+ org.openhab.core.config.discovery.addon.ip
+ ${project.version}
+ compile
+
org.openhab.core.bundles
org.openhab.core.config.discovery.addon.mdns
diff --git a/bundles/org.openhab.core.config.discovery.addon.ip/.classpath b/bundles/org.openhab.core.config.discovery.addon.ip/.classpath
new file mode 100644
index 00000000000..d3d6b3c11b6
--- /dev/null
+++ b/bundles/org.openhab.core.config.discovery.addon.ip/.classpath
@@ -0,0 +1,29 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/bundles/org.openhab.core.config.discovery.addon.ip/.project b/bundles/org.openhab.core.config.discovery.addon.ip/.project
new file mode 100644
index 00000000000..f2cee8bcbb9
--- /dev/null
+++ b/bundles/org.openhab.core.config.discovery.addon.ip/.project
@@ -0,0 +1,23 @@
+
+
+ org.openhab.core.config.discovery.addon.ip
+
+
+
+
+
+ org.eclipse.jdt.core.javabuilder
+
+
+
+
+ org.eclipse.m2e.core.maven2Builder
+
+
+
+
+
+ org.eclipse.m2e.core.maven2Nature
+ org.eclipse.jdt.core.javanature
+
+
diff --git a/bundles/org.openhab.core.config.discovery.addon.ip/NOTICE b/bundles/org.openhab.core.config.discovery.addon.ip/NOTICE
new file mode 100644
index 00000000000..6c17d0d8a45
--- /dev/null
+++ b/bundles/org.openhab.core.config.discovery.addon.ip/NOTICE
@@ -0,0 +1,14 @@
+This content is produced and maintained by the openHAB project.
+
+* Project home: https://www.openhab.org
+
+== Declared Project Licenses
+
+This program and the accompanying materials are made available under the terms
+of the Eclipse Public License 2.0 which is available at
+https://www.eclipse.org/legal/epl-2.0/.
+
+== Source Code
+
+https://github.com/openhab/openhab-core
+
diff --git a/bundles/org.openhab.core.config.discovery.addon.ip/pom.xml b/bundles/org.openhab.core.config.discovery.addon.ip/pom.xml
new file mode 100644
index 00000000000..d70ff9728c8
--- /dev/null
+++ b/bundles/org.openhab.core.config.discovery.addon.ip/pom.xml
@@ -0,0 +1,29 @@
+
+
+
+ 4.0.0
+
+
+ org.openhab.core.bundles
+ org.openhab.core.reactor.bundles
+ 4.1.0-SNAPSHOT
+
+
+ org.openhab.core.config.discovery.addon.ip
+
+ openHAB Core :: Bundles :: IP-based Suggested Add-on Finder
+
+
+
+ org.openhab.core.bundles
+ org.openhab.core.config.discovery.addon
+ ${project.version}
+
+
+ org.openhab.core.bundles
+ org.openhab.core.addon
+ ${project.version}
+
+
+
diff --git a/bundles/org.openhab.core.config.discovery.addon.ip/src/main/java/org/openhab/core/config/discovery/addon/ip/IpAddonFinder.java b/bundles/org.openhab.core.config.discovery.addon.ip/src/main/java/org/openhab/core/config/discovery/addon/ip/IpAddonFinder.java
new file mode 100644
index 00000000000..ab6175074c9
--- /dev/null
+++ b/bundles/org.openhab.core.config.discovery.addon.ip/src/main/java/org/openhab/core/config/discovery/addon/ip/IpAddonFinder.java
@@ -0,0 +1,179 @@
+/**
+ * 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.core.config.discovery.addon.ip;
+
+import static org.openhab.core.config.discovery.addon.AddonFinderConstants.SERVICE_NAME_IP;
+import static org.openhab.core.config.discovery.addon.AddonFinderConstants.SERVICE_TYPE_IP;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.net.Inet4Address;
+import java.net.InetAddress;
+import java.net.InetSocketAddress;
+import java.net.SocketAddress;
+import java.net.StandardProtocolFamily;
+import java.net.StandardSocketOptions;
+import java.net.UnknownHostException;
+import java.nio.ByteBuffer;
+import java.nio.channels.DatagramChannel;
+import java.nio.channels.SelectionKey;
+import java.nio.channels.Selector;
+import java.util.HashSet;
+import java.util.HexFormat;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import java.util.Set;
+import java.util.StringTokenizer;
+import java.util.stream.Collectors;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.eclipse.jdt.annotation.Nullable;
+import org.openhab.core.addon.AddonDiscoveryMethod;
+import org.openhab.core.addon.AddonInfo;
+import org.openhab.core.config.discovery.addon.AddonFinder;
+import org.openhab.core.config.discovery.addon.BaseAddonFinder;
+import org.openhab.core.net.NetUtil;
+import org.osgi.service.component.annotations.Activate;
+import org.osgi.service.component.annotations.Component;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * This is a {@link IpAddonFinder} for finding suggested add-ons by sending IP packets to the
+ * network and collecting responses.
+ *
+ * @author Holger Friedrich - Initial contribution
+ */
+@NonNullByDefault
+@Component(service = AddonFinder.class, name = IpAddonFinder.SERVICE_NAME)
+public class IpAddonFinder extends BaseAddonFinder {
+
+ public static final String SERVICE_TYPE = SERVICE_TYPE_IP;
+ public static final String SERVICE_NAME = SERVICE_NAME_IP;
+
+ private final Logger logger = LoggerFactory.getLogger(IpAddonFinder.class);
+
+ @Activate
+ public IpAddonFinder() {
+ logger.warn("IpAddonFinder::IpAddonFinder");
+ }
+
+ Set scan() {
+ Set result = new HashSet<>();
+ for (AddonInfo candidate : addonCandidates) {
+ for (AddonDiscoveryMethod method : candidate.getDiscoveryMethods().stream()
+ .filter(method -> SERVICE_TYPE.equals(method.getServiceType())).toList()) {
+
+ Map matchProperties = method.getMatchProperties().stream()
+ .collect(Collectors.toMap(property -> property.getName(), property -> property.getRegex()));
+
+ String type = matchProperties.get("type");
+ String request = matchProperties.get("request");
+ int timeoutMs = Integer.parseInt(Objects.toString(matchProperties.get("timeout_ms")));
+ @Nullable
+ InetAddress destIp = null;
+ try {
+ destIp = InetAddress.getByName(matchProperties.get("dest_ip"));
+ } catch (UnknownHostException e) {
+ // TODO Auto-generated catch block
+
+ }
+ int destPort = Integer.parseInt(Objects.toString(matchProperties.get("dest_port")));
+
+ if ("ip_multicast".equals(type)) {
+
+ List ipAddresses = NetUtil.getAllInterfaceAddresses().stream()
+ .filter(a -> a.getAddress() instanceof Inet4Address)
+ .map(a -> a.getAddress().getHostAddress()).toList();
+
+ for (String localIp : ipAddresses) {
+ try {
+
+ DatagramChannel channel = (DatagramChannel) DatagramChannel
+ .open(StandardProtocolFamily.INET)
+ .setOption(StandardSocketOptions.SO_REUSEADDR, true)
+ .bind(new InetSocketAddress(localIp, 0))
+ .setOption(StandardSocketOptions.IP_MULTICAST_TTL, 64).configureBlocking(false);
+ InetSocketAddress sock = (InetSocketAddress) channel.getLocalAddress();
+
+ ByteArrayOutputStream requestFrame = new ByteArrayOutputStream();
+ StringTokenizer parts = new StringTokenizer(request);
+
+ while (parts.hasMoreTokens()) {
+ String token = parts.nextToken();
+ if (token.startsWith("$")) {
+ switch (token) {
+ case "$src_ip":
+ byte[] adr = sock.getAddress().getAddress();
+ requestFrame.write(adr);
+ break;
+ case "$src_port":
+ int dPort = sock.getPort();
+ requestFrame.write((byte) ((dPort >> 8) & 0xff));
+ requestFrame.write((byte) (dPort & 0xff));
+ break;
+ default:
+ logger.warn("unknown token");
+ }
+ } else {
+ int i = Integer.decode(token);
+ requestFrame.write((byte) i);
+ }
+ }
+ logger.info("{}", HexFormat.of().withDelimiter(" ").formatHex(requestFrame.toByteArray()));
+
+ channel.send(ByteBuffer.wrap(requestFrame.toByteArray()),
+ new InetSocketAddress(destIp, destPort));
+
+ // listen to responses
+ Selector selector = Selector.open();
+ ByteBuffer buffer = ByteBuffer.wrap(new byte[50]);
+ channel.register(selector, SelectionKey.OP_READ);
+ selector.select(timeoutMs);
+ Iterator it = selector.selectedKeys().iterator();
+ if (it.hasNext()) {
+ final SocketAddress source = ((DatagramChannel) it.next().channel()).receive(buffer);
+ logger.debug("Received return frame from {}",
+ ((InetSocketAddress) source).getAddress().getHostAddress());
+ result.add(candidate);
+ } else {
+ logger.debug("no response");
+ }
+
+ } catch (IOException e) {
+ logger.trace("KNXnet/IP discovery failed on {}", localIp, e);
+ }
+ }
+ }
+ }
+ }
+ return result;
+ }
+
+ @Override
+ public Set getSuggestedAddons() {
+ logger.trace("IpAddonFinder::getSuggestedAddons");
+ Set result = new HashSet<>();
+
+ result = scan();
+
+ return result;
+ }
+
+ @Override
+ public String getServiceName() {
+ return SERVICE_NAME;
+ }
+}
diff --git a/bundles/org.openhab.core.config.discovery.addon/src/main/java/org/openhab/core/config/discovery/addon/AddonFinderConstants.java b/bundles/org.openhab.core.config.discovery.addon/src/main/java/org/openhab/core/config/discovery/addon/AddonFinderConstants.java
index 596498898d5..ffb15a5577d 100644
--- a/bundles/org.openhab.core.config.discovery.addon/src/main/java/org/openhab/core/config/discovery/addon/AddonFinderConstants.java
+++ b/bundles/org.openhab.core.config.discovery.addon/src/main/java/org/openhab/core/config/discovery/addon/AddonFinderConstants.java
@@ -28,6 +28,11 @@ public class AddonFinderConstants {
private static final String ADDON_SUGGESTION_FINDER = "-addon-suggestion-finder";
private static final String ADDON_SUGGESTION_FINDER_FEATURE = "openhab-core-config-discovery-addon-";
+ public static final String SERVICE_TYPE_IP = "ip";
+ public static final String CFG_FINDER_IP = "suggestionFinderIp";
+ public static final String SERVICE_NAME_IP = SERVICE_TYPE_IP + ADDON_SUGGESTION_FINDER;
+ public static final String FEATURE_IP = ADDON_SUGGESTION_FINDER_FEATURE + SERVICE_TYPE_IP;
+
public static final String SERVICE_TYPE_MDNS = "mdns";
public static final String CFG_FINDER_MDNS = "suggestionFinderMdns";
public static final String SERVICE_NAME_MDNS = SERVICE_TYPE_MDNS + ADDON_SUGGESTION_FINDER;
@@ -38,9 +43,10 @@ public class AddonFinderConstants {
public static final String SERVICE_NAME_UPNP = SERVICE_TYPE_UPNP + ADDON_SUGGESTION_FINDER;
public static final String FEATURE_UPNP = ADDON_SUGGESTION_FINDER_FEATURE + SERVICE_TYPE_UPNP;
- public static final List SUGGESTION_FINDERS = List.of(SERVICE_NAME_MDNS, SERVICE_NAME_UPNP);
- public static final Map SUGGESTION_FINDER_CONFIGS = Map.of(SERVICE_NAME_MDNS, CFG_FINDER_MDNS,
- SERVICE_NAME_UPNP, CFG_FINDER_UPNP);
- public static final Map SUGGESTION_FINDER_FEATURES = Map.of(SERVICE_NAME_MDNS, FEATURE_MDNS,
- SERVICE_NAME_UPNP, FEATURE_UPNP);
+ public static final List SUGGESTION_FINDERS = List.of(SERVICE_NAME_IP, SERVICE_NAME_MDNS,
+ SERVICE_NAME_UPNP);
+ public static final Map SUGGESTION_FINDER_CONFIGS = Map.of(SERVICE_NAME_IP, CFG_FINDER_IP,
+ SERVICE_NAME_MDNS, CFG_FINDER_MDNS, SERVICE_NAME_UPNP, CFG_FINDER_UPNP);
+ public static final Map SUGGESTION_FINDER_FEATURES = Map.of(SERVICE_NAME_IP, FEATURE_IP,
+ SERVICE_NAME_MDNS, FEATURE_MDNS, SERVICE_NAME_UPNP, FEATURE_UPNP);
}
diff --git a/bundles/org.openhab.core/src/main/resources/OH-INF/config/addons.xml b/bundles/org.openhab.core/src/main/resources/OH-INF/config/addons.xml
index 8859ef7814f..f175329fd41 100644
--- a/bundles/org.openhab.core/src/main/resources/OH-INF/config/addons.xml
+++ b/bundles/org.openhab.core/src/main/resources/OH-INF/config/addons.xml
@@ -29,6 +29,12 @@
Use mDNS network scan to suggest add-ons. Enabling/disabling may take up to 1 minute.
true
+
+ true
+
+ Use IP network scan to suggest add-ons. Enabling/disabling may take up to 1 minute.
+ true
+
diff --git a/bundles/org.openhab.core/src/main/resources/OH-INF/i18n/addons.properties b/bundles/org.openhab.core/src/main/resources/OH-INF/i18n/addons.properties
index 46f8519531d..f4fde0f03d5 100644
--- a/bundles/org.openhab.core/src/main/resources/OH-INF/i18n/addons.properties
+++ b/bundles/org.openhab.core/src/main/resources/OH-INF/i18n/addons.properties
@@ -2,6 +2,8 @@ system.config.addons.includeIncompatible.label = Include (Potentially) Incompati
system.config.addons.includeIncompatible.description = Some add-on services may provide add-ons where compatibility with the currently running system is not expected. Enabling this option will include these entries in the list of available add-ons.
system.config.addons.remote.label = Access Remote Repository
system.config.addons.remote.description = Defines whether openHAB should access the remote repository for add-on installation.
+system.config.addons.suggestionFinderIp.label = IP-based Suggestion Finder
+system.config.addons.suggestionFinderIp.description = Use IP network scan to suggest add-ons. Enabling/disabling may take up to 1 minute.
system.config.addons.suggestionFinderMdns.label = mDNS Suggestion Finder
system.config.addons.suggestionFinderMdns.description = Use mDNS network scan to suggest add-ons. Enabling/disabling may take up to 1 minute.
system.config.addons.suggestionFinderUpnp.label = UPnP Suggestion Finder
diff --git a/bundles/pom.xml b/bundles/pom.xml
index 01799d77195..5110022de5a 100644
--- a/bundles/pom.xml
+++ b/bundles/pom.xml
@@ -31,6 +31,7 @@
org.openhab.core.config.core
org.openhab.core.config.discovery
org.openhab.core.config.discovery.addon
+ org.openhab.core.config.discovery.addon.ip
org.openhab.core.config.discovery.addon.mdns
org.openhab.core.config.discovery.addon.upnp
org.openhab.core.config.discovery.mdns
diff --git a/features/karaf/openhab-core/src/main/feature/feature.xml b/features/karaf/openhab-core/src/main/feature/feature.xml
index 5062c2ead67..336e0e6e854 100644
--- a/features/karaf/openhab-core/src/main/feature/feature.xml
+++ b/features/karaf/openhab-core/src/main/feature/feature.xml
@@ -83,6 +83,12 @@
openhab.tp-jmdns
+
+ openhab-core-base
+ openhab-core-config-discovery-addon
+ mvn:org.openhab.core.bundles/org.openhab.core.config.discovery.addon.ip/${project.version}
+
+
openhab-core-base
openhab-core-config-discovery-addon