#include <WiFi.h>
#include <WiFiClientSecure.h>
#include <HTTPClient.h>
#include <ArduinoJson.h>
#include <DHT.h>
// --- FUNCTION PROTOTYPES (Prevents scope errors) ---
void setFanHardware(int speedLevel, String direction);
void updateDatabaseSensors(int currentSpeed, String currentDir);
void fetchAndApplyLogic();
// --- WIFI & SUPABASE CREDENTIALS ---
const char* ssid = "Wokwi-GUEST";
const char* password = "";
const String supabaseUrl = "https://sippgekvqvpjrsmjxmoh.supabase.co/rest/v1/vent_system?id=eq.1";
const String supabaseKey = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6InNpcHBnZWt2cXZwanJzbWp4bW9oIiwicm9sZSI6ImFub24iLCJpYXQiOjE3NzA0NjQyNDYsImV4cCI6MjA4NjA0MDI0Nn0.G2u1c99HDmRJjUXX2xp81KlUCDrrnUq5PPs6b1OxJYE";
// --- PIN DEFINITIONS ---
const int FAN_ENA = 13;
const int FAN_IN1 = 12;
const int FAN_IN2 = 14;
const int GAS_SENSOR_PIN = 34;
const int PIR_PIN = 5;
const int DHT_IN_PIN = 4;
const int DHT_OUT_PIN = 15;
const int LDR_PIN = 35;
#define DHTTYPE DHT22
DHT dhtIn(DHT_IN_PIN, DHTTYPE);
DHT dhtOut(DHT_OUT_PIN, DHTTYPE);
// --- GLOBALS & TIMERS ---
WiFiClientSecure secureClient;
unsigned long lastClimateUpdate = 0;
const long climateInterval = 10000; // 10 seconds sync interval
unsigned long lastMotionReset = 0;
const long motionInterval = 600000; // 10 minutes
float tempIn = 0.0, humIn = 0.0, tempOut = 0.0, humOut = 0.0;
int gasLevel = 0;
int lightLevel = 0;
bool motionDetected = false;
bool rainDetected = false;
bool forceFirstRun = true;
void setup() {
Serial.begin(115200);
pinMode(FAN_ENA, OUTPUT);
pinMode(FAN_IN1, OUTPUT);
pinMode(FAN_IN2, OUTPUT);
analogWrite(FAN_ENA, 0);
digitalWrite(FAN_IN1, LOW);
digitalWrite(FAN_IN2, LOW);
pinMode(PIR_PIN, INPUT);
dhtIn.begin();
dhtOut.begin();
WiFi.begin(ssid, password);
Serial.print("Connecting to WiFi");
while (WiFi.status() != WL_CONNECTED) {
delay(500);
Serial.print(".");
}
Serial.println("\nā
Connected to WiFi!");
secureClient.setInsecure();
}
void loop() {
// 1. CONTINUOUS SENSOR READINGS
gasLevel = analogRead(GAS_SENSOR_PIN);
lightLevel = analogRead(LDR_PIN);
rainDetected = (humOut > 70.0);
// Motion Retrigger Logic
if (digitalRead(PIR_PIN) == HIGH) {
if (!motionDetected) {
Serial.println("š Motion Detected! Occupancy set to TRUE. Timer restarted.");
}
motionDetected = true;
lastMotionReset = millis();
}
if (motionDetected && (millis() - lastMotionReset >= motionInterval)) {
Serial.println("ā±ļø 10 Minutes with ZERO motion. Occupancy set to FALSE.");
motionDetected = false;
}
// 2. EMERGENCY OVERRIDE (Highest Priority)
if (gasLevel > 2500) {
Serial.println("šØ EMERGENCY: Gas > 2500ppm! FORCING MAX EXHAUST.");
setFanHardware(5, "EXHAUST");
updateDatabaseSensors(5, "EXHAUST");
delay(2000);
return;
}
// 3. DATABASE SYNC TIMER
if (forceFirstRun || millis() - lastClimateUpdate >= climateInterval) {
lastClimateUpdate = millis();
forceFirstRun = false;
// Read DHT Sensors
float newTempIn = dhtIn.readTemperature();
float newHumIn = dhtIn.readHumidity();
if (!isnan(newTempIn) && !isnan(newHumIn)) {
tempIn = newTempIn;
humIn = newHumIn;
}
float newTempOut = dhtOut.readTemperature();
float newHumOut = dhtOut.readHumidity();
if (!isnan(newTempOut) && !isnan(newHumOut)) {
tempOut = newTempOut;
humOut = newHumOut;
}
Serial.printf("š IN: %.1f°C/%.1f%% | OUT: %.1f°C/%.1f%% | Gas: %d | Light: %d\n",
tempIn, humIn, tempOut, humOut, gasLevel, lightLevel);
fetchAndApplyLogic();
}
}
void fetchAndApplyLogic() {
if (WiFi.status() == WL_CONNECTED) {
HTTPClient http;
http.begin(secureClient, supabaseUrl + "&select=*");
http.addHeader("apikey", supabaseKey);
http.addHeader("Authorization", "Bearer " + supabaseKey);
int httpResponseCode = http.GET();
int currentAppliedSpeed = 0;
String targetDirection = "EXHAUST";
if (httpResponseCode == 200) {
String payload = http.getString();
DynamicJsonDocument doc(1024);
deserializeJson(doc, payload);
JsonObject data = doc[0];
bool isManualMode = data["manual_mode"];
bool isEcoMode = data["eco_mode"];
int dbFanSpeed = data["fan_speed"];
String dbFanDirection = data["fan_direction"].as<String>();
int targetSpeed = 0;
// šØ 1. EMERGENCY GAS OVERRIDE HAPPENS FIRST
if (gasLevel > 2500) {
targetSpeed = 5;
targetDirection = "EXHAUST";
Serial.println("šØ EMERGENCY: Gas > 2500! Forcing MAX EXHAUST.");
}
// āļø 2. IF NO EMERGENCY, CHECK MANUAL MODE
else if (isManualMode) {
targetSpeed = dbFanSpeed;
targetDirection = dbFanDirection;
if (isEcoMode && targetSpeed > 2) {
targetSpeed = 2;
Serial.println("āļø Manual + Eco: Capping speed to Level 2");
} else {
Serial.printf("āļø Manual Mode | Speed: %d | Dir: %s\n", targetSpeed, targetDirection.c_str());
}
}
// š¤ 3. IF NO EMERGENCY AND NOT MANUAL, DO AUTO MODE
else {
// --- AUTO MODE (Based on Temp Difference) ---
float tempDifference = abs(tempIn - tempOut);
if (tempDifference < 1.0) targetSpeed = 1;
else if (tempDifference >= 1.0 && tempDifference < 3.0) targetSpeed = 2;
else if (tempDifference >= 3.0 && tempDifference < 5.0) targetSpeed = 3;
else if (tempDifference >= 5.0 && tempDifference < 7.0) targetSpeed = 4;
else targetSpeed = 5;
// Night Mode Speed Cap
if (lightLevel < 500 && targetSpeed > 2) {
targetSpeed = 2;
Serial.println("š Auto Night Mode: Capping to Level 2.");
}
// Calculate Direction
if (tempIn > tempOut) {
targetDirection = "INTAKE";
} else {
targetDirection = "EXHAUST";
}
} // š FIXED: Added the missing bracket here!
setFanHardware(targetSpeed, targetDirection);
currentAppliedSpeed = targetSpeed;
} else {
Serial.printf("ā ļø Error fetching DB. HTTP: %d\n", httpResponseCode);
}
http.end();
updateDatabaseSensors(currentAppliedSpeed, targetDirection);
}
}
void updateDatabaseSensors(int currentSpeed, String currentDir) {
if (WiFi.status() == WL_CONNECTED) {
HTTPClient http;
http.begin(secureClient, supabaseUrl);
http.addHeader("apikey", supabaseKey);
http.addHeader("Authorization", "Bearer " + supabaseKey);
http.addHeader("Content-Type", "application/json");
http.addHeader("Prefer", "return=representation");
DynamicJsonDocument doc(512);
doc["temp_indoor"] = tempIn;
doc["humidity_indoor"] = humIn;
doc["temp_outdoor"] = tempOut;
doc["humidity_outdoor"] = humOut;
doc["gas_level"] = gasLevel;
doc["fan_speed"] = currentSpeed;
doc["fan_direction"] = currentDir;
doc["motion_detected"] = motionDetected;
doc["rain_detected"] = rainDetected;
doc["light_level"] = lightLevel;
String requestBody;
serializeJson(doc, requestBody);
int patchResponseCode = http.sendRequest("PATCH", requestBody);
if (patchResponseCode > 0) {
if (patchResponseCode >= 300) {
String response = http.getString();
Serial.println("ā ļø DB Error Message: " + response);
} else {
Serial.println("ā
DB Updated Successfully");
}
} else {
Serial.printf("ā ļø ESP32 Network Error: %d\n", patchResponseCode);
}
http.end();
}
}
void setFanHardware(int speedLevel, String direction) {
int pwmValue = map(speedLevel, 0, 5, 0, 255);
if (speedLevel == 0) {
digitalWrite(FAN_IN1, LOW);
digitalWrite(FAN_IN2, LOW);
} else if (direction == "EXHAUST") {
digitalWrite(FAN_IN1, HIGH);
digitalWrite(FAN_IN2, LOW);
} else {
digitalWrite(FAN_IN1, LOW);
digitalWrite(FAN_IN2, HIGH);
}
analogWrite(FAN_ENA, pwmValue);
}