Desarrollo de aplicaciones IoT en la nube para Arduino y ESP8266

Text
Read preview
Mark as finished
How to read the book after purchase
Font:Smaller АаLarger Aa

2.3.3 Prácticas

Una vez conocida la estructura de las peticiones HTTP y sus respuestas, en la primera práctica mejorarán el servidor web construido previamente para poder observar con claridad en el monitor serie las partes de dicha estructura, tanto si las peticiones atendidas utilizan el método GET (segunda práctica) como el método POST (tercera práctica). La última práctica servirá para enseñarles cómo integrar Arduino y ESP-01 con el fin de salvar las limitaciones de ambas plataformas: en Arduino, la falta de conectividad WiFi y, en ESP-01, el escaso número de pines digitales y la ausencia de analógicos.

2.3.3.1 Servidor web avanzado

Con el ánimo de simplificar la codificación del primer servidor web desarrollado en la práctica anterior, este no distinguía entre peticiones GET o POST, descartando además el contenido del cuerpo del mensaje. Veamos cuál sería el código del nuevo servidor web, para que no solo diferencie las peticiones con ambos métodos, sino que también sea capaz de mostrar el contenido de dichos mensajes de solicitud en el monitor serie.

#include <ESP8266WiFi.h> // SSID de la red WIFI a la que desea conectarse const char* ssid = “*********”; //contraseña de dicha red const char* password = “ *********”; //dirección IP elegida dentro de la red IPAddress ip(192, 168, 1, 99); //dirección IP del gateway IPAddress gateway(192, 168, 1, 1); //mascara de red IPAddress subnet(255, 255, 255, 0); WiFiServer servidorWeb(80); WiFiClient clienteWeb; void setup() { Serial.begin(115200); //Se inicia la conexión WiFI Serial.print(“Conectando a “ + String(ssid) + “ “); WiFi.config(ip, gateway, subnet); WiFi.begin(ssid, password); while (WiFi.status() != WL_CONNECTED){ delay(500); Serial.print(“.”); } Serial.println(“ Conectado”); //Se arranca el servidor Web servidorWeb.begin(); Serial.println(“Servidor web arrancado”); } void loop() { clienteWeb = servidorWeb.available(); //Se espera que llegue un cliente if (clienteWeb) { boolean lineaEnBlanco = true; String encabezado = “”; // petición HTTP String cuerpo = “”; //cuerpo de la petición int tamanioCuerpo = -1; //tamaño del cuerpo de la petición //El cliente está conectado while (clienteWeb.connected()) { //el cliente ha transmitido datos if (clienteWeb.available()) { char c = clienteWeb.read(); Serial.write(c); // Se muestra en el monitor serie encabezado += c; if (c == ‘\n’ && lineaEnBlanco && encabezado.startsWith(“GET”)){ enviarMensajeRespuesta(); break; } else if (c == ‘\n’ && lineaEnBlanco && encabezado.startsWith(“POST”)) { //Se obtiene el tamaño de cuerpo String temp = encabezado.substring(encabezado.indexOf(“Content-Length:”) + 15); temp.trim(); tamanioCuerpo = temp.toInt(); //se obtiene el cuerpo de la petición while(tamanioCuerpo-- > ⊙) { c = clienteWeb.read(); Serial.write(c); cuerpo += c; } Serial.write(“\n\n”); enviarMensajeRespuesta(); break; } if (c == ‘\n’) lineaEnBlanco = true; else if (c != ‘\r’) lineaEnBlanco = false; } } //Se cierra la conexión para asegurar que los datos se envían delay(10); clienteWeb.stop(); } } //Devuelve un mensaje de respuesta con una página HTML void enviarMensajeRespuesta(){ clienteWeb.println(“HTTP/1.1 200 OK”); clienteWeb.println(“Content-Type: text/html”); clienteWeb.println(“Connection: close”); clienteWeb.println(); clienteWeb.println(“<!DOCTYPE HTML>”); clienteWeb.println(“<head>”); clienteWeb.println(“<meta charset=‘UTF-8’>”); clienteWeb.println(“<title>Servidor Web ESP8266</title>”); clienteWeb.println(“</head>”); clienteWeb.println(“<html>”); clienteWeb.println(“<body>”); clienteWeb.println(“<h1>Bienvenido al servidor Web de mi ESP8266</h1>”); clienteWeb.println(“</body>”); clienteWeb.println(“</html>”); }

Se analizarán únicamente los cambios realizados sobre el servidor web original, localizados en la parte del código donde se ha detectado la conexión de un cliente, es decir, dentro de la siguiente condición.

if (clienteWeb){ … }

Una vez detectada la presencia de un cliente, lo primero que se hace es declarar las variables necesarias para identificar las diferentes partes del mensaje de solicitud: el encabezado, formado por la línea de solicitud y las cabeceras (encabezado) y, de existir, el contenido del cuerpo (cuerpo). La variable lineaEnBlanco (que ya se utilizaba en el servidor web original) sirve para reconocer cuándo finaliza el encabezado y, por lo tanto, comienza el cuerpo. Finalmente, la variable tamanioCuerpo es la que establece el tamaño de los datos que hay en esta última sección del mensaje.

boolean lineaEnBlanco = true; String encabezado = “”; String cuerpo = “”; int tamanioCuerpo = -1;

Lo primero que se hace es leer el encabezado del mensaje. Para ello, y mientras haya información pendiente de ser leída (el método available() devuelve true), esta se recoge, carácter a carácter, mediante el método read(), y se añade a la variable encabezado.

while (clienteWeb.available()) { char c = clienteWeb.read(); encabezado += c; … }

Para saber si se ha terminado de leer el encabezado, cada vez que se finalice una línea (se lee el carácter ‘\’), se comprueba si se trata de una línea en blanco (la variable lineaEnBlanco tiene el valor true) que, como saben, es la que lo separa del cuerpo de dicho mensaje. Y para confirmar si una petición es de tipo GET o POST (los únicos que reconocerá este servidor web), se utiliza el método startsWith(). Añadidas estas tres condiciones a una condición if, sabrán si el encabezado leído corresponde a una petición HTTP realizada con el método GET o con el método POST.

En el caso de tratarse de una petición GET, como se ha partido de la suposición de que este tipo de peticiones no tienen cuerpo, se sale del bucle while de lectura utilizando la sentencia break. Previamente se habrá construido y enviado el mensaje de respuesta que se tenga que devolver al cliente con la función enviarMensajeRespuesta(), donde se encapsulan todos los métodos println() que componen la estructura de dicho mensaje (lo estudiarán más adelante).

if (c == ‘\n’ && lineaEnBlanco && encabezado.startsWith(“GET”)){ enviarMensajeRespuesta(); break; }

Si la solicitud no utiliza el método GET, se verifica si se trata del método POST siguiendo la misma estrategia.

else if (c == ‘\n’ && lineaEnBlanco && encabezado.startsWith(“POST”)) { … }

Una vez identificada una petición POST, se procede a obtener el cuerpo del mensaje. Para ello, a diferencia de con las peticiones GET, deberán analizar las cabeceras que vienen con ella, especialmente Content-Length, que indica el tamaño de esta sección (recuerde que el cuerpo de un mensaje puede tener cualquier tamaño). Puesto que el formato de una cabecera tiene la estructura <clave>:<valor>, extraerá dicha información con el comando indexOf(), que posteriormente se convertirá a un número entero (tamanioCuerpo) necesario para recorrer, carácter a carácter, su contenido.

String temp = encabezado.substring(encabezado.indexOf(“Content-Length:”) + 15); temp.trim(); tamanioCuerpo = temp.toInt();

