Note de ce sujet :
  • Moyenne : 0 (0 vote(s))
  • 1
  • 2
  • 3
  • 4
  • 5
Petit prog pour esp32
#11
(28-05-2025, 10:07 PM)grostoto a écrit : oui j'ai reussi à le déplacer.

- un bouton pour passer un routeur en plein ecran et un pour revenir en affichage partagée  ?

Bonjour,
avec ptit bouton a coté du nom de page pour grand ecran et revenir
https://mega.nz/file/0BJGRAba#lxWs8oirpz...ShcvMKf7N0
bonne journee
ESP32Wroom, Triac 40A "BTA40", Source UxIx2, Cumulus 300L 3000W.
Sonde temperature sur radiateur triac mise en route ventilateur a 25°
réactivité 30 seuil -50
2 esp32 pour gestion charge batteries
14 panneaux de 410wcc en autoconso micro-onduleur APS DS3
Suivi sur Domoticz
Répondre
#12
Bonjour lucky
Je n'arrive pas à ouvrir le fichier. Pb de format ou de fichier corrompu.
Routeur UxIx2, V 14.2 Triac robodyn avec bta 40 déporté sur dissipateur ventilé avec sonde T° commandé en sortie TOR , seconde sonde sur C-E, 12 panneaux sanyo 236wc, onduleur réseau solarmax 6kw. CE 3300w. Installé depuis avril 2024.
Répondre
#13
(29-05-2025, 05:32 PM)Philmaz a écrit : Bonjour lucky
Je n'arrive pas à ouvrir le fichier. Pb de format ou de fichier corrompu.

slt
pour ouvrir il faut utiliser winrar j ai pas fait en .zip
ESP32Wroom, Triac 40A "BTA40", Source UxIx2, Cumulus 300L 3000W.
Sonde temperature sur radiateur triac mise en route ventilateur a 25°
réactivité 30 seuil -50
2 esp32 pour gestion charge batteries
14 panneaux de 410wcc en autoconso micro-onduleur APS DS3
Suivi sur Domoticz
Répondre
#14
j'essaie vainement de supprimer l'étiquette 'interface 1' et ses petites soeurs .. mais je n'y arrive pas..

très bien le bouton plein ecran.
Répondre
#15
(29-05-2025, 06:08 PM)grostoto a écrit : j'essaie vainement de supprimer l'étiquette 'interface 1' et ses petites soeurs .. mais je n'y arrive pas..

très bien le bouton plein ecran.

pour supprimer l etiquette qui je vois t embete hihihi

il faut commenter ou supprimer le :
html
Code :
<span class="frame-label" id="label1">Interface 1</span>

html
Code :
<span class="frame-label" id="label2">Interface 2</span>

html
Code :
<span class="frame-label" id="label3">Interface 3</span>

html
Code :
<span class="frame-label" id="label4">Interface 4</span>


pour commenter
<!-- <span class="frame-label" id="label1">Interface 1</span> -->
ESP32Wroom, Triac 40A "BTA40", Source UxIx2, Cumulus 300L 3000W.
Sonde temperature sur radiateur triac mise en route ventilateur a 25°
réactivité 30 seuil -50
2 esp32 pour gestion charge batteries
14 panneaux de 410wcc en autoconso micro-onduleur APS DS3
Suivi sur Domoticz
Répondre
#16
(29-05-2025, 06:03 PM)lucky a écrit :
(29-05-2025, 05:32 PM)Philmaz a écrit : Bonjour lucky
Je n'arrive pas à ouvrir le fichier. Pb de format ou de fichier corrompu.

slt
pour ouvrir il faut utiliser winrar j ai pas fait en .zip

Merci
Boulet que je suis
Routeur UxIx2, V 14.2 Triac robodyn avec bta 40 déporté sur dissipateur ventilé avec sonde T° commandé en sortie TOR , seconde sonde sur C-E, 12 panneaux sanyo 236wc, onduleur réseau solarmax 6kw. CE 3300w. Installé depuis avril 2024.
Répondre
#17
bon
doit y avoir un cheveux dans le potage..

ligne 534 je met ceci:

Code :
<!-- <span class="frame-label" id="label1">Interface 1</span> -->


certes cela me supprime l'étiquette.. mais cela me supprime aussi l'affichage du 2d routeur.. (écran splitté de droite noir)

dès que je décommente, cela réapparait.
Répondre
#18
(29-05-2025, 07:49 PM)grostoto a écrit : bon
doit y avoir un cheveux dans le potage..

ligne 534 je met ceci:

Code :
<!-- <span class="frame-label" id="label1">Interface 1</span> -->


certes cela me supprime l'étiquette.. mais cela me supprime aussi l'affichage du 2d routeur.. (écran splitté de droite noir)

dès que je décommente, cela réapparait.
je test

oui un oubli ....il faut aussi commenter cette ligne

// label.textContent = config[`name${i}`] || `Interface ${i}`;

Code :
// ESP32 Dashboard avec mode AP pour configuration initiale
#include <WiFi.h>
#include <WebServer.h>
#include <EEPROM.h>
#include <ArduinoJson.h>

// Structure pour stocker la config en EEPROM
struct Config {
  char magic[4];  // "RMS" pour vérifier si config valide
  char ssid[32];
  char password[64];
  char dashUser[32];     // Nom d'utilisateur pour le dashboard
  char dashPass[32];     // Mot de passe pour le dashboard
  char ip1[16];
  char ip2[16];
  char ip3[16];
  char ip4[16];
  char name1[32];
  char name2[32];
  char name3[32];
  char name4[32];
};

Config config;
WebServer server(80);
bool isAPMode = false;

