Historisation : allers-retours brefs de la borne et du contacteur retrouves dans l'historique HA (echange VW -> JEEP de 20 s)
This commit is contained in:
24
tools/allers-retours.mjs
Normal file
24
tools/allers-retours.mjs
Normal file
@@ -0,0 +1,24 @@
|
||||
// Transitions d'etat retrouvees dans l'historique HA (06/10/2026).
|
||||
// Voir ALLERS_RETOURS_SUIVIS dans historise-evcs.mjs : un aller-retour plus
|
||||
// bref qu'un cycle du service ne se voit que par le `last_changed` de HA.
|
||||
const txt = (s) => (s ?? '').toString().trim();
|
||||
export const ETATS_ABSENTS = new Set(['', 'unavailable', 'unknown']);
|
||||
|
||||
// Transitions reelles entre `depuisIso` (exclu) et `jusquaIso` (inclus), en
|
||||
// partant de l'etat `depart`. Les lectures indisponibles ne comptent pas.
|
||||
// Pure (testee) : `serie` = reponse de /api/history/period pour une entite.
|
||||
export function transitionsDansSerie(serie, depart, depuisIso, jusquaIso) {
|
||||
const depuis = Date.parse(depuisIso), jusqua = Date.parse(jusquaIso);
|
||||
const res = [];
|
||||
let courant = depart;
|
||||
for (const e of serie || []) {
|
||||
const t = Date.parse(e.last_changed);
|
||||
if (!(t > depuis && t <= jusqua)) continue;
|
||||
const etat = txt(e.state);
|
||||
if (ETATS_ABSENTS.has(etat) || etat === courant) continue;
|
||||
res.push({ avant: courant, apres: etat, a: e.last_changed });
|
||||
courant = etat;
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
@@ -32,6 +32,7 @@ import pg from 'pg';
|
||||
import { creerSchemaKm, ingererReleves, ingererNiveauSaisi, ingererJeep } from './kilometrage.mjs';
|
||||
import { texteRapport, envoyer, donneesNuit, texteDepuis } from './rapport-nuit.mjs';
|
||||
import { diagnosticPv, etatCellules, productionJour, chargeSolaire, bilanNuit, etatReseau, importJour, minuitLocal, COMPTEURS_IMPORT, COMPTEUR_EDF, consoJour, COMPTEURS_CONSO, projectionSoir, previsionNuit, vrmVersCourbes, nuitMaisonPrevue } from './diagnostics-agent.mjs';
|
||||
import { transitionsDansSerie } from './allers-retours.mjs';
|
||||
|
||||
const ROOT = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..');
|
||||
const ONCE = process.argv.includes('--once');
|
||||
@@ -158,10 +159,13 @@ function demarrageHaMs(st) {
|
||||
return n >= 10 ? meilleur * 1000 : null;
|
||||
}
|
||||
|
||||
async function journaliser(sql, genre, resume, detail) {
|
||||
async function journaliser(sql, genre, resume, detail, ts = null) {
|
||||
// `ts` : horodatage HA d'une transition retrouvee apres coup (aller-retour
|
||||
// bref, cf. detecterEvenements) ; sinon l'heure d'ecriture (defaut de la table).
|
||||
await sql(
|
||||
`INSERT INTO evenement (genre, source, resume, detail) VALUES ($1, $2, $3, $4)`,
|
||||
[genre, 'historisation', resume, JSON.stringify(detail || {})]
|
||||
`INSERT INTO evenement (ts, genre, source, resume, detail)
|
||||
VALUES (COALESCE($5::timestamptz, now()), $1, $2, $3, $4)`,
|
||||
[genre, 'historisation', resume, JSON.stringify(detail || {}), ts]
|
||||
);
|
||||
console.log(` evenement [${genre}] ${resume}`);
|
||||
}
|
||||
@@ -430,13 +434,37 @@ async function ecrirePrevision(sql, st) {
|
||||
// Memoire entre cycles. Au PREMIER passage on enregistre l'etat sans rien
|
||||
// journaliser : sinon chaque redemarrage du service produirait douze fausses
|
||||
// transitions. Seul le demarrage du service lui-meme est consigne.
|
||||
const memoire = { amorce: false, demarrageHa: null, valeurs: new Map() };
|
||||
const memoire = { amorce: false, demarrageHa: null, valeurs: new Map(), changes: new Map() };
|
||||
|
||||
// ALLERS-RETOURS BREFS (06/10/2026). Le service lit l'etat une fois par cycle
|
||||
// (~75 s) : un debranchement-rebranchement de 20 s (echange VW -> JEEP a
|
||||
// 19:52:36 / 19:52:56 le 06/10) tombe entre deux lectures, l'etat est le meme
|
||||
// avant et apres, et AUCUN branchement n'etait vu — ni par l'etiquetage, ni par
|
||||
// le concierge qui lit cette table. Le `last_changed` de HA, lui, avance : sur
|
||||
// ces entites, s'il a bouge alors que l'etat est identique, on relit
|
||||
// l'historique HA de l'intervalle et on journalise chaque transition a SON
|
||||
// horodatage. C'est la signature d'un echange de vehicule.
|
||||
const ALLERS_RETOURS_SUIVIS = new Set([
|
||||
'binary_sensor.victron_evcs_vehicle_connected',
|
||||
'switch.edf_general_ok_switch',
|
||||
]);
|
||||
async function transitionsCachees(id, depart, depuisIso, jusquaIso) {
|
||||
const r = await fetch(
|
||||
`${HA_URL}/api/history/period/${encodeURIComponent(depuisIso)}?end_time=` +
|
||||
`${encodeURIComponent(new Date(Date.parse(jusquaIso) + 1000).toISOString())}` +
|
||||
`&filter_entity_id=${id}&minimal_response&no_attributes`,
|
||||
{ headers: { Authorization: `Bearer ${HA_TOKEN}` }, signal: AbortSignal.timeout(15000) },
|
||||
);
|
||||
if (!r.ok) throw new Error(`historique HA ${r.status}`);
|
||||
const series = await r.json();
|
||||
return transitionsDansSerie(series[0], depart, depuisIso, jusquaIso);
|
||||
}
|
||||
|
||||
async function detecterEvenements(sql, st) {
|
||||
if (!memoire.amorce) {
|
||||
for (const [id] of TRANSITIONS_SUIVIES.map((t) => [t[0]])) {
|
||||
const e = st.get(id);
|
||||
if (e) memoire.valeurs.set(id, txt(e.state));
|
||||
if (e) { memoire.valeurs.set(id, txt(e.state)); memoire.changes.set(id, e.last_changed); }
|
||||
}
|
||||
memoire.demarrageHa = demarrageHaMs(st);
|
||||
memoire.amorce = true;
|
||||
@@ -473,8 +501,30 @@ async function detecterEvenements(sql, st) {
|
||||
// lecture. La consigner ferait deux lignes de bruit a chaque hoquet MQTT.
|
||||
if (cur === '' || cur === 'unavailable' || cur === 'unknown') continue;
|
||||
const prec = memoire.valeurs.get(id);
|
||||
const changePrec = memoire.changes.get(id);
|
||||
memoire.changes.set(id, e.last_changed);
|
||||
if (prec === undefined) { memoire.valeurs.set(id, cur); continue; }
|
||||
if (prec === cur) continue;
|
||||
if (prec === cur) {
|
||||
if (!ALLERS_RETOURS_SUIVIS.has(id) || !changePrec
|
||||
|| !(Date.parse(e.last_changed) > Date.parse(changePrec))) continue;
|
||||
let cachees = [];
|
||||
try {
|
||||
cachees = await transitionsCachees(id, prec, changePrec, e.last_changed);
|
||||
} catch (err) {
|
||||
console.log(` aller-retour ${id} : historique illisible (${err.message})`);
|
||||
}
|
||||
for (const tr of cachees) {
|
||||
const quiC = await auteurDeLaTransition(id, tr.a);
|
||||
const resumeC = `${libelle} : ${tr.avant} -> ${tr.apres} (aller-retour bref, retrouve dans l historique HA)`
|
||||
+ (quiC ? (quiC.type === 'derivation' ? ` — derive de ${quiC.nom}` : ` — par ${quiC.nom}`) : '');
|
||||
await journaliser(sql, genre, resumeC, {
|
||||
entite: id, avant: tr.avant, apres: tr.apres, horodatage_ha: tr.a, aller_retour: true,
|
||||
...(quiC ? { auteur: quiC.auteur, auteur_nom: quiC.nom, auteur_type: quiC.type, declencheur: quiC.declencheur } : {}),
|
||||
}, tr.a);
|
||||
n++;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
const qui = await auteurDeLaTransition(id, e.last_changed);
|
||||
// L auteur est colle au RESUME, pas seulement au detail : c est le resume
|
||||
// que Grafana affiche en annotation sur tous les tableaux et dans les
|
||||
|
||||
Reference in New Issue
Block a user