From cf0d24f785c6207f26579bcb45f2906c98fe7c7c Mon Sep 17 00:00:00 2001 From: Andrew Stewart Date: Mon, 5 Jan 2015 12:20:53 -0800 Subject: [PATCH] Add API event publication This update gives Cylon support for the latest changes to the CPPP-IO spec. --- lib/adaptor.js | 2 +- lib/api/routes.js | 56 +++++++++++++++++++++++++++++++++++++---- lib/cylon.js | 33 ++++++++++++++---------- lib/driver.js | 4 ++- lib/robot.js | 3 ++- lib/test/ping.js | 2 ++ spec/lib/cylon.spec.js | 4 +++ spec/lib/driver.spec.js | 44 ++++++++++++++++++++++++++++++++ spec/lib/robot.spec.js | 11 +++++++- 9 files changed, 137 insertions(+), 22 deletions(-) diff --git a/lib/adaptor.js b/lib/adaptor.js index cf157ad..336a762 100644 --- a/lib/adaptor.js +++ b/lib/adaptor.js @@ -35,7 +35,7 @@ var Adaptor = module.exports = function Adaptor(opts) { this.details = {}; _.forEach(opts, function(opt, name) { - if (_.include(["robot", "name", "adaptor"], name)) { + if (_.include(["robot", "name", "adaptor", "events"], name)) { return; } diff --git a/lib/api/routes.js b/lib/api/routes.js index bdc91fe..d484620 100644 --- a/lib/api/routes.js +++ b/lib/api/routes.js @@ -14,6 +14,12 @@ var Cylon = require("../cylon"); var router = module.exports = require("express").Router(); +var eventHeaders = { + "Content-Type": "text/event-stream", + "Connection": "keep-alive", + "Cache-Control": "no-cache" +}; + // Loads up the appropriate Robot/Device/Connection instances, if they are // present in the route params. var load = function load(req, res, next) { @@ -55,6 +61,26 @@ router.get("/", function(req, res) { res.json({ MCP: Cylon.toJSON() }); }); +router.get("/events", function(req, res) { + res.json({ events: Cylon.events }); +}); + +router.get("/events/:event", function(req, res) { + var event = req.params.event; + + res.writeHead(200, eventHeaders); + + var writeData = function(data) { + res.write("data: " + JSON.stringify(data) + "\n\n"); + }; + + Cylon.on(event, writeData); + + res.on("close", function() { + Cylon.removeListener(event, writeData); + }); +}); + router.get("/commands", function(req, res) { res.json({ commands: Object.keys(Cylon.commands) }); }); @@ -81,6 +107,26 @@ router.all("/robots/:robot/commands/:command", load, function(req, res) { router.runCommand(req, res, req.robot, command); }); +router.get("/robots/:robot/events", load, function(req, res) { + res.json({ events: req.robot.events }); +}); + +router.all("/robots/:robot/events/:event", load, function(req, res) { + var event = req.params.event; + + res.writeHead(200, eventHeaders); + + var writeData = function(data) { + res.write("data: " + JSON.stringify(data) + "\n\n"); + }; + + req.robot.on(event, writeData); + + res.on("close", function() { + req.robot.removeListener(event, writeData); + }); +}); + router.get("/robots/:robot/devices", load, function(req, res) { res.json({ devices: req.robot.toJSON().devices }); }); @@ -89,14 +135,14 @@ router.get("/robots/:robot/devices/:device", load, function(req, res) { res.json({ device: req.device }); }); +router.get("/robots/:robot/devices/:device/events", load, function(req, res) { + res.json({ events: req.device.events }); +}); + router.get("/robots/:robot/devices/:device/events/:event", load, function(req, res) { var event = req.params.event; - res.writeHead(200, { - "Content-Type": "text/event-stream", - "Connection": "keep-alive", - "Cache-Control": "no-cache" - }); + res.writeHead(200, eventHeaders); var writeData = function(data) { res.write("data: " + JSON.stringify(data) + "\n\n"); diff --git a/lib/cylon.js b/lib/cylon.js index 0aea4cc..299c74d 100644 --- a/lib/cylon.js +++ b/lib/cylon.js @@ -16,23 +16,27 @@ var Logger = require("./logger"), Utils = require("./utils"), _ = require("./lodash"); -var Cylon = module.exports = { - Logger: Logger, - Driver: require("./driver"), - Adaptor: require("./adaptor"), - Utils: Utils, +var EventEmitter = require("events").EventEmitter; - IO: { - DigitalPin: require("./io/digital-pin"), - Utils: require("./io/utils") - }, +var Cylon = module.exports = new EventEmitter(); - api_instance: null, +Cylon.Logger = Logger; +Cylon.Driver = require("./driver"); +Cylon.Adaptor = require("./adaptor"); +Cylon.Utils = Utils; - robots: {}, - commands: {} +Cylon.IO = { + DigitalPin: require("./io/digital-pin"), + Utils: require("./io/utils") }; +Cylon.api_instance = null; + +Cylon.robots = {}; +Cylon.commands = {}; + +Cylon.events = [ "robot_added", "robot_removed" ]; + // Public: Creates a new Robot // // opts - hash of Robot attributes @@ -61,6 +65,8 @@ Cylon.robot = function robot(opts) { var bot = new Robot(opts); this.robots[bot.name] = bot; + this.emit("robot_added", bot.name); + return bot; }; @@ -134,7 +140,8 @@ Cylon.halt = function halt(callback) { Cylon.toJSON = function() { return { robots: _.invoke(this.robots, "toJSON"), - commands: _.keys(this.commands) + commands: _.keys(this.commands), + events: this.events }; }; diff --git a/lib/driver.js b/lib/driver.js index 3de30f8..37276fd 100644 --- a/lib/driver.js +++ b/lib/driver.js @@ -28,6 +28,7 @@ var Driver = module.exports = function Driver(opts) { this.connection = opts.connection; this.commands = {}; + this.events = []; // some default options this.pin = opts.pin; @@ -36,7 +37,7 @@ var Driver = module.exports = function Driver(opts) { this.details = {}; _.forEach(opts, function(opt, name) { - if (_.include(["robot", "name", "connection", "driver"], name)) { + if (_.include(["robot", "name", "connection", "driver", "events"], name)) { return; } @@ -74,6 +75,7 @@ Driver.prototype.toJSON = function() { driver: this.constructor.name || this.name, connection: this.connection.name, commands: _.keys(this.commands), + events: this.events, details: this.details }; }; diff --git a/lib/robot.js b/lib/robot.js index b839b43..12d4a4d 100644 --- a/lib/robot.js +++ b/lib/robot.js @@ -120,7 +120,8 @@ Robot.prototype.toJSON = function() { name: this.name, connections: _.invoke(this.connections, "toJSON"), devices: _.invoke(this.devices, "toJSON"), - commands: _.keys(this.commands) + commands: _.keys(this.commands), + events: _.isArray(this.events) ? this.events : [] }; }; diff --git a/lib/test/ping.js b/lib/test/ping.js index d88cd70..9eca822 100644 --- a/lib/test/ping.js +++ b/lib/test/ping.js @@ -17,6 +17,8 @@ var Ping = module.exports = function Ping() { this.commands = { ping: this.ping }; + + this.events = ["ping"]; }; Utils.subclass(Ping, Driver); diff --git a/spec/lib/cylon.spec.js b/spec/lib/cylon.spec.js index f0754b5..d88ea58 100644 --- a/spec/lib/cylon.spec.js +++ b/spec/lib/cylon.spec.js @@ -181,5 +181,9 @@ describe("Cylon", function() { it("contains an array of MCP commands", function() { expect(json.commands).to.be.eql(["echo"]); }); + + it("contains an array of MCP events", function() { + expect(json.events).to.be.eql(["robot_added", "robot_removed"]); + }); }); }); diff --git a/spec/lib/driver.spec.js b/spec/lib/driver.spec.js index 5f5e2e4..207b5a4 100644 --- a/spec/lib/driver.spec.js +++ b/spec/lib/driver.spec.js @@ -30,6 +30,10 @@ describe("Driver", function() { expect(driver.commands).to.be.eql({}); }); + it("sets @events to an empty array by default", function() { + expect(driver.events).to.be.eql([]); + }); + it("sets @interval to 10ms by default, or the provided value", function() { expect(driver.interval).to.be.eql(10); @@ -43,6 +47,46 @@ describe("Driver", function() { }); }); + describe("#toJSON", function() { + var driver, json; + + beforeEach(function() { + driver = new Driver({ + connection: { name: "conn" }, + name: "name", + port: "3000" + }); + + json = driver.toJSON(); + }); + + it("returns an object", function() { + expect(json).to.be.a("object"); + }); + + it("contains the driver's name", function() { + expect(json.name).to.eql("name"); + }); + + it("contains the driver's constructor name", function() { + expect(json.driver).to.eql("Driver"); + }); + + it("contains the driver's connection name", function() { + expect(json.connection).to.eql("conn"); + }); + + it("contains the driver's commands", function() { + expect(json.commands).to.eql(Object.keys(driver.commands)); + }); + + it("contains the driver's events, or an empty array", function() { + expect(json.events).to.eql([]); + driver.events = ["hello", "world"]; + expect(driver.toJSON().events).to.be.eql(["hello", "world"]); + }); + }); + describe("#setupCommands", function() { beforeEach(function() { driver.proxyMethods = spy(); diff --git a/spec/lib/robot.spec.js b/spec/lib/robot.spec.js index 9f59862..fa570fc 100644 --- a/spec/lib/robot.spec.js +++ b/spec/lib/robot.spec.js @@ -215,7 +215,9 @@ describe("Robot", function() { devices: { ping: { driver: "ping" } - } + }, + + events: ["hello", "world"] }); var json = bot.toJSON(); @@ -239,6 +241,13 @@ describe("Robot", function() { it("contains the robot's connections", function() { expect(json.connections).to.eql([bot.connections.loopback.toJSON()]); }); + + it("contains the robot's events, or an empty array", function() { + expect(json.events).to.eql(["hello", "world"]); + + var bot = new Robot(); + expect(bot.toJSON().events).to.be.eql([]); + }); }); describe("#connection", function() {