1 # Copyright 2014 The Chromium Authors. All rights reserved.
2 # Use of this source code is governed by a BSD-style license that can be
3 # found in the LICENSE file.
5 """Human Interface Device gadget module.
7 This gadget emulates a USB Human Interface Device. Multiple logical components
8 of a device can be composed together as separate "features" where each has its
9 own Report ID and will be called upon to answer get/set input/output/feature
10 report requests as necessary.
17 import composite_gadget
20 import usb_descriptors
23 class HidCompositeFeature(composite_gadget
.CompositeFeature
):
24 """Generic HID feature for a composite device.
27 def __init__(self
, report_desc
, features
,
28 packet_size
=64, interval_ms
=10, interface_number
=0,
30 in_endpoint
=0x81, out_endpoint
=0x01):
31 """Create a composite device feature implementing the HID protocol.
34 report_desc: HID report descriptor.
35 features: Map between Report IDs and HidFeature objects to handle them.
36 packet_size: Maximum interrupt packet size.
37 interval_ms: Interrupt transfer interval in milliseconds.
38 interface_number: Interface number for this feature (default 0).
39 in_endpoint: Endpoint number for the IN endpoint (defualt 0x81).
40 out_endpoint: Endpoint number for the OUT endpoint or None to disable
41 the endpoint (default 0x01).
44 ValueError: If any of the parameters are out of range.
46 fs_interface_desc
= usb_descriptors
.InterfaceDescriptor(
47 bInterfaceNumber
=interface_number
,
48 bInterfaceClass
=usb_constants
.DeviceClass
.HID
,
49 bInterfaceSubClass
=0, # Non-bootable.
50 bInterfaceProtocol
=0, # None.
51 iInterface
=interface_string
,
54 hs_interface_desc
= usb_descriptors
.InterfaceDescriptor(
55 bInterfaceNumber
=interface_number
,
56 bInterfaceClass
=usb_constants
.DeviceClass
.HID
,
57 bInterfaceSubClass
=0, # Non-bootable.
58 bInterfaceProtocol
=0, # None.
59 iInterface
=interface_string
,
62 hid_desc
= usb_descriptors
.HidDescriptor()
63 hid_desc
.AddDescriptor(hid_constants
.DescriptorType
.REPORT
,
65 fs_interface_desc
.Add(hid_desc
)
66 hs_interface_desc
.Add(hid_desc
)
68 fs_interval
= math
.ceil(math
.log(interval_ms
, 2)) + 1
69 if fs_interval
< 1 or fs_interval
> 16:
70 raise ValueError('Full speed interval out of range: {} ({} ms)'
71 .format(fs_interval
, interval_ms
))
73 fs_interface_desc
.AddEndpoint(usb_descriptors
.EndpointDescriptor(
74 bEndpointAddress
=in_endpoint
,
75 bmAttributes
=usb_constants
.TransferType
.INTERRUPT
,
76 wMaxPacketSize
=packet_size
,
80 hs_interval
= math
.ceil(math
.log(interval_ms
, 2)) + 4
81 if hs_interval
< 1 or hs_interval
> 16:
82 raise ValueError('High speed interval out of range: {} ({} ms)'
83 .format(hs_interval
, interval_ms
))
85 hs_interface_desc
.AddEndpoint(usb_descriptors
.EndpointDescriptor(
86 bEndpointAddress
=in_endpoint
,
87 bmAttributes
=usb_constants
.TransferType
.INTERRUPT
,
88 wMaxPacketSize
=packet_size
,
92 if out_endpoint
is not None:
93 fs_interface_desc
.AddEndpoint(usb_descriptors
.EndpointDescriptor(
94 bEndpointAddress
=out_endpoint
,
95 bmAttributes
=usb_constants
.TransferType
.INTERRUPT
,
96 wMaxPacketSize
=packet_size
,
99 hs_interface_desc
.AddEndpoint(usb_descriptors
.EndpointDescriptor(
100 bEndpointAddress
=out_endpoint
,
101 bmAttributes
=usb_constants
.TransferType
.INTERRUPT
,
102 wMaxPacketSize
=packet_size
,
103 bInterval
=hs_interval
106 super(HidCompositeFeature
, self
).__init
__(
107 [fs_interface_desc
], [hs_interface_desc
])
108 self
._report
_desc
= report_desc
109 self
._features
= features
110 self
._interface
_number
= interface_number
111 self
._in
_endpoint
= in_endpoint
112 self
._out
_endpoint
= out_endpoint
114 def Connected(self
, gadget
):
115 super(HidCompositeFeature
, self
).Connected(gadget
)
116 for report_id
, feature
in self
._features
.iteritems():
117 feature
.Connected(self
, report_id
)
119 def Disconnected(self
):
120 super(HidCompositeFeature
, self
).Disconnected()
121 for feature
in self
._features
.itervalues():
122 feature
.Disconnected()
124 def StandardControlRead(self
, recipient
, request
, value
, index
, length
):
125 if recipient
== usb_constants
.Recipient
.INTERFACE
:
126 if index
== self
._interface
_number
:
127 desc_type
= value
>> 8
128 desc_index
= value
& 0xff
129 if desc_type
== hid_constants
.DescriptorType
.REPORT
:
131 return self
._report
_desc
[:length
]
133 return super(HidCompositeFeature
, self
).StandardControlRead(
134 recipient
, request
, value
, index
, length
)
136 def ClassControlRead(self
, recipient
, request
, value
, index
, length
):
137 """Handle class-specific control requests.
139 See Device Class Definition for Human Interface Devices (HID) Version 1.11
143 recipient: Request recipient (device, interface, endpoint, etc.)
144 request: bRequest field of the setup packet.
145 value: wValue field of the setup packet.
146 index: wIndex field of the setup packet.
147 length: Maximum amount of data the host expects the device to return.
150 A buffer to return to the USB host with len <= length on success or
151 None to stall the pipe.
153 if recipient
!= usb_constants
.Recipient
.INTERFACE
:
155 if index
!= self
._interface
_number
:
158 if request
== hid_constants
.Request
.GET_REPORT
:
159 report_type
, report_id
= value
>> 8, value
& 0xFF
160 print ('GetReport(type={}, id={}, length={})'
161 .format(report_type
, report_id
, length
))
162 return self
.GetReport(report_type
, report_id
, length
)
164 def ClassControlWrite(self
, recipient
, request
, value
, index
, data
):
165 """Handle class-specific control requests.
167 See Device Class Definition for Human Interface Devices (HID) Version 1.11
171 recipient: Request recipient (device, interface, endpoint, etc.)
172 request: bRequest field of the setup packet.
173 value: wValue field of the setup packet.
174 index: wIndex field of the setup packet.
175 data: Data stage of the request.
178 True on success, None to stall the pipe.
180 if recipient
!= usb_constants
.Recipient
.INTERFACE
:
182 if index
!= self
._interface
_number
:
185 if request
== hid_constants
.Request
.SET_REPORT
:
186 report_type
, report_id
= value
>> 8, value
& 0xFF
187 print('SetReport(type={}, id={}, length={})'
188 .format(report_type
, report_id
, len(data
)))
189 return self
.SetReport(report_type
, report_id
, data
)
190 elif request
== hid_constants
.Request
.SET_IDLE
:
191 duration
, report_id
= value
>> 8, value
& 0xFF
192 print('SetIdle(duration={}, report={})'
193 .format(duration
, report_id
))
196 def GetReport(self
, report_type
, report_id
, length
):
197 """Handle GET_REPORT requests.
199 See Device Class Definition for Human Interface Devices (HID) Version 1.11
203 report_type: Requested report type.
204 report_id: Requested report ID.
205 length: Maximum amount of data the host expects the device to return.
208 A buffer to return to the USB host with len <= length on success or
209 None to stall the pipe.
211 feature
= self
._features
.get(report_id
, None)
215 if report_type
== hid_constants
.ReportType
.INPUT
:
216 return feature
.GetInputReport()[:length
]
217 elif report_type
== hid_constants
.ReportType
.OUTPUT
:
218 return feature
.GetOutputReport()[:length
]
219 elif report_type
== hid_constants
.ReportType
.FEATURE
:
220 return feature
.GetFeatureReport()[:length
]
222 def SetReport(self
, report_type
, report_id
, data
):
223 """Handle SET_REPORT requests.
225 See Device Class Definition for Human Interface Devices (HID) Version 1.11
229 report_type: Report type.
230 report_id: Report ID.
234 True on success, None to stall the pipe.
236 feature
= self
._features
.get(report_id
, None)
240 if report_type
== hid_constants
.ReportType
.INPUT
:
241 return feature
.SetInputReport(data
)
242 elif report_type
== hid_constants
.ReportType
.OUTPUT
:
243 return feature
.SetOutputReport(data
)
244 elif report_type
== hid_constants
.ReportType
.FEATURE
:
245 return feature
.SetFeatureReport(data
)
247 def SendReport(self
, report_id
, data
):
248 """Send a HID report.
250 See Device Class Definition for Human Interface Devices (HID) Version 1.11
254 report_id: Report ID associated with the data.
255 data: Contents of the report.
258 self
.SendPacket(self
._in
_endpoint
, data
)
260 self
.SendPacket(self
._in
_endpoint
, struct
.pack('B', report_id
) + data
)
262 def ReceivePacket(self
, endpoint
, data
):
263 """Dispatch a report to the appropriate feature.
265 See Device Class Definition for Human Interface Devices (HID) Version 1.11
269 endpoint: Incoming endpoint (must be the Interrupt OUT pipe).
270 data: Interrupt packet data.
272 assert endpoint
== self
._out
_endpoint
274 if 0 in self
._features
:
275 self
._features
[0].SetOutputReport(data
)
277 report_id
, = struct
.unpack('B', data
[0])
278 feature
= self
._features
.get(report_id
, None)
279 if feature
is None or feature
.SetOutputReport(data
[1:]) is None:
280 self
.HaltEndpoint(endpoint
)
283 class HidFeature(object):
284 """Represents a component of a HID gadget.
286 A "feature" produces and consumes reports with a particular Report ID. For
287 example a keyboard, mouse or vendor specific functionality.
292 self
._report
_id
= None
294 def Connected(self
, my_gadget
, report_id
):
295 self
._gadget
= my_gadget
296 self
._report
_id
= report_id
298 def Disconnected(self
):
300 self
._report
_id
= None
302 def IsConnected(self
):
303 return self
._gadget
is not None
305 def SendReport(self
, data
):
306 """Send a report with this feature's Report ID.
309 data: Report to send. If necessary the Report ID will be added.
312 RuntimeError: If a report cannot be sent at this time.
314 if not self
.IsConnected():
315 raise RuntimeError('Device is not connected.')
316 self
._gadget
.SendReport(self
._report
_id
, data
)
318 def SetInputReport(self
, data
):
319 """Handle an input report sent from the host.
321 This function is called when a SET_REPORT(input) command for this class's
322 Report ID is received. It should be overridden by a subclass.
325 data: Contents of the input report.
327 pass # pragma: no cover
329 def SetOutputReport(self
, data
):
330 """Handle an feature report sent from the host.
332 This function is called when a SET_REPORT(output) command or interrupt OUT
333 transfer is received with this class's Report ID. It should be overridden
337 data: Contents of the output report.
339 pass # pragma: no cover
341 def SetFeatureReport(self
, data
):
342 """Handle an feature report sent from the host.
344 This function is called when a SET_REPORT(feature) command for this class's
345 Report ID is received. It should be overridden by a subclass.
348 data: Contents of the feature report.
350 pass # pragma: no cover
352 def GetInputReport(self
):
353 """Handle a input report request from the host.
355 This function is called when a GET_REPORT(input) command for this class's
356 Report ID is received. It should be overridden by a subclass.
359 The input report or None to stall the pipe.
361 pass # pragma: no cover
363 def GetOutputReport(self
):
364 """Handle a output report request from the host.
366 This function is called when a GET_REPORT(output) command for this class's
367 Report ID is received. It should be overridden by a subclass.
370 The output report or None to stall the pipe.
372 pass # pragma: no cover
374 def GetFeatureReport(self
):
375 """Handle a feature report request from the host.
377 This function is called when a GET_REPORT(feature) command for this class's
378 Report ID is received. It should be overridden by a subclass.
381 The feature report or None to stall the pipe.
383 pass # pragma: no cover
385 class HidGadget(composite_gadget
.CompositeGadget
):
386 """Generic HID gadget.
389 def __init__(self
, report_desc
, features
, vendor_id
, product_id
,
390 packet_size
=64, interval_ms
=10, out_endpoint
=True,
391 device_version
=0x0100):
392 """Create a HID gadget.
395 report_desc: HID report descriptor.
396 features: Map between Report IDs and HidFeature objects to handle them.
397 vendor_id: Device Vendor ID.
398 product_id: Device Product ID.
399 packet_size: Maximum interrupt packet size.
400 interval_ms: Interrupt transfer interval in milliseconds.
401 out_endpoint: Should this device have an interrupt OUT endpoint?
402 device_version: Device version number.
405 ValueError: If any of the parameters are out of range.
407 device_desc
= usb_descriptors
.DeviceDescriptor(
409 idProduct
=product_id
,
414 bcdDevice
=device_version
)
421 self
._hid
_feature
= HidCompositeFeature(
422 report_desc
=report_desc
,
424 packet_size
=packet_size
,
425 interval_ms
=interval_ms
,
426 out_endpoint
=out_endpoint
)
428 super(HidGadget
, self
).__init
__(device_desc
, [self
._hid
_feature
])
429 self
.AddStringDescriptor(3, '{:06X}'.format(uuid
.getnode()))
431 def SendReport(self
, report_id
, data
):
432 self
._hid
_feature
.SendReport(report_id
, data
)