UNPKG

johnny-five-electron

Version:

Temporary fork to support Electron (to be deprecated)

663 lines (527 loc) 16.5 kB
var Board = require("../lib/board.js"), events = require("events"), util = require("util"); var DEBUG = true; var Devices, Change, Update; // Event type alias map var aliases = { down: ["down", "press", "tap", "impact", "hit"], up: ["up", "release"], hold: ["hold"] }; // all private instances var priv = new Map(); // hold time out for buttons. var holdTimeout = new Map(); // keeps data between cycles and fires change event // if data changes var last = new Map(); /** * Wii * @constructor * * five.Wii({ * device: "RVL-004", * holdtime: ms before firing a hold event on a button, * freq: ms to throttle the read data loop * threshold: difference of change to qualify for a change event * }); * * Available events: * "read" - firehose. * "down", "press", "tap", "impact", "hit" - button press * "up", "release" - button release * "hold" - button hold * * @param {Object} opts [description] * */ function Wii(opts) { if (!(this instanceof Wii)) { return new Wii(opts); } var address, bytes, data, device, delay, setup, preread; Board.Component.call(this, opts); // Derive device definition from Devices device = Devices[opts.device]; address = device.address; bytes = device.bytes; delay = device.delay; data = device.data; setup = device.setup; preread = device.preread; // Wii controller instance properties this.freq = opts.freq || 500; // Button instance properties this.holdtime = opts.holdtime || 500; this.threshold = opts.threshold || 10; // Initialize components device.initialize.call(this); // Set initial "last data" byte array last.set(this, [0, 0, 0, 0, 0, 0, 0]); // Set up I2C data connection this.io.i2cConfig(opts); // Iterate and write each set of setup instructions setup.forEach(function(bytes) { this.io.i2cWrite(address, bytes); }, this); // Unthrottled i2c read request loop setInterval(function() { // Send this command to get all sensor data and store into // the 6-byte register within Wii controller. // This must be execute before reading data from the Wii. // Iterate and write each set of setup instructions preread.forEach(function(bytes) { this.io.i2cWrite(address, bytes); }, this); // Request six bytes of data from the controller this.io.i2cReadOnce(address, bytes, data.bind(this)); // Use the high-frequency data read loop as the change event // emitting loop. This drastically improves change event // frequency and sensitivity // // Emit change events if any delta is greater than // the threshold // RVL-005 does not have a read method at this time. if (typeof device.read !== "undefined") { device.read.call(this); } }.bind(this), delay || this.freq); // Throttled "read" event loop setInterval(function() { var event = Board.Event({ target: this }); // @DEPRECATE this.emit("read", null, event); // The "read" event has been deprecated in // favor of a "data" event. this.emit("data", null, event); }.bind(this), this.freq); } Wii.Components = {}; // A nunchuck button (c or z.) Wii.Components.Button = function(which, controller) { if (!(this instanceof Wii.Components.Button)) { return new Wii.Components.Button(which, controller); } // c or z. this.which = which; // reference to parent controller this.controller = controller; // Set initial values for state tracking priv.set(this, { isDown: false }); Object.defineProperties(this, { // is the button up (not pressed)? isUp: { get: function() { return !priv.get(this).isDown; } }, // is the button pressed? isDown: { get: function() { return priv.get(this).isDown; } } }); }; Wii.Components.Joystick = function(controller) { if (!(this instanceof Wii.Components.Joystick)) { return new Wii.Components.Joystick(controller); } this.controller = controller; var state, accessors; // Initialize empty state object state = {}; // Initialize empty accessors object accessors = {}; // Enumerate Joystick properties ["x", "y", "dx", "dy"].forEach(function(key) { state[key] = 0; // Define accessors for each property in Joystick list accessors[key] = { get: function() { return priv.get(this)[key]; } }; }, this); // Store private state cache priv.set(this, state); // Register newly defined accessors Object.defineProperties(this, accessors); }; Wii.Components.Accelerometer = function(controller) { if (!(this instanceof Wii.Components.Accelerometer)) { return new Wii.Components.Accelerometer(controller); } this.controller = controller; var state, accessors; // Initialize empty state object state = {}; // Initialize empty accessors object accessors = {}; // Enumerate Joystick properties ["x", "y", "z", "dx", "dy", "dz"].forEach(function(key) { state[key] = 0; // Define accessors for each property in Joystick list accessors[key] = { get: function() { return priv.get(this)[key]; } }; }, this); // Store private state cache priv.set(this, state); // Register newly defined accessors Object.defineProperties(this, accessors); }; util.inherits(Wii, events.EventEmitter); util.inherits(Wii.Components.Button, events.EventEmitter); util.inherits(Wii.Components.Joystick, events.EventEmitter); util.inherits(Wii.Components.Accelerometer, events.EventEmitter); // Regular Wiimote driver bytes will be encoded 0x17 function decodeByte(x) { return (x ^ 0x17) + 0x17; } // Change handlers for disparate controller event types // // Note: Change.* methods are |this| sensitive, // therefore, call sites must use: // // Change.button.call( instance, data ); // // Change.component.call( instance, data ); // // Change = { // Fire a "down", "up" or "hold" (and aliases) event // for a button context button: function(key) { // |this| is button context set by calling as: // Change.button.call( button instance, event key ); // // Enumerate all button event aliases, // fire matching types aliases[key].forEach(function(type) { var event = new Board.Event({ // |this| value is a button instance target: this, type: type }); // fire button event on the button itself this.emit(type, null, event); // fire button event on the controller this.controller.emit(type, null, event); }, this); }, // Fire a "change" event on a component context component: function(coordinate) { // |this| is component context set by calling as: // Change.component.call( component instance, coordinate, val ); // ["axischange", "change"].forEach(function(type) { var event; if (this._events && this._events[type]) { event = new Board.Event({ // |this| value is a button instance target: this, type: type, axis: coordinate, // Check dx/dy/dz change to determine direction direction: this["d" + coordinate] < 0 ? -1 : 1 }); // Fire change event on actual component this.emit(type, null, event); // Fire change on controller this.controller.emit(type, null, event); } }, this); } }; // Update handlers for disparate controller event types // // Note: Update.* methods are |this| sensitive, // therefore, call sites must use: // // Update.button.call( button instance, boolean down ); // // Update.component.call( component instance, coordinate, val ); // // Update = { // Set "down" state for button context. button: function(isDown) { // |this| is button context set by calling as: // Update.button.call( button instance, boolean down ); // var state, isFireable; // Derive state from private cache state = priv.get(this); // if this is a state change, mark this // change as fireable. isFireable = false; if (isDown !== state.isDown) { isFireable = true; } state.isDown = isDown; priv.set(this, state); if (isFireable) { // start hold timeout for broadcasting hold. holdTimeout.set(this, setTimeout(function() { if (state.isDown) { Change.button.call(this, "hold"); } }.bind(this), this.controller.holdtime)); Change.button.call(this, isDown ? "down" : "up"); } }, // Set "coordinate value" state for component context. component: function(coordinate, val) { // |this| is component context set by calling as: // Update.component.call( component instance, coordinate, val ); // var state = priv.get(this); state["d" + coordinate] = val - state[coordinate]; state[coordinate] = val; priv.set(this, state); } }; Devices = { // Nunchuk "RVL-004": { address: 0x52, bytes: 6, delay: 100, setup: [ [0x40, 0x00] ], preread: [ [0x00] ], // device.read.call(this); read: function() { var axes = ["x", "y", "z"]; [ this.joystick, this.accelerometer ].forEach(function(component) { axes.forEach(function(axis) { var delta = "d" + axis; if (typeof component[delta] !== "undefined") { if (Math.abs(component[delta]) > this.threshold) { Change.component.call(component, axis); } } }, this); }, this); }, // Call as: // device.initialize.call(this); initialize: function() { this.joystick = new Wii.Components.Joystick(this); this.accelerometer = new Wii.Components.Accelerometer(this); this.c = new Wii.Components.Button("c", this); this.z = new Wii.Components.Button("z", this); }, data: function(data) { // TODO: Shift state management to weakmap, this // should only update an entry in the map // if (data[0] !== 254 && data[1] !== 254 && data[2] !== 254) { // Byte 0x00 : X-axis data of the joystick Update.component.call( this.joystick, "x", decodeByte(data[0]) << 2 ); // Byte 0x01 : Y-axis data of the joystick Update.component.call( this.joystick, "y", decodeByte(data[1]) << 2 ); // Byte 0x02 : X-axis data of the accellerometer sensor Update.component.call( this.accelerometer, "x", decodeByte(data[2]) << 2 ); // Byte 0x03 : Y-axis data of the accellerometer sensor Update.component.call( this.accelerometer, "y", decodeByte(data[3]) << 2 ); // Byte 0x04 : Z-axis data of the accellerometer sensor Update.component.call( this.accelerometer, "z", decodeByte(data[4]) << 2 ); // Update Z button // Grab the first byte of the sixth bit Update.button.call( this.z, (decodeByte(data[5]) & 0x01) === 0 ? true : false ); // Update C button // Grab the second byte of the sixth bit Update.button.call( this.c, (decodeByte(data[5]) & 0x02) === 0 ? true : false ); // Update last data array cache last.set(this, data); } } }, // Classic Controller "RVL-005": { address: 0x52, bytes: 6, delay: 100, setup: [ [0x40, 0x00] ], preread: [ [0x00] ], // read: function( this ) { // var axes = [ "x", "y", "z" ]; // [ this.joystick.left, this.joystick.right ].forEach(function( component ) { // axes.forEach( function( axis ) { // var delta = "d" + axis; // if ( typeof component[ delta ] !== "undefined" ) { // if ( Math.abs( component[ delta ] ) > this.threshold ) { // Change.component.call( component, axis ); // } // } // }, this ); // }, this ); // }, initialize: function() { this.joystick = { left: new Wii.Components.Joystick(this), right: new Wii.Components.Joystick(this) }; // obj.direction_pad = new Wii.DirectionPad( obj ); [ "y", "x", "up", "down", "left", "right", "a", "b", "l", "r", "zl", "zr", "start", "home", "select" ].forEach(function(id) { this[id] = new Wii.Components.Button(id, this); }, this); }, data: function(data) { // TODO: Shift state management to weakmap, this // should only update an entry in the map // // console.log("data read"); if (data[0] !== 254 && data[1] !== 254 && data[2] !== 254) { // Update.button.call( // this.l, // ( decodeByte( data[4] ) & 0x05 ) === 0 ? true : false // ); // console.log("L:"+( decodeByte( data[4] ) & (1 << 5) ) === 0 ? true : false); // LEFT/RIGHT Update.button.call( this.l, (decodeByte(data[4]) & 0x20) === 0 ? true : false ); Update.button.call( this.r, (decodeByte(data[4]) & 0x02) === 0 ? true : false ); // Direction Update.button.call( this.up, (decodeByte(data[5]) & 0x01) === 0 ? true : false ); Update.button.call( this.left, (decodeByte(data[5]) & 0x02) === 0 ? true : false ); Update.button.call( this.down, (decodeByte(data[4]) & 0x40) === 0 ? true : false ); Update.button.call( this.right, (decodeByte(data[4]) & 0x80) === 0 ? true : false ); // Z* Update.button.call( this.zr, (decodeByte(data[5]) & 0x04) === 0 ? true : false ); Update.button.call( this.zl, (decodeByte(data[5]) & 0x80) === 0 ? true : false ); // X/Y Update.button.call( this.x, (decodeByte(data[5]) & 0x08) === 0 ? true : false ); Update.button.call( this.y, (decodeByte(data[5]) & 0x20) === 0 ? true : false ); // A/B Update.button.call( this.a, (decodeByte(data[5]) & 0x10) === 0 ? true : false ); Update.button.call( this.b, (decodeByte(data[5]) & 0x40) === 0 ? true : false ); // MENU Update.button.call( this.select, (decodeByte(data[4]) & 0x10) === 0 ? true : false ); Update.button.call( this.start, (decodeByte(data[4]) & 0x04) === 0 ? true : false ); Update.button.call( this.home, (decodeByte(data[4]) & 0x08) === 0 ? true : false ); /// debugger to parse out keycodes. if (DEBUG) { // var leftX = ( decodeByte( data[1] ) & 0x0f ); // var leftX = ( decodeByte( data[1] ) & 0x0f ); // console.log("--------------------"); // console.log(data.join(",")); // console.log("--------------------"); // for (var b = 3; b < 6; b++) { // for (var c = 0; c <= 255; c++) { // var t = ( decodeByte( data[b] ) & c ) === 0 ? true : false; // if (t) // console.log(b+">"+c+":"); // } // } // console.log("--------------------"); // ( pressedRowBit( decodeByte( data[0] ), 5 )); } Update.component.call( this.joystick.left, "x", decodeByte(data[0]) & 0x3f ); // console.log("X"+decodeByte( data[0] ) << 2); // Byte 0x01 : Y-axis data of the joystick Update.component.call( this.joystick.left, "y", decodeByte(data[0]) & 0x3f ); Update.component.call( this.joystick.right, "x", ((data[0] & 0xc0) >> 3) + ((data[1] & 0xc0) >> 5) + ((data[2] & 0x80) >> 7) ); Update.component.call( this.joystick.right, "y", data[2] & 0x1f ); // Update last data array cache last.set(this, data); } } } }; Wii.Nunchuk = function(opts) { return new Wii({ freq: opts && "freq" in opts ? opts.freq : 100, device: "RVL-004" }); }; Wii.Classic = function(opts) { return new Wii({ freq: opts && "freq" in opts ? opts.freq : 100, device: "RVL-005" }); }; module.exports = Wii;