Objetivos
Material requerido.
Sensor BMP280 |
ESP32 Servidor web con BMP280
En la última sesión vimos como conectar un sensor BMP280 (Temperatura, presión barométrica y altitud) a un Arduino UNO por aquello de la sencillez. Pero fue tan sencillo que al final necesitaba complicar todo un poco y que mejor manera, que montar un servidor web con un ESP32 que publique esos datos en una página que podamos leer desde un navegador externo.
Para ello vamos a usar un ESP32 como procesador por incluir ya WIFI y además aprovechamos para seguir con este pequeño procesador maravilla que nos ha regalado los chicos de Expressif.
La ventaja de estos sensores BMP280 todo en uno es su pequeño tamaño y consumo lo que nos permite integrar una pequeña estación meteorológica en un único sensor y mas adelante usaremos un sensor BME280 que tiene lo mismo que el BMP280, pero además incluye un sensor de humedad ambiente, que tiene una doble ventaja. Por un lado, nos da una lectura adicional y por otra la humedad ambiente nos permite calibrar mucho mejor la estimación de altitud ya que al depender de la presión atmosférica, la humedad puede arruinar la lectura.
Montando el ESP32 en la protoboard
El diagrama de conexión del BMP280 al ESP32 es de lo más sencilla, pero nos plantea un problema logístico con el ESP32 y la protoboard ya que este es mas ancho de lo que nos gustaría y nos impide usar una sola protoboard, obligándonos a usar dos unidades por las muescas laterales. Por si no os habíais dado cuenta, las protoboard pueden unirse lateralmente mediante las muescas macho o hembra que tienen disponibles:
Encajándolas con cuidado podemos hacer crecer nuestra protoboard hasta el tamaño adecuado (En ancho y en largo) y para montar un ESP32 necesitamos un mínimo de dos protos. El resultado final será algo como esto:
Ahora ya podemos encajar el ESP32 mediante la protoboard, y conectar cables sin problemas al procesador.
Esquema de conexión del ESP32 al BMP280
Vamos a conectar nuestro BMP280 mediante I2C usando las mismas librerías que usamos con Arduino UNO, y adaptaremos los pines al ESP32. Para ello es imprescindible que tengas clara la disposición de pines de tu modelo porque no todos los disponibles en el mercado usan la misma distribución.
Dicho de otro modo: No te fíes de la posición de mis pines porque en tu ESP32 la disposición puede ser diferente y es importante que te asegures de no cometer errores para evitar disgustos (Y visitas a Prometec a comprar más ESP32s)
- Por eso es importante a la hora de conectar que tengas en cuenta el nombre de los pines que te indicamos mas que en las posiciones relativas de os mismos en tu placa. Asegúrate de leer el nombre de los pines en el propio ESP32 o tendrás problemas.
Por si acaso y a efectos de inventario, os pongo aquí la distribución de pines de los ESP32 que vendemos en nuestra tienda:
Las conexiones del BMP280 al ESP32 deben ser de este modo:
ESP32 | BMP280 |
---|---|
GND | GND |
3.3V | Vcc |
SCL | IO22 |
SDA | IO21 |
SDO | GND |
Hemos conectado el pin SDO a GND para asignar la dirección 0x76 I2C al módulo BMP280. Recuerda que podemos cambiar esta dirección dependiendo de la conexión de este pin:
Pin BMP280 | Arduino | Dirección I2C |
---|---|---|
SD0 | GND | 0x76 |
SD0 | 3,3V | 0x77 |
SD0 | Flotando | No está muy claro |
Y sin otra cosa pasamos a la parte del programa
Programa ESP32 con BMP80
Podemos descargar la misma librería que usamos en la sesión previa con el Arduino UNO: BMP280_DEV y ya podemos empezar importando las librerías precisas:
#include <WiFi.h> #include <WebServer.h> #include <Wire.h> #include <BMP280_DEV.h>
Creamos ahora una instancia del sensor y definimos tres variables que contengan las lecturas:
BMP280_DEV bme; float temperature, humidity, pressure, altitude;
Y ahora definimos un par de variables como siempre, que contengan los datos de nuestra WIFI: SSID y contraseña, y arrancamos el servidor web en el puerto 80::
const char* ssid = "TU_WIFI"; const char* password = "TU_CONTRASEÑA"; WebServer server(80);
En el setup inicializamos el sensor con la dirección 0x76, programamos las lecturas cada 2 segundos y arrancamos la toma de datos:
bme.begin(BMP280_I2C_ALT_ADDR); bme.setTimeStandby(TIME_STANDBY_2000MS); // Set the standby time to 2 seconds bme.startNormalConversion();
Vamos a arrancar la conexión WIFI e imprimimos la dirección que obtengamos para mas adelante podernos conectar con un navegador a ver los datos publicados:
while (WiFi.status() != WL_CONNECTED) { delay(1000); Serial.print("."); } Serial.println(""); Serial.println("WiFi connected..!"); Serial.print("Got IP: "); Serial.println(WiFi.localIP());
Ahora vamos a lanzar el server con una función callback (O sea a la que llamamos cuando alguien se conecte) que definiremos mas abajo. La primera línea server.on() liga la función handle_OnConnect a “/”, o sea que cuando alguien se conecte, será esta función la que se invoque para servir las peticiones.
server.on("/", handle_OnConnect); server.onNotFound(handle_NotFound); server.begin(); Serial.println("HTTP server started");
Y con esto vamos al loop() principal y a definir las funciones que hemos usado:
void loop() { server.handleClient(); bme.getMeasurements(temperature, pressure, altitude); }
¿Fácil. Nooo? Vamos con las funciones auxiliares. ¿Que queremos hacer cuando alguien conecte a nuestro servidor web? Pues simplemente leemos el sensor y publicamos las lecturas:
void handle_OnConnect() { bme.getMeasurements(temperature, pressure, altitude); server.send(200, "text/html", SendHTML(temperature,humidity,pressure,altitude)); }
La última línea envía un código “200 “ que en HTML es un OK y luego enviamos los datos para que los procese el navegador. Y en el caso de que no encontremos el servidor:
void handle_NotFound() { server.send(404, "text/plain", "No hay respuesta"); }
La función SendHTML() parece más complicada, pero la idea básica parte de construir un string dinámicamente, desde la función, que contenga el HTML de la respuesta:
String SendHTML(float temperature,float humidity,float pressure,float altitude) { String ptr = "<!DOCTYPE html> <html>\n"; ptr +="<head><meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0, user-scalable=no\">\n"; ptr +="<title>ESP32 Estacion Meteo</title>\n"; ptr +="<style>html { font-family: Helvetica; display: inline-block; margin: 0px auto; text-align: center;}\n"; ptr +="body{margin-top: 50px;} h1 {color: #444444;margin: 50px auto 30px;}\n"; ptr +="p {font-size: 24px;color: #444444;margin-bottom: 10px;}\n"; ptr +="</style>\n"; ptr +="</head>\n"; ptr +="<body>\n"; ptr +="<div id=\"webpage\">\n"; ptr +="<h1>ESP32 Estacion Meteo</h1>\n"; ptr +="<p>Temperatura: "; ptr +=temperature; ptr +="°C</p>"; ptr +="<p>Humedad: "; ptr +=humidity; ptr +="%</p>"; ptr +="<p>Presión: "; ptr +=pressure; ptr +="hPa</p>"; ptr +="<p>Altitud: "; ptr +=altitude; ptr +="m</p>"; ptr +="</div>\n"; ptr +="</body>\n"; ptr +="</html>\n"; return ptr; }
Programa Completo: BMP280_ESP32
Simplemente vamos creando un String añadiendo lo necesario para componer la respuesta en formato HTML. Esta forma de formatear la página HTML, y las funciones usadas, se las hemos copiado descaradamente a los chicos de LastMinuteEngineers, que ya hemos usado en algún tuto previo con el ESP8266, porque nos parece la forma mas elegante y sencilla que he visto hasta la fecha de como componer una pagina desde Arduino y la vamos a usar como base para futuros tutos.
Si subimos el programa y lo ejecutamos, podemos ir a la dirección IP de nuestro servidor web (Que os la mandado imprimir en la consola Arduino para poder usarla ahora) Veremos algo como esto:
Donde podéis ver el resultado con la temperatura en grados centígrados (Con su gradito y todo º) La presión en hectopascales (Pero podéis convertirlo en bares si os apetece) y la altitud en metros. Como el sensor que estoy usando no nos proporciona humedad el resultado es simplemente cero y podéis eliminar la linea tambien si estáis de humor.
Mejorando la presentacion de datos
La página web que hemos creado mas arriba esta muy bien y es mas que suficiente para unos técnicos como nosotros que no solemos apreciar demasiado las sutilezas del estilo y la presentación pero es indudable que con un poco de gusto (Algo que no suele sobarnos) y trabajo para mejorar el resultado puede dar resultados espectaculares.
Por eso no me puedo resistir a incluir aquí la versión de la función SendHTML() que ha publicado en LastMinuteEngineers con mucho mas estilo y una presentación con colores y pequeños gráficos para representar las variables.
Simplemente sustituir la funcion anterior por esta otra y veréis como el resultado en pantalla mejora espectacularmente:
Programa Completo: BMP280_ESP32_2
String SendHTML(float temperature,float humidity,float pressure,float altitude)
{ String ptr = "<!DOCTYPE html>";
ptr +="<html>";
ptr +="<head>";
ptr +="<title>ESP32 Weather Station</title>";
ptr +="<meta name='viewport' content='width=device-width, initial-scale=1.0'>";
ptr +="<link href='https://fonts.googleapis.com/css?family=Open+Sans:300,400,600' rel='stylesheet'>";
ptr +="<style>";
ptr +="html { font-family: 'Open Sans', sans-serif; display: block; margin: 0px auto; text-align: center;color: #444444;}";
ptr +="body{margin: 0px;} ";
ptr +="h1 {margin: 50px auto 30px;} ";
ptr +=".side-by-side{display: table-cell;vertical-align: middle;position: relative;}";
ptr +=".text{font-weight: 600;font-size: 19px;width: 200px;}";
ptr +=".reading{font-weight: 300;font-size: 50px;padding-right: 25px;}";
ptr +=".temperature .reading{color: #F29C1F;}";
ptr +=".humidity .reading{color: #3B97D3;}";
ptr +=".pressure .reading{color: #26B99A;}";
ptr +=".altitude .reading{color: #955BA5;}";
ptr +=".superscript{font-size: 17px;font-weight: 600;position: absolute;top: 10px;}";
ptr +=".data{padding: 10px;}";
ptr +=".container{display: table;margin: 0 auto;}";
ptr +=".icon{width:65px}";
ptr +="</style>";
ptr +="</head>";
ptr +="<body>";
ptr +="<h1>ESP32 Weather Station</h1>";
ptr +="<div class='container'>";
ptr +="<div class='data temperature'>";
ptr +="<div class='side-by-side icon'>";
ptr +="<svg enable-background='new 0 0 19.438 54.003'height=54.003px id=Layer_1 version=1.1 viewBox='0 0 19.438 54.003'width=19.438px x=0px xml:space=preserve xmlns=http://www.w3.org/2000/svg xmlns:xlink=http://www.w3.org/1999/xlink y=0px><g><path d='M11.976,8.82v-2h4.084V6.063C16.06,2.715,13.345,0,9.996,0H9.313C5.965,0,3.252,2.715,3.252,6.063v30.982";
ptr +="C1.261,38.825,0,41.403,0,44.286c0,5.367,4.351,9.718,9.719,9.718c5.368,0,9.719-4.351,9.719-9.718";
ptr +="c0-2.943-1.312-5.574-3.378-7.355V18.436h-3.914v-2h3.914v-2.808h-4.084v-2h4.084V8.82H11.976z M15.302,44.833";
ptr +="c0,3.083-2.5,5.583-5.583,5.583s-5.583-2.5-5.583-5.583c0-2.279,1.368-4.236,3.326-5.104V24.257C7.462,23.01,8.472,22,9.719,22";
ptr +="s2.257,1.01,2.257,2.257V39.73C13.934,40.597,15.302,42.554,15.302,44.833z'fill=#F29C21 /></g></svg>";
ptr +="</div>";
ptr +="<div class='side-by-side text'>Temperature</div>";
ptr +="<div class='side-by-side reading'>";
ptr +=(int)temperature;
ptr +="<span class='superscript'>°C</span></div>";
ptr +="</div>";
ptr +="<div class='data humidity'>";
ptr +="<div class='side-by-side icon'>";
ptr +="<svg enable-background='new 0 0 29.235 40.64'height=40.64px id=Layer_1 version=1.1 viewBox='0 0 29.235 40.64'width=29.235px x=0px xml:space=preserve xmlns=http://www.w3.org/2000/svg xmlns:xlink=http://www.w3.org/1999/xlink y=0px><path d='M14.618,0C14.618,0,0,17.95,0,26.022C0,34.096,6.544,40.64,14.618,40.64s14.617-6.544,14.617-14.617";
ptr +="C29.235,17.95,14.618,0,14.618,0z M13.667,37.135c-5.604,0-10.162-4.56-10.162-10.162c0-0.787,0.638-1.426,1.426-1.426";
ptr +="c0.787,0,1.425,0.639,1.425,1.426c0,4.031,3.28,7.312,7.311,7.312c0.787,0,1.425,0.638,1.425,1.425";
ptr +="C15.093,36.497,14.455,37.135,13.667,37.135z'fill=#3C97D3 /></svg>";
ptr +="</div>";
ptr +="<div class='side-by-side text'>Humidity</div>";
ptr +="<div class='side-by-side reading'>";
ptr +=(int)humidity;
ptr +="<span class='superscript'>%</span></div>";
ptr +="</div>";
ptr +="<div class='data pressure'>";
ptr +="<div class='side-by-side icon'>";
ptr +="<svg enable-background='new 0 0 40.542 40.541'height=40.541px id=Layer_1 version=1.1 viewBox='0 0 40.542 40.541'width=40.542px x=0px xml:space=preserve xmlns=http://www.w3.org/2000/svg xmlns:xlink=http://www.w3.org/1999/xlink y=0px><g><path d='M34.313,20.271c0-0.552,0.447-1,1-1h5.178c-0.236-4.841-2.163-9.228-5.214-12.593l-3.425,3.424";
ptr +="c-0.195,0.195-0.451,0.293-0.707,0.293s-0.512-0.098-0.707-0.293c-0.391-0.391-0.391-1.023,0-1.414l3.425-3.424";
ptr +="c-3.375-3.059-7.776-4.987-12.634-5.215c0.015,0.067,0.041,0.13,0.041,0.202v4.687c0,0.552-0.447,1-1,1s-1-0.448-1-1V0.25";
ptr +="c0-0.071,0.026-0.134,0.041-0.202C14.39,0.279,9.936,2.256,6.544,5.385l3.576,3.577c0.391,0.391,0.391,1.024,0,1.414";
ptr +="c-0.195,0.195-0.451,0.293-0.707,0.293s-0.512-0.098-0.707-0.293L5.142,6.812c-2.98,3.348-4.858,7.682-5.092,12.459h4.804";
ptr +="c0.552,0,1,0.448,1,1s-0.448,1-1,1H0.05c0.525,10.728,9.362,19.271,20.22,19.271c10.857,0,19.696-8.543,20.22-19.271h-5.178";
ptr +="C34.76,21.271,34.313,20.823,34.313,20.271z M23.084,22.037c-0.559,1.561-2.274,2.372-3.833,1.814";
ptr +="c-1.561-0.557-2.373-2.272-1.815-3.833c0.372-1.041,1.263-1.737,2.277-1.928L25.2,7.202L22.497,19.05";
ptr +="C23.196,19.843,23.464,20.973,23.084,22.037z'fill=#26B999 /></g></svg>";
ptr +="</div>";
ptr +="<div class='side-by-side text'>Pressure</div>";
ptr +="<div class='side-by-side reading'>";
ptr +=(int)pressure;
ptr +="<span class='superscript'>hPa</span></div>";
ptr +="</div>";
ptr +="<div class='data altitude'>";
ptr +="<div class='side-by-side icon'>";
ptr +="<svg enable-background='new 0 0 58.422 40.639'height=40.639px id=Layer_1 version=1.1 viewBox='0 0 58.422 40.639'width=58.422px x=0px xml:space=preserve xmlns=http://www.w3.org/2000/svg xmlns:xlink=http://www.w3.org/1999/xlink y=0px><g><path d='M58.203,37.754l0.007-0.004L42.09,9.935l-0.001,0.001c-0.356-0.543-0.969-0.902-1.667-0.902";
ptr +="c-0.655,0-1.231,0.32-1.595,0.808l-0.011-0.007l-0.039,0.067c-0.021,0.03-0.035,0.063-0.054,0.094L22.78,37.692l0.008,0.004";
ptr +="c-0.149,0.28-0.242,0.594-0.242,0.934c0,1.102,0.894,1.995,1.994,1.995v0.015h31.888c1.101,0,1.994-0.893,1.994-1.994";
ptr +="C58.422,38.323,58.339,38.024,58.203,37.754z'fill=#955BA5 /><path d='M19.704,38.674l-0.013-0.004l13.544-23.522L25.13,1.156l-0.002,0.001C24.671,0.459,23.885,0,22.985,0";
ptr +="c-0.84,0-1.582,0.41-2.051,1.038l-0.016-0.01L20.87,1.114c-0.025,0.039-0.046,0.082-0.068,0.124L0.299,36.851l0.013,0.004";
ptr +="C0.117,37.215,0,37.62,0,38.059c0,1.412,1.147,2.565,2.565,2.565v0.015h16.989c-0.091-0.256-0.149-0.526-0.149-0.813";
ptr +="C19.405,39.407,19.518,39.019,19.704,38.674z'fill=#955BA5 /></g></svg>";
ptr +="</div>";
ptr +="<div class='side-by-side text'>Altitude</div>";
ptr +="<div class='side-by-side reading'>";
ptr +=(int)altitude;
ptr +="<span class='superscript'>m</span></div>";
ptr +="</div>";
ptr +="</div>";
ptr +="</body>";
ptr +="</html>";
return ptr;
}
Como verás, la diferencia es apreciable.
Y esto es todo por hoy amigos. Hasta la próxima.