|
| 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 | + |
| 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 | + |
0 commit comments