├── .bowerrc ├── .gitignore ├── README.md ├── app.js ├── bower.json ├── database.db ├── front ├── css │ └── style.css ├── imgs │ ├── .DS_Store │ ├── ._.DS_Store │ ├── ._balao.png │ ├── ._planta.png │ ├── balao.png │ └── planta.png └── js │ └── app.js ├── init ├── cron.js └── db.js ├── package.json └── view └── index.html /.bowerrc: -------------------------------------------------------------------------------- 1 | { 2 | "directory": "front/bower_components" 3 | } -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ### Bower ### 2 | bower_components 3 | .bower-cache 4 | .bower-registry 5 | .bower-tmp 6 | 7 | 8 | ### Node ### 9 | # Logs 10 | logs 11 | *.log 12 | 13 | # Runtime data 14 | pids 15 | *.pid 16 | *.seed 17 | 18 | # Directory for instrumented libs generated by jscoverage/JSCover 19 | lib-cov 20 | 21 | # Coverage directory used by tools like istanbul 22 | coverage 23 | 24 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 25 | .grunt 26 | 27 | # node-waf configuration 28 | .lock-wscript 29 | 30 | # Compiled binary addons (http://nodejs.org/api/addons.html) 31 | build/Release 32 | 33 | # Dependency directory 34 | # https://www.npmjs.org/doc/misc/npm-faq.html#should-i-check-my-node_modules-folder-into-git 35 | node_modules 36 | 37 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Controlando umidade do solo com arduino e node.js 2 | Sistema simples que pega informações do solo usando sensor de umidade higrômetro com arduino uno. 3 | 4 | ### Instalação 5 | 6 | ```bash 7 | $ git clone https://github.com/pedrohs/umidade-solo-arduino.git 8 | $ cd umidade-solo-arduino 9 | $ npm install 10 | $ bower install 11 | $ node app.js 12 | ``` 13 | ### Configuração do arduino 14 | 15 | - Abra a IDE do Arduino, selecione: File > Examples > Firmata > StandardFirmata. 16 | - Clique no botão Upload. 17 | 18 | ### Dependências utilizadas 19 | * [Johnny Five](https://github.com/rwaldron/johnny-five) 20 | * [Cron](https://github.com/ncb000gt/node-cron) 21 | * [Async](https://github.com/caolan/async) 22 | * [Connect](https://github.com/senchalabs/connect) 23 | * [Connect-route](https://github.com/baryshev/connect-route) 24 | * [neDB](https://github.com/louischatriot/nedb) 25 | * [serve-static](https://github.com/expressjs/serve-static) 26 | * [Socket.io](https://github.com/Automattic/socket.io) 27 | * [Moment](https://github.com/moment/moment) 28 | 29 | ### Imagem 30 |  31 | 32 | ### FAQ 33 | * Para acessar o painel: 34 | Depois de iniciar o servidor acesse pelo navegador: http://localhost:3000/ 35 | 36 | ### Atualização 37 | * Foi removido o antigo sistema de salvamento das informações e substituído pelo [neDB](https://github.com/louischatriot/nedb) 38 | 39 | ### Contribuidores 40 | * [gpedro](https://github.com/gpedro) 41 | -------------------------------------------------------------------------------- /app.js: -------------------------------------------------------------------------------- 1 | var five = require('johnny-five'); 2 | var arduino = new five.Board(); 3 | var DataStore = require('nedb'); 4 | var connect = require('connect'); 5 | var connectRoute = require('connect-route'); 6 | var serveStatic = require('serve-static'); 7 | var app = connect(); 8 | var http = require('http').createServer(app); 9 | var fs = require('fs'); 10 | var io = require('socket.io')(http); 11 | 12 | var db = new DataStore({filename: 'database.db', autoload: true}); 13 | var Fn = five.Fn; 14 | var rele; 15 | 16 | require('./init/db.js')(app, db); 17 | require('./init/cron.js')(app, db); 18 | 19 | app.use(serveStatic("front/")); 20 | app.cronJob(); 21 | 22 | arduino.on('ready', function(){ 23 | console.log("Arduino Pronto"); 24 | 25 | //sensor higrômetro 26 | var sensor = new five.Sensor("A0"); 27 | 28 | //verifica se o rele esta ligado ou não no banco de dados 29 | if(app.rele.status){ 30 | rele = new five.Pin(app.rele.porta); 31 | } 32 | 33 | sensor.on('data', function(){ 34 | app.valor = Fn.map(this.value, 0, 1023, 100, 0); 35 | releAction(app.valor); 36 | io.emit('umidade real', app.valor); 37 | }); 38 | }); 39 | 40 | //controla o liga/desliga do rele 41 | function releAction(porcent){ 42 | if(app.rele.status){ 43 | if(porcent <= app.rele.porcent){ 44 | rele.high(); 45 | }else{ 46 | rele.low(); 47 | } 48 | } 49 | }; 50 | 51 | //Rotas 52 | app.use(connectRoute(function(router){ 53 | router.get('/', function(req, res, next){ 54 | res.end(fs.readFileSync('view/index.html')); 55 | }); 56 | router.get('/dados', function(req, res, next){ 57 | db.find({type: 'dados'}).sort({ dia: 1 }).exec(function(err, data){ 58 | var dias = []; 59 | var porcents = []; 60 | var dados = []; 61 | for (var i = 0; i < data.length; i++) { 62 | dias.push(data[i].dia); 63 | porcents.push(data[i].porcent); 64 | }; 65 | dados.push(dias); 66 | dados.push(porcents); 67 | 68 | res.end(JSON.stringify(dados)); 69 | }); 70 | }); 71 | })); 72 | 73 | //Socket.io 74 | io.on('connection', function(socket){ 75 | console.log("Conectado"); 76 | socket.emit('umidade real', app.valor); 77 | socket.emit('rele config', app.rele); 78 | 79 | socket.on('set cron', function(cron){ 80 | db.update({type: 'cron'}, {type: 'cron', hora: cron.hora, minuto: cron.minuto, maxDados: cron.maxDados}, {}, function(err){ 81 | if(err){return console.log(err)} 82 | app.cronJob(); 83 | }); 84 | }); 85 | 86 | socket.on('rele config', function(releConfig){ 87 | if(releConfig.status){ 88 | db.update({type: 'ConfigRele'}, {$set: {status: releConfig.status, porta: releConfig.porta, porcent: releConfig.porcent}}, function(err){ 89 | if(err){return console.log(err)}; 90 | db.findOne({type: 'ConfigRele'}, function(err, data){ 91 | if(err){return console.log(err)}; 92 | app.rele = data; 93 | }); 94 | }); 95 | }else{ 96 | db.update({type: 'ConfigRele'}, {$set: {status: releConfig.status}}, {}, function(err){ 97 | if(err){return console.log(err)}; 98 | db.findOne({type: 'ConfigRele'}, function(err, data){ 99 | if(err){return console.log(err)}; 100 | app.rele = data; 101 | rele.query(function(state) { 102 | if(state.value == 1){ 103 | rele.low(); 104 | } 105 | }); 106 | }); 107 | }); 108 | } 109 | }); 110 | 111 | db.findOne({type: "cron"}, function(err, dados){ 112 | if(err){return console.log(err)} 113 | socket.emit('cron config', dados); 114 | }); 115 | }); 116 | 117 | http.listen(4000, function(){ 118 | console.log("Servidor http Online"); 119 | }); -------------------------------------------------------------------------------- /bower.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "umidade-solo", 3 | "version": "1.0.2", 4 | "homepage": "https://github.com/pedrohs/umidade-solo-arduino", 5 | "authors": [ 6 | "Pedro HS <9.pedro.deoliveira@gmail.com>" 7 | ], 8 | "description": "Exibe dados da umidade do solo com arduino e node.js", 9 | "main": "app.js", 10 | "moduleType": [ 11 | "node" 12 | ], 13 | "license": "MIT", 14 | "ignore": [ 15 | "**/.*", 16 | "node_modules", 17 | "bower_components", 18 | "front/bower_components", 19 | "test", 20 | "tests" 21 | ], 22 | "dependencies": { 23 | "jquery": "~2.1.4", 24 | "Chart.js": "~1.0.2", 25 | "bootstrap": "~3.3.4", 26 | "bootstrap-switch": "~3.3.2" 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /database.db: -------------------------------------------------------------------------------- 1 | {"type":"dados","dia":"15/05","porcent":60,"_id":"1Z6cSGVaEEdqsJFw"} 2 | {"type":"dados","dia":"15/05","porcent":65,"_id":"3XAOl4eGzFP28Nf8"} 3 | {"type":"dados","dia":"15/05","porcent":66,"_id":"4EgSqYsdi3WeuopP"} 4 | {"type":"dados","dia":"15/05","porcent":0,"_id":"AeRtcgZsmKo1Ny7t"} 5 | {"type":"dados","dia":"15/05","porcent":82,"_id":"BnTz54yVI1fM2xyW"} 6 | {"type":"dados","dia":"04/01","porcent":40,"_id":"GzxIl9LCwJuqVat6"} 7 | {"type":"dados","dia":"02/01","porcent":20,"_id":"HRzZr2l5NBeMwHWf"} 8 | {"type":"dados","dia":"03/01","porcent":30,"_id":"IoxDTVx0yixH3KoF"} 9 | {"type":"cron","hora":19,"minuto":29,"maxDados":15,"_id":"NoLkVeXkc6ATvIcW"} 10 | {"type":"ConfigRele","status":true,"porta":2,"porcent":50,"_id":"Yyn5s3MPxcSWhSBL"} 11 | {"type":"dados","dia":"15/05","porcent":0,"_id":"f7ihqnkENMMlx5pT"} 12 | {"type":"dados","dia":"15/05","porcent":82,"_id":"nedSyMiX3QGZX3Gn"} 13 | {"type":"dados","dia":"06/01","porcent":60,"_id":"oR36dYjhwOhXfLpK"} 14 | {"type":"dados","dia":"01/01","porcent":10,"_id":"tQS9r1TH6OU7fOZu"} 15 | {"type":"ConfigRele","status":false,"_id":"Yyn5s3MPxcSWhSBL"} 16 | {"type":"ConfigRele","status":true,"porta":2,"porcent":50,"_id":"Yyn5s3MPxcSWhSBL"} 17 | {"type":"ConfigRele","status":true,"porta":2,"porcent":50,"_id":"Yyn5s3MPxcSWhSBL"} 18 | {"type":"ConfigRele","status":true,"porta":2,"porcent":50,"_id":"Yyn5s3MPxcSWhSBL"} 19 | {"type":"ConfigRele","status":true,"porta":2,"porcent":10,"_id":"Yyn5s3MPxcSWhSBL"} 20 | {"type":"ConfigRele","status":true,"porta":2,"porcent":10,"_id":"Yyn5s3MPxcSWhSBL"} 21 | {"type":"ConfigRele","status":true,"porta":2,"porcent":10,"_id":"Yyn5s3MPxcSWhSBL"} 22 | -------------------------------------------------------------------------------- /front/css/style.css: -------------------------------------------------------------------------------- 1 | html{ 2 | height: 100%; 3 | } 4 | body{ 5 | margin: 0; 6 | padding: 0; 7 | background-color: #f3f3f3; 8 | } 9 | #topo { 10 | width: 100%; 11 | height: 560px; 12 | overflow: hidden; 13 | position: relative; 14 | padding-top: 40px; 15 | box-sizing: border-box; 16 | background: #e5eecd; /* Old browsers */ 17 | background: -moz-radial-gradient(center, ellipse cover, #e5eecd 0%, #a1c24b 100%); /* FF3.6+ */ 18 | background: -webkit-gradient(radial, center center, 0px, center center, 100%, color-stop(0%,#e5eecd), color-stop(100%,#a1c24b)); /* Chrome,Safari4+ */ 19 | background: -webkit-radial-gradient(center, ellipse cover, #e5eecd 0%,#a1c24b 100%); /* Chrome10+,Safari5.1+ */ 20 | background: -o-radial-gradient(center, ellipse cover, #e5eecd 0%,#a1c24b 100%); /* Opera 12+ */ 21 | background: -ms-radial-gradient(center, ellipse cover, #e5eecd 0%,#a1c24b 100%); /* IE10+ */ 22 | background: radial-gradient(ellipse at center, #e5eecd 0%,#a1c24b 100%); /* W3C */ 23 | filter: progid:DXImageTransform.Microsoft.gradient( startColorstr='#e5eecd', endColorstr='#a1c24b',GradientType=1 ); /* IE6-9 fallback on horizontal gradient */ 24 | } 25 | #planta { 26 | width: 335px; 27 | height: 478px; 28 | background-image: url('/imgs/planta.png'); 29 | margin: 0 auto; 30 | position: relative; 31 | } 32 | #planta > div { 33 | width: 124px; 34 | height: 124px; 35 | background-image: url('/imgs/balao.png'); 36 | background-repeat: no-repeat; 37 | position: absolute; 38 | top: 89px; 39 | left: 367px; 40 | text-align: center; 41 | font-family: 'Open Sans', sans-serif; 42 | font-size: 24px; 43 | color: #38bbff; 44 | padding-top: 46px; 45 | } 46 | #conteudo { 47 | width: 1000px; 48 | margin: 0 auto; 49 | padding-top: 15px; 50 | } 51 | #grafico { 52 | width: 1000px; 53 | height: 300px; 54 | padding: 15px; 55 | background-color: #fff; 56 | -webkit-box-shadow: 0px 0px 10px 0px rgba(173,173,173,1); 57 | -moz-box-shadow: 0px 0px 10px 0px rgba(173,173,173,1); 58 | box-shadow: 0px 0px 10px 0px rgba(173,173,173,1); 59 | } 60 | h1 { 61 | font-family: 'Open Sans', sans-serif; 62 | font-size: 24px; 63 | color: #393939; 64 | font-weight: 300; 65 | text-decoration: none; 66 | text-align: center; 67 | } 68 | #tab h1 { 69 | margin: 0; 70 | } 71 | h2 { 72 | font-family: 'Open Sans', sans-serif; 73 | font-size: 20px; 74 | color: #393939; 75 | font-weight: 300; 76 | text-decoration: none; 77 | } 78 | #releTitulo { 79 | float: left; 80 | } 81 | #inputMinutos, #porcetRele, #inputMaxDados{ 82 | width: 60px; 83 | } 84 | #configTitulo { 85 | list-style-type: none; 86 | margin: 0; 87 | padding: 0; 88 | } 89 | #configTitulo > li { 90 | display: inline; 91 | } 92 | .bootstrap-switch { 93 | margin: 12px; 94 | } 95 | .input { 96 | width: auto; 97 | } 98 | #rele > form { 99 | display: none; 100 | } -------------------------------------------------------------------------------- /front/imgs/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pedrohs/umidade-solo-arduino/23619717d49adcc5b093166e49d4b8f26a3ae39b/front/imgs/.DS_Store -------------------------------------------------------------------------------- /front/imgs/._.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pedrohs/umidade-solo-arduino/23619717d49adcc5b093166e49d4b8f26a3ae39b/front/imgs/._.DS_Store -------------------------------------------------------------------------------- /front/imgs/._balao.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pedrohs/umidade-solo-arduino/23619717d49adcc5b093166e49d4b8f26a3ae39b/front/imgs/._balao.png -------------------------------------------------------------------------------- /front/imgs/._planta.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pedrohs/umidade-solo-arduino/23619717d49adcc5b093166e49d4b8f26a3ae39b/front/imgs/._planta.png -------------------------------------------------------------------------------- /front/imgs/balao.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pedrohs/umidade-solo-arduino/23619717d49adcc5b093166e49d4b8f26a3ae39b/front/imgs/balao.png -------------------------------------------------------------------------------- /front/imgs/planta.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pedrohs/umidade-solo-arduino/23619717d49adcc5b093166e49d4b8f26a3ae39b/front/imgs/planta.png -------------------------------------------------------------------------------- /front/js/app.js: -------------------------------------------------------------------------------- 1 | $(document).ready(function(){ 2 | var socket = io(); 3 | 4 | socket.on('umidade real', function(dados){ 5 | $("#planta > div").text(dados + '%'); 6 | }); 7 | 8 | socket.on('rele config', function(rele){ 9 | if(rele.status){ 10 | $("[name='releStade']").bootstrapSwitch('state', true); 11 | $("#portaRele").val(rele.porta); 12 | $("#porcetRele").val(rele.porcent); 13 | }else{ 14 | $("[name='releStade']").bootstrapSwitch('state', false); 15 | } 16 | }); 17 | 18 | socket.on('cron config', function(cron){ 19 | $("#selectHora").val(cron.hora); 20 | $("#inputMinutos").val(cron.minuto); 21 | $("#inputMaxDados").val(cron.maxDados); 22 | }); 23 | 24 | $.getJSON("/dados", function(result){ 25 | var data = { 26 | labels: result[0], 27 | datasets: [ 28 | { 29 | fillColor: "rgba(100,255,23,0.2)", 30 | strokeColor: "rgba(66,199,0,1)", 31 | pointColor: "rgba(58,173,0,1)", 32 | pointStrokeColor: "#fff", 33 | pointHighlightFill: "#fff", 34 | pointHighlightStroke: "rgba(220,220,220,1)", 35 | data: result[1] 36 | } 37 | ] 38 | }; 39 | var ctx = $("#grafico").get(0).getContext("2d"); 40 | var grafico = new Chart(ctx).Line(data); 41 | }); 42 | 43 | $('#tab a').click(function (e) { 44 | e.preventDefault() 45 | $(this).tab('show') 46 | }); 47 | 48 | $("#setDados").submit(function(){ 49 | var hora = parseInt($("#selectHora").val()); 50 | var minutos = parseInt($("#inputMinutos").val()); 51 | var maxDados = parseInt($("#inputMaxDados").val()); 52 | socket.emit('set cron', {'hora': hora, 'minuto': minutos, 'maxDados': maxDados}); 53 | 54 | return false; 55 | }); 56 | 57 | $("[name='releStade']").bootstrapSwitch('state', false); 58 | $('input[name="releStade"]').on('switchChange.bootstrapSwitch', function(event, state) { 59 | $("#rele > form").slideToggle("slow"); 60 | if(state){ 61 | $("#formRele").submit(function(){ 62 | var portaRele = parseInt($("#portaRele").val()); 63 | var porcetRele = parseInt($("#porcetRele").val()); 64 | console.log(porcetRele); 65 | socket.emit('rele config', {'status': true, 'porta': portaRele, 'porcent': porcetRele}); 66 | return false; 67 | }); 68 | }else{ 69 | socket.emit('rele config', {'status': false}); 70 | } 71 | }); 72 | }); -------------------------------------------------------------------------------- /init/cron.js: -------------------------------------------------------------------------------- 1 | module.exports = function(app, db){ 2 | var moment = require('moment'); 3 | var CronJob = require('cron').CronJob; 4 | var async = require('async'); 5 | 6 | app.cronJob = function(){ 7 | var cron; 8 | async.series([ 9 | function(callback){ 10 | db.findOne({type: "cron"}, function(err, dados){ 11 | if(err){return console.log(err);} 12 | cron = dados; 13 | callback(null); 14 | }); 15 | }, 16 | function(callback){ 17 | var job = new CronJob('00 '+ cron.minuto +' '+ cron.hora +' * * 1-6', function(){ 18 | db.find({type: 'dados'}).sort({ dia: 1 }).exec(function(err, data){ 19 | if(data.length < cron.maxDados){ 20 | var newDado = { 21 | type: "dados", 22 | dia: moment().format('DD/MM'), 23 | porcent: app.valor 24 | }; 25 | db.insert(newDado, function(err, newdado){ 26 | if(err){return console.log(err);} 27 | console.log("inserido"); 28 | }); 29 | }else{ 30 | var last = data.length - 1; 31 | console.log(last); 32 | db.remove({_id: data[last]._id}, function(err){ 33 | if(err){return console.log(err);} 34 | console.log("Removido"); 35 | var newDado = { 36 | type: "dados", 37 | dia: moment().format('DD/MM'), 38 | porcent: app.valor 39 | }; 40 | db.insert(newDado, function(err, newdado){ 41 | if(err){return console.log(err);} 42 | console.log("inserido"); 43 | }); 44 | }); 45 | } 46 | }); 47 | },true, "Brazil/Brasilia"); 48 | callback(null); 49 | } 50 | ]); 51 | }; 52 | 53 | function dbInsertDados(){ 54 | var newDado = { 55 | type: "dados", 56 | dia: moment().format('DD/MM'), 57 | porcent: app.valor 58 | }; 59 | db.insert(newDado, function(err, newdado){ 60 | if(err){return console.log(err);} 61 | console.log("inserido"); 62 | }); 63 | }; 64 | } -------------------------------------------------------------------------------- /init/db.js: -------------------------------------------------------------------------------- 1 | module.exports = function(app, db){ 2 | db.findOne({type: 'ConfigRele'}, function(err, data){ 3 | if(data.length == 0){ 4 | var rele = { 5 | config: "rele", 6 | status: false, 7 | porta: 2, 8 | porcent: 50 9 | }; 10 | db.insert(rele, function(err, newRele){ 11 | console.log(err); 12 | app.rele = newRele; 13 | }); 14 | }else{ 15 | app.rele = data; 16 | } 17 | }); 18 | } -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "umidade-solo", 3 | "version": "1.0.2", 4 | "description": "Exibe dados da umidade do solo com arduino e node.js", 5 | "main": "app.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "repository": { 10 | "type": "git", 11 | "url": "https://github.com/pedrohs/umidade-solo-arduino.git" 12 | }, 13 | "author": "Pedro H.S", 14 | "license": "MIT", 15 | "bugs": { 16 | "url": "https://github.com/pedrohs/umidade-solo-arduino/issues" 17 | }, 18 | "homepage": "https://github.com/pedrohs/umidade-solo-arduino", 19 | "dependencies": { 20 | "async": "^0.9.0", 21 | "connect": "^3.3.5", 22 | "connect-route": "^0.1.5", 23 | "cron": "^1.0.9", 24 | "johnny-five": "^0.8.74", 25 | "moment": "^2.10.3", 26 | "nedb": "^1.1.2", 27 | "serve-static": "^1.9.3", 28 | "socket.io": "^1.3.5" 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /view/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 |
4 | 5 |