Ajout publication MQTT (PubSubClient)
- Topics individuels par capteur sous un topic de base configurable (défaut: solar/) PV, batterie (tension/SOC/temp/statut), load, énergie, soleil, RS485, relais, entrées DI - Abonnement relay/1/set et relay/2/set pour piloter les relais depuis MQTT - Config NVS : serveur, port, user/pass optionnel, topic base, intervalle (défaut 30s) - Reconnexion automatique toutes les 15s si broker inaccessible - Publication immédiate après connexion et après changement de config - Route GET/POST /api/mqtt + UI onglet Config avec liste des topics générée dynamiquement - Stubs QEMU (#ifndef QEMU_BUILD) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,249 @@
|
||||
#include "mqtt_client.h"
|
||||
|
||||
#ifndef QEMU_BUILD
|
||||
|
||||
#include <WiFi.h>
|
||||
#include <PubSubClient.h>
|
||||
#include <Preferences.h>
|
||||
#include <ArduinoJson.h>
|
||||
#include "state.h"
|
||||
#include "config.h"
|
||||
|
||||
// --- Config ---
|
||||
static bool mqttActif = false;
|
||||
static String mqttServer = "192.168.1.36";
|
||||
static uint16_t mqttPort = 1883;
|
||||
static String mqttUser;
|
||||
static String mqttPass;
|
||||
static String mqttBase = "solar"; // topic de base
|
||||
static uint32_t mqttInterval = 30000; // ms entre publications
|
||||
|
||||
// --- État runtime ---
|
||||
static WiFiClient wifiClient;
|
||||
static PubSubClient mqttClient(wifiClient);
|
||||
static bool mqttConnecte = false;
|
||||
static unsigned long tDernierePubli = 0;
|
||||
static unsigned long tDerniereConnex = 0;
|
||||
static const unsigned long RECONNECT_INTERVAL = 15000UL;
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// NVS
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
static void chargerNVS() {
|
||||
Preferences p;
|
||||
p.begin("mqtt", true);
|
||||
mqttActif = p.getBool("enabled", false);
|
||||
mqttServer = p.getString("server", "192.168.1.36");
|
||||
mqttPort = p.getUShort("port", 1883);
|
||||
mqttUser = p.getString("user", "");
|
||||
mqttPass = p.getString("pass", "");
|
||||
mqttBase = p.getString("base", "solar");
|
||||
mqttInterval = p.getULong("interval", 30000);
|
||||
p.end();
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Topics dérivés du topic de base
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
static String t(const char* suffixe) { return mqttBase + "/" + suffixe; }
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Callback réception (commande relais / entrées)
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
static void mqttCallback(char* topic, byte* payload, unsigned int len) {
|
||||
String msg;
|
||||
for (unsigned int i = 0; i < len; i++) msg += (char)payload[i];
|
||||
msg.trim();
|
||||
|
||||
bool etat = (msg == "ON" || msg == "1" || msg == "true");
|
||||
|
||||
String top = String(topic);
|
||||
if (top == t("relay/1/set")) {
|
||||
state.relay1 = etat;
|
||||
digitalWrite(PIN_RELAY1, etat ? HIGH : LOW);
|
||||
Serial.printf("[MQTT] Relais 1 → %s\n", etat ? "ON" : "OFF");
|
||||
} else if (top == t("relay/2/set")) {
|
||||
state.relay2 = etat;
|
||||
digitalWrite(PIN_RELAY2, etat ? HIGH : LOW);
|
||||
Serial.printf("[MQTT] Relais 2 → %s\n", etat ? "ON" : "OFF");
|
||||
}
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Connexion / reconnexion
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
static void connecterMqtt() {
|
||||
if (WiFi.status() != WL_CONNECTED) return;
|
||||
|
||||
mqttClient.setServer(mqttServer.c_str(), mqttPort);
|
||||
mqttClient.setCallback(mqttCallback);
|
||||
mqttClient.setBufferSize(512);
|
||||
|
||||
String clientId = "kc868-" + WiFi.macAddress();
|
||||
clientId.replace(":", "");
|
||||
|
||||
bool ok;
|
||||
if (mqttUser.length() > 0) {
|
||||
ok = mqttClient.connect(clientId.c_str(), mqttUser.c_str(), mqttPass.c_str());
|
||||
} else {
|
||||
ok = mqttClient.connect(clientId.c_str());
|
||||
}
|
||||
|
||||
if (ok) {
|
||||
mqttConnecte = true;
|
||||
Serial.printf("[MQTT] Connecté — %s:%u base: %s intervalle: %us\n",
|
||||
mqttServer.c_str(), mqttPort,
|
||||
mqttBase.c_str(), mqttInterval / 1000);
|
||||
// Abonnements commandes
|
||||
mqttClient.subscribe(t("relay/1/set").c_str());
|
||||
mqttClient.subscribe(t("relay/2/set").c_str());
|
||||
// Publication immédiate après connexion
|
||||
mqttPublierEtat();
|
||||
} else {
|
||||
mqttConnecte = false;
|
||||
Serial.printf("[MQTT] Échec connexion (rc=%d) — retry dans %lus\n",
|
||||
mqttClient.state(), RECONNECT_INTERVAL / 1000);
|
||||
}
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Publication état complet
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
static void pub(const char* suffixe, float val, int decimales = 2) {
|
||||
char buf[16];
|
||||
dtostrf(val, 1, decimales, buf);
|
||||
mqttClient.publish(t(suffixe).c_str(), buf, true);
|
||||
}
|
||||
|
||||
static void pub(const char* suffixe, bool val) {
|
||||
mqttClient.publish(t(suffixe).c_str(), val ? "ON" : "OFF", true);
|
||||
}
|
||||
|
||||
static void pub(const char* suffixe, int val) {
|
||||
mqttClient.publish(t(suffixe).c_str(), String(val).c_str(), true);
|
||||
}
|
||||
|
||||
void mqttPublierEtat() {
|
||||
if (!mqttConnecte || !mqttClient.connected()) return;
|
||||
|
||||
// Panneau solaire
|
||||
pub("pv/voltage", state.pv);
|
||||
pub("pv/current", state.pvCurrent);
|
||||
|
||||
// Batterie
|
||||
pub("battery/voltage", state.battery);
|
||||
pub("battery/soc", (int)state.batSOC);
|
||||
pub("battery/temperature", state.batTemperature, 1);
|
||||
pub("battery/status", (int)state.batStatut);
|
||||
|
||||
// Sortie de charge
|
||||
pub("load/voltage", state.loadVoltage);
|
||||
pub("load/current", state.loadCurrent);
|
||||
pub("load/power", state.loadPower, 1);
|
||||
|
||||
// Énergie
|
||||
pub("energy/generated/today", state.energieGenJour);
|
||||
pub("energy/generated/total", state.energieGenTotal);
|
||||
pub("energy/consumed/today", state.energieConJour);
|
||||
pub("energy/consumed/total", state.energieConTotal);
|
||||
|
||||
// État général
|
||||
pub("sun", state.sun);
|
||||
pub("rs485/ok", state.rs485_ok);
|
||||
|
||||
// Relais
|
||||
pub("relay/1", state.relay1);
|
||||
pub("relay/2", state.relay2);
|
||||
|
||||
// Entrées numériques
|
||||
pub("input/1", state.di1);
|
||||
pub("input/2", state.di2);
|
||||
|
||||
tDernierePubli = millis();
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// API publique
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
void initMqtt() {
|
||||
chargerNVS();
|
||||
Serial.printf("[MQTT] %s — %s:%u base: %s intervalle: %us\n",
|
||||
mqttActif ? "Activé" : "Désactivé",
|
||||
mqttServer.c_str(), mqttPort,
|
||||
mqttBase.c_str(), mqttInterval / 1000);
|
||||
}
|
||||
|
||||
void gererMqtt() {
|
||||
if (!mqttActif) return;
|
||||
if (WiFi.status() != WL_CONNECTED) {
|
||||
if (mqttConnecte) { mqttConnecte = false; }
|
||||
return;
|
||||
}
|
||||
|
||||
if (!mqttClient.connected()) {
|
||||
mqttConnecte = false;
|
||||
unsigned long maintenant = millis();
|
||||
if ((maintenant - tDerniereConnex) >= RECONNECT_INTERVAL) {
|
||||
tDerniereConnex = maintenant;
|
||||
connecterMqtt();
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
mqttClient.loop();
|
||||
|
||||
// Publication périodique
|
||||
if ((millis() - tDernierePubli) >= mqttInterval) {
|
||||
mqttPublierEtat();
|
||||
}
|
||||
}
|
||||
|
||||
bool setMqttConfig(bool enabled, const char* server, uint16_t port,
|
||||
const char* user, const char* pass,
|
||||
const char* topicBase, uint32_t intervalMs) {
|
||||
Preferences p;
|
||||
p.begin("mqtt", false);
|
||||
p.putBool("enabled", enabled);
|
||||
p.putString("server", server);
|
||||
p.putUShort("port", port);
|
||||
p.putString("user", user);
|
||||
p.putString("pass", pass);
|
||||
p.putString("base", topicBase);
|
||||
p.putULong("interval", intervalMs);
|
||||
p.end();
|
||||
|
||||
if (mqttConnecte) { mqttClient.disconnect(); mqttConnecte = false; }
|
||||
tDerniereConnex = 0;
|
||||
chargerNVS();
|
||||
Serial.printf("[MQTT] Config mise à jour — %s\n", mqttActif ? "activé" : "désactivé");
|
||||
return true;
|
||||
}
|
||||
|
||||
void getMqttJson(String &out) {
|
||||
JsonDocument doc;
|
||||
doc["enabled"] = mqttActif;
|
||||
doc["connected"] = mqttConnecte;
|
||||
doc["server"] = mqttServer;
|
||||
doc["port"] = mqttPort;
|
||||
doc["user"] = mqttUser;
|
||||
doc["pass"] = mqttPass;
|
||||
doc["base"] = mqttBase;
|
||||
doc["interval"] = mqttInterval / 1000; // en secondes pour l'UI
|
||||
serializeJson(doc, out);
|
||||
}
|
||||
|
||||
#else
|
||||
// --- Stubs QEMU ---
|
||||
void initMqtt() { Serial.println("[MQTT] Désactivé (build QEMU)"); }
|
||||
void gererMqtt() {}
|
||||
void mqttPublierEtat() {}
|
||||
void getMqttJson(String &out) { out = "{\"enabled\":false,\"connected\":false}"; }
|
||||
bool setMqttConfig(bool, const char*, uint16_t, const char*, const char*,
|
||||
const char*, uint32_t) { return false; }
|
||||
#endif
|
||||
Reference in New Issue
Block a user