`voltage` était déclarée une seule fois hors des trois blocs et mutée par l'un d'eux : une phase sans tension déclarée était calculée avec celle d'une autre. Et le défaut n'était pas celui qu'on croyait — le mesurer l'a montré. L'interface energymeter rend currentPhaseA et voltagePhaseA OBLIGATOIRES, B et C optionnels : la branche « puissance ÷ tension » de A est morte par construction. La fuite n'allait donc pas de A vers B, comme la note d'origine l'affirmait, mais de B vers C. Démontré : B à 200 V, C à 4 000 W sans tension déclarée → 5,0 A de marge au lieu de 7,6. Deux conditions, dont aucune n'était évidente avant d'écrire le test : le masque doit couvrir B ET C — interroger C seule n'exécute pas le bloc de B — et C doit être la phase la plus contrainte, sinon le qMin retient B et masque l'écart. Mes deux premières versions du test passaient pour ces deux raisons. Le mock ne savait pas produire le cas : il a fallu meterPowerOnly, avec les puissances et la tension de B mais sans les courants de B/C ni la tension de C. Troisième fois qu'ouvrir le mock révèle un défaut plutôt que de seulement débloquer un test. draw.bindingPhase dit LAQUELLE des phases borne. perPhaseBound disait qu'une phase borne, jamais laquelle : l'écran ne pouvait dire que « bridé » sans dire par quoi. DRAW_CAP la porte aussi mais n'est émis que si une charge est refusée — un cycle qui borde sans rien refuser laisserait l'écran muet. Et dans AGENTS.md : une conclusion juste posée sur un raisonnement faux survit jusqu'à ce qu'on bâtisse dessus, parce que la conclusion PROTÈGE le raisonnement de l'examen. Un résultat faux se voit ; un résultat juste ferme la question. Parade : quand une décision est écrite avec sa raison, la raison mérite son propre contrôle — c'est elle que le lot suivant utilisera. Suite : simulation 65/65, loadmodel 22/22, charging 48/48 identique à la référence, spotmarket 32/32, doxygen 0 avertissement. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015F7G5VeaPVSMeVNjiGj36p
141 lines
6.0 KiB
C++
141 lines
6.0 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 "rootmeter.h"
|
|
|
|
#include "integrations/thing.h"
|
|
|
|
#include <QLoggingCategory>
|
|
Q_DECLARE_LOGGING_CATEGORY(dcNymeaEnergy)
|
|
|
|
RootMeter::RootMeter(Thing *parent)
|
|
: QObject{parent},
|
|
m_thing(parent)
|
|
{
|
|
connect(m_thing, &Thing::stateValueChanged, this, [=](const StateTypeId &stateTypeId, const QVariant &value){
|
|
if (m_thing->thingClass().getStateType(stateTypeId).name() == "currentPower") {
|
|
emit currentPowerChanged(value.toDouble());
|
|
}
|
|
});
|
|
}
|
|
|
|
double RootMeter::currentPower() const
|
|
{
|
|
return m_thing->stateValue("currentPower").toDouble();
|
|
}
|
|
|
|
double RootMeter::currentPowerPhaseA() const
|
|
{
|
|
return m_thing->stateValue("currentPowerPhaseA").toDouble();
|
|
}
|
|
|
|
double RootMeter::currentPowerPhaseB() const
|
|
{
|
|
return m_thing->stateValue("currentPowerPhaseB").toDouble();
|
|
}
|
|
|
|
double RootMeter::currentPowerPhaseC() const
|
|
{
|
|
return m_thing->stateValue("currentPowerPhaseC").toDouble();
|
|
}
|
|
|
|
double RootMeter::currentPhaseA() const
|
|
{
|
|
return m_thing->stateValue("currentPhaseA").toDouble();
|
|
}
|
|
|
|
double RootMeter::currentPhaseB() const
|
|
{
|
|
return m_thing->stateValue("currentPhaseB").toDouble();
|
|
}
|
|
|
|
double RootMeter::currentPhaseC() const
|
|
{
|
|
return m_thing->stateValue("currentPhaseC").toDouble();
|
|
}
|
|
|
|
double RootMeter::calculateAllowanceAmpere(Electricity::Phases phases, uint phasePowerLimit)
|
|
{
|
|
double allowance = phasePowerLimit;
|
|
|
|
// [upstream-fix] La tension est LOCALE À CHAQUE PHASE. Elle était déclarée une seule fois
|
|
// ici et mutée par le bloc de la phase B, si bien que la phase C — quand elle ne publie pas
|
|
// sa propre tension — était calculée avec celle de B. Démontré : B à 200 V et C à 4 000 W
|
|
// donnaient 5,0 A de marge au lieu de 7,6.
|
|
//
|
|
// Le défaut n'était pas atteignable depuis la phase A : l'interface `energymeter` rend
|
|
// `currentPhaseA` et `voltagePhaseA` OBLIGATOIRES, donc la branche « puissance ÷ tension »
|
|
// de A est morte par construction. Seules B et C sont optionnelles, et la fuite allait donc
|
|
// de B vers C — plus étroit que « la tension de A sert à calculer B », mais bien réel.
|
|
auto tensionDe = [this](const char *nomTension) {
|
|
return m_thing->hasState(nomTension) ? m_thing->stateValue(nomTension).toDouble() : 230.0;
|
|
};
|
|
|
|
if (phases.testFlag(Electricity::PhaseA) || phases == Electricity::PhaseNone) {
|
|
if (m_thing->hasState("currentPhaseA")) {
|
|
allowance = qMin(allowance, 1.0 * phasePowerLimit - m_thing->stateValue("currentPhaseA").toDouble());
|
|
} else if (m_thing->hasState("currentPowerPhaseA")) {
|
|
allowance = qMin(allowance, 1.0 * phasePowerLimit
|
|
- (m_thing->stateValue("currentPowerPhaseA").toDouble() / tensionDe("voltagePhaseA")));
|
|
} else {
|
|
// No currentPhaseA state... Assume single phase and deduct from currentPower
|
|
double current = m_thing->stateValue("currentPower").toDouble() / 230;
|
|
allowance = qMin(allowance, 1.0 * phasePowerLimit - current);
|
|
}
|
|
}
|
|
if (phases.testFlag(Electricity::PhaseB) || phases == Electricity::PhaseNone) {
|
|
if (m_thing->hasState("currentPhaseB")) {
|
|
// [upstream-fix] Lire l'état qu'on vient de tester. Cette branche testait
|
|
// currentPhaseB (ampères) et lisait currentPowerPhaseB/230 (watts) — asymétrique
|
|
// avec la phase A, et surtout AVEUGLE sur un compteur qui publie les courants
|
|
// sans les puissances par phase : la lecture y rendait 0, donc la place libre
|
|
// entière, donc aucun écrêtage.
|
|
allowance = qMin(allowance, 1.0 * phasePowerLimit - m_thing->stateValue("currentPhaseB").toDouble());
|
|
} else if (m_thing->hasState("currentPowerPhaseB")) {
|
|
allowance = qMin(allowance, 1.0 * phasePowerLimit
|
|
- (m_thing->stateValue("currentPowerPhaseB").toDouble() / tensionDe("voltagePhaseB")));
|
|
} else {
|
|
qCDebug(dcNymeaEnergy()) << "Asked to calculate allowence for Phase B on a single phase root meter.";
|
|
}
|
|
}
|
|
|
|
if (phases.testFlag(Electricity::PhaseC) || phases == Electricity::PhaseNone) {
|
|
if (m_thing->hasState("currentPhaseC")) {
|
|
// [upstream-fix] Lire l'état qu'on vient de tester. Cette branche testait
|
|
// currentPhaseC (ampères) et lisait currentPowerPhaseC/230 (watts) — asymétrique
|
|
// avec la phase A, et surtout AVEUGLE sur un compteur qui publie les courants
|
|
// sans les puissances par phase : la lecture y rendait 0, donc la place libre
|
|
// entière, donc aucun écrêtage.
|
|
allowance = qMin(allowance, 1.0 * phasePowerLimit - m_thing->stateValue("currentPhaseC").toDouble());
|
|
} else if (m_thing->hasState("currentPowerPhaseC")) {
|
|
allowance = qMin(allowance, 1.0 * phasePowerLimit
|
|
- (m_thing->stateValue("currentPowerPhaseC").toDouble() / tensionDe("voltagePhaseC")));
|
|
} else {
|
|
qCDebug(dcNymeaEnergy()) << "Asked to calculate allowence for Phase C on a single phase root meter.";
|
|
}
|
|
}
|
|
|
|
return allowance;
|
|
}
|