Adds a fronius vendor and a froniusConnection thing class alongside the
generic sunspec and solarEdge ones, following the SolarEdge mechanism but
none of its workarounds - no proprietary battery block and no PV
production computed by difference, since the GEN24 exposes model 160.
FroniusRules holds the vendor decisions as pure functions:
- discovery only keeps devices announcing Fronius as manufacturer;
- the byte order is the SunSpec default, not the forced big endian that
is specific to SolarEdge, and no endianness parameter is exposed;
- meterSlaveId defaults to 200, 0 meaning no meter on this connection;
- a hard 7 s floor on the poll interval. Polling faster can push the
inverter offline towards the vendor portal, and recovery needs an
AC+DC power cycle on site, so this is a guard in the code rather than
a configurable default;
- the registers the inverter accepts and then silently ignores are
refused before anything reaches the wire;
- the six HEMS battery modes map onto ChaGriSet, StorCtl_Mod, InWRte and
OutWRte, negative meaning charge;
- model 160 blocks are classified by IDStr, never by index: on a three
or four MPPT inverter the battery blocks are not in position 3 and 4.
Storages under a Fronius connection default revertTime to 300 s.
Setpoints now reread the scale factor before writing and read the written
registers back afterwards, reporting a mismatch through a new
controlHealthy state - the only defence against writes that are accepted
and then ignored without a Modbus exception.
Byte order and the revert timer semantics on model 124 are inferences, not
measurements; see specs/fronius-sunspec.md, H-2 and H-3.
127 lines
4.6 KiB
C++
127 lines
4.6 KiB
C++
// SPDX-License-Identifier: GPL-3.0-or-later
|
|
|
|
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
|
|
*
|
|
* Copyright (C) 2026, ETM-PowerSync
|
|
*
|
|
* 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 <https://www.gnu.org/licenses/>.
|
|
*
|
|
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
|
|
|
|
#ifndef FRONIUSRULES_H
|
|
#define FRONIUSRULES_H
|
|
|
|
#include <QList>
|
|
#include <QString>
|
|
#include <QStringList>
|
|
|
|
#include <sunspecdatapoint.h>
|
|
|
|
#include "sunspecstoragelogic.h"
|
|
|
|
/* Vendor specific rules for Fronius GEN24 inverters.
|
|
*
|
|
* Everything here is a pure decision: no Modbus, no thing, no timer. The
|
|
* plugin asks these functions what to do; the answers are what the unit tests
|
|
* pin down. See specs/fronius-sunspec.md.
|
|
*/
|
|
namespace FroniusRules {
|
|
|
|
/* Polling. The manufacturer's support recommends staying above seven seconds
|
|
* for third party applications. Below that, communication load can push the
|
|
* inverter offline towards the manufacturer's portal, and recovery needs an
|
|
* AC+DC power cycle on site. This floor is a guard in the code, not a
|
|
* configurable default. */
|
|
const int MinimumUpdateIntervalSeconds = 7;
|
|
const int DefaultUpdateIntervalSeconds = 10;
|
|
const int DefaultTimeoutMilliSeconds = 2000;
|
|
const int DefaultNumberOfRetries = 3;
|
|
|
|
/* Revert timer armed with every setpoint, so that a setpoint left behind by a
|
|
* stopped or crashed HEMS expires instead of holding the battery forever.
|
|
*
|
|
* WORKING HYPOTHESIS: the timer semantics are documented in the manual's
|
|
* general chapter and illustrated on model 123, not on model 124. This must be
|
|
* verified experimentally on model 124 before being frozen. If it does not
|
|
* hold, fall back to 0 plus an application level watchdog in the HEMS. */
|
|
const int DefaultRevertTimeSeconds = 300;
|
|
|
|
/* Default unit IDs. The inverter is fixed at 1 over TCP; the meter defaults to
|
|
* 200, with 201, 202... for secondary meters. 0 means "no meter on this
|
|
* connection". */
|
|
const int InverterSlaveId = 1;
|
|
const int DefaultMeterSlaveId = 200;
|
|
|
|
bool matchesManufacturer(const QStringList &manufacturers);
|
|
|
|
/* INFERRED: the byte order of the GEN24 is not documented by the manufacturer,
|
|
* so the SunSpec default is assumed. The forced big endian of the SolarEdge
|
|
* vendor is an anomaly specific to SolarEdge and must not be extended here.
|
|
* To be confirmed on the bench, then on hardware. */
|
|
SunSpecDataPoint::ByteOrder byteOrder();
|
|
|
|
int clampUpdateInterval(int requestedSeconds);
|
|
|
|
/* Registers the GEN24 accepts and then ignores without raising a Modbus
|
|
* exception. Writing them fails invisibly, so they are refused before being
|
|
* put on the wire. */
|
|
bool isWriteForbidden(const QString ®isterName);
|
|
|
|
enum BatteryMode {
|
|
BatteryModeNormal,
|
|
BatteryModeNoDischarge,
|
|
BatteryModeNoCharge,
|
|
BatteryModeFrozen,
|
|
BatteryModeForceCharge,
|
|
BatteryModeLimit
|
|
};
|
|
|
|
/* Sign convention of the manufacturer: negative is charging, positive is
|
|
* discharging. chargeLimit and dischargeLimit are percentages of WChaMax and
|
|
* are only used by the modes that need them. */
|
|
SunSpecStorageLogic::SetpointPlan planBatteryMode(BatteryMode mode,
|
|
double chargeLimit,
|
|
double dischargeLimit,
|
|
quint16 revertTime);
|
|
|
|
/* Model 160 repeating blocks are identified by their IDStr, never by their
|
|
* index: on a three or four MPPT inverter the battery blocks are not in
|
|
* position 3 and 4. */
|
|
enum BlockRole {
|
|
BlockRolePvString,
|
|
BlockRoleBatteryCharge,
|
|
BlockRoleBatteryDischarge,
|
|
BlockRoleUnknown
|
|
};
|
|
|
|
BlockRole classifyBlock(const QString &inputIdString);
|
|
|
|
struct ModuleReading {
|
|
QString inputIdString;
|
|
double dcPower;
|
|
};
|
|
|
|
/* Sum of the DC power of the PV string blocks. */
|
|
double pvPower(const QList<ModuleReading> &readings);
|
|
|
|
/* Discharge minus charge, so positive means discharging. DCW is a uint16 and
|
|
* therefore always positive: the sign is carried by whichever block is active,
|
|
* the inactive one reading zero. */
|
|
double batteryPower(const QList<ModuleReading> &readings);
|
|
|
|
}
|
|
|
|
#endif // FRONIUSRULES_H
|