Skip to content

Commit

Permalink
Service to suggest addons via generic IP scan
Browse files Browse the repository at this point in the history
Signed-off-by: Holger Friedrich <mail@holger-friedrich.de>
  • Loading branch information
holgerfriedrich committed Dec 13, 2023
1 parent 8082daa commit 9fb0fb2
Show file tree
Hide file tree
Showing 11 changed files with 306 additions and 5 deletions.
6 changes: 6 additions & 0 deletions bom/openhab-core/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -310,6 +310,12 @@
<version>${project.version}</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>org.openhab.core.bundles</groupId>
<artifactId>org.openhab.core.config.discovery.addon.ip</artifactId>
<version>${project.version}</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>org.openhab.core.bundles</groupId>
<artifactId>org.openhab.core.config.discovery.addon.mdns</artifactId>
Expand Down
29 changes: 29 additions & 0 deletions bundles/org.openhab.core.config.discovery.addon.ip/.classpath
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
<?xml version="1.0" encoding="UTF-8"?>
<classpath>
<classpathentry kind="src" output="target/classes" path="src/main/java">
<attributes>
<attribute name="optional" value="true"/>
<attribute name="maven.pomderived" value="true"/>
</attributes>
</classpathentry>
<classpathentry kind="src" output="target/test-classes" path="src/test/java">
<attributes>
<attribute name="test" value="true"/>
<attribute name="optional" value="true"/>
<attribute name="maven.pomderived" value="true"/>
</attributes>
</classpathentry>
<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-17">
<attributes>
<attribute name="maven.pomderived" value="true"/>
<attribute name="annotationpath" value="target/dependency"/>
</attributes>
</classpathentry>
<classpathentry kind="con" path="org.eclipse.m2e.MAVEN2_CLASSPATH_CONTAINER">
<attributes>
<attribute name="maven.pomderived" value="true"/>
<attribute name="annotationpath" value="target/dependency"/>
</attributes>
</classpathentry>
<classpathentry kind="output" path="target/classes"/>
</classpath>
23 changes: 23 additions & 0 deletions bundles/org.openhab.core.config.discovery.addon.ip/.project
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
<?xml version="1.0" encoding="UTF-8"?>
<projectDescription>
<name>org.openhab.core.config.discovery.addon.ip</name>
<comment></comment>
<projects>
</projects>
<buildSpec>
<buildCommand>
<name>org.eclipse.jdt.core.javabuilder</name>
<arguments>
</arguments>
</buildCommand>
<buildCommand>
<name>org.eclipse.m2e.core.maven2Builder</name>
<arguments>
</arguments>
</buildCommand>
</buildSpec>
<natures>
<nature>org.eclipse.m2e.core.maven2Nature</nature>
<nature>org.eclipse.jdt.core.javanature</nature>
</natures>
</projectDescription>
14 changes: 14 additions & 0 deletions bundles/org.openhab.core.config.discovery.addon.ip/NOTICE
Original file line number Diff line number Diff line change
@@ -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

29 changes: 29 additions & 0 deletions bundles/org.openhab.core.config.discovery.addon.ip/pom.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">

<modelVersion>4.0.0</modelVersion>

<parent>
<groupId>org.openhab.core.bundles</groupId>
<artifactId>org.openhab.core.reactor.bundles</artifactId>
<version>4.1.0-SNAPSHOT</version>
</parent>

<artifactId>org.openhab.core.config.discovery.addon.ip</artifactId>

<name>openHAB Core :: Bundles :: IP-based Suggested Add-on Finder</name>

<dependencies>
<dependency>
<groupId>org.openhab.core.bundles</groupId>
<artifactId>org.openhab.core.config.discovery.addon</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.openhab.core.bundles</groupId>
<artifactId>org.openhab.core.addon</artifactId>
<version>${project.version}</version>
</dependency>
</dependencies>
</project>
Original file line number Diff line number Diff line change
@@ -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<AddonInfo> scan() {
Set<AddonInfo> result = new HashSet<>();
for (AddonInfo candidate : addonCandidates) {
for (AddonDiscoveryMethod method : candidate.getDiscoveryMethods().stream()
.filter(method -> SERVICE_TYPE.equals(method.getServiceType())).toList()) {

Map<String, String> 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<String> 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<SelectionKey> 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<AddonInfo> getSuggestedAddons() {
logger.trace("IpAddonFinder::getSuggestedAddons");
Set<AddonInfo> result = new HashSet<>();

result = scan();

return result;
}

@Override
public String getServiceName() {
return SERVICE_NAME;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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<String> SUGGESTION_FINDERS = List.of(SERVICE_NAME_MDNS, SERVICE_NAME_UPNP);
public static final Map<String, String> SUGGESTION_FINDER_CONFIGS = Map.of(SERVICE_NAME_MDNS, CFG_FINDER_MDNS,
SERVICE_NAME_UPNP, CFG_FINDER_UPNP);
public static final Map<String, String> SUGGESTION_FINDER_FEATURES = Map.of(SERVICE_NAME_MDNS, FEATURE_MDNS,
SERVICE_NAME_UPNP, FEATURE_UPNP);
public static final List<String> SUGGESTION_FINDERS = List.of(SERVICE_NAME_IP, SERVICE_NAME_MDNS,
SERVICE_NAME_UPNP);
public static final Map<String, String> 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<String, String> SUGGESTION_FINDER_FEATURES = Map.of(SERVICE_NAME_IP, FEATURE_IP,
SERVICE_NAME_MDNS, FEATURE_MDNS, SERVICE_NAME_UPNP, FEATURE_UPNP);
}
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,12 @@
<description>Use mDNS network scan to suggest add-ons. Enabling/disabling may take up to 1 minute.</description>
<default>true</default>
</parameter>
<parameter name="suggestionFinderIp" type="boolean">
<advanced>true</advanced>
<label>IP-based Suggestion Finder</label>
<description>Use IP network scan to suggest add-ons. Enabling/disabling may take up to 1 minute.</description>
<default>true</default>
</parameter>
</config-description>

</config-description:config-descriptions>
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
1 change: 1 addition & 0 deletions bundles/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
<module>org.openhab.core.config.core</module>
<module>org.openhab.core.config.discovery</module>
<module>org.openhab.core.config.discovery.addon</module>
<module>org.openhab.core.config.discovery.addon.ip</module>
<module>org.openhab.core.config.discovery.addon.mdns</module>
<module>org.openhab.core.config.discovery.addon.upnp</module>
<module>org.openhab.core.config.discovery.mdns</module>
Expand Down
6 changes: 6 additions & 0 deletions features/karaf/openhab-core/src/main/feature/feature.xml
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,12 @@
<feature dependency="true">openhab.tp-jmdns</feature>
</feature>

<feature name="openhab-core-config-discovery-addon-ip" version="${project.version}">
<feature>openhab-core-base</feature>
<feature>openhab-core-config-discovery-addon</feature>
<bundle>mvn:org.openhab.core.bundles/org.openhab.core.config.discovery.addon.ip/${project.version}</bundle>
</feature>

<feature name="openhab-core-config-discovery-addon-upnp" version="${project.version}">
<feature>openhab-core-base</feature>
<feature>openhab-core-config-discovery-addon</feature>
Expand Down

0 comments on commit 9fb0fb2

Please sign in to comment.