Add Tibber Pulse (#5185)
This commit is contained in:
parent
66abe9e4cd
commit
75470e8f5e
9 changed files with 244 additions and 17 deletions
1
go.mod
1
go.mod
|
|
@ -44,6 +44,7 @@ require (
|
|||
github.com/gregdel/pushover v1.1.0
|
||||
github.com/grid-x/modbus v0.0.0-20221109090852-6bf5f3bc63ea
|
||||
github.com/hashicorp/go-version v1.6.0
|
||||
github.com/hasura/go-graphql-client v0.8.1
|
||||
github.com/imdario/mergo v0.3.13
|
||||
github.com/influxdata/influxdb-client-go/v2 v2.12.0
|
||||
github.com/itchyny/gojq v0.12.9
|
||||
|
|
|
|||
12
go.sum
12
go.sum
|
|
@ -276,6 +276,9 @@ github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V
|
|||
github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A=
|
||||
github.com/go-logfmt/logfmt v0.5.1 h1:otpy5pqBCBZ1ng9RQ0dPu4PN7ba75Y/aA+UpowDyNVA=
|
||||
github.com/go-logfmt/logfmt v0.5.1/go.mod h1:WYhtIu8zTZfxdn5+rREduYbwxfcBr/Vr6KEVveWlfTs=
|
||||
github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
|
||||
github.com/go-logr/logr v1.2.3/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
|
||||
github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
|
||||
github.com/go-ping/ping v1.1.0 h1:3MCGhVX4fyEUuhsfwPrsEdQw6xspHkv5zHsiSoDFZYw=
|
||||
github.com/go-ping/ping v1.1.0/go.mod h1:xIFjORFzTxqIV/tDVGO4eDy/bLuSyawEeojSm3GfRGk=
|
||||
github.com/go-playground/assert/v2 v2.0.1/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=
|
||||
|
|
@ -428,6 +431,10 @@ github.com/gorilla/websocket v1.4.1/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/ad
|
|||
github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
|
||||
github.com/gorilla/websocket v1.5.0 h1:PPwGk2jz7EePpoHN/+ClbZu8SPxiqlu12wZP/3sWmnc=
|
||||
github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
|
||||
github.com/graph-gophers/graphql-go v1.4.0 h1:JE9wveRTSXwJyjdRd6bOQ7Ob5bewTUQ58Jv4OiVdpdE=
|
||||
github.com/graph-gophers/graphql-go v1.4.0/go.mod h1:YtmJZDLbF1YYNrlNAuiO5zAStUWc3XZT07iGsVqe1Os=
|
||||
github.com/graph-gophers/graphql-transport-ws v0.0.2 h1:DbmSkbIGzj8SvHei6n8Mh9eLQin8PtA8xY9eCzjRpvo=
|
||||
github.com/graph-gophers/graphql-transport-ws v0.0.2/go.mod h1:5BVKvFzOd2BalVIBFfnfmHjpJi/MZ5rOj8G55mXvZ8g=
|
||||
github.com/gregdel/pushover v1.1.0 h1:dwHyvrcpZCOS9V1fAnKPaGRRI5OC55cVaKhMybqNsKQ=
|
||||
github.com/gregdel/pushover v1.1.0/go.mod h1:EcaO66Nn1StkpEm1iKtBTV3d2A16SoMsVER1PthX7to=
|
||||
github.com/grid-x/modbus v0.0.0-20210714071042-7af2b65ec03b/go.mod h1:YaK0rKJenZ74vZFcSSLlAQqtG74PMI68eDjpDCDDmTw=
|
||||
|
|
@ -466,6 +473,8 @@ github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO
|
|||
github.com/hashicorp/mdns v1.0.0/go.mod h1:tL+uN++7HEJ6SQLQ2/p+z2pH24WQKWjBPkE0mNTz8vQ=
|
||||
github.com/hashicorp/memberlist v0.1.3/go.mod h1:ajVTdAv/9Im8oMAAj5G31PhhMCZJV2pPBoIllUwCN7I=
|
||||
github.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/JwenrHc=
|
||||
github.com/hasura/go-graphql-client v0.8.1 h1:yU4888urgkW4L47cs+QQDXl3YfVaNraUqym5qsJ41Ms=
|
||||
github.com/hasura/go-graphql-client v0.8.1/go.mod h1:NVifIwv+YFIUYGLQ7SM2/vBbzS/9rFP4vmIf/vf/zXM=
|
||||
github.com/hinshun/vt10x v0.0.0-20220119200601-820417d04eec h1:qv2VnGeEQHchGaZ/u7lxST/RaJw+cv273q79D81Xbog=
|
||||
github.com/hinshun/vt10x v0.0.0-20220119200601-820417d04eec/go.mod h1:Q48J4R4DvxnHolD5P8pOtXigYlRuPLGl6moFx3ulM68=
|
||||
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
|
||||
|
|
@ -672,6 +681,7 @@ github.com/opentracing-contrib/go-observer v0.0.0-20170622124052-a52f23424492/go
|
|||
github.com/opentracing/basictracer-go v1.0.0/go.mod h1:QfBfYuafItcjQuMwinw9GhYKwFXS9KnPs5lxoYwgW74=
|
||||
github.com/opentracing/opentracing-go v1.0.2/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o=
|
||||
github.com/opentracing/opentracing-go v1.1.0/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o=
|
||||
github.com/opentracing/opentracing-go v1.2.0/go.mod h1:GxEUsuufX4nBwe+T+Wl9TAgYrxe9dPLANfrWvHYVTgc=
|
||||
github.com/openzipkin-contrib/zipkin-go-opentracing v0.4.5/go.mod h1:/wsWhb9smxSfWAKL3wpBW7V8scJMt8N8gnaMCS9E/cA=
|
||||
github.com/openzipkin/zipkin-go v0.1.6/go.mod h1:QgAqvLzwWbR/WpD4A3cGpPtJrZXNIiJc5AZX7/PBEpw=
|
||||
github.com/openzipkin/zipkin-go v0.2.1/go.mod h1:NaW6tEwdmWMaCDZzg8sh+IBNOxHMPnhQw8ySjnjRyN4=
|
||||
|
|
@ -886,6 +896,8 @@ go.opencensus.io v0.22.5/go.mod h1:5pWMHQbX5EPX2/62yrJeAkowc+lfs/XD7Uxpq3pI6kk=
|
|||
go.opencensus.io v0.23.0/go.mod h1:XItmlyltB5F7CS4xOC1DcqMoFqwtC6OG2xF7mCv7P7E=
|
||||
go.opencensus.io v0.24.0 h1:y73uSU6J157QMP2kn2r30vwW1A2W2WFwSCGnAVxeaD0=
|
||||
go.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo=
|
||||
go.opentelemetry.io/otel v1.6.3/go.mod h1:7BgNga5fNlF/iZjG06hM3yofffp0ofKCDwSXx1GC4dI=
|
||||
go.opentelemetry.io/otel/trace v1.6.3/go.mod h1:GNJQusJlUgZl9/TQBPKU/Y/ty+0iVB5fjhKeJGZPGFs=
|
||||
go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI=
|
||||
go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
|
||||
go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
|
||||
|
|
|
|||
160
meter/tibber-pulse.go
Normal file
160
meter/tibber-pulse.go
Normal 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
17
meter/tibber/types.go
Normal 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
|
||||
}
|
||||
|
|
@ -6,7 +6,6 @@ import (
|
|||
"github.com/evcc-io/evcc/util"
|
||||
)
|
||||
|
||||
// TP-Link meter implementation
|
||||
func init() {
|
||||
registry.Add("tplink", NewTPLinkFromConfig)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -28,14 +28,14 @@ type Tibber struct {
|
|||
var _ api.Tariff = (*Tibber)(nil)
|
||||
|
||||
func NewTibber(other map[string]interface{}) (*Tibber, error) {
|
||||
t := &Tibber{
|
||||
log: util.NewLogger("tibber"),
|
||||
}
|
||||
t := &Tibber{}
|
||||
|
||||
if err := util.DecodeOther(other, &t); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
t.log = util.NewLogger("tibber").Redact(t.HomeID, t.Token)
|
||||
|
||||
ctx := context.WithValue(
|
||||
context.Background(),
|
||||
oauth2.HTTPClient,
|
||||
|
|
@ -49,21 +49,10 @@ func NewTibber(other map[string]interface{}) (*Tibber, error) {
|
|||
t.client = graphql.NewClient(tibber.URI, client)
|
||||
|
||||
if t.HomeID == "" {
|
||||
var res struct {
|
||||
Viewer struct {
|
||||
Homes []tibber.Home
|
||||
}
|
||||
}
|
||||
|
||||
if err := t.client.Query(context.Background(), &res, nil); err != nil {
|
||||
var err error
|
||||
if t.HomeID, err = t.DefaultHomeID(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if len(res.Viewer.Homes) != 1 {
|
||||
return nil, fmt.Errorf("could not determine home id: %v", res.Viewer.Homes)
|
||||
}
|
||||
|
||||
t.HomeID = res.Viewer.Homes[0].ID
|
||||
}
|
||||
|
||||
go t.Run()
|
||||
|
|
@ -71,6 +60,24 @@ 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 {
|
||||
|
|
|
|||
19
templates/definition/meter/tibber-pulse.yaml
Normal file
19
templates/definition/meter/tibber-pulse.yaml
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
template: tibber-pulse
|
||||
products:
|
||||
- brand: Tibber
|
||||
description:
|
||||
generic: Pulse
|
||||
params:
|
||||
- name: usage
|
||||
choice: ["grid"]
|
||||
- name: token
|
||||
mask: true
|
||||
required: true
|
||||
example: 5K4MVS-OjfWhK_4yrjOlFe1F6kJXPVf7eQYggo8ebAE
|
||||
- name: homeid
|
||||
required: true
|
||||
example: 96a14971-525a-4420-aae9-e5aedaa129ff
|
||||
render: |
|
||||
type: tibber-pulse
|
||||
token: {{ .token }}
|
||||
homeid: {{ .homeid }}
|
||||
11
templates/docs/meter/tibber-pulse_0.yaml
Normal file
11
templates/docs/meter/tibber-pulse_0.yaml
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
product:
|
||||
brand: Tibber
|
||||
description: Pulse
|
||||
render:
|
||||
- usage: grid
|
||||
default: |
|
||||
type: template
|
||||
template: tibber-pulse
|
||||
usage: grid
|
||||
token: 5K4MVS-OjfWhK_4yrjOlFe1F6kJXPVf7eQYggo8ebAE
|
||||
homeid: 96a14971-525a-4420-aae9-e5aedaa129ff
|
||||
|
|
@ -85,6 +85,7 @@
|
|||
"Sonnen",
|
||||
"Sungrow",
|
||||
"Tesla",
|
||||
"Tibber",
|
||||
"TQ",
|
||||
"VARTA",
|
||||
"Victron",
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue