Add odometer api (#1357)

This commit is contained in:
andig 2021-08-23 13:27:07 +02:00 • committed by GitHub
parent 4cc6e93a1f
commit f6035829bd
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
12 changed files with 200 additions and 25 deletions

View file

@ -126,6 +126,11 @@ type VehicleClimater interface {
Climater() (active bool, outsideTemp float64, targetTemp float64, err error)
}
// VehicleOdometer returns the vehicles milage
type VehicleOdometer interface {
Odometer() (float64, error)
}
// VehicleStartCharge starts the charging session on the vehicle side
type VehicleStartCharge interface {
StartCharge() error

View file

@ -128,6 +128,14 @@ func (d *dumper) Dump(name string, v interface{}) {
}
}
if v, ok := v.(api.VehicleOdometer); ok {
if odo, err := v.Odometer(); err != nil {
fmt.Fprintf(w, "Odometer:\t%.0f\n", err)
} else {
fmt.Fprintf(w, "Odometer:\t%vkm\n", odo)
}
}
if v, ok := v.(api.VehicleFinishTimer); ok {
if ft, err := v.FinishTime(); err != nil {
fmt.Fprintf(w, "Finish time:\t%v\n", err)

View file

@ -133,6 +133,8 @@ type LoadPoint struct {
socCharge float64 // Vehicle SoC
chargedEnergy float64 // Charged energy while connected in Wh
chargeDuration time.Duration // Charge duration
tasks []func() error // task list for repeated execution
}
// NewLoadPointFromConfig creates a new loadpoint
@ -710,12 +712,15 @@ func (lp *LoadPoint) setActiveVehicle(vehicle api.Vehicle) {
lp.publish("hasVehicle", true)
lp.publish("socTitle", lp.vehicle.Title())
lp.publish("socCapacity", lp.vehicle.Capacity())
lp.task(lp.odometer)
} else {
lp.socEstimator = nil
lp.publish("hasVehicle", false)
lp.publish("socTitle", "")
lp.publish("socCapacity", 0)
lp.publish("socCapacity", int64(0))
lp.publish("socOdometer", 0.0)
}
}
@ -1243,6 +1248,9 @@ func (lp *LoadPoint) Update(sitePower float64, cheap bool) {
}
}
// odometer etc, if active
lp.runTasks()
// publish soc after updating charger status to make sure
// initial update of connected state matches charger status
lp.publishSoCAndRange()

41
core/loadpoint_task.go Normal file
View file

@ -0,0 +1,41 @@
package core
import (
"errors"
"github.com/evcc-io/evcc/api"
)
// task adds a task to the list of running tasks
func (lp *LoadPoint) task(task func() error) {
lp.tasks = append(lp.tasks, task)
}
// runTasks runs all defined tasks
func (lp *LoadPoint) runTasks() {
var incomplete []func() error
for _, task := range lp.tasks {
err := task()
if errors.Is(err, api.ErrMustRetry) {
incomplete = append(incomplete, task)
}
}
lp.tasks = incomplete
}
func (lp *LoadPoint) odometer() error {
v, ok := lp.vehicle.(api.VehicleOdometer)
if !ok {
return nil
}
odo, err := v.Odometer()
switch err {
case nil:
lp.publish("socOdometer", odo)
case api.ErrMustRetry:
default:
lp.log.ERROR.Printf("vehicle odometer: %v", err)
}
return err
}

View file

@ -69,11 +69,23 @@ func (v *Provider) Status() (api.ChargeStatus, error) {
var _ api.VehicleRange = (*Provider)(nil)
// Range implements the api.VehicleRange interface
func (v *Provider) Range() (rng int64, err error) {
func (v *Provider) Range() (int64, error) {
res, err := v.statusG()
if res, ok := res.(StatusResponse); err == nil && ok {
rng = int64(res.VehicleStatus.RemainingRangeElectric)
return int64(res.VehicleStatus.RemainingRangeElectric), nil
}
return rng, err
return 0, err
}
var _ api.VehicleOdometer = (*Provider)(nil)
// Odometer implements the api.VehicleOdometer interface
func (v *Provider) Odometer() (float64, error) {
res, err := v.statusG()
if res, ok := res.(StatusResponse); err == nil && ok {
return float64(res.VehicleStatus.Mileage), nil
}
return 0, err
}

View file

@ -111,3 +111,15 @@ func (v *Provider) Range() (int64, error) {
return 0, err
}
var _ api.VehicleOdometer = (*Provider)(nil)
// Odometer implements the api.VehicleOdometer interface
func (v *Provider) Odometer() (float64, error) {
res, err := v.statusG()
if res, ok := res.(StatusResponse); err == nil && ok {
return res.CarControlData.Mileage.Value, nil
}
return 0, err
}

View file

@ -95,7 +95,10 @@ type Status struct {
Status string // Disabled
}
}
Energy []Energy
Energy []Energy
Odometer struct {
Mileage float64
} `json:"timed.odometer"`
}
// Energy is the /status partial energy response

View file

@ -63,6 +63,19 @@ func (v *Provider) Range() (int64, error) {
return 0, err
}
var _ api.VehicleOdometer = (*Provider)(nil)
// Odometer implements the api.VehicleOdometer interface
func (v *Provider) Odometer() (float64, error) {
res, err := v.statusG()
if res, ok := res.(Status); err == nil && ok {
return res.Odometer.Mileage, nil
}
return 0, err
}
var _ api.VehicleFinishTimer = (*Provider)(nil)
// FinishTime implements the api.VehicleFinishTimer interface

View file

@ -92,6 +92,8 @@ type attributes struct {
// hvac-status
ExternalTemperature float64 `json:"externalTemperature"`
HvacStatus string `json:"hvacStatus"`
// cockpit
TotalMileage float64 `json:"totalMileage"`
}
// Renault is an api.Vehicle implementation for Renault cars
@ -103,6 +105,7 @@ type Renault struct {
gigyaJwtToken string
accountID string
batteryG func() (interface{}, error)
cockpitG func() (interface{}, error)
hvacG func() (interface{}, error)
}
@ -148,6 +151,7 @@ func NewRenaultFromConfig(other map[string]interface{}) (api.Vehicle, error) {
}
v.batteryG = provider.NewCached(v.batteryAPI, cc.Cache).InterfaceGetter()
v.cockpitG = provider.NewCached(v.cockpitAPI, cc.Cache).InterfaceGetter()
v.hvacG = provider.NewCached(v.hvacAPI, cc.Cache).InterfaceGetter()
return v, err
@ -337,6 +341,21 @@ func (v *Renault) hvacAPI() (interface{}, error) {
return res, err
}
// cockpitAPI provides cockpit api response
func (v *Renault) cockpitAPI() (interface{}, error) {
uri := fmt.Sprintf("%s/commerce/v1/accounts/%s/kamereon/kca/car-adapter/v2/cars/%s/cockpit", v.kamereon.Target, v.accountID, v.vin)
res, err := v.kamereonRequest(uri)
// repeat auth if error
if err != nil {
if err = v.authFlow(); err == nil {
res, err = v.kamereonRequest(uri)
}
}
return res, err
}
// SoC implements the api.Vehicle interface
func (v *Renault) SoC() (float64, error) {
res, err := v.batteryG()
@ -380,6 +399,19 @@ func (v *Renault) Range() (int64, error) {
return 0, err
}
var _ api.VehicleOdometer = (*Renault)(nil)
// Odometer implements the api.VehicleOdometer interface
func (v *Renault) Odometer() (float64, error) {
res, err := v.cockpitG()
if res, ok := res.(kamereonResponse); err == nil && ok {
return res.Data.Attributes.TotalMileage, nil
}
return 0, err
}
var _ api.VehicleFinishTimer = (*Renault)(nil)
// FinishTime implements the api.VehicleFinishTimer interface

View file

@ -19,7 +19,7 @@ type Tesla struct {
*embed
vehicle *tesla.Vehicle
chargeStateG func() (interface{}, error)
climateStateG func() (interface{}, error)
vehicleStateG func() (interface{}, error)
}
func init() {
@ -89,7 +89,7 @@ func NewTeslaFromConfig(other map[string]interface{}) (api.Vehicle, error) {
}
v.chargeStateG = provider.NewCached(v.chargeState, cc.Cache).InterfaceGetter()
v.climateStateG = provider.NewCached(v.climateState, cc.Cache).InterfaceGetter()
v.vehicleStateG = provider.NewCached(v.vehicleState, cc.Cache).InterfaceGetter()
return v, nil
}
@ -99,9 +99,9 @@ func (v *Tesla) chargeState() (interface{}, error) {
return v.vehicle.ChargeState()
}
// climateState implements the climater api
func (v *Tesla) climateState() (interface{}, error) {
return v.vehicle.ClimateState()
// vehicleState implements the climater api
func (v *Tesla) vehicleState() (interface{}, error) {
return v.vehicle.VehicleState()
}
// SoC implements the api.Vehicle interface
@ -147,6 +147,8 @@ func (v *Tesla) ChargedEnergy() (float64, error) {
return 0, err
}
const kmPerMile = 1.609344
var _ api.VehicleRange = (*Tesla)(nil)
// Range implements the api.VehicleRange interface
@ -155,7 +157,21 @@ func (v *Tesla) Range() (int64, error) {
if res, ok := res.(*tesla.ChargeState); err == nil && ok {
// miles to km
return int64(1.609344 * res.EstBatteryRange), nil
return int64(kmPerMile * res.EstBatteryRange), nil
}
return 0, err
}
var _ api.VehicleOdometer = (*Tesla)(nil)
// Odometer implements the api.VehicleOdometer interface
func (v *Tesla) Odometer() (float64, error) {
res, err := v.vehicleStateG()
if res, ok := res.(*tesla.VehicleState); err == nil && ok {
// miles to km
return kmPerMile * res.Odometer, nil
}
return 0, err

View file

@ -217,6 +217,19 @@ func (v *Tronity) Range() (int64, error) {
return 0, err
}
var _ api.VehicleOdometer = (*Tronity)(nil)
// Odometer implements the api.VehicleOdometer interface
func (v *Tronity) Odometer() (float64, error) {
res, err := v.bulkG()
if res, ok := res.(tronity.Bulk); err == nil && ok {
return res.Odometer, nil
}
return 0, err
}
func (v *Tronity) post(uri string) error {
resp, err := v.Post(uri, "", nil)
if err == nil {

View file

@ -66,20 +66,20 @@ type volvoStatus struct {
TimeToHVBatteryFullyCharged int `json:"timeToHVBatteryFullyCharged"`
TimeToHVBatteryFullyChargedTimestamp string `json:"timeToHVBatteryFullyChargedTimestamp"`
} `json:"hvBattery"`
Odometer int `json:"odometer"`
OdometerTimestamp string `json:"odometerTimestamp"`
PrivacyPolicyEnabled bool `json:"privacyPolicyEnabled"`
PrivacyPolicyEnabledTimestamp string `json:"privacyPolicyEnabledTimestamp"`
RemoteClimatizationStatus string `json:"remoteClimatizationStatus"` // CableConnectedWithoutPower
RemoteClimatizationStatusTimestamp string `json:"remoteClimatizationStatusTimestamp"`
ServiceWarningStatus string `json:"serviceWarningStatus"`
ServiceWarningStatusTimestamp string `json:"serviceWarningStatusTimestamp"`
TimeFullyAccessibleUntil string `json:"timeFullyAccessibleUntil"`
TimePartiallyAccessibleUntil string `json:"timePartiallyAccessibleUntil"`
TripMeter1 int `json:"tripMeter1"`
TripMeter1Timestamp string `json:"tripMeter1Timestamp"`
TripMeter2 int `json:"tripMeter2"`
TripMeter2Timestamp string `json:"tripMeter2Timestamp"`
Odometer float64 `json:"odometer"`
OdometerTimestamp string `json:"odometerTimestamp"`
PrivacyPolicyEnabled bool `json:"privacyPolicyEnabled"`
PrivacyPolicyEnabledTimestamp string `json:"privacyPolicyEnabledTimestamp"`
RemoteClimatizationStatus string `json:"remoteClimatizationStatus"` // CableConnectedWithoutPower
RemoteClimatizationStatusTimestamp string `json:"remoteClimatizationStatusTimestamp"`
ServiceWarningStatus string `json:"serviceWarningStatus"`
ServiceWarningStatusTimestamp string `json:"serviceWarningStatusTimestamp"`
TimeFullyAccessibleUntil string `json:"timeFullyAccessibleUntil"`
TimePartiallyAccessibleUntil string `json:"timePartiallyAccessibleUntil"`
TripMeter1 int `json:"tripMeter1"`
TripMeter1Timestamp string `json:"tripMeter1Timestamp"`
TripMeter2 int `json:"tripMeter2"`
TripMeter2Timestamp string `json:"tripMeter2Timestamp"`
}
// Volvo is an api.Vehicle implementation for Volvo cars
@ -220,6 +220,18 @@ func (v *Volvo) Range() (int64, error) {
return 0, err
}
var _ api.VehicleOdometer = (*Volvo)(nil)
// VehicleOdometer implements the api.VehicleOdometer interface
func (v *Volvo) Odometer() (float64, error) {
res, err := v.statusG()
if res, ok := res.(volvoStatus); err == nil && ok {
return float64(res.Odometer), nil
}
return 0, err
}
var _ api.VehicleFinishTimer = (*Volvo)(nil)
// FinishTime implements the VehicleFinishTimer interface