IoT garden irrigation for less than 30€

(1 comment)

MQTT irrigation control with Arduino nano and Ethernet shield

My garden irrigation was driven by a six-zone traditional controller (I bought one made by Signature a few years ago), where I would use only four zones, each of which with 4 or 5 sprinklers.

The water comes from a well, equipped with a submersible pump and an expansion vessel with a pressure switch: the pressure switch turns on the pump when the pressure is below an adjustable threshold, and turns the pump off when the pressure is above another (mechanically) adjustable threshold.

A pressure switch failure one year ago caused the pump to run without water flow long enough to break and both the pressure switch and the pump had to be replaced.

The six-zone controller was not driving the pump, and was not connected to the Internet.

Target system

How nice would it be to build an enhanced new controller with the missing features?

  1. Control the pump, in order to minimize the likelyhood of the pump to break again
  2. Connect and receive commands from the Internet (e.g. from a cloud server)
  3. Control the water flow so that the pressure switch is not triggered during a normal irrigation cycle
  4. Program the irrigation schedule and reconfigure the irrigation cycles from an Internet connected PC
  5. Cost less than the previous controller!

mounted prototype

System architecture

The pump and (up to 7) electrovalves are driven by the 8-channel relay module, which in turn receives the corresponding logical signals from 8 digital PINs of an Arduino Nano.

Three additional PINs of the Nano are used by the Ethernet shield, which is connected to the home LAN.

fritzing bredboard schematic

A server is hosting an MQTT broker (in my case, a Linux cloud server running mosquitto).

By subscribing to the “irrigation_cmd” MQTT topic, the Nano waits for commands and is ready to execute them.

Furthermore, the Nano can publish to the “irrigation_evt” MQTT topic, reporting its status: it can do so spontaneously, or in response to a command issued on the “irrigation_cmd” topic.

 

The system needs to behave safely in case of server failure or network issues, which could happen in the middle of an irrigation cycle; in other words, we need to avoid the water from being sprinkled forever, if we miss the commands that turn off the pump and the electrovalves. This is easily accomplished in the Arduino code, by checking at regular intervals if we did not receive any commands during the last hour, and returning every relay to the off position when such condition is met.

 

Finally, a NodeRed instance is run on the server, and a NodeRed flow is used to edit the schedule and duration of irrigation cycles.

BoM (Bill of Material)

Arduino Nano 3.0 compatible 3-5 EUR

nano Ethernet shield (ENC28J60 based) 3-4 EUR

8-channel relay module with optocouplers 4-5 EUR

 

220 to 24v ac transformer (reuse transformer from original irrigation controller), for driving electrovalves

220v ac to 9v dc power supply connected to breadboard power regulator (MB102 Breadboard Power Supply Module, 2 EUR), which in turn powers both Arduino nano and the relay module

 

plastic box 6 EUR + wiring accessories 5 EUR

 

Total expense: less than 30 €

(ok, that’s the capex; the opex, i.e. the 6 € monthly fee for the cloud server, was not considered, because the server can do a lot more things in addition to irrigation control!)

Arduino sketch

#include <SPI.h>
#include <UIPEthernet.h>
#include <PubSubClient.h>

#define clientId "nanoeth1"
#define subTopic "irrigation_cmd"
#define pubTopic "irrigation_evt"
#define mqttUser "mqtt_user"
#define mqttPwd "mqtt_pwdxxxxx"
#define mqttServer "mqtt.example.com"

EthernetClient ethclient;
PubSubClient mclient(ethclient);


byte mac[] = { 0xDE, 0xAD, 0xBE, 0xEF, 0x00, 0x00 }; // made up MAC addr
byte ip[] = { 192, 168, 1, 15 }; // Arduino statically assigned IP addr
byte server[] = { 10, 10, 10, 10}; // MQTT broker IP addr (not used)
byte dns_ip[] = {192, 168, 1, 1};
byte gw[] = {192, 168, 1, 1};
byte mask[] = {255, 255, 255, 0};

unsigned long cur_msec;
unsigned long last_cmd_msec = 0;
unsigned long safety_idle_msec = 3600000;

// eth shield used pins: D10, D11, D12, D13
// --> D2..D9 left for other uses
// A0 to A5 are D14 to D19 (to be verified)

// D2---IN1 (relay module)
// D3---IN2
// ...
// D9---IN8

int stayConnectedSubscribed() {
  if (!mclient.connected()) {
    mclient.connect(clientId, mqttUser, mqttPwd);
    mclient.subscribe(subTopic);
  }
  if (mclient.connected()) return 1;
  else return 0;
}

int mqtt_pub_cstr(const char* data) {
  if (mclient.connected()) {
    mclient.publish(pubTopic, data);
    return 1;
  }
  return 0;
}

void report_status() {
  char msg[80] = "";
  char subm[10] = "";
  for (int i=2; i<10; i++) {
    if (i != 2) strcat(msg, ",");
    if (digitalRead(i)) {
      sprintf(subm, "%d:off", i);
    }
    else {
      sprintf(subm,"%d:on", i);
    }
    strcat(msg, subm);
  }
  mqtt_pub_cstr(msg);
}

// expected msg examples: "3:on" "5:off" "2:on,3:off"
// or "get_status"
void mcallback(char* topic, byte* payload, unsigned int len) {
  if (len>80) return;
  char input[len + 1];
  strncpy(input, (const char*)payload, len);
  // Add the final 0 to end the C string
  input[len] = 0;
  mqtt_pub_cstr("command_received");

  // Read each command pair
  char* cmd = strtok(input, ",");
  while (cmd != 0) {
    // Split the command in two values
    char* sep = strchr(cmd, ':');
    if (sep != 0) {
        // split id/value pairs in two strings, by replacing ':' with 0
        *sep = 0;
        int pinNum = atoi(cmd);
        ++sep;
        char* pinVal = sep;

        // change pinNum to output pinVal ("on" is LOW)
        if (strcmp(pinVal, "on") == 0) digitalWrite(pinNum, LOW);
        else digitalWrite(pinNum, HIGH);
    }
    else { // no ':' separator
      if (strcmp(cmd, "get_status") == 0) report_status();
    }
    // Find the next command in input string
    cmd = strtok(0, ",");
  }
  last_cmd_msec = millis();
}


void reset_to_idle_status() {
  for (int i=2; i<10; i++) {
    digitalWrite(i, HIGH); // relay module LEDs turned off
  }
  stayConnectedSubscribed();
  mqtt_pub_cstr("reset_to_idle_status");
}

void check_idle_time() {
  cur_msec = millis();
  if (cur_msec - last_cmd_msec > safety_idle_msec) {
    reset_to_idle_status();
    last_cmd_msec = cur_msec;
  }
}

void setup() {
  Serial.begin(9600);
  Serial.println("serial init done");
  Ethernet.begin(mac, ip, dns_ip, gw, mask);
  Serial.println("ethernet init done");
  for (int i=2; i<10; i++) {
    pinMode(i, OUTPUT);
    digitalWrite(i, HIGH); // relay module LEDs turned off
  }
  Serial.println("digital pins init done");
 
  mclient.setServer(mqttServer, 1883);
  mclient.setCallback(mcallback);
  stayConnectedSubscribed();
  mqtt_pub_cstr("initial_setup_completed");
}


void loop() {
  mclient.loop();
  delay(40);
  check_idle_time();
}

NodeRed flow

Currently unrated

Comments

JONKE177 3 years, 3 months ago

Thank you!!

Link | Reply
Currently unrated

New Comment

required

required (not published)

optional

required