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');
|
||||
bouton.classList.add('active');
|
||||
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 === 'debug') chargerDebug();
|
||||
if (nom === 'epever-config') lireConfigEpever();
|
||||
@@ -848,6 +848,87 @@ function fermerSunPopup() {
|
||||
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 ---
|
||||
|
||||
async function chargerWireGuard() {
|
||||
|
||||
@@ -425,6 +425,57 @@
|
||||
</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="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>
|
||||
|
||||
Reference in New Issue
Block a user