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
87 changes: 87 additions & 0 deletions usb_protocol/emitters/descriptors/hid.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
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(self, report_enum, *report_data):
Copy link

@FFY00 FFY00 Aug 9, 2020

Choose a reason for hiding this comment

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

Where does report come from here? To me, when I talk about HID reports, they are the reports exchanged between the host and the device. This is actually a HID report descriptor item, I would rename it add_item. I would also rename add_input to add_input_item, etc. but that's just nitpicking.

Copy link
Author

Choose a reason for hiding this comment

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

I do suppose this can be documented better. A HID report item consists of a 4 bit tag and a 2 bit type, however, tags are not quite consistent within types, so only the 6 bit combined tag/type value has any map-able meaning. Input/output/feature items are special, because while other report items pretty consistently store byte-oriented data within their additional data fields, input/output/feature items contain a 9-bit bitmap of flags (only 8 bits of which are supported within this PR, as setting the 9th bit will make things a bit of a mess). Input/output/feature items have convenience functions for this reason, as generating them is less trivial than other report items.

Copy link

Choose a reason for hiding this comment

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

Sorry for not explaining correctly. My point was not about the documentation, although that is always better, but about the naming of the method. I believe the name add_report is not correct, IMO it should be called add_item instead 😊

Copy link
Author

Choose a reason for hiding this comment

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

Do the changes in 2e7f95f address this?

Copy link

Choose a reason for hiding this comment

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

Ah sorry, missed that. Yes!

Copy link

Choose a reason for hiding this comment

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

I would also rename add_report_raw acordingly, but overall is fine.

hid_report = ReportDescriptorEmitter()
report_len = _hid_item_length.index(len(report_data))
hid_report.bHeader = {
"prefix": report_enum,
"bSize": report_len
}
hid_report.data = report_data
self._reports.append(hid_report)

def add_input(self,
data_constant = False,
array_variable = True,
absolute_relative = False,
wrap = False,
linear = False,
preferred = True,
null = False,
volatile = False):
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(HIDPrefixes.INPUT, ord(item_flags))

def add_output(self,
data_constant = False,
array_variable = True,
absolute_relative = False,
wrap = False,
linear = False,
preferred = True,
null = False,
volatile = False):
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(HIDPrefixes.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 HIDPrefixes(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), HIDPrefixes),
"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,
)