Add vehicle range api (#486)

Co-authored-by: mark-sch <mark@schmalohr.de>
This commit is contained in:
andig 2020-11-27 14:02:36 +01:00 • committed by GitHub
parent 530ecb3b4c
commit fcfc22fa72
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
9 changed files with 143 additions and 23 deletions

View file

@ -97,6 +97,11 @@ type VehicleStatus interface {
Status() (ChargeStatus, error)
}
// VehicleRange provides the vehicles remaining km range
type VehicleRange interface {
Range() (int64, error)
}
// Climater provides climatisation data
type Climater interface {
Climater() (active bool, outsideTemp float64, targetTemp float64, err error)

View file

@ -74,12 +74,6 @@ func (d *dumper) Dump(name string, v interface{}) {
// charger
if v, ok := v.(api.Charger); ok {
// if status, err := v.Status(); err != nil {
// fmt.Fprintf(w, "Status:\t%v\n", err)
// } else {
// fmt.Fprintf(w, "Status:\t%s\n", status)
// }
if enabled, err := v.Enabled(); err != nil {
fmt.Fprintf(w, "Enabled:\t%v\n", err)
} else {
@ -103,23 +97,23 @@ func (d *dumper) Dump(name string, v interface{}) {
}
}
if v, ok := v.(api.ChargeFinishTimer); ok {
if ft, err := v.FinishTime(); err != nil {
fmt.Fprintf(w, "Finish time:\t%v\n", err)
} else {
fmt.Fprintf(w, "Finish time:\t%v\n", ft.Truncate(time.Minute))
}
}
// vehicle
if v, ok := v.(api.Vehicle); ok {
fmt.Printf("Capacity: %dkWh\n", v.Capacity())
fmt.Fprintf(w, "Capacity:\t%dkWh\n", v.Capacity())
if soc, err := v.ChargeState(); err != nil {
fmt.Printf("State: %v\n", err)
fmt.Fprintf(w, "State:\t%v\n", err)
} else {
fmt.Printf("State: %.0f%%\n", soc)
fmt.Fprintf(w, "State:\t%.0f%%\n", soc)
}
}
if v, ok := v.(api.VehicleRange); ok {
if rng, err := v.Range(); err != nil {
fmt.Fprintf(w, "Vehicle range:\t%v\n", err)
} else {
fmt.Fprintf(w, "Vehicle range:\t%vkm\n", rng)
}
}
@ -131,6 +125,14 @@ func (d *dumper) Dump(name string, v interface{}) {
}
}
if v, ok := v.(api.ChargeFinishTimer); ok {
if ft, err := v.FinishTime(); err != nil {
fmt.Fprintf(w, "Finish time:\t%v\n", err)
} else {
fmt.Fprintf(w, "Finish time:\t%v\n", ft.Truncate(time.Minute))
}
}
if v, ok := v.(api.Climater); ok {
if active, ot, tt, err := v.Climater(); err != nil {
fmt.Fprintf(w, "Climater:\t%v\n", err)

View file

@ -750,6 +750,20 @@ func (lp *LoadPoint) publishSoC() {
lp.publish("chargeEstimate", time.Duration(-1))
}
// publish remaining vehicle range
func (lp *LoadPoint) publishRange() {
if vs, ok := lp.vehicle.(api.VehicleRange); ok {
if rng, err := vs.Range(); err == nil {
lp.log.DEBUG.Printf("vehicle range: %vkm", rng)
lp.publish("range", rng)
return
}
}
lp.publish("range", -1)
}
// Update is the main control function. It reevaluates meters and charger state
func (lp *LoadPoint) Update(sitePower float64) {
mode := lp.GetMode()
@ -779,6 +793,7 @@ func (lp *LoadPoint) Update(sitePower float64) {
// initial update of connected state matches charger status
lp.findActiveVehicle()
lp.publishSoC()
lp.publishRange()
// sync settings with charger
lp.syncCharger()

View file

@ -6,6 +6,7 @@ import (
"github.com/andig/evcc/api"
)
// Response structure for kamereon api
type Response struct {
Data data `json:"data"`
}
@ -18,7 +19,8 @@ type attributes struct {
Timestamp string `json:"timestamp"`
ChargingStatus float32 `json:"chargingStatus"`
InstantaneousPower int `json:"instantaneousPower"`
RangeHvacOff int `json:"rangeHvacOff"`
RangeHvacOff int `json:"rangeHvacOff"` // Nissan
BatteryAutonomy int `json:"batteryAutonomy"` // Renault
BatteryLevel int `json:"batteryLevel"`
BatteryCapacity int `json:"batteryCapacity"` // Nissan
BatteryTemperature int `json:"batteryTemperature"`
@ -66,6 +68,17 @@ func (v *API) Status() (api.ChargeStatus, error) {
return status, err
}
// Range implements the Vehicle.Range interface
func (v *API) Range() (int64, error) {
res, err := v.apiG()
if res, ok := res.(Response); err == nil && ok {
return int64(res.Data.Attributes.RangeHvacOff), nil
}
return 0, err
}
// FinishTime implements the Vehicle.ChargeFinishTimer interface
func (v *API) FinishTime() (time.Time, error) {
res, err := v.apiG()

View file

@ -79,6 +79,7 @@ type attributes struct {
ChargingStatus float32 `json:"chargingStatus"`
InstantaneousPower int `json:"instantaneousPower"`
RangeHvacOff int `json:"rangeHvacOff"`
BatteryAutonomy int `json:"batteryAutonomy"`
BatteryLevel int `json:"batteryLevel"`
BatteryTemperature int `json:"batteryTemperature"`
PlugStatus int `json:"plugStatus"`
@ -341,6 +342,17 @@ func (v *Renault) Status() (api.ChargeStatus, error) {
return status, err
}
// Range implements the Vehicle.Range interface
func (v *Renault) Range() (int64, error) {
res, err := v.apiG()
if res, ok := res.(kamereonResponse); err == nil && ok {
return int64(res.Data.Attributes.BatteryAutonomy), nil
}
return 0, err
}
// FinishTime implements the Vehicle.ChargeFinishTimer interface
func (v *Renault) FinishTime() (time.Time, error) {
res, err := v.apiG()

View file

@ -24,13 +24,14 @@ func (m *embed) Capacity() int64 {
return m.capacity
}
//go:generate go run ../cmd/tools/decorate.go -p vehicle -f decorateVehicle -b api.Vehicle -o vehicle_decorators -t "api.VehicleStatus,Status,func() (api.ChargeStatus, error)"
//go:generate go run ../cmd/tools/decorate.go -p vehicle -f decorateVehicle -b api.Vehicle -o vehicle_decorators -t "api.VehicleStatus,Status,func() (api.ChargeStatus, error)" -t "api.VehicleRange,Range,func() (int64, error)"
// Vehicle is an api.Vehicle implementation with configurable getters and setters.
type Vehicle struct {
*embed
chargeG func() (float64, error)
statusG func() (string, error)
rangeG func() (int64, error)
}
func init() {
@ -44,6 +45,7 @@ func NewConfigurableFromConfig(other map[string]interface{}) (api.Vehicle, error
Capacity int64
Charge provider.Config
Status *provider.Config
Range *provider.Config
Cache time.Duration
}{}
@ -81,7 +83,17 @@ func NewConfigurableFromConfig(other map[string]interface{}) (api.Vehicle, error
status = v.status
}
res := decorateVehicle(v, status)
// decorate vehicle with Range
var rng func() (int64, error)
if cc.Range != nil {
v.rangeG, err = provider.NewIntGetterFromConfig(*cc.Range)
if err != nil {
return nil, err
}
rng = v.rng
}
res := decorateVehicle(v, status, rng)
return res, nil
}
@ -102,3 +114,8 @@ func (m *Vehicle) status() (api.ChargeStatus, error) {
return status, err
}
// rng implements the Vehicle.Range interface
func (m *Vehicle) rng() (int64, error) {
return m.rangeG()
}

View file

@ -6,12 +6,12 @@ import (
"github.com/andig/evcc/api"
)
func decorateVehicle(base api.Vehicle, vehicleStatus func() (api.ChargeStatus, error)) api.Vehicle {
func decorateVehicle(base api.Vehicle, vehicleStatus func() (api.ChargeStatus, error), vehicleRange func() (int64, error)) api.Vehicle {
switch {
case vehicleStatus == nil:
case vehicleRange == nil && vehicleStatus == nil:
return base
case vehicleStatus != nil:
case vehicleRange == nil && vehicleStatus != nil:
return &struct {
api.Vehicle
api.VehicleStatus
@ -21,11 +21,45 @@ func decorateVehicle(base api.Vehicle, vehicleStatus func() (api.ChargeStatus, e
vehicleStatus: vehicleStatus,
},
}
case vehicleRange != nil && vehicleStatus == nil:
return &struct {
api.Vehicle
api.VehicleRange
}{
Vehicle: base,
VehicleRange: &decorateVehicleVehicleRangeImpl{
vehicleRange: vehicleRange,
},
}
case vehicleRange != nil && vehicleStatus != nil:
return &struct {
api.Vehicle
api.VehicleRange
api.VehicleStatus
}{
Vehicle: base,
VehicleRange: &decorateVehicleVehicleRangeImpl{
vehicleRange: vehicleRange,
},
VehicleStatus: &decorateVehicleVehicleStatusImpl{
vehicleStatus: vehicleStatus,
},
}
}
return nil
}
type decorateVehicleVehicleRangeImpl struct {
vehicleRange func() (int64, error)
}
func (impl *decorateVehicleVehicleRangeImpl) Range() (int64, error) {
return impl.vehicleRange()
}
type decorateVehicleVehicleStatusImpl struct {
vehicleStatus func() (api.ChargeStatus, error)
}

View file

@ -51,6 +51,13 @@ type ChargerResponse struct {
PlugStatusData struct {
PlugState TimedString // connected
}
CruisingRangeStatusData struct {
EngineTypeFirstEngine TimedString // typeIsElectric, petrolGasoline
EngineTypeSecondEngine TimedString // typeIsElectric, petrolGasoline
PrimaryEngineRange TimedInt
SecondaryEngineRange TimedInt
HybridRange TimedInt
}
}
}
}

View file

@ -74,6 +74,21 @@ func (v *Implementation) Status() (api.ChargeStatus, error) {
return status, err
}
// Range implements the Vehicle.Range interface
func (v *Implementation) Range() (rng int64, err error) {
res, err := v.chargerG()
if res, ok := res.(ChargerResponse); err == nil && ok {
crsd := res.Charger.Status.CruisingRangeStatusData
rng = int64(crsd.PrimaryEngineRange.Content)
if crsd.EngineTypeFirstEngine.Content != "typeIsElectric" {
rng = int64(crsd.SecondaryEngineRange.Content)
}
}
return rng, err
}
// Climater implements the Vehicle.Climater interface
func (v *Implementation) Climater() (active bool, outsideTemp float64, targetTemp float64, err error) {
res, err := v.climateG()