Quantcast
Channel: PDC Blogs
Viewing all articles
Browse latest Browse all 26

Accessing USB devices via HID

$
0
0

I briefly covered some USB and HID features related to Plantronics devices in my previous post and now I'd like to actually show you some code on how to do it, not only to Plantronics devices, but to theoretically any device.

 

It will take some reverse engineering, but once you get a grasp of what you're doing it gets easier. Plantronics devices as well as other USB device manufacturers use the HID descriptors as a standard way to inform host devices (such as your PC or slate) of the capabilities of the USB device being plugged to it.

 

Plantronics headsets often use the consumer (0x0c) and the telephony (0x08) pages in order to exchange events and commands with the host device. That is the same way that other vendors also use the same usage pages and usages.

 

Here's more or less where the reverse engineering starts. Although the usages and usage pages are the same, the way the information is presented may differ, since vendors can chose to have the information organized in reports or not.

 

For example, my other reference headset is an older Microsoft LifeChat LX-3000 and the Plantronics headset I'm using for this blog post is a Blackwire 320.

 

Since each vendor may present the usages organized within reports or not, the devices present their HID descriptors to describe how they have the data organized, so the host device knows/learns how to parse the information.

 

Here's the descriptor (taken from the lsusb command in a linux box, trimmed down for clarity) for Microsoft's LX-3000:

Bus 005 Device 003: ID 045e:070f Microsoft Corp.
Device Descriptor:  idVendor           0x045e Microsoft Corp.  idProduct          0x070f  iProduct                1 Microsoft LifeChat LX-3000    Interface Descriptor:      bInterfaceClass         3 Human Interface Device        HID Device Descriptor:          Report Descriptor:            Item(Global): Usage Page, data= [ 0x0c ] 12                  Consumer            Item(Local ): Usage, data= [ 0x01 ] 1                                Consumer Control            Item(Local ): Usage, data= [ 0xe9 ] 233                            Volume Increment            Item(Local ): Usage, data= [ 0xea ] 234                            Volume Decrement            Item(Local ): Usage, data= [ 0xe2 ] 226                            Mute

 

 

And, here's the descriptor for the BW-320:

 

 

Bus 005 Device 002: ID 047f:c01f Plantronics, Inc.
Device Descriptor:  idVendor           0x047f Plantronics, Inc.  idProduct          0xc01f  iProduct                2 Plantronics C320-M    Interface Descriptor:      bInterfaceClass         3 Human Interface Device        HID Device Descriptor:          Report Descriptor:            Item(Global): Usage Page, data= [ 0x0c ] 12                  Consumer            Item(Local ): Usage, data= [ 0x01 ] 1                                Consumer Control            Item(Global): Report ID, data= [ 0x01 ] 1            Item(Local ): Usage, data= [ 0xe9 ] 233                            Volume Increment            Item(Local ): Usage, data= [ 0xea ] 234                            Volume Decrement            Item(Global): Usage Page, data= [ 0x0b ] 11                   Telephony            Item(Local ): Usage, data= [ 0x05 ] 5                                 Headset            Item(Global): Report ID, data= [ 0x08 ] 8            Item(Local ): Usage, data= [ 0x2f ] 47                               Phone Mute            Item(Local ): Usage, data= [ 0x20 ] 32                              Hook Switch            Item(Local ): Usage, data= [ 0x21 ] 33                              Flash

 

 

Note that while Microsoft presents no reports ids within its descriptor and Plantronics organizes the data within reports, the usages presented and the usage pages are the same, and if you look at the USB HID spec for their description, you get a better understanding of what they actually mean.

 

I have omitted the details on the types and size of the data as well as some other usages present in the descriptors. If you run a USB tracer tool such as USBTrace or USBLyzer, you can plug the device and monitor the data exchanged as you press buttons on the headset (or any other USB device for that matter).

 

I also created the python code below to help me in the development phase:

import time
import usb
dev=usb.core.find(idVendor=0x045e)  #vendor id 0x45e = Microsoft; 047f=Plantronics
intf=dev[0][3,0]
ep=intf[0]
if dev.is_kernel_driver_active(intf):    dev.detach_kernel_driver(intf) #libusb organizes the USB devices in configurations, interfaces (with alternates) and endpoints
eps=[] #list of HID endpoints in the device
for c in dev:    for i in c:        if i.bInterfaceClass == 3: #find HID interfaces' endpoints            print vars(i)            eps.append(i[0]) #HACK: grab the first endpoint from this interface
for x in range(500):    time.sleep(.5)    try:        for ep in eps:            d = ep.read(64)            for b in d: print hex(b),            print    except:        pass

 

Once you run the code above (assuming all the dependencies from libusb 1.0 and pyusb are installed), you'll start getting printouts of the events received from the devices when you press buttons.

 

