#include <Arduino.h>
#include <WiFi.h>
#include <PubSubClient.h>
#include <ArduinoJson.h>
#include <DHT.h>
#include <ESP32Servo.h>
#include <WiFiClientSecure.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>
#include <Wire.h>
// --- 1. WiFi and MQTT Configuration ---
const char *WIFI_SSID = "Wokwi-GUEST";
const char *WIFI_PASSWORD = "";
const char *MQTT_BROKER_URL = "59deb5a867b74886adb035cfb71e9c99.s1.eu.hivemq.cloud"; // erfan
const char *MQTT_USERNAME = "erfan";
const char *MQTT_PASSWORD = "Cps123456";
const int MQTT_PORT = 8883;
// --- 2. Pin Definitions ---
#define DHT_PIN 4
#define SOIL_PIN 35
#define LDR_PIN 34
#define ULTRASONIC_TRIG 12
#define ULTRASONIC_ECHO 13
#define FAN_RELAY_PIN 22
#define PUMP_RELAY_PIN 23
#define SERVO_PIN 15
#define RGB_R_PIN 18
#define RGB_G_PIN 19
#define RGB_B_PIN 21
#define OLED_SDA 17
#define OLED_SCL 16
#define WIFI_LED_PIN 2
#define MQTT_LED_PIN 5
#define MODE_BUTTON_PIN 32
// --- 3. MQTT Topics ---
#define TEMP_TOPIC "greenhouse/sensors/temperature"
#define HUMIDITY_TOPIC "greenhouse/sensors/humidity"
#define SOIL_TOPIC "greenhouse/sensors/soilMoisture"
#define LIGHT_TOPIC "greenhouse/sensors/light"
#define WATER_LEVEL_TOPIC "greenhouse/sensors/waterLevel"
#define STATUS_TOPIC "greenhouse/status"
#define FAN_CONTROL_TOPIC "greenhouse/control/fan"
#define PUMP_CONTROL_TOPIC "greenhouse/control/pump"
#define LIGHT_CONTROL_TOPIC "greenhouse/control/light"
#define WINDOW_CONTROL_TOPIC "greenhouse/control/window"
// --- 4. Device and Client Objects ---
WiFiClientSecure wifiClient;
PubSubClient mqttClient(wifiClient);
DHT dht(DHT_PIN, DHT22);
Servo windowServo;
Adafruit_SSD1306 display(128, 64, &Wire, -1);
// --- 5. Global State Variables ---
volatile float temperature = 0.0;
volatile float humidity = 0.0;
volatile int soilMoisture = 0;
volatile int lightIntensity = 0;
volatile float waterLevel = 0.0;
bool fanState = false;
bool pumpState = false;
int growthLightIntensity = 0;
int windowAngle = 0;
enum SystemMode
{
MANUAL,
AUTO
};
SystemMode currentMode = AUTO;
volatile bool modeChangeRequested = false;
String systemStatus = "Running";
// --- 6. FreeRTOS Task Handles ---
TaskHandle_t ReadSensorsTaskHandle;
TaskHandle_t MqttTaskHandle;
TaskHandle_t ActuatorControlTaskHandle;
TaskHandle_t ButtonWatcherTaskHandle;
// --- 7. Function Prototypes ---
void setup_wifi();
void mqtt_callback(char *topic, byte *payload, unsigned int length);
void reconnect_mqtt();
void publishSensorData();
void updateOLED();
void applyActuatorStates();
void IRAM_ATTR onButtonPress();
void ReadSensorsTask(void *pvParameters);
void MqttTask(void *pvParameters);
void ActuatorControlTask(void *pvParameters);
void ButtonWatcherTask(void *pvParameters);
void setup()
{
// Start of Debugging Setup
Serial.begin(115200);
delay(1000); // Wait 1 second for Serial Monitor to connect
Serial.println("DEBUG: Serial communication started.");
pinMode(SOIL_PIN, INPUT);
pinMode(LDR_PIN, INPUT);
pinMode(ULTRASONIC_TRIG, OUTPUT);
pinMode(ULTRASONIC_ECHO, INPUT);
pinMode(FAN_RELAY_PIN, OUTPUT);
pinMode(PUMP_RELAY_PIN, OUTPUT);
pinMode(WIFI_LED_PIN, OUTPUT);
pinMode(MQTT_LED_PIN, OUTPUT);
pinMode(MODE_BUTTON_PIN, INPUT_PULLUP);
Serial.println("DEBUG: Basic pin modes set.");
ledcSetup(0, 5000, 8);
ledcAttachPin(RGB_R_PIN, 0);
ledcSetup(1, 5000, 8);
ledcAttachPin(RGB_G_PIN, 1);
ledcSetup(2, 5000, 8);
ledcAttachPin(RGB_B_PIN, 2);
Serial.println("DEBUG: PWM for RGB LED setup complete.");
dht.begin();
Serial.println("DEBUG: DHT sensor initialized.");
windowServo.attach(SERVO_PIN);
Serial.println("DEBUG: Servo attached.");
Wire.begin(OLED_SDA, OLED_SCL);
Serial.println("DEBUG: I2C (Wire) initialized.");
if (!display.begin(SSD1306_SWITCHCAPVCC, 0x3C))
{
Serial.println(F("DEBUG: SSD1306 allocation FAILED"));
}
else
{
Serial.println("DEBUG: Display initialized successfully.");
display.clearDisplay();
display.setTextSize(1);
display.setTextColor(WHITE);
display.setCursor(0, 0);
display.println("Starting up...");
display.display();
Serial.println("DEBUG: 'Starting up...' message sent to OLED.");
}
setup_wifi();
Serial.println("DEBUG: WiFi setup complete.");
mqttClient.setServer(MQTT_BROKER_URL, MQTT_PORT);
mqttClient.setCallback(mqtt_callback);
Serial.println("DEBUG: MQTT client configured.");
attachInterrupt(digitalPinToInterrupt(MODE_BUTTON_PIN), onButtonPress, FALLING);
Serial.println("DEBUG: Button interrupt attached.");
xTaskCreatePinnedToCore(ReadSensorsTask, "ReadSensors", 4096, NULL, 1, &ReadSensorsTaskHandle, 0);
xTaskCreatePinnedToCore(MqttTask, "MqttLoop", 4096, NULL, 1, &MqttTaskHandle, 0);
xTaskCreatePinnedToCore(ActuatorControlTask, "ActuatorControl", 4096, NULL, 1, &ActuatorControlTaskHandle, 1);
xTaskCreatePinnedToCore(ButtonWatcherTask, "ButtonWatcher", 2048, NULL, 2, &ButtonWatcherTaskHandle, 1);
Serial.println("DEBUG: All FreeRTOS tasks created.");
Serial.println("--- SETUP COMPLETE ---");
}
void loop()
{
vTaskDelay(1000 / portTICK_PERIOD_MS);
}
// --- 8. Task Implementations ---
void ReadSensorsTask(void *pvParameters)
{
unsigned long lastPublishTime = 0;
const long publishInterval = 10000; // 30 seconds
for (;;)
{
// Read sensors
float temp_reading = dht.readTemperature();
if (!isnan(temp_reading))
{
temperature = temp_reading;
}
float hum_reading = dht.readHumidity();
if (!isnan(hum_reading))
{
humidity = hum_reading;
}
int soil_raw = analogRead(SOIL_PIN);
soilMoisture = map(soil_raw, 4095, 0, 0, 100);
int ldr_raw = analogRead(LDR_PIN);
lightIntensity = map(ldr_raw, 0, 4095, 0, 100);
digitalWrite(ULTRASONIC_TRIG, LOW);
delayMicroseconds(2);
digitalWrite(ULTRASONIC_TRIG, HIGH);
delayMicroseconds(10);
digitalWrite(ULTRASONIC_TRIG, LOW);
long duration = pulseIn(ULTRASONIC_ECHO, HIGH, 25000);
waterLevel = duration * 0.034 / 2;
// Check if it's time to publish data to MQTT
if (millis() - lastPublishTime >= publishInterval)
{
if (mqttClient.connected())
{
// We only publish data every 30 seconds as required
publishSensorData();
}
lastPublishTime = millis();
}
// The task now runs every 2 seconds to keep sensor data fresh for the control loop
vTaskDelay(2000 / portTICK_PERIOD_MS);
}
}
void ActuatorControlTask(void *pvParameters)
{
bool isWatering = false;
unsigned long wateringStartTime = 0;
for (;;)
{
if (currentMode == AUTO)
{
// Scenario 1: Fan Control
if (temperature > 30.0 || humidity > 70.0)
{
fanState = true;
}
else
{
fanState = false;
}
// Scenario 2: Window Control (Servo)
if (temperature > 30.0 && humidity > 50.0)
{
if (windowAngle != 90)
{
Serial.println("DEBUG: Conditions met, opening window.");
windowAngle = 90;
}
}
else
{
if (windowAngle != 0)
{
Serial.println("DEBUG: Conditions not met, closing window.");
windowAngle = 0;
}
}
// Scenario 3: Growth Light Control
if (lightIntensity < 50)
{
growthLightIntensity = 100;
}
else if (lightIntensity > 70)
{
growthLightIntensity = 0;
}
// Scenario 4: Automatic Irrigation
if (waterLevel < 10.0)
{
systemStatus = "LOW WATER";
pumpState = false;
isWatering = false;
}
else
{
if (isWatering)
{
if (millis() - wateringStartTime >= 10000 || soilMoisture >= 30)
{
isWatering = false;
pumpState = false;
systemStatus = "Running";
}
}
else
{
if (soilMoisture < 30)
{
isWatering = true;
pumpState = true;
wateringStartTime = millis();
systemStatus = "Watering...";
}
else
{
systemStatus = "Running";
}
}
}
}
applyActuatorStates();
updateOLED();
vTaskDelay(1000 / portTICK_PERIOD_MS);
}
}
void MqttTask(void *pvParameters)
{
for (;;)
{
if (!mqttClient.connected())
{
reconnect_mqtt();
}
mqttClient.loop();
digitalWrite(MQTT_LED_PIN, mqttClient.connected() ? HIGH : !digitalRead(MQTT_LED_PIN));
vTaskDelay(500 / portTICK_PERIOD_MS);
}
}
void ButtonWatcherTask(void *pvParameters)
{
for (;;)
{
if (modeChangeRequested)
{
vTaskDelay(50 / portTICK_PERIOD_MS);
unsigned long pressStartTime = millis();
while (digitalRead(MODE_BUTTON_PIN) == LOW)
{
}
unsigned long pressDuration = millis() - pressStartTime;
if (pressDuration > 5000)
{
Serial.println("Resetting ESP...");
ESP.restart();
}
else if (pressDuration > 100)
{
currentMode = (currentMode == MANUAL) ? AUTO : MANUAL;
Serial.print("Mode changed to: ");
Serial.println(currentMode == MANUAL ? "MANUAL" : "AUTO");
}
modeChangeRequested = false;
}
vTaskDelay(50 / portTICK_PERIOD_MS);
}
}
// --- 9. Helper Function Implementations ---
void setup_wifi()
{
Serial.print("Connecting to WiFi...");
WiFi.begin(WIFI_SSID, WIFI_PASSWORD);
while (WiFi.status() != WL_CONNECTED)
{
digitalWrite(WIFI_LED_PIN, !digitalRead(WIFI_LED_PIN));
delay(500);
Serial.print(".");
}
digitalWrite(WIFI_LED_PIN, HIGH);
Serial.println(" WiFi Connected!");
Serial.print("IP Address: ");
Serial.println(WiFi.localIP());
}
void mqtt_callback(char *topic, byte *payload, unsigned int length)
{
Serial.print("Message arrived [");
Serial.print(topic);
Serial.print("] ");
char message[length + 1];
for (int i = 0; i < length; i++)
{
message[i] = (char)payload[i];
}
message[length] = '\0';
Serial.println(message);
if (currentMode == MANUAL)
{
if (strcmp(topic, FAN_CONTROL_TOPIC) == 0)
{
fanState = (strcmp(message, "ON") == 0);
}
else if (strcmp(topic, PUMP_CONTROL_TOPIC) == 0)
{
pumpState = (strcmp(message, "ON") == 0);
}
else if (strcmp(topic, LIGHT_CONTROL_TOPIC) == 0)
{
growthLightIntensity = atoi(message);
}
else if (strcmp(topic, WINDOW_CONTROL_TOPIC) == 0)
{
windowAngle = atoi(message);
}
}
}
void reconnect_mqtt()
{
while (!mqttClient.connected())
{
Serial.print("Attempting MQTT connection...");
if (mqttClient.connect("esp32-greenhouse-client", MQTT_USERNAME, MQTT_PASSWORD))
{
Serial.println("connected");
digitalWrite(MQTT_LED_PIN, HIGH);
mqttClient.subscribe(FAN_CONTROL_TOPIC);
mqttClient.subscribe(PUMP_CONTROL_TOPIC);
mqttClient.subscribe(LIGHT_CONTROL_TOPIC);
mqttClient.subscribe(WINDOW_CONTROL_TOPIC);
}
else
{
Serial.print("failed, rc=");
Serial.print(mqttClient.state());
Serial.println(" try again in 5 seconds");
digitalWrite(MQTT_LED_PIN, LOW);
delay(5000);
}
}
}
void publishSensorData()
{
char buffer[10];
dtostrf(temperature, 4, 2, buffer);
mqttClient.publish(TEMP_TOPIC, buffer);
dtostrf(humidity, 4, 2, buffer);
mqttClient.publish(HUMIDITY_TOPIC, buffer);
itoa(soilMoisture, buffer, 10);
mqttClient.publish(SOIL_TOPIC, buffer);
itoa(lightIntensity, buffer, 10);
mqttClient.publish(LIGHT_TOPIC, buffer);
dtostrf(waterLevel, 4, 2, buffer);
mqttClient.publish(WATER_LEVEL_TOPIC, buffer);
mqttClient.publish(STATUS_TOPIC, systemStatus.c_str());
Serial.println("Published all sensor data.");
}
void applyActuatorStates()
{
digitalWrite(FAN_RELAY_PIN, fanState ? HIGH : LOW);
digitalWrite(PUMP_RELAY_PIN, pumpState ? HIGH : LOW);
windowServo.write(windowAngle);
int pwmValue = map(growthLightIntensity, 0, 100, 0, 255);
ledcWrite(0, pwmValue);
ledcWrite(1, pwmValue);
ledcWrite(2, pwmValue);
}
void updateOLED()
{
display.clearDisplay();
display.setCursor(0, 0);
display.print("Mode: ");
display.println(currentMode == MANUAL ? "MANUAL" : "AUTO");
display.printf("Temp: %.1fC\n", temperature);
display.printf("Hum: %.1f%%\n", humidity);
display.printf("Soil: %d%%\n", soilMoisture);
display.printf("Light: %d%%\n", lightIntensity);
display.print("Status: ");
display.print(systemStatus);
display.display();
}
void IRAM_ATTR onButtonPress()
{
modeChangeRequested = true;
}