Add-on suggestion finder for USB devices (#3922)

Also-by: Holger Friedrich <mail@holger-friedrich.de>
Signed-off-by: Andrew Fiddian-Green <software@whitebear.ch>
This commit is contained in:
Andrew Fiddian-Green 2023-12-27 17:27:40 +00:00 committed by GitHub
parent f5bd7f94f3
commit a93f3d7d90
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
17 changed files with 451 additions and 12 deletions

View File

@ -334,6 +334,12 @@
<version>${project.version}</version> <version>${project.version}</version>
<scope>compile</scope> <scope>compile</scope>
</dependency> </dependency>
<dependency>
<groupId>org.openhab.core.bundles</groupId>
<artifactId>org.openhab.core.config.discovery.addon.usb</artifactId>
<version>${project.version}</version>
<scope>compile</scope>
</dependency>
<dependency> <dependency>
<groupId>org.openhab.core.bundles</groupId> <groupId>org.openhab.core.bundles</groupId>
<artifactId>org.openhab.core.config.discovery.mdns</artifactId> <artifactId>org.openhab.core.config.discovery.mdns</artifactId>

View File

@ -105,7 +105,7 @@ public class ProcessAddonFinder extends BaseAddonFinder {
} }
// now check if a process matches the pattern defined in addon.xml // now check if a process matches the pattern defined in addon.xml
logger.debug("Checking candidate: {}", candidate.getUID()); logger.trace("Checking candidate: {}", candidate.getUID());
for (AddonMatchProperty command : commands) { for (AddonMatchProperty command : commands) {
logger.trace("Candidate {}, pattern \"{}\"", candidate.getUID(), command.getRegex()); logger.trace("Candidate {}, pattern \"{}\"", candidate.getUID(), command.getRegex());

View File

@ -188,7 +188,7 @@ public class UpnpAddonFinder extends BaseAddonFinder implements RegistryListener
&& propertyMatches(matchProperties, SERIAL_NUMBER, serialNumber) && propertyMatches(matchProperties, SERIAL_NUMBER, serialNumber)
&& propertyMatches(matchProperties, FRIENDLY_NAME, friendlyName)) { && propertyMatches(matchProperties, FRIENDLY_NAME, friendlyName)) {
result.add(candidate); result.add(candidate);
logger.debug("Suggested addon found: {}", candidate.getUID()); logger.debug("Suggested add-on found: {}", candidate.getUID());
break; break;
} }
} }

View File

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

View File

@ -0,0 +1,23 @@
<?xml version="1.0" encoding="UTF-8"?>
<projectDescription>
<name>org.openhab.core.config.discovery.addon.usb</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>

View File

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

View File

@ -0,0 +1,35 @@
<?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.2.0-SNAPSHOT</version>
</parent>
<artifactId>org.openhab.core.config.discovery.addon.usb</artifactId>
<name>openHAB Core :: Bundles :: USB 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>
<dependency>
<groupId>org.openhab.core.bundles</groupId>
<artifactId>org.openhab.core.config.discovery.usbserial</artifactId>
<version>${project.version}</version>
</dependency>
</dependencies>
</project>

View File

