pátek 27. září 2013

Using twistedinput

Recently I described basics of asynchronous file reading with Twisted framework. Using this approach is build my twistedinput library. Now I would like describe more this library and show you basic usage.

InputEvent objects

Before we can start using twistedinput, you have to understand of basic building block of this library.

If you read previous article, you should remember, that we simply printed out length of received data. Let me quickly describe these data.

When you press button on your input device, protocol from previous article actually received couple of input_event structures. This structure is defined in linux/input.h header file.

struct input_event {
    struct timeval time;
    __u16 type;
    __u16 code;
    __s32 value;
};

Time is another structure. It is composed of two fields – seconds and microseconds. Both are 64-bit long. Type and code are unsigned 16-bit values. Value field is signed 32-bit value.

Twistedinput knows this structure and has wrapper class InputEvent for it. See the basic usage.

>>> from twistedinput.event import InputEvent 
>>> event = InputEvent.buildInputEvent(1, 2, 3) 
>>> print unicode(event) 
time: 1379719268.6574370, type: 0x0001, code: 0x0002, value: 0x00000003 
>>> event.time.seconds 
1379719268 
>>> event.type 
1 
>>> event.code 
2 
>>> event.value 
3 

Receiving events

Now you are familiar with InputEvent class. Lets write small script, which receives event from your input device. I'm using Genius gamepad with device file located at /dev/input/event15.

from twistedinput.device import EventDevice
from twistedinput.protocol import EventProtocol
from twistedinput.factory import InputEventFactory
from twisted.internet import reactor
import sys

class GamepadProtocol(EventProtocol):

    def eventReceived(self, event):
        print unicode(event)

gamepad = EventDevice(
    GamepadProtocol(InputEventFactory()),
    sys.argv[1])
gamepad.startReading()
reactor.run()

Twistedinput has protocol for receiving events. Is simply receive data from input device and save them in buffer. When it has enough data for creating an event, it use factory (given as first argument) for building it and calls eventReceived method.

When I run this script and press and release buttons 1, 2, 3 and 4 respectively, I get following output

buben@debian:~$ python gamepad.py /dev/input/event15 
time: 1379976614.640730, type: 0x0004, code: 0x0004, value: 0x00090001 
time: 1379976614.640733, type: 0x0001, code: 0x0120, value: 0x00000001 
time: 1379976614.640781, type: 0x0000, code: 0x0000, value: 0x00000000 
time: 1379976614.712725, type: 0x0004, code: 0x0004, value: 0x00090001 
time: 1379976614.712730, type: 0x0001, code: 0x0120, value: 0x00000000 
time: 1379976614.712774, type: 0x0000, code: 0x0000, value: 0x00000000 
time: 1379976614.888736, type: 0x0004, code: 0x0004, value: 0x00090002 
time: 1379976614.888741, type: 0x0001, code: 0x0121, value: 0x00000001 
time: 1379976614.888786, type: 0x0000, code: 0x0000, value: 0x00000000 
time: 1379976614.944742, type: 0x0004, code: 0x0004, value: 0x00090002 
time: 1379976614.944747, type: 0x0001, code: 0x0121, value: 0x00000000 
time: 1379976614.944790, type: 0x0000, code: 0x0000, value: 0x00000000 
time: 1379976615.192746, type: 0x0004, code: 0x0004, value: 0x00090003 
time: 1379976615.192752, type: 0x0001, code: 0x0122, value: 0x00000001 
time: 1379976615.192792, type: 0x0000, code: 0x0000, value: 0x00000000 
time: 1379976615.272742, type: 0x0004, code: 0x0004, value: 0x00090003 
time: 1379976615.272747, type: 0x0001, code: 0x0122, value: 0x00000000 
time: 1379976615.272788, type: 0x0000, code: 0x0000, value: 0x00000000 
time: 1379976615.632773, type: 0x0004, code: 0x0004, value: 0x00090004 
time: 1379976615.632778, type: 0x0001, code: 0x0123, value: 0x00000001 
time: 1379976615.632815, type: 0x0000, code: 0x0000, value: 0x00000000 
time: 1379976615.712743, type: 0x0004, code: 0x0004, value: 0x00090004 
time: 1379976615.712746, type: 0x0001, code: 0x0123, value: 0x00000000 
time: 1379976615.712768, type: 0x0000, code: 0x0000, value: 0x00000000 

There are 24 events in total. Four buttons has been pressed and released. There are three events for each physical event. In other words, when you press a button, three events is generated. Not all buttons generate three events, some generate only two.

Let's despite first three events which belongs to button 1.

time: 1379976614.640730, type: 0x0004, code: 0x0004, value: 0x00090001 
time: 1379976614.640733, type: 0x0001, code: 0x0120, value: 0x00000001 
time: 1379976614.640781, type: 0x0000, code: 0x0000, value: 0x00000000 

First event has type 0x4 and code 0x4. These values corresponds to symbolic names EV_MSC and MSC_SCAN in linux/input.h header file. This sort of events are not important to us for now.

The second one has type 0x1 and code 0x120. Symbolic names are EV_KEY and BTN_TRIGGER. So type telling us that some button has been pressed. Code describing which button has been pressed. And finally value is value of this button. This is the most important event for us.

The last one is synchronization event. It is used as event separator. We can ignore it too.

Using Mapping

Now we can handle events in one eventReceived method. It's fine if you are interested in for few event types. But what if you are writing driver for keyboard. Your eventReceived should be composed from very long list of elif statemets for handling all different key. That's not good idea. In this case event mapping comes for your rescue.

Event mapping maps event into their handle routines. Everything what you have to do is implement only those routines, which you are interested in.

Instance of mapping is second optional argument for EventProtocol. If omnited, you have to override eventReceived method. If you provide mapping, simply implement methods defined by mapping and only those, which are interesting for you. Let's look at following example.

from twistedinput.device import EventDevice
from twistedinput.protocol import EventProtocol
from twistedinput.factory import InputEventFactory
from twistedinput.mapping import GamepadEventMapping
from twisted.internet import reactor
import sys

class GamepadProtocol(EventProtocol):

    def button1(self, event):
        print "button 1:", event.value

    def button2(self, event):
        print "button 2:", event.value

    def button3(self, event):
        print "button 3:", event.value

    def button4(self, event):
        print "button 4:", event.value

    def nonMappedEvent(self, event):
        print "not supported event:", unicode(event)

gamepad = EventDevice(
    GamepadProtocol(
        InputEventFactory(),
        GamepadEventMapping()),
    sys.argv[1])
gamepad.startReading()
reactor.run()

Lets run this script and see its output

buben@debian:~$ python gamepad.py /dev/input/event15 
button 1: 1 
button 1: 0 
button 2: 1 
button 2: 0 
button 3: 1 
button 3: 0 
button 4: 1 
button 4: 0 

This is much better. If you don't implement method defined by mapping, nothing happened. If mapping doesn't map some event, nonMappedEvent method is called with this event as an argument.

č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