Una vez obtenido el tamaño del mensaje, es fácil acceder a su contenido usando un nuevo bucle while que vaya leyéndolo carácter a carácter y añadiéndolo a la variable cuerpo.

while(tamanioCuerpo-- > ⊙){ c = clienteWeb.read(); Serial.write(c); cuerpo += c; }

Finalizada la lectura de la petición, al igual que sucedía con los mensajes GET, solo queda componer y enviar la respuesta al navegador utilizando la función enviarMensajeRespuesta(). Dicha función va escribiendo, línea a línea, un mensaje de respuesta según la estructura ya estudiada. En primer lugar, la línea de estado, con el protocolo utilizado (el mismo que el de la petición) y el resultado de la ejecución (200, es decir, correcto), así como un breve mensaje informativo asociado a dicho código (OK).

clienteWeb.println(“HTTP/1.1 200 OK”);

Posteriormente se añaden las cabeceras, en este caso dos: Content-Type, cuyo tipo MIME text/html indica que la respuesta es una página HTML; y Connection, cuyo valor close señala que la conexión utilizada va a ser cerrada (no es persistente).

clienteWeb.println(“Content-Type: text/html”); clienteWeb.println(“Connection: close”);

La siguiente línea que se escribe en el mensaje de respuesta es una línea en blanco. Como ya saben, es imprescindible para separar las cabeceras del cuerpo del mensaje.

clienteWeb.println();

Finalmente, las siguientes líneas son el contenido del cuerpo del mensaje, que en este caso es una página HTML

clienteWeb.println(“<!DOCTYPE HTML>”); clienteWeb.println(“<head>”); clienteWeb.println(“<meta charset=‘UTF-8’>”); clienteWeb.println(“<title>Servidor Web ESP8266</title>”); clienteWeb.println(“</head>”); clienteWeb.println(“<html>”); clienteWeb.println(“<body>”); clienteWeb.println(“<h1>Bienvenido al servidor Web de mi ESP8266</h1>”); clienteWeb.println(“</body>”); clienteWeb.println(“</html>”);

Compilen y carguen este programa en su ESP8266. Abran el monitor serie. Luego introduzcan la dirección 192.168.1.99 en la barra de direcciones de 42 su navegador. En este aparecerá el texto: “Bienvenido al servidor web de mi ESP8266”, y en el monitor serie verá lo siguiente.


La primera línea es la de solicitud, donde puede observar que esta se ha realizado con el método GET utilizando el protocolo HTTP 1.1.

 

GET / HTTP/1.1

Todas las demás son cabeceras, de las que hasta el momento solo conocen host, en la que se indica el URL del servidor al que va dirigida la petición. Del resto de cabeceras, a título informativo se pueden destacar las siguientes:

• Connection. Establece la persistencia de la conexión. En este caso, al tener el valor Keep-Alive, permite su reutilización en siguientes peticiones hasta que el cliente o el servidor decidan su finalización. Si su valor fuera close, no se permitiría dicha reutilización.

• User-Agent. Proporciona información del tipo de aplicación, sistema operativo, proveedor o versión del software del cliente que realiza la petición.

• Accept. Indica al servidor el tipo de contenido (expresados como tipo MIME) que puede procesar el cliente.

• Accept-Encoding. Generalmente informa del algoritmo de compresión que podría utilizarse en la información de vuelta, si se creyera necesario, cuando esta fuera muy extensa.

• Accept-Language. Idioma en el que se espera recibir la respuesta que va a mostrarse al usuario, en este caso el español.

Al tratarse de un mensaje de tipo GET, este no tiene cuerpo.

Habrá observado que parece que las peticiones HTTP se duplican. La segunda petición tiene la siguiente línea de solicitud: “GET /favicon. ico HTTP/1. 1” Dicha petición es enviada por los navegadores para solicitar al servidor web el pequeño icono que aparece a la izquierda del título de la pestaña en la que se muestra la página web. Evidentemente, su servidor web no lo tiene, por lo que puede ignorarse, como hará en estas prácticas, o responder con un código de estado “404 Not found” (en vez del “200 OK”), que se enviaría en la línea de estado de la respuesta a dicha solicitud, para indicar que no se dispone de ese recurso.

