Servidor Web con ESP32 y BMP280

Objetivos

 

  • Jugar con el sensor BMP280 y el ESP32.
  • Crear un servidor web que publique las lecturas.
  •  

    Material requerido.

     

    Vista principal

    Un ESP32

     Imagen principal  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:

    Muescas de encaje

    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:

    Imagen de las conexiones

    Ahora ya podemos encajar el ESP32 mediante la protoboard, y conectar cables sin problemas al procesador.

  • En la imagen de arriba, he retirado los raíles de una de las protoboards antes de encajar el ESP32, porque pura vagancia (Ya la tenía separada)
  • Pero también seria posible colocar el ESP32 sobre el canal central y añadir la segunda protoboard con o sin railes. A tu gusto siempre que dejes sitio para pinchar cables Dupont en los costados.
  •  

    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:

    Detalle de pines con nombre

    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());

    Display de consola

    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 +="&deg;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.

  • De hecho todo este tutorial es prácticamente una copiada de la pagina para crear una estación meteo con el ESP32 y que podéis encontrar aquí, entre otros magníficos tutoriales que no podemos por menos que recomendaros si entendéis Ingles.
  • Aquí hemos hecho poco mas que traducir el tuto y cambiar las llamadas a la librería a usar, porque mientras que lastminuteEngineers usa las librerías de Adafruit y el BME280 (Que proporciona humedad, además) no he sido capaz de conseguir que funcionen con el BMP280 (Que no suministra datos de humedad)
  • Espero hacer pronto un nuevo tuto muy como este con el BME280 que acabo de recibir y espero que para entonces ya me rulen las librerías de Adafruit como Dios manda. 
  •  

    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:

    Pantallazo del navegador

    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'>&deg;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.

    Presentacion de datos personalizada

    Y esto es todo por hoy amigos. Hasta la próxima.

    IMAGEN DE MARCA

    Deja una respuesta