Site: retain last battery soc when all reads fail (#31211)

This commit is contained in:
andig 2026-06-25 14:34:54 +02:00 • committed by GitHub
parent 1b748deaef
commit 518556e6b8
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 50 additions and 13 deletions

View file

@ -668,21 +668,27 @@ func (site *Site) updateBatteryMeters() {
mm[i].Controllable = new(controllable)
}
var batterySocAcc float64
var totalCapacity float64
if lo.SomeBy(mm, func(m types.Measurement) bool { return m.Capacity == nil || *m.Capacity <= 0 }) {
// any capacity is missing
batterySocAcc = sumOfSocs(mm)
totalCapacity = float64(len(site.batteryMeters))
// retain the last known soc when every battery read failed this cycle, so a
// transient meter error does not report the pack as empty (0%)
if lo.EveryBy(mm, func(m types.Measurement) bool { return m.Soc == nil }) {
site.log.WARN.Printf("battery soc: read failed, keeping last %.0f%%", site.battery.Soc)
} else {
// all capacities available - weigh soc by capacity
batterySocAcc = weightedSumOfSocs(mm)
totalCapacity = lo.SumBy(mm, func(m types.Measurement) float64 { return *m.Capacity })
}
var batterySocAcc float64
var totalCapacity float64
site.battery.Soc = math.Min(100, batterySocAcc/totalCapacity)
site.battery.Capacity = totalCapacity
if lo.SomeBy(mm, func(m types.Measurement) bool { return m.Capacity == nil || *m.Capacity <= 0 }) {
// any capacity is missing
batterySocAcc = sumOfSocs(mm)
totalCapacity = float64(len(site.batteryMeters))
} else {
// all capacities available - weigh soc by capacity
batterySocAcc = weightedSumOfSocs(mm)
totalCapacity = lo.SumBy(mm, func(m types.Measurement) float64 { return *m.Capacity })
}
site.battery.Soc = math.Min(100, batterySocAcc/totalCapacity)
site.battery.Capacity = totalCapacity
}
site.battery.Power = lo.SumBy(mm, func(m types.Measurement) float64 {
return m.Power

View file

@ -1,6 +1,7 @@
package core
import (
"errors"
"testing"
"time"
@ -11,6 +12,36 @@ import (
"go.uber.org/mock/gomock"
)
// TestBatterySocRetainOnReadError guards that a failed soc read keeps the last
// known soc instead of reporting the pack as empty (discussion #26560).
func TestBatterySocRetainOnReadError(t *testing.T) {
ctrl := gomock.NewController(t)
meter := api.NewMockMeter(ctrl)
meter.EXPECT().CurrentPower().Return(0.0, nil).AnyTimes()
battery := api.NewMockBattery(ctrl)
battery.EXPECT().Soc().Return(0.0, errors.New("read failed")).AnyTimes()
var bat api.Meter = &struct {
api.Meter
api.Battery
}{
Meter: meter,
Battery: battery,
}
site := &Site{
log: util.NewLogger("foo"),
batteryMeters: []config.Device[api.Meter]{config.NewStaticDevice(config.Named{}, bat)},
}
site.battery.Soc = 84
site.updateBatteryMeters()
assert.Equal(t, 84.0, site.battery.Soc, "soc retained when the read fails")
}
func TestApplyBatteryMode(t *testing.T) {
for _, tc := range []struct {
internal, expected api.BatteryMode