You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 

513 lines
17 KiB

<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Contrôleur Solaire - Console</title>
<style>
body {
font-family: Arial, sans-serif;
max-width: 900px;
margin: 50px auto;
padding: 20px;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
min-height: 100vh;
}
.container {
background: white;
padding: 40px;
border-radius: 10px;
box-shadow: 0 10px 30px rgba(0,0,0,0.3);
}
h1 {
color: #333;
text-align: center;
margin-bottom: 30px;
}
h2 {
color: #667eea;
border-bottom: 2px solid #667eea;
padding-bottom: 10px;
margin-top: 30px;
}
.nav-links {
text-align: center;
margin-top: 30px;
}
.nav-links a {
display: inline-block;
padding: 10px 20px;
background: #95a5a6;
color: white;
text-decoration: none;
border-radius: 5px;
margin: 5px;
transition: background 0.3s;
}
.nav-links a:hover {
background: #7f8c8d;
}
.logout-btn {
display: inline-block;
padding: 8px 20px;
background: #e74c3c;
color: white;
text-decoration: none;
border-radius: 5px;
font-size: 14px;
transition: background 0.3s;
}
.logout-btn:hover {
background: #c0392b;
}
.header-bar {
text-align: right;
margin-bottom: 20px;
}
.status-bar {
background: #f0f0f0;
padding: 10px 15px;
border-radius: 5px;
margin-top: 20px;
display: flex;
justify-content: space-between;
align-items: center;
font-size: 12px;
color: #555;
}
.data-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
gap: 20px;
margin: 30px 0;
}
.data-card {
background: linear-gradient(135deg, #f5f7fa 0%, #c3cfe2 100%);
padding: 15px;
border-radius: 10px;
box-shadow: 0 4px 6px rgba(0,0,0,0.1);
text-align: center;
transition: transform 0.3s;
}
.data-card:hover {
transform: translateY(-5px);
}
.mode-btn {
padding: 15px 25px;
border: 2px solid #667eea;
background: white;
color: #667eea;
border-radius: 5px;
font-size: 16px;
font-weight: bold;
cursor: pointer;
transition: all 0.3s;
}
.mode-btn:hover {
background: #667eea;
color: white;
}
.mode-btn.active {
background: #667eea;
color: white;
}
.mode-btn.off {
border-color: #e74c3c;
color: #e74c3c;
}
.mode-btn.off.active {
background: #e74c3c;
color: white;
}
.mode-btn.on {
border-color: #27ae60;
color: #27ae60;
}
.mode-btn.on.active {
background: #27ae60;
color: white;
}
.data-card.solar {
background: linear-gradient(135deg, #ffeaa7 0%, #fdcb6e 100%);
}
.data-card.consumption {
background: linear-gradient(135deg, #74b9ff 0%, #0984e3 100%);
color: white;
}
.data-card.heater {
background: linear-gradient(135deg, #ff7675 0%, #d63031 100%);
color: white;
}
.data-card.temperature {
background: linear-gradient(135deg, #a29bfe 0%, #6c5ce7 100%);
color: white;
}
.data-icon {
font-size: 32px;
margin-bottom: 5px;
}
.data-label {
font-size: 14px;
font-weight: bold;
text-transform: uppercase;
margin-bottom: 5px;
opacity: 0.8;
}
.data-value {
font-size: 28px;
font-weight: bold;
margin-bottom: 3px;
}
.data-unit {
font-size: 14px;
opacity: 0.9;
}
.status-indicator {
display: flex;
align-items: center;
gap: 8px;
}
.status-dot {
width: 10px;
height: 10px;
border-radius: 50%;
background: #27ae60;
animation: pulse 2s infinite;
}
.status-dot.red {
background: #e74c3c;
}
.status-dot.orange {
background: #f39c12;
}
@keyframes pulse {
0%, 100% { opacity: 1; }
50% { opacity: 0.5; }
}
.progress-bar {
width: 100%;
height: 30px;
background: #ecf0f1;
border-radius: 15px;
overflow: hidden;
margin-top: 15px;
position: relative;
}
.progress-fill {
height: 100%;
background: linear-gradient(90deg, #e74c3c 0%, #c0392b 100%);
transition: width 0.5s ease;
display: flex;
align-items: center;
justify-content: center;
color: white;
font-weight: bold;
font-size: 14px;
}
.mode-selector {
display: flex;
gap: 10px;
justify-content: center;
flex-wrap: wrap;
margin-top: 20px;
}
</style>
</head>
<body>
<div class="container">
<div class="header-bar">
<a href="/logout" class="logout-btn">🔓 Déconnexion</a>
</div>
<h1> Contrôleur Solaire pour Chauffe-Eau</h1>
<div id="statusBar" class="status-bar">
<div class="status-indicator">
<div class="status-dot" id="statusDot"></div>
<span id="statusText">Chargement...</span>
</div>
<div style="display: flex; gap: 18px; align-items: center;">
<span id="sunriseStatus">🌅 --:--</span>
<span id="sunsetStatus">🌇 --:--</span>
<span id="lastUpdate">Dernière mise à jour: --</span>
</div>
</div>
<div id="dataDisplay" style="display:none;">
<h2>📊 Données en temps réel</h2>
<div class="data-grid">
<div class="data-card solar">
<div class="data-icon"></div>
<div class="data-label">Production Solaire</div>
<div class="data-value" id="solarValue">--</div>
<div class="data-unit">Watts</div>
</div>
<div class="data-card consumption">
<div class="data-icon"></div>
<div class="data-label">Consommation</div>
<div class="data-value" id="consumptionValue">--</div>
<div class="data-unit">Watts</div>
</div>
<div class="data-card heater">
<div class="data-icon">🔥</div>
<div class="data-label">Puissance Chauffe-Eau</div>
<div class="data-value" id="heaterValue">--</div>
<div class="data-unit">%</div>
<div class="progress-bar">
<div class="progress-fill" id="heaterProgress" style="width: 0%"></div>
</div>
</div>
<div class="data-card temperature">
<div class="data-icon">🌡</div>
<div class="data-label">Température Eau</div>
<div class="data-value" id="temperatureValue">--</div>
<div class="data-unit">°C</div>
</div>
</div>
<h2>🎛 Mode de Fonctionnement</h2>
<div class="mode-selector">
<button class="mode-btn" data-bit="16" onclick="toggleMode(16)">🟢 ON</button>
<button class="mode-btn" data-bit="8" onclick="toggleMode(8)">🌙 NUIT</button>
<button class="mode-btn" data-bit="4" onclick="toggleMode(4)"> SOLEIL</button>
<button class="mode-btn" data-bit="2" onclick="toggleMode(2)">☼ JOUR</button>
<button class="mode-btn" data-bit="0" onclick="toggleMode(0)">🔴 OFF</button>
</div>
<div class="nav-links">
<a href="/settings.html" > Configuration</a>
<a href="/update.html" >📦 Mise à jour OTA</a>
</div>
</div>
</div>
<script>
let refreshInterval;
let currentMode = 0; // Mode par défaut (OFF)
function showError(message) {
const statusDot = document.getElementById('statusDot');
const statusText = document.getElementById('statusText');
statusDot.className = 'status-dot red';
statusText.textContent = message;
setTimeout(() => {
updateStatusBar();
}, 5000);
}
function updateStatusBar() {
const statusDot = document.getElementById('statusDot');
const statusText = document.getElementById('statusText');
// Vérifier l'état d'erreur Enphase
if (window.enphaseConnectionError) {
statusDot.className = 'status-dot orange';
statusText.textContent = 'Erreur connexion Enphase';
} else {
statusDot.className = 'status-dot';
statusText.textContent = 'Connecté';
}
}
function updateModeDisplay() {
// Mettre à jour l'affichage des boutons actifs
document.querySelectorAll('.mode-btn').forEach(btn => {
btn.classList.remove('active', 'on', 'off');
});
if (currentMode === 0) {
// Mode OFF
document.querySelector('[data-bit="0"]').classList.add('active', 'off');
} else if (currentMode === 16) {
// Mode ON
document.querySelector('[data-bit="16"]').classList.add('active', 'on');
} else {
// Combinaisons NUIT, SOLEIL, JOUR
if (currentMode & 8) {
document.querySelector('[data-bit="8"]').classList.add('active');
}
if (currentMode & 4) {
document.querySelector('[data-bit="4"]').classList.add('active');
}
if (currentMode & 2) {
document.querySelector('[data-bit="2"]').classList.add('active');
}
}
}
function toggleMode(bitValue) {
let newMode;
if (bitValue === 16) {
// Bouton ON: activer uniquement ON (16)
newMode = 16;
} else if (bitValue === 0) {
// Bouton OFF: désactiver tout (0)
newMode = 0;
} else {
// Boutons NUIT (8), SOLEIL (4), JOUR (2)
// Si ON ou OFF est actif, commencer avec 0
if (currentMode === 16 || currentMode === 0) {
newMode = bitValue;
} else {
// Toggle le bit
newMode = currentMode ^ bitValue;
// Si tout est désactivé, mettre à 0 (OFF)
if (newMode === 0) {
newMode = 0;
}
}
}
currentMode = newMode;
updateModeDisplay();
// Envoyer le mode au serveur
fetch('/api/mode', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({ mode: currentMode })
})
.then(response => {
if (response.status === 401) {
// Session expirée, rediriger vers login
window.location.href = '/login';
return;
}
if (!response.ok) {
throw new Error('Erreur lors du changement de mode');
}
return response.json();
})
.then(data => {
if (!data) return; // Éviter l'erreur si redirection 401
console.log('Mode changé:', currentMode);
})
.catch(error => {
console.error('Erreur:', error);
showError('Erreur lors du changement de mode');
});
}
function loadMode() {
fetch('/api/mode')
.then(response => {
if (response.status === 401) {
// Session expirée, rediriger vers login
window.location.href = '/login';
return;
}
return response.json();
})
.then(data => {
if (!data) return; // Éviter l'erreur si redirection 401
if (typeof data.mode === 'number') {
currentMode = data.mode;
updateModeDisplay();
}
})
.catch(error => {
console.error('Erreur chargement mode:', error);
});
}
function updateData() {
fetch('/api/data')
.then(response => {
if (response.status === 401) {
window.location.href = '/login';
return;
}
if (!response.ok) {
throw new Error('Erreur réseau');
}
return response.json();
})
.then(data => {
if (!data) return;
// Mettre à jour le flag global d'erreur Enphase
window.enphaseConnectionError = !!data.enphase_connection_error;
updateStatusBar();
document.getElementById('dataDisplay').style.display = 'block';
// Mettre à jour la production solaire
document.getElementById('solarValue').textContent =
data.solar_production.toFixed(0);
// Mettre à jour la consommation
document.getElementById('consumptionValue').textContent =
data.power_consumption.toFixed(0);
// Mettre à jour la puissance du chauffe-eau
document.getElementById('heaterValue').textContent =
data.heater_power;
// Mettre à jour la température de l'eau
document.getElementById('temperatureValue').textContent =
data.water_temperature ? data.water_temperature.toFixed(1) : '--';
// Mettre à jour les heures de lever/coucher du soleil
document.getElementById('sunriseStatus').textContent = '🌅 ' + (data.sunrise_time || '--:--');
document.getElementById('sunsetStatus').textContent = '🌇 ' + (data.sunset_time || '--:--');
// Mettre à jour la barre de progression
const progressBar = document.getElementById('heaterProgress');
progressBar.style.width = data.heater_power + '%';
// Mettre à jour l'heure de dernière mise à jour
const now = new Date();
const timeString = now.toLocaleTimeString('fr-FR');
document.getElementById('lastUpdate').textContent =
'Dernière mise à jour: ' + timeString;
})
.catch(error => {
console.error('Erreur:', error);
showError('Erreur lors de la récupération des données');
});
}
// Charger les données au démarrage
window.addEventListener('DOMContentLoaded', () => {
loadMode(); // Charger le mode actuel
updateData();
// Rafraîchir toutes les 2 secondes
refreshInterval = setInterval(updateData, 2000);
});
// Nettoyer l'intervalle quand on quitte la page
window.addEventListener('beforeunload', () => {
if (refreshInterval) {
clearInterval(refreshInterval);
}
});
</script>
</body>
</html>