Several sensors trying to access one pump relay at the same time (irrigation)

Thanks for pointing that out. I've modified the code to address the potential issues caused by dropped UDP packets, and to include request retransmission, periodic status updates, and timeout mechanisms. Here's the updated code . Let me know what you think:

// Sensor Node or Pump Controller Code
#include <ESP8266WiFi.h>
#include <WiFiUdp.h>

// Wi-Fi credentials
const char* ssid = "YOUR_WIFI_SSID";
const char* password = "YOUR_WIFI_PASSWORD";

// Pump controller IP address and port
IPAddress pumpControllerIP(192, 168, 1, 100);
unsigned int pumpControllerPort = 3333;

// Sensor pin and relay pin (for sensor nodes)
const int sensorPin = A0;
const int relayPin = D1; // Pin connected to the relay for the sprinkler valve

// Sensor ID (0, 1, or 2 for sensor nodes, -1 for pump controller)
const int sensorId = -1;

// Pump relay pin (for pump controller)
const int pumpRelayPin = D1;

// Request retransmission parameters
const int MAX_RETRIES = 3;
const unsigned long RETRY_DELAY = 5000; // 5 seconds

// Periodic status update interval (in milliseconds)
const unsigned long STATUS_UPDATE_INTERVAL = 60000; // 1 minute

// Timeout parameters (in milliseconds)
const unsigned long SENSOR_TIMEOUT = 120000; // 2 minutes
const unsigned long PUMP_TIMEOUT = 180000; // 3 minutes

WiFiUDP udp;

// Keep track of active requests (for pump controller)
bool sensorRequests[3] = {false, false, false};
unsigned long lastStatusUpdate[3] = {0, 0, 0};

unsigned long lastPumpUpdate = 0;
bool pumpStatus = false;

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

  if (sensorId == -1) {
    // Pump controller setup
    Serial.println("Setting up pump controller...");
    udp.begin(pumpControllerPort);
    pinMode(pumpRelayPin, OUTPUT);
    digitalWrite(pumpRelayPin, LOW); // Initially turn off pump
    Serial.println("Pump controller setup complete!");
  } else {
    // Sensor node setup
    Serial.print("Setting up sensor node ");
    Serial.print(sensorId);
    Serial.println("...");
    udp.begin(WiFi.localIP(), pumpControllerPort);
    pinMode(relayPin, OUTPUT);
    digitalWrite(relayPin, LOW); // Initially turn off the sprinkler valve
    Serial.print("Sensor node ");
    Serial.print(sensorId);
    Serial.println(" setup complete!");
  }
}

void loop() {
  if (sensorId == -1) {
    // Pump controller loop
    int packetSize = udp.parsePacket();
    if (packetSize) {
      Serial.println("Received packet from sensor node...");
      byte packet[3];
      udp.read(packet, 3);
      int sensorId = packet[0];
      bool requestStatus = packet[1] == 1;
      bool statusUpdate = packet[2] == 2;

      if (requestStatus) {
        Serial.print("Handling request from sensor node ");
        Serial.println(sensorId);
        handleRequest(sensorId);
      } else if (statusUpdate) {
        Serial.print("Handling status update from sensor node ");
        Serial.println(sensorId);
        handleStatusUpdate(sensorId, requestStatus);
      } else {
        Serial.print("Handling request removal from sensor node ");
        Serial.println(sensorId);
        handleRequestRemoval(sensorId);
      }
    }
    
    // Check for pump timeout
    if (millis() - lastPumpUpdate > PUMP_TIMEOUT) {
      Serial.println("Pump timeout, turning off pump");
      pumpStatus = false;
      digitalWrite(pumpRelayPin, LOW);
    }
  } else {
    // Sensor node loop
    int moisture = analogRead(sensorPin);
    bool soilIsDry = moisture < 300; // Adjust threshold as needed

    if (soilIsDry) {
      Serial.print("Sensor node ");
      Serial.print(sensorId);
      Serial.println(" needs water, sending request...");
      sendRequest(true);
      Serial.print("Turning on sprinkler valve for sensor node ");
      Serial.println(sensorId);
      digitalWrite(relayPin, HIGH); // Turn on the sprinkler valve
    } else {
      Serial.print("Sensor node ");
      Serial.print(sensorId);
      Serial.println(" has enough water, sending removal request...");
      sendRequest(false);
      Serial.print("Turning off sprinkler valve for sensor node ");
      Serial.println(sensorId);
      digitalWrite(relayPin, LOW); // Turn off the sprinkler valve
    }

    // Send periodic status update
    if (millis() - lastStatusUpdate[sensorId] > STATUS_UPDATE_INTERVAL) {
      sendStatusUpdate(soilIsDry);
      lastStatusUpdate[sensorId] = millis();
    }
  }
}

// Sensor node functions
void sendRequest(bool needsWater) {
  byte message[3] = {sensorId, needsWater ? 1 : 0, 0};
  int retries = 0;
  bool ackReceived = false;

  while (retries < MAX_RETRIES && !ackReceived) {
    udp.beginPacket(pumpControllerIP, pumpControllerPort);
    udp.write(message, 3);
    udp.endPacket();
    
    unsigned long startTime = millis();
    while (millis() - startTime < RETRY_DELAY) {
      int packetSize = udp.parsePacket();
      if (packetSize) {
        byte ack[1];
        udp.read(ack, 1);
        if (ack[0] == sensorId) {
          ackReceived = true;
          break;
        }
      }
    }
    
    retries++;
  }
}

void sendStatusUpdate(bool needsWater) {
  byte message[3] = {sensorId, needsWater ? 1 : 0, 2};
  udp.beginPacket(pumpControllerIP, pumpControllerPort);
  udp.write(message, 3);
  udp.endPacket();
}

// Pump controller functions
void handleRequest(int sensorId) {
  sensorRequests[sensorId] = true;
  pumpStatus = needsWater();
  Serial.print("Sensor node ");
  Serial.print(sensorId);
  Serial.print(" needs water. Pump status: ");
  Serial.println(pumpStatus ? "ON" : "OFF");
  digitalWrite(pumpRelayPin, pumpStatus);
  lastPumpUpdate = millis();
  sendAck(sensorId);
}

void handleRequestRemoval(int sensorId) {
  sensorRequests[sensorId] = false;
  pumpStatus = needsWater();
  Serial.print("Sensor node ");
  Serial.print(sensorId);
  Serial.print(" has enough water. Pump status: ");
  Serial.println(pumpStatus ? "ON" : "OFF");
  digitalWrite(pumpRelayPin, pumpStatus);
  lastPumpUpdate = millis();
  sendAck(sensorId);
}

void handleStatusUpdate(int sensorId, bool needsWater) {
  sensorRequests[sensorId] = needsWater;
  pumpStatus = needsWater();
  Serial.print("Sensor node ");
  Serial.print(sensorId);
  Serial.print(" status update: ");
  Serial.println(needsWater ? "Needs water" : "Has enough water");
  Serial.print("Pump status: ");
  Serial.println(pumpStatus ? "ON" : "OFF");
  digitalWrite(pumpRelayPin, pumpStatus);
  lastPumpUpdate = millis();
  sendAck(sensorId);
}

bool needsWater() {
  return sensorRequests[0] || sensorRequests[1] || sensorRequests[2];
}

void sendAck(int sensorId) {
  byte ack[1] = {sensorId};
  udp.beginPacket(udp.remoteIP(), udp.remotePort());
  udp.write(ack, 1);
  udp.endPacket();
}

// Common function
void connectToWiFi() {
  Serial.println("Connecting to WiFi...");
  WiFi.begin(ssid, password);
  while (WiFi.status() != WL_CONNECTED) {
    delay(1000);
    Serial.print(".");
  }
  Serial.println("\nConnected to WiFi");
}

Once you have discovered your irrigation system has FLOODED the garden, you will find you need a safety system to stop the water when too much has been applied.

1 Like

Supposedly when the soil sensor(s) reach the preset value they will instruct the valve and pump to shut off.

Another suppose is how much time will it take for the water to get to the sensor?
Is the soil sand or clay or what?

Two questions here:
You seem to be using the same pin for both sprinklers and pump
Will using pin D1 conflict with Serial.print()?

Also, make sure your relays close with HIGH. Most work with LOW.

THe sensor is a capacitance soil sensor. the sprinkler is beside it.

Good suggestion though, might work in a maximum time on for each sensor.

Needs lots of testing and adjustments! Good luck!

are you speaking from experience?

Not with that sensor. Just lots of irrigation experience. Some with the water storage from the well being controlled by an Arduino Nano and float switches. Sometimes the switches stick and the tanks overflow for hours!

I developed (with help from Horace) a great system for monitoring our water supply. It uses an Ultrasonic sensor, sends it by LoRa to the cottage where it's uploaded to Blynk. Been working great for three years now. The newest version also monitors the voltage of the battery running the system. The only failure I've had was because of a failure in the solar controller, hence the adding of a voltage monitor. The thread is on the forum here if you're interested.

