9

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

我在这个领域做了一些研究,afaik 这个问题的大多数解决方案都取决于特定的设备 VID&PID(RawInput @ filter by vid&pid string),在我的情况下这是不可接受的,因为我正在尝试开发一个独立于设备的设备解决方案,它将与任何 USB 条形码扫描仪一起使用。

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

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

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

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

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

正是我现在正在做的事情:

  1. 通过 RID_INPUTSINK 读取输入。
  2. 通过设备的 vid&pid 区分所有输入
  3. 当 VK_ENTER 显示在缓冲区上时,将所有输入放入单独的缓冲区并从缓冲区收集条形码。

我目前要做的事情:

  1. 通过 RID_INPUTSINK 读取输入
  2. 启动特定设备的计时器,如果下一个符号是 VK_ENTER - 停止计时器
  3. 如果定时器超过 50 毫秒限制 - 关闭它并丢弃所有进一步的设备输入。
  4. 如果设备将成功读取从第一个符号到 VK_ENTER 的字符序列 - 提取设备 VID&PID/句柄并以更方便的方式使用它(无需计时)。

我在 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 键盘无法区分。可能这些值在内核模式环境中可用(其中一个选项是开发 hid 过滤器驱动程序)。

4

1 回答 1

24

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

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

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

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

输入被缓冲区消耗,直到不满足机器生成输入的标准,或者验证通过,条形码侦听器将收到通知。在任何一种情况下,都可以转发输入,就好像什么都没发生一样。

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


编辑:

刚挖出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);
}
于 2012-12-31T22:23:54.767 回答