From c9876e38aec90631331dea4de79759dad8f2b3ef Mon Sep 17 00:00:00 2001 From: andig Date: Wed, 11 Mar 2026 11:49:27 +0100 Subject: [PATCH] Migrate optimizer (BC) (#28088) --- core/site_optimizer.go | 50 ++++++++++++++++++------------------- core/site_optimizer_test.go | 6 ++--- go.mod | 2 +- go.sum | 4 +-- 4 files changed, 31 insertions(+), 31 deletions(-) diff --git a/core/site_optimizer.go b/core/site_optimizer.go index b31164999..732e99c2a 100644 --- a/core/site_optimizer.go +++ b/core/site_optimizer.go @@ -11,7 +11,6 @@ import ( "sync/atomic" "time" - evopt "github.com/andig/evopt/client" "github.com/evcc-io/evcc/api" "github.com/evcc-io/evcc/core/keys" "github.com/evcc-io/evcc/core/loadpoint" @@ -21,6 +20,7 @@ import ( "github.com/evcc-io/evcc/util/config" "github.com/evcc-io/evcc/util/request" "github.com/evcc-io/evcc/util/sponsor" + optimizer "github.com/evcc-io/optimizer/client" "github.com/jinzhu/now" "github.com/samber/lo" "golang.org/x/exp/constraints" @@ -110,7 +110,7 @@ func (site *Site) optimizerUpdateAsync() { } func (site *Site) optimizerUpdate(battery []types.Measurement) error { - uri := os.Getenv("EVOPT_URI") + uri := os.Getenv("OPTIMIZER_URI") if uri == "" { return nil } @@ -156,14 +156,14 @@ func (site *Site) optimizerUpdate(battery []types.Measurement) error { ft = prorate(scaleAndPrune(solarEnergy, 1, minLen), firstSlotDuration) } - req := evopt.OptimizationInput{ - Strategy: evopt.OptimizerStrategy{ - ChargingStrategy: evopt.OptimizerStrategyChargingStrategyChargeBeforeExport, // AttenuateGridPeaks - DischargingStrategy: evopt.OptimizerStrategyDischargingStrategyDischargeBeforeImport, + req := optimizer.OptimizationInput{ + Strategy: optimizer.OptimizerStrategy{ + ChargingStrategy: optimizer.OptimizerStrategyChargingStrategyChargeBeforeExport, // AttenuateGridPeaks + DischargingStrategy: optimizer.OptimizerStrategyDischargingStrategyDischargeBeforeImport, }, EtaC: eta, EtaD: eta, - TimeSeries: evopt.TimeSeries{ + TimeSeries: optimizer.TimeSeries{ Dt: dt, Gt: prorate(gt, firstSlotDuration), Ft: ft, @@ -181,14 +181,14 @@ func (site *Site) optimizerUpdate(battery []types.Measurement) error { if site.circuit != nil { if pMaxImp := site.circuit.GetMaxPower(); pMaxImp > 0 { - req.Grid = evopt.GridConfig{ + req.Grid = optimizer.GridConfig{ // hard grid import limit if no price penalty is set by PrcPExcImp PMaxImp: float32(pMaxImp), } } } - add := func(battery evopt.BatteryConfig, detail batteryDetail) { + add := func(battery optimizer.BatteryConfig, detail batteryDetail) { battery.PA = pa req.Batteries = append(req.Batteries, battery) details.BatteryDetails = append(details.BatteryDetails, detail) @@ -220,7 +220,7 @@ func (site *Site) optimizerUpdate(battery []types.Measurement) error { httpClient := request.NewClient(site.log) httpClient.Timeout = 30 * time.Second - apiClient, err := evopt.NewClientWithResponses(uri, evopt.WithHTTPClient(httpClient)) + apiClient, err := optimizer.NewClientWithResponses(uri, optimizer.WithHTTPClient(httpClient)) if err != nil { return err } @@ -240,9 +240,9 @@ func (site *Site) optimizerUpdate(battery []types.Measurement) error { } site.publish("evopt", struct { - Req evopt.OptimizationInput `json:"req"` - Res evopt.OptimizationResult `json:"res"` - Details requestDetails `json:"details"` + Req optimizer.OptimizationInput `json:"req"` + Res optimizer.OptimizationResult `json:"res"` + Details requestDetails `json:"details"` }{ Req: req, Res: *resp.JSON200, @@ -275,7 +275,7 @@ func (site *Site) optimizerUpdate(battery []types.Measurement) error { return nil } -func (site *Site) addBatteryForecastTotals(req []evopt.BatteryConfig, resp []evopt.BatteryResult) *types.BatteryForecast { +func (site *Site) addBatteryForecastTotals(req []optimizer.BatteryConfig, resp []optimizer.BatteryResult) *types.BatteryForecast { if len(resp) == 0 || len(resp[0].StateOfCharge) == 0 { return nil } @@ -303,8 +303,8 @@ func (site *Site) addBatteryForecastTotals(req []evopt.BatteryConfig, resp []evo return &res } -func (site *Site) batteryForecastFullAndEmptySlots(req []evopt.BatteryConfig, resp []evopt.BatteryResult) (int, int) { - matchSlot := func(fun func(soc float32, bat evopt.BatteryConfig) bool) int { +func (site *Site) batteryForecastFullAndEmptySlots(req []optimizer.BatteryConfig, resp []optimizer.BatteryResult) (int, int) { + matchSlot := func(fun func(soc float32, bat optimizer.BatteryConfig) bool) int { NEXT: for i := range resp[0].StateOfCharge { for batIdx := range req { @@ -317,18 +317,18 @@ func (site *Site) batteryForecastFullAndEmptySlots(req []evopt.BatteryConfig, re return -1 } - fullSlot := matchSlot(func(soc float32, bat evopt.BatteryConfig) bool { + fullSlot := matchSlot(func(soc float32, bat optimizer.BatteryConfig) bool { return soc >= bat.SMax }) - emptySlot := matchSlot(func(soc float32, bat evopt.BatteryConfig) bool { + emptySlot := matchSlot(func(soc float32, bat optimizer.BatteryConfig) bool { return soc <= bat.SMin }) return fullSlot, emptySlot } -func (site *Site) loadpointRequest(lp loadpoint.API, minLen int, firstSlotDuration time.Duration, grid api.Rates) (evopt.BatteryConfig, batteryDetail) { - bat := evopt.BatteryConfig{ +func (site *Site) loadpointRequest(lp loadpoint.API, minLen int, firstSlotDuration time.Duration, grid api.Rates) (optimizer.BatteryConfig, batteryDetail) { + bat := optimizer.BatteryConfig{ ChargeFromGrid: true, CMin: float32(lp.EffectiveMinPower()), CMax: float32(lp.EffectiveMaxPower()), @@ -407,8 +407,8 @@ func (site *Site) loadpointRequest(lp loadpoint.API, minLen int, firstSlotDurati return bat, detail } -func (site *Site) batteryRequest(dev config.Device[api.Meter], b types.Measurement, grid api.Rates, minLen int, firstSlotDuration time.Duration) (evopt.BatteryConfig, batteryDetail) { - bat := evopt.BatteryConfig{ +func (site *Site) batteryRequest(dev config.Device[api.Meter], b types.Measurement, grid api.Rates, minLen int, firstSlotDuration time.Duration) (optimizer.BatteryConfig, batteryDetail) { + bat := optimizer.BatteryConfig{ CMax: batteryPower, DMax: batteryPower, SInitial: float32(*b.Capacity * *b.Soc * 10), // Wh @@ -633,7 +633,7 @@ func scaleAndPrune(rates api.Rates, div float64, maxLen int) []float32 { return res } -func (site *Site) applyPlanGoal(lp loadpoint.API, bat *evopt.BatteryConfig, minLen int) { +func (site *Site) applyPlanGoal(lp loadpoint.API, bat *optimizer.BatteryConfig, minLen int) { goal, socBased := lp.GetPlanGoal() if goal <= 0 { return @@ -719,8 +719,8 @@ func (site *Site) applyBatteryGridChargeLimit(cMax float32, grid api.Rates, minL } // apiError extracts error message from optimizer API response -func apiError(resp *evopt.PostOptimizeChargeScheduleResponse) error { - var errObj *evopt.Error +func apiError(resp *optimizer.PostOptimizeChargeScheduleResponse) error { + var errObj *optimizer.Error switch resp.StatusCode() { case http.StatusBadRequest: errObj = resp.JSON400 diff --git a/core/site_optimizer_test.go b/core/site_optimizer_test.go index 43a8ee58a..70fda2362 100644 --- a/core/site_optimizer_test.go +++ b/core/site_optimizer_test.go @@ -4,12 +4,12 @@ import ( "testing" "time" - evopt "github.com/andig/evopt/client" "github.com/benbjohnson/clock" "github.com/evcc-io/evcc/api" "github.com/evcc-io/evcc/core/loadpoint" "github.com/evcc-io/evcc/core/metrics" "github.com/evcc-io/evcc/server/db" + optimizer "github.com/evcc-io/optimizer/client" "github.com/jinzhu/now" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" @@ -114,7 +114,7 @@ func TestLoadpointProfile(t *testing.T) { func TestBatteryForecastTotals(t *testing.T) { site := new(Site) - req := []evopt.BatteryConfig{ + req := []optimizer.BatteryConfig{ {SMax: 80}, {SMax: 80}, } @@ -164,7 +164,7 @@ func TestBatteryForecastTotals(t *testing.T) { }, } { t.Run(tc.name, func(t *testing.T) { - resp := []evopt.BatteryResult{ + resp := []optimizer.BatteryResult{ {StateOfCharge: tc.bat1}, {StateOfCharge: tc.bat2}, } diff --git a/go.mod b/go.mod index c34d3a583..33d665e52 100644 --- a/go.mod +++ b/go.mod @@ -8,7 +8,6 @@ require ( github.com/Masterminds/sprig/v3 v3.3.0 github.com/PuerkitoBio/goquery v1.11.0 github.com/WulfgarW/sensonet v0.0.7 - github.com/andig/evopt v0.0.0-20260202080401-5c7f3dae3896 github.com/andig/go-powerwall v0.2.1-0.20230808194509-dd70cdb6e140 github.com/andig/gosunspec v0.0.0-20240918203654-860ce51d602b github.com/andig/mbserver v0.0.0-20230310211055-1d29cbb5820e @@ -33,6 +32,7 @@ require ( github.com/enbility/ship-go v0.6.0 github.com/enbility/spine-go v0.7.0 github.com/evcc-io/openapi-mcp v0.6.0 + github.com/evcc-io/optimizer v0.0.0-20260310135618-de48ea1cf9f6 github.com/evcc-io/rct v0.1.2-0.20260222151045-de2e4108c99c github.com/evcc-io/tesla-proxy-client v0.0.0-20240221194046-4168b3759701 github.com/fatih/structs v1.1.0 diff --git a/go.sum b/go.sum index d78a766c3..a7a777665 100644 --- a/go.sum +++ b/go.sum @@ -33,8 +33,6 @@ github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuy github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d/go.mod h1:rBZYJk541a8SKzHPHnH3zbiI+7dagKZ0cgpgrD7Fyho= -github.com/andig/evopt v0.0.0-20260202080401-5c7f3dae3896 h1:veq0GvT15cv7124nu4XvhsyFIrYcTw6E2/FUVs+AXRY= -github.com/andig/evopt v0.0.0-20260202080401-5c7f3dae3896/go.mod h1:C05cTe1ffvaip2xNNHMJSy/OtGT/NH1sXQgmV/7be8k= github.com/andig/go-powerwall v0.2.1-0.20230808194509-dd70cdb6e140 h1:C93T8vg7CN4Q4BBDbBMOvBoTYBA+CaBEvlY2SZF8aa0= github.com/andig/go-powerwall v0.2.1-0.20230808194509-dd70cdb6e140/go.mod h1:Xk09mD+7RTCuuHMX5RxlqgEUeh9oZdIX0rL0qiY6l/4= github.com/andig/gosunspec v0.0.0-20240918203654-860ce51d602b h1:81UMfM949I7StrRay7YDUZazY8M1u/JHkzwcFEjiilQ= @@ -201,6 +199,8 @@ github.com/evcc-io/ocpp-go v0.0.0-20251212212612-b7f92ee0443b h1:5vFr7wOwoi5ylyb github.com/evcc-io/ocpp-go v0.0.0-20251212212612-b7f92ee0443b/go.mod h1:2kcukDdhui4u730VfnYVWuwzDLgw+mBRGDir/QAyBhg= github.com/evcc-io/openapi-mcp v0.6.0 h1:cq8CWG+3gNQ9ID0ZE8MoDHZxgdJmBZ1fFyt5xAfTRfA= github.com/evcc-io/openapi-mcp v0.6.0/go.mod h1:YyOx4zwr6xVUcchAETHERZ+75cZMIrdcjVIZFwBlE1Q= +github.com/evcc-io/optimizer v0.0.0-20260310135618-de48ea1cf9f6 h1:W4j2gpIs5d5c1lLS4cD0/rsZYuluPS3KJoGXXANBBYY= +github.com/evcc-io/optimizer v0.0.0-20260310135618-de48ea1cf9f6/go.mod h1:cHMeq6GfoXQ8LxqY4LH1wn/hrgYEn+ka5RPI1CF37SY= github.com/evcc-io/rct v0.1.2-0.20260222151045-de2e4108c99c h1:bmtzp2QjwIylg7QuAAxclew4rd/G6Pub5MkTkUTjV/w= github.com/evcc-io/rct v0.1.2-0.20260222151045-de2e4108c99c/go.mod h1:n6MTBU36QOadGlxxiADu86VaT5l0eYdbmFBHF14AD/s= github.com/evcc-io/tesla-proxy-client v0.0.0-20240221194046-4168b3759701 h1:3JplY3KS6KMDVDNAU+3+KWmSWmoHIU34qwuIpW6SiHk=