From e48927af3220cedbfc36418c608251fdae75c814 Mon Sep 17 00:00:00 2001 From: andig Date: Thu, 9 Oct 2025 08:26:04 +0200 Subject: [PATCH] Volvo Connected: require vin (BC) (#24247) --- plugin/auth/oauth.go | 1 + .../definition/vehicle/volvo-connected.yaml | 1 + vehicle/volvo-connected.go | 21 ++++++++++++++----- vehicle/volvo/connected/provider.go | 16 +++++++++++--- 4 files changed, 31 insertions(+), 8 deletions(-) diff --git a/plugin/auth/oauth.go b/plugin/auth/oauth.go index 28c8b3da9..43cebb22b 100644 --- a/plugin/auth/oauth.go +++ b/plugin/auth/oauth.go @@ -178,6 +178,7 @@ func (o *OAuth) Token() (*oauth2.Token, error) { if err != nil { // force logout if strings.Contains(err.Error(), "invalid_grant") && settings.Exists(o.subject) { + o.token = nil o.onlineC <- false settings.Delete(o.subject) } diff --git a/templates/definition/vehicle/volvo-connected.yaml b/templates/definition/vehicle/volvo-connected.yaml index 6722744e0..ac1dc3061 100644 --- a/templates/definition/vehicle/volvo-connected.yaml +++ b/templates/definition/vehicle/volvo-connected.yaml @@ -49,6 +49,7 @@ params: example: "https://evcc.example.org/providerauth/callback" - name: vin example: WF0FXX... + required: true - name: accessToken deprecated: true - name: refreshToken diff --git a/vehicle/volvo-connected.go b/vehicle/volvo-connected.go index d96af176b..f49cf984c 100644 --- a/vehicle/volvo-connected.go +++ b/vehicle/volvo-connected.go @@ -2,6 +2,7 @@ package vehicle import ( "context" + "errors" "time" "github.com/evcc-io/evcc/api" @@ -37,21 +38,31 @@ func NewVolvoConnectedFromConfig(ctx context.Context, other map[string]interface return nil, err } - log := util.NewLogger("volvo-connected").Redact(cc.VIN, cc.VccApiKey) + if cc.VccApiKey == "" { + return nil, errors.New("missing vccapikey") + } + + if cc.VIN == "" { + return nil, errors.New("missing vin") + } + + if err := cc.Credentials.Error(); err != nil { + return nil, err + } + + log := util.NewLogger("volvo-connected").Redact(cc.VIN, cc.Credentials.ID, cc.Credentials.Secret, cc.VccApiKey) oc := connected.Oauth2Config(cc.Credentials.ID, cc.Credentials.Secret, cc.RedirectUri) - ts, err := auth.NewOauth(ctx, cc.embed.GetTitle(), oc) + ts, err := auth.NewOauth(ctx, "Volvo", oc) if err != nil { return nil, err } api := connected.NewAPI(log, cc.VccApiKey, ts) - cc.VIN, err = ensureVehicle(cc.VIN, api.Vehicles) - v := &VolvoConnected{ embed: &cc.embed, - Provider: connected.NewProvider(api, cc.VIN, cc.Cache), + Provider: connected.NewProvider(api, ts, cc.VIN, cc.Cache), } return v, err diff --git a/vehicle/volvo/connected/provider.go b/vehicle/volvo/connected/provider.go index a11e3e303..78e27e3a7 100644 --- a/vehicle/volvo/connected/provider.go +++ b/vehicle/volvo/connected/provider.go @@ -6,6 +6,7 @@ import ( "github.com/evcc-io/evcc/api" "github.com/evcc-io/evcc/util" + "golang.org/x/oauth2" ) // Provider implements the vehicle api @@ -14,14 +15,23 @@ type Provider struct { odoG func() (OdometerState, error) } +func tokenGuard[T any](fun func(string) (T, error), ts oauth2.TokenSource, vin string) (T, error) { + // don't try as long as there's no token + if _, err := ts.Token(); err != nil { + var zero T + return zero, api.ErrNotAvailable + } + return fun(vin) +} + // NewProvider creates a vehicle api provider -func NewProvider(api *API, vin string, cache time.Duration) *Provider { +func NewProvider(api *API, ts oauth2.TokenSource, vin string, cache time.Duration) *Provider { impl := &Provider{ statusG: util.Cached(func() (EnergyState, error) { - return api.EnergyState(vin) + return tokenGuard(api.EnergyState, ts, vin) }, cache), odoG: util.Cached(func() (OdometerState, error) { - return api.OdometerState(vin) + return tokenGuard(api.OdometerState, ts, vin) }, cache), } return impl