#include #include "secrets.h" // Feature Configuration - Comment out to disable #define ENABLE_SENSORS #define ENABLE_DISPLAY #define ENABLE_WIFI #define ENABLE_NOTIFICATIONS #ifdef ENABLE_WIFI #include #include #include #include #include #endif #ifdef ENABLE_SENSORS #include #endif #ifdef ENABLE_DISPLAY #include #include #endif // WiFi credentials #ifdef ENABLE_WIFI const char* WIFI_SSID = SECRET_WIFI_SSID; const char* WIFI_PASSWORD = SECRET_WIFI_PASSWORD; #endif // ntfy.sh topic const char* NTFY_TOPIC = SECRET_NTFY_TOPIC; // Device Names const char* DEVICE1_NAME = "Wash"; // Monitored by Light 1 (0x23) const char* DEVICE2_NAME = "Dry "; // Monitored by Light 2 (0x5C) // OLED display settings #define SCREEN_WIDTH 128 #define SCREEN_HEIGHT 32 #define OLED_RESET -1 // Reset pin # (or -1 if sharing Arduino reset pin) #define SCREEN_ADDRESS 0x3C #ifdef ENABLE_DISPLAY Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, OLED_RESET); #endif // Light sensor #ifdef ENABLE_SENSORS BH1750 lightMeter1(0x23); BH1750 lightMeter2(0x5C); #endif // Thresholds const float LIGHT_ACTIVATION_THRESHOLD = 50.0; const unsigned long WINDOW_DURATION = 10000; // 10 seconds const int REQUIRED_CONSECUTIVE_WINDOWS = 6; const unsigned long WARMUP_DURATION = 30000; // 20 seconds warmup // Device state bool isDevice1Active = false; bool isDevice2Active = false; // Time tracking for sensor states unsigned long lastWindowStartTime = 0; // OLED display data #ifdef ENABLE_DISPLAY String displayData[5]; int currentDisplayLine = 0; unsigned long lastDisplayScrollTime = 0; const unsigned long DISPLAY_SCROLL_INTERVAL = 2000; // 1000 = 1 second const int displayTextLines = 4; int displayDataLines = displayTextLines; #endif // Network Task & Queue struct NotificationMessage { char message[64]; char title[32]; char priority[10]; }; QueueHandle_t notificationQueue; volatile bool isNetworkActive = false; #ifdef ENABLE_SENSORS int light1ConsecutiveOn = 0; int light1ConsecutiveOff = 0; float maxLight1InWindow = 0.0; int light2ConsecutiveOn = 0; int light2ConsecutiveOff = 0; float maxLight2InWindow = 0.0; #endif #ifdef ENABLE_WIFI void networkTask(void *parameter) { NotificationMessage msg; unsigned long lastWifiCheckTime = 0; const unsigned long WIFI_CHECK_INTERVAL = 30000; while (true) { unsigned long currentTime = millis(); if (currentTime - lastWifiCheckTime >= WIFI_CHECK_INTERVAL) { lastWifiCheckTime = currentTime; if (WiFi.status() != WL_CONNECTED) { Serial.println("WiFi disconnected. Attempting to reconnect..."); WiFi.reconnect(); } } if (xQueueReceive(notificationQueue, &msg, 1000 / portTICK_PERIOD_MS) == pdTRUE) { isNetworkActive = true; bool sent = false; while (!sent) { if (WiFi.status() == WL_CONNECTED) { HTTPClient http; String url = "http://ntfy.sh/" + String(NTFY_TOPIC); http.begin(url); http.addHeader("Content-Type", "text/plain"); http.addHeader("Title", msg.title); http.addHeader("Priority", msg.priority); int httpResponseCode = http.POST(msg.message); http.end(); if (httpResponseCode > 0) { Serial.print("HTTP Response code: "); Serial.println(httpResponseCode); sent = true; } else { Serial.print("Error on sending POST: "); Serial.println(httpResponseCode); vTaskDelay(2000 / portTICK_PERIOD_MS); } } else { unsigned long now = millis(); if (now - lastWifiCheckTime >= WIFI_CHECK_INTERVAL) { lastWifiCheckTime = now; Serial.println("WiFi disconnected. Attempting to reconnect..."); WiFi.reconnect(); } vTaskDelay(1000 / portTICK_PERIOD_MS); } } isNetworkActive = false; } } } #endif void setup() { delay(500); Serial.begin(9600); delay(100); Serial.println("Setup started."); #if defined(ENABLE_SENSORS) || defined(ENABLE_DISPLAY) Wire.begin(); #endif #ifdef ENABLE_DISPLAY // Initialize OLED display if (!display.begin(SSD1306_SWITCHCAPVCC, SCREEN_ADDRESS)) { Serial.println(F("SSD1306 allocation failed")); while (1) { delay(1000); } } display.clearDisplay(); display.setTextSize(1); display.setTextColor(WHITE); display.setCursor(0, 0); display.println(F("Hello!")); display.display(); delay(500); #ifdef ENABLE_SENSORS // Initialize light sensors if (!lightMeter1.begin(BH1750::CONTINUOUS_HIGH_RES_MODE, 0x23)) { Serial.println(F("Could not find valid BH1750 sensor at 0x23, check wiring!")); display.clearDisplay(); display.setCursor(0, 0); display.println(F("Err: Sensor 0x23")); display.println(" " + String(DEVICE1_NAME)); display.println(F(" not found")); display.display(); while (1) { delay(1000); } } if (!lightMeter2.begin(BH1750::CONTINUOUS_HIGH_RES_MODE, 0x5C)) { Serial.println(F("Could not find valid BH1750 sensor at 0x5C, check wiring!")); display.clearDisplay(); display.setCursor(0, 0); display.println(F("Err: Sensor 0x5C")); display.println(" " + String(DEVICE2_NAME)); display.println(F(" not found")); display.display(); while (1) { delay(1000); } } #endif #ifdef ENABLE_WIFI display.clearDisplay(); display.setCursor(0, 0); display.println(F("WiFi:")); display.println(WIFI_SSID); display.display(); delay(1000); #endif #ifdef ENABLE_WIFI // Connect to WiFi Serial.print("Connecting to "); Serial.println(WIFI_SSID); WiFi.begin(WIFI_SSID, WIFI_PASSWORD); while (WiFi.status() != WL_CONNECTED) { delay(500); Serial.print("."); } Serial.println(""); Serial.println("WiFi connected"); Serial.println("IP address: "); Serial.println(WiFi.localIP()); #ifdef ENABLE_NOTIFICATIONS notificationQueue = xQueueCreate(10, sizeof(NotificationMessage)); xTaskCreatePinnedToCore(networkTask, "NetworkTask", 8192, NULL, 1, NULL, 0); #endif #endif #if defined(ENABLE_DISPLAY) && defined(ENABLE_WIFI) display.clearDisplay(); display.setCursor(0,0); display.println("WiFi Connected"); display.display(); delay(1000); #endif display.clearDisplay(); display.setCursor(0, 0); display.println(F("Topic:")); display.println(NTFY_TOPIC); display.display(); delay(1000); #endif Serial.println("Setup finished."); } void updateDisplay(); void loop() { unsigned long currentTime = millis(); // Sensor readings #ifdef ENABLE_SENSORS float currentLight1 = lightMeter1.readLightLevel(); if (currentLight1 > maxLight1InWindow) maxLight1InWindow = currentLight1; float currentLight2 = lightMeter2.readLightLevel(); if (currentLight2 > maxLight2InWindow) maxLight2InWindow = currentLight2; #else float currentLight1 = 0.0; float currentLight2 = 0.0; #endif bool previousDevice1State = isDevice1Active; bool previousDevice2State = isDevice2Active; // Window Logic if (currentTime - lastWindowStartTime >= WINDOW_DURATION) { lastWindowStartTime = currentTime; // 1. Process Device 1 (Light 1) #ifdef ENABLE_SENSORS bool light1WindowOn = (maxLight1InWindow > LIGHT_ACTIVATION_THRESHOLD); if (light1WindowOn) { light1ConsecutiveOn++; light1ConsecutiveOff = 0; } else { light1ConsecutiveOff++; light1ConsecutiveOn = 0; } if (light1ConsecutiveOn >= REQUIRED_CONSECUTIVE_WINDOWS && currentTime > WARMUP_DURATION) { isDevice1Active = true; } else if (light1ConsecutiveOff >= REQUIRED_CONSECUTIVE_WINDOWS) { isDevice1Active = false; } // 2. Process Device 2 (Light 2) bool light2WindowOn = (maxLight2InWindow > LIGHT_ACTIVATION_THRESHOLD); if (light2WindowOn) { light2ConsecutiveOn++; light2ConsecutiveOff = 0; } else { light2ConsecutiveOff++; light2ConsecutiveOn = 0; } if (light2ConsecutiveOn >= REQUIRED_CONSECUTIVE_WINDOWS && currentTime > WARMUP_DURATION) { isDevice2Active = true; } else if (light2ConsecutiveOff >= REQUIRED_CONSECUTIVE_WINDOWS) { isDevice2Active = false; } // Reset Light Accumulators maxLight1InWindow = 0.0; maxLight2InWindow = 0.0; #endif } // Device 1 Notifications if (isDevice1Active != previousDevice1State) { String message; String title; String priority; if (isDevice1Active) { message = String(DEVICE1_NAME) + " started."; title = String(DEVICE1_NAME) + " START"; priority = "default"; } else { message = String(DEVICE1_NAME) + " finished."; title = String(DEVICE1_NAME) + " END"; priority = "high"; } #ifdef ENABLE_NOTIFICATIONS #ifdef ENABLE_WIFI NotificationMessage msg; strncpy(msg.message, message.c_str(), sizeof(msg.message) - 1); msg.message[sizeof(msg.message) - 1] = 0; strncpy(msg.title, title.c_str(), sizeof(msg.title) - 1); msg.title[sizeof(msg.title) - 1] = 0; strncpy(msg.priority, priority.c_str(), sizeof(msg.priority) - 1); msg.priority[sizeof(msg.priority) - 1] = 0; xQueueSend(notificationQueue, &msg, 0); #else Serial.println("WiFi Disabled. Notification skipped: " + message); #endif #else Serial.println("Notification (Disabled): " + title + " - " + message); #endif } // Device 2 Notifications if (isDevice2Active != previousDevice2State) { String message; String title; String priority; if (isDevice2Active) { message = String(DEVICE2_NAME) + " started."; title = String(DEVICE2_NAME) + " START"; priority = "default"; } else { message = String(DEVICE2_NAME) + " finished."; title = String(DEVICE2_NAME) + " END"; priority = "high"; } #ifdef ENABLE_NOTIFICATIONS #ifdef ENABLE_WIFI NotificationMessage msg; strncpy(msg.message, message.c_str(), sizeof(msg.message) - 1); msg.message[sizeof(msg.message) - 1] = 0; strncpy(msg.title, title.c_str(), sizeof(msg.title) - 1); msg.title[sizeof(msg.title) - 1] = 0; strncpy(msg.priority, priority.c_str(), sizeof(msg.priority) - 1); msg.priority[sizeof(msg.priority) - 1] = 0; xQueueSend(notificationQueue, &msg, 0); #else Serial.println("WiFi Disabled. Notification skipped: " + message); #endif #else Serial.println("Notification (Disabled): " + title + " - " + message); #endif } #ifdef ENABLE_DISPLAY // Update display data displayData[0] = String(DEVICE1_NAME) + ": " + String(currentLight1, 0) + (currentLight1 > LIGHT_ACTIVATION_THRESHOLD ? " = ON" : " = OFF"); displayData[1] = " " + String(isDevice1Active ? "OFF? " + String(light1ConsecutiveOff) : "ON? " + String(light1ConsecutiveOn)) + "/" + String(REQUIRED_CONSECUTIVE_WINDOWS); displayData[2] = String(DEVICE2_NAME) + ": " + String(currentLight2, 0) + (currentLight2 > LIGHT_ACTIVATION_THRESHOLD ? " = ON" : " = OFF"); displayData[3] = " " + String(isDevice2Active ? "OFF? " + String(light2ConsecutiveOff) : "ON? " + String(light2ConsecutiveOn)) + "/" + String(REQUIRED_CONSECUTIVE_WINDOWS); displayDataLines = 4; // Update display updateDisplay(); #endif } void updateDisplay() { #ifdef ENABLE_DISPLAY unsigned long currentTime = millis(); // Scroll logic if ((displayDataLines > displayTextLines) && (currentTime - lastDisplayScrollTime > DISPLAY_SCROLL_INTERVAL)) { lastDisplayScrollTime = currentTime; currentDisplayLine = (currentDisplayLine + 1) % displayDataLines; } display.clearDisplay(); display.setTextSize(1); display.setTextColor(WHITE); // Draw active status indicator int rectWidth = 10; int rectHeight = (SCREEN_HEIGHT / 2) - 2; int rectX = 2; int rectY1 = 1; int rectY2 = (SCREEN_HEIGHT / 2) + 1; int textX = rectX + rectWidth + 4; for (int i = 0; i < displayDataLines; i++) { int lineIndex = (currentDisplayLine + i) % displayDataLines; display.setCursor(textX, i * 8); display.println(displayData[lineIndex]); } // Draw Device 1 Indicator (Top) display.drawRect(rectX, rectY1, rectWidth, rectHeight, WHITE); if (isDevice1Active) { int innerWidth = rectWidth - 4; int step = (millis() / 150) % (innerWidth + 1); display.fillRect(rectX + 2, rectY1 + 2, step, rectHeight - 4, WHITE); } // Draw Device 2 Indicator (Bottom) display.drawRect(rectX, rectY2, rectWidth, rectHeight, WHITE); if (isDevice2Active) { int innerWidth = rectWidth - 4; int step = (millis() / 150) % (innerWidth + 1); display.fillRect(rectX + 2, rectY2 + 2, step, rectHeight - 4, WHITE); } // Network Status Indicator #ifdef ENABLE_WIFI int r = 8; int cx = SCREEN_WIDTH - r - 2; int cy = SCREEN_HEIGHT / 2; if (WiFi.status() != WL_CONNECTED) { display.drawLine(cx - r, cy - r, cx + r, cy + r, WHITE); display.drawLine(cx - r, cy + r, cx + r, cy - r, WHITE); } else if (isNetworkActive) { display.fillCircle(cx, cy, r, WHITE); } else { display.drawCircle(cx, cy, r, WHITE); } #ifdef ENABLE_NOTIFICATIONS if (notificationQueue != NULL) { int waiting = (int)uxQueueMessagesWaiting(notificationQueue); int startY = cy + r + 2; int h = SCREEN_HEIGHT - startY; for (int i = 0; i < waiting; i++) { int x = (cx - r) + (i * 2); if (x < SCREEN_WIDTH && h > 0) display.drawFastVLine(x, startY, h, WHITE); } } #endif #endif // Warmup Indicator if (currentTime < WARMUP_DURATION) { display.drawRect(0, 0, SCREEN_WIDTH, SCREEN_HEIGHT, WHITE); } display.display(); #endif }