├── test
├── database
│ └── dummy
├── agent.test.js
├── freebird.signature.test.js
└── freebird.functional.test.js
├── index.js
├── .travis.yml
├── Makefile
├── lib
├── utils
│ ├── constants.js
│ ├── validate.js
│ └── utils.js
├── components
│ ├── registry.js
│ ├── loader.js
│ ├── netmgmt.js
│ └── handlers.js
├── rpc
│ ├── agent.js
│ └── apis.js
└── freebird.js
├── .gitignore
├── package.json
└── README.md
/test/database/dummy:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/index.js:
--------------------------------------------------------------------------------
1 | module.exports = require('./lib/freebird.js');
2 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: node_js
2 | node_js:
3 | - "0.12.10"
4 | - "4.1.0"
5 | - "6.9.2"
--------------------------------------------------------------------------------
/Makefile:
--------------------------------------------------------------------------------
1 | test-all:
2 | @./node_modules/.bin/mocha -u bdd --reporter spec
3 |
4 | .PHONY: test-all
--------------------------------------------------------------------------------
/lib/utils/constants.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | FB_STATE: {
3 | UNKNOW: 0,
4 | NORMAL: 1,
5 | BOOTING: 2,
6 | STOPPING: 3,
7 | RESETTING: 4
8 | },
9 | NC_STATE: {
10 | UNKNOW: 0,
11 | NORMAL: 1
12 | }
13 | };
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Logs
2 | logs
3 | *.log
4 | npm-debug.log*
5 |
6 | # Runtime data
7 | pids
8 | *.pid
9 | *.seed
10 |
11 | # Directory for instrumented libs generated by jscoverage/JSCover
12 | lib-cov
13 |
14 | # Coverage directory used by tools like istanbul
15 | coverage
16 |
17 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
18 | .grunt
19 |
20 | # node-waf configuration
21 | .lock-wscript
22 |
23 | # Compiled binary addons (http://nodejs.org/api/addons.html)
24 | build/Release
25 |
26 | # Dependency directory
27 | node_modules
28 |
29 | # Optional npm cache directory
30 | .npm
31 |
32 | # Optional REPL history
33 | .node_repl_history
34 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "freebird",
3 | "version": "0.1.9",
4 | "description": "app framework",
5 | "main": "index.js",
6 | "scripts": {
7 | "test": "make test-all",
8 | "clean": "rm -rf database/*"
9 | },
10 | "repository": {
11 | "type": "git",
12 | "url": "https://github.com/simenkid/freebird.git"
13 | },
14 | "author": "Simen Li",
15 | "license": "MIT",
16 | "bugs": {
17 | "url": "https://github.com/simenkid/freebird/issues"
18 | },
19 | "homepage": "https://github.com/simenkid/freebird",
20 | "dependencies": {
21 | "busyman": "^0.3.0",
22 | "freebird-base": "^0.4.6",
23 | "freebird-constants": "^0.2.4",
24 | "objectbox": "^0.3.0",
25 | "proving": "^0.1.0"
26 | },
27 | "devDependencies": {
28 | "chai": "^3.5.0",
29 | "freebird-netcore-mockup": "^0.2.5",
30 | "freebird-rpc": "^0.3.0",
31 | "mocha": "^2.5.3",
32 | "sinon": "^1.17.7",
33 | "sinon-chai": "^2.8.0"
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/lib/utils/validate.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | var validate = require('proving');
4 | var _ = require('busyman');
5 |
6 | // Duck Type Checking
7 | validate.isGadget = function (gad) {
8 | return (_.isObject(gad) && _.has(gad, '_id') && _.has(gad, '_auxId') && _.has(gad, '_dev'))
9 | };
10 |
11 | validate.isDevice = function (dev) {
12 | return (_.isObject(dev) && _.has(dev, '_netcore') && _.has(dev, '_id'))
13 | };
14 |
15 | validate.isNetcore = function (nc) {
16 | return (_.isObject(nc) && _.has(nc, '_freebird') && _.has(nc, '_controller'))
17 | };
18 |
19 | validate.ncNameTypeError = function (ncName) {
20 | return _.isString(ncName) ? undefined : new TypeError('ncName should be a string');
21 | }
22 |
23 | validate.callbackTypeError = function (cb) {
24 | return _.isFunction(cb) ? undefined : new TypeError('callback should be a function');
25 | }
26 |
27 | validate.ncNamesTypeError = function (ncNames) {
28 | if(!_.isArray(ncNames) || !_.every(ncNames, _.isString))
29 | return new TypeError('ncNames should be an array of string');
30 | }
31 |
32 | validate.permAddrTypeError = function (permAddr) {
33 | return _.isString(permAddr) ? undefined : new TypeError('permAddr should be a string');
34 | }
35 |
36 | validate.idsTypeError = function (ids) {
37 | function validId(id) {
38 | return (_.isString(id) || _.isNumber(id));
39 | }
40 |
41 | if(!_.isArray(ids) || !_.every(ids, validId))
42 | return new TypeError('ids should be an array of number or string');
43 | }
44 |
45 | validate.modeTypeError = function (mode) {
46 | return (mode === 1 || mode === 0) ? undefined : new TypeError('mode only accepts 0 or 1');
47 | }
48 |
49 | validate.idTypeError = function (id) {
50 | return (_.isNumber(id) || _.isString(id)) ? undefined : new TypeError('id should be a number or a string');
51 | }
52 |
53 | validate.durationTypeError = function (du) {
54 | return (_.isNumber(du)) ? undefined : new TypeError('duration should be a number');
55 | }
56 |
57 | validate.propsTypeError = function (props) {
58 | return _.isPlainObject(props) ? undefined : new TypeError('props should be an object');
59 | }
60 |
61 | validate.rptCfgTypeError = function (rptCfg) {
62 | return _.isPlainObject(rptCfg) ? undefined : new TypeError('rptCfg should be an object');
63 | }
64 |
65 | validate.propNamesTypeError = function (propNames) {
66 | if (!_.isNil(propNames)) {
67 | if(!_.isArray(propNames) || !_.every(propNames, _.isString))
68 | return new TypeError('propNames should be an array of string');
69 | }
70 | }
71 |
72 | validate.attrNameTypeError = function (attrName) {
73 | return _.isString(attrName) ? undefined : new TypeError('attrName should be a string');
74 | }
75 |
76 | validate.paramsTypeError = function (params) {
77 | return _.isArray(params) ? undefined : new TypeError('params should be an array');
78 | }
79 |
80 | module.exports = validate;
81 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
2 |
3 | # freebird
4 |
5 | [](https://nodei.co/npm/freebird/)
6 |
7 | [](https://travis-ci.org/freebirdjs/freebird)
8 | [](https://www.npmjs.com/package/freebird)
9 | [](https://www.npmjs.com/package/freebird)
10 |
11 |
12 |
13 | ## Status: Experimental, Unstable
14 |
15 |
16 |
17 |
18 |
19 | ## 1. Overview
20 |
21 | **freebird** is a Node.js framework that can building a heterogeneous network, such as BLE, ZigBee, CoAP and MQTT Protocols. It easy to deploy in many platforms like PC, Raspberry Pi, Beaglebone or other embedded device. This framework also provides a uniform interface for developers to operate the various protocols device at the same time, and as the Infrastructure of the IoT/WoT. With **freebird**, you can make **Front-end**, **Cloud** and **Machine** that simply connected with each other.
22 |
23 | The **freebird** framework has three basic classes of [**Netcore**](https://github.com/freebirdjs/freebird-base/blob/master/docs/NetcoreClass.md), [**Device**](https://github.com/freebirdjs/freebird-base/blob/master/docs/DeviceClass.md) and [**Gadget**](https://github.com/freebirdjs/freebird-base/blob/master/docs/GadgetClass.md) that are represent the network controller, remote device and resource of the device, respectively. For the RPC interface, you can create your own transportation to communicate with freebird, like TCP socket, RESTful APIs, WebSocket, and so on.
24 |
25 |
26 |
27 |
28 | ## 2. Features
29 |
30 | * Cross protocol, such as BLE, ZigBee, CoAP and MQTT.
31 | * Hierarchical data model in [Smart Object (IPSO)](http://www.ipso-alliance.org/ipso-community/resources/smart-objects-interoperability/) .
32 | * The local network management center and application gateway.
33 | * Based-on node.js that can easy to integrate machine applications with other services or frameworks, e.g., http server, express, React.js, Angular.js.
34 | * Handle the most basic part of internet of things and help front-end developers build any fascinating GUI and dashboard.
35 |
36 |
37 |
38 |
39 | ## 3. Installation
40 |
41 | > $ npm install freebird --save
42 |
43 |
44 |
45 |
46 | ## 4. Basic Usage
47 |
48 | ```js
49 | var Freebird = require('freebird'),
50 | bleCore = require('freebird-netcore-ble'),
51 | mqttCore = require('freebird-netcore-mqtt'),
52 | coapCore = require('freebird-netcore-coap'),
53 | zigbeeCore = require('freebird-netcore-zigbee');
54 |
55 | var freebird = new Freebird([ bleCore, mqttCore, coapCore, zigbeeCore ]);
56 |
57 | freebird.start(function (err) {
58 | console.log('Server started');
59 | });
60 |
61 | freebird.on('ready', function () {
62 |
63 | });
64 |
65 | freebird.on('devIncoming', function (dev) {
66 |
67 | });
68 |
69 | freebird.on('gadIncoming', function (gad) {
70 |
71 | });
72 | ```
73 |
74 |
75 |
76 |
--------------------------------------------------------------------------------
/lib/utils/utils.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | var _ = require('busyman'),
4 | RPC = require('freebird-constants').RPC;
5 |
6 | var utils = {};
7 |
8 | utils.dumpNetcoreInfo = function (nc) {
9 | var freebird = nc._freebird,
10 | info = nc.dump(),
11 | allDevs,
12 | allGads;
13 |
14 | info.numDevs = 0;
15 | info.numGads = 0;
16 |
17 | if (freebird) {
18 | allDevs = freebird._devbox.filter(function (dev) {
19 | return nc === dev.get('netcore');
20 | });
21 |
22 | allGads = freebird._gadbox.filter(function (gad) {
23 | return nc === gad.get('netcore');
24 | });
25 |
26 | info.numDevs = allDevs.length;
27 | info.numGads = allGads.length;
28 | }
29 |
30 | return info;
31 | };
32 |
33 | utils.dumpDeviceInfo = function (dev) {
34 | var info = dev.dump(),
35 | gadIds = _.map(info.gads, function (rec) {
36 | return rec.gadId;
37 | });
38 |
39 | info.gads = null;
40 | info.gads = gadIds;
41 | delete info.net.maySleep;
42 | delete info.net.sleepPeriod;
43 |
44 | return info;
45 | };
46 |
47 | utils.dumpGadgetInfo = function (gad) {
48 | return gad.dump();
49 | };
50 |
51 | utils.findByKey = function (obj, key) {
52 | return _.has(obj, key) ? { key: key, value: obj[key] } : undefined;
53 | };
54 |
55 | utils.findByValue = function (obj, val) {
56 | var item;
57 |
58 | _.forEach(obj, function (v, k) {
59 | if (v === val) {
60 | item = { key: k, value: v };
61 | return false;
62 | }
63 | });
64 |
65 | return item;
66 | };
67 |
68 | utils.rpcIntf = function (intf) {
69 | if (_.isString(intf))
70 | return utils.findByKey(RPC.Interface, intf);
71 | else if (_.isNumber(intf))
72 | return utils.findByValue(RPC.Interface, intf);
73 | };
74 |
75 | utils.rpcSubsys = function (subsys) {
76 | if (_.isString(subsys))
77 | return utils.findByKey(RPC.Subsys, subsys);
78 | else if (_.isNumber(subsys))
79 | return utils.findByValue(RPC.Subsys, subsys);
80 | };
81 |
82 | utils.rpcApi = function (subsys, cmdId) {
83 | var subsysItem = utils.rpcSubsys(subsys),
84 | apiNames = subsysItem ? RPC.Api[subsysItem.key] : undefined;
85 |
86 | if (!apiNames)
87 | return;
88 | else if (_.isString(cmdId))
89 | return utils.findByKey(apiNames, cmdId);
90 | else if (_.isNumber(cmdId))
91 | return utils.findByValue(apiNames, cmdId);
92 | }
93 |
94 | utils.rpcIntfString = function (intf) {
95 | var item = utils.rpcIntf(intf);
96 | return _.isObject(item) ? item.key : undefined;
97 | }
98 |
99 | utils.rpcSubsysString = function (subsys) {
100 | var item = utils.rpcSubsys(subsys);
101 | return _.isObject(item) ? item.key : undefined;
102 | }
103 |
104 | utils.rpcApiString = function (subsys, cmdId) {
105 | // [TODO] make sure
106 |
107 | // var subsysItem = utils.rpcSubsys(subsys);
108 |
109 | // return _.isObject(item) ? item.key : undefined;
110 |
111 |
112 | var subsysStr = utils.rpcSubsysString(subsys),
113 | apiNames = subsysStr ? RPC.Api[subsysStr] : undefined,
114 | item;
115 |
116 | if (!apiNames)
117 | return;
118 | else if (_.isString(cmdId))
119 | item = utils.findByKey(apiNames, cmdId);
120 | else if (_.isNumber(cmdId))
121 | item = utils.findByValue(apiNames, cmdId);
122 |
123 | return _.isObject(item) ? item.key : undefined;
124 | }
125 |
126 | module.exports = utils;
127 |
--------------------------------------------------------------------------------
/lib/components/registry.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | var _ = require('busyman'),
4 | validate = require('../utils/validate.js');
5 |
6 | var registry = {
7 | register: function (type, obj, callback) {
8 | if (type === 'device')
9 | return registerDevice(this, obj, callback);
10 | else if (type === 'gadget')
11 | return registerGadget(this, obj, callback);
12 | else
13 | setImmediate(callback, new TypeError('Unknown type: ' + type + ' to register with'));
14 |
15 | return this;
16 | },
17 | unregister: function (type, obj, callback) {
18 | if (type === 'device')
19 | return unregisterDevice(this, obj, callback);
20 | else if (type === 'gadget')
21 | return unregisterGadget(this, obj, callback);
22 | else
23 | setImmediate(callback, new TypeError('Unknown type: ' + type + ' to unregister with'));
24 |
25 | return this;
26 | }
27 | };
28 |
29 | /***********************************************************************/
30 | /*** Private Functions ***/
31 | /***********************************************************************/
32 | function registerDevice(freebird, dev, callback) {
33 | var devId, oldDev;
34 |
35 | if (!validate.isDevice(dev)) {
36 | setImmediate(callback, new TypeError('Input dev is not a valid device instance'));
37 | return freebird;
38 | }
39 |
40 | devId = dev.get('id');
41 | oldDev = _.isNil(devId) ? undefined : freebird.findById('device', devId);
42 |
43 | if (oldDev) {
44 | setImmediate(callback, new Error('Device with id: ' + devId + ' already exists, unregister it first'));
45 | return freebird;
46 | }
47 |
48 | if (dev._recovering) { // recovered from database (when at booting up or restarting stage)
49 | freebird._devbox.set(devId, dev, function (err, id) {
50 | if (!err)
51 | dev._recovering = false;
52 |
53 | callback(err, id);
54 | });
55 | } else {
56 | dev._poke();
57 | dev.set('net', {
58 | joinTime: Math.floor(Date.now()/1000) // seconds
59 | });
60 |
61 | freebird._devbox.add(dev, function (err, id) {
62 | if (!err)
63 | dev.set('_id', id); // set id to dev, registered successfully
64 |
65 | callback(err, id);
66 | });
67 | }
68 |
69 | return freebird;
70 | };
71 |
72 | function unregisterDevice(freebird, dev, callback) {
73 | if (!validate.isDevice(dev)) {
74 | setImmediate(callback, new TypeError('Input dev is not a valid device instance'));
75 | return freebird;
76 | }
77 |
78 | // unregister gadgets - 'gadLeaving' will be emitted@'devLeaving' handler. Let gadLeaving handler do the unregistration
79 | freebird._devbox.remove(dev.get('id'), callback);
80 | return freebird;
81 | };
82 |
83 | function registerGadget(freebird, gad, callback) {
84 | var gadId, oldGad;
85 |
86 | if (!validate.isGadget(gad)) {
87 | setImmediate(callback, new TypeError('Input gad is not a valid gadget instance'));
88 | return freebird;
89 | }
90 |
91 | gadId = gad.get('id');
92 | oldGad = _.isNil(gadId) ? undefined : freebird.findById('gadget', gadId);
93 |
94 | if (oldGad) {
95 | setImmediate(callback, new Error('Gadget with id: ' + gadId + ' already exists, unregister it first.'));
96 | return freebird;
97 | }
98 |
99 | if (gad._recovering) { // it was registered before, and now this instance is recovered from database
100 | freebird._gadbox.set(gadId, gad, function (err, id) {
101 | if (!err)
102 | gad._recovering = false;
103 |
104 | callback(err, id);
105 | });
106 | } else {
107 | freebird._gadbox.add(gad, function (err, id) {
108 | if (!err) {
109 | gad.set('_id', id); // dev._connectGadIdToAuxId(id, auxId) will be called in gad.set('_id', id)
110 |
111 | var dev = gad.get('device'),
112 | devId = dev.get('id'),
113 | gadTbl = dev.get('gadTable');
114 |
115 | var oldRec = _.find(gadTbl, function (gadRec) {
116 | return gadRec.auxId === gad.get('auxId');
117 | });
118 |
119 | if (oldRec) {
120 | oldRec.gadId = id;
121 | } else {
122 | // push new gadget record to gadTable
123 | gadTbl.push({ gadId: id, auxId: gad.get('auxId') });
124 | }
125 |
126 | freebird._devbox.replace(devId, 'gads', gadTbl, function (er) {
127 | callback(er, id);
128 | });
129 | } else {
130 | callback(err, id);
131 | }
132 | });
133 | }
134 |
135 | return freebird;
136 | };
137 |
138 | function unregisterGadget(freebird, gad, callback) {
139 | if (!validate.isGadget(gad)) {
140 | setImmediate(callback, new TypeError('Input gad is not a valid gadget instance'));
141 | return freebird;
142 | }
143 |
144 | freebird._gadbox.remove(gad.get('id'), function (err) {
145 | if (!err) {
146 | gad._clear();
147 | // if not invoking _clear() before disable(), updateComponent() will try to modify a non-existent gad
148 | // _clear() will also do dev._unlinkGad(gadId, auxId)
149 | gad.disable();
150 | gad = null;
151 | }
152 | callback(err);
153 | });
154 |
155 | return freebird;
156 | };
157 |
158 | module.exports = registry;
159 |
--------------------------------------------------------------------------------
/lib/rpc/agent.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | var _ = require('busyman'),
4 | RPC = require('freebird-constants').RPC;
5 |
6 | var apis = require('./apis.js'),
7 | utils = require('../utils/utils.js');
8 |
9 | function Agent(freebird) {
10 | this._transports = [];
11 | this._freebird = freebird;
12 | }
13 |
14 | Agent.prototype.addTransport = function (name, transp, callback) {
15 | var self = this,
16 | nameConflict = false,
17 | callback = callback || function (err) {
18 | throw err;
19 | };
20 |
21 | if (!_.isObject(transp) || !_.isFunction(transp.send) || !_.isFunction(transp.broadcast))
22 | return setImmediate(callback, new TypeError('Invalid transp object'));
23 |
24 | if (!_.isFunction(transp.emit) || !_.isFunction(transp.on))
25 | return setImmediate(callback, new TypeError('transp object should be an event emitter'));
26 |
27 | transp._name = name;
28 |
29 | nameConflict = _.some(this._transports, function (trsp) {
30 | return trsp._name === name;
31 | });
32 |
33 | if (nameConflict)
34 | return setImmediate(callback, new Error('name of transportation conflicts: ' + name));
35 |
36 | if (!_.includes(this._transports, transp)) {
37 | this._transports.push(transp);
38 |
39 | transp.on('message', function (msg) { // msg: { clientId, data }
40 | self.incomingMessageHandler(transp, msg);
41 | });
42 | }
43 |
44 | setImmediate(callback, null);
45 | };
46 |
47 | Agent.prototype.incomingMessageHandler = function (transp, msg) { // msg: { clientId, data }
48 | // msg.data could be the json strings of { __intf, subsys, seq, id, cmd, args } (REQ)
49 |
50 | var self = this,
51 | clientId = msg.clientId;
52 |
53 | this.parseIncomingMessage(msg, function (result) { // { valid: true, data: jmsg }
54 | var jmsg = result.data;
55 |
56 | if (!result.valid || utils.rpcIntfString(jmsg.__intf) !== 'REQ') {
57 | setImmediate(function () {
58 | transp.emit('unhandledMessage', msg); // Loop back to transp if not a REQ message
59 | });
60 | } else {
61 | self.incomingRequestHandler(transp, jmsg, function (rsp) {
62 | transp.send({ clientId: clientId, data: JSON.stringify(rsp) }, function (err) {
63 | if (err)
64 | self.emit('error', err);
65 | });
66 | });
67 | }
68 | });
69 | };
70 |
71 | Agent.prototype.parseIncomingMessage = function (msg, callback) {
72 | var result = jsonToObject(msg.data),
73 | jmsg = result.valid ? result.data : undefined,
74 | parsed; // { valid: true/false, data: msg }
75 |
76 | if (!result.valid)
77 | parsed = result;
78 | else if (!_.has(jmsg, '__intf') || !_.has(jmsg, 'subsys'))
79 | parsed = { valid: false, data: msg.data };
80 | else
81 | parsed = { valid: true, data: jmsg };
82 |
83 | setImmediate(callback, parsed);
84 | };
85 |
86 | Agent.prototype.incomingRequestHandler = function (transp, reqObj, callback) {
87 | var freebird = this._freebird,
88 | subsysName = utils.rpcSubsysString(reqObj.subsys),
89 | apiName = utils.rpcApiString(subsysName, reqObj.cmd),
90 | args = reqObj.args,
91 | api,
92 | rsp = {
93 | __intf: 'RSP',
94 | subsys: reqObj.subsys,
95 | seq: reqObj.seq,
96 | id: reqObj.id, // will be replaced after api called
97 | cmd: reqObj.cmd,
98 | status: 0, // will be replaced after api called
99 | data: null // will be attached after api called
100 | };
101 |
102 | if (_.isNumber(rsp.subsys))
103 | rsp.__intf = RPC.Interface.RSP;
104 |
105 | if (!apiName) {
106 | rsp.status = RPC.Status.BadRequest;
107 | return setImmediate(callback, rsp);
108 | }
109 |
110 | if (subsysName === 'dev' || subsysName === 'gad')
111 | apiName = subsysName + _.upperFirst(apiName);
112 |
113 | api = apis[apiName];
114 |
115 | if (!_.isFunction(api)) {
116 | rsp.status = RPC.Status.BadRequest;
117 | return setImmediate(callback, rsp);
118 | } else {
119 | api.call(freebird, args, function (err, result) {
120 | // RSP: { __intf, subsys, seq, id, cmd, status, data }
121 | rsp.status = result.status;
122 | rsp.id = result.id;
123 | rsp.data = result;
124 |
125 | delete result.id;
126 | delete result.status;
127 |
128 | if (err)
129 | freebird._fire('warn', err);
130 |
131 | callback(rsp);
132 | });
133 | }
134 | };
135 |
136 | Agent.prototype.indicate = function (msg, callback) {
137 | var freebird = this._freebird,
138 | numTransp = this._transports.length,
139 | shouldReportError = (numTransp === 1) ? true : false;
140 |
141 | if (numTransp === 0)
142 | return setImmediate(callback, null, 0);
143 |
144 | if (_.isObject(msg)) {
145 | try {
146 | msg = JSON.stringify(msg);
147 | } catch (e) {
148 | return setImmediate(callback, e);
149 | }
150 | } else if (!_.isString(msg)) {
151 | return setImmediate(callback, new TypeError('Message of an indication should be a string or an data object'));
152 | }
153 |
154 | _.forEach(this._transports, function (transp) {
155 | transp.broadcast({ data: msg }, function (err, bytes) {
156 | if (err)
157 | freebird._fire('warn', err);
158 |
159 | numTransp -= 1;
160 | if (numTransp === 0)
161 | callback(shouldReportError ? err : null, bytes); // Don't care error if transp num > 1
162 | });
163 | });
164 | };
165 |
166 | function jsonToObject(msg) {
167 | var message = Buffer.isBuffer(msg) ? msg.toString() : undefined,
168 | jmsg;
169 |
170 | if (_.isString(msg))
171 | message = msg;
172 |
173 | if (!message)
174 | return { valid: false, data: msg };
175 |
176 | try {
177 | jmsg = JSON.parse(message);
178 | } catch (e) {
179 | return { valid: false, data: msg };
180 | }
181 |
182 | return { valid: true, data: jmsg };
183 | }
184 |
185 | module.exports = Agent;
186 |
--------------------------------------------------------------------------------
/test/agent.test.js:
--------------------------------------------------------------------------------
1 | var EventEmitter = require('events'),
2 | transp_1 = new EventEmitter(),
3 | fb = new EventEmitter();
4 |
5 | var chai = require('chai'),
6 | expect = chai.expect,
7 | sinon = require('sinon'),
8 | _ = require('busyman'),
9 | RPC = require('freebird-constants').RPC;
10 |
11 | var Agent = require('../lib/rpc/agent'),
12 | apiAgent = new Agent(fb);
13 |
14 | fb._fire = function (evt, data) {
15 | var self = this;
16 | setImmediate(function () {
17 | self.emit(evt, data);
18 | });
19 | };
20 |
21 | fb.findById = function (type, id) {};
22 |
23 | describe('APIs - methods checks', function() {
24 | describe('#.addTransport', function() {
25 | it('should has error if transp.send is not a function', function (done) {
26 | apiAgent.addTransport('transp_1', transp_1, function (err) {
27 | if (err.message === 'Invalid transp object') {
28 | transp_1.send = function () {};
29 | done();
30 | }
31 | });
32 | });
33 |
34 | it('should has error if transp.broadcast is not a function', function (done) {
35 | apiAgent.addTransport('transp_1', transp_1, function (err) {
36 | if (err.message === 'Invalid transp object') {
37 | transp_1.broadcast = function () {};
38 | done();
39 | }
40 | });
41 | });
42 |
43 | it('add transport successfully', function (done) {
44 | apiAgent.addTransport('transp_1', transp_1, function (err) {
45 | if (!err)
46 | done();
47 | });
48 | });
49 |
50 | it('should has error if name of transportation conflicts', function (done) {
51 | apiAgent.addTransport('transp_1', transp_1, function (err) {
52 | if (err.message === 'name of transportation conflicts: transp_1')
53 | done();
54 | });
55 | });
56 | });
57 |
58 | describe('#.parseIncomingMessage', function() {
59 | it('should has error if msg.data is not a buffer or a json string', function (done) {
60 | var msg = {
61 | data: '{ __intf: "REQ" }'
62 | };
63 |
64 | apiAgent.parseIncomingMessage(msg, function (result) {
65 | if (!result.valid && result.data === '{ __intf: "REQ" }')
66 | done();
67 | });
68 | });
69 |
70 | it('msg.data should be a buffer', function (done) {
71 | var msg = {
72 | data: new Buffer('{ "__intf": "REQ", "subsys": "net", "seq": 3, "id": 0, "cmd": "getDevs", "args": { "ids": [ 2, 4, 18, 61 ] } }')
73 | };
74 |
75 | apiAgent.parseIncomingMessage(msg, function (result) {
76 | if (result.valid)
77 | done();
78 | });
79 | });
80 |
81 | it('msg.data should be the json string', function (done) {
82 | var msg = {
83 | data: '{ "__intf": "REQ", "subsys": "net", "seq": 3, "id": 0, "cmd": "getDevs", "args": { "ids": [ 2, 4, 18, 61 ] } }'
84 | };
85 |
86 | apiAgent.parseIncomingMessage(msg, function (result) {
87 | if (result.valid)
88 | done();
89 | });
90 | });
91 | });
92 |
93 | describe('#.incomingMessageHandler', function() {
94 | it('should call incomingMessageHandler function', function (done) {
95 | var incomingMessageHandlerStub = sinon.stub(apiAgent, 'incomingMessageHandler', function (transp, msg) {
96 | if (transp._name === 'transp_1' && msg === 'abc') {
97 | incomingMessageHandlerStub.restore();
98 | done();
99 | }
100 | });
101 |
102 | transp_1.emit('message', 'abc');
103 | });
104 |
105 | it('should emit error unhandledMessage', function (done) {
106 | var emitMsg = {
107 | clientId: 1,
108 | data: '{ "__intf": "REQ" }'
109 | };
110 |
111 | transp_1.once('unhandledMessage', function (msg) {
112 | if (_.isEqual(msg, emitMsg))
113 | done();
114 | });
115 |
116 | transp_1.emit('message',emitMsg);
117 | });
118 |
119 | it('should call transp_1.send function', function (done) {
120 | var transpSendStub = sinon.stub(transp_1, 'send', function (msg) {
121 | if (msg.clientId === 2) {
122 | incomingRequestHandlerStub.restore();
123 | transpSendStub.restore();
124 | done();
125 | }
126 | }),
127 | incomingRequestHandlerStub = sinon.stub(apiAgent, 'incomingRequestHandler', function (transp, reqObj, callback) {
128 | var rsp = {
129 | __intf: 'RSP',
130 | subsys: reqObj.subsys,
131 | seq: reqObj.seq,
132 | id: reqObj.id,
133 | cmd: reqObj.cmd,
134 | status: 0,
135 | data: null
136 | };
137 | callback(rsp);
138 | }),
139 | emitMsg = {
140 | clientId: 2,
141 | data: '{ "__intf": "REQ", "subsys": "net", "seq": 3, "id": 0, "cmd": "getDevs", "args": { "ids": [ 2, 4, 18, 61 ] } }'
142 | };
143 |
144 | transp_1.emit('message',emitMsg);
145 | });
146 | });
147 |
148 | describe('#.incomingRequestHandler', function() {
149 | it('should has error if no such cmd', function (done) {
150 | var reqObj = { "__intf": "REQ", "subsys": "net", "seq": 3, "id": 0, "cmd": "xxx", "args": { "ids": [ 2, 4, 18, 61 ] } };
151 |
152 | apiAgent.incomingRequestHandler(transp_1, reqObj, function (rsp) {
153 | if (rsp.status === RPC.Status.BadRequest)
154 | done();
155 | });
156 | });
157 |
158 | it('should has warn if call apis error', function (done) {
159 | var reqObj = { "__intf": "REQ", "subsys": "dev", "seq": 3, "id": 0, "cmd": "enable", "args": { "ids": [ 2, 4, 18, 61 ] } },
160 | warned = false,
161 | cbCalled = false;
162 |
163 | fb.once('warn', function (err) {
164 | if (err.message === 'id should be a number or a string') {
165 | warned = true;
166 | if (cbCalled)
167 | done();
168 | }
169 | });
170 |
171 | apiAgent.incomingRequestHandler(transp_1, reqObj, function (rsp) {
172 | if (rsp.status === RPC.Status.BadRequest) {
173 | cbCalled = true;
174 | if (warned)
175 | done();
176 | }
177 | });
178 | });
179 |
180 | it('should call apis cmd', function (done) {
181 | var reqObj = { "__intf": "REQ", "subsys": "net", "seq": 3, "id": 0, "cmd": "getDevs", "args": { "ids": [ 2, 4, 18, 61 ] } };
182 |
183 | apiAgent.incomingRequestHandler(transp_1, reqObj, function (rsp) {
184 | if (rsp.status === RPC.Status.Content)
185 | done();
186 | });
187 | });
188 | });
189 |
190 | describe('#.indicate', function() {
191 | it('should call transp_1.broadcast function', function (done) {
192 | var transpBroadcastStub = sinon.stub(transp_1, 'broadcast', function (msg, callback) {
193 | callback(null, 0);
194 | }),
195 | ind = { __intf: 'IND', subsys: 'net', type: 'xxx', id: 6, data: {} };
196 |
197 | apiAgent.indicate(ind, function (err) {
198 | if (!err)
199 | done();
200 | });
201 | });
202 | });
203 | });
204 |
--------------------------------------------------------------------------------
/lib/components/loader.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | var _ = require('busyman'),
4 | FBase = require('freebird-base'),
5 | validate = require('../utils/validate.js'),
6 | FB_STATE = require('../utils/constants.js').FB_STATE;
7 |
8 | var loader = {};
9 |
10 | /***********************************************************************/
11 | /*** Reload Methods ***/
12 | /***********************************************************************/
13 | loader.reloadSingleDev = function (freebird, devRec, callback) {
14 | var ncName = devRec.netcore,
15 | nc = freebird.findById('netcore', ncName),
16 | dev = freebird.findById('device', devRec.id),
17 | recoveredDev;
18 |
19 | if (!nc) // netcore is not registered, do not reload
20 | return setImmediate(callback);
21 | else if (dev && isSameDevice(dev, devRec)) // same dev exists, do not reload
22 | return setImmediate(callback);
23 |
24 | if (dev) {
25 | if (ncName === nc.getName() && devRec.net.address.permanent === dev.get('permAddr'))
26 | return; // already there, do not reload
27 | }
28 |
29 | recoveredDev = FBase.createDevice(nc);
30 | recoveredDev._recoverFromRecord(devRec);
31 | freebird.register('device', recoveredDev, callback); // return (err, id)
32 | };
33 |
34 | loader.reloadSingleGad = function (freebird, gadRec, callback) {
35 | var ncName = gadRec.netcore,
36 | permAddr = gadRec.dev.permAddr,
37 | itsDev = freebird.findByNet('device', ncName, permAddr),
38 | gad = freebird.findByNet('gadget', ncName, permAddr, gadRec.auxId),
39 | recoveredGad;
40 |
41 | if (!itsDev || gad)
42 | return setImmediate(callback); // find no device or gad is there, do not load
43 |
44 | recoveredGad = FBase.createGadget(itsDev, gadRec.auxId, gadRec);
45 | recoveredGad._recoverFromRecord(gadRec);
46 | freebird.register('gadget', recoveredGad, callback); // return (err, id)
47 | };
48 |
49 | loader.reloadByType = function (freebird, type, callback) {
50 | var box,
51 | reloadSingle,
52 | total = 0,
53 | recoveredIds = [];
54 |
55 | if (type === 'device') {
56 | box = freebird._devbox;
57 | reloadSingle = loader.reloadSingleDev;
58 | } else if (type === 'gadget') {
59 | box = freebird._gadbox;
60 | reloadSingle = loader.reloadSingleGad;
61 | } else {
62 | return setImmediate(callback, new TypeError('Unknown type: ' + type + ' to reload from'));
63 | }
64 |
65 | box.findFromDb({}, function (err, recs) {
66 | if (err)
67 | return callback(err);
68 | else if (recs.length === 0)
69 | return callback(null, recoveredIds);
70 |
71 | total = recs.length;
72 |
73 | _.forEach(recs, function (rec) {
74 | reloadSingle(freebird, rec, function (err, id) {
75 | recoveredIds.push(err ? null : id);
76 | total = total - 1;
77 | if (total === 0) // all done
78 | callback(null, recoveredIds);
79 | });
80 | });
81 | });
82 | };
83 |
84 | loader.reload = function (freebird, callback) {
85 | var loadedDevIds,
86 | loadedGadIds;
87 |
88 | loader.reloadByType(freebird, 'device', function (err, devIds) {
89 | loadedDevIds = devIds;
90 | if (err)
91 | return loader.unloadDevs(loadedDevIds, function () {
92 | callback(err);
93 | });
94 |
95 | loader.reloadByType(freebird, 'gadget', function (err, gadIds) {
96 | loadedGadIds = gadIds;
97 | if (err) {
98 | loader.unloadGads(freebird, loadedGadIds, function () {
99 | loader.unloadDevs(freebird, loadedDevIds, function () {
100 | callback(err);
101 | });
102 | });
103 | } else {
104 | loader.sync(freebird, function () {
105 | callback(null); // whether sync or not, return success
106 | });
107 | }
108 | });
109 | });
110 | };
111 |
112 | loader.sync = function (freebird, callback) {
113 | loader._syncByType(freebird, 'gadget', function (err) {
114 | if (err)
115 | callback(err);
116 | else
117 | loader._syncByType(freebird, 'device', callback);
118 | });
119 | };
120 |
121 | /***********************************************************************/
122 | /*** Unload Methods ***/
123 | /***********************************************************************/
124 | loader.unloadDevs = function (freebird, devIds, callback) {
125 | if (freebird._getState() !== FB_STATE.RESETTING) {
126 | _.forEach(devIds, function (id) {
127 | if (!_.isNil(id))
128 | freebird._devbox.removeElement(id);
129 | });
130 | return setImmediate(callback);
131 | } else {
132 | var devId = devIds.pop(),
133 | removeCb = function (err) {
134 | if (err)
135 | return callback(err);
136 | else if (devIds.length === 0)
137 | return callback(null);
138 | else
139 | freebird._devbox.remove(devIds.pop(), removeCb);
140 | };
141 |
142 | freebird._devbox.remove(devId, removeCb);
143 | }
144 | };
145 |
146 | loader.unloadGads = function (freebird, gadIds, callback) {
147 | if (freebird._getState() !== FB_STATE.RESETTING) {
148 | _.forEach(gadIds, function (id) {
149 | if (!_.isNil(id))
150 | freebird._gadbox.removeElement(id);
151 | });
152 | return setImmediate(callback);
153 | } else {
154 | var gadId = gadIds.pop(),
155 | removeCb = function (err) {
156 | if (err)
157 | return callback(err);
158 | else if (gadIds.length === 0)
159 | return callback(null);
160 | else
161 | freebird._gadbox.remove(gadIds.pop(), removeCb);
162 | };
163 |
164 | freebird._gadbox.remove(gadId, removeCb);
165 | }
166 | };
167 |
168 | loader.unloadByNetcore = function (freebird, ncName, callback) {
169 | var nc = freebird.findByNet('netcore', ncName),
170 | loadedGadIds,
171 | loadedDevIds;
172 |
173 | if (!validate.callbackTypeError(callback) && !nc)
174 | return setImmediate(callback, new Error('netcore: ' + ncName + ' not found.'));
175 |
176 | loadedGadIds = freebird._gadbox.filter(function (gad) {
177 | return gad.get('netcore') === nc;
178 | }).map(function (gad) {
179 | return gad.get('id');
180 | });
181 |
182 | loadedDevIds = freebird._devbox.filter(function (dev) {
183 | return dev.get('netcore') === nc;
184 | }).map(function (dev) {
185 | return dev.get('id');
186 | });
187 |
188 | loader.unloadGads(freebird, loadedGadIds, function () {
189 | loader.unloadDevs(freebird, loadedDevIds, function (err) {
190 | callback(err);
191 | });
192 | });
193 | };
194 |
195 | /***********************************************************************/
196 | /*** Private Methods ***/
197 | /***********************************************************************/
198 | loader._syncByType = function (freebird, type, callback) {
199 | var box,
200 | ops = 0,
201 | idsNotInBox = [];
202 |
203 | if (type === 'device')
204 | box = freebird._devbox;
205 | else if (type === 'gadget')
206 | box = freebird._gadbox;
207 | else
208 | return setImmediate(callback, new TypeError('Unknown type: ' + type + ' to sync with'));
209 |
210 | box.findFromDb({}, function (err, recs) {
211 | if (err)
212 | return callback(err);
213 |
214 | ops = recs.length;
215 | _.forEach(recs, function (rec) {
216 | if (!freebird.findById(type, rec.id))
217 | idsNotInBox.push(rec.id);
218 | });
219 |
220 | if (idsNotInBox.length) {
221 | _.forEach(idsNotInBox, function (id) {
222 | setImmediate(function () {
223 | box.remove(id, function () {
224 | ops = ops - 1;
225 | if (ops === 0)
226 | callback(null);
227 | });
228 | });
229 | });
230 | } else {
231 | callback(null);
232 | }
233 | });
234 | };
235 |
236 | function isSameDevice(dev, devRec) {
237 | if (dev.get('permAddr') !== devRec.net.address.permanent)
238 | return false;
239 | else
240 | return (dev.get('netcore').getName() === devRec.netcore);
241 | }
242 |
243 | module.exports = loader;
244 |
--------------------------------------------------------------------------------
/lib/freebird.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | var fs = require('fs'),
4 | path = require('path'),
5 | util = require('util'),
6 | EventEmitter = require('events');
7 |
8 | var _ = require('busyman'),
9 | Objectbox = require('objectbox'),
10 | RPC = require('freebird-constants').RPC,
11 | BTM_EVTS = require('freebird-constants').EVENTS_FROM_BOTTOM;
12 |
13 | var Agent = require('./rpc/agent.js'),
14 | validate = require('./utils/validate.js'),
15 | netmgmt = require('./components/netmgmt.js'),
16 | registry = require('./components/registry.js'),
17 | attachHandlers = require('./components/handlers.js'),
18 | FB_STATE = require('./utils/constants.js').FB_STATE;
19 |
20 | /***********************************************************************/
21 | /*** Freebird Class ***/
22 | /***********************************************************************/
23 | function Freebird(netcores, options) {
24 | // options: { maxDevNum: x, maxGadNum: y, dbPaths: { device, gadget } }
25 | if (!(this instanceof Freebird))
26 | return new Freebird(netcores, options);
27 |
28 | var self = this,
29 | options = options || {},
30 | devboxPath = path.resolve(__dirname, '../database/devices.db'),
31 | gadboxPath = path.resolve(__dirname, '../database/gadgets.db'),
32 | defaultDbFolder = path.resolve(__dirname, '../database'),
33 | propWritable = { writable: true, enumerable: false, configurable: false },
34 | propUnwritable = { writable: false, enumerable: false, configurable: false };
35 |
36 | var maxDevNum = options.maxDevNum || 200,
37 | maxGadNum = options.maxGadNum || (3 * maxDevNum),
38 | devDbPath = (_.isObject(options.dbPaths) ? options.dbPaths.device : undefined),
39 | gadDbPath = (_.isObject(options.dbPaths) ? options.dbPaths.gadget : undefined) || gadboxPath;
40 |
41 | if (devDbPath)
42 | defaultDbFolder = undefined;
43 | else
44 | devDbPath = devboxPath;
45 |
46 | if (maxGadNum < maxDevNum)
47 | throw new Error('Max gadget number cannot be less than max device number');
48 |
49 | try {
50 | if (defaultDbFolder)
51 | fs.statSync(defaultDbFolder);
52 | } catch (e) {
53 | fs.mkdirSync(defaultDbFolder);
54 | }
55 |
56 | netcores = _.isArray(netcores) ? netcores : [ netcores ];
57 |
58 | this.__ncNames = []; // For checking duplicated ncName, delete after checked
59 |
60 | _.forEach(netcores, function (nc, i) {
61 | var ncName;
62 |
63 | if (!validate.isNetcore(nc))
64 | throw new TypeError('Element of index ' + i + ' is not a valid netcore');
65 |
66 | nc._freebird = self;
67 | ncName = nc.getName();
68 |
69 | if (!_.includes(self.__ncNames, ncName))
70 | self.__ncNames.push(ncName);
71 | else
72 | throw new Error('Netcore name duplicates: ' + ncName);
73 | });
74 |
75 | this.__ncNames = null;
76 | delete this.__ncNames;
77 |
78 | EventEmitter.call(this);
79 |
80 | Object.defineProperty(this, '_netcores', _.assign({ value: netcores }, propUnwritable));
81 | Object.defineProperty(this, '_devbox', _.assign({ value: new Objectbox(devDbPath, maxDevNum) }, propWritable));
82 | Object.defineProperty(this, '_gadbox', _.assign({ value: new Objectbox(gadDbPath, maxGadNum) }, propWritable));
83 | Object.defineProperty(this, '_apiAgent', _.assign({ value: new Agent(self) }, propUnwritable));
84 | Object.defineProperty(this, '_state', _.assign({ value: FB_STATE.UNKNOW }, propWritable));
85 | Object.defineProperty(this, '_eventQueue', _.assign({ value: [] }, propWritable));
86 | Object.defineProperty(this, '_gadEventQueue', _.assign({ value: [] }, propWritable));
87 |
88 | attachHandlers(this);
89 |
90 | // Leave authenticate and authorize to rpc server implementer
91 | }
92 |
93 | util.inherits(Freebird, EventEmitter);
94 |
95 | /***********************************************************************/
96 | /*** Public Methods ***/
97 | /***********************************************************************/
98 | Freebird.prototype.addTransport = function (name, transp, callback) {
99 | if (!_.isString(name))
100 | throw new TypeError('name should be a string');
101 |
102 | this._apiAgent.addTransport(name, transp, callback);
103 | };
104 |
105 | Freebird.prototype.findById = function (type, id) {
106 | // type only accepts: 'netcore', 'device', 'gagdet'
107 | if (type === 'netcore')
108 | return _.find(this._netcores, function (nc) {
109 | return nc.getName() === id;
110 | });
111 | else if (type === 'device')
112 | return this._devbox.get(id);
113 | else if (type === 'gadget')
114 | return this._gadbox.get(id);
115 | else
116 | throw new TypeError('Unknow type: ' + type + ' to find with');
117 | };
118 |
119 | Freebird.prototype.findByNet = function (type, ncName, permAddr, auxId) {
120 | // type only accepts: 'netcore', 'device', 'gagdet'
121 | if (type === 'netcore')
122 | return _.find(this._netcores, function (nc) {
123 | return nc.getName() === ncName;
124 | });
125 | else if (type === 'device')
126 | return this._devbox.find(function (dev) {
127 | return (dev.get('permAddr') === permAddr) && (dev.get('netcore').getName() === ncName);
128 | });
129 | else if (type === 'gadget')
130 | return this._gadbox.find(function (gad) {
131 | return (gad.get('permAddr') === permAddr) && (gad.get('auxId') === auxId) && (gad.get('netcore').getName() === ncName);
132 | });
133 | else
134 | throw new TypeError('Unknow type: ' + type + ' to find with');
135 | };
136 |
137 | Freebird.prototype.filter = function (type, pred) {
138 | if (!_.isFunction(pred))
139 | throw new TypeError('pred should be a function');
140 |
141 | if (type === 'netcore')
142 | return _.filter(this._netcores, pred);
143 | else if (type === 'device')
144 | return this._devbox.filter(pred);
145 | else if (type === 'gadget')
146 | return this._gadbox.filter(pred);
147 | else
148 | throw new TypeError('Unknow type: ' + type + ' to find with');
149 | };
150 |
151 | /***********************************************************************/
152 | /*** Public Methods: Registeration and Network Management ***/
153 | /***********************************************************************/
154 | Freebird.prototype.register = registry.register;
155 |
156 | Freebird.prototype.unregister = registry.unregister;
157 |
158 | Freebird.prototype.start = netmgmt.start;
159 |
160 | Freebird.prototype.stop = netmgmt.stop;
161 |
162 | Freebird.prototype.reset = netmgmt.reset;
163 |
164 | Freebird.prototype.permitJoin = netmgmt.permitJoin;
165 |
166 | Freebird.prototype.remove = netmgmt.remove;
167 |
168 | Freebird.prototype.ban = netmgmt.ban;
169 |
170 | Freebird.prototype.unban = netmgmt.unban;
171 |
172 | Freebird.prototype.ping = netmgmt.ping;
173 |
174 | Freebird.prototype.maintain = netmgmt.maintain;
175 |
176 | /***********************************************************************/
177 | /*** Protected Methods ***/
178 | /***********************************************************************/
179 | Freebird.prototype._normalFire = function (evt, data) {
180 | var self = this;
181 |
182 | if (evt === BTM_EVTS.NcDevIncoming)
183 | process.nextTick(function () { self.emit(evt, data);});
184 | else
185 | setImmediate(function () { self.emit(evt, data);});
186 | };
187 |
188 | Freebird.prototype._lazyFire = function (evt, data) {
189 | this._eventQueue.push({ name: evt, msg: data});
190 | };
191 |
192 | Freebird.prototype._fire = Freebird.prototype._normalFire;
193 |
194 | Freebird.prototype._tweet = function (subsys, indType, id, data) { // Send RPC indications
195 | var self = this,
196 | ind = { __intf: 'IND', subsys: null, type: indType, id: id, data: data };
197 |
198 | if (subsys === 'net' || subsys === RPC.Subsys.net)
199 | ind.subsys = RPC.Subsys.net;
200 | else if (subsys === 'dev' || subsys === RPC.Subsys.dev)
201 | ind.subsys = RPC.Subsys.dev;
202 | else if (subsys === 'gad' || subsys === RPC.Subsys.gad)
203 | ind.subsys = RPC.Subsys.gad;
204 |
205 | setImmediate(function () {
206 | self._apiAgent.indicate(ind, function (err) {
207 | if (err)
208 | self._fire('warn', err);
209 | });
210 | });
211 | };
212 |
213 | Freebird.prototype._setState = function (state) {
214 | this._state = state;
215 | };
216 |
217 | Freebird.prototype._getState = function (state) {
218 | return this._state;
219 | };
220 |
221 | Freebird.prototype._changeFireMode = function (mode) { // 0: lazy, 1: normal
222 | mode = !!mode;
223 |
224 | if (mode === true) {
225 | if (this._eventQueue.length !== 0) {
226 | setImmediate(keepReleasing.bind(this));
227 | } else {
228 | this._fire = this._normalFire;
229 | }
230 | } else {
231 | this._fire = this._lazyFire;
232 | }
233 |
234 | function keepReleasing() {
235 | var evtObj;
236 |
237 | if (this._eventQueue.length !== 0) {
238 | evtObj = this._eventQueue.shift();
239 | this.emit(evtObj.name, evtObj.msg);
240 | setImmediate(keepReleasing.bind(this));
241 | } else {
242 | this._fire = this._normalFire;
243 | }
244 | }
245 | };
246 |
247 | Freebird.prototype._clearEventQueue = function () {
248 | this._eventQueue.length = 0;
249 | };
250 |
251 | module.exports = Freebird;
252 |
--------------------------------------------------------------------------------
/lib/components/netmgmt.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | var _ = require('busyman'),
4 | FreebirdConsts = require('freebird-constants');
5 |
6 | var loader = require('./loader.js'),
7 | validate = require('../utils/validate.js'),
8 | Constants = require('../utils/constants.js');
9 |
10 | var FB_STATE = Constants.FB_STATE,
11 | NC_STATE = Constants.NC_STATE,
12 | EVT_TOP = FreebirdConsts.EVENTS_TO_TOP;
13 |
14 | var netMgmt = {};
15 |
16 | netMgmt.start = function (callback) { // callback is optional
17 | var self = this,
18 | typeErr,
19 | fbState = this._getState(),
20 | ncNum = this._netcores.length,
21 | stoppedNcs = [],
22 | startedNcs = [],
23 | cbCalled = false;
24 |
25 | var innerCallback = function (er) {
26 | if (er) self._setState(fbState);
27 |
28 | if (_.isFunction(callback) && !cbCalled) {
29 | cbCalled = true;
30 | callback(er);
31 | } else if (er) {
32 | self.emit('error', er);
33 | }
34 |
35 | self._changeFireMode(1);
36 | };
37 |
38 | if (!_.isUndefined(callback))
39 | if (typeErr = validate.callbackTypeError(callback))
40 | throw typeErr;
41 |
42 | if (ncNum === 0)
43 | return setImmediate(innerCallback, new Error('No netcore found, cannot start'));
44 | if (fbState === FB_STATE.BOOTING || fbState === FB_STATE.STOPPING || fbState === FB_STATE.RESETTING)
45 | return setImmediate(innerCallback, new Error('Freebird can not start now'));
46 |
47 | this._setState(FB_STATE.BOOTING);
48 | stoppedNcs = this._netcores.filter(function (netcore) {
49 | return netcore._state === NC_STATE.UNKNOW;
50 | });
51 |
52 | if (fbState === FB_STATE.UNKNOW) {
53 | // reload all devices and gadgets from database
54 | loader.reload(this, function (err) {
55 | if (err) {
56 | innerCallback(err);
57 | } else {
58 | keepStarting();
59 | }
60 | });
61 | } else if (fbState === FB_STATE.NORMAL) {
62 | if (stoppedNcs.length === 0)
63 | return setImmediate(innerCallback, new Error('All netcores have been started'));
64 | else {
65 | this._changeFireMode(0);
66 | keepStarting();
67 | }
68 | }
69 |
70 | function keepStarting () {
71 | var ncore;
72 |
73 | if (stoppedNcs.length === 0 && !cbCalled) {
74 | self._setState(FB_STATE.NORMAL);
75 | self.maintain();
76 |
77 | if (fbState === FB_STATE.UNKNOW)
78 | self.emit(EVT_TOP.READY);
79 |
80 | return innerCallback(null);
81 | }
82 |
83 | ncore = stoppedNcs.pop();
84 |
85 | ncore.start(function (e) {
86 | var startedNcNum;
87 |
88 | if (fbState === FB_STATE.UNKNOW) {
89 | if (!e) {
90 | startedNcs.push(ncore);
91 | keepStarting();
92 | } else {
93 | startedNcNum = startedNcs.length;
94 |
95 | if (!startedNcNum)
96 | return innerCallback(new Error(ncore.getName() + ' netcore start failed with error: ' + e.message));
97 |
98 | _.forEach(startedNcs, function (netcore) {
99 | netcore.stop(function (err) {
100 | startedNcNum -= 1;
101 |
102 | if (err) {
103 | self.emit('error', err);
104 | } else if (startedNcNum === 0) {
105 | self._clearEventQueue();
106 | innerCallback(new Error(ncore.getName() + ' netcore start failed with error: ' + e.message));
107 | }
108 | });
109 | });
110 | }
111 | } else if (fbState === FB_STATE.NORMAL) {
112 | if (!e)
113 | keepStarting();
114 | else
115 | innerCallback(new Error(ncore.getName() + ' netcore start failed with error: ' + e.message));
116 | }
117 | });
118 | }
119 | };
120 |
121 | netMgmt.stop = function (callback) { // callback is optional
122 | var self = this,
123 | typeErr,
124 | fbState = this._getState(),
125 | ncNum = this._netcores.length,
126 | shouldCbWithError = (ncNum === 1),
127 | startedNcs = [],
128 | cbCalled = false;
129 |
130 | var innerCallback = function (er) {
131 | if (er) self._setState(fbState);
132 |
133 | if (_.isFunction(callback) && !cbCalled) {
134 | cbCalled = true;
135 | callback(er);
136 | } else if (er) {
137 | self.emit('error', er);
138 | }
139 | };
140 |
141 | if (!_.isUndefined(callback))
142 | if (typeErr = validate.callbackTypeError(callback))
143 | throw typeErr;
144 |
145 | if (ncNum === 0) // no netcore found, no need to stop
146 | return setImmediate(innerCallback, new Error('No netcore found, stop fails'));
147 | if (fbState !== FB_STATE.NORMAL)
148 | return setImmediate(innerCallback, new Error('Freebird can not stop now'));
149 |
150 | this._setState(FB_STATE.STOPPING);
151 | startedNcs = this._netcores.filter(function (netcore) {
152 | return netcore._state === NC_STATE.NORMAL;
153 | });
154 |
155 | keepStopping();
156 |
157 | function keepStopping () {
158 | var ncore;
159 |
160 | if (startedNcs.length === 0 && !cbCalled) {
161 | self._setState(FB_STATE.UNKNOW);
162 | return innerCallback(null);
163 | }
164 |
165 | ncore = startedNcs.pop();
166 | ncore.stop(function (e) {
167 | if (!e)
168 | keepStopping();
169 | else
170 | innerCallback(new Error(ncore.getName() + ' netcore stop failed with error: ' + e.message));
171 | });
172 | }
173 | };
174 |
175 | netMgmt.reset = function (mode, callback) { // 0: soft, 1: hard
176 | var self = this,
177 | typeErr,
178 | fbState = this._getState(),
179 | ncs = _.clone(this._netcores),
180 | resettedNcs = [],
181 | cbCalled = false;
182 |
183 | var innerCallback = function (er) {
184 | if (er) self._setState(fbState);
185 |
186 | if (_.isFunction(callback) && !cbCalled) {
187 | cbCalled = true;
188 | callback(er);
189 | } else if (er) {
190 | self.emit('error', er);
191 | }
192 |
193 | self._changeFireMode(1);
194 | };
195 |
196 | if (typeErr = validate.modeTypeError(mode)) {
197 | throw typeErr;
198 | } else if (!_.isUndefined(callback)) {
199 | if (typeErr = validate.callbackTypeError(callback))
200 | throw typeErr;
201 | }
202 |
203 | if (ncs.length === 0) // no netcore found
204 | return setImmediate(innerCallback, new Error('No netcore found, reset fails'));
205 | if (fbState === FB_STATE.BOOTING || fbState === FB_STATE.STOPPING || fbState === FB_STATE.RESETTING)
206 | return setImmediate(innerCallback, new Error('Freebird can not reset now'));
207 | if (fbState === FB_STATE.UNKNOW && mode === 0)
208 | return setImmediate(innerCallback, new Error('You can only hard reset when freebird is stopped'));
209 |
210 | this._setState(FB_STATE.RESETTING);
211 | if (fbState === FB_STATE.UNKNOW && mode === 1)
212 | this._changeFireMode(0);
213 | keepResetting();
214 |
215 | function keepResetting () {
216 | var ncore;
217 |
218 | if (ncs.length === 0 && !cbCalled) {
219 | self._setState(FB_STATE.NORMAL);
220 | self.emit(EVT_TOP.READY);
221 | return innerCallback(null);
222 | }
223 |
224 | ncore = ncs.pop();
225 | ncore.reset(mode, function (e) {
226 | var resettedNcNums;
227 |
228 | if (fbState === FB_STATE.UNKNOW) {
229 | if (!e) {
230 | loader.unloadByNetcore(self, ncore.getName(), function () {
231 | resettedNcs.push(ncore);
232 | keepResetting();
233 | });
234 | } else {
235 | resettedNcNums = resettedNcs.length;
236 |
237 | if (!resettedNcNums)
238 | return innerCallback(new Error(ncore.getName() + ' netcore reset failed with error: ' + e.message));
239 |
240 | _.forEach(resettedNcs, function(netcore) {
241 | netcore.stop(function (err) {
242 | resettedNcNums -= 1;
243 |
244 | if (err) {
245 | self.emit('error', err);
246 | } else if (resettedNcNums === 0) {
247 | self._clearEventQueue();
248 | innerCallback(new Error(ncore.getName() + ' netcore reset failed with error: ' + e.message));
249 | }
250 | });
251 | });
252 | }
253 | } else if (fbState === FB_STATE.NORMAL) {
254 | if (!e && mode)
255 | loader.unloadByNetcore(self, ncore.getName(), function () {
256 | keepResetting();
257 | });
258 | else if (!e)
259 | keepResetting();
260 | else
261 | innerCallback(new Error(ncore.getName() + ' netcore reset failed with error: ' + e.message));
262 | }
263 | });
264 | }
265 | };
266 |
267 | netMgmt.permitJoin = function (duration, callback) { // callback is optional
268 | var self = this,
269 | typeErr,
270 | ncs = _.clone(this._netcores),
271 | originalNcNum = ncs.length,
272 | cbCalled = false;
273 |
274 | var innerCallback = function (er) {
275 | if (_.isFunction(callback) && !cbCalled) {
276 | cbCalled = true;
277 | callback(er, duration);
278 | } else if (er) {
279 | self.emit('error', er);
280 | }
281 | };
282 |
283 | if (typeErr = validate.durationTypeError(duration)) {
284 | throw typeErr;
285 | } else if (!_.isUndefined(callback)) {
286 | if (typeErr = validate.callbackTypeError(callback))
287 | throw typeErr;
288 | }
289 |
290 | if (originalNcNum === 0) { // no netcore found
291 | return setImmediate(innerCallback, new Error('No netcore found, permitJoin fails'));
292 | }
293 |
294 | keepPermitting();
295 |
296 | function keepPermitting() {
297 | var ncore;
298 |
299 | if (ncs.length === 0) {
300 | return innerCallback(null);
301 | }
302 |
303 | ncore = ncs.pop();
304 | ncore.permitJoin(duration, function (err) {
305 | if (err && (originalNcNum === 1))
306 | innerCallback(err);
307 | else if (err)
308 | self.emit('error', err);
309 |
310 | keepPermitting(); // just keep openning permission
311 | });
312 | }
313 | };
314 |
315 | netMgmt.maintain = function (ncName, callback) { // ncName is optional. maintain all if not given. callback is optional
316 | var self = this,
317 | nc, ncs, typeErr, innerCallback,
318 | cbCalled = false;
319 |
320 | innerCallback = function (er) {
321 | if (_.isFunction(callback) && !cbCalled) {
322 | cbCalled = true;
323 | callback(er);
324 | } else if (er) {
325 | self.emit('warn', er);
326 | }
327 | };
328 |
329 | if (_.isFunction(ncName)) {
330 | callback = ncName;
331 | ncName = undefined;
332 | }
333 |
334 | if (typeErr = _.isNil(ncName) ? undefined : validate.ncNameTypeError(ncName)) {
335 | throw typeErr;
336 | } else if (!_.isUndefined(callback)) {
337 | if (typeErr = validate.callbackTypeError(callback))
338 | throw typeErr;
339 | }
340 |
341 | if (_.isString(ncName)) { // maintain a single
342 | nc = this.findByNet('netcore', ncName);
343 | if (!nc)
344 | return setImmediate(innerCallback, new Error('Cannot maintain, netcore not found'));
345 | else
346 | return nc.maintain(innerCallback);
347 | } else { // maintain all
348 | ncs = _.clone(this._netcores);
349 | keepNetcoreSyncing();
350 | }
351 |
352 | function keepNetcoreSyncing() {
353 | var ncore;
354 |
355 | if (ncs.length === 0) {
356 | return innerCallback(null);
357 | }
358 |
359 | ncore = ncs.pop();
360 |
361 | ncore.maintain(function (err) {
362 | keepNetcoreSyncing(); // just keep syncing. Errors will be emitted by device or gadget
363 | });
364 | }
365 | };
366 |
367 | netMgmt.remove = function (ncName, permAddr, callback) {
368 | var nc, typeErr;
369 |
370 | if (typeErr = validate.ncNameTypeError(ncName) || validate.permAddrTypeError(permAddr) || validate.callbackTypeError(callback))
371 | throw typeErr;
372 |
373 | if (!(nc = this.findByNet('netcore', ncName)))
374 | return setImmediate(callback, new Error('netcore not found'));
375 |
376 | nc.remove(permAddr, callback);
377 | };
378 |
379 | netMgmt.ban = function (ncName, permAddr, callback) {
380 | var nc, typeErr;
381 |
382 | if (typeErr = validate.ncNameTypeError(ncName) || validate.permAddrTypeError(permAddr) || validate.callbackTypeError(callback))
383 | throw typeErr;
384 |
385 | if (!(nc = this.findByNet('netcore', ncName)))
386 | return setImmediate(callback, new Error('netcore not found'));
387 |
388 | nc.ban(permAddr, callback);
389 | };
390 |
391 | netMgmt.unban = function (ncName, permAddr, callback) {
392 | var nc, typeErr;
393 |
394 | if (typeErr = validate.ncNameTypeError(ncName) || validate.permAddrTypeError(permAddr) || validate.callbackTypeError(callback))
395 | throw typeErr;
396 |
397 | if (!(nc = this.findByNet('netcore', ncName)))
398 | return setImmediate(callback, new Error('netcore not found'));
399 |
400 | nc.unban(permAddr, callback);
401 | };
402 |
403 | netMgmt.ping = function (ncName, permAddr, callback) {
404 | var nc, dev, typeErr;
405 |
406 | if (typeErr = validate.ncNameTypeError(ncName) || validate.permAddrTypeError(permAddr) || validate.callbackTypeError(callback))
407 | throw typeErr;
408 |
409 | if (!(nc = this.findByNet('netcore', ncName)))
410 | return setImmediate(callback, new Error('netcore not found'));
411 | else if (!(dev = this.findByNet('device', ncName, permAddr)))
412 | return setImmediate(callback, new Error('device not found'));
413 |
414 | dev.ping(callback);
415 | };
416 |
417 | module.exports = netMgmt;
418 |
--------------------------------------------------------------------------------
/lib/components/handlers.js:
--------------------------------------------------------------------------------
1 | // netcore event handlers
2 | 'use strict';
3 | var validate;
4 |
5 | var _ = require('busyman'),
6 | FBase = require('freebird-base'),
7 | FreebirdConsts = require('freebird-constants');
8 |
9 | var utils = require('../utils/utils');
10 |
11 | var EVT_TOP = FreebirdConsts.EVENTS_TO_TOP,
12 | EVT_BTM = FreebirdConsts.EVENTS_FROM_BOTTOM;
13 |
14 | var handlers = {};
15 |
16 | // all 'this' in the handler fuctions will be bound to freebird
17 | /***********************************************************************/
18 | /*** Netcore Event Listeners ***/
19 | /***********************************************************************/
20 | handlers.ncError = function (errMsg) { // errMsg = { ncName, error }
21 | this._fire(EVT_TOP.ERROR, errMsg.error/*.message*/);
22 |
23 | this._tweet('net', 'error', 0, {
24 | netcore: errMsg.ncName,
25 | message: errMsg.error.message
26 | });
27 | };
28 |
29 | handlers.ncEnabled = function (msg) { // { ncName }
30 | this._fire(EVT_TOP.NC_ENABLED, msg);
31 | this._tweet('net', 'enabled', 0, { netcore: msg.ncName });
32 | };
33 |
34 | handlers.ncDisabled = function (msg) { // { ncName }
35 | this._fire(EVT_TOP.NC_DISABLED, msg);
36 | this._tweet('net', 'disabled', 0, { netcore: msg.ncName });
37 | };
38 |
39 | handlers.ncStarted = function (msg) { // { ncName }
40 | this._fire(EVT_TOP.NC_STARTED, msg);
41 | this._tweet('net', 'started', 0, { netcore: msg.ncName });
42 | };
43 |
44 | handlers.ncStopped = function (msg) { // { ncName }
45 | this._fire(EVT_TOP.NC_STOPPED, msg);
46 | this._tweet('net', 'stopped', 0, { netcore: msg.ncName });
47 | };
48 |
49 | /***********************************************************************/
50 | /*** tackle device and gadget incoming, leaving, and reporting ***/
51 | /***********************************************************************/
52 | // v => ncNetReady
53 | handlers.ncReady = function (msg) { // { ncName }
54 | // No tweeting
55 |
56 | // this._tweet(EVT_TOP.NET_READY, msg);
57 |
58 | // this._tweet('net', 'stopped', 0, {
59 | // netcore: msg.netcore.getName()
60 | // });
61 |
62 | // [TODO]
63 | };
64 |
65 | handlers.ncPermitJoin = function (msg) { // { ncName, timeLeft }
66 | this._fire(EVT_TOP.NC_PERMIT_JOIN, msg);
67 | this._tweet('net', 'permitJoining', 0, { netcore: msg.ncName, timeLeft: msg.timeLeft });
68 | };
69 |
70 | handlers.ncDevIncoming = function (msg) { // { ncName, permAddr, raw: rawDev }
71 | var freebird = this,
72 | nc = this.findByNet('netcore', msg.ncName),
73 | dev, devIn;
74 |
75 | function queueEvent(gadMsgIn) {
76 | var isEventExist = _.some(freebird._gadEventQueue, function (gadMsg) {
77 | return gadMsg.ncName === gadMsgIn.ncName && gadMsg.permAddr === gadMsgIn.permAddr && gadMsg.auxId === gadMsgIn.auxId;
78 | });
79 |
80 | if (isEventExist) {
81 | _.remove(freebird._gadEventQueue, function (gadMsg) {
82 | return gadMsg.ncName === gadMsgIn.ncName && gadMsg.permAddr === gadMsgIn.permAddr && gadMsg.auxId === gadMsgIn.auxId;
83 | });
84 | } else {
85 | freebird._gadEventQueue.push(gadMsgIn);
86 | }
87 | }
88 |
89 | freebird.on(EVT_BTM.NcGadIncoming, queueEvent);
90 |
91 | function releasQueueEvent() {
92 | var evt = _.remove(freebird._gadEventQueue, function (gadMsg) {
93 | return gadMsg.ncName === msg.ncName && gadMsg.permAddr === msg.permAddr;
94 | });
95 |
96 | if (evt.length !== 0) {
97 | _.forEach(evt, function (msg) {
98 | freebird.emit(EVT_BTM.NcGadIncoming, msg);
99 | });
100 | }
101 | }
102 |
103 | if (!nc)
104 | return;
105 |
106 | dev = freebird.findByNet('device', msg.ncName, msg.permAddr);
107 | devIn = FBase.createDevice(nc, msg.raw);
108 |
109 | nc.cookRawDev(devIn, msg.raw, function (err, brewedDev) {
110 | if (err)
111 | return freebird._tweet('net', 'error', 0, {
112 | netcore: msg.ncName,
113 | message: err.message
114 | });
115 |
116 | devIn = brewedDev;
117 |
118 | if (dev) { // dev already exists, no need to fire EVT_TOP.DEV_INCOMING
119 | var netInfo = devIn.get('net');
120 | freebird.removeListener(EVT_BTM.NcGadIncoming, queueEvent);
121 | dev._poke();
122 | dev.set('_raw', devIn.get('raw')); // should call set('_raw', raw) to reset raw (safe for recoverd device)
123 | dev.extra = devIn.extra; // should assign extra (safe for recoverd device)
124 |
125 | dev.set('net', { // set('net', info) will find out network changes and report
126 | role: netInfo.role,
127 | parent: netInfo.parent,
128 | maySleep: netInfo.maySleep,
129 | sleepPeriod: netInfo.sleepPeriod,
130 | address: netInfo.address
131 | });
132 | dev.set('attrs', devIn.get('attrs')); // set('attrs', attrs) will find out attribute changes and report
133 | dev.set('net', { status: 'online' });
134 | } else if (nc.isJoinable()) {
135 | freebird.register('device', devIn, function (err, id) { // joinTime tagged in freebird.register('device')
136 | freebird.removeListener(EVT_BTM.NcGadIncoming, queueEvent);
137 |
138 | if (err) {
139 | devIn = null;
140 | freebird._tweet('net', 'error', 0, {
141 | netcore: msg.ncName,
142 | message: err.message
143 | });
144 | } else {
145 | devIn.enable();
146 | devIn._poke();
147 | devIn.set('net', { status: 'online' });
148 |
149 | freebird._fire(EVT_TOP.DEV_INCOMING, { ncName: msg.ncName, permAddr: msg.permAddr, id: devIn.get('id'), device: devIn });
150 | freebird._tweet('dev', 'devIncoming', devIn.get('id'), utils.dumpDeviceInfo(devIn));
151 | releasQueueEvent();
152 | }
153 | });
154 | } else {
155 | freebird.removeListener(EVT_BTM.NcGadIncoming, queueEvent);
156 | return; // do nothing if netcore is not allowed for joining
157 | }
158 | });
159 | };
160 |
161 | handlers.ncDevLeaving = function (msg) { // { ncName, permAddr }
162 | var freebird = this,
163 | nc = freebird.findByNet('netcore', msg.ncName),
164 | dev = freebird.findByNet('device', msg.ncName, msg.permAddr),
165 | devId,
166 | gad,
167 | gadTbl;
168 |
169 | if (!dev)
170 | return; // dev not found, do nothing
171 |
172 | dev._poke();
173 | devId = dev.get('id');
174 | dev.set('net', { status: 'offline' }); // 'netChanged', 'statusChanged'
175 |
176 | if (dev._removing) { // manually remove, should unregister all things
177 | gadTbl = dev.get('gadTable').map(function (rec) {
178 | return freebird.findById('gadget', rec.gadId);
179 | });
180 |
181 | removeGadget();
182 | }
183 |
184 | function removeGadget() {
185 | var gad, gadId, auxId;
186 |
187 | if (gadTbl.length === 0) {
188 | // remove device
189 | freebird.unregister('device', dev, function (err) {
190 | if (!err) {
191 | freebird._fire(EVT_TOP.DEV_LEAVING, { ncName: msg.ncName, permAddr: msg.permAddr, id: devId });
192 | freebird._tweet('dev', 'devLeaving', devId, { netcore: msg.ncName, permAddr: msg.permAddr });
193 | } else {
194 | freebird._fire('warn', err);
195 | freebird._tweet('dev', 'error', devId, { netcore: msg.ncName, message: err.message });
196 | }
197 | });
198 | } else {
199 | gad = gadTbl.pop();
200 | if (!gad)
201 | return removeGadget();
202 |
203 | gadId = gad.get('id');
204 | auxId = gad.get('auxId');
205 |
206 | freebird.unregister('gadget', gad, function (err) {
207 | if (!err) {
208 | freebird._fire(EVT_TOP.GAD_LEAVING, { ncName: msg.ncName, permAddr: msg.permAddr, auxId: auxId, id: gadId });
209 | freebird._tweet('gad', 'gadLeaving', gadId, { netcore: msg.ncName, permAddr: msg.permAddr, auxId: auxId });
210 | } else {
211 | freebird._fire('warn', err);
212 | freebird._tweet('gad', 'error', gadId, { netcore: msg.ncName, message: err.message });
213 | }
214 |
215 | removeGadget();
216 | });
217 | }
218 | }
219 | };
220 |
221 | handlers.ncDevNetChanging = function (msg) { // { ncName, permAddr, data: changes }
222 | var dev = this.findByNet('device', msg.ncName, msg.permAddr);
223 |
224 | if (dev)
225 | dev.set('net', msg.data); // device will check changes and then fire event: EVT_TOP.DevNetChanged
226 | };
227 |
228 | handlers.ncDevReporting = function (msg) { // { ncName, permAddr, data: devAttrs }
229 | var freebird = this,
230 | dev = freebird.findByNet('device', msg.ncName, msg.permAddr),
231 | devId;
232 |
233 | if (!dev)
234 | return;
235 |
236 | devId = dev.get('id');
237 |
238 | dev._poke();
239 | dev.set('net', { status: 'online' });
240 | dev.set('attrs', msg.data); // 'attrsChanged'
241 |
242 | freebird._fire(EVT_TOP.DEV_REPORTING, { ncName: msg.ncName, permAddr: msg.permAddr, id: devId, data: msg.data });
243 | // no need to tweet
244 | };
245 |
246 | handlers.ncGadIncoming = function (msg) { // { ncName, permAddr, auxId, raw: rawGad }
247 | var freebird = this,
248 | ncName = msg.ncName,
249 | nc = freebird.findByNet('netcore', ncName),
250 | dev = freebird.findByNet('device', ncName, msg.permAddr),
251 | gad = freebird.findByNet('gadget', ncName, msg.permAddr, msg.auxId),
252 | gadIn,
253 | doGadCook,
254 | syncTimes = 0;
255 |
256 | if (!dev)
257 | return; // device not found, ignore this gad incoming
258 |
259 | freebird._gadEventQueue.push(msg);
260 | dev._poke();
261 | dev.set('net', { status: 'online' });
262 |
263 | gadIn = FBase.createGadget(dev, msg.auxId, msg.raw);
264 |
265 | doGadCook = function () {
266 | // This is used to sync gadIncoming
267 | if (_.isNil(dev.get('id'))) {
268 | if (++syncTimes > 50) // try resync for 1 second, discard this gadIncoming message if fails
269 | return;
270 |
271 | return setTimeout(function () {
272 | doGadCook();
273 | }, 20);
274 | }
275 |
276 | nc.cookRawGad(gadIn, msg.raw, function (err, brewedGad) {
277 | _.remove(freebird._gadEventQueue, function (gadMsg) {
278 | return gadMsg.ncName === msg.ncName && gadMsg.permAddr === msg.permAddr && gadMsg.auxId === msg.auxId;
279 | });
280 |
281 | if (err)
282 | return freebird._tweet('net', 'error', 0, {
283 | netcore: msg.ncName,
284 | message: err.message
285 | });
286 |
287 | gadIn = brewedGad;
288 |
289 | if (gad) {
290 | gad.set('_raw', gadIn.get('raw'));
291 | gad.extra = gadIn.extra;
292 | gad.set('panel', gadIn.get('panel')); // 'panelChanged'
293 | gad.set('attrs', gadIn.get('attrs')); // 'attrsChanged'
294 |
295 | if (gad.get('props').name === 'unknown')
296 | gad.set('props', { name: gadIn.get('panel').classId });
297 |
298 | } else if (nc.isJoinable()) {
299 | freebird.register('gadget', gadIn, function (err, id) {
300 | if (err) {
301 | gadIn = null;
302 | freebird._fire('warn', err);
303 | freebird._tweet('gad', 'error', 0, { netcore: ncName, message: err.message});
304 | } else {
305 | gadIn.enable();
306 |
307 | if (gadIn.get('props').name === 'unknown')
308 | gadIn.set('props', { name: gadIn.get('panel').classId });
309 |
310 | freebird._fire(EVT_TOP.GAD_INCOMING, { ncName: msg.ncName, permAddr: msg.permAddr, auxId: msg.auxId, id: gadIn.get('id'), gadget: gadIn });
311 | freebird._tweet('gad', 'gadIncoming', gadIn.get('id'), utils.dumpGadgetInfo(gadIn));
312 | }
313 | });
314 | }
315 | });
316 | };
317 |
318 | doGadCook();
319 | };
320 |
321 | handlers.ncGadReporting = function (msg) { // { ncName, permAddr, auxId, data: gadAttrs, [appendFlag] }
322 | var freebird = this,
323 | ncName = msg.ncName,
324 | dev = freebird.findByNet('device', ncName, msg.permAddr),
325 | gad = freebird.findByNet('gadget', ncName, msg.permAddr, msg.auxId);
326 |
327 | if (!dev)
328 | return;
329 |
330 | dev._poke();
331 | dev.set('net', { status: 'online' });
332 |
333 | if (!gad)
334 | return;
335 |
336 | if (!msg.appendFlag)
337 | gad.set('attrs', msg.data); // 'attrsChanged'
338 | else
339 | gad._dangerouslyAppendAttrs(msg.data); // 'attrsAppend'
340 |
341 | freebird._fire(EVT_TOP.GAD_REPORTING, { ncName: msg.ncName, permAddr: msg.permAddr, auxId: msg.auxId, id: gad.get('id'), data: msg.data });
342 | freebird._tweet('gad', 'attrsReport', gad.get('id'), msg.data);
343 | };
344 |
345 | /***********************************************************************/
346 | /*** tackle banned device and gadget events ***/
347 | /***********************************************************************/
348 | handlers.ncBannedDevIncoming = function (msg) {
349 | return bannedComponent(this, 'device', 'bannedIncoming', msg);
350 | };
351 |
352 | handlers.ncBannedDevReporting = function (msg) {
353 | return bannedComponent(this, 'device', 'bannedReport', msg);
354 | };
355 |
356 | handlers.ncBannedGadIncoming = function (msg) {
357 | return bannedComponent(this, 'gadget', 'bannedIncoming', msg);
358 | };
359 |
360 | handlers.ncBannedGadReporting = function (msg) {
361 | return bannedComponent(this, 'gadget', 'bannedReport', msg);
362 | };
363 |
364 | /***********************************************************************/
365 | /*** tackle response event of netcore command ***/
366 | /***********************************************************************/
367 | handlers.ncNetBan = function (msg) { // { netcore, permAddr }
368 | var fb = this,
369 | ncName = msg.ncName,
370 | permAddr = msg.permAddr,
371 | nc = fb.findByNet('netcore', ncName),
372 | dev = fb.findByNet('device', ncName, permAddr);
373 |
374 | if (dev) {
375 | nc.remove(permAddr, function (err) {
376 | if (err) {
377 | fb._fire('warn', err);
378 | fb._tweet('dev', 'error', dev.get('id'), { netcore: ncName, message: err.message});
379 | }
380 | });
381 | }
382 | };
383 |
384 | handlers.ncNetUnban = function (msg) {
385 | // do nothing
386 | };
387 |
388 | handlers.ncNetPing = function (msg) {
389 | // do nothing
390 | };
391 |
392 | /***********************************************************************/
393 | /*** device and gadget events: instance has been changed ***/
394 | /***********************************************************************/
395 | handlers.devError = function (msg) { // { ncName, error, id }
396 | this._fire('warn', msg.error);
397 | this._tweet('dev', 'error', msg.id, { netcore: msg.ncName, message: msg.error.message});
398 | };
399 |
400 | handlers.devNetChanged = function (msg) {
401 | return updateComponent(this, 'dev', 'net', msg);
402 | };
403 |
404 | handlers.devPropsChanged = function (msg) {
405 | return updatePropsComponent(this, 'dev', msg);
406 | };
407 |
408 | handlers.devAttrsChanged = function (msg) {
409 | return updateComponent(this, 'dev', 'attrs', msg);
410 | };
411 |
412 | handlers.devRead = function (msg) {
413 | // do nothing
414 | };
415 |
416 | handlers.devWrite = function (msg) {
417 | // do nothing
418 | };
419 |
420 | handlers.devIdentify = function (msg) {
421 | // do nothing
422 | };
423 |
424 | handlers.gadError = function (msg) { // { ncName, error, id }
425 | this._fire('warn', msg.error);
426 | this._tweet('gad', 'error', msg.id, { netcore: msg.ncName, message: msg.error.message});
427 | };
428 |
429 | handlers.gadPanelChanged = function (msg) {
430 | return updateComponent(this, 'gad', 'panel', msg);
431 | };
432 |
433 | handlers.gadPropsChanged = function (msg) {
434 | return updatePropsComponent(this, 'gad', msg);
435 | };
436 |
437 | handlers.gadAttrsChanged = function (msg) {
438 | return updateComponent(this, 'gad', 'attrs', msg);
439 | };
440 |
441 | handlers.gadAttrsAppend = function (msg) {
442 | // msg: { ncName, permAddr, auxId, id, data: attrs }
443 | var fb = this,
444 | ncName = msg.ncName,
445 | id = msg.id,
446 | attrs = msg.data;
447 |
448 | this._gadbox.replace(id, 'attrs', attrs, function (err) {
449 | if (err) {
450 | fb._fire('warn', err);
451 | fb._tweet('gad', 'error', id, { netcore: ncName, message: err.message});
452 | } else {
453 | fb._fire(EVT_TOP.GAD_ATTRS_CHANGED, msg);
454 | fb._tweet('gad', 'attrsChanged', id, attrs);
455 | }
456 | });
457 | };
458 |
459 | handlers.gadRead = function (msg) {
460 | // do nothing
461 | };
462 |
463 | handlers.gadWrite = function (msg) {
464 | // do nothing
465 | };
466 |
467 | handlers.gadExec = function (msg) {
468 | // do nothing
469 | };
470 |
471 | handlers.gadReadReportCfg = function (msg) {
472 | // do nothing
473 | };
474 |
475 | handlers.gadWriteReportCfg = function (msg) {
476 | // do nothing
477 | };
478 |
479 | handlers.gadPanelChangedDisabled = function (msg) {
480 | // do nothing
481 | };
482 |
483 | /***********************************************************************/
484 | /*** Private Functions ***/
485 | /***********************************************************************/
486 | function bannedComponent(fb, type, indType, msg, cb) {
487 | // { ncName, permAddr, raw: rawDev } - bannedDevIncoming
488 | // { ncName, permAddr, auxId, raw: rawGad } - bannedGadIncoming
489 | // { ncName, permAddr, data: attrs } - bannedDevReporting
490 | // { ncName, permAddr, auxId, data: attrs } - bannedGadReporting
491 |
492 | var nc = fb.findByNet('netcore', msg.ncName),
493 | ncName = msg.ncName,
494 | permAddr = msg.permAddr,
495 | // upMsg = { netcore: nc, permAddr: permAddr },
496 | evtName = getBannedEventName(type, indType),
497 | component,
498 | componentId;
499 |
500 | if (type === 'device') {
501 | component = fb.findByNet(type, ncName, permAddr);
502 | componentId = component ? component.get('id') : 0;
503 |
504 | fb._fire(evtName, { ncName: msg.ncName, permAddr: msg.permAddr });
505 | fb._tweet('dev', evtName, componentId, { netcore: msg.ncName, permAddr: msg.permAddr });
506 |
507 | if (component) {
508 | nc.remove(permAddr, function (err) {
509 | if (err) {
510 | fb._fire('warn', err);
511 | fb._tweet('dev', 'error', component.get('id'), { netcore: ncName, message: err.message});
512 | }
513 | });
514 | }
515 | } else if (type === 'gadget') {
516 | component = fb.findByNet(type, ncName, permAddr, msg.auxId);
517 | componentId = component ? component.get('id') : 0;
518 |
519 | fb._fire(evtName, { ncName: msg.ncName, permAddr: msg.permAddr, auxId: msg.auxId });
520 | fb._tweet('gad', evtName, componentId, { netcore: msg.ncName, permAddr: msg.permAddr, auxId: msg.auxId });
521 |
522 | if (component) {
523 | var gadId = component.get('id');
524 |
525 | fb.unregister('gadget', component, function (err) {
526 | if (!err) {
527 | fb._fire(EVT_TOP.GAD_LEAVING, { ncName: msg.ncName, permAddr: msg.permAddr, auxId: msg.auxId, id: gadId });
528 | fb._tweet('gad', 'gadLeaving', gadId, { netcore: msg.ncName, permAddr: msg.permAddr, auxId: msg.auxId });
529 | } else {
530 | fb._fire('warn', err);
531 | fb._tweet('gad', 'error', gadId, { netcore: msg.ncName, message: err.message });
532 | }
533 | });
534 | }
535 | }
536 |
537 | if (_.isFunction(cb))
538 | cb();
539 | }
540 |
541 | function getBannedEventName(type, indType) {
542 | var evt;
543 |
544 | if (type === 'device') {
545 | if (indType === 'bannedReport')
546 | evt = EVT_TOP.DEV_BAN_REPORTING;
547 | else if (indType === 'bannedIncoming')
548 | evt = EVT_TOP.DEV_BAN_INCOMING;
549 | } else if (type === 'gadget') {
550 | if (indType === 'bannedReport')
551 | evt = EVT_TOP.GAD_BAN_REPORTING;
552 | else if (indType === 'bannedIncoming')
553 | evt = EVT_TOP.GAD_BAN_INCOMING;
554 | }
555 | return evt;
556 | }
557 |
558 | function updateComponent(fb, type, namespace, msg, cb) {
559 | // type = 'dev', msg: { ncName, permAddr, id, data: delta, _data: oldVal }
560 | // type = 'gad' ,msg: { ncName, permAddr, auxId, id, data: delta, _data: oldVal }
561 | var ncName = msg.ncName,
562 | id = msg.id,
563 | delta = msg.data,
564 | evtName = getUpdateEventName(type, namespace),
565 | box = type === 'dev' ? fb._devbox : fb._gadbox;
566 |
567 | box.modify(id, namespace, delta, function (err, diff) {
568 | if (err) {
569 | fb._fire('warn', err);
570 | fb._tweet(type, 'error', id, { netcore: ncName, message: err.message});
571 | } else {
572 | fb._fire(evtName, msg);
573 | fb._tweet(type, namespace + 'Changed', msg.id, delta);
574 |
575 | if (evtName === EVT_TOP.DEV_NET_CHANGED && delta.status) {
576 |
577 | fb._fire(EVT_TOP.DEV_STATUS_CHANGED, { ncName: msg.ncName, permAddr: msg.permAddr, id: msg.id, data: { status: delta.status } });
578 | fb._tweet('dev', 'statusChanged', msg.id, { status: delta.status });
579 | }
580 | }
581 |
582 | if (_.isFunction(cb))
583 | cb(err, diff);
584 | });
585 | }
586 |
587 | function updatePropsComponent(fb, type, msg, cb) {
588 | // type = 'dev', msg: { ncName, permAddr, id, data: delta, _data: oldVal }
589 | // type = 'gad' ,msg: { ncName, permAddr, auxId, id, data: delta, _data: oldVal }
590 | var ncName = msg.ncName,
591 | id = msg.id,
592 | delta = msg.data,
593 | evtName = getUpdateEventName(type, 'props'),
594 | box = type === 'dev' ? fb._devbox : fb._gadbox,
595 | component = type === 'dev' ? fb.findById('device', id) : fb.findById('gadget', id),
596 | newProps;
597 |
598 | if (!component)
599 | return;
600 |
601 | newProps = component.get('props');
602 |
603 | box.replace(id, 'props', newProps, function (err) {
604 | if (err) {
605 | fb._fire('warn', err);
606 | fb._tweet(type, 'error', id, { netcore: ncName, message: err.message});
607 | } else {
608 | fb._fire(evtName, msg);
609 | fb._tweet(type, 'propsChanged', msg.id, delta);
610 | }
611 |
612 | if (_.isFunction(cb))
613 | cb(err, delta);
614 | });
615 | }
616 |
617 | function getUpdateEventName(type, namespace) {
618 | var evtName;
619 |
620 | if (type === 'dev') {
621 | if (namespace === 'net')
622 | evtName = EVT_TOP.DEV_NET_CHANGED;
623 | else if (namespace === 'props')
624 | evtName = EVT_TOP.DEV_PROPS_CHANGED;
625 | else if (namespace === 'attrs')
626 | evtName = EVT_TOP.DEV_ATTRS_CHANGED;
627 | } else if (type === 'gad') {
628 | if (namespace === 'panel')
629 | evtName = EVT_TOP.GAD_PANEL_CHANGED;
630 | else if (namespace === 'props')
631 | evtName = EVT_TOP.GAD_PROPS_CHANGED;
632 | else if (namespace === 'attrs')
633 | evtName = EVT_TOP.GAD_ATTRS_CHANGED;
634 | }
635 |
636 | return evtName;
637 | }
638 |
639 | module.exports = function attachEventListeners(freebird) {
640 | var ncLsns = freebird._ncEventListeners = freebird._ncEventListeners || {};
641 |
642 | freebird.removeAllListeners(EVT_BTM.NcError)
643 | .removeAllListeners(EVT_BTM.NcEnabled)
644 | .removeAllListeners(EVT_BTM.NcDisabled)
645 | .removeAllListeners(EVT_BTM.NcStarted)
646 | .removeAllListeners(EVT_BTM.NcStopped)
647 | .removeAllListeners(EVT_BTM.NcReady)
648 | .removeAllListeners(EVT_BTM.NcPermitJoin)
649 | .removeAllListeners(EVT_BTM.NcDevIncoming)
650 | .removeAllListeners(EVT_BTM.NcDevLeaving)
651 | .removeAllListeners(EVT_BTM.NcDevNetChanging)
652 | .removeAllListeners(EVT_BTM.NcDevReporting)
653 | .removeAllListeners(EVT_BTM.NcGadIncoming)
654 | .removeAllListeners(EVT_BTM.NcGadReporting)
655 | .removeAllListeners(EVT_BTM.NcBannedDevIncoming)
656 | .removeAllListeners(EVT_BTM.NcBannedDevReporting)
657 | .removeAllListeners(EVT_BTM.NcBannedGadIncoming)
658 | .removeAllListeners(EVT_BTM.NcBannedGadReporting)
659 | .removeAllListeners(EVT_BTM.DevError)
660 | .removeAllListeners(EVT_BTM.DevNetChanged)
661 | .removeAllListeners(EVT_BTM.DevPropsChanged)
662 | .removeAllListeners(EVT_BTM.DevAttrsChanged)
663 | .removeAllListeners(EVT_BTM.GadError)
664 | .removeAllListeners(EVT_BTM.GadPanelChanged)
665 | .removeAllListeners(EVT_BTM.GadPropsChanged)
666 | .removeAllListeners(EVT_BTM.GadAttrsChanged)
667 | .removeAllListeners(EVT_BTM.GadAttrsAppend);
668 |
669 | // Firstly bind the handlers to freebird
670 | _.forEach(handlers, function (lsn, key) {
671 | ncLsns[key] = lsn.bind(freebird);
672 | });
673 |
674 | // Then, attach the handlers to events // VVVVVVVVVVVVVVVVVVVVVV [TODO] all kvps need to be checked
675 | freebird.on(EVT_BTM.NcError, ncLsns.ncError); // { ncName, error: err }
676 | freebird.on(EVT_BTM.NcEnabled, ncLsns.ncEnabled);
677 | freebird.on(EVT_BTM.NcDisabled, ncLsns.ncDisabled);
678 | freebird.on(EVT_BTM.NcStarted, ncLsns.ncStarted);
679 | freebird.on(EVT_BTM.NcStopped, ncLsns.ncStopped);
680 | freebird.on(EVT_BTM.NcReady, ncLsns.ncReady); // { ncName }
681 | freebird.on(EVT_BTM.NcPermitJoin, ncLsns.ncPermitJoin); // { ncName, timeLeft: self._joinTicks }
682 |
683 | freebird.on(EVT_BTM.NcDevIncoming, ncLsns.ncDevIncoming); // { ncName, permAddr: permAddr, raw: rawDev }
684 | freebird.on(EVT_BTM.NcDevLeaving, ncLsns.ncDevLeaving); // { ncName, permAddr: permAddr }
685 | freebird.on(EVT_BTM.NcDevNetChanging, ncLsns.ncDevNetChanging); // { ncName, permAddr: permAddr, data: changes }
686 | freebird.on(EVT_BTM.NcDevReporting, ncLsns.ncDevReporting); // { ncName, permAddr: permAddr, data: devAttrs }
687 |
688 | freebird.on(EVT_BTM.NcGadIncoming, ncLsns.ncGadIncoming); // { ncName, permAddr: permAddr, auxId: auxId, raw: rawGad }
689 | freebird.on(EVT_BTM.NcGadReporting, ncLsns.ncGadReporting); // { ncName: this, permAddr: permAddr, auxId: auxId, data: gadAttrs }
690 |
691 | freebird.on(EVT_BTM.NcBannedDevIncoming, ncLsns.ncBannedDevIncoming); // { ncName, permAddr: permAddr, raw: rawDev }
692 | freebird.on(EVT_BTM.NcBannedDevReporting, ncLsns.ncBannedDevReporting); // { ncName, permAddr: permAddr, data: devAttrs }
693 |
694 | freebird.on(EVT_BTM.NcBannedGadIncoming, ncLsns.ncBannedGadIncoming); // { ncName, permAddr: permAddr, auxId: auxId, raw: rawGad }
695 | freebird.on(EVT_BTM.NcBannedGadReporting, ncLsns.ncBannedGadReporting); // { ncName: this, permAddr: permAddr, auxId: auxId, data: gadAttrs }
696 |
697 | freebird.on(EVT_BTM.NcNetBan, ncLsns.ncNetBan); // { ncName, permAddr: permAddr }
698 | freebird.on(EVT_BTM.NcNetUnban, ncLsns.ncNetUnban); // { ncName, permAddr: permAddr }
699 | freebird.on(EVT_BTM.NcNetPing, ncLsns.ncNetPing); // { ncName, permAddr: permAddr, data: time }
700 |
701 | freebird.on(EVT_BTM.DevError, ncLsns.devError); // { ncName, error: err }
702 | freebird.on(EVT_BTM.DevNetChanged, ncLsns.devNetChanged); // { ncName, data: delta }, setNetInfo
703 | freebird.on(EVT_BTM.DevPropsChanged, ncLsns.devPropsChanged); // { ncName, data: delta }, setProps
704 | freebird.on(EVT_BTM.DevAttrsChanged, ncLsns.devAttrsChanged); // { ncName, data: delta }, setAttrs
705 |
706 | freebird.on(EVT_BTM.DevRead, ncLsns.devRead); // { ncName, permAddr: permAddr, data: result }
707 | freebird.on(EVT_BTM.DevWrite, ncLsns.devWrite); // { ncName, permAddr: permAddr, data: result }
708 | freebird.on(EVT_BTM.DevIdentify, ncLsns.devIdentify); // { ncName, permAddr: permAddr }
709 |
710 | freebird.on(EVT_BTM.GadError, ncLsns.gadError); // { id, error: err }
711 | freebird.on(EVT_BTM.GadPanelChanged, ncLsns.gadPanelChanged); // { id, data: delta }, setPanelInfo
712 | freebird.on(EVT_BTM.GadPropsChanged, ncLsns.gadPropsChanged); // { id, data: delta }, setProps
713 | freebird.on(EVT_BTM.GadAttrsChanged, ncLsns.gadAttrsChanged); // { id, data: delta }, setAttrs
714 | freebird.on(EVT_BTM.GadAttrsAppend, ncLsns.gadAttrsAppend); // { id, data: attrs }, _dangerouslySetAttrs
715 |
716 | freebird.on(EVT_BTM.GadRead, ncLsns.gadRead); // { ncName, permAddr: permAddr, auxId: auxId, data: result }
717 | freebird.on(EVT_BTM.GadWrite, ncLsns.gadWrite); // { ncName, permAddr: permAddr, auxId: auxId, data: result }
718 | freebird.on(EVT_BTM.GadExec, ncLsns.gadExec); // { ncName, permAddr: permAddr, auxId: auxId, data: result }
719 | freebird.on(EVT_BTM.GadReadReportCfg, ncLsns.gadReadReportCfg); // { ncName, permAddr: permAddr, auxId: auxId, data: result }
720 | freebird.on(EVT_BTM.GadWriteReportCfg, ncLsns.gadWriteReportCfg); // { ncName, permAddr: permAddr, auxId: auxId, data: result }
721 | freebird.on(EVT_BTM.GadPanelChangedDisabled, ncLsns.gadPanelChangedDisabled);
722 | };
723 |
724 | // [TODO] HOW TO DEAL WITH ERROR MESSAGES?
--------------------------------------------------------------------------------
/test/freebird.signature.test.js:
--------------------------------------------------------------------------------
1 | var fs = require('fs'),
2 | path = require('path');
3 |
4 | var _ = require('busyman'),
5 | expect = require('chai').expect;
6 |
7 | var Freebird = require('../index');
8 |
9 | var fakeNc = {
10 | _freebird: {},
11 | _controller: {},
12 | getName: function () { return 'fakeNc1'; },
13 | start: function (cb) { cb(); },
14 | stop: function (cb) { cb(); },
15 | reset: function (mode, cb) { cb(); },
16 | permitJoin: function (d, cb) { cb(); },
17 | remove: function (ncName, permAddr, cb) { cb(); },
18 | ban: function (ncName, permAddr, cb) { cb(); },
19 | unban: function (ncName, permAddr, cb) { cb(); },
20 | ping: function (ncName, permAddr, cb) { cb(); },
21 | maintain: function (cb) { cb(); }
22 | },
23 | fbWithNc = new Freebird([fakeNc], { dbPaths: {
24 | device: path.resolve(__dirname, './database/devices1.db'),
25 | gadget: path.resolve(__dirname, './database/gadgets1.db')
26 | }}),
27 | fbWithoutNc = new Freebird([], { dbPaths: {
28 | device: path.resolve(__dirname, './database/devices2.db'),
29 | gadget: path.resolve(__dirname, './database/gadgets2.db')
30 | }});
31 |
32 | describe('freebird - Constructor Check', function () {
33 | describe('Freebird Constructor', function () {
34 | it('#No Arg', function () {
35 | expect(function () { return new Freebird(); }).to.throw(Error);
36 | // throw TypeError 'TypeError: Element of index 0 is not a valid netcore'
37 | });
38 |
39 | it('#netcores', function () {
40 | expect(function () { return new Freebird([fakeNc]); }).not.to.throw(Error);
41 | });
42 |
43 | it('#netcores, options', function () {
44 | expect(function () { return new Freebird([fakeNc], {maxDevNum: 30, maxGadNum: 10}); }).to.throw(Error);
45 | });
46 |
47 | it('Base Property Check', function () {
48 | expect(fbWithNc._netcores[0]).to.be.equal(fakeNc);
49 | // _devbox, _gadbox, _apiAgent
50 | });
51 | });
52 | });
53 |
54 | describe('freebird - Signature Check', function () {
55 | describe('#addTransport(name, transp, callback)', function () {
56 | it('should throw if name is not a string', function () {
57 | expect(function () { return fbWithNc.addTransport(10); }).to.throw(TypeError);
58 | expect(function () { return fbWithNc.addTransport([]); }).to.throw(TypeError);
59 | expect(function () { return fbWithNc.addTransport(null); }).to.throw(TypeError);
60 | expect(function () { return fbWithNc.addTransport(NaN); }).to.throw(TypeError);
61 | expect(function () { return fbWithNc.addTransport(true); }).to.throw(TypeError);
62 | expect(function () { return fbWithNc.addTransport(function () {}); }).to.throw(TypeError);
63 | });
64 |
65 | it('should not throw if name is a string', function () {
66 | expect(function () { return fbWithNc.addTransport('xxx'); }).not.to.throw(TypeError);
67 | });
68 |
69 | it('should has error if transp is not a transp', function (done) {
70 | var cb = getCheckedCb(7, done);
71 |
72 | fbWithNc.addTransport('xxx', 1, cb);
73 | fbWithNc.addTransport('xxx', 'yyy', cb);
74 | fbWithNc.addTransport('xxx', [], cb);
75 | fbWithNc.addTransport('xxx', null, cb);
76 | fbWithNc.addTransport('xxx', NaN, cb);
77 | fbWithNc.addTransport('xxx', true, cb);
78 | fbWithNc.addTransport('xxx', function () {}, cb);
79 | });
80 | });
81 |
82 | describe('#findById(type, id)', function () {
83 | it('should throw if type is unknow', function () {
84 | expect(function () { return fbWithNc.findById(1); }).to.throw(TypeError);
85 | expect(function () { return fbWithNc.findById('xxx'); }).to.throw(TypeError);
86 | expect(function () { return fbWithNc.findById([]); }).to.throw(TypeError);
87 | expect(function () { return fbWithNc.findById(null); }).to.throw(TypeError);
88 | expect(function () { return fbWithNc.findById(NaN); }).to.throw(TypeError);
89 | expect(function () { return fbWithNc.findById(true); }).to.throw(TypeError);
90 | expect(function () { return fbWithNc.findById(function () {}); }).to.throw(TypeError);
91 | });
92 |
93 | it('should throw if type is know', function () {
94 | expect(function () { return fbWithNc.findById('netcore'); }).not.to.throw(TypeError);
95 | expect(function () { return fbWithNc.findById('device'); }).not.to.throw(TypeError);
96 | expect(function () { return fbWithNc.findById('gadget'); }).not.to.throw(TypeError);
97 | });
98 |
99 | // id don't check
100 | });
101 |
102 | describe('#findByNet(type, ncName, permAddr, auxId)', function () {
103 | it('should throw if type is unknow', function () {
104 | expect(function () { return fbWithNc.findByNet(1); }).to.throw(TypeError);
105 | expect(function () { return fbWithNc.findByNet('xxx'); }).to.throw(TypeError);
106 | expect(function () { return fbWithNc.findByNet([]); }).to.throw(TypeError);
107 | expect(function () { return fbWithNc.findByNet(null); }).to.throw(TypeError);
108 | expect(function () { return fbWithNc.findByNet(NaN); }).to.throw(TypeError);
109 | expect(function () { return fbWithNc.findByNet(true); }).to.throw(TypeError);
110 | expect(function () { return fbWithNc.findByNet(function () {}); }).to.throw(TypeError);
111 | });
112 |
113 | it('should throw if type is know', function () {
114 | expect(function () { return fbWithNc.findByNet('netcore'); }).not.to.throw(TypeError);
115 | expect(function () { return fbWithNc.findByNet('device'); }).not.to.throw(TypeError);
116 | expect(function () { return fbWithNc.findByNet('gadget'); }).not.to.throw(TypeError);
117 | });
118 |
119 | // ncName, permAddr, auxId don't check
120 | });
121 |
122 | describe('#filter(type, pred)', function () {
123 | it('should throw if type is unknow', function () {
124 | expect(function () { return fbWithNc.filter(1); }).to.throw(TypeError);
125 | expect(function () { return fbWithNc.filter('xxx'); }).to.throw(TypeError);
126 | expect(function () { return fbWithNc.filter([]); }).to.throw(TypeError);
127 | expect(function () { return fbWithNc.filter(null); }).to.throw(TypeError);
128 | expect(function () { return fbWithNc.filter(NaN); }).to.throw(TypeError);
129 | expect(function () { return fbWithNc.filter(true); }).to.throw(TypeError);
130 | expect(function () { return fbWithNc.filter(function () {}); }).to.throw(TypeError);
131 | });
132 |
133 | it('should throw if pred is not a function', function () {
134 | expect(function () { return fbWithNc.filter('device', 1); }).to.throw(TypeError);
135 | expect(function () { return fbWithNc.filter('device', 'xxx'); }).to.throw(TypeError);
136 | expect(function () { return fbWithNc.filter('device', []); }).to.throw(TypeError);
137 | expect(function () { return fbWithNc.filter('device', null); }).to.throw(TypeError);
138 | expect(function () { return fbWithNc.filter('device', NaN); }).to.throw(TypeError);
139 | expect(function () { return fbWithNc.filter('device', true); }).to.throw(TypeError);
140 | });
141 |
142 | it('should not throw if type is know and pred is a function', function () {
143 | expect(function () { return fbWithNc.filter('netcore', function () {}); }).not.to.throw(TypeError);
144 | expect(function () { return fbWithNc.filter('device', function () {}); }).not.to.throw(TypeError);
145 | expect(function () { return fbWithNc.filter('gadget', function () {}); }).not.to.throw(TypeError);
146 | });
147 | });
148 |
149 | describe('#register(type, obj, callback)', function () {
150 | it('should has error if type is unknow', function (done) {
151 | var cb = getCheckedCb(7, done);
152 |
153 | fbWithNc.register(1, {}, cb);
154 | fbWithNc.register('xxx', {}, cb);
155 | fbWithNc.register([], {}, cb);
156 | fbWithNc.register(null, {}, cb);
157 | fbWithNc.register(NaN, {}, cb);
158 | fbWithNc.register(true, {}, cb);
159 | fbWithNc.register(function () {}, {}, cb);
160 | });
161 |
162 | it('should has error if obj is not a object', function (done) {
163 | var cb = getCheckedCb(7, done);
164 |
165 | fbWithNc.register('device', 1, cb);
166 | fbWithNc.register('device', 'xxx', cb);
167 | fbWithNc.register('device', [], cb);
168 | fbWithNc.register('device', null, cb);
169 | fbWithNc.register('device', NaN, cb);
170 | fbWithNc.register('device', true, cb);
171 | fbWithNc.register('device', function () {}, cb);
172 | });
173 | });
174 |
175 | describe('#unregister(type, obj, callback)', function () {
176 | it('should has error if type is unknow', function (done) {
177 | var cb = getCheckedCb(7, done);
178 |
179 | fbWithNc.unregister(1, {}, cb);
180 | fbWithNc.unregister('xxx', {}, cb);
181 | fbWithNc.unregister([], {}, cb);
182 | fbWithNc.unregister(null, {}, cb);
183 | fbWithNc.unregister(NaN, {}, cb);
184 | fbWithNc.unregister(true, {}, cb);
185 | fbWithNc.unregister(function () {}, {}, cb);
186 | });
187 |
188 | it('should has error if obj is not a object', function (done) {
189 | var cb = getCheckedCb(7, done);
190 |
191 | fbWithNc.unregister('device', 1, cb);
192 | fbWithNc.unregister('device', 'xxx', cb);
193 | fbWithNc.unregister('device', [], cb);
194 | fbWithNc.unregister('device', null, cb);
195 | fbWithNc.unregister('device', NaN, cb);
196 | fbWithNc.unregister('device', true, cb);
197 | fbWithNc.unregister('device', function () {}, cb);
198 | });
199 | });
200 |
201 | describe('#start(callback)', function () {
202 | it('should throw if callback is not a function', function () {
203 | expect(function () { return fbWithNc.start(1); }).to.throw(TypeError);
204 | expect(function () { return fbWithNc.start('xxx'); }).to.throw(TypeError);
205 | expect(function () { return fbWithNc.start([]); }).to.throw(TypeError);
206 | expect(function () { return fbWithNc.start(null); }).to.throw(TypeError);
207 | expect(function () { return fbWithNc.start(NaN); }).to.throw(TypeError);
208 | expect(function () { return fbWithNc.start(true); }).to.throw(TypeError);
209 | });
210 |
211 | it('should not throw if callback is a function', function () {
212 | expect(function () { return fbWithNc.start(function () {}); }).not.to.throw(TypeError);
213 | });
214 |
215 | it('should not throw if no callback', function () {
216 | expect(function () { return fbWithNc.start(); }).not.to.throw(Error);
217 | expect(function () { return fbWithoutNc.start(); }).not.to.throw(Error);
218 | });
219 |
220 | it('should not has error in callback then fb has nc', function (done) {
221 | fakeNc._state = 0;
222 | fbWithNc.start(function (err) {
223 | if (!err)
224 | done();
225 | });
226 | });
227 |
228 | it('should has error in callback then fb without nc', function (done) {
229 | fbWithoutNc.start(function (err) {
230 | if (err)
231 | done();
232 | });
233 | });
234 | });
235 |
236 | describe('#stop(callback)', function () {
237 | it('should throw if callback is not a function', function () {
238 | expect(function () { return fbWithNc.stop(1); }).to.throw(TypeError);
239 | expect(function () { return fbWithNc.stop('xxx'); }).to.throw(TypeError);
240 | expect(function () { return fbWithNc.stop([]); }).to.throw(TypeError);
241 | expect(function () { return fbWithNc.stop(null); }).to.throw(TypeError);
242 | expect(function () { return fbWithNc.stop(NaN); }).to.throw(TypeError);
243 | expect(function () { return fbWithNc.stop(true); }).to.throw(TypeError);
244 | });
245 |
246 | it('should not throw if callback is a function', function () {
247 | expect(function () { return fbWithNc.stop(function () {}); }).not.to.throw(TypeError);
248 | });
249 |
250 | it('should not throw if no callback', function () {
251 | expect(function () { return fbWithNc.stop(); }).not.to.throw(Error);
252 | expect(function () { return fbWithoutNc.stop(); }).not.to.throw(Error);
253 | });
254 |
255 | it('should has null in callback then fb has nc', function (done) {
256 | fbWithNc.stop(function (err) {
257 | done();
258 | });
259 | });
260 |
261 | it('should has null in callback then fb without nc', function (done) {
262 | fbWithoutNc.stop(function (err) {
263 | done();
264 | });
265 | });
266 | });
267 |
268 | describe('#reset(mode, callback)', function () {
269 | it('should throw if mode is unknow', function () {
270 | expect(function () { return fbWithNc.reset(10); }).to.throw(TypeError);
271 | expect(function () { return fbWithNc.reset('xxx'); }).to.throw(TypeError);
272 | expect(function () { return fbWithNc.reset([]); }).to.throw(TypeError);
273 | expect(function () { return fbWithNc.reset(null); }).to.throw(TypeError);
274 | expect(function () { return fbWithNc.reset(NaN); }).to.throw(TypeError);
275 | expect(function () { return fbWithNc.reset(true); }).to.throw(TypeError);
276 | expect(function () { return fbWithNc.reset(function () {}); }).to.throw(TypeError);
277 | });
278 |
279 | it('should not throw if mode is know', function () {
280 | expect(function () { return fbWithNc.reset(0); }).not.to.throw(TypeError);
281 | expect(function () { return fbWithNc.reset(1); }).not.to.throw(TypeError);
282 | });
283 |
284 | it('should throw if callback is not a function', function () {
285 | expect(function () { return fbWithNc.reset(0, 1); }).to.throw(TypeError);
286 | expect(function () { return fbWithNc.reset(0, 'xxx'); }).to.throw(TypeError);
287 | expect(function () { return fbWithNc.reset(0, []); }).to.throw(TypeError);
288 | expect(function () { return fbWithNc.reset(0, null); }).to.throw(TypeError);
289 | expect(function () { return fbWithNc.reset(0, NaN); }).to.throw(TypeError);
290 | expect(function () { return fbWithNc.reset(0, true); }).to.throw(TypeError);
291 | });
292 |
293 | it('should not throw if callback is a function', function () {
294 | expect(function () { return fbWithNc.reset(0, function () {}); }).not.to.throw(TypeError);
295 | });
296 |
297 | it('should not throw if no callback', function () {
298 | expect(function () { return fbWithNc.reset(0); }).not.to.throw(Error);
299 | expect(function () { return fbWithoutNc.reset(0); }).not.to.throw(Error);
300 | });
301 |
302 | it('should not has error in callback then fb has nc', function (done) {
303 | fbWithNc.reset(0, function (err) {
304 | if (!err)
305 | done();
306 | });
307 | });
308 |
309 | it('should has error in callback then fb without nc', function (done) {
310 | fbWithoutNc.reset(0, function (err) {
311 | if (err)
312 | done();
313 | });
314 | });
315 | });
316 |
317 | describe('#permitJoin(duration, callback)', function () {
318 | it('should throw if duration is not a number', function () {
319 | expect(function () { return fbWithNc.permitJoin('xxx'); }).to.throw(TypeError);
320 | expect(function () { return fbWithNc.permitJoin([]); }).to.throw(TypeError);
321 | expect(function () { return fbWithNc.permitJoin(null); }).to.throw(TypeError);
322 | expect(function () { return fbWithNc.permitJoin(true); }).to.throw(TypeError);
323 | expect(function () { return fbWithNc.permitJoin(function () {}); }).to.throw(TypeError);
324 | });
325 |
326 | it('should not throw if duration is a number', function () {
327 | expect(function () { return fbWithNc.permitJoin(10); }).not.to.throw(TypeError);
328 | });
329 |
330 | it('should throw if callback is not a function', function () {
331 | expect(function () { return fbWithNc.permitJoin(30, 1); }).to.throw(TypeError);
332 | expect(function () { return fbWithNc.permitJoin(30, 'xxx'); }).to.throw(TypeError);
333 | expect(function () { return fbWithNc.permitJoin(30, []); }).to.throw(TypeError);
334 | expect(function () { return fbWithNc.permitJoin(30, null); }).to.throw(TypeError);
335 | expect(function () { return fbWithNc.permitJoin(30, NaN); }).to.throw(TypeError);
336 | expect(function () { return fbWithNc.permitJoin(30, true); }).to.throw(TypeError);
337 | });
338 |
339 | it('should not throw if callback is a function', function () {
340 | expect(function () { return fbWithNc.permitJoin(30, function () {}); }).not.to.throw(TypeError);
341 | });
342 |
343 | it('should not throw if no callback', function () {
344 | expect(function () { return fbWithNc.permitJoin(30); }).not.to.throw(Error);
345 | expect(function () { return fbWithoutNc.permitJoin(30); }).not.to.throw(Error);
346 | });
347 |
348 | it('should not has error in callback then fb has nc', function (done) {
349 | fbWithNc.permitJoin(30, function (err) {
350 | if (!err)
351 | done();
352 | });
353 | });
354 |
355 | it('should has error in callback then fb without nc', function (done) {
356 | fbWithoutNc.permitJoin(30, function (err) {
357 | if (err)
358 | done();
359 | });
360 | });
361 | });
362 |
363 | describe('#maintain(ncName, callback)', function () {
364 | it('should throw if duration is not a string or null if given', function () {
365 | expect(function () { return fbWithNc.maintain(1); }).to.throw(TypeError);
366 | expect(function () { return fbWithNc.maintain([]); }).to.throw(TypeError);
367 | expect(function () { return fbWithNc.maintain(NaN); }).to.throw(TypeError);
368 | expect(function () { return fbWithNc.maintain(true); }).to.throw(TypeError);
369 | });
370 |
371 | it('should not throw if duration is a string, null or not given', function () {
372 | expect(function () { return fbWithNc.maintain(); }).not.to.throw(TypeError);
373 | expect(function () { return fbWithNc.maintain(null); }).not.to.throw(TypeError); // need to check
374 | expect(function () { return fbWithNc.maintain('fakeNc1'); }).not.to.throw(TypeError);
375 | });
376 |
377 | it('should throw if callback is not a function', function () {
378 | expect(function () { return fbWithNc.maintain('fakeNc1', 1); }).to.throw(TypeError);
379 | expect(function () { return fbWithNc.maintain('fakeNc1', 'xxx'); }).to.throw(TypeError);
380 | expect(function () { return fbWithNc.maintain('fakeNc1', []); }).to.throw(TypeError);
381 | expect(function () { return fbWithNc.maintain('fakeNc1', null); }).to.throw(TypeError);
382 | expect(function () { return fbWithNc.maintain('fakeNc1', NaN); }).to.throw(TypeError);
383 | expect(function () { return fbWithNc.maintain('fakeNc1', true); }).to.throw(TypeError);
384 | });
385 |
386 | it('should not throw if callback is a function', function () {
387 | expect(function () { return fbWithNc.maintain('fakeNc1', function () {}); }).not.to.throw(TypeError);
388 | });
389 |
390 | it('should not throw if no callback', function () {
391 | expect(function () { return fbWithNc.maintain('fakeNc1'); }).not.to.throw(Error);
392 | expect(function () { return fbWithoutNc.maintain('fakeNc1'); }).not.to.throw(Error);
393 | });
394 |
395 | it('should not has error in callback then fb has nc', function (done) {
396 | fbWithNc.maintain('fakeNc1', function (err) {
397 | if (!err)
398 | done();
399 | });
400 | });
401 |
402 | it('should has error in callback then fb without nc', function (done) {
403 | fbWithoutNc.maintain('fakeNc1', function (err) {
404 | if (err)
405 | done();
406 | });
407 | });
408 | });
409 |
410 | describe('#remove(ncName, permAddr, callback)', function () {
411 | it('should throw if ncName is not a string', function () {
412 | expect(function () { return fbWithNc.remove(10); }).to.throw(TypeError);
413 | expect(function () { return fbWithNc.remove([]); }).to.throw(TypeError);
414 | expect(function () { return fbWithNc.remove(null); }).to.throw(TypeError);
415 | expect(function () { return fbWithNc.remove(NaN); }).to.throw(TypeError);
416 | expect(function () { return fbWithNc.remove(true); }).to.throw(TypeError);
417 | expect(function () { return fbWithNc.remove(function () {}); }).to.throw(TypeError);
418 | });
419 |
420 | it('should throw if permAddr is not a string', function () {
421 | expect(function () { return fbWithNc.remove('xxx', 10); }).to.throw(TypeError);
422 | expect(function () { return fbWithNc.remove('xxx', []); }).to.throw(TypeError);
423 | expect(function () { return fbWithNc.remove('xxx', null); }).to.throw(TypeError);
424 | expect(function () { return fbWithNc.remove('xxx', NaN); }).to.throw(TypeError);
425 | expect(function () { return fbWithNc.remove('xxx', true); }).to.throw(TypeError);
426 | expect(function () { return fbWithNc.remove('xxx', function () {}); }).to.throw(TypeError);
427 | });
428 |
429 | it('should throw if callback is not a function', function () {
430 | expect(function () { return fbWithNc.remove('xxx', 'yyy', 1); }).to.throw(TypeError);
431 | expect(function () { return fbWithNc.remove('xxx', 'yyy', 'xxx'); }).to.throw(TypeError);
432 | expect(function () { return fbWithNc.remove('xxx', 'yyy', []); }).to.throw(TypeError);
433 | expect(function () { return fbWithNc.remove('xxx', 'yyy', null); }).to.throw(TypeError);
434 | expect(function () { return fbWithNc.remove('xxx', 'yyy', NaN); }).to.throw(TypeError);
435 | expect(function () { return fbWithNc.remove('xxx', 'yyy', true); }).to.throw(TypeError);
436 | });
437 |
438 | it('should not throw if ncName and permAddr is a string, callback is a function', function () {
439 | expect(function () { return fbWithNc.remove('xxx', 'yyy', function () {}); }).not.to.throw(TypeError);
440 | });
441 | });
442 |
443 | describe('#ban(ncName, permAddr, callback)', function () {
444 | it('should throw if ncName is not a string', function () {
445 | expect(function () { return fbWithNc.ban(10); }).to.throw(TypeError);
446 | expect(function () { return fbWithNc.ban([]); }).to.throw(TypeError);
447 | expect(function () { return fbWithNc.ban(null); }).to.throw(TypeError);
448 | expect(function () { return fbWithNc.ban(NaN); }).to.throw(TypeError);
449 | expect(function () { return fbWithNc.ban(true); }).to.throw(TypeError);
450 | expect(function () { return fbWithNc.ban(function () {}); }).to.throw(TypeError);
451 | });
452 |
453 | it('should throw if permAddr is not a string', function () {
454 | expect(function () { return fbWithNc.ban('xxx', 10); }).to.throw(TypeError);
455 | expect(function () { return fbWithNc.ban('xxx', []); }).to.throw(TypeError);
456 | expect(function () { return fbWithNc.ban('xxx', null); }).to.throw(TypeError);
457 | expect(function () { return fbWithNc.ban('xxx', NaN); }).to.throw(TypeError);
458 | expect(function () { return fbWithNc.ban('xxx', true); }).to.throw(TypeError);
459 | expect(function () { return fbWithNc.ban('xxx', function () {}); }).to.throw(TypeError);
460 | });
461 |
462 | it('should throw if callback is not a function', function () {
463 | expect(function () { return fbWithNc.ban('xxx', 'yyy', 1); }).to.throw(TypeError);
464 | expect(function () { return fbWithNc.ban('xxx', 'yyy', 'xxx'); }).to.throw(TypeError);
465 | expect(function () { return fbWithNc.ban('xxx', 'yyy', []); }).to.throw(TypeError);
466 | expect(function () { return fbWithNc.ban('xxx', 'yyy', null); }).to.throw(TypeError);
467 | expect(function () { return fbWithNc.ban('xxx', 'yyy', NaN); }).to.throw(TypeError);
468 | expect(function () { return fbWithNc.ban('xxx', 'yyy', true); }).to.throw(TypeError);
469 | });
470 |
471 | it('should not throw if ncName and permAddr is a string, callback is a function', function () {
472 | expect(function () { return fbWithNc.ban('xxx', 'yyy', function () {}); }).not.to.throw(TypeError);
473 | });
474 | });
475 |
476 | describe('#unban(ncName, permAddr, callback)', function () {
477 | it('should throw if ncName is not a string', function () {
478 | expect(function () { return fbWithNc.unban(10); }).to.throw(TypeError);
479 | expect(function () { return fbWithNc.unban([]); }).to.throw(TypeError);
480 | expect(function () { return fbWithNc.unban(null); }).to.throw(TypeError);
481 | expect(function () { return fbWithNc.unban(NaN); }).to.throw(TypeError);
482 | expect(function () { return fbWithNc.unban(true); }).to.throw(TypeError);
483 | expect(function () { return fbWithNc.unban(function () {}); }).to.throw(TypeError);
484 | });
485 |
486 | it('should throw if permAddr is not a string', function () {
487 | expect(function () { return fbWithNc.unban('xxx', 10); }).to.throw(TypeError);
488 | expect(function () { return fbWithNc.unban('xxx', []); }).to.throw(TypeError);
489 | expect(function () { return fbWithNc.unban('xxx', null); }).to.throw(TypeError);
490 | expect(function () { return fbWithNc.unban('xxx', NaN); }).to.throw(TypeError);
491 | expect(function () { return fbWithNc.unban('xxx', true); }).to.throw(TypeError);
492 | expect(function () { return fbWithNc.unban('xxx', function () {}); }).to.throw(TypeError);
493 | });
494 |
495 | it('should throw if callback is not a function', function () {
496 | expect(function () { return fbWithNc.unban('xxx', 'yyy', 1); }).to.throw(TypeError);
497 | expect(function () { return fbWithNc.unban('xxx', 'yyy', 'xxx'); }).to.throw(TypeError);
498 | expect(function () { return fbWithNc.unban('xxx', 'yyy', []); }).to.throw(TypeError);
499 | expect(function () { return fbWithNc.unban('xxx', 'yyy', null); }).to.throw(TypeError);
500 | expect(function () { return fbWithNc.unban('xxx', 'yyy', NaN); }).to.throw(TypeError);
501 | expect(function () { return fbWithNc.unban('xxx', 'yyy', true); }).to.throw(TypeError);
502 | });
503 |
504 | it('should not throw if ncName and permAddr is a string, callback is a function', function () {
505 | expect(function () { return fbWithNc.unban('xxx', 'yyy', function () {}); }).not.to.throw(TypeError);
506 | });
507 | });
508 |
509 | describe('#ping(ncName, permAddr, callback)', function () {
510 | it('should throw if ncName is not a string', function () {
511 | expect(function () { return fbWithNc.ping(10); }).to.throw(TypeError);
512 | expect(function () { return fbWithNc.ping([]); }).to.throw(TypeError);
513 | expect(function () { return fbWithNc.ping(null); }).to.throw(TypeError);
514 | expect(function () { return fbWithNc.ping(NaN); }).to.throw(TypeError);
515 | expect(function () { return fbWithNc.ping(true); }).to.throw(TypeError);
516 | expect(function () { return fbWithNc.ping(function () {}); }).to.throw(TypeError);
517 | });
518 |
519 | it('should throw if permAddr is not a string', function () {
520 | expect(function () { return fbWithNc.ping('xxx', 10); }).to.throw(TypeError);
521 | expect(function () { return fbWithNc.ping('xxx', []); }).to.throw(TypeError);
522 | expect(function () { return fbWithNc.ping('xxx', null); }).to.throw(TypeError);
523 | expect(function () { return fbWithNc.ping('xxx', NaN); }).to.throw(TypeError);
524 | expect(function () { return fbWithNc.ping('xxx', true); }).to.throw(TypeError);
525 | expect(function () { return fbWithNc.ping('xxx', function () {}); }).to.throw(TypeError);
526 | });
527 |
528 | it('should throw if callback is not a function', function () {
529 | expect(function () { return fbWithNc.ping('xxx', 'yyy', 1); }).to.throw(TypeError);
530 | expect(function () { return fbWithNc.ping('xxx', 'yyy', 'xxx'); }).to.throw(TypeError);
531 | expect(function () { return fbWithNc.ping('xxx', 'yyy', []); }).to.throw(TypeError);
532 | expect(function () { return fbWithNc.ping('xxx', 'yyy', null); }).to.throw(TypeError);
533 | expect(function () { return fbWithNc.ping('xxx', 'yyy', NaN); }).to.throw(TypeError);
534 | expect(function () { return fbWithNc.ping('xxx', 'yyy', true); }).to.throw(TypeError);
535 | });
536 |
537 | it('should not throw if ncName and permAddr is a string, callback is a function', function () {
538 | expect(function () { return fbWithNc.ping('xxx', 'yyy', function () {}); }).not.to.throw(TypeError);
539 | });
540 |
541 | after(function (done) {
542 | try {
543 | fs.unlink(path.resolve(__dirname, './database/devices1.db'));
544 | fs.unlink(path.resolve(__dirname, './database/gadgets1.db'));
545 | fs.unlink(path.resolve(__dirname, './database/devices2.db'));
546 | fs.unlink(path.resolve(__dirname, './database/gadgets2.db'));
547 | } catch (e) {
548 | console.log(e);
549 | }
550 |
551 | done();
552 | });
553 | });
554 | });
555 |
556 | function getCheckedCb (times, done) {
557 | var checkNum = 0,
558 | cbCalled = false;
559 |
560 | return function (err) {
561 | if (err && (err instanceof TypeError))
562 | checkNum += 1;
563 |
564 | if (cbCalled) {
565 | done();
566 | }
567 |
568 | if (checkNum === times) {
569 | cbCalled = true;
570 | done();
571 | }
572 | };
573 | }
574 |
--------------------------------------------------------------------------------
/lib/rpc/apis.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | var _ = require('busyman'),
4 | RPC = require('freebird-constants').RPC;
5 |
6 | var utils = require('../utils/utils.js'),
7 | validate = require('../utils/validate.js');
8 |
9 | var apis = {};
10 |
11 | /**********************************************************************************/
12 | /*** APIs for Remote Access: Must bind 'this' to freebird while calling ***/
13 | /**********************************************************************************/
14 | apis.getAllDevIds = function (args, callback) { // args = { [ncName:String] }
15 | var nc, ids,
16 | typeErr = _.isNil(args.ncName) ? undefined : validate.ncNameTypeError(args.ncName);
17 |
18 | if (typeErr)
19 | return setImmediate(callback, typeErr, { id: 0, ids: null, status: RPC.Status.BadRequest });
20 |
21 | if (_.isNil(args.ncName))
22 | ids = this._devbox.exportAllIds();
23 | else if (!(nc = this.findByNet('netcore', args.ncName)))
24 | return setImmediate(callback, new Error('netcore not found'), { id: 0, ids: null, status: RPC.Status.NotFound });
25 | else
26 | ids = this._devbox.filter(function (dev) {
27 | return dev.get('netcore') === nc;
28 | }).map(function (dev) {
29 | return dev.get('id');
30 | });
31 |
32 | setImmediate(callback, null, { id: 0, ids: ids, status: RPC.Status.Content });
33 | }; // return { id: 0, ids: [ 1, 2, 3, 8, 12 ], status: 205 }
34 |
35 | apis.getAllGadIds = function (args, callback) { // args = { [ncName:String] }
36 | var nc, ids,
37 | typeErr = _.isNil(args.ncName) ? undefined : validate.ncNameTypeError(args.ncName);
38 |
39 | if (typeErr)
40 | return setImmediate(callback, typeErr, { id: 0, ids: null, status: RPC.Status.BadRequest });
41 |
42 | if (_.isNil(args.ncName))
43 | ids = this._gadbox.exportAllIds();
44 | else if (!(nc = this.findByNet('netcore', args.ncName)))
45 | return setImmediate(callback, new Error('netcore not found'), { id: 0, ids: null, status: RPC.Status.NotFound });
46 | else
47 | ids = this._gadbox.filter(function (gad) {
48 | return gad.get('netcore') === nc;
49 | }).map(function (gad) {
50 | return gad.get('id');
51 | });
52 |
53 | setImmediate(callback, null, { id: 0, ids: ids, status: RPC.Status.Content });
54 | }; // return { id: 0, ids: [ 2, 3, 5, 11, 12, 13, 14, 15 ], status: 205 }
55 |
56 | apis.getDevs = function (args, callback) { // args = { ids:Number[] }
57 | var devs, typeErr,
58 | self = this;
59 |
60 | if (typeErr = validate.idsTypeError(args.ids))
61 | return setImmediate(callback, typeErr, { id: 0, devs: null, status: RPC.Status.BadRequest });
62 |
63 | devs = (args.ids).map(function (id) {
64 | var dev = self.findById('device', id);
65 | return dev ? utils.dumpDeviceInfo(dev) : null;
66 | });
67 |
68 | return setImmediate(callback, null, { id: 0, devs: devs, status: RPC.Status.Content });
69 | }; // return { id: 0, devs: [ devInfo, ... ], status: 205 }
70 |
71 | apis.getGads = function (args, callback) { // args = { ids:Number[] }
72 | var gads, typeErr,
73 | self = this;
74 |
75 | if (typeErr = validate.idsTypeError(args.ids))
76 | return setImmediate(callback, typeErr, { id: 0, gads: null, status: RPC.Status.BadRequest });
77 |
78 | gads = (args.ids).map(function (id) {
79 | var gad = self.findById('gadget', id);
80 | return gad ? utils.dumpGadgetInfo(gad) : null;
81 | });
82 |
83 | return setImmediate(callback, null, { id: 0, gads: gads, status: RPC.Status.Content });
84 | }; // return { id: 0, gads: [ gadInfo , ... ], status: 205 }
85 |
86 | apis.getNetcores = function (args, callback) { // args = { [ncNames:String[]] }
87 | var netcores,
88 | self = this,
89 | ncNames = args.ncNames,
90 | typeErr = _.isNil(ncNames) ? undefined : validate.ncNamesTypeError(ncNames);
91 |
92 | if (typeErr)
93 | return setImmediate(callback, typeErr, { id: 0, netcores: null, status: RPC.Status.BadRequest });
94 |
95 | if (!ncNames)
96 | ncNames = this._netcores.map(function (nc) {
97 | return nc.getName();
98 | });
99 |
100 | netcores = ncNames.map(function (name) {
101 | var nc = self.findByNet('netcore', name);
102 | return nc ? utils.dumpNetcoreInfo(nc) : null;
103 | });
104 |
105 | setImmediate(callback, null, { id: 0, netcores: netcores, status: RPC.Status.Content });
106 | }; // return { id: 0, netcores: [ ncInfo, ... ], status: 205 }
107 |
108 | apis.getBlacklist = function (args, callback) { // args = { ncName:String }
109 | var nc, typeErr;
110 |
111 | if (typeErr = validate.ncNameTypeError(args.ncName))
112 | return setImmediate(callback, typeErr, { id: 0, list: null, status: RPC.Status.BadRequest });
113 | else if (!(nc = this.findByNet('netcore', args.ncName)))
114 | return setImmediate(callback, new Error('netcore not found'), { id: 0, list: null, status: RPC.Status.NotFound });
115 |
116 | try {
117 | var blist = nc.getBlacklist();
118 | return setImmediate(callback, null, { id: 0, list: blist, status: RPC.Status.Content });
119 | } catch (e) {
120 | return setImmediate(callback, e, { id: 0, list: null, status: RPC.Status.InternalServerError });
121 | }
122 | }; // return { id: 0, list: [ '0x00124b0001ce4b89', ... ], status }
123 |
124 | apis.permitJoin = function (args, callback) { // args = { [ncName:String], duration:Number }
125 | var nc,
126 | ncNum,
127 | typeErr = _.isNil(args.ncName) ? undefined : validate.ncNameTypeError(args.ncName);
128 |
129 | if (typeErr = typeErr || validate.durationTypeError(args.duration))
130 | return setImmediate(callback, typeErr, { id: 0, status: RPC.Status.BadRequest });
131 |
132 | if (_.isString(args.ncName)) {
133 | if (!(nc = this.findByNet('netcore', args.ncName)))
134 | return setImmediate(callback, new Error('netcore not found'), { id: 0, status: RPC.Status.NotFound });
135 |
136 | nc.permitJoin(args.duration, function (err) {
137 | if (err)
138 | setImmediate(callback, err, { id: 0, status: RPC.Status.InternalServerError });
139 | else
140 | setImmediate(callback, null, { id: 0, status: RPC.Status.Ok });
141 | });
142 | } else {
143 | this.permitJoin(args.duration, function (err) {
144 | if (err)
145 | setImmediate(callback, err, { id: 0, status: RPC.Status.InternalServerError });
146 | else
147 | setImmediate(callback, null, { id: 0, status: RPC.Status.Ok });
148 | });
149 | }
150 | }; // return { id: 0, status }
151 |
152 | apis.reset = function (args, callback) { // args = { [ncName:String], mode:Number } => mode: 0(SOFT)/1(HARD)
153 | var nc, ncNum,
154 | typeErr = _.isNil(args.ncName) ? undefined : (validate.ncNameTypeError(args.ncName) || validate.modeTypeError(args.mode));
155 |
156 | if (typeErr)
157 | return setImmediate(callback, typeErr, { id: 0, status: RPC.Status.BadRequest });
158 |
159 | if (_.isString(args.ncName)) {
160 | if (!(nc = this.findByNet('netcore', args.ncName)))
161 | return setImmediate(callback, new Error('netcore not found'), { id: 0, status: RPC.Status.NotFound });
162 |
163 | nc.reset(args.mode, function (err) {
164 | if (err)
165 | setImmediate(callback, err, { id: 0, status: RPC.Status.InternalServerError });
166 | else
167 | setImmediate(callback, null, { id: 0, status: RPC.Status.Ok });
168 | });
169 | } else {
170 | this.reset(args.mode, function (err) {
171 | if (err)
172 | setImmediate(callback, err, { id: 0, status: RPC.Status.InternalServerError });
173 | else
174 | setImmediate(callback, null, { id: 0, status: RPC.Status.Ok });
175 | });
176 | }
177 | }; // return { id: 0, status }
178 |
179 | apis.enable = function (args, callback) { // args = { [ncName:String] }
180 | var self = this,
181 | nc, ncNum,
182 | typeErr = _.isNil(args.ncName) ? undefined : validate.ncNameTypeError(args.ncName);
183 |
184 | if (typeErr)
185 | return setImmediate(callback, typeErr, { id: 0, status: RPC.Status.BadRequest });
186 |
187 | if (_.isString(args.ncName)) {
188 | if (!(nc = this.findByNet('netcore', args.ncName)))
189 | return setImmediate(callback, new Error('netcore not found'), { id: 0, status: RPC.Status.NotFound });
190 |
191 | try {
192 | nc.enable();
193 | return setImmediate(callback, null, { id: 0, status: RPC.Status.Ok });
194 | } catch (e) {
195 | return setImmediate(callback, e, { id: 0, status: RPC.Status.InternalServerError });
196 | }
197 | } else {
198 | ncNum = this._netcores.length;
199 | _.forEach(this._netcores, function (netcore) {
200 | try {
201 | netcore.enable();
202 | } catch (e) {
203 | self._fire('warn', e);
204 | } finally {
205 | ncNum -= 1;
206 | if (ncNum === 0)
207 | setImmediate(callback, null, { id: 0, status: RPC.Status.Ok });
208 | }
209 | });
210 | }
211 | }; // return { id: 0, status }
212 |
213 | apis.disable = function (args, callback) { // args = { [ncName:String] }
214 | var self = this,
215 | nc, ncNum,
216 | typeErr = _.isNil(args.ncName) ? undefined : validate.ncNameTypeError(args.ncName);
217 |
218 | if (typeErr)
219 | return setImmediate(callback, typeErr, { id: 0, status: RPC.Status.BadRequest });
220 |
221 | if (_.isString(args.ncName)) {
222 | if (!(nc = this.findByNet('netcore', args.ncName)))
223 | return setImmediate(callback, new Error('netcore not found'), { id: 0, status: RPC.Status.NotFound });
224 |
225 | try {
226 | nc.disable();
227 | return setImmediate(callback, null, { id: 0, status: RPC.Status.Ok });
228 | } catch (e) {
229 | return setImmediate(callback, e, { id: 0, status: RPC.Status.InternalServerError });
230 | }
231 | } else {
232 | ncNum = this._netcores.length;
233 | _.forEach(this._netcores, function (netcore) {
234 | try {
235 | netcore.disable();
236 | } catch (e) {
237 | self._fire('warn', e);
238 | } finally {
239 | ncNum -= 1;
240 | if (ncNum === 0)
241 | setImmediate(callback, null, { id: 0, status: RPC.Status.Ok });
242 | }
243 | });
244 | }
245 | }; // return { id: 0, status }
246 |
247 | apis.ban = function (args, callback) { // args = { ncName:String, permAddr:String }
248 | var nc, typeErr;
249 |
250 | if (typeErr = validate.ncNameTypeError(args.ncName) || validate.permAddrTypeError(args.permAddr))
251 | return setImmediate(callback, typeErr, { id: 0, permAddr: null, status: RPC.Status.BadRequest });
252 | else if (!(nc = this.findByNet('netcore', args.ncName)))
253 | return setImmediate(callback, new Error('netcore not found'), { id: 0, permAddr: null, status: RPC.Status.NotFound });
254 |
255 | nc.ban(args.permAddr, function (err, permAddr) {
256 | if (err)
257 | setImmediate(callback, err, { id: 0, permAddr: null, status: RPC.Status.InternalServerError });
258 | else
259 | setImmediate(callback, null, { id: 0, permAddr: permAddr, status: RPC.Status.Ok });
260 | });
261 | }; // return { id: 0, permAddr: '0x00124b0001ce4b89', status }
262 |
263 | apis.unban = function (args, callback) { // args = { ncName:String, permAddr:String }
264 | var nc, typeErr;
265 |
266 | if (typeErr = validate.ncNameTypeError(args.ncName) || validate.permAddrTypeError(args.permAddr))
267 | return setImmediate(callback, typeErr, { id: 0, permAddr: null, status: RPC.Status.BadRequest });
268 | else if (!(nc = this.findByNet('netcore', args.ncName)))
269 | return setImmediate(callback, new Error('netcore not found'), { id: 0, permAddr: null, status: RPC.Status.NotFound });
270 |
271 | nc.unban(args.permAddr, function (err, permAddr) {
272 | if (err)
273 | setImmediate(callback, err, { id: 0, permAddr: null, status: RPC.Status.InternalServerError });
274 | else
275 | setImmediate(callback, null, { id: 0, permAddr: permAddr, status: RPC.Status.Ok });
276 | });
277 | }; // return { id: 0, permAddr: '0x00124b0001ce4b89', status }
278 |
279 | apis.remove = function (args, callback) { // args = { ncName, permAddr }
280 | var nc, dev, typeErr;
281 |
282 | if (typeErr = validate.ncNameTypeError(args.ncName) || validate.permAddrTypeError(args.permAddr))
283 | return setImmediate(callback, typeErr, { id: 0, permAddr: null, status: RPC.Status.BadRequest });
284 | else if (!(nc = this.findByNet('netcore', args.ncName)))
285 | return setImmediate(callback, new Error('netcore not found'), { id: 0, permAddr: null, status: RPC.Status.NotFound });
286 | else if (!(dev = this.findByNet('device', args.ncName, args.permAddr)))
287 | return setImmediate(callback, new Error('device not found'), { id: 0, permAddr: null, status: RPC.Status.NotFound });
288 |
289 | nc.remove(args.permAddr, function (err, permAddr) {
290 | if (err)
291 | setImmediate(callback, err, { id: 0, permAddr: null, status: RPC.Status.InternalServerError });
292 | else
293 | setImmediate(callback, null, { id: 0, permAddr: permAddr, status: RPC.Status.Deleted });
294 | });
295 | }; // return { id: 0, permAddr: '0x00124b0001ce4b89', status }
296 |
297 | apis.ping = function (args, callback) { // args = { ncName, permAddr }
298 | var nc, dev, typeErr;
299 |
300 | if (typeErr = validate.ncNameTypeError(args.ncName) || validate.permAddrTypeError(args.permAddr))
301 | return setImmediate(callback, typeErr, { id: 0, time: null, status: RPC.Status.BadRequest });
302 | else if (!(nc = this.findByNet('netcore', args.ncName)))
303 | return setImmediate(callback, new Error('netcore not found'), { id: 0, time: null, status: RPC.Status.NotFound });
304 | else if (!(dev = this.findByNet('device', args.ncName, args.permAddr)))
305 | return setImmediate(callback, new Error('device not found'), { id: 0, time: null, status: RPC.Status.NotFound });
306 |
307 | dev.ping(function (err, time) {
308 | if (err)
309 | setImmediate(callback, err, { id: 0, time: null, status: RPC.Status.InternalServerError });
310 | else
311 | setImmediate(callback, null, { id: 0, time: time, status: RPC.Status.Content });
312 | });
313 | }; // return { id, time: 12, status }
314 |
315 | apis.maintain = function (args, callback) { // args = { [ncName:String] }
316 | var nc,
317 | typeErr = _.isNil(args.ncName) ? undefined : validate.ncNameTypeError(args.ncName);
318 |
319 | if (typeErr)
320 | return setImmediate(callback, typeErr, { id: 0, status: RPC.Status.BadRequest });
321 | else if (!_.isNil(args.ncName) && !(nc = this.findByNet('netcore', args.ncName)))
322 | return setImmediate(callback, new Error('netcore not found'), { id: 0, status: RPC.Status.NotFound });
323 |
324 | this.maintain(args.ncName, function (err) {
325 | if (err)
326 | setImmediate(callback, err, { id: 0, status: RPC.Status.InternalServerError });
327 | else
328 | setImmediate(callback, null, { id: 0, status: RPC.Status.Ok });
329 | });
330 | }; // return { id: 0, status }
331 |
332 | /*************************************************************************************************/
333 | /*** Device APIs ***/
334 | /*************************************************************************************************/
335 | apis.devEnable = function (args, callback) { // args = { id:Number }
336 | var dev, typeErr;
337 |
338 | if (typeErr = validate.idTypeError(args.id))
339 | return setImmediate(callback, typeErr, { id: args.id, enabled: null, status: RPC.Status.BadRequest });
340 | else if (!(dev = this.findById('device', args.id)))
341 | return setImmediate(callback, new Error('device not found'), { id: args.id, enabled: null, status: RPC.Status.NotFound });
342 |
343 | try {
344 | dev.enable();
345 | return setImmediate(callback, null, { id: args.id, enabled: dev.isEnabled(), status: RPC.Status.Ok });
346 | } catch (e) {
347 | return setImmediate(callback, e, { id: args.id, enabled: null, status: RPC.Status.InternalServerError });
348 | }
349 | }; // return { id, enabled: true, status }
350 |
351 | apis.devDisable = function (args, callback) { // args = { id:Number }
352 | var dev, typeErr;
353 |
354 | if (typeErr = validate.idTypeError(args.id))
355 | return setImmediate(callback, typeErr, { id: args.id, enabled: null, status: RPC.Status.BadRequest });
356 | else if (!(dev = this.findById('device', args.id)))
357 | return setImmediate(callback, new Error('device not found'), { id: args.id, enabled: null, status: RPC.Status.NotFound });
358 |
359 | try {
360 | dev.disable();
361 | return setImmediate(callback, null, { id: args.id, enabled: dev.isEnabled(), status: RPC.Status.Ok });
362 | } catch (e) {
363 | return setImmediate(callback, e, { id: args.id, enabled: null, status: RPC.Status.InternalServerError });
364 | }
365 | }; // return { id, enabled: false, status }
366 |
367 | apis.devRead = function (args, callback) { // args = { id:Number, attrName:String }
368 | var dev, typeErr;
369 |
370 | if (typeErr = validate.idTypeError(args.id) || validate.attrNameTypeError(args.attrName))
371 | return setImmediate(callback, typeErr, { id: args.id, value: null, status: RPC.Status.BadRequest });
372 | else if (!(dev = this.findById('device', args.id)))
373 | return setImmediate(callback, new Error('device not found'), { id: args.id, value: null, status: RPC.Status.NotFound });
374 |
375 | dev.read(args.attrName, function (err, val) {
376 | if (err)
377 | setImmediate(callback, err, { id: args.id, value: null, status: RPC.Status.InternalServerError });
378 | else
379 | setImmediate(callback, null, { id: args.id, value: val, status: RPC.Status.Content });
380 | });
381 | }; // return { id, value: 3, status }
382 |
383 | apis.devWrite = function (args, callback) { // args = { id:Number, attrName:String, value:Any }
384 | var dev, typeErr;
385 |
386 | if (typeErr = validate.idTypeError(args.id) || validate.attrNameTypeError(args.attrName))
387 | return setImmediate(callback, typeErr, { id: args.id, value: null, status: RPC.Status.BadRequest });
388 | else if (!(dev = this.findById('device', args.id)))
389 | return setImmediate(callback, new Error('device not found'), { id: args.id, value: null, status: RPC.Status.NotFound });
390 |
391 | dev.write(args.attrName, args.value, function (err, val) {
392 | if (err)
393 | setImmediate(callback, err, { id: args.id, value: null, status: RPC.Status.InternalServerError });
394 | else
395 | setImmediate(callback, null, { id: args.id, value: val, status: RPC.Status.Changed });
396 | });
397 | }; // return { id, value: 'kitchen', status }
398 |
399 | apis.devIdentify = function (args, callback) { // args = { id:Number }
400 | var dev, typeErr;
401 |
402 | if (typeErr = validate.idTypeError(args.id))
403 | return setImmediate(callback, typeErr, { id: args.id, status: RPC.Status.BadRequest });
404 | else if (!(dev = this.findById('device', args.id)))
405 | return setImmediate(callback, new Error('device not found'), { id: args.id, status: RPC.Status.NotFound });
406 |
407 | dev.identify(function (err) {
408 | if (err)
409 | setImmediate(callback, err, { id: args.id, status: RPC.Status.InternalServerError });
410 | else
411 | setImmediate(callback, null, { id: args.id, status: RPC.Status.Ok });
412 | });
413 | }; // return { id, status }
414 |
415 | apis.devPing = function (args, callback) { // args = { id:Number }
416 | var dev, typeErr;
417 |
418 | if (typeErr = validate.idTypeError(args.id))
419 | return setImmediate(callback, typeErr, { id: args.id, time: null, status: RPC.Status.BadRequest });
420 | else if (!(dev = this.findById('device', args.id)))
421 | return setImmediate(callback, new Error('device not found'), { id: args.id, time: null, status: RPC.Status.NotFound });
422 |
423 | dev.ping(function (err, time) {
424 | if (err)
425 | setImmediate(callback, err, { id: args.id, time: null, status: RPC.Status.InternalServerError });
426 | else
427 | setImmediate(callback, null, { id: args.id, time: time, status: RPC.Status.Content });
428 | });
429 | }; // return { id, time: 12, status }
430 |
431 | apis.devRemove = function (args, callback) { // args = { id:Number }
432 | var nc, dev, typeErr;
433 |
434 | if (typeErr = validate.idTypeError(args.id))
435 | return setImmediate(callback, typeErr, { id: args.id, permAddr: null, status: RPC.Status.BadRequest });
436 | else if (!(dev = this.findById('device', args.id)))
437 | return setImmediate(callback, new Error('device not found'), { id: args.id, permAddr: null, status: RPC.Status.NotFound });
438 | else if (!(nc = dev.get('netcore')))
439 | return setImmediate(callback, new Error('netcore not found'), { id: args.id, permAddr: null, status: RPC.Status.NotFound });
440 |
441 | nc.remove(dev.get('permAddr'), function (err, permAddr) {
442 | if (err)
443 | setImmediate(callback, err, { id: args.id, permAddr: null, status: RPC.Status.InternalServerError });
444 | else
445 | setImmediate(callback, null, { id: args.id, permAddr: permAddr, status: RPC.Status.Deleted });
446 | });
447 | };
448 |
449 | apis.devGetProps = function (args, callback) { // args = { id:Number, [propNames:String[]] }
450 | var dev, props, typeErr;
451 |
452 | if (typeErr = validate.idTypeError(args.id) || validate.propNamesTypeError(args.propNames))
453 | return setImmediate(callback, typeErr, { id: args.id, props: null, status: RPC.Status.BadRequest });
454 | else if (!(dev = this.findById('device', args.id)))
455 | return setImmediate(callback, new Error('device not found'), { id: args.id, props: null, status: RPC.Status.NotFound });
456 |
457 | try {
458 | props = dev.get('props', args.propNames);
459 | return setImmediate(callback, null, { id: args.id, props: props, status: RPC.Status.Content });
460 | } catch (e) {
461 | return setImmediate(callback, e, { id: args.id, props: null, status: RPC.Status.InternalServerError });
462 | }
463 | }; // return { id, props: { name: 'xxx', location: 'xxx' }, status }
464 |
465 | apis.devSetProps = function (args, callback) { // args = { id:Number, props:Object }
466 | var dev, typeErr;
467 |
468 | if (typeErr = validate.idTypeError(args.id) || validate.propsTypeError(args.props))
469 | return setImmediate(callback, typeErr, { id: args.id, status: RPC.Status.BadRequest });
470 | else if (!(dev = this.findById('device', args.id)))
471 | return setImmediate(callback, new Error('device not found'), { id: args.id, status: RPC.Status.NotFound });
472 |
473 | try {
474 | dev.set('props', args.props);
475 | return setImmediate(callback, null, { id: args.id, status: RPC.Status.Changed });
476 | } catch (e) {
477 | return setImmediate(callback, e, { id: args.id, status: RPC.Status.InternalServerError });
478 | }
479 | }; // return { id, status }
480 |
481 | /*************************************************************************************************/
482 | /*** Gadget APIs ***/
483 | /*************************************************************************************************/
484 | apis.gadEnable = function (args, callback) { // args = { id:Number }
485 | var gad, typeErr;
486 |
487 | if (typeErr = validate.idTypeError(args.id))
488 | return setImmediate(callback, typeErr, { id: args.id, enabled: null, status: RPC.Status.BadRequest });
489 | else if (!(gad = this.findById('gadget', args.id)))
490 | return setImmediate(callback, new Error('gadget not found'), { id: args.id, enabled: null, status: RPC.Status.NotFound });
491 |
492 | try {
493 | gad.enable();
494 | return setImmediate(callback, null, { id: args.id, enabled: gad.isEnabled(), status: RPC.Status.Ok });
495 | } catch (e) {
496 | return setImmediate(callback, e, { id: args.id, enabled: null, status: RPC.Status.InternalServerError });
497 | }
498 | }; // return { id, enabled: true, status }
499 |
500 | apis.gadDisable = function (args, callback) { // args = { id:Number }
501 | var gad, typeErr;
502 |
503 | if (typeErr = validate.idTypeError(args.id))
504 | return setImmediate(callback, typeErr, { id: args.id, enabled: null, status: RPC.Status.BadRequest });
505 | else if (!(gad = this.findById('gadget', args.id)))
506 | return setImmediate(callback, new Error('gadget not found'), { id: args.id, enabled: null, status: RPC.Status.NotFound });
507 |
508 | try {
509 | gad.disable();
510 | return setImmediate(callback, null, { id: args.id, enabled: gad.isEnabled(), status: RPC.Status.Ok });
511 | } catch (e) {
512 | return setImmediate(callback, e, { id: args.id, enabled: null, status: RPC.Status.InternalServerError });
513 | }
514 | }; // return { id, enabled: false, status }
515 |
516 | apis.gadRead = function (args, callback) { // args = { id:Number, attrName:String }
517 | var gad, typeErr;
518 |
519 | if (typeErr = validate.idTypeError(args.id) || validate.attrNameTypeError(args.attrName))
520 | return setImmediate(callback, typeErr, { id: args.id, value: null, status: RPC.Status.BadRequest });
521 | else if (!(gad = this.findById('gadget', args.id)))
522 | return setImmediate(callback, new Error('gadget not found'), { id: args.id, value: null, status: RPC.Status.NotFound });
523 |
524 | gad.read(args.attrName, function (err, val) {
525 | if (err)
526 | setImmediate(callback, err, { id: args.id, value: null, status: RPC.Status.InternalServerError });
527 | else
528 | setImmediate(callback, null, { id: args.id, value: val, status: RPC.Status.Content });
529 | });
530 | }; // return { id, value: 371.42, status }
531 |
532 | apis.gadWrite = function (args, callback) { // args = { id:Number, attrName:String, value:Any }
533 | var gad, typeErr;
534 |
535 | if (typeErr = validate.idTypeError(args.id) || validate.attrNameTypeError(args.attrName))
536 | return setImmediate(callback, typeErr, { id: args.id, value: null, status: RPC.Status.BadRequest });
537 | else if (!(gad = this.findById('gadget', args.id)))
538 | return setImmediate(callback, new Error('gadget not found'), { id: args.id, value: null, status: RPC.Status.NotFound });
539 |
540 | gad.write(args.attrName, args.value, function (err, val) {
541 | if (err)
542 | setImmediate(callback, err, { id: args.id, value: null, status: RPC.Status.InternalServerError });
543 | else
544 | setImmediate(callback, null, { id: args.id, value: val, status: RPC.Status.Changed });
545 | });
546 | }; // return { id, value: false, status }
547 |
548 | apis.gadExec = function (args, callback) { // args = { id:Number, attrName:String[, params:Any[]] }
549 | var gad, typeErr,
550 | params = args.params || [];
551 |
552 | if (typeErr = validate.idTypeError(args.id) || validate.attrNameTypeError(args.attrName) || validate.paramsTypeError(params))
553 | return setImmediate(callback, typeErr, { id: args.id, result: null, status: RPC.Status.BadRequest });
554 | else if (!(gad = this.findById('gadget', args.id)))
555 | return setImmediate(callback, new Error('gadget not found'), { id: args.id, result: null, status: RPC.Status.NotFound });
556 |
557 | gad.exec(args.attrName, params, function (err, result) {
558 | if (err)
559 | setImmediate(callback, err, { id: args.id, result: null, status: RPC.Status.InternalServerError });
560 | else
561 | setImmediate(callback, null, { id: args.id, result: result, status: RPC.Status.Ok });
562 | });
563 | }; // return { id, result: 'completed', status }
564 |
565 | apis.gadWriteReportCfg = function (args, callback) { // args = { id:Number, attrName:String, rptCfg:Object }
566 | var gad, typeErr;
567 |
568 | if (typeErr = validate.idTypeError(args.id) || validate.attrNameTypeError(args.attrName) || validate.rptCfgTypeError(args.rptCfg))
569 | return setImmediate(callback, typeErr, { id: args.id, result: null, status: RPC.Status.BadRequest });
570 | else if (!(gad = this.findById('gadget', args.id)))
571 | return setImmediate(callback, new Error('gadget not found'), { id: args.id, result: null, status: RPC.Status.NotFound });
572 |
573 | gad.writeReportCfg(args.attrName, args.rptCfg, function (err, result) {
574 | if (err)
575 | setImmediate(callback, err, { id: args.id, result: null, status: RPC.Status.InternalServerError });
576 | else
577 | setImmediate(callback, null, { id: args.id, result: result, status: RPC.Status.Changed });
578 | });
579 | }; // return { id, status }
580 |
581 | apis.gadReadReportCfg = function (args, callback) { // args = { id:Number, attrName:String }
582 | var gad, typeErr;
583 |
584 | if (typeErr = validate.idTypeError(args.id) || validate.attrNameTypeError(args.attrName))
585 | return setImmediate(callback, typeErr, { id: args.id, cfg: null, status: RPC.Status.BadRequest });
586 | else if (!(gad = this.findById('gadget', args.id)))
587 | return setImmediate(callback, new Error('gadget not found'), { id: args.id, cfg: null, status: RPC.Status.NotFound });
588 |
589 | gad.readReportCfg(args.attrName, function (err, cfg) {
590 | if (err)
591 | setImmediate(callback, err, { id: args.id, cfg: null, status: RPC.Status.InternalServerError });
592 | else
593 | setImmediate(callback, null, { id: args.id, cfg: cfg, status: RPC.Status.Content });
594 | });
595 | }; // return { id, cfg: rptCfg, status }
596 |
597 | apis.gadGetProps = function (args, callback) { // args = { id:Number, [propNames:String[]] }
598 | var gad, props, typeErr;
599 |
600 | if (typeErr = validate.idTypeError(args.id) || validate.propNamesTypeError(args.propNames))
601 | return setImmediate(callback, typeErr, { id: args.id, props: null, status: RPC.Status.BadRequest });
602 | else if (!(gad = this.findById('gadget', args.id)))
603 | return setImmediate(callback, new Error('gadget not found'), { id: args.id, props: null, status: RPC.Status.NotFound });
604 |
605 | try {
606 | props = gad.get('props', args.propNames);
607 | return setImmediate(callback, null, { id: args.id, props: props, status: RPC.Status.Content });
608 | } catch (e) {
609 | return setImmediate(callback, e, { id: args.id, props: null, status: RPC.Status.InternalServerError });
610 | }
611 | }; // return { id, props: { name: 'xxx' }, status }
612 |
613 | apis.gadSetProps = function (args, callback) { // args = { id:Number, props:Object }
614 | var gad, typeErr;
615 |
616 | if (typeErr = validate.idTypeError(args.id) || validate.propsTypeError(args.props))
617 | return setImmediate(callback, typeErr, { id: args.id, status: RPC.Status.BadRequest });
618 | else if (!(gad = this.findById('gadget', args.id)))
619 | return setImmediate(callback, new Error('gadget not found'), { id: args.id, status: RPC.Status.NotFound });
620 |
621 | try {
622 | gad.set('props', args.props);
623 | return setImmediate(callback, null, { id: args.id, status: RPC.Status.Changed });
624 | } catch (e) {
625 | return setImmediate(callback, e, { id: args.id, status: RPC.Status.InternalServerError });
626 | }
627 | }; // return { id, status }
628 |
629 | module.exports = apis;
630 |
--------------------------------------------------------------------------------
/test/freebird.functional.test.js:
--------------------------------------------------------------------------------
1 | var fs = require('fs'),
2 | path = require('path');
3 |
4 | var _ = require('busyman'),
5 | chai = require('chai'),
6 | sinon = require('sinon'),
7 | sinonChai = require('sinon-chai'),
8 | expect = chai.expect,
9 | FbConst = require('freebird-constants');
10 |
11 | chai.use(sinonChai);
12 |
13 | var loader = require('../lib/components/loader');
14 | Freebird = require('../index'),
15 | Constants = require('../lib/utils/constants'),
16 | FB_STATE = Constants.FB_STATE,
17 | EVT_TOP = FbConst.EVENTS_TO_TOP;
18 |
19 | var fakeNc = {
20 | _freebird: {},
21 | _controller: {},
22 | getName: function () { return 'fakeNc1'; },
23 | start: function () {},
24 | stop: function () {},
25 | reset: function () {},
26 | permitJoin: function () {},
27 | remove: function () {},
28 | ban: function () {},
29 | unban: function () {},
30 | ping: function () {},
31 | maintain: function () {},
32 | _setState: function (state) { this._state = state; },
33 | _getState: function (state) { return this._state; }
34 | },
35 | fakeNc2 = {
36 | _freebird: {},
37 | _controller: {},
38 | getName: function () { return 'fakeNc2'; },
39 | start: function () {},
40 | stop: function () {},
41 | reset: function () {},
42 | permitJoin: function () {},
43 | remove: function () {},
44 | ban: function () {},
45 | unban: function () {},
46 | ping: function () {},
47 | maintain: function () {},
48 | _setState: function (state) { this._state = state; },
49 | _getState: function (state) { return this._state; }
50 | },
51 | fb = new Freebird([fakeNc, fakeNc2], { dbPaths: {
52 | device: path.resolve(__dirname, './database/devices.db'),
53 | gadget: path.resolve(__dirname, './database/gadgets.db')
54 | }}),
55 | fakeGetFunc = function (name) {
56 | switch (name) {
57 | case 'netcore':
58 | return fakeNc;
59 |
60 | case 'permAddr':
61 | return '00:00:00:00:00';
62 |
63 | case 'auxId':
64 | return 'aa/bb';
65 |
66 | case 'id':
67 | return this._id;
68 |
69 | case 'device':
70 | return fakeDev;
71 |
72 | case 'gadTable':
73 | return this._gads;
74 |
75 | }
76 | },
77 | fakeSetFunc = function (name, value) {
78 | switch (name) {
79 | case '_id':
80 | this._id = value;
81 | break;
82 | }
83 | },
84 | fakeDev = {
85 | _netcore: {},
86 | _id: 1,
87 | _recovering: false,
88 | _poke: function () {},
89 | _gads: [],
90 | get: fakeGetFunc,
91 | set: fakeSetFunc,
92 | ping: function () {},
93 | dump: function () {
94 | return {
95 | netcore: 'fakeNc1',
96 | id: this._id,
97 | gads: this._gads,
98 | net: { address: { permanent: '00:00:00:00:00' }}
99 | };
100 | }
101 | },
102 | fakeGad = {
103 | _id: 1,
104 | _auxId: 'aa/bb',
105 | _dev: fakeDev,
106 | _recovering: false,
107 | _clear: function () {},
108 | disable: function () {},
109 | get: fakeGetFunc,
110 | set: fakeSetFunc,
111 | dump: function () {
112 | return {
113 | netcore: 'fakeNc1',
114 | id: this._id,
115 | auxId: this._auxId,
116 | dev: {
117 | id: fakeDev._id,
118 | permAddr: '00:00:00:00:00'
119 | }
120 | };
121 | }
122 | };
123 |
124 | describe('freebird - Functional Check', function () {
125 | describe('#findById(type, id)', function () {
126 | before(function(done) {
127 | fakeDev._recovering = false;
128 | fb.register('device', fakeDev, function (err, id) {
129 | fakeGad._recovering = false;
130 | fb.register('gadget', fakeGad, function (err, id) {
131 | done();
132 | });
133 | });
134 | });
135 |
136 | it('should find netcore by id', function (done) {
137 | if (fb.findById('netcore', 'fakeNc1') === fakeNc)
138 | done();
139 | });
140 |
141 | it('should find device by id', function (done) {
142 | if (fb.findById('device', 1) === fakeDev)
143 | done();
144 | });
145 |
146 | it('should find gadget by id', function (done) {
147 | if (fb.findById('gadget', 1) === fakeGad)
148 | done();
149 | });
150 | });
151 |
152 | describe('#findByNet(type, ncName, permAddr, auxId)', function () {
153 | it('should find netcore by ncName', function (done) {
154 | if (fb.findByNet('netcore', 'fakeNc1') === fakeNc)
155 | done();
156 | });
157 |
158 | it('should find device by permAddr', function (done) {
159 | if (fb.findByNet('device', 'fakeNc1', '00:00:00:00:00') === fakeDev)
160 | done();
161 | });
162 |
163 | it('should find gadget by auxId', function (done) {
164 | if (fb.findByNet('gadget', 'fakeNc1', '00:00:00:00:00', 'aa/bb') === fakeGad)
165 | done();
166 | });
167 | });
168 |
169 | describe('#filter(type, pred)', function () {
170 | it('should filter netcore by pred', function (done) {
171 | var nc = fb.filter('netcore', function (nc) {
172 | return nc.getName() === 'fakeNc1';
173 | })[0];
174 |
175 | if (nc === fakeNc)
176 | done();
177 | });
178 |
179 | it('should filter device by pred', function (done) {
180 | var dev = fb.filter('device', function (dev) {
181 | return dev._id === 1;
182 | })[0];
183 |
184 | if (dev === fakeDev)
185 | done();
186 | });
187 |
188 | it('should filter gadget by pred', function (done) {
189 | var gad = fb.filter('gadget', function (gad) {
190 | return gad._id === 1;
191 | })[0];
192 |
193 | if (gad === fakeGad)
194 | done();
195 | });
196 |
197 | after(function(done) {
198 | fb.unregister('gadget', fakeGad, function (err, id) {
199 | fb.unregister('device', fakeDev, function (err, id) {
200 | done();
201 | });
202 | });
203 | });
204 | });
205 |
206 | describe('#register(type, obj, callback)', function () {
207 | it('should register device with recovering false', function (done) {
208 | fakeDev._poke = sinon.spy();
209 | fakeDev._recovering = false;
210 | fb.register('device', fakeDev, function (err, id) {
211 | if (id === fakeDev._id) {
212 | expect(fakeDev._poke).to.have.been.calledOnce;
213 | expect(fb.findById('device', 1)).to.be.equal(fakeDev);
214 | fb.unregister('device', fakeDev, function (err, id) {
215 | done();
216 | });
217 | }
218 | });
219 | });
220 |
221 | it('should register device with recovering true', function (done) {
222 | fakeDev._recovering = true;
223 | fb.register('device', fakeDev, function (err, id) {
224 | if (id === fakeDev._id && fakeDev._recovering === false) {
225 | done();
226 | }
227 | });
228 | });
229 |
230 | it('should register gadget with recovering false', function (done) {
231 | fakeGad._recovering = false;
232 | fb.register('gadget', fakeGad, function (err, id) {
233 | if (id === fakeGad._id && fakeDev._gads.length !== 0) {
234 | fb.unregister('gadget', fakeGad, function (err, id) {
235 | done();
236 | });
237 | }
238 | });
239 | });
240 |
241 | it('should register gadget with recovering true', function (done) {
242 | fakeGad._recovering = true;
243 | fb.register('gadget', fakeGad, function (err, id) {
244 | if (id === fakeGad._id && fakeDev._recovering === false) {
245 | done();
246 | }
247 | });
248 | });
249 | });
250 |
251 | describe('#unregister(type, obj, callback)', function () {
252 | it('should register device', function (done) {
253 | fb.unregister('device', fakeDev, function (err, id) {
254 | if (!fb.findById('device', 1))
255 | done();
256 | });
257 | });
258 |
259 | it('should register gadget', function (done) {
260 | fb.unregister('gadget', fakeGad, function (err, id) {
261 | if (!fb.findById('gadget', 1))
262 | done();
263 | });
264 | });
265 | });
266 |
267 | describe('#start(callback)', function () {
268 | it('should start netcore and not thing reload', function (done) {
269 | var startStub = sinon.stub(fakeNc, 'start', function (callback) {
270 | if (_.isFunction(callback)) {
271 | startStub.restore();
272 | expect(fb.findById('device', 1)).to.be.equal(undefined);
273 | callback();
274 | }
275 | });
276 |
277 | fb._state = FB_STATE.NORMAL;
278 | fakeNc._state = FB_STATE.UNKNOW;
279 |
280 | fb.start(function (err) {
281 | done();
282 | });
283 | });
284 |
285 | it('should start netcore and some thing reload', function (done) {
286 | var devFindFromDbStub = sinon.stub(fb._devbox, 'findFromDb', function (obj, callback) {
287 | callback(null, [{"netcore":"fakeNc1","id":10,"gads":[{"gadId":10,"auxId":"aa/cc"}],"net":{"address":{"permanent":"00:00:00:00:01"}}, "attrs": {}, "props": {}, "_id":"eD3Tg3iGOZJQgfjZ"}]);
288 | }),
289 | gadFindFromDbStub = sinon.stub(fb._gadbox, 'findFromDb', function (obj, callback) {
290 | callback(null, [{"netcore":"fakeNc1","id":10,"auxId":"aa/cc","dev":{"id":10,"permAddr":"00:00:00:00:01"}, "attrs": {}, "props": {}, "panel": {},"_id":"tUmqSZCXgvC5Fc6u"}]);
291 | }),
292 | startStub = sinon.stub(fakeNc, 'start', function (callback) {
293 | if (_.isFunction(callback) && fb.findById('device', 10)) {
294 | startStub.restore();
295 | devFindFromDbStub.restore();
296 | gadFindFromDbStub.restore();
297 | callback();
298 | }
299 | });
300 |
301 | fb._state = FB_STATE.UNKNOW;
302 | fakeNc._state = FB_STATE.UNKNOW;
303 |
304 | fb.start(function (err) {
305 | done();
306 | });
307 | });
308 |
309 | // it('should start multi netcore', function (done) {
310 | // var startStub = sinon.stub(fakeNc, 'start', function (callback) {
311 | // if (_.isFunction(callback)) {
312 | // startStub.restore();
313 | // expect(fb.findById('device', 1)).to.be.equal(undefined);
314 | // callback();
315 | // }
316 | // }),
317 | // startStub2 = sinon.stub(fakeNc2, 'start', function (callback) {
318 |
319 | // if (_.isFunction(callback)) {
320 | // startStub2.restore();
321 | // expect(fb.findById('device', 1)).to.be.equal(undefined);
322 | // callback();
323 | // }
324 | // });
325 |
326 | // fb._state = FB_STATE.NORMAL;
327 | // fakeNc._state = FB_STATE.UNKNOW;
328 | // fakeNc2._state = FB_STATE.UNKNOW;
329 |
330 | // fb.start(function (err) {
331 | // done();
332 | // });
333 | // });
334 |
335 | it('should start success and change state from unknow to normal', function (done) {
336 | // unknow
337 | var devFindFromDbStub = sinon.stub(fb._devbox, 'findFromDb', function (obj, cb) {
338 | setImmediate(cb, null, []);
339 | }),
340 | gadFindFromDbStub = sinon.stub(fb._gadbox, 'findFromDb', function (obj, cb) {
341 | setImmediate(cb, null, []);
342 | }),
343 | startStub = sinon.stub(fakeNc, 'start', function (callback) {
344 | callback();
345 | }),
346 | startStub2 = sinon.stub(fakeNc2, 'start', function (callback) {
347 | callback();
348 | }),
349 | maintainSpy = sinon.spy(fb, 'maintain'),
350 | emitSpy = sinon.spy(fb, 'emit');
351 |
352 | fb._state = FB_STATE.UNKNOW;
353 | fakeNc._state = FB_STATE.UNKNOW;
354 | fakeNc2._state = FB_STATE.UNKNOW;
355 |
356 | fb.start(function () {
357 | expect(devFindFromDbStub).to.be.calledTwice;
358 | expect(gadFindFromDbStub).to.be.calledTwice;
359 | expect(startStub).to.be.calledOnce;
360 | expect(startStub2).to.be.calledOnce;
361 | expect(fb._getState()).to.be.equal(FB_STATE.NORMAL);
362 | expect(maintainSpy).to.be.calledOnce;
363 | expect(emitSpy).to.be.calledWith(EVT_TOP.READY);
364 |
365 | devFindFromDbStub.restore();
366 | gadFindFromDbStub.restore();
367 | startStub.restore();
368 | startStub2.restore();
369 | maintainSpy.restore();
370 | emitSpy.restore();
371 |
372 | done();
373 | });
374 | });
375 |
376 | it('should fail if one netcore start fails', function (done) {
377 | // unknow
378 | var devFindFromDbStub = sinon.stub(fb._devbox, 'findFromDb', function (obj, cb) {
379 | setImmediate(cb, null, []);
380 | }),
381 | gadFindFromDbStub = sinon.stub(fb._gadbox, 'findFromDb', function (obj, cb) {
382 | setImmediate(cb, null, []);
383 | }),
384 | startStub = sinon.stub(fakeNc, 'start', function (callback) {
385 | callback(new Error('error'));
386 | }),
387 | startStub2 = sinon.stub(fakeNc2, 'start', function (callback) {
388 | callback();
389 | }),
390 | stopStub2 = sinon.stub(fakeNc2, 'stop', function (callback) {
391 | callback();
392 | });
393 |
394 | fb._state = FB_STATE.UNKNOW;
395 |
396 | fb.start(function (err) {
397 | expect(devFindFromDbStub).to.be.calledTwice;
398 | expect(gadFindFromDbStub).to.be.calledTwice;
399 | expect(startStub).to.be.calledOnce;
400 | expect(stopStub2).to.be.calledOnce;
401 | expect(startStub2).to.be.calledOnce;
402 | expect(fb._getState()).to.be.equal(FB_STATE.UNKNOW);
403 | expect(err.message).to.be.equal('fakeNc1 netcore start failed with error: error');
404 |
405 | devFindFromDbStub.restore();
406 | gadFindFromDbStub.restore();
407 | startStub.restore();
408 | stopStub2.restore();
409 | startStub2.restore();
410 |
411 | done();
412 | });
413 | });
414 |
415 | it('should return error if all netcore are started', function (done) {
416 | // normal
417 | fb._state = FB_STATE.NORMAL;
418 | fakeNc._state = FB_STATE.NORMAL;
419 | fakeNc2._state = FB_STATE.NORMAL;
420 |
421 | fb.start(function (err) {
422 | expect(err.message).to.be.equal('All netcores have been started');
423 | expect(fb._getState()).to.be.equal(FB_STATE.NORMAL);
424 |
425 | done();
426 | });
427 | });
428 |
429 | it('should only start the stopped netcore and start success', function (done) {
430 | // normal
431 | var startStub = sinon.stub(fakeNc, 'start', function (callback) {
432 | callback();
433 | }),
434 | startStub2 = sinon.stub(fakeNc2, 'start', function (callback) {
435 | callback();
436 | }),
437 | maintainSpy = sinon.spy(fb, 'maintain');
438 |
439 | fb._state = FB_STATE.NORMAL;
440 | fakeNc._state = FB_STATE.UNKNOW;
441 | fakeNc2._state = FB_STATE.NORMAL;
442 |
443 | fb.start(function (err) {
444 | expect(startStub).to.be.calledOnce;
445 | expect(startStub2).to.be.callCount(0);
446 | expect(fb._getState()).to.be.equal(FB_STATE.NORMAL);
447 | expect(maintainSpy).to.be.calledOnce;
448 |
449 | startStub.restore();
450 | startStub2.restore();
451 | maintainSpy.restore();
452 |
453 | done();
454 | });
455 | });
456 |
457 | it('should only start the stopped netcore and start fail', function (done) {
458 | // normal
459 | var startStub = sinon.stub(fakeNc, 'start', function (callback) {
460 | callback(new Error('error'));
461 | }),
462 | startStub2 = sinon.stub(fakeNc2, 'start', function (callback) {
463 | callback();
464 | }),
465 | stopStub2 = sinon.stub(fakeNc2, 'stop', function (callback) {
466 | callback();
467 | });
468 |
469 | fb._state = FB_STATE.NORMAL;
470 | fakeNc._state = FB_STATE.UNKNOW;
471 | fakeNc2._state = FB_STATE.UNKNOW;
472 |
473 | fb.start(function (err) {
474 | expect(startStub2).to.be.calledOnce;
475 | expect(startStub).to.be.calledOnce;
476 | expect(stopStub2).to.be.callCount(0);
477 | expect(fb._getState()).to.be.equal(FB_STATE.NORMAL);
478 | expect(err.message).to.be.equal('fakeNc1 netcore start failed with error: error');
479 |
480 | startStub2.restore();
481 | startStub.restore();
482 | stopStub2.restore();
483 |
484 | done();
485 | });
486 | });
487 | });
488 |
489 | describe('#stop(callback)', function () {
490 | it('should stop netcore', function (done) {
491 | var stopStub = sinon.stub(fakeNc, 'stop', function (callback) {
492 | if (_.isFunction(callback)) {
493 | stopStub.restore();
494 | done();
495 | }
496 | });
497 |
498 | fb._state = FB_STATE.NORMAL;
499 | fakeNc._state = FB_STATE.NORMAL;
500 |
501 | fb.stop(function (err) {});
502 | });
503 |
504 | // it('should stop multi netcore', function (done) {
505 | // var stopStub = sinon.stub(fakeNc, 'stop', function (callback) {
506 | // if (_.isFunction(callback)) {
507 | // stopStub.restore();
508 | // callback();
509 | // }
510 | // }),
511 | // stopStub2 = sinon.stub(fakeNc2, 'stop', function (callback) {
512 | // if (_.isFunction(callback)) {
513 | // stopStub2.restore();
514 | // callback();
515 | // }
516 | // });
517 |
518 | // fb.stop(function (err) {
519 | // done();
520 | // });
521 | // });
522 |
523 | it('should be error if freebird already stopped', function (done) {
524 | fb._state = FB_STATE.UNKNOW;
525 |
526 | fb.stop(function (err) {
527 | expect(fb._getState()).to.be.equal(FB_STATE.UNKNOW);
528 | expect(err.message).to.be.equal('Freebird can not stop now');
529 | done();
530 | });
531 | });
532 |
533 | it('should only stop the started netcores and stop fails', function (done) {
534 | var stopStub = sinon.stub(fakeNc, 'stop', function (callback) {
535 | callback(new Error('error'));
536 | }),
537 | stopStub2 = sinon.stub(fakeNc2, 'stop', function (callback) {
538 | callback();
539 | });
540 |
541 | fb._state = FB_STATE.NORMAL;
542 | fakeNc._state = FB_STATE.NORMAL;
543 | fakeNc2._state = FB_STATE.NORMAL;
544 |
545 | fb.stop(function (err) {
546 | expect(stopStub).to.be.calledOnce;
547 | expect(stopStub2).to.be.calledOnce;
548 | expect(fb._getState()).to.be.equal(FB_STATE.NORMAL);
549 | expect(err.message).to.be.equal('fakeNc1 netcore stop failed with error: error');
550 |
551 | stopStub.restore();
552 | stopStub2.restore();
553 |
554 | done();
555 | });
556 | });
557 |
558 | it('should only stop the started netcores and stop success', function (done) {
559 | var stopStub = sinon.stub(fakeNc, 'stop', function (callback) {
560 | callback();
561 | }),
562 | stopStub2 = sinon.stub(fakeNc2, 'stop', function (callback) {
563 | callback();
564 | });
565 |
566 | fb._state = FB_STATE.NORMAL;
567 | fakeNc._state = FB_STATE.UNKNOW;
568 | fakeNc2._state = FB_STATE.NORMAL;
569 |
570 | fb.stop(function (err) {
571 | expect(stopStub).to.be.callCount(0);
572 | expect(stopStub2).to.be.calledOnce;
573 | expect(fb._getState()).to.be.equal(FB_STATE.UNKNOW);
574 |
575 | stopStub.restore();
576 | stopStub2.restore();
577 |
578 | done();
579 | });
580 | });
581 | });
582 |
583 | describe('#reset(mode, callback)', function () {
584 | before(function(done) {
585 | fakeDev._recovering = false;
586 | fb.register('device', fakeDev, function (err, id) {
587 | fakeGad._recovering = false;
588 | fb.register('gadget', fakeGad, function (err, id) {
589 | done();
590 | });
591 | });
592 | });
593 |
594 | it('should reset netcore with mode 0', function (done) {
595 | var resetStub1 = sinon.stub(fakeNc, 'reset', function (mode, callback) {
596 | if (mode === 0 && _.isFunction(callback)) {
597 | resetStub1.restore();
598 | callback();
599 | }
600 | }),
601 | resetStub2 = sinon.stub(fakeNc2, 'reset', function (mode, callback) {
602 | if (mode === 0 && _.isFunction(callback)) {
603 | resetStub2.restore();
604 | callback();
605 | }
606 | });
607 |
608 | fb._state = FB_STATE.NORMAL;
609 |
610 | fb.reset(0, function (err) {
611 | expect(fb.findById('device', 1)).to.be.equal(fakeDev);
612 | expect(fb.findById('gadget', 1)).to.be.equal(fakeGad);
613 | done();
614 | });
615 | });
616 |
617 | it('should reset netcore with mode 1', function (done) {
618 | var resetStub1 = sinon.stub(fakeNc, 'reset', function (mode, callback) {
619 | if (mode === 1 && _.isFunction(callback)) {
620 | resetStub1.restore();
621 | callback();
622 | }
623 | }),
624 | resetStub2 = sinon.stub(fakeNc2, 'reset', function (mode, callback) {
625 | if (mode === 1 && _.isFunction(callback)) {
626 | resetStub2.restore();
627 | callback();
628 | }
629 | });
630 |
631 | fb._state = FB_STATE.NORMAL;
632 |
633 | fb.reset(1, function (err) {
634 | expect(fb.findById('device', 1)).to.be.equal(undefined);
635 | expect(fb.findById('gadget', 1)).to.be.equal(undefined);
636 | done();
637 | });
638 | });
639 |
640 | // it('should reset multi netcore', function (done) {
641 | // var resetStub = sinon.stub(fakeNc, 'reset', function (mode, callback) {
642 | // if (mode === 0 && _.isFunction(callback)) {
643 | // resetStub.restore();
644 | // callback();
645 | // }
646 | // }),
647 | // resetStub2 = sinon.stub(fakeNc2, 'reset', function (mode, callback) {
648 | // if (mode === 0 && _.isFunction(callback)) {
649 | // resetStub2.restore();
650 | // callback();
651 | // }
652 | // });
653 |
654 | // fb.reset(0, function (err) {
655 | // done();
656 | // });
657 | // });
658 |
659 | it('should be error if call soft reset when freebird is stopped', function (done) {
660 | // unknow
661 | fb._state = FB_STATE.UNKNOW;
662 |
663 | fb.reset(0, function(err) {
664 | expect(fb._getState()).to.be.equal(FB_STATE.UNKNOW);
665 | expect(err.message).to.be.equal('You can only hard reset when freebird is stopped');
666 |
667 | done();
668 | });
669 | });
670 |
671 | it('should hard reset error if one netcore reset fails when freebird is stopped', function (done) {
672 | // unknow
673 | var unloadByNcStub = sinon.stub(loader, 'unloadByNetcore', function (fb, ncName, cb) {
674 | setImmediate(cb, null);
675 | }),
676 | resetStub = sinon.stub(fakeNc, 'reset', function (mode, callback) {
677 | callback(new Error('error'));
678 | }),
679 | resetStub2 = sinon.stub(fakeNc2, 'reset', function (mode, callback) {
680 | callback();
681 | }),
682 | stopStub = sinon.stub(fakeNc2, 'stop', function (callback) {
683 | callback();
684 | });
685 |
686 | fb._state = FB_STATE.UNKNOW;
687 |
688 | fb.reset(1, function (err) {
689 | expect(unloadByNcStub).to.be.calledOnce;
690 | expect(unloadByNcStub).to.be.calledWith(fb, fakeNc2.getName());
691 | expect(resetStub).to.be.calledOnce;
692 | expect(resetStub2).to.be.calledOnce;
693 | expect(stopStub).to.be.calledOnce;
694 | expect(fb._getState()).to.be.equal(FB_STATE.UNKNOW);
695 | expect(err.message).to.be.equal('fakeNc1 netcore reset failed with error: error');
696 |
697 | unloadByNcStub.restore();
698 | resetStub.restore();
699 | resetStub2.restore();
700 | stopStub.restore();
701 |
702 | done();
703 | });
704 | });
705 |
706 | it('should hard reset success when freebird is stopped', function (done) {
707 | // unknow
708 | var unloadByNcStub = sinon.stub(loader, 'unloadByNetcore', function (fb, ncName, cb) {
709 | setImmediate(cb, null);
710 | }),
711 | resetStub = sinon.stub(fakeNc, 'reset', function (mode, callback) {
712 | callback();
713 | }),
714 | resetStub2 = sinon.stub(fakeNc2, 'reset', function (mode, callback) {
715 | callback();
716 | }),
717 | fbEmitSpy = sinon.spy(fb, 'emit');
718 |
719 | fb._state = FB_STATE.UNKNOW;
720 |
721 | fb.reset(1, function (err) {
722 | expect(unloadByNcStub).to.be.calledTwice;
723 | expect(unloadByNcStub).to.be.calledWith(fb, fakeNc.getName());
724 | expect(unloadByNcStub).to.be.calledWith(fb, fakeNc2.getName());
725 | expect(resetStub).to.be.calledOnce;
726 | expect(resetStub2).to.be.calledOnce;
727 | expect(fb._getState()).to.be.equal(FB_STATE.NORMAL);
728 | expect(fbEmitSpy).to.be.calledOnce;
729 | expect(fbEmitSpy).to.be.calledWith(EVT_TOP.READY);
730 |
731 | unloadByNcStub.restore();
732 | resetStub.restore();
733 | resetStub2.restore();
734 | fbEmitSpy.restore();
735 |
736 | done();
737 | });
738 | });
739 |
740 | it('should be error if one netcore reset fail when freebird is started', function (done) {
741 | // normal
742 | var resetStub = sinon.stub(fakeNc, 'reset', function (mode, callback) {
743 | callback(new Error('error'));
744 | }),
745 | resetStub2 = sinon.stub(fakeNc2, 'reset', function (mode, callback) {
746 | callback();
747 | });
748 |
749 | fb._state = FB_STATE.NORMAL;
750 |
751 | fb.reset(0, function (err) {
752 | expect(resetStub).to.be.calledOnce;
753 | expect(resetStub2).to.be.calledOnce;
754 | expect(fb._getState()).to.be.equal(FB_STATE.NORMAL);
755 | expect(err.message).to.be.equal('fakeNc1 netcore reset failed with error: error');
756 |
757 | resetStub.restore();
758 | resetStub2.restore();
759 |
760 | done();
761 | });
762 | });
763 |
764 | it('should soft reset success when freebird is started', function (done) {
765 | // normal
766 | var resetStub = sinon.stub(fakeNc, 'reset', function (mode, callback) {
767 | callback();
768 | }),
769 | resetStub2 = sinon.stub(fakeNc2, 'reset', function (mode, callback) {
770 | callback();
771 | }),
772 | fbEmitSpy = sinon.spy(fb, 'emit');
773 |
774 | fb._state = FB_STATE.NORMAL;
775 |
776 | fb.reset(0, function (err) {
777 | expect(resetStub).to.be.calledOnce;
778 | expect(resetStub2).to.be.calledOnce;
779 | expect(fb._getState()).to.be.equal(FB_STATE.NORMAL);
780 | expect(fbEmitSpy).to.be.calledOnce;
781 |
782 | resetStub.restore();
783 | resetStub2.restore();
784 | fbEmitSpy.restore();
785 |
786 | done();
787 | });
788 | });
789 |
790 | it('should hard reset success when freebird is started', function (done) {
791 | // normal
792 | var unloadByNcStub = sinon.stub(loader, 'unloadByNetcore', function (fb, ncName, cb) {
793 | setImmediate(cb, null);
794 | }),
795 | resetStub = sinon.stub(fakeNc, 'reset', function (mode, callback) {
796 | callback();
797 | }),
798 | resetStub2 = sinon.stub(fakeNc2, 'reset', function (mode, callback) {
799 | callback();
800 | }),
801 | fbEmitSpy = sinon.spy(fb, 'emit');
802 |
803 | fb._state = FB_STATE.NORMAL;
804 |
805 | fb.reset(1, function (err) {
806 | expect(resetStub).to.be.calledOnce;
807 | expect(resetStub2).to.be.calledOnce;
808 | expect(unloadByNcStub).to.be.calledTwice;
809 | expect(unloadByNcStub).to.be.calledWith(fb, fakeNc.getName());
810 | expect(unloadByNcStub).to.be.calledWith(fb, fakeNc2.getName());
811 | expect(fb._getState()).to.be.equal(FB_STATE.NORMAL);
812 | expect(fbEmitSpy).to.be.calledOnce;
813 |
814 | resetStub.restore();
815 | resetStub2.restore();
816 | fbEmitSpy.restore();
817 | unloadByNcStub.restore();
818 |
819 | done();
820 | });
821 | });
822 |
823 | after(function(done) {
824 | fb.unregister('gadget', fakeGad, function (err, id) {
825 | fb.unregister('device', fakeDev, function (err, id) {
826 | done();
827 | });
828 | });
829 | });
830 | });
831 |
832 | describe('#permitJoin(duration, callback)', function () {
833 | it('should set netcore permitJoin', function (done) {
834 | var permitJoinStub1 = sinon.stub(fakeNc, 'permitJoin', function (duration, callback) {
835 | if (duration === 30 && _.isFunction(callback)) {
836 | permitJoinStub1.restore();
837 | callback();
838 | }
839 | }),
840 | permitJoinStub2 = sinon.stub(fakeNc2, 'permitJoin', function (duration, callback) {
841 | if (duration === 30 && _.isFunction(callback)) {
842 | permitJoinStub2.restore();
843 | callback();
844 | }
845 | });
846 |
847 | fb.permitJoin(30, function (err) {
848 | done();
849 | });
850 | });
851 | });
852 |
853 | describe('#remove(ncName, permAddr, callback)', function () {
854 | it('should remove device', function (done) {
855 | var removeStub = sinon.stub(fakeNc, 'remove', function (permAddr, callback) {
856 | if (permAddr === '00:00:00:00:00' && _.isFunction(callback)) {
857 | removeStub.restore();
858 | callback();
859 | }
860 | });
861 |
862 | fb.remove('fakeNc1', '00:00:00:00:00', function (err) {
863 | done();
864 | });
865 | });
866 |
867 | it('should has error if netcore is not exist', function (done) {
868 | fb.remove('fakeNcxxx', '00:00:00:00:00', function (err) {
869 | if (err)
870 | done();
871 | });
872 | });
873 | });
874 |
875 | describe('#ban(ncName, permAddr, callback)', function () {
876 | it('should ban device', function (done) {
877 | var banStub = sinon.stub(fakeNc, 'ban', function (permAddr, callback) {
878 | if (permAddr === '00:00:00:00:00' && _.isFunction(callback)) {
879 | banStub.restore();
880 | callback();
881 | }
882 | });
883 |
884 | fb.ban('fakeNc1', '00:00:00:00:00', function (err) {
885 | done();
886 | });
887 | });
888 |
889 | it('should has error if netcore is not exist', function (done) {
890 | fb.ban('fakeNcxxx', '00:00:00:00:00', function (err) {
891 | if (err)
892 | done();
893 | });
894 | });
895 | });
896 |
897 | describe('#unban(ncName, permAddr, callback)', function () {
898 | it('should unban device', function (done) {
899 | var unbanStub = sinon.stub(fakeNc, 'unban', function (permAddr, callback) {
900 | if (permAddr === '00:00:00:00:00' && _.isFunction(callback)) {
901 | unbanStub.restore();
902 | callback();
903 | }
904 | });
905 |
906 | fb.unban('fakeNc1', '00:00:00:00:00', function (err) {
907 | done();
908 | });
909 | });
910 |
911 | it('should has error if netcore is not exist', function (done) {
912 | fb.unban('fakeNcxxx', '00:00:00:00:00', function (err) {
913 | if (err)
914 | done();
915 | });
916 | });
917 | });
918 |
919 | describe('#ping(ncName, permAddr, callback)', function () {
920 | before(function(done) {
921 | fakeDev._recovering = true;
922 | fb.register('device', fakeDev, function (err, id) {
923 | done();
924 | });
925 | });
926 |
927 | it('should ping device', function (done) {
928 | var pingStub = sinon.stub(fakeDev, 'ping', function (callback) {
929 | if (_.isFunction(callback)) {
930 | pingStub.restore();
931 | callback();
932 | }
933 | });
934 |
935 | fb.ping('fakeNc1', '00:00:00:00:00', function (err) {
936 | done();
937 |
938 | });
939 | });
940 |
941 | it('should has error if netcore is not exist', function (done) {
942 | fb.ping('fakeNcxxx', '00:00:00:00:00', function (err) {
943 | if (err)
944 | done();
945 | });
946 | });
947 |
948 | it('should has error if device is not exist', function (done) {
949 | fb.ping('fakeNc1', '00:00:00:00:xx', function (err) {
950 | if (err)
951 | done();
952 | });
953 | });
954 |
955 | after(function(done) {
956 | fb.unregister('device', fakeDev, function (err, id) {
957 | done();
958 | });
959 | });
960 | });
961 |
962 | describe('#maintain(ncName, callback)', function () {
963 | it('should maintain with ncName', function (done) {
964 | var maintainStub = sinon.stub(fakeNc, 'maintain', function (callback) {
965 | if (_.isFunction(callback)) {
966 | maintainStub.restore();
967 | callback();
968 | }
969 | });
970 |
971 | fb.maintain('fakeNc1', function (err) {
972 | done();
973 | });
974 | });
975 |
976 | it('should maintain without ncName', function (done) {
977 | var maintainStub = sinon.stub(fakeNc, 'maintain', function (callback) {
978 | if (_.isFunction(callback)) {
979 | maintainStub.restore();
980 | callback();
981 | }
982 | }),
983 | maintainStub2 = sinon.stub(fakeNc2, 'maintain', function (callback) {
984 | if (_.isFunction(callback)) {
985 | maintainStub2.restore();
986 | callback();
987 | }
988 | });
989 |
990 | fb.maintain(function (err) {
991 | done();
992 | });
993 | });
994 | });
995 |
996 | describe('#_fire(evt, data)', function () {
997 | it('should emit evt', function (done) {
998 | fb.on('testEvt', function (data) {
999 | if (data.result === 'test')
1000 | done();
1001 | });
1002 |
1003 | fb._fire('testEvt', { result: 'test'});
1004 | });
1005 | });
1006 |
1007 | describe('#_tweet(subsys, indType, id, data)', function () {
1008 | it('should _tweet net evt', function (done) {
1009 | var indicateStub = sinon.stub(fb._apiAgent, 'indicate', function (ind) {
1010 | if (_.isEqual(ind, {__intf: 'IND', subsys: 0, type: 'test', id: 1, data: 'test'}))
1011 | indicateStub.restore();
1012 | done();
1013 | });
1014 |
1015 | fb._tweet('net', 'test', 1, 'test');
1016 | });
1017 |
1018 | it('should _tweet dev evt', function (done) {
1019 | var indicateStub = sinon.stub(fb._apiAgent, 'indicate', function (ind) {
1020 | if (_.isEqual(ind, {__intf: 'IND', subsys: 1, type: 'test', id: 1, data: 'test'}))
1021 | indicateStub.restore();
1022 | done();
1023 | });
1024 |
1025 | fb._tweet('dev', 'test', 1, 'test');
1026 | });
1027 |
1028 | it('should _tweet gad evt', function (done) {
1029 | var indicateStub = sinon.stub(fb._apiAgent, 'indicate', function (ind) {
1030 | if (_.isEqual(ind, {__intf: 'IND', subsys: 2, type: 'test', id: 1, data: 'test'}))
1031 | indicateStub.restore();
1032 | done();
1033 | });
1034 |
1035 | fb._tweet('gad', 'test', 1, 'test');
1036 | });
1037 |
1038 | after(function (done) {
1039 | try {
1040 | fs.unlink(path.resolve(__dirname, './database/devices.db'));
1041 | fs.unlink(path.resolve(__dirname, './database/gadgets.db'));
1042 | } catch (e) {
1043 | console.log(e);
1044 | }
1045 |
1046 | done();
1047 | });
1048 | });
1049 | });
1050 |
--------------------------------------------------------------------------------