MQTT-Display-LaserCutter/include/mqtt_client.h
MaPaLo76 fc5e1694fd feat(mqtt,web): FR-015 + FR-016 sofortiger MQTT-Publish + WebSocket Live-Update (v1.6.0)
FR-015:
- publishHeartbeat() nach Display-Toggle, Session-Reset und Reboot-CMD
- publishHeartbeat() von private nach public (mqtt_client.h)
- Heartbeat-Intervall 60s -> 10s (config.h)
- HA Discovery Switch: state_on/state_off ergaenzt (mqtt_client.cpp)

FR-016:
- Neuer WebSocket-Endpunkt /status-ws (AsyncWebSocket)
- sendStatusWs() am Ende von publishHeartbeat() -> alle Ausloeser abgedeckt
- Statusseite: DOM-Updates via WebSocket, kein location.reload() mehr
- Config-Seite: Reboot-Button live deaktiviert wenn Laser aktiv
- Alle Action-Buttons auf fetch() umgestellt (Reboot, WLAN-Reset, Laufzeit-Reset)
- Display-Button: blau+gelb wenn an, grau+grau wenn aus
2026-03-07 16:17:39 +01:00

125 lines
4.6 KiB
C++
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#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 <Arduino.h>
#include <PubSubClient.h>
#include <WiFi.h>
#include <WiFiClientSecure.h>
#include "config.h"
#include "settings.h"
#include <freertos/FreeRTOS.h>
#include <freertos/task.h>
#include <freertos/queue.h>
#include <esp_system.h>
// 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();
// Home Assistant MQTT Discovery (retained Config-Nachrichten für alle Entities)
void publishDiscovery();
// Status-Heartbeat sofort senden (normalerweise alle 10s automatisch)
// Kann von außen aufgerufen werden um HA sofort zu aktualisieren (FR-015)
void publishHeartbeat();
// 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;
// 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;