Merge pull request #18 from hybridgroup/api

Add API backend
This commit is contained in:
Ron Evans 2013-11-01 17:44:40 -07:00
commit 333327aa63
11 changed files with 483 additions and 10 deletions

164
dist/api/api.js vendored Normal file
View File

@ -0,0 +1,164 @@
/*
* api
* cylonjs.com
*
* Copyright (c) 2013 The Hybrid Group
* Licensed under the Apache 2.0 license.
*/
(function() {
var express, namespace;
express = require('express.io');
namespace = require('node-namespace');
namespace("Api", function() {
return this.Server = (function() {
var master;
master = null;
function Server(opts) {
var _this = this;
if (opts == null) {
opts = {};
}
this.host = opts.host || "127.0.0.1";
this.port = opts.port || "3000";
master = opts.master;
this.server = express().http().io();
this.server.set('name', 'Cylon API Server');
this.server.use(express.bodyParser());
this.server.get("/*", function(req, res, next) {
res.set('Content-Type', 'application/json');
return next();
});
this.routes(this.server);
this.server.listen(this.port, this.host, function() {
return Logger.info("" + _this.server.name + " is listening at " + _this.host + ":" + _this.port);
});
}
Server.prototype.routes = function(server) {
server.get("/robots", this.getRobots);
server.get("/robots/:robotid", this.getRobotByName);
server.get("/robots/:robotid/devices", this.getDevices);
server.get("/robots/:robotid/devices/:deviceid", this.getDeviceByName);
server.get("/robots/:robotid/devices/:deviceid/commands", this.getDeviceCommands);
server.post("/robots/:robotid/devices/:deviceid/commands/:commandid", this.runDeviceCommand);
server.get("/robots/:robotid/connections", this.getConnections);
server.get("/robots/:robotid/connections/:connectionid", this.getConnectionByName);
server.get("/robots/:robotid/devices/:deviceid/events", function(req, res) {
return req.io.route('events');
});
return server.io.route('events', this.ioSetupDeviceEventClient);
};
Server.prototype.getRobots = function(req, res) {
var robot;
return res.json((function() {
var _i, _len, _ref, _results;
_ref = master.robots();
_results = [];
for (_i = 0, _len = _ref.length; _i < _len; _i++) {
robot = _ref[_i];
_results.push(robot.data());
}
return _results;
})());
};
Server.prototype.getRobotByName = function(req, res) {
return master.findRobot(req.params.robotid, function(err, robot) {
return res.json(err ? err : robot.data());
});
};
Server.prototype.getDevices = function(req, res) {
return master.findRobot(req.params.robotid, function(err, robot) {
return res.json(err ? err : robot.data().devices);
});
};
Server.prototype.getDeviceByName = function(req, res) {
var deviceid, robotid;
robotid = req.params.robotid;
deviceid = req.params.deviceid;
return master.findRobotDevice(robotid, deviceid, function(err, device) {
return res.json(err ? err : device.data());
});
};
Server.prototype.getDeviceCommands = function(req, res) {
var deviceid, robotid;
robotid = req.params.robotid;
deviceid = req.params.deviceid;
return master.findRobotDevice(robotid, deviceid, function(err, device) {
return res.json(err ? err : device.data().commands);
});
};
Server.prototype.runDeviceCommand = function(req, res) {
var commandid, deviceid, key, params, robotid, value, _ref;
robotid = req.params.robotid;
deviceid = req.params.deviceid;
commandid = req.params.commandid;
params = [];
if (typeof req.body === 'object') {
_ref = req.body;
for (key in _ref) {
value = _ref[key];
params.push(value);
}
}
return master.findRobotDevice(robotid, deviceid, function(err, device) {
var result;
if (err) {
return res.json(err);
}
result = device[commandid].apply(device, params);
return res.json({
result: result
});
});
};
Server.prototype.getConnections = function(req, res) {
return master.findRobot(req.params.robotid, function(err, robot) {
return res.json(err ? err : robot.data().connections);
});
};
Server.prototype.getConnectionByName = function(req, res) {
var connectionid, robotid;
robotid = req.params.robotid;
connectionid = req.params.connectionid;
return master.findRobotConnection(robotid, connectionid, function(err, connection) {
return res.json(err ? err : connection.data());
});
};
Server.prototype.ioSetupDeviceEventClient = function(req) {
var deviceid, robotid;
robotid = req.params.robotid;
deviceid = req.params.deviceid;
return master.findRobotDevice(robotid, deviceid, function(err, device) {
if (err) {
res.io.respond(err);
}
return device.on('update', function(data) {
return res.io.respond({
data: data
});
});
});
};
return Server;
})();
});
}).call(this);

