Migrate optimizer (BC) (#28088)

This commit is contained in:
andig 2026-03-11 11:49:27 +01:00 • committed by GitHub
parent d8084ed71a
commit c9876e38ae
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 31 additions and 31 deletions

View file

@ -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

View file

@ -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},
}

2
go.mod
View file

@ -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

4
go.sum
View file

@ -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=