Send HID report with PyUSB
Asked Answered
J

2

6

UPDATE


I managed to send the data properly. For anyone who ran into the same problem, I used the following code:

data=[0x00, 0x04, 0x04, 0xFF, 0xFF, 0xFF, 0x00, 0x00]
result=dev.ctrl_transfer(0x21, 0x9, wValue=0x200, wIndex=0x00, data_or_wLength=data)

(This is based on the answer posted here: link)

But I don't understand in detail, why I have to use

bmRequestType=0x21
bRequest=0x9
wValue=0x200

What is the explanation?


Initial request:


I'm desperately trying to send a simple report to a HID-device using PyUSB.

Using "SimpleHIDwrite" I confirmed that the device works just as expected. I want to send this data:

report ID: 00

data: [00, 04, 04, FF, FF, FF, 00, 00]

Sending data using SimpleHIDwrite

I'm quite new to Python and USB and I can't figure out how to do this using dev.ctrl_transfer or dev.write.

Also, there are some posts about sending data to HID devices, but I couldn't figure out how to solve my problem. How can I fix it?

Here are some more details:

 # Based on https://github.com/walac/pyusb/blob/master/docs/tutorial.rst

import usb.core
import usb.util

# Find our device
# dev = usb.core.find(idVendor=0xfffe, idProduct=0x0001)
dev = usb.core.find(idVendor=0x1781, idProduct=0x8c0)


# Was it found?
if dev is None:
    raise ValueError('Device not found')

dev.set_configuration()

cfg = dev[0]
intf = cfg[(0,0)]
ep = intf[0]

# dev.write(ep.bEndpointAddress, [0x00, 0x00,0x04,0x04,0xFF,0xFF,0xFF,0x00, 0x00], 1000)
# dev.ctrl_transfer(bmRequestType, bRequest, wValue=0, wIndex=0, data_or_wLength=None, timeout=None)

print("print ep")
print(ep)
print("print cfg")
print(cfg)
print("print intf")
print(intf)

And the result of the script above is this:

print ep
      ENDPOINT 0x81: Interrupt IN ==========================
       bLength          :    0x7 (7 bytes)
       bDescriptorType  :    0x5 Endpoint
       bEndpointAddress :   0x81 IN
       bmAttributes     :    0x3 Interrupt
       wMaxPacketSize   :    0x8 (8 bytes)
       bInterval        :    0xa
print cfg
  CONFIGURATION 1: 100 mA ==================================
   bLength              :    0x9 (9 bytes)
   bDescriptorType      :    0x2 Configuration
   wTotalLength         :   0x22 (34 bytes)
   bNumInterfaces       :    0x1
   bConfigurationValue  :    0x1
   iConfiguration       :    0x0
   bmAttributes         :   0x80 Bus Powered
   bMaxPower            :   0x32 (100 mA)
    INTERFACE 0: Human Interface Device ====================
     bLength            :    0x9 (9 bytes)
     bDescriptorType    :    0x4 Interface
     bInterfaceNumber   :    0x0
     bAlternateSetting  :    0x0
     bNumEndpoints      :    0x1
     bInterfaceClass    :    0x3 Human Interface Device
     bInterfaceSubClass :    0x0
     bInterfaceProtocol :    0x0
     iInterface         :    0x0
      ENDPOINT 0x81: Interrupt IN ==========================
       bLength          :    0x7 (7 bytes)
       bDescriptorType  :    0x5 Endpoint
       bEndpointAddress :   0x81 IN
       bmAttributes     :    0x3 Interrupt
       wMaxPacketSize   :    0x8 (8 bytes)
       bInterval        :    0xa
