Finalisation dashboard EcoCharge dynamique
This commit is contained in:
144
app.py
144
app.py
@@ -30,12 +30,39 @@ def get_connection():
|
||||
)
|
||||
|
||||
|
||||
def safe_float(value):
|
||||
"""Convertit une valeur en float de façon sûre.
|
||||
Accepte la virgule ou le point. Retourne None si impossible."""
|
||||
if value is None:
|
||||
return None
|
||||
try:
|
||||
return float(str(value).replace(",", ".").strip())
|
||||
except (TypeError, ValueError):
|
||||
return None
|
||||
|
||||
|
||||
def normalize_number(value):
|
||||
"""Normalise une saisie utilisateur (virgule -> point)."""
|
||||
if value is None:
|
||||
return None
|
||||
return str(value).replace(",", ".").strip()
|
||||
|
||||
|
||||
def get_setting_value(cur, key, default_value):
|
||||
cur.execute("SELECT value FROM settings WHERE key = %s", (key,))
|
||||
row = cur.fetchone()
|
||||
return row[0] if row else default_value
|
||||
|
||||
|
||||
def upsert_setting(cur, key, value):
|
||||
cur.execute("""
|
||||
INSERT INTO settings (key, value)
|
||||
VALUES (%s, %s)
|
||||
ON CONFLICT (key)
|
||||
DO UPDATE SET value = EXCLUDED.value
|
||||
""", (key, value))
|
||||
|
||||
|
||||
def admin_required(view_func):
|
||||
@wraps(view_func)
|
||||
def wrapped_view(*args, **kwargs):
|
||||
@@ -72,6 +99,7 @@ def receive_data():
|
||||
if not data:
|
||||
return jsonify({"error": "Aucune donnée JSON reçue"}), 400
|
||||
|
||||
# --- Noms JSON envoyés par l'ESP32 : INCHANGÉS ---
|
||||
temperature_ext = data.get("temperature_ext")
|
||||
humidity_ext = data.get("humidity_ext")
|
||||
system_status_msg = data.get("system_status")
|
||||
@@ -89,18 +117,29 @@ def receive_data():
|
||||
conn = get_connection()
|
||||
cur = conn.cursor()
|
||||
|
||||
max_battery_temperature = float(get_setting_value(cur, "max_battery_temperature", "60"))
|
||||
min_battery_voltage = float(get_setting_value(cur, "min_battery_voltage", "11"))
|
||||
min_solar_power = float(get_setting_value(cur, "min_solar_power", "5"))
|
||||
# --- Seuils visuels (cohérents avec les VRAIS capteurs) ---
|
||||
# Nouvelles clés pour ignorer d'anciennes valeurs incohérentes restées en base.
|
||||
max_temperature = safe_float(get_setting_value(cur, "max_temp", "60")) or 60.0
|
||||
min_voltage = safe_float(get_setting_value(cur, "min_voltage", "0.2")) or 0.2
|
||||
min_power = safe_float(get_setting_value(cur, "min_power", "0.1")) or 0.1
|
||||
|
||||
battery_alert = "none"
|
||||
# --- Alerte basée UNIQUEMENT sur les mesures réelles ---
|
||||
# - température DHT11 (temperature_ext)
|
||||
# - tension mesurée INA219 (voltage_pv)
|
||||
# - puissance calculée (power_pv)
|
||||
# Plus aucune référence à une "batterie" (pas de BMS sur ce montage).
|
||||
temp_value = safe_float(temperature_ext)
|
||||
voltage_value = safe_float(voltage_pv)
|
||||
power_value = safe_float(power_pv)
|
||||
|
||||
if battery_temp is not None and float(battery_temp) > max_battery_temperature:
|
||||
battery_alert = "Température batterie trop élevée"
|
||||
elif voltage_battery is not None and float(voltage_battery) < min_battery_voltage:
|
||||
battery_alert = "Tension batterie trop faible"
|
||||
elif power_pv is not None and float(power_pv) < min_solar_power:
|
||||
battery_alert = "Puissance solaire insuffisante"
|
||||
battery_alert = "none" # nom de colonne conservé pour ne rien casser en base
|
||||
|
||||
if temp_value is not None and temp_value > max_temperature:
|
||||
battery_alert = "Température trop élevée"
|
||||
elif voltage_value is not None and voltage_value < min_voltage:
|
||||
battery_alert = "Tension mesurée trop faible"
|
||||
elif power_value is not None and power_value < min_power:
|
||||
battery_alert = "Puissance calculée insuffisante"
|
||||
|
||||
cur.execute("""
|
||||
INSERT INTO telemetry (
|
||||
@@ -147,10 +186,13 @@ def latest_data():
|
||||
conn = get_connection()
|
||||
cur = conn.cursor()
|
||||
|
||||
# data_age_seconds = ancienneté de la dernière mesure, calculée côté serveur.
|
||||
# Permet au dashboard de savoir si l'ESP32 envoie encore des données récentes.
|
||||
cur.execute("""
|
||||
SELECT
|
||||
id,
|
||||
created_at,
|
||||
EXTRACT(EPOCH FROM (NOW() - created_at)) AS age_seconds,
|
||||
temperature_ext,
|
||||
humidity_ext,
|
||||
system_status_msg,
|
||||
@@ -176,30 +218,33 @@ def latest_data():
|
||||
if not row:
|
||||
return jsonify({"message": "Aucune donnée disponible"}), 404
|
||||
|
||||
age = row[2]
|
||||
|
||||
return jsonify({
|
||||
"id": row[0],
|
||||
"created_at": str(row[1]),
|
||||
"temperature_ext": row[2],
|
||||
"humidity_ext": row[3],
|
||||
"system_status": row[4],
|
||||
"voltage_pv": row[5],
|
||||
"current_pv": row[6],
|
||||
"power_pv": row[7],
|
||||
"luminosity": row[8],
|
||||
"voltage_battery": row[9],
|
||||
"current_battery": row[10],
|
||||
"battery_temp": row[11],
|
||||
"battery_level": row[12],
|
||||
"battery_alert": row[13]
|
||||
"data_age_seconds": float(age) if age is not None else None,
|
||||
"temperature_ext": row[3],
|
||||
"humidity_ext": row[4],
|
||||
"system_status": row[5],
|
||||
"voltage_pv": row[6],
|
||||
"current_pv": row[7],
|
||||
"power_pv": row[8],
|
||||
"luminosity": row[9],
|
||||
"voltage_battery": row[10],
|
||||
"current_battery": row[11],
|
||||
"battery_temp": row[12],
|
||||
"battery_level": row[13],
|
||||
"battery_alert": row[14]
|
||||
})
|
||||
|
||||
|
||||
@app.route("/api/history", methods=["GET"])
|
||||
def history_data():
|
||||
limit = request.args.get("limit", default=10, type=int)
|
||||
limit = request.args.get("limit", default=15, type=int)
|
||||
|
||||
if limit is None or limit <= 0:
|
||||
limit = 10
|
||||
limit = 15
|
||||
|
||||
if limit > 50:
|
||||
limit = 50
|
||||
@@ -207,6 +252,7 @@ def history_data():
|
||||
conn = get_connection()
|
||||
cur = conn.cursor()
|
||||
|
||||
# On renvoie maintenant current_pv (courant mesuré) pour le graphique électrique.
|
||||
cur.execute("""
|
||||
SELECT
|
||||
id,
|
||||
@@ -214,7 +260,7 @@ def history_data():
|
||||
temperature_ext,
|
||||
humidity_ext,
|
||||
power_pv,
|
||||
battery_level
|
||||
current_pv
|
||||
FROM telemetry
|
||||
ORDER BY created_at DESC
|
||||
LIMIT %s
|
||||
@@ -233,7 +279,7 @@ def history_data():
|
||||
"temperature_ext": row[2],
|
||||
"humidity_ext": row[3],
|
||||
"power_pv": row[4],
|
||||
"battery_level": row[5]
|
||||
"current_pv": row[5]
|
||||
})
|
||||
|
||||
return jsonify(history)
|
||||
@@ -286,43 +332,35 @@ def admin_dashboard():
|
||||
cur = conn.cursor()
|
||||
|
||||
if request.method == "POST":
|
||||
min_battery = request.form.get("min_battery")
|
||||
max_temp = request.form.get("max_temp")
|
||||
min_power = request.form.get("min_power")
|
||||
# Normalisation : on accepte la virgule ET le point (ex : 0,3 -> 0.3)
|
||||
min_voltage = normalize_number(request.form.get("min_battery"))
|
||||
max_temp = normalize_number(request.form.get("max_temp"))
|
||||
min_power = normalize_number(request.form.get("min_power"))
|
||||
|
||||
cur.execute("""
|
||||
INSERT INTO settings (key, value)
|
||||
VALUES ('min_battery_voltage', %s)
|
||||
ON CONFLICT (key)
|
||||
DO UPDATE SET value = EXCLUDED.value
|
||||
""", (min_battery,))
|
||||
|
||||
cur.execute("""
|
||||
INSERT INTO settings (key, value)
|
||||
VALUES ('max_battery_temperature', %s)
|
||||
ON CONFLICT (key)
|
||||
DO UPDATE SET value = EXCLUDED.value
|
||||
""", (max_temp,))
|
||||
|
||||
cur.execute("""
|
||||
INSERT INTO settings (key, value)
|
||||
VALUES ('min_solar_power', %s)
|
||||
ON CONFLICT (key)
|
||||
DO UPDATE SET value = EXCLUDED.value
|
||||
""", (min_power,))
|
||||
upsert_setting(cur, "min_voltage", min_voltage)
|
||||
upsert_setting(cur, "max_temp", max_temp)
|
||||
upsert_setting(cur, "min_power", min_power)
|
||||
|
||||
conn.commit()
|
||||
cur.close()
|
||||
conn.close()
|
||||
|
||||
# Post/Redirect/Get : évite le ré-envoi du formulaire si on actualise,
|
||||
# et affiche proprement le message de succès.
|
||||
return redirect(url_for("admin_dashboard", saved=1))
|
||||
|
||||
settings = {
|
||||
"min_battery": get_setting_value(cur, "min_battery_voltage", "11"),
|
||||
"max_temp": get_setting_value(cur, "max_battery_temperature", "60"),
|
||||
"min_power": get_setting_value(cur, "min_solar_power", "5")
|
||||
"min_battery": get_setting_value(cur, "min_voltage", "0.2"),
|
||||
"max_temp": get_setting_value(cur, "max_temp", "60"),
|
||||
"min_power": get_setting_value(cur, "min_power", "0.1")
|
||||
}
|
||||
|
||||
cur.close()
|
||||
conn.close()
|
||||
|
||||
return render_template("admin_dashboard.html", settings=settings)
|
||||
saved = request.args.get("saved") == "1"
|
||||
|
||||
return render_template("admin_dashboard.html", settings=settings, saved=saved)
|
||||
|
||||
|
||||
@app.route("/admin/logout", methods=["GET", "POST"])
|
||||
|
||||
Reference in New Issue
Block a user