You have had a lot more successful experience than 90% of the posters on the forum!

thanks all for your comments and suggestions. My son was up at the cottage this weekend and has reported that the internet is even less robust than last year. Hopefully it will get better with a new provider. So with that in mind, I'm putting aside the approach above that leans heavily on the Internet to work and moving to a non-internet based solution using NRF24L01 and nanos in place of the ESP8266 in the previous approach.
Again I've tried to work out that sensor nodes don't interfere with each other with conflicting shutoff commands. Also I've tried to implement Paul's concern about the garden being flooded by placing a command that stops the pump after 30 minutes regardless of other commands. I'd be interested in suggestion or concerns on the sketch.

Here is the sensor node sketch:

#include <RF24.h>
#include <RF24Network.h>
#include <RF24Mesh.h>

// Soil Sensor and Relay Pins
#define SOIL_SENSOR_PIN A0
#define SPRINKLER_RELAY_PIN 3

// RF24 Setup
RF24 radio(7, 8); // CE, CSN
RF24Network network(radio);
RF24Mesh mesh(radio, network);

// Node Address
uint16_t nodeId = 0; // Set unique node ID for each sensor node

// Thresholds for soil moisture (adjust as needed)
const int DRY_THRESHOLD = 300; // Value to be provided by you
const int WET_THRESHOLD = 600; // Value to be provided by you

// Sensor value range
const int SENSOR_MIN_VALUE = 0;
const int SENSOR_MAX_VALUE = 1023;

// Data Structure
struct PayloadData {
  uint16_t nodeId;
  int soilMoisture;
};

bool sprinklerActive = false;

void setup() {
  Serial.begin(115200);
  mesh.setNodeID(nodeId);
  mesh.begin();
  pinMode(SPRINKLER_RELAY_PIN, OUTPUT);
  digitalWrite(SPRINKLER_RELAY_PIN, LOW); // Initially turn off the sprinkler

  // Check if the provided thresholds are valid
  if (DRY_THRESHOLD < SENSOR_MIN_VALUE || DRY_THRESHOLD > SENSOR_MAX_VALUE ||
      WET_THRESHOLD < SENSOR_MIN_VALUE || WET_THRESHOLD > SENSOR_MAX_VALUE ||
      DRY_THRESHOLD >= WET_THRESHOLD) {
    Serial.println("Error: Invalid threshold values provided!");
    while (1); // Halt the program
  }

  Serial.print("Node ID: ");
  Serial.println(nodeId);
  Serial.print("Dry Threshold: ");
  Serial.println(DRY_THRESHOLD);
  Serial.print("Wet Threshold: ");
  Serial.println(WET_THRESHOLD);
}

void loop() {
  mesh.update();
  mesh.DHCP();

  int soilMoisture = analogRead(SOIL_SENSOR_PIN);
  Serial.print("Soil Moisture: ");
  Serial.println(soilMoisture);

  if (soilMoisture > DRY_THRESHOLD && !sprinklerActive) {
    // Soil is dry, request the pump
    PayloadData payload = {nodeId, soilMoisture};
    mesh.writeTo(mesh.getNodeId(), 'P', &payload, sizeof(payload));
    digitalWrite(SPRINKLER_RELAY_PIN, HIGH); // Turn on the sprinkler
    sprinklerActive = true;
    Serial.println("Sending pump request and turning on sprinkler");
  } else if (soilMoisture < WET_THRESHOLD && sprinklerActive) {
    // Soil is wet enough, turn off the sprinkler
    digitalWrite(SPRINKLER_RELAY_PIN, LOW); // Turn off the sprinkler
    sprinklerActive = false;
    Serial.println("Turning off sprinkler");
  }

  delay(5000); // Adjust the delay as needed
}

and here is the Pump sketch:

#include <RF24.h>
#include <RF24Network.h>
#include <RF24Mesh.h>

// Pump Relay Pin
#define PUMP_RELAY_PIN 3

// RF24 Setup
RF24 radio(7, 8); // CE, CSN
RF24Network network(radio);
RF24Mesh mesh(radio, network);

// Node Address
uint16_t nodeId = 1; // Set a unique node ID for the pump node

// Data Structure
struct PayloadData {
  uint16_t nodeId;
  int soilMoisture;
  byte priority; // Add a priority field
};

