Optimizer: reduce interval to 15min (#29137)

This commit is contained in:
andig 2026-04-15 17:03:59 +02:00 • committed by GitHub
parent 467c3c572e
commit f69252e672
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
6 changed files with 55 additions and 37 deletions

View file

@ -1,9 +1,19 @@
<template>
<div class="container px-4 safe-area-inset">
<TopHeader title="Optimize Debug" />
<div class="alert alert-light mb-5">
This page is for development purposes only. Gives insights into the upcoming
optimization algorithm.
<div class="alert alert-light mb-5 d-flex justify-content-between align-items-center">
<span>
This page is for development purposes only. Gives insights into the upcoming
optimization algorithm.
</span>
<button
class="btn btn-sm ms-3 text-nowrap"
:class="optimizeCooldown ? 'btn-outline-secondary disabled' : 'btn-outline-dark'"
:disabled="optimizeCooldown"
@click="optimizeNow"
>
{{ optimizeCooldown ? "Requested" : "Optimize now" }}
</button>
</div>
<div class="row">
<main class="col-12">
@ -133,6 +143,7 @@ import PriceChart from "../components/Optimize/PriceChart.vue";
import TimeSeriesDataTable from "../components/Optimize/TimeSeriesDataTable.vue";
import CopyButton from "../components/Optimize/CopyButton.vue";
import { formatCompactJson } from "../components/Optimize/compactJson";
import api from "../api";
import store from "../store";
import formatter from "../mixins/formatter";
import colors from "../colors";
@ -150,6 +161,11 @@ export default defineComponent({
CopyButton,
},
mixins: [formatter],
data() {
return {
optimizeCooldown: false,
};
},
head() {
return { title: "Optimize Debug" };
},
@ -191,7 +207,16 @@ export default defineComponent({
return this.evopt?.res ? formatCompactJson(this.evopt.res) : "";
},
},
watch: {
evopt() {
this.optimizeCooldown = false;
},
},
methods: {
optimizeNow() {
api.post("optimize");
this.optimizeCooldown = true;
},
dimColorBy25Percent(color: string): string {
// Convert color to 25% opacity (40 in hex = 25% of 255)
return color?.toLowerCase().replace(/ff$/, "40") || color;

View file

@ -792,7 +792,7 @@ func (site *Site) updateMeters() error {
return err
}
if sponsor.IsAuthorized() && optimizerEnabled() {
if sponsor.IsAuthorized() && optimizerEnabled() && time.Since(optimizerUpdated) >= tariff.SlotDuration {
go site.optimizerUpdateAsync()
}

View file

@ -16,6 +16,7 @@ type API interface {
Loadpoints() []loadpoint.API
Vehicles() Vehicles
Optimize() error
// Meta
GetTitle() string

View file

@ -11,6 +11,7 @@ import (
"github.com/evcc-io/evcc/core/site"
"github.com/evcc-io/evcc/server/db/settings"
"github.com/evcc-io/evcc/util/config"
"github.com/evcc-io/evcc/util/sponsor"
"github.com/samber/lo"
)
@ -35,6 +36,16 @@ func filterConfigurable(ref []string) []string {
})
}
// Optimize updates the optimizer
func (site *Site) Optimize() error {
if !sponsor.IsAuthorized() || !optimizerEnabled() {
return api.ErrNotAvailable
}
go site.optimizerUpdateAsync()
return nil
}
// GetTitle returns the title
func (site *Site) GetTitle() string {
site.RLock()

View file

@ -9,7 +9,7 @@ import (
"os"
"slices"
"strings"
"sync/atomic"
"sync"
"time"
"github.com/evcc-io/evcc/api"
@ -30,8 +30,8 @@ var (
eta = float32(0.9) // efficiency of the battery charging/discharging
batteryPower = float32(6000) // default power of the battery in W
updated time.Time
mu atomic.Uint32
mu sync.Mutex
optimizerUpdated time.Time
)
// optimizerResult wraps the optimizer publish payload to implement BytesMarshaler.
@ -73,26 +73,6 @@ type batteryResult struct {
Empty time.Time `json:"empty,omitzero"`
}
// func (br batteryResult) MarshalJSON() ([]byte, error) {
// var full, empty int64
// if !br.Full.IsZero() {
// full = int64(time.Until(br.Full).Seconds())
// }
// if !br.Empty.IsZero() {
// empty = int64(time.Until(br.Empty).Seconds())
// }
// return json.Marshal(struct {
// batteryResult
// UntilFull int64 `json:"untilFull,omitempty"`
// UntilEmpty int64 `json:"untilEmpty,omitempty"`
// }{
// batteryResult: br,
// UntilFull: full,
// UntilEmpty: empty,
// })
// }
type requestDetails struct {
Timestamps []time.Time `json:"timestamp"`
BatteryDetails []batteryDetail `json:"batteryDetails"`
@ -101,19 +81,19 @@ type requestDetails struct {
const slotsPerHour = float64(time.Hour / tariff.SlotDuration)
func (site *Site) optimizerUpdateAsync() {
if !mu.TryLock() {
return
}
defer mu.Unlock()
if time.Since(optimizerUpdated) < 2*time.Minute {
return
}
var err error
if time.Since(updated) < 2*time.Minute {
return
}
if !mu.CompareAndSwap(0, 1) {
return
}
defer func() {
updated = time.Now()
mu.Store(0)
optimizerUpdated = time.Now()
if r := recover(); r != nil {
err = fmt.Errorf("panic %v", r)

View file

@ -160,6 +160,7 @@ func (s *HTTPd) RegisterSiteHandlers(site site.API) {
"deletesession": {"DELETE", "/session/{id:[0-9]+}", deleteSessionHandler},
"gridsessions": {"GET", "/gridsessions", gridSessionsHandler},
"energyhistory": {"GET", "/history/energy", energyHistoryHandler},
"optimize": {"POST", "/optimize", getHandler(site.Optimize)},
"telemetry2": {"POST", "/settings/telemetry/{value:[01truefalse]+}", boolHandler(telemetry.Enable, telemetry.Enabled)},
}