18 KiB
18 KiB
HANDOFF.md — EVCS / Grid Hysteresis — État au 09/07/2026
1. Contexte Projet
- Installation : Victron ESS sans injection réseau. Batterie 17,5 kWh utile.
- Workflows n8n actifs :
HA Snapshot EVCS Surplus Controller V2.6(idATswvGfG4KJUls0K) — pilote la borne EVCS (30 s)Victron - Battery / Grid Hysteresis Fix V1(id7iIlu7LLTW7TZA8o) — pilote le switch EDF (1 min)
- Accès :
- HA :
http://10.10.10.17:8123— jeton dansjeton_EVCS.txt(⚠️ en clair, à révoquer si dépôt partagé) - n8n :
https://n8n.airtria.pro— clé API dans~/.config/opencode/opencode.json(mcp.n8n-mcp) - MCP opencode (option 1 déployée) : 5 serveurs distants dans
~/.config/opencode/opencode.json(ha-get-state,ha-set-helper,grid-set-state,victron-set-charge-limits,pv-get-forecast) → endpoints SSEhttps://n8n.airtria.pro/mcp/<path>/sse. Les 4 outils paramétrés utilisent$fromAI(...)(corrigé le 09/07). ⚠️ Endpoints MCP n8n sans authentification — à sécuriser (Bearer sur les nodes MCP Server Trigger).
- HA :
- Fichiers de référence locaux :
WINDOWS/WINDOW_SOURCEV2.js,BATTERY/BATTERY_CLOUD_LOCKS.js,HYSTERESIS/COMPUTE.js— copies de lecture (le code qui fait foi est dans n8n)modified/GRID_HYSTERESIS_backup_20260709-*.json— rollback avant P1modified/EVCS_V2.6_backup_20260709-*.json— rollbacks avant P2 puis P3 (2 fichiers)
2. Architecture Data-Flow
EVCS V2.6 (30s) :
HA Snapshot (template) → INPUT_PARSE → WINDOWS_SOURCEV2
→ BATTERY_CLOUD_LOCKS → DECISION_ENGINEV2 → HA_SERVICE_CALLS_OUTPUT
→ Build Actions → Execute HA Service
Grid Hysteresis (1min) :
HA Snapshot → Compute Grid Hysteresis → Build Actions → Execute HA Service
Communication inter-workflows via helpers HA :
input_text.victron_evcs_source_mode (labels FR), input_boolean.victron_evcs_night_grid_takeover_needed,
input_text.victron_evcs_surplus_state, input_select.victron_evcs_strategy.
3. Patchs déployés le 09/07/2026 (session opencode)
P1 — Grid Hysteresis (déployé, vérifié)
- Entité fantôme corrigée : le snapshot lisait
input_select.victron_evcs_user_strategy(inexistante → toujoursunknown). Corrigé eninput_select.victron_evcs_strategy. Conséquence avant fix : les overridesforce_grid_now/force_charge_nowet la coupure « voiture pleine » ne fonctionnaient JAMAIS dans l'hystérésis. - Switch EDF
unavailable: nouvel étatswitch_unavailable= aucun ordre, aucun timestamp, réévaluation complète au retour du switch. Avant : spamturn_onchaque minute + rallumage aveugle à la reconnexion (incident du 08-09/07 : disjoncteur physique coupé par l'utilisateur, EDF rallumé à 00:41 au retour, batterie chargée sur réseau à SOC 48 %). last_on_at: réécrit uniquement sur vraie transition (latch OFF→ON), plus à chaque retry. Avant :hold_on_min_runtimepouvait durer éternellement.- Bug latent corrigé : le bloc
turn_off_force_grid_vehicle_full(V2.5) était écrasé par la chaîne principale (code mort). FlagforcedCutajouté.
P2 — EVCS V2.6 : continuité takeover nuit / anti-créneaux (déployé, vérifié en réel)
- Le snapshot relit
night_takeover_prev(= helper takeover du cycle précédent). nightTakeoverLatcheddansWINDOWS_SOURCEV2etBATTERY_CLOUD_LOCKS: une fois le réseau HC engagé en session nuit, on y reste jusqu'à fin de session (fenêtre/débranchement) ou SOC ≥ 90 (NIGHT_TAKEOVER_RELEASE_SOC_PCT).- Supprime le cycle « château fort » : batterie→jeep→seuil 20 %→EDF ON→30 %→EDF OFF→batterie...
- Vérifié en production dans la nuit du 09/07 : takeover engagé à SOC 35 %, EDF resté ON,
Jeep en charge continue (
hold_min_run).
P3 — EVCS V2.6 : provocation PV batteries pleines (déployé, dry-run complet)
- Problème (ancien sujet de ce HANDOFF) : sans injection, batteries pleines ⇒ MPPT bridé à la
conso maison ⇒ aucun seuil PV observable ; en plus
forecastBufferKwh(10,5 kWh véhicule soustraits) toujours ≤ 0 l'après-midi, etcloudDebten deadlock (batterie pleine = courant nul = dette jamais remboursée). Sonde afternoon inopérante (probe_enabled = off+ seuil PV 1000 W). - Fix :
pvProvocationEligible = SOC ≥ 90 && batterie non déchargée→solar_potential(intégré àisHighBatteryForPotential, donc actif sur les 4 profils jour). Gardes aval intactes. - Attention réglage :
soc_start_min(HA) = 94 ⇒ plancher de démarrage hors fenêtre. Le déclenchement pratique est donc SOC ≥ 94 %. Baisser ce helper à 90 pour aligner.
P4 — Maintien SOC adapté au forecast pendant le takeover nuit (déployé 09/07 après-midi)
- Découverte clé :
Victron - Auto Charge Manager V1.2(idcTeeHy4ePiVeELLh) est le propriétaire des limites DVCC (MaxChargeCurrent/MaxChargeVoltagevia MQTT, feedback :sensor.victron_max_charge_current_setting). Son mode « nuit idle » laissait 30 A de charge batterie dès que EDF était ON → batterie rechargée à fond pendant un takeover. - ACM patché : nouvelle branche prioritaire
evcs_takeover_hold/evcs_takeover_recover: sinight_grid_takeover_neededON + EDF ON → SOC < cible-1 : recharge 30 A ; sinon maintien 2 A / 50.1 V (maison + VE sur HC, batterie en pause). Cible lue depuisinput_number.victron_evcs_night_target_soc(écrite par le workflow EVCS : forecast fort→35 %, moyen→55 %, faible→85 % — donc été ~35, hiver ~85 comme demandé). - EVCS patché : avant minuit (20h→8h), la cible matin utilise
max(today_remaining, tomorrow_kwh)(sinon today_remaining≈0 à 23h → cible 85 % à tort en été). Ajouttomorrow_kwhau snapshot +parsed.tomorrowKwh. - Dry-runs : 8 scénarios ACM + 2 scénarios cible EVCS validés (voir session 09/07).
Autres réalisations du 09/07 (après-midi)
- Borne fantôme résolue : firmware EVCS 2.05 → 2.08 (session repartie de zéro, détection véhicule correcte). Les 19 kWh affichés étaient un cumul de session non réinitialisé.
- Dashboard « EDF Urgence » créé via API HA (sidebar) : scripts
script.edf_urgence_off(hystérésis OFF + latch OFF + stratégie stop + switch OFF — rien ne rallume),script.edf_force_on,script.edf_rendre_la_main+ cartes d'état et « pourquoi ». - Incident Synology : NAS éteint dans la nuit (cause à chercher dans les logs DSM) → n8n + HA morts → EDF resté ON toute la nuit, batterie chargée à 96 % sur réseau. ⚠️ SPOF structurel : tout le contrôle vit sur le Synology.
P5 — Plafond courant EVCS configurable quand EDF est ON (déployé 09/07 soir)
- Le plafond 20 A venait de
cfg.evcsDedicatedMaxW(4900−300 W / 230 V) codé en dur dans INPUT_PARSE. - Nouveau helper
input_number.evcs_plafond_courant_edf_on(6–32 A, défaut 20) : quand AC-IN est physiquement présent (acin1_available, pas juste le switch), le plafond = helper ; hors réseau, retour au plafond câblé ~20 A. Dry-run 5 scénarios OK (dont disjoncteur coupé). - Subtilité :
victron_evcs_current_max_auto_a(22) plafonne EN PLUS les modes auto ; en force_grid/force_charge seul le nouveau helper compte. - Boutons dashboards (EVCS Master = fichier YAML
/mnt/ARCH3/docker/HA/dashboards/evcs_master.yamlédité via NFS ; EDF Urgence = API) : carte « Plafonds courant EVCS » + boutons ⚡ BOOST EDF 28A (script.evcs_boost_edf: plafonds 28/28 + force_grid_now + consigne 28) et Retour normal (script.evcs_boost_off: 20/22 + auto_solar_plus). - Sémantique clarifiée : « Courant demandé » = « Consigne envoyée » =
number.victron_evcs_set_current_command(réécrit toutes les 30 s par le workflow) ; « Consigne lue » = readback borne. En force_charge_now, PV ≥ 700 W ⇒ source « Solaire » ⇒ gardes nuages/batterie freinent : pour une grosse charge, force_grid_now.
P6 — Override « abondance » du plancher SOC de démarrage (déployé 12/07 midi)
- Symptôme : 12/07 midi, PV 8,9 kW, batterie en charge +125 A, Jeep branchée → EVCS bloqué
idle_wait_conditions. Cause unique (dry-run) : plancher SOC fenêtre midi (input_number.victron_evcs_midday_soc_min= 75) > SOC 66. - Fix (
BATTERY_CLOUD_LOCKS) : sipvPowerAvg10m ≥ 4000 WetbatteryCurrentA ≥ +20 A(abondance physique : la batterie monte même avec l'EVCS), plancher de démarrage abaissé à 40 % (effectiveSocStartFloor). Jours nuageux : planchers d'origine intacts. Debug exporté :solarAbundance,effectiveSocStartFloor(safeguards). - Vérifié en réel : déploiement 12:43 →
start_min_currentimmédiat → charge 12 A. - Observation borne : après des heures branchée en statut 6, la Jeep a mis ~2,5 min à
s'engager ; le workflow renvoie
turn_ontoutes les 30 s (retry intégré suffisant). - Planchers de référence : matin 35, midi 75, soir ?, hors fenêtre 94 (
soc_start_min).
Maintenance du 09/07 soir (analyse des messages d'erreurs)
- W-105 borne (surcharge) : survenu en charge 20 A sur onduleur seul (disjoncteur EDF coupé). Déjà couvert : gardes vebus_overload + P5 (plafond retombe à 20 A si AC-IN absent).
saveDataErrorExecution: allactivé sur les 8 workflows critiques (diagnostic des prochaines erreurs avec données des nodes ; Water Heater V2.3 avait erré 4× le 09/07 20:20-20:35, cause inconnue faute de données, rétabli seul).- Garage Thermal : la prise
switch.clim_hygro_socket_1est débranchée volontairement par moments → l'appel au switch est maintenant conditionné à sa disponibilité (execute: switchAvailable). Plus de warnings « missing entity » toutes les 10 min. Backupmodified/GARAGE_backup_*. - Templates MQTT durcis (33 occurrences,
configuration.yaml+packages/victron_vebus_mqtt.yaml) : gardevalue_json is defined→ plus d'erreursvalue_json is undefinedaux reconnexions MQTT. BackupsSAVE_CONFIG/*.bak.20260709-234800,check_configvalid,mqtt.reloadfait. ⚠️ Après unmqtt.reload, les sensors restentunknownjusqu'à republication Venus : publierR/c0619ab34ac5/keepalive(payload vide) force la republication complète.
4. Root Cause de l'incident « EDF ON à 48 % sans charge Jeep » (nuit 08→09/07)
Chaîne complète reconstituée via l'historique HA :
- Borne fantôme au matin du 08/07 (« véhicule en charge » sans véhicule) +
force_grid_nowde la veille ⇒ EDF maintenu ON (la coupure voiture-pleine était du code mort, cf. P1.1/P1.4). - L'utilisateur coupe le disjoncteur physique ⇒ switch
unavailablependant ~5 h. - Le workflow reste coincé en
hold_on_min_runtime(P1.3) en spammantturn_on(P1.2). - 00:40:54 : le switch revient ⇒ 00:41:03 EDF ON sans cause actuelle ⇒ Multiplus charge la batterie sur réseau (2980 W) ⇒ coupé à 01:06 (min_on 25 min, SOC ≥ 30). Tous les maillons sont corrigés par P1.
5. Sémantique utile (pour ne pas re-chercher)
- Latch (
input_boolean.victron_grid_hysteresis_latch) : « c'est l'automate qui a allumé EDF et il défend cet état ». Reprise en main manuelle : stratégiestop+ hystérésis OFF + latch OFF. - Fenêtres EVCS (helpers HA, corrigées/vérifiées le 09/07) :
night_hc01:24→07:24 (HC réelles),morning_solar07:30→11:00,midday_hc_or_solar12:24→14:24 (HC réelles),evening_high_soc17:00→20:30, sinonoutside_window. L'Auto Charge Manager utilisait des fenêtres codées en dur légèrement fausses (01:30-07:15 / 12:25-14:25) — alignées sur 01:24-07:24 / 12:24-14:24 le 09/07. - Seuils hystérésis réseau (HA, valeurs réelles) : ON ≤ 20 %, OFF ≥ 30 %, min_on 25 min.
- Cibles nuit : hard floor 22 % ; cible matin selon forecast : ≥18 kWh→35 %, ≥10→55 %, sinon 85 %.
nightSessionActive = fenêtre nuit && véhicule branché(⚠️ pas besoin de charge active). - Statut borne 6 = branchée en attente de démarrage (« Inconnu (6) » sur le dashboard).
- Labels FR dans
source_mode:Réseau forcé,Réseau HC autorisé,Assistance batterie nuit,Bloqué,Solaire,Potentiel solaire— l'hystérésis matche ces labels.
6. Méthode de travail validée (à réutiliser)
- Backup :
GET /api/v1/workflows/<id>→modified/*_backup_<stamp>.json. - Patch des
jsCodepar remplacement exact (script node),node --checksur chaque node. - Dry-run local de la chaîne complète : snapshot réel via
POST /api/templateHA, exécution séquentielle des 5 nodes avec shim$input, horloge simulable en surchargeantglobal.Date(voir session du 09/07). PUT /api/v1/workflows/<id>(⚠️ filtrersettings: seulsexecutionOrder/timezone/save*/executionTimeout/errorWorkflowsont acceptés).- Vérification live : helpers HA 40-70 s après.
7. Backlog / Point du 10/07 au réveil
- Vérifier la nuit de test P2+P4 (fenêtre HC 01:24→07:24, forecast fort → cible 35 %) :
- takeover engagé quand SOC ≤ 35 → EDF ON continu (pas de créneaux) ;
input_text.victron_charge_mode=evcs_takeover_recoverpuisevcs_takeover_hold;sensor.victron_max_charge_current_setting= 2 pendant le hold (batterie tenue ~35 %) ;- 07:24 : fin de fenêtre → EDF coupé (
turn_off_recovered), ACM repasse en profil jour. Historique utile :switch.edf_general_ok_switch,sensor.victron_battery_soc,input_text.victron_charge_modeentre 01:00 et 08:00.
- Cause de l'extinction du Synology (DSM → Centre des journaux) + réflexion SPOF : le fail-safe physique actuel est « EDF ON + charge batterie » quand le NAS meurt.
- Baisser
input_number.victron_evcs_soc_start_min94 → 90 (aligner avec P3) si souhaité. - Sécuriser : auth Bearer sur les 5 MCP Server Triggers n8n + révoquer/déplacer le jeton HA.
- À investiguer : « conditions de démarrage nuit batterie non réunies » quand la batterie
suffit pour la nuit (conditions de départ
battery_assist_night). - Optionnel : brancher un keepalive/republish Venus automatique après
mqtt.reload(cf. note maintenance §3).
8. Historique des sessions
- 12/06/2026 : analyse blocage outside_window, déploiement afternoon_probe (V2.6),
fix clignotement EDF (
edf_general_ok_onconditionné au sourceMode). - 09/07/2026 (nuit) : incident EDF nocturne analysé (root cause complète), MCP option 1 en place, P1 (hystérésis robuste + entité stratégie), P2 (anti-créneaux nuit), P3 (provocation PV).
- 09/07/2026 (journée/soir) : incident Synology (NAS éteint → EDF ON toute la nuit, batterie 96 %), firmware borne 2.08 (fantôme résolu), dashboard EDF Urgence + boost 28 A, P4 (maintien SOC au forecast pendant takeover, via Auto Charge Manager), P5 (plafond EDF ON configurable), fenêtres HC corrigées (01:24-07:24 / 12:24-14:24), maintenance erreurs (save error executions, prise garage conditionnelle, 33 templates MQTT durcis).
- 12/07/2026 : P6 (override abondance du plancher SOC midi — PV fort + batterie en charge ⇒ démarrage dès SOC 40) ; validation en réel à 12:43, charge Jeep engagée.
⚙️ CONFIG LOCALE — Qwen3.6-35B sur DGX Spark (≠ gros modèle cloud)
⚠️ Cette config est CALIBRÉE pour le modèle LOCAL Qwen3.6-35B-A3B (3B actifs, contexte limité). NE PAS appliquer telle quelle à un gros modèle cloud (FABLE/Claude), qui n'a besoin ni du bornage strict ni du découpage par nœud.
Infra (validée le 16/07/2026)
- DGX Spark = 10.10.10.8, endpoint vLLM unique sur port 8000.
- UN SEUL modèle à la fois (contrainte NVRAM 1 Spark) → swap via scripts :
use-code: docker compose down (VL) + sparkrun run qwen3.6-35b sur :8000use-vl: sparkrun stop --all + docker compose up (Qwen3-VL, vision)
- Recette : @official/qwen3.6-35b-a3b-fp8-mtp-vllm --tp 1 --port 8000 --gpu-mem 0.8 --served-model-name qwen3.6-35b (MTP + flashinfer + tool-parser qwen3_coder déjà inclus dans la recette).
- Qwen3-VL (docker /opt/vllm) réservé à l'analyse de captures d'écran.
opencode.json (poste Ubuntu, PAS la Spark)
- baseURL: http://10.10.10.8:8000/v1, apiKey "EMPTY".
- Modèle "qwen3.6-35b" : options { temperature: 0.15, reasoningEffort: "none" }, cost tout à 0 (sinon compteur fantôme FABLE), limit context 65536.
- permission.bash : "curl *": "deny", "wget *": "deny" (bloque hallucinations HTTP).
- Les 6 MCP (n8n-mcp, ha-get-state, ha-set-helper, grid-set-state, victron-set-charge-limits, pv-get-forecast) : NE JAMAIS toucher ce bloc.
RÈGLES DE TRAVAIL spécifiques au local (dans AGENTS.md)
- Contexte trop petit pour un workflow entier (~357K = ~102K tokens > 65K). => JAMAIS charger le workflow complet. UN nœud / UNE tâche à la fois.
- n8n-mcp_get_workflow renvoie le workflow entier => opencode l'écrit dans ~/.local/share/opencode/tool-output/tool_* (tronqué). Le fichier commence par "Retrieved workflow: " AVANT le JSON => json.load ÉCHOUE. Extraction validée : grep -n '"name":"NOEUD"' puis sed -n 'X,Yp'. (ripgrep/rg N'EST PAS installé sur la Spark, utiliser grep.)
- reasoningEffort none coupe le thinking (le tag /no_think en tête de chat NE marche pas : opencode l'intercepte comme slash-command).
- Skill n8n installée dans .opencode/skills/ — surveiller les compactions.
PÉRIMÈTRE (le plus important)
- LOCAL Qwen3.6 = tâches CIBLÉES : lire 1 nœud, repérer une faille logique, patch simple, dry-run. Rapide, gratuit, fiable SI bien borné.
- CLOUD (FABLE/gros modèle) = refontes multi-nœuds, analyse d'un workflow ENTIER, orchestration MCP longue. Ne pas forcer le local dessus (il boucle).
Document mis à jour le : 17/07/2026 (~00:20) — ajout du skill n8n-opencode-skill session opencode avec accès HA (REST+WS+NFS) + n8n (API+MCP) État live à la clôture : Jeep en charge solaire (abondance), batterie +114 A, P6 validé en production