├── 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 | [![NPM](https://nodei.co/npm/freebird.png?downloads=true)](https://nodei.co/npm/freebird/) 6 | 7 | [![Travis branch](https://img.shields.io/travis/freebirdjs/freebird/master.svg?maxAge=2592000)](https://travis-ci.org/freebirdjs/freebird) 8 | [![npm](https://img.shields.io/npm/v/freebird.svg?maxAge=2592000)](https://www.npmjs.com/package/freebird) 9 | [![npm](https://img.shields.io/npm/l/freebird.svg?maxAge=2592000)](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 | --------------------------------------------------------------------------------