Powerwall: add battery control (#10758)
This commit is contained in:
parent
b2f0d8cf25
commit
91f33f8569
5 changed files with 296 additions and 38 deletions
14
go.mod
14
go.mod
|
|
@ -19,7 +19,7 @@ require (
|
|||
github.com/basgys/goxml2json v1.1.0
|
||||
github.com/basvdlei/gotsmart v0.0.3
|
||||
github.com/benbjohnson/clock v1.3.5
|
||||
github.com/bogosj/tesla v1.1.1
|
||||
github.com/bogosj/tesla v1.3.0
|
||||
github.com/cenkalti/backoff/v4 v4.2.1
|
||||
github.com/cjrd/allocate v0.0.0-20220510215731-986f24f0fb18
|
||||
github.com/cloudfoundry/jibber_jabber v0.0.0-20151120183258-bcc4c8345a21
|
||||
|
|
@ -94,10 +94,10 @@ require (
|
|||
gitlab.com/bboehmke/sunny v0.16.0
|
||||
golang.org/x/crypto/x509roots/fallback v0.0.0-20231030152948-74c2ba9521f1
|
||||
golang.org/x/exp v0.0.0-20231006140011-7918f672742d
|
||||
golang.org/x/net v0.17.0
|
||||
golang.org/x/oauth2 v0.13.0
|
||||
golang.org/x/net v0.18.0
|
||||
golang.org/x/oauth2 v0.14.0
|
||||
golang.org/x/sync v0.4.0
|
||||
golang.org/x/text v0.13.0
|
||||
golang.org/x/text v0.14.0
|
||||
google.golang.org/grpc v1.59.0
|
||||
google.golang.org/protobuf v1.31.0
|
||||
gopkg.in/yaml.v3 v3.0.1
|
||||
|
|
@ -187,11 +187,11 @@ require (
|
|||
github.com/vmihailenco/tagparser/v2 v2.0.0 // indirect
|
||||
gitlab.com/c0b/go-ordered-json v0.0.0-20201030195603-febf46534d5a // indirect
|
||||
go.uber.org/multierr v1.11.0 // indirect
|
||||
golang.org/x/crypto v0.14.0 // indirect
|
||||
golang.org/x/crypto v0.15.0 // indirect
|
||||
golang.org/x/lint v0.0.0-20210508222113-6edffad5e616 // indirect
|
||||
golang.org/x/mod v0.13.0 // indirect
|
||||
golang.org/x/sys v0.13.0 // indirect
|
||||
golang.org/x/term v0.13.0 // indirect
|
||||
golang.org/x/sys v0.14.0 // indirect
|
||||
golang.org/x/term v0.14.0 // indirect
|
||||
golang.org/x/tools v0.14.0 // indirect
|
||||
google.golang.org/appengine v1.6.8 // indirect
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20231030173426-d783a09b4405 // indirect
|
||||
|
|
|
|||
28
go.sum
28
go.sum
|
|
@ -110,8 +110,8 @@ github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kB
|
|||
github.com/bitly/go-simplejson v0.5.0 h1:6IH+V8/tVMab511d5bn4M7EwGXZf9Hj6i2xSwkNEM+Y=
|
||||
github.com/bitly/go-simplejson v0.5.0/go.mod h1:cXHtHw4XUPsvGaxgjIAn8PhEWG9NfngEKAMDJEczWVA=
|
||||
github.com/bmatcuk/doublestar v1.1.1/go.mod h1:UD6OnuiIn0yFxxA2le/rnRU1G4RaI4UvFv1sNto9p6w=
|
||||
github.com/bogosj/tesla v1.1.1 h1:OMh870k3hb7NpoqAg3Donu/IDNEahRo/GUbBOseYqIs=
|
||||
github.com/bogosj/tesla v1.1.1/go.mod h1:U0lGPUZLtWaC1q+lKHlj5Zzj3U+/El2NgzRCz6AFiJc=
|
||||
github.com/bogosj/tesla v1.3.0 h1:6K/4ZMNYXe3toXUwBbBhtYcKwNFnCTKc4YyUj1Pxc2c=
|
||||
github.com/bogosj/tesla v1.3.0/go.mod h1:l10JPNhF1f+61XwzHZZ2uoTINBF9Vo+PVYBtlUnVx6E=
|
||||
github.com/casbin/casbin/v2 v2.1.2/go.mod h1:YcPU1XXisHhLzuxH9coDNf2FbKpjGlbCg3n9yuLkIJQ=
|
||||
github.com/cenkalti/backoff v2.2.1+incompatible/go.mod h1:90ReRw6GdpyfrHakVjL/QHaoyV4aDUVVkXQJJJ3NXXM=
|
||||
github.com/cenkalti/backoff/v4 v4.2.1 h1:y4OZtCnogmCPw98Zjyt5a6+QwPLGkiQsYW5oUqylYbM=
|
||||
|
|
@ -822,8 +822,8 @@ golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b/go.mod h1:T9bdIzuCu7OtxOm
|
|||
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||
golang.org/x/crypto v0.0.0-20220722155217-630584e8d5aa/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
|
||||
golang.org/x/crypto v0.3.0/go.mod h1:hebNnKkNXi2UzZN1eVRvBB7co0a+JxK6XbPiWVs/3J4=
|
||||
golang.org/x/crypto v0.14.0 h1:wBqGXzWJW6m1XrIKlAH0Hs1JJ7+9KBwnIO8v66Q9cHc=
|
||||
golang.org/x/crypto v0.14.0/go.mod h1:MVFd36DqK4CsrnJYDkBA3VC4m2GkXAM0PvzMCn4JQf4=
|
||||
golang.org/x/crypto v0.15.0 h1:frVn1TEaCEaZcn3Tmd7Y2b5KKPaZ+I32Q2OA3kYp5TA=
|
||||
golang.org/x/crypto v0.15.0/go.mod h1:4ChreQoLWfG3xLDer1WdlH5NdlQ3+mwnQq1YTKY+72g=
|
||||
golang.org/x/crypto/x509roots/fallback v0.0.0-20231030152948-74c2ba9521f1 h1:wQ75dCmVn5ExryuIUzbi2MC1/10fUNIL1FP918r4jx8=
|
||||
golang.org/x/crypto/x509roots/fallback v0.0.0-20231030152948-74c2ba9521f1/go.mod h1:kNa9WdvYnzFwC79zRpLRMJbdEFlhyM5RPFBBZp/wWH8=
|
||||
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
|
|
@ -918,8 +918,8 @@ golang.org/x/net v0.2.0/go.mod h1:KqCZLdyyvdV855qA2rE3GC2aiw5xGR5TEjj8smXukLY=
|
|||
golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
|
||||
golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
|
||||
golang.org/x/net v0.9.0/go.mod h1:d48xBJpPfHeWQsugry2m+kC02ZBRGRgulfHnEXEuWns=
|
||||
golang.org/x/net v0.17.0 h1:pVaXccu2ozPjCXewfr1S7xza/zcXTity9cCdXQYSjIM=
|
||||
golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE=
|
||||
golang.org/x/net v0.18.0 h1:mIYleuAkSbHh0tCv7RvjL3F6ZVbLjq4+R7zbOn3Kokg=
|
||||
golang.org/x/net v0.18.0/go.mod h1:/czyP5RqHAH4odGYxBJ1qz0+CE5WZ+2j1YgoEo8F2jQ=
|
||||
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||
|
|
@ -929,8 +929,8 @@ golang.org/x/oauth2 v0.0.0-20200902213428-5d25da1a8d43/go.mod h1:KelEdhl1UZF7XfJ
|
|||
golang.org/x/oauth2 v0.0.0-20201109201403-9fd604954f58/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
|
||||
golang.org/x/oauth2 v0.0.0-20201208152858-08078c50e5b5/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
|
||||
golang.org/x/oauth2 v0.0.0-20210218202405-ba52d332ba99/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
|
||||
golang.org/x/oauth2 v0.13.0 h1:jDDenyj+WgFtmV3zYVoi8aE2BwtXFLWOA67ZfNWftiY=
|
||||
golang.org/x/oauth2 v0.13.0/go.mod h1:/JMhi4ZRXAf4HG9LiNmxvk+45+96RUlVThiH8FzNBn0=
|
||||
golang.org/x/oauth2 v0.14.0 h1:P0Vrf/2538nmC0H+pEQ3MNFRRnVR7RlqyVw+bvm26z0=
|
||||
golang.org/x/oauth2 v0.14.0/go.mod h1:lAtNWgaWfL4cm7j2OV8TxGi9Qb7ECORx8DktCY74OwM=
|
||||
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
|
|
@ -1017,15 +1017,15 @@ golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
|||
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.7.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.13.0 h1:Af8nKPmuFypiUBjVoU9V20FiaFXOcuZI21p0ycVYYGE=
|
||||
golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.14.0 h1:Vz7Qs629MkJkGyHxUlRHizWJRG2j8fbQKjELVSNhy7Q=
|
||||
golang.org/x/sys v0.14.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||
golang.org/x/term v0.2.0/go.mod h1:TVmDHMZPmdnySmBfhjOoOdhjzdE1h4u1VwSiw2l1Nuc=
|
||||
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
|
||||
golang.org/x/term v0.7.0/go.mod h1:P32HKFT3hSsZrRxla30E9HqToFYAQPCMs/zFMBUFqPY=
|
||||
golang.org/x/term v0.13.0 h1:bb+I9cTfFazGW51MZqBVmZy7+JEJMouUHTUSKVQLBek=
|
||||
golang.org/x/term v0.13.0/go.mod h1:LTmsnFJwVN6bCy1rVCoS+qHT1HhALEFxKncY3WNNh4U=
|
||||
golang.org/x/term v0.14.0 h1:LGK9IlZ8T9jvdy6cTdfKUCltatMFOehAQo9SRC46UQ8=
|
||||
golang.org/x/term v0.14.0/go.mod h1:TySc+nGkYR6qt8km8wUhuFRTVSMIX3XPR58y2lC8vww=
|
||||
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
|
|
@ -1038,8 +1038,8 @@ golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ=
|
|||
golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
||||
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
||||
golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
|
||||
golang.org/x/text v0.13.0 h1:ablQoSUd0tRdKxZewP80B+BaqeKJuVhuRxj/dkrun3k=
|
||||
golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
|
||||
golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ=
|
||||
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
|
||||
golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
|
|
|
|||
|
|
@ -1,24 +1,30 @@
|
|||
package meter
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"math"
|
||||
"net/http"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/andig/go-powerwall"
|
||||
"github.com/bogosj/tesla"
|
||||
"github.com/evcc-io/evcc/api"
|
||||
"github.com/evcc-io/evcc/provider"
|
||||
"github.com/evcc-io/evcc/util"
|
||||
"github.com/evcc-io/evcc/util/request"
|
||||
"golang.org/x/oauth2"
|
||||
)
|
||||
|
||||
// PowerWall is the tesla powerwall meter
|
||||
type PowerWall struct {
|
||||
usage string
|
||||
client *powerwall.Client
|
||||
meterG func() (map[string]powerwall.MeterAggregatesData, error)
|
||||
usage string
|
||||
client *powerwall.Client
|
||||
meterG func() (map[string]powerwall.MeterAggregatesData, error)
|
||||
energySite *tesla.EnergySite
|
||||
}
|
||||
|
||||
func init() {
|
||||
|
|
@ -26,15 +32,22 @@ func init() {
|
|||
registry.Add("powerwall", NewPowerWallFromConfig)
|
||||
}
|
||||
|
||||
//go:generate go run ../cmd/tools/decorate.go -f decoratePowerWall -b *PowerWall -r api.Meter -t "api.MeterEnergy,TotalEnergy,func() (float64, error)" -t "api.Battery,Soc,func() (float64, error)" -t "api.BatteryCapacity,Capacity,func() float64"
|
||||
// go:generate go run ../cmd/tools/decorate.go -f decoratePowerWall -b *PowerWall -r api.Meter -t "api.MeterEnergy,TotalEnergy,func() (float64, error)" -t "api.Battery,Soc,func() (float64, error)" -t "api.BatteryCapacity,Capacity,func() float64" -t "api.BatteryController,SetBatteryMode,func(api.BatteryMode) error"
|
||||
|
||||
// NewPowerWallFromConfig creates a PowerWall Powerwall Meter from generic config
|
||||
func NewPowerWallFromConfig(other map[string]interface{}) (api.Meter, error) {
|
||||
cc := struct {
|
||||
URI, Usage, User, Password string
|
||||
Cache time.Duration
|
||||
RefreshToken string
|
||||
SiteId int64
|
||||
battery `mapstructure:",squash"`
|
||||
}{
|
||||
Cache: time.Second,
|
||||
battery: battery{
|
||||
MinSoc: 20,
|
||||
MaxSoc: 95,
|
||||
},
|
||||
}
|
||||
|
||||
if err := util.DecodeOther(other, &cc); err != nil {
|
||||
|
|
@ -57,12 +70,12 @@ func NewPowerWallFromConfig(other map[string]interface{}) (api.Meter, error) {
|
|||
cc.Usage = "solar"
|
||||
}
|
||||
|
||||
return NewPowerWall(cc.URI, cc.Usage, cc.User, cc.Password, cc.Cache)
|
||||
return NewPowerWall(cc.URI, cc.Usage, cc.User, cc.Password, cc.Cache, cc.RefreshToken, cc.SiteId, cc.battery)
|
||||
}
|
||||
|
||||
// NewPowerWall creates a Tesla PowerWall Meter
|
||||
func NewPowerWall(uri, usage, user, password string, cache time.Duration) (api.Meter, error) {
|
||||
log := util.NewLogger("powerwall").Redact(user, password)
|
||||
func NewPowerWall(uri, usage, user, password string, cache time.Duration, refreshToken string, siteId int64, battery battery) (api.Meter, error) {
|
||||
log := util.NewLogger("powerwall").Redact(user, password, refreshToken, strconv.FormatInt(siteId, 10))
|
||||
|
||||
httpClient := &http.Client{
|
||||
Transport: request.NewTripper(log, powerwall.DefaultTransport()),
|
||||
|
|
@ -80,6 +93,49 @@ func NewPowerWall(uri, usage, user, password string, cache time.Duration) (api.M
|
|||
meterG: provider.Cached(client.GetMetersAggregates, cache),
|
||||
}
|
||||
|
||||
var batteryControl bool
|
||||
if refreshToken != "" || siteId != 0 {
|
||||
if refreshToken == "" {
|
||||
return nil, errors.New("missing refresh token")
|
||||
}
|
||||
batteryControl = true
|
||||
}
|
||||
|
||||
if batteryControl {
|
||||
ctx := context.WithValue(context.Background(), oauth2.HTTPClient, request.NewClient(log))
|
||||
|
||||
options := []tesla.ClientOption{tesla.WithToken(&oauth2.Token{
|
||||
RefreshToken: refreshToken,
|
||||
Expiry: time.Now(),
|
||||
})}
|
||||
|
||||
cloudClient, err := tesla.NewClient(ctx, options...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if siteId == 0 {
|
||||
//auto detect energy site ID, picking first
|
||||
products, err := cloudClient.Products()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for _, p := range products {
|
||||
if p.EnergySiteId != 0 {
|
||||
siteId = p.EnergySiteId
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
energySite, err := cloudClient.EnergySite(siteId)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
m.energySite = energySite
|
||||
}
|
||||
|
||||
// decorate api.MeterEnergy
|
||||
var totalEnergy func() (float64, error)
|
||||
if m.usage == "load" || m.usage == "solar" {
|
||||
|
|
@ -102,7 +158,15 @@ func NewPowerWall(uri, usage, user, password string, cache time.Duration) (api.M
|
|||
}
|
||||
}
|
||||
|
||||
return decoratePowerWall(m, totalEnergy, batterySoc, batteryCapacity), nil
|
||||
// decorate api.BatteryController
|
||||
var batModeS func(api.BatteryMode) error
|
||||
if batteryControl {
|
||||
batModeS = battery.BatteryController(m.socG, func(limit float64) error {
|
||||
return m.energySite.SetBatteryReserve(uint64(limit))
|
||||
})
|
||||
}
|
||||
|
||||
return decoratePowerWall(m, totalEnergy, batterySoc, batteryCapacity, batModeS), nil
|
||||
}
|
||||
|
||||
var _ api.Meter = (*PowerWall)(nil)
|
||||
|
|
@ -149,3 +213,13 @@ func (m *PowerWall) batterySoc() (float64, error) {
|
|||
|
||||
return float64(res.Percentage), err
|
||||
}
|
||||
|
||||
// decorate soc
|
||||
func (m *PowerWall) socG() (float64, error) {
|
||||
ess, err := m.energySite.EnergySiteStatus()
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
currentSoc := math.Round(ess.PercentageCharged + 0.5) // .5 ensures we round up
|
||||
return currentSoc, nil
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,12 +6,12 @@ import (
|
|||
"github.com/evcc-io/evcc/api"
|
||||
)
|
||||
|
||||
func decoratePowerWall(base *PowerWall, meterEnergy func() (float64, error), battery func() (float64, error), batteryCapacity func() float64) api.Meter {
|
||||
func decoratePowerWall(base *PowerWall, meterEnergy func() (float64, error), battery func() (float64, error), batteryCapacity func() float64, batteryController func(api.BatteryMode) error) api.Meter {
|
||||
switch {
|
||||
case battery == nil && batteryCapacity == nil && meterEnergy == nil:
|
||||
case battery == nil && batteryCapacity == nil && batteryController == nil && meterEnergy == nil:
|
||||
return base
|
||||
|
||||
case battery == nil && batteryCapacity == nil && meterEnergy != nil:
|
||||
case battery == nil && batteryCapacity == nil && batteryController == nil && meterEnergy != nil:
|
||||
return &struct {
|
||||
*PowerWall
|
||||
api.MeterEnergy
|
||||
|
|
@ -22,7 +22,7 @@ func decoratePowerWall(base *PowerWall, meterEnergy func() (float64, error), bat
|
|||
},
|
||||
}
|
||||
|
||||
case battery != nil && batteryCapacity == nil && meterEnergy == nil:
|
||||
case battery != nil && batteryCapacity == nil && batteryController == nil && meterEnergy == nil:
|
||||
return &struct {
|
||||
*PowerWall
|
||||
api.Battery
|
||||
|
|
@ -33,7 +33,7 @@ func decoratePowerWall(base *PowerWall, meterEnergy func() (float64, error), bat
|
|||
},
|
||||
}
|
||||
|
||||
case battery != nil && batteryCapacity == nil && meterEnergy != nil:
|
||||
case battery != nil && batteryCapacity == nil && batteryController == nil && meterEnergy != nil:
|
||||
return &struct {
|
||||
*PowerWall
|
||||
api.Battery
|
||||
|
|
@ -48,7 +48,7 @@ func decoratePowerWall(base *PowerWall, meterEnergy func() (float64, error), bat
|
|||
},
|
||||
}
|
||||
|
||||
case battery == nil && batteryCapacity != nil && meterEnergy == nil:
|
||||
case battery == nil && batteryCapacity != nil && batteryController == nil && meterEnergy == nil:
|
||||
return &struct {
|
||||
*PowerWall
|
||||
api.BatteryCapacity
|
||||
|
|
@ -59,7 +59,7 @@ func decoratePowerWall(base *PowerWall, meterEnergy func() (float64, error), bat
|
|||
},
|
||||
}
|
||||
|
||||
case battery == nil && batteryCapacity != nil && meterEnergy != nil:
|
||||
case battery == nil && batteryCapacity != nil && batteryController == nil && meterEnergy != nil:
|
||||
return &struct {
|
||||
*PowerWall
|
||||
api.BatteryCapacity
|
||||
|
|
@ -74,7 +74,7 @@ func decoratePowerWall(base *PowerWall, meterEnergy func() (float64, error), bat
|
|||
},
|
||||
}
|
||||
|
||||
case battery != nil && batteryCapacity != nil && meterEnergy == nil:
|
||||
case battery != nil && batteryCapacity != nil && batteryController == nil && meterEnergy == nil:
|
||||
return &struct {
|
||||
*PowerWall
|
||||
api.Battery
|
||||
|
|
@ -89,7 +89,7 @@ func decoratePowerWall(base *PowerWall, meterEnergy func() (float64, error), bat
|
|||
},
|
||||
}
|
||||
|
||||
case battery != nil && batteryCapacity != nil && meterEnergy != nil:
|
||||
case battery != nil && batteryCapacity != nil && batteryController == nil && meterEnergy != nil:
|
||||
return &struct {
|
||||
*PowerWall
|
||||
api.Battery
|
||||
|
|
@ -107,6 +107,142 @@ func decoratePowerWall(base *PowerWall, meterEnergy func() (float64, error), bat
|
|||
meterEnergy: meterEnergy,
|
||||
},
|
||||
}
|
||||
|
||||
case battery == nil && batteryCapacity == nil && batteryController != nil && meterEnergy == nil:
|
||||
return &struct {
|
||||
*PowerWall
|
||||
api.BatteryController
|
||||
}{
|
||||
PowerWall: base,
|
||||
BatteryController: &decoratePowerWallBatteryControllerImpl{
|
||||
batteryController: batteryController,
|
||||
},
|
||||
}
|
||||
|
||||
case battery == nil && batteryCapacity == nil && batteryController != nil && meterEnergy != nil:
|
||||
return &struct {
|
||||
*PowerWall
|
||||
api.BatteryController
|
||||
api.MeterEnergy
|
||||
}{
|
||||
PowerWall: base,
|
||||
BatteryController: &decoratePowerWallBatteryControllerImpl{
|
||||
batteryController: batteryController,
|
||||
},
|
||||
MeterEnergy: &decoratePowerWallMeterEnergyImpl{
|
||||
meterEnergy: meterEnergy,
|
||||
},
|
||||
}
|
||||
|
||||
case battery != nil && batteryCapacity == nil && batteryController != nil && meterEnergy == nil:
|
||||
return &struct {
|
||||
*PowerWall
|
||||
api.Battery
|
||||
api.BatteryController
|
||||
}{
|
||||
PowerWall: base,
|
||||
Battery: &decoratePowerWallBatteryImpl{
|
||||
battery: battery,
|
||||
},
|
||||
BatteryController: &decoratePowerWallBatteryControllerImpl{
|
||||
batteryController: batteryController,
|
||||
},
|
||||
}
|
||||
|
||||
case battery != nil && batteryCapacity == nil && batteryController != nil && meterEnergy != nil:
|
||||
return &struct {
|
||||
*PowerWall
|
||||
api.Battery
|
||||
api.BatteryController
|
||||
api.MeterEnergy
|
||||
}{
|
||||
PowerWall: base,
|
||||
Battery: &decoratePowerWallBatteryImpl{
|
||||
battery: battery,
|
||||
},
|
||||
BatteryController: &decoratePowerWallBatteryControllerImpl{
|
||||
batteryController: batteryController,
|
||||
},
|
||||
MeterEnergy: &decoratePowerWallMeterEnergyImpl{
|
||||
meterEnergy: meterEnergy,
|
||||
},
|
||||
}
|
||||
|
||||
case battery == nil && batteryCapacity != nil && batteryController != nil && meterEnergy == nil:
|
||||
return &struct {
|
||||
*PowerWall
|
||||
api.BatteryCapacity
|
||||
api.BatteryController
|
||||
}{
|
||||
PowerWall: base,
|
||||
BatteryCapacity: &decoratePowerWallBatteryCapacityImpl{
|
||||
batteryCapacity: batteryCapacity,
|
||||
},
|
||||
BatteryController: &decoratePowerWallBatteryControllerImpl{
|
||||
batteryController: batteryController,
|
||||
},
|
||||
}
|
||||
|
||||
case battery == nil && batteryCapacity != nil && batteryController != nil && meterEnergy != nil:
|
||||
return &struct {
|
||||
*PowerWall
|
||||
api.BatteryCapacity
|
||||
api.BatteryController
|
||||
api.MeterEnergy
|
||||
}{
|
||||
PowerWall: base,
|
||||
BatteryCapacity: &decoratePowerWallBatteryCapacityImpl{
|
||||
batteryCapacity: batteryCapacity,
|
||||
},
|
||||
BatteryController: &decoratePowerWallBatteryControllerImpl{
|
||||
batteryController: batteryController,
|
||||
},
|
||||
MeterEnergy: &decoratePowerWallMeterEnergyImpl{
|
||||
meterEnergy: meterEnergy,
|
||||
},
|
||||
}
|
||||
|
||||
case battery != nil && batteryCapacity != nil && batteryController != nil && meterEnergy == nil:
|
||||
return &struct {
|
||||
*PowerWall
|
||||
api.Battery
|
||||
api.BatteryCapacity
|
||||
api.BatteryController
|
||||
}{
|
||||
PowerWall: base,
|
||||
Battery: &decoratePowerWallBatteryImpl{
|
||||
battery: battery,
|
||||
},
|
||||
BatteryCapacity: &decoratePowerWallBatteryCapacityImpl{
|
||||
batteryCapacity: batteryCapacity,
|
||||
},
|
||||
BatteryController: &decoratePowerWallBatteryControllerImpl{
|
||||
batteryController: batteryController,
|
||||
},
|
||||
}
|
||||
|
||||
case battery != nil && batteryCapacity != nil && batteryController != nil && meterEnergy != nil:
|
||||
return &struct {
|
||||
*PowerWall
|
||||
api.Battery
|
||||
api.BatteryCapacity
|
||||
api.BatteryController
|
||||
api.MeterEnergy
|
||||
}{
|
||||
PowerWall: base,
|
||||
Battery: &decoratePowerWallBatteryImpl{
|
||||
battery: battery,
|
||||
},
|
||||
BatteryCapacity: &decoratePowerWallBatteryCapacityImpl{
|
||||
batteryCapacity: batteryCapacity,
|
||||
},
|
||||
BatteryController: &decoratePowerWallBatteryControllerImpl{
|
||||
batteryController: batteryController,
|
||||
},
|
||||
MeterEnergy: &decoratePowerWallMeterEnergyImpl{
|
||||
meterEnergy: meterEnergy,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
|
|
@ -128,6 +264,14 @@ func (impl *decoratePowerWallBatteryCapacityImpl) Capacity() float64 {
|
|||
return impl.batteryCapacity()
|
||||
}
|
||||
|
||||
type decoratePowerWallBatteryControllerImpl struct {
|
||||
batteryController func(api.BatteryMode) error
|
||||
}
|
||||
|
||||
func (impl *decoratePowerWallBatteryControllerImpl) SetBatteryMode(m api.BatteryMode) error {
|
||||
return impl.batteryController(m)
|
||||
}
|
||||
|
||||
type decoratePowerWallMeterEnergyImpl struct {
|
||||
meterEnergy func() (float64, error)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,6 +3,22 @@ products:
|
|||
- brand: Tesla
|
||||
description:
|
||||
generic: Powerwall
|
||||
requirements:
|
||||
description:
|
||||
de: |
|
||||
Um die optionale Entladesteuerung der Battery zu nutzen wird ein `refresh` Token für die Kommunikation mit der Tesla API benötigt.
|
||||
|
||||
Folgende Apps ermöglichen das Erstellen des Tokens:
|
||||
- [Auth app for Tesla (iOS)](https://apps.apple.com/us/app/auth-app-for-tesla/id1552058613#?platform=iphone)
|
||||
- [Tesla Tokens (Android)](https://play.google.com/store/apps/details?id=net.leveugle.teslatokens)
|
||||
- [Tesla Auth (macOS, Linux)](https://github.com/adriankumpf/tesla_auth)
|
||||
en: |
|
||||
To use the optional battery control you need to generate a `refresh` token for communicating with the Tesla API.
|
||||
|
||||
The following apps allow to create the token:
|
||||
- [Auth app for Tesla (iOS)](https://apps.apple.com/us/app/auth-app-for-tesla/id1552058613#?platform=iphone)
|
||||
- [Tesla Tokens (Android)](https://play.google.com/store/apps/details?id=net.leveugle.teslatokens)
|
||||
- [Tesla Auth (macOS, Linux)](https://github.com/adriankumpf/tesla_auth)
|
||||
params:
|
||||
- name: usage
|
||||
choice: ["grid", "pv", "battery"]
|
||||
|
|
@ -13,9 +29,33 @@ params:
|
|||
help:
|
||||
en: Password of the user "customer"
|
||||
de: Passwort des Benutzers "Kunde"
|
||||
- name: refreshToken
|
||||
help:
|
||||
en: "See https://docs.evcc.io/en/docs/devices/meters#tesla-powerwall"
|
||||
de: "Siehe https://docs.evcc.io/docs/devices/meters#tesla-powerwall"
|
||||
- name: siteId
|
||||
help:
|
||||
en: optional product identifier of the energy site, use to override autodectction
|
||||
de: optionale Product ID dieser Energy Site, zum Übersteuern der automatischen Erkennung
|
||||
- name: minSoc
|
||||
type: number
|
||||
advanced: true
|
||||
help:
|
||||
en: the battery reserve percentage to reset to when switching the Battery back to normal operations
|
||||
de: die Energyreserve auf die zurückgesetzt wird, wenn die Entladung wieder freigegeben wird
|
||||
- name: maxSoc
|
||||
type: number
|
||||
advanced: true
|
||||
help:
|
||||
en: the battery reserve percentage to set to when switching the Battery to charge mode
|
||||
de: die Energyreserve auf die gesetzt wird, wenn Batterieladung angefordert wird
|
||||
render: |
|
||||
type: powerwall
|
||||
uri: {{ .host }}
|
||||
usage: {{ .usage }}
|
||||
user: customer
|
||||
password: {{ .password }} # for user 'customer'
|
||||
refreshToken: {{ .refreshToken }}
|
||||
siteId: {{ .siteId }}
|
||||
minSoc: {{ .minSoc }}
|
||||
maxSoc: {{ .maxSoc }}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue