Nissan: add Ariya template using v2 api (#13401)

This commit is contained in:
andig 2024-04-11 20:09:04 +02:00 • committed by GitHub
parent c85ec40ee1
commit 2bae9d8f7e
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
6 changed files with 91 additions and 55 deletions

View file

@ -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" . }}

View file

@ -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

View file

@ -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)

View file

@ -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 {

View file

@ -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)

View file

@ -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 {