- Fiche agent maison (AGENTS.md) : prevision de nuit relayee par script, import
EDF, regle JEEP midi, saisie vehicule/niveau/depart, concierge qui relaie
« ✅ Noté » ou « Rien n'est noté », groupe A deplace (28 649 -> 25 831 car.).
- Nouvelles references : reference-concierge.md, reference-workspace.md.
- Scripts : rapport-solaire.js, prevision-nuit.js, airtria-ha-vehicule.js,
concierge-live.js (answer ne sort plus jamais vide, mode CONCIERGE_SIMULATION).
- Courrier Orange / AIRTRIA / Gmail : audits, regles, menage, recents.
- Bancs EVCS tests-pv (G5 accepte « anomal »).
- .gitignore : liste blanche etendue aux nouveaux scripts et bancs.
87 lines
4.0 KiB
JavaScript
87 lines
4.0 KiB
JavaScript
// airtria-ha-read.js <entité> [<entité> …] — 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 comme FAIT neutre (« inchangé depuis
|
|
// X »), JAMAIS un verdict « figé » : un système stable a des entités immobiles (voiture
|
|
// branchée depuis 12 h, mode inchangé) et aucun champ de l'API ne distingue un écrivain mort
|
|
// d'une réécriture identique (EVCS 16/09) — la vivacité se lit sur le battement de cœur, pas
|
|
// sur l'âge d'une entité métier. En cas d'échec, imprime le VRAI code HTTP / la vraie erreur
|
|
// (le 16/09 Airtria a affirmé un 401 alors que le jeton renvoyait 200 : ne rien inventer).
|
|
// Rappel EVCS : 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();
|
|
|
|
// Âge = FAIT neutre, jamais un verdict de panne (cf. en-tête). La péremption d'une valeur
|
|
// ne se déduit PAS de son âge : ancienneté ≠ panne pour une entité métier.
|
|
function since(iso) {
|
|
if (!iso) return '?';
|
|
const d = new Date(iso);
|
|
const t = d.toLocaleString('fr-FR',
|
|
{ timeZone: 'Europe/Paris', dateStyle: 'short', timeStyle: 'short' });
|
|
const min = Math.round((Date.now() - d.getTime()) / 60000);
|
|
let age;
|
|
if (min < 1) age = "à l'instant";
|
|
else if (min < 90) age = `inchangé depuis ${min} min`;
|
|
else {
|
|
const h = Math.floor(min / 60), m = min % 60;
|
|
age = `inchangé depuis ${h} h${m ? ' ' + String(m).padStart(2, '0') : ''}`;
|
|
}
|
|
return `${t} (Paris) · ${age}`;
|
|
}
|
|
|
|
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} | ${since(j.last_changed)}`);
|
|
// Attributs utiles (le module d'une cellule, le statut complet/partiel, l'explication
|
|
// ou la raison du contrôleur sont des ATTRIBUTS, pas l'état — les imprimer sinon
|
|
// l'agent ne peut pas les relayer). On masque le bruit de config.
|
|
const NOISE = new Set(['unit_of_measurement', 'friendly_name', 'icon', 'device_class',
|
|
'state_class', 'supported_features', 'attribution', 'entity_picture', 'assumed_state',
|
|
'editable', 'min', 'max', 'step', 'mode', 'initial', 'options', 'last_reset']);
|
|
if (j.attributes) {
|
|
for (const [k, v] of Object.entries(j.attributes)) {
|
|
if (NOISE.has(k)) continue;
|
|
const val = (v !== null && typeof v === 'object') ? JSON.stringify(v) : String(v);
|
|
console.log(` · ${k} = ${val}`);
|
|
}
|
|
}
|
|
}
|
|
|
|
(async () => {
|
|
const entities = process.argv.slice(2);
|
|
if (!entities.length) {
|
|
console.error('usage: airtria-ha-read.js <entity_id> [entity_id ...]');
|
|
process.exit(2);
|
|
}
|
|
for (const e of entities) await readOne(e);
|
|
})().catch(e => { console.error('[ha-read]', e.message); process.exit(1); });
|