Search code examples
c#windowswinapihid

HID macropad key programming doesn't give expected results


TLDR: I need help determining the correct format for an HID Output Report. I don't know if it's a standard report type of some sort or if this is a device-specific report. I don't know if there's some way to query the device for the correct format or somewhere I might be able to read documentation. Any information or help is appreciated! If you skip down to "The Actual Question", you can skip all of my ramblings.

-- The long version --

Background I purchased this "6 Key Mini Keypad with Knob" (cheap generic macro pad) on Amazon. The software is... not great. I'm attempting to update the software to be more user friendly and to add some features that I feel are sorely needed. I plan to release my project on Github to so anyone can use it or make improvements. Luckily, I was able to find the open source project their software was based on but I have run into questions that I haven't been able to find the answers to. I'm hopeful someone with HID experience can shed some light on them for me. When I started on this a couple weeks ago, I had zero HID knowledge. I'm kinda working backwards here. I have code that I don't fully understand the context for. I can see what it's attempting to do, but without strong HID knowledge, I can't say if it's correct or not.

What I know so far The device uses a CH552 microcontroller and appears to be operating as a "Composite HID device". It exposes 3 HID devices, only one of which is accessible to the user for programming, a vendor defined device labeled "mi_01" with a VID of 1189 and a PID of 8890.

The code uses HIDLibrary by Mike O'Brien to communicate with the device. Although it builds a byte array with a fixed length of 64 elements, it only seems to use the first 7 bytes when programming a key. As you click around the application, it builds a "data" array. Then, when you tell it to write the data, it uses that array to build another "sendBuffer" byte array that is then used to create the final HIDReport byte array that gets sent to the device. Byte arrays, byte arrays everywhere! For clarity, I've created a simplified version, at least as simple as I could make it, to demonstrate.

The sample I've included connects to the device and then creates a data array that is identical to the array generated by the actual application if you choose "SHIFT + A, B, SHIFT + C, D", which should output "AbCd" when you kit the first key. The demo then uses that data to create a "sendBuffer" array and writes the (maybe?) appropriate data to the device. See the comments in the code for details.

The Actual Question The code (and original application) doesn't do what you'd expect it to. What should output "AbCd" outputs "Abcd" instead. In fact, the only modifier key ever honored is the first key. I'm trying to determine if the problem lies in the application that's programming the device (something I can fix) or if the device's firmware is not honoring the modifier keys (something I can't fix, at least without solder involved). So my questions are:

  1. Is this a standardized report that I can get documentation on?
  2. Is there some way to query the device to find out what it's expecting in these reports?
  3. If this is a standard report, is it correct that the first time through the loop, the first key's modifier is sent (if there is one) along with a hard coded 0 for the key scan code? It seems odd, especially since the next iteration of the loop sends the same modifier value again, just with the key scan code included this time.

I believe that the HID protocol allows for some interrogation of the device and it's report definitions, but I don't know how to do it, how detailed it gets, if it's what I think it is, or if the device even supports it. I'm hoping someone familiar with developing HID devices can shed some light on it for me.

This is a stripped down version of the code as it is now:

using System;
using System.Linq;
using HidLibrary;
using MacroPadPlusPlus;

namespace HID_Stackoverflow_Example
{
    class Program
    {

        public static class KeyParam
        {
            //They byte indexes that make up the header of the SendBuffer array
            public const byte KeySet_KeyNum = 0;
            public const byte KeyType_Num = 1;
            public const byte KeyGroupCharNum = 2;
            public const byte KeySet_KeyValNum = 3;
            public const byte Key_Fun_Num = 4;
            public const byte KEY_Char_Num = 5;

            public const byte ReportID = 3; //Normally detected by the code
            public const byte KEY_Cur_Layer = 1; //Normally selected in code, but my device doesn't seem to support layers anyway.
        }

        static void Main(string[] args)
        {
            Console.WriteLine("Press any key to begin");
            Console.ReadKey(true);

            Console.Write("Connecting to device...");
            var device = HidDevices.Enumerate(0x1189, 0x8890).Where(x => x.DevicePath.Contains("mi_01")).FirstOrDefault();

            if (device.IsConnected)
                Console.WriteLine("Device connected successfully");
            else
            {
                Console.WriteLine("Could not communicate with device.  Check device is connected and try again");
                Main(args);
                return;
            }

            Console.WriteLine("Writing data");

            var data = MockData();
            var result = WriteToDevice(device, data);

            if (result)
                Console.WriteLine("Write Succeeded");
            else
                Console.WriteLine("Write Failed");

            Console.WriteLine("Press any key to exit");
            Console.ReadKey(true);
        }





