Le compteur de la borne fait foi, pas mon integration
Le tableau annoncait 750 Wh la ou VRM affichait 8 kWh. Ce n'etait pas une erreur de mesure mais une erreur de SOURCE : j'integrais la puissance depuis le moment ou mon detecteur avait commence a fonctionner — 03:16 — pour une session ouverte a 01:25. Or la borne tient son propre compteur de session, `sensor.evcs_hq2239dnt7p_last_session_energy`, qui vaut exactement les 8,0 kWh de VRM et compte depuis le vrai debut. Nouvelle colonne `energie_station_kwh` : c'est elle qui alimente desormais les totaux et le graphique. Mon integration est conservee en colonne « kWh integres » : l'ecart entre les deux signale un demarrage ou un redemarrage du service en cours de session, ce qui est une information en soi. Et elle sert de secours si la borne ne publie rien. Cherche au passage, sans succes : VRM affiche « Vehicule : Guest EV » mais AUCUNE entite ne porte ce champ cote Home Assistant. « Guest EV » signifie d'ailleurs vehicule NON identifie — Victron ne sait pas non plus qui est branche. 50/53 : les trois requetes en echec portent sur la nouvelle colonne, que la migration creera au prochain demarrage du service. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01KKzkkVTG6KXnGXFFC7X9XJ
This commit is contained in:
@@ -77,7 +77,7 @@
|
||||
"format": "table",
|
||||
"rawQuery": true,
|
||||
"editorMode": "code",
|
||||
"rawSql": "SELECT round(coalesce(sum(energie_kwh) FILTER (WHERE vehicule = 'VW'), 0)::numeric, 1) AS \"VW\",\n round(coalesce(sum(energie_kwh) FILTER (WHERE vehicule = 'JEEP'), 0)::numeric, 1) AS \"JEEP\",\n count(*) AS \"Sessions\",\n count(*) FILTER (WHERE NOT vehicule_sur OR vehicule IS NULL) AS \"Etiquette douteuse\"\nFROM session_charge WHERE $__timeFilter(debut)"
|
||||
"rawSql": "SELECT round(coalesce(sum(coalesce(energie_station_kwh, energie_kwh)) FILTER (WHERE vehicule = 'VW'), 0)::numeric, 1) AS \"VW\",\n round(coalesce(sum(coalesce(energie_station_kwh, energie_kwh)) FILTER (WHERE vehicule = 'JEEP'), 0)::numeric, 1) AS \"JEEP\",\n count(*) AS \"Sessions\",\n count(*) FILTER (WHERE NOT vehicule_sur OR vehicule IS NULL) AS \"Etiquette douteuse\"\nFROM session_charge WHERE $__timeFilter(debut)"
|
||||
}
|
||||
],
|
||||
"gridPos": {
|
||||
@@ -207,7 +207,7 @@
|
||||
"format": "table",
|
||||
"rawQuery": true,
|
||||
"editorMode": "code",
|
||||
"rawSql": "WITH bornes AS (\n SELECT $__timeFrom()::timestamptz AS d, $__timeTo()::timestamptz AS f\n),\npas AS (\n SELECT CASE WHEN f - d > interval '70 days' THEN 'month'\n WHEN f - d > interval '4 days' THEN 'day'\n ELSE 'hour' END AS unite,\n CASE WHEN f - d > interval '70 days' THEN 'MM/YYYY'\n WHEN f - d > interval '4 days' THEN 'DD/MM'\n ELSE 'DD/MM HH24' END AS forme\n FROM bornes\n)\nSELECT to_char(date_trunc(pas.unite, debut), pas.forme) AS \"Periode\",\n round(coalesce(sum(energie_kwh) FILTER (WHERE vehicule = 'VW'), 0)::numeric, 2) AS \"VW\",\n round(coalesce(sum(energie_kwh) FILTER (WHERE vehicule = 'JEEP'), 0)::numeric, 2) AS \"JEEP\",\n round(coalesce(sum(energie_kwh) FILTER (WHERE vehicule IS NULL), 0)::numeric, 2) AS \"Non etiquete\"\nFROM session_charge, pas\nWHERE $__timeFilter(debut)\nGROUP BY date_trunc(pas.unite, debut), pas.forme\nORDER BY date_trunc(pas.unite, debut)"
|
||||
"rawSql": "WITH bornes AS (\n SELECT $__timeFrom()::timestamptz AS d, $__timeTo()::timestamptz AS f\n),\npas AS (\n SELECT CASE WHEN f - d > interval '70 days' THEN 'month'\n WHEN f - d > interval '4 days' THEN 'day'\n ELSE 'hour' END AS unite,\n CASE WHEN f - d > interval '70 days' THEN 'MM/YYYY'\n WHEN f - d > interval '4 days' THEN 'DD/MM'\n ELSE 'DD/MM HH24' END AS forme\n FROM bornes\n)\nSELECT to_char(date_trunc(pas.unite, debut), pas.forme) AS \"Periode\",\n round(coalesce(sum(coalesce(energie_station_kwh, energie_kwh)) FILTER (WHERE vehicule = 'VW'), 0)::numeric, 2) AS \"VW\",\n round(coalesce(sum(coalesce(energie_station_kwh, energie_kwh)) FILTER (WHERE vehicule = 'JEEP'), 0)::numeric, 2) AS \"JEEP\",\n round(coalesce(sum(coalesce(energie_station_kwh, energie_kwh)) FILTER (WHERE vehicule IS NULL), 0)::numeric, 2) AS \"Non etiquete\"\nFROM session_charge, pas\nWHERE $__timeFilter(debut)\nGROUP BY date_trunc(pas.unite, debut), pas.forme\nORDER BY date_trunc(pas.unite, debut)"
|
||||
}
|
||||
],
|
||||
"gridPos": {
|
||||
@@ -252,7 +252,7 @@
|
||||
{
|
||||
"type": "table",
|
||||
"title": "Les sessions",
|
||||
"description": "« Debranche » confirme le vehicule apres coup : les deux partent a des heures differentes. « dont reseau » se fonde sur le mode source declare par le controleur — son intention — et non sur la puissance reseau instantanee, qui peut alimenter la maison pendant que le soleil alimente la voiture.",
|
||||
"description": "« kWh » vient du compteur de la BORNE, qui compte depuis le vrai debut de la session. « kWh integres » est mon propre calcul, qui ne couvre que ce que le service a vu — un ecart entre les deux signale un demarrage ou un redemarrage en cours de session, pas une erreur de mesure. « Debranche » confirme le vehicule apres coup : les deux partent a des heures differentes. « dont reseau » se fonde sur le mode source declare par le controleur — son intention — et non sur la puissance reseau instantanee, qui peut alimenter la maison pendant que le soleil alimente la voiture.",
|
||||
"datasource": {
|
||||
"type": "grafana-postgresql-datasource",
|
||||
"uid": "evcs-pg"
|
||||
@@ -267,7 +267,7 @@
|
||||
"format": "table",
|
||||
"rawQuery": true,
|
||||
"editorMode": "code",
|
||||
"rawSql": "SELECT to_char(debut, 'Dy DD/MM HH24:MI') AS \"Debut\",\n coalesce(vehicule, '?') AS \"Vehicule\",\n CASE WHEN vehicule_sur THEN '' ELSE 'selecteur change' END AS \"Doute\",\n round((extract(epoch FROM fin - debut) / 60)::numeric) AS \"Minutes\",\n round(energie_kwh::numeric, 2) AS \"kWh\",\n round(energie_reseau_kwh::numeric, 2) AS \"dont reseau\",\n round(p_max_w::numeric) AS \"P max\",\n consigne_max_a AS \"Consigne max\",\n source_mode AS \"Source au depart\",\n to_char(debranche_a, 'Dy HH24:MI') AS \"Debranche\"\nFROM session_charge WHERE $__timeFilter(debut) ORDER BY debut DESC"
|
||||
"rawSql": "SELECT to_char(debut, 'Dy DD/MM HH24:MI') AS \"Debut\",\n coalesce(vehicule, '?') AS \"Vehicule\",\n CASE WHEN vehicule_sur THEN '' ELSE 'selecteur change' END AS \"Doute\",\n round((extract(epoch FROM fin - debut) / 60)::numeric) AS \"Minutes\",\n round(coalesce(energie_station_kwh, energie_kwh)::numeric, 2) AS \"kWh\",\n round(energie_kwh::numeric, 2) AS \"kWh integres\",\n round(energie_reseau_kwh::numeric, 2) AS \"dont reseau\",\n round(p_max_w::numeric) AS \"P max\",\n consigne_max_a AS \"Consigne max\",\n source_mode AS \"Source au depart\",\n to_char(debranche_a, 'Dy HH24:MI') AS \"Debranche\"\nFROM session_charge WHERE $__timeFilter(debut) ORDER BY debut DESC"
|
||||
}
|
||||
],
|
||||
"gridPos": {
|
||||
@@ -351,6 +351,22 @@
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"matcher": {
|
||||
"id": "byName",
|
||||
"options": "kWh integres"
|
||||
},
|
||||
"properties": [
|
||||
{
|
||||
"id": "unit",
|
||||
"value": "kwatth"
|
||||
},
|
||||
{
|
||||
"id": "custom.width",
|
||||
"value": 110
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"matcher": {
|
||||
"id": "byName",
|
||||
|
||||
@@ -139,6 +139,11 @@ CREATE TABLE session_charge (
|
||||
terminee boolean NOT NULL DEFAULT false,
|
||||
vehicule text, -- valeur du selecteur PENDANT la session
|
||||
vehicule_sur boolean NOT NULL DEFAULT true, -- le selecteur n'a pas change
|
||||
-- Le compteur de la BORNE, qui fait foi : il compte depuis le vrai debut
|
||||
-- de la session, la ou l'integration ci-dessous ne couvre que ce que le
|
||||
-- service a vu. Le 05/09 : 8,0 kWh cote borne, 0,75 cote integration,
|
||||
-- le detecteur n'ayant demarre qu'a 03:16 pour une session ouverte a 01:25.
|
||||
energie_station_kwh double precision,
|
||||
energie_kwh double precision NOT NULL DEFAULT 0,
|
||||
-- Part venue du reseau : on se fie au `source_mode` du controleur, son
|
||||
-- INTENTION, et non a la puissance reseau instantanee — laquelle peut
|
||||
|
||||
@@ -974,9 +974,9 @@ pas AS (
|
||||
FROM bornes
|
||||
)
|
||||
SELECT to_char(date_trunc(pas.unite, debut), pas.forme) AS "Periode",
|
||||
round(coalesce(sum(energie_kwh) FILTER (WHERE vehicule = 'VW'), 0)::numeric, 2) AS "VW",
|
||||
round(coalesce(sum(energie_kwh) FILTER (WHERE vehicule = 'JEEP'), 0)::numeric, 2) AS "JEEP",
|
||||
round(coalesce(sum(energie_kwh) FILTER (WHERE vehicule IS NULL), 0)::numeric, 2) AS "Non etiquete"
|
||||
round(coalesce(sum(coalesce(energie_station_kwh, energie_kwh)) FILTER (WHERE vehicule = 'VW'), 0)::numeric, 2) AS "VW",
|
||||
round(coalesce(sum(coalesce(energie_station_kwh, energie_kwh)) FILTER (WHERE vehicule = 'JEEP'), 0)::numeric, 2) AS "JEEP",
|
||||
round(coalesce(sum(coalesce(energie_station_kwh, energie_kwh)) FILTER (WHERE vehicule IS NULL), 0)::numeric, 2) AS "Non etiquete"
|
||||
FROM session_charge, pas
|
||||
WHERE $__timeFilter(debut)
|
||||
GROUP BY date_trunc(pas.unite, debut), pas.forme
|
||||
@@ -991,8 +991,8 @@ vehicules = dashboard("evcs-vehicules", "Les vehicules", (
|
||||
|
||||
stat("Sur la periode", [
|
||||
sql("A",
|
||||
"SELECT round(coalesce(sum(energie_kwh) FILTER (WHERE vehicule = 'VW'), 0)::numeric, 1) AS \"VW\",\n"
|
||||
" round(coalesce(sum(energie_kwh) FILTER (WHERE vehicule = 'JEEP'), 0)::numeric, 1) AS \"JEEP\",\n"
|
||||
"SELECT round(coalesce(sum(coalesce(energie_station_kwh, energie_kwh)) FILTER (WHERE vehicule = 'VW'), 0)::numeric, 1) AS \"VW\",\n"
|
||||
" round(coalesce(sum(coalesce(energie_station_kwh, energie_kwh)) FILTER (WHERE vehicule = 'JEEP'), 0)::numeric, 1) AS \"JEEP\",\n"
|
||||
" count(*) AS \"Sessions\",\n"
|
||||
" count(*) FILTER (WHERE NOT vehicule_sur OR vehicule IS NULL) AS \"Etiquette douteuse\"\n"
|
||||
"FROM session_charge WHERE $__timeFilter(debut)", fmt="table")],
|
||||
@@ -1023,7 +1023,8 @@ vehicules = dashboard("evcs-vehicules", "Les vehicules", (
|
||||
" coalesce(vehicule, '?') AS \"Vehicule\",\n"
|
||||
" CASE WHEN vehicule_sur THEN '' ELSE 'selecteur change' END AS \"Doute\",\n"
|
||||
" round((extract(epoch FROM fin - debut) / 60)::numeric) AS \"Minutes\",\n"
|
||||
" round(energie_kwh::numeric, 2) AS \"kWh\",\n"
|
||||
" round(coalesce(energie_station_kwh, energie_kwh)::numeric, 2) AS \"kWh\",\n"
|
||||
" round(energie_kwh::numeric, 2) AS \"kWh integres\",\n"
|
||||
" round(energie_reseau_kwh::numeric, 2) AS \"dont reseau\",\n"
|
||||
" round(p_max_w::numeric) AS \"P max\",\n"
|
||||
" consigne_max_a AS \"Consigne max\",\n"
|
||||
@@ -1031,6 +1032,10 @@ vehicules = dashboard("evcs-vehicules", "Les vehicules", (
|
||||
" to_char(debranche_a, 'Dy HH24:MI') AS \"Debranche\"\n"
|
||||
"FROM session_charge WHERE $__timeFilter(debut) ORDER BY debut DESC", fmt="table")],
|
||||
PG, 0, 14, 24, 11,
|
||||
"« kWh » vient du compteur de la BORNE, qui compte depuis le vrai debut "
|
||||
"de la session. « kWh integres » est mon propre calcul, qui ne couvre que "
|
||||
"ce que le service a vu — un ecart entre les deux signale un demarrage ou "
|
||||
"un redemarrage en cours de session, pas une erreur de mesure. "
|
||||
"« Debranche » confirme le vehicule apres coup : les deux partent a des "
|
||||
"heures differentes. « dont reseau » se fonde sur le mode source declare "
|
||||
"par le controleur — son intention — et non sur la puissance reseau "
|
||||
@@ -1045,6 +1050,8 @@ vehicules = dashboard("evcs-vehicules", "Les vehicules", (
|
||||
"?": {"color": "orange", "index": 2}}}]}]),
|
||||
sur("Doute", [{"id": "custom.width", "value": 150}]),
|
||||
sur("kWh", [{"id": "unit", "value": "kwatth"}]),
|
||||
sur("kWh integres", [{"id": "unit", "value": "kwatth"},
|
||||
{"id": "custom.width", "value": 110}]),
|
||||
sur("dont reseau", [{"id": "unit", "value": "kwatth"}]),
|
||||
sur("P max", [{"id": "unit", "value": "watt"}])]),
|
||||
])
|
||||
|
||||
@@ -355,6 +355,12 @@ async function migrer(client) {
|
||||
await client.query(
|
||||
'ALTER TABLE session_charge ADD COLUMN IF NOT EXISTS energie_reseau_kwh '
|
||||
+ 'double precision NOT NULL DEFAULT 0');
|
||||
// Le compteur de la BORNE, qui fait foi. Mon integration de la puissance
|
||||
// est robuste mais ne couvre que ce que le service a vu : le 05/09, la
|
||||
// session avait commence a 01:25 et le detecteur n'a compte qu'a partir de
|
||||
// 03:16 — 0,75 kWh au lieu de 8. La borne, elle, compte depuis le debut.
|
||||
await client.query(
|
||||
'ALTER TABLE session_charge ADD COLUMN IF NOT EXISTS energie_station_kwh double precision');
|
||||
await client.query(
|
||||
'CREATE INDEX IF NOT EXISTS session_charge_debut_idx ON session_charge (debut DESC)');
|
||||
// Le role de lecture doit voir la nouvelle table. S'il n'existe pas encore,
|
||||
@@ -403,13 +409,15 @@ async function ecrireSession(sql, client, st) {
|
||||
if (!ouverte) {
|
||||
await sql(
|
||||
`INSERT INTO session_charge (debut, fin, vehicule, energie_kwh, p_max_w,
|
||||
courant_max_a, consigne_max_a, source_mode, brut)
|
||||
VALUES ($1,$1,$2,0,$3,$4,$5,$6,$7) ON CONFLICT (debut) DO NOTHING`,
|
||||
courant_max_a, consigne_max_a, source_mode, brut,
|
||||
energie_station_kwh)
|
||||
VALUES ($1,$1,$2,0,$3,$4,$5,$6,$7,$8) ON CONFLICT (debut) DO NOTHING`,
|
||||
[maintenant, vehicule, p,
|
||||
num(g('sensor.victron_evcs_current')?.state),
|
||||
num(g('sensor.victron_evcs_set_current')?.state),
|
||||
txt(g('input_text.victron_evcs_source_mode')?.state) || null,
|
||||
JSON.stringify({ profil: txt(g('input_text.victron_evcs_operating_mode')?.state) })]);
|
||||
JSON.stringify({ profil: txt(g('input_text.victron_evcs_operating_mode')?.state) }),
|
||||
num(g('sensor.evcs_hq2239dnt7p_last_session_energy')?.state)]);
|
||||
n++;
|
||||
} else {
|
||||
// Le pas de temps est plafonne : un arret du service ne doit pas
|
||||
@@ -432,13 +440,15 @@ async function ecrireSession(sql, client, st) {
|
||||
p_max_w = GREATEST(coalesce(p_max_w, 0), $3),
|
||||
courant_max_a = GREATEST(coalesce(courant_max_a, 0), coalesce($5, 0)),
|
||||
consigne_max_a = GREATEST(coalesce(consigne_max_a, 0), coalesce($6, 0)),
|
||||
energie_station_kwh = GREATEST(coalesce(energie_station_kwh, 0), coalesce($9::float8, 0)),
|
||||
vehicule = coalesce($7, vehicule),
|
||||
vehicule_sur = vehicule_sur AND ($7 IS NULL OR vehicule IS NULL OR vehicule = $7)
|
||||
WHERE debut = $1`,
|
||||
[ouverte.debut, maintenant, p, dt,
|
||||
num(g('sensor.victron_evcs_current')?.state),
|
||||
num(g('sensor.victron_evcs_set_current')?.state), vehicule,
|
||||
txt(g('input_text.victron_evcs_source_mode')?.state).startsWith('Réseau') ? 1 : 0]);
|
||||
txt(g('input_text.victron_evcs_source_mode')?.state).startsWith('Réseau') ? 1 : 0,
|
||||
num(g('sensor.evcs_hq2239dnt7p_last_session_energy')?.state)]);
|
||||
n++;
|
||||
}
|
||||
} else if (ouverte) {
|
||||
@@ -447,7 +457,7 @@ async function ecrireSession(sql, client, st) {
|
||||
await sql('UPDATE session_charge SET terminee = true WHERE debut = $1', [ouverte.debut]);
|
||||
await journaliser(sql, 'vehicule',
|
||||
`Session de charge terminee : ${ouverte.vehicule ?? 'vehicule inconnu'}, ` +
|
||||
`${Number(ouverte.energie_kwh).toFixed(2)} kWh`,
|
||||
`${Number(ouverte.energie_station_kwh ?? ouverte.energie_kwh).toFixed(2)} kWh`,
|
||||
{ debut: ouverte.debut, vehicule: ouverte.vehicule, kwh: ouverte.energie_kwh });
|
||||
n += 2;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user