diff --git a/templates/definition/vehicle/nissan-ariya.yaml b/templates/definition/vehicle/nissan-ariya.yaml new file mode 100644 index 000000000..b455b4486 --- /dev/null +++ b/templates/definition/vehicle/nissan-ariya.yaml @@ -0,0 +1,13 @@ +template: nissan-ariya +products: + - brand: Nissan + description: + generic: Ariya +params: + - preset: vehicle-base + - preset: vehicle-identify +render: | + type: nissan + version: v2 + {{ include "vehicle-base" . }} + {{ include "vehicle-identify" . }} diff --git a/vehicle/nissan.go b/vehicle/nissan.go index af44dfbb5..a56f59787 100644 --- a/vehicle/nissan.go +++ b/vehicle/nissan.go @@ -32,11 +32,13 @@ func NewNissanFromConfig(other map[string]interface{}) (api.Vehicle, error) { cc := struct { embed `mapstructure:",squash"` User, Password, VIN string + Version string Expiry time.Duration Cache time.Duration }{ - Expiry: expiry, - Cache: interval, + Version: "v1", // battery api version: v2 for Ariya + Expiry: expiry, + Cache: interval, } if err := util.DecodeOther(other, &cc); err != nil { @@ -64,7 +66,7 @@ func NewNissanFromConfig(other map[string]interface{}) (api.Vehicle, error) { cc.VIN, err = ensureVehicle(cc.VIN, api.Vehicles) if err == nil { - v.Provider = nissan.NewProvider(api, cc.VIN, cc.Expiry, cc.Cache) + v.Provider = nissan.NewProvider(api, cc.VIN, cc.Version, cc.Expiry, cc.Cache) } return v, err diff --git a/vehicle/nissan/api.go b/vehicle/nissan/api.go index 92ddab85d..db06210fa 100644 --- a/vehicle/nissan/api.go +++ b/vehicle/nissan/api.go @@ -59,9 +59,9 @@ func (v *API) Vehicles() ([]string, error) { return vehicles, err } -// Battery provides battery api response -func (v *API) BatteryStatus(vin string) (StatusResponse, error) { - uri := fmt.Sprintf("%s/v1/cars/%s/battery-status", CarAdapterBaseURL, vin) +// BatteryStatus provides battery api response +func (v *API) BatteryStatus(vin, version string) (StatusResponse, error) { + uri := fmt.Sprintf("%s/%s/cars/%s/battery-status", CarAdapterBaseURL, version, vin) var res StatusResponse err := v.GetJSON(uri, &res) diff --git a/vehicle/nissan/identity.go b/vehicle/nissan/identity.go index dd940e793..b7e13e66d 100644 --- a/vehicle/nissan/identity.go +++ b/vehicle/nissan/identity.go @@ -64,29 +64,27 @@ func (v *Identity) Login(user, password string) error { } // https://github.com/Tobiaswk/dartnissanconnect/commit/7d28dd5461aaed3e46b5be0c9fd58887e1e0cd0b - if err == nil { - err = api.ErrNotAvailable // not nil - for attempt := 1; attempt <= 10 && err != nil; attempt++ { - req, err = request.New(http.MethodPost, uri, request.MarshalJSON(res), map[string]string{ - "Accept-Api-Version": APIVersion, - "X-Username": "anonymous", - "X-Password": "anonymous", - "Content-type": "application/json", - "Accept": "application/json", - }) - if err == nil { - if err = v.DoJSON(req, &nToken); err != nil && !nToken.SessionExpired() { - break - } + err = api.ErrNotAvailable // not nil + for attempt := 1; attempt <= 10 && err != nil; attempt++ { + req, err = request.New(http.MethodPost, uri, request.MarshalJSON(res), map[string]string{ + "Accept-Api-Version": APIVersion, + "X-Username": "anonymous", + "X-Password": "anonymous", + "Content-type": "application/json", + "Accept": "application/json", + }) + if err == nil { + if err = v.DoJSON(req, &nToken); err != nil && !nToken.SessionExpired() { + break } } - - if errT := nToken.Error(); err != nil && errT != nil { - err = errT - } - - realm = strings.Trim(nToken.Realm, "/") } + + if errT := nToken.Error(); err != nil && errT != nil { + err = errT + } + + realm = strings.Trim(nToken.Realm, "/") } if err == nil { diff --git a/vehicle/nissan/provider.go b/vehicle/nissan/provider.go index e75d6d3b0..d17b95ed3 100644 --- a/vehicle/nissan/provider.go +++ b/vehicle/nissan/provider.go @@ -19,7 +19,7 @@ type Provider struct { } // NewProvider returns a kamereon provider -func NewProvider(api *API, vin string, expiry, cache time.Duration) *Provider { +func NewProvider(api *API, vin, version string, expiry, cache time.Duration) *Provider { impl := &Provider{ action: func(value Action) error { _, err := api.ChargingAction(vin, value) @@ -30,7 +30,7 @@ func NewProvider(api *API, vin string, expiry, cache time.Duration) *Provider { impl.statusG = provider.Cached(func() (StatusResponse, error) { return impl.status( - func() (StatusResponse, error) { return api.BatteryStatus(vin) }, + func() (StatusResponse, error) { return api.BatteryStatus(vin, version) }, func() (ActionResponse, error) { return api.RefreshRequest(vin, "RefreshBatteryStatus") }, ) }, cache) @@ -43,7 +43,8 @@ func (v *Provider) status(battery func() (StatusResponse, error), refresh func() if err == nil { // result valid? - if time.Since(res.Attributes.LastUpdateTime.Time) < v.expiry || res.Attributes.LastUpdateTime.IsZero() { + updated := res.Attributes.Updated() + if time.Since(updated) < v.expiry || updated.IsZero() { v.refreshTime = time.Time{} return res, err } @@ -116,16 +117,20 @@ 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 0, err + } - if err == nil { - if res.Attributes.RangeHvacOff == nil { - return 0, api.ErrNotAvailable - } - + if res.Attributes.RangeHvacOff != nil { return int64(*res.Attributes.RangeHvacOff), nil } - return 0, err + // v2 + if res.Attributes.BatteryAutonomy != nil { + return int64(*res.Attributes.BatteryAutonomy), nil + } + + return 0, api.ErrNotAvailable } var _ api.VehicleFinishTimer = (*Provider)(nil) @@ -133,16 +138,20 @@ var _ api.VehicleFinishTimer = (*Provider)(nil) // FinishTime implements the api.VehicleFinishTimer interface func (v *Provider) FinishTime() (time.Time, error) { res, err := v.statusG() - - if err == nil { - if res.Attributes.RemainingTime == nil { - return time.Time{}, api.ErrNotAvailable - } - - return res.Attributes.LastUpdateTime.Time.Add(time.Duration(*res.Attributes.RemainingTime) * time.Minute), err + if err != nil { + return time.Time{}, err } - return time.Time{}, err + if res.Attributes.RemainingTime != nil { + minutes := time.Duration(*res.Attributes.RemainingTime) * time.Minute + + updated := res.Attributes.Updated() + if !updated.IsZero() { + return updated.Add(minutes), nil + } + } + + return time.Time{}, api.ErrNotAvailable } var _ api.ChargeController = (*Provider)(nil) diff --git a/vehicle/nissan/types.go b/vehicle/nissan/types.go index 212b127a1..0919b687a 100644 --- a/vehicle/nissan/types.go +++ b/vehicle/nissan/types.go @@ -77,18 +77,32 @@ type StatusResponse struct { } type Attributes struct { - ChargeStatus float32 `json:"chargeStatus"` - RangeHvacOff *int `json:"rangeHvacOff"` - BatteryLevel int `json:"batteryLevel"` - BatteryCapacity int `json:"batteryCapacity"` - BatteryTemperature int `json:"batteryTemperature"` - PlugStatus int `json:"plugStatus"` - LastUpdateTime Timestamp `json:"lastUpdateTime"` - ChargePower int `json:"chargePower"` - RemainingTime *int `json:"chargingRemainingTime"` - RemainingToFullFast int `json:"timeRequiredToFullFast"` - RemainingToFullNormal int `json:"timeRequiredToFullNormal"` - RemainingToFullSlow int `json:"timeRequiredToFullSlow"` + ChargeStatus float32 `json:"chargeStatus"` + RangeHvacOff *int `json:"rangeHvacOff"` + BatteryLevel int `json:"batteryLevel"` + BatteryCapacity int `json:"batteryCapacity"` + BatteryTemperature int `json:"batteryTemperature"` + PlugStatus int `json:"plugStatus"` + LastUpdateTime *Timestamp `json:"lastUpdateTime"` + ChargePower int `json:"chargePower"` + RemainingTime *int `json:"chargingRemainingTime"` + RemainingToFullFast int `json:"timeRequiredToFullFast"` + RemainingToFullNormal int `json:"timeRequiredToFullNormal"` + RemainingToFullSlow int `json:"timeRequiredToFullSlow"` + // v2 + Timestamp *time.Time `json:"timestamp"` + BatteryAutonomy *int `json:"batteryAutonomy"` +} + +func (a *Attributes) Updated() time.Time { + if a.LastUpdateTime != nil { + // v1 + return a.LastUpdateTime.Time + } else if a.Timestamp != nil { + // v2 + return *a.Timestamp + } + return time.Time{} } type ActionResponse struct {