14b3967590
- 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>
250 lines
8.0 KiB
C++
250 lines
8.0 KiB
C++
#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
|