Skip to content

Commit d6601ae

Browse files
committed
usbd: HID clean up, add specific examples.
Signed-off-by: Angus Gratton <[email protected]>
1 parent e6bd0c7 commit d6601ae

File tree

6 files changed

+230
-151
lines changed

6 files changed

+230
-151
lines changed

micropython/usbd/__init__.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
from .device import get_usbdevice, USBInterface
2-
from .hid import HIDInterface, MouseInterface
2+
from .hid import HIDInterface
33
from .midi import MIDIInterface, MIDIUSB
44
from .cdc import CDC
55
from . import utils

micropython/usbd/device.py

+7-8
Original file line numberDiff line numberDiff line change
@@ -354,18 +354,17 @@ def _submit_xfer(self, ep_addr, data, done_cb=None):
354354
raise RuntimeError("xfer_pending")
355355

356356
# USBD callback may be called immediately, before Python execution
357-
# continues
358-
self._ep_cbs[ep_addr] = done_cb
359-
360-
if not self._usbd.submit_xfer(ep_addr, data):
361-
self._ep_cbs[ep_addr] = None
362-
raise RuntimeError("submit failed")
357+
# continues, so set it first.
358+
#
359+
# To allow xfer_pending checks to work, store True instead of None.
360+
self._ep_cbs[ep_addr] = done_cb or True
361+
self._usbd.submit_xfer(ep_addr, data)
363362

364363
def _xfer_cb(self, ep_addr, result, xferred_bytes):
365364
# Singleton callback from TinyUSB custom class driver when a transfer completes.
366365
cb = self._ep_cbs.get(ep_addr, None)
367-
if cb:
368-
self._ep_cbs[ep_addr] = None
366+
self._ep_cbs[ep_addr] = None
367+
if callable(cb):
369368
cb(ep_addr, result, xferred_bytes)
370369

371370
def _control_xfer_cb(self, stage, request):

micropython/usbd/hid_keypad.py renamed to micropython/usbd/examples/hid_keypad_example.py

+61-29
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,65 @@
1-
# MicroPython USB keypad module
2-
# MIT license; Copyright (c) 2023 Dave Wickham, Angus Gratton
3-
4-
from .hid import HIDInterface
51
from micropython import const
2+
import time
3+
from usbd import device
4+
from usbd.hid import HIDInterface
65

76
_INTERFACE_PROTOCOL_KEYBOARD = const(0x01)
87

8+
9+
def keypad_example():
10+
ud = device.get_usbdevice()
11+
k = KeypadInterface()
12+
13+
ud.add_interface(k)
14+
ud.reenumerate()
15+
16+
while not k.is_open():
17+
time.sleep_ms(100)
18+
19+
while True:
20+
time.sleep(2)
21+
print("Press NumLock...")
22+
k.send_key("<NumLock>")
23+
time.sleep_ms(100)
24+
k.send_key()
25+
time.sleep(1)
26+
# continue
27+
print("Press ...")
28+
for _ in range(3):
29+
time.sleep(0.1)
30+
k.send_key(".")
31+
time.sleep(0.1)
32+
k.send_key()
33+
print("Starting again...")
34+
35+
36+
class KeypadInterface(HIDInterface):
37+
# Very basic synchronous USB keypad HID interface
38+
39+
def __init__(self):
40+
self.numlock = False
41+
self.set_report_initialised = False
42+
super().__init__(
43+
_KEYPAD_REPORT_DESC,
44+
set_report_buf=bytearray(1),
45+
protocol=_INTERFACE_PROTOCOL_KEYBOARD,
46+
interface_str="MicroPython Keypad",
47+
)
48+
49+
def handle_set_report(self, report_data, _report_id, _report_type):
50+
report = report_data[0]
51+
b = bool(report & 1)
52+
if b != self.numlock:
53+
print("Numlock: ", b)
54+
self.numlock = b
55+
56+
def send_key(self, key=None):
57+
if key is None:
58+
self.send_report(b"\x00")
59+
else:
60+
self.send_report(_key_to_id(key).to_bytes(1, "big"))
61+
62+
963
# See HID Usages and Descriptions 1.4, section 10 Keyboard/Keypad Page (0x07)
1064
#
1165
# This keypad example has a contiguous series of keys (KEYPAD_KEY_IDS) starting
@@ -40,6 +94,8 @@ def _key_to_id(key):
4094
return _KEYPAD_KEY_IDS.index(key) + _KEYPAD_KEY_OFFS
4195

4296

97+
# HID Report descriptor for a numeric keypad
98+
#
4399
# fmt: off
44100
_KEYPAD_REPORT_DESC = bytes(
45101
[
@@ -69,28 +125,4 @@ def _key_to_id(key):
69125
# fmt: on
70126

71127

72-
class KeypadInterface(HIDInterface):
73-
# Very basic synchronous USB keypad HID interface
74-
75-
def __init__(self):
76-
self.numlock = False
77-
self.set_report_initialised = False
78-
super().__init__(
79-
_KEYPAD_REPORT_DESC,
80-
set_report_buf=bytearray(1),
81-
protocol=_INTERFACE_PROTOCOL_KEYBOARD,
82-
interface_str="MicroPython Keypad",
83-
)
84-
85-
def handle_set_report(self, report_data, _report_id, _report_type):
86-
report = report_data[0]
87-
b = bool(report & 1)
88-
if b != self.numlock:
89-
print("Numlock: ", b)
90-
self.numlock = b
91-
92-
def send_key(self, key=None):
93-
if key is None:
94-
self.send_report(b"\x00")
95-
else:
96-
self.send_report(_key_to_id(key).to_bytes(1, "big"))
128+
keypad_example()
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,146 @@
1+
# MicroPython USB HID Mouse example
2+
# MIT license; Copyright (c) 2023 Angus Gratton
3+
from micropython import const
4+
import struct
5+
import time
6+
from usbd import device
7+
from usbd.hid import HIDInterface
8+
9+
_INTERFACE_PROTOCOL_MOUSE = const(0x02)
10+
11+
12+
def mouse_example():
13+
ud = device.get_usbdevice()
14+
15+
m = MouseInterface()
16+
ud.add_interface(m)
17+
ud.reenumerate()
18+
19+
# wait for host to enumerate as a HID device...
20+
while not m.is_open():
21+
time.sleep_ms(100)
22+
23+
time.sleep_ms(2000)
24+
25+
print("Moving...")
26+
m.move_by(-100, 0)
27+
m.move_by(-100, 0)
28+
time.sleep_ms(500)
29+
30+
print("Clicking...")
31+
m.click_right(True)
32+
time.sleep_ms(200)
33+
m.click_right(False)
34+
35+
print("Done!")
36+
37+
38+
class MouseInterface(HIDInterface):
39+
# Very basic example USB mouse HID interface
40+
def __init__(self):
41+
super().__init__(
42+
_MOUSE_REPORT_DESC,
43+
protocol=_INTERFACE_PROTOCOL_MOUSE,
44+
interface_str="MicroPython Mouse",
45+
)
46+
self._l = False # Left button
47+
self._m = False # Middle button
48+
self._r = False # Right button
49+
50+
def send_report(self, dx=0, dy=0):
51+
b = 0
52+
if self._l:
53+
b |= 1 << 0
54+
if self._r:
55+
b |= 1 << 1
56+
if self._m:
57+
b |= 1 << 2
58+
# Note: This allocates the bytes object 'report' each time a report is
59+
# sent.
60+
#
61+
# However, at the moment the base class doesn't keep track of each
62+
# transfer after it's submitted. So reusing a bytearray() creates a risk
63+
# of a race condition if a new report transfer is submitted using the
64+
# same buffer, before the previous one has completed.
65+
report = struct.pack("Bbb", b, dx, dy)
66+
67+
super().send_report(report)
68+
69+
def click_left(self, down=True):
70+
self._l = down
71+
self.send_report()
72+
73+
def click_middle(self, down=True):
74+
self._m = down
75+
self.send_report()
76+
77+
def click_right(self, down=True):
78+
self._r = down
79+
self.send_report()
80+
81+
def move_by(self, dx, dy):
82+
if not -127 <= dx <= 127:
83+
raise ValueError("dx")
84+
if not -127 <= dy <= 127:
85+
raise ValueError("dy")
86+
self.send_report(dx, dy)
87+
88+
89+
# Basic 3-button mouse HID Report Descriptor.
90+
# This is cribbed from Appendix E.10 of the HID v1.11 document.
91+
_MOUSE_REPORT_DESC = bytes(
92+
[
93+
0x05,
94+
0x01, # Usage Page (Generic Desktop)
95+
0x09,
96+
0x02, # Usage (Mouse)
97+
0xA1,
98+
0x01, # Collection (Application)
99+
0x09,
100+
0x01, # Usage (Pointer)
101+
0xA1,
102+
0x00, # Collection (Physical)
103+
0x05,
104+
0x09, # Usage Page (Buttons)
105+
0x19,
106+
0x01, # Usage Minimum (01),
107+
0x29,
108+
0x03, # Usage Maximun (03),
109+
0x15,
110+
0x00, # Logical Minimum (0),
111+
0x25,
112+
0x01, # Logical Maximum (1),
113+
0x95,
114+
0x03, # Report Count (3),
115+
0x75,
116+
0x01, # Report Size (1),
117+
0x81,
118+
0x02, # Input (Data, Variable, Absolute), ;3 button bits
119+
0x95,
120+
0x01, # Report Count (1),
121+
0x75,
122+
0x05, # Report Size (5),
123+
0x81,
124+
0x01, # Input (Constant), ;5 bit padding
125+
0x05,
126+
0x01, # Usage Page (Generic Desktop),
127+
0x09,
128+
0x30, # Usage (X),
129+
0x09,
130+
0x31, # Usage (Y),
131+
0x15,
132+
0x81, # Logical Minimum (-127),
133+
0x25,
134+
0x7F, # Logical Maximum (127),
135+
0x75,
136+
0x08, # Report Size (8),
137+
0x95,
138+
0x02, # Report Count (2),
139+
0x81,
140+
0x06, # Input (Data, Variable, Relative), ;2 position bytes (X & Y)
141+
0xC0, # End Collection,
142+
0xC0, # End Collection
143+
]
144+
)
145+
146+
mouse_example()

0 commit comments

Comments
 (0)