feat(ev): une borne à pleine puissance n'est pas arbitrée — les deux écrans le nomment
Geste de mise en service, signalé par le moteur : toute borne détectée reçoit d'office ChargingModeNormal, c'est-à-dire pleine puissance HORS arbitrage. Une borne neuve n'est donc jamais pilotée tant que personne n'a posé un mode éco, et rien ne le disait — l'installateur pose une borne, elle charge à fond, et il cherche du côté du HEMS un défaut qui n'existe pas. La note apparaît aux deux endroits qui comptent, mot pour mot (kEvUnmanagedNote) : · la carte de configuration, celle qu'on ouvre à la mise en service ; · la tuile des favoris, où les trois boutons de mode sont juste à côté. ⚠️ Elle nomme l'ÉTAT, pas une négligence. La box publie le même chargingMode pour un boost délibéré et pour une borne jamais réglée : l'app ne peut pas les distinguer, donc elle dit « pleine puissance, hors arbitrage » et rappelle que c'est aussi le mode d'office. 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. Un mode NON LU ne déclenche rien non plus : on ne conclut pas d'une absence. Le piège qui aurait rendu la note invisible est couvert par un test : GetLoadConfig rend l'id de charge AVEC accolades ({4d1adafd-…}), GetChargingInfos rend l'evChargerId SANS. Comparer les deux tels quels ne rend jamais rien — en silence : l'écran aurait conclu « mode non lu » sur une borne parfaitement lue. chargingModeOf() réconcilie les deux. Six tests montés (trois par écran), dont les deux contrôles négatifs. Et la réserve sessionEnergy est documentée à sa juste place : le champ est CONSTRUIT par le plugin V2C 1.15.2+etm13 — il accumule les incréments de chargeEnergy, détecte les remises à zéro, ne repart qu'à l'ouverture d'une session — donc l'égalité observée hier s'explique d'elle-même. La réserve tient jusqu'à la trace que la campagne produira (chargeEnergy à zéro, sessionEnergy qui continue) : aucun avancement affiché d'ici là. Noté aussi, pour la suite : sessionEnergy n'est PAS historisé (suggestLogging écrasé par things.conf sur un Thing déjà appairé ; la V2C n'y a que connected, charging, power). La valeur courante est juste, il n'y aura pas de courbe — le choix des états enregistrés est côté app, entrée au TODO. 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
This commit is contained in:
parent
e45ca67151
commit
e0264b5ed6
19
TODO.md
19
TODO.md
@ -35,8 +35,23 @@
|
||||
prêtes, les motifs n'ont **jamais été vus sur machine**. Signaler la première occurrence
|
||||
réelle. Ne rien bâtir autour d'ici là.
|
||||
|
||||
- [ ] ⏳ **`sessionEnergy` existe maintenant — mais rien ne prouve encore qu'il survive à
|
||||
une interruption.** Relevé du 2026-08-28 après-midi sur `.75` (`Integrations.GetThings`) :
|
||||
- [ ] ⏳ **`sessionEnergy` est CONSTRUIT par le plugin — il ne reste que la trace.**
|
||||
Le plugin V2C `1.15.2+etm13` (déployé le 2026-08-28 à midi) accumule les incréments de
|
||||
`chargeEnergy`, détecte les remises à zéro, et ne repart qu'à l'ouverture d'une session :
|
||||
ce n'est **pas** un alias du registre `0x0BC4`. La campagne de validation en cours produit
|
||||
le cas qui lève la réserve — `chargeEnergy` à zéro, `sessionEnergy` qui continue. **Aucun
|
||||
avancement affiché avant cette trace** (LM-1009 §C1).
|
||||
|
||||
- [ ] **Historique des états d'une borne — `sessionEnergy` n'est pas enregistré.**
|
||||
`suggestLogging` est écrasé par les préférences persistées de `things.conf` sur un Thing
|
||||
déjà appairé, et la V2C n'y a que `connected`, `charging`, `power`. La valeur courante est
|
||||
juste — « la nuit relue à 7 h » fonctionne — mais **il n'y aura pas de courbe**. Le
|
||||
réglage est côté app : choisir les états enregistrés d'un Thing. À décider si un
|
||||
historique de recharge est voulu ; sans lui, l'écran ne peut montrer qu'un instantané.
|
||||
|
||||
- [x] ~~⏳ `sessionEnergy` existe — mais rien ne prouve qu'il survive à une interruption~~ —
|
||||
**répondu le 2026-08-28** par l'origine du champ (entrée ci-dessus). Le relevé qui a posé
|
||||
la question est conservé : `Integrations.GetThings` sur `.75`, cet après-midi —
|
||||
la V2C publie `sessionEnergy` **et** `chargeEnergy`, tous deux à **4,229 kWh** — égaux,
|
||||
ce qui est exactement ce qu'on attend d'une session sans interruption. Le test qui
|
||||
tranche est une **remise à zéro observée de `chargeEnergy` avec un `sessionEnergy` qui
|
||||
|
||||
@ -21,14 +21,26 @@
|
||||
///
|
||||
/// **`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 : voir la note de `LoadMechanism`
|
||||
/// (`models/load_telemetry.dart`), l'égalité `sessionEnergy == chargeEnergy` relevée ce
|
||||
/// jour ne prouve pas encore que le premier survit à une interruption de charge, et c'est
|
||||
/// exactement ce que LM-1009 §C1 demande de savoir avant d'afficher un avancement.
|
||||
/// 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) sort de la campagne en cours. **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;
|
||||
@ -68,6 +80,22 @@ class EvChargerLive {
|
||||
_ => 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 =>
|
||||
|
||||
@ -192,9 +192,24 @@ class LoadMechanism {
|
||||
/// la carte Modbus était en retard sur la machine ;
|
||||
/// - `sessionEnergy` **existe aussi** (4,229 kWh) — mais il valait **exactement**
|
||||
/// `chargeEnergy` au moment du relevé. L'égalité ne prouve rien : c'est justement ce
|
||||
/// qu'on attend d'une session sans interruption. **Tant qu'une remise à zéro de
|
||||
/// `chargeEnergy` n'a pas été observée avec un `sessionEnergy` qui, lui, continue,
|
||||
/// la condition C1 de LM-1009 reste entière** : ne pas afficher d'avancement.
|
||||
/// qu'on attend d'une session sans interruption.
|
||||
///
|
||||
/// **Ce que le plugin en dit** (V2C `1.15.2+etm13`, déployé le 28 à midi) : ce
|
||||
/// `sessionEnergy` est **construit**, pas un alias du registre `0x0BC4`. Il accumule les
|
||||
/// incréments de `chargeEnergy`, détecte les remises à zéro, et ne repart qu'à
|
||||
/// l'ouverture d'une session. L'égalité observée s'explique donc d'elle-même.
|
||||
///
|
||||
/// **La réserve tient quand même, jusqu'à la trace.** La campagne de validation en cours
|
||||
/// produit exactement le cas qui la lève — `chargeEnergy` à zéro, `sessionEnergy` qui
|
||||
/// continue. Tant qu'elle n'est pas au dossier, **aucun avancement de charge n'est
|
||||
/// affiché** : la condition C1 de LM-1009 reste entière. Ce n'est pas de la défiance
|
||||
/// envers le plugin, c'est la règle qui a évité d'afficher `chargeEnergy` lui-même.
|
||||
///
|
||||
/// > ⚠️ **`sessionEnergy` ne sera pas historisé.** `suggestLogging` est écrasé par les
|
||||
/// > préférences persistées de `things.conf` sur un Thing déjà appairé, et la V2C n'y a
|
||||
/// > que `connected`, `charging`, `power`. La **valeur courante** est juste — « la nuit
|
||||
/// > relue à 7 h » fonctionne — mais il n'y aura **pas de courbe** tant que les états
|
||||
/// > enregistrés n'auront pas été choisis côté app. Cf. `TODO.md`.
|
||||
|
||||
// ── evcharger (3g) — l'ÉTAT RÉEL de la borne ────────────────────────────────
|
||||
//
|
||||
|
||||
@ -1368,6 +1368,34 @@ class NymeaService extends ChangeNotifier {
|
||||
}
|
||||
}
|
||||
|
||||
/// Mode de recharge que la box porte pour cette borne, ou `null` si rien n'a été lu.
|
||||
///
|
||||
/// Accepte indifféremment un `thingId` et un identifiant de charge `GetLoadConfig` : le
|
||||
/// premier arrive **sans** accolades (`4d1adafd-…`), le second **avec** (`{4d1adafd-…}`),
|
||||
/// et c'est la même borne. Comparer les deux tels quels ne rend jamais rien — en
|
||||
/// silence, ce qui est le pire des cas : l'écran conclurait « mode non lu » sur une borne
|
||||
/// parfaitement lue.
|
||||
String? chargingModeOf(String id) {
|
||||
final nu = id.replaceAll('{', '').replaceAll('}', '').toLowerCase();
|
||||
for (final e in _chargingInfos.entries) {
|
||||
final k = e.key.replaceAll('{', '').replaceAll('}', '').toLowerCase();
|
||||
if (k == nu) return e.value['chargingMode'] as String?;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/// Porte de test — même usage que `LoadConfigProvider.adoptForTest` : éprouver
|
||||
/// l'affichage sur une charge utile réelle sans box.
|
||||
@visibleForTesting
|
||||
void adoptChargingInfosForTest(List<Map<String, dynamic>> infos) {
|
||||
_chargingInfos.clear();
|
||||
for (final m in infos) {
|
||||
final id = m['evChargerId'] as String?;
|
||||
if (id != null) _chargingInfos[id] = m;
|
||||
}
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
/// Valeur d'un état du Thing **par son nom** (`pluggedIn`, `currentPower`, …).
|
||||
///
|
||||
/// Les états voyagent par `stateTypeId` (un UUID) ; le nom vit dans la `ThingClass`. Sans
|
||||
|
||||
@ -3,10 +3,12 @@ import 'package:provider/provider.dart';
|
||||
|
||||
import '../l10n/app_localizations.dart';
|
||||
import '../l10n/l10n_ext.dart';
|
||||
import '../models/ev_charger_live.dart';
|
||||
import '../models/load_config_entry.dart';
|
||||
import '../providers/load_config_provider.dart';
|
||||
import '../providers/load_telemetry_provider.dart';
|
||||
import '../services/load_hardware.dart';
|
||||
import '../services/nymea_service.dart';
|
||||
import '../services/load_priority.dart';
|
||||
import '../theme/etm_tokens.dart';
|
||||
import 'load_cards.dart';
|
||||
@ -260,6 +262,52 @@ class _NoteRangsExAequo extends StatelessWidget {
|
||||
}
|
||||
}
|
||||
|
||||
/// **Une borne en pleine puissance ne passe pas par l'arbitrage — et c'est son défaut.**
|
||||
///
|
||||
/// Le moteur crée d'office une entrée de configuration par borne détectée, en
|
||||
/// `ChargingModeNormal` : pleine puissance, hors arbitrage. Tant que personne n'a posé un
|
||||
/// mode éco, le HEMS ne pilote donc pas cette borne — et rien ne le disait. C'est le
|
||||
/// pendant exact de la note de rangs ex æquo : un état par défaut qui a toutes les
|
||||
/// apparences d'un réglage.
|
||||
///
|
||||
/// ⚠️ Elle ne dit pas « personne n'a choisi » : la box publie le même `chargingMode` pour
|
||||
/// un boost délibéré et pour une borne jamais réglée. Elle nomme l'ÉTAT, et rappelle qu'il
|
||||
/// est aussi le défaut.
|
||||
class _NoteBornePleinePuissance extends StatelessWidget {
|
||||
final LoadConfigEntry entry;
|
||||
|
||||
const _NoteBornePleinePuissance({required this.entry});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
// `GetChargingInfos` porte l'`evChargerId` sans accolades, `GetLoadConfig` l'id de
|
||||
// charge avec : `chargingModeOf` réconcilie les deux.
|
||||
final mode = context.watch<NymeaService>().chargingModeOf(entry.id);
|
||||
if (mode != kChargingModeNormal) return const SizedBox.shrink();
|
||||
|
||||
return Padding(
|
||||
padding: const EdgeInsets.only(bottom: 8),
|
||||
child: Container(
|
||||
width: double.infinity,
|
||||
padding: const EdgeInsets.all(9),
|
||||
decoration: BoxDecoration(
|
||||
color: EtmTokens.solar.withValues(alpha: 0.10),
|
||||
borderRadius: BorderRadius.circular(EtmTokens.radiusCtrl),
|
||||
),
|
||||
child: Row(crossAxisAlignment: CrossAxisAlignment.start, children: [
|
||||
Icon(Icons.flash_on_rounded, size: 15, color: EtmTokens.solar),
|
||||
const SizedBox(width: 7),
|
||||
Expanded(
|
||||
child: Text(kEvUnmanagedNote,
|
||||
style: TextStyle(
|
||||
fontSize: 11.5, height: 1.35, color: EtmTokens.inkOf(context))),
|
||||
),
|
||||
]),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// Un domaine et ses charges. Les domaines se déplacent entre eux, les charges à
|
||||
/// l'intérieur — les deux niveaux que l'ordre de service demande.
|
||||
class _DomainBlock extends StatelessWidget {
|
||||
@ -560,6 +608,14 @@ class LoadConfigCard extends StatelessWidget {
|
||||
const SizedBox(height: 8),
|
||||
Divider(height: 1, color: EtmTokens.lineOf(context)),
|
||||
const SizedBox(height: 8),
|
||||
// ── Une borne à pleine puissance n'est pas arbitrée ────────────────
|
||||
//
|
||||
// Elle se lit ici et nulle part ailleurs : la télémétrie n'en dit rien (une
|
||||
// borne en `ChargingModeNormal` sans véhicule branché est simplement absente de
|
||||
// `loads[]`), et l'écran de configuration est celui que l'installateur ouvre à
|
||||
// la mise en service. Sans cette ligne, il pose une borne, elle charge à fond,
|
||||
// et il cherche du côté du HEMS un défaut qui n'existe pas.
|
||||
if (entry.isEvCharger) _NoteBornePleinePuissance(entry: entry),
|
||||
if (live != null)
|
||||
LoadLiveBlock(
|
||||
live: live,
|
||||
|
||||
@ -117,6 +117,22 @@ class _EvFavoriteTileState extends State<EvFavoriteTile> {
|
||||
Text(_mode(live),
|
||||
style: EtmTokens.sans(
|
||||
size: 11, color: EtmTokens.mutedOf(context))),
|
||||
// Pleine puissance = hors arbitrage, et c'est le mode d'office d'une borne
|
||||
// détectée. Les trois boutons sont juste à côté : c'est l'endroit où le
|
||||
// dire coûte le moins à celui qui veut le corriger.
|
||||
if (live.isUnmanaged) ...[
|
||||
const SizedBox(height: 3),
|
||||
Row(crossAxisAlignment: CrossAxisAlignment.start, children: [
|
||||
Icon(Icons.info_outline_rounded,
|
||||
size: 13, color: EtmTokens.amber),
|
||||
const SizedBox(width: 4),
|
||||
Expanded(
|
||||
child: Text(kEvUnmanagedNote,
|
||||
style: EtmTokens.sans(
|
||||
size: 10.5, color: EtmTokens.mutedOf(context))),
|
||||
),
|
||||
]),
|
||||
],
|
||||
],
|
||||
),
|
||||
),
|
||||
|
||||
@ -106,6 +106,37 @@ void main() {
|
||||
});
|
||||
});
|
||||
|
||||
group('pleine puissance = hors arbitrage, et l\'écran le dit', () {
|
||||
testWidgets('ChargingModeNormal porte la note — c\'est le mode d\'office',
|
||||
(tester) async {
|
||||
// Une borne détectée arrive en ChargingModeNormal : pleine puissance, hors
|
||||
// arbitrage. Sans cette ligne, l'installateur pose une borne, elle charge à fond,
|
||||
// et il cherche pourquoi le HEMS l'ignore.
|
||||
await tester.pumpWidget(_harness(live: _v2c(apiMode: 'ChargingModeNormal')));
|
||||
|
||||
expect(find.textContaining('hors arbitrage'), findsOneWidget);
|
||||
expect(find.textContaining('mode d\'office'), findsOneWidget);
|
||||
// Elle n'accuse personne : la box publie le même code pour un boost délibéré.
|
||||
final note = tester
|
||||
.widget<Text>(find.textContaining('hors arbitrage'))
|
||||
.data!;
|
||||
expect(note, isNot(contains('personne')));
|
||||
expect(note, isNot(contains('oubli')));
|
||||
});
|
||||
|
||||
testWidgets('un mode éco la fait disparaître', (tester) async {
|
||||
await tester.pumpWidget(_harness(live: _v2c(apiMode: 'ChargingModeEco')));
|
||||
expect(find.textContaining('hors arbitrage'), findsNothing);
|
||||
});
|
||||
|
||||
testWidgets('mode non lu : aucune note — on ne conclut pas d\'une absence',
|
||||
(tester) async {
|
||||
await tester.pumpWidget(_harness(live: _v2c(apiMode: null)));
|
||||
expect(find.textContaining('hors arbitrage'), findsNothing);
|
||||
expect(find.text('Mode non lu'), findsOneWidget);
|
||||
});
|
||||
});
|
||||
|
||||
group('l\'écriture du mode restitue le refus', () {
|
||||
testWidgets('un refus de la box est affiché, avec son code', (tester) async {
|
||||
await tester.pumpWidget(_harness(
|
||||
|
||||
@ -41,6 +41,7 @@ List<Map<String, dynamic>> _benchConfig() {
|
||||
Widget _harness({
|
||||
required LoadConfigProvider config,
|
||||
required LoadTelemetryProvider telemetry,
|
||||
NymeaService? service,
|
||||
}) =>
|
||||
MultiProvider(
|
||||
providers: [
|
||||
@ -49,7 +50,7 @@ Widget _harness({
|
||||
// La section « Recharge véhicule » lit les Things de la box pour savoir s'il y a
|
||||
// une borne. Hors connexion, la liste est vide et la section s'efface — c'est
|
||||
// exactement le comportement attendu sur une installation sans borne.
|
||||
ChangeNotifierProvider.value(value: NymeaService()),
|
||||
ChangeNotifierProvider.value(value: service ?? NymeaService()),
|
||||
],
|
||||
child: MaterialApp(
|
||||
locale: const Locale('fr'),
|
||||
@ -228,6 +229,71 @@ void main() {
|
||||
});
|
||||
});
|
||||
|
||||
// ───────────────────────────────────────────────────────────────────────────
|
||||
group('une borne à pleine puissance n\'est pas arbitrée — et l\'écran le nomme', () {
|
||||
List<Map<String, dynamic>> v2cConfig() =>
|
||||
(_fixture('loadconfig_hems75_v2c.json')['loadConfigs'] as List)
|
||||
.cast<Map<String, dynamic>>();
|
||||
|
||||
/// `GetChargingInfos` du banc, 2026-08-28 : les deux bornes appairées y sont en
|
||||
/// `ChargingModeNormal` / `ChargingStateIdle`. **L'`evChargerId` n'a pas d'accolades**,
|
||||
/// là où l'id de charge de `GetLoadConfig` en a — c'est le piège que
|
||||
/// `chargingModeOf` réconcilie.
|
||||
NymeaService service({String mode = 'ChargingModeNormal'}) =>
|
||||
NymeaService()
|
||||
..adoptChargingInfosForTest([
|
||||
{
|
||||
'evChargerId': '4d1adafd-cafc-4813-93b1-ca8933ce71ab',
|
||||
'chargingMode': mode,
|
||||
'chargingState': 'ChargingStateIdle',
|
||||
},
|
||||
]);
|
||||
|
||||
testWidgets('la note est à l\'écran sur la borne en ChargingModeNormal',
|
||||
(tester) async {
|
||||
final config = LoadConfigProvider()..adoptForTest(v2cConfig());
|
||||
final telemetry = LoadTelemetryProvider()
|
||||
..adoptForTest({'degradedMode': false, 'loads': const []});
|
||||
|
||||
await tester.pumpWidget(_harness(
|
||||
config: config, telemetry: telemetry, service: service()));
|
||||
await tester.pump();
|
||||
|
||||
// Une seule des cinq charges est concernée : la V2C. Les deux autres bornes n'ont
|
||||
// pas d'entrée dans la charge utile lue, et une absence ne se conclut pas.
|
||||
expect(find.textContaining('hors arbitrage'), findsOneWidget);
|
||||
expect(find.textContaining('choisir PV ou Min+PV'), findsOneWidget);
|
||||
});
|
||||
|
||||
testWidgets('un mode éco fait taire la note', (tester) async {
|
||||
final config = LoadConfigProvider()..adoptForTest(v2cConfig());
|
||||
final telemetry = LoadTelemetryProvider()
|
||||
..adoptForTest({'degradedMode': false, 'loads': const []});
|
||||
|
||||
await tester.pumpWidget(_harness(
|
||||
config: config,
|
||||
telemetry: telemetry,
|
||||
service: service(mode: 'ChargingModeEcoWithMinCurrent')));
|
||||
await tester.pump();
|
||||
|
||||
expect(find.textContaining('hors arbitrage'), findsNothing);
|
||||
});
|
||||
|
||||
testWidgets('⚠️ les accolades de l\'id de charge ne doivent PAS faire rater la borne',
|
||||
(tester) async {
|
||||
// `GetLoadConfig` rend `{4d1adafd-…}`, `GetChargingInfos` rend `4d1adafd-…`. Comparer
|
||||
// les deux tels quels ne rend jamais rien — en silence : l'écran conclurait « mode
|
||||
// non lu » sur une borne parfaitement lue, et la note ne sortirait jamais.
|
||||
final svc = service();
|
||||
expect(svc.chargingModeOf('{4d1adafd-cafc-4813-93b1-ca8933ce71ab}'),
|
||||
'ChargingModeNormal');
|
||||
expect(svc.chargingModeOf('4d1adafd-cafc-4813-93b1-ca8933ce71ab'),
|
||||
'ChargingModeNormal');
|
||||
expect(svc.chargingModeOf('{09e520fd-d472-47c4-8887-6e7759372889}'), isNull,
|
||||
reason: 'une borne sans entrée lue n\'a pas de mode — pas un mode par défaut');
|
||||
});
|
||||
});
|
||||
|
||||
// ───────────────────────────────────────────────────────────────────────────
|
||||
// Les deux notes de la configuration réelle du 2026-08-28 — MONTÉES, pas
|
||||
// seulement calculées.
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user