Ir al contenido

← Estación de dos bombas con telemetría LoRaWAN en PLC ESP32

Bombeo de agua (saneamiento)ESP32 PLC 38ARGPIOControl

Estación de dos bombas con telemetría LoRaWAN en PLC ESP32 — ejemplo completo

Monta una estación de dos bombas con alternancia automática, failover y telemetría LoRaWAN en un ESP32 PLC 38AR. Código Arduino completo con tareas FreeRTOS.

Programa completo y ejecutable para el ESP32 PLC 38AR (dual-pump-control-lorawan-38ar.ino): incluye cabecera de conexionado, requisitos y notas de integración.

Descarga el pack completo del proyecto — gratisEste ejemplo + los relacionados + lista de materiales

Vista de solo lectura.

/*
 * COMPLETE EXAMPLE — Dual pump control with alternation + LoRaWAN telemetry
 *
 * Hardware: ESP32 PLC 38AR (Industrial Shields) + LoRa RN2xx3 module
 * Based on: water pumping project (sanitation), bombament-2b-38ar.ino
 *
 * Wiring:
 *   I0_0  Minimum-level float switch      I0_5  Manual selector pump 2
 *   I0_1  Maximum-level float switch      I0_6  Contactor feedback P1
 *   I0_2  Overlevel float switch          I0_7  Contactor feedback P2
 *   I0_3  Reset push button               I0_8  Thermal relay P1 (closed = fault)
 *   I0_4  Manual selector pump 1          I0_9  Thermal relay P2 (closed = fault)
 *   I0_11 4-20 mA level sensor
 *   Q0_0  Pump 1 contactor     Q0_1  Pump 2 contactor     Q0_2  Alarm pilot light
 *
 * Logic:
 *   Alternation: every fill cycle starts the pump opposite to the previous one,
 *   to equalize running hours. If the active pump fails (thermal relay or
 *   missing feedback), it automatically switches to the other one and flags
 *   the error. Manual mode via selector. Telemetry packed into 4 bytes over
 *   LoRaWAN every 60 s using an independent FreeRTOS task.
 */

#define I_MIN      I0_0
#define I_MAX      I0_1
#define I_OVERLVL  I0_2
#define I_RESET    I0_3
#define I_MAN_P1   I0_4
#define I_MAN_P2   I0_5
#define I_CONF_P1  I0_6
#define I_CONF_P2  I0_7
#define I_THERM_P1 I0_8
#define I_THERM_P2 I0_9
#define I_SENSOR   I0_11
#define Q_P1       Q0_0
#define Q_P2       Q0_1
#define Q_PILOT    Q0_2

enum State { IDLE, PUMPING, ERROR_S };
State state = IDLE;
uint8_t activePump = 1;         // alternation: the next one to start
bool errorP1 = false, errorP2 = false;
uint16_t sensorLevel = 0;

const uint32_t T_CONFIRMATION_MS = 2000;
uint32_t tStart = 0;

void startPump(uint8_t p) {
  digitalWrite(p == 1 ? Q_P1 : Q_P2, HIGH);
  tStart = millis();
}
void stopAll() { digitalWrite(Q_P1, LOW); digitalWrite(Q_P2, LOW); }

