etm-powersync-app/lib/providers/energy_setup_provider.dart
pakutz79 c19c9d1a98 feat: navigation drawer, EMS setup, scheduler, tarifs, paramètres app
- Drawer custom (overlay Stack) avec mode installateur PIN SHA-256
- GoRouter + ShellRoute : navigation préservée entre onglets
- 6 providers : NavigationProvider, InstallerModeProvider, AppSettingsProvider,
  EnergySetupProvider, SchedulerProvider, TariffProvider
- Écrans Energy Manager : RoleConfigFlow (3 étapes), Scheduler, Tarifs, Timeline
- Écrans Paramètres : Apparence, Écrans actifs, AppSettingsScreen
- DrawerMenuButton présent dans les 5 AppBars principaux
- Simulation : _thingClasses générées avec interfaces EMS pour filtrage des rôles
- Compteur solaire : ajout smartmeter aux interfaces compatibles
- Thème ETM (etm_theme.dart), ProLockBadge, widgets PowerBar/RoleCard/TimelineSlotCard
- Dépendances : go_router, shared_preferences, crypto, url_launcher

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-24 14:52:32 +01:00

172 lines
5.8 KiB
Dart

import 'package:flutter/foundation.dart';
import '../services/nymea_service.dart';
import '../models/nymea_models.dart';
/// Rôles de l'EMS (Energy Management System).
enum EmsRole {
evCharger,
dhw, // Chauffe-eau
heatPump, // Pompe à chaleur / SG-Ready
battery,
solarMeter, // Compteur solaire
gridMeter, // Compteur réseau
}
extension EmsRoleExt on EmsRole {
String get label => switch (this) {
EmsRole.evCharger => 'EV Charger',
EmsRole.dhw => 'Chauffe-eau',
EmsRole.heatPump => 'Pompe à chaleur',
EmsRole.battery => 'Batterie',
EmsRole.solarMeter => 'Compteur solaire',
EmsRole.gridMeter => 'Compteur réseau',
};
String get icon => switch (this) {
EmsRole.evCharger => '🚗',
EmsRole.dhw => '🌡️',
EmsRole.heatPump => '♨️',
EmsRole.battery => '🔋',
EmsRole.solarMeter => '☀️',
EmsRole.gridMeter => '',
};
/// Interfaces nymea compatibles avec ce rôle.
List<String> get compatibleInterfaces => switch (this) {
EmsRole.evCharger => ['evcharger'],
EmsRole.dhw => ['simpleheatpump', 'relay', 'smartplug', 'powerswitch'],
EmsRole.heatPump => ['sgready', 'sgrelay', 'heatpump'],
EmsRole.battery => ['battery', 'energystorage', 'batterymonitor'],
EmsRole.solarMeter => ['solarinverter', 'energymeter', 'meter', 'inverter', 'smartmeter'],
EmsRole.gridMeter => ['energymeter', 'smartmeter', 'meter'],
};
/// Label d'interface affiché dans la liste de sélection (Step 1).
String interfaceLabelFor(String iface) => switch (iface.toLowerCase()) {
'sgready' => 'SG-Ready',
'sgrelay' => 'SG-Ready',
'heatpump' => 'Heat Pump',
'simpleheatpump' => 'SimpleHeatpump',
'solarinverter' => 'Inverter',
'energymeter' => 'Meter',
'meter' => 'Meter',
'inverter' => 'Inverter',
'smartmeter' => 'Smart Meter',
'evcharger' => 'EV Charger',
'battery' => 'Battery',
'energystorage' => 'Energy Storage',
_ => iface,
};
}
/// Assignation d'un thing à un rôle EMS.
class RoleAssignment {
final EmsRole role;
final NymeaThing thing;
final bool enabled;
final Map<String, dynamic> params; // puissance, priorité, etc.
const RoleAssignment({
required this.role,
required this.thing,
this.enabled = true,
this.params = const {},
});
RoleAssignment copyWith({
bool? enabled,
Map<String, dynamic>? params,
}) => RoleAssignment(
role: role,
thing: thing,
enabled: enabled ?? this.enabled,
params: params ?? this.params,
);
}
/// Résultat d'un test de connexion de rôle.
enum ConnectionTestStatus { idle, testing, success, failure }
class ConnectionTestResult {
final ConnectionTestStatus status;
final String? message;
const ConnectionTestResult(this.status, [this.message]);
}
/// Gère les assignations de rôles EMS et les flux de configuration.
class EnergySetupProvider extends ChangeNotifier {
final Map<EmsRole, RoleAssignment?> _assignments = {
for (final r in EmsRole.values) r: null,
};
ConnectionTestResult _testResult =
const ConnectionTestResult(ConnectionTestStatus.idle);
Map<EmsRole, RoleAssignment?> get assignments => _assignments;
ConnectionTestResult get testResult => _testResult;
RoleAssignment? assignmentFor(EmsRole role) => _assignments[role];
bool isConfigured(EmsRole role) => _assignments[role] != null;
// ── Assignation locale ───────────────────────────────────────────────────────
void assign(EmsRole role, NymeaThing thing, Map<String, dynamic> params) {
_assignments[role] = RoleAssignment(role: role, thing: thing, params: params);
notifyListeners();
}
void unassign(EmsRole role) {
_assignments[role] = null;
notifyListeners();
}
void setEnabled(EmsRole role, bool enabled) {
final current = _assignments[role];
if (current != null) {
_assignments[role] = current.copyWith(enabled: enabled);
notifyListeners();
}
}
// ── Filtrage des things compatibles avec un rôle ─────────────────────────────
List<NymeaThing> compatibleThings(
EmsRole role, NymeaService service) {
return service.things.where((thing) {
try {
final cls = service.thingClasses
.firstWhere((c) => c.id == thing.thingClassId);
return cls.interfaces.any(
(i) => role.compatibleInterfaces
.contains(i.toLowerCase()));
} catch (_) {
return false;
}
}).toList();
}
// ── Test de connexion (simulé — à brancher sur RPC réel) ────────────────────
Future<void> testConnection(EmsRole role, NymeaService service) async {
_testResult = const ConnectionTestResult(ConnectionTestStatus.testing);
notifyListeners();
await Future.delayed(const Duration(seconds: 2));
final assignment = _assignments[role];
if (assignment == null) {
_testResult = const ConnectionTestResult(
ConnectionTestStatus.failure,
'Aucun appareil configuré pour ce rôle.');
} else {
// En mode simulation, retourne toujours succès
_testResult = ConnectionTestResult(
ConnectionTestStatus.success,
'${assignment.thing.name} a répondu correctement.');
}
notifyListeners();
}
void resetTest() {
_testResult = const ConnectionTestResult(ConnectionTestStatus.idle);
notifyListeners();
}
}