355 lines
12 KiB
JavaScript
355 lines
12 KiB
JavaScript
let isOnline = true;
|
|
let lastHeartbeat = Date.now();
|
|
const TIMEOUT_MS = 5000;
|
|
|
|
const statusDot = document.getElementById('system-status');
|
|
const statusText = document.getElementById('status-text');
|
|
|
|
const tempEl = document.getElementById('temp-val');
|
|
const humEl = document.getElementById('hum-val');
|
|
|
|
const apiTempEl = document.getElementById('api-temp');
|
|
const apiDescEl = document.getElementById('api-desc');
|
|
const apiIconEl = document.getElementById('api-icon');
|
|
|
|
const batValEl = document.getElementById('bat-val');
|
|
const batCircleEl = document.getElementById('bat-circle');
|
|
const batAlertBadge = document.getElementById('bat-alert-badge');
|
|
const batAlertIcon = document.getElementById('bat-alert-icon');
|
|
const batAlertText = document.getElementById('bat-alert');
|
|
|
|
const MAX_POWER = 100;
|
|
const gaugeCircle = document.querySelector('.gauge-fill circle');
|
|
const halfCircumference = 283 / 2;
|
|
|
|
let envChart = null;
|
|
let energyChart = null;
|
|
|
|
function getWeatherDetails(code) {
|
|
if (code === 0) return { desc: "Dégagé", icon: "bi-brightness-high", color: "text-warning" };
|
|
if (code >= 1 && code <= 3) return { desc: "Nuageux", icon: "bi-cloud", color: "text-light" };
|
|
if (code >= 45 && code <= 48) return { desc: "Brouillard", icon: "bi-cloud-haze", color: "text-secondary" };
|
|
if (code >= 51 && code <= 67) return { desc: "Pluie", icon: "bi-cloud-rain", color: "text-info" };
|
|
if (code >= 71 && code <= 77) return { desc: "Neige", icon: "bi-cloud-snow", color: "text-white" };
|
|
if (code >= 95) return { desc: "Orage", icon: "bi-cloud-lightning", color: "text-danger" };
|
|
return { desc: "Inconnu", icon: "bi-thermometer", color: "text-muted" };
|
|
}
|
|
|
|
async function fetchRealWeather() {
|
|
try {
|
|
const lat = 48.8566;
|
|
const lon = 2.3522;
|
|
const url = `https://api.open-meteo.com/v1/forecast?latitude=${lat}&longitude=${lon}¤t=temperature_2m,weather_code`;
|
|
|
|
const response = await fetch(url);
|
|
const data = await response.json();
|
|
|
|
const temp = data.current.temperature_2m;
|
|
const code = data.current.weather_code;
|
|
const weather = getWeatherDetails(code);
|
|
|
|
apiTempEl.innerText = temp + "°C";
|
|
apiDescEl.innerText = weather.desc;
|
|
apiIconEl.className = "bi " + weather.icon + " " + weather.color + " display-4 me-3";
|
|
} catch (error) {
|
|
console.error("Erreur Météo:", error);
|
|
}
|
|
}
|
|
|
|
function updateBattery(percent, alertTextFromApi = null) {
|
|
batValEl.innerText = percent + "%";
|
|
let color = "#34d399";
|
|
let shadowColor = "rgba(52, 211, 153, 0.4)";
|
|
|
|
if (alertTextFromApi && alertTextFromApi !== "none") {
|
|
color = "#ef4444";
|
|
shadowColor = "rgba(239, 68, 68, 0.6)";
|
|
batAlertText.innerText = alertTextFromApi;
|
|
batAlertIcon.className = "bi bi-exclamation-triangle-fill text-danger";
|
|
batAlertBadge.className = "badge bg-dark border border-danger text-secondary w-100 py-2";
|
|
} else if (percent <= 20) {
|
|
color = "#ef4444";
|
|
shadowColor = "rgba(239, 68, 68, 0.6)";
|
|
batAlertText.innerText = "Niveau critique";
|
|
batAlertIcon.className = "bi bi-exclamation-triangle-fill text-danger";
|
|
batAlertBadge.className = "badge bg-dark border border-danger text-secondary w-100 py-2";
|
|
} else if (percent <= 50) {
|
|
color = "#fbbf24";
|
|
shadowColor = "rgba(251, 191, 36, 0.5)";
|
|
batAlertText.innerText = "Niveau moyen";
|
|
batAlertIcon.className = "bi bi-exclamation-circle text-warning";
|
|
batAlertBadge.className = "badge bg-dark border border-warning text-secondary w-100 py-2";
|
|
} else {
|
|
batAlertText.innerText = "Système Normal";
|
|
batAlertIcon.className = "bi bi-check-circle text-success";
|
|
batAlertBadge.className = "badge bg-dark border border-secondary text-secondary w-100 py-2";
|
|
}
|
|
|
|
const angle = percent * 3.6;
|
|
batCircleEl.style.background = `conic-gradient(${color} ${angle}deg, #0f172a 0deg)`;
|
|
batCircleEl.style.boxShadow = `0 0 15px ${shadowColor}, inset 0 0 15px ${shadowColor}`;
|
|
batValEl.style.textShadow = `0 0 10px ${shadowColor}`;
|
|
}
|
|
|
|
function updateSolar(voltage, current, power, lux) {
|
|
const v = Number(voltage).toFixed(1);
|
|
const c = Number(current).toFixed(2);
|
|
const p = Number(power).toFixed(1);
|
|
const l = Math.floor(Number(lux));
|
|
|
|
const efficiency = (p > 0) ? ((p / MAX_POWER) * 100).toFixed(1) : "0.0";
|
|
|
|
document.getElementById('powerValue').textContent = p;
|
|
document.getElementById('voltage').innerHTML = v + ' <span class="stat-unit">V</span>';
|
|
document.getElementById('current').innerHTML = c + ' <span class="stat-unit">A</span>';
|
|
document.getElementById('lux').innerHTML = l.toLocaleString() + ' <span class="stat-unit">Lux</span>';
|
|
document.getElementById('efficiency').innerHTML = efficiency + ' <span class="stat-unit">%</span>';
|
|
|
|
const ratio = Math.min(p / MAX_POWER, 1);
|
|
const offset = halfCircumference - (ratio * halfCircumference);
|
|
|
|
if (gaugeCircle) {
|
|
gaugeCircle.style.strokeDashoffset = offset;
|
|
}
|
|
}
|
|
|
|
function createEnvChart() {
|
|
const ctx = document.getElementById('envChart');
|
|
if (!ctx) return;
|
|
|
|
envChart = new Chart(ctx, {
|
|
type: 'line',
|
|
data: {
|
|
labels: [],
|
|
datasets: [
|
|
{
|
|
label: 'Température (°C)',
|
|
data: [],
|
|
borderColor: '#38bdf8',
|
|
backgroundColor: 'rgba(56, 189, 248, 0.2)',
|
|
pointBackgroundColor: '#38bdf8',
|
|
pointBorderColor: '#38bdf8',
|
|
borderWidth: 2,
|
|
tension: 0.3
|
|
},
|
|
{
|
|
label: 'Humidité (%)',
|
|
data: [],
|
|
borderColor: '#ff5c8a',
|
|
backgroundColor: 'rgba(255, 92, 138, 0.2)',
|
|
pointBackgroundColor: '#ff5c8a',
|
|
pointBorderColor: '#ff5c8a',
|
|
borderWidth: 2,
|
|
tension: 0.3
|
|
}
|
|
]
|
|
},
|
|
options: {
|
|
responsive: true,
|
|
maintainAspectRatio: false,
|
|
interaction: {
|
|
mode: 'index',
|
|
intersect: false
|
|
},
|
|
plugins: {
|
|
legend: {
|
|
labels: {
|
|
color: '#e2e8f0'
|
|
}
|
|
}
|
|
},
|
|
scales: {
|
|
x: {
|
|
ticks: {
|
|
color: '#94a3b8'
|
|
},
|
|
grid: {
|
|
color: 'rgba(148, 163, 184, 0.1)'
|
|
}
|
|
},
|
|
y: {
|
|
ticks: {
|
|
color: '#94a3b8'
|
|
},
|
|
grid: {
|
|
color: 'rgba(148, 163, 184, 0.1)'
|
|
}
|
|
}
|
|
}
|
|
}
|
|
});
|
|
}
|
|
|
|
function createEnergyChart() {
|
|
const ctx = document.getElementById('energyChart');
|
|
if (!ctx) return;
|
|
|
|
energyChart = new Chart(ctx, {
|
|
type: 'line',
|
|
data: {
|
|
labels: [],
|
|
datasets: [
|
|
{
|
|
label: 'Batterie (%)',
|
|
data: [],
|
|
borderColor: '#34d399',
|
|
backgroundColor: 'rgba(52, 211, 153, 0.2)',
|
|
pointBackgroundColor: '#34d399',
|
|
pointBorderColor: '#34d399',
|
|
borderWidth: 2,
|
|
tension: 0.3
|
|
},
|
|
{
|
|
label: 'Puissance solaire (W)',
|
|
data: [],
|
|
borderColor: '#facc15',
|
|
backgroundColor: 'rgba(250, 204, 21, 0.2)',
|
|
pointBackgroundColor: '#facc15',
|
|
pointBorderColor: '#facc15',
|
|
borderWidth: 2,
|
|
tension: 0.3
|
|
}
|
|
]
|
|
},
|
|
options: {
|
|
responsive: true,
|
|
maintainAspectRatio: false,
|
|
interaction: {
|
|
mode: 'index',
|
|
intersect: false
|
|
},
|
|
plugins: {
|
|
legend: {
|
|
labels: {
|
|
color: '#e2e8f0'
|
|
}
|
|
}
|
|
},
|
|
scales: {
|
|
x: {
|
|
ticks: {
|
|
color: '#94a3b8'
|
|
},
|
|
grid: {
|
|
color: 'rgba(148, 163, 184, 0.1)'
|
|
}
|
|
},
|
|
y: {
|
|
ticks: {
|
|
color: '#94a3b8'
|
|
},
|
|
grid: {
|
|
color: 'rgba(148, 163, 184, 0.1)'
|
|
}
|
|
}
|
|
}
|
|
}
|
|
});
|
|
}
|
|
|
|
async function fetchHistoryData() {
|
|
try {
|
|
const response = await fetch('/api/history?limit=10');
|
|
|
|
if (!response.ok) {
|
|
throw new Error("Impossible de récupérer l'historique");
|
|
}
|
|
|
|
const history = await response.json();
|
|
|
|
const labels = history.map(item => {
|
|
const date = new Date(item.created_at);
|
|
return date.toLocaleTimeString('fr-FR', {
|
|
hour: '2-digit',
|
|
minute: '2-digit',
|
|
second: '2-digit'
|
|
});
|
|
});
|
|
|
|
if (envChart) {
|
|
envChart.data.labels = labels;
|
|
envChart.data.datasets[0].data = history.map(item => item.temperature_ext ?? null);
|
|
envChart.data.datasets[1].data = history.map(item => item.humidity_ext ?? null);
|
|
envChart.update();
|
|
}
|
|
|
|
if (energyChart) {
|
|
energyChart.data.labels = labels;
|
|
energyChart.data.datasets[0].data = history.map(item => item.battery_level ?? null);
|
|
energyChart.data.datasets[1].data = history.map(item => item.power_pv ?? null);
|
|
energyChart.update();
|
|
}
|
|
|
|
} catch (error) {
|
|
console.error("Erreur historique :", error);
|
|
}
|
|
}
|
|
|
|
async function fetchDatabaseData() {
|
|
if (!isOnline) return;
|
|
|
|
try {
|
|
const response = await fetch('/api/latest');
|
|
|
|
if (!response.ok) {
|
|
throw new Error("Base de données vide ou serveur injoignable");
|
|
}
|
|
|
|
const data = await response.json();
|
|
|
|
tempEl.innerText = data.temperature_ext !== null ? Number(data.temperature_ext).toFixed(1) : "--";
|
|
humEl.innerText = data.humidity_ext !== null ? Math.floor(data.humidity_ext) : "--";
|
|
|
|
if (data.battery_level !== null) {
|
|
const batteryAlert = data.battery_alert === true || data.battery_alert === "true" || data.battery_alert === 1 || data.battery_alert === "1";
|
|
updateBattery(Math.floor(data.battery_level), batteryAlert ? "Alerte batterie" : null);
|
|
}
|
|
|
|
updateSolar(
|
|
data.voltage_pv || 0,
|
|
data.current_pv || 0,
|
|
data.power_pv || 0,
|
|
data.luminosity || 0
|
|
);
|
|
|
|
lastHeartbeat = Date.now();
|
|
} catch (error) {
|
|
console.error("En attente de données capteurs...", error);
|
|
}
|
|
}
|
|
|
|
function checkSystemStatus() {
|
|
if (Date.now() - lastHeartbeat > TIMEOUT_MS) {
|
|
statusDot.className = "status-dot rounded-circle pulse-offline";
|
|
statusText.innerText = "HORS LIGNE";
|
|
statusText.style.color = "#ef4444";
|
|
} else {
|
|
statusDot.className = "status-dot rounded-circle pulse-online";
|
|
statusText.innerText = "EN LIGNE";
|
|
statusText.style.color = "#e2e8f0";
|
|
}
|
|
}
|
|
|
|
document.getElementById('btn-test').addEventListener('click', function () {
|
|
isOnline = !isOnline;
|
|
if (isOnline) {
|
|
this.innerHTML = '<i class="bi bi-wifi-off"></i> Simuler Coupure';
|
|
this.classList.replace('btn-outline-success', 'btn-outline-danger');
|
|
lastHeartbeat = Date.now();
|
|
} else {
|
|
this.innerHTML = '<i class="bi bi-wifi"></i> Rétablir Connexion';
|
|
this.classList.replace('btn-outline-danger', 'btn-outline-success');
|
|
}
|
|
});
|
|
|
|
createEnvChart();
|
|
createEnergyChart();
|
|
fetchRealWeather();
|
|
fetchDatabaseData();
|
|
fetchHistoryData();
|
|
checkSystemStatus();
|
|
|
|
setInterval(fetchRealWeather, 900000);
|
|
setInterval(fetchDatabaseData, 10000);
|
|
setInterval(fetchHistoryData, 30000);
|
|
setInterval(checkSystemStatus, 10000); |