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:
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);
}
}
}
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)