El próximo feriado. Panel inteligente con e-Paper. Parte 3

En esta tercera y última parte de esta serie de artículos vamos a ver como se integran todas las piezas que vimos en las partes anteriores en el código final del panel inteligente.

Principio de funcionamiento

En la primera y segunda parte de esta serie de artículos te expliqué en qué consiste y cómo usar el protocolo NTP para sincronizar el RTC del ESP32 y cómo usar el formato epoch para comparar dos fechas de manera sencilla. También qué es el HTTPS y en que se diferencia del HTTP y como hacer una solicitud GET para obtener información de un servidor Web. Y también vimos que es JSON y cómo extraer información de un texto en ese formato a través del proceso de deserealización.

En esta última parte vamos a usar todos esos conocimientos para darle forma final al indicador del próximo feriado, uniendo todas las piezas de este proyecto.

Si recuerdas, en la primer parte vimos a grandes rasgos cómo seria el funcionamiento del panel:

Este funcionamiento se explica con un poco mas de detalle en el siguiente diagrama de flujo:

Código

Este es el código completo del panel. Es un poco extenso, pero no te preocupes. Más abajo lo iremos analizando parte por parte.

/*
 * Autor: Ernesto Tolocka (Profe Tolocka)
 * Date: 2026-2-15
 * Descripción: Muestra el próximo dia feriado en pantalla
 * Notas: Usa la placa EE04 y una EPD de 5.83-inch Monocromo
 * 
 * Licencia: MIT
 */

#include <WiFi.h>
#include <time.h>
#include "TFT_eSPI.h"

#include <WiFiClientSecure.h>
#include <HTTPClient.h>

#include <ArduinoJson.h>    // Para parsear JSON

#include <esp_sleep.h>    //Para deep sleep

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

EPaper epaper;

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


//Localizacion
//Modifica estos valores de acuerdo a tu país
const int timeZone = -3;      //Timezone local
const char* country = "AR";   //Código de país. Mira https://date.nager.at/Country

//Endpoint de Date.Nager
String endpoint = String("https://date.nager.at/api/v3/PublicHolidays/2026/") + country;

//Función para pasar de fecha en formato texto a epoch
time_t stringDateToEpoch (const char* stringDate) {

  //Pasa un string como 2026-02-02 a epoch

  int year,month, day;
  struct tm timeDate = {};   //Arranca vacío

  sscanf(stringDate, "%d-%d-%d", &year, &month, &day);

  timeDate.tm_sec=0;
  timeDate.tm_min=0;
  timeDate.tm_hour=0;
  timeDate.tm_mday=day;
  timeDate.tm_mon=month-1;
  timeDate.tm_year=year-1900;
  
  Serial.println(&timeDate, "Time: %H:%M:%S");
  Serial.println(&timeDate, "Date: %Y-%m-%d");

  //Pasar la estructura a epoch
  return (mktime(&timeDate));

}

// Funcion para limpiar acentos o tildes
String quitarAcentos(String s) {
  s.replace("á", "a");
  s.replace("é", "e");
  s.replace("í", "i");
  s.replace("ó", "o");
  s.replace("ú", "u");
  s.replace("ñ", "n");

  s.replace("Á", "A");
  s.replace("É", "E");
  s.replace("Í", "I");
  s.replace("Ó", "O");
  s.replace("Ú", "U");
  s.replace("Ñ", "N");

  return s;
}

// Función para llevar el ESP32 a deep sleep
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();
}

