Как идентифицировать HID-устройство с помощью информации из BluetoothDeviceInfo или наоборот
Я пытаюсь написать программу, которая делает управление Wiimotes в паре с Windows намного проще и автоматизированнее. Программа использует WiimoteLib (которая использует hidsdi.h и setupapi.h) для подключения к устройствам Wiimote, и 32feet (использует Windows Bluetooth API) для автоматического сопряжения / отключения устройств. Код для pairer/unpairer основан на Wiipair. На данный момент процесс немного неровный и медленный, но он работает. (Но только для одного Wiimote)
Проблема заключается в том, что в моем модуле для сопряжения / отмены сопряжения устройств Bluetooth нет информации о том, как определить, является ли устройство HID (используемое классом Wiimote) тем же устройством. Я хочу иметь возможность предупреждать класс Wiimote, если устройство Bluetooth было принудительно отключено или не подключено, чтобы оно могло изящно отключиться. И наоборот, я хотел бы, чтобы Wiimote предупредил pairer/unpairer, когда устройство HID отключено, так что устройство Bluetooth может быть опционально отключено (если вы планируете отключить Wiimote).
Если бы я хотел получить доступ только к одному Wiimote, то это не было бы большой проблемой, но я бы хотел иметь возможность доступа к нескольким Wiimote и различать их, используя информацию HID и информацию Bluetooth. Я уже использую множество своих собственных P/Invoke для покрытия областей, в которых не хватает 32 футов, так что использование больше не является проблемой.
Вот основной код моего pairer. (Хотя я не уверен, действительно ли это необходимо)
(Заметка: IsDiscoverable()
, ToPin()
, а также ToMacAddress()
все методы расширения.)
// Once this is true, the Wiimote will
// attempt to connect to an HID device.
public bool IsAnyDeviceAvailable { get; private set; }
private void PairTask(CancellationToken token) {
// Setup automatic authentication
BluetoothWin32Authentication auth = new BluetoothWin32Authentication(OnHandleRequests);
while (!token.IsCancellationRequested)
PairLoop(token);
}
private void PairLoop(CancellationToken token) {
// Get a copy of known addresses since
// these are added to in another task.
BluetoothAddress[] addresses;
lock (knownAddresses)
addresses = KnownAddresses.ToArray();
bool available = false;
foreach (BluetoothAddress address in addresses) {
if (token.IsCancellationRequested)
return;
BluetoothDeviceInfo device = new BluetoothDeviceInfo(address);
if (device.Connected) {
if (!available && !IsAnyDeviceAvailable) {
lock (knownAddresses)
IsAnyDeviceAvailable = true;
}
available = true;
continue;
}
if (device.Remembered) {
RemoveDevice(device, token);
}
else if (device.IsDiscoverable() && !device.Authenticated) {
if (PairDevice(device, token, available))
available = true;
}
token.WaitHandle.WaitOne(500);
}
if (!available && IsAnyDeviceAvailable) {
Trace.WriteLine("No more devices connected");
lock (knownAddresses)
IsAnyDeviceAvailable = false;
}
}
private void RemoveDevice(BluetoothDeviceInfo device, CancellationToken token) {
token.WaitHandle.WaitOne(1000);
if (BluetoothSecurity.RemoveDevice(device.DeviceAddress)) {
Trace.WriteLine($"Wiimote removed: {device.DeviceAddress.ToMacAddress()}");
token.WaitHandle.WaitOne(2000);
}
}
private bool PairDevice(BluetoothDeviceInfo device, CancellationToken token,
bool available)
{
string pin = device.DeviceAddress.ToPin();
try {
if (BluetoothSecurity.PairRequest(device.DeviceAddress, pin)) {
Trace.WriteLine($"Wiimote authenticated: {device.DeviceAddress.ToMacAddress()}");
token.WaitHandle.WaitOne(1000);
// Calling this before and after seems to help unsure
// the device works when paired programmatically.
Guid[] services = device.InstalledServices;
device.SetServiceState(Uuids.HumanInterfaceDeviceServiceClass_UUID, true, true);
services = device.InstalledServices;
Trace.WriteLine($"Wiimote paired: {device.DeviceAddress.ToMacAddress()}");
token.WaitHandle.WaitOne(8000);
if (!available && !IsAnyDeviceAvailable) {
Trace.WriteLine("First device has been connected");
lock (knownAddresses)
IsAnyDeviceAvailable = true;
}
return true;
}
else {
Trace.WriteLine($"Wiimote authentication failed: {device.DeviceAddress.ToMacAddress()}");
}
}
catch {
Trace.WriteLine($"Wiimote pairing failed: {device.DeviceAddress.ToMacAddress()}");
}
return false;
}
private void OnHandleRequests(object sender, BluetoothWin32AuthenticationEventArgs e) {
e.Confirm = true;
}
2 ответа
Вам не нужно соединяться с вашим Wiimote. Сопряжение с Wiimote делает только одно: Wiimote запоминает MAC-адрес сопряженного устройства, а затем может включить его и подключиться к нему (также Wii или другое устройство). Однако это не работает с Windows, поэтому сопряжение не требуется. Если вам нужно сопряжение, используйте устаревшее сопряжение PIN-кода. PIN-код - это wiimote MAC в обратном порядке байтов.
Используйте BluetoothSetServiceState, чтобы добавить свой wiimote в качестве устройства HID в систему.
Вот код, показывающий, как найти Wiimote HID по его MAC-адресу (код взят из нашей библиотеки беспроводной связи, которая включает поддержку Wiimote).
m_fInstalled = true;
Sleep(1000);
CwclStringList* Wiis = new CwclStringList();
m_sDevicePath = WCL_EMPTY_STR;
DWORD dwTik = GetTickCount();
while (m_sDevicePath == WCL_EMPTY_STR && GetTickCount() - dwTik < 20000)
if (EnumHID(*Wiis) == WCL_E_SUCCESS)
{
CwclString sAddress = GetBluetoothParams()->GetAddress();
CwclString sAdr = sAddress.Mid(1, 2) + sAddress.Mid(4, 2) + sAddress.Mid(7, 2) + sAddress.Mid(10, 2) + sAddress.Mid(13, 2) + sAddress.Mid(16, 2);
HKEY hKey;
CwclString aRegKey(WCL_WII_REG_KEY);
DWORD dwRes = RegOpenKey(HKEY_LOCAL_MACHINE, aRegKey, &hKey);
if (dwRes != ERROR_SUCCESS)
aRegKey = CwclString(WCL_WII_REG_KEY_NEW);
dwRes = RegOpenKey(HKEY_LOCAL_MACHINE, aRegKey, &hKey);
if (dwRes == ERROR_SUCCESS)
{
DWORD dwNdx = 0;
WCHAR szSubKeyName[512];
DWORD dwSubKeySize = sizeof(szSubKeyName);
ZeroMemory(szSubKeyName, dwSubKeySize);
while (RegEnumKeyEx(hKey, dwNdx, szSubKeyName, &dwSubKeySize, NULL, NULL, NULL, NULL) == ERROR_SUCCESS)
{
CwclString sSubKeyStr = CwclString(szSubKeyName);
if (sSubKeyStr.Find(sAdr) >= 0)
{
CwclString sSubKeyPath = aRegKey + L"\\" + sSubKeyStr;
HKEY hSubKey;
dwRes = RegOpenKey(HKEY_LOCAL_MACHINE, sSubKeyPath, &hSubKey);
if (dwRes == ERROR_SUCCESS)
{
WCHAR szValue[512];
DWORD dwValueSize = sizeof(szValue);
ZeroMemory(szValue, dwValueSize);
dwRes = RegQueryValueEx(hSubKey, L"ParentIdPrefix", NULL, NULL, (LPBYTE)szValue, &dwValueSize);
if (dwRes == ERROR_SUCCESS)
{
CwclString sValueStr = CwclString(szValue);
for (INT_PTR i = 0; i < Wiis->GetCount(); i++)
if (Wiis->GetItems(i).Find(sValueStr) >= 0)
{
m_sDevicePath = Wiis->GetItems(i);
break;
}
}
RegCloseKey(hSubKey);
}
}
if (m_sDevicePath != WCL_EMPTY_STR)
break;
dwSubKeySize = sizeof(szSubKeyName);
ZeroMemory(szSubKeyName, dwSubKeySize);
dwNdx++;
}
RegCloseKey(hKey);
}
}
Получив DevicePath, вы можете использовать CreateFile, чтобы открыть дескриптор устройства HID.
Я забыл добавить функцию EnumHid. Вот
int CwclWiimote::EnumHID(CwclStringList& rWiis)
{
if (!wclIsTransportAvailable(trBluetooth))
return WCL_E_TRANSPORT_NOT_AVAILABLE;
GUID Guid;
HidD_GetHidGuid(&Guid);
HDEVINFO hDevInfo = SetupDiGetClassDevs(&Guid, NULL, 0, DIGCF_DEVICEINTERFACE);
if (hDevInfo == INVALID_HANDLE_VALUE)
return WCL_E_INTERNAL;
SP_DEVICE_INTERFACE_DATA diData;
ZeroMemory(&diData, sizeof(SP_DEVICE_INTERFACE_DATA));
diData.cbSize = sizeof(SP_DEVICE_INTERFACE_DATA);
rWiis.Clear();
DWORD dwIndex = 0;
PSP_DEVICE_INTERFACE_DETAIL_DATA_W diDetail;
while (SetupDiEnumDeviceInterfaces(hDevInfo, NULL, &Guid, dwIndex, &diData))
{
DWORD dwSize = 0;
SetupDiGetDeviceInterfaceDetail(hDevInfo, &diData, NULL, 0, &dwSize, NULL);
diDetail = (PSP_DEVICE_INTERFACE_DETAIL_DATA_W)LocalAlloc(LPTR, dwSize);
if (diDetail)
{
ZeroMemory(diDetail, dwSize);
diDetail->cbSize = sizeof(SP_DEVICE_INTERFACE_DETAIL_DATA_W);
if (SetupDiGetDeviceInterfaceDetail(hDevInfo, &diData, diDetail, dwSize, &dwSize, NULL))
{
HANDLE hHandle = CreateFile(diDetail->DevicePath, GENERIC_READ | GENERIC_WRITE, FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_EXISTING, FILE_FLAG_OVERLAPPED, NULL);
if (hHandle != INVALID_HANDLE_VALUE)
{
HIDD_ATTRIBUTES Attrib;
ZeroMemory(&Attrib, sizeof(HIDD_ATTRIBUTES));
Attrib.Size = sizeof(HIDD_ATTRIBUTES);
if (HidD_GetAttributes(hHandle, &Attrib))
if (Attrib.VendorID == WCL_WII_VID && (Attrib.ProductID == WCL_WII_PID || Attrib.ProductID == WCL_WII_PID_NEW))
{
CwclString Str = diDetail->DevicePath;
rWiis.Add(Str);
}
CloseHandle(hHandle);
}
}
LocalFree((HLOCAL)diDetail);
}
dwIndex++;
}
SetupDiDestroyDeviceInfoList(hDevInfo);
return WCL_E_SUCCESS;
}
Чтобы обнаружить отключение Wiimote, мы используем ошибку в ReadFile при чтении HID-пакета из Wiimote. Этот метод прекрасно работает с любыми драйверами Bluetooth (все выше о MS). Однако с MS вы также можете обрабатывать WM_DEVICE_CHANGE с флагом HCI_DISCONNECT.
Константы, используемые там:
#define WCL_WII_VID 0x057E
#define WCL_WII_PID 0x0306
#define WCL_WII_PID_NEW 0x0330
#define WCL_WII_REG_KEY (L"SYSTEM\\CurrentControlSet\\Enum\\BTHENUM\\{00001124-0000-1000-8000-00805f9b34fb}_VID&0002057e_PID&0306")
#define WCL_WII_REG_KEY_NEW (L"SYSTEM\\CurrentControlSet\\Enum\\BTHENUM\\{00001124-0000-1000-8000-00805f9b34fb}_VID&0002057e_PID&0330")