diff --git a/core/helper.go b/core/helper.go index 5f9fedab4..11f34dd92 100644 --- a/core/helper.go +++ b/core/helper.go @@ -3,6 +3,7 @@ package core import ( "fmt" + "github.com/evcc-io/evcc/api" "github.com/evcc-io/evcc/util/config" ) @@ -53,3 +54,22 @@ func deviceProperties[T any](dev config.Device[T]) config.Properties { } return config.Properties{} } + +// deviceTitleOrName returns device title or name +func deviceTitleOrName[T any](dev config.Device[T]) string { + if d, ok := dev.(config.ConfigurableDevice[T]); ok { + if title := d.Properties().Title; title != "" { + return title + } + } + return dev.Config().Name +} + +// circuitMaxPower returns a circuits power limit +func circuitMaxPower(circuit api.Circuit) float64 { + if circuit == nil { + return 0 + } + + return circuit.GetMaxPower() +} diff --git a/core/loadpoint.go b/core/loadpoint.go index a9766d3ef..650087ec6 100644 --- a/core/loadpoint.go +++ b/core/loadpoint.go @@ -1236,7 +1236,7 @@ func (lp *Loadpoint) fastCharging() error { maxPower1p := Voltage * lp.effectiveMaxCurrent() // load management limit active - if circuitMaxPower := lp.circuitMaxPower(); circuitMaxPower > 0 && circuitMaxPower < 1.1*maxPower1p { + if circuitMaxPower := circuitMaxPower(lp.circuit); circuitMaxPower > 0 && circuitMaxPower < 1.1*maxPower1p { phases = 1 lp.log.DEBUG.Printf("fast charging: scaled to 1p to match %.0fW max circuit power", circuitMaxPower) } diff --git a/core/loadpoint_effective.go b/core/loadpoint_effective.go index 404bc8002..10a3a92ee 100644 --- a/core/loadpoint_effective.go +++ b/core/loadpoint_effective.go @@ -217,21 +217,13 @@ func (lp *Loadpoint) EffectiveMinPower() float64 { return Voltage * lp.effectiveMinCurrent() * float64(lp.minActivePhases()) } -func (lp *Loadpoint) circuitMaxPower() float64 { - if lp.circuit == nil { - return 0 - } - - return lp.circuit.GetMaxPower() -} - // EffectiveMaxPower returns the effective max power taking vehicle capabilities, // phase scaling and load management power limits into account func (lp *Loadpoint) EffectiveMaxPower() float64 { lp.RLock() defer lp.RUnlock() - if circuitMaxPower := lp.circuitMaxPower(); circuitMaxPower > 0 { + if circuitMaxPower := circuitMaxPower(lp.circuit); circuitMaxPower > 0 { return min(lp.effectiveMaxPower(), circuitMaxPower) } diff --git a/core/site_battery.go b/core/site_battery.go index 947580c47..81e4ed943 100644 --- a/core/site_battery.go +++ b/core/site_battery.go @@ -7,6 +7,7 @@ import ( "github.com/evcc-io/evcc/api" "github.com/evcc-io/evcc/core/keys" "github.com/evcc-io/evcc/core/loadpoint" + "github.com/evcc-io/evcc/util/config" ) func batteryModeModified(mode api.BatteryMode) bool { @@ -94,7 +95,9 @@ func (site *Site) requiredBatteryMode(batteryGridChargeActive bool, rate api.Rat } // batteryMaxSocReached checks is battery has exceed max soc limit -func batteryMaxSocReached(meter api.Meter) (bool, error) { +func (site *Site) batteryMaxSocReached(dev config.Device[api.Meter]) (bool, error) { + meter := dev.Instance() + batLimiter, ok := meter.(api.BatterySocLimiter) if !ok { return false, nil @@ -110,8 +113,12 @@ func batteryMaxSocReached(meter api.Meter) (bool, error) { return false, err } - _, max := batLimiter.GetSocLimits() - return soc >= max, nil + if _, max := batLimiter.GetSocLimits(); max > 0 && max < 100 && soc >= max { + site.log.DEBUG.Printf("battery %s: limit soc reached (%.0f > %.0f)", deviceTitleOrName(dev), soc, max) + return true, nil + } + + return false, nil } // applyBatteryMode applies the mode to each battery @@ -121,6 +128,15 @@ func batteryMaxSocReached(meter api.Meter) (bool, error) { // The current soc is validated against max soc. // In case max soc is reached, hold mode is applied. func (site *Site) applyBatteryMode(mode api.BatteryMode) error { + isCharge := mode == api.BatteryCharge || mode == api.BatteryUnknown && site.batteryMode == api.BatteryCharge + + // put battery into hold mode when charging and load management limit active + if circuitMaxPower := circuitMaxPower(site.circuit); isCharge && circuitMaxPower > 0 { + // TODO do this only once + site.log.DEBUG.Printf("battery mode: load management active at %.0fW", circuitMaxPower) + mode = api.BatteryHold + } + for _, dev := range site.batteryMeters { meter := dev.Instance() @@ -129,18 +145,16 @@ func (site *Site) applyBatteryMode(mode api.BatteryMode) error { continue } - isCharge := mode == api.BatteryCharge || mode == api.BatteryUnknown && site.batteryMode == api.BatteryCharge - // validate max soc - if isCharge { - ok, err := batteryMaxSocReached(meter) + if isCharge && mode != api.BatteryHold { + ok, err := site.batteryMaxSocReached(dev) if err != nil { return err } // put battery into hold mode when soc limit reached if ok { - // TODO do this once only + // TODO do this only once mode = api.BatteryHold } }