Skip to content

Commit

Permalink
Site: remove root circuit configuration (evcc-io#14244)
Browse files Browse the repository at this point in the history
  • Loading branch information
andig authored Jun 16, 2024
1 parent 477141e commit 68e66af
Show file tree
Hide file tree
Showing 4 changed files with 100 additions and 46 deletions.
32 changes: 20 additions & 12 deletions cmd/setup.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -914,34 +913,47 @@ 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}
}
}

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
Expand All @@ -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
}

Expand Down
87 changes: 60 additions & 27 deletions cmd/setup_circuits_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -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
Expand All @@ -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")

Expand All @@ -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 changes: 15 additions & 0 deletions core/circuit/config.go
Original file line number Diff line number Diff line change
@@ -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
}
12 changes: 5 additions & 7 deletions core/site.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -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

Expand Down Expand Up @@ -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
Expand Down

0 comments on commit 68e66af

Please sign in to comment.