Skip to content

Commit 0161906

Browse files
committed
Add linux joystick article
1 parent 561be11 commit 0161906

3 files changed

Lines changed: 214 additions & 0 deletions

File tree

articles/linux-joystick.json

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
{
2+
"author": "authors/creationix",
3+
"title": "Linux Joysticks and Other System Devices in Node",
4+
"date": "Feb 7, 2012",
5+
"tags": ["linux", "low-level", "games"],
6+
"nodeVersion": "0.6.10"
7+
}

articles/linux-joystick.markdown

Lines changed: 207 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,207 @@
1+
Everyone knows that NodeJS can be used to make uber fast webservers. But did
2+
you know that it's good for low level tasks too? In this article we'll write a
3+
joystick driver for linux. Technically it's not that low level, the kernel is
4+
handling the hard bits for us, but the results are still very cool.
5+
6+
Those who have seen my past experiments with SDL and OpenGL in node know that I
7+
love to give demos where I hook up a usb gamepad to a node server and do
8+
something cool with it. The problem was that I needed C++ bindings to libSDL to
9+
be able to talk to the gamepad.
10+
11+
It turns out I was wrong and (on Linux systems at least) it's trivial to read
12+
directly from the system device file and parse the protocol.
13+
14+
## Reading the Device
15+
16+
One nice thing about Unix systems is that *everything* is a file. Folders are
17+
files. Processes are represented as files. And USB gamepads are represented as
18+
files!. So to test this theory, I ran `cat` on `/dev/input/js0` which is the
19+
representation of my first joystick. I then moved the joystick around and stuff
20+
got emitted.
21+
22+
![/dev/input/js0](/linux-joystick/js0.png)
23+
24+
Hot dog, We're in business! Now if only there was a document that explained
25+
what all that binary gobbly goop meant.
26+
27+
## Parsing the Output
28+
29+
After some brief searching online, I discovered that this is the linux input
30+
joystick api documented at <http://www.kernel.org/doc/Documentation/input/joystick-api.txt>.
31+
32+
In particular, the section about the format of the binary stuff that was getting
33+
emitted by the file when I moved the joystick says that I need to:
34+
35+
struct js_event e;
36+
read (fd, &e, sizeof(struct js_event));
37+
38+
where js_event is defined as
39+
40+
struct js_event {
41+
__u32 time; /* event timestamp in milliseconds */
42+
__s16 value; /* value */
43+
__u8 type; /* event type */
44+
__u8 number; /* axis/button number */
45+
};
46+
47+
Clearly this is meant for C programmers, but using this information from a node
48+
program isn't hard. We have to calculate the `sizeof(struct js_event)` by hand.
49+
It's 8 bytes. And we don't want to use a blocking read, but luckilly a
50+
non-blocking read works fine too.
51+
52+
Let's write a small program that constantly reads 8 byte chunks from the file.
53+
54+
#@git://github.com/nodebits/linux-joystick.git#read-loop.js,3-15
55+
56+
Running that and moving the joystick around gives me somewhat structured data:
57+
58+
event <Buffer 10 60 f8 1a c3 f6 02 00>
59+
event <Buffer 48 60 f8 1a 00 00 02 00>
60+
event <Buffer 10 62 f8 1a 01 00 01 01>
61+
event <Buffer 40 62 f8 1a 01 00 01 00>
62+
63+
I know from the kernel documentation the the first four bytes are a timestamp.
64+
I can see from the output that it's little endian (the first byte changes very
65+
fast, the last doesn't change at all). From the node docs, I see that we need
66+
[Buffer.readUInt32LE][].
67+
68+
The next two bytes are the value as a signed 16 bit integer. For this we need
69+
[Buffer.readInt16LE][]. I assume the same endianess for everything else. It's
70+
rarely mixed within a single struct.
71+
72+
Then the last two values are regular unsigned 8 bit integers. I can use
73+
[Buffer.readInt8][] or just use the normal `[]` access that buffers always
74+
provided.
75+
76+
Updating the example, we add the following parse function:
77+
78+
#@git://github.com/nodebits/linux-joystick.git#read-loop2.js,3-10
79+
80+
Which outputs lines like:
81+
82+
{ time: 453074028, value: -9797, type: 2, number: 0 }
83+
84+
## Making is Developer Friendly
85+
86+
Ok, so we've gone from ram binary blobs to some nice integers in a json object.
87+
But we can do better. For example, the value is a 16 bit signed integer. A
88+
float from -1 to 1 would probably make more sense to a game developer. Also,
89+
what does type 2 mean anyway? Going back to the kernel docs, we read that the
90+
possible values of `type` are:
91+
92+
#define JS_EVENT_BUTTON 0x01 /* button pressed/released */
93+
#define JS_EVENT_AXIS 0x02 /* joystick moved */
94+
#define JS_EVENT_INIT 0x80 /* initial state of device */
95+
96+
> As mentioned above, the driver will issue synthetic `JS_EVENT_INIT` ORed
97+
> events on open. That is, if it's issuing a `INIT BUTTON` event, the
98+
> current type value will be
99+
100+
int type = JS_EVENT_BUTTON | JS_EVENT_INIT; /* 0x81 */
101+
102+
So to make things easier on the user, we can parse out this information as well
103+
and set the string `button` or `axis` for type. Also we'll add a `init`
104+
property if that bit is set.
105+
106+
With these changes the new parse function looks like:
107+
108+
#@git://github.com/nodebits/linux-joystick.git#joystick.js,4-15
109+
110+
## Objectifying the Code
111+
112+
The other problem with out code is it's a nested mess and makes some inflexible
113+
assumptions like which joystick to open and throws on all errors. We can create
114+
a Joystick constructor class that is reusable.
115+
116+
#@git://github.com/nodebits/linux-joystick.git#joystick.js,18-27
117+
118+
This constructor inherits from `EventEmitter` and is thus an emitter itself. I
119+
wanted errors to be routed to an `error` event instead of littering all my
120+
callbacks. The wrap function seen here is a small utility to both bind the
121+
method to this instance and route the error parameter to the `error` event.
122+
123+
#@git://github.com/nodebits/linux-joystick.git#joystick.js,30-37
124+
125+
With this framework in place, implementing `onOpen` is very straightforward:
126+
127+
#@git://github.com/nodebits/linux-joystick.git#joystick.js,39-42
128+
129+
Once the file is open and we have a valid file descriptor, all that's left is
130+
the recursive read loop. It's implemented as:
131+
132+
#@git://github.com/nodebits/linux-joystick.git#joystick.js,44-53
133+
134+
Remember that the `onRead` and `onOpen` functions in the prototype are wrapped
135+
and bound to the instance. That's why I'm able to use them directly in place of
136+
the callback. This is an example of how smart use of the language can make
137+
async callbacks not ugly.
138+
139+
All that's left is to provide a way to eventually close this resource. So we'll
140+
add a simple close function.
141+
142+
#@git://github.com/nodebits/linux-joystick.git#joystick.js,55-58
143+
144+
## Using the API
145+
146+
Now that we have this nice shiny API, how is it used? Quite simply:
147+
148+
#@git://github.com/nodebits/linux-joystick.git#joystick.js,63-65
149+
150+
When run on my local machine, I get the following output:
151+
152+
$ node joystick.js
153+
{ time: 454750604,
154+
value: 0,
155+
number: 0,
156+
init: true,
157+
type: 'button',
158+
id: 0 }
159+
{ time: 454750608,
160+
value: 0,
161+
number: 1,
162+
init: true,
163+
type: 'button',
164+
id: 0 }
165+
{ time: 454750612,
166+
value: 0,
167+
number: 0,
168+
init: true,
169+
type: 'axis',
170+
id: 0 }
171+
{ time: 454750612,
172+
value: 0,
173+
number: 1,
174+
init: true,
175+
type: 'axis',
176+
id: 0 }
177+
{ time: 454752448, value: 337, number: 0, type: 'axis', id: 0 }
178+
{ time: 454752456, value: 1689, number: 0, type: 'axis', id: 0 }
179+
{ time: 454752464, value: 2364, number: 0, type: 'axis', id: 0 }
180+
{ time: 454752480, value: 3715, number: 0, type: 'axis', id: 0 }
181+
{ time: 454752488, value: 4391, number: 0, type: 'axis', id: 0 }
182+
{ time: 454752496, value: 5067, number: 0, type: 'axis', id: 0 }
183+
...
184+
185+
## Going on From Here
186+
187+
There are many places you can go on from here. I will note that this code
188+
probably won't run on your web server unless you happen to have an USB gamepad
189+
or joystick plugged into it. It will however run on your Linux desktop or
190+
laptop. Often the acceleromoter in a laptop is exposed as a joystick in linux.
191+
192+
Also this isn't limited to joysticks. Any special device on your system is open
193+
to being read from node. No special binary addons are required. Just read up
194+
on the documentation of the protocol and implement it in javascript. Ony of my
195+
first node projects was implementing the PostgreSQL wire protocol in pure JS.
196+
I was able to query my database without using any C++.
197+
198+
The world is wide open with possibilities. Don't feel limited by your lack of
199+
ability or desire to program in C++. A great many things can be done in pure
200+
JavaScript. NodeJS provides an amazing amount of system primitives used to
201+
write many types of software.
202+
203+
204+
[Buffer.readUInt32LE]: http://nodemanual.org/latest/nodejs_ref_guide/buffer.html#Buffer.readUInt32LE
205+
[Buffer.readInt16LE]: http://nodemanual.org/latest/nodejs_ref_guide/buffer.html#Buffer.readInt16LE
206+
[Buffer.readInt8]: http://nodemanual.org/latest/nodejs_ref_guide/buffer.html#Buffer.readInt8
207+

resources/linux-joystick/js0.png

31.3 KB
Loading

0 commit comments

Comments
 (0)