Merge branch 'master' into servo-level-up

This commit is contained in:
Edgar Silva 2014-07-02 17:05:15 -05:00
commit c4b10e66f4
48 changed files with 1418 additions and 1176 deletions

View File

@ -1,10 +1,7 @@
language: node_js
script:
- npm install -g codeclimate-test-reporter
- npm install -g istanbul codeclimate-test-reporter
- make cover
- CODECLIMATE_REPO_TOKEN=d3aad610220b6eaf4f51e38393c1b62586b1d68b898b42e418d9c2a8e0a7cb0d codeclimate < coverage/lcov.info
node_js:
- '0.10'
branches:
except:
- cylonjs.com

View File

@ -10,6 +10,9 @@ Cylon.js exists thanks to the efforts of these hardworking humans:
- Adrian Zankich ([@zankich](https://github.com/zankich))
- Javier Cervantes ([@solojavier](https://github.com/solojavier))
- Chris Mattheiu ([@chrismatthieu](https://github.com/chrismatthieu))
- Fábio Franco Uechi ([@fabito](https://github.com/fabito))
- Andrew Nesbitt ([@andrew](https://github.com/andrew))
- Chris Boette ([@chrisbodhi](https://github.com/chrisbodhi))
Thank you!

View File

@ -8,14 +8,14 @@ VERSION := $(shell node -e "console.log(require('./package.json').version)")
test:
@$(BIN)/mocha --colors $(TEST_FILES)
cover:
@$(BIN)/istanbul cover $(BIN)/_mocha $(TEST_FILES) --report lcovonly -- -R spec
bdd:
@$(BIN)/mocha --colors -R spec $(TEST_FILES)
cover:
@istanbul cover $(BIN)/_mocha $(TEST_FILES) --report lcovonly -- -R spec
lint:
@$(BIN)/jshint ./lib
@jshint ./lib
release:
@git tag -m "$(VERSION)" v$(VERSION)

View File

@ -1,4 +1,4 @@
[![Cylon.js](https://raw.github.com/hybridgroup/cylon/gh-pages/images/elements/logo.png)](http://cylonjs.com)
[![Cylon.js](https://cdn.rawgit.com/hybridgroup/cylon-site/master/source/images/elements/cylon.png)](http://cylonjs.com)
Cylon.js is a JavaScript framework for robotics and physical computing built on
top of Node.js.
@ -16,7 +16,7 @@ Want to use Golang to power your robots? Check out our sister project,
## Build Status:
[![Build Status](https://secure.travis-ci.org/hybridgroup/cylon.png?branch=master)](http://travis-ci.org/hybridgroup/cylon) [![Code Climate](https://codeclimate.com/github/hybridgroup/cylon/coverage.png)](https://codeclimate.com/github/hybridgroup/cylon)
[![Build Status](https://secure.travis-ci.org/hybridgroup/cylon.png?branch=master)](http://travis-ci.org/hybridgroup/cylon) [![Code Climate](https://codeclimate.com/github/hybridgroup/cylon.png)](https://codeclimate.com/github/hybridgroup/cylon) [![Code Climate](https://codeclimate.com/github/hybridgroup/cylon/coverage.png)](https://codeclimate.com/github/hybridgroup/cylon)
## Examples
@ -222,11 +222,11 @@ Cylon itself. Check it out at https://github.com/hybridgroup/cylon-cli.
We're busy adding documentation to our website, check it out at
[cylonjs.com/documentation][docs].
If you want to help with documentation, you can find some helpful instructions
on the [cylonjs.com branch][cylonjs-branch].
If you want to help with documentation, you can find the code for our website at
on the [https://github.com/hybridgroup/cylon-site](https://github.com/hybridgroup/cylon-site).
[docs]: http://cylonjs.com/documentation
[cylonjs-branch]: https://github.com/hybridgroup/cylon/tree/cylonjs.com
[docs site]: https://github.com/hybridgroup/cylon-site
## Contributing
@ -244,6 +244,11 @@ on the [cylonjs.com branch][cylonjs-branch].
## Release History
Version 0.15.1 - Fixed issue with the API on Tessel
Version 0.15.0 - Better halting, cleaner startup, removed 'connect' and 'start'
events, and misc other cleanups/refactors.
Version 0.14.0 - Removal of node-namespace and misc. cleanup
Version 0.13.3 - Fixes bug with disconnect functions not being called.

View File

@ -28,16 +28,16 @@ skynet = {
console.log(data);
if (data.payload != null){
var robot,
bot,
robots = data.payload.robots;
for(var index in robots){
robot = robots[index];
console.log(robot);
my.master.findRobot(robot.name, function(err, bot){
if (robot.cmd == 'on')
bot.devices[robot.device].turnOn();
else
bot.devices[robot.device].turnOff();
});
bot = Cylon.robots[robot.name];
if (robot.cmd == 'on')
bot.devices[robot.device].turnOn();
else
bot.devices[robot.device].turnOff();
}
}
});

View File

@ -19,8 +19,8 @@ It will have a single connection and device, both to the keyboard.
When we tell this robot to work, it's going to listen to the 'a' key on the
keyboard and let us know when it's been pressed.
work: (my) function() {
my.keyboard.on('a', function(key) { console.log "A PRESSED!" });
work: function(my) {
my.keyboard.on('a', function(key) { console.log("A PRESSED!") });
}
With that done, let's get started!

View File

@ -0,0 +1,169 @@
/*
* leap_ardrone.js
*
* Written by Giuliano Sposito and Fábio Uechi
* Copyright (c) 2013-2014 CI&T Software
* Licensed under the Apache 2.0 license.
*/
Cylon = require('cylon');
// constants
var lastS = 0,
handStartPosition = [],
handStartDirection = [],
UP_CONTROL_THRESHOLD = 50,
UP_SPEED_FACTOR = 0.01,
CIRCLE_THRESHOLD = 1.5,
DIRECTION_THRESHOLD = 0.25,
DIRECTION_SPEED_FACTOR = 0.05,
TURN_SPEED = 0.5,
TURN_TRESHOLD = 0.2,
TURN_SPEED_FACTOR = 2.0;
Cylon.robot({
connections: [
{ name: 'leapmotion', adaptor: 'leapmotion', port: '127.0.0.1:6437' },
{ name: 'ardrone', adaptor: 'ardrone', port: '192.168.1.1' },
{ name: 'keyboard', adaptor: 'keyboard' }
],
devices: [
{ name: 'drone', driver: 'ardrone', connection:'ardrone' },
{ name: 'leapmotion', driver: 'leapmotion', connection:'leapmotion' },
{ name: 'keyboard', driver: 'keyboard', connection:'keyboard'}
],
work: function(my) {
// HAND
my.leapmotion.on('hand', function(hand) {
// detects open hand ==> reset
if (hand.s>1.5 && lastS<=1.5) {
handStartPosition = hand.palmPosition;
handStartDirection = hand.direction;
};
// TURNS
if(hand.s>1.5 && Math.abs(handStartDirection[0]-hand.direction[0]) > TURN_TRESHOLD ) {
var signal = handStartDirection[0]-hand.direction[0];
var value = (Math.abs(handStartDirection[0]-hand.direction[0])-TURN_TRESHOLD)*TURN_SPEED_FACTOR;
if (signal>0){
my.drone.counterClockwise(value);
}
if (signal<0){
my.drone.clockwise(value);
}
}
// UP and DOWN
if (hand.s>1.5 && Math.abs(hand.palmPosition[1]-handStartPosition[1]) > UP_CONTROL_THRESHOLD) {
var signal = (hand.palmPosition[1]-handStartPosition[1]) >= 0 ? 1 : -1;
var value = Math.round(Math.abs((hand.palmPosition[1]-handStartPosition[1]))-UP_CONTROL_THRESHOLD) * UP_SPEED_FACTOR;
if (signal>0) {
my.drone.up(value);
};
if (signal<0) {
my.drone.down(value);
}
}
// DIRECTION FRONT/BACK
if (hand.s>1.5 && (Math.abs(hand.palmNormal[2])>DIRECTION_THRESHOLD)) {
if (hand.palmNormal[2]>0) {
var value = Math.abs(Math.round( hand.palmNormal[2]*10 + DIRECTION_THRESHOLD )*DIRECTION_SPEED_FACTOR);
my.drone.forward( value );
};
if (hand.palmNormal[2]<0) {
var value = Math.abs(Math.round( hand.palmNormal[2]*10 - DIRECTION_THRESHOLD )*DIRECTION_SPEED_FACTOR);
my.drone.back( value );
};
}
// DIRECTION LEFT/RIGHT
if (hand.s>1.5 && (Math.abs(hand.palmNormal[0])>DIRECTION_THRESHOLD)) {
if (hand.palmNormal[0]>0) {
var value = Math.abs(Math.round( hand.palmNormal[0]*10 + DIRECTION_THRESHOLD )*DIRECTION_SPEED_FACTOR);
my.drone.left( value );
};
if (hand.palmNormal[0]<0) {
var value = Math.abs(Math.round( hand.palmNormal[0]*10 - DIRECTION_THRESHOLD )*DIRECTION_SPEED_FACTOR);
my.drone.right( value );
};
}
// AUTO FREEZE
if ( hand.s>1.5 && // open hand
(Math.abs(hand.palmNormal[0])<DIRECTION_THRESHOLD) && // within left/right threshold
(Math.abs(hand.palmNormal[2])<DIRECTION_THRESHOLD) && // within forward/back threshold
Math.abs(hand.palmPosition[1]-handStartPosition[1]) < UP_CONTROL_THRESHOLD && // within up/down threshold
Math.abs(handStartDirection[0]-hand.direction[0]) < TURN_TRESHOLD) // within turn threshold
{
my.drone.stop();
}
// COMMAND FREEZE
if (hand.s<=1.5 && lastS > 1.5) { // closed hand
my.drone.stop();
}
lastS = hand.s;
});// end hand
// Gestures
my.leapmotion.on('gesture', function(gesture) {
if (gesture.type=='circle' && gesture.state=='stop' && gesture.progress > CIRCLE_THRESHOLD ){
if (gesture.normal[2] < 0) {
my.drone.takeoff();
};
if (gesture.normal[2] > 0) {
my.drone.land();
}
}
// EMERGENCE STOP
if (gesture.type=='keyTap' || gesture.type=='screenTap') {
my.drone.stop();
};
}); // end gesture
//KEYBOARD
my.keyboard.on('right', function(key) {
my.drone.rightFlip();
});
my.keyboard.on('left', function(key) {
my.drone.leftFlip();
});
my.keyboard.on('up', function(key) {
my.drone.frontFlip();
});
my.keyboard.on('down', function(key) {
my.drone.backFlip();
});
my.keyboard.on('w', function(key) {
my.drone.wave();
});
my.keyboard.on('s', function(key) {
my.drone.stop();
});
my.keyboard.on('l', function(key) {
my.drone.land();
});
} // end work
}).start();

View File

@ -0,0 +1,142 @@
# Leapmotion Ardrone 2.0
This example illustrates how to use Leap Motion and the keyboard to control an ARDrone. We basically use one hand to drive the drone (takeoff, land, up, down, etc) and the arrow keys to perform some cool stunts (flips).
First, let's import Cylon:
var Cylon = require('../..');
Now that we have Cylon imported, we can start defining our robot
Cylon.robot({
Let's define the connections and devices:
connections: [
{ name: 'leapmotion', adaptor: 'leapmotion', port: '127.0.0.1:6437' },
{ name: 'ardrone', adaptor: 'ardrone', port: '192.168.1.1' },
{ name: 'keyboard', adaptor: 'keyboard' }
],
devices: [
{ name: 'drone', driver: 'ardrone', connection:'ardrone' },
{ name: 'leapmotion', driver: 'leapmotion', connection:'leapmotion' },
{ name: 'keyboard', driver: 'keyboard', connection:'keyboard'}
],
Now that Cylon knows about the necessary hardware we're going to be using, we'll
tell it what work we want to do:
work: function(my) {
Lets use the circle gesture to take off and land : two rounds clockwise will trigger the takeoff() and counter clockwise will tell the drone to land:
my.leapmotion.on('gesture', function(gesture) {
if (gesture.type=='circle' && gesture.state=='stop' && gesture.progress > CIRCLE_THRESHOLD ){
if (gesture.normal[2] < 0) {
my.drone.takeoff();
};
if (gesture.normal[2] > 0) {
my.drone.land();
}
}
});
Whenever we get a 'hand' gesture event from Leap Motion we need to tell ARDrone what to do.
my.leapmotion.on('hand', function(hand) {
In case we turn our hand to the right or left we want the drone to rotate clockwise or counter clockwise respectively:
if(hand.s>1.5 && Math.abs(handStartDirection[0]-hand.direction[0]) > TURN_TRESHOLD ) {
var signal = handStartDirection[0]-hand.direction[0];
var value = (Math.abs(handStartDirection[0]-hand.direction[0])-TURN_TRESHOLD) * TURN_SPEED_FACTOR;
if (signal>0){
my.drone.counterClockwise(value);
}
if (signal<0){
my.drone.clockwise(value);
}
}
In case we raise our hand up or lower it down we tell the drone to go up or down respectively:
if (hand.s>1.5 && Math.abs(hand.palmPosition[1]-handStartPosition[1]) > UP_CONTROL_THRESHOLD) {
var signal = (hand.palmPosition[1]-handStartPosition[1]) >= 0 ? 1 : -1;
var value = Math.round(Math.abs((hand.palmPosition[1]-handStartPosition[1]))-UP_CONTROL_THRESHOLD) * UP_SPEED_FACTOR;
if (signal>0) {
my.drone.up(value);
};
if (signal<0) {
my.drone.down(value);
}
}
In order to move the drone forward, backward, left or right we need to detect the hand inclination. Imagine your hand is the drone and lean it towards the direction we want it to move.
if (hand.s>1.5 && (Math.abs(hand.palmNormal[2])>DIRECTION_THRESHOLD)) {
if (hand.palmNormal[2]>0) {
var value = Math.abs(Math.round( hand.palmNormal[2] * 10 + DIRECTION_THRESHOLD ) * DIRECTION_SPEED_FACTOR);
my.drone.forward( value );
};
if (hand.palmNormal[2]<0) {
var value = Math.abs(Math.round( hand.palmNormal[2] * 10 - DIRECTION_THRESHOLD ) * DIRECTION_SPEED_FACTOR);
my.drone.back( value );
};
}
if (hand.s>1.5 && (Math.abs(hand.palmNormal[0])>DIRECTION_THRESHOLD)) {
if (hand.palmNormal[0]>0) {
var value = Math.abs(Math.round( hand.palmNormal[0] * 10 + DIRECTION_THRESHOLD ) * DIRECTION_SPEED_FACTOR);
my.drone.left( value );
};
if (hand.palmNormal[0]<0) {
var value = Math.abs(Math.round( hand.palmNormal[0] * 10 - DIRECTION_THRESHOLD ) * DIRECTION_SPEED_FACTOR);
my.drone.right( value );
};
}
Whenever we close our hand we tell the drone no stop:
if (hand.s<=1.5 && lastS > 1.5) { // closed hand
my.drone.stop();
}
And finally lets use the arrow keys to tell the drone to do some cool stunts:
my.keyboard.on('right', function(key) {
my.drone.rightFlip();
});
my.keyboard.on('left', function(key) {
my.drone.leftFlip();
});
my.keyboard.on('up', function(key) {
my.drone.frontFlip();
});
my.keyboard.on('down', function(key) {
my.drone.backFlip();
});
Now that our robot knows what work to do, and the work it will be doing that
hardware with, we can start it:
}).start();

View File

@ -29,9 +29,8 @@ var SalesforceRobot = (function() {
msg += "Bucks: " + data.sobject.Bucks__c + ",";
msg += "SM_Id: " + data.sobject.Id;
console.log(msg);
me.master.findRobot(data.sobject.Sphero_Name__c, function(err, spheroBot) {
spheroBot.react(spheroBot.devices.sphero);
});
var spheroBot = Cylon.robots[data.sobject.Sphero_Name__c];
spheroBot.react(spheroBot.devices.sphero);
});
});
};
@ -70,9 +69,8 @@ var SpheroRobot = (function() {
spheroName: "" + me.name,
bucks: "" + (me.totalBucks++)
});
me.master.findRobot('salesforce', function(err, sf) {
sf.devices.salesforce.push('SpheroController', 'POST', data);
});
var sf = Cylon.robots['salesforce'];
sf.devices.salesforce.push('SpheroController', 'POST', data);
});
};

View File

@ -42,9 +42,8 @@ tell it what work we want to do:
msg += "Bucks: " + data.sobject.Bucks__c + ",";
msg += "SM_Id: " + data.sobject.Id;
console.log(msg);
me.master.findRobot(data.sobject.Sphero_Name__c, function(err, spheroBot) {
spheroBot.react(spheroBot.devices.sphero);
});
var spheroBot = Cylon.robots[data.sobject.Sphero_Name__c];
spheroBot.react(spheroBot.devices.sphero);
});
});
};
@ -83,9 +82,8 @@ tell it what work we want to do:
spheroName: "" + me.name,
bucks: "" + (me.totalBucks++)
});
me.master.findRobot('salesforce', function(err, sf) {
sf.devices.salesforce.push('SpheroController', 'POST', data);
});
var sf = Cylon.robots['salesforce'];
sf.devices.salesforce.push('SpheroController', 'POST', data);
});
};

View File

@ -57,9 +57,8 @@ var SalesforceRobot = (function() {
console.log(msg);
me.master.findRobot(name, function(err, spheroBot) {
spheroBot.react(spheroBot.devices.sphero);
});
var spheroBot = Cylon.robots[name];
spheroBot.react(spheroBot.devices.sphero);
me.spheroReport[name] = bucks;
toPebble = "";
@ -71,9 +70,8 @@ var SalesforceRobot = (function() {
toPebble += "" + key + ": $" + val + "\n";
}
me.master.findRobot('pebble', function(error, pebbleBot) {
pebbleBot.message(pebbleBot.devices.pebble, toPebble);
});
var pebbleBot = Cylon.robots['pebble'];
pebbleBot.message(pebbleBot.devices.pebble, toPebble);
});
});
};
@ -141,9 +139,8 @@ var SpheroRobot = (function() {
bucks: "" + (me.totalBucks++)
});
me.master.findRobot('salesforce', function(err, sf) {
sf.devices.salesforce.push("SpheroController", "POST", data);
});
var sf = Cylon.robots['salesforce'];
sf.devices.salesforce.push("SpheroController", "POST", data);
});
};

View File

@ -78,9 +78,8 @@ Tell it what work we want to do:
console.log(msg);
me.master.findRobot(name, function(err, spheroBot) {
spheroBot.react(spheroBot.devices.sphero);
});
var spheroBot = Cylon.robots[name];
spheroBot.react(spheroBot.devices.sphero);
me.spheroReport[name] = bucks;
toPebble = "";
@ -92,9 +91,8 @@ Tell it what work we want to do:
toPebble += "" + key + ": $" + val + "\n";
}
me.master.findRobot('pebble', function(error, pebbleBot) {
pebbleBot.message(pebbleBot.devices.pebble, toPebble);
});
var pebbleBot = Cylon.robots['pebble'];
pebbleBot.message(pebbleBot.devices.pebble, toPebble);
});
});
};
@ -166,9 +164,8 @@ Tell it what work we want to do:
bucks: "" + (me.totalBucks++)
});
me.master.findRobot('salesforce', function(err, sf) {
sf.devices.salesforce.push("SpheroController", "POST", data);
});
var sf = Cylon.robots['salesforce'];
sf.devices.salesforce.push("SpheroController", "POST", data);
});
};

