diff --git a/README.md b/README.md index c1f8d72..cff1cb3 100644 --- a/README.md +++ b/README.md @@ -1,11 +1,10 @@ -# ESP32 Washing Machine Activity Notifier +# ESP32 Dual Machine Activity Notifier -This project turns an ESP32 into a smart device that monitors a washing machine's activity using vibration and light sensors and sends notifications when a cycle starts and finishes. It also displays the current status on an OLED display. +This project turns an ESP32 into a smart device that monitors two machines (e.g., washer and dryer) using light sensors and sends notifications when a cycle starts and finishes. It also displays the current status on an OLED display. ## Features -- **Vibration Sensing:** Detects machine operation by monitoring vibrations. -- **Light Sensing:** Detects the machine's internal light to determine if the door is open. +- **Dual Light Sensing:** Monitors two separate devices using two BH1750 light sensors. - **Status Display:** Shows real-time sensor data and device status on a small OLED display. - **Notifications:** Sends start and stop notifications to a `ntfy.sh` topic. - **WiFi Connectivity:** Connects to your local WiFi network. @@ -13,33 +12,31 @@ This project turns an ESP32 into a smart device that monitors a washing machine' ## Hardware Requirements - ESP32 development board -- SW-420 vibration sensor module (or similar) -- BH1750 light sensor module +- 2x BH1750 light sensor modules - SSD1306 128x64 OLED display (I2C) - Breadboard and jumper wires ## Pinout -| Component | Pin on ESP32 | -| ------------------- | ------------ | -| Vibration Sensor | GPIO 4 | -| I2C SCL (OLED, BH1750) | GPIO 22 | -| I2C SDA (OLED, BH1750) | GPIO 21 | +| Component | Pin on ESP32 | Notes | +| ------------------- | ------------ | ----- | +| I2C SCL (OLED, Sensors)| GPIO 22 | Shared bus | +| I2C SDA (OLED, Sensors)| GPIO 21 | Shared bus | ### Wiring Diagram ``` -+-----------------+ +-----------------+ +-----------------+ +-----------------+ -| ESP32 Dev Board | | Vibration Sensor| | BH1750 Sensor | | OLED Display | -+-----------------+ +-----------------+ +-----------------+ +-----------------+ -| 3.3V |----->| VCC |----->| VCC |----->| VCC | -| GND |----->| GND |----->| GND |----->| GND | -| GPIO 4 |----->| OUT | | | | | -| | +-----------------+ | | | | -| GPIO 22 (SCL) |------------------------------>| SCL |----->| SCL | -| GPIO 21 (SDA) |------------------------------>| SDA |----->| SDA | -+-----------------+ +-----------------+ +-----------------+ ++-----------------+ +-----------------+ +-----------------+ +| ESP32 Dev Board | | BH1750 Sensor | | OLED Display | ++-----------------+ +-----------------+ +-----------------+ +| 3.3V |---------->| VCC |----->| VCC | +| GND |---------->| GND |----->| GND | +| | | | | | +| GPIO 22 (SCL) |---------->| SCL |----->| SCL | +| GPIO 21 (SDA) |---------->| SDA |----->| SDA | ++-----------------+ +-----------------+ +-----------------+ ``` +**Note:** For Sensor 1, connect the ADDR pin to GND (or leave floating if default is low). For Sensor 2, connect the ADDR pin to 3.3V to change its I2C address to 0x5C. ## Setup & Usage @@ -61,19 +58,16 @@ This project turns an ESP32 into a smart device that monitors a washing machine' - Click the upload button. 4. **Deploy the Device:** - - Place the vibration sensor on the washing machine where it can detect vibrations from the spin cycle. - - Place the light sensor inside the machine's drum to detect the internal light. + - Place Sensor 1 inside the first machine (e.g., Washer) to detect internal light. + - Place Sensor 2 inside the second machine (e.g., Dryer) to detect internal light. ## Code Explanation The code is structured as follows: -- **Includes and Definitions:** Includes the necessary libraries for WiFi, I2C devices, and the display. It also defines pins, thresholds, and other constants. -- **`setup()`:** Initializes serial communication, I2C, sensors, the OLED display, and connects to WiFi. -- **`loop()`:** This is the main part of the program. - - It continuously reads the values from the vibration and light sensors. - - It implements a non-blocking logic using `millis()` to check if the sensors have been in a certain state (e.g., vibration active or light level high) for a continuous period of 5 seconds. - - It updates the overall device state (`isDeviceActive`) based on the individual sensor states. + - It continuously reads the values from both light sensors. + - It implements a non-blocking logic using `millis()` to check if the sensors have been in a certain state (light level high) for a continuous period. + - It updates the overall device states (`isDevice1Active`, `isDevice2Active`) based on the individual sensor states. - If the device state changes, it calls `sendNotification()`. - It calls `updateDisplay()` to show the latest data. - **`sendNotification()`:** Sends a POST request to the configured `ntfy.sh` topic with a message. diff --git a/esp32_MachineNotify.ino b/esp32_MachineNotify.ino index 5c566d7..51cf678 100644 --- a/esp32_MachineNotify.ino +++ b/esp32_MachineNotify.ino @@ -34,11 +34,8 @@ const char* WIFI_PASSWORD = SECRET_WIFI_PASSWORD; 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; +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 @@ -51,19 +48,19 @@ Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, OLED_RESET); // Light sensor #ifdef ENABLE_SENSORS -BH1750 lightMeter; +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 VIBRATION_ACTIVE_THRESHOLD = 100000; const unsigned long WARMUP_DURATION = 30000; // 20 seconds warmup // Device state -bool isVibrationActive = false; -bool isLightActive = false; +bool isDevice1Active = false; +bool isDevice2Active = false; // Time tracking for sensor states unsigned long lastWindowStartTime = 0; @@ -88,29 +85,13 @@ QueueHandle_t notificationQueue; volatile bool isNetworkActive = false; #ifdef ENABLE_SENSORS -unsigned long vibrationTotalLow = 0; -int vibConsecutiveOn = 0; -int vibConsecutiveOff = 0; -int lightConsecutiveOn = 0; -int lightConsecutiveOff = 0; -float maxLightInWindow = 0.0; -volatile unsigned long lastChangeTime = 0; -volatile unsigned long lowTimeAccumulator = 0; -volatile unsigned long highTimeAccumulator = 0; +int light1ConsecutiveOn = 0; +int light1ConsecutiveOff = 0; +float maxLight1InWindow = 0.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; - } -} +int light2ConsecutiveOn = 0; +int light2ConsecutiveOff = 0; +float maxLight2InWindow = 0.0; #endif #ifdef ENABLE_WIFI @@ -180,14 +161,13 @@ void setup() { #endif #ifdef ENABLE_SENSORS - // Initialize vibration sensor pin - pinMode(VIBRATION_PIN, INPUT_PULLUP); - lastChangeTime = micros(); - attachInterrupt(digitalPinToInterrupt(VIBRATION_PIN), onChange, CHANGE); - - // Initialize light sensor - if (!lightMeter.begin(BH1750::CONTINUOUS_HIGH_RES_MODE)) { - Serial.println(F("Could not find a valid BH1750 sensor, check wiring!")); + // 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!")); + while (1) { } + } + if (!lightMeter2.begin(BH1750::CONTINUOUS_HIGH_RES_MODE, 0x5C)) { + Serial.println(F("Could not find valid BH1750 sensor at 0x5C, check wiring!")); while (1) { } } #endif @@ -202,8 +182,17 @@ void setup() { display.setTextSize(1); display.setTextColor(WHITE); display.setCursor(0, 0); - display.println(F("Initializing...")); + display.println(F("Hello!")); display.display(); + delay(500); + + #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 @@ -234,6 +223,13 @@ void setup() { 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."); } @@ -245,83 +241,70 @@ void loop() { // Sensor readings #ifdef ENABLE_SENSORS - float currentLightLevel = lightMeter.readLightLevel(); - if (currentLightLevel > maxLightInWindow) { - maxLightInWindow = currentLightLevel; - } + float currentLight1 = lightMeter1.readLightLevel(); + if (currentLight1 > maxLight1InWindow) maxLight1InWindow = currentLight1; + + float currentLight2 = lightMeter2.readLightLevel(); + if (currentLight2 > maxLight2InWindow) maxLight2InWindow = currentLight2; #else - float currentLightLevel = 0.0; + float currentLight1 = 0.0; + float currentLight2 = 0.0; #endif - bool previousVibrationState = isVibrationActive; - bool previousLightState = isLightActive; + bool previousDevice1State = isDevice1Active; + bool previousDevice2State = isDevice2Active; // Window Logic if (currentTime - lastWindowStartTime >= WINDOW_DURATION) { lastWindowStartTime = currentTime; - // 1. Process Vibration + // 1. Process Device 1 (Light 1) #ifdef ENABLE_SENSORS - noInterrupts(); - unsigned long now = micros(); - unsigned long duration = now - lastChangeTime; - lastChangeTime = now; + bool light1WindowOn = (maxLight1InWindow > LIGHT_ACTIVATION_THRESHOLD); - if (digitalRead(VIBRATION_PIN) == LOW) { - lowTimeAccumulator += duration; + if (light1WindowOn) { + light1ConsecutiveOn++; + light1ConsecutiveOff = 0; } else { - highTimeAccumulator += duration; + light1ConsecutiveOff++; + light1ConsecutiveOn = 0; } - vibrationTotalLow = lowTimeAccumulator; - lowTimeAccumulator = 0; - highTimeAccumulator = 0; - interrupts(); + if (light1ConsecutiveOn >= REQUIRED_CONSECUTIVE_WINDOWS && currentTime > WARMUP_DURATION) { + isDevice1Active = true; + } else if (light1ConsecutiveOff >= REQUIRED_CONSECUTIVE_WINDOWS) { + isDevice1Active = false; + } - bool vibWindowOn = (vibrationTotalLow > VIBRATION_ACTIVE_THRESHOLD); + // 2. Process Device 2 (Light 2) + bool light2WindowOn = (maxLight2InWindow > LIGHT_ACTIVATION_THRESHOLD); - if (vibWindowOn) { - vibConsecutiveOn++; - vibConsecutiveOff = 0; + if (light2WindowOn) { + light2ConsecutiveOn++; + light2ConsecutiveOff = 0; } else { - vibConsecutiveOff++; - vibConsecutiveOn = 0; + light2ConsecutiveOff++; + light2ConsecutiveOn = 0; } - if (vibConsecutiveOn >= REQUIRED_CONSECUTIVE_WINDOWS && currentTime > WARMUP_DURATION) { - isVibrationActive = true; - } else if (vibConsecutiveOff >= REQUIRED_CONSECUTIVE_WINDOWS) { - isVibrationActive = false; + 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 - - // 2. Process Light - bool lightWindowOn = (maxLightInWindow > LIGHT_ACTIVATION_THRESHOLD); - - if (lightWindowOn) { - lightConsecutiveOn++; - lightConsecutiveOff = 0; - } else { - lightConsecutiveOff++; - lightConsecutiveOn = 0; - } - - if (lightConsecutiveOn >= REQUIRED_CONSECUTIVE_WINDOWS && currentTime > WARMUP_DURATION) { - isLightActive = true; - } else if (lightConsecutiveOff >= REQUIRED_CONSECUTIVE_WINDOWS) { - isLightActive = false; - } - - // Reset Light Accumulator - maxLightInWindow = 0.0; } - // Device 1 (Vibration) Notifications - if (isVibrationActive != previousVibrationState) { + // Device 1 Notifications + if (isDevice1Active != previousDevice1State) { String message; String title; String priority; - if (isVibrationActive) { + if (isDevice1Active) { message = String(DEVICE1_NAME) + " started."; title = String(DEVICE1_NAME) + " START"; priority = "default"; @@ -349,18 +332,18 @@ void loop() { #endif } - // Device 2 (Light) Notifications - if (isLightActive != previousLightState) { + // Device 2 Notifications + if (isDevice2Active != previousDevice2State) { String message; String title; String priority; - if (isLightActive) { - message = String(DEVICE2_NAME) + " active."; - title = String(DEVICE2_NAME) + " ACTIVE"; + if (isDevice2Active) { + message = String(DEVICE2_NAME) + " started."; + title = String(DEVICE2_NAME) + " START"; priority = "default"; } else { - message = String(DEVICE2_NAME) + " inactive."; - title = String(DEVICE2_NAME) + " INACTIVE"; + message = String(DEVICE2_NAME) + " finished."; + title = String(DEVICE2_NAME) + " END"; priority = "high"; } @@ -384,10 +367,10 @@ void loop() { #ifdef ENABLE_DISPLAY // Update display data - displayData[0] = String(DEVICE1_NAME) + ": " + (vibrationTotalLow > VIBRATION_ACTIVE_THRESHOLD ? "YES" : "NO"); - displayData[1] = "Vibra: " + String(isVibrationActive ? "OFF? " + String(vibConsecutiveOff) : "ON? " + String(vibConsecutiveOn)) + "/" + String(REQUIRED_CONSECUTIVE_WINDOWS); - displayData[2] = String(DEVICE2_NAME) + ": " + String(currentLightLevel, 0); - displayData[3] = "Light: " + String(isLightActive ? "OFF? " + String(lightConsecutiveOff) : "ON? " + String(lightConsecutiveOn)) + "/" + String(REQUIRED_CONSECUTIVE_WINDOWS); + 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 @@ -426,7 +409,7 @@ void updateDisplay() { // Draw Device 1 Indicator (Top) display.drawRect(rectX, rectY1, rectWidth, rectHeight, WHITE); - if (isVibrationActive) { + if (isDevice1Active) { int innerWidth = rectWidth - 4; int step = (millis() / 150) % (innerWidth + 1); display.fillRect(rectX + 2, rectY1 + 2, step, rectHeight - 4, WHITE); @@ -434,7 +417,7 @@ void updateDisplay() { // Draw Device 2 Indicator (Bottom) display.drawRect(rectX, rectY2, rectWidth, rectHeight, WHITE); - if (isLightActive) { + if (isDevice2Active) { int innerWidth = rectWidth - 4; int step = (millis() / 150) % (innerWidth + 1); display.fillRect(rectX + 2, rectY2 + 2, step, rectHeight - 4, WHITE);