Skip to content

Commit 9d7ce9f

Browse files
committed
usbd: Implement SET_REPORT support for OUT direction HID data.
1 parent 29e9185 commit 9d7ce9f

File tree

1 file changed

+73
-27
lines changed

1 file changed

+73
-27
lines changed

micropython/usbd/hid.py

Lines changed: 73 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
split_bmRequestType,
99
EP_IN_FLAG,
1010
STAGE_SETUP,
11+
STAGE_DATA,
1112
REQ_TYPE_STANDARD,
1213
REQ_TYPE_CLASS,
1314
)
@@ -43,6 +44,7 @@ def __init__(
4344
self,
4445
report_descriptor,
4546
extra_descriptors=[],
47+
set_report_buf=None,
4648
protocol=_INTERFACE_PROTOCOL_NONE,
4749
interface_str=None,
4850
):
@@ -57,17 +59,34 @@ def __init__(
5759
# descriptors, to append after the mandatory report descriptor. Most
5860
# HID devices do not use these.
5961
#
62+
# - set_report_buf is an optional writable buffer object (i.e.
63+
# bytearray), where SET_REPORT requests from the host can be
64+
# written. Only necessary if the report_descriptor contains Output
65+
# entries. If set, the size must be at least the size of the largest
66+
# Output entry.
67+
#
6068
# - protocol can be set to a specific value as per HID v1.11 section 4.3 Protocols, p9.
6169
#
6270
# - interface_str is an optional string descriptor to associate with the HID USB interface.
6371
super().__init__(_INTERFACE_CLASS, _INTERFACE_SUBCLASS_NONE, protocol, interface_str)
6472
self.extra_descriptors = extra_descriptors
6573
self.report_descriptor = report_descriptor
74+
self._set_report_buf = set_report_buf
6675
self._int_ep = None # set during enumeration
6776

6877
def get_report(self):
6978
return False
7079

80+
def handle_set_report(self, report_data, report_id, report_type):
81+
# Override this function in order to handle SET REPORT requests from the host,
82+
# where it sends data to the HID device.
83+
#
84+
# This function will only be called if the Report descriptor contains at least one Output entry,
85+
# and the set_report_buf argument is provided to the constructor.
86+
#
87+
# Return True to complete the control transfer normally, False to abort it.
88+
return True
89+
7190
def send_report(self, report_data):
7291
# Helper function to send a HID report in the typical USB interrupt
7392
# endpoint associated with a HID interface. return
@@ -115,36 +134,63 @@ def get_hid_descriptor(self):
115134

116135
def handle_interface_control_xfer(self, stage, request):
117136
# Handle standard and class-specific interface control transfers for HID devices.
118-
bmRequestType, bRequest, wValue, _, _ = request
137+
bmRequestType, bRequest, wValue, _, wLength = request
119138

120139
recipient, req_type, _ = split_bmRequestType(bmRequestType)
121140

122-
if stage != STAGE_SETUP:
123-
return True # allow request DATA/ACK stages to complete normally
124-
125-
if req_type == REQ_TYPE_STANDARD:
126-
# HID Spec p48: 7.1 Standard Requests
127-
if bRequest == _REQ_CONTROL_GET_DESCRIPTOR:
128-
desc_type = wValue >> 8
129-
if desc_type == _DESC_HID_TYPE:
130-
return self.get_hid_descriptor()
131-
if desc_type == _DESC_REPORT_TYPE:
132-
return self.report_descriptor
133-
elif req_type == REQ_TYPE_CLASS:
134-
# HID Spec p50: 7.2 Class-Specific Requests
135-
if bRequest == _REQ_CONTROL_GET_REPORT:
136-
return False # Unsupported for now
137-
if bRequest == _REQ_CONTROL_GET_IDLE:
138-
return bytes([self.idle_rate])
139-
if bRequest == _REQ_CONTROL_GET_PROTOCOL:
140-
return bytes([self.protocol])
141-
if bRequest == _REQ_CONTROL_SET_IDLE:
142-
self.idle_rate = wValue >> 8
143-
return b""
144-
if bRequest == _REQ_CONTROL_SET_PROTOCOL:
145-
self.protocol = wValue
146-
return b""
147-
return False # Unsupported
141+
if stage == STAGE_SETUP:
142+
if req_type == REQ_TYPE_STANDARD:
143+
# HID Spec p48: 7.1 Standard Requests
144+
if bRequest == _REQ_CONTROL_GET_DESCRIPTOR:
145+
desc_type = wValue >> 8
146+
if desc_type == _DESC_HID_TYPE:
147+
return self.get_hid_descriptor()
148+
if desc_type == _DESC_REPORT_TYPE:
149+
return self.report_descriptor
150+
elif req_type == REQ_TYPE_CLASS:
151+
# HID Spec p50: 7.2 Class-Specific Requests
152+
if bRequest == _REQ_CONTROL_GET_REPORT:
153+
print("GET_REPORT?")
154+
return False # Unsupported for now
155+
if bRequest == _REQ_CONTROL_GET_IDLE:
156+
return bytes([self.idle_rate])
157+
if bRequest == _REQ_CONTROL_GET_PROTOCOL:
158+
return bytes([self.protocol])
159+
if bRequest == _REQ_CONTROL_SET_IDLE:
160+
self.idle_rate = wValue >> 8
161+
return b""
162+
if bRequest == _REQ_CONTROL_SET_PROTOCOL:
163+
self.protocol = wValue
164+
return b""
165+
if bRequest == _REQ_CONTROL_SET_REPORT:
166+
# Return the _set_report_buf to be filled with the
167+
# report data
168+
if not self._set_report_buf:
169+
return False
170+
elif wLength >= len(self._set_report_buf):
171+
# Saves an allocation if the size is exactly right (or will be a short read)
172+
return self._set_report_buf
173+
else:
174+
# Otherwise, need to wrap the buffer in a memoryview of the correct length
175+
#
176+
# TODO: check this is correct, maybe TinyUSB won't mind if we ask for more
177+
# bytes than the host has offered us.
178+
return memoryview(self._set_report_buf)[:wLength]
179+
return False # Unsupported
180+
181+
if stage == STAGE_DATA:
182+
if req_type == REQ_TYPE_CLASS:
183+
if bRequest == _REQ_CONTROL_SET_REPORT and self._set_report_buf:
184+
report_id = wValue & 0xFF
185+
report_type = wValue >> 8
186+
report_data = self._set_report_buf
187+
if wLength < len(report_data):
188+
# as above, need to truncate the buffer if we read less
189+
# bytes than what was provided
190+
report_data = memoryview(self._set_report_buf)[:wLength]
191+
self.handle_set_report(report_data, report_id, report_type)
192+
193+
return True # allow DATA/ACK stages to complete normally
148194

149195

150196
# Basic 3-button mouse HID Report Descriptor.

0 commit comments

Comments
 (0)