        //Noramally, the application builds up this array as the user interacts with the on-screen controls.  To simplify for the question
        //and make things a little easier to follow, I am mocking up the results as if the user selected "SHIFT + A, B, SHIFT + C, D".
        //Which *should* result in "AbCd" typed out when they user hits the first key.  Instead, it results in "Abcd", which is undesireable.
        public static byte[] MockData()
        {
            //The [bracketed] comments are just showing the value of the constant.
            //I included the named indexes, with their original names, just in case they are standard terms (though I think not)

            var data = new byte[65];
            data[KeyParam.KeySet_KeyNum] = 1; //[KeySet_KeyNum = 0] The index of the key to program 

            //Indicates the type of macro function
            //1 = Standard HID Keyboard Key
            //2 = Multimedia Key,
            //3 = Mouse (with optional keyboard modifier keys)
            //8 = LED (dunno, mine doesn't have LEDs, but the application has 3 selectable modes)
            data[KeyParam.KeyType_Num] = 1; //[KeyType_Num = 1]

            data[KeyParam.KeyGroupCharNum] = 4; //[KeyGroupCharNum = 2] The count of "standard" keys (non-modifier keys)
            data[KeyParam.KeySet_KeyValNum] = 0; //[KeySet_KeyValNum = 3] UNUSED. Always 0.  This is set to 0 when the Data_Send_Buff is initialized and never referenced again or read anywhere.

            //everything up to this point, I consider the "header", with everything below this line, the "body"
            /*==========================================*/
            // *I believe* Everything below this line is a two-byte pair of Modifier and Standard Key scan codes
            // Although only the first modifier key seems to be honored.  That's what I'm trying to figure out.
            // Is the code that sends the data to the device wrong, or is the device not using the stored data correctly?
            // For example, if you choose to have "SHIFT + A, SHIFT + B, SHIFT + C, D" set to one of the keys, the output for
            // that key results in "Abcd" instead of the expected "ABCd".

            //This is where things start to fall apart.  There's some ambiguity that makes me doubt my understanding.
            //There is a named index for this field "[Key_Fun_Num]" but like [KeySet_KeyValNum], it is initialized to 0 using
            //this named index, but then never reffered to by name again. The field is instead referred
            //to by [KEY_Char_Num -1] with [KEY_Char_Num] being the current index of standard keys so far (this is
            //incremented by 2 for each key, so it will be double [KeyGroupCharNum])
            //That means data[4] will only have data if the first key uses a modifier key.  So I believe this is the
            //first byte of the actual key data.

            //In the application, if I select the options to send "SHIFT + A, B, SHIFT + C, D", expecting output "AbCd",
            //when I save the results and press the key, I get "Abcd" instead.  I've logged the keycodes as it "types" and
            //only the first "SHIFT" is ever sent.

            data[4] = Scancodes.ModifierKeys.ShiftLeft; //0x02
            data[5] = Scancodes.StandardKeys.A; //0x04
            data[6] = 0; //no modifier
            data[7] = Scancodes.StandardKeys.B; //0x05
            data[8] = Scancodes.ModifierKeys.ShiftLeft; //0x02
            data[9] = Scancodes.StandardKeys.C; //0x06
            data[10] = 0; //no modifier
            data[11] = Scancodes.StandardKeys.D; //0x07

            return data;
        }


