Montaje y código

De pruebas y errores. Una pequeña introducción

Tenía mis cachivaches funcionando tan alegremente cuando se sucedieron los siguientes tres problemas que me obligaron a rehacer completamente la programación:

    1. El montaje no funcionaba si no se conecta a la WiFi. Esto en sí no era un problema, estaba diseñado así, pero había pedido el favor de comparar las lecturas obtenidas con un sensor “de verdad”, de los pata negra, y claro, en cuanto se intentó conectar el chisme fuera del ámbito de la red WiFi en la que estaba configurado, no se pudo.
    2. Uno de los sensores perdió conexión a la WiFi y no se volvió a reconectar. Esto ya es un problema algo más grave, porque no sólo perdía la conexión, sino que dejaba de mostrar los datos. En principio el script estaba preparado para reconectarse, pero no lo hacía, así que vuelta a buscar por la red.
    3. Contrastando lecturas con el sensor “pata negra”, las medidas mostraban un margen de error creciente conforme más alta era la lectura del CO2, yendo desde un 10% en niveles bajos, en el entorno de los cuatrocientos y pico, hasta un 20% en lecturas de tan solo setecientos y pico. Esto obviamente era una barbaridad. En ese momento se estaba tomando la lectura del sensor mediante la señal PWM, lo que me hizo sospechar que en el proceso de conversión y lectura pudiera generarse este error, al menos en parte, pero el que aumentara de forma no lineal sino porcentualmente el error conforme subiera la señal daba que pensar. Todavía no he podido verificar esta sospecha, pero por si acaso he modificado el código para tomar los datos del sensor por UART.

La parte buena de todo esto es que los tres problemas se sucedieron casi a la vez, en la misma semana, así que se pudieron afrontar todos al mismo tiempo ¿cómo? Pues rehaciendo la programación completamente, por eso esta entrada ha tardado algo más de la cuenta.

Y es que, ya puestos, me pareció buena idea que si el ESP32 tenía dos núcleos se podían utilizar ambos para realizar tareas distintas. Se podía, con un núcleo, tomar la lectura de los sensores y mostrar los datos por la pantalla, y el otro dedicarlo a todo lo demás, que por el momento sólo es la conexión WiFi y subir los datos a la nube.

Dicho así es fácil, el problema era que no tenía ni idea de cómo hacerlo y hubo que documentarse “un poco”.

Pero vamos ya directamente al montaje y programación del engendro

Esquema y código.

Funcionamiento

    • Cada seis segundos mostrará por la pantalla los datos de temperatura, humedad y CO2
    • Cada cinco minutos subirá a ThingSpeak la media de las diez últimas lecturas de los mencionados datos.
    • Si el nivel de CO2 supera las 700 ppm parpadeará un led azul
    • Si el nivel de CO2 supera las 900 ppm parpadearán un led azul y uno rojo alternativamente
    • Al inicio, el montaje se intentará conectar a la WiFi, mientras lo intenta parpadeará el led azul de la placa.
    • Intentará establecer conexión durante un par de minutos, en el momento que lo consiga el led azul pasará a estar fijo, si transcurrido ese tiempo no ha conseguido conexión, el led azul se apagará y no se volverá a intentar una conexión, habría que resetear o apagar y encender.
    • Si una vez establecida la conexión esta se pierde, el sistema intentará reconectarse. Lo veremos porque el led azul de la placa parpadea.

Montaje

ADVERTENCIAS PREVIAS IMPORTANTES

Vamos primero con un descargo de responsabilidades: No me hago responsable de los daños que se puedan derivar por el montaje descrito a continuación o el uso del mismo. Que cada cual actúe bajo su propia responsabilidad.

Por favor, mucha atención a lo siguiente antes de hacer nada porque, aunque a algunos les parecerá de Perogrullo, es muy importante:

El montaje propuesto se alimenta con una fuente de 5v y a través del pin Vin y esto requiere de una cierta prudencia. Con 500 mA son suficientes para alimentar este circuito. Si mis cálculos son correctos, la suma de los consumos máximos de cada componente no excede los 430 mA. Por supuesto, nada nos impide usar alimentadores de 1 ó 2 amperios, pero 500 mA es lo mínimo y es suficiente.

Como hemos dicho, alimentaremos el montaje a través del pin Vin para alimentar también directamente desde la fuente al sensor. Esto es así porque he comprobado que si alimentamos a la placa por el puerto micro USB la salida Vin proporciona, en mi caso, 4,5 V, lo que es muy justo para el modelo MH-Z19B, e insuficiente, según el datasheet, para el MH-Z19C, aunque funciona, pero no me fío, puede que algunos de los fallos que experimentara en las pruebas fueran por alimentar el montaje así. No obstante, alimentar el sistema por el PIN V in exige ciertas precauciones.

Mucha precaución: Desconectar completamente el montaje antes de conectarlo al puerto USB del ordenador para programarlo. Ojo con esto, porque nos podemos cargar la placa base del ordenador y liarla parda. Así que repito: HAY QUE DESCONECTAR COMPLETAMENTE EL MONTAJE DE LA FUENTE DE ALIMENTACIÓN ANTES DE CONECTARLO AL ORDENADOR PARA PROGRAMAR LA PLACA. Y a riesgo de ser mu pesao, vuelvo a insistir: Siempre que queramos conectar el montaje al ordenador lo tendremos que conectar por el puerto micro USB de la placa, obviamente, así lo programaremos y podremos realizar otras pruebas, podremos usar el monitor serie del IDE de Arduino, etc. Pues bien, en este contexto, jamás, jamás ha de estar conectado a la fuente de alimentación puesto que ya está recibiendo la alimentación por el puerto USB del ordenador y podríamos armar un cortocircuito de consecuencias bastante desagradables. Espero que este punto haya quedado suficientemente claro.

Ojo con las conexiones: Hay diferentes placas con el ESP32 y tienen patillajes diferentes. En este montaje estoy usando unas con 30 pines, pero hay otras en las que el número y orden de los pines cambia, así que no es cuestión de “conectar el quinto por arriba a la derecha con el…” no, ni hablar. Hay que fijarse en el pinout de la placa que podamos tener y buscar la correspondencia de pines.

El esquema para la placa de 30 pines que estoy usando es este:

Pinchar en la imagen para ampliar

Que está extraído de aquí:

DOIT ESP32 DEV KIT v1 high resolution pinout and specs

Pero hay unos cuantos modelos de estas plaquitas. Podéis echar un vistazo aquí:

https://randomnerdtutorials.com/getting-started-with-esp32/

Y, en caso de duda, la siguiente librería puede ser útil:

https://www.arduino.cc/reference/en/libraries/board-identify/

Con los DHT22 pasa algo parecido: según el modelo puede cambiar el orden de los pines, así que tampoco se trata de “este al del medio y estotro al del…” Pues no, tampoco. Hay que prestar atención a la serigrafía de los pines para realizar las conexiones adecuadamente.

Dos modelos de DHT22 en los que las patillas llevan diferente orden

En cuanto a los sensores, tanto el modelo «B» como el «C» comparten patillaje. Si vamos al datasheet de ambos, podremos ver esto:

Esta es la distribución viendo el sensor desde abajo, del lado de las patas

Esquema de montaje

Pincha en la imagen para ampliar

Alimentamos, como hemos dicho anteriormente, por el pin Vin y a la vez alimentamos directamente el sensor. El positivo del alimentador, +5V, está en este caso coloreado de rojo y blanco, para diferenciarlo del +3V que sale de la placa y que alimentará a la pantalla y al DHT22.

Hemos usado un led rojo y uno azul. Para el rojo pondremos una resistencia de 100Ω, para el azul no hace falta resistencia. Si queremos usar otros colores deberemos informarnos de su tensión de funcionamiento y colocar la resistencia que corresponda.

Conexiones

Sugiero pinchar en el esquema y ampliarlo para poder ver bien las conexiones. Estas van así:

MH-Z19B: Positivo a Vin de la tarjeta y GND a GND de la tarjeta. Alimentaremos por estos pines a la tarjeta y a la vez al sensor directamente. Para la lectura de datos vamos a usar el Serial 2 de la tarjeta, así que el pin TX del sensor lo conectamos al pin RX2 de la tarjeta, GPIO 16, y el pin RX del sensor al TX2 de la tarjeta, GPIO 17 (Nota: Los GPIO 16 y 17 no aparecen serigrafiados en la placa, pero por el esquema podemos ver que corresponden a RX2 y TX2 respectivamente)

DHT22: El pin de datos del sensor lo conectaremos al GPIO 4 de la tarjeta. El pin Vcc lo conectaremos a la salida de 3V de la tarjeta y GND a GND

Pantalla: La alimentaremos igualmente a 3V, así que VCC a +3V y GND a GND, como antes. El SDA de la pantalla lo conectaremos al GPIO 21 y el SCK al GPIO 22, que por cierto, no están consecutivos, ojo.

