a8f0d6ccba
Fonctionnalités : - Lecture RS485 Modbus Epever Tracer 4210N (115200 bps, FC03/FC04/FC16) - Moteur de règles JSON (LittleFS) — commande automatique des relais - Interface web mobile-first (dashboard, règles, config, historique, EPEVER, debug) - WiFi AP+STA simultanés avec reconnexion automatique et portail captif - mDNS configurable (pv.local par défaut) - Configuration registres EPEVER depuis l'UI (18 registres holding) - Historique basse/haute résolution avec graphes canvas - VPN WireGuard optionnel (désactivé par défaut, config via UI) - OTA firmware + filesystem via ElegantOTA - Deep sleep / économie d'énergie Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
823 lines
43 KiB
HTML
823 lines
43 KiB
HTML
<!DOCTYPE html>
|
||
<html lang="fr">
|
||
<head>
|
||
<meta charset="UTF-8">
|
||
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
|
||
<title>KC868 Solaire</title>
|
||
<link rel="icon" type="image/svg+xml" href="favicon.svg">
|
||
<link rel="stylesheet" href="style.css">
|
||
</head>
|
||
<body>
|
||
|
||
<header>
|
||
<div class="header-title">
|
||
<h1>⚡ Contrôleur Solaire</h1>
|
||
<span id="header-clock" class="header-clock">--</span>
|
||
</div>
|
||
<span id="rs485-badge" class="badge badge-err">RS485 --</span>
|
||
</header>
|
||
|
||
<nav>
|
||
<!-- Dashboard : grille 2×2 -->
|
||
<button class="tab active" title="Dashboard" onclick="afficherOnglet('dashboard', this)">
|
||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
||
<rect x="3" y="3" width="7" height="7" rx="1"/><rect x="14" y="3" width="7" height="7" rx="1"/>
|
||
<rect x="3" y="14" width="7" height="7" rx="1"/><rect x="14" y="14" width="7" height="7" rx="1"/>
|
||
</svg>
|
||
</button>
|
||
<!-- Règles : liste à puces -->
|
||
<button class="tab" title="Règles" onclick="afficherOnglet('regles', this)">
|
||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round">
|
||
<line x1="9" y1="6" x2="20" y2="6"/><line x1="9" y1="12" x2="20" y2="12"/><line x1="9" y1="18" x2="20" y2="18"/>
|
||
<circle cx="4.5" cy="6" r="1.5" fill="currentColor" stroke="none"/>
|
||
<circle cx="4.5" cy="12" r="1.5" fill="currentColor" stroke="none"/>
|
||
<circle cx="4.5" cy="18" r="1.5" fill="currentColor" stroke="none"/>
|
||
</svg>
|
||
</button>
|
||
<!-- Config : engrenage -->
|
||
<button class="tab" title="Configuration" onclick="afficherOnglet('config', this)">
|
||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
||
<circle cx="12" cy="12" r="3"/>
|
||
<path d="M19.4 15a1.65 1.65 0 0 0 .33 1.82l.06.06a2 2 0 0 1-2.83 2.83l-.06-.06a1.65 1.65 0 0 0-1.82-.33 1.65 1.65 0 0 0-1 1.51V21a2 2 0 0 1-4 0v-.09A1.65 1.65 0 0 0 9 19.4a1.65 1.65 0 0 0-1.82.33l-.06.06a2 2 0 0 1-2.83-2.83l.06-.06A1.65 1.65 0 0 0 4.68 15a1.65 1.65 0 0 0-1.51-1H3a2 2 0 0 1 0-4h.09A1.65 1.65 0 0 0 4.6 9a1.65 1.65 0 0 0-.33-1.82l-.06-.06a2 2 0 0 1 2.83-2.83l.06.06A1.65 1.65 0 0 0 9 4.68a1.65 1.65 0 0 0 1-1.51V3a2 2 0 0 1 4 0v.09a1.65 1.65 0 0 0 1 1.51 1.65 1.65 0 0 0 1.82-.33l.06-.06a2 2 0 0 1 2.83 2.83l-.06.06A1.65 1.65 0 0 0 19.4 9a1.65 1.65 0 0 0 1.51 1H21a2 2 0 0 1 0 4h-.09a1.65 1.65 0 0 0-1.51 1z"/>
|
||
</svg>
|
||
</button>
|
||
<!-- Historique : courbe -->
|
||
<button class="tab" title="Historique" onclick="afficherOnglet('historique', this)">
|
||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
||
<polyline points="22 12 18 12 15 21 9 3 6 12 2 12"/>
|
||
</svg>
|
||
</button>
|
||
<!-- EPEVER Config : curseurs -->
|
||
<button class="tab" title="Config EPEVER" onclick="afficherOnglet('epever-config', this)">
|
||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
||
<line x1="4" y1="21" x2="4" y2="14"/><line x1="4" y1="10" x2="4" y2="3"/>
|
||
<line x1="12" y1="21" x2="12" y2="12"/><line x1="12" y1="8" x2="12" y2="3"/>
|
||
<line x1="20" y1="21" x2="20" y2="16"/><line x1="20" y1="12" x2="20" y2="3"/>
|
||
<line x1="1" y1="14" x2="7" y2="14"/><line x1="9" y1="8" x2="15" y2="8"/>
|
||
<line x1="17" y1="16" x2="23" y2="16"/>
|
||
</svg>
|
||
</button>
|
||
<!-- Debug : terminal -->
|
||
<button class="tab" title="Debug" onclick="afficherOnglet('debug', this)">
|
||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
||
<polyline points="4 17 10 11 4 5"/>
|
||
<line x1="12" y1="19" x2="20" y2="19"/>
|
||
</svg>
|
||
</button>
|
||
</nav>
|
||
|
||
<main>
|
||
|
||
<!-- Dashboard -->
|
||
<section id="dashboard" class="onglet actif">
|
||
|
||
<div class="dash-section">Relais <span class="dash-hint">appui 1,1s = toggle + save</span></div>
|
||
<div class="grille">
|
||
<div class="carte" id="carte-relay1">
|
||
<div class="etiquette" id="label-relay1">Relais 1</div>
|
||
<div class="valeur" id="relay1-etat">--</div>
|
||
</div>
|
||
<div class="carte" id="carte-relay2">
|
||
<div class="etiquette" id="label-relay2">Relais 2</div>
|
||
<div class="valeur" id="relay2-etat">--</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="dash-section">Entrées</div>
|
||
<div class="grille">
|
||
<div class="carte">
|
||
<div class="etiquette" id="label-di1">Entrée 1</div>
|
||
<div class="valeur" id="di1-etat">--</div>
|
||
</div>
|
||
<div class="carte">
|
||
<div class="etiquette" id="label-di2">Entrée 2</div>
|
||
<div class="valeur" id="di2-etat">--</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="dash-section">Solaire</div>
|
||
<div class="grille grille-3">
|
||
<div class="carte">
|
||
<div class="etiquette">Tension PV</div>
|
||
<div class="valeur" id="pv">-- V</div>
|
||
</div>
|
||
<div class="carte">
|
||
<div class="etiquette">Courant PV</div>
|
||
<div class="valeur" id="pvCurrent">-- A</div>
|
||
</div>
|
||
<div class="carte" id="carte-sun">
|
||
<div class="etiquette">Ensoleillement</div>
|
||
<div class="valeur" id="sun">--</div>
|
||
</div>
|
||
</div>
|
||
<div class="grille">
|
||
<div class="carte">
|
||
<div class="etiquette">Horloge EPEVER</div>
|
||
<div class="valeur valeur-compacte" id="epeverTime">--</div>
|
||
</div>
|
||
<div class="carte">
|
||
<div class="etiquette">RTC EPEVER</div>
|
||
<div class="valeur" id="epeverClockOk">--</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="dash-section">Batterie</div>
|
||
<div class="grille">
|
||
<div class="carte">
|
||
<div class="etiquette">Tension</div>
|
||
<div class="valeur" id="battery">-- V</div>
|
||
</div>
|
||
<div class="carte">
|
||
<div class="etiquette">SOC</div>
|
||
<div class="valeur" id="batSOC">-- %</div>
|
||
</div>
|
||
<div class="carte">
|
||
<div class="etiquette">Statut</div>
|
||
<div class="valeur" id="batStatut">--</div>
|
||
</div>
|
||
<div class="carte">
|
||
<div class="etiquette">Température</div>
|
||
<div class="valeur" id="batTemp">-- °C</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="dash-section">Sortie 12V EPEVER</div>
|
||
<div class="grille grille-3">
|
||
<div class="carte">
|
||
<div class="etiquette">Tension load</div>
|
||
<div class="valeur" id="loadVoltage">-- V</div>
|
||
</div>
|
||
<div class="carte">
|
||
<div class="etiquette">Courant load</div>
|
||
<div class="valeur" id="loadCurrent">-- A</div>
|
||
</div>
|
||
<div class="carte">
|
||
<div class="etiquette">Puissance load</div>
|
||
<div class="valeur" id="loadPower">-- W</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="dash-section">Énergie</div>
|
||
<div class="grille">
|
||
<div class="carte">
|
||
<div class="etiquette">Prod. jour</div>
|
||
<div class="valeur" id="energieGenJour">-- kWh</div>
|
||
</div>
|
||
<div class="carte">
|
||
<div class="etiquette">Conso. jour</div>
|
||
<div class="valeur" id="energieConJour">-- kWh</div>
|
||
</div>
|
||
<div class="carte">
|
||
<div class="etiquette">Prod. total</div>
|
||
<div class="valeur" id="energieGenTotal">-- kWh</div>
|
||
</div>
|
||
<div class="carte">
|
||
<div class="etiquette">Conso. total</div>
|
||
<div class="valeur" id="energieConTotal">-- kWh</div>
|
||
</div>
|
||
</div>
|
||
|
||
</section>
|
||
|
||
<!-- Règles -->
|
||
<section id="regles" class="onglet">
|
||
<p class="aide">Chaque règle surveille des conditions (ensoleillement, tension batterie) et commande automatiquement un relais. Un délai optionnel évite les basculements intempestifs. Les règles s'appliquent en parallèle de la commande manuelle.</p>
|
||
|
||
<!-- Liste des règles -->
|
||
<div id="liste-regles"></div>
|
||
|
||
<!-- Formulaire ajout -->
|
||
<div class="regle-form">
|
||
<div class="form-titre">Ajouter une règle</div>
|
||
|
||
<div class="form-section-label">Déclencheur</div>
|
||
<div class="form-ligne">
|
||
<label>Soleil</label>
|
||
<select id="f-sun">
|
||
<option value="">Ignoré</option>
|
||
<option value="true">Jour</option>
|
||
<option value="false">Nuit</option>
|
||
</select>
|
||
</div>
|
||
<div class="form-ligne">
|
||
<label>Entrée DI1</label>
|
||
<select id="f-di1">
|
||
<option value="">Ignoré</option>
|
||
<option value="true">Fermé (ON)</option>
|
||
<option value="false">Ouvert (OFF)</option>
|
||
</select>
|
||
</div>
|
||
<div class="form-ligne">
|
||
<label>Entrée DI2</label>
|
||
<select id="f-di2">
|
||
<option value="">Ignoré</option>
|
||
<option value="true">Fermé (ON)</option>
|
||
<option value="false">Ouvert (OFF)</option>
|
||
</select>
|
||
</div>
|
||
|
||
<div class="form-section-label">Condition</div>
|
||
<div class="form-ligne">
|
||
<label>Batt. min (V)</label>
|
||
<input type="number" id="f-batmin" step="0.1" min="0" max="30" placeholder="0 = ignoré">
|
||
</div>
|
||
<div class="form-ligne">
|
||
<label>Batt. max (V)</label>
|
||
<input type="number" id="f-batmax" step="0.1" min="0" max="30" placeholder="0 = ignoré">
|
||
</div>
|
||
<div class="form-ligne">
|
||
<label>PV min (V)</label>
|
||
<input type="number" id="f-pvmin" step="0.1" min="0" max="200" placeholder="0 = ignoré">
|
||
</div>
|
||
<div class="form-ligne">
|
||
<label>PV max (V)</label>
|
||
<input type="number" id="f-pvmax" step="0.1" min="0" max="200" placeholder="0 = ignoré">
|
||
</div>
|
||
|
||
<div class="form-section-label">Action</div>
|
||
<div class="form-ligne">
|
||
<label>Relais</label>
|
||
<select id="f-relay">
|
||
<option value="1">Relais 1</option>
|
||
<option value="2">Relais 2</option>
|
||
</select>
|
||
</div>
|
||
<div class="form-ligne">
|
||
<label>État</label>
|
||
<select id="f-state">
|
||
<option value="true">ON</option>
|
||
<option value="false">OFF</option>
|
||
</select>
|
||
</div>
|
||
<div class="form-ligne">
|
||
<label>Délai (s)</label>
|
||
<input type="number" id="f-delay" min="0" value="0" placeholder="0 = immédiat">
|
||
</div>
|
||
<div class="form-ligne">
|
||
<label>Hystérésis (V)</label>
|
||
<input type="number" id="f-hysteresis" step="0.1" min="0" max="5" value="0" placeholder="0 = désactivé">
|
||
</div>
|
||
|
||
<button class="btn btn-primaire btn-plein" onclick="ajouterRegle()">Ajouter</button>
|
||
</div>
|
||
|
||
</section>
|
||
|
||
<!-- Config / Paramètres -->
|
||
<section id="config" class="onglet">
|
||
<p class="aide">Commande manuelle des relais, noms personnalisables, sleep, OTA et redémarrage. Un appui maintenu sur une carte relais du dashboard bascule et sauvegarde l'état.</p>
|
||
|
||
<div class="regle-form">
|
||
<div class="form-titre">Commande manuelle</div>
|
||
<div class="ligne-commande relay-row">
|
||
<span class="label-cmd"><span id="cmd-label-r1">Relais 1</span> <span id="led-r1" class="led led-off"></span></span>
|
||
<button id="btn-r1-on" class="btn btn-vert btn-dim" onclick="relay(1,'on')">ON</button>
|
||
<button id="btn-r1-off" class="btn btn-rouge btn-glow-rouge" onclick="relay(1,'off')">OFF</button>
|
||
</div>
|
||
<div class="ligne-commande relay-row">
|
||
<span class="label-cmd"><span id="cmd-label-r2">Relais 2</span> <span id="led-r2" class="led led-off"></span></span>
|
||
<button id="btn-r2-on" class="btn btn-vert btn-dim" onclick="relay(2,'on')">ON</button>
|
||
<button id="btn-r2-off" class="btn btn-rouge btn-glow-rouge" onclick="relay(2,'off')">OFF</button>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="regle-form">
|
||
<div class="form-titre">Noms des relais et entrées</div>
|
||
<div class="form-ligne">
|
||
<label>Relais 1</label>
|
||
<input type="text" id="c-n-relay1" maxlength="20" placeholder="Relais 1">
|
||
</div>
|
||
<div class="form-ligne">
|
||
<label>Relais 2</label>
|
||
<input type="text" id="c-n-relay2" maxlength="20" placeholder="Relais 2">
|
||
</div>
|
||
<div class="form-ligne">
|
||
<label>Entrée 1</label>
|
||
<input type="text" id="c-n-di1" maxlength="20" placeholder="Entrée 1">
|
||
</div>
|
||
<div class="form-ligne">
|
||
<label>Entrée 2</label>
|
||
<input type="text" id="c-n-di2" maxlength="20" placeholder="Entrée 2">
|
||
</div>
|
||
<button class="btn btn-primaire btn-plein" onclick="sauvegarderNoms()">Enregistrer et recharger</button>
|
||
</div>
|
||
|
||
<div class="regle-form">
|
||
<div class="form-titre">Mode économie d'énergie (sleep)</div>
|
||
<p class="aide">En mode sleep, l'ESP32 s'éteint entre deux cycles de mesure pour réduire la consommation. Il se réveille périodiquement, lit les données Modbus, évalue les règles, puis se rendort si la tension PV est inférieure au seuil (nuit détectée). En journée (PV > seuil), il reste actif en permanence.</p>
|
||
<div class="form-ligne">
|
||
<label>Activé</label>
|
||
<select id="c-sleep-actif">
|
||
<option value="false">Non</option>
|
||
<option value="true">Oui</option>
|
||
</select>
|
||
</div>
|
||
<div class="form-ligne">
|
||
<label>Réveil (min)</label>
|
||
<input type="number" id="c-sleep-intervalle" min="1" max="120" value="10">
|
||
</div>
|
||
<div class="form-ligne">
|
||
<label>Seuil PV (V)</label>
|
||
<input type="number" id="c-sleep-seuil" step="0.5" min="0" max="10" value="2.0">
|
||
</div>
|
||
<button class="btn btn-primaire btn-plein" onclick="sauvegarderSleep()">Enregistrer</button>
|
||
</div>
|
||
|
||
<div class="regle-form">
|
||
<div class="form-titre">Interface</div>
|
||
<div class="form-ligne">
|
||
<label>Rafraîchissement (s)</label>
|
||
<input type="number" id="c-refresh" min="1" max="60" step="1" value="1">
|
||
</div>
|
||
<div class="form-ligne">
|
||
<label>Appui long (ms)</label>
|
||
<input type="number" id="c-longpress2" min="200" max="3000" step="100" value="500">
|
||
</div>
|
||
<button class="btn btn-primaire btn-plein" onclick="sauvegarderInterface()">Enregistrer et recharger</button>
|
||
</div>
|
||
|
||
<div class="regle-form">
|
||
<div class="form-titre">Intervalles Modbus</div>
|
||
<p class="aide">En <strong>mode soleil</strong> (PV actif), les données sont lues fréquemment. En <strong>mode veille</strong> (nuit / PV absent), l'intervalle est plus long pour économiser l'énergie.</p>
|
||
<div class="form-ligne">
|
||
<label>Mode soleil (s)</label>
|
||
<input type="number" id="c-mb-jour" min="1" max="60" step="1" value="5">
|
||
</div>
|
||
<div class="form-ligne">
|
||
<label>Mode veille (s)</label>
|
||
<input type="number" id="c-mb-nuit" min="5" max="300" step="5" value="30">
|
||
</div>
|
||
<button class="btn btn-primaire btn-plein" onclick="sauvegarderModbus()">Enregistrer</button>
|
||
</div>
|
||
|
||
<div class="regle-form">
|
||
<div class="form-titre">Horloge EPEVER</div>
|
||
<p class="aide">L'ESP32 cale son horloge sur l'EPEVER au boot puis toutes les 6h. Utilise ce réglage si l'heure du MPPT est décalée.</p>
|
||
<div class="form-ligne">
|
||
<label>Date/heure</label>
|
||
<input type="datetime-local" id="c-epever-time" step="1">
|
||
</div>
|
||
<button class="btn btn-primaire btn-plein" onclick="remplirHeureNavigateur()">Utiliser l'heure du navigateur</button>
|
||
<button class="btn btn-vert btn-plein" onclick="sauvegarderHeureEpever()">Régler l'EPEVER</button>
|
||
</div>
|
||
|
||
<div class="regle-form">
|
||
<div class="form-titre">Connexion WiFi</div>
|
||
|
||
<div id="wifi-status-bar" class="ec-statusbar">Chargement…</div>
|
||
|
||
<div class="form-section-label">Point d'accès (AP)</div>
|
||
<div class="form-ligne">
|
||
<label>SSID AP</label>
|
||
<span id="wifi-ap-ssid" class="wifi-val">--</span>
|
||
</div>
|
||
<div class="form-ligne">
|
||
<label>IP AP</label>
|
||
<span id="wifi-ap-ip" class="wifi-val">192.168.4.1</span>
|
||
</div>
|
||
<div class="form-ligne">
|
||
<label>Clients</label>
|
||
<span id="wifi-ap-clients" class="wifi-val">--</span>
|
||
</div>
|
||
|
||
<div class="form-section-label" style="margin-top:0.6rem">Nom réseau local (mDNS)</div>
|
||
<div class="form-ligne">
|
||
<label>Adresse .local</label>
|
||
<div class="ec-field-unit">
|
||
<input type="text" id="wifi-mdns-host" maxlength="63" placeholder="pv" autocomplete="off">
|
||
<span class="ec-unit">.local</span>
|
||
</div>
|
||
</div>
|
||
<button class="btn btn-primaire btn-plein" onclick="sauvegarderMdns()">Enregistrer le nom</button>
|
||
|
||
<div class="form-section-label" style="margin-top:0.6rem">Rejoindre un réseau WiFi</div>
|
||
<div class="form-ligne">
|
||
<label>Réseau</label>
|
||
<div class="wifi-ssid-row">
|
||
<input type="text" id="wifi-sta-ssid" placeholder="SSID du réseau" autocomplete="off">
|
||
<button class="btn btn-sm" id="wifi-btn-scan" onclick="scannerWifi()">Scanner</button>
|
||
</div>
|
||
</div>
|
||
<div id="wifi-scan-list" class="wifi-scan-list hidden"></div>
|
||
<div class="form-ligne">
|
||
<label>Mot de passe</label>
|
||
<input type="password" id="wifi-sta-pass" placeholder="Mot de passe" autocomplete="new-password">
|
||
</div>
|
||
<div class="wifi-actions">
|
||
<button class="btn btn-vert" onclick="connecterWifi()">Connecter</button>
|
||
<button class="btn btn-rouge" id="wifi-btn-oublier" onclick="oublierWifi()">Oublier</button>
|
||
</div>
|
||
|
||
<div id="wifi-sta-info" class="hidden">
|
||
<div class="form-section-label" style="margin-top:0.6rem">Connecté au réseau</div>
|
||
<div class="form-ligne">
|
||
<label>SSID</label>
|
||
<span id="wifi-sta-ssid-cur" class="wifi-val wifi-connected">--</span>
|
||
</div>
|
||
<div class="form-ligne">
|
||
<label>IP locale</label>
|
||
<span id="wifi-sta-ip" class="wifi-val wifi-connected">--</span>
|
||
</div>
|
||
<div class="form-ligne">
|
||
<label>Signal</label>
|
||
<span id="wifi-sta-rssi" class="wifi-val">--</span>
|
||
</div>
|
||
</div>
|
||
</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>
|
||
|
||
<div id="wg-status-bar" class="ec-statusbar">Chargement…</div>
|
||
|
||
<div class="form-ligne">
|
||
<label>Activé</label>
|
||
<select id="wg-enabled">
|
||
<option value="false">Non</option>
|
||
<option value="true">Oui</option>
|
||
</select>
|
||
</div>
|
||
<div class="form-ligne">
|
||
<label>Clé privée ESP32</label>
|
||
<input type="password" id="wg-privkey" maxlength="64" placeholder="Base64 44 car." autocomplete="new-password">
|
||
</div>
|
||
<div class="form-ligne">
|
||
<label>Clé publique serveur</label>
|
||
<input type="text" id="wg-pubkey" maxlength="64" placeholder="Base64 44 car." autocomplete="off">
|
||
</div>
|
||
<div class="form-ligne">
|
||
<label>Clé pré-partagée <span class="ec-aide" title="Optionnel mais recommandé. Doit correspondre au PresharedKey du serveur.">ℹ</span></label>
|
||
<input type="password" id="wg-psk" maxlength="64" placeholder="Optionnel" autocomplete="new-password">
|
||
</div>
|
||
<div class="form-ligne">
|
||
<label>Endpoint (serveur)</label>
|
||
<input type="text" id="wg-endpoint" maxlength="64" placeholder="mon.domaine.org" autocomplete="off">
|
||
</div>
|
||
<div class="form-ligne">
|
||
<label>Port</label>
|
||
<input type="number" id="wg-port" min="1" max="65535" value="51820">
|
||
</div>
|
||
<div class="form-ligne">
|
||
<label>IP locale WG</label>
|
||
<div class="ec-field-unit">
|
||
<input type="text" id="wg-localip" maxlength="18" placeholder="10.8.0.x" autocomplete="off">
|
||
<span class="ec-unit">/24</span>
|
||
</div>
|
||
</div>
|
||
<div class="form-ligne">
|
||
<label>Keepalive <span class="ec-aide" title="Maintien NAT actif. 25s recommandé pour accès distant depuis le serveur.">ℹ</span></label>
|
||
<div class="ec-field-unit">
|
||
<input type="number" id="wg-keepalive" min="0" max="300" value="25">
|
||
<span class="ec-unit">s</span>
|
||
</div>
|
||
</div>
|
||
<button class="btn btn-primaire btn-plein" onclick="sauvegarderWireGuard()">Enregistrer et appliquer</button>
|
||
</div>
|
||
|
||
<div class="regle-form" style="margin-top:0.75rem">
|
||
<div class="form-titre">Mise à jour firmware (OTA)</div>
|
||
<p class="ota-info">Identifiants : aucun requis</p>
|
||
<a href="/update" class="btn btn-primaire btn-plein">Ouvrir l'interface OTA</a>
|
||
</div>
|
||
|
||
<div class="regle-form" style="margin-top:0.75rem">
|
||
<div class="form-titre">Exporter les données</div>
|
||
<p class="aide">Historique basse résolution : jusqu'à 30h de mesures (pas 5 min). Fichier CSV importable dans Excel, LibreOffice ou Google Sheets.</p>
|
||
<a href="/api/history/csv" download="historique.csv" class="btn btn-primaire btn-plein">Télécharger l'historique (CSV)</a>
|
||
</div>
|
||
|
||
<div class="regle-form" style="margin-top:0.75rem">
|
||
<div class="form-titre">Système</div>
|
||
<button class="btn btn-rouge btn-plein" onclick="rebootESP()">Redémarrer l'ESP32</button>
|
||
</div>
|
||
|
||
<img src="board.jpg" alt="KC868-A2 board" class="board-img">
|
||
|
||
<div class="regle-form rs485-info">
|
||
<div class="form-titre">Raccordement RS485 — Epever Tracer 4210N</div>
|
||
<p class="aide">Le contrôleur Epever utilise un connecteur <strong>RJ45 8P8C</strong> pour la communication RS485 (Modbus RTU, <strong>115200 bps</strong>, 8N1). Les signaux A et B sont doublés (pins 3&4 = B, pins 5&6 = A).<br>⚠ Ne jamais connecter les pins 1&2 (+7.5V) au KC868-A2.</p>
|
||
<pre class="rs485-schema">
|
||
Epever 4210N — RJ45 vue de face (languette vers le bas)
|
||
|
||
╔══════════════════════════════════╗
|
||
║ ┌──────────────────────────┐ ║
|
||
║ │ ╷ ╷ ╷ ╷ ╷ ╷ ╷ ╷ │ ║
|
||
║ │ 1 2 3 4 5 6 7 8 │ ║
|
||
║ └──────────────────────────┘ ║
|
||
╚══════════════════════════════════╝
|
||
│ │ │ │ │ │ │ │
|
||
GRI ORA NOI ROU VER JAU BLE MAR
|
||
+7V +7V B− B− A+ A+ GND GND
|
||
⚠️ ⚠️
|
||
│ │ │
|
||
(au choix, ex: ROU / JAU / BLE)
|
||
│ │ │
|
||
▼ ▼ ▼
|
||
KC868-A2 : B− A+ GND
|
||
</pre>
|
||
<table class="rs485-table">
|
||
<thead><tr><th>Pin</th><th>Couleur</th><th>Signal</th><th>KC868-A2</th></tr></thead>
|
||
<tbody>
|
||
<tr><td>1</td><td><span class="fil" style="background:#888">Gris</span></td><td>+7.5V ⚠</td><td>Ne pas connecter</td></tr>
|
||
<tr><td>2</td><td><span class="fil" style="background:#f80">Orange</span></td><td>+7.5V ⚠</td><td>Ne pas connecter</td></tr>
|
||
<tr><td>3</td><td><span class="fil" style="background:#222">Noir</span></td><td>RS-485-B</td><td rowspan="2">B−</td></tr>
|
||
<tr><td>4</td><td><span class="fil" style="background:#e00">Rouge</span></td><td>RS-485-B</td></tr>
|
||
<tr><td>5</td><td><span class="fil" style="background:#0a0">Vert</span></td><td>RS-485-A</td><td rowspan="2">A+</td></tr>
|
||
<tr><td>6</td><td><span class="fil" style="background:#cc0;color:#333">Jaune</span></td><td>RS-485-A</td></tr>
|
||
<tr><td>7</td><td><span class="fil" style="background:#00c">Bleu</span></td><td>GND</td><td rowspan="2">GND (optionnel)</td></tr>
|
||
<tr><td>8</td><td><span class="fil" style="background:#6b3a2a">Marron</span></td><td>GND</td></tr>
|
||
</tbody>
|
||
</table>
|
||
<p class="aide" style="margin-top:0.5rem">⚠ Le port RS485 de l'Epever n'est pas isolé. Un module d'isolation RS485 est recommandé pour éviter les boucles de masse.</p>
|
||
<p class="aide">🔋 <strong>Alimentation KC868-A2</strong> : nécessite <strong>12V DC</strong> — brancher directement sur la batterie du système solaire. Ne pas utiliser les pins 1&2 du RJ45 (+7.5V, courant trop faible).</p>
|
||
</div>
|
||
|
||
<div class="regle-form rs485-info">
|
||
<div class="form-titre">Raccordement des relais</div>
|
||
<p class="aide">Chaque relais dispose de 3 bornes : <strong>COM</strong> (commun), <strong>NO</strong> (normalement ouvert) et <strong>NC</strong> (normalement fermé). Capacité max : <strong>10A / 250V AC</strong>.</p>
|
||
<pre class="rs485-schema">
|
||
┌──────────────────────────────────────────────┐
|
||
│ RELAIS HORS TENSION (OFF) │
|
||
│ │
|
||
│ COM ────● ○ NO (circuit ouvert) │
|
||
│ COM ────●───● NC (circuit fermé) │
|
||
└──────────────────────────────────────────────┘
|
||
┌──────────────────────────────────────────────┐
|
||
│ RELAIS ALIMENTÉ (ON) │
|
||
│ │
|
||
│ COM ────●───● NO (circuit fermé) │
|
||
│ COM ────● ○ NC (circuit ouvert) │
|
||
└──────────────────────────────────────────────┘
|
||
</pre>
|
||
<table class="rs485-table">
|
||
<thead><tr><th>Contact</th><th>Relais OFF</th><th>Relais ON</th><th>Usage typique</th></tr></thead>
|
||
<tbody>
|
||
<tr><td>NO</td><td>Ouvert</td><td>Fermé</td><td>Charge OFF par défaut</td></tr>
|
||
<tr><td>NC</td><td>Fermé</td><td>Ouvert</td><td>Charge ON par défaut</td></tr>
|
||
</tbody>
|
||
</table>
|
||
|
||
<div class="form-titre" style="margin-top:1rem">Exemple : commande d'une lampe 230V</div>
|
||
<p class="aide">⚡ Travaux sur le 230V : couper le disjoncteur avant toute intervention. La tension 230V est dangereuse et potentiellement mortelle.</p>
|
||
<pre class="rs485-schema">
|
||
Utilisation du contact NO (lampe éteinte par défaut)
|
||
|
||
Tableau Bornier relais KC868-A2 Lampe
|
||
électrique ┌───────────────────────────┐
|
||
│ │
|
||
Phase (L) ───►│ COM NO ►───┼──── Lampe ──┐
|
||
│ NC │ │
|
||
└───────────────────────────┘ │
|
||
Neutre (N) ──────────────────────────────────────────────┘
|
||
|
||
Relais OFF → NO ouvert → lampe ÉTEINTE
|
||
Relais ON → NO fermé → lampe ALLUMÉE
|
||
|
||
|
||
Utilisation du contact NC (lampe allumée par défaut)
|
||
|
||
Tableau Bornier relais KC868-A2 Lampe
|
||
électrique ┌───────────────────────────┐
|
||
│ │
|
||
Phase (L) ───►│ COM NC ►───┼──── Lampe ──┐
|
||
│ NO │ │
|
||
└───────────────────────────┘ │
|
||
Neutre (N) ──────────────────────────────────────────────┘
|
||
|
||
Relais OFF → NC fermé → lampe ALLUMÉE
|
||
Relais ON → NC ouvert → lampe ÉTEINTE
|
||
</pre>
|
||
</div>
|
||
</section>
|
||
|
||
<!-- Historique -->
|
||
<section id="historique" class="onglet">
|
||
<p class="aide">Mode <strong>4h</strong> : un point par minute (RAM uniquement). Mode <strong>30h</strong> : moyenne sur 5 min, sauvegardée toutes les heures sur le système de fichiers.</p>
|
||
<div class="hist-toggle">
|
||
<button id="btn-hires" class="btn btn-primaire active-mode" onclick="setHistMode('hires')">4h</button>
|
||
<button id="btn-lores" class="btn" onclick="setHistMode('lores')">30h</button>
|
||
<button class="btn" onclick="chargerHistorique()">↻</button>
|
||
</div>
|
||
<div id="hist-debug" class="hist-debug">Historique en attente...</div>
|
||
<div id="hist-last" class="hist-debug">Derniers points en attente...</div>
|
||
<div class="graphe-conteneur">
|
||
<div class="graphe-titre">Tension batterie (V)</div>
|
||
<canvas id="chart-battery" width="600" height="150"></canvas>
|
||
</div>
|
||
<div class="graphe-conteneur">
|
||
<div class="graphe-titre">Tension PV (V)</div>
|
||
<canvas id="chart-pv" width="600" height="150"></canvas>
|
||
</div>
|
||
<div class="graphe-conteneur">
|
||
<div class="graphe-titre">Puissance load (W)</div>
|
||
<canvas id="chart-load" width="600" height="150"></canvas>
|
||
</div>
|
||
<div class="graphe-conteneur">
|
||
<div class="graphe-titre">SOC batterie (%)</div>
|
||
<canvas id="chart-soc" width="600" height="150"></canvas>
|
||
</div>
|
||
</section>
|
||
|
||
<!-- Config EPEVER -->
|
||
<section id="epever-config" class="onglet">
|
||
<p class="aide">Lecture et modification des paramètres de charge du régulateur EPEVER. Cliquer <strong>Lire</strong> pour synchroniser, modifier les valeurs, puis <strong>Écrire</strong> pour appliquer. Une confirmation est demandée avant toute écriture.</p>
|
||
|
||
<div class="ec-statusbar" id="ec-status">En attente — cliquer Lire pour synchroniser</div>
|
||
|
||
<div class="regle-form">
|
||
<div class="form-titre">Actions</div>
|
||
<div class="ec-actions">
|
||
<button class="btn btn-primaire" onclick="lireConfigEpever()">Lire</button>
|
||
<button class="btn btn-vert" onclick="sauvegarderConfigEpever()">Sauvegarder</button>
|
||
<button class="btn" onclick="restaurerConfigEpever()">Restaurer</button>
|
||
<button class="btn btn-sm" onclick="exporterConfigEpever()">Exporter</button>
|
||
<label class="btn btn-sm ec-import-label">Importer
|
||
<input type="file" id="ec-import-file" accept=".json" style="display:none" onchange="importerConfigEpever(this)">
|
||
</label>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Batterie -->
|
||
<div class="regle-form">
|
||
<div class="form-titre">Batterie</div>
|
||
<div class="form-ligne">
|
||
<label>Type <span class="ec-aide" title="0=Perso, 1=Scellée (AGM), 2=GEL, 3=Liquide, 4=Lithium. Détermine les algorithmes de charge.">ℹ</span></label>
|
||
<select id="ec-battery_type" class="ec-field">
|
||
<option value="0">Perso (User)</option>
|
||
<option value="1">Scellée / AGM</option>
|
||
<option value="2">GEL</option>
|
||
<option value="3">Liquide (Flooded)</option>
|
||
<option value="4">Lithium</option>
|
||
</select>
|
||
</div>
|
||
<div class="form-ligne">
|
||
<label>Capacité <span class="ec-aide" title="Capacité nominale en Ah. Influence les calculs de protection.">ℹ</span></label>
|
||
<div class="ec-field-unit">
|
||
<input type="number" id="ec-battery_capacity" class="ec-field" min="1" max="9999" step="1">
|
||
<span class="ec-unit">Ah</span>
|
||
</div>
|
||
</div>
|
||
<div class="form-ligne">
|
||
<label>Tension système <span class="ec-aide" title="0=Auto détecté, 1=12V, 2=24V. Laisser Auto si non sûr.">ℹ</span></label>
|
||
<select id="ec-rated_voltage" class="ec-field">
|
||
<option value="0">Auto</option>
|
||
<option value="1">12V</option>
|
||
<option value="2">24V</option>
|
||
</select>
|
||
</div>
|
||
<div class="form-ligne">
|
||
<label>Comp. température <span class="ec-aide" title="Correction de tension selon la température. Typique plomb-acide : -3. Saisir 0 si inconnu.">ℹ</span></label>
|
||
<div class="ec-field-unit">
|
||
<input type="number" id="ec-temp_compensation" class="ec-field" min="-9" max="0" step="1">
|
||
<span class="ec-unit">mV/°C/2V</span>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Seuils de charge -->
|
||
<div class="regle-form">
|
||
<div class="form-titre">Seuils de charge</div>
|
||
<div class="form-ligne">
|
||
<label>Float <span class="ec-aide" title="Tension de maintien après charge complète. Typique 13.6V. Trop haute = usure prématurée.">ℹ</span></label>
|
||
<div class="ec-field-unit">
|
||
<input type="number" id="ec-float_voltage" class="ec-field" min="13.0" max="14.5" step="0.01">
|
||
<span class="ec-unit">V</span>
|
||
</div>
|
||
</div>
|
||
<div class="form-ligne">
|
||
<label>Boost <span class="ec-aide" title="Tension d'absorption (charge rapide). Typique 14.4V plomb-acide.">ℹ</span></label>
|
||
<div class="ec-field-unit">
|
||
<input type="number" id="ec-boost_voltage" class="ec-field" min="13.0" max="15.5" step="0.01">
|
||
<span class="ec-unit">V</span>
|
||
</div>
|
||
</div>
|
||
<div class="form-ligne">
|
||
<label>Égalisation <span class="ec-aide" title="Tension des cycles de désulfatation périodiques. Typique 14.6V.">ℹ</span></label>
|
||
<div class="ec-field-unit">
|
||
<input type="number" id="ec-equalize_voltage" class="ec-field" min="13.0" max="16.0" step="0.01">
|
||
<span class="ec-unit">V</span>
|
||
</div>
|
||
</div>
|
||
<div class="form-ligne">
|
||
<label>Limite charge <span class="ec-aide" title="Tension maximale autorisée pendant la charge.">ℹ</span></label>
|
||
<div class="ec-field-unit">
|
||
<input type="number" id="ec-charging_limit" class="ec-field" min="13.0" max="16.0" step="0.01">
|
||
<span class="ec-unit">V</span>
|
||
</div>
|
||
</div>
|
||
<div class="form-ligne">
|
||
<label>Déconnexion survoltage <span class="ec-aide" title="Coupure de protection si la tension dépasse ce seuil.">ℹ</span></label>
|
||
<div class="ec-field-unit">
|
||
<input type="number" id="ec-high_volt_disconnect" class="ec-field" min="13.0" max="17.0" step="0.01">
|
||
<span class="ec-unit">V</span>
|
||
</div>
|
||
</div>
|
||
<div class="form-ligne">
|
||
<label>Reconnexion survoltage <span class="ec-aide" title="La charge reprend quand la tension descend sous ce seuil après une coupure survoltage.">ℹ</span></label>
|
||
<div class="ec-field-unit">
|
||
<input type="number" id="ec-overvolt_reconnect" class="ec-field" min="13.0" max="16.0" step="0.01">
|
||
<span class="ec-unit">V</span>
|
||
</div>
|
||
</div>
|
||
<div class="form-ligne">
|
||
<label>Reconnexion boost <span class="ec-aide" title="Si la tension descend sous ce seuil, la phase boost reprend automatiquement.">ℹ</span></label>
|
||
<div class="ec-field-unit">
|
||
<input type="number" id="ec-boost_reconnect" class="ec-field" min="11.0" max="14.0" step="0.01">
|
||
<span class="ec-unit">V</span>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Seuils de décharge / protection -->
|
||
<div class="regle-form">
|
||
<div class="form-titre">Protection décharge</div>
|
||
<div class="form-ligne">
|
||
<label>Alerte basse tension <span class="ec-aide" title="Déclenche une alarme sans couper. Signal préventif.">ℹ</span></label>
|
||
<div class="ec-field-unit">
|
||
<input type="number" id="ec-undervolt_warning" class="ec-field" min="10.0" max="13.0" step="0.01">
|
||
<span class="ec-unit">V</span>
|
||
</div>
|
||
</div>
|
||
<div class="form-ligne">
|
||
<label>Déconnexion basse tension <span class="ec-aide" title="La sortie de charge est coupée sous cette tension pour protéger la batterie.">ℹ</span></label>
|
||
<div class="ec-field-unit">
|
||
<input type="number" id="ec-low_disconnect" class="ec-field" min="9.0" max="13.0" step="0.01">
|
||
<span class="ec-unit">V</span>
|
||
</div>
|
||
</div>
|
||
<div class="form-ligne">
|
||
<label>Reconnexion basse tension <span class="ec-aide" title="La sortie de charge est réactivée quand la tension remonte au-dessus de ce seuil.">ℹ</span></label>
|
||
<div class="ec-field-unit">
|
||
<input type="number" id="ec-low_reconnect" class="ec-field" min="10.0" max="13.5" step="0.01">
|
||
<span class="ec-unit">V</span>
|
||
</div>
|
||
</div>
|
||
<div class="form-ligne">
|
||
<label>Limite décharge <span class="ec-aide" title="Tension minimale absolue. Ne jamais descendre en dessous.">ℹ</span></label>
|
||
<div class="ec-field-unit">
|
||
<input type="number" id="ec-discharge_limit" class="ec-field" min="9.0" max="13.0" step="0.01">
|
||
<span class="ec-unit">V</span>
|
||
</div>
|
||
</div>
|
||
<div class="form-ligne">
|
||
<label>SOC déconnexion <span class="ec-aide" title="Seuil de charge minimum (%) avant coupure de la sortie.">ℹ</span></label>
|
||
<div class="ec-field-unit">
|
||
<input type="number" id="ec-bat_discharge_soc" class="ec-field" min="20" max="100" step="1">
|
||
<span class="ec-unit">%</span>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Timing -->
|
||
<div class="regle-form">
|
||
<div class="form-titre">Timing</div>
|
||
<div class="form-ligne">
|
||
<label>Durée boost <span class="ec-aide" title="Durée maximale de la phase d'absorption en minutes.">ℹ</span></label>
|
||
<div class="ec-field-unit">
|
||
<input type="number" id="ec-boost_duration" class="ec-field" min="10" max="180" step="1">
|
||
<span class="ec-unit">min</span>
|
||
</div>
|
||
</div>
|
||
<div class="form-ligne">
|
||
<label>Durée égalisation <span class="ec-aide" title="Durée du cycle d'égalisation en minutes. 0 = désactivé.">ℹ</span></label>
|
||
<div class="ec-field-unit">
|
||
<input type="number" id="ec-equalize_duration" class="ec-field" min="0" max="180" step="1">
|
||
<span class="ec-unit">min</span>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<button class="btn btn-rouge btn-plein ec-btn-write" onclick="ecrireConfigEpever()">
|
||
Écrire vers l'EPEVER
|
||
</button>
|
||
|
||
</section>
|
||
|
||
<!-- Debug -->
|
||
<section id="debug" class="onglet">
|
||
<div class="debug-actions">
|
||
<button class="btn btn-primaire" onclick="chargerDebug()">Rafraîchir</button>
|
||
<button class="btn btn-rouge" onclick="viderDebug()">Vider</button>
|
||
</div>
|
||
<div class="debug-meta" id="debug-meta">Journal en attente</div>
|
||
<pre id="debug-console" class="debug-console">Chargement...</pre>
|
||
</section>
|
||
|
||
|
||
</main>
|
||
|
||
<div id="sun-modal" class="modal hidden">
|
||
<div class="modal-box">
|
||
<div class="modal-title">Changements Jour/Nuit</div>
|
||
<div id="sun-history-list" class="modal-list">Chargement...</div>
|
||
<button class="btn btn-primaire btn-plein" onclick="fermerSunPopup()">Fermer</button>
|
||
</div>
|
||
</div>
|
||
|
||
<footer id="pied-page">En attente de données…</footer>
|
||
|
||
<script src="app.js"></script>
|
||
</body>
|
||
</html>
|