Patrick Schurig 0f64609611 docs(relevé): 3g-1 sur machine — ce que le banc a dit, y compris ce qui fait mal
La transplantation ne commande rien tant que adjustEvChargers() n'est pas traité : l'arbitre
refuse, publie un motif juste, et le proxy commande ON à 6 A une ligne plus loin. La même
ligne part vers la Terra AC réelle et n'a rien fait UNIQUEMENT parce qu'elle est hors ligne.

Le plancher EV vaut la moitié du seuil de démarrage réel — acquisitionTolerance, qui est un
seuil de déclenchement, a été pris pour un plancher. 2828 W alloués, 4,1 A demandés, 6 A
exigés : allocation perdue.

La bascule 0,4 / 0,9 est faite dans les deux sens et la correction withheldW: 0 est vérifiée
sur machine. La moitié qui manque — la réserve retenant un surplus RÉEL — demande du soleil.

LM-1204-b n'est pas franchi : il exige deux bornes arbitrées, la Terra AC ne répond pas.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XPUo3RMr8SzK6qbFtfBm8H
2026-08-26 18:39:10 +02:00

194 lines
10 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# Relevé machine — 3g-1 sur le banc `.75`, 2026-08-26
**Paquets** : `1.15.2+etm20` → `+etm21` → `+etm22` (le dernier corrige ce que le passage
machine a révélé). **Batterie** : Fronius Storage, SoC 50 %, `Holding`, `capacity` 10 kWh.
**Bornes** : Terra AC réelle (192.168.1.132:502) + wallbox simulée.
> **Méthode.** Ce qui suit distingue ce qui a été **observé sur machine**, ce qui vient d'un
> **test unitaire**, et ce qui n'est qu'une **lecture de code**. Les trois ne valent pas pareil,
> et mélanger les trois est exactement ce qui a envoyé un agent dans le vide le 2026-08-25.
---
## 1. La bascule `batteryLevelConsideration` — FAITE, dans les deux sens
Première exécution possible depuis que `nymea-plugin-sunspec-etm01 1.15.2+etm11` déclare
`energystorage` : la batterie est enfin **vue** (`Having 1 batteries. Overall battery level is
50 %`, journal 17:16:00).
| | seuil | surplus | motif publié |
|---|---|---|---|
| **etm20**, 17:12:00 | 0,9 | **négatif** | `BATTERY_RESERVE`, *« **0 W** de surplus retenus »* |
| **etm21**, 17:17:00 et 17:18:00 | 0,9 | **négatif** | `SURPLUS_INSUFFICIENT` |
| **etm21**, 17:19:00 | 0,4 | négatif | `SURPLUS_INSUFFICIENT` |
**La correction `withheldW: 0` est vérifiée sur machine.** L'A/B est propre et les deux côtés
sont au journal : dans des conditions identiques — même batterie, même seuil, même surplus
négatif — etm20 disait « une règle vous bloque » et etm22 dit « il n'y a rien ». C'était la
confusion que ce motif existe pour supprimer, retournée contre elle-même.
⚠️ **La moitié qui manque, et il faut la dire** : la réserve n'a **jamais été observée en train
de retenir un surplus réel** (`withheldW > 0`). Il y aurait fallu un surplus positif **et** un
SoC sous le seuil, et l'observation s'est faite en soirée. La courbe du jour montre que c'est
atteignable : **surplus maximum 8 733 W à 12:46Z**. À refaire vers midi.
**Défaut de rendu corrigé au passage** : le journal affichait *« batterie à 50 %% »*. `%%` est
une échappée `printf` ; `QString::arg` ne l'interprète pas.
---
## 2. LM-1204-b — NON franchi, et pour deux raisons distinctes
### 2.1 Ce qui a été démontré
Le discriminant **fonctionne** et il a été observé sur une borne réelle du contexte
(cycle 17:34:00, `+etm22`) :
```
{09e520fd…} 0 W funding=surplus BELOW_MIN_POWER {budgetW: 1096, minPowerW: 2070}
```
journal : *« Budget 1096 W sous le premier palier de Simulated wallbox (2070 W) — 0 W, le
budget passe à la charge suivante »*. **C'est l'arbitre qui a refusé, et il le dit.**
La cascade se réconcilie : `chauffe-eau 3500 + pac-terrain 3000 = 6500 = budget.allocatedW`.
La borne indisponible sort en `LOAD_UNAVAILABLE` + `faultCode: THING_MISSING`.
### 2.2 Pourquoi le critère n'est pas franchi
**Il faut DEUX bornes arbitrées. Il n'y en a qu'une.** La Terra AC est injoignable —
`ThingErrorTimeout` à chaque tentative de setup, `connected: false` — donc elle n'est pas une
concurrente dans la cascade, elle est un défaut. Le scénario « 4 kW, deux planchers de 1,4 kW,
une seule démarre » n'a pas d'objet tant que la seconde borne ne répond pas.
**Et le test de rang est impossible tel qu'il est écrit** — voir §3.2 : le rang d'une borne
n'est pas configurable.
---
## 3. Ce que le passage machine a révélé, et qui n'était visible d'aucune autre façon
### 3.1 CORRIGÉ (`+etm22`) — les bornes étaient arbitrées sans franchir la frontière RPC
`buildTelemetry()` n'interrogeait que la table des charges de configuration et sautait l'EV
avec un commentaire devenu faux. Le journal montrait les décisions des deux bornes ;
`GetLoadTelemetry` n'en montrait **aucune**. Le changement de contrat annoncé par `+etm21`
n'était donc pas livré — et LM-1204-b était **inéprouvable**, puisque son discriminant *est* le
motif publié.
Nouveau champ **`loads[].funding`** (`"surplus"` / `"grid"`), rendu nécessaire par l'entrée des
bornes : une borne servie par la cascade est comptée dans `budget.allocatedW`, une borne servie
par l'échéance ou le tarif dynamique soutire au réseau et vit dans `budget.evReservedW`.
### 3.2 NON CORRIGÉ — le rang d'une borne n'est pas configurable
`EvAdapter::descriptor()` fixe `priority = 100` **en dur, pour toutes les bornes**. Deux bornes
ont donc la même clé de tri, et `std::sort` n'est pas stable.
La cascade reste correcte — une seule démarre, l'autre reçoit son motif — mais **laquelle** n'est
ni réglable ni garanti reproductible d'un cycle à l'autre. À ne pas présenter comme un réglage
dans l'app avant qu'un lot dédié le traite.
### 3.3 NON CORRIGÉ, ET C'EST LE PLUS GRAVE — la double commande
Journal 17:34:00, deux lignes consécutives :
```
[Arbitre] "{09e520fd…}" → "Budget 1096 W sous le premier palier … — 0 W"
---------------- Adjusting chargers ----------------
Executing action Simulated wallbox to power: ON, Charging current: 6A, Issuer: Surplus
```
**`adjustEvChargers()` re-décide le surplus après le waterfall et passe outre le refus de
l'arbitre.** 3g-1 a retiré la *planification* du surplus EV (la branche `else` de
`planSurplusCharging` est devenue `continue`) mais a laissé tourner le chemin d'*exécution* du
proxy, qui dérive sa propre consigne. La même ligne part vers la **Terra AC réelle** — elle n'a
rien fait cette fois **uniquement parce que la borne est hors ligne**.
> **Conséquence à énoncer sans détour : tant que ceci n'est pas traité, la transplantation ne
> commande rien.** L'arbitre décide, publie un motif juste, et le proxy commande autre chose une
> milliseconde plus tard. Tout ce qui précède dans ce relevé décrit une décision qui n'atteint
> pas le matériel.
**Ce n'est pas corrigé ici volontairement** : la correction porte sur le chemin d'exécution
d'une borne réelle, et le choix — `adjustEvChargers()` ne doit plus agir que sur les bornes
ayant reçu une action financée au **réseau** — est un arbitrage de lot (3g-2), pas une
retouche. L'improviser un soir sur un banc qui porte une Terra AC serait exactement le genre de
décision que le workflow interdit.
### 3.4 NON CORRIGÉ — le plancher EV est sous le seuil de démarrage réel
`evFloorW()` = `max(borne, voiture) × acquisitionTolerance × 230 × phases`. Avec 6 A, une
tolérance de 0,5 et 3 phases : **2 070 W**. Or démarrer exige réellement `6 A × 230 × 3`, soit
**4 140 W**.
Observé au cycle 17:33:00 : le waterfall alloue **2 828 W** à la wallbox (au-dessus du
« plancher » de 2 070), puis l'adaptateur convertit — `2828 / (230 × 3) = 4,1 A`, sous les 6 A
minimum — et refuse d'enclencher : `activé: false | courant: 0 A`. **2 828 W ont été retirés du
budget et donnés à personne.**
`acquisitionTolerance` était, chez le proxy, un seuil de **déclenchement** — la part de réseau
qu'on accepte de soutirer pour démarrer. Ce n'est pas la même grandeur qu'un **plancher** au
sens du §12, qui doit être la puissance que la charge exige réellement. Les deux ont été
confondus en écrivant `evFloorW()`.
---
## 4. Autres constats, hors 3g
- **`Energy.GetPowerBalanceLogs` à `SampleRate15Mins` rend 50 échantillons TOUS À ZÉRO**, alors
que `SampleRate1Min` (681 échantillons) et `SampleRate1Hour` (12) sont peuplés sur la même
fenêtre. C'est le cœur nymea, pas notre plugin — mais c'est la table que l'écran d'historique
de l'app voudra. **À vérifier avant de promettre les 90 jours.**
- **`SetChargingInfo` substitue `targetPercentage = 80` à un champ absent.** Constaté en
restaurant le banc : envoyer l'objet sans ce champ l'a mis à 80 alors qu'il valait 0. Un
champ omis y acquiert donc une valeur — l'app doit **toujours** l'envoyer explicitement.
- **Les Things Fronius portent des octets nuls de bourrage dans leur nom** (`"Fronius"` suivi de
25 `\0` avant `" Storage"`). Cosmétique, mais cela traversera jusqu'à l'écran.
- **`currentPowerStorage` vaut 0** au bilan de puissance. Cohérent avec une batterie
`Holding` / `idle`, donc **non concluant** : on ne peut pas encore distinguer « au repos » de
« non câblé ». À reprendre quand la batterie charge ou décharge vraiment.
---
## 5. État où le banc a été rendu
| | valeur | pourquoi |
|---|---|---|
| Paquet | **`1.15.2+etm22`** | contient la correction du §3.1 |
| `batteryLevelConsideration` | **0,4 — PAS remis à 0,9** | voir ci-dessous |
| `acquisitionTolerance` | 0,5 | inchangé |
| `phasePowerLimit` | 0 | inchangé — désactive la protection de surcharge |
| `LoadConfig` | **identique à l'octet près** au relevé d'avant | vérifié par comparaison |
| `ChargingInfos` | **identiques à l'origine** | les deux bornes en `ChargingModeNormal`, sans voiture |
| Voitures simulées de test | **retirées** | `LM1204-CAR-A/B-A-SUPPRIMER`, ajoutées puis supprimées |
**Le seuil n'est délibérément PAS remis à 0,9.** À 0,9 avec une batterie à 50 %, la règle gèle
toute l'installation — c'est exactement ce que le journal de 17:12:00 montre, et c'est la raison
pour laquelle le défaut d'usine est passé à 0,2 en `+etm20`. Y revenir rendrait le banc
inutilisable pour la reprise de LM-1204-b, sans rien démontrer de plus. **0,9 n'était pas un
réglage voulu du banc : c'était l'ancien défaut d'usine, hérité.**
### Pour reprendre l'essai (les deux voitures sont à recréer)
```bash
dart tools/rpc/probe.dart 192.168.1.75 Integrations.AddThing \
'{"thingClassId":"4afcafb1-d341-4532-8062-738cae529cb5","name":"LM1204-CAR-A-A-SUPPRIMER"}'
dart tools/rpc/probe.dart 192.168.1.75 NymeaEnergy.SetChargingInfo \
'{"chargingInfo":{"evChargerId":"<borne>","chargingMode":"ChargingModeEco","assignedCarId":"<voiture>"}}'
```
---
## 6. Ce qui manque au banc — la question posée, la réponse
**La Terra AC ne répond pas** (`192.168.1.132:502`, `ThingErrorTimeout` à chaque cycle). C'est
le seul obstacle matériel, et il bloque à lui seul LM-1204-b, qui exige deux bornes arbitrées.
**Et une wallbox simulée ne remplace pas la seconde** : elle ne prouve ni le délai
d'établissement, ni la négociation de courant, ni ce qu'une borne fait d'une consigne qu'elle
n'accepte pas. Elle a un défaut de plus, visible ce soir : elle annonce `desiredPhaseCount: 3`
en permanence, ce qui a suffi à faire viser 3 phases et à porter le plancher publié à 2 070 W
alors qu'aucun véhicule n'est branché. Sur ce point précis, **le simulateur a fabriqué la
condition du défaut §3.4** — utile pour le trouver, sans valeur pour le valider.