diff --git a/core/loadpoint/api.go b/core/loadpoint/api.go index f52b6e4b3..902f393c3 100644 --- a/core/loadpoint/api.go +++ b/core/loadpoint/api.go @@ -117,7 +117,7 @@ type API interface { GetPlanEnergy() (time.Time, time.Duration, float64) // SetPlanEnergy sets the charge plan energy SetPlanEnergy(time.Time, time.Duration, float64) error - // GetPlanGoal returns the plan goal, precondition duration and if the goal is soc based + // GetPlanGoal returns the plan goal and if the goal is soc based GetPlanGoal() (float64, bool) // GetPlanRequiredDuration returns required duration of plan to reach the goal from current state GetPlanRequiredDuration(goal, maxPower float64) time.Duration diff --git a/core/site_optimizer.go b/core/site_optimizer.go index a041cfc3f..59f3f4ee8 100644 --- a/core/site_optimizer.go +++ b/core/site_optimizer.go @@ -170,6 +170,35 @@ func (site *Site) optimizerUpdate(battery []measurement) error { } } + switch lp.GetMode() { + case api.ModeOff: + // disable charging + bat.CMax = 0 + + case api.ModeNow, api.ModeMinPV: + // forced min/max charging + bat.PDemand = continuousDemand(lp, minLen) + + case api.ModePV: + // add plan goal + goal, socBased := lp.GetPlanGoal() + if goal > 0 { + if v := lp.GetVehicle(); socBased && v != nil { + goal *= v.Capacity() + } + } + + if ts := lp.EffectivePlanTime(); !ts.IsZero() { + // TODO precise slot placement + if slot := int(time.Until(ts) / time.Hour); slot < minLen { + bat.SGoal = lo.ToPtr(lo.RepeatBy(minLen, func(_ int) float32 { return 0 })) + (*bat.SGoal)[slot] = float32(goal) + } else { + site.log.WARN.Printf("plan beyond forecast range: %.1f at %v", goal, ts.Round(time.Minute)) + } + } + } + req.Batteries = append(req.Batteries, bat) details.BatteryDetails = append(details.BatteryDetails, detail) @@ -259,6 +288,22 @@ func (site *Site) optimizerUpdate(battery []measurement) error { return nil } +// continuousDemand creates a slice of power demands depending on loadpoint mode +func continuousDemand(lp loadpoint.API, minLen int) *[]float32 { + if lp.GetStatus() != api.StatusC { + return nil + } + + pwr := lp.EffectiveMaxPower() + if lp.GetMode() == api.ModeMinPV { + pwr = lp.EffectiveMinPower() + } + + return lo.ToPtr(lo.RepeatBy(minLen, func(_ int) float32 { + return float32(pwr) + })) +} + // loadpointProfile returns the loadpoint's charging profile in Wh // TODO consider charging efficiency func loadpointProfile(lp loadpoint.API, firstSlotDuration time.Duration, minLen int) []float64 {