#include // Feature Configuration - Comment out to disable #define ENABLE_SENSORS #define ENABLE_DISPLAY //#define ENABLE_WIFI //#define ENABLE_NOTIFICATIONS #ifdef ENABLE_WIFI #include #include #endif #ifdef ENABLE_SENSORS #include #endif #ifdef ENABLE_DISPLAY #include #include #endif // WiFi credentials #ifdef ENABLE_WIFI const char* WIFI_SSID = "YOUR_WIFI_SSID"; const char* WIFI_PASSWORD = "YOUR_WIFI_PASSWORD"; #endif // ntfy.sh topic const char* NTFY_TOPIC = "YOUR_NTFY_TOPIC"; // 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 // Device state bool isVibrationActive = false; bool isLightActive = false; bool isDeviceActive = 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 // Notification queue String queuedNotification = ""; String queuedTitle = ""; String queuedPriority = ""; #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 void setup() { Serial.begin(9600); 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); 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) { 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) { 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; } // Overall device state bool previousDeviceState = isDeviceActive; isDeviceActive = isVibrationActive || isLightActive; if (isDeviceActive != previousDeviceState) { String message; String title; String priority; if (isDeviceActive) { message = "Washing machine cycle started."; title = "Machine START"; priority = "default"; } else { message = "Washing machine cycle finished."; title = "Machine END"; priority = "high"; } #ifdef ENABLE_NOTIFICATIONS if (!sendNotification(message, title, priority)) { queuedNotification = message; // Failed to send, queue it (overwrites any older one) queuedTitle = title; queuedPriority = priority; } #else Serial.println("Notification (Disabled): " + title + " - " + message); #endif } #ifdef ENABLE_DISPLAY // Update display data displayData[0] = "Vibra: " + String(vibrationWindowTotal); displayData[1] = " : " + String(isVibrationActive ? "ON" : "OFF"); displayData[2] = "Light: " + String(currentLightLevel) + " lx"; displayData[3] = " : " + String(isLightActive ? "ON" : "OFF"); displayDataLines = 4; // Update display updateDisplay(); #endif } bool sendNotification(String message, String title, String priority) { #ifdef ENABLE_WIFI 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", title); http.addHeader("Priority", priority); int httpResponseCode = http.POST(message); http.end(); if (httpResponseCode > 0) { Serial.print("HTTP Response code: "); Serial.println(httpResponseCode); return true; } else { Serial.print("Error on sending POST: "); Serial.println(httpResponseCode); return false; } } else { Serial.println("WiFi Disconnected. Cannot send notification."); return false; } #else Serial.println("WiFi Disabled. Notification skipped."); return true; #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); display.setCursor(0, 0); for (int i = 0; i < displayDataLines; i++) { int lineIndex = (currentDisplayLine + i) % displayDataLines; display.println(displayData[lineIndex]); } display.display(); #endif }