View File

@ -9,7 +9,8 @@
"use strict";
var Basestar = require('./basestar'),
Logger = require('./logger');
Logger = require('./logger'),
Utils = require('./utils');
// The Adaptor class is a base class for Adaptor classes in external Cylon
// modules to use. It offers basic functions for connecting/disconnecting that
@ -31,33 +32,31 @@ module.exports = Adaptor = function Adaptor(opts) {
this.self = this;
this.name = opts.name;
this.connection = opts.connection;
this.commandList = [];
};
subclass(Adaptor, Basestar);
Utils.subclass(Adaptor, Basestar);
// Public: Exposes all commands the adaptor will respond to/proxy
//
// Returns an array of string method names
Adaptor.prototype.commands = function() {
return this.commandList;
};
Adaptor.prototype.commands = [];
// Public: Connects to the adaptor, and emits 'connect' from the @connection
// when done.
// Public: Connects to the adaptor, and triggers the provided callback when
// done.
//
// callback - function to run when the adaptor is connected
//
// Returns nothing
Adaptor.prototype.connect = function(callback) {
Logger.info("Connecting to adaptor '" + this.name + "'...");
Logger.info("Connecting to adaptor '" + this.name + "'.");
callback(null);
return this.connection.emit('connect');
return true;
};
// Public: Disconnects from the adaptor
//
// callback - function to run when the adaptor is disconnected
//
// Returns nothing
Adaptor.prototype.disconnect = function() {
return Logger.info("Disconnecting from adaptor '" + this.name + "'...");
Adaptor.prototype.disconnect = function(callback) {
Logger.info("Disconnecting from adaptor '" + this.name + "'.");
this.removeAllListeners();
callback();
};

View File

@ -1,5 +1,5 @@
/*
* api
* Cylon API
* cylonjs.com
*
* Copyright (c) 2013-2014 The Hybrid Group
@ -8,9 +8,11 @@
"use strict";
var fs = require('fs');
var fs = require('fs'),
path = require('path');
var express = require('express');
var express = require('express'),
bodyParser = require('body-parser');
var Logger = require('./logger');
@ -21,198 +23,103 @@ var API = module.exports = function API(opts) {
opts = {};
}
this.opts = opts;
this.host = opts.host || "127.0.0.1";
this.port = opts.port || "3000";
this.ssl = opts.ssl;
this.master = opts.master;
this.server = express();
for (var d in this.defaults) {
this[d] = opts.hasOwnProperty(d) ? opts[d] : this.defaults[d];
}
this.createServer();
this.express.set('title', 'Cylon API Server');
this.express.use(self.setupAuth());
this.express.use(bodyParser());
this.express.use(express["static"](__dirname + "/../node_modules/robeaux/"));
// set CORS headers for API requests
this.express.use(function(req, res, next) {
res.set("Access-Control-Allow-Origin", self.CORS || "*");
res.set("Access-Control-Allow-Headers", "Content-Type");
res.set('Content-Type', 'application/json');
return next();
});
// extracts command params from request
this.express.use(function(req, res, next) {
var method = req.method.toLowerCase(),
container = {};
req.commandParams = [];
if (method === 'get' || Object.keys(req.query).length > 0) {
container = req.query;
} else if (typeof(req.body) === 'object') {
container = req.body;
}
for (var p in container) {
req.commandParams.push(container[p]);
};
return next();
});
// load route definitions
this.express.use('/', require('./api/routes'))
};
API.prototype.defaults = {
host: '127.0.0.1',
port: '3000',
auth: false,
CORS: '',
ssl: {
key: path.normalize(__dirname + "/api/ssl/server.key"),
cert: path.normalize(__dirname + "/api/ssl/server.crt")
}
};
API.prototype.createServer = function createServer() {
this.express = express();
//configure ssl if requested
if (this.ssl && typeof(this.ssl) === 'object') {
var https = require('https');
var options = {
key: fs.readFileSync(this.ssl.key || __dirname + "/ssl/server.key"),
cert: fs.readFileSync(this.ssl.cert || __dirname + "/ssl/server.crt")
};
this.server.node = https.createServer(options, this.server);
this.server = https.createServer({
key: fs.readFileSync(this.ssl.key),
cert: fs.readFileSync(this.ssl.cert)
}, this.express);
} else {
Logger.warn("API using insecure connection. We recommend using an SSL certificate with Cylon.")
this.server.node = this.server;
this.server = this.express;
}
};
// configure basic auth, if requested
if (opts.auth && opts.auth.type && opts.auth.type === 'basic') {
var user = opts.auth.user,
pass = opts.auth.pass;
API.prototype.setupAuth = function setupAuth() {
var authfn = function auth(req, res, next) { next(); };
if (user && pass) {
this.server.use(express.basicAuth(user, pass));
if (typeof(this.auth) === 'object' && this.auth.type) {
var type = this.auth.type,
module = "./api/auth/" + type,
filename = path.normalize(__dirname + "/" + module + ".js"),
exists = fs.existsSync(filename);
if (exists) {
authfn = require(filename)(this.auth);
}
}
};
this.server.set('title', 'Cylon API Server');
this.server.use(express.json());
this.server.use(express.urlencoded());
this.server.use(express["static"](__dirname + "/../node_modules/robeaux/"));
return authfn;
};
API.prototype.listen = function() {
var self = this;
this.server.node.listen(this.port, this.host, null, function() {
var title = self.server.get('title');
this.server.listen(this.port, this.host, null, function() {
var title = self.express.get('title');
var protocol = self.ssl ? "https" : "http";
Logger.info(title + " is now online.");
Logger.info("Listening at " + protocol + "://" + self.host + ":" + self.port);
});
};
// Parses req to extract params to be used for commands.
//
// Returns an array of params
API.prototype.parseCommandParams = function(req) {
var param_container = {},
params = [];
if (req.method === 'GET' || Object.keys(req.query).length > 0) {
param_container = req.query;
} else if (typeof req.body === 'object') {
param_container = req.body;
}
for (var p in param_container) { params.push(param_container[p]); }
return params;
};
// Sets all routes for the server
API.prototype.configureRoutes = function() {
var self = this;
var master = this.master;
this.server.all("/*", function(req, res, next) {
res.set("Access-Control-Allow-Origin", self.opts.CORS || "*");
res.set("Access-Control-Allow-Headers", "Content-Type");
res.set('Content-Type', 'application/json');
return next();
});
this.server.get("/robots", function(req, res) {
var data = [];
for (var i = 0; i < master.robots.length; i++) {
var robot = master.robots[i];
data.push(robot.data());
}
res.json(data);
});
this.server.get("/robots/:robot", function(req, res) {
master.findRobot(req.params.robot, function(err, robot) {
res.json(err ? err : robot.data());
});
});
this.server.get("/robots/:robot/commands", function(req, res) {
master.findRobot(req.params.robot, function(err, robot) {
res.json(err ? err : robot.data().commands);
});
});
this.server.all("/robots/:robot/commands/:command", function(req, res) {
var params = self.parseCommandParams(req);
master.findRobot(req.params.robot, function(err, robot) {
if (err) { return res.json(err); }
var result = robot[req.params.command].apply(robot, params);
res.json({ result: result });
});
});
this.server.get("/robots/:robot/devices", function(req, res) {
master.findRobot(req.params.robot, function(err, robot) {
res.json(err ? err : robot.data().devices);
});
});
this.server.get("/robots/:robot/devices/:device", function(req, res) {
var robot = req.params.robot,
device = req.params.device;
master.findRobotDevice(robot, device, function(err, device) {
res.json(err ? err : device.data());
});
});
this.server.get("/robots/:robot/devices/:device/events/:event", function(req, res) {
var robot = req.params.robot,
device = req.params.device,
event = req.params.event;
master.findRobotDevice(robot, device, function(err, device) {
if (err) { res.json(err); }
res.writeHead(200, {
'Content-Type': 'text/event-stream',
'Connection': 'keep-alive',
'Cache-Control': 'no-cache'
});
var writeData = function(data) {
res.write("data: " + JSON.stringify(data) + "\n\n")
};
device.on(event, writeData);
res.on('close', function() {
device.removeListener(event, writeData);
});
});
});
this.server.get("/robots/:robot/devices/:device/commands", function(req, res) {
var robot = req.params.robot,
device = req.params.device;
master.findRobotDevice(robot, device, function(err, device) {
res.json(err ? err : device.data().commands);
});
});
this.server.all("/robots/:robot/devices/:device/commands/:command", function(req, res) {
var robot = req.params.robot,
device = req.params.device,
command = req.params.command;
var params = self.parseCommandParams(req);
master.findRobotDevice(robot, device, function(err, device) {
if (err) { return res.json(err); }
var result = device[command].apply(device, params);
res.json({ result: result });
});
});
this.server.get("/robots/:robot/connections", function(req, res) {
master.findRobot(req.params.robot, function(err, robot) {
res.json(err ? err : robot.data().connections);
});
});
this.server.get("/robots/:robot/connections/:connection", function(req, res) {
var robot = req.params.robot,
connection = req.params.connection;
master.findRobotConnection(robot, connection, function(err, connection) {
res.json(err ? err : connection.data());
});
});
};

57
lib/api/auth/basic.js Normal file
View File

@ -0,0 +1,57 @@
/*
* Cylon API - Basic Auth
* cylonjs.com
*
* Copyright (c) 2013-2014 The Hybrid Group
* Licensed under the Apache 2.0 license.
*/
var http = require('http');
module.exports = function(config) {
var user = config.user,
pass = config.pass;
return function auth(req, res, next) {
var auth = req.headers.authorization;
if (!auth) {
return unauthorized(res);
}
// malformed
var parts = auth.split(' ');
if ('basic' != parts[0].toLowerCase() || !parts[1]) {
return next(error(400));
}
auth = parts[1];
// credentials
auth = new Buffer(auth, 'base64').toString();
auth = auth.match(/^([^:]+):(.+)$/);
if (!auth) {
return unauthorized(res);
}
if (auth[1] === user && auth[2] === pass) {
return next();
}
return unauthorized(res);
};
};
var unauthorized = function unauthorized(res) {
res.statusCode = 401;
res.setHeader('WWW-Authenticate', 'Basic realm="Authorization Required"');
res.end('Unauthorized');
};
var error = function error(code, msg){
var err = new Error(msg || http.STATUS_CODES[code]);
err.status = code;
return err;
};

114
lib/api/routes.js Normal file
View File

@ -0,0 +1,114 @@
/*
* Cylon API - Route Definitions
* cylonjs.com
*
* Copyright (c) 2013-2014 The Hybrid Group
* Licensed under the Apache 2.0 license.
*/
var Cylon = require('../cylon');
var router = module.exports = require('express').Router();
// Loads up the appropriate Robot/Device/Connection instances, if they are
// present in the route params.
var load = function load(req, res, next) {
var robot = req.params.robot,
device = req.params.device,
connection = req.params.connection;
if (robot) {
req.robot = Cylon.robots[robot];
if (!req.robot) {
return res.json({ error: "No Robot found with the name " + robot });
}
}
if (device) {
req.device = req.robot.devices[device];
if (!req.device) {
return res.json({ error: "No device found with the name " + device });
}
}
if (connection) {
req.connection = req.robot.connections[connection];
if (!req.connection) {
return res.json({ error: "No connection found with the name " + connection });
}
}
next();
};
router.get("/robots", function(req, res) {
var data = [];
for (var bot in Cylon.robots) {
data.push(Cylon.robots[bot]);
}
res.json(data);
});
router.get("/robots/:robot", load, function(req, res) {
res.json(req.robot);
});
router.get("/robots/:robot/commands", load, function(req, res) {
res.json(req.robot.toJSON().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);
res.json({ result: result });
});
router.get("/robots/:robot/devices", load, function(req, res) {
res.json(req.robot.toJSON().devices);
});
router.get("/robots/:robot/devices/:device", load, function(req, res) {
res.json(req.device);
});
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'
});
var writeData = function(data) {
res.write("data: " + JSON.stringify(data) + "\n\n");
};
req.device.on(event, writeData);
res.on('close', function() {
req.device.removeListener(event, writeData);
});
});
router.get("/robots/:robot/devices/:device/commands", load, function(req, res) {
res.json(req.device.toJSON().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);
res.json({ result: result });
});
router.get("/robots/:robot/connections", load, function(req, res) {
res.json(req.robot.toJSON().connections);
});
router.get("/robots/:robot/connections/:connection", load, function(req, res) {
res.json(req.connection);
});

