sensor.evcs_reseau : verdict de raccordement conscient du contacteur (un 0 fige n'est pas une panne quand EDF est ouvert)
Incident du 26/09 : l'agent vocal a annonce le capteur reseau bloque a 07:24, alors que le contacteur s'etait ouvert a la fin des heures creuses. Le verdict ne juge l'age de la mesure que contacteur ferme ; ouvert, c'est non_raccorde. Tests diagnostics 37 -> 48, trois mutations qui mordent. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011xd7aRDEChdShwsECohnDN
This commit is contained in:
@@ -318,3 +318,95 @@ export function bilanNuit(d, texte, maintenant = new Date()) {
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------
|
||||
// Raccordement reseau — un 0 « fige » n'est PAS une panne quand EDF est ouvert
|
||||
// ---------------------------------------------------------------------
|
||||
// Incident du 26/09 : l'agent a annonce « capteur reseau bloque a 7h24 ». C'etait
|
||||
// la fin des heures creuses : contacteur ouvert, AC-IN non raccorde, import
|
||||
// reellement nul. HA ne date une valeur qu'a son CHANGEMENT : un 0 legitime garde
|
||||
// le last_changed de l'ouverture. L'age de la mesure seul donnerait donc une
|
||||
// fausse panne tous les matins. Le seul signal d'une vraie panne de mesure est la
|
||||
// CONTRADICTION commande/mesure (MANUEL §0) : contacteur commande ferme alors que
|
||||
// le MultiPlus ne voit pas le reseau, ou que la puissance ne publie plus.
|
||||
//
|
||||
// Fenetre d'etablissement apres fermeture : meme helper que le workflow
|
||||
// d'hysteresis (HANDOFF §29), 180 s par defaut.
|
||||
export const ETABLISSEMENT_DEFAUT_S = 180;
|
||||
// Contacteur ferme, la puissance reseau bouge en permanence (bruit de l'ESS a
|
||||
// consigne). Inchangee pendant ce delai => elle ne publie plus. Valeur
|
||||
// PROVISOIRE, a confronter a la cadence reelle de sensor.victron_grid_power_l1.
|
||||
export const MESURE_FIGEE_MIN = 10;
|
||||
|
||||
function ageMin(s, maintenant) {
|
||||
const t = s?.last_changed ? Date.parse(s.last_changed) : NaN;
|
||||
return Number.isFinite(t) ? Math.round((maintenant - t) / 60000) : null;
|
||||
}
|
||||
|
||||
function heureParis(iso) {
|
||||
if (!iso) return null;
|
||||
return new Date(iso).toLocaleTimeString('fr-FR', { timeZone: 'Europe/Paris', hour: '2-digit', minute: '2-digit' });
|
||||
}
|
||||
|
||||
export function etatReseau(st, maintenant = new Date()) {
|
||||
const sw = st.get('switch.edf_general_ok_switch');
|
||||
const contacteur = sw?.state ?? null;
|
||||
const acIn = nombre(st, 'sensor.victron_ac_in1_available'); // 1 = reseau vu
|
||||
const entree = st.get('sensor.airtria_mpii_v1_active_ac_input')?.state ?? null;
|
||||
const gp = st.get('sensor.victron_grid_power_l1');
|
||||
const reseau = nombre(st, 'sensor.victron_grid_power_l1');
|
||||
const etablissementS = nombre(st, 'input_number.evcs_grid_settle_seconds') ?? ETABLISSEMENT_DEFAUT_S;
|
||||
const depuisMin = ageMin(sw, maintenant);
|
||||
const mesureAgeMin = ageMin(gp, maintenant);
|
||||
|
||||
const attributs = {
|
||||
contacteur_edf: contacteur,
|
||||
depuis: sw?.last_changed ?? null,
|
||||
depuis_heure: heureParis(sw?.last_changed),
|
||||
ac_in_disponible: acIn,
|
||||
entree_active: entree,
|
||||
import_w: reseau === null ? null : Math.max(0, Math.round(reseau)),
|
||||
mesure_age_min: mesureAgeMin,
|
||||
seuil_mesure_figee_min: MESURE_FIGEE_MIN,
|
||||
};
|
||||
const verdict = (etat, explication, extra = {}) => ({ etat, attributs: { ...attributs, ...extra, explication } });
|
||||
|
||||
if (contacteur !== 'on' && contacteur !== 'off') {
|
||||
return verdict('indetermine', 'Etat du contacteur EDF inconnu : ne rien affirmer sur le reseau.');
|
||||
}
|
||||
|
||||
if (contacteur === 'off') {
|
||||
// Reseau vu ou import mesure alors que le contacteur est commande ouvert.
|
||||
if (acIn === 1 || (reseau !== null && reseau > IMPORT_BRUIT_W)) {
|
||||
return verdict('incoherent',
|
||||
`ANOMALIE : contacteur EDF commande ouvert, mais le MultiPlus voit le reseau ` +
|
||||
`(ac_in ${acIn}, ${attributs.import_w ?? '?'} W). A signaler.`);
|
||||
}
|
||||
return verdict('non_raccorde',
|
||||
`Reseau EDF non raccorde depuis ${attributs.depuis_heure ?? '?'} (contacteur ouvert) : ` +
|
||||
`import nul, ce n'est pas une panne de capteur.`, { import_w: 0 });
|
||||
}
|
||||
|
||||
// Contacteur ferme.
|
||||
if (depuisMin !== null && depuisMin * 60 < etablissementS) {
|
||||
return verdict('etablissement',
|
||||
`Contacteur EDF ferme a ${attributs.depuis_heure}, reseau en cours d'etablissement.`);
|
||||
}
|
||||
if (acIn === 0) {
|
||||
return verdict('mesure_muette',
|
||||
`ANOMALIE : contacteur EDF ferme depuis ${attributs.depuis_heure ?? '?'} mais le MultiPlus ` +
|
||||
`ne voit pas le reseau (coupure EDF ou defaut de lecture). Import inconnu.`, { import_w: null });
|
||||
}
|
||||
if (reseau === null) {
|
||||
return verdict('mesure_muette',
|
||||
'Contacteur EDF ferme mais la puissance reseau ne publie pas : import inconnu.', { import_w: null });
|
||||
}
|
||||
if (mesureAgeMin !== null && mesureAgeMin > MESURE_FIGEE_MIN
|
||||
&& (depuisMin === null || depuisMin > MESURE_FIGEE_MIN)) {
|
||||
return verdict('mesure_muette',
|
||||
`Contacteur EDF ferme mais la puissance reseau n'a pas bouge depuis ${mesureAgeMin} min : ` +
|
||||
`mesure figee, import inconnu.`, { import_w: null });
|
||||
}
|
||||
return verdict('raccorde',
|
||||
`Reseau EDF raccorde depuis ${attributs.depuis_heure ?? '?'}, import ${attributs.import_w} W.`);
|
||||
}
|
||||
|
||||
@@ -30,7 +30,7 @@ import path from 'node:path';
|
||||
import { fileURLToPath } from 'node:url';
|
||||
import pg from 'pg';
|
||||
import { texteRapport, envoyer, donneesNuit, texteDepuis } from './rapport-nuit.mjs';
|
||||
import { diagnosticPv, etatCellules, productionJour, chargeSolaire, bilanNuit } from './diagnostics-agent.mjs';
|
||||
import { diagnosticPv, etatCellules, productionJour, chargeSolaire, bilanNuit, etatReseau } from './diagnostics-agent.mjs';
|
||||
|
||||
const ROOT = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..');
|
||||
const ONCE = process.argv.includes('--once');
|
||||
@@ -1853,6 +1853,11 @@ async function publierDiagnosticPv(st) {
|
||||
await publierCapteur('sensor.evcs_charge_solaire', ch.etat, {
|
||||
friendly_name: 'EVCS — charge voiture (decision du controleur)', icon: 'mdi:ev-station', ...ch.attributs,
|
||||
});
|
||||
// Raccordement reseau : contacteur ouvert = import nul, pas une panne (26/09).
|
||||
const rr = etatReseau(st);
|
||||
await publierCapteur('sensor.evcs_reseau', rr.etat, {
|
||||
friendly_name: 'EVCS — raccordement reseau EDF', icon: 'mdi:transmission-tower', ...rr.attributs,
|
||||
});
|
||||
return 1;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user