Home Assistant: controla la calidad del aire con ePaper

En este proyecto construiremos un panel basado en una pantalla ePaper que muestra de una forma clara los valores medidos por un sensor de calidad de aire conectado a Home Assistant.

Introducción

En mi casa tengo instalado Home Assistant, que utilizo para supervisar distintos sensores —como detectores de apertura de puertas, movimiento, temperatura y humedad— y también para controlar numerosas luces distribuidas por toda la vivienda. En una de las habitaciones, que uso como taller y donde guardo elementos como pinturas y solventes, tengo además un sensor de calidad de aire que mide distintas variables del ambiente.

Aunque puedo consultar esas mediciones desde el panel de Home Assistant en una computadora o en el teléfono, pensé que sería mucho más práctico contar con un panel dedicado, siempre visible, que me permitiera ver el estado del ambiente de un vistazo y en cualquier momento.

Para esta tipo de aplicación me pareció que una pantalla ePaper o EPD (Electronic paper display) sería la elección perfecta para un panel como el que tenía en mente. Este tipo de display es ideal para mostrar información que no cambia con demasiada frecuencia, tiene un consumo muy bajo, mantiene la imagen visible incluso sin alimentación gracias a su característica de bistabilidad, y ofrece una excelente legibilidad aun en ambientes con mucha luz.

Maqueta del panel medidor de calidad del aire

El panel además aprovecha una característica sumamente útil de Home Assistant: tiene distintos mecanismos mediante los cuales permite que otras placas y dispositivos interactúen con la información de los sensores que monitorea. Aprovechando esta característica, el panel puede solicitarle valores del sensor de calidad de aire y mostrarlos en el ePaper.

En las secciones siguientes te voy a mostrar como utilizar esta funcionalidad de HA y como construir un panel informativo basado en la placa EE05 de Seeed Studio programada con Seeed_GFX en Arduino.

Midiendo la calidad del aire

Las condiciones del ambiente en el interior de una vivienda, una escuela o el lugar de trabajo influyen directamente en la salud, el bienestar y la comodidad de las personas que permanecen allí durante varias horas.

Existen distintos tipos de medidores, de mayor o menor calidad y precisión, que registran diferentes parámetros relacionados con la calidad del aire.

En mi caso, el medidor que tengo instalado no es de grado profesional ni ofrece mediciones de alta precisión. Todo lo contrario, se trata de un dispositivo de bajo costo, pero que resulta útil como referencia para determinar en general la calidad del aire del ambiente.

Aunque estos valores no deben interpretarse como mediciones profesionales, sí permiten tener una referencia útil del estado general del ambiente. De esta manera, el sensor ayuda a detectar cambios, identificar cuándo conviene ventilar y tomar mejores decisiones para mantener un espacio más saludable y confortable.

Conectando a Home Assistant

El sensor de calidad de aire que tengo instalado es compatible con Tuya y se integra con Home Assistant a través de la integración Tuya, utilizando la red WiFi de mi hogar. De esta manera, el sensor envía sus mediciones a la plataforma Tuya y Home Assistant puede incorporarlas como entidades dentro del sistema.

En Home Assistant, el sensor tiene asociadas cinco entidades principales, que corresponden a los valores medidos o calculados por el equipo. Sus nombres o identificadores son:

  •   sensor.calidad_de_aire_temperature
  •   sensor.calidad_de_aire_humidity
  •   sensor.calidad_de_aire_carbon_dioxide
  •   sensor.calidad_de_aire_formaldehyde
  •  sensor.calidad_de_aire_volatile_organic_compounds

Los valores de estas entidades pueden visualizarse desde el dashboard de Home Assistant, donde cada una se muestra como una tarjeta independiente. Este es un vistazo de mi panel personalizado, que muestra los datos de calidad de aire y el estado de la iluminación de ese ambiente (Taller)

Panel de información de HA

Esta es la forma habitual de visualizar los valores de las entidades de HA, pero ¿cómo podemos hacer para verlos en un dispositivo externo, como un panel con pantall ePaper?

Cada uno de estos mecanismos tiene sus características particulares, ventajas y desventajas. Para este proyecto utilizaremos la primera: REST API, ya que además de ser simple de utilizar, es sumamente potente.

REST API

Una REST API es una tecnología empleada en el desarrollo de aplicaciones Web que permite acceder a un sitio para el intercambio de información. Emplea el protocolo HTTP y alguno de sus métodos (como GET o POST) para consultar o enviar información.

Para acceder a la REST API se debe usar una URL o dirección específica llamada endpoint. Revisando la documentación de Home Assistant, podemos encontrar el endpoint correspondiente, que es:

http://IP_ADDRESS:8123/api/

Donde IP_ADDRESS es la misma IP que se usa para acceder al panel principal de la instalación de Home Assistant.

A ese endpoint se pueden enviar solicitudes HTTP empleando los métodos GET (para consultar por un valor de un sensor por ejemplo), POST (para fijar el valor de una entidad) y DELETE para borrar una entidad.

Para acceder al panel principal de Home Assistant desde un navegador se puede usar la dirección http://homeassistant.local:8123, si la red local permite resolver ese nombre. En algunos casos, especialmente desde equipos conectados por WiFi, esa dirección puede no funcionar. En ese caso se debe usar directamente la dirección IP del equipo donde está instalado Home Assistant, por ejemplo http://192.168.1.50:8123.

En todos los casos, la información que se envía y recibe está en formato JSON. Además, para restringir el acceso a Home Assistant, cada solicitud HTTP debe incluir un header «Authorization: Bearer TOKEN» donde el token es un identificador único que nos identifica como usuario autorizado.

