// 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 . * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ #ifndef FRONIUSRULES_H #define FRONIUSRULES_H #include #include #include #include #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 &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 &readings); } #endif // FRONIUSRULES_H