// ------------------------------------------------- Control task (100 ms)
void vTaskControl(void *pv) {
  for (;;) {
    bool minLvl = digitalRead(I_MIN), maxLvl = digitalRead(I_MAX);
    sensorLevel = analogRead(I_SENSOR);         // 4-20 mA -> 0-1023

    switch (state) {
      case IDLE:
        if (digitalRead(I_MAN_P1)) { startPump(1); state = PUMPING; activePump = 1; }
        else if (digitalRead(I_MAN_P2)) { startPump(2); state = PUMPING; activePump = 2; }
        else if (minLvl && maxLvl) {            // automatic start with alternation
          activePump = (activePump == 1) ? 2 : 1;
          if ((activePump == 1 && errorP1) || (activePump == 2 && errorP2))
            activePump = (activePump == 1) ? 2 : 1;   // skip faulty pump
          startPump(activePump);
          state = PUMPING;
        }
        break;

      case PUMPING: {
        if (!minLvl) { stopAll(); state = IDLE; break; }    // emptying finished
        bool thermal = digitalRead(activePump == 1 ? I_THERM_P1 : I_THERM_P2);
        bool conf    = digitalRead(activePump == 1 ? I_CONF_P1 : I_CONF_P2);
        bool failure = thermal || (!conf && millis() - tStart > T_CONFIRMATION_MS);
        if (failure) {
          if (activePump == 1) errorP1 = true; else errorP2 = true;
          stopAll();
          digitalWrite(Q_PILOT, HIGH);
          uint8_t other = (activePump == 1) ? 2 : 1;
          bool errOther = (other == 1) ? errorP1 : errorP2;
          if (!errOther) { activePump = other; startPump(other); }  // switch over
          else state = ERROR_S;                                     // both KO
        }
        break;
      }

      case ERROR_S:
        if (digitalRead(I_RESET)) {
          errorP1 = errorP2 = false;
          digitalWrite(Q_PILOT, LOW);
          state = IDLE;
        }
        break;
    }
    vTaskDelay(100 / portTICK_PERIOD_MS);
  }
}

// ------------------------------------------------- LoRaWAN task (60 s)
// 4-byte frame: inputs (11 bits) + outputs (2) + errors (2) + sensor (10 bits)
void vTaskLora(void *pv) {
  for (;;) {
    byte msg[4] = {0, 0, 0, 0};
    const int pins[] = {I_MIN, I_MAX, I_OVERLVL, I_RESET, I_MAN_P1, I_MAN_P2,
                        I_CONF_P1, I_CONF_P2, I_THERM_P1, I_THERM_P2};
    for (uint8_t i = 0; i < 10; i++)
      msg[i / 8] |= digitalRead(pins[i]) << (7 - i % 8);
    msg[1] |= digitalRead(Q_P1) << 5;
    msg[1] |= digitalRead(Q_P2) << 4;
    msg[1] |= errorP1 << 3;
    msg[1] |= errorP2 << 2;
    msg[2]  = (sensorLevel >> 8) & 0x03;
    msg[3]  =  sensorLevel & 0xFF;
    lora_send_bytes(msg, 4);                    // see LoRaWAN module in the catalog
    vTaskDelay(60000 / portTICK_PERIOD_MS);
  }
}

// Hourly OTAA re-join: if the link drops, it recovers by itself (comms watchdog)
void vTaskRejoin(void *pv) {
  for (;;) {
    vTaskDelay(3600000 / portTICK_PERIOD_MS);
    lora_join_otaa("APP_EUI", "APP_KEY");       // credentials: placeholders
  }
}

void setup() {
  Serial.begin(115200);
  const int ins[] = {I_MIN, I_MAX, I_OVERLVL, I_RESET, I_MAN_P1, I_MAN_P2,
                     I_CONF_P1, I_CONF_P2, I_THERM_P1, I_THERM_P2};
  for (int p : ins) pinMode(p, INPUT);
  pinMode(Q_P1, OUTPUT); pinMode(Q_P2, OUTPUT); pinMode(Q_PILOT, OUTPUT);
  stopAll();

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

  xTaskCreate(vTaskControl, "control", 4096, NULL, 3, NULL);
  xTaskCreate(vTaskLora,    "lora",    4096, NULL, 1, NULL);
  xTaskCreate(vTaskRejoin,  "rejoin",  4096, NULL, 2, NULL);
}

void loop() { vTaskDelay(portMAX_DELAY); }      // everything lives in FreeRTOS tasks
Descarga el pack completo del proyecto — gratisEste ejemplo + los relacionados + lista de materiales