// Función para conectar al Wifi con timeout
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;
}


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

  if (!connectWiFi(30000)) {
    Serial.println("WiFi no conecta. A dormir 5 min.");
    sleepSeconds(300);
    return;
  }
  Serial.println("WiFi connected!");


  //Configurar e inicializar pantalla
  epaper.begin();
  epaper.fillScreen(TFT_WHITE);          // Borrar pantalla
  epaper.setFreeFont(&Yellowtail_32);    // Seleccionar tipo de letra
  epaper.setRotation(0);            
  epaper.setTextDatum(MC_DATUM);         // Configurar centrado

  epaper.setTextSize (1);
  epaper.drawString ("Dias hasta el Proximo Feriado:", epaper.width()/2,98);

  // Cargar imagen de fondo
  epaper.drawBitmap(0, 0, holidayBack, 648, 480, TFT_BLACK);

  // Mostrar todo
  epaper.update ();

  //Acceder al servidor NTP y obtener fecha y hora actual (en RTC)
  configTime(timeZone * 3600, 0, "pool.ntp.org");

  // Leer la hora en timeinfo
  struct tm timeinfo;
  if (!getLocalTime(&timeinfo, 10000)) {
    Serial.println("Error al leer RTC");
    sleepSeconds (300);
    return;    //Solo por claridad
  }

  Serial.println(&timeinfo, "Time: %H:%M:%S");
  Serial.println(&timeinfo, "Date: %Y-%m-%d");

  // Pasar a epoch
  time_t epochNow = mktime(&timeinfo);

  // Calcular cuanto falta para la siguiente medianoche

  struct tm tomorrow = timeinfo;

  // Llevar a medianoche de mañana
  tomorrow.tm_sec  = 0;
  tomorrow.tm_min  = 0;
  tomorrow.tm_hour = 0;
  
  tomorrow.tm_mday +=1; 
  
  time_t epochTomorrow = mktime(&tomorrow);

  int32_t seconds2Tomorrow = (int32_t)(epochTomorrow - epochNow);

  Serial.print ("Segundos hasta mañana:");
  Serial.println (seconds2Tomorrow);

  // Reajusto la hora actual en epoch desde medianoche
  timeinfo.tm_sec=0;
  timeinfo.tm_min=0;
  timeinfo.tm_hour=0;

  // Pasar a epoch
  time_t epochToday = mktime(&timeinfo);

  //Establece una fecha para hacer pruebas
  //timeinfo.tm_mday=30;
  //timeinfo.tm_mon=12-1;
  
  //Acceder al servidor de Nager para obtener el listado de feriados

  WiFiClientSecure client;
  client.setInsecure(); // HTTPS cifrado, sin validar certificado

  HTTPClient http;

  if (!http.begin(client, endpoint)) {
    Serial.println("http.begin() failed");
    sleepSeconds (300);
    return;    //Solo por claridad
  }

  int httpCode = http.GET();
  Serial.printf("HTTP code: %d\n", httpCode);

  if (httpCode != HTTP_CODE_OK) {
    Serial.println("Respuesta del server (primeros 200 chars):");
    String errBody = http.getString();
    Serial.println(errBody.substring(0, 200));
    http.end();
    
    sleepSeconds (300);
    return;    //Solo por claridad
  }

  // Descargar todo el JSON
  String payload = http.getString();
  http.end();

  Serial.printf("Payload length: %d bytes\n", payload.length());

  JsonDocument doc;

  DeserializationError error = deserializeJson(doc, payload);

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

    sleepSeconds (300);
    return;    //Solo por claridad
  }

  //Explorar el JSON calculando diferencia de dias
  //El primero que de mayor a 0 es el siguiente

  bool found = false; 

  for (int i = 0; i < doc.size(); i++) {
    const char* holiday = doc[i]["date"];
    const char* name = doc[i]["name"];

    Serial.println(holiday);
    Serial.println(name);

    int epochDif = stringDateToEpoch (holiday) - epochToday;
    Serial.println (epochDif);

    if (epochDif >= 0) {   //Si es hoy, indica 0 dias

      int daysDif = epochDif / 86400;  //Seconds in a day
      Serial.println (daysDif);
      String holidayName = doc[i]["localName"];
   
      // Selecciona tamaño y font para la cantidad de días
      epaper.setTextSize (3);
      epaper.setTextFont (7);
    
      epaper.drawNumber (daysDif, epaper.width()/2, 220);

      epaper.setTextSize (1);
      epaper.setFreeFont(&Yellowtail_32);    // Seleccionar tipo de letra

      //Muestra solo los primeros 30 caracteres
      epaper.drawString (quitarAcentos(holidayName.substring (0,38)), epaper.width()/2, 314);
      epaper.update ();

      found = true;
      break;
    }
  }

  if (!found) {
    //No hay mas feriados :(
    
    // Selecciona tamaño y font para la cantidad de días
    epaper.setTextSize (3);
    epaper.setTextFont (7);
    epaper.drawString ("--", epaper.width()/2, 220);

    epaper.setTextSize (1);
    epaper.setFreeFont(&Yellowtail_32);    // Seleccionar tipo de letra
    epaper.drawString ("No hay mas feriados!", epaper.width()/2, 314);

    epaper.update ();
  }

  //Todo listo, ir a dormir hasta mañana

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

}

