diff --git a/eg/keypad-MPR121.js b/eg/keypad-MPR121.js new file mode 100644 index 000000000..113b56d02 --- /dev/null +++ b/eg/keypad-MPR121.js @@ -0,0 +1,46 @@ +var exec = require("child_process").exec; +var argv = require("minimist")(process.argv.slice(2)); +var five = require("../"); +var board = new five.Board(); + +board.on("ready", function() { + // MPR121 3x3 Capacitive Touch Pad + var keypad; + + if (argv.show === 1) { + keypad = new five.Keypad({ + controller: "MPR121", + address: 0x5A + }); + } + + if (argv.show === 2) { + keypad = new five.Keypad({ + controller: "MPR121", + address: 0x5A, + keys: [ + ["!", "@", "#"], + ["$", "%", "^"], + ["&", "-", "+"], + ] + }); + } + + if (argv.show === 3) { + keypad = new five.Keypad({ + controller: "MPR121", + address: 0x5A, + keys: ["!", "@", "#", "$", "%", "^", "&", "-", "+"] + }); + } + + ["change", "press", "hold", "release"].forEach(function(event) { + keypad.on(event, function(data) { + // console.log("Event: %s, Which: %s", event, data); + + if (event === "press") { + exec("say " + data); + } + }); + }); +}); diff --git a/eg/keypad-analog-ad.js b/eg/keypad-analog-ad.js new file mode 100644 index 000000000..79e0bdb1f --- /dev/null +++ b/eg/keypad-analog-ad.js @@ -0,0 +1,47 @@ +var exec = require("child_process").exec; +var argv = require("minimist")(process.argv.slice(2)); +var five = require("../"); +var board = new five.Board(); + +board.on("ready", function() { + // WaveShare AD Keypad + var keypad; + + if (argv.show === 1) { + keypad = new five.Keypad({ + pin: "A0", + length: 16 + }); + } + + if (argv.show === 2) { + keypad = new five.Keypad({ + pin: "A0", + keys: [ + ["1", "!", "@", "#"], + ["2", "$", "%", "^"], + ["3", "&", "-", "+"], + ["4", "<", ">", "?"], + ] + }); + } + + if (argv.show === 3) { + keypad = new five.Keypad({ + pin: "A0", + keys: ["1", "!", "@", "#", "2", "$", "%", "^", "3", "&", "-", "+", "4", "<", ">", "?"] + }); + } + + ["change", "press", "hold", "release"].forEach(function(event) { + keypad.on(event, function(data) { + console.log("Event: %s, Which: %s", event, data); + + if (event === "press") { + exec("say " + data); + } + }); + }); +}); + + diff --git a/eg/keypad-analog-vkey.js b/eg/keypad-analog-vkey.js new file mode 100644 index 000000000..d0be0a151 --- /dev/null +++ b/eg/keypad-analog-vkey.js @@ -0,0 +1,49 @@ +var exec = require("child_process").exec; +var argv = require("minimist")(process.argv.slice(2)); +var five = require("../"); +var board = new five.Board(); + +board.on("ready", function() { + // Sparkfun's VKey Voltage Keypad + var keypad; + + if (argv.show === 1) { + keypad = new five.Keypad({ + controller: "VKEY", + pin: "A0", + }); + } + + if (argv.show === 2) { + keypad = new five.Keypad({ + controller: "VKEY", + pin: "A0", + keys: [ + ["!", "@", "#"], + ["$", "%", "^"], + ["&", "-", "+"], + ["<", ">", "?"], + ] + }); + } + + if (argv.show === 3) { + keypad = new five.Keypad({ + controller: "VKEY", + pin: "A0", + keys: ["!", "@", "#", "$", "%", "^", "&", "-", "+", "<", ">", "?"] + }); + } + + ["change", "press", "hold", "release"].forEach(function(event) { + keypad.on(event, function(data) { + console.log("Event: %s, Which: %s", event, data); + + if (event === "press") { + exec("say " + data); + } + }); + }); +}); + + diff --git a/eg/keypad.js b/eg/keypad.js new file mode 100644 index 000000000..180482cf0 --- /dev/null +++ b/eg/keypad.js @@ -0,0 +1,34 @@ +var five = require("../"); +var board = new five.Board(); + +board.on("ready", function() { + var vkey = new five.Keypad("A0"); + + // TODO: digital 10 pin keypad + // var dkey = new five.Keypad({ + // // Digital Pins must be mapped to + // // appropriate keypad number. + // // pin => num/char + // pins: { + // // ? + // } + // }); + + var ikey = new five.Keypad({ + controller: "MPR121", + address: 0x5A + }); + + var pads = { + vkey: vkey, + ikey: ikey, + }; + + Object.keys(pads).forEach(function(key) { + ["change", "press", "hold", "release"].forEach(function(event) { + pads[key].on(event, function(data) { + console.log("Pad: %s, Event: %s, Which: %s", key, event, data); + }); + }); + }); +}); diff --git a/lib/definitions/mpr121.js b/lib/definitions/mpr121.js new file mode 100644 index 000000000..7ecd06d2e --- /dev/null +++ b/lib/definitions/mpr121.js @@ -0,0 +1,158 @@ +// MPR121 Register Defines +module.exports = { + KEY_MAP: { + 8: 1, + 5: 2, + 2: 3, + 7: 4, + 4: 5, + 1: 6, + 6: 7, + 3: 8, + 0: 9 + }, + MPR121_DEFAULT_ADDRESS: 0x5A, + + // MPR121 Registers (from data sheet) + ELE0_ELE7_TOUCH_STATUS: 0x00, + ELE8_ELE11_ELEPROX_TOUCH_STATUS: 0x01, + + ELE0_7_OOR_STATUS: 0x02, + ELE8_11_ELEPROX_OOR_STATUS: 0x03, + + ELE0_FILTERED_DATA_LSB: 0x04, + ELE0_FILTERED_DATA_MSB: 0x05, + ELE1_FILTERED_DATA_LSB: 0x06, + ELE1_FILTERED_DATA_MSB: 0x07, + ELE2_FILTERED_DATA_LSB: 0x08, + ELE2_FILTERED_DATA_MSB: 0x09, + ELE3_FILTERED_DATA_LSB: 0x0A, + ELE3_FILTERED_DATA_MSB: 0x0B, + ELE4_FILTERED_DATA_LSB: 0x0C, + ELE4_FILTERED_DATA_MSB: 0x0D, + ELE5_FILTERED_DATA_LSB: 0x0E, + ELE5_FILTERED_DATA_MSB: 0x0F, + ELE6_FILTERED_DATA_LSB: 0x10, + ELE6_FILTERED_DATA_MSB: 0x11, + ELE7_FILTERED_DATA_LSB: 0x12, + ELE7_FILTERED_DATA_MSB: 0x13, + ELE8_FILTERED_DATA_LSB: 0x14, + ELE8_FILTERED_DATA_MSB: 0x15, + ELE9_FILTERED_DATA_LSB: 0x16, + ELE9_FILTERED_DATA_MSB: 0x17, + ELE10_FILTERED_DATA_LSB: 0x18, + ELE10_FILTERED_DATA_MSB: 0x19, + ELE11_FILTERED_DATA_LSB: 0x1A, + ELE11_FILTERED_DATA_MSB: 0x1B, + ELEPROX_FILTERED_DATA_LSB: 0x1C, + ELEPROX_FILTERED_DATA_MSB: 0x1D, + + ELE0_BASELINE_VALUE: 0x1E, + ELE1_BASELINE_VALUE: 0x1F, + ELE2_BASELINE_VALUE: 0x20, + ELE3_BASELINE_VALUE: 0x21, + ELE4_BASELINE_VALUE: 0x22, + ELE5_BASELINE_VALUE: 0x23, + ELE6_BASELINE_VALUE: 0x24, + ELE7_BASELINE_VALUE: 0x25, + ELE8_BASELINE_VALUE: 0x26, + ELE9_BASELINE_VALUE: 0x27, + ELE10_BASELINE_VALUE: 0x28, + ELE11_BASELINE_VALUE: 0x29, + ELEPROX_BASELINE_VALUE: 0x2A, + + MHD_RISING: 0x2B, + NHD_AMOUNT_RISING: 0x2C, + NCL_RISING: 0x2D, + FDL_RISING: 0x2E, + MHD_FALLING: 0x2F, + NHD_AMOUNT_FALLING: 0x30, + NCL_FALLING: 0x31, + FDL_FALLING: 0x32, + NHD_AMOUNT_TOUCHED: 0x33, + NCL_TOUCHED: 0x34, + FDL_TOUCHED: 0x35, + ELEPROX_MHD_RISING: 0x36, + ELEPROX_NHD_AMOUNT_RISING: 0x37, + ELEPROX_NCL_RISING: 0x38, + ELEPROX_FDL_RISING: 0x39, + ELEPROX_MHD_FALLING: 0x3A, + ELEPROX_NHD_AMOUNT_FALLING: 0x3B, + ELEPROX_FDL_FALLING: 0x3C, + ELEPROX_NHD_AMOUNT_TOUCHED: 0x3E, + ELEPROX_NCL_TOUCHED: 0x3F, + ELEPROX_FDL_TOUCHED: 0x40, + + ELE0_TOUCH_THRESHOLD: 0x41, + ELE0_RELEASE_THRESHOLD: 0x42, + ELE1_TOUCH_THRESHOLD: 0x43, + ELE1_RELEASE_THRESHOLD: 0x44, + ELE2_TOUCH_THRESHOLD: 0x45, + ELE2_RELEASE_THRESHOLD: 0x46, + ELE3_TOUCH_THRESHOLD: 0x47, + ELE3_RELEASE_THRESHOLD: 0x48, + ELE4_TOUCH_THRESHOLD: 0x49, + ELE4_RELEASE_THRESHOLD: 0x4A, + ELE5_TOUCH_THRESHOLD: 0x4B, + ELE5_RELEASE_THRESHOLD: 0x4C, + ELE6_TOUCH_THRESHOLD: 0x4D, + ELE6_RELEASE_THRESHOLD: 0x4E, + ELE7_TOUCH_THRESHOLD: 0x4F, + ELE7_RELEASE_THRESHOLD: 0x50, + ELE8_TOUCH_THRESHOLD: 0x51, + ELE8_RELEASE_THRESHOLD: 0x52, + ELE9_TOUCH_THRESHOLD: 0x53, + ELE9_RELEASE_THRESHOLD: 0x54, + ELE10_TOUCH_THRESHOLD: 0x55, + ELE10_RELEASE_THRESHOLD: 0x56, + ELE11_TOUCH_THRESHOLD: 0x57, + ELE11_RELEASE_THRESHOLD: 0x58, + ELEPROX_TOUCH_THRESHOLD: 0x59, + ELEPROX_RELEASE_THRESHOLD: 0x5A, + DEBOUNCE_TOUCH_AND_RELEASE: 0x5B, + AFE_CONFIGURATION: 0x5C, + + FILTER_CONFIG: 0x5D, + ELECTRODE_CONFIG: 0x5E, + ELE0_CURRENT: 0x5F, + ELE1_CURRENT: 0x60, + ELE2_CURRENT: 0x61, + ELE3_CURRENT: 0x62, + ELE4_CURRENT: 0x63, + ELE5_CURRENT: 0x64, + ELE6_CURRENT: 0x65, + ELE7_CURRENT: 0x66, + ELE8_CURRENT: 0x67, + ELE9_CURRENT: 0x68, + ELE10_CURRENT: 0x69, + ELE11_CURRENT: 0x6A, + ELEPROX_CURRENT: 0x6B, + + ELE0_ELE1_CHARGE_TIME: 0x6C, + ELE2_ELE3_CHARGE_TIME: 0x6D, + ELE4_ELE5_CHARGE_TIME: 0x6E, + ELE6_ELE7_CHARGE_TIME: 0x6F, + ELE8_ELE9_CHARGE_TIME: 0x70, + ELE10_ELE11_CHARGE_TIME: 0x71, + ELEPROX_CHARGE_TIME: 0x72, + + GPIO_CONTROL_0: 0x73, + GPIO_CONTROL_1: 0x74, + GPIO_DATA: 0x75, + GPIO_DIRECTION: 0x76, + GPIO_ENABLE: 0x77, + GPIO_SET: 0x78, + GPIO_CLEAR: 0x79, + GPIO_TOGGLE: 0x7A, + AUTO_CONFIG_CONTROL_0: 0x7B, + AUTO_CONFIG_CONTROL_1: 0x7C, + AUTO_CONFIG_USL: 0x7D, + AUTO_CONFIG_LSL: 0x7E, + AUTO_CONFIG_TARGET_LEVEL: 0x7F, + + // Other Constants + // these are suggested values from app note 3944 + TOUCH_THRESHOLD: 0x0F, + RELEASE_THRESHOLD: 0x0A, + NUM_CHANNELS: 12 +}; diff --git a/lib/johnny-five.js b/lib/johnny-five.js index e5a7dcd4a..2a7d7d60c 100644 --- a/lib/johnny-five.js +++ b/lib/johnny-five.js @@ -16,6 +16,7 @@ module.exports = { Gyro: require("./gyro"), IMU: require("./imu"), IR: require("./ir"), + Keypad: require("./keypad"), LCD: require("./lcd"), Led: require("./led"), LedControl: require("./led/ledcontrol"), diff --git a/lib/keypad.js b/lib/keypad.js new file mode 100644 index 000000000..379c47c6a --- /dev/null +++ b/lib/keypad.js @@ -0,0 +1,373 @@ +var Emitter = require("events").EventEmitter; +var util = require("util"); +var Board = require("../lib/board.js"); +var __ = require("../lib/fn.js"); +var Pins = Board.Pins; +var map = Board.map; +var int16 = __.ToInt16FromTwoBytes; + +var priv = new Map(); + +var aliases = { + down: ["down", "press", "tap", "impact", "hit"], + up: ["up", "release"], + "hold": ["hold"] +}; + +var trigger = function(key, value) { + aliases[key].forEach(function(type) { + this.emit(type, value); + }, this); +}; + + +function flatKeys(opts) { + var keys = []; + + if (opts.keys && Array.isArray(opts.keys)) { + keys = opts.keys.slice(); + + if (keys.every(Array.isArray)) { + keys = keys.reduce(function(accum, row) { + return accum.concat(row); + }, []); + } + } + + return keys; +} + +// TODO: +// +// Provide a mechanism for explicitly naming aliases for buttons +// +// +var Controllers = { + MPR121QR2: { + COMMANDS: { + value: require("../lib/definitions/mpr121.js") + }, + initialize: { + value: function(opts) { + + var state = priv.get(this); + var address = opts.address || 0x5A; + var keys = flatKeys(opts); + var mapping = Object.keys(this.COMMANDS.KEY_MAP).reduce(function(accum, index) { + accum[index] = this.COMMANDS.KEY_MAP[index]; + return accum; + }.bind(this), []); + var length = mapping.length; + + + this.io.i2cConfig(); + + this.io.i2cWrite(address, this.COMMANDS.MHD_RISING, 0x01); + this.io.i2cWrite(address, this.COMMANDS.NHD_AMOUNT_RISING, 0x01); + this.io.i2cWrite(address, this.COMMANDS.NCL_RISING, 0x00); + this.io.i2cWrite(address, this.COMMANDS.FDL_RISING, 0x00); + this.io.i2cWrite(address, this.COMMANDS.MHD_FALLING, 0x01); + this.io.i2cWrite(address, this.COMMANDS.NHD_AMOUNT_FALLING, 0x01); + this.io.i2cWrite(address, this.COMMANDS.NCL_FALLING, 0xFF); + this.io.i2cWrite(address, this.COMMANDS.FDL_FALLING, 0x02); + + // TODO: Fix this to work with varying size MPR121 components + for (var i = 0; i < 13; i++) { + this.io.i2cWrite(address, this.COMMANDS.ELE0_TOUCH_THRESHOLD + (i << 1), 40); + this.io.i2cWrite(address, this.COMMANDS.ELE0_RELEASE_THRESHOLD + (i << 1), 20); + } + + this.io.i2cWrite(address, this.COMMANDS.FILTER_CONFIG, 0x04); + this.io.i2cWrite(address, this.COMMANDS.ELECTRODE_CONFIG, 0x0C); + + state.length = length; + state.touches = touches(length); + + this.io.i2cRead(address, 0, 2, function(bytes) { + var key = this.toKey(bytes); + var mapped = null; + var alias = null; + + for (var i = 0; i < length; i++) { + mapped = mapping[i]; + // "mapped" is 1-9 + // "keys" will be zero indexed + alias = keys[mapped - 1]; + + if (alias) { + mapped = alias; + } + + if (key & (1 << i)) { + if (state.touches[i] === 0) { + + state.timeout = Date.now() + opts.holdTime; + trigger.call(this, "down", mapped); + + } else if (state.touches[i] === 1) { + if (state.timeout !== null && Date.now() > state.timeout) { + state.timeout = Date.now() + opts.holdTime; + trigger.call(this, "hold", mapped); + } + } + + state.touches[i] = 1; + } else { + if (state.touches[i] === 1) { + state.timeout = null; + trigger.call(this, "up", mapped); + } + + state.touches[i] = 0; + } + alias = null; + } + }.bind(this)); + } + }, + toKey: { + value: function(raw) { + if (raw.length !== 2) { + return null; + } + return int16(raw[1], raw[0]); + } + } + }, + + // https://learn.sparkfun.com/tutorials/vkey-voltage-keypad-hookup-guide + VKEY: { + initialize: { + value: function(opts) { + + var state = priv.get(this); + var keys = flatKeys(opts); + var mapping = [ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12 ]; + var length = 0; + + if (!keys.length) { + keys = mapping; + } + + length = mapping.length; + + state.length = length; + state.scale = { bottom: 17, step: 40, top: 496 }; + state.touches = touches(length); + + this.io.pinMode(this.pin, this.io.MODES.ANALOG); + this.io.analogRead(this.pin, function(adc) { + var key = this.toKey(adc); + var mapped = null; + var alias = null; + + for (var i = 0; i < length; i++) { + mapped = mapping[i]; + alias = keys[i]; + + if (alias) { + mapped = alias; + } + + if (key === i) { + if (state.touches[i] === 0) { + + state.timeout = Date.now() + opts.holdTime; + trigger.call(this, "down", mapped); + + } else if (state.touches[i] === 1) { + if (state.timeout !== null && Date.now() > state.timeout) { + state.timeout = Date.now() + opts.holdTime; + trigger.call(this, "hold", mapped); + } + } + + state.touches[i] = 1; + } else { + if (state.touches[i] === 1) { + state.timeout = null; + trigger.call(this, "up", mapped); + } + state.touches[i] = 0; + } + alias = null; + } + }.bind(this)); + }, + }, + toKey: { + value: function(raw) { + var state = priv.get(this); + var scale = state.scale; + var length = state.length; + + if (raw < scale.bottom || raw > scale.top) { + return null; + } + + return (length - ((raw - scale.bottom) / scale.step)) | 0; + } + } + }, + + // WaveShare AD + // - http://www.amazon.com/WaveShare-Accessory-buttons-controlled-keyboard/dp/B00KM6UXVS + // - http://www.wvshare.com/product/A_D-Keypad.htm + // + // TODO: Create docs to show how to create a DIY keypad + // that works with this class. + // + ANALOG: { + initialize: { + value: function(opts) { + + var keys = flatKeys(opts); + var mapping = []; + var length = 0; + + if (opts.length && !keys.length) { + keys = Array.from({ length: opts.length }, function(_, key) { + return key; + }); + } + + if (!keys.length) { + throw new Error( + "Missing `keys`. Analog Keypad requires either a numeric `length` or a `keys` array." + ); + } + + mapping = keys; + length = mapping.length; + + var state = priv.get(this); + // keys + Idle state == length + 1 + var total = length + 1; + var vrange = Math.round(1023 / total); + var ranges = Array.from({ length: total }, function(_, index) { + var start = vrange * index; + return Array.from({ length: vrange - 1 }, function(_, index) { + return start + index; + }); + }); + + state.length = length; + state.ranges = ranges; + state.touches = touches(length); + + this.io.pinMode(this.pin, this.io.MODES.ANALOG); + this.io.analogRead(this.pin, function(adc) { + var key = this.toKey(adc); + var mapped = null; + var alias = null; + + for (var i = 0; i < length; i++) { + mapped = mapping[i]; + alias = keys[i]; + + if (alias) { + mapped = alias; + } + + if (key === i) { + if (state.touches[i] === 0) { + state.timeout = Date.now() + opts.holdTime; + trigger.call(this, "down", mapped); + + } else if (state.touches[i] === 1) { + if (state.timeout !== null && Date.now() > state.timeout) { + state.timeout = Date.now() + opts.holdTime; + trigger.call(this, "hold", mapped); + } + } + + state.touches[i] = 1; + } else { + if (state.touches[i] === 1) { + state.timeout = null; + trigger.call(this, "up", mapped); + } + state.touches[i] = 0; + } + alias = null; + } + }.bind(this)); + } + }, + toKey: { + value: function(raw) { + var state = priv.get(this); + var ranges = state.ranges; + var index = ranges.findIndex(function(range) { + return range.includes(raw); + }); + + if (index === state.length) { + index--; + } + + if (index < 0) { + return null; + } + + return index; + } + } + } +}; + + + + + +// Otherwise known as... +Controllers["MPR121"] = Controllers.MPR121QR2; + +function touches(length) { + return Array.from({ length: length }, function() { + return 0; + }); +} + +function Keypad(opts) { + + if (!(this instanceof Keypad)) { + return new Keypad(opts); + } + + // Initialize a Device instance on a Board + Board.Device.call( + this, opts = Board.Options(opts) + ); + + var controller = null; + var state = { + touches: null, + timeout: null, + length: null + }; + + priv.set(this, state); + + if (opts.controller && typeof opts.controller === "string") { + controller = Controllers[opts.controller.toUpperCase()]; + } else { + controller = opts.controller; + } + + if (controller == null) { + controller = Controllers.ANALOG; + } + + Object.defineProperties(this, controller); + + opts.holdTime = opts.holdTime ? opts.holdTime : 500; + + if (controller.initialize) { + this.initialize(opts); + } +} + +util.inherits(Keypad, Emitter); + +module.exports = Keypad; diff --git a/test/keypad.js b/test/keypad.js new file mode 100644 index 000000000..5ecddaf7a --- /dev/null +++ b/test/keypad.js @@ -0,0 +1,517 @@ +var MockFirmata = require("./mock-firmata"), + five = require("../lib/johnny-five.js"), + events = require("events"), + sinon = require("sinon"), + Board = five.Board, + Keypad = five.Keypad, + board = new Board({ + io: new MockFirmata(), + debug: false, + repl: false + }); + +board.io.sp = { + write: function() { + + } +}; + +exports["Keypad: Analog"] = { + setUp: function(done) { + this.clock = sinon.useFakeTimers(); + this.analogRead = sinon.spy(board.io, "analogRead"); + this.keypad = new Keypad({ + pin: "A1", + length: 16, + board: board + }); + + done(); + }, + + tearDown: function(done) { + this.clock.restore(); + this.analogRead.restore(); + done(); + }, + + invalid: function(test) { + test.expect(1); + + // Missing both a length and keys + test.throws(function() { + new Keypad({ + pin: "A1", + board: board + }); + }); + test.done(); + }, + + keysRowsCols: function(test) { + test.expect(16); + + var keys = ["1", "!", "@", "#", "2", "$", "%", "^", "3", "&", "-", "+", "4", "<", ">", "?"]; + var keypad = new five.Keypad({ + board: board, + pin: "A0", + keys: [ + ["1", "!", "@", "#"], + ["2", "$", "%", "^"], + ["3", "&", "-", "+"], + ["4", "<", ">", "?"], + ] + }); + var callback = this.analogRead.getCall(1).args[1]; + var spy = sinon.spy(); + + keypad.on("down", spy); + + callback(0); + callback(61); + callback(125); + callback(189); + callback(252); + callback(315); + callback(379); + callback(445); + callback(508); + callback(573); + callback(639); + callback(700); + callback(763); + callback(830); + callback(896); + callback(960); + + keys.forEach(function(key, index) { + test.equal(spy.args[index][0], key); + }); + + test.done(); + }, + + keysList: function(test) { + test.expect(16); + + var keys = ["1", "!", "@", "#", "2", "$", "%", "^", "3", "&", "-", "+", "4", "<", ">", "?"]; + var keypad = new five.Keypad({ + board: board, + pin: "A0", + keys: keys + }); + var callback = this.analogRead.getCall(1).args[1]; + var spy = sinon.spy(); + + keypad.on("down", spy); + + callback(0); + callback(61); + callback(125); + callback(189); + callback(252); + callback(315); + callback(379); + callback(445); + callback(508); + callback(573); + callback(639); + callback(700); + callback(763); + callback(830); + callback(896); + callback(960); + + keys.forEach(function(key, index) { + test.equal(spy.args[index][0], key); + }); + + test.done(); + }, + press: function(test) { + test.expect(1); + + var callback = this.analogRead.getCall(0).args[1]; + var spy = sinon.spy(); + + this.keypad.on("down", spy); + + callback(0); + callback(61); + callback(125); + callback(189); + callback(252); + callback(315); + callback(379); + callback(445); + callback(508); + callback(573); + callback(639); + callback(700); + callback(763); + callback(830); + callback(896); + callback(960); + + test.equal(spy.callCount, 16); + test.done(); + }, + + hold: function(test) { + test.expect(1); + + var callback = this.analogRead.getCall(0).args[1]; + var spy = sinon.spy(); + + this.keypad.on("hold", spy); + + callback(403); + this.clock.tick(600); + callback(403); + + test.equal(spy.callCount, 1); + test.done(); + }, + + release: function(test) { + test.expect(1); + + var callback = this.analogRead.getCall(0).args[1]; + var spy = sinon.spy(); + + this.keypad.on("release", spy); + + callback(403); + callback(0); + + test.equal(spy.callCount, 1); + test.done(); + }, + + context: function(test) { + test.expect(1); + + var callback = this.analogRead.getCall(0).args[1]; + var keypad = this.keypad; + + this.keypad.on("press", function() { + test.equal(this, keypad); + test.done(); + }); + callback(403); + + } +}; + +exports["Keypad: VKey"] = { + setUp: function(done) { + this.clock = sinon.useFakeTimers(); + this.analogRead = sinon.spy(board.io, "analogRead"); + this.keypad = new Keypad({ + controller: "VKEY", + pin: "A1", + board: board + }); + + done(); + }, + + tearDown: function(done) { + this.clock.restore(); + this.analogRead.restore(); + done(); + }, + + keysRowsCols: function(test) { + test.expect(12); + + var keys = ["!", "@", "#", "$", "%", "^", "&", "-", "+", "<", ">", "?"]; + var keypad = new five.Keypad({ + board: board, + controller: "VKEY", + pin: "A0", + keys: [ + ["!", "@", "#"], + ["$", "%", "^"], + ["&", "-", "+"], + ["<", ">", "?"], + ] + }); + var callback = this.analogRead.getCall(1).args[1]; + var spy = sinon.spy(); + + keypad.on("down", spy); + + callback(487); + callback(444); + callback(404); + callback(365); + callback(323); + callback(282); + callback(242); + callback(201); + callback(160); + callback(119); + callback(79); + callback(38); + + keys.forEach(function(key, index) { + test.equal(spy.args[index][0], key); + }); + + test.done(); + }, + + keysList: function(test) { + test.expect(12); + + var keys = ["!", "@", "#", "$", "%", "^", "&", "-", "+", "<", ">", "?"]; + var keypad = new five.Keypad({ + board: board, + controller: "VKEY", + pin: "A0", + keys: keys + }); + var callback = this.analogRead.getCall(1).args[1]; + var spy = sinon.spy(); + + keypad.on("down", spy); + + callback(485); + callback(444); + callback(404); + callback(365); + callback(323); + callback(282); + callback(242); + callback(201); + callback(160); + callback(119); + callback(79); + callback(38); + + keys.forEach(function(key, index) { + test.equal(spy.args[index][0], key); + }); + + test.done(); + }, + + press: function(test) { + test.expect(1); + + var callback = this.analogRead.getCall(0).args[1]; + var spy = sinon.spy(); + + this.keypad.on("down", spy); + + // Only 3 are valid. + callback(403); + callback(322); + callback(11); + callback(38); + callback(512); + + test.equal(spy.callCount, 3); + test.done(); + }, + + hold: function(test) { + test.expect(1); + + var callback = this.analogRead.getCall(0).args[1]; + var spy = sinon.spy(); + + this.keypad.on("hold", spy); + + callback(403); + this.clock.tick(600); + callback(403); + + test.equal(spy.callCount, 1); + test.done(); + }, + + release: function(test) { + test.expect(1); + + var callback = this.analogRead.getCall(0).args[1]; + var spy = sinon.spy(); + + this.keypad.on("release", spy); + + callback(403); + callback(0); + + test.equal(spy.callCount, 1); + test.done(); + }, + + context: function(test) { + test.expect(1); + + var callback = this.analogRead.getCall(0).args[1]; + var keypad = this.keypad; + + this.keypad.on("press", function() { + test.equal(this, keypad); + test.done(); + }); + callback(403); + + } +}; + +exports["Keypad: MPR121"] = { + setUp: function(done) { + this.clock = sinon.useFakeTimers(); + this.i2cConfig = sinon.spy(board.io, "i2cConfig"); + this.i2cWrite = sinon.spy(board.io, "i2cWrite"); + this.i2cRead = sinon.spy(board.io, "i2cRead"); + + this.keypad = new Keypad({ + controller: "MPR121", + address: 0x5A, + board: board + }); + + done(); + }, + + tearDown: function(done) { + this.i2cConfig.restore(); + this.i2cWrite.restore(); + this.i2cRead.restore(); + this.clock.restore(); + done(); + }, + initialize: function(test) { + test.expect(2); + + test.equal(this.i2cConfig.callCount, 1); + // 10 settings + // 26 Thresholds + test.equal(this.i2cWrite.callCount, 36); + + test.done(); + }, + + keysRowsCols: function(test) { + test.expect(9); + + var keys = ["!", "@", "#", "$", "%", "^", "&", "-", "+"]; + var keypad = new five.Keypad({ + board: board, + controller: "MPR121", + address: 0x5A, + keys: [ + ["!", "@", "#"], + ["$", "%", "^"], + ["&", "-", "+"], + ] + }); + var callback = this.i2cRead.getCall(1).args[3]; + var spy = sinon.spy(); + + keypad.on("down", spy); + + callback([ 0, 1 ]); + callback([ 32, 0 ]); + callback([ 4, 0 ]); + callback([ 128, 0 ]); + callback([ 16, 0 ]); + callback([ 2, 0 ]); + callback([ 64, 0 ]); + callback([ 8, 0 ]); + callback([ 1, 0 ]); + + keys.forEach(function(key, index) { + test.equal(spy.args[index][0], key); + }); + + test.done(); + }, + + keysList: function(test) { + test.expect(9); + + var keys = ["!", "@", "#", "$", "%", "^", "&", "-", "+"]; + var keypad = new five.Keypad({ + board: board, + controller: "MPR121", + address: 0x5A, + keys: keys + }); + var callback = this.i2cRead.getCall(1).args[3]; + var spy = sinon.spy(); + + keypad.on("down", spy); + + callback([ 0, 1 ]); + callback([ 32, 0 ]); + callback([ 4, 0 ]); + callback([ 128, 0 ]); + callback([ 16, 0 ]); + callback([ 2, 0 ]); + callback([ 64, 0 ]); + callback([ 8, 0 ]); + callback([ 1, 0 ]); + + + keys.forEach(function(key, index) { + test.equal(spy.args[index][0], key); + }); + + test.done(); + }, + + press: function(test) { + test.expect(1); + + var callback = this.i2cRead.getCall(0).args[3]; + var spy = sinon.spy(); + + this.keypad.on("down", spy); + + // Only 3 are valid. + callback([ 64, 0 ]); + callback([ 2, 0 ]); + callback([ 4, 0, 0 ]); + callback([ 4 ]); + callback([ 4, 0 ]); + + test.equal(spy.callCount, 3); + test.done(); + }, + + hold: function(test) { + test.expect(1); + + var callback = this.i2cRead.getCall(0).args[3]; + var spy = sinon.spy(); + + this.keypad.on("hold", spy); + + callback([ 64, 0 ]); + this.clock.tick(600); + callback([ 64, 0 ]); + + test.equal(spy.callCount, 1); + test.done(); + }, + + release: function(test) { + test.expect(1); + + var callback = this.i2cRead.getCall(0).args[3]; + var spy = sinon.spy(); + + this.keypad.on("release", spy); + + callback([ 64, 0 ]); + callback([ 0, 0 ]); + + test.equal(spy.callCount, 1); + test.done(); + }, +};