From 372dde00783f4db0b5168dc2fcf71fbd7a8a6bdc Mon Sep 17 00:00:00 2001 From: deadprogram Date: Sat, 23 Nov 2013 17:13:20 -0800 Subject: [PATCH 1/6] Refactoring adaptor and driver initialization to simplify, and fix init overwrite error --- dist/connection.js | 6 +- dist/device.js | 6 +- dist/robot.js | 69 ++++++++++------------ src/connection.coffee | 6 +- src/device.coffee | 10 ++-- src/robot.coffee | 83 ++++++++++++--------------- test/dist/specs/connection.spec.js | 4 +- test/dist/specs/device.spec.js | 8 +-- test/src/specs/connection.spec.coffee | 2 +- test/src/specs/device.spec.coffee | 6 +- 10 files changed, 92 insertions(+), 108 deletions(-) diff --git a/dist/connection.js b/dist/connection.js index 285eff7..931ca8a 100644 --- a/dist/connection.js +++ b/dist/connection.js @@ -38,7 +38,7 @@ this.robot = opts.robot; this.name = opts.name; this.connection_id = opts.id; - this.adaptor = this.requireAdaptor(opts); + this.adaptor = this.initAdaptor(opts); this.port = new Cylon.Port(opts.port); proxyFunctionsToObject(this.adaptor.commands(), this.adaptor, this.self); } @@ -72,9 +72,9 @@ return this.adaptor.disconnect(); }; - Connection.prototype.requireAdaptor = function(opts) { + Connection.prototype.initAdaptor = function(opts) { Logger.debug("Loading adaptor '" + opts.adaptor + "'"); - return this.robot.requireAdaptor(opts.adaptor, this.self, opts); + return this.robot.initAdaptor(opts.adaptor, this.self, opts); }; return Connection; diff --git a/dist/device.js b/dist/device.js index 947e500..51a15d8 100644 --- a/dist/device.js +++ b/dist/device.js @@ -35,7 +35,7 @@ this.name = opts.name; this.pin = opts.pin; this.connection = this.determineConnection(opts.connection) || this.defaultConnection(); - this.driver = this.requireDriver(opts); + this.driver = this.initDriver(opts); proxyFunctionsToObject(this.driver.commands(), this.driver, this.self); } @@ -81,12 +81,12 @@ return first; }; - Device.prototype.requireDriver = function(opts) { + Device.prototype.initDriver = function(opts) { if (opts == null) { opts = {}; } Logger.debug("Loading driver '" + opts.driver + "'"); - return this.robot.requireDriver(opts.driver, this.self, opts); + return this.robot.initDriver(opts.driver, this.self, opts); }; return Device; diff --git a/dist/robot.js b/dist/robot.js index 39bf10c..5cd1d10 100644 --- a/dist/robot.js +++ b/dist/robot.js @@ -39,6 +39,9 @@ opts = {}; } this.registerDriver = __bind(this.registerDriver, this); + this.requireDriver = __bind(this.requireDriver, this); + this.registerAdaptor = __bind(this.registerAdaptor, this); + this.requireAdaptor = __bind(this.requireAdaptor, this); this.stop = __bind(this.stop, this); this.startDevices = __bind(this.startDevices, this); this.startConnections = __bind(this.startConnections, this); @@ -182,64 +185,54 @@ return _results; }; - Robot.prototype.requireAdaptor = function(adaptorName, connection, opts) { + Robot.prototype.initAdaptor = function(adaptorName, connection, opts) { if (opts == null) { opts = {}; } - if (this.robot.adaptors[adaptorName] != null) { - if (typeof this.robot.adaptors[adaptorName] === 'string') { - this.robot.adaptors[adaptorName] = require(this.robot.adaptors[adaptorName]).adaptor({ - name: adaptorName, - connection: connection, - extraParams: opts - }); - } - } else { - require("cylon-" + adaptorName).register(this); - this.robot.adaptors[adaptorName] = require("cylon-" + adaptorName).adaptor({ - name: adaptorName, - connection: connection, - extraParams: opts - }); + return this.robot.requireAdaptor(adaptorName).adaptor({ + name: adaptorName, + connection: connection, + extraParams: opts + }); + }; + + Robot.prototype.requireAdaptor = function(adaptorName) { + if (this.robot.adaptors[adaptorName] == null) { + this.robot.registerAdaptor("cylon-" + adaptorName, adaptorName); + this.robot.adaptors[adaptorName].register(this); } return this.robot.adaptors[adaptorName]; }; Robot.prototype.registerAdaptor = function(moduleName, adaptorName) { - if (this.adaptors[adaptorName] != null) { - return; + if (this.adaptors[adaptorName] == null) { + return this.adaptors[adaptorName] = require(moduleName); } - return this.adaptors[adaptorName] = moduleName; }; - Robot.prototype.requireDriver = function(driverName, device, opts) { + Robot.prototype.initDriver = function(driverName, device, opts) { if (opts == null) { opts = {}; } - if (this.robot.drivers[driverName] != null) { - if (typeof this.robot.drivers[driverName] === 'string') { - this.robot.drivers[driverName] = require(this.robot.drivers[driverName]).driver({ - name: driverName, - device: device, - extraParams: opts - }); - } - } else { - require("cylon-" + driverName).register(this); - this.robot.drivers[driverName] = require("cylon-" + driverName).driver({ - name: driverName, - device: device, - extraParams: opts - }); + return this.robot.requireDriver(driverName).driver({ + name: driverName, + device: device, + extraParams: opts + }); + }; + + Robot.prototype.requireDriver = function(driverName) { + if (this.robot.drivers[driverName] == null) { + this.robot.registerDriver("cylon-" + driverName, driverName); + this.robot.drivers[driverName].register(this); } return this.robot.drivers[driverName]; }; Robot.prototype.registerDriver = function(moduleName, driverName) { - if (this.drivers[driverName] != null) { - return; + if (this.drivers[driverName] == null) { + return this.drivers[driverName] = require(moduleName); } - return this.drivers[driverName] = moduleName; }; return Robot; diff --git a/src/connection.coffee b/src/connection.coffee index 6d8e5c2..49e3c4f 100644 --- a/src/connection.coffee +++ b/src/connection.coffee @@ -35,7 +35,7 @@ namespace 'Cylon', -> @robot = opts.robot @name = opts.name @connection_id = opts.id - @adaptor = @requireAdaptor opts + @adaptor = @initAdaptor opts @port = new Cylon.Port opts.port proxyFunctionsToObject @adaptor.commands(), @adaptor, @self @@ -75,8 +75,8 @@ namespace 'Cylon', -> # adaptorName - module name of adaptor to require # # Returns the set-up adaptor - requireAdaptor: (opts) -> + initAdaptor: (opts) -> Logger.debug "Loading adaptor '#{opts.adaptor}'" - @robot.requireAdaptor(opts.adaptor, @self, opts) + @robot.initAdaptor(opts.adaptor, @self, opts) module.exports = Cylon.Connection diff --git a/src/device.coffee b/src/device.coffee index ff5d482..6acbf01 100644 --- a/src/device.coffee +++ b/src/device.coffee @@ -34,7 +34,7 @@ namespace 'Cylon', -> @name = opts.name @pin = opts.pin @connection = @determineConnection(opts.connection) or @defaultConnection() - @driver = @requireDriver(opts) + @driver = @initDriver(opts) proxyFunctionsToObject @driver.commands(), @driver, @self # Public: Starts the device driver @@ -86,12 +86,12 @@ namespace 'Cylon', -> # Public: sets up driver with @robot # - # opts - object containing options when requiring driver - # driver - name of the module to require() + # opts - object containing options when initializing driver + # driver - name of the driver to intt() # # Returns the set-up driver - requireDriver: (opts = {}) -> + initDriver: (opts = {}) -> Logger.debug "Loading driver '#{ opts.driver }'" - @robot.requireDriver(opts.driver, @self, opts) + @robot.initDriver(opts.driver, @self, opts) module.exports = Cylon.Device diff --git a/src/robot.coffee b/src/robot.coffee index 1b4fb8e..8237dce 100644 --- a/src/robot.coffee +++ b/src/robot.coffee @@ -162,31 +162,28 @@ namespace 'Cylon', -> for n, connection of @connections connection.disconnect() + # Public: Initialize an adaptor and adds it to @robot.adaptors + # + # adaptorName - module name of adaptor to require + # connection - the Connection that requested the adaptor be required + # + # Returns the adaptor + initAdaptor: (adaptorName, connection, opts = {}) -> + @robot.requireAdaptor(adaptorName).adaptor + name: adaptorName, + connection: connection, + extraParams: opts + # Public: Requires a hardware adaptor and adds it to @robot.adaptors # # adaptorName - module name of adaptor to require # connection - the Connection that requested the adaptor be required # # Returns the set-up adaptor - requireAdaptor: (adaptorName, connection, opts = {}) -> - if @robot.adaptors[adaptorName]? - if typeof @robot.adaptors[adaptorName] is 'string' - @robot.adaptors[adaptorName] = require( - @robot.adaptors[adaptorName] - ).adaptor( - name: adaptorName, - connection: connection, - extraParams: opts - ) - else - require("cylon-#{adaptorName}").register(this) - @robot.adaptors[adaptorName] = require( - "cylon-#{adaptorName}" - ).adaptor( - name: adaptorName, - connection: connection, - extraParams: opts - ) + requireAdaptor: (adaptorName) => + unless @robot.adaptors[adaptorName]? + @robot.registerAdaptor "cylon-#{adaptorName}", adaptorName + @robot.adaptors[adaptorName].register this return @robot.adaptors[adaptorName] @@ -196,35 +193,30 @@ namespace 'Cylon', -> # adaptorName - name of the adaptor to register the moduleName under # # Returns the registered module name - registerAdaptor: (moduleName, adaptorName) -> - return if @adaptors[adaptorName]? - @adaptors[adaptorName] = moduleName + registerAdaptor: (moduleName, adaptorName) => + @adaptors[adaptorName] = require(moduleName) unless @adaptors[adaptorName]? - # Public: Requires a hardware driver and adds it to @robot.drivers + # Public: Init a hardware driver + # + # driverName - driver name + # device - the Device that requested the driver be initialized + # + # Returns the new driver + initDriver: (driverName, device, opts = {}) -> + @robot.requireDriver(driverName).driver + name: driverName, + device: device, + extraParams: opts + + # Public: Requires module for a driver and adds it to @robot.drivers # # driverName - module name of driver to require - # connection - the Connection that requested the driver be required # - # Returns the set-up driver - requireDriver: (driverName, device, opts = {}) -> - if @robot.drivers[driverName]? - if typeof @robot.drivers[driverName] is 'string' - @robot.drivers[driverName] = require( - @robot.drivers[driverName] - ).driver( - name: driverName, - device: device, - extraParams: opts - ) - else - require("cylon-#{driverName}").register(this) - @robot.drivers[driverName] = require( - "cylon-#{driverName}" - ).driver( - name: driverName, - device: device, - extraParams: opts - ) + # Returns the module for driver + requireDriver: (driverName) => + unless @robot.drivers[driverName]? + @robot.registerDriver "cylon-#{driverName}", driverName + @robot.drivers[driverName].register this return @robot.drivers[driverName] @@ -235,7 +227,6 @@ namespace 'Cylon', -> # # Returns the registered module name registerDriver: (moduleName, driverName) => - return if @drivers[driverName]? - @drivers[driverName] = moduleName + @drivers[driverName] = require(moduleName) unless @drivers[driverName]? module.exports = Cylon.Robot diff --git a/test/dist/specs/connection.spec.js b/test/dist/specs/connection.spec.js index 313a040..fa7b6c0 100644 --- a/test/dist/specs/connection.spec.js +++ b/test/dist/specs/connection.spec.js @@ -7,14 +7,14 @@ source("robot"); describe("Connection", function() { - var adaptor, connection, requireAdaptor, robot; + var adaptor, connection, initAdaptor, robot; robot = new Cylon.Robot({ name: 'me' }); adaptor = new Cylon.Adaptor({ name: 'loopback' }); - requireAdaptor = sinon.stub(robot, 'requireAdaptor').returns(adaptor); + initAdaptor = sinon.stub(robot, 'initAdaptor').returns(adaptor); connection = new Cylon.Connection({ name: "connective", adaptor: "loopback", diff --git a/test/dist/specs/device.spec.js b/test/dist/specs/device.spec.js index 6f7d2e1..6342a4f 100644 --- a/test/dist/specs/device.spec.js +++ b/test/dist/specs/device.spec.js @@ -7,14 +7,14 @@ source("test/driver"); describe("Device", function() { - var device, driver, requireDriver, robot; + var device, driver, initDriver, robot; robot = new Cylon.Robot({ name: 'me' }); driver = new Cylon.Driver({ name: 'driving' }); - requireDriver = sinon.stub(robot, 'requireDriver').returns(driver); + initDriver = sinon.stub(robot, 'initDriver').returns(driver); device = new Cylon.Device({ name: "devisive", driver: 'driving', @@ -28,8 +28,8 @@ }); it("should use default connection if none specified"); it("should use connection if one is specified"); - return it("should require a driver", function() { - return requireDriver.should.be.called; + return it("should init a driver", function() { + return initDriver.should.be.called; }); }); diff --git a/test/src/specs/connection.spec.coffee b/test/src/specs/connection.spec.coffee index 3fb1694..94d26af 100644 --- a/test/src/specs/connection.spec.coffee +++ b/test/src/specs/connection.spec.coffee @@ -7,7 +7,7 @@ source "robot" describe "Connection", -> robot = new Cylon.Robot(name: 'me') adaptor = new Cylon.Adaptor(name: 'loopback') - requireAdaptor = sinon.stub(robot, 'requireAdaptor').returns(adaptor) + initAdaptor = sinon.stub(robot, 'initAdaptor').returns(adaptor) connection = new Cylon.Connection name: "connective" adaptor: "loopback" diff --git a/test/src/specs/device.spec.coffee b/test/src/specs/device.spec.coffee index 93c7815..bac2186 100644 --- a/test/src/specs/device.spec.coffee +++ b/test/src/specs/device.spec.coffee @@ -7,7 +7,7 @@ source "test/driver" describe "Device", -> robot = new Cylon.Robot(name: 'me') driver = new Cylon.Driver(name: 'driving') - requireDriver = sinon.stub(robot, 'requireDriver').returns(driver) + initDriver = sinon.stub(robot, 'initDriver').returns(driver) device = new Cylon.Device(name: "devisive", driver: 'driving', robot: robot) it "should belong to a robot", -> @@ -19,5 +19,5 @@ describe "Device", -> it "should use default connection if none specified" it "should use connection if one is specified" - it "should require a driver", -> - requireDriver.should.be.called + it "should init a driver", -> + initDriver.should.be.called From c90372001efce6be84a9ef2779bbe71281e04d94 Mon Sep 17 00:00:00 2001 From: deadprogram Date: Sat, 23 Nov 2013 21:18:09 -0800 Subject: [PATCH 2/6] Docs corrections --- src/connection.coffee | 7 ++++--- src/robot.coffee | 4 ++-- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/src/connection.coffee b/src/connection.coffee index 49e3c4f..377b62e 100644 --- a/src/connection.coffee +++ b/src/connection.coffee @@ -50,7 +50,7 @@ namespace 'Cylon', -> connection_id: @connection_id } - # Public: Creates the adaptor connection + # Public: Connect the adaptor's connection # # callback - callback function to run when the adaptor is connected # @@ -61,7 +61,7 @@ namespace 'Cylon', -> Logger.info msg @adaptor.connect(callback) - # Public: Closes the adaptor connection + # Public: Disconnect the adaptor's connection # # Returns nothing disconnect: -> @@ -72,7 +72,8 @@ namespace 'Cylon', -> # Public: sets up adaptor with @robot # - # adaptorName - module name of adaptor to require + # opts - options for adaptor being initialized + # adaptor - name of the adaptor # # Returns the set-up adaptor initAdaptor: (opts) -> diff --git a/src/robot.coffee b/src/robot.coffee index 8237dce..8041b74 100644 --- a/src/robot.coffee +++ b/src/robot.coffee @@ -177,9 +177,8 @@ namespace 'Cylon', -> # Public: Requires a hardware adaptor and adds it to @robot.adaptors # # adaptorName - module name of adaptor to require - # connection - the Connection that requested the adaptor be required # - # Returns the set-up adaptor + # Returns the module for the adaptor requireAdaptor: (adaptorName) => unless @robot.adaptors[adaptorName]? @robot.registerAdaptor "cylon-#{adaptorName}", adaptorName @@ -200,6 +199,7 @@ namespace 'Cylon', -> # # driverName - driver name # device - the Device that requested the driver be initialized + # opts - object containing options when initializing driver # # Returns the new driver initDriver: (driverName, device, opts = {}) -> From 0bf4d110a51e4d86792110633b08f4ae7baf3858 Mon Sep 17 00:00:00 2001 From: Adrian Zankich Date: Mon, 25 Nov 2013 13:35:20 -0800 Subject: [PATCH 3/6] Replace api.litcoffee with api.coffee --- src/api.coffee | 100 ++++++++++++++++++++ src/api.litcoffee | 234 ---------------------------------------------- 2 files changed, 100 insertions(+), 234 deletions(-) create mode 100644 src/api.coffee delete mode 100644 src/api.litcoffee diff --git a/src/api.coffee b/src/api.coffee new file mode 100644 index 0000000..9ec7a0c --- /dev/null +++ b/src/api.coffee @@ -0,0 +1,100 @@ +'use strict' + +express = require 'express.io' + +namespace = require 'node-namespace' + +namespace 'Api', -> + class @Server + + master = null + + constructor: (opts = {}) -> + @host = opts.host || "127.0.0.1" + @port = opts.port || "3000" + + master = opts.master + + @server = express().http().io() + + @server.set 'name', 'Cylon API Server' + + @server.use express.bodyParser() + + @server.get "/*", (req, res, next) -> + res.set 'Content-Type', 'application/json' + do next + + do @configureRoutes + + @server.listen @port, @host, => + Logger.info "#{@server.name} is listening at #{@host}:#{@port}" + + configureRoutes: -> + + @server.get "/robots", (req, res) -> + res.json (robot.data() for robot in master.robots()) + + @server.get "/robots/:robotname", (req, res) -> + master.findRobot req.params.robotname, (err, robot) -> + res.json if err then err else robot.data() + + @server.get "/robots/:robotname/devices", (req, res) -> + master.findRobot req.params.robotname, (err, robot) -> + res.json if err then err else robot.data().devices + + @server.get "/robots/:robotname/devices/:devicename", (req, res) -> + params = [req.params.robotname, req.params.devicename] + [robotname, devicename] = params + + master.findRobotDevice robotname, devicename, (err, device) -> + res.json if err then err else device.data() + + @server.get "/robots/:robotname/devices/:devicename/commands", (req, res) -> + params = [req.params.robotname, req.params.devicename] + [robotname, devicename] = params + + master.findRobotDevice robotname, devicename, (err, device) -> + res.json if err then err else device.data().commands + + @server.all "/robots/:robot/devices/:device/commands/:commandname", (req, res) -> + + params = [ + req.params.robot, + req.params.device, + req.params.commandname + ] + + [robotname, devicename, commandname] = params + + params = [] + if typeof req.body is 'object' + params.push(value) for key, value of req.body + + master.findRobotDevice robotname, devicename, (err, device) -> + if err then return res.json err + result = device[commandname](params...) + res.json result: result + + @server.get "/robots/:robotname/connections", (req, res) -> + master.findRobot req.params.robotname, (err, robot) -> + res.json if err then err else robot.data().connections + + @server.get "/robots/:robot/connections/:connection", (req, res) -> + params = [req.params.robot, req.params.connection] + [robotname, connectionname] = params + + master.findRobotConnection robotname, connectionname, (err, connection) -> + res.json if err then err else connection.data() + + @server.get "/robots/:robotname/devices/:devicename/events", (req, res) -> + req.io.route 'events' + + @server.io.route 'events', (req) -> + params = [req.params.robotname, req.params.devicename] + [robotname, devicename] = params + + master.findRobotDevice robotname, devicename, (err, device) -> + req.io.respond(err) if err + device.on 'update', (data) -> + req.io.emit 'update', { data: data } diff --git a/src/api.litcoffee b/src/api.litcoffee deleted file mode 100644 index 384ef70..0000000 --- a/src/api.litcoffee +++ /dev/null @@ -1,234 +0,0 @@ -# API - -The Cylon API provides an interface for retreiving information and communicating -with the currently running robots. - -First of all, let's make sure we're running in ECMAScript 5's [strict mode][]. - -[strict mode]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions_and_function_scope/Strict_mode - - 'use strict' - -## Dependencies - -Our server needs to primarily respond to HTTP requests with JSON responses, but -also needs to handle a WebSocket connection to listen for events. To accomodate -both of these requirements, we're going to use [express.io][]. - -[express.io]: http://express-io.org - - express = require 'express.io' - -`express.io` integrates some [socket.io][] functionality on top of the popular -[express][] framework for Node. This lets us use the same server to handle both -types of connections, and also use Express-style routing to resolve to -a Socket.IO connection. - -[socket.io]: http://socket.io -[express]: http://expressjs.com - -To keep in line with the rest of Cylon, we'll also be namespacing our API server -with the [node-namespace][] module. - -[node-namespace]: https://github.com/kaero/node-namespace - - namespace = require 'node-namespace' - -## Namespacing - -First, let's namespace our server class so it's available to other parts of -Cylon: - - namespace 'Api', -> - class @Server - -Now from another module, you can just `require('./api')` and you automatically -have access to the API server via `Api.Server`. - -## Master - -We need to hold a class-wide reference to the Cylon instance that's started our -server instance, so we can inspect and send messages to all the robots it's -controlling. For now, we can set it to null, it will be changed later in the -constructor. - - master = null - -## Constructor - -The server constructor accepts an arguments object, `opts`. This -defines options to be used when creating the server, as follows: - -- **master** - required, reference to Cylon.Master instance that's creating the - Server instance -- **host** - optional, string IP address that the server should host content - from -- **port** - optional, string port number the server should listen for requests - on - - constructor: (opts = {}) -> - @host = opts.host || "127.0.0.1" - @port = opts.port || "3000" - - master = opts.master - -Once we have this information, we can create our server: - - @server = express().http().io() - -Give it a name: - - @server.set 'name', 'Cylon API Server' - -And tell it to use the `express.bodyParser()` mixin, which will parse body -params for POST requests: - - @server.use express.bodyParser() - -We need to define a catch-all route here, that will specify that all our -responses will be JSON. Otherwise, Express will automatically send everything in -text/plain, possibly confusing clients. - - @server.get "/*", (req, res, next) -> - res.set 'Content-Type', 'application/json' - do next - -Next up, we run a function to define all our routes (you'll see it in a minute) - - do @configureRoutes - -And finally, we start our server, announcing so via the Logger. - - @server.listen @port, @host, => - Logger.info "#{@server.name} is listening at #{@host}:#{@port}" - -## Routes - -We'll use the previously referenced `@configureRoutes` function to define our -server's routes. - - configureRoutes: -> - -### GET /robots - -Our first route returns all the Robots the master class knows about in JSON -format. - - @server.get "/robots", (req, res) -> - res.json (robot.data() for robot in master.robots()) - -### GET /robots/:robotname - -Given a robot's name, returns JSON information about the requested Robot: - - @server.get "/robots/:robotname", (req, res) -> - master.findRobot req.params.robotname, (err, robot) -> - res.json if err then err else robot.data() - -### GET /robots/:robotname/devices - -Given a robot's name, returns JSON information about the devices belonging to -the requested Robot: - - @server.get "/robots/:robotname/devices", (req, res) -> - master.findRobot req.params.robotname, (err, robot) -> - res.json if err then err else robot.data().devices - -### GET /robots/:robotname/devices/:devicename - -Given the names of a device and the robot it belongs to, returns data on the -specified device. - - @server.get "/robots/:robotname/devices/:devicename", (req, res) -> - params = [req.params.robotname, req.params.devicename] - [robotname, devicename] = params - - master.findRobotDevice robotname, devicename, (err, device) -> - res.json if err then err else device.data() - -### GET /robots/:robotname/devices/:devicename/commands - -Given the names of a device and the robot it belongs to, returns all commands -available for the specified device. - - @server.get "/robots/:robotname/devices/:devicename/commands", (req, res) -> - params = [req.params.robotname, req.params.devicename] - [robotname, devicename] = params - - master.findRobotDevice robotname, devicename, (err, device) -> - res.json if err then err else device.data().commands - -### ALL /robots/:robotname/devices/:devicename/commands/:commandname - -Given a robot name, device name, and command name, executes a robot's command -and returns the result. - - @server.all "/robots/:robot/devices/:device/commands/:commandname", (req, res) -> - - params = [ - req.params.robot, - req.params.device, - req.params.commandname - ] - - [robotname, devicename, commandname] = params - -This parses params from the request body into values that can be used while -calling the command, if params have been supplied. - - params = [] - if typeof req.body is 'object' - params.push(value) for key, value of req.body - -Runs the command on the Robot's device, passing in params as provided. - - master.findRobotDevice robotname, devicename, (err, device) -> - if err then return res.json err - result = device[commandname](params...) - res.json result: result - -### GET /robots/:robotname/connections - -Given a robot's name, returns JSON information about the connections belonging -to the requested Robot: - - @server.get "/robots/:robotname/connections", (req, res) -> - master.findRobot req.params.robotname, (err, robot) -> - res.json if err then err else robot.data().connections - -### GET /robots/:robotname/connections/:connectionname - -Given a robot's name, returns JSON information about the connections belonging -to the requested Robot: - - @server.get "/robots/:robot/connections/:connection", (req, res) -> - params = [req.params.robot, req.params.connection] - [robotname, connectionname] = params - - master.findRobotConnection robotname, connectionname, (err, connection) -> - res.json if err then err else connection.data() - -### GET /robots/:robotname/devices/:devicename/events - -Routes to a Socket.IO route to handle WebSockets connections requesting updates -on device events. - - @server.get "/robots/:robotname/devices/:devicename/events", (req, res) -> - req.io.route 'events' - -### WS_GET /events - -A Socket.IO route to handle updating clients whenever a device sends -an 'update' event. - -Listens for the 'update' event on a particular Robot's device, and whenever the -device sends the 'update' event, passes the data along to the client. - - @server.io.route 'events', (req) -> - params = [req.params.robotname, req.params.devicename] - [robotname, devicename] = params - - master.findRobotDevice robotname, devicename, (err, device) -> - req.io.respond(err) if err - device.on 'update', (data) -> - req.io.emit 'update', { data: data } From 40aa152b779bf64c64a03435a85373c98dc21dd7 Mon Sep 17 00:00:00 2001 From: Adrian Zankich Date: Mon, 25 Nov 2013 13:35:20 -0800 Subject: [PATCH 4/6] Replace api.litcoffee with api.coffee --- src/api.coffee | 100 ++++++++++++++++++++ src/api.litcoffee | 234 ---------------------------------------------- 2 files changed, 100 insertions(+), 234 deletions(-) create mode 100644 src/api.coffee delete mode 100644 src/api.litcoffee diff --git a/src/api.coffee b/src/api.coffee new file mode 100644 index 0000000..9ec7a0c --- /dev/null +++ b/src/api.coffee @@ -0,0 +1,100 @@ +'use strict' + +express = require 'express.io' + +namespace = require 'node-namespace' + +namespace 'Api', -> + class @Server + + master = null + + constructor: (opts = {}) -> + @host = opts.host || "127.0.0.1" + @port = opts.port || "3000" + + master = opts.master + + @server = express().http().io() + + @server.set 'name', 'Cylon API Server' + + @server.use express.bodyParser() + + @server.get "/*", (req, res, next) -> + res.set 'Content-Type', 'application/json' + do next + + do @configureRoutes + + @server.listen @port, @host, => + Logger.info "#{@server.name} is listening at #{@host}:#{@port}" + + configureRoutes: -> + + @server.get "/robots", (req, res) -> + res.json (robot.data() for robot in master.robots()) + + @server.get "/robots/:robotname", (req, res) -> + master.findRobot req.params.robotname, (err, robot) -> + res.json if err then err else robot.data() + + @server.get "/robots/:robotname/devices", (req, res) -> + master.findRobot req.params.robotname, (err, robot) -> + res.json if err then err else robot.data().devices + + @server.get "/robots/:robotname/devices/:devicename", (req, res) -> + params = [req.params.robotname, req.params.devicename] + [robotname, devicename] = params + + master.findRobotDevice robotname, devicename, (err, device) -> + res.json if err then err else device.data() + + @server.get "/robots/:robotname/devices/:devicename/commands", (req, res) -> + params = [req.params.robotname, req.params.devicename] + [robotname, devicename] = params + + master.findRobotDevice robotname, devicename, (err, device) -> + res.json if err then err else device.data().commands + + @server.all "/robots/:robot/devices/:device/commands/:commandname", (req, res) -> + + params = [ + req.params.robot, + req.params.device, + req.params.commandname + ] + + [robotname, devicename, commandname] = params + + params = [] + if typeof req.body is 'object' + params.push(value) for key, value of req.body + + master.findRobotDevice robotname, devicename, (err, device) -> + if err then return res.json err + result = device[commandname](params...) + res.json result: result + + @server.get "/robots/:robotname/connections", (req, res) -> + master.findRobot req.params.robotname, (err, robot) -> + res.json if err then err else robot.data().connections + + @server.get "/robots/:robot/connections/:connection", (req, res) -> + params = [req.params.robot, req.params.connection] + [robotname, connectionname] = params + + master.findRobotConnection robotname, connectionname, (err, connection) -> + res.json if err then err else connection.data() + + @server.get "/robots/:robotname/devices/:devicename/events", (req, res) -> + req.io.route 'events' + + @server.io.route 'events', (req) -> + params = [req.params.robotname, req.params.devicename] + [robotname, devicename] = params + + master.findRobotDevice robotname, devicename, (err, device) -> + req.io.respond(err) if err + device.on 'update', (data) -> + req.io.emit 'update', { data: data } diff --git a/src/api.litcoffee b/src/api.litcoffee deleted file mode 100644 index 384ef70..0000000 --- a/src/api.litcoffee +++ /dev/null @@ -1,234 +0,0 @@ -# API - -The Cylon API provides an interface for retreiving information and communicating -with the currently running robots. - -First of all, let's make sure we're running in ECMAScript 5's [strict mode][]. - -[strict mode]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions_and_function_scope/Strict_mode - - 'use strict' - -## Dependencies - -Our server needs to primarily respond to HTTP requests with JSON responses, but -also needs to handle a WebSocket connection to listen for events. To accomodate -both of these requirements, we're going to use [express.io][]. - -[express.io]: http://express-io.org - - express = require 'express.io' - -`express.io` integrates some [socket.io][] functionality on top of the popular -[express][] framework for Node. This lets us use the same server to handle both -types of connections, and also use Express-style routing to resolve to -a Socket.IO connection. - -[socket.io]: http://socket.io -[express]: http://expressjs.com - -To keep in line with the rest of Cylon, we'll also be namespacing our API server -with the [node-namespace][] module. - -[node-namespace]: https://github.com/kaero/node-namespace - - namespace = require 'node-namespace' - -## Namespacing - -First, let's namespace our server class so it's available to other parts of -Cylon: - - namespace 'Api', -> - class @Server - -Now from another module, you can just `require('./api')` and you automatically -have access to the API server via `Api.Server`. - -## Master - -We need to hold a class-wide reference to the Cylon instance that's started our -server instance, so we can inspect and send messages to all the robots it's -controlling. For now, we can set it to null, it will be changed later in the -constructor. - - master = null - -## Constructor - -The server constructor accepts an arguments object, `opts`. This -defines options to be used when creating the server, as follows: - -- **master** - required, reference to Cylon.Master instance that's creating the - Server instance -- **host** - optional, string IP address that the server should host content - from -- **port** - optional, string port number the server should listen for requests - on - - constructor: (opts = {}) -> - @host = opts.host || "127.0.0.1" - @port = opts.port || "3000" - - master = opts.master - -Once we have this information, we can create our server: - - @server = express().http().io() - -Give it a name: - - @server.set 'name', 'Cylon API Server' - -And tell it to use the `express.bodyParser()` mixin, which will parse body -params for POST requests: - - @server.use express.bodyParser() - -We need to define a catch-all route here, that will specify that all our -responses will be JSON. Otherwise, Express will automatically send everything in -text/plain, possibly confusing clients. - - @server.get "/*", (req, res, next) -> - res.set 'Content-Type', 'application/json' - do next - -Next up, we run a function to define all our routes (you'll see it in a minute) - - do @configureRoutes - -And finally, we start our server, announcing so via the Logger. - - @server.listen @port, @host, => - Logger.info "#{@server.name} is listening at #{@host}:#{@port}" - -## Routes - -We'll use the previously referenced `@configureRoutes` function to define our -server's routes. - - configureRoutes: -> - -### GET /robots - -Our first route returns all the Robots the master class knows about in JSON -format. - - @server.get "/robots", (req, res) -> - res.json (robot.data() for robot in master.robots()) - -### GET /robots/:robotname - -Given a robot's name, returns JSON information about the requested Robot: - - @server.get "/robots/:robotname", (req, res) -> - master.findRobot req.params.robotname, (err, robot) -> - res.json if err then err else robot.data() - -### GET /robots/:robotname/devices - -Given a robot's name, returns JSON information about the devices belonging to -the requested Robot: - - @server.get "/robots/:robotname/devices", (req, res) -> - master.findRobot req.params.robotname, (err, robot) -> - res.json if err then err else robot.data().devices - -### GET /robots/:robotname/devices/:devicename - -Given the names of a device and the robot it belongs to, returns data on the -specified device. - - @server.get "/robots/:robotname/devices/:devicename", (req, res) -> - params = [req.params.robotname, req.params.devicename] - [robotname, devicename] = params - - master.findRobotDevice robotname, devicename, (err, device) -> - res.json if err then err else device.data() - -### GET /robots/:robotname/devices/:devicename/commands - -Given the names of a device and the robot it belongs to, returns all commands -available for the specified device. - - @server.get "/robots/:robotname/devices/:devicename/commands", (req, res) -> - params = [req.params.robotname, req.params.devicename] - [robotname, devicename] = params - - master.findRobotDevice robotname, devicename, (err, device) -> - res.json if err then err else device.data().commands - -### ALL /robots/:robotname/devices/:devicename/commands/:commandname - -Given a robot name, device name, and command name, executes a robot's command -and returns the result. - - @server.all "/robots/:robot/devices/:device/commands/:commandname", (req, res) -> - - params = [ - req.params.robot, - req.params.device, - req.params.commandname - ] - - [robotname, devicename, commandname] = params - -This parses params from the request body into values that can be used while -calling the command, if params have been supplied. - - params = [] - if typeof req.body is 'object' - params.push(value) for key, value of req.body - -Runs the command on the Robot's device, passing in params as provided. - - master.findRobotDevice robotname, devicename, (err, device) -> - if err then return res.json err - result = device[commandname](params...) - res.json result: result - -### GET /robots/:robotname/connections - -Given a robot's name, returns JSON information about the connections belonging -to the requested Robot: - - @server.get "/robots/:robotname/connections", (req, res) -> - master.findRobot req.params.robotname, (err, robot) -> - res.json if err then err else robot.data().connections - -### GET /robots/:robotname/connections/:connectionname - -Given a robot's name, returns JSON information about the connections belonging -to the requested Robot: - - @server.get "/robots/:robot/connections/:connection", (req, res) -> - params = [req.params.robot, req.params.connection] - [robotname, connectionname] = params - - master.findRobotConnection robotname, connectionname, (err, connection) -> - res.json if err then err else connection.data() - -### GET /robots/:robotname/devices/:devicename/events - -Routes to a Socket.IO route to handle WebSockets connections requesting updates -on device events. - - @server.get "/robots/:robotname/devices/:devicename/events", (req, res) -> - req.io.route 'events' - -### WS_GET /events - -A Socket.IO route to handle updating clients whenever a device sends -an 'update' event. - -Listens for the 'update' event on a particular Robot's device, and whenever the -device sends the 'update' event, passes the data along to the client. - - @server.io.route 'events', (req) -> - params = [req.params.robotname, req.params.devicename] - [robotname, devicename] = params - - master.findRobotDevice robotname, devicename, (err, device) -> - req.io.respond(err) if err - device.on 'update', (data) -> - req.io.emit 'update', { data: data } From b46d4242ea9db77efaa471fa6cd1ad9206a18e45 Mon Sep 17 00:00:00 2001 From: Adrian Zankich Date: Mon, 25 Nov 2013 14:28:13 -0800 Subject: [PATCH 5/6] Execute robot commands from api --- dist/api.js | 26 ++++++++++++++++++++++++++ dist/robot.js | 4 +++- examples/robot_commands.coffee | 20 ++++++++++++++++++++ src/api.coffee | 14 ++++++++++++++ src/robot.coffee | 2 ++ 5 files changed, 65 insertions(+), 1 deletion(-) create mode 100644 examples/robot_commands.coffee diff --git a/dist/api.js b/dist/api.js index 86befa3..9f9fa1e 100644 --- a/dist/api.js +++ b/dist/api.js @@ -52,6 +52,32 @@ return res.json(err ? err : robot.data()); }); }); + this.server.get("/robots/:robotname/commands", function(req, res) { + return master.findRobot(req.params.robotname, function(err, robot) { + return res.json(err ? err : robot.data().commands); + }); + }); + this.server.all("/robots/:robotname/commands/:commandname", function(req, res) { + var key, params, value, _ref; + params = []; + if (typeof req.body === 'object') { + _ref = req.body; + for (key in _ref) { + value = _ref[key]; + params.push(value); + } + } + return master.findRobot(req.params.robotname, function(err, robot) { + var result; + if (err) { + return res.json(err); + } + result = robot[req.params.commandname].apply(robot, params); + return res.json({ + result: result + }); + }); + }); this.server.get("/robots/:robotname/devices", function(req, res) { return master.findRobot(req.params.robotname, function(err, robot) { return res.json(err ? err : robot.data().devices); diff --git a/dist/robot.js b/dist/robot.js index 39bf10c..929d2c5 100644 --- a/dist/robot.js +++ b/dist/robot.js @@ -52,6 +52,7 @@ this.devices = {}; this.adaptors = {}; this.drivers = {}; + this.commands = []; this.registerAdaptor("./loopback", "loopback"); this.registerDriver("./ping", "ping"); this.initConnections(opts.connection || opts.connections); @@ -95,7 +96,8 @@ _results.push(device.data()); } return _results; - }).call(this) + }).call(this), + commands: this.commands }; }; diff --git a/examples/robot_commands.coffee b/examples/robot_commands.coffee new file mode 100644 index 0000000..4d72760 --- /dev/null +++ b/examples/robot_commands.coffee @@ -0,0 +1,20 @@ +Cylon = require '..' + +Cylon.api host: '0.0.0.0', port: '8080' + +class MyRobot + commands: + ["relax"] + + relax: -> + return "#{this.name} says relax" + + work: (me) -> + every 1.seconds(), -> + Logger.info me.name + +robot = new MyRobot +robot.name = "frankie" +Cylon.robot robot + +Cylon.start() diff --git a/src/api.coffee b/src/api.coffee index 9ec7a0c..8a3d26f 100644 --- a/src/api.coffee +++ b/src/api.coffee @@ -39,6 +39,20 @@ namespace 'Api', -> master.findRobot req.params.robotname, (err, robot) -> res.json if err then err else robot.data() + @server.get "/robots/:robotname/commands", (req, res) -> + master.findRobot req.params.robotname, (err, robot) -> + res.json if err then err else robot.data().commands + + @server.all "/robots/:robotname/commands/:commandname", (req, res) -> + params = [] + if typeof req.body is 'object' + params.push(value) for key, value of req.body + + master.findRobot req.params.robotname, (err, robot) -> + if err then return res.json err + result = robot[req.params.commandname](params...) + res.json result: result + @server.get "/robots/:robotname/devices", (req, res) -> master.findRobot req.params.robotname, (err, robot) -> res.json if err then err else robot.data().devices diff --git a/src/robot.coffee b/src/robot.coffee index 1b4fb8e..af1bba6 100644 --- a/src/robot.coffee +++ b/src/robot.coffee @@ -55,6 +55,7 @@ namespace 'Cylon', -> @devices = {} @adaptors = {} @drivers = {} + @commands = [] @registerAdaptor "./loopback", "loopback" @registerDriver "./ping", "ping" @@ -81,6 +82,7 @@ namespace 'Cylon', -> name: @name connections: (connection.data() for n, connection of @connections) devices: (device.data() for n, device of @devices) + commands: @commands } # Public: Initializes all connections for the robot From d1dd1c7df2a1af8806dd6f6e45f0e8d9d20eb008 Mon Sep 17 00:00:00 2001 From: deadprogram Date: Mon, 25 Nov 2013 15:47:57 -0800 Subject: [PATCH 6/6] Update to version 0.6.0 --- README.md | 2 ++ package.json | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index d2f2512..0e4d9c7 100644 --- a/README.md +++ b/README.md @@ -108,6 +108,8 @@ In lieu of a formal styleguide, take care to maintain the existing coding style. [![NPM](https://nodei.co/npm/cylon.png?compact=true)](https://nodei.co/npm/cylon/) +Version 0.6.0 - API exposes robot commands, fixes issues in driver/adaptor init + Version 0.5.0 - Improve API, add GPIO support for reuse in adaptors Version 0.4.0 - Refactor proxy in Cylon.Basestar, improve API diff --git a/package.json b/package.json index b40e82d..3be5d3b 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "cylon", - "version": "0.5.0", + "version": "0.6.0", "main": "dist/cylon.js", "description": "A JavaScript robotics framework using Node.js", "homepage": "http://cylonjs.com",