// Page de configuration en mode AP
const char config_html[] PROGMEM = R"rawliteral(
<!DOCTYPE html>
<html lang="fr">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Configuration Dashboard RMS</title>
    <style>
        body {
            font-family: Arial, sans-serif;
            margin: 0;
            padding: 20px;
            background: #1a1a2e;
            color: white;
        }
        .container {
            max-width: 600px;
            margin: 0 auto;
            background: rgba(255,255,255,0.1);
            padding: 30px;
            border-radius: 10px;
        }
        h1 {
            text-align: center;
            color: #4CAF50;
        }
        .section {
            background: rgba(0,0,0,0.3);
            padding: 20px;
            margin: 20px 0;
            border-radius: 5px;
        }
        h2 {
            color: #2196F3;
            margin-top: 0;
        }
        label {
            display: block;
            margin: 10px 0 5px;
        }
        input, select {
            width: 100%;
            padding: 10px;
            border: 1px solid #555;
            border-radius: 3px;
            background: #333;
            color: white;
            box-sizing: border-box;
        }
        .ip-group {
            display: grid;
            grid-template-columns: 2fr 1fr;
            gap: 10px;
            margin: 10px 0;
        }
        button {
            background: #4CAF50;
            color: white;
            padding: 12px 20px;
            border: none;
            border-radius: 5px;
            cursor: pointer;
            font-size: 16px;
            width: 100%;
            margin-top: 20px;
        }
        button:hover {
            background: #45a049;
        }
        .scan-btn {
            background: #2196F3;
            margin: 10px 0;
        }
        #scanResults {
            max-height: 200px;
            overflow-y: auto;
            background: rgba(0,0,0,0.5);
            padding: 10px;
            border-radius: 3px;
            margin: 10px 0;
        }
        .network-item {
            padding: 5px;
            cursor: pointer;
            border-bottom: 1px solid #444;
        }
        .network-item:hover {
            background: rgba(255,255,255,0.1);
        }
        .loading {
            text-align: center;
            color: #4CAF50;
        }
        .info {
            background: #ff9800;
            color: black;
            padding: 10px;
            border-radius: 5px;
            margin: 10px 0;
            text-align: center;
        }
    </style>
</head>
<body>
    <div class="container">
        <h1>Configuration Dashboard RMS</h1>
       
        <div class="info" id="networkInfo">
            <!-- L'info sera remplie dynamiquement -->
        </div>
       
        <div class="section">
            <h2>1. Configuration WiFi</h2>
            <div id="currentWifi" style="background: #4CAF50; color: white; padding: 10px; border-radius: 3px; margin-bottom: 10px; display: none;">
                ? Connecté à : <strong id="wifiName">---</strong>
            </div>
            <button class="scan-btn" onclick="scanNetworks()">? Scanner les réseaux</button>
            <div id="scanResults"></div>
           
            <label>SSID:</label>
            <input type="text" id="ssid" placeholder="Nom du réseau WiFi">
           
            <label>Mot de passe:</label>
            <input type="password" id="password" placeholder="Mot de passe WiFi">
        </div>
       
        <div class="section">
            <h2>2. Sécurité du Dashboard</h2>
            <label>Nom d'utilisateur:</label>
            <input type="text" id="dashUser" placeholder="admin">
           
            <label>Mot de passe:</label>
            <input type="password" id="dashPass" placeholder="Mot de passe dashboard">
            <p style="color: #aaa; font-size: 12px;">Laissez vide pour désactiver l'authentification</p>
        </div>
       
        <div class="section">
            <h2>3. Configuration des interfaces RMS</h2>
            <p style="color: #aaa;">Configurez entre 2 et 4 interfaces</p>
           
            <div class="ip-group">
                <input type="text" id="ip1" placeholder="IP Interface 1 (ex: 192.168.1.100)">
                <input type="text" id="name1" placeholder="Nom 1">
            </div>
           
            <div class="ip-group">
                <input type="text" id="ip2" placeholder="IP Interface 2 (ex: 192.168.1.101)">
                <input type="text" id="name2" placeholder="Nom 2">
            </div>
           
            <div class="ip-group">
                <input type="text" id="ip3" placeholder="IP Interface 3 (optionnel)">
                <input type="text" id="name3" placeholder="Nom 3">
            </div>
           
            <div class="ip-group">
                <input type="text" id="ip4" placeholder="IP Interface 4 (optionnel)">
                <input type="text" id="name4" placeholder="Nom 4">
            </div>
        </div>
       
        <button onclick="saveConfig()">? Sauvegarder et redémarrer</button>

        <button style="background: #666; margin-top: 10px;" onclick="window.location.href='/'">
            ↩️ Retour au Dashboard
        </button>
       
        <div id="message"></div>
    </div>
   
    <script>
        // Charger la config existante au démarrage
        window.onload = function() {
    // Afficher l'info réseau
    fetch('/networkinfo')
        .then(response => response.json())
        .then(info => {
            const networkInfo = document.getElementById('networkInfo');
            if (info.isAP) {
                networkInfo.innerHTML = 'Mode Configuration - Connectez-vous au WiFi pour continuer';
                networkInfo.style.background = '#ff9800';
            } else {
                networkInfo.innerHTML = `IP réseau : ${info.ip}`;
                networkInfo.style.background = '#4CAF50';
            }
        });
   
    fetch('/getconfig')
        .then(response => response.json())
        .then(data => {
            // Pré-remplir les IPs et noms
            if (data.ip1) document.getElementById('ip1').value = data.ip1;
            if (data.ip2) document.getElementById('ip2').value = data.ip2;
            if (data.ip3) document.getElementById('ip3').value = data.ip3;
            if (data.ip4) document.getElementById('ip4').value = data.ip4;
            if (data.name1) document.getElementById('name1').value = data.name1;
            if (data.name2) document.getElementById('name2').value = data.name2;
            if (data.name3) document.getElementById('name3').value = data.name3;
            if (data.name4) document.getElementById('name4').value = data.name4;
           
            // AJOUT : Afficher le WiFi actuel si connecté
            if (data.ssid && window.location.hostname !== '192.168.4.1') {
                document.getElementById('currentWifi').style.display = 'block';
                document.getElementById('wifiName').textContent = data.ssid;
            }
        })
        .catch(err => {
            console.log('Pas de config existante');
        });
};
       
        function scanNetworks() {
            document.getElementById('scanResults').innerHTML = '<div class="loading">Scan en cours...</div>';
           
            fetch('/scan')
                .then(response => response.json())
                .then(data => {
                    let html = '';
                    data.networks.forEach(network => {
                        html += `<div class="network-item" onclick="selectNetwork('${network.ssid}')">
                                   ? ${network.ssid} (${network.rssi} dBm)
                                 </div>`;
                    });
                    document.getElementById('scanResults').innerHTML = html || 'Aucun réseau trouvé';
                });
        }
       
        function selectNetwork(ssid) {
            document.getElementById('ssid').value = ssid;
        }
       
        function saveConfig() {
            const config = {
                ssid: document.getElementById('ssid').value,
                password: document.getElementById('password').value,
                dashUser: document.getElementById('dashUser').value,
                dashPass: document.getElementById('dashPass').value,
                ip1: document.getElementById('ip1').value,
                ip2: document.getElementById('ip2').value,
                ip3: document.getElementById('ip3').value,
                ip4: document.getElementById('ip4').value,
                name1: document.getElementById('name1').value || 'Interface 1',
                name2: document.getElementById('name2').value || 'Interface 2',
                name3: document.getElementById('name3').value || 'Interface 3',
                name4: document.getElementById('name4').value || 'Interface 4'
            };
           
            // Validation
            if (!config.ssid || !config.password) {
                alert('Veuillez remplir les informations WiFi');
                return;
            }
           
            if (!config.ip1 || !config.ip2) {
                alert('Veuillez configurer au moins 2 interfaces');
                return;
            }
           
            document.getElementById('message').innerHTML = '<div class="loading">Sauvegarde en cours...</div>';
           
            fetch('/save', {
                method: 'POST',
                headers: {'Content-Type': 'application/json'},
                body: JSON.stringify(config)
            })
            .then(response => response.text())
            .then(data => {
                document.getElementById('message').innerHTML =
                    '<div class="info">Configuration sauvegardée! Redémarrage dans 5 secondes...</div>';
                setTimeout(() => {
                    document.getElementById('message').innerHTML +=
                        '<div class="info">Connectez-vous au WiFi configuré pour accéder au dashboard</div>';
                }, 2000);
            });
        }
    </script>