print intf
    INTERFACE 0: Human Interface Device ====================
     bLength            :    0x9 (9 bytes)
     bDescriptorType    :    0x4 Interface
     bInterfaceNumber   :    0x0
     bAlternateSetting  :    0x0
     bNumEndpoints      :    0x1
     bInterfaceClass    :    0x3 Human Interface Device
     bInterfaceSubClass :    0x0
     bInterfaceProtocol :    0x0
     iInterface         :    0x0
      ENDPOINT 0x81: Interrupt IN ==========================
       bLength          :    0x7 (7 bytes)
       bDescriptorType  :    0x5 Endpoint
       bEndpointAddress :   0x81 IN
       bmAttributes     :    0x3 Interrupt
       wMaxPacketSize   :    0x8 (8 bytes)
       bInterval        :    0xa

Process finished with exit code 0
Jacquijacquie answered 21/6, 2016 at 11:58 Comment(0)
H
8

This is all you need to do HID with just PyUSB:

  def hid_set_report(dev, report):
      """ Implements HID SetReport via USB control transfer """
      dev.ctrl_transfer(
          0x21,  # REQUEST_TYPE_CLASS | RECIPIENT_INTERFACE | ENDPOINT_OUT
          9,     # SET_REPORT
          0x200, # "Vendor" Descriptor Type + 0 Descriptor Index
          0,     # USB interface № 0
          report # the HID payload as a byte array -- e.g. from struct.pack()
      )

  def hid_get_report(dev):
      """ Implements HID GetReport via USB control transfer """
      return dev.ctrl_transfer(
          0xA1,  # REQUEST_TYPE_CLASS | RECIPIENT_INTERFACE | ENDPOINT_IN
          1,     # GET_REPORT
          0x200, # "Vendor" Descriptor Type + 0 Descriptor Index
          0,     # USB interface № 0
          64     # max reply size
      )

There isn't any need to jump onto the library-wrappers-around-libraries bandwagon. Are you an engineer or what? Just read the documentation. The protocol is not going to change anytime soon.

Finally, yeah. All four libusbhid's I've read are written in disastrously horrible C and depend on yet even more libraries. For what is essentially 10 lines of code. Make your own decision.


HID 101

From comments... What is this "HID report" you ask?

"HID report" is the payload datum in HID protocol. When you move a mouse or click its buttons, the coordinate deltas + pressed button bitmap get packed into a HID report by a microcontroller inside the mouse — and that report is then sent to the host computer over HID. (The sending happens over USB – or Bluetooth – or BLE – or Zigbee – … whatever is the transport layer that HID layers on top of in any particular use-case.)

HID supports many different devices:

  • gamepads,

  • barcode scanners,

  • keyboards,

    • not only the usual 104-key keyboards — all sorts of keyboards: standalone numpads, joke keyboards, keyboards for "weird" applications:

      tiny 3-key joke keyboard

  • fingerprint scanners,

  • dance arcade pads:

    dance-dance-revolution floor pads in use

  • the snap button in USB-microscopes:

    USB microscope with SNAP button annotated "sends HID report"

  • the touch part of touchscreens,

  • race car simulator steering wheels and pedals,

  • Wii Remote motion-sensor controllers,

  • and so on, and so on, and so forth.

If you think about it, you'll see that to support this huge variety of input devices — there'd better be an abstract thing that carries whatever data the device has to send, yet encapsulates device-specific details. Then, most of HID would treat that thing abstractly and uniformly. That thing is the HID report.

My 2 functions above, hid_get_report() & hid_set_report() do the same: they treat the HID-report abstractly. I don't know what application you have; I can't possibly predict what your HID device can do; therefore, I must treat the hid-report abstractly, simply as an opaque bytearray.

The specific format of that bytearray is described by the HID descriptor. It varies by each particular device. For example, the HID descriptor of a 3-button 1-scrollwheel mouse will say "I've got 2 relative axis, 3 buttons and 1 scrollwheel" with further details mapping out how long its HID-reports are, and which bytes of it carry which input features in what style of encoding.

... If still unclear how to use these 2 functions — refer maybe to this HID tutorial, it goes to good depth explaining HID reports bit-by-bit.

