Ford: remove legacy integration (BC) (#25516)
This commit is contained in:
parent
8d94166d93
commit
628a12ee45
9 changed files with 0 additions and 702 deletions
|
|
@ -1,21 +0,0 @@
|
|||
template: ford
|
||||
deprecated: true
|
||||
products:
|
||||
- brand: Ford
|
||||
requirements:
|
||||
description:
|
||||
de: "Hinweis: Ford hat kürzlich den API-Zugriff für seine Benutzer deaktiviert."
|
||||
en: "Note: Ford has recently disabled API-access for their users."
|
||||
params:
|
||||
- preset: vehicle-base
|
||||
- name: vin
|
||||
example: WF0FXX...
|
||||
- name: domain
|
||||
type: choice
|
||||
choice: ["com", "de"]
|
||||
default: com
|
||||
required: true
|
||||
render: |
|
||||
type: ford
|
||||
{{ include "vehicle-base" . }}
|
||||
domain: {{ .domain }}
|
||||
|
|
@ -1,73 +0,0 @@
|
|||
package vehicle
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/evcc-io/evcc/api"
|
||||
"github.com/evcc-io/evcc/util"
|
||||
"github.com/evcc-io/evcc/vehicle/ford"
|
||||
"github.com/evcc-io/evcc/vehicle/ford/autonomic"
|
||||
)
|
||||
|
||||
// https://github.com/d4v3y0rk/ffpass-module
|
||||
// https://github.com/ianjwhite99/connected-car-node-sdk
|
||||
// https://github.com/TA2k/ioBroker.ford
|
||||
|
||||
// Ford is an api.Vehicle implementation for Ford cars
|
||||
type Ford struct {
|
||||
*embed
|
||||
*ford.Provider
|
||||
}
|
||||
|
||||
func init() {
|
||||
registry.Add("ford", NewFordFromConfig)
|
||||
}
|
||||
|
||||
// NewFordFromConfig creates a new vehicle
|
||||
func NewFordFromConfig(other map[string]any) (api.Vehicle, error) {
|
||||
cc := struct {
|
||||
embed `mapstructure:",squash"`
|
||||
User, Password, VIN string
|
||||
Domain string
|
||||
Cache time.Duration
|
||||
}{
|
||||
Domain: "com",
|
||||
Cache: interval,
|
||||
}
|
||||
|
||||
if err := util.DecodeOther(other, &cc); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if cc.User == "" || cc.Password == "" {
|
||||
return nil, api.ErrMissingCredentials
|
||||
}
|
||||
|
||||
v := &Ford{
|
||||
embed: &cc.embed,
|
||||
}
|
||||
|
||||
log := util.NewLogger("ford").Redact(cc.User, cc.Password, cc.VIN)
|
||||
identity := ford.NewIdentity(log, cc.User, cc.Password, cc.Domain)
|
||||
|
||||
err := identity.Login()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("login failed: %w", err)
|
||||
}
|
||||
|
||||
api := ford.NewAPI(log, identity)
|
||||
|
||||
cc.VIN, err = ensureVehicle(cc.VIN, api.Vehicles)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
autoIdentity, err := autonomic.NewIdentity(log, identity)
|
||||
if err == nil {
|
||||
api := autonomic.NewAPI(log, autoIdentity)
|
||||
v.Provider = ford.NewProvider(api, cc.VIN, cc.Cache)
|
||||
}
|
||||
|
||||
return v, err
|
||||
}
|
||||
|
|
@ -1,73 +0,0 @@
|
|||
package ford
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
|
||||
"github.com/evcc-io/evcc/util"
|
||||
"github.com/evcc-io/evcc/util/request"
|
||||
"github.com/evcc-io/evcc/util/transport"
|
||||
"golang.org/x/oauth2"
|
||||
)
|
||||
|
||||
const ApiURI = "https://usapi.cv.ford.com"
|
||||
|
||||
// API is the Ford api client
|
||||
type API struct {
|
||||
*request.Helper
|
||||
}
|
||||
|
||||
// NewAPI creates a new api client
|
||||
func NewAPI(log *util.Logger, ts oauth2.TokenSource) *API {
|
||||
v := &API{
|
||||
Helper: request.NewHelper(log),
|
||||
}
|
||||
|
||||
v.Client.Transport = &transport.Decorator{
|
||||
Decorator: func(req *http.Request) error {
|
||||
token, err := ts.Token()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for k, v := range map[string]string{
|
||||
"Content-type": request.JSONContent,
|
||||
"User-Agent": "FordPass/5 CFNetwork/1333.0.4 Darwin/21.5.0",
|
||||
"locale": "de-DE",
|
||||
"Application-Id": ApplicationID,
|
||||
"Auth-Token": token.AccessToken,
|
||||
"CountryCode": "DEU",
|
||||
} {
|
||||
req.Header.Set(k, v)
|
||||
}
|
||||
|
||||
return nil
|
||||
},
|
||||
Base: v.Client.Transport,
|
||||
}
|
||||
|
||||
return v
|
||||
}
|
||||
|
||||
// Vehicles returns the list of user vehicles
|
||||
func (v *API) Vehicles() ([]string, error) {
|
||||
var res []string
|
||||
|
||||
data := map[string]string{
|
||||
"dashboardRefreshRequest": "All",
|
||||
}
|
||||
|
||||
uri := fmt.Sprintf("%s/api/expdashboard/v1/details", TokenURI)
|
||||
|
||||
req, err := request.New(http.MethodPost, uri, request.MarshalJSON(data), request.JSONEncoding)
|
||||
if err == nil {
|
||||
var resp VehiclesResponse
|
||||
if err = v.DoJSON(req, &resp); err == nil {
|
||||
for _, v := range resp.UserVehicles.VehicleDetails {
|
||||
res = append(res, v.VIN)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return res, err
|
||||
}
|
||||
|
|
@ -1,87 +0,0 @@
|
|||
package autonomic
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
"github.com/evcc-io/evcc/util"
|
||||
"github.com/evcc-io/evcc/util/request"
|
||||
"github.com/evcc-io/evcc/util/transport"
|
||||
"golang.org/x/oauth2"
|
||||
)
|
||||
|
||||
const ApiURI = "https://api.autonomic.ai/v1beta/telemetry/sources/fordpass"
|
||||
|
||||
var (
|
||||
ApplicationID = "667D773E-1BDC-4139-8AD0-2B16474E8DC7"
|
||||
Dyna = "MT_3_30_2352378557_3-0_" + "uuidv4()" + "_0_789_87"
|
||||
)
|
||||
|
||||
// API is the Ford api client
|
||||
type API struct {
|
||||
*request.Helper
|
||||
}
|
||||
|
||||
// NewAPI creates a new api client
|
||||
func NewAPI(log *util.Logger, ts oauth2.TokenSource) *API {
|
||||
v := &API{
|
||||
Helper: request.NewHelper(log),
|
||||
}
|
||||
|
||||
v.Client.Transport = &transport.Decorator{
|
||||
Decorator: func(req *http.Request) error {
|
||||
token, err := ts.Token()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for k, v := range map[string]string{
|
||||
"Content-type": request.JSONContent,
|
||||
"Application-Id": ApplicationID,
|
||||
"x-dynatrace": Dyna,
|
||||
"Authorization": "Bearer " + token.AccessToken,
|
||||
} {
|
||||
req.Header.Set(k, v)
|
||||
}
|
||||
|
||||
return nil
|
||||
},
|
||||
Base: v.Client.Transport,
|
||||
}
|
||||
|
||||
return v
|
||||
}
|
||||
|
||||
// Status retrieves a metrics result using :query
|
||||
func (v *API) Status(vin string) (MetricsResponse, error) {
|
||||
var res MetricsResponse
|
||||
|
||||
uri := fmt.Sprintf("%s/vehicles/%s:query", ApiURI, vin)
|
||||
req, err := request.New(http.MethodPost, uri, strings.NewReader("{}"), request.JSONEncoding)
|
||||
if err == nil {
|
||||
err = v.DoJSON(req, &res)
|
||||
}
|
||||
|
||||
return res, err
|
||||
}
|
||||
|
||||
// Refresh executes the refresh command
|
||||
func (v *API) Refresh(vin string) (any, error) {
|
||||
var res map[string]any
|
||||
|
||||
data := map[string]any{
|
||||
"properties": struct{}{},
|
||||
"tags": struct{}{},
|
||||
"type": "statusRefresh",
|
||||
"wakeUp": true,
|
||||
}
|
||||
|
||||
uri := fmt.Sprintf("%s/vehicles/%s/command", ApiURI, vin)
|
||||
req, err := request.New(http.MethodPost, uri, request.MarshalJSON(data), request.JSONEncoding)
|
||||
if err == nil {
|
||||
err = v.DoJSON(req, &res)
|
||||
}
|
||||
|
||||
return res, err
|
||||
}
|
||||
|
|
@ -1,78 +0,0 @@
|
|||
package autonomic
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strings"
|
||||
|
||||
"github.com/evcc-io/evcc/util"
|
||||
"github.com/evcc-io/evcc/util/oauth"
|
||||
"github.com/evcc-io/evcc/util/request"
|
||||
"golang.org/x/oauth2"
|
||||
)
|
||||
|
||||
// https://accounts.autonomic.ai/v1/auth/oidc/.well-known/openid-configuration/
|
||||
var OAuth2Config = &oauth2.Config{
|
||||
Endpoint: oauth2.Endpoint{
|
||||
AuthURL: "https://accounts.autonomic.ai/v1/auth/oidc/authorize",
|
||||
TokenURL: "https://accounts.autonomic.ai/v1/auth/oidc/token",
|
||||
},
|
||||
Scopes: []string{"openid"},
|
||||
}
|
||||
|
||||
type Identity struct {
|
||||
*request.Helper
|
||||
ts oauth2.TokenSource
|
||||
oauth2.TokenSource
|
||||
}
|
||||
|
||||
// NewIdentity creates autonomic token source
|
||||
func NewIdentity(log *util.Logger, ts oauth2.TokenSource) (*Identity, error) {
|
||||
v := &Identity{
|
||||
Helper: request.NewHelper(log),
|
||||
ts: ts,
|
||||
}
|
||||
|
||||
token, err := ts.Token()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
auto, err := v.exchange(token)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
v.TokenSource = oauth.RefreshTokenSource(auto, v)
|
||||
|
||||
return v, nil
|
||||
}
|
||||
|
||||
// exchange authenticates with username/password to get new aws credentials
|
||||
func (v *Identity) exchange(token *oauth2.Token) (*oauth2.Token, error) {
|
||||
data := url.Values{
|
||||
"subject_token": {token.AccessToken},
|
||||
"subject_issuer": {"fordpass"},
|
||||
"client_id": {"fordpass-prod"},
|
||||
"grant_type": {"urn:ietf:params:oauth:grant-type:token-exchange"},
|
||||
"subject_token_type": {"urn:ietf:params:oauth:token-type:jwt"},
|
||||
}
|
||||
|
||||
var res *oauth2.Token
|
||||
req, err := request.New(http.MethodPost, OAuth2Config.Endpoint.TokenURL, strings.NewReader(data.Encode()), request.URLEncoding)
|
||||
if err == nil {
|
||||
err = v.DoJSON(req, &res)
|
||||
}
|
||||
|
||||
return util.TokenWithExpiry(res), err
|
||||
}
|
||||
|
||||
// RefreshToken implements oauth.TokenRefresher
|
||||
func (v *Identity) RefreshToken(_ *oauth2.Token) (*oauth2.Token, error) {
|
||||
token, err := v.ts.Token()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return v.exchange(token)
|
||||
}
|
||||
|
|
@ -1,32 +0,0 @@
|
|||
package autonomic
|
||||
|
||||
import "time"
|
||||
|
||||
type IntValue struct {
|
||||
UpdateTime time.Time
|
||||
Value int
|
||||
}
|
||||
type FloatValue struct {
|
||||
UpdateTime time.Time
|
||||
Value float64
|
||||
}
|
||||
type StringValue struct {
|
||||
UpdateTime time.Time
|
||||
Value string
|
||||
}
|
||||
|
||||
type MetricsResponse struct {
|
||||
Metrics struct {
|
||||
Position struct {
|
||||
Value struct {
|
||||
Location struct {
|
||||
Lat, Lon float64
|
||||
}
|
||||
}
|
||||
}
|
||||
Odometer FloatValue
|
||||
XevPlugChargerStatus StringValue
|
||||
XevBatteryRange FloatValue
|
||||
XevBatteryStateOfCharge FloatValue
|
||||
}
|
||||
}
|
||||
|
|
@ -1,229 +0,0 @@
|
|||
package ford
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/http/cookiejar"
|
||||
"net/url"
|
||||
"regexp"
|
||||
"strings"
|
||||
|
||||
"github.com/evcc-io/evcc/util"
|
||||
"github.com/evcc-io/evcc/util/oauth"
|
||||
"github.com/evcc-io/evcc/util/request"
|
||||
"github.com/samber/lo"
|
||||
"golang.org/x/oauth2"
|
||||
)
|
||||
|
||||
const (
|
||||
TokenURI = "https://api.mps.ford.com"
|
||||
LoginUriTmpl = "https://login.ford.%s/4566605f-43a7-400a-946e-89cc9fdb0bd7/B2C_1A_SignInSignUp_de-DE"
|
||||
ClientID = "09852200-05fd-41f6-8c21-d36d3497dc64"
|
||||
ApplicationID = "1E8C7794-FF5F-49BC-9596-A1E0C86C5B19"
|
||||
)
|
||||
|
||||
var loginHeaders = map[string]string{
|
||||
"Accept": "*/*",
|
||||
"Accept-Language": "en-us",
|
||||
"User-Agent": "Mozilla/5.0 (iPhone; CPU iPhone OS 17_0 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.1 Mobile/15E148 Safari/604.1",
|
||||
}
|
||||
|
||||
func OAuth2Config(domain string) *oauth2.Config {
|
||||
uri := loginUri(domain)
|
||||
return &oauth2.Config{
|
||||
ClientID: ClientID,
|
||||
Endpoint: oauth2.Endpoint{
|
||||
AuthURL: fmt.Sprintf("%s/oauth2/v2.0/authorize", uri),
|
||||
TokenURL: fmt.Sprintf("%s/oauth2/v2.0/token", uri),
|
||||
},
|
||||
RedirectURL: "fordapp://userauthorized",
|
||||
Scopes: []string{ClientID, "openid"},
|
||||
}
|
||||
}
|
||||
|
||||
func loginUri(domain string) string {
|
||||
return fmt.Sprintf(LoginUriTmpl, domain)
|
||||
}
|
||||
|
||||
type Settings struct {
|
||||
TransId string `json:"transId"`
|
||||
Csrf string `json:"csrf"`
|
||||
}
|
||||
|
||||
type Identity struct {
|
||||
*request.Helper
|
||||
user, password, domain string
|
||||
oauth2.TokenSource
|
||||
}
|
||||
|
||||
// NewIdentity creates Ford identity
|
||||
func NewIdentity(log *util.Logger, user, password, domain string) *Identity {
|
||||
return &Identity{
|
||||
Helper: request.NewHelper(log),
|
||||
user: user,
|
||||
password: password,
|
||||
domain: domain,
|
||||
}
|
||||
}
|
||||
|
||||
// Login authenticates with username/password to get new aws credentials
|
||||
func (v *Identity) Login() error {
|
||||
token, err := v.login()
|
||||
if err == nil {
|
||||
v.TokenSource = oauth.RefreshTokenSource(token, v)
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
// login authenticates with username/password to get new token
|
||||
func (v *Identity) login() (*oauth2.Token, error) {
|
||||
oc := OAuth2Config(v.domain)
|
||||
|
||||
cv := oauth2.GenerateVerifier()
|
||||
state := lo.RandomString(16, lo.AlphanumericCharset)
|
||||
uri := oc.AuthCodeURL(state, oauth2.S256ChallengeOption(cv),
|
||||
oauth2.SetAuthURLParam("max_age", "3600"),
|
||||
oauth2.SetAuthURLParam("ui_locales", "de-DE"),
|
||||
oauth2.SetAuthURLParam("language_code", "de-DE"),
|
||||
oauth2.SetAuthURLParam("country_code", "DE"),
|
||||
oauth2.SetAuthURLParam("ford_application_id", ApplicationID),
|
||||
)
|
||||
|
||||
v.Jar, _ = cookiejar.New(nil)
|
||||
defer func() { v.Jar = nil }()
|
||||
|
||||
var body []byte
|
||||
req, err := request.New(http.MethodGet, uri, nil, loginHeaders)
|
||||
if err == nil {
|
||||
body, err = v.DoBody(req)
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
match := regexp.MustCompile(`var SETTINGS = (\{[^;]*\});`).FindSubmatch(body)
|
||||
if len(match) < 2 {
|
||||
return nil, errors.New("missing settings variable")
|
||||
}
|
||||
|
||||
var settings Settings
|
||||
|
||||
if err := json.Unmarshal(match[1], &settings); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
data := url.Values{
|
||||
"request_type": {"RESPONSE"},
|
||||
"signInName": {v.user},
|
||||
"password": {v.password},
|
||||
}
|
||||
|
||||
v.Client.CheckRedirect = func(req *http.Request, via []*http.Request) error {
|
||||
if req.URL.Scheme != "https" {
|
||||
return http.ErrUseLastResponse
|
||||
}
|
||||
return nil
|
||||
}
|
||||
defer func() { v.Client.CheckRedirect = nil }()
|
||||
|
||||
uri2 := fmt.Sprintf("%s/SelfAsserted?tx=%s&p=B2C_1A_SignInSignUp_de-DE", loginUri(v.domain), settings.TransId)
|
||||
req, err = request.New(http.MethodPost, uri2, strings.NewReader(data.Encode()), request.URLEncoding, map[string]string{
|
||||
"Accept": "*/*",
|
||||
"Accept-Language": "en-us",
|
||||
"User-Agent": "Mozilla/5.0 (iPhone; CPU iPhone OS 17_0 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.1 Mobile/15E148 Safari/604.1",
|
||||
"Origin": "https://login.ford.com",
|
||||
"Referer": uri,
|
||||
"X-Csrf-Token": settings.Csrf,
|
||||
})
|
||||
|
||||
if err == nil {
|
||||
var resp *http.Response
|
||||
if resp, err = v.Do(req); err == nil {
|
||||
defer resp.Body.Close()
|
||||
|
||||
if resp.StatusCode != 200 {
|
||||
return nil, errors.New("self-assert failed")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
uri3 := fmt.Sprintf("%s/api/CombinedSigninAndSignup/confirmed?rememberMe=false&csrf_token=%s&tx=%s&p=B2C_1A_SignInSignUp_de-DE", loginUri(v.domain), settings.Csrf, settings.TransId)
|
||||
req, err = request.New(http.MethodGet, uri3, nil, request.URLEncoding, map[string]string{
|
||||
"Origin": "https://login.ford.com",
|
||||
"Referer": uri,
|
||||
"X-Csrf-Token": settings.Csrf,
|
||||
})
|
||||
|
||||
var loc *url.URL
|
||||
if err == nil {
|
||||
var resp *http.Response
|
||||
if resp, err = v.Do(req); err == nil {
|
||||
defer resp.Body.Close()
|
||||
loc, err = url.Parse(resp.Header.Get("location"))
|
||||
}
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
ctx, cancel := context.WithTimeout(
|
||||
context.WithValue(context.Background(), oauth2.HTTPClient, v.Client),
|
||||
request.Timeout,
|
||||
)
|
||||
defer cancel()
|
||||
|
||||
code := loc.Query().Get("code")
|
||||
if code == "" {
|
||||
return nil, errors.New("could not obtain auth code- check user and password")
|
||||
}
|
||||
|
||||
tok, err := oc.Exchange(ctx, code, oauth2.VerifierOption(cv))
|
||||
|
||||
// exchange code for api token
|
||||
var token oauth2.Token
|
||||
if err == nil {
|
||||
data := map[string]string{
|
||||
"idpToken": tok.AccessToken,
|
||||
}
|
||||
|
||||
uri := fmt.Sprintf("%s/api/token/v2/cat-with-b2c-access-token", TokenURI)
|
||||
|
||||
var req *http.Request
|
||||
req, err = request.New(http.MethodPost, uri, request.MarshalJSON(data), map[string]string{
|
||||
"Content-type": request.JSONContent,
|
||||
"Application-Id": ApplicationID,
|
||||
})
|
||||
|
||||
if err == nil {
|
||||
err = v.DoJSON(req, &token)
|
||||
}
|
||||
}
|
||||
|
||||
return util.TokenWithExpiry(&token), err
|
||||
}
|
||||
|
||||
// RefreshToken implements oauth.TokenRefresher
|
||||
func (v *Identity) RefreshToken(token *oauth2.Token) (*oauth2.Token, error) {
|
||||
data := map[string]string{
|
||||
"refresh_token": token.RefreshToken,
|
||||
}
|
||||
|
||||
uri := fmt.Sprintf("%s/api/token/v2/cat-with-refresh-token", TokenURI)
|
||||
req, _ := request.New(http.MethodPost, uri, request.MarshalJSON(data), map[string]string{
|
||||
"Content-type": request.JSONContent,
|
||||
"Application-Id": ApplicationID,
|
||||
})
|
||||
|
||||
var res *oauth2.Token
|
||||
err := v.DoJSON(req, &res)
|
||||
if err != nil {
|
||||
res, err = v.login()
|
||||
}
|
||||
|
||||
return util.TokenWithExpiry(res), err
|
||||
}
|
||||
|
|
@ -1,100 +0,0 @@
|
|||
package ford
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/evcc-io/evcc/api"
|
||||
"github.com/evcc-io/evcc/util"
|
||||
"github.com/evcc-io/evcc/vehicle/ford/autonomic"
|
||||
)
|
||||
|
||||
type Provider struct {
|
||||
statusG func() (autonomic.MetricsResponse, error)
|
||||
refreshG func() error
|
||||
}
|
||||
|
||||
func NewProvider(api *autonomic.API, vin string, cache time.Duration) *Provider {
|
||||
impl := &Provider{
|
||||
statusG: util.Cached(func() (autonomic.MetricsResponse, error) {
|
||||
return api.Status(vin)
|
||||
}, cache),
|
||||
refreshG: func() error {
|
||||
_, err := api.Refresh(vin)
|
||||
return err
|
||||
},
|
||||
}
|
||||
|
||||
return impl
|
||||
}
|
||||
|
||||
var _ api.Battery = (*Provider)(nil)
|
||||
|
||||
// Soc implements the api.Battery interface
|
||||
func (v *Provider) Soc() (float64, error) {
|
||||
res, err := v.statusG()
|
||||
if err == nil {
|
||||
return res.Metrics.XevBatteryStateOfCharge.Value, nil
|
||||
}
|
||||
|
||||
return 0, err
|
||||
}
|
||||
|
||||
var _ api.VehicleRange = (*Provider)(nil)
|
||||
|
||||
// Range implements the api.VehicleRange interface
|
||||
func (v *Provider) Range() (int64, error) {
|
||||
res, err := v.statusG()
|
||||
if err == nil {
|
||||
return int64(res.Metrics.XevBatteryRange.Value), nil
|
||||
}
|
||||
|
||||
return 0, err
|
||||
}
|
||||
|
||||
var _ api.ChargeState = (*Provider)(nil)
|
||||
|
||||
// Status implements the api.ChargeState interface
|
||||
func (v *Provider) Status() (api.ChargeStatus, error) {
|
||||
status := api.StatusNone
|
||||
|
||||
res, err := v.statusG()
|
||||
if err == nil {
|
||||
switch res.Metrics.XevPlugChargerStatus.Value {
|
||||
case "DISCONNECTED":
|
||||
status = api.StatusA // disconnected
|
||||
case "CONNECTED":
|
||||
status = api.StatusB // connected, not charging
|
||||
case "CHARGING", "CHARGINGAC":
|
||||
status = api.StatusC // charging
|
||||
default:
|
||||
err = fmt.Errorf("unknown charge status: %s", res.Metrics.XevPlugChargerStatus.Value)
|
||||
}
|
||||
}
|
||||
|
||||
return status, err
|
||||
}
|
||||
|
||||
var _ api.VehicleOdometer = (*Provider)(nil)
|
||||
|
||||
// Odometer implements the api.VehicleOdometer interface
|
||||
func (v *Provider) Odometer() (float64, error) {
|
||||
res, err := v.statusG()
|
||||
return res.Metrics.Odometer.Value, err
|
||||
}
|
||||
|
||||
var _ api.VehiclePosition = (*Provider)(nil)
|
||||
|
||||
// Position implements the api.VehiclePosition interface
|
||||
func (v *Provider) Position() (float64, float64, error) {
|
||||
res, err := v.statusG()
|
||||
loc := res.Metrics.Position.Value.Location
|
||||
return loc.Lat, loc.Lon, err
|
||||
}
|
||||
|
||||
var _ api.Resurrector = (*Provider)(nil)
|
||||
|
||||
// WakeUp implements the api.Resurrector interface
|
||||
func (v *Provider) WakeUp() error {
|
||||
return v.refreshG()
|
||||
}
|
||||
|
|
@ -1,9 +0,0 @@
|
|||
package ford
|
||||
|
||||
type VehiclesResponse struct {
|
||||
UserVehicles struct {
|
||||
VehicleDetails []struct {
|
||||
VIN string
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue