diff --git a/vehicle/jlr.go b/vehicle/jlr.go index c3e1e1ef4..9602fd603 100644 --- a/vehicle/jlr.go +++ b/vehicle/jlr.go @@ -82,7 +82,7 @@ func NewJLRFromConfig(other map[string]interface{}) (api.Vehicle, error) { }) if err == nil { - v.Provider = jlr.NewProvider(api, cc.VIN, cc.Cache) + v.Provider = jlr.NewProvider(api, cc.VIN, user.UserId, cc.Cache) } return v, err diff --git a/vehicle/jlr/api.go b/vehicle/jlr/api.go index 216a62b2a..a84c1838e 100644 --- a/vehicle/jlr/api.go +++ b/vehicle/jlr/api.go @@ -1,6 +1,7 @@ package jlr import ( + "errors" "fmt" "net/http" "net/url" @@ -33,7 +34,6 @@ func NewAPI(log *util.Logger, device string, ts oauth2.TokenSource) *API { if err == nil { for k, v := range map[string]string{ "Authorization": fmt.Sprintf("Bearer %s", token.AccessToken), - "Content-Type": request.JSONContent, "X-Device-Id": device, "x-telematicsprogramtype": "jlrpy", } { @@ -96,3 +96,67 @@ func (v *API) Status(vin string) (StatusResponse, error) { return status, err } + +// Position returns the vehicle position +func (v *API) Position(vin string) (PositionResponse, error) { + var status PositionResponse + + uri := fmt.Sprintf("%s/vehicles/%s/position", IF9_BASE_URL, vin) + req, err := request.New(http.MethodGet, uri, nil, map[string]string{ + "Content-Type": "application/json", + }) + + if err == nil { + err = v.DoJSON(req, &status) + } + + return status, err +} + +func (v *API) AuthenticateVinService(vin, user, service string) (PinResponse, error) { + pin := vin[len(vin)-4:] + data := map[string]string{ + "serviceName": service, + "pin": pin} + + uri := fmt.Sprintf("%s/vehicles/%s/users/%s/authenticate", IF9_BASE_URL, vin, user) + req, err := request.New(http.MethodPost, uri, request.MarshalJSON(data), map[string]string{ + "Content-Type": "application/vnd.wirelesscar.ngtp.if9.AuthenticateRequest-v2+json", + }) + + var res PinResponse + if err == nil { + err = v.DoJSON(req, &res) + } + + return res, err +} + +func (v *API) ChargeAction(vin, user string, start bool) error { + var data map[string]interface{} + + pin, err := v.AuthenticateVinService(vin, user, "CP") + if err == nil { + onoff := map[bool]string{false: "FORCE_OFF", true: "FORCE_ON"} + + data = map[string]interface{}{ + "token": pin.Token, + "serviceParameters": []KeyValue{{"CHARGE_NOW_SETTING", onoff[start]}}, + } + } + + uri := fmt.Sprintf("%s/vehicles/%s/chargeProfile", IF9_BASE_URL, vin) + req, err := request.New(http.MethodPost, uri, request.MarshalJSON(data), map[string]string{ + "Content-Type": "application/vnd.wirelesscar.ngtp.if9.PhevService-v1+json", + "Accept": "application/vnd.wirelesscar.ngtp.if9.ServiceStatus-v5+json", + }) + + var res ActionResponse + if err == nil { + if err = v.DoJSON(req, &res); err == nil && res.FailureDescription != "" { + err = errors.New(res.FailureDescription) + } + } + + return err +} diff --git a/vehicle/jlr/provider.go b/vehicle/jlr/provider.go index b1eee2597..113ed7718 100644 --- a/vehicle/jlr/provider.go +++ b/vehicle/jlr/provider.go @@ -8,14 +8,22 @@ import ( ) type Provider struct { - statusG func() (interface{}, error) + statusG func() (interface{}, error) + positionG func() (interface{}, error) + actionS func(bool) error } -func NewProvider(api *API, vin string, cache time.Duration) *Provider { +func NewProvider(api *API, vin, user string, cache time.Duration) *Provider { impl := &Provider{ statusG: provider.NewCached(func() (interface{}, error) { return api.Status(vin) }, cache).InterfaceGetter(), + positionG: provider.NewCached(func() (interface{}, error) { + return api.Position(vin) + }, cache).InterfaceGetter(), + actionS: func(start bool) error { + return api.ChargeAction(vin, user, start) + }, } return impl @@ -95,3 +103,29 @@ func (v *Provider) Odometer() (float64, error) { return val / 1e3, err } + +var _ api.VehiclePosition = (*Provider)(nil) + +// Position implements the api.VehiclePosition interface +func (v *Provider) Position() (float64, float64, error) { + res, err := v.positionG() + if res, ok := res.(PositionResponse); err == nil && ok { + return res.Position.Latitude, res.Position.Longitude, nil + } + + return 0, 0, err +} + +var _ api.VehicleStartCharge = (*Provider)(nil) + +// StartCharge implements the api.VehicleStartCharge interface +func (v *Provider) StartCharge() error { + return v.actionS(true) +} + +var _ api.VehicleStopCharge = (*Provider)(nil) + +// StopCharge implements the api.VehicleStopCharge interface +func (v *Provider) StopCharge() error { + return v.actionS(false) +} diff --git a/vehicle/jlr/types.go b/vehicle/jlr/types.go index d66cea86f..9b2bffabb 100644 --- a/vehicle/jlr/types.go +++ b/vehicle/jlr/types.go @@ -29,7 +29,8 @@ type VehiclesResponse struct { } type KeyValue struct { - Key, Value string + Key string `json:"key"` + Value string `json:"value"` } type KeyValueList []KeyValue @@ -41,6 +42,25 @@ type StatusResponse struct { } } +type PositionResponse struct { + Position struct { + Latitude float64 + Longitude float64 + Timestamp string + Speed float64 + Heading float64 + PositionQuality interface{} + } +} + +type PinResponse struct { + Token string +} + +type ActionResponse struct { + FailureDescription string +} + func (l KeyValueList) StringVal(key string) (string, error) { for _, el := range l { if el.Key == key {