Site: remove root circuit configuration (#14244)

This commit is contained in:
andig 2024-06-16 18:32:54 +02:00 • committed by GitHub
parent 477141e7f1
commit 68e66af64f
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 100 additions and 46 deletions

View file

@ -22,7 +22,6 @@ import (
"github.com/evcc-io/evcc/cmd/shutdown"
"github.com/evcc-io/evcc/core"
"github.com/evcc-io/evcc/core/keys"
"github.com/evcc-io/evcc/core/site"
"github.com/evcc-io/evcc/hems"
"github.com/evcc-io/evcc/meter"
"github.com/evcc-io/evcc/provider/golang"
@ -914,7 +913,7 @@ func configureSiteAndLoadpoints(conf *globalconfig.All) (*core.Site, error) {
}
if len(config.Circuits().Devices()) > 0 {
if err := validateCircuits(site, loadpoints); err != nil {
if err := validateCircuits(loadpoints); err != nil {
return nil, &ClassError{ClassCircuit, err}
}
}
@ -922,26 +921,39 @@ func configureSiteAndLoadpoints(conf *globalconfig.All) (*core.Site, error) {
return site, nil
}
func validateCircuits(site site.API, loadpoints []*core.Loadpoint) error {
func validateCircuits(loadpoints []*core.Loadpoint) error {
var hasRoot bool
CONTINUE:
for _, dev := range config.Circuits().Devices() {
instance := dev.Instance()
if instance.HasMeter() || site.GetCircuit() == instance {
continue
isRoot := instance.GetParent() == nil
if isRoot {
if hasRoot {
return errors.New("multiple root circuits")
}
hasRoot = true
}
for _, lp := range loadpoints {
if lp.GetCircuit() == instance {
if isRoot {
return fmt.Errorf("root circuit must not be assigned to loadpoint %s", lp.Title())
}
continue CONTINUE
}
}
return fmt.Errorf("circuit %s has no meter or loadpoint assigned", dev.Config().Name)
if !isRoot && !instance.HasMeter() {
return fmt.Errorf("circuit %s has no meter and no loadpoint assigned", dev.Config().Name)
}
}
if site.GetCircuit() == nil {
return errors.New("site has no circuit")
if !hasRoot {
return errors.New("missing root circuit")
}
return nil
@ -957,10 +969,6 @@ func configureSite(conf map[string]interface{}, loadpoints []*core.Loadpoint, ta
return nil, fmt.Errorf("failed configuring site: %w", err)
}
if len(config.Circuits().Devices()) > 0 && site.GetCircuit() == nil {
return nil, errors.New("site has no circuit")
}
return site, nil
}

View file

@ -6,10 +6,7 @@ import (
"github.com/evcc-io/evcc/api"
"github.com/evcc-io/evcc/api/globalconfig"
"github.com/evcc-io/evcc/core"
"github.com/evcc-io/evcc/meter"
"github.com/evcc-io/evcc/server/db"
"github.com/evcc-io/evcc/tariff"
"github.com/evcc-io/evcc/util/config"
"github.com/stretchr/testify/suite"
"go.uber.org/mock/gomock"
@ -39,7 +36,8 @@ func (suite *circuitsTestSuite) TestCircuitConf() {
var conf globalconfig.All
viper.SetConfigType("yaml")
suite.Require().NoError(viper.ReadConfig(strings.NewReader(`circuits:
suite.Require().NoError(viper.ReadConfig(strings.NewReader(`
circuits:
- name: master
maxPower: 10000
- name: slave
@ -63,11 +61,43 @@ loadpoints:
lps, err := configureLoadpoints(conf)
suite.Require().NoError(err)
suite.Require().Len(lps, 1)
suite.Require().NotNil(lps[0].GetCircuit())
}
func (suite *circuitsTestSuite) TestLoadpointMissingCircuitError() {
func (suite *circuitsTestSuite) TestCircuitMissingLoadpoint() {
var conf globalconfig.All
viper.SetConfigType("yaml")
suite.Require().NoError(viper.ReadConfig(strings.NewReader(`
circuits:
- name: master
- name: slave
parent: master
loadpoints:
- charger: test
`)))
suite.Require().NoError(viper.UnmarshalExact(&conf))
suite.Require().NoError(configureCircuits(conf.Circuits))
suite.Require().Len(config.Circuits().Devices(), 2)
suite.Require().False(config.Circuits().Devices()[0].Instance().HasMeter())
// empty charger
suite.Require().NoError(config.Chargers().Add(config.NewStaticDevice(config.Named{
Name: "test",
}, api.Charger(nil))))
lps, err := configureLoadpoints(conf)
suite.Require().NoError(err)
// circuit without device
err = validateCircuits(lps)
suite.Require().Error(err)
suite.Require().Equal("circuit slave has no meter and no loadpoint assigned", err.Error())
}
func (suite *circuitsTestSuite) TestMissingRootCircuit() {
var conf globalconfig.All
viper.SetConfigType("yaml")
@ -94,47 +124,50 @@ loadpoints:
lps, err := configureLoadpoints(conf)
suite.Require().NoError(err)
site := core.NewSite()
circuit.EXPECT().HasMeter().Return(false)
suite.Require().Error(validateCircuits(site, lps))
// root circuit
circuit.EXPECT().GetParent().Return(nil)
circuit.EXPECT().HasMeter().Return(true)
suite.Require().NoError(validateCircuits(lps))
// no root circuit
circuit.EXPECT().GetParent().Return(circuit)
// circuit.EXPECT().HasMeter().Return(true)
err = validateCircuits(lps)
suite.Require().Error(err)
suite.Require().Equal("missing root circuit", err.Error())
}
func (suite *circuitsTestSuite) TestSiteMissingCircuitError() {
func (suite *circuitsTestSuite) TestLoadpointUsingRootCircuit() {
var conf globalconfig.All
viper.SetConfigType("yaml")
suite.Require().NoError(viper.ReadConfig(strings.NewReader(`
loadpoints:
- charger: test
site:
meters:
grid: grid
circuit: root
`)))
suite.Require().NoError(viper.UnmarshalExact(&conf))
lps := []*core.Loadpoint{
new(core.Loadpoint),
}
ctrl := gomock.NewController(suite.T())
circuit := api.NewMockCircuit(ctrl)
// mock circuit
suite.Require().NoError(config.Circuits().Add(config.NewStaticDevice(config.Named{
Name: "test",
}, api.Circuit(nil))))
// mock meter
m, _ := meter.NewConfigurable(func() (float64, error) {
return 0, nil
})
suite.Require().NoError(config.Meters().Add(config.NewStaticDevice(config.Named{
Name: "grid",
}, api.Meter(m))))
Name: "root",
}, api.Circuit(circuit))))
// mock charger
suite.Require().NoError(config.Chargers().Add(config.NewStaticDevice(config.Named{
Name: "test",
}, api.Charger(nil))))
_, err := configureSite(conf.Site, lps, new(tariff.Tariffs))
lps, err := configureLoadpoints(conf)
suite.Require().NoError(err)
// root circuit
circuit.EXPECT().GetParent().Return(nil)
err = validateCircuits(lps)
suite.Require().Error(err)
suite.Require().Equal("root circuit must not be assigned to loadpoint ", err.Error())
}

15
core/circuit/config.go Normal file
View file

@ -0,0 +1,15 @@
package circuit
import (
"github.com/evcc-io/evcc/api"
"github.com/evcc-io/evcc/util/config"
)
func Root() api.Circuit {
for _, dev := range config.Circuits().Devices() {
if c := dev.Instance(); c.GetParent() == nil {
return c
}
}
return nil
}

View file

@ -13,6 +13,7 @@ import (
"github.com/cenkalti/backoff/v4"
"github.com/evcc-io/evcc/api"
"github.com/evcc-io/evcc/cmd/shutdown"
"github.com/evcc-io/evcc/core/circuit"
"github.com/evcc-io/evcc/core/coordinator"
"github.com/evcc-io/evcc/core/keys"
"github.com/evcc-io/evcc/core/loadpoint"
@ -69,7 +70,8 @@ type Site struct {
Voltage float64 `mapstructure:"voltage"` // Operating voltage. 230V for Germany.
ResidualPower float64 `mapstructure:"residualPower"` // PV meter only: household usage. Grid meter: household safety margin
Meters MetersConfig `mapstructure:"meters"` // Meter references
CircuitRef string `mapstructure:"circuit"` // Circuit reference
// TODO deprecated
CircuitRef_ string `mapstructure:"circuit"` // Circuit reference
MaxGridSupplyWhileBatteryCharging float64 `mapstructure:"maxGridSupplyWhileBatteryCharging"` // ignore battery charging if AC consumption is above this value
@ -169,12 +171,8 @@ func (site *Site) Boot(log *util.Logger, loadpoints []*Loadpoint, tariffs *tarif
}
// circuit
if site.CircuitRef != "" {
dev, err := config.Circuits().ByName(site.CircuitRef)
if err != nil {
return err
}
site.circuit = dev.Instance()
if c := circuit.Root(); c != nil {
site.circuit = c
}
// grid meter