Diodos: Las patas cortas son los cátodos y las conectaremos ambas a GND. Las largas, ánodos, las conectaremos: La del led azul al GPIO 32 y la del led rojo al GPIO 33. Como el led azul funciona a 3.6 V no necesita resistencia, sin embargo el led rojo funciona a 1.6V y necesita que entre el ánodo y el GPIO pongamos una resistencia de 100Ω (Bueno, en realidad creo que son unos 90Ω, 100 era la que tenía a mano y funciona. También la resistencia puede conectarse al negativo, el caso es que el circuito tenga el diodo y la resistencia en serie).

Preparando el IDE de Arduino

Vamos a usar el IDE de Arduino para grabar el ESP32. La forma de instalarlo es muy fácil y por si acaso, el link y alguna cosilla más están aquí:

Preparando la placa Arduino

Además de tener instalado ese IDE necesitaremos seleccionar la placa e instalar alguna otra librería que vamos a necesitar. Los pasos son los siguientes:

    • Seleccionar la placa ESP32
    • Instalar la librería DHTesp.h
    • Instalar la librería Adafruit_SSD1306.h
    • Instalar la librería ThingSpeak.h
    • Instalar la librería ErriezMHZ19B.h

Para Instalar la tarjeta ESP32 en el IDE de Arduino tendremos que ir a «Archivo» >> «Preferencias» y en «Gestor de URLs Adicionales de Tarjetas» añadir lo siguiente:

https://dl.espressif.com/dl/package_esp32_index.json

Tras esto, ya podemos ir a «Herramientas» >> «Placa» y en «Gestor de Tarjetas» escribir ESP32 y seleccionarla:

Tras esto ya podemos seleccionar la tarjeta ESP32. En mi caso, la programación la he realizado seleccionando ESP32 Dev Module:

Vamos ahora con la librería para el DHT22. En este caso iremos a «Programa» >> «Incluir Librería» y en «Administrar Bibliotecas» haremos una búsqueda poniendo dhtesp. El autor de la librería es Beegee-Tokyo y es una librería preparada para el ESP32. Es la que he usado y funciona perfectamente. El código fuente y documentación está aquí:

Librería DHTesp de Beegee-Tokyo

Instalamos la pantalla. En este caso vamos igualmente a «Adminstrar Bibliotecas» y en la búsqueda ponemos ssd1306 En este caso la librería a instalar es la de Adafruit:

Librería SSD1306 de Adafruit

Muy probablemente nos diga que necesita instalar otras librerías adicionales. Decimos que instale todas:

Instalar librerías adicionales

Ahora instalamos la librería de ThingSpeak. En este caso la instalaremos desde zip, así que primero habrá que descargarla. Lo hacemos desde esta dirección:

Para descargar, pinchamos en «Code» y luego en «Download ZIP»

No hay que descomprimir el fichero, simplemente, una vez descargado, vamos a «Incluir Librería» pero en este caso seleccionamos «Añadir biblioteca ZIP», buscamos el fichero que acabamos de descargar y lo seleccionamos:

Listo, ya tenemos la librería preparada para usarla en nuestros desarrollos.

Y por último vamos con una librería para tomar los datos del MH-Z19B. Podríamos tomar los datos nosotros mismos, y la verdad es que para solo tomar las lecturas del CO2 estamos metiendo un montón de código de más, pero es que esta librería hace casi de todo, incluso con instrucciones no documentadas, que por supuesto habrán de usarse bajo el criterio y responsabilidad de cada uno, y tengo un par de ideas que llevar adelante y esta librería me facilita muchísimo el trabajo:

A mí me ha parecido una joya. La he probado con un MH-Z19C y también funciona. Por cierto, hablaré de este modelo de sensor en otra entrada porque lo que he visto me ha gustado bastante, más que el «B».

Bien, pues para instalar esta librería procedemos igual que con la de ThingSpeak, acudimos a la dirección mencionada, bajamos el Zip y lo instalamos:

Para los atrevidos, si pinchamos en «master» accederemos a otras ramas. Una de ellas es bastante interesante porque puede darnos la temperatura que está midiendo el sensor. Lo he probado con un MH-Z19B y funciona bien, así que quien no necesite los datos de humedad y tenga un «B» (desconozco «aún» si funciona con otros modelos) puede sacar el nivel de CO2 y la temperatura sólo con el sensor.

Rama que implementa la posibilidad de tomar los datos de temperatura del sensor

El código

Para hacerlo funcionar necesitaremos introducir cuatro datos:

    • Nombre de la WiFi
    • Password
    • Número de canal de ThingSpeak
    • Write API Key del canal

Estos cuatro datos se encuentran al principio del código: «AAAAA», «BBBBB», 11111 y «CCCCC» respectivamente. Han de introducirse todos con comillas excepto el número del canal, que va sin comillas. Tras colocar esos datos, se salva, se graba la tarjeta y a funcionar.

Pero vuelvo a insistir en lo de antes: Mucha precaución y cuando grabemos la tarjeta hay que asegurarse de que el montaje está completamente desconectado de cualquier fuente de alimentación. Y viceversa, cuando vayamos a conectarlo con un alimentador, hay que asegurarse bien de que está completamente desconectado del ordenador.

Aquí está todo el código para poder echarle un ojo. Más abajo se puede descargar en formato .ino para meterlo al IDE de Arduino o editarlo al gusto.

// PROYECTO DE MONITORIZACIÓN DE CONCENTRACIÓN DE
// CO2, TEMPERATURA Y HUMEDAD
// CON ENVÍO DE DATOS A THINGSPEAK

// Placa SP-Cow ESP32-WROOM-32
// IDE Arduino: ESP32 Dev Module

// Sensor de CO2: MH-Z19B - MH-Z14A - MH-Z19C
// Sensor de temperatura y humedad: DHT22
// Pantalla OLED monocromo: GM009605 v4

// Versión 2.0

// https://wampirius.com


#include <SPI.h> // Master In Slave Out
#include <Wire.h> // Comunicación I2C
#include <Adafruit_GFX.h> // Pantalla OLED
#include <Adafruit_SSD1306.h> // Pantalla OLED
#include <DHTesp.h> // Sensor de temperatura y humedad
#include <WiFi.h> // ESP32 WiFi
#include <ThingSpeak.h> // Librería ThingSpeak
#include <ErriezMHZ19B.h> // Librería para pedir y obtener datos del sensor
// Librerías FreeRTOS para separar tareas, etc.
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/semphr.h"

// La mayoría de los códigos que comprende la librería Erriez son compatibles entre los tres sensores enumerados al principio.
// Consultar los datasheet para comprobar la compatibilidad
// Tal como dice su autor: Usar la librería con los códigos no documentados bajo la responsabilidad de cada cual
// https://github.com/Erriez/ErriezMHZ19B

// Pin sensor DHT
#define DHT_IN 4

#define MHZ19B_RX_PIN        16 // Pin serie RX
#define MHZ19B_TX_PIN        17 // Pin serie TX
#define mhzSerial           Serial2 // Usamos tercer hardware serial   

// Led exceso CO2
#define LED_AZUL 32

// Led gran exceso CO2
#define LED_ROJO 33

// Usaremos el led de la placa para el estado de la WiFi
#define LED_BUILTIN 2

// **** INTRODUCIR LOS DATOS DE WIFI Y THINGSPEAK ****
// AAAAA: Nombre de la WiFi
// BBBBB: Password de la Wifi
// 11111: Número del canal de ThingSpeak
// CCCCC: Write API Key del canal de ThingSpeak
// Los valores AAAAA, BBBBB y CCCCC han de ir entre comillas dobles, el valor 11111 ha de ir sin comillas.

// WIFI
const char* ssid = "AAAAA"; // SSID WiFi
const char* password = "BBBBB"; // Password WiFi

WiFiClient client; // Initializa la biblioteca Client

// Datos ThingSpeak
unsigned long myChannelNumber = 11111; // Canal Thingspeak
const char* myWriteAPIKey = "CCCCC"; // ThingSpeak Write API Key

// Datos pantalla
#define ANCHO_PANTALLA 128 // ancho pantalla OLED
#define ALTO_PANTALLA 64 // alto pantalla OLED

// Sensor DHT22
DHTesp dht;

// Create MHZ19B object
ErriezMHZ19B mhz19b(&mhzSerial);

// Semáforo mutex para intercambiar valores entre tareas
SemaphoreHandle_t mutex_v; 

// Variables globales para pasar los datos
float global_temperatura = 0;
float global_humedad = 0;
int global_co2 = 0;

// Objeto de la clase Adafruit_SSD1306
Adafruit_SSD1306 display(ANCHO_PANTALLA, ALTO_PANTALLA, &Wire, -1);

// Creamos las tareas para cada núcleo
TaskHandle_t Task0; //Tarea para el núcleo 0
TaskHandle_t Task1; //Tarea para el núcleo 1 -núcleo por defecto-

