From e6c567a0908205b2cefc2b0c148b255f2093d8a7 Mon Sep 17 00:00:00 2001 From: Andrew Stewart Date: Tue, 5 Aug 2014 18:41:57 -0700 Subject: [PATCH 1/3] Add new command structure --- examples/robot_commands/robot_commands.js | 36 ++++----- .../robot_commands/robot_commands.markdown | 54 ++++++------- lib/api/routes.js | 6 +- lib/driver.js | 4 +- lib/robot.js | 28 +++++-- lib/test/ping.js | 13 +-- test/specs/driver.spec.js | 2 +- test/specs/robot.spec.js | 81 ++++++++++++++++++- 8 files changed, 155 insertions(+), 69 deletions(-) diff --git a/examples/robot_commands/robot_commands.js b/examples/robot_commands/robot_commands.js index b7adf40..65c7c5c 100644 --- a/examples/robot_commands/robot_commands.js +++ b/examples/robot_commands/robot_commands.js @@ -1,29 +1,25 @@ var Cylon = require('../..'); -Cylon.api({ host: '0.0.0.0', port: '8080' }); +Cylon.api(); -var MyRobot = (function() { - function MyRobot() {} +Cylon.robot({ + name: 'Frankie', - MyRobot.prototype.commands = ["relax"]; + sayRelax: function() { + return this.name + " says relax"; + }, - MyRobot.prototype.relax = function() { - return "" + this.name + " says relax"; - }; - - MyRobot.prototype.work = function(me) { - every((1).seconds(), function() { - console.log(me.name); + work: function(my) { + every((5).seconds(), function() { + console.log(my.sayRelax()); }); - }; + }, - return MyRobot; - -})(); - -var robot = new MyRobot; -robot.name = "frankie"; - -Cylon.robot(robot); + commands: function() { + return { + say_relax: this.sayRelax + }; + } +}); Cylon.start(); diff --git a/examples/robot_commands/robot_commands.markdown b/examples/robot_commands/robot_commands.markdown index 0ae7c46..6261b22 100644 --- a/examples/robot_commands/robot_commands.markdown +++ b/examples/robot_commands/robot_commands.markdown @@ -14,48 +14,40 @@ First, let's make sure to load up Cylon: var Cylon = require('../..'); -Now that we've got that, let's set up a custom API port: +Now that we've got that, let's set up the api: - Cylon.api({ host: '0.0.0.0', port: '8080' }); + Cylon.api(); -And with that done let's define our robot. We'll make a class to contain this -robot's logic: +And with that done let's define our robot: - var MyRobot = (function() { - function MyRobot() {} + Cylon.robot({ + name: 'Frankie', -To let the API know what commands this robot has, we need to provide a `commands` array. +The result of this method will be returned to the HTTP client as part of a JSON +object. - MyRobot.prototype.commands = ["relax"]; - -And with that done, we can now define the method. The result of this method will -be returned to the HTTP client as part of a JSON object. - - MyRobot.prototype.relax = function() { - return "" + this.name + " says relax"; - }; + sayRelax: function() { + return this.name + " says relax"); + }, Since we don't really care what actual work this robot does, but need to keep it -busy, we'll just tell it to print it's name every second. +busy, we'll just tell it to print it's name every five seconds. - MyRobot.prototype.work = function(me) { - every((1).seconds(), function() { - console.log(me.name); + work: function(my) { + every((5).seconds(), function() { + console.log(my.sayRelax()); }); - }; + }, - return MyRobot; +We'll then set up the `commands` object, which tells the API which commands the +Robot has should be publically accessible: - })(); - -And with that all done, we can now instantiate our robot: - - var robot = new MyRobot; - -Now we can just give it a name and send it off to Cylon. - - robot.name = "frankie"; - Cylon.robot(robot); + commands: function() { + return { + say_relax: this.sayRelax + }; + } + }); And now that all the pieces are in place, we can start up Cylon: diff --git a/lib/api/routes.js b/lib/api/routes.js index 0cf5ee6..a266a9b 100644 --- a/lib/api/routes.js +++ b/lib/api/routes.js @@ -64,12 +64,12 @@ router.get("/robots/:robot", load, function(req, res) { }); router.get("/robots/:robot/commands", load, function(req, res) { - res.json({ commands: req.robot.commands }); + res.json({ commands: Object.keys(req.robot.commands) }); }); router.all("/robots/:robot/commands/:command", load, function(req, res) { - var command = req.params.command; - var result = req.robot[command].apply(req.robot, req.commandParams); + var command = req.robot.commands[req.params.command]; + var result = command.apply(req.robot, req.commandParams); res.json({ result: result }); }); diff --git a/lib/driver.js b/lib/driver.js index 46df2b4..1af1978 100644 --- a/lib/driver.js +++ b/lib/driver.js @@ -25,12 +25,12 @@ var Driver = module.exports = function Driver(opts) { this.name = opts.name; this.device = opts.device; this.connection = this.device.connection; + + this.commands = {}; }; Utils.subclass(Driver, Basestar); -Driver.prototype.commands = []; - // Public: Starts up the driver, and triggers the provided callback when done. // // callback - function to run when the driver is started diff --git a/lib/robot.js b/lib/robot.js index b831ca1..fdcb99e 100644 --- a/lib/robot.js +++ b/lib/robot.js @@ -74,7 +74,7 @@ var Robot = module.exports = function Robot(opts) { this.devices = {}; this.adaptors = {}; this.drivers = {}; - this.commands = []; + this.commands = {}; this.running = false; this.work = opts.work || opts.play; @@ -95,13 +95,31 @@ var Robot = module.exports = function Robot(opts) { } for (var n in opts) { - var func = opts[n], - reserved = ['connection', 'connections', 'device', 'devices', 'work']; + var opt = opts[n], + reserved = ['connection', 'connections', 'device', 'devices', 'work', 'commands']; if (reserved.indexOf(n) < 0) { - this[n] = func; + this[n] = opt; + } + + if (opts.commands == null && typeof(opt) === 'function') { + this.commands[n] = opt; } } + + if (typeof opts.commands === 'function') { + var result = opts.commands.call(this, this); + if (typeof result === 'object' && !Array.isArray(result)) { + this.commands = result; + } else { + throw new Error("commands must be an object or a function that returns an object"); + } + } + + if (typeof opts.commands === 'object') { + this.commands = opts.commands; + } + }; Utils.subclass(Robot, EventEmitter); @@ -143,7 +161,7 @@ Robot.prototype.toJSON = function() { name: this.name, connections: connections, devices: devices, - commands: this.commands + commands: Object.keys(this.commands) }; }; diff --git a/lib/test/ping.js b/lib/test/ping.js index 91a622b..48b767b 100644 --- a/lib/test/ping.js +++ b/lib/test/ping.js @@ -11,11 +11,12 @@ var Driver = require('../driver'), Utils = require('../utils'); -var Ping; - -module.exports = Ping = function Ping() { +var Ping = module.exports = function Ping() { Ping.__super__.constructor.apply(this, arguments); - this.commands = ['ping']; + + this.commands = { + ping: this.ping + }; }; Utils.subclass(Ping, Driver); @@ -25,4 +26,6 @@ Ping.prototype.ping = function() { return "pong"; }; -Ping.driver = function(opts) { return new Ping(opts); }; +Ping.driver = function(opts) { + return new Ping(opts); +}; diff --git a/test/specs/driver.spec.js b/test/specs/driver.spec.js index 8667bac..61021d3 100644 --- a/test/specs/driver.spec.js +++ b/test/specs/driver.spec.js @@ -31,7 +31,7 @@ describe("Driver", function() { }); it("sets @commands to an empty array by default", function() { - expect(driver.commands).to.be.eql([]); + expect(driver.commands).to.be.eql({}); }); }); diff --git a/test/specs/robot.spec.js b/test/specs/robot.spec.js index fbc7bc1..fe7a7e7 100644 --- a/test/specs/robot.spec.js +++ b/test/specs/robot.spec.js @@ -84,6 +84,83 @@ describe("Robot", function() { expect(fn).to.throw(Error, "No connections specified"); }); }); + + context("if no commands are provided", function() { + var robot; + + beforeEach(function() { + robot = new Robot({ + name: 'NewBot', + otherThings: { more: 'details' }, + sayHello: function() { return "Hello!" } + }); + }); + + it("sets #commands to the additionally provided functions", function() { + expect(robot.commands).to.be.eql({ sayHello: robot.sayHello }); + }); + }); + + context("if a commands function is provided", function() { + var robot; + + beforeEach(function() { + robot = new Robot({ + name: 'NewBot', + + sayHello: function() { return this.name + " says hello" }, + + commands: function() { + return { + say_hello: this.sayHello + } + } + }); + }); + + it("sets #commands to the returned object", function() { + expect(robot.commands.say_hello).to.be.eql(robot.sayHello); + }); + + context("if the function doesn't return an object", function() { + var fn; + beforeEach(function() { + fn = function() { + new Robot({ + name: 'NewBot', + + commands: function() { + return []; + } + }); + } + }); + + it("throws an error", function() { + expect(fn).to.throw(Error, "commands must be an object or a function that returns an object"); + }); + }) + }); + + context("if a commands object is provided", function() { + var robot; + + beforeEach(function() { + robot = new Robot({ + name: 'NewBot', + + sayHello: function() { return this.name + " says hello" }, + + commands: { + say_hello: function() {} + } + }); + }); + + it("sets #commands to the provided object", function() { + expect(robot.commands.say_hello).to.be.a('function'); + }); + }); }); describe("all work and no play", function() { @@ -96,7 +173,7 @@ describe("Robot", function() { it('makes Jack a dull boy', function() { expect(playBot.work).to.be.eql(play); }) - }) + }); describe("#toJSON", function() { var bot = new Robot({ @@ -115,7 +192,7 @@ describe("Robot", function() { }); it("contains the robot's commands", function() { - expect(json.commands).to.eql(bot.commands); + expect(json.commands).to.eql(Object.keys(bot.commands)); }); it("contains the robot's devices", function() { From 7a481f6767f70c72298583d53998f9f4ff2c1096 Mon Sep 17 00:00:00 2001 From: Andrew Stewart Date: Thu, 7 Aug 2014 13:44:25 -0700 Subject: [PATCH 2/3] Correctly execute device commands --- examples/hello/hello.js | 6 +++++- lib/api/routes.js | 7 +++---- lib/device.js | 4 ++-- lib/test/ping.js | 2 +- test/specs/device.spec.js | 2 +- 5 files changed, 12 insertions(+), 9 deletions(-) diff --git a/examples/hello/hello.js b/examples/hello/hello.js index 4be89c5..905f933 100644 --- a/examples/hello/hello.js +++ b/examples/hello/hello.js @@ -1,12 +1,16 @@ var Cylon = require('../..'); +Cylon.api(); + Cylon.robot({ + name: 'test', connection: { name: 'loopback', adaptor: 'loopback' }, device: { name: 'ping', driver: 'ping' }, - work: function() { + work: function(my) { every((1).seconds(), function(){ console.log("Hello, human!") + console.log(my.ping.ping()); }); after((5).seconds(), function(){ diff --git a/lib/api/routes.js b/lib/api/routes.js index a266a9b..1d799a1 100644 --- a/lib/api/routes.js +++ b/lib/api/routes.js @@ -102,13 +102,12 @@ router.get("/robots/:robot/devices/:device/events/:event", load, function(req, r }); router.get("/robots/:robot/devices/:device/commands", load, function(req, res) { - res.json({ commands: req.device.toJSON().commands }); + res.json({ commands: Object.keys(req.device.commands) }); }); router.all("/robots/:robot/devices/:device/commands/:command", load, function(req, res) { - var command = req.params.command; - - var result = req.device[command].apply(req.device, req.commandParams); + var command = req.device.driver.commands[req.params.command]; + var result = command.apply(req.device, req.commandParams); res.json({ result: result }); }); diff --git a/lib/device.js b/lib/device.js index b9d567a..34accab 100644 --- a/lib/device.js +++ b/lib/device.js @@ -45,7 +45,7 @@ var Device = module.exports = function Device(opts) { } } - Utils.proxyFunctionsToObject(this.driver.commands, this.driver, this); + Utils.proxyFunctionsToObject(Object.keys(this.driver.commands), this.driver, this); }; Utils.subclass(Device, EventEmitter); @@ -86,7 +86,7 @@ Device.prototype.toJSON = function() { name: this.name, driver: this.driver.constructor.name || this.driver.name, connection: this.connection.name, - commands: this.driver.commands, + commands: Object.keys(this.driver.commands), details: this.details }; }; diff --git a/lib/test/ping.js b/lib/test/ping.js index 48b767b..133f575 100644 --- a/lib/test/ping.js +++ b/lib/test/ping.js @@ -15,7 +15,7 @@ var Ping = module.exports = function Ping() { Ping.__super__.constructor.apply(this, arguments); this.commands = { - ping: this.ping + ping: this.ping.bind(this) }; }; diff --git a/test/specs/device.spec.js b/test/specs/device.spec.js index 99673e4..300019c 100644 --- a/test/specs/device.spec.js +++ b/test/specs/device.spec.js @@ -130,7 +130,7 @@ describe("Device", function() { }); it("contains the device's driver commands", function() { - expect(json.commands).to.be.eql(driver.commands); + expect(json.commands).to.be.eql(Object.keys(driver.commands)); }); }); From bad414be39eff057968cf9837024e906c0da8ac4 Mon Sep 17 00:00:00 2001 From: Andrew Stewart Date: Thu, 7 Aug 2014 14:00:30 -0700 Subject: [PATCH 3/3] Correctly avoid labelling 'work' as a command --- lib/robot.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/robot.js b/lib/robot.js index fdcb99e..5cfda5d 100644 --- a/lib/robot.js +++ b/lib/robot.js @@ -100,10 +100,10 @@ var Robot = module.exports = function Robot(opts) { if (reserved.indexOf(n) < 0) { this[n] = opt; - } - if (opts.commands == null && typeof(opt) === 'function') { - this.commands[n] = opt; + if (opts.commands == null && typeof(opt) === 'function') { + this.commands[n] = opt; + } } }