All Downloads are FREE. Search and download functionalities are using the official Maven repository.

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);
        }

    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy