Arduino - Plotter Web

Neste tutorial, vamos aprender a construir um plotter web semelhante ao Serial Plotter da Arduino IDE. Com este sistema, você pode monitorar de forma prática dados em tempo real de um Arduino usando um navegador web no seu smartphone ou computador. Os dados serão exibidos em formato de gráfico, semelhante ao que você vê no Serial Plotter da Arduino IDE.

Arduino plotter web

Como o Plotter Web Funciona

Veja como o processo funciona:

  • O código do Arduino configura tanto um servidor web quanto um servidor WebSocket.
  • Quando um usuário acessa a página web hospedada na placa Arduino por meio de um navegador web, o servidor web envia o conteúdo da página web (HTML, CSS, JavaScript) de volta ao navegador.
  • O código JavaScript na página web cria um gráfico semelhante ao Serial Plotter.
  • Quando o usuário clica no botão connect na página web, o JavaScript inicia uma conexão WebSocket com o servidor WebSocket na placa Arduino.
  • O Arduino transmite dados para o navegador web por essa conexão WebSocket em um formato semelhante ao usado pelo Serial Plotter (detalhes na próxima seção).
  • O código JavaScript no navegador web recebe esses dados e os plota no gráfico.

O formato dos dados que o Arduino envia ao plotter web

Para plotar várias variáveis, precisamos separar as variáveis umas das outras pelo caractere “\t” ou " ". O último valor DEVE ser terminado pelos caracteres “\r\n”.

Em detalhes:

  • A primeira variável
webSocket->broadcastTXT(variable_1);
  • As variáveis do meio
webSocket->broadcastTXT(" "); // a tab '\t' or space ' ' character is printed between the two values. webSocket->broadcastTXT(variable_2); webSocket->broadcastTXT(" "); // a tab '\t' or space ' ' character is printed between the two values. webSocket->broadcastTXT(variable_3);
  • A última variável
webSocket->broadcastTXT(" "); // a tab '\t' or space ' ' character is printed between the two values. webSocket->broadcastTXT(variable_4); webSocket->broadcastTXT("\r\n"); // the last value is terminated by a carriage return and a newline characters.

Para mais detalhes, consulte o tutorial Arduino - Serial Plotter

Código Arduino - Plotter Web

O conteúdo da página web (HTML, CSS, JavaScript) fica armazenado separadamente em um arquivo index.h. Assim, teremos dois arquivos de código na Arduino IDE:

  • Um arquivo .ino, que é o código do Arduino, que cria um servidor web e um servidor WebSocket
  • Um arquivo .h, que contém o conteúdo da página web.

Passos Rápidos

  • Se esta é a primeira vez que você usa o Arduino Uno R4, veja como configurar o ambiente para o Arduino Uno R4 na Arduino IDE.
  • Conecte a placa Arduino ao seu PC por meio de um cabo USB Type-C
  • Abra a Arduino IDE no seu PC.
  • Selecione a placa Arduino correta (Arduino Uno R4 WiFi) e a porta COM.
  • Abra o Gerenciador de Bibliotecas clicando no ícone Library Manager na barra de navegação à esquerda da Arduino IDE.
  • Pesquise por Web Server for Arduino Uno R4 WiFi e localize a biblioteca Web Server criada pela DIYables.
  • Clique no botão Install para instalar a biblioteca Web Server.
Arduino UNO R4 biblioteca Web Server
  • Na Arduino IDE, crie um novo sketch e dê um nome a ele, por exemplo, ArduinoGetStarted.com.ino
  • Copie o código abaixo e abra-o com a Arduino IDE
