【问题标题】:How to identify HID Device with information from BluetoothDeviceInfo or vice-versa如何使用来自 BluetoothDeviceInfo 的信息识别 HID 设备,反之亦然
【发布时间】:2018-06-28 21:29:46
【问题描述】:

我正在尝试编写一个程序,使与 Windows 配对的 Wiimotes 管理变得更加简单和自动化。程序使用WiimoteLib(使用hidsdi.hsetupapi.h连接Wiimote设备,32feet(使用Windows Bluetooth API自动配对/取消配对设备。配对器/取消配对器的代码基于Wiipair。目前,这个过程有点颠簸和缓慢,但它确实有效。 (但仅限于一个 Wiimote)

问题是我的蓝牙设备配对/取消配对模块没有关于如何识别 HID 设备(由 Wiimote 类使用)是否是同一设备的信息。如果蓝牙设备已被强制关闭或未配对,我希望能够提醒 Wiimote 类,以便它可以优雅地断开连接。反之亦然,我希望 Wiimote 在 HID 设备断开连接时提醒配对器/取消配对器,以便可以选择取消配对蓝牙设备(假设您计划关闭 Wiimote)。

如果我只想访问一个 Wiimote,那么这不是什么大问题,但我希望能够访问多个 Wiimote,并能够通过使用它们的 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;
}

【问题讨论】:

    标签: c# bluetooth hid


    【解决方案1】:
    1. 您无需与 Wiimote 配对。与 Wiimote 配对只做一件事:Wiimote 记住配对设备的 MAC,然后可以打开它并连接到它(Wii 或其他设备)。但是它不适用于 Windows,因此不需要配对。如果您需要配对,请使用旧版 PIN 配对。 PIN 是字节顺序相反的 wiimote MAC。

    2. 使用 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 设备的句柄。

    【讨论】:

    • 您能列出WCL_WII_REG_KEYWCL_WII_REG_KEY_NEW 的值吗?
    • 看我的第二个答案。
    • 如果我没有看到它,请原谅我,但我认为您在第二个答案中列出了 WCL_WII_VIDWCL_WII_PIDWCL_WII_PID_NEW,而不是注册表项。
    • 对不起,我的错。也将它们添加到第二个答案中。
    【解决方案2】:

    我忘了添加 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")
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多