evcs_import_jour_kwh : le compteur du contacteur EDF fait foi, le MultiPlus en recoupement et en repli
Premier releve live (26/09 12:19) : compteur EDF 0,8 = VRM, delta des compteurs AC-in du MultiPlus 0,7 (0,6 + 0,1, pas apparent de 0,1 kWh). Repli sur le MultiPlus si le compteur EDF est muet ou remis a zero. Tests diagnostics 55/55, deux 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:
@@ -223,24 +223,25 @@ for (const [nom, r, test] of res) {
|
|||||||
console.log(`${ok ? 'OK ' : 'KO '} ${nom}`);
|
console.log(`${ok ? 'OK ' : 'KO '} ${nom}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Import depuis minuit. Valeurs de minuit fictives, deltas du 26/09 (0,8 kWh VRM).
|
// Import depuis minuit. Releve reel du 26/09 12:19 : EDF 0,8 (= VRM), MultiPlus 0,6 + 0,1.
|
||||||
const [OUT, INV] = COMPTEURS_IMPORT;
|
const [OUT, INV] = COMPTEURS_IMPORT;
|
||||||
const mn = { [OUT]: 1200.00, [INV]: 845.00, [COMPTEUR_EDF]: 3082.00 };
|
const mn = { [OUT]: 1200.0, [INV]: 845.0, [COMPTEUR_EDF]: 3082.0 };
|
||||||
|
const ok26 = { [OUT]: 1200.6, [INV]: 845.1, [COMPTEUR_EDF]: 3082.83 };
|
||||||
const imp = [
|
const imp = [
|
||||||
['26/09 : 0,5 vers maison + 0,3 vers batterie -> 0,8 complet, recoupe EDF',
|
['26/09 : le compteur EDF fait foi -> 0,8 comme VRM, MultiPlus 0,7 en recoupement',
|
||||||
importJour({ [OUT]: 1200.5, [INV]: 845.3, [COMPTEUR_EDF]: 3082.83 }, mn),
|
importJour(ok26, mn), (r) => r.etat === 0.8 && r.attributs.source === 'compteur_edf' && r.attributs.victron_kwh === 0.7],
|
||||||
(r) => r.etat === 0.8 && r.attributs.statut === 'complet' && r.attributs.compteur_edf_kwh === 0.8
|
|
||||||
&& r.attributs.detail_kwh.vers_ac_out === 0.5 && r.attributs.detail_kwh.vers_inverter === 0.3],
|
|
||||||
['journee sans reseau -> 0, pas indisponible',
|
['journee sans reseau -> 0, pas indisponible',
|
||||||
importJour({ [OUT]: 1200, [INV]: 845, [COMPTEUR_EDF]: 3082 }, mn), (r) => r.etat === 0 && r.attributs.statut === 'complet'],
|
importJour({ [OUT]: 1200, [INV]: 845, [COMPTEUR_EDF]: 3082 }, mn), (r) => r.etat === 0 && r.attributs.statut === 'complet'],
|
||||||
['compteur remis a zero -> indetermine, jamais un import negatif',
|
['compteur EDF remis a zero -> repli MultiPlus, jamais un import negatif',
|
||||||
importJour({ [OUT]: 3.2, [INV]: 845.3 }, mn), (r) => r.etat === 'indetermine'],
|
importJour({ ...ok26, [COMPTEUR_EDF]: 0.4 }, mn), (r) => r.etat === 0.7 && r.attributs.statut === 'repli_victron'],
|
||||||
['un compteur illisible -> partiel, au moins',
|
['compteur EDF muet -> repli MultiPlus, dit « environ »',
|
||||||
importJour({ [OUT]: 1200.5, [INV]: null }, mn), (r) => r.etat === 0.5 && r.attributs.statut === 'partiel'],
|
importJour({ ...ok26, [COMPTEUR_EDF]: null }, mn), (r) => r.etat === 0.7 && /Environ/.test(r.attributs.explication)],
|
||||||
['valeur de minuit inconnue -> indisponible',
|
['EDF muet ET un MultiPlus remis a zero -> indisponible',
|
||||||
importJour({ [OUT]: 1200.5, [INV]: 845.3 }, {}), (r) => r.etat === 'indisponible'],
|
importJour({ [OUT]: 3.2, [INV]: 845.1, [COMPTEUR_EDF]: null }, mn), (r) => r.etat === 'indisponible'],
|
||||||
['compteur EDF absent : le total ne change pas, recoupement null',
|
['EDF muet ET un MultiPlus muet -> indisponible, pas de total partiel',
|
||||||
importJour({ [OUT]: 1200.5, [INV]: 845.3 }, mn), (r) => r.etat === 0.8 && r.attributs.compteur_edf_kwh === null],
|
importJour({ [OUT]: 1200.6, [INV]: null, [COMPTEUR_EDF]: null }, mn), (r) => r.etat === 'indisponible'],
|
||||||
|
['valeurs de minuit inconnues -> indisponible',
|
||||||
|
importJour(ok26, {}), (r) => r.etat === 'indisponible'],
|
||||||
];
|
];
|
||||||
for (const [nom, r, test] of imp) {
|
for (const [nom, r, test] of imp) {
|
||||||
const ok = test(r);
|
const ok = test(r);
|
||||||
|
|||||||
@@ -418,51 +418,58 @@ export function etatReseau(st, maintenant = new Date()) {
|
|||||||
// La question « combien ai-je importe » est une QUANTITE ; la puissance
|
// La question « combien ai-je importe » est une QUANTITE ; la puissance
|
||||||
// instantanee (victron_grid_power_l1) n'y repond pas, elle vaut 0 des que le
|
// instantanee (victron_grid_power_l1) n'y repond pas, elle vaut 0 des que le
|
||||||
// contacteur est ouvert.
|
// contacteur est ouvert.
|
||||||
// Source : les compteurs cumules AC-in du MultiPlus, sains et monotones (HANDOFF
|
//
|
||||||
// §76 : 1,60 kWh la nuit du 24/09, recoupes par le compteur EDF a 1,53). Import
|
// SOURCE QUI FAIT FOI : le compteur du contacteur EDF (Tuya, 0,01 kWh). Premier
|
||||||
// = vers la maison (ac_out) + vers la batterie (inverter). Le compteur du
|
// jour en service (26/09 12:19) : il donnait 0,8 comme VRM, quand le delta des
|
||||||
// contacteur EDF est publie a cote comme recoupement, pas additionne.
|
// compteurs AC-in du MultiPlus donnait 0,7 (0,6 + 0,1). Ces compteurs VE.Bus
|
||||||
|
// semblent avancer par pas de 0,1 kWh : sur une matinee, deux arrondis de ce
|
||||||
|
// pas font 12 %. Ils restent publies en recoupement, et servent de REPLI si le
|
||||||
|
// compteur EDF est muet ou remis a zero (statut repli_victron).
|
||||||
// ⚠️ Compteurs sans last_reset : une valeur courante INFERIEURE a celle de
|
// ⚠️ Compteurs sans last_reset : une valeur courante INFERIEURE a celle de
|
||||||
// minuit est une remise a zero, pas un import negatif -> indetermine.
|
// minuit est une remise a zero, pas un import negatif.
|
||||||
export const COMPTEURS_IMPORT = [
|
export const COMPTEURS_IMPORT = [
|
||||||
'sensor.airtria_mpii_v1_energy_from_ac_in_1_to_ac_out',
|
'sensor.airtria_mpii_v1_energy_from_ac_in_1_to_ac_out',
|
||||||
'sensor.airtria_mpii_v1_energy_from_ac_in_1_to_inverter',
|
'sensor.airtria_mpii_v1_energy_from_ac_in_1_to_inverter',
|
||||||
];
|
];
|
||||||
export const COMPTEUR_EDF = 'sensor.edf_general_ok_energie_totale';
|
export const COMPTEUR_EDF = 'sensor.edf_general_ok_energie_totale';
|
||||||
|
|
||||||
|
function delta(c, m) {
|
||||||
|
if (!Number.isFinite(c) || !Number.isFinite(m)) return { muet: true };
|
||||||
|
if (c < m) return { remis: true };
|
||||||
|
return { v: c - m };
|
||||||
|
}
|
||||||
|
|
||||||
// courant, minuit : { [entity_id]: nombre | null }
|
// courant, minuit : { [entity_id]: nombre | null }
|
||||||
export function importJour(courant, minuit) {
|
export function importJour(courant, minuit) {
|
||||||
|
const edf = delta(courant[COMPTEUR_EDF], minuit[COMPTEUR_EDF]);
|
||||||
|
|
||||||
|
// Recoupement Victron (et repli).
|
||||||
const detail = {};
|
const detail = {};
|
||||||
const muets = [];
|
const muets = [];
|
||||||
let total = 0;
|
let remis = null;
|
||||||
|
let victron = 0;
|
||||||
for (const id of COMPTEURS_IMPORT) {
|
for (const id of COMPTEURS_IMPORT) {
|
||||||
const c = courant[id], m = minuit[id];
|
|
||||||
const court = id.replace('sensor.airtria_mpii_v1_energy_from_ac_in_1_to_', 'vers_');
|
const court = id.replace('sensor.airtria_mpii_v1_energy_from_ac_in_1_to_', 'vers_');
|
||||||
if (!Number.isFinite(c) || !Number.isFinite(m)) { muets.push(court); detail[court] = null; continue; }
|
const d = delta(courant[id], minuit[id]);
|
||||||
if (c < m) {
|
if (d.remis) { remis = court; detail[court] = null; continue; }
|
||||||
return { etat: 'indetermine', attributs: { statut: 'indetermine',
|
if (d.muet) { muets.push(court); detail[court] = null; continue; }
|
||||||
explication: `Compteur ${court} remis a zero depuis minuit : import du jour inconnu.` } };
|
detail[court] = Math.round(d.v * 100) / 100;
|
||||||
|
victron += d.v;
|
||||||
}
|
}
|
||||||
detail[court] = Math.round((c - m) * 100) / 100;
|
const victronOk = remis === null && muets.length === 0;
|
||||||
total += c - m;
|
const victronKwh = victronOk ? Math.round(victron * 10) / 10 : null;
|
||||||
|
const commun = { detail_victron_kwh: detail, victron_kwh: victronKwh };
|
||||||
|
|
||||||
|
if (edf.v !== undefined) {
|
||||||
|
const total = Math.round(edf.v * 10) / 10;
|
||||||
|
return { etat: total, attributs: { statut: 'complet', source: 'compteur_edf', ...commun,
|
||||||
|
explication: `${total} kWh importes d'EDF depuis minuit.` } };
|
||||||
}
|
}
|
||||||
if (muets.length === COMPTEURS_IMPORT.length) {
|
if (victronOk) {
|
||||||
return { etat: 'indisponible', attributs: { statut: 'indisponible',
|
return { etat: victronKwh, attributs: { statut: 'repli_victron', source: 'compteurs_multiplus', ...commun,
|
||||||
explication: 'Compteurs d\'energie AC-in illisibles : import du jour inconnu.' } };
|
explication: `Environ ${victronKwh} kWh importes d'EDF depuis minuit (compteur EDF ` +
|
||||||
|
`${edf.remis ? 'remis a zero' : 'muet'}, valeur des compteurs du MultiPlus, a 0,1 kWh pres).` } };
|
||||||
}
|
}
|
||||||
total = Math.round(total * 10) / 10;
|
return { etat: 'indisponible', attributs: { statut: 'indisponible', ...commun,
|
||||||
const ce = courant[COMPTEUR_EDF], me = minuit[COMPTEUR_EDF];
|
explication: 'Compteurs d\'energie muets ou remis a zero : import du jour inconnu.' } };
|
||||||
const edf = Number.isFinite(ce) && Number.isFinite(me) && ce >= me ? Math.round((ce - me) * 10) / 10 : null;
|
|
||||||
const partiel = muets.length > 0;
|
|
||||||
return {
|
|
||||||
etat: total,
|
|
||||||
attributs: {
|
|
||||||
statut: partiel ? 'partiel' : 'complet',
|
|
||||||
detail_kwh: detail,
|
|
||||||
compteur_edf_kwh: edf,
|
|
||||||
explication: partiel
|
|
||||||
? `Au moins ${total} kWh importes d'EDF depuis minuit (compteur ${muets.join(', ')} muet).`
|
|
||||||
: `${total} kWh importes d'EDF depuis minuit.`,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1895,7 +1895,7 @@ async function valeursAMinuit() {
|
|||||||
if (id) valeurs[id] = nums.length ? nums[nums.length - 1] : null;
|
if (id) valeurs[id] = nums.length ? nums[nums.length - 1] : null;
|
||||||
}
|
}
|
||||||
// On ne met en cache qu'une lecture complete : sinon on reessaie au cycle suivant.
|
// On ne met en cache qu'une lecture complete : sinon on reessaie au cycle suivant.
|
||||||
if (COMPTEURS_IMPORT.every((id) => Number.isFinite(valeurs[id]))) minuitCache = { jour, valeurs };
|
if (IDS_IMPORT.every((id) => Number.isFinite(valeurs[id]))) minuitCache = { jour, valeurs };
|
||||||
return valeurs;
|
return valeurs;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user