sunspec: decide whether a Fronius meter connection is wanted
A GEN24 answers for the inverter on one unit ID and for the Smart Meter on another, behind the same host and port. Isolate the decision to open that second connection as a pure function, so it can be pinned down without nymea, and leave the logging of the refusal to the caller. Refuses a meter slave ID out of the 0..247 range, treats 0 as "no meter on this connection" as documented, and refuses a meter slave ID equal to the inverter one: a second socket on the same unit ID would rediscover the very same models and duplicate every child thing. That last case is reachable, since the discovery writes the scanned slave ID into the thing parameter. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
e7b751673d
commit
4f2076f504
@ -40,6 +40,23 @@ SunSpecDataPoint::ByteOrder byteOrder()
|
||||
return SunSpecDataPoint::ByteOrderLittleEndian;
|
||||
}
|
||||
|
||||
MeterConnectionDecision decideMeterConnection(int meterSlaveId, int inverterSlaveId)
|
||||
{
|
||||
if (meterSlaveId == 0)
|
||||
return MeterConnectionDisabled;
|
||||
|
||||
if (meterSlaveId < 0 || meterSlaveId > MaximumSlaveId)
|
||||
return MeterConnectionInvalidSlaveId;
|
||||
|
||||
/* Not a theoretical case: the slave ID of the thing is written by the
|
||||
* discovery from the slave ID it was asked to scan, so an inverter found
|
||||
* with a discovery slave ID of 200 ends up with both values at 200. */
|
||||
if (meterSlaveId == inverterSlaveId)
|
||||
return MeterConnectionCollidesWithInverter;
|
||||
|
||||
return MeterConnectionCreate;
|
||||
}
|
||||
|
||||
int clampUpdateInterval(int requestedSeconds)
|
||||
{
|
||||
return qMax(requestedSeconds, MinimumUpdateIntervalSeconds);
|
||||
|
||||
@ -63,6 +63,22 @@ const int DefaultRevertTimeSeconds = 300;
|
||||
* connection". */
|
||||
const int InverterSlaveId = 1;
|
||||
const int DefaultMeterSlaveId = 200;
|
||||
const int MaximumSlaveId = 247;
|
||||
|
||||
/* Whether a second SunSpecConnection should be opened for the Smart Meter, and
|
||||
* why not when it should not. The caller logs the reason; keeping it out of
|
||||
* this function is what makes the decision testable without nymea. */
|
||||
enum MeterConnectionDecision {
|
||||
MeterConnectionCreate,
|
||||
/* 0 is the documented way of saying "no meter behind this inverter". */
|
||||
MeterConnectionDisabled,
|
||||
/* A second socket on the unit ID already served by the inverter would
|
||||
* rediscover the very same models and duplicate every child thing. */
|
||||
MeterConnectionCollidesWithInverter,
|
||||
MeterConnectionInvalidSlaveId
|
||||
};
|
||||
|
||||
MeterConnectionDecision decideMeterConnection(int meterSlaveId, int inverterSlaveId);
|
||||
|
||||
bool matchesManufacturer(const QStringList &manufacturers);
|
||||
|
||||
|
||||
@ -161,6 +161,14 @@ private slots:
|
||||
void test_AC_15_battery_power_is_positive_when_discharging();
|
||||
void test_AC_15_battery_power_is_negative_when_charging();
|
||||
|
||||
// F3 - multi unit ID
|
||||
void test_F3_meter_slave_id_zero_disables_the_meter_connection();
|
||||
void test_F3_meter_slave_id_out_of_range_is_refused();
|
||||
void test_F3_meter_slave_id_equal_to_the_inverter_is_refused();
|
||||
void test_F3_meter_slave_id_creates_the_connection();
|
||||
void test_F3_meter_slave_id_is_not_marked_advanced();
|
||||
void test_F3_generic_vendors_have_no_meter_slave_id();
|
||||
|
||||
private:
|
||||
QJsonObject m_pluginJson;
|
||||
|
||||
@ -782,6 +790,86 @@ void SunSpecFroniusTest::test_AC_15_battery_power_is_negative_when_charging()
|
||||
QCOMPARE(batteryPower(readings), -600.0);
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// F3 - multi unit ID
|
||||
//
|
||||
// Le GEN24 repond pour l'onduleur sur un unit ID et pour le Smart Meter sur un
|
||||
// autre, derriere le meme host:port. La decision d'ouvrir ou non la seconde
|
||||
// connexion est isolee ici pour etre testable sans nymea ; le plugin se contente
|
||||
// de journaliser le motif du refus.
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
void SunSpecFroniusTest::test_F3_meter_slave_id_zero_disables_the_meter_connection()
|
||||
{
|
||||
using namespace FroniusRules;
|
||||
|
||||
// 0 est la facon documentee de dire "pas de compteur derriere cet onduleur".
|
||||
QCOMPARE(decideMeterConnection(0, InverterSlaveId), MeterConnectionDisabled);
|
||||
}
|
||||
|
||||
void SunSpecFroniusTest::test_F3_meter_slave_id_out_of_range_is_refused()
|
||||
{
|
||||
using namespace FroniusRules;
|
||||
|
||||
QCOMPARE(decideMeterConnection(248, InverterSlaveId), MeterConnectionInvalidSlaveId);
|
||||
QCOMPARE(decideMeterConnection(-1, InverterSlaveId), MeterConnectionInvalidSlaveId);
|
||||
|
||||
// La borne haute elle-meme reste valide.
|
||||
QCOMPARE(decideMeterConnection(MaximumSlaveId, InverterSlaveId), MeterConnectionCreate);
|
||||
}
|
||||
|
||||
void SunSpecFroniusTest::test_F3_meter_slave_id_equal_to_the_inverter_is_refused()
|
||||
{
|
||||
using namespace FroniusRules;
|
||||
|
||||
// Une seconde socket sur l'unit ID deja servi par l'onduleur redecouvrirait
|
||||
// exactement les memes modeles et dupliquerait chaque thing enfant. Le cas
|
||||
// n'est pas theorique : la decouverte ecrit dans le parametre slaveId du
|
||||
// thing l'unit ID qu'on lui a demande de scanner.
|
||||
QCOMPARE(decideMeterConnection(1, 1), MeterConnectionCollidesWithInverter);
|
||||
QCOMPARE(decideMeterConnection(200, 200), MeterConnectionCollidesWithInverter);
|
||||
}
|
||||
|
||||
void SunSpecFroniusTest::test_F3_meter_slave_id_creates_the_connection()
|
||||
{
|
||||
using namespace FroniusRules;
|
||||
|
||||
QCOMPARE(decideMeterConnection(DefaultMeterSlaveId, InverterSlaveId), MeterConnectionCreate);
|
||||
QCOMPARE(DefaultMeterSlaveId, 200);
|
||||
|
||||
// Compteurs secondaires.
|
||||
QCOMPARE(decideMeterConnection(201, InverterSlaveId), MeterConnectionCreate);
|
||||
QCOMPARE(decideMeterConnection(202, InverterSlaveId), MeterConnectionCreate);
|
||||
}
|
||||
|
||||
/* AC-7 demande meterSlaveId en "advanced". Cette propriete n'existe pas sur les
|
||||
* paramTypes de nymea 1.15.2 : ParamType::typeProperties() ne la connait pas, et
|
||||
* le verificateur de metadonnee rejette tout champ inconnu. Une metadonnee
|
||||
* rejetee, ce n'est pas un parametre mal affiche, c'est un plugin qui ne se
|
||||
* charge plus du tout.
|
||||
*
|
||||
* Ce test n'entérine pas l'ecart : il empeche qu'on le "corrige" sans mesurer.
|
||||
* Voir sunspec/DIVERGENCES.md. */
|
||||
void SunSpecFroniusTest::test_F3_meter_slave_id_is_not_marked_advanced()
|
||||
{
|
||||
const QJsonObject connection = thingClassByName("froniusConnection");
|
||||
const QJsonObject meterSlaveId = paramByName(connection, "meterSlaveId");
|
||||
QVERIFY(!meterSlaveId.isEmpty());
|
||||
|
||||
QVERIFY2(!meterSlaveId.contains("advanced"),
|
||||
"\"advanced\" n'est pas une propriete de paramType connue de nymea 1.15.2 : "
|
||||
"l'ajouter rend la metadonnee du plugin invalide et le plugin ne se charge plus. "
|
||||
"Voir sunspec/DIVERGENCES.md.");
|
||||
}
|
||||
|
||||
void SunSpecFroniusTest::test_F3_generic_vendors_have_no_meter_slave_id()
|
||||
{
|
||||
// Le multi unit ID est une particularite Fronius. Les vendors generique et
|
||||
// solarEdge gardent exactement une connexion par thing.
|
||||
QVERIFY(paramByName(thingClassByName("sunspecConnection"), "meterSlaveId").isEmpty());
|
||||
QVERIFY(paramByName(thingClassByName("solarEdgeConnection"), "meterSlaveId").isEmpty());
|
||||
}
|
||||
|
||||
QTEST_MAIN(SunSpecFroniusTest)
|
||||
|
||||
#include "tst_sunspec.moc"
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user