void loop() {

  //No hace nada

}

Inicialización

En las primeras líneas, como siempre, tenemos los #include para agregar todas las librerías necesarias. También se incluye «bitmap.h» que es la imagen de fondo del panel.

Para el control de la EPD usaremos la librería Seeed_GFX, porque está optimizada para la placa EE04 y la pantalla que utilizaremos.

#include <WiFi.h>
#include <time.h>
#include "TFT_eSPI.h"

#include <WiFiClientSecure.h>
#include <HTTPClient.h>

#include <ArduinoJson.h>    // Para parsear JSON

#include <esp_sleep.h>    //Para deep sleep

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

A continuación se crea el objeto epaper que se usará para controlar la pantalla y se definen las credenciales para acceder al WiFi, esto es el nombre de la red y su contraseña. Recuerda reemplazar «yourSSID» por el nombre de tu red y «yourPassword» por su contraseña.

EPaper epaper;

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

Luego se definen dos valores relacionados con tu localización, que deberás cambiar según el país donde vivas.

El timezone es la zona horaria y puedes encontrarla en este sitio. Y country contiene un código de país que es empleado por el sitio Nager.date para suministrate el listado de feriados específico de ese país. El listado de paises disponibles y el código de cada uno se encuentra en esta página.

En mi caso, timeZone = -3 y el código de país es «AR» (Argentina).

//Localizacion
//Modifica estos valores de acuerdo a tu país
const int timeZone = -3;      //Timezone local
const char* country = "AR";   //Código de país. Mira https://date.nager.at/Country

La inicialización termina definiendo el endpoint. Se llama endpoint a la URL específica a la que se le hacen solicitudes a un sitio web. Es la dirección exacta del recurso o servicio dentro de un servidor: no solo el sitio web, sino el punto preciso donde nuestra aplicación envía datos o pide información.

Según la documentación de Nager, el endpoint tiene este formato:

https://date.nager.at/api/v3/publicholidays/{Year}/{Country code}

Por lo tanto, se hace la definición con un string fijo que incluye el año (2026) y se le concatena el código de pais definido un poco antes.

//Endpoint de Date.Nager
String endpoint = String("https://date.nager.at/api/v3/PublicHolidays/2026/") + country;

Definición de funciones

A continuación en el código se definen tres funciones:

  • stringDateToEpoch: pasa un valor de fecha en texto, como «2026-2-16» al formato epoch.
  • quitarAcentos: reemplaza las vocales acentuadas por vocales sin acentos, así como «ñ» por «n» porque no están definidas en el font empleado para mostrar textos en la pantalla.
  • sleepSeconds: Pone al ESP32 en modo deep sleep por un tiempo determinado en segundos.
  • connectWiFi: Conecta a la red WiFi con timeout.

A medida que las usemos iremos viendo más en detalle la utilidad de estas funciones.

Conexión WiFi

Ya dentro de setup el siguiente paso es conectar a la red WiFi. Esto se hace llamando a la funcion connectWiFi definida mas arriba con un timeout de 30 segundos. Si la conexión tiene exito se muestra el mensaje «WiFi connected» pero si pasado el timeout no se logró la conexión, se pone el ESP32 en modo de bajo consumo por 5 minutos (300 segundos) para que sea reiniciado y vuelva a intentarlo.

Esta estrategia se usará mas adelante en otras situaciones que pueden generar una condición de error.

La instrucción return que cierra el bloque en realidad no funciona nunca, pero se la incluye por una formalidad.

  if (!connectWiFi(30000)) {
    Serial.println("WiFi no conecta. A dormir 5 min.");
    sleepSeconds(300);
    return;
  }
  Serial.println("WiFi connected!");

Inicialización del EPD

Lo que sigue es inicializar la pantalla e-Paper, borrar el contenido que pueda haber quedado, seleccionar el tipo de letra Yellowtail_32, mostrar el texto «Dias hasta el Proximo Feriado:» y cargar la imagen de fondo.

  //Configurar e inicializar pantalla
  epaper.begin();
  epaper.fillScreen(TFT_WHITE);          // Borrar pantalla
  epaper.setFreeFont(&Yellowtail_32);    // Seleccionar tipo de letra
  epaper.setRotation(0);            
  epaper.setTextDatum(MC_DATUM);         // Configurar centrado

  epaper.setTextSize (1);
  epaper.drawString ("Dias hasta el Proximo Feriado:", epaper.width()/2,98);

  // Cargar imagen de fondo
  epaper.drawBitmap(0, 0, holidayBack, 648, 480, TFT_BLACK);

  // Mostrar todo
  epaper.update ();

La imagen de fondo es un bitmap monocromo de 648 x 480 pixeles (el mismo tamaño de la pantalla) creada usando IA y editada para modificar algunos detalles.

Para poder cargarla desde el código hay que transformarla en un archivo «.h» con la información de cada pixel.

Existen varias herramientas para hacerlo, pero una de las mas completas y simples de usar es la que incluye Sense Craft HMI de Seeed Studio, que es gratuita.

El enlace a la herramienta de conversión es https://sensecraft.seeed.cc/hmi/tools/dither Si no funciona, probá de crear una cuenta en https://sensecraft.seeed.cc/hmi y luego buscá la opción Tools

Esta es la configuración que yo utilicé:

Al pulsar sobre el botón Generate Header se genera el archivo que necesitamos incluir (la herramienta siempre agrega una letra «e» adelante del nombre que nosotros elegimos en la configuracion).

Finalmente, debemos agregar este archivo a nuestro proyecto en el Arduino IDE:

La carga de la imagen se hace con la función drawBitmap especificando la coordenada de origen (0,0), el nombre del array con la definición de la imagen (holidayBack) el tamaño de la imagen (648 x 480) y el color de los pixeles (TFT_BLACK).

  // Cargar imagen de fondo
  epaper.drawBitmap(0, 0, holidayBack, 648, 480, TFT_BLACK);

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.

Recordá que para que Seeed_GFX funcione correctamente con la pantalla, es necesario generar previamente el archivo «driver.h» empleando su herramienta de configuración online.

#define BOARD_SCREEN_COMBO 503 // 5.83 inch monochrome ePaper Screen (UC8179)
#define USE_XIAO_EPAPER_DISPLAY_BOARD_EE04

Sincronización del RTC

En esta parte del código se realizan varias tareas relacionadas con el RTC y la información de hora y fecha.

Lo primero es «poner en hora» el RTC interno del ESP32 usando como referencia un servidor NTP. Esto se logra con la instrucción configTime, como ya vimos.

  //Acceder al servidor NTP y obtener fecha y hora actual (en RTC)
  configTime(timeZone * 3600, 0, "pool.ntp.org");

A continuación se lee la fecha y hora desde el RTC y se la pasa a la estructura timeinfo. Si en ese proceso se produce algún error, como ya hicimos antes, se pone el ESP32 en deep sleep por 5 minutos y luego se reinicia.

Si todo funciona correctamente, se muestra la fecha y hora por el monitor serie como control y luego se convierte esta información al formato epoch en la variable epochNow para procesarla con mas facilidad.

  // Leer la hora en timeinfo
  struct tm timeinfo;
  if (!getLocalTime(&timeinfo, 10000)) {
    Serial.println("Error al leer RTC");
    sleepSeconds (300);
    return;    //Solo por claridad
  }

  Serial.println(&timeinfo, "Time: %H:%M:%S");
  Serial.println(&timeinfo, "Date: %Y-%m-%d");

  // Pasar a epoch
  time_t epochNow = mktime(&timeinfo);

