/*
Plant Monitor – ESP32 + DHT11 + LCD (I2C) + Soil Moisture + PIR + RGB + Buzzer
- RGB logic by soil value (ESP32 ADC 0..4095):
soil < 1700 -> Blue (too wet)
1700..2300 -> Green (OK)
soil > 2300 -> Red (too dry)
- Buzzer: Dry harmony when soil dry, PIR harmony when motion (PIR overrides)
- LCD: T/H on line 1, Soil raw value on line 2
- Uses analogWrite + tone/noTone (works in current ESP32 Arduino core & Wokwi)
Wiring (ESP32):
DHT11 data -> GPIO 13
Soil sensor analog out -> GPIO 34 (ADC1)
PIR OUT -> GPIO 14
RGB LED: R->GPIO 18, G->GPIO 19, B->GPIO 23 (each via 220-330Ω resistor)
Buzzer -> GPIO 27
LCD I2C: SDA->GPIO 21, SCL->GPIO 22, VCC->5V, GND->GND
*/
#include <Arduino.h>
#include <Wire.h>
#include <LiquidCrystal_I2C.h>
#include "DHT.h"
#include <WiFi.h>
#include "WiFiClientSecure.h"
#include "Adafruit_MQTT.h"
#include "Adafruit_MQTT_Client.h"
// -------------------------- Pins (ESP32) --------------------------
#define PIN_DHT 13
#define PIN_SOIL 34 // ADC1 pin
#define PIN_PIR 14
#define PIN_RED 18
#define PIN_GREEN 19
#define PIN_BLUE 23 // DO NOT use 21 (I2C SDA)
#define PIN_BUZZER 27
#define ADC_MAX_VAL 4095
// -------------------------- User Config --------------------------
#define DHTTYPE DHT11
LiquidCrystal_I2C lcd(0x27, 16, 2); // if LCD is blank, try 0x3F
const bool RGB_COMMON_ANODE = false;
// Soil thresholds (raw ADC values)
const int SOIL_WET_MAX = 1500; // Blue
const int SOIL_DRY_MIN = 2300; // Red
// Timing
const unsigned long DHT_MIN_INTERVAL_MS = 1500;
const unsigned long LCD_UPDATE_MS = 1000;
const unsigned long NOTE_MS = 120;
// -------------------------- Globals --------------------------
DHT dht(PIN_DHT, DHTTYPE);
const int melodyDry[] = { 880, 988, 880, 988 };
const int melodyPir[] = { 659, 523, 587, 523 };
const int melodyDryLen = sizeof(melodyDry)/sizeof(melodyDry[0]);
const int melodyPirLen = sizeof(melodyPir)/sizeof(melodyPir[0]);
enum BuzzerMode { BUZZ_OFF, BUZZ_DRY, BUZZ_PIR };
BuzzerMode buzzerMode = BUZZ_OFF;
unsigned long nextNoteAt = 0;
int noteIndex = 0;
unsigned long lastLCD = 0;
unsigned long lastDHT = 0;
float lastTempC = NAN;
float lastHum = NAN;
// -------------------------- WiFi / Adafruit IO --------------------------
#define WLAN_SSID "Wokwi-GUEST"
#define WLAN_PASS ""
#define AIO_SERVER "io.adafruit.com"
#define AIO_SERVERPORT 1883
#define AIO_USERNAME "Kanny"
#define AIO_KEY "aio_uhUF30zD6G84Tqc1vxBCmVnuVl5I"
WiFiClient client;
Adafruit_MQTT_Client mqtt(&client, AIO_SERVER, AIO_SERVERPORT, AIO_USERNAME, AIO_KEY);
Adafruit_MQTT_Subscribe LED = Adafruit_MQTT_Subscribe(&mqtt, AIO_USERNAME "/feeds/LED");
Adafruit_MQTT_Publish humidity = Adafruit_MQTT_Publish(&mqtt, AIO_USERNAME "/feeds/humidity");
Adafruit_MQTT_Publish temperature = Adafruit_MQTT_Publish(&mqtt, AIO_USERNAME "/feeds/temperature");
// Adafruit IO root CA
const char* adafruitio_root_ca = R"EOF(
-----BEGIN CERTIFICATE-----
MIIDQTCCAimgAwIBAgITBmyfz5m/...
-----END CERTIFICATE-----
)EOF";
// -------------------------- RGB (analogWrite) --------------------------
void setRGBDuty(uint8_t r, uint8_t g, uint8_t b) {
// analogWrite on ESP32 (Arduino core 2.x/3.x) maps to LEDC internally
analogWrite(PIN_RED, r);
analogWrite(PIN_GREEN, g);
analogWrite(PIN_BLUE, b);
}
void setRGB(uint8_t r, uint8_t g, uint8_t b) {
if (RGB_COMMON_ANODE) { r = 255 - r; g = 255 - g; b = 255 - b; }
setRGBDuty(r, g, b);
}
void rgbOff() { setRGB(0, 0, 0); }
void updateRGBFromSoil(int soilRaw){
if (soilRaw < SOIL_WET_MAX) setRGB(0, 0, 255); // Blue (wet)
else if (soilRaw > SOIL_DRY_MIN) setRGB(255, 0, 0); // Red (dry)
else setRGB(0, 255, 0); // Green (OK)
}
// -------------------------- Buzzer Helpers --------------------------
void playTone(int freq){
if (freq <= 0) noTone(PIN_BUZZER);
else tone(PIN_BUZZER, freq);
}
void buzzerOff() { playTone(0); }
void setBuzzerMode(BuzzerMode m){
if (buzzerMode != m) {
buzzerMode = m;
noteIndex = 0;
nextNoteAt = 0;
if (m == BUZZ_OFF) buzzerOff();
}
}
void updateBuzzer(){
unsigned long now = millis();
if (buzzerMode == BUZZ_OFF) return;
if (now >= nextNoteAt){
int freq = 0;
if (buzzerMode == BUZZ_DRY) freq = melodyDry[noteIndex % melodyDryLen];
else if (buzzerMode == BUZZ_PIR) freq = melodyPir[noteIndex % melodyPirLen];
playTone(freq);
noteIndex++;
nextNoteAt = now + NOTE_MS;
}
}
// -------------------------- LCD Helpers --------------------------
void showOnLCD(float tempC, float hum, int soilRaw){
lcd.setCursor(0,0);
lcd.print("T:");
if (isnan(tempC)) lcd.print("--.-");
else lcd.print(String(tempC,1));
lcd.print((char)223); lcd.print("C H:");
if (isnan(hum)) lcd.print("--");
else lcd.print((int)hum);
lcd.print("% ");
lcd.setCursor(0,1);
lcd.print("Soil:");
lcd.print(soilRaw);
lcd.print(" ");
}
// -------------------------- MQTT Callback --------------------------
void ledCallback(char *data, uint16_t len) {
data[len] = 0; // terminate string
Serial.print("LED command: "); Serial.println(data);
if (strcmp(data,"BLUE")==0) setRGB(0,0,255);
else if (strcmp(data,"GREEN")==0) setRGB(0,255,0);
else if (strcmp(data,"RED")==0) setRGB(255,0,0);
else rgbOff();
}
// -------------------------- Setup (NO LEDC needed) --------------------------
void setup(){
Serial.begin(115200);
delay(200);
// WiFi
WiFi.begin(WLAN_SSID, WLAN_PASS);
while (WiFi.status() != WL_CONNECTED) { delay(500); Serial.print("."); }
Serial.println(); Serial.println("WiFi connected");
mqtt.subscribe(&LED);
// Depending on your Adafruit_MQTT library version,
// you might need a different callback signature cast:
LED.setCallback((SubscribeCallbackBufferType)ledCallback);
// Pins
pinMode(PIN_PIR, INPUT);
pinMode(PIN_BUZZER, OUTPUT);
pinMode(PIN_RED, OUTPUT);
pinMode(PIN_GREEN, OUTPUT);
pinMode(PIN_BLUE, OUTPUT);
rgbOff();
buzzerOff();
dht.begin();
lcd.init();
lcd.backlight();
lcd.clear();
lcd.setCursor(0,0); lcd.print("Plant Guard v1");
lcd.setCursor(0,1); lcd.print("Init sensors...");
delay(600);
lcd.clear();
// Optional RGB self-test
setRGB(255, 0, 0); delay(250);
setRGB(0, 255, 0); delay(250);
setRGB(0, 0, 255); delay(250);
rgbOff();
}
// -------------------------- MQTT reconnect --------------------------
void MQTT_connect(){
int8_t ret;
if (mqtt.connected()) return;
Serial.print("Connecting MQTT... ");
uint8_t retries = 3;
while ((ret = mqtt.connect()) != 0) {
Serial.println(mqtt.connectErrorString(ret));
Serial.println("Retrying in 5s...");
mqtt.disconnect(); delay(5000);
if (--retries == 0) while(1);
}
Serial.println("MQTT Connected!");
}
// -------------------------- Publish humidity/temp --------------------------
void indicatorPublish(){
float humVal = dht.readHumidity();
float tempVal = dht.readTemperature();
if (isnan(humVal) || isnan(tempVal)) {
Serial.println("Failed reading DHT!");
return;
}
temperature.publish(tempVal);
humidity.publish(humVal);
}
// -------------------------- Main Loop --------------------------
void loop(){
unsigned long now = millis();
MQTT_connect();
indicatorPublish();
mqtt.processPackets(5000);
// DHT (rate-limited)
if (now - lastDHT >= DHT_MIN_INTERVAL_MS) {
float h = dht.readHumidity();
float t = dht.readTemperature();
if (!isnan(h) && !isnan(t)) { lastHum = h; lastTempC = t; }
lastDHT = now;
}
int soilRaw = analogRead(PIN_SOIL);
int pirState = digitalRead(PIN_PIR);
// Buzzer priority
bool soilDry = (soilRaw > SOIL_DRY_MIN);
if (pirState == HIGH) setBuzzerMode(BUZZ_PIR);
else if (soilDry) setBuzzerMode(BUZZ_DRY);
else setBuzzerMode(BUZZ_OFF);
updateRGBFromSoil(soilRaw);
updateBuzzer();
if (now - lastLCD >= LCD_UPDATE_MS){
showOnLCD(lastTempC, lastHum, soilRaw);
lastLCD = now;
Serial.print("Soil="); Serial.print(soilRaw);
Serial.print(" | Color: ");
if (soilRaw < SOIL_WET_MAX) Serial.print("Blue");
else if (soilRaw > SOIL_DRY_MIN) Serial.print("Red");
else Serial.print("Green");
Serial.print(" | PIR="); Serial.println(pirState);
}
}