Skip to content

Simplified USB-HID based protocol library, designed for use with WebUSB

License

Notifications You must be signed in to change notification settings

riggs/SimpleHID

Repository files navigation

SimpleHID

Protocol

Device acts like a normal HID class device, except Vendor class type in Interface descriptor.

Device (optionally) defines SimpleHID BOS platform descriptor (with all the normal Capability and Platform headers).

SimpleHID BOS platform descriptor has UUID of 'a8adf97c-6a20-48e4-a97c-79978eec00c7'.

CapabilityData, 18 bytes (Platform descriptor total of 38 bytes):

  • bcdVersion, 2 bytes, binary-coded-decimal version number (1.0.0)
  • Vendor Usage Page for SimpleHID data-type mapping (default to 0xFFAA), 2 bytes
  • Usage ID applied to Application-type Collections to indicate SimpleHID compatibility, 2 bytes (default 0x0000)
  • Usage ID for report data type of Array, 2 bytes (default 0x0001)
    • Only valid when applied to Collection items with Report, Logical, or Physical types.
    • Default report type is Array, so this can be omitted.
  • Usage ID for report data type of Object, 2 bytes (default 0x0002)
    • Only valid when applied to Collection items with Report, Logical, or Physical types.
    • Only valid when applied to (declared before) Collection items with Report type.
  • Usage ID for Uint, 2 bytes (default 0x0003)
    • Valid Report Sizes for Usage: 8, 16, 32, 64
  • Usage ID for int, 2 bytes (default 0x0004)
    • Valid Report Sizes for Usage: 8, 16, 32
  • Usage ID for float, 2 bytes (default 0x0005)
    • Valid Report Sizes for Usage: 32, 64
  • Usage ID for utf8, 2 bytes (default 0x0006)
    • Valid Report Sizes for Usage: multiples of 8