View File

@ -8,22 +8,19 @@
"use strict";
require('./utils');
var EventEmitter = require('events').EventEmitter;
var Utils = require('./utils');
// Basestar is a base class to be used when writing external Cylon adaptors and
// drivers. It provides some useful base methods and functionality
//
// It also extends EventEmitter, so child classes are capable of emitting events
// for other parts of the system to handle.
var Basestar;
var Basestar = module.exports = function Basestar() {
};
module.exports = Basestar = function Basestar(opts) {
this.self = this;
}
subclass(Basestar, EventEmitter);
Utils.subclass(Basestar, EventEmitter);
// Public: Proxies calls from all methods in the object to a target object
//
@ -34,8 +31,11 @@ subclass(Basestar, EventEmitter);
//
// Returns the klass where the methods have been proxied
Basestar.prototype.proxyMethods = function(methods, target, source, force) {
if (force == null) { force = false; }
return proxyFunctionsToObject(methods, target, source, force);
if (force == null) {
force = false;
}
return Utils.proxyFunctionsToObject(methods, target, source, force);
};
// Public: Defines an event handler that proxies events from a source object
@ -50,8 +50,6 @@ Basestar.prototype.proxyMethods = function(methods, target, source, force) {
//
// Returns the source
Basestar.prototype.defineEvent = function(opts) {
var self = this;
opts.sendUpdate = opts.sendUpdate || false;
opts.targetEventName = opts.targetEventName || opts.eventName;
@ -77,7 +75,9 @@ Basestar.prototype.defineEvent = function(opts) {
//
// Returns this.connector
Basestar.prototype.defineAdaptorEvent = function(opts) {
if (typeof opts === 'string') { opts = { eventName: opts }; }
if (typeof opts === 'string') {
opts = { eventName: opts };
}
opts['source'] = this.connector;
opts['target'] = this.connection;
@ -93,7 +93,9 @@ Basestar.prototype.defineAdaptorEvent = function(opts) {
//
// Returns this.connection
Basestar.prototype.defineDriverEvent = function(opts) {
if (typeof opts === 'string') { opts = { eventName: opts }; }
if (typeof opts === 'string') {
opts = { eventName: opts };
}
opts['source'] = this.connection;
opts['target'] = this.device;

View File

@ -8,11 +8,10 @@
'use strict';
require("./utils");
var EventEmitter = require('events').EventEmitter;
var Logger = require('./logger');
var Logger = require('./logger'),
Utils = require('./utils');
// The Connection class represents the interface to
// a specific group of hardware devices. Examples would be an
@ -38,7 +37,7 @@ module.exports = Connection = function Connection(opts) {
opts.id = Math.floor(Math.random() * 10000);
}
this.connect = bind(this.connect, this);
this.connect = Utils.bind(this.connect, this);
this.self = this;
this.robot = opts.robot;
@ -47,15 +46,15 @@ module.exports = Connection = function Connection(opts) {
this.port = opts.port;
this.adaptor = this.initAdaptor(opts);
proxyFunctionsToObject(this.adaptor.commands(), this.adaptor, this.self);
Utils.proxyFunctionsToObject(this.adaptor.commands, this.adaptor, this.self);
}
subclass(Connection, EventEmitter);
Utils.subclass(Connection, EventEmitter);
// Public: Exports basic data for the Connection
// Public: Expresses the Connection in JSON format
//
// Returns an Object containing Connection data
Connection.prototype.data = function() {
Connection.prototype.toJSON = function() {
return {
name: this.name,
port: this.port,
@ -71,27 +70,34 @@ Connection.prototype.data = function() {
// Returns the result of the supplied callback function
Connection.prototype.connect = function(callback) {
var msg = "Connecting to " + this.name;
var msg = "Connecting to '" + this.name + "'";
if (this.port != null) {
msg += " on port " + this.port;
}
msg += ".";
Logger.info(msg);
return this.adaptor.connect(callback);
};
// Public: Disconnect the adaptor's connection
//
// callback - function to be triggered then the adaptor has disconnected
//
// Returns nothing
Connection.prototype.disconnect = function() {
var msg = "Disconnecting from " + this.name;
Connection.prototype.disconnect = function(callback) {
var msg = "Disconnecting from '" + this.name + "'";
if (this.port != null) {
msg += " on port " + this.port;
}
msg += ".";
Logger.info(msg);
return this.adaptor.disconnect();
return this.adaptor.disconnect(callback);
};
// Public: sets up adaptor with @robot
@ -101,14 +107,16 @@ Connection.prototype.disconnect = function() {
//
// Returns the set-up adaptor
Connection.prototype.initAdaptor = function(opts) {
Logger.debug("Loading adaptor '" + opts.adaptor + "'");
Logger.debug("Loading adaptor '" + opts.adaptor + "'.");
return this.robot.initAdaptor(opts.adaptor, this.self, opts);
};
// Public: Halt the adaptor's connection
//
// callback - function to be triggered when the connection has halted
//
// Returns nothing
Connection.prototype.halt = function() {
Connection.prototype.halt = function(callback) {
var msg = "Halting adaptor " + this.name;
if (this.port != null) {
@ -116,5 +124,5 @@ Connection.prototype.halt = function() {
}
Logger.info(msg);
return this.disconnect();
return this.disconnect(callback);
};

View File

@ -8,6 +8,8 @@
"use strict";
var Async = require('async');
var Logger = require('./logger'),
Robot = require('./robot'),
Utils = require('./utils');
@ -18,6 +20,7 @@ var Cylon = module.exports = {
Logger: Logger,
Driver: require('./driver'),
Adaptor: require('./adaptor'),
Utils: Utils,
IO: {
DigitalPin: require('./io/digital-pin'),
@ -26,15 +29,7 @@ var Cylon = module.exports = {
api_instance: null,
robots: [],
api_config: {
host: '127.0.0.1',
port: '3000',
auth: {},
CORS: null,
ssl: {}
}
robots: {}
};
// Public: Creates a new Robot
@ -50,143 +45,53 @@ var Cylon = module.exports = {
// work: (me) ->
// me.led.toggle()
Cylon.robot = function robot(opts) {
opts.master = this;
var robot = new Robot(opts);
this.robots.push(robot);
this.robots[robot.name] = robot;
return robot;
};
// Public: Configures the API host and port based on passed options
// Public: Creates a new API based on passed options
//
// opts - object containing API options
// host - host address API should serve from
// port - port API should listen for requests on
//
// Returns the API configuration
// Returns nothing
Cylon.api = function api(opts) {
if (opts == null) {
opts = {};
}
var keys = ['host', 'port', 'auth', 'CORS', 'ssl'];
var API = require('./api');
for (var i = 0; i < keys.length; i++) {
var key = keys[i];
if (typeof opts[key] !== "undefined") {
this.api_config[key] = opts[key];
}
}
return this.api_config;
};
// Public: Finds a particular robot by name
//
// name - name of the robot to find
// callback - optional callback to be executed
//
// Returns the found Robot or result of the callback if it's supplied
Cylon.findRobot = function findRobot(name, callback) {
var error,
robot = null;
for (var i = 0; i < this.robots.length; i++) {
var bot = this.robots[i];
if (bot.name === name) { robot = bot; }
}
if (robot == null) {
error = { error: "No Robot found with the name " + name };
}
return callback ? callback(error, robot) : robot;
};
// Public: Finds a particular Robot's device by name
//
// robotid - name of the robot to find
// deviceid - name of the device to find
// callback - optional callback to be executed
//
// Returns the found Device or result of the callback if it's supplied
Cylon.findRobotDevice = function findRobotDevice(robotid, deviceid, callback) {
return this.findRobot(robotid, function(err, robot) {
var error,
device = null;
if (err) { return callback ? callback(err, robot) : robot }
if (robot.devices[deviceid]) { device = robot.devices[deviceid]; }
if (device == null) {
error = { error: "No device found with the name " + deviceid + "." };
}
return callback ? callback(error, device) : device;
});
};
// Public: Finds a particular Robot's connection by name
//
// robotid - name of the robot to find
// connid - name of the device to find
// callback - optional callback to be executed
//
// Returns the found Connection or result of the callback if it's supplied
Cylon.findRobotConnection = function findRobotConnection(robotid, connid, callback) {
return this.findRobot(robotid, function(err, robot) {
var error,
connection = null;
if (err) { return callback ? callback(err, robot) : robot }
if (robot.connections[connid]) { connection = robot.connections[connid]; }
if (connection == null) {
error = { error: "No connection found with the name " + connid + "." };
}
return callback ? callback(error, connection) : connection;
});
this.api_instance = new API(opts);
this.api_instance.listen();
};
// Public: Starts up the API and the robots
//
// Returns nothing
Cylon.start = function start() {
this.startAPI();
for (var i = 0; i < this.robots.length; i++) {
var robot = this.robots[i];
robot.start();
for (var bot in this.robots) {
this.robots[bot].start();
}
};
// Public: Halts the API and the robots
//
// Returns nothing
Cylon.halt = function halt() {
for (var i = 0; i < this.robots.length; i++) {
var robot = this.robots[i];
robot.halt();
}
};
// Creates a new instance of the Cylon API server, or returns the
// already-existing one.
// callback - callback to be triggered when Cylon is ready to shutdown
//
// Returns an Cylon.ApiServer instance
Cylon.startAPI = function startAPI() {
var API = require('./api');
this.api_config.master = this;
// Returns nothing
Cylon.halt = function halt(callback) {
// set a timeout, in case trying to shut everything down nicely doesn't work
Utils.after((1.5).seconds(), callback);
if (this.api_instance === null) {
this.api_instance = new API(this.api_config);
this.api_instance.configureRoutes();
this.api_instance.listen();
var fns = [];
for (var bot in this.robots) {
var robot = this.robots[bot];
fns.push(robot.halt.bind(robot));
}
return this.api_instance;
Async.parallel(fns, callback);
};
if (process.platform === "win32") {
@ -199,6 +104,7 @@ if (process.platform === "win32") {
};
process.on("SIGINT", function() {
Cylon.halt();
process.kill(process.pid);
Cylon.halt(function() {
process.kill(process.pid);
});
});

View File

@ -8,16 +8,10 @@
'use strict';
require('./utils');
var EventEmitter = require('events').EventEmitter;
var Logger = require('./logger');
// The Artoo::Device class represents the interface to
// a specific individual hardware devices. Examples would be a digital
// thermometer connected to an Arduino, or a Sphero's accelerometer
var Device;
var Logger = require('./logger'),
Utils = require('./utils');
// Public: Creates a new Device
//
@ -29,25 +23,24 @@ var Device;
// driver - string name of the module the device driver logic lives in
//
// Returns a new Device
module.exports = Device = function Device(opts) {
var Device = module.exports = function Device(opts) {
if (opts == null) {
opts = {};
}
this.halt = bind(this.halt, this);
this.start = bind(this.start, this);
this.halt = Utils.bind(this.halt, this);
this.start = Utils.bind(this.start, this);
this.self = this;
this.robot = opts.robot;
this.name = opts.name;
this.pin = opts.pin;
this.connection = this.determineConnection(opts.connection) || this.defaultConnection();
this.driver = this.initDriver(opts);
proxyFunctionsToObject(this.driver.commands(), this.driver, this.self);
Utils.proxyFunctionsToObject(this.driver.commands, this.driver, this);
};
subclass(Device, EventEmitter);
Utils.subclass(Device, EventEmitter);
// Public: Starts the device driver
//
@ -55,44 +48,48 @@ subclass(Device, EventEmitter);
//
// Returns result of supplied callback
Device.prototype.start = function(callback) {
var msg = "Starting device " + this.name;
var msg = "Starting device '" + this.name + "'";
if (this.pin != null) {
msg += " on pin " + this.pin;
}
msg += ".";
Logger.info(msg);
return this.driver.start(callback);
};
// Public: Halt the device driver
//
// callback - function to trigger when the device has been halted
//
// Returns result of supplied callback
Device.prototype.halt = function() {
Logger.info("Halting device " + this.name);
return this.driver.halt();
Device.prototype.halt = function(callback) {
Logger.info("Halting device '" + this.name + "'.");
this.driver.halt(callback);
};
// Public: Exports basic data for the Connection
// Public: Expresses the Device in JSON format
//
// Returns an Object containing Connection data
Device.prototype.data = function() {
Device.prototype.toJSON = function() {
return {
name: this.name,
driver: this.driver.constructor.name || this.driver.name,
pin: this.pin,
connection: this.connection.data(),
commands: this.driver.commands()
connection: this.connection.toJSON(),
commands: this.driver.commands
};
};
// Public: Retrieves the connections from the parent Robot instances
//
// c - name of the connection to fetch
// conn - name of the connection to fetch
//
// Returns a Connection instance
Device.prototype.determineConnection = function(c) {
if (c) { return this.robot.connections[c]; }
Device.prototype.determineConnection = function(conn) {
return this.robot.connections[conn];
};
// Public: Returns a default Connection to use
@ -116,7 +113,10 @@ Device.prototype.defaultConnection = function() {
//
// Returns the set-up driver
Device.prototype.initDriver = function(opts) {
if (opts == null) { opts = {}; }
Logger.debug("Loading driver '" + opts.driver + "'");
return this.robot.initDriver(opts.driver, this.self, opts);
if (opts == null) {
opts = {};
}
Logger.debug("Loading driver '" + opts.driver + "'.");
return this.robot.initDriver(opts.driver, this, opts);
};

View File

@ -9,7 +9,8 @@
'use strict';
var Basestar = require('./basestar'),
Logger = require('./logger');
Logger = require('./logger'),
Utils = require('./utils');
// The Driver class is a base class for Driver classes in external Cylon
// modules to use. It offers basic functions for starting/halting that
@ -32,34 +33,30 @@ module.exports = Driver = function Driver(opts) {
this.name = opts.name;
this.device = opts.device;
this.connection = this.device.connection;
this.commandList = [];
};
subclass(Driver, Basestar);
Utils.subclass(Driver, Basestar);
// Public: Exposes all commands the driver will respond to/proxy
//
// Returns an array of string method names
Driver.prototype.commands = function() {
return this.commandList;
};
Driver.prototype.commands = [];
// Public: Starts up the driver, and emits 'connect' from the @device
// when done.
// Public: Starts up the driver, and triggers the provided callback when done.
//
// callback - function to run when the driver is started
//
// Returns nothing
Driver.prototype.start = function(callback) {
Logger.info("Driver " + this.name + " started");
Logger.info("Driver " + this.name + " started.");
callback(null);
this.device.emit('start');
return true;
};
// Public: Halts the driver
//
// callback - function to be triggered when the driver is halted
//
// Returns nothing
Driver.prototype.halt = function() {
return Logger.info("Driver " + this.name + " halted");
Driver.prototype.halt = function(callback) {
Logger.info("Driver " + this.name + " halted.");
this.removeAllListeners();
callback();
};

View File

@ -11,6 +11,8 @@
var FS = require('fs'),
EventEmitter = require('events').EventEmitter;
var Utils = require('../utils');
var GPIO_PATH = "/sys/class/gpio";
var GPIO_READ = "in";
@ -28,7 +30,7 @@ var DigitalPin = module.exports = function DigitalPin(opts) {
this.mode = opts.mode;
}
subclass(DigitalPin, EventEmitter);
Utils.subclass(DigitalPin, EventEmitter);
DigitalPin.prototype.connect = function(mode) {
var _this = this;
@ -81,7 +83,7 @@ DigitalPin.prototype.digitalRead = function(interval) {
if (this.mode !== 'r') { this._setMode('r'); }
every(interval, function() {
Utils.every(interval, function() {
FS.readFile(_this._valuePath(), function(err, data) {
if (err) {
var error = "Error occurred while reading from pin " + _this.pinNum;

View File

@ -1,50 +1,29 @@
'use strict';
// The BasicLogger pushes stuff to console.log. Nothing more, nothing less.
var BasicLogger = module.exports = function BasicLogger() {}
var BasicLogger = module.exports = function BasicLogger() {};
BasicLogger.prototype.toString = function() {
return "BasicLogger";
};
BasicLogger.prototype.debug = function() {
var args = getArgs(arguments),
string = ["D, [" + (new Date().toISOString()) + "] DEBUG -- :"],
data = string.concat(args.slice());
return console.log.apply(console, data);
};
BasicLogger.prototype.info = function() {
var args = getArgs(arguments),
string = ["I, [" + (new Date().toISOString()) + "] INFO -- :"],
data = string.concat(args.slice());
return console.log.apply(console, data);
};
BasicLogger.prototype.warn = function() {
var args = getArgs(arguments),
string = ["W, [" + (new Date().toISOString()) + "] WARN -- :"],
data = string.concat(args.slice());
return console.log.apply(console, data);
};
BasicLogger.prototype.error = function() {
var args = getArgs(arguments),
string = ["E, [" + (new Date().toISOString()) + "] ERROR -- :"],
data = string.concat(args.slice());
return console.log.apply(console, data);
};
BasicLogger.prototype.fatal = function() {
var args = getArgs(arguments),
string = ["F, [" + (new Date().toISOString()) + "] FATAL -- :"],
data = string.concat(args.slice());
return console.log.apply(console, data);
};
var getArgs = function(args) {
return args.length >= 1 ? [].slice.call(args, 0) : [];
};
var logString = function(type) {
var upcase = String(type).toUpperCase(),
time = new Date().toISOString();
var padded = String(" " + upcase).slice(-5);
return upcase[0] + ", [" + time + "] " + padded + " -- :";
};
['debug', 'info', 'warn', 'error', 'fatal'].forEach(function(type) {
BasicLogger.prototype[type] = function() {
var args = getArgs(arguments);
return console.log.apply(console, [].concat(logString(type), args));
};
});

View File

@ -8,25 +8,28 @@
'use strict';
require('./utils');
var Connection = require("./connection"),
Device = require("./device"),
Logger = require('./logger'),
Utils = require('./utils'),
config = require('./config');
var Async = require("async"),
EventEmitter = require('events').EventEmitter;
// A Robot is the primary interface for interacting with a collection of physical
// computing capabilities.
var Robot;
var missingModuleError = function(module) {
var string = "Cannot find the '" + module + "' module. ";
string += "Please install it with 'npm install " + module + "' and try again.";
console.log(string);
process.emit('SIGINT');
};
// Public: Creates a new Robot
//
// opts - object containing Robot options
// name - optional, string name of the robot
// master - Cylon.Master class that orchestrates robots
// connection/connections - object connections to connect to
// device/devices - object devices to connect to
// work - work to be performed when the Robot is started
@ -43,13 +46,15 @@ var Robot;
// name: 'sphero', driver: 'sphero'
//
// work: (me) ->
// every 1.second(), ->
// Utils.every 1.second(), ->
// me.sphero.roll 60, Math.floor(Math.random() * 360//
module.exports = Robot = function Robot(opts) {
var Robot = module.exports = function Robot(opts) {
if (opts == null) {
opts = {};
}
var self = this;
var methods = [
"toString",
"registerDriver",
@ -64,42 +69,50 @@ module.exports = Robot = function Robot(opts) {
"initConnections"
];
for (var i = 0; i < methods.length ; i++) {
var method = methods[i];
this[method] = bind(this[method], this);
}
methods.forEach(function(method) {
self[method] = Utils.bind(self[method], self);
});
this.robot = this;
this.name = opts.name || this.constructor.randomName();
this.master = opts.master;
this.connections = {};
this.devices = {};
this.adaptors = {};
this.drivers = {};
this.commands = [];
this.running = false;
this.work = opts.work || opts.play;
this.registerAdaptor("./test/loopback", "loopback");
this.registerAdaptor("./test/test-adaptor", "test");
this.registerDriver("./test/ping", "ping");
this.registerDriver("./test/test-driver", "test");
if (!this.work) {
this.work = function() { console.log("No work yet."); }
}
this.registerDefaults();
this.initConnections(opts.connection || opts.connections);
this.initDevices(opts.device || opts.devices);
this.work = opts.work || function() { Logger.info("No work yet"); };
for (var n in opts) {
var func = opts[n],
reserved = ['connection', 'connections', 'device', 'devices', 'work'];
if (reserved.indexOf(n) < 0) { this.robot[n] = func; }
if (reserved.indexOf(n) < 0) {
this[n] = func;
}
}
};
subclass(Robot, EventEmitter);
Utils.subclass(Robot, EventEmitter);
// Public: Registers the default Drivers and Adaptors with Cylon.
//
// Returns nothing.
Robot.prototype.registerDefaults = function registerDefaults() {
this.registerAdaptor("./test/loopback", "loopback");
this.registerAdaptor("./test/test-adaptor", "test");
this.registerDriver("./test/ping", "ping");
this.registerDriver("./test/test-driver", "test");
};
// Public: Generates a random name for a Robot.
//
@ -108,15 +121,15 @@ Robot.randomName = function() {
return "Robot " + (Math.floor(Math.random() * 100000));
};
// Public: Exports basic data for the Robot
// Public: Expresses the Robot in a JSON-serializable format
//
// Returns an Object containing Robot data
Robot.prototype.data = function() {
Robot.prototype.toJSON = function() {
var connections = (function() {
var results = [];
for (var n in this.connections) {
var conn = this.connections[n];
results.push(conn.data());
results.push(conn.toJSON());
}
return results;
}).call(this);
@ -125,7 +138,7 @@ Robot.prototype.data = function() {
var results = [];
for (var n in this.devices) {
var device = this.devices[n];
results.push(device.data());
results.push(device.toJSON());
}
return results;
}).call(this);
@ -144,14 +157,17 @@ Robot.prototype.data = function() {
//
// Returns initialized connections
Robot.prototype.initConnections = function(connections) {
Logger.info("Initializing connections...");
if (connections == null) { return; }
Logger.info("Initializing connections.");
if (connections == null) {
return;
}
connections = [].concat(connections);
for (var i = 0; i < connections.length; i++) {
var connection = connections[i];
Logger.info("Initializing connection '" + connection.name + "'...");
Logger.info("Initializing connection '" + connection.name + "'.");
connection['robot'] = this;
this.connections[connection.name] = new Connection(connection);
}
@ -165,14 +181,17 @@ Robot.prototype.initConnections = function(connections) {
//
// Returns initialized devices
Robot.prototype.initDevices = function(devices) {
Logger.info("Initializing devices...");
if (devices == null) { return; }
Logger.info("Initializing devices.");
if (devices == null) {
return;
}
devices = [].concat(devices);
for (var i = 0; i < devices.length; i++) {
var device = devices[i];
Logger.info("Initializing device '" + device.name + "'...");
Logger.info("Initializing device '" + device.name + "'.");
device['robot'] = this;
this.devices[device.name] = this._createDevice(device);
}
@ -191,17 +210,26 @@ Robot.prototype._createDevice = function(device) {
// Returns the result of the work
Robot.prototype.start = function() {
var self = this;
return this.startConnections(function() {
return self.robot.startDevices(function(err) {
if (err) {
throw err;
}else{
self.robot.work.call(self.robot, self.robot);
self.running = true;
Logger.info("Working...");
self.robot.emit('working');
}
});
var begin = function(callback) {
self.work.call(self, self);
self.running = true;
self.emit('working');
Logger.info('Working.');
callback(null, true);
};
Async.series([
self.startConnections,
self.startDevices,
begin
], function(err) {
if (!!err) {
Logger.fatal("An error occured while trying to start the robot:");
Logger.fatal(err);
}
});
};
@ -213,11 +241,11 @@ Robot.prototype.start = function() {
Robot.prototype.startConnections = function(callback) {
var starters = {};
Logger.info("Starting connections...");
Logger.info("Starting connections.");
for (var n in this.connections) {
var connection = this.connections[n];
this.robot[n] = connection;
this[n] = connection;
starters[n] = connection.connect;
}
@ -232,11 +260,11 @@ Robot.prototype.startConnections = function(callback) {
Robot.prototype.startDevices = function(callback) {
var starters = {};
Logger.info("Starting devices...");
Logger.info("Starting devices.");
for (var n in this.devices) {
var device = this.devices[n];
this.robot[n] = device;
this[n] = device;
starters[n] = device.start;
}
@ -247,10 +275,23 @@ Robot.prototype.startDevices = function(callback) {
//
// Halts the devices, disconnects the connections.
//
// callback - callback to be triggered when the Robot is stopped
//
// Returns nothing
Robot.prototype.halt = function() {
for (var d in this.devices) { this.devices[d].halt(); }
for (var c in this.connections) { this.connections[c].halt(); }
Robot.prototype.halt = function(callback) {
var fns = [];
for (var d in this.devices) {
var device = this.devices[d];
fns.push(device.halt.bind(device));
}
for (var c in this.connections) {
var connection = this.connections[c];
fns.push(connection.halt.bind(connection));
}
Async.parallel(fns, callback);
};
// Public: Initialize an adaptor and adds it to @robot.adaptors
@ -260,21 +301,23 @@ Robot.prototype.halt = function() {
//
// Returns the adaptor
Robot.prototype.initAdaptor = function(adaptorName, connection, opts) {
if (opts == null) { opts = {}; }
if (opts == null) {
opts = {};
}
var adaptor = this.robot.requireAdaptor(adaptorName, opts).adaptor({
var adaptor = this.requireAdaptor(adaptorName, opts).adaptor({
name: adaptorName,
connection: connection,
extraParams: opts
});
if (config.testing_mode) {
var testAdaptor = this.robot.requireAdaptor('test').adaptor({
var testAdaptor = this.requireAdaptor('test').adaptor({
name: adaptorName,
connection: connection,
extraParams: opts
});
return proxyTestStubs(adaptor.commands(), testAdaptor);
return Utils.proxyTestStubs(adaptor.commands, testAdaptor);
} else {
return adaptor;
}
@ -286,12 +329,12 @@ Robot.prototype.initAdaptor = function(adaptorName, connection, opts) {
//
// Returns the module for the adaptor
Robot.prototype.requireAdaptor = function(adaptorName, opts) {
if (this.robot.adaptors[adaptorName] == null) {
if (this.adaptors[adaptorName] == null) {
var moduleName = opts.module || adaptorName;
this.robot.registerAdaptor("cylon-" + moduleName, adaptorName);
this.robot.adaptors[adaptorName].register(this);
this.registerAdaptor("cylon-" + moduleName, adaptorName);
this.adaptors[adaptorName].register(this);
}
return this.robot.adaptors[adaptorName];
return this.adaptors[adaptorName];
};
// Public: Registers an Adaptor with the Robot
@ -322,22 +365,24 @@ Robot.prototype.registerAdaptor = function(moduleName, adaptorName) {
//
// Returns the new driver
Robot.prototype.initDriver = function(driverName, device, opts) {
if (opts == null) { opts = {}; }
if (opts == null) {
opts = {};
}
var driver = this.robot.requireDriver(driverName).driver({
var driver = this.requireDriver(driverName).driver({
name: driverName,
device: device,
extraParams: opts
});
if (config.testing_mode) {
var testDriver = this.robot.requireDriver('test').driver({
var testDriver = this.requireDriver('test').driver({
name: driverName,
device: device,
extraParams: opts
});
return proxyTestStubs(driver.commands(), testDriver);
return Utils.proxyTestStubs(driver.commands, testDriver);
} else {
return driver;
}
@ -349,11 +394,11 @@ Robot.prototype.initDriver = function(driverName, device, opts) {
//
// Returns the module for driver
Robot.prototype.requireDriver = function(driverName) {
if (this.robot.drivers[driverName] == null) {
this.robot.registerDriver("cylon-" + driverName, driverName);
this.robot.drivers[driverName].register(this);
if (this.drivers[driverName] == null) {
this.registerDriver("cylon-" + driverName, driverName);
this.drivers[driverName].register(this);
}
return this.robot.drivers[driverName];
return this.drivers[driverName];
};
// Public: Registers an Driver with the Robot
@ -382,12 +427,3 @@ Robot.prototype.registerDriver = function(moduleName, driverName) {
Robot.prototype.toString = function() {
return "[Robot name='" + this.name + "']";
};
var missingModuleError = function(module) {
var string = "Cannot find the '" + module + "' module. ";
string += "Please install it with 'npm install " + module + "' and try again.";
console.log(string);
process.emit('SIGINT');
};

View File

@ -8,18 +8,16 @@
"use strict";
var Adaptor = require('../adaptor');
var Adaptor = require('../adaptor'),
Utils = require('../utils');
var Loopback;
module.exports = Loopback = function Loopback() {
Loopback.__super__.constructor.apply(this, arguments);
this.commands = ['ping'];
};
subclass(Loopback, Adaptor);
Loopback.prototype.commands = function() {
return ['ping'];
};
Utils.subclass(Loopback, Adaptor);
Loopback.adaptor = function(opts) { return new Loopback(opts); };

View File

@ -8,19 +8,17 @@
'use strict';
var Driver = require('../driver');
var Driver = require('../driver'),
Utils = require('../utils');
var Ping;
module.exports = Ping = function Ping() {
Ping.__super__.constructor.apply(this, arguments);
this.commands = ['ping'];
};
subclass(Ping, Driver);
Ping.prototype.commands = function() {
return ['ping'];
};
Utils.subclass(Ping, Driver);
Ping.prototype.ping = function() {
return "pong";

View File

@ -8,7 +8,8 @@
"use strict";
var Adaptor = require('../adaptor')
var Adaptor = require('../adaptor'),
Utils = require('../utils');
var TestAdaptor;
@ -16,6 +17,6 @@ module.exports = TestAdaptor = function TestAdaptor() {
TestAdaptor.__super__.constructor.apply(this, arguments);
};
subclass(TestAdaptor, Adaptor);
Utils.subclass(TestAdaptor, Adaptor);
TestAdaptor.adaptor = function(opts) { return new TestAdaptor(opts); };

View File

@ -8,7 +8,8 @@
'use strict';
var Driver = require('../driver');
var Driver = require('../driver'),
Utils = require('../utils');
var TestDriver;
@ -16,6 +17,6 @@ module.exports = TestDriver = function TestDriver() {
TestDriver.__super__.constructor.apply(this, arguments);
};
subclass(TestDriver, Driver);
Utils.subclass(TestDriver, Driver);
TestDriver.driver = function(opts) { return new TestDriver(opts); };

View File

@ -1,82 +1,12 @@
/*
* utils
* Cylon - Utils
* cylonjs.com
*
* Copyright (c) 2013 The Hybrid Group
* Licensed under the Apache 2.0 license.
*/
// Public: Monkey-patches Number to have Rails-like //seconds() function. Warning,
// due to the way the Javascript parser works, applying functions on numbers is
// kind of weird. See examples for details.
//
// Examples
//
// 2.seconds()
// //=> SyntaxError: Unexpected token ILLEGAL
//
// 10..seconds()
// //=> 10000
//
// (5).seconds()
// //=> 5000
//
// Returns an integer representing time in milliseconds
Number.prototype.seconds = function() {
return this * 1000;
};
// Public: Alias for Number::seconds, see comments for that method
//
// Examples
//
// 1.second()
// //=> 1000
//
// Returns an integer representing time in milliseconds
Number.prototype.second = function() {
return this.seconds(this);
};
// Public: Convert value from old scale (start, end) to (0..1) scale
//
// start - low point of scale to convert value from
// end - high point of scale to convert value from
//
// Examples
//
// 5..fromScale(0, 10)
// //=> 0.5
//
// Returns an integer representing the scaled value
Number.prototype.fromScale = function(start, end) {
return (this - Math.min(start, end)) / (Math.max(start, end) - Math.min(start, end));
};
// Public: Convert value from (0..1) scale to new (start, end) scale
//
// start - low point of scale to convert value to
// end - high point of scale to convert value to
//
// Examples
//
// 0.5.toScale(0, 10)
// //=> 5
//
// Returns an integer representing the scaled value
Number.prototype.toScale = function(start, end) {
var i = this * (Math.max(start, end) - Math.min(start, end)) + Math.min(start, end);
if (i < Math.min(start, end)) {
return Math.min(start, end);
} else if (i > Math.max(start,end)){
return Math.max(start, end);
} else {
return i;
}
};
global.Utils = {
var Utils = module.exports = {
// Public: Alias to setInterval, combined with Number monkeypatches below to
// create an artoo-like syntax.
//
@ -85,7 +15,9 @@ global.Utils = {
//
// Examples
//
// every 5.seconds(), -> console.log("hello world (and again in 5 seconds)!")
// every((5).seconds(), function() {
// console.log('Hello world (and again in 5 seconds)!');
// });
//
// Returns an interval
every: function every(interval, action) {
@ -100,7 +32,9 @@ global.Utils = {
//
// Examples
//
// after 10.seconds(), -> console.log("hello world from ten seconds ago!")
// after((10).seconds(), function() {
// console.log('Hello world from ten seconds ago!');
// });
//
// Returns an interval
after: function after(delay, action) {
@ -110,7 +44,9 @@ global.Utils = {
// Public: Alias to the `every` function, but passing 0
// Examples
//
// constantly -> console.log("hello world (and again and again)!")
// constantly(function() {
// console.log('hello world (and again and again)!');
// });
//
// Returns an interval
constantly: function constantly(action) {
@ -121,9 +57,11 @@ global.Utils = {
//
// ms - number of ms to sleep for
//
// Examples
//
// sleep((1).second());
//
// Returns a function
// Examples:
// sleep 1.second()
sleep: function sleep(ms) {
var start = Date.now();
@ -132,27 +70,28 @@ global.Utils = {
}
},
// Copies
slice: [].slice,
hasProp: {}.hasOwnProperty,
// Public: Function to use for class inheritance. Copy of a CoffeeScript helper
// function.
//
// Example
//
// var Sphero = (function(klass) {
// subclass(Sphero, klass);
// // Sphero is now a subclass of Parent, and can access it's methods through
// // Sphero.__super__
// })(Parent);
// var Sphero = function Sphero() {};
//
// subclass(Sphero, ParentClass);
//
// // Sphero is now a subclass of Parent, and can access parent methods
// // through Sphero.__super__
//
// Returns subclass
subclass: function subclass(child, parent) {
var ctor = function() { this.constructor = child; };
var ctor = function() {
this.constructor = child;
};
for (var key in parent) {
if (hasProp.call(parent, key)) { child[key] = parent[key]; }
if (Object.hasOwnProperty.call(parent, key)) {
child[key] = parent[key];
}
}
ctor.prototype = parent.prototype;
@ -172,8 +111,13 @@ global.Utils = {
//
// Returns base
proxyFunctionsToObject: function proxyFunctionsToObject(methods, target, base, force) {
if (base == null) { base = this; }
if (force == null) { force = false; }
if (base == null) {
base = this;
}
if (force == null) {
force = false;
}
var fn = function(method) {
return base[method] = function() {
@ -184,8 +128,9 @@ global.Utils = {
for (var i = 0; i < methods.length; i++) {
var method = methods[i];
if (!force) {
if (typeof base[method] === 'function') { continue; }
if (!force && typeof(base[method]) === 'function') {
continue;
}
fn(method);
@ -201,11 +146,16 @@ global.Utils = {
//
// Returns base
proxyTestStubs: function proxyTestStubs(methods, base) {
if (base == null) { base = this; }
if (base == null) {
base = this;
}
methods.forEach(function(method) {
base[method] = function() { return true; };
base.commandList.push(method);
base[method] = function() {
return true;
};
base.commands.push(method);
});
return base;
@ -220,34 +170,124 @@ global.Utils = {
//
// var me = { hello: "Hello World" };
// var proxy = { boundMethod: function() { return this.hello; } };
//
// proxy.boundMethod = bind(proxy.boundMethod, me);
// proxy.boundMethod();
//
// //=> "Hello World"
//
// Returns a function wrapper
bind: function bind(fn, me) {
return function() { return fn.apply(me, arguments); };
return function() {
return fn.apply(me, arguments);
};
},
// Public: Adds all methods from Cylon.Utils directly to the global
// namespace.
// Public: Adds necessary utils to global namespace, along with base class
// extensions.
//
// Examples
//
// Cylon.Utils.bootstrap();
// (after === Cylon.Utils.After) // true
// Number.prototype.seconds // undefined
// after // undefined
//
// Utils.bootstrap();
//
// Number.prototype.seconds // [function]
// (after === Utils.after) // true
//
// Returns Cylon.Utils
bootstrap: function bootstrap() {
for (util in this) {
// we're not going to attach the 'bootstrap' method
if (!(util === "bootstrap")) {
global[util] = this[util];
}
}
global.every = this.every;
global.after = this.after;
global.constantly = this.constantly;
addCoreExtensions();
return this;
}
};
module.exports = Utils.bootstrap();
var addCoreExtensions = function addCoreExtensions() {
// Public: Monkey-patches Number to have Rails-like //seconds() function.
// Warning, due to the way the Javascript parser works, applying functions on
// numbers is kind of weird. See examples for details.
//
// Examples
//
// 2.seconds()
// //=> SyntaxError: Unexpected token ILLEGAL
//
// 10..seconds()
// //=> 10000
//
// (5).seconds()
// //=> 5000
// // This is the preferred way to represent numbers when calling these
// // methods on them
//
// Returns an integer representing time in milliseconds
Number.prototype.seconds = function() {
return this * 1000;
};
// Public: Alias for Number::seconds, see comments for that method
//
// Examples
//
// 1.second()
// //=> 1000
//
// Returns an integer representing time in milliseconds
Number.prototype.second = function() {
return this.seconds(this);
};
// Public: Convert value from old scale (start, end) to (0..1) scale
//
// start - low point of scale to convert value from
// end - high point of scale to convert value from
//
// Examples
//
// (5).fromScale(0, 10)
// //=> 0.5
//
// Returns an integer representing the scaled value
Number.prototype.fromScale = function(start, end) {
var val = (this - Math.min(start, end)) / (Math.max(start, end) - Math.min(start, end));
if (val > 1) {
val = 1;
} else if (val < 0){
val = 0;
}
return val;
};
// Public: Convert value from (0..1) scale to new (start, end) scale
//
// start - low point of scale to convert value to
// end - high point of scale to convert value to
//
// Examples
//
// (0.5).toScale(0, 10)
// //=> 5
//
// Returns an integer representing the scaled value
Number.prototype.toScale = function(start, end) {
var i = this * (Math.max(start, end) - Math.min(start, end)) + Math.min(start, end);
if (i < Math.min(start, end)) {
return Math.min(start, end);
} else if (i > Math.max(start,end)){
return Math.max(start, end);
} else {
return i;
}
};
};
Utils.bootstrap();

View File

@ -1,6 +1,6 @@
{
"name": "cylon",
"version": "0.14.0",
"version": "0.15.1",
"main": "lib/cylon.js",
"description": "A JavaScript robotics framework using Node.js",
"homepage": "http://cylonjs.com",
@ -39,14 +39,13 @@
"sinon-chai": "2.5.0",
"chai": "1.9.1",
"mocha": "1.18.2",
"sinon": "1.9.1",
"jshint": "2.5.0",
"istanbul": "0.2.10"
"sinon": "1.9.1"
},
"dependencies": {
"async": "0.7.0",
"express": "3.5.1",
"robeaux": ">= 0.1.0"
"async": "0.7.0",
"express": "4.4.1",
"body-parser": "1.3.0",
"robeaux": ">= 0.1.0"
}
}

View File

@ -3,7 +3,8 @@
var EventEmitter = require('events').EventEmitter;
var Adaptor = source("adaptor"),
Logger = source('logger');
Logger = source('logger'),
Utils = source('utils');
describe("Adaptor", function() {
var connection = new EventEmitter;
@ -22,23 +23,8 @@ describe("Adaptor", function() {
expect(adaptor.connection).to.be.eql(connection);
});
it("sets @commandList to an empty array by default", function() {
expect(adaptor.commandList).to.be.eql([]);
});
});
describe("#commands", function() {
var commands = ['list', 'of', 'commands']
before(function() {
adaptor.commandList = commands;
});
after(function() {
adaptor.commandList = [];
});
it("returns the adaptor's @commandList", function() {
expect(adaptor.commands()).to.be.eql(commands);
it("sets @commands to an empty array by default", function() {
expect(adaptor.commands).to.be.eql([]);
});
});
@ -46,34 +32,31 @@ describe("Adaptor", function() {
var callback = spy();
before(function() {
stub(connection, 'emit');
stub(Logger, 'info');
adaptor.connect(callback);
});
after(function() {
connection.emit.restore();
Logger.info.restore();
});
it("logs that it's connecting to the adaptor", function() {
var string = "Connecting to adaptor 'adaptor'...";
var string = "Connecting to adaptor 'adaptor'.";
expect(Logger.info).to.be.calledWith(string);
});
it("triggers the provided callback", function() {
expect(callback).to.be.called;
});
it("tells the connection to emit the 'connect' event", function() {
expect(connection.emit).to.be.calledWith('connect');
});
});
describe("#disconnect", function() {
var callback;
before(function() {
stub(Logger, 'info');
adaptor.disconnect();
callback = spy();
adaptor.disconnect(callback);
});
after(function() {
@ -81,8 +64,12 @@ describe("Adaptor", function() {
});
it("logs that it's disconnecting to the adaptor", function() {
var string = "Disconnecting from adaptor 'adaptor'...";
var string = "Disconnecting from adaptor 'adaptor'.";
expect(Logger.info).to.be.calledWith(string);
});
it("triggers the callback", function() {
expect(callback).to.be.called;
})
});
});

View File

@ -4,7 +4,8 @@ var express = require('express'),
https = require('https'),
fs = require('fs');
var API = source('api');
var API = source('api'),
Utils = source('utils');
describe("API", function() {
var api, opts;
@ -14,8 +15,6 @@ describe("API", function() {
beforeEach(function() {
stub(https, 'createServer').returns({ listen: spy() });
opts = { master: { name: 'master' }, ssl: {} }
api = new API(opts);
});
@ -35,17 +34,13 @@ describe("API", function() {
expect(api.port).to.be.eql("3000")
});
it("sets @master to the passed master", function() {
expect(api.master).to.be.eql(opts.master)
});
it("sets @server to an Express server instance", function() {
expect(api.server).to.be.a('function');
it("sets @express to an Express server instance", function() {
expect(api.express).to.be.a('function');
var methods = ['get', 'post', 'put', 'delete'];
for (var i = 0; i < methods.length; i++) {
expect(api.server[methods[i]]).to.be.a('function');
expect(api.express[methods[i]]).to.be.a('function');
}
});
@ -54,7 +49,7 @@ describe("API", function() {
});
it("sets the server's title", function() {
var title = api.server.get('title');
var title = api.express.get('title');
expect(title).to.be.eql("Cylon API Server");
});
@ -80,72 +75,4 @@ describe("API", function() {
});
});
describe("#configureRoutes", function() {
var server;
var methods = ['all', 'get', 'post'];
beforeEach(function() {
server = api.server;
for (var i = 0; i < methods.length; i++) {
stub(server, methods[i]);
}
api.configureRoutes();
});
afterEach(function() {
for (var i = 0; i < methods.length; i++) {
server[methods[i]].restore();
}
});
it("ALL /*", function() {
expect(server.all).to.be.calledWith("/*");
});
it("GET /robots", function() {
expect(server.get).to.be.calledWith("/robots");
});
it("GET /robots/:robot", function() {
expect(server.get).to.be.calledWith("/robots/:robot");
});
it("GET /robots/:robot/commands", function() {
expect(server.get).to.be.calledWith("/robots/:robot/commands");
});
it("ALL /robots/:robot/commands/:command", function() {
expect(server.all).to.be.calledWith("/robots/:robot/commands/:command");
});
it("GET /robots/:robot/devices", function() {
expect(server.get).to.be.calledWith("/robots/:robot/devices");
});
it("GET /robots/:robot/devices/:device", function() {
expect(server.get).to.be.calledWith("/robots/:robot/devices/:device");
});
it("GET /robots/:robot/devices/:device/events/:event", function() {
expect(server.get).to.be.calledWith("/robots/:robot/devices/:device/events/:event");
});
it("GET /robots/:robot/devices/:device/commands", function() {
expect(server.get).to.be.calledWith("/robots/:robot/devices/:device/commands");
});
it("ALL /robots/:robot/devices/:device/commands/:command", function() {
expect(server.all).to.be.calledWith("/robots/:robot/devices/:device/commands/:command");
});
it("GET /robots/:robot/connections", function() {
expect(server.get).to.be.calledWith("/robots/:robot/connections");
});
it("GET /robots/:robot/connections/:connection", function() {
expect(server.get).to.be.calledWith("/robots/:robot/connections/:connection");
});
});
});

View File

@ -0,0 +1,129 @@
'use strict'
var basic = source('api/auth/basic');
var MockRequest = require('../../../support/mock_request'),
MockResponse = require('../../../support/mock_response');
describe("Basic Auth", function() {
var opts = { user: 'user', pass: 'pass' },
req,
res,
next;
basic = basic(opts);
beforeEach(function() {
req = new MockRequest();
res = new MockResponse();
next = spy();
var auth = new Buffer("user:pass", "utf8").toString('base64');
req.headers = { authorization: "Basic " + auth };
});
var checkUnauthorized = function() {
var result;
beforeEach(function() {
result = basic(req, res, next);
});
it("returns false", function() {
expect(result).to.be.falsy;
});
it("sends a 401 error", function() {
expect(res.statusCode).to.be.eql(401);
expect(res.end).to.be.calledWith("Unauthorized");
});
};
var checkError = function() {
var result;
beforeEach(function() {
result = basic(req, res, next);
});
it("triggers next with an error", function() {
expect(next).to.be.called;
});
};
context("with a valid request", function() {
var result;
beforeEach(function() {
result = basic(req, res, next);
});
it("returns true", function() {
expect(result).to.be.truthy;
});
it("doesn't modify the response", function() {
expect(res.end).to.not.be.called;
})
});
context("if the user/pass don't match", function() {
beforeEach(function() {
var auth = new Buffer("bad:wrong", "utf8").toString('base64');
req.headers = { authorization: "Basic " + auth };
});
checkUnauthorized();
});
context("if there is already an authorized user", function() {
var result;
beforeEach(function() {
req.user = 'user';
result = basic(req, res, next);
});
it("returns true", function() {
expect(result).to.be.truthy;
});
it("doesn't modify the response", function() {
expect(res.end).to.not.be.called;
})
});
context("if there are no authorization headers", function() {
beforeEach(function() {
delete req.headers.authorization;
});
checkUnauthorized();
});
context("the authorization type isn't Basic", function() {
beforeEach(function() {
var auth = new Buffer("user:pass", "utf8").toString('base64');
req.headers = { authorization: "Digest " + auth };
});
checkError();
});
context("the authorization header is missing content", function() {
beforeEach(function() {
req.headers = { authorization: "Basic" };
});
checkError();
});
context("if the authorization header isn't formatted correctly", function() {
beforeEach(function() {
var auth = new Buffer("user-pass", "utf8").toString('base64');
req.headers = { authorization: "Basic " + auth };
});
checkUnauthorized();
});
});

View File

@ -1,17 +1,11 @@
"use strict";
var Basestar = source('basestar');
var Basestar = source('basestar'),
Utils = source('utils');
var EventEmitter = require('events').EventEmitter;
describe('Basestar', function() {
describe('constructor', function() {
it('assigns @self to the instance of the Basestar class', function() {
var instance = new Basestar();
expect(instance.self).to.be.eql(instance);
});
});
describe('#proxyMethods', function() {
var methods = ['asString', 'toString', 'returnString'];
@ -34,7 +28,7 @@ describe('Basestar', function() {
this.proxyMethods(methods, this.testInstance, this, true);
}
subclass(TestClass, Basestar);
Utils.subclass(TestClass, Basestar);
it('can alias methods', function() {
var testclass = new TestClass;
@ -70,8 +64,8 @@ describe('Basestar', function() {
});
}
subclass(ProxyClass, Basestar);
subclass(EmitterClass, Basestar);
Utils.subclass(ProxyClass, Basestar);
Utils.subclass(EmitterClass, Basestar);
it("proxies events from one class to another", function() {
var eventSpy = spy(),

View File

@ -1,9 +1,10 @@
"use strict";
var Robot = source("robot"),
Logger = source('logger');
Logger = source('logger'),
Utils = source('utils');
describe("Cylon.Connection", function() {
describe("Connection", function() {
var robot = new Robot({
name: "Robby",
connection: { name: 'loopback', adaptor: 'loopback', port: "/dev/null" }
@ -29,28 +30,28 @@ describe("Cylon.Connection", function() {
});
});
describe("#data", function() {
var data = connection.data();
describe("#toJSON", function() {
var json = connection.toJSON();
it("returns an object", function() {
expect(data).to.be.an('object');
expect(json).to.be.an('object');
});
it("contains the connection's name", function() {
expect(data.name).to.be.eql("loopback");
expect(json.name).to.be.eql("loopback");
});
it("contains the connection's port", function() {
expect(data.port).to.be.eql("/dev/null");
expect(json.port).to.be.eql("/dev/null");
});
it("contains the connection's adaptor name", function() {
expect(data.adaptor).to.be.eql("Loopback");
expect(json.adaptor).to.be.eql("Loopback");
});
it("contains the connection's ID", function() {
var id = connection.connection_id;
expect(data.connection_id).to.be.eql(id);
expect(json.connection_id).to.be.eql(id);
});
});
@ -70,7 +71,7 @@ describe("Cylon.Connection", function() {
});
it("logs that it's connecting the device", function() {
var message = "Connecting to loopback on port /dev/null";
var message = "Connecting to 'loopback' on port /dev/null.";
expect(Logger.info).to.be.calledWith(message);
});
@ -93,7 +94,7 @@ describe("Cylon.Connection", function() {
});
it("logs that it's disconnecting from the device", function() {
var message = "Disconnecting from loopback on port /dev/null";
var message = "Disconnecting from 'loopback' on port /dev/null.";
expect(Logger.info).to.be.calledWith(message);
});

View File

@ -1,8 +1,10 @@
"use strict";
var Cylon = source("cylon");
var Cylon = source("cylon"),
Utils = source('utils');
var Logger = source('logger'),
var API = source('api'),
Logger = source('logger'),
Adaptor = source('adaptor'),
Driver = source('driver');
@ -25,21 +27,13 @@ describe("Cylon", function() {
expect(Cylon.api_instance).to.be.eql(null);
});
it("sets @api_config to an object containing host/port info", function() {
var config = Cylon.api_config;
expect(config).to.be.an('object');
expect(config.host).to.be.eql('127.0.0.1');
expect(config.port).to.be.eql('3000');
});
it("sets @robots to an empty array by default", function() {
expect(Cylon.robots).to.be.eql([]);
it("sets @robots to an empty object by default", function() {
expect(Cylon.robots).to.be.eql({});
});
describe("#robot", function() {
after(function() {
Cylon.robots = [];
Cylon.robots = {};
});
it("uses passed options to create a new Robot", function() {
@ -47,284 +41,39 @@ describe("Cylon", function() {
var robot = Cylon.robot(opts);
expect(robot.toString()).to.be.eql("[Robot name='Ultron']")
expect(Cylon.robots.pop()).to.be.eql(robot);
expect(Cylon.robots['Ultron']).to.be.eql(robot);
});
});
describe("#api", function() {
var expectedConfig;
beforeEach(function() {
expectedConfig = {
host: '127.0.0.1',
port: '3000',
auth: {},
CORS: null,
ssl: {}
};
stub(API.prototype, 'listen');
});
// this is the shortest, cheapest way to dup an object in JS.
// I don't like it either.
Cylon.api_config = JSON.parse(JSON.stringify(expectedConfig));
afterEach(function() {
API.prototype.listen.restore();
});
it('creates a new API instance', function() {
Cylon.api();
expect(Cylon.api_instance).to.be.an.instanceOf(API);
});
it('passes arguments to the API constructor', function() {
Cylon.api({ port: '1234' });
expect(Cylon.api_instance.port).to.be.eql('1234');
})
context("without arguments", function() {
it("returns the current API configuration", function() {
Cylon.api();
expect(Cylon.api_config).to.be.eql(expectedConfig);
});
});
context("only specifying port", function() {
it("changes the port, but not the host", function() {
expectedConfig.port = "4000";
Cylon.api({ port: "4000" });
expect(Cylon.api_config).to.be.eql(expectedConfig);
});
});
context("only specifying host", function() {
it("changes the host, but not the port", function() {
expectedConfig.host = "0.0.0.0";
Cylon.api({ host: "0.0.0.0" });
expect(Cylon.api_config).to.be.eql(expectedConfig);
});
});
context("specifying new host and port", function() {
it("changes both the host and port", function() {
expectedConfig.host = "0.0.0.0";
expectedConfig.port = "4000";
Cylon.api({ host: "0.0.0.0", port: "4000" });
expect(Cylon.api_config).to.be.eql(expectedConfig);
});
});
context("specifiying SSL key and cert", function() {
it("changes the SSL key and cert", function() {
expectedConfig.ssl.cert = "/path/to/cert/file";
expectedConfig.ssl.key = "/path/to/key/file";
Cylon.api({
ssl: {
cert: "/path/to/cert/file",
key: "/path/to/key/file"
}
});
expect(Cylon.api_config).to.be.eql(expectedConfig);
});
});
context("specifying an auth strategy", function() {
it("changes the auth strategy", function() {
var auth = { type: 'basic', user: 'user', pass: 'pass'}
expectedConfig.auth = auth;
Cylon.api({ auth: auth })
expect(Cylon.api_config).to.be.eql(expectedConfig);
});
});
context("specifying CORS restrictions", function() {
it("changes the CORS restrictions", function() {
var CORS = "https://localhost:4000";
expectedConfig.CORS = CORS;
Cylon.api({ CORS: CORS })
expect(Cylon.api_config).to.be.eql(expectedConfig);
});
});
});
describe("#findRobot", function() {
var bot;
before(function() {
bot = Cylon.robot({ name: "Robby" })
});
describe("async", function() {
context("looking for a robot that exists", function() {
it("calls the callback with the robot", function() {
var callback = spy();
Cylon.findRobot("Robby", callback);
expect(callback).to.be.calledWith(undefined, bot);
});
});
context("looking for a robot that does not exist", function(){
it("calls the callback with no robot and an error message", function() {
var callback = spy();
Cylon.findRobot("Ultron", callback);
var error = { error: "No Robot found with the name Ultron" };
expect(callback).to.be.calledWith(error, null);
});
});
});
describe("sync", function() {
context("looking for a robot that exists", function() {
it("returns the robot", function() {
expect(Cylon.findRobot("Robby")).to.be.eql(bot);
});
});
context("looking for a robot that does not exist", function(){
it("returns null", function() {
expect(Cylon.findRobot("Ultron")).to.be.eql(null);
});
});
});
});
describe("#findRobotDevice", function() {
var bot, device;
before(function() {
bot = Cylon.robot({
name: "Ultron",
device: { name: "ping", driver: "ping" }
});
device = bot.devices.ping;
});
describe("async", function() {
context("looking for a valid robot/device", function() {
it("calls the callback with the device and no error message", function() {
var callback = spy();
Cylon.findRobotDevice("Ultron", "ping", callback);
expect(callback).to.be.calledWith(undefined, device);
});
});
context("looking for a valid robot and invalid device", function() {
it("calls the callback with no device and an error message", function() {
var callback = spy();
Cylon.findRobotDevice("Ultron", "nope", callback);
var error = { error: "No device found with the name nope." };
expect(callback).to.be.calledWith(error, null);
});
});
context("looking for an invalid robot", function() {
it("calls the callback with no device and an error message", function() {
var callback = spy();
Cylon.findRobotDevice("Rob", "ping", callback);
var error = { error: "No Robot found with the name Rob" };
expect(callback).to.be.calledWith(error, null);
});
});
});
describe("synchronous", function() {
context("looking for a valid robot/device", function() {
it("returns the device", function() {
expect(Cylon.findRobotDevice("Ultron", "ping")).to.be.eql(device);
});
});
context("looking for a valid robot and invalid device", function() {
it("returns null", function() {
expect(Cylon.findRobotDevice("Ultron", "nope")).to.be.eql(null);
});
});
context("looking for an invalid robot", function() {
it("returns null", function() {
expect(Cylon.findRobotDevice("Rob", "ping")).to.be.eql(null);
});
});
});
});
describe("#findRobotConnection", function() {
var bot, conn;
before(function() {
bot = Cylon.robot({
name: "JARVIS",
connection: { name: "loopback", adaptor: "loopback" }
});
conn = bot.connections.loopback;
});
describe("async", function() {
context("looking for a valid robot/connection", function() {
it("calls the callback with the connection and no error message", function() {
var callback = spy();
Cylon.findRobotConnection("JARVIS", "loopback", callback);
expect(callback).to.be.calledWith(undefined, conn);
});
});
context("looking for a valid robot and invalid connection", function() {
it("calls the callback with no connection and an error message", function() {
var callback = spy();
Cylon.findRobotConnection("JARVIS", "nope", callback);
var error = { error: "No connection found with the name nope." };
expect(callback).to.be.calledWith(error, null);
});
});
context("looking for an invalid robot", function() {
it("calls the callback with no connection and an error message", function() {
var callback = spy();
Cylon.findRobotConnection("Rob", "loopback", callback);
var error = { error: "No Robot found with the name Rob" };
expect(callback).to.be.calledWith(error, null);
});
});
});
describe("synchronous", function() {
context("looking for a valid robot/connection", function() {
it("returns the connection", function() {
expect(Cylon.findRobotConnection("JARVIS", "loopback")).to.be.eql(conn);
});
});
context("looking for a valid robot and invalid connection", function() {
it("returns null", function() {
expect(Cylon.findRobotConnection("JARVIS", "nope")).to.be.eql(null);
});
});
context("looking for an invalid robot", function() {
it("returns null", function() {
expect(Cylon.findRobotConnection("Rob", "loopback")).to.be.eql(null);
});
});
});
});
describe("#start", function() {
before(function() {
Cylon.robots = [];
stub(Cylon, 'startAPI').returns(true);
});
after(function() {
Cylon.startAPI.restore();
});
it("starts the API", function() {
Cylon.start();
expect(Cylon.startAPI).to.be.called;
});
it("calls #start() on all robots", function() {
var bot1 = { start: spy() },
bot2 = { start: spy() };
Cylon.robots = [bot1, bot2];
Cylon.robots = {
'bot1': bot1,
'bot2': bot2
};
Cylon.start();
@ -334,15 +83,14 @@ describe("Cylon", function() {
});
describe("#halt", function() {
before(function() {
Cylon.robots = [];
});
it("calls #halt() on all robots", function() {
var bot1 = { halt: spy() },
bot2 = { halt: spy() };
Cylon.robots = [bot1, bot2];
Cylon.robots = {
'bot1': bot1,
'bot2': bot2
};
Cylon.halt();

View File

@ -3,9 +3,10 @@
var Ping = source('test/ping'),
Device = source("device"),
Robot = source("robot"),
Logger = source('logger');
Logger = source('logger'),
Utils = source('utils');
describe("Cylon.Device", function() {
describe("Device", function() {
var robot = new Robot({
name: "TestingBot",
connection: { name: 'loopback', adaptor: 'loopback' }
@ -28,10 +29,6 @@ describe("Cylon.Device", function() {
});
describe("constructor", function() {
it("sets @self as a circular reference", function() {
expect(device.self).to.be.eql(device);
});
it("sets @robot to the passed robot", function() {
expect(device.robot).to.be.eql(robot);
});
@ -74,7 +71,7 @@ describe("Cylon.Device", function() {
it("logs that it's starting the device", function() {
stub(Logger, 'info');
var message = "Starting device ping on pin 13";
var message = "Starting device 'ping' on pin 13.";
device.start()
@ -99,7 +96,7 @@ describe("Cylon.Device", function() {
});
it("logs that it's halt the device", function() {
var message = "Halting device ping";
var message = "Halting device 'ping'.";
stub(Logger, 'info');
device.halt();
@ -109,31 +106,31 @@ describe("Cylon.Device", function() {
});
});
describe("#data", function() {
var data = device.data();
describe("#toJSON", function() {
var json = device.toJSON();
it("returns an object", function() {
expect(data).to.be.a('object');
expect(json).to.be.a('object');
});
it("contains the device's name", function() {
expect(data.name).to.be.eql(device.name);
expect(json.name).to.be.eql(device.name);
});
it("contains the device's pin", function() {
expect(data.pin).to.be.eql(device.pin);
expect(json.pin).to.be.eql(device.pin);
});
it("contains the device's driver name", function() {
expect(data.driver).to.be.eql('Ping');
expect(json.driver).to.be.eql('Ping');
});
it("contains the device's connection data", function() {
expect(data.connection).to.be.eql(device.connection.data());
it("contains the device's connection json", function() {
expect(json.connection).to.be.eql(device.connection.toJSON());
});
it("contains the device's driver commands", function() {
expect(data.commands).to.be.eql(driver.commands());
expect(json.commands).to.be.eql(driver.commands);
});
});

View File

@ -2,7 +2,8 @@
var fs = require('fs');
var DigitalPin = source('io/digital-pin');
var DigitalPin = source('io/digital-pin'),
Utils = source('utils');
describe("Cylon.IO.DigitalPin", function() {
var pin = new DigitalPin({ pin: '4', mode: 'w' })
@ -195,12 +196,12 @@ describe("Cylon.IO.DigitalPin", function() {
context("if the mode isn't 'r'", function() {
before(function() {
stub(global, 'every');
stub(Utils, 'every');
stub(pin, '_setMode');
});
after(function() {
global.every.restore();
Utils.every.restore();
pin._setMode.restore();
});

View File

@ -3,7 +3,8 @@
var EventEmitter = require('events').EventEmitter;
var Driver = source("driver"),
Logger = source('logger');
Logger = source('logger'),
Utils = source('utils');
describe("Driver", function() {
var device = {
@ -33,23 +34,8 @@ describe("Driver", function() {
expect(driver.connection).to.be.eql(device.connection);
});
it("sets @commandList to an empty array by default", function() {
expect(driver.commandList).to.be.eql([]);
});
});
describe("#commands", function() {
var commands = ['list', 'of', 'commands']
before(function() {
driver.commandList = commands;
});
after(function() {
driver.commandList = [];
});
it("returns the driver's @commandList", function() {
expect(driver.commands()).to.be.eql(commands);
it("sets @commands to an empty array by default", function() {
expect(driver.commands).to.be.eql([]);
});
});
@ -66,23 +52,22 @@ describe("Driver", function() {
});
it("logs that it's starting the driver", function() {
var string = "Driver driver started";
var string = "Driver driver started.";
expect(Logger.info).to.be.calledWith(string);
});
it("triggers the provided callback", function() {
expect(callback).to.be.called;
});
it("tells the device to emit the 'start' event", function() {
expect(device.emit).to.be.calledWith('start');
});
});
describe("#halt", function() {
var callback;
before(function() {
stub(Logger, 'info');
driver.halt();
callback = spy();
driver.halt(callback);
});
after(function() {
@ -90,7 +75,11 @@ describe("Driver", function() {
});
it("logs that it's halting the driver", function() {
expect(Logger.info).to.be.calledWith("Driver driver halted")
expect(Logger.info).to.be.calledWith("Driver driver halted.")
});
it("triggers the callback", function() {
expect(callback).to.be.called;
})
});
});

View File

@ -1,6 +1,7 @@
'use strict';
var Logger = source('logger');
var Logger = source('logger'),
Utils = source('utils');
describe('Logger', function() {
after(function() {

View File

@ -0,0 +1,71 @@
'use strict';
var BasicLogger = source('logger/basic_logger');
var date = new Date(0).toISOString();
describe('BasicLogger', function() {
var logger = new BasicLogger(),
clock;
beforeEach(function() {
stub(console, 'log');
clock = sinon.useFakeTimers(0);
});
afterEach(function() {
console.log.restore();
clock.restore();
});
describe("#toString", function() {
it("returns 'BasicLogger'", function() {
expect(logger.toString()).to.be.eql('BasicLogger');
});
});
describe("#debug", function() {
it("logs to the console with a debug string", function() {
var logstring = "D, [" + date + "] DEBUG -- :";
logger.debug("Hello, World");
expect(console.log).to.be.calledWith(logstring, "Hello, World");
});
});
describe("#info", function() {
it("logs to the console with a info string", function() {
var logstring = "I, [" + date + "] INFO -- :";
logger.info("Hello, World");
expect(console.log).to.be.calledWith(logstring, "Hello, World");
});
});
describe("#warn", function() {
it("logs to the console with a warn string", function() {
var logstring = "W, [" + date + "] WARN -- :";
logger.warn("Hello, World");
expect(console.log).to.be.calledWith(logstring, "Hello, World");
});
});
describe("#error", function() {
it("logs to the console with a error string", function() {
var logstring = "E, [" + date + "] ERROR -- :";
logger.error("Hello, World");
expect(console.log).to.be.calledWith(logstring, "Hello, World");
});
});
describe("#fatal", function() {
it("logs to the console with a fatal string", function() {
var logstring = "F, [" + date + "] FATAL -- :";
logger.fatal("Hello, World");
expect(console.log).to.be.calledWith(logstring, "Hello, World");
});
});
});

View File

@ -2,7 +2,8 @@
var Device = source('device'),
Connection = source('connection'),
Robot = source("robot");
Robot = source("robot"),
Utils = source('utils');
describe("Robot", function() {
var work = spy();
@ -19,10 +20,6 @@ describe("Robot", function() {
});
describe("constructor", function() {
it("sets a @robot variable as a circular reference to the robot", function() {
expect(robot.robot).to.be.eql(robot);
});
describe("name", function() {
context("if provided", function() {
it("is set to the passed value", function() {
@ -76,34 +73,46 @@ describe("Robot", function() {
});
});
describe("#data", function() {
describe("all work and no play", function() {
var play = spy();
var playBot = new Robot({
play: play
});
it('makes Jack a dull boy', function() {
expect(playBot.work).to.be.eql(play);
})
})
describe("#toJSON", function() {
var bot = new Robot({
connection: { name: 'loopback', adaptor: 'loopback' },
device: { name: 'ping', driver: 'ping' }
});
var data = bot.data();
var json = bot.toJSON();
it("returns an object", function() {
expect(data).to.be.a('object');
expect(json).to.be.a('object');
});
it("contains the robot's name", function() {
expect(data.name).to.eql(bot.name);
expect(json.name).to.eql(bot.name);
});
it("contains the robot's commands", function() {
expect(data.commands).to.eql(bot.commands);
expect(json.commands).to.eql(bot.commands);
});
it("contains the robot's devices", function() {
var deviceData = bot.devices.ping.data();
expect(data.devices).to.eql([deviceData]);
var deviceJSON = bot.devices.ping.toJSON();
expect(json.devices).to.eql([deviceJSON]);
});
it("contains the robot's connections", function() {
var connectionData = bot.connections.loopback.data();
expect(data.connections).to.eql([connectionData]);
var connectionJSON = bot.connections.loopback.toJSON();
expect(json.connections).to.eql([connectionJSON]);
});
});

View File

@ -25,6 +25,14 @@ describe("Utils", function() {
it("converts floats", function() {
expect(2.5.fromScale(0, 10)).to.be.eql(0.25);
});
it("should return 1 if the number goes above the top of the scale", function() {
expect((15).fromScale(0, 10)).to.be.eql(1);
});
it("should return 0 if the number goes below the bottom of the scale", function() {
expect((5).fromScale(10, 20)).to.be.eql(0);
});
});
describe("#toScale", function() {
@ -32,6 +40,14 @@ describe("Utils", function() {
expect((0.5).toScale(0, 10)).to.be.eql(5);
});
it("bottom of scale should be returned when value goes below it", function() {
expect((-5).toScale(0, 10)).to.be.eql(0);
});
it("top of scale should be returned when value goes above it", function() {
expect((15).toScale(0, 10)).to.be.eql(10);
});
it("converts to floats", function() {
expect(0.25.toScale(0, 10)).to.be.eql(2.5);
});
@ -95,20 +111,6 @@ describe("Utils", function() {
});
});
describe("#slice", function() {
it("performs array slices", function() {
var arr = [1, 2, 3, 4, 5];
expect(slice.call(arr, 1)).to.be.eql([2, 3, 4, 5]);
});
});
describe("hasProp", function() {
it("checks objects have properties", function() {
var obj = { test: 'test' };
expect(hasProp.call(obj, 'test')).to.be.true;
});
});
describe("#subclass", function() {
var BaseClass = (function() {
function BaseClass(opts) {
@ -123,7 +125,7 @@ describe("Utils", function() {
})();
var SubClass = (function(klass) {
subclass(SubClass, klass);
utils.subclass(SubClass, klass);
function SubClass(opts) {
SubClass.__super__.constructor.apply(this, arguments);
@ -164,7 +166,7 @@ describe("Utils", function() {
function TestClass() {
this.self = this;
this.testInstance = new ProxyClass;
proxyFunctionsToObject(methods, this.testInstance, this.self, true);
utils.proxyFunctionsToObject(methods, this.testInstance, this.self, true);
}
return TestClass;
@ -186,19 +188,19 @@ describe("Utils", function() {
});
describe("#proxyTestStubs", function() {
it("proxies methods to an object's commandList", function() {
it("proxies methods to an object's commands", function() {
var methods = ["hello", "goodbye"],
base = { commandList: [] };
base = { commands: [] };
proxyTestStubs(methods, base);
expect(base.commandList).to.be.eql(methods);
utils.proxyTestStubs(methods, base);
expect(base.commands).to.be.eql(methods);
});
it("returns the object methods have been proxied to", function() {
var methods = ["hello", "goodbye"],
base = { commandList: [] };
base = { commands: [] };
expect(proxyTestStubs(methods, base)).to.be.eql(base);
expect(utils.proxyTestStubs(methods, base)).to.be.eql(base);
});
});
@ -208,14 +210,14 @@ describe("Utils", function() {
it("binds the 'this' scope for the method", function() {
proxy.boundMethod = function() { return this.hello; };
proxy.boundMethod = bind(proxy.boundMethod, me);
proxy.boundMethod = utils.bind(proxy.boundMethod, me);
expect(proxy.boundMethod()).to.eql("Hello World");
});
it("passes arguments along to bound functions", function() {
proxy.boundMethod = function(hello, world) { return [hello, world]; };
proxy.boundMethod = bind(proxy.boundMethod, me);
proxy.boundMethod = utils.bind(proxy.boundMethod, me);
expect(proxy.boundMethod("Hello", "World")).to.eql(["Hello", "World"]);
})

View File

@ -0,0 +1,20 @@
'use strict';
var sinon = require('sinon'),
spy = sinon.spy,
stub = sinon.stub;
// A mock version of the http.ClientRequest class
var MockRequest = module.exports = function MockRequest(opts) {
if (opts == null) {
opts = {};
}
this.url = "/";
this.headers = {};
for (var opt in opts) {
this[opt] = opts[opt];
}
};

View File

@ -0,0 +1,16 @@
'use strict';
var sinon = require('sinon'),
spy = sinon.spy,
stub = sinon.stub;
// A mock version of http.ServerResponse to be used in tests
var MockResponse = module.exports = function MockResponse() {
this.end = spy();
this.headers = {};
};
MockResponse.prototype.setHeader = function setHeader(name, value) {
this.headers[name] = value;
};