For example, here's the sequence of events received for pressing the following buttons: volume up/volume down/talk/mute, in this order:

LX-3000
volume up:
0x1 0x0 0x0 0x0 0x0 0x0 0x0 0x0
volume down:
0x2 0x0 0x0 0x0 0x0 0x0 0x0 0x0
talk:
0x0 0x1 0x0 0x0 0x0 0x0 0x0 0x0
mute:
0x8 0x4 0x0 0x0
0x8 0x0 0x0 0x0
BW-320
volume up:
0x1 0x1
volume down:
0x1 0x2
talk:
0x8 0x2
mute:
0x8 0x1

 

For the cases where no report ID is present in the HID descriptor, the very first byte is already data, if a report ID is present, the first byte is the report ID itself.

 

The data is presented as a bit-field, starting at the least significant bit. For example, for a descriptor with usages presented as usage 1, u2, u3, etc, the data is organized within the bytes as:

bit 76543210
usage 8u7u6u5u4u3u2u1

 

Now, if we take the volume up and down scenarios, you'll see that the usages align with the bits reported in the data received.

 

Removing the report ID, from the plantronics headset, we're left with exactly the same data, 0x2 for volume down and 0x1 for volume down, which happens to be 00000010 and 00000001 in binary respectively , or usage 2 and usage 1 in the descriptor, or per the HID specs, usages 234 and 233.

 

Ok, so how can you use this all?

 

There are lots of great things that can be built once you have the USB layer built and figured out. If it seems like a lot, it is because it is, and that's exactly why we offer an SDK. If you are the adventurous type and would like to build your own, then you could use the information here to get a jump start.

 

Just as a simple example and to try to make it a bit more fun, I have created a small scenario using a USB toy Missile Launcher.

 

I used the exact same thought process that I used for the headsets to reverse engineer the missile launcher and created my own code to control it in python as well. If you plan to use the same code, make sure you have the right device as there are many different option out there and may not all work the same.

 

My launcher displays the following under lsusb:

Bus 005 Device 004: ID 2123:1010
Device Descriptor:  idVendor           0x2123  idProduct          0x1010  iManufacturer           1 Syntek  iProduct                2 USB Missile Launcher


Here's an abbreviated version of the missile launcher code:

 

class MissileLauncher:    def move_left(self):        self._send_cmd(MissileLauncher.LEFT)    def move_right(self):        self._send_cmd(MissileLauncher.RIGHT)    def move_up(self):        self._send_cmd(MissileLauncher.UP)    def move_down(self):        self._send_cmd(MissileLauncher.DOWN)    def fire(self):        self._send_cmd(MissileLauncher.FIRE)

 

My next step was to decide which buttons from the headset trigger which actions on the toy launcher. I went with the following configuration:

 

Headset ButtonMissile Launcher Action
TalkFire/Shoot
MuteUp/Down
Volume UpRight
Volume DownLeft

 

The reason I assigned the volume up/down to the horizontal rotation of the launcher is that there's a lot more travel horizontally than vertically. Since there were no more buttons to have separate buttons assigned for both directions, I figured I could reuse the mute button for the vertical movement. Once the launcher reaches the limit in one direction, it will revert to the other, for example, if you are pressing mute and it's going up, once it reaches its max height, it'll start to lower until it reaches its lowest position and revert again.

 

And here's how it looks like when I take the headset input and plugin with the missile launcher commands:

 

#### read events
for loop in range(1,500):  data=None  while data == None:    try:      data = ep.read(timeout)      print time.ctime(), "<<<", data    except:      #print sys.exc_info()      continue  print time.ctime(), "here", data[0], data[1]  if data[0] == 0x01:    ml.move_right()    print "right", data  elif data[0] == 0x02:    ml.move_left()    print "left", data  elif data[0] == 0x08:    if ml.limits()&ml.UP or ml.limits()&ml.DOWN:  # check vertical limits      print "bump"      going_up = not going_up  #reverse vertical direction    if going_up:      ml.move_up()      print "up", data    else:      ml.move_down()      print "down", data  elif data[0] == 0x00:    ml.fire()    time.sleep(3)    print "fire", data

 

For simplicity, I'm not properly parsing the data received from the headset based on its HID descriptors, but rather using the raw data. Also, I'm showing here only the code with the LX-3000 headset data, and only minor modifications would be needed to use the code with the Plantronics headset or any other USB HID device that could send some data back to the host machine.

 

And now to make this even more interesting in a geek way, I have the whole code running on a Raspberry Pi with the headset and the launcher plugged in to its USB ports.

 

To run the app, simply invoke it with python and press the headset buttons to see the launcher react to it.

missile_launcher_raspberrypi.png

 

See the video below:

 

Have fun!!

 

Ricardo


Viewing all articles
Browse latest Browse all 26

Trending Articles