void setup() {

  // Initialize serial port to print diagnostics and CO2 output
    mhzSerial.begin(9600, SERIAL_8N1, MHZ19B_RX_PIN, MHZ19B_TX_PIN);

  // Configuramos el pin para el led de la placa
    pinMode(LED_BUILTIN, OUTPUT);

  // Configuramos el pin para el led azul de aviso
    pinMode(LED_AZUL, OUTPUT);

  // Configuramos el pin para el led rojo de mala calidad del aire
    pinMode(LED_ROJO, OUTPUT);

  // Sensor de temperatura
    dht.setup(DHT_IN, DHTesp::DHT22);

  // Iniciar pantalla OLED en la dirección 0x3C
    display.begin(SSD1306_SWITCHCAPVCC, 0x3C);

  // Limpiar buffer
    display.clearDisplay();

  // Tamaño del texto
    display.setTextSize(2);

  // Color del texto
    display.setTextColor(SSD1306_WHITE);

  // Enviar a pantalla
    display.display();

  // Creamos el semáforo
    mutex_v = xSemaphoreCreateMutex();

  // Creamos la tarea para el Core 0
    xTaskCreatePinnedToCore(
                    Task0code,   // Task function.
                    "Task0",     // name of task.
                    10000,       // Stack size of task
                    NULL,        // parameter of the task
                    1,           // priority of the task
                    &Task0,      // Task handle to keep track of created task
                    0);          // pin task to core 0                  

  // Creamos la tarea para el Core 1 -núcleo por defecto-
    xTaskCreatePinnedToCore(
                    Task1code,   // Task function.
                    "Task1",     // name of task.
                    10000,       // Stack size of task
                    NULL,        // parameter of the task
                    2,           // priority of the task
                    &Task1,      // Task handle to keep track of created task
                    1);          // pin task to core 0  

}

// Task0: En esta tarea llevaremos lo relativo a la Wifi
void Task0code(void *parameter){

  // Variables a usar para controlar la conexión y la subida de datos
  int conexion =0; // Lo usaremos para mostrar los intentos de conexión a la WiFi
  unsigned long subidaIntervalo = 300000; // Tiempo en milisegundos para subir los datos a ThingSpeak: 5 minutos (300000)
  unsigned long tiempo; // Variable en la que almacenaremos millis()
  unsigned long subidaUltima = 0; // Variable en la que almacenaremos el valor en milisegundos de la última subida

  //Variables en las que cargaremos los datos:
  float enviar_temperatura;
  float enviar_humedad;
  int enviar_co2;

  // Conexión WiFi
  WiFi.mode(WIFI_STA);
  WiFi.begin(ssid, password);

  ThingSpeak.begin(client); // Inicia ThingSpeak

  // Esperamos conexión con WiFi
  while (WiFi.status() != WL_CONNECTED) 
  {
    // Usaremos el led de la placa para verificar la conexión
  	conexion++;
  	digitalWrite(LED_BUILTIN, HIGH);
  	vTaskDelay(200 / portTICK_PERIOD_MS);
  	digitalWrite(LED_BUILTIN,LOW);
  	vTaskDelay(300 / portTICK_PERIOD_MS);
    // Si al cabo de dos minutos no hemos conseguido conexión, apagamos el led de la placa y trabajaremos sin WiFi matando esta tarea
  	if(conexion > 240){
  		digitalWrite(LED_BUILTIN, LOW);
  		vTaskDelete(Task0);
  	}
  }
  // Cuando conectemos, encendemos el led
  Serial.println(WiFi.localIP());
  digitalWrite(LED_BUILTIN, HIGH);

  // Loop para ejecutar la subida de datos a la nube
  for(;;){

    // Subimos datos a ThingSpeak cada 5 minutos
    // Anotamos el tiempo actual
  	tiempo = millis();
    // Transcurrido el tiempo, llamamos al semáforo, cargamos los valores y efectuamos la subida
  	if((tiempo - subidaUltima) > subidaIntervalo){
  		
  		xSemaphoreTake(mutex_v, portMAX_DELAY);
  		enviar_temperatura = global_temperatura;
  		enviar_humedad = global_humedad;
  		enviar_co2 = global_co2;
  		xSemaphoreGive(mutex_v);
  		
  		ThingSpeak.setField(1, enviar_temperatura);
  		ThingSpeak.setField(2, enviar_humedad);
  		ThingSpeak.setField(3, enviar_co2);

      // Comprobamos la conexión WiFi
  		if (WiFi.status() != WL_CONNECTED) 
  		{
  			digitalWrite(LED_BUILTIN,LOW);
  			conexion = 0;
  			WiFi.disconnect();
  			vTaskDelay(200 / portTICK_PERIOD_MS);
  			WiFi.reconnect();
  			
        // Esperamos dos minutos a conseguir conexión y si no ejecutamos de nuevo el loop sin subir los datos para actualizar estos.
  			while ((WiFi.status() != WL_CONNECTED) && (conexion < 240))
  			{
            // Usaremos el led de la placa para verificar la conexión
  				conexion++;
  				digitalWrite(LED_BUILTIN, HIGH);
  				vTaskDelay(200 / portTICK_PERIOD_MS);
  				digitalWrite(LED_BUILTIN,LOW);
  				vTaskDelay(300 / portTICK_PERIOD_MS);
  				Serial.println(conexion);
  			}
  		}

      // Volvemos a comprobar si estamos conectados y en caso afirmativo subimos los datos y si no volvemos a ejecutar el bucle
  		if (WiFi.status() == WL_CONNECTED) 
  		{
  			digitalWrite(LED_BUILTIN, HIGH);
        // Subimos los datos a ThingSpeak
  			int x = ThingSpeak.writeFields(myChannelNumber, myWriteAPIKey);
  			if (x == 200) {
          // Si la subida ha tenido éxito anotamos el tiempo
  				subidaUltima = millis();
  			}
  		}
  	}

    // Este delay es necesario o se produce un error watchdog 
  	vTaskDelay(10 / portTICK_PERIOD_MS);

  }

}

