Tariff: add forecast provider (#13451)

This commit is contained in:
andig 2024-04-17 23:43:49 +02:00 • committed by GitHub
parent 9d8e2b19d5
commit f483f2c74b
No known key found for this signature in database
GPG key ID: B5690EEEBB952194

View file

@ -1,9 +1,13 @@
package tariff
import (
"encoding/json"
"fmt"
"slices"
"sync"
"time"
"github.com/cenkalti/backoff/v4"
"github.com/evcc-io/evcc/api"
"github.com/evcc-io/evcc/provider"
"github.com/evcc-io/evcc/util"
@ -12,6 +16,8 @@ import (
type Tariff struct {
*embed
log *util.Logger
data *util.Monitor[api.Rates]
priceG func() (float64, error)
}
@ -23,29 +29,92 @@ func init() {
func NewConfigurableFromConfig(other map[string]interface{}) (api.Tariff, error) {
var cc struct {
embed `mapstructure:",squash"`
Price provider.Config
embed `mapstructure:",squash"`
Price *provider.Config
Forecast *provider.Config
}
if err := util.DecodeOther(other, &cc); err != nil {
return nil, err
}
priceG, err := provider.NewFloatGetterFromConfig(cc.Price)
if err != nil {
return nil, fmt.Errorf("price: %w", err)
if (cc.Price != nil) == (cc.Forecast != nil) {
return nil, fmt.Errorf("must have either price or forecast")
}
var (
err error
priceG func() (float64, error)
forecastG func() (string, error)
)
if cc.Price != nil {
priceG, err = provider.NewFloatGetterFromConfig(*cc.Price)
if err != nil {
return nil, fmt.Errorf("price: %w", err)
}
}
if cc.Forecast != nil {
forecastG, err = provider.NewStringGetterFromConfig(*cc.Forecast)
if err != nil {
return nil, fmt.Errorf("forecast: %w", err)
}
}
t := &Tariff{
log: util.NewLogger("tariff"),
embed: &cc.embed,
priceG: priceG,
data: util.NewMonitor[api.Rates](2 * time.Hour),
}
return t, nil
if forecastG != nil {
done := make(chan error)
go t.run(forecastG, done)
err = <-done
}
return t, err
}
// Rates implements the api.Tariff interface
func (t *Tariff) Rates() (api.Rates, error) {
func (t *Tariff) run(forecastG func() (string, error), done chan error) {
var once sync.Once
bo := newBackoff()
tick := time.NewTicker(time.Hour)
for ; true; <-tick.C {
var data api.Rates
if err := backoff.Retry(func() error {
s, err := forecastG()
if err != nil {
return backoffPermanentError(err)
}
return json.Unmarshal([]byte(s), &data)
}, bo); err != nil {
once.Do(func() { done <- err })
t.log.ERROR.Println(err)
continue
}
data.Sort()
t.data.Set(data)
once.Do(func() { close(done) })
}
}
func (t *Tariff) forecastRates() (api.Rates, error) {
var res api.Rates
err := t.data.GetFunc(func(val api.Rates) {
res = slices.Clone(val)
})
return res, err
}
func (t *Tariff) priceRates() (api.Rates, error) {
price, err := t.priceG()
if err != nil {
return nil, err
@ -66,7 +135,19 @@ func (t *Tariff) Rates() (api.Rates, error) {
return res, nil
}
// Rates implements the api.Tariff interface
func (t *Tariff) Rates() (api.Rates, error) {
if t.priceG != nil {
return t.priceRates()
}
return t.forecastRates()
}
// Type implements the api.Tariff interface
func (t *Tariff) Type() api.TariffType {
return api.TariffTypePriceDynamic
if t.priceG != nil {
return api.TariffTypePriceDynamic
}
return api.TariffTypePriceForecast
}