diff --git a/.DS_Store b/.DS_Store new file mode 100644 index 0000000..2c24b54 Binary files /dev/null and b/.DS_Store differ diff --git a/.gitignore b/.gitignore index 287da1f..6df6fcc 100644 --- a/.gitignore +++ b/.gitignore @@ -35,3 +35,5 @@ node_modules #Intellij settings .idea + +.DS_Store diff --git a/.jshintrc b/.jshintrc new file mode 100644 index 0000000..ded0fd8 --- /dev/null +++ b/.jshintrc @@ -0,0 +1,108 @@ + + +{ + // JSHint Default Configuration File (as on JSHint website) + // See http://jshint.com/docs/ for more details + + "maxerr" : 25, // {int} Maximum error before stopping + + // Enforcing + "bitwise" : true, // true: Prohibit bitwise operators (&, |, ^, etc.) + "camelcase" : false, // true: Identifiers must be in camelCase + "curly" : true, // true: Require {} for every new block or scope + "eqeqeq" : true, // true: Require triple equals (===) for comparison + "forin" : true, // true: Require filtering for..in loops with obj.hasOwnProperty() + "immed" : true, // true: Require immediate invocations to be wrapped in parens e.g. `(function () { } ());` + "indent" : 4, // {int} Number of spaces to use for indentation + "latedef" : true, // true: Require variables/functions to be defined before being used + "newcap" : true, // true: Require capitalization of all constructor functions e.g. `new F()` + "noarg" : true, // true: Prohibit use of `arguments.caller` and `arguments.callee` + "noempty" : true, // true: Prohibit use of empty blocks + "nonew" : true, // true: Prohibit use of constructors for side-effects (without assignment) + "plusplus" : true, // true: Prohibit use of `++` & `--` + "quotmark" : false, // Quotation mark consistency: + // false : do nothing (default) + // true : ensure whatever is used is consistent + // "single" : require single quotes + // "double" : require double quotes + "undef" : true, // true: Require all non-global variables to be declared (prevents global leaks) + "unused" : true, // true: Require all defined variables be used + "strict" : false, // true: Requires all functions run in ES5 Strict Mode + "maxparams" : false, // {int} Max number of formal params allowed per function + "maxdepth" : 5, // {int} Max depth of nested blocks (within functions) + "maxstatements" : false, // {int} Max number statements per function + "maxcomplexity" : false, + "maxlen" : 250, // {int} Max number of characters per line + + // Relaxing + "asi" : false, // true: Tolerate Automatic Semicolon Insertion (no semicolons) + "boss" : false, // true: Tolerate assignments where comparisons would be expected + "debug" : false, // true: Allow debugger statements e.g. browser breakpoints. + "eqnull" : false, // true: Tolerate use of `== null` + "es5" : false, // true: Allow ES5 syntax (ex: getters and setters) + "esnext" : false, // true: Allow ES.next (ES6) syntax (ex: `const`) + "moz" : false, // true: Allow Mozilla specific syntax (extends and overrides esnext features) + // (ex: `for each`, multiple try/catch, function expression…) + "evil" : false, // true: Tolerate use of `eval` and `new Function()` + "expr" : false, // true: Tolerate `ExpressionStatement` as Programs + "funcscope" : false, // true: Tolerate defining variables inside control statements" + "globalstrict" : false, // true: Allow global "use strict" (also enables 'strict') + "iterator" : false, // true: Tolerate using the `__iterator__` property + "lastsemic" : false, // true: Tolerate omitting a semicolon for the last statement of a 1-line block + "laxbreak" : true, // true: Tolerate possibly unsafe line breakings + "laxcomma" : false, // true: Tolerate comma-first style coding + "loopfunc" : false, // true: Tolerate functions being defined in loops + "multistr" : false, // true: Tolerate multi-line strings + "proto" : false, // true: Tolerate using the `__proto__` property + "scripturl" : false, // true: Tolerate script-targeted URLs + "shadow" : false, // true: Allows re-define variables later in code e.g. `var x=1; x=2;` + "sub" : false, // true: Tolerate using `[]` notation when it can still be expressed in dot notation + "supernew" : false, // true: Tolerate `new function () { ... };` and `new Object;` + "validthis" : false, // true: Tolerate using this in a non-constructor function + + // Environments + "browser" : true, // Web Browser (window, document, etc) + "couch" : false, // CouchDB + "devel" : true, // Development/debugging (alert, confirm, etc) + "dojo" : false, // Dojo Toolkit + "jquery" : true, // jQuery + "mootools" : false, // MooTools + "node" : false, // Node.js + "nonstandard" : false, // Widely adopted globals (escape, unescape, etc) + "prototypejs" : false, // Prototype and Scriptaculous + "rhino" : false, // Rhino + "worker" : false, // Web Workers + "wsh" : false, // Windows Scripting Host + "yui" : false, // Yahoo User Interface + + // Custom Globals + // additional predefined global variables + "globals" : { + "module": true, + "require": true, + "exports": true, + "define": true, + // for when we lint tests + "describe": true, + "xdescribe": true, + "xit": true, + "it": true, + "expect": true, + "beforeEach": true, + "waitsFor": true, + "runs": true, + "afterEach": true, + "jasmine": true, + "__base": true, + "__dirname": true, + "requirejs": true, + "global": true, + "process": true, + "morph": true, + "React": true, + "spyOn": true, + "Promise": true, + "twttr": true, + "instgrm": true + } +} diff --git a/README.md b/README.md index a413fbd..f25ce35 100644 --- a/README.md +++ b/README.md @@ -10,6 +10,8 @@ Not yet on npm so you'll have to do it the good'ol fasioned way with a cheeky gi * `bb8 setup` * Use commands below +If you are struggling to connect to BB8 it might be worth checking out the troubleshoot section of @saraford's [blog post](https://medium.com/@saraford/how-to-have-hubot-in-slack-send-commands-to-bb-8-700d2f3c953d#.4zjscutl83). + # Commands ### Utility Commands @@ -107,6 +109,50 @@ Obviously you wouldn’t pass your OAuth information like this (BB8 Commander su A suite difference between native commands and custom commands is that native commands that require multiple parameters will be passed as an array whilst custom commands will be objects. The reason for this is custom commands are key value pairs due to them sharing the same code as the CLI tool. +### Stopping a custom command with the express server. +Some custom commands such as the desk-buddy command or the weather command loop forever until you tell BB8 to stop via the express server. + +To stop a previous BB8 Command send the following POST Request. This will keep the express server running but will stop BB8 doing whatever he's doing. + +Post Request - localhost:3000/ + +Request Body + +``` +{ + "mode":"custom", + "command":"stop" +} +``` + +### Using BB8 Commander in your own projects. +It's cool being able to run a tool from your terminal but it's even cooler to be able to extend and build your own applications. + +Run `npm install bb8-commander --save` within your projects root directory and here is some example code. + +Some commands such as `disco` return a setInterval ID. This allows you to stop a command from continuously running by running `clearInterval(id)` + +``` +var bb8 = require('bb8-commander'); + +// Used to create a .bb8config file within your users home directory. +bb8.setup(); + +var name = 'express'; +var options = { + port: 4000 +}; + +// Used to execute the command express +bb8.executeCommand(name, options); + +// Used to execute the command disco +var id = bb8.executeCommand('disco'); + +// Used to cancel the disco command. +clearInterval(id); +``` + # Examples * [How to have Hubot talk to BB-8 using Express Server](https://medium.com/@saraford/how-to-have-hubot-in-slack-send-commands-to-bb-8-700d2f3c953d) by [@saraford](https://github.com/saraford) diff --git a/bin/index.js b/bin/index.js index 404e196..36f0558 100644 --- a/bin/index.js +++ b/bin/index.js @@ -17,7 +17,7 @@ program .command('disconnect') .description('Command to disconnect from your BB8 Unit') .action(function () { - executeCommand('disconnect'); + executeCommand.connectAndSendCommand('disconnect'); }); // Real Actions @@ -26,14 +26,14 @@ program .command("dance") .description("dance dance BB-8") .action(function () { - executeCommand("dance"); + executeCommand.connectAndSendCommand("dance"); }); program .command('disco') .description('Command to make your BB8 Unit A disco ball') .action(function () { - executeCommand('disco'); + executeCommand.connectAndSendCommand('disco'); }); program @@ -43,7 +43,7 @@ program .option('-cc, --country ', 'Country name such as uk') .option('-t, --access-token ', 'API Key') .action(function(options) { - executeCommand('weather', options); + executeCommand.connectAndSendCommand('weather', options); }); program @@ -56,7 +56,7 @@ program .command('roll') .description('BB8 will roll!') .action(function () { - executeCommand('roll'); + executeCommand.connectAndSendCommand('roll'); }); @@ -70,7 +70,7 @@ program .option('--access-token-key ', 'Twitter api access token key') .option('--access-token-secret ', 'Twitter api access token secret') .action(function (options) { - executeCommand('tweet', options); + executeCommand.connectAndSendCommand('tweet', options); }); program @@ -78,35 +78,35 @@ program .description('Command to setup express server') .option('-p, --port ', 'Port to run express on. Defaults to 3000') .action(function (options) { - executeCommand('express', options); + executeCommand.connectAndSendCommand('express', options); }); program .command('desk-buddy') .description('Command to keep you company whilst working at your desk. Place your BB8 in the charging station.') .action(function (options) { - executeCommand('desk-buddy'); + executeCommand.connectAndSendCommand('desk-buddy'); }); program .command('power') .description('Command to get the power state of the BB-8') .action(function (options) { - executeCommand('power'); + executeCommand.connectAndSendCommand('power'); }); program .command('gestures') .description('Some gestures for your BB-8') .action(function (options) { - executeCommand('gestures'); + executeCommand.connectAndSendCommand('gestures'); }); program .command('drive') .description('Command to accept keyboard input--use arrow keys') .action(function (options) { - executeCommand('drive'); + executeCommand.connectAndSendCommand('drive'); }); try { diff --git a/commands/dance.js b/commands/dance.js index 45aefed..c975ca4 100644 --- a/commands/dance.js +++ b/commands/dance.js @@ -1,4 +1,5 @@ module.exports = function (bb8) { + console.log("BB-8 got moves!!"); var movesTimer = setInterval(function() { @@ -17,4 +18,5 @@ module.exports = function (bb8) { clearInterval(colorTimer); }, 5000); + return false; }; diff --git a/commands/desk-buddy.js b/commands/desk-buddy.js index dedc1bc..016dba6 100644 --- a/commands/desk-buddy.js +++ b/commands/desk-buddy.js @@ -13,7 +13,7 @@ module.exports = function (bb8) { var partiallyAppliedMoveHead = _.partial(moveHead, bb8); - setInterval(partiallyAppliedMoveHead, 4000); + return setInterval(partiallyAppliedMoveHead, 4000); }; diff --git a/commands/disco.js b/commands/disco.js index d8b9682..50edec4 100644 --- a/commands/disco.js +++ b/commands/disco.js @@ -1,8 +1,10 @@ module.exports = function (bb8) { + console.log('Let\'s Party!!'); bb8.randomColor(); - setInterval(function () { + + return setInterval(function () { bb8.randomColor(); }, 1000); }; diff --git a/commands/drive.js b/commands/drive.js index dea5e4d..ad5c0cc 100644 --- a/commands/drive.js +++ b/commands/drive.js @@ -16,7 +16,7 @@ module.exports = function (bb8) { console.log("starting to listen for arrow key presses"); stdin.on("keypress", function(ch,key) { - //console.log('got "keypress"', key); + bb8.color('#000000'); if(key && key.name === 'e') { @@ -29,33 +29,35 @@ module.exports = function (bb8) { }, 300); }); } + if(key && key.name === 'q') { bb8.finishCalibration(); } + if(key && key.name === 'w'){ - //console.log('up'); bb8.stop(); bb8.roll(150, 0); } + if(key && key.name === 'd'){ - //console.log('right'); bb8.stop(); bb8.roll(150, 90); } + if(key && key.name === 's'){ - //console.log('down'); bb8.stop(); bb8.roll(150, 180); } + if(key && key.name === 'a'){ - //console.log('left'); bb8.stop(); bb8.roll(150, 270); } + if(key && key.name === 'space'){ - //console.log('space'); bb8.stop(); } + if (key && key.ctrl && key.name === 'c') { process.stdin.pause(); process.exit(); @@ -64,4 +66,6 @@ module.exports = function (bb8) { stdin.setRawMode(true); stdin.resume(); + + return false; }; diff --git a/commands/express.js b/commands/express.js index 9c84593..7813ddb 100644 --- a/commands/express.js +++ b/commands/express.js @@ -2,77 +2,85 @@ var bb8 = require('../libs/bb8-instance')(), config = require('../libs/bb8-instance').config, expressInstance = require('../libs/express'), executeCustomCommand = require('../libs/execute-command'), - _ = require('lodash'); + _ = require('lodash'), + runningCommandIds = []; -var callbackFactory = function(res){ - return function(err, data){ - if(!err) { - res.send({data: data}); +var callbackFactory = function (res) { + return function (err, data) { + if (!err) { + res.send({ data: data }); } else { - res.send({error: err}); + res.send({ error: err }); } }; }; -var spheroCommandExecuter = function(bb8, requestBody, res) { +var stopCustomCommands = function () { - if(_.isString(requestBody.value)) { + var commandsStopped = runningCommandIds.map(function (commandObj) { + clearInterval(commandObj.commandRunningId); + return commandObj.commandName; + }); - bb8[requestBody.command](requestBody.value, callbackFactory(res)); - - } else if(_.isArray(requestBody.value)) { + runningCommandIds = []; + + return commandsStopped; +}; +var spheroCommandExecuter = function (bb8, requestBody, res) { + if (_.isString(requestBody.value)) { + bb8[requestBody.command](requestBody.value, callbackFactory(res)); + } else if (_.isArray(requestBody.value)) { requestBody.value.push(callbackFactory(res)); - bb8[requestBody.command].apply(this, requestBody.value); - } else { - bb8[requestBody.command](callbackFactory(res)); - } - }; -var customCommandExecuter = function(bb8, requestBody, res){ +var customCommandExecuter = function (bb8, requestBody, res) { - if(_.isString(requestBody.value) || _.isObject(requestBody.value)) { + var commandId; + var commandName = requestBody.command; + var commandValue = requestBody.value; - executeCustomCommand.alreadyConnectedSingleValue(bb8, requestBody.command, requestBody.value); - - } else if(_.isArray(requestBody.value)) { - - executeCustomCommand.alreadyConnectedMultipleValues(bb8, requestBody.command, requestBody.value); + if (commandName === 'stop') { + var stoppedCommandsArray = stopCustomCommands(); + res.send('Commands stopped - ' + stoppedCommandsArray.join(', ')); + return; + } + if (_.isString(commandValue) || _.isObject(commandValue)) { + commandId = executeCustomCommand.alreadyConnectedSingleValue(bb8, commandName, commandValue); + } else if (_.isArray(commandValue)) { + commandId = executeCustomCommand.alreadyConnectedMultipleValues(bb8, commandName, commandValue); } else { - - executeCustomCommand.alreadyConnectedSingleValue(bb8, requestBody.command, {}); - + commandId = executeCustomCommand.alreadyConnectedSingleValue(bb8, commandName, {}); } - res.send('Command Executed - ' + requestBody.command); + if (commandId) { + runningCommandIds.push({ + commandName: commandName, + commandValue: commandValue, + commandRunningId: commandId + }); + } + res.send('Command Executed - ' + commandName); + return; }; -module.exports = function(bb8, options) { - +module.exports = function (bb8, options) { expressInstance(function (req, res) { var requestBody = req.body; - if(requestBody.command && requestBody.mode === 'sphero') { - + if (requestBody.command && requestBody.mode === 'sphero') { spheroCommandExecuter(bb8, req.body, res); - - } else if(requestBody.command && requestBody.mode === 'custom') { - + } else if (requestBody.command && requestBody.mode === 'custom') { customCommandExecuter(bb8, req.body, res); - } else { - res.send('Command is invalid'); - } - }, options); }; diff --git a/commands/gestures.js b/commands/gestures.js index deba830..e80fc8d 100644 --- a/commands/gestures.js +++ b/commands/gestures.js @@ -106,4 +106,5 @@ module.exports = function (bb8) { stdin.setRawMode(true); stdin.resume(); + return false; }; diff --git a/commands/roll.js b/commands/roll.js index 66930a7..f944cea 100644 --- a/commands/roll.js +++ b/commands/roll.js @@ -2,11 +2,9 @@ module.exports = function (bb8) { console.log('Let\'s Roll!!'); - setInterval(function () { - + return setInterval(function () { var direction = Math.floor(Math.random() * 360); bb8.roll(150, direction); - }, 1000); }; diff --git a/commands/setup.js b/commands/setup.js index bd3018d..35745d9 100644 --- a/commands/setup.js +++ b/commands/setup.js @@ -43,7 +43,5 @@ module.exports = function() { console.log('UUID: ' + peripheral.uuid); console.log('Local Name: ' + peripheral.advertisement.localName); } - }); - }; diff --git a/commands/tweet.js b/commands/tweet.js index cf05a22..e93f6fe 100644 --- a/commands/tweet.js +++ b/commands/tweet.js @@ -44,6 +44,7 @@ var executeTwitterCommand = function (bb8, options) { }; module.exports = function (bb8, options) { + var intervalDelay = options.delay || 10000; console.log('Let\'s get tweets!'); @@ -52,7 +53,7 @@ module.exports = function (bb8, options) { commanderExecuter(); - setInterval(function () { + return setInterval(function () { commanderExecuter(); }, intervalDelay); }; diff --git a/commands/weather.js b/commands/weather.js index eb974a3..b0d2a5c 100644 --- a/commands/weather.js +++ b/commands/weather.js @@ -12,7 +12,7 @@ module.exports = function (bb8, options) { console.log('Rain Rain go away, come back another day!'); // Every 10 seconds, lets poll the weather - setInterval(function () { + return setInterval(function () { weatherRequester(function (error, weatherData) { @@ -37,4 +37,5 @@ module.exports = function (bb8, options) { console.log('WEATHER_KEY env for openweather is not present'); } + return false; }; diff --git a/libs/execute-command.js b/libs/execute-command.js index 482171e..1ef1125 100644 --- a/libs/execute-command.js +++ b/libs/execute-command.js @@ -1,32 +1,43 @@ var bb8 = require('./bb8-instance')(), - appRootPath = require('app-root-path'), - _ = require('lodash'); + appRootPath = require('app-root-path'), + _ = require('lodash'); -module.exports = function (command, options) { +module.exports.connectAndSendCommand = function (command, options) { + if (bb8) { bb8.connect(function () { require(appRootPath + '/commands/' + command)(bb8, options); }); + + return; } + + console.log("BB8 Is not Connected"); }; -module.exports.alreadyConnected = function(bb8, command) { +module.exports.alreadyConnected = function (bb8, command) { + if (bb8) { - require(appRootPath + '/commands/' + command)(bb8); + return require(appRootPath + '/commands/' + command)(bb8); } + + console.log("BB8 Is not Connected"); }; -module.exports.alreadyConnectedSingleValue = function(bb8, command, options) { +module.exports.alreadyConnectedSingleValue = function (bb8, command, options) { + if (bb8) { - require(appRootPath + '/commands/' + command)(bb8, options); + return require(appRootPath + '/commands/' + command)(bb8, options); } + + console.log("BB8 Is not Connected"); }; -module.exports.alreadyConnectedMultipleValues = function(bb8, command, options) { +module.exports.alreadyConnectedMultipleValues = function (bb8, command, options) { if (bb8) { - var parameters = _.union([bb8], options); - require(appRootPath + '/commands/' + command).apply(this, parameters); + return require(appRootPath + '/commands/' + command).apply(this, _.union([bb8], options)); } + console.log("BB8 Is not Connected"); }; diff --git a/libs/express.js b/libs/express.js index 6b51779..ff19cbe 100644 --- a/libs/express.js +++ b/libs/express.js @@ -7,11 +7,8 @@ module.exports = function(callback, options) { var port = options.port || 3000; app.use(bodyParser.json()); - app.post('/', callback); - app.listen(port, function () { console.log( 'Server listening on port ' + port ); }); - } diff --git a/package.json b/package.json index 9affb13..e928a34 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "bb8-commander", - "version": "2.0.1", + "version": "2.1.0", "description": "A Node CLI Tool for Sphero BB8 Robot.", "main": "index.js", "bin": {