Lot Connexions multi-HEMS : - modèle Installation + InstallationStore (persistance par UUID ; métadonnées SharedPreferences, token via flutter_secure_storage) - ConnectionManager au-dessus de nymea_service : mono-connexion, switchTo, connectNew, enterDemo (démo = active factice), contrôle d'identité DHCP - nymea_service : connect() réveillé (ws://4444, token par UUID), capture Hello (uuid/name/initialSetupRequired), authenticate/createUser, resetState - écran Installations (3 états) + déverrouillage installateur depuis la racine - écran Connexion : auth 2 branches (JSONRPC.Authenticate / CreateUser), détection auto via initialSetupRequired, segmenteur de repli - gate de routage (redirect + refreshListenable merge[cm,svc]) ; hasActiveConnection = isSimulation || (connected && authenticated) - en-tête drawer cliquable → Installations - invalidation des caches au switch (service.resetState + clearForSwitch rôles/scheduler/tariff), sans persist() Lot Rôles & appareils : - EnergySetupProvider refondu en tri-état (present/absent/non-configuré) - écran roles_devices_screen (3 zones, drag-priorité, inférence solaire/batterie) - LoadDescriptor (etmvariableload, rév.2 : PAC exclue → SG-Ready) ; Energy.SetRootMeter réel, Get/SetLoadConfig en stub loggé Correctifs : - bug signe énergie : production +, consommation NÉGATIVE sur nymea 1.15.2 → consumptionW/home en .abs() (autoconso n'est plus clouée à 0) - UUID Hello brace-wrapped normalisé Tests : flutter analyze 0 erreur ; gate + zéro-fuite au switch (5/5 verts). Validé en vrai : login auth .120, .75 ouvert, switch, déverrouillage installateur. Note : embarque aussi le WIP dashboard/thème déjà présent dans l'arbre. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
149 lines
4.3 KiB
Dart
149 lines
4.3 KiB
Dart
import 'package:flutter/foundation.dart';
|
|
import 'package:flutter/material.dart' show TimeOfDay;
|
|
|
|
enum TariffProviderType {
|
|
manual, // Tarif manuel saisi par l'utilisateur
|
|
customJson,
|
|
linkyTIC,
|
|
tibberPro,
|
|
octopusPro,
|
|
entsoe,
|
|
jsonRpc,
|
|
}
|
|
|
|
extension TariffProviderTypeExt on TariffProviderType {
|
|
String get label => switch (this) {
|
|
TariffProviderType.manual => 'Tarif manuel',
|
|
TariffProviderType.customJson => 'Fichier JSON custom',
|
|
TariffProviderType.linkyTIC => 'Linky TIC',
|
|
TariffProviderType.tibberPro => 'Tibber',
|
|
TariffProviderType.octopusPro => 'Octopus',
|
|
TariffProviderType.entsoe => 'ENTSO-E',
|
|
TariffProviderType.jsonRpc => 'JSON-RPC externe',
|
|
};
|
|
bool get isPro => this == TariffProviderType.tibberPro ||
|
|
this == TariffProviderType.octopusPro ||
|
|
this == TariffProviderType.entsoe;
|
|
}
|
|
|
|
enum TariffMode {
|
|
spotMarket,
|
|
hchp, // Heures Creuses / Heures Pleines
|
|
tou, // TOU contractuel
|
|
fixed,
|
|
}
|
|
|
|
extension TariffModeExt on TariffMode {
|
|
String get label => switch (this) {
|
|
TariffMode.spotMarket => 'Spot Market',
|
|
TariffMode.hchp => 'Heures Creuses / Heures Pleines',
|
|
TariffMode.tou => 'TOU contractuel',
|
|
TariffMode.fixed => 'Tarif fixe',
|
|
};
|
|
}
|
|
|
|
class HcHpConfig {
|
|
final double offPeakPrice; // €/kWh HC
|
|
final double peakPrice; // €/kWh HP
|
|
final TimeOfDay offPeakStart;
|
|
final TimeOfDay offPeakEnd;
|
|
|
|
const HcHpConfig({
|
|
this.offPeakPrice = 0.096,
|
|
this.peakPrice = 0.146,
|
|
this.offPeakStart = const TimeOfDay(hour: 22, minute: 0),
|
|
this.offPeakEnd = const TimeOfDay(hour: 6, minute: 0),
|
|
});
|
|
|
|
HcHpConfig copyWith({
|
|
double? offPeakPrice,
|
|
double? peakPrice,
|
|
TimeOfDay? offPeakStart,
|
|
TimeOfDay? offPeakEnd,
|
|
}) => HcHpConfig(
|
|
offPeakPrice: offPeakPrice ?? this.offPeakPrice,
|
|
peakPrice: peakPrice ?? this.peakPrice,
|
|
offPeakStart: offPeakStart ?? this.offPeakStart,
|
|
offPeakEnd: offPeakEnd ?? this.offPeakEnd,
|
|
);
|
|
}
|
|
|
|
class CurrentPrices {
|
|
final double now;
|
|
final double inOneHour;
|
|
final double min24h;
|
|
final double max24h;
|
|
final DateTime? minTime;
|
|
final DateTime? maxTime;
|
|
|
|
const CurrentPrices({
|
|
this.now = 0,
|
|
this.inOneHour = 0,
|
|
this.min24h = 0,
|
|
this.max24h = 0,
|
|
this.minTime,
|
|
this.maxTime,
|
|
});
|
|
}
|
|
|
|
/// Gère le provider tarifaire actif, le mode et les prix courants.
|
|
class TariffProvider extends ChangeNotifier {
|
|
TariffProviderType _activeProvider = TariffProviderType.manual;
|
|
TariffMode _mode = TariffMode.fixed;
|
|
double _manualPrice = 0.25; // €/kWh tarif manuel
|
|
HcHpConfig _hchp = const HcHpConfig();
|
|
CurrentPrices _prices = const CurrentPrices(
|
|
now: 0.092, inOneHour: 0.085,
|
|
min24h: 0.048, max24h: 0.182,
|
|
);
|
|
bool _loading = false;
|
|
|
|
/// Réinitialise au switch d'installation (reset mémoire aux défauts ; la
|
|
/// config tarifaire de la nouvelle box sera rechargée par `load()`).
|
|
void clearForSwitch() {
|
|
_activeProvider = TariffProviderType.manual;
|
|
_mode = TariffMode.fixed;
|
|
_manualPrice = 0.25;
|
|
_hchp = const HcHpConfig();
|
|
_prices = const CurrentPrices(
|
|
now: 0.092, inOneHour: 0.085,
|
|
min24h: 0.048, max24h: 0.182,
|
|
);
|
|
_loading = false;
|
|
notifyListeners();
|
|
}
|
|
|
|
TariffProviderType get activeProvider => _activeProvider;
|
|
TariffMode get mode => _mode;
|
|
HcHpConfig get hchp => _hchp;
|
|
CurrentPrices get prices => _prices;
|
|
bool get loading => _loading;
|
|
double get manualPrice => _manualPrice;
|
|
|
|
Future<void> load() async {
|
|
_loading = true;
|
|
notifyListeners();
|
|
await Future.delayed(const Duration(milliseconds: 200));
|
|
_loading = false;
|
|
notifyListeners();
|
|
// TODO: charger depuis nymea RPC
|
|
}
|
|
|
|
void setProvider(TariffProviderType p) {
|
|
_activeProvider = p; notifyListeners();
|
|
}
|
|
|
|
void setMode(TariffMode m) {
|
|
_mode = m; notifyListeners();
|
|
}
|
|
|
|
void updateHcHp(HcHpConfig c) {
|
|
_hchp = c; notifyListeners();
|
|
}
|
|
|
|
void setManualPrice(double price) {
|
|
_manualPrice = price.clamp(0.001, 9.999);
|
|
notifyListeners();
|
|
}
|
|
}
|