Le piège des accolades n'était pas un détail de chargingModeOf(). C'est la deuxième
occurrence en une semaine d'un motif qui se répétera : deux appels JSON-RPC de la même box,
portant le même objet, n'emploient pas la même convention, et RIEN ne le dit. Écrit comme
tel dans docs/UI_data_contract.md §9, avec renvoi depuis les deux sites de code et depuis
tools/rpc/README.md — là où quelqu'un croise deux réponses brutes.
Les occurrences, leur symptôme :
· GetPowerBalance (currentPowerProduction…) × GetPowerBalanceLogs (production…) →
tout tombe à zéro, l'historique paraît vide sur 7 960 échantillons bien présents
· GetChargingInfos (evChargerId sans accolades) × GetLoadConfig (id avec) →
« mode non lu » sur une borne parfaitement lue, et la note « hors arbitrage » muette
· famille voisine, même silence : currentPower* en W × total* en kWh, facteur 1000
Ce qui en fait un motif et pas trois anecdotes : le symptôme n'est JAMAIS une exception,
c'est une valeur neutre plausible, et un test unitaire qui construit ses deux côtés avec la
même convention passe aussi. D'où les trois gestes écrits au §9 — sonder les deux appels
côte à côte sur les charges utiles brutes, éprouver le rapprochement sur les deux formes
réelles, et se méfier d'un vide qui ne coûte rien à expliquer.
Et le lien avec une règle qu'on suivait déjà sans la relier à ça : une valeur neutre issue
d'un croisement raté est indiscernable d'une vraie absence. « Non lu » laisse une chance de
trouver le bug ; « 0 » l'enterre.
── Campagne V2C arrêtée, et la réserve sessionEnergy avec elle ────────────────
La Trydan non-Pro est en WiFi ESP32 sans repli filaire, lien inexploitable à deux mètres du
point d'accès : 70 % de scrutations avortées, 12 bascules en injoignable en vingt minutes.
Le modèle sort du catalogue ETM ; seule la Trydan Pro (port Ethernet) y reste.
La trace qui lève C1 — chargeEnergy à zéro AVEC un sessionEnergy qui continue — n'a donc
pas été prise et ne viendra pas de cette borne. Les notes restent en place telles quelles,
avec la raison mise à jour : elles seront levées sur une Trydan Pro. Aucun avancement de
charge affiché d'ici là.
164 tests, flutter analyze inchangé à 27 remarques.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SQbZKrWqsMFP1Lh2jjjd9f
106 lines
5.3 KiB
Dart
106 lines
5.3 KiB
Dart
/// Ce qu'une borne de recharge **publie réellement**, et rien d'autre.
|
|
///
|
|
/// ## Pourquoi ce type existe
|
|
///
|
|
/// L'app portait trois grandeurs de recharge qui n'étaient jamais lues sur la box :
|
|
/// `EnergyData.chargingMode` (défaut `pv`), `chargingPower` (défaut 3,6 kW) et
|
|
/// `solarSourcePercent` (défaut 82 %). Elles étaient affichées comme des mesures — « 3600
|
|
/// W », « 82 % solaire » — sur un écran de favoris que le client peut ouvrir. C'est la même
|
|
/// famille de défaut que le SOC à 62 % en dur et les quatre pièces de climatisation
|
|
/// fictives : une valeur par défaut présentée comme une lecture.
|
|
///
|
|
/// Ce type ne porte donc **que des champs nullables**, et `null` veut dire *la box ne le
|
|
/// publie pas* — jamais zéro. L'écran doit le dire, pas le combler.
|
|
///
|
|
/// ## Ce qui est lisible aujourd'hui, mesuré sur `.75` le 2026-08-28
|
|
///
|
|
/// Les deux bornes du banc (Simulated wallbox, V2C Trydan) publient `pluggedIn`,
|
|
/// `charging`, `currentPower` et `maxChargingCurrent`. Le mode, lui, ne vient pas du Thing
|
|
/// mais de `NymeaEnergy.GetChargingInfos` — les deux bornes y étaient en
|
|
/// `ChargingModeNormal`, `ChargingStateIdle`.
|
|
///
|
|
/// **`sessionEnergy` existe** sur les deux (4,229 kWh sur la V2C), et le renommage
|
|
/// `currentL1/L2/L3` **est déployé** — plus de `powerL1/L2/L3`. Ces deux champs ne sont pas
|
|
/// portés ici pour autant. Le plugin V2C `1.15.2+etm13` **construit** ce `sessionEnergy` —
|
|
/// il accumule les incréments de `chargeEnergy`, détecte les remises à zéro, et ne repart
|
|
/// qu'à l'ouverture d'une session — mais la trace qui le montre à l'œuvre (`chargeEnergy`
|
|
/// à zéro, `sessionEnergy` qui continue) **n'a pas été prise** : la campagne V2C est
|
|
/// arrêtée, la Trydan non-Pro étant inexploitable en WiFi. Elle se prendra sur une Trydan
|
|
/// Pro. **Aucun avancement n'est affiché avant elle** : LM-1009 §C1, cf. `LoadMechanism`.
|
|
library;
|
|
|
|
import 'energy_data.dart' show ChargingMode;
|
|
|
|
/// Pleine puissance — et **mode d'office** de toute borne détectée par le moteur.
|
|
const String kChargingModeNormal = 'ChargingModeNormal';
|
|
|
|
/// Ce que l'écran doit dire d'une borne en [EvChargerLive.isUnmanaged], mot pour mot aux
|
|
/// deux endroits qui l'affichent : la tuile des favoris, où les modes se changent, et la
|
|
/// carte de configuration, que l'installateur ouvre à la mise en service.
|
|
const String kEvUnmanagedNote =
|
|
'Pleine puissance, hors arbitrage : le HEMS ne pilote pas cette borne. '
|
|
'C\'est aussi le mode d\'office d\'une borne qui vient d\'être détectée — '
|
|
'choisir PV ou Min+PV la fait entrer dans l\'arbitrage du surplus.';
|
|
|
|
class EvChargerLive {
|
|
/// `thingId` de la borne — l'identifiant que `SetChargingInfo` attend.
|
|
final String thingId;
|
|
final String name;
|
|
|
|
/// États du Thing. `null` = non publié par cette borne.
|
|
final bool? pluggedIn;
|
|
final bool? charging;
|
|
final double? currentPowerW;
|
|
|
|
/// Mode **tel que la box le porte** (`ChargingModeEco`, `ChargingModeNormal`, …), lu par
|
|
/// `GetChargingInfos`. `null` = pas encore lu, ou aucune entrée pour cette borne — et
|
|
/// dans ce cas l'écran n'a **aucun** mode à présenter comme courant.
|
|
final String? apiMode;
|
|
|
|
const EvChargerLive({
|
|
required this.thingId,
|
|
required this.name,
|
|
this.pluggedIn,
|
|
this.charging,
|
|
this.currentPowerW,
|
|
this.apiMode,
|
|
});
|
|
|
|
/// Traduction du mode de la box vers les trois boutons de l'IU.
|
|
///
|
|
/// Les cinq valeurs de l'énumération se replient sur trois : les variantes « avec
|
|
/// échéance » gardent le bouton de base, l'échéance étant une option à côté. `null` pour
|
|
/// une valeur inconnue — une borne peut porter un mode qu'aucune version d'app ne
|
|
/// connaît, et deviner serait pire que dire « mode non reconnu ».
|
|
ChargingMode? get uiMode => switch (apiMode) {
|
|
'ChargingModeNormal' => ChargingMode.boost,
|
|
'ChargingModeEco' || 'ChargingModeEcoWithTargetTime' => ChargingMode.pv,
|
|
'ChargingModeEcoWithMinCurrent' ||
|
|
'ChargingModeEcoMinWithTargetTime' =>
|
|
ChargingMode.minPv,
|
|
_ => null,
|
|
};
|
|
|
|
/// **La borne charge à pleine puissance, hors arbitrage.**
|
|
///
|
|
/// `ChargingModeNormal` n'est pas seulement le « boost » que quelqu'un peut choisir :
|
|
/// c'est le **mode d'office d'une borne détectée**. Une borne neuve n'est donc jamais
|
|
/// pilotée par le HEMS tant que personne n'a posé un mode éco — et rien, ni dans la
|
|
/// télémétrie ni sur l'écran, ne le disait. L'installateur pose une borne, elle charge à
|
|
/// fond, et il cherche pourquoi le HEMS l'ignore.
|
|
///
|
|
/// ⚠️ **L'app ne peut PAS distinguer « personne n'a choisi » de « quelqu'un a choisi
|
|
/// Boost »** : la box publie le même `chargingMode` dans les deux cas. La note dit donc
|
|
/// l'état — pleine puissance, hors arbitrage — et signale qu'il est aussi le défaut ;
|
|
/// elle n'accuse personne de ne pas avoir réglé. Même règle que la note de rangs ex
|
|
/// æquo : reproductible n'est pas choisi, et on ne fait pas dire à un écran ce que la
|
|
/// donnée ne porte pas.
|
|
bool get isUnmanaged => apiMode == kChargingModeNormal;
|
|
|
|
/// Vrai quand la borne porte une échéance dans son mode — l'information est dans le nom
|
|
/// de l'énumération, pas dans un champ à part.
|
|
bool get hasDeadlineMode =>
|
|
apiMode == 'ChargingModeEcoWithTargetTime' ||
|
|
apiMode == 'ChargingModeEcoMinWithTargetTime';
|
|
}
|