Add battery max charge/discharge power (#22944)

This commit is contained in:
andig 2025-08-25 10:46:20 +02:00 • committed by GitHub
parent 48175688b7
commit 7101f7049f
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
9 changed files with 3016 additions and 191 deletions

View file

@ -49,6 +49,11 @@ type MaxACPowerGetter interface {
MaxACPower() float64
}
// BatteryMaxPowerGetter provides max AC charge- and discharge power in W
type BatteryMaxPowerGetter interface {
GetMaxChargeDischargePower() (float64, float64)
}
// ChargeState provides current charging status
type ChargeState interface {
Status() (ChargeStatus, error)

View file

@ -49,6 +49,7 @@ var a struct {
api.BatteryCapacity
api.BatteryController
api.SocLimiter
api.BatteryMaxPowerGetter
}
func typ(i any) string {
@ -59,7 +60,7 @@ var dependents = map[string][]string{
typ(&a.Meter): {typ(&a.MeterEnergy), typ(&a.PhaseCurrents), typ(&a.PhaseVoltages), typ(&a.PhasePowers), typ(&a.MaxACPowerGetter)},
typ(&a.PhaseCurrents): {typ(&a.PhasePowers)}, // phase powers are only used to determine currents sign
typ(&a.PhaseSwitcher): {typ(&a.PhaseGetter)},
typ(&a.Battery): {typ(&a.BatteryCapacity), typ(&a.BatteryController), typ(&a.SocLimiter)},
typ(&a.Battery): {typ(&a.BatteryCapacity), typ(&a.BatteryController), typ(&a.SocLimiter), typ(&a.BatteryMaxPowerGetter)},
}
// hasIntersection returns if the slices intersect

View file

@ -22,7 +22,7 @@ import (
var (
eta = float32(0.9) // efficiency of the battery charging/discharging
batteryPower = float32(6000) // power of the battery in W
batteryPower = float32(6000) // default power of the battery in W
updated time.Time
)
@ -183,20 +183,25 @@ func (site *Site) optimizerUpdate(battery []measurement) error {
dev := site.batteryMeters[i]
bat := evopt.BatteryConfig{
CMin: 0,
CMax: batteryPower,
DMax: batteryPower,
SMin: 0,
SMax: float32(*b.Capacity * 1e3), // Wh
SInitial: float32(*b.Capacity * *b.Soc * 10), // Wh
PA: pa,
}
// TODO atm we cannot cannot control charge from grid speed
if _, ok := (dev.Instance()).(api.BatteryController); ok {
instance := dev.Instance()
if _, ok := instance.(api.BatteryController); ok {
bat.ChargeFromGrid = lo.ToPtr(true)
}
if m, ok := instance.(api.BatteryMaxPowerGetter); ok {
min, max := m.GetMaxChargeDischargePower()
bat.CMin = float32(min)
bat.CMax = float32(max)
}
req.Batteries = append(req.Batteries, bat)
details.BatteryDetails = append(details.BatteryDetails, batteryDetail{

View file

@ -14,7 +14,7 @@ func init() {
registry.AddCtx(api.Custom, NewConfigurableFromConfig)
}
//go:generate go tool decorate -f decorateMeter -b api.Meter -t "api.MeterEnergy,TotalEnergy,func() (float64, error)" -t "api.PhaseCurrents,Currents,func() (float64, float64, float64, error)" -t "api.PhaseVoltages,Voltages,func() (float64, float64, float64, error)" -t "api.PhasePowers,Powers,func() (float64, float64, float64, error)" -t "api.Battery,Soc,func() (float64, error)" -t "api.BatteryCapacity,Capacity,func() float64" -t "api.MaxACPowerGetter,MaxACPower,func() float64" -t "api.BatteryController,SetBatteryMode,func(api.BatteryMode) error"
//go:generate go tool decorate -f decorateMeter -b api.Meter -t "api.MeterEnergy,TotalEnergy,func() (float64, error)" -t "api.PhaseCurrents,Currents,func() (float64, float64, float64, error)" -t "api.PhaseVoltages,Voltages,func() (float64, float64, float64, error)" -t "api.PhasePowers,Powers,func() (float64, float64, float64, error)" -t "api.Battery,Soc,func() (float64, error)" -t "api.BatteryCapacity,Capacity,func() float64" -t "api.BatteryMaxPowerGetter,GetMaxChargeDischargePower,func() (float64, float64)" -t "api.BatteryController,SetBatteryMode,func(api.BatteryMode) error" -t "api.MaxACPowerGetter,MaxACPower,func() float64"
// NewConfigurableFromConfig creates api.Meter from config
func NewConfigurableFromConfig(ctx context.Context, other map[string]interface{}) (api.Meter, error) {
@ -26,11 +26,12 @@ func NewConfigurableFromConfig(ctx context.Context, other map[string]interface{}
pvMaxACPower `mapstructure:",squash"`
// battery
batteryCapacity `mapstructure:",squash"`
batterySocLimits `mapstructure:",squash"`
Soc *plugin.Config // optional
LimitSoc *plugin.Config // optional
BatteryMode *plugin.Config // optional
batteryCapacity `mapstructure:",squash"`
batterySocLimits `mapstructure:",squash"`
batteryChargeDischargePower `mapstructure:",squash"`
Soc *plugin.Config // optional
LimitSoc *plugin.Config // optional
BatteryMode *plugin.Config // optional
}{
batterySocLimits: batterySocLimits{
MinSoc: 20,
@ -82,7 +83,11 @@ func NewConfigurableFromConfig(ctx context.Context, other map[string]interface{}
}
}
res := m.Decorate(energyG, currentsG, voltagesG, powersG, socG, cc.batteryCapacity.Decorator(), cc.pvMaxACPower.Decorator(), batModeS)
res := m.Decorate(
energyG, currentsG, voltagesG, powersG,
socG, cc.batteryCapacity.Decorator(), cc.batteryChargeDischargePower.Decorator(), batModeS,
cc.pvMaxACPower.Decorator(),
)
return res, nil
}
@ -106,10 +111,15 @@ func (m *Meter) Decorate(
currents, voltages, powers func() (float64, float64, float64, error),
batterySoc func() (float64, error),
batteryCapacity func() float64,
maxACPower func() float64,
batteryChargeDischargePower func() (float64, float64),
setBatteryMode func(api.BatteryMode) error,
maxACPower func() float64,
) api.Meter {
return decorateMeter(m, totalEnergy, currents, voltages, powers, batterySoc, batteryCapacity, maxACPower, setBatteryMode)
return decorateMeter(m,
totalEnergy, currents, voltages, powers,
batterySoc, batteryCapacity, batteryChargeDischargePower, setBatteryMode,
maxACPower,
)
}
// CurrentPower implements the api.Meter interface

View file

@ -70,7 +70,7 @@ func NewMovingAverageFromConfig(ctx context.Context, other map[string]interface{
powers = m.Powers
}
return meter.Decorate(totalEnergy, currents, voltages, powers, batterySoc, cc.Meter.batteryCapacity.Decorator(), nil, nil), nil
return meter.Decorate(totalEnergy, currents, voltages, powers, batterySoc, cc.Meter.batteryCapacity.Decorator(), nil, nil, nil), nil
}
type MovingAverage struct {

File diff suppressed because it is too large Load diff

View file

@ -139,7 +139,7 @@ func NewOpenWBFromConfig(other map[string]interface{}) (api.Meter, error) {
return nil, err
}
res := m.Decorate(nil, currents, nil, nil, soc, capacity, nil, nil)
res := m.Decorate(nil, currents, nil, nil, soc, capacity, nil, nil, nil)
return res, nil
}

View file

@ -18,6 +18,23 @@ func (m *batteryCapacity) Decorator() func() float64 {
}
}
type batteryChargeDischargePower struct {
MaxChargePower float64
MaxDischargePower float64
}
// var _ api.BatteryMaxPowerGetter = (*batteryChargeDischargePower)(nil)
// Decorator returns the max charge/discharge power decorator
func (m *batteryChargeDischargePower) Decorator() func() (float64, float64) {
if m.MaxChargePower == 0 || m.MaxDischargePower == 0 {
return nil
}
return func() (float64, float64) {
return m.MaxChargePower, m.MaxDischargePower
}
}
type batterySocLimits struct {
MinSoc, MaxSoc float64
}

View file

@ -337,11 +337,6 @@ params:
en: For forced charging of the battery.
de: Für erzwungenes Laden des Speichers.
advanced: true
- name: chargepower
description:
en: Maximum charge power
de: Maximale Ladeleistung
deprecated: true
- name: maxchargerate
unit: "%"
type: int