vaccom
Version:
Module for controlling Roomba iCreate 2
251 lines (220 loc) • 7.18 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.serialOpen = serialOpen;
exports.serialClose = serialClose;
exports.serialWrite = serialWrite;
exports.wait = wait;
exports.safeMode = safeMode;
exports.passiveMode = passiveMode;
exports.moveForward = moveForward;
exports.stopMotion = stopMotion;
exports.turnClockwise = turnClockwise;
exports.turnCounterClockwise = turnCounterClockwise;
exports.programSong = programSong;
exports.playSong = playSong;
require('babel-polyfill');
var _serialport = require('serialport');
var _debug = require('debug');
var _debug2 = _interopRequireDefault(_debug);
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
function _toConsumableArray(arr) { if (Array.isArray(arr)) { for (var i = 0, arr2 = Array(arr.length); i < arr.length; i++) { arr2[i] = arr[i]; } return arr2; } else { return Array.from(arr); } }
var log = (0, _debug2.default)('Vaccom');
// TODO: Create a constants file later...
var CREATE_2_BAUDRATE = 115200;
/**
* Roomba Commands
*/
var Command = {
START: 0x80,
SAFE: 0x83,
DRIVE: 0x89,
LED: 0x8B,
SONG: 0x8C,
PLAY: 0x8D,
STREAM: 0x94,
SENSORS: 0x8E,
CLEAN: 0x87,
SEEK_DOCK: 0x8F
};
// Special values for movement
var STRAIGHT = 32768;
var CLOCKWISE = -1;
var COUNTER_CLOCKWISE = 1;
var FALLBACK_PORT = '/dev/ttyAMA0';
// TODO: Build wait times into the commands (since they are required)
// TODO: Only export the useable methods, not the raw communication methods...
// TODO: Add eslint
var serialPort = void 0;
function findComPort() {
log('Trying to find communication port');
return new Promise(function (resolve, reject) {
(0, _serialport.list)(function (err, ports) {
if (err) {
return reject(err);
}
var usbComPorts = ports.map(function (port) {
return port.comName;
}).filter(function (name) {
return name.toLowerCase().indexOf('usb') !== -1;
});
if (usbComPorts.length > 0) {
return resolve(usbComPorts[0]);
}
return resolve(FALLBACK_PORT);
});
});
}
/**
* Opens communications for future commands
* @param {String} comPort (not required, if left undefined will try to automatically find a COM port)
*/
function serialOpen(comPort) {
var findPort = comPort ? Promise.resolve(comPort) : findComPort();
return findPort.then(function (port) {
log('Using communication port "' + port + '"');
// Setting global port here for re-use
serialPort = new _serialport.SerialPort(port, {
baudrate: CREATE_2_BAUDRATE
}, false); // this is the openImmediately flag [default is true]
return new Promise(function (resolve, reject) {
serialPort.open(function (err) {
return err ? reject(err) : resolve();
});
});
});
}
/**
* Close communications
*/
function serialClose() {
return new Promise(function (resolve, reject) {
serialPort.close(function (err) {
return err ? reject(err) : resolve();
});
});
}
/**
* Write raw command/data to serial port (not recommended)
* @param {Number} command
* @param {Array/Buffer} data
*/
function serialWrite(command, data) {
var buffer = [command].concat(data || []);
return new Promise(function (resolve, reject) {
serialPort.write(buffer, function (err, result) {
return err ? reject(err) : resolve(result);
});
});
}
/**
* Instructs Roomba to wait to a specified time
* @param {Number} ms milliseconds to wait
*/
function wait(ms) {
return new Promise(function (resolve) {
return setTimeout(resolve, ms);
});
}
/**
* Instructs Rooma to go into safe mode
*/
function safeMode() {
log('starting safe mode');
return serialWrite(Command.SAFE);
}
/**
* Instructs Rooma to go into passive mode
*/
function passiveMode() {
log('starting passive mode');
return serialWrite(Command.START);
}
/**
* Move Roomba Forward
* @param {Number} velocity mm/s
* @param {Number} radius mm (default is straight)
*/
function moveForward(velocity) {
var radius = arguments.length <= 1 || arguments[1] === undefined ? STRAIGHT : arguments[1];
if (velocity < -500 || velocity > 500) {
throw new Error('Must use velocity between -500 and 500 mm/s');
}
if (radius !== STRAIGHT && (radius < -2000 || radius > 2000)) {
throw new Error('Must use radius between -2000 and 2000 mm');
}
log('moving forward with velocity ' + velocity + ' and radius ' + radius);
var velocityBuffer = new Buffer(2);
velocityBuffer.writeInt16BE(velocity);
var radiusBuffer = new Buffer(2);
if (radius === STRAIGHT) {
radiusBuffer.writeUInt16BE(radius);
} else {
radiusBuffer.writeInt16BE(radius);
}
return serialWrite(Command.DRIVE, [].concat(_toConsumableArray(velocityBuffer), _toConsumableArray(radiusBuffer)));
}
/**
* Instructs Rooma to stop all motion
*/
function stopMotion() {
log('stopping motion');
return moveForward(0);
}
/**
* Rotate Roomba clockwise with a specificed velocity
* @param {Number} velocity (default is 100)
*/
function turnClockwise() {
var velocity = arguments.length <= 0 || arguments[0] === undefined ? 100 : arguments[0];
log('rotating clockwise with ' + velocity + ' mm/s velocity');
return moveForward(velocity, CLOCKWISE);
}
/**
* Rotate Roomba counter clockwise with a specificed velocity
* @param {Number} velocity (default is 100)
*/
function turnCounterClockwise() {
var velocity = arguments.length <= 0 || arguments[0] === undefined ? 100 : arguments[0];
log('rotating counter clockwise with ' + velocity + ' mm/s velocity');
return moveForward(velocity, COUNTER_CLOCKWISE);
}
/**
* Program song into roomba memory
* @param {Number} songNumber
* @param {Array} notes note numbers
* @param {Array} durations note durations (must be same length as notes)
*/
function programSong(songNumber, notes, durations) {
// Maybe move this out into its own file... or a music file
// http://www.irobotweb.com/~/media/MainSite/PDFs/About/STEM/Create/iRobot_Roomba_600_Open_Interface_Spec.pdf?la=en
if (notes.length !== durations.length) {
throw new Error('Notes and durations must be of the same length');
}
if (notes.length > 16) {
throw new Error('Songs are limited to 16 notes');
}
var song = Array(notes.length * 2).fill().map(function (_, i) {
return i % 2 === 0 ? notes[i / 2] : durations[(i - 1) / 2];
});
log('programming song ' + songNumber + ' with ' + notes.length + ' notes');
return serialWrite(Command.SONG, [songNumber, notes.length].concat(_toConsumableArray(song)));
}
/**
* Play specified song
* @param {Number} songNumber
*/
function playSong(songNumber) {
if (songNumber < 0 || songNumber > 4) {
throw new Error('Song number can be (0-4)');
}
log('playing song ' + songNumber);
return serialWrite(Command.PLAY, [songNumber]);
// Find a way to wait the correct time here maybe...
}
// TODO: Way down the line... allow users to play MIDI files somehow.
// - Choose one instrument (argument) -> must be linear...
// - Convert notes to roomba notes
// - Break into songs
// - Program/Play songs in sequence