</body>
</html>
)rawliteral";

// Page de login
const char login_html[] PROGMEM = R"rawliteral(
<!DOCTYPE html>
<html lang="fr">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Login Dashboard RMS</title>
    <style>
        body {
            font-family: Arial, sans-serif;
            margin: 0;
            padding: 0;
            background: #1a1a2e;
            color: white;
            display: flex;
            justify-content: center;
            align-items: center;
            height: 100vh;
        }
        .login-box {
            background: rgba(255,255,255,0.1);
            padding: 40px;
            border-radius: 10px;
            box-shadow: 0 0 20px rgba(0,0,0,0.5);
            width: 300px;
        }
        h2 {
            text-align: center;
            color: #4CAF50;
            margin-bottom: 30px;
        }
        input {
            width: 100%;
            padding: 12px;
            margin: 10px 0;
            border: 1px solid #555;
            border-radius: 5px;
            background: #333;
            color: white;
            box-sizing: border-box;
        }
        button {
            width: 100%;
            padding: 12px;
            margin-top: 20px;
            background: #4CAF50;
            color: white;
            border: none;
            border-radius: 5px;
            cursor: pointer;
            font-size: 16px;
        }
        button:hover {
            background: #45a049;
        }
        .error {
            color: #ff4444;
            text-align: center;
            margin-top: 10px;
        }
    </style>
</head>
<body>
    <div class="login-box">
        <h2>? Dashboard RMS</h2>
        <form action="/login" method="POST">
            <input type="text" name="user" placeholder="Nom d'utilisateur" required>
            <input type="password" name="pass" placeholder="Mot de passe" required>
            <button type="submit">Se connecter</button>
        </form>
        <div id="error" class="error"></div>
    </div>
    <script>
        if (window.location.search.includes('error=1')) {
            document.getElementById('error').textContent = 'Identifiants incorrects';
        }
    </script>
</body>
</html>
)rawliteral";

// Page dashboard modifiée avec bouton plein écran
const char dashboard_html[] PROGMEM = R"rawliteral(
<!DOCTYPE html>
<html lang="fr">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Dashboard RMS</title>
    <style>
        body {
            margin: 0;
            padding: 0;
            background: #222;
            font-family: Arial, sans-serif;
            height: 100vh;
            overflow: hidden;
        }
       
        #dashboard {
            display: grid;
            height: 100vh;
            gap: 2px;
            transition: all 0.3s ease;
        }
       
        #dashboard.fullscreen {
            display: block !important;
        }
       
        .layout-2 {
            grid-template-columns: 1fr 1fr;
            grid-template-rows: 1fr;
        }
       
        .layout-3 {
            grid-template-columns: 1fr 1fr;
            grid-template-rows: 1fr 1fr;
        }
        .layout-3 .frame-container:first-child {
            grid-row: span 2;
        }
       
        .layout-4 {
            grid-template-columns: 1fr 1fr;
            grid-template-rows: 1fr 1fr;
        }
       
        .frame-container {
            position: relative;
            overflow: hidden;
            background: white;
            display: none;
        }
       
        .frame-container.active {
            display: block;
        }
       
        .frame-container.fullscreen-active {
            display: block !important;
            position: fixed;
            top: 0;
            left: 0;
            width: 100vw;
            height: 100vh;
            z-index: 50;
        }
       
        .frame-wrapper {
            width: 100%;
            height: 100%;
            transform-origin: top left;
            transition: transform 0.3s ease;
        }
       
        iframe {
            width: 100%;
            height: 100%;
            border: none;
            background: white;
        }
       
        .frame-header {
            position: absolute;
            top: 5px;
            left: 5px;
            display: flex;
            align-items: center;
            gap: 10px;
            z-index: 10;
        }
       
        .frame-label {
            background: rgba(0,0,0,0.8);
            color: white;
            padding: 5px 10px;
            font-size: 12px;
            border-radius: 3px;
            pointer-events: none;
        }
       
        .fullscreen-btn {
            background: rgba(0,0,0,0.8);
            color: white;
            border: none;
            padding: 5px 8px;
            font-size: 12px;
            border-radius: 3px;
            cursor: pointer;
            transition: background 0.2s;
        }
       
        .fullscreen-btn:hover {
            background: rgba(0,0,0,0.9);
        }
       
        .fullscreen-btn.exit {
            background: rgba(220,53,69,0.9);
        }
       
        .fullscreen-btn.exit:hover {
            background: rgba(220,53,69,1);
        }
       
        /* Contrôles de zoom */
        .zoom-controls {
            position: absolute;
            top: 5px;
            right: 5px;
            background: rgba(0,0,0,0.8);
            padding: 5px;
            border-radius: 3px;
            z-index: 10;
            display: flex;
            gap: 5px;
        }
       
        .zoom-btn {
            width: 25px;
            height: 25px;
            border: none;
            background: #444;
            color: white;
            cursor: pointer;
            border-radius: 3px;
            font-size: 16px;
            display: flex;
            align-items: center;
            justify-content: center;
        }
       
        .zoom-btn:hover {
            background: #666;
        }
       
        .zoom-value {
            color: white;
            font-size: 12px;
            min-width: 45px;
            text-align: center;
            display: flex;
            align-items: center;
        }
       
        .btn-config {
            position: fixed;
            bottom: 10px;
            right: 10px;
            background: #2196F3;
            color: white;
            border: none;
            padding: 10px 20px;
            border-radius: 5px;
            cursor: pointer;
            z-index: 100;
        }
       
        .btn-config:hover {
            background: #1976D2;
        }
       
        .hidden {
            display: none !important;
        }
    </style>