A continuación se calculan cuantos segundos faltan desde el momento actual hasta la medianoche del día siguiente.

Esto se hace para que, al terminar todo el proceso de búsqueda del feriado e impresión de resultados, el ESP32 entre en modo de bajo consumo hasta ese momento. De esta forma, el panel funcionará solo unos segundos cada 24 horas (a medianoche) para actualizar información y el resto del tiempo permanecerá «dormido» disminuyendo el consumo al máximo.

La información permanecerá visible en pantalla gracias a la propiedad de bistabilidad del e-Paper.

  // Calcular cuanto falta para la siguiente medianoche

  struct tm tomorrow = timeinfo;

  // Llevar a medianoche de mañana
  tomorrow.tm_sec  = 0;
  tomorrow.tm_min  = 0;
  tomorrow.tm_hour = 0;
  
  tomorrow.tm_mday +=1; 
  
  time_t epochTomorrow = mktime(&tomorrow);

  int32_t seconds2Tomorrow = (int32_t)(epochTomorrow - epochNow);

  Serial.print ("Segundos hasta mañana:");
  Serial.println (seconds2Tomorrow);

Este cálculo funciona de la siguiente forma:

Se parte de dos estructuras tipo tm (de fecha y hora) iguales: tomorrow tiene la misma información que timeinfo, es decir, la fecha y hora actual.

Luego se modifica tomorrow. Se fijan los valores de hora, minutos y segundos a 0 y se incrementa en uno el día. Con eso tomorrow se transforma en la próxima medianoche.

Luego se pasa tomorrow a epoch en epochTomorrow (timeinfo ya está pasado a epochNow) y se calcula la diferencia en segundos entre ambos, es decir entre «ahora» y la medianoche de mañana. Ese es el valor que se usará luego para configurar el timer que «despertará» al ESP32 y lo sacará del modo deep-sleep.

Finalmente se crea la variable epochToday a partir de timeinfo pero llevando los valores de hora, minutos y segundos a 0, para que cuente el tiempo a partir de la medianoche del día actual y no desde el momento en que el dispositivo se encendió.

Esto se hace para que, cuando se calcule la diferencia con las fechas de los feriados, el día actual cuente como un dia completo y si el feriado ocurre al día siguiente, la diferencia sea de un dia.

  // Reajusto la hora actual en epoch desde medianoche
  timeinfo.tm_sec=0;
  timeinfo.tm_min=0;
  timeinfo.tm_hour=0;

  // Pasar a epoch
  time_t epochToday = mktime(&timeinfo);

  //Establece una fecha para hacer pruebas
  //timeinfo.tm_mday=30;
  //timeinfo.tm_mon=12-1;

Conexión con el servidor

Aquí se realiza la conexión con el servidor HTTPS y se prepara todo para hacer la solicitud GET al servidor de Nagle, tal como vimos en la segunda parte de estos artículos.

Si no hubo errores, al final la variable payload contiene la respuesta del servidor, que es la información de todos los feriados del año para el país que especificamos antes. Esta información está en formato JSON.

  //Acceder al servidor de Nager para obtener el listado de feriados

  WiFiClientSecure client;
  client.setInsecure(); // HTTPS cifrado, sin validar certificado

  HTTPClient http;

  if (!http.begin(client, endpoint)) {
    Serial.println("http.begin() failed");
    sleepSeconds (300);
    return;    //Solo por claridad
  }

  int httpCode = http.GET();
  Serial.printf("HTTP code: %d\n", httpCode);

  if (httpCode != HTTP_CODE_OK) {
    Serial.println("Respuesta del server (primeros 200 chars):");
    String errBody = http.getString();
    Serial.println(errBody.substring(0, 200));
    http.end();
    
    sleepSeconds (300);
    return;    //Solo por claridad
  }

  // Descargar todo el JSON
  String payload = http.getString();
  http.end();

  Serial.printf("Payload length: %d bytes\n", payload.length());

