Search Unity

How do you read from the raw HID?

Discussion in 'Input System' started by HeyItsLollie, Jun 5, 2019.

  1. HeyItsLollie

    HeyItsLollie

    Joined:
    May 16, 2015
    Posts:
    68
    So, I have a controller adapter/input sniffer that makes itself usable on PC via HID, but some of the HID's elements are undefined - and those elements store usable controller input data. I'm trying to read this data so that I can build my own input display.

    I can't target this data through the Input Actions window (I really wish I could set action binding paths to specific HID descriptor elements or bit/byte offsets!), so I need to be able to target the specific device that I'm trying to read HID data from, read bits/bytes from specific offsets, and parse that data into values I can use (float/integer/axis/bool).

    Unfortunately for me, the documentation for HID support isn't complete, so I have zero clues about where to start. Any help would be greatly appreciated.
     
  2. jonas-echterhoff

    jonas-echterhoff

    Unity Technologies

    Joined:
    Aug 18, 2005
    Posts:
    1,666
    You should be able to create your own device creating controls from your own data structure matching the raw HID layout. It's true that there are no Docs for this atm, but you should be able to figure it out by looking at existing HID device implementations in the input system package code. Look for `XInputControllerWirelessOSX` for an example.
     
  3. zammle2009wtf

    zammle2009wtf

    Joined:
    May 30, 2018
    Posts:
    5
    Did you ever find the solution to this?
    I am also having the same problem.
     
  4. HeyItsLollie

    HeyItsLollie

    Joined:
    May 16, 2015
    Posts:
    68
    I can't remember exactly how I handled the Unity side of this, but I can share the work that I did. All I remember is that I got walled by a different bug, and ended up abandoning my attempt. What I ended up with was mostly functional though.

    The documentation has improved since I last saw it, though it still suffers from not showing a complete script. There's zero mention of what needs to be included via "using" or the namespace required. I'll include my entire script, but I highly encourage Unity to correct this on the documentation side.

    First, two tools that helped me along the way:
    HID Trace by "Atomix Productions", and SimpleHIDWrite.
    http://domoticx.com/usb-hid-sniffer-software-windows/

    You only need one of the above tools - I ended up sticking with SimpleHIDWrite, but both tools do the same job. Pick whichever is easier for you to use.

    If you don't have any info about the inner workings of the controller, figuring out how a controller's HID is laid out is a matter of trial and error. Test each input, and see which bytes react. Write them down somewhere.

    Here are the notes I took while figuring out how the PS2 variant of the MUNIA adapter (MUSIA) was laid out.
    Later on, I got in contact with the developer of the adapter, and they pointed me towards the struct for the adapter for verification.
    Code (text):
    1. Vendor name: "munia.io"
    2. Product name: "MUSIA PS2 controller"
    3. Serial No: "03003200"
    4.  
    5.      VID (hex): 1209
    6.      PID (hex): 8844
    7. Revision (hex): 010B
    8.  
    9. Report size (including ReportID):
    10.   Input: 20 Bytes
    11. Output:  0 Bytes
    12. Feature:  0 Bytes
    13.  
    14.  
    15.  
    16.        00 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F 10 11 12
    17.        --------------------------------------------------------
    18. RD 00  00 80 80 80 80 80 01 00 00 00 00 00 00 00 00 00 00 00 00
    19. _____
    20.  
    21. 00:  (00 Default)
    22. Buttons, Part 1 of 2
    23. - 01 Cross
    24. - 02 Square
    25. - 04 Circle
    26. - 08 Triangle
    27. - 10 Select
    28. - 20 Start
    29. - 40 L3
    30. - 80 R3
    31. _____
    32.  
    33. 01:  (80 Default)
    34. Buttons, Part 2 of 2
    35. - 01 L1
    36. - 02 R1
    37. - 04 L2
    38. - 08 R2
    39.  
    40. D-Pad Directions
    41. - 00 Up
    42. - 10 Up+Right
    43. - 20 Right
    44. - 30 Right+Down
    45. - 40 Down
    46. - 50 Down+Left
    47. - 60 Left
    48. - 70 Left+Up
    49. - 80 D-Pad Rest
    50. _____
    51.  
    52. 02:
    53. Left Stick X (80 Default)
    54. - 00 Left
    55. - FF Right
    56. _____
    57.  
    58. 03:
    59. Left Stick Y (80 Default)
    60. - 00 Up
    61. - FF Down
    62. _____
    63.  
    64. 04:
    65. Right Stick X (80 Default)
    66. - 00 Left
    67. - FF Right
    68. _____
    69.  
    70. 05:
    71. Right Stick Y (80 Default)
    72. - 00 Up
    73. - FF Down
    74. _____
    75.  
    76. 06:
    77. Analog Enabled/Disabled Switch (01 Default)
    78. - 00 Disabled
    79. - 01 Enabled
    80. _____
    81.  
    82. Everything from here onwards is an axis, related to Pressure Sensitivity (00 Default)
    83. - 00 Rest
    84. - FF Full Press
    85.  
    86. 07: D-Pad Right
    87. 08: D-Pad Left
    88. 09: D-Pad Up
    89. 0A: D-Pad Down
    90. 0B: Triangle
    91. 0C: Circle
    92. 0D: Cross
    93. 0E: Square
    94. 0F: L1
    95. 10: R1
    96. 11: L2
    97. 12: R2
    And here's the HID script that I ended up with. Take note of [FieldOffset(X)], and how it aligns with each byte above. [FieldOffset(0)] seems to be reserved for the reportID, so the number for each byte will need to be shifted up by one. EG: Byte 00 / [FieldOffset(1)] = All the face buttons, Byte 01 / [FieldOffset(2)] = The shoulders, triggers, and D-Pad directions.. And so on.
    Code (CSharp):
    1.  
    2. using UnityEngine;
    3. using UnityEditor;
    4. using System.Runtime.InteropServices;
    5. using UnityEngine.InputSystem.Layouts;
    6. using UnityEngine.InputSystem.LowLevel;
    7. using UnityEngine.InputSystem.Utilities;
    8.  
    9. namespace UnityEngine.InputSystem.LowLevel
    10. {
    11.     [StructLayout(LayoutKind.Explicit, Size = 20)]
    12.     struct DualShock2HIDInputReport : IInputStateTypeInfo
    13.     {
    14.         public FourCC format => new FourCC('H', 'I', 'D');
    15.  
    16.         [FieldOffset(0)] public byte reportId;
    17.  
    18.         [InputControl(name = "buttonSouth", format = "BIT", displayName = "Cross", layout = "Button", bit = 0)]
    19.         [InputControl(name = "buttonWest", format = "BIT", displayName = "Square", layout = "Button", bit = 1)]
    20.         [InputControl(name = "buttonEast", format = "BIT", displayName = "Circle", layout = "Button", bit = 2)]
    21.         [InputControl(name = "buttonNorth", format = "BIT", displayName = "Triangle", layout = "Button", bit = 3)]
    22.         [InputControl(name = "select", format = "BIT", displayName = "Select", layout = "Button", bit = 4)]
    23.         [InputControl(name = "start", format = "BIT", displayName = "Start", layout = "Button", bit = 5)]
    24.         [InputControl(name = "leftStickPress", format = "BIT", displayName = "L3", layout = "Button", bit = 6)]
    25.         [InputControl(name = "rightStickPress", format = "BIT", displayName = "R3", layout = "Button", bit = 7)]
    26.         [FieldOffset(1)] public byte buttons1;
    27.  
    28.         [InputControl(name = "leftShoulder", format = "BIT", displayName = "L1", layout = "Button", bit = 0)]
    29.         [InputControl(name = "rightShoulder", format = "BIT", displayName = "R1", layout = "Button", bit = 1)]
    30.         [InputControl(name = "leftTrigger", format = "BIT", displayName = "L2", layout = "Button", bit = 2)]
    31.         [InputControl(name = "rightTrigger", format = "BIT", displayName = "R2", layout = "Button", bit = 3)]
    32.         [InputControl(name = "dpad", format = "BIT", layout = "Dpad", sizeInBits = 4, defaultState = 8)]
    33.         [InputControl(name = "dpad/up", format = "BIT", layout = "DiscreteButton", bit = 4, sizeInBits = 4, parameters = "minValue=7, maxValue=1, wrapAtValue=7, nullValue=8")]
    34.         [InputControl(name = "dpad/right", format = "BIT", layout = "DiscreteButton", bit = 4, sizeInBits = 4, parameters = "minValue=1, maxValue=3")]
    35.         [InputControl(name = "dpad/down", format = "BIT", layout = "DiscreteButton", bit = 4, sizeInBits = 4, parameters = "minValue=3, maxValue=5")]
    36.         [InputControl(name = "dpad/left", format = "BIT", layout = "DiscreteButton", bit = 4, sizeInBits = 4, parameters = "minValue=5, maxValue=7")]
    37.         [FieldOffset(2)] public byte buttons2;
    38.  
    39.         [InputControl(name = "leftStick", layout = "Stick", format = "VC2B")]
    40.         [InputControl(name = "leftStick/x", layout = "Axis", offset = 0, format = "BYTE", parameters = "normalize,normalizeMin=0,normalizeMax=1,normalizeZero=0.5")]
    41.         [InputControl(name = "leftStick/left", layout = "Button", offset = 0, format = "BYTE", parameters = "normalize,normalizeMin=0,normalizeMax=1,normalizeZero=0.5,clamp,clampMin=0,clampMax=0.5,invert")]
    42.         [InputControl(name = "leftStick/right", layout = "Button", offset = 0, format = "BYTE", parameters = "normalize,normalizeMin=0,normalizeMax=1,normalizeZero=0.5,clamp,clampMin=0.5,clampMax=1")]
    43.         [InputControl(name = "leftStick/y", layout = "Axis", offset = 1, format = "BYTE", parameters = "invert,normalize,normalizeMin=0,normalizeMax=1,normalizeZero=0.5")]
    44.         [InputControl(name = "leftStick/up", layout = "Button", offset = 1, format = "BYTE", parameters = "normalize,normalizeMin=0,normalizeMax=1,normalizeZero=0.5,clamp,clampMin=0,clampMax=0.5,invert")]
    45.         [InputControl(name = "leftStick/down", layout = "Button", offset = 1, format = "BYTE", parameters = "normalize,normalizeMin=0,normalizeMax=1,normalizeZero=0.5,clamp,clampMin=0.5,clampMax=1,invert=false")]
    46.         [FieldOffset(3)] public byte leftStickX;
    47.         [FieldOffset(4)] public byte leftStickY;
    48.  
    49.         [InputControl(name = "rightStick", layout = "Stick", format = "VC2B")]
    50.         [InputControl(name = "rightStick/x", layout = "Axis", offset = 0, format = "BYTE", parameters = "normalize,normalizeMin=0,normalizeMax=1,normalizeZero=0.5")]
    51.         [InputControl(name = "rightStick/left", layout = "Button", offset = 0, format = "BYTE", parameters = "normalize,normalizeMin=0,normalizeMax=1,normalizeZero=0.5,clamp,clampMin=0,clampMax=0.5,invert")]
    52.         [InputControl(name = "rightStick/right", layout = "Button", offset = 0, format = "BYTE", parameters = "normalize,normalizeMin=0,normalizeMax=1,normalizeZero=0.5,clamp,clampMin=0.5,clampMax=1")]
    53.         [InputControl(name = "rightStick/y", layout = "Axis", offset = 1, format = "BYTE", parameters = "invert,normalize,normalizeMin=0,normalizeMax=1,normalizeZero=0.5")]
    54.         [InputControl(name = "rightStick/up", layout = "Button", offset = 1, format = "BYTE", parameters = "normalize,normalizeMin=0,normalizeMax=1,normalizeZero=0.5,clamp,clampMin=0,clampMax=0.5,invert")]
    55.         [InputControl(name = "rightStick/down", layout = "Button", offset = 1, format = "BYTE", parameters = "normalize,normalizeMin=0,normalizeMax=1,normalizeZero=0.5,clamp,clampMin=0.5,clampMax=1,invert=false")]
    56.         [FieldOffset(5)] public byte rightStickX;
    57.         [FieldOffset(6)] public byte rightStickY;
    58.  
    59.         [InputControl(name = "isPressureOn", format = "BIT", displayName = "Is Pressure On", layout = "Button", bit = 0)]
    60.         [FieldOffset(7)] public byte isPressureOn;
    61.  
    62.         [InputControl(name = "dpadRightPressure", displayName = "DpadRight Pressure", layout = "Axis", format = "BYTE")]
    63.         [FieldOffset(8)] public byte pressureDR;
    64.  
    65.         [InputControl(name = "dpadLeftPressure", displayName = "DpadLeft Pressure", layout = "Axis", format = "BYTE")]
    66.         [FieldOffset(9)] public byte pressureDL;
    67.  
    68.         [InputControl(name = "dpadUpPressure", displayName = "DpadUp Pressure", layout = "Axis", format = "BYTE")]
    69.         [FieldOffset(10)] public byte pressureDU;
    70.  
    71.         [InputControl(name = "dpadDownPressure", displayName = "DpadDown Pressure", layout = "Axis", format = "BYTE")]
    72.         [FieldOffset(11)] public byte pressureDD;
    73.  
    74.         [InputControl(name = "buttonNorthPressure", displayName = "Triangle Pressure", layout = "Axis", format = "BYTE")]
    75.         [FieldOffset(12)] public byte pressureBU;
    76.  
    77.         [InputControl(name = "buttonEastPressure", displayName = "Circle Pressure", layout = "Axis", format = "BYTE")]
    78.         [FieldOffset(13)] public byte pressureBR;
    79.  
    80.         [InputControl(name = "buttonSouthPressure", displayName = "Cross Pressure", layout = "Axis", format = "BYTE")]
    81.         [FieldOffset(14)] public byte pressureBD;
    82.  
    83.         [InputControl(name = "buttonWestPressure", displayName = "Square Pressure", layout = "Axis", format = "BYTE")]
    84.         [FieldOffset(15)] public byte pressureBL;
    85.  
    86.         [InputControl(name = "leftShoulderPressure", displayName = "L1 Pressure", layout = "Axis", format = "BYTE")]
    87.         [FieldOffset(16)] public byte pressureL1;
    88.  
    89.         [InputControl(name = "rightShoulderPressure", displayName = "R1 Pressure", layout = "Axis", format = "BYTE")]
    90.         [FieldOffset(17)] public byte pressureR1;
    91.  
    92.         [InputControl(name = "leftTriggerPressure", displayName = "L2 Pressure", layout = "Axis", format = "BYTE")]
    93.         [FieldOffset(18)] public byte pressureL2;
    94.  
    95.         [InputControl(name = "rightTriggerPressure", displayName = "R2 Pressure", layout = "Axis", format = "BYTE")]
    96.         [FieldOffset(19)] public byte pressureR2;
    97.     }
    98.  
    99.     [InputControlLayout(stateType = typeof(DualShock2HIDInputReport))]
    100.     #if UNITY_EDITOR
    101.     [InitializeOnLoad] // Make sure static constructor is called during startup.
    102.     #endif
    103.     public class DualShock2GamepadHID : Gamepad
    104.     {
    105.     static DualShock2GamepadHID()
    106.         {
    107.         InputSystem.RegisterLayout<DualShock2GamepadHID>(
    108.             matches: new InputDeviceMatcher()
    109.                 .WithInterface("HID")
    110.                 .WithProduct("MUSIA PS2 controller"));
    111.         }
    112.  
    113.         // In the player, trigger the calling of our static constructor
    114.         // by having an empty method annotated with RuntimeInitializeOnLoadMethod.
    115.         [RuntimeInitializeOnLoadMethod]
    116.         static void Init() {}
    117.     }
    118. }
    119.  
    I hope these examples point you in the right direction!
     
    Last edited: Nov 26, 2020
    SrNull and zammle2009wtf like this.
  5. MollySpryFox

    MollySpryFox

    Joined:
    May 4, 2020
    Posts:
    11
    You can view raw HID memory for figuring out the layout in unity itself without a third party program now. Window -> Analysis -> Input Debugger. Then double click on the device and there is a button that says "state" that will show the raw HID descriptor.
     
  6. WhosTheBoss

    WhosTheBoss

    Joined:
    Jan 8, 2013
    Posts:
    64
    I'm guessing it's possible to read Mouse HID?
    I have two mice plugged into my PC. Since Unity will only read one Mouse. Can I just read the raw HID values from my second mouse?