Beware that this post is related to an SDK in maintenance mode
More Info: Consider nRF Connect SDK for new designs
This post is older than 2 years and might not be relevant anymore
More Info: Consider searching for newer posts

Custom HID and Sensor example for nRF5 SDK 15.x

Hi,

I had some troubles getting a custom HID device to work that has both input and output reports to Windows 10 host. Here is my working example template that might save some time for others attempting something similar.

https://github.com/tikonen/ble_app_hids_custom

This implements

  • Custom BLE HID device that has both an output and input report. Tested to work with Windows 10. I used Win32 API SetupDiEnumDeviceInterfaces+SetupDiGetDeviceInterfaceDetail to find the device path. Device path can be opened CreateFile. Reports can be read/received simply via WriteFile and ReadFile
  • Delayed I2C read from si7021 sensor (this sensor requires wait of 25ms before data is ready to read)
  • GPIO interrupt triggered reading from sensor

Thanks!

  • Thank you Tikonen!

    I am currently developing a BLE HID composite device, consisting of a standard keyboard, a standard mouse and a custom HID device.

    On the device side, I have managed to implement everything and it seems to work: keyboard and mouse work as expected, and I can see the custom HID data with an nRF sniffer.

    My problem is that I can't find out how to access (on the PC side) the data exchanged with the custom HID device.

    May I kindly ask you if you can share some more details about your code on Windows? Would you perhaps be willing to share the piece of source code, like you did with your Nordic chip firmware?

    What SDK are you using to develop the Windows application? (I use Xamarin).

    Thanks in advance!

  • This answer probably comes too late but for anybody who comes here by google:

    Windows 10 does not allow anymore direct access to certain devices (Mouse and Keyboard namely) for security reasons. However, it's still possible to send and receive raw HID packets for many device classes, at least vendor specific. There are quite good examples in MSDN but essentially you look up device path (based on vendor and device id) by using SetupDiEnumDeviceInterfaces and then call CreateFile to open the device. Then simpy call WriteFile and ReadFile. Here are some example snippets.

    #include <SetupAPI.h>
    #include <hidsdi.h>
    #include <usbiodef.h>
    #include <fileapi.h>
    #include <Cfgmgr32.h>
    
    #include <initguid.h>
    #include <hidclass.h>
    
    
    void listHidDevices()
    {
        CONFIGRET cr = CR_SUCCESS;
        PWSTR DeviceInterfaceList = NULL;
        ULONG DeviceInterfaceListLength = 0;
    
        do {
            cr = CM_Get_Device_Interface_List_Size(&DeviceInterfaceListLength, (LPGUID)&GUID_DEVINTERFACE_HID, NULL, CM_GET_DEVICE_INTERFACE_LIST_ALL_DEVICES);
    
            if (cr != CR_SUCCESS) {
                break;
            }
    
            if (DeviceInterfaceList != NULL) {
                HeapFree(GetProcessHeap(), 0, DeviceInterfaceList);
            }
    
            DeviceInterfaceList = (PWSTR)HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, DeviceInterfaceListLength * sizeof(WCHAR));
    
            if (DeviceInterfaceList == NULL) {
                cr = CR_OUT_OF_MEMORY;
                break;
            }
    
            cr = CM_Get_Device_Interface_List(
                (LPGUID)&GUID_DEVINTERFACE_HID, NULL, DeviceInterfaceList, DeviceInterfaceListLength, CM_GET_DEVICE_INTERFACE_LIST_ALL_DEVICES);
        } while (cr == CR_BUFFER_SMALL);
    
        if (DeviceInterfaceList != NULL) {
            unsigned int idx = 0;
    
            while (idx < DeviceInterfaceListLength) {
                unsigned int i = idx;
                while (DeviceInterfaceList[i]) i++;
                printf("%.*S\n", i - idx, DeviceInterfaceList + idx);
                idx = i + 1;
            }
            HeapFree(GetProcessHeap(), 0, DeviceInterfaceList);
        }
    }
    
    HANDLE openHID(uint16_t vid, uint16_t pid, bool verbose)
    {
        int sensorIf = 0;
        int collectionNumber = 0;
        std::wstring id;
        HANDLE deviceHandle = INVALID_HANDLE_VALUE;
    
        // listHidDevices();
    
        if (!findHID(vid, pid, sensorIf, collectionNumber, id, verbose)) {
            std::cout << "Device not found" << std::endl;
            return deviceHandle;
        }
        if (verbose) {
            std::wcout << L"dev inst " << id << std::endl;
        }
    
        // Not possible to read mouse reports directly
        // https://social.msdn.microsoft.com/Forums/en-US/f14af725-8140-4774-bf95-3f15e885ead2/readingwriting-reports-from-an-hid-keyboardmouse
        // "the security for keyboards and mice is handled above the keyboard and mouse drivers. you could write a class filter driver that circumvented the
        // security measures that are in place, it would be rather complicated though."
    
    
        deviceHandle =
            CreateFileW(id.c_str(), GENERIC_READ | GENERIC_WRITE, FILE_SHARE_READ | FILE_SHARE_WRITE, nullptr, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, nullptr);
        // deviceHandle = CreateFileW(id.c_str(), 0, FILE_SHARE_READ, nullptr, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, nullptr);
        if (deviceHandle == INVALID_HANDLE_VALUE) {
            std::cout << "Device open failed: " << GetLastErrorString() << std::endl;
            return deviceHandle;
        }
    
        wchar_t wcharBuffer[128];
        if (HidD_GetSerialNumberString(deviceHandle, wcharBuffer, sizeof(wcharBuffer))) {
            std::wcout << L"USB Serial: " << wcharBuffer << std::endl;
        }
    
        PHIDP_PREPARSED_DATA hidData;
    
        if (HidD_GetPreparsedData(deviceHandle, &hidData)) {
            HidD_FlushQueue(deviceHandle);
    
            HIDP_CAPS hidCapabilities;
            if (HidP_GetCaps(hidData, &hidCapabilities)) {
                if (verbose) {
                    printf("HID Capabilities\n");
                    printHidCaps(hidCapabilities);
                }
            } else {
                std::cerr << "No Hid caps" << std::endl;
                return deviceHandle;
            }
            if (verbose) {
                printf("HID Collections\n");
                printHidCollections(hidData, hidCapabilities.NumberLinkCollectionNodes);
            }
    
            HIDP_VALUE_CAPS valueCaps[4];
            USHORT valueCapsLength = ARRAYSIZE(valueCaps);
            if (HidP_GetValueCaps(HidP_Input, valueCaps, &valueCapsLength, hidData) == HIDP_STATUS_SUCCESS) {
                if (verbose) {
                    printf("HID Value Capabilities\n");
                    for (int i = 0; i < valueCapsLength; i++) {
                        printValueCaps(valueCaps[i]);
                    }
                }
            } else {
                std::cerr << "No value caps" << std::endl;
            }
        } else {
            std::cout << "Could not get preparsed data" << std::endl;
        }
    
        return deviceHandle;
    }
    
    bool findHID(uint16_t vid, uint16_t pid, uint16_t interfaceIndex, uint16_t collectionNumber, std::wstring& id, bool verbose)
    {
        if (verbose) {
            printf("Searching vid=%04X pid=%04X\n", vid, pid);
        }
    
        // Find HID interfaces
        GUID hidGuid;
        HidD_GetHidGuid(&hidGuid);
        HDEVINFO const hDevInfoSet = SetupDiGetClassDevs(&hidGuid, nullptr, nullptr, DIGCF_DEVICEINTERFACE | DIGCF_PRESENT);
    
        if (hDevInfoSet != INVALID_HANDLE_VALUE) {
            DWORD devIndex = 0;
    
            SP_DEVINFO_DATA devInfo;
            devInfo.cbSize = sizeof(SP_DEVINFO_DATA);
            SP_DEVICE_INTERFACE_DATA devInterfaceData;
            devInterfaceData.cbSize = sizeof(SP_DEVICE_INTERFACE_DATA);
    
            while (SetupDiEnumDeviceInterfaces(hDevInfoSet, nullptr, &hidGuid, devIndex++, &devInterfaceData)) {
                DWORD reqSize{0};
    
                devInterfaceData.cbSize = sizeof(SP_DEVICE_INTERFACE_DATA);
                SetupDiGetDeviceInterfaceDetailW(hDevInfoSet, &devInterfaceData, nullptr, 0, &reqSize, nullptr);
                std::unique_ptr<SP_DEVICE_INTERFACE_DETAIL_DATA, decltype(free)*> devInterfaceDetailData{
                    static_cast<SP_DEVICE_INTERFACE_DETAIL_DATA*>(malloc(reqSize)), free};
    
    
                devInterfaceDetailData->cbSize = sizeof(SP_DEVICE_INTERFACE_DETAIL_DATA_W);
                if (!SetupDiGetDeviceInterfaceDetailW(hDevInfoSet, &devInterfaceData, devInterfaceDetailData.get(), reqSize, nullptr, nullptr)) {
                    LOG("Could not read interface details: %s", GetLastErrorString());
                    return nullptr;
                }
    
                if (verbose) {
                    printf("%S\n", devInterfaceDetailData->DevicePath);
                }
    
    
    			// \\?\hid#{00001812-0000-1000-8000-00805f9b34fb}_dev_vid&021915_pid&eeee_rev&0001_cc75b95cacec&col01#9&1101c0b7&0&0000#{4d1e55b2-f16f-11cf-88cb-001111000030}
    			// check if string matches desired vid and pid
                if (parseHIDIdentifiers(devInterfaceDetailData->DevicePath, dVid, dPid)) {
                    if (vid == dVid && pid == dPid) {
                        size_t const pathLength = wcsnlen(devInterfaceDetailData->DevicePath, MAX_PATH);
                        id.resize(pathLength);
                        memcpy(&id[0], devInterfaceDetailData->DevicePath, pathLength * sizeof(wchar_t));
                        SetupDiDestroyDeviceInfoList(hDevInfoSet);
                        return true;
                    }
                }
            }
        }
        SetupDiDestroyDeviceInfoList(hDevInfoSet);
        return false;
    }
    
    
    
    // Example
    bool writeHidData(HANDLE deviceHandle)
    {
        // Send command
        struct OutputReport {
            uint8_t reportId;
            uint8_t data[2];
        };
        OutputReport dummyMsg = {0x02, {0xA1, 0xB2}};
        DWORD wrote = 0;
        return WriteFile(deviceHandle, &dummyMsg, sizeof(OutputReport), &wrote, nullptr);
    }
    
    // Read and append to data buffer
    bool readHidData(HANDLE deviceHandle, std::vector<char>& data)
    {
        BYTE readBuffer[512];
        const int bufferSize = sizeof(readBuffer);
        int len = 0;
    
        // Blocking read
        DWORD rCount = 0;
        if (ReadFile(deviceHandle, readBuffer, bufferSize, &rCount, nullptr)) {
            size_t idx = data.size();
            data.resize(data.size() + rCount);
            memcpy(&data[idx], readBuffer, rCount);
            return true;
        }
    
        return false;
    }
    
    
    
    
    
    
    

Related