From dbba9103afbb31b1e3587ffb9df01e53f99fd366 Mon Sep 17 00:00:00 2001 From: premultiply Date: Thu, 7 Oct 2021 22:18:23 +0200 Subject: [PATCH] Support multiple batteries (#1702) --- core/site.go | 110 +++++++++++++++++++++++++++++++---------------- core/site_api.go | 3 +- 2 files changed, 74 insertions(+), 39 deletions(-) diff --git a/core/site.go b/core/site.go index e27fbb246..db02184ea 100644 --- a/core/site.go +++ b/core/site.go @@ -38,9 +38,9 @@ type Site struct { BufferSoC float64 `mapstructure:"bufferSoC"` // ignore battery above this SoC // meters - gridMeter api.Meter // Grid usage meter - pvMeters []api.Meter // PV generation meters - batteryMeter api.Meter // Battery charging meter + gridMeter api.Meter // Grid usage meter + pvMeters []api.Meter // PV generation meters + batteryMeters []api.Meter // Battery charging meters tariff api.Tariff // Tariff loadpoints []*LoadPoint // Loadpoints @@ -54,10 +54,11 @@ type Site struct { // MetersConfig contains the loadpoint's meter configuration type MetersConfig struct { - GridMeterRef string `mapstructure:"grid"` // Grid usage meter - PVMeterRef string `mapstructure:"pv"` // PV meter - PVMetersRef []string `mapstructure:"pvs"` // Multiple PV meters - BatteryMeterRef string `mapstructure:"battery"` // Battery charging meter + GridMeterRef string `mapstructure:"grid"` // Grid usage meter + PVMeterRef string `mapstructure:"pv"` // PV meter + PVMetersRef []string `mapstructure:"pvs"` // Multiple PV meters + BatteryMeterRef string `mapstructure:"battery"` // Battery charging meter + BatteryMetersRef []string `mapstructure:"batterys"` // Multiple Battery charging meters } // NewSiteFromConfig creates a new site @@ -96,8 +97,19 @@ func NewSiteFromConfig( site.pvMeters = append(site.pvMeters, pv) } + // multiple batteries + for _, ref := range site.Meters.BatteryMetersRef { + battery := cp.Meter(ref) + site.batteryMeters = append(site.batteryMeters, battery) + } + + // single battery if site.Meters.BatteryMeterRef != "" { - site.batteryMeter = cp.Meter(site.Meters.BatteryMeterRef) + if len(site.batteryMeters) > 0 { + return nil, errors.New("cannot have battery and batteries both") + } + battery := cp.Meter(site.Meters.BatteryMeterRef) + site.batteryMeters = append(site.batteryMeters, battery) } // configure meter from references @@ -150,7 +162,7 @@ func (site *Site) DumpConfig() { site.log.INFO.Printf(" meters: grid %s pv %s battery %s", presence[site.gridMeter != nil], presence[len(site.pvMeters) > 0], - presence[site.batteryMeter != nil], + presence[len(site.batteryMeters) > 0], ) site.publish("gridConfigured", site.gridMeter != nil) @@ -165,16 +177,18 @@ func (site *Site) DumpConfig() { } } - site.publish("batteryConfigured", site.batteryMeter != nil) - if site.batteryMeter != nil { - _, ok := site.batteryMeter.(api.Battery) - site.log.INFO.Println( - meterCapabilities("battery", site.batteryMeter), - fmt.Sprintf("soc %s", presence[ok]), - ) + site.publish("batteryConfigured", len(site.batteryMeters) > 0) + if len(site.batteryMeters) > 0 { + for i, battery := range site.batteryMeters { + _, ok := battery.(api.Battery) + site.log.INFO.Println( + meterCapabilities(fmt.Sprintf("battery %d", i), battery), + fmt.Sprintf("soc %s", presence[ok]), + ) - if ok { - site.publish("prioritySoC", site.PrioritySoC) + if ok { + site.publish("prioritySoC", site.PrioritySoC) + } } } @@ -281,8 +295,24 @@ func (site *Site) updateMeters() error { } err := retryMeter("grid", site.gridMeter, &site.gridPower) - if err == nil { - err = retryMeter("battery", site.batteryMeter, &site.batteryPower) + + if len(site.batteryMeters) > 0 { + site.batteryPower = 0 + + for id, meter := range site.batteryMeters { + var power float64 + err := retry.Do(site.updateMeter(meter, &power), retryOptions...) + + if err == nil { + site.batteryPower += power + } else { + err = fmt.Errorf("updating battery meter %d: %v", id, err) + site.log.ERROR.Println(err) + } + } + + site.log.DEBUG.Printf("battery power: %.0fW", site.batteryPower) + site.publish("batteryPower", site.batteryPower) } // currents @@ -315,26 +345,32 @@ func (site *Site) sitePower() (float64, error) { // honour battery priority batteryPower := site.batteryPower - if battery, ok := site.batteryMeter.(api.Battery); ok { - soc, err := battery.SoC() - if err != nil { - site.log.ERROR.Printf("updating battery soc: %v", err) - } else { - site.log.DEBUG.Printf("battery soc: %.0f%%", soc) - site.publish("batterySoC", math.Trunc(soc)) - site.Lock() - defer site.Unlock() - - // if battery is charging below prioritySoC give it priority - if soc < site.PrioritySoC && batteryPower < 0 { - site.log.DEBUG.Printf("giving priority to battery charging at soc: %.0f", soc) - batteryPower = 0 + if len(site.batteryMeters) > 0 { + var socs float64 + for id, battery := range site.batteryMeters { + soc, err := battery.(api.Battery).SoC() + if err != nil { + err = fmt.Errorf("updating battery soc %d: %v", id, err) + site.log.ERROR.Println(err) + } else { + site.log.DEBUG.Printf("battery soc %d: %.0f%%", id, soc) + socs += soc / float64(len(site.batteryMeters)) } - - // if battery is discharging above bufferSoC ignore it - site.batteryBuffered = batteryPower > 0 && site.BufferSoC > 0 && soc > site.BufferSoC } + site.publish("batterySoC", math.Trunc(socs)) + + site.Lock() + defer site.Unlock() + + // if battery is charging below prioritySoC give it priority + if socs < site.PrioritySoC && batteryPower < 0 { + site.log.DEBUG.Printf("giving priority to battery charging at soc: %.0f", socs) + batteryPower = 0 + } + + // if battery is discharging above bufferSoC ignore it + site.batteryBuffered = batteryPower > 0 && site.BufferSoC > 0 && socs > site.BufferSoC } sitePower := sitePower(site.gridPower, batteryPower, site.ResidualPower) diff --git a/core/site_api.go b/core/site_api.go index f8976aab4..843799cd3 100644 --- a/core/site_api.go +++ b/core/site_api.go @@ -3,7 +3,6 @@ package core import ( "errors" - "github.com/evcc-io/evcc/api" "github.com/evcc-io/evcc/core/site" ) @@ -21,7 +20,7 @@ func (site *Site) SetPrioritySoC(soc float64) error { site.Lock() defer site.Unlock() - if _, ok := site.batteryMeter.(api.Battery); !ok { + if len(site.batteryMeters) == 0 { return errors.New("battery not configured") }