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>
73 lines
2.2 KiB
Dart
73 lines
2.2 KiB
Dart
/// Une installation HEMS enregistrée.
|
|
///
|
|
/// **Identité stable = [uuid]** (Server UUID renvoyé par `JSONRPC.Hello`), jamais
|
|
/// l'IP : en DHCP, [host] change. Le [token] est un **credential** persisté à
|
|
/// part (Keychain/Keystore via `flutter_secure_storage`) — il ne figure donc
|
|
/// **pas** dans [toJson] (métadonnées SharedPreferences uniquement).
|
|
class Installation {
|
|
/// Server UUID (clé d'identité, Hello `uuid`).
|
|
final String uuid;
|
|
|
|
/// Nom de la box (Hello `name`).
|
|
final String name;
|
|
|
|
/// Hôte courant (IP/hostname) — peut changer en DHCP, jamais une identité.
|
|
final String host;
|
|
|
|
/// Port JSON-RPC. Défaut **4444** (ws clair, LAN).
|
|
final int port;
|
|
|
|
/// Dernière fois où la box a répondu (connexion ou découverte).
|
|
final DateTime? lastSeen;
|
|
|
|
/// Token d'authentification — **transitoire en mémoire**, persisté séparément
|
|
/// par UUID dans le secure storage. `null` tant que la box n'est pas appairée.
|
|
final String? token;
|
|
|
|
const Installation({
|
|
required this.uuid,
|
|
required this.name,
|
|
required this.host,
|
|
this.port = 4444,
|
|
this.lastSeen,
|
|
this.token,
|
|
});
|
|
|
|
bool get hasToken => token != null && token!.isNotEmpty;
|
|
|
|
Installation copyWith({
|
|
String? name,
|
|
String? host,
|
|
int? port,
|
|
DateTime? lastSeen,
|
|
String? token,
|
|
}) =>
|
|
Installation(
|
|
uuid: uuid,
|
|
name: name ?? this.name,
|
|
host: host ?? this.host,
|
|
port: port ?? this.port,
|
|
lastSeen: lastSeen ?? this.lastSeen,
|
|
token: token ?? this.token,
|
|
);
|
|
|
|
/// Métadonnées persistées (SharedPreferences). **Sans token** (secure storage).
|
|
Map<String, dynamic> toJson() => {
|
|
'uuid': uuid,
|
|
'name': name,
|
|
'host': host,
|
|
'port': port,
|
|
if (lastSeen != null) 'lastSeen': lastSeen!.toIso8601String(),
|
|
};
|
|
|
|
factory Installation.fromJson(Map<String, dynamic> j) => Installation(
|
|
uuid: j['uuid'] as String,
|
|
name: (j['name'] as String?) ?? 'nymea',
|
|
host: j['host'] as String,
|
|
port: (j['port'] as num?)?.toInt() ?? 4444,
|
|
lastSeen: j['lastSeen'] != null
|
|
? DateTime.tryParse(j['lastSeen'] as String)
|
|
: null,
|
|
);
|
|
}
|