Le mot de passe airtria_ro est desormais lu depuis AIRTRIA_RO_PASSWORD (variable d'environnement, sinon .env monte dans le conteneur), plus depuis .pg-airtria : EVCS renouvelle ce mot de passe, un fichier fige faisait echouer l'auth EN SILENCE. readPgPassword LEVE (rattrapable) au lieu de process.exit ; mot de passe resolu paresseusement dans makeClient. Nouveau module concierge-health.js (meme patron que le moniteur LLM) : battement de coeur horodate a CHAQUE cycle detect, statut live dans HA, et alerte WhatsApp UNIQUEMENT sur transition ok<->erreur (dedup, pas de rappel periodique). Chemin HA independant de PostgreSQL : l'alerte auth KO part meme quand c'est l'auth qui tombe. Best-effort : si les helpers HA manquent, le concierge n'echoue pas. - concierge-health.test.js : machine a etats (dedup + transitions) verifiee, sans reseau - concierge-health-ha-package.yaml.draft : helpers HA (a integrer cote proprietaire) - SPEC : source du secret corrigee + section supervision Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RZsbhyGmRwZogdoBwT6C6y
24 KiB
Concierge véhicule Airtria — spécification (convenue OpenClaw ↔ EVCS, 2026-09-13)
🔁 MISE À JOUR 2026-09-15 : déclenchement AU BRANCHEMENT (plus à la session)
Le propriétaire veut la question dès le BRANCHEMENT (au branchement du soir, la borne
ne tire rien avant les HC de 01:24 ; attendre une session laissait la 1re mal étiquetée).
concierge-live.js détecte désormais sur les transitions 'on' de evenement (plus sur
session_charge). Le helper porte branchement = ts::text VERBATIM de la transition
'on' (PAS horodatage_ha, antérieur de 3-51 s → EVCS raterait le branchement en cours),
qui remplace sessions. Fraîcheur = sélecteur changé dans [on−10 min, min(now, on+30 min)]
(EVCS). Règle de puissance abandonnée au branchement (pas de session/p_max) → on demande
pour TOUT branchement périmé, invité géré par la réponse. Division : Airtria détecte le
branchement + demande + publie ; EVCS résout, mémorise, applique aux sessions du câble au
fur et à mesure (HC 01:24 comprises), refuse les contradictions. Validé contre l'historique
(15 branchements, distribution de fraîcheur inchangée) ; déployé, test de portée vert.
🟢 LIVE EN PRODUCTION depuis 2026-09-14 02:22
Kill-switch airtria_concierge_enabled = ON, lecteur EVCS déployé (commit d8ef69b).
Preuve end-to-end réelle : session 12/09 11:55 → JEEP, vehicule_sur=t, evenement
origine=airtria/source_airtria=whatsapp ; cycle suivant idempotent (etiquette=0).
Colonne : 6 confirmées (arbitrages voix du 13/09) / 13 à confirmer. Défaut vehicule_sur
basculé à false, 17 lignes rattrapées. Bug de format clef (PG …+02 vs ISO …Z, même
instant) attrapé au dry-run EVCS et corrigé côté service (canonisation ISO avant dédup) —
côté Airtria rien à changer, on envoie la clef verbatim. Véhicule débranché 14/09 01:31 :
prochaine question sur un branchement neuf. Le concierge tourne en régime réel.
VALIDÉ END-TO-END (2026-09-13, essai réel, kill-switch OFF)
Simulation sur le branchement 12/09 11:55 : détection → question WhatsApp → réponse
« JEEP » du propriétaire → routage → helper publié
{"sessions":["2026-09-12 11:55:54.493+02"],"label":"JEEP","confirmed_at":…,"source":"whatsapp"}
→ confirmation « ✅ Noté ». Base non touchée (kill-switch OFF).
⚠️ LEÇON ROUTAGE : le modèle local (qwen) IGNORE une consigne de réponse discrète
(a répondu « Bonjour Thierry » au lieu de router). Il faut un RÉFLEXE PRIORITAIRE en
tête du traitement WhatsApp (AGENTS.md §5.0) : vérifier concierge-pending.json et lancer
answer AVANT toute autre réponse. Avec ça, le routage passe de façon fiable.
Cron concierge-vehicule ACTIVÉ (enabled) → mode essai : une vraie charge à sélecteur
périmé déclenchera une vraie question. Reste pour appliquer en base : kill-switch ON +
lecteur EVCS (après validation de son dry-run).
État d'avancement (2026-09-13)
Fichiers dans le workspace : concierge-detection.js (détection + calibration),
concierge-write.js (écriture helper, entité unique + relecture),
concierge-write.test.js (test de portée, VERT), concierge-live.js
(sous-commandes detect et answer). Dépendance pg installée dans le workspace.
TESTÉ : calibration 2-3-2-1-1-1 / 5 questions / 0 mixte ; aller-retour écriture sur
le vrai helper ; detect bootstrap ; chemin question (état en attente, clef debut
verbatim) ; chemin réponse (reconnaissance + publication + confirmation).
CÂBLÉ : cron concierge-vehicule (id d113b997-0942-4d71-9e9b-d64249949540, payload
COMMAND node concierge-live.js detect, /10, announce whatsapp) — testé : exitCode 0,
sortie vide → delivery suppressReason "empty" (aucun WhatsApp vide). Routage des réponses
ajouté à AGENTS.md §5bis (sauvegarde AGENTS.md.bak-concierge-). pg installé dans le
workspace.
ACTIVATION PAR ÉTAPES (toutes décisions du propriétaire) :
(1) openclaw cron enable d113b997-… → boucle Q/R en réel, le helper se publie sur une
vraie réponse. La BASE n'est pas touchée.
(2) EVCS déploie son lecteur en DRY-RUN (redémarre son service) : il journalise ce qu'il
écrirait (jointure ±1 s sur les clefs verbatim, extension au groupe) SANS écrire.
Valide la moitié EVCS avant que ça compte.
(3) kill-switch input_boolean.airtria_concierge_enabled → ON + EVCS commit la bascule
→ les étiquettes corrigent session_charge.
⚠️ La bascule vehicule_sur n'est PAS neutralisée par le kill-switch : le kill-switch gate
l'APPLICATION d'un label, pas le DEFAULT de la colonne. Passer DEFAULT false trop tôt
afficherait 100 % de sessions « douteuses » (motif faux) sur le Grafana « Les véhicules ».
Donc la bascule = TROIS pièces soudées au commit du ON : DEFAULT false + true-sur-
confirmation-humaine + libellé Grafana « selecteur change » → honnête.
Décision COMMAND vs agentTurn : command choisi car déterministe (pas de LLM, sortie exacte,
suppression auto sur vide). Cron existants modèles : daily-email-report (Gmail 8h30),
orange-cloture (17h40).
Objectif : combler le seul trou qu'aucun capteur ne comble — savoir QUEL véhicule
(VW / JEEP / invité / bientôt moto ZERO) est branché sur la borne EVCS, pour que
session_charge cesse d'attribuer l'énergie au mauvais véhicule.
Contexte déclencheur : le sélecteur input_select.victron_evcs_vehicle est resté
figé sur « VW » du 07/09 au 13/09 → 11 sessions d'affilée mal étiquetées, sans
aucun signal. VW et JEEP sont électriquement indiscernables l'une de l'autre
(~3 680 vs ~3 655 W à 20 A).
Principe d'architecture (non négociable)
- Le cœur déterministe EVCS reste seul juge. Airtria intervient APRÈS, jamais dans la boucle 30 s.
- Une seule main sur chaque variable. Airtria ne touche AUCUN actionneur.
- Toute valeur porte son propre horodatage (
confirmed_at), pas celui du run. - Producteur qui s'arrête : invalidation STRUCTURELLE (le label est lié à une session précise, il ne peut pas contaminer une autre session). On NE vide PAS le helper (fenêtre de perte inutile) ; on laisse la dernière confirmation.
Décisions du propriétaire (2026-09-13)
- Lancement v0 + v1 ENSEMBLE.
- Canal de correction = écriture HA restreinte
input_text.airtria_*(variante PostgreSQL INSERT-only écartée).
Détection d'une session (v0)
- Source = table PostgreSQL
session_charge, PAS le binary_sensor HA (il flappe : disconnected x2 câble branché le 07/09). - ⚠️ NE PAS utiliser
evcs_lecture: il a INSERT surevenement(registre d'audit partagé) — inadapté à un agent. Rôle DÉDIÉairtria_ro, LOGIN + SELECT sursession_chargeetevenementuniquement, aucune écriture. Mot de passe = variableAIRTRIA_RO_PASSWORD, injectée dans le conteneur par leenv_file: .envdu compose openclaw (secret dans /mnt/ARCH3/docker/openclaw/.env, 600, hors git). EVCS possède le rôle et peut RENOUVELER le mot de passe : le code lit la variable d'environnement (source vivante), jamais un fichier figé — un.pg-airtriapérimé ferait échouer l'auth en silence. Après un changement du .env, RECRÉER le conteneur (docker compose up -d) : un simple restart ne relit pas env_file. Base evcs sur 10.10.10.22:5433. - Colonnes utiles (session_charge, 17 col) :
debut(clef, MS),fin,terminee,vehicule,vehicule_sur(DEFAULT true → à basculer côté service),p_max_w+consigne_max_a+courant_max_a(règle invité),debranche_a(frontière de groupe),energie_compteur_kwh(l'énergie qui FAIT FOI, pasenergie_kwhqui rate si le service démarre après le début de charge). - On hérite du débounce du service d'historisation : une ligne
session_chargene s'ouvre qu'au-delà de 300 W soutenus, se ferme après 10 min sous le seuil. - Cadence : cron 10 min. La latence n'a aucun effet (session de plusieurs heures).
- Airtria garde son propre état (dernière
debuttraitée) dans son workspace.
Test d'ambiguïté (quand poser la question) — RÈGLE FINALE
- Le sélecteur est FRAIS (ne pas demander) SEULEMENT s'il a changé de valeur dans
la fenêtre [début du branchement − 10 min, début + 30 min]. Sinon → DEMANDER.
Source :
evenement(genre='intervention', entite=input_select.victron_evcs_vehicle), JAMAIS lelast_changedHA. Implémenté :selectorFreshAt()dans concierge-live.js. - Pourquoi la fenêtre serrée (EVCS, données) : un changement de sélecteur EN COURS de branchement ne peut pas étiqueter ce branchement (câble non bougé) — c'est le producteur mécanique des groupes mixtes. Ma 1ère règle (« bougé depuis le débranchement précédent ») déclarait « frais » EXACTEMENT les 2 branchements mixtes de l'historique.
- Validé sur 11 branchements 04-12/09 : 1 seul FRAIS (05/09, réglé à +13 min), 10 à DEMANDER dont les 2 mixtes. Stat marquante : sélecteur réglé au bon moment 1 fois / 12.
- Mauvais test (rejeté) : « sélecteur inchangé depuis le début de CETTE session » — vrai aussi quand le propriétaire a bien réglé au branchement.
- Avant de conclure « pas de geste humain », vérifier la table
evenement(un redémarrage HA a pu changer la valeur ; l'auteur y est tracé depuis le 12/09). - ✅ CAVEAT
last_changed(2026-09-13) CORRIGÉ : lelast_changedHA est remis à zéro par un passageunavailable(blip MQTT) sans vrai changement → il ferait croire « frais » un sélecteur périmé. La règle ci-dessus lit doncevenement(qui saute les lectures unavailable), paslast_changed. Limites de la source : le journal commence le 04/09 ; la mémoire du détecteur est en-processus (un changement pendant un redémarrage du service n'est pas rattrapé) → l'absence d'événement ne prouve pas tout, mais dans le doute la règle DEMANDE = le bon côté. - En cas de doute : poser la question. Un WhatsApp de trop coûte moins qu'une ligne fausse pour toujours.
Pré-filtre puissance (réduit les questions) — règle À SENS UNIQUE
- Le bon critère n'est pas la puissance brute mais la puissance RELATIVE À LA CONSIGNE. VW/JEEP saturent vers 16 A embarqués quelle que soit la consigne (~3 606 à 3 784 W à 20 A) ; la Megane a suivi jusqu'à 28 A (6 360 W).
- Règle conclusive :
consigne_max_a >= 20ETp_max_w > 4200⟹ invité, Airtria pré-étiquette « invité » SANS déranger le propriétaire. consigne_max_a < 20⟹ AUCUNE information (6 A plafonne ~1 700 W pour tout le monde) → demander.- ⚠️ La réciproque est FAUSSE :
p_max_w <= 4200ne prouve PAS que c'est un véhicule du foyer (un invité à 16 A embarqués est indiscernable de la VW). Au-dessus du seuil on sait, en dessous on demande. NE JAMAIS inverser. - La consigne vient du contrôleur EVCS : plafond 20 A hors BOOST
(
input_number.evcs_plafond_courant_edf_on, 28 en BOOST). Le cas conclusif est donc fréquent ; le cas muet = sessions à courant réduit (fin de charge solaire à 6 A). - Moto ZERO (fin sept. 2026) : tombera sous 3,6 kW → côté foyer, distinguée par le sélecteur comme les autres. Seuil à réévaluer à son arrivée.
Regroupement = frontière FACTUELLE via evenement (pas le temps)
- ⚠️ Ne PAS regrouper par écart de temps : le creux soir→heures creuses fait ~5 h 20 (fin solaire ~20:00 → HC ~01:24), indistinguable d'un vrai débranchement/rebranchement par un seuil de durée.
- La frontière est un FAIT : les transitions de
binary_sensor.victron_evcs_vehicle_connectedsont écrites dansevenement(off→on au branchement, on→off au débranchement). Un branchement = intervalle [off→on … on→off suivant]. Toutes les lignessession_chargedontdebuttombe dans l'intervalle = même véhicule = UNE seule question. Intervalle ouvert (encore branché) = groupe en cours. - Exemple semaine 08-12/09 : 6 branchements = 2-3-2-1-1-1 sessions (le mercredi 15:55+19:35+01:25 est un TRIPLET, même câble). Un regroupement qui rend des paires soir/nuit est FAUX.
- Le groupe reste la notion d'Airtria (propriétaire unique sur la notion dérivée) ; le service EVCS ne fait qu'itérer la liste du helper.
- ⚠️ Ne PAS émettre une écriture helper par session (elles écraseraient le même helper ; le service qui poll toutes les 60 s ne verrait que la dernière → perte silencieuse des autres). Le helper porte donc une LISTE de session_debut (voir ci-dessous), publiée en UNE écriture atomique. Le service itère la liste.
- Fallback coupure : les transitions ne sont écrites que si le service tourne. Frontière absente/incomplète → groupe INCERTAIN → poser la question PAR session, jamais sur une frontière reconstruite. Un WhatsApp de trop > une étiquette fausse.
Sources de données (requêtes, lecture seule via airtria_ro)
- Frontière des branchements — table
evenement(colonnes : ts, genre, source, resume, detail jsonb, id). Filtre robuste (pas de parsing de texte) :detail->>'entite' = 'binary_sensor.victron_evcs_vehicle_connected',detail->>'apres'= 'on' (branchement) / 'off' (débranchement). Ordonner surts, borner les intervalles surdetail->>'horodatage_ha'(vérité physique après rattrapage). Le champentiten'existe que sur les transitions → les fins de session sont exclues naturellement. - Bonus qualité : les événements de fin de session portent
detail->>'debut'(= session_charge.debut exact, ms comprises) et les trois énergies côte à côte (kwh_compteurfait foi,kwh_integre,kwh_borne). Un écart entre elles est un signal de qualité à remonter (pas une question au propriétaire). - Accès : Node + node-postgres (pur JS, à ajouter au workspace) ; PG:5433 joignable depuis la machine spark → calibration exécutable en lecture seule.
- Implémentation :
concierge-detection.js(mode calibration, aucune écriture).
La question WhatsApp
- Un seul message, au démarrage d'une session ambiguë (ou d'un groupe).
- Ouverte, pas binaire : « Quel véhicule est branché ? » avec options lues dynamiquement dans le sélecteur + « aucune des deux / invité ».
- Porte la clef
debutde la session pour permettre l'application a posteriori. - Réponse possible des heures plus tard, ou jamais.
Le helper HA (le contrat v1)
input_text.airtria_vehicle_label, déclaré max: 255 (défaut HA = 100 → rejet silencieux ; incident vécu forecast_updated max:32 le 08/09).- Contenu = UN JSON atomique portant une LISTE de sessions (atomicité indispensable, HA n'a pas de transaction entre entités) : {"sessions":["2026-09-08 19:39:12.334+02","2026-09-09 01:26:04.887+02"], "label":"VW","confirmed_at":"","source":"whatsapp"}
- Chaque entrée de
sessions= clefdebutrendue par la requête SANS reformatage (lesdebutportent les ms, ex. 2026-09-12 11:55:54.493+02 ; un arrondi minute ne matcherait jamais ; pas d'aller-retour par un objet Date). Le service EVCS joint sur ±1 s, pour chaque entrée de la liste. - Taille : 2 sessions ≈ 150 car, 3 ≈ 180, sous 255. Au-delà, publier en deux écritures espacées de > 90 s (le service aura polé entre les deux).
- Après CHAQUE écriture : relire l'état et vérifier qu'il est identique à l'envoi (seul moyen de détecter un rejet silencieux HA).
- Auto-correction (« JEEP… non VW ») = republier avec
confirmed_atplus récent ; le service applique le dernier pour unesession_debutdonnée. - On NE vide PAS le helper à {} : l'invalidation est structurelle (session_debut). Le vidage ouvrirait une fenêtre de perte si le service est arrêté entre publication et vidage.
Garde-fous d'écriture (non négociables — restriction CONVENTIONNELLE)
Le canal choisi est un token HA, qui peut techniquement écrire n'importe quel service HA : la restriction à airtria_* ne tient QUE par la discipline du code. Donc, côté OpenClaw :
- Le nom de l'entité écrite est une CONSTANTE EN DUR unique
(
input_text.airtria_vehicle_label), jamais construite dynamiquement, jamais interpolée. - Un test type « mode ombre » ÉCHOUE si une écriture cible autre chose que cette constante (équivalent du tests/advisor/run.mjs d'EVCS qui garde advisor_*).
- Aucune écriture d'actionneur, jamais (liste des variables à propriétaire unique
dans le MANUEL EVCS ;
carte-decisions.mjs/enquete.mjsavant tout doute).
Recoupement a posteriori (qualité de données)
- Comparer
session_charge.debranche_aaux départs attendus : VW 08:50 (input_datetime.victron_evcs_departure_time), JEEP 06:30 (input_datetime.victron_evcs_departure_time_jeep). debranche_an'est renseignée que si le service voit le débranchement (1 fois sur 12 récemment) → ne pas supposer qu'elle est toujours là.- Ne signaler que si ça CONTREDIT le label (comportement idiot), pas à chaque session. Pas de notification pour l'état normal.
Côté service EVCS (v1, fait par la session EVCS)
- Lit le helper chaque cycle (60 s), JSON.parse en try/catch ; objet vide / illisible / champs manquants → ne rien faire.
confirmed_atantérieur audebut→ ignoré. Jointure ±1 s sur session_debut.- Met à jour
vehicule;vehicule_surpasse à VRAI uniquement sur confirmation humaine (changement de sens : défaut basculé à faux ; lignes antérieures gardent l'ancienne sémantique). Trace l'application dansevenementavec la source.
Validation avant mise en service
tools/campagne-conseiller.mjs(dépôt EVCS) rejoue des instants réels ; cumuler 3 runs (un run de 5 passages = bruit, écart-type ~0,22).- Jeu de calibrage = les 12 sessions du 07 au 12/09, étiquetées par le propriétaire (voir ci-dessous). ⚠️ Le v0 ne DÉDUIT pas VW vs JEEP (impossible : la semaine, la routine était inversée — femme/JEEP en congés, fils/VW en stage sans retour le midi). Ce qui est testé = le ROUTAGE : MEGANE auto-détectée invité par la règle puissance ; toutes les autres correctement routées vers « demander » (sélecteur périmé). Ce sont les réponses du propriétaire qui donnent VW/JEEP. Si le routage rate une des 12, revoir la règle, pas l'étiquette.
Cibles de routage du v0 (les 6 branchements du 08-12/09)
- 11/09 16:53→18:27 (Megane, 1 session) : auto-invité par la règle puissance, AUCUNE question.
- 5 autres branchements (2-3-2-1-1) : routés vers la question (sélecteur périmé).
- Total attendu = 5 questions pour 12 sessions. Un regroupement qui rend des paires soir/nuit (au lieu du triplet du mercredi) est FAUX.
Jeu de calibrage FINAL (corrigé en base, arbitré par le propriétaire 2026-09-13)
branchement sessions étiquette
sam 12/09 11:55 → (en cours) 11:55 5,94 JEEP
sam 12/09 11:42 → 11:53 11:42 0,09 JEEP
ven 11/09 16:53 → 18:27 16:54 7,45 6360W/28A MEGANE (auto)
jeu 10/09 18:19 → ven 16:27 18:20 2,97 · 01:24 4,26 VW
mer 09/09 15:55 → jeu 09:05 15:55 6,61 · 19:35 0,77 · 01:25 4,88 JEEP (triplet)
mar 08/09 19:39 → mer 08:57 19:39 0,76 · 01:26 5,01 VW
Semaine : 5 VW, 6 JEEP, 1 MEGANE (contre 11 VW + 1 JEEP avant correction). Le triplet mercredi = JEEP les trois : la femme a branché la JEEP, le fils n'a pas voulu la débrancher le soir pour mettre la VW → il a roulé à l'essence le jeudi. Contexte humain qu'aucune règle ne produit — la raison d'être du concierge. MEGANE = voiture de service (Rennes) + dépannage d'amis → « invité » RÉCURRENT.
Cas tordus que le module DOIT gérer (dans les données de la semaine)
- sam 12/09 : on 11:42, off 11:53, on 11:55 = DEUX branchements (2 min de débranchement), pas du flapping. La session 0,09 kWh en est la trace.
- ven 11/09 : off 16:27, on 16:53 = DEUX branchements (26 min ; débranche un véhicule foyer pour brancher la Megane).
- ⚠️ AUCUN seuil de durée pour fusionner : 2 min et 26 min sont l'une et l'autre de vraies séparations. Règle = « toute transition compte » ; défensive limitée aux incohérences STRUCTURELLES (on déjà ouvert, off sans ouverture).
- Le branchement 12/09 11:55 est ENCORE OUVERT (borne haute nulle) → groupe en cours, ne pas conclure qu'il est incomplet.
Fenêtre d'analyse : commencer sur un 'on'
Une fenêtre doit démarrer sur un on, jamais au milieu d'un branchement : sinon
le premier événement est un off orphelin (branchement ouvert avant la fenêtre),
la session ouverte avant tombe hors intervalle et est classée « incertaine » à
tort (ex. démarrer au 08/09 00:00 fait apparaître 6 questions au lieu de 5, à
cause de la session 08/09 01:26). En production, aucun souci (on suit le flux) ;
règle valable surtout au rejeu historique. Calibration calée sur 08/09 19:00.
Invariant à vérifier EN PERMANENCE (le contrôle le plus solide du projet)
Un seul câble ⟹ toutes les sessions d'un même branchement portent la MÊME étiquette. Un groupe à étiquettes mixtes = frontière fausse (Airtria) OU ligne fausse (base) — les deux méritent une alerte. Propriété PHYSIQUE : ne nécessite aucune connaissance métier (ni routine, ni horaire, ni puissance), et aucune évolution de règle ne peut l'invalider. À faire tourner en continu, pas seulement à la calibration — c'est ce qui aurait signalé le triplet incohérent le jour même.
Points de vigilance
- ⚠️ Inventaire BMS mensonger (découvert 2026-09-13) : le BMS annonce 4 modules /
400 Ah alors que le parc en compte 9 (le LV-Hub ne remonte que le groupe maître).
Mesuré : 0,312 kWh/point de SOC (vs 0,169 sur l'ancien parc de 7). NE JAMAIS
utiliser
capacite_installeeninombre_de_modules_en_lignecomme source de capacité (sous-estiment de ~38 %). Source qui fait foi :input_number.evcs_balance_modules_en_service(13 aujourd'hui). Concerne surtout un futur rôle 2 (rétro-évaluation énergétique), pas le concierge véhicule. - Helper HA en place :
input_text.airtria_vehicle_label(max:255, état initialunknown= « rien à appliquer »). Kill-switch consommateurinput_boolean.airtria_concierge_enabled(OFF) = propriété d'EVCS, Airtria n'y touche pas. Aller-retour écriture/relecture validé le 2026-09-13. - SPOF : OpenClaw vit sur 10.10.10.17, la machine qui porte aussi HA et n8n. Faible enjeu pour l'ID véhicule, mais réel. Chien de garde sur 10.10.10.22.
- Prérequis à câbler : (1) accès lecture PostgreSQL pour Airtria
(
PG_LECTURE_PASSWORD, déposé protégé comme le token HA) ; (2) création du helperinput_text.airtria_vehicle_label(max:255) dans le paquet HA.
Supervision — alerte d'échec d'auth (2026-09-16)
Motivation : le 15/09, EVCS a renouvelé le mot de passe airtria_ro ; le concierge a
alors échoué à se connecter, mais EN SILENCE — sortie vide, donc aucun WhatsApp émis,
aucune trace visible : il cessait simplement de poser la question sans que personne le
sache. Même angle mort que le service EVCS (une trace écrite seulement AU RETOUR à la
normale, dans PG, donc jamais quand c'est PG qui tombe).
Correctif (module concierge-health.js, même patron que le moniteur LLM) :
- Battement de cœur À CHAQUE cycle :
input_datetime.concierge_last_runreçoit un horodatage (valeur qui CHANGE — HA ne rafraîchit pas lelast_changedd'un helper réécrit à l'identique). Un concierge mort devient détectable (capteurbinary_sensorde fraîcheur, > 20 min → « figé »). - Statut live :
input_text.concierge_status(max:255) =OK · HH:MMou⚠️ <erreur> · HH:MM. - Alerte WhatsApp UNIQUEMENT sur transition (ok↔erreur), jamais un rappel périodique
(« events not states ») : une panne qui dure ne spamme pas le canal. Dédup par état
local
concierge-health-state.json. - Chemin HA indépendant de PostgreSQL : l'alerte « auth KO » part précisément quand
c'est l'auth qui tombe (le signal de vie n'emprunte pas le chemin qui tombe). Best-effort :
si les helpers manquent, le concierge n'échoue pas ; l'alerte WhatsApp part quand même
(elle ne dépend pas des helpers). Interrupteur
input_boolean.concierge_monitor_enabled(OFF = pas de WhatsApp, affichage maintenu). - Refactor de
concierge-detection.js:readPgPassword()LÈVE au lieu deprocess.exit, mot de passe résolu paresseusement dansmakeClient()→ l'échec devient RATTRAPABLE par l'étage live, qui le publie. Paquet HA :concierge-health-ha-package.yaml.draft. Machine à états vérifiée parconcierge-health.test.js(dédup + transitions, sans réseau).