Протокол HID (Human Interface Device) изначально предназначался для того, чтобы упростить использование устройств вроде мыши, клавиатуры и джойстика. Однако из-за его уникальных особенностей, в том числе возможностей, не требующих дополнительного описания, производители оборудования стали использовать этот протокол для поддержки медицинских приборов и оборудования, а также для пользовательских датчиков. Если вы новичок в HID API, обратитесь на сайт USB HID Information (bit.ly/1mbtyTz). Еще один великолепный ресурс — книга Йена Экселсона (Jan Axelson) «USB Complete: The Developer’s Guide, Fourth Edition» (Lakeview Research LLC, 2009).
До Windows 8.1, если вы писали приложение для HID-устройства, вы создавали неуправляемое Win32-приложение. Но если вы были веб- или .NET-разработчиком, цена была непомерной. Чтобы устранить эту проблему, Microsoft ввела HID Windows Runtime (WinRT) API с появлением Windows 8.1 (bit.ly/1aot1by). Этот новый API позволяет писать приложения Windows Store для вашего устройства, используя JavaScript, Visual Basic, C# или C++.
Кроме того, Microsoft недавно добавила поддержку нескольких новых транспортов, поэтому вы не ограничены USB-кабелем. Сегодня вы можете создать HID-устройство, которое передает и принимает пакеты по USB, Bluetooth, Bluetooth LE и I2C. (Подробнее на эту тему см. «HID Transports» по ссылке bit.ly/1asvwg6.)
В этой статье я покажу, как можно создать простой датчик температуры, совместимый с протоколом HID. Затем опишу пример приложения Windows Store, способного отображать температурные данные от этого устройства.
Конструирование датчика температуры
Это устройство базируется на плате Netduino (netduino.com). Это плата с открытым исходным кодом, используемая любителями, научными сотрудниками и инженерами в промышленности для построения рабочих прототипов своих устройств. А поскольку Netduino совместима по выводам с Arduino, вы можете подключать имеющиеся у вас насадки (shields) для Arduino для быстрого расширения функционала. (Насадка — это плата со специфической функциональностью, например для беспроводной связи, управления мотором, Ethernet, RS232, ЖК-дисплея и т. д.) В моем устройстве используется насадка с поддержкой RS232 для загрузки прошивки. Для передачи и приема данных задействован разъем USB на самой плате.
Netduino поддерживает .NET Micro Framework, и ее прошивка создается с помощью бесплатной копии Visual C# Express.
Чтобы получать температурные данные, устройство использует датчик Texas Instruments LM35. Датчик принимает пятивольтный ввод от Netduino и преобразует его в выходное напряжение, пропорциональное текущей температуре по Цельсию.
Ниже перечислены детали, которые понадобятся для сборки собственного HID-датчика.
- Netduino 1 или Netduino Plus 1 (Amazon, amzn.to/1dvTeLh) — макетная плата с программируемым микроконтроллером, который поддерживает .NET Micro Framework.
- Насадка RS232 (CuteDigi, bit.ly/1j7uaMR) — модуль RS232 для загрузки и отладки прошивки. (Эта насадка необходима для используемой бета-версии прошивки.)
- Датчик LM35 (DigiKey, bit.ly/KjbQkN) —датчик температуры, который преобразует входное напряжение в выходное в зависимости от текущей температуры.
- Кабель-преобразователь между RS232 и USB (Parallax, bit.ly/1iVmP0a) — кабель для загрузки прошивки датчика температуры через насадку RS232. (Заметьте, что для совместимости с этой насадкой требуется чипсет FTDI.)
- Блок питания на 9 В и 650 мА (Amazon, amzn.to/1d6R8LH) — блок питания для платы Netduino.
- Кабель-переходник с USB на Micro-USB (Amazon, amzn.to/Kjc8Ii) — кабель для отправки HID-пакетов от Netduino на планшет или лэптоп с Windows 8.1.
Собранный HID-датчик температуры показан на рис. 1.
Рис. 1. Полностью собранный HID-датчик температуры
Насадка RS232 подключается к верхней части Netduino. Макетная плата содержит датчик LM35, который подключается к 5 В, земле и разъему 0. (Разъем 0 — один из шести разъемов аналого-цифрового преобразователя [АЦП] на плате.) Итак, начнем.
Прошивка, с которой поставляется Netduino 1 (или Netduino Plus 1), не поддерживает протокол HID. Вам придется сконфигурировать свою макетную плату, установив бета-версию прошивки 4.1.1, которая включает поддержку HID. Эту прошивку вы найдете по ссылке bit.ly/1a7f6MB. (Чтобы скачать этот файл, нужно создать учетную запись, зарегистрировавшись в Secret Labs.)
Страница скачивания на этом веб-сайте включает инструкции по обновлению прошивки. Однако эти инструкции довольно сложны, особенно если вы новичок в Netduino. По ссылке bit.ly/1d73P9x вы найдете полезный видеоролик с четким описанием всего процесса.
После обновления прошивки платы вы готовы приступить к конструированию схемы датчика температуры. На первом этапе вы должны подключить насадку RS232 к своей плате. (Как уже упоминалось, Netduino совместима по выводам с Arduino, поэтому, если вы уже работали с Arduino и у вас есть насадка RS232, вы можете воспользоваться ею.) Вставьте насадку RS232 в Netduino, как показано на рис. 2.
рис. 2. Подключение насадки RS232 к Netduino
После подключения насадки RS232 следующий этап — подключение датчика температуры к пятивольтному источнику, земле и коннектору 0 в Netduino. Разъемы датчика, взятые из спецификации TI, показаны на рис. 3.
рис. 3. Разъемы датчика
Установка прошивки датчика
Существует два уровня, или экземпляра, прошивки в Netduino. Первый — это прошивка от производителя, которая включает .NET Micro Framework; второй — прошивка вашего устройства. Прошивка от производителя обрабатывает запросы от прошивки устройства. Прошивка от производителя загружается в макетную плату и выполняется каждый раз, когда вы включаете устройство. В противоположность этому вы обычно многократно обновляете прошивку своего устройства в процессе разработки или испытания прототипа.
Чтобы установить прошивку какого-либо устройства, вам нужно сначала установить на компьютер разработки экземпляр Visual C# Express 2010. Ссылку на его скачивание вы найдете на bit.ly/1eRBed1.
В большинстве проектов с применением Netduino вы можете загружать и отлаживать свою прошивку, используя «родное» USB-соединение. Однако бета-версия прошивки от производителя требует RS232-соединения (вот зачем нужна насадка RS232).
После установки Visual C# Express подключите кабель RS232-USB и откройте диспетчер устройств в Windows, чтобы определить, какой COM-порт Windows назначила этому кабелю.
Например, когда я подключаю к своему компьютеру разработки конвертер Parallax RS232-to-USB, Windows сопоставляет его с COM6, как показано на рис. 4.
рис. 4. COM-порт, назначенный кабелю RS232-USB
Теперь, зная COM-порт, связанный с конвертером, я могу включить свою Netduino Plus 1, присоединить кабель RS232-USB и запустить экземпляр Visual C# Express, чтобы выполнить загрузку.
После запуска Visual C# Express первым делом нужно идентифицировать корректный транспорт и COM-порт. Для этого щелкните правой кнопкой мыши имя проекта в Solution Explorer и выберите Properties.
Когда появится диалог Properties, выберите вкладку .NET Micro Framework и укажите соответствующие параметры, как показано на рис. 5.
рис. 5. Настройка свойств .NET Micro Framework
Указав Transport и Device, можно устанавливать прошивку. И вновь щелкните правой кнопкой мыши имя проекта в Solution Explorer, но на этот раз выберите Deploy. По окончании установки Visual C# Express сообщит об успехе в секции Output.
Теперь вы готовы подключить свое устройство к планшету или лэптопу с Windows 8.1 и протестировать его с приложением-примером Custom Temperature Sensor.
Сначала отключите RS232-кабель, выключите Netduino, а затем перезапустите его с использованием вспомогательного источника питания. Дайте устройству несколько секунд на переход в рабочий режим и подключите USB-кабель к Netduino. После этого вы должны увидеть свое устройство добавленным в набор HID-устройств в диспетчере устройств. (VID и PID на рис. 6 соответствуют VID и PID образца устройства; эти сокращения расшифровываются как идентификаторы поставщика [vendor] и продукта.)
рис. 6. VID и PID образца устройства
Установив устройство в Windows 8.1, вы захотите создать и установить пример-приложения. При запуске этого приложения можно выбрать датчик и начать мониторинг температуры воздуха в вашем офисе.
Прошивка устройства
Теперь подробно рассмотрим прошивку датчика температуры. Прежде всего я хотел бы поблагодарить сотрудников Secret Labs (производителя Netduino) за ту работу, которую они проделали для поддержки HID по USB на платформе Netduino. Базой для этой прошивки послужил проект-пример UsbHidEchoNetduinoApp на форуме (bit.ly/1eUYxAM).
Поддержка транспорта USB Как говорилось ранее, Microsoft поддерживает HID-устройства, работающие по USB, Bluetooth, Bluetooth LE и I2C. Однако устройство-образец, описываемое в этой статье, использует транспорт USB. Это означает, что USB-драйверы будут перемещать пакеты в обоих направлениях: пакеты, исходящие от устройства, передаются драйверу HID (а тот передает их в API, если есть приложения, заинтересованные в этих пакетах), а пакеты, исходящие от драйвера HID, передаются устройству.
Чтобы определить, какие USB-драйверы должны быть загружены, Windows использует специфические данные, выдаваемые устройством при подключении.
После запуска Visual C# Express первым делом нужно идентифицировать корректный транспорт и COM-порт.
Определение классов в прошивке Прошивка для датчика температуры создается на основе двух классов: Program и Sensor. Класс Program поддерживает одну процедуру Main, вызываемую при запуске. Класс Sensor определяет параметры USB и HID для датчика температуры. Кроме того, он поддерживает методы, которые отправляют отчеты ввода (input reports) и считывают отчеты вывода (output reports).
Класс Sensor содержит весь код, необходимый для конфигурирования транспорта USB. К нему относится код, который:
- настраивает конечную точку для чтения;
- настраивает конечную точку для записи;
- указывает Vendor ID (VID);
- указывает Product ID (PID);
- указывает описательные названия (название производителя, название продукта и т. д.);
- указывает другие необходимые параметры USB для HID-устройства.
Большая часть конфигурационного кода для USB находится в методе ConfigureHID в модуле Sensors.cs. В свою очередь этот метод создает и инициализирует объект Configuration (bit.ly/1i1IcQ3), который содержит параметры USB для устройства (конечные точки, VID, PID и др.).
Конечная точка для чтения позволяет устройству принимать пакеты от API и драйвера HID, а конечная точка для записи дает возможность драйверу посылать пакеты вверх по стеку драйверов в API.
Windows использует VID, PID и другие параметры USB (которые были указаны в методе ConfigureHID), чтобы определять, является ли устройство допустимым USB-устройством, а затем загружать соответствующие драйверы.
Открытие соединения с устройством Класс Sensor включает метод Open, вызываемый из процедуры Main класса Program. Как видно из рис. 7, метод Open:
- получает список доступных контроллеров USB;
- вызывает метод ConfigureHID, чтобы задать параметры USB и HID для устройства;
- вызывает метод Start применительно к первому доступному контроллеру;
- создает объект потока данных USB с конечными точками для чтения и записи.
рис. 7. Метод Open
public bool Open()
{
bool succeed = true;
started = false;
UsbController[] usbControllers = UsbController.GetControllers();
if (usbControllers.Length < 1)
{
succeed = false;
}
if (succeed)
{
usbController = usbControllers[0];
try
{
succeed = ConfigureHID();
if (succeed)
{
succeed = usbController.Start();
}
if (succeed)
{
stream = usbController.CreateUsbStream(WRITE_ENDPOINT,
READ_ENDPOINT);
}
}
catch (Exception)
{
succeed = false;
}
}
started = true;
return succeed;
}
Класс Sensor также включает метод Close, вызываемый, когда устройство отключается от хоста (лэптопа или планшета).
Поддержка протокола HID
Протокол HID базируется на отчетах возможностей (feature reports), ввода (input reports) и вывода (output reports). Отчеты возможностей отправляются либо хостом (т. е. подключенным лэптопом или планшетом), либо устройством. Отчеты ввода передаются устройством хосту, а отчеты вывода — хостом устройству.
В случае нашего датчика температуры отчет ввода — это очень простой двухбайтовый пакет. Первый байт указывает текущую температуру в градусах Фаренгейта, а второй — текущий интервал отчетов в миллисекундах. (Прошивка датчика выдает отчет ввода с частотой, заданной интервалом отчетов.)
Отчет вывода для нашего устройства еще проще: всего один байт, указывающий интервал отчетов. (Это целое значение, представляющее интервал в миллисекундах.)
Создание дескриптора отчета Как уже говорилось, одна из особенностей HID-устройства — отсутствие необходимости в дополнительном описании возможностей: при подключении к хосту устройство само предоставляет описание своего предназначения, возможностей и формата пакетов в виде так называемого дескриптора отчетов (report descriptor). Этот дескриптор указывает, какое место данное устройство занимает в мире HID (является ли оно мышью, клавиатурой или устройством, определенным поставщиком). Дескриптор также сообщает формат отчетов индивидуальных возможностей, ввода и вывода.
Дескриптор отчетов для датчика температуры находится в Sensors.cs (рис. 8).
Рис. 8. Дескриптор отчетов для датчика температуры
hidGenericReportDescriptorPayload = new byte[]
{
0x06,0x55,0xFF, //HID_USAGE_PAGE_VENDOR_DEFINED
0x09,0xA5, //HID_USAGE (vendor_defined)
0xA1,0x01, //HID_COLLECTION(Application),
// Отчет ввода (передаваемые устройством данные)
0x09,0xA7, //HID_USAGE (vendor_defined)
// Мин. температура - 0 градусов F
0x15,0x00, //HID_LOGICAL_MIN_8(0),
// Макс. поддерживаемая температура - 150 градусов F
0x25,0x96, //HID_LOGICAL_MAX_8(150),
0x75,0x08, //HID_REPORT_SIZE(8),
0x95,0x01, //HID_REPORT_COUNT(1),
0x81,0x02, //HID_INPUT(Data_Var_Abs),
0x09,0xA8, //HID_USAGE (vendor_defined)
0x15,0x4B, //HID_LOGICAL_MIN_8(75), // мин. 75 мс
0x25,0xFF, //HID_LOGICAL_MAX_8(255), // макс. 255 мс
0x75,0x08, //HID_REPORT_SIZE(8),
0x95,0x01, //HID_REPORT_COUNT(1),
0x81,0x02, //HID_INPUT(Data_Var_Abs),
// Отчет вывода (принимаемые устройством данные)
0x09,0xA9, //HID_USAGE (vendor_defined)
0x15,0x4B, //HID_LOGICAL_MIN_8(75), // мин. 75 мс
0x25,0xFF, //HID_LOGICAL_MAX_8(255), // макс. 255 мс
0x75,0x08, //HID_REPORT_SIZE(8),
0x95,0x01, //HID_REPORT_COUNT(1),
0x91,0x02, //HID_OUTPUT(Data_Var_Abs),
0xC0 //HID_END_COLLECTION
};
Первые две строки дескриптора информируют хост о том, что данное устройство определено поставщиком (vendor_defined):
0x06,0x55,0xFF, //HID_USAGE_PAGE_VENDOR_DEFINED
0x09,0xA5, //HID_USAGE (vendor_defined)
Строки 4–15 задают формат двухбайтового отчета ввода. Строки 4–9 описывают первый байт отчета ввода, который сообщает показание температуры:
0x09,0xA7, //HID_USAGE (vendor_defined)
0x15,0x00, //HID_LOGICAL_MIN_8(0), // Мин. температура - 0 градусов F
0x25,0x96, //HID_LOGICAL_MAX_8(150), // Макс. поддерживаемая температура - 150 градусов F
0x75,0x08, //HID_REPORT_SIZE(8),
0x95,0x01, //HID_REPORT_COUNT(1),
0x81,0x02, //HID_INPUT(Data_Var_Abs),
Протокол HID базируется на отчетах возможностей, ввода и вывода.
Строки 10–15 описывают второй байт отчета ввода, который указывает интервал отчетов (в миллисекундах):
0x09,0xA8, //HID_USAGE (vendor_defined)
0x15,0x4B, //HID_LOGICAL_MIN_8(75), // мин. 75 мс
0x25,0xFF, //HID_LOGICAL_MAX_8(255), // макс. 255 мс
0x75,0x08, //HID_REPORT_SIZE(8),
0x95,0x01, //HID_REPORT_COUNT(1),
0x81,0x02, //HID_INPUT(Data_Var_Abs),
Дескриптор отчета для устройства-образца включается в объект UsbController.Configuration (bit.ly/1cvcq5G), создаваемый в методе ConfigureHID в Sensor.cs.
Поддержка HID-отчета ввода Отчет ввода определяется как структура в модуле Sensor.cs:
struct InputReport
{
public byte Temperature; // Температура в градусах Фаренгейта
public byte Interval; // Интервал отчетов (или частота) в секундах
}
Прошивка выдает отчеты ввода, используя объект UsbStream (bit.ly/1kElfUZ), созданный в методе Open. Эти отчеты ввода отправляются методом SendInputReport, когда прошивка вызывает метод stream.Write:
protected void SendInputReport(InputReport report)
{
byte[] inputReport = new byte[2];
inputReport[0] = (byte)report.Temperature;
inputReport[1] = (byte)report.Interval;
stream.Write(inputReport, 0, 2);
}
Передача температурных данных в отчетах ввода Метод Update класса Sensor выдает отчет ввода подключенному хосту:
public int Update(int iTemperature, int iInterval)
{
InputReport inputReport = new InputReport();
byte Interval = 0;
inputReport.Temperature = (byte)iTemperature;
inputReport.Interval = (byte)iInterval;
SendInputReport(inputReport);
Interval = GetOutputReport();
return (int)Interval;
}
Метод Update вызывается из бесконечного цикла while (рис. 9), который выполняется в процедуре Main прошивки (в Program.cs).
рис. 9. Цикл while, вызывающий метод Update
while (true)
{
// Получаем текущее показание температуры
// Read возвращает значение в указанном диапазоне
milliVolts = (double)voltsPin.Read();
// Датчик возвращает 10 мВ на градус шкалы Цельсия
tempC = milliVolts / 10.0;
// Преобразуем в градусы по Фаренгейту
tempF = 1.8 * tempC + 32;
simpleTemp = (int)tempF;
// Поскольку при подключении внешнего блока питания
// к Netduino возникают флуктуации напряжения,
// используем скользящее среднее (running average)
// для "сглаживания" значений
if (firstReading)
{
firstReading = false;
currentTemp = simpleTemp;
for (i = 0; i < 12; i++)
tempArray[i] = simpleTemp;
}
else
{
// Смещаем элементы массива и вставляем новую температуру
tempArray = Shift(simpleTemp, tempArray);
// Вычисляем скользящее среднее по последним 12 показаниям
currentTemp = Average(tempArray);
}
RequestedInterval = sensor.Update(
currentTemp, CurrentInterval);
// Проверяем, не был ли запрошен новый интервал
// через отчет вывода
if (RequestedInterval != 0)
{
CurrentInterval = RequestedInterval;
}
led.Write(true);
Thread.Sleep(CurrentInterval);
led.Write(false);
}
}
Этот новый API позволяет вашему приложению получать данные от HID-устройств, а также управлять ими.
Поддержка HID-отчета вывода Отчет вывода определяется как структура в модуле Sensor.cs:
struct OutputReport
{
public byte Interval; // Интервал отчетов (или частота) в секундах
}
Прошивка принимает отчеты вывода через тот же объект UsbStream, созданный в методе Open. Эти отчеты вывода принимаются методом GetOutputReport:
protected byte GetOutputReport()
{
byte[] outputReport = new byte[1];
int bytesRead = 0;
if (stream.CanRead)
{
bytesRead = stream.Read(outputReport, 0, 1);
}
if (bytesRead > 0)
return outputReport[0];
else
return 0;
}
Регулировка интервала отчетов с помощью отчетов вывода Прошивка поддерживает задание интервала отчетов в миллисекундах от 75 до 255. Приложение запрашивает новый интервал, отправляя отчет вывода устройству. Устройство в свою очередь сообщает текущий интервал в каждом отчете ввода, который передается подключенному приложению.
Прошивка применяет текущий интервал вызовом метода Thread.Sleep (bit.ly/LaSYVF) на то число секунд, которое указано текущим интервалом:
led.Write(true);
Thread.Sleep(CurrentInterval);
led.Write(false);
Приостанавливая цикл while на это время, зарегистрированные приложения принимают отчеты ввода через определенный интервал.
Приложение для HID-датчика температуры
Приложение-пример демонстрирует, как можно отображать температурные данные от подключенного HID-датчика температуры, используя новый HID WinRT API для Windows 8.1. Этот новый API позволяет вашему приложению получать данные от HID-устройств, а также управлять ими.
Пример рассчитан на работу с подключенным HID-устройством, которое распознает температуры в диапазоне от 0 до 150 градусов по Фаренгейту. Приложение отслеживает и отображает текущее показание датчика температуры.
Приложение поддерживает три «сценария», каждый из которых связан со специфическими функциями в UI этого приложения. Кроме того, каждый сценарий сопоставлен с соответствующими XAML и файлом исходного кода на C#. Ниже перечислены все сценарии, их модули и то, для чего они предназначены.
Device Connect (Scenario1_ConnectToSensor.xaml и Scenario1_ConnectToSensor.xaml.cs):
- поддерживает подключение HID-устройства к компьютеру с Windows 8.1;
- перечисляет подключенные датчики температуры, чтобы пользователь мог выбрать нужный;
- устанавливает сторож устройства (device watcher), который следит за состоянием устройства. (Сторож устройства генерирует событие, когда пользователь отключает или заново подключает выбранное HID-устройство.)
Get Temperature Data (Scenario2_GetTemperatureData.xaml и Scenario2_GetTemperatureData.xaml.cs):
- ведет мониторинг выбранного датчика температуры;
- рисует температурную шкалу и визуализирует текущее показание, используя элемент управления «ползунок».
Set Report Interval (Scenario3_SetReportInterval.xaml и Scenario3_SetReportInterval.xaml.cs):
- позволяет контролировать частоту, с которой датчик температуры сообщает о своем состоянии. (Интервал по умолчанию — 250 мс, но пользователь может выбрать любой интервал от 75 до 250 мс.)
Поддержка соединений устройства
Сценарий соединений с устройством охватывает несколько аспектов подключения HID-устройства к компьютеру с Windows 8.1: перечисление подключенных устройств, установку сторожа устройства, обработку отключения устройства и его повторное подключение.
Установление соединения с устройством Код, обрабатывающий соединения устройства, находится в трех модулях: Scenario1_ConnectToSensor.xaml.cs, EventHandlerForDevices.cs и DeviceList.cs. (Первый модуль содержит основной код для этого сценария; остальные два включают вспомогательную функциональность.)
Первая фаза соединения осуществляется до того, как UI становится видимым. В этой фазе приложение создает объект DeviceWatcher, который уведомляет приложение о добавлении, удалении или изменении устройств. Вторая фаза наступает после отображения UI, и пользователь может выбрать конкретное устройство из числа подключенных HID-устройств. Приложение выводит строку DeviceInstanceId для каждого подключенного устройства; эта строка включает VID и PID для данного устройства. В случае нашего датчика температуры строка DeviceInstanceId имеет вид:
HID\VID_16C0&PID_0012\6&523698d&0&0000
На рис. 10 показано приложение по окончании перечисления и после того, как пользователь подключился к устройству.
рис. 10. Приложение Windows 8.1, подключенное к HID-устройству
Первая фаза соединения с устройством Ниже кратко описываются методы, вызываемые в первой фазе соединения с устройством (до отображения UI), а также задачи, выполняемые каждым методом:
DeviceConnect (Scenario1_DeviceConnect.xaml.cs) вызывает метод CustomTemperatureSensor.InitializeComponent, который инициализирует UI-компоненты приложения, такие как блоки текста и кнопки.
InitializeDeviceWatchers (Scenario1_DeviceConnect.xaml.cs) вызывает метод HidDevice.GetDeviceSelector, чтобы получить строку селектора устройств. (Селектор необходим для создания сторожа устройства.) Получив селектор, приложение вызывает DeviceInformation.CreateWatcher, чтобы создать объект DeviceWatcher, а потом вызывает EventHandlerForDevice.Current.AddDeviceWatcher. (Последний метод позволяет отслеживать изменения в состоянии устройства.)
AddDeviceWatcher (EventHandlerForDevices.cs) создает обработчики трех событий устройств: Enumeration Completed (перечисление завершено), Device Added (добавлено устройство) и Device Removed (устройство удалено).
SelectDeviceInList (Scenario1_DeviceConnect.xaml.cs) проверяет, выбрал ли пользователь какое-то устройство, и, если да, сохраняет индекс для этого устройства.
В терминах HID API основной код находится в методе InitializeDeviceWatchers (рис. 11). Этот код вызывает метод HidDevice.GetDeviceSelector (bit.ly/1eGQI1k) и передает параметры UsagePage, UsageId, VID и PID для датчика температуры.
рис. 11. Метод InitializeDeviceWatchers
private void InitializeDeviceWatchers()
{
// Пользовательский датчик
var CustomSensorSelector =
HidDevice.GetDeviceSelector(CustomSensor.Device.UsagePage,
CustomSensor.Device.UsageId, CustomSensor.Device.Vid,
CustomSensor.Device.Pid);
// Создаем сторож устройства для поиска
// экземпляров пользовательского датчика
var CustomSensorWatcher =
DeviceInformation.CreateWatcher(CustomSensorSelector);
// Разрешаем EventHandlerForDevice обрабтывать события
// сторожа устройства, которые относятся к устройству
// или влияют на него (удаление устройства, добавление,
// приостановка/возобновление работы приложения)
AddDeviceWatcher(CustomSensorWatcher, CustomSensorSelector);
}
Значения UsagePage и UsageId определены в файле constants.cs:
public class Device
{
public const UInt16 Vid = 0x16C0;
public const UInt16 Pid = 0x0012;
public const UInt16 UsagePage = 0xFF55;
public const UInt16 UsageId = 0xA5;
}
Эти члены класса соответствуют значениям, указанным в HID-дескрипторе отчета, который определен в прошивке устройства:
hidGenericReportDescriptorPayload = new byte[]
{
0x06,0x55,0xFF, //HID_USAGE_PAGE_VENDOR_DEFINED
0x09,0xA5, //HID_USAGE (vendor_defined)
Метод GetDeviceSelector возвращает строку Advanced Query Syntax (AQS) в переменной CustomSensorSelector. Приложение использует эту строку, когда создает устройство и когда перечисляет объекты DeviceInformation.
Вторая фаза соединения с устройством В этой фазе пользователь может сделать выбор в списке подключенных устройств. Тем самым определяется текущее выбранное устройство. Ниже перечислены вызываемые при этом методы (все они содержатся в EventHandlerForDevices.cs) и кратко описано, что делает каждый из них.
OpenDeviceAsync открывает соединение с устройством.
RegisterForAppEvents выполняет регистрацию на события приостановки и возобновления работы приложения.
RegisterForDeviceAccessStatusChange прослушивает изменения в разрешениях на доступ к устройству.
RegisterForDeviceWatcherEvents выполняет регистрацию на события добавления и удаления.
StartDeviceWatcher запускает сторож устройства.
SelectDeviceInList проверяет, выбрал ли пользователь какое-то устройство, и, если да, сохраняет индекс для этого устройства. Кроме того, он записывает строку «Currently connected…» в окно вывода, если соединение установлено успешно.
В терминах HID API основной код находится в методе OpenDeviceAsync. Этот код вызывает метод HidDevice.FromIdAsync (bit.ly/1hyhVpI), возвращающий объект HidDevice (bit.ly/1dsD2rR), который используется приложением для доступа к устройству, получения отчетов ввода и отправки отчетов вывода:
public async Task<Boolean> OpenDeviceAsync(DeviceInformation deviceInfo,
String deviceSelector)
{
// В этом примере для открытия устройства используется
// FileAccessMode.ReadWrite, поскольку вы не захотите,
// чтобы другие приложения открывали это устройство
// и изменяли его состояние. В ином случае можно было бы
// задействовать FileAccessMode.Read.
device = await HidDevice.FromIdAsync(deviceInfo.Id,
FileAccessMode.ReadWrite);
...
Поддержка сторожа устройства Перечисление устройств происходит, когда приложение еще только запускается — даже до отображения UI. По окончании перечисления приложение отслеживает состояние устройств.
О состоянии устройств сообщает объект DeviceWatcher (bit.ly/1dBPMPd). Как и предполагает его имя, этот объект «следит» за подключенными устройствами: если пользователь удаляет или подключает свое устройство, сторож уведомляет приложение об этом событии. (Об этих событиях сообщается только по окончании процесса перечисления.)
Получение отчетов ввода
В сценарии получения показаний температуры осуществляется мониторинг отчетов ввода, выдаваемых датчиком температуры, и используется элемент управления «ползунок» для отображения текущей температуры, как показано на рис. 12. (Заметьте, что этот элемент управления ограничен выводом температурных данных. Свойства IsDoubleTapEnabled, IsHoldingEnabled, IsRightTapEnabled и IsTapEnabled выставлены в False.)
рис. 12. Отображение текущей температуры
Основной метод, поддерживающий этот сценарий, заключен в обработчик событий OnInputReportEvent, содержащийся в Scenario2_GetTemperatureData.xaml.cs. Приложение регистрирует этот обработчик событий, когда пользователь выбирает сценарий получения температурных данных и нажимает кнопку Register for Temperature Detection. Регистрация обработчика осуществляется в методе RegisterForInputReportEvents. Помимо регистрации обработчика, этот метод сохраняет маркер события, чтобы регистрацию данного обработчика можно было потом отменить:
private void RegisterForInputReportEvents()
{
if (!isRegisteredForInputReportEvents)
{
inputReportEventHandler = new TypedEventHandler<HidDevice,
HidInputReportReceivedEventArgs>(this.OnInputReportEvent);
registeredDevice = EventHandlerForDevice.Current.Device;
registeredDevice.InputReportReceived += inputReportEventHandler;
isRegisteredForInputReportEvents = true;
}
}
После регистрации обработчик событий считывает каждый отчет ввода, выдаваемый датчиком, и использует новое показание температуры для обновления элемента управления TemperatureSlider. Затем этот метод записывает значения текущей температуры и интервала отчетов в раздел Output приложения, как показано на рис. 13.
рис. 13. Чтение и отображение данных датчика
private async void OnInputReportEvent(HidDevice sender,
HidInputReportReceivedEventArgs eventArgs)
{
// Получаем данные от датчика
HidInputReport inputReport = eventArgs.Report;
IBuffer buffer = inputReport.Data;
DataReader dr = DataReader.FromBuffer(buffer);
byte[] bytes = new byte[inputReport.Data.Length];
dr.ReadBytes(bytes);
// Визуализируем данные от датчика
await Dispatcher.RunAsync(CoreDispatcherPriority.Normal, () =>
{
CurrentReadingText.TextAlignment = TextAlignment.Center;
CurrentReadingText.Text = bytes[1].ToString();
TemperatureSlider.Value = (int)bytes[1];
rootPage.NotifyUser(bytes[1].ToString() +
" degrees Fahrenheit, " +
bytes[2].ToString() +
" millisecond report-interval", NotifyType.StatusMessage);
});
}
Отправка отчетов вывода
В сценарии с заданием интервала отчетов датчику температуры посылается отчет вывода и записывается количество байтов, а также значение, выводимое в секцию Output окна приложения. Приложение посылает отчет вывода после того, как пользователь выбирает сценарий Set Report Interval, указав значение в раскрывающемся списке Value to Write, а затем нажимает кнопку Send Output Report.
рис. 14 иллюстрирует этот сценарий и раскрывающийся список, заполняемый значениями интервала отчетов. (Эти значения представляют интервал отчетов в миллисекундах, поэтому, если выбирается 100, приложение будет принимать 10 показаний в секунду.)
рис. 14. Установка интервала отчетов
Основной метод в сценарии с заданием интервала отчетов — SetReportIntervalAsync, содержащийся в модуле Scenario3_SetReportInterval.xaml.cs (рис. 15). Этот метод вызывает метод HidDevice.SendOutputReportAsync (bit.ly/1ad6unK) для отправки устройству отчета вывода.
Рис. 15. SetReportIntervalAsync
private async Task SetReportIntervalAsync(Byte valueToWrite)
{
var outputReport =
EventHandlerForDevice.Current.Device.CreateOutputReport();
var dataWriter = new DataWriter();
// Первый байт содержит идентификатор отчета
dataWriter.WriteByte((Byte)outputReport.Id);
dataWriter.WriteByte((Byte)valueToWrite);
outputReport.Data = dataWriter.DetachBuffer();
uint bytesWritten =
await EventHandlerForDevice.Current.Device.
SendOutputReportAsync(outputReport);
rootPage.NotifyUser("Bytes written: " +
bytesWritten.ToString() + ";
Value Written: " + valueToWrite.ToString(),
NotifyType.StatusMessage);
}
Заключение
Итак, сначала я кратко рассмотрел построение HID-устройства, которое отслеживает напряжение, генерируемое простым датчиком. Пример того, как можно отслеживать датчик, который переключает разъем цифрового ввода-вывода (вместо генерации диапазона напряжений), см. в MSDN (датчик движения) по ссылке bit.ly/1gWOlcC.
Затем мы обсудили написание простого приложения, способного отслеживать HID-устройство и управлять им. Подробнее о HID WinRT API см. по ссылке bit.ly/1aot1by.