evcc-io/tariff/octopusde/graphql/api.go

94 lines
2.5 KiB
Go

package graphql
import (
"context"
"errors"
"net/http"
"time"
"github.com/evcc-io/evcc/util"
"github.com/evcc-io/evcc/util/request"
"github.com/evcc-io/evcc/util/transport"
"github.com/hasura/go-graphql-client"
"golang.org/x/oauth2"
)
// BaseURI is Octopus Energy Germany's Kraken API root.
// The implementation in this file follows the published example at https://octopusenergy.de/blog/wohnen/dynamisch-sparen-per-api
const BaseURI = "https://api.oeg-kraken.energy/v1/graphql/"
// OctopusDeGraphQLClient provides an interface for communicating with Octopus Energy Germany's Kraken platform.
type OctopusDeGraphQLClient struct {
log *util.Logger
*graphql.Client
accountNumber string
}
// NewClient returns a new, authenticated instance of OctopusDeGraphQLClient.
func NewClient(log *util.Logger, email, password, accountNumber string) (*OctopusDeGraphQLClient, error) {
ts := oauth2.ReuseTokenSource(nil, &tokenSource{
log: log,
email: email,
password: password,
})
cli := request.NewClient(log)
cli.Transport = &transport.Decorator{
Decorator: func(req *http.Request) error {
token, err := ts.Token()
if err != nil {
return err
}
// Kraken API requires Authorization header without "Bearer" prefix
req.Header.Set("Authorization", token.AccessToken)
return nil
},
Base: cli.Transport,
}
gq := &OctopusDeGraphQLClient{
log: log,
accountNumber: accountNumber,
Client: graphql.NewClient(BaseURI, cli),
}
return gq, nil
}
// ActiveAgreement queries the Kraken API and returns the active electricity supply agreement.
func (c *OctopusDeGraphQLClient) ActiveAgreement() (Agreement, error) {
ctx, cancel := context.WithTimeout(context.Background(), time.Second*10)
defer cancel()
var q getAgreements
if err := c.Client.Query(ctx, &q, map[string]any{
"accountNumber": c.accountNumber,
}); err != nil {
return Agreement{}, err
}
if len(q.Account.Properties) == 0 {
return Agreement{}, errors.New("no properties found")
}
agr, err := findActiveAgreement(&q)
if err != nil {
return Agreement{}, err
}
return *agr, nil
}
// findActiveAgreement returns the first agreement marked IsActive across all properties.
func findActiveAgreement(q *getAgreements) (*Agreement, error) {
for _, property := range q.Account.Properties {
for _, malo := range property.ElectricityMalos {
for _, agr := range malo.Agreements {
if agr.IsActive {
return &agr, nil
}
}
}
}
return nil, errors.New("no active agreement found")
}