// Task1: En esta tarea llevaremos lo relativo a toma de datos de los sensores
void Task1code(void *parameter){

  // Arrays y variables para almacenar 10 valores obtenidos por los sensores y sacar la media
    float serie_tem[10] = {0,0,0,0,0,0,0,0,0,0};
    float serie_hum[10] = {0,0,0,0,0,0,0,0,0,0};
    int serie_co2[10] = {0,0,0,0,0,0,0,0,0,0};
    float total_tem = 0;
    float total_hum = 0;
    int total_co2 = 0;
    float media_tem = 0;
    float media_hum = 0;
    int media_co2 = 0;
    int serie_contador = 0;

  // Con las siguientes variables controlaremos si hay algún error en las lecturas.
    char* hum;
    char* tem;
    char* aire;

  // Precalentamos durante 3 minutos el sensor
    int precalporcentaje = 0;
    int precalcontador = 0;
    while (mhz19b.isWarmingUp()) {
        //Serial.println(F("Warming up..."));
        display.clearDisplay();
        display.setCursor(0, 0);
        display.print("Calentando");
        display.setCursor(0, 30);
        display.print("Sensor ");
        display.print(precalporcentaje);
        display.print("%"); 
        display.display();
        precalcontador++;
        if(precalcontador==9){
            precalporcentaje+=5;
            precalcontador=0;
        }
        vTaskDelay(1000 / portTICK_PERIOD_MS);

    };


  // Loop para mostrar los datos por pantalla
    for(;;){

    // CO2
        int co2;
      // Minimum interval between CO2 reads is required
        if (mhz19b.isReady()) {
          // Read CO2 concentration
            co2 = mhz19b.readCO2();
        }

    // Temperatura y humedad
        float h = dht.getHumidity();
        float t = dht.getTemperature();

    // Comprobamos si hemos recibido datos o marcamos error
        if( co2 < 0){
            aire = "ERROR";
        }
        else{
            aire = "OK";
        }     
        if(isnan(h)){
            hum = "ERROR";
        }
        else{
            hum = "OK";
        }
        if(isnan(t)){
            tem = "ERROR";
        }
        else{
            tem = "OK";
        }

    // Mostramos los datos que se van tomando o si se produce algún error
        display.clearDisplay();
        display.setCursor(0, 0);
        display.print("Tmp: ");
        if( tem == "OK"){
            display.print(t);
        }else{
            display.print("ERROR");
        }
        display.setCursor(0, 20);
        display.print("Hum: ");
        if( hum == "OK"){
            display.print(h);
        }else{
            display.print("ERROR");
        }
        display.setCursor(0, 40);
        display.print("CO2: ");
        if( aire == "OK"){
            display.print(co2);
        }else{
            display.print("ERROR");
        }    
        display.display();

    // Calculamos la media de las lecturas, que será lo que subamos a ThingSpeak
    // Comprobamos que la primera lectura tenga datos correctos, si no, esperamos a la siguiente
        if ((serie_contador == 0) && (serie_co2[0] == 0)){
            if ((tem == "OK") && (hum == "OK") && (aire == "OK")){
                serie_tem[serie_contador] = t;
                serie_hum[serie_contador] = h;
                serie_co2[serie_contador] = co2;
                serie_contador++;
            }
        }else{
      //Si no es la primera lectura, cargamos los valores que tengamos, si falla alguno cargaremos el anterior
            int serie_anterior = serie_contador - 1;
            if(serie_contador == 10){
                serie_contador = 0;
            }
            if (tem == "OK"){
                serie_tem[serie_contador] = t;
            }else{
                serie_tem[serie_contador] = serie_tem[serie_anterior];
            }
            if (hum == "OK"){
                serie_hum[serie_contador] = h;
            }else{
                serie_hum[serie_contador] = serie_hum[serie_anterior];
            }
            if (aire == "OK"){
                serie_co2[serie_contador] = co2;
            }else{
                serie_co2[serie_contador] = serie_co2[serie_anterior];
            }
            serie_contador++;
        }

    // Almacenamos la media de las 10 últimas lecturas
    // Si tenemos un mínimo de 10 lecturas sacamos la media, en caso contrario tomamos el último valor
        if(serie_co2[9] > 0){

            for (int indice_datos = 0; indice_datos < 10; indice_datos++){
                total_tem += serie_tem[indice_datos];
                total_hum += serie_hum[indice_datos];
                total_co2 += serie_co2[indice_datos];   
            }
            
            media_tem = total_tem/10;
            media_hum = total_hum/10;
            media_co2 = ((total_co2/10)+0.5);
      // Reseteamos los totales
            total_tem = 0;
            total_hum = 0;
            total_co2 = 0;

        }else{
            if(tem == "OK"){
                media_tem = t;
            }
            if(hum == "OK"){
                media_hum = h;
            }
            if(aire == "OK"){
                media_co2 = co2;
            }
        }

    // Final del loop: Cargar las variables a subir a ThingSpeak en variables globales
    // Llamamos al semáforo, cargamos los valores a las variables globales y liberamos el semáforo
        xSemaphoreTake(mutex_v, portMAX_DELAY);
        global_temperatura = media_tem;
        global_humedad = media_hum;
        global_co2 = media_co2;
        xSemaphoreGive(mutex_v);


        // Esperamos 6 segundos y volvemos a tomar datos
        // (La librería Erriez establece un intervalo mínimo de 5 segundos para pedir datos al sensor)


        // *** LEDS ***
        // Si no queremos usar los leds, descomentar la siguiente línea y comentar el fragmento CONTROL LEDS

        //vTaskDelay(6000 / portTICK_PERIOD_MS);

    // *** CONTROL LEDS  ***
        if (co2<700){
            vTaskDelay(6000 / portTICK_PERIOD_MS);
        }else if (co2<900){
            for (int t=0; t<6; t++){
                digitalWrite(LED_AZUL,HIGH);
                vTaskDelay(200 / portTICK_PERIOD_MS);
                digitalWrite(LED_AZUL,LOW);
                vTaskDelay(200 / portTICK_PERIOD_MS);
                digitalWrite(LED_AZUL,HIGH);
                vTaskDelay(200 / portTICK_PERIOD_MS);
                digitalWrite(LED_AZUL,LOW);
                vTaskDelay(400 / portTICK_PERIOD_MS);
            }
        }else{
            for (int t=0; t<6; t++){
                digitalWrite(LED_AZUL,HIGH);
                vTaskDelay(500 / portTICK_PERIOD_MS);
                digitalWrite(LED_AZUL,LOW);
                digitalWrite(LED_ROJO,HIGH);
                vTaskDelay(500 / portTICK_PERIOD_MS);
                digitalWrite(LED_ROJO,LOW);
            }
        }
    // *** FIN CONTROL LEDS ***

    }
}

// Si lo programamos con el IDE de Arduino, tenemos que meter un loop vacío o dará error
void loop(){

}

 

Aquí está para su descarga. Hay que descomprimir y colocar la carpeta y el archivo donde tengamos nuestros desarrollos de Arduino, abrirlo y colocar los cuatro datos relativos a la WiFi y al canal de ThingSpeak.

Con esto ya se puede realizar el montaje, programar la tarjeta y ponerlo en funcionamiento. Vamos a ello:

Sin conectar todavía nada podemos comprobar si el código se compila bien y sin errores:

Bueno, pues el primer paso está superado. Ahora conectamos la tarjeta con el ESP32 para programarla. Si es la primera vez el sistema realizará la instalación necesaria para asignar un puerto COM. En mi portátil me ha tocado en suerte el COM 3:

El siguiente paso es configurar la velocidad. El ESP32 habla rapidito, así que podemos configurar la velocidad en 115200 tanto en el sistema como en el IDE:

Y con esto ya tenemos todo para grabar la tarjeta, pero un pequeño apunte: En algunas tarjetas es tan sólo dar a «subir» y listo, pero en otras será necesario pulsar el botón «BOOT» y mantenerlo hasta que comience la grabación de datos. También tengo por aquí alguna especialmente dura de mollera, en la que en ocasiones hay que insistir o pulsar el otro botón para forzar un reinicio e inmediatamente pulsar boot.

Botón BOOT: En algunas placas habrá que pulsarlo cuando el IDE comience a «intentar» subir el código tras realizar la compilación y hasta que veamos que en efecto comienza a subirse.

Y ya está. Con todo esto, a funcionar.

Ahora, por si no han llegado todavía las piezas compradas en el «lejano oriente», vamos a comentar un poco el código, por matar el rato…

Comentarios al código

Bien, como ya he comentado al inicio de esta entrada, al principio programé el engendro “a lo Arduino”: un setup, un void y a funcionar. Y funcionaba.

Paralelamente seguí documentándome sobre el ESP32, ya que para mí era completamente nuevo, y me topé con este vídeo:

https://youtu.be/l3LR_ljKEPQ

Así que cuando surgieron los problemas y vi que tenía que retocar el código, pensé que el camino correcto era usar los dos núcleos para separar las tareas. Encontré más información al respecto aquí:

https://savjee.be/2020/01/multitasking-esp32-arduino-freertos/

En las líneas 90 y 91 se creaan las tareas
Estas se definen en el setup utilizando los nombres que les hemos dado antes
Y después cada tarea tiene una primera parte que, por llamarlo de algún modo, sería el equivalente al setup si estuviéramos programando como normalmente hacemos en Arduino
Y luego establecemos un bucle infinito. Aquí lo hemos hecho con for(;;) pero podría hacerse con while (1) o while (true), etc. A gustos. El caso es que este bucle sería el equivalente al habitual loop de Arduino

En cuanto al problema de la WiFi, de tres montajes que puse en funcionamiento sólo uno experimentó problemas, y la programación era la misma. Buscando por la red me quedé con la simple solución que aparece aquí:

https://randomnerdtutorials.com/solved-reconnect-esp32-to-wifi/

Definimos la conexión WiFi en la tarea correspondiente
Y cuando la necesitamos comprobamos su estado y si no estamos conectados desconectamos y reconectamos.

Ni siquiera eché mano de los eventos, simplemente cuando hay que subir datos se comprueba el estado de la WiFi y si es preciso se reconecta. Y parece que ahora funciona. Probé con un montaje y le puteé hasta la saciedad en casa, primero a idea, apagando y encendiendo el router, y después de forma involuntaria, ya que tuve a los tres sensores en la ventana un día entero para que se “autocalibraran” y como allí la señal de la Wifi llegaba llorando, pues iba viendo cómo a ratos parpadeaba un led u otro.

Bien, pues ya tenía una tarea para la lectura de los sensores y mostrar los datos por pantalla y otra para subir estos datos por WiFi y lo que se me ocurriera más adelante. Para pasar los datos de una tarea a otra era lógico: había que usar variables globales.

El vídeo mencionado anteriormente tiene una continuación:

https://youtu.be/_JzfJi2z1CY

Sin embargo, la forma de resolver el problema de que podamos acceder a la vez a la variable global desde las dos tareas no me convence cómo está resuelto en ese vídeo. Vamos a ver, una tarea es la que se encarga de las lecturas y la que tiene que escribir en las variables globales los datos para que estén disponibles cuando la otra tarea tenga que acceder a esas variables para leer los datos y subirlos a la nube. El proceso descrito en el vídeo no garantiza que en un momento dado podamos intentar leer y escribir a la vez en la misma variable y eso no sé qué consecuencias podría tener, pero de pasar algo, seguro que no pasaba nada bueno.

Pero ojo, que esto no quite ni un ápice al mérito de esos vídeos. Gracias a ellos aprendí bastante de cómo poder asignar tareas distintas a cada núcleo del ESP32. Y encima en nuestro idioma, es muy de agradecer.

Buscando y buscando, encontré que la solución era usar “semáforos”. Una buena explicación está aquí:

https://circuitdigest.com/microcontroller-projects/arduino-freertos-tutorial-using-semaphore-and-mutex-in-freertos-with-arduino

Básicamente consiste en lo siguiente: Definimos un semáforo y cuando una tarea lo llama lo pone en rojo, hace lo que sea, y libera al semáforo poniéndolo de nuevo en verde. Si mientras está el semáforo en rojo es llamado por otra tarea, esta se para hasta que encuentra la luz verde, momento en el que toma el semáforo poniéndolo en rojo, realiza lo que tenga que hacer y lo devuelve poniéndolo de nuevo en verde.

Uso de los semáforos: Primero lo creamos
En una tarea lo usamos para guardar los datos en las variables globales
Y en la otra tarea lo usamos para leer los datos de las variables globales

El concepto de semáforo hace que sea muy fácil de entender, ya que todos sabemos cómo funciona un semáforo en la vida real. Así, con una tarea llamamos al semáforo, escribimos en las variables globales y liberamos el semáforo; y con la otra lo mismo para la lectura de esas variables. Problema resuelto.

Aún quedaban por resolver un par de problemas. Con este nuevo script apareció un error: “task watchdog got triggered” creo recordar, que hacía que la placa se reiniciara una y otra vez. Buscando y buscando encontré un “apaño”, que no solución, aquí:

https://github.com/espressif/arduino-esp32/issues/595

En ese hilo se dan varias soluciones. La que ha funcionado es poner un delay de 10 milisegundos al final de la tarea del núcleo 0. No me parece que eso suene a solución, aunque en este desarrollo funciona, todavía tengo pendiente el documentarme más sobre el particular.

O ponemos este delay o se cabrea al perro (aunque no me parece que sea una solución sino un apaño, pero en este script funciona)

Por si acaso, aunque se está ejecutando una tarea, y sólo una, en cada núcleo, pensé que no sobraba el establecer prioridades diferentes a cada una de ellas. No sé si esto hará algo o no, pero no sobra:

Task0 con prioridad 1 y Task1 con prioridad 2. Contra más alto es el número mayor es la prioridad.

Otro de los problemas era que al estar en el IDE de Arduino debemos de poner un loop aunque esté vacío, o nos dará error. Tampoco es algo que me apasione, pero mientras estemos usando este IDE es lo que toca.

Esto del final tampoco me apasiona, pero con el IDE de Arduino es necesario

En cuanto a la toma de datos del sensor, como ya he comentado al principio, inicialmente lo hacía leyendo la señal PWM y estableciendo una simple regla de tres. Como sospecho, y sólo sospecho porque aún no tengo evidencia que lo demuestre, que este método no es del todo fiable, decidí hacer las cosas bien y leer los datos mediante UART.

Tenía dos opciones, o programar unas cuantas líneas o buscar una librería ya hecha. Y encontré esta:

https://github.com/Erriez/ErriezMHZ19B

Estuve haciendo unas cuantas pruebas con la variante que lee la temperatura y funciona de maravilla. Como llevo pensado incorporar alguna funcionalidad más al montaje, me decidí por esta librería que facilita muchísimo la faena.

La única diferencia es que en el desarrollo que he presentado prescindo de SoftwareSerial, que es el usado en los ejemplos de esta librería. Directamente tenemos un Serial 2 sin usar en la tarjeta ESP32, así que hacemos un par de mínimos cambios para usar la librería de Erriez sin necesidad de usar la de SoftwareSerial. Lo vemos a continuación.

En este link tenemos un ejemplo de cómo el autor usa su librería:

https://github.com/Erriez/ErriezMHZ19B/blob/master/examples/ErriezMHZ19BGettingStarted/ErriezMHZ19BGettingStarted.ino

Para evitar el uso de SoftwareSerial hemos realizado los siguientes cambios (ponemos primero en blanco las capturas del código del autor de la librería y debajo los cambios efectuados):

Asignamos los pines correspondientes al Serial 2

 

El objeto lo creamos igual

 

A la hora de inicializar el puerto definimos su funcionamiento.

Y listo, ya tenemos la librería de Erriez lista para usar por el Serial 2 del ESP32.

En cuanto a la toma de datos, esta librería de Erriez establece un intervalo mínimo de 5 segundos para pedir los datos al sensor, en el script propuesto nosotros establecemos las lecturas cada 6 segundos. Además, para subir los datos a ThingSpeak se han creado unos arrays que almacenan las últimas 10 lecturas y la variable global almacena la media de esas lecturas, de forma que a ThingSpeak mandamos la media de lecturas del último minuto.

Cada vez que se toman las lecturas se almacena el dato en el Array, así que, ante una posible desconexión, siempre tendremos disponible el dato de la media del último minuto. Esto es así porque ThingSpeak determina cuándo se ha subido un dato, no mandamos nosotros el dato de fecha y hora. Si en lugar de ThingSpeak subiéramos los datos a una web propia en la que creáramos las gráficas con los datos de tiempo aportados por nosotros podríamos realizar la programación de forma distinta, para que no se perdiera ningún dato y la gráfica no se viera afectada, pero en ThingSpeak no es posible (que yo sepa).

Y bueno, creo que esta entrada ya ha quedado lo suficientemente larga como para proporcionar un plácido sopor al más insomne, así que ponemos el punto y aparte, que no el final, porque tengo en el tintero alguna cosilla curiosa respecto a la auto calibración de los sensores y alguna idea más para la versión 3.0 que será objeto de siguientes entradas.

Pero antes de acabar, aunque me he asegurado de que el código funciona correctamente y está requeteprobado, seguro, seguro, que hay formas más eficientes de llevarlo a cabo. Lo mostrado anteriormente no es más que lo aprendido durante estas últimas semanas, mucha prueba y error, pero al fin y al cabo lo aprendido sobre la marcha y que seguro es mejorable, aunque sin embargo…

Hasta la próxima!

ThingSpeak

Vamos a comenzar por abrir una cuenta gratuita en ThingSpeak y a configurar un canal. Es un proceso bastante sencillo, vamos a ver varias capturas de pantalla y apuntaremos algunas cosas que son optativas y otras que de momento no son necesarias, pero así ya las tenemos todas ahí a mano.

Creación de un canal

https://thingspeak.com/

Para crear una cuenta sólo hay que pinchar en el botón verde o en el monigote de la esquina superior derecha y después en el link de No account? Create one! que hay bajo el campo para introducir el correo electrónico y seguir los pasos. No tiene más misterio.

Creación de un canal

Ya tenemos la cuenta creada, así que es momento de crear nuestro primer canal:

Simplemente pinchar el botón y apareceremos en la siguiente pantalla. Hemos colocado ya unos datos de ejemplo de cómo tiene que quedar, más o menos:

 

 

Para empezar bautizaremos al canal con el nombre que queramos, ese será el que aparezca en pantalla, así que «sensor de casa» no es la opción más atractiva de cara al público, pero como ejemplo vale.

En descripción, pues eso, cuatro palabras que definan lo que estamos haciendo.

Lo importante viene ahora: Hay que crear un campo por cada tipo de dato que queramos mandar. Como puede verse se pueden definir hasta ocho campos. en nuestro caso vamos a mandar datos de temperatura, humedad y CO2, así que activaremos tres campos recordando qué valor pasaremos a qué campo, en el ejemplo vemos que el 1 será temperatura, el 2 humedad y el 3 el de CO2

El resto de apartados son opcionales. En este caso hemos puesto algunas palabras claves en tag y un link a este blog para quien busque más información.

No obstante, el campo «Metadata» sí que puede hacer cosillas interesantes, pero como es completamente optativo lo dejaremos para una posterior entrada llegado el momento.

Una vez creado el canal nos encontraremos en una pantalla como la siguiente:

Por defecto el canal nos aparece como «Privado». Vemos marcado en amarillo que tenemos dos tipos de vistas, una privada, la actual, y otra pública. Mientras tengamos el canal en privado sólo lo podemos ver nosotros. Si queremos pasarlo a Público tendremos que ir a la pestaña Sharing y marcar «Share channel…»

Si hacemos esto tenemos que configurar ambas vistas, la pública y la privada. Si lo deseamos, podemos añadir Widgets (marcado en la imagen anterior en azul) en una vista, en otra o en ambas. Esto lo vemos más adelante, vamos ahora a lo que nos interesa.

API Key

Estos son los datos que vamos a necesitar para mandar información desde nuestro ESP32 a TingSpeak: El Channel ID y la Write API Key, y estos datos se encuentran en la pestaña API Keys:

Copiamos el identificador del canal y la API Key de escritura. ¡OJO! esta clave de escritura es privada y debe guardarse a buen recaudo o cualquiera que la tenga podría escribir en nuestro canal. Vale, ahora mismo seguro que hay alguien pensando -Jodo Wampi, y tú vas, le echas una foto y la cuelgas en Internet, ababol- Ya, bueno. Si pensáis que puede haberse filtrado o a alguien se le ocurre hacer como yo, lo de airearla a los cuatro vientos, es fácil: basta con pulsar en el botón que hay justo debajo de la clave y se generará una nueva invalidando la actual (como podéis sospechar, la de la foto ya está cambiada nada más tomarla). El inconveniente de este desliz, si lo hay, es que nos obligará a reprogramar nuestros dispositivos con la nueva clave, pero nada más (o nada menos).

