IoT Garden Watering With Arduino ESP32 & Openweathermap
by Jonathan Robson in Circuits > Arduino
2899 Views, 39 Favorites, 0 Comments
IoT Garden Watering With Arduino ESP32 & Openweathermap
I have a small garden irrigation system of sprayers and drippers that was being controlled with rain and temperature sensors to water the garden as required, year round with time from an RTC. The rain sensors kept failing so I upgraded to this system taking weather info from openweathermap and date and time from NTP.
It waters every other day in spring and autumn and every day in summer but ONLY on days that it hasn't rained. In winter we have some fern trees that like to be watered all year so it waters those with drippers set under the frost protection material, but only if the temperature is above freezing to avoid trying to water through frozen pipes.
The NTP server time toggles automatically between GMT and BST.
Rain is logged by simply setting an LED to HIGH when openweathermap records rain. At 6pm the loop checks to see if the LED is LOW or HIGH. If LOW, it runs the correct watering programme depending on the date. If HIGH, the LED is reset to LOW, ready for the next 24 hour period.
The L298N module includes a very handy 5v step-down jumper so you can input 12v for the solenoid but also output 5v to run the ESP32.
We also have a problem with the neighbourhood cats hiding in the bushes to ambush birds coming for food and water so a CatFlush function was added. We can turn on the system from the kitchen and flush cats out of the borders.
Supplies
ESP32
L298N
12v input
128x64 OLED
Toggle switch
LED x 3 colours + resistors
Solenoid Valve
Circuit
Here's the circuit
Code
/*
Jonathan Robson 2023
Board: ESP32-WROOM-DA Module. Stuff that helped:
http://arduino-er.blogspot.com/2020/05/esp8266-nodemcu-get-current-weather.html (openweathermap + json)
https://how2electronics.com/internet-clock-esp32-lcd-display-ntp-client/ (readable time and date from NTP)
https://simple-circuit.com/arduino-gps-clock-local-time-neo-6m/ (Auto change to BST. With ChatGPT solution in the if statement)
https://randomnerdtutorials.com/esp32-dc-motor-l298n-motor-driver-control-speed-direction/ (L298)
*/
#include <WiFi.h>
#include <ArduinoJson.h>
#include <Wire.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>
#include <NTPClient.h>
#include <WiFiUdp.h>
#include <TimeLib.h>
#include "arduino_secrets.h"
#define time_GMT 0
#define time_BST 3600
const int waterOn = 14; // Water valve
const int rainLED = 5; // rain LED - red
const int tempLED = 4; // temperature LED - blue
const int valveLED = 12; // Valve LED - green
const int switchLED = 13; // override switch
Adafruit_SSD1306 display(128, 64, &Wire, -1);
char ssid[] = SECRET_SSID; // network SSID (name) Main
char password[] = SECRET_PSW; // network PASSWORD () Main
//char ssid[] = SECRET_SSID1; // network SSID (name) Devolo
//char password[] = SECRET_PSW1; // network PASSWORD () Devolo
String apiKey = SECRET_APIKEY; // SECRET_APIKEY;
String CityID = "2643741"; // Penge, SE London
boolean updateWeather = true;
bool id = false;
WiFiClient client;
char servername[] = "api.openweathermap.org";
String result;
WiFiUDP ntpUDP;
NTPClient timeClient(ntpUDP, "pool.ntp.org"); // GMT
char Time[] = "TIME:00:00:00";
char Date[] = "DATE:00/00/2000";
byte last_second, second_, minute_, hour_, day_, month_;
int year_;
void setup() {
pinMode(valveLED, OUTPUT); // set the valve LED pin out so that its state can be read
pinMode(rainLED, OUTPUT); // set the rain LED pin out so that its state can be read
pinMode(tempLED, OUTPUT); // set the temp LED pin out so that its state can be read
pinMode(waterOn, OUTPUT); // set pin 14 as output to turn water on
pinMode(switchLED, OUTPUT); // valve override (catFlush) on pin 13
Serial.begin(115200);
Serial.print("Connecting to ");
WiFi.mode(WIFI_STA);
Serial.println(ssid);
WiFi.begin(ssid, password);
display.begin(SSD1306_SWITCHCAPVCC, 0x3C);
delay(200);
display.clearDisplay();
display.setTextColor(SSD1306_WHITE);
display.setCursor(0, 0);
display.print("Connecting.");
display.display();
while (WiFi.status() != WL_CONNECTED) {
delay(500);
Serial.print(".");
display.print(".");
display.display();
}
Serial.println("");
Serial.println("IP address: ");
Serial.println(WiFi.localIP());
display.clearDisplay();
display.setCursor(0, 0);
display.println("Connected ");
display.println("IP Address: ");
display.println(WiFi.localIP());
display.display();
delay(1000);
display.clearDisplay();
timeClient.begin();
}
void loop()
{
timeClient.update();
// switch between GMT and BST time zones based on a condition
int offset = time_GMT; // set the default offset to GMT
if (month_ >= 4 && month_ <= 10) { // if April 1st to October 31st
offset = time_BST; // set the offset to BST
}
// apply the offset to the current time
time_t adjustedTime = timeClient.getEpochTime() + offset;
unsigned long unix_epoch = timeClient.getEpochTime() + offset;
second_ = second(unix_epoch);
if (last_second != second_) {
minute_ = minute(unix_epoch);
hour_ = hour(unix_epoch);
day_ = day(unix_epoch);
month_ = month(unix_epoch);
year_ = year(unix_epoch);
Time[12] = second_ % 10 + 48;
Time[11] = second_ / 10 + 48;
Time[9] = minute_ % 10 + 48;
Time[8] = minute_ / 10 + 48;
Time[6] = hour_ % 10 + 48;
Time[5] = hour_ / 10 + 48;
Date[5] = day_ / 10 + 48;
Date[6] = day_ % 10 + 48;
Date[8] = month_ / 10 + 48;
Date[9] = month_ % 10 + 48;
Date[13] = (year_ / 10) % 10 + 48;
Date[14] = year_ % 10 % 10 + 48;
Serial.println(Time);
Serial.println(Date);
display.setCursor(0, 46);
display.setTextSize(1);
display.println(Date);
display.setCursor(0, 56);
display.println(Time);
last_second = second_;
display.display();
}
// Get a weather update every half hour //
if ((minute_ == 0 && second_ == 0) || (minute_ == 30 && second_ == 0)) {
updateWeather = true;
}
if (updateWeather == true) {
getWeather();
updateWeather = false;
}
// catFlush. Turn on watering to get neighbourhood cats out of the borders :-)
while (digitalRead(switchLED) == HIGH) { // read override switch LED state
digitalWrite(waterOn, HIGH); // turn it on
digitalWrite(valveLED, HIGH);
if (digitalRead(switchLED) == LOW) { // turn it off
digitalWrite(waterOn, LOW);
digitalWrite(valveLED, LOW);
}
}
// Spring watering schedule. March 1st to May 31st. EVERY OTHER DAY //
if ((month_ >= 3 && month_ <= 5) && day_ % 2 == 1 && hour_ == 18 && minute_ == 5 && second_ == 0) { // if odd days and time 6.05pm (avoid clash with 6pm weather update)
if (digitalRead(rainLED) == HIGH) { // if it rained..
digitalWrite(rainLED, LOW); // turn off rain LED
delay(1000);
} else if (digitalRead(rainLED) == LOW) { // if it didn't rain...
digitalWrite(waterOn, HIGH); // turn on valve
digitalWrite(valveLED, HIGH); // turn on valve LED
delay(1000);
}
}
if ((month_ >= 3 && month_ <= 5) && day_ % 2 == 1 && hour_ == 18 && minute_ == 10 && second_ == 0 && digitalRead(valveLED) == HIGH) { // turn water off after 5 minutes
digitalWrite(waterOn, LOW);
digitalWrite(valveLED, LOW);
delay(500);
}
// Summer watering schedule. June 1st to Sept 30th. EVERY DAY //
if ((month_ >= 6 && month_ <= 9) && hour_ == 18 && minute_ == 5 && second_ == 0) { // if time 6.05pm (avoid clash with 6pm weather update)
if (digitalRead(rainLED) == HIGH) { // if it rained..
digitalWrite(rainLED, LOW); // turn off rain LED
delay(1000);
} else if (digitalRead(rainLED) == LOW) { // if it didn't rain...
digitalWrite(waterOn, HIGH); // turn on valve
digitalWrite(valveLED, HIGH); // turn on valve LED
delay(1000);
}
}
if ((month_ >= 6 && month_ <= 9) && hour_ == 18 && minute_ == 15 && second_ == 0 && digitalRead(valveLED) == HIGH) { // turn water off after 10 minutes
digitalWrite(waterOn, LOW);
digitalWrite(valveLED, LOW);
delay(500);
}
// Autumn watering schedule. Oct 1st to Nov 30th. EVERY OTHER DAY //
if ((month_ == 10 || month_ == 11) && day_ % 2 == 1 && hour_ == 16 && minute_ == 5 && second_ == 0) { // if odd days and time 4.05pm (avoid clash with 6pm weather update)
if (digitalRead(rainLED) == HIGH) { // if it rained..
digitalWrite(rainLED, LOW); // turn off rain LED
delay(1000);
} else if (digitalRead(rainLED) == LOW) { // if it didn't rain...
digitalWrite(waterOn, HIGH); // turn on valve
digitalWrite(valveLED, HIGH); // turn on valve LED
delay(1000);
}
}
if ((month_ == 10 || month_ == 11) && day_ % 2 == 1 && hour_ == 16 && minute_ == 10 && second_ == 0 && digitalRead(valveLED) == HIGH) { // turn water off after 5 minutes
digitalWrite(waterOn, LOW);
digitalWrite(valveLED, LOW);
delay(500);
}
// Winter watering schedule. Dec 1st to Feb 31st. ONCE A WEEK //
if ((month_ == 12 || month_ <= 2) && (day_ == 1 || day_ == 8 || day_ == 15 || day_ == 22) && hour_ == 12 && minute_ == 5 && second_ == 0) { // ONCE A WEEK. if time 12.05pm (avoid clash with noon weather update)
if (digitalRead(tempLED) == HIGH) { // if it froze..
digitalWrite(tempLED, LOW); // turn off temp LED
delay(1000);
} else if (digitalRead(tempLED) == LOW) { // if it didn't freeze...
digitalWrite(valveLED, HIGH); // turn on valve LED
digitalWrite(waterOn, HIGH); // turn on valve
delay(1000);
}
}
if ((month_ == 12 || month_ <= 2) && (day_ == 1 || day_ == 8 || day_ == 15 || day_ == 22) && hour_ == 12 && minute_ == 10 && second_ == 0 && digitalRead(valveLED) == HIGH) { // ONCE A WEEK. turn water off after 5 minutes
digitalWrite(valveLED, LOW);
digitalWrite(waterOn, LOW);
delay(500);
}
// reset LEDs every evening, regardless of watering schedule
if (hour_ == 18 && minute_ == 28 && second_ == 0) { // if time 18.28pm
if (digitalRead(rainLED) == HIGH) { // if it rained..
digitalWrite(rainLED, LOW); // turn off rain LED
delay(1000);
}
if (hour_ == 18 && minute_ == 28 && second_ == 30) { // if time 18.28pm
if (digitalRead(tempLED) == HIGH) { // if it froze..
digitalWrite(tempLED, LOW); // turn off temp LED
delay(1000);
}
}
}
}
// Weather update //
void getWeather() {
if (client.connect(servername, 80)) {
client.println("GET /data/2.5/weather?id=" + CityID + "&units=metric&APPID=" + apiKey);
client.println("Host: api.openweathermap.org");
client.println("User-Agent: ArduinoWiFi/1.1");
client.println("Connection: close");
client.println();
} else {
Serial.println("connection failed");
Serial.println();
}
while (client.connected() && !client.available())
delay(1);
while (client.connected() || client.available()) {
char c = client.read();
result = result + c;
}
client.stop();
result.replace('[', ' ');
result.replace(']', ' ');
char jsonArray[result.length() + 1];
result.toCharArray(jsonArray, sizeof(jsonArray));
jsonArray[result.length() + 1] = '\0';
StaticJsonDocument<1024> doc;
DeserializationError error = deserializeJson(doc, jsonArray);
if (error) {
Serial.print(F("deserializeJson() failed: "));
Serial.println(error.f_str());
return;
}
const char* weather_0_main = doc["weather"][0]["main"]; // eg "Clouds"
float main_temp = doc["main"]["temp"];
String weather = doc["weather"]["main"]; // eg "Clouds"
int temperature = doc["main"]["temp"];
Serial.println();
Serial.println(weather);
Serial.printf("Temp: %d°C\r\n", temperature);
display.clearDisplay();
display.setCursor(0, 0);
display.setTextSize(2);
display.setTextColor(SSD1306_WHITE, SSD1306_BLACK);
display.print(weather);
display.setCursor(0, 8);
display.println();
display.print(temperature);
display.print((char)247);
display.print("C ");
if (weather == "Rain") { // if "Rain"...
digitalWrite(rainLED, HIGH); // turn on rain LED
}
if (temperature <= 0) { // if temp 0°C or below...
digitalWrite(tempLED, HIGH); // turn on temp LED
}
}