Chauffe-eau : Grafana « Le chauffe-eau », historisation (compteur rattrape depuis le 14/08), jour faible = aussi surplus solaire reel et securite douche de 20 h
This commit is contained in:
468
deploy/historisation/grafana/dashboards/chauffe-eau.json
Normal file
468
deploy/historisation/grafana/dashboards/chauffe-eau.json
Normal file
@@ -0,0 +1,468 @@
|
||||
{
|
||||
"templating": {
|
||||
"list": []
|
||||
},
|
||||
"uid": "evcs-chauffe-eau",
|
||||
"title": "Le chauffe-eau",
|
||||
"description": "Quand le chauffe-eau chauffe, et sur quoi : solaire, batterie ou EDF. Les jours faibles il doit chauffer la nuit sur EDF (decision du bilan apres 07:24), jamais sur la batterie.",
|
||||
"tags": [
|
||||
"evcs"
|
||||
],
|
||||
"timezone": "Europe/Paris",
|
||||
"editable": true,
|
||||
"schemaVersion": 39,
|
||||
"version": 1,
|
||||
"refresh": "1m",
|
||||
"time": {
|
||||
"from": "now-7d",
|
||||
"to": "now"
|
||||
},
|
||||
"timepicker": {
|
||||
"refresh_intervals": [
|
||||
"30s",
|
||||
"1m",
|
||||
"5m",
|
||||
"15m",
|
||||
"1h"
|
||||
]
|
||||
},
|
||||
"annotations": {
|
||||
"list": [
|
||||
{
|
||||
"builtIn": 1,
|
||||
"datasource": {
|
||||
"type": "grafana",
|
||||
"uid": "-- Grafana --"
|
||||
},
|
||||
"enable": true,
|
||||
"hide": true,
|
||||
"name": "Annotations & Alerts",
|
||||
"iconColor": "rgba(0, 211, 255, 1)",
|
||||
"type": "dashboard"
|
||||
},
|
||||
{
|
||||
"name": "Evenements",
|
||||
"datasource": {
|
||||
"type": "grafana-postgresql-datasource",
|
||||
"uid": "evcs-pg"
|
||||
},
|
||||
"enable": false,
|
||||
"iconColor": "semi-dark-orange",
|
||||
"hide": false,
|
||||
"target": {
|
||||
"format": "table",
|
||||
"rawQuery": true,
|
||||
"editorMode": "code",
|
||||
"rawSql": "SELECT ts AS time, resume AS text, genre || ',' || coalesce(source,'?') AS tags FROM evenement WHERE $__timeFilter(ts) AND genre <> 'bilan' ORDER BY 1"
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"panels": [
|
||||
{
|
||||
"type": "stat",
|
||||
"title": "Maintenant",
|
||||
"description": "Energie des 24 dernieres heures (securite eau chaude : sous 1,5 kWh il chauffe meme un jour faible) et puissance mesuree par le workflow ECS sur ses 5 dernieres minutes (morte jusqu'au 07/10).",
|
||||
"datasource": {
|
||||
"type": "grafana-postgresql-datasource",
|
||||
"uid": "evcs-pg"
|
||||
},
|
||||
"targets": [
|
||||
{
|
||||
"refId": "A",
|
||||
"datasource": {
|
||||
"type": "grafana-postgresql-datasource",
|
||||
"uid": "evcs-pg"
|
||||
},
|
||||
"format": "table",
|
||||
"rawQuery": true,
|
||||
"editorMode": "code",
|
||||
"rawSql": "SELECT now() AS time,\n (SELECT valeur FROM mesure WHERE cle = 'sensor.evcs_chauffe_eau_24h_kwh' ORDER BY ts DESC LIMIT 1) AS \"Sur 24 h\",\n (SELECT valeur FROM mesure WHERE cle = 'input_number.victron_water_heater_observed_power_w' ORDER BY ts DESC LIMIT 1) AS \"Puissance\""
|
||||
}
|
||||
],
|
||||
"gridPos": {
|
||||
"x": 0,
|
||||
"y": 0,
|
||||
"w": 8,
|
||||
"h": 4
|
||||
},
|
||||
"fieldConfig": {
|
||||
"defaults": {
|
||||
"color": {
|
||||
"mode": "thresholds"
|
||||
},
|
||||
"thresholds": {
|
||||
"mode": "absolute",
|
||||
"steps": [
|
||||
{
|
||||
"color": "text",
|
||||
"value": null
|
||||
}
|
||||
]
|
||||
},
|
||||
"mappings": []
|
||||
},
|
||||
"overrides": [
|
||||
{
|
||||
"matcher": {
|
||||
"id": "byName",
|
||||
"options": "Sur 24 h"
|
||||
},
|
||||
"properties": [
|
||||
{
|
||||
"id": "unit",
|
||||
"value": "kwatth"
|
||||
},
|
||||
{
|
||||
"id": "decimals",
|
||||
"value": 2
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"matcher": {
|
||||
"id": "byName",
|
||||
"options": "Puissance"
|
||||
},
|
||||
"properties": [
|
||||
{
|
||||
"id": "unit",
|
||||
"value": "watt"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
"options": {
|
||||
"reduceOptions": {
|
||||
"calcs": [
|
||||
"lastNotNull"
|
||||
],
|
||||
"fields": "",
|
||||
"values": false
|
||||
},
|
||||
"orientation": "horizontal",
|
||||
"textMode": "auto",
|
||||
"colorMode": "value",
|
||||
"graphMode": "none",
|
||||
"justifyMode": "center",
|
||||
"percentChangeColorMode": "standard",
|
||||
"noValue": "—",
|
||||
"wideLayout": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "table",
|
||||
"title": "Decisions et etats",
|
||||
"description": "Decision quotidienne du bilan (jour faible : eau chaude en HC de nuit), mise sous tension / coupure, changements de regime (pompe a chaleur, resistance). Journalise depuis le 07/10.",
|
||||
"datasource": {
|
||||
"type": "grafana-postgresql-datasource",
|
||||
"uid": "evcs-pg"
|
||||
},
|
||||
"targets": [
|
||||
{
|
||||
"refId": "A",
|
||||
"datasource": {
|
||||
"type": "grafana-postgresql-datasource",
|
||||
"uid": "evcs-pg"
|
||||
},
|
||||
"format": "table",
|
||||
"rawQuery": true,
|
||||
"editorMode": "code",
|
||||
"rawSql": "SELECT to_char(ts AT TIME ZONE 'Europe/Paris', 'DD/MM HH24:MI') AS \"Quand\", resume AS \"Quoi\"\nFROM evenement WHERE genre = 'chauffe_eau' AND $__timeFilter(ts) ORDER BY ts DESC"
|
||||
}
|
||||
],
|
||||
"gridPos": {
|
||||
"x": 8,
|
||||
"y": 0,
|
||||
"w": 16,
|
||||
"h": 8
|
||||
},
|
||||
"fieldConfig": {
|
||||
"defaults": {
|
||||
"custom": {
|
||||
"align": "left",
|
||||
"cellOptions": {
|
||||
"type": "auto"
|
||||
},
|
||||
"filterable": true
|
||||
}
|
||||
},
|
||||
"overrides": []
|
||||
},
|
||||
"options": {
|
||||
"showHeader": true,
|
||||
"cellHeight": "sm",
|
||||
"footer": {
|
||||
"show": false
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "timeseries",
|
||||
"title": "Energie par heure, selon d'ou elle venait",
|
||||
"description": "Une barre par heure. Contexte de l'heure : EDF si le reseau fournit plus de 300 W en moyenne, solaire si la production couvre la maison, batterie sinon (attribution par heure). Le rouge est ce qu'on veut faire disparaitre : de l'eau chauffee sur la batterie.",
|
||||
"datasource": {
|
||||
"type": "grafana-postgresql-datasource",
|
||||
"uid": "evcs-pg"
|
||||
},
|
||||
"targets": [
|
||||
{
|
||||
"refId": "A",
|
||||
"datasource": {
|
||||
"type": "grafana-postgresql-datasource",
|
||||
"uid": "evcs-pg"
|
||||
},
|
||||
"format": "time_series",
|
||||
"rawQuery": true,
|
||||
"editorMode": "code",
|
||||
"rawSql": "WITH h AS (SELECT generate_series(date_trunc('hour', $__timeFrom()::timestamptz),\n date_trunc('hour', least(now(), $__timeTo()::timestamptz)), interval '1 hour') AS t),\nv AS (SELECT t, (SELECT valeur FROM mesure WHERE cle = 'sensor.tt_chauffe_eau_pleumeur_energie_totale' AND ts <= t\n ORDER BY ts DESC LIMIT 1) AS e FROM h),\nk AS (SELECT t, greatest(0, lead(e) OVER (ORDER BY t) - e) AS kwh FROM v),\nc AS (SELECT k.t, k.kwh,\n coalesce((SELECT avg(valeur) FROM mesure WHERE cle = 'sensor.victron_grid_power_l1' AND ts >= k.t AND ts < k.t + interval '1 hour'), (SELECT valeur FROM mesure WHERE cle = 'sensor.victron_grid_power_l1' AND ts < k.t ORDER BY ts DESC LIMIT 1), 0) AS reseau,\n coalesce((SELECT avg(valeur) FROM mesure WHERE cle = 'sensor.victron_pv_power_system' AND ts >= k.t AND ts < k.t + interval '1 hour'), (SELECT valeur FROM mesure WHERE cle = 'sensor.victron_pv_power_system' AND ts < k.t ORDER BY ts DESC LIMIT 1), 0) AS pv,\n coalesce((SELECT avg(valeur) FROM mesure WHERE cle = 'sensor.victron_consumption_power_l1' AND ts >= k.t AND ts < k.t + interval '1 hour'), (SELECT valeur FROM mesure WHERE cle = 'sensor.victron_consumption_power_l1' AND ts < k.t ORDER BY ts DESC LIMIT 1), 0) AS maison\n FROM k WHERE k.kwh IS NOT NULL),\nx AS (SELECT t, kwh, CASE WHEN reseau > 300 THEN 'EDF' WHEN pv >= maison THEN 'solaire'\n ELSE 'batterie' END AS ctx FROM c)\nSELECT t AS time,\n CASE WHEN ctx = 'solaire' THEN kwh END AS \"Solaire\",\n CASE WHEN ctx = 'batterie' THEN kwh END AS \"Batterie\",\n CASE WHEN ctx = 'EDF' THEN kwh END AS \"EDF\"\nFROM x ORDER BY t"
|
||||
}
|
||||
],
|
||||
"gridPos": {
|
||||
"x": 0,
|
||||
"y": 8,
|
||||
"w": 24,
|
||||
"h": 9
|
||||
},
|
||||
"fieldConfig": {
|
||||
"defaults": {
|
||||
"unit": "kwatth",
|
||||
"color": {
|
||||
"mode": "palette-classic"
|
||||
},
|
||||
"custom": {
|
||||
"drawStyle": "bars",
|
||||
"fillOpacity": 85,
|
||||
"lineWidth": 0,
|
||||
"stacking": {
|
||||
"mode": "normal",
|
||||
"group": "A"
|
||||
},
|
||||
"showPoints": "never",
|
||||
"spanNulls": false
|
||||
}
|
||||
},
|
||||
"overrides": [
|
||||
{
|
||||
"matcher": {
|
||||
"id": "byName",
|
||||
"options": "Solaire"
|
||||
},
|
||||
"properties": [
|
||||
{
|
||||
"id": "color",
|
||||
"value": {
|
||||
"mode": "fixed",
|
||||
"fixedColor": "yellow"
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"matcher": {
|
||||
"id": "byName",
|
||||
"options": "Batterie"
|
||||
},
|
||||
"properties": [
|
||||
{
|
||||
"id": "color",
|
||||
"value": {
|
||||
"mode": "fixed",
|
||||
"fixedColor": "red"
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"matcher": {
|
||||
"id": "byName",
|
||||
"options": "EDF"
|
||||
},
|
||||
"properties": [
|
||||
{
|
||||
"id": "color",
|
||||
"value": {
|
||||
"mode": "fixed",
|
||||
"fixedColor": "blue"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
"options": {
|
||||
"legend": {
|
||||
"displayMode": "list",
|
||||
"placement": "bottom",
|
||||
"showLegend": true,
|
||||
"calcs": []
|
||||
},
|
||||
"tooltip": {
|
||||
"mode": "multi",
|
||||
"sort": "desc"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "barchart",
|
||||
"title": "Par jour",
|
||||
"description": "kWh du chauffe-eau par jour et par contexte. Repere : le 06/10, 5,4 kWh dont une partie sur la batterie a midi ; un jour faible reussi n'a que du bleu (EDF en heures creuses).",
|
||||
"datasource": {
|
||||
"type": "grafana-postgresql-datasource",
|
||||
"uid": "evcs-pg"
|
||||
},
|
||||
"targets": [
|
||||
{
|
||||
"refId": "A",
|
||||
"datasource": {
|
||||
"type": "grafana-postgresql-datasource",
|
||||
"uid": "evcs-pg"
|
||||
},
|
||||
"format": "table",
|
||||
"rawQuery": true,
|
||||
"editorMode": "code",
|
||||
"rawSql": "WITH h AS (SELECT generate_series(date_trunc('hour', $__timeFrom()::timestamptz),\n date_trunc('hour', least(now(), $__timeTo()::timestamptz)), interval '1 hour') AS t),\nv AS (SELECT t, (SELECT valeur FROM mesure WHERE cle = 'sensor.tt_chauffe_eau_pleumeur_energie_totale' AND ts <= t\n ORDER BY ts DESC LIMIT 1) AS e FROM h),\nk AS (SELECT t, greatest(0, lead(e) OVER (ORDER BY t) - e) AS kwh FROM v),\nc AS (SELECT k.t, k.kwh,\n coalesce((SELECT avg(valeur) FROM mesure WHERE cle = 'sensor.victron_grid_power_l1' AND ts >= k.t AND ts < k.t + interval '1 hour'), (SELECT valeur FROM mesure WHERE cle = 'sensor.victron_grid_power_l1' AND ts < k.t ORDER BY ts DESC LIMIT 1), 0) AS reseau,\n coalesce((SELECT avg(valeur) FROM mesure WHERE cle = 'sensor.victron_pv_power_system' AND ts >= k.t AND ts < k.t + interval '1 hour'), (SELECT valeur FROM mesure WHERE cle = 'sensor.victron_pv_power_system' AND ts < k.t ORDER BY ts DESC LIMIT 1), 0) AS pv,\n coalesce((SELECT avg(valeur) FROM mesure WHERE cle = 'sensor.victron_consumption_power_l1' AND ts >= k.t AND ts < k.t + interval '1 hour'), (SELECT valeur FROM mesure WHERE cle = 'sensor.victron_consumption_power_l1' AND ts < k.t ORDER BY ts DESC LIMIT 1), 0) AS maison\n FROM k WHERE k.kwh IS NOT NULL),\nx AS (SELECT t, kwh, CASE WHEN reseau > 300 THEN 'EDF' WHEN pv >= maison THEN 'solaire'\n ELSE 'batterie' END AS ctx FROM c)\nSELECT to_char(t AT TIME ZONE 'Europe/Paris', 'DD/MM') AS \"Jour\",\n round(sum(kwh) FILTER (WHERE ctx = 'solaire')::numeric, 2) AS \"Solaire\",\n round(sum(kwh) FILTER (WHERE ctx = 'batterie')::numeric, 2) AS \"Batterie\",\n round(sum(kwh) FILTER (WHERE ctx = 'EDF')::numeric, 2) AS \"EDF\"\nFROM x GROUP BY 1, date_trunc('day', t AT TIME ZONE 'Europe/Paris')\nORDER BY date_trunc('day', t AT TIME ZONE 'Europe/Paris')"
|
||||
}
|
||||
],
|
||||
"gridPos": {
|
||||
"x": 0,
|
||||
"y": 17,
|
||||
"w": 24,
|
||||
"h": 8
|
||||
},
|
||||
"fieldConfig": {
|
||||
"defaults": {
|
||||
"unit": "kwatth",
|
||||
"color": {
|
||||
"mode": "palette-classic"
|
||||
},
|
||||
"custom": {
|
||||
"lineWidth": 1,
|
||||
"fillOpacity": 80,
|
||||
"gradientMode": "none",
|
||||
"axisBorderShow": false
|
||||
}
|
||||
},
|
||||
"overrides": [
|
||||
{
|
||||
"matcher": {
|
||||
"id": "byName",
|
||||
"options": "Solaire"
|
||||
},
|
||||
"properties": [
|
||||
{
|
||||
"id": "color",
|
||||
"value": {
|
||||
"mode": "fixed",
|
||||
"fixedColor": "yellow"
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"matcher": {
|
||||
"id": "byName",
|
||||
"options": "Batterie"
|
||||
},
|
||||
"properties": [
|
||||
{
|
||||
"id": "color",
|
||||
"value": {
|
||||
"mode": "fixed",
|
||||
"fixedColor": "red"
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"matcher": {
|
||||
"id": "byName",
|
||||
"options": "EDF"
|
||||
},
|
||||
"properties": [
|
||||
{
|
||||
"id": "color",
|
||||
"value": {
|
||||
"mode": "fixed",
|
||||
"fixedColor": "blue"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
"options": {
|
||||
"orientation": "auto",
|
||||
"xTickLabelRotation": -45,
|
||||
"showValue": "never",
|
||||
"stacking": "none",
|
||||
"groupWidth": 0.7,
|
||||
"barWidth": 0.9,
|
||||
"legend": {
|
||||
"displayMode": "list",
|
||||
"placement": "bottom",
|
||||
"showLegend": true
|
||||
},
|
||||
"tooltip": {
|
||||
"mode": "multi",
|
||||
"sort": "none"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "timeseries",
|
||||
"title": "Puissance mesuree et regime",
|
||||
"description": "Puissance moyenne sur 5 minutes calculee par le workflow ECS depuis le compteur. Pompe a chaleur : puissance basse, ~5 h pour 55 °C ; resistance (~1,2 kW) : de 55 a 65 °C. Disponible depuis le 07/10 (avant, la mesure ne lisait jamais ses horodatages).",
|
||||
"datasource": {
|
||||
"type": "grafana-postgresql-datasource",
|
||||
"uid": "evcs-pg"
|
||||
},
|
||||
"targets": [
|
||||
{
|
||||
"refId": "A",
|
||||
"datasource": {
|
||||
"type": "grafana-postgresql-datasource",
|
||||
"uid": "evcs-pg"
|
||||
},
|
||||
"format": "time_series",
|
||||
"rawQuery": true,
|
||||
"editorMode": "code",
|
||||
"rawSql": "SELECT ts AS time, valeur AS \"Puissance\" FROM mesure\nWHERE cle = 'input_number.victron_water_heater_observed_power_w' AND $__timeFilter(ts) ORDER BY ts"
|
||||
}
|
||||
],
|
||||
"gridPos": {
|
||||
"x": 0,
|
||||
"y": 25,
|
||||
"w": 24,
|
||||
"h": 8
|
||||
},
|
||||
"fieldConfig": {
|
||||
"defaults": {
|
||||
"unit": "watt",
|
||||
"color": {
|
||||
"mode": "palette-classic"
|
||||
},
|
||||
"custom": {
|
||||
"drawStyle": "line",
|
||||
"lineInterpolation": "stepAfter",
|
||||
"fillOpacity": 20
|
||||
}
|
||||
},
|
||||
"overrides": []
|
||||
},
|
||||
"options": {
|
||||
"legend": {
|
||||
"displayMode": "list",
|
||||
"placement": "bottom",
|
||||
"showLegend": true,
|
||||
"calcs": []
|
||||
},
|
||||
"tooltip": {
|
||||
"mode": "multi",
|
||||
"sort": "desc"
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -269,8 +269,15 @@ if (!forceOffNow && !restoreNow && forcedOffByAdvisorPrev && waterHeaterSwitchOn
|
||||
// comportement d'avant revient, jamais un ballon froid par panne de capteur).
|
||||
// Jour normal : le chauffe-eau coupe par cette regle est remis sous tension.
|
||||
const ECS_EAU_FROIDE_KWH = 1.5;
|
||||
// Douches a 06:00 et 20:00 tous les jours (proprietaire, 07/10) : couper toute
|
||||
// la journee laisserait l'eau froide a 20 h. Un jour faible, il chauffe donc
|
||||
// AUSSI sur un vrai surplus solaire (jamais sur la batterie), et a partir de
|
||||
// 16 h s'il n'a presque pas chauffe depuis 07:24 (securite de la douche du soir).
|
||||
const ECS_SOIR_DEBUT_H = 16;
|
||||
const ECS_SOIR_FIN_H = 20;
|
||||
const {
|
||||
ecsNuit = false, hcSessionCommitted = false, ecs24hKwh = NaN, offJourFaiblePrev = false,
|
||||
ecsNuit = false, hcSessionCommitted = false, ecs24hKwh = NaN, ecsJourKwh = NaN,
|
||||
offJourFaiblePrev = false, pvPowerW = 0, autoPowerW = 1200,
|
||||
} = normalized;
|
||||
let offJourFaible = false;
|
||||
let jourFaibleState = 'none';
|
||||
@@ -278,11 +285,21 @@ if (advisorEnabled && ecsNuit) {
|
||||
const energieConnue = Number.isFinite(ecs24hKwh);
|
||||
const eauFroide = energieConnue && ecs24hKwh < ECS_EAU_FROIDE_KWH;
|
||||
const heuresCreusesEdf = hcSessionCommitted && gridIsOn;
|
||||
const veutChauffer = !energieConnue || heuresCreusesEdf || eauFroide;
|
||||
// Surplus reel : sous tension, la production couvre la maison (chauffe-eau
|
||||
// compris) ; hors tension, il reste de quoi l'alimenter. Reseau ouvert.
|
||||
const solaireCouvre = !gridIsOn && (waterHeaterSwitchOn
|
||||
? pvPowerW >= houseAcPowerW
|
||||
: pvPowerW - houseAcPowerW >= autoPowerW);
|
||||
const heure = Number.isFinite(now.hour) ? now.hour : -1;
|
||||
const securiteSoir = heure >= ECS_SOIR_DEBUT_H && heure < ECS_SOIR_FIN_H
|
||||
&& !(Number.isFinite(ecsJourKwh) && ecsJourKwh >= ECS_EAU_FROIDE_KWH);
|
||||
const veutChauffer = !energieConnue || heuresCreusesEdf || eauFroide || solaireCouvre || securiteSoir;
|
||||
offJourFaible = true;
|
||||
jourFaibleState = !energieConnue ? 'jour_faible_energie_illisible'
|
||||
: heuresCreusesEdf ? 'jour_faible_chauffe_hc_edf'
|
||||
: eauFroide ? 'jour_faible_eau_froide'
|
||||
: securiteSoir ? 'jour_faible_securite_soir'
|
||||
: solaireCouvre ? 'jour_faible_surplus_solaire'
|
||||
: 'jour_faible_coupe';
|
||||
if (veutChauffer !== waterHeaterSwitchOn) {
|
||||
powerSwitchAction = veutChauffer ? 'turn_on' : 'turn_off';
|
||||
@@ -299,6 +316,8 @@ if (advisorEnabled && ecsNuit) {
|
||||
jourFaibleState === 'jour_faible_chauffe_hc_edf' ? 'Jour faible : eau chaude en heures creuses sur EDF (session reseau engagee)'
|
||||
: jourFaibleState === 'jour_faible_eau_froide' ? `Jour faible mais seulement ${round1(ecs24hKwh)} kWh en 24 h : chauffe pour garantir l eau chaude`
|
||||
: jourFaibleState === 'jour_faible_energie_illisible' ? 'Jour faible, energie 24 h illisible : aucune restriction'
|
||||
: jourFaibleState === 'jour_faible_securite_soir' ? 'Jour faible : chauffe pour la douche de 20 h (peu chauffe depuis 07:24)'
|
||||
: jourFaibleState === 'jour_faible_surplus_solaire' ? 'Jour faible : surplus solaire reel, il chauffe sans toucher la batterie'
|
||||
: 'Jour faible : chauffe-eau coupe, il chauffera en heures creuses sur EDF'
|
||||
);
|
||||
} else if (advisorEnabled && offJourFaiblePrev && !waterHeaterSwitchOn) {
|
||||
|
||||
@@ -31,6 +31,7 @@
|
||||
"ecs_nuit_jour": states("input_text.evcs_balance_ecs_nuit_jour"),
|
||||
"hc_session_committed": states("input_boolean.victron_evcs_night_grid_session_committed"),
|
||||
"ecs_24h_kwh": states("sensor.evcs_chauffe_eau_24h_kwh"),
|
||||
"ecs_jour_kwh": states("sensor.evcs_chauffe_eau_jour_kwh"),
|
||||
"off_jour_faible_prev": states("input_boolean.victron_water_heater_off_jour_faible"),
|
||||
|
||||
"advisor_enabled": states("input_boolean.victron_water_heater_advisor_enabled"),
|
||||
|
||||
@@ -195,6 +195,7 @@ const lastSignature = cleanStateText(input.last_signature);
|
||||
const ecsNuit = low(input.ecs_nuit) === 'on';
|
||||
const hcSessionCommitted = low(input.hc_session_committed) === 'on';
|
||||
const ecs24hKwh = num(input.ecs_24h_kwh, NaN);
|
||||
const ecsJourKwh = num(input.ecs_jour_kwh, NaN);
|
||||
const offJourFaiblePrev = low(input.off_jour_faible_prev) === 'on';
|
||||
const forecastSource = cleanStateText(input.forecast_source) || 'forecast_solar';
|
||||
|
||||
@@ -243,6 +244,7 @@ return [{
|
||||
ecsNuit,
|
||||
hcSessionCommitted,
|
||||
ecs24hKwh,
|
||||
ecsJourKwh,
|
||||
offJourFaiblePrev,
|
||||
forecastSource
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
#!/usr/bin/env node
|
||||
// Energie du chauffe-eau sur 24 h (07/10/2026). Usage : node tests/allers-retours/chauffe-eau.mjs
|
||||
import { energieSur24h } from '../../tools/chauffe-eau.mjs';
|
||||
import { energieSur24h, energieDepuis } from '../../tools/chauffe-eau.mjs';
|
||||
let ok = 0, ko = 0;
|
||||
const v = (n, a, b) => { if (a === b) { ok++; console.log(`✅ ${n}`); } else { ko++; console.log(`❌ ${n} : attendu ${b}, obtenu ${a}`); } };
|
||||
const s = (...x) => x.map((state) => ({ state: String(state) }));
|
||||
@@ -9,5 +9,8 @@ v('compteur-immobile-0-kwh', energieSur24h(s(18.254)), 0);
|
||||
v('indisponible-ignore', energieSur24h(s(10, 'unavailable', 11)), 1);
|
||||
v('remise-a-zero-sans-negatif', energieSur24h(s(10, 11, 0, 0.5)), 1.5);
|
||||
v('illisible-null', energieSur24h(s('unavailable')), null);
|
||||
const p = (iso, state) => ({ last_changed: iso, state: String(state) });
|
||||
const serie = [p('2026-10-06T20:00:00Z', 18.0), p('2026-10-07T05:00:00Z', 18.2), p('2026-10-07T06:00:00Z', 18.6), p('2026-10-07T08:00:00Z', 19.1)];
|
||||
v('depuis-07h24-reference-avant-la-borne', energieDepuis(serie, Date.parse('2026-10-07T05:24:00Z')), 0.9);
|
||||
console.log(ko ? `\n${ko} ECHEC(S)` : `\nCHAUFFE-EAU 24 H OK — ${ok}/${ok}`);
|
||||
process.exit(ko ? 1 : 0);
|
||||
|
||||
@@ -26,11 +26,11 @@ function executer(snapshot) {
|
||||
// Snapshot de base : matin d'un jour faible, contacteur ouvert, chauffe-eau sous tension.
|
||||
const BASE = {
|
||||
soc_percent: '45', battery_current_a: '-5', pv_power_w: '900', pv_power_10m_avg_w: '850',
|
||||
house_ac_power_w: '700', grid_power_w: '0', grid_state: 'off', victron_acin1_available: '0',
|
||||
house_ac_power_w: '2000', grid_power_w: '0', grid_state: 'off', victron_acin1_available: '0',
|
||||
today_remaining_kwh: '15', tomorrow_kwh: '25', forecast_source: 'vrm',
|
||||
advisor_enabled: 'on', notifications_enabled: 'off', hot_water_demand: 'normal',
|
||||
water_heater_switch: 'on', water_heater_energy_total_kwh: '18.25',
|
||||
ecs_nuit: 'on', hc_session_committed: 'off', ecs_24h_kwh: '5.5', off_jour_faible_prev: 'off',
|
||||
ecs_nuit: 'on', hc_session_committed: 'off', ecs_24h_kwh: '5.5', ecs_jour_kwh: '3', off_jour_faible_prev: 'off',
|
||||
evcs_priority_active: 'off', forced_off_by_advisor: 'off',
|
||||
};
|
||||
const cas = [
|
||||
@@ -43,6 +43,12 @@ const cas = [
|
||||
attendu: { action: 'turn_on', desired: true, etat: 'jour_faible_eau_froide', owner: true } },
|
||||
{ id: 'jour-faible-energie-illisible-aucune-restriction', snap: { ecs_24h_kwh: 'indisponible' },
|
||||
attendu: { action: 'none', etat: 'jour_faible_energie_illisible', owner: true } },
|
||||
{ id: 'jour-faible-surplus-solaire-chauffe', snap: { water_heater_switch: 'off', off_jour_faible_prev: 'on', pv_power_w: '3500', pv_power_10m_avg_w: '3400', house_ac_power_w: '900' },
|
||||
attendu: { action: 'turn_on', desired: true, etat: 'jour_faible_surplus_solaire', owner: true } },
|
||||
{ id: 'jour-faible-surplus-insuffisant-reste-coupe', snap: { water_heater_switch: 'off', off_jour_faible_prev: 'on', pv_power_w: '1500', house_ac_power_w: '900' },
|
||||
attendu: { action: 'none', etat: 'jour_faible_coupe', owner: true } },
|
||||
{ id: 'jour-faible-sous-tension-sur-batterie-coupe', snap: { water_heater_switch: 'on', pv_power_w: '1500', house_ac_power_w: '2100' },
|
||||
attendu: { action: 'turn_off', desired: false, etat: 'jour_faible_coupe', owner: true } },
|
||||
{ id: 'jour-normal-remise-sous-tension', snap: { ecs_nuit: 'off', water_heater_switch: 'off', off_jour_faible_prev: 'on' },
|
||||
attendu: { action: 'turn_on', desired: true, etat: 'jour_normal_remise_sous_tension', owner: false } },
|
||||
{ id: 'jour-normal-coupure-manuelle-respectee', snap: { ecs_nuit: 'off', water_heater_switch: 'off', off_jour_faible_prev: 'off' },
|
||||
|
||||
@@ -5,6 +5,20 @@
|
||||
// indisponible ne cree ni energie negative ni saut.
|
||||
export const COMPTEUR_CHAUFFE_EAU = 'sensor.tt_chauffe_eau_pleumeur_energie_totale';
|
||||
|
||||
// Energie depuis `depuisMs` (meme regle : somme des hausses ; le point juste
|
||||
// avant la borne sert de reference).
|
||||
export function energieDepuis(serie, depuisMs) {
|
||||
const pts = (serie || []).map((e) => [Date.parse(e.last_changed), Number.parseFloat(e.state)])
|
||||
.filter(([t, v]) => Number.isFinite(t) && Number.isFinite(v));
|
||||
if (!pts.length) return null;
|
||||
let ref = null; const apres = [];
|
||||
for (const p of pts) { if (p[0] <= depuisMs) ref = p; else apres.push(p); }
|
||||
const vals = (ref ? [ref, ...apres] : apres).map((p) => p[1]);
|
||||
let kwh = 0;
|
||||
for (let i = 1; i < vals.length; i++) if (vals[i] > vals[i - 1]) kwh += vals[i] - vals[i - 1];
|
||||
return Math.round(kwh * 100) / 100;
|
||||
}
|
||||
|
||||
export function energieSur24h(serie) {
|
||||
const vals = (serie || []).map((e) => Number.parseFloat(e.state)).filter(Number.isFinite);
|
||||
if (!vals.length) return null; // compteur illisible sur 24 h : on ne dit rien
|
||||
|
||||
@@ -1971,6 +1971,112 @@ journee = dashboard(
|
||||
], frm="now/d", jusqu="now/d")
|
||||
|
||||
|
||||
# =====================================================================
|
||||
# LE CHAUFFE-EAU (07/10/2026) — demande du proprietaire
|
||||
# =====================================================================
|
||||
# Le 06/10 il a pris 5,4 kWh de 10 h a 15 h, l'energie qui manquait a la
|
||||
# batterie. Depuis le 07/10 il chauffe en HC de nuit les jours faibles
|
||||
# (decision du bilan, workflow ECS). Ce tableau dit QUAND il chauffe et SUR
|
||||
# QUOI : l'energie vient du compteur cumule du ballon (historique HA rattrape
|
||||
# depuis le 14/08), le contexte de chaque heure des puissances moyennes de
|
||||
# l'heure — EDF si le reseau fournit plus de 300 W, solaire si la production
|
||||
# couvre la maison, batterie sinon. C'est une ATTRIBUTION par heure, pas un
|
||||
# comptage par electron : une heure moitie soleil moitie batterie est classee
|
||||
# d'un seul cote.
|
||||
ECS_C = "sensor.tt_chauffe_eau_pleumeur_energie_totale"
|
||||
def ecs_heures(select, filtre=""):
|
||||
"""kWh du chauffe-eau par heure locale, avec le contexte de l'heure."""
|
||||
def moy(cle):
|
||||
return (f"coalesce((SELECT avg(valeur) FROM mesure WHERE cle = '{cle}' "
|
||||
f"AND ts >= k.t AND ts < k.t + interval '1 hour'), "
|
||||
f"(SELECT valeur FROM mesure WHERE cle = '{cle}' AND ts < k.t "
|
||||
f"ORDER BY ts DESC LIMIT 1), 0)")
|
||||
return (
|
||||
"WITH h AS (SELECT generate_series(date_trunc('hour', $__timeFrom()::timestamptz),\n"
|
||||
" date_trunc('hour', least(now(), $__timeTo()::timestamptz)), interval '1 hour') AS t),\n"
|
||||
f"v AS (SELECT t, (SELECT valeur FROM mesure WHERE cle = '{ECS_C}' AND ts <= t\n"
|
||||
" ORDER BY ts DESC LIMIT 1) AS e FROM h),\n"
|
||||
"k AS (SELECT t, greatest(0, lead(e) OVER (ORDER BY t) - e) AS kwh FROM v),\n"
|
||||
"c AS (SELECT k.t, k.kwh,\n"
|
||||
f" {moy('sensor.victron_grid_power_l1')} AS reseau,\n"
|
||||
f" {moy('sensor.victron_pv_power_system')} AS pv,\n"
|
||||
f" {moy('sensor.victron_consumption_power_l1')} AS maison\n"
|
||||
" FROM k WHERE k.kwh IS NOT NULL),\n"
|
||||
"x AS (SELECT t, kwh, CASE WHEN reseau > 300 THEN 'EDF' WHEN pv >= maison THEN 'solaire'\n"
|
||||
" ELSE 'batterie' END AS ctx FROM c)\n" + select)
|
||||
|
||||
ECS_PAR_SOURCE = sur # (alias de lisibilite)
|
||||
COULEURS_ECS = [sur("Solaire", [{"id": "color", "value": {"mode": "fixed", "fixedColor": "yellow"}}]),
|
||||
sur("Batterie", [{"id": "color", "value": {"mode": "fixed", "fixedColor": "red"}}]),
|
||||
sur("EDF", [{"id": "color", "value": {"mode": "fixed", "fixedColor": "blue"}}])]
|
||||
|
||||
chauffe_eau = dashboard("evcs-chauffe-eau", "Le chauffe-eau", (
|
||||
"Quand le chauffe-eau chauffe, et sur quoi : solaire, batterie ou EDF. "
|
||||
"Les jours faibles il doit chauffer la nuit sur EDF (decision du bilan "
|
||||
"apres 07:24), jamais sur la batterie."), [
|
||||
|
||||
stat("Maintenant", [sql("A",
|
||||
"SELECT now() AS time,\n"
|
||||
" (SELECT valeur FROM mesure WHERE cle = 'sensor.evcs_chauffe_eau_24h_kwh' ORDER BY ts DESC LIMIT 1) AS \"Sur 24 h\",\n"
|
||||
" (SELECT valeur FROM mesure WHERE cle = 'input_number.victron_water_heater_observed_power_w' ORDER BY ts DESC LIMIT 1) AS \"Puissance\"",
|
||||
fmt="table")],
|
||||
PG, 0, 0, 8, 4,
|
||||
desc="Energie des 24 dernieres heures (securite eau chaude : sous 1,5 kWh "
|
||||
"il chauffe meme un jour faible) et puissance mesuree par le workflow "
|
||||
"ECS sur ses 5 dernieres minutes (morte jusqu'au 07/10).",
|
||||
overrides=[sur("Sur 24 h", [{"id": "unit", "value": "kwatth"}, {"id": "decimals", "value": 2}]),
|
||||
sur("Puissance", [{"id": "unit", "value": "watt"}])]),
|
||||
|
||||
table("Decisions et etats", [sql("A",
|
||||
"SELECT to_char(ts AT TIME ZONE 'Europe/Paris', 'DD/MM HH24:MI') AS \"Quand\", resume AS \"Quoi\"\n"
|
||||
"FROM evenement WHERE genre = 'chauffe_eau' AND $__timeFilter(ts) ORDER BY ts DESC", fmt="table")],
|
||||
PG, 8, 0, 16, 8,
|
||||
desc="Decision quotidienne du bilan (jour faible : eau chaude en HC de "
|
||||
"nuit), mise sous tension / coupure, changements de regime (pompe a "
|
||||
"chaleur, resistance). Journalise depuis le 07/10."),
|
||||
|
||||
ts("Energie par heure, selon d'ou elle venait", [sql("A", ecs_heures(
|
||||
"SELECT t AS time,\n"
|
||||
" CASE WHEN ctx = 'solaire' THEN kwh END AS \"Solaire\",\n"
|
||||
" CASE WHEN ctx = 'batterie' THEN kwh END AS \"Batterie\",\n"
|
||||
" CASE WHEN ctx = 'EDF' THEN kwh END AS \"EDF\"\n"
|
||||
"FROM x ORDER BY t"))],
|
||||
PG, 0, 8, 24, 9, unit="kwatth",
|
||||
desc="Une barre par heure. Contexte de l'heure : EDF si le reseau fournit "
|
||||
"plus de 300 W en moyenne, solaire si la production couvre la maison, "
|
||||
"batterie sinon (attribution par heure). Le rouge est ce qu'on veut "
|
||||
"faire disparaitre : de l'eau chauffee sur la batterie.",
|
||||
extra={"defaults": {"custom": {"drawStyle": "bars", "fillOpacity": 85, "lineWidth": 0,
|
||||
"stacking": {"mode": "normal", "group": "A"},
|
||||
"showPoints": "never", "spanNulls": False}},
|
||||
"overrides": COULEURS_ECS}),
|
||||
|
||||
barchart("Par jour", [sql("A", ecs_heures(
|
||||
"SELECT to_char(t AT TIME ZONE 'Europe/Paris', 'DD/MM') AS \"Jour\",\n"
|
||||
" round(sum(kwh) FILTER (WHERE ctx = 'solaire')::numeric, 2) AS \"Solaire\",\n"
|
||||
" round(sum(kwh) FILTER (WHERE ctx = 'batterie')::numeric, 2) AS \"Batterie\",\n"
|
||||
" round(sum(kwh) FILTER (WHERE ctx = 'EDF')::numeric, 2) AS \"EDF\"\n"
|
||||
"FROM x GROUP BY 1, date_trunc('day', t AT TIME ZONE 'Europe/Paris')\n"
|
||||
"ORDER BY date_trunc('day', t AT TIME ZONE 'Europe/Paris')"), fmt="table")],
|
||||
PG, 0, 17, 24, 8, unit="kwatth",
|
||||
desc="kWh du chauffe-eau par jour et par contexte. Repere : le 06/10, 5,4 kWh "
|
||||
"dont une partie sur la batterie a midi ; un jour faible reussi n'a que "
|
||||
"du bleu (EDF en heures creuses).",
|
||||
overrides=COULEURS_ECS),
|
||||
|
||||
ts("Puissance mesuree et regime", [sql("A",
|
||||
"SELECT ts AS time, valeur AS \"Puissance\" FROM mesure\n"
|
||||
"WHERE cle = 'input_number.victron_water_heater_observed_power_w' AND $__timeFilter(ts) ORDER BY ts")],
|
||||
PG, 0, 25, 24, 8, unit="watt",
|
||||
desc="Puissance moyenne sur 5 minutes calculee par le workflow ECS depuis le "
|
||||
"compteur. Pompe a chaleur : puissance basse, ~5 h pour 55 °C ; "
|
||||
"resistance (~1,2 kW) : de 55 a 65 °C. Disponible depuis le 07/10 "
|
||||
"(avant, la mesure ne lisait jamais ses horodatages).",
|
||||
extra={"defaults": {"custom": {"drawStyle": "line", "lineInterpolation": "stepAfter",
|
||||
"fillOpacity": 20}}}),
|
||||
], frm="now-7d")
|
||||
|
||||
|
||||
base = pathlib.Path('deploy/historisation/grafana/dashboards')
|
||||
for nom, d in [('journee.json', journee),
|
||||
('nuit.json', nuit), ('solaire.json', solaire),
|
||||
@@ -1978,6 +2084,7 @@ for nom, d in [('journee.json', journee),
|
||||
('telephone.json', telephone),
|
||||
('systeme.json', systeme),
|
||||
('vehicules.json', vehicules),
|
||||
('batteries.json', batteries)]:
|
||||
('batteries.json', batteries),
|
||||
('chauffe-eau.json', chauffe_eau)]:
|
||||
(base / nom).write_text(json.dumps(d, indent=2, ensure_ascii=False) + '\n')
|
||||
print(f"{nom:16s} {len(d['panels'])} panneaux")
|
||||
|
||||
@@ -33,7 +33,7 @@ import { creerSchemaKm, ingererReleves, ingererNiveauSaisi, ingererJeep } from '
|
||||
import { texteRapport, envoyer, donneesNuit, texteDepuis } from './rapport-nuit.mjs';
|
||||
import { diagnosticPv, etatCellules, productionJour, chargeSolaire, bilanNuit, etatReseau, importJour, minuitLocal, COMPTEURS_IMPORT, COMPTEUR_EDF, consoJour, COMPTEURS_CONSO, projectionSoir, previsionNuit, vrmVersCourbes, nuitMaisonPrevue } from './diagnostics-agent.mjs';
|
||||
import { transitionsDansSerie } from './allers-retours.mjs';
|
||||
import { energieSur24h, COMPTEUR_CHAUFFE_EAU } from './chauffe-eau.mjs';
|
||||
import { energieSur24h, energieDepuis, COMPTEUR_CHAUFFE_EAU } from './chauffe-eau.mjs';
|
||||
|
||||
const ROOT = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..');
|
||||
const ONCE = process.argv.includes('--once');
|
||||
@@ -99,6 +99,11 @@ const MESURES = [
|
||||
'sensor.jeep_renegade_phev_odometer',
|
||||
'sensor.jeep_renegade_phev_hvbattery_charge',
|
||||
'sensor.jeep_renegade_phev_extrapolated_battery',
|
||||
// Chauffe-eau (07/10/2026) : compteur cumule, puissance observee par le
|
||||
// workflow ECS (morte jusqu'au 07/10, cf. HANDOFF), energie sur 24 h.
|
||||
'sensor.tt_chauffe_eau_pleumeur_energie_totale',
|
||||
'input_number.victron_water_heater_observed_power_w',
|
||||
'sensor.evcs_chauffe_eau_24h_kwh',
|
||||
];
|
||||
|
||||
// ---------------------------------------------------------------------
|
||||
@@ -129,6 +134,10 @@ const TRANSITIONS_SUIVIES = [
|
||||
['input_select.victron_evcs_vehicle', 'intervention','Vehicule selectionne'],
|
||||
['input_select.victron_evcs_strategy', 'intervention','Strategie'],
|
||||
['input_select.victron_evcs_night_charge_mode', 'intervention','Charge de la voiture la nuit'],
|
||||
['switch.tt_chauffe_eau_pleumeur_switch', 'chauffe_eau', 'Chauffe-eau sous tension'],
|
||||
['input_boolean.evcs_balance_ecs_nuit', 'chauffe_eau', 'Chauffe-eau en HC de nuit (jour faible)'],
|
||||
['input_text.evcs_balance_ecs_nuit_jour', 'chauffe_eau', 'Decision du jour pour le chauffe-eau'],
|
||||
['input_text.victron_water_heater_observed_regime', 'chauffe_eau', 'Regime du chauffe-eau'],
|
||||
['input_text.victron_evcs_night_budget', 'vehicule', 'Budget voiture de la nuit fige (kWh|index borne)'],
|
||||
['sensor.gx_device_ess_batterylife_state', 'ess', 'Etat BatteryLife de l ESS'],
|
||||
['input_text.victron_charge_mode', 'ess', 'Mode Auto Charge Manager'],
|
||||
@@ -2076,12 +2085,25 @@ async function publierChauffeEau24h() {
|
||||
{ headers: { Authorization: `Bearer ${HA_TOKEN}` }, signal: AbortSignal.timeout(15000) },
|
||||
);
|
||||
if (!r.ok) throw new Error(`historique HA ${r.status}`);
|
||||
const kwh = energieSur24h((await r.json())[0]);
|
||||
const serie = (await r.json())[0];
|
||||
const kwh = energieSur24h(serie);
|
||||
await publierCapteur('sensor.evcs_chauffe_eau_24h_kwh', kwh === null ? 'indisponible' : kwh, {
|
||||
friendly_name: 'EVCS — chauffe-eau sur 24 h', unit_of_measurement: 'kWh', icon: 'mdi:water-boiler',
|
||||
compteur: COMPTEUR_CHAUFFE_EAU, calcule_a: fin.toISOString(),
|
||||
});
|
||||
return 1;
|
||||
// Depuis la fin de la derniere fenetre HC de nuit (07:24 heure de Paris) :
|
||||
// securite de la douche du soir (20 h), regle « jour faible ».
|
||||
const p = new Intl.DateTimeFormat('sv-SE', { timeZone: 'Europe/Paris', year: 'numeric', month: '2-digit', day: '2-digit', hour: '2-digit', minute: '2-digit', hour12: false }).format(fin);
|
||||
const [jour, hm] = p.split(' ');
|
||||
const decalage = new Date(fin.toLocaleString('en-US', { timeZone: 'Europe/Paris' })).getTime() - new Date(fin.toLocaleString('en-US', { timeZone: 'UTC' })).getTime();
|
||||
let borne = Date.parse(`${jour}T07:24:00Z`) - decalage;
|
||||
if (hm < '07:24') borne -= 24 * 3600e3;
|
||||
const kwhJour = energieDepuis(serie, borne);
|
||||
await publierCapteur('sensor.evcs_chauffe_eau_jour_kwh', kwhJour === null ? 'indisponible' : kwhJour, {
|
||||
friendly_name: 'EVCS — chauffe-eau depuis 07:24', unit_of_measurement: 'kWh', icon: 'mdi:water-boiler',
|
||||
depuis: new Date(borne).toISOString(), calcule_a: fin.toISOString(),
|
||||
});
|
||||
return 2;
|
||||
}
|
||||
|
||||
async function publierImportJour(st) {
|
||||
|
||||
File diff suppressed because one or more lines are too long
Reference in New Issue
Block a user