Считывание входных данных со сканера штрих-кода USB HID без знания VID и PID
Я пытаюсь разработать независимую от устройства библиотеку для сканеров штрих-кода, она должна работать в среде Windows.
Я провел некоторые исследования в этой области, поскольку большинство решений этой проблемы зависят от VID и PID конкретного устройства (RawInput @ filter by vid&pid string), в моей ситуации это недопустимо, потому что я пытаюсь разработать независимое устройство решение, которое будет работать с любым USB-сканером штрих-кода.
На самом деле эта вещь довольно сложная, для меня, по крайней мере, вот точные требования. Также я не могу попросить пользователя подключить устройство с поддержкой "горячей" замены (в этом случае я мог бы просто обнаружить подключенное устройство и извлечь его из vid/pid). Также я не могу использовать VID&PID базу данных устройств. Вообще я вообще не могу использовать vid & pid.
Кроме того, я никак не могу перепрограммировать сканер штрих-кода, если это не сделано из моей программы (может быть, я смогу послать некоторые IOCTL, специфичные для штрих-кодов, которые ответят мне?).
В настоящее время я собираюсь использовать решение, предложенное в этом вопросе: чтение штрих-кода с помощью USB-сканера штрих-кода, а также игнорирование ввода данных с клавиатуры, в то время как идентификатор продукта сканера и идентификатор поставщика неизвестны
Кроме того, я видел коммерческую библиотеку (которая, конечно, поставляется без каких-либо источников и какой-либо информации о том, как она реализована, но, учитывая, что в их списках изменений есть слово "Счетчик производительности", я думаю, что они использовали решение по ссылке выше), которое реализует эту функциональность, но она не работает в системах x64. Возможно, либо из-за грязного кода, либо потому, что он, вероятно, использует какой-то фильтр (мини) драйвер. Это зашифровано, и я не могу распространять его.
Мой точный вопрос: есть ли способ определить, что эта HID клавиатура на самом деле не клавиатура, а сканер штрих-кода? Я видел на Win 7 x64, что он подключается как сканер штрих-кода, а не клавиатура (это была системная ошибка, или вроде того).
Именно то, что я делаю сейчас:
- Чтение ввода по RID_INPUTSINK.
- Различение всего ввода по vid и pid устройства
- Помещение всего ввода в отдельные буферы и сбор штрих-кодов из буфера, когда VK_ENTER показывает в буфере.
Что я сейчас собираюсь сделать:
- Прочитать ввод от RID_INPUTSINK
- Запустить таймер для определенного устройства и, если следующий символ - VK_ENTER - остановить таймер
- Если время таймера превышает 50 мс, отключите его и сбросьте все последующие входные данные устройства.
- Если устройство успешно прочитает последовательность символов из первого символа в VK_ENTER - извлеките VID и 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 сканер штрих-кода имеет UsagePage 0x8C и Usage 0x02. К сожалению, мне не удалось использовать его как RAWINPUTDEVICE.dwUsage и RAWINPUTDEVICE.dwUsagePage. Возможно, потому что система устанавливает драйвер USB-клавиатуры поверх нее, а в пользовательском режиме она неотличима от реальной USB-клавиатуры. Вероятно, эти значения можно использовать в среде режима ядра (один из вариантов - разработка драйвера фильтра hid).
1 ответ
Это не отвечает на ваш конкретный вопрос, но в любом случае...
Более года назад я реализовал поддержку считывателей штрих-кодов в еще более неблагоприятных обстоятельствах. Это было для приложения отчетности с привязкой к логистическим данным на чистом 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);
}