UNPKG

vaccom

Version:

Module for controlling Roomba iCreate 2

251 lines (220 loc) 7.18 kB
'use strict'; 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