diff --git a/deploy/historisation/grafana/dashboards/vehicules.json b/deploy/historisation/grafana/dashboards/vehicules.json index 3d21c38..b8042dd 100644 --- a/deploy/historisation/grafana/dashboards/vehicules.json +++ b/deploy/historisation/grafana/dashboards/vehicules.json @@ -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", diff --git a/deploy/historisation/schema.sql b/deploy/historisation/schema.sql index 27b3b8b..b9079a4 100644 --- a/deploy/historisation/schema.sql +++ b/deploy/historisation/schema.sql @@ -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 diff --git a/tools/gen-grafana-dashboards.py b/tools/gen-grafana-dashboards.py index 8dba909..bfc5f9a 100644 --- a/tools/gen-grafana-dashboards.py +++ b/tools/gen-grafana-dashboards.py @@ -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"}])]), ]) diff --git a/tools/historise-evcs.mjs b/tools/historise-evcs.mjs index 85abb36..a01a1b9 100644 --- a/tools/historise-evcs.mjs +++ b/tools/historise-evcs.mjs @@ -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; }