#pragma once // ============================================================================= // mqtt_client.h - MQTT-Verbindung, Publish und Subscribe // Projekt: MQTT-Display LaserCutter // // Wrapper um PubSubClient mit Non-Blocking Reconnect. // // Publish: // publishSession() - beim Ende eines Laser-Bursts (aus LaserTracker) // publishStatus() - Heartbeat alle MQTT_HEARTBEAT_MS // // Subscribe: // lasercutter/reset - Payload "1" oder {"reset":true} -> laserTracker.resetTotal() // Payload {"reset_session":true} -> laserTracker.resetSessionSum() // // Verwendung: // mqttClient.begin(); // einmalig in setup(), nach WiFi-Connect // mqttClient.loop(); // in jedem loop()-Aufruf // ============================================================================= #include #include #include #include #include "config.h" #include "settings.h" #include #include #include #include // Session-Daten die per Queue von Core 1 -> Core 0 übergeben werden struct SessionPayload { int sessionSec; int gratisSec; time_t startTime; // exakter Startzeitpunkt – bei Reconnect korrekt, nicht "jetzt" uint32_t sessionId; // aufsteigender Zähler, Reset bei resetSessionSum/resetTotal }; class MqttClient { public: MqttClient(); // MQTT konfigurieren und erste Verbindung versuchen (einmalig in setup()) void begin(); // No-Op: Reconnect, Heartbeat und Publish werden vom internen FreeRTOS-Task erledigt. // Diese Methode bleibt im Interface, damit main.cpp unveraendert bleibt. void loop(); // Publish: Session-Ende (wird von LaserTracker-Logik in main aufgerufen) // lastSessionSec : Netto-Sekunden der letzten Session // gratisSec : konfigurierte Gratiszeit // JSON-Felder: session_minutes (ceiling int), session_seconds (raw), freetime_s, ip void publishSession(int lastSessionSec, int gratisSec); // Verbindungsstatus bool isConnected(); // Session-Zähler zurücksetzen (bei reset / reset_session Kommando) void resetSessionCounter(); // Debug-Ausgabe auf Serial void printToSerial(); private: // WICHTIG: Diese Objekte werden als nullptr initialisiert und erst in // _taskLoop() auf Core 0 per 'new' angelegt. mbedtls/WiFiClientSecure // bindet Heap-Kontexte an den erstellenden Task/Core – daher KEIN // Anlegen im globalen Konstruktor (Core 1)! WiFiClient* _wifiClient; WiFiClientSecure* _secureClient; PubSubClient* _client; uint32_t _lastReconnectMs; uint32_t _lastHeartbeatMs; char _clientId[32]; // MQTT_CLIENT_ID + MAC-Suffix (eindeutig auf Public Broker) // FreeRTOS-Task: laeuft auf Core 0, erledigt Reconnect/Heartbeat/Publish TaskHandle_t _taskHandle; // 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; // Aufsteigender Session-Zähler; Reset bei resetSessionSum/resetTotal // Zugriff nur von Core 1 (publishSession) → kein Mutex nötig uint32_t _sessionCounter; // Neustart-Grund (einmalig in begin() gespeichert, bevor er überschrieben wird) char _resetReason[32]; // Verbindungsaufbau (blockierend - NUR aus MQTT-Task auf Core 0 aufrufen!) bool reconnect(); // TLS-Client komplett abreissen und neu aufbauen (Fix: Heap-Korruption bei // unerwartetem SSL-Verbindungsabbruch durch kaputten mbedTLS-Kontext) void _rebuildClient(); // Gecachte Broker-Einstellungen fuer _rebuildClient() char _broker[64]; uint16_t _port; // Heartbeat-Publish (lasercutter/status) void publishHeartbeat(); // Eigentlicher Session-Publish (aus MQTT-Task auf Core 0) // Gibt true zurück wenn Publish erfolgreich, false bei Fehler (Queue-Eintrag bleibt dann erhalten) bool _doPublishSession(int lastSessionSec, int gratisSec, time_t startTime, uint32_t sessionId);; // FreeRTOS-Task static void _taskFn(void* param); void _taskLoop(); // Callback fuer eingehende Nachrichten (static wegen PubSubClient-API) static void onMessage(const char* topic, byte* payload, unsigned int length); }; // Globale Instanz extern MqttClient mqttClient;