diff --git a/.gitignore b/.gitignore index 666c390..f8d2c28 100644 --- a/.gitignore +++ b/.gitignore @@ -26,8 +26,8 @@ workspace/* !workspace/bourse-config.json # code du concierge véhicule (aucun secret : lit les tokens depuis des fichiers ignorés) !workspace/concierge-*.js -# lecteur SQL lecture-seule de la session de charge en cours (aucun secret) -!workspace/airtria-charge-session.js +# scripts Airtria (lecteurs lecture-seule PG/HA ; aucun secret embarqué) +!workspace/airtria-*.js # moniteur LLM/RAM + brouillons de config HA (aucun secret) !workspace/llm-monitor.js !workspace/*.yaml.draft diff --git a/workspace/AGENTS.md b/workspace/AGENTS.md index a37bc2c..6ddf077 100644 --- a/workspace/AGENTS.md +++ b/workspace/AGENTS.md @@ -75,6 +75,14 @@ grandeurs voisines comme si elles répondaient → 5 affirmations fausses sur 6) comme ACTUELLE : figée depuis des heures ≠ « maintenant » / « cette nuit ». - **Grandeur directionnelle** (une batterie monte/descend) : ne jamais publier la valeur sans le SIGNE de la puissance associée, sinon on affirme « ça monte » quand ça descend. +- **Ne jamais FABRIQUER une panne ni une cause.** Si une lecture échoue, rapporter l'erreur + RÉELLE (le code / l'état que l'outil imprime), jamais une cause inventée : le 16/09 Airtria + a affirmé « le jeton HA renvoie 401, régénéré côté HA » alors que le jeton était VALIDE + (HTTP 200). Un diagnostic non observé est une fabrication — pire qu'un « je n'ai pas pu lire ». +- **Lire pour de vrai, ne pas raisonner sur une valeur.** Toute valeur HA se lit en exécutant + `node /home/node/.openclaw/workspace/airtria-ha-read.js …` (voir §5ter) et se + rapporte telle qu'imprimée (état + horodatage déjà converti en heure de Paris). Ne jamais + deviner ce qu'une valeur « devrait » être. --- @@ -329,6 +337,12 @@ réponse reconnue. Référence EVCS (propriétaire du système EVCS), 16/09. Utiliser CES entités et appliquer §1.7 (ne pas substituer, vérifier `last_changed`). +Lire toute valeur HA avec `node /home/node/.openclaw/workspace/airtria-ha-read.js ` +(un ou plusieurs à la suite) : il imprime l'état, l'unité et `last_changed` DÉJÀ converti en +heure de Paris + l'âge (les horodatages /api/states sont en UTC — ne jamais afficher un +« 1:24 GMT+2 » brut). En cas d'échec il imprime le VRAI code HTTP : le rapporter, ne rien +inventer. + - **SOC de la voiture (VW/JEEP)** : N'EXISTE PAS (aucun capteur véhicule). Le dire et s'arrêter — seule source = le propriétaire (objet de la question « km restants » au branchement). Ne jamais le déduire d'autre chose. diff --git a/workspace/airtria-ha-read.js b/workspace/airtria-ha-read.js new file mode 100644 index 0000000..a2e835a --- /dev/null +++ b/workspace/airtria-ha-read.js @@ -0,0 +1,62 @@ +// airtria-ha-read.js [ …] — lecture Home Assistant fiable, UNE vérité. +// ----------------------------------------------------------------------------- +// Airtria doit LIRE l'état via ce script (exec), jamais raisonner sur ce qu'une valeur +// « devrait » être, jamais fabriquer une erreur. Pour chaque entité, imprime : état, unité, +// et `last_changed` CONVERTI en heure de Paris + âge (marqué FIGÉ au-delà de 15 min). En cas +// d'échec, imprime le VRAI code HTTP / la vraie erreur — pour que la panne rapportée soit +// réelle, pas inventée (le 16/09 Airtria a affirmé un 401 alors que le jeton renvoyait 200). +// Rappel EVCS 16/09 : les horodatages /api/states sont en UTC → toujours convertir. +// ----------------------------------------------------------------------------- +'use strict'; +const fs = require('fs'); +const path = require('path'); + +const HA_BASE = process.env.HA_BASE || 'http://10.10.10.17:8123'; +const token = () => fs.readFileSync( + process.env.HA_TOKEN_FILE || path.join(__dirname, '.ha-token'), 'utf8').trim(); + +function parisAge(iso) { + if (!iso) return '?'; + const d = new Date(iso); + const t = d.toLocaleString('fr-FR', + { timeZone: 'Europe/Paris', dateStyle: 'short', timeStyle: 'medium' }); + const ageMin = Math.round((Date.now() - d.getTime()) / 60000); + const stale = ageMin > 15 ? ' ⚠️ FIGÉ (>15 min, PAS une mesure actuelle)' : ''; + return `${t} (Paris, il y a ${ageMin} min)${stale}`; +} + +async function readOne(entity) { + const ac = new AbortController(); + const to = setTimeout(() => ac.abort(), 10000); + let r; + try { + r = await fetch(`${HA_BASE}/api/states/${entity}`, + { headers: { Authorization: `Bearer ${token()}` }, signal: ac.signal }); + } catch (e) { + clearTimeout(to); + console.log(`${entity} : ERREUR réseau (${e.name}) — ne rien inventer`); + return; + } + clearTimeout(to); + if (r.status === 404) { + console.log(`${entity} : INEXISTANT (HTTP 404) — cette entité n'existe pas, ne pas substituer`); + return; + } + if (!r.ok) { + console.log(`${entity} : ERREUR HTTP ${r.status} — rapporter ce code, ne rien inventer`); + return; + } + const j = await r.json(); + const unit = j.attributes && j.attributes.unit_of_measurement + ? ` ${j.attributes.unit_of_measurement}` : ''; + console.log(`${entity} = ${j.state}${unit} | maj ${parisAge(j.last_changed)}`); +} + +(async () => { + const entities = process.argv.slice(2); + if (!entities.length) { + console.error('usage: airtria-ha-read.js [entity_id ...]'); + process.exit(2); + } + for (const e of entities) await readOne(e); +})().catch(e => { console.error('[ha-read]', e.message); process.exit(1); });