mirror of
https://github.com/danieldemus/openhab-core.git
synced 2025-01-25 11:45:49 +01:00
[discovery] Add ser2net mDNS USB serial discovery (#2519)
* 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 <github@maindrain.net>
This commit is contained in:
parent
5d5b7665b4
commit
917e268e68
@ -316,6 +316,12 @@
|
||||
<version>${project.version}</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.openhab.core.bundles</groupId>
|
||||
<artifactId>org.openhab.core.config.discovery.usbserial.ser2net</artifactId>
|
||||
<version>${project.version}</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.openhab.core.bundles</groupId>
|
||||
<artifactId>org.openhab.core.config.discovery.upnp</artifactId>
|
||||
|
@ -0,0 +1,29 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<classpath>
|
||||
<classpathentry kind="src" output="target/classes" path="src/main/java">
|
||||
<attributes>
|
||||
<attribute name="optional" value="true"/>
|
||||
<attribute name="maven.pomderived" value="true"/>
|
||||
</attributes>
|
||||
</classpathentry>
|
||||
<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-11">
|
||||
<attributes>
|
||||
<attribute name="maven.pomderived" value="true"/>
|
||||
<attribute name="annotationpath" value="target/dependency"/>
|
||||
</attributes>
|
||||
</classpathentry>
|
||||
<classpathentry kind="con" path="org.eclipse.m2e.MAVEN2_CLASSPATH_CONTAINER">
|
||||
<attributes>
|
||||
<attribute name="maven.pomderived" value="true"/>
|
||||
<attribute name="annotationpath" value="target/dependency"/>
|
||||
</attributes>
|
||||
</classpathentry>
|
||||
<classpathentry kind="src" output="target/test-classes" path="src/test/java">
|
||||
<attributes>
|
||||
<attribute name="optional" value="true"/>
|
||||
<attribute name="maven.pomderived" value="true"/>
|
||||
<attribute name="test" value="true"/>
|
||||
</attributes>
|
||||
</classpathentry>
|
||||
<classpathentry kind="output" path="target/classes"/>
|
||||
</classpath>
|
@ -0,0 +1,23 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<projectDescription>
|
||||
<name>org.openhab.core.config.discovery.usbserial.ser2net</name>
|
||||
<comment></comment>
|
||||
<projects>
|
||||
</projects>
|
||||
<buildSpec>
|
||||
<buildCommand>
|
||||
<name>org.eclipse.jdt.core.javabuilder</name>
|
||||
<arguments>
|
||||
</arguments>
|
||||
</buildCommand>
|
||||
<buildCommand>
|
||||
<name>org.eclipse.m2e.core.maven2Builder</name>
|
||||
<arguments>
|
||||
</arguments>
|
||||
</buildCommand>
|
||||
</buildSpec>
|
||||
<natures>
|
||||
<nature>org.eclipse.jdt.core.javanature</nature>
|
||||
<nature>org.eclipse.m2e.core.maven2Nature</nature>
|
||||
</natures>
|
||||
</projectDescription>
|
@ -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
|
||||
|
@ -0,0 +1,30 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<parent>
|
||||
<groupId>org.openhab.core.bundles</groupId>
|
||||
<artifactId>org.openhab.core.reactor.bundles</artifactId>
|
||||
<version>3.2.0-SNAPSHOT</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>org.openhab.core.config.discovery.usbserial.ser2net</artifactId>
|
||||
|
||||
<name>openHAB Core :: Bundles :: Configuration USB-Serial Discovery using ser2net mDNS scanning</name>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.openhab.core.bundles</groupId>
|
||||
<artifactId>org.openhab.core.config.discovery.usbserial</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.openhab.core.bundles</groupId>
|
||||
<artifactId>org.openhab.core.io.transport.mdns</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
</project>
|
@ -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<UsbSerialDiscoveryListener> discoveryListeners = new CopyOnWriteArraySet<>();
|
||||
private final MDNSClient mdnsClient;
|
||||
|
||||
private boolean notifyListeners = false;
|
||||
|
||||
private Set<UsbSerialDeviceInformation> 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<UsbSerialDeviceInformation> scanResult = Stream.of(mdnsClient.list(SERVICE_TYPE, SINGLE_SCAN_DURATION))
|
||||
.map(this::createUsbSerialDeviceInformation) //
|
||||
.filter(Optional::isPresent) //
|
||||
.map(Optional::get) //
|
||||
.collect(Collectors.toSet());
|
||||
|
||||
Set<UsbSerialDeviceInformation> added = setDifference(scanResult, lastScanResult);
|
||||
Set<UsbSerialDeviceInformation> removed = setDifference(lastScanResult, scanResult);
|
||||
Set<UsbSerialDeviceInformation> 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 <T> Set<T> setDifference(Set<T> set1, Set<T> set2) {
|
||||
Set<T> 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<UsbSerialDeviceInformation> deviceInfo = createUsbSerialDeviceInformation(event.getInfo());
|
||||
deviceInfo.ifPresent(this::announceAddedDevice);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void serviceRemoved(@NonNullByDefault({}) ServiceEvent event) {
|
||||
if (notifyListeners) {
|
||||
Optional<UsbSerialDeviceInformation> deviceInfo = createUsbSerialDeviceInformation(event.getInfo());
|
||||
deviceInfo.ifPresent(this::announceRemovedDevice);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void serviceResolved(@NonNullByDefault({}) ServiceEvent event) {
|
||||
serviceAdded(event);
|
||||
}
|
||||
|
||||
private Optional<UsbSerialDeviceInformation> 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();
|
||||
}
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
@ -47,14 +47,14 @@ import org.slf4j.LoggerFactory;
|
||||
* 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.
|
||||
* <p/>
|
||||
* 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.
|
||||
* <p/>
|
||||
* 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<UsbSerialDiscoveryParticipant> discoveryParticipants = new CopyOnWriteArraySet<>();
|
||||
|
||||
private final Set<UsbSerialDeviceInformation> previouslyDiscovered = new CopyOnWriteArraySet<>();
|
||||
|
||||
private @NonNullByDefault({}) UsbSerialDiscovery usbSerialDiscovery;
|
||||
private final Set<UsbSerialDiscovery> usbSerialDiscoveries = new CopyOnWriteArraySet<>();
|
||||
|
||||
public UsbSerialDiscoveryService() {
|
||||
super(5);
|
||||
@ -83,7 +81,6 @@ public class UsbSerialDiscoveryService extends AbstractDiscoveryService implemen
|
||||
@Activate
|
||||
protected void activate(@Nullable Map<String, Object> configProperties) {
|
||||
super.activate(configProperties);
|
||||
usbSerialDiscovery.registerDiscoveryListener(this);
|
||||
}
|
||||
|
||||
@Modified
|
||||
@ -100,7 +97,7 @@ public class UsbSerialDiscoveryService extends AbstractDiscoveryService implemen
|
||||
|
||||
@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 @@ public class UsbSerialDiscoveryService extends AbstractDiscoveryService implemen
|
||||
}
|
||||
|
||||
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 class UsbSerialDiscoveryService extends AbstractDiscoveryService implemen
|
||||
|
||||
@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
|
||||
|
@ -25,6 +25,11 @@
|
||||
<artifactId>org.openhab.core.io.transport.serial</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.openhab.core.bundles</groupId>
|
||||
<artifactId>org.openhab.core.config.discovery.usbserial</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
</project>
|
||||
|
@ -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<UsbSerialDeviceInformation> previouslyDiscovered = new CopyOnWriteArraySet<>();
|
||||
private final Set<UsbSerialDiscovery> 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 Collection<ParameterOption> getParameterOptions(URI uri, String param, String context, Locale locale) {
|
||||
List<ParameterOption> options = new ArrayList<>();
|
||||
if ("serial-port".equals(context)) {
|
||||
serialPortManager.getIdentifiers()
|
||||
.forEach(id -> options.add(new ParameterOption(id.getName(), id.getName())));
|
||||
public void usbSerialDeviceDiscovered(UsbSerialDeviceInformation usbSerialDeviceInformation) {
|
||||
previouslyDiscovered.add(usbSerialDeviceInformation);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void usbSerialDeviceRemoved(UsbSerialDeviceInformation usbSerialDeviceInformation) {
|
||||
previouslyDiscovered.remove(usbSerialDeviceInformation);
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Nullable Collection<ParameterOption> 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;
|
||||
}
|
||||
}
|
||||
|
@ -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<ParameterOption> actual = provider.getParameterOptions(URI.create("uri"), "serialPort", SERIAL_PORT,
|
||||
null);
|
||||
Collection<ParameterOption> 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<ParameterOption> actual = provider.getParameterOptions(URI.create("uri"), "serialPort",
|
||||
"otherContext", null);
|
||||
assertThat(actual, is(nullValue()));
|
||||
}
|
||||
}
|
@ -49,7 +49,6 @@ public class RFC2217PortProvider implements SerialPortProvider {
|
||||
|
||||
@Override
|
||||
public Stream<SerialPortIdentifier> getSerialPortIdentifiers() {
|
||||
// TODO implement discovery here. https://github.com/eclipse/smarthome/pull/5560
|
||||
return Stream.empty();
|
||||
}
|
||||
}
|
||||
|
@ -31,6 +31,7 @@
|
||||
<module>org.openhab.core.config.discovery.mdns</module>
|
||||
<module>org.openhab.core.config.discovery.usbserial</module>
|
||||
<module>org.openhab.core.config.discovery.usbserial.linuxsysfs</module>
|
||||
<module>org.openhab.core.config.discovery.usbserial.ser2net</module>
|
||||
<module>org.openhab.core.config.discovery.upnp</module>
|
||||
<module>org.openhab.core.config.dispatch</module>
|
||||
<module>org.openhab.core.config.serial</module>
|
||||
|
@ -463,9 +463,12 @@
|
||||
<requirement>openhab.tp;filter:="(&(feature=serial)(impl=rxtx))"</requirement>
|
||||
<feature dependency="true">openhab.tp-serial-rxtx</feature>
|
||||
|
||||
<feature dependency="true">openhab-core-io-transport-mdns</feature>
|
||||
|
||||
<bundle>mvn:org.openhab.core.bundles/org.openhab.core.config.serial/${project.version}</bundle>
|
||||
<bundle>mvn:org.openhab.core.bundles/org.openhab.core.config.discovery.usbserial/${project.version}</bundle>
|
||||
<bundle>mvn:org.openhab.core.bundles/org.openhab.core.config.discovery.usbserial.linuxsysfs/${project.version}</bundle>
|
||||
<bundle>mvn:org.openhab.core.bundles/org.openhab.core.config.discovery.usbserial.ser2net/${project.version}</bundle>
|
||||
<bundle>mvn:org.openhab.core.bundles/org.openhab.core.io.transport.serial/${project.version}</bundle>
|
||||
<bundle>mvn:org.openhab.core.bundles/org.openhab.core.io.transport.serial.rxtx/${project.version}</bundle>
|
||||
<bundle>mvn:org.openhab.core.bundles/org.openhab.core.io.transport.serial.rxtx.rfc2217/${project.version}</bundle>
|
||||
|
Loading…
Reference in New Issue
Block a user