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
.
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.