#include <Adafruit_GFX.h>
#include <Adafruit_ILI9341.h>
#include <DHT.h>
#include <Keypad.h>
#include <SPI.h>
// --- PIN DEFINITIONS ---
#define SOIL_PIN 32
#define DHT_PIN 4
#define DHT_TYPE DHT22
#define TFT_CS 5
#define TFT_DC 2
#define TFT_RST 15
#define RED_PIN 12
#define GREEN_PIN 13
#define BLUE_PIN 14
#define WATER_PIN 27
// DHT sensor
DHT dht(DHT_PIN, DHT_TYPE);
// TFT display
Adafruit_ILI9341 tft(TFT_CS, TFT_DC, TFT_RST);
// Keypad setup
const byte ROWS = 4, COLS = 4;
char keys[ROWS][COLS] = {
{'1', '2', '3', 'A'},
{'4', '5', '6', 'B'},
{'7', '8', '9', 'C'},
{'*', '0', '#', 'D'}
};
byte rowPins[ROWS] = {16, 17, 19, 33};
byte colPins[COLS] = {21, 22, 25, 26};
Keypad keypad = Keypad(makeKeymap(keys), rowPins, colPins, ROWS, COLS);
// Modes
enum Mode { DRY, MOIST, WET, CUSTOM };
Mode currentMode = MOIST;
int minMoisture = 30, maxMoisture = 50;
// Moisture log
struct LogEntry {
unsigned long timestamp;
int moisture;
};
LogEntry logData[1440]; // 24 hours of data
int logIndex = 0;
unsigned long lastLogTime = 0;
unsigned long lastSerialTime = 0;
// Mode change log
struct ModeChange {
unsigned long timestamp;
Mode mode;
};
ModeChange modeHistory[20];
int modeChangeIndex = 0;
bool watering = false;
unsigned long wateringStartTime = 0;
unsigned long lastWateringDuration = 0;
int minLogged = 100, maxLogged = 0;
int moisture = 0;
float temp = 0;
float hum = 0;
void setup() {
Serial.begin(115200);
Serial.println("Booting...");
dht.begin();
delay(1000);
tft.begin();
tft.setRotation(3);
tft.fillScreen(ILI9341_BLACK);
tft.setTextColor(ILI9341_WHITE);
tft.setTextSize(2);
tft.setCursor(20, 100);
tft.println("Initializing...");
pinMode(RED_PIN, OUTPUT);
pinMode(GREEN_PIN, OUTPUT);
pinMode(BLUE_PIN, OUTPUT);
pinMode(WATER_PIN, OUTPUT);
Serial.println("Setup complete.");
delay(1000);
}
void loop() {
handleModeInput();
static unsigned long lastSensorRead = 0;
if (millis() - lastSensorRead >= 2000) {
moisture = analogRead(SOIL_PIN) * 100 / 4095;
temp = dht.readTemperature();
hum = dht.readHumidity();
if (isnan(temp) || isnan(hum)) {
Serial.println("DHT22 failed");
temp = 0;
hum = 0;
}
updateLED(moisture);
updateDisplay(moisture, temp, hum);
lastSensorRead = millis();
}
if (millis() - lastLogTime >= 60000) {
logData[logIndex++] = {millis(), moisture};
logIndex %= 1440;
recalculateMinMax();
lastLogTime = millis();
}
if (millis() - lastSerialTime >= 5000) {
Serial.printf("Moisture: %d%%, Temp: %.1fC, Hum: %.1f%% - ", moisture, temp, hum);
if (moisture < minMoisture || moisture > maxMoisture)
Serial.println("MOISTURE issue");
else
Serial.println("OK");
Serial.printf("Min 24h: %d%%, Max 24h: %d%%\n", minLogged, maxLogged);
lastSerialTime = millis();
}
}
void handleModeInput() {
char key = keypad.getKey();
if (!key) return;
Serial.print("Key pressed: ");
Serial.println(key);
switch (key) {
case 'A':
currentMode = DRY;
minMoisture = 10;
maxMoisture = 20;
break;
case 'B':
currentMode = MOIST;
minMoisture = 30;
maxMoisture = 50;
break;
case 'C':
currentMode = WET;
minMoisture = 60;
maxMoisture = 80;
break;
case 'D':
currentMode = CUSTOM;
bool validRange = false;
while (!validRange) {
tft.fillScreen(ILI9341_BLACK);
tft.setCursor(0, 0);
tft.setTextColor(ILI9341_YELLOW);
tft.setTextSize(2);
tft.println("CUSTOM MODE");
tft.println("Enter Min %:");
minMoisture = getKeypadNumber();
tft.println("\nEnter Max %:");
maxMoisture = getKeypadNumber();
if (minMoisture >= maxMoisture) {
tft.fillScreen(ILI9341_BLACK);
tft.setCursor(0, 0);
tft.setTextColor(ILI9341_RED);
tft.setTextSize(2);
tft.println("Invalid Range!");
tft.println("Min >= Max");
tft.println("Try Again...");
delay(2000);
} else {
validRange = true;
Serial.printf("Custom min: %d, max: %d\n", minMoisture, maxMoisture);
}
}
break;
}
modeHistory[modeChangeIndex++] = {millis(), currentMode};
modeChangeIndex %= 20;
}
int getKeypadNumber() {
String input = "";
char k;
while (true) {
k = keypad.getKey();
if (k >= '0' && k <= '9') {
input += k;
tft.print(k);
} else if (k == '#') {
break;
}
delay(100);
}
return input.toInt();
}
void updateLED(int moisture) {
bool shouldWater = (moisture < minMoisture || moisture > maxMoisture);
if (shouldWater && !watering) {
wateringStartTime = millis();
}
if (!shouldWater && watering) {
lastWateringDuration = millis() - wateringStartTime;
}
watering = shouldWater;
digitalWrite(WATER_PIN, watering ? HIGH : LOW);
digitalWrite(RED_PIN, watering ? HIGH : LOW);
digitalWrite(GREEN_PIN, !watering ? HIGH : LOW);
digitalWrite(BLUE_PIN, LOW);
}
void updateDisplay(int moisture, float temp, float hum) {
tft.fillScreen(ILI9341_BLACK);
tft.setCursor(0, 0);
tft.setTextSize(2);
tft.setTextColor(ILI9341_GREEN);
tft.printf("Moisture: %d%%\n", moisture);
tft.printf("Temp: %.1f C\n", temp);
tft.printf("Hum: %.1f %%\n", hum);
tft.print("Mode: ");
switch (currentMode) {
case DRY: tft.println("DRY"); break;
case MOIST: tft.println("MOIST"); break;
case WET: tft.println("WET"); break;
case CUSTOM: tft.println("CUSTOM"); break;
}
tft.printf("Min 24h: %d%%\n", minLogged);
tft.printf("Max 24h: %d%%\n", maxLogged);
if (watering) {
tft.setTextColor(ILI9341_RED);
tft.println("WATERING...");
} else {
tft.setTextColor(ILI9341_BLUE);
tft.println("Stable Watering Stopped");
if (lastWateringDuration > 0) {
int mins = (lastWateringDuration / 60000) % 60;
int hrs = (lastWateringDuration / 3600000);
tft.setTextColor(ILI9341_MAGENTA);
tft.printf("Last Water: %02dh:%02dm\n", hrs, mins);
}
}
tft.setCursor(0, 200);
tft.setTextColor(ILI9341_CYAN);
tft.setTextSize(1);
tft.println("Press A: DRY | B: MOIST");
tft.println("Press C: WET | D: CUSTOM");
}
void recalculateMinMax() {
minLogged = 100;
maxLogged = 0;
for (int i = 0; i < 1440; i++) {
if (logData[i].timestamp == 0) continue;
minLogged = min(minLogged, logData[i].moisture);
maxLogged = max(maxLogged, logData[i].moisture);
}
}