parent
73ae646023
commit
709fc827e2
5 changed files with 306 additions and 145 deletions
|
|
@ -72,12 +72,12 @@ func NewRenaultDaciaFromConfig(brand string, other map[string]any) (api.Vehicle,
|
|||
return nil, err
|
||||
}
|
||||
|
||||
api := kamereon.New(log, keys.Kamereon, identity, func() error {
|
||||
api := kamereon.NewAPI(log, keys.Kamereon, identity, func() error {
|
||||
return identity.Login(cc.User, cc.Password)
|
||||
})
|
||||
api.Client.Timeout = cc.Timeout
|
||||
|
||||
accountID, err := api.Person(identity.PersonID, brand)
|
||||
accountID, err := api.AccountID(identity.PersonID, brand)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,12 +1,9 @@
|
|||
package kamereon
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strings"
|
||||
|
||||
"github.com/evcc-io/evcc/util"
|
||||
|
|
@ -16,8 +13,9 @@ import (
|
|||
)
|
||||
|
||||
const (
|
||||
ActionStart = "start"
|
||||
ActionStop = "stop"
|
||||
ActionStart = "start"
|
||||
ActionStop = "stop"
|
||||
ActionResume = "resume"
|
||||
)
|
||||
|
||||
type API struct {
|
||||
|
|
@ -27,70 +25,43 @@ type API struct {
|
|||
login func() error
|
||||
}
|
||||
|
||||
func New(log *util.Logger, keys keys.ConfigServer, identity *gigya.Identity, login func() error) *API {
|
||||
return &API{
|
||||
func NewAPI(log *util.Logger, keys keys.ConfigServer, identity *gigya.Identity, login func() error) *API {
|
||||
v := &API{
|
||||
Helper: request.NewHelper(log),
|
||||
keys: keys,
|
||||
identity: identity,
|
||||
login: login,
|
||||
}
|
||||
|
||||
v.Client.Transport = &AuthDecorator{
|
||||
Login: v.login,
|
||||
Keys: v.keys,
|
||||
Identity: v.identity,
|
||||
Base: v.Client.Transport,
|
||||
}
|
||||
|
||||
return v
|
||||
}
|
||||
|
||||
func (v *API) request_(uri string, body io.Reader) (Response, error) {
|
||||
params := url.Values{"country": []string{"DE"}}
|
||||
headers := map[string]string{
|
||||
"content-type": "application/vnd.api+json",
|
||||
"x-gigya-id_token": v.identity.Token,
|
||||
"apikey": v.keys.APIKey,
|
||||
}
|
||||
|
||||
method := http.MethodGet
|
||||
if body != nil {
|
||||
method = http.MethodPost
|
||||
}
|
||||
|
||||
var res Response
|
||||
req, err := request.New(method, uri+"?"+params.Encode(), body, headers)
|
||||
if err == nil {
|
||||
err = v.DoJSON(req, &res)
|
||||
}
|
||||
|
||||
return res, err
|
||||
}
|
||||
|
||||
func (v *API) request(uri string, body io.Reader) (Response, error) {
|
||||
if body != nil {
|
||||
b, err := io.ReadAll(body)
|
||||
if err != nil {
|
||||
return Response{}, err
|
||||
}
|
||||
// read from buffer
|
||||
body = bytes.NewReader(b)
|
||||
}
|
||||
|
||||
res, err := v.request_(uri, body)
|
||||
// repeat auth if error
|
||||
if err != nil {
|
||||
if err = v.login(); err == nil {
|
||||
if body != nil {
|
||||
// rewind body
|
||||
body.(*bytes.Reader).Seek(0, io.SeekStart)
|
||||
}
|
||||
res, err = v.request_(uri, body)
|
||||
}
|
||||
}
|
||||
|
||||
return res, err
|
||||
}
|
||||
|
||||
func (v *API) Person(personID, brand string) (string, error) {
|
||||
func (v *API) Accounts(personID string) ([]Account, error) {
|
||||
uri := fmt.Sprintf("%s/commerce/v1/persons/%s", v.keys.Target, personID)
|
||||
res, err := v.request(uri, nil)
|
||||
|
||||
var res struct {
|
||||
Accounts []Account `json:"accounts"`
|
||||
}
|
||||
err := v.GetJSON(uri, &res)
|
||||
|
||||
return res.Accounts, err
|
||||
}
|
||||
|
||||
func (v *API) AccountID(personID, brand string) (string, error) {
|
||||
accounts, err := v.Accounts(personID)
|
||||
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
for _, account := range res.Accounts {
|
||||
for _, account := range accounts {
|
||||
if strings.Contains(strings.ToLower(account.AccountType), strings.ToLower(brand)) {
|
||||
return account.AccountID, nil
|
||||
}
|
||||
|
|
@ -101,47 +72,90 @@ func (v *API) Person(personID, brand string) (string, error) {
|
|||
|
||||
func (v *API) Vehicles(accountID string) ([]Vehicle, error) {
|
||||
uri := fmt.Sprintf("%s/commerce/v1/accounts/%s/vehicles", v.keys.Target, accountID)
|
||||
res, err := v.request(uri, nil)
|
||||
|
||||
var res struct {
|
||||
VehicleLinks []Vehicle `json:"vehicleLinks"`
|
||||
}
|
||||
err := v.GetJSON(uri, &res)
|
||||
|
||||
return res.VehicleLinks, err
|
||||
}
|
||||
|
||||
// Battery provides battery-status api response
|
||||
func (v *API) Battery(accountID string, vin string) (Response, error) {
|
||||
func (v *API) BatteryStatus(accountID string, vin string) (BatteryStatus, error) {
|
||||
uri := fmt.Sprintf("%s/commerce/v1/accounts/%s/kamereon/kca/car-adapter/v2/cars/%s/battery-status", v.keys.Target, accountID, vin)
|
||||
return v.request(uri, nil)
|
||||
|
||||
var res DataEnvelope[BatteryStatus]
|
||||
err := v.GetJSON(uri, &res)
|
||||
|
||||
return res.Data.Attributes, err
|
||||
}
|
||||
|
||||
// Hvac provides hvac-status api response
|
||||
func (v *API) Hvac(accountID string, vin string) (Response, error) {
|
||||
func (v *API) HvacStatus(accountID string, vin string) (HvacStatus, error) {
|
||||
uri := fmt.Sprintf("%s/commerce/v1/accounts/%s/kamereon/kca/car-adapter/v1/cars/%s/hvac-status", v.keys.Target, accountID, vin)
|
||||
return v.request(uri, nil)
|
||||
|
||||
var res DataEnvelope[HvacStatus]
|
||||
err := v.GetJSON(uri, &res)
|
||||
|
||||
return res.Data.Attributes, err
|
||||
}
|
||||
|
||||
// Cockpit provides cockpit api response
|
||||
func (v *API) Cockpit(accountID string, vin string) (Response, error) {
|
||||
func (v *API) Cockpit(accountID string, vin string) (Cockpit, error) {
|
||||
uri := fmt.Sprintf("%s/commerce/v1/accounts/%s/kamereon/kca/car-adapter/v1/cars/%s/cockpit", v.keys.Target, accountID, vin)
|
||||
return v.request(uri, nil)
|
||||
|
||||
var res DataEnvelope[Cockpit]
|
||||
err := v.GetJSON(uri, &res)
|
||||
|
||||
return res.Data.Attributes, err
|
||||
}
|
||||
|
||||
func (v *API) WakeUp(accountID string, vin string) (Response, error) {
|
||||
func (v *API) SocLevels(accountID string, vin string) (SocLevels, error) {
|
||||
uri := fmt.Sprintf("%s/commerce/v1/accounts/%s/kamereon/kcm/v1/vehicles/%s/ev/soc-levels", v.keys.Target, accountID, vin)
|
||||
|
||||
var res SocLevels
|
||||
err := v.GetJSON(uri, &res)
|
||||
|
||||
return res, err
|
||||
}
|
||||
|
||||
func (v *API) Position(accountID string, vin string) (Position, error) {
|
||||
uri := fmt.Sprintf("%s/commerce/v1/accounts/%s/kamereon/kca/car-adapter/v1/cars/%s/location", v.keys.Target, accountID, vin)
|
||||
|
||||
var res DataEnvelope[Position]
|
||||
err := v.GetJSON(uri, &res)
|
||||
|
||||
return res.Data.Attributes, err
|
||||
}
|
||||
|
||||
func (v *API) WakeUp(accountID string, vin string) (ChargeAction, error) {
|
||||
uri := fmt.Sprintf("%s/commerce/v1/accounts/%s/kamereon/kcm/v1/vehicles/%s/charge/pause-resume", v.keys.Target, accountID, vin)
|
||||
|
||||
data := map[string]any{
|
||||
"data": map[string]any{
|
||||
"type": "ChargePauseResume",
|
||||
"attributes": map[string]any{
|
||||
"action": "resume",
|
||||
reqBody := map[string]any{
|
||||
"data": ChargeAction{
|
||||
Type: "ChargePauseResume",
|
||||
Attributes: ChargeActionAttributes{
|
||||
Action: ActionResume,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
return v.request(uri, request.MarshalJSON(data))
|
||||
var res struct {
|
||||
Data ChargeAction `json:"data"`
|
||||
}
|
||||
req, err := request.New(http.MethodPost, uri, request.MarshalJSON(reqBody))
|
||||
|
||||
if err != nil {
|
||||
return ChargeAction{}, err
|
||||
}
|
||||
|
||||
err = v.DoJSON(req, &res)
|
||||
|
||||
return res.Data, err
|
||||
}
|
||||
|
||||
func (v *API) WakeUpMY24(accountID string, vin string) (Response, error) {
|
||||
func (v *API) WakeUpMy24(accountID string, vin string) (EvSettingsResponse, error) {
|
||||
uri := fmt.Sprintf("%s/commerce/v1/accounts/%s/kamereon/kcm/v1/vehicles/%s/ev/settings", v.keys.Target, accountID, vin)
|
||||
|
||||
data := EvSettingsRequest{
|
||||
reqBody := EvSettingsRequest{
|
||||
LastSettingsUpdateTimestamp: "2025-04-24T12:41:41.823Z",
|
||||
DelegatedActivated: false,
|
||||
ChargeModeRq: "SCHEDULED",
|
||||
|
|
@ -151,25 +165,40 @@ func (v *API) WakeUpMY24(accountID string, vin string) (Response, error) {
|
|||
Programs: []any{},
|
||||
}
|
||||
|
||||
return v.request(uri, request.MarshalJSON(data))
|
||||
req, err := request.New(http.MethodPost, uri, request.MarshalJSON(reqBody))
|
||||
|
||||
if err != nil {
|
||||
return EvSettingsResponse{}, err
|
||||
}
|
||||
|
||||
var res EvSettingsResponse
|
||||
err = v.DoJSON(req, &res)
|
||||
|
||||
return res, err
|
||||
}
|
||||
|
||||
func (v *API) Position(accountID string, vin string) (Response, error) {
|
||||
uri := fmt.Sprintf("%s/commerce/v1/accounts/%s/kamereon/kca/car-adapter/v1/cars/%s/location", v.keys.Target, accountID, vin)
|
||||
return v.request(uri, nil)
|
||||
}
|
||||
|
||||
func (v *API) Action(accountID, action string, vin string) (Response, error) {
|
||||
func (v *API) ChargeAction(accountID, action string, vin string) (ChargeAction, error) {
|
||||
uri := fmt.Sprintf("%s/commerce/v1/accounts/%s/kamereon/kca/car-adapter/v1/cars/%s/actions/charging-start", v.keys.Target, accountID, vin)
|
||||
|
||||
data := map[string]any{
|
||||
"data": map[string]any{
|
||||
"type": "ChargingStart",
|
||||
"attributes": map[string]any{
|
||||
"action": action,
|
||||
reqBody := map[string]any{
|
||||
"data": ChargeAction{
|
||||
Type: "ChargingStart",
|
||||
Attributes: ChargeActionAttributes{
|
||||
Action: action,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
return v.request(uri, request.MarshalJSON(data))
|
||||
var res struct {
|
||||
Data ChargeAction `json:"data"`
|
||||
}
|
||||
req, err := request.New(http.MethodPost, uri, request.MarshalJSON(reqBody))
|
||||
|
||||
if err != nil {
|
||||
return ChargeAction{}, err
|
||||
}
|
||||
|
||||
err = v.DoJSON(req, &res)
|
||||
|
||||
return res.Data, err
|
||||
}
|
||||
|
|
|
|||
76
vehicle/renault/kamereon/auth.go
Normal file
76
vehicle/renault/kamereon/auth.go
Normal file
|
|
@ -0,0 +1,76 @@
|
|||
package kamereon
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"io"
|
||||
"net/http"
|
||||
|
||||
"github.com/evcc-io/evcc/vehicle/renault/gigya"
|
||||
"github.com/evcc-io/evcc/vehicle/renault/keys"
|
||||
)
|
||||
|
||||
type AuthDecorator struct {
|
||||
Base http.RoundTripper
|
||||
Login func() error
|
||||
Keys keys.ConfigServer
|
||||
Identity *gigya.Identity
|
||||
}
|
||||
|
||||
func (rt *AuthDecorator) RoundTrip(req *http.Request) (*http.Response, error) {
|
||||
// Buffer request body for potential retries
|
||||
var (
|
||||
bodyBuffer []byte
|
||||
err error
|
||||
)
|
||||
if req.Body != nil {
|
||||
bodyBuffer, err = io.ReadAll(req.Body)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
_ = req.Body.Close()
|
||||
|
||||
req.Body = io.NopCloser(bytes.NewReader(bodyBuffer))
|
||||
}
|
||||
|
||||
resp, err := rt.executeRequest(req)
|
||||
|
||||
if err == nil && resp != nil && resp.StatusCode == http.StatusUnauthorized {
|
||||
// Drain and close response body
|
||||
io.Copy(io.Discard, resp.Body)
|
||||
resp.Body.Close()
|
||||
|
||||
// Try reauthenticating
|
||||
if err := rt.Login(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Reset request body
|
||||
if bodyBuffer != nil {
|
||||
req.Body = io.NopCloser(bytes.NewReader(bodyBuffer))
|
||||
}
|
||||
|
||||
// Retry the request
|
||||
resp, err = rt.executeRequest(req)
|
||||
}
|
||||
|
||||
return resp, err
|
||||
}
|
||||
|
||||
func (rt *AuthDecorator) executeRequest(req *http.Request) (*http.Response, error) {
|
||||
// Set required headers
|
||||
req.Header.Set("content-type", "application/vnd.api+json")
|
||||
req.Header.Set("x-gigya-id_token", rt.Identity.Token)
|
||||
req.Header.Set("apikey", rt.Keys.APIKey)
|
||||
|
||||
// Set country query parameter
|
||||
q := req.URL.Query()
|
||||
q.Set("country", "DE")
|
||||
req.URL.RawQuery = q.Encode()
|
||||
|
||||
base := rt.Base
|
||||
if base == nil {
|
||||
base = http.DefaultTransport
|
||||
}
|
||||
|
||||
return base.RoundTrip(req)
|
||||
}
|
||||
|
|
@ -5,13 +5,6 @@ import (
|
|||
"strings"
|
||||
)
|
||||
|
||||
type Response struct {
|
||||
Accounts []Account // /commerce/v1/persons/%s
|
||||
AccessToken string // /commerce/v1/accounts/%s/kamereon/token
|
||||
VehicleLinks []Vehicle // /commerce/v1/accounts/%s/vehicles
|
||||
Data Data // /commerce/v1/accounts/%s/kamereon/kca/car-adapter/vX/cars/%s/...
|
||||
}
|
||||
|
||||
type Account struct {
|
||||
AccountID string
|
||||
AccountType string
|
||||
|
|
@ -44,12 +37,7 @@ func (v *Vehicle) Available() error {
|
|||
return nil
|
||||
}
|
||||
|
||||
type Data struct {
|
||||
Attributes attributes
|
||||
}
|
||||
|
||||
type attributes struct {
|
||||
// battery-status
|
||||
type BatteryStatus struct {
|
||||
Timestamp string `json:"timestamp"`
|
||||
ChargingStatus float32 `json:"chargingStatus"`
|
||||
InstantaneousPower int `json:"instantaneousPower"`
|
||||
|
|
@ -61,16 +49,43 @@ type attributes struct {
|
|||
LastUpdateTime string `json:"lastUpdateTime"`
|
||||
ChargePower int `json:"chargePower"`
|
||||
RemainingTime *int `json:"chargingRemainingTime"`
|
||||
// hvac-status
|
||||
}
|
||||
|
||||
type HvacStatus struct {
|
||||
ExternalTemperature float64 `json:"externalTemperature"`
|
||||
HvacStatus string `json:"hvacStatus"`
|
||||
// cockpit
|
||||
}
|
||||
|
||||
type Cockpit struct {
|
||||
TotalMileage *float64 `json:"totalMileage"`
|
||||
// position
|
||||
}
|
||||
|
||||
type SocLevels struct {
|
||||
SocMin *int `json:"socMin"`
|
||||
SocTarget *int `json:"socTarget"`
|
||||
LastEnergyUpdateTimestamp string `json:"lastEnergyUpdateTimestamp"`
|
||||
}
|
||||
|
||||
type Position struct {
|
||||
Latitude float64 `json:"gpsLatitude"`
|
||||
Longitude float64 `json:"gpsLongitude"`
|
||||
}
|
||||
|
||||
type ChargeAction struct {
|
||||
Type string `json:"type"`
|
||||
Attributes ChargeActionAttributes `json:"attributes"`
|
||||
}
|
||||
|
||||
type ChargeActionAttributes struct {
|
||||
Action string `json:"action"`
|
||||
}
|
||||
|
||||
type DataEnvelope[T any] struct {
|
||||
Data struct {
|
||||
Attributes T `json:"attributes"`
|
||||
} `json:"data"`
|
||||
}
|
||||
|
||||
type EvSettingsRequest struct {
|
||||
LastSettingsUpdateTimestamp string `json:"lastSettingsUpdateTimestamp"`
|
||||
DelegatedActivated bool `json:"delegatedActivated"`
|
||||
|
|
@ -83,3 +98,9 @@ type EvSettingsRequest struct {
|
|||
PreconditioningHeatedLeftSeat bool `json:"preconditioningHeatedLeftSeat"`
|
||||
Programs []any `json:"programs"`
|
||||
}
|
||||
|
||||
type EvSettingsResponse struct {
|
||||
CommandId string `json:"commandId"`
|
||||
Type string `json:"type"`
|
||||
Status string `json:"status"`
|
||||
}
|
||||
|
|
|
|||
|
|
@ -15,41 +15,53 @@ import (
|
|||
|
||||
// Provider is an api.Vehicle implementation for PSA cars
|
||||
type Provider struct {
|
||||
batteryG func() (kamereon.Response, error)
|
||||
cockpitG func() (kamereon.Response, error)
|
||||
hvacG func() (kamereon.Response, error)
|
||||
wakeup func() (kamereon.Response, error)
|
||||
position func() (kamereon.Response, error)
|
||||
action func(action string) (kamereon.Response, error)
|
||||
batteryStatusG func() (kamereon.BatteryStatus, error)
|
||||
cockpitG func() (kamereon.Cockpit, error)
|
||||
socLevelsG func() (kamereon.SocLevels, error)
|
||||
hvacG func() (kamereon.HvacStatus, error)
|
||||
wakeup func() error
|
||||
position func() (kamereon.Position, error)
|
||||
chargeAction func(action string) (kamereon.ChargeAction, error)
|
||||
}
|
||||
|
||||
// NewProvider creates a vehicle api provider
|
||||
func NewProvider(api *kamereon.API, accountID, vin string, wakeupMode string, cache time.Duration) *Provider {
|
||||
impl := &Provider{
|
||||
batteryG: util.Cached(func() (kamereon.Response, error) {
|
||||
return api.Battery(accountID, vin)
|
||||
batteryStatusG: util.Cached(func() (kamereon.BatteryStatus, error) {
|
||||
return api.BatteryStatus(accountID, vin)
|
||||
}, cache),
|
||||
cockpitG: util.Cached(func() (kamereon.Response, error) {
|
||||
cockpitG: util.Cached(func() (kamereon.Cockpit, error) {
|
||||
return api.Cockpit(accountID, vin)
|
||||
}, cache),
|
||||
hvacG: util.Cached(func() (kamereon.Response, error) {
|
||||
return api.Hvac(accountID, vin)
|
||||
socLevelsG: util.Cached(func() (kamereon.SocLevels, error) {
|
||||
return api.SocLevels(accountID, vin)
|
||||
}, cache),
|
||||
wakeup: func() (kamereon.Response, error) {
|
||||
hvacG: util.Cached(func() (kamereon.HvacStatus, error) {
|
||||
return api.HvacStatus(accountID, vin)
|
||||
}, cache),
|
||||
wakeup: func() error {
|
||||
var err error
|
||||
switch wakeupMode {
|
||||
case "alternative":
|
||||
return api.Action(accountID, kamereon.ActionStart, vin)
|
||||
_, err = api.ChargeAction(accountID, kamereon.ActionStart, vin)
|
||||
case "MY24":
|
||||
return api.WakeUpMY24(accountID, vin)
|
||||
_, err = api.WakeUpMy24(accountID, vin)
|
||||
default:
|
||||
return api.WakeUp(accountID, vin)
|
||||
_, err = api.WakeUp(accountID, vin)
|
||||
|
||||
// Check if default wakeup is unsupported
|
||||
var se *request.StatusError
|
||||
if errors.As(err, &se) && se.HasStatus(http.StatusForbidden, http.StatusNotFound, http.StatusBadGateway) {
|
||||
_, err = api.WakeUpMy24(accountID, vin)
|
||||
}
|
||||
}
|
||||
return err
|
||||
},
|
||||
position: func() (kamereon.Response, error) {
|
||||
position: func() (kamereon.Position, error) {
|
||||
return api.Position(accountID, vin)
|
||||
},
|
||||
action: func(action string) (kamereon.Response, error) {
|
||||
return api.Action(accountID, action, vin)
|
||||
chargeAction: func(action string) (kamereon.ChargeAction, error) {
|
||||
return api.ChargeAction(accountID, action, vin)
|
||||
},
|
||||
}
|
||||
return impl
|
||||
|
|
@ -59,16 +71,16 @@ var _ api.Battery = (*Provider)(nil)
|
|||
|
||||
// Soc implements the api.Vehicle interface
|
||||
func (v *Provider) Soc() (float64, error) {
|
||||
res, err := v.batteryG()
|
||||
res, err := v.batteryStatusG()
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
if res.Data.Attributes.BatteryLevel == nil {
|
||||
if res.BatteryLevel == nil {
|
||||
return 0, api.ErrNotAvailable
|
||||
}
|
||||
|
||||
return float64(*res.Data.Attributes.BatteryLevel), nil
|
||||
return float64(*res.BatteryLevel), nil
|
||||
}
|
||||
|
||||
var _ api.ChargeState = (*Provider)(nil)
|
||||
|
|
@ -77,12 +89,12 @@ var _ api.ChargeState = (*Provider)(nil)
|
|||
func (v *Provider) Status() (api.ChargeStatus, error) {
|
||||
status := api.StatusA // disconnected
|
||||
|
||||
res, err := v.batteryG()
|
||||
res, err := v.batteryStatusG()
|
||||
if err == nil {
|
||||
if res.Data.Attributes.PlugStatus == 1 {
|
||||
if res.PlugStatus == 1 {
|
||||
status = api.StatusB
|
||||
}
|
||||
if res.Data.Attributes.ChargingStatus >= 1.0 {
|
||||
if res.ChargingStatus >= 1.0 {
|
||||
status = api.StatusC
|
||||
}
|
||||
}
|
||||
|
|
@ -94,10 +106,10 @@ var _ api.VehicleRange = (*Provider)(nil)
|
|||
|
||||
// Range implements the api.VehicleRange interface
|
||||
func (v *Provider) Range() (int64, error) {
|
||||
res, err := v.batteryG()
|
||||
res, err := v.batteryStatusG()
|
||||
|
||||
if err == nil {
|
||||
return int64(res.Data.Attributes.BatteryAutonomy), nil
|
||||
return int64(res.BatteryAutonomy), nil
|
||||
}
|
||||
|
||||
return 0, err
|
||||
|
|
@ -112,8 +124,31 @@ func (v *Provider) Odometer() (float64, error) {
|
|||
return 0, err
|
||||
}
|
||||
|
||||
if res.Data.Attributes.TotalMileage != nil {
|
||||
return *res.Data.Attributes.TotalMileage, nil
|
||||
if res.TotalMileage != nil {
|
||||
return *res.TotalMileage, nil
|
||||
}
|
||||
|
||||
return 0, api.ErrNotAvailable
|
||||
}
|
||||
|
||||
var _ api.SocLimiter = (*Provider)(nil)
|
||||
|
||||
// GetLimitSoc implements the api.SocLimiter interface
|
||||
func (v *Provider) GetLimitSoc() (int64, error) {
|
||||
res, err := v.socLevelsG()
|
||||
|
||||
// Check if endpoint is unavailable
|
||||
var se *request.StatusError
|
||||
if errors.As(err, &se) && se.HasStatus(http.StatusForbidden, http.StatusNotFound, http.StatusBadGateway) {
|
||||
return 0, api.ErrNotAvailable
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
if res.SocTarget != nil {
|
||||
return int64(*res.SocTarget), nil
|
||||
}
|
||||
|
||||
return 0, api.ErrNotAvailable
|
||||
|
|
@ -123,16 +158,16 @@ var _ api.VehicleFinishTimer = (*Provider)(nil)
|
|||
|
||||
// FinishTime implements the api.VehicleFinishTimer interface
|
||||
func (v *Provider) FinishTime() (time.Time, error) {
|
||||
res, err := v.batteryG()
|
||||
res, err := v.batteryStatusG()
|
||||
|
||||
if err == nil {
|
||||
timestamp, err := time.Parse(time.RFC3339, res.Data.Attributes.Timestamp)
|
||||
timestamp, err := time.Parse(time.RFC3339, res.Timestamp)
|
||||
|
||||
if res.Data.Attributes.RemainingTime == nil {
|
||||
if res.RemainingTime == nil {
|
||||
return time.Time{}, api.ErrNotAvailable
|
||||
}
|
||||
|
||||
return timestamp.Add(time.Duration(*res.Data.Attributes.RemainingTime) * time.Minute), err
|
||||
return timestamp.Add(time.Duration(*res.RemainingTime) * time.Minute), err
|
||||
}
|
||||
|
||||
return time.Time{}, err
|
||||
|
|
@ -145,12 +180,13 @@ func (v *Provider) Climater() (bool, error) {
|
|||
res, err := v.hvacG()
|
||||
|
||||
// Zoe Ph2, Megane e-tech
|
||||
if se := new(request.StatusError); errors.As(err, &se) && se.HasStatus(http.StatusForbidden, http.StatusNotFound, http.StatusBadGateway) {
|
||||
var se *request.StatusError
|
||||
if errors.As(err, &se) && se.HasStatus(http.StatusForbidden, http.StatusNotFound, http.StatusBadGateway) {
|
||||
return false, api.ErrNotAvailable
|
||||
}
|
||||
|
||||
if err == nil {
|
||||
state := strings.ToLower(res.Data.Attributes.HvacStatus)
|
||||
state := strings.ToLower(res.HvacStatus)
|
||||
if state == "" {
|
||||
return false, api.ErrNotAvailable
|
||||
}
|
||||
|
|
@ -166,8 +202,7 @@ var _ api.Resurrector = (*Provider)(nil)
|
|||
|
||||
// WakeUp implements the api.Resurrector interface
|
||||
func (v *Provider) WakeUp() error {
|
||||
_, err := v.wakeup()
|
||||
return err
|
||||
return v.wakeup()
|
||||
}
|
||||
|
||||
var _ api.VehiclePosition = (*Provider)(nil)
|
||||
|
|
@ -176,7 +211,7 @@ var _ api.VehiclePosition = (*Provider)(nil)
|
|||
func (v *Provider) Position() (float64, float64, error) {
|
||||
res, err := v.position()
|
||||
if err == nil {
|
||||
return res.Data.Attributes.Latitude, res.Data.Attributes.Longitude, nil
|
||||
return res.Latitude, res.Longitude, nil
|
||||
}
|
||||
|
||||
return 0, 0, err
|
||||
|
|
@ -187,6 +222,6 @@ var _ api.ChargeController = (*Provider)(nil)
|
|||
// ChargeEnable implements the api.ChargeController interface
|
||||
func (v *Provider) ChargeEnable(enable bool) error {
|
||||
action := map[bool]string{true: kamereon.ActionStart, false: kamereon.ActionStop}
|
||||
_, err := v.action(action[enable])
|
||||
_, err := v.chargeAction(action[enable])
|
||||
return err
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue