Files
EVCS/deploy/historisation
Tallagrand Thierry 8e2b4f3e34 Un tableau Grafana pour les cellules, et le plafond du BMS enfin historise
Le GX n'expose que l'agregat du parc : les quinze tensions d'un module
ne sortent que par la console de son BMS. Le tableau les trace, avec
les paliers d'alarme du materiel lui-meme - 3480, 3520, 3550, 3600 mV
- et met la cellule meneuse en regard du plafond de charge.

Le plafond impose par le BMS, grandeur centrale de tout ce diagnostic,
n'etait pas historise : il l'est desormais, comme l'alarme de tension
de cellule. Ils manquaient tous les deux a la memoire.

Les quatre requetes cellule echouent tant que la table n'existe pas ;
elles passeront apres redemarrage du service et import.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KKzkkVTG6KXnGXFFC7X9XJ
2026-09-05 13:41:28 +02:00
..

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)

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 :

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 :

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 :

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 :

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

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

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 :

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.

⚠️ Ce que --dry-run ne peut pas vérifier

L'essai à blanc remplace l'envoi à la base par un compteur : il valide la logique, jamais le SQL. Une erreur de typage passe donc inaperçue.

C'est arrivé le 05/09 : energie_kwh + $3 * $4 / 3600000.0 échouait sur operator is not unique: unknown * unknown, parce que node-postgres envoie ses paramètres sans type et que PostgreSQL ne sait alors pas quel opérateur * choisir. Aucun UPDATE de session n'a abouti pendant vingt minutes, et cela ne s'est vu qu'au journal du conteneur.

Règle : tout paramètre entrant dans un calcul porte son ::float8.

Et après tout déploiement du service :

sudo docker compose restart historisation
sudo docker compose logs --since 2m historisation

⚠️ --since, pas --tail. Le service ne journalise que lorsqu'il a écrit quelque chose : --tail=5 affiche donc des lignes qui peuvent dater de plusieurs minutes, y compris des erreurs déjà corrigées. On a perdu dix minutes là-dessus le 05/09 — le service était réparé, les erreurs affichées étaient antérieures au redémarrage.

Une ligne cycle en echec postérieure au redémarrage vaut arrêt immédiat. Une ligne mesures=… session=… vaut confirmation.

Et la vérification qui ne ment pas, puisqu'elle regarde le résultat et non le journal :

SELECT to_char(fin,'HH24:MI:SS'),
       round(extract(epoch FROM now()-fin)) AS "il y a (s)",
       round(energie_kwh::numeric,3)
FROM session_charge ORDER BY debut DESC LIMIT 1;