MQTT API Key

Esto no vamos a usarlo, de momento, pero más adelante, si deseamos mandar los datos a una DB  podemos generar una API para ello y ThingSpeak actuará como broker MQTT permitiendo suscripciones a nuestro canal mediante dicha API.

Para generar dicha API hay que ir a la opción My Profile que encontraremos en el menú de la esquina superior derecha:

Allí vemos que hay unos cuantos datos más o menos privados y que el campo MQTT API Key aparece vacío. Pinchando en el botón junto a él generaremos dicha clave, la apuntaríamos y a seguir. Pero como digo, esto no es necesario ahora, tan sólo lo dejamos apuntado para más adelante.

Widgets

Cada campo va a disponer de su propio gráfico que es ligeramente configurable, pero además del gráfico podemos añadir otros widgets. Repito lo dicho unos párrafos antes: cada configuración de gráficos y/o widgets es independiente en la vista privada y la pública.

Para añadir un widget, primero lo seleccionamos, por ejemplo un reloj:

Y lo configuramos a nuestro gusto:

Damos a salvar y luego lo podemos arrastrar a la posición que queramos, en este ejemplo quedaría así:

Y ya está. ThingSpeak ofrece más opciones, aquí hemos visto las que vamos a necesitar ahora y hemos apuntado alguna más. Para el resto es cosa de cada cual profundizar y configurar a su gusto.

Ah! Una cosa: Estamos con una cuenta gratuita, nos dejan usar 4 canales, cada uno puede tener 8 campos, podemos usarlo todo, pero no nos volvamos locos con el número de datos que enviamos que hay un límite, esto aparece en el apartado My Account:

¡Listo! A la siguiente entrada nos liamos ya con el código.

¡Hasta pronto!

Niveles y alertas

A la hora de programar nuestro montaje había que definir alguna alarma y colocar alguna indicación en ThingSpeak que informara sobre la medida que estábamos obteniendo. ¿Son 735 ppm de CO2 una medida correcta? ¿Y una humedad del 37%? ¿Hace demasiado frío si estamos a 18 ºC?

Una cosa es “lo que nos pueda parecer” y otra “lo que es”, que a veces no siempre coinciden y no se trata de tener ojo de buen cubero. Había que buscar en normativas y consejos de expertos.

Comencemos por el CO2

El Ministerio de Sanidad tiene publicado el Documento Técnico: “Evaluación del riesgo de la transmisión del SARS-CoV-2 mediante aerosoles. Medidas de prevención y recomendaciones”

https://www.mscbs.gob.es/profesionales/saludPublica/ccayes/alertasActual/nCov/documentos/COVID19_Aerosoles.pdf

Respecto a la concentración de CO2, en el punto 5.5.1.1 encontramos

Se podría establecer un umbral de 800-1000 ppm de concentración de CO2 que no debería superarse como garantía de una buena ventilación. Esta concentración de CO2 está muy lejos de ser perjudicial para la salud humana y sólo debe interpretarse como indicador para la necesidad de ventilación.

También, como referencia de concentración de CO2 en el exterior, marca los siguientes valores:

En el exterior, las concentraciones de CO2 son de aproximadamente 420-450 ppm aunque puede variar de entornos urbanos o rurales

Estos niveles de concentración de aire en el exterior no son fijos, tal como pone, y desde luego pueden variar considerablemente en distintos núcleos y zonas urbanas. Si algo tiene de bueno el cierzo de Zaragoza es que hará bajar esa medida los días que le da por soplar, que son unos cuantos al cabo del año. Sin duda alguna, la concentración de CO2 en Zaragoza en uno de esos días será muy distinta a la que pueda mostrar Madrid en uno de los que luce su grisácea “boina”. Si alguien tiene más interés en el tema, Greenpeace tiene un interesante artículo aquí:

https://es.greenpeace.org/es/sala-de-prensa/comunicados/maximo-historico-de-concentraciones-de-co2-en-la-atmosfera/

Siguiendo con las normativas, tenemos el RITE, Reglamento de Instalaciones Térmicas en los Edificios, que está publicado en el BOE. Es el Real Decreto 1027/2007:

https://www.boe.es/eli/es/rd/2007/07/20/1027

Si nos vamos a la Parte II. Instrucciones Técnicas, en el apartado IT 1.1.4.2.2 aparecen las categorías de calidad del aire interior en función del uso de los edificios. Con la siguiente catalogación:

    • IDA 1 (aire de óptima calidad): hospitales, clínicas, laboratorios y guarderías.
    • IDA 2 (aire de buena calidad): oficinas, residencias (locales comunes de hoteles y similares, residencias de ancianos y de estudiantes), salas de lectura, museos, salas de tribunales, aulas de enseñanza y asimilables y piscinas.
    • IDA 3 (aire de calidad media): edificios comerciales, cines, teatros, salones de actos, habitaciones de hoteles y similares, restaurantes, cafeterías, bares, salas de fiestas, gimnasios, locales para el deporte (salvo piscinas) y salas de ordenadores.
    • IDA 4 (aire de calidad baja)

La catalogación IDA 2 encaja en el ámbito universitario donde desarrollo mi trabajo. Ahora, si vamos a la tabla 1.4.2.3 encontramos lo siguiente:

Categoría ppm(*)
IDA 1 350
IDA 2 500
IDA 3 800
IDA 4 1200
(*) Concentración de CO2 (en partes por millón en volumen) por encima de la concentración en el aire exterior

Es decir. Para una catalogación IDA 2 se da por buena una concentración de 500 ppm de CO2 por encima de la concentración de aire en el exterior. Ahora bien, en este reglamento no define dicha concentración exterior.

Podríamos atender al documento del Ministerio de Sanidad y sumar entre 420 ó 450 ppm al valor de la tabla, pero no sería del todo acertado para nosotros por lo siguiente: Nosotros vamos a emplear un sensor autocalibrado MH-Z19B y este sensor “estima” que la concentración de CO2 en el exterior es de 400 ppm (“the zero point”), ver datasheet apartado 8.C Self-calibration:

https://www.winsen-sensor.com/d/files/infrared-gas-sensor/ndir-co2-sensor/mh-z19b-co2-manual(ver1_6).pdf

Bien, pues con estos datos, tendríamos que si nuestro sensor nos marca una concentración de hasta 900 ppm (400 del zero point + 500 que establece IDA 2) el aire se entendería “correcto”.

Ahora bien, el RITE se elaboró hace algunos años y no precisamente en un contexto de pandemia como el actual. Si bien esos valores ahora son plenamente vigentes, hemos de tener en cuenta que una concentración superior de CO2 nos indicará que el aire está “viciado” y lo que en condiciones normales podría provocar una cierta somnolencia, incomodidad, etc. ahora se puede convertir en un contagio por SARS-CoV-2, que es precisamente lo que queremos evitar midiendo la concentración de CO2. Entonces, con los datos que tenemos, creo que se puede concluir que una lectura que nos arroje el sensor por encima de 900 nos debería “obligar” a abrir las ventanas.

Pero aún hay más.

El Dr. José Luis Jiménez, verdadero experto en la materia, recomienda mantener la concentración de CO2 por debajo de 700 ppm. Hay una entrevista interesantísima aquí:

http://www.medicosypacientes.com/articulo/dr-jose-luis-jimenez-un-confinamiento-supone-un-fracaso-de-las-medidas-mas-inteligentes-no

Pues bien, con todos estos datos, vamos a programar un par de alertas en el montaje que nos indiquen cuándo tenemos que abrir las ventanas. Así pues, tomamos como referencia el valor indicado por el Dr. Jiménez y situamos la primera alerta en 700 ppm y para la segunda, tomaremos el valor de 900 según la normativa RITE. Es decir, vamos a movernos entre 700 y 900 en lugar de entre 800 y 1000 que indica Sanidad, pero recordamos que nuestro sensor “cree” que en la calle hay 400 ppm y Sanidad indica que puede haber entre 420 y 450. En la primera alerta haremos destellar el led azul y en la segunda se alternará el azul y el rojo.

De todos modos, este sensor tampoco es el paradigma de la precisión, tiene un margen de error del 5% que es bastante amplio. Lo que se ha pretendido con toda la parrafada anterior es “justificar” de donde han salido los valores para fijar las alertas, pero sería absurdo intentar fijar unos valores exactos con los materiales que vamos a usar. Con un MH-Z19B podemos tener una buena referencia, muy buena referencia, y nos es perfectamente útil para nuestros propósitos, pero nada más. Aunque tampoco necesitamos una precisión de 1 ppm para saber cuándo tenemos que abrir la ventana.

Humedad

En cuanto a la humedad, el Real Decreto 486/1997 por el que se establecen las disposiciones mínimas de seguridad y salud en los lugares de trabajo determina, en el punto 3.b del Anexo III, unos valores comprendidos entre el 30 y el 70 por ciento.

