Compare commits
4 Commits
daa3cff439
...
f0714e352f
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f0714e352f | ||
|
|
ac08a2aa2b | ||
|
|
ee7df4f6e3 | ||
|
|
5bf3870869 |
1
.gitignore
vendored
Normal file
1
.gitignore
vendored
Normal file
@ -0,0 +1 @@
|
|||||||
|
secrets.h
|
||||||
@ -51,8 +51,9 @@ 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:**
|
||||||
- Open the `esp32_MachineNotify.ino` file.
|
- Copy `secrets_template.h` to a new file named `secrets.h` in the same directory.
|
||||||
- Replace the placeholder values for `WIFI_SSID`, `WIFI_PASSWORD`, and `NTFY_TOPIC` with your WiFi credentials and desired `ntfy.sh` topic.
|
- Open `secrets.h` and populate `SECRET_WIFI_SSID`, `SECRET_WIFI_PASSWORD`, and `SECRET_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.
|
||||||
|
|||||||
@ -1,10 +1,11 @@
|
|||||||
#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>
|
||||||
@ -22,19 +23,23 @@
|
|||||||
|
|
||||||
// WiFi credentials
|
// WiFi credentials
|
||||||
#ifdef ENABLE_WIFI
|
#ifdef ENABLE_WIFI
|
||||||
const char* WIFI_SSID = "YOUR_WIFI_SSID";
|
const char* WIFI_SSID = SECRET_WIFI_SSID;
|
||||||
const char* WIFI_PASSWORD = "YOUR_WIFI_PASSWORD";
|
const char* WIFI_PASSWORD = SECRET_WIFI_PASSWORD;
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
// ntfy.sh topic
|
// ntfy.sh topic
|
||||||
const char* NTFY_TOPIC = "YOUR_NTFY_TOPIC";
|
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
|
// 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 64
|
#define SCREEN_HEIGHT 32
|
||||||
#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
|
||||||
@ -47,36 +52,67 @@ BH1750 lightMeter;
|
|||||||
#endif
|
#endif
|
||||||
|
|
||||||
// Thresholds
|
// Thresholds
|
||||||
const float LIGHT_ACTIVATION_THRESHOLD = 200.0;
|
const float LIGHT_ACTIVATION_THRESHOLD = 100.0;
|
||||||
const float LIGHT_DEACTIVATION_THRESHOLD = 10.0;
|
const float LIGHT_DEACTIVATION_THRESHOLD = 20.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 vibrationLowStartTime = 0;
|
unsigned long lastVibrationCheckTime = 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[4];
|
String displayData[5];
|
||||||
int currentDisplayLine = 0;
|
int currentDisplayLine = 0;
|
||||||
unsigned long lastDisplayScrollTime = 0;
|
unsigned long lastDisplayScrollTime = 0;
|
||||||
const unsigned long DISPLAY_SCROLL_INTERVAL = 1000; // 1000 = 1 second
|
const unsigned long DISPLAY_SCROLL_INTERVAL = 2000; // 1000 = 1 second
|
||||||
|
const int displayTextLines = 4;
|
||||||
|
int displayDataLines = displayTextLines;
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
// Notification queue
|
// Notification queue
|
||||||
String queuedNotification = "";
|
String queuedMessage1 = "";
|
||||||
String queuedTitle = "";
|
String queuedTitle1 = "";
|
||||||
String queuedPriority = "";
|
String queuedPriority1 = "";
|
||||||
|
|
||||||
|
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(115200);
|
Serial.begin(9600);
|
||||||
|
Serial.println("Setup started.");
|
||||||
|
|
||||||
#if defined(ENABLE_SENSORS) || defined(ENABLE_DISPLAY)
|
#if defined(ENABLE_SENSORS) || defined(ENABLE_DISPLAY)
|
||||||
Wire.begin();
|
Wire.begin();
|
||||||
@ -85,6 +121,11 @@ 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)) {
|
||||||
@ -129,6 +170,9 @@ 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);
|
||||||
@ -137,14 +181,20 @@ 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 (queuedNotification != "") {
|
if (queuedMessage1 != "") {
|
||||||
if (sendNotification(queuedNotification, queuedTitle, queuedPriority)) {
|
if (sendNotification(queuedMessage1, queuedTitle1, queuedPriority1)) {
|
||||||
Serial.println("Successfully sent queued notification.");
|
Serial.println("Successfully sent queued notification 1.");
|
||||||
queuedNotification = ""; // Clear queue
|
queuedMessage1 = ""; // Clear queue
|
||||||
queuedTitle = "";
|
queuedTitle1 = "";
|
||||||
queuedPriority = "";
|
queuedPriority1 = "";
|
||||||
} 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
|
||||||
@ -153,35 +203,47 @@ 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
|
||||||
if (currentVibrationState && !isVibrationActive) { // Vibration is active (LOW)
|
#ifdef ENABLE_SENSORS
|
||||||
if (vibrationLowStartTime == 0) {
|
if (currentTime - lastVibrationCheckTime >= VIBRATION_STATE_CHANGE_INTERVAL) {
|
||||||
vibrationLowStartTime = currentTime;
|
lastVibrationCheckTime = 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) {
|
||||||
@ -206,30 +268,52 @@ void loop() {
|
|||||||
lightHighStartTime = 0;
|
lightHighStartTime = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Device 1 (Vibration) Notifications
|
||||||
// Overall device state
|
if (isVibrationActive != previousVibrationState) {
|
||||||
bool previousDeviceState = isDeviceActive;
|
|
||||||
isDeviceActive = isVibrationActive || isLightActive;
|
|
||||||
|
|
||||||
if (isDeviceActive != previousDeviceState) {
|
|
||||||
String message;
|
String message;
|
||||||
String title;
|
String title;
|
||||||
String priority;
|
String priority;
|
||||||
if (isDeviceActive) {
|
if (isVibrationActive) {
|
||||||
message = "Washing machine cycle started.";
|
message = String(DEVICE1_NAME) + " started.";
|
||||||
title = "Machine START";
|
title = String(DEVICE1_NAME) + " START";
|
||||||
priority = "default";
|
priority = "default";
|
||||||
} else {
|
} else {
|
||||||
message = "Washing machine cycle finished.";
|
message = String(DEVICE1_NAME) + " finished.";
|
||||||
title = "Machine END";
|
title = String(DEVICE1_NAME) + " END";
|
||||||
priority = "high";
|
priority = "high";
|
||||||
}
|
}
|
||||||
|
|
||||||
#ifdef ENABLE_NOTIFICATIONS
|
#ifdef ENABLE_NOTIFICATIONS
|
||||||
if (!sendNotification(message, title, priority)) {
|
if (!sendNotification(message, title, priority)) {
|
||||||
queuedNotification = message; // Failed to send, queue it (overwrites any older one)
|
queuedMessage1 = message;
|
||||||
queuedTitle = title;
|
queuedTitle1 = title;
|
||||||
queuedPriority = priority;
|
queuedPriority1 = 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);
|
||||||
@ -238,10 +322,11 @@ void loop() {
|
|||||||
|
|
||||||
#ifdef ENABLE_DISPLAY
|
#ifdef ENABLE_DISPLAY
|
||||||
// Update display data
|
// Update display data
|
||||||
displayData[0] = "Vibration: " + String(isVibrationActive ? "ON" : "OFF");
|
displayData[0] = String(DEVICE1_NAME) + ": " + String(vibrationWindowTotal);
|
||||||
displayData[1] = "Light: " + String(currentLightLevel) + " lx";
|
displayData[1] = "Vibra: " + String(isVibrationActive ? "ON" : "OFF");
|
||||||
displayData[2] = "Light Active: " + String(isLightActive ? "ON" : "OFF");
|
displayData[2] = String(DEVICE2_NAME) + ": " + String(currentLightLevel, 0);
|
||||||
displayData[3] = "Device: " + String(isDeviceActive ? "ACTIVE" : "INACTIVE");
|
displayData[3] = "Light: " + String(isLightActive ? "ON" : "OFF");
|
||||||
|
displayDataLines = 4;
|
||||||
|
|
||||||
// Update display
|
// Update display
|
||||||
updateDisplay();
|
updateDisplay();
|
||||||
@ -285,21 +370,46 @@ void updateDisplay() {
|
|||||||
unsigned long currentTime = millis();
|
unsigned long currentTime = millis();
|
||||||
|
|
||||||
// Scroll logic
|
// Scroll logic
|
||||||
if (currentTime - lastDisplayScrollTime > DISPLAY_SCROLL_INTERVAL) {
|
if ((displayDataLines > displayTextLines) && (currentTime - lastDisplayScrollTime > DISPLAY_SCROLL_INTERVAL)) {
|
||||||
lastDisplayScrollTime = currentTime;
|
lastDisplayScrollTime = currentTime;
|
||||||
currentDisplayLine = (currentDisplayLine + 1) % 4;
|
currentDisplayLine = (currentDisplayLine + 1) % displayDataLines;
|
||||||
}
|
}
|
||||||
|
|
||||||
display.clearDisplay();
|
display.clearDisplay();
|
||||||
display.setTextSize(1);
|
display.setTextSize(1);
|
||||||
display.setTextColor(WHITE);
|
display.setTextColor(WHITE);
|
||||||
display.setCursor(0, 0);
|
|
||||||
|
|
||||||
for (int i = 0; i < 4; i++) {
|
// Draw active status indicator
|
||||||
int lineIndex = (currentDisplayLine + i) % 4;
|
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]);
|
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
|
||||||
}
|
}
|
||||||
|
|||||||
20
secrets_template.h
Normal file
20
secrets_template.h
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
/*
|
||||||
|
* 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
|
||||||
Loading…
Reference in New Issue
Block a user