snapshot
This commit is contained in:
@@ -26,9 +26,11 @@ static const int NUM_WIFI = sizeof(wifiNetworks) / sizeof(wifiNetworks[0]);
|
|||||||
#define TOPIC_SUFFIX ""
|
#define TOPIC_SUFFIX ""
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#define ALERT_URL NTFY_BASE "/ALERT_klubhaus_topic" TOPIC_SUFFIX "/json?since=10s&poll=1"
|
// Change since=10s to since=20s (must be > poll interval of 15s, but not too large)
|
||||||
#define SILENCE_URL NTFY_BASE "/SILENCE_klubhaus_topic" TOPIC_SUFFIX "/json?since=10s&poll=1"
|
#define ALERT_URL NTFY_BASE "/ALERT_klubhaus_topic" TOPIC_SUFFIX "/json?since=20s&poll=1"
|
||||||
#define ADMIN_URL NTFY_BASE "/ADMIN_klubhaus_topic" TOPIC_SUFFIX "/json?since=10s&poll=1"
|
#define SILENCE_URL NTFY_BASE "/SILENCE_klubhaus_topic" TOPIC_SUFFIX "/json?since=20s&poll=1"
|
||||||
|
#define ADMIN_URL NTFY_BASE "/ADMIN_klubhaus_topic" TOPIC_SUFFIX "/json?since=20s&poll=1"
|
||||||
|
|
||||||
#define STATUS_URL NTFY_BASE "/STATUS_klubhaus_topic" TOPIC_SUFFIX
|
#define STATUS_URL NTFY_BASE "/STATUS_klubhaus_topic" TOPIC_SUFFIX
|
||||||
|
|
||||||
// =====================================================================
|
// =====================================================================
|
||||||
|
|||||||
@@ -13,6 +13,13 @@ void DoorbellLogic::begin() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void DoorbellLogic::beginWiFi() {
|
void DoorbellLogic::beginWiFi() {
|
||||||
|
_instance = this; // ← MISSING
|
||||||
|
|
||||||
|
WiFi.mode(WIFI_STA);
|
||||||
|
WiFi.setSleep(false);
|
||||||
|
WiFi.setAutoReconnect(true); // ← MISSING
|
||||||
|
WiFi.onEvent(onWiFiEvent); // ← MISSING
|
||||||
|
|
||||||
for (int i = 0; i < NUM_WIFI; i++) {
|
for (int i = 0; i < NUM_WIFI; i++) {
|
||||||
_wifiMulti.addAP(wifiNetworks[i].ssid, wifiNetworks[i].pass);
|
_wifiMulti.addAP(wifiNetworks[i].ssid, wifiNetworks[i].pass);
|
||||||
}
|
}
|
||||||
@@ -127,15 +134,26 @@ void DoorbellLogic::update() {
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (now - _lastHeartbeat >= HEARTBEAT_INTERVAL_MS) {
|
if (now - _lastHeartbeat >= HEARTBEAT_INTERVAL_MS) {
|
||||||
_lastHeartbeat = now;
|
_lastHeartbeat = now;
|
||||||
Serial.printf("[%lus] %s | WiFi:%s | heap:%dKB\n",
|
uint32_t heap = ESP.getFreeHeap();
|
||||||
now / 1000,
|
Serial.printf("[%lus] %s | WiFi:%s RSSI:%d | heap:%dKB | minHeap:%dKB\n",
|
||||||
_state == DeviceState::SILENT ? "SILENT" :
|
now / 1000,
|
||||||
_state == DeviceState::ALERTING ? "ALERT" : "WAKE",
|
_state == DeviceState::SILENT ? "SILENT" :
|
||||||
WiFi.isConnected() ? "OK" : "DOWN",
|
_state == DeviceState::ALERTING ? "ALERT" : "WAKE",
|
||||||
ESP.getFreeHeap() / 1024);
|
WiFi.isConnected() ? "OK" : "DOWN",
|
||||||
|
WiFi.RSSI(),
|
||||||
|
heap / 1024,
|
||||||
|
ESP.getMinFreeHeap() / 1024);
|
||||||
|
|
||||||
|
if (heap < 20000) {
|
||||||
|
Serial.println("[HEAP] CRITICAL — rebooting!");
|
||||||
|
queueStatus("REBOOTING", "low heap");
|
||||||
|
flushStatus();
|
||||||
|
delay(200);
|
||||||
|
ESP.restart();
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
updateScreenState();
|
updateScreenState();
|
||||||
}
|
}
|
||||||
@@ -205,10 +223,12 @@ void DoorbellLogic::transitionTo(DeviceState newState) {
|
|||||||
switch (newState) {
|
switch (newState) {
|
||||||
case DeviceState::SILENT:
|
case DeviceState::SILENT:
|
||||||
_screen.screen = ScreenID::OFF;
|
_screen.screen = ScreenID::OFF;
|
||||||
|
_alertStartEpoch = 0; // <-- ADD: clear on silence
|
||||||
Serial.println("-> SILENT");
|
Serial.println("-> SILENT");
|
||||||
break;
|
break;
|
||||||
case DeviceState::ALERTING:
|
case DeviceState::ALERTING:
|
||||||
_alertStart = now;
|
_alertStart = now;
|
||||||
|
_alertStartEpoch = _lastEpoch; // <-- ADD: record NTP time of alert
|
||||||
_lastBlink = now;
|
_lastBlink = now;
|
||||||
_blinkState = false;
|
_blinkState = false;
|
||||||
_screen.screen = ScreenID::ALERT;
|
_screen.screen = ScreenID::ALERT;
|
||||||
@@ -233,6 +253,18 @@ void DoorbellLogic::handleAlert(const String& msg) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void DoorbellLogic::handleSilence(const String& msg) {
|
void DoorbellLogic::handleSilence(const String& msg) {
|
||||||
|
// When called from ntfy poll, reject silence messages that predate the current alert.
|
||||||
|
// This prevents stale silence messages from immediately canceling new alerts.
|
||||||
|
// When called from touch/admin/timeout, _lastParsedMsgEpoch is 0 → bypass check.
|
||||||
|
if (_state == DeviceState::ALERTING &&
|
||||||
|
_lastParsedMsgEpoch > 0 &&
|
||||||
|
_alertStartEpoch > 0 &&
|
||||||
|
_lastParsedMsgEpoch <= _alertStartEpoch) {
|
||||||
|
Serial.printf("[SILENCE] Ignored — predates alert (msg:%ld alert:%ld)\n",
|
||||||
|
(long)_lastParsedMsgEpoch, (long)_alertStartEpoch);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
_currentMessage = "";
|
_currentMessage = "";
|
||||||
transitionTo(DeviceState::SILENT);
|
transitionTo(DeviceState::SILENT);
|
||||||
queueStatus("SILENT", "silenced");
|
queueStatus("SILENT", "silenced");
|
||||||
@@ -331,44 +363,53 @@ void DoorbellLogic::checkNetwork() {
|
|||||||
// ntfy Polling
|
// ntfy Polling
|
||||||
// =====================================================================
|
// =====================================================================
|
||||||
void DoorbellLogic::pollTopics() {
|
void DoorbellLogic::pollTopics() {
|
||||||
|
Serial.println("[POLL] Starting poll cycle...");
|
||||||
pollTopic(ALERT_URL, &DoorbellLogic::handleAlert, "ALERT", _lastAlertId);
|
pollTopic(ALERT_URL, &DoorbellLogic::handleAlert, "ALERT", _lastAlertId);
|
||||||
|
yield(); // ← MISSING
|
||||||
pollTopic(SILENCE_URL, &DoorbellLogic::handleSilence, "SILENCE", _lastSilenceId);
|
pollTopic(SILENCE_URL, &DoorbellLogic::handleSilence, "SILENCE", _lastSilenceId);
|
||||||
|
yield(); // ← MISSING
|
||||||
pollTopic(ADMIN_URL, &DoorbellLogic::handleAdmin, "ADMIN", _lastAdminId);
|
pollTopic(ADMIN_URL, &DoorbellLogic::handleAdmin, "ADMIN", _lastAdminId);
|
||||||
|
Serial.printf("[POLL] Done. Heap: %dKB\n", ESP.getFreeHeap() / 1024); // ← MISSING
|
||||||
}
|
}
|
||||||
|
|
||||||
void DoorbellLogic::pollTopic(const char* url,
|
void DoorbellLogic::pollTopic(const char* url,
|
||||||
void (DoorbellLogic::*handler)(const String&),
|
void (DoorbellLogic::*handler)(const String&),
|
||||||
const char* name, String& lastId) {
|
const char* name, String& lastId) {
|
||||||
|
if (!WiFi.isConnected()) { // ← MISSING
|
||||||
|
Serial.printf("[%s] Skipped — WiFi down\n", name);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
WiFiClientSecure client;
|
WiFiClientSecure client;
|
||||||
client.setInsecure();
|
client.setInsecure();
|
||||||
|
client.setTimeout(10); // ← MISSING
|
||||||
|
|
||||||
HTTPClient http;
|
HTTPClient http;
|
||||||
http.setFollowRedirects(HTTPC_STRICT_FOLLOW_REDIRECTS);
|
http.setFollowRedirects(HTTPC_STRICT_FOLLOW_REDIRECTS);
|
||||||
http.setTimeout(10000);
|
http.setTimeout(10000);
|
||||||
|
http.setReuse(false); // ← MISSING
|
||||||
|
|
||||||
if (!http.begin(client, url)) {
|
// ... rest of method ...
|
||||||
Serial.printf("[%s] begin() failed\n", name);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
int code = http.GET();
|
|
||||||
if (code == HTTP_CODE_OK) {
|
|
||||||
String response = http.getString();
|
|
||||||
if (response.length() > 0) {
|
|
||||||
parseMessages(response, name, handler, lastId);
|
|
||||||
}
|
|
||||||
} else if (code < 0) {
|
|
||||||
Serial.printf("[%s] HTTP error: %s\n", name, http.errorToString(code).c_str());
|
|
||||||
}
|
|
||||||
http.end();
|
http.end();
|
||||||
|
client.stop(); // ← MISSING
|
||||||
|
|
||||||
|
yield(); // ← MISSING
|
||||||
}
|
}
|
||||||
|
|
||||||
void DoorbellLogic::parseMessages(String& response, const char* name,
|
void DoorbellLogic::parseMessages(String& response, const char* name,
|
||||||
void (DoorbellLogic::*handler)(const String&),
|
void (DoorbellLogic::*handler)(const String&),
|
||||||
String& lastId) {
|
String& lastId) {
|
||||||
if (_inBootGrace || !_ntpSynced || _lastEpoch == 0) return;
|
Serial.printf("[%s] parseMessages: grace=%d ntp=%d epoch=%ld\n",
|
||||||
|
name, _inBootGrace, _ntpSynced, (long)_lastEpoch);
|
||||||
|
|
||||||
|
if (_inBootGrace || !_ntpSynced || _lastEpoch == 0) {
|
||||||
|
Serial.printf("[%s] SKIPPED — guard failed\n", name);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
int lineStart = 0;
|
int lineStart = 0;
|
||||||
|
int msgCount = 0;
|
||||||
while (lineStart < (int)response.length()) {
|
while (lineStart < (int)response.length()) {
|
||||||
int lineEnd = response.indexOf('\n', lineStart);
|
int lineEnd = response.indexOf('\n', lineStart);
|
||||||
if (lineEnd == -1) lineEnd = response.length();
|
if (lineEnd == -1) lineEnd = response.length();
|
||||||
@@ -377,43 +418,63 @@ void DoorbellLogic::parseMessages(String& response, const char* name,
|
|||||||
line.trim();
|
line.trim();
|
||||||
|
|
||||||
if (line.length() > 0 && line.indexOf('{') >= 0) {
|
if (line.length() > 0 && line.indexOf('{') >= 0) {
|
||||||
|
msgCount++;
|
||||||
JsonDocument doc;
|
JsonDocument doc;
|
||||||
if (!deserializeJson(doc, line)) {
|
if (deserializeJson(doc, line)) {
|
||||||
const char* event = doc["event"];
|
Serial.printf("[%s] JSON parse FAILED on line %d\n", name, msgCount);
|
||||||
const char* msgId = doc["id"];
|
lineStart = lineEnd + 1;
|
||||||
const char* message = doc["message"];
|
continue;
|
||||||
time_t msgTime = doc["time"] | 0;
|
|
||||||
|
|
||||||
if (event && strcmp(event, "message") != 0) {
|
|
||||||
lineStart = lineEnd + 1;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!message || strlen(message) == 0) {
|
|
||||||
lineStart = lineEnd + 1;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
String idStr = msgId ? String(msgId) : "";
|
|
||||||
if (idStr.length() > 0 && idStr == lastId) {
|
|
||||||
lineStart = lineEnd + 1;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (msgTime > 0 && (_lastEpoch - msgTime) > (time_t)STALE_MSG_THRESHOLD_S) {
|
|
||||||
Serial.printf("[%s] Stale: %.30s (age %llds)\n",
|
|
||||||
name, message, (long long)(_lastEpoch - msgTime));
|
|
||||||
lineStart = lineEnd + 1;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
Serial.printf("[%s] %.50s\n", name, message);
|
|
||||||
if (idStr.length() > 0) lastId = idStr;
|
|
||||||
(this->*handler)(String(message));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const char* event = doc["event"];
|
||||||
|
const char* msgId = doc["id"];
|
||||||
|
const char* message = doc["message"];
|
||||||
|
time_t msgTime = doc["time"] | 0;
|
||||||
|
|
||||||
|
Serial.printf("[%s] msg#%d: event=%s id=%s time=%ld msg=%.30s\n",
|
||||||
|
name, msgCount,
|
||||||
|
event ? event : "null",
|
||||||
|
msgId ? msgId : "null",
|
||||||
|
(long)msgTime,
|
||||||
|
message ? message : "null");
|
||||||
|
|
||||||
|
if (event && strcmp(event, "message") != 0) {
|
||||||
|
Serial.printf("[%s] SKIP — not a message event (event=%s)\n", name, event);
|
||||||
|
lineStart = lineEnd + 1;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!message || strlen(message) == 0) {
|
||||||
|
Serial.printf("[%s] SKIP — empty message\n", name);
|
||||||
|
lineStart = lineEnd + 1;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
String idStr = msgId ? String(msgId) : "";
|
||||||
|
if (idStr.length() > 0 && idStr == lastId) {
|
||||||
|
Serial.printf("[%s] SKIP — dedup (id=%s)\n", name, msgId);
|
||||||
|
lineStart = lineEnd + 1;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (msgTime > 0 && (_lastEpoch - msgTime) > (time_t)STALE_MSG_THRESHOLD_S) {
|
||||||
|
Serial.printf("[%s] SKIP — stale (age=%llds, threshold=%ds)\n",
|
||||||
|
name, (long long)(_lastEpoch - msgTime), STALE_MSG_THRESHOLD_S);
|
||||||
|
lineStart = lineEnd + 1;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
Serial.printf("[%s] ACCEPTED: %.50s\n", name, message);
|
||||||
|
if (idStr.length() > 0) lastId = idStr;
|
||||||
|
|
||||||
|
_lastParsedMsgEpoch = msgTime;
|
||||||
|
(this->*handler)(String(message));
|
||||||
|
_lastParsedMsgEpoch = 0;
|
||||||
}
|
}
|
||||||
lineStart = lineEnd + 1;
|
lineStart = lineEnd + 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Serial.printf("[%s] Parsed %d JSON objects\n", name, msgCount);
|
||||||
}
|
}
|
||||||
|
|
||||||
// =====================================================================
|
// =====================================================================
|
||||||
@@ -451,3 +512,25 @@ void DoorbellLogic::flushStatus() {
|
|||||||
Serial.printf("[STATUS] Sent (%d): %s\n", code, _pendStatusState.c_str());
|
Serial.printf("[STATUS] Sent (%d): %s\n", code, _pendStatusState.c_str());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
DoorbellLogic* DoorbellLogic::_instance = nullptr;
|
||||||
|
|
||||||
|
void DoorbellLogic::onWiFiEvent(WiFiEvent_t event) {
|
||||||
|
if (!_instance) return;
|
||||||
|
|
||||||
|
switch (event) {
|
||||||
|
case ARDUINO_EVENT_WIFI_STA_DISCONNECTED:
|
||||||
|
Serial.println("[WIFI] Disconnected — will reconnect");
|
||||||
|
WiFi.reconnect();
|
||||||
|
break;
|
||||||
|
case ARDUINO_EVENT_WIFI_STA_CONNECTED:
|
||||||
|
Serial.println("[WIFI] Reconnected to AP");
|
||||||
|
break;
|
||||||
|
case ARDUINO_EVENT_WIFI_STA_GOT_IP:
|
||||||
|
Serial.printf("[WIFI] Got IP: %s\n", WiFi.localIP().toString().c_str());
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -49,6 +49,9 @@ private:
|
|||||||
unsigned long _lastHeartbeat = 0;
|
unsigned long _lastHeartbeat = 0;
|
||||||
bool _blinkState = false;
|
bool _blinkState = false;
|
||||||
|
|
||||||
|
time_t _lastParsedMsgEpoch = 0; // <-- ADD
|
||||||
|
time_t _alertStartEpoch = 0; // <-- ADD
|
||||||
|
|
||||||
// Deferred status publish
|
// Deferred status publish
|
||||||
bool _pendingStatus = false;
|
bool _pendingStatus = false;
|
||||||
String _pendStatusState;
|
String _pendStatusState;
|
||||||
@@ -83,5 +86,8 @@ private:
|
|||||||
void flushStatus();
|
void flushStatus();
|
||||||
|
|
||||||
void updateScreenState();
|
void updateScreenState();
|
||||||
|
static DoorbellLogic* _instance; // for static event callback
|
||||||
|
static void onWiFiEvent(WiFiEvent_t event);
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user