SDB : capteur muet et garde batterie (workflow sous controle du depot) ; alertes de piles des capteurs
This commit is contained in:
@@ -33,6 +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 { 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';
|
||||
import { energieSur24h, energieDepuis, COMPTEUR_CHAUFFE_EAU } from './chauffe-eau.mjs';
|
||||
@@ -2064,6 +2065,28 @@ async function appliquerOrdreAirtria(sql, client, st) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------
|
||||
// PILES DES CAPTEURS (10/10/2026, tools/piles.mjs) : toutes les 15 min,
|
||||
// sensor.evcs_piles = nombre d'alertes ; attributs alertes [{entite, nom,
|
||||
// statut faible|muet, niveau, depuis, texte}] et texte. SPARK previent par
|
||||
// WhatsApp. input_text.evcs_piles_ignorees (liste separee par des virgules)
|
||||
// pour faire taire un appareil.
|
||||
let pilesPubliees = false;
|
||||
async function publierPiles() {
|
||||
const r = await fetch(`${HA_URL}/api/states`, { headers: { Authorization: `Bearer ${HA_TOKEN}` }, signal: AbortSignal.timeout(15000) });
|
||||
if (!r.ok) throw new Error(`HA ${r.status}`);
|
||||
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, {
|
||||
friendly_name: 'Piles des capteurs a changer', icon: 'mdi:battery-alert-variant-outline',
|
||||
alertes: p.alertes, texte: p.texte, ignorees: ign,
|
||||
});
|
||||
pilesPubliees = true;
|
||||
return 1;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------
|
||||
// ARBITRE ENCADRE (08/10/2026, tools/arbitrage.mjs)
|
||||
// ---------------------------------------------------------------------
|
||||
@@ -2633,6 +2656,9 @@ async function cycle(client) {
|
||||
rapport: DRY ? 0 : await envoyerRapportDuMatin(sql, client).catch((e) => {
|
||||
console.error('rapport de nuit :', e.message); return 0;
|
||||
}),
|
||||
piles: (DRY || (pilesPubliees && new Date().getMinutes() % 15 !== 6)) ? 0 : await publierPiles().catch((e) => {
|
||||
console.error('piles :', e.message); return 0;
|
||||
}),
|
||||
arbitrage: DRY ? 0 : await arbitrer(sql, client, st).catch((e) => {
|
||||
console.error('arbitrage :', e.message); return 0;
|
||||
}),
|
||||
|
||||
@@ -28,6 +28,17 @@ export const WORKFLOWS = [
|
||||
{ file: 'HA_SNAPSHOT_TEMPLATE.json.tpl', node: 'HA Snapshot Advisor', kind: 'template' },
|
||||
],
|
||||
},
|
||||
{
|
||||
// SDB Climate & Surplus Manager V2.2 (2NhrfxuGm4pTUNs9), sous controle du depot le 10/10/2026.
|
||||
id: 'sdb',
|
||||
workflowFile: 'workflows/sdb-climate-surplus-v2.2.json',
|
||||
srcDir: 'src/sdb',
|
||||
nodes: [
|
||||
{ file: 'COMPUTE.js', node: 'Compute SDB Climate & Surplus V2.2', kind: 'jsCode' },
|
||||
{ file: 'BUILD_ACTIONS.js', node: 'Build SDB Climate & Surplus Actions V2.2', kind: 'jsCode' },
|
||||
{ file: 'HA_SNAPSHOT_TEMPLATE.json.tpl', node: 'HA Snapshot SDB Climate & Surplus', kind: 'template' },
|
||||
],
|
||||
},
|
||||
{
|
||||
// Dialogue avec le conseiller dans Home Assistant (08/10/2026).
|
||||
id: 'conseiller-dialogue',
|
||||
|
||||
@@ -109,6 +109,7 @@ console.log('Syntaxe OK sur tous les nodes jsCode.');
|
||||
const isEvcsWorkflow = path.basename(INPUT) === 'evcs-surplus-controller-v2.6.json';
|
||||
const isAdvisorWorkflow = path.basename(INPUT) === 'evcs-advisor-shadow-v1.json';
|
||||
const isDialogueWorkflow = path.basename(INPUT) === 'evcs-conseiller-dialogue-v1.json';
|
||||
const isSdbWorkflow = path.basename(INPUT) === 'sdb-climate-surplus-v2.2.json';
|
||||
const isHysteresisWorkflow = path.basename(INPUT) === 'victron-grid-hysteresis-v1.json';
|
||||
const isAcmWorkflow = path.basename(INPUT) === 'victron-auto-charge-manager-v1.2.json';
|
||||
const isWaterHeaterWorkflow = path.basename(INPUT) === 'water-heater-ecs-v1.json';
|
||||
@@ -120,6 +121,7 @@ const testSuite =
|
||||
isEvcsWorkflow && fs.existsSync('tests/golden/run.mjs') ? 'tests/golden/run.mjs' :
|
||||
isAdvisorWorkflow && fs.existsSync('tests/advisor/run.mjs') ? 'tests/advisor/run.mjs' :
|
||||
isDialogueWorkflow && fs.existsSync('tests/conseiller-dialogue/run.mjs') ? 'tests/conseiller-dialogue/run.mjs' :
|
||||
isSdbWorkflow && fs.existsSync('tests/sdb/run.mjs') ? 'tests/sdb/run.mjs' :
|
||||
isHysteresisWorkflow && fs.existsSync('tests/golden-hysteresis/run.mjs') ? 'tests/golden-hysteresis/run.mjs' :
|
||||
isAcmWorkflow && fs.existsSync('tests/golden-acm/run.mjs') ? 'tests/golden-acm/run.mjs' :
|
||||
isBalanceWorkflow && fs.existsSync('tests/energy-balance/run.mjs') ? 'tests/energy-balance/run.mjs' :
|
||||
|
||||
52
tools/piles.mjs
Normal file
52
tools/piles.mjs
Normal file
@@ -0,0 +1,52 @@
|
||||
// =====================================================================
|
||||
// PILES DES CAPTEURS (10/10/2026, demande du proprietaire)
|
||||
// Le capteur temperature/humidite de la SDB est reste muet six jours (piles)
|
||||
// sans que personne ne le sache. Ce module repere, parmi les etats HA, les
|
||||
// capteurs d'APPAREILS a pile dont la pile est faible ou qui ne publient plus ;
|
||||
// le service d'historisation publie sensor.evcs_piles et SPARK previent par
|
||||
// WhatsApp. Fonction pure (tests : tests/piles/run.mjs).
|
||||
// =====================================================================
|
||||
|
||||
export const SEUIL_PCT = 20; // pile faible a 20 % et moins
|
||||
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_)/;
|
||||
|
||||
/**
|
||||
* @param {Array} etats liste des etats HA ({entity_id, state, attributes, last_changed})
|
||||
* @param {number} maintenantMs
|
||||
* @param {string[]} ignorees entites que le proprietaire a demande d'ignorer
|
||||
*/
|
||||
export function pilesAAlerter(etats, maintenantMs, ignorees = []) {
|
||||
const ign = new Set(ignorees.map((x) => x.trim()).filter(Boolean));
|
||||
const alertes = [];
|
||||
for (const s of etats) {
|
||||
const id = s.entity_id;
|
||||
if (!id.startsWith('sensor.') || EXCLUS.test(id) || ign.has(id)) continue;
|
||||
const a = s.attributes || {};
|
||||
const enPct = a.device_class === 'battery' && (a.unit_of_measurement === '%' || a.unit_of_measurement === undefined);
|
||||
const enEnum = /_etat_batterie$/.test(id);
|
||||
if (!enPct && !enEnum) continue;
|
||||
const nom = String(a.friendly_name || id).replace(/\s+/g, ' ').trim();
|
||||
const v = String(s.state ?? '').trim().toLowerCase();
|
||||
const depuisMs = Date.parse(s.last_changed);
|
||||
if (v === 'unavailable' || v === 'unknown' || v === '') {
|
||||
const h = Number.isFinite(depuisMs) ? (maintenantMs - depuisMs) / 3600000 : Infinity;
|
||||
if (h > MUET_H) alertes.push({ entite: id, nom, statut: 'muet', niveau: null, depuis: s.last_changed,
|
||||
texte: `${nom} ne publie plus depuis ${Number.isFinite(h) ? Math.round(h / 24) + ' j' : 'longtemps'} (pile vide ou appareil hors ligne)` });
|
||||
continue;
|
||||
}
|
||||
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,
|
||||
texte: `${nom} : pile a ${n} %` });
|
||||
} else if (v === 'low') {
|
||||
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));
|
||||
return {
|
||||
alertes,
|
||||
texte: alertes.length ? alertes.map((x) => `- ${x.texte}`).join('\n') : 'Toutes les piles des capteurs sont bonnes.',
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user