问题
I'm trying to write a program that makes management of Wiimotes paired with Windows a lot simpler and automated. The program uses 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. At the moment, the process is a little bumpy and slow, but it works. (But only for one Wiimote)
The problem is that my module to pair/unpair Bluetooth devices has no information on how to identify if the HID device (used by the Wiimote class) is the same device. I want to be able to alert the Wiimote class if the Bluetooth device has been forcefully shutdown or unpaired so that it can gracefully disconnect itself. And vice-versa, I'd like the Wiimote to alert the pairer/unpairer when the HID device is disconnected so that the Bluetooth device can optionally be unpaired (assuming you plan on shutting down the Wiimote).
If I only wanted access to one Wiimote then this wouldn't be much of a problem, but I'd like to be able to access multiple Wiimotes and be able to differentiate them by using their HID info and Bluetooth Info. I'm already using plenty of my own P/Invoke to cover for areas that 32feet lacks in so using any more isn't a problem.
Here's the main code for my pairer. (Although I'm not sure if it's really necessary):
(Note: IsDiscoverable()
, ToPin()
, and ToMacAddress()
are all extension methods.)
// 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;
}
回答1:
You do not need to pair with your Wiimote. Pairing with Wiimote does only one thing: Wiimote remembers MAC of paired device and then can trun it ON and connect to it (too Wii or other device). However it doe snot work with Windows so pairing is not required. If you need pairing then use legacy PIN pairing. PIN is wiimote MAC in reversed bytes order.
Use BluetoothSetServiceState to add your wiimote as HID device into the system.
Here is code shows how to find Wiimote HID by its MAC (the code is taken from our Wireless Communication Library that includes support for 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);
}
}
Once you get DevicePath you can use CreateFile to open HID device's handle.
回答2:
I forgot to add EnumHid function. Here it is
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;
}
To detect Wiimote disconnection we use error on ReadFile when reading HID packet from Wiimote. The method works great with any Bluetooth drivers (all above about MS). However with MS you can also handle WM_DEVICE_CHANGE with HCI_DISCONNECT flag.
Constancts used there:
#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")
来源:https://stackoverflow.com/questions/51091490/how-to-identify-hid-device-with-information-from-bluetoothdeviceinfo-or-vice-ver