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.
How nice would it be to build an enhanced new controller with the missing features?
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.
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.
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!)
#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();
}
Share on Twitter Share on Facebook
Comments
JONKE177 3 years, 11 months ago
Thank you!!
Link | ReplyNew Comment