@ -0,0 +1,186 @@
/**
* 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.usb;
import static org.openhab.core.config.discovery.addon.AddonFinderConstants.SERVICE_NAME_USB;
import static org.openhab.core.config.discovery.addon.AddonFinderConstants.SERVICE_TYPE_USB;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CopyOnWriteArraySet;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
import org.eclipse.jdt.annotation.NonNullByDefault;
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.config.discovery.usbserial.UsbSerialDeviceInformation;
import org.openhab.core.config.discovery.usbserial.UsbSerialDiscovery;
import org.openhab.core.config.discovery.usbserial.UsbSerialDiscoveryListener;
import org.osgi.service.component.annotations.Component;
import org.osgi.service.component.annotations.Reference;
import org.osgi.service.component.annotations.ReferenceCardinality;
import org.osgi.service.component.annotations.ReferencePolicy;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* This is a {@link USBAddonFinder} for finding suggested add-ons related to USB devices.
* <p>
* It supports the following values for the 'match-property' 'name' element:
* <li>product - match on the product description text
* <li>manufacturer - match on the device manufacturer text
* <li>chipId - match on the chip vendor id plus product id
* <li>remote - match on whether the device is connected remotely or locally
*
* @author Andrew Fiddian-Green - Initial contribution
*/
@NonNullByDefault
@Component(service = AddonFinder.class, name = UsbAddonFinder.SERVICE_NAME)
public class UsbAddonFinder extends BaseAddonFinder implements UsbSerialDiscoveryListener {
public static final String SERVICE_TYPE = SERVICE_TYPE_USB;
public static final String SERVICE_NAME = SERVICE_NAME_USB;
/*
* Supported 'match-property' names
*/
public static final String PRODUCT = "product";
public static final String MANUFACTURER = "manufacturer";
public static final String CHIP_ID = "chipId";
public static final String REMOTE = "remote";
public static final Set<String> SUPPORTED_PROPERTIES = Set.of(PRODUCT, MANUFACTURER, CHIP_ID, REMOTE);
private final Logger logger = LoggerFactory.getLogger(UsbAddonFinder.class);
private final Set<UsbSerialDiscovery> usbSerialDiscoveries = new CopyOnWriteArraySet<>();
private final Map<Long, UsbSerialDeviceInformation> usbDeviceInformations = new ConcurrentHashMap<>();
@Reference(cardinality = ReferenceCardinality.MULTIPLE, policy = ReferencePolicy.DYNAMIC)
protected void addUsbSerialDiscovery(UsbSerialDiscovery usbSerialDiscovery) {
usbSerialDiscoveries.add(usbSerialDiscovery);
usbSerialDiscovery.registerDiscoveryListener(this);
usbSerialDiscovery.doSingleScan();
}
protected synchronized void removeUsbSerialDiscovery(UsbSerialDiscovery usbSerialDiscovery) {
usbSerialDiscovery.unregisterDiscoveryListener(this);
usbSerialDiscoveries.remove(usbSerialDiscovery);
}
@Override
public Set<AddonInfo> getSuggestedAddons() {
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, Pattern> matchProperties = method.getMatchProperties().stream()
.collect(Collectors.toMap(property -> property.getName(), property -> property.getPattern()));
Set<String> propertyNames = new HashSet<>(matchProperties.keySet());
propertyNames.removeAll(SUPPORTED_PROPERTIES);
if (!propertyNames.isEmpty()) {
logger.warn("Add-on '{}' addon.xml file contains unsupported 'match-property' [{}]",
candidate.getUID(), String.join(",", propertyNames));
break;
}
logger.trace("Checking candidate: {}", candidate.getUID());
for (UsbSerialDeviceInformation device : usbDeviceInformations.values()) {
logger.trace("Checking device: {}", device);
if (propertyMatches(matchProperties, PRODUCT, device.getProduct())
&& propertyMatches(matchProperties, MANUFACTURER, device.getManufacturer())
&& propertyMatches(matchProperties, CHIP_ID,
getChipId(device.getVendorId(), device.getProductId()))
&& propertyMatches(matchProperties, REMOTE, String.valueOf(device.getRemote()))) {
result.add(candidate);
logger.debug("Suggested add-on found: {}", candidate.getUID());
break;
}
}
}
}
return result;
}
private String getChipId(int vendorId, int productId) {
return String.format("%04x:%04x", vendorId, productId);
}
@Override
public String getServiceName() {
return SERVICE_NAME;
}
/**
* Create a unique 33 bit integer map hash key comprising the remote flag in the upper bit, the vendorId in the
* middle 16 bits, and the productId in the lower 16 bits.
*/
private long keyOf(UsbSerialDeviceInformation deviceInfo) {
return (deviceInfo.getRemote() ? 0x1_0000_0000L : 0) + (deviceInfo.getVendorId() * 0x1_0000L)
+ deviceInfo.getProductId();
}
/**
* Add the discovered USB device information record to our internal map. If there is already an entry in the map
* then merge the two sets of data.
*
* @param discoveredInfo the newly discovered USB device information.
*/
@Override
public void usbSerialDeviceDiscovered(UsbSerialDeviceInformation discoveredInfo) {
UsbSerialDeviceInformation targetInfo = discoveredInfo;
UsbSerialDeviceInformation existingInfo = usbDeviceInformations.get(keyOf(targetInfo));
if (existingInfo != null) {
boolean isMerging = false;
String product = existingInfo.getProduct();
if (product != null) {
product = discoveredInfo.getProduct();
isMerging = true;
}
String manufacturer = existingInfo.getManufacturer();
if (manufacturer != null) {
manufacturer = discoveredInfo.getManufacturer();
isMerging = true;
}
String serialNumber = existingInfo.getSerialNumber();
if (serialNumber != null) {
serialNumber = discoveredInfo.getSerialNumber();
isMerging = true;
}
boolean remote = existingInfo.getRemote();
if (remote == discoveredInfo.getRemote()) {
isMerging = true;
}
if (isMerging) {
targetInfo = new UsbSerialDeviceInformation(discoveredInfo.getVendorId(), discoveredInfo.getProductId(),
serialNumber, manufacturer, product, discoveredInfo.getInterfaceNumber(),
discoveredInfo.getInterfaceDescription(), discoveredInfo.getSerialPort()).setRemote(remote);
}
}
usbDeviceInformations.put(keyOf(targetInfo), targetInfo);
}
@Override
public void usbSerialDeviceRemoved(UsbSerialDeviceInformation removedInfo) {
usbDeviceInformations.remove(keyOf(removedInfo));
}
}

View File

@ -0,0 +1,101 @@
/**
* 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.usb;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertTrue;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.TestInstance;
import org.junit.jupiter.api.TestInstance.Lifecycle;
import org.openhab.core.addon.AddonDiscoveryMethod;
import org.openhab.core.addon.AddonInfo;
import org.openhab.core.addon.AddonMatchProperty;
import org.openhab.core.config.discovery.usbserial.UsbSerialDeviceInformation;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import ch.qos.logback.classic.Level;
/**
* This contains tests for the {@link UsbAddonFinder} class.
*
* @author Andrew Fiddian-Green - Initial contribution
*/
@NonNullByDefault
@TestInstance(Lifecycle.PER_CLASS)
class UsbAddonFinderTests {
@Test
void testSuggestionFinder() {
Logger root = LoggerFactory.getLogger(org.slf4j.Logger.ROOT_LOGGER_NAME);
((ch.qos.logback.classic.Logger) root).setLevel(Level.ERROR);
AddonMatchProperty matchProperty = new AddonMatchProperty("product", "(?i).*zigbee.*");
AddonDiscoveryMethod discoveryMethod = new AddonDiscoveryMethod();
discoveryMethod.setMatchProperties(List.of(matchProperty)).setServiceType("usb");
List<AddonInfo> addons = new ArrayList<>();
addons.add(AddonInfo.builder("id", "binding").withName("name").withDescription("description")
.withDiscoveryMethods(List.of(discoveryMethod)).build());
UsbAddonFinder finder = new UsbAddonFinder();
finder.setAddonCandidates(addons);
finder.usbSerialDeviceDiscovered(
new UsbSerialDeviceInformation(0x123, 0x234, null, null, null, 0, "n/a", "n/a"));
Set<AddonInfo> suggestions = finder.getSuggestedAddons();
assertNotNull(suggestions);
assertTrue(suggestions.isEmpty());
finder.usbSerialDeviceDiscovered(
new UsbSerialDeviceInformation(0x345, 0x456, null, null, "some zigBEE product", 0, "n/a", "n/a"));
suggestions = finder.getSuggestedAddons();
assertNotNull(suggestions);
assertFalse(suggestions.isEmpty());
}
@Test
void testBadSyntax() {
Logger root = LoggerFactory.getLogger(org.slf4j.Logger.ROOT_LOGGER_NAME);
((ch.qos.logback.classic.Logger) root).setLevel(Level.ERROR);
AddonMatchProperty matchProperty = new AddonMatchProperty("aardvark", "(?i).*zigbee.*");
AddonDiscoveryMethod discoveryMethod = new AddonDiscoveryMethod();
discoveryMethod.setMatchProperties(List.of(matchProperty)).setServiceType("usb");
List<AddonInfo> addons = new ArrayList<>();
addons.add(AddonInfo.builder("id", "binding").withName("name").withDescription("description")
.withDiscoveryMethods(List.of(discoveryMethod)).build());
UsbAddonFinder finder = new UsbAddonFinder();
finder.setAddonCandidates(addons);
finder.usbSerialDeviceDiscovered(
new UsbSerialDeviceInformation(0x123, 0x234, null, null, null, 0, "n/a", "n/a"));
Set<AddonInfo> suggestions = finder.getSuggestedAddons();
assertNotNull(suggestions);
assertTrue(suggestions.isEmpty());
}
}

