读取 USB HID 条形码扫描仪输入,而无需了解 VID&PID



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

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

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

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

目前,我将使用在这个问题中提出的解决方案:使用USB条形码扫描仪读取条形码,同时忽略键盘数据输入,而扫描仪产品id和供应商id未知

此外,我还看到了商业库(当然,它没有任何来源,也没有任何关于它是如何实现的信息,但考虑到他们的变更日志中有一些单词"Perfomance counter",我想他们在上面的链接中使用了解决方案),它实现了这一功能,但它在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的字符序列;PID/手柄,并以更方便的方式使用它(无需计时)

我正在C++上开发它,纯WinAPI,它将是一个DLL库,并可以在Windows XP、Vista、7、8的x32-86和x32-64架构中工作。

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

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

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

一年多前,我在更不利的情况下实现了对条形码阅读器的支持。这是一个与纯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);
}

最新更新