Lien Modbus rétabli, Skoda en charge, valeurs en mouvement. - `sessionEnergy` : notre doc était EN RETARD, pas contredite. La V2C la publie depuis le plugin +etm13 et elle bouge (15,8015 → 15,8405 kWh). L'écart avec `chargeEnergy` vaut 7,2044 kWh aux deux échantillons pris à deux heures d'écart — constant au dix-millième : c'est l'accumulateur du plugin, et il prouve l'accumulation que le code seul ne montrait pas. LM-1009 C1 reste vraie pour les bornes sans cet état ; la V2C n'en fait plus partie. La maquette ne la nomme donc plus dans l'exemple « non mesurable ». - `chargingState` reste `Idle` sur un lien VIVANT, pendant que la borne tire 4 372 W : ce n'est pas de la péremption, personne ne maintient le champ. Et un constat qui n'est pas dû au WiFi : la box a journalisé « Restoring manual values of false 6 A » trois secondes après la coupure ; la borne est restée à `power: true`, 7 A, et le lien est revenu sans que rien ne réapplique. En mode manuel personne ne commande, donc l'écart est permanent — 4,4 kW tirés, `draw.committedW` à 0. Question posée au moteur. Aucun avancement n'est affiché pour autant : c'est une décision (D1), pas une mesure qui manquerait. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01GKM8FDNHZWogAsjjfB8pSw
166 lines
8.4 KiB
Dart
166 lines
8.4 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, 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. **Relevé sur charge réelle le 2026-08-30** (Skoda branchée, lien rétabli),
|
|
/// deux échantillons à deux heures d'écart :
|
|
///
|
|
/// | | 12:15 | 14:12 |
|
|
/// |---|---|---|
|
|
/// | `sessionEnergy` | 7,5147 kWh | 15,8015 → 15,8405 kWh, en mouvement |
|
|
/// | `chargeEnergy` | 0,3103 kWh | 8,5972 → 8,6361 kWh |
|
|
/// | écart | **7,2044** | **7,2044** |
|
|
///
|
|
/// L'écart est **constant au dix-millième sur deux heures** : c'est l'accumulateur du
|
|
/// plugin, et il porte les incréments d'avant la dernière remise à zéro. La remise à zéro
|
|
/// elle-même n'a pas été surprise en direct, mais l'accumulation, elle, est prouvée par cet
|
|
/// écart — elle ne se déduisait pas du code.
|
|
///
|
|
/// **Aucun avancement n'est affiché pour autant** : c'est une décision (LM-1009 §D1 —
|
|
/// l'avancement se dit en énergie livrée, et il n'y a pas encore d'écran pour le porter),
|
|
/// plus une mesure manquante. LM-1009 §C1 reste vraie pour les bornes qui ne publient pas
|
|
/// `sessionEnergy` ; **la V2C n'en fait plus partie**.
|
|
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, **ou borne injoignable**.
|
|
///
|
|
/// ## Pourquoi l'injoignable produit `null` ici, et pas la dernière valeur connue
|
|
///
|
|
/// Mesuré sur `.75` le 2026-08-30, V2C Trydan : quand le lien Modbus tombe, la box ne se
|
|
/// contente pas de figer les états — elle **publie des zéros**.
|
|
///
|
|
/// | État | Avant la coupure | Après |
|
|
/// |---|---|---|
|
|
/// | `connected` | true | **false** |
|
|
/// | `pluggedIn` | true | **false** |
|
|
/// | `charging` | true | **false** |
|
|
/// | `currentPower` | 4 396 W | **0** |
|
|
/// | `statusMessage` | "" | « Hors ligne depuis 30/08 12:39 — timeout Modbus … » |
|
|
///
|
|
/// Une voiture branchée et en charge devient donc, à l'écran, « aucun véhicule branché,
|
|
/// 0 W » — **un fait forgé, pas une absence**. Et avant ces zéros, la dernière valeur
|
|
/// connue reste affichée un moment comme si elle était actuelle (vérifié : aucun état ne
|
|
/// bouge pendant 45 s alors que la borne est censée tirer 4 356 W).
|
|
///
|
|
/// L'app refuse donc de relayer les deux : sous [horsLigne], ces trois champs sont mis à
|
|
/// `null` **à la construction**, pour qu'aucun écran ne puisse afficher ni le zéro forgé
|
|
/// ni la valeur périmée. Seuls [joignable] et [messageEtat] disent quelque chose de vrai.
|
|
final bool? pluggedIn;
|
|
final bool? charging;
|
|
final double? currentPowerW;
|
|
|
|
/// L'état `connected` du Thing — **le seul champ qui reste vrai quand le lien tombe**.
|
|
///
|
|
/// `null` = la borne ne publie pas cet état ; on ne conclut alors rien.
|
|
final bool? joignable;
|
|
|
|
/// `statusMessage` du Thing : une phrase déjà écrite par le plugin, en français, avec
|
|
/// l'heure de la coupure et la piste à suivre. On l'affiche telle quelle — la
|
|
/// reformuler perdrait l'horodatage et le diagnostic.
|
|
final String? messageEtat;
|
|
|
|
/// Le mode reste lisible **même hors ligne**, et c'est voulu : il est stocké dans la
|
|
/// box (`GetChargingInfos`), pas lu sur la borne. Un lien coupé n'invalide donc pas le
|
|
/// mode, seulement les états du matériel — et les trois boutons continuent d'écrire,
|
|
/// puisqu'ils écrivent eux aussi dans la box.
|
|
///
|
|
/// 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,
|
|
this.joignable,
|
|
this.messageEtat,
|
|
});
|
|
|
|
/// **La box ne joint plus la borne.** Rien de ce qu'elle publierait d'autre n'est un fait.
|
|
///
|
|
/// `joignable == null` n'est pas hors ligne : une borne qui ne publie pas `connected` ne
|
|
/// dit rien de son lien, et supposer la panne serait le symétrique du zéro forgé.
|
|
bool get horsLigne => joignable == false;
|
|
|
|
/// 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';
|
|
}
|