View File

@ -43,10 +43,17 @@ public class AddonFinderConstants {
public static final String SERVICE_NAME_UPNP = SERVICE_TYPE_UPNP + ADDON_SUGGESTION_FINDER; 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 String FEATURE_UPNP = ADDON_SUGGESTION_FINDER_FEATURE + SERVICE_TYPE_UPNP;
public static final List<String> SUGGESTION_FINDERS = List.of(SERVICE_NAME_IP, SERVICE_NAME_MDNS, public static final String SERVICE_TYPE_USB = "usb";
SERVICE_NAME_UPNP); public static final String CFG_FINDER_USB = "suggestionFinderUsb";
public static final String SERVICE_NAME_USB = SERVICE_TYPE_USB + ADDON_SUGGESTION_FINDER;
public static final String FEATURE_USB = ADDON_SUGGESTION_FINDER_FEATURE + SERVICE_TYPE_USB;
public static final List<String> SUGGESTION_FINDERS = List.of(SERVICE_NAME_IP, SERVICE_NAME_MDNS, SERVICE_NAME_UPNP,
SERVICE_NAME_USB);
public static final Map<String, String> SUGGESTION_FINDER_CONFIGS = Map.of(SERVICE_NAME_IP, CFG_FINDER_IP, 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); SERVICE_NAME_MDNS, CFG_FINDER_MDNS, SERVICE_NAME_UPNP, CFG_FINDER_UPNP, SERVICE_NAME_USB, CFG_FINDER_USB);
public static final Map<String, String> SUGGESTION_FINDER_FEATURES = Map.of(SERVICE_NAME_IP, FEATURE_IP, 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); SERVICE_NAME_MDNS, FEATURE_MDNS, SERVICE_NAME_UPNP, FEATURE_UPNP, SERVICE_NAME_USB, FEATURE_USB);
} }

View File

