washing-machine-notify/esp32_MachineNotify.ino
2026-02-12 19:55:45 +01:00

306 lines
8.0 KiB
C++

#include <Wire.h>
// Feature Configuration - Comment out to disable
#define ENABLE_SENSORS
#define ENABLE_DISPLAY
#define ENABLE_WIFI
#define ENABLE_NOTIFICATIONS
#ifdef ENABLE_WIFI
#include <WiFi.h>
#include <HTTPClient.h>
#endif
#ifdef ENABLE_SENSORS
#include <BH1750.h>
#endif
#ifdef ENABLE_DISPLAY
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>
#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 64
#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 = 200.0;
const float LIGHT_DEACTIVATION_THRESHOLD = 10.0;
const unsigned long VIBRATION_STATE_CHANGE_INTERVAL = 5000; // 5 seconds
// Device state
bool isVibrationActive = false;
bool isLightActive = false;
bool isDeviceActive = false;
// Time tracking for sensor states
unsigned long vibrationLowStartTime = 0;
unsigned long vibrationHighStartTime = 0;
unsigned long lightHighStartTime = 0;
unsigned long lightLowStartTime = 0;
// OLED display data
#ifdef ENABLE_DISPLAY
String displayData[4];
int currentDisplayLine = 0;
unsigned long lastDisplayScrollTime = 0;
const unsigned long DISPLAY_SCROLL_INTERVAL = 1000; // 1000 = 1 second
#endif
// Notification queue
String queuedNotification = "";
String queuedTitle = "";
String queuedPriority = "";
void setup() {
Serial.begin(115200);
#if defined(ENABLE_SENSORS) || defined(ENABLE_DISPLAY)
Wire.begin();
#endif
#ifdef ENABLE_SENSORS
// Initialize vibration sensor pin
pinMode(VIBRATION_PIN, INPUT);
// Initialize light sensor
if (!lightMeter.begin(BH1750::CONTINUOUS_HIGH_RES_MODE)) {
Serial.println(F("Could not find a valid BH1750 sensor, check wiring!"));
while (1) { }
}
#endif
#ifdef ENABLE_DISPLAY
// Initialize OLED display
if (!display.begin(SSD1306_SWITCHCAPVCC, SCREEN_ADDRESS)) {
Serial.println(F("SSD1306 allocation failed"));
for (;;);
}
display.clearDisplay();
display.setTextSize(1);
display.setTextColor(WHITE);
display.setCursor(0, 0);
display.println(F("Initializing..."));
display.display();
#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());
#endif
#if defined(ENABLE_DISPLAY) && defined(ENABLE_WIFI)
display.clearDisplay();
display.setCursor(0,0);
display.println("WiFi Connected");
display.display();
delay(1000);
#endif
}
bool sendNotification(String message, String title, String priority);
void updateDisplay();
void loop() {
#ifdef ENABLE_NOTIFICATIONS
// Try to send any queued notification
if (queuedNotification != "") {
if (sendNotification(queuedNotification, queuedTitle, queuedPriority)) {
Serial.println("Successfully sent queued notification.");
queuedNotification = ""; // Clear queue
queuedTitle = "";
queuedPriority = "";
} else {
// Failed, will retry on next loop
}
}
#endif
unsigned long currentTime = millis();
// Sensor readings
#ifdef ENABLE_SENSORS
bool currentVibrationState = (digitalRead(VIBRATION_PIN) == LOW);
float currentLightLevel = lightMeter.readLightLevel();
#else
bool currentVibrationState = false;
float currentLightLevel = 0.0;
#endif
// Vibration state change logic
if (currentVibrationState && !isVibrationActive) { // Vibration is active (LOW)
if (vibrationLowStartTime == 0) {
vibrationLowStartTime = currentTime;
}
if (currentTime - vibrationLowStartTime >= VIBRATION_STATE_CHANGE_INTERVAL) {
isVibrationActive = true;
}
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;
}
vibrationLowStartTime = 0; // Reset low timer
} else if (currentVibrationState) {
vibrationHighStartTime = 0;
} else {
vibrationLowStartTime = 0;
}
// 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] = "Vibration: " + String(isVibrationActive ? "ON" : "OFF");
displayData[1] = "Light: " + String(currentLightLevel) + " lx";
displayData[2] = "Light Active: " + String(isLightActive ? "ON" : "OFF");
displayData[3] = "Device: " + String(isDeviceActive ? "ACTIVE" : "INACTIVE");
// 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 (currentTime - lastDisplayScrollTime > DISPLAY_SCROLL_INTERVAL) {
lastDisplayScrollTime = currentTime;
currentDisplayLine = (currentDisplayLine + 1) % 4;
}
display.clearDisplay();
display.setTextSize(1);
display.setTextColor(WHITE);
display.setCursor(0, 0);
for (int i = 0; i < 4; i++) {
int lineIndex = (currentDisplayLine + i) % 4;
display.println(displayData[lineIndex]);
}
display.display();
#endif
}