L'entrée 🔴 du TODO. Aucune des quatre tuiles n'était journalière : deux pourcentages instantanés, deux compteurs cumulés depuis l'origine dont deux portaient le mot « aujourd'hui ». Elles montrent désormais la journée, sous un en-tête qui le dit une fois. MÉTHODE : une DIFFÉRENCE de compteurs entre minuit local et maintenant, jamais une intégration des puissances. La première est une mesure, prise sur les mêmes compteurs que ceux du fournisseur ; la seconde est une estimation qui suppose que chaque échantillon représente son intervalle. Mesuré sur .75 : l'intégration rend 77,70 kWh là où la différence rend 78,31 — 0,8 % d'écart, systématiquement à la baisse. Il n'y a donc PAS de repli silencieux de l'une vers l'autre : quand la mesure n'est pas exploitable, le type le dit. DEUX BUGS D'UNITÉ, trouvés en cherchant à savoir ce que valaient les compteurs. Aucune documentation ne tranchait, donc recoupement sur machine : l'intégration des puissances (en W) rend 77 697 Wh quand la différence de totalProduction rend 78,310. Le rapport est mille — LES COMPTEURS SONT EN kWh, et le modèle Dart les nommait Wh. 1. L'écran Énergie divisait ces kWh par mille pour les afficher : une journée de 78 kWh s'y lisait « 78 Wh », sous un axe intitulé « Bilan énergétique (Wh) ». Invisible sans connaître l'ordre de grandeur attendu. 2. La simulation accumulait des Wh : un écran juste en simulation aurait cassé d'un facteur mille contre une vraie box. LE CONTRÔLE QUI VALIDE LE BILAN : production + soutiré == consommation + injecté. C'est le seul recoupement disponible sur des compteurs qu'on ne peut pas vérifier autrement, et il tient EXACTEMENT sur .75 — 112,244 des deux côtés sur 21,75 h. Faux, il désigne un compteur cassé ou une remise à zéro, et la carte le signale au lieu de présenter comme une mesure ce qui ne se recoupe pas. Les trois pièges du TODO n'ont pas été repayés : bornes à minuit LOCAL via GetPowerBalanceLogs · décodage délégué à fetchPowerBalanceLogs, qui porte déjà les bonnes clés sans préfixe currentPower · plausibilité vérifiée sur les DEUX bornes avant de soustraire, parce que deux valeurs absurdes peuvent avoir une différence d'apparence saine. Plus un quatrième trouvé en chemin : un compteur qui RECULE (redémarrage en milieu de fenêtre) donne un bilan négatif, et un max(0, …) le rendrait faux en silence. Rien ne s'affiche jamais à zéro quand c'est inconnu. Sans production, le taux d'autoconsommation est null et non 0 % — à 3 h du matin, « 0 % » se lirait comme un défaut alors qu'il n'y a rien à autoconsommer. VÉRIFIÉ SUR APPAREIL contre .75, en lecture seule (le banc est en préparation V2C, rien n'a été écrit) : 32 échantillons depuis minuit, prod 28,37 · conso 26,55 · import 11,95 · export 13,77 kWh, identité OK, et les tuiles affichent 51 % / 55 % / 13,8 / 11,9. Le passage sur appareil a trouvé ce qu'aucun des 138 tests unitaires ne pouvait voir : LES QUATRE LIBELLÉS DÉBORDAIENT — « Autoconsomm… », « Vers réseau · aujou… ». Le suffixe « aujourd'hui » répété quatre fois mangeait le nom de la grandeur ; il est dit une fois en en-tête. Le harnais d'appareil échoue désormais sur tout libellé finissant par « … ». Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016vrTVifar2GN5rtUh89Wkq
181 lines
6.7 KiB
Dart
181 lines
6.7 KiB
Dart
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
|
|
final ChargingMode chargingMode;
|
|
final double chargingPower; // kW
|
|
final double solarSourcePercent; // %
|
|
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.chargingMode = ChargingMode.pv,
|
|
this.chargingPower = 3.6,
|
|
this.solarSourcePercent = 82,
|
|
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,
|
|
ChargingMode? chargingMode,
|
|
double? chargingPower,
|
|
double? solarSourcePercent,
|
|
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,
|
|
chargingMode: chargingMode ?? this.chargingMode,
|
|
chargingPower: chargingPower ?? this.chargingPower,
|
|
solarSourcePercent: solarSourcePercent ?? this.solarSourcePercent,
|
|
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,
|
|
});
|
|
} |