From 4ec7aa50e8ea828990fd93b6b5f622fc97dac350 Mon Sep 17 00:00:00 2001 From: Patrick Schurig Date: Fri, 31 Jul 2026 10:22:44 +0200 Subject: [PATCH] sunspec: reconcile storage states from the registers actions write onStorageBlockUpdated() fed chargingRate from wChaGra() and dischargingRate from wDisChaGra(). Those are the gradients, in percent of WChaMax per second, and read only on several inverters. The matching actions write InWRte and OutWRte, in percent of WChaMax. The user wrote one quantity and read back another. Feed the two rate states from inWRte() and outWRte(), and keep the gradients under names that cannot be mistaken for setpoints: chargeGradient and dischargeGradient, read only. enableDischarging was never reconciled either, while enableCharging was. It stayed frozen on the last written value and drifted silently as soon as the inverter changed StorCtl_Mod on its own. Reconcile both flags. The mapping moves into SunSpecStorageLogic so it can be exercised on a register dump without a running nymea. --- sunspec/integrationpluginsunspec.cpp | 17 ++++--- sunspec/integrationpluginsunspec.h | 1 + sunspec/integrationpluginsunspec.json | 18 +++++++ sunspec/sunspec.pro | 2 + sunspec/sunspecstoragelogic.cpp | 59 ++++++++++++++++++++++ sunspec/sunspecstoragelogic.h | 70 +++++++++++++++++++++++++++ 6 files changed, 161 insertions(+), 6 deletions(-) create mode 100644 sunspec/sunspecstoragelogic.cpp create mode 100644 sunspec/sunspecstoragelogic.h diff --git a/sunspec/integrationpluginsunspec.cpp b/sunspec/integrationpluginsunspec.cpp index 2ef9d3d..5c50122 100644 --- a/sunspec/integrationpluginsunspec.cpp +++ b/sunspec/integrationpluginsunspec.cpp @@ -1616,12 +1616,17 @@ void IntegrationPluginSunSpec::onStorageBlockUpdated() thing->setStateValue(sunspecStorageConnectedStateTypeId, true); thing->setStateValue(sunspecStorageVersionStateTypeId, model->commonModelInfo().versionString); - thing->setStateValue(sunspecStorageBatteryCriticalStateTypeId, storage->chaState() < 5); - thing->setStateValue(sunspecStorageBatteryLevelStateTypeId, qRound(storage->chaState())); - thing->setStateValue(sunspecStorageGridChargingStateTypeId, storage->chaGriSet() == SunSpecStorageModel::ChagrisetGrid); - thing->setStateValue(sunspecStorageEnableChargingStateTypeId, storage->storCtlMod().testFlag(SunSpecStorageModel::Storctl_modCharge)); - thing->setStateValue(sunspecStorageChargingRateStateTypeId, storage->wChaGra()); - thing->setStateValue(sunspecStorageDischargingRateStateTypeId, storage->wDisChaGra()); + const SunSpecStorageLogic::StorageStates states = SunSpecStorageLogic::readStates(storage); + + thing->setStateValue(sunspecStorageBatteryCriticalStateTypeId, states.batteryCritical); + thing->setStateValue(sunspecStorageBatteryLevelStateTypeId, states.batteryLevel); + thing->setStateValue(sunspecStorageGridChargingStateTypeId, states.gridCharging); + thing->setStateValue(sunspecStorageEnableChargingStateTypeId, states.enableCharging); + thing->setStateValue(sunspecStorageEnableDischargingStateTypeId, states.enableDischarging); + thing->setStateValue(sunspecStorageChargingRateStateTypeId, states.chargingRate); + thing->setStateValue(sunspecStorageDischargingRateStateTypeId, states.dischargingRate); + thing->setStateValue(sunspecStorageChargeGradientStateTypeId, states.chargeGradient); + thing->setStateValue(sunspecStorageDischargeGradientStateTypeId, states.dischargeGradient); switch (storage->chaSt()) { case SunSpecStorageModel::ChastOff: diff --git a/sunspec/integrationpluginsunspec.h b/sunspec/integrationpluginsunspec.h index d6a09b4..72ffc36 100644 --- a/sunspec/integrationpluginsunspec.h +++ b/sunspec/integrationpluginsunspec.h @@ -33,6 +33,7 @@ #include #include "sunspecthing.h" +#include "sunspecstoragelogic.h" #include "extern-plugininfo.h" #include diff --git a/sunspec/integrationpluginsunspec.json b/sunspec/integrationpluginsunspec.json index 6c0951c..81bf635 100644 --- a/sunspec/integrationpluginsunspec.json +++ b/sunspec/integrationpluginsunspec.json @@ -1462,6 +1462,24 @@ "writable": true, "displayNameAction": "Set discharging rate" }, + { + "id": "47035ac0-d054-491a-969d-b2aaa9168b0a", + "name": "chargeGradient", + "displayName": "Charging gradient", + "displayNameEvent": "Charging gradient changed", + "type": "double", + "unit": "Percentage", + "defaultValue": 0 + }, + { + "id": "ad00480a-54a3-498a-b937-f9943a98d453", + "name": "dischargeGradient", + "displayName": "Discharging gradient", + "displayNameEvent": "Discharging gradient changed", + "type": "double", + "unit": "Percentage", + "defaultValue": 0 + }, { "id": "fca0c151-d8fa-4d6e-a5d7-b3bc965abc09", "name": "version", diff --git a/sunspec/sunspec.pro b/sunspec/sunspec.pro index df3837a..6496657 100644 --- a/sunspec/sunspec.pro +++ b/sunspec/sunspec.pro @@ -5,10 +5,12 @@ SOURCES += \ integrationpluginsunspec.cpp \ solaredgebattery.cpp \ sunspecdiscovery.cpp \ + sunspecstoragelogic.cpp \ sunspecthing.cpp HEADERS += \ integrationpluginsunspec.h \ solaredgebattery.h \ sunspecdiscovery.h \ + sunspecstoragelogic.h \ sunspecthing.h diff --git a/sunspec/sunspecstoragelogic.cpp b/sunspec/sunspecstoragelogic.cpp new file mode 100644 index 0000000..c2b15d9 --- /dev/null +++ b/sunspec/sunspecstoragelogic.cpp @@ -0,0 +1,59 @@ +// 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. +* +* nymea is 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 is 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. If not, see . +* +* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + +#include "sunspecstoragelogic.h" + +#include + +namespace SunSpecStorageLogic { + +StorageStates readStates(SunSpecStorageModel *storage) +{ + StorageStates states; + if (!storage) + return states; + + /* InWRte / OutWRte are what the chargingRate / dischargingRate actions + * write. Reading the gradients here instead would mean the user writes one + * quantity and reads back another. */ + states.chargingRate = storage->inWRte(); + states.dischargingRate = storage->outWRte(); + + /* The gradients stay available, under names that cannot be mistaken for the + * setpoints above. */ + states.chargeGradient = storage->wChaGra(); + states.dischargeGradient = storage->wDisChaGra(); + + const SunSpecStorageModel::Storctl_modFlags controlMode = storage->storCtlMod(); + states.enableCharging = controlMode.testFlag(SunSpecStorageModel::Storctl_modCharge); + states.enableDischarging = controlMode.testFlag(SunSpecStorageModel::Storctl_modDiScharge); + + states.gridCharging = (storage->chaGriSet() == SunSpecStorageModel::ChagrisetGrid); + + states.batteryLevel = qRound(storage->chaState()); + states.batteryCritical = storage->chaState() < 5; + + return states; +} + +} diff --git a/sunspec/sunspecstoragelogic.h b/sunspec/sunspecstoragelogic.h new file mode 100644 index 0000000..815c79c --- /dev/null +++ b/sunspec/sunspecstoragelogic.h @@ -0,0 +1,70 @@ +// 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. +* +* nymea is 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 is 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. If not, see . +* +* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + +#ifndef SUNSPECSTORAGELOGIC_H +#define SUNSPECSTORAGELOGIC_H + +#include + +#include + +/* Decision logic of the storage (model 124) handling, kept free of the + * integration framework so it can be exercised without a running nymea. + * + * readStates() maps a storage block update onto the state values the plugin + * publishes. Having it in one place is what makes it possible to check that a + * state is fed from the very register the matching action writes to. + */ +namespace SunSpecStorageLogic { + +struct StorageStates { + /* Percent of WChaMax, from InWRte / OutWRte - the very registers the + * chargingRate / dischargingRate actions write to. */ + double chargingRate = 0; + double dischargingRate = 0; + + /* Percent of WChaMax per second, from WChaGra / WDisChaGra. A different + * quantity from the two above, and read only on several inverters. */ + double chargeGradient = 0; + double dischargeGradient = 0; + + /* Both flags are reconciled from StorCtl_Mod. Reconciling only one lets the + * other drift silently as soon as the inverter changes it on its own. */ + bool enableCharging = false; + bool enableDischarging = false; + + bool gridCharging = false; + + int batteryLevel = 0; + bool batteryCritical = false; +}; + +/* Value range of the InWRte / OutWRte setpoints of model 124. */ +const double MinimumRatePercent = -100.0; +const double MaximumRatePercent = 100.0; + +StorageStates readStates(SunSpecStorageModel *storage); + +} + +#endif // SUNSPECSTORAGELOGIC_H