From 7c92e0daa5869208f3f657ae05d6635fa45baad3 Mon Sep 17 00:00:00 2001 From: andig Date: Thu, 27 Jul 2023 09:31:40 +0200 Subject: [PATCH] Tariffs: retry download with backoff (#8833) --- tariff/awattar.go | 9 +++++++-- tariff/electricitymaps.go | 9 +++++++-- tariff/elering.go | 8 ++++++-- tariff/energinet.go | 8 ++++++-- tariff/fixed.go | 2 +- tariff/gruenstromindex.go | 17 +++++++++++------ tariff/helper.go | 14 ++++++++++++++ tariff/octopus.go | 9 +++++++-- tariff/tibber.go | 32 +++++++++++++++++--------------- 9 files changed, 76 insertions(+), 32 deletions(-) create mode 100644 tariff/helper.go diff --git a/tariff/awattar.go b/tariff/awattar.go index 34c2eb602..6a2764b1f 100644 --- a/tariff/awattar.go +++ b/tariff/awattar.go @@ -6,6 +6,7 @@ import ( "sync" "time" + "github.com/cenkalti/backoff/v4" "github.com/evcc-io/evcc/api" "github.com/evcc-io/evcc/tariff/awattar" "github.com/evcc-io/evcc/util" @@ -55,11 +56,15 @@ func NewAwattarFromConfig(other map[string]interface{}) (api.Tariff, error) { func (t *Awattar) run(done chan error) { var once sync.Once + bo := newBackoff() client := request.NewHelper(t.log) for ; true; <-time.Tick(time.Hour) { var res awattar.Prices - if err := client.GetJSON(t.uri, &res); err != nil { + + if err := backoff.Retry(func() error { + return client.GetJSON(t.uri, &res) + }, bo); err != nil { once.Do(func() { done <- err }) t.log.ERROR.Println(err) @@ -92,7 +97,7 @@ func (t *Awattar) Rates() (api.Rates, error) { return slices.Clone(t.data), outdatedError(t.updated, time.Hour) } -// Type returns the tariff type +// Type implements the api.Tariff interface func (t *Awattar) Type() api.TariffType { return api.TariffTypePriceDynamic } diff --git a/tariff/electricitymaps.go b/tariff/electricitymaps.go index 4a0455e2a..95ff072b3 100644 --- a/tariff/electricitymaps.go +++ b/tariff/electricitymaps.go @@ -7,6 +7,7 @@ import ( "sync" "time" + "github.com/cenkalti/backoff/v4" "github.com/evcc-io/evcc/api" "github.com/evcc-io/evcc/util" "github.com/evcc-io/evcc/util/request" @@ -78,11 +79,15 @@ func NewElectricityMapsFromConfig(other map[string]interface{}) (api.Tariff, err func (t *ElectricityMaps) run(done chan error) { var once sync.Once + bo := newBackoff() uri := fmt.Sprintf("%s/carbon-intensity/forecast?zone=%s", t.uri, t.zone) for ; true; <-time.Tick(time.Hour) { var res CarbonIntensity - if err := t.GetJSON(uri, &res); err != nil { + + if err := backoff.Retry(func() error { + return t.GetJSON(uri, &res) + }, bo); err != nil { if res.Error != "" { err = errors.New(res.Error) } @@ -119,7 +124,7 @@ func (t *ElectricityMaps) Rates() (api.Rates, error) { return res, outdatedError(t.updated, time.Hour) } -// Type returns the tariff type +// Type implements the api.Tariff interface func (t *ElectricityMaps) Type() api.TariffType { return api.TariffTypeCo2 } diff --git a/tariff/elering.go b/tariff/elering.go index 241597f46..7b73ac5b1 100644 --- a/tariff/elering.go +++ b/tariff/elering.go @@ -8,6 +8,7 @@ import ( "sync" "time" + "github.com/cenkalti/backoff/v4" "github.com/evcc-io/evcc/api" "github.com/evcc-io/evcc/tariff/elering" "github.com/evcc-io/evcc/util" @@ -60,6 +61,7 @@ func NewEleringFromConfig(other map[string]interface{}) (api.Tariff, error) { func (t *Elering) run(done chan error) { var once sync.Once client := request.NewHelper(t.log) + bo := newBackoff() for ; true; <-time.Tick(time.Hour) { var res elering.NpsPrice @@ -69,7 +71,9 @@ func (t *Elering) run(done chan error) { url.QueryEscape(ts.Format(time.RFC3339)), url.QueryEscape(ts.Add(48*time.Hour).Format(time.RFC3339))) - if err := client.GetJSON(uri, &res); err != nil { + if err := backoff.Retry(func() error { + return client.GetJSON(uri, &res) + }, bo); err != nil { once.Do(func() { done <- err }) t.log.ERROR.Println(err) @@ -106,7 +110,7 @@ func (t *Elering) Rates() (api.Rates, error) { return slices.Clone(t.data), outdatedError(t.updated, time.Hour) } -// Type returns the tariff type +// Type implements the api.Tariff interface func (t *Elering) Type() api.TariffType { return api.TariffTypePriceDynamic } diff --git a/tariff/energinet.go b/tariff/energinet.go index a41cff590..24bc58743 100644 --- a/tariff/energinet.go +++ b/tariff/energinet.go @@ -7,6 +7,7 @@ import ( "sync" "time" + "github.com/cenkalti/backoff/v4" "github.com/evcc-io/evcc/api" "github.com/evcc-io/evcc/tariff/energinet" "github.com/evcc-io/evcc/util" @@ -59,6 +60,7 @@ func NewEnerginetFromConfig(other map[string]interface{}) (api.Tariff, error) { func (t *Energinet) run(done chan error) { var once sync.Once client := request.NewHelper(t.log) + bo := newBackoff() for ; true; <-time.Tick(time.Hour) { var res energinet.Prices @@ -69,7 +71,9 @@ func (t *Energinet) run(done chan error) { ts.Add(24*time.Hour).Format(time.RFC3339), t.region) - if err := client.GetJSON(uri, &res); err != nil { + if err := backoff.Retry(func() error { + return client.GetJSON(uri, &res) + }, bo); err != nil { once.Do(func() { done <- err }) t.log.ERROR.Println(err) @@ -103,7 +107,7 @@ func (t *Energinet) Rates() (api.Rates, error) { return slices.Clone(t.data), outdatedError(t.updated, time.Hour) } -// Type returns the tariff type +// Type implements the api.Tariff interface func (t *Energinet) Type() api.TariffType { return api.TariffTypePriceDynamic } diff --git a/tariff/fixed.go b/tariff/fixed.go index 6f168407f..da317af26 100644 --- a/tariff/fixed.go +++ b/tariff/fixed.go @@ -129,7 +129,7 @@ func (t *Fixed) Rates() (api.Rates, error) { return res, nil } -// Type returns the tariff type +// Type implements the api.Tariff interface func (t *Fixed) Type() api.TariffType { if t.dynamic { return api.TariffTypePriceDynamic diff --git a/tariff/gruenstromindex.go b/tariff/gruenstromindex.go index 80bd39583..db7bc1ea2 100644 --- a/tariff/gruenstromindex.go +++ b/tariff/gruenstromindex.go @@ -6,6 +6,7 @@ import ( "sync" "time" + "github.com/cenkalti/backoff/v4" "github.com/evcc-io/evcc/api" "github.com/evcc-io/evcc/util" "github.com/evcc-io/evcc/util/request" @@ -13,7 +14,6 @@ import ( ) type GrünStromIndex struct { - *request.Helper log *util.Logger mux sync.Mutex zip string @@ -76,9 +76,8 @@ func NewGrünStromIndexFromConfig(other map[string]interface{}) (api.Tariff, err log := util.NewLogger("gsi").Redact(cc.Zip) t := &GrünStromIndex{ - log: log, - Helper: request.NewHelper(log), - zip: cc.Zip, + log: log, + zip: cc.Zip, } done := make(chan error) @@ -90,11 +89,17 @@ func NewGrünStromIndexFromConfig(other map[string]interface{}) (api.Tariff, err func (t *GrünStromIndex) run(done chan error) { var once sync.Once + client := request.NewHelper(t.log) + bo := newBackoff() uri := fmt.Sprintf("https://api.corrently.io/v2.0/gsi/prediction?zip=%s", t.zip) for ; true; <-time.Tick(time.Hour) { var res gsiForecast - err := t.GetJSON(uri, &res) + + err := backoff.Retry(func() error { + return client.GetJSON(uri, &res) + }, bo) + if err == nil && res.Err { if s, ok := res.Message.(string); ok { err = errors.New(s) @@ -135,7 +140,7 @@ func (t *GrünStromIndex) Rates() (api.Rates, error) { return slices.Clone(t.data), outdatedError(t.updated, time.Hour) } -// Type returns the tariff type +// Type implements the api.Tariff interface func (t *GrünStromIndex) Type() api.TariffType { return api.TariffTypeCo2 } diff --git a/tariff/helper.go b/tariff/helper.go new file mode 100644 index 000000000..d20bfb3ab --- /dev/null +++ b/tariff/helper.go @@ -0,0 +1,14 @@ +package tariff + +import ( + "time" + + "github.com/cenkalti/backoff/v4" +) + +func newBackoff() backoff.BackOff { + bo := backoff.NewExponentialBackOff() + bo.InitialInterval = 5 * time.Second + bo.MaxElapsedTime = time.Minute + return bo +} diff --git a/tariff/octopus.go b/tariff/octopus.go index b0a924a88..f2d2f1820 100644 --- a/tariff/octopus.go +++ b/tariff/octopus.go @@ -5,6 +5,7 @@ import ( "sync" "time" + "github.com/cenkalti/backoff/v4" "github.com/evcc-io/evcc/api" "github.com/evcc-io/evcc/tariff/octopus" "github.com/evcc-io/evcc/util" @@ -60,10 +61,14 @@ func NewOctopusFromConfig(other map[string]interface{}) (api.Tariff, error) { func (t *Octopus) run(done chan error) { var once sync.Once client := request.NewHelper(t.log) + bo := newBackoff() for ; true; <-time.Tick(time.Hour) { var res octopus.UnitRates - if err := client.GetJSON(t.uri, &res); err != nil { + + if err := backoff.Retry(func() error { + return client.GetJSON(t.uri, &res) + }, bo); err != nil { once.Do(func() { done <- err }) t.log.ERROR.Println(err) @@ -103,7 +108,7 @@ func (t *Octopus) Rates() (api.Rates, error) { return slices.Clone(t.data), outdatedError(t.updated, time.Hour) } -// Type returns the tariff type +// Type implements the api.Tariff interface func (t *Octopus) Type() api.TariffType { return api.TariffTypePriceDynamic } diff --git a/tariff/tibber.go b/tariff/tibber.go index 16faebed6..60d89399e 100644 --- a/tariff/tibber.go +++ b/tariff/tibber.go @@ -6,6 +6,7 @@ import ( "sync" "time" + "github.com/cenkalti/backoff/v4" "github.com/evcc-io/evcc/api" "github.com/evcc-io/evcc/meter/tibber" "github.com/evcc-io/evcc/util" @@ -73,27 +74,28 @@ func NewTibberFromConfig(other map[string]interface{}) (api.Tariff, error) { func (t *Tibber) run(done chan error) { var once sync.Once - - var res struct { - Viewer struct { - Home struct { - ID string - TimeZone string - CurrentSubscription tibber.Subscription - } `graphql:"home(id: $id)"` - } - } + bo := newBackoff() v := map[string]interface{}{ "id": graphql.ID(t.homeID), } for ; true; <-time.Tick(time.Hour) { - ctx, cancel := context.WithTimeout(context.Background(), request.Timeout) - err := t.client.Query(ctx, &res, v) - cancel() + var res struct { + Viewer struct { + Home struct { + ID string + TimeZone string + CurrentSubscription tibber.Subscription + } `graphql:"home(id: $id)"` + } + } - if err != nil { + if err := backoff.Retry(func() error { + ctx, cancel := context.WithTimeout(context.Background(), request.Timeout) + defer cancel() + return t.client.Query(ctx, &res, v) + }, bo); err != nil { once.Do(func() { done <- err }) t.log.ERROR.Println(err) @@ -137,7 +139,7 @@ func (t *Tibber) Rates() (api.Rates, error) { return slices.Clone(t.data), outdatedError(t.updated, time.Hour) } -// Type returns the tariff type +// Type implements the api.Tariff interface func (t *Tibber) Type() api.TariffType { return api.TariffTypePriceDynamic }