Un tableau Grafana pour les cellules, et le plafond du BMS enfin historise
Le GX n'expose que l'agregat du parc : les quinze tensions d'un module ne sortent que par la console de son BMS. Le tableau les trace, avec les paliers d'alarme du materiel lui-meme - 3480, 3520, 3550, 3600 mV - et met la cellule meneuse en regard du plafond de charge. Le plafond impose par le BMS, grandeur centrale de tout ce diagnostic, n'etait pas historise : il l'est desormais, comme l'alarme de tension de cellule. Ils manquaient tous les deux a la memoire. Les quatre requetes cellule echouent tant que la table n'existe pas ; elles passeront apres redemarrage du service et import. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01KKzkkVTG6KXnGXFFC7X9XJ
This commit is contained in:
@@ -1069,11 +1069,116 @@ vehicules = dashboard("evcs-vehicules", "Les vehicules", (
|
||||
sur("P max", [{"id": "unit", "value": "watt"}])]),
|
||||
])
|
||||
|
||||
# =====================================================================
|
||||
# 7. LES BATTERIES — ce qu'aucun capteur Victron ne publie
|
||||
# =====================================================================
|
||||
# Le GX n'expose que l'agregat du parc : une tension, un courant, un SOC. Les
|
||||
# quinze tensions de cellule d'un module ne sortent QUE par la console serie de
|
||||
# son BMS. C'est pour cela que le desequilibre est reste invisible des annees.
|
||||
|
||||
VARIABLE_MODULE = {
|
||||
"name": "module", "label": "Module", "type": "query", "datasource": PG,
|
||||
"refresh": 1, "includeAll": False, "multi": False,
|
||||
"query": "SELECT DISTINCT module FROM cellule ORDER BY 1",
|
||||
}
|
||||
|
||||
# Echelle d'alarme du materiel, lue dans `sysinfo` du LVHUB le 05/09/2026.
|
||||
# Ce ne sont pas des valeurs choisies ici.
|
||||
SEUILS_CELLULE = seuils(("green", None), ("#EAB839", 3480), ("orange", 3520),
|
||||
("red", 3550), ("dark-red", 3600))
|
||||
|
||||
batteries = dashboard("evcs-batteries", "Les batteries", (
|
||||
"Les quinze tensions de cellule de chaque module Pylontech, relevees sur la "
|
||||
"console de leur BMS. Aucune de ces valeurs n'existe cote Victron, qui ne "
|
||||
"publie que l'agregat du parc. Les paliers de couleur sont ceux du materiel "
|
||||
"lui-meme : 3480, 3520, 3550 et 3600 mV."), [
|
||||
|
||||
ts("Cellule par cellule — $module", [
|
||||
sql("A", "SELECT ts AS time, 'cellule ' || lpad(cellule::text, 2, '0') AS metric,\n"
|
||||
" mv\n"
|
||||
"FROM cellule\n"
|
||||
"WHERE module = '$module' AND $__timeFilter(ts)\n"
|
||||
"ORDER BY 1, 2")],
|
||||
PG, 0, 0, 24, 10, unit="mvolt",
|
||||
desc="Une courbe par cellule. Une cellule saine suit exactement ses "
|
||||
"quatorze voisines ; une cellule qui a perdu de la capacite s'en "
|
||||
"detache en haut de charge ET en bas de decharge, parce qu'elle se "
|
||||
"remplit et se vide avant les autres.",
|
||||
extra={"fieldConfig": {"defaults": {"custom": {"lineWidth": 1},
|
||||
"thresholds": SEUILS_CELLULE,
|
||||
"custom.thresholdsStyle": {"mode": "dashed"}}}},
|
||||
legend="right"),
|
||||
|
||||
ts("Ecart entre la plus haute et la plus basse", [
|
||||
sql("A", "SELECT ts AS time, module AS metric, max(mv) - min(mv) AS ecart\n"
|
||||
"FROM cellule WHERE $__timeFilter(ts)\n"
|
||||
"GROUP BY ts, module ORDER BY 1")],
|
||||
PG, 0, 10, 12, 8, unit="mvolt",
|
||||
desc="L'indicateur qui resume un module en une courbe. Un module sain de "
|
||||
"ce parc tient sous 20 mV ; au-dela de 150 mV le BMS commence a "
|
||||
"reduire le courant de charge de TOUT le parc."),
|
||||
|
||||
ts("La cellule meneuse, contre le plafond que le BMS impose", [
|
||||
sql("A", "SELECT ts AS time, module || ' — cellule meneuse' AS metric,\n"
|
||||
" max(mv) AS mv\n"
|
||||
"FROM cellule WHERE $__timeFilter(ts)\n"
|
||||
"GROUP BY ts, module ORDER BY 1"),
|
||||
sql("B", "SELECT ts AS time, 'plafond de charge (A)' AS metric,\n"
|
||||
" valeur * 20 AS mv\n"
|
||||
"FROM mesure\n"
|
||||
"WHERE cle = 'sensor.airtria_8_us2000c_maximum_allowed_charge_current'\n"
|
||||
" AND $__timeFilter(ts) ORDER BY 1")],
|
||||
PG, 12, 10, 12, 8, unit="mvolt",
|
||||
desc="Le plafond de charge est trace a l'echelle (20 mV par ampere) pour "
|
||||
"tenir sur le meme axe. Le 05/09 a 13:05:08 la cellule meneuse est "
|
||||
"passee de 3541 a 3555 mV ; cinq secondes plus tard le plafond "
|
||||
"tombait de 50 a 30 A. Ensuite le courant baisse, la cellule se "
|
||||
"detend, l'alarme repasse au vert — et le plafond, lui, reste bas.",
|
||||
extra={"fieldConfig": {"defaults": {"thresholds": SEUILS_CELLULE}}}),
|
||||
|
||||
table("Amplitude de chaque cellule sur la periode", [
|
||||
sql("A", "SELECT cellule AS \"Cellule\",\n"
|
||||
" min(mv) AS \"Minimum\", max(mv) AS \"Maximum\",\n"
|
||||
" max(mv) - min(mv) AS \"Amplitude\"\n"
|
||||
"FROM cellule\n"
|
||||
"WHERE module = '$module' AND $__timeFilter(ts)\n"
|
||||
"GROUP BY cellule ORDER BY 4 DESC", fmt="table")],
|
||||
PG, 0, 18, 12, 10,
|
||||
desc="Le classement qui designe les cellules a traiter. Sur un module "
|
||||
"sain les quinze amplitudes sont identiques a quelques millivolts "
|
||||
"pres. Une amplitude de trois a quatre fois celle des voisines "
|
||||
"designe une cellule qui a perdu sa capacite — et cela ne se "
|
||||
"rattrape pas par un reequilibrage.",
|
||||
overrides=[sur("Amplitude", [{"id": "custom.cellOptions",
|
||||
"value": {"type": "color-background"}},
|
||||
{"id": "unit", "value": "mvolt"},
|
||||
{"id": "thresholds", "value": seuils(
|
||||
("green", None), ("#EAB839", 250),
|
||||
("orange", 400), ("red", 600))}]),
|
||||
sur("Minimum", [{"id": "unit", "value": "mvolt"}]),
|
||||
sur("Maximum", [{"id": "unit", "value": "mvolt"}])]),
|
||||
|
||||
ts("Etat de charge du parc pendant les relevés", [
|
||||
sql("A", "SELECT ts AS time, valeur AS \"SOC du parc\"\n"
|
||||
"FROM mesure WHERE cle = 'sensor.victron_battery_soc'\n"
|
||||
" AND $__timeFilter(ts) ORDER BY 1"),
|
||||
sql("B", "SELECT ts AS time, valeur AS \"Courant du parc\"\n"
|
||||
"FROM mesure WHERE cle = 'sensor.victron_battery_current'\n"
|
||||
" AND $__timeFilter(ts) ORDER BY 1")],
|
||||
PG, 12, 18, 12, 10,
|
||||
desc="Le contexte sans lequel un ecart entre cellules ne veut rien dire : "
|
||||
"l'ecart grandit avec l'etat de charge, et l'excursion de la cellule "
|
||||
"abimee grandit avec le courant. Le 05/09, a tension de parc egale, "
|
||||
"la meneuse etait a 3555 mV sous 50 A et a 3496 mV sous 30 A."),
|
||||
|
||||
], frm="now-7d", variables=[VARIABLE_MODULE])
|
||||
|
||||
base = pathlib.Path('deploy/historisation/grafana/dashboards')
|
||||
for nom, d in [('nuit.json', nuit), ('solaire.json', solaire),
|
||||
('prevision.json', prevision),
|
||||
('telephone.json', telephone),
|
||||
('systeme.json', systeme),
|
||||
('vehicules.json', vehicules)]:
|
||||
('vehicules.json', vehicules),
|
||||
('batteries.json', batteries)]:
|
||||
(base / nom).write_text(json.dumps(d, indent=2, ensure_ascii=False) + '\n')
|
||||
print(f"{nom:16s} {len(d['panels'])} panneaux")
|
||||
|
||||
Reference in New Issue
Block a user