class EnergyData { final double pvPower; // Watts - production solaire final double homePower; // Watts - consommation maison final double batteryPower; // Watts - puissance batterie (+ charge, - décharge) final double gridPower; // Watts - réseau (+ injection, - soutif) final double batterySOC; // % - état de charge batterie final double temperature; // °C final String weatherIcon; // Journée final double dayProductionWh; final double daySelfConsumptionWh; final double dayGridInjectionWh; /// Compteurs CUMULÉS de la box (kWh), tels qu'elle les publie — pas des totaux du jour. /// /// `Energy.GetPowerBalance` rend `totalReturn` (exporté) et `totalAcquisition` /// (importé). Ils sont **cumulés depuis toujours**, pas remis à zéro chaque nuit : les /// afficher sous un libellé « aujourd'hui » serait faux. /// /// `NaN` signifie **valeur non exploitable**, et c'est un état à part entière : le banc /// `.75` publie des compteurs manifestement corrompus (7,5e+33 kWh importés) alors que /// `totalProduction` reste sain. L'app ne les corrige pas — elle refuse de les /// présenter comme une mesure (cf. [gridExportKwhOrNull]). final double gridExportKwh; final double gridImportKwh; final double selfConsumptionRate; // % final double autonomyRate; // % final double dayGains; // € // ── Borne de recharge : PLUS RIEN ICI ────────────────────────────────────── // // `chargingMode`, `chargingPower` (3,6 kW) et `solarSourcePercent` (82 %) ont vécu ici // avec des valeurs par défaut que rien ne remplaçait : `_updateEnergyData` ne les // touchait pas, et le mode n'avançait qu'en optimiste après une commande envoyée depuis // l'app. Trois écrans les affichaient comme des mesures — la tuile des favoris, le // Sankey du tableau de bord, et la carte véhicule (supprimée le 2026-08-28). // // Ce que la box publie vraiment sur une borne vit désormais dans `EvChargerLive` // (`models/ev_charger_live.dart`), champ par champ, nullable : `null` veut dire « non // publié », jamais zéro. final bool gridLimitOk; const EnergyData({ this.pvPower = 0, this.homePower = 0, this.batteryPower = 0, this.gridPower = 0, this.batterySOC = 0, this.temperature = 0, this.weatherIcon = 'cloud_snow', this.dayProductionWh = 0, this.daySelfConsumptionWh = 0, this.dayGridInjectionWh = 0, this.gridExportKwh = 0, this.gridImportKwh = 0, this.selfConsumptionRate = 0, this.autonomyRate = 0, this.dayGains = 0, this.gridLimitOk = true, }); EnergyData copyWith({ double? pvPower, double? homePower, double? batteryPower, double? gridPower, double? batterySOC, double? temperature, String? weatherIcon, double? dayProductionWh, double? daySelfConsumptionWh, double? dayGridInjectionWh, double? gridExportKwh, double? gridImportKwh, double? selfConsumptionRate, double? autonomyRate, double? dayGains, bool? gridLimitOk, }) { return EnergyData( pvPower: pvPower ?? this.pvPower, homePower: homePower ?? this.homePower, batteryPower: batteryPower ?? this.batteryPower, gridPower: gridPower ?? this.gridPower, batterySOC: batterySOC ?? this.batterySOC, temperature: temperature ?? this.temperature, weatherIcon: weatherIcon ?? this.weatherIcon, dayProductionWh: dayProductionWh ?? this.dayProductionWh, daySelfConsumptionWh: daySelfConsumptionWh ?? this.daySelfConsumptionWh, dayGridInjectionWh: dayGridInjectionWh ?? this.dayGridInjectionWh, gridExportKwh: gridExportKwh ?? this.gridExportKwh, gridImportKwh: gridImportKwh ?? this.gridImportKwh, selfConsumptionRate: selfConsumptionRate ?? this.selfConsumptionRate, autonomyRate: autonomyRate ?? this.autonomyRate, dayGains: dayGains ?? this.dayGains, gridLimitOk: gridLimitOk ?? this.gridLimitOk, ); } /// Plafond de plausibilité pour un compteur d'énergie domestique cumulé, en kWh. /// /// 10 GWh, soit environ **trois mille ans** de consommation d'une maison. Aucune /// installation résidentielle ne l'atteindra ; tout ce qui le dépasse est un compteur /// cassé, pas un record. Le seuil est volontairement absurde-haut : il ne s'agit pas de /// juger une valeur, seulement d'attraper les corrompues. static const double kPlausibleMaxKwh = 1e7; /// Vrai si [v] peut être présenté comme une mesure. static bool isPlausibleKwh(double v) => v.isFinite && v >= 0 && v <= kPlausibleMaxKwh; /// Export cumulé, ou `null` si la box publie une valeur inexploitable. double? get gridExportKwhOrNull => isPlausibleKwh(gridExportKwh) ? gridExportKwh : null; /// Import cumulé, ou `null` si la box publie une valeur inexploitable. double? get gridImportKwhOrNull => isPlausibleKwh(gridImportKwh) ? gridImportKwh : null; } enum ChargingMode { pv, minPv, boost } class HistoryPoint { final DateTime time; final double pvWh; final double homeWh; final double gridWh; final double batteryWh; const HistoryPoint({ required this.time, this.pvWh = 0, this.homeWh = 0, this.gridWh = 0, this.batteryWh = 0, }); } // ── Entrée Energy.GetPowerBalanceLogs ───────────────────────────────────────── class PowerBalanceEntry { final DateTime timestamp; final double productionW; // puissance PV (valeur absolue, W) final double consumptionW; // consommation maison (W) final double acquisitionW; // import réseau (W, positif) final double storageW; // batterie : +charge / -décharge (W) /// Compteurs cumulés depuis l'origine, en **kWh** — et non en Wh comme le nom de ces /// champs l'a longtemps dit. /// /// Vérifié sur `.75` le 2026-08-27 par recoupement, parce qu'aucune documentation ne le /// tranchait : sur une fenêtre de 21,75 h, l'intégration trapézoïdale des puissances /// instantanées (qui sont bien en **W**) rend **77 697 Wh**, quand la différence de /// `totalProduction` sur la même fenêtre rend **78,31**. Le rapport est mille. /// /// Le mauvais nom n'était pas cosmétique : l'écran Énergie affichait « 78 Wh » pour une /// journée de 78 kWh, et personne ne pouvait le voir sans connaître l'ordre de grandeur /// attendu. final double totalProductionKwh; final double totalConsumptionKwh; /// Injecté vers le réseau, cumulé (kWh). final double totalReturnKwh; /// Soutiré au réseau, cumulé (kWh). final double totalAcquisitionKwh; const PowerBalanceEntry({ required this.timestamp, this.productionW = 0, this.consumptionW = 0, this.acquisitionW = 0, this.storageW = 0, this.totalProductionKwh = 0, this.totalConsumptionKwh = 0, this.totalReturnKwh = 0, this.totalAcquisitionKwh = 0, }); }