Search code examples
pythonendpointpyusb

How to write to Control Endpoint with PyUSB


I have a USB device for which the following code

import usb.core
import usb.util

device = usb.core.find(idVendor=0xC251, idProduct=0x2201)
print(device)

produces

DEVICE ID c251:2201 on Bus 002 Address 020 =================
 bLength                :   0x12 (18 bytes)
 bDescriptorType        :    0x1 Device
 bcdUSB                 :  0x200 USB 2.0
 bDeviceClass           :    0x0 Specified at interface
 bDeviceSubClass        :    0x0
 bDeviceProtocol        :    0x0
 bMaxPacketSize0        :   0x40 (64 bytes)
 idVendor               : 0xc251
 idProduct              : 0x2201
 bcdDevice              :  0x100 Device 1.0
 iManufacturer          :    0x1 LASER Driver 
 iProduct               :    0x2 LASER Driver IJS
 iSerialNumber          :    0x3 0001A0000000
 bNumConfigurations     :    0x1
  CONFIGURATION 1: 100 mA ==================================
   bLength              :    0x9 (9 bytes)
   bDescriptorType      :    0x2 Configuration
   wTotalLength         :   0x22 (34 bytes)
   bNumInterfaces       :    0x1
   bConfigurationValue  :    0x1
   iConfiguration       :    0x0 
   bmAttributes         :   0xc0 Self 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         :    0x4 HID
      ENDPOINT 0x81: Interrupt IN ==========================
       bLength          :    0x7 (7 bytes)
       bDescriptorType  :    0x5 Endpoint
       bEndpointAddress :   0x81 IN
       bmAttributes     :    0x3 Interrupt
       wMaxPacketSize   :   0x40 (64 bytes)
       bInterval        :    0x1

in Ubuntu 20.04 under Python 3. As can be seen there is no OUT endpoint. I am not an expert in USB but as far as I know we need an out endpoint to send data to the device, so this device looks like a read-only device.

However, I know that there is some way of sending/writing data to the device because it is a laser controller and from Windows I can turn on/off the laser, change the intensity, etc. I have part of the C++ source code of this controller for Windows which uses hidapi. According to the documentation of hid_write it writes to the "Control Endpoint" when there is no other out endpoint, which seems to be the case here. So now I want to replicate this from Python using PyUSB.

So far I have this

import usb.core
import usb.util
import array

device = usb.core.find(idVendor=0xC251, idProduct=0x2201)

if device is None:
    raise RuntimeError('Device not found')
interface = device[0].interfaces()[0]
endpoint = device[0].interfaces()[0].endpoints()[0] # This is the in endpoint, I can read the status of the laser from here and it works fine.

if device.is_kernel_driver_active(interface.bInterfaceNumber):
    device.detach_kernel_driver(interface.bInterfaceNumber)

cmd = chr(90) # This is the command to turn off the laser.
packet = chr(0) + cmd + chr(0)*(64-len(cmd)-1) # The first byte has to be always 0, see https://codedocs.xyz/GerryFerdinandus/hidapi/group__API.html#gad14ea48e440cf5066df87cc6488493af
packet = array.array('B', [ord(c) for c in packet])
bytes_sent = endpoint.write(packet)
print(bytes_sent) # This prints out 64 so it is fine.

which seems to be writing but the laser does nothing (it should turn off). I suspect it is somehow writing into the "IN endpoint" and not into the "Control Endpoint". I would like to send this packet to the control endpoint. How can this be done?

PD: I have also tried

device.write(0x0, packet)

but this produces ValueError: Invalid endpoint address 0x0.


Solution

  • To write into endpoint 0, you'll need the device.ctrl_transfer(bmRequestType, bmRequest, wValue, wIndex, packet) instead of endpoint.write(packet).

    The bmRequestType, bmRequest, wValue and wIndex correspond to the same elements in the USB control request. The fact that the Windows software uses hidapi suggests the control transfers are done according to the USB HID specification.

    This answer here on Stack Overflow describes how to make USB HID set/get operations on plain PyUSB.

    But since the source code you're porting uses hidapi, using the Python hidapi interface might make the process more straightforward. This question has an example of using hidapi in Python, and the answers also talk about alternatives.