org.bidib.jbidibc.scm.HotPlugEventWatcher Maven / Gradle / Ivy
The newest version!
package org.bidib.jbidibc.scm;
import java.io.File;
import java.util.LinkedHashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
import org.apache.commons.collections4.MapUtils;
import org.apache.commons.lang3.builder.ToStringBuilder;
import org.apache.commons.lang3.builder.ToStringStyle;
import org.bidib.jbidibc.messages.utils.ByteUtils;
import org.bidib.jbidibc.messages.utils.ThreadFactoryBuilder;
import org.bidib.jbidibc.scm.exception.InitDeviceInProgressException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.serialpundit.core.SerialComException;
import com.serialpundit.usb.ISerialComUSBHotPlugListener;
import com.serialpundit.usb.SerialComUSB;
import com.serialpundit.usb.SerialComUSBdevice;
public class HotPlugEventWatcher implements ISerialComUSBHotPlugListener, Runnable {
private static final Logger LOGGER = LoggerFactory.getLogger(HotPlugEventWatcher.class);
private static final String LIBRARY_DIRECTORY_NAME = "jbidibc-usb2";
private static final String LIBRARY_NAME = "usb2";
private SerialComUSB scu;
private Map registeredUsbDevices = new LinkedHashMap<>();
private List listeners = new LinkedList<>();
final Object lock = new Object();
private final AtomicBoolean running = new AtomicBoolean();
private final ScheduledExecutorService serialDeviceWorker =
Executors
.newScheduledThreadPool(1,
new ThreadFactoryBuilder().setNameFormat("serialDeviceWorkers-thread-%d").build());
private final ScheduledExecutorService serialDevicePublisher =
Executors
.newScheduledThreadPool(1,
new ThreadFactoryBuilder().setNameFormat("serialDevicePublishers-thread-%d").build());
public HotPlugEventWatcher() {
this(LIBRARY_NAME, LIBRARY_DIRECTORY_NAME);
}
public HotPlugEventWatcher(String libraryName, String libraryDirectoryName) {
LOGGER
.info("Create new instance of SerialComUSB, libraryName: {}, libraryDirectoryName: {}", libraryName,
libraryDirectoryName);
String tempDir = System.getProperty("java.io.tmpdir");
try {
File extractionDirectory = new File(tempDir, libraryDirectoryName);
extractionDirectory.mkdirs();
scu = new SerialComUSB(extractionDirectory.getAbsolutePath(), libraryName);
}
catch (Exception ex) {
LOGGER.warn("Create new instance of SerialComManager failed.", ex);
throw new RuntimeException("Create new instance of SerialComManager failed.", ex);
}
catch (Error err) {
LOGGER.warn("Create new instance of SerialComManager failed.", err);
throw new RuntimeException("Create new instance of SerialComManager failed.", err);
}
}
public void addEventListener(HotPlugEventListener listener) {
listeners.add(listener);
}
public void removeEventListener(HotPlugEventListener listener) {
listeners.remove(listener);
}
@Override
public void onUSBHotPlugEvent(int event, int USBVID, int USBPID, String serialNumber) {
LOGGER
.info("Received hotplug event: {}, USBVID: {}, USBPID: {}, serialNumber: {}", event,
ByteUtils.intToHex(USBVID), ByteUtils.intToHex(USBPID), serialNumber);
if (event == SerialComUSB.DEV_ADDED) {
LOGGER.info("USB device was added.");
}
else if (event == SerialComUSB.DEV_REMOVED) {
LOGGER.info("USB device was removed.");
}
// let the worker list the devices
serialDevicePublisher.submit(() -> listConnectedDevices());
}
private void listConnectedDevices() {
LOGGER.info("List connected USB devices.");
try {
SerialComUSBdevice[] usbDevices = null;
try {
usbDevices = scu.listUSBdevicesWithInfo(SerialComUSB.V_ALL);
}
catch (SerialComException ex) {
LOGGER.info("List devices failed. Try again after a second.");
Thread.sleep(1000);
usbDevices = scu.listUSBdevicesWithInfo(SerialComUSB.V_ALL);
}
updateRegisteredUsbDevice(usbDevices);
}
catch (Exception e) {
LOGGER.warn("List USB devices failed.", e);
}
catch (Error err) {
LOGGER.warn("Create SerialComManager and list USB devices failed.", err);
}
}
private void updateRegisteredUsbDevice(SerialComUSBdevice[] usbDevices) {
LOGGER.info("Update the registered USB devices.");
// keep the currently registered devices
Map tempDevices = new LinkedHashMap<>();
tempDevices.putAll(registeredUsbDevices);
for (int x = 0; x < usbDevices.length; x++) {
final SerialComUSBdevice scmUsbDevice = usbDevices[x];
StringBuilder keyBuilder = new StringBuilder();
keyBuilder
.append(scmUsbDevice.getVendorID()).append(":").append(scmUsbDevice.getProductID()).append(":")
.append(scmUsbDevice.getSerialNumber());
final String key = keyBuilder.toString();
UsbDevice device = registeredUsbDevices.get(key);
if (device == null) {
final String deviceInfo =
ToStringBuilder.reflectionToString(scmUsbDevice, ToStringStyle.SHORT_PREFIX_STYLE);
LOGGER.info("Register new device: {}", deviceInfo);
final UsbDevice usbDevice = new UsbDevice(scmUsbDevice);
registeredUsbDevices.put(key, usbDevice);
boolean isSerialDevice = false;
String[] comPorts = null;
try {
comPorts = findSerialDeviceComPorts(scmUsbDevice, deviceInfo);
}
catch (InitDeviceInProgressException ex) {
LOGGER.warn("Find serial devices failed. Will retry find devices."/* , ex */);
}
if (comPorts == null || comPorts.length == 0) {
LOGGER.info("Current usbDevice is not a serial device (no COM port assigned): {}", deviceInfo);
if (!this.running.get() || serialDeviceWorker.isShutdown()) {
LOGGER.info("The watcher is not running. Do not schedule check again.");
}
else {
// schedule task to check again for serial port after 10 seconds
try {
serialDeviceWorker.schedule(() -> fetchComPorts(scmUsbDevice, deviceInfo, key), 10, TimeUnit.SECONDS);
}
catch (Exception ex) {
LOGGER.warn("Schedule task to check again for serial port failed.", ex);
}
}
}
else {
isSerialDevice = true;
LOGGER.info("The current device has COM ports: {}", new Object[] { comPorts });
usbDevice.setComPorts(comPorts);
}
if (isSerialDevice) {
notifyListeners(SerialComUSB.DEV_ADDED, usbDevice);
}
}
else {
// the current device was registered already, remove from temp map
tempDevices.remove(key);
}
}
if (MapUtils.isNotEmpty(tempDevices)) {
// remove devices from map
for (Entry entry : tempDevices.entrySet()) {
LOGGER
.info("The current device was removed, key: {}, device: {}", entry.getKey(),
ToStringBuilder.reflectionToString(entry.getValue(), ToStringStyle.SHORT_PREFIX_STYLE));
UsbDevice usbDevice = registeredUsbDevices.remove(entry.getKey());
if (usbDevice != null && usbDevice.getComPorts() != null) {
notifyListeners(SerialComUSB.DEV_REMOVED, usbDevice);
}
}
}
}
private void fetchComPorts(final SerialComUSBdevice scmUsbDevice, final String deviceInfo, final String key) {
String[] comPorts = null;
for (int retryCount = 0; retryCount < 3; retryCount++) {
LOGGER.debug("Try to find serial device COM ports, current retryCount: {}", retryCount);
try {
comPorts = findSerialDeviceComPorts(scmUsbDevice, deviceInfo);
if (comPorts != null && comPorts.length > 0) {
LOGGER.info("The current device has COM ports: {}", new Object[]{comPorts});
UsbDevice usbDevice = registeredUsbDevices.get(key);
if (usbDevice != null) {
usbDevice.setComPorts(comPorts);
LOGGER.info("Found registered USB device to notify: {}", usbDevice);
notifyListeners(SerialComUSB.DEV_ADDED, usbDevice);
} else {
LOGGER.info("Registered USB device not found: {}", usbDevice);
}
} else {
LOGGER
.info("Current usbDevice is not a serial device (no COM port assigned): {}",
deviceInfo);
}
break;
} catch (InitDeviceInProgressException ex) {
LOGGER.warn("Find serial devices failed. Will retry find devices."/* , ex */);
if (HotPlugEventWatcher.this.running.get()) {
try {
Thread.sleep(10000);
} catch (Exception ex1) {
LOGGER
.warn(
"Wait for next try to get the COM ports from USB device was interrupted.");
break;
}
} else {
LOGGER.info("The watcher is stopped. Terminate.");
break;
}
} catch (Exception ex) {
LOGGER.warn("Find serial devices failed.");
}
}
if (comPorts == null || comPorts.length == 0) {
LOGGER.debug("No COM ports found on new device.");
}
}
private String[] findSerialDeviceComPorts(final SerialComUSBdevice scmUsbDevice, final String deviceInfo)
throws InitDeviceInProgressException {
String[] comPorts = null;
try {
comPorts =
scu
.findComPortFromUSBAttributes(scmUsbDevice.getVendorID(), scmUsbDevice.getProductID(),
scmUsbDevice.getSerialNumber());
}
catch (SerialComException ex) {
LOGGER.info("Find COM ports for usbDevice failed: {}", deviceInfo);
if ("CM_Get_DevNode_Registry_Property CR_xxxx error code : 0x25".equals(ex.getMessage())) {
LOGGER.info("This exception occurs if the USB devices are not fully initialized.");
throw new InitDeviceInProgressException("The USB devices are not fully initialized.", ex);
}
}
return comPorts;
}
private void notifyListeners(int event, UsbDevice usbDevice) {
for (HotPlugEventListener listener : listeners) {
switch (event) {
case SerialComUSB.DEV_ADDED:
listener.usbDeviceAdded(usbDevice);
break;
case SerialComUSB.DEV_REMOVED:
listener.usbDeviceRemoved(usbDevice);
break;
default:
break;
}
}
}
@Override
public void run() {
// this is used by DefaultSystemInfoService
this.running.set(true);
// int PRODUCT_VID = 0x0403;
// int PRODUCT_PID = 0x6001;
int PRODUCT_VID = SerialComUSB.DEV_ANY;
int PRODUCT_PID = SerialComUSB.DEV_ANY;
int handle = -1;
try {
LOGGER.info("Try to register USBHotPlugEventListener.");
handle = scu.registerUSBHotPlugEventListener(this, PRODUCT_VID, PRODUCT_PID, null);
listConnectedDevices();
LOGGER.info("Registered USBHotPlugEventListener, handle: {}", handle);
synchronized (lock) {
lock.wait();
}
LOGGER.info("Wait for termination passed.");
}
catch (Exception ex) {
LOGGER.warn("Register USBHotPlugEventListener failed.", ex);
}
catch (Error ex) {
LOGGER.warn("Register USBHotPlugEventListener failed.", ex);
}
finally {
if (handle > -1) {
try {
scu.unregisterUSBHotPlugEventListener(handle);
}
catch (Exception ex1) {
LOGGER.warn("Unregister USBHotPlugEventListener failed.", ex1);
}
handle = -1;
}
}
LOGGER.info("The watcher has terminated.");
}
public void stop() {
LOGGER.info("Stop the watcher.");
this.running.set(false);
try {
synchronized (lock) {
lock.notify();
}
}
catch (Exception ex) {
LOGGER.warn("Notify lock to shutdown USB hotplug event listener failed.", ex);
}
try {
serialDeviceWorker.shutdownNow();
}
catch (Exception ex) {
LOGGER.warn("Shutdown serialDeviceWorker failed.", ex);
}
try {
serialDevicePublisher.shutdownNow();
}
catch (Exception ex) {
LOGGER.warn("Shutdown serialDevicePublisher failed.", ex);
}
}
}