#include <Wire.h>
#include <LiquidCrystal_I2C.h>
static const int PIN_SOIL = 34;
static const int PIN_LED_R = 25;
static const int PIN_LED_Y = 26;
static const int PIN_LED_G = 27;
static const int PIN_RELAY = 32;
static const int PIN_BUZZ = 33;
static const int DANGER_MAX = 30;
static const int WARN_MAX = 60;
static const unsigned long PUMP_RUN_MS = 8000;
static const unsigned long PUMP_COOLDOWN_MS = 12000;
static const int RELAY_ON = HIGH;
static const int RELAY_OFF = LOW;
bool pumpOn = false;
unsigned long pumpStopAt = 0;
unsigned long nextPumpAllowedAt = 0;
bool beepState = false;
unsigned long lastBeepToggle = 0;
int lastMode = -1;
uint8_t lcdAddr = 0x27;
LiquidCrystal_I2C lcd(0x27, 16, 2);
static bool i2cPing(uint8_t addr) {
Wire.beginTransmission(addr);
return (Wire.endTransmission() == 0);
}
static int moisturePercentFromRaw(int raw) {
// In Wokwi, 3.3V behaves like ~2703 counts if ADC full-scale is 5V
const int ADC_MAX_FOR_3V3 = 2703;
raw = constrain(raw, 0, ADC_MAX_FOR_3V3);
long pct = 100L - (long)raw * 100L / ADC_MAX_FOR_3V3; // wet -> higher %
pct = constrain((int)pct, 0, 100);
return (int)pct;
}
static const char* statusFromPct(int pct) {
if (pct < DANGER_MAX) return "DANGER";
if (pct < WARN_MAX) return "WARN";
return "SAFE";
}
static void setIndicators(int pct) {
digitalWrite(PIN_LED_R, (pct < DANGER_MAX) ? HIGH : LOW);
digitalWrite(PIN_LED_Y, (pct >= DANGER_MAX && pct < WARN_MAX) ? HIGH : LOW);
digitalWrite(PIN_LED_G, (pct >= WARN_MAX) ? HIGH : LOW);
}
// RED: continuous high tone
// YELLOW: slow beeps (lower tone)
// SAFE: off
static void buzzerByLevel(int pct) {
int mode;
if (pct < DANGER_MAX) mode = 2;
else if (pct < WARN_MAX) mode = 1;
else mode = 0;
if (mode != lastMode) {
lastMode = mode;
beepState = false;
lastBeepToggle = millis();
noTone(PIN_BUZZ);
}
if (mode == 0) {
noTone(PIN_BUZZ);
return;
}
if (mode == 2) {
tone(PIN_BUZZ, 2000);
return;
}
const unsigned long intervalMs = 500;
const int freq = 1200;
unsigned long now = millis();
if (now - lastBeepToggle >= intervalMs) {
lastBeepToggle = now;
beepState = !beepState;
if (beepState) tone(PIN_BUZZ, freq);
else noTone(PIN_BUZZ);
}
}
static void pumpControl(int pct) {
unsigned long now = millis();
if (pumpOn && now >= pumpStopAt) {
pumpOn = false;
digitalWrite(PIN_RELAY, RELAY_OFF);
nextPumpAllowedAt = now + PUMP_COOLDOWN_MS;
}
if (!pumpOn && pct < DANGER_MAX && now >= nextPumpAllowedAt) {
pumpOn = true;
digitalWrite(PIN_RELAY, RELAY_ON);
pumpStopAt = now + PUMP_RUN_MS;
}
}
static void lcdShow(int pct, const char* st) {
lcd.setCursor(0, 0);
lcd.print("Moist: ");
if (pct < 100) lcd.print(" ");
if (pct < 10) lcd.print(" ");
lcd.print(pct);
lcd.print("% ");
lcd.setCursor(0, 1);
lcd.print("St:");
lcd.print(st);
lcd.print(" Pump:");
lcd.print(pumpOn ? "ON " : "OFF");
}
void setup() {
Serial.begin(115200);
pinMode(PIN_LED_R, OUTPUT);
pinMode(PIN_LED_Y, OUTPUT);
pinMode(PIN_LED_G, OUTPUT);
pinMode(PIN_RELAY, OUTPUT);
digitalWrite(PIN_RELAY, RELAY_OFF);
pinMode(PIN_BUZZ, OUTPUT);
noTone(PIN_BUZZ);
analogReadResolution(12);
Wire.begin(21, 22);
if (i2cPing(0x27)) lcdAddr = 0x27;
else if (i2cPing(0x3F)) lcdAddr = 0x3F;
lcd = LiquidCrystal_I2C(lcdAddr, 16, 2);
lcd.init();
lcd.backlight();
lcd.clear();
lcd.setCursor(0, 0);
lcd.print("Smart Watering");
lcd.setCursor(0, 1);
lcd.print("Ready");
delay(800);
lcd.clear();
}
void loop() {
int rawSum = 0;
for (int i = 0; i < 8; i++) rawSum += analogRead(PIN_SOIL);
int raw = rawSum / 8;
int pct = moisturePercentFromRaw(raw);
const char* st = statusFromPct(pct);
setIndicators(pct);
buzzerByLevel(pct);
pumpControl(pct);
lcdShow(pct, st);
Serial.print("raw=");
Serial.print(raw);
Serial.print(" pct=");
Serial.print(pct);
Serial.print("% st=");
Serial.print(st);
Serial.print(" pump=");
Serial.println(pumpOn ? "ON" : "OFF");
delay(200);
}