fix(FR-006): MQTT Session-Queue (128 Slots) statt volatile Einzelslot; Dequeue erst nach erfolgreichem Publish
This commit is contained in:
parent
abde7fb154
commit
96508910b0
|
|
@ -19,15 +19,6 @@ Status: `[ ]` = offen · `[x]` = erledigt
|
|||
|
||||
## Offen
|
||||
|
||||
- [x] **FR-008** Bug: Reset-Befehle setzen Maschinenlaufzeit (NVS) ungewollt zurück ✅
|
||||
- **Bug A – MQTT `{"reset":true}`** ruft `resetTotal()` auf → `settings.saveTotalMinutes(0.0f)` → Maschinenlaufzeit (Gesamtbetriebszeit) wird **unwiderruflich aus dem NVS gelöscht**
|
||||
- Gewünscht: `{"reset":true}` soll nur die Session-Summe (Tages-/Wochenzähler) zurücksetzen, nicht die Gesamtbetriebszeit
|
||||
- `resetTotal()` sollte gar nicht per MQTT erreichbar sein (oder einen separaten, expliziten Befehl erfordern, z.B. `{"reset_total":true}`)
|
||||
- **Bug B – Web-Button "Summe Laserzeit zurücksetzen"** ruft `resetSessionSum()` auf → NVS wird nicht angefasst, aber `_sessionNetSec = 0` → `getTotalMinutes()` sinkt sofort in der Anzeige (weil `_sessionNetSec`-Anteil verschwindet), obwohl der NVS-Wert korrekt bleibt. Täuscht einen Gesamt-Reset vor.
|
||||
- Fix: `_totalMinutesBase` beim `resetSessionSum()` um den bereits akkumulierten `_sessionNetSec`-Anteil erhöhen, bevor `_sessionNetSec` auf 0 gesetzt wird → Kontinuität der Gesamtzeit sicherstellen
|
||||
- Betroffene Dateien: `src/laser_tracker.cpp` (`resetSessionSum`, `resetTotal`), `src/mqtt_client.cpp` (MQTT-Reset-Handler)
|
||||
- Commit: `c636add`
|
||||
|
||||
- [ ] **FR-007** Feature: Laufende Session-ID in MQTT Session-Payload
|
||||
- Jede Session erhält eine aufsteigende Ganzzahl (`session_id`), die im MQTT-Payload `MQTT_TOPIC_SESSION` mitgesendet wird
|
||||
- Empfänger (z. B. Home Assistant, Node-RED) kann fehlende Sessions sofort erkennen: Lücke zwischen `session_id` 47 und 49 → Session 48 ging verloren
|
||||
|
|
@ -37,7 +28,7 @@ Status: `[ ]` = offen · `[x]` = erledigt
|
|||
- Betroffene Dateien: `include/mqtt_client.h` (Counter-Member), `src/mqtt_client.cpp` (`_doPublishSession`: `doc["session_id"] = _sessionCounter++`)
|
||||
- Commit: –
|
||||
|
||||
- [ ] **FR-006** Bug: MQTT Session-Publish nicht zuverlässig bei Verbindungsausfall
|
||||
- [x] **FR-006** Bug: MQTT Session-Publish nicht zuverlässig bei Verbindungsausfall ✅
|
||||
- **Bug A – Race Condition** (`mqtt_client.cpp`, `_doPublishSession`):
|
||||
`_pendingSession = false` wird gesetzt **bevor** `_client->publish()` ausgeführt wird.
|
||||
Scheitert der Publish (z. B. MQTT trennt in diesem Moment), ist das Flag bereits gelöscht → Session-Daten gehen still verloren.
|
||||
|
|
@ -45,15 +36,23 @@ Status: `[ ]` = offen · `[x]` = erledigt
|
|||
- **Bug B – Nur 1 Slot** (`mqtt_client.h`, `volatile`-Felder):
|
||||
`_pendingSessionSec` / `_pendingGratisSec` / `_pendingSession` speichern genau eine Session.
|
||||
Enden zwei Sessions während einer Offline-Phase, überschreibt der zweite `publishSession()`-Aufruf die Daten des ersten → erste Session unwiederbringlich verloren.
|
||||
- Fix: `QueueHandle_t` mit **128 Slots** (à `sizeof(SessionPayload)` = 8 Bytes = ~1 KB RAM); `xQueuePeek` + Dequeue erst nach erfolgreichem `_client->publish()` → kein Datenverlust bei Verbindungsabbruch
|
||||
- Betroffene Dateien: `src/mqtt_client.cpp`, `include/mqtt_client.h`
|
||||
- Lösungsansatz: `QueueHandle_t` mit **mindestens 128 Slots** statt volatile Einzelslot; Publish erst nach erfolgreichem `_client->publish()` aus der Queue entfernen
|
||||
Begründung: Theoretisch 1 Session/Minute × 2h Offline = **120 Sessions** im Worst Case → 10 Slots wären zu knapp; 128 Slots à ~16 Bytes = ~2 KB RAM (vertretbar)
|
||||
- Commit: –
|
||||
|
||||
---
|
||||
|
||||
## Erledigt
|
||||
|
||||
- [x] **FR-008** Bug: Reset-Befehle setzen Maschinenlaufzeit (NVS) ungewollt zurück ✅
|
||||
- **Bug A – MQTT `{"reset":true}`** ruft `resetTotal()` auf → `settings.saveTotalMinutes(0.0f)` → Maschinenlaufzeit (Gesamtbetriebszeit) wird **unwiderruflich aus dem NVS gelöscht**
|
||||
- Gewünscht: `{"reset":true}` soll nur die Session-Summe (Tages-/Wochenzähler) zurücksetzen, nicht die Gesamtbetriebszeit
|
||||
- `resetTotal()` sollte gar nicht per MQTT erreichbar sein (oder einen separaten, expliziten Befehl erfordern, z.B. `{"reset_total":true}`)
|
||||
- **Bug B – Web-Button "Summe Laserzeit zurücksetzen"** ruft `resetSessionSum()` auf → NVS wird nicht angefasst, aber `_sessionNetSec = 0` → `getTotalMinutes()` sinkt sofort in der Anzeige (weil `_sessionNetSec`-Anteil verschwindet), obwohl der NVS-Wert korrekt bleibt. Täuscht einen Gesamt-Reset vor.
|
||||
- Fix: `_totalMinutesBase` beim `resetSessionSum()` um den bereits akkumulierten `_sessionNetSec`-Anteil erhöhen, bevor `_sessionNetSec` auf 0 gesetzt wird → Kontinuität der Gesamtzeit sicherstellen
|
||||
- Betroffene Dateien: `src/laser_tracker.cpp` (`resetSessionSum`, `resetTotal`), `src/mqtt_client.cpp` (MQTT-Reset-Handler)
|
||||
- Commit: `c636add`
|
||||
|
||||
- [x] **FR-005** Bug: WDT-Crash + Display-/Browser-Freeze durch blockierenden TLS-Handshake ✅
|
||||
- MQTT `reconnect()` mit TLS (Port 8883) blockiert `loop()` auf Core 1 bis zu 15 s
|
||||
- Folge: Task-Watchdog (30 s) feuert wenn zwei Reconnect-Versuche in einem 30s-Fenster → Neustart mit `WATCHDOG (Task)`
|
||||
|
|
|
|||
|
|
@ -27,6 +27,13 @@
|
|||
#include "settings.h"
|
||||
#include <freertos/FreeRTOS.h>
|
||||
#include <freertos/task.h>
|
||||
#include <freertos/queue.h>
|
||||
|
||||
// Session-Daten die per Queue von Core 1 -> Core 0 übergeben werden
|
||||
struct SessionPayload {
|
||||
int sessionSec;
|
||||
int gratisSec;
|
||||
};
|
||||
|
||||
class MqttClient {
|
||||
public:
|
||||
|
|
@ -67,10 +74,9 @@ private:
|
|||
// FreeRTOS-Task: laeuft auf Core 0, erledigt Reconnect/Heartbeat/Publish
|
||||
TaskHandle_t _taskHandle;
|
||||
|
||||
// Pending-Session-Daten: von Core 1 (main) gesetzt, von Core 0 (Task) gelesen
|
||||
volatile bool _pendingSession;
|
||||
volatile int _pendingSessionSec;
|
||||
volatile int _pendingGratisSec;
|
||||
// Session-Queue: Core 1 schreibt per publishSession(), Core 0 liest und publiziert.
|
||||
// 128 Slots = Worst Case ~2h Offline bei 1 Session/min. ~2 KB RAM.
|
||||
QueueHandle_t _sessionQueue;
|
||||
|
||||
// Verbindungsaufbau (blockierend - NUR aus MQTT-Task auf Core 0 aufrufen!)
|
||||
bool reconnect();
|
||||
|
|
@ -79,7 +85,8 @@ private:
|
|||
void publishHeartbeat();
|
||||
|
||||
// Eigentlicher Session-Publish (aus MQTT-Task auf Core 0)
|
||||
void _doPublishSession(int lastSessionSec, int gratisSec);
|
||||
// Gibt true zurück wenn Publish erfolgreich, false bei Fehler (Queue-Eintrag bleibt dann erhalten)
|
||||
bool _doPublishSession(int lastSessionSec, int gratisSec);
|
||||
|
||||
// FreeRTOS-Task
|
||||
static void _taskFn(void* param);
|
||||
|
|
|
|||
|
|
@ -22,11 +22,10 @@ MqttClient::MqttClient()
|
|||
, _lastReconnectMs(0)
|
||||
, _lastHeartbeatMs(0)
|
||||
, _taskHandle(nullptr)
|
||||
, _pendingSession(false)
|
||||
, _pendingSessionSec(0)
|
||||
, _pendingGratisSec(0)
|
||||
, _sessionQueue(nullptr)
|
||||
{
|
||||
_clientId[0] = '\0';
|
||||
_sessionQueue = xQueueCreate(128, sizeof(SessionPayload));
|
||||
}
|
||||
|
||||
// --------------------------------------------------------------------------
|
||||
|
|
@ -72,19 +71,22 @@ void MqttClient::loop() {
|
|||
// Daten werden via volatile Flag an den MQTT-Task auf Core 0 uebergeben.
|
||||
// --------------------------------------------------------------------------
|
||||
void MqttClient::publishSession(int lastSessionSec, int gratisSec) {
|
||||
_pendingSessionSec = lastSessionSec;
|
||||
_pendingGratisSec = gratisSec;
|
||||
_pendingSession = true; // Task auf Core 0 fuehrt den Publish aus
|
||||
LOG_I("MQTT", "publishSession vorgemerkt: %d s (gratis %d s)", lastSessionSec, gratisSec);
|
||||
SessionPayload p = { lastSessionSec, gratisSec };
|
||||
if (xQueueSend(_sessionQueue, &p, 0) != pdTRUE) {
|
||||
LOG_E("MQTT", "publishSession: Queue voll! Session %d s verworfen", lastSessionSec);
|
||||
} else {
|
||||
LOG_I("MQTT", "publishSession eingereihrt: %d s (gratis %d s), Slots frei: %u",
|
||||
lastSessionSec, gratisSec, (unsigned)uxQueueSpacesAvailable(_sessionQueue));
|
||||
}
|
||||
}
|
||||
|
||||
// --------------------------------------------------------------------------
|
||||
// _doPublishSession() - eigentlicher Publish, wird aus MQTT-Task aufgerufen
|
||||
// --------------------------------------------------------------------------
|
||||
void MqttClient::_doPublishSession(int lastSessionSec, int gratisSec) {
|
||||
bool MqttClient::_doPublishSession(int lastSessionSec, int gratisSec) {
|
||||
if (!_client->connected()) {
|
||||
LOG_E("MQTT", "_doPublishSession: nicht verbunden, uebersprungen");
|
||||
return;
|
||||
LOG_E("MQTT", "_doPublishSession: nicht verbunden, Session bleibt in Queue");
|
||||
return false;
|
||||
}
|
||||
|
||||
// session_minutes: Decken-Rundung (62 s = 2 min)
|
||||
|
|
@ -110,6 +112,7 @@ void MqttClient::_doPublishSession(int lastSessionSec, int gratisSec) {
|
|||
|
||||
bool ok = _client->publish(MQTT_TOPIC_SESSION, buf, /*retained=*/false);
|
||||
LOG_I("MQTT", "publishSession: %s -> %s", buf, ok ? "OK" : "FEHLER");
|
||||
return ok;
|
||||
}
|
||||
|
||||
// --------------------------------------------------------------------------
|
||||
|
|
@ -224,10 +227,15 @@ void MqttClient::_taskLoop() {
|
|||
// PubSubClient-interne Verarbeitung (eingehende Nachrichten)
|
||||
_client->loop();
|
||||
|
||||
// Pending Session-Publish (von Core 1 via volatile Flag gesetzt)
|
||||
if (_pendingSession) {
|
||||
_pendingSession = false;
|
||||
_doPublishSession(_pendingSessionSec, _pendingGratisSec);
|
||||
// Pending Sessions aus Queue publizieren (FIFO)
|
||||
// Peek + Publish + nur bei Erfolg Dequeue → kein Datenverlust bei Verbindungsabbruch
|
||||
SessionPayload pending;
|
||||
while (xQueuePeek(_sessionQueue, &pending, 0) == pdTRUE) {
|
||||
if (_doPublishSession(pending.sessionSec, pending.gratisSec)) {
|
||||
xQueueReceive(_sessionQueue, &pending, 0); // erfolgreich: aus Queue entfernen
|
||||
} else {
|
||||
break; // nicht verbunden: beim nächsten Loop-Durchlauf erneut versuchen
|
||||
}
|
||||
}
|
||||
|
||||
// Heartbeat
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user