Ir al contenido

← Todas las funcionalidades

Bombeo de agua (saneamiento)ESP32 PLC 14 / 38ARLoRaWANResiliencia / OTA

Multitarea FreeRTOS en un PLC ESP32 con re-join LoRaWAN periódico

Una estructura de firmware basada en FreeRTOS es lo que mantiene vivos los PLCs remotos cuando nadie los vigila. Este ejemplo, extraído de un despliegue real en una estación de bombeo de agua, divide el firmware de un ESP32 PLC 14 en tres tareas: control de bombas cada 100 ms, telemetría LoRaWAN cada 60 s y un re-join OTAA cada hora que actúa como watchdog de comunicaciones. La radio puede quedarse colgada durante segundos — la lógica de bombas ni se entera.

Tres tareas, tres prioridades

El control corre a prioridad 3 con vTaskDelayUntil para un periodo de 100 ms sin deriva; la telemetría corre a prioridad 1 porque una transmisión LoRa puede tardar legítimamente segundos con spreading factors altos; la tarea de re-join queda entre ambas. En el ESP32 de doble núcleo esta separación sale gratis — y significa que un comando de módem colgado nunca retrasa un paro de bomba.

El re-join horario como watchdog

Las sesiones LoRaWAN pueden morir en silencio: un reinicio del gateway, un contador de tramas perdido, un microcorte en el módem. En lugar de detectar cada modo de fallo, el firmware simplemente renegocia la sesión OTAA cada hora. En el peor caso, la estación pierde una hora de uplinks y luego se recupera sola — sin desplazamientos a un emplazamiento remoto.

Compartir estado entre tareas de forma segura

La tarea de control escribe flags volatile de un solo byte (bomba en marcha, rebose, fallo) y la tarea de telemetría los lee. Las lecturas de un byte son atómicas en el ESP32, así que este patrón no necesita mutex — mantén el estado compartido en bytes sueltos o protégelo, y nunca llames a la radio desde la tarea de control.

Un fragmento de la implementación

Tal cual del ejemplo desplegado en el ESP32 PLC 14 / 38AR — cópialo libremente:

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

  pinMode(I_FLOAT_MIN, INPUT);
  pinMode(I_FLOAT_MAX, INPUT);
  pinMode(I_OVERLEVEL, INPUT);
  pinMode(I_THERMAL, INPUT);
  pinMode(Q_PUMP, OUTPUT);
  pinMode(Q_PILOT, OUTPUT);
  digitalWrite(Q_PUMP, LOW);
  digitalWrite(Q_PILOT, LOW);

  lora_init(57600);                                     // RN2xx3 via SerialSC1
  lora_join_otaa("APP_EUI", "APP_KEY");                 // initial join

  // 4096 bytes of stack per task: plenty for digitalRead + AT commands
  xTaskCreate(vTaskControl, "control", 4096, NULL, 3, NULL);  // 100 ms
  xTaskCreate(vTaskLora,    "lora",    4096, NULL, 1, NULL);  // 60 s
  xTaskCreate(vTaskRejoin,  "rejoin",  4096, NULL, 2, NULL);  // 1 h
}

El ejemplo completo es un programa entero — cabecera de conexionado, setup y bucle principal — listo para adaptar a tu aplicación.

Preguntas frecuentes

¿Por qué usar FreeRTOS en lugar de temporizar con millis() en loop()?

Porque una operación de radio bloqueante (join, envío con reintentos) congelaría todo el loop. Con tareas, la lógica de control mantiene su cadencia de 100 ms haga lo que haga el módem.

¿Cada cuánto debería re-hacer el join OTAA?

Una vez por hora es un buen equilibrio para estaciones que envían cada 60 s. Re-hacer el join demasiado a menudo desperdicia airtime y duty cycle; hacerlo demasiado poco significa cortes largos tras una incidencia del gateway.

¿Necesito un mutex para las variables compartidas?

No para flags volatile de un solo byte, que se leen y escriben de forma atómica en el ESP32. Para estructuras de varios bytes, protégelas con un mutex o pásalas por una cola de FreeRTOS.

Funcionalidades relacionadas