Hekate answered 17/9, 2018 at 13:8 Comment(13)
As a python beginners, do you have a more complete example of this? does this allow the writing of reports with mouse movement?Spontaneity
@Notflip Yes you can do mouse movement too. The report is just a byte array; its format is described in the HID descriptor. See e.g. here — you basically struct.pack the pieces (coords, buttons, report_id) into the report byte array, and send that via hid_set_report(). A good way to approach this: setup Wireshark with USB sniffing, look at real HID reports and try to figure out what each byte means. Then emulate that.Hekate
-1 cause this does not answer the question. The OP clearly manages is already using this method but is asking why it works. And this where this answer fails. It does not explain the parameters, especially the wValue=0x200, which incidentally to me (I'm no expert in this) seems to be wrong in this answer.Sapid
"Why it works" — do your own research. Open the spec and behold the magic of Ctrl-F wValue: the first three hits explain what wValue=0x200 encodes. It's not within my capacity to offer a complete USB lecture here. Neither I'm aiming to sort out the OP's confusion in the basics — notice that they left StackOverflow long ago, leaving behind a nasty mess of a question under a good title which people still find on web search. When answering 2 years after the "question" has been asked, I'm trying to help the latter people, not the OP.Hekate
Thank you Ulidtko, can you add an example for read and write?Ranit
@Ranit read and write — to what sort of device? The content you pass into hid_set_report() and get out of hid_get_report() is completely device-specific. There's a mouse example linked above in the comments. Please reach out in private (my username @ gmail) for further assistance.Hekate
@Hekate I am looking for a simple read and write. I am not sure what report means. But in the hidapi report is a different api from read and write.Ranit
@Ranit "HID report" is the payload datum in HID protocol. When you move a mouse or click its buttons, the coordinate deltas + pressed button bitmap get packed into a HID report by a microcontroller inside the mouse — and that report is then sent to the host computer over HID [over USB]. HID supports many different devices: gamepads, barcode scanners, keyboards, race car steering wheels, so on so forth. In fact, HID itself is agnostic to the specifics of an actual device; that layer of detail in incapsulated into the HID report, and described by the HID descriptor.Hekate
I do show a simple read and write. That's exactly what my answer does. However, to get any use out of it @DrM, you'll need to understand the basics of PyUSB and HID in general. Once you do that, "how to use this code" will become obvious. Hidapi is garbage, in my opinion. But you need to learn the concepts of what you're working with, there's no other way.Hekate
@Hekate Then what are the read() and write() calls in the HID API? Are you saying those are identical to the set and get report? In the Teensy board I am using RawHID.send() and RawHID.recv(). I believe there is a different set of calls for "report".Ranit
@Ranit yes, this is the same. On Teensy you do usb_rawhid_send(), on host PC you do hid_get_report().Hekate
I am using RAWHID.send() and RAWHID.recv(). I'll experiment and see what happens. Next question, from hid.core, set_configuration() fails with device busy. How do I get around that?Ranit
@Hekate I got something working, https://mcmap.net/q/1769776/-read-and-write-hid-device-with-pyusb-not-hidapiRanit
S
6

Don't use PyUSB (unless you need other protocols too). Managing HID isn't difficult, but there is a much easier solution.

HIDAPI is a C-library which manages the protocol, and there is a Python wrapper available too.

Also, it hides the necessity to take control back from the operating system, which recognizes the HID protocol on connection, and install its own driver.

Succinct answered 26/6, 2016 at 20:28 Comment(3)
Thanks! You are right: I only need HID so there is no need to actually use PyUSB. So, I will give hidapi a try. However, I managed it to send the data properly using ctrl_transfer.Jacquijacquie
You can use PyUSB, but it's a lot of extra effort. We've used HIDAPI at the university where I teach, and it makes HID just a formality.Succinct
The python wrapper, for read, returns a list of ints rather than a bytearray. I find that is very inconvenient, not a good design choice in that it is not faithful to what was sent obligates another copy in many scenarios, can result in unnecessary cache misses, and can easily be an issue for large fast transfers. In other words, I think its junk, I am really looking to replace it.Ranit

© 2022 - 2024 — McMap. All rights reserved.