gary-rowe / hid4java

A cross-platform Java Native Access (JNA) wrapper for the libusb/hidapi library. Works out of the box on Windows/Mac/Linux.
MIT License
229 stars 71 forks source link

Add HidDeviceListener interface and implementation in order to listen to device events #95

Closed mpetzold closed 3 years ago

mpetzold commented 4 years ago

I would suggest to add a HidDeviceListener interface so that it should be possible to listen to device events.

gary-rowe commented 4 years ago

Hi. This sounds similar in nature to #30, can you elaborate on what you have in mind? Perhaps a skeleton interface definition that would suit your project's needs?

gary-rowe commented 3 years ago

Given the lack of activity I'm going to close this issue.

Laivindur commented 3 years ago

Hi Gary, Once we started to work with hid4java we have missed the feature suggested here.

The same way HidService listen and trigger HidServiceEvents due to attachments and detachments, would be great if there were some way to attach our components to some listener or queue to handle incoming messages from devices. We have, somewhat, implemented a walkaround based on the Observer pattern wrapping the hidDevice instance we want to listen. It's a Runnable, running in a different thread which performs an endless loop around the statement

 while (active) {
            try {
                observe();
                //XXX CYV : Walkaround to avoid input messages to be read partialy due to interruptions
                //XXX CYV : Thread sleeps as much time as device keeps the input buffer open for new messages
                //XXX CYV : The lock and the sleep generates the innactivity period of readTimeoutx2
                Thread.sleep(readTimeout);
            }catch(InterruptedException ex) {
                LOG.error("USBHIDDeviceObserver' sleep interrupted!",ex);
                active = false;
            }
        }

And the observe

 try {
            List<byte[]> output = new LinkedList<byte[]>();   
            boolean hasMoreData = Boolean.TRUE;
            do {
                byte[] data = new byte[maxPackageSize];
                // This method will now block for {readTimeout} ms or until data
                // is read
                int val = readDevice(data);
                switch (val) {
                case RUNTIME_ERROR:
                case TRANSFER_ERROR:
                    listener.onTransferError(new USBHidDeviceEvent(hidDevice, hidDevice.getLastErrorMessage()));
                    hasMoreData = Boolean.FALSE;
                    break;
                case END_OF_TRANSFER:
                    if (output.size() > 0) {
                        listener.onTransfer(
                                new USBHidDeviceEvent(hidDevice, new LinkedList<byte[]>(output)));
                        LOG.debug("observe() - " + output.size()+" packages ("+maxPackageSize+" length) were transfered from HidDevice " + hidDevice);
                    }
                    hasMoreData = Boolean.FALSE;
                    break;
                default:
                    output.add(data);
                    break;
                }
            } while (hasMoreData);

        } catch (Exception ex) {
            LOG.warn("Error observing hid device data transfers. Cause: " + ex.getMessage(), ex);
            listener.onTransferError(new USBHidDeviceEvent(hidDevice, ex.getMessage()));
        } 

The listener interface is

public interface USBHidDeviceListener {

    /**
     * Handles errors during the loading of data comming from the HID Device.
     * {@link USBHidDeviceEvent} handled by {@code onTransferError} has no content nor error codes. Instead 
     * they provide with a {@code message} describing the error. 
     * @param usbHidDeviceEvent
     */
    void onTransferError(USBHidDeviceEvent usbHidDeviceEvent);

    /**
     * Handles the latest {@code content} transfered by the HID Device to the host. 
     * {@link USBHidDeviceEvent} handled by {@code onTransfer} has no output message nor response codes, just content.
     * @param usbHidDeviceEvent
     */
    void onTransfer(USBHidDeviceEvent usbHidDeviceEvent);
}

The event

public class USBHidDeviceEvent {

    //Null Object Pattern
    private static final String EMPTY_MESSAGE = "";

    private Optional<List<byte[]>> content;
    private Optional<String> message;    
    private final HidDevice hidDevice;

    public USBHidDeviceEvent(HidDevice hidDevice) {
        this.hidDevice = hidDevice;
        this.content = Optional.empty();
        this.message = Optional.empty();
    }

    public USBHidDeviceEvent(HidDevice hidDevice, String message) {
        this.hidDevice = hidDevice;
        this.content = Optional.empty();
        this.message = Optional.ofNullable(message);
    }

    public USBHidDeviceEvent(HidDevice hidDevice, List<byte[]> content) {
        this.hidDevice = hidDevice;
        this.content = Optional.ofNullable(content);
        this.message = Optional.empty();
    }

    public String getMessage() {
        return message.orElse(EMPTY_MESSAGE);
    }

    public List<byte[]> getContent() {
        return content.orElse(Collections.emptyList());
    }

    @Override
    public String toString() {
        return getClass().getSimpleName() + 
                "[Hid ="+hidDevice+"]"
                +"[Message="+getMessage()+"]";
    }    
}

I could have added getHidDevice() to the event, but I didn't because I don't want everybody being aware of the hid4java lib since it's an implementation detail.

The Listener interface also could have been better named and get better names for the handlers, as for example onInputTransfer to handle inputs from the device or onOutputTransfer() to handle messages sent to the device. In our case, we only need to be aware of incoming messages.

Hope it helps!

gary-rowe commented 3 years ago

Thanks for stepping up here @Laivindur. I've reopened the issue and I'll add in data read support as an event.

gary-rowe commented 3 years ago

I've added some code to support this into the issue-95 branch. If you'd like to take an early look and offer up some feedback on its usefulness it would help greatly.

Laivindur commented 3 years ago

Hi Gary.

Unfortunately, we are kinda busy at the moment due to the release. We are performing several task forces to round some corners of the actual version. I will look at it as soon as I can.

gary-rowe commented 3 years ago

No worries. I've still got a few extra items to add in but thought I'd let you know that work is underway.

Laivindur commented 3 years ago

Hi Gary, I didn't forget this "feature" I'm still striving to find a gap in my schedule to look at it with care and the due attention. We have stumbled upon more critical problems and we think hid4java is involved. I will open another issue and see what can we do.

gary-rowe commented 3 years ago

Merged and closed.