diff --git a/hems/fnn/fnn.go b/hems/fnn/fnn.go index dd66667a9..8281382e8 100644 --- a/hems/fnn/fnn.go +++ b/hems/fnn/fnn.go @@ -23,14 +23,15 @@ func init() { // NewFromConfig creates an FNN HEMS from generic config. func NewFromConfig(ctx context.Context, other map[string]any, site site.API) (*Fnn, error) { cc := struct { - MaxPower float64 // TODO deprecated - MaxDimPower float64 - MaxCurtailPower float64 - W3 *plugin.Config - S1 *plugin.Config - S2 *plugin.Config - W4 *plugin.Config - Interval time.Duration + MaxPower float64 // TODO deprecated + MaxDimPower float64 + MaxCurtailPower float64 // TODO deprecated + ProductionNominalMax float64 + W3 *plugin.Config + S1 *plugin.Config + S2 *plugin.Config + W4 *plugin.Config + Interval time.Duration }{ Interval: 10 * time.Second, } @@ -64,30 +65,34 @@ func NewFromConfig(ctx context.Context, other map[string]any, site site.API) (*F return nil, err } - maxCurtailPower := math.Abs(cc.MaxCurtailPower) + productionNominalMax := math.Abs(cc.MaxCurtailPower) if cc.MaxPower > 0 { - maxCurtailPower = cc.MaxPower + productionNominalMax = cc.MaxPower + } + // ProductionNominalMax supersedes deprecated MaxCurtailPower/MaxPower + if cc.ProductionNominalMax > 0 { + productionNominalMax = math.Abs(cc.ProductionNominalMax) } - return NewFnn(site, math.Abs(cc.MaxDimPower), maxCurtailPower, w3G, s1G, s2G, w4G, cc.Interval) + return NewFnn(site, math.Abs(cc.MaxDimPower), productionNominalMax, w3G, s1G, s2G, w4G, cc.Interval) } -func NewFnn(site site.API, maxDimPower, maxCurtailPower float64, w3G, s1G, s2G, w4G func() (bool, error), interval time.Duration) (*Fnn, error) { +func NewFnn(site site.API, maxDimPower, productionNominalMax float64, w3G, s1G, s2G, w4G func() (bool, error), interval time.Duration) (*Fnn, error) { if w4G != nil && maxDimPower == 0 { return nil, errors.New("cannot have w4 without power limit") } c := &Fnn{ - log: util.NewLogger("fnn"), - site: site, - maxDimPower: maxDimPower, - maxCurtailPower: maxCurtailPower, - s1: s1G, - s2: s2G, - w3: w3G, - w4: w4G, - productionPercent: 100, - interval: interval, + log: util.NewLogger("fnn"), + site: site, + maxDimPower: maxDimPower, + productionNominalMax: productionNominalMax, + s1: s1G, + s2: s2G, + w3: w3G, + w4: w4G, + productionPercent: 100, + interval: interval, } // read the relays once synchronously so limits are valid as soon as NewFnn returns @@ -111,8 +116,8 @@ type Fnn struct { w4 func() (bool, error) publishFunc func() - maxDimPower float64 - maxCurtailPower float64 + maxDimPower float64 + productionNominalMax float64 smartgridConsumptionID uint smartgridProductionID uint @@ -211,7 +216,7 @@ func (c *Fnn) setProductionLimit(percent int) error { limit := 0.0 if active { - limit = float64(percent) / 100 * c.maxCurtailPower + limit = float64(percent) / 100 * c.productionNominalMax } if err := smartgrid.UpdateSession(&c.smartgridProductionID, smartgrid.Curtail, c.site.GetGridPower(), limit, active); err != nil { @@ -279,5 +284,5 @@ func (c *Fnn) MaxProductionPower() *float64 { return new(0.0) } - return new(float64(c.productionPercent) / 100 * c.maxCurtailPower) + return new(float64(c.productionPercent) / 100 * c.productionNominalMax) } diff --git a/hems/fnn/fnn_config_test.go b/hems/fnn/fnn_config_test.go new file mode 100644 index 000000000..aae58ddc7 --- /dev/null +++ b/hems/fnn/fnn_config_test.go @@ -0,0 +1,40 @@ +package fnn + +import ( + "maps" + "testing" + + "github.com/evcc-io/evcc/server/db" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +// TestPowerParamFallback verifies productionnominalmax supersedes the +// deprecated maxcurtailpower/maxpower params while both keep working. +func TestPowerParamFallback(t *testing.T) { + require.NoError(t, db.NewInstance("sqlite", ":memory:")) + + w3 := map[string]any{"source": "const", "value": "true"} + + for _, tc := range []struct { + name string + conf map[string]any + want float64 + }{ + {"new param", map[string]any{"productionnominalmax": 5000.0}, 5000}, + {"deprecated maxcurtailpower", map[string]any{"maxcurtailpower": 4000.0}, 4000}, + {"deprecated maxpower", map[string]any{"maxpower": 3000.0}, 3000}, + {"new supersedes deprecated", map[string]any{ + "productionnominalmax": 5000.0, "maxcurtailpower": 4000.0, "maxpower": 3000.0, + }, 5000}, + } { + t.Run(tc.name, func(t *testing.T) { + conf := map[string]any{"w3": w3} + maps.Copy(conf, tc.conf) + + fnn, err := NewFromConfig(t.Context(), conf, &stubSite{}) + require.NoError(t, err) + assert.Equal(t, tc.want, fnn.productionNominalMax) + }) + } +} diff --git a/templates/definition/hems/fnn-gpio.yaml b/templates/definition/hems/fnn-gpio.yaml index a92448de3..a22103f5b 100644 --- a/templates/definition/hems/fnn-gpio.yaml +++ b/templates/definition/hems/fnn-gpio.yaml @@ -22,16 +22,17 @@ params: help: en: Input pin for the W4 signal (Dimmer for controllable loads) de: Eingangspin für das W4-Signal (Dimmen der steuerbaren Verbrauchseinrichtungen) - - name: maxproductionpower + - name: productionnominalmax type: float unit: W - required: true description: en: Installed generator power (Wp) de: Installierte Generatorleistung (Wp) help: en: Rated generator/module power (Wp) of the installation. de: Gesamtnennleistung bzw. Modul- oder Generatorleistung der Anlage (Bezugsgröße nach § 9 EEG). + - name: maxproductionpower + deprecated: true - name: w3pin type: int required: true @@ -64,7 +65,7 @@ render: | source: gpio pin: {{ .w4pin }} function: read - maxcurtailpower: {{ .maxproductionpower }} + productionnominalmax: {{ or .productionnominalmax .maxproductionpower }} w3: source: gpio pin: {{ .w3pin }} diff --git a/templates/definition/hems/hemspro-gpio.yaml b/templates/definition/hems/hemspro-gpio.yaml index 93897afda..5e04c95ff 100644 --- a/templates/definition/hems/hemspro-gpio.yaml +++ b/templates/definition/hems/hemspro-gpio.yaml @@ -14,16 +14,17 @@ params: help: en: Power limit applied to the root circuit while the limit is active. de: Leistungsbegrenzung des Hauptstromkreises während die Limitierung aktiv ist. - - name: maxproductionpower + - name: productionnominalmax type: float unit: W - required: true description: en: Installed generator power (Wp) de: Installierte Generatorleistung (Wp) help: en: Rated generator/module power (Wp) of the installation. de: Gesamtnennleistung bzw. Modul- oder Generatorleistung der Anlage (Bezugsgröße nach § 9 EEG). + - name: maxproductionpower + deprecated: true render: | type: fnn maxdimpower: {{ .maxconsumptionpower }} @@ -31,7 +32,7 @@ render: | source: gpio pin: 13 function: read - maxcurtailpower: {{ .maxproductionpower }} + productionnominalmax: {{ or .productionnominalmax .maxproductionpower }} w3: source: gpio pin: 5