Считывание входных данных со сканера штрих-кода 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, что он подключается как сканер штрих-кода, а не клавиатура (это была системная ошибка, или вроде того).

Именно то, что я делаю сейчас:

  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, и я должен работать в 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);
}
Другие вопросы по тегам