</head>
<body>
    <div id="dashboard">
        <div class="frame-container" id="frame1-container">
            <div class="frame-header">
                <span class="frame-label" id="label1">Interface 1</span>
                <button class="fullscreen-btn" onclick="toggleFullscreen(1)" id="fullscreen-btn-1">⛶</button>
            </div>
            <div class="zoom-controls">
                <button class="zoom-btn" onclick="zoom(1, -0.1)">−</button>
                <span class="zoom-value" id="zoom1">100%</span>
                <button class="zoom-btn" onclick="zoom(1, 0.1)">+</button>
            </div>
            <div class="frame-wrapper" id="wrapper1">
                <iframe id="frame1"></iframe>
            </div>
        </div>
       
        <div class="frame-container" id="frame2-container">
            <div class="frame-header">
                <span class="frame-label" id="label2">Interface 2</span>
                <button class="fullscreen-btn" onclick="toggleFullscreen(2)" id="fullscreen-btn-2">⛶</button>
            </div>
            <div class="zoom-controls">
                <button class="zoom-btn" onclick="zoom(2, -0.1)">−</button>
                <span class="zoom-value" id="zoom2">100%</span>
                <button class="zoom-btn" onclick="zoom(2, 0.1)">+</button>
            </div>
            <div class="frame-wrapper" id="wrapper2">
                <iframe id="frame2"></iframe>
            </div>
        </div>
       
        <div class="frame-container" id="frame3-container">
            <div class="frame-header">
                <span class="frame-label" id="label3">Interface 3</span>
                <button class="fullscreen-btn" onclick="toggleFullscreen(3)" id="fullscreen-btn-3">⛶</button>
            </div>
            <div class="zoom-controls">
                <button class="zoom-btn" onclick="zoom(3, -0.1)">−</button>
                <span class="zoom-value" id="zoom3">100%</span>
                <button class="zoom-btn" onclick="zoom(3, 0.1)">+</button>
            </div>
            <div class="frame-wrapper" id="wrapper3">
                <iframe id="frame3"></iframe>
            </div>
        </div>
       
        <div class="frame-container" id="frame4-container">
            <div class="frame-header">
                <span class="frame-label" id="label4">Interface 4</span>
                <button class="fullscreen-btn" onclick="toggleFullscreen(4)" id="fullscreen-btn-4">⛶</button>
            </div>
            <div class="zoom-controls">
                <button class="zoom-btn" onclick="zoom(4, -0.1)">−</button>
                <span class="zoom-value" id="zoom4">100%</span>
                <button class="zoom-btn" onclick="zoom(4, 0.1)">+</button>
            </div>
            <div class="frame-wrapper" id="wrapper4">
                <iframe id="frame4"></iframe>
            </div>
        </div>
    </div>
   
    <button class="btn-config" onclick="window.location.href='/config'">
        ⚙️ Configuration
    </button>
   
    <script>
        // Stocker les niveaux de zoom et l'état plein écran
        const zoomLevels = {1: 1, 2: 1, 3: 1, 4: 1};
        let currentFullscreen = null;
       
        // Fonction de basculement plein écran
        function toggleFullscreen(frameNum) {
            const container = document.getElementById(`frame${frameNum}-container`);
            const dashboard = document.getElementById('dashboard');
            const btn = document.getElementById(`fullscreen-btn-${frameNum}`);
            const configBtn = document.querySelector('.btn-config');
           
            if (currentFullscreen === frameNum) {
                // Sortir du plein écran
                container.classList.remove('fullscreen-active');
                dashboard.classList.remove('fullscreen');
                btn.classList.remove('exit');
                btn.textContent = '⛶';
                configBtn.classList.remove('hidden');
                currentFullscreen = null;
               
                // Réafficher toutes les autres vues actives
                for (let i = 1; i <= 4; i++) {
                    const otherContainer = document.getElementById(`frame${i}-container`);
                    if (otherContainer.classList.contains('active') && i !== frameNum) {
                        otherContainer.style.display = '';
                    }
                }
            } else {
                // Entrer en plein écran
               
                // D'abord sortir du plein écran si une autre vue l'est
                if (currentFullscreen !== null) {
                    toggleFullscreen(currentFullscreen);
                }
               
                container.classList.add('fullscreen-active');
                dashboard.classList.add('fullscreen');
                btn.classList.add('exit');
                btn.textContent = '✕';
                configBtn.classList.add('hidden');
                currentFullscreen = frameNum;
               
                // Masquer toutes les autres vues
                for (let i = 1; i <= 4; i++) {
                    if (i !== frameNum) {
                        const otherContainer = document.getElementById(`frame${i}-container`);
                        otherContainer.style.display = 'none';
                    }
                }
            }
        }
       
        // Fonction de zoom individuel
        function zoom(frameNum, delta) {
            zoomLevels[frameNum] = Math.max(0.5, Math.min(2, zoomLevels[frameNum] + delta));
            updateZoom(frameNum);
        }
       
        // Mettre à jour l'affichage du zoom
        function updateZoom(frameNum) {
            const wrapper = document.getElementById(`wrapper${frameNum}`);
            const zoomDisplay = document.getElementById(`zoom${frameNum}`);
            const scale = zoomLevels[frameNum];
           
            wrapper.style.transform = `scale(${scale})`;
            wrapper.style.width = `${100 / scale}%`;
            wrapper.style.height = `${100 / scale}%`;
            zoomDisplay.textContent = `${Math.round(scale * 100)}%`;
        }
       
        // Gestion de la touche Escape pour sortir du plein écran
        document.addEventListener('keydown', function(e) {
            if (e.key === 'Escape' && currentFullscreen !== null) {
                toggleFullscreen(currentFullscreen);
            }
        });
       
        // Charger la config depuis l'ESP32
        fetch('/getconfig')
            .then(response => response.json())
            .then(config => {
                applyConfig(config);
            });
       
        function applyConfig(config) {
            const dashboard = document.getElementById('dashboard');
            let count = 0;
           
            // Compter les interfaces configurées
            for (let i = 1; i <= 4; i++) {
                if (config[`ip${i}`]) count++;
            }
           
            // Appliquer le layout
            dashboard.className = '';
            dashboard.classList.add(`layout-${count}`);
           
            // Configurer les frames
            let frameIndex = 1;
            for (let i = 1; i <= 4; i++) {
                if (config[`ip${i}`]) {
                    const container = document.getElementById(`frame${frameIndex}-container`);
                    const frame = document.getElementById(`frame${frameIndex}`);
                    const label = document.getElementById(`label${frameIndex}`);
                   
                    container.classList.add('active');
                    frame.src = `http://${config[`ip${i}`]}`;
                    label.textContent = config[`name${i}`] || `Interface ${i}`;
                   
                    frameIndex++;
                }
            }
        }
    </script>
