Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add HID descriptors #6

Closed
wants to merge 10 commits into from
109 changes: 109 additions & 0 deletions usb_protocol/emitters/descriptors/hid.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
import unittest

from contextlib import contextmanager

from .. import emitter_for_format
from ..descriptor import ComplexDescriptorEmitter
from ...types.descriptors.hid import \
HIDDescriptor as HIDDescriptorType
from ...types.descriptors.hid import *

ReportDescriptorEmitter = emitter_for_format(ReportDescriptor)

_hid_item_length = [ 0, 1, 2, 4 ]

class HIDDescriptor(ComplexDescriptorEmitter):
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There seem to be a bunch of naming inconsistencies, here.

For example, this looks like it produces more than a HID descriptor (it also builds report descriptors), so perhaps this would be better called e.g. a HIDDescriptorCollection?

DESCRIPTOR_FORMAT = HIDDescriptorType

def add_report_raw(self, report_data):
"""Append raw report item or bytes to HID report

Arguments:
report_data -- bytes-like or ReportDescriptor to be appended to
the HID report.
"""
self._reports.append(hid_data)
TiltMeSenpai marked this conversation as resolved.
Show resolved Hide resolved

def add_report_item(self, report_prefix, *report_data):
"""Convenience function to add formatted HID report item

Arguments:
report_prefix -- HIDPrefix enum representing report item type
*report_data -- Additional bytes-like report item data.
Valid lengths are 1, 2, or 4 bytes.
"""
hid_report = ReportDescriptorEmitter()
report_len = _hid_item_length.index(len(report_data))
hid_report.bHeader = {
"prefix": report_prefix,
"bSize": report_len
}
hid_report.data = report_data
self._reports.append(hid_report)

def add_input_item(self,
data_constant = False,
array_variable = True,
absolute_relative = False,
wrap = False,
linear = False,
preferred = True,
null = False,
volatile = False):
"""Convenience function to add HID input item with preformatted flags.
See HID 1.11 section 6.2.2.5 for flag meanings.
"""
item_flags = ItemFlags.build({
"data_constant": data_constant,
"array_variable": array_variable,
"absolute_relative": absolute_relative,
"wrap": wrap,
"linear": linear,
"nPreferred": ~preferred,
"null": null,
"volatile": volatile,
})
self.add_report_item(HIDPrefix.INPUT, ord(item_flags))

def add_output_item(self,
data_constant = False,
array_variable = True,
absolute_relative = False,
wrap = False,
linear = False,
preferred = True,
null = False,
volatile = False):
"""Convenience function to add HID output item with preformatted flags.
See HID 1.11 section 6.2.2.5 for flag meanings.
"""
item_flags = ItemFlags.build({
"data_constant": data_constant,
"array_variable": array_variable,
"absolute_relative": absolute_relative,
"wrap": wrap,
"linear": linear,
"nPreferred": ~preferred,
"null": null,
"volatile": volatile,
})
self.add_report_item(HIDPrefix.OUTPUT, ord(item_flags))

def __init__(self, parent_descriptor):
super().__init__()
# The HID Report Descriptor sits under a different USB Descriptor,
# we need access to the descriptor root to create this.
self._parent_descriptor = parent_descriptor
self._reports = []

def _pre_emit(self):
report_descriptor = []
for report in self._reports:
if hasattr(report, "emit"):
report_descriptor.append(report.emit())
else:
report_descriptor.append(report)
report_descriptor = b"".join(report_descriptor)
descriptor_len = len(report_descriptor)
self.wDescriptorLength = descriptor_len
self._parent_descriptor.add_descriptor(report_descriptor, 0x22)
11 changes: 7 additions & 4 deletions usb_protocol/emitters/descriptors/standard.py
Original file line number Diff line number Diff line change
Expand Up @@ -177,20 +177,23 @@ def get_index_for_string(self, string):
return index


def add_descriptor(self, descriptor, index=0):
def add_descriptor(self, descriptor, descriptor_type=None, index=0):
TiltMeSenpai marked this conversation as resolved.
Show resolved Hide resolved
""" Adds a descriptor to our collection.

Parameters:
descriptor -- The descriptor to be added.
index -- The index of the relevant descriptor. Defaults to 0.
descriptor -- The descriptor to be added.
descriptor_type -- The type of the descriptor to be added. If `None`,
this is automatically derived from the descriptor contents.
index -- The index of the relevant descriptor. Defaults to 0.
"""

