From fa6f757618556cfad5abffdd16ef8519d5d42d13 Mon Sep 17 00:00:00 2001 From: andig Date: Sun, 16 Aug 2026 18:24:18 +0200 Subject: [PATCH] Revert "Optimizer: respect session energy limit when vehicle soc is unknown (#32895)" This reverts commit bb6854b1edb9be1898e84479becfef40e67ff4e0. --- core/loadpoint/api.go | 2 -- core/loadpoint/mock.go | 14 ----------- core/optimizer.md | 4 ---- core/site_optimizer.go | 27 +++++++++------------ core/site_optimizer_test.go | 48 ------------------------------------- 5 files changed, 11 insertions(+), 84 deletions(-) diff --git a/core/loadpoint/api.go b/core/loadpoint/api.go index 93a4dd36a..ef4b5b05a 100644 --- a/core/loadpoint/api.go +++ b/core/loadpoint/api.go @@ -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 diff --git a/core/loadpoint/mock.go b/core/loadpoint/mock.go index b2509b049..c156ad5a2 100644 --- a/core/loadpoint/mock.go +++ b/core/loadpoint/mock.go @@ -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() diff --git a/core/optimizer.md b/core/optimizer.md index f403699a5..c792b84cc 100644 --- a/core/optimizer.md +++ b/core/optimizer.md @@ -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. diff --git a/core/site_optimizer.go b/core/site_optimizer.go index 3952062ba..0af96ef38 100644 --- a/core/site_optimizer.go +++ b/core/site_optimizer.go @@ -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 != "" { diff --git a/core/site_optimizer_test.go b/core/site_optimizer_test.go index 446e8889c..634e90ebe 100644 --- a/core/site_optimizer_test.go +++ b/core/site_optimizer_test.go @@ -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")}