/* * Este código de Arduino foi desenvolvido por pt.newbiely.com * Este código de Arduino é disponibilizado ao público sem nenhuma restrição. * Para tutoriais completos e diagramas de ligação, visite: * https://pt.newbiely.com/tutorials/arduino/arduino-web-plotter */ #include <UnoR4WiFi_WebServer.h> #include "index.h" // WiFi credentials const char WIFI_SSID[] = "YOUR_WIFI_SSID"; const char WIFI_PASSWORD[] = "YOUR_WIFI_PASSWORD"; // Create web server instance UnoR4WiFi_WebServer server(80); UnoR4WiFi_WebSocket* webSocket; int last_update = 0; // Page handlers void handleHome(WiFiClient& client, const String& method, const String& request, const QueryParams& params, const String& jsonData) { server.sendResponse(client, HTML_CONTENT); } // WebSocket event handlers void onWebSocketOpen(net::WebSocket& ws) { Serial.println("New WebSocket connection"); } void onWebSocketMessage(net::WebSocket& ws, const net::WebSocket::DataType dataType, const char* message, uint16_t length) { // no message from plotter } void onWebSocketClose(net::WebSocket& ws, const net::WebSocket::CloseCode code, const char* reason, uint16_t length) { Serial.println("WebSocket client disconnected"); } void setup() { Serial.begin(9600); delay(1000); // TO DO Serial.println("Arduino Uno R4 WiFi - WebSocket Server"); // Configure web server routes server.addRoute("/", handleHome); // Start web server with WiFi connection server.begin(WIFI_SSID, WIFI_PASSWORD); // Enable WebSocket functionality webSocket = server.enableWebSocket(81); if (webSocket != nullptr) { // Set up WebSocket event handlers webSocket->onOpen(onWebSocketOpen); webSocket->onMessage(onWebSocketMessage); webSocket->onClose(onWebSocketClose); } else { Serial.println("Failed to start WebSocket server"); } } void loop() { // Handle HTTP requests and WebSocket connections using the library server.handleClient(); server.handleWebSocket(); if (millis() - last_update > 500) { last_update = millis(); String variable_1 = String(random(0, 100)); String variable_2 = String(random(0, 100)); String variable_3 = String(random(0, 100)); String variable_4 = String(random(0, 100)); // TO SERIAL PLOTTER Serial.print(variable_1); Serial.print(" "); // a tab '\t' or space ' ' character is printed between the two values. Serial.print(variable_2); Serial.print(" "); // a tab '\t' or space ' ' character is printed between the two values. Serial.print(variable_3); Serial.print(" "); // a tab '\t' or space ' ' character is printed between the two values. Serial.println(variable_4); // the last value is terminated by a carriage return and a newline characters. // TO WEB PLOTTER webSocket->broadcastTXT(variable_1); webSocket->broadcastTXT(" "); // a tab '\t' or space ' ' character is printed between the two values. webSocket->broadcastTXT(variable_2); webSocket->broadcastTXT(" "); // a tab '\t' or space ' ' character is printed between the two values. webSocket->broadcastTXT(variable_3); webSocket->broadcastTXT(" "); // a tab '\t' or space ' ' character is printed between the two values. webSocket->broadcastTXT(variable_4); webSocket->broadcastTXT("\r\n"); // the last value is terminated by a carriage return and a newline characters. } delay(10); }
  • Modifique as informações de WiFi (SSID e senha) no código para corresponder às credenciais da sua própria rede.
  • Crie o arquivo index.h na Arduino IDE da seguinte forma:
    • Clique no botão logo abaixo do ícone do serial monitor e escolha New Tab, ou use as teclas Ctrl+Shift+N.
    Arduino IDE 2 adiciona arquivo
    • Dê ao arquivo o nome index.h e clique no botão OK
    Arduino IDE 2 adiciona arquivo index.h
    • Copie o código abaixo e cole-o no index.h.
    /* * Este código de Arduino foi desenvolvido por pt.newbiely.com * Este código de Arduino é disponibilizado ao público sem nenhuma restrição. * Para tutoriais completos e diagramas de ligação, visite: * https://pt.newbiely.com/tutorials/arduino/arduino-web-plotter */ const char *HTML_CONTENT = R"=====( <!DOCTYPE html> <html> <head> <title>Arduino - Web Plotter</title> <meta name="viewport" content="width=device-width, initial-scale=0.7"> <style> body {text-align: center; height: 750px; } h1 {font-weight: bold; font-size: 20pt; padding-bottom: 5px; color: navy; } h2 {font-weight: bold; font-size: 15pt; padding-bottom: 5px; } button {font-weight: bold; font-size: 15pt; } #footer {width: 100%; margin: 0px; padding: 0px 0px 10px 0px; bottom: 0px; } .sub-footer {margin: 0 auto; position: relative; width:400px; } .sub-footer a {position: absolute; font-size: 10pt; top: 3px; } </style> <script> var COLOR_BACKGROUND = "#FFFFFF"; var COLOR_TEXT = "#000000"; var COLOR_BOUND = "#000000"; var COLOR_GRIDLINE = "#F0F0F0"; //var COLOR_LINE = ["#33FFFF", "#FF00FF", "#FF0000", "#FF8C00", "#00FF00"]; //var COLOR_LINE = ["#0000FF", "#FF0000", "#00FF00", "#FF8C00", "#00FF00"]; //var COLOR_LINE = ["#33FFFF", "#FF0000", "#00FF00", "#FF8C00", "#00FF00"]; var COLOR_LINE = ["#0000FF", "#FF0000", "#009900", "#FF9900", "#CC00CC", "#666666", "#00CCFF", "#000000"]; var LEGEND_WIDTH = 10; var X_AXIS_TITLE_HEIGHT = 40; var Y_AXIS_TITLE_WIDTH = 40; var X_AXIS_VALUE_HEIGHT = 40; var Y_AXIS_VALUE_WIDTH = 50; var PLOT_AREA_PADDING_TOP = 30; var PLOT_AREA_PADDING_RIGHT = 30; var X_GRIDLINE_NUM = 5; var Y_GRIDLINE_NUM = 4; var WSP_SIZE_TYPE = 1; /* 0: Fixed size, 1: full screen */ var WSP_WIDTH = 400; var WSP_HEIGHT = 200; var MAX_SAMPLE = 50; // in sample var X_AXIS_MIN = 0; var X_AXIS_MAX = MAX_SAMPLE; var Y_AXIS_AUTO_SCALE = 1; /* 0: y axis fixed range, 1: y axis auto scale */ var Y_AXIS_MIN = -5; var Y_AXIS_MAX = 5; var X_AXIS_TITLE = "X"; var Y_AXIS_TITLE = "Y"; var plot_area_width; var plot_area_height; var plot_area_pivot_x; var plot_area_pivot_y; var sample_count = 0; var buffer = ""; var data = []; var ws; var canvas; var ctx; function init() { canvas = document.getElementById("graph"); canvas.style.backgroundColor = COLOR_BACKGROUND; ctx = canvas.getContext("2d"); canvas_resize(); setInterval(update_view, 1000 / 60); } function connect_onclick() { if(ws == null) { ws = new WebSocket("ws://" + window.location.host + ":81"); document.getElementById("ws_state").innerHTML = "CONNECTING"; ws.onopen = ws_onopen; ws.onclose = ws_onclose; ws.onmessage = ws_onmessage; ws.binaryType = "arraybuffer"; } else ws.close(); } function ws_onopen() { document.getElementById("ws_state").innerHTML = "<span style='color: blue'>CONNECTED</span>"; document.getElementById("bt_connect").innerHTML = "Disconnect"; } function ws_onclose() { document.getElementById("ws_state").innerHTML = "<span style='color: gray'>CLOSED</span>"; document.getElementById("bt_connect").innerHTML = "Connect"; ws.onopen = null; ws.onclose = null; ws.onmessage = null; ws = null; } function ws_onmessage(e_msg) { e_msg = e_msg || window.event; // MessageEvent console.log(e_msg.data); buffer += e_msg.data; buffer = buffer.replace(/\r\n/g, "\n"); buffer = buffer.replace(/\r/g, "\n"); while(buffer.indexOf("\n") == 0) buffer = buffer.substr(1); if(buffer.indexOf("\n") <= 0) return; var pos = buffer.lastIndexOf("\n"); var str = buffer.substr(0, pos); var new_sample_arr = str.split("\n"); buffer = buffer.substr(pos + 1); for(var si = 0; si < new_sample_arr.length; si++) { var str = new_sample_arr[si]; var arr = []; if(str.indexOf("\t") > 0) arr = str.split("\t"); else arr = str.split(" "); for(var i = 0; i < arr.length; i++) { var value = parseFloat(arr[i]); if(isNaN(value)) continue; if(i >= data.length) { var new_line = [value]; data.push(new_line); // new line } else data[i].push(value); } sample_count++; } for(var line = 0; line < data.length; line++) { while(data[line].length > MAX_SAMPLE) data[line].splice(0, 1); } if(Y_AXIS_AUTO_SCALE) auto_scale(); } function map(x, in_min, in_max, out_min, out_max) { return (x - in_min) * (out_max - out_min) / (in_max - in_min) + out_min; } function get_random_color() { var letters = '0123456789ABCDEF'; var _color = '#'; for (var i = 0; i < 6; i++) _color += letters[Math.floor(Math.random() * 16)]; return _color; } function update_view() { if(sample_count <= MAX_SAMPLE) X_AXIS_MAX = sample_count; else X_AXIS_MAX = 50; ctx.clearRect(0, 0, WSP_WIDTH, WSP_HEIGHT); ctx.save(); ctx.translate(plot_area_pivot_x, plot_area_pivot_y); ctx.font = "bold 20px Arial"; ctx.textBaseline = "middle"; ctx.textAlign = "center"; ctx.fillStyle = COLOR_TEXT; // draw X axis title if(X_AXIS_TITLE != "") { ctx.fillText(X_AXIS_TITLE, plot_area_width / 2, X_AXIS_VALUE_HEIGHT + X_AXIS_TITLE_HEIGHT / 2); } // draw Y axis title if(Y_AXIS_TITLE != "") { ctx.rotate(-90 * Math.PI / 180); ctx.fillText(Y_AXIS_TITLE, plot_area_height / 2, -Y_AXIS_VALUE_WIDTH - Y_AXIS_TITLE_WIDTH / 2); ctx.rotate(90 * Math.PI / 180); } ctx.font = "16px Arial"; ctx.textAlign = "right"; ctx.strokeStyle = COLOR_BOUND; for(var i = 0; i <= Y_GRIDLINE_NUM; i++) { var y_gridline_px = -map(i, 0, Y_GRIDLINE_NUM, 0, plot_area_height); y_gridline_px = Math.round(y_gridline_px) + 0.5; ctx.beginPath(); ctx.moveTo(0, y_gridline_px); ctx.lineTo(plot_area_width, y_gridline_px); ctx.stroke(); ctx.strokeStyle = COLOR_BOUND; ctx.beginPath(); ctx.moveTo(-7 , y_gridline_px); ctx.lineTo(4, y_gridline_px); ctx.stroke(); var y_gridline_value = map(i, 0, Y_GRIDLINE_NUM, Y_AXIS_MIN, Y_AXIS_MAX); y_gridline_value = y_gridline_value.toFixed(1); ctx.fillText(y_gridline_value + "", -15, y_gridline_px); ctx.strokeStyle = COLOR_GRIDLINE; } ctx.strokeStyle = COLOR_BOUND; ctx.textAlign = "center"; ctx.beginPath(); ctx.moveTo(0.5, y_gridline_px - 7); ctx.lineTo(0.5, y_gridline_px + 4); ctx.stroke(); for(var i = 0; i <= X_GRIDLINE_NUM; i++) { var x_gridline_px = map(i, 0, X_GRIDLINE_NUM, 0, plot_area_width); x_gridline_px = Math.round(x_gridline_px) + 0.5; ctx.beginPath(); ctx.moveTo(x_gridline_px, 0); ctx.lineTo(x_gridline_px, -plot_area_height); ctx.stroke(); ctx.strokeStyle = COLOR_BOUND; ctx.beginPath(); ctx.moveTo(x_gridline_px, 7); ctx.lineTo(x_gridline_px, -4); ctx.stroke(); var x_gridline_value; if(sample_count <= MAX_SAMPLE) x_gridline_value = map(i, 0, X_GRIDLINE_NUM, X_AXIS_MIN, X_AXIS_MAX); else x_gridline_value = map(i, 0, X_GRIDLINE_NUM, sample_count - MAX_SAMPLE, sample_count);; ctx.fillText(x_gridline_value.toString(), x_gridline_px, X_AXIS_VALUE_HEIGHT / 2 + 5); ctx.strokeStyle = COLOR_GRIDLINE; } //ctx.lineWidth = 2; var line_num = data.length; for(var line = 0; line < line_num; line++) { // draw graph var sample_num = data[line].length; if(sample_num >= 2) { var y_value = data[line][0]; var x_px = 0; var y_px = -map(y_value, Y_AXIS_MIN, Y_AXIS_MAX, 0, plot_area_height); if(line == COLOR_LINE.length) COLOR_LINE.push(get_random_color()); ctx.strokeStyle = COLOR_LINE[line]; ctx.beginPath(); ctx.moveTo(x_px, y_px); for(var i = 0; i < sample_num; i++) { y_value = data[line][i]; x_px = map(i, X_AXIS_MIN, X_AXIS_MAX -1, 0, plot_area_width); y_px = -map(y_value, Y_AXIS_MIN, Y_AXIS_MAX, 0, plot_area_height); ctx.lineTo(x_px, y_px); } ctx.stroke(); } // draw legend var x = plot_area_width - (line_num - line) * LEGEND_WIDTH - (line_num - line - 1) * LEGEND_WIDTH / 2; var y = -plot_area_height - PLOT_AREA_PADDING_TOP / 2 - LEGEND_WIDTH / 2; ctx.fillStyle = COLOR_LINE[line]; ctx.beginPath(); ctx.rect(x, y, LEGEND_WIDTH, LEGEND_WIDTH); ctx.fill(); } ctx.restore(); } function canvas_resize() { canvas.width = 0; // to avoid wrong screen size canvas.height = 0; if(WSP_SIZE_TYPE) { // full screen document.getElementById('footer').style.position = "fixed"; var width = window.innerWidth - 20; var height = window.innerHeight - 20; WSP_WIDTH = width; WSP_HEIGHT = height - document.getElementById('header').offsetHeight - document.getElementById('footer').offsetHeight; } canvas.width = WSP_WIDTH; canvas.height = WSP_HEIGHT; ctx.font = "16px Arial"; var y_min_text_size = ctx.measureText(Y_AXIS_MIN.toFixed(1) + "").width; var y_max_text_size = ctx.measureText(Y_AXIS_MAX.toFixed(1) + "").width; Y_AXIS_VALUE_WIDTH = Math.round(Math.max(y_min_text_size, y_max_text_size)) + 15; plot_area_width = WSP_WIDTH - Y_AXIS_VALUE_WIDTH - PLOT_AREA_PADDING_RIGHT; plot_area_height = WSP_HEIGHT - X_AXIS_VALUE_HEIGHT - PLOT_AREA_PADDING_TOP; plot_area_pivot_x = Y_AXIS_VALUE_WIDTH; plot_area_pivot_y = WSP_HEIGHT - X_AXIS_VALUE_HEIGHT; if(X_AXIS_TITLE != "") { plot_area_height -= X_AXIS_TITLE_HEIGHT; plot_area_pivot_y -= X_AXIS_TITLE_HEIGHT; } if(Y_AXIS_TITLE != "") { plot_area_width -= Y_AXIS_TITLE_WIDTH; plot_area_pivot_x += Y_AXIS_TITLE_WIDTH; } ctx.lineWidth = 1; } function auto_scale() { if(data.length >= 1) { var max_arr = []; var min_arr = []; for(var i = 0; i < data.length; i++) { if(data[i].length >= 1) { var max = Math.max.apply(null, data[i]); var min = Math.min.apply(null, data[i]); max_arr.push(max); min_arr.push(min); } } var max = Math.max.apply(null, max_arr); var min = Math.min.apply(null, min_arr); var MIN_DELTA = 10.0; if((max - min) < MIN_DELTA) { var mid = (max + min) / 2; max = mid + MIN_DELTA / 2; min = mid - MIN_DELTA / 2; } var range = max - min; var exp; if (range == 0.0) exp = 0; else exp = Math.floor(Math.log10(range / 4)); var scale = Math.pow(10, exp); var raw_step = (range / 4) / scale; var step; potential_steps =[1.0, 1.5, 2.0, 2.5, 3.0, 4.0, 5.0, 6.0, 8.0, 10.0]; for (var i = 0; i < potential_steps.length; i++) { if (potential_steps[i] < raw_step) continue; step = potential_steps[i] * scale; Y_AXIS_MIN = step * Math.floor(min / step); Y_AXIS_MAX = Y_AXIS_MIN + step * (4); if (Y_AXIS_MAX >= max) break; } var count = 5 - Math.floor((Y_AXIS_MAX - max) / step); Y_AXIS_MAX = Y_AXIS_MIN + step * (count - 1); ctx.font = "16px Arial"; var y_min_text_size = ctx.measureText(Y_AXIS_MIN.toFixed(1) + "").width; var y_max_text_size = ctx.measureText(Y_AXIS_MAX.toFixed(1) + "").width; Y_AXIS_VALUE_WIDTH = Math.round(Math.max(y_min_text_size, y_max_text_size)) + 15; plot_area_width = WSP_WIDTH - Y_AXIS_VALUE_WIDTH - PLOT_AREA_PADDING_RIGHT; plot_area_pivot_x = Y_AXIS_VALUE_WIDTH; } } window.onload = init; </script> </head> <body onresize="canvas_resize()"> <h1 id="header">Arduino - Web Plotter</h1> <canvas id="graph"></canvas> <br> <div id="footer"> <div class="sub-footer"> <h2>WebSocket <span id="ws_state"><span style="color: gray">CLOSED</span></span></h2> </div> <button id="bt_connect" type="button" onclick="connect_onclick();">Connect</button> </div> </body> </html> )=====";
    • Agora você tem o código em dois arquivos: ArduinoGetStarted.com.ino e index.h
    • Clique no botão Upload na Arduino IDE para carregar o código no Arduino.
    • Abra o Serial Monitor
    • Confira o resultado no Serial Monitor.
    ∞
    Newbiely | Arduino IDE 2.3.8
    ──
    ☐
    ✕
    File
    Edit
    Sketch
    Tools
    Help
    Arduino Uno
    Newbiely.ino
    ···
    8 Serial.println("Hello World!");
    Output
    Serial Monitor
    Message (Enter to send message to 'Arduino Uno' on 'COM15')
    New Line
    9600 baud
    Arduino Uno R4 WiFi - WebSocket Server Connected! IP Address: 192.168.0.254 SSID: YOUR_WIFI_SSID IP Address: 192.168.0.254 Signal strength (RSSI): -44 dBm WebSocket server started on port 81 WebSocket URL: ws://192.168.0.254:81 WebSocket server enabled successfully
    Ln 11, Col 1
    Arduino Uno on COM15
    2
    • Anote o endereço IP exibido e digite esse endereço na barra de endereços de um navegador web no seu smartphone ou PC.
    • Você verá a página web como abaixo:
    Arduino plotter no navegador web
    • Clique no botão CONNECT para conectar a página web ao Arduino via WebSocket.
    • Você verá o plotter plotando os dados como na imagem abaixo.
    Arduino gráfico web
    • Você pode abrir o Serial Plotter na Arduino IDE para comparar com o Plotter Web no navegador web.

    ※ Nota:

    • Se você modificar o conteúdo HTML no index.h e não mexer em nada no arquivo ArduinoGetStarted.com.ino, quando você compilar e carregar o código no Arduino, a Arduino IDE não vai atualizar o conteúdo HTML.
    • Para fazer a Arduino IDE atualizar o conteúdo HTML nesse caso, faça uma alteração no arquivo ArduinoGetStarted.com.ino (por exemplo, adicionar uma linha vazia, adicionar um comentário....)

    Explicação do Código Linha por Linha

    O código Arduino acima contém explicações linha por linha. Leia os comentários no código!

※ NOSSAS MENSAGENS

  • Fique à vontade para compartilhar o link deste tutorial. Porém, por favor, não use nosso conteúdo em outros sites. Investimos muito esforço e tempo para criar este conteúdo, por favor respeite nosso trabalho!