Note de ce sujet :
  • Moyenne : 0 (0 vote(s))
  • 1
  • 2
  • 3
  • 4
  • 5
Petit prog pour esp32
#31
(Il y a 9 heures)Philmaz a écrit :
(Il y a 9 heures)lucky a écrit :
(Il y a 9 heures)Philmaz a écrit : Desolé mais tjrs pas compris Undecided
Pass wifi ?
Voilà comment je fais.
Ca veut dire que esp ne se connecte pas au réseau local ?
J'ai essayé avec plusieurs esp.

oui c est ca , et apres sauvegarde l esp va redémarrer et sur la console la nouvelle ip de connexion sera affichée
si revient en mode AP c est qu il ne se connecte pas au reseau ......(probleme wifi ou autre )

Pour moi ça ne se passe pas comme pour vous autres.
La seule adresse qui sort c'est
 AP IP : 192.168.4.1
Serveur démarré

essaye avec "esp32 dev module" dans IDE
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
#32
j'ai essayé de mette mon home assistant (192.168.1.XX:8123) dans la console, mais cela ne fonctionne pas..
je pense qu'il ne prend pas en compte le 8123.
Répondre
#33
La version HTML est simple à mettre en oeuvre et à utiliser. C'est un add-on très sympathique. Bravo.
quels outils sont nécessaires pour développer graphiquement et simplement de telles pages HTML ?
C'est quand même un peu indigeste tel quel, quand on n'est pas né dedans...
Wink
V14.21 modifiée. 1 serveur UxIx3, 1 Linky de référence, 1 client Triac CE tampon + 1 client SSR CE tampon + 1 client SSR sur CE tri + 3 clients SSR sur 3 radiateurs bain d'huile d'appoint. Variateurs de fréquence Piscine.
8 panneaux (3 SE 2 S, 3 SO ) 425Wc sur 4 HM800 produisent 20kWh par jour au 16 Mars.
Répondre
#34
(Il y a 8 heures)PhDV61 a écrit : La version HTML est simple à mettre en oeuvre et à utiliser. C'est un add-on très sympathique. Bravo.
quels outils sont nécessaires pour développer graphiquement et simplement de telles pages HTML ?
C'est quand même un peu indigeste tel quel, quand on n'est pas né dedans...
Wink

l aide de IA Claude n y est pas pour rien ........
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
#35
Bonjour Lucky,

Bien pratique pour affiner les réglages de plusieurs routeurs.
Pour le zoom , ça serait utile de le mettre à 50 d'office pour 4 vues
merci du partage
Répondre
#36
Bonjour,

Merci Lucky pour ce partage,
le fichier html c'est une superbe idée et qui économise une adresse IP  j' Heart

Bonne journée 

At + Michel
14 panneaux 425W avec MO IQ8AC Installés par EDF ENR 
Routeur F1 ATB v 14.23, une sonde JSY MK 194T sur Esp32 n° 1, 1 relais SSR sur Eps32 n° 2 au niveau du CE 3kW et 1 Esp32 Ecran.
Merci André  Wink
Répondre
#37
Merci lucky,

Je ne sais pas si cette solution peut aussi embarquer une page de stats mais c'est déjà très bien car certains dont moi souhaitent voir la production solaire sur l'affichage de leur routeur JSY.

Je n'ai pas le temps de tester la version esp32 et dans l'immédiat j'utilise sur mon mobile la page html (en dehors de la maison aussi même si pas trop sécure), rien que çà c'est top!

Encore merci,
très bonne journée, Paul
Routeur UxIx2 (Maison et CE) - Dimmer Robotdyn avec triac BTA40 - Sonde T° sur CE - 4 PV 400Wc sur 2 PowerStream
Répondre
#38
(Il y a 8 heures)grostoto a écrit : j'ai essayé de mette mon home assistant (192.168.1.XX:8123) dans la console, mais cela ne fonctionne pas..
je pense qu'il ne prend pas en compte le 8123.

alors homeassistant bloque les connexions iframe ....c est foutu pour HA pour l instant

re edition
pour HA : ajouter dans config.yam 

http:
  cors_allowed_origins:
    - "*"
  use_x_frame_options: false

ou

