From d8dec42a32c806005a8770b5510a9d23ad04bb79 Mon Sep 17 00:00:00 2001 From: Donovan Buck Date: Sat, 26 Aug 2017 23:33:11 -0500 Subject: [PATCH] firmataAccelStepper First commit --- examples/stepper-accel.js | 29 ++ examples/stepper-driver.js | 30 ++ examples/stepper-multi.js | 54 ++++ examples/stepper-three-wire.js | 27 ++ lib/firmata.js | 456 ++++++++++++++++++++++++++++++- readme.md | 130 ++++++++- test/unit/firmata.test.js | 482 ++++++++++++++++++++++++++++++++- 7 files changed, 1182 insertions(+), 26 deletions(-) create mode 100644 examples/stepper-accel.js create mode 100644 examples/stepper-driver.js create mode 100644 examples/stepper-multi.js create mode 100644 examples/stepper-three-wire.js diff --git a/examples/stepper-accel.js b/examples/stepper-accel.js new file mode 100644 index 0000000..4107632 --- /dev/null +++ b/examples/stepper-accel.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.FOUR_WIRE, + motorPin1: 4, + motorPin2: 5, + motorPin3: 6, + motorPin4: 7, + stepType: board.STEPPER.STEPTYPE.WHOLE + }); + + board.accelStepperSpeed(0, 300); + board.accelStepperAcceleration(0, 100); + board.accelStepperStep(0, 2000, 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..077d23d --- /dev/null +++ b/examples/stepper-driver.js @@ -0,0 +1,30 @@ +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, 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..c9d252f --- /dev/null +++ b/examples/stepper-multi.js @@ -0,0 +1,54 @@ +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], function() { + + board.accelStepperReportPosition(0, function(value) { + console.log("Stepper 0 position: " + value); + }); + + board.accelStepperReportPosition(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..76d27d9 --- /dev/null +++ b/examples/stepper-three-wire.js @@ -0,0 +1,27 @@ +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, function(position) { + console.log("Current position: " + position); + }); + + }); +}); diff --git a/lib/firmata.js b/lib/firmata.js index 74e470f..ec03920 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, @@ -919,7 +947,7 @@ Board.prototype.pinMode = function(pin, mode) { /** * Asks the arduino to write a value to a digital pin * @param {number} pin The pin you want to write a value to. - * @param {number} value The value you want to write. Must be board.HIGH or board.LOW + * @param {value} value The value you want to write. Must be board.HIGH or board.LOW */ Board.prototype.digitalWrite = function(pin, value) { @@ -1148,13 +1176,13 @@ Board.prototype.sendI2CWriteRequest = function(slaveAddress, bytes) { * Write data to a register * * @param {number} address The address of the I2C device. - * @param {Array} cmdRegOrData An array of bytes + * @param {array} cmdRegOrData An array of bytes * * Write a command to a register * * @param {number} address The address of the I2C device. * @param {number} cmdRegOrData The register - * @param {Array} inBytes An array of bytes + * @param {array} inBytes An array of bytes * */ @@ -1689,6 +1717,327 @@ 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, callback) { + + var data = [ + START_SYSEX, + ACCELSTEPPER, + 0x05, // STEPPER_STOP from firmware + deviceNum, + END_SYSEX + ]; + + this.transport.write(new Buffer(data)); + + if (callback) { + this.once("stepper-done-" + deviceNum, callback); + } + +}; + +/** + * 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, callback) { + + var data = [ + START_SYSEX, + ACCELSTEPPER, + 0x06, // STEPPER_REPORT_POSITION from firmware + deviceNum, + END_SYSEX + ]; + + this.transport.write(new Buffer(data)); + + if (callback) { + this.once("stepper-position-" + deviceNum, callback); + } + +}; + +/** + * 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 +2048,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 +2063,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); @@ -1819,7 +2168,7 @@ Board.prototype.serialConfig = function(options) { /** * Write an array of bytes to the specified serial port. * @param {number} portId The serial port to write to. - * @param {Array} inBytes An array of bytes to write to the serial port. + * @param {array} inBytes An array of bytes to write to the serial port. */ Board.prototype.serialWrite = function(portId, inBytes) { @@ -1959,7 +2308,7 @@ Board.prototype.sysexResponse = function(commandByte, handler) { } Board.SYSEX_RESPONSE[commandByte] = function(board) { - handler.call(board, board.currentBuffer.slice(2, -1)); + handler(board.currentBuffer.slice(2, -1)); }; return this; @@ -1968,7 +2317,7 @@ Board.prototype.sysexResponse = function(commandByte, handler) { /** * Allow user code to send arbitrary sysex messages * - * @param {Array} message The message array is expected to be all necessary bytes + * @param {array} message The message array is expected to be all necessary bytes * between START_SYSEX and END_SYSEX (non-inclusive). It will * be assumed that the data in the message array is * already encoded as 2 7-bit bytes LSB first. @@ -2073,6 +2422,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 c63f27a..1fa7e7e 100644 --- a/readme.md +++ b/readme.md @@ -437,6 +437,134 @@ The `Board` constructor creates an instance that represents a physical board. **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 @@ -487,4 +615,4 @@ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE -SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. \ No newline at end of file diff --git a/test/unit/firmata.test.js b/test/unit/firmata.test.js index 2e2a407..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); @@ -3119,18 +3525,6 @@ describe("Board: lifecycle", function() { transport.emit("data", incoming); }); - it("SYSEX_RESPONSE handler context is board", function(done) { - - var incoming = [START_SYSEX, NON_STANDARD_REPLY, 0, 0, 1, 0, 2, 0, 3, 0, 4, 0, END_SYSEX]; - - board.sysexResponse(NON_STANDARD_REPLY, function(data) { - assert.equal(this, board); - done(); - }); - - transport.emit("data", incoming); - }); - it("fail when overwriting SYSEX_RESPONSE command byte", function(done) { Board.SYSEX_RESPONSE[0xFF] = function() {}; @@ -3299,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() {