Jeep identifiee par Uconnect : etiquette du branchement, selecteur et niveau poses automatiquement

This commit is contained in:
Tallagrand Thierry
2026-10-04 15:23:19 +02:00
parent a1ff5633db
commit 60bdf70e2d
3 changed files with 58 additions and 7 deletions

View File

@@ -1131,6 +1131,9 @@ const HOUSEHOLD = new Set(['VW', 'JEEP']);
// un canal inconnu ne doit pas valoir confirmation par defaut. `regle-puissance`
// n'y est pas et n'y sera jamais — c'est une deduction, pas une parole.
const CANAUX_HUMAINS = new Set(['whatsapp', 'proprietaire']);
// Sources qui CONFIRMENT un vehicule : l'humain, ou la voiture elle-meme
// (04/10/2026 : la Jeep se declare branchee via Uconnect).
const CANAUX_SURS = new Set([...CANAUX_HUMAINS, 'uconnect']);
const LABELS = new Set(['VW', 'JEEP', 'MEGANE', 'INVITE', 'ZERO']);
let dernierBlancVu = null; // anti-repetition du journal en essai a blanc
@@ -1196,7 +1199,7 @@ async function piloterSelecteurVehicule(sql, st, label, source) {
return 1;
}
await journaliser(sql, 'vehicule',
`Selecteur vehicule pilote par le concierge : ${actuel || 'inconnu'} -> ${label}`,
`Selecteur vehicule pilote par ${source === 'uconnect' ? 'la Jeep (Uconnect)' : 'le concierge'} : ${actuel || 'inconnu'} -> ${label}`,
{ origine: 'airtria', source_airtria: source, label, avant: actuel,
entite: 'input_select.victron_evcs_vehicle',
motif: 'la reponse du proprietaire fait foi, et le controleur lit le selecteur' });
@@ -1394,6 +1397,45 @@ async function appliquerEtiquetteConcierge(sql, client, st) {
// debranchement suivant — l invariant « un cable, un vehicule ». Les 2 secondes
// de tolerance sur la borne basse sont celles du 09/09 : une session s ouvre
// jusqu a 62 ms AVANT que sa transition de branchement soit journalisee.
// LA JEEP SE DECLARE ELLE-MEME (04/10/2026, accord du proprietaire). Appelee par
// ingererJeep a la premiere mise a jour Uconnect qui suit un branchement de la
// borne, Jeep « branchee ». Trois gestes, une fois par branchement :
// 1. etiquette JEEP du branchement (source uconnect, confirmee) — SANS ecraser
// une etiquette deja donnee par le proprietaire (ON CONFLICT DO NOTHING) ;
// 2. selecteur sur JEEP (heure de depart, regles du controleur) ;
// 3. niveau « saisi » du vehicule = niveau lu, sauf saisie humaine depuis le
// branchement - 1 h (l'automatisation HA recale la reference du compteur).
// Le concierge ne doit alors plus poser sa question : il lit etiquette_branchement.
async function etiqueterJeepUconnect(sql, client, st, branche, niveau) {
if (DRY) return 0;
const { rowCount } = await client.query(
`INSERT INTO etiquette_branchement (branchement, label, source_airtria, confirmed_at)
VALUES ($1, 'JEEP', 'uconnect', $2) ON CONFLICT (branchement) DO NOTHING`,
[branche, new Date().toISOString()]);
if (!rowCount) return 0; // deja etiquete (le proprietaire a repondu) : on ne touche a rien
await journaliser(sql, 'vehicule', `Branchement identifie : JEEP (la voiture se declare branchee, Uconnect), niveau ${niveau} %`,
{ origine: 'uconnect', label: 'JEEP', branchement: branche.toISOString(), niveau });
let n = 1 + await piloterSelecteurVehicule(sql, st, 'JEEP', 'uconnect');
const saisieA = Number(st.get('input_datetime.victron_evcs_vehicle_level_at')?.attributes?.timestamp) * 1000;
if (!(saisieA >= branche.getTime() - 3600000)) {
try {
const r = await fetch(`${HA_URL}/api/services/input_number/set_value`, {
method: 'POST',
headers: { Authorization: `Bearer ${HA_TOKEN}`, 'Content-Type': 'application/json' },
body: JSON.stringify({ entity_id: 'input_number.victron_evcs_vehicle_level_pct', value: niveau }),
signal: AbortSignal.timeout(8000),
});
if (!r.ok) throw new Error(`HTTP ${r.status}`);
await journaliser(sql, 'vehicule', `Niveau du vehicule branche pose a ${niveau} % (lu par la Jeep)`,
{ origine: 'uconnect', niveau, entite: 'input_number.victron_evcs_vehicle_level_pct' });
n++;
} catch (e) {
await journaliser(sql, 'vehicule', `Niveau JEEP NON pose (${e.message})`, { origine: 'uconnect', niveau });
}
}
return n;
}
async function appliquerEtiquettesEnAttente(sql, client) {
if (DRY) return 0;
const { rows } = await client.query(
@@ -1401,7 +1443,7 @@ async function appliquerEtiquettesEnAttente(sql, client) {
WHERE branchement > now() - interval '48 hours' ORDER BY branchement`);
let n = 0;
for (const e of rows) {
const sur = CANAUX_HUMAINS.has(e.source_airtria);
const sur = CANAUX_SURS.has(e.source_airtria);
const { rowCount } = await sql(
`UPDATE session_charge s SET vehicule = $2, vehicule_sur = $3
WHERE s.debut >= $1::timestamptz - interval '2 seconds'
@@ -2140,7 +2182,8 @@ async function cycle(client) {
console.error(`releves km en echec : ${e.message}`); return 0;
}) + await ingererNiveauSaisi(sql, st).catch((e) => {
console.error(`niveau saisi en echec : ${e.message}`); return 0;
}) + await ingererJeep(sql, (q, v) => client.query(q, v), st, journaliser).catch((e) => {
}) + await ingererJeep(sql, (q, v) => client.query(q, v), st, journaliser,
(branche, niveau) => etiqueterJeepUconnect(sql, client, st, branche, niveau)).catch((e) => {
console.error(`jeep uconnect en echec : ${e.message}`); return 0;
}),
pvDiagnostic: DRY ? 0 : await publierDiagnosticPv(st).catch((e) => {