Grafana : production du jour par chargeur ; suiveur V1 ajoute a l'export InfluxDB

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011uJ47NxgfpGnuuMr3f2n7Z
This commit is contained in:
Tallagrand Thierry
2026-09-25 15:50:21 +02:00
parent fc2429efc5
commit ac70dcf1e1
5 changed files with 160 additions and 8 deletions

View File

@@ -645,5 +645,15 @@ actifs), tous agents : timer systemd `openclaw-session-rotate.timer` (quotidien
archives (0/89 au 25/09). ⚠️ Une mise a jour majeure de DSM a deja efface une
config systeme (sudo NOPASSWD, §75) : reverifier le timer apres chaque MAJ DSM.
Grafana « Le solaire » : panneau **Production du jour par chargeur** (yield_today
des trois chargeurs + Total, trace seulement si les trois publient). Le suiveur
V1 manquait a InfluxDB : `sensor.v1_*` absent de `influxdb.include` de HA —
ajoute le 25/09 (sauvegarde `configuration.yaml.bak-20260925-v1`, check_config
valide), **effectif au prochain redemarrage de HA, que le proprio fait lui-meme**
(apres : verifier le selecteur vehicule, §47). Le suiveur est aussi ajoute aux
panneaux des modes (etiquette « Suiveur », sinon il tombait dans « Ensemble ») et
du detail par tracker, dont la description attribuait a tort son absence a un
defaut de publication.
---
*Document mis a jour le : 25/09/2026 — bilan de nuit pour l'agent, retention 30 j des journaux OpenClaw*
*Document mis a jour le : 25/09/2026 — bilan de nuit pour l'agent, retention des journaux, production par chargeur dans Grafana*

View File

@@ -285,7 +285,7 @@
"type": "influxdb",
"uid": "evcs-influx"
},
"query": "from(bucket: \"evcs\")\n |> range(start: v.timeRangeStart, stop: v.timeRangeStop)\n |> filter(fn: (r) => r._field == \"state\")\n |> filter(fn: (r) => r._measurement =~ /operation_mode$/)\n |> aggregateWindow(every: v.windowPeriod, fn: last, createEmpty: false)\n |> map(fn: (r) => ({ r with tracker:\n if r._measurement =~ /solar_1/ then \"Sud 1\"\n else if r._measurement =~ /solar_2/ then \"Sud 2\"\n else if r._measurement =~ /solar_3/ then \"Sud 3\"\n else if r._measurement =~ /solar_4/ then \"Ouest\"\n else if r._measurement =~ /mppt1/ then \"Sud-est\"\n else \"Ensemble\" }))\n |> keep(columns: [\"_time\", \"_value\", \"tracker\"])\n |> pivot(rowKey: [\"_time\"], columnKey: [\"tracker\"], valueColumn: \"_value\")"
"query": "from(bucket: \"evcs\")\n |> range(start: v.timeRangeStart, stop: v.timeRangeStop)\n |> filter(fn: (r) => r._field == \"state\")\n |> filter(fn: (r) => r._measurement =~ /operation_mode$/)\n |> aggregateWindow(every: v.windowPeriod, fn: last, createEmpty: false)\n |> map(fn: (r) => ({ r with tracker:\n if r._measurement =~ /^v1_/ then \"Suiveur\"\n else if r._measurement =~ /solar_1/ then \"Sud 1\"\n else if r._measurement =~ /solar_2/ then \"Sud 2\"\n else if r._measurement =~ /solar_3/ then \"Sud 3\"\n else if r._measurement =~ /solar_4/ then \"Ouest\"\n else if r._measurement =~ /mppt1/ then \"Sud-est\"\n else \"Ensemble\" }))\n |> keep(columns: [\"_time\", \"_value\", \"tracker\"])\n |> pivot(rowKey: [\"_time\"], columnKey: [\"tracker\"], valueColumn: \"_value\")"
}
],
"gridPos": {
@@ -342,6 +342,92 @@
}
}
},
{
"type": "timeseries",
"title": "Production du jour par chargeur",
"description": "Le rendement du jour que chaque chargeur declare lui-meme (yield_today), remis a zero chaque nuit. C'est la production REELLE (MANUEL §0), la meme que sensor.evcs_production_jour_kwh et que l'agent vocal. Le Total n'apparait que si les trois chargeurs publient : un chargeur muet donne un total partiel, qu'on ne trace pas. Les trois ne se remettent pas a zero a la meme minute (vers 00:40). Le suiveur n'est exporte que depuis le 25/09.",
"datasource": {
"type": "influxdb",
"uid": "evcs-influx"
},
"targets": [
{
"refId": "A",
"datasource": {
"type": "influxdb",
"uid": "evcs-influx"
},
"query": "from(bucket: \"evcs\")\n |> range(start: v.timeRangeStart, stop: v.timeRangeStop)\n |> filter(fn: (r) => r._measurement == \"kWh\" and r._field == \"value\")\n |> filter(fn: (r) => r.entity_id == \"airtria_mppt1_yield_today\"\n or r.entity_id == \"mppt_450_200_hq2449yr9pt_yield_today\"\n or r.entity_id == \"v1_yield_today\")\n |> aggregateWindow(every: v.windowPeriod, fn: last, createEmpty: false)\n |> map(fn: (r) => ({ r with chargeur:\n if r.entity_id =~ /^airtria/ then \"MPPT 250/70 sud-est\"\n else if r.entity_id =~ /^mppt_450/ then \"RS 450/200 sud + ouest\"\n else \"150/35 suiveur\" }))\n |> group()\n |> keep(columns: [\"_time\", \"_value\", \"chargeur\"])\n |> pivot(rowKey: [\"_time\"], columnKey: [\"chargeur\"], valueColumn: \"_value\")\n |> sort(columns: [\"_time\"])"
},
{
"refId": "B",
"datasource": {
"type": "influxdb",
"uid": "evcs-influx"
},
"query": "from(bucket: \"evcs\")\n |> range(start: v.timeRangeStart, stop: v.timeRangeStop)\n |> filter(fn: (r) => r._measurement == \"kWh\" and r._field == \"value\")\n |> filter(fn: (r) => r.entity_id == \"airtria_mppt1_yield_today\"\n or r.entity_id == \"mppt_450_200_hq2449yr9pt_yield_today\"\n or r.entity_id == \"v1_yield_today\")\n |> aggregateWindow(every: v.windowPeriod, fn: last, createEmpty: false)\n |> map(fn: (r) => ({ r with chargeur:\n if r.entity_id =~ /^airtria/ then \"MPPT 250/70 sud-est\"\n else if r.entity_id =~ /^mppt_450/ then \"RS 450/200 sud + ouest\"\n else \"150/35 suiveur\" }))\n |> group()\n |> keep(columns: [\"_time\", \"_value\", \"chargeur\"])\n |> pivot(rowKey: [\"_time\"], columnKey: [\"chargeur\"], valueColumn: \"_value\")\n |> sort(columns: [\"_time\"])\n |> fill(usePrevious: true, column: \"MPPT 250/70 sud-est\")\n |> fill(usePrevious: true, column: \"RS 450/200 sud + ouest\")\n |> fill(usePrevious: true, column: \"150/35 suiveur\")\n |> filter(fn: (r) => exists r[\"MPPT 250/70 sud-est\"] and exists r[\"RS 450/200 sud + ouest\"]\n and exists r[\"150/35 suiveur\"])\n |> map(fn: (r) => ({ _time: r._time, Total: r[\"MPPT 250/70 sud-est\"]\n + r[\"RS 450/200 sud + ouest\"] + r[\"150/35 suiveur\"] }))"
}
],
"gridPos": {
"x": 0,
"y": 23,
"w": 24,
"h": 8
},
"fieldConfig": {
"defaults": {
"unit": "kwatth",
"color": {
"mode": "palette-classic"
},
"custom": {
"lineWidth": 2,
"fillOpacity": 8,
"showPoints": "never",
"spanNulls": true,
"axisBorderShow": false,
"gradientMode": "opacity"
}
},
"overrides": [
{
"matcher": {
"id": "byName",
"options": "Total"
},
"properties": [
{
"id": "color",
"value": {
"mode": "fixed",
"fixedColor": "yellow"
}
},
{
"id": "custom.lineWidth",
"value": 3
},
{
"id": "custom.fillOpacity",
"value": 0
}
]
}
]
},
"options": {
"legend": {
"displayMode": "list",
"placement": "bottom",
"showLegend": true,
"calcs": []
},
"tooltip": {
"mode": "multi",
"sort": "desc"
}
}
},
{
"type": "timeseries",
"title": "Batterie",

View File

@@ -796,7 +796,7 @@
{
"type": "table",
"title": "Detail par tracker",
"description": "Les 52 panneaux en quatre plans : sud-est 40 degres, trois chaines sud 35 degres, ouest 45 degres. Le suiveur de trois panneaux ne publie pas de mode de fonctionnement et n'apparait donc pas ici.",
"description": "Les 52 panneaux en quatre plans : sud-est 40 degres, trois chaines sud 35 degres, ouest 45 degres, et le suiveur de trois panneaux. Le suiveur n'est exporte vers InfluxDB que depuis le 25/09 (il manquait a la liste d'export de Home Assistant, et non faute de publier).",
"datasource": {
"type": "influxdb",
"uid": "evcs-influx"
@@ -808,7 +808,7 @@
"type": "influxdb",
"uid": "evcs-influx"
},
"query": "import \"strings\"\n\nbase = from(bucket: \"evcs\")\n |> range(start: -30m)\n |> filter(fn: (r) => r._field == \"value\")\n |> filter(fn: (r) => r._measurement == \"V\" or r._measurement == \"A\" or r._measurement == \"W\")\n |> last()\n\nrs = base\n |> filter(fn: (r) => strings.containsStr(v: r.entity_id, substr: \"pv_tracker_solar_\"))\n |> map(fn: (r) => ({ r with tracker:\n if strings.containsStr(v: r.entity_id, substr: \"solar_1\") then \"Sud 1 (10 pan.)\"\n else if strings.containsStr(v: r.entity_id, substr: \"solar_2\") then \"Sud 2 (10 pan.)\"\n else if strings.containsStr(v: r.entity_id, substr: \"solar_3\") then \"Sud 3 (10 pan.)\"\n else \"Ouest (9 pan.)\" }))\n\nse = base\n |> filter(fn: (r) => r.entity_id == \"airtria_mppt1_pv_bus_voltage\"\n or r.entity_id == \"airtria_mppt1_pv_current\"\n or r.entity_id == \"airtria_mppt1_pv_yield_power\")\n |> map(fn: (r) => ({ r with tracker: \"Sud-est (10 pan.)\" }))\n\nunion(tables: [rs, se])\n |> keep(columns: [\"tracker\", \"_measurement\", \"_value\"])\n |> pivot(rowKey: [\"tracker\"], columnKey: [\"_measurement\"], valueColumn: \"_value\")\n |> sort(columns: [\"tracker\"])"
"query": "import \"strings\"\n\nbase = from(bucket: \"evcs\")\n |> range(start: -30m)\n |> filter(fn: (r) => r._field == \"value\")\n |> filter(fn: (r) => r._measurement == \"V\" or r._measurement == \"A\" or r._measurement == \"W\")\n |> last()\n\nrs = base\n |> filter(fn: (r) => strings.containsStr(v: r.entity_id, substr: \"pv_tracker_solar_\"))\n |> map(fn: (r) => ({ r with tracker:\n if strings.containsStr(v: r.entity_id, substr: \"solar_1\") then \"Sud 1 (10 pan.)\"\n else if strings.containsStr(v: r.entity_id, substr: \"solar_2\") then \"Sud 2 (10 pan.)\"\n else if strings.containsStr(v: r.entity_id, substr: \"solar_3\") then \"Sud 3 (10 pan.)\"\n else \"Ouest (9 pan.)\" }))\n\nse = base\n |> filter(fn: (r) => r.entity_id == \"airtria_mppt1_pv_bus_voltage\"\n or r.entity_id == \"airtria_mppt1_pv_current\"\n or r.entity_id == \"airtria_mppt1_pv_yield_power\")\n |> map(fn: (r) => ({ r with tracker: \"Sud-est (10 pan.)\" }))\n\nsv = base\n |> filter(fn: (r) => r.entity_id == \"v1_pv_bus_voltage\"\n or r.entity_id == \"v1_pv_current\"\n or r.entity_id == \"v1_pv_yield_power\")\n |> map(fn: (r) => ({ r with tracker: \"Suiveur (3 pan.)\" }))\n\nunion(tables: [rs, se, sv])\n |> keep(columns: [\"tracker\", \"_measurement\", \"_value\"])\n |> pivot(rowKey: [\"tracker\"], columnKey: [\"_measurement\"], valueColumn: \"_value\")\n |> sort(columns: [\"tracker\"])"
}
],
"gridPos": {

View File

@@ -34,6 +34,9 @@ influxdb:
- sensor.evcs_*
- sensor.mppt_*
- sensor.airtria_*
# Le suiveur (MPPT 150/35, 3 panneaux) : absent jusqu'au 25/09, donc ni
# son rendement journalier ni son mode dans les tableaux Grafana.
- sensor.v1_*
- sensor.gx_device_*
- sensor.sun_solar_elevation
- binary_sensor.victron_*

View File

@@ -341,6 +341,22 @@ nuit = dashboard("evcs-nuit", "La nuit", (
# =====================================================================
# 2. LE SOLAIRE — excedent, censure, et ce que les chargeurs disent
# =====================================================================
# Rendement journalier declare par chaque chargeur (MANUEL §0 : la production
# reelle du jour est leur SOMME, jamais un sous-tracker du 450/200 en plus).
RENDEMENTS_FLUX = (
'from(bucket: "evcs")\n'
' |> range(start: v.timeRangeStart, stop: v.timeRangeStop)\n'
' |> filter(fn: (r) => r._measurement == "kWh" and r._field == "value")\n'
' |> filter(fn: (r) => r.entity_id == "airtria_mppt1_yield_today"\n'
' or r.entity_id == "mppt_450_200_hq2449yr9pt_yield_today"\n'
' or r.entity_id == "v1_yield_today")\n'
' |> aggregateWindow(every: v.windowPeriod, fn: last, createEmpty: false)\n'
' |> map(fn: (r) => ({ r with chargeur:\n'
' if r.entity_id =~ /^airtria/ then "MPPT 250/70 sud-est"\n'
' else if r.entity_id =~ /^mppt_450/ then "RS 450/200 sud + ouest"\n'
' else "150/35 suiveur" }))\n'
' |> group()\n')
solaire = dashboard("evcs-solaire", "Le solaire", (
"La production mesuree n'est jamais la production disponible : sans "
"injection, le MPPT ne produit que ce que le systeme peut absorber. "
@@ -393,7 +409,8 @@ solaire = dashboard("evcs-solaire", "Le solaire", (
' |> filter(fn: (r) => r._measurement =~ /operation_mode$/)\n'
' |> aggregateWindow(every: v.windowPeriod, fn: last, createEmpty: false)\n'
' |> map(fn: (r) => ({ r with tracker:\n'
' if r._measurement =~ /solar_1/ then "Sud 1"\n'
' if r._measurement =~ /^v1_/ then "Suiveur"\n'
' else if r._measurement =~ /solar_1/ then "Sud 1"\n'
' else if r._measurement =~ /solar_2/ then "Sud 2"\n'
' else if r._measurement =~ /solar_3/ then "Sud 3"\n'
' else if r._measurement =~ /solar_4/ then "Ouest"\n'
@@ -411,6 +428,35 @@ solaire = dashboard("evcs-solaire", "Le solaire", (
"voltage_current_limited": {"color": "orange", "index": 1},
"off": {"color": "dark-blue", "index": 2}}}]),
ts("Production du jour par chargeur", [
flux("A", RENDEMENTS_FLUX + ' |> keep(columns: ["_time", "_value", "chargeur"])\n'
' |> pivot(rowKey: ["_time"], columnKey: ["chargeur"], valueColumn: "_value")\n'
' |> sort(columns: ["_time"])'),
flux("B", RENDEMENTS_FLUX +
' |> keep(columns: ["_time", "_value", "chargeur"])\n'
' |> pivot(rowKey: ["_time"], columnKey: ["chargeur"], valueColumn: "_value")\n'
' |> sort(columns: ["_time"])\n'
' |> fill(usePrevious: true, column: "MPPT 250/70 sud-est")\n'
' |> fill(usePrevious: true, column: "RS 450/200 sud + ouest")\n'
' |> fill(usePrevious: true, column: "150/35 suiveur")\n'
' |> filter(fn: (r) => exists r["MPPT 250/70 sud-est"] and exists r["RS 450/200 sud + ouest"]\n'
' and exists r["150/35 suiveur"])\n'
' |> map(fn: (r) => ({ _time: r._time, Total: r["MPPT 250/70 sud-est"]\n'
' + r["RS 450/200 sud + ouest"] + r["150/35 suiveur"] }))')],
IN, 0, 23, 24, 8, "kwatth",
"Le rendement du jour que chaque chargeur declare lui-meme (yield_today), "
"remis a zero chaque nuit. C'est la production REELLE (MANUEL §0), la meme "
"que sensor.evcs_production_jour_kwh et que l'agent vocal. Le Total "
"n'apparait que si les trois chargeurs publient : un chargeur muet donne "
"un total partiel, qu'on ne trace pas. Les trois ne se remettent pas a "
"zero a la meme minute (vers 00:40). Le suiveur n'est exporte que depuis "
"le 25/09.",
extra={"overrides": [
{"matcher": {"id": "byName", "options": "Total"},
"properties": [{"id": "color", "value": {"mode": "fixed", "fixedColor": "yellow"}},
{"id": "custom.lineWidth", "value": 3},
{"id": "custom.fillOpacity", "value": 0}]}]}),
ts("Batterie", [
sql("A", "SELECT date_trunc('minute', ts) AS time, avg(valeur) AS \"SOC\"\n"
"FROM mesure WHERE cle = 'sensor.victron_battery_soc'\n"
@@ -852,7 +898,13 @@ se = base
or r.entity_id == "airtria_mppt1_pv_yield_power")
|> map(fn: (r) => ({ r with tracker: "Sud-est (10 pan.)" }))
union(tables: [rs, se])
sv = base
|> filter(fn: (r) => r.entity_id == "v1_pv_bus_voltage"
or r.entity_id == "v1_pv_current"
or r.entity_id == "v1_pv_yield_power")
|> map(fn: (r) => ({ r with tracker: "Suiveur (3 pan.)" }))
union(tables: [rs, se, sv])
|> keep(columns: ["tracker", "_measurement", "_value"])
|> pivot(rowKey: ["tracker"], columnKey: ["_measurement"], valueColumn: "_value")
|> sort(columns: ["tracker"])'''
@@ -968,8 +1020,9 @@ systeme = dashboard("evcs-systeme", "Le systeme", (
dict(table("Detail par tracker", [flux("A", TRACKERS_FLUX)],
IN, 5, 7, 13, 8,
"Les 52 panneaux en quatre plans : sud-est 40 degres, trois chaines sud "
"35 degres, ouest 45 degres. Le suiveur de trois panneaux ne publie pas "
"de mode de fonctionnement et n'apparait donc pas ici.",
"35 degres, ouest 45 degres, et le suiveur de trois panneaux. Le suiveur "
"n'est exporte vers InfluxDB que depuis le 25/09 (il manquait a la liste "
"d'export de Home Assistant, et non faute de publier).",
overrides=[sur("tracker", [{"id": "displayName", "value": "Tracker"},
{"id": "custom.width", "value": 200}]),
sur("V", [{"id": "displayName", "value": "Tension"},