diff --git a/examples/stepper-accel.js b/examples/stepper-accel.js new file mode 100644 index 0000000..499947c --- /dev/null +++ b/examples/stepper-accel.js @@ -0,0 +1,32 @@ +var Board = require("../"); + +Board.requestPort(function(error, port) { + if (error) { + console.log(error); + return; + } + + var board = new Board(port.comName); + + board.on("ready", function() { + + board.accelStepperConfig({ + deviceNum: 0, + type: board.STEPPER.TYPE.FOUR_WIRE, + motorPin1: 5, + motorPin2: 6, + motorPin3: 7, + motorPin4: 8, + stepType: board.STEPPER.STEPTYPE.WHOLE + }); + + board.accelStepperSpeed(0, 400); + board.accelStepperAcceleration(0, 100); + board.accelStepperStep(0, 2000); + + board.on("stepper-done-0", function(position) { + console.log("Current position: " + position); + }); + + }); +}); diff --git a/examples/stepper-driver.js b/examples/stepper-driver.js new file mode 100644 index 0000000..fb608fd --- /dev/null +++ b/examples/stepper-driver.js @@ -0,0 +1,32 @@ +var Board = require("../"); + +Board.requestPort(function(error, port) { + if (error) { + console.log(error); + return; + } + + var board = new Board(port.comName); + + board.on("ready", function() { + + board.accelStepperConfig({ + deviceNum: 0, + type: board.STEPPER.TYPE.DRIVER, + stepPin: 5, + directionPin: 6, + enablePin: 2, + invertPins: [2] + }); + + board.accelStepperSpeed(0, 400); + board.accelStepperAcceleration(0, 100); + board.accelStepperEnable(0, true); + board.accelStepperStep(0, 200); + + board.on("stepper-done-0", function(position) { + console.log("Current position: " + position); + }); + + }); +}); diff --git a/examples/stepper-multi.js b/examples/stepper-multi.js new file mode 100644 index 0000000..fd8d50b --- /dev/null +++ b/examples/stepper-multi.js @@ -0,0 +1,57 @@ +var Board = require("../"); + +Board.requestPort(function(error, port) { + if (error) { + console.log(error); + return; + } + + var board = new Board(port.comName); + + board.on("ready", function() { + + board.accelStepperConfig({ + deviceNum: 0, + type: board.STEPPER.TYPE.FOUR_WIRE, + motorPin1: 5, + motorPin2: 6, + motorPin3: 7, + motorPin4: 8, + stepType: board.STEPPER.STEPTYPE.WHOLE + }); + + board.accelStepperConfig({ + deviceNum: 1, + type: board.STEPPER.TYPE.FOUR_WIRE, + motorPin1: 9, + motorPin2: 10, + motorPin3: 11, + motorPin4: 12, + stepType: board.STEPPER.STEPTYPE.HALF + }); + + board.accelStepperSpeed(0, 400); + board.accelStepperSpeed(1, 400); + + board.multiStepperConfig({ + groupNum: 0, + devices: [0, 1] + }); + + board.multiStepperTo(0, [2000, 3000]); + + board.on("multi-stepper-done-0", function() { + board.accelStepperReportPosition(0); + board.accelStepperReportPosition(1); + }); + + board.on("stepper-position-0", function(value) { + console.log("Stepper 0 position: " + value); + }); + + board.on("stepper-position-1", function(value) { + console.log("Stepper 1 position: " + value); + }); + + }); +}); diff --git a/examples/stepper-three-wire.js b/examples/stepper-three-wire.js new file mode 100644 index 0000000..cd3eb56 --- /dev/null +++ b/examples/stepper-three-wire.js @@ -0,0 +1,29 @@ +var Board = require("../"); + +Board.requestPort(function(error, port) { + if (error) { + console.log(error); + return; + } + + var board = new Board(port.comName); + + board.on("ready", function() { + + board.accelStepperConfig({ + deviceNum: 0, + type: board.STEPPER.TYPE.THREE_WIRE, + motorPin1: 2, + motorPin2: 3, + motorPin3: 4 + }); + + board.accelStepperSpeed(0, 100); + board.accelStepperStep(0, 1000); + + board.on("stepper-done-0", function(position) { + console.log("Current position: " + position); + }); + + }); +}); diff --git a/lib/firmata.js b/lib/firmata.js index f7b505d..ba8869c 100644 --- a/lib/firmata.js +++ b/lib/firmata.js @@ -67,6 +67,7 @@ var SERIAL_FLUSH = 0x60; var SERIAL_LISTEN = 0x70; var START_SYSEX = 0xF0; var STEPPER = 0x72; +var ACCELSTEPPER = 0x62; var STRING_DATA = 0x71; var SYSTEM_RESET = 0xFF; @@ -385,6 +386,28 @@ SYSEX_RESPONSE[STEPPER] = function(board) { board.emit("stepper-done-" + deviceNum, true); }; +/** + * Handles the message from a stepper or group of steppers completing move + * @param {Board} board + */ + +SYSEX_RESPONSE[ACCELSTEPPER] = function(board) { + var command = board.currentBuffer[2]; + var deviceNum = board.currentBuffer[3]; + + if (command === 0x06) { + var value = Board.decode32BitSignedInteger(board.currentBuffer.slice(4, 9)); + board.emit("stepper-position-" + deviceNum, value); + } + if (command === 0x0A) { + var value = Board.decode32BitSignedInteger(board.currentBuffer.slice(4, 9)); + board.emit("stepper-done-" + deviceNum, value); + } + if (command === 0x24) { + board.emit("multi-stepper-done-" + deviceNum); + } +}; + /** * Handles a SERIAL_REPLY response and emits the "serial-data-"+n event where n is the id of the * serial port. @@ -480,8 +503,13 @@ function Board(port, options, callback) { TYPE: { DRIVER: 1, TWO_WIRE: 2, + THREE_WIRE: 3, FOUR_WIRE: 4, }, + STEPTYPE: { + WHOLE: 0, + HALF: 1 + }, RUNSTATE: { STOP: 0, ACCEL: 1, @@ -1689,6 +1717,318 @@ Board.prototype.pingRead = function(opts, callback) { this.once("ping-read-" + pin, callback); }; +/** + * Stepper functions to support version 2 of ConfigurableFirmata's asynchronous control of stepper motors + * https://github.com/soundanalogous/ConfigurableFirmata + */ + +/** + * Asks the arduino to configure a stepper motor with the given config to allow asynchronous control of the stepper + * @param {object} opts Options: + * {number} deviceNum: Device number for the stepper (range 0-9) + * {number} type: One of this.STEPPER.TYPE.* + * {number} stepType: One of this.STEPPER.STEPTYPE.* + * {number} stepPin: Only used if STEPPER.TYPE.DRIVER + * {number} directionPin: Only used if STEPPER.TYPE.DRIVER + * {number} motorPin1: motor pin 1 + * {number} motorPin2: motor pin 2 + * {number} [motorPin3]: Only required if type == this.STEPPER.TYPE.THREE_WIRE || this.STEPPER.TYPE.FOUR_WIRE + * {number} [motorPin4]: Only required if type == this.STEPPER.TYPE.FOUR_WIRE + * {number} [enablePin]: Enable pin + * {array} [invertPins]: Array of pins to invert + */ + +Board.prototype.accelStepperConfig = function(opts) { + + var interface, pinsToInvert = 0x00; + var data = [ + START_SYSEX, + ACCELSTEPPER, + 0x00, // STEPPER_CONFIG from firmware + opts.deviceNum + ]; + + if (typeof opts.type === "undefined") { + opts.type = this.STEPPER.TYPE.FOUR_WIRE; + } + + if (typeof opts.stepType === "undefined") { + opts.stepType = this.STEPPER.STEPTYPE.WHOLE; + } + + interface = ((opts.type & 0x07) << 4) | ((opts.stepType & 0x07) << 1); + + if (typeof opts.enablePin !== "undefined") { + interface = interface | 0x01; + } + + data.push(interface); + + ["stepPin", "motorPin1", "directionPin", "motorPin2", "motorPin3", "motorPin4", "enablePin"].forEach(function(pin) { + if (typeof opts[pin] !== "undefined") { + data.push(opts[pin]); + } + }); + + if (Array.isArray(opts.invertPins)) { + if (opts.invertPins.indexOf(opts.motorPin1) != -1) pinsToInvert |= 0x01; + if (opts.invertPins.indexOf(opts.motorPin2) != -1) pinsToInvert |= 0x02; + if (opts.invertPins.indexOf(opts.motorPin3) != -1) pinsToInvert |= 0x04; + if (opts.invertPins.indexOf(opts.motorPin4) != -1) pinsToInvert |= 0x08; + if (opts.invertPins.indexOf(opts.enablePin) != -1) pinsToInvert |= 0x10; + } + + data.push(pinsToInvert); + data.push(END_SYSEX); + this.transport.write(new Buffer(data)); + +}; + +/** + * Asks the arduino to set the stepper position to 0 + * Note: This is not a move. We are setting the current position equal to zero + * @param {number} deviceNum Device number for the stepper (range 0-9) + */ + +Board.prototype.accelStepperZero = function(deviceNum) { + + var data = [ + START_SYSEX, + ACCELSTEPPER, + 0x01, // STEPPER_ZERO from firmware + deviceNum, + END_SYSEX + ]; + + this.transport.write(new Buffer(data)); +}; + +/** + * Asks the arduino to move a stepper a number of steps + * (and optionally with and acceleration and deceleration) + * speed is in units of steps/sec + * @param {number} deviceNum Device number for the stepper (range 0-5) + * @param {number} steps Number of steps to make + */ +Board.prototype.accelStepperStep = function(deviceNum, steps, callback) { + + var data = [ + START_SYSEX, + ACCELSTEPPER, + 0x02, // STEPPER_STEP from firmware + deviceNum + ]; + + Array.prototype.push.apply(data, Board.encode32BitSignedInteger(steps)); + data.push(END_SYSEX); + + this.transport.write(new Buffer(data)); + + if (callback) { + this.once("stepper-done-" + deviceNum, callback); + } + +}; + +/** + * Asks the arduino to move a stepper to a specific location + * @param {number} deviceNum Device number for the stepper (range 0-5) + * @param {number} position Desired position + */ +Board.prototype.accelStepperTo = function(deviceNum, position, callback) { + + var data = [ + START_SYSEX, + ACCELSTEPPER, + 0x03, // STEPPER_TO from firmware + deviceNum + ]; + + Array.prototype.push.apply(data, Board.encode32BitSignedInteger(position)); + data.push(END_SYSEX); + + this.transport.write(new Buffer(data)); + + if (callback) { + this.once("stepper-done-" + deviceNum, callback); + } + +}; + +/** + * Asks the arduino to enable/disable a stepper + * @param {number} deviceNum Device number for the stepper (range 0-9) + * @param {boolean} [enabled] + */ + +Board.prototype.accelStepperEnable = function(deviceNum, enabled) { + + if (typeof enabled === "undefined") { + enabled = true; + } + + var data = [ + START_SYSEX, + ACCELSTEPPER, + 0x04, // ENABLE from firmware + deviceNum, + enabled & 0x01, + END_SYSEX + ]; + + this.transport.write(new Buffer(data)); +}; + +/** + * Asks the arduino to stop a stepper + * @param {number} deviceNum Device number for the stepper (range 0-9) + */ + +Board.prototype.accelStepperStop = function(deviceNum) { + + var data = [ + START_SYSEX, + ACCELSTEPPER, + 0x05, // STEPPER_STOP from firmware + deviceNum, + END_SYSEX + ]; + + this.transport.write(new Buffer(data)); + +}; + +/** + * Asks the arduino to report the position of a stepper + * @param {number} deviceNum Device number for the stepper (range 0-9) + */ + +Board.prototype.accelStepperReportPosition = function(deviceNum) { + + var data = [ + START_SYSEX, + ACCELSTEPPER, + 0x06, // STEPPER_REPORT_POSITION from firmware + deviceNum, + END_SYSEX + ]; + + this.transport.write(new Buffer(data)); +}; + +/** + * Asks the arduino to set the acceleration for a stepper + * @param {number} deviceNum Device number for the stepper (range 0-9) + * @param {number} acceleration Desired acceleration in steps per sec^2 + */ + +Board.prototype.accelStepperAcceleration = function(deviceNum, acceleration) { + + var data = [ + START_SYSEX, + ACCELSTEPPER, + 0x08, // STEPPER_SET_ACCELERATION from firmware + deviceNum]; + + Array.prototype.push.apply(data, Board.encodeCustomFloat(acceleration)); + data.push(END_SYSEX); + + this.transport.write(new Buffer(data)); +}; + + +/** + * Asks the arduino to set the max speed for a stepper + * @param {number} deviceNum Device number for the stepper (range 0-9) + * @param {number} speed Desired speed or maxSpeed in steps per second + * @param {function} [callback] + */ + +Board.prototype.accelStepperSpeed = function(deviceNum, speed) { + + var data = [ + START_SYSEX, + ACCELSTEPPER, + 0x09, // STEPPER_SET_SPEED from firmware + deviceNum]; + + Array.prototype.push.apply(data, Board.encodeCustomFloat(speed)); + data.push(END_SYSEX); + + this.transport.write(new Buffer(data)); +}; + +/** + * Asks the arduino to configure a multiStepper group + * @param {object} opts Options: + * {number} groupNum: Group number for the multiSteppers (range 0-5) + * {number} devices: array of accelStepper device numbers in group + **/ + +Board.prototype.multiStepperConfig = function(opts) { + + var data = [ + START_SYSEX, + ACCELSTEPPER, + 0x20, // MULTISTEPPER_CONFIG from firmware + opts.groupNum + ]; + + Array.prototype.push.apply(data, opts.devices); + + data.push(END_SYSEX); + this.transport.write(new Buffer(data)); +}; + +/** + * Asks the arduino to move a multiStepper group + * @param {object} opts Options: + * {number} groupNum: Group number for the multiSteppers (range 0-5) + * {number} positions: array of absolute stepper positions + **/ + +Board.prototype.multiStepperTo = function(groupNum, positions, callback) { + + var data = [ + START_SYSEX, + ACCELSTEPPER, + 0x21, // MULTISTEPPER_TO from firmware + groupNum + ]; + + positions.forEach( function(position) { + Array.prototype.push.apply(data, Board.encode32BitSignedInteger(position)); + }); + + data.push(END_SYSEX); + this.transport.write(new Buffer(data)); + + if (callback) { + this.once("multi-stepper-done-" + groupNum, callback); + } + +}; + +/** + * Asks the arduino to stop a multiStepper group + * @param {object} opts Options: + * {number} groupNum: Group number for the multiSteppers (range 0-5) + **/ + +Board.prototype.multiStepperStop = function(groupNum) { + + var data = [ + START_SYSEX, + ACCELSTEPPER, + 0x23, // MULTISTEPPER_STOP from firmware + groupNum, + END_SYSEX + ]; + + this.transport.write(new Buffer(data)); + +}; + /** * Stepper functions to support AdvancedFirmata"s asynchronous control of stepper motors * https://github.com/soundanalogous/AdvancedFirmata @@ -1699,13 +2039,13 @@ Board.prototype.pingRead = function(opts, callback) { * @param {number} deviceNum Device number for the stepper (range 0-5, expects steppers to be setup in order from 0 to 5) * @param {number} type One of this.STEPPER.TYPE.* * @param {number} stepsPerRev Number of steps motor takes to make one revolution - * @param {number} dirOrMotor1Pin If using EasyDriver type stepper driver, this is direction pin, otherwise it is motor 1 pin - * @param {number} stepOrMotor2Pin If using EasyDriver type stepper driver, this is step pin, otherwise it is motor 2 pin - * @param {number} [motor3Pin] Only required if type == this.STEPPER.TYPE.FOUR_WIRE - * @param {number} [motor4Pin] Only required if type == this.STEPPER.TYPE.FOUR_WIRE + * @param {number} stepOrMotor1Pin If using EasyDriver type stepper driver, this is direction pin, otherwise it is motor 1 pin + * @param {number} dirOrMotor2Pin If using EasyDriver type stepper driver, this is step pin, otherwise it is motor 2 pin + * @param {number} [motorPin3] Only required if type == this.STEPPER.TYPE.FOUR_WIRE + * @param {number} [motorPin4] Only required if type == this.STEPPER.TYPE.FOUR_WIRE */ -Board.prototype.stepperConfig = function(deviceNum, type, stepsPerRev, dirOrMotor1Pin, stepOrMotor2Pin, motor3Pin, motor4Pin) { +Board.prototype.stepperConfig = function(deviceNum, type, stepsPerRev, dirOrMotor1Pin, dirOrMotor2Pin, motorPin3, motorPin4) { var data = [ START_SYSEX, STEPPER, @@ -1714,10 +2054,10 @@ Board.prototype.stepperConfig = function(deviceNum, type, stepsPerRev, dirOrMoto type, stepsPerRev & 0x7F, (stepsPerRev >> 7) & 0x7F, dirOrMotor1Pin, - stepOrMotor2Pin, + dirOrMotor2Pin, ]; if (type === this.STEPPER.TYPE.FOUR_WIRE) { - data.push(motor3Pin, motor4Pin); + data.push(motorPin3, motorPin4); } data.push(END_SYSEX); writeToTransport(this, data); @@ -2073,6 +2413,87 @@ Board.decode = function(data) { return decoded; }; +Board.encode32BitSignedInteger = function(data) { + var encoded = []; + + var negative = data < 0; + data = Math.abs(data); + + encoded.push(data & 0x7F); + encoded.push((data >> 7) & 0x7F); + encoded.push((data >> 14) & 0x7F); + encoded.push((data >> 21) & 0x7F); + encoded.push((data >> 28) & 0x07); + + if (negative) { + encoded[encoded.length - 1] |= 0x08; + } + + return encoded; +} + +Board.decode32BitSignedInteger = function(bytes) { + var result = (bytes[0] & 0x7f) | + ((bytes[1] & 0x7f) << 7) | + ((bytes[2] & 0x7f) << 14) | + ((bytes[3] & 0x7f) << 21) | + ((bytes[4] & 0x07) << 28); + + if (bytes[4] >> 3) { + result *= -1; + } + + return result; +} + +Board.encodeCustomFloat = function(input) { + var encoded = [], + exponent = 0, + maxSignificand = Math.pow(2, 23), + sign = input < 0 ? 1 : 0; + + input = Math.abs(input); + + var base10 = Math.floor(Math.log10(input)); + + // Shift decimal to start of significand + exponent += base10; + input /= Math.pow(10, base10); + + // Shift decimal to the right as far as we can + while( !Number.isInteger(input) && input < maxSignificand ) { + exponent -= 1; + input *= 10; + } + + // Reduce precision if necessary + while( input > maxSignificand ) { + exponent += 1; + input /= 10; + } + + input = Math.trunc(input); + exponent += 11; + + encoded = [ + input & 0x7f, + (input >> 7) & 0x7f, + (input >> 14) & 0x7f, + (input >> 21) & 0x03 | (exponent & 0x0f) << 2 | (sign & 0x01) << 6 + ]; + + return encoded; +} + +Board.decodeCustomFloat = function(input) { + var result = input[0] | input[1] << 7 | input[2] << 14 | (input[3] & 0x03) << 21; + var exponent = ((input[3] >> 2) & 0x0f) - 11; + var sign = (input[3] >> 6) & 0x01; + if (sign) result *= -1; + return result * Math.pow(10, exponent); +} + + /* istanbul ignore else */ if (process.env.IS_TEST_MODE) { Board.test = { diff --git a/readme.md b/readme.md index feeb141..fc4d81e 100644 --- a/readme.md +++ b/readme.md @@ -345,6 +345,135 @@ If you run *firmata* from the command line it will prompt you for the serial por **For SoftwareSerial only**. Only a single SoftwareSerial instance can read data at a time. Call this method to set this port to be the reading port in the case there are multiple SoftwareSerial instances. +### AccelStepperFirmata + +AccelStepperFirmata in configurableFirmata wraps [Mike McCauley's AccelStepper library](http://www.airspayce.com/mikem/arduino/AccelStepper/). Accelstepper gives basic acceleration for individual steppers and support for multiSteppers. multiSteppers allow you to coordinate the movements of a group of steppers so that they arrive at their desired positions simultaneously. + +Requests for stepper movements are made asyncrhonously and movements can be interrupted with a call to stop or by setting a new target position with accelStepperTo or accelStepperMove. + +accelStepper support 2, 3, and 4 wire configurations as well as step + direction controllers like the easyDriver. + +- `board.STEPPER.TYPE` + + Available Stepper or controller types. + + ```js + { + DRIVER: 1, + TWO_WIRE: 2, + THREE_WIRE: 3, + FOUR_WIRE: 4, + } + ``` + +- `board.STEPPER.STEPTYPE` + + Available step sizes. + + ```js + { + WHOLE: 0, + HALF: 1 + } + ``` + +- `board.STEPPER.DIRECTION` + + Stepper directions. + + ```js + { + CCW: 0, + CW: 1 + } + ``` + + - `board.accelStepperConfig(opts)` + + Configure a stepper motor + + ``` + opts = { + deviceNum: 0, // Device number for the stepper (range 0-9) + type: board.STEPPER.TYPE.DRIVER, // (optional) Type of stepper or controller; default is FOUR_WIRE + stepType: board.STEPPER.STEPTYPE.HALF, // (optional) Size of step; default is WHOLE + stepPin: 2, // (required if type === DRIVER) The step pin for a step+direction stepper driver + directionPin: 3, // (required if type === DRIVER) The direction pin for a step+direction stepper driver + motorPin1: 2, // (required if type !== DRIVER) Motor control pin 1 + motorPin2: 3, // (required if type !== DRIVER) Motor control pin 2 + motorPin3: 4, // (required if type === THREE_WIRE or FOUR_WIRE) Motor control pin 3 + motorPin4: 5, // (required if type === FOUR_WIRE) Motor control pin 4 + enablePin: 6, // (optional) Enable pin for motor controller pin + invertPins: 0 // (optional) Controls which pins to invert (see table below); default is 0 + } + ``` + + **invertPins** + + The invertPins value is a 5 bit number + + bit 5 |bit 4 |bit 3 |bit 2 |bit 1 + ----------------|----------------|----------------|----------------|---------------- + invert motorPin1|invert motorPin2|invert motorPin3|invert motorPin4|invert enablePin + + Examples: + + 1. Invert motor pins 1, 2, 3 & 4 = 0b11110 = 30 + + 1. Invert motor pins 1, 2 & enablePin = 0b11001 = 25 + + +- `board.prototype.accelStepperZero(deviceNum)` + + Set the current stepper position to zero + +- `Board.prototype.accelStepperStep(deviceNum, steps, callback)` + + Move the stepper motor by a number of steps. Optional callback will be called when motor has finished moving or stop is called + +- `Board.prototype.accelStepperTo(deviceNum, position, callback)` + + Move the stepper motor to a specified position. Optional callback will be called when motor has finished moving or stop is called + +- `Board.prototype.accelStepperEnable(deviceNum, enabled)` + + If enabled param is set to false, stepper will be disabled, otherwise stepper will be enabled + +- `Board.prototype.accelStepperStop(deviceNum)` + + Stop the stepper motor. Triggers a stepper-done event + +- `Board.prototype.accelStepperReportPosition(deviceNum)` + + Request the current position of the stepper. Triggers a stepper-position event + +- `Board.prototype.accelStepperSpeed(deviceNum, speed)` + + Set teh speed of the stepper in steps per second + +- `Board.prototype.accelStepperAcceleration(deviceNum, acceleration)` + + Set the acceleration and deceleration for the stepper in steps / sec^2 + +- `Board.prototype.multiStepperConfig(opts)` + + Configure a multStepper group. multiStepper groups allow you to pass an array of targeted positions and have all the steppers move to their targets and arrive at the same time. Note that acceleration cannot be used when moving a multiStepper group. + + ``` + opts = { + groupNum: 0, // Group number for the stepper group (range 0-4) + devices: board.STEPPER.TYPE.DRIVER // [] Array of deviceNum's used in group + } + ``` + +- `Board.prototype.multiStepperTo(groupNum, positions, callback)` + + Move a goup of steppers to and array of desired positions. Optional callback will be called when group has finished moving or multiStepperStop is called + +- `Board.prototype.multiStepperStop(groupNum)` + + Stop a group of stepper motors. Triggers a multi-stepper-done event + ### Sysex - `board.sysexResponse(commandByte, handler)` diff --git a/test/unit/firmata.test.js b/test/unit/firmata.test.js index 66f8a0a..e18fa08 100644 --- a/test/unit/firmata.test.js +++ b/test/unit/firmata.test.js @@ -51,6 +51,7 @@ var SERIAL_FLUSH = 0x60; var SERIAL_LISTEN = 0x70; var START_SYSEX = 0xF0; var STEPPER = 0x72; +var ACCELSTEPPER = 0x62; var STRING_DATA = 0x71; var SYSTEM_RESET = 0xFF; @@ -1961,6 +1962,411 @@ describe("Board: lifecycle", function() { transport.emit("data", [3]); transport.emit("data", [END_SYSEX]); }); + + it("can send a accelStepper config for a driver configuration with enable and invert", function(done) { + board.accelStepperConfig({ deviceNum: 0, type: board.STEPPER.TYPE.DRIVER, stepPin: 5, directionPin: 6, enablePin: 2, invertPins: [2] }); + assert.equal(transport.lastWrite[0], START_SYSEX); + assert.equal(transport.lastWrite[1], ACCELSTEPPER); + assert.equal(transport.lastWrite[2], 0); + assert.equal(transport.lastWrite[3], 0); + assert.equal(transport.lastWrite[4], 0x11); + assert.equal(transport.lastWrite[5], 5); + assert.equal(transport.lastWrite[6], 6); + assert.equal(transport.lastWrite[7], 2); + assert.equal(transport.lastWrite[8], 16); + assert.equal(transport.lastWrite[9], END_SYSEX); + done(); + }); + + it("can send a accelStepper config for a two wire configuration", function(done) { + board.accelStepperConfig({ deviceNum: 0, type: board.STEPPER.TYPE.TWO_WIRE, motorPin1: 5, motorPin2: 6, invertPins: [5, 6] }); + assert.equal(transport.lastWrite[0], START_SYSEX); + assert.equal(transport.lastWrite[1], ACCELSTEPPER); + assert.equal(transport.lastWrite[2], 0); + assert.equal(transport.lastWrite[3], 0); + assert.equal(transport.lastWrite[4], 0x20); + assert.equal(transport.lastWrite[5], 5); + assert.equal(transport.lastWrite[6], 6); + assert.equal(transport.lastWrite[7], 3); + assert.equal(transport.lastWrite[8], END_SYSEX); + done(); + }); + + it("can send a accelStepper config for a four wire configuration", function(done) { + board.accelStepperConfig({ deviceNum: 0, type: board.STEPPER.TYPE.FOUR_WIRE, motorPin1: 5, motorPin2: 6, motorPin3: 3, motorPin4: 4 }); + assert.equal(transport.lastWrite[0], START_SYSEX); + assert.equal(transport.lastWrite[1], ACCELSTEPPER); + assert.equal(transport.lastWrite[2], 0); + assert.equal(transport.lastWrite[3], 0); + assert.equal(transport.lastWrite[4], 0x40); + assert.equal(transport.lastWrite[5], 5); + assert.equal(transport.lastWrite[6], 6); + assert.equal(transport.lastWrite[7], 3); + assert.equal(transport.lastWrite[8], 4); + assert.equal(transport.lastWrite[9], 0); + assert.equal(transport.lastWrite[10], END_SYSEX); + done(); + }); + + it("can send a accelStepper config for a four wire, half step configuration", function(done) { + board.accelStepperConfig({ deviceNum: 0, type: board.STEPPER.TYPE.FOUR_WIRE, stepType: board.STEPPER.STEPTYPE.HALF, motorPin1: 5, motorPin2: 6, motorPin3: 3, motorPin4: 4 }); + assert.equal(transport.lastWrite[0], START_SYSEX); + assert.equal(transport.lastWrite[1], ACCELSTEPPER); + assert.equal(transport.lastWrite[2], 0); + assert.equal(transport.lastWrite[3], 0); + assert.equal(transport.lastWrite[4], 0x42); + assert.equal(transport.lastWrite[5], 5); + assert.equal(transport.lastWrite[6], 6); + assert.equal(transport.lastWrite[7], 3); + assert.equal(transport.lastWrite[8], 4); + assert.equal(transport.lastWrite[9], 0); + assert.equal(transport.lastWrite[10], END_SYSEX); + done(); + }); + + it("can send a accelStepper config with four wire and whole step as defaults", function(done) { + board.accelStepperConfig({ deviceNum: 0, motorPin1: 5, motorPin2: 6, motorPin3: 3, motorPin4: 4 }); + assert.equal(transport.lastWrite[0], START_SYSEX); + assert.equal(transport.lastWrite[1], ACCELSTEPPER); + assert.equal(transport.lastWrite[2], 0); + assert.equal(transport.lastWrite[3], 0); + assert.equal(transport.lastWrite[4], 0x40); + assert.equal(transport.lastWrite[5], 5); + assert.equal(transport.lastWrite[6], 6); + assert.equal(transport.lastWrite[7], 3); + assert.equal(transport.lastWrite[8], 4); + assert.equal(transport.lastWrite[9], 0); + assert.equal(transport.lastWrite[10], END_SYSEX); + done(); + }); + + it("can send a accelStepper config for a default four wire configuration with inverted motor and enable pins", function(done) { + board.accelStepperConfig({ deviceNum: 0, motorPin1: 5, motorPin2: 6, motorPin3: 3, motorPin4: 4, enablePin: 2, invertPins: [2, 3, 4, 5, 6] }); + assert.equal(transport.lastWrite[0], START_SYSEX); + assert.equal(transport.lastWrite[1], ACCELSTEPPER); + assert.equal(transport.lastWrite[2], 0); + assert.equal(transport.lastWrite[3], 0); + assert.equal(transport.lastWrite[4], 0x41); + assert.equal(transport.lastWrite[5], 5); + assert.equal(transport.lastWrite[6], 6); + assert.equal(transport.lastWrite[7], 3); + assert.equal(transport.lastWrite[8], 4); + assert.equal(transport.lastWrite[9], 2); + assert.equal(transport.lastWrite[10], 31); + assert.equal(transport.lastWrite[11], END_SYSEX); + done(); + }); + + it("can send a accelStepper step", function(done) { + board.accelStepperStep(0, 12345, function(value) { + assert.equal(value, 12345); + done(); + }); + + assert.equal(transport.lastWrite[0], START_SYSEX); + assert.equal(transport.lastWrite[1], ACCELSTEPPER); + assert.equal(transport.lastWrite[2], 2); + assert.equal(transport.lastWrite[3], 0); + assert.equal(transport.lastWrite[4], 57); + assert.equal(transport.lastWrite[5], 96); + assert.equal(transport.lastWrite[6], 0); + assert.equal(transport.lastWrite[7], 0); + assert.equal(transport.lastWrite[8], 0); + assert.equal(transport.lastWrite[9], END_SYSEX); + + transport.emit("data", [START_SYSEX]); + transport.emit("data", [ACCELSTEPPER]); + transport.emit("data", [0x0A]); + transport.emit("data", [0]); + transport.emit("data", [57]); + transport.emit("data", [96]); + transport.emit("data", [0]); + transport.emit("data", [0]); + transport.emit("data", [0]); + transport.emit("data", [END_SYSEX]); + }); + + it("can send a accelStepper step w/o a callback", function(done) { + board.accelStepperStep(0, 12345); + + assert.equal(transport.lastWrite[0], START_SYSEX); + assert.equal(transport.lastWrite[1], ACCELSTEPPER); + assert.equal(transport.lastWrite[2], 2); + assert.equal(transport.lastWrite[3], 0); + assert.equal(transport.lastWrite[4], 57); + assert.equal(transport.lastWrite[5], 96); + assert.equal(transport.lastWrite[6], 0); + assert.equal(transport.lastWrite[7], 0); + assert.equal(transport.lastWrite[8], 0); + assert.equal(transport.lastWrite[9], END_SYSEX); + + done(); + }); + + it("can send a accelStepper zero", function(done) { + board.accelStepperZero(0); + + assert.equal(transport.lastWrite[0], START_SYSEX); + assert.equal(transport.lastWrite[1], ACCELSTEPPER); + assert.equal(transport.lastWrite[2], 1); + assert.equal(transport.lastWrite[3], 0); + assert.equal(transport.lastWrite[4], END_SYSEX); + done(); + }); + + it("can send a accelStepper enable", function(done) { + board.accelStepperEnable(0, true); + + assert.equal(transport.lastWrite[0], START_SYSEX); + assert.equal(transport.lastWrite[1], ACCELSTEPPER); + assert.equal(transport.lastWrite[2], 4); + assert.equal(transport.lastWrite[3], 0); + assert.equal(transport.lastWrite[4], 1); + assert.equal(transport.lastWrite[5], END_SYSEX); + done(); + }); + + it("can send a accelStepper enable using default value", function(done) { + board.accelStepperEnable(0); + + assert.equal(transport.lastWrite[0], START_SYSEX); + assert.equal(transport.lastWrite[1], ACCELSTEPPER); + assert.equal(transport.lastWrite[2], 4); + assert.equal(transport.lastWrite[3], 0); + assert.equal(transport.lastWrite[4], 1); + assert.equal(transport.lastWrite[5], END_SYSEX); + done(); + }); + + it("can can send a accelStepper disable", function(done) { + board.accelStepperEnable(0, false); + + assert.equal(transport.lastWrite[0], START_SYSEX); + assert.equal(transport.lastWrite[1], ACCELSTEPPER); + assert.equal(transport.lastWrite[2], 4); + assert.equal(transport.lastWrite[3], 0); + assert.equal(transport.lastWrite[4], 0); + assert.equal(transport.lastWrite[5], END_SYSEX); + done(); + }); + + it("can send a accelStepper move to as specified position", function(done) { + board.accelStepperTo(0, 2000, function(value) { + assert.equal(value, 2000); + done(); + }); + + assert.equal(transport.lastWrite[0], START_SYSEX); + assert.equal(transport.lastWrite[1], ACCELSTEPPER); + assert.equal(transport.lastWrite[2], 3); + assert.equal(transport.lastWrite[3], 0); + assert.equal(transport.lastWrite[4], 80); + assert.equal(transport.lastWrite[5], 15); + assert.equal(transport.lastWrite[6], 0); + assert.equal(transport.lastWrite[7], 0); + assert.equal(transport.lastWrite[8], 0); + assert.equal(transport.lastWrite[9], END_SYSEX); + + transport.emit("data", [START_SYSEX]); + transport.emit("data", [ACCELSTEPPER]); + transport.emit("data", [0x0A]); + transport.emit("data", [0]); + transport.emit("data", [80]); + transport.emit("data", [15]); + transport.emit("data", [0]); + transport.emit("data", [0]); + transport.emit("data", [0]); + transport.emit("data", [END_SYSEX]); + }); + + it("can send a accelStepper move to as specified position w/o a callback", function(done) { + board.accelStepperTo(0, 2000); + + assert.equal(transport.lastWrite[0], START_SYSEX); + assert.equal(transport.lastWrite[1], ACCELSTEPPER); + assert.equal(transport.lastWrite[2], 3); + assert.equal(transport.lastWrite[3], 0); + assert.equal(transport.lastWrite[4], 80); + assert.equal(transport.lastWrite[5], 15); + assert.equal(transport.lastWrite[6], 0); + assert.equal(transport.lastWrite[7], 0); + assert.equal(transport.lastWrite[8], 0); + assert.equal(transport.lastWrite[9], END_SYSEX); + + done(); + }); + + it("can send a accelStepper stop", function(done) { + board.accelStepperStop(0); + + assert.equal(transport.lastWrite[0], START_SYSEX); + assert.equal(transport.lastWrite[1], ACCELSTEPPER); + assert.equal(transport.lastWrite[2], 5); + assert.equal(transport.lastWrite[3], 0); + assert.equal(transport.lastWrite[4], END_SYSEX); + done(); + }); + + it("can send a accelStepper reportPosition", function(done) { + board.accelStepperReportPosition(0); + + assert.equal(transport.lastWrite[0], START_SYSEX); + assert.equal(transport.lastWrite[1], ACCELSTEPPER); + assert.equal(transport.lastWrite[2], 6); + assert.equal(transport.lastWrite[3], 0); + assert.equal(transport.lastWrite[4], END_SYSEX); + done(); + }); + + it("can send a accelStepper set speed", function(done) { + board.accelStepperSpeed(0, 123.4); + + assert.equal(transport.lastWrite[0], START_SYSEX); + assert.equal(transport.lastWrite[1], ACCELSTEPPER); + assert.equal(transport.lastWrite[2], 9); + assert.equal(transport.lastWrite[3], 0); + assert.equal(transport.lastWrite[4], 82); + assert.equal(transport.lastWrite[5], 9); + assert.equal(transport.lastWrite[6], 0); + assert.equal(transport.lastWrite[7], 40); + assert.equal(transport.lastWrite[8], END_SYSEX); + done(); + }); + + it("can send a accelStepper set acceleration", function(done) { + board.accelStepperAcceleration(0, 199.9); + + assert.equal(transport.lastWrite[0], START_SYSEX); + assert.equal(transport.lastWrite[1], ACCELSTEPPER); + assert.equal(transport.lastWrite[2], 8); + assert.equal(transport.lastWrite[3], 0); + assert.equal(transport.lastWrite[4], 24); + assert.equal(transport.lastWrite[5], 1); + assert.equal(transport.lastWrite[6], 122); + assert.equal(transport.lastWrite[7], 28); + assert.equal(transport.lastWrite[8], END_SYSEX); + done(); + }); + + it("can configure a multiStepper", function(done) { + board.multiStepperConfig({ groupNum: 0, devices: [0, 1, 2] }); + + assert.equal(transport.lastWrite[0], START_SYSEX); + assert.equal(transport.lastWrite[1], ACCELSTEPPER); + assert.equal(transport.lastWrite[2], 0x20); + assert.equal(transport.lastWrite[3], 0); + assert.equal(transport.lastWrite[4], 0); + assert.equal(transport.lastWrite[5], 1); + assert.equal(transport.lastWrite[6], 2); + assert.equal(transport.lastWrite[7], END_SYSEX); + done(); + }); + + it("can send a multiStepper stop", function(done) { + board.multiStepperStop(0); + + assert.equal(transport.lastWrite[0], START_SYSEX); + assert.equal(transport.lastWrite[1], ACCELSTEPPER); + assert.equal(transport.lastWrite[2], 0x23); + assert.equal(transport.lastWrite[3], 0); + assert.equal(transport.lastWrite[4], END_SYSEX); + done(); + }); + + it("can send a multiStepper to", function(done) { + board.multiStepperTo(0, [200, 400, 600], function() { + done(); + }); + + assert.equal(transport.lastWrite[0], START_SYSEX); + assert.equal(transport.lastWrite[1], ACCELSTEPPER); + assert.equal(transport.lastWrite[2], 0x21); + assert.equal(transport.lastWrite[3], 0); + assert.equal(transport.lastWrite[4], 72); + assert.equal(transport.lastWrite[5], 1); + assert.equal(transport.lastWrite[6], 0); + assert.equal(transport.lastWrite[7], 0); + assert.equal(transport.lastWrite[8], 0); + assert.equal(transport.lastWrite[9], 16); + assert.equal(transport.lastWrite[10], 3); + assert.equal(transport.lastWrite[11], 0); + assert.equal(transport.lastWrite[12], 0); + assert.equal(transport.lastWrite[13], 0); + assert.equal(transport.lastWrite[14], 88); + assert.equal(transport.lastWrite[15], 4); + assert.equal(transport.lastWrite[16], 0); + assert.equal(transport.lastWrite[17], 0); + assert.equal(transport.lastWrite[18], 0); + assert.equal(transport.lastWrite[19], END_SYSEX); + + transport.emit("data", [START_SYSEX]); + transport.emit("data", [ACCELSTEPPER]); + transport.emit("data", [0x24]); + transport.emit("data", [0]); + transport.emit("data", [END_SYSEX]); + + }); + + it("can send a multiStepper to w/o a callback", function(done) { + board.multiStepperTo(0, [200, 400, 600]); + + assert.equal(transport.lastWrite[0], START_SYSEX); + assert.equal(transport.lastWrite[1], ACCELSTEPPER); + assert.equal(transport.lastWrite[2], 0x21); + assert.equal(transport.lastWrite[3], 0); + assert.equal(transport.lastWrite[4], 72); + assert.equal(transport.lastWrite[5], 1); + assert.equal(transport.lastWrite[6], 0); + assert.equal(transport.lastWrite[7], 0); + assert.equal(transport.lastWrite[8], 0); + assert.equal(transport.lastWrite[9], 16); + assert.equal(transport.lastWrite[10], 3); + assert.equal(transport.lastWrite[11], 0); + assert.equal(transport.lastWrite[12], 0); + assert.equal(transport.lastWrite[13], 0); + assert.equal(transport.lastWrite[14], 88); + assert.equal(transport.lastWrite[15], 4); + assert.equal(transport.lastWrite[16], 0); + assert.equal(transport.lastWrite[17], 0); + assert.equal(transport.lastWrite[18], 0); + assert.equal(transport.lastWrite[19], END_SYSEX); + + done(); + + }); + + it("can receive a stepper position", function(done) { + board.once("stepper-position-0", function(value) { + assert.equal(value, 1234); + done(); + }); + + transport.emit("data", [START_SYSEX]); + transport.emit("data", [ACCELSTEPPER]); + transport.emit("data", [0x06]); + transport.emit("data", [0]); + transport.emit("data", [82]); + transport.emit("data", [9]); + transport.emit("data", [0]); + transport.emit("data", [0]); + transport.emit("data", [0]); + transport.emit("data", [END_SYSEX]); + + }); + + it("can receive a multStepper done", function(done) { + board.once("multi-stepper-done-0", function() { + done(); + }); + + transport.emit("data", [START_SYSEX]); + transport.emit("data", [ACCELSTEPPER]); + transport.emit("data", [0x24]); + transport.emit("data", [0]); + transport.emit("data", [END_SYSEX]); + + }); + it("must be able to send a 1-wire config with parasitic power enabled", function(done) { board.sendOneWireConfig(1, true); assert.equal(transport.lastWrite[0], START_SYSEX); @@ -3287,6 +3693,70 @@ describe("Board: lifecycle", function() { }); }); +describe("Board number format helpers", function() { + + it("must encode 32 bit signed integers", function(done) { + var value = Board.encode32BitSignedInteger(5786); + assert.deepEqual(value, [ 26, 45, 0, 0, 0 ]); + done(); + }); + + it("must encode 32 bit signed integers when they are negative", function(done) { + var value = Board.encode32BitSignedInteger(-5786); + assert.deepEqual(value, [ 26, 45, 0, 0, 8 ]); + done(); + }); + + it("must decode 32 bit signed integers", function(done) { + var value = Board.decode32BitSignedInteger([ 26, 45, 0, 0, 0 ]); + assert.equal(value, 5786); + done(); + }); + + it("must decode 32 bit signed integers when they are negative", function(done) { + var value = Board.decode32BitSignedInteger([ 26, 45, 0, 0, 8 ]); + assert.equal(value, -5786); + done(); + }); + + it("must encode custom floats", function(done) { + var value = Board.encodeCustomFloat(123.456); + assert.deepEqual(value, [ 0, 45, 75, 28 ]); + done(); + }); + + it("must encode custom floats (even when they are integers)", function(done) { + var value = Board.encodeCustomFloat(100); + assert.deepEqual(value, [ 1, 0, 0, 52 ]); + done(); + }); + + it("must encode custom floats when they are negative", function(done) { + var value = Board.encodeCustomFloat(-7321.783); + assert.deepEqual(value, [ 54, 113, 62, 99 ]); + done(); + }); + + it("must encode custom floats when they are less than 1", function(done) { + var value = Board.encodeCustomFloat(0.000325); + assert.deepEqual(value, [ 79, 46, 70, 5 ]); + done(); + }); + + it("must decode custom floats", function(done) { + var value = Board.decodeCustomFloat([ 110, 92, 44, 32 ]); + assert.equal(value, 732.782); + done(); + }); + + it("must decode custom floats when they are negative", function(done) { + var value = Board.decodeCustomFloat([ 110, 92, 44, 96 ]); + assert.equal(value, -732.782); + done(); + }); + +}); + describe("Board.encode/Board.decode", function() { describe("Board.encode", function() {