import 'package:flutter/foundation.dart'; import '../services/nymea_service.dart'; import '../services/rpc_schema.dart'; /// Porte le [RpcSchema] de la box courante et le tient à jour au changement /// d'installation. /// /// ## Trois états, jamais deux /// /// Un écran qui grise ses champs doit distinguer : /// /// - **schéma lu, champ absent** → « cette box ne l'expose pas encore » ; /// - **schéma lu, champ `r:`** → mesure affichable, jamais éditable ; /// - **schéma non lu** → on ne sait pas, et il faut le dire. /// /// Écraser le troisième cas sur l'un des deux premiers est exactement l'erreur que ce /// provider existe pour empêcher : un champ grisé sans raison se lit comme un bug ou un /// droit manquant, et un champ actif qui part en écriture vers une box qui n'en veut pas /// fait rejeter **tout** l'appel (nymea refuse la requête entière sur clé inconnue). class SchemaProvider extends ChangeNotifier { NymeaService? _service; RpcSchema _schema = RpcSchema.unknown; bool _loading = false; String? _error; RpcSchema get schema => _schema; bool get loading => _loading; /// Message d'échec de lecture, s'il y en a eu un. Non nul ⇒ [RpcSchema.loaded] faux. String? get error => _error; void attach(NymeaService service) { if (identical(_service, service)) return; _service = service; } /// Lit le schéma. À appeler une fois la connexion établie — le résultat ne change pas /// tant que la box ne redémarre pas avec un autre paquet. Future load() async { final service = _service; if (service == null) return; _loading = true; _error = null; notifyListeners(); try { final raw = await service.introspect(); if (raw == null) { _schema = RpcSchema.unknown; _error = 'introspection indisponible'; } else { _schema = RpcSchema.fromIntrospect(raw, hello: service.helloPayload); _error = null; } } catch (e) { _schema = RpcSchema.unknown; _error = e.toString(); } finally { _loading = false; notifyListeners(); } } /// Ce qu'un écran peut annoncer honnêtement comme « version de la box ». /// /// **Ce n'est pas la version du paquet du moteur.** `1.15.2+etm15` n'est exposée par /// aucune méthode JSON-RPC ; elle ne se lit que par `dpkg` sur la box. Ce que le fil /// porte, c'est la version d'API de l'expérience — qui est de toute façon le bon /// discriminant, puisqu'elle décrit le contrat et non le paquet qui l'implémente. String versionLine() { final parts = []; final nymea = _schema.nymeaVersion ?? _service?.nymeaVersion; if (nymea != null && nymea.isNotEmpty) parts.add('nymea $nymea'); final energy = _schema.experienceVersions['NymeaEnergy']; if (energy != null) parts.add('NymeaEnergy $energy'); final ac = _schema.experienceVersions['AirConditioning']; if (ac != null) parts.add('AirConditioning $ac'); if (parts.isEmpty) return 'version de la box inconnue'; return parts.join(' · '); } /// Remet à zéro au changement d'installation — le schéma appartient à la box quittée. void clearForSwitch() { _schema = RpcSchema.unknown; _error = null; _loading = false; notifyListeners(); } }