├── .gitignore ├── .travis.yml ├── res └── logo.png ├── examples ├── list.js ├── joystick.js ├── stream.js └── button.js ├── package.json ├── gamecontroller.js ├── README.md └── lib └── vendors.js /.gitignore: -------------------------------------------------------------------------------- 1 | _ 2 | .DS_Store 3 | node_modules 4 | 5 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - "stable" 4 | -------------------------------------------------------------------------------- /res/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/infusion/node-gamecontroller/HEAD/res/logo.png -------------------------------------------------------------------------------- /examples/list.js: -------------------------------------------------------------------------------- 1 | 2 | var GameController = require('../gamecontroller.js'); 3 | 4 | var dev = GameController.getDevices(); 5 | 6 | console.log(dev); -------------------------------------------------------------------------------- /examples/joystick.js: -------------------------------------------------------------------------------- 1 | 2 | var GameController = require('../gamecontroller.js'); 3 | 4 | var gc = new GameController('ps2'); 5 | 6 | gc.connect(); 7 | 8 | gc.on('JOYL:move', function(o) { 9 | console.log(o); 10 | }); 11 | -------------------------------------------------------------------------------- /examples/stream.js: -------------------------------------------------------------------------------- 1 | 2 | var GameController = require('../gamecontroller.js'); 3 | 4 | var gc = new GameController('ps2'); 5 | 6 | gc.connect(); 7 | 8 | gc.on('data', function(data) { 9 | console.log(data); 10 | }); 11 | 12 | gc.on('error', function(err) { 13 | console.error(err); 14 | }); 15 | -------------------------------------------------------------------------------- /examples/button.js: -------------------------------------------------------------------------------- 1 | 2 | var GameController = require('../gamecontroller.js'); 3 | 4 | var gc = new GameController('ps2'); 5 | 6 | gc.connect(); 7 | 8 | gc.on('X:press', function() { 9 | console.log("X Pressed"); 10 | }); 11 | 12 | gc.on('X:release', function() { 13 | console.log("X Released"); 14 | }); 15 | 16 | gc.on('error', function(err) { 17 | console.error(err); 18 | }); 19 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "gamecontroller", 3 | "homepage": "https://github.com/infusion/node-gamecontroller", 4 | "bugs": "https://github.com/infusion/node-gamecontroller/issues", 5 | "title": "gamecontroller", 6 | "version": "0.0.2", 7 | "description": "A node driver for several gamecontroller", 8 | "keywords": [ 9 | "gamepad", 10 | "gamecontroller", 11 | "playstation", 12 | "xbox", 13 | "snes", 14 | "gaming", 15 | "game", 16 | "controller", 17 | "joystick", 18 | "robotics", 19 | "robot" 20 | ], 21 | "author": "Robert Eisele (https://raw.org/)", 22 | "main": "gamecontroller.js", 23 | "private": false, 24 | "directories": { 25 | "example": "examples" 26 | }, 27 | "readmeFilename": "README.md", 28 | "license": "MIT OR GPL-2.0", 29 | "repository": { 30 | "type": "git", 31 | "url": "git://github.com/infusion/node-gamecontroller.git" 32 | }, 33 | "engines": { 34 | "node": "*" 35 | }, 36 | "dependencies": { 37 | "node-hid": "*" 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /gamecontroller.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @license GameController v0.0.2 09/07/2017 3 | * 4 | * Copyright (c) 2017, Robert Eisele (robert@xarg.org) 5 | * Dual licensed under the MIT or GPL Version 2 licenses. 6 | **/ 7 | 8 | /* 9 | * TODO: 10 | * - multiple controllers (number as 2nd argument) 11 | * - construct from serial number or path 12 | */ 13 | 14 | 15 | const HID = require('node-hid'); 16 | const EventEmitter = require('events').EventEmitter; 17 | const Vendors = require('./lib/vendors.js'); 18 | 19 | const util = require('util'); 20 | 21 | function GameController(type) { 22 | 23 | if (!(this instanceof GameController)) { 24 | return new GameController(type); 25 | } 26 | 27 | this._vendor = Vendors[type]; 28 | 29 | EventEmitter.call(this); 30 | process.on('exit', this.close.bind(this)); 31 | } 32 | 33 | GameController.prototype = { 34 | _hid: null, 35 | _vendor: null, 36 | connect: function(cb) { 37 | let ven = this._vendor; 38 | 39 | try { 40 | this._hid = new HID.HID(ven.vendorId, ven.productId); 41 | } catch (e) { 42 | this.emit('error', e); 43 | return; 44 | } 45 | 46 | let hid = this._hid; 47 | let self = this; 48 | let pass = {x: 0, y: 0}; 49 | 50 | hid.on('data', function(data) { 51 | 52 | let newState = ven.update(data); 53 | let oldState = ven.prev; 54 | 55 | self.emit('data', newState); 56 | 57 | for (let s in newState) { 58 | 59 | let os = oldState[s]; 60 | let ns = newState[s]; 61 | let sp = s.split(":"); 62 | 63 | if (sp[0] === 'axis') { 64 | 65 | let Ykey = sp[0] + ':' + sp[1] + ':Y'; 66 | 67 | if (sp[2] === 'X' && (ns !== os || newState[Ykey] !== oldState[Ykey])) { 68 | pass.x = ns; 69 | pass.y = newState[Ykey]; 70 | self.emit(sp[1] + ':move', pass); 71 | } 72 | } else if (os !== ns) { 73 | 74 | if (sp[0] === 'button') { 75 | if (ns === 1 && os === 0) { 76 | self.emit(sp[1] + ':press'); 77 | } else { 78 | self.emit(sp[1] + ':release'); 79 | } 80 | } 81 | } 82 | oldState[s] = newState[s]; 83 | } 84 | }); 85 | 86 | if (cb instanceof Function) { 87 | cb(); 88 | } 89 | 90 | return this; 91 | }, 92 | close: function() { 93 | if (this._hid) { 94 | this._hid.close(); 95 | this._hid = null; 96 | } 97 | 98 | this.emit('close'); 99 | 100 | return this; 101 | } 102 | }; 103 | 104 | GameController.getDevices = function() { 105 | let dev = HID.devices(); 106 | let ret = []; 107 | 108 | for (let i = 0; i < dev.length; i++) { 109 | 110 | for (let ven in Vendors) { 111 | 112 | if (Vendors[ven].productId === dev[i].productId && Vendors[ven].vendorId === dev[i].vendorId) { 113 | ret.push(ven); 114 | } 115 | } 116 | } 117 | return ret; 118 | }; 119 | 120 | 121 | module.exports = GameController; 122 | 123 | util.inherits(GameController, EventEmitter); 124 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | ![node-gamecontroller](https://github.com/infusion/node-gamecontroller/blob/master/res/logo.png?raw=true "JavaScript Gamecontroller") 3 | 4 | [![NPM Package](https://img.shields.io/npm/v/gamecontroller.svg?style=flat)](https://npmjs.org/package/gamecontroller "View this project on npm") 5 | [![MIT license](http://img.shields.io/badge/license-MIT-brightgreen.svg)](http://opensource.org/licenses/MIT) 6 | 7 | Gamecontroller.js is a small layer on top of HID to interact with any USB game controller, like Sony PlayStation, XBOX, SNES, ... with node.js, depending on a small config for each controller only. 8 | 9 | 10 | 11 | Installation 12 | === 13 | 14 | Installing node-gamecontroller is as easy as cloning this repo or use npmjs: 15 | 16 | ```bash 17 | npm install gamecontroller 18 | ``` 19 | 20 | Usage 21 | === 22 | 23 | Plug in your game controller and run the following code: 24 | 25 | ```js 26 | const Gamecontroller = require('gamecontroller'); 27 | const ctrl = new Gamecontroller('ps2'); 28 | 29 | ctrl.connect(function() { 30 | console.log('Game On!'); 31 | }); 32 | 33 | ctrl.on('X:press', function() { 34 | console.log('X was pressed'); 35 | }); 36 | 37 | ctrl.on('X:release', function() { 38 | console.log('X was released'); 39 | }); 40 | ``` 41 | 42 | To get the full parsed HID data stream, you can run 43 | 44 | ```js 45 | ctrl.on('data', function(data) { 46 | console.log(data); 47 | }); 48 | ``` 49 | 50 | Supported Events 51 | === 52 | 53 | Data 54 | --- 55 | 56 | - `data`- Get parsed data as it comes in 57 | 58 | Buttons 59 | --- 60 | 61 | - `{type}:press` - Button with given type was pressed 62 | - `{type}:release` - Button with given type was released 63 | 64 | Joysticks 65 | --- 66 | 67 | - `{type}:move` - Joystick with given type was moved in either x or y direction. Object with positions gets passed 68 | 69 | Status 70 | --- 71 | 72 | - `{type}:change` - The status of a measure like battery changed 73 | 74 | Misc 75 | --- 76 | 77 | - `error` - An error has occurred 78 | - `close` - The connection was closed successfully 79 | 80 | 81 | Supported Controllers 82 | === 83 | 84 | At the moment, the following controllers are supported: 85 | 86 | - Playstation 2 Ripoff ("ps2") 87 | - XBOX 360 ("xbox360") 88 | - Tomee SNES Controller ("snes-tomee") 89 | - Retrolink SNES Controller ("snes-retrolink") 90 | 91 | If you've connected a supported controller, you can run the following to find the name of it: 92 | 93 | ```js 94 | var Gamecontroller = require('gamecontroller'); 95 | 96 | var dev = Gamecontroller.getDevices(); 97 | 98 | console.log(dev); 99 | ``` 100 | 101 | Adding new controllers 102 | === 103 | 104 | If your controller isn't supported yet, add the config to the `lib/vendor.js` file and send a pull request or file a bug ticket. To get all the information follow the following simple steps. Run the following snippet, locate your controller and note the vendorId and productId: 105 | 106 | ```js 107 | var HID = require('node-hid'); 108 | console.log(HID.devices()); 109 | ``` 110 | 111 | Using the vendorId and productId you can run the following snippet, press all the keys on your controller and get the array position of what key changes what array index: 112 | 113 | ```js 114 | var hid = new HID.HID(vendorId, productId); 115 | hid.on("data", function(data) { 116 | console.log(data); 117 | }); 118 | ``` 119 | 120 | Copyright and licensing 121 | === 122 | Copyright (c) 2017, [Robert Eisele](https://raw.org/) 123 | Dual licensed under the MIT or GPL Version 2 licenses. 124 | -------------------------------------------------------------------------------- /lib/vendors.js: -------------------------------------------------------------------------------- 1 | 2 | module.exports = { 3 | "ps2": { 4 | "vendorId": 3727, 5 | "productId": 3, 6 | "state": { 7 | "button:T": 0, 8 | "button:O": 0, 9 | "button:X": 0, 10 | "button:S": 0, 11 | 12 | "button:L1": 0, 13 | "button:R1": 0, 14 | "button:L2": 0, 15 | "button:R2": 0, 16 | 17 | "axis:LY": 0, 18 | "axis:LX": 0, 19 | "axis:RY": 0, 20 | "axis:RX": 0, 21 | 22 | "button:Up": 0, 23 | "button:Right": 0, 24 | "button:Down": 0, 25 | "button:Left": 0, 26 | 27 | "button:Start": 0, 28 | "button:Select": 0 29 | }, 30 | "prev": {// Simple copy of state 31 | "button:T": 0, 32 | "button:O": 0, 33 | "button:X": 0, 34 | "button:S": 0, 35 | 36 | "button:L1": 0, 37 | "button:R1": 0, 38 | "button:L2": 0, 39 | "button:R2": 0, 40 | 41 | "axis:LY": 0, 42 | "axis:LX": 0, 43 | "axis:RY": 0, 44 | "axis:RX": 0, 45 | 46 | "button:Up": 0, 47 | "button:Right": 0, 48 | "button:Down": 0, 49 | "button:Left": 0, 50 | 51 | "button:Start": 0, 52 | "button:Select": 0 53 | }, 54 | "update": function(data) { 55 | 56 | var state = this.state; 57 | 58 | state['button:T'] = data[5] >> 4 & 1; 59 | state['button:O'] = data[5] >> 5 & 1; 60 | state['button:X'] = data[5] >> 6 & 1; 61 | state['button:S'] = data[5] >> 7 & 1; 62 | 63 | state['button:L1'] = data[6] >> 0 & 1; 64 | state['button:R1'] = data[6] >> 1 & 1; 65 | state['button:L2'] = data[6] >> 2 & 1; 66 | state['button:R2'] = data[6] >> 3 & 1; 67 | 68 | state['axis:JOYL:Y'] = data[3]; 69 | state['axis:JOYL:X'] = data[2]; 70 | state['axis:JOYR:Y'] = data[1]; 71 | state['axis:JOYR:X'] = data[0]; 72 | 73 | state['button:Up'] = data[7] >> 0 & 1; 74 | state['button:Right'] = data[7] >> 1 & 1; 75 | state['button:Down'] = data[7] >> 2 & 1; 76 | state['button:Left'] = data[7] >> 3 & 1; 77 | 78 | state['button:Start'] = data[6] >> 5 & 1; 79 | state['button:Select'] = data[6] >> 4 & 1; 80 | 81 | return state; 82 | }, 83 | "setRumble": function() { 84 | 85 | }, 86 | "setLED": function(led, val) { 87 | 88 | } 89 | }, 90 | "xbox360": { 91 | "vendorId": 1118, 92 | "productId": 654, 93 | "state": { 94 | 95 | }, 96 | "prev": {// Simple copy of state 97 | 98 | }, 99 | "update": function(data) { 100 | 101 | var state = this.state; 102 | 103 | return state; 104 | }, 105 | "setRumble": function() { 106 | 107 | }, 108 | "setLED": function(led, val) { 109 | 110 | } 111 | }, 112 | "xbox360-guitar-xplorer" : { 113 | "vendorId": 5168, 114 | "productId": 18248, 115 | "state": { 116 | // Frets 117 | "button:G": 0, 118 | "button:R": 0, 119 | "button:Y": 0, 120 | "button:B": 0, 121 | "button:O": 0, 122 | 123 | // 8 directional D-pad 124 | "button:N": 0, 125 | "button:NE": 0, 126 | "button:E": 0, 127 | "button:SE": 0, 128 | "button:S": 0, 129 | "button:SW": 0, 130 | "button:W": 0, 131 | "button:NW": 0, 132 | 133 | "button:Start": 0, 134 | "button:Select": 0, 135 | }, 136 | "prev": {// Simple copy of state 137 | // Frets 138 | "button:G": 0, 139 | "button:R": 0, 140 | "button:Y": 0, 141 | "button:B": 0, 142 | "button:O": 0, 143 | 144 | // 8 directional D-pad 145 | "button:N": 0, 146 | "button:NE": 0, 147 | "button:E": 0, 148 | "button:SE": 0, 149 | "button:S": 0, 150 | "button:SW": 0, 151 | "button:W": 0, 152 | "button:NW": 0, 153 | 154 | "button:Start": 0, 155 | "button:Select": 0, 156 | }, 157 | "update": function(data) { 158 | 159 | var state = this.state; 160 | 161 | state['button:G'] = data[6] >> 0 & 1; 162 | state['button:R'] = data[6] >> 1 & 1; 163 | state['button:B'] = data[6] >> 2 & 1; 164 | state['button:Y'] = data[6] >> 3 & 1; 165 | state['button:O'] = data[6] >> 4 & 1; 166 | 167 | data[8] = data[8] % 80; 168 | state['button:N'] = +(data[8] === 17); 169 | state['button:NE'] = +(data[8] === 18); 170 | state['button:E'] = +(data[8] === 19); 171 | state['button:SE'] = +(data[8] === 20); 172 | state['button:S'] = +(data[8] === 21); 173 | state['button:SW'] = +(data[8] === 22); 174 | state['button:W'] = +(data[8] === 23); 175 | state['button:NW'] = +(data[8] === 24); 176 | 177 | state['button:Start'] = data[6] >> 6 & 1; 178 | state['button:Select'] = data[6] >> 7 & 1; 179 | 180 | state['axis:GUITAR:X'] = data[0]; 181 | state['axis:GUITAR:Y'] = data[1]; 182 | state['axis:WHAMMY:X'] = data[2]; 183 | state['axis:WHAMMY:Y'] = data[3]; 184 | 185 | return state; 186 | }, 187 | "setRumble": function() { 188 | 189 | }, 190 | "setLED": function(led, val) { 191 | 192 | } 193 | }, 194 | "snes-tomee": { 195 | "vendorId": 4797, 196 | "productId": 53269, 197 | "state": { 198 | "button:X": 0, 199 | "button:A": 0, 200 | "button:B": 0, 201 | "button:Y": 0, 202 | "button:L": 0, 203 | "button:R": 0, 204 | 205 | "button:Up": 0, 206 | "button:Right": 0, 207 | "button:Down": 0, 208 | "button:Left": 0, 209 | 210 | "button:Start": 0, 211 | "button:Select": 0 212 | }, 213 | "prev": {// Simple copy of state 214 | "button:X": 0, 215 | "button:A": 0, 216 | "button:B": 0, 217 | "button:Y": 0, 218 | "button:L": 0, 219 | "button:R": 0, 220 | 221 | "button:Up": 0, 222 | "button:Right": 0, 223 | "button:Down": 0, 224 | "button:Left": 0, 225 | 226 | "button:Start": 0, 227 | "button:Select": 0 228 | }, 229 | "update": function(data) { 230 | 231 | var state = this.state; 232 | 233 | state["button:X"] = data[3] >> 0 & 1; 234 | state["button:A"] = data[3] >> 1 & 1; 235 | state["button:B"] = data[3] >> 2 & 1; 236 | state["button:Y"] = data[3] >> 3 & 1; 237 | state["button:L"] = data[3] >> 4 & 1; 238 | state["button:R"] = data[3] >> 5 & 1; 239 | 240 | state["button:Left"] = +(data[0] === 0); 241 | state["button:Right"] = +(data[0] === 255); 242 | state["button:Up"] = +(data[1] === 0); 243 | state["button:Down"] = +(data[1] === 255); 244 | 245 | state["button:Start"] = data[4] >> 0 & 1; 246 | state["button:Select"] = data[4] >> 1 & 1; 247 | 248 | return state; 249 | }, 250 | "setRumble": function() { 251 | 252 | }, 253 | "setLED": function(led, val) { 254 | 255 | } 256 | }, 257 | "snes-retrolink": { 258 | "vendorId": 121, 259 | "productId": 17, 260 | "state": { 261 | "button:X": 0, 262 | "button:A": 0, 263 | "button:B": 0, 264 | "button:Y": 0, 265 | "button:L": 0, 266 | "button:R": 0, 267 | 268 | "button:Up": 0, 269 | "button:Right": 0, 270 | "button:Down": 0, 271 | "button:Left": 0, 272 | 273 | "button:Start": 0, 274 | "button:Select": 0 275 | }, 276 | "prev": {// Simple copy of state 277 | "button:X": 0, 278 | "button:A": 0, 279 | "button:B": 0, 280 | "button:Y": 0, 281 | "button:L": 0, 282 | "button:R": 0, 283 | 284 | "button:Up": 0, 285 | "button:Right": 0, 286 | "button:Down": 0, 287 | "button:Left": 0, 288 | 289 | "button:Start": 0, 290 | "button:Select": 0 291 | }, 292 | "update": function(data) { 293 | 294 | var state = this.state; 295 | 296 | state["button:X"] = data[5] === 31; 297 | state["button:A"] = data[5] === 47; 298 | state["button:B"] = data[5] === 79; 299 | state["button:Y"] = data[5] === 143; 300 | state["button:L"] = data[6] >> 0 & 1; 301 | state["button:R"] = data[6] >> 1 & 1; 302 | 303 | state["button:Left"] = +(data[3] === 0); 304 | state["button:Right"] = +(data[3] === 255); 305 | state["button:Up"] = +(data[4] === 0); 306 | state["button:Down"] = +(data[4] === 255); 307 | 308 | state["button:Start"] = data[6] >> 5 & 1; 309 | state["button:Select"] = data[6] >> 4 & 1; 310 | 311 | return state; 312 | }, 313 | "setRumble": function() { 314 | 315 | }, 316 | "setLED": function(led, val) { 317 | 318 | } 319 | } 320 | }; 321 | --------------------------------------------------------------------------------