├── samples ├── trendyjoystick.js └── index.html ├── README.md ├── package.json ├── grunt.js ├── LICENSE └── joystick.js /samples/trendyjoystick.js: -------------------------------------------------------------------------------- 1 | var joystick = new (require('joystick'))(0, 3500, 350); 2 | 3 | joystick.on('button', console.log); 4 | joystick.on('axis', console.log); -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # node-joystick 2 | 3 | A node module for reading joystick data based on the work of [Nodebits](http://nodebits.org/linux-joystick). 4 | 5 | ## Example 6 | 7 | ```javascript 8 | // Set a deadzone of +/-3500 (out of +/-32k) and a sensitivty of 350 to reduce signal noise in joystick axis 9 | var joystick = new (require('joystick'))(0, 3500, 350); 10 | joystick.on('button', console.log); 11 | joystick.on('axis', console.log); 12 | ``` -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "joystick", 3 | "description": "Node.js module for reading joystick data under Linux", 4 | "version": "0.1.2", 5 | "homepage": "https://github.com/JayBeavers/node-joystick.git", 6 | "author": { 7 | "name": "Jay Beavers", 8 | "email": "jay@hikinghomeschoolers.org", 9 | "url": "http://jaybeavers.org" 10 | }, 11 | "repository": { 12 | "type": "git", 13 | "url": "https://github.com/JayBeavers/node-joystick.git" 14 | }, 15 | "bugs": { 16 | "url": "https://github.com/jaybeavers/node-joystick/issues" 17 | }, 18 | "licenses": [ 19 | { 20 | "type": "BSD", 21 | "url": "https://github.com/JayBeavers/jaybeavers/node-joystick/blob/master/LICENSE" 22 | } 23 | ], 24 | "main": "joystick", 25 | "engines": { 26 | "node": ">= 0.8.1" 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /grunt.js: -------------------------------------------------------------------------------- 1 | module.exports = function(grunt) { 2 | 3 | // Project configuration. 4 | grunt.initConfig({ 5 | pkg: '', 6 | //test: { 7 | // files: ['test/**/*.js'] 8 | //}, 9 | lint: { 10 | files: ['grunt.js', '*.js', 'test/**/*.js', 'samples/*.js', 'samples/*/*.js'] 11 | }, 12 | watch: { 13 | files: '', 14 | tasks: 'default' 15 | }, 16 | jshint: { 17 | options: { 18 | curly: true, 19 | eqeqeq: true, 20 | immed: true, 21 | latedef: true, 22 | newcap: true, 23 | noarg: true, 24 | sub: true, 25 | undef: true, 26 | boss: true, 27 | eqnull: true, 28 | node: true, 29 | strict: false, 30 | es5: true 31 | }, 32 | globals: { 33 | exports: true, 34 | describe: true, 35 | it: true 36 | } 37 | } 38 | }); 39 | 40 | // Default task. 41 | grunt.registerTask('default', 'lint test'); 42 | 43 | }; -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2012, Jay Beavers 2 | All rights reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without modification, 5 | are permitted provided that the following conditions are met: 6 | 7 | Redistributions of source code must retain the above copyright notice, this list 8 | of conditions and the following disclaimer. 9 | 10 | Redistributions in binary form must reproduce the above copyright notice, this 11 | list of conditions and the following disclaimer in the documentation and/or 12 | other materials provided with the distribution. 13 | 14 | The name of the Jay Beavers may not be used to endorse or promote products 15 | derived from this software without specific prior written permission. 16 | 17 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 18 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 19 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 20 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR 21 | ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 22 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 23 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON 24 | ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 25 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 26 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 27 | -------------------------------------------------------------------------------- /samples/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 |
10 | 11 |
12 | 13 | 14 | 15 | 16 | 62 | 63 | -------------------------------------------------------------------------------- /joystick.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs'); 2 | const events = require('events'); 3 | /* 4 | * id is the file system index of the joystick (e.g. /dev/input/js0 has id '0') 5 | * 6 | * deadzone is the amount of sensitivity at the center of the axis to ignore. 7 | * Axis reads from -32k to +32k and empirical testing on an XBox360 controller 8 | * shows that a good 'dead stick' value is 3500 9 | * Note that this deadzone algorithm assumes that 'center is zero' which is not generally 10 | * the case so you may want to set deadzone === 0 and instead perform some form of 11 | * calibration. 12 | * 13 | * sensitivity is the amount of change in an axis reading before an event will be emitted. 14 | * Empirical testing on an XBox360 controller shows that sensitivity is around 350 to remove 15 | * noise in the data 16 | */ 17 | 18 | module.exports = class Joystick extends events { 19 | constructor (id, deadzone, sensitivity) { 20 | super(); 21 | 22 | const buffer = new Buffer(8); 23 | let fd; 24 | 25 | // Last reading from this axis, used for debouncing events using sensitivty setting 26 | let lastAxisValue = []; 27 | let lastAxisEmittedValue = []; 28 | 29 | const parse = (buffer) => { 30 | const event = { 31 | time : buffer.readUInt32LE(0), 32 | value: buffer.readInt16LE(4), 33 | number: buffer[7] 34 | }; 35 | 36 | const type = buffer[6]; 37 | 38 | if (type & 0x80) { 39 | event.init = true; 40 | } 41 | 42 | if (type & 0x01) { 43 | event.type = 'button'; 44 | } 45 | 46 | if (type & 0x02) { 47 | event.type = 'axis'; 48 | } 49 | 50 | event.id = id; 51 | 52 | return event; 53 | }; 54 | 55 | const startRead = () => { 56 | fs.read(fd, buffer, 0, 8, null, onRead); 57 | }; 58 | 59 | const onOpen = (err, fdOpened) => { 60 | if (err) return this.emit('error', err); 61 | else { 62 | this.emit('ready'); 63 | 64 | fd = fdOpened; 65 | startRead(); 66 | } 67 | }; 68 | 69 | const onRead = (err, bytesRead) => { 70 | if (err) return this.emit('error', err); 71 | const event = parse(buffer); 72 | 73 | let squelch = false; 74 | 75 | if (event.type === 'axis') { 76 | if (sensitivity) { 77 | if (lastAxisValue[event.number] && Math.abs(lastAxisValue[event.number] - event.value) < sensitivity) { 78 | // data squelched due to sensitivity, no self.emit 79 | squelch = true; 80 | } else { 81 | lastAxisValue[event.number] = event.value; 82 | } 83 | } 84 | 85 | if (deadzone && Math.abs(event.value) < deadzone) event.value = 0; 86 | 87 | if (lastAxisEmittedValue[event.number] === event.value) { 88 | squelch = true; 89 | } else { 90 | lastAxisEmittedValue[event.number] = event.value; 91 | } 92 | } 93 | 94 | if (!squelch) this.emit(event.type, event); 95 | if (fd) startRead(); 96 | }; 97 | 98 | this.close = function (callback) { 99 | fs.close(fd, callback); 100 | fd = undefined; 101 | }; 102 | 103 | fs.open('/dev/input/js' + id, 'r', onOpen); 104 | } 105 | }; 106 | --------------------------------------------------------------------------------