Même borne, même durée, +etm61 : commandée 13 à 20 A au lieu de 9, mesurée 8,4 kW au lieu de 4,4, et 716 Wh livrés en 5 minutes contre 276 en 10. Le motif dit maintenant « 13800 W achetés sur 13800 W possibles » et boundBy vaut loadCeiling — le plafond suit le soutirage de la maison d'un cycle à l'autre, parce que evPhaseAllowanceW() le recalcule sur la bonne grandeur. Trois observations gardées, dont une pour l'étape 4 : la FERMETURE d'une dérogation ouvre elle aussi une divergence (attendu 0 W, mesuré 8367 W), et elle est plus trompeuse que celle d'ouverture. Une divergence qui naît dans le même cycle qu'un DEROGATION_ENDED est une descente en cours, jamais une commande perdue — sans quoi chaque boost compterait pour un incident. La fin par débranchement reste non éprouvée : la voiture est revenue avant les deux confirmations. Le mécanisme a en revanche montré ce qu'on lui demande — une absence isolée n'a PAS terminé la dérogation (LM-1500-c). Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015F7G5VeaPVSMeVNjiGj36p
459 lines
25 KiB
Markdown
459 lines
25 KiB
Markdown
# Dérogations — le boost n'est pas un mode
|
||
|
||
> Ouvert le 2026-09-07. **Design avant code, rien d'implémenté.**
|
||
> Cadrage donné par Patrick, et il vaut au-delà du VE.
|
||
|
||
## 1. La distinction qui structure tout le lot
|
||
|
||
| | mode | **dérogation** |
|
||
|---|---|---|
|
||
| se choisit | **une fois** | pour un **besoin ponctuel** |
|
||
| vaut | jusqu'au prochain choix | **jusqu'à ce que le besoin cesse** |
|
||
| se termine | jamais tout seul | **toujours tout seul** |
|
||
|
||
> **Un boost encore actif après la fin est un mode qu'on a oublié de quitter. Un boost qui rend
|
||
> la main est une dérogation terminée.**
|
||
|
||
C'est le même geste que forcer un chauffe-eau parce qu'on veut plus d'eau chaude : on ne
|
||
reconfigure pas l'installation, on demande quelque chose **maintenant**.
|
||
|
||
**Conséquence de conception** : ce n'est ni un `ChargingMode` de plus, ni une demande dans
|
||
`needs`. Une dérogation se **superpose** au mode courant et s'efface. Elle n'a ni cible d'énergie,
|
||
ni échéance, ni cadence — les trois supposent un besoin qui dure.
|
||
|
||
## 2. Ce qui la borne : la sûreté, et rien d'autre
|
||
|
||
Les trois comportements se distinguent par **ce qui les borne** :
|
||
|
||
| | borné par |
|
||
|---|---|
|
||
| PV | le **surplus** |
|
||
| Min+PV | un **plancher** + le surplus |
|
||
| **Boost** | la **sûreté seule** — `DRAW_CAP` et le délestage |
|
||
|
||
Et c'est cette dernière ligne qui le rend sûr : les plafonds deviennent la seule limite, **donc
|
||
une limite obligatoire**. Un boost sur un branchement 3×30 A sans `phasePowerLimit` ferait
|
||
disjoncter — ce n'est plus une hypothèse depuis le 2026-09-07, où l'on a découvert des semaines
|
||
de fonctionnement à `0`.
|
||
|
||
**Le boost prend la tête de la passe éco ; il ne surplombe pas la sûreté.** Si `DRAW_CAP` mord, il
|
||
est servi le premier **dans ce qui reste**, pas au-delà. Sans cette précision on retomberait sur
|
||
le second chemin d'achat refusé pour le minimum (`DESIGN_MIN_RESEAU.md` §3).
|
||
|
||
### Le rang
|
||
|
||
Le boost passe **devant**, y compris devant une charge mieux rangée. `LM-1002` dit que le rang est
|
||
ce que l'**installateur** décide ; elle ne dit rien d'une **action ponctuelle de l'utilisateur**.
|
||
Prendre la tête ne contredit donc pas la règle : cela passe par une porte qu'elle ne ferme pas.
|
||
Un boost servi après une autre charge contredirait « tout de suite », qui est sa définition.
|
||
|
||
## 3. Précaution 1 — le mode précédent se MÉMORISE, il ne se déduit pas
|
||
|
||
À la fin de la dérogation, la charge revient **au mode qu'elle avait**, relu depuis ce qui a été
|
||
stocké au déclenchement. Min+PV redevient Min+PV, PV redevient PV.
|
||
|
||
**Revenir à un défaut serait un défaut.** Retomber sur `Normal` sortirait la borne de l'arbitrage
|
||
— l'inverse exact du but, et une dérogation qui laisse l'installation moins pilotée qu'avant.
|
||
C'est la même famille que le repli d'échéance qui réécrivait `EcoWithMinCurrent` en `Eco` :
|
||
un état détruit en silence par un mécanisme qui croyait bien faire.
|
||
|
||
## 4. Précaution 2 — « terminé » se définit PAR MÉCANISME
|
||
|
||
Le moteur ne peut pas deviner ce qui met fin à une dérogation : cela dépend de ce qu'on déroge.
|
||
|
||
| charge | fins possibles |
|
||
|---|---|
|
||
| borne VE | **débranchement**, **cible en ÉNERGIE** (`sessionEnergy`), durée |
|
||
| chauffe-eau | **température** atteinte (si sonde), durée |
|
||
| batterie | cible en énergie, durée |
|
||
|
||
### La cible se dit en ÉNERGIE, jamais en SOC — et ce n'est pas un pis-aller
|
||
|
||
**Il n'y a pas de communication véhicule**, et il n'y en aura pas : aucun plugin constructeur
|
||
n'est prévu, et une intégration cloud Skoda/VW serait contraire au **100 % local**, fragile, et
|
||
rafraîchie à la minute au mieux.
|
||
|
||
Mais surtout, l'énergie est la **bonne** grandeur, indépendamment de cette contrainte :
|
||
|
||
| | ce qu'il faut savoir pour l'affirmer |
|
||
|---|---|
|
||
| « 20 kWh puis tu t'arrêtes » | `sessionEnergy`, que le plugin V2C construit depuis le 2026-08-29 |
|
||
| « jusqu'à 80 % » | la **capacité**, l'**état de santé** et le **rendement** de charge |
|
||
|
||
C'est exactement ce que `LM-1009` a établi pour l'obligation éco : **on mesure de l'énergie parce
|
||
que c'est ce qu'on sait mesurer.** Un SoC demanderait trois inconnues pour dire la même chose en
|
||
moins sûr.
|
||
|
||
> **La fin par SOC ne figure donc pas dans ce design**, et son absence n'est pas un manque à
|
||
> combler plus tard : c'est une décision. L'écrire comme un manque aurait laissé quelqu'un
|
||
> l'ouvrir comme un lot.
|
||
|
||
### Et `sessionEnergy` a un silence connu, qui justifie le filet
|
||
|
||
`LM-1012-d`, mesuré sur la V2C le 2026-08-30 : à la **perte du lien**, `currentPower` tombe à 0
|
||
et les booléens à `false`, mais **`sessionEnergy` reste à sa dernière valeur**.
|
||
|
||
Une dérogation « 20 kWh puis stop » dont le compteur se fige à 12 kWh **n'atteindra jamais sa
|
||
cible**. Elle achèterait au maximum jusqu'à ce que quelqu'un s'en aperçoive — sauf que la durée
|
||
est là. Ce n'est pas un cas d'école : c'est le comportement documenté de la borne installée.
|
||
|
||
**Chaque mécanisme déclare ce qui termine sa dérogation.** C'est un ajout à `ILoadAdapter`, au
|
||
même titre que `commandSettleS()` : une propriété du mécanisme, pas une constante du moteur.
|
||
|
||
## 5. LA RÈGLE — une dérogation qui ne sait pas dire quand elle finit ne devrait pas exister
|
||
|
||
C'est la règle centrale du lot, et elle se teste.
|
||
|
||
Sur un chauffe-eau **sans sonde**, « jusqu'à ce que ce soit chaud » n'est **pas mesurable**. La
|
||
fin doit alors être une **durée**, explicitement — et **l'app doit le dire à la saisie**, au
|
||
moment où l'utilisateur croit demander une température.
|
||
|
||
C'est exactement le raisonnement de `progressMeasurable` : une capacité absente se **dit** à la
|
||
saisie, parce que l'écran de résultat parle trop tard. Une dérogation dont la fin n'est pas
|
||
observable est une dérogation qui ne finira pas.
|
||
|
||
### La question qui manquait : que fait-elle ENTRE le figement et l'échéance ?
|
||
|
||
La durée arrêtera la dérogation — **au bout**. Entre-temps, elle achète au maximum sur une mesure
|
||
morte. Sur un lien qui perd 40 % de ses trames, « au bout » peut vouloir dire des heures.
|
||
|
||
**Position retenue : une dérogation dont la cible cesse d'être MESURABLE cesse.** Elle n'attend
|
||
pas sa durée.
|
||
|
||
C'est `R6` transposée. Là-bas : *servie oui, tenue jamais* — on ne conclut pas « tenue » sur un
|
||
avancement qu'on ne mesure pas. Ici : **active oui, vérifiable non, donc on s'arrête.** Une
|
||
dérogation est une commande qui **achète** ; la maintenir sans pouvoir dire où elle en est, c'est
|
||
acheter à l'aveugle.
|
||
|
||
**Et l'instrument existe déjà** : `measurement.source` et le régime de `LM-1009`. Quand le régime
|
||
d'une charge en dérogation cesse d'être mesuré, la condition de fin est perdue — et cela se sait
|
||
au cycle même, pas au bout de la durée.
|
||
|
||
### QUATRE fins, et le motif dit laquelle a mordu
|
||
|
||
| fin | ce qu'elle dit à l'utilisateur | geste |
|
||
|---|---|---|
|
||
| `targetReached` | la cible en énergie est atteinte | aucun — c'est le succès |
|
||
| `unplugged` | le véhicule est parti | aucun |
|
||
| **`measurementLost`** | on ne sait plus où en est la charge | **regarder le lien / le compteur** |
|
||
| `durationElapsed` | le temps imparti est écoulé | relancer si besoin |
|
||
|
||
Les quatre appellent des gestes différents, et une dérogation qui se terminerait sans dire
|
||
laquelle a mordu laisserait l'utilisateur relancer un boost que le lien empêchera encore.
|
||
|
||
### L'asymétrie, et la règle qu'elle donne
|
||
|
||
Le débranchement peut **clignoter** sur cette borne. Le plugin V2C a déjà la parade —
|
||
`k_absencesPourClore` polls consécutifs avant de clore une session — et elle porte une propriété
|
||
qu'il faut reprendre : **seuls les polls RÉUSSIS font avancer le compteur.** Une coupure réseau
|
||
le **gèle**, elle ne le fait pas progresser. Un lien mort ne peut donc pas fabriquer un faux
|
||
débranchement.
|
||
|
||
Cette confirmation vaut pour la dérogation aussi. Mais les deux erreurs ne coûtent pas la même
|
||
chose :
|
||
|
||
> **Terminer à tort** rend la main : la charge repasse à son mode mémorisé, et l'utilisateur
|
||
> relance s'il veut.
|
||
> **Continuer à tort** achète au maximum sur une mesure morte.
|
||
|
||
D'où la règle : **dans le doute, une dérogation s'arrête.** Les seuils de confirmation sont donc
|
||
asymétriques par conception — prompts à conclure la fin, lents à conclure la poursuite. C'est
|
||
l'inverse du réglage qu'on choisirait pour un mode, et c'est parce qu'une dérogation achète.
|
||
|
||
### Corollaire — la DURÉE est un filet, pas une option
|
||
|
||
Toute dérogation porte une durée maximale, **même quand elle a une meilleure condition de fin**.
|
||
|
||
Raison : la condition de fin repose sur un **instrument**, et un instrument peut se taire. Une
|
||
cible de charge qui ne remonte plus, une sonde qui tombe, un compteur qui se fige — et la
|
||
dérogation ne se termine jamais, en tirant le maximum. C'est « l'instrument dont le silence
|
||
ressemble à un résultat », appliqué à une commande qui achète.
|
||
|
||
**Donc : jamais « jusqu'à annulation ».** Quatre fins, et la durée est toujours l'une d'elles.
|
||
|
||
La durée est désormais **le filet du filet** : la perte de mesure attrape le cas où l'instrument
|
||
se tait ET se signale ; la durée attrape le cas où **même le régime ne se signale pas**. Deux
|
||
filets parce que le premier repose lui-même sur un instrument.
|
||
|
||
Cela rejoint ce que la maquette batterie demande pour son propre boost.
|
||
|
||
## 6. Ce que ça publie
|
||
|
||
Une dérogation active **se voit**, et sa fin **se lit** :
|
||
|
||
- un motif dédié, qui dit que la charge est en dérogation et **ce qui y mettra fin** ;
|
||
- l'instant de fin prévu, pour que l'écran affiche un compte à rebours plutôt qu'un état sans
|
||
horizon ;
|
||
- l'instant est **absolu, ancré à la capture** (`toUTC()`), jamais une heure murale — cf. la
|
||
dérive mesurée le 2026-09-06.
|
||
|
||
**Et la fin est un ÉVÉNEMENT**, publié le cycle où elle survient. Sans lui, l'écran ne peut pas
|
||
distinguer « la dérogation s'est terminée normalement » de « quelqu'un l'a annulée ».
|
||
|
||
## 7. Étapes, à comportement constant d'abord
|
||
|
||
**A** — le type et sa publication, sans qu'aucune dérogation ne puisse être posée. Le champ
|
||
existe, il ne décide de rien.
|
||
**B** — la déclaration des fins par mécanisme (`ILoadAdapter`), avec la durée obligatoire.
|
||
**C** — la pose, la mémorisation du mode précédent, et le retour automatique.
|
||
**D** — le service en tête de passe éco, borné par `DRAW_CAP` et le délestage.
|
||
|
||
Le pas **D** est le seul qui change ce qui est commandé, et il arrive quand tout le reste est
|
||
déjà vérifiable.
|
||
|
||
## 8. Ce que le banc ne prouvera pas
|
||
|
||
**Les trois fins du VE sont exerçables sans véhicule communicant** : débranchement, cible en
|
||
énergie via `sessionEnergy`, et durée. C'est ce qui rend ce lot éprouvable sur le banc réel, là
|
||
où une fin par SoC ne l'aurait jamais été.
|
||
|
||
Reste hors de portée : la fin par **température**, qui demande la sonde du §12 — elle n'existe
|
||
qu'en mock, et c'est le sixième manque de `INVENTAIRE_MOCK.md` sous un autre angle.
|
||
|
||
**La durée, elle, s'éprouve partout** — une raison de plus d'en faire le filet plutôt qu'un
|
||
recours.
|
||
|
||
---
|
||
|
||
## 9. Implémenté — `+etm59`, 2026-09-08
|
||
|
||
Le design ci-dessus est posé **d'un bloc** et non par les quatre pas du §7 : la fenêtre de peu de
|
||
production du matin était le bon moment pour éprouver un mécanisme qui **achète**, et découper
|
||
l'implémentation aurait fait passer cette fenêtre.
|
||
|
||
### Où vit quoi
|
||
|
||
| | fichier |
|
||
|---|---|
|
||
| le type, les quatre fins, les seuils | `etm/types/derogation.h` |
|
||
| pose, levée, évaluation des fins, restitution du mode | `etm/energyarbitrator.cpp` |
|
||
| service en **tête de passe éco** | `etm/scheduler/rulebasedscheduler.cpp` |
|
||
| `SetLoadDerogation` / `ClearLoadDerogation`, `derogationEnded` | `nymeaenergyjsonhandler.cpp` |
|
||
|
||
### Trois décisions prises en écrivant, qui ne sont pas dans le design
|
||
|
||
**a. Les fins se lisent dans le CONTEXTE DU CYCLE, pas dans les adaptateurs.**
|
||
`evaluerDerogations()` tourne **entre** `buildContext()` et `getPlan()`, sur les `LoadContext`
|
||
que le scheduler va lire. Une seconde lecture donnerait une seconde vérité, et une dérogation
|
||
pourrait se terminer sur une mesure que le plan ne voit pas. C'est aussi ce qui rend le
|
||
débranchement gratuit : une borne débranchée est déjà filtrée du contexte par
|
||
`evParticipatesInWaterfall()`, son absence **est** le signal.
|
||
|
||
**b. `measurementLost` ne mord QUE s'il y a une cible.**
|
||
Le design disait « une dérogation dont la cible cesse d'être mesurable doit cesser ». Sans cible,
|
||
rien ne dépend de la mesure : le filet de durée suffit, et couper là punirait la charge du
|
||
silence d'un instrument dont elle n'a pas besoin. La garde porte donc sur `targetWh > 0`.
|
||
|
||
C'est aussi pourquoi le critère est `telemetry.sessionMeasurable` et **non** `progress.regime` :
|
||
`progress` est absent sur une charge sans obligation en énergie — c'est-à-dire exactement le cas
|
||
du boost, où le critère aurait été inerte.
|
||
|
||
**c. La base de la cible se capture au PREMIER CYCLE, pas à la pose.**
|
||
Au moment où l'utilisateur appuie, aucun cycle n'a construit de contexte et le cumul de session
|
||
n'est lisible nulle part. Prendre `0` compterait depuis le début de la **session de charge** :
|
||
une dérogation « 5 kWh » posée sur une session déjà à 6 kWh serait atteinte avant d'avoir
|
||
commencé. Mécanisme de `LM-1012-b`, même raison — une base se prend sur ce qu'on mesure.
|
||
|
||
### Le seul endroit où le mode est ÉCRIT
|
||
|
||
Une borne en `Normal` (manuel) est **hors waterfall** : le moteur ne la commande pas, donc une
|
||
dérogation n'y produirait rien du tout. La pose la bascule alors en éco pour la durée, et
|
||
`modeForce` retient qu'elle l'a fait. C'est le seul cas — et c'est lui qui donne sa raison d'être
|
||
à `previousMode` : sans mémorisation, on rendrait une borne manuelle en éco, c'est-à-dire qu'on
|
||
lui prendrait un réglage en silence.
|
||
|
||
### La passe confort est SAUTÉE sous dérogation
|
||
|
||
La fusion des niveaux est une **addition** (`la.powerW += eco.targetW`). La dérogation demande
|
||
déjà le maximum de la charge : y ajouter du surplus dépasserait son plafond matériel. Le surplus
|
||
non dépensé reste au budget et passe aux charges suivantes.
|
||
|
||
**Conséquence comptable à connaître** : la dérogation est comptée **entièrement en achat** alors
|
||
que le surplus présent la finance en partie, physiquement — `gridW` la surestime dès qu'il y a du
|
||
soleil. Ce n'est pas une approximation neuve : le §10 la porte déjà pour le plancher éco, et elle
|
||
sera reprise avec lui. C'est aussi pourquoi la première vérification au banc se fait le matin.
|
||
|
||
### Ce que le test prouve, et par quelle contre-épreuve
|
||
|
||
`Simulation::testDerogationTakesTheHeadButNotTheSafety` — deux charges, la borne au **rang 9**
|
||
(la queue) et l'ECS au **rang 1**, sans aucun surplus :
|
||
|
||
1. **contre-épreuve** — la même borne **sans** dérogation reçoit `0 W`, l'ECS ses 1 000 W de
|
||
cadence. Sans elle, on ne saurait pas d'où viennent les 7 000 W du cas suivant ;
|
||
2. avec dérogation : 7 000 W, `funding = Grid`, motif `DEROGATION_ACTIVE`, `endsAt` publié, et
|
||
**pas** de `targetWh` puisqu'il n'y en a pas ;
|
||
3. **la tête de passe prouvée par la rareté** — 7 500 W d'autorisation pour 8 000 demandés : la
|
||
borne de rang 9 est servie **entièrement**, l'ECS de rang 1 prend les 500 restants. L'inverse
|
||
exact de l'ordre des rangs, donc la preuve que c'est bien la tête de passe qui agit ;
|
||
4. 1 000 W d'autorisation : servie à 1 000, `boundBy: drawCap` — elle ne surplombe pas la sûreté ;
|
||
5. autorisation nulle : **aucun niveau**, pas un niveau à zéro.
|
||
|
||
### Ce qui reste ouvert
|
||
|
||
- La **fin par cible** n'est pas éprouvée au banc : elle demande une session assez longue pour
|
||
franchir un seuil, et la V2C a un `sessionEnergy` qui **se fige** à la perte du lien
|
||
(`LM-1012-d`) — c'est précisément ce que le filet de durée couvre.
|
||
- `ClearLoadDerogation` publie la cause `cancelled`, qui n'est pas une des quatre fins par
|
||
mécanisme : c'est une fin **par décision**, et la distinguer est le but.
|
||
|
||
### Deux effets de `setChargingInfo()` trouvés en préparant l'essai — `+etm60`
|
||
|
||
`SmartChargingManager::setChargingInfo()` **déclenche lui-même un `update()`**. Écrire le mode
|
||
n'est donc pas un geste neutre, et le lot le touche aux deux bouts.
|
||
|
||
**À la pose, l'ordre décide d'une minute.** L'écriture du mode vient **en dernier**, après
|
||
l'insertion dans `m_derogations` : le cycle qu'elle déclenche trouve alors la dérogation déjà
|
||
posée et la sert immédiatement. L'ordre inverse la lui faisait manquer d'un cycle — soixante
|
||
secondes d'attente sur un mécanisme dont la définition est « tout de suite ».
|
||
|
||
**À la fin, la restitution est DIFFÉRÉE d'un tour de boucle.** Les fins sont évaluées *depuis*
|
||
`update()` ; y appeler `setChargingInfo()` imbriquerait un `update()` dans un `update()` — deux
|
||
plans construits et deux dispatchs pour le même instant, sur des charges qu'un seul des deux
|
||
croirait avoir commandées. `update()` n'a aucun verrou de ré-entrance, donc rien ne l'aurait
|
||
signalé. Le décalage ne se voit nulle part : l'événement de fin part dans la trame du cycle
|
||
courant, et le mode est rendu avant le suivant.
|
||
|
||
**Effet heureux du retour à `Normal`** : l'amont restaure alors les valeurs manuelles
|
||
(`manualChargingEnabled`, `manualMaxChargingCurrent`, `manualDesiredPhaseCount`). La borne
|
||
retrouve donc *ses* réglages, et pas seulement son mode.
|
||
|
||
---
|
||
|
||
## 10. Premier essai au banc — 2026-09-08, 11:05:54 → 11:15:54 (`+etm60`)
|
||
|
||
V2C Trydan, voiture branchée, **mode manuel** avant la pose. Durée 600 s, sans cible.
|
||
Authentification désactivée sur `.75` pour l'essai ; appel par WebSocket `4444`.
|
||
|
||
### Les quatre refus mordent sur la vraie machine
|
||
|
||
| appel | réponse |
|
||
|---|---|
|
||
| `durationS: 30` (sous le plancher de 60) | `EnergyErrorInvalidParameter` |
|
||
| `durationS: 25200` (au-delà des 6 h) | `EnergyErrorInvalidParameter` |
|
||
| `loadId` inconnu | `EnergyErrorInvalidParameter` |
|
||
| `ClearLoadDerogation` sans dérogation en cours | `EnergyErrorInvalidParameter` |
|
||
|
||
Journal : `[Dérogation] durée hors bornes : 25200 s (attendu 60 .. 21600)`,
|
||
`[Dérogation] charge inconnue`.
|
||
|
||
### La pose est servie DANS LA MÊME SECONDE
|
||
|
||
```
|
||
11:05:54 [Dérogation] POSÉE {4d1adafd…} — durée 600 s, sans cible,
|
||
mode mémorisé 0 (forcé en éco pour la durée)
|
||
11:05:54 Charging mode changed for "V2C Trydan" ChargingModeEco
|
||
11:05:54 Executing action V2C Trydan to power: ON, Charging current: 9A
|
||
```
|
||
|
||
**C'est la correction `+etm60` qui se voit ici**, et rien d'autre : le cycle déclenché par
|
||
`setChargingInfo()` trouve la dérogation déjà posée et la sert. Dans l'ordre inverse il l'aurait
|
||
manquée, et la borne serait partie en éco ordinaire jusqu'au cycle suivant.
|
||
|
||
### Ce qui la borne, et rien d'autre
|
||
|
||
`boundBy: "drawCap"` à **tous** les cycles, et `maxW: 13800` (20 A × 3 × 230 V) **jamais
|
||
atteint** :
|
||
|
||
```
|
||
draw : { authorisedW: 5984, committedW: 5984, remainingW: 0,
|
||
binding: "connection", bindingPhase: "A", perPhaseBound: true }
|
||
```
|
||
|
||
La dérogation prend la tête, **consomme toute l'autorisation de soutirage — et s'arrête là**.
|
||
`remainingW` tombe à `0`, jamais en négatif. Les charges suivantes ne reçoivent plus que du
|
||
surplus : le chauffe-eau passe `BELOW_MIN_POWER` (241 W restants), la PAC tient sur son recrédit.
|
||
|
||
> **« Pleine puissance » ne veut pas dire 13 800 W.** La phase A du branchement 3×30 A n'en
|
||
> autorisait que ~6 000 avec la maison dessus. La borne est allée au maximum que la **sûreté**
|
||
> permettait, et le motif l'a dit à chaque cycle. C'est `LM-1500-e` observée, pas contournée.
|
||
|
||
### Une étiquette de journal à ne pas mal lire
|
||
|
||
L'amont journalise `Issuer: Surplus` sur une action pourtant financée au **réseau**.
|
||
`ChargingActionIssuerSurplusCharging` nomme le **canal** par lequel l'arbitre commande la borne,
|
||
pas l'origine des watts — `EV_GRID_START` porte déjà la même étiquette depuis 3g-2. Rien à
|
||
corriger, mais un lecteur du journal peut y voir une contradiction avec `funding: grid`.
|
||
|
||
### Ce que le premier essai a trouvé, et que le code seul ne disait pas — `+etm61`
|
||
|
||
**a. La collision de commandeurs (`LM-1500-g`).** À 11:09:51 le bouton « boost » de l'app a écrit
|
||
`ChargingModeNormal` pendant la dérogation. L'amont a alors restitué les valeurs manuelles —
|
||
`chargingEnabled` à **faux** — et armé un verrou de 300 s, pendant que la dérogation continuait
|
||
de commander 9 A. Six minutes de dérogation servie **sur le papier** dans une borne éteinte, que
|
||
seul `[Divergence] DÉBUT — attendu 6210 W, mesuré 0 W` a signalées, sans pouvoir en dire la cause.
|
||
|
||
Corrigé : une écriture de mode **termine** la dérogation (cause `modeChanged`), et **sans**
|
||
restituer le mode mémorisé — il écraserait celui que l'utilisateur vient de choisir.
|
||
|
||
**b. Le double écrêtage des bornes (`LM-1500-h`).** La tête de passe éco appliquait
|
||
`margeRestante` — la marge d'**une** phase — au plafond **triphasé** d'une borne. 6 210 W au lieu
|
||
de 13 800 : **9 A par phase au lieu de 27**, 4 kW au lieu des 11 que la voiture pouvait prendre.
|
||
|
||
La passe confort ne fait pas cette faute, et son commentaire dit pourquoi depuis 3g-2 : *« leur
|
||
plafond de cycle est déjà écrêté par `evPhaseAllowanceW()`, qui connaît les phases qu'elles
|
||
occupent »*. Je l'ai réécrite quinze lignes plus haut sans la relire.
|
||
|
||
> **Une règle notée à un seul endroit du code n'est pas une règle.** Et une erreur qui va dans le
|
||
> sens prudent ne se signale nulle part : ni le disjoncteur, ni le délestage, ni un test qui
|
||
> vérifie qu'on ne dépasse pas. Elle ne se voit qu'en **regardant la voiture**.
|
||
|
||
**c. Le motif n'était pas rendu au journal.** `[Arbitre] … (motif inconnu DEROGATION_ACTIVE …)` :
|
||
le catalogue français de `decisionreason.cpp` ignorait les deux codes neufs. Le repli 7-c a
|
||
fonctionné — le code brut et ses paramètres, jamais une chaîne vide — mais la ligne que
|
||
l'utilisateur lit était illisible.
|
||
|
||
**d. La pose lisait l'horloge murale, le cycle l'horloge injectée.** Sans conséquence en
|
||
production, où les deux sont la même ; en test, la fin par durée n'arrivait jamais. Trouvé par le
|
||
test, pas par la lecture — `poseDerogation()` prend maintenant l'instant en paramètre.
|
||
|
||
---
|
||
|
||
## 11. Second essai — 2026-09-08, 11:52:34 → 11:58:00 (`+etm61`)
|
||
|
||
Même borne, durée 300 s, sans cible. La voiture a été rebranchée pendant la pose.
|
||
|
||
### Le double écrêtage est bien la cause : de 9 A à 20 A
|
||
|
||
```
|
||
11:52:34 [Dérogation] POSÉE — durée 300 s, mode mémorisé 0 (forcé en éco pour la durée)
|
||
11:53:00 Dérogation sur V2C Trydan — 13800 W achetés sur 13800 W possibles
|
||
11:53:00 Executing action V2C Trydan to power: ON, Charging current: 20A
|
||
11:54:00 … 9006 W … 13A
|
||
11:56:00 … 11248 W … 16A
|
||
11:57:00 … 9245 W … 13A
|
||
11:58:00 [Dérogation] FIN — cause durationElapsed, livré 716 Wh, mode rendu 0
|
||
11:58:00 changed to normal mode. Restoring manual values of false 6 A 3 phases
|
||
```
|
||
|
||
| | avant (`+etm60`) | après (`+etm61`) |
|
||
|---|---|---|
|
||
| commandé | **9 A** par phase | **13 à 20 A** |
|
||
| mesuré | 4,4 kW | **8,4 kW** (`[Divergence] FIN — attendu 8970 W, mesuré 8396 W`) |
|
||
| livré | 276 Wh en 10 min | **716 Wh en 5 min** |
|
||
| motif | `boundBy: drawCap` (faux) | `boundBy: loadCeiling` |
|
||
|
||
Le plafond suit le soutirage de la maison d'un cycle à l'autre — 13 800, 9 006, 11 248, 9 245 W —
|
||
parce que `evPhaseAllowanceW()` le recalcule à chaque cycle. **C'est la sûreté qui module, et
|
||
elle module sur la bonne grandeur.** Le motif est enfin rendu en clair : *« Dérogation sur V2C
|
||
Trydan — 13800 W achetés sur 13800 W possibles »*.
|
||
|
||
### Trois observations à garder
|
||
|
||
**a. La divergence d'OUVERTURE est une latence, pas une commande perdue.**
|
||
`11:53:00 [Divergence] DÉBUT — attendu 13800 W, mesuré 3930 W` puis `11:57:00 FIN après 300 s —
|
||
attendu 8970 W, mesuré 8396 W`. La voiture négocie sa montée (~30 s mesurés sur la V2C, et
|
||
davantage depuis l'arrêt). C'est le même profil que l'épisode Min+PV du 2026-09-05, et il
|
||
confirme le critère de tri de `DESIGN_COMMANDE_PERDUE.md` §6.
|
||
|
||
**b. La FERMETURE en produit une aussi**, et elle est plus trompeuse :
|
||
`11:58:00 [Divergence] DÉBUT — attendu 0 W, mesuré 8367 W`. La dérogation vient de rendre la
|
||
main, la borne n'a pas encore décru. **Une divergence qui ouvre dans le même cycle qu'un
|
||
`DEROGATION_ENDED` est une descente en cours**, jamais une commande perdue — à écrire dans le
|
||
critère de tri de l'étape 4, sous peine de compter chaque boost comme un incident.
|
||
|
||
**c. La fin par DÉBRANCHEMENT n'est toujours pas éprouvée.** L'essai avait été lancé sur une
|
||
borne débranchée pour l'exercer ; la voiture est revenue avant les deux confirmations. On a en
|
||
revanche vu le mécanisme faire ce qu'on lui demande : une absence isolée (09:55) **n'a pas**
|
||
terminé la dérogation, parce que le compteur se remet à zéro dès que la charge reparaît
|
||
(`LM-1500-c`). Un clignotement ne conclut rien.
|