13
dist/connection.js vendored
View File

@ -32,14 +32,27 @@
opts = {};
}
this.connect = __bind(this.connect, this);
if (opts.id == null) {
opts.id = Math.floor(Math.random() * 10000);
}
this.self = this;
this.robot = opts.robot;
this.name = opts.name;
this.connection_id = opts.id;
this.adaptor = this.requireAdaptor(opts.adaptor);
this.port = new Port(opts.port);
proxyFunctionsToObject(this.adaptor.commands(), this.adaptor, klass);
}
Connection.prototype.data = function() {
return {
name: this.name,
port: this.port.toString(),
adaptor: this.adaptor.constructor.name || this.adaptor.name,
connection_id: this.connection_id
};
};
Connection.prototype.connect = function(callback) {
var msg;
msg = "Connecting to '" + this.name + "'";

87
dist/cylon.js vendored
View File

@ -19,6 +19,8 @@
require('./logger');
require('./api/api');
Logger.setup();
Cylon = (function() {
@ -39,14 +41,17 @@
};
Master = (function() {
var robots;
var api, robots;
robots = [];
api = null;
function Master() {
this.robot = __bind(this.robot, this);
this.self = this;
}
robots = [];
Master.prototype.robot = function(opts) {
var robot;
opts.master = this;
@ -55,8 +60,78 @@
return robot;
};
Master.prototype.robots = function() {
return robots;
};
Master.prototype.findRobot = function(name, callback) {
var bot, error, robot, _i, _len;
robot = null;
for (_i = 0, _len = robots.length; _i < _len; _i++) {
bot = robots[_i];
if (bot.name === name) {
robot = bot;
}
}
if (robot == null) {
error = {
error: "No Robot found with the name " + name
};
}
if (callback) {
return callback(error, robot);
} else {
return robot;
}
};
Master.prototype.findRobotDevice = function(robotid, deviceid, callback) {
return this.findRobot(robotid, function(err, robot) {
var device, error;
if (err) {
callback(err, robot);
}
if (robot.devices[deviceid]) {
device = robot.devices[deviceid];
}
if (device == null) {
error = {
error: "No device found with the name " + device + "."
};
}
if (callback) {
return callback(error, device);
} else {
return device;
}
});
};
Master.prototype.findRobotConnection = function(robotid, connid, callback) {
return this.findRobot(robotid, function(err, robot) {
var connection, error;
if (err) {
callback(err, robot);
}
if (robot.connections[connid]) {
connection = robot.connections[connid];
}
if (connection == null) {
error = {
error: "No connection found with the name " + connection + "."
};
}
if (callback) {
return callback(error, connection);
} else {
return connection;
}
});
};
Master.prototype.start = function() {
var robot, _i, _len, _results;
this.startAPI();
_results = [];
for (_i = 0, _len = robots.length; _i < _len; _i++) {
robot = robots[_i];
@ -65,6 +140,12 @@
return _results;
};
Master.prototype.startAPI = function() {
return api != null ? api : api = new Api.Server({
master: this.self
});
};
return Master;
})();

10
dist/device.js vendored
View File

@ -49,6 +49,16 @@
return this.driver.start(callback);
};
Device.prototype.data = function() {
return {
name: this.name,
driver: this.driver.constructor.name || this.driver.name,
pin: this.pin != null ? this.pin.toString : null,
connection: this.connection.data(),
commands: this.driver.commands()
};
};
Device.prototype.determineConnection = function(c) {
if (c) {
return this.robot.connections[c];

35
dist/robot.js vendored
View File

@ -10,7 +10,8 @@
(function() {
'use strict';
var Async, Connection, Device, Robot,
__bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; };
__bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; },
__indexOf = [].indexOf || function(item) { for (var i = 0, l = this.length; i < l; i++) { if (i in this && this[i] === item) return i; } return -1; };
require('./cylon');
@ -28,7 +29,7 @@
klass = Robot;
function Robot(opts) {
var func, n;
var func, n, reserved;
if (opts == null) {
opts = {};
}
@ -54,7 +55,8 @@
};
for (n in opts) {
func = opts[n];
if (n !== 'connection' && n !== 'connections' && n !== 'device' && n !== 'devices' && n !== 'work') {
reserved = ['connection', 'connections', 'device', 'devices', 'work'];
if (__indexOf.call(reserved, n) < 0) {
this.robot[n] = func;
}
}
@ -64,6 +66,33 @@
return "Robot " + (Math.floor(Math.random() * 100000));
};
Robot.prototype.data = function() {
var connection, device, n;
return {
name: this.name,
connections: (function() {
var _ref, _results;
_ref = this.connections;
_results = [];
for (n in _ref) {
connection = _ref[n];
_results.push(connection.data());
}
return _results;
}).call(this),
devices: (function() {
var _ref, _results;
_ref = this.devices;
_results = [];
for (n in _ref) {
device = _ref[n];
_results.push(device.data());
}
return _results;
}).call(this)
};
};
Robot.prototype.initConnections = function(connections) {
var connection, _i, _len, _results;
Logger.info("Initializing connections...");

View File

@ -33,6 +33,7 @@
},
"dependencies": {
"async": "~0.2.9",
"node-namespace": "~1.0.0"
"node-namespace": "~1.0.0",
"express.io": "~1.1.13"
}
}

107
src/api/api.coffee Normal file
View File

@ -0,0 +1,107 @@
###
* api
* cylonjs.com
*
* Copyright (c) 2013 The Hybrid Group
* Licensed under the Apache 2.0 license.
###
express = require('express.io')
namespace = require 'node-namespace'
namespace "Api", ->
# The Cylon API Server provides an interface to communicate with master class
# and retrieve information about the robots being controlled.
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
@routes @server
@server.listen @port, @host, =>
Logger.info "#{@server.name} is listening at #{@host}:#{@port}"
routes: (server) ->
server.get "/robots", @getRobots
server.get "/robots/:robotid", @getRobotByName
server.get "/robots/:robotid/devices", @getDevices
server.get "/robots/:robotid/devices/:deviceid", @getDeviceByName
server.get "/robots/:robotid/devices/:deviceid/commands", @getDeviceCommands
server.post "/robots/:robotid/devices/:deviceid/commands/:commandid", @runDeviceCommand
server.get "/robots/:robotid/connections", @getConnections
server.get "/robots/:robotid/connections/:connectionid", @getConnectionByName
server.get "/robots/:robotid/devices/:deviceid/events", (req, res) ->
req.io.route 'events'
server.io.route 'events', @ioSetupDeviceEventClient
getRobots: (req, res) ->
res.json (robot.data() for robot in master.robots())
getRobotByName: (req, res) ->
master.findRobot req.params.robotid, (err, robot) ->
res.json if err then err else robot.data()
getDevices: (req, res) ->
master.findRobot req.params.robotid, (err, robot) ->
res.json if err then err else robot.data().devices
getDeviceByName: (req, res) ->
robotid = req.params.robotid
deviceid = req.params.deviceid
master.findRobotDevice robotid, deviceid, (err, device) ->
res.json if err then err else device.data()
getDeviceCommands: (req, res) ->
robotid = req.params.robotid
deviceid = req.params.deviceid
master.findRobotDevice robotid, deviceid, (err, device) ->
res.json if err then err else device.data().commands
runDeviceCommand: (req, res) ->
robotid = req.params.robotid
deviceid = req.params.deviceid
commandid = req.params.commandid
params = []
if typeof req.body is 'object'
params.push(value) for key, value of req.body
master.findRobotDevice robotid, deviceid, (err, device) ->
if err then return res.json err
result = device[commandid](params...)
res.json result: result
getConnections: (req, res) ->
master.findRobot req.params.robotid, (err, robot) ->
res.json if err then err else robot.data().connections
getConnectionByName: (req, res) ->
robotid = req.params.robotid
connectionid = req.params.connectionid
master.findRobotConnection robotid, connectionid, (err, connection) ->
res.json if err then err else connection.data()
ioSetupDeviceEventClient: (req) ->
robotid = req.params.robotid
deviceid = req.params.deviceid
master.findRobotDevice robotid, deviceid, (err, device) ->
res.io.respond(err) if err
device.on 'update', (data) -> res.io.respond { data: data }

View File

@ -16,13 +16,23 @@ module.exports = class Connection extends EventEmitter
klass = this
constructor: (opts = {}) ->
opts.id ?= Math.floor(Math.random() * 10000)
@self = this
@robot = opts.robot
@name = opts.name
@connection_id = opts.id
@adaptor = @requireAdaptor(opts.adaptor) # or 'loopback')
@port = new Port(opts.port)
proxyFunctionsToObject @adaptor.commands(), @adaptor, klass
data: ->
{
name: @name,
port: @port.toString()
adaptor: @adaptor.constructor.name || @adaptor.name
connection_id: @connection_id
}
connect: (callback) =>
msg = "Connecting to '#{@name}'"
msg += " on port '#{@port.toString()}'" if @port?

View File

@ -13,6 +13,8 @@ Robot = require("./robot")
require('./utils')
require('./logger')
require('./api/api')
Logger.setup()
class Cylon
@ -23,6 +25,10 @@ class Cylon
class Master
robots = []
api = null
constructor: ->
@self = this
robot: (opts) =>
opts.master = this
@ -30,7 +36,42 @@ class Cylon
robots.push robot
robot
robots: -> robots
findRobot: (name, callback) ->
robot = null
for bot in robots
robot = bot if bot.name is name
error = { error: "No Robot found with the name #{name}" } unless robot?
if callback then callback(error, robot) else robot
findRobotDevice: (robotid, deviceid, callback) ->
@findRobot robotid, (err, robot) ->
callback(err, robot) if err
device = robot.devices[deviceid] if robot.devices[deviceid]
unless device?
error = { error: "No device found with the name #{device}." }
if callback then callback(error, device) else device
findRobotConnection: (robotid, connid, callback) ->
@findRobot robotid, (err, robot) ->
callback(err, robot) if err
connection = robot.connections[connid] if robot.connections[connid]
unless connection?
error = { error: "No connection found with the name #{connection}." }
if callback then callback(error, connection) else connection
start: ->
do @startAPI
robot.start() for robot in robots
startAPI: ->
api ?= new Api.Server(master: @self)
module.exports = Cylon.getInstance()

View File

@ -29,6 +29,15 @@ module.exports = class Device extends EventEmitter
Logger.info msg
@driver.start(callback)
data: ->
{
name: @name
driver: @driver.constructor.name || @driver.name
pin: if @pin? then @pin.toString else null
connection: @connection.data()
commands: @driver.commands()
}
determineConnection: (c) ->
@robot.connections[c] if c

View File

@ -32,13 +32,21 @@ module.exports = class Robot
@initConnections(opts.connection or opts.connections)
@initDevices(opts.device or opts.devices)
@work = opts.work or -> (Logger.info "No work yet")
for n, func of opts
@robot[n] = func unless n in ['connection', 'connections', 'device', 'devices', 'work']
reserved = ['connection', 'connections', 'device', 'devices', 'work']
@robot[n] = func unless n in reserved
@randomName: ->
"Robot #{ Math.floor(Math.random() * 100000) }"
data: ->
{
name: @name
connections: (connection.data() for n, connection of @connections)
devices: (device.data() for n, device of @devices)
}
initConnections: (connections) =>
Logger.info "Initializing connections..."
return unless connections?
@ -76,7 +84,7 @@ module.exports = class Robot
for n, device of @devices
@robot[n] = device
starters[n] = device.start
Async.parallel starters, callback
requireAdaptor: (adaptorName, connection) ->