如何使用 BluetoothDeviceInfo 中的信息识别 HID 设备,反之亦然
How to identify HID Device with information from BluetoothDeviceInfo or vice-versa
我正在尝试编写一个程序,使与 Windows 配对的 Wiimotes 的管理更加简单和自动化。该程序使用 WiimoteLib (which uses hidsdi.h and setupapi.h) to connect to Wiimote devices, and 32feet (uses Windows Bluetooth API) to automatically pair/unpair devices. The code for the pairer/unpairer is based off of Wiipair。目前,这个过程有点颠簸和缓慢,但它有效。 (但仅限于一台 Wiimote)
问题是我的 pair/unpair 蓝牙设备模块没有关于如何识别 HID 设备(由 Wiimote class 使用)是否为同一设备的信息。如果蓝牙设备被强制关闭或取消配对,我希望能够提醒 Wiimote class,以便它可以正常断开连接。反之亦然,我希望 Wiimote 在 HID 设备断开连接时提醒 pairer/unpairer,以便蓝牙设备可以选择取消配对(假设您计划关闭 Wiimote)。
如果我只想访问一个 Wiimote 那么这不是什么大问题,但我希望能够访问多个 Wiimotes 并能够通过使用它们的 HID 信息和蓝牙信息来区分它们.我已经用了很多我自己的 P/Invoke 来覆盖 32feet 缺少的区域,所以再使用也不成问题。
这是我的配对器的主要代码。 (虽然我不确定是否真的有必要):
(注: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;
}
您不需要与 Wiimote 配对。与 Wiimote 配对只会做一件事:Wiimote 会记住配对设备的 MAC,然后可以将其打开并连接到它(Wii 或其他设备也是如此)。然而,它不能与 Windows 一起使用,因此不需要配对。如果您需要 配对,请使用旧版 PIN 配对。 PIN 是 wiimote MAC,字节顺序相反。
使用BluetoothSetServiceState将您的wiimote作为HID设备添加到系统中。
这里的代码显示了如何通过 MAC 找到 Wiimote HID(代码取自我们的 Wireless Communication Library,其中包括对 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 断开连接,我们在从 Wiimote 读取 HID 数据包时在 ReadFile 上使用错误。该方法适用于任何蓝牙驱动程序(以上都是关于 MS 的)。但是,对于 MS,您还可以使用 HCI_DISCONNECT 标志处理 WM_DEVICE_CHANGE。
那里使用的常量:
#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")
我正在尝试编写一个程序,使与 Windows 配对的 Wiimotes 的管理更加简单和自动化。该程序使用 WiimoteLib (which uses hidsdi.h and setupapi.h) to connect to Wiimote devices, and 32feet (uses Windows Bluetooth API) to automatically pair/unpair devices. The code for the pairer/unpairer is based off of Wiipair。目前,这个过程有点颠簸和缓慢,但它有效。 (但仅限于一台 Wiimote)
问题是我的 pair/unpair 蓝牙设备模块没有关于如何识别 HID 设备(由 Wiimote class 使用)是否为同一设备的信息。如果蓝牙设备被强制关闭或取消配对,我希望能够提醒 Wiimote class,以便它可以正常断开连接。反之亦然,我希望 Wiimote 在 HID 设备断开连接时提醒 pairer/unpairer,以便蓝牙设备可以选择取消配对(假设您计划关闭 Wiimote)。
如果我只想访问一个 Wiimote 那么这不是什么大问题,但我希望能够访问多个 Wiimotes 并能够通过使用它们的 HID 信息和蓝牙信息来区分它们.我已经用了很多我自己的 P/Invoke 来覆盖 32feet 缺少的区域,所以再使用也不成问题。
这是我的配对器的主要代码。 (虽然我不确定是否真的有必要):
(注: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;
}
您不需要与 Wiimote 配对。与 Wiimote 配对只会做一件事:Wiimote 会记住配对设备的 MAC,然后可以将其打开并连接到它(Wii 或其他设备也是如此)。然而,它不能与 Windows 一起使用,因此不需要配对。如果您需要 配对,请使用旧版 PIN 配对。 PIN 是 wiimote MAC,字节顺序相反。
使用BluetoothSetServiceState将您的wiimote作为HID设备添加到系统中。
这里的代码显示了如何通过 MAC 找到 Wiimote HID(代码取自我们的 Wireless Communication Library,其中包括对 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 断开连接,我们在从 Wiimote 读取 HID 数据包时在 ReadFile 上使用错误。该方法适用于任何蓝牙驱动程序(以上都是关于 MS 的)。但是,对于 MS,您还可以使用 HCI_DISCONNECT 标志处理 WM_DEVICE_CHANGE。
那里使用的常量:
#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")