El token («Long-Lived Access Token» o «Tokens de acceso de larga duración») se debe crear en la página de perfil del usuario en el panel de administración de Home Assistant (http://IP_ADDRESS:8123/profile/security):

Haciendo solicitudes

Veamos ahora con mas detalle como hacer solicitudes GET a HA y en que formato nos devuelve su respuesta.

Si hacemos una solicitud GET a la URL

http://IP_ADDRESS:8123/api/api/states/<entity_id>

Home Assistant va a devolver un objeto JSON con la información de estado de la entidad denominada <entity_id>

Esto se puede probar de una manera sencilla desde la linea de comandos con el comando curl. Por ejemplo, si la entidad es el valor de temperatura medido por el sensor (sensor.calidad_de_aire_temperature), el comando curl para solicitar su valor tiene este formato usando Windows:

curl -H "Authorization: Bearer --token--" -H "Content-Type: application/json" http://192.168.100.142:8123/api/states/sensor.calidad_de_aire_temperature

Donde «–token–» es el token de acceso de larga duración generado en el panel de administración, como vimos antes.

Y esta es la respuesta devuelta por HA:

En un formato mas legible la respuesta es:

{
  "entity_id": "sensor.calidad_de_aire_temperature",
  "state": "20.2",
  "attributes": {
    "state_class": "measurement",
    "unit_of_measurement": "°C",
    "device_class": "temperature",
    "friendly_name": "Calidad de aire Temperature"
  },
  "last_changed": "2026-05-29T23:15:39.702639+00:00",
  "last_reported": "2026-05-29T23:15:39.702639+00:00",
  "last_updated": "2026-05-29T23:15:39.702639+00:00",
  "context": {
    "id": "01KSV0B71PNQ42C4HZ22FTA4YG",
    "parent_id": null,
    "user_id": null
  }
}

Dentro de este JSON hay mucha información. Para este proyecto en particular nos interesa sólo el campo state que contiene el valor del parámetro medido y es lo que vamos a mostrar en el epaper.

Si bien es posible obtener el estado de todas las entidades con una sola solicitud GET, el JSON con la respuesta puede ser demasiado extenso si hay muchos sensores y dispositivos conectados. En este proyecto yo opté por enviar cinco solicitudes, una por cada entidad asociada con el sensor de calidad de aire.

El panel con ePaper

Ahora que ya vimos como recuperar valores de entidades en Home Assitant, pongamos manos a la obra y construyamos el panel con pantalla ePaper. Veamos primero los componentes a utilizar y luego el software que los hace funcionar todos juntos.

La pantalla ePaper

Para este proyecto elegí una pantalla ePaper monocromo de 4,26 pulgadas de Seeed Studio. Una de las razones fue su excelente resolución de 800 × 480 píxeles, que permite mostrar información con gran nitidez y contraste. Además, su bajo consumo de energía y su rápido tiempo de actualización la convierten en una opción ideal para paneles informativos, lectores electrónicos y otros dispositivos portátiles.

Pantalla ePaper de 4,26 pulgadas monocromo

La placa EE05

Para controlar la pantalla de 4,26 pulgadas usaré una placa controladora EE05, también de Seeed Studio. La EE05 es una placa driver para pantallas ePaper, con funcionalidades similares a la EE04, pero en un formato más compacto. Está basada en el potente módulo XIAO ESP32-S3 Plus e integra conectividad WiFi y Bluetooth, conector para batería, circuito de gestión de carga e interfaz de 24 pines para pantallas ePaper compatibles.

Además de controlar la pantalla, esta misma placa será la encargada de conectarse con Home Assistant para recuperar los valores medidos por el sensor de calidad de aire. Aprovechando la conectividad WiFi y la capacidad de procesamiento del XIAO ESP32-S3 Plus, la EE05 realizará las solicitudes necesarias a Home Assistant, obtendrá el estado de las cinco entidades correspondientes y luego mostrará esos datos en la pantalla ePaper que tiene conectada.

El panel armado

Esta imagen muestra el panel en funcionamiento antes de montarlo en su gabinete:

Y esta otra con el panel en la primer versión del gabinete impreso en 3D:

El software

El programa del panel está hecho en Arduino empleando la librería Seeed_GFX, una librería desarrollada por Seeed Studio optimizada para soportar sus pantallas ePaper y placas controladoras, como la EE05.

El código completo es el siguiente:

/*
 * Autor: Ernesto Tolocka (Profe Tolocka)
 * Fecha: 2026-May-17
 * Descripción: Muestra valores de calidad del aire desde Home Assistant.
 * Notas: Usa la placa EE05 y una pantalla EPD monocromática de 4,26 pulgadas
 * 
 * Licencia: MIT
 */

#include <WiFi.h>
#include "TFT_eSPI.h"        //Libreria Seeed_GFX para el ePaper

#include <HTTPClient.h>
#include <ArduinoJson.h>    // Usado para interpretar JSON

#include <esp_sleep.h>    // Para sueño profundo

#include "image.h"   // Imagen de fondo

EPaper epaper;

// Nombre y contraseña de la red WiFi
const char* ssid = "--tu red--";
const char* password = "--tu contraseña--";

// Tiempo de actualización (minutos)
const int RefreshTime = 15;

// Token proporcionado por Home Assistant
const char* Token = "PEGAR_AQUI_EL_TOKEN_DE_ACCESO_DE_LARGA_DURACION_DE_HOME_ASSISTANT"; 

// Función para poner el ESP32 en sueño profundo
void sleepSeconds (uint32_t s) {

  if (s < 60) s = 60;    // Por las dudas

  WiFi.disconnect(true);
  WiFi.mode(WIFI_OFF);
  
  esp_sleep_enable_timer_wakeup((uint64_t)s * 1000000ULL);
  esp_deep_sleep_start();
  delay ((uint64_t)s * 1000000ULL);
}

// Función para conectarse a WiFi con tiempo máximo de espera
bool connectWiFi(uint32_t timeoutMs) {
  
  WiFi.mode(WIFI_STA);
  WiFi.begin(ssid, password);

  uint32_t t0 = millis();
  while (WiFi.status() != WL_CONNECTED && (millis() - t0) < timeoutMs) {
    delay(250);
    Serial.print(".");
  }
  Serial.println();
  return WiFi.status() == WL_CONNECTED;
}

// Función para obtener estados desde Home Assistant
String getHAState(String entity_id) {
  if (WiFi.status() != WL_CONNECTED) {
    Serial.println("Error! WiFi no conectado");
    return "--";
  }

  WiFiClient client;
  HTTPClient http;

  String url = "http://192.168.100.142:8123/api/states/" + entity_id;

  Serial.println();
  Serial.print("Enviando solicitud a: ");
  Serial.println(url);

  http.setTimeout(5000); // 5 segundos

  if (!http.begin(client, url)) {
    Serial.println("Error en http.begin()");
    return "--";
  }

  http.addHeader("Authorization", String("Bearer ") + Token);
  http.addHeader("Content-Type", "application/json");
  
  int httpCode = http.GET();

  Serial.print("HTTP code: ");
  Serial.println(httpCode);

  if (httpCode != HTTP_CODE_OK) {
    String errBody = http.getString();
    Serial.println("Error body:");
    Serial.println(errBody.substring(0, 200));

    http.end();
    return "--";
  }

  String payload = http.getString();

  Serial.print("Tamaño del payload: ");
  Serial.println(payload.length());

  http.end();

  JsonDocument doc;

  DeserializationError error = deserializeJson(doc, payload);

  if (error) {
    Serial.print("Error al parsear JSON: ");
    Serial.println(error.c_str());
    return "--";
  }

  String value = doc["state"].as<String>();

  Serial.print("Valor de State: ");
  Serial.println(value);

  return value;
}

void setup() {
  Serial.begin(115200);

  Serial.println ("\nArrancando..");

  if (!connectWiFi(30000)) {
    Serial.println("WiFi no conecta. Yendo a dormir por 5 min.");
    sleepSeconds(300);
    return;
  }
  Serial.println("WiFi conectado!");

  // Configura e inicializa la pantalla
  epaper.begin();
  epaper.fillScreen(TFT_WHITE);          // Limpia la pantalla
  epaper.setTextFont (6); 
  epaper.setRotation(0);            
  epaper.setTextDatum(MC_DATUM);         // Establece alineación centrada

  epaper.setTextSize (1);

  // Carga la imagen de fondo
  epaper.drawBitmap(0, 0, background, 800, 480, TFT_BLACK);


  // Accede a la REST API de Home Assistant
  String temp = getHAState("sensor.calidad_de_aire_temperature");
  String hum  = getHAState("sensor.calidad_de_aire_humidity");
  String co2  = getHAState("sensor.calidad_de_aire_carbon_dioxide");
  String hcho = getHAState("sensor.calidad_de_aire_formaldehyde");
  String voc  = getHAState("sensor.calidad_de_aire_volatile_organic_compounds");

 
  epaper.drawString (temp, 90, 290);
  epaper.drawString (hum,  244, 290 );
  epaper.drawNumber (co2.toInt(),  398, 290);
  epaper.drawString (hcho, 556, 290 );
  epaper.drawString (voc,  710, 290 );

  epaper.update ();

  Serial.println ("A dormir...");

  // Demora para permitir el acceso desde el IDE si fuera necesario.
  delay (10000);

  sleepSeconds (RefreshTime * 60);
}

void loop() {

  // No hace nada

}

Analicemos con mas detalles las distintas secciones de este código:

Los include

#include <WiFi.h>
#include "TFT_eSPI.h"        //Libreria Seeed_GFX para el ePaper

#include <HTTPClient.h>
#include <ArduinoJson.h>    // Usado para interpretar JSON

#include <esp_sleep.h>    // Para sueño profundo

#include "image.h"   // Imagen de fondo

En el inicio tenemos las directivas #include, para cargar distintas librerías, para el WiFi, Seeed_GFX para el control del ePaper, HTTPClient para hacer solicitudes al servidor, ArduinoJson para tratar con las respuestas del servidor, e image, que es el archivo que contiene la definición de la imagen usada como fondo del panel.

La imagen de fondo

La imagen de fondo del panel fué creada como un BMP de 16 niveles de gris, como el que se vé a continuación.

Luego convertí este BMP en un archivo de cabecera usando la herramienta de dithering de SenseCraft HMI. Con esta herramienta primero reduje la cantidad de colores a 1 bpp (bits per pixel) aplicando el algoritmo de dithering Floyd-Steinmberg y luego generé el archivo de cabecera que contiene la descripción de la imagen.

Este es un fragmento del archivo de cabecera (header) generado:

El driver

Aunque no aparezca de manera explícita en las directivas #include, la librería Seeed_GFX requiere que creemos el archivo driver.h con las definiciones de la pantalla ePaper y la placa controladora utilizada. Para ello usé la herramienta on-line provista por Seeed Studio:

Luego copié el código generado en un nuevo archivo de nombre «driver.h»

#define BOARD_SCREEN_COMBO 506 // 4.26 inch monochrome ePaper Screen (SSD1677)
#define USE_XIAO_EPAPER_DISPLAY_BOARD_EE05

Si querés aprender mas sobre el uso de imágenes monocromo y color en pantallas e-Paper, mirá mi curso grauito Dominando las pantallas de papel electrónico.

Declaraciones

En esta parte se crea el objeto epaper, que va a estar asociado a la pantalla, se definen las constantes que almacenan el nombre de usuario y contraseña de la red WiFi a utilizar, el tiempo de refresco del panel, es decir cada cuantos minutos accede a HA para actualizar los datos y el Token que necesitamos para autenticarnos.

EPaper epaper;

// Nombre y contraseña de la red WiFi
const char* ssid = "--tu red--";
const char* password = "--tu contraseña--";

// Tiempo de actualización (minutos)
const int RefreshTime = 15;

// Token proporcionado por Home Assistant
const char* Token = "PEGAR_AQUI_EL_TOKEN_DE_ACCESO_DE_LARGA_DURACION_DE_HOME_ASSISTANT"; 

Funciones

A continuación en el código se definen algunas funciones.

Tal vez la función más importante del programa sea getHAState(), ya que es la encargada de consultar la REST API de Home Assistant para obtener el estado de una entidad determinada.

La función recibe como parámetro el nombre de la entidad, por ejemplo sensor.calidad_de_aire_temperature, y construye con ese dato la URL del endpoint correspondiente. Luego verifica que la placa esté conectada a la red WiFi y realiza una solicitud HTTP GET a Home Assistant.

Para que la consulta sea aceptada, la solicitud incluye el token de acceso mediante el encabezado Authorization. Si Home Assistant responde correctamente, la función recibe un objeto JSON, lo analiza y busca el campo state, que contiene el valor actual de la entidad.

Finalmente, devuelve ese valor como un String. Si ocurre algún error, como falta de conexión WiFi, error en la solicitud HTTP o un problema al interpretar el JSON, la función devuelve "--".

// Función para obtener estados desde Home Assistant
String getHAState(String entity_id) {
  if (WiFi.status() != WL_CONNECTED) {
    Serial.println("Error! WiFi no conectado");
    return "--";
  }

  WiFiClient client;
  HTTPClient http;

  String url = "http://192.168.100.142:8123/api/states/" + entity_id;

  Serial.println();
  Serial.print("Enviando solicitud a: ");
  Serial.println(url);

  http.setTimeout(5000); // 5 segundos

  if (!http.begin(client, url)) {
    Serial.println("Error en http.begin()");
    return "--";
  }

  http.addHeader("Authorization", String("Bearer ") + Token);
  http.addHeader("Content-Type", "application/json");
  
  int httpCode = http.GET();

  Serial.print("HTTP code: ");
  Serial.println(httpCode);

  if (httpCode != HTTP_CODE_OK) {
    String errBody = http.getString();
    Serial.println("Error body:");
    Serial.println(errBody.substring(0, 200));

    http.end();
    return "--";
  }

  String payload = http.getString();

  Serial.print("Tamaño del payload: ");
  Serial.println(payload.length());

  http.end();

  JsonDocument doc;

  DeserializationError error = deserializeJson(doc, payload);

  if (error) {
    Serial.print("Error al parsear JSON: ");
    Serial.println(error.c_str());
    return "--";
  }

  String value = doc["state"].as<String>();

  Serial.print("Valor de State: ");
  Serial.println(value);

  return value;
}

Preparaciones

En esta parte del código comiena la función setup. Aquí se hacen algunas preparaciones: se inicializa la consola serie para la depuración, se conecta con el WiFi y se prepara la pantalla ePaper. Se configuran distintos parámetros de la pantalla y se carga en la memoria del driver la imagen de fondo, aunque todavía no se muestra.

void setup() {
  Serial.begin(115200);

  Serial.println ("\nArrancando..");

  if (!connectWiFi(30000)) {
    Serial.println("WiFi no conecta. Yendo a dormir por 5 min.");
    sleepSeconds(300);
    return;
  }
  Serial.println("WiFi conectado!");

  // Configura e inicializa la pantalla
  epaper.begin();
  epaper.fillScreen(TFT_WHITE);          // Limpia la pantalla
  epaper.setTextFont (6); 
  epaper.setRotation(0);            
  epaper.setTextDatum(MC_DATUM);         // Establece alineación centrada

  epaper.setTextSize (1);

  // Carga la imagen de fondo
  epaper.drawBitmap(0, 0, background, 800, 480, TFT_BLACK);

Lectura de valores e impresión

En este último bloque, se hacen cinco solicitudes a la REST API pidiendo los valores de las cinco identidades del sensor de calidad de aire empleando la función getHAState definida mas arriba. Luego se cargan esos valores en la pantalla. El método epaper.update hace un refresco total, con lo que se muestran tanto la imagen de fondo como los valores de las entidades.

// Accede a la REST API de Home Assistant
  String temp = getHAState("sensor.calidad_de_aire_temperature");
  String hum  = getHAState("sensor.calidad_de_aire_humidity");
  String co2  = getHAState("sensor.calidad_de_aire_carbon_dioxide");
  String hcho = getHAState("sensor.calidad_de_aire_formaldehyde");
  String voc  = getHAState("sensor.calidad_de_aire_volatile_organic_compounds");

 
  epaper.drawString (temp, 90, 290);
  epaper.drawString (hum,  244, 290 );
  epaper.drawNumber (co2.toInt(),  398, 290);
  epaper.drawString (hcho, 556, 290 );
  epaper.drawString (voc,  710, 290 );

  epaper.update ();

A dormir…

Una vez completado el refresco de la pantalla, se pone al ESP32_S3 de la EE05 en modo de bajo consumo por un tiempo definido por el valor de RefreshTime en minutos. Gracias a la propiedad de bistabilidad del ePaper, la información se mantiene sin cambios hasta la próxima actualización, con un consumo mínimo.

El delay de 10 segundos es útil mientras se trabaja en el programa, porque permite retomar el control de la placa desde el IDE antes de que entre en el modo de bajo consumo.

Y luego se va a dormir

   Serial.println ("A dormir...");

  // Demora para permitir el acceso desde el IDE si fuera necesario.
  delay (10000);

  sleepSeconds (RefreshTime * 60);
}

Eso es todo, en loop no hace nada

Conclusiones

En este proyecto construimos un panel sencillo, pero muy útil, para visualizar de un vistazo los valores medidos por un sensor de calidad del aire integrado con Home Assistant. A partir de esos datos, es posible tener una referencia rápida del estado general de un ambiente y detectar cuándo conviene ventilar o mejorar sus condiciones.

A lo largo del artículo vimos primero qué parámetros mide el sensor y cómo esos valores aparecen dentro de Home Assistant como entidades independientes. Luego repasamos algunas de las opciones disponibles para acceder a esa información desde dispositivos externos y nos enfocamos especialmente en el uso de la REST API, realizando solicitudes HTTP GET a los endpoints correspondientes y utilizando un token de autorización para acceder de forma segura a los datos.

También analizamos los criterios utilizados para seleccionar el hardware del proyecto. La pantalla ePaper resulta ideal para este tipo de paneles informativos, ya que ofrece muy buena legibilidad, bajo consumo y la posibilidad de mantener la imagen visible incluso sin alimentación. En este punto, la placa EE05 cumple un papel central: no solo actúa como controladora de la pantalla ePaper de 4,26 pulgadas, sino que además, gracias al módulo XIAO ESP32-S3 Plus integrado, aporta conectividad WiFi, capacidad de procesamiento, gestión de batería y un formato compacto especialmente práctico para desarrollar este tipo de aplicaciones.

Finalmente, revisamos en detalle el software desarrollado en Arduino con Seeed_GFX, desde la conexión WiFi y la consulta a Home Assistant, hasta el procesamiento de la respuesta JSON, la extracción del campo state, la actualización de la pantalla y el uso del modo de bajo consumo.

El resultado es un panel dedicado, autónomo y fácil de consultar, que combina las posibilidades de Home Assistant con las ventajas de las pantallas ePaper y la practicidad de la placa EE05. Además de resolver una necesidad concreta, este proyecto muestra una forma simple de crear interfaces físicas para visualizar información del hogar inteligente sin depender siempre de una computadora o un teléfono.

Espero que este proyecto te haya gustado y hayas aprendido cosas nuevas. Si es así, compartelo.

Nos vemos! 🚀

Deja un comentario

Este sitio usa Akismet para reducir el spam. Aprende cómo se procesan los datos de tus comentarios.