Sunday, March 13, 2011

Logitech Dual Action USB Gamepad Interface Using Python

You can get a list of all input device files on your machine by using the command 'ls /dev/input'. My Logitech gamepad is showing up as 'js0'.


Using the cat command on the gamepad's device file 'cat /dev/input/js0' and then pressing some buttons on the game pad spews out a bunch of garbage in the terminal. This lets me know the gamepad is working but I am going to have to do a bit of programming to make some sense of all this gibberish.


Using python we can open and read the device pipe as if it were a normal file and print it in a slightly more readable format.

import sys

# Open the js0 device as if it were a file in read mode.
pipe = open('/dev/input/js0', 'r')

# Loop forever.
while 1:

    # For each character read from the /dev/input/js0 pipe...
    for char in pipe.read(1):
 
        # write to the standard output the string representation of 'char'.
        sys.stdout.write(repr(char))
  
        # Flush the stdout pipe.
        sys.stdout.flush()


By observing the output of the simple python program I notice that each event on the gamepad (button down, button up, axis movement) produces 8 bytes of data. I then went on to modify the program to assemble the incoming data into eight byte messages and print each message to the screen.

# Open the js0 device as if it were a file in read mode.
pipe = open('/dev/input/js0', 'r')

# Create an empty list to store read characters.
msg = []

# Loop forever.
while 1:

    # For each character read from the /dev/input/js0 pipe...
    for char in pipe.read(1):
 
        # append the integer representation of the unicode character read to the msg list.
        msg += [ord(char)]
 
        # If the length of the msg list is 8...
        if len(msg) == 8:
 
            # Print the msg list.
            print msg
   
            # Reset msg as an empty list.
            msg = []


By observing the data in this format I am able to determine a few things:
  • Bytes 0, 1, 2,and 3 appear to count up as messages stream in. This could possibly be useful to determine the order or time relationship between messages.
  • Bytes 4 and 5 are the value of the message.
  • Byte 6 seems to serves two functions.
    • When the device pipe is first opened we receive messages that indicate how many buttons and how many joystick axis are on the gamepad. If the byte is 129 this represents a button or if it is 130 it represent a joystick axis.
    • After these initial messages if the byte is 1 it represents a button event or if its 2 it represents a axis event.
  • Byte 7 identifies the button or axis number that triggered the message.
Using this information I modified the program again to output messages based on the gamepad events.

# Open the js0 device as if it were a file in read mode.
pipe = open('/dev/input/js0', 'r')

# Create an empty list to store read characters.
msg = []

# Loop forever.
while 1:

    # For each character read from the /dev/input/js0 pipe...
    for char in pipe.read(1):
 
        # append the integer representation of the unicode character read to the msg list.
        msg += [ord(char)]
 
        # If the length of the msg list is 8...
        if len(msg) == 8:
 
         # Button event if 6th byte is 1
            if msg[6] == 1:
                if msg[4]  == 1:
                    print 'button', msg[7], 'down'
                else:
                    print 'button', msg[7], 'up'
            
            # Axis event if 6th byte is 2
            elif msg[6] == 2:
                print 'axis', msg[7], msg[5]
   
            # Reset msg as an empty list.
            msg = []


From here I am just a hop, skip, and a jump away from wrapping this into a class and saving the state of the buttons and axis into some sort of easily used data structures. I will be posting a follow up post when I get this concept into a usable format.

12 comments:

  1. Thanks a bunch , this was really useful for me to control MPD using a gamepad

    ReplyDelete
  2. Great! I am glad to hear that you found this post useful in your project.

    ReplyDelete
  3. cool ! but i want to do it under windows ! what would i use for the device link ?!!! plz help

    ReplyDelete
  4. when are you posting your next steps??

    ReplyDelete
  5. I am wondering how to read from the device on windows, I can't seem to find it's directory. If anybody has figured this out please respond, thanks.

    ReplyDelete
  6. very useful, just a quick note as your example does not use the 4th byte.

    for every 255 in the 4th byte the 5th byte increases by 1 some doing this: ( ( msg[5] * 255) + 255 ) will give your axis a range of 65280 instead of the 255 shown in your example code

    ReplyDelete
    Replies
    1. I think this would be more accurate: `(msg[5] * 256) + msg[4]`

      But well spotted!

      Delete
  7. Thanks man, it works perfectly with DUALSHOCK4!

    ReplyDelete
  8. Useful tips for learning to manually control joypad input with Python. Good learning material for me!

    ReplyDelete
  9. Thanks man.
    All ingenious is simple :)

    ReplyDelete
  10. I want to get steering data from Logitech G27. Is it worked at windows 10 environment?

    ReplyDelete