evcc-io/vehicle/volvo.go
2023-03-19 13:28:26 +01:00

167 lines
4.1 KiB
Go

package vehicle
import (
"fmt"
"time"
"github.com/evcc-io/evcc/api"
"github.com/evcc-io/evcc/provider"
"github.com/evcc-io/evcc/util"
"github.com/evcc-io/evcc/util/request"
"github.com/evcc-io/evcc/util/transport"
"github.com/evcc-io/evcc/vehicle/volvo"
)
// Volvo is an api.Vehicle implementation for Volvo. cars
type Volvo struct {
*embed
*request.Helper
vin string
statusG func() (volvo.Status, error)
}
func init() {
registry.Add("volvo", NewVolvoFromConfig)
}
// NewVolvoFromConfig creates a new vehicle
func NewVolvoFromConfig(other map[string]interface{}) (api.Vehicle, error) {
cc := struct {
embed `mapstructure:",squash"`
User, Password, VIN string
Cache time.Duration
}{
Cache: interval,
}
if err := util.DecodeOther(other, &cc); err != nil {
return nil, err
}
if cc.User == "" || cc.Password == "" {
return nil, api.ErrMissingCredentials
}
basicAuth := transport.BasicAuthHeader(cc.User, cc.Password)
log := util.NewLogger("volvo").Redact(cc.User, cc.Password, cc.VIN, basicAuth)
v := &Volvo{
embed: &cc.embed,
Helper: request.NewHelper(log),
vin: cc.VIN,
}
v.Client.Transport = &transport.Decorator{
Base: v.Client.Transport,
Decorator: transport.DecorateHeaders(map[string]string{
"Authorization": basicAuth,
"Content-Type": "application/json",
"X-Device-Id": "Device",
"X-OS-Type": "Android",
"X-Originator-Type": "App",
"X-OS-Version": "22",
}),
}
v.statusG = provider.Cached(v.StatusRequest, cc.Cache)
var err error
v.vin, err = ensureVehicle(cc.VIN, v.Vehicles)
return v, err
}
// vehicles implements returns the list of user vehicles
func (v *Volvo) Vehicles() ([]string, error) {
var vehicles []string
uri := fmt.Sprintf("%s/customeraccounts", volvo.ApiURI)
var res volvo.AccountResponse
err := v.GetJSON(uri, &res)
if err == nil {
for _, rel := range res.VehicleRelations {
var vehicle volvo.VehicleRelation
if err := v.GetJSON(rel, &vehicle); err != nil {
return vehicles, err
}
vehicles = append(vehicles, vehicle.VehicleID)
}
} else if res.ErrorLabel != "" {
err = fmt.Errorf("%w: %s: %s", err, res.ErrorLabel, res.ErrorDescription)
}
return vehicles, err
}
func (v *Volvo) StatusRequest() (volvo.Status, error) {
var res volvo.Status
uri := fmt.Sprintf("%s/vehicles/%s/status", volvo.ApiURI, v.vin)
err := v.GetJSON(uri, &res)
if err != nil && res.ErrorLabel != "" {
err = fmt.Errorf("%w: %s: %s", err, res.ErrorLabel, res.ErrorDescription)
}
return res, err
}
// Soc implements the api.Vehicle interface
func (v *Volvo) Soc() (float64, error) {
res, err := v.statusG()
return float64(res.HvBattery.HvBatteryLevel), err
}
var _ api.ChargeState = (*Volvo)(nil)
// Status implements the api.ChargeState interface
func (v *Volvo) Status() (api.ChargeStatus, error) {
res, err := v.statusG()
if err == nil {
switch res.HvBattery.HvBatteryChargeStatusDerived {
case "CableNotPluggedInCar":
return api.StatusA, nil
case "CablePluggedInCar", "CablePluggedInCar_FullyCharged", "CablePluggedInCar_ChargingPaused":
return api.StatusB, nil
case "Charging", "CablePluggedInCar_Charging":
return api.StatusC, nil
}
}
return api.StatusNone, err
}
var _ api.VehicleRange = (*Volvo)(nil)
// VehicleRange implements the api.VehicleRange interface
func (v *Volvo) Range() (int64, error) {
res, err := v.statusG()
return int64(res.HvBattery.DistanceToHVBatteryEmpty), err
}
var _ api.VehicleOdometer = (*Volvo)(nil)
// VehicleOdometer implements the api.VehicleOdometer interface
func (v *Volvo) Odometer() (float64, error) {
res, err := v.statusG()
return res.Odometer / 1e3, err
}
var _ api.VehicleFinishTimer = (*Volvo)(nil)
// FinishTime implements the VehicleFinishTimer interface
func (v *Volvo) FinishTime() (time.Time, error) {
res, err := v.statusG()
if err == nil {
timestamp := res.HvBattery.TimeToHVBatteryFullyChargedTimestamp.Add(time.Duration(res.HvBattery.DistanceToHVBatteryEmpty) * time.Minute)
if timestamp.Before(time.Now()) {
return time.Time{}, api.ErrNotAvailable
}
return timestamp, err
}
return time.Time{}, err
}