4

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
Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
xquitz
  • 41
  • 1
  • 1
  • 3

2 Answers2

7

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 the four libusbhid's I've seen 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.

ulidtko
  • 14,740
  • 10
  • 56
  • 88
  • As a python beginners, do you have a more complete example of this? does this allow the writing of reports with mouse movement? – Miguel Stevens Jul 24 '19 at 12:21
  • 1
    @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](https://eleccelerator.com/tutorial-about-usb-hid-report-descriptors/) — 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. – ulidtko Jul 31 '19 at 10:48
  • -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. – nyholku Jun 01 '20 at 08:58
  • 1
    "Why it works" — do your own research. [Open the spec](https://google-url.com/A6m2n) 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. – ulidtko Aug 17 '20 at 12:35
  • Thank you Ulidtko, can you add an example for read and write? – DrM Jun 23 '21 at 13:48
  • @DrM 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. – ulidtko Jun 23 '21 at 22:40
  • @ulidtko 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. – DrM Jun 24 '21 at 02:53
  • @DrM "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. – ulidtko Jun 24 '21 at 11:48
  • 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. – ulidtko Jun 24 '21 at 11:55
  • @ulidtko 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". – DrM Jun 24 '21 at 13:22
  • @DrM yes, this is the same. On Teensy you do `usb_rawhid_send()`, on host PC you do `hid_get_report()`. – ulidtko Jun 24 '21 at 17:15
  • 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? – DrM Jun 24 '21 at 18:22
  • @ulidtko I got something working, https://stackoverflow.com/a/68122440/4867193 – DrM Jun 24 '21 at 20:36
5

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.

Gary
  • 7,167
  • 3
  • 38
  • 57
jcoppens
  • 5,306
  • 6
  • 27
  • 47
  • 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. – xquitz Jun 29 '16 at 14:26
  • 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. – jcoppens Jun 29 '16 at 21:27
  • 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. – DrM Jun 23 '21 at 13:47