http:
  use_x_frame_options: false
  cors_allowed_origins:
    - "*"
  trusted_proxies:
    - 127.0.0.1
    - ::1

pour l instant version html

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;
        }
       
        .help-text {
            font-size: 12px;
            color: #666;
            margin-top: 15px;
            padding: 10px;
            background-color: #f0f0f0;
            border-radius: 5px;
            line-height: 1.6;
        }
       
        /* 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;
            max-width: 70%;
            overflow: hidden;
            text-overflow: ellipsis;
            white-space: nowrap;
        }
       
        /* 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;
        }
       
        /* Message d'erreur iframe */
        .iframe-error {
            position: absolute;
            top: 50%;
            left: 50%;
            transform: translate(-50%, -50%);
            background: #f8d7da;
            color: #721c24;
            padding: 20px;
            border-radius: 8px;
            text-align: center;
            max-width: 80%;
            z-index: 20;
            display: none;
        }
       
        .iframe-error h3 {
            margin: 0 0 10px 0;
            font-size: 16px;
        }
       
        .iframe-error p {
            margin: 5px 0;
            font-size: 14px;
        }
       
        .iframe-error code {
            background: rgba(0,0,0,0.1);
            padding: 2px 5px;
            border-radius: 3px;
            font-size: 12px;
        }
    </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 ci-dessous pour commencer.</p>
            </div>
           
            <div id="ipList" class="ip-list"></div>
           
            <button class="add-btn" onclick="addIP()">+ Ajouter une interface</button>
           
            <div class="help-text">
                <strong>Formats acceptés :</strong><br>
                • IP : 192.168.1.100<br>
                • Domaine : example.com<br>
                • Sous-domaine : app.example.com<br>
                • URL complète : https://example.com/dashboard<br>
                • Port personnalisé : 192.168.1.100:8080 ou example.com:3000
            </div>
           
            <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 adresses - MODIFIÉE AUTOMATIQUEMENT
        let addresses = ['192.168.1.145'];
        let zoomLevels = {};
        let currentMode = 'config';
       
        // Initialisation
        window.onload = function() {
            // Si des adresses sont déjà configurées dans le fichier
            if (addresses.length > 0 && addresses[0] !== '') {
                document.getElementById('welcomeMessage').style.display = 'none';
                // Aller directement au dashboard si configuré
                if (addresses.length > 1 || (addresses.length === 1 && addresses[0] !== '192.168.1.145')) {
                    showDashboard();
                }
            }
           
            renderIPList();
        };
       
        // Afficher la liste des adresses
        function renderIPList() {
            const container = document.getElementById('ipList');
            container.innerHTML = '';
           
            addresses.forEach((address, index) => {
                const div = document.createElement('div');
                div.className = 'ip-group';
                div.innerHTML = `
                    <span class="ip-number">${index + 1}.</span>
                    <input type="text" id="address${index}" value="${address}" placeholder="Ex: 192.168.1.100, example.com, https://app.example.com">
                    ${addresses.length > 1 ? `<button class="remove-btn" onclick="removeIP(${index})">Supprimer</button>` : ''}
                `;
                container.appendChild(div);
            });
           
            updateLayoutPreview();
        }
       
        // Ajouter une adresse
        function addIP() {
            addresses.push('');
            renderIPList();
        }
       
        // Supprimer une adresse
        function removeIP(index) {
            addresses.splice(index, 1);
            renderIPList();
        }
       
        // Mettre à jour l'aperçu
        function updateLayoutPreview() {
            const preview = document.getElementById('layoutPreview');
            const count = addresses.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 et formater une adresse
        function validateAndFormatAddress(address) {
            // Supprimer les espaces
            address = address.trim();
           
            if (!address) {
                return { valid: false, error: "L'adresse ne peut pas être vide" };
            }
           
            // Si c'est déjà une URL complète avec protocole
            if (address.match(/^https?:\/\//)) {
                return { valid: true, formatted: address, display: address };
            }
           
            // Vérifier si c'est une IP valide
            const ipPattern = /^(?:(?: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]?)(?::\d{1,5})?$/;
            if (ipPattern.test(address)) {
                return {
                    valid: true,
                    formatted: `http://${address}`,
                    display: address
                };
            }
           
            // Vérifier si c'est un domaine ou sous-domaine valide
            const domainPattern = /^([a-zA-Z0-9-]+\.)*[a-zA-Z0-9-]+\.[a-zA-Z]{2,}(:\d{1,5})?(\/.*)?$/;
            const localhostPattern = /^localhost(:\d{1,5})?(\/.*)?$/;
           
            if (domainPattern.test(address) || localhostPattern.test(address)) {
                return {
                    valid: true,
                    formatted: `http://${address}`,
                    display: address
                };
            }
           
            // Si c'est juste un nom sans extension (peut-être un hostname local)
            if (/^[a-zA-Z0-9-]+(:\d{1,5})?(\/.*)?$/.test(address)) {
                return {
                    valid: true,
                    formatted: `http://${address}`,
                    display: address
                };
            }
           
            return { valid: false, error: "Format d'adresse non valide" };
        }
       
        // 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 adresses
            const validatedAddresses = [];
            for (let i = 0; i < addresses.length; i++) {
                const addressInput = document.getElementById(`address${i}`);
                const addressValue = addressInput ? addressInput.value.trim() : addresses[i];
               
                const validation = validateAndFormatAddress(addressValue);
                if (!validation.valid) {
                    showMessage(`Adresse n°${i + 1} : ${validation.error}`, 'error');
                    return;
                }
                validatedAddresses.push(validation);
            }
           
            addresses = validatedAddresses.map(v => v.display);
           
            // Construire le dashboard avec les adresses formatées
            buildDashboard(validatedAddresses);
           
            // 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 adresses
            const newAddresses = [];
            for (let i = 0; i < addresses.length; i++) {
                const addressValue = document.getElementById(`address${i}`).value.trim();
               
                const validation = validateAndFormatAddress(addressValue);
                if (!validation.valid) {
                    showMessage(`Adresse n°${i + 1} : ${validation.error}`, 'error');
                    return;
                }
                newAddresses.push(addressValue);
            }
           
            // 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 addresses = \[[^\]]*\];/,
                `let addresses = ${JSON.stringify(newAddresses)};`
            );
           
            // 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(validatedAddresses) {
            const container = document.getElementById('dashboardContainer');
            const layout = calculateGridLayout(validatedAddresses.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 = '';
            validatedAddresses.forEach((addressInfo, 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" title="${addressInfo.display}">${addressInfo.display}</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="${addressInfo.formatted}"
                            sandbox="allow-same-origin allow-scripts allow-popups allow-forms allow-modals allow-downloads allow-presentation allow-top-navigation"
                            referrerpolicy="no-referrer"
                            id="iframe${index + 1}"
                            onerror="handleIframeError(${index + 1}, '${addressInfo.display}')"
                        ></iframe>
                    </div>
                    <div class="iframe-error" id="error${index + 1}">
                        <h3>⚠️ Impossible de charger ${addressInfo.display}</h3>
                        <p>Cette interface ne peut pas être affichée dans une iframe.</p>
                        <p><small>Pour Home Assistant, ajoutez dans configuration.yaml :</small></p>
                        <code>http:<br>&nbsp;&nbsp;use_x_frame_options: false</code>
                        <p style="margin-top: 15px;">
                            <a href="${addressInfo.formatted}" target="_blank" style="color: #0056b3;">Ouvrir dans un nouvel onglet →</a>
                        </p>
                    </div>
                `;
               
                container.appendChild(frameDiv);
            });
           
            // Initialiser les niveaux de zoom
            zoomLevels = {};
            for (let i = 1; i <= validatedAddresses.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 <= addresses.length; i++) {
                zoomLevels[i] = 1;
                updateZoom(i);
            }
        }
       
        function zoomAll(delta) {
            for (let i = 1; i <= addresses.length; i++) {
                zoom(i, delta);
            }
        }
       
        // Gestion des erreurs d'iframe
        function handleIframeError(frameNum, address) {
            console.error(`Erreur de chargement pour l'iframe ${frameNum}: ${address}`);
            const errorDiv = document.getElementById(`error${frameNum}`);
            if (errorDiv) {
                errorDiv.style.display = 'block';
            }
        }
       
        // Détection des erreurs de chargement après un délai
        function checkIframeLoading() {
            if (currentMode === 'dashboard') {
                for (let i = 1; i <= addresses.length; i++) {
                    const iframe = document.getElementById(`iframe${i}`);
                    if (iframe) {
                        iframe.addEventListener('load', function() {
                            try {
                                // Tenter d'accéder au document pour vérifier si c'est bloqué
                                const iframeDoc = iframe.contentDocument || iframe.contentWindow.document;
                                // Si on arrive ici, l'iframe est chargée correctement
                            } catch (e) {
                                // Erreur de cross-origin, probablement bloqué
                                console.warn(`L'iframe ${i} pourrait être bloquée par des politiques de sécurité`);
                            }
                        });
                    }
                }
            }
        }
       
        // Appeler la vérification après le chargement du dashboard
        setTimeout(checkIframeLoading, 1000);
    </script>
</body>
</html>
j essaye de voir pour esp ....
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
#39
ayé version esp et meme homeassistant

juste pour homeassistant placez dans congiguration.yam
http:
  cors_allowed_origins:
    - "*"
  use_x_frame_options: false

et redémarrer homeassistant

Code :
// ESP32 Dashboard avec mode AP pour configuration initiale - Support URLs complètes
#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 url1[128];        // URLs complètes au lieu d'IP simples
  char url2[128];
  char url3[128];
  char url4[128];
  char name1[32];
  char name2[32];
  char name3[32];
  char name4[32];
};

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

// Variables globales pour le test d'IP
String testingIP = "";
bool isTestingConnection = false;
unsigned long testStartTime = 0;

// 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;
        }
        .url-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;
        }
        .help-text {
            font-size: 12px;
            color: #aaa;
            margin-top: 5px;
        }
        .example {
            font-family: monospace;
            background: rgba(255,255,255,0.1);
            padding: 2px 5px;
            border-radius: 3px;
        }
    </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 avec leurs URLs complètes</p>
            <div class="help-text">
                Exemples d'URLs valides:
                <ul style="margin: 5px 0;">
                    <li><span class="example">http://192.168.1.100</span> (IP simple)</li>
                    <li><span class="example">http://192.168.1.100:8080</span> (avec port)</li>
                    <li><span class="example">https://monserveur.local</span> (domaine)</li>
                    <li><span class="example">http://serveur.com:3000/dashboard</span> (URL complète)</li>
                </ul>
            </div>
           
            <div class="url-group">
                <input type="text" id="url1" placeholder="URL Interface 1 (ex: http://192.168.1.100:8080)">
                <input type="text" id="name1" placeholder="Nom 1">
            </div>
           
            <div class="url-group">
                <input type="text" id="url2" placeholder="URL Interface 2 (ex: https://serveur.local)">
                <input type="text" id="name2" placeholder="Nom 2">
            </div>
           
            <div class="url-group">
                <input type="text" id="url3" placeholder="URL Interface 3 (optionnel)">
                <input type="text" id="name3" placeholder="Nom 3">
            </div>
           
            <div class="url-group">
                <input type="text" id="url4" placeholder="URL 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>
        // Fonction pour valider une URL
        function validateURL(url) {
            if (!url) return false;
           
            // Accepter les URLs simples (IP ou domaine) sans protocole
            if (!url.startsWith('http://') && !url.startsWith('https://')) {
                // Ajouter http:// par défaut
                url = 'http://' + url;
            }
           
            try {
                new URL(url);
                return true;
            } catch (e) {
                return false;
            }
        }
       
        // Fonction pour normaliser une URL
        function normalizeURL(url) {
            if (!url) return '';
           
            // Si pas de protocole, ajouter http://
            if (!url.startsWith('http://') && !url.startsWith('https://')) {
                url = 'http://' + url;
            }
           
            return url;
        }
       
        // 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 URLs et noms
                    if (data.url1) document.getElementById('url1').value = data.url1;
                    if (data.url2) document.getElementById('url2').value = data.url2;
                    if (data.url3) document.getElementById('url3').value = data.url3;
                    if (data.url4) document.getElementById('url4').value = data.url4;
                    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;
                   
                    // 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() {
            // Normaliser les URLs
            const url1 = normalizeURL(document.getElementById('url1').value);
            const url2 = normalizeURL(document.getElementById('url2').value);
            const url3 = normalizeURL(document.getElementById('url3').value);
            const url4 = normalizeURL(document.getElementById('url4').value);
           
            const config = {
                ssid: document.getElementById('ssid').value,
                password: document.getElementById('password').value,
                dashUser: document.getElementById('dashUser').value,
                dashPass: document.getElementById('dashPass').value,
                url1: url1,
                url2: url2,
                url3: url3,
                url4: url4,
                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.url1 || !config.url2) {
                alert('Veuillez configurer au moins 2 interfaces');
                return;
            }
           
            // Valider les URLs
            if (!validateURL(config.url1) || !validateURL(config.url2)) {
                alert('Veuillez entrer des URLs valides pour les interfaces 1 et 2');
                return;
            }
           
            if (config.url3 && !validateURL(config.url3)) {
                alert('URL invalide pour l\'interface 3');
                return;
            }
           
            if (config.url4 && !validateURL(config.url4)) {
                alert('URL invalide pour l\'interface 4');
                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.json())
            .then(data => {
                if (data.status === 'testing') {
                    document.getElementById('message').innerHTML =
                        '<div class="info">✅ Configuration sauvegardée!</div>' +
                        '<div class="loading">Test de connexion au réseau...</div>';
                   
                    // Commencer à vérifier l'IP
                    checkIPStatus(config.ssid);
                }
            })
            .catch(err => {
                document.getElementById('message').innerHTML =
                    '<div class="info" style="background: #ff4444;">Erreur de sauvegarde: ' + err + '</div>';
            });
        }
       
        function checkIPStatus(ssid) {
            const checkInterval = setInterval(() => {
                fetch('/checkip')
                    .then(response => response.json())
                    .then(data => {
                        if (data.status === 'connected' && data.ip) {
                            clearInterval(checkInterval);
                            document.getElementById('message').innerHTML =
                                '<div class="info">✅ Configuration sauvegardée!</div>' +
                                '<div class="info" style="background: #4CAF50; margin-top: 10px;">' +
                                '? Nouvelle adresse IP : <a href="http://' + data.ip + '" style="color: white; font-size: 24px; font-weight: bold;">' + data.ip + '</a></div>' +
                                '<div class="info">Cliquez sur l\'IP ou notez cette adresse! Redémarrage dans 10 secondes...</div>' +
                                '<div class="info">Connectez-vous au WiFi "' + ssid +
                                '" et accédez à <a href="http://' + data.ip + '" style="color: white; font-weight: bold;">http://' + data.ip + '</a></div>';
                           
                            // Redémarrer après 10 secondes
                            setTimeout(() => {
                                window.location.href = 'http://' + data.ip;
                            }, 10000);
                           
                        } else if (data.status === 'failed') {
                            clearInterval(checkInterval);
                            document.getElementById('message').innerHTML =
                                '<div class="info" style="background: #ff4444;">❌ Impossible de se connecter au réseau!</div>' +
                                '<div class="info">Vérifiez le mot de passe WiFi et réessayez.</div>' +
                                '<div class="info">Redémarrage dans 10 secondes...</div>';
                        }
                        // Si status est "testing", on continue à vérifier
                    })
                    .catch(err => {
                        // Si erreur, c'est peut-être que l'ESP32 a redémarré
                        clearInterval(checkInterval);
                    });
            }, 1000); // Vérifier chaque seconde
        }
    </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 pour supporter les URLs complètes
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;
        }
       
        /* Indicateur d'erreur de chargement */
        .error-indicator {
            position: absolute;
            top: 50%;
            left: 50%;
            transform: translate(-50%, -50%);
            background: rgba(255,0,0,0.1);
            color: #ff4444;
            padding: 20px;
            border-radius: 5px;
            text-align: center;
            display: none;
        }
    </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" sandbox="allow-scripts allow-same-origin allow-forms allow-popups"></iframe>
            </div>
            <div class="error-indicator" id="error1">
                ⚠️ Impossible de charger cette interface
            </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" sandbox="allow-scripts allow-same-origin allow-forms allow-popups"></iframe>
            </div>
            <div class="error-indicator" id="error2">
                ⚠️ Impossible de charger cette interface
            </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" sandbox="allow-scripts allow-same-origin allow-forms allow-popups"></iframe>
            </div>
            <div class="error-indicator" id="error3">
                ⚠️ Impossible de charger cette interface
            </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" sandbox="allow-scripts allow-same-origin allow-forms allow-popups"></iframe>
            </div>
            <div class="error-indicator" id="error4">
                ⚠️ Impossible de charger cette interface
            </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);
            }
        });
       
        // Gestion des erreurs de chargement des iframes
        function handleFrameError(frameNum) {
            document.getElementById(`error${frameNum}`).style.display = 'block';
            setTimeout(() => {
                document.getElementById(`error${frameNum}`).style.display = 'none';
            }, 5000);
        }
       
        // 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[`url${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[`url${i}`]) {
                    const container = document.getElementById(`frame${frameIndex}-container`);
                    const frame = document.getElementById(`frame${frameIndex}`);
                    const label = document.getElementById(`label${frameIndex}`);
                   
                    container.classList.add('active');
                   
                    // Utiliser l'URL complète telle quelle
                    frame.src = config[`url${i}`];
                    label.textContent = config[`name${i}`] || `Interface ${i}`;
                   
                    // Ajouter gestionnaire d'erreur
                    frame.onerror = () => handleFrameError(frameIndex);
                   
                    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;
   
    // Initialiser la config avec des valeurs vides
    memset(&config, 0, sizeof(Config));
    strcpy(config.magic, "");
    strcpy(config.ssid, "");
    strcpy(config.password, "");
    strcpy(config.dashUser, "");
    strcpy(config.dashPass, "");
    strcpy(config.url1, "");
    strcpy(config.url2, "");
    strcpy(config.url3, "");
    strcpy(config.url4, "");
    strcpy(config.name1, "");
    strcpy(config.name2, "");
    strcpy(config.name3, "");
    strcpy(config.name4, "");
  }
}

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.url1, doc["url1"]);
    strcpy(config.url2, doc["url2"]);
    strcpy(config.url3, doc["url3"] | "");
    strcpy(config.url4, doc["url4"] | "");
    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();
   
    // Démarrer le test de connexion
    isTestingConnection = true;
    testStartTime = millis();
    testingIP = "";
   
    // Mode AP+STA
    WiFi.mode(WIFI_AP_STA);
    WiFi.softAP("RMS_Dashboard_Config", "rms12345");
    WiFi.begin(config.ssid, config.password);
   
    Serial.println("Test de connexion au nouveau réseau...");
   
    // Répondre immédiatement
    server.send(200, "application/json", "{\"status\":\"testing\"}");
  }
}

