Battery control: prevent grid charging if load management restrictions apply (#23920)

This commit is contained in:
andig 2025-09-28 17:24:54 +02:00 • committed by GitHub
parent 55fc2e40c5
commit 3c2f0dfad6
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 44 additions and 18 deletions

View file

@ -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()
}

View file

@ -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)
}

View file

@ -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)
}

View file

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