// Priority mapping for sensor nodes
const byte nodePriority[] = {3, 1, 2}; // Assign priorities based on node IDs

// Maximum pump runtime (in milliseconds)
const unsigned long MAX_PUMP_RUNTIME = 30 * 60 * 1000; // 30 minutes

bool pumpActive = false;
uint16_t activePumpNodeId = 0;
unsigned long pumpStartTime = 0;

void setup() {
  Serial.begin(115200);
  mesh.setNodeID(nodeId);
  mesh.begin();
  pinMode(PUMP_RELAY_PIN, OUTPUT);
  digitalWrite(PUMP_RELAY_PIN, LOW); // Initially turn off the pump

  Serial.print("Node ID: ");
  Serial.println(nodeId);
  Serial.print("Priority Mapping: ");
  for (byte i = 0; i < sizeof(nodePriority); i++) {
    Serial.print(nodePriority[i]);
    Serial.print(" ");
  }
  Serial.println();
  Serial.print("Maximum Pump Runtime: ");
  Serial.print(MAX_PUMP_RUNTIME / 60000);
  Serial.println(" minutes");
}

void loop() {
  mesh.update();
  mesh.DHCP();

  if (network.available()) {
    RF24NetworkHeader header;
    PayloadData payload;
    network.read(header, &payload, sizeof(payload));

    if (header.type == 'P') {
      // Pump request received
      Serial.print("Node ID: ");
      Serial.print(payload.nodeId);
      Serial.print(", Soil Moisture: ");
      Serial.print(payload.soilMoisture);
      Serial.print(", Priority: ");
      Serial.println(payload.priority);

      // Check if the new request has a higher priority than the active one
      if (!pumpActive || payload.priority > nodePriority[activePumpNodeId - 1]) {
        // Turn off the pump if it's active
        if (pumpActive) {
          digitalWrite(PUMP_RELAY_PIN, LOW);
          pumpActive = false;
          Serial.println("Turning off pump");
        }

        // Turn on the pump for the new request
        digitalWrite(PUMP_RELAY_PIN, HIGH);
        pumpActive = true;
        activePumpNodeId = payload.nodeId;
        pumpStartTime = millis(); // Record the start time
        Serial.println("Turning on pump");
      }
    }
  }

  // Check if the pump has been running for too long
  if (pumpActive && (millis() - pumpStartTime >= MAX_PUMP_RUNTIME)) {
    // Turn off the pump
    digitalWrite(PUMP_RELAY_PIN, LOW);
    pumpActive = false;
    Serial.println("Maximum pump runtime reached. Turning off the pump.");
  }

  delay(100); // Adjust the delay as needed
}

For any of you who are following my adventures, I now have the two sketches using the NRF24L01 compliling okay. I've attached the latest working versions. Be careful with the libraries. Make sure there are not conflicting libraries and use: dng them from the official GitHub repositories:

#include <RF24.h>
#include <RF24Network.h>

// Soil Sensor and Relay Pins
#define SOIL_SENSOR_PIN A0
#define SPRINKLER_RELAY_PIN 3

// RF24 Setup
RF24 radio(7, 8); // CE, CSN
RF24Network network(radio);

// Node Address
uint16_t nodeId = 0; // Set unique node ID for each sensor node

// Thresholds for soil moisture (adjust as needed)
const int DRY_THRESHOLD = 300; // Value to be provided by you
const int WET_THRESHOLD = 600; // Value to be provided by you

// Sensor value range
const int SENSOR_MIN_VALUE = 0;
const int SENSOR_MAX_VALUE = 1023;

// Data Structure
struct PayloadData {
  uint16_t nodeId;
  int soilMoisture;
};

bool sprinklerActive = false;

void setup() {
  Serial.begin(115200);
  network.begin(/*channel*/ 90, /*node address*/ nodeId);
  pinMode(SPRINKLER_RELAY_PIN, OUTPUT);
  digitalWrite(SPRINKLER_RELAY_PIN, LOW); // Initially turn off the sprinkler

  // Check if the provided thresholds are valid
  if (DRY_THRESHOLD < SENSOR_MIN_VALUE || DRY_THRESHOLD > SENSOR_MAX_VALUE ||
      WET_THRESHOLD < SENSOR_MIN_VALUE || WET_THRESHOLD > SENSOR_MAX_VALUE ||
      DRY_THRESHOLD >= WET_THRESHOLD) {
    Serial.println("Error: Invalid threshold values provided!");
    while (1); // Halt the program
  }

  Serial.print("Node ID: ");
  Serial.println(nodeId);
  Serial.print("Dry Threshold: ");
  Serial.println(DRY_THRESHOLD);
  Serial.print("Wet Threshold: ");
  Serial.println(WET_THRESHOLD);
}