@ -198,7 +198,8 @@ public class Ser2NetUsbSerialDiscovery implements ServiceListener, UsbSerialDisc
serviceInfo.getPort()); serviceInfo.getPort());
UsbSerialDeviceInformation deviceInfo = new UsbSerialDeviceInformation(vendorId, productId, serialNumber, UsbSerialDeviceInformation deviceInfo = new UsbSerialDeviceInformation(vendorId, productId, serialNumber,
manufacturer, product, interfaceNumber, interfaceDescription, serialPortName); manufacturer, product, interfaceNumber, interfaceDescription, serialPortName).setRemote(true);
logger.debug("Created {} based on {}", deviceInfo, serviceInfo); logger.debug("Created {} based on {}", deviceInfo, serviceInfo);
return Optional.of(deviceInfo); return Optional.of(deviceInfo);
} catch (NumberFormatException e) { } catch (NumberFormatException e) {

View File

@ -61,11 +61,11 @@ public class Ser2NetUsbSerialDiscoveryTest {
private @NonNullByDefault({}) Ser2NetUsbSerialDiscovery discovery; private @NonNullByDefault({}) Ser2NetUsbSerialDiscovery discovery;
private UsbSerialDeviceInformation usb1 = new UsbSerialDeviceInformation(0x100, 0x111, "serial1", "manufacturer1", private UsbSerialDeviceInformation usb1 = new UsbSerialDeviceInformation(0x100, 0x111, "serial1", "manufacturer1",
"product1", 0x1, "interface1", "rfc2217://1.1.1.1:1000"); "product1", 0x1, "interface1", "rfc2217://1.1.1.1:1000").setRemote(true);
private UsbSerialDeviceInformation usb2 = new UsbSerialDeviceInformation(0x200, 0x222, "serial2", "manufacturer2", private UsbSerialDeviceInformation usb2 = new UsbSerialDeviceInformation(0x200, 0x222, "serial2", "manufacturer2",
"product2", 0x2, "interface2", "rfc2217://[0:0:0:0:0:ffff:0202:0202]:2222"); "product2", 0x2, "interface2", "rfc2217://[0:0:0:0:0:ffff:0202:0202]:2222").setRemote(true);
private UsbSerialDeviceInformation usb3 = new UsbSerialDeviceInformation(0x300, 0x333, null, null, null, 0x3, null, private UsbSerialDeviceInformation usb3 = new UsbSerialDeviceInformation(0x300, 0x333, null, null, null, 0x3, null,
"rfc2217://123.222.100.000:3030"); "rfc2217://123.222.100.000:3030").setRemote(true);
@BeforeEach @BeforeEach
public void beforeEach() { public void beforeEach() {

View File

@ -45,6 +45,8 @@ public class UsbSerialDeviceInformation {
private final String serialPort; private final String serialPort;
private boolean remote = false;
public UsbSerialDeviceInformation(int vendorId, int productId, @Nullable String serialNumber, public UsbSerialDeviceInformation(int vendorId, int productId, @Nullable String serialNumber,
@Nullable String manufacturer, @Nullable String product, int interfaceNumber, @Nullable String manufacturer, @Nullable String product, int interfaceNumber,
@Nullable String interfaceDescription, String serialPort) { @Nullable String interfaceDescription, String serialPort) {
@ -118,6 +120,21 @@ public class UsbSerialDeviceInformation {
return serialPort; return serialPort;
} }
/**
* @return if the serial device is remote or local.
*/
public boolean getRemote() {
return remote;
}
/**
* Set the remote flag
*/
public UsbSerialDeviceInformation setRemote(boolean remote) {
this.remote = remote;
return this;
}
@SuppressWarnings("null") @SuppressWarnings("null")
@Override @Override
public int hashCode() { public int hashCode() {
@ -131,6 +148,7 @@ public class UsbSerialDeviceInformation {
result = prime * result + ((product == null) ? 0 : product.hashCode()); result = prime * result + ((product == null) ? 0 : product.hashCode());
result = prime * result + ((serialNumber == null) ? 0 : serialNumber.hashCode()); result = prime * result + ((serialNumber == null) ? 0 : serialNumber.hashCode());
result = prime * result + ((interfaceDescription == null) ? 0 : interfaceDescription.hashCode()); result = prime * result + ((interfaceDescription == null) ? 0 : interfaceDescription.hashCode());
result = prime * result + (remote ? 1 : 0);
return result; return result;
} }
@ -180,6 +198,10 @@ public class UsbSerialDeviceInformation {
return false; return false;
} }
if (remote != other.remote) {
return false;
}
return true; return true;
} }
@ -187,8 +209,8 @@ public class UsbSerialDeviceInformation {
public String toString() { public String toString() {
return String.format( return String.format(
"UsbSerialDeviceInformation [vendorId=0x%04X, productId=0x%04X, serialNumber=%s, manufacturer=%s, " "UsbSerialDeviceInformation [vendorId=0x%04X, productId=0x%04X, serialNumber=%s, manufacturer=%s, "
+ "product=%s, interfaceNumber=0x%02X, interfaceDescription=%s, serialPort=%s]", + "product=%s, interfaceNumber=0x%02X, interfaceDescription=%s, serialPort=%s, remote=%b]",
vendorId, productId, serialNumber, manufacturer, product, interfaceNumber, interfaceDescription, vendorId, productId, serialNumber, manufacturer, product, interfaceNumber, interfaceDescription,
serialPort); serialPort, remote);
} }
} }

View File

@ -33,6 +33,11 @@
<advanced>true</advanced> <advanced>true</advanced>
<label>IP-based Suggestion Finder</label> <label>IP-based Suggestion Finder</label>
<description>Use IP network discovery broadcasts to suggest add-ons. Enabling/disabling may take up to 1 minute.</description> <description>Use IP network discovery broadcasts to suggest add-ons. Enabling/disabling may take up to 1 minute.</description>
</parameter>
<parameter name="suggestionFinderUsb" type="boolean">
<advanced>true</advanced>
<label>USB Suggestion Finder</label>
<description>Scan connected USB devices to suggest add-ons. Enabling/disabling may take up to 1 minute.</description>
<default>true</default> <default>true</default>
</parameter> </parameter>
</config-description> </config-description>

View File

@ -8,5 +8,7 @@ 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.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 system.config.addons.suggestionFinderUpnp.label = UPnP Suggestion Finder
system.config.addons.suggestionFinderUpnp.description = Use UPnP network scan to suggest add-ons. Enabling/disabling may take up to 1 minute. system.config.addons.suggestionFinderUpnp.description = Use UPnP network scan to suggest add-ons. Enabling/disabling may take up to 1 minute.
system.config.addons.suggestionFinderUsb.label = USB Suggestion Finder
system.config.addons.suggestionFinderUsb.description = Scan connected USB devices to suggest add-ons. Enabling/disabling may take up to 1 minute.
service.system.addons.label = Add-on Management service.system.addons.label = Add-on Management

View File

@ -35,6 +35,7 @@
<module>org.openhab.core.config.discovery.addon.mdns</module> <module>org.openhab.core.config.discovery.addon.mdns</module>
<module>org.openhab.core.config.discovery.addon.process</module> <module>org.openhab.core.config.discovery.addon.process</module>
<module>org.openhab.core.config.discovery.addon.upnp</module> <module>org.openhab.core.config.discovery.addon.upnp</module>
<module>org.openhab.core.config.discovery.addon.usb</module>
<module>org.openhab.core.config.discovery.mdns</module> <module>org.openhab.core.config.discovery.mdns</module>
<module>org.openhab.core.config.discovery.usbserial</module> <module>org.openhab.core.config.discovery.usbserial</module>
<module>org.openhab.core.config.discovery.usbserial.linuxsysfs</module> <module>org.openhab.core.config.discovery.usbserial.linuxsysfs</module>

View File

@ -98,6 +98,13 @@
<feature dependency="true">openhab.tp-jupnp</feature> <feature dependency="true">openhab.tp-jupnp</feature>
</feature> </feature>
<feature name="openhab-core-config-discovery-addon-usb" version="${project.version}">
<feature>openhab-core-base</feature>
<feature>openhab-core-config-discovery-addon</feature>
<feature dependency="true">openhab-transport-serial</feature>
<bundle>mvn:org.openhab.core.bundles/org.openhab.core.config.discovery.addon.usb/${project.version}</bundle>
</feature>
<feature name="openhab-core-addon-marketplace" version="${project.version}"> <feature name="openhab-core-addon-marketplace" version="${project.version}">
<feature>kar</feature> <feature>kar</feature>
<feature>openhab-core-base</feature> <feature>openhab-core-base</feature>