From b6cca9ea1b791be9fa0ceca7240675e99d320e67 Mon Sep 17 00:00:00 2001 From: andig Date: Sat, 18 Jul 2026 18:34:40 +0200 Subject: [PATCH] HEMS: add per-hems tests for nil statements when unconfigured or not connected (#31430) --- hems/eebus/eebus.go | 5 ++++ hems/eebus/eebus_test.go | 13 +++++++++++ hems/fnn/fnn_test.go | 49 ++++++++++++++++++++++------------------ hems/relay/relay_test.go | 42 +++++++++++++++++----------------- 4 files changed, 66 insertions(+), 43 deletions(-) diff --git a/hems/eebus/eebus.go b/hems/eebus/eebus.go index 3f6c6f645..f8d6fea74 100644 --- a/hems/eebus/eebus.go +++ b/hems/eebus/eebus.go @@ -339,6 +339,11 @@ func (c *EEBus) CurtailedPercent() *int { c.mux.RLock() defer c.mux.RUnlock() + // no statement until first connected + if c.productionLimitActivated == nil { + return nil + } + // without a nominal reference the W limit cannot be expressed as a percent if c.productionNominalMax <= 0 { return nil diff --git a/hems/eebus/eebus_test.go b/hems/eebus/eebus_test.go index d36d2cae4..26d3fcc16 100644 --- a/hems/eebus/eebus_test.go +++ b/hems/eebus/eebus_test.go @@ -6,6 +6,7 @@ import ( ucapi "github.com/enbility/eebus-go/usecases/api" "github.com/evcc-io/evcc/core/site" + "github.com/evcc-io/evcc/hems/hems" "github.com/evcc-io/evcc/server/db" "github.com/evcc-io/evcc/server/eebus" "github.com/evcc-io/evcc/util" @@ -168,6 +169,18 @@ func TestRun_ProductionLimitReleasedEarly(t *testing.T) { assertProductionLimit(t, c, false) } +// TestNotConnected verifies that all api.HEMS getters make no statement while +// no upstream controlbox has connected yet. +func TestNotConnected(t *testing.T) { + c := newTestEEBus(t) // Connector never connected + + assert.Nil(t, c.MaxConsumptionPower()) + assert.Nil(t, c.MaxProductionPower()) + assert.Nil(t, c.CurtailedPercent()) + assert.Nil(t, hems.Dimmed(c)) + assert.Nil(t, hems.Curtailed(c)) +} + // TestRun_ProductionLimitWithoutNominalMax verifies an incoming LPP limit // errors instead of being silently ignored when productionNominalMax is unset (#31469). func TestRun_ProductionLimitWithoutNominalMax(t *testing.T) { diff --git a/hems/fnn/fnn_test.go b/hems/fnn/fnn_test.go index 19972b3ce..50daf38c0 100644 --- a/hems/fnn/fnn_test.go +++ b/hems/fnn/fnn_test.go @@ -4,7 +4,9 @@ import ( "testing" "github.com/evcc-io/evcc/core/site" + "github.com/evcc-io/evcc/hems/hems" "github.com/evcc-io/evcc/server/db" + "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) @@ -15,32 +17,35 @@ type stubSite struct { func (s *stubSite) GetGridPower() float64 { return 0 } -func TestFnn(t *testing.T) { - require.NoError(t, db.NewInstance("sqlite", ":memory:")) - - fnn, err := NewFnn(&stubSite{}, 1e3, 1e3, nil, nil, nil, func() (bool, error) { - return true, nil - }, 0) - require.NoError(t, err) - require.Nil(t, fnn.CurtailedPercent()) +func boolG(v bool) func() (bool, error) { + return func() (bool, error) { return v, nil } } -// TestFnnNilWhenNotConfigured verifies "nil = limiting undefined": nil unless -// the relay input is configured, else valid right after NewFnn returns. -func TestFnnNilWhenNotConfigured(t *testing.T) { +// TestCurtailmentNotConfigured verifies that without W3 no curtailment +// statement is made, while dimming via W4 remains available. +func TestCurtailmentNotConfigured(t *testing.T) { require.NoError(t, db.NewInstance("sqlite", ":memory:")) - unconfigured, err := NewFnn(&stubSite{}, 1e3, 1e3, nil, nil, nil, nil, 0) - require.NoError(t, err) - require.Nil(t, unconfigured.MaxConsumptionPower()) - require.Nil(t, unconfigured.MaxProductionPower()) - - off := func() (bool, error) { return false, nil } - configured, err := NewFnn(&stubSite{}, 1e3, 1e3, off, nil, nil, off, 0) + fnn, err := NewFnn(&stubSite{}, 1e3, 1e3, nil, nil, nil, boolG(true), 0) require.NoError(t, err) - require.NotNil(t, configured.MaxConsumptionPower()) - require.Equal(t, 0.0, *configured.MaxConsumptionPower()) - require.NotNil(t, configured.MaxProductionPower()) - require.Equal(t, 0.0, *configured.MaxProductionPower()) + assert.Nil(t, fnn.CurtailedPercent()) + assert.Nil(t, fnn.MaxProductionPower()) + assert.Nil(t, hems.Curtailed(fnn)) + + assert.NotNil(t, fnn.MaxConsumptionPower()) + assert.NotNil(t, hems.Dimmed(fnn)) +} + +// TestDimmingNotConfigured verifies that without W4 no dimming statement is +// made, while curtailment via W3 remains available. +func TestDimmingNotConfigured(t *testing.T) { + fnn, err := NewFnn(&stubSite{}, 0, 1e3, boolG(false), nil, nil, nil, 0) + require.NoError(t, err) + + assert.Nil(t, fnn.MaxConsumptionPower()) + assert.Nil(t, hems.Dimmed(fnn)) + + assert.NotNil(t, fnn.CurtailedPercent()) + assert.NotNil(t, hems.Curtailed(fnn)) } diff --git a/hems/relay/relay_test.go b/hems/relay/relay_test.go index 16c995671..dad5c53ae 100644 --- a/hems/relay/relay_test.go +++ b/hems/relay/relay_test.go @@ -3,28 +3,28 @@ package relay import ( "testing" - "github.com/evcc-io/evcc/core/site" - "github.com/evcc-io/evcc/server/db" + "github.com/evcc-io/evcc/hems/hems" + "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) -// stubSite implements site.API for testing — only GetGridPower is exercised. -type stubSite struct { - site.API -} - -func (s *stubSite) GetGridPower() float64 { return 0 } - -// TestRelayNoNilState verifies MaxConsumptionPower is always determinable (w1 -// is mandatory) — NewRelay reads it once so the state is valid immediately. -func TestRelayNoNilState(t *testing.T) { - require.NoError(t, db.NewInstance("sqlite", ":memory:")) - - off := func() (bool, error) { return false, nil } - c, err := NewRelay(&stubSite{}, off, nil, 1000, 0) - require.NoError(t, err) - - require.NotNil(t, c.MaxConsumptionPower()) - require.Equal(t, 0.0, *c.MaxConsumptionPower()) - require.Nil(t, c.MaxProductionPower()) // scaffolding only, always nil +// TestCurtailmentNotConfigured verifies that relay never makes a curtailment +// statement, and that an active relay dims to maxPower. +func TestCurtailmentNotConfigured(t *testing.T) { + c := new(Relay) + + // relay never makes a curtailment statement + assert.Nil(t, c.CurtailedPercent()) + assert.Nil(t, c.MaxProductionPower()) + assert.Nil(t, hems.Curtailed(c)) + + // active relay dims to maxPower + c.maxPower = 1e3 + require.NoError(t, c.setConsumptionLimit(c.maxPower)) + assert.Equal(t, new(1e3), c.MaxConsumptionPower()) + assert.Equal(t, new(true), hems.Dimmed(c)) + + // curtailment statement unaffected by relay activity + assert.Nil(t, c.CurtailedPercent()) + assert.Nil(t, hems.Curtailed(c)) }