├── .gitignore ├── favor.jpg ├── favor.png ├── tests ├── mocks │ ├── component_methods.js │ ├── linked_temp_humidity_mock.js │ ├── gpio.js │ ├── i2c.js │ ├── spi.js │ └── favorit.js ├── add_modules.spec.js ├── error_testing.spec.js ├── protocols.spec.js ├── favor_obj_builder.spec.js ├── device_abilities.spec.js ├── spi_getters_setters.spec.js ├── i2c_getters_setters.spec.js └── getters_setters.spec.js ├── index.js ├── lib ├── add_modules.js ├── error_handling.js ├── close.js ├── protocols │ ├── i2c.js │ ├── spi.js │ └── gpio.js ├── device_abilities.js ├── favor_obj_builder.js ├── component_utils.js └── getters_and_setters.js ├── gulpfile.js ├── package.json ├── license.mit ├── .jscsrc └── readme.md /.gitignore: -------------------------------------------------------------------------------- 1 | /node_modules/* 2 | /tests/gpio-test/* 3 | -------------------------------------------------------------------------------- /favor.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pedalpete/it/HEAD/favor.jpg -------------------------------------------------------------------------------- /favor.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pedalpete/it/HEAD/favor.png -------------------------------------------------------------------------------- /tests/mocks/component_methods.js: -------------------------------------------------------------------------------- 1 | module.exports = function(cb) { 2 | cb.call(this, 'defined in component'); 3 | }; -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | module.exports = function(config, modules) { 2 | return require('./lib/favor_obj_builder.js')(config, modules); 3 | }; -------------------------------------------------------------------------------- /tests/mocks/linked_temp_humidity_mock.js: -------------------------------------------------------------------------------- 1 | module.exports = function(cb) { 2 | cb.call(this, {'temperature': '26c', 'humidity': 80}); 3 | }; -------------------------------------------------------------------------------- /lib/add_modules.js: -------------------------------------------------------------------------------- 1 | module.exports = function(modulesArray) { 2 | var newObj = {}; 3 | modulesArray.forEach(function(moduleName) { 4 | var moduleToAdd = require(moduleName); 5 | for (var key in moduleToAdd) { 6 | newObj[key] = moduleToAdd[key]; 7 | } 8 | }); 9 | return newObj; 10 | }; -------------------------------------------------------------------------------- /lib/error_handling.js: -------------------------------------------------------------------------------- 1 | module.exports.onError = function(err, line, script) { 2 | if (_fvr[this._index].onError) { 3 | return _fvr[this._index].onError.call(this, err, line, script); 4 | } 5 | if (_fvr.onError) return _fvr.onError.call(this, err, line, script); 6 | console.log('The following error curred :' + 7 | err + ' on line ' + line + ' in script ' + script); 8 | }; -------------------------------------------------------------------------------- /tests/add_modules.spec.js: -------------------------------------------------------------------------------- 1 | var favoritjs = require('./mocks/favorit'); 2 | var $$ = require('../index.js')(favoritjs); 3 | 4 | describe('setting the device object prototype methods', function() { 5 | it('should add the get and set methods', function() { 6 | expect($$().get).toBeDefined(); 7 | expect($$('led').get).toBeDefined(); 8 | expect($$().set).toBeDefined(); 9 | expect($$('led').set).toBeDefined(); 10 | }); 11 | }); -------------------------------------------------------------------------------- /tests/error_testing.spec.js: -------------------------------------------------------------------------------- 1 | var favoritjs = require('./mocks/favorit'); 2 | var $$ = require('../index.js')(favoritjs); 3 | 4 | describe('error handling', function() { 5 | it('should allow the user to change the error handling', function() { 6 | var led = $$('led*1'); 7 | spyOn(led, 'onError'); 8 | led.set(function() {var err = 'this is in the error_testing.spec';}); 9 | expect(led.onError).toHaveBeenCalled(); 10 | }); 11 | }); -------------------------------------------------------------------------------- /gulpfile.js: -------------------------------------------------------------------------------- 1 | var gulp = require('gulp'); 2 | var jscs = require('gulp-jscs'); 3 | var jshint = require('gulp-jshint'); 4 | 5 | var jsFiles = ['./*.js', './**/*.js', '!./node_modules/*']; 6 | 7 | gulp.task('jshint', function() { 8 | gulp.src('./index.js') 9 | .pipe(jshint()) 10 | .pipe(jshint.reporter('default')); 11 | }); 12 | 13 | gulp.task('jscs', function() { 14 | return gulp.src(jsFiles) 15 | .pipe(jscs()) 16 | .pipe(jscs.reporter()); 17 | }); -------------------------------------------------------------------------------- /lib/close.js: -------------------------------------------------------------------------------- 1 | /* Close a gpio component. Closes only one component at a time 2 | */ 3 | function closeGpio() { 4 | _fvr[this._index].gpio.unexport(); 5 | _fvr[this._index].initialized = false; 6 | } 7 | 8 | /* Close i2c closes the entire i2c bus. 9 | Favor currently only supports a single i2c bus, 10 | this will need to change to support multiple in the future 11 | */ 12 | function closeI2C() { 13 | _fvr.i2c.closeSync(); 14 | for (var i = 0; i < _fvr.length; i++) { 15 | if (_fvr[i].interface === 'i2c') { 16 | _fvr[i].initialized = false; 17 | } 18 | }; 19 | } 20 | 21 | function close() { 22 | switch (this._component.interface) { 23 | case 'gpio': 24 | closeGpio.call(this); 25 | break; 26 | case 'i2c': 27 | closeI2C.call(this); 28 | default: 29 | return; 30 | } 31 | } 32 | 33 | module.exports = close; -------------------------------------------------------------------------------- /tests/mocks/gpio.js: -------------------------------------------------------------------------------- 1 | var thisOnChange; 2 | function watchChanges() { 3 | if (this.onChange.length > 0) { 4 | this.onChange.forEach(function(cbWatch) { 5 | cbWatch(this.pinValue); 6 | }); 7 | } 8 | } 9 | function MockPiPins() { 10 | return { 11 | onChange: [], 12 | read: function(cb) { 13 | watchChanges.call(this); 14 | cb.call(this, null, this.pin.value || 0); 15 | }, 16 | write: function(val, cb) { 17 | this.pin.value = val; 18 | 19 | watchChanges.call(this); 20 | return cb.call(this, null, this.pin.value); 21 | }, 22 | watch: function(cb) { 23 | this.onChange.push(cb); 24 | }, 25 | unwatch: function(cb) { 26 | var idx = this.onChange.indexOf(cb); 27 | this.onChange.splice(idx, 1); 28 | }, 29 | unexport: function() { 30 | // what to put in unexport mock? 31 | }, 32 | pin: {} 33 | }; 34 | }; 35 | 36 | module.exports = function() { 37 | return new MockPiPins; 38 | }; -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "author": "Pete Field aka. PedalPete", 3 | "name": "favor", 4 | "description": "A simple library for creating hardware-agnostic IoT applications in Javascript", 5 | "version": "0.0.20", 6 | "main": "./index.js", 7 | "keywords": [ 8 | "iot", 9 | "hardware", 10 | "internet of things", 11 | "robotics", 12 | "favor", 13 | "gpio", 14 | "spi", 15 | "i2c", 16 | "raspberrypi", 17 | "beaglebone" 18 | ], 19 | "scripts": { 20 | "test": "envade DEVICE_MOCK_PATH=../../tests/mocks/ -- jasmine-node tests --autotest --watch ." 21 | }, 22 | "dependencies": { 23 | "async": "^1.4.2" 24 | }, 25 | "optionalDependencies" : { 26 | "onoff": "1.0.4", 27 | "i2c-bus": "1.0.3", 28 | "pi-spi": "1.0.1" 29 | }, 30 | "devDependencies": { 31 | "envade": "^2.0.0", 32 | "fs-extra": "^0.30.0", 33 | "gulp": "^3.9.0", 34 | "gulp-jscs": "^3.0.0", 35 | "gulp-jshint": "^1.11.2", 36 | "jasmine-node": "^1.14.5" 37 | }, 38 | "repository": { 39 | "type": "git", 40 | "url": "https://github.com/favor/it.git" 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /license.mit: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | [OSI Approved License] 3 | The MIT License (MIT) 4 | 5 | Copyright (c) 6 | 7 | Permission is hereby granted, free of charge, to any person obtaining a copy 8 | of this software and associated documentation files (the "Software"), to deal 9 | in the Software without restriction, including without limitation the rights 10 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 11 | copies of the Software, and to permit persons to whom the Software is 12 | furnished to do so, subject to the following conditions: 13 | 14 | The above copyright notice and this permission notice shall be included in 15 | all copies or substantial portions of the Software. 16 | 17 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 20 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 21 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 22 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 23 | THE SOFTWARE. -------------------------------------------------------------------------------- /tests/protocols.spec.js: -------------------------------------------------------------------------------- 1 | var favoritjs = require('./mocks/favorit'); 2 | var $$ = require('../index.js')(favoritjs); 3 | 4 | describe('protocol', function() { 5 | it('should match the component interface', function() { 6 | var protocols = []; 7 | var spiCount = _fvr.spi ? _fvr.spi.counts.length : 0; 8 | spiCount += $$('led[interface=spi]')._componentMatches.filter(function(c) { 9 | return !_fvr[c].initialized; 10 | }).length || 0; 11 | 12 | // TODO: This is a crap test! what was I even trying to prove here? 13 | var i2cCount = _fvr.i2c ? _fvr.i2c.counts.writeI2cBlock.length : 0; 14 | i2cCount += $$('led[interface=i2c]')._componentMatches.filter(function(c) { 15 | return !_fvr[c].initialized; 16 | }).length || 0; 17 | 18 | runs(function() { 19 | $$('led').set(1, function(val) { 20 | protocols.push(val); 21 | }); 22 | }); 23 | 24 | waitsFor(function() { 25 | return protocols.length === 9; 26 | },1000); 27 | 28 | runs(function() { 29 | expect(protocols.length).toBe(9); 30 | expect(_fvr.spi.counts.length).toBe(spiCount + 31 | $$('led[interface=spi]')._componentMatches.length); 32 | 33 | // added " -1 " to i2c because close test was breaking this test 34 | expect(_fvr.i2c.counts.writeI2cBlock.length).toBe(i2cCount + 35 | $$('led[interface=i2c]')._componentMatches.length - 1); 36 | }); 37 | }); 38 | }); -------------------------------------------------------------------------------- /tests/favor_obj_builder.spec.js: -------------------------------------------------------------------------------- 1 | var fs = require('fs-extra'); 2 | var favoritjs = require('./mocks/favorit'); 3 | var $$ = require('../index.js')(favoritjs); 4 | 5 | var gpioPath = 'tests/gpio-test/class/gpio'; 6 | 7 | describe('device build', function() { 8 | it('should describe the structure of the device', function() { 9 | expect($$().name).toBe('Test-Device'); 10 | expect($$().get).toBeDefined(); 11 | }); 12 | }); 13 | 14 | describe('get device with query', function() { 15 | it('should return the query provided', function() { 16 | expect($$('led').query).toBe('led'); 17 | }); 18 | }); 19 | 20 | describe('get device parsed query' , function() { 21 | it('should show how the query was parsed', function() { 22 | expect($$('led').parsedQuery.type).toBe('led'); 23 | expect($$('led.blue,red').parsedQuery.class[0]).toBe('blue'); 24 | expect($$('led*3').parsedQuery.count).toBe(3); 25 | }); 26 | }); 27 | 28 | describe('add component specfic methods', function() { 29 | it('should have a test method', function() { 30 | var leds = $$('led'); 31 | expect(_fvr[leds._componentMatches[2]].get).toBeDefined(); 32 | }); 33 | }); 34 | 35 | describe('get values from linked component', function() { 36 | it('should find the component details through the link', function() { 37 | expect($$('temperature')._componentMatches[0]).toBeGreaterThan(-1); 38 | expect($$('humidity')._componentMatches[0]).toBeGreaterThan(-1); 39 | }); 40 | }); -------------------------------------------------------------------------------- /lib/protocols/i2c.js: -------------------------------------------------------------------------------- 1 | var I2C = process.env.DEVICE_MOCK_PATH ? 2 | require(process.env.DEVICE_MOCK_PATH + '/i2c') : 3 | require('i2c-bus'); 4 | var componentUtils = require('../component_utils'); 5 | 6 | function getI2cFunc(i2cCmd) { 7 | if (i2cCmd.type === 'read') return 'readI2cBlock'; 8 | if (i2cCmd.type === 'write') return 'writeI2cBlock'; 9 | return i2cCmd.type; 10 | } 11 | 12 | function i2c(i2cCmd, cb) { 13 | var i2cCb = function(err, length, val) { 14 | cb(err, val); 15 | }; 16 | var i2cFunc = getI2cFunc(i2cCmd); 17 | var val = i2cCmd.val; 18 | if (!Buffer.isBuffer(val)) val = new Buffer(val); 19 | var valLength = val.length || 0; 20 | _fvr.i2c[i2cFunc](i2cCmd.address, i2cCmd.cmd, valLength, val, i2cCb); 21 | } 22 | 23 | module.exports = function() { 24 | var fvr = this; 25 | 26 | if (!fvr.getI2cBus === undefined) { 27 | return fvr.onError(this, 'i2c bus address must be specified'); 28 | } 29 | if (!_fvr.i2c) { 30 | _fvr.i2c = I2C.openSync(fvr.getI2cBus()); 31 | } 32 | var i2cMethod = componentUtils.runMethod.bind(fvr); 33 | 34 | if (!_fvr[fvr._index].init || _fvr[fvr._index].initialized) { 35 | if (!_fvr[fvr._index].initialized) _fvr[fvr._index].initialized = true; //no init sequence needed 36 | // if this initialize was called but not needed, return the callback 37 | if (!fvr._method) return fvr._callback.call(fvr, null); 38 | return i2cMethod(i2c); 39 | } 40 | // if this device has not been initialized, initialize then run the required method 41 | i2cMethod(i2c, true); 42 | }; -------------------------------------------------------------------------------- /lib/protocols/spi.js: -------------------------------------------------------------------------------- 1 | var SPI = process.env.DEVICE_MOCK_PATH ? 2 | require(process.env.DEVICE_MOCK_PATH + 'spi') : 3 | require('pi-spi'); 4 | var componentUtils = require('../component_utils'); 5 | 6 | function respond(res, val) { 7 | componentUtils.emitEvents.call(this, val); 8 | if (this._component.formatOutput) { 9 | return this._callback.call(this, 10 | this._component.formatOutput.call(this, val)); 11 | } 12 | this._callback.call(this, val); 13 | } 14 | 15 | function getWriteValue(cmd) { 16 | if (cmd.val) return cmd.val; 17 | if (cmd.length) return cmd.length; 18 | return 0; 19 | } 20 | 21 | function spi(cmd, cb) { 22 | var write = getWriteValue(cmd); 23 | if (!Buffer.isBuffer(write)) write = new Buffer(write); 24 | var length = cmd.length ? cmd.length : write.length || 0; 25 | return _fvr[cmd._componentIndex]._spi.transfer(write, 26 | length, function(err, data) { 27 | cb(err, data); 28 | } 29 | ); 30 | } 31 | 32 | function init() { 33 | var fvr = this; 34 | var device = _fvr[this._index]; 35 | device._spi = new SPI.initialize(device.address); 36 | var spiMethod = componentUtils.runMethod.bind(fvr); 37 | // Does the device have an initialize sequence or process? 38 | if (device.init) return spiMethod(spi, true); 39 | device.initialized = true; 40 | spiMethod(spi); 41 | } 42 | 43 | module.exports = function() { 44 | var fvr = this; 45 | var device = _fvr[this._index]; 46 | if (!_fvr[fvr._index].initialized) return init.call(fvr); 47 | if (fvr._method === 'initialize') return fvr._callback.call(fvr, null); 48 | var spiMethod = componentUtils.runMethod.bind(fvr); 49 | spiMethod(spi); 50 | }; -------------------------------------------------------------------------------- /tests/mocks/i2c.js: -------------------------------------------------------------------------------- 1 | function increment(cmd, evt) { 2 | _fvr.i2c.counts[cmd].push(evt.toString()); 3 | } 4 | 5 | var openSync = function(address) { 6 | _fvr.i2c = new MockObj(address); 7 | increment('open', 1); 8 | return _fvr.i2c; 9 | }; 10 | 11 | var writeI2cBlock = function(address, cmd, length, val, cb) { 12 | increment('writeI2cBlock',[address, cmd, Buffer.isBuffer(val)]); 13 | cb.call(this, null, length, val); 14 | }; 15 | 16 | var readI2cBlock = function(address, cmd, length, val, cb) { 17 | increment('readI2cBlock', [address, cmd, Buffer.isBuffer(val)]); 18 | cb.call(this, null, length, val); 19 | }; 20 | var on = function(data) { 21 | var res; 22 | setTimeout( 23 | function() { 24 | return res = 'on called with on data'; 25 | }, 26 | 2000); 27 | }; 28 | 29 | var stream = function(command, length, delay) { 30 | var Stream = new mockstream.MockDataStream({ 31 | chunkSize: length, 32 | streamLength: length * 10 33 | }); 34 | Stream.start(); 35 | }; 36 | 37 | var reset = function() { 38 | this.writeI2cBlock = []; 39 | this.readI2cBlock = []; 40 | this.open = []; 41 | }; 42 | 43 | var closeSync = function() { 44 | // not sure how to get this into an output... 45 | }; 46 | 47 | var MockObj = function(address) { 48 | return { 49 | address: address, 50 | writeI2cBlock: writeI2cBlock, 51 | readI2cBlock: readI2cBlock, 52 | on: on, 53 | stream: stream, 54 | closeSync: closeSync, 55 | counts: { 56 | writeI2cBlock: [], 57 | readI2cBlock: [], 58 | open: [], 59 | reset: reset 60 | }, 61 | }; 62 | }; 63 | 64 | var I2C = { 65 | openSync: openSync 66 | }; 67 | 68 | module.exports = I2C; -------------------------------------------------------------------------------- /tests/device_abilities.spec.js: -------------------------------------------------------------------------------- 1 | var favoritjs = require('./mocks/favorit'); 2 | var $$ = require('../index.js')(favoritjs); 3 | 4 | describe('components', function() { 5 | 6 | it('should match multiple components', function() { 7 | var leds = $$('led'); 8 | expect(leds._componentMatches.length).toBe(7); 9 | expect(_fvr[leds._componentMatches[0]].address).toBe(1); 10 | expect(_fvr[leds._componentMatches[1]].address).toBe(2); 11 | }); 12 | 13 | it('should get the correct number of results', function() { 14 | var leds = $$('led*4'); 15 | expect(leds._componentMatches.length).toBe(4); 16 | }); 17 | 18 | it('should get the led by name', function() { 19 | var led = $$('led#rgb'); 20 | expect(_fvr[led._componentMatches[0]].structure.red.address).toBe(4); 21 | expect(_fvr[led._componentMatches[0]].structure.green.address).toBe(5); 22 | expect(_fvr[led._componentMatches[0]].structure.blue.address).toBe(6); 23 | }); 24 | 25 | it('should get the the led based on the color', function() { 26 | var redLed = $$('led.red,yellow'); 27 | expect(_fvr[redLed._componentMatches[0]].address).toBe(1); 28 | expect(_fvr[redLed._componentMatches[1]].structure.red.address).toBe(4); 29 | }); 30 | 31 | it('should not return where a class does not match', function() { 32 | var noMatch = $$('led.none'); 33 | expect(noMatch._componentMatches.length).toBe(0); 34 | }); 35 | 36 | it('should search components by attribute', function() { 37 | var spiLed = $$('led[interface=spi]'); 38 | expect(spiLed._componentMatches.length).toBe(1); 39 | }); 40 | }); 41 | 42 | describe('linked components', function() { 43 | it('should return the linked component details', function() { 44 | var temp = $$('temperature'); 45 | expect(_fvr[temp._componentMatches[0]].address).toBe(8); 46 | expect(_fvr[temp._componentMatches[1]].address).toBe(9); 47 | var humidity = $$('humidity'); 48 | expect(_fvr[humidity._componentMatches[0]].address).toBe(8); 49 | }); 50 | }); -------------------------------------------------------------------------------- /lib/protocols/gpio.js: -------------------------------------------------------------------------------- 1 | var Gpio = process.env.DEVICE_MOCK_PATH ? 2 | require(process.env.DEVICE_MOCK_PATH + 'gpio') : 3 | require('onoff').Gpio; 4 | 5 | var file = 'protocols/gpio.js'; 6 | var componentUtils = require('../component_utils'); 7 | 8 | function initialize() { 9 | var component = componentUtils.getComponent.call(this); 10 | if (!component.direction && _fvr[this._parent] && 11 | _fvr[this._parent].direction) { 12 | component.direction = _fvr[this._parent].direction; 13 | } 14 | 15 | var options = {}; 16 | if (component.debounce) options.debounce = component.debounce; 17 | component.gpio = new Gpio(component.address, 18 | component.direction, 19 | component.edge || 'none', 20 | options); 21 | component.initialized = true; 22 | } 23 | 24 | function returnCallback(val, cb) { 25 | var self = this; 26 | if (_fvr[self._index].formatOutput && cb) { 27 | return cb.call(self, 28 | _fvr[self._index].formatOutput.call(self, val)); 29 | } 30 | if (self._returnAs) return cb.call(self, val[self._returnAs]); 31 | if (cb) return cb.call(self, val); 32 | return; 33 | } 34 | 35 | function get() { 36 | var self = this; 37 | _fvr[this._index].gpio.read(function(err, val) { 38 | if (err) return self.onError.call(self, 'error getting gpio ', err); 39 | returnCallback.call(self, val, self._callback); 40 | }); 41 | } 42 | 43 | function isValidVal() { 44 | if (typeof this._valueToSet === 'number') return true; 45 | return this.onError.call(this, 'invalid value ' + 46 | this._valueToSet + ' to set GPIO.', 46, file); 47 | } 48 | 49 | function set() { 50 | var self = this; 51 | var val = this._valueToSet; 52 | if (!isValidVal.call(this, val)) return; 53 | var component = componentUtils.getComponent.call(this); 54 | component.gpio.write(val, function(err, val) { 55 | if (err) return self.onError.call(self, err); 56 | if (self._formatOutput) val = _fvr[self._index].formatOutput.call(self, val); 57 | if (self._returnAs) return val[self._returnAs]; 58 | self._callback.call(self, val); 59 | }); 60 | } 61 | 62 | function watch() { 63 | var fvr = this; 64 | var component = componentUtils.getComponent.call(this); 65 | component.gpio.watch(this._callback); 66 | } 67 | 68 | function unwatch() { 69 | var self = this; 70 | var component = componentUtils.getComponent.call(self); 71 | componentUtils.removeWatch.call(self); 72 | // remove watched intervals 73 | component.gpio.unwatch(this._callback); 74 | } 75 | module.exports = { 76 | initialize: initialize, 77 | set: set, 78 | get: get, 79 | watch: watch, 80 | unwatch: unwatch 81 | }; -------------------------------------------------------------------------------- /lib/device_abilities.js: -------------------------------------------------------------------------------- 1 | var parseObject = function(el) { 2 | return new ParsedObject(this, el); 3 | }; 4 | 5 | function ParsedObject(device, el) { 6 | this.device = device; 7 | return this.init(el); 8 | } 9 | 10 | ParsedObject.prototype = { 11 | init: function(el) { 12 | this.el = el; 13 | return this.getComponent(el); 14 | }, 15 | getComponent: function(el) { 16 | var elObj = this.splitElement(el); 17 | return this.filterComponents(elObj); 18 | }, 19 | splitElement: function(el) { 20 | var type = el.match(/^\w+/)[0]; 21 | var count = el.match(/\*(\d+)/); 22 | var name = el.match(/#([\w+-_]+)/); 23 | var classes = el.match(/\.([\w,-_]+)/); 24 | var attribute = el.match(/\[(\w+)\=(\w+)\]/); 25 | if (classes) { 26 | classes = classes[1].split(','); 27 | } 28 | 29 | var query = el; 30 | parsedQuery = { 31 | type: type, 32 | count: !count ? null : parseFloat(count[1]), 33 | name: name ? name[1] : null, 34 | class: classes 35 | }; 36 | 37 | if (attribute) parsedQuery[attribute[1]] = attribute[2]; 38 | this.device.parsedQuery = parsedQuery; 39 | this.device.query = query; 40 | return parsedQuery; 41 | }, 42 | propertyMatch: function(elProp, componentProp) { 43 | //if we're not searching on el_prop, just return true 44 | //if we are searching on el_prop, it must match component_prop 45 | if (elProp === null || elProp === componentProp) return true; 46 | return false; 47 | }, 48 | checkClass: function(classes, component) { 49 | for (var key in component) { 50 | if (key !== 'structure' && classes.indexOf(component[key]) >= 0) { 51 | return true; 52 | } 53 | } 54 | if (component.structure) { 55 | var structureArray = Object.keys(component.structure); 56 | var hasClass = structureArray.filter(function(struct) { 57 | return classes.indexOf(struct) >= 0; 58 | }); 59 | return hasClass.length > 0; 60 | } 61 | return false; 62 | }, 63 | filterComponents: function(elObj) { 64 | //find the matching components 65 | var matches = []; 66 | for (var i = 0; i < _fvr.length; i++) { 67 | var component = _fvr[i]; 68 | var isTrue = true; 69 | var skipCheck = ['count', 'class']; 70 | for (var key in elObj) { 71 | if (skipCheck.indexOf(key) == -1 && isTrue) { 72 | isTrue = this.propertyMatch(elObj[key], component[key]); 73 | } 74 | //if key is class, check attributes and structure 75 | if (key === 'class' && elObj.class !== null) { 76 | isTrue = this.checkClass(elObj.class,component); 77 | } 78 | } 79 | if (isTrue) matches.push(i); 80 | if (elObj.count === matches.length) { 81 | this.device._componentMatches = matches; 82 | return this.device; 83 | } 84 | } 85 | 86 | this.device._componentMatches = matches; 87 | return this.device; 88 | } 89 | }; 90 | 91 | module.exports.deviceAbilities = parseObject; -------------------------------------------------------------------------------- /tests/mocks/spi.js: -------------------------------------------------------------------------------- 1 | exports.mode = { 2 | CPHA: 0x01, 3 | CPOL: 0x02 4 | }; 5 | 6 | exports.order = { 7 | MSB_FIRST: 0, 8 | LSB_FIRST: 1 9 | }; 10 | 11 | function isFunction(object) { 12 | return object && typeof object == 'function'; 13 | } 14 | 15 | var initialize = function(dev) { 16 | var spi = { 17 | address: dev, 18 | transferred: 0, 19 | counts: [] 20 | }; 21 | 22 | _fvr.spi = spi; 23 | 24 | spi.increment = function(cmd, evt) { 25 | spi.counts.push([cmd, evt]); 26 | spi.transferred++; 27 | }; 28 | 29 | spi.reset = function() { 30 | spi.counts = []; 31 | spi.transferred = 0; 32 | }; 33 | 34 | spi.clockSpeed = function(speed) { 35 | if (arguments.length < 1) return _speed; 36 | else if (typeof speed === 'number') { 37 | _speed = speed; 38 | } else { 39 | throw TypeError('Clock speed must be a number.'); 40 | }; 41 | }; 42 | 43 | spi.dataMode = function(mode) { 44 | if (arguments.length < 1) return _mode; 45 | else if (typeof mode === 'number') { 46 | _mode = mode; 47 | } else { 48 | throw TypeError('Data mode should be CPHA or CPOL.'); 49 | } 50 | }; 51 | 52 | spi.bitOrder = function(order) { 53 | if (arguments.length < 1) return _order; 54 | else if (typeof order === 'number') { 55 | _order = order; 56 | } else { 57 | throw TypeError('Bit order should be MSB_FIRST or LSB_FIRST.'); 58 | } 59 | }; 60 | 61 | function _transfer(txbuf, length, callback) { 62 | var err = null; 63 | isFunction(callback) && callback(err, spi); 64 | } 65 | 66 | spi.write = function(writebuf, cb) { 67 | if (!Buffer.isBuffer(writebuf)) { 68 | throw TypeError('Write data is not a buffer'); 69 | } 70 | if (typeof cb !== 'function') throw TypeError('Callback not provided'); 71 | _transfer(writebuf, 0, cb); 72 | }; 73 | 74 | spi.read = function(readcount, cb) { 75 | if (typeof readcount !== 'number') { 76 | throw TypeError('Read count is not a number'); 77 | } 78 | if (typeof cb !== 'function') throw TypeError('Callback not provided'); 79 | _transfer(null, readcount, cb); 80 | }; 81 | spi.transfer = function(writebuf, readcount, cb) { 82 | spi.increment('transfer', [writebuf, readcount, !Buffer.isBuffer(writebuf)]); 83 | if (!Buffer.isBuffer(writebuf)) { 84 | throw TypeError('Write data is not a buffer'); 85 | } 86 | if (typeof readcount === 'function') { 87 | cb = readcount; 88 | readcount = writebuf.length; 89 | } else if (typeof readcount !== 'number') { 90 | throw TypeError('Read count is not a number'); 91 | } 92 | if (typeof cb !== 'function') throw TypeError('Callback not provided'); 93 | _transfer(writebuf, readcount, cb); 94 | }; 95 | spi.close = function() { 96 | fs.close(_fd); 97 | }; 98 | 99 | return spi; 100 | }; 101 | 102 | var SPI = { 103 | initialize: initialize 104 | }; 105 | 106 | module.exports = SPI; -------------------------------------------------------------------------------- /.jscsrc: -------------------------------------------------------------------------------- 1 | { 2 | "requireCurlyBraces": [ 3 | "else", 4 | "for", 5 | "while", 6 | "do", 7 | "try", 8 | "catch" 9 | ], 10 | "requireOperatorBeforeLineBreak": true, 11 | "validateLineBreaks": "LF", 12 | "requireCamelCaseOrUpperCaseIdentifiers": true, 13 | "maximumLineLength": { 14 | "value": 80, 15 | "allExcept": ["comments", "regex"] 16 | }, 17 | "validateIndentation": "\t", 18 | "validateQuoteMarks": "'", 19 | "disallowMultipleLineStrings": true, 20 | "disallowMixedSpacesAndTabs": true, 21 | "disallowTrailingWhitespace": true, 22 | "disallowSpaceAfterPrefixUnaryOperators": true, 23 | "disallowMultipleVarDecl": true, 24 | "disallowKeywordsOnNewLine": ["else"], 25 | 26 | "requireSpaceAfterKeywords": [ 27 | "if", 28 | "else", 29 | "for", 30 | "while", 31 | "do", 32 | "switch", 33 | "return", 34 | "try", 35 | "catch" 36 | ], 37 | "requireSpaceBeforeBinaryOperators": [ 38 | "=", "+=", "-=", "*=", "/=", "%=", "<<=", ">>=", ">>>=", 39 | "&=", "|=", "^=", "+=", 40 | 41 | "+", "-", "*", "/", "%", "<<", ">>", ">>>", "&", 42 | "|", "^", "&&", "||", "===", "==", ">=", 43 | "<=", "<", ">", "!=", "!==" 44 | ], 45 | "requireSpaceAfterBinaryOperators": true, 46 | "requireSpacesInConditionalExpression": true, 47 | "requireSpaceBeforeBlockStatements": true, 48 | "requireSpacesInForStatement": true, 49 | "requireLineFeedAtFileEnd": false, 50 | "requireSpacesInFunctionExpression": { 51 | "beforeOpeningCurlyBrace": true 52 | }, 53 | "disallowSpacesInAnonymousFunctionExpression": { 54 | "beforeOpeningRoundBrace": true 55 | }, 56 | "disallowSpacesInsideObjectBrackets": "all", 57 | "disallowSpacesInsideArrayBrackets": "all", 58 | "disallowSpacesInsideParentheses": true, 59 | 60 | "disallowMultipleLineBreaks": true, 61 | "disallowNewlineBeforeBlockStatements": true, 62 | "disallowKeywords": ["with"], 63 | "disallowSpacesInFunctionExpression": { 64 | "beforeOpeningRoundBrace": true 65 | }, 66 | "disallowSpacesInFunctionDeclaration": { 67 | "beforeOpeningRoundBrace": true 68 | }, 69 | "disallowSpacesInCallExpression": true, 70 | "disallowSpaceAfterObjectKeys": true, 71 | "requireSpaceBeforeObjectValues": true, 72 | "requireCapitalizedConstructors": true, 73 | "requireDotNotation": true, 74 | "requireSemicolons": true, 75 | "validateParameterSeparator": ", ", 76 | 77 | "jsDoc": { 78 | "checkAnnotations": "closurecompiler", 79 | "checkParamNames": true, 80 | "requireParamTypes": true, 81 | "checkRedundantParams": true, 82 | "checkReturnTypes": true, 83 | "checkRedundantReturns": true, 84 | "requireReturnTypes": true, 85 | "checkTypes": true, 86 | "checkRedundantAccess": true, 87 | "requireNewlineAfterDescription": true 88 | } 89 | } -------------------------------------------------------------------------------- /tests/spi_getters_setters.spec.js: -------------------------------------------------------------------------------- 1 | var favoritjs = require('./mocks/favorit'); 2 | var $$ = require('../index.js')(favoritjs); 3 | 4 | describe('spi', function() { 5 | it('should initialize a new spi', function() { 6 | var getSpi = false; 7 | var device; 8 | runs(function() { 9 | $$('temperature#spi').get(function(data) { 10 | device = this; 11 | getSpi = data[0]; 12 | }); 13 | }); 14 | 15 | waitsFor(function() { 16 | return getSpi; 17 | },1000); 18 | 19 | runs(function() { 20 | expect(getSpi.counts.length).toBe(1); 21 | expect(_fvr[device._index].initialized).toBeTruthy(); 22 | }); 23 | }); 24 | 25 | it('should transfer without initializing again', function() { 26 | var getSpi = false; 27 | var device; 28 | runs(function() { 29 | $$('temperature#spi').get(function(data) { 30 | device = this; 31 | getSpi = data[0]; 32 | }); 33 | }); 34 | 35 | waitsFor(function() { 36 | return getSpi; 37 | },1000); 38 | 39 | runs(function() { 40 | expect(getSpi.counts.length).toBe(2); 41 | getSpi.reset(); 42 | }); 43 | }); 44 | 45 | // Watch events not cancelling 46 | // it('should watch an spi', function() { 47 | // var reqs = 0; 48 | // var device; 49 | // var getSpi = false; 50 | // function getData(data) { 51 | // reqs++; 52 | // if (reqs === 5) { 53 | // device = this; 54 | // getSpi = data; 55 | // } 56 | // } 57 | 58 | // runs(function() { 59 | // $$('temperature#spi').on('change', getData); 60 | // }); 61 | 62 | // waitsFor(function() { 63 | // return getSpi; 64 | // $$('temperature#spi').removeListener('change', getData); 65 | // },1000); 66 | 67 | // runs(function() { 68 | // expect(device._component._spi.transferred).toBeGreaterThan(4); 69 | // }); 70 | // }); 71 | 72 | it('should format output', function() { 73 | var output; 74 | 75 | runs(function() { 76 | $$('accelerometer#formatOutputSpi').get(function(data) { 77 | output = data; 78 | }); 79 | }); 80 | 81 | waitsFor(function() { 82 | return output; 83 | }, 1000); 84 | 85 | runs(function() { 86 | expect(output).toBe('postFormat returned'); 87 | }); 88 | }); 89 | 90 | it('should format an input value', function() { 91 | var output; 92 | 93 | runs(function() { 94 | $$('led#spiSet').set('test color', function(data) { 95 | output = data; 96 | }); 97 | }); 98 | 99 | waitsFor(function() { 100 | return output; 101 | }, 1000); 102 | 103 | runs(function() { 104 | expect(output).toBe('test color'); 105 | }); 106 | }); 107 | }); 108 | 109 | describe('initialize only', function() { 110 | it('should initialize spi', function() { 111 | var init; 112 | runs(function() { 113 | $$('init_only_spi').initialize(function(val) { 114 | init = val; 115 | }); 116 | }); 117 | 118 | waitsFor(function() { 119 | return init; 120 | }, 500); 121 | 122 | runs(function() { 123 | expect(init).toBe(true); 124 | }); 125 | }); 126 | }); -------------------------------------------------------------------------------- /lib/favor_obj_builder.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | var fs = require('fs'); 3 | var componentUtils = require('./component_utils'); 4 | var configFile; 5 | var modules; 6 | var favorJson; 7 | var prototypeModules; 8 | var coreModules = ['../lib/device_abilities.js', 9 | '../lib/getters_and_setters.js', 10 | '../lib/error_handling.js']; 11 | var addModules = require('../lib/add_modules.js'); 12 | 13 | function convertToObject(jsonObj, obj) { 14 | //this converts our json object into a javascript object with prototype 15 | Object.keys(jsonObj).forEach(function(key) { 16 | obj[key] = jsonObj[key]; 17 | }); 18 | return obj; 19 | } 20 | 21 | function addRootMethods(prototypeObject, favorJson) { 22 | var keys = Object.keys(favorJson).filter(function(c) { 23 | return c !== 'components'; 24 | }); 25 | 26 | var functionNames = keys.map(function(m) { 27 | return componentUtils.convertToCamelCase(m); 28 | }); 29 | 30 | functionNames.forEach(function(fnc, idx) { 31 | var getfnc = function() { 32 | return favorJson[keys[idx]]; 33 | }; 34 | prototypeObject['get' + fnc] = getfnc; 35 | }); 36 | } 37 | 38 | function setupComponents(favorObj) { 39 | favorObj.components.forEach(function(component) { 40 | if (component.link) { 41 | //link component to it's linked values from another component 42 | var linkedComponent = favorObj.components.filter(function(lnkCmp) { 43 | return lnkCmp.name === component.link; 44 | })[0]; 45 | if (linkedComponent) { 46 | if (linkedComponent.address) { 47 | component.address = linkedComponent.address; 48 | } 49 | if (linkedComponent.structure) { 50 | var linkStruct = component.type; 51 | if (linkedComponent.structure[linkStruct].address) { 52 | //the structure is directly addressed, so link return the direct address 53 | component.address = linkedComponent.structure[linkStruct].address; 54 | } else { 55 | // the structure has a nested structure or is not directly addressed, so return the nested structure 56 | component.structure = linkedComponent.structure[linkStruct]; 57 | } 58 | } 59 | // check if the linked component has methods, and if so 60 | if (linkedComponent.methods) { 61 | addMethods(linkedComponent.methods,component); 62 | } 63 | } 64 | } 65 | if (component.methods) { 66 | addMethods(component.methods, component); 67 | } 68 | }); 69 | return global._fvr = favorObj.components; 70 | } 71 | 72 | function FavorObj() {}; 73 | 74 | FavorObj.prototype.init = function(passedVar) { 75 | //search for passed components 76 | return this.deviceAbilities(passedVar); 77 | }; 78 | 79 | function favorCore(passedVar) { 80 | var newFavor = new FavorObj; 81 | convertToObject(favorJson, newFavor); 82 | for (var key in prototypeModules) { 83 | newFavor[key] = prototypeModules[key]; 84 | } 85 | if (passedVar) { 86 | return newFavor.init(passedVar); 87 | } 88 | return newFavor; 89 | } 90 | 91 | module.exports = function(config, modules) { 92 | favorJson = config; 93 | if (modules) { 94 | coreModules.push.apply(coreModules, modules); 95 | } 96 | prototypeModules = addModules(coreModules); 97 | addRootMethods(prototypeModules, favorJson); 98 | Object.keys(prototypeModules).forEach(function(protoName) { 99 | favorCore[protoName] = prototypeModules[protoName]; 100 | }); 101 | setupComponents(favorJson); 102 | return favorCore; 103 | }; -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | #Favor 2 | ![Favor Logo](https://avatars0.githubusercontent.com/u/6392732?v=3&u=f09ed61c17bb341847c81028090c36c5b005aa8b&s=200) 3 | 4 | [![bitHound Overall Score](https://www.bithound.io/github/favor/it/badges/score.svg)](https://www.bithound.io/github/favor/it) 5 | [![bitHound Dependencies](https://www.bithound.io/github/favor/it/badges/dependencies.svg)](https://www.bithound.io/github/favor/it/master/dependencies/npm) 6 | [![bitHound Code](https://www.bithound.io/github/favor/it/badges/code.svg)](https://www.bithound.io/github/favor/it) 7 | 8 | # A simple library for creating hardware agnostic IoT devices. 9 | 10 | Favor is a javascript/node.js library to abstract away complex and inconsistent hardware interfaces into a single simple to use API. 11 | 12 | ## In a nutshell 13 | 14 | Have you ever wanted to write 15 | 16 | `$$('temperature').get(function(temp) { console.log('The temperature is ', temp) };` 17 | 18 | Well, now you can, and run on multiple devices without mixing business logic with device configuration considerations. 19 | 20 | Check out the demo video of getting started with Favor. 21 | [![demo hello world led video](http://i3.ytimg.com/vi/bHKyFJ41amA/hqdefault.jpg)](https://www.youtube.com/watch?v=bHKyFJ41amA "Getting Started With Favor") 22 | 23 | ### What do you mean by Hardware agnostic 24 | 25 | In this sense, hardware agnostic means that you can write your business logic 26 | without needing to worry about what hardware it will run on. 27 | 28 | Currently Favor works with linux based devices like the Raspberry Pi, Beaglebone 29 | and others. But more than just the hardware platform, with Favor you can run 30 | your application on different chips and sensors, without needing to consider 31 | what type of chip the application is runnning on, or even what protocol (gpio, I2C, SPI). 32 | 33 | Favor makes it possible to run your application on completely different hardware. 34 | 35 | Other platforms are currently in the process of being supported, and more 36 | protocols can be added as well. Need something special? Just ask! 37 | 38 | ### Supported Versions of node 39 | Favor has been tested with node v4, it should also work with v5. v0.10 and v0.12 have not been tested and may not work. 40 | 41 | ## What's supported? 42 | Currently Favor has been tested on RaspberryPi v1b and Beaglebone Black, 43 | but any linux device which runs node.js should work. I'll be happy to test with other devices, 44 | I just need to get access to them. 45 | 46 | Favor works with GPIO, i2C and SPI protocols. 47 | 48 | ### How Does It Work? 49 | 50 | Favor uses a js configuration file stored on your device which describes the structure of your hardware. 51 | It queries this js file, similar to how jQuery parses and interacts with the DOM. 52 | Once Favor knows what devices are connected, and how to interact with them, 53 | you can easly write jQuery style statements like `$$('temperature').get(callback)` 54 | will get you the temperature on any device running Favor which has a temperature sensor. 55 | 56 | ### What are the benefits of Favor 57 | 1) a single consistent api for interacting with different devices and protocols 58 | 59 | 2) a separation of concerns between hardware and software 60 | 61 | 3) write-once run-anywhere 62 | 63 | 4) testable logic which will run across different hardware devices 64 | 65 | ### Installing 66 | 67 | `npm install favor` 68 | 69 | ### See the favor wiki for more documentation 70 | [Favor Wiki](https://github.com/favor/it/wiki) 71 | 72 | ### Live-code example of getting data from a temperature sensor 73 | 74 | [![Get Temperature - live code](http://i3.ytimg.com/vi/ujHa-I3ZRUM/hqdefault.jpg)](https://www.youtube.com/watch?v=ujHa-I3ZRUM "Get Temperature live-code example") 75 | 76 | ### Live-code example of watching buttons and sensors 77 | [![Watch Sensors - live code](http://i3.ytimg.com/vi/-TNrHAl5VF0/hqdefault.jpg)](https://www.youtube.com/watch?v=-TNrHAl5VF0 "Watch buttons and values live-code example") -------------------------------------------------------------------------------- /tests/mocks/favorit.js: -------------------------------------------------------------------------------- 1 | function postFormat(val) { 2 | return 'postFormat returned'; 3 | } 4 | 5 | module.exports = { 6 | name: 'Test-Device', 7 | 'gpio-path': 'tests/gpio-test/class/gpio', 8 | 'i2c-bus': '/test/i2cbus', 9 | components: [ 10 | {type: 'led', color: 'yellow', address: 1, interface: 'gpio', 11 | direction: 'out'}, 12 | {type: 'led', address: 2, interface: 'gpio', direction: 'out'}, 13 | {type: 'led', address: 3, name: 'has_get', interface: 'gpio', 14 | get: require('./component_methods'), direction: 'out'}, 15 | {type: 'led', name: 'rgb', structure: { 16 | red: {address: 4}, green: {address: 5}, blue: {address: 6} 17 | }, interface: 'gpio', direction: 'out', formatInput: function(x) { 18 | if (typeof x === 'number') return x; 19 | return x[Object.keys(this._component.structure)[this._index]]; 20 | }}, 21 | {type: 'button', name: 'light',address: 7, interface: 'gpio', 22 | direction: 'in', interupt: true}, 23 | {type: 'link', name: 'rht03',address: 8, direction: 'in', 24 | get: require('./linked_temp_humidity_mock')}, 25 | {type: 'temperature', name: 'link', link: 'rht03'}, 26 | {type: 'humidity', link: 'rht03'}, 27 | {type: 'accelerometer', name: 'bridge', address: 1, init: [ 28 | {type: 'write', cmd: 0x2D, val: [1 << 3]}, 29 | {type: 'write', cmd: 0x31, val: [0x09]}, 30 | {type: 'write', cmd: 0x2c, val: [8 + 2 + 1]} 31 | ], get: {type: 'read', cmd: 0x33}, interface: 'i2c'}, 32 | {type: 'accelerometer', name: 'test_wait', address: 0x1d, init: [ 33 | {type: 'write', cmd: 0x2D, val: [1 << 3]}, 34 | {type: 'write', cmd: 0x31, val: [0x09], wait: 500}, 35 | {type: 'write', cmd: 0x2c, val: [8 + 2 + 1], wait: 500}], 36 | get: {type: 'read', cmd: 0x33}, interface: 'i2c'}, 37 | {type: 'accelerometer', name: 'init_stream', address: 0x1d, init: [ 38 | {type: 'write',cmd: 0x2D, val: [1 << 3]}, 39 | {type: 'write',cmd: 0x31, val: [0x09], wait: 500}, 40 | {type: 'write',cmd: 0x2c, val: [8 + 2 + 1], wait: 500}], 41 | get: {type: 'read', cmd: 0x33, val: 6}, interface: 'i2c'}, 42 | {type: 'led', name: 'blinkm', address: 0x09, 43 | init: {type: 'write', cmd: 0x6d}, 44 | set: {type: 'write', cmd: 0x6E, val: true}, interface: 'i2c'}, 45 | {type: 'led', address: 0x05, name: 'blinkm_with_func', 46 | set: {type: 'write', cmd: 0x6E, val: true, formatInput: function(val) { 47 | return [val.r, val.g, val.b]; 48 | }}, interface: 'i2c'}, 49 | {type: 'accelerometer', name: 'formatOutputI2c', 50 | address: 0x11, init: [ 51 | {type: 'write', cmd: 0x2D, val: [1 << 3]}, 52 | {type: 'write', cmd: 0x31, val: [0x09]}, 53 | {type: 'write', cmd: 0x2c, val: [8 + 2 + 1]} 54 | ], 55 | get: {type: 'read', cmd: 0x33, val: 6}, 56 | interface: 'i2c', formatOutput: postFormat}, 57 | {type: 'temperature', name: 'formatOutputGpio', address: 9, 58 | interface: 'gpio', direction: 'in', formatOutput: postFormat}, 59 | {type: 'temperature', name: 'spi', interface: 'spi', 60 | address: '/dev/spidev0.0', 61 | get: {val: [0x23, 0x48, 0xAF, 0x19, 0x19, 0x19]}}, 62 | {type: 'accelerometer', name: 'formatOutputSpi', address: 'dev/spidev0.1', 63 | 'get': {val: [0x11, 0xf2]}, 64 | formatOutput: postFormat, interface: 'spi'}, 65 | {type: 'led', name: 'spiSet', address: '/dev/spidev0.1', 66 | init: {val: [0x00, 0x00, 0x00, 0x00]}, 67 | set: {val: true, formatInput: function(val) { 68 | return val; 69 | }}, interface: 'spi', formatOutput: function(val) { return val.toString();}}, 70 | {type: 'link', name: 'HiH-6130', interface: 'i2c', address: 0x27, get: { 71 | type: 'write', cmd: 0, val: new Buffer(4)}, formatOutput: function(buf) { 72 | return { 73 | temperature: 80 74 | }; 75 | } 76 | }, 77 | {type: 'temperature', name: 'i2cLink', link: 'HiH-6130'}, 78 | {type: 'init_only', interface: 'gpio', address: 22}, 79 | {type: 'init_only_i2c', interface: 'i2c', address: 0x00, 80 | init: {type: 'write', cmd: 0}, get: {type: 'read', cmd: 1} 81 | }, 82 | {type: 'init_only_spi', interface: 'spi', address: '/dev/spidev0.1', 83 | init: {val: [0x00, 0x00]}, set: 0x3dD 84 | } 85 | ] 86 | }; -------------------------------------------------------------------------------- /lib/component_utils.js: -------------------------------------------------------------------------------- 1 | var async = require('async'); 2 | 3 | function getComponent() { 4 | if (this._parent) { 5 | var keyFromIndex = Object.keys(_fvr[this._parent].structure)[this._index]; 6 | return _fvr[this._parent].structure[keyFromIndex]; 7 | } 8 | return _fvr[this._index]; 9 | }; 10 | 11 | exports.getComponent = getComponent; 12 | 13 | function getMethod() { 14 | if (this._method === 'initialize') return 'init'; 15 | return this._method.name || this._method; 16 | } 17 | 18 | exports.getMethod = getMethod; 19 | 20 | function returnCallback(val) { 21 | var fvr = this; 22 | if (_fvr[fvr._index].formatOutput && val) { 23 | val = _fvr[fvr._index].formatOutput.call(fvr, val); 24 | } 25 | if (fvr._returnAs) return fvr._callback.call(fvr, val[this._returnAs]); 26 | fvr._callback.call(fvr, val); 27 | } 28 | 29 | function convertToArray(input) { 30 | if (!Array.isArray(input)) return [input]; 31 | return input; 32 | } 33 | 34 | function getValueToSet(cmd) { 35 | if (cmd.formatInput) { 36 | return cmd.formatInput.call(this, this._valueToSet, cmd); 37 | } 38 | if (cmd.val) return cmd.val === true ? this._valueToSet : cmd.val; 39 | return 0; 40 | } 41 | 42 | function runMethod(protocolFunc, withInit) { 43 | var fvr = this; 44 | 45 | var action = getMethod.call(fvr); 46 | if (action === 'watch') { 47 | return setupWatch.call(fvr, protocolFunc); 48 | } 49 | 50 | var methodSeries = withInit ? 51 | convertToArray(fvr._component.init) 52 | .concat(convertToArray(fvr._component[action])) 53 | : 54 | convertToArray(fvr._component[action]); 55 | 56 | var arrayFuncs = methodSeries.map(function(cmd, idx) { 57 | cmd.address = fvr._component.address; 58 | cmd.val = getValueToSet.call(fvr, cmd); 59 | cmd._componentIndex = fvr._index; 60 | return function(cb) { 61 | protocolFunc(cmd, cb); 62 | if (withInit && idx === 63 | convertToArray(_fvr[fvr._index].init).length - 1) { 64 | _fvr[fvr._index].initialized = true; 65 | } 66 | }; 67 | }); 68 | async.series(arrayFuncs , function(err, val) { 69 | if (err) return fvr.onError.call(fvr, err); 70 | if (action === 'init') { 71 | // if this was an init only action, return true 72 | return fvr._callback.call(fvr, true); 73 | } 74 | if (fvr._method.name === 'set') { 75 | val = fvr._valueToSet; 76 | if (_fvr[fvr._index].watchCallbacks) { 77 | _fvr[fvr._index].watchCallbacks.forEach(function(cb) { 78 | cb.call(fvr, val); 79 | }); 80 | } 81 | } 82 | returnCallback.call(fvr, val); 83 | }); 84 | } 85 | 86 | exports.runMethod = runMethod; 87 | 88 | // takes a string and converts it to camelCase from dash-case 89 | exports.convertToCamelCase = function(string) { 90 | return string.split('-').map(function(u, idx) { 91 | var firstChar = u[0]; 92 | return firstChar.toUpperCase() + u.replace(firstChar, ''); 93 | }).join().replace(',',''); 94 | }; 95 | 96 | function setupWatch(protocolFunc) { 97 | var self = this; 98 | var component = getComponent.call(self); 99 | // setup watchCallbacks if it doesnot exist 100 | if (!_fvr[this._index].watchCallbacks) _fvr[this._index].watchCallbacks = []; 101 | // if this callback already exists, don't add it again 102 | if (_fvr[this._index].watchCallbacks.indexOf(this._callback) > -1) return; 103 | 104 | _fvr[this._index].watchCallbacks.push(this._callback); 105 | // package all the callbaks in this callback 106 | self._callback = function(val) { 107 | _fvr[self._index].watchCallbacks.forEach(function(cb) { 108 | cb.call(self, val); 109 | }); 110 | }; 111 | // Don't setup interval watching on 'set' 112 | if (component.set) return; 113 | _fvr[self._index].interval = setInterval(function() { 114 | if (component.interface === 'gpio') { 115 | return component[component.interface].get.call(self); 116 | } 117 | 118 | if (isFunc.call(self, 'get')) return getFromFunc.call(self); 119 | self._method = 'get'; 120 | runMethod.call(self, protocolFunc); 121 | }, self._watchInterval || 100); 122 | } 123 | 124 | exports.setupWatch = setupWatch; 125 | 126 | function removeWatch() { 127 | if (_fvr[this._index].watchCallbacks) { 128 | var idx = _fvr[this._index].watchCallbacks.indexOf(this._callback); 129 | _fvr[this._index].watchCallbacks.splice(idx, 1); 130 | if (_fvr[this._index].watchCallbacks.length === 0) { 131 | clearInterval(_fvr[this._index].interval); 132 | delete _fvr[this._index].interval; 133 | } 134 | } 135 | } 136 | 137 | exports.removeWatch = removeWatch; 138 | 139 | function getFromFunc() { 140 | var fvr = this; 141 | if (!_fvr[this._index].initialized) _fvr[this._index].initialized = true; 142 | return _fvr[this._index].get.call(fvr,function(val) { 143 | if (fvr._returnAs) return this._callback.call(fvr, val[fvr._returnAs]); 144 | this._callback.call(fvr, val); 145 | }); 146 | } 147 | 148 | exports.getFromFunc = getFromFunc; 149 | 150 | function isFunc(typ) { 151 | if (this._component.structure) return false; // structures can't have funcs at the mo 152 | var func = _fvr[this._index][typ]; 153 | return typeof func === 'function'; 154 | } 155 | 156 | exports.isFunc = isFunc; -------------------------------------------------------------------------------- /tests/i2c_getters_setters.spec.js: -------------------------------------------------------------------------------- 1 | var favoritjs = require('./mocks/favorit'); 2 | var $$ = require('../index.js')(favoritjs); 3 | 4 | describe('working with i2c', function() { 5 | 6 | it('should get i2c', function() { 7 | var mocki2c = false; 8 | var start = new Date(); 9 | runs(function() { 10 | var accel = $$('accelerometer*1').get(function(f) { 11 | mocki2c = _fvr.i2c.counts; 12 | }); 13 | }); 14 | 15 | waitsFor(function() { 16 | return mocki2c; 17 | },3000); 18 | 19 | runs(function() { 20 | expect(mocki2c.writeI2cBlock.length).toBe(3); 21 | expect(mocki2c.writeI2cBlock[0]).toBe('1,45,true'); 22 | expect(mocki2c.writeI2cBlock[2]).toBe('1,44,true'); 23 | expect(mocki2c.readI2cBlock[0]).toBe('1,51,true'); 24 | mocki2c.reset(); 25 | }); 26 | }); 27 | 28 | it('should run a formatOutput function before returning on i2c', function() { 29 | var changed = false; 30 | var x; 31 | runs(function() { 32 | $$('accelerometer#formatOutputI2c').get(function(val) { 33 | x = val; 34 | changed = true; 35 | }); 36 | }); 37 | 38 | waitsFor(function() { 39 | return changed; 40 | }, 1000); 41 | 42 | runs(function() { 43 | expect(x).toBe('postFormat returned'); 44 | }); 45 | }); 46 | 47 | it('should use the input value as the bytes value', function() { 48 | var mocki2c; 49 | var led = $$('led#blinkm'); 50 | runs(function() { 51 | led.set([0,0,0],function(f) { 52 | mocki2c = _fvr.i2c.counts; 53 | }); 54 | }); 55 | 56 | waitsFor(function() { 57 | return mocki2c; 58 | },1000); 59 | 60 | runs(function() { 61 | expect(mocki2c.writeI2cBlock.length).toBe(5); //gets initialized first 62 | expect(mocki2c.writeI2cBlock[3]).toBe('9,109,true'); 63 | expect(mocki2c.writeI2cBlock[4]).toBe('9,110,true'); 64 | mocki2c.reset(); 65 | }); 66 | }); 67 | 68 | it('should take a function as the set value', function() { 69 | var mocki2c; 70 | var val; 71 | var led = $$('led#blinkm_with_func'); 72 | runs(function() { 73 | led.set({r: 1, g: 2, b: 3}, 74 | function(f) { 75 | val = f; 76 | mocki2c = _fvr.i2c.counts; 77 | }); 78 | }); 79 | 80 | waitsFor(function() { 81 | return mocki2c; 82 | },1000); 83 | 84 | runs(function() { 85 | expect(val.g).toBe(2); 86 | expect(mocki2c.writeI2cBlock.length).toBe(1); 87 | expect(mocki2c.writeI2cBlock[0]).toBe('5,110,true'); 88 | mocki2c.reset(); 89 | }); 90 | }); 91 | 92 | it('should get the correct function after initialized', function() { 93 | var mocki2c; 94 | var val; 95 | runs(function() { 96 | $$('led#blinkm').set([2,2,2], function(f) { 97 | val = f; 98 | mocki2c = _fvr.i2c.counts; 99 | }); 100 | }); 101 | 102 | waitsFor(function() { 103 | return mocki2c; 104 | },1000); 105 | 106 | runs(function() { 107 | expect(mocki2c.writeI2cBlock.length).toBe(1); 108 | expect(mocki2c.writeI2cBlock[0]).toBe('9,110,true'); 109 | mocki2c.reset(); 110 | }); 111 | }); 112 | 113 | it('should get linked i2c', function() { 114 | var mocki2c; 115 | var val; 116 | var temp = $$('temp.i2cLink'); 117 | 118 | runs(function() { 119 | temp.get(function(data) { 120 | mocki2c = _fvr.i2c.counts; 121 | val = data; 122 | }); 123 | }); 124 | 125 | waitsFor(function() { 126 | return val; 127 | }, 1000); 128 | 129 | runs(function() { 130 | expect(val).toBe(80); 131 | }); 132 | }); 133 | 134 | // it('should error if get or set is not defined', function() { 135 | // var mocki2c; 136 | // var led = $$('led#blinkm'); 137 | // spyOn(led, 'onError'); 138 | // runs(function() { 139 | // led.get(function(f) { 140 | // mocki2c = _fvr.i2c; 141 | // }); 142 | // }); 143 | 144 | // waitsFor(function() { 145 | // return mocki2c; 146 | // },1000); 147 | 148 | // runs(function() { 149 | // expect(led.onError).toHaveBeenCalled(); 150 | // expect(mocki2c.err).toBeDefined(); 151 | // }); 152 | // }); 153 | 154 | it('should initialize and watch i2c', function() { 155 | var watchEvents = []; 156 | var done = false; 157 | var removedEvt = false; 158 | function watch(data) { 159 | watchEvents.push(data); 160 | if (watchEvents.length === 5) { 161 | $$('accelerometer#init_stream').removeWatch(watch); 162 | } 163 | } 164 | 165 | runs(function() { 166 | $$('accelerometer#init_stream').watch(watch); 167 | }); 168 | 169 | waitsFor(function() { 170 | return watchEvents.length = 5; 171 | },3000); 172 | 173 | runs(function() { 174 | expect(watchEvents.length).toBe(5); 175 | }); 176 | }); 177 | 178 | it('should watch for input changes', function() { 179 | var watched; 180 | 181 | function watchInput(data) { 182 | watched = data; 183 | } 184 | 185 | runs(function() { 186 | $$('led#blinkm').watch(watchInput); 187 | $$('led#blinkm').set(1, function() { 188 | // nothing needed here 189 | }); 190 | }); 191 | 192 | waitsFor(function() { 193 | return watched; 194 | }, 1000); 195 | 196 | runs(function() { 197 | expect(watched).toBe(1); 198 | }); 199 | }); 200 | }); 201 | 202 | describe('i2c initialize only', function() { 203 | it('should only initialize', function() { 204 | var init; 205 | runs(function() { 206 | $$('init_only_i2c').initialize(function(val) { 207 | init = val; 208 | }); 209 | }); 210 | 211 | waitsFor(function() { 212 | return init; 213 | }, 500); 214 | 215 | runs(function() { 216 | expect(init).toBe(true); 217 | }); 218 | }); 219 | }); 220 | 221 | describe('closing i2c', function() { 222 | it('should close i2c bus', function() { 223 | var led = $$('led#blinkm'); 224 | var initialized = _fvr[led._componentMatches[0]].initialized; 225 | 226 | led.close(); 227 | expect(initialized).toBe(true); 228 | expect(_fvr[led._componentMatches[0]].initialized).toBe(false); 229 | }); 230 | }); -------------------------------------------------------------------------------- /tests/getters_setters.spec.js: -------------------------------------------------------------------------------- 1 | var favoritjs = require('./mocks/favorit'); 2 | var $$ = require('../index.js')(favoritjs); 3 | 4 | describe('setters getters', function() { 5 | it('should initialize and get the values for the led', function() { 6 | var led = $$('led*1'); 7 | var check; 8 | runs(function() { 9 | led.get(function(val) { 10 | check = val + 1; 11 | }); 12 | }); 13 | 14 | waitsFor(function() { 15 | return check; 16 | }, 1000); 17 | // doesn't return on 0, so need to add one. 18 | runs(function() { 19 | expect(check).toBe(1); 20 | }); 21 | }); 22 | 23 | it('should set the value for the led', function() { 24 | var led = $$('led*1'); 25 | var check = []; 26 | runs(function() { 27 | led.set(1, function(val) { 28 | check.push(val); 29 | led.set(0, function(val) { 30 | check.push(val); 31 | }); 32 | }); 33 | }); 34 | 35 | waitsFor(function() { 36 | return check.length === 2; 37 | },1000); 38 | 39 | runs(function() { 40 | expect(check[0]).toBe(1); 41 | expect(check[1]).toBe(0); 42 | check = false; 43 | }); 44 | }); 45 | 46 | it('should set the change watcher', function() { 47 | var button = $$('button'); 48 | var wasPressed = false; 49 | function pressed() { 50 | return wasPressed = true; 51 | } 52 | 53 | button.watch(pressed); 54 | expect(_fvr[button._componentMatches[0]].initialized).toBeTruthy(); 55 | 56 | runs(function() { 57 | button.set(1); 58 | expect(wasPressed).toBeTruthy(); 59 | }); 60 | 61 | waitsFor(function() { 62 | return wasPressed; 63 | }, 500); 64 | 65 | runs(function() { 66 | expect(wasPressed).toBeTruthy(); 67 | }); 68 | }); 69 | }); 70 | 71 | describe('use component defined methods', function() { 72 | it('led.get should return defined in component', function() { 73 | var val; 74 | runs(function() { 75 | var led = $$('led.has_get'); 76 | led.get(function(l) { 77 | val = l; 78 | }); 79 | }); 80 | 81 | waitsFor(function() { 82 | return val; 83 | }, 'the value should be false', 5000); 84 | 85 | runs(function() { 86 | expect(val).toBe('defined in component'); 87 | }); 88 | }); 89 | }); 90 | 91 | describe('use linked components', function() { 92 | it('should get values from linked component', function() { 93 | var temp; 94 | var humid; 95 | 96 | runs(function() { 97 | $$('temperature#link').get(function(t) { 98 | temp = t; 99 | }); 100 | }); 101 | 102 | waitsFor(function() { 103 | return temp; 104 | },'waiting for temp',4000); 105 | 106 | runs(function() { 107 | expect(temp).toBe('26c'); 108 | }); 109 | 110 | runs(function() { 111 | $$('humidity*1').get(function(h) { 112 | humid = h; 113 | }); 114 | }); 115 | 116 | waitsFor(function() { 117 | return humid; 118 | }, 'waiting for humid', 3000); 119 | 120 | runs(function() { 121 | expect(humid).toBe(80); 122 | }); 123 | }); 124 | }); 125 | 126 | describe('on Change events', function() { 127 | it('should be triggered on set events', function() { 128 | var changed = false; 129 | 130 | function watchEvt() { 131 | return changed = true; 132 | } 133 | runs(function() { 134 | var led = $$('led#rgb'); 135 | led.watch(watchEvt); 136 | led.set({red: 1, green: 2, blue: 3}, function() { 137 | }); 138 | }); 139 | 140 | waitsFor(function() { 141 | return changed; 142 | },2000); 143 | 144 | runs(function() { 145 | expect(changed).toBe(true); 146 | }); 147 | }); 148 | }); 149 | 150 | describe('watch for data on gpio elements', function() { 151 | it('should trigger the on data updates', function() { 152 | var watchEvents = []; 153 | function testRemove(data) { 154 | watchEvents.push(data); 155 | if (watchEvents.length === 4) { 156 | $$('temperature[link=rht03]').removeWatch(testRemove); 157 | } 158 | } 159 | 160 | runs(function() { 161 | $$('temperature[link=rht03]').watch(testRemove); 162 | }); 163 | 164 | waitsFor(function() { 165 | return watchEvents.length > 3; 166 | }, 2000); 167 | 168 | runs(function() { 169 | expect(watchEvents.length).toBe(4); 170 | }); 171 | }); 172 | }); 173 | 174 | describe('formatOutput', function() { 175 | it('should run a formatOutput before returning on gpio', function() { 176 | var changed = false; 177 | var x; 178 | 179 | runs(function() { 180 | $$('temperature#formatOutputGpio').get(function(val) { 181 | x = val; 182 | changed = true; 183 | }); 184 | }); 185 | 186 | waitsFor(function() { 187 | return changed; 188 | }, 1000); 189 | 190 | runs(function() { 191 | expect(x).toBe('postFormat returned'); 192 | }); 193 | }); 194 | }); 195 | 196 | describe('format input', function() { 197 | it('should pass the set value through a format function', function() { 198 | var x = { 199 | 'red': null, 200 | 'green': null, 201 | 'blue': null 202 | }; 203 | 204 | runs(function() { 205 | $$('led#rgb').set({red: 250, green: 40, blue: 8}, function() { 206 | x[Object.keys(this._component.structure)[this._index]] = this._valueToSet; 207 | }); 208 | }); 209 | 210 | waitsFor(function() { 211 | return x.blue; 212 | }, 1000); 213 | 214 | runs(function() { 215 | expect(x.red).toBe(250); 216 | }); 217 | }); 218 | }); 219 | 220 | describe('initialize only', function() { 221 | it('should initialize the sensor', function() { 222 | var init; 223 | runs(function() { 224 | $$('init_only').initialize(function(val) { 225 | init = val; 226 | }); 227 | }); 228 | 229 | waitsFor(function() { 230 | return init; 231 | }, 500); 232 | 233 | runs(function() { 234 | expect(init).toBe(true); 235 | }); 236 | }); 237 | }); 238 | 239 | describe('closing components', function() { 240 | it('should unexport each component of type gpio', function() { 241 | var gpio = $$('led.gpio*1'); 242 | var initialized = _fvr[gpio._componentMatches[0]].initialized; 243 | 244 | gpio.close(); 245 | expect(initialized).toBe(true); 246 | expect(_fvr[gpio._componentMatches[0]].initialized).toBe(false); 247 | }); 248 | }); -------------------------------------------------------------------------------- /lib/getters_and_setters.js: -------------------------------------------------------------------------------- 1 | var gpio = require('./protocols/gpio'); 2 | var i2c = require('./protocols/i2c'); 3 | var spi = require('./protocols/spi'); 4 | 5 | var close = require('./close'); 6 | var file = 'getters_and_setters.js'; 7 | var componentUtils = require('./component_utils'); 8 | 9 | function initializeComponent() { 10 | var fvr = this; 11 | var protocol = _fvr[this._index].interface || _fvr[this._parent].interface; 12 | switch (protocol) { 13 | case 'i2c': 14 | i2c.call(this); 15 | break; 16 | case 'gpio': 17 | gpio.initialize.call(this); 18 | if (this._method === 'initialize') { 19 | this._callback.call(this, true); 20 | break; 21 | } 22 | this._method.call(this); 23 | break; 24 | case 'spi': 25 | spi.call(this); 26 | break; 27 | } 28 | } 29 | 30 | function get() { 31 | var fvr = this; 32 | if (componentUtils.isFunc.call(this,'get')) { 33 | return componentUtils.getFromFunc.call(this); 34 | } 35 | switch (_fvr[this._index].interface) { 36 | case 'gpio' : 37 | gpio.get.call(fvr); 38 | break; 39 | case 'i2c' : 40 | fvr._method = 'get'; //currently a string would be nice to just have this as the function 41 | i2c.call(fvr, null); 42 | break; 43 | case 'spi' : 44 | spi.call(fvr); 45 | break; 46 | } 47 | } 48 | 49 | function set(val) { 50 | if (componentUtils.isFunc.call(this, 'set')) setFromFunc.call(this, val); 51 | switch (_fvr[this._index].interface) { 52 | case 'gpio' : 53 | gpio.set.call(this, val); 54 | break; 55 | case 'i2c' : 56 | i2c.call(this); 57 | break; 58 | case 'spi' : 59 | spi.call(this); 60 | break; 61 | } 62 | } 63 | 64 | function setFromFunc(val) { 65 | return _fvr[this._index].set.call(fvr, val, function() { 66 | this._callback.call(fvr); 67 | }); 68 | } 69 | 70 | function passComponentsToSet(x) { 71 | /* 72 | gets a list of components to set, checks that address is a structure 73 | or a directly addressed component, and then passes to set 74 | */ 75 | for (var i = 0; i < this._componentMatches.length; i++) { 76 | setupFvr.call(this, i); 77 | if (componentUtils.isFunc.call(this, 'set') && !_fvr[this._index].interface) { 78 | if (!_fvr[this._index].initialized) _fvr[this._index].initialized = true; 79 | return setFromFunc.call(this, val); 80 | } 81 | 82 | if (this._component.structure) { 83 | this._componentValueToSet = x; 84 | structuredComponent.call(this); 85 | } else { 86 | this._valueToSet = valueToSet.call(this, x); 87 | if (!_fvr[this._index].initialized) { 88 | initializeComponent.call(this); 89 | } else { 90 | if (typeof this._method !== 'function') { 91 | console.log('no method', _fvr[this._index], typeof this._method); 92 | } 93 | this._method.call(this); 94 | } 95 | } 96 | } 97 | } 98 | 99 | function valueToSet(x) { 100 | var component = componentUtils.getComponent.call(this); 101 | if (component.formatInput) return component.formatInput.call(this, x); 102 | 103 | if (this._parent && _fvr[this._parent].formatInput) { 104 | return _fvr[this._parent].formatInput.call(this, x); 105 | } 106 | 107 | return x; 108 | } 109 | 110 | var _set = function(x, cb) { 111 | this._method = set; 112 | addCallbackToObject.call(this, cb); 113 | passComponentsToSet.call(this, x); 114 | }; 115 | 116 | function getComponentAsObject() { 117 | //the I/O is not directly addressed, but has an object of addresses. 118 | var structureKeys = Object.keys(this._component.structure); 119 | var fvr = this; 120 | structureKeys.forEach(function(key, idx) { 121 | fvr._index = idx; 122 | if (!_fvr[fvr._parent].structure[fvr._index].initialized) { 123 | return initializeComponent.call(fvr); 124 | } 125 | fvr._method.call(fvr); 126 | }); 127 | } 128 | 129 | function getLinkIndex() { 130 | var self = this; 131 | var linkName = _fvr[self._index].link; 132 | return _fvr.map(function(f, idx) { 133 | if (f.name === linkName) return idx; 134 | }).filter(Number.isInteger)[0]; 135 | } 136 | 137 | function setupFvr(i) { 138 | var ci = this._componentMatches[i]; //component index 139 | var cmp = _fvr[ci]; 140 | this._component = cmp; 141 | if (cmp.structure) { 142 | this._parent = ci; 143 | } else { 144 | delete this._parent; 145 | this._index = ci; 146 | } 147 | if (cmp.link) { 148 | var linkIndex = getLinkIndex.call(this); //getting the index of the linked component 149 | this._component = _fvr[linkIndex]; 150 | this._index = linkIndex; 151 | this._returnAs = _fvr[ci].type; 152 | } 153 | return this; 154 | } 155 | 156 | var _get = function(cb) { 157 | var fvr = this; 158 | this._method = get; 159 | addCallbackToObject.call(this, cb); 160 | for (var i = 0; i < this._componentMatches.length; i++) { 161 | setupFvr.call(this, i); 162 | if (componentUtils.isFunc.call(this, 'get') & !this._component.interface) { 163 | return componentUtils.getFromFunc.call(this); 164 | } 165 | if (this._component.structure) { //not directly addressed, this is a group of I/Os 166 | getComponentAsObject.call(this); 167 | } else { 168 | if (!_fvr[this._index].initialized) { 169 | return initializeComponent.call(this); 170 | } 171 | return this._method.call(this); 172 | } 173 | } 174 | }; 175 | 176 | function watch() { 177 | var fvr = this; 178 | if (!_fvr[this._index].interface) { 179 | return componentUtils.setupWatch.call(fvr); 180 | } 181 | switch (_fvr[this._index].interface) { 182 | case 'gpio' : 183 | gpio.watch.call(fvr, fvr._callback); 184 | break; 185 | case 'i2c' : 186 | i2c.call(this); 187 | break; 188 | case 'spi' : 189 | spi.call(this); 190 | break; 191 | } 192 | } 193 | 194 | function removeWatch(cb) { 195 | this._callback = cb; 196 | if (!_fvr[this._index].interface) { 197 | return componentUtils.removeWatch.call(this); 198 | } 199 | 200 | switch (_fvr[this._index].interface) { 201 | case 'gpio' : 202 | gpio.unwatch(this, cb); 203 | break; 204 | case 'i2c' : 205 | componentUtils.removeWatch.call(this); 206 | break; 207 | case 'spi' : 208 | componentUtils.removeWatch.call(this); 209 | break; 210 | } 211 | } 212 | 213 | var _removeWatch = function(cb) { 214 | this._method = removeWatch; 215 | for (var i = 0; i < this._componentMatches.length; i++) { 216 | setupFvr.call(this, i); 217 | if (_fvr[this._index].structure) { 218 | structuredComponent.call(this, removeWatch); 219 | } else { 220 | if (!this._component.initialized) { 221 | return initializeComponent.call(this); 222 | } 223 | removeWatch.call(this, cb); 224 | } 225 | } 226 | }; 227 | 228 | function structuredComponent() { 229 | var structureArray = Object.keys(this._component.structure); 230 | var fvr = this; 231 | 232 | structureArray.forEach(function(key, idx) { 233 | fvr._index = idx; 234 | var component = componentUtils.getComponent.call(fvr); 235 | if (fvr._method.name === 'set') { 236 | fvr._valueToSet = valueToSet.call(fvr, fvr._componentValueToSet); 237 | } 238 | if (!component.initialized) { 239 | return initializeComponent.call(fvr); 240 | } else { 241 | return fvr._method.call(fvr); 242 | } 243 | }); 244 | } 245 | 246 | function _watch(cb) { 247 | var fvr = this; 248 | fvr._method = watch; 249 | for (var i = 0; i < this._componentMatches.length; i++) { 250 | setupFvr.call(this, i); 251 | addCallbackToObject.call(this, cb); 252 | if (this._component.structure) { 253 | structuredComponent.call(this); 254 | } else { 255 | if (!this._component.initialized) { 256 | return initializeComponent.call(this); 257 | } 258 | return watch.call(this); 259 | } 260 | } 261 | } 262 | 263 | function addCallbackToObject(cb) { 264 | if (cb) return this._callback = cb; 265 | } 266 | 267 | function _init(cb) { 268 | var fvr = this; 269 | fvr._method = 'initialize'; 270 | for (var i = 0; i < this._componentMatches.length; i++) { 271 | setupFvr.call(this, i); 272 | addCallbackToObject.call(this, cb); 273 | if (this._component.structure) { 274 | structuredComponent.call(this); 275 | } else { 276 | if (!this._component.initialized) { 277 | return initializeComponent.call(this); 278 | } 279 | return cb.call(fvr, null); 280 | } 281 | } 282 | } 283 | 284 | function _close() { 285 | var self = this; 286 | for (var i = 0; i < this._componentMatches.length; i++) { 287 | setupFvr.call(self, i); 288 | close.call(self); 289 | }; 290 | } 291 | 292 | module.exports.get = _get; 293 | module.exports.set = _set; 294 | module.exports.watch = _watch; 295 | module.exports.removeWatch = _removeWatch; 296 | module.exports.initialize = _init; 297 | module.exports.close = _close; --------------------------------------------------------------------------------