diff --git a/core/site.go b/core/site.go index ad83a1420..abe294d17 100644 --- a/core/site.go +++ b/core/site.go @@ -715,8 +715,18 @@ func (site *Site) updateBatteryMeters() { } if bpl, ok := api.Cap[api.BatteryPowerLimiter](meter); ok && maxDischargePower >= 0 { - _, discharge := bpl.GetPowerLimits() - maxDischargePower += discharge + var empty bool + if bsl, ok := api.Cap[api.BatterySocLimiter](meter); ok { + minSoc, _ := bsl.GetSocLimits() + if mm[i].Soc != nil && *mm[i].Soc <= minSoc { + empty = true + } + } + + if !empty { + _, discharge := bpl.GetPowerLimits() + maxDischargePower += discharge + } } else { maxDischargePower = -1 // any battery without a limit disables the cap } diff --git a/core/site_battery_limit_test.go b/core/site_battery_limit_test.go index 1cad1b716..036d64ac6 100644 --- a/core/site_battery_limit_test.go +++ b/core/site_battery_limit_test.go @@ -18,6 +18,10 @@ func (m *mockBatteryPowerLimiter) GetPowerLimits() (float64, float64) { return m.charge, m.discharge } +func (m *mockBatteryPowerLimiter) Soc() (float64, error) { + return 50, nil +} + type mockMeter struct { api.Meter } @@ -26,6 +30,10 @@ func (m *mockMeter) CurrentPower() (float64, error) { return 0, nil } +func (m *mockMeter) Soc() (float64, error) { + return 50, nil +} + func TestBatteryMaxDischargePowerAggregation(t *testing.T) { site := &Site{ log: util.NewLogger("foo"), @@ -53,3 +61,54 @@ func TestBatteryMaxDischargePowerAggregation(t *testing.T) { site.updateBatteryMeters() assert.Equal(t, 5000.0, site.GetBatteryMaxDischargePower()) } + +type mockBatterySocLimiter struct { + api.Meter + soc, min, max float64 +} + +func (m *mockBatterySocLimiter) Soc() (float64, error) { + return m.soc, nil +} + +func (m *mockBatterySocLimiter) GetSocLimits() (float64, float64) { + return m.min, m.max +} + +type mockLimiter struct { + mockBatterySocLimiter + discharge float64 +} + +func (m *mockLimiter) GetPowerLimits() (float64, float64) { + return 0, m.discharge +} + +func TestBatteryMaxDischargePowerWithMinSoc(t *testing.T) { + site := &Site{ + log: util.NewLogger("foo"), + } + + // one battery empty (soc <= min), one normal + m1 := &mockLimiter{mockBatterySocLimiter: mockBatterySocLimiter{Meter: &mockMeter{}, soc: 10, min: 20}, discharge: 2000} + m2 := &mockLimiter{mockBatterySocLimiter: mockBatterySocLimiter{Meter: &mockMeter{}, soc: 50, min: 20}, discharge: 3000} + + site.batteryMeters = []config.Device[api.Meter]{ + config.NewStaticDevice[api.Meter](config.Named{Name: "bat1"}, m1), + config.NewStaticDevice[api.Meter](config.Named{Name: "bat2"}, m2), + } + + site.updateBatteryMeters() + // Only m2 should contribute + assert.Equal(t, 3000.0, site.GetBatteryMaxDischargePower()) + + // Both empty + m3 := &mockLimiter{mockBatterySocLimiter: mockBatterySocLimiter{Meter: &mockMeter{}, soc: 15, min: 20}, discharge: 3000} + site.batteryMeters = []config.Device[api.Meter]{ + config.NewStaticDevice[api.Meter](config.Named{Name: "bat1"}, m1), + config.NewStaticDevice[api.Meter](config.Named{Name: "bat3"}, m3), + } + + site.updateBatteryMeters() + assert.Equal(t, 0.0, site.GetBatteryMaxDischargePower()) +}