473 lines
14 KiB
C++
473 lines
14 KiB
C++
#include <Wire.h>
|
|
#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 <WiFi.h>
|
|
#include <HTTPClient.h>
|
|
#include <freertos/FreeRTOS.h>
|
|
#include <freertos/task.h>
|
|
#include <freertos/queue.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 = 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 = "Wash"; // Monitored by Light 1 (0x23)
|
|
const char* DEVICE2_NAME = "Dry "; // Monitored by Light 2 (0x5C)
|
|
|
|
// 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 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 WARMUP_DURATION = 30000; // 20 seconds warmup
|
|
|
|
// Device state
|
|
bool isDevice1Active = false;
|
|
bool isDevice2Active = false;
|
|
|
|
// Time tracking for sensor states
|
|
unsigned long lastWindowStartTime = 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
|
|
int light1ConsecutiveOn = 0;
|
|
int light1ConsecutiveOff = 0;
|
|
float maxLight1InWindow = 0.0;
|
|
|
|
int light2ConsecutiveOn = 0;
|
|
int light2ConsecutiveOff = 0;
|
|
float maxLight2InWindow = 0.0;
|
|
#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_DISPLAY
|
|
// Initialize OLED display
|
|
if (!display.begin(SSD1306_SWITCHCAPVCC, SCREEN_ADDRESS)) {
|
|
Serial.println(F("SSD1306 allocation failed"));
|
|
while (1) { delay(1000); }
|
|
}
|
|
display.clearDisplay();
|
|
display.setTextSize(1);
|
|
display.setTextColor(WHITE);
|
|
display.setCursor(0, 0);
|
|
display.println(F("Hello!"));
|
|
display.display();
|
|
delay(500);
|
|
|
|
#ifdef ENABLE_SENSORS
|
|
// 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!"));
|
|
display.clearDisplay();
|
|
display.setCursor(0, 0);
|
|
display.println(F("Err: Sensor 0x23"));
|
|
display.println(" " + String(DEVICE1_NAME));
|
|
display.println(F(" not found"));
|
|
display.display();
|
|
while (1) { delay(1000); }
|
|
}
|
|
if (!lightMeter2.begin(BH1750::CONTINUOUS_HIGH_RES_MODE, 0x5C)) {
|
|
Serial.println(F("Could not find valid BH1750 sensor at 0x5C, check wiring!"));
|
|
display.clearDisplay();
|
|
display.setCursor(0, 0);
|
|
display.println(F("Err: Sensor 0x5C"));
|
|
display.println(" " + String(DEVICE2_NAME));
|
|
display.println(F(" not found"));
|
|
display.display();
|
|
while (1) { delay(1000); }
|
|
}
|
|
#endif
|
|
|
|
#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
|
|
// 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());
|
|
|
|
#ifdef ENABLE_NOTIFICATIONS
|
|
notificationQueue = xQueueCreate(10, sizeof(NotificationMessage));
|
|
xTaskCreatePinnedToCore(networkTask, "NetworkTask", 8192, NULL, 1, NULL, 0);
|
|
#endif
|
|
#endif
|
|
|
|
#if defined(ENABLE_DISPLAY) && defined(ENABLE_WIFI)
|
|
display.clearDisplay();
|
|
display.setCursor(0,0);
|
|
display.println("WiFi Connected");
|
|
display.display();
|
|
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.");
|
|
}
|
|
|
|
void updateDisplay();
|
|
|
|
void loop() {
|
|
unsigned long currentTime = millis();
|
|
|
|
// Sensor readings
|
|
#ifdef ENABLE_SENSORS
|
|
float currentLight1 = lightMeter1.readLightLevel();
|
|
if (currentLight1 > maxLight1InWindow) maxLight1InWindow = currentLight1;
|
|
|
|
float currentLight2 = lightMeter2.readLightLevel();
|
|
if (currentLight2 > maxLight2InWindow) maxLight2InWindow = currentLight2;
|
|
#else
|
|
float currentLight1 = 0.0;
|
|
float currentLight2 = 0.0;
|
|
#endif
|
|
|
|
bool previousDevice1State = isDevice1Active;
|
|
bool previousDevice2State = isDevice2Active;
|
|
|
|
// Window Logic
|
|
if (currentTime - lastWindowStartTime >= WINDOW_DURATION) {
|
|
lastWindowStartTime = currentTime;
|
|
|
|
// 1. Process Device 1 (Light 1)
|
|
#ifdef ENABLE_SENSORS
|
|
bool light1WindowOn = (maxLight1InWindow > LIGHT_ACTIVATION_THRESHOLD);
|
|
|
|
if (light1WindowOn) {
|
|
light1ConsecutiveOn++;
|
|
light1ConsecutiveOff = 0;
|
|
} else {
|
|
light1ConsecutiveOff++;
|
|
light1ConsecutiveOn = 0;
|
|
}
|
|
|
|
if (light1ConsecutiveOn >= REQUIRED_CONSECUTIVE_WINDOWS && currentTime > WARMUP_DURATION) {
|
|
isDevice1Active = true;
|
|
} else if (light1ConsecutiveOff >= REQUIRED_CONSECUTIVE_WINDOWS) {
|
|
isDevice1Active = false;
|
|
}
|
|
|
|
// 2. Process Device 2 (Light 2)
|
|
bool light2WindowOn = (maxLight2InWindow > LIGHT_ACTIVATION_THRESHOLD);
|
|
|
|
if (light2WindowOn) {
|
|
light2ConsecutiveOn++;
|
|
light2ConsecutiveOff = 0;
|
|
} else {
|
|
light2ConsecutiveOff++;
|
|
light2ConsecutiveOn = 0;
|
|
}
|
|
|
|
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
|
|
}
|
|
|
|
// Device 1 Notifications
|
|
if (isDevice1Active != previousDevice1State) {
|
|
String message;
|
|
String title;
|
|
String priority;
|
|
if (isDevice1Active) {
|
|
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 Notifications
|
|
if (isDevice2Active != previousDevice2State) {
|
|
String message;
|
|
String title;
|
|
String priority;
|
|
if (isDevice2Active) {
|
|
message = String(DEVICE2_NAME) + " started.";
|
|
title = String(DEVICE2_NAME) + " START";
|
|
priority = "default";
|
|
} else {
|
|
message = String(DEVICE2_NAME) + " finished.";
|
|
title = String(DEVICE2_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
|
|
}
|
|
|
|
#ifdef ENABLE_DISPLAY
|
|
// Update display data
|
|
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
|
|
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 (isDevice1Active) {
|
|
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 (isDevice2Active) {
|
|
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
|
|
}
|