// Nouvelle route pour vérifier le statut
void handleCheckIP() {
  String response = "{";
 
  if (isTestingConnection) {
    if (WiFi.status() == WL_CONNECTED && testingIP.isEmpty()) {
      testingIP = WiFi.localIP().toString();
      Serial.print("IP obtenue: ");
      Serial.println(testingIP);
    }
   
    if (!testingIP.isEmpty()) {
      response += "\"status\":\"connected\",\"ip\":\"" + testingIP + "\"";
    } else if (millis() - testStartTime > 10000) {
      response += "\"status\":\"failed\"";
      isTestingConnection = false;
    } else {
      response += "\"status\":\"testing\"";
    }
  } else {
    response += "\"status\":\"idle\"";
  }
 
  response += "}";
  server.send(200, "application/json", response);
}

void handleConfig() {
  String json = "{";
  json += "\"ssid\":\"" + String(config.ssid) + "\","; 
  json += "\"url1\":\"" + String(config.url1) + "\",";
  json += "\"url2\":\"" + String(config.url2) + "\",";
  json += "\"url3\":\"" + String(config.url3) + "\",";
  json += "\"url4\":\"" + String(config.url4) + "\",";
  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("/checkip", handleCheckIP);  // Route pour vérifier l'IP
  server.on("/networkinfo", handleNetworkInfo);
 
  server.begin();
  Serial.println("Serveur démarré");
}

void loop() {
  server.handleClient();
 
  // Si on est en train de tester la connexion, redémarrer après 15 secondes
  if (isTestingConnection && millis() - testStartTime > 15000) {
    ESP.restart();
  }
}
voili
   
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 : 2 visiteur(s)