logitechReverse engineering is a method of discovering how device works, just by observing how does it communicate, how it was build or which parts it contains. We are not going to disassemble anything, but it could be great fun to observe communication protocol. Of course we have to have proper tools and some time. In this article, I will show how simple reverse engineering could be done. Tested devices are Logitech Extreme 3D Pro and Logiteh Cordless Rumble Pad 2, connected through USB port into Raspberry Pi. Our goal will be to analyze communication protocol, detect frame and conclude how control values are encoded. Logitech Extreme 3D Pro was chosen because it has 6 analog axis and 9 buttons. Raspberry Pi was chosen because of my plans of creating “Robotics lab” :) Of course you can choose one popular windows USB sniffers, like this one: http://www.usblyzer.com/. But for me linux is more challenging!

Before we begin.

Few words from future: I’ve created small C# Mono code. It’s available HERE. it allows you to test if buttons was pressed and read axis values.

Initialisation.

So let’s connect our hardware. There are few ways to find if our joystick is recognized by the system. Firstly, we can search in system messages:

sudo dmesg | grep "usb"

dmesg

Secondly, we can use USB tool that displays information about buses and devices connected to them:

sudo lsusb | grep "Logitech"

lsusb

Ok, it’s clearly seen that device is recognized properly. Let’s check if there is new device file available.

cd /dev/input
ls

System has detected new device as js0. There is an easy way to check if joystick is working correctly. It’s important to do this before we start analyzing communication protocol. To do this, we can use joystick package.

sudo apt-get install joystick
sudo jstest --normal js0

3

Let’s see if we can read data from the device file. There will be plain text frames visible if we are lucky. Many devices – for example bar code scanner or libra operates in character mode.

sudo cat /dev/input/js0

2

As we see in picture above, there is a lot of unreadable data received. Console application interprets this data and tries to convert received bytes to characters. This method will not help us, we need to see what is inside received data.

sudo cat /dev/input/js0 | hexdump

This method will show us hexadecimal data received from device. At this moment, our task is to reverse engineer data frame and see what is inside. There are many challenges.

  • Data can be encrypted – if they are, we have a serious problem. It would be much easier if it is not encrypted.
  • Frame length could be of variable size. If it is we have to find bytes that are responsible for frame detection (statistical analysis is very handy in those cases).

The easiest case is if frame length is fixed. Let’s sniff some bytes and see what can be found.

4

It looks like frame has fixed length. Word 0x0070 is repeating every 8 bytes. For us it’s very good information, we can start testing single buttons. But before we begin, it will be handy to switch to binary view.

sudo cat /dev/input/js0 | xxd -b -c 8

Where -b means that data will be formated in binary, and -c 8 means that there will be 8 octets (bytes) in one data line.

Buttons test.

Let’s test first button which is “Fire button”. At this moment, our task is to hit and release button 2 times and analyze what was received.

button1
We can clearly see changes on BYTE[4]. This could be “Fire button” state. But lets check other buttons. “Thumb Button” – the same test, hit and release two times.

button2
The same bit in BYTE[4] changes again, so it destroys our previous theory. But maybe this is not a “Fire button” flag but only value. If it is, we need to find an address or button number value. Bytes BYTE[0], BYTE[1], BYTE[2] could be what we are looking for, but they change too chaotically. Bytes BYTE[3], BYTE[5], BYTE[6] does not change during tests. Lets look at BYTE[7] and compare it with this from “Fire Button” test – it changes… so maybe it is a button address ? Lets check next button to prove it. “Button 3” – Same test, hit and release two times.

button3
BYTE[7] changed, and because of that I am clearly sure that this is an address value. So, after checking the rest of buttons we know that:

BYTE[4] - Button state value
	0x01 = pressed
	0x00 = released
BYTE[7] - Button address value
	0x00 = "Fire button 1"
	0x01 = "Thumb button 2" 
	0x02 = "Thumb button 3"
	0x03 = "Thumb button 4"
	0x04 = "Thumb button 5"
	0x05 = "Thumb button 6"
	0x06 = "Button 7"
	0x07 = "Button 8"
	0x08 = "Button 9"
	0x09 = "Button 10"
	0x0a = "Button 11"
	0x0b = "Button 12"

Analogs test.

At this point we have buttons detected, lets focus on more difficult part – analogs. First of all, we will check “Throttle Axis” because it’s easiest to test without changing position of any other axis. This test has to contain:

  • Maximum position.
  • Medium position.
  • Minimum position.
  • Fast move.
  • Slow move.

