Compare commits

..

No commits in common. "f0714e352fcdce18d4d44368425acb600360f411" and "daa3cff43974340fdaf9e4a70357edf3084a9766" have entirely different histories.

4 changed files with 68 additions and 200 deletions

1
.gitignore vendored
View File

@ -1 +0,0 @@
secrets.h

View File

@ -51,9 +51,8 @@ This project turns an ESP32 into a smart device that monitors a washing machine'
- `BH1750` by Christopher Laws - `BH1750` by Christopher Laws
2. **Configure the Code:** 2. **Configure the Code:**
- Copy `secrets_template.h` to a new file named `secrets.h` in the same directory. - Open the `esp32_MachineNotify.ino` file.
- Open `secrets.h` and populate `SECRET_WIFI_SSID`, `SECRET_WIFI_PASSWORD`, and `SECRET_NTFY_TOPIC` with your WiFi credentials and desired `ntfy.sh` topic. - Replace the placeholder values for `WIFI_SSID`, `WIFI_PASSWORD`, and `NTFY_TOPIC` with your WiFi credentials and desired `ntfy.sh` topic.
- The `secrets.h` file is ignored by git to keep your credentials safe.
3. **Upload the Code:** 3. **Upload the Code:**
- Connect your ESP32 board to your computer. - Connect your ESP32 board to your computer.

View File

@ -1,11 +1,10 @@
#include <Wire.h> #include <Wire.h>
#include "secrets.h"
// Feature Configuration - Comment out to disable // Feature Configuration - Comment out to disable
#define ENABLE_SENSORS #define ENABLE_SENSORS
#define ENABLE_DISPLAY #define ENABLE_DISPLAY
//#define ENABLE_WIFI #define ENABLE_WIFI
//#define ENABLE_NOTIFICATIONS #define ENABLE_NOTIFICATIONS
#ifdef ENABLE_WIFI #ifdef ENABLE_WIFI
#include <WiFi.h> #include <WiFi.h>
@ -23,23 +22,19 @@
// WiFi credentials // WiFi credentials
#ifdef ENABLE_WIFI #ifdef ENABLE_WIFI
const char* WIFI_SSID = SECRET_WIFI_SSID; const char* WIFI_SSID = "YOUR_WIFI_SSID";
const char* WIFI_PASSWORD = SECRET_WIFI_PASSWORD; const char* WIFI_PASSWORD = "YOUR_WIFI_PASSWORD";
#endif #endif
// ntfy.sh topic // ntfy.sh topic
const char* NTFY_TOPIC = SECRET_NTFY_TOPIC; const char* NTFY_TOPIC = "YOUR_NTFY_TOPIC";
// Device Names
const char* DEVICE1_NAME = "Dryer"; // Monitored by Vibration
const char* DEVICE2_NAME = "Washer"; // Monitored by Light
// Pin definitions // Pin definitions
const int VIBRATION_PIN = 4; const int VIBRATION_PIN = 4;
// OLED display settings // OLED display settings
#define SCREEN_WIDTH 128 #define SCREEN_WIDTH 128
#define SCREEN_HEIGHT 32 #define SCREEN_HEIGHT 64
#define OLED_RESET -1 // Reset pin # (or -1 if sharing Arduino reset pin) #define OLED_RESET -1 // Reset pin # (or -1 if sharing Arduino reset pin)
#define SCREEN_ADDRESS 0x3C #define SCREEN_ADDRESS 0x3C
#ifdef ENABLE_DISPLAY #ifdef ENABLE_DISPLAY
@ -52,67 +47,36 @@ BH1750 lightMeter;
#endif #endif
// Thresholds // Thresholds
const float LIGHT_ACTIVATION_THRESHOLD = 100.0; const float LIGHT_ACTIVATION_THRESHOLD = 200.0;
const float LIGHT_DEACTIVATION_THRESHOLD = 20.0; const float LIGHT_DEACTIVATION_THRESHOLD = 10.0;
const unsigned long VIBRATION_STATE_CHANGE_INTERVAL = 5000; // 5 seconds 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 // Device state
bool isVibrationActive = false; bool isVibrationActive = false;
bool isLightActive = false; bool isLightActive = false;
bool isDeviceActive = false;
// Time tracking for sensor states // Time tracking for sensor states
unsigned long lastVibrationCheckTime = 0; unsigned long vibrationLowStartTime = 0;
unsigned long vibrationHighStartTime = 0;
unsigned long lightHighStartTime = 0; unsigned long lightHighStartTime = 0;
unsigned long lightLowStartTime = 0; unsigned long lightLowStartTime = 0;
// OLED display data // OLED display data
#ifdef ENABLE_DISPLAY #ifdef ENABLE_DISPLAY
String displayData[5]; String displayData[4];
int currentDisplayLine = 0; int currentDisplayLine = 0;
unsigned long lastDisplayScrollTime = 0; unsigned long lastDisplayScrollTime = 0;
const unsigned long DISPLAY_SCROLL_INTERVAL = 2000; // 1000 = 1 second const unsigned long DISPLAY_SCROLL_INTERVAL = 1000; // 1000 = 1 second
const int displayTextLines = 4;
int displayDataLines = displayTextLines;
#endif #endif
// Notification queue // Notification queue
String queuedMessage1 = ""; String queuedNotification = "";
String queuedTitle1 = ""; String queuedTitle = "";
String queuedPriority1 = ""; String queuedPriority = "";
String queuedMessage2 = "";
String queuedTitle2 = "";
String queuedPriority2 = "";
#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() { void setup() {
Serial.begin(9600); Serial.begin(115200);
Serial.println("Setup started.");
#if defined(ENABLE_SENSORS) || defined(ENABLE_DISPLAY) #if defined(ENABLE_SENSORS) || defined(ENABLE_DISPLAY)
Wire.begin(); Wire.begin();
@ -121,11 +85,6 @@ void setup() {
#ifdef ENABLE_SENSORS #ifdef ENABLE_SENSORS
// Initialize vibration sensor pin // Initialize vibration sensor pin
pinMode(VIBRATION_PIN, INPUT); pinMode(VIBRATION_PIN, INPUT);
lastChangeTime = micros();
attachInterrupt(digitalPinToInterrupt(VIBRATION_PIN), onChange, CHANGE);
// Initialize history buffer
for(int i=0; i<VIBRATION_WINDOW_SIZE; i++) vibrationHistory[i] = 0;
// Initialize light sensor // Initialize light sensor
if (!lightMeter.begin(BH1750::CONTINUOUS_HIGH_RES_MODE)) { if (!lightMeter.begin(BH1750::CONTINUOUS_HIGH_RES_MODE)) {
@ -170,9 +129,6 @@ void setup() {
display.display(); display.display();
delay(1000); delay(1000);
#endif #endif
Serial.println("Setup finished.");
} }
bool sendNotification(String message, String title, String priority); bool sendNotification(String message, String title, String priority);
@ -181,20 +137,14 @@ void updateDisplay();
void loop() { void loop() {
#ifdef ENABLE_NOTIFICATIONS #ifdef ENABLE_NOTIFICATIONS
// Try to send any queued notification // Try to send any queued notification
if (queuedMessage1 != "") { if (queuedNotification != "") {
if (sendNotification(queuedMessage1, queuedTitle1, queuedPriority1)) { if (sendNotification(queuedNotification, queuedTitle, queuedPriority)) {
Serial.println("Successfully sent queued notification 1."); Serial.println("Successfully sent queued notification.");
queuedMessage1 = ""; // Clear queue queuedNotification = ""; // Clear queue
queuedTitle1 = ""; queuedTitle = "";
queuedPriority1 = ""; queuedPriority = "";
} } else {
} // Failed, will retry on next loop
if (queuedMessage2 != "") {
if (sendNotification(queuedMessage2, queuedTitle2, queuedPriority2)) {
Serial.println("Successfully sent queued notification 2.");
queuedMessage2 = ""; // Clear queue
queuedTitle2 = "";
queuedPriority2 = "";
} }
} }
#endif #endif
@ -203,47 +153,35 @@ void loop() {
// Sensor readings // Sensor readings
#ifdef ENABLE_SENSORS #ifdef ENABLE_SENSORS
bool currentVibrationState = (digitalRead(VIBRATION_PIN) == LOW);
float currentLightLevel = lightMeter.readLightLevel(); float currentLightLevel = lightMeter.readLightLevel();
#else #else
bool currentVibrationState = false;
float currentLightLevel = 0.0; float currentLightLevel = 0.0;
#endif #endif
bool previousVibrationState = isVibrationActive;
bool previousLightState = isLightActive;
// Vibration state change logic // Vibration state change logic
#ifdef ENABLE_SENSORS if (currentVibrationState && !isVibrationActive) { // Vibration is active (LOW)
if (currentTime - lastVibrationCheckTime >= VIBRATION_STATE_CHANGE_INTERVAL) { if (vibrationLowStartTime == 0) {
lastVibrationCheckTime = currentTime; vibrationLowStartTime = currentTime;
noInterrupts();
unsigned long now = micros();
unsigned long duration = now - lastChangeTime;
lastChangeTime = now;
if (digitalRead(VIBRATION_PIN) == LOW) {
lowTimeAccumulator += duration;
} else {
highTimeAccumulator += duration;
} }
if (currentTime - vibrationLowStartTime >= VIBRATION_STATE_CHANGE_INTERVAL) {
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; isVibrationActive = true;
} else { }
vibrationHighStartTime = 0; // Reset high timer
} else if (!currentVibrationState && isVibrationActive) { // Vibration is inactive (HIGH)
if (vibrationHighStartTime == 0) {
vibrationHighStartTime = currentTime;
}
if (currentTime - vibrationHighStartTime >= VIBRATION_STATE_CHANGE_INTERVAL) {
isVibrationActive = false; isVibrationActive = false;
} }
vibrationLowStartTime = 0; // Reset low timer
} else if (currentVibrationState) {
vibrationHighStartTime = 0;
} else {
vibrationLowStartTime = 0;
} }
#endif
// Light state change logic // Light state change logic
if (currentLightLevel >= LIGHT_ACTIVATION_THRESHOLD && !isLightActive) { if (currentLightLevel >= LIGHT_ACTIVATION_THRESHOLD && !isLightActive) {
@ -268,52 +206,30 @@ void loop() {
lightHighStartTime = 0; lightHighStartTime = 0;
} }
// Device 1 (Vibration) Notifications
if (isVibrationActive != previousVibrationState) { // Overall device state
bool previousDeviceState = isDeviceActive;
isDeviceActive = isVibrationActive || isLightActive;
if (isDeviceActive != previousDeviceState) {
String message; String message;
String title; String title;
String priority; String priority;
if (isVibrationActive) { if (isDeviceActive) {
message = String(DEVICE1_NAME) + " started."; message = "Washing machine cycle started.";
title = String(DEVICE1_NAME) + " START"; title = "Machine START";
priority = "default"; priority = "default";
} else { } else {
message = String(DEVICE1_NAME) + " finished."; message = "Washing machine cycle finished.";
title = String(DEVICE1_NAME) + " END"; title = "Machine END";
priority = "high"; priority = "high";
} }
#ifdef ENABLE_NOTIFICATIONS #ifdef ENABLE_NOTIFICATIONS
if (!sendNotification(message, title, priority)) { if (!sendNotification(message, title, priority)) {
queuedMessage1 = message; queuedNotification = message; // Failed to send, queue it (overwrites any older one)
queuedTitle1 = title; queuedTitle = title;
queuedPriority1 = priority; queuedPriority = priority;
}
#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
if (!sendNotification(message, title, priority)) {
queuedMessage2 = message;
queuedTitle2 = title;
queuedPriority2 = priority;
} }
#else #else
Serial.println("Notification (Disabled): " + title + " - " + message); Serial.println("Notification (Disabled): " + title + " - " + message);
@ -322,11 +238,10 @@ void loop() {
#ifdef ENABLE_DISPLAY #ifdef ENABLE_DISPLAY
// Update display data // Update display data
displayData[0] = String(DEVICE1_NAME) + ": " + String(vibrationWindowTotal); displayData[0] = "Vibration: " + String(isVibrationActive ? "ON" : "OFF");
displayData[1] = "Vibra: " + String(isVibrationActive ? "ON" : "OFF"); displayData[1] = "Light: " + String(currentLightLevel) + " lx";
displayData[2] = String(DEVICE2_NAME) + ": " + String(currentLightLevel, 0); displayData[2] = "Light Active: " + String(isLightActive ? "ON" : "OFF");
displayData[3] = "Light: " + String(isLightActive ? "ON" : "OFF"); displayData[3] = "Device: " + String(isDeviceActive ? "ACTIVE" : "INACTIVE");
displayDataLines = 4;
// Update display // Update display
updateDisplay(); updateDisplay();
@ -370,46 +285,21 @@ void updateDisplay() {
unsigned long currentTime = millis(); unsigned long currentTime = millis();
// Scroll logic // Scroll logic
if ((displayDataLines > displayTextLines) && (currentTime - lastDisplayScrollTime > DISPLAY_SCROLL_INTERVAL)) { if (currentTime - lastDisplayScrollTime > DISPLAY_SCROLL_INTERVAL) {
lastDisplayScrollTime = currentTime; lastDisplayScrollTime = currentTime;
currentDisplayLine = (currentDisplayLine + 1) % displayDataLines; currentDisplayLine = (currentDisplayLine + 1) % 4;
} }
display.clearDisplay(); display.clearDisplay();
display.setTextSize(1); display.setTextSize(1);
display.setTextColor(WHITE); display.setTextColor(WHITE);
display.setCursor(0, 0);
// Draw active status indicator for (int i = 0; i < 4; i++) {
int rectWidth = 10; int lineIndex = (currentDisplayLine + i) % 4;
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]); 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);
}
display.display(); display.display();
#endif #endif
} }

View File

@ -1,20 +0,0 @@
/*
* secrets_template.h
*
* INSTRUCTIONS:
* 1. Copy this file to a new file named "secrets.h" in the same directory.
* 2. Open "secrets.h" and populate the values below with your actual configuration.
* 3. "secrets.h" is excluded from version control to protect your sensitive data.
*/
#ifndef SECRETS_H
#define SECRETS_H
// WiFi credentials
#define SECRET_WIFI_SSID "YOUR_WIFI_SSID"
#define SECRET_WIFI_PASSWORD "YOUR_WIFI_PASSWORD"
// ntfy.sh topic
#define SECRET_NTFY_TOPIC "YOUR_NTFY_TOPIC"
#endif