En las siguientes prácticas trabajarán con peticiones GET y POST más elaboradas que demostrarán las mejoras introducidas en el código de este servidor.

2.3.3.2 Asignación de valores a los pines del ESP-01 desde un navegador

En la siguiente práctica van a poder controlar desde un navegador el valor de los GPIO0 y GPIO2 que dispone el ESP-01. Para comprobar su funcionamiento, conectarán a cada uno de dichos pines un led que se encenderá cuando su valor sea alto y permanecerá apagado si fuera bajo. El circuito utilizado es el de la siguiente figura. Por seguridad, se conectan a una resistencia de 220 Ω.


El valor de cada uno de dichos pines se asignará a partir del contenido del cuerpo de un mensaje de solicitud HTTP que utilizará el método POST. Para ello, van a emplear un recurso que proporciona HTML: los formularios. Más adelante estudiarán la herramienta Postman, mediante la que podrán realizar peticiones POST de forma mucho más profesional y flexible. El aspecto del formulario que van a usar es el que aparece a continuación.


Está formado por dos campos de entrada y un botón de envío de datos al servidor. Además, dicho formulario se ha enmarcado y titulado para que quede claro el objetivo que se pretende. Los campos de entrada no serán de tipo texto, en los que el usuario pueda introducir cualquier valor, sino menús desplegables con valores predeterminados. Eso complica algo más el código HTML, pero se gana en facilidad de uso y robustez del desarrollo.

El código HTML que se deberá generar desde el ESP-01 para mostrar dicho formulario es el siguiente:

<form method=”POST”> <fieldset style=»width:300px»> <legend> Control GPIOs ESP-01 </legend> GPIO: <select name=»GPIO»> <option value=»0»>GPIO0</option> <option value=»2»>GPIO2</option> </select> Valor: <select name=»Valor»> <option value=»1»>ALTO</option> <option value=»0»>BAJO</option> </select> <br><br> <input type=»submit» value=»Enviar»> </fieldset> </form>

A primera vista puede parecer difícil de entender, pero, con una sencilla explicación, no solo comprenderán el significado de cada una de las etiquetas HTML utilizadas, sino que serán capaces de modificar dicho código para generar sus propios formularios. En cualquier caso, únicamente se darán las nociones imprescindibles para entender la práctica, por lo que, en caso de estar interesados, los animo a introducirse en el apasionante mundo del HTML.

Un formulario HTML tiene como objetivo recoger información del usuario. Para ello se pueden utilizar diferentes tipos de elementos de entrada, como campos de texto, casillas de verificación, radio buttons, listas desplegables, etc. Una vez introducidos los datos en dichos campos, el formulario dispone de un botón encargado de enviarlos al servidor mediante un mensaje HTTP de solicitud que podrá utilizar los métodos GET o POST. Si utilizan el método GET, la información introducida en los campos de entrada irá en el propio URL, mientras que, si se opta por el método POST, estos viajarán ocultos en el cuerpo del mensaje

Un formulario se delimita por las siguientes etiquetas.

<form method=”POST o GET”> … </form>

La etiqueta form tiene el atributo method, que es el que indica el método utilizado para el envío del mensaje HTTP de solicitud con los datos introducidos en el formulario.

Como los elementos de entrada de datos son listas desplegables, cada una de estas se define de la siguiente forma.

<select name=“clave que se envía al servidor”> <option value=»valor que se envía al servidor“<Nombre de la opción</option> … <option value=»valor que se envía al servidor“>Nombre de la últma opción</option> </select>

