Com o surgimento do Arduino, foi possível ampliar as aplicações de eletrônica e robótica em geral com a rápida adesão e criação de bibliotecas e módulos específicos e fáceis de manusear. Tratando-se de software, o Node.js tornou-se muito popular com o surgimento de milhares de pacotes e a possibilidade de estabelecer conexões com várias linguagens, tipos de bancos de dados e inúmeras tecnologias. Dentre elas, a comunicação serial, padrão usado na maioria dos projetos de sistemas microcontrolados.
Estabelecendo a conexão via serial de seu sistema embarcado com o Node.js torna possível expandir a sua aplicação uma vez que o node implementa um servidor web. Você pode facilmente utilizar as milhares de APIs web disponíveis além de hospedar seu sistema em um servidor e fornecer acesso por smartphones, tablets e demais dispositivos que se conectam à rede mundial de computadores.
Esse tutorial tem como objetivo explicar como estabelecer a comunicação serial entre seu sistema microcontrolado (não necessariamente Arduino, mas qualquer um que implemente comunicação serial) e o Node.js, além de utilizar uma biblioteca em JavaScript para plotar gráficos em tempo real utilizando uma página web.
Primeiro passo, é preciso instalar o Node.js em sua máquina, basta acessar o site https://nodejs.org/download/ e seguir para a seção de downloads.
A instalação segue um fluxo tranquilo em Windows e Mac. No Windows, você precisa adicionar o caminho do executável do node para a variável de ambiente do sistema PATH para poder executá-lo do terminal do Windows. Isso pode ser feito em painel de controle, configurações avançadas.
Se está usando ambiente GNU/Linux, basta baixar o arquivo compactado com o código-fonte, extrair os dados para uma pasta qualquer, em seguida, acesse a pasta e digite os comandos:
./configure
make
#make install
Se você encontrou algum erro nessa parte, certifique-se de ter instalado o pacote build-essential que contém os compiladores necessários para gerar o executável do node. O comando make pode demorar mais de 5 minutos porque ele está compilando o sistema e isso demora mesmo. Lembre-se que o terceiro comando deve ser feito com privilégios de administrador (sudo), uma vez que ele copia o arquivo para a pasta bin do sistema.
Pronto, node.js já está instalado em sua máquina. Junto com ele, existe o npm (node package manager) que é o gerenciador de pacotes do node. A partir do qual é possível instalar pacotes para as mais variadas aplicações (acesse esse link para conferir do que estou falando: https://www.npmjs.com/).
Vamos inicialmente criar um diretório para organizar nosso app (geralmente os projetos em node são chamados de app - aplicação). Abre um terminal e navegue até o diretório. Feito isso, digite o seguinte comando:
npm install express
Esse é o primeiro pacote que vamos instalar. Verifique que após a instalação, um subdiretório foi criado: node_modules. Todos os módulos que você instalar ficarão localizados dentro dessa pasta. A menos que use a opção -g em que o módulo é instalado globalmente no diretório do node no seu sistema (opção usada quando vamos instalar o socket.io - algumas vezes ele dá problema e exige que seja instalado globalmente).
http://expressjs.com/ esse pacote é interessante porque ele é um framework que encapsula muitas funções do node para você e permite um desenvolvimento mais fluido para seu app. Para maiores informações, acesse a página dele.
Agora digite o seguinte comando:
npm install serialport
Esse módulo é o responsável em estabelecer a comunicação serial com o node. Maiores detalhes aqui: https://www.npmjs.com/package/serialport .
Por fim:
npm install socket.io
Já que temos nossos pacotes básicos, vamos criar um arquivo chamado server.js dentro do nosso diretório de trabalho. Esse é o arquivo principal de nossa aplicação. Vamos iniciar a codificação. Primeiro requisite os seguintes arquivos (semelhante ao include do C e require do PHP):
var app = require('express')();
var express = require('express');
Essas variáveis irão armazenar objetos que utilizaremos mais adiante. Em seguida vamos utilizar um diretório para servir arquivos públicos como arquivos .css e .js.
app.use(express.static(__dirname + '/public'));
Definimos executando o método use do app que instanciamos mais adiante. Eu separei o arquivo express para justamente poder utilizar a chamada express.static acima. Agora crie também um diretório chamado public na pasta do projeto. Iremos adicionar alguns arquivos mais na frente no tutorial.
O próximo passo é criar o nosso servidor http e instanciar o socket.
var http = require('http').Server(app);
var io = require('socket.io')(http);
Feito isso, temos o nosso servidor e o socket prontos para serem usados. Agora definimos rotas para o servidor web responder às requisições dos usuários com a seguinte linha:
app.get('/', function(req, res) {
res.sendfile('index.html');
});
A função de callback aceita dois parâmetros, uma requisição e uma resposta. Isso quer dizer que, quando o usuário requisitar o endereço raiz da nossa aplicação "/", o node vai retornar uma página chamada index.html que depois criaremos.
Agora vamos criar a variável do socket:
var mySocket;
Registramos o socket quando a conexão é estabelecida da seguinte maneira:
io.on('connection', function(socket) {
mySocket = socket;
console.log('a user connected');
socket.on('disconnect', function() {
mySerialPort.close(function(err) {
if (err) console.log("Error at disconnect event: " + err);
console.log('port closed');
});
});
});
Registramos eventos usando o método on. Quando o usuário emitir um sinal para fechar a conexão, o nosso socket irá encerrar a comunicação (on 'disconnect').
Vamos definir agora a porta serial:
var serialport = require("serialport");
var SerialPort = serialport.SerialPort;
Em seguida instanciamos o objeto com as configurações:
var mySerialPort = new SerialPort("/dev/ttyACM0", {
baudrate: 115200,
parser: serialport.parsers.readline("\n")
});
Observe que é preciso dizer a porta serial onde seu microcontrolador está conectado. No Debian ela foi reconhecida como /dev/ttyACM0, mas no Windows será COMx e no Mac como /dev/tty.usbmodemx. Certifique-se de colocar o caminho correto do seu dispositivo. Definimos também a taxa de transmissão dos dados e escolhemos um parser para a serial. Ele vai ler linhas e reconhecer quando houver mudança de linha "\n". Isso pode ser encontrado na documentação do serialport.
Agora vamos criar eventos para a nossa porta serial.
Quando ela for aberta corretamente, vamos indicar uma mensagem para o usuário:
mySerialPort.on("open", function() {
console.log("Port Open");
});
Veja que em cada evento, existe uma callback relacionada como ele a qual é executada quando o evento é disparado.
Um evento especial chamado 'data' é quando os dados chegam na porta serial. Vamos implementar a leitura dos dados que chegam:
mySerialPort.on("data", function(data) {
console.log('data received: ' + data); // apenas debug
io.emit('serialData', {
dado: parseFloat(data)
});
});
app.use(express.static(__dirname + '/public'));
Definimos executando o método use do app que instanciamos mais adiante. Eu separei o arquivo express para justamente poder utilizar a chamada express.static acima. Agora crie também um diretório chamado public na pasta do projeto. Iremos adicionar alguns arquivos mais na frente no tutorial.
O próximo passo é criar o nosso servidor http e instanciar o socket.
var http = require('http').Server(app);
var io = require('socket.io')(http);
Feito isso, temos o nosso servidor e o socket prontos para serem usados. Agora definimos rotas para o servidor web responder às requisições dos usuários com a seguinte linha:
app.get('/', function(req, res) {
res.sendfile('index.html');
});
A função de callback aceita dois parâmetros, uma requisição e uma resposta. Isso quer dizer que, quando o usuário requisitar o endereço raiz da nossa aplicação "/", o node vai retornar uma página chamada index.html que depois criaremos.
Agora vamos criar a variável do socket:
var mySocket;
Registramos o socket quando a conexão é estabelecida da seguinte maneira:
io.on('connection', function(socket) {
mySocket = socket;
console.log('a user connected');
socket.on('disconnect', function() {
mySerialPort.close(function(err) {
if (err) console.log("Error at disconnect event: " + err);
console.log('port closed');
});
});
});
Registramos eventos usando o método on. Quando o usuário emitir um sinal para fechar a conexão, o nosso socket irá encerrar a comunicação (on 'disconnect').
Vamos definir agora a porta serial:
var serialport = require("serialport");
var SerialPort = serialport.SerialPort;
Em seguida instanciamos o objeto com as configurações:
var mySerialPort = new SerialPort("/dev/ttyACM0", {
baudrate: 115200,
parser: serialport.parsers.readline("\n")
});
Observe que é preciso dizer a porta serial onde seu microcontrolador está conectado. No Debian ela foi reconhecida como /dev/ttyACM0, mas no Windows será COMx e no Mac como /dev/tty.usbmodemx. Certifique-se de colocar o caminho correto do seu dispositivo. Definimos também a taxa de transmissão dos dados e escolhemos um parser para a serial. Ele vai ler linhas e reconhecer quando houver mudança de linha "\n". Isso pode ser encontrado na documentação do serialport.
Agora vamos criar eventos para a nossa porta serial.
Quando ela for aberta corretamente, vamos indicar uma mensagem para o usuário:
mySerialPort.on("open", function() {
console.log("Port Open");
});
Veja que em cada evento, existe uma callback relacionada como ele a qual é executada quando o evento é disparado.
Um evento especial chamado 'data' é quando os dados chegam na porta serial. Vamos implementar a leitura dos dados que chegam:
mySerialPort.on("data", function(data) {
console.log('data received: ' + data); // apenas debug
io.emit('serialData', {
dado: parseFloat(data)
});
});
Observe que o socket já foi incluído dentro do evento, e, quando os dados chegam, já emitimos um evento via socket para que o usuário que está acessando a página veja o resultado. O evento 'serialData' foi criado por mim e esse nome será utilizado posteriormente na página index.html para gerenciar a chegada de novos dados pelo socket. Eu configurei o Arduino para de tempos em tempos enviar um número ponto flutuante (float) pela serial.
Por fim, vamos iniciar o nosso servidor:
http.listen(3000, function() {
console.log('listening on *:3000');
});
Esse comando faz com que o servidor esteja ligado e funcione na porta 3000.
Vamos agora implementar o nosso index.html. Primeiramente, vamos acessar a página da biblioteca Chart.js que vamos utilizar para plotar os gráficos. Crie um diretório js dentro da pasta public do projeto. Acesse a página http://www.chartjs.org/ e clique em download. Quando abrir o repositório do github, clique no arquivo Chart.js, depois na opção Raw e salve o arquivo dentro do diretório public/js de sua aplicação.
Estarei utilizando gráfico de linhas e, para maiores informações, acesse a documentação dele aqui: http://www.chartjs.org/docs/ .
Vamos criar nosso index.html. Basicamente configuramos o socket, instanciamos o Chartjs, configuramos alguns parâmetros e tratamos os eventos do socket para que os dados sejam plotados. Definimos um canvas, que é o lugar onde o gráfico será plotado. No meu caso, usei algumas configurações globais e coloquei dentro do diretório js também, o arquivo está em logo abaixo.
// Conteúdo do arquivo ChartConfig.js
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 | Chart.defaults.global = { // Boolean - Whether to animate the chart animation: false, // Number - Number of animation steps animationSteps: 0, // String - Animation easing effect animationEasing: "easeOutQuart", // Boolean - If we should show the scale at all showScale: true, // Boolean - If we want to override with a hard coded scale scaleOverride: false, // ** Required if scaleOverride is true ** // Number - The number of steps in a hard coded scale scaleSteps: null, // Number - The value jump in the hard coded scale scaleStepWidth: null, // Number - The scale starting value scaleStartValue: null, // String - Colour of the scale line scaleLineColor: "rgba(0,0,0,.1)", // Number - Pixel width of the scale line scaleLineWidth: 1, // Boolean - Whether to show labels on the scale scaleShowLabels: true, // Interpolated JS string - can access value scaleLabel: "<%=value%>", // Boolean - Whether the scale should stick to integers, not floats even if drawing space is there scaleIntegersOnly: true, // Boolean - Whether the scale should start at zero, or an order of magnitude down from the lowest value scaleBeginAtZero: false, // String - Scale label font declaration for the scale label scaleFontFamily: "'Helvetica Neue', 'Helvetica', 'Arial', sans-serif", // Number - Scale label font size in pixels scaleFontSize: 12, // String - Scale label font weight style scaleFontStyle: "normal", // String - Scale label font colour scaleFontColor: "#666", // Boolean - whether or not the chart should be responsive and resize when the browser does. responsive: true, // Boolean - whether to maintain the starting aspect ratio or not when responsive, if set to false, will take up entire container maintainAspectRatio: true, // Boolean - Determines whether to draw tooltips on the canvas or not showTooltips: false, // Function - Determines whether to execute the customTooltips function instead of drawing the built in tooltips (See [Advanced - External Tooltips](#advanced-usage-custom-tooltips)) customTooltips: false, // Array - Array of string names to attach tooltip events tooltipEvents: ["mousemove", "touchstart", "touchmove"], // String - Tooltip background colour tooltipFillColor: "rgba(0,0,0,0.8)", // String - Tooltip label font declaration for the scale label tooltipFontFamily: "'Helvetica Neue', 'Helvetica', 'Arial', sans-serif", // Number - Tooltip label font size in pixels tooltipFontSize: 14, // String - Tooltip font weight style tooltipFontStyle: "normal", // String - Tooltip label font colour tooltipFontColor: "#fff", // String - Tooltip title font declaration for the scale label tooltipTitleFontFamily: "'Helvetica Neue', 'Helvetica', 'Arial', sans-serif", // Number - Tooltip title font size in pixels tooltipTitleFontSize: 14, // String - Tooltip title font weight style tooltipTitleFontStyle: "bold", // String - Tooltip title font colour tooltipTitleFontColor: "#fff", // Number - pixel width of padding around tooltip text tooltipYPadding: 10, // Number - pixel width of padding around tooltip text tooltipXPadding: 10, // Number - Size of the caret on the tooltip tooltipCaretSize: 8, // Number - Pixel radius of the tooltip border tooltipCornerRadius: 6, // Number - Pixel offset from point x to tooltip edge tooltipXOffset: 10, // String - Template string for single tooltips tooltipTemplate: "<%if (label){%><%=label%>: <%}%><%= value %>", // String - Template string for multiple tooltips multiTooltipTemplate: "<%= value %>", // Function - Will fire on animation progression. onAnimationProgress: function() {}, // Function - Will fire on animation completion. onAnimationComplete: function() {} } |
// fim do arquivo ChartConfig.js
Aqui começa o index.html:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 | <!DOCTYPE html> <html lang="en"> <head> <meta charset="utf-8" /> <title>Realtime plot</title> <script src="/socket.io/socket.io.js"></script> </head> <body> <canvas id="meusDados" width="900" height="400"></canvas> <script src="js/Chart.js"></script> <script src="js/ChartConfig.js"></script> <script language="javascript"> // conjunto de dados vazio var formatoDados = { labels: [], datasets: [{ fillColor: "rgba(172,194,132,0.4)", strokeColor: "#ACC26D", pointColor: "#fff", pointStrokeColor: "#9DB86D", data: [] }] }; var meusDados = document.getElementById("meusDados").getContext("2d"); var myChart = new Chart(meusDados).Line(formatoDados); </script> <script> var socket = io(); var cont = 1; var valores = []; socket.on('serialData', function(res) { if (cont > 81) { myChart.removeData(); //socket.disconnect(); //console.log("serialport closed"); } myChart.addData([res.dado], cont++); //console.log(res.dado); }); </script> </body> </html> |
Utilizo a variável cont para servir como eixo das abscissas, os dados provenientes da serial comporão o eixo das ordenadas. Inicialmente o conjunto de dados é vazio e ele é preenchido com os dados da serial. Por motivos de visualização, eu decidi remover o conjunto de dados quando o contador for maior do que 81, caso contrário, se o fluxo de dados for muito grande, você poderá perceber travamentos no seu browser.
Pronto, o seu aplicativo está pronto para receber dados da serial e plotar em tempo real.
Execute o seguinte comando para iniciar o servidor node em terminal:
node server.js
Você verá uma mensagem indicando que o servidor está "escutando" na porta 3000.
Para verificar o funcionamento, abra um navegador e digite o endereço http://localhost:3000
node server.js
Você verá uma mensagem indicando que o servidor está "escutando" na porta 3000.
Para verificar o funcionamento, abra um navegador e digite o endereço http://localhost:3000
Algumas melhorias ainda precisam ser feitas na utilização da biblioteca Chart.js. Se o fluxo de chegada de dados for grande, pode ser que o sistema trave. É preciso ter um maior controle disso e evitar que ocorre estouro de buffer pelo uso exagerado de memória.
Todo esse processo foi feito em videoaulas e você acompanhá-las pelos links abaixo:
Qualquer dúvida, pode comentar nos vídeos que eu responderei.
Um abraço,
Bruno Pinho
Todo esse processo foi feito em videoaulas e você acompanhá-las pelos links abaixo:
Parte 1: https://www.youtube.com/watch?v=LNtmLapUSzw
Parte 2: https://www.youtube.com/watch?v=ewMkFQ6surQ
Parte 3: https://www.youtube.com/watch?v=d_SoD0iE54Y
Um abraço,
Bruno Pinho