Búsqueda del próximo feriado

Ya estamos llegando al final (Por fin!) 😊

En este último tramo se explora la respuesta del servidor para buscar cual es el día feriado mas cercano.

Como ya te dije, la respuesta está en formato JSON y tiene mas o menos este aspecto (muestro sólo una parte de los feriados de mi país para este año 2026).

[
  {
    "date": "2026-01-01",
    "localName": "Año Nuevo",
    "name": "New Year's Day",
    "countryCode": "AR",
    "fixed": false,
    "global": true,
    "counties": null,
    "launchYear": null,
    "types": [
      "Public"
    ]
  },
  {
    "date": "2026-02-16",
    "localName": "Carnaval",
    "name": "Carnival",
    "countryCode": "AR",
    "fixed": false,
    "global": true,
    "counties": null,
    "launchYear": null,
    "types": [
      "Public"
    ]
  }, ....
  {
    "date": "2026-12-25",
    "localName": "Navidad",
    "name": "Christmas Day",
    "countryCode": "AR",
    "fixed": false,
    "global": true,
    "counties": null,
    "launchYear": null,
    "types": [
      "Public"
    ]
  }
]

Lo primero que se aprecia es que el texto empieza y termina con los símbolos de corchete («[ ]») lo que significa que estamos ante un array de objetos. Cada objeto es un dia feriado que contiene 5 propiedades. La última propiedad («types») es a su vez otro array.

El significado de cada propiedad está explicado en la documentación de Nagel. Para este proyecto nos interesan sólo las tres primeras:

dateLa fecha del feriado
localNameNombre del feriado en idioma local
nameNombre del feriado en inglés

La fecha es imprescindible para ubicar el próximo feriado. El nombre en idioma local parece la mejor opción para mostrar en pantalla, pero tiene sus inconvenientes. Finalmente el nombre en inglés es útil si queremos hacer una versión internacional del panel.

Lo primero que debemos hacer antes de explorar el contenido de este archivo es deserealizarlo, o sea, pasar de texto a una variable interna. Esto se hace a continuación:

JsonDocument doc;

  DeserializationError error = deserializeJson(doc, payload);

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

    sleepSeconds (300);
    return;    //Solo por claridad
  }

Luego de la deserealización, la variable doc contiene toda la información de los feriados.

Ahora toca explorar feriado por feriado. Como ya vimos, este array se indexa como cualquier array en C/C++.

Por ejemplo, doc [0] contiene el primer objeto, es decir:

{
  "date": "2026-01-01",
  "localName": "Año Nuevo",
  "name": "New Year's Day",
  "countryCode": "AR",
  "fixed": false,
  "global": true,
  "counties": null,
  "launchYear": null,
  "types": ["Public"]
}

Cada propiedad dentro del objeto se puede referenciar por su clave, por lo tanto doc[0][«date»] nos devuelve:

"2026-01-01"

