Messages : 200
Sujets : 14
Inscription : Jun 2024
Réputation :
7
(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 
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
Messages : 285
Sujets : 3
Inscription : Aug 2024
Réputation :
7
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.
Messages : 230
Sujets : 11
Inscription : Jun 2024
Réputation :
11
Il y a 8 heures
(Modification du message : Il y a 8 heures par PhDV61.)
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...
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.
Messages : 200
Sujets : 14
Inscription : Jun 2024
Réputation :
7
(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...

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
Messages : 14
Sujets : 1
Inscription : Jun 2024
Réputation :
3
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
Messages : 32
Sujets : 2
Inscription : Nov 2024
Réputation :
0
Il y a 3 heures
(Modification du message : Il y a 3 heures par glu3.)
Bonjour,
Merci Lucky pour ce partage,
le fichier html c'est une superbe idée et qui économise une adresse IP j'
Bonne journée
 + 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é
Messages : 24
Sujets : 1
Inscription : Sep 2024
Réputation :
0
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
Messages : 200
Sujets : 14
Inscription : Jun 2024
Réputation :
7
Il y a 2 heures
(Modification du message : Il y a 1 heure par lucky.)
(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> 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
Messages : 200
Sujets : 14
Inscription : Jun 2024
Réputation :
7
Il y a 20 minutes
(Modification du message : Il y a 18 minutes par lucky.)
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
|