Ctrl K
ComunidadGuías…

tariff/tibber.go

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

Archivo original ↗Preguntar a KiBot
package tariff

import (
	"context"
	"slices"
	"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"
	"github.com/evcc-io/evcc/util/request"
	"github.com/hasura/go-graphql-client"
)

type Tibber struct {
	*embed
	log    *util.Logger
	homeID string
	client *tibber.Client
	data   *util.Monitor[api.Rates]
}

var _ api.Tariff = (*Tibber)(nil)

func init() {
	registry.Add("tibber", NewTibberFromConfig)
}

func NewTibberFromConfig(other map[string]any) (api.Tariff, error) {
	var cc struct {
		embed  `mapstructure:",squash"`
		Token  string
		HomeID string
	}

	if err := util.DecodeOther(other, &cc); err != nil {
		return nil, err
	}

	if cc.Token == "" {
		return nil, api.ErrMissingToken
	}

	if err := cc.init(); err != nil {
		return nil, err
	}

	log := util.NewLogger("tibber").Redact(cc.Token, cc.HomeID)

	t := &Tibber{
		embed:  &cc.embed,
		log:    log,
		homeID: cc.HomeID,
		client: tibber.NewClient(log, cc.Token),
		data:   util.NewMonitor[api.Rates](2 * time.Hour),
	}

	if t.homeID == "" {
		home, err := t.client.DefaultHome(t.homeID)
		if err != nil {
			return nil, err
		}

		t.homeID = home.ID
	}

	return runOrError(t)
}

func (t *Tibber) run(done chan error) {
	var once sync.Once

	v := map[string]any{
		"id": graphql.ID(t.homeID),
	}

	for tick := time.Tick(time.Hour); ; <-tick {
		var res struct {
			Viewer struct {
				Home struct {
					ID                  string
					TimeZone            string
					CurrentSubscription tibber.Subscription
				} `graphql:"home(id: $id)"`
			}
		}

		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 {
			if reportError(&once, done, err) {
				return
			}

			t.log.ERROR.Println(err)
			continue
		}

		pi := res.Viewer.Home.CurrentSubscription.PriceInfo
		data := append(t.rates(pi.Today), t.rates(pi.Tomorrow)...)

		mergeRates(t.data, data)
		once.Do(func() { close(done) })
	}
}

func (t *Tibber) rates(pi []tibber.Price) api.Rates {
	data := make(api.Rates, 0, len(pi))
	for _, r := range pi {
		price := r.Total
		if t.Charges != 0 || len(t.chargesZones) > 0 || t.Tax != 0 || t.Formula != "" {
			price = t.totalPrice(r.Energy, r.StartsAt)
		}
		ar := api.Rate{
			Start: r.StartsAt.Local(),
			End:   r.StartsAt.Add(SlotDuration).Local(),
			Value: price,
		}
		data = append(data, ar)
	}
	return data
}

// Rates implements the api.Tariff interface
func (t *Tibber) Rates() (api.Rates, error) {
	var res api.Rates
	err := t.data.GetFunc(func(val api.Rates) {
		res = slices.Clone(val)
	})
	return res, err
}

// Type implements the api.Tariff interface
func (t *Tibber) Type() api.TariffType {
	return api.TariffTypePriceForecast
}