HEMS: add per-hems tests for nil statements when unconfigured or not connected (#31430)

This commit is contained in:
andig 2026-07-18 18:34:40 +02:00 • committed by GitHub
parent 5e6f1e7356
commit b6cca9ea1b
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 66 additions and 43 deletions

View file

@ -339,6 +339,11 @@ func (c *EEBus) CurtailedPercent() *int {
c.mux.RLock() c.mux.RLock()
defer c.mux.RUnlock() 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 // without a nominal reference the W limit cannot be expressed as a percent
if c.productionNominalMax <= 0 { if c.productionNominalMax <= 0 {
return nil return nil

View file

@ -6,6 +6,7 @@ import (
ucapi "github.com/enbility/eebus-go/usecases/api" ucapi "github.com/enbility/eebus-go/usecases/api"
"github.com/evcc-io/evcc/core/site" "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/db"
"github.com/evcc-io/evcc/server/eebus" "github.com/evcc-io/evcc/server/eebus"
"github.com/evcc-io/evcc/util" "github.com/evcc-io/evcc/util"
@ -168,6 +169,18 @@ func TestRun_ProductionLimitReleasedEarly(t *testing.T) {
assertProductionLimit(t, c, false) 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 // TestRun_ProductionLimitWithoutNominalMax verifies an incoming LPP limit
// errors instead of being silently ignored when productionNominalMax is unset (#31469). // errors instead of being silently ignored when productionNominalMax is unset (#31469).
func TestRun_ProductionLimitWithoutNominalMax(t *testing.T) { func TestRun_ProductionLimitWithoutNominalMax(t *testing.T) {

View file

@ -4,7 +4,9 @@ import (
"testing" "testing"
"github.com/evcc-io/evcc/core/site" "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/db"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
) )
@ -15,32 +17,35 @@ type stubSite struct {
func (s *stubSite) GetGridPower() float64 { return 0 } func (s *stubSite) GetGridPower() float64 { return 0 }
func TestFnn(t *testing.T) { func boolG(v bool) func() (bool, error) {
require.NoError(t, db.NewInstance("sqlite", ":memory:")) return func() (bool, error) { return v, nil }
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())
} }
// TestFnnNilWhenNotConfigured verifies "nil = limiting undefined": nil unless // TestCurtailmentNotConfigured verifies that without W3 no curtailment
// the relay input is configured, else valid right after NewFnn returns. // statement is made, while dimming via W4 remains available.
func TestFnnNilWhenNotConfigured(t *testing.T) { func TestCurtailmentNotConfigured(t *testing.T) {
require.NoError(t, db.NewInstance("sqlite", ":memory:")) require.NoError(t, db.NewInstance("sqlite", ":memory:"))
unconfigured, err := NewFnn(&stubSite{}, 1e3, 1e3, nil, nil, nil, nil, 0) fnn, err := NewFnn(&stubSite{}, 1e3, 1e3, nil, nil, nil, boolG(true), 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)
require.NoError(t, err) require.NoError(t, err)
require.NotNil(t, configured.MaxConsumptionPower()) assert.Nil(t, fnn.CurtailedPercent())
require.Equal(t, 0.0, *configured.MaxConsumptionPower()) assert.Nil(t, fnn.MaxProductionPower())
require.NotNil(t, configured.MaxProductionPower()) assert.Nil(t, hems.Curtailed(fnn))
require.Equal(t, 0.0, *configured.MaxProductionPower())
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))
} }

View file

@ -3,28 +3,28 @@ package relay
import ( import (
"testing" "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" "github.com/stretchr/testify/require"
) )
// stubSite implements site.API for testing — only GetGridPower is exercised. // TestCurtailmentNotConfigured verifies that relay never makes a curtailment
type stubSite struct { // statement, and that an active relay dims to maxPower.
site.API func TestCurtailmentNotConfigured(t *testing.T) {
} c := new(Relay)
func (s *stubSite) GetGridPower() float64 { return 0 } // relay never makes a curtailment statement
assert.Nil(t, c.CurtailedPercent())
// TestRelayNoNilState verifies MaxConsumptionPower is always determinable (w1 assert.Nil(t, c.MaxProductionPower())
// is mandatory) — NewRelay reads it once so the state is valid immediately. assert.Nil(t, hems.Curtailed(c))
func TestRelayNoNilState(t *testing.T) {
require.NoError(t, db.NewInstance("sqlite", ":memory:")) // active relay dims to maxPower
c.maxPower = 1e3
off := func() (bool, error) { return false, nil } require.NoError(t, c.setConsumptionLimit(c.maxPower))
c, err := NewRelay(&stubSite{}, off, nil, 1000, 0) assert.Equal(t, new(1e3), c.MaxConsumptionPower())
require.NoError(t, err) assert.Equal(t, new(true), hems.Dimmed(c))
require.NotNil(t, c.MaxConsumptionPower()) // curtailment statement unaffected by relay activity
require.Equal(t, 0.0, *c.MaxConsumptionPower()) assert.Nil(t, c.CurtailedPercent())
require.Nil(t, c.MaxProductionPower()) // scaffolding only, always nil assert.Nil(t, hems.Curtailed(c))
} }