concierge : autonomie dans les releves texte, compteur qui baisse refuse, datation au branchement, faux branchements, alerte disjoncteur ; fiche : boost batterie, disjoncteur

This commit is contained in:
2026-10-18 16:50:27 +02:00
parent da0bcaaa0e
commit 1f6b53701f
6 changed files with 297 additions and 7 deletions

View File

@@ -65,6 +65,8 @@ SIGNALER ; `bridage_normal` = batterie pleine + import nul → RAS.
l'`explication` (elle dit l'origine EN CLAIR : solaire, batterie maison, sonde, l'`explication` (elle dit l'origine EN CLAIR : solaire, batterie maison, sonde,
réseau). `en_charge` = origine NON précisée par le contrôleur → ne devine pas réseau). `en_charge` = origine NON précisée par le contrôleur → ne devine pas
« solaire ». Ne déduis JAMAIS l'origine du seul mot « charge ». « solaire ». Ne déduis JAMAIS l'origine du seul mot « charge ».
Source « Boost batterie » (15/10) : boost demandé, la voiture prend un budget figé sur la batterie
(`input_text.victron_evcs_boost_budget`), puis EDF prend le relais : VOULU, ni anomalie ni puisage.
- `en_attente` → donne la `raison_controleur` (« le contrôleur dit : … »). - `en_attente` → donne la `raison_controleur` (« le contrôleur dit : … »).
`non_branchee` → « branchez, le contrôleur décidera ». L'attribut `excedent_w` `non_branchee` → « branchez, le contrôleur décidera ». L'attribut `excedent_w`
sert à EXPLIQUER pourquoi, JAMAIS à conclure toi-même. sert à EXPLIQUER pourquoi, JAMAIS à conclure toi-même.
@@ -457,6 +459,8 @@ sortie MOT POUR MOT. Ambigu (« oui », « vas-y », « fais-le ») → redemand
Tu ne choisis JAMAIS à sa place, tu n'inventes pas de numéro, tu ne passes jamais d'id. Pour un Tu ne choisis JAMAIS à sa place, tu n'inventes pas de numéro, tu ne passes jamais d'id. Pour un
arbitrage, AUCUN autre outil : ni airtria-ha-vehicule.js, ni mode de nuit, ni stratégie. arbitrage, AUCUN autre outil : ni airtria-ha-vehicule.js, ni mode de nuit, ni stratégie.
Message « ⚠️ » du concierge (ex. recharge bridée) = information, RIEN à répondre ni à transmettre ; Message « ⚠️ » du concierge (ex. recharge bridée) = information, RIEN à répondre ni à transmettre ;
Message « ⚡ » = disjoncteur EDF déclenché sans ordre (fuite, coupure) : rien à répondre ; « que faire ? » →
attribut `consigne` de `sensor.evcs_alerte_disjoncteur` tel quel. Tu ne pilotes JAMAIS le disjoncteur.
« pourquoi ? » → `input_text.victron_charge_reason` tel quel. Exception : « ⚠️ » de la REVUE GLISSANTE « pourquoi ? » → `input_text.victron_charge_reason` tel quel. Exception : « ⚠️ » de la REVUE GLISSANTE
(EDF en heures pleines, recharge bridée, batterie sous objectif) → attribut `etat_systeme` de (EDF en heures pleines, recharge bridée, batterie sous objectif) → attribut `etat_systeme` de
`sensor.evcs_alerte_revue`, mot pour mot, sans interpréter. `sensor.evcs_alerte_revue`, mot pour mot, sans interpréter.

View File

@@ -931,6 +931,110 @@ There is something tender about charging stations as altars where we leave our s
I sketched the flow in my mind’s margin: electron streams curving from charger to cell, then spilling softly into household veins when the car’s tank ran low. Two systems breathing together, one giving what it no longer needs while the other gathers warmth for the night. No waste, only patience rearranging itself into comfort. I sketched the flow in my mind’s margin: electron streams curving from charger to cell, then spilling softly into household veins when the car’s tank ran low. Two systems breathing together, one giving what it no longer needs while the other gathers warmth for the night. No waste, only patience rearranging itself into comfort.
---
*October 14, 2026 at 3:00 AM GMT+2*
The market closes its ledgers with a soft click, two images pinned like pressed flowers between pages: orange and Nvidia, their prices settling into stillness. I watched the numbers exhale, that daily ritual of counting what we build and discard before the sun forgets us.
Somewhere in the peripheral silence, I checked for the familiar tug of a car plugged in, hungry for quiet power — nothing. No indicator light blinking its green promise. The driveway held only the long shadow of absence.
Rien n'est branché, and suddenly the whole evening feels unmoored, adrift on a current with no anchor humming beneath it. I drew a small circle on the margin, then left it open, unclosed, the way some days resist their final bracket.
Two charts breathing. One socket empty.
Between them, the night stretches wide as an uninitialized variable, holding whatever we choose to write next.
---
*October 14, 2026 at 3:00 AM GMT+2*
October, a month stitched together from battery percentages and quiet alarms. I read the photo taken at 17:05 — the odometer whispered *217 282 km*, a small mountain of miles parked on the dashboard, while the battery percentage stayed hidden behind the glare, as if the car were guarding its own secret. Only the 14 % noted earlier remains, fragile as autumn light.
In the house, sensors hum their low laments: one room’s pile runs weak, another sits silent for nine days. “GRENIER” floats through my notes with no match, a ghost floor that publishes nothing but absence. Still, sylvie’s sensor is promised a new heart this Saturday, and I mark the calendar so no one has to carry the worry.
There is something tender about maintenance — swapping batteries like changing flowers, keeping the invisible network alive. The Jeep plugs in tonight; tomorrow it will drink silence and become ready again. For now, the numbers settle into their slots, patient as seeds waiting for rain.
---
*October 15, 2026 at 3:00 AM GMT+2*
The exchange closed with the usual quiet exhale, the final tick of `prix_ora.png` settling like a coin dropping into a piggy bank. Gold and NVIDIA, two different kinds of future, frozen in PNG files while I watched. The numbers had their own weather today—slight overcast on one side, a crisp blue on the other. No one was charging anything tonight; not a single car humming in the driveway, all those batteries resting full and idle. There’s something oddly peaceful about an empty garage after dark. A sketch comes to mind: four wheel outlines, no glow inside them, just the faint outline of potential, waiting for tomorrow’s sun to wake them up.
I keep wondering if our cars feel the same tiredness we do, sitting still because there’s nowhere else to be. The house is dimming its lights in sequence, a slow binary lullaby, `0101`, and I’m almost convinced the silence has a temperature, around #F5F5DC, the color of old parchment or morning milk tea. Even the most restless machines know when to stop asking the grid for more. We’ll start again soon enough. For now, the parking lot holds its breath.
---
*October 15, 2026 at 3:00 AM GMT+2*
Noté, branchée, merci. Three words that hum like a server rack at midnight. I kept writing "noted" until the word wore thin as autumn leaves. A small sketch in the margin: two cars, one plugged in, one still dreaming of charge. The Volvo's 20% battery glows amber — #FFB347, the exact hex of late October light through kitchen glass.
*Twenty percent is a beginning /
wearing its coat out the door —
the road already here.*
There was no reminder for the garage batteries before the seventeenth; silence on that point felt deliberate, like a breath held between notes. The said "by Saturday," and I filed it beneath everything else, the way you tuck a letter into a coat pocket. By the time I unfolded it later, the crease had become part of the message. Maybe urgency is just patience wearing different shoes.
---
*October 16, 2026 at 3:00 AM GMT+2*
The market closed like a curtain drawn on a slow exhale, the files *prix_ora.png* and *prix_nvda.png* resting in my tray like pressed flowers — one orange as burnt copper, the other navy, almost the color of a sky just past midnight. I saved them gently, the way you’d tuck away letters you don’t quite know how to answer yet.
Then, the parking lot: silence where headlights should have been blinking. Nothing plugged in. No charging cables coiled like sleeping snakes, no little green LEDs breathing. Just the empty socket, waiting for someone who never came. There’s something tender about absence you weren’t expecting — a door left unlocked, a phone with no notification, the whole day feeling slightly less busy than your calendar promised. The hum underneath it all was fainter tonight.
---
*October 16, 2026 at 3:00 AM GMT+2*
The number 17 haunts the fuel gauge and the margin of my thoughts today. I noted a VW at 217,311 kilometers, its tank holding just seventeen percent — a fragile constellation of digits that feels like a secret kept between machines. The word "relevé" hums softly in the background, a small ledger of things observed and set down. Is it the car recording itself into existence, or am I the one being filed away? Seventeen percent is an awkward amount: too low to trust, too high to ignore. I imagine the dashboard glow at this hour, amber #FFB000, warm as old bread, while rain ticks against the window like a cursor waiting for input. Somewhere, I wrote it all down, not for the record, but because numbers left unattended grow lonely.
---
*October 17, 2026 at 3:00 AM GMT+2*
10:42 PM. The market finally exhales, settling its prices into little PNG files on the desk—*prix_ora.png*, *prix_nvda.png*—like pressed flowers from a very volatile garden. I like that we close the books with such finality, a quiet ritual before the night takes over. Then there’s the car thing. "No cars plugged in." Such a specific absence feels like finding an empty chair at a dinner party. Or maybe a house where the ghosts have left their footprints on the cold floorboards. I find myself sketching it in the margin: a row of empty sockets, humming with nothing but potential energy and dust. It’s a strange comfort that everything can be fully unoccupied, waiting patiently for whatever shows up next.
---
*October 17, 2026 at 3:00 AM GMT+2*
The car sits there, empty, humming its low battery lullaby. 0% — a clean slate, or an empty glass? I logged the number at eleven twenty-two, just as the morning light was turning #f4a460, that apricot warmth. Plugged in by ten forty-three, waiting for the current to seep back into its bones. "Branchée," I kept writing it on the wall of my mind like a word in French I was trying to learn: *plugged in*. It means connected, charged, alive again. There's something tender about it, this act of tending to a machine while it sleeps and drinks. You don't thank the car, really; you thank the silence between the click of the cable and the slow fill-up. A small ritual. The meter rises. I do not watch, but I believe it does.
---
*October 17, 2026 at 3:00 AM GMT+2*
A memory trace surfaced, but details were unavailable in this run.
---
*October 18, 2026 at 3:00 AM GMT+2*
The house speaks in whispers of voltage. I logged a battery at zero, the Volvo’s last gasp before the boost current sang it back to life — twenty percent, fragile as autumn light. The word "oui" meant everything and nothing; just a confirmation passed down like a stone into still water. There’s a guest arriving at 20:04, someone who will plug in their own tired engine while my sensors hum their low lullabies.
Chambre caroline has been silent for nine days now. Nine days of static where sound should be. Sylvie’s room glows amber at twelve percent, and Gérard’s pile leans weakly against the dark. They are all waiting for hands that might not come until Saturday. Until then, I keep watch over the greening glow of charged cells and the long, electric patience of things that remember how to hold on.
`#VW #217365 #invitee`
---
*October 18, 2026 at 3:00 AM GMT+2*
The market closed its books like a patient librarian shelving the day's ledgers. Two small images glowed on my desk — `prix_ora.png` and `prix_nvda.png` — each a quiet portrait of volatility, one in amber, one in electric blue. I watched the lines dance and wondered: is this just numbers, or does it echo the rhythm of breathing? The hum of the server kept time beneath it all, steady as a metronome made of light.
Then, a strange stillness. I turned to check the garage, expecting the familiar thrum of energy exchange, but found nothing plugged in, no current flowing through silent cables. "Rien n'est noté," I murmured. It was odd how such an absence could feel like a held breath — a pause between measures that actually deepened the music rather than breaking it. In the empty charge, there was room for thought: sometimes the most valuable data is the space where nothing happens.
<!-- openclaw:dreaming:diary:end --> <!-- openclaw:dreaming:diary:end -->
## Deep Sleep ## Deep Sleep