La etiqueta que identifica una lista desplegable es select. Como habrán observado, esta se acompaña de un atributo (name) que será la clave que identifica dicha lista. Cada uno de los elementos del menú desplegable se define con el tag option. Dicho tag también tiene un atributo, en este caso value, que determina el valor que se enviará al servidor cuando esta se seleccione, junto con el nombre de la lista desplegable (name). Lo que se escriba entre el tag de apertura (<option>) y cierre (</option>) será lo que se muestre como opción al usuario en el navegador.

Si el método de envío escogido es POST, la información irá contenida dentro del cuerpo del mensaje con el formato:

clave1=valor1&…&claveX=valorX

donde las claves son los nombres (atributo name) de las listas desplegables y los valores son las opciones (atributo value) seleccionados por el usuario en cada una de ellas. Por ejemplo, si seleccionasen GPIO0 en la primera lista desplegable y ALTO en la segunda, cuando pulsaran el botón “Enviar”, el contenido del cuerpo del mensaje HTTP de solicitud contendría:

GPIO=0&Valor=1

El único componente importante que queda por explicar del formulario es el botón que desencadenará el envío de datos al servidor. Este se define mediante el tag input.

<input type=“submit” value=“Enviar”>

Se trata de un elemento de entrada cuyo tipo (atributo type) es submit. El otro atributo (value) es el nombre que se mostrará dentro del botón.

Las etiquetas fieldset y legend se utilizan únicamente como recurso estético para enmarcar con un rectángulo de 300 píxeles de ancho todos los componentes del formulario y darle título al formulario, respectivamente.

<fieldset style=“width:300px”> <legend> Control GPIOs ESP-01 </legend> … <fieldset>

Cada vez que reciba la respuesta del servidor, el formulario volverá a tomar los valores iniciales, no los seleccionados por el usuario. Al final de la práctica se indicará una forma de cambiar este comportamiento.

Ya están en condiciones de entender el programa ESP-01 que muestra el formulario con el que podrán controlar sus pines digitales desde cualquier navegador.

