Refactor and VW identity and api
This commit is contained in:
parent
c92e8669cc
commit
def4c8a565
5 changed files with 162 additions and 245 deletions
|
|
@ -1,39 +1,28 @@
|
|||
package vehicle
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"net/http"
|
||||
"net/http/cookiejar"
|
||||
"net/url"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/andig/evcc/api"
|
||||
"github.com/andig/evcc/util"
|
||||
"github.com/andig/evcc/util/request"
|
||||
"github.com/andig/evcc/vehicle/oidc"
|
||||
"github.com/andig/evcc/vehicle/vw"
|
||||
"golang.org/x/net/publicsuffix"
|
||||
)
|
||||
|
||||
// https://github.com/davidgiga1993/AudiAPI
|
||||
// https://github.com/TA2k/ioBroker.vw-connect
|
||||
|
||||
// Audi is an api.Vehicle implementation for Audi cars
|
||||
type Audi struct {
|
||||
*embed
|
||||
*request.Helper
|
||||
user, password string
|
||||
tokens oidc.Tokens
|
||||
api *vw.API
|
||||
*vw.Implementation // provides the api implementations
|
||||
*vw.Provider // provides the api implementations
|
||||
}
|
||||
|
||||
func init() {
|
||||
registry.Add("audi", NewAudiFromConfig)
|
||||
}
|
||||
|
||||
const audiClientID = "77869e21-e30a-4a92-b016-48ab7d3db1d8"
|
||||
|
||||
// NewAudiFromConfig creates a new vehicle
|
||||
func NewAudiFromConfig(other map[string]interface{}) (api.Vehicle, error) {
|
||||
cc := struct {
|
||||
|
|
@ -49,89 +38,35 @@ func NewAudiFromConfig(other map[string]interface{}) (api.Vehicle, error) {
|
|||
return nil, err
|
||||
}
|
||||
|
||||
log := util.NewLogger("audi")
|
||||
|
||||
v := &Audi{
|
||||
embed: &embed{cc.Title, cc.Capacity},
|
||||
Helper: request.NewHelper(log),
|
||||
user: cc.User,
|
||||
password: cc.Password,
|
||||
embed: &embed{cc.Title, cc.Capacity},
|
||||
}
|
||||
|
||||
v.api = vw.NewAPI(v.Helper, &v.tokens, v.authFlow, v.refreshHeaders, strings.ToUpper(cc.VIN), "Audi", "DE")
|
||||
v.Implementation = vw.NewImplementation(v.api, cc.Cache)
|
||||
|
||||
var err error
|
||||
jar, err := cookiejar.New(&cookiejar.Options{
|
||||
PublicSuffixList: publicsuffix.List,
|
||||
})
|
||||
|
||||
// track cookies and don't follow redirects
|
||||
v.Client.Jar = jar
|
||||
v.Client.CheckRedirect = func(req *http.Request, via []*http.Request) error {
|
||||
return http.ErrUseLastResponse
|
||||
}
|
||||
|
||||
if err == nil {
|
||||
err = v.authFlow()
|
||||
}
|
||||
|
||||
if err == nil && cc.VIN == "" {
|
||||
v.api.VIN, err = findVehicle(v.api.Vehicles())
|
||||
if err == nil {
|
||||
log.DEBUG.Printf("found vehicle: %v", v.api.VIN)
|
||||
}
|
||||
}
|
||||
|
||||
return v, err
|
||||
}
|
||||
|
||||
func (v *Audi) authFlow() error {
|
||||
var req *http.Request
|
||||
|
||||
const clientID = "09b6cbec-cd19-4589-82fd-363dfa8c24da@apps_vw-dilab_com"
|
||||
const redirectURI = "myaudi:///"
|
||||
log := util.NewLogger("audi")
|
||||
identity := vw.NewIdentity(log, "77869e21-e30a-4a92-b016-48ab7d3db1d8")
|
||||
|
||||
query := url.Values(map[string][]string{
|
||||
"response_type": {"id_token token"},
|
||||
"client_id": {clientID},
|
||||
"redirect_uri": {redirectURI},
|
||||
"client_id": {"09b6cbec-cd19-4589-82fd-363dfa8c24da@apps_vw-dilab_com"},
|
||||
"redirect_uri": {"myaudi:///"},
|
||||
"scope": {"openid profile mbb vin badge birthdate nickname email address phone name picture"},
|
||||
"state": {vw.RandomString(43)},
|
||||
"nonce": {vw.RandomString(43)},
|
||||
"prompt": {"login"},
|
||||
"ui_locales": {"de-DE"},
|
||||
})
|
||||
|
||||
identity := &vw.Identity{Client: v.Client}
|
||||
idToken, err := identity.Login(query, v.user, v.password)
|
||||
|
||||
err := identity.Login(query, cc.User, cc.Password)
|
||||
if err == nil {
|
||||
data := url.Values(map[string][]string{
|
||||
"grant_type": {"id_token"},
|
||||
"scope": {"sc2:fal"},
|
||||
"token": {idToken},
|
||||
})
|
||||
api := vw.NewAPI(log, identity, "Audi", "DE")
|
||||
|
||||
req, err = request.New(http.MethodPost, vw.OauthTokenURI, strings.NewReader(data.Encode()), map[string]string{
|
||||
"Content-Type": "application/x-www-form-urlencoded",
|
||||
"X-Client-Id": audiClientID,
|
||||
})
|
||||
if err == nil {
|
||||
if err = v.DoJSON(req, &v.tokens); err == nil && v.tokens.AccessToken == "" {
|
||||
err = errors.New("missing access token")
|
||||
if cc.VIN == "" {
|
||||
cc.VIN, err = findVehicle(api.Vehicles())
|
||||
if err == nil {
|
||||
log.DEBUG.Printf("found vehicle: %v", cc.VIN)
|
||||
}
|
||||
}
|
||||
|
||||
v.Provider = vw.NewProvider(api, strings.ToUpper(cc.VIN), cc.Cache)
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
func (v *Audi) refreshHeaders() map[string]string {
|
||||
return map[string]string{
|
||||
"Content-Type": "application/x-www-form-urlencoded",
|
||||
"X-App-Version": "3.14.0",
|
||||
"X-App-Name": "myAudi",
|
||||
"X-Client-Id": audiClientID,
|
||||
}
|
||||
return v, err
|
||||
}
|
||||
|
|
|
|||
109
vehicle/vw.go
109
vehicle/vw.go
|
|
@ -1,40 +1,28 @@
|
|||
package vehicle
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"net/http"
|
||||
"net/http/cookiejar"
|
||||
"net/url"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/andig/evcc/api"
|
||||
"github.com/andig/evcc/util"
|
||||
"github.com/andig/evcc/util/request"
|
||||
"github.com/andig/evcc/vehicle/oidc"
|
||||
"github.com/andig/evcc/vehicle/vw"
|
||||
"golang.org/x/net/publicsuffix"
|
||||
)
|
||||
|
||||
// https://github.com/trocotronic/weconnect
|
||||
// https://github.com/TA2k/ioBroker.vw-connect
|
||||
|
||||
// VW is an api.Vehicle implementation for VW cars
|
||||
type VW struct {
|
||||
*embed
|
||||
*request.Helper
|
||||
user, password string
|
||||
clientID string
|
||||
tokens oidc.Tokens
|
||||
api *vw.API
|
||||
*vw.Implementation // provides the api implementations
|
||||
*vw.Provider // provides the api implementations
|
||||
}
|
||||
|
||||
func init() {
|
||||
registry.Add("vw", NewVWFromConfig)
|
||||
}
|
||||
|
||||
const vwClientID = "38761134-34d0-41f3-9a73-c4be88d7d337"
|
||||
|
||||
// NewVWFromConfig creates a new vehicle
|
||||
func NewVWFromConfig(other map[string]interface{}) (api.Vehicle, error) {
|
||||
cc := struct {
|
||||
|
|
@ -42,92 +30,41 @@ func NewVWFromConfig(other map[string]interface{}) (api.Vehicle, error) {
|
|||
Capacity int64
|
||||
User, Password, VIN string
|
||||
Cache time.Duration
|
||||
}{}
|
||||
}{
|
||||
Cache: interval,
|
||||
}
|
||||
|
||||
if err := util.DecodeOther(other, &cc); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
log := util.NewLogger("vw")
|
||||
|
||||
v := &VW{
|
||||
embed: &embed{cc.Title, cc.Capacity},
|
||||
Helper: request.NewHelper(log),
|
||||
user: cc.User,
|
||||
password: cc.Password,
|
||||
embed: &embed{cc.Title, cc.Capacity},
|
||||
}
|
||||
|
||||
v.api = vw.NewAPI(v.Helper, &v.tokens, v.authFlow, v.refreshHeaders, strings.ToUpper(cc.VIN), "VW", "DE")
|
||||
v.Implementation = vw.NewImplementation(v.api, cc.Cache)
|
||||
log := util.NewLogger("vw")
|
||||
identity := vw.NewIdentity(log, "38761134-34d0-41f3-9a73-c4be88d7d337")
|
||||
|
||||
var err error
|
||||
jar, err := cookiejar.New(&cookiejar.Options{
|
||||
PublicSuffixList: publicsuffix.List,
|
||||
query := url.Values(map[string][]string{
|
||||
"response_type": {"id_token token"},
|
||||
"client_id": {"9496332b-ea03-4091-a224-8c746b885068@apps_vw-dilab_com"},
|
||||
"redirect_uri": {"carnet://identity-kit/login"},
|
||||
"scope": {"openid profile mbb cars birthdate nickname address phone"},
|
||||
})
|
||||
|
||||
// track cookies and don't follow redirects
|
||||
v.Client.Jar = jar
|
||||
v.Client.CheckRedirect = func(req *http.Request, via []*http.Request) error {
|
||||
return http.ErrUseLastResponse
|
||||
}
|
||||
|
||||
err := identity.Login(query, cc.User, cc.Password)
|
||||
if err == nil {
|
||||
err = v.authFlow()
|
||||
}
|
||||
api := vw.NewAPI(log, identity, "VW", "DE")
|
||||
|
||||
if err == nil && cc.VIN == "" {
|
||||
v.api.VIN, err = findVehicle(v.api.Vehicles())
|
||||
if err == nil {
|
||||
log.DEBUG.Printf("found vehicle: %v", v.api.VIN)
|
||||
if cc.VIN == "" {
|
||||
cc.VIN, err = findVehicle(api.Vehicles())
|
||||
if err == nil {
|
||||
log.DEBUG.Printf("found vehicle: %v", cc.VIN)
|
||||
}
|
||||
}
|
||||
|
||||
v.Provider = vw.NewProvider(api, strings.ToUpper(cc.VIN), cc.Cache)
|
||||
}
|
||||
|
||||
return v, err
|
||||
}
|
||||
|
||||
func (v *VW) authFlow() error {
|
||||
var req *http.Request
|
||||
|
||||
const clientID = "9496332b-ea03-4091-a224-8c746b885068@apps_vw-dilab_com"
|
||||
const redirectURI = "carnet://identity-kit/login"
|
||||
|
||||
query := url.Values(map[string][]string{
|
||||
"response_type": {"id_token token"},
|
||||
"client_id": {clientID},
|
||||
"redirect_uri": {redirectURI},
|
||||
"scope": {"openid profile mbb cars birthdate nickname address phone"},
|
||||
"state": {vw.RandomString(43)},
|
||||
"nonce": {vw.RandomString(43)},
|
||||
"prompt": {"login"},
|
||||
})
|
||||
|
||||
identity := &vw.Identity{Client: v.Client}
|
||||
idToken, err := identity.Login(query, v.user, v.password)
|
||||
|
||||
if err == nil {
|
||||
data := url.Values(map[string][]string{
|
||||
"grant_type": {"id_token"},
|
||||
"scope": {"sc2:fal"},
|
||||
"token": {idToken},
|
||||
})
|
||||
|
||||
req, err = request.New(http.MethodPost, vw.OauthTokenURI, strings.NewReader(data.Encode()), map[string]string{
|
||||
"Content-type": "application/x-www-form-urlencoded",
|
||||
"X-Client-Id": vwClientID,
|
||||
})
|
||||
|
||||
if err == nil {
|
||||
if err = v.DoJSON(req, &v.tokens); err == nil && v.tokens.AccessToken == "" {
|
||||
err = errors.New("missing access token")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
func (v *VW) refreshHeaders() map[string]string {
|
||||
return map[string]string{
|
||||
"Content-Type": "application/x-www-form-urlencoded",
|
||||
"X-Client-Id": vwClientID,
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,14 +1,12 @@
|
|||
package vw
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strings"
|
||||
|
||||
"github.com/andig/evcc/util"
|
||||
"github.com/andig/evcc/util/request"
|
||||
"github.com/andig/evcc/vehicle/oidc"
|
||||
)
|
||||
|
||||
// BaseURI is the VW api base URI
|
||||
|
|
@ -93,63 +91,25 @@ func Temp2Float(val int) float64 {
|
|||
// API is the VW api client
|
||||
type API struct {
|
||||
*request.Helper
|
||||
tokens *oidc.Tokens
|
||||
authFlow func() error
|
||||
refreshHeaders func() map[string]string
|
||||
identity *Identity
|
||||
brand, country string
|
||||
VIN string
|
||||
}
|
||||
|
||||
// NewAPI creates a new api client
|
||||
func NewAPI(
|
||||
helper *request.Helper, tokens *oidc.Tokens,
|
||||
authFlow func() error, refreshHeaders func() map[string]string,
|
||||
vin, brand, country string,
|
||||
) *API {
|
||||
func NewAPI(log *util.Logger, identity *Identity, brand, country string) *API {
|
||||
v := &API{
|
||||
Helper: helper,
|
||||
tokens: tokens,
|
||||
authFlow: authFlow,
|
||||
refreshHeaders: refreshHeaders,
|
||||
VIN: vin,
|
||||
brand: brand,
|
||||
country: country,
|
||||
Helper: request.NewHelper(log),
|
||||
identity: identity,
|
||||
brand: brand,
|
||||
country: country,
|
||||
}
|
||||
return v
|
||||
}
|
||||
|
||||
func (v *API) refreshToken() error {
|
||||
if v.tokens.RefreshToken == "" {
|
||||
return errors.New("missing refresh token")
|
||||
}
|
||||
|
||||
data := url.Values(map[string][]string{
|
||||
"grant_type": {"refresh_token"},
|
||||
"refresh_token": {v.tokens.RefreshToken},
|
||||
"scope": {"sc2:fal"},
|
||||
})
|
||||
|
||||
req, err := request.New(http.MethodPost, OauthTokenURI, strings.NewReader(data.Encode()), v.refreshHeaders())
|
||||
|
||||
if err == nil {
|
||||
var tokens oidc.Tokens
|
||||
|
||||
err = v.DoJSON(req, &tokens)
|
||||
if err == nil {
|
||||
v.tokens.AccessToken = tokens.AccessToken
|
||||
if tokens.RefreshToken != "" {
|
||||
v.tokens.RefreshToken = tokens.RefreshToken
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
func (v *API) getJSON(uri string, res interface{}) error {
|
||||
req, err := request.New(http.MethodGet, uri, nil, map[string]string{
|
||||
"Accept": "application/json",
|
||||
"Authorization": "Bearer " + v.tokens.AccessToken,
|
||||
"Authorization": "Bearer " + v.identity.Token(),
|
||||
})
|
||||
|
||||
if err == nil {
|
||||
|
|
@ -159,16 +119,13 @@ func (v *API) getJSON(uri string, res interface{}) error {
|
|||
if err != nil {
|
||||
// handle http 401
|
||||
if se, ok := err.(request.StatusError); ok && se.StatusCode() == http.StatusUnauthorized {
|
||||
// use refresh token
|
||||
if err = v.refreshToken(); err != nil {
|
||||
// re-run full auth flow
|
||||
err = v.authFlow()
|
||||
}
|
||||
// refresh the token
|
||||
err = v.identity.RefreshToken()
|
||||
}
|
||||
|
||||
// retry original requests
|
||||
if err == nil {
|
||||
req.Header.Set("Authorization", "Bearer "+v.tokens.AccessToken)
|
||||
req.Header.Set("Authorization", "Bearer "+v.identity.Token())
|
||||
err = v.DoJSON(req, &res)
|
||||
}
|
||||
}
|
||||
|
|
@ -186,25 +143,25 @@ func (v *API) Vehicles() ([]string, error) {
|
|||
}
|
||||
|
||||
// Charger implements the /charger response
|
||||
func (v *API) Charger() (ChargerResponse, error) {
|
||||
func (v *API) Charger(vin string) (ChargerResponse, error) {
|
||||
var res ChargerResponse
|
||||
uri := fmt.Sprintf("%s/bs/batterycharge/v1/%s/%s/vehicles/%s/charger", BaseURI, v.brand, v.country, v.VIN)
|
||||
uri := fmt.Sprintf("%s/bs/batterycharge/v1/%s/%s/vehicles/%s/charger", BaseURI, v.brand, v.country, vin)
|
||||
err := v.getJSON(uri, &res)
|
||||
return res, err
|
||||
}
|
||||
|
||||
// Climater implements the /climater response
|
||||
func (v *API) Climater() (ClimaterResponse, error) {
|
||||
func (v *API) Climater(vin string) (ClimaterResponse, error) {
|
||||
var res ClimaterResponse
|
||||
uri := fmt.Sprintf("%s/bs/climatisation/v1/%s/%s/vehicles/%s/climater", BaseURI, v.brand, v.country, v.VIN)
|
||||
uri := fmt.Sprintf("%s/bs/climatisation/v1/%s/%s/vehicles/%s/climater", BaseURI, v.brand, v.country, vin)
|
||||
err := v.getJSON(uri, &res)
|
||||
return res, err
|
||||
}
|
||||
|
||||
// Any implements any api response
|
||||
func (v *API) Any(base string) (interface{}, error) {
|
||||
func (v *API) Any(base, vin string) (interface{}, error) {
|
||||
var res interface{}
|
||||
uri := fmt.Sprintf("%s/"+strings.TrimLeft(base, "/"), BaseURI, v.brand, v.country, v.VIN)
|
||||
uri := fmt.Sprintf("%s/"+strings.TrimLeft(base, "/"), BaseURI, v.brand, v.country, vin)
|
||||
err := v.getJSON(uri, &res)
|
||||
return res, err
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,8 +3,14 @@ package vw
|
|||
import (
|
||||
"errors"
|
||||
"net/http"
|
||||
"net/http/cookiejar"
|
||||
"net/url"
|
||||
"strings"
|
||||
|
||||
"github.com/andig/evcc/util"
|
||||
"github.com/andig/evcc/util/request"
|
||||
"github.com/andig/evcc/vehicle/oidc"
|
||||
"golang.org/x/net/publicsuffix"
|
||||
)
|
||||
|
||||
const (
|
||||
|
|
@ -20,7 +26,29 @@ const (
|
|||
|
||||
// Identity provides the identity.vwgroup.io login
|
||||
type Identity struct {
|
||||
*http.Client
|
||||
*request.Helper
|
||||
clientID string
|
||||
tokens oidc.Tokens
|
||||
}
|
||||
|
||||
// NewIdentity creates VW identity
|
||||
func NewIdentity(log *util.Logger, clientID string) *Identity {
|
||||
v := &Identity{
|
||||
Helper: request.NewHelper(log),
|
||||
clientID: clientID,
|
||||
}
|
||||
|
||||
jar, _ := cookiejar.New(&cookiejar.Options{
|
||||
PublicSuffixList: publicsuffix.List,
|
||||
})
|
||||
|
||||
// track cookies and don't follow redirects
|
||||
v.Client.Jar = jar
|
||||
v.Client.CheckRedirect = func(req *http.Request, via []*http.Request) error {
|
||||
return http.ErrUseLastResponse
|
||||
}
|
||||
|
||||
return v
|
||||
}
|
||||
|
||||
// redirect follows HTTP redirect header if error is nil. Request body is closed.
|
||||
|
|
@ -40,10 +68,14 @@ func (v *Identity) redirect(resp *http.Response, err error) (*http.Response, err
|
|||
}
|
||||
|
||||
// Login performs the identity.vwgroup.io login
|
||||
func (v *Identity) Login(query url.Values, user, password string) (string, error) {
|
||||
func (v *Identity) Login(query url.Values, user, password string) error {
|
||||
var vars FormVars
|
||||
var req *http.Request
|
||||
|
||||
// add nonce and state
|
||||
query.Set("nonce", RandomString(43))
|
||||
query.Set("state", RandomString(43))
|
||||
|
||||
// GET identity.vwgroup.io/oidc/v1/authorize?ui_locales=de&scope=openid%20profile%20birthdate%20nickname%20address%20phone%20cars%20mbb&response_type=code&state=gmiJOaB4&redirect_uri=https%3A%2F%2Fwww.portal.volkswagen-we.com%2Fportal%2Fweb%2Fguest%2Fcomplete-login&nonce=38042ee3-b7a7-43cf-a9c1-63d2f3f2d9f3&prompt=login&client_id=b7a5bb47-f875-47cf-ab83-2ba3bf6bb738@apps_vw-dilab_com
|
||||
uri := "https://identity.vwgroup.io/oidc/v1/authorize?" + query.Encode()
|
||||
resp, err := v.Get(uri)
|
||||
|
|
@ -115,19 +147,75 @@ func (v *Identity) Login(query url.Values, user, password string) (string, error
|
|||
resp, err = v.redirect(resp, err)
|
||||
}
|
||||
|
||||
var idToken string
|
||||
if err == nil {
|
||||
loc := strings.ReplaceAll(resp.Header.Get("Location"), "#", "?") // convert to parseable url
|
||||
|
||||
var location *url.URL
|
||||
if location, err = url.Parse(loc); err == nil {
|
||||
idToken = location.Query().Get("id_token")
|
||||
v.tokens.IDToken = location.Query().Get("id_token")
|
||||
|
||||
if idToken == "" {
|
||||
if v.tokens.IDToken == "" {
|
||||
err = errors.New("missing id token")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return idToken, err
|
||||
if err == nil {
|
||||
data := url.Values(map[string][]string{
|
||||
"grant_type": {"id_token"},
|
||||
"scope": {"sc2:fal"},
|
||||
"token": {v.tokens.IDToken},
|
||||
})
|
||||
|
||||
req, err = request.New(http.MethodPost, OauthTokenURI, strings.NewReader(data.Encode()), map[string]string{
|
||||
"Content-Type": "application/x-www-form-urlencoded",
|
||||
"X-Client-Id": v.clientID,
|
||||
})
|
||||
if err == nil {
|
||||
if err = v.DoJSON(req, &v.tokens); err == nil && v.tokens.AccessToken == "" {
|
||||
err = errors.New("missing access token")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
// Token returns the access token
|
||||
func (v *Identity) Token() string {
|
||||
return v.tokens.AccessToken
|
||||
}
|
||||
|
||||
// RefreshToken uses the refresh token to obtain a new access token
|
||||
func (v *Identity) RefreshToken() error {
|
||||
if v.tokens.RefreshToken == "" {
|
||||
return errors.New("missing refresh token")
|
||||
}
|
||||
|
||||
data := url.Values(map[string][]string{
|
||||
"grant_type": {"refresh_token"},
|
||||
"refresh_token": {v.tokens.RefreshToken},
|
||||
"scope": {"sc2:fal"},
|
||||
})
|
||||
|
||||
headers := map[string]string{
|
||||
"Content-Type": "application/x-www-form-urlencoded",
|
||||
"X-Client-Id": v.clientID,
|
||||
}
|
||||
|
||||
req, err := request.New(http.MethodPost, OauthTokenURI, strings.NewReader(data.Encode()), headers)
|
||||
|
||||
if err == nil {
|
||||
var tokens oidc.Tokens
|
||||
|
||||
err = v.DoJSON(req, &tokens)
|
||||
if err == nil {
|
||||
v.tokens.AccessToken = tokens.AccessToken
|
||||
if tokens.RefreshToken != "" {
|
||||
v.tokens.RefreshToken = tokens.RefreshToken
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
|
|
|
|||
|
|
@ -8,27 +8,27 @@ import (
|
|||
"github.com/andig/evcc/provider"
|
||||
)
|
||||
|
||||
// Implementation implements the evcc vehicle api
|
||||
type Implementation struct {
|
||||
// Provider implements the evcc vehicle api
|
||||
type Provider struct {
|
||||
chargerG func() (interface{}, error)
|
||||
climateG func() (interface{}, error)
|
||||
}
|
||||
|
||||
// NewImplementation provides the evcc vehicle api implementation
|
||||
func NewImplementation(api *API, cache time.Duration) *Implementation {
|
||||
impl := &Implementation{
|
||||
// NewProvider provides the evcc vehicle api provider
|
||||
func NewProvider(api *API, vin string, cache time.Duration) *Provider {
|
||||
impl := &Provider{
|
||||
chargerG: provider.NewCached(func() (interface{}, error) {
|
||||
return api.Charger()
|
||||
return api.Charger(vin)
|
||||
}, cache).InterfaceGetter(),
|
||||
climateG: provider.NewCached(func() (interface{}, error) {
|
||||
return api.Climater()
|
||||
return api.Climater(vin)
|
||||
}, cache).InterfaceGetter(),
|
||||
}
|
||||
return impl
|
||||
}
|
||||
|
||||
// ChargeState implements the Vehicle.ChargeState interface
|
||||
func (v *Implementation) ChargeState() (float64, error) {
|
||||
func (v *Provider) ChargeState() (float64, error) {
|
||||
res, err := v.chargerG()
|
||||
if res, ok := res.(ChargerResponse); err == nil && ok {
|
||||
return float64(res.Charger.Status.BatteryStatusData.StateOfCharge.Content), nil
|
||||
|
|
@ -38,7 +38,7 @@ func (v *Implementation) ChargeState() (float64, error) {
|
|||
}
|
||||
|
||||
// FinishTime implements the Vehicle.ChargeFinishTimer interface
|
||||
func (v *Implementation) FinishTime() (time.Time, error) {
|
||||
func (v *Provider) FinishTime() (time.Time, error) {
|
||||
res, err := v.chargerG()
|
||||
if res, ok := res.(ChargerResponse); err == nil && ok {
|
||||
rct := res.Charger.Status.BatteryStatusData.RemainingChargingTime
|
||||
|
|
@ -58,7 +58,7 @@ func (v *Implementation) FinishTime() (time.Time, error) {
|
|||
}
|
||||
|
||||
// Status implements the Vehicle.Status interface
|
||||
func (v *Implementation) Status() (api.ChargeStatus, error) {
|
||||
func (v *Provider) Status() (api.ChargeStatus, error) {
|
||||
status := api.StatusA // disconnected
|
||||
|
||||
res, err := v.chargerG()
|
||||
|
|
@ -75,7 +75,7 @@ func (v *Implementation) Status() (api.ChargeStatus, error) {
|
|||
}
|
||||
|
||||
// Range implements the Vehicle.Range interface
|
||||
func (v *Implementation) Range() (rng int64, err error) {
|
||||
func (v *Provider) Range() (rng int64, err error) {
|
||||
res, err := v.chargerG()
|
||||
if res, ok := res.(ChargerResponse); err == nil && ok {
|
||||
crsd := res.Charger.Status.CruisingRangeStatusData
|
||||
|
|
@ -90,7 +90,7 @@ func (v *Implementation) Range() (rng int64, err error) {
|
|||
}
|
||||
|
||||
// Climater implements the Vehicle.Climater interface
|
||||
func (v *Implementation) Climater() (active bool, outsideTemp float64, targetTemp float64, err error) {
|
||||
func (v *Provider) Climater() (active bool, outsideTemp float64, targetTemp float64, err error) {
|
||||
res, err := v.climateG()
|
||||
if res, ok := res.(ClimaterResponse); err == nil && ok {
|
||||
state := strings.ToLower(res.Climater.Status.ClimatisationStatusData.ClimatisationState.Content)
|
||||
Loading…
Add table
Add a link
Reference in a new issue