- MQTT_TOPIC_AVAILABILITY + MQTT_DISCOVERY_PREFIX in config.h
- LWT auf lasercutter/availability (online/offline, retained)
- publishDiscovery(): 8 retained Entities nach jedem (Re-)Connect
binary_sensor: laser_active (device_class: running)
sensor: session_sum (min), session_sec (s)
sensor: total_time (hh:mm via Jinja2 value_template)
sensor: firmware (diagnostic)
switch: display (state aus status.display_on, cmd -> lasercutter/cmd)
button: reset_session, reboot (entity_category: config)
- Heartbeat: neue Felder laser_active, session_minutes_sum,
session_seconds, total_minutes fuer HA value_templates
- PubSubClient Buffer: 512 -> 1024 (Discovery-Nachrichten ~640 Bytes)
124 lines
4.5 KiB
C++
124 lines
4.5 KiB
C++
#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();
|
||
|
||
// 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; |