Revert "Optimizer: respect session energy limit when vehicle soc is unknown (#32895)"

This reverts commit bb6854b1ed.
This commit is contained in:
andig 2026-08-16 18:24:18 +02:00
parent dfad373ab3
commit fa6f757618
5 changed files with 11 additions and 84 deletions

View file

@ -219,8 +219,6 @@ type API interface {
GetRemainingDuration() time.Duration
// GetRemainingEnergy is the remaining charge energy in kWh
GetRemainingEnergy() float64
// GetChargedEnergy returns session charge energy in Wh
GetChargedEnergy() float64
//
// vehicles

View file

@ -221,20 +221,6 @@ func (mr *MockAPIMockRecorder) GetChargePowerFlexibility(rates any) *gomock.Call
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetChargePowerFlexibility", reflect.TypeOf((*MockAPI)(nil).GetChargePowerFlexibility), rates)
}
// GetChargedEnergy mocks base method.
func (m *MockAPI) GetChargedEnergy() float64 {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "GetChargedEnergy")
ret0, _ := ret[0].(float64)
return ret0
}
// GetChargedEnergy indicates an expected call of GetChargedEnergy.
func (mr *MockAPIMockRecorder) GetChargedEnergy() *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetChargedEnergy", reflect.TypeOf((*MockAPI)(nil).GetChargedEnergy))
}
// GetChargerRef mocks base method.
func (m *MockAPI) GetChargerRef() string {
m.ctrl.T.Helper()

View file

@ -47,7 +47,3 @@ Use minimum of energy consumption cost.
- home battery or loadpoint/vehicle...
- capacity, soc and charge goals
- charge/discharge power limits and efficiency
Without vehicle capacity or soc a configured session energy limit is modelled instead:
state is the session's charged energy, goal is the limit. Loadpoints with neither are
not modelled at all- their power is added to the base load.

View file

@ -522,9 +522,8 @@ func (site *Site) optimizerRequest(battery []types.Measurement) (optimizer.Optim
continue
}
// no vehicle capacity and no session energy limit to model against:
// account for the consumption as uncontrollable load
if v := lp.GetVehicle(); v == nil || (v.Capacity() == 0 && lp.GetLimitEnergy() == 0) {
// unknown vehicle capacity: account for the consumption as uncontrollable load
if v := lp.GetVehicle(); v == nil || v.Capacity() == 0 {
unmodelled += unmodelledPower(lp)
continue
}
@ -791,22 +790,18 @@ func (site *Site) loadpointRequest(lp loadpoint.API, minLen int, firstSlotDurati
// vehicle
v := lp.GetVehicle()
capacity := v.Capacity() // kWh
soc := lp.GetSoc() // percent
// without capacity or soc there is no battery state to model, but a session energy
// limit still bounds the charge- use charged energy as state (see remainingLimitEnergy)
if limit := lp.GetLimitEnergy(); limit > 0 && (capacity == 0 || soc == 0) {
bat.SInitial = float32(lp.GetChargedEnergy()) // Wh
bat.SMax = max(bat.SInitial, float32(limit*1e3)) // prevent infeasible if limit already exceeded
} else {
maxSoc := capacity * float64(lp.EffectiveLimitSoc()) * 10 // Wh
bat.SInitial = float32(capacity * soc * 10) // Wh
bat.SMax = max(bat.SInitial, float32(maxSoc)) // prevent infeasible if current soc above maximum
maxSoc := v.Capacity() * 1e3 // Wh
if v := lp.EffectiveLimitSoc(); v > 0 {
maxSoc *= float64(v) / 100
} else if v := lp.GetLimitEnergy(); v > 0 {
maxSoc = v * 1e3
}
bat.SInitial = float32(v.Capacity() * lp.GetSoc() * 10) // Wh
bat.SMax = max(bat.SInitial, float32(maxSoc)) // prevent infeasible if current soc above maximum
detail.Type = batteryTypeVehicle
detail.Capacity = capacity
detail.Capacity = v.Capacity()
if vt := v.GetTitle(); vt != "" {
if detail.Title != "" {

View file

@ -320,54 +320,6 @@ func TestBatteryRequestSocLimitsClamp(t *testing.T) {
})
}
// charge goal for vehicles with and without known capacity/soc, see #32890
func TestLoadpointRequestChargeGoal(t *testing.T) {
site := &Site{log: util.NewLogger("foo")}
for _, tc := range []struct {
name string
capacity, soc float64 // kWh, percent
limitSoc int // percent
limitEnergy, charged float64 // kWh, Wh
wantInitial, wantSMax float32 // Wh
}{
{"soc limit", 50, 20, 80, 0, 0, 10000, 40000},
{"no capacity, energy limit", 0, 0, 100, 10, 0, 0, 10000},
{"no capacity, energy limit partially charged", 0, 0, 100, 10, 4000, 4000, 10000},
{"no capacity, limit exceeded", 0, 0, 100, 10, 11000, 11000, 11000},
{"capacity but no soc, energy limit", 50, 0, 100, 10, 0, 0, 10000},
{"capacity but no soc, no energy limit", 50, 0, 100, 0, 0, 0, 50000},
} {
t.Run(tc.name, func(t *testing.T) {
ctrl := gomock.NewController(t)
v := api.NewMockVehicle(ctrl)
v.EXPECT().Capacity().Return(tc.capacity).AnyTimes()
v.EXPECT().GetTitle().Return("").AnyTimes()
lp := loadpoint.NewMockAPI(ctrl)
lp.EXPECT().GetVehicle().Return(v).AnyTimes()
lp.EXPECT().GetSoc().Return(tc.soc).AnyTimes()
lp.EXPECT().EffectiveLimitSoc().Return(tc.limitSoc).AnyTimes()
lp.EXPECT().GetLimitEnergy().Return(tc.limitEnergy).AnyTimes()
lp.EXPECT().GetChargedEnergy().Return(tc.charged).AnyTimes()
lp.EXPECT().GetTitle().Return("lp").AnyTimes()
lp.EXPECT().EffectiveMinPower().Return(1380.0).AnyTimes()
lp.EXPECT().EffectiveMaxPower().Return(11000.0).AnyTimes()
lp.EXPECT().GetMode().Return(api.ModePV).AnyTimes()
lp.EXPECT().GetStatus().Return(api.StatusB).AnyTimes()
lp.EXPECT().GetSmartCostLimit().Return(nil).AnyTimes()
lp.EXPECT().EffectivePlanStrategy().Return(api.PlanStrategy{}).AnyTimes()
lp.EXPECT().GetPlanGoal().Return(0.0, false).AnyTimes()
req, _ := site.loadpointRequest(lp, 8, 15*time.Minute, nil)
assert.Equal(t, tc.wantInitial, req.SInitial)
assert.Equal(t, tc.wantSMax, req.SMax)
})
}
}
func TestOptimizerChargingStrategy(t *testing.T) {
site := &Site{log: util.NewLogger("foo")}