// SPDX-License-Identifier: GPL-3.0-or-later /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * Copyright (C) 2013 - 2024, nymea GmbH * Copyright (C) 2024 - 2025, chargebyte austria GmbH * * This file is part of nymea-energy-plugin-nymea. * * nymea-energy-plugin-nymea.s free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * nymea-energy-plugin-nymea.s distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with nymea-energy-plugin-nymea. If not, see . * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ #ifndef NYMEAENERGYJSONHANDLER_H #define NYMEAENERGYJSONHANDLER_H #include #include #include "types/scoreentry.h" #include "etm/ratios/energyratioscalculator.h" class SmartChargingManager; class SpotMarketManager; class LoadConfigStore; class LoadConfig; class EnergyManager; class NymeaEnergyJsonHandler : public JsonHandler { Q_OBJECT public: explicit NymeaEnergyJsonHandler(SpotMarketManager *spotMarketManager, SmartChargingManager *smartChargingManager, LoadConfigStore *loadConfigStore = nullptr, EnergyManager *energyManager = nullptr, QObject *parent = nullptr); QString name() const override; Q_INVOKABLE JsonReply *GetPhasePowerLimit(const QVariantMap ¶ms); Q_INVOKABLE JsonReply *SetPhasePowerLimit(const QVariantMap ¶ms); Q_INVOKABLE JsonReply *GetAcquisitionTolerance(const QVariantMap ¶ms); Q_INVOKABLE JsonReply *SetAcquisitionTolerance(const QVariantMap ¶ms); Q_INVOKABLE JsonReply *GetBatteryLevelConsideration(const QVariantMap ¶ms); Q_INVOKABLE JsonReply *SetBatteryLevelConsideration(const QVariantMap ¶ms); Q_INVOKABLE JsonReply *GetChargingInfos(const QVariantMap ¶ms); Q_INVOKABLE JsonReply *SetChargingInfo(const QVariantMap ¶ms, const JsonContext &context); Q_INVOKABLE JsonReply *GetLockOnUnplug(const QVariantMap ¶ms); Q_INVOKABLE JsonReply *SetLockOnUnplug(const QVariantMap ¶ms); Q_INVOKABLE JsonReply *GetAvailableSpotMarketProviders(const QVariantMap ¶ms); Q_INVOKABLE JsonReply *GetSpotMarketConfiguration(const QVariantMap ¶ms); Q_INVOKABLE JsonReply *SetSpotMarketConfiguration(const QVariantMap ¶ms); Q_INVOKABLE JsonReply *GetSpotMarketScoreEntries(const QVariantMap ¶ms); Q_INVOKABLE JsonReply *GetChargingSchedules(const QVariantMap ¶ms); Q_INVOKABLE JsonReply *GetLoadConfig(const QVariantMap ¶ms); /*! * \brief L'avancement d'une obligation est-il MESURABLE pour cette charge ? * \param c Configuration de la charge. \param[out] cause Code opaque si non mesurable. * \return Vrai si un cumul d'énergie existe pour juger l'avancement. * * \par Pourquoi ce signal vit dans GetLoadConfig et non dans la télémétrie * \c measurement n'existe que pour les charges de \c loads[] ; or une charge qu'on DÉCLARE * est précisément celle qui n'y est pas encore — pas arbitrée, ou créée à l'instant. * L'écran serait muet au moment exact où il doit parler : celui de la saisie. * * \par Trois causes, trois gestes * \c noMeter (réparable en une manipulation), \c meterWithoutEnergy (compteur à remplacer), * \c noSessionEnergy (rien à faire, c'est le matériel). Sans la cause, l'avertissement ne * peut dire que « ça ne marchera pas ». * * \note \c meterThingId ne permet PAS de le déduire : un compteur peut publier * \c currentPower sans \c totalEnergyConsumed (mesuré sur l'ECS du banc). */ bool progressMesurable(const LoadConfig &c, QString &cause) const; Q_INVOKABLE JsonReply *SetLoadConfig(const QVariantMap ¶ms); // [Phase 2] Ratios canoniques (autoconsommation / autonomie), % [0,100]. Q_INVOKABLE JsonReply *GetEnergyRatios(const QVariantMap ¶ms); /*! * \brief [ETM] Lève le verrou de défaut d'une charge pilotée (ECS-410). * \param params \c {"loadId": string}. * \return \c energyError — \c EnergyErrorNoError si la charge existe et son défaut est * levé, \c EnergyErrorInvalidParameter si l'identifiant est inconnu. * \note Acte DÉLIBÉRÉ de l'opérateur : le défaut d'ECS-410 est collant par conception, et * cet appel est le seul moyen de le lever hors reconstruction de l'adaptateur. Il est * journalisé côté arbitre — c'est ce qui rend le diagnostic possible après coup. */ Q_INVOKABLE JsonReply *ClearLoadFault(const QVariantMap ¶ms); /*! * \brief [ETM] Instantané de l'état runtime de l'arbitrage (allocation, budget, verrous, * mécanismes, motifs). * \param params Aucun. * \return La MÊME charge utile que la notification \c LoadTelemetryChanged — pas un * recalcul : l'objet rendu est celui qui a été publié. * \note Répond à la règle « tout élément runtime a un \c Get* ET un \c *Changed ». Un * client connecté après un changement doit pouvoir lire l'état, sans quoi la * notification seule le laisse dans un silence ambigu (famille ECS-411). */ Q_INVOKABLE JsonReply *GetLoadTelemetry(const QVariantMap ¶ms); //! \brief Pose une dérogation (servie au maximum, financée au réseau, en tête de passe éco). //! \param params \c loadId, \c durationS (obligatoire, borné), \c targetWh (optionnel). //! \return \c energyError — \c InvalidParameter si la charge est inconnue ou la durée hors bornes. //! \brief Lit la courbe de prévision PV stockée. \param params \c from, \c to, \c o:knownAt. //! \return \c forecastPoints — les heures SANS prévision sont absentes, jamais à zéro. //! \note Un \c Get* SANS \c *Changed : l'invariant 11 vise l'état vivant, pas l'historique. Q_INVOKABLE JsonReply *GetPvForecast(const QVariantMap ¶ms); Q_INVOKABLE JsonReply *SetLoadDerogation(const QVariantMap ¶ms); //! \brief Lève une dérogation avant son terme et rend le mode mémorisé. //! \param params \c loadId. \return \c energyError — \c InvalidParameter si aucune n'était en cours. Q_INVOKABLE JsonReply *ClearLoadDerogation(const QVariantMap ¶ms); signals: void PhasePowerLimitChanged(const QVariantMap ¶ms); void AcquisitionToleranceChanged(const QVariantMap ¶ms); void LockOnUnplugChanged(const QVariantMap ¶ms); void ChargingInfoAdded(const QVariantMap ¶ms); void ChargingInfoRemoved(const QVariantMap ¶ms); void ChargingInfoChanged(const QVariantMap ¶ms); void SpotMarketConfigurationChanged(const QVariantMap ¶ms); void SpotMarketScoreEntriesChanged(const QVariantMap ¶ms); void ChargingSchedulesChanged(const QVariantMap ¶ms); void BatteryLevelConsiderationChanged(const QVariantMap ¶ms); void LoadConfigChanged(const QVariantMap ¶ms); void EnergyRatiosChanged(const QVariantMap ¶ms); //! [ETM] État runtime d'arbitrage — sur changement significatif ET au battement périodique. void LoadTelemetryChanged(const QVariantMap ¶ms); private slots: void onPowerBalanceChanged(); private: SpotMarketManager *m_spotMarketManager; SmartChargingManager *m_smartChargingManager = nullptr; LoadConfigStore *m_loadConfigStore = nullptr; EnergyManager *m_energyManager = nullptr; EnergyRatiosCalculator m_ratiosCalculator; EnergyRatiosCalculator::Ratios m_lastRatios; bool m_lastRatiosValid = false; // un calcul a-t-il déjà été émis ? QVariantMap currentEnergyRatios(); /*! * \brief Schéma de la charge utile de télémétrie — UNE déclaration, deux usages. * \return Le map de schéma, posé à l'identique dans les \c returns de * \c GetLoadTelemetry et dans les \c params de \c LoadTelemetryChanged. * \note C'est la règle « snapshot ≡ notification » rendue impossible à enfreindre : * deux déclarations divergeraient, et c'est l'appel de reprise à froid qui casserait — * celui qu'on ne teste pas tous les jours. */ static QVariantMap loadTelemetrySchema(); //! \brief Charge utile des ratios — DÉCLARÉE UNE FOIS, servie au \c Get et au \c Changed. //! Deux déclarations divergent, et c'est l'appel de reprise à froid qui casse (invariant 11). //! \return Le schéma des champs, prêt pour \c registerMethod et \c registerNotification. static QVariantMap energyRatiosSchema(); //! \return Charge utile courante ; carte MINIMALE (sans \c timestamp) si aucun cycle //! d'arbitrage n'a encore eu lieu — cf. \c EnergyArbitrator::loadTelemetry(). QVariantMap currentLoadTelemetry() const; void sendSpotMarketConfigurationChangedNotification(); }; #endif // NYMEAENERGYJSONHANDLER_H