# If this is an emitter rather than a descriptor itself, convert it.
if hasattr(descriptor, 'emit'):
descriptor = descriptor.emit()

# Figure out the identifier (type + index) for this descriptor...
descriptor_type = descriptor[1]
if(descriptor_type == None):
TiltMeSenpai marked this conversation as resolved.
Show resolved Hide resolved
descriptor_type = descriptor[1]
identifier = descriptor_type, index

# ... and store it.
Expand Down
85 changes: 85 additions & 0 deletions usb_protocol/types/descriptors/hid.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
#
# This file is part of usb-protocol.
#
""" Structures describing Communications Device Class descriptors. """

import unittest
from enum import IntEnum, unique

import construct
from construct import this, Default

from .. import LanguageIDs
from ..descriptor import \
DescriptorField, DescriptorNumber, DescriptorFormat, \
BCDFieldAdapter, DescriptorLength

@unique
class HIDPrefix(IntEnum):
# Main items
INPUT = 0b1000_00
OUTPUT = 0b1001_00
FEATURE = 0b1011_00
COLLECTION = 0b1010_00
END_COLLECTION = 0b1100_00
# Global items
USAGE_PAGE = 0b0000_01
LOGICAL_MIN = 0b0001_01
LOGICAL_MAX = 0b0010_01
PHYSICAL_MIN = 0b0011_01
PHYSICAL_MAX = 0b0100_01
UNIT_EXPONENT = 0b0101_01
UNIT = 0b0110_01
REPORT_SIZE = 0b0111_01
REPORT_ID = 0b1000_01
REPORT_COUNT = 0b1001_01
PUSH = 0b1010_01
POP = 0b1011_01
# Local Items
USAGE = 0b0000_10
USAGE_MIN = 0b0001_10
USAGE_MAX = 0b0010_10
DESIGNATOR_IDX = 0b0011_10
DESIGNATOR_MIN = 0b0100_10
DESIGNATOR_MAX = 0b0101_10
STRING_IDX = 0b0111_10
STRING_MIN = 0b1000_10
STRING_MAX = 0b1001_10
DELIMITER = 0b1010_10

HIDDescriptor = DescriptorFormat(
"bLength" / construct.Const(0x09, construct.Int8ul),
"bDescriptorType" / DescriptorNumber(33),
"bcdHID" / DescriptorField("HID Protocol Version", default=1.11),
"bCountryCode" / DescriptorField("HID Device Language", default=0),
"bNumDescriptors" / DescriptorField("Number of HID Descriptors", default=1),
"bDescriptorType" / DescriptorField("HID Descriptor Type", default=34),
"wDescriptorLength" / DescriptorField("HID Descriptor Length")
# bDescriptorType and wDescriptorLength repeat bNumDescriptors times
)

_hid_item_length = [ 0, 1, 2, 4 ]
ReportDescriptor = DescriptorFormat(
"bHeader" / construct.BitStruct(
# prefix technically consists of a 4 byte tag and a 2 byte type,
# however, they're all listed together in the HID spec
"prefix" / construct.Enum(construct.BitsInteger(6), HIDPrefix),
"bSize" / construct.BitsInteger(2),
),
"data" / construct.Byte[lambda ctx: _hid_item_length[ctx.bHeader.bSize]]
)

# Flags for INPUT/OUTPUT/FEATURE items. Named under one of the following conventions:
# valA_valB: valA when 0, valB when 1
# flag: Flag disabled when 0, flag enabled when 1
# nFlag: Flag enabled when 0, flag disabled when 1
ItemFlags = construct.BitStruct(
"volatile" / construct.Flag,
"null" / construct.Flag,
"nPreferred" / construct.Flag,
"linear" / construct.Flag,
"wrap" / construct.Flag,
"absolute_relative" / construct.Flag,
"array_variable" / construct.Flag,
"data_constant" / construct.Flag,
)