├── .gitignore ├── .travis.yml ├── LICENSE ├── README.md ├── bridge ├── deamon.sh ├── index.js ├── install.sh ├── lib ├── FlowerBridge.js ├── SyncFP.js ├── TaskFP.js └── helpers.js ├── package.json ├── start.js └── updatedb /.gitignore: -------------------------------------------------------------------------------- 1 | credentials.json 2 | .flowerIsRunning 3 | .*.sw* 4 | database/ 5 | node_modules/ 6 | node-flower-power-cloud/ 7 | node-flower-power/ 8 | test.js 9 | trace.log 10 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - "4.1" 4 | - "4.2" 5 | - "5.0" 6 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (C) 2015 Parrot SA 2 | Redistribution and use in source and binary forms, with or without 3 | modification, are permitted provided that the following conditions 4 | are met: 5 | * Redistributions of source code must retain the above copyright 6 | notice, this list of conditions and the following disclaimer. 7 | * Redistributions in binary form must reproduce the above copyright 8 | notice, this list of conditions and the following disclaimer in 9 | the documentation and/or other materials provided with the 10 | distribution. 11 | * Neither the name of Parrot nor the names 12 | of its contributors may be used to endorse or promote products 13 | derived from this software without specific prior written 14 | permission. 15 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 16 | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 17 | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS 18 | FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE 19 | COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, 20 | INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, 21 | BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS 22 | OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED 23 | AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 24 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT 25 | OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 26 | SUCH DAMAGE. 27 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ![Bridge](https://upload.wikimedia.org/wikipedia/commons/thumb/d/de/Suspension_bridge_icon.svg/2000px-Suspension_bridge_icon.svg.png) 2 | ![node-flower-bridge](http://img15.hostingpics.net/pics/880820nfb.png) 3 | ![Work](http://img15.hostingpics.net/pics/57414831fb.png) 4 | 5 | 6 | [![Version node](https://img.shields.io/badge/node-4.x-brightgreen.svg)](https://nodejs.org/en/) 7 | [![Issues](https://img.shields.io/badge/issues-open-orange.svg)](https://github.com/Parrot-Developers/node-flower-bridge/issues) 8 | [![Forum](https://img.shields.io/badge/question-forum-blue.svg)](http://forum.developer.parrot.com/c/flower-power) 9 | 10 | ## Get your access API 11 | * `username` `password` 12 | * Make sure you have an account created by your smartphone. 13 | * `client_id` `client_secret` 14 | * [Sign up to API here](https://api-flower-power-pot.parrot.com/api_access/signup), and got by **email** your *Access ID* (`client_id`) and your *Access secret* (`client_secret`). 15 | 16 | ## How to install 17 | This program works with any *BLE-equipped/BLE-dongle-equipped* computers as well. 18 | To install it on your raspberry is really easy. You require Node (with npm) and BLE libraries. 19 | 20 | First, you need a raspberry with a *USB BLE dongle*. This raspberry must be up and running. 21 | Then you need to install some required tools on your raspberry. 22 | 23 | ### Step 1: NodeJs 24 | First, nodejs needs to be installed, proceed as following: 25 | ```bash 26 | $ wget http://node-arm.herokuapp.com/node_latest_armhf.deb 27 | $ sudo dpkg -i node_latest_armhf.deb 28 | ``` 29 | 30 | Then do a `node --version` to check if it worked. 31 | 32 | ### Step 2: BLE libraries 33 | Then we need to install the BLE libraries: 34 | ```bash 35 | $ sudo apt-get install libdbus-1-dev libdbus-glib-1-dev libglib2.0-dev libical-dev libreadline-dev libudev-dev libusb-dev glib2.0 bluetooth bluez libbluetooth-dev 36 | ``` 37 | The command `hciconfig` will be show your dongle (hci0): 38 | ```bash 39 | $ sudo hciconfig hci0 up 40 | ``` 41 | You should be able to discover peripheral around you. To check it, do `sudo hcitool lescan` and if it shows you a list of surrounding BLE devices – or at least your Flower Power -- it works fine. If not, do `sudo apt-get install bluez` and try again. 42 | 43 | ### Step 3: Build the brigde 44 | Now Nodejs and BLE libraries are installed. 45 | 46 | #### Ready to use it 47 | If you have cloned this project, install: 48 | ```bash 49 | $ ./install.sh 50 | ``` 51 | Edit `credentials.json`: 52 | ```javascript 53 | { 54 | "client_id": "...", 55 | "client_secret": "...", 56 | "username": "...", 57 | "password": "...", 58 | "url": "..." // Url of API cloud 59 | } 60 | ``` 61 | And walk on the brigde: 62 | ```bash 63 | $ ./bridge display : To have a output: 64 | $ ./bridge background : To run the program in background 65 | $ ./bridge restart : To restart the program 66 | $ ./bridge status : To show if the program is running or not 67 | $ ./bridge stop : To stop the program 68 | $ ./bridge : To have help 69 | ``` 70 | 71 | ##### How it works 72 | * Login Cloud 73 | * Loop (every 15 minutes by default) 74 | * Get Inforamtions from Cloud 75 | * Your garden 76 | * Your user-config 77 | * For each of your FlowerPowers (1 by 1) 78 | * Scan to discover the Flower Power 79 | * Retrieve his history samples 80 | * Send his history samples to the Cloud 81 | * End Loop 82 | 83 | The program relive a new `Loop` only if all Flower Powers have been checked. 84 | 85 | #### Quick started for developers 86 | If you have this module in dependencies: 87 | ```bash 88 | $ npm install node-flower-bridge 89 | ``` 90 | ```javascript 91 | var bridge = require('node-flower-bridge'); 92 | var credentials = { 93 | "client_id": "...", 94 | "client_secret": "...", 95 | "username": "...", 96 | "password": "..." 97 | }; 98 | 99 | bridge.loginToApi(credentials, function(err, res) { 100 | if (err) return console.error(err); 101 | bridge.syncAll(); 102 | bridge.live('...', 5); 103 | bridge.synchronize('...'); 104 | }); 105 | 106 | bridge.on('newProcess', function(flowerPower) { 107 | console.log(flowerPower.name, flowerPower.lastProcess); 108 | }); 109 | bridge.on('info', function(info) { 110 | console.log(info.message); 111 | }); 112 | bridge.on('error', function(error) { 113 | console.log(error.message); 114 | }); 115 | ``` 116 | 117 | The bridge is a continual queud. Method like `syncAll` `synchronize` or `live` push back to this queud. 118 | 119 | ##### Events 120 | ```js 121 | 'login' = {access_token, expires_in, refresh_token} 122 | 'info' = {message, date} 123 | 'error' = {message, date} 124 | 'newState' = state 125 | 'newProcess' = {name, lastProcess, process, date} 126 | ``` 127 | 128 | ##### Api 129 | ```js 130 | // Login in to the Cloud 131 | bridge.loginToApi(credentials [, callback]); // event: 'login' 132 | 133 | // Get your garden configuration 134 | bridge.getUser(callback); 135 | 136 | // Make an automatic syncronization 137 | var options = { 138 | delay: 15, // loop delay 139 | priority: [], // add a 'name' 140 | }; 141 | 142 | brigde.automatic([options]); // Synchronize all flower power in your garden every 15 minutes by default 143 | bridge.syncAll([options]); // Synchronize all flower power in your garden 144 | bridge.synchronize(NAME); // Synchronize a flower power 145 | bridge.live(NAME [, delay]); // Live for a flower power every 10 seconds by default 146 | bridge.update(NAME, file); // Update the firmware [features: no file param = last firmware] 147 | ``` 148 | -------------------------------------------------------------------------------- /bridge: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | pid=0 4 | filestart="/home/pi/node-flower-bridge/start.js" 5 | filetest="/home/pi/node-flower-bridge/test.js" 6 | 7 | usage () { 8 | echo "Flower Bridge: See Flowers fall on the bridge" 9 | echo "Every 15 minutes (default), all of your Flower Power will be update" 10 | echo 11 | echo "USAGE: ./bridge cmd [delay]" 12 | echo 13 | echo "ARGUMENTS:" 14 | echo " cmd:" 15 | echo " display: Lannch and show updating into your console" 16 | echo " background: Launch updatind to background. To know what happening: \`$ ./updatedb\`" 17 | echo " restart: Stop the current program and re-launch. If no program is running, 'restart' is similar as 'background'" 18 | echo " status: Show if the program is running or not" 19 | echo " stop: Stop all processings and kill the program" 20 | echo 21 | echo " delay (optional):" 22 | echo " Specify the loop time in minute" 23 | echo 24 | echo "EXAMPLES:" 25 | echo " ./bridge display : (loop every 15 minutes)" 26 | echo " ./bridge display 60 : (loop every 1 hour)" 27 | echo " ./bridge background 1440 : (loop every day without render in console)" 28 | echo " ./bridge stop : (Stop the program)" 29 | echo " ./bridge restart : (Restart the program)" 30 | echo 31 | echo "AUTHOR:" 32 | echo " Written by Bruno Sautron." 33 | } 34 | 35 | welcome () { 36 | echo 37 | echo "-- Flower Bridge --" 38 | echo 39 | } 40 | 41 | pidexist () { 42 | kill -0 $1 2> /dev/null 43 | } 44 | 45 | 46 | # @return 47 | # 0 -> running 48 | # 1 -> stoping 49 | # 2 -> waiting 50 | isrunning () { 51 | pid=$(head -n 1 "/home/pi/node-flower-bridge/.flowerIsRunning" 2> /dev/null) 52 | if [ -z "$pid" ] 53 | then 54 | echo "[---] No program is running" 55 | return 1 56 | else 57 | pidexist $pid 58 | if [ "$?" == 0 ] 59 | then 60 | echo "[running] A program is running - $pid" 61 | return 0 62 | else 63 | crontab -u pi -l | grep -n "node-flower-bridge\/bridge background$" > /dev/null 64 | if [ $? == 0 ] 65 | then 66 | echo "[waiting] A program is on waiting, it will restart it self " 67 | return 2 68 | fi 69 | fi 70 | echo "[--+] No program is running" 71 | return 1 72 | fi 73 | } 74 | 75 | statusit () { 76 | welcome 77 | isrunning 78 | exit 0 79 | } 80 | 81 | displayit () { 82 | welcome 83 | isrunning 84 | status="$?" 85 | if [ "$status" == 1 ] 86 | then 87 | echo node $1 $2 88 | node $1 $2 89 | else 90 | stopit 91 | node $1 $2 92 | fi 93 | } 94 | 95 | backgroundit () { 96 | welcome 97 | isrunning 98 | status="$?" 99 | if [ "$status" == 1 ] 100 | then 101 | node $1 $2 2> /home/pi/node-flower-bridge/trace.log 1> /dev/null & 102 | echo $! > "/home/pi/node-flower-bridge/.flowerIsRunning" 103 | echo "Starting bridge in background" 104 | echo "node $1 $2 2> trace.log 1> /dev/null &" 105 | echo "$> ./bridge status" 106 | installit 107 | elif [ "$status" == 2 ] 108 | then 109 | node $1 $2 2> /home/pi/node-flower-bridge/trace.log 1> /dev/null & 110 | echo $! > "/home/pi/node-flower-bridge/.flowerIsRunning" 111 | echo "The bridge wakup" 112 | fi 113 | } 114 | 115 | stopit () { 116 | isrunning $> /dev/null 117 | status="$?" 118 | if [ "$status" == 0 ] 119 | then 120 | killit 121 | crontab -u pi -l | sed "/node-flower-bridge\/bridge background$/d" | crontab 122 | echo "Bye :)" 123 | elif [ "$status" == 2 ] 124 | then 125 | crontab -u pi -l | sed "/node-flower-bridge\/bridge background$/d" | crontab 126 | fi 127 | } 128 | 129 | restartit () { 130 | stopit 131 | backgroundit $1 $2 132 | } 133 | 134 | killit () { 135 | kill $pid 136 | rm -f "/home/pi/node-flower-bridge/.flowerIsRunning" 137 | echo "kill" $pid 138 | pid=0 139 | } 140 | 141 | installit () { 142 | crontab -l | grep -n "node-flower-bridge\/bridge background$" > /dev/null 143 | if [ "$?" == 1 ] 144 | then 145 | crontab -u pi -l > /home/pi/node-flower-bridge/mycron 146 | echo "*/15 * * * * /home/pi/node-flower-bridge/bridge background" >> /home/pi/node-flower-bridge/mycron 147 | crontab -u pi /home/pi/node-flower-bridge/mycron 148 | rm /home/pi/node-flower-bridge/mycron 149 | fi 150 | } 151 | 152 | if [ -z "$1" ] 153 | then 154 | usage 155 | else 156 | if [ "$1" == "install" ] 157 | then 158 | installit 159 | elif [ "$1" == "display" ] 160 | then 161 | displayit $filestart $2 162 | elif [ "$1" == "background" ] 163 | then 164 | backgroundit $filestart $2 165 | elif [ "$1" == "stop" ] 166 | then 167 | stopit 168 | elif [ $1 == "restart" ] 169 | then 170 | restartit $filestart $2 171 | elif [ $1 == "running" ] 172 | then 173 | isrunning 174 | elif [ $1 == "crontab" ] 175 | then 176 | echo pi: 177 | crontab -u pi -l 178 | echo root: 179 | sudo crontab -l 180 | elif [ $1 == "status" ] 181 | then 182 | statusit 183 | elif [ $1 == "test" ] 184 | then 185 | displayit $filetest $2 186 | else 187 | usage 188 | fi 189 | fi 190 | -------------------------------------------------------------------------------- /deamon.sh: -------------------------------------------------------------------------------- 1 | ./bridge running 2 | if [ "$?" == 1 ] 3 | then 4 | ip=`/sbin/ifconfig eth0 | grep 'inet addr:' | cut -d: -f2 | awk '{ print $1}'` 5 | (echo "Crash Flower Bridge" && \ 6 | echo "ip: $ip" && \ 7 | echo "" && \ 8 | echo "trace.log:" && \ 9 | cat trace.log && 10 | echo && 11 | echo "credenitals.json:" && 12 | cat credentials.json) | \ 13 | mail -s "Flower Bridge crashed" "bruno.sautron@parrot.com" 14 | ./bridge restart > /dev/null 15 | fi 16 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | var Bridge = require('./lib/FlowerBridge'); 2 | 3 | module.exports = Bridge; 4 | -------------------------------------------------------------------------------- /install.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | sudo setcap cap_net_raw+eip $(eval readlink -f `which node`) 4 | (git clone -b v2 https://github.com/Parrot-Developers/node-flower-power && cd node-flower-power && npm install) 5 | (git clone -b v2 https://github.com/Parrot-Developers/node-flower-power-cloud && cd node-flower-power-cloud && npm install) 6 | npm install 7 | -------------------------------------------------------------------------------- /lib/FlowerBridge.js: -------------------------------------------------------------------------------- 1 | var fs = require('fs'); 2 | var SyncFP = require('./SyncFP'); 3 | var TaskFP = require('./TaskFP'); 4 | var FlowerPowerApi = require('../node-flower-power-cloud/FlowerPowerCloud'); 5 | var util = require('util'); 6 | var async = require('async'); 7 | var EventEmitter = require('events'); 8 | var clc = require('cli-color'); 9 | var Chance = require('chance'); 10 | var chance = new Chance(); 11 | 12 | function FlowerBridge(url) { 13 | EventEmitter.call(this); 14 | this._state = 'off'; 15 | this.user = null; 16 | this.api = new FlowerPowerApi(url); 17 | this.q = null; 18 | this.initBridge(); 19 | }; 20 | 21 | util.inherits(FlowerBridge, EventEmitter); 22 | 23 | FlowerBridge.prototype.initBridge = function() { 24 | var self = this; 25 | 26 | self.q = async.queue(function(task, callbackNext) { 27 | if (!task.user) { 28 | self.getUser(function (err, user) { 29 | if (err) return callbackNext(err); 30 | else { 31 | task.user = user; 32 | self.do(task, callbackNext); 33 | } 34 | }); 35 | } 36 | else self.do(task, callbackNext); 37 | }, 1); 38 | self.q.drain = function() { 39 | self.emit('end'); 40 | } 41 | }; 42 | 43 | FlowerBridge.prototype.do = function(task, callbackNext) { 44 | var self = this; 45 | var flowerPower = new SyncFP(task.name, task.user, self.api); 46 | 47 | self.state(task.mode); 48 | flowerPower.on('newProcess', function(flowerPower) { 49 | self.proc(flowerPower); 50 | if (flowerPower.lastProcess == 'Disconnected') { 51 | self.state('off'); 52 | return callbackNext(); 53 | } 54 | }); 55 | task.startAction(flowerPower, task.options, function(err, res) { 56 | self.state('off'); 57 | callbackNext(err, res); 58 | }); 59 | }; 60 | 61 | FlowerBridge.prototype.loginToApi = function(credentials, callback) { 62 | var self = this; 63 | 64 | this.api.login(credentials, function(err, token) { 65 | if (!err) self.emit('login', token); 66 | if (typeof callback != 'undefined') callback(err, token); 67 | }); 68 | }; 69 | 70 | FlowerBridge.prototype.getState = function() { 71 | return (this._state); 72 | }; 73 | 74 | FlowerBridge.prototype.getUser = function(callback) { 75 | var self = this; 76 | 77 | async.parallel({ 78 | garden: self.api.getGarden.bind(self.api), 79 | userConfig: self.api.getProfile.bind(self.api) 80 | }, function(error, results) { 81 | var user = self.api.concatJson(results.userConfig, results.garden); 82 | self.user = user; 83 | callback(error, user); 84 | }); 85 | }; 86 | 87 | FlowerBridge.prototype.info = function(message) { 88 | this.emit('info', { 89 | message: message, 90 | date: new Date() 91 | }); 92 | }; 93 | 94 | FlowerBridge.prototype.error = function(message) { 95 | this.emit('error', { 96 | message: message, 97 | date: new Date() 98 | }); 99 | }; 100 | 101 | FlowerBridge.prototype.proc = function(flowerPower) { 102 | this.emit('newProcess', flowerPower); 103 | }; 104 | 105 | FlowerBridge.prototype.stop = function () { 106 | this.emit('stop'); 107 | }; 108 | 109 | FlowerBridge.prototype.state = function(state) { 110 | this._state = state; 111 | this.emit('newState', state); 112 | }; 113 | 114 | FlowerBridge.prototype.automatic = function(options) { 115 | var self = this; 116 | var delay = 15; 117 | 118 | if (typeof options != 'undefined' && typeof options['delay'] != 'undefinded') { 119 | delay = options['delay']; 120 | } 121 | self.info('New process every ' + delay + ' minutes'); 122 | self.all('synchronize', options); 123 | setInterval(function() { 124 | if (self._state == 'off') self.all('synchronize', options); 125 | }, delay * 60 * 1000); 126 | }; 127 | 128 | FlowerBridge.prototype.syncFlowerPower = function(flowerPower, callback) { 129 | async.series([ 130 | function(callback) { 131 | flowerPower.syncStatus(callback); 132 | }, 133 | function(callback) { 134 | flowerPower.syncSamples(callback); 135 | } 136 | // More features? 137 | ], function(err, results) { 138 | callback(err, results); 139 | }); 140 | }; 141 | 142 | FlowerBridge.prototype.fnSyncronize = function(flowerPower, options, callback) { 143 | var self = this; 144 | 145 | flowerPower.findAndConnect(function(err) { 146 | if (err) return callback(err) 147 | else self.syncFlowerPower(flowerPower, function(err, res) { 148 | if (err) self.error(err); 149 | flowerPower.disconnect(); 150 | }); 151 | }); 152 | }; 153 | 154 | FlowerBridge.prototype.fnLive = function(flowerPower, options, callback) { 155 | var self = this; 156 | 157 | flowerPower.findAndConnect(function(err, res) { 158 | if (err) return callback(err); 159 | else flowerPower.live(options, function(err) { 160 | if (err) self.error(err); 161 | flowerPower.disconnect(); 162 | }); 163 | }); 164 | }; 165 | 166 | FlowerBridge.prototype.fnUpdate = function(flowerPower, options, callback) { 167 | var self = this; 168 | 169 | flowerPower.findAndConnect(function(err, res) { 170 | if (err) return callback(err); 171 | else flowerPower.syncSamples(function(err) { 172 | if (err != 'No update required' && err) flowerPower.disconnect(); 173 | else flowerPower.update(options.file, function(err) { 174 | if (err) self.error(err); 175 | flowerPower.disconnect(); 176 | }); 177 | }); 178 | }); 179 | }; 180 | 181 | FlowerBridge.prototype.update = function(name, options, user) { 182 | var self = this; 183 | 184 | self.q.push({ 185 | name: name, 186 | mode: 'update', 187 | options: options, 188 | user: user, 189 | startAction: self.fnUpdate.bind(self) 190 | }); 191 | }; 192 | 193 | FlowerBridge.prototype.synchronize = function(name, options, user) { 194 | var self = this; 195 | 196 | self.q.push({ 197 | name: name, 198 | mode: 'synchronize', 199 | options: options, 200 | user: user, 201 | startAction: self.fnSyncronize.bind(self) 202 | }); 203 | }; 204 | 205 | FlowerBridge.prototype.live = function(name, options, user) { 206 | var self = this; 207 | 208 | self.q.push({ 209 | name: name, 210 | mode: 'live', 211 | options: options, 212 | user: user, 213 | startAction: self.fnLive.bind(self) 214 | }); 215 | }; 216 | 217 | 218 | FlowerBridge.prototype.all = function(fn, options) { 219 | var self = this; 220 | 221 | self.getUser(function(err, user) { 222 | if (err) self.error(err); 223 | else { 224 | var sensors = []; 225 | var typeFilter = []; 226 | var fpPriority = []; 227 | 228 | for (var name in user.locations) { 229 | if (typeof options.filter != 'undefined') { 230 | if (options.filter(user.locations[name])) { 231 | sensors.push(name); 232 | } 233 | } 234 | else sensors.push(name); 235 | } 236 | 237 | if (typeof options != 'undefined') { 238 | if (typeof options['type'] != 'undefined') typeFilter = options['type']; 239 | if (typeof options['priority'] != 'undefined') fpPriority = options['priority']; 240 | } 241 | 242 | // Look type in json 243 | for (var i = 0; i < fpPriority.length; i++) { 244 | sensors.unshift(fpPriority[i]); 245 | // self[fn](fpPriority[i], options, user); 246 | } 247 | console.log(sensors); 248 | for (var sensor of sensors) { 249 | self[fn](sensor, options, user); 250 | } 251 | } 252 | }); 253 | }; 254 | 255 | module.exports = FlowerBridge; 256 | -------------------------------------------------------------------------------- /lib/SyncFP.js: -------------------------------------------------------------------------------- 1 | var TaskFP = require('./TaskFP'); 2 | var helpers = require('./helpers'); 3 | 4 | function SyncFP(flowerPowerName, user, api) { 5 | TaskFP.call(this, flowerPowerName); 6 | this.user = user; 7 | this.api = api; 8 | } 9 | 10 | SyncFP.prototype = Object.create(TaskFP.prototype); 11 | SyncFP.prototype.constructor = SyncFP; 12 | 13 | SyncFP.prototype.syncSamples = function(callback) { 14 | var self = this; 15 | var cloudIndex = self.user.locations[self.FP.name].sensor.current_history_index; 16 | 17 | self.getSamples(cloudIndex, function(err, dataBLE) { 18 | if (err) return callback(err, null); 19 | else if (!self.user) return callback(new Error('Not logged'), null); 20 | 21 | self.proc('Sending samples'); 22 | var param = {}; 23 | var session = {}; 24 | var uploads = {}; 25 | var now = new Date(); 26 | 27 | param["client_datetime_utc"] = now.toISOString(); 28 | param["user_config_version"] = self.user.user_config_version; 29 | param["plant_science_database_identifier"] = "en_20151020_3.0.2"; 30 | 31 | session["sensor_serial"] = self.FP.name; 32 | session["sensor_startup_timestamp_utc"] = dataBLE.start_up_time; 33 | session["session_id"] = dataBLE.history_current_session_id; 34 | session["session_start_index"] = dataBLE.history_current_session_start_index; 35 | session["sample_measure_period"] = dataBLE.history_current_session_period; 36 | 37 | uploads["sensor_serial"] = self.FP.name; 38 | uploads["upload_timestamp_utc"] = now.toISOString(); 39 | uploads["buffer_base64"] = dataBLE.buffer_base64; 40 | uploads["app_version"] = ""; 41 | uploads["sensor_fw_version"] = dataBLE.firmware_version; 42 | uploads["sensor_hw_identifier"] = dataBLE.hardware_version; 43 | 44 | param["session_histories"] = [session]; 45 | param["uploads"] = [uploads]; 46 | 47 | self.api.sendSamples(param, function(error, resutls) { 48 | if (!error) { 49 | self.state = 'Updated'; 50 | self.proc('Updated', true); 51 | } 52 | else { 53 | self.state = 'Failed to updated'; 54 | self.proc('Failed to update', true); 55 | } 56 | return callback(error, resutls); 57 | }); 58 | }); 59 | }; 60 | 61 | SyncFP.prototype.syncStatus = function(callback) { 62 | var self = this; 63 | 64 | self.getStatusWatering(function(err, watering) { 65 | self.proc('Sending status watering'); 66 | var param = {}; 67 | var update_status = {}; 68 | var now = new Date(); 69 | 70 | if (!self.user) return callback(new Error('Not logged'), null); 71 | param["client_datetime_utc"] = now.toISOString(); 72 | param["user_config_version"] = self.user.user_config_version; 73 | update_status['location_identifier'] = self.user.locations[self.FP.name].location_identifier; 74 | update_status['status_creation_datetime_utc'] = now.toISOString(); 75 | update_status['watering'] = watering; 76 | 77 | param['update_status'] = [update_status]; 78 | self.api.sendGardenStatus(param, function(error, results) { 79 | if (!error) { 80 | self.state = 'Status updated'; 81 | self.proc('Status updated', true); 82 | } 83 | else { 84 | self.state = 'Failed to status updated'; 85 | self.proc('Failed to status updated', true); 86 | } 87 | return callback(error, self.state); 88 | }); 89 | }); 90 | }; 91 | 92 | module.exports = SyncFP; 93 | -------------------------------------------------------------------------------- /lib/TaskFP.js: -------------------------------------------------------------------------------- 1 | var util = require('util'); 2 | var async = require('async'); 3 | var clc = require('cli-color'); 4 | var EventEmitter = require('events'); 5 | var FlowerPower = require('../node-flower-power/index'); 6 | var Promise = require('promise'); 7 | 8 | var Datastore = require('nedb'); 9 | var db = new Datastore(); 10 | 11 | const DELAY_SEARCHING_ATTEMPT = 30000; 12 | const DELAY_CONNECTION_ATTEMPT = 60000; 13 | const RETRY_SEARCHING = 3; 14 | 15 | function TaskFP(flowerPowerName) { 16 | EventEmitter.call(this); 17 | this.FP = null; 18 | this.lastDate = new Date(); 19 | this.name = flowerPowerName; 20 | this.process = []; 21 | this.lastProcess = 'Standby'; 22 | this.charac = { 23 | status_flags: 'getStatusFlags', 24 | start_up_time: "getStartupTime", 25 | watering_mode: 'getWateringMode', 26 | water_tank_level: "getWaterTankLevel", 27 | firmware_version: "readFirmwareRevision", 28 | hardware_version: "readHardwareRevision", 29 | history_nb_entries: "getHistoryNbEntries", 30 | full_tank_autonomy: 'getFullTankAutonomy', 31 | next_empty_tank_date: 'getNextEmptyTankDate', 32 | soil_percent_vwc: 'getCalibratedSoilMoisture', 33 | next_watering_date: 'getNextWateringDateTime', 34 | history_last_entry_index: "getHistoryLastEntryIdx", 35 | watering_algorithm_status: "getWateringAlgorithmStatus", 36 | history_current_session_id: "getHistoryCurrentSessionID", 37 | history_current_session_period: "getHistoryCurrentSessionPeriod", 38 | history_current_session_start_index: "getHistoryCurrentSessionStartIdx", 39 | }; 40 | 41 | return this; 42 | }; 43 | 44 | util.inherits(TaskFP, EventEmitter); 45 | 46 | TaskFP.prototype.proc = function(processMsg, pushDb) { 47 | var self = this; 48 | 49 | self.process.unshift(processMsg); 50 | self.lastProcess = processMsg; 51 | self.lastDate = new Date(); 52 | self.emit('newProcess', self); 53 | if (pushDb) { 54 | db.insert({ 55 | name: self.name, 56 | proc: self.lastProcess, 57 | date: self.lastDate 58 | }); 59 | } 60 | }; 61 | 62 | TaskFP.prototype.toString = function() { 63 | return ("[" + this.lastDate.toString().substr(4, 20) + "]: " + this.name + ": " + this.lastProcess); 64 | }; 65 | 66 | TaskFP.prototype.readDataBLE = function(keys) { 67 | var self = this; 68 | 69 | return new Promise(function(resolve, reject) { 70 | var array = {}; 71 | var makeFn = function(fnName) { 72 | return function(callback) { 73 | self.FP[fnName](callback); 74 | }; 75 | } 76 | 77 | for (var i in keys) { 78 | array[keys[i]] = makeFn(self.charac[keys[i]]); 79 | } 80 | async.parallel(array, function(err, results) { 81 | if (err) reject(err); 82 | else resolve(results); 83 | }); 84 | }); 85 | }; 86 | 87 | TaskFP.prototype.findAndConnect = function(callback) { 88 | var self = this; 89 | 90 | async.auto({ 91 | search: async.retry({times: RETRY_SEARCHING, interval: 2000}, self.search.bind(self)), 92 | init: ['search', self.init.bind(self)], 93 | connect: ['init', self.connect.bind(self)] 94 | }, callback); 95 | 96 | }; 97 | 98 | TaskFP.prototype.search = function(callback) { 99 | var self = this; 100 | 101 | self.proc('Searching'); 102 | var discover = function(device) { 103 | if (device.name == self.name) { 104 | self.FP = device; 105 | FlowerPower.stopDiscoverAll(discover); 106 | self.proc('Found'); 107 | return callback(null, device); 108 | } 109 | else self.destroy(device); 110 | }; 111 | setTimeout(function() { 112 | if (self.process[0] == 'Searching') { 113 | FlowerPower.stopDiscoverAll(discover); 114 | self.proc('Not found', true); 115 | return callback('Not found'); 116 | } 117 | }, DELAY_SEARCHING_ATTEMPT); 118 | 119 | FlowerPower.discoverAll(discover); 120 | }; 121 | 122 | TaskFP.prototype.init = function(callback) { 123 | var self = this; 124 | 125 | self.FP._peripheral.on('disconnect', function() { 126 | self.proc('Disconnected'); 127 | self.destroy(self.FP); 128 | }); 129 | self.FP._peripheral.on('connect', function() { 130 | self.proc('Connected'); 131 | }); 132 | 133 | if (self.FP._peripheral.state == 'disconnected') { 134 | self.proc('Connection'); 135 | return callback(null); 136 | } 137 | else if (self.FP._peripheral.state == 'connecting') { 138 | self.proc('Not availaible: is on connection'); 139 | return callback('Connecting'); 140 | } 141 | else { 142 | self.proc('Not available: ' + self.FP._peripheral.state, true); 143 | return callback('Not available'); 144 | } 145 | }; 146 | 147 | TaskFP.prototype.connect = function(callback) { 148 | var self = this; 149 | 150 | setTimeout(function() { 151 | if (self.process[0] == 'Connection') { 152 | self.proc('Connection failed', true); 153 | self.destroy(self.FP); 154 | throw (self.FP.name + ': Connection failed'); 155 | } 156 | }, DELAY_CONNECTION_ATTEMPT); 157 | 158 | self.FP.connectAndSetup(function() { 159 | return callback(null); 160 | }); 161 | }; 162 | 163 | TaskFP.prototype.disconnect = function(callback) { 164 | var self = this; 165 | 166 | self.FP.disconnect(function() { 167 | if (typeof callback == 'function') { 168 | return callback(null); 169 | } 170 | }); 171 | }; 172 | 173 | TaskFP.prototype.destroy = function(device) { 174 | device._peripheral.removeAllListeners(); 175 | device.removeAllListeners(); 176 | device = null; 177 | }; 178 | 179 | TaskFP.prototype.getSamples = function(index, callback) { 180 | var self = this; 181 | 182 | self.proc('Getting samples'); 183 | self.readDataBLE([ 184 | 'start_up_time', 185 | 'firmware_version', 186 | 'hardware_version', 187 | 'history_nb_entries', 188 | 'history_last_entry_index', 189 | 'history_current_session_id', 190 | 'history_current_session_period', 191 | 'history_current_session_start_index' 192 | ]).then(function(dataBLE) { 193 | var hw_v = dataBLE.hardware_version; 194 | var fw_v = dataBLE.firmware_version; 195 | var firstEntryIndex = dataBLE.history_last_entry_index - dataBLE.history_nb_entries + 1; 196 | var startIndex = (index >= firstEntryIndex) ? index : firstEntryIndex; 197 | 198 | dataBLE.hardware_version = hw_v.substr(0, (hw_v.indexOf('\u0000')) ? hw_v.indexOf('\u0000') : hw_v.length); 199 | dataBLE.firmware_version = fw_v.substr(0, (fw_v.indexOf('\u0000')) ? fw_v.indexOf('\u0000') : fw_v.length); 200 | 201 | if (startIndex > dataBLE.history_last_entry_index) { 202 | self.proc('No update required', true); 203 | return callback('No update required'); 204 | } 205 | 206 | self.FP.getHistory(startIndex, function(error, history) { 207 | dataBLE.buffer_base64 = history; 208 | return callback(error, dataBLE); 209 | }); 210 | }); 211 | }; 212 | 213 | TaskFP.prototype.getStatusWatering = function(callback) { 214 | var self = this; 215 | 216 | self.proc('Getting status watering'); 217 | var watering = { 218 | 'status_key': 'status_ok', 219 | 'instruction_key': 'soil_moisture_good', 220 | 'soil_moisture': { 221 | 'status_key': 'status_ok', 222 | 'instruction_key': 'soil_moisture_good', 223 | 'current_vwc': 0 224 | }, 225 | 'automatic_watering': { 226 | 'status_key': 'status_ok', 227 | 'instruction_key': 'automatic_watering_off', 228 | 'next_watering_datetime_utc': null, 229 | 'full_autonomy_days': null, 230 | 'predicted_action_datetime_utc': null, 231 | 'current_water_level': 0 232 | } 233 | }; 234 | 235 | self.readDataBLE(['soil_percent_vwc']).then(function(dataCommun) { 236 | 237 | watering['soil_moisture']['current_vwc'] = dataCommun.soil_percent_vwc; 238 | 239 | if (self.FP.generation == 1) return callback(null, watering); 240 | else if (self.FP.generation == 2) { 241 | self.readDataBLE([ 242 | 'status_flags' 243 | ]).then(function(dataFlags) { 244 | if (dataFlags.status_flags['Soil dry'] && !dataFlags.status_flags['Soil wet']) { 245 | watering['soil_moisture']['status_key'] = 'status_critical'; 246 | watering['soil_moisture']['instruction_key'] = 'soil_moisture_too_low'; 247 | } 248 | else if (!dataFlags.status_flags['Soil dry'] && dataFlags.status_flags['Soil wet']) { 249 | watering['soil_moisture']['status_key'] = 'status_warning'; 250 | watering['soil_moisture']['instruction_key'] = 'soil_moisture_too_high'; 251 | } 252 | 253 | if (self.FP.type == 'Flower power') return callback(null, watering); 254 | else { 255 | self.readDataBLE([ 256 | 'next_watering_date', 257 | 'next_empty_tank_date', 258 | 'full_tank_autonomy', 259 | 'watering_mode', 260 | 'watering_algorithm_status', 261 | 'water_tank_level' 262 | ]).then(function(dataWatering) { 263 | watering['automatic_watering']['next_watering_datetime_utc'] = (dataWatering.next_watering_date) ? dataWatering.next_watering_date.toISOString() : null; 264 | watering['automatic_watering']['predicted_action_datetime_utc'] = (dataWatering.next_empty_tank_date) ? dataWatering.next_empty_tank_date.toISOString() : null; 265 | watering['automatic_watering']['full_autonomy_days'] = (dataWatering.full_tank_autonomy) ? dataWatering.full_tank_autonomy : null; 266 | if (dataWatering.watering_mode == 'Manual') return callback(null, watering); 267 | else { 268 | watering['automatic_watering']['current_water_level'] = dataWatering.water_tank_level; 269 | watering['automatic_watering']['status_key'] = 'status_critical'; 270 | if (dataWatering.watering_algorithm_status == 'Error vwc still') watering['automatic_watering']['instruction_key'] = 'automatic_watering_check_system'; 271 | else if (dataWatering.watering_algorithm_status == 'Error internal') watering['automatic_watering']['instruction_key'] = 'automatic_watering_unkwown_error'; 272 | else { 273 | if (dataFlags.status_flags['Sensor in air']) { 274 | watering['automatic_watering']['status_key'] = 'status_warning'; 275 | watering['automatic_watering']['instruction_key'] = 'automatic_watering_in_air'; 276 | } 277 | else if (dataFlags.status_flags['Tank empty']) { 278 | watering['automatic_watering']['status_key'] = 'status_critical'; 279 | watering['automatic_watering']['instruction_key'] = 'automatic_watering_reserve_empty'; 280 | } 281 | else { 282 | watering['automatic_watering']['status_key'] = 'status_ok'; 283 | watering['automatic_watering']['instruction_key'] = 'automatic_watering_good'; 284 | } 285 | } 286 | watering['status_key'] = watering['automatic_watering']['status_key']; 287 | watering['instruction_key'] = watering['automatic_watering']['instruction_key']; 288 | return callback(null, watering); 289 | } 290 | }); 291 | } 292 | }); 293 | } 294 | }); 295 | }; 296 | 297 | TaskFP.prototype.live = function(options, callback) { 298 | var self = this; 299 | var delay = 10; 300 | 301 | if (typeof options.delay != 'undefined') delay = options.delay; 302 | async.series([ 303 | function(callback) { 304 | self.FP.on('sunlightChange', function(sunlight) { 305 | console.log('sunlight = ' + sunlight.toFixed(2) + ' mol/m²/d'); 306 | }); 307 | 308 | self.FP.on('soilTemperatureChange', function(temperature) { 309 | console.log('soil temperature = ' + temperature.toFixed(2) + '°C'); 310 | }); 311 | 312 | self.FP.on('airTemperatureChange', function(temperature) { 313 | console.log('air temperature = ' + temperature.toFixed(2) + '°C'); 314 | }); 315 | 316 | self.FP.on('soilMoistureChange', function(soilMoisture) { 317 | console.log('soil moisture = ' + soilMoisture.toFixed(2) + '%'); 318 | }); 319 | 320 | self.FP.on('calibratedSoilMoistureChange', function(soilMoisture) { 321 | console.log('calibrated soil moisture = ' + soilMoisture.toFixed(2) + '%'); 322 | }); 323 | 324 | self.FP.on('calibratedAirTemperatureChange', function(temperature) { 325 | console.log('calibrated air temperature = ' + temperature.toFixed(2) + '°C'); 326 | }); 327 | 328 | self.FP.on('calibratedSunlightChange', function(sunlight) { 329 | console.log('calibrated sunlight = ' + sunlight.toFixed(2) + ' mol/m²/d'); 330 | }); 331 | 332 | self.FP.on('calibratedEaChange', function(ea) { 333 | console.log('calibrated EA = ' + ea.toFixed(2)); 334 | }); 335 | 336 | self.FP.on('calibratedEcbChange', function(ecb) { 337 | console.log('calibrated ECB = ' + ecb.toFixed(2) + ' dS/m'); 338 | }); 339 | 340 | self.FP.on('calibratedEcPorousChange', function(ecPorous) { 341 | console.log('calibrated EC porous = ' + ecPorous.toFixed(2)+ ' dS/m'); 342 | }); 343 | callback(); 344 | }, 345 | function(callback) { 346 | self.proc('Live'); 347 | self.FP.enableLiveMode(callback); 348 | }, 349 | function(callback) { 350 | setTimeout(callback, delay * 1000); 351 | }, 352 | function(callback) { 353 | self.proc('End live'); 354 | self.FP.disableLiveMode(callback); 355 | } 356 | ], function(err, res) { 357 | callback(err); 358 | }); 359 | }; 360 | 361 | TaskFP.prototype.update = function(file, callback) { 362 | this.proc('Update'); 363 | this.FP.updateFirmware(file, callback); 364 | }; 365 | 366 | module.exports = TaskFP; 367 | -------------------------------------------------------------------------------- /lib/helpers.js: -------------------------------------------------------------------------------- 1 | function isFormatPeripheral(str) { 2 | if (typeof str == 'string' && str.length === 12) { 3 | return true; 4 | } 5 | return false; 6 | } 7 | 8 | function isFormatCloud(str) { 9 | if (typeof str == 'string' && str.length === 16) { 10 | return true; 11 | } 12 | return false; 13 | } 14 | 15 | function uuidPeripheralToCloud(uuid) { 16 | if (isFormatPeripheral(uuid)) { 17 | return ((uuid.substr(0, 6) + '0000' + uuid.substr(6, 6)).toUpperCase()); 18 | } 19 | else { 20 | return (uuid); 21 | } 22 | } 23 | 24 | function uuidCloudToPeripheral(uuid) { 25 | if (isFormatCloud(uuid)) { 26 | return (uuid.substr(0, 6).toLowerCase() + uuid.substr(10, 6).toLowerCase()); 27 | } 28 | else { 29 | return (uuid); 30 | } 31 | } 32 | 33 | exports.uuidPeripheralToCloud = uuidPeripheralToCloud; 34 | exports.uuidCloudToPeripheral = uuidCloudToPeripheral; 35 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "node-flower-bridge", 3 | "version": "1.5.2", 4 | "description": "Flower Bridge", 5 | "main": "lib/FlowerPower.js", 6 | "scripts": { 7 | "test": "echo 'test' && exit 0", 8 | "start": "./bridge display" 9 | }, 10 | "author": "Bruno Sautron", 11 | "license": "ISC", 12 | "dependencies": { 13 | "async": "^1.5.1", 14 | "buffer-equal": "^1.0.0", 15 | "chance": "^0.8.0", 16 | "cli-color": "^1.1.0", 17 | "deasync": "^0.1.4", 18 | "nedb": "^1.2.1", 19 | "promise": "^7.1.1" 20 | }, 21 | "repository": { 22 | "type": "git", 23 | "url": "git+https://github.com/Parrot-Developers/node-flower-bridge.git" 24 | }, 25 | "bugs": { 26 | "url": "https://github.com/Parrot-Developers/node-flower-bridge/issues" 27 | }, 28 | "homepage": "https://github.com/Parrot-Developers/node-flower-bridge#readme" 29 | } 30 | -------------------------------------------------------------------------------- /start.js: -------------------------------------------------------------------------------- 1 | var clc = require('cli-color'); 2 | var Bridge = require('./index'); 3 | var credentials = require('./credentials'); 4 | 5 | var brooklyn = new Bridge(credentials.url); 6 | delete credentials.url; 7 | 8 | var valid = clc.green.bold('✔'); 9 | var bad = clc.red.bold('✘'); 10 | 11 | var options = { 12 | type: [], 13 | priority: [], 14 | }; 15 | 16 | credentials['auto-refresh'] = false; 17 | brooklyn.loginToApi(credentials, function(err) { 18 | if (err) { 19 | console.error(err.toString()); 20 | process.exit(1); 21 | } 22 | }); 23 | 24 | brooklyn.on('login', function() { 25 | console.log(valid, clc.green('Login!')); 26 | brooklyn.all('synchronize', options); 27 | }); 28 | 29 | brooklyn.on('newProcess', function(flowerPower) { 30 | console.log("[" + flowerPower.lastDate.toString().substr(4, 20) + "]:", flowerPower.name + ": " + flowerPower.lastProcess); 31 | }); 32 | 33 | brooklyn.on('info', function(info) { 34 | console.log(clc.yellow("[" + info.date.toString().substr(4, 20) + "]:", info.message)); 35 | }); 36 | 37 | brooklyn.on('newState', function(state) { 38 | console.log(clc.xterm(0)("[" + new Date().toString().substr(4, 20) + "]:", state)); 39 | }); 40 | 41 | brooklyn.on('error', function(error) { 42 | console.log(clc.red("[" + error.date.toString().substr(4, 20) + "]:", error.message)); 43 | }); 44 | 45 | brooklyn.on('end', function() { 46 | process.exit(0); 47 | }); 48 | -------------------------------------------------------------------------------- /updatedb: -------------------------------------------------------------------------------- 1 | #!/usr/bin/node 2 | var Datastore = require('nedb'); 3 | var clc = require('cli-color'); 4 | var db = new Datastore({filename: './database/process.db', autoload: true}); 5 | 6 | var messColor = { 7 | 'No update required': clc.yellow('No update required'), 8 | 'Updated': clc.green.bold('Updated'), 9 | 'Not found': clc.red.bold('Not found'), 10 | 'Disconnected': clc.yellow.bold('Disconnected') 11 | } 12 | 13 | function usage() { 14 | console.log("All states of your Flower Powers are push in a local Database"); 15 | console.log("You can request this Database to know the last process/update/errors ..."); 16 | console.log("Each requests are sorted by date"); 17 | console.log(); 18 | console.log("USAGE:"); 19 | console.log("\t./updatedb all [limit]"); 20 | console.log("\t./updatedb key=value [...] [limit]"); 21 | console.log(); 22 | console.log("ARGUMENTS:"); 23 | console.log("\tall: Show last process"); 24 | console.log(); 25 | console.log("\tkey:"); 26 | console.log("\t\name: For the name of device"); 27 | console.log("\t\tproc: For the proc message"); 28 | console.log("\t\tdate: For the date of process"); 29 | console.log(); 30 | console.log("\tvalue:"); 31 | for (var mess in messColor) { 32 | console.log("\t\t" + mess); 33 | } 34 | console.log(); 35 | console.log("\tlimit (optional): Show N last process. By default, limit=10"); 36 | console.log(); 37 | console.log('EXAMPLES:'); 38 | console.log("\t./updatedb all\t\t\t: 10 last process"); 39 | console.log("\t./updatedb name=\"9003b7e7aa64\"\t: last process of this Flower power"); 40 | console.log("\t./updatedb name=\"9003b7e7aa64\" proc=\"Updated\" 5\t: Show 5 last update for this Flower Power"); 41 | console.log(); 42 | console.log("AUTHOR:"); 43 | console.log("\tWritten by Bruno Sautron."); 44 | process.exit(0); 45 | } 46 | 47 | function printReq(find, limit) { 48 | console.log( ' - REQUEST for (limit: ' + limit + '):' ); 49 | 50 | if (find instanceof Array) { 51 | for (var i in find) { 52 | var key = Object.keys(find[i])[0]; 53 | console.log("\t" + key + ": " + find[i][key]); 54 | } 55 | } 56 | else { 57 | console.log("\t" + limit + ' last process'); 58 | } 59 | console.log(); 60 | } 61 | 62 | function findDb(find, max) { 63 | printReq(find, max); 64 | var toFind = (find) ? {$and: find} : {}; 65 | 66 | db.find(toFind).sort({date: -1}).limit(max).exec(function(err, docs) { 67 | for (var i in docs) { 68 | console.log("[" + docs[i].date + "]:", clc.xterm(docs[i].color)(docs[i].name + ":"), (messColor[docs[i].proc]) ? messColor[docs[i].proc] : docs[i].proc); 69 | } 70 | }); 71 | } 72 | 73 | 74 | /* main */ 75 | var argv = process.argv; 76 | var max = 10; 77 | 78 | if (!isNaN(argv[argv.length - 1])) { 79 | max = argv[argv.length - 1]; 80 | argv.pop(); 81 | } 82 | 83 | if (argv.length == 2) usage(); 84 | argv.shift(); 85 | argv.shift(); 86 | 87 | if (argv[0].toLowerCase() == 'all') findDb(null, max); 88 | else { 89 | var toFind = []; 90 | 91 | for (var i in argv) { 92 | var key = argv[i].split("=")[0]; 93 | var json = {}; 94 | 95 | json[key] = argv[i].split("=")[1]; 96 | toFind.push(json); 97 | } 98 | findDb(toFind, max); 99 | } 100 | --------------------------------------------------------------------------------