IoT Garden Watering With Arduino ESP32 & Openweathermap

by Jonathan Robson in Circuits > Arduino

2762 Views, 37 Favorites, 0 Comments

IoT Garden Watering With Arduino ESP32 & Openweathermap

IMG_6794 Large.jpeg
IMG_6331 Large.jpeg
IMG_6335 Large.jpeg
20170412_171428.jpg
20170412_171257.jpg
20170412_171237.jpg

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

Screenshot 2023-06-06 at 16.02.20.png

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

}

}