diff --git a/bom/openhab-core/pom.xml b/bom/openhab-core/pom.xml
index 00c4a1dff15..ad505b5ef4e 100644
--- a/bom/openhab-core/pom.xml
+++ b/bom/openhab-core/pom.xml
@@ -316,6 +316,12 @@
${project.version}
compile
+
+ org.openhab.core.bundles
+ org.openhab.core.config.discovery.usbserial.ser2net
+ ${project.version}
+ compile
+
org.openhab.core.bundles
org.openhab.core.config.discovery.upnp
diff --git a/bundles/org.openhab.core.config.discovery.usbserial.ser2net/.classpath b/bundles/org.openhab.core.config.discovery.usbserial.ser2net/.classpath
new file mode 100644
index 00000000000..4244343f8aa
--- /dev/null
+++ b/bundles/org.openhab.core.config.discovery.usbserial.ser2net/.classpath
@@ -0,0 +1,29 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/bundles/org.openhab.core.config.discovery.usbserial.ser2net/.project b/bundles/org.openhab.core.config.discovery.usbserial.ser2net/.project
new file mode 100644
index 00000000000..30e9c110128
--- /dev/null
+++ b/bundles/org.openhab.core.config.discovery.usbserial.ser2net/.project
@@ -0,0 +1,23 @@
+
+
+ org.openhab.core.config.discovery.usbserial.ser2net
+
+
+
+
+
+ org.eclipse.jdt.core.javabuilder
+
+
+
+
+ org.eclipse.m2e.core.maven2Builder
+
+
+
+
+
+ org.eclipse.jdt.core.javanature
+ org.eclipse.m2e.core.maven2Nature
+
+
diff --git a/bundles/org.openhab.core.config.discovery.usbserial.ser2net/NOTICE b/bundles/org.openhab.core.config.discovery.usbserial.ser2net/NOTICE
new file mode 100644
index 00000000000..6c17d0d8a45
--- /dev/null
+++ b/bundles/org.openhab.core.config.discovery.usbserial.ser2net/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.usbserial.ser2net/pom.xml b/bundles/org.openhab.core.config.discovery.usbserial.ser2net/pom.xml
new file mode 100644
index 00000000000..1384c0fd078
--- /dev/null
+++ b/bundles/org.openhab.core.config.discovery.usbserial.ser2net/pom.xml
@@ -0,0 +1,30 @@
+
+
+
+ 4.0.0
+
+
+ org.openhab.core.bundles
+ org.openhab.core.reactor.bundles
+ 3.2.0-SNAPSHOT
+
+
+ org.openhab.core.config.discovery.usbserial.ser2net
+
+ openHAB Core :: Bundles :: Configuration USB-Serial Discovery using ser2net mDNS scanning
+
+
+
+ org.openhab.core.bundles
+ org.openhab.core.config.discovery.usbserial
+ ${project.version}
+
+
+ org.openhab.core.bundles
+ org.openhab.core.io.transport.mdns
+ ${project.version}
+
+
+
+
diff --git a/bundles/org.openhab.core.config.discovery.usbserial.ser2net/src/main/java/org/openhab/core/config/discovery/usbserial/ser2net/internal/Ser2NetUsbSerialDiscovery.java b/bundles/org.openhab.core.config.discovery.usbserial.ser2net/src/main/java/org/openhab/core/config/discovery/usbserial/ser2net/internal/Ser2NetUsbSerialDiscovery.java
new file mode 100644
index 00000000000..4b7d2ac7c84
--- /dev/null
+++ b/bundles/org.openhab.core.config.discovery.usbserial.ser2net/src/main/java/org/openhab/core/config/discovery/usbserial/ser2net/internal/Ser2NetUsbSerialDiscovery.java
@@ -0,0 +1,206 @@
+/**
+ * Copyright (c) 2010-2021 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.usbserial.ser2net.internal;
+
+import java.time.Duration;
+import java.util.HashSet;
+import java.util.Optional;
+import java.util.Set;
+import java.util.concurrent.CopyOnWriteArraySet;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+
+import javax.jmdns.ServiceEvent;
+import javax.jmdns.ServiceInfo;
+import javax.jmdns.ServiceListener;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+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.openhab.core.io.transport.mdns.MDNSClient;
+import org.osgi.service.component.annotations.Activate;
+import org.osgi.service.component.annotations.Component;
+import org.osgi.service.component.annotations.Reference;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * A {@link UsbSerialDiscovery} that implements background discovery of RFC2217 by listening to
+ * ser2net mDNS service events.
+ *
+ * @author Wouter Born - Initial contribution
+ */
+@NonNullByDefault
+@Component(service = UsbSerialDiscovery.class)
+public class Ser2NetUsbSerialDiscovery implements ServiceListener, UsbSerialDiscovery {
+
+ private final Logger logger = LoggerFactory.getLogger(Ser2NetUsbSerialDiscovery.class);
+
+ static final String SERVICE_TYPE = "_iostream._tcp.local.";
+
+ static final String PROPERTY_PROVIDER = "provider";
+ static final String PROPERTY_DEVICE_TYPE = "devicetype";
+ static final String PROPERTY_GENSIO_STACK = "gensiostack";
+
+ static final String PROPERTY_VENDOR_ID = "idVendor";
+ static final String PROPERTY_PRODUCT_ID = "idProduct";
+
+ static final String PROPERTY_SERIAL_NUMBER = "serial";
+ static final String PROPERTY_MANUFACTURER = "manufacturer";
+ static final String PROPERTY_PRODUCT = "product";
+
+ static final String PROPERTY_INTERFACE_NUMBER = "bInterfaceNumber";
+ static final String PROPERTY_INTERFACE = "interface";
+
+ static final String SER2NET = "ser2net";
+ static final String SERIALUSB = "serialusb";
+ static final String TELNET_RFC2217_TCP = "telnet(rfc2217),tcp";
+
+ static final Duration SINGLE_SCAN_DURATION = Duration.ofSeconds(5);
+ static final String SERIAL_PORT_NAME_FORMAT = "rfc2217://%s:%s";
+
+ private final Set discoveryListeners = new CopyOnWriteArraySet<>();
+ private final MDNSClient mdnsClient;
+
+ private boolean notifyListeners = false;
+
+ private Set lastScanResult = new HashSet<>();
+
+ @Activate
+ public Ser2NetUsbSerialDiscovery(final @Reference MDNSClient mdnsClient) {
+ this.mdnsClient = mdnsClient;
+ }
+
+ @Override
+ public void registerDiscoveryListener(UsbSerialDiscoveryListener listener) {
+ discoveryListeners.add(listener);
+ }
+
+ @Override
+ public void unregisterDiscoveryListener(UsbSerialDiscoveryListener listener) {
+ discoveryListeners.remove(listener);
+ }
+
+ @Override
+ public synchronized void startBackgroundScanning() {
+ notifyListeners = true;
+ mdnsClient.addServiceListener(SERVICE_TYPE, this);
+ logger.debug("Started ser2net USB-Serial mDNS background discovery");
+ }
+
+ @Override
+ public synchronized void stopBackgroundScanning() {
+ notifyListeners = false;
+ mdnsClient.removeServiceListener(SERVICE_TYPE, this);
+ logger.debug("Stopped ser2net USB-Serial mDNS background discovery");
+ }
+
+ @Override
+ public synchronized void doSingleScan() {
+ logger.debug("Starting ser2net USB-Serial mDNS single discovery scan");
+
+ Set scanResult = Stream.of(mdnsClient.list(SERVICE_TYPE, SINGLE_SCAN_DURATION))
+ .map(this::createUsbSerialDeviceInformation) //
+ .filter(Optional::isPresent) //
+ .map(Optional::get) //
+ .collect(Collectors.toSet());
+
+ Set added = setDifference(scanResult, lastScanResult);
+ Set removed = setDifference(lastScanResult, scanResult);
+ Set unchanged = setDifference(scanResult, added);
+
+ lastScanResult = scanResult;
+
+ removed.stream().forEach(this::announceRemovedDevice);
+ added.stream().forEach(this::announceAddedDevice);
+ unchanged.stream().forEach(this::announceAddedDevice);
+
+ logger.debug("Completed ser2net USB-Serial mDNS single discovery scan");
+ }
+
+ private Set setDifference(Set set1, Set set2) {
+ Set result = new HashSet<>(set1);
+ result.removeAll(set2);
+ return result;
+ }
+
+ private void announceAddedDevice(UsbSerialDeviceInformation deviceInfo) {
+ for (UsbSerialDiscoveryListener listener : discoveryListeners) {
+ listener.usbSerialDeviceDiscovered(deviceInfo);
+ }
+ }
+
+ private void announceRemovedDevice(UsbSerialDeviceInformation deviceInfo) {
+ for (UsbSerialDiscoveryListener listener : discoveryListeners) {
+ listener.usbSerialDeviceRemoved(deviceInfo);
+ }
+ }
+
+ @Override
+ public void serviceAdded(@NonNullByDefault({}) ServiceEvent event) {
+ if (notifyListeners) {
+ Optional deviceInfo = createUsbSerialDeviceInformation(event.getInfo());
+ deviceInfo.ifPresent(this::announceAddedDevice);
+ }
+ }
+
+ @Override
+ public void serviceRemoved(@NonNullByDefault({}) ServiceEvent event) {
+ if (notifyListeners) {
+ Optional deviceInfo = createUsbSerialDeviceInformation(event.getInfo());
+ deviceInfo.ifPresent(this::announceRemovedDevice);
+ }
+ }
+
+ @Override
+ public void serviceResolved(@NonNullByDefault({}) ServiceEvent event) {
+ serviceAdded(event);
+ }
+
+ private Optional createUsbSerialDeviceInformation(ServiceInfo serviceInfo) {
+ String provider = serviceInfo.getPropertyString(PROPERTY_PROVIDER);
+ String deviceType = serviceInfo.getPropertyString(PROPERTY_DEVICE_TYPE);
+ String gensioStack = serviceInfo.getPropertyString(PROPERTY_GENSIO_STACK);
+
+ // Check ser2net specific properties when present
+ if (SER2NET.equals(provider) && (deviceType != null && !SERIALUSB.equals(deviceType))
+ || (gensioStack != null && !TELNET_RFC2217_TCP.equals(gensioStack))) {
+ logger.debug("Skipping creation of UsbSerialDeviceInformation based on {}", serviceInfo);
+ return Optional.empty();
+ }
+
+ try {
+ int vendorId = Integer.parseInt(serviceInfo.getPropertyString(PROPERTY_VENDOR_ID), 16);
+ int productId = Integer.parseInt(serviceInfo.getPropertyString(PROPERTY_PRODUCT_ID), 16);
+
+ String serialNumber = serviceInfo.getPropertyString(PROPERTY_SERIAL_NUMBER);
+ String manufacturer = serviceInfo.getPropertyString(PROPERTY_MANUFACTURER);
+ String product = serviceInfo.getPropertyString(PROPERTY_PRODUCT);
+
+ int interfaceNumber = Integer.parseInt(serviceInfo.getPropertyString(PROPERTY_INTERFACE_NUMBER), 16);
+ String interfaceDescription = serviceInfo.getPropertyString(PROPERTY_INTERFACE);
+
+ String serialPortName = String.format(SERIAL_PORT_NAME_FORMAT, serviceInfo.getHostAddresses()[0],
+ serviceInfo.getPort());
+
+ UsbSerialDeviceInformation deviceInfo = new UsbSerialDeviceInformation(vendorId, productId, serialNumber,
+ manufacturer, product, interfaceNumber, interfaceDescription, serialPortName);
+ logger.debug("Created {} based on {}", deviceInfo, serviceInfo);
+ return Optional.of(deviceInfo);
+ } catch (NumberFormatException e) {
+ logger.debug("Failed to create UsbSerialDeviceInformation based on {}", serviceInfo, e);
+ return Optional.empty();
+ }
+ }
+}
diff --git a/bundles/org.openhab.core.config.discovery.usbserial.ser2net/src/test/java/org/openhab/core/config/discovery/usbserial/ser2net/internal/Ser2NetUsbSerialDiscoveryTest.java b/bundles/org.openhab.core.config.discovery.usbserial.ser2net/src/test/java/org/openhab/core/config/discovery/usbserial/ser2net/internal/Ser2NetUsbSerialDiscoveryTest.java
new file mode 100644
index 00000000000..6b28f2be411
--- /dev/null
+++ b/bundles/org.openhab.core.config.discovery.usbserial.ser2net/src/test/java/org/openhab/core/config/discovery/usbserial/ser2net/internal/Ser2NetUsbSerialDiscoveryTest.java
@@ -0,0 +1,269 @@
+/**
+ * Copyright (c) 2010-2021 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.usbserial.ser2net.internal;
+
+import static org.mockito.ArgumentMatchers.*;
+import static org.mockito.Mockito.*;
+import static org.openhab.core.config.discovery.usbserial.ser2net.internal.Ser2NetUsbSerialDiscovery.*;
+
+import java.io.IOException;
+import java.time.Duration;
+
+import javax.jmdns.ServiceEvent;
+import javax.jmdns.ServiceInfo;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.mockito.ArgumentMatchers;
+import org.mockito.Mock;
+import org.mockito.junit.jupiter.MockitoExtension;
+import org.mockito.junit.jupiter.MockitoSettings;
+import org.mockito.quality.Strictness;
+import org.openhab.core.config.discovery.usbserial.UsbSerialDeviceInformation;
+import org.openhab.core.config.discovery.usbserial.UsbSerialDiscoveryListener;
+import org.openhab.core.io.transport.mdns.MDNSClient;
+
+/**
+ * Unit tests for the {@link Ser2NetUsbSerialDiscovery}.
+ *
+ * @author Wouter Born - Initial contribution
+ */
+@NonNullByDefault
+@ExtendWith(MockitoExtension.class)
+@MockitoSettings(strictness = Strictness.LENIENT)
+public class Ser2NetUsbSerialDiscoveryTest {
+
+ private @Mock @NonNullByDefault({}) UsbSerialDiscoveryListener discoveryListenerMock;
+ private @Mock @NonNullByDefault({}) MDNSClient mdnsClientMock;
+
+ private @Mock @NonNullByDefault({}) ServiceInfo serviceInfo1Mock;
+ private @Mock @NonNullByDefault({}) ServiceInfo serviceInfo2Mock;
+ private @Mock @NonNullByDefault({}) ServiceInfo serviceInfo3Mock;
+ private @Mock @NonNullByDefault({}) ServiceInfo invalidServiceInfoMock;
+
+ private @Mock @NonNullByDefault({}) ServiceEvent serviceEvent1Mock;
+ private @Mock @NonNullByDefault({}) ServiceEvent serviceEvent2Mock;
+ private @Mock @NonNullByDefault({}) ServiceEvent serviceEvent3Mock;
+ private @Mock @NonNullByDefault({}) ServiceEvent invalidServiceEventMock;
+
+ private @NonNullByDefault({}) Ser2NetUsbSerialDiscovery discovery;
+
+ private UsbSerialDeviceInformation usb1 = new UsbSerialDeviceInformation(0x100, 0x111, "serial1", "manufacturer1",
+ "product1", 0x1, "interface1", "rfc2217://1.1.1.1:1000");
+ private UsbSerialDeviceInformation usb2 = new UsbSerialDeviceInformation(0x200, 0x222, "serial2", "manufacturer2",
+ "product2", 0x2, "interface2", "rfc2217://[0:0:0:0:0:ffff:0202:0202]:2222");
+ private UsbSerialDeviceInformation usb3 = new UsbSerialDeviceInformation(0x300, 0x333, null, null, null, 0x3, null,
+ "rfc2217://123.222.100.000:3030");
+
+ @BeforeEach
+ public void beforeEach() {
+ discovery = new Ser2NetUsbSerialDiscovery(mdnsClientMock);
+ discovery.registerDiscoveryListener(discoveryListenerMock);
+
+ setupServiceInfo1Mock();
+ setupServiceInfo2Mock();
+ setupServiceInfo3Mock();
+ setupInvalidServiceInfoMock();
+
+ when(serviceEvent1Mock.getInfo()).thenReturn(serviceInfo1Mock);
+ when(serviceEvent2Mock.getInfo()).thenReturn(serviceInfo2Mock);
+ when(serviceEvent3Mock.getInfo()).thenReturn(serviceInfo3Mock);
+ when(invalidServiceEventMock.getInfo()).thenReturn(invalidServiceInfoMock);
+ }
+
+ private void setupServiceInfo1Mock() {
+ when(serviceInfo1Mock.getHostAddresses()).thenReturn(new String[] { "1.1.1.1" });
+ when(serviceInfo1Mock.getPort()).thenReturn(1000);
+
+ when(serviceInfo1Mock.getPropertyString(PROPERTY_VENDOR_ID)).thenReturn("0100");
+ when(serviceInfo1Mock.getPropertyString(PROPERTY_PRODUCT_ID)).thenReturn("0111");
+ when(serviceInfo1Mock.getPropertyString(PROPERTY_SERIAL_NUMBER)).thenReturn("serial1");
+ when(serviceInfo1Mock.getPropertyString(PROPERTY_MANUFACTURER)).thenReturn("manufacturer1");
+ when(serviceInfo1Mock.getPropertyString(PROPERTY_PRODUCT)).thenReturn("product1");
+ when(serviceInfo1Mock.getPropertyString(PROPERTY_INTERFACE_NUMBER)).thenReturn("01");
+ when(serviceInfo1Mock.getPropertyString(PROPERTY_INTERFACE)).thenReturn("interface1");
+
+ when(serviceInfo1Mock.getPropertyString(PROPERTY_PROVIDER)).thenReturn(SER2NET);
+ when(serviceInfo1Mock.getPropertyString(PROPERTY_DEVICE_TYPE)).thenReturn(SERIALUSB);
+ when(serviceInfo1Mock.getPropertyString(PROPERTY_GENSIO_STACK)).thenReturn(TELNET_RFC2217_TCP);
+ }
+
+ private void setupServiceInfo2Mock() {
+ when(serviceInfo2Mock.getHostAddresses()).thenReturn(new String[] { "[0:0:0:0:0:ffff:0202:0202]" });
+ when(serviceInfo2Mock.getPort()).thenReturn(2222);
+
+ when(serviceInfo2Mock.getPropertyString(PROPERTY_VENDOR_ID)).thenReturn("0200");
+ when(serviceInfo2Mock.getPropertyString(PROPERTY_PRODUCT_ID)).thenReturn("0222");
+ when(serviceInfo2Mock.getPropertyString(PROPERTY_SERIAL_NUMBER)).thenReturn("serial2");
+ when(serviceInfo2Mock.getPropertyString(PROPERTY_MANUFACTURER)).thenReturn("manufacturer2");
+ when(serviceInfo2Mock.getPropertyString(PROPERTY_PRODUCT)).thenReturn("product2");
+ when(serviceInfo2Mock.getPropertyString(PROPERTY_INTERFACE_NUMBER)).thenReturn("02");
+ when(serviceInfo2Mock.getPropertyString(PROPERTY_INTERFACE)).thenReturn("interface2");
+ }
+
+ private void setupServiceInfo3Mock() {
+ when(serviceInfo3Mock.getHostAddresses()).thenReturn(new String[] { "123.222.100.000" });
+ when(serviceInfo3Mock.getPort()).thenReturn(3030);
+
+ when(serviceInfo3Mock.getPropertyString(PROPERTY_VENDOR_ID)).thenReturn("0300");
+ when(serviceInfo3Mock.getPropertyString(PROPERTY_PRODUCT_ID)).thenReturn("0333");
+ when(serviceInfo3Mock.getPropertyString(PROPERTY_INTERFACE_NUMBER)).thenReturn("03");
+ }
+
+ private void setupInvalidServiceInfoMock() {
+ when(invalidServiceInfoMock.getHostAddresses()).thenReturn(new String[] { "1.1.1.1" });
+ when(invalidServiceInfoMock.getPort()).thenReturn(1000);
+
+ when(invalidServiceInfoMock.getPropertyString(PROPERTY_VENDOR_ID)).thenReturn("invalid");
+ }
+
+ @Test
+ public void noScansWithoutBackgroundDiscovery() throws InterruptedException {
+ // Wait a little more than one second to give background scanning a chance to kick in.
+ Thread.sleep(1200);
+
+ verify(mdnsClientMock, never()).list(anyString());
+ verify(mdnsClientMock, never()).list(anyString(), ArgumentMatchers.any(Duration.class));
+ }
+
+ @Test
+ public void singleScanReportsResultsCorrectAfterOneScan() {
+ when(mdnsClientMock.list(SERVICE_TYPE, SINGLE_SCAN_DURATION))
+ .thenReturn(new ServiceInfo[] { serviceInfo1Mock, serviceInfo2Mock });
+
+ discovery.doSingleScan();
+
+ // Expectation: discovery listener called with newly discovered devices usb1 and usb2; not called with removed
+ // devices.
+
+ verify(discoveryListenerMock, times(1)).usbSerialDeviceDiscovered(usb1);
+ verify(discoveryListenerMock, times(1)).usbSerialDeviceDiscovered(usb2);
+ verify(discoveryListenerMock, never()).usbSerialDeviceDiscovered(usb3);
+
+ verify(discoveryListenerMock, never()).usbSerialDeviceRemoved(any(UsbSerialDeviceInformation.class));
+ }
+
+ @Test
+ public void singleScanReportsResultsCorrectAfterOneScanWithInvalidServiceInfo() {
+ when(mdnsClientMock.list(SERVICE_TYPE, SINGLE_SCAN_DURATION))
+ .thenReturn(new ServiceInfo[] { serviceInfo1Mock, invalidServiceInfoMock, serviceInfo2Mock });
+
+ discovery.doSingleScan();
+
+ // Expectation: discovery listener is still called with newly discovered devices usb1 and usb2; not called with
+ // removed devices.
+
+ verify(discoveryListenerMock, times(1)).usbSerialDeviceDiscovered(usb1);
+ verify(discoveryListenerMock, times(1)).usbSerialDeviceDiscovered(usb2);
+ verify(discoveryListenerMock, never()).usbSerialDeviceDiscovered(usb3);
+
+ verify(discoveryListenerMock, never()).usbSerialDeviceRemoved(any(UsbSerialDeviceInformation.class));
+ }
+
+ @Test
+ public void singleScanReportsResultsCorrectlyAfterTwoScans() {
+ when(mdnsClientMock.list(SERVICE_TYPE, SINGLE_SCAN_DURATION))
+ .thenReturn(new ServiceInfo[] { serviceInfo1Mock, serviceInfo2Mock })
+ .thenReturn(new ServiceInfo[] { serviceInfo2Mock, serviceInfo3Mock });
+
+ discovery.unregisterDiscoveryListener(discoveryListenerMock);
+ discovery.doSingleScan();
+
+ discovery.registerDiscoveryListener(discoveryListenerMock);
+ discovery.doSingleScan();
+
+ // Expectation: discovery listener called once for removing usb1, and once for adding usb2/usb3 each.
+
+ verify(discoveryListenerMock, never()).usbSerialDeviceDiscovered(usb1);
+ verify(discoveryListenerMock, times(1)).usbSerialDeviceRemoved(usb1);
+
+ verify(discoveryListenerMock, times(1)).usbSerialDeviceDiscovered(usb2);
+ verify(discoveryListenerMock, never()).usbSerialDeviceRemoved(usb2);
+
+ verify(discoveryListenerMock, times(1)).usbSerialDeviceDiscovered(usb3);
+ verify(discoveryListenerMock, never()).usbSerialDeviceRemoved(usb3);
+ }
+
+ @Test
+ public void backgroundScanning() {
+ discovery.startBackgroundScanning();
+
+ discovery.serviceAdded(serviceEvent1Mock);
+ discovery.serviceRemoved(serviceEvent1Mock);
+ discovery.serviceAdded(serviceEvent2Mock);
+ discovery.serviceAdded(invalidServiceEventMock);
+ discovery.serviceResolved(serviceEvent3Mock);
+
+ discovery.stopBackgroundScanning();
+
+ // Expectation: discovery listener called once for each discovered device, and once for removal of usb1.
+
+ verify(discoveryListenerMock, times(1)).usbSerialDeviceDiscovered(usb1);
+ verify(discoveryListenerMock, times(1)).usbSerialDeviceRemoved(usb1);
+
+ verify(discoveryListenerMock, times(1)).usbSerialDeviceDiscovered(usb2);
+ verify(discoveryListenerMock, never()).usbSerialDeviceRemoved(usb2);
+
+ verify(discoveryListenerMock, times(1)).usbSerialDeviceDiscovered(usb3);
+ verify(discoveryListenerMock, never()).usbSerialDeviceRemoved(usb3);
+ }
+
+ @Test
+ public void noBackgroundScanning() throws IOException, InterruptedException {
+ discovery.stopBackgroundScanning();
+
+ discovery.serviceAdded(serviceEvent1Mock);
+ discovery.serviceRemoved(serviceEvent1Mock);
+ discovery.serviceAdded(serviceEvent2Mock);
+ discovery.serviceResolved(serviceEvent3Mock);
+
+ // Expectation: discovery listener is never called when there is no background scanning is.
+
+ verify(discoveryListenerMock, never()).usbSerialDeviceDiscovered(usb1);
+ verify(discoveryListenerMock, never()).usbSerialDeviceRemoved(usb1);
+
+ verify(discoveryListenerMock, never()).usbSerialDeviceDiscovered(usb2);
+ verify(discoveryListenerMock, never()).usbSerialDeviceRemoved(usb2);
+
+ verify(discoveryListenerMock, never()).usbSerialDeviceDiscovered(usb3);
+ verify(discoveryListenerMock, never()).usbSerialDeviceRemoved(usb3);
+ }
+
+ @Test
+ public void discoveryChecksSer2NetSpecificProperties() {
+ discovery.startBackgroundScanning();
+
+ when(serviceInfo3Mock.getPropertyString(PROPERTY_PROVIDER)).thenReturn(SER2NET);
+ when(serviceInfo3Mock.getPropertyString(PROPERTY_GENSIO_STACK)).thenReturn("incompatible");
+
+ discovery.serviceAdded(serviceEvent3Mock);
+
+ when(serviceInfo3Mock.getPropertyString(PROPERTY_PROVIDER)).thenReturn(SER2NET);
+ when(serviceInfo3Mock.getPropertyString(PROPERTY_DEVICE_TYPE)).thenReturn("incompatible");
+
+ discovery.serviceAdded(serviceEvent3Mock);
+
+ // Expectation: discovery listener is never called when the ser2net specific properties do not match.
+
+ verify(discoveryListenerMock, never()).usbSerialDeviceDiscovered(usb1);
+ verify(discoveryListenerMock, never()).usbSerialDeviceRemoved(usb1);
+
+ verify(discoveryListenerMock, never()).usbSerialDeviceDiscovered(usb2);
+ verify(discoveryListenerMock, never()).usbSerialDeviceRemoved(usb2);
+
+ verify(discoveryListenerMock, never()).usbSerialDeviceDiscovered(usb3);
+ verify(discoveryListenerMock, never()).usbSerialDeviceRemoved(usb3);
+ }
+}
diff --git a/bundles/org.openhab.core.config.discovery.usbserial/src/main/java/org/openhab/core/config/discovery/usbserial/internal/UsbSerialDiscoveryService.java b/bundles/org.openhab.core.config.discovery.usbserial/src/main/java/org/openhab/core/config/discovery/usbserial/internal/UsbSerialDiscoveryService.java
index 69893fbf9c0..676e64a9e2a 100644
--- a/bundles/org.openhab.core.config.discovery.usbserial/src/main/java/org/openhab/core/config/discovery/usbserial/internal/UsbSerialDiscoveryService.java
+++ b/bundles/org.openhab.core.config.discovery.usbserial/src/main/java/org/openhab/core/config/discovery/usbserial/internal/UsbSerialDiscoveryService.java
@@ -47,14 +47,14 @@
* This discovery service is intended to be used by bindings that support USB devices, but do not directly talk to the
* USB devices but rather use a serial port for the communication, where the serial port is provided by an operating
* system driver outside the scope of openHAB. Examples for such USB devices are USB dongles that provide
- * access to wireless networks, like, e.g., Zigbeee or Zwave dongles.
+ * access to wireless networks, like, e.g., Zigbee or Zwave dongles.
*
* This discovery service provides functionality for discovering added and removed USB devices and the corresponding
* serial ports. The actual {@link DiscoveryResult}s are then provided by {@link UsbSerialDiscoveryParticipant}s, which
* are called by this discovery service whenever new devices are detected or devices are removed. Such
* {@link UsbSerialDiscoveryParticipant}s should be provided by bindings accessing USB devices via a serial port.
*
- * This discovery service requires a component implementing the interface {@link UsbSerialDiscovery}, which performs the
+ * This discovery service requires components implementing the interface {@link UsbSerialDiscovery}, which perform the
* actual serial port and USB device discovery (as this discovery might differ depending on the operating system).
*
* @author Henning Sudbrock - Initial contribution
@@ -70,10 +70,8 @@ public class UsbSerialDiscoveryService extends AbstractDiscoveryService implemen
private static final String THING_PROPERTY_USB_PRODUCT_ID = "usb_product_id";
private final Set discoveryParticipants = new CopyOnWriteArraySet<>();
-
private final Set previouslyDiscovered = new CopyOnWriteArraySet<>();
-
- private @NonNullByDefault({}) UsbSerialDiscovery usbSerialDiscovery;
+ private final Set usbSerialDiscoveries = new CopyOnWriteArraySet<>();
public UsbSerialDiscoveryService() {
super(5);
@@ -83,7 +81,6 @@ public UsbSerialDiscoveryService() {
@Activate
protected void activate(@Nullable Map configProperties) {
super.activate(configProperties);
- usbSerialDiscovery.registerDiscoveryListener(this);
}
@Modified
@@ -100,7 +97,7 @@ protected void deactivate() {
@Reference(cardinality = ReferenceCardinality.MULTIPLE, policy = ReferencePolicy.DYNAMIC)
protected void addUsbSerialDiscoveryParticipant(UsbSerialDiscoveryParticipant participant) {
- this.discoveryParticipants.add(participant);
+ discoveryParticipants.add(participant);
for (UsbSerialDeviceInformation usbSerialDeviceInformation : previouslyDiscovered) {
DiscoveryResult result = participant.createResult(usbSerialDeviceInformation);
if (result != null) {
@@ -110,19 +107,23 @@ protected void addUsbSerialDiscoveryParticipant(UsbSerialDiscoveryParticipant pa
}
protected void removeUsbSerialDiscoveryParticipant(UsbSerialDiscoveryParticipant participant) {
- this.discoveryParticipants.remove(participant);
+ discoveryParticipants.remove(participant);
}
- @Reference
- protected void setUsbSerialDiscovery(UsbSerialDiscovery usbSerialDiscovery) {
- this.usbSerialDiscovery = usbSerialDiscovery;
+ @Reference(cardinality = ReferenceCardinality.MULTIPLE, policy = ReferencePolicy.DYNAMIC)
+ protected void addUsbSerialDiscovery(UsbSerialDiscovery usbSerialDiscovery) {
+ usbSerialDiscoveries.add(usbSerialDiscovery);
+ usbSerialDiscovery.registerDiscoveryListener(this);
+ if (isBackgroundDiscoveryEnabled()) {
+ usbSerialDiscovery.startBackgroundScanning();
+ }
}
- protected synchronized void unsetUsbSerialDiscovery(UsbSerialDiscovery usbSerialDiscovery) {
+ protected synchronized void removeUsbSerialDiscovery(UsbSerialDiscovery usbSerialDiscovery) {
usbSerialDiscovery.stopBackgroundScanning();
usbSerialDiscovery.unregisterDiscoveryListener(this);
- this.usbSerialDiscovery = null;
- this.previouslyDiscovered.clear();
+ usbSerialDiscoveries.remove(usbSerialDiscovery);
+ previouslyDiscovered.clear();
}
@Override
@@ -133,30 +134,17 @@ public Set getSupportedThingTypes() {
@Override
protected void startScan() {
- if (usbSerialDiscovery != null) {
- usbSerialDiscovery.doSingleScan();
- } else {
- logger.info("Could not scan, as there is no USB-Serial discovery service configured.");
- }
+ usbSerialDiscoveries.forEach(UsbSerialDiscovery::doSingleScan);
}
@Override
protected void startBackgroundDiscovery() {
- if (usbSerialDiscovery != null) {
- usbSerialDiscovery.startBackgroundScanning();
- } else {
- logger.info(
- "Could not start background discovery, as there is no USB-Serial discovery service configured.");
- }
+ usbSerialDiscoveries.forEach(UsbSerialDiscovery::startBackgroundScanning);
}
@Override
protected void stopBackgroundDiscovery() {
- if (usbSerialDiscovery != null) {
- usbSerialDiscovery.stopBackgroundScanning();
- } else {
- logger.info("Could not stop background discovery, as there is no USB-Serial discovery service configured.");
- }
+ usbSerialDiscoveries.forEach(UsbSerialDiscovery::stopBackgroundScanning);
}
@Override
diff --git a/bundles/org.openhab.core.config.serial/pom.xml b/bundles/org.openhab.core.config.serial/pom.xml
index 83f4ea7380c..22e833f0ed5 100644
--- a/bundles/org.openhab.core.config.serial/pom.xml
+++ b/bundles/org.openhab.core.config.serial/pom.xml
@@ -25,6 +25,11 @@
org.openhab.core.io.transport.serial
${project.version}
+
+ org.openhab.core.bundles
+ org.openhab.core.config.discovery.usbserial
+ ${project.version}
+
diff --git a/bundles/org.openhab.core.config.serial/src/main/java/org/openhab/core/config/serial/internal/SerialConfigOptionProvider.java b/bundles/org.openhab.core.config.serial/src/main/java/org/openhab/core/config/serial/internal/SerialConfigOptionProvider.java
index 99411929586..2e0449bf57c 100644
--- a/bundles/org.openhab.core.config.serial/src/main/java/org/openhab/core/config/serial/internal/SerialConfigOptionProvider.java
+++ b/bundles/org.openhab.core.config.serial/src/main/java/org/openhab/core/config/serial/internal/SerialConfigOptionProvider.java
@@ -13,43 +13,82 @@
package org.openhab.core.config.serial.internal;
import java.net.URI;
-import java.util.ArrayList;
import java.util.Collection;
-import java.util.List;
import java.util.Locale;
+import java.util.Set;
+import java.util.concurrent.CopyOnWriteArraySet;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.eclipse.jdt.annotation.Nullable;
import org.openhab.core.config.core.ConfigOptionProvider;
import org.openhab.core.config.core.ParameterOption;
+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.openhab.core.io.transport.serial.SerialPortIdentifier;
import org.openhab.core.io.transport.serial.SerialPortManager;
+import org.osgi.service.component.annotations.Activate;
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;
/**
* This service provides serial port names as options for configuration parameters.
*
* @author Kai Kreuzer - Initial contribution
+ * @author Wouter Born - Add discovered USB serial port names to serial port parameter options
*/
+@NonNullByDefault
@Component
-public class SerialConfigOptionProvider implements ConfigOptionProvider {
+public class SerialConfigOptionProvider implements ConfigOptionProvider, UsbSerialDiscoveryListener {
- private SerialPortManager serialPortManager;
+ static final String SERIAL_PORT = "serial-port";
- @Reference
- protected void setSerialPortManager(final SerialPortManager serialPortManager) {
+ private final SerialPortManager serialPortManager;
+ private final Set previouslyDiscovered = new CopyOnWriteArraySet<>();
+ private final Set usbSerialDiscoveries = new CopyOnWriteArraySet<>();
+
+ @Activate
+ public SerialConfigOptionProvider(final @Reference SerialPortManager serialPortManager) {
this.serialPortManager = serialPortManager;
}
- protected void unsetSerialPortManager(final SerialPortManager serialPortManager) {
- this.serialPortManager = null;
+ @Reference(cardinality = ReferenceCardinality.MULTIPLE, policy = ReferencePolicy.DYNAMIC)
+ protected void addUsbSerialDiscovery(UsbSerialDiscovery usbSerialDiscovery) {
+ usbSerialDiscoveries.add(usbSerialDiscovery);
+ usbSerialDiscovery.registerDiscoveryListener(this);
+ }
+
+ protected synchronized void removeUsbSerialDiscovery(UsbSerialDiscovery usbSerialDiscovery) {
+ usbSerialDiscovery.unregisterDiscoveryListener(this);
+ usbSerialDiscoveries.remove(usbSerialDiscovery);
+ previouslyDiscovered.clear();
+ }
+
+ @Override
+ public void usbSerialDeviceDiscovered(UsbSerialDeviceInformation usbSerialDeviceInformation) {
+ previouslyDiscovered.add(usbSerialDeviceInformation);
+ }
+
+ @Override
+ public void usbSerialDeviceRemoved(UsbSerialDeviceInformation usbSerialDeviceInformation) {
+ previouslyDiscovered.remove(usbSerialDeviceInformation);
}
@Override
- public Collection getParameterOptions(URI uri, String param, String context, Locale locale) {
- List options = new ArrayList<>();
- if ("serial-port".equals(context)) {
- serialPortManager.getIdentifiers()
- .forEach(id -> options.add(new ParameterOption(id.getName(), id.getName())));
+ public @Nullable Collection getParameterOptions(URI uri, String param, @Nullable String context,
+ @Nullable Locale locale) {
+ if (SERIAL_PORT.equals(context)) {
+ return Stream
+ .concat(serialPortManager.getIdentifiers().map(SerialPortIdentifier::getName),
+ previouslyDiscovered.stream().map(UsbSerialDeviceInformation::getSerialPort))
+ .distinct() //
+ .map(serialPortName -> new ParameterOption(serialPortName, serialPortName)) //
+ .collect(Collectors.toList());
}
- return options;
+ return null;
}
}
diff --git a/bundles/org.openhab.core.config.serial/src/test/java/org/openhab/core/config/serial/internal/SerialConfigOptionProviderTest.java b/bundles/org.openhab.core.config.serial/src/test/java/org/openhab/core/config/serial/internal/SerialConfigOptionProviderTest.java
new file mode 100644
index 00000000000..c5394d36e67
--- /dev/null
+++ b/bundles/org.openhab.core.config.serial/src/test/java/org/openhab/core/config/serial/internal/SerialConfigOptionProviderTest.java
@@ -0,0 +1,178 @@
+/**
+ * Copyright (c) 2010-2021 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.serial.internal;
+
+import static org.hamcrest.CoreMatchers.*;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.mockito.Mockito.when;
+import static org.openhab.core.config.serial.internal.SerialConfigOptionProvider.SERIAL_PORT;
+
+import java.net.URI;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.mockito.Mock;
+import org.mockito.junit.jupiter.MockitoExtension;
+import org.mockito.junit.jupiter.MockitoSettings;
+import org.mockito.quality.Strictness;
+import org.openhab.core.config.core.ParameterOption;
+import org.openhab.core.config.discovery.usbserial.UsbSerialDeviceInformation;
+import org.openhab.core.config.discovery.usbserial.UsbSerialDiscovery;
+import org.openhab.core.io.transport.serial.SerialPortIdentifier;
+import org.openhab.core.io.transport.serial.SerialPortManager;
+
+/**
+ * Unit tests for the {@link SerialConfigOptionProvider}.
+ *
+ * @author Wouter Born - Initial contribution
+ */
+@NonNullByDefault
+@ExtendWith(MockitoExtension.class)
+@MockitoSettings(strictness = Strictness.LENIENT)
+public class SerialConfigOptionProviderTest {
+
+ private static final String DEV_TTY_S1 = "/dev/ttyS1";
+ private static final String DEV_TTY_S2 = "/dev/ttyS2";
+ private static final String DEV_TTY_S3 = "/dev/ttyS3";
+
+ private static final String RFC2217_IPV4 = "rfc2217://1.1.1.1:1000";
+ private static final String RFC2217_IPV6 = "rfc2217://[0:0:0:0:0:ffff:0202:0202]:2222";
+
+ private UsbSerialDeviceInformation usb1 = new UsbSerialDeviceInformation(0x100, 0x111, "serial1", "manufacturer1",
+ "product1", 0x1, "interface1", RFC2217_IPV4);
+ private UsbSerialDeviceInformation usb2 = new UsbSerialDeviceInformation(0x200, 0x222, "serial2", "manufacturer2",
+ "product2", 0x2, "interface2", RFC2217_IPV6);
+ private UsbSerialDeviceInformation usb3 = new UsbSerialDeviceInformation(0x300, 0x333, "serial3", "manufacturer3",
+ "product3", 0x3, "interface3", DEV_TTY_S3);
+
+ private @Mock @NonNullByDefault({}) SerialPortManager serialPortManagerMock;
+ private @Mock @NonNullByDefault({}) UsbSerialDiscovery usbSerialDiscoveryMock;
+
+ private @Mock @NonNullByDefault({}) SerialPortIdentifier serialPortIdentifier1Mock;
+ private @Mock @NonNullByDefault({}) SerialPortIdentifier serialPortIdentifier2Mock;
+ private @Mock @NonNullByDefault({}) SerialPortIdentifier serialPortIdentifier3Mock;
+
+ private @NonNullByDefault({}) SerialConfigOptionProvider provider;
+
+ @BeforeEach
+ public void beforeEach() {
+ provider = new SerialConfigOptionProvider(serialPortManagerMock);
+
+ when(serialPortIdentifier1Mock.getName()).thenReturn(DEV_TTY_S1);
+ when(serialPortIdentifier2Mock.getName()).thenReturn(DEV_TTY_S2);
+ when(serialPortIdentifier3Mock.getName()).thenReturn(DEV_TTY_S3);
+ }
+
+ private void assertParameterOptions(String... serialPortIdentifiers) {
+ Collection actual = provider.getParameterOptions(URI.create("uri"), "serialPort", SERIAL_PORT,
+ null);
+ Collection expected = Arrays.stream(serialPortIdentifiers)
+ .map(id -> new ParameterOption(id, id)).collect(Collectors.toList());
+ assertThat(actual, is(expected));
+ }
+
+ @Test
+ public void noSerialPortIdentifiers() {
+ when(serialPortManagerMock.getIdentifiers()).thenReturn(Stream.of());
+ assertParameterOptions();
+ }
+
+ @Test
+ public void serialPortManagerIdentifiersOnly() {
+ when(serialPortManagerMock.getIdentifiers())
+ .thenReturn(Stream.of(serialPortIdentifier1Mock, serialPortIdentifier2Mock));
+
+ assertParameterOptions(DEV_TTY_S1, DEV_TTY_S2);
+ }
+
+ @Test
+ public void discoveredIdentifiersOnly() {
+ provider.addUsbSerialDiscovery(usbSerialDiscoveryMock);
+
+ provider.usbSerialDeviceDiscovered(usb1);
+ provider.usbSerialDeviceDiscovered(usb2);
+
+ when(serialPortManagerMock.getIdentifiers()).thenReturn(Stream.of());
+
+ assertParameterOptions(RFC2217_IPV4, RFC2217_IPV6);
+ }
+
+ @Test
+ public void serialPortManagerAndDiscoveredIdentifiers() {
+ provider.addUsbSerialDiscovery(usbSerialDiscoveryMock);
+
+ provider.usbSerialDeviceDiscovered(usb1);
+ provider.usbSerialDeviceDiscovered(usb2);
+
+ when(serialPortManagerMock.getIdentifiers())
+ .thenReturn(Stream.of(serialPortIdentifier1Mock, serialPortIdentifier2Mock));
+
+ assertParameterOptions(DEV_TTY_S1, DEV_TTY_S2, RFC2217_IPV4, RFC2217_IPV6);
+ }
+
+ @Test
+ public void removedDevicesAreRemoved() {
+ provider.addUsbSerialDiscovery(usbSerialDiscoveryMock);
+
+ provider.usbSerialDeviceDiscovered(usb1);
+
+ when(serialPortManagerMock.getIdentifiers()).thenReturn(Stream.of());
+ assertParameterOptions(RFC2217_IPV4);
+
+ provider.usbSerialDeviceRemoved(usb1);
+
+ when(serialPortManagerMock.getIdentifiers()).thenReturn(Stream.of());
+ assertParameterOptions();
+ }
+
+ @Test
+ public void discoveryRemovalClearsDiscoveryResults() {
+ provider.addUsbSerialDiscovery(usbSerialDiscoveryMock);
+
+ provider.usbSerialDeviceDiscovered(usb1);
+ provider.usbSerialDeviceDiscovered(usb2);
+ provider.usbSerialDeviceDiscovered(usb3);
+
+ when(serialPortManagerMock.getIdentifiers()).thenReturn(Stream.of());
+ assertParameterOptions(RFC2217_IPV4, RFC2217_IPV6, DEV_TTY_S3);
+
+ provider.removeUsbSerialDiscovery(usbSerialDiscoveryMock);
+
+ when(serialPortManagerMock.getIdentifiers()).thenReturn(Stream.of());
+ assertParameterOptions();
+ }
+
+ @Test
+ public void serialPortIdentifiersAreUnique() {
+ provider.addUsbSerialDiscovery(usbSerialDiscoveryMock);
+
+ provider.usbSerialDeviceDiscovered(usb3);
+
+ when(serialPortManagerMock.getIdentifiers()).thenReturn(Stream.of(serialPortIdentifier3Mock));
+
+ assertParameterOptions(DEV_TTY_S3);
+ }
+
+ @Test
+ public void nullResultIfContextDoesNotMatch() {
+ Collection actual = provider.getParameterOptions(URI.create("uri"), "serialPort",
+ "otherContext", null);
+ assertThat(actual, is(nullValue()));
+ }
+}
diff --git a/bundles/org.openhab.core.io.transport.serial.rxtx.rfc2217/src/main/java/org/openhab/core/io/transport/serial/rxtx/rfc2217/internal/RFC2217PortProvider.java b/bundles/org.openhab.core.io.transport.serial.rxtx.rfc2217/src/main/java/org/openhab/core/io/transport/serial/rxtx/rfc2217/internal/RFC2217PortProvider.java
index fef063d36a3..1c25fb9ace6 100644
--- a/bundles/org.openhab.core.io.transport.serial.rxtx.rfc2217/src/main/java/org/openhab/core/io/transport/serial/rxtx/rfc2217/internal/RFC2217PortProvider.java
+++ b/bundles/org.openhab.core.io.transport.serial.rxtx.rfc2217/src/main/java/org/openhab/core/io/transport/serial/rxtx/rfc2217/internal/RFC2217PortProvider.java
@@ -49,7 +49,6 @@ public Stream getAcceptedProtocols() {
@Override
public Stream getSerialPortIdentifiers() {
- // TODO implement discovery here. https://github.com/eclipse/smarthome/pull/5560
return Stream.empty();
}
}
diff --git a/bundles/pom.xml b/bundles/pom.xml
index 101aeca02aa..748a9326d26 100644
--- a/bundles/pom.xml
+++ b/bundles/pom.xml
@@ -31,6 +31,7 @@
org.openhab.core.config.discovery.mdns
org.openhab.core.config.discovery.usbserial
org.openhab.core.config.discovery.usbserial.linuxsysfs
+ org.openhab.core.config.discovery.usbserial.ser2net
org.openhab.core.config.discovery.upnp
org.openhab.core.config.dispatch
org.openhab.core.config.serial
diff --git a/features/karaf/openhab-core/src/main/feature/feature.xml b/features/karaf/openhab-core/src/main/feature/feature.xml
index c6af467b5b1..bf21e7f26bf 100644
--- a/features/karaf/openhab-core/src/main/feature/feature.xml
+++ b/features/karaf/openhab-core/src/main/feature/feature.xml
@@ -463,9 +463,12 @@
openhab.tp;filter:="(&(feature=serial)(impl=rxtx))"
openhab.tp-serial-rxtx
+ openhab-core-io-transport-mdns
+
mvn:org.openhab.core.bundles/org.openhab.core.config.serial/${project.version}
mvn:org.openhab.core.bundles/org.openhab.core.config.discovery.usbserial/${project.version}
mvn:org.openhab.core.bundles/org.openhab.core.config.discovery.usbserial.linuxsysfs/${project.version}
+ mvn:org.openhab.core.bundles/org.openhab.core.config.discovery.usbserial.ser2net/${project.version}
mvn:org.openhab.core.bundles/org.openhab.core.io.transport.serial/${project.version}
mvn:org.openhab.core.bundles/org.openhab.core.io.transport.serial.rxtx/${project.version}
mvn:org.openhab.core.bundles/org.openhab.core.io.transport.serial.rxtx.rfc2217/${project.version}