XA//VX

Just some stuff about front-end development

Back

Node.js gamepad driver

Node.js gamepad in action

Some time ago I wanted to play an old-school game and I wanted to use my gamepad, and of course I could not find it. The solution? Create my own gamepad, but with limited hardware related skills that would be a little bit difficult. The next best thing - to use an touch capable device. But it turned out quite quickly that it would not be so easy. It’s not a problem when the HTML5 gamepad controls an HTML5 game on the same server/browser, but what about native games? A driver would be needed for that and my level of expertise in that area was the same as the level in hardware bulding mumbo-jumbo. My experimental “driver” had two main goals: to run on ubuntu and be build in node.js

The following Post is not a tutorial, so I’m not covering the subject from top to bottom, but I’m providing, a great starting point. This should give you some idea how things work and what to expect from such a device. At the end I’m linking to a Node.js application which acts like a device driver. This app is not in any way a production ready solution, but only an experiment, so keep in mind that there are many bugs, and that there is a high posibility that it will not run on your system (I’ve created it and tested only on Ubuntu 13.10)

First idea

First thing I’ve tried was to read some mouse input from the /dev/input/event* device (because reading is always more easier when you heave no idea what to write). Those devices from which we can read (and write) are located in /dev/input directory and are pointing to various (mostly) physical devices.

/dev/input listing

First thing: a way to access the data stream from the mouse, on my system it was the /dev/input/event5 device. In Node.js I’ve could just open and read from the device by using the fs.open and fs.read functions and repeat the process in a loop for a constant data retrival from the device. But there is a much simpler approach - streams. In the most basic and spartan form of the reading the script would look like this:

require('fs').createReadStream('/dev/input/event5').on('data', function( buffer ){ console.log( buffer ) });

The buffer available for the callback contains the following data:

  • tv_sec and tv_usec - time of the event in seconds and microseconds
  • type - there are various types of events, for example events related to a change of absolute or relative position or a button press.
  • code - for example: for which axis this event was called.
  • value - can be the position on an axis or id of a button

For a mouse those values probably will be set as follows:

  • type: 2 (EV_REL) for relative position
  • code: 0 (REL_X) and 1 (REL_Y) for horizontal and vertical axis
  • value: from -N to +N for the difference of previous positon on a given axis

For a joystick:

  • type: 3 (EV_ABS) for the absolute position
  • code: 0 (REL_X) and 1 (REL_Y) for horizontal and vertical axis
  • value: change in the position from -N to +N

Those properties can be obtained by using Buffer methods, as displayed below:

var tv_sec   = buffer.readInt32LE(0),
    tv_usec  = buffer.readInt32LE(8),
    type     = buffer.readUInt16LE(16),
    code     = buffer.readUInt16LE(18),
    value    = buffer.readInt32LE(20);

More info about input events and their structure (and types of those fields) can be found under the following documentation: https://www.kernel.org/doc/Documentation/input/input.txt
http://lxr.free-electrons.com/source/include/linux/input.h

Now, when we know the stucture of the data, we can start to write some stuff to this device, because the structure for writing is exactly the same.

The script that I've used to read the device data can be downloaded here.

/dev/input/event5 output

Writing to the input device

What a suprise, I’ve found my gamepad (actualy, I’ve asked my girlfriend where it is, whenever something goes missing, she knows where it is… not that it is suspicious or anything). Of cource I could stop my research right now and just play the game, but no, the show must go on. So I’ve plugged the joystick in and got the same structure as with the mouse but with different values.

Creating a device

First, we need to open /dev/uinput for reading and writing.

var fd = fs.openSync( '/dev/uinput', 'w+');

Then, we need to write some basic info about the device we want to create.

  • name - name of the device which can be up to 80 characters long
  • id - which contains following the IDs and numbers
  • bustype
  • vendor
  • product
  • version
var name    = "Custom device"; // max 80 chars
var bustype = 0x3; // USB
var vendor  = 0x1337; // Just some random numbers
var product = 0x1010;
var version = 1; 

var buff = new Buffer(1116);
buff.fill(0);
buff.write(name, 0, 80);
buff.writeUInt16LE( bustype, 80 );
buff.writeUInt16LE( vendor , 82 );
buff.writeUInt16LE( product, 84 );
buff.writeUInt16LE( version, 86 );

fs.writeSync( fd, buff, 0, buff.length, null);

But that’s not all. We also need to specify features which our device will support. We can do it by using ioctl (input/output control). By default Node.js doesn’t support ioctl out of the box. But of couse there is a module for that. I’ve used a simplified version of https://github.com/bramp/node-ioctl for this job.

For my joystick I’ve used the following setting. Values for that data can be found in the documentation to which I’ve linked earlier or in the uinput.js file

[[ 'UI_SET_EVBIT' , 'EV_KEY'    ],
 [ 'UI_SET_KEYBIT', 'BTN_LEFT'  ],
 [ 'UI_SET_KEYBIT', 'BTN_RIGHT' ],
 [ 'UI_SET_EVBIT' , 'EV_REL'    ],
 [ 'UI_SET_RELBIT', 'REL_X'     ],
 [ 'UI_SET_RELBIT', 'REL_Y'     ],
 [ 'UI_SET_RELBIT', 'REL_WHEEL' ]].forEach(function( data ){
     ioctl.ioctl( fd, uinput[ data[0] ], uinput[ data[1] ]);
});

The last thing to do is to actually create the device by calling:

ioctl.ioctl(dev_fd, uinput.UI_DEV_CREATE, 0 );

Writing to a device

Writing is the easiest part of the whole process. Like I’ve mentined before, the structure of the data that we are writing is identical with the one we had read from the device before.

var ev = new Buffer(24);
ev.fill(0);

var tv_sec   = Math.round( Date.now() / 1000 ),
    tv_usec  = Math.round( Date.now() % 1000 * 1000 ),
    type     = 0x03, // EV_ABS,
    code     = 0x00, // ABS_X,
    value    = -32767;

ev.writeInt32LE(tv_sec, 0);
ev.writeInt32LE(tv_usec, 8);
ev.writeInt16LE(type, 16);
ev.writeInt16LE(code, 18);
ev.writeInt32LE(value, 20);

var ev_end = new Buffer(24);
ev_end.fill(0);

ev_end.writeInt32LE(tv_sec, 0);
ev_end.writeInt32LE(tv_usec, 8);

fs.writeSync( fd, ev, 0, ev.length, null );
fs.writeSync( fd, ev_end, 0, ev_end.length, null );

Destroying device

var UI_DEV_DESTROY = 21762;
    ioctl.ioctl( dev_fd, UI_DEV_DESTROY, 0);

Frontend client

For the client I’ve prepared a small and dirty application for gamepad-like input controlls. It has a small bonus of being able to emulate a touchpad on touch devices.

Photo of the front-end client simulating a gamepad

The whole application works almost perfectly on a touch screen device - the catch? Without physical feedback from the device, it is quite hard in the beginning to play a game with XYAB buttons (thou the stick is fine, it’s dynamic so the user won’t loose it). Those buttons cannot be moved like the joystick, because there are four of them, and it would be impossible to determine which button the player wanted to press.

Source

All the files can be downloaded under the WTFPL License here.

comments powered by Disqus