- 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>
172 lines
5.8 KiB
Dart
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();
|
|
}
|
|
}
|