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.
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 MuteAnd, 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 FlashNote 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[3,0] ep=intf 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) #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: passOnce 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 0x1For 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:
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 Button||Missile Launcher Action|
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, data if data == 0x01: ml.move_right() print "right", data elif data == 0x02: ml.move_left() print "left", data elif data == 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 == 0x00: ml.fire() time.sleep(3) print "fire", dataFor 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.
See the video below: