Add Tibber Pulse (#5185)

This commit is contained in:
andig 2022-11-17 18:36:16 +01:00
parent 66abe9e4cd
commit 75470e8f5e
9 changed files with 244 additions and 17 deletions

160
meter/tibber-pulse.go Normal file
View file

@ -0,0 +1,160 @@
package meter
import (
"encoding/json"
"sync"
"time"
"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"
)
func init() {
registry.Add("tibber-pulse", NewTibberFromConfig)
}
type Tibber struct {
mu sync.Mutex
log *util.Logger
updated time.Time
live tibber.LiveMeasurement
}
func NewTibberFromConfig(other map[string]interface{}) (api.Meter, error) {
var cc struct {
Token string
HomeID string
}
if err := util.DecodeOther(other, &cc); err != nil {
return nil, err
}
t := &Tibber{
log: util.NewLogger("pulse").Redact(cc.HomeID, cc.Token),
}
// TODO
// https://developer.tibber.com/docs/overview#breaking-websocket-change
// https://github.com/hasura/go-graphql-client/issues/38
// 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 res struct {
// Viewer struct {
// WebsocketSubscriptionUrl string
// }
// }
// if err := urlClient.Query(context.Background(), &res, nil); err != nil {
// panic(err)
// }
// println(res.Viewer.WebsocketSubscriptionUrl)
client := graphql.NewSubscriptionClient(tibber.SubscriptionURI).
WithConnectionParams(map[string]any{
"token": cc.Token,
}).
WithLog(t.log.TRACE.Println)
// Run the client
go func() {
if err := client.Run(); err != nil {
panic(err)
}
println("running")
}()
// subscribe
var query struct {
tibber.LiveMeasurement `graphql:"liveMeasurement(homeId: $homeId)"`
}
var once sync.Once
recv := make(chan struct{})
errC := make(chan error)
_, err := client.Subscribe(&query, map[string]any{
"homeId": graphql.ID(cc.HomeID),
}, func(data []byte, err error) error {
if err != nil {
select {
case errC <- err:
default:
}
return nil
}
var res struct {
LiveMeasurement tibber.LiveMeasurement
}
if err := json.Unmarshal(data, &res); err != nil {
t.log.ERROR.Println(err)
return nil
}
once.Do(func() {
close(recv)
})
t.mu.Lock()
t.live = res.LiveMeasurement
t.updated = time.Now()
t.mu.Unlock()
return nil
})
// wait for connection
if err == nil {
select {
case <-recv:
case <-time.After(request.Timeout):
err = api.ErrTimeout
case err = <-errC:
}
}
return t, err
}
// CurrentPower implements the api.Meter interface
func (t *Tibber) CurrentPower() (float64, error) {
t.mu.Lock()
defer t.mu.Unlock()
if time.Since(t.updated) > request.Timeout {
return 0, api.ErrTimeout
}
return t.live.Power, nil
}
var _ api.MeterCurrent = (*Tibber)(nil)
// Currents implements the api.MeterCurrent interface
func (t *Tibber) Currents() (float64, float64, float64, error) {
t.mu.Lock()
defer t.mu.Unlock()
if time.Since(t.updated) > request.Timeout {
return 0, 0, 0, api.ErrTimeout
}
return t.live.CurrentL1, t.live.CurrentL2, t.live.CurrentL3, nil
}

17
meter/tibber/types.go Normal file
View file

@ -0,0 +1,17 @@
package tibber
const SubscriptionURI = "wss://api.tibber.com/v1-beta/gql/subscriptions"
type LiveMeasurement struct {
// Timestamp time.Time
Power float64
LastMeterConsumption float64
LastMeterProduction float64
CurrentL1, CurrentL2, CurrentL3 float64
// Currency string
// AccumulatedConsumption float64
// AccumulatedCost float64
// MinPower float64
// AveragePower float64
// MaxPower float64
}

View file

@ -6,7 +6,6 @@ import (
"github.com/evcc-io/evcc/util"
)
// TP-Link meter implementation
func init() {
registry.Add("tplink", NewTPLinkFromConfig)
}