Vaillant: share token source across chargers on the same account (#32471)

This commit is contained in:
Johannes 2026-08-03 14:19:41 +02:00 • committed by GitHub
parent 6f430d4a99
commit dc1ac7c6dd
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 49 additions and 4 deletions

View file

@ -33,7 +33,6 @@ import (
"github.com/evcc-io/evcc/util"
"github.com/evcc-io/evcc/util/request"
"github.com/samber/lo"
"golang.org/x/oauth2"
)
func init() {
@ -79,15 +78,14 @@ func NewVaillantFromConfig(ctx context.Context, other map[string]any) (api.Charg
}
log := util.NewLogger("vaillant").Redact(cc.User, cc.Password)
logCtx := context.WithValue(ctx, oauth2.HTTPClient, request.NewClient(log))
oc := sensonet.Oauth2ConfigForRealm(cc.Realm)
token, err := vaillant.Login(logCtx, log, oc, cc.User, cc.Password)
ts, err := vaillant.Identity(log, oc, cc.Realm, cc.User, cc.Password)
if err != nil {
return nil, err
}
conn, err := sensonet.NewConnection(oc.TokenSource(logCtx, token), sensonet.WithHttpClient(request.NewClient(log)))
conn, err := sensonet.NewConnection(ts, sensonet.WithHttpClient(request.NewClient(log)))
if err != nil {
return nil, err
}

View file

@ -0,0 +1,47 @@
package vaillant
import (
"context"
"strings"
"sync"
"github.com/WulfgarW/sensonet"
"github.com/evcc-io/evcc/util"
"github.com/evcc-io/evcc/util/request"
"golang.org/x/oauth2"
)
var (
mu sync.Mutex
identities = make(map[string]oauth2.TokenSource)
)
// Identity returns an oauth2 token source shared by all Vaillant chargers on the
// same myVaillant account. The login is performed once while holding the lock,
// which serialises concurrent startups so the parallel login flows can no longer
// clobber each other's session (#30625).
func Identity(log *util.Logger, oc *sensonet.Oauth2Config, realm, user, password string) (oauth2.TokenSource, error) {
// serialise instance handling
mu.Lock()
defer mu.Unlock()
// reuse identity instance
subject := "vaillant." + strings.ToLower(realm) + "." + strings.ToLower(user)
if ts, ok := identities[subject]; ok {
return ts, nil
}
// decoupled from the calling charger's context, which is bounded by a timeout
// during device creation and would otherwise cancel the shared token refresh
ctx := context.WithValue(context.Background(), oauth2.HTTPClient, request.NewClient(log))
token, err := Login(ctx, log, oc, user, password)
if err != nil {
return nil, err
}
ts := oc.TokenSource(ctx, token)
identities[subject] = ts
return ts, nil
}