INTRODUCCIÓN
En la práctica anterior se hacía una introducción a la comunicación de datos mediante la tecnología WebSocket. Se explicaba que es un método que permite establecer y mantener abierta una conexión TCP, por lo que se pueden enviar y recibir datos constantemente entre el servidor y cliente con muy baja latencia, a costa de una pequeña sobrecarga en el protocolo.
En esa práctica se utilizó la tecnología WebSocket para regular la intensidad de un LED desde nuestro navegador WEB (mediante una barra de desplazamiento), conectado en modo Access Point con el servidor (ESP8266). Por lo tanto era, el cliente quien, conectado con el servidor, controlaba el funcionamiento del LED.
En esta práctica se va a hacer justo lo contrario, que uno o varios clientes visualicen los datos que genera un dispositivo controlado desde el lado del servidor. En concreto, que se pueda visualizar a través de un navegador WEB, la electricidad que deja pasar un potenciómetro (que no es mas que una resistencia variable -en nuestro caso entre 0 y 10.000 Ω-), hacia el pin analógico de lectura (A0) de la placa NodeMCU.
La página WEB de visualización se va a implementar en el código del sketch de Arduino y alojar en la memoria flash (PROGMEM) del ESP8266, en lugar de la memoria SRAM, lo que facilita en gran medida su escritura, compresión y uso.
MATERIAL NECESARIO
CONEXIONADO
Es interesante saber que el ESP8266 tiene una sola entrada analógica de 10 bits (toma valores entre 0 y 210=1023), pero trabaja con un voltaje de referencia interno de 1V.
La placa NodeMCU (al igual que la mayoría de las placas que llevan montado el chip ESP8266) tiene un divisor de voltaje en su configuración, para adaptar el rango de lectura hasta los 3.3V, por tanto puede leer voltajes por la entrada analógica (A0) entre 0 y 3,3V, dando como resultado la lectura de valores entre 0 y 1023. 0V dará una lectura de 0 y 3,3V dará una lectura de 1023.
En nuestro caso, utilizando la salida de 3,3V de la placa y el potenciómetro, que es una resistencia variable, podremos regular el voltaje que va a leer la entrada analógica (A0) entre los 0 y 3,3V para los que está preparada.
CODIGO WEB (HTML Y JAVASCRIPT)
El código de la página WEB para generar un «CANVAS» en el que representar la lectura del voltaje que deja pasar el POTENCIÓMETRO sería el siguiente:
<!DOCTYPE html> <!--Declaración del tipo de documento: HTML5--> <html> <!--Inicio del documento HTML--> <head> <!--Inicio de la cabecera -inf.sobre el doc.--> <meta charset=utf-8> <!--Config. de carácteres utilizada: UTF-8--> <meta name='viewport' content='initial-scale=1, maximum-scale=1'> <!--Control de la composición del documento (hace que se muestre similar en distintos dispositivos y navegadores)--> <title>WebSocket ESP8266 - POTENCIOMETRO</title> <!--Título del documento --> </head> <!--Fin de la cabecera --> <body> <!--Inicio el cuerpo -contenido visible del doc.--> <h1>LECTURA DE UN POTENCIÓMETRO</h1> <!--Texto de enabezado -título principal (h1)--> <p>Comunicación vía WebSocket: Servidor (ESP8266) <---> Cliente</p> <!--Párrafo--> <canvas id="myCanvas" width="350" height="350" style="border:1px solid #d3d3d3;"> <!--Área de dibujo (canvas) de 350x350px. con--> Tu navegador no soporta el elemento CANVAS de HTML5.</canvas> <!--borde de 1px gris. Nombre de ID: 'myCanvas' --> <!--Si es incompat. con nav.se muestra msg.error--> <script> //Inicio del JavaScript -programación del docum.- var connection = new WebSocket('ws://'+location.hostname+':81/', ['arduino']); /*Crea un WebSocket conectándose con el servidor ws://[IP del servidor]:81/ con el protocolo de 'arduino'*/ connection.onopen = function () { //Al abrir la conexión... connection.send('Conectado - ' + new Date()); //...envía 'Conectado' + fecha y hora al servidor console.log('Conectado - ' + new Date()); //...envía 'Conectado' + fecha y hora a la consola } connection.onmessage = function (event) { //Al recibir un msg...(voltaje leído por ESP8266) console.log('Servidor (recibe): ', event.data); //...envía 'Serv. (recibe):'+ mensaje a la consola verValor(); //...ejecuta la función verValor() } connection.onerror = function (error) { //Si hay un error en la conexión... console.log('WebSocket Error!!!', error); //...envía 'WS Error!!!'+tipo de error a la consola } function verValor() { //Declara la función verValor() Cuando se ejecuta: var valor = event.data; //Asigna a la variable local valor el msg.recibido var c = document.getElementById('myCanvas'); //Crea el nodo DOM para el elemento c (canvas) var ctx = c.getContext('2d'); //Establece el contexto de representación (2D) ctx.clearRect(0, 0, myCanvas.width, myCanvas.height); //Limpia el canvas (para evitar superposiciones) ctx.beginPath(); //Comienza un nuevo trazado (una parte del dibujo) ctx.lineWidth = 40; //Anchura de los trazos: 40 pixels ctx.strokeStyle = '#EEEEEE'; //Color de los trazos: gris claro ctx.arc(175,175,100,0.75*Math.PI,0.25*Math.PI); /*Trazo: arco con centro (175,175), radio 100px y ángulo de giro entre 0.75π y 0.25π rad*/ ctx.stroke(); //Representa en pantalla el trazado ctx.beginPath(); //Comienza un nuevo trazado ctx.strokeStyle = '#87CEEB'; //Color de los trazos: azul cielo (sky blue) ctx.arc(175,175,100,(0.25-(1.5/3.3)*valor)*Math.PI,0.25*Math.PI); /*Trazo: arco con centro (175,175), radio 100px y ángulo de giro proporcional al voltaje medido 3.3V serán 1.5π rad.El arco termina en 0.25π rad*/ ctx.stroke(); //Representa en pantalla el trazado ctx.font = 'bold 40px Arial'; //Fuente de texto: arial, 40 px negrilla ctx.fillStyle = '#87CEEB'; //Color de texto: azul cielo ctx.textAlign = 'center'; //Posición de texto: centrado ctx.fillText(valor+"V",175, 185); //Texto a representar: mensaje+V. Centro (175,185) } </script> <!--Fin del JavaScrip--> </body> <!--Fin del cuerpo--> </html> <!--Fin del documento HTML-->
La variable para establecer la conexión WebSocket (la tenemos en la línea 14). También se han creado las funciones para controlar su estado y el envío y recepción de datos. Las analizamos a continuación:
El resultado del anterior código en un navegador se visualizará así:
Abriendo la consola del navegador, por ejemplo en Google Chrome (en Windows sería: botón derecho del ratón / inspeccionar / console), en una comunicación correcta entre servidor y cliente, veremos algo así:
SKETCH
El código definitivo queda recogido en el siguiente sketch de Arduino:
/* Ejemplo de comunicación WebSocket Servidor <---> Cliente. Escrito por Dani No www.esploradores.com Este sofware está escrito bajo la licencia CREATIVE COMMONS con Reconocimiento-CompartirIgual(CC BY-SA) https://creativecommons.org/ -Redistributions of source code must retain the above creative commons and this list of conditions and the following disclaimer. -Redistributions in binary form must reproduce the above creative commons notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHTHOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #include <ESP8266WiFi.h> // Incluye una librería externa para gestionar la conexión WiFi #include <WebSocketsServer.h> // Incluye una librería externa para gestionar la conexión WebSocket #include <ESP8266WebServer.h> // Incluye una librería externa para facilitar la gestión del servidor #define analog_ip A0 //-analog_ip- Nombre del pin analógico (A0) de entrada de datos -analog input- static unsigned long last; //-last- Variable para almacenar el tiempo (ms) que lleva el procesador encendido int inputVal; //-inputVal- Variable para almacenar el último valor leído en la entrada analógica float voltVal; //-voltVal- Variable para almacenar el voltaje (calculado a partir de inputVal) float voltValPrev = 0 ; //-VoltValPrev- Variable para almacenar el voltaje tomado en la lectura previa String myString; //-myString- Variable para almacenar el voltaje convertido a cadena de texto const char* ssid = "POTENCIOMETRO"; //Nombre de la red -SSID- (Access Point) que vamos a crear const char* password = "12345678"; //Clave de la red -PASSWORD- static const char INDEX_HTML[] PROGMEM = R"( <!--Documento HTML para almacenar en la memoria flash (En la variable INDEX_HTML)--> <!DOCTYPE html> <!--Declaración del tipo de documento: HTML5--> <html> <!--Inicio del documento HTML--> <head> <!--Inicio de la cabecera -inf.sobre el doc.--> <meta charset=utf-8> <!--Config. de carácteres utilizada: UTF-8--> <meta name='viewport' content='initial-scale=1, maximum-scale=1'> <!--Control de la composición del documento (hace que se muestre similar en distintos dispositivos y navegadores)--> <title>WebSocket ESP8266 - POTENCIOMETRO</title> <!--Título del documento --> </head> <!--Fin de la cabecera --> <body> <!--Inicio el cuerpo -contenido visible del doc.--> <h1>LECTURA DE UN POTENCIÓMETRO</h1> <!--Texto de enabezado -título principal (h1)--> <p>Comunicación vía WebSocket: Servidor (ESP8266) <---> Cliente</p> <!--Párrafo--> <canvas id="myCanvas" width="350" height="350" style="border:1px solid #d3d3d3;"> <!--Área de dibujo (canvas) de 350x350px. con--> Tu navegador no soporta el elemento CANVAS de HTML5.</canvas> <!--borde de 1px gris. Nombre de ID: 'myCanvas' --> <!--Si es incompat. con nav.se muestra msg.error--> <script> //Inicio del JavaScript -programación del docum.- var connection = new WebSocket('ws://'+location.hostname+':81/', ['arduino']); /*Crea un WebSocket conectándose con el servidor ws://[IP del servidor]:81/ con el protocolo de 'arduino'*/ connection.onopen = function () { //Al abrir la conexión... connection.send('Conectado - ' + new Date()); //...envía 'Conectado' + fecha y hora al servidor console.log('Conectado - ' + new Date()); //...envía 'Conectado' + fecha y hora a la consola } connection.onmessage = function (event) { //Al recibir un msg...(voltaje leído por ESP8266) console.log('Servidor (recibe): ', event.data); //...envía 'Serv. (recibe):'+ mensaje a la consola verValor(); //...ejecuta la función verValor() } connection.onerror = function (error) { //Si hay un error en la conexión... console.log('WebSocket Error!!!', error); //...envía 'WS Error!!!'+tipo de error a la consola } function verValor() { //Declara la función verValor() Cuando se ejecuta: var valor = event.data; //Asigna a la variable local valor el msg.recibido var c = document.getElementById('myCanvas'); //Crea el nodo DOM para el elemento c (canvas) var ctx = c.getContext('2d'); //Establece el contexto de representación (2D) ctx.clearRect(0, 0, myCanvas.width, myCanvas.height); //Limpia el canvas (para evitar superposiciones) ctx.beginPath(); //Comienza un nuevo trazado (una parte del dibujo) ctx.lineWidth = 40; //Anchura de los trazos: 40 pixels ctx.strokeStyle = '#EEEEEE'; //Color de los trazos: gris claro ctx.arc(175,175,100,0.75*Math.PI,0.25*Math.PI); /*Trazo: arco con centro (175,175), radio 100px y ángulo de giro entre 0.75π y 0.25π rad*/ ctx.stroke(); //Representa en pantalla el trazado ctx.beginPath(); //Comienza un nuevo trazado ctx.strokeStyle = '#87CEEB'; //Color de los trazos: azul cielo (sky blue) ctx.arc(175,175,100,(0.25-(1.5/3.3)*valor)*Math.PI,0.25*Math.PI); /*Trazo: arco con centro (175,175), radio 100px y ángulo de giro proporcional al voltaje medido 3.3V serán 1.5π rad.El arco termina en 0.25π rad*/ ctx.stroke(); //Representa en pantalla el trazado ctx.font = 'bold 40px Arial'; //Fuente de texto: arial, 40 px negrilla ctx.fillStyle = '#87CEEB'; //Color de texto: azul cielo ctx.textAlign = 'center'; //Posición de texto: centrado ctx.fillText(valor+"V",175, 185); //Texto a representar: mensaje+V. Centro (175,185) } </script> <!--Fin del JavaScrip--> </body> <!--Fin del cuerpo--> </html> <!--Fin del documento HTML--> )"; ESP8266WebServer server (80); //Puerto de conex. del Servidor (Access Point) nº80 WebSocketsServer webSocket = WebSocketsServer(81); //Puerto de conex. del WebSocket nº81 void webSocketEvent(uint8_t num, WStype_t type, uint8_t * payload, size_t length) { /*Declara la función webSocketEvent para gestionar los 'eventos' relativos a la conexión WebSocket*/ switch(type) { //SI EL 'EVENTO' ES... case WStype_CONNECTED: { //...UNA NUEVA CONEXIÓN WEBSOCKET CON UN CLIENTE: IPAddress ip = webSocket.remoteIP(num); /*Asigna a la variable ip la dirección IP de la conexión y el nº de conexión del WS*/ Serial.printf("[%u] Conectado a través de la URL: %d.%d.%d.%d - %s\n", num, ip[0], ip[1], ip[2], ip[3], payload); //Envía al Mon. Serie el nº de conex. y direcc. IP myString = String(voltVal); //Convierte el voltaje medido en una cadena de txt. webSocket.sendTXT(num,myString); //Envía la cadena de texto al nuevo cliente } break; //Finaliza el evento case WStype_DISCONNECTED: //...EL CESE DE UNA CONEXIÓN WEBSOCKET: Serial.printf("[%u] Desconectado!\n", num); //Envía al Mon.Serie el msg. con nº WS desconectado break; //Finaliza el evento case WStype_TEXT: //...UN MENSAJE DE TEXTO RECIBIDO POR LA CONEX. WS: Serial.printf("Número de conexión: %u - Carácteres recibidos: %s\n ", num, payload); //Envía al Mon. Serie el msg. con nº WS de origen break; //Finaliza el evento case WStype_ERROR: //...UN ERROR EN LA CONEXIÓN WEBSOCKET Serial.printf("Se ha recibido un error. \n"); //Envía al Monitor Serie el mensaje de error break; //Finaliza el evento } } void setup() { //Declara la func. SETUP -Configuración inicial del sketch- Serial.begin(115200); //Velocidad del Puerto Serie en baudios (115200) Serial.println(); //Salto de línea en el Puerto Serie WiFi.softAP(ssid, password); //Inicializa la conexión WiFi -Access Point- (AP) IPAddress myIP = WiFi.softAPIP(); //Asigna a la variable myIP la dirección IP del AP Serial.print("IP del access point: "); //Envía al Monitor Serie el texto Serial.println(myIP); //Envía al Monitor Serie el la dirección IP del AP webSocket.begin(); //Inicializa la conexión WebSocket webSocket.onEvent(webSocketEvent); /*Cuando se recibe un 'evento' relativo al WS se ejecuta la función webSocketEvent para su gestión*/ server.on("/", []() { //Si el servidor recibe la dirección IP del AP (myIP) desde un cliente... server.send_P(200, "text/html", INDEX_HTML); //...envía la página WEB almacenada en la memoria flash al cliente }); server.begin(); //Inicializa el servidor Serial.println("WebServer iniciado..."); //Envía al Monitor Serie el texto } void loop() { //Declara la func. LOOP -Funciones del sketch que se repiten indefinidamente- webSocket.loop(); //El servidor 'escucha' los 'eventos' de la conexión WebSocket server.handleClient(); /*El servidor 'escucha' las peticiones entrantes de los clientes. Conforme a lo programado solo puede responder a la petición de la página WEB almacenada*/ if (abs(millis()-last) > 100) { //Cada 100 ms (0,1s)... inputVal = analogRead (analog_ip); //...se realiza la lectura del pin analógico. Da valores entre 0 y 1023 (10 bits) voltVal = 0.01*(round(inputVal*3.30/1023*100)); /*...se mapea el resultado entre 0 y 3.30 para obtener el voltaje que deja pasar el potenciómetro y se redondea a dos decimales*/ last = millis(); //...se toma el tiempo de referencia para volver a ejecutar esta func. condicional } if (voltVal != voltValPrev) { //Si el voltaje ha cambiado respecto a la lectura previa... Serial.println (voltVal); //...envía al Monitor Serie el nuevo voltaje myString = String(voltVal); //...convierte el voltaje de nº(float) a cadena de texto (String) webSocket.broadcastTXT(myString); //...envía el voltaje como txt. a todos los dispositivos conectados vía WebSocket voltValPrev = voltVal; //...se toma el voltaje de referencia para volver a ejecutar esta func. condicional } }
Como se indicaba en la introducción, toda la programación de la página WEB va alojada en la memoria flash (utilizando la función PROGMEM) del ESP8266, la tenemos entre las líneas 32 y 88 del sketch.
La función para gestionar el WebSocket (webSocketEvent) la tenemos entre las líneas 93 y 119. Se establecen cuatro «paths» (opciones) para gestionar la comunicación:
En el void loop() que es la parte del sketch que se repite indefinidamente en entre las líneas 146 y 151 está el código para que se realicen lecturas de la entrada analógica (A0) cada 0,1 segundos y entre las líneas 153 y 158 el código para hacer que si la lectura haya variado, se envíe a todos los clientes conectados (hasta 5 clientes).
Una vez que hemos cargado el sketch en el ESP8266, podremos comprobar con nuestro smartphone, tablet u ordenador, que crea un red WiFi. El nombre de la red es «POTENCIOMETRO» y la clave de conexión «12345678«.
Cuando nos conectamos, si accedemos a la dirección IP del punto de acceso: http://192.168.4.1 o simplemente 192.168.4.1, desde cualquier navegador WEB (Mozzilla, Google Chrome, Safari, Internet Explorer, Microsoft Edge…), se ejecutará el código WEB y se visualizará el voltaje que deja pasar el potenciómetro en el navegador. Según modifiquemos el voltaje su valor se actualizará y podremos tener hasta 5 dispositivos conectados a la vez visualizando su valor.
31 Comments
Leave your reply.