From 3626b7f658cf60533b29e0ed147e2f7c20da6903 Mon Sep 17 00:00:00 2001 From: RenatusRo <126881912+RenatusRo@users.noreply.github.com> Date: Fri, 27 Feb 2026 08:55:24 +0100 Subject: [PATCH] Optimizer: add battery grid charging (#27696) --- core/site_optimizer.go | 35 +++++++++++++++++++++++++++++++++-- 1 file changed, 33 insertions(+), 2 deletions(-) diff --git a/core/site_optimizer.go b/core/site_optimizer.go index 1f1b1ed6d..a21052921 100644 --- a/core/site_optimizer.go +++ b/core/site_optimizer.go @@ -205,7 +205,7 @@ func (site *Site) optimizerUpdate(battery []types.Measurement) error { continue } - add(site.batteryRequest(dev, b)) + add(site.batteryRequest(dev, b, grid, minLen, firstSlotDuration)) } httpClient := request.NewClient(site.log) @@ -398,7 +398,7 @@ 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) (evopt.BatteryConfig, batteryDetail) { +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{ CMax: batteryPower, DMax: batteryPower, @@ -432,6 +432,13 @@ func (site *Site) batteryRequest(dev config.Device[api.Meter], b types.Measureme Capacity: *b.Capacity, } + // tariff forecast-based grid charging demand + if bat.ChargeFromGrid { + if demand := site.applyBatteryGridChargeLimit(bat.CMax, grid, minLen); demand != nil { + bat.PDemand = prorate(demand, firstSlotDuration) + } + } + return bat, detail } @@ -678,6 +685,30 @@ func applySmartCostLimit(lp loadpoint.API, demand []float32, grid api.Rates, min return demand } +func (site *Site) applyBatteryGridChargeLimit(cMax float32, grid api.Rates, minLen int) []float32 { + limit := site.GetBatteryGridChargeLimit() + if limit == nil { + return nil + } + + maxLen := min(minLen, len(grid)) + + if hasAffordableSlots := slices.ContainsFunc(grid[:maxLen], func(r api.Rate) bool { + return r.Value <= *limit + }); !hasAffordableSlots { + return nil + } + + demand := make([]float32, minLen) + for i := range maxLen { + if grid[i].Value <= *limit { + demand[i] = float32(float64(cMax) / slotsPerHour) + } + } + + return demand +} + // apiError extracts error message from optimizer API response func apiError(resp *evopt.PostOptimizeChargeScheduleResponse) error { var errObj *evopt.Error