From f9eeeb50e2e5d911c6fe7ce6245f52b698827af0 Mon Sep 17 00:00:00 2001 From: andig Date: Sun, 20 Nov 2022 15:35:10 +0100 Subject: [PATCH] Tibber Pulse: detect home id --- meter/tibber-pulse.go | 52 ++++++++++++++++--------------- meter/tibber/client.go | 52 +++++++++++++++++++++++++++++++ meter/tibber/types.go | 37 +++++++++++++++++++++- tariff/tibber.go | 70 ++++++++++++++++-------------------------- tariff/tibber/api.go | 37 ---------------------- 5 files changed, 141 insertions(+), 107 deletions(-) create mode 100644 meter/tibber/client.go delete mode 100644 tariff/tibber/api.go diff --git a/meter/tibber-pulse.go b/meter/tibber-pulse.go index ce2f7ce40..c1820c7e5 100644 --- a/meter/tibber-pulse.go +++ b/meter/tibber-pulse.go @@ -34,52 +34,54 @@ func NewTibberFromConfig(other map[string]interface{}) (api.Meter, error) { } t := &Tibber{ - log: util.NewLogger("pulse").Redact(cc.HomeID, cc.Token), + log: util.NewLogger("pulse").Redact(cc.Token, cc.HomeID), } - // TODO - // https://developer.tibber.com/docs/overview#breaking-websocket-change - // https://github.com/hasura/go-graphql-client/issues/38 + if cc.HomeID == "" { + // query client + qclient := tibber.NewClient(t.log, cc.Token) - // ctx := context.WithValue( - // context.Background(), - // oauth2.HTTPClient, - // request.NewHelper(t.log).Client, - // ) - - // client := oauth2.NewClient(ctx, oauth2.StaticTokenSource(&oauth2.Token{ - // AccessToken: t.Token, - // })) - - // urlClient := graphql.NewClient(tibber.URI, client) + var err error + if cc.HomeID, err = qclient.DefaultHomeID(); err != nil { + return nil, err + } + } + // // get websocket uri // var res struct { // Viewer struct { // WebsocketSubscriptionUrl string // } // } - // if err := urlClient.Query(context.Background(), &res, nil); err != nil { - // panic(err) + // ctx, cancel := context.WithTimeout(context.Background(), request.Timeout) + // defer cancel() + + // if err := qclient.Query(ctx, &res, nil); err != nil { + // return nil, err // } - // println(res.Viewer.WebsocketSubscriptionUrl) - + // subscription client client := graphql.NewSubscriptionClient(tibber.SubscriptionURI). WithConnectionParams(map[string]any{ "token": cc.Token, }). WithLog(t.log.TRACE.Println) - // Run the client + // run the client go func() { if err := client.Run(); err != nil { - panic(err) + t.log.ERROR.Println(err) } - println("running") }() - // subscribe + err := t.subscribe(client, cc.HomeID) + + return t, err +} + +// subscribe to the websocket query +func (t *Tibber) subscribe(client *graphql.SubscriptionClient, homeID string) error { var query struct { tibber.LiveMeasurement `graphql:"liveMeasurement(homeId: $homeId)"` } @@ -89,7 +91,7 @@ func NewTibberFromConfig(other map[string]interface{}) (api.Meter, error) { errC := make(chan error) _, err := client.Subscribe(&query, map[string]any{ - "homeId": graphql.ID(cc.HomeID), + "homeId": graphql.ID(homeID), }, func(data []byte, err error) error { if err != nil { select { @@ -130,7 +132,7 @@ func NewTibberFromConfig(other map[string]interface{}) (api.Meter, error) { } } - return t, err + return err } // CurrentPower implements the api.Meter interface diff --git a/meter/tibber/client.go b/meter/tibber/client.go new file mode 100644 index 000000000..05d875699 --- /dev/null +++ b/meter/tibber/client.go @@ -0,0 +1,52 @@ +package tibber + +import ( + "context" + "fmt" + + "github.com/evcc-io/evcc/util" + "github.com/evcc-io/evcc/util/request" + "github.com/shurcooL/graphql" + "golang.org/x/oauth2" +) + +type Client struct { + *graphql.Client +} + +func NewClient(log *util.Logger, token string) *Client { + ctx := context.WithValue( + context.Background(), + oauth2.HTTPClient, + request.NewHelper(log).Client, + ) + + client := oauth2.NewClient(ctx, oauth2.StaticTokenSource(&oauth2.Token{ + AccessToken: token, + })) + + return &Client{ + Client: graphql.NewClient(URI, client), + } +} + +func (c *Client) DefaultHomeID() (string, error) { + var res struct { + Viewer struct { + Homes []Home + } + } + + ctx, cancel := context.WithTimeout(context.Background(), request.Timeout) + defer cancel() + + if err := c.Query(ctx, &res, nil); err != nil { + return "", err + } + + if len(res.Viewer.Homes) != 1 { + return "", fmt.Errorf("could not determine home id: %v", res.Viewer.Homes) + } + + return res.Viewer.Homes[0].ID, nil +} diff --git a/meter/tibber/types.go b/meter/tibber/types.go index 0a7713ec9..b2b3d6298 100644 --- a/meter/tibber/types.go +++ b/meter/tibber/types.go @@ -1,6 +1,41 @@ package tibber -const SubscriptionURI = "wss://api.tibber.com/v1-beta/gql/subscriptions" +import "time" + +const ( + URI = "https://api.tibber.com/v1-beta/gql" + SubscriptionURI = "wss://api.tibber.com/v1-beta/gql/subscriptions" +) + +type Home struct { + ID string + TimeZone string + Address Address + MeteringPointData struct { + GridCompany string + } +} + +type Address struct { + Address1, PostalCode, City, Country string +} + +type Subscription struct { + ID string + Status string + PriceInfo struct { + Current PriceInfo + Today []PriceInfo + // Tomorrow []PriceInfo + } +} + +type PriceInfo struct { + Level string + StartsAt time.Time + Total float64 + // Energy, Tax float64 +} type LiveMeasurement struct { // Timestamp time.Time diff --git a/tariff/tibber.go b/tariff/tibber.go index 5b54bbfb1..0a5ec7635 100644 --- a/tariff/tibber.go +++ b/tariff/tibber.go @@ -3,54 +3,50 @@ package tariff import ( "context" "errors" - "fmt" "sync" "time" "github.com/evcc-io/evcc/api" - "github.com/evcc-io/evcc/tariff/tibber" + "github.com/evcc-io/evcc/meter/tibber" "github.com/evcc-io/evcc/util" "github.com/evcc-io/evcc/util/request" "github.com/shurcooL/graphql" - "golang.org/x/oauth2" ) type Tibber struct { mux sync.Mutex log *util.Logger - Token string - HomeID string - Cheap float64 - client *graphql.Client + homeID string + cheap float64 + client *tibber.Client data []tibber.PriceInfo } var _ api.Tariff = (*Tibber)(nil) func NewTibber(other map[string]interface{}) (*Tibber, error) { - t := &Tibber{} + var cc struct { + Token string + HomeID string + Cheap float64 + } - if err := util.DecodeOther(other, &t); err != nil { + if err := util.DecodeOther(other, &cc); err != nil { return nil, err } - t.log = util.NewLogger("tibber").Redact(t.HomeID, t.Token) + log := util.NewLogger("tibber").Redact(cc.Token, cc.HomeID) - ctx := context.WithValue( - context.Background(), - oauth2.HTTPClient, - request.NewHelper(t.log).Client, - ) + t := &Tibber{ + log: log, + homeID: cc.HomeID, + cheap: cc.Cheap, + client: tibber.NewClient(log, cc.Token), + } - client := oauth2.NewClient(ctx, oauth2.StaticTokenSource(&oauth2.Token{ - AccessToken: t.Token, - })) - - t.client = graphql.NewClient(tibber.URI, client) - - if t.HomeID == "" { + if t.homeID == "" { var err error - if t.HomeID, err = t.DefaultHomeID(); err != nil { + if t.homeID, err = t.client.DefaultHomeID(); err != nil { return nil, err } } @@ -60,24 +56,6 @@ func NewTibber(other map[string]interface{}) (*Tibber, error) { return t, nil } -func (t *Tibber) DefaultHomeID() (string, error) { - var res struct { - Viewer struct { - Homes []tibber.Home - } - } - - if err := t.client.Query(context.Background(), &res, nil); err != nil { - return "", err - } - - if len(res.Viewer.Homes) != 1 { - return "", fmt.Errorf("could not determine home id: %v", res.Viewer.Homes) - } - - return res.Viewer.Homes[0].ID, nil -} - func (t *Tibber) Run() { for ; true; <-time.NewTicker(time.Hour).C { var res struct { @@ -91,10 +69,14 @@ func (t *Tibber) Run() { } v := map[string]interface{}{ - "id": graphql.ID(t.HomeID), + "id": graphql.ID(t.homeID), } - if err := t.client.Query(context.Background(), &res, v); err != nil { + ctx, cancel := context.WithTimeout(context.Background(), request.Timeout) + err := t.client.Query(ctx, &res, v) + cancel() + + if err != nil { t.log.ERROR.Println(err) continue } @@ -121,5 +103,5 @@ func (t *Tibber) CurrentPrice() (float64, error) { func (t *Tibber) IsCheap() (bool, error) { price, err := t.CurrentPrice() - return price <= t.Cheap, err + return price <= t.cheap, err } diff --git a/tariff/tibber/api.go b/tariff/tibber/api.go deleted file mode 100644 index ff2a749a5..000000000 --- a/tariff/tibber/api.go +++ /dev/null @@ -1,37 +0,0 @@ -package tibber - -import ( - "time" -) - -const URI = "https://api.tibber.com/v1-beta/gql" - -type Home struct { - ID string - TimeZone string - Address Address - MeteringPointData struct { - GridCompany string - } -} - -type Address struct { - Address1, PostalCode, City, Country string -} - -type Subscription struct { - ID string - Status string - PriceInfo struct { - Current PriceInfo - Today []PriceInfo - // Tomorrow []PriceInfo - } -} - -type PriceInfo struct { - Level string - StartsAt time.Time - Total float64 - // Energy, Tax float64 -}