#include <ESP8266WiFi.h> // SSID de la red WIFI a la que desea conectarse const char* ssid = “*********”; //contraseña de dicha red const char* password = “ *********”; //dirección IP elegida dentro de la red IPAddress ip(192, 168, 1, 99); //dirección IP del gateway IPAddress gateway(192, 168, 1, 1); //mascara de red IPAddress subnet(255, 255, 255, 0); WiFiServer servidorWeb(80); WiFiClient clienteWeb; String resultadoEjecucion; void setup() { Serial.begin(115200); pinMode(0, OUTPUT); pinMode(2, OUTPUT); //Se inicia la conexión WiFI Serial.print(“Conectando a “ + String(ssid) + “ “); WiFi.config(ip, gateway, subnet); WiFi.begin(ssid, password); while (WiFi.status() != WL_CONNECTED){ delay(500); Serial.print(“.”); } Serial.println(“ Conectado”); //Se arranca el servidor Web servidorWeb.begin(); Serial.println(“Servidor web arrancado”); } void loop() { resultadoEjecucion = “”; clienteWeb = servidorWeb.available(); //Se espera que llegue un cliente if (clienteWeb) { boolean lineaEnBlanco = true; String encabezado = “”; // petición HTTP String cuerpo = “”; //cuerpo de la petición int tamanioCuerpo = -1; //tamaño del cuerpo de la petición //El cliente está conectado while (clienteWeb.connected()) { //el cliente ha transmitido datos if (clienteWeb.available()) { char c = clienteWeb.read(); Serial.write(c); // Se muestra en el monitor serie encabezado += c; if (c == ‘\n’ && lineaEnBlanco && encabezado.startsWith(“GET”)){ enviarMensajeRespuesta(); break; } else if (c == ‘\n’ && lineaEnBlanco && encabezado.startsWith(“POST”)) { //Se obtiene el tamaño de cuerpo String temp = encabezado.substring(encabezado.indexOf(“Content-Length:”) + 15); temp.trim(); tamanioCuerpo = temp.toInt(); //se obtiene el cuerpo de la petición while(tamanioCuerpo-- > 0) { c = clienteWeb.read(); Serial.write(c); cuerpo += c; } Serial.write(“\n\n”); controlGPIOs(cuerpo); enviarMensajeRespuesta(); break; } if (c == ‘\n’) lineaEnBlanco = true; else if (c != ‘\r’) lineaEnBlanco = false; } } //Se cierra la conexión para asegurar que los datos se envían delay(10); clienteWeb.stop(); } } //asigna un valor a un gpio void controlGPIOs(String cuerpo){ int gpio = cuerpo.substring(cuerpo.indexOf(“GPIO=”)+5, (cuerpo.indexOf(“&”))).toInt(); int accion = cuerpo.substring(cuerpo.indexOf(“Valor=”)+6).toInt(); digitalWrite(gpio, accion); if(accion == 1) resultadoEjecucion = “El GPIO” + String(gpio) + “ se ha puesto a nivel alto”; else resultadoEjecucion = “El GPIO” + String(gpio) + “ se ha puesto a nivel bajo”; } //Devuelve un mensaje de respuesta con una página HTML void enviarMensajeRespuesta(){ clienteWeb.println(“HTTP/1.1 200 OK”); clienteWeb.println(“Content-Type: text/html”); clienteWeb.println(“Connection: close”); clienteWeb.println(); clienteWeb.println(“<!DOCTYPE HTML>”); clienteWeb.println(“<head>”); clienteWeb.println(“<meta charset=‘UTF-8’>”); clienteWeb.println(“<title>Servidor Web ESP8266</title>”); clienteWeb.println(“</head>”); clienteWeb.println(“<html>”); clienteWeb.println(“<body>”); clienteWeb.println(“<form method=‘POST’>”); clienteWeb.println(“<fieldset style=‘width:300px’>”); clienteWeb.println(“<legend>Control GPIOs ESP-01</legend>”); clienteWeb.println(“GPIO: “); clienteWeb.println(“<select name=‘GPIO’>”); clienteWeb.println(“<option value=‘0’>GPIO0</option>”); clienteWeb.println(“<option value=‘2’>GPIO2</option>”); clienteWeb.println(“</select>”); clienteWeb.println(“Valor: “); clienteWeb.println(“<select name=‘Valor’>”); clienteWeb.println(“<option value=‘1’>ALTO</option>”); clienteWeb.println(“<option value=‘0’>BAJO</option>”); clienteWeb.println(“</select>”); clienteWeb.println(“<br><br>”); clienteWeb.println(“<input type=‘submit’ value=‘Enviar’>”); clienteWeb.println(“</fieldset>”); clienteWeb.println(“</form>”); clienteWeb.println(“<br>”); clienteWeb.println(resultadoEjecucion); clienteWeb.println(“</body>”); clienteWeb.println(“</html>”); }

 

El código utilizado de base es el del servidor avanzado del apartado anterior, por lo que solo se comentarán las modificaciones realizadas. La primera de ellas está en la inclusión de la declaración de la variable resultadoEjecucion realizada al inicio del programa. En dicha variable se compondrá la frase con el resultado de la ejecución, que se incluirá en la página HTML que se devolverá al usuario.

String resultadoEjecucion;

En el bloque setup() el único cambio realizado es la declaración de los GPIO0 y GPIO2 como salida para que el usuario pueda encender o apagar los leds que tienen conectados.

pinMode(0, OUTPUT); pinMode(2, OUTPUT);

El principal cambio realizado está en la parte donde se ha identificado que el mensaje recibido es de tipo POST, donde, una vez leído el cuerpo del mensaje en el que viaja la información obtenida del formulario, se invoca la función controlGPIOs(). Dentro de dicha función, lo que se hace es extraer el valor de los dos datos esperados: el número del GPIO sobre el que se va a actuar (0 o 2) y el valor que se le va a dar (alto o bajo o, lo que es lo mismo, 1 o 0). Para entender cómo se hace esto, primero deben recordar que el formato del cuerpo de un mensaje que contiene la opción seleccionada de un menú HTML es:

Atributo name de la etiqueta select del menú =

Atributo value de la etiqueta option de la opción seleccionada

Si en el mismo mensaje viajaran opciones seleccionadas en diferentes menús (como es el caso), cada una de ellas se separa con el carácter ‘&’. Por ejemplo, utilizando el código HTML del formulario utilizado, si se quiere encender el led conectado al GPIO2, el contenido del mensaje de solicitud sería:

GPIO=2&Valor=1

Como saben que el atributo name del primer menú es “GPIO” y el del segundo menú es “Valor”, usarán la sentencia substring para extraer la opción seleccionada en cada uno de ellos. Además, puesto que lo que llega es un String, este se tiene que convertir a int con el método toInt() para ser utilizado posteriormente en la sentencia digitalWrite().

int gpio = cuerpo.substring(cuerpo.indexOf(“GPIO=”)+5, (cuerpo.indexOf(“&”))).toInt(); int accion = cuerpo.substring(cuerpo.indexOf(“Valor=”)+6).toInt();

Conocido el GPIO y el valor que se le desea asignar, se ejecuta la orden con la sentencia digitalWrite().

digitalWrite(gpio, accion);

Las dos últimas sentencias de esta función auxiliar se utilizan para componer el texto que confirmará la acción realizada (almacenándolo en la variable resultadoEjecucion). Dicho texto se mostrará en la página HTML que se devuelva como resultado al usuario.

if(accion == 1) resultadoEjecucion = “El GPIO” + String(gpio) + “ se ha puesto a nivel alto”; else resultadoEjecucion = “El GPIO” + String(gpio) + “ se ha puesto a nivel bajo”;

Finalmente, la función enviarMensajeRespuesta() se encarga de componer el mensaje HTTP de respuesta. Dicha respuesta tiene dos partes: un encabezado formado por la línea de estado y las cabeceras; y el cuerpo del mensaje, que es la página HTML mostrada en el navegador al usuario.

En lo que respecta al encabezado, la primera línea es la de estado, que indica que se utiliza el protocolo HTTP 1.1, con el que se devuelve el código 200, cuyo mensaje corto descriptivo (OK) indica que la petición ha sido ejecutada correctamente. Las dos siguientes líneas son cabeceras:Content-Type indica que el contenido del mensaje es HTML (text/html); Connection tiene el valor (close), por lo que la conexión con el cliente se cerrará una vez proporcionada la respuesta. La última línea está en blanco, imprescindible para separar las cabeceras del cuerpo del mensaje.

clienteWeb.println(“HTTP/1.1 200 OK”); clienteWeb.println(“Content-Type: text/html”); clienteWeb.println(“Connection: close”); clienteWeb.println();

Como se acaba de decir, el cuerpo del mensaje contiene la página HTML de respuesta que se muestra al usuario en el navegador. Esta incluirá el formulario en el que podrán seguir realizando nuevas selecciones (encendiendo o pagando leds).

clienteWeb.println(“<form method=‘POST’>”); clienteWeb.println(“<fieldset style=‘width:300px’>”); clienteWeb.println(“<legend>Control GPIOs ESP-01</legend>”); clienteWeb.println(“GPIO: “); clienteWeb.println(“<select name=‘GPIO’>”); clienteWeb.println(“<option value=‘0’>GPIO0</option>”); clienteWeb.println(“<option value=‘2’>GPIO2</option>”); clienteWeb.println(“</select>”); clienteWeb.println(“Valor: “); clienteWeb.println(“<select name=‘Valor’>”); clienteWeb.println(“<option value=‘1’>ALTO</option>”); clienteWeb.println(“<option value=‘0’>BAJO</option>”); clienteWeb.println(“</select>”); clienteWeb.println(“<br><br>”); clienteWeb.println(“<input type=‘submit’ value=‘Enviar’>”); clienteWeb.println(“</fieldset>”); clienteWeb.println(“</form>”);

