čtvrtek 19. září 2013

Reading gamepad with Twisted

Recently, I decided to develop library for reading input devices in Python. I like Twisted framework and asynchronous programming, so I used it for implementing my library.

In the past, when I needed to read some files, I just used synchronous approach. Even when I was using asynchronous framework, such as Twisted. The reason is simple, Twisted does not provide convenient API for accessing files asynchronously. If you really need it, you have to program it yourself.

In this article I will describe how to use twisted framework for asynchronous reading from files. In this case it will be special files for devices but you can use same approach for any file you want.

You can find my library, which I called twistedinput, on the GitHub.

Everything is file

If you are familiar with Linux, you probably know, that everything is file. Regular files, folders, pipes, sockets and also devices are represented by a file. For the simplest way of reading data from gamepad, you can use following script:

buben@debian:~$ cat simple_gamepad.py
#!/usr/bin/env python
import sys

# open file
f = open(sys.argv[1])

# read one byte and print it out
while True:
    b = f.read(1)
    print “0x%02x” % ord(b)

First of all, we need locate file representation of gamepad.

buben@debian:~$ cat /proc/bus/input/devices

. . . truncated . . .

I: Bus=0003 Vendor=1345 Product=1000 Version=0110 
N: Name="Generic   USB  Joystick  " 
P: Phys=usb-0000:00:12.0-2/input0 
S: Sysfs=/devices/pci0000:00/0000:00:12.0/usb3/3-2/3-2:1.0/input/input15 
U: Uniq= 
H: Handlers=event15 js0 
B: PROP=0 
B: EV=1b 
B: KEY=fff00000000 0 0 0 0 
B: ABS=30027 
B: MSC=10 

Nice, file name for gamepad is event15, full path to it is /dev/input/event15. If you will run simple_gamepad.py with this path as an argument and press some button on gamepad, you will get lot of output, one byte on each line.

Time for Twisted

Fine, we are reading data from device and printing them out in hexadecimal format. But in synchronous fashion. It's time for basic implementation in Twisted framework.

First of all, we need to define class, which will represent an input device. This device can be basically anything, like mouse, keyboard, webcam, power button or my gamepad. Instance of this class will be able to read and write data from or into a file. It has no idea what these data are.

For interpreting data we have to create another class. Twisted calls these classes as protocols. Protocols receive data from their transport layers and know meaning of these data.

So let's start with device class

buben@debian:~$ cat device.py
from __future__ import unicode_literals
from twisted.internet.abstract import FileDescriptor
from twisted.internet import fdesc

class EventDevice(FileDescriptor):

    __device = None

    def __init__(self, protocol, device):
        FileDescriptor.__init__(self)
        self.protocol = protocol
        self.protocol.makeConnection(self)
        self.__device = open(device)

    def fileno(self):
        return self.__device.fileno()

    def doRead(self):
        return fdesc.readFromFD(self.fileno(), self.protocol.dataReceived)

This is very simple implementation of device class. As you can see, it takes two arguments. A protocol, which is responsible for handling data. And a device, which is only file name of an input device in your system.

There are also two important methods:

  • fileno - this provides a file descriptor. Twisted will need it for calling select system call.
  • doRead - when some data is available, Twisted calls this method for receiving them. This method calls readFromFD, a Twisted utility function which simply reads data from file descriptor and gives them to a callback function - in this case dataReceived in the protocol.

Also note, that EventDevice is derived from the FileDescriptor class. If you are interested in, you can check full documentation for this class.

Okay, lets't write a simple protocol.

buben@debian:~$ cat protocol.py
from __future__ import unicode_literals
from twisted.internet.protocol import Protocol

class EventProtocol(Protocol):

    def dataReceived(self, data):
        print len(data)

When protocol receives chunk of some data, it prints out their length. This isn't anything useful, but it's fine for demonstration purpose.

Now we can put all these things together with the following script:

buben@debian:~$ cat test.py
#!/usr/bin/env python
from __future__ import unicode_literals
import sys
from device import EventDevice
from protocol import EventProtocol
from twisted.internet import reactor

EventDevice(
    EventProtocol(),
    sys.argv[1]).startReading()
reactor.run()

If you run the script and press some button on the gamepad, you will get the following output:

buben@debian:~$ chmod +x test.py 
buben@debian:~$ ./test.py /dev/input/event15 
48
48

The program tells us that it received 48 bytes of data. In asynchronous way!

This is basic asynchronous reading from input devices. For doing something useful, you will need to understood these data. Decoding them isn't topic of this article, maybe later :)

Further reading

Žádné komentáře:

Okomentovat