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:
+82
-1
@@ -51,7 +51,7 @@ function afficherOnglet(nom, bouton) {
|
|||||||
document.getElementById(nom).classList.add('actif');
|
document.getElementById(nom).classList.add('actif');
|
||||||
bouton.classList.add('active');
|
bouton.classList.add('active');
|
||||||
if (nom === 'regles') chargerRegles();
|
if (nom === 'regles') chargerRegles();
|
||||||
if (nom === 'config') { chargerSleep(); chargerWifi(); chargerPrefsUI(); chargerModbus(); chargerWireGuard(); }
|
if (nom === 'config') { chargerSleep(); chargerWifi(); chargerPrefsUI(); chargerModbus(); chargerMqtt(); chargerWireGuard(); }
|
||||||
if (nom === 'historique') chargerHistorique();
|
if (nom === 'historique') chargerHistorique();
|
||||||
if (nom === 'debug') chargerDebug();
|
if (nom === 'debug') chargerDebug();
|
||||||
if (nom === 'epever-config') lireConfigEpever();
|
if (nom === 'epever-config') lireConfigEpever();
|
||||||
@@ -848,6 +848,87 @@ function fermerSunPopup() {
|
|||||||
if (modal) modal.classList.add('hidden');
|
if (modal) modal.classList.add('hidden');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// --- MQTT ---
|
||||||
|
|
||||||
|
function mqttAfficherTopics(base) {
|
||||||
|
const info = document.getElementById('mqtt-topics-info');
|
||||||
|
const pub = document.getElementById('mqtt-topics-list');
|
||||||
|
const cmd = document.getElementById('mqtt-cmd-list');
|
||||||
|
if (!info || !pub || !cmd) return;
|
||||||
|
const b = base || 'solar';
|
||||||
|
const pubTopics = [
|
||||||
|
'pv/voltage', 'pv/current',
|
||||||
|
'battery/voltage', 'battery/soc', 'battery/temperature', 'battery/status',
|
||||||
|
'load/voltage', 'load/current', 'load/power',
|
||||||
|
'energy/generated/today', 'energy/generated/total',
|
||||||
|
'energy/consumed/today', 'energy/consumed/total',
|
||||||
|
'sun', 'rs485/ok', 'relay/1', 'relay/2', 'input/1', 'input/2'
|
||||||
|
];
|
||||||
|
pub.innerHTML = pubTopics.map(t => `<code>${b}/${t}</code>`).join(' ');
|
||||||
|
cmd.innerHTML = `<code>${b}/relay/1/set</code> <code>${b}/relay/2/set</code>` +
|
||||||
|
`<br><small style="color:var(--muted)">Valeurs acceptées : ON / OFF</small>`;
|
||||||
|
info.classList.remove('hidden');
|
||||||
|
}
|
||||||
|
|
||||||
|
async function chargerMqtt() {
|
||||||
|
try {
|
||||||
|
const d = await (await fetch('/api/mqtt')).json();
|
||||||
|
const bar = document.getElementById('mqtt-status-bar');
|
||||||
|
if (bar) {
|
||||||
|
if (d.enabled && d.connected) {
|
||||||
|
bar.textContent = '✓ Connecté — ' + d.server + ':' + d.port;
|
||||||
|
bar.className = 'ec-statusbar ec-ok';
|
||||||
|
} else if (d.enabled) {
|
||||||
|
bar.textContent = '⏳ Activé — en attente de connexion WiFi ou broker';
|
||||||
|
bar.className = 'ec-statusbar';
|
||||||
|
} else {
|
||||||
|
bar.textContent = 'Désactivé';
|
||||||
|
bar.className = 'ec-statusbar';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const s = id => document.getElementById(id);
|
||||||
|
s('mqtt-enabled').value = String(!!d.enabled);
|
||||||
|
if (s('mqtt-server')) s('mqtt-server').value = d.server || '192.168.1.36';
|
||||||
|
if (s('mqtt-port')) s('mqtt-port').value = d.port || 1883;
|
||||||
|
if (s('mqtt-user')) s('mqtt-user').value = d.user || '';
|
||||||
|
if (s('mqtt-pass')) s('mqtt-pass').value = d.pass || '';
|
||||||
|
if (s('mqtt-base')) s('mqtt-base').value = d.base || 'solar';
|
||||||
|
if (s('mqtt-interval')) s('mqtt-interval').value = d.interval || 30;
|
||||||
|
mqttAfficherTopics(d.base || 'solar');
|
||||||
|
} catch { /* silencieux */ }
|
||||||
|
}
|
||||||
|
|
||||||
|
async function sauvegarderMqtt() {
|
||||||
|
const g = id => (document.getElementById(id)?.value || '').trim();
|
||||||
|
const enabled = g('mqtt-enabled') === 'true';
|
||||||
|
const server = g('mqtt-server') || '192.168.1.36';
|
||||||
|
const port = parseInt(g('mqtt-port')) || 1883;
|
||||||
|
const user = g('mqtt-user');
|
||||||
|
const pass = g('mqtt-pass');
|
||||||
|
const base = g('mqtt-base') || 'solar';
|
||||||
|
const interval = parseInt(g('mqtt-interval')) || 30;
|
||||||
|
|
||||||
|
if (enabled && !server) { afficherToast('⚠ Adresse serveur requise'); return; }
|
||||||
|
|
||||||
|
try {
|
||||||
|
const res = await fetch('/api/mqtt', {
|
||||||
|
method: 'POST',
|
||||||
|
headers: { 'Content-Type': 'application/json' },
|
||||||
|
body: JSON.stringify({ enabled, server, port, user, pass, base, interval })
|
||||||
|
});
|
||||||
|
const d = await res.json();
|
||||||
|
if (d.ok) {
|
||||||
|
afficherToast(enabled ? '✓ MQTT activé — connexion en cours' : '✓ MQTT désactivé');
|
||||||
|
mqttAfficherTopics(base);
|
||||||
|
setTimeout(chargerMqtt, 3000);
|
||||||
|
} else {
|
||||||
|
afficherToast('⚠ Erreur sauvegarde MQTT');
|
||||||
|
}
|
||||||
|
} catch(e) {
|
||||||
|
afficherToast('Erreur : ' + e.message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// --- WireGuard ---
|
// --- WireGuard ---
|
||||||
|
|
||||||
async function chargerWireGuard() {
|
async function chargerWireGuard() {
|
||||||
|
|||||||
@@ -425,6 +425,57 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div class="regle-form">
|
||||||
|
<div class="form-titre">Publication MQTT</div>
|
||||||
|
<p class="aide">Envoie les données des capteurs vers un broker MQTT (Home Assistant, Mosquitto…). Les relais sont pilotables via les topics de commande.</p>
|
||||||
|
|
||||||
|
<div id="mqtt-status-bar" class="ec-statusbar">Chargement…</div>
|
||||||
|
|
||||||
|
<div class="form-ligne">
|
||||||
|
<label>Activé</label>
|
||||||
|
<select id="mqtt-enabled">
|
||||||
|
<option value="false">Non</option>
|
||||||
|
<option value="true">Oui</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
<div class="form-ligne">
|
||||||
|
<label>Serveur</label>
|
||||||
|
<input type="text" id="mqtt-server" placeholder="192.168.1.36" autocomplete="off">
|
||||||
|
</div>
|
||||||
|
<div class="form-ligne">
|
||||||
|
<label>Port</label>
|
||||||
|
<input type="number" id="mqtt-port" min="1" max="65535" value="1883">
|
||||||
|
</div>
|
||||||
|
<div class="form-ligne">
|
||||||
|
<label>Utilisateur</label>
|
||||||
|
<input type="text" id="mqtt-user" placeholder="Optionnel" autocomplete="off">
|
||||||
|
</div>
|
||||||
|
<div class="form-ligne">
|
||||||
|
<label>Mot de passe</label>
|
||||||
|
<input type="password" id="mqtt-pass" placeholder="Optionnel" autocomplete="new-password">
|
||||||
|
</div>
|
||||||
|
<div class="form-ligne">
|
||||||
|
<label>Topic de base <span class="ec-aide" title="Tous les topics seront préfixés par cette valeur. Ex: solar → solar/battery/voltage">ℹ</span></label>
|
||||||
|
<input type="text" id="mqtt-base" placeholder="solar" autocomplete="off">
|
||||||
|
</div>
|
||||||
|
<div class="form-ligne">
|
||||||
|
<label>Intervalle</label>
|
||||||
|
<div class="ec-field-unit">
|
||||||
|
<input type="number" id="mqtt-interval" min="5" max="3600" value="30">
|
||||||
|
<span class="ec-unit">s</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div id="mqtt-topics-info" class="hidden">
|
||||||
|
<div class="form-section-label" style="margin-top:0.5rem">Topics publiés</div>
|
||||||
|
<div id="mqtt-topics-list" class="aide" style="font-size:0.72rem;line-height:1.7"></div>
|
||||||
|
<div class="form-section-label" style="margin-top:0.4rem">Topics de commande</div>
|
||||||
|
<div id="mqtt-cmd-list" class="aide" style="font-size:0.72rem;line-height:1.7"></div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<button class="btn btn-primaire btn-plein" onclick="sauvegarderMqtt()">Enregistrer et appliquer</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="regle-form">
|
<div class="regle-form">
|
||||||
<div class="form-titre">VPN WireGuard</div>
|
<div class="form-titre">VPN WireGuard</div>
|
||||||
<p class="aide">Tunnel chiffré vers votre serveur WireGuard. Nécessite une connexion WiFi (mode STA). Désactivé par défaut — la configuration locale reste accessible même si le VPN est coupé.</p>
|
<p class="aide">Tunnel chiffré vers votre serveur WireGuard. Nécessite une connexion WiFi (mode STA). Désactivé par défaut — la configuration locale reste accessible même si le VPN est coupé.</p>
|
||||||
|
|||||||
@@ -0,0 +1,10 @@
|
|||||||
|
#pragma once
|
||||||
|
#include <Arduino.h>
|
||||||
|
|
||||||
|
void initMqtt();
|
||||||
|
void gererMqtt();
|
||||||
|
void getMqttJson(String &out);
|
||||||
|
bool setMqttConfig(bool enabled, const char* server, uint16_t port,
|
||||||
|
const char* user, const char* pass,
|
||||||
|
const char* topicBase, uint32_t intervalMs);
|
||||||
|
void mqttPublierEtat(); // publication immédiate (ex: après changement relais)
|
||||||
@@ -13,6 +13,7 @@ lib_deps =
|
|||||||
https://github.com/me-no-dev/ESPAsyncWebServer.git
|
https://github.com/me-no-dev/ESPAsyncWebServer.git
|
||||||
ayushsharma82/ElegantOTA @ ^3.1.0
|
ayushsharma82/ElegantOTA @ ^3.1.0
|
||||||
emelianov/modbus-esp8266 @ ^4.1.0
|
emelianov/modbus-esp8266 @ ^4.1.0
|
||||||
|
knolleary/PubSubClient @ ^2.8
|
||||||
|
|
||||||
; --- Cible physique KC868-A2 ---
|
; --- Cible physique KC868-A2 ---
|
||||||
[env:kc868_a2]
|
[env:kc868_a2]
|
||||||
@@ -21,6 +22,7 @@ build_flags = -D ELEGANTOTA_USE_ASYNC_WEBSERVER=1
|
|||||||
lib_deps =
|
lib_deps =
|
||||||
${common.lib_deps}
|
${common.lib_deps}
|
||||||
https://github.com/ciniml/WireGuard-ESP32-Arduino.git
|
https://github.com/ciniml/WireGuard-ESP32-Arduino.git
|
||||||
|
knolleary/PubSubClient @ ^2.8
|
||||||
|
|
||||||
; --- Build QEMU : WiFi/sleep désactivés, Modbus + règles actifs ---
|
; --- Build QEMU : WiFi/sleep désactivés, Modbus + règles actifs ---
|
||||||
; Compiler : pio run -e qemu
|
; Compiler : pio run -e qemu
|
||||||
|
|||||||
@@ -12,6 +12,7 @@
|
|||||||
#include "debug_log.h"
|
#include "debug_log.h"
|
||||||
#include "epever_config.h"
|
#include "epever_config.h"
|
||||||
#include "wireguard_vpn.h"
|
#include "wireguard_vpn.h"
|
||||||
|
#include "mqtt_client.h"
|
||||||
|
|
||||||
// Instance globale partagée entre tous les modules
|
// Instance globale partagée entre tous les modules
|
||||||
SystemState state;
|
SystemState state;
|
||||||
@@ -43,6 +44,7 @@ void setup() {
|
|||||||
initHistorique();
|
initHistorique();
|
||||||
initConfigEpever();
|
initConfigEpever();
|
||||||
initWireGuard();
|
initWireGuard();
|
||||||
|
initMqtt();
|
||||||
|
|
||||||
debugLogf("Système prêt.");
|
debugLogf("Système prêt.");
|
||||||
}
|
}
|
||||||
@@ -50,6 +52,7 @@ void setup() {
|
|||||||
void loop() {
|
void loop() {
|
||||||
gererWifi();
|
gererWifi();
|
||||||
gererWireGuard();
|
gererWireGuard();
|
||||||
|
gererMqtt();
|
||||||
gererOTA();
|
gererOTA();
|
||||||
gererBoutons();
|
gererBoutons();
|
||||||
gererModbus();
|
gererModbus();
|
||||||
|
|||||||
@@ -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
|
||||||
@@ -14,6 +14,7 @@
|
|||||||
#include "epever_config.h"
|
#include "epever_config.h"
|
||||||
#include "wifi_ap.h"
|
#include "wifi_ap.h"
|
||||||
#include "wireguard_vpn.h"
|
#include "wireguard_vpn.h"
|
||||||
|
#include "mqtt_client.h"
|
||||||
#include "debug_log.h"
|
#include "debug_log.h"
|
||||||
|
|
||||||
AsyncWebServer server(80);
|
AsyncWebServer server(80);
|
||||||
@@ -440,6 +441,30 @@ void demarrerWebserveur() {
|
|||||||
});
|
});
|
||||||
server.addHandler(handlerMdns);
|
server.addHandler(handlerMdns);
|
||||||
|
|
||||||
|
// --- API MQTT ---
|
||||||
|
|
||||||
|
server.on("/api/mqtt", HTTP_GET, [](AsyncWebServerRequest *r) {
|
||||||
|
String json;
|
||||||
|
getMqttJson(json);
|
||||||
|
r->send(200, "application/json", json);
|
||||||
|
});
|
||||||
|
|
||||||
|
auto *handlerMqtt = new AsyncCallbackJsonWebHandler("/api/mqtt",
|
||||||
|
[](AsyncWebServerRequest *r, JsonVariant &json) {
|
||||||
|
JsonObject obj = json.as<JsonObject>();
|
||||||
|
bool enabled = obj["enabled"] | false;
|
||||||
|
const char* srv = obj["server"] | "192.168.1.36";
|
||||||
|
uint16_t port = obj["port"] | 1883u;
|
||||||
|
const char* user = obj["user"] | "";
|
||||||
|
const char* pass = obj["pass"] | "";
|
||||||
|
const char* base = obj["base"] | "solar";
|
||||||
|
uint32_t interval = (uint32_t)((obj["interval"] | 30u)) * 1000u;
|
||||||
|
interval = constrain(interval, 5000u, 3600000u);
|
||||||
|
bool ok = setMqttConfig(enabled, srv, port, user, pass, base, interval);
|
||||||
|
r->send(ok ? 200 : 500, "application/json", ok ? "{\"ok\":true}" : "{\"ok\":false}");
|
||||||
|
});
|
||||||
|
server.addHandler(handlerMqtt);
|
||||||
|
|
||||||
// --- API WireGuard ---
|
// --- API WireGuard ---
|
||||||
|
|
||||||
server.on("/api/wireguard", HTTP_GET, [](AsyncWebServerRequest *r) {
|
server.on("/api/wireguard", HTTP_GET, [](AsyncWebServerRequest *r) {
|
||||||
|
|||||||
Reference in New Issue
Block a user