Ctrl K
ComunidadGuías…

tariff/helper.go

Rama master · réplica de lectura del repositorio original.

Archivo original ↗Preguntar a KiBot
package tariff

import (
	"errors"
	"net/http"
	"strings"
	"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/config"
	"github.com/evcc-io/evcc/util/request"
	"github.com/jinzhu/now"
)

// Name returns the tariff type name
func Name(conf config.Typed) string {
	if conf.Other != nil && conf.Other["tariff"] != nil {
		return conf.Other["tariff"].(string)
	}
	return conf.Type
}

func bo() backoff.BackOff {
	return backoff.NewExponentialBackOff(
		backoff.WithInitialInterval(time.Second),
		backoff.WithMaxElapsedTime(time.Minute),
	)
}

// backoffPermanentError returns a permanent error in case of HTTP errors except 429
func backoffPermanentError(err error) error {
	if se, ok := errors.AsType[*request.StatusError](err); ok {
		code := se.StatusCode()
		if code == http.StatusTooManyRequests {
			return se // drop the permanent wrapper added by the request package
		}
		if code >= 400 && code <= 599 {
			return backoff.Permanent(se)
		}
	}
	if err != nil && strings.HasPrefix(err.Error(), "jq: query failed") {
		return backoff.Permanent(err)
	}
	return err
}

// mergeRates blends new and existing rates, keeping existing rates after current hour
func mergeRates(data *util.Monitor[api.Rates], new api.Rates) {
	mergeRatesAfter(data, new, time.Now().Truncate(SlotDuration))
}

// mergeRatesAfter blends new and existing rates, keeping existing rates after timestamp
func mergeRatesAfter(data *util.Monitor[api.Rates], new api.Rates, now time.Time) {
	data.SetFunc(func(old api.Rates) api.Rates {
		return mergeAfter(old, new, now)
	})
}

// mergeAfter keeps the old slots that end after now and before the new slots begin
func mergeAfter(old, new api.Rates, now time.Time) api.Rates {
	new.Sort()

	var newStart time.Time
	if len(new) > 0 {
		newStart = new[0].Start
	}

	var between api.Rates
	for _, r := range old {
		if (newStart.IsZero() || !r.End.After(newStart)) && r.End.After(now) {
			between = append(between, r)
		}
	}

	return append(between, new...)
}

// beginningOfDay returns the beginning of the current day
func beginningOfDay() time.Time {
	return now.BeginningOfDay()
}

type runnable[T any] interface {
	*T
	run(done chan error)
}

// https://groups.google.com/g/golang-nuts/c/1cl9v_hPYHk
// runOrError invokes t.run(chan error) and waits for the channel to return
func runOrError[T any, I runnable[T]](t I) (*T, error) {
	done := make(chan error)
	go t.run(done)

	if err := <-done; err != nil {
		return nil, err
	}

	return t, nil
}

// reportError reports the first error to done via once and returns true when
// this is the startup failure - i.e. run's first update failed and runOrError
// is about to discard the tariff. The caller must then return so the goroutine
// exits instead of polling the API forever in the background.
//
// Once a tariff has started successfully, once has already fired (with close),
// so reportError is a no-op returning false and the caller keeps retrying
// transient errors as before.
func reportError(once *sync.Once, done chan<- error, err error) (startupFailed bool) {
	once.Do(func() {
		startupFailed = true
		done <- err
	})
	return startupFailed
}