Revue : EDF absent en session, voiture payee par la batterie, interventions, kWh sur la puissance ; piles : hysteresis et acquittement
This commit is contained in:
@@ -33,7 +33,7 @@ import { creerSchemaKm, ingererReleves, ingererNiveauSaisi, ingererJeep } from '
|
||||
import { texteRapport, envoyer, donneesNuit, texteDepuis } from './rapport-nuit.mjs';
|
||||
import { donneesRevue, constatsRevue } from './revue-jour.mjs';
|
||||
import { detecterSituation, lireChoix, DUREE_MIN as ARBITRAGE_DUREE_MIN } from './arbitrage.mjs';
|
||||
import { pilesAAlerter } from './piles.mjs';
|
||||
import { pilesAAlerter, lireAcquittements } from './piles.mjs';
|
||||
import { DOMAINES, lireCertificat, certificatsAAlerter } from './certificats.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';
|
||||
@@ -138,6 +138,7 @@ const TRANSITIONS_SUIVIES = [
|
||||
['input_select.victron_evcs_vehicle', 'intervention','Vehicule selectionne'],
|
||||
['input_select.victron_evcs_strategy', 'intervention','Strategie'],
|
||||
['input_select.victron_evcs_night_charge_mode', 'intervention','Charge de la voiture la nuit'],
|
||||
['input_boolean.evcs_battery_manual_hc', 'intervention','Charge batterie manuelle en heures creuses'],
|
||||
['switch.tt_chauffe_eau_pleumeur_switch', 'chauffe_eau', 'Chauffe-eau sous tension'],
|
||||
['input_boolean.evcs_balance_ecs_nuit', 'chauffe_eau', 'Chauffe-eau en HC de nuit (jour faible)'],
|
||||
['input_text.evcs_balance_ecs_nuit_jour', 'chauffe_eau', 'Decision du jour pour le chauffe-eau'],
|
||||
@@ -1288,7 +1289,7 @@ async function publierRevueHistorique(client) {
|
||||
// relayee par SPARK sur WhatsApp, historisee dans revue_alerte. La voiture
|
||||
// bloquee reste a l'arbitre (pas de doublon). Un episode qui dure (EDF en heures
|
||||
// pleines) ne donne qu'une alerte : on prolonge la ligne existante.
|
||||
const CODES_GLISSANTS = new Set(['edf_heures_pleines', 'charge_max_bridee_en_cours', 'charge_max_bridee',
|
||||
const CODES_GLISSANTS = new Set(['edf_heures_pleines', 'charge_max_bridee_en_cours', 'charge_max_bridee', 'voiture_payee_par_batterie',
|
||||
'batterie_sous_objectif_nuit', 'batterie_sous_objectif_midi']);
|
||||
let revueGlissanteSchema = false;
|
||||
async function revueGlissante(sql, client, st) {
|
||||
@@ -2160,10 +2161,15 @@ async function publierPiles() {
|
||||
const etats = await r.json();
|
||||
const ign = String(etats.find((x) => x.entity_id === 'input_text.evcs_piles_ignorees')?.state || '')
|
||||
.split(',').filter((x) => x && x !== 'unknown');
|
||||
const p = pilesAAlerter(etats, Date.now(), ign);
|
||||
await publierCapteur('sensor.evcs_piles', p.alertes.length, {
|
||||
const avant = etats.find((x) => x.entity_id === 'sensor.evcs_piles')?.attributes?.alertes || [];
|
||||
const acquit = etats.find((x) => x.entity_id === 'input_text.evcs_piles_acquittees')?.state;
|
||||
const p = pilesAAlerter(etats, Date.now(), ign, {
|
||||
precedentes: avant.map((a) => a.entite), acquittements: lireAcquittements(acquit) });
|
||||
// Etat = alertes NON acquittees (13/10) : ce que le concierge doit rappeler.
|
||||
await publierCapteur('sensor.evcs_piles', p.a_rappeler.length, {
|
||||
friendly_name: 'Piles des capteurs a changer', icon: 'mdi:battery-alert-variant-outline',
|
||||
alertes: p.alertes, texte: p.texte, ignorees: ign,
|
||||
alertes: p.alertes, a_rappeler: p.a_rappeler, acquittees: p.acquittees, texte: p.texte, ignorees: ign,
|
||||
format_acquittement: 'input_text.evcs_piles_acquittees = cle:AAAA-MM-JJ[,cle:AAAA-MM-JJ] (cle = attribut cle de l alerte)',
|
||||
});
|
||||
pilesPubliees = true;
|
||||
return 1;
|
||||
|
||||
@@ -8,6 +8,10 @@
|
||||
// =====================================================================
|
||||
|
||||
export const SEUIL_PCT = 20; // pile faible a 20 % et moins
|
||||
// HYSTERESIS (13/10) : une alerte ne retombe qu'a 40 % et plus (en pratique : un
|
||||
// changement de piles). GRENIER 13 -> 19 -> 6 % et Chambre Gerard ont fait
|
||||
// partir 5 messages le 13/10, l'alerte disparaissant puis revenant autour de 20 %.
|
||||
export const SEUIL_RETOUR_PCT = 40;
|
||||
export const MUET_H = 24; // indisponible depuis plus de 24 h
|
||||
// Pas des piles de capteurs : parc Victron, voiture, telephones, reglages GX.
|
||||
const EXCLUS = /^(sensor|number|binary_sensor)\.(victron_|gx_device_|airtria_8_|jeep_|iphone_|ipad_|samsung_)/;
|
||||
@@ -17,8 +21,31 @@ const EXCLUS = /^(sensor|number|binary_sensor)\.(victron_|gx_device_|airtria_8_|
|
||||
* @param {number} maintenantMs
|
||||
* @param {string[]} ignorees entites que le proprietaire a demande d'ignorer
|
||||
*/
|
||||
export function pilesAAlerter(etats, maintenantMs, ignorees = []) {
|
||||
// ACQUITTEMENT (13/10, demande du proprietaire) : « j'ai compris, je change les
|
||||
// piles dans N jours » -> plus de rappel pour cet appareil jusqu'a la date dite.
|
||||
// input_text.evcs_piles_acquittees = « cle:AAAA-MM-JJ,cle:AAAA-MM-JJ », cle =
|
||||
// entite sans « sensor. » ; valable jusqu'a la fin de ce jour (heure de Paris).
|
||||
// Ecrit par le concierge (SPARK) ; une cle d'un appareil qui n'est plus en
|
||||
// alerte (piles changees) est simplement sans effet.
|
||||
export function lireAcquittements(texte) {
|
||||
const out = {};
|
||||
for (const part of String(texte || '').split(',')) {
|
||||
const m = /^\s*([a-z0-9_]+)\s*:\s*(\d{4}-\d{2}-\d{2})\s*$/.exec(part);
|
||||
if (!m) continue;
|
||||
const fin = Date.parse(`${m[2]}T23:59:59+02:00`);
|
||||
if (Number.isFinite(fin)) out[`sensor.${m[1]}`] = { jusquAu: m[2], finMs: fin };
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {object} opts precedentes : entites en alerte au passage precedent (hysteresis)
|
||||
* acquittements : resultat de lireAcquittements
|
||||
*/
|
||||
export function pilesAAlerter(etats, maintenantMs, ignorees = [], opts = {}) {
|
||||
const ign = new Set(ignorees.map((x) => x.trim()).filter(Boolean));
|
||||
const prec = new Set(opts.precedentes || []);
|
||||
const acq = opts.acquittements || {};
|
||||
const alertes = [];
|
||||
for (const s of etats) {
|
||||
const id = s.entity_id;
|
||||
@@ -38,15 +65,30 @@ export function pilesAAlerter(etats, maintenantMs, ignorees = []) {
|
||||
}
|
||||
if (enPct) {
|
||||
const n = Number(v);
|
||||
if (Number.isFinite(n) && n <= SEUIL_PCT) alertes.push({ entite: id, nom, statut: 'faible', niveau: n, depuis: s.last_changed,
|
||||
if (Number.isFinite(n) && (n <= SEUIL_PCT || (prec.has(id) && n < SEUIL_RETOUR_PCT))) alertes.push({ entite: id, nom, statut: 'faible', niveau: n, depuis: s.last_changed,
|
||||
texte: `${nom} : pile a ${n} %` });
|
||||
} else if (v === 'low') {
|
||||
} else if (v === 'low' || (v === 'middle' && prec.has(id))) {
|
||||
// Meme hysteresis pour les etats : Chambre Gerard battait entre low et
|
||||
// middle le 13/10 (deux messages). L'alerte ne retombe qu'a « high ».
|
||||
alertes.push({ entite: id, nom, statut: 'faible', niveau: 'low', depuis: s.last_changed, texte: `${nom} : pile faible` });
|
||||
}
|
||||
}
|
||||
alertes.sort((x, y) => (x.statut === y.statut ? x.nom.localeCompare(y.nom) : x.statut === 'faible' ? -1 : 1));
|
||||
for (const a of alertes) {
|
||||
const k = acq[a.entite];
|
||||
a.acquittee = !!(k && k.finMs >= maintenantMs);
|
||||
if (a.acquittee) a.jusqu_au = k.jusquAu;
|
||||
a.cle = a.entite.replace(/^sensor\./, '');
|
||||
}
|
||||
const aRappeler = alertes.filter((a) => !a.acquittee);
|
||||
const acquittees = alertes.filter((a) => a.acquittee);
|
||||
const jour = (iso) => iso.split('-').reverse().slice(0, 2).join('/');
|
||||
return {
|
||||
alertes,
|
||||
texte: alertes.length ? alertes.map((x) => `- ${x.texte}`).join('\n') : 'Toutes les piles des capteurs sont bonnes.',
|
||||
a_rappeler: aRappeler,
|
||||
acquittees,
|
||||
texte: !alertes.length ? 'Toutes les piles des capteurs sont bonnes.'
|
||||
: [...aRappeler.map((x) => `- ${x.texte}`),
|
||||
...(acquittees.length ? [`Deja vues, pas de rappel avant la date dite : ${acquittees.map((x) => `${x.nom} (jusqu'au ${jour(x.jusqu_au)})`).join(', ')}.`] : [])].join('\n'),
|
||||
};
|
||||
}
|
||||
|
||||
@@ -21,6 +21,9 @@ const SEUIL_HP_MIN = 5; // fermeture EDF en heures pleines retenue
|
||||
const SEUIL_RESEAU_W = 100; // bruit de mesure du reseau (comme sensor.evcs_pv_diagnostic)
|
||||
const SEUIL_VOITURE_MIN = 10; // voiture bloquee pendant une session HC
|
||||
const SEUIL_OBJECTIF_PTS = 2; // batterie sous son objectif en fin de HC
|
||||
const SEUIL_EDF_ABSENT_MIN = 10; // EDF demande mais absent (13/10)
|
||||
const SEUIL_DECHARGE_W = 150; // la batterie DONNE (bruit ~ +-100 W)
|
||||
const SEUIL_VOITURE_BATT_KWH = 0.5; // voiture payee par la batterie (12/10)
|
||||
|
||||
const CHARGEURS_MAX_W = 7300; // 2 MultiPlus, 140 A (HANDOFF 07/10)
|
||||
const RENDEMENT_CHARGE = 0.92;
|
||||
@@ -70,10 +73,26 @@ export function enHc(ms, fenetres) {
|
||||
// Valeur d'une serie en escalier (derniere valeur connue a l'instant t).
|
||||
function aInstant(serie, t) {
|
||||
let v = null;
|
||||
for (const p of serie) { if (p.ts <= t) v = p.v; else break; }
|
||||
for (const p of serie || []) { if (p.ts <= t) v = p.v; else break; }
|
||||
return v;
|
||||
}
|
||||
|
||||
// Energie (kWh) d'une serie de puissance (W) entre a et b, minute par minute sur
|
||||
// la valeur EN VIGUEUR (les puissances ne sont historisees qu'a leur changement :
|
||||
// une valeur constante vaut jusqu'au point suivant). Seules les valeurs > 0
|
||||
// comptent. 13/10 : le compteur du disjoncteur EDF a ete REMPLACE (reparti de
|
||||
// zero) et la revue glissante a annonce « 0 kWh achetes » pour 17 min a ~600 W.
|
||||
// La puissance mesuree par le Victron ne depend d'aucun compteur remplacable.
|
||||
export function energieKwh(serie, a, b, signe = 1) {
|
||||
if (!serie || !serie.length) return null;
|
||||
let kwh = 0;
|
||||
for (let t = a; t < b; t += MIN) {
|
||||
const w = signe * Number(aInstant(serie, t));
|
||||
if (Number.isFinite(w) && w > 0) kwh += w / 60000;
|
||||
}
|
||||
return kwh;
|
||||
}
|
||||
|
||||
// Plages contigues ou `pred(t)` est vrai, echantillonnees a la minute.
|
||||
function plages(debut, fin, pred) {
|
||||
const out = [];
|
||||
@@ -104,7 +123,9 @@ function etatA(d, t) {
|
||||
`consigne reseau ${v(d.consigneW, ' W')}`,
|
||||
`courant de charge max fixe par l'ACM ${v(d.acmA, ' A')}`,
|
||||
`borne ${v(d.borneW, ' W')}`,
|
||||
`session HC engagee ${x ? (x.session_engagee ? 'oui' : 'non') : 'inconnu'}`,
|
||||
// La table `decision` n'enregistre que les changements d'ETAT du controleur :
|
||||
// la session de nuit se lit dans le journal quand il est fourni (13/10).
|
||||
`session HC engagee ${d.sessionNuit && d.sessionNuit.length ? (aInstant(d.sessionNuit, t) === 'on' ? 'oui' : 'non') : x ? (x.session_engagee ? 'oui' : 'non') : 'inconnu'}`,
|
||||
`mode de nuit ${mode || 'inconnu'}`,
|
||||
`budget voiture sans EDF ${v(d.budgetKwh, ' kWh', 2)}`,
|
||||
`SOC requis (bilan) ${v(d.requis, ' %', 1)}`,
|
||||
@@ -142,8 +163,7 @@ export function constatsRevue(d) {
|
||||
for (const p of fusion) {
|
||||
const min = (p.fin - p.debut) / MIN;
|
||||
if (min < SEUIL_HP_MIN) continue;
|
||||
const k0 = aInstant(d.edfKwh, p.debut), k1 = aInstant(d.edfKwh, p.fin);
|
||||
const kwh = k0 !== null && k1 !== null && k1 >= k0 ? k1 - k0 : null;
|
||||
const kwh = energieKwh(d.reseauW, p.debut, p.fin);
|
||||
const ouverture = [...d.contacteur].reverse().find((c) => c.v === 'on' && c.ts <= p.debut + 2 * MIN && c.ts >= p.debut - 30 * MIN);
|
||||
const socDebut = aInstant(d.soc, p.debut);
|
||||
constats.push({
|
||||
@@ -252,10 +272,80 @@ export function constatsRevue(d) {
|
||||
}
|
||||
}
|
||||
|
||||
// 5. EDF DEMANDE MAIS ABSENT (13/10). Nuit du 12-13/10 : session de nuit
|
||||
// engagee a 01:24, le disjoncteur n'executait pas la fermeture ; reseau a 0 W,
|
||||
// batterie 31 -> 26 % jusqu'au rearmement au levier a 05:57. La revue avait
|
||||
// ecrit « Rien a signaler ». Demande = session HC engagee (en HC) ou verrou
|
||||
// d'hysteresis ; absent = reseau <= 100 W ET batterie qui DONNE.
|
||||
const surEtat = (serie, t) => aInstant(serie || [], t) === 'on';
|
||||
const absentes = plages(d.debut, d.fin, (t) =>
|
||||
((surEtat(d.sessionNuit, t) && enHc(t, d.fenetres)) || surEtat(d.verrouHysteresis, t))
|
||||
&& !((aInstant(d.reseauW, t) ?? 0) > SEUIL_RESEAU_W)
|
||||
&& (aInstant(d.batterieW, t) ?? 0) < -SEUIL_DECHARGE_W);
|
||||
const fusionAbs = [];
|
||||
for (const p of absentes) {
|
||||
const der = fusionAbs[fusionAbs.length - 1];
|
||||
if (der && p.debut - der.fin <= 5 * MIN) der.fin = p.fin; else fusionAbs.push({ ...p });
|
||||
}
|
||||
for (const p of fusionAbs) {
|
||||
const min = (p.fin - p.debut) / MIN;
|
||||
if (min < SEUIL_EDF_ABSENT_MIN) continue;
|
||||
const s0 = aInstant(d.soc, p.debut), s1 = aInstant(d.soc, p.fin);
|
||||
const donne = energieKwh(d.batterieW, p.debut, p.fin, -1);
|
||||
const pourquoi = surEtat(d.sessionNuit, p.debut) ? 'session d\'heures creuses engagee' : 'verrou d\'hysteresis (batterie basse)';
|
||||
constats.push({
|
||||
code: 'edf_absent_session', gravite: 'a_examiner', debut: p.debut, fin: p.fin,
|
||||
texte: `EDF demande (${pourquoi}) mais ABSENT de ${hhmm(p.debut)} a ${hhmm(p.fin)} (${duree(min)}) : reseau a 0 W`
|
||||
+ (s0 !== null && s1 !== null ? `, batterie ${Math.round(s0)} % -> ${Math.round(s1)} %` : '')
|
||||
+ (donne !== null ? ` (${f1(donne)} kWh donnes par la batterie au lieu d'EDF)` : '')
|
||||
+ ' ; l\'ordre de fermeture n\'a pas ete execute par le disjoncteur.',
|
||||
chiffres: { minutes: Math.round(min), socDebut: s0, socFin: s1, kwh: donne === null ? null : Math.round(donne * 100) / 100 },
|
||||
etat: etatA(d, p.debut + MIN), contexte: '',
|
||||
});
|
||||
}
|
||||
|
||||
// 6. VOITURE PAYEE PAR LA BATTERIE (12/10). VW de 16:16 a 16:44 tenue par le
|
||||
// temps mini puis la tolerance nuage, batterie a -40/-74 A : ~1,9 kWh de la
|
||||
// batterie pour la voiture. Hors session reseau ; part batterie = min(borne,
|
||||
// decharge de la batterie), minute par minute.
|
||||
const payees = plages(d.debut, d.fin, (t) => {
|
||||
const x = dec(t);
|
||||
return (aInstant(d.borneW, t) ?? 0) > 500 && (aInstant(d.batterieW, t) ?? 0) < -SEUIL_DECHARGE_W
|
||||
&& !(x && /r[ée]seau/i.test(x.source_mode || '')) && !((aInstant(d.reseauW, t) ?? 0) > SEUIL_RESEAU_W);
|
||||
});
|
||||
const fusionPay = [];
|
||||
for (const p of payees) {
|
||||
const der = fusionPay[fusionPay.length - 1];
|
||||
if (der && p.debut - der.fin <= 5 * MIN) der.fin = p.fin; else fusionPay.push({ ...p });
|
||||
}
|
||||
for (const p of fusionPay) {
|
||||
let kwh = 0;
|
||||
for (let t = p.debut; t < p.fin; t += MIN) kwh += Math.min(aInstant(d.borneW, t) ?? 0, -(aInstant(d.batterieW, t) ?? 0)) / 60000;
|
||||
if (kwh < SEUIL_VOITURE_BATT_KWH) continue;
|
||||
const min = (p.fin - p.debut) / MIN;
|
||||
const x = dec(p.debut);
|
||||
constats.push({
|
||||
code: 'voiture_payee_par_batterie', gravite: 'a_examiner', debut: p.debut, fin: p.fin,
|
||||
texte: `Voiture en charge payee par la batterie maison de ${hhmm(p.debut)} a ${hhmm(p.fin)} (${duree(min)}) : ${f1(kwh)} kWh pris a la batterie (etat ${x ? x.etat : 'inconnu'}).`,
|
||||
chiffres: { minutes: Math.round(min), kwh: Math.round(kwh * 100) / 100 },
|
||||
etat: etatA(d, p.debut + 2 * MIN), contexte: (x?.raison || '').slice(0, 400),
|
||||
});
|
||||
}
|
||||
|
||||
// 7. INTERVENTIONS MANUELLES (info, 13/10) : une journee « tenue » grace a un
|
||||
// geste du proprietaire n'est pas une journee sans probleme.
|
||||
for (const iv of d.interventions || []) {
|
||||
if (iv.ts < d.debut || iv.ts >= d.fin) continue;
|
||||
constats.push({ code: 'intervention', gravite: 'info', debut: iv.ts, fin: iv.ts,
|
||||
texte: `Intervention a ${hhmm(iv.ts)} : ${iv.texte}.`, chiffres: {}, etat: etatA(d, iv.ts), contexte: '' });
|
||||
}
|
||||
|
||||
constats.sort((a, b) => a.debut - b.debut);
|
||||
const texte = constats.length
|
||||
? constats.map((c) => `- ${c.texte}`).join('\n')
|
||||
: 'Rien a signaler : pas d\'EDF en heures pleines, pas de voiture bloquee pendant une session, batterie a son objectif en fin d\'heures creuses.';
|
||||
: 'Aucun des cas surveilles n\'est apparu : EDF en heures pleines, EDF demande mais absent, voiture bloquee pendant une session, '
|
||||
+ 'voiture payee par la batterie, batterie sous son objectif en fin d\'heures creuses, recharge maximale bridee, intervention manuelle. '
|
||||
+ 'Ce n\'est pas la preuve que tout s\'est bien passe : seuls ces cas sont verifies.';
|
||||
return { constats, texte };
|
||||
}
|
||||
|
||||
@@ -284,6 +374,37 @@ export async function donneesRevue(client, fenetres, finMs, dureeMin = 24 * 60)
|
||||
(SELECT ts, valeur FROM mesure WHERE cle=$1 AND ts < $2 ORDER BY ts DESC LIMIT 1)
|
||||
UNION ALL (SELECT ts, valeur FROM mesure WHERE cle=$1 AND ts >= $2 AND ts < $3)) s ORDER BY ts`, [cle, d0, d1]))
|
||||
.map((r) => ({ ts: ms(r), v: Number(r.v) }));
|
||||
// Etats discrets suivis par le journal (evenement), avec l'etat avant `debut`.
|
||||
const etats = async (entite) => (await q(`SELECT ts, v FROM (
|
||||
(SELECT ts, detail->>'apres' v FROM evenement WHERE detail->>'entite'=$1 AND ts < $2 ORDER BY ts DESC LIMIT 1)
|
||||
UNION ALL (SELECT ts, detail->>'apres' v FROM evenement WHERE detail->>'entite'=$1 AND ts >= $2 AND ts < $3)) s ORDER BY ts`, [entite, d0, d1]))
|
||||
.map((r, i) => ({ ts: i === 0 && ms(r) < debut ? debut : ms(r), v: r.v }));
|
||||
// Gestes manuels : contacteur manoeuvre hors du systeme (aucun auteur HA :
|
||||
// levier ou appli), charge batterie manuelle armee, mode de nuit change
|
||||
// autrement que par l'automatisation de 12:00.
|
||||
const interventions = async () => {
|
||||
// Contacteur : seulement un etat qui TIENT 5 min (la nuit du 12-13/10, les
|
||||
// claquements sans auteur identifie ressemblaient sinon a des gestes).
|
||||
const rows = await q(`SELECT ts, resume, detail FROM (
|
||||
SELECT ts, resume, detail, lead(ts) OVER (PARTITION BY detail->>'entite' ORDER BY ts) suivant
|
||||
FROM evenement WHERE ts >= $1::timestamptz - interval '1 day' AND ts < $2::timestamptz + interval '1 day' AND detail->>'entite' IN
|
||||
('switch.edf_general_ok_switch','input_boolean.evcs_battery_manual_hc','input_select.victron_evcs_night_charge_mode')) e
|
||||
WHERE ts >= $1 AND ts < $2 AND (
|
||||
(detail->>'entite'='switch.edf_general_ok_switch' AND detail->>'auteur' IS NULL AND NOT coalesce((detail->>'aller_retour')::boolean, false)
|
||||
AND (suivant IS NULL OR suivant - ts >= interval '5 minutes'))
|
||||
OR (detail->>'entite'='input_boolean.evcs_battery_manual_hc' AND detail->>'apres'='on')
|
||||
OR (detail->>'entite'='input_select.victron_evcs_night_charge_mode' AND coalesce(detail->>'auteur_type','') <> 'automation'
|
||||
-- remise automatique a « sans_edf » a 12:00 (evcs_charge_nuit_retour_sans_edf), pas toujours attribuee
|
||||
AND NOT (detail->>'apres' = 'sans_edf' AND to_char(ts AT TIME ZONE 'Europe/Paris', 'HH24:MI') BETWEEN '11:59' AND '12:02'))) ORDER BY ts`, [d0, d1]);
|
||||
return rows.map((r) => {
|
||||
const e = r.detail?.entite, ap = r.detail?.apres;
|
||||
const texte = e === 'switch.edf_general_ok_switch'
|
||||
? `contacteur EDF ${ap === 'on' ? 'ferme' : 'ouvert'} hors du systeme (levier ou appli)`
|
||||
: e === 'input_boolean.evcs_battery_manual_hc' ? 'charge batterie manuelle en heures creuses armee'
|
||||
: `mode de nuit change en « ${ap} »${r.detail?.auteur_nom ? ` (${r.detail.auteur_nom})` : ''}`;
|
||||
return { ts: ms(r), texte };
|
||||
});
|
||||
};
|
||||
const decisions = (await q(`SELECT * FROM (
|
||||
(SELECT ts, profil, source_mode, etat, session_engagee, soc_pct, cible_matin_pct, raison FROM decision WHERE ts < $1 ORDER BY ts DESC LIMIT 1)
|
||||
UNION ALL (SELECT ts, profil, source_mode, etat, session_engagee, soc_pct, cible_matin_pct, raison FROM decision WHERE ts >= $1 AND ts < $2)) s ORDER BY ts`, [d0, d1]))
|
||||
@@ -303,5 +424,9 @@ export async function donneesRevue(client, fenetres, finMs, dureeMin = 24 * 60)
|
||||
.map((r) => ({ ts: ms(r), v: r.v })),
|
||||
soc: await serie('sensor.victron_battery_soc'),
|
||||
requis: await serie('input_number.evcs_balance_required_soc'),
|
||||
batterieW: await serie('sensor.victron_battery_power'),
|
||||
sessionNuit: await etats('input_boolean.victron_evcs_night_grid_session_committed'),
|
||||
verrouHysteresis: await etats('input_boolean.victron_grid_hysteresis_latch'),
|
||||
interventions: await interventions(),
|
||||
};
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user