UsbSerialDiscovery service based on Windows registry (#3934)
Signed-off-by: Andrew Fiddian-Green <software@whitebear.ch>pull/4034/head
parent
6b2182dec6
commit
9cb4b9ee1f
|
@ -125,9 +125,9 @@ public class Ser2NetUsbSerialDiscovery implements ServiceListener, UsbSerialDisc
|
|||
|
||||
lastScanResult = scanResult;
|
||||
|
||||
removed.stream().forEach(this::announceRemovedDevice);
|
||||
added.stream().forEach(this::announceAddedDevice);
|
||||
unchanged.stream().forEach(this::announceAddedDevice);
|
||||
removed.forEach(this::announceRemovedDevice);
|
||||
added.forEach(this::announceAddedDevice);
|
||||
unchanged.forEach(this::announceAddedDevice);
|
||||
|
||||
logger.debug("Completed ser2net USB-Serial mDNS single discovery scan");
|
||||
}
|
||||
|
|
|
@ -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-17">
|
||||
<attributes>
|
||||
<attribute name="maven.pomderived" value="true"/>
|
||||
<attribute name="annotationpath" value="target/dependency"/>
|
||||
</attributes>
|
||||
</classpathentry>
|
||||
<classpathentry kind="con" path="org.eclipse.m2e.MAVEN2_CLASSPATH_CONTAINER">
|
||||
<attributes>
|
||||
<attribute name="maven.pomderived" value="true"/>
|
||||
<attribute name="annotationpath" value="target/dependency"/>
|
||||
</attributes>
|
||||
</classpathentry>
|
||||
<classpathentry kind="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.javaxusb</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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<parent>
|
||||
<groupId>org.openhab.core.bundles</groupId>
|
||||
<artifactId>org.openhab.core.reactor.bundles</artifactId>
|
||||
<version>4.2.0-SNAPSHOT</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>org.openhab.core.config.discovery.usbserial.windowsregistry</artifactId>
|
||||
|
||||
<name>openHAB Core :: Bundles :: Configuration USB-Serial Discovery for Windows</name>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.openhab.core.bundles</groupId>
|
||||
<artifactId>org.openhab.core.config.discovery.usbserial</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>net.java.dev.jna</groupId>
|
||||
<artifactId>jna-platform</artifactId>
|
||||
<version>5.13.0</version>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
</project>
|
|
@ -0,0 +1,261 @@
|
|||
/**
|
||||
* Copyright (c) 2010-2024 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.windowsregistry.internal;
|
||||
|
||||
import static com.sun.jna.platform.win32.WinReg.HKEY_LOCAL_MACHINE;
|
||||
|
||||
import java.time.Duration;
|
||||
import java.util.HashSet;
|
||||
import java.util.Map.Entry;
|
||||
import java.util.Set;
|
||||
import java.util.TreeMap;
|
||||
import java.util.concurrent.CopyOnWriteArraySet;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.ScheduledExecutorService;
|
||||
import java.util.concurrent.ScheduledFuture;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.openhab.core.common.ThreadFactoryBuilder;
|
||||
import org.openhab.core.config.discovery.usbserial.UsbSerialDeviceInformation;
|
||||
import org.openhab.core.config.discovery.usbserial.UsbSerialDiscovery;
|
||||
import org.openhab.core.config.discovery.usbserial.UsbSerialDiscoveryListener;
|
||||
import org.osgi.service.component.annotations.Activate;
|
||||
import org.osgi.service.component.annotations.Component;
|
||||
import org.osgi.service.component.annotations.Deactivate;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import com.sun.jna.Platform;
|
||||
import com.sun.jna.platform.win32.Advapi32Util;
|
||||
|
||||
/**
|
||||
* This is a {@link UsbSerialDiscovery} implementation component for Windows.
|
||||
* It parses the Windows registry for USB device entries.
|
||||
*
|
||||
* @author Andrew Fiddian-Green - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
@Component(service = UsbSerialDiscovery.class, name = WindowsUsbSerialDiscovery.SERVICE_NAME)
|
||||
public class WindowsUsbSerialDiscovery implements UsbSerialDiscovery {
|
||||
|
||||
protected static final String SERVICE_NAME = "usb-serial-discovery-windows";
|
||||
|
||||
// registry accessor strings
|
||||
private static final String USB_REGISTRY_ROOT = "SYSTEM\\CurrentControlSet\\Enum\\USB";
|
||||
private static final String BACKSLASH = "\\";
|
||||
private static final String PREFIX_PID = "PID_";
|
||||
private static final String PREFIX_VID = "VID_";
|
||||
private static final String PREFIX_HEX = "0x";
|
||||
private static final String SPLIT_IDS = "&";
|
||||
private static final String SPLIT_VALUES = ";";
|
||||
private static final String KEY_MANUFACTURER = "Mfg";
|
||||
private static final String KEY_PRODUCT = "DeviceDesc";
|
||||
private static final String KEY_DEVICE_PARAMETERS = "Device Parameters";
|
||||
private static final String KEY_SERIAL_PORT = "PortName";
|
||||
|
||||
private final Logger logger = LoggerFactory.getLogger(WindowsUsbSerialDiscovery.class);
|
||||
private final Set<UsbSerialDiscoveryListener> discoveryListeners = new CopyOnWriteArraySet<>();
|
||||
private final Duration scanInterval = Duration.ofSeconds(15);
|
||||
private final ScheduledExecutorService scheduler;
|
||||
|
||||
private Set<UsbSerialDeviceInformation> lastScanResult = new HashSet<>();
|
||||
private @Nullable ScheduledFuture<?> scanTask;
|
||||
|
||||
@Activate
|
||||
public WindowsUsbSerialDiscovery() {
|
||||
scheduler = Executors.newSingleThreadScheduledExecutor(
|
||||
ThreadFactoryBuilder.create().withName(SERVICE_NAME).withDaemonThreads(true).build());
|
||||
}
|
||||
|
||||
private void announceAddedDevice(UsbSerialDeviceInformation deviceInfo) {
|
||||
for (UsbSerialDiscoveryListener listener : discoveryListeners) {
|
||||
listener.usbSerialDeviceDiscovered(deviceInfo);
|
||||
}
|
||||
}
|
||||
|
||||
private void announceRemovedDevice(UsbSerialDeviceInformation deviceInfo) {
|
||||
for (UsbSerialDiscoveryListener listener : discoveryListeners) {
|
||||
listener.usbSerialDeviceRemoved(deviceInfo);
|
||||
}
|
||||
}
|
||||
|
||||
@Deactivate
|
||||
public void deactivate() {
|
||||
stopBackgroundScanning();
|
||||
lastScanResult.clear();
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized void doSingleScan() {
|
||||
Set<UsbSerialDeviceInformation> scanResult = scanAllUsbDevicesInformation();
|
||||
Set<UsbSerialDeviceInformation> added = setDifference(scanResult, lastScanResult);
|
||||
Set<UsbSerialDeviceInformation> removed = setDifference(lastScanResult, scanResult);
|
||||
Set<UsbSerialDeviceInformation> unchanged = setDifference(scanResult, added);
|
||||
|
||||
lastScanResult = scanResult;
|
||||
|
||||
removed.forEach(this::announceRemovedDevice);
|
||||
added.forEach(this::announceAddedDevice);
|
||||
unchanged.forEach(this::announceAddedDevice);
|
||||
}
|
||||
|
||||
private <T> Set<T> setDifference(Set<T> set1, Set<T> set2) {
|
||||
Set<T> result = new HashSet<>(set1);
|
||||
result.removeAll(set2);
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void registerDiscoveryListener(UsbSerialDiscoveryListener listener) {
|
||||
discoveryListeners.add(listener);
|
||||
for (UsbSerialDeviceInformation deviceInfo : lastScanResult) {
|
||||
listener.usbSerialDeviceDiscovered(deviceInfo);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void unregisterDiscoveryListener(UsbSerialDiscoveryListener listener) {
|
||||
discoveryListeners.remove(listener);
|
||||
}
|
||||
|
||||
/**
|
||||
* Traverse the USB tree in Windows registry and return a set of USB device information.
|
||||
*
|
||||
* @return a set of USB device information.
|
||||
*/
|
||||
public Set<UsbSerialDeviceInformation> scanAllUsbDevicesInformation() {
|
||||
if (!Platform.isWindows()) {
|
||||
return Set.of();
|
||||
}
|
||||
|
||||
Set<UsbSerialDeviceInformation> result = new HashSet<>();
|
||||
String[] deviceKeys = Advapi32Util.registryGetKeys(HKEY_LOCAL_MACHINE, USB_REGISTRY_ROOT);
|
||||
|
||||
for (String deviceKey : deviceKeys) {
|
||||
logger.trace("{}", deviceKey);
|
||||
|
||||
if (!deviceKey.startsWith(PREFIX_VID)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
String[] ids = deviceKey.split(SPLIT_IDS);
|
||||
if (ids.length < 2) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!ids[1].startsWith(PREFIX_PID)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
int vendorId;
|
||||
int productId;
|
||||
try {
|
||||
vendorId = Integer.decode(PREFIX_HEX + ids[0].substring(4));
|
||||
productId = Integer.decode(PREFIX_HEX + ids[1].substring(4));
|
||||
} catch (NumberFormatException e) {
|
||||
continue;
|
||||
}
|
||||
|
||||
String serialNumber = ids.length > 2 ? ids[2] : null;
|
||||
|
||||
String devicePath = USB_REGISTRY_ROOT + BACKSLASH + deviceKey;
|
||||
String[] interfaceNames = Advapi32Util.registryGetKeys(HKEY_LOCAL_MACHINE, devicePath);
|
||||
|
||||
int interfaceId = 0;
|
||||
for (String interfaceName : interfaceNames) {
|
||||
logger.trace(" interfaceId:{}, interfaceName:{}", interfaceId, interfaceName);
|
||||
|
||||
String interfacePath = devicePath + BACKSLASH + interfaceName;
|
||||
TreeMap<String, Object> values = Advapi32Util.registryGetValues(HKEY_LOCAL_MACHINE, interfacePath);
|
||||
|
||||
if (logger.isTraceEnabled()) {
|
||||
for (Entry<String, Object> value : values.entrySet()) {
|
||||
logger.trace(" {}={}", value.getKey(), value.getValue());
|
||||
}
|
||||
}
|
||||
|
||||
String manufacturer;
|
||||
Object manufacturerValue = values.get(KEY_MANUFACTURER);
|
||||
if (manufacturerValue instanceof String manufacturerString) {
|
||||
String[] manufacturerData = manufacturerString.split(SPLIT_VALUES);
|
||||
if (manufacturerData.length < 2) {
|
||||
continue;
|
||||
}
|
||||
manufacturer = manufacturerData[1];
|
||||
} else {
|
||||
continue;
|
||||
}
|
||||
|
||||
String product;
|
||||
Object productValue = values.get(KEY_PRODUCT);
|
||||
if (productValue instanceof String productString) {
|
||||
String[] productData = productString.split(SPLIT_VALUES);
|
||||
if (productData.length < 2) {
|
||||
continue;
|
||||
}
|
||||
product = productData[1];
|
||||
} else {
|
||||
continue;
|
||||
}
|
||||
|
||||
String serialPort = "";
|
||||
String[] interfaceSubKeys = Advapi32Util.registryGetKeys(HKEY_LOCAL_MACHINE, interfacePath);
|
||||
|
||||
for (String interfaceSubKey : interfaceSubKeys) {
|
||||
if (!KEY_DEVICE_PARAMETERS.equals(interfaceSubKey)) {
|
||||
continue;
|
||||
}
|
||||
String deviceParametersPath = interfacePath + BACKSLASH + interfaceSubKey;
|
||||
TreeMap<String, Object> deviceParameterValues = Advapi32Util.registryGetValues(HKEY_LOCAL_MACHINE,
|
||||
deviceParametersPath);
|
||||
Object serialPortValue = deviceParameterValues.get(KEY_SERIAL_PORT);
|
||||
if (serialPortValue instanceof String serialPortString) {
|
||||
serialPort = serialPortString;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
UsbSerialDeviceInformation usbSerialDeviceInformation = new UsbSerialDeviceInformation(vendorId,
|
||||
productId, serialNumber, manufacturer, product, interfaceId, interfaceName, serialPort);
|
||||
|
||||
logger.debug("Add {}", usbSerialDeviceInformation);
|
||||
result.add(usbSerialDeviceInformation);
|
||||
|
||||
interfaceId++;
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized void startBackgroundScanning() {
|
||||
if (Platform.isWindows()) {
|
||||
ScheduledFuture<?> scanTask = this.scanTask;
|
||||
if (scanTask == null || scanTask.isDone()) {
|
||||
this.scanTask = scheduler.scheduleWithFixedDelay(this::doSingleScan, 0, scanInterval.toSeconds(),
|
||||
TimeUnit.SECONDS);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized void stopBackgroundScanning() {
|
||||
ScheduledFuture<?> scanTask = this.scanTask;
|
||||
if (scanTask != null) {
|
||||
scanTask.cancel(false);
|
||||
}
|
||||
this.scanTask = null;
|
||||
}
|
||||
}
|
|
@ -40,6 +40,7 @@
|
|||
<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.usbserial.windowsregistry</module>
|
||||
<module>org.openhab.core.config.discovery.upnp</module>
|
||||
<module>org.openhab.core.config.dispatch</module>
|
||||
<module>org.openhab.core.config.serial</module>
|
||||
|
|
|
@ -519,8 +519,19 @@
|
|||
|
||||
<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>
|
||||
|
||||
<conditional>
|
||||
<condition>req:osgi.native;filter:="(osgi.native.osname=Linux)"</condition>
|
||||
<bundle>mvn:org.openhab.core.bundles/org.openhab.core.config.discovery.usbserial.linuxsysfs/${project.version}</bundle>
|
||||
</conditional>
|
||||
|
||||
<bundle>mvn:org.openhab.core.bundles/org.openhab.core.config.discovery.usbserial.ser2net/${project.version}</bundle>
|
||||
|
||||
<conditional>
|
||||
<condition>req:osgi.native;filter:="(osgi.native.osname=Windows*)"</condition>
|
||||
<bundle>mvn:org.openhab.core.bundles/org.openhab.core.config.discovery.usbserial.windowsregistry/${project.version}</bundle>
|
||||
</conditional>
|
||||
|
||||
<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