Понадобилась мне как-то для своих поделок, а позже и по работе пригодилась, библиотека для работы с USB HID устройствами. После непродолжительных поисков в далеком 2010 году был найден проект USB HID Component for C#.
Но она явно была не доделана, имелись некоторые проблемы и не хватало поддержки Feature Report’ов. Также она не была предназначена или, скорее, не было примера, как эту библиотеку использовать на WPF, т.к. изначальный пример в статье дан для Windows Forms. Попробуем доработать… :)
Общее описание и существующие проблемы
Когда изучил эту библиотеку, она понравилась продуманностью и хорошими комментариями, на момент необходимости подобной библиотеки мне не хватало только реализации обмена с устройством посредством Feature Report’ов. Также была пара небольших косяков, к примеру, если подписаться на основное событие подключения указанного устройства (OnSpecifiedDeviceArrived), могли вызываться другие дополнительные события с null ссылкой без проверки (OnDataRecieved или OnDataSend), а также если не было подписки на OnDeviceArrived, то устройства вообще не проверяются! (не вызывается функция CheckDevicePresent(); ) Из дополнений для оригинальной библиотеки:
- реализована возможность искать и подключаться устройству не только по паре PID\VID, но еще и дополнительно с проверкой Product String;
- обновление на лету Product String с автопереподключением;
- проверка возможности подключения к указанному устройству без поддержания связи (т.е. библиотека пробует подключиться к устройству с заданными параметрами, если успешно найдено и не занято — возвращает true и отключается от него, тем самым освобождая доступ для других приложений);
- чтение не только длины всех поддерживаемых устройством Report’ов, но и строк Manufacturer и Product.
Но остался не решенный, как и в оригинальной библиотеке, так и в мною измененной, такой баг — категорически не работает поиск устройств, если собрать программу под x64 или All Platforms. Для корректной работы библиотеки необходимо, чтобы она была скомпилирована под x86, тогда прекрасно работает и на x86, и на x64 системах (проверялась временем на Windows 7, 8, 8.1, 10).
Пример использования библиотеки
Пример взаимодействия библиотеки я сделал как для Windows Forms, так и для WPF, по структуре кода и взаимодействию с библиотекой они практически идентичны, отличается только часть, отвечающая за парсинг событий ОС от USB шины.
Визуально же код оформлен в обоих проектах одинаково и показано минимально необходимое количество кода для работы с библиотекой:
Внешний вид также максимально похож, хотя это и не имеет особого значения:
Минимальный код для обоих проектов, чтобы открыть USB устройство и записать\прочитать что-либо:
UsbHidPort usb = new UsbHidPort(); usb.ProductId = 0x2301; usb.VendorId = 0xC251; usb.Open(true); if(usb.Ready()) { // тут исполняемый код }
Но этот кусок кода будет работать, только если устройство уже подключено к ПК. Если оно не было подключено до запуска программы или его переподключили\отключили — программа ничего об этом не узнает и в первом случае просто не сможет работать с устройством, во втором же даже будет считать, что она всё еще подключена к нему! Для этого и необходимо парсить события ОС по части USB шины, чтобы оперативно реагировать на подключение\отключение любых USB устройств.
Для Windows Forms парсинг событий ОС выглядит крайне просто и переписывает оригинальные функции окна приложения. Просто вставляем в Main Windows этот код и ничего более делать не надо:
protected override void OnHandleCreated(EventArgs e) { base.OnHandleCreated(e); usb.RegisterHandle(Handle); } protected override void WndProc(ref Message m) { usb.ParseMessages(ref m); base.WndProc(ref m); }
Для WPF чуть сложнее, тут нельзя просто так переопределить подобные функции, к примеру, WndProc тут просто-напросто нет (это Windows Forms функция), для реализации этого в WPF необходимо добавить в References ссылку на System.Windows.Forms:
Добавить при загрузке приложения нашу функцию-обработчик событий:
private void Window_Loaded(object sender, RoutedEventArgs e) { HwndSource src = HwndSource.FromHwnd(new WindowInteropHelper(this).Handle); src.AddHook(new HwndSourceHook(WndProc)); // .... // ваш код }
А так выглядят переделанные функции парсинга событий ОС для WPF:
#region Обработка системных событий private IntPtr WndProc(IntPtr hwnd, int msg, IntPtr wParam, IntPtr lParam, ref bool handled) { Message m = new Message(); m.HWnd = hwnd; m.Msg = msg; m.WParam = wParam; m.LParam = lParam; usb.ParseMessages(ref m); return IntPtr.Zero; } protected override void OnSourceInitialized(EventArgs e) { base.OnSourceInitialized(e); var handle = new WindowInteropHelper(this).Handle; usb.RegisterHandle(handle); } #endregion
Пример отправки Report’а
В качестве примера взаимодействия программы на ПК и устройства разберем функцию команды условного протокола:
// определяем константой, сколько у нас байт в Report'е // можно прочитать это и из устройства, но проверять его наличие до этого по usb.Ready() // или же по возвращаемому значению - если = 0, то отменяем отправку const byte USB_ReportLength = 127 + 1; // +1 - Report ID // условная функция для применения настроек, которая описана уже в приложении, не библиотеке public static bool ApplySettings() { // создаем массивы для отправки и приёма byte[] ReportData = new byte[USB_ReportLength]; byte[] ReportAnswer = new byte[USB_ReportLength]; // флаг успешной отправки bool Succsess = true; // Условная команда в устройстве // используется только первые 2 байта (не считая Report ID) ReportData[0] = 0x82; ReportData[1] = 0x90; // устройство подключено? if (usb.Ready()) { // Report ID = 0, массив для отправки = ReportData, принимаемый массив = ReportAnswer Succsess = usb.WriteFeatureReport(0x00, ReportData, ref ReportAnswer); // далее можно обработать принятый массив (report) // в принятом report'e 0й байт = Report ID // после него уже идет по порядку сам ответный Report if(ReportAnswer[1] != 0x82) Succsess = false; } else { Succsess = false; } return Succsess; }
Итог и ссылки
Библиотека проверялась с различными программными реализациями USB HID устройств на разных архитектурах МК и проблем стабильности работы замечено не было. Проверенные библиотеки для МК:
- для AVR — LUFA;
- для STM32 — Keil CMSIS USB Device;
- для 1986ВЕ9х, 1986ВЕ1Т — самописная библиотека (в основном благодаря R Max).
Cсылка для скачивания библиотеки и двух простейших примеров — UsbLibrary-Example (по лицензии WTFPL ;) )