workspace: rapport solaire, prevision de nuit, concierge, courrier, fiche allegee
- 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.
This commit is contained in:
@@ -1,11 +1,16 @@
|
||||
// concierge-write.js — Concierge véhicule Airtria, ÉTAGE ÉCRITURE (canal HA restreint)
|
||||
// -----------------------------------------------------------------------------
|
||||
// SEULE écriture HA d'Airtria. La restriction à cette entité est CONVENTIONNELLE
|
||||
// SEULES écritures HA d'Airtria. La restriction à ces entités est CONVENTIONNELLE
|
||||
// (le token HA peut techniquement tout écrire) : elle ne tient que par ce code.
|
||||
// Donc, invariants tenus ici et vérifiés par concierge-write.test.js :
|
||||
// - l'entité écrite est une CONSTANTE EN DUR, jamais interpolée ni construite ;
|
||||
// - une seule écriture, via input_text.set_value, aucun actionneur ;
|
||||
// - relecture obligatoire après écriture (détecte un rejet silencieux HA).
|
||||
// - les entités écrites sont DEUX CONSTANTES EN DUR, jamais interpolées ;
|
||||
// - deux services seulement : input_text.set_value (étiquette) et
|
||||
// input_select.select_option (sélecteur, ajouté le 19/09/2026 à la demande
|
||||
// du propriétaire relayée par EVCS) ;
|
||||
// - le sélecteur n'accepte que les véhicules du FOYER (liste en dur) et une
|
||||
// source HUMAINE — jamais la déduction règle-puissance ;
|
||||
// - relecture obligatoire après chaque écriture (rejet silencieux HA :
|
||||
// set_value tronque au-delà de max, select_option IGNORE une option hors liste).
|
||||
// Voir airtria-concierge-vehicule-SPEC.md.
|
||||
// -----------------------------------------------------------------------------
|
||||
'use strict';
|
||||
@@ -13,8 +18,17 @@
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
|
||||
// ⚠️ Constante unique. Ne JAMAIS la rendre dynamique ni en ajouter d'autres.
|
||||
// ⚠️ Deux constantes, pas une de plus. Ne JAMAIS les rendre dynamiques.
|
||||
const HELPER_ENTITY = 'input_text.airtria_vehicle_label';
|
||||
const SELECTOR_ENTITY = 'input_select.victron_evcs_vehicle';
|
||||
// Liste EN DUR, volontairement PAS lue depuis HA : le sélecteur ne pilote que
|
||||
// les deux véhicules du foyer (départs VW 08:50 / JEEP 06:30). Si une option
|
||||
// est ajoutée côté HA un jour (ZERO ?), l'ajouter ici est une DÉCISION.
|
||||
const SELECTOR_LABELS = ['VW', 'JEEP'];
|
||||
// Le sélecteur ne s'écrit QUE sur confirmation humaine. La déduction
|
||||
// règle-puissance ne conclut jamais VW vs JEEP (indiscernables électriquement),
|
||||
// donc ne passe jamais ici — et ce garde le garantit même si ça changeait.
|
||||
const HUMAN_SOURCES = ['whatsapp'];
|
||||
const HA_BASE = process.env.HA_BASE || 'http://10.10.10.17:8123';
|
||||
const MAX_LEN = 255; // le helper DOIT être déclaré max:255 côté HA (défaut = 100)
|
||||
|
||||
@@ -59,4 +73,34 @@ async function publishLabel(branchement, label, source = 'whatsapp') {
|
||||
return { entity: HELPER_ENTITY, payload };
|
||||
}
|
||||
|
||||
module.exports = { publishLabel, HELPER_ENTITY, MAX_LEN };
|
||||
// Aligne le sélecteur temps réel sur la confirmation HUMAINE d'un véhicule du
|
||||
// FOYER. Pour MEGANE / INVITE / ZERO : ne pas appeler — le sélecteur reste tel
|
||||
// quel, seule l'étiquette est publiée. La relecture est le SEUL moyen de voir
|
||||
// qu'HA a refusé l'option : select_option ne renvoie pas d'erreur, il ignore.
|
||||
async function setVehicleSelector(label, source) {
|
||||
if (!HUMAN_SOURCES.includes(source)) {
|
||||
throw new Error(`sélecteur : source '${source}' non humaine — écriture interdite`);
|
||||
}
|
||||
if (!SELECTOR_LABELS.includes(label)) {
|
||||
throw new Error(`sélecteur : '${label}' hors foyer — on n'écrit pas (VW/JEEP seulement)`);
|
||||
}
|
||||
const token = haToken();
|
||||
const headers = { Authorization: `Bearer ${token}`, 'Content-Type': 'application/json' };
|
||||
const res = await fetch(`${HA_BASE}/api/services/input_select/select_option`, {
|
||||
method: 'POST', headers,
|
||||
body: JSON.stringify({ entity_id: SELECTOR_ENTITY, option: label }),
|
||||
});
|
||||
if (!res.ok) throw new Error(`select_option HTTP ${res.status}`);
|
||||
const back = await fetch(`${HA_BASE}/api/states/${SELECTOR_ENTITY}`, { headers });
|
||||
if (!back.ok) throw new Error(`relecture HTTP ${back.status}`);
|
||||
const state = (await back.json()).state;
|
||||
if (state !== label) {
|
||||
throw new Error(`relecture '${state}' != '${label}' — option refusée en silence par HA`);
|
||||
}
|
||||
return { entity: SELECTOR_ENTITY, option: label };
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
publishLabel, setVehicleSelector,
|
||||
HELPER_ENTITY, SELECTOR_ENTITY, SELECTOR_LABELS, MAX_LEN,
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user