#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 = "Dryer"; // Monitored by Vibration const char* DEVICE2_NAME = "Washer"; // Monitored by Light // Pin definitions const int VIBRATION_PIN = 4; // 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 lightMeter; #endif // Thresholds const float LIGHT_ACTIVATION_THRESHOLD = 100.0; const float LIGHT_DEACTIVATION_THRESHOLD = 20.0; const unsigned long VIBRATION_STATE_CHANGE_INTERVAL = 5000; // 5 seconds const unsigned long VIBRATION_ACTIVE_THRESHOLD = 10000; const int VIBRATION_WINDOW_SIZE = 4; // X * 5s seconds window const unsigned long WARMUP_DURATION = 20000; // 20 seconds warmup // Device state bool isVibrationActive = false; bool isLightActive = false; // Time tracking for sensor states unsigned long lastVibrationCheckTime = 0; unsigned long lightHighStartTime = 0; unsigned long lightLowStartTime = 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 unsigned long vibrationHistory[VIBRATION_WINDOW_SIZE]; int vibrationHistoryIdx = 0; unsigned long vibrationWindowTotal = 0; unsigned long vibrationTotalLow = 0; volatile unsigned long lastChangeTime = 0; volatile unsigned long lowTimeAccumulator = 0; volatile unsigned long highTimeAccumulator = 0; void IRAM_ATTR onChange() { unsigned long now = micros(); unsigned long duration = now - lastChangeTime; lastChangeTime = now; if (digitalRead(VIBRATION_PIN) == HIGH) { // Changed to HIGH, so previous state was LOW lowTimeAccumulator += duration; } else { // Changed to LOW, so previous state was HIGH highTimeAccumulator += duration; } } #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_SENSORS // Initialize vibration sensor pin pinMode(VIBRATION_PIN, INPUT_PULLUP); lastChangeTime = micros(); attachInterrupt(digitalPinToInterrupt(VIBRATION_PIN), onChange, CHANGE); // Initialize history buffer for(int i=0; i= VIBRATION_STATE_CHANGE_INTERVAL) { lastVibrationCheckTime = currentTime; noInterrupts(); unsigned long now = micros(); unsigned long duration = now - lastChangeTime; lastChangeTime = now; if (digitalRead(VIBRATION_PIN) == LOW) { lowTimeAccumulator += duration; } else { highTimeAccumulator += duration; } vibrationTotalLow = lowTimeAccumulator; lowTimeAccumulator = 0; highTimeAccumulator = 0; interrupts(); vibrationWindowTotal -= vibrationHistory[vibrationHistoryIdx]; vibrationHistory[vibrationHistoryIdx] = vibrationTotalLow; vibrationWindowTotal += vibrationTotalLow; vibrationHistoryIdx = (vibrationHistoryIdx + 1) % VIBRATION_WINDOW_SIZE; if (vibrationWindowTotal > VIBRATION_ACTIVE_THRESHOLD && currentTime > WARMUP_DURATION) { isVibrationActive = true; } else { isVibrationActive = false; } } #endif // Light state change logic if (currentLightLevel >= LIGHT_ACTIVATION_THRESHOLD && !isLightActive) { if (lightHighStartTime == 0) { lightHighStartTime = currentTime; } if (currentTime - lightHighStartTime >= VIBRATION_STATE_CHANGE_INTERVAL && currentTime > WARMUP_DURATION) { isLightActive = true; } lightLowStartTime = 0; } else if (currentLightLevel < LIGHT_DEACTIVATION_THRESHOLD && isLightActive) { if (lightLowStartTime == 0) { lightLowStartTime = currentTime; } if (currentTime - lightLowStartTime >= VIBRATION_STATE_CHANGE_INTERVAL) { isLightActive = false; } lightHighStartTime = 0; } else if(currentLightLevel >= LIGHT_ACTIVATION_THRESHOLD) { lightLowStartTime = 0; } else { lightHighStartTime = 0; } // Device 1 (Vibration) Notifications if (isVibrationActive != previousVibrationState) { String message; String title; String priority; if (isVibrationActive) { 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 (Light) Notifications if (isLightActive != previousLightState) { String message; String title; String priority; if (isLightActive) { message = String(DEVICE2_NAME) + " active."; title = String(DEVICE2_NAME) + " ACTIVE"; priority = "default"; } else { message = String(DEVICE2_NAME) + " inactive."; title = String(DEVICE2_NAME) + " INACTIVE"; 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(vibrationWindowTotal); displayData[1] = "Vibra: " + String(isVibrationActive ? "ON" : "OFF"); displayData[2] = String(DEVICE2_NAME) + ": " + String(currentLightLevel, 0); displayData[3] = "Light: " + String(isLightActive ? "ON" : "OFF"); 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 (isVibrationActive) { 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 (isLightActive) { 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 }