</body>
</html>
)rawliteral";

void loadConfig() {
  EEPROM.begin(sizeof(Config));
  EEPROM.get(0, config);
 
  // Vérifier si config valide
  if (String(config.magic) != "RMS") {
    // Config invalide, mode AP
    isAPMode = true;
  }
}

void saveConfig() {
  strcpy(config.magic, "RMS");
  EEPROM.put(0, config);
  EEPROM.commit();
}

void clearConfig() {
  strcpy(config.magic, "");
  EEPROM.put(0, config);
  EEPROM.commit();
}

void startAPMode() {
  Serial.println("Démarrage en mode AP...");
  WiFi.mode(WIFI_AP);
  WiFi.softAP("RMS_Dashboard_Config", "rms12345");
 
  IPAddress IP = WiFi.softAPIP();
  Serial.print("AP IP: ");
  Serial.println(IP);
}

void handleConfigPage() {
  server.send(200, "text/html", config_html);
}

// Variables globales pour l'authentification
bool isAuthenticated = false;
String sessionID = "";

// Générer un ID de session aléatoire
String generateSessionID() {
  String id = "";
  for (int i = 0; i < 16; i++) {
    id += String(random(0, 16), HEX);
  }
  return id;
}

// Vérifier l'authentification
bool checkAuth() {
  // En mode AP, pas d'authentification
  if (isAPMode) {
    return true;
  }
 
  // Si pas de mot de passe configuré, accès libre
  if (strlen(config.dashUser) == 0 && strlen(config.dashPass) == 0) {
    return true;
  }
 
  // Si seulement un des deux est vide, accès libre aussi (config incomplète)
  if (strlen(config.dashUser) == 0 || strlen(config.dashPass) == 0) {
    return true;
  }
 
  // Vérifier le cookie de session
  if (server.hasHeader("Cookie")) {
    String cookie = server.header("Cookie");
    if (cookie.indexOf("session=" + sessionID) != -1 && sessionID != "") {
      return true;
    }
  }
  return false;
}

void handleNetworkInfo() {
  String json = "{";
  if (isAPMode) {
    json += "\"isAP\":true,";
    json += "\"ip\":\"192.168.4.1\"";
  } else {
    json += "\"isAP\":false,";
    json += "\"ip\":\"" + WiFi.localIP().toString() + "\"";
  }
  json += "}";
 
  server.send(200, "application/json", json);
}

void handleLogin() {
  if (server.method() == HTTP_POST) {
    String user = server.arg("user");
    String pass = server.arg("pass");
   
    if (user == config.dashUser && pass == config.dashPass) {
      // Login réussi
      sessionID = generateSessionID();
      server.sendHeader("Set-Cookie", "session=" + sessionID + "; Path=/");
      server.sendHeader("Location", "/");
      server.send(302);
    } else {
      // Login échoué
      server.sendHeader("Location", "/?error=1");
      server.send(302);
    }
  } else {
    server.send(200, "text/html", login_html);
  }
}

void handleRoot() {
  if (isAPMode) {
    server.send(200, "text/html", config_html);
  } else {
    if (!checkAuth()) {
      server.send(200, "text/html", login_html);
    } else {
      server.send(200, "text/html", dashboard_html);
    }
  }
}

void handleScan() {
  int n = WiFi.scanNetworks();
  String json = "{\"networks\":[";
 
  for (int i = 0; i < n; i++) {
    if (i > 0) json += ",";
    String ssid = WiFi.SSID(i);
    // Échapper les caractères spéciaux dans le SSID
    ssid.replace("\"", "\\\"");
    ssid.replace("\\", "\\\\");
    json += "{\"ssid\":\"" + ssid + "\",\"rssi\":" + String(WiFi.RSSI(i)) + "}";
  }
  json += "]}";
 
  server.send(200, "application/json", json);
}

void handleSave() {
  if (server.hasArg("plain")) {
    DynamicJsonDocument doc(1024);
    deserializeJson(doc, server.arg("plain"));
   
    // Sauvegarder la config
    strcpy(config.ssid, doc["ssid"]);
    strcpy(config.password, doc["password"]);
    strcpy(config.dashUser, doc["dashUser"] | "");
    strcpy(config.dashPass, doc["dashPass"] | "");
    strcpy(config.ip1, doc["ip1"]);
    strcpy(config.ip2, doc["ip2"]);
    strcpy(config.ip3, doc["ip3"] | "");
    strcpy(config.ip4, doc["ip4"] | "");
    strcpy(config.name1, doc["name1"] | "Interface 1");
    strcpy(config.name2, doc["name2"] | "Interface 2");
    strcpy(config.name3, doc["name3"] | "Interface 3");
    strcpy(config.name4, doc["name4"] | "Interface 4");
   
    saveConfig();
    server.send(200, "text/plain", "OK");
   
    delay(5000);
    ESP.restart();
  }
}

void handleConfig() {
  String json = "{";
  json += "\"ssid\":\"" + String(config.ssid) + "\","; 
  json += "\"ip1\":\"" + String(config.ip1) + "\",";
  json += "\"ip2\":\"" + String(config.ip2) + "\",";
  json += "\"ip3\":\"" + String(config.ip3) + "\",";
  json += "\"ip4\":\"" + String(config.ip4) + "\",";
  json += "\"name1\":\"" + String(config.name1) + "\",";
  json += "\"name2\":\"" + String(config.name2) + "\",";
  json += "\"name3\":\"" + String(config.name3) + "\",";
  json += "\"name4\":\"" + String(config.name4) + "\"";
  json += "}";
 
  server.send(200, "application/json", json);
}