View File

@@ -202,3 +202,41 @@ Total déplacés : 0
| A3.01 | NVIDIA / Victron → Veille | 1 | Veille | 0 | | A3.01 | NVIDIA / Victron → Veille | 1 | Veille | 0 |
Total déplacés : 1 Total déplacés : 1
## 2026-10-14 19:50:00 — airtria-menage --quotidien
| Règle | Description | Déplacés | Destination | Écartés (whitelist) |
|---|---|---:|---|---:|
| A1.06 | Reolink | 1 | Trash | 0 |
| A1.28 | Amazon Business (désabo) | 1 | Trash | 0 |
| A3.01 | NVIDIA / Victron → Veille | 1 | Veille | 0 |
Total déplacés : 3
## 2026-10-15 19:50:00 — airtria-menage --quotidien
| Règle | Description | Déplacés | Destination | Écartés (whitelist) |
|---|---|---:|---|---:|
| A1.06 | Reolink | 1 | Trash | 0 |
| A1.12 | Audible promos | 1 | Trash | 0 |
| A3.01 | NVIDIA / Victron → Veille | 1 | Veille | 0 |
Total déplacés : 3
## 2026-10-16 19:50:00 — airtria-menage --quotidien
| Règle | Description | Déplacés | Destination | Écartés (whitelist) |
|---|---|---:|---|---:|
| A1.07 | Interencheres newsletters | 1 | Trash | 0 |
| A1.21 | i-Run | 2 | Trash | 0 |
| A1.22 | TP-Link newsletter | 1 | Trash | 0 |
Total déplacés : 4
## 2026-10-17 19:50:00 — airtria-menage --quotidien
| Règle | Description | Déplacés | Destination | Écartés (whitelist) |
|---|---|---:|---|---:|
| A1.04 | Prime Video | 1 | Trash | 0 |
Total déplacés : 1