        public static bool WriteToDevice(HidDevice device, byte[] data)
        {
            byte[] sendBuffer = new byte[65];

            sendBuffer[0] = data[KeyParam.KeySet_KeyNum];

            sendBuffer[1] = KeyParam.KEY_Cur_Layer; //[KEY_Cur_Layer = 1] The application offers 3 "layers" but the device has no way to switch between them and changing values doesn't seem to matter.
            sendBuffer[1] <<= 4; //move layer to the "high" side
            sendBuffer[1] |= data[KeyParam.KeyType_Num]; //[KeyType_Num = 1] Set the macro type to the "low" side

            sendBuffer[2] = data[KeyParam.KeyGroupCharNum]; //[KeyGroupCharNum = 2] set how many keys we're going to send

            //I *think* this loop is a little wrong.
            //What it currently does:
            //Loops from 0 to the number of keys selected (4 for this example, so it will loop a total of 5 times.)
            //  For the first loop, it sends just the modifier of the first key byte pair (if any) and a hard coded 0 for the key scan code
            //  for the second loop, it sends the first key byte pair modifier it *just* sent on the first loop but this time includes the key scan code, as well
            //  for the third loop, it sends the second key byte pair (modifier & key scan code)
            //  for the fourth loop, it sends the third key byte pair (modifier & key scan code)
            //  for the fith and final loop, it sends the fourth key byte pair (modifier & key scan code)

            //I wonder if the first send should be something different.  It seems odd that it would just send the first modifier (if it exists) with no keycode
            for (byte index = 0; index <= data[KeyParam.KeyGroupCharNum]; ++index) //[KeyGroupCharNum = 2]
            {
                sendBuffer[3] = index; //the current character we'ere sending, I think.  Not sure about index 0!
                switch (index)
                {
                    case 0:
                        sendBuffer[4] = data[4];
                        sendBuffer[5] = 0;
                        break;
                    case 1:
                        sendBuffer[4] = data[4];
                        sendBuffer[5] = data[5];
                        break;
                    case 2:
                        sendBuffer[4] = data[6];
                        sendBuffer[5] = data[7];
                        break;
                    case 3:
                        sendBuffer[4] = data[8];
                        sendBuffer[5] = data[9];
                        break;
                    case 4:
                        sendBuffer[4] = data[10];
                        sendBuffer[5] = data[11];
                        break;
                    case 5:
                        sendBuffer[4] = data[12];
                        sendBuffer[5] = data[13];
                        break;
                    case 6:
                        sendBuffer[4] = data[14];
                        sendBuffer[5] = data[15];
                        break;
                }

                WriteDevice(device, KeyParam.ReportID, sendBuffer);
            }
            return Send_WriteFlash_Cmd(device);
        }

        //I'm not sure why this sends 8 bytes.  The code never uses more than 6.  Perhapse the device just expects 8 bytes?
        public static bool WriteDevice(HidDevice device, byte reportId, byte[] arrayBuff)
        {
            HidReport report = device.CreateReport();
            report.ReportId = reportId;
            report.Data[0] = arrayBuff[0];
            report.Data[1] = arrayBuff[1];
            report.Data[2] = arrayBuff[2];
            report.Data[3] = arrayBuff[3];
            report.Data[4] = arrayBuff[4];
            report.Data[5] = arrayBuff[5];
            report.Data[6] = arrayBuff[6];
            report.Data[7] = arrayBuff[7];
            return device.WriteReport(report, 500); //attempt to write, time out after 500ms (.5 sec)
        }

        //I assume this lets the device know we're done sending data.
        private static bool Send_WriteFlash_Cmd(HidDevice device)
        {
            byte[] arrayBuff = new byte[65];
            arrayBuff[0] = 170;
            arrayBuff[1] = 170;

            return WriteDevice(device, KeyParam.ReportID, arrayBuff);
        }
    }
}


