micropython-lib/micropython/usb/usb-device-hid/usb/device/hid.py

233 wiersze
8.7 KiB
Python

# MicroPython USB hid module
#
# This implements a base HIDInterface class that can be used directly,
# or subclassed into more specific HID interface types.
#
# MIT license; Copyright (c) 2023 Angus Gratton
from micropython import const
import machine
import struct
import time
from .core import Interface, Descriptor, split_bmRequestType
_EP_IN_FLAG = const(1 << 7)
# Control transfer stages
_STAGE_IDLE = const(0)
_STAGE_SETUP = const(1)
_STAGE_DATA = const(2)
_STAGE_ACK = const(3)
# Request types
_REQ_TYPE_STANDARD = const(0x0)
_REQ_TYPE_CLASS = const(0x1)
_REQ_TYPE_VENDOR = const(0x2)
_REQ_TYPE_RESERVED = const(0x3)
# Descriptor types
_DESC_HID_TYPE = const(0x21)
_DESC_REPORT_TYPE = const(0x22)
_DESC_PHYSICAL_TYPE = const(0x23)
# Interface and protocol identifiers
_INTERFACE_CLASS = const(0x03)
_INTERFACE_SUBCLASS_NONE = const(0x00)
_INTERFACE_SUBCLASS_BOOT = const(0x01)
_INTERFACE_PROTOCOL_NONE = const(0x00)
_INTERFACE_PROTOCOL_KEYBOARD = const(0x01)
_INTERFACE_PROTOCOL_MOUSE = const(0x02)
# bRequest values for HID control requests
_REQ_CONTROL_GET_REPORT = const(0x01)
_REQ_CONTROL_GET_IDLE = const(0x02)
_REQ_CONTROL_GET_PROTOCOL = const(0x03)
_REQ_CONTROL_GET_DESCRIPTOR = const(0x06)
_REQ_CONTROL_SET_REPORT = const(0x09)
_REQ_CONTROL_SET_IDLE = const(0x0A)
_REQ_CONTROL_SET_PROTOCOL = const(0x0B)
# Standard descriptor lengths
_STD_DESC_INTERFACE_LEN = const(9)
_STD_DESC_ENDPOINT_LEN = const(7)
class HIDInterface(Interface):
# Abstract base class to implement a USB device HID interface in Python.
def __init__(
self,
report_descriptor,
extra_descriptors=[],
set_report_buf=None,
protocol=_INTERFACE_PROTOCOL_NONE,
interface_str=None,
):
# Construct a new HID interface.
#
# - report_descriptor is the only mandatory argument, which is the binary
# data consisting of the HID Report Descriptor. See Device Class
# Definition for Human Interface Devices (HID) v1.11 section 6.2.2 Report
# Descriptor, p23.
#
# - extra_descriptors is an optional argument holding additional HID
# descriptors, to append after the mandatory report descriptor. Most
# HID devices do not use these.
#
# - set_report_buf is an optional writable buffer object (i.e.
# bytearray), where SET_REPORT requests from the host can be
# written. Only necessary if the report_descriptor contains Output
# entries. If set, the size must be at least the size of the largest
# Output entry.
#
# - protocol can be set to a specific value as per HID v1.11 section 4.3 Protocols, p9.
#
# - interface_str is an optional string descriptor to associate with the HID USB interface.
super().__init__()
self.report_descriptor = report_descriptor
self.extra_descriptors = extra_descriptors
self._set_report_buf = set_report_buf
self.protocol = protocol
self.interface_str = interface_str
self._int_ep = None # set during enumeration
def get_report(self):
return False
def on_set_report(self, report_data, report_id, report_type):
# Override this function in order to handle SET REPORT requests from the host,
# where it sends data to the HID device.
#
# This function will only be called if the Report descriptor contains at least one Output entry,
# and the set_report_buf argument is provided to the constructor.
#
# Return True to complete the control transfer normally, False to abort it.
return True
def busy(self):
# Returns True if the interrupt endpoint is busy (i.e. existing transfer is pending)
return self.is_open() and self.xfer_pending(self._int_ep)
def send_report(self, report_data, timeout_ms=100):
# Helper function to send a HID report in the typical USB interrupt
# endpoint associated with a HID interface.
#
# Returns True if successful, False if HID device is not active or timeout
# is reached without being able to queue the report for sending.
deadline = time.ticks_add(time.ticks_ms(), timeout_ms)
while self.busy():
if time.ticks_diff(deadline, time.ticks_ms()) <= 0:
return False
machine.idle()
if not self.is_open():
return False
self.submit_xfer(self._int_ep, report_data)
def desc_cfg(self, desc, itf_num, ep_num, strs):
# Add the standard interface descriptor
desc.interface(
itf_num,
1,
_INTERFACE_CLASS,
_INTERFACE_SUBCLASS_NONE,
self.protocol,
len(strs) if self.interface_str else 0,
)
if self.interface_str:
strs.append(self.interface_str)
# As per HID v1.11 section 7.1 Standard Requests, return the contents of
# the standard HID descriptor before the associated endpoint descriptor.
self.get_hid_descriptor(desc)
# Add the typical single USB interrupt endpoint descriptor associated
# with a HID interface.
self._int_ep = ep_num | _EP_IN_FLAG
desc.endpoint(self._int_ep, "interrupt", 8, 8)
self.idle_rate = 0
self.protocol = 0
def num_eps(self):
return 1
def get_hid_descriptor(self, desc=None):
# Append a full USB HID descriptor from the object's report descriptor
# and optional additional descriptors.
#
# See HID Specification Version 1.1, Section 6.2.1 HID Descriptor p22
l = 9 + 3 * len(self.extra_descriptors) # total length
if desc is None:
desc = Descriptor(bytearray(l))
desc.pack(
"<BBHBBBH",
l, # bLength
_DESC_HID_TYPE, # bDescriptorType
0x111, # bcdHID
0, # bCountryCode
len(self.extra_descriptors) + 1, # bNumDescriptors
0x22, # bDescriptorType, Report
len(self.report_descriptor), # wDescriptorLength, Report
)
# Fill in any additional descriptor type/length pairs
#
# TODO: unclear if this functionality is ever used, may be easier to not
# support in base class
for dt, dd in self.extra_descriptors:
desc.pack("<BH", dt, len(dd))
return desc.b
def on_interface_control_xfer(self, stage, request):
# Handle standard and class-specific interface control transfers for HID devices.
bmRequestType, bRequest, wValue, _, wLength = struct.unpack("BBHHH", request)
recipient, req_type, _ = split_bmRequestType(bmRequestType)
if stage == _STAGE_SETUP:
if req_type == _REQ_TYPE_STANDARD:
# HID Spec p48: 7.1 Standard Requests
if bRequest == _REQ_CONTROL_GET_DESCRIPTOR:
desc_type = wValue >> 8
if desc_type == _DESC_HID_TYPE:
return self.get_hid_descriptor()
if desc_type == _DESC_REPORT_TYPE:
return self.report_descriptor
elif req_type == _REQ_TYPE_CLASS:
# HID Spec p50: 7.2 Class-Specific Requests
if bRequest == _REQ_CONTROL_GET_REPORT:
print("GET_REPORT?")
return False # Unsupported for now
if bRequest == _REQ_CONTROL_GET_IDLE:
return bytes([self.idle_rate])
if bRequest == _REQ_CONTROL_GET_PROTOCOL:
return bytes([self.protocol])
if bRequest in (_REQ_CONTROL_SET_IDLE, _REQ_CONTROL_SET_PROTOCOL):
return True
if bRequest == _REQ_CONTROL_SET_REPORT:
return self._set_report_buf # If None, request will stall
return False # Unsupported request
if stage == _STAGE_ACK:
if req_type == _REQ_TYPE_CLASS:
if bRequest == _REQ_CONTROL_SET_IDLE:
self.idle_rate = wValue >> 8
elif bRequest == _REQ_CONTROL_SET_PROTOCOL:
self.protocol = wValue
elif bRequest == _REQ_CONTROL_SET_REPORT:
report_id = wValue & 0xFF
report_type = wValue >> 8
report_data = self._set_report_buf
if wLength < len(report_data):
# need to truncate the response in the callback if we got less bytes
# than allowed for in the buffer
report_data = memoryview(self._set_report_buf)[:wLength]
self.on_set_report(report_data, report_id, report_type)
return True # allow DATA/ACK stages to complete normally