Jeep identifiee par Uconnect : etiquette du branchement, selecteur et niveau poses automatiquement
This commit is contained in:
@@ -46,21 +46,25 @@ eq('helper corrompu repart a vide', JSON.parse(ajouterReleve('pas du json', { ve
|
|||||||
return { rows: [] };
|
return { rows: [] };
|
||||||
};
|
};
|
||||||
const journ = async () => {};
|
const journ = async () => {};
|
||||||
|
const appels = [];
|
||||||
|
const etiq = async (b, n) => { appels.push([b.toISOString(), n]); return 1; };
|
||||||
const etat = (km, niv, maj, branchee = 'on', borne = 'on') => new Map([
|
const etat = (km, niv, maj, branchee = 'on', borne = 'on') => new Map([
|
||||||
[JEEP.odometre, { state: String(km) }], [JEEP.niveau, { state: String(niv) }], [JEEP.majA, { state: maj }],
|
[JEEP.odometre, { state: String(km) }], [JEEP.niveau, { state: String(niv) }], [JEEP.majA, { state: maj }],
|
||||||
[JEEP.branchee, { state: branchee }], ['binary_sensor.victron_evcs_vehicle_connected', { state: borne }],
|
[JEEP.branchee, { state: branchee }], ['binary_sensor.victron_evcs_vehicle_connected', { state: borne }],
|
||||||
['input_number.victron_evcs_capacity_jeep_kwh', { state: '8.7' }], ['input_number.victron_evcs_autonomie_jeep_km', { state: '50' }]]);
|
['input_number.victron_evcs_capacity_jeep_kwh', { state: '8.7' }], ['input_number.victron_evcs_autonomie_jeep_km', { state: '50' }]]);
|
||||||
await ingererJeep(sql, lire, etat(112716, 46, '2026-10-04T11:28:39+00:00'), journ);
|
await ingererJeep(sql, lire, etat(112716, 46, '2026-10-04T11:28:39+00:00'), journ, etiq);
|
||||||
eq('jeep : premier releve + niveau au branchement', [base.releves.length, base.niveaux.length, base.niveaux[0]?.niveau], [1, 1, 46]);
|
eq('jeep : premier releve + niveau au branchement', [base.releves.length, base.niveaux.length, base.niveaux[0]?.niveau], [1, 1, 46]);
|
||||||
await ingererJeep(sql, lire, etat(112716, 60, '2026-10-04T11:58:39+00:00'), journ);
|
await ingererJeep(sql, lire, etat(112716, 60, '2026-10-04T11:58:39+00:00'), journ, etiq);
|
||||||
eq('jeep : compteur immobile et 2e maj du branchement -> rien', [base.releves.length, base.niveaux.length], [1, 1]);
|
eq('jeep : compteur immobile et 2e maj du branchement -> rien', [base.releves.length, base.niveaux.length], [1, 1]);
|
||||||
|
eq('jeep : branchement identifie UNE fois, avec le niveau lu', appels, [['2026-10-04T11:28:00.000Z', 46]]);
|
||||||
base.branche = new Date('2026-10-05T16:00:00Z');
|
base.branche = new Date('2026-10-05T16:00:00Z');
|
||||||
await ingererJeep(sql, lire, etat(112760, 30, '2026-10-05T17:00:00+00:00'), journ);
|
await ingererJeep(sql, lire, etat(112760, 30, '2026-10-05T17:00:00+00:00'), journ);
|
||||||
eq('jeep : trajet -> releve ; maj 1 h apres le branchement -> pas de niveau', [base.releves.length, base.releves[1]?.km, base.niveaux.length], [2, 112760, 1]);
|
eq('jeep : trajet -> releve ; maj 1 h apres le branchement -> pas de niveau', [base.releves.length, base.releves[1]?.km, base.niveaux.length], [2, 112760, 1]);
|
||||||
await ingererJeep(sql, lire, etat(112700, 30, '2026-10-05T18:00:00+00:00'), journ);
|
await ingererJeep(sql, lire, etat(112700, 30, '2026-10-05T18:00:00+00:00'), journ);
|
||||||
eq('jeep : compteur en baisse refuse', base.releves.length, 2);
|
eq('jeep : compteur en baisse refuse', base.releves.length, 2);
|
||||||
base.branche = new Date('2026-10-06T16:00:00Z');
|
base.branche = new Date('2026-10-06T16:00:00Z');
|
||||||
await ingererJeep(sql, lire, etat(112800, 25, '2026-10-06T16:05:00+00:00', 'on', 'off'), journ);
|
await ingererJeep(sql, lire, etat(112800, 25, '2026-10-06T16:05:00+00:00', 'on', 'off'), journ, etiq);
|
||||||
|
eq('jeep : borne debranchee -> pas d identification', appels.length, 1);
|
||||||
eq('jeep : borne debranchee -> releve oui, niveau au branchement non', [base.releves.length, base.niveaux.length], [3, 1]);
|
eq('jeep : borne debranchee -> releve oui, niveau au branchement non', [base.releves.length, base.niveaux.length], [3, 1]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1131,6 +1131,9 @@ const HOUSEHOLD = new Set(['VW', 'JEEP']);
|
|||||||
// un canal inconnu ne doit pas valoir confirmation par defaut. `regle-puissance`
|
// 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.
|
// n'y est pas et n'y sera jamais — c'est une deduction, pas une parole.
|
||||||
const CANAUX_HUMAINS = new Set(['whatsapp', 'proprietaire']);
|
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']);
|
const LABELS = new Set(['VW', 'JEEP', 'MEGANE', 'INVITE', 'ZERO']);
|
||||||
let dernierBlancVu = null; // anti-repetition du journal en essai a blanc
|
let dernierBlancVu = null; // anti-repetition du journal en essai a blanc
|
||||||
|
|
||||||
@@ -1196,7 +1199,7 @@ async function piloterSelecteurVehicule(sql, st, label, source) {
|
|||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
await journaliser(sql, 'vehicule',
|
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,
|
{ origine: 'airtria', source_airtria: source, label, avant: actuel,
|
||||||
entite: 'input_select.victron_evcs_vehicle',
|
entite: 'input_select.victron_evcs_vehicle',
|
||||||
motif: 'la reponse du proprietaire fait foi, et le controleur lit le selecteur' });
|
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
|
// 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
|
// 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.
|
// 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) {
|
async function appliquerEtiquettesEnAttente(sql, client) {
|
||||||
if (DRY) return 0;
|
if (DRY) return 0;
|
||||||
const { rows } = await client.query(
|
const { rows } = await client.query(
|
||||||
@@ -1401,7 +1443,7 @@ async function appliquerEtiquettesEnAttente(sql, client) {
|
|||||||
WHERE branchement > now() - interval '48 hours' ORDER BY branchement`);
|
WHERE branchement > now() - interval '48 hours' ORDER BY branchement`);
|
||||||
let n = 0;
|
let n = 0;
|
||||||
for (const e of rows) {
|
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(
|
const { rowCount } = await sql(
|
||||||
`UPDATE session_charge s SET vehicule = $2, vehicule_sur = $3
|
`UPDATE session_charge s SET vehicule = $2, vehicule_sur = $3
|
||||||
WHERE s.debut >= $1::timestamptz - interval '2 seconds'
|
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;
|
console.error(`releves km en echec : ${e.message}`); return 0;
|
||||||
}) + await ingererNiveauSaisi(sql, st).catch((e) => {
|
}) + await ingererNiveauSaisi(sql, st).catch((e) => {
|
||||||
console.error(`niveau saisi en echec : ${e.message}`); return 0;
|
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;
|
console.error(`jeep uconnect en echec : ${e.message}`); return 0;
|
||||||
}),
|
}),
|
||||||
pvDiagnostic: DRY ? 0 : await publierDiagnosticPv(st).catch((e) => {
|
pvDiagnostic: DRY ? 0 : await publierDiagnosticPv(st).catch((e) => {
|
||||||
|
|||||||
@@ -276,7 +276,10 @@ export const JEEP = {
|
|||||||
branchee: 'binary_sensor.jeep_renegade_phev_ev_charger',
|
branchee: 'binary_sensor.jeep_renegade_phev_ev_charger',
|
||||||
};
|
};
|
||||||
|
|
||||||
export async function ingererJeep(sql, lire, st, journaliser) {
|
// `etiqueter(branche, niveau)` (fourni par le service) : pose l'etiquette JEEP
|
||||||
|
// du branchement, le selecteur et le niveau « saisi » — appelee UNE fois par
|
||||||
|
// branchement, a la premiere mise a jour Uconnect qui le confirme.
|
||||||
|
export async function ingererJeep(sql, lire, st, journaliser, etiqueter = null) {
|
||||||
const km = Number(st.get(JEEP.odometre)?.state);
|
const km = Number(st.get(JEEP.odometre)?.state);
|
||||||
const niv = Number(st.get(JEEP.niveau)?.state);
|
const niv = Number(st.get(JEEP.niveau)?.state);
|
||||||
const t = Date.parse(st.get(JEEP.majA)?.state ?? '');
|
const t = Date.parse(st.get(JEEP.majA)?.state ?? '');
|
||||||
@@ -317,6 +320,7 @@ export async function ingererJeep(sql, lire, st, journaliser) {
|
|||||||
n++;
|
n++;
|
||||||
await journaliser(sql, 'vehicule', `Niveau JEEP au branchement : ${niveau} % (lu par Uconnect)`,
|
await journaliser(sql, 'vehicule', `Niveau JEEP au branchement : ${niveau} % (lu par Uconnect)`,
|
||||||
{ niveau, ts: ts.toISOString(), branchement: branche.toISOString() });
|
{ niveau, ts: ts.toISOString(), branchement: branche.toISOString() });
|
||||||
|
if (etiqueter) n += await etiqueter(branche, niveau);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user