Solution

  • I'm not familiar with C# HidLibrary.

    If we look from Win32 perspective: You can call HidP_GetCaps to get the HIDP_CAPS structure. Among other info it contains OutputReportByteLength - its a proper buffer size that HID driver expects for WriteFile/HidD_SetOutputReport calls on a HID device handle (if device descriptor contains several output reports - then OutputReportByteLength will be maximum size between them. You have to just fill extra bytes with zeros for other reports). Windows cannot sent reports to HID device if they are not declared in device's HID report descriptor.

    You can use HidP_GetValueCaps/HidP_GetButtonCaps to get report information (which values it contains etc). And then use HidP_Set* routines to set information in report buffer. More info on how to extact report format here:

    Alternatively you can extract HID report descriptor for your device with a hid_get_report_descriptor hidapi function. Or via Wireshark with USBPcap installed. After you have HID report descriptor you can analyze it online via USB Descriptor and Request Parser or offline with RDD! HID Report Descriptor Decoder tool - which can even generate proper C structs for reports that can be used to fill the report buffer.

    Here is example output from RDD! HID Report Descriptor Decoder from a Google Stadia Controller:

    
    //--------------------------------------------------------------------------------
    // Report descriptor data in hex (length 182 bytes)
    //--------------------------------------------------------------------------------
    
    
    // 05010905 A1018503 05017504 95012507 463B0165 14093981 42450065 00750195
    // 04810105 09150025 01750195 0F091209 11091409 13090D09 0C090B09 0F090E09
    // 08090709 05090409 02090181 02750195 01810105 01150126 FF000901 A1000930
    // 09317508 95028102 C00901A1 00093209 35750895 028102C0 05027508 95021500
    // 26FF0009 C509C481 02050C15 00250109 E909EA75 01950281 0209CD95 01810295
    // 05810185 05060F00 09977510 950227FF FF000091 02C0  
    
    
    //--------------------------------------------------------------------------------
    // Decoded Application Collection
    //--------------------------------------------------------------------------------
    
    /*
    05 01        (GLOBAL) USAGE_PAGE         0x0001 Generic Desktop Page 
    09 05        (LOCAL)  USAGE              0x00010005 Game Pad (Application Collection)  
    A1 01        (MAIN)   COLLECTION         0x01 Application (Usage=0x00010005: Page=Generic Desktop Page, Usage=Game Pad, Type=Application Collection)
    85 03          (GLOBAL) REPORT_ID          0x03 (3)  
    05 01          (GLOBAL) USAGE_PAGE         0x0001 Generic Desktop Page <-- Redundant: USAGE_PAGE is already 0x0001
    75 04          (GLOBAL) REPORT_SIZE        0x04 (4) Number of bits per field  
    95 01          (GLOBAL) REPORT_COUNT       0x01 (1) Number of fields  
    25 07          (GLOBAL) LOGICAL_MAXIMUM    0x07 (7)  
    46 3B01        (GLOBAL) PHYSICAL_MAXIMUM   0x013B (315)  
    65 14          (GLOBAL) UNIT               0x14 Rotation in degrees [1° units] (4=System=English Rotation, 1=Rotation=Degrees)  
    09 39          (LOCAL)  USAGE              0x00010039 Hat switch (Dynamic Value)  
    81 42          (MAIN)   INPUT              0x00000042 (1 field x 4 bits) 0=Data 1=Variable 0=Absolute 0=NoWrap 0=Linear 0=PrefState 1=Null 0=NonVolatile 0=Bitmap  <-- Error: LOGICAL_MINIMUM is undefined <-- Error: PHYSICAL_MINIMUM is undefined
    45 00          (GLOBAL) PHYSICAL_MAXIMUM   0x00 (0)  <-- Info: Consider replacing 45 00 with 44
    65 00          (GLOBAL) UNIT               0x00 No unit (0=None)  <-- Info: Consider replacing 65 00 with 64
    75 01          (GLOBAL) REPORT_SIZE        0x01 (1) Number of bits per field  
    95 04          (GLOBAL) REPORT_COUNT       0x04 (4) Number of fields  
    81 01          (MAIN)   INPUT              0x00000001 (4 fields x 1 bit) 1=Constant 0=Array 0=Absolute 
    05 09          (GLOBAL) USAGE_PAGE         0x0009 Button Page 
    15 00          (GLOBAL) LOGICAL_MINIMUM    0x00 (0)  <-- Info: Consider replacing 15 00 with 14
    25 01          (GLOBAL) LOGICAL_MAXIMUM    0x01 (1)  
    75 01          (GLOBAL) REPORT_SIZE        0x01 (1) Number of bits per field <-- Redundant: REPORT_SIZE is already 1 
    95 0F          (GLOBAL) REPORT_COUNT       0x0F (15) Number of fields  
    09 12          (LOCAL)  USAGE              0x00090012 Button 18 (Selector, On/Off Control, Momentary Control, or One Shot Control)  
    09 11          (LOCAL)  USAGE              0x00090011 Button 17 (Selector, On/Off Control, Momentary Control, or One Shot Control)  
    09 14          (LOCAL)  USAGE              0x00090014 Button 20 (Selector, On/Off Control, Momentary Control, or One Shot Control)  
    09 13          (LOCAL)  USAGE              0x00090013 Button 19 (Selector, On/Off Control, Momentary Control, or One Shot Control)  
    09 0D          (LOCAL)  USAGE              0x0009000D Button 13 (Selector, On/Off Control, Momentary Control, or One Shot Control)  
    09 0C          (LOCAL)  USAGE              0x0009000C Button 12 (Selector, On/Off Control, Momentary Control, or One Shot Control)  
    09 0B          (LOCAL)  USAGE              0x0009000B Button 11 (Selector, On/Off Control, Momentary Control, or One Shot Control)  
    09 0F          (LOCAL)  USAGE              0x0009000F Button 15 (Selector, On/Off Control, Momentary Control, or One Shot Control)  
    09 0E          (LOCAL)  USAGE              0x0009000E Button 14 (Selector, On/Off Control, Momentary Control, or One Shot Control)  
    09 08          (LOCAL)  USAGE              0x00090008 Button 8 (Selector, On/Off Control, Momentary Control, or One Shot Control)  
    09 07          (LOCAL)  USAGE              0x00090007 Button 7 (Selector, On/Off Control, Momentary Control, or One Shot Control)  
    09 05          (LOCAL)  USAGE              0x00090005 Button 5 (Selector, On/Off Control, Momentary Control, or One Shot Control)  
    09 04          (LOCAL)  USAGE              0x00090004 Button 4 (Selector, On/Off Control, Momentary Control, or One Shot Control)  
    09 02          (LOCAL)  USAGE              0x00090002 Button 2 Secondary (Selector, On/Off Control, Momentary Control, or One Shot Control)  
    09 01          (LOCAL)  USAGE              0x00090001 Button 1 Primary/trigger (Selector, On/Off Control, Momentary Control, or One Shot Control)  
    81 02          (MAIN)   INPUT              0x00000002 (15 fields x 1 bit) 0=Data 1=Variable 0=Absolute 0=NoWrap 0=Linear 0=PrefState 0=NoNull 0=NonVolatile 0=Bitmap  <-- Error: PHYSICAL_MINIMUM is undefined
    75 01          (GLOBAL) REPORT_SIZE        0x01 (1) Number of bits per field <-- Redundant: REPORT_SIZE is already 1 
    95 01          (GLOBAL) REPORT_COUNT       0x01 (1) Number of fields  
    81 01          (MAIN)   INPUT              0x00000001 (1 field x 1 bit) 1=Constant 0=Array 0=Absolute 
    05 01          (GLOBAL) USAGE_PAGE         0x0001 Generic Desktop Page 
    15 01          (GLOBAL) LOGICAL_MINIMUM    0x01 (1)  
    26 FF00        (GLOBAL) LOGICAL_MAXIMUM    0x00FF (255)  
    09 01          (LOCAL)  USAGE              0x00010001 Pointer (Physical Collection)  
    A1 00          (MAIN)   COLLECTION         0x00 Physical (Usage=0x00010001: Page=Generic Desktop Page, Usage=Pointer, Type=Physical Collection)
    09 30            (LOCAL)  USAGE              0x00010030 X (Dynamic Value)  
    09 31            (LOCAL)  USAGE              0x00010031 Y (Dynamic Value)  
    75 08            (GLOBAL) REPORT_SIZE        0x08 (8) Number of bits per field  
    95 02            (GLOBAL) REPORT_COUNT       0x02 (2) Number of fields  
    81 02            (MAIN)   INPUT              0x00000002 (2 fields x 8 bits) 0=Data 1=Variable 0=Absolute 0=NoWrap 0=Linear 0=PrefState 0=NoNull 0=NonVolatile 0=Bitmap  <-- Error: PHYSICAL_MINIMUM is undefined
    C0             (MAIN)   END_COLLECTION     Physical 
    09 01          (LOCAL)  USAGE              0x00010001 Pointer (Physical Collection)  
    A1 00          (MAIN)   COLLECTION         0x00 Physical (Usage=0x00010001: Page=Generic Desktop Page, Usage=Pointer, Type=Physical Collection)
    09 32            (LOCAL)  USAGE              0x00010032 Z (Dynamic Value)  
    09 35            (LOCAL)  USAGE              0x00010035 Rz (Dynamic Value)  
    75 08            (GLOBAL) REPORT_SIZE        0x08 (8) Number of bits per field <-- Redundant: REPORT_SIZE is already 8 
    95 02            (GLOBAL) REPORT_COUNT       0x02 (2) Number of fields <-- Redundant: REPORT_COUNT is already 2 
    81 02            (MAIN)   INPUT              0x00000002 (2 fields x 8 bits) 0=Data 1=Variable 0=Absolute 0=NoWrap 0=Linear 0=PrefState 0=NoNull 0=NonVolatile 0=Bitmap  <-- Error: PHYSICAL_MINIMUM is undefined
    C0             (MAIN)   END_COLLECTION     Physical 
    05 02          (GLOBAL) USAGE_PAGE         0x0002 Simulation Controls Page 
    75 08          (GLOBAL) REPORT_SIZE        0x08 (8) Number of bits per field <-- Redundant: REPORT_SIZE is already 8 
    95 02          (GLOBAL) REPORT_COUNT       0x02 (2) Number of fields <-- Redundant: REPORT_COUNT is already 2 
    15 00          (GLOBAL) LOGICAL_MINIMUM    0x00 (0)  <-- Info: Consider replacing 15 00 with 14
    26 FF00        (GLOBAL) LOGICAL_MAXIMUM    0x00FF (255) <-- Redundant: LOGICAL_MAXIMUM is already 255 
    09 C5          (LOCAL)  USAGE              0x000200C5 Brake (Dynamic Value)  
    09 C4          (LOCAL)  USAGE              0x000200C4 Accelerator (Dynamic Value)  
    81 02          (MAIN)   INPUT              0x00000002 (2 fields x 8 bits) 0=Data 1=Variable 0=Absolute 0=NoWrap 0=Linear 0=PrefState 0=NoNull 0=NonVolatile 0=Bitmap  <-- Error: PHYSICAL_MINIMUM is undefined
    05 0C          (GLOBAL) USAGE_PAGE         0x000C Consumer Device Page 
    15 00          (GLOBAL) LOGICAL_MINIMUM    0x00 (0) <-- Redundant: LOGICAL_MINIMUM is already 0 <-- Info: Consider replacing 15 00 with 14
    25 01          (GLOBAL) LOGICAL_MAXIMUM    0x01 (1)  
    09 E9          (LOCAL)  USAGE              0x000C00E9 Volume Increment (Re-trigger Control)  
    09 EA          (LOCAL)  USAGE              0x000C00EA Volume Decrement (Re-trigger Control)  
    75 01          (GLOBAL) REPORT_SIZE        0x01 (1) Number of bits per field  
    95 02          (GLOBAL) REPORT_COUNT       0x02 (2) Number of fields <-- Redundant: REPORT_COUNT is already 2 
    81 02          (MAIN)   INPUT              0x00000002 (2 fields x 1 bit) 0=Data 1=Variable 0=Absolute 0=NoWrap 0=Linear 0=PrefState 0=NoNull 0=NonVolatile 0=Bitmap  <-- Error: PHYSICAL_MINIMUM is undefined
    09 CD          (LOCAL)  USAGE              0x000C00CD Play/Pause (One Shot Control)  
    95 01          (GLOBAL) REPORT_COUNT       0x01 (1) Number of fields  
    81 02          (MAIN)   INPUT              0x00000002 (1 field x 1 bit) 0=Data 1=Variable 0=Absolute 0=NoWrap 0=Linear 0=PrefState 0=NoNull 0=NonVolatile 0=Bitmap  <-- Error: PHYSICAL_MINIMUM is undefined
    95 05          (GLOBAL) REPORT_COUNT       0x05 (5) Number of fields  
    81 01          (MAIN)   INPUT              0x00000001 (5 fields x 1 bit) 1=Constant 0=Array 0=Absolute 
    85 05          (GLOBAL) REPORT_ID          0x05 (5)  
    06 0F00        (GLOBAL) USAGE_PAGE         0x000F Physical Interface Device Page 
    09 97          (LOCAL)  USAGE              0x000F0097 DC Enable Actuators (Selector)  
    75 10          (GLOBAL) REPORT_SIZE        0x10 (16) Number of bits per field  
    95 02          (GLOBAL) REPORT_COUNT       0x02 (2) Number of fields  
    27 FFFF0000    (GLOBAL) LOGICAL_MAXIMUM    0x0000FFFF (65535)  
    91 02          (MAIN)   OUTPUT             0x00000002 (2 fields x 16 bits) 0=Data 1=Variable 0=Absolute 0=NoWrap 0=Linear 0=PrefState 0=NoNull 0=NonVolatile 0=Bitmap  <-- Error: PHYSICAL_MINIMUM is undefined
    C0           (MAIN)   END_COLLECTION     Application 
    */
    
    // All structure fields should be byte-aligned...
    #pragma pack(push,1)
    
    //--------------------------------------------------------------------------------
    // Generic Desktop Page inputReport 03 (Device --> Host)
    //--------------------------------------------------------------------------------
    
    typedef struct
    {
      uint8_t  reportId;                                 // Report ID = 0x03 (3)
                                                         // Collection: CA:GamePad
      int8_t   GD_GamePadHatSwitch : 4;                  // Usage 0x00010039: Hat switch, Value =  to 7, Physical = Value x 45 in degrees
      int8_t   : 1;                                      // Pad
      int8_t   : 1;                                      // Pad
      int8_t   : 1;                                      // Pad
      int8_t   : 1;                                      // Pad
      uint8_t  BTN_GamePadButton18 : 1;                  // Usage 0x00090012: Button 18, Value = 0 to 1
      uint8_t  BTN_GamePadButton17 : 1;                  // Usage 0x00090011: Button 17, Value = 0 to 1
      uint8_t  BTN_GamePadButton20 : 1;                  // Usage 0x00090014: Button 20, Value = 0 to 1
      uint8_t  BTN_GamePadButton19 : 1;                  // Usage 0x00090013: Button 19, Value = 0 to 1
      uint8_t  BTN_GamePadButton13 : 1;                  // Usage 0x0009000D: Button 13, Value = 0 to 1
      uint8_t  BTN_GamePadButton12 : 1;                  // Usage 0x0009000C: Button 12, Value = 0 to 1
      uint8_t  BTN_GamePadButton11 : 1;                  // Usage 0x0009000B: Button 11, Value = 0 to 1
      uint8_t  BTN_GamePadButton15 : 1;                  // Usage 0x0009000F: Button 15, Value = 0 to 1
      uint8_t  BTN_GamePadButton14 : 1;                  // Usage 0x0009000E: Button 14, Value = 0 to 1
      uint8_t  BTN_GamePadButton8 : 1;                   // Usage 0x00090008: Button 8, Value = 0 to 1
      uint8_t  BTN_GamePadButton7 : 1;                   // Usage 0x00090007: Button 7, Value = 0 to 1
      uint8_t  BTN_GamePadButton5 : 1;                   // Usage 0x00090005: Button 5, Value = 0 to 1
      uint8_t  BTN_GamePadButton4 : 1;                   // Usage 0x00090004: Button 4, Value = 0 to 1
      uint8_t  BTN_GamePadButton2 : 1;                   // Usage 0x00090002: Button 2 Secondary, Value = 0 to 1
      uint8_t  BTN_GamePadButton1 : 1;                   // Usage 0x00090001: Button 1 Primary/trigger, Value = 0 to 1
      uint8_t  : 1;                                      // Pad
                                                         // Collection: CA:GamePad CP:Pointer
      uint8_t  GD_GamePadPointerX;                       // Usage 0x00010030: X, Value = 1 to 255
      uint8_t  GD_GamePadPointerY;                       // Usage 0x00010031: Y, Value = 1 to 255
      uint8_t  GD_GamePadPointerZ;                       // Usage 0x00010032: Z, Value = 1 to 255
      uint8_t  GD_GamePadPointerRz;                      // Usage 0x00010035: Rz, Value = 1 to 255
                                                         // Collection: CA:GamePad
      uint8_t  SIM_GamePadBrake;                         // Usage 0x000200C5: Brake, Value = 0 to 255
      uint8_t  SIM_GamePadAccelerator;                   // Usage 0x000200C4: Accelerator, Value = 0 to 255
      uint8_t  CD_GamePadVolumeIncrement : 1;            // Usage 0x000C00E9: Volume Increment, Value = 0 to 1
      uint8_t  CD_GamePadVolumeDecrement : 1;            // Usage 0x000C00EA: Volume Decrement, Value = 0 to 1
      uint8_t  CD_GamePadPlayPause : 1;                  // Usage 0x000C00CD: Play/Pause, Value = 0 to 1
      uint8_t  : 1;                                      // Pad
      uint8_t  : 1;                                      // Pad
      uint8_t  : 1;                                      // Pad
      uint8_t  : 1;                                      // Pad
      uint8_t  : 1;                                      // Pad
    } inputReport03_t;
    
    
    //--------------------------------------------------------------------------------
    // Physical Interface Device Page outputReport 05 (Device <-- Host)
    //--------------------------------------------------------------------------------
    
    typedef struct
    {
      uint8_t  reportId;                                 // Report ID = 0x05 (5)
                                                         // Collection: CA:GamePad
      uint16_t PID_GamePadDcEnableActuators[2];          // Usage 0x000F0097: DC Enable Actuators, Value = 0 to 65535
    } outputReport05_t;
    
    #pragma pack(pop)