Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Reading USB HID barcode scanner input without knowing VID&PID

I'm trying to develop device independent library for barcode scanners, it has to be working in windows environment.

I've done some research in this field, afaik most of the solutions of this problem are depending on specific device VID&PID (RawInput @ filter by vid&pid string), in my situation this is inacceptable, because i'm trying to develop a device independent solution, which will be working with any USB-barcode scanner.

Actually this thing is quite challenging, for me atleast, here are exact requiriments. Also i can't ask user to hot-plug device (in that case i could've just detect plugged device and extract it vid/pid). Also i can't use VID&PID database of devices. In general i can't use vid&pid at all actually.

Also i can't in any way reprogramm barcode scanner, unless it's done from my programm (maybe i can send some barcodescanner-specific IOCTLs which will make it answer to me?).

Currently i'm going to use solution proposed in this question: Reading a barcode using a USB barcode scanner along with ignoring keyboard data input while scanner product id and vendor id are not known

Also i have seen commercial library (which is offcourse comes without any sources and any info about how it's implemented, but considering they have'd some word "Perfomance counter" in their changelogs, i guess they used solution in the link above), which implements this functionality, but it doesn't work in x64 systems. Probably either because of messy code or beacause it probably uses some kind of filter (mini) driver. It's crypted and i can't redistribute it.

My exact question is: Is there any way to determine that this HID keyboard is in fact not a keyboard, but a barcode scanner? I've seen on Win 7 x64 that it connects as Barcode scanner, not keyboard (this was a system bug, or sort of).

Exactly what i'm doing now:

  1. Reading input by RID_INPUTSINK.
  2. Distinguishing all input by vid&pid of device
  3. Putting all input to separate buffers and collecting barcodes from buffer when VK_ENTER shows on buffer.

What i'm currently going to do:

  1. Read input by RID_INPUTSINK
  2. Start timer for specific device and if next symbol is VK_ENTER - stop timer
  3. If timer exceeds 50 ms limit - off it and drop all further device input.
  4. If device will successfully read sequence of characters from first symbol to VK_ENTER - extract device VID&PID/handle and work with it in more convenient way (without timering).

I'm developng it on C++, pure WinAPI, it will be a DLL library, and got to work in Windows XP, Vista, 7, 8 on x32-86 and x32-64 architectures.

UPDATE 0: Just found that barcode scanner have their own usagePage and usage in USB specs: http://www.usb.org/developers/devclass_docs/pos1_02.pdf

According to this document USB Barcode scanner have UsagePage 0x8C and Usage 0x02. Unfortunately i've failed using it as RAWINPUTDEVICE.dwUsage and RAWINPUTDEVICE.dwUsagePage. Probably because system install it's usb keyboard driver on top of it and in user mode it is indistinguishable from real usb keyboard. Probably those values are usable in kernelmode environment (one of the options is to develop hid filter driver).

like image 308
Ivan0x32 Avatar asked Sep 19 '12 14:09

Ivan0x32


People also ask

What is a USB-HID barcode scanner?

USB barcode scanner. A USB connected barcode scanner must be configured in HID POS Scanner mode to work with the barcode scanner driver that is included in Windows. This driver is an implementation of the HID Point of Sale Usage Tables specification published to USB-HID.

How do I set up my barcode scanner as a hid?

Please refer to your barcode scanner documentation or contact your barcode scanner manufacturer for instructions to enable the HID POS Scanner mode. Once configured as a HID POS Scanner your barcode scanner will appear in Device Manager under the POS Barcode Scanner node as POS HID Barcode scanner.

How does a USB barcode reader work?

[The barcode reader is programmed to add a carriage return character to the end of the string on barcode read completion.] The ASCII text data is passed to the PC via the USB HID (Human Interface Device) protocol. The barcode reader uses keyboard emulation to implement USB HID .

Why is the device Pos hid barcode scanner offline?

Source: DriverFrameworks-UserMode Event ID: 10111 Level: Critical User: SYSTEM Description: The device POS HID Barcode scanner (location (unknown)) is offline due to a user-mode driver crash. Windows will attempt to restart the device 5 more times. Please contact the device manufacturer for more information about this problem.


1 Answers

This does not answer your specific question, but anyway...

More than a year ago, I implemented barcode reader support under even more adverse circumstances. It was for a reporting applicaton with association to logistical data in pure Java (cross platform rich client, primarily on Windows). I found out the same you're saying about the keyboard driver, which prevents distinction of actual USB devices in user mode, at least at the first glance. There are more expensive devices with own drivers and advanced features, which would allow some sort of distinction. All barcode readers I encountered in that environment were visible as keyboards and used to simply fill an SAP form field and hit the enter key, which is a common case. The termination may be configurable using 'magic barcodes' or another manufacturer specific method.

So the decision was against any JNI based, platform specific implementation. Instead, I implemented also an interception-like approach (extended version of yours) by evaluating generic keyoard input within certain Swing/AWT forms using these criteria:

  • key stroke frequency determined by the first two chars (initially / after timeout)
  • jitter (frequency/rate change)
  • set of valid chars
  • terminating line break.

The input gets consumed by a buffer until the criteria for machine generated input aren't met, or the validation has been passed, where barcode listeners will be notified. In either situation, the input can be forwarded as if nothing else happened.

This has proven to be very accurate, since for a human, it's all but impossible to enter a valid sequence at the barcode reader's rate with (almost) zero jitter.


EDIT:

Just dug out the Java source; I can give you the code of an early revision of the implementation above as an example (no warranty, also consider to implement CR):

import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * A {@link KeyListener} implementation for barcode readers. This implementation
 * checks for input rate and jitter to distinguish human and scanner
 * input sequences by 'precision'. A barcode input sequence from a scanner is
 * typically terminated with a line break.
 * 
 * @author Me
 */
public abstract class AbstractBarcodeInputListener implements KeyListener {
    public static final int DEFAULT_MIN_PAUSE = 300;// [ms]
    public static final int DEFAULT_MAX_TIME_DELTA = 200;// [ms]
    public static final int DEFAULT_MAX_TIME_JITTER = 50;// [ms]

    public static Integer parseInt(Pattern pattern, int group, String line) {
        final Matcher matcher = pattern.matcher(line);
        if (matcher.matches())
            return Integer.parseInt(matcher.group(group));
        return null;
    }

    private String input;

    private final long minPause;
    private long maxTimeDelta;
    private final long maxTimeJitter;

    private long firstTime;
    private long firstTimeDelta;
    private long lastTimeDelta;
    private long lastTime;

    public AbstractBarcodeInputListener(long maxTimeDelta, long maxTimeJitter) {
        this.input = new String();

        this.minPause = AbstractBarcodeInputListener.DEFAULT_MIN_PAUSE;
        this.maxTimeDelta = maxTimeDelta;
        this.maxTimeJitter = maxTimeJitter;

        this.firstTime = 0;
        this.firstTimeDelta = 0;
        this.lastTimeDelta = 0;
        this.lastTime = 0;
    }

    public AbstractBarcodeInputListener() {
        this(AbstractBarcodeInputListener.DEFAULT_MAX_TIME_DELTA,
                AbstractBarcodeInputListener.DEFAULT_MAX_TIME_JITTER);
    }

    private boolean checkTiming(KeyEvent e) {
        final int inputLength = this.input.length();
        final long time = e.getWhen();
        long timeDelta = time - this.lastTime;
        long absJitter = 0;
        long relJitter = 0;

        boolean inputOK = true;

        switch (inputLength) {
        case 0: // pause check
            inputOK &= (timeDelta > this.minPause);
            this.firstTime = time;
            this.firstTimeDelta = timeDelta = 0;
            break;
        case 1: // delta check
            this.firstTimeDelta = timeDelta;
            inputOK &= (timeDelta < this.maxTimeDelta);
            break;
        default:// jitter check & delta check
            absJitter = Math.abs(timeDelta - this.firstTimeDelta);
            relJitter = Math.abs(timeDelta - this.lastTimeDelta);
            inputOK &= (absJitter < this.maxTimeJitter);
            inputOK &= (relJitter < this.maxTimeJitter);
            inputOK &= (timeDelta < this.maxTimeDelta);
            break;
        }

        this.lastTime = time;
        this.lastTimeDelta = timeDelta;

        return inputOK;
    }

    @Override
    public void keyPressed(KeyEvent e) {
    }

    private void clearInput() {
        this.input = new String();
    }

    private void commitInput(KeyEvent e) {
        final String code = this.input;
        if (!code.isEmpty()) {
            final long avgIntervalTime = e.getWhen() - this.firstTime;
            this.maxTimeDelta = (avgIntervalTime * 15) / 10;
            this.clearInput();
            this.codeRead(code);
        }
    }

    @Override
    public void keyReleased(KeyEvent e) {
    }

    @Override
    public void keyTyped(KeyEvent e) {
        if (this.checkTiming(e)) {
            final char c = e.getKeyChar();
            switch (c) {
            case '\b':
                this.clearInput();
                break;
            case '\n':
                this.commitInput(e);
                break;
            default:
                this.input += c;
                break;
            }
        } else {
            this.clearInput();
        }
    }

    public abstract void codeRead(String line);
}
like image 119
Sam Avatar answered Nov 21 '22 14:11

Sam