void loop() {
  network.update();

  int soilMoisture = analogRead(SOIL_SENSOR_PIN);
  Serial.print("Soil Moisture: ");
  Serial.println(soilMoisture);

  if (soilMoisture > DRY_THRESHOLD && !sprinklerActive) {
    // Soil is dry, request the pump
    PayloadData payload = {nodeId, soilMoisture};
    RF24NetworkHeader header(/*to node*/ 0, 'P');
    network.write(header, &payload, sizeof(payload));
    digitalWrite(SPRINKLER_RELAY_PIN, HIGH); // Turn on the sprinkler
    sprinklerActive = true;
    Serial.println("Sending pump request and turning on sprinkler");
  } else if (soilMoisture < WET_THRESHOLD && sprinklerActive) {
    // Soil is wet enough, turn off the sprinkler
    digitalWrite(SPRINKLER_RELAY_PIN, LOW); // Turn off the sprinkler
    sprinklerActive = false;
    Serial.println("Turning off sprinkler");
  }

  delay(5000); // Adjust the delay as needed
}

Receiving Sketch:

#include <RF24.h>
#include <RF24Network.h>

// Pump Relay Pin
#define PUMP_RELAY_PIN 3

// RF24 Setup
RF24 radio(7, 8); // CE, CSN
RF24Network network(radio);

// Node Address
uint16_t nodeId = 1; // Set a unique node ID for the pump node

// Data Structure
struct PayloadData {
  uint16_t nodeId;
  int soilMoisture;
  byte priority; // Add a priority field
};

// Priority mapping for sensor nodes
const byte nodePriority[] = {3, 1, 2}; // Assign priorities based on node IDs

// Maximum pump runtime (in milliseconds)
const unsigned long MAX_PUMP_RUNTIME = 30 * 60 * 1000; // 30 minutes

bool pumpActive = false;
uint16_t activePumpNodeId = 0;
unsigned long pumpStartTime = 0;

void setup() {
  Serial.begin(115200);
  network.begin(/*channel*/ 90, /*node address*/ nodeId);
  pinMode(PUMP_RELAY_PIN, OUTPUT);
  digitalWrite(PUMP_RELAY_PIN, LOW); // Initially turn off the pump

  Serial.print("Node ID: ");
  Serial.println(nodeId);
  Serial.print("Priority Mapping: ");
  for (byte i = 0; i < sizeof(nodePriority); i++) {
    Serial.print(nodePriority[i]);
    Serial.print(" ");
  }
  Serial.println();
  Serial.print("Maximum Pump Runtime: ");
  Serial.print(MAX_PUMP_RUNTIME / 60000);
  Serial.println(" minutes");
}

void loop() {
  network.update();

  while (network.available()) {
    RF24NetworkHeader header;
    PayloadData payload;
    network.read(header, &payload, sizeof(payload));

    if (header.type == 'P') {
      // Pump request received
      Serial.print("Node ID: ");
      Serial.print(payload.nodeId);
      Serial.print(", Soil Moisture: ");
      Serial.print(payload.soilMoisture);
      Serial.print(", Priority: ");
      Serial.println(payload.priority);

      // Check if the new request has a higher priority than the active one
      if (!pumpActive || payload.priority > nodePriority[activePumpNodeId - 1]) {
        // Turn off the pump if it's active
        if (pumpActive) {
          digitalWrite(PUMP_RELAY_PIN, LOW);
          pumpActive = false;
          Serial.println("Turning off pump");
        }

        // Turn on the pump for the new request
        digitalWrite(PUMP_RELAY_PIN, HIGH);
        pumpActive = true;
        activePumpNodeId = payload.nodeId;
        pumpStartTime = millis(); // Record the start time
        Serial.println("Turning on pump");
      }
    }
  }

  // Check if the pump has been running for too long
  if (pumpActive && (millis() - pumpStartTime >= MAX_PUMP_RUNTIME)) {
    // Turn off the pump
    digitalWrite(PUMP_RELAY_PIN, LOW);
    pumpActive = false;
    Serial.println("Maximum pump runtime reached. Turning off the pump.");
  }

  delay(100); // Adjust the delay as needed
}

Any suggestions or thoughts welcome.

This topic was automatically closed 180 days after the last reply. New replies are no longer allowed.