Top-level Application-type Collections in a Report Descriptor will be ignored unless they are tagged with (preceded by) the Usage Page & ID specified in the BOS platform descriptor (or the default values if the device doesn't have a SimpleHID BOS platform descriptor).

Report request types (Input, Output, or Feature) are grouped into Report-type Collections. Report IDs (if used) shall precede each Report Collection declaration.

A String Index item preceding a Report Collection item will be used to name the Report ID. This allows using the designated name instead of the Report ID in API calls. The combination of report type (Input, Output, or Feature) and report name must be unique in order to identify Report ID.

The Object Usage ID is intended to modify Report Collections. Applying the Object usage causes the Javascript API to return report data values as Javascript Objects. Additionally, any inputs to the API are expected to be Objects as well. By default, report data will be accessed as an Array.

The property names for the values in the returned (and output) objects are given by String Descriptors on the device, and are indicated by modifying the Data Main item (Input, Output, or Feature Main items) with the String Index for the appropriate String Descriptor. If a String Index are not present for a given Data item, it will be given a numerical property name.

The Array or Object Usage ID can be applied to Logical or Physical type Collections within other Report Collections to nest data structures, with the enclosed Data items being accessed via a nested Array or Object. This can be used to construct arbitrarily complex objects. Best practice, however, is to split data between Report IDs to create additional smaller reports.

Example SimpleHID Report Descriptor

Usage_Page (SimpleHID_vendor_page)          /* default of 0xFFAA */
Usage_ID (SimpleHID_application_collection) /* default of 0x0000 */
Collection (Application)

    /* Report Name: 'config'
     * Report ID:   1
     * Report Type: Feature
     * Report Data: { 'timeout': Uint64,
     *                'error_threshold': Uint16,
     *                'item_order': Uint8[10] }
     */
    String_Index(7)         /* u'config'*/
    Report_ID(1)
    Usage_ID(USAGE_OBJECT)
    Collection(Report)
        String_Index(8)     /* u'timeout' */
        Usage_ID(USAGE_UINT) Report_Size(64) Report_Count(1) Feature(Variable | Volatile)
        String_Index(9)     /* u'threshold' */
        Usage_ID(USAGE_UINT) Report_Size(16) Report_Count(1) Feature(Variable | Volatile)
        String_Index(10)    /* u'item_order' */
        Usage_ID(USAGE_UINT) Report_Size(8) Report_Count(10) Feature(Variable | Volatile)
    End_Collection

    /* Report Name: 'timestamp'
     * Report ID:   1
     * Report Type: Output
     * Report Data: [ Uint64 ]
     */
    String_Index(4)             /* u'timestamp' */
    Report_ID(1)
    Usage_ID(USAGE_ARRAY)       /* default of 0x0001, can be omitted as Array is default */
    Collection(Report)
        Usage_ID(USAGE_UINT)    /* default of 0x0003 */
        Report_Size(64)
        Report_Count(1)         /* 1x Uint64 */
        Output(Variable)
    End_Collection

    /* Report Name: 'status'
     * Report ID:   1
     * Report Type: Input
     * Report Data: { 'timestamp': Uint64,
     *                'serial_number': Uint32,
     *                'status': Uint8[4] }
     */
    String_Index(5)         /* u'status' */
    Report_ID(1)        /* Same Report ID as 'timestamp', but this is an Input */
    Usage_ID(USAGE_OBJECT)  /* default of 0x0002 */
    Collection(Report)
        String_Index(4)     /* u'timestamp' */
        Usage_ID(USAGE_UINT) Report_Size(64) Report_Count(1) Input(Variable)        /* 1x Uint64 */
        String_Index(6)     /* u'serial_number' */
        Usage_ID(USAGE_UINT) Report_Size(32) Report_Count(1) Input(Variable)        /* 1x Uint32 */
        String_Index(5)     /* u'status */
        Usage_ID(USAGE_UINT) Report_Size(8) Report_Count(4) Input(Variable)         /* 4x Uint8 */
    End_Collection

    /* Report Name: 'event'
     * Report ID:   17
     * Report Type: Input
     * Report Data: { 'timestamp': Uint64,
     *                'event': Uint8 }
     */
    String_Index(11)        /* u'event' */
    Report_ID(17)
    Usage_ID(USAGE_OBJECT)
    Collection(Report)
        String_Index(4)     /* u'timestamp' */
        Usage_ID(USAGE_UINT) Report_Size(64) Report_Count(1) Input(Variable)
        String_Index(11)    /* u'event' */
        Usage_ID(USAGE_INT) Report_Size(8) Report_Count(1) Input(Variable)
    End_Collection

    /* Report Name: 'raw_values'
     * Report ID:   0x45
     *
     * Report Type: Output
     * Report Data: None
     */
    String_Index(42)            /* u'raw_values' */
    Report_ID(0x45)
    Collection(Report)
        Report_Size(0) Report_Count(0) Output(Variable | Volatile) /* Output report with no data */
    End_Collection

    /* Report Name: 'raw_values'
     * Report ID:   0x45
     *
     * Report Type: Input
     * Report Data: { 'single_array': [Uint16, Uint16, ...Uint16, Uint8, Uint8, ...Uint8],
     *                'nested_arrays': [ [Uint16, Uint16, ...Uint16],
     *                                   [Uint8, Uint8, ...Uint8] ]
     *                'object': { 'ADCs': [Uint16, Uint16, ...Uint16],
     *                            'switches': [Uint8, Uint8, ...Uint8] }
     */
    String_Index(42)            /* u'raw_values' */
    Report_ID(0x45)
    Usage_ID(USAGE_OBJECT)
    Collection(Report)
        /* Input report of 10x Uint16, 10x Uint8 in a single array */
        /* Multiple Data Items with the same String Index are concatenated */
        String_Index(43)        /* u'single_array' */
        Usage_ID(USAGE_UINT) Report_Size(16) Report_Count(10) Input(Variable | Buffered_Bytes)
        String_Index(43)        /* u'single_array' */
        Usage_ID(USAGE_UINT) Report_Size(8) Report_Count(10) Input(Variable | Buffered_Bytes)

        /* Input report of [Uint16[10], Uint8[10]]: two arrays nested in an array */
        String_Index(44)        /* u'nested_arrays' */
        Usage_ID(USAGE_ARRAY)   /* Declare outter array */
        Collection(Physical)
            Usage_ID(USAGE_UINT) Report_Size(16) Report_Count(10) Input(Variable | Buffered_Bytes)
            Usage_ID(USAGE_UINT) Report_Size(8) Report_Count(10) Input(Variable | Buffered_Bytes)
        End_Collection

        String_Index(45)        /* u'object' */
        Usage_ID(USAGE_OBJECT)  /* Declare nested object */
        Collection(Physical)
            String_Index(46)        /* u'ADCs' */
            Usage_ID(USAGE_UINT) Report_Size(16) Report_Count(10) Input(Variable | Buffered_Bytes)
            String_Index(47)        /* u'switches' */
            Usage_ID(USAGE_UINT) Report_Size(8) Report_Count(10) Input(Variable | Buffered_Bytes)
        End_Collection
    End_Collection
End_Collection

Javascript Library Example

Connecting to the device defined above:

let device = await navigator.simpleHID.connect();     /* Pop-up window prompts user to select device */

await device.set_feature('config', {'timeout': 6*60*100 /* ms */,
                                    'error_threshold': 42,
                                    'item_order': [0, 2, 4, 6, 8, 9, 7, 5, 3, 1]});

await device.send('timestamp', [ Date.now() ]);

let report = await device.receive();

console.assert(report.id === device.reports.input.status, device.reports.input[report.id]);

console.log(report.data.status);    /* [0, 1, 2, 3] */

let handle = (report) => {
  switch (report.id) {
    case device.reports.input.status:
      console.log(`new status: ${report.data.status}`);
      break;
    case device.reports.input.event:
      console.log(`Event ${report.data.event} happened at ${report.data.timestamp}`);
      break;
    case device.reports.input.raw_values:
      Grapher.Graph(report.data.raw_values.slice(4, -4), Date.now());
      break;
    default:
      console.log(`Unknown Input report: ${report}`);
  }
}

let poll = async () => {
  try {
    let report = await device.receive();
    handle(report);
    setTimeout(poll, 0);
  } catch (error) {
    if (error isinstanceof StopError) {
      console.log("stopping polling");
    } else {
      throw error;
    }
  }
}
setTimeout(poll, 0);

await device.send('raw_values');     /* Turn on raw ADC value reporting. */

Logical Minimum & Maximum are ignored because they cannot specify Uint32 or 64, or Floats. (Logical Min & Max are effectively int32 values.)

About

Simplified USB-HID based protocol library, designed for use with WebUSB

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published