LM-301 vérifié à l'usage : brancher le mécanisme n'a touché qu'une branche de fabrique dans rebuildLoadAdapters(), un champ optionnel au schéma RPC, et la comparaison sameHardware() — la charge utile fait partie du « matériel », un état ré-encodé impose une reconstruction. Aucun code d'arbitrage n'a bougé. La PAC de configuration vit dans m_loadAdapters, la MÊME table que les autres charges. Elle hérite donc d'ECS-412 (reconstruction incrémentale) et d'ECS-413 (état sûr à la désactivation, qui vaut ici ÉTAT 2 et non « contacts ouverts ») sans code dédié. Le dispatch des actions route par loadId d'abord, l'enregistrement en dur ne servant plus que de repli. Garde neuve, créée par cette configurabilité même : une PAC de config peut viser les mêmes contacts que la PAC de banc câblée dans energypluginnymea.cpp. Deux adaptateurs sur un contact, c'est deux commandes contradictoires et une charge comptée deux fois dans le budget (règle absolue 1). La configuration fait autorité : l'exemplaire en dur est retiré — sans mise en état sûr, les contacts ayant un propriétaire légitime. testSgReadyFromConfig couvre les deux refus (sans état 2, charges utiles mélangées), le round-trip par le store, le pilotage, et la désactivation vers l'état 2. Suite complète : 111 tests, 0 échec. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
128 lines
5.6 KiB
C++
128 lines
5.6 KiB
C++
// 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 <https://www.gnu.org/licenses/>.
|
|
*
|
|
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
|
|
|
|
#include <QtTest>
|
|
#include <QDateTime>
|
|
|
|
#include "simulationbase.h"
|
|
|
|
using namespace nymeaserver;
|
|
|
|
class ChargerPlugEvent
|
|
{
|
|
public:
|
|
ChargerPlugEvent() = default;
|
|
ChargerPlugEvent(const QDateTime &dt, bool pi = false, int pu = 0)
|
|
: dateTime{dt},
|
|
pluggedIn{pi},
|
|
percentageUsed{pu}
|
|
{}
|
|
QDateTime dateTime;
|
|
bool pluggedIn;
|
|
int percentageUsed = 0;
|
|
};
|
|
|
|
class Simulation: public SimulationBase
|
|
{
|
|
Q_OBJECT
|
|
public:
|
|
explicit Simulation(QObject *parent = nullptr) : SimulationBase(parent) { };
|
|
|
|
typedef QList<ChargerPlugEvent> ChargerPlugEvents;
|
|
typedef QList<int> DetailsStepList;
|
|
|
|
private slots:
|
|
void run_data();
|
|
void run();
|
|
|
|
// [T3] etmvariableload (ECS/routeur) : arrondi fixed (powerLevels) + clamp dynamic
|
|
// (maxPowerW), recrédit currentPowerW (anti-clignotement), délestage, round-trip powerSetpoint.
|
|
void testEcsSurplusPV();
|
|
|
|
// [T4] Watchdog L2 : compteur muet >90 s → etmvariableload setPowerSetpoint(0) force=true,
|
|
// planif suspendue (reste 0 sur N cycles), reprise au retour compteur.
|
|
void testMeterSilentFallback();
|
|
|
|
// [T4] Persistance LoadConfig (§4) : SetConfigs → fichier → relecture round-trip + validation.
|
|
void testLoadConfigPersistence();
|
|
// [T4] Construction des adaptateurs depuis la config : enabled servi, disabled exclu (§9).
|
|
void testLoadConfigBuildsAdapters();
|
|
// [T4] Injection RPC end-to-end : NymeaEnergy.Get/SetLoadConfig (couche consommée par l'app).
|
|
void testLoadConfigRpc();
|
|
// [rév.3] Chaîne complète : SetConfigs(relays[]) → rebuild → RelayRouter → cycle → commutation relais.
|
|
void testLoadConfigRelayRouter();
|
|
|
|
// SG-Ready (PAC) : montée d'états sur surplus, hystérésis 3↔4, protection court-cycling,
|
|
// + Volet 4 budget PARTAGÉ etmvariableload(ECS)↔PAC (waterfall unifié, inversion priorité).
|
|
void testSgReadySurplus();
|
|
|
|
// [rév.3] Combinatoire watts→relais DANS le RelayRouter : paliers dérivés, off-before-on,
|
|
// transition non-cascadée, et DÉDUPLICATION des niveaux (relais identiques fusionnés).
|
|
void testEcsRelayTopologies();
|
|
|
|
// [Phase 2] Ratios canoniques : EnergyRatiosCalculator aligné 1:1 sur le seam interim app
|
|
// (EnergyRatiosInterim.compute) — seed, normal, clamp, den≤0→n/a, non-monotone, nouveau jour.
|
|
void testEnergyRatiosAlignment();
|
|
|
|
// [étape 1 / ECS-306] Le budget décrémenté par le scheduler correspond au palier
|
|
// RÉELLEMENT applicable sous verrou minOn — les charges suivantes ne reçoivent pas un
|
|
// résidu surestimé.
|
|
void testEcsBudgetUnderLock();
|
|
// [étape 1 / ECS-411] Le palier courant est déduit de l'état réel des Things relais au
|
|
// démarrage, pas réinitialisé à 0.
|
|
void testEcsRestartRecovery();
|
|
// [étape 1 / ECS-412] SetLoadConfig pendant une fenêtre de verrou active ne réarme pas
|
|
// le verrou : seul le matériel modifié est reconstruit. Couvre aussi l'armement à froid.
|
|
void testEcsRebuildPreservesLock();
|
|
// [étape 1 / ECS-412] Démarrage à froid, palier 0, minOffS > 0, budget largement
|
|
// suffisant : la charge reste éteinte PENDANT minOffS puis s'enclenche. L'armement à
|
|
// froid doit être transitoire, jamais permanent (défaut constaté au banc 2026-08-09).
|
|
void testEcsColdStartLockExpires();
|
|
// [ECS-410] Échec d'écriture en cours de transition : retour arrière, arrêt total, puis
|
|
// état de défaut collant. Chaque marche de l'échelle a son cas, et la levée est délibérée.
|
|
void testEcsPartialFailure();
|
|
// [ECS-413] Désactiver une charge la laisse dans l'état SÛR de son adaptateur, pas dans
|
|
// son dernier état commandé — et un rebuild SANS désactivation ne coupe rien (cas négatif
|
|
// obligatoire, sans lui ECS-412 serait annulé).
|
|
void testEcsDisableLeavesSafeState();
|
|
// [ECS-414] L'échelle d'échec généralisée à SgReadyAdapter et EtmVariableLoadAdapter.
|
|
// Plancher propre à chaque adaptateur : ÉTAT 2 pour la PAC, jamais « contacts ouverts ».
|
|
void testSgReadyPartialFailure();
|
|
// [LM-300] Union discriminée : le mécanisme sg-ready se configure comme les autres, ses
|
|
// charges utiles sont mutuellement exclusives, et l'état 2 est obligatoire (ECS-110).
|
|
void testSgReadyFromConfig();
|
|
|
|
void printStates(Thing *thing);
|
|
void updateChargerMeter(Thing *thing);
|
|
|
|
QStringList plotOriginalData(int powerBalanceCount);
|
|
QStringList plotSimulation(const QString &title, int powerBalanceCount);
|
|
QStringList plotSpotMarketData(int powerBalanceCount);
|
|
|
|
};
|
|
|
|
Q_DECLARE_METATYPE(ChargerPlugEvent)
|
|
Q_DECLARE_METATYPE(Simulation::ChargerPlugEvents)
|
|
Q_DECLARE_METATYPE(Simulation::DetailsStepList)
|