From 8ea689caffc92c5004cffd6d9a2a547a3c8724b8 Mon Sep 17 00:00:00 2001 From: Wouter Born Date: Tue, 5 Oct 2021 15:45:12 +0200 Subject: [PATCH 1/3] Add ser2net mDNS USB serial discovery * Add support for using multiple UsbSerialDiscovery services * Add Ser2NetUsbSerialDiscovery that can use mDNS to discover ser2net RFC2217 serial ports * Use discovered USB ports in SerialConfigOptionProvider mDNS discovery is supported in ser2net 4.3.0 and newer. E.g. you can install a ser2net version that provides this using APT in Ubuntu 21.04 and Debian 11. Example ser2net YAML configuration that allows a serial port to be discovered using mDNS discovery: %YAML 1.1 --- connection: &con01 accepter: telnet(rfc2217),tcp,2222 connector: serialdev,/dev/ttyUSB0 options: mdns: true mdns-sysattrs: true mdns-name: devicename Closes #1511 Signed-off-by: Wouter Born --- bom/openhab-core/pom.xml | 6 + .../.classpath | 29 ++ .../.project | 23 ++ .../NOTICE | 14 + .../pom.xml | 30 ++ .../internal/Ser2NetUsbSerialDiscovery.java | 206 ++++++++++++++ .../Ser2NetUsbSerialDiscoveryTest.java | 269 ++++++++++++++++++ .../internal/UsbSerialDiscoveryService.java | 48 ++-- .../org.openhab.core.config.serial/pom.xml | 5 + .../internal/SerialConfigOptionProvider.java | 67 ++++- .../SerialConfigOptionProviderTest.java | 178 ++++++++++++ .../rfc2217/internal/RFC2217PortProvider.java | 1 - bundles/pom.xml | 1 + .../openhab-core/src/main/feature/feature.xml | 3 + 14 files changed, 835 insertions(+), 45 deletions(-) create mode 100644 bundles/org.openhab.core.config.discovery.usbserial.ser2net/.classpath create mode 100644 bundles/org.openhab.core.config.discovery.usbserial.ser2net/.project create mode 100644 bundles/org.openhab.core.config.discovery.usbserial.ser2net/NOTICE create mode 100644 bundles/org.openhab.core.config.discovery.usbserial.ser2net/pom.xml create mode 100644 bundles/org.openhab.core.config.discovery.usbserial.ser2net/src/main/java/org/openhab/core/config/discovery/usbserial/ser2net/internal/Ser2NetUsbSerialDiscovery.java create mode 100644 bundles/org.openhab.core.config.discovery.usbserial.ser2net/src/test/java/org/openhab/core/config/discovery/usbserial/ser2net/internal/Ser2NetUsbSerialDiscoveryTest.java create mode 100644 bundles/org.openhab.core.config.serial/src/test/java/org/openhab/core/config/serial/internal/SerialConfigOptionProviderTest.java 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..60f099c5d56 --- /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 backgroundDiscoveryEnabled = 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() { + backgroundDiscoveryEnabled = true; + mdnsClient.addServiceListener(SERVICE_TYPE, this); + logger.debug("Started ser2net USB-Serial mDNS background discovery"); + } + + @Override + public synchronized void stopBackgroundScanning() { + backgroundDiscoveryEnabled = 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 Set.copyOf(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 (backgroundDiscoveryEnabled) { + Optional deviceInfo = createUsbSerialDeviceInformation(event.getInfo()); + deviceInfo.ifPresent(this::announceAddedDevice); + } + } + + @Override + public void serviceRemoved(@NonNullByDefault({}) ServiceEvent event) { + if (backgroundDiscoveryEnabled) { + 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} From 0711ee6279f90cb819c45dba442575fdeed78cbe Mon Sep 17 00:00:00 2001 From: Wouter Born Date: Fri, 12 Nov 2021 21:29:45 +0100 Subject: [PATCH 2/3] Stop copying the copy Signed-off-by: Wouter Born --- .../usbserial/ser2net/internal/Ser2NetUsbSerialDiscovery.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 index 60f099c5d56..aa85a197dc3 100644 --- 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 @@ -132,7 +132,7 @@ public synchronized void doSingleScan() { private Set setDifference(Set set1, Set set2) { Set result = new HashSet<>(set1); result.removeAll(set2); - return Set.copyOf(result); + return result; } private void announceAddedDevice(UsbSerialDeviceInformation deviceInfo) { From b190fdc0a31f253de68f02ac4390a200f3378177 Mon Sep 17 00:00:00 2001 From: Wouter Born Date: Fri, 12 Nov 2021 22:32:41 +0100 Subject: [PATCH 3/3] Rename backgroundDiscoveryEnabled to notifyListeners Signed-off-by: Wouter Born --- .../ser2net/internal/Ser2NetUsbSerialDiscovery.java | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) 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 index aa85a197dc3..4b7d2ac7c84 100644 --- 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 @@ -73,7 +73,7 @@ public class Ser2NetUsbSerialDiscovery implements ServiceListener, UsbSerialDisc private final Set discoveryListeners = new CopyOnWriteArraySet<>(); private final MDNSClient mdnsClient; - private boolean backgroundDiscoveryEnabled = false; + private boolean notifyListeners = false; private Set lastScanResult = new HashSet<>(); @@ -94,14 +94,14 @@ public void unregisterDiscoveryListener(UsbSerialDiscoveryListener listener) { @Override public synchronized void startBackgroundScanning() { - backgroundDiscoveryEnabled = true; + notifyListeners = true; mdnsClient.addServiceListener(SERVICE_TYPE, this); logger.debug("Started ser2net USB-Serial mDNS background discovery"); } @Override public synchronized void stopBackgroundScanning() { - backgroundDiscoveryEnabled = false; + notifyListeners = false; mdnsClient.removeServiceListener(SERVICE_TYPE, this); logger.debug("Stopped ser2net USB-Serial mDNS background discovery"); } @@ -149,7 +149,7 @@ private void announceRemovedDevice(UsbSerialDeviceInformation deviceInfo) { @Override public void serviceAdded(@NonNullByDefault({}) ServiceEvent event) { - if (backgroundDiscoveryEnabled) { + if (notifyListeners) { Optional deviceInfo = createUsbSerialDeviceInformation(event.getInfo()); deviceInfo.ifPresent(this::announceAddedDevice); } @@ -157,7 +157,7 @@ public void serviceAdded(@NonNullByDefault({}) ServiceEvent event) { @Override public void serviceRemoved(@NonNullByDefault({}) ServiceEvent event) { - if (backgroundDiscoveryEnabled) { + if (notifyListeners) { Optional deviceInfo = createUsbSerialDeviceInformation(event.getInfo()); deviceInfo.ifPresent(this::announceRemovedDevice); }