https://www.boe.es/eli/es/rd/1997/04/14/486/con

Sin embargo, en el punto 4.4 del Documento Técnico del Ministerio de Sanidad mencionado anteriormente se fijan unos valores de entre el 40% y el 60%

Por todo lo expuesto, se puede decir que la HR ideal en ambientes interiores estaría entre el 40% al 60%. Estas condiciones pueden ayudar a limitar la propagación y supervivencia del SARSCoV-2 en estos espacios, al tiempo que se minimiza el riesgo de crecimiento de moho y se mantienen las barreras mucosas hidratadas e intactas de las personas

Temperatura

No he encontrado unos valores de temperatura idóneos para la lucha contra la Covid-19, por tanto, los valores que se considerarán como “aconsejables” serán los que marca la normativa de seguridad y salud en los lugares de trabajo, que en el apartado 3.a del Anexo III establece entre 17 y 27 ºC

Adicionalmente, la Universidad de Zaragoza tiene publicada la “Instrucción para la reducción de las consecuencias para la salud de los trabajadores por temperaturas extremas en edificios de la Universidad de Zaragoza”, aquí:

Instrucción para la reducción de las consecuencias para la salud de los trabajadores por temperaturas extremas en edificios de la Universidad de Zaragoza

De los límites mencionados en la instrucción es de donde salen las distintas zonas indicadas en ThingSpeak fuera del rango de entre 17 y 27 ºC, que no se van a detallar aquí dado el restringido ámbito de aplicación de dicho procedimiento.

 

Nota: La imagen de cabecera se ha realizado utilizando como base una imagen de Pexels disponible en Pixabay

 

 

Materiales empleados

En la entrada anterior ya mencionaba los materiales que había ido probando. En esta voy a detallar con los que me he quedado para seguir haciendo pruebas y diseños. El coste de los mismos puede variar bastante según dónde lo compremos, cuestión de buscar. No voy a recomendar enlaces de compra pero voy a poner los precios aproximados más baratos que se pueden encontrar.

Sensor de CO2

Es el corazón del proyecto, lo que vamos a construir gira en torno a él. He optado por unos MH-Z19B que fabrica Winsen:

https://www.winsen-sensor.com/sensors/co2-sensor/mh-z19b.html

Sensor MH-Z19B

Es un sensor NDIR (sensor de infrarrojo no dispersivo) que realiza un autocalibrado y además posee salidas PWM y UART. De momento estoy realizando las lecturas a traves de la salida PWM y eso permite no usar ningún tipo de librería, simplemente realizando los cálculos indicados en el datasheet, una simple regla de tres.

https://www.winsen-sensor.com/d/files/infrared-gas-sensor/ndir-co2-sensor/mh-z19b-co2-manual(ver1_6).pdf

Hay varias versiones: con pines, sin pines y con conector. Mi consejo es pedir un modelo con pines, que facilita el montaje en las protoboards.

Estos sensores pueden encontrarse por unos 18 euros con los gastos de envío incluidos.

Y un aviso: Tal como pone en este blog, los hay falsos:

¿Sensores MH-Z19B falsos?

¡Vaya que si los hay! A mi me colaron uno y puedo corroborar que va como una escopeta de feria y las mediciones que arroja son inferiores a lo normal. No son en absoluto fiables. Mi consejo es que si se recibe un sensor de las características indicadas se devuelva de inmediato, sin más pruebas.

Aquí se puede ver el aspecto comentado en el blog anterior: Ni rebordes ni protuberancias en la chapa, placa completamente negra y hasta la etiqueta está puesta tal cual con un código QR diferente.

Sensor MH-Z19B falso

Espero que los otros dos que tengo sean auténticos… al menos si son falsos están muy bien hechos y parece que funcionan correctamente.

Sensor de temperatura y humedad

En este caso es el conocidísimo DHT22, montado ya en una placa para ahorrarnos trabajo

Sensor DHT22

Este sensor puede salir por unos 3 euros con los gastos.

La placa

La elegida ha sido una basada en el ESP32: Tiene Wifi es dual core y un montón de cosas más que podremos aprovechar en otros proyectos. Además, para todo lo que llevan, son muy económicas. Con gastos de envío la podemos tener por unos 4 euros.

Placa con el ESP32

Pantalla

Los datos los subiremos a la nube, pero para tener una lectura de los valores que se van capturando hace falta una pantalla.

Una de reducido tamaño, bajo consumo y que viene perfecta para nuestros propósitos son unas pantallas OLED de 128×60 con cuatro pines de conexión: GND, VCC, SCL y SDA

Son monocromáticas, disponibles en blanco o azul. También hay en estos mismos colores con la parte superior en amarillo. Hay librerías para usarlas con la ESP32 y por unos 2,50€ gastos incluidos las tenemos en casa.

Pantalla OLED

Diodo led

Para «llamar la atención» si los niveles de CO2 suben por encima de lo aconsejado he puesto un par de diodos led. En el montaje inicial, que es la imagen de cabecera, sólo hay uno de color azul. Estos azules funcionan a 3,6v y como la salida digital del ESP32 arroja 3,3 no vamos a necesitar resistencia y funciona perfectamente.

Son tremendamente baratos, contando los gastos de envío, que cuesta casi lo mismo que el paquete, podemos tener 100 diodos led por 3 euros (sí, cien), bien todos iguales o en colores surtidos.

Diodos LED

Si vamos a usar de otros colores necesitaremos poner una resistencia. El valor de la misma puede obtenerse aquí:

https://www.inventable.eu/paginas/LedResCalculatorSp/LedResCalculatorSp.html

Son muy baratas también, puede adquirirse un surtidillo  por unos dos euros.

Conector Micro-USB

A ver: Podemos alimentar la placa por su conector y utilizar la patilla Vin para obtener «casi» 5v. Pero las protecciones que lleva hacen que se quede en ese «casi», vienen a ser unos 4,5 más o menos, y el sensor con menos de 5 a mi me ha dado algún problemilla.

Conectores Micro-USB

La solución pasa por usar un conector como los de la imagen y alimentar al sensor por un lado y a la placa a través del pin. La única complicación es que habría que tirar de soldador, pero si no se tiene se puede suplir con algo de creatividad, total sólo tenemos que conectar los agujeros de los extremos: GND para masa y VBUS para el positivo. Eso sí, hay que tener la precaución de desconectar completamente la alimentación si vamos a programar la placa. Como conectemos la placa al ordenador estando alimentada por el pin, podemos liarla parda. Ojito.

Se suelen vender varios juntos. Por 73 céntimos he visto que venden cinco y no te cobran gastos (el sobre debe costar más).

Protoboard

Para hacer pruebas o realizar un montaje sin soldadura, como el de la imagen de arriba, necesitamos unas protoboard.

Esta placa, como sus modelos hermanos y la mayoría de las versiones de las ESP8266, tiene un ancho que ocupa casi toda una protoboard normal de lado a lado, dejando sólo una hilera libre e impidiendo, o en todo caso dificultando bastante, las conexiones. Ver las siguientes imágenes:

Por ello lo más práctico es usar estos otros modelos «mini» que se pueden ensamblar entre ellos y como llevan autoadhesivo se pueden fijar en cualquier superficie. Hay de diversos surtidos y precios: todas del mismo color, en colores diversos… Como es un artículo que abulta algo más que un pequeño sensor, aquí nos podemos encontrar con que los gastos de envío superan tranquilamente el coste del artículo. Podemos tener cinco unidades por unos 3 euros.

Cables Dupont

Hay macho-macho, macho-hembra y hembra-hembra, y en distintas cantidades y longitudes. Para un montaje como el de la imagen se necesitan macho-macho y cortos. Por unos 2 euros seguro que se puede tener un surtidillo más que suficiente.

Otra opción es usar cables de un hilo de 22 awg. Esto permite cortarlos a la medida, se pelan las puntas y listo. Son los que lleva el montaje de la imagen de cabecera, por eso lo menciono. Merece la pena si vamos a cacharrear mucho, y en ese caso también merece la pena adquirir un buen pelacables. Aquí ya no pongo precios porque son opciones más caras y seguramente quien lo necesite ya los conoce.

Alimentador de 5 V.

Un cargador de móvil viejo es más que suficiente. Todos los componentes son de un consumo muy reducido, con 500 mA tenemos suficiente y eso lo da, creo, cualquier cargador que podamos tener por un cajón.

También se pueden usar otro tipo de alimentadores para circuitos, pero esto ya lo tocaremos en otra entrada porque con ellos hay que tirar de soldador.

Resumen

Con los componentes mencionados cualquiera puede realizar el montaje, no hace falta soldar nada y la programación se verá más adelante.