Recorrer todo el array entonces se puede hacer con un bucle for de la siguiente forma:

  //Explorar el JSON calculando diferencia de dias
  //El primero que de mayor a 0 es el siguiente

  bool found = false; 

  for (int i = 0; i < doc.size(); i++) {
    const char* holiday = doc[i]["date"];
    const char* name = doc[i]["name"];

    Serial.println(holiday);
    Serial.println(name);

size es un método que devuelve la cantidad de objetos dentro de doc. Dentro del bucle se van recuperando los valores de «date» y «name» de cada objeto y se muestran por el monitor serie.

Dentro del mismo bucle for se calcula epochDif, la diferencia (en formato epoch) entre la fecha del feriado y la fecha de hoy. Si esta diferencia es menor que 0, significa que el feriado ya quedó atrás y por lo tanto no nos interesa. Pero si la diferencia es mayor o igual a cero, implica que el feriado está en el futuro o es justamente hoy.

En cualquiera de los dos casos, se calcula la diferencia entre las dos fechas en días, dividiendo los segundos de epochDif por 86400, que son los segundos que hay en un dia y se recupera el «localName» de ese feriado. A continuación se imprime la cantidad de días con números grandes en la EPD y el nombre del feriado con una fuente mas pequeña.

Esto último tiene un pequeño problemita. Las fuentes definidas en la biblioteca Seeed_GFX no son internacionales, no incluyen letras con tildes, diéresis ni otros signos característicos. Si imprimimos el texto contenido en localName, estos caracteres directamente no aparecerán.

Esto puede resolverse de distintas maneras: podés crear tu propio font con todos los caracteres que necesites, usar el nombre en inglés o como hice yo, quitar estos símbolos de los caracteres a imprimir con la función quitarAcentos definida al principio.

Otro problemita adicional: el nombre del feriado puede ser extenso. A los fines de simplificar, yo opté por imprimir sólo los primeros 38 caracteres, que son los que entran en una línea con la fuente que elegí. Lo óptimo sería escribir una función que imprima en dos o mas lineas cuidando que no se corten las palabras (pero esa mejora te la dejo como desafío) 💪

    int epochDif = stringDateToEpoch (holiday) - epochToday;
    Serial.println (epochDif);

    if (epochDif >= 0) {   //Si es hoy, indica 0 dias

      int daysDif = epochDif / 86400;  //Segundos en un dia
      Serial.println (daysDif);
      String holidayName = doc[i]["localName"];
   
      // Selecciona tamaño y font para la cantidad de días
      epaper.setTextSize (3);
      epaper.setTextFont (7);
    
      epaper.drawNumber (daysDif, epaper.width()/2, 220);

      epaper.setTextSize (1);
      epaper.setFreeFont(&Yellowtail_32);    // Seleccionar tipo de letra

      //Muestra solo los primeros 30 caracteres
      epaper.drawString (quitarAcentos(holidayName.substring (0,38)), epaper.width()/2, 314);
      epaper.update ();

      found = true;
      break;
    }
  }

Antes de realizar la búsqueda del feriado mas cercano, se crea la variable booleana found y se le asigna el valor false. Si se encuentra el próximo feriado, después de actualizar la pantalla, esta variable se pone a true y se sale del bucle for.

A continuación, se verifica el valor de found. Si vale false significa que no se encontró el próximo feriado. Esto puede suceder cuando ya estamos finalizando el año y no hay mas feriados en el calendario. En ese caso especial, el programa muestra «–» en la cantidad de días faltantes y el texto «No hay mas feriados!».

  if (!found) {
    //No hay mas feriados :(
    
    // Selecciona tamaño y font para la cantidad de días
    epaper.setTextSize (3);
    epaper.setTextFont (7);
    epaper.drawString ("--", epaper.width()/2, 220);

    epaper.setTextSize (1);
    epaper.setFreeFont(&Yellowtail_32);    // Seleccionar tipo de letra
    epaper.drawString ("No hay mas feriados!", epaper.width()/2, 314);

    epaper.update ();
  }

Lo último es programar el timer para despertar al ESP32 a la medianoche de mañana y ponerlo a dormir.

  //Todo listo, ir a dormir hasta mañana

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

Conclusiones

Con este tercer artículo completamos el proyecto del panel inteligente Próximo Feriado. Se trata de un proyecto sencillo, simpático y útil que nos sirvió como excusa para analizar algunas de las tecnologías y procedimientos más habituales en desarrollos IoT que interactúan con servidores web: el manejo de fecha y hora, el acceso mediante HTTP y HTTPS, la realización de solicitudes con el método GET y el procesamiento de archivos JSON.

Todos estos conocimientos te serán de gran utilidad en una amplia variedad de proyectos, en los que muy probablemente termines utilizando una o varias de estas técnicas.

También vimos cómo utilizar una pantalla e-paper junto con la placa controladora EE04 de Seeed Studio, cómo mostrar textos con distintas fuentes y cómo visualizar imágenes bitmap, empleando en todo momento la biblioteca Seeed_GFX.

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

Nos vemos! 🚀

El próximo feriado

El próximo feriado. Panel inteligente con e-Paper. Parte 2

Deja un comentario

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