La console VRM est hebergee dans le cloud Victron et disparait quand une coupure d'orage emporte Internet. Meme vision, lue sur le reseau local : reseau, charges, batterie, borne, production, detail par tracker, les flux dans le temps et l'energie heure par heure. Tout dans Grafana, rien dans Home Assistant — et pour une raison trouvee en verifiant si son tableau Energie pouvait servir : gx_device_pv_energy et les compteurs d'energie batterie du GX se remettent a zero sans le signaler. state_class total sans last_reset, donc HA n'auto-detecte pas la remise a zero et a enregistre -373,35 kWh sur l'heure de 07:00. Les energies sont donc obtenues en INTEGRANT LA PUISSANCE, methode qui ignore le probleme. Trois versions du calcul avant la bonne : somme ponderee (laissait des trous, une grandeur stable n'etant jamais reecrite), moyenne des minutes presentes (sur-comptait : 5,13 kWh a 01 h pour 22 minutes observees), puis axe des minutes complet + report + avg x count/60 = energie reellement accumulee, correcte y compris pour l'heure en cours. Recoupement avec la capture VRM du proprietaire : 8,07 kWh de reseau contre "Depuis reseau 9,0 kWh", l'ecart valant exactement les treize minutes entre l'ouverture des heures creuses et le demarrage du service d'historisation. 45/45 requetes verifiees contre les bases reelles. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01KKzkkVTG6KXnGXFFC7X9XJ
282 lines
12 KiB
Markdown
282 lines
12 KiB
Markdown
# Mémoire de mesures EVCS
|
||
|
||
Deux bases sur **10.10.10.22**, décidées le 04/09/2026 : on met les deux en
|
||
service et on décide à l'usage s'il faut garder les deux.
|
||
|
||
| | rôle | source | résolution |
|
||
|---|---|---|---|
|
||
| **PostgreSQL** | ce que n8n **calcule** — bilan, décisions, prévisions publiées | workflows n8n | cadence des workflows |
|
||
| **InfluxDB** | ce que Home Assistant **observe** — tous les états du périmètre énergie | intégration HA | chaque changement d'état |
|
||
|
||
Le raisonnement complet et le comparatif : `HANDOFF.md` §42 et suivantes.
|
||
|
||
## Pourquoi pas sur 10.10.10.17
|
||
|
||
Cette machine porte déjà Home Assistant, n8n, openclaw et open-webui. Une
|
||
mémoire hébergée par la machine dont elle documente les pannes ne survit pas à
|
||
celles-ci. Le volume n'est pas l'argument : les deux Synology ont des téraoctets
|
||
libres.
|
||
|
||
## Mise en service
|
||
|
||
### 1. Les deux bases (à faire sur 10.10.10.22)
|
||
|
||
```sh
|
||
cd /volume2/projects/EVCS/EVCS/deploy/historisation
|
||
bash preparer-env.sh # génère .env avec des secrets aléatoires
|
||
docker compose up -d
|
||
docker compose logs -f
|
||
```
|
||
|
||
### D'où viennent les secrets
|
||
|
||
| secret | origine |
|
||
|---|---|
|
||
| `HA_TOKEN` | **existe déjà** — repris du `.env` à la racine du dépôt |
|
||
| `POSTGRES_PASSWORD` | **créé ici** — la base n'existe pas encore, ce fichier décide de son mot de passe |
|
||
| `DOCKER_INFLUXDB_INIT_PASSWORD` | **créé ici**, idem (InfluxDB exige au moins 8 caractères) |
|
||
| `DOCKER_INFLUXDB_INIT_ADMIN_TOKEN` | **créé ici** — c'est le jeton que Home Assistant utilisera pour écrire |
|
||
|
||
Aucun ne s'obtient auprès de quelqu'un, et aucun ne doit être choisi à la main :
|
||
personne ne les tape jamais — les conteneurs les lisent dans `.env`, et Home
|
||
Assistant lit le jeton dans son propre `secrets.yaml`. Une phrase inventée est
|
||
plus courte et plus devinable qu'elle n'en a l'air ; `openssl rand` ne l'est pas.
|
||
|
||
`preparer-env.sh` refuse d'écraser un `.env` existant : régénérer les mots de
|
||
passe d'une base déjà créée la rendrait inaccessible, puisqu'elle continuerait
|
||
d'attendre les anciens.
|
||
|
||
Vérifications :
|
||
|
||
```sh
|
||
docker compose exec postgres psql -U evcs -d evcs -c '\dt'
|
||
curl -s http://10.10.10.22:8086/health
|
||
docker compose logs historisation --tail 5
|
||
```
|
||
|
||
### Ports : 5433, et pas 5432
|
||
|
||
DSM fait tourner **son propre PostgreSQL sur 5432**, pour ses services internes.
|
||
Notre base est donc publiée sur **5433**. Ce port ne sert qu'à l'analyse depuis
|
||
un poste de travail :
|
||
|
||
```sh
|
||
psql -h 10.10.10.22 -p 5433 -U evcs -d evcs
|
||
```
|
||
|
||
Les trois conteneurs, eux, se parlent par le réseau interne du compose et par
|
||
nom de service (`postgres:5432`) — ni adresse IP en dur, ni dépendance au port
|
||
publié. Vérifier ce qui occupe un port avant de conclure :
|
||
|
||
```sh
|
||
sudo ss -lntp | grep -E '5432|5433|8086'
|
||
```
|
||
|
||
Le schéma (`schema.sql`) est appliqué **une seule fois**, à la création de la
|
||
base. Pour le faire évoluer ensuite, écrire une migration et la passer à `psql` :
|
||
recréer le volume effacerait l'historique, ce qui est précisément ce qu'on
|
||
cherche à ne plus faire.
|
||
|
||
### 2. Brancher Home Assistant sur InfluxDB
|
||
|
||
⚠️ **Demande un redémarrage de Home Assistant**, pendant lequel le contrôleur
|
||
EVCS ne décide plus rien. **Jamais entre 01:24 et 07:24**, ni pendant une charge.
|
||
Le bon créneau est le milieu de matinée.
|
||
|
||
1. mettre le jeton dans `secrets.yaml` sous la clé `influxdb_token` ;
|
||
2. coller le bloc de `ha-influxdb.yaml` dans `configuration.yaml` ;
|
||
3. `homeassistant.check_config`, puis redémarrer.
|
||
|
||
### 3. Alimenter PostgreSQL depuis n8n
|
||
|
||
À faire après l'étape 1 — n8n a besoin que la base existe pour qu'on y crée
|
||
l'identifiant de connexion.
|
||
|
||
## Événements : détectés par le service, annotés à la main
|
||
|
||
La table `evenement` existe parce qu'une courbe sans ses événements ne
|
||
s'interprète pas : « le rendement a changé le 12 novembre » ne veut rien dire
|
||
sans la ligne qui dit qu'un pack a été rebranché ce jour-là.
|
||
|
||
**Le service consigne seul ce qu'il peut constater** — il voit passer les états
|
||
toutes les minutes, autant qu'il en tire les conclusions plutôt que d'attendre
|
||
que quelqu'un y pense :
|
||
|
||
| genre | ce qui est consigné |
|
||
|---|---|
|
||
| `systeme` | démarrage du service, redémarrage de Home Assistant, épisode d'indisponibilité avec sa durée |
|
||
| `reseau` | contacteur EDF, session réseau de nuit, reprise nuit, verrou hystérésis |
|
||
| `vehicule` | véhicule branché ou débranché |
|
||
| `intervention` | changement de stratégie ou de véhicule sélectionné |
|
||
| `ess` | état BatteryLife, mode de l'Auto Charge Manager |
|
||
| `bilan` | réseau nécessaire, vigilance orage, production bridée |
|
||
|
||
On suit des états **discrets** dont chaque changement mérite une ligne. Les
|
||
grandeurs continues sont dans `mesure`, les décisions du contrôleur dans
|
||
`decision` : un événement qui double une autre table est du bruit. Une lecture
|
||
`unavailable` n'est pas une transition mais une absence de lecture — elle n'est
|
||
pas consignée.
|
||
|
||
Le redémarrage de Home Assistant est detecté **sans capteur d'uptime**, qui
|
||
n'existe pas sur cette installation : au redémarrage HA restaure ses helpers et
|
||
leur pose un `last_changed` neuf. Le 04/09, 352 entités partageaient la seconde
|
||
du redémarrage, dont 183 `input_number`. Le service prend donc le **mode** des
|
||
`last_changed` des `input_number` — aucune entité codée en dur, et l'estimation
|
||
se corrige d'elle-même.
|
||
|
||
**Les annotations qu'aucun service ne peut deviner** — « pack rebranché »,
|
||
« correctif déployé » — s'écrivent à la main. Pour cela le rôle d'analyse a
|
||
besoin d'un `INSERT` sur cette seule table :
|
||
|
||
```sh
|
||
sudo docker compose exec -T postgres psql -U evcs -d evcs -c \
|
||
"GRANT INSERT ON evenement TO evcs_lecture;
|
||
GRANT USAGE, SELECT ON SEQUENCE evenement_id_seq TO evcs_lecture;"
|
||
```
|
||
|
||
`INSERT` seulement : ni `UPDATE`, ni `DELETE`. On peut ajouter une annotation,
|
||
jamais réécrire ni effacer les précédentes — y compris les siennes. C'est ce
|
||
qu'on attend d'un journal, et ça laisse les tables de mesure hors de portée.
|
||
|
||
## Accès en lecture pour l'analyse
|
||
|
||
```sh
|
||
sudo bash creer-role-lecture.sh
|
||
```
|
||
|
||
Crée le rôle `evcs_lecture` en **lecture seule** et dépose son mot de passe dans
|
||
le `.env` à la racine du dépôt. Le mot de passe est généré, jamais choisi, et
|
||
**n'est pas affiché** : il va du générateur au fichier, donc rien à recopier et
|
||
rien à exposer dans une capture.
|
||
|
||
Deux raisons de ne pas analyser avec le compte `evcs` : une mémoire dont tout
|
||
l'intérêt est de ne pas être réécrite ne se consulte pas avec un compte qui peut
|
||
écrire, et un mot de passe rangé dans un fichier lisible ne doit pas être celui
|
||
qui gouverne la base.
|
||
|
||
Le script est rejouable : si le rôle existe déjà et que son mot de passe est
|
||
dans le `.env`, il ne fait rien.
|
||
|
||
## Ce qu'aucune des deux ne fait
|
||
|
||
Elles ne remontent pas dans le passé. La mémoire commence le jour où on les
|
||
branche : c'est un argument pour les mettre en service vite, pas pour les
|
||
mettre en service parfaitement.
|
||
|
||
## Sauvegarde
|
||
|
||
Les deux bases vivent dans des volumes Docker nommés, donc **hors du dépôt et
|
||
hors des sauvegardes Home Assistant**. Une mémoire non sauvegardée est une
|
||
mémoire qu'on perdra une fois — voir `HANDOFF.md` §34, qui raconte la même
|
||
histoire pour Home Assistant.
|
||
|
||
---
|
||
|
||
## Consultation — Grafana (ajouté le 04/09/2026)
|
||
|
||
### Ce qu'il y a à consulter, et où
|
||
|
||
| Base | Où | Ce qu'elle garde | Consultable dans Grafana |
|
||
|---|---|---|---|
|
||
| **PostgreSQL `evcs`** | 10.10.10.22:5433 | ce que le système **calcule** : bilan, décisions, prévision publiée, événements | oui |
|
||
| **InfluxDB `evcs`** | 10.10.10.22:8086 | ce que HA **observe**, ~1 point/s, 113 grandeurs | oui |
|
||
| SQLite de Home Assistant | 10.10.10.17 | son historique brut (20 j) et ses statistiques long terme (186 j) | **non — volontairement** |
|
||
|
||
Le PostgreSQL du port **5432** appartient à DSM et à ses services internes.
|
||
Ce n'est pas une base du projet ; ne pas s'y connecter.
|
||
|
||
L'historique de Home Assistant reste consulté **dans Home Assistant**. L'y
|
||
brancher demanderait de lire un fichier SQLite de 4,4 Go en mode WAL, à travers
|
||
un partage réseau, pendant que HA écrit dedans : c'est la meilleure façon de
|
||
lire une base incohérente, et le risque n'est pas payé en retour puisque tout
|
||
ce qui compte pour l'avenir arrive désormais dans InfluxDB à une résolution
|
||
bien meilleure.
|
||
|
||
### Mise en service
|
||
|
||
```bash
|
||
cd /volume2/projects/EVCS/EVCS/deploy/historisation
|
||
bash preparer-grafana.sh # génère le mot de passe admin, l'affiche une fois
|
||
sudo docker compose up -d grafana
|
||
sudo docker compose logs -f grafana
|
||
```
|
||
|
||
⚠️ **`sudo` est obligatoire sur DSM** : l'utilisateur ordinaire n'a pas accès
|
||
au socket Docker (`permission denied while trying to connect to the Docker
|
||
daemon socket`).
|
||
|
||
⚠️ **Aucun `git pull` n'est nécessaire.** `/volume2/projects` sur le Syno et
|
||
`/mnt/projects` sur le poste de travail sont **le même dossier** (export NFS
|
||
depuis 10.10.10.22) : il n'existe qu'une seule copie du dépôt, et un fichier
|
||
écrit depuis le poste est déjà là côté Syno.
|
||
|
||
Puis **http://10.10.10.22:3001** — port 3001 et non 3000, occupé sur cette
|
||
machine par autre chose.
|
||
|
||
Mot de passe perdu :
|
||
`grep GRAFANA_ADMIN_PASSWORD deploy/historisation/.env`
|
||
|
||
### Les trois tableaux de bord
|
||
|
||
- **La nuit** — le SOC réel contre le SOC exigé par le bilan, la marge entre
|
||
les deux, la session réseau, et ce que la nuit a coûté. C'est la question qui
|
||
avait demandé une nuit d'archéologie le 03/09.
|
||
- **Le solaire** — production, maison, et surtout **l'excédent** (la grandeur
|
||
qui décide d'un démarrage depuis le 03/09), avec la censure du MPPT et le
|
||
mode déclaré par chacun des sept chargeurs.
|
||
- **Prévision contre réel** — le biais de forme de la prévision, question
|
||
ouverte qui demande des semaines de données.
|
||
- **Le système** — réplique locale de la vue générale VRM : réseau, charges,
|
||
batterie, borne, production, **détail par tracker**, les flux dans le temps
|
||
et l'énergie heure par heure. Existe parce que la console Victron est
|
||
hébergée dans le cloud et disparaît quand une coupure d'orage emporte
|
||
Internet. Tout y est lu sur le réseau local.
|
||
|
||
⚠️ Les énergies sont obtenues **en intégrant la puissance**, jamais en lisant
|
||
les compteurs kWh du GX : `gx_device_pv_energy` et les compteurs d'énergie
|
||
batterie se remettent à zéro sans le signaler, et Home Assistant enregistre
|
||
alors une variation de −373 kWh sur une heure (vérifié le 04/09). Les
|
||
compteurs `*_total_yield` des chargeurs et ceux du MultiPlus, eux, sont
|
||
monotones et sains.
|
||
- **Sur le téléphone** — une seule colonne, six panneaux, et la réponse en
|
||
premier. À épingler sur l'écran d'accueil de l'iPhone :
|
||
`http://10.10.10.22:3001/d/evcs-telephone?kiosk` (Safari → Partager → Sur
|
||
l'écran d'accueil). `?kiosk` retire les menus.
|
||
|
||
⚠️ Contrainte de conception à connaître avant d'y ajouter quoi que ce soit :
|
||
sous ~770 px de large, Grafana passe **chaque** panneau en pleine largeur et
|
||
les empile. Le nombre de panneaux est donc le nombre d'écrans à faire
|
||
défiler — d'où les chiffres regroupés dans deux panneaux « stat » plutôt que
|
||
répartis en six.
|
||
|
||
Les événements de la table `evenement` apparaissent en **marqueurs verticaux
|
||
orange sur tous les graphiques** : une courbe sans ses événements ne
|
||
s'interprète pas.
|
||
|
||
### Modifier un tableau de bord
|
||
|
||
Ils viennent du dépôt git, comme le code des nodes n8n — et pour la même
|
||
raison : une modification faite dans l'interface serait écrasée au prochain
|
||
démarrage, en silence. Le générateur est `tools/gen-grafana-dashboards.py` :
|
||
|
||
```bash
|
||
python3 tools/gen-grafana-dashboards.py # réécrit les trois JSON
|
||
node tools/verifier-dashboards.mjs # rejoue CHAQUE requête pour de vrai
|
||
```
|
||
|
||
`verifier-dashboards.mjs` exécute les requêtes SQL contre PostgreSQL et les
|
||
requêtes Flux contre InfluxDB, macros de Grafana substituées. Sans lui, une
|
||
requête cassée ne se voit qu'à l'écran, panneau par panneau. Sortie non nulle
|
||
si une seule échoue.
|
||
|
||
Grafana relit le dossier toutes les 30 s : pas besoin de redémarrer.
|
||
|
||
Pour explorer librement sans rien casser : **« Save as… »** depuis n'importe
|
||
quel tableau de bord en crée une copie personnelle et modifiable.
|
||
|
||
### Ce que Grafana ne peut pas faire
|
||
|
||
Il ne **stocke** rien : il interroge les deux bases à la demande. Le perdre ne
|
||
perd aucune mesure. Il n'écrit rien non plus — sa connexion PostgreSQL utilise
|
||
le rôle `evcs_lecture`, en lecture seule.
|