/*
Jonathan Robson 2025 updated from 2023 OpenWeatherMap 2.5 to 3.0
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)
https://arduinojson.org/v7/tutorial/deserialization/
*/
#include <WiFi.h>
#include <ArduinoJson.h> // 7.4.2
#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 CommunityFibre
char password[] = SECRET_PSW; // network PASSWORD CommunityFibre
//char ssid[] = SECRET_SSID1; // network SSID Devolo
//char password[] = SECRET_PSW1; // network PASSWORD Devolo
String apiKey = SECRET_APIKEY; // SECRET_APIKEY;
//String CityID = "2643741"; // Penge, SE London
// String openweather_lat = "51.4147"; // Penge, SE London
//String openweather_lon = "-0.0534";
String lat = "51.4147000"; // lat/lon for Penge, SE London, on OpenweatherMap
String lon = "-0.0534000";
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. Openweathermap 3.0 OneCall. Max 1,000 calls / 24 hrs //
if ((minute_ == 0 && second_ == 20) || (minute_ == 20 && second_ == 20) || (minute_ == 40 && second_ == 20)) { //every 20 minutes
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. April 1st to May 31st. //
if ((month_ >= 4 && month_ <= 5) && hour_ == 18 && minute_ == 0 && second_ == 0) { // if time 6pm
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_ >= 4 && month_ <= 5) && hour_ == 18 && minute_ == 5 && 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. //
if ((month_ >= 6 && month_ <= 9) && hour_ == 18 && minute_ == 0 && second_ == 0) { // if time 6pm
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_ == 10 && second_ == 0 && digitalRead(valveLED) == HIGH) { // turn water off after 10 minutes
digitalWrite(waterOn, LOW);
digitalWrite(valveLED, LOW);
delay(500);
}
// Winter1 watering schedule. Oct 1st to Dec 31st. //
if (month_ >= 10 && hour_ == 16 && minute_ == 0 && second_ == 0) { // if time 4pm
if (digitalRead(rainLED) == LOW) { // if it didn't rain...
digitalWrite(waterOn, HIGH); // turn on valve
digitalWrite(valveLED, HIGH); // turn on valve LED
delay(1000);
}
else if (digitalRead(rainLED) == HIGH) { // if it rained..
digitalWrite(rainLED, LOW); // turn off rain LED
delay(1000);
}
else if (digitalRead(tempLED) == HIGH) { // if it froze..
digitalWrite(tempLED, LOW); // turn off temp LED
delay(1000);
}
}
if (month_ >= 10 && hour_ == 16 && minute_ == 3 && second_ == 0 && digitalRead(valveLED) == HIGH) { // turn water off after 3 minutes
digitalWrite(waterOn, LOW);
digitalWrite(valveLED, LOW);
delay(500);
}
// Winter2 watering schedule. Jan 1st to March 31st. //
if (month_ <= 3 && hour_ == 16 && minute_ == 0 && second_ == 0) { // if time 4pm
if (digitalRead(rainLED) == LOW) { // if it didn't rain...
digitalWrite(waterOn, HIGH); // turn on valve
digitalWrite(valveLED, HIGH); // turn on valve LED
delay(1000);
}
else if (digitalRead(rainLED) == HIGH) { // if it rained..
digitalWrite(rainLED, LOW); // turn off rain LED
delay(1000);
}
else if (digitalRead(tempLED) == HIGH) { // if it froze..
digitalWrite(tempLED, LOW); // turn off temp LED
delay(1000);
}
}
if (month_ <= 3 && hour_ == 16 && minute_ == 3 && second_ == 0 && digitalRead(valveLED) == HIGH) { // turn water off after 3 minutes
digitalWrite(waterOn, LOW);
digitalWrite(valveLED, LOW);
delay(500);
}
}
// Weather update //
void getWeather() {
if (client.connect(servername, 80)) {
client.println("GET /data/3.0/onecall/weather?id=" + apiKey + lat + lon + "&units=metric&APPID="); // 3.0
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';
JsonDocument doc;
DeserializationError error = deserializeJson(doc, jsonArray);
if (error) {
Serial.print("deserializeJson() failed: ");
Serial.println(error.c_str());
return;
}
bool weather_0_description = doc["weather"][0]["description"]; // true
bool main_temp = doc["main"]["temp"]; // true
// const char* weather_0_main = doc["weather"][0]["description"]; // eg "Clouds"
// float main_temp = doc["main"]["temp"];
String weather = doc["weather"]["description"]; // eg "Clouds"
int temperature = doc["main"]["temp"];
Serial.println();
Serial.println(weather);
Serial.printf("Temp: %d°C\r\n", main_temp);
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(main_temp);
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
}
}