It’s easy to notice that in comparison to previous tests, BYTE[4] and BYTE[5] are changing a lot, so let’s focus on those bytes.

This is a result:

BYTE[4]
	 Max position    = 0b00000001
	 Middle position = 0b00000000
	 Min position    = 0b11111111
BYTE[5]
	 Max position    = 0b10000000
	 Middle position = 0b00000000
	 Min position    = 0b01111111

BYTE[5] looks like U2 encoding, but I have no idea what to do with BYTE[4]… Lets see what jstest did before. It decodes value of throttle as signed short (2 bytes), so what if BYTE[4] is less significant byte of that value ? Let’s check it…

Position| BYTE[5]	  | BYTE[4]	| jstest	| U2 to DEC
Max 	| 0b10000000      | 0b00000001	| -32767	| -32767
Middle 	| 0b00000000      | 0b00000000	| 0		| 0
Min 	| 0b01111111      | 0b11111111	| 32767		| 32767

BINGO :)
This means that BYTE[4] and BYTE[5] are signed short value. What about an address byte: BYTE[7] ? Throttle has 0x03 value here which is the same as “Thumb button 4”. It’s sensible only if there is other flag/address somewhere, so let’s check other bytes. First few bytes changes in same manner as in previous tests – chaotically. But BYTE[6] changed from 0x01 to 0x02. So, maybe it encodes button/axis flag ? Let’s check other analogs and write them down.

Finally, after all tests we know that:

BYTE[3], BYTE[2], BYTE[1], BYTE[0]
	It's only guess. It looks like 32 bit counter. It changes every new frame. 
	It's clearly seen on BYTE[1].
BYTE[4] - Button state value/Axis value least significant byte
	0x01 = pressed
	0x00 = released
BYTE[5] - Axis value most significant byte
BYTE[6] - Button/Axis enabled flag
	0x01 = Button
	0x02 = Axis
BYTE[7] - Button/Axis address value
	0x00 = "Fire button 1"  | "X Axis"
	0x01 = "Thumb button 2" | "Y Axis"
	0x02 = "Thumb button 3" | "Rotate Axis"
	0x03 = "Thumb button 4" | "Throttle Axis"
	0x04 = "Thumb button 5" | "Thumb Axis Y"
	0x05 = "Thumb button 6" | "Thumb Axis X"
	0x06 = "Button 7"
	0x07 = "Button 8"
	0x08 = "Button 9"
	0x09 = "Button 10"
	0x0a = "Button 11"
	0x0b = "Button 12"

What about axis values ? It’s easy to check them just by playing a device and observing values.

Axis			  BYTE[5]    BYTE[4]       signed short
X Left			= 0b10000000 0b00000001 = -32767
X Right			= 0b01111111 0b11111111 =  32767
Y Pull			= 0b01111111 0b11111111 =  32767
Y Push			= 0b10000000 0b00000001 = -32767
Rotate Left		= 0b10000000 0b00000001 = -32767
Rotate Right	        = 0b01111111 0b11111111 =  32767
Throttle Min	 	= 0b01111111 0b11111111 =  32767
Throttle Max	 	= 0b10000000 0b00000001 = -32767
Thumb Y Pull	 	= 0b01111111 0b11111111 =  32767
Thumb Y Push	 	= 0b10000000 0b00000001 = -32767
Thumb X Left 	 	= 0b10000000 0b00000001 = -32767
Thumb X Right	 	= 0b01111111 0b11111111 =  32767

Now, there is easy way to write some Python interpreter and control for example a robot. But, this is good idea for another article :)

Edit 1.

sudo lsusb -d vid:pid -v

This line shows extra info about USB descriptors. Use this next time 😉

lsusbdv

Edit 2.

USB joystick is informing us about his buttons and analogs configuration. Just focus on first 144 bytes received right after connecting to device file. This information could be helpful when experimenting with other joystick models.

How to recognize configuration data ?

MSB of BYTE[7] is set to one.

Red frame: Buttons. Orange frame: Analogs.

firstBytes

Edit 3.

There is C# Mono code available here: http://mpolaczyk.wordpress.com/2013/05/23/raspberry-pi-mono-c-joystick-handler it allows you to test if buttons was pressed and read axis values. It was tested also on Logiteh Cordless Rumble Pad, so I assume that it would work with every joystick/pad :)