Tesla: require personal developer account (#17982)
This commit is contained in:
parent
bc6e0350c6
commit
10b9f318c7
19 changed files with 105 additions and 103 deletions
3
.github/workflows/nightly.yml
vendored
3
.github/workflows/nightly.yml
vendored
|
|
@ -68,8 +68,6 @@ jobs:
|
|||
context: .
|
||||
platforms: linux/amd64,linux/arm64,linux/arm/v6
|
||||
push: true
|
||||
build-args: |
|
||||
TESLA_CLIENT_ID=${{ secrets.TESLA_CLIENT_ID }}
|
||||
tags: |
|
||||
evcc/evcc:nightly
|
||||
|
||||
|
|
@ -106,7 +104,6 @@ jobs:
|
|||
args: --snapshot -f .goreleaser-nightly.yml --clean
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
TESLA_CLIENT_ID: ${{ secrets.TESLA_CLIENT_ID }}
|
||||
|
||||
- uses: actions/setup-python@v5
|
||||
with:
|
||||
|
|
|
|||
2
.github/workflows/release.yml
vendored
2
.github/workflows/release.yml
vendored
|
|
@ -48,7 +48,6 @@ jobs:
|
|||
push: true
|
||||
build-args: |
|
||||
RELEASE=1
|
||||
TESLA_CLIENT_ID=${{ secrets.TESLA_CLIENT_ID }}
|
||||
tags: ${{ steps.meta.outputs.tags }}
|
||||
|
||||
apt:
|
||||
|
|
@ -100,7 +99,6 @@ jobs:
|
|||
env:
|
||||
# use GH_TOKEN for access to evcc-io/homebrew-tap
|
||||
GITHUB_TOKEN: ${{ secrets.GH_TOKEN }}
|
||||
TESLA_CLIENT_ID: ${{ secrets.TESLA_CLIENT_ID }}
|
||||
|
||||
- uses: actions/setup-python@v5
|
||||
with:
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@ builds:
|
|||
- -trimpath
|
||||
- -tags=release
|
||||
ldflags:
|
||||
- -X github.com/evcc-io/evcc/server.Version={{ .Tag }} -X github.com/evcc-io/evcc/server.Commit={{ .ShortCommit }} -X github.com/evcc-io/evcc/vehicle/tesla.TESLA_CLIENT_ID={{ .Env.TESLA_CLIENT_ID }} -s -w
|
||||
- -X github.com/evcc-io/evcc/server.Version={{ .Tag }} -X github.com/evcc-io/evcc/server.Commit={{ .ShortCommit }} -s -w
|
||||
env:
|
||||
- CGO_ENABLED=0
|
||||
goos:
|
||||
|
|
@ -36,11 +36,11 @@ builds:
|
|||
- goos: darwin
|
||||
goarch: arm64
|
||||
ldflags:
|
||||
- -X github.com/evcc-io/evcc/server.Version={{ .Tag }} -X github.com/evcc-io/evcc/server.Commit={{ .ShortCommit }} -X github.com/evcc-io/evcc/vehicle/tesla.TESLA_CLIENT_ID={{ .Env.TESLA_CLIENT_ID }} -s -w -B gobuildid
|
||||
- -X github.com/evcc-io/evcc/server.Version={{ .Tag }} -X github.com/evcc-io/evcc/server.Commit={{ .ShortCommit }} -s -w -B gobuildid
|
||||
- goos: darwin
|
||||
goarch: amd64
|
||||
ldflags:
|
||||
- -X github.com/evcc-io/evcc/server.Version={{ .Tag }} -X github.com/evcc-io/evcc/server.Commit={{ .ShortCommit }} -X github.com/evcc-io/evcc/vehicle/tesla.TESLA_CLIENT_ID={{ .Env.TESLA_CLIENT_ID }} -s -w -B gobuildid
|
||||
- -X github.com/evcc-io/evcc/server.Version={{ .Tag }} -X github.com/evcc-io/evcc/server.Commit={{ .ShortCommit }} -s -w -B gobuildid
|
||||
|
||||
archives:
|
||||
- builds:
|
||||
|
|
|
|||
|
|
@ -14,7 +14,7 @@ builds:
|
|||
- -trimpath
|
||||
- -tags=release
|
||||
ldflags:
|
||||
- -X github.com/evcc-io/evcc/server.Version={{ .Version }} -X github.com/evcc-io/evcc/vehicle/tesla.TESLA_CLIENT_ID={{ .Env.TESLA_CLIENT_ID }} -s -w
|
||||
- -X github.com/evcc-io/evcc/server.Version={{ .Version }} -s -w
|
||||
env:
|
||||
- CGO_ENABLED=0
|
||||
goos:
|
||||
|
|
@ -41,11 +41,11 @@ builds:
|
|||
- goos: darwin
|
||||
goarch: arm64
|
||||
ldflags:
|
||||
- -X github.com/evcc-io/evcc/server.Version={{ .Version }} -X github.com/evcc-io/evcc/vehicle/tesla.TESLA_CLIENT_ID={{ .Env.TESLA_CLIENT_ID }} -s -w -B gobuildid
|
||||
- -X github.com/evcc-io/evcc/server.Version={{ .Version }} -s -w -B gobuildid
|
||||
- goos: darwin
|
||||
goarch: amd64
|
||||
ldflags:
|
||||
- -X github.com/evcc-io/evcc/server.Version={{ .Version }} -X github.com/evcc-io/evcc/vehicle/tesla.TESLA_CLIENT_ID={{ .Env.TESLA_CLIENT_ID }} -s -w -B gobuildid
|
||||
- -X github.com/evcc-io/evcc/server.Version={{ .Version }} -s -w -B gobuildid
|
||||
|
||||
env:
|
||||
- CGO_ENABLED=0
|
||||
|
|
|
|||
|
|
@ -12,7 +12,10 @@ var ErrMustRetry = errors.New("must retry")
|
|||
var ErrSponsorRequired = errors.New("sponsorship required, see https://github.com/evcc-io/evcc#sponsorship")
|
||||
|
||||
// ErrMissingCredentials indicates that user/password are missing
|
||||
var ErrMissingCredentials = errors.New("missing credentials")
|
||||
var ErrMissingCredentials = errors.New("missing user/password credentials")
|
||||
|
||||
// ErrMissingToken indicates that access/refresh tokens are missing
|
||||
var ErrMissingToken = errors.New("missing token credentials")
|
||||
|
||||
// ErrOutdated indicates that result is outdated
|
||||
var ErrOutdated = errors.New("outdated")
|
||||
|
|
|
|||
|
|
@ -4,11 +4,14 @@ import (
|
|||
"context"
|
||||
"testing"
|
||||
|
||||
"github.com/evcc-io/evcc/api"
|
||||
"github.com/evcc-io/evcc/util/templates"
|
||||
"github.com/evcc-io/evcc/util/test"
|
||||
)
|
||||
|
||||
var acceptable = []string{
|
||||
api.ErrMissingCredentials.Error(),
|
||||
api.ErrMissingToken.Error(),
|
||||
"invalid plugin source: ...",
|
||||
"missing mqtt broker configuration",
|
||||
"mqtt not configured",
|
||||
|
|
@ -29,7 +32,6 @@ var acceptable = []string{
|
|||
"sponsorship required, see https://github.com/evcc-io/evcc#sponsorship",
|
||||
"eebus not configured",
|
||||
"context deadline exceeded",
|
||||
"missing credentials",
|
||||
"timeout", // ocpp
|
||||
"must have uri and password", // Wattpilot
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ import (
|
|||
"net/url"
|
||||
"strings"
|
||||
|
||||
"github.com/evcc-io/evcc/api"
|
||||
"github.com/evcc-io/evcc/util"
|
||||
"github.com/insomniacslk/tapo"
|
||||
)
|
||||
|
|
@ -33,7 +34,7 @@ func NewConnection(uri, user, password string) (*Connection, error) {
|
|||
}
|
||||
|
||||
if user == "" || password == "" {
|
||||
return nil, fmt.Errorf("missing user or password")
|
||||
return nil, api.ErrMissingCredentials
|
||||
}
|
||||
|
||||
log := util.NewLogger("tapo").Redact(user, password)
|
||||
|
|
|
|||
|
|
@ -4,18 +4,19 @@ import (
|
|||
"context"
|
||||
"testing"
|
||||
|
||||
"github.com/evcc-io/evcc/api"
|
||||
"github.com/evcc-io/evcc/util/templates"
|
||||
"github.com/evcc-io/evcc/util/test"
|
||||
)
|
||||
|
||||
var acceptable = []string{
|
||||
api.ErrMissingCredentials.Error(),
|
||||
api.ErrMissingToken.Error(),
|
||||
"invalid plugin source: ...",
|
||||
"missing mqtt broker configuration",
|
||||
"missing token",
|
||||
"mqtt not configured",
|
||||
"not a SunSpec device",
|
||||
"connect: connection refused", // sockets
|
||||
"missing credentials", // sockets
|
||||
"power: timeout", // sockets
|
||||
"missing password", // Powerwall
|
||||
"connect: no route to host",
|
||||
|
|
|
|||
|
|
@ -3,7 +3,6 @@ package meter
|
|||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"net/http"
|
||||
"strings"
|
||||
"time"
|
||||
|
|
@ -38,7 +37,7 @@ func NewTibberFromConfig(ctx context.Context, other map[string]interface{}) (api
|
|||
}
|
||||
|
||||
if cc.Token == "" {
|
||||
return nil, errors.New("missing token")
|
||||
return nil, api.ErrMissingToken
|
||||
}
|
||||
|
||||
log := util.NewLogger("pulse").Redact(cc.Token, cc.HomeID)
|
||||
|
|
|
|||
|
|
@ -44,7 +44,7 @@ func NewAmberFromConfig(other map[string]interface{}) (api.Tariff, error) {
|
|||
}
|
||||
|
||||
if cc.Token == "" {
|
||||
return nil, errors.New("missing token")
|
||||
return nil, api.ErrMissingToken
|
||||
}
|
||||
|
||||
if cc.SiteID == "" {
|
||||
|
|
|
|||
|
|
@ -4,16 +4,18 @@ import (
|
|||
"context"
|
||||
"testing"
|
||||
|
||||
"github.com/evcc-io/evcc/api"
|
||||
"github.com/evcc-io/evcc/util/templates"
|
||||
"github.com/evcc-io/evcc/util/test"
|
||||
)
|
||||
|
||||
var acceptable = []string{
|
||||
"missing token", // amber, tibber
|
||||
"invalid zipcode", // grünstromindex
|
||||
"invalid apikey format", // octopusenergy
|
||||
"missing region", // octopusenergy
|
||||
"missing securitytoken", // entsoe
|
||||
api.ErrMissingCredentials.Error(),
|
||||
api.ErrMissingToken.Error(),
|
||||
"invalid zipcode", // grünstromindex
|
||||
"invalid apikey format", // octopusenergy
|
||||
"missing region", // octopusenergy
|
||||
"missing securitytoken", // entsoe
|
||||
"cannot define region and postcode simultaneously", // ngeso
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -2,7 +2,6 @@ package tariff
|
|||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"slices"
|
||||
"sync"
|
||||
"time"
|
||||
|
|
@ -41,7 +40,7 @@ func NewTibberFromConfig(other map[string]interface{}) (api.Tariff, error) {
|
|||
}
|
||||
|
||||
if cc.Token == "" {
|
||||
return nil, errors.New("missing token")
|
||||
return nil, api.ErrMissingToken
|
||||
}
|
||||
|
||||
if err := cc.init(); err != nil {
|
||||
|
|
|
|||
|
|
@ -5,22 +5,39 @@ products:
|
|||
requirements:
|
||||
description:
|
||||
de: |
|
||||
Benötigt `access` und `refresh` Tokens. Diese können über [tesla.evcc.io](https://tesla.evcc.io) erstellt werden.
|
||||
Die Steuerung von Fahrzeugen im Zusammenspiel mit einem Tesla Wall Connector erfolgt über einen evcc Proxy-Server und benötigt ein Sponsor Token. Der virtuelle evcc Schlüssel muss auf dem Fahrzeug installiert sein.
|
||||
Siehe [tesla.evcc.io](https://tesla.evcc.io).
|
||||
Tesla bietet eine offizielle, aber kostenpflichtige Fahrzeug-API an.
|
||||
Für private Nutzung kannst du dir einen [Tesla Developer Account](https://developer.tesla.com/) erstellen und erhältst ein monatliches API-Guthaben von $10. Das ist für die gängigen evcc-Anwendungsfälle in der Regel ausreichend.
|
||||
|
||||
Die Anleitung von [myteslamate.com](https://www.myteslamate.com/tesla-api-application-registration/) erklärt den Prozess und generiert dir kostenfrei die für evcc benötigten Access- und Refresh-Token.
|
||||
Mit diesem Tokenpaar und deiner im Tesla Developer Account erstellten Client ID kann evcc direkt mit der Tesla API kommunizieren. Dein verbrauchtes Guthaben kannst du bei Tesla einsehen.
|
||||
|
||||
Für die Nutzung des "Tesla Wall Connectors" benötigst du einen öffentlichen Command-Proxy-Server. [myteslamate.com](https://app.myteslamate.com/) stellt diesen Dienst für 12€/Jahr bereit. Konfiguriere dafür bei myteslamate.com die Command-Berechtigungen und trage das Proxy-Token hier ein. Start-, Stopp- und Stromstärken-Kommandos werden über diesen Proxy an Tesla geschickt.
|
||||
en: |
|
||||
Tesla `access` and `refresh` tokens are required. These can be generated through [tesla.evcc.io](https://tesla.evcc.io).
|
||||
Controlling vehicles in conjunction with a Tesla Wall Connector is done via an evcc proxy server and requires a sponsor token. The evcc virtual key must be installed on the vehicle.
|
||||
See [tesla.evcc.io](https://tesla.evcc.io).
|
||||
Tesla offers an official, but paid vehicle API.
|
||||
For private use, you can create a [Tesla Developer Account](https://developer.tesla.com/) and receive a monthly API credit of $10. This is usually sufficient for the common evcc use cases.
|
||||
|
||||
The [myteslamate.com](https://www.myteslamate.com/tesla-api-application-registration/) guide explains the process and generates a free Access and Refresh Token. With this token pair and your Client ID created in the Tesla Developer Account, evcc can directly communicate with the Tesla API. You can see your used credit at Tesla.
|
||||
|
||||
To use a Tesla Wall Connector, you need a public Command Proxy Server. [myteslamate.com](https://app.myteslamate.com/) provides this service for 12€/year. Configure the Command permissions at myteslamate.com and enter the Proxy Token here. Start, stopp and current commands are sent to Tesla via this proxy.
|
||||
evcc: ["sponsorship"]
|
||||
params:
|
||||
- preset: vehicle-common
|
||||
- name: clientId
|
||||
help:
|
||||
en: Client ID of your [Tesla Developer App](https://developer.tesla.com/dashboard).
|
||||
de: Kunden ID deiner [Tesla Developer App](https://developer.tesla.com/dashboard).
|
||||
required: true
|
||||
advanced: true
|
||||
- name: accessToken
|
||||
help:
|
||||
en: From [myteslamate.com](https://app.myteslamate.com/).
|
||||
de: Von [myteslamate.com](https://app.myteslamate.com/).
|
||||
required: true
|
||||
mask: true
|
||||
- name: refreshToken
|
||||
help:
|
||||
en: From [myteslamate.com](https://app.myteslamate.com/).
|
||||
de: Von [myteslamate.com](https://app.myteslamate.com/).
|
||||
required: true
|
||||
mask: true
|
||||
- name: vin
|
||||
|
|
@ -28,11 +45,16 @@ params:
|
|||
- name: control
|
||||
deprecated: true
|
||||
- name: commandProxy
|
||||
default: https://tesla.evcc.io/
|
||||
default: https://api.myteslamate.com
|
||||
advanced: true
|
||||
help:
|
||||
en: "When using a TWC3 (or other 'dumb' charger not capable of control), evcc can manage the charge directly by communicating with the vehicle through a Command Proxy. By default, the proxy provided by evcc is used. With this parameter, you set the base URL of a custom Command Proxy to use instead of the default evcc one. See for example https://github.com/wimaha/TeslaBleHttpProxy for a proxy sending commands via bluetooth."
|
||||
de: "Bei Verwendung eines TWC3 (oder eines anderen 'dummen' Ladegeräts, das nicht steuerbar ist) kann evcc die Ladung direkt verwalten, indem es über einen Command Proxy mit dem Fahrzeug kommuniziert. Standardmäßig wird der von evcc bereitgestellte Proxy verwendet. Dieses parameter setzt die Basis-URL eines benutzerdefinierten Command Proxy, der anstelle des standardmäßigen evcc-Proxy verwendet werden soll. Siehe zum Beispiel https://github.com/wimaha/TeslaBleHttpProxy für einen Proxy, der Kommandos über Bluetooth sendet."
|
||||
en: "When using a TWC3 (or other 'dumb' charger not capable of control), evcc can manage the charge directly by communicating with the vehicle through a Command Proxy. By default the [myteslamate.com](https://app.myteslamate.com/) proxy is used. With this parameter, you set the base URL of a custom Command Proxy. See for example [TeslaBleHttpProxy](https://github.com/wimaha/TeslaBleHttpProxy) for a proxy sending commands via bluetooth."
|
||||
de: "Bei Verwendung eines TWC3 (oder eines anderen 'dummen' Ladegeräts, das nicht steuerbar ist) kann evcc die Ladung direkt verwalten, indem es über einen Command Proxy mit dem Fahrzeug kommuniziert. Standardmäßig wird der [myteslamate.com](https://app.myteslamate.com/) Proxy verwendet. Mit diesem Parameter kannst du die Basis-URL ändern. Siehe zum Beispiel [TeslaBleHttpProxy](https://github.com/wimaha/TeslaBleHttpProxy) für einen Proxy, der Kommandos über Bluetooth sendet."
|
||||
- name: proxyToken
|
||||
advanced: true
|
||||
help:
|
||||
en: Token for the [myteslamate.com](https://app.myteslamate.com/) command proxy (12€/year fee). Ensure, that you've installed their Virtual Key and granted 'Charge Start', 'Charge Stop' and 'Set Charging Amps' permissions.
|
||||
de: Token für den Command Proxy von [myteslamate.com](https://app.myteslamate.com/) (12€/Jahr). Stelle sicher, dass du den Virtual Key installiert hast und die Berechtigungen 'Ladung starten', 'Ladung stoppen' und 'Ladestrom setzen' erteilt hast.
|
||||
- name: cache
|
||||
default: 15m
|
||||
render: |
|
||||
|
|
@ -44,6 +66,7 @@ render: |
|
|||
access: {{ .accessToken }}
|
||||
refresh: {{ .refreshToken }}
|
||||
commandProxy: {{ .commandProxy }}
|
||||
proxyToken: {{ .proxyToken }}
|
||||
{{ include "vehicle-common" . }}
|
||||
features: ["coarsecurrent"]
|
||||
cache: {{ .cache }}
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ import (
|
|||
"errors"
|
||||
"time"
|
||||
|
||||
"github.com/evcc-io/evcc/api"
|
||||
"golang.org/x/oauth2"
|
||||
)
|
||||
|
||||
|
|
@ -35,7 +36,7 @@ func (t *Token) UnmarshalJSON(data []byte) error {
|
|||
if msg := res.Data.Desc; msg != "" {
|
||||
return errors.New(msg)
|
||||
}
|
||||
return errors.New("missing token")
|
||||
return api.ErrMissingToken
|
||||
}
|
||||
|
||||
(*t) = (Token)(res.Data.Token.Token)
|
||||
|
|
|
|||
|
|
@ -4,11 +4,15 @@ import (
|
|||
"context"
|
||||
"testing"
|
||||
|
||||
"github.com/evcc-io/evcc/api"
|
||||
"github.com/evcc-io/evcc/util/templates"
|
||||
"github.com/evcc-io/evcc/util/test"
|
||||
)
|
||||
|
||||
var acceptable = []string{
|
||||
api.ErrMissingCredentials.Error(),
|
||||
api.ErrMissingToken.Error(),
|
||||
"missing client id",
|
||||
"invalid plugin source: ...",
|
||||
"missing mqtt broker configuration",
|
||||
"received status code 404 (INVALID PARAMS)", // Nissan
|
||||
|
|
@ -20,12 +24,9 @@ var acceptable = []string{
|
|||
"network is unreachable",
|
||||
"error connecting: Network Error",
|
||||
"unexpected status: 401",
|
||||
"missing credentials", // Tesla
|
||||
"missing credentials id", // Tronity
|
||||
"missing access and/or refresh token, use `evcc token` to create", // Tesla
|
||||
"login failed: code not found", // Polestar
|
||||
"empty instance type- check for missing usage", // Merces
|
||||
"invalid vehicle type: tesla", // Tesla
|
||||
"discussions/17501", // Tesla
|
||||
"login failed: code not found", // Polestar
|
||||
"empty instance type- check for missing usage", // Merces
|
||||
}
|
||||
|
||||
func TestTemplates(t *testing.T) {
|
||||
|
|
|
|||
|
|
@ -2,13 +2,12 @@ package vehicle
|
|||
|
||||
import (
|
||||
"context"
|
||||
"os"
|
||||
"errors"
|
||||
"time"
|
||||
|
||||
"github.com/evcc-io/evcc/api"
|
||||
"github.com/evcc-io/evcc/util"
|
||||
"github.com/evcc-io/evcc/util/request"
|
||||
"github.com/evcc-io/evcc/util/sponsor"
|
||||
"github.com/evcc-io/evcc/util/transport"
|
||||
"github.com/evcc-io/evcc/vehicle/tesla"
|
||||
teslaclient "github.com/evcc-io/tesla-proxy-client"
|
||||
|
|
@ -23,15 +22,7 @@ type Tesla struct {
|
|||
}
|
||||
|
||||
func init() {
|
||||
if id := os.Getenv("TESLA_CLIENT_ID"); id != "" {
|
||||
tesla.OAuth2Config.ClientID = id
|
||||
}
|
||||
if secret := os.Getenv("TESLA_CLIENT_SECRET"); secret != "" {
|
||||
tesla.OAuth2Config.ClientSecret = secret
|
||||
}
|
||||
if tesla.OAuth2Config.ClientID != "" {
|
||||
registry.Add("tesla", NewTeslaFromConfig)
|
||||
}
|
||||
registry.Add("tesla", NewTeslaFromConfig)
|
||||
}
|
||||
|
||||
// NewTeslaFromConfig creates a new vehicle
|
||||
|
|
@ -42,6 +33,7 @@ func NewTeslaFromConfig(other map[string]interface{}) (api.Vehicle, error) {
|
|||
Tokens Tokens
|
||||
VIN string
|
||||
CommandProxy string
|
||||
ProxyToken string
|
||||
Cache time.Duration
|
||||
Timeout time.Duration
|
||||
}{
|
||||
|
|
@ -54,12 +46,8 @@ func NewTeslaFromConfig(other map[string]interface{}) (api.Vehicle, error) {
|
|||
return nil, err
|
||||
}
|
||||
|
||||
if cc.Credentials.ID != "" {
|
||||
tesla.OAuth2Config.ClientID = cc.Credentials.ID
|
||||
}
|
||||
|
||||
if cc.Credentials.Secret != "" {
|
||||
tesla.OAuth2Config.ClientSecret = cc.Credentials.Secret
|
||||
if cc.Credentials.ID == "" {
|
||||
return nil, errors.New("missing client id, see https://github.com/evcc-io/evcc/discussions/17501")
|
||||
}
|
||||
|
||||
token, err := cc.Tokens.Token()
|
||||
|
|
@ -68,19 +56,21 @@ func NewTeslaFromConfig(other map[string]interface{}) (api.Vehicle, error) {
|
|||
}
|
||||
|
||||
log := util.NewLogger("tesla").Redact(
|
||||
cc.Tokens.Access, cc.Tokens.Refresh,
|
||||
tesla.OAuth2Config.ClientID, tesla.OAuth2Config.ClientSecret,
|
||||
cc.Tokens.Access, cc.Tokens.Refresh, cc.ProxyToken,
|
||||
cc.Credentials.ID, cc.Credentials.Secret,
|
||||
)
|
||||
|
||||
identity, err := tesla.NewIdentity(log, token)
|
||||
identity, err := tesla.NewIdentity(log, tesla.OAuth2Config(cc.Credentials.ID, cc.Credentials.Secret), token)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
hc := request.NewClient(log)
|
||||
baseTransport := hc.Transport
|
||||
|
||||
hc.Transport = &oauth2.Transport{
|
||||
Source: identity,
|
||||
Base: hc.Transport,
|
||||
Base: baseTransport,
|
||||
}
|
||||
|
||||
tc, err := teslaclient.NewClient(context.Background(), teslaclient.WithClient(hc))
|
||||
|
|
@ -109,9 +99,9 @@ func NewTeslaFromConfig(other map[string]interface{}) (api.Vehicle, error) {
|
|||
pc := request.NewClient(log)
|
||||
pc.Transport = &transport.Decorator{
|
||||
Decorator: transport.DecorateHeaders(map[string]string{
|
||||
"X-Auth-Token": sponsor.Token,
|
||||
"Authorization": "Bearer " + cc.ProxyToken,
|
||||
}),
|
||||
Base: hc.Transport,
|
||||
Base: baseTransport,
|
||||
}
|
||||
|
||||
tcc, err := teslaclient.NewClient(context.Background(), teslaclient.WithClient(pc))
|
||||
|
|
|
|||
|
|
@ -5,11 +5,10 @@ import (
|
|||
"slices"
|
||||
|
||||
"github.com/evcc-io/evcc/api"
|
||||
"github.com/evcc-io/evcc/util/sponsor"
|
||||
"github.com/evcc-io/tesla-proxy-client"
|
||||
)
|
||||
|
||||
const ProxyBaseUrl = "https://tesla.evcc.io"
|
||||
const ProxyBaseUrl = "https://api.myteslamate.com"
|
||||
|
||||
type Controller struct {
|
||||
vehicle *tesla.Vehicle
|
||||
|
|
@ -28,10 +27,6 @@ var _ api.CurrentController = (*Controller)(nil)
|
|||
|
||||
// MaxCurrent implements the api.CurrentController interface
|
||||
func (v *Controller) MaxCurrent(current int64) error {
|
||||
if !sponsor.IsAuthorized() {
|
||||
return api.ErrSponsorRequired
|
||||
}
|
||||
|
||||
return apiError(v.vehicle.SetChargingAmps(int(current)))
|
||||
}
|
||||
|
||||
|
|
@ -39,10 +34,6 @@ var _ api.ChargeController = (*Controller)(nil)
|
|||
|
||||
// ChargeEnable implements the api.ChargeController interface
|
||||
func (v *Controller) ChargeEnable(enable bool) error {
|
||||
if !sponsor.IsAuthorized() {
|
||||
return api.ErrSponsorRequired
|
||||
}
|
||||
|
||||
var err error
|
||||
|
||||
if enable {
|
||||
|
|
|
|||
|
|
@ -17,35 +17,28 @@ import (
|
|||
// https://auth.tesla.com/oauth2/v3/.well-known/openid-configuration
|
||||
|
||||
// OAuth2Config is the OAuth2 configuration for authenticating with the Tesla API.
|
||||
var OAuth2Config = &oauth2.Config{
|
||||
RedirectURL: "https://auth.tesla.com/void/callback",
|
||||
Endpoint: oauth2.Endpoint{
|
||||
AuthURL: "https://auth.tesla.com/en_us/oauth2/v3/authorize",
|
||||
TokenURL: "https://auth.tesla.com/oauth2/v3/token",
|
||||
AuthStyle: oauth2.AuthStyleInParams,
|
||||
},
|
||||
Scopes: []string{"openid", "email", "offline_access"},
|
||||
}
|
||||
|
||||
var TESLA_CLIENT_ID, TESLA_CLIENT_SECRET string
|
||||
|
||||
func init() {
|
||||
if TESLA_CLIENT_ID != "" {
|
||||
OAuth2Config.ClientID = TESLA_CLIENT_ID
|
||||
}
|
||||
if TESLA_CLIENT_SECRET != "" {
|
||||
OAuth2Config.ClientSecret = TESLA_CLIENT_SECRET
|
||||
func OAuth2Config(id, secret string) *oauth2.Config {
|
||||
return &oauth2.Config{
|
||||
ClientID: id,
|
||||
ClientSecret: secret,
|
||||
RedirectURL: "https://auth.tesla.com/void/callback",
|
||||
Endpoint: oauth2.Endpoint{
|
||||
AuthURL: "https://auth.tesla.com/en_us/oauth2/v3/authorize",
|
||||
TokenURL: "https://auth.tesla.com/oauth2/v3/token",
|
||||
AuthStyle: oauth2.AuthStyleInParams,
|
||||
},
|
||||
Scopes: []string{"openid", "email", "offline_access"},
|
||||
}
|
||||
}
|
||||
|
||||
type Identity struct {
|
||||
oauth2.TokenSource
|
||||
mu sync.Mutex
|
||||
log *util.Logger
|
||||
rts oauth2.TokenSource
|
||||
subject string
|
||||
}
|
||||
|
||||
func NewIdentity(log *util.Logger, token *oauth2.Token) (oauth2.TokenSource, error) {
|
||||
func NewIdentity(log *util.Logger, oc *oauth2.Config, token *oauth2.Token) (oauth2.TokenSource, error) {
|
||||
// serialise instance handling
|
||||
mu.Lock()
|
||||
defer mu.Unlock()
|
||||
|
|
@ -66,7 +59,6 @@ func NewIdentity(log *util.Logger, token *oauth2.Token) (oauth2.TokenSource, err
|
|||
}
|
||||
|
||||
v := &Identity{
|
||||
log: log,
|
||||
subject: claims.Subject,
|
||||
}
|
||||
|
||||
|
|
@ -90,6 +82,10 @@ func NewIdentity(log *util.Logger, token *oauth2.Token) (oauth2.TokenSource, err
|
|||
|
||||
v.TokenSource = oauth.RefreshTokenSource(token, v)
|
||||
|
||||
// refresh token source
|
||||
ctx := context.WithValue(context.Background(), oauth2.HTTPClient, request.NewClient(log))
|
||||
v.rts = oc.TokenSource(ctx, token)
|
||||
|
||||
// add instance
|
||||
addInstance(claims.Subject, v)
|
||||
|
||||
|
|
@ -104,10 +100,7 @@ func (v *Identity) RefreshToken(token *oauth2.Token) (*oauth2.Token, error) {
|
|||
v.mu.Lock()
|
||||
defer v.mu.Unlock()
|
||||
|
||||
ctx := context.WithValue(context.Background(), oauth2.HTTPClient, request.NewClient(v.log))
|
||||
ts := OAuth2Config.TokenSource(ctx, token)
|
||||
|
||||
token, err := ts.Token()
|
||||
token, err := v.rts.Token()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ import (
|
|||
"errors"
|
||||
"time"
|
||||
|
||||
"github.com/evcc-io/evcc/api"
|
||||
"golang.org/x/oauth2"
|
||||
)
|
||||
|
||||
|
|
@ -15,11 +16,11 @@ type ClientCredentials struct {
|
|||
// Error validates the credentials and returns an error if they are incomplete
|
||||
func (c *ClientCredentials) Error() error {
|
||||
if c.ID == "" {
|
||||
return errors.New("missing credentials id")
|
||||
return errors.New("missing client id")
|
||||
}
|
||||
|
||||
if c.Secret == "" {
|
||||
return errors.New("missing credentials secret")
|
||||
return errors.New("missing client secret")
|
||||
}
|
||||
|
||||
return nil
|
||||
|
|
@ -33,7 +34,7 @@ type Tokens struct {
|
|||
// Token builds token from credentials and returns an error if they are incomplete
|
||||
func (t *Tokens) Token() (*oauth2.Token, error) {
|
||||
if t.Access == "" && t.Refresh == "" {
|
||||
return nil, errors.New("missing access and/or refresh token, use `evcc token` to create")
|
||||
return nil, api.ErrMissingToken
|
||||
}
|
||||
|
||||
return &oauth2.Token{
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue