Skip to content

Commit

Permalink
Tariff: add forecast provider (#13451)
Browse files Browse the repository at this point in the history
  • Loading branch information
andig authored Apr 17, 2024
1 parent 9d8e2b1 commit f483f2c
Showing 1 changed file with 90 additions and 9 deletions.
99 changes: 90 additions & 9 deletions tariff/tariff.go
Original file line number Diff line number Diff line change
@@ -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"
Expand All @@ -12,6 +16,8 @@ import (

type Tariff struct {
*embed
log *util.Logger
data *util.Monitor[api.Rates]
priceG func() (float64, error)
}

Expand All @@ -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
Expand All @@ -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
}

0 comments on commit f483f2c

Please sign in to comment.