// SPDX-License-Identifier: GPL-3.0-or-later // Copyright (C) 2025 - 2026, Patrick Schurig / ETM PowerSync #pragma once #include #include #include "../types/loadconfig.h" /*! * \brief Store persistant des configs de charge pilotée (contrat etmvariableload §4). * * Master store unique : \c setConfigs() valide, **persiste** (\c /var/lib/nymea/\c * energy-load-configuration.json, à côté de \c energy-manager-configuration.json) et émet * \c changed() — l'arbitre reconstruit ses adaptateurs sur ce signal, le handler notifie l'app. * * Format fichier : \c {"version":1,"loads":[ ... ]}, chaque entrée étant un \c LoadConfig §4. * Le tableau \c loads[] est **mot pour mot** le \c LoadDescriptor §4 (jonction inter-repos * avec l'app). * * \invariant Écriture atomique (fichier temporaire + \c rename) — jamais de fichier mi-écrit. * \invariant \c setConfigs() rejette en bloc (rien persisté) si une seule entrée est invalide. */ class LoadConfigStore : public QObject { Q_OBJECT public: /*! * \brief Construit le store et charge le fichier s'il existe. * \param parent Propriétaire Qt. * \note Un fichier absent, illisible ou au JSON invalide n'est pas une erreur : * le store démarre à vide. Une ENTRÉE invalide est ignorée individuellement * (tolérance ascendante) — on ne perd pas les autres pour une seule. */ explicit LoadConfigStore(QObject *parent = nullptr); /*! \brief Configs actuellement en mémoire (chargées au démarrage ou via setConfigs). * \return Copie de l'ensemble courant ; toutes ont passé \c LoadConfig::isValid(). */ LoadConfigs configs() const { return m_configs; } /*! * \brief Remplace l'ensemble des configs : valide tout → persiste → \c emit changed(). * \param configs Nouvel ensemble (remplace l'existant — l'app envoie la liste complète). * \param[out] error Message FR si rejet (aucune écriture dans ce cas). * \return true si validé + persisté ; false si invalide ou échec d'écriture. */ bool setConfigs(const LoadConfigs &configs, QString *error = nullptr); /*! \brief Cohérence de l'ENSEMBLE, ce qu'aucune charge ne peut vérifier seule. * * ECS-110 s'arrête au bord d'une charge : \c LoadConfig::isValid() ne voit pas les * autres. Deux exigences ne vivent donc que ici — identifiants uniques, et un Thing * revendiqué par au plus UNE charge active. Le second cas est le même défaut que la * garde de l'arbitre contre la PAC codée en dur, mais entre deux charges de * configuration : deux commandes contradictoires sur un organe, et sa puissance * comptée deux fois dans le budget (règle absolue 1). * * \note Seules les charges \c enabled sont confrontées. Une charge désactivée ne * construit aucun adaptateur et ne commande rien ; l'interdire empêcherait de * préparer une configuration de remplacement. La bascule \c enabled repasse par * \c setConfigs(), donc par cette vérification. */ static bool validateSet(const LoadConfigs &configs, QString *error = nullptr); signals: /*! \brief Émis après une persistance réussie de \c setConfigs(). */ void changed(); private: QString filePath() const; bool load(); bool save(QString *error) const; LoadConfigs m_configs; };