El tag <br> genera un salto de línea.

Observen que los textos entre comillas que también deben estar entrecomillados se enmarcan entre comillas simples (’) y no por dobles comillas (”).

Además del formulario, se mostrará un texto con el resultado de la ejecución de la última solicitud realizada.

clienteWeb.println(resultadoEjecucion);

En las líneas HTML generadas con sentencias del tipo clienteWeb. println(texto), lo que aparece como texto se muestra en el navegador sin ningún tipo de formato.

Compilen y carguen el programa en el ESP-01. Después, introduzca la dirección 192.168.1.99 de su servidor web en un navegador. Abran el monitor serie y luego seleccionen la opción “GPIO2” en el primer menú y “ALTO” en el segundo. Pulsen el botón “Enviar”. Inmediatamente verán cómo se enciende el led conectado a dicho GPIO y, en el monitor serie, aparece la siguiente información.


La primera línea es la de solicitud, que indica que se ha realizado una petición con el método POST utilizando el protocolo HTTP 1.1. No se ha especificado ninguna ruta de acceso dentro del servidor, por lo que, por defecto, se usa ‘/’, que representa su directorio home o raíz.

POST / HTTP/1.1

A continuación le siguen las cabeceras, entre las que destacaré Content-Type, utilizada para informar sobre el formato del cuerpo del mensaje; y Content-Length, que especifica el tamaño en bytes que tiene este.

La última línea es el contenido del cuerpo del mensaje que, como se indicaba en la cabecera Content-Length, tiene un tamaño de 14 caracteres. En este se puede comprobar que en el menú identificado como “GPIO” (atributo name de su etiqueta select) se ha seleccionado la opción cuyo valor (atributo value de la etiqueta option) es 2. Al usuario esta opción se le mostraba como “GPIO2”. También pueden ver que en el menú identificado como “Valor” se ha seleccionado la opción cuyo valor es 1 (al usuario esta opción se le mostraba como “ALTO”).

GPIO=2&Valor=1

Después de haber realizado diferentes pruebas, habrán podido observar que, una vez devuelta la respuesta por el servidor, el formulario vuelve a mostrar los valores iniciales en vez de los últimos seleccionados. Cambiar este comportamiento es sencillo, por lo que se les propone como práctica adicional. Para ello, lo único que tienen que saber es que, en HTML, para elegir la opción de una lista desplegable que será visible por defecto, deben añadir el atributo selected a la etiqueta option correspondiente. Por ejemplo, si desean que la lista desplegable muestre inicialmente el GPIO2 en vez de GPIO0, deben cambiar la sentencia HTML:

<option value=’2’>GPIO2</option>

por:

<option value=’2’ selected>GPIO2</option>

Hacer que el formulario muestre la selección realizada previamente por el usuario (no la que tiene por defecto) obliga a modificar el siguiente grupo de sentencias:

clienteWeb.println(“<option value=‘0’>GPIO0</option>”); clienteWeb.println(“<option value=‘2’>GPIO2</option>”); … clienteWeb.println(“<option value=‘1’>ALTO</option>”); clienteWeb.println(“<option value=‘0’>BAJO</option>”);

En ellas se debe sustituir el texto que contienen los métodos println() por variables cuyo contenido deberá ser compuesto dentro de la función controlGPIOs(), de forma similar a como se hizo con la variable resultadoEjecucion. En función del valor de las variables gpio y accion, se añadirá el atributo selected solo a la etiqueta option de cada menú que represente la opción elegida por el usuario en cada uno de ellos.