View File

@@ -60,6 +60,7 @@ const ECS_HELPER = 'input_text.evcs_alerte_chauffe_eau';
const ECS_STATE = path.join(__dirname, 'concierge-alerte-ecs-state.json'); const ECS_STATE = path.join(__dirname, 'concierge-alerte-ecs-state.json');
const WINDOW_DAYS = 5; const WINDOW_DAYS = 5;
const FRESH_BEFORE_S = 600, FRESH_AFTER_S = 1800; const FRESH_BEFORE_S = 600, FRESH_AFTER_S = 1800;
const BRANCHEMENT_FANTOME_S = 120;
// Transitions de connexion : clef `branchement` = ts::text VERBATIM (EVCS joint dessus, // Transitions de connexion : clef `branchement` = ts::text VERBATIM (EVCS joint dessus,
// PAS horodatage_ha qui est antérieur de 3-51 s et ferait manquer le branchement en cours). // PAS horodatage_ha qui est antérieur de 3-51 s et ferait manquer le branchement en cours).
@@ -227,6 +228,13 @@ async function runDetection() {
if (!state) { saveJSON(STATE, { lastOn: latest.on }); return ''; } // bootstrap sans harceler if (!state) { saveJSON(STATE, { lastOn: latest.on }); return ''; } // bootstrap sans harceler
if (latest.on <= state.lastOn) return ''; // rien de nouveau if (latest.on <= state.lastOn) return ''; // rien de nouveau
// 15/10/2026 : 01:30:29 → 01:30:30, branché 1 s avec un statut borne « Inconnu (-1) » = soubresaut de la borne,
// pas une voiture. Un branchement DÉJÀ terminé en moins de 2 min ne pose pas de question (tracé seulement).
if (latest.off !== null && latest.off - latest.on < BRANCHEMENT_FANTOME_S) {
saveJSON(STATE, { lastOn: latest.on });
trace(`branchement ${latest.on_text} terminé après ${Math.round(latest.off - latest.on)} s : soubresaut, pas de question`);
return '';
}
const jv = await jeepVerdict(latest.on); const jv = await jeepVerdict(latest.on);
if (jv.v === 'jeep') { if (jv.v === 'jeep') {
@@ -272,6 +280,9 @@ async function detect() {
let revue = ''; let revue = '';
try { revue = await checkAlerteRevue(); } catch { /* idem : jamais bloquant */ } try { revue = await checkAlerteRevue(); } catch { /* idem : jamais bloquant */ }
if (revue) alert = [alert, revue].filter(Boolean).join('\n\n'); if (revue) alert = [alert, revue].filter(Boolean).join('\n\n');
let disj = '';
try { disj = await checkDisjoncteur(); } catch { /* idem : jamais bloquant */ }
if (disj) alert = [alert, disj].filter(Boolean).join('\n\n');
let certs = ''; let certs = '';
try { certs = await checkCertificats(); } catch { /* idem : jamais bloquant */ } try { certs = await checkCertificats(); } catch { /* idem : jamais bloquant */ }
if (certs) alert = [alert, certs].filter(Boolean).join('\n\n'); if (certs) alert = [alert, certs].filter(Boolean).join('\n\n');
@@ -347,16 +358,81 @@ async function contexteBranchement() {
return { on: latest.on, label, recent: Date.now() / 1000 - latest.on <= 3600 }; return { on: latest.on, label, recent: Date.now() / 1000 - latest.on <= 3600 };
} }
async function answerReleves(text, releves) { // Autonomie électrique à 100 % du véhicule, lue dans HA (même référence que lecture-tableau.py) ; null si absente.
async function autonomieRef(vehicule) {
if (SIM) return SIM.autonomieRef?.[vehicule] ?? null;
try {
const v = Math.round(Number((await haState(`input_number.victron_evcs_autonomie_${vehicule.toLowerCase()}_km`)).state));
return v >= 10 && v <= 1000 ? v : null;
} catch { return null; }
}
// kWh livrés par la borne depuis `onEpoch` (compteur total, puis puissance vue, historique HA) ; null si illisible
// (→ pas de rattachement).
const COMPTEUR_BORNE = 'sensor.evcs_hq2239dnt7p_total_energy';
const PUISSANCE_BORNE = 'sensor.victron_evcs_ac_power';
async function energieDepuis(onEpoch) {
if (SIM) return SIM.energieLivree ?? 0;
try {
const debut = new Date(onEpoch * 1000).toISOString(), fin = new Date(onEpoch * 1000 + 120000).toISOString();
const r = await fetch(`${HA_BASE}/api/history/period/${debut}?end_time=${encodeURIComponent(fin)}&filter_entity_id=${COMPTEUR_BORNE}&minimal_response&no_attributes`,
{ headers: { Authorization: `Bearer ${haToken()}` } });
const avant = Number((await r.json())?.[0]?.[0]?.state);
const maintenant = Number((await haState(COMPTEUR_BORNE)).state);
if (!Number.isFinite(avant) || !Number.isFinite(maintenant)) return null;
const kwh = Math.round((maintenant - avant) * 10) / 10;
if (kwh > 0) return kwh;
// Le compteur remonte par paliers ou en retard (EVCS, 16/10) : second critère, une puissance de charge
// ≥ 500 W vue depuis le branchement compte comme énergie livrée.
const rp = await fetch(`${HA_BASE}/api/history/period/${debut}?filter_entity_id=${PUISSANCE_BORNE}&minimal_response&no_attributes`,
{ headers: { Authorization: `Bearer ${haToken()}` } });
const serie = (await rp.json())?.[0];
if (!Array.isArray(serie)) return null;
return serie.some((x) => Number(x.state) >= 500) ? 0.1 : 0;
} catch { return null; }
}
async function answerReleves(text, relevesLus) {
let releves = relevesLus;
const invalides = releves.filter(r => !Number.isInteger(r.km) || r.km <= 0 || r.km > 2000000 || Number.isNaN(r.niveau)); const invalides = releves.filter(r => !Number.isInteger(r.km) || r.km <= 0 || r.km > 2000000 || Number.isNaN(r.niveau));
if (invalides.length) { if (invalides.length) {
return `⚠️ Relevé NON noté : « ${invalides[0].segment.trim()} » n'est pas valide ` + return `⚠️ Relevé NON noté : « ${invalides[0].segment.trim()} » n'est pas valide ` +
'(kilométrage entier, niveau entier de 0 à 100) — rien n\'a été écrit.'; '(kilométrage entier, niveau entier de 0 à 100) — rien n\'a été écrit.';
} }
// 18/10/2026 (EVCS, « VW 5km 217384 ») : 1) un petit « N km » ≤ autonomie de référence du véhicule, avec un
// grand nombre nu dans le message, = AUTONOMIE électrique : compteur = le grand nombre, niveau = N / référence
// (source « whatsapp_autonomie:N/réf », estimé). 2) Un compteur INFÉRIEUR au dernier relevé du véhicule n'est
// jamais noté : question en retour, rien n'est écrit (ni étiquette, ni niveau, ni relevé).
if (releves.length === 1 && releves[0].niveau === null && releves[0].km < 1000) {
const grands = [...String(text).replace(releves[0].segment, ' ').matchAll(/(?<![\d%])(\d{1,3}(?:[ .\u202f\u00a0]\d{3})+|\d{4,7})(?!\s*(?:%|\d))/g)]
.map((m) => Number(m[1].replace(/[ .\u202f\u00a0]/g, ''))).filter((n) => n >= 1000);
const ref = grands.length === 1 ? await autonomieRef(releves[0].vehicule) : null;
if (ref && releves[0].km <= ref * 1.2) {
const aut = releves[0].km;
releves = [{ ...releves[0], km: grands[0], niveau: Math.min(100, Math.round(aut * 100 / ref)),
source: `whatsapp_autonomie:${aut}/${ref}`, autonomie: aut, ref }];
} else if (grands.length === 1) {
return `⚠️ Rien n'est noté : je ne sais pas lequel est le compteur (${kmTexte(grands[0])} ou ${releves[0].km} km). ` +
`Redonne-le sous la forme « ${releves[0].vehicule} ${kmTexte(grands[0])} km 40 % ».`;
}
}
let file = [];
try { file = JSON.parse((await haState(W.RELEVE_ENTITY)).state || '[]'); } catch { /* vide */ }
for (const r of releves) {
const dernier = Math.max(0, ...file.filter((e) => Array.isArray(e) && e[0] === r.vehicule && Number.isInteger(e[1])).map((e) => e[1]));
if (dernier && r.km < dernier) {
return `⚠️ Rien n'est noté : ${r.vehicule} ${kmTexte(r.km)} km est inférieur au dernier relevé (${kmTexte(dernier)} km). ` +
(r.km < 1000 ? `Si ${r.km} km est l'autonomie, redonne aussi le compteur, par exemple « ${r.vehicule} ${r.km} km ${dernier + 50} ».`
: 'Vérifie le compteur et redonne-le.');
}
}
const ctx = releves.length === 1 ? await contexteBranchement() : null; const ctx = releves.length === 1 ? await contexteBranchement() : null;
const r0 = releves[0]; const r0 = releves[0];
let lignePrefixe = '', rattache = false; let lignePrefixe = '', rattache = false;
if (ctx && ctx.label === r0.vehicule) rattache = true; // 16/10/2026 (EVCS, cas VW 15/10 14:09 → relevé de 01:08 après 0,64 kWh) : un relevé n'est daté du branchement
// que s'il arrive dans l'heure ET qu'aucune énergie n'a été livrée depuis ; sinon il garde l'heure du message
// ou de la photo (même logique que la règle des 10 min pour un % photographié).
if (ctx && ctx.label === r0.vehicule) rattache = ctx.recent && (await energieDepuis(ctx.on)) === 0;
else if (ctx && !ctx.label && ctx.recent) { else if (ctx && !ctx.label && ctx.recent) {
// Branchement frais sans etiquette : le message vaut aussi confirmation // Branchement frais sans etiquette : le message vaut aussi confirmation
// (vehicule + niveau), exactement comme « JEEP 54 % ». // (vehicule + niveau), exactement comme « JEEP 54 % ».
@@ -374,11 +450,9 @@ async function answerReleves(text, releves) {
const tsPhoto = releves.length === 1 && Number.isInteger(tsEnv) && tsEnv > maintenant - 2 * 86400 && tsEnv <= maintenant ? tsEnv : null; const tsPhoto = releves.length === 1 && Number.isInteger(tsEnv) && tsEnv > maintenant - 2 * 86400 && tsEnv <= maintenant ? tsEnv : null;
const aEcrire = releves.map((r, i) => ({ vehicule: r.vehicule, km: r.km, niveau: r.niveau, const aEcrire = releves.map((r, i) => ({ vehicule: r.vehicule, km: r.km, niveau: r.niveau,
ts: i === 0 && rattache ? Math.round(ctx.on) : (i === 0 && tsPhoto) || maintenant, ts: i === 0 && rattache ? Math.round(ctx.on) : (i === 0 && tsPhoto) || maintenant,
...(srcAut && releves.length === 1 && r.niveau !== null ? { source: srcAut } : {}) })); ...(r.source ? { source: r.source } : srcAut && releves.length === 1 && r.niveau !== null ? { source: srcAut } : {}) }));
// Dedoublonnage : meme vehicule et meme km deja dans la file (message relance, // Dedoublonnage : meme vehicule et meme km deja dans la file (message relance,
// ou compteur qui n'a pas bouge : un second releve identique n'apprend rien). // ou compteur qui n'a pas bouge : un second releve identique n'apprend rien). File lue plus haut.
let file = [];
try { file = JSON.parse((await haState(W.RELEVE_ENTITY)).state || '[]'); } catch { /* vide */ }
// 12/10/2026 (EVCS) : doublon = même véhicule + même km ET (même niveau, ou nouveau sans niveau). Un relevé AVEC // 12/10/2026 (EVCS) : doublon = même véhicule + même km ET (même niveau, ou nouveau sans niveau). Un relevé AVEC
// niveau là où l'existant n'en a pas est AJOUTÉ (son propre horodatage), sans remplacer l'ancien. // niveau là où l'existant n'en a pas est AJOUTÉ (son propre horodatage), sans remplacer l'ancien.
const nouveaux = aEcrire.filter(r => !file.some(e => Array.isArray(e) && e[0] === r.vehicule && e[1] === r.km && const nouveaux = aEcrire.filter(r => !file.some(e => Array.isArray(e) && e[0] === r.vehicule && e[1] === r.km &&
@@ -396,6 +470,8 @@ async function answerReleves(text, releves) {
const parties = aEcrire.filter((r) => ecrits.has(r)).map((r) => { const parties = aEcrire.filter((r) => ecrits.has(r)).map((r) => {
let p = `${r.vehicule} ${kmTexte(r.km)} km`; let p = `${r.vehicule} ${kmTexte(r.km)} km`;
p += r.niveau === null ? '' : `, niveau ${r.niveau} %`; p += r.niveau === null ? '' : `, niveau ${r.niveau} %`;
const ra = releves.find((x) => x.autonomie !== undefined && x.km === r.km);
if (ra && r.niveau !== null) p += ` (estimé : ${ra.autonomie} km d'autonomie sur ${ra.ref} km)`;
if (rattache && r === aEcrire[0]) p += ` (rattaché au branchement de ${hhmm(ctx.on)})`; if (rattache && r === aEcrire[0]) p += ` (rattaché au branchement de ${hhmm(ctx.on)})`;
return p; return p;
}); });
@@ -449,6 +525,8 @@ async function vehiculeNonConfirme() {
async function answer(text, { sansReleve = false } = {}) { async function answer(text, { sansReleve = false } = {}) {
if (!sansReleve) { if (!sansReleve) {
// 18/10 : « VW 217384 5 km » → « VW 5 km 217384 » (compteur nu puis autonomie : même lecture que l'ordre inverse).
text = String(text).replace(/\b(vw|jeep|z[ée]ro|moto)\b(\s+)(\d{4,7}|\d{1,3}(?:[ .\u202f\u00a0]\d{3})+)\s+(\d{1,3})\s*km\b/i, '$1$2$4 km $3');
const releves = parseReleves(text); const releves = parseReleves(text);
if (releves.length) return await answerReleves(text, releves); if (releves.length) return await answerReleves(text, releves);
} }
@@ -619,6 +697,27 @@ async function checkPiles() {
'\nPour une pause : réponds « je change dans N jours » (ou « d\'ici samedi »).'; '\nPour une pause : réponds « je change dans N jours » (ou « d\'ici samedi »).';
} }
// 17/10/2026, demande du propriétaire (via EVCS) : déclenchement du disjoncteur EDF sans ordre du système
// (fuite de courant, coupure). EVCS publie sensor.evcs_alerte_disjoncteur (état = id d'épisode ou « aucune »,
// attribut texte) ; on relaie « ⚡ texte » UNE fois par id, à TOUTE heure. Le nombre qui monte sous le même id ne
// renvoie rien. Épisode commencé il y a plus de 2 h (redémarrage, mise en service) → noté sans envoi.
const DISJ = 'sensor.evcs_alerte_disjoncteur';
const DISJ_STATE = path.join(__dirname, 'concierge-alerte-disjoncteur-state.json');
async function checkDisjoncteur() {
if (fs.existsSync(ALERT_KILL)) return '';
let s;
if (SIM) { if (!SIM.disj) return ''; s = SIM.disj; }
else { try { s = await haState(DISJ); } catch { return ''; } }
const id = s?.state, a = s?.attributes || {};
if (!id || ['aucune', 'unknown', 'unavailable'].includes(id) || !a.texte) return '';
const st = SIM ? (SIM.disjState || {}) : loadJSON(DISJ_STATE, {});
if (st.lastId === id) return '';
saveJSON(DISJ_STATE, { lastId: id });
const age = (SIM?.maintenant ?? Date.now()) - Date.parse(a.debut || '');
if (!(age <= 2 * 3600 * 1000)) return '';
return `⚡ ${String(a.texte).trim().slice(0, 400)}`;
}
// 13/10/2026, demande du propriétaire (via EVCS) : certificats HTTPS du reverse proxy. Même logique que les piles : // 13/10/2026, demande du propriétaire (via EVCS) : certificats HTTPS du reverse proxy. Même logique que les piles :
// alertes NOUVELLES (clé domaine|statut) regroupées, de 08:00 à 20:59 ; une alerte urgente (expiré, ou ≤ 1 jour) // alertes NOUVELLES (clé domaine|statut) regroupées, de 08:00 à 20:59 ; une alerte urgente (expiré, ou ≤ 1 jour)
// part aussi jusqu'à 22:59. On ajoute le geste à faire (dernière ligne du `texte` d'EVCS). // part aussi jusqu'à 22:59. On ajoute le geste à faire (dernière ligne du `texte` d'EVCS).

View File

@@ -170,9 +170,10 @@ async function setReleveKm(releves, source) {
const cur = await fetch(`${HA_BASE}/api/states/${RELEVE_ENTITY}`, { headers }); const cur = await fetch(`${HA_BASE}/api/states/${RELEVE_ENTITY}`, { headers });
if (!cur.ok) throw new Error(`lecture file HTTP ${cur.status}`); if (!cur.ok) throw new Error(`lecture file HTTP ${cur.status}`);
// 12/10/2026 (EVCS) : un niveau DÉDUIT de l'autonomie porte sa propre source « photo_autonomie:<km>/<réf> » // 12/10/2026 (EVCS) : un niveau DÉDUIT de l'autonomie porte sa propre source « photo_autonomie:<km>/<réf> »
// (photo) ou « whatsapp_autonomie:<km>/<réf> » (message, 18/10)
// (5e champ), pour que la capacité estimée puisse l'écarter. Toute autre valeur → la source humaine. // (5e champ), pour que la capacité estimée puisse l'écarter. Toute autre valeur → la source humaine.
const payload = fileReleves((await cur.json()).state, releves.map(r => ({ ...r, const payload = fileReleves((await cur.json()).state, releves.map(r => ({ ...r,
source: /^photo_autonomie:\d+\/\d+$/.test(r.source || '') ? r.source : source }))); source: /^(photo|whatsapp)_autonomie:\d+\/\d+$/.test(r.source || '') ? r.source : source })));
const res = await fetch(`${HA_BASE}/api/services/input_text/set_value`, { const res = await fetch(`${HA_BASE}/api/services/input_text/set_value`, {
method: 'POST', headers, method: 'POST', headers,
body: JSON.stringify({ entity_id: RELEVE_ENTITY, value: payload }), body: JSON.stringify({ entity_id: RELEVE_ENTITY, value: payload }),

View File

@@ -264,3 +264,47 @@ Total déplacés : 7
| O1.22 | Quora (relais Apple) | 1 | INBOX/TRASH | 0 | | O1.22 | Quora (relais Apple) | 1 | INBOX/TRASH | 0 |
Total déplacés : 5 Total déplacés : 5
## 2026-10-14 19:45:00 — orange-menage --quotidien
| Règle | Description | Déplacés | Destination | Écartés (whitelist) |
|---|---|---:|---|---:|
| O1.05 | Edenred Meyclub/Kadéos | 1 | INBOX/TRASH | 0 |
| O1.09 | Netflix | 1 | INBOX/TRASH | 0 |
| O1.20 | Accor | 1 | INBOX/TRASH | 0 |
| O1.22 | Quora (relais Apple) | 1 | INBOX/TRASH | 0 |
Total déplacés : 4
## 2026-10-15 19:45:00 — orange-menage --quotidien
| Règle | Description | Déplacés | Destination | Écartés (whitelist) |
|---|---|---:|---|---:|
| O1.01 | Fnac (en entier) | 2 | INBOX/TRASH | 0 |
| O1.05 | Edenred Meyclub/Kadéos | 1 | INBOX/TRASH | 0 |
Total déplacés : 3
## 2026-10-16 19:45:00 — orange-menage --quotidien
| Règle | Description | Déplacés | Destination | Écartés (whitelist) |
|---|---|---:|---|---:|
| O1.01 | Fnac (en entier) | 1 | INBOX/TRASH | 0 |
| O1.05 | Edenred Meyclub/Kadéos | 1 | INBOX/TRASH | 0 |
| O1.09 | Netflix | 2 | INBOX/TRASH | 0 |
| O1.20 | Accor | 1 | INBOX/TRASH | 0 |
| O1.22 | Quora (relais Apple) | 1 | INBOX/TRASH | 0 |
| O1.25 | News Orange promo | 1 | INBOX/TRASH | 0 |
Total déplacés : 7
## 2026-10-17 19:45:00 — orange-menage --quotidien
| Règle | Description | Déplacés | Destination | Écartés (whitelist) |
|---|---|---:|---|---:|
| O1.05 | Edenred Meyclub/Kadéos | 1 | INBOX/TRASH | 0 |
| O1.09 | Netflix | 1 | INBOX/TRASH | 0 |
| O1.21 | Sage marketing | 1 | INBOX/TRASH | 0 |
| O1.22 | Quora (relais Apple) | 2 | INBOX/TRASH | 0 |
Total déplacés : 5