Merge pull request #221 from hybridgroup/add/commands

Add New Command Structure
This commit is contained in:
Ron Evans 2014-08-10 15:47:36 -07:00
commit bde6132377
11 changed files with 166 additions and 77 deletions

View File

@ -1,12 +1,16 @@
var Cylon = require('../..'); var Cylon = require('../..');
Cylon.api();
Cylon.robot({ Cylon.robot({
name: 'test',
connection: { name: 'loopback', adaptor: 'loopback' }, connection: { name: 'loopback', adaptor: 'loopback' },
device: { name: 'ping', driver: 'ping' }, device: { name: 'ping', driver: 'ping' },
work: function() { work: function(my) {
every((1).seconds(), function(){ every((1).seconds(), function(){
console.log("Hello, human!") console.log("Hello, human!")
console.log(my.ping.ping());
}); });
after((5).seconds(), function(){ after((5).seconds(), function(){

View File

@ -1,29 +1,25 @@
var Cylon = require('../..'); var Cylon = require('../..');
Cylon.api({ host: '0.0.0.0', port: '8080' }); Cylon.api();
var MyRobot = (function() { Cylon.robot({
function MyRobot() {} name: 'Frankie',
MyRobot.prototype.commands = ["relax"]; sayRelax: function() {
return this.name + " says relax";
},
MyRobot.prototype.relax = function() { work: function(my) {
return "" + this.name + " says relax"; every((5).seconds(), function() {
}; console.log(my.sayRelax());
MyRobot.prototype.work = function(me) {
every((1).seconds(), function() {
console.log(me.name);
}); });
},
commands: function() {
return {
say_relax: this.sayRelax
}; };
}
return MyRobot; });
})();
var robot = new MyRobot;
robot.name = "frankie";
Cylon.robot(robot);
Cylon.start(); Cylon.start();

View File

@ -14,48 +14,40 @@ First, let's make sure to load up Cylon:
var Cylon = require('../..'); 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 And with that done let's define our robot:
robot's logic:
var MyRobot = (function() { Cylon.robot({
function MyRobot() {} 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"]; sayRelax: function() {
return this.name + " says 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";
};
Since we don't really care what actual work this robot does, but need to keep it 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) { work: function(my) {
every((1).seconds(), function() { every((5).seconds(), function() {
console.log(me.name); console.log(my.sayRelax());
}); });
},
We'll then set up the `commands` object, which tells the API which commands the
Robot has should be publically accessible:
commands: function() {
return {
say_relax: this.sayRelax
}; };
}
return MyRobot; });
})();
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);
And now that all the pieces are in place, we can start up Cylon: And now that all the pieces are in place, we can start up Cylon:

View File

@ -64,12 +64,12 @@ router.get("/robots/:robot", load, function(req, res) {
}); });
router.get("/robots/:robot/commands", 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) { router.all("/robots/:robot/commands/:command", load, function(req, res) {
var command = req.params.command; var command = req.robot.commands[req.params.command];
var result = req.robot[command].apply(req.robot, req.commandParams); var result = command.apply(req.robot, req.commandParams);
res.json({ result: result }); res.json({ result: result });
}); });
@ -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) { 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) { router.all("/robots/:robot/devices/:device/commands/:command", load, function(req, res) {
var command = req.params.command; var command = req.device.driver.commands[req.params.command];
var result = command.apply(req.device, req.commandParams);
var result = req.device[command].apply(req.device, req.commandParams);
res.json({ result: result }); res.json({ result: result });
}); });

View File

@ -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); Utils.subclass(Device, EventEmitter);
@ -86,7 +86,7 @@ Device.prototype.toJSON = function() {
name: this.name, name: this.name,
driver: this.driver.constructor.name || this.driver.name, driver: this.driver.constructor.name || this.driver.name,
connection: this.connection.name, connection: this.connection.name,
commands: this.driver.commands, commands: Object.keys(this.driver.commands),
details: this.details details: this.details
}; };
}; };

View File

@ -25,12 +25,12 @@ var Driver = module.exports = function Driver(opts) {
this.name = opts.name; this.name = opts.name;
this.device = opts.device; this.device = opts.device;
this.connection = this.device.connection; this.connection = this.device.connection;
this.commands = {};
}; };
Utils.subclass(Driver, Basestar); Utils.subclass(Driver, Basestar);
Driver.prototype.commands = [];
// Public: Starts up the driver, and triggers the provided callback when done. // Public: Starts up the driver, and triggers the provided callback when done.
// //
// callback - function to run when the driver is started // callback - function to run when the driver is started

View File

@ -74,7 +74,7 @@ var Robot = module.exports = function Robot(opts) {
this.devices = {}; this.devices = {};
this.adaptors = {}; this.adaptors = {};
this.drivers = {}; this.drivers = {};
this.commands = []; this.commands = {};
this.running = false; this.running = false;
this.work = opts.work || opts.play; this.work = opts.work || opts.play;
@ -95,13 +95,31 @@ var Robot = module.exports = function Robot(opts) {
} }
for (var n in opts) { for (var n in opts) {
var func = opts[n], var opt = opts[n],
reserved = ['connection', 'connections', 'device', 'devices', 'work']; reserved = ['connection', 'connections', 'device', 'devices', 'work', 'commands'];
if (reserved.indexOf(n) < 0) { 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); Utils.subclass(Robot, EventEmitter);
@ -143,7 +161,7 @@ Robot.prototype.toJSON = function() {
name: this.name, name: this.name,
connections: connections, connections: connections,
devices: devices, devices: devices,
commands: this.commands commands: Object.keys(this.commands)
}; };
}; };

View File

@ -11,11 +11,12 @@
var Driver = require('../driver'), var Driver = require('../driver'),
Utils = require('../utils'); Utils = require('../utils');
var Ping; var Ping = module.exports = function Ping() {
module.exports = Ping = function Ping() {
Ping.__super__.constructor.apply(this, arguments); Ping.__super__.constructor.apply(this, arguments);
this.commands = ['ping'];
this.commands = {
ping: this.ping.bind(this)
};
}; };
Utils.subclass(Ping, Driver); Utils.subclass(Ping, Driver);
@ -25,4 +26,6 @@ Ping.prototype.ping = function() {
return "pong"; return "pong";
}; };
Ping.driver = function(opts) { return new Ping(opts); }; Ping.driver = function(opts) {
return new Ping(opts);
};

View File

@ -130,7 +130,7 @@ describe("Device", function() {
}); });
it("contains the device's driver commands", 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));
}); });
}); });

View File

@ -31,7 +31,7 @@ describe("Driver", function() {
}); });
it("sets @commands to an empty array by default", function() { it("sets @commands to an empty array by default", function() {
expect(driver.commands).to.be.eql([]); expect(driver.commands).to.be.eql({});
}); });
}); });

View File

@ -84,6 +84,83 @@ describe("Robot", function() {
expect(fn).to.throw(Error, "No connections specified"); 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() { describe("all work and no play", function() {
@ -96,7 +173,7 @@ describe("Robot", function() {
it('makes Jack a dull boy', function() { it('makes Jack a dull boy', function() {
expect(playBot.work).to.be.eql(play); expect(playBot.work).to.be.eql(play);
}) })
}) });
describe("#toJSON", function() { describe("#toJSON", function() {
var bot = new Robot({ var bot = new Robot({
@ -115,7 +192,7 @@ describe("Robot", function() {
}); });
it("contains the robot's commands", 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() { it("contains the robot's devices", function() {