在不知道VID和PID的情况下读取USB HID条形码扫描器输入

时间:2012-09-19 14:43:06

标签: windows winapi 64-bit barcode barcode-scanner

我正在尝试为条形码扫描仪开发设备独立库,它必须在Windows环境中工作。

我已经在这个领域做了一些研究,afaik这个问题的大部分解决方案都取决于具体的设备VID和PID(RawInput @ filter by vid& pid string),在我的情况下这是不可接受的,因为我'我正在尝试开发一种独立于设备的解决方案,该解决方案将与任何USB条形码扫描仪配合使用。

实际上这件事非常具有挑战性,至少对我来说,这里有确切的要求。此外,我不能要求用户热插拔设备(在这种情况下,我只能检测插入的设备并提取它的视频/ pid)。此外,我不能使用设备的VID和PID数据库。一般来说,我实际上根本不能使用vid和pid。

另外我不能以任何方式重新编程条形码扫描仪,除非它是从我的程序完成的(也许我可以发送一些特定于条形码扫描器的IOCTL,它会让它回答我?)。

目前我将使用此问题中提出的解决方案: Reading a barcode using a USB barcode scanner along with ignoring keyboard data input while scanner product id and vendor id are not known

此外,我已经看到商业图书馆(这是一个没有任何来源和任何有关如何实施的信息,但考虑到他们在他们的更改日志中有一些单词“Perfomance counter”,我猜他们在上面的链接中使用了解决方案),它实现了这个功能,但它在x64系统中不起作用。可能是因为代码混乱或因为它可能使用某种过滤器(迷你)驱动程序。它是加密的,我无法重新分配它。

我的确切问题是: 有没有办法确定这个HID键盘实际上不是键盘,而是条形码扫描仪?我在Win 7 x64上看到它连接的是条码扫描器,而不是键盘(这是一个系统错误,或者某种类型)。

我现在正在做的事情:

  1. 通过RID_INPUTSINK读取输入。
  2. 通过vid和设备的pid区分所有输入
  3. 当VK_ENTER显示在缓冲区上时,将所有输入放入单独的缓冲区并从缓冲区收集条形码。
  4. 我目前要做的事情:

    1. 通过RID_INPUTSINK读取输入
    2. 启动特定设备的计时器,如果下一个符号是VK_ENTER - 停止计时器
    3. 如果定时器超过50 ms限制 - 关闭它并丢弃所有进一步的设备输入。
    4. 如果设备将成功读取第一个符号到VK_ENTER的字符序列 - 提取设备VID和PID /句柄并以更方便的方式使用它(无需定时)。
    5. 我在C ++,纯WinAPI上开发它,它将是一个DLL库,并且可以在x32-86和x32-64架构上的Windows XP,Vista,7,8中工作。

      更新0: 刚刚发现条码扫描器有自己的用法页面和USB规格的用法: http://www.usb.org/developers/devclass_docs/pos1_02.pdf

      根据此文档,USB条形码扫描仪具有UsagePage 0x8C和Usage 0x02。不幸的是我没能将它用作RAWINPUTDEVICE.dwUsage和RAWINPUTDEVICE.dwUsagePage。可能是因为系统安装了它的usb键盘驱动程序,在用户模式下,它与真正的USB键盘无法区分。这些值可能在kernelmode环境中可用(其中一个选项是开发hid过滤器驱动程序)。

1 个答案:

答案 0 :(得分:18)

这不能回答您的具体问题,但无论如何......

一年多以前,我在更恶劣的环境下实施了条形码阅读器支持。这是一个报告应用程序,与纯Java(跨平台富客户端,主要是Windows)中的逻辑数据相关联。 我发现了关于键盘驱动程序的相同信息,这可以防止在用户模式下区分实际USB设备,至少乍一看。有更昂贵的设备具有自己的驱动程序和高级功能,这将允许某种区别。 我在该环境中遇到的所有条形码阅读器都显示为键盘,用于简单填写SAP表单字段并点击回车键,这是一种常见情况。终止可以使用“魔术条形码”或其他制造商特定方法进行配置。

因此决定反对任何基于JNI的平台特定实现。 相反,我通过使用以下标准评估某些Swing / AWT表单中的通用keyoard输入,实现了类似拦截的方法(你的扩展版本):

  • 由前两个字符(最初/超时后)确定的击键频率
  • 抖动(频率/速率变化)
  • 有效字符集
  • 终止换行符。

输入被缓冲区占用,直到不满足机器生成输入的条件,或者验证已通过,条形码监听器将被通知。在任何一种情况下,都可以转发输入,就像没有其他事情一样。

事实证明这是非常准确的,因为对于人来说,几乎不可能以条形码阅读器的速率输入有效序列(几乎)零抖动。


修改

刚刚挖出Java源代码;我可以为您提供上述实施的早期修订代码示例(无保修,也考虑实施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);
}