// #include <WiFi.h>
// #include <HTTPClient.h>
#include "freertos/FreeRTOS.h"
#include "freertos/semphr.h"
#include "freertos/task.h"
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>
#include <DHTesp.h>
#define SCREEN_WIDTH 128
#define SCREEN_HEIGHT 64
#define OLED_RESET -1
Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, OLED_RESET);
#define DHT_PIN 14
#define RGB_RED_PIN 15
#define RGB_GREEN_PIN 2
#define RGB_BLUE_PIN 4
#define SWITCH_URL "http://<smart-switch-ip>/toggle"
#define WIFI_SSID "Wokwi-GUEST"
#define WIFI_PASS ""
// #define MIN_RUNTIME_MS (13 * 60 * 1000) // Minimum drying time
#define MIN_RUNTIME_MS (15 * 1000) // 15 seconds
#define HUM_STABLE_THRESHOLD 0.3f // Max delta in humidity for stability
#define TEMP_MIN 45.0f // Min temp to consider dryer running
#define TEMP_COOLDOWN 40.0f // Consider dryer "off" below this
// #define COOLDOWN_GRACE_MS (5 * 60 * 1000) // 5 minutes grace to allow
// pause
#define COOLDOWN_GRACE_MS (20 * 1000) // 20 seconds
#define STABLE_WINDOW 10 // Readings to check for plateau
DHTesp dht;
float humHistory[STABLE_WINDOW] = {0};
int humIndex = 0;
bool clothesDry = false;
bool dryerRunning = false;
unsigned long startTime = 0;
unsigned long lastHotTime = 0;
// Status management
enum SystemState { INITIALIZING, PAUSED, IDLE, MONITORING, DRY };
SystemState currentState = INITIALIZING;
const char *stateNames[] = {"Initializing", "Paused", "Idle", "Monitoring",
"Dry"};
bool systemPaused = false;
// Semaphores for protecting shared data
SemaphoreHandle_t stateMutex;
SemaphoreHandle_t sensorDataMutex;
// Shared sensor data
float currentTemp = 0.0;
float currentHum = 0.0;
bool sensorDataValid = false;
unsigned long sensorDataTimestamp = 0;
// RGB LED control variables
unsigned long lastLedUpdate = 0;
bool ledBlinkState = false;
int ledPulseValue = 0;
bool ledPulseDirection = true;
void setRGBColor(int red, int green, int blue) {
analogWrite(RGB_RED_PIN, red);
analogWrite(RGB_GREEN_PIN, green);
analogWrite(RGB_BLUE_PIN, blue);
}
void updateRGBLed() {
unsigned long currentTime = millis();
SystemState localState;
// Safely read the current state
if (xSemaphoreTake(stateMutex, pdMS_TO_TICKS(10)) == pdTRUE) {
localState = currentState;
xSemaphoreGive(stateMutex);
} else {
return; // Skip update if can't get mutex
}
switch (localState) {
case INITIALIZING:
// Blinking white (1 Hz)
if (currentTime - lastLedUpdate >= 500) {
ledBlinkState = !ledBlinkState;
if (ledBlinkState) {
setRGBColor(255, 255, 255); // White
} else {
setRGBColor(0, 0, 0); // Off
}
lastLedUpdate = currentTime;
}
break;
case PAUSED:
// Solid yellow
setRGBColor(255, 255, 0);
break;
case IDLE:
// Solid blue
setRGBColor(0, 0, 255);
break;
case MONITORING:
// Slow pulsing orange (2 second cycle)
if (currentTime - lastLedUpdate >= 20) {
if (ledPulseDirection) {
ledPulseValue += 3;
if (ledPulseValue >= 255) {
ledPulseValue = 255;
ledPulseDirection = false;
}
} else {
ledPulseValue -= 3;
if (ledPulseValue <= 50) {
ledPulseValue = 50;
ledPulseDirection = true;
}
}
setRGBColor(ledPulseValue, ledPulseValue / 2, 0); // Orange
lastLedUpdate = currentTime;
}
break;
case DRY:
// Solid green
setRGBColor(0, 255, 0);
break;
}
}
void updateDisplay(float temp, float hum) {
SystemState localState;
// Safely read the current state
if (xSemaphoreTake(stateMutex, pdMS_TO_TICKS(10)) == pdTRUE) {
localState = currentState;
xSemaphoreGive(stateMutex);
} else {
localState = INITIALIZING; // Default state if can't get mutex
}
display.clearDisplay();
display.setTextSize(1);
display.setTextColor(SSD1306_WHITE);
display.setCursor(0, 0);
display.printf("Temp: %.1f C", temp);
display.setCursor(0, 12);
display.printf("Hum: %.1f %%", hum);
display.setCursor(0, 24);
display.print("Status:");
display.setCursor(0, 36);
display.print(stateNames[localState]);
display.display();
}
// Display update task - runs independently for smooth display updates
void displayTask(void *pv) {
for (;;) {
float temp, hum;
SystemState localState;
// Safely read sensor data
if (xSemaphoreTake(sensorDataMutex, pdMS_TO_TICKS(10)) == pdTRUE) {
temp = currentTemp;
hum = currentHum;
xSemaphoreGive(sensorDataMutex);
} else {
// Skip update if can't get sensor data
vTaskDelay(pdMS_TO_TICKS(100));
continue;
}
// Safely read the current state
if (xSemaphoreTake(stateMutex, pdMS_TO_TICKS(10)) == pdTRUE) {
localState = currentState;
xSemaphoreGive(stateMutex);
} else {
localState = INITIALIZING; // Default state if can't get mutex
}
display.clearDisplay();
display.setTextSize(1);
display.setTextColor(SSD1306_WHITE);
display.setCursor(0, 0);
display.printf("Temp: %.1f C", temp);
display.setCursor(0, 12);
display.printf("Hum: %.1f %%", hum);
display.setCursor(0, 24);
display.print("Status:");
display.setCursor(0, 36);
display.print(stateNames[localState]);
display.display();
vTaskDelay(pdMS_TO_TICKS(250)); // Update display every 250ms
}
}
// void connectWiFi() {
// WiFi.begin(WIFI_SSID, WIFI_PASS);
// while (WiFi.status() != WL_CONNECTED) {
// Serial.print(".");
// vTaskDelay(pdMS_TO_TICKS(1000));
// }
// Serial.println("\nWi-Fi connected.");
// }
bool isHumidityStable() {
float minH = humHistory[0], maxH = humHistory[0];
for (int i = 1; i < STABLE_WINDOW; ++i) {
if (humHistory[i] < minH)
minH = humHistory[i];
if (humHistory[i] > maxH)
maxH = humHistory[i];
}
float delta = maxH - minH;
Serial.printf("π Humidity Ξ in window: %.2f%%\n", delta);
return delta < HUM_STABLE_THRESHOLD;
}
void sendSwitchRequest() {
Serial.println("π‘ Sending shutdown signal to smart switch...");
// HTTPClient http;
// http.begin(SWITCH_URL);
// int responseCode = http.GET();
// Serial.printf("π HTTP response: %d\n", responseCode);
// http.end();
}
void updateSystemState() {
SystemState newState;
if (systemPaused) {
newState = PAUSED;
} else if (dryerRunning) {
if (clothesDry) {
newState = DRY;
} else {
newState = MONITORING;
}
} else {
newState = IDLE;
}
// Safely update the current state
if (xSemaphoreTake(stateMutex, pdMS_TO_TICKS(10)) == pdTRUE) {
currentState = newState;
xSemaphoreGive(stateMutex);
}
}
// LED update task - runs independently for smooth LED updates
void ledTask(void *pv) {
for (;;) {
updateRGBLed();
vTaskDelay(
pdMS_TO_TICKS(50)); // Update LED every 50ms for smooth animations
}
}
// Function to toggle pause state (can be called from button press, etc.)
void togglePause() {
systemPaused = !systemPaused;
Serial.printf("System %s\n", systemPaused ? "PAUSED" : "RESUMED");
}
// Sensor reading task - reads DHT22 as fast as possible
void sensorReadingTask(void *pv) {
const TickType_t delayTime = pdMS_TO_TICKS(dht.getMinimumSamplingPeriod());
// Wait a bit for initialization
vTaskDelay(pdMS_TO_TICKS(1000));
for (;;) {
float hum = dht.getHumidity();
float temp = dht.getTemperature();
if (!isnan(hum) && !isnan(temp)) {
// Safely update shared sensor data
if (xSemaphoreTake(sensorDataMutex, pdMS_TO_TICKS(10)) == pdTRUE) {
currentTemp = temp;
currentHum = hum;
sensorDataValid = true;
sensorDataTimestamp = millis();
xSemaphoreGive(sensorDataMutex);
}
Serial.printf("π Raw: %.2fΒ°C %.2f%%\n", temp, hum);
} else {
// Serial.println("β οΈ DHT read error");
// Mark data as invalid
if (xSemaphoreTake(sensorDataMutex, pdMS_TO_TICKS(10)) == pdTRUE) {
sensorDataValid = false;
xSemaphoreGive(sensorDataMutex);
}
vTaskDelay(pdMS_TO_TICKS(200));
continue;
}
vTaskDelay(delayTime);
}
}
// Data processing task - handles all the logic and decision making
void dataProcessingTask(void *pv) {
// Wait for initialization and first sensor readings
vTaskDelay(pdMS_TO_TICKS(3000));
// Safely update state to IDLE
if (xSemaphoreTake(stateMutex, pdMS_TO_TICKS(100)) == pdTRUE) {
currentState = IDLE;
xSemaphoreGive(stateMutex);
}
unsigned long lastProcessedTimestamp = 0;
for (;;) {
float temp, hum;
bool dataValid;
unsigned long dataTimestamp;
// Safely read current sensor data
if (xSemaphoreTake(sensorDataMutex, pdMS_TO_TICKS(10)) == pdTRUE) {
temp = currentTemp;
hum = currentHum;
dataValid = sensorDataValid;
dataTimestamp = sensorDataTimestamp;
xSemaphoreGive(sensorDataMutex);
} else {
// Skip processing if can't get sensor data
vTaskDelay(pdMS_TO_TICKS(100));
continue;
}
if (!dataValid) {
// Skip processing if sensor data is invalid
vTaskDelay(pdMS_TO_TICKS(500));
continue;
}
// Skip processing if we've already processed this data
if (dataTimestamp <= lastProcessedTimestamp) {
vTaskDelay(pdMS_TO_TICKS(500));
continue;
}
lastProcessedTimestamp = dataTimestamp;
Serial.printf("π Processing: %.2fΒ°C %.2f%%\n", temp, hum);
// ...existing code...
updateSystemState();
bool tempHigh = temp >= TEMP_MIN;
bool tempLow = temp <= TEMP_COOLDOWN;
humHistory[humIndex] = hum;
humIndex = (humIndex + 1) % STABLE_WINDOW;
if (tempHigh) {
lastHotTime = millis();
if (!dryerRunning) {
dryerRunning = true;
clothesDry = false;
startTime = millis();
Serial.println("π₯ Dryer started. Monitoring...");
}
}
// Allow cooling tolerance before resetting
if (dryerRunning && millis() - lastHotTime > COOLDOWN_GRACE_MS) {
dryerRunning = false;
clothesDry = false;
Serial.println("βοΈ Dryer stopped (grace timeout). Resetting state.");
}
if (dryerRunning && !clothesDry && millis() - startTime >= MIN_RUNTIME_MS &&
isHumidityStable() && tempHigh) {
clothesDry = true;
Serial.println("β
Conditions met. Clothes are likely dry.");
xTaskCreatePinnedToCore(
[](void *) {
sendSwitchRequest();
vTaskDelete(NULL);
},
"HTTPTask", 4096, NULL, 2, NULL, 1);
}
const TickType_t delayTime = pdMS_TO_TICKS(dht.getMinimumSamplingPeriod());
vTaskDelay(pdMS_TO_TICKS(
delayTime +
500)); // Process data every 2.5 seconds (slower than sensor reading)
}
}
void setup() {
Serial.begin(115200);
// Create semaphores for protecting shared data
stateMutex = xSemaphoreCreateMutex();
sensorDataMutex = xSemaphoreCreateMutex();
if (stateMutex == NULL || sensorDataMutex == NULL) {
Serial.println("β Failed to create semaphores");
return;
}
currentState = INITIALIZING;
if (!display.begin(SSD1306_SWITCHCAPVCC, 0x3C)) {
Serial.println("β OLED init failed");
} else {
display.clearDisplay();
display.display();
display.setTextSize(1);
display.setTextColor(SSD1306_WHITE);
display.setCursor(0, 0);
display.print("System Status:");
display.setCursor(0, 12);
display.print(stateNames[currentState]);
display.display();
}
dht.setup(DHT_PIN, DHTesp::DHT22);
// Initialize RGB LED pins
pinMode(RGB_RED_PIN, OUTPUT);
pinMode(RGB_GREEN_PIN, OUTPUT);
pinMode(RGB_BLUE_PIN, OUTPUT);
// connectWiFi();
// Create tasks
xTaskCreatePinnedToCore(sensorReadingTask, "SensorReadingTask", 4096, NULL, 2,
NULL, 1);
xTaskCreatePinnedToCore(dataProcessingTask, "DataProcessingTask", 8192, NULL,
1, NULL, 1);
xTaskCreatePinnedToCore(ledTask, "LEDTask", 2048, NULL, 1, NULL, 0);
xTaskCreatePinnedToCore(displayTask, "DisplayTask", 4096, NULL, 1, NULL, 0);
}
void loop() {
vTaskDelay(pdMS_TO_TICKS(500)); // Idle loop
}