Según dónde compremos, los costes pueden variar mucho. Además, hay que vigilar también los gastos de envío. Si tenemos intención de montar más de un aparato o nos ponemos de acuerdo con alguien, podemos comprar algunas piezas juntas y ahorrarnos algo. Pero ojo con las cantidades que pedimos y los importes, no sea cosa que en Aduanas nos espere una sorpresa. Además de que siempre es un riesgo pedir sin conocer al proveedor: Si la mercancía te llega defectuosa, como el sensor que he recibido falso, a pelear si estás dentro del plazo de reclamación y si no a comerte la mercancía con patatas. En pedidos pequeños la pérdida es pequeña, pero para cosas serias yo no usaría esta vía ni loco, y ya no por las aduanas, que además me parece totalmente razonable que se cobren los aranceles, sino porque prácticamente estamos comprando artículos sin garantía.

En cuanto al coste del montaje tal como está en la imagen, con los precios mencionados sería:

    • Sensor de CO2: 18 €
    • Sensor de temperatura y humedad: 3 €
    • Placa ESP32: 4 €
    • Pantalla: 2,50 €
    • Diodo led: 3 € (y nos sobran para el árbol de Navidad)
    • Resistencias: 2 € (y también nos sobran, o incluso nos podemos ahorrar esto)
    • Conector Micro-USB: 0,75 € (por redondear)
    • Protoboard: 3 €
    • Cables: 2 €
    • Alimentador: Ya será raro que no haya ninguno por algún cajón, pero si hubiera que comprarlo lo podemos encontrar por unos 3 € e incluso menos.

TOTAL: 40€ A ojo de buen cubero (41,25 da la suma de las cantidades anteriores). Porque este importe puede variar bastante hacia un lado u otro si compramos en el lejano oriente varias piezas a la vez o si lo hacemos en otros proveedores no tan lejanos y queremos tenerlo todo «mañana», donde esa inmediatez tiene también su coste.

 

Medición del CO2

Medición del CO2 con subida de datos a la nube. Motivación y primeros pasos.

Cuando comenzó a divulgarse información que alertaba sobre que uno de los principales modos de contagio por SARS-CoV-2 era inhalando los ahora ya famosos aerosoles, se puso en evidencia la importancia de la medición de CO2 en espacios cerrados de forma que diera una idea de la necesidad de ventilación o renovación de aire de los mismos.

Obviamente, cuanto más se ventile, mejor, de esto no cabe duda, pero estamos en invierno, y permanecer en una oficina o un aula con las ventanas abiertas permanentemente puede que no sea ni lo más agradable ni tampoco lo más conveniente. Midiendo la concentración de CO2 podemos saber cuán viciado está el aire que respiramos y mantener un equilibrio en la ventilación, maximizando el confort térmico dentro de los parámetros de salubridad del aire, que siguen siendo prioritarios. En román paladino: Ventilar lo adecuado, ni poco ni en exceso.

Con estas premisas pensé en que se podía hacer algún medidor low-cost con Arduino o similares, y me puse a ello. No es una idea original, pude ver que en la red hay numerosos ejemplos e información muy útil.

MQ-135: Un dolor

Comencé las pruebas con un Arduino Uno y un sensor MQ-135. Ese sensor es tremendamente barato y no mide sólo CO2 sino que es sensible a un buen número de sustancias, de hecho, cuando lo quería volver loco le acercaba un trapo con isopropílico a las narices y los valores se disparaban. En un principio, el que fuera sensible a varias sustancias (monóxido y dióxido de carbono, alcoholes, dióxido de nitrógeno, amoniaco, benceno…) no debería de ser un problema en sí mismo: si el sensor arrojaba un valor alto era que lo que estábamos respirando no era muy sano. El problema era que no hay forma de saber qué está excitando el sensor. El sensor arroja una única señal, trasladar eso a un valor de ppm para el CO2 se realiza mediante un algoritmo, pero si excitamos el sensor con otra sustancia nos dará una lectura de CO2 tan elevada como irreal. Quizás para un entorno libre de pandemias, medianamente controlado y «normal» pueda tener algo de utilidad para saber la «calidad del aire», pero ahora que estamos usando hidrogeles, viricidas con base de alcohol, etc. sincera y humildemente, creo que estos sensores no sirven en absoluto para medir la concentración de CO2

Sensor MQ135

Y no acaban ahí los problemas. Ya para empezar hay que “quemarlo”, esto es, tenerlo unas 24 horas a 5 voltios. Luego, tras ese tiempo, hay que “calibrarlo”. La única forma de hacerlo en casa sin más aparataje es ponerlo en la ventana y, “presuponiendo” que el valor que ahí tiene que arrojar es de 400, realizar unos ajustes para “hacer que marque 400” y listo.

Pero no.

Lo calibré unas cuantas veces y en cada una de ellas el ajuste era distinto. Además, cada vez que se apagaba y se volvía a encender había que esperar un buen rato para tener lecturas coherentes. Había ratos en que las mediciones “parecían” correctas, y otros en las que eran de lo más erráticas.

Conclusión: un dolor.

MH-Z19B: Fácil (o casi)

Cuando ya empezaba a hartarme del MQ-135 encargué allende los mares un MH-Z19B, y en cuanto llegó metí a los MQ en un cajón. Como de la noche al día.

Sensor MH-Z19B

Con un par de minutillos que le demos al MH-Z19B lo tenemos mandando valores “correctos”, pero ojo, contextualizando “correctos” en el ámbito en el que lo queremos usar: medir la concentración de CO2 en un espacio cerrado como una oficina, un despacho, un aula, nuestro domicilio, etc. para saber cuándo tenemos que ventilar.

El MH-Z19B puede encontrarse en torno a unos 15 euros actualmente. Porcentualmente es mucho más caro que el MQ-135, pero comparado con otro tipo de sensores este es tremendamente barato, aunque la precisión no es la misma. En el ámbito industrial una precisión de un +-5% es una auténtica aberración, pero para nuestros propósitos cumple perfectamente.

El «casi» viene por la autocalibración de estos sensores, que facilita mucho su uso pero que es un tanto particular. Hablaremos de ello en otras entradas.

Una placa para controlarlos a todos

 

Arduino D1

Las primeras pruebas fueron con una placa de Arduino, pero ya desde antes de empezar con el mecano la idea era conectarlo a una red wifi y mandar los datos a donde fuera. Buscando información encontré que había unas placas basadas en el ESP8266, muy baratas, pequeñas, con wifi y compatibles con el IDE de Arduino, así que empecé las pruebas con ellas porque simplificaban mucho las cosas y funcionaron de maravilla.

Placa Lolin con el ESP8266

Pero al mismo tiempo descubrí otras, las ESP32, que son como las anteriores pero bien vitaminadas. Una excelente comparativa está aquí:

https://www.luisllamas.es/comparativa-esp8266-esp32/

Al final opté por estas últimas porque tienen unas cuantas ventajas y me permitían hacer otra serie de pruebas de las que ya hablaré… si es que consigo algo.

Placa con el ESP32

Los datos en la nube

Para enviar los datos a algún sitio y representarlos en una web hay varias opciones: montar unas tablas y enviar los datos por POST, usar MQTT… O lo más rápido: ThingSpeak

https://thingspeak.com/

ThingSpeak tiene sus limitaciones, pero comparado con la rapidez y comodidad a la hora de poner los datos en una web frente a los anteriores sistemas a muchos les compensará con creces. En mi caso me sirve como punto de partida para tener los datos disponibles y mientras tanto me voy enterando de qué es eso del MQTT y montando un servidor para ello. Esto ya lo dejamos para posteriores entradas.

ThingSpeak tiene una opción gratuita que para proyectos de pequeña envergadura como este cumple perfectamente, así que no nos va a costar ni un duro. Y lo mejor, en unos minutos tenemos un canal, o más, funcionando y mostrando datos. Más no se puede pedir.

Niveles y alertas

Con los datos en la nube ya podemos realizar un seguimiento y tomar nuestras notas en cuanto a situación y medidas. No obstante la prioridad es avisarnos de cuándo tenemos que ventilar. El montaje lleva una pantalla en la que se nos muestran los datos tomados por los sensores cada cinco segundos y además un par de leds que, según el nivel alcanzado, se encienden uno u otro para avisarnos de que debemos abrir las ventanas.

En el apartado de niveles y alertas se referencian varias normativas y documentos que nos indican los valores de salubridad del aire y de confort en cuanto a temperatura y humedad. Sabiendo estos parámetros y haciendo una lectura de los valores de CO2 podremos establecer las alarmas para actuar en consecuencia y regular la necesaria ventilación.

Pero debo insistir y hacer un inciso de lo más serio: Lo principal es la calidad del aire. Si cerramos las ventanas para estar calentitos, alguien puede resultar muerto, literalmente, quizás no nosotros, pero sí un padre, una madre o cualquiera de nuestros mayores.

https://momo.isciii.es/public/momo/dashboard/momo_dashboard.html

Con este tema del COVID, tontadicas las justas.

 

Nota: La imagen de cabecera se ha realizado utilizando como base una imagen de Полина Андреева (mystraysoul) en Pixabay