void handleReset() {
  clearConfig();
  server.send(200, "text/plain", "Config cleared");
}

void setup() {
  Serial.begin(115200);
 
  // Charger la config
  loadConfig();
 
  if (isAPMode) {
    // Mode AP pour configuration
    startAPMode();
  } else {
    // Mode normal - connexion WiFi
    WiFi.begin(config.ssid, config.password);
   
    int attempts = 0;
    while (WiFi.status() != WL_CONNECTED && attempts < 20) {
      delay(500);
      Serial.print(".");
      attempts++;
    }
   
    if (WiFi.status() != WL_CONNECTED) {
      // Échec connexion, retour en mode AP
      isAPMode = true;
      startAPMode();
    } else {
      Serial.println("\nConnecté!");
      Serial.print("IP: ");
      Serial.println(WiFi.localIP());
    }
  }
 
  // Routes
  server.on("/", handleRoot);
  server.on("/login", handleLogin);
  server.on("/config", handleConfigPage);  // Page de configuration
  server.on("/scan", handleScan);
  server.on("/save", HTTP_POST, handleSave);
  server.on("/getconfig", handleConfig);  // API pour récupérer la config
  server.on("/reset", handleReset);

  server.on("/networkinfo", handleNetworkInfo);
 
  server.begin();
  Serial.println("Serveur démarré");
}

void loop() {
  server.handleClient();
}


j ai fait des modifs dans config (bouton retor et rappel des enregistrements d ip et autre)
pour intergrer creez un dossier" webif_multi " et dedans un fichier "webif_multi.ino" avec le contenu du code si dessus

Cdlt
ESP32Wroom, Triac 40A "BTA40", Source UxIx2, Cumulus 300L 3000W.
Sonde temperature sur radiateur triac mise en route ventilateur a 25°
réactivité 30 seuil -50
2 esp32 pour gestion charge batteries
14 panneaux de 410wcc en autoconso micro-onduleur APS DS3
Suivi sur Domoticz
Répondre
#19
super.. la c'est tout bon..
bravo encore.
Répondre
#20
bon la un petit fichier html qui fait la meme chose si vous avez pas d esp en rab

Code :
<!DOCTYPE html>
<html lang="fr">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Dashboard Multi-Interfaces</title>
    <style>
        body {
            margin: 0;
            padding: 0;
            font-family: Arial, sans-serif;
            background: #222;
        }
       
        /* Styles pour le mode configuration */
        .config-mode {
            background-color: #f5f5f5 !important;
        }
       
        .config-container {
            display: none;
            max-width: 800px;
            margin: 50px auto;
            padding: 20px;
        }
       
        .config-mode .config-container {
            display: block;
        }
       
        .config-panel {
            background: white;
            padding: 30px;
            border-radius: 10px;
            box-shadow: 0 2px 10px rgba(0,0,0,0.1);
        }
       
        h1 {
            color: #333;
            text-align: center;
            margin-bottom: 30px;
        }
       
        .ip-list {
            margin-bottom: 20px;
        }
       
        .ip-group {
            margin-bottom: 15px;
            padding: 15px;
            background-color: #f9f9f9;
            border-radius: 5px;
            display: flex;
            align-items: center;
            gap: 10px;
        }
       
        .ip-number {
            font-weight: bold;
            color: #555;
            min-width: 30px;
        }
       
        input[type="text"] {
            flex: 1;
            padding: 10px;
            border: 1px solid #ddd;
            border-radius: 4px;
            font-size: 16px;
        }
       
        input[type="text"]:focus {
            outline: none;
            border-color: #4CAF50;
        }
       
        .remove-btn {
            background-color: #f44336;
            color: white;
            border: none;
            padding: 8px 15px;
            border-radius: 4px;
            cursor: pointer;
            font-size: 14px;
        }
       
        .remove-btn:hover {
            background-color: #d32f2f;
        }
       
        .add-btn {
            background-color: #2196F3;
            color: white;
            border: none;
            padding: 10px 20px;
            border-radius: 5px;
            cursor: pointer;
            font-size: 16px;
            margin-bottom: 20px;
            width: 100%;
        }
       
        .add-btn:hover {
            background-color: #1976D2;
        }
       
        .button-group {
            margin-top: 30px;
            display: flex;
            gap: 10px;
            justify-content: center;
        }
       
        button {
            padding: 12px 30px;
            border: none;
            border-radius: 5px;
            font-size: 16px;
            cursor: pointer;
            transition: background-color 0.3s;
        }
       
        .save-btn {
            background-color: #4CAF50;
            color: white;
        }
       
        .save-btn:hover {
            background-color: #45a049;
        }
       
        .message {
            margin-top: 20px;
            padding: 10px;
            border-radius: 5px;
            text-align: center;
            display: none;
        }
       
        .success {
            background-color: #d4edda;
            color: #155724;
            border: 1px solid #c3e6cb;
        }
       
        .error {
            background-color: #f8d7da;
            color: #721c24;
            border: 1px solid #f5c6cb;
        }
       
        .layout-preview {
            margin-top: 20px;
            padding: 15px;
            background-color: #e3f2fd;
            border-radius: 5px;
            text-align: center;
            font-size: 14px;
            color: #1976D2;
        }
       
        /* Styles pour le mode dashboard */
        .dashboard-container {
            display: none;
            height: 100vh;
            gap: 2px;
            background: #222;
        }
       
        .dashboard-mode .dashboard-container {
            display: grid;
        }
       
        .frame-container {
            position: relative;
            overflow: hidden;
            background: white;
        }
       
        .frame-wrapper {
            width: 100%;
            height: 100%;
            transform-origin: top left;
            transition: transform 0.3s ease;
        }
       
        iframe {
            width: 100%;
            height: 100%;
            border: none;
            background: white;
        }
       
        /* Contrôles de zoom */
        .zoom-controls {
            position: absolute;
            top: 5px;
            right: 5px;
            background: rgba(0,0,0,0.8);
            padding: 5px;
            border-radius: 3px;
            z-index: 10;
            display: flex;
            gap: 5px;
        }
       
        .zoom-btn {
            width: 25px;
            height: 25px;
            border: none;
            background: #444;
            color: white;
            cursor: pointer;
            border-radius: 3px;
            font-size: 16px;
            display: flex;
            align-items: center;
            justify-content: center;
        }
       
        .zoom-btn:hover {
            background: #666;
        }
       
        .zoom-value {
            color: white;
            font-family: Arial, sans-serif;
            font-size: 12px;
            min-width: 45px;
            text-align: center;
            display: flex;
            align-items: center;
        }
       
        /* Label optionnel */
        .frame-label {
            position: absolute;
            top: 5px;
            left: 5px;
            background: rgba(0,0,0,0.7);
            color: white;
            padding: 5px 10px;
            font-family: Arial, sans-serif;
            font-size: 12px;
            border-radius: 3px;
            z-index: 10;
            pointer-events: none;
        }
       
        /* Panneau de contrôle global */
        .global-controls {
            display: none;
            position: fixed;
            bottom: 10px;
            left: 50%;
            transform: translateX(-50%);
            background: rgba(0,0,0,0.9);
            padding: 10px 20px;
            border-radius: 5px;
            z-index: 100;
        }
       
        .dashboard-mode .global-controls {
            display: block;
        }
       
        .global-btn {
            background: #4CAF50;
            color: white;
            border: none;
            padding: 8px 15px;
            margin: 0 5px;
            border-radius: 3px;
            cursor: pointer;
        }
       
        .global-btn:hover {
            background: #45a049;
        }
       
        .config-btn {
            background: #FF9800;
        }
       
        .config-btn:hover {
            background: #F57C00;
        }
       
        /* Message d'accueil */
        .welcome-message {
            text-align: center;
            padding: 40px;
            background-color: #e3f2fd;
            border-radius: 10px;
            margin-bottom: 30px;
        }
       
        .welcome-message h2 {
            color: #1976D2;
            margin-bottom: 10px;
        }
    </style>
</head>
<body class="config-mode">
    <!-- Mode Configuration -->
    <div class="config-container">
        <div class="config-panel">
            <h1>Dashboard Multi-Interfaces</h1>
           
            <div id="welcomeMessage" class="welcome-message">
                <h2>Bienvenue !</h2>
                <p>Configurez vos adresses IP ci-dessous pour commencer.</p>
            </div>
           
            <div id="ipList" class="ip-list"></div>
           
            <button class="add-btn" onclick="addIP()">+ Ajouter une interface</button>
           
            <div id="layoutPreview" class="layout-preview"></div>
           
            <div class="button-group">
                <button class="save-btn" onclick="showDashboard()">Afficher le dashboard</button>
                <button class="save-btn" style="background-color: #FF5722;" onclick="saveToFile()">? Sauvegarder config</button>
            </div>
           
            <div id="message" class="message"></div>
        </div>
    </div>
   
    <!-- Mode Dashboard -->
    <div id="dashboardContainer" class="dashboard-container"></div>
   
    <!-- Contrôles globaux du dashboard -->
    <div class="global-controls">
        <button class="global-btn" onclick="resetAllZoom()">Réinitialiser tout</button>
        <button class="global-btn" onclick="zoomAll(-0.1)">Tout réduire</button>
        <button class="global-btn" onclick="zoomAll(0.1)">Tout agrandir</button>
        <button class="global-btn config-btn" onclick="showConfig()">⚙️ Configuration</button>
    </div>
   
    <script>
        // Configuration des IP - MODIFIÉE AUTOMATIQUEMENT
        let ipAddresses = ['192.168.1.145'];
        let zoomLevels = {};
        let currentMode = 'config';
       
        // Initialisation
        window.onload = function() {
            // Si des IP sont déjà configurées dans le fichier
            if (ipAddresses.length > 0 && ipAddresses[0] !== '') {
                document.getElementById('welcomeMessage').style.display = 'none';
                // Aller directement au dashboard si configuré
                if (ipAddresses.length > 1 || (ipAddresses.length === 1 && ipAddresses[0] !== '192.168.1.145')) {
                    showDashboard();
                }
            }
           
            renderIPList();
        };
       
        // Afficher la liste des IP
        function renderIPList() {
            const container = document.getElementById('ipList');
            container.innerHTML = '';
           
            ipAddresses.forEach((ip, index) => {
                const div = document.createElement('div');
                div.className = 'ip-group';
                div.innerHTML = `
                    <span class="ip-number">${index + 1}.</span>
                    <input type="text" id="ip${index}" value="${ip}" placeholder="Ex: 192.168.1.100">
                    ${ipAddresses.length > 1 ? `<button class="remove-btn" onclick="removeIP(${index})">Supprimer</button>` : ''}
                `;
                container.appendChild(div);
            });
           
            updateLayoutPreview();
        }
       
        // Ajouter une IP
        function addIP() {
            ipAddresses.push('');
            renderIPList();
        }
       
        // Supprimer une IP
        function removeIP(index) {
            ipAddresses.splice(index, 1);
            renderIPList();
        }
       
        // Mettre à jour l'aperçu
        function updateLayoutPreview() {
            const preview = document.getElementById('layoutPreview');
            const count = ipAddresses.length;
           
            let layout = '';
            if (count === 1) {
                layout = 'Plein écran';
            } else if (count === 2) {
                layout = '2 colonnes côte à côte';
            } else if (count === 3) {
                layout = '1 grande vue à gauche + 2 petites à droite';
            } else if (count === 4) {
                layout = 'Grille 2x2';
            } else if (count <= 6) {
                layout = 'Grille 2x3';
            } else if (count <= 9) {
                layout = 'Grille 3x3';
            } else {
                layout = `Grille ${Math.ceil(Math.sqrt(count))}x${Math.ceil(count / Math.ceil(Math.sqrt(count)))}`;
            }
           
            preview.innerHTML = `<strong>Disposition :</strong> ${count} interface${count > 1 ? 's' : ''} - ${layout}`;
        }
       
        // Valider une adresse IP
        function validateIP(ip) {
            const pattern = /^(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$/;
            return pattern.test(ip);
        }
       
        // Afficher un message
        function showMessage(text, type) {
            const messageEl = document.getElementById('message');
            messageEl.textContent = text;
            messageEl.className = 'message ' + type;
            messageEl.style.display = 'block';
           
            setTimeout(() => {
                messageEl.style.display = 'none';
            }, 3000);
        }
       
        // Calculer la disposition
        function calculateGridLayout(count) {
            if (count === 1) return { cols: 1, rows: 1, special: 'single' };
            if (count === 2) return { cols: 2, rows: 1, special: 'two-columns' };
            if (count === 3) return { cols: 2, rows: 2, special: 'three-special' };
            if (count === 4) return { cols: 2, rows: 2, special: 'grid' };
           
            const cols = Math.ceil(Math.sqrt(count));
            const rows = Math.ceil(count / cols);
            return { cols, rows, special: 'grid' };
        }
       
        // Afficher le dashboard
        function showDashboard() {
            // Récupérer et valider les IP
            const newIPs = [];
            for (let i = 0; i < ipAddresses.length; i++) {
                const ipInput = document.getElementById(`ip${i}`);
                const ip = ipInput ? ipInput.value.trim() : ipAddresses[i];
                if (!ip) {
                    showMessage('Veuillez remplir toutes les adresses IP', 'error');
                    return;
                }
                if (!validateIP(ip)) {
                    showMessage(`L'adresse IP n°${i + 1} n'est pas valide`, 'error');
                    return;
                }
                newIPs.push(ip);
            }
           
            ipAddresses = newIPs;
           
            // Construire le dashboard
            buildDashboard();
           
            // Basculer en mode dashboard
            document.body.className = 'dashboard-mode';
            currentMode = 'dashboard';
        }
       
        // Sauvegarder la configuration dans le fichier actuel
        function saveToFile() {
            // Récupérer et valider les IP
            const newIPs = [];
            for (let i = 0; i < ipAddresses.length; i++) {
                const ip = document.getElementById(`ip${i}`).value.trim();
                if (!ip) {
                    showMessage('Veuillez remplir toutes les adresses IP', 'error');
                    return;
                }
                if (!validateIP(ip)) {
                    showMessage(`L'adresse IP n°${i + 1} n'est pas valide`, 'error');
                    return;
                }
                newIPs.push(ip);
            }
           
            // Demander le nom du fichier
            let filename = prompt('Nom du fichier à sauvegarder :', 'dashboard.html');
            if (!filename) return; // Annulé
           
            // Ajouter .html si pas présent
            if (!filename.endsWith('.html')) {
                filename += '.html';
            }
           
            // Obtenir le HTML actuel
            const currentHTML = document.documentElement.outerHTML;
           
            // Remplacer la configuration actuelle
            const modifiedHTML = currentHTML.replace(
                /let ipAddresses = \[[^\]]*\];/,
                `let ipAddresses = ${JSON.stringify(newIPs)};`
            );
           
            // Créer et télécharger le fichier
            const blob = new Blob([modifiedHTML], { type: 'text/html' });
            const url = URL.createObjectURL(blob);
            const a = document.createElement('a');
            a.href = url;
            a.download = filename;
            document.body.appendChild(a);
            a.click();
            document.body.removeChild(a);
            URL.revokeObjectURL(url);
           
            showMessage(`Configuration sauvegardée dans ${filename} !`, 'success');
        }
       
        // Construire le dashboard
        function buildDashboard() {
            const container = document.getElementById('dashboardContainer');
            const layout = calculateGridLayout(ipAddresses.length);
           
            // Appliquer les styles de grille
            if (layout.special === 'single') {
                container.style.gridTemplateColumns = '1fr';
                container.style.gridTemplateRows = '1fr';
            } else if (layout.special === 'two-columns') {
                container.style.gridTemplateColumns = '1fr 1fr';
                container.style.gridTemplateRows = '1fr';
            } else if (layout.special === 'three-special') {
                container.style.gridTemplateColumns = '1fr 1fr';
                container.style.gridTemplateRows = '1fr 1fr';
            } else {
                container.style.gridTemplateColumns = `repeat(${layout.cols}, 1fr)`;
                container.style.gridTemplateRows = `repeat(${layout.rows}, 1fr)`;
            }
           
            // Créer les conteneurs
            container.innerHTML = '';
            ipAddresses.forEach((ip, index) => {
                const frameDiv = document.createElement('div');
                frameDiv.className = 'frame-container';
               
                // Style spécial pour 3 interfaces
                if (layout.special === 'three-special' && index === 0) {
                    frameDiv.style.gridRow = 'span 2';
                }
               
                frameDiv.innerHTML = `
                    <span class="frame-label">${ip}</span>
                    <div class="zoom-controls">
                        <button class="zoom-btn" onclick="zoom(${index + 1}, -0.1)">−</button>
                        <span class="zoom-value" id="zoom${index + 1}">100%</span>
                        <button class="zoom-btn" onclick="zoom(${index + 1}, 0.1)">+</button>
                    </div>
                    <div class="frame-wrapper" id="frame${index + 1}">
                        <iframe src="http://${ip}"></iframe>
                    </div>
                `;
               
                container.appendChild(frameDiv);
            });
           
            // Initialiser les niveaux de zoom
            zoomLevels = {};
            for (let i = 1; i <= ipAddresses.length; i++) {
                zoomLevels[i] = 1;
            }
        }
       
        // Retourner à la configuration
        function showConfig() {
            document.body.className = 'config-mode';
            currentMode = 'config';
            renderIPList();
        }
       
        // Fonctions de zoom
        function zoom(frameNum, delta) {
            zoomLevels[frameNum] = Math.max(0.2, Math.min(2, zoomLevels[frameNum] + delta));
            updateZoom(frameNum);
        }
       
        function updateZoom(frameNum) {
            const frame = document.getElementById(`frame${frameNum}`);
            const zoomDisplay = document.getElementById(`zoom${frameNum}`);
            const scale = zoomLevels[frameNum];
           
            frame.style.transform = `scale(${scale})`;
            frame.style.width = `${100 / scale}%`;
            frame.style.height = `${100 / scale}%`;
            zoomDisplay.textContent = `${Math.round(scale * 100)}%`;
        }
       
        function resetAllZoom() {
            for (let i = 1; i <= ipAddresses.length; i++) {
                zoomLevels[i] = 1;
                updateZoom(i);
            }
        }
       
        function zoomAll(delta) {
            for (let i = 1; i <= ipAddresses.length; i++) {
                zoom(i, delta);
            }
        }
    </script>
</body>
</html>


creez un fichier .html ,collez le code enregistrez et lancer avec un navigateur
ESP32Wroom, Triac 40A "BTA40", Source UxIx2, Cumulus 300L 3000W.
Sonde temperature sur radiateur triac mise en route ventilateur a 25°
réactivité 30 seuil -50
2 esp32 pour gestion charge batteries
14 panneaux de 410wcc en autoconso micro-onduleur APS DS3
Suivi sur Domoticz
Répondre


Atteindre :


Utilisateur(s) parcourant ce sujet : 1 visiteur(s)