Add odometer api (#1357)
This commit is contained in:
parent
4cc6e93a1f
commit
f6035829bd
12 changed files with 200 additions and 25 deletions
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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
41
core/loadpoint_task.go
Normal 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
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue