Improve vehicle identification experience (#1889)
- allow multiple identifiers per vehicle to support RFID and MAC across multiple chargers - move onIdentify action config from loadpoint to vehicle - add minSoC to action config - replace onDisconnect with simpler resetOnDisconnect
This commit is contained in:
parent
15a51a13a4
commit
9b16f9bcf2
10 changed files with 172 additions and 98 deletions
12
api/api.go
12
api/api.go
|
|
@ -40,6 +40,15 @@ func (c ChargeStatus) String() string {
|
|||
return string(c)
|
||||
}
|
||||
|
||||
// ActionConfig defines an action to take on event
|
||||
type ActionConfig struct {
|
||||
Mode ChargeMode `mapstructure:"mode"` // Charge mode
|
||||
MinCurrent float64 `mapstructure:"minCurrent"` // Minimum Current
|
||||
MaxCurrent float64 `mapstructure:"maxCurrent"` // Maximum Current
|
||||
MinSoC int `mapstructure:"minSoC"` // Minimum SoC
|
||||
TargetSoC int `mapstructure:"targetSoC"` // Target SoC
|
||||
}
|
||||
|
||||
// Meter is able to provide current power in W
|
||||
type Meter interface {
|
||||
CurrentPower() (float64, error)
|
||||
|
|
@ -111,9 +120,10 @@ type Authorizer interface {
|
|||
// Vehicle represents the EV and it's battery
|
||||
type Vehicle interface {
|
||||
Battery
|
||||
Identifier
|
||||
Title() string
|
||||
Capacity() int64
|
||||
Identifiers() []string
|
||||
OnIdentified() ActionConfig
|
||||
}
|
||||
|
||||
// VehicleFinishTimer provides estimated charge cycle finish time
|
||||
|
|
|
|||
|
|
@ -179,6 +179,7 @@ func (d *dumper) Dump(name string, v interface{}) {
|
|||
|
||||
if v, ok := v.(api.Vehicle); ok {
|
||||
fmt.Fprintf(w, "Capacity:\t%dkWh\n", v.Capacity())
|
||||
fmt.Fprintf(w, "Identifiers:\t%v\n", v.Identifiers())
|
||||
}
|
||||
|
||||
w.Flush()
|
||||
|
|
|
|||
|
|
@ -16,6 +16,7 @@ import (
|
|||
"github.com/evcc-io/evcc/provider"
|
||||
"github.com/evcc-io/evcc/push"
|
||||
"github.com/evcc-io/evcc/util"
|
||||
"github.com/thoas/go-funk"
|
||||
|
||||
evbus "github.com/asaskevich/EventBus"
|
||||
"github.com/avast/retry-go/v3"
|
||||
|
|
@ -65,12 +66,6 @@ type ThresholdConfig struct {
|
|||
Threshold float64
|
||||
}
|
||||
|
||||
// ActionConfig defines an action to take on event
|
||||
type ActionConfig struct {
|
||||
Mode api.ChargeMode `mapstructure:"mode"` // Charge mode to apply when car disconnected
|
||||
TargetSoC int `mapstructure:"targetSoC"` // Target SoC to apply when car disconnected
|
||||
}
|
||||
|
||||
// LoadPoint is responsible for controlling charge depending on
|
||||
// SoC needs and power availability.
|
||||
type LoadPoint struct {
|
||||
|
|
@ -93,10 +88,12 @@ type LoadPoint struct {
|
|||
Meters struct {
|
||||
ChargeMeterRef string `mapstructure:"charge"` // Charge meter reference
|
||||
}
|
||||
SoC SoCConfig
|
||||
OnDisconnect ActionConfig `mapstructure:"onDisconnect"`
|
||||
OnIdentify map[string]ActionConfig `mapstructure:"onIdentify"`
|
||||
Enable, Disable ThresholdConfig
|
||||
SoC SoCConfig
|
||||
OnDisconnect_ interface{} `mapstructure:"onDisconnect"`
|
||||
OnIdentify_ interface{} `mapstructure:"onIdentify"`
|
||||
Enable, Disable ThresholdConfig
|
||||
ResetOnDisconnect bool `mapstructure:"resetOnDisconnect"`
|
||||
onDisconnect api.ActionConfig
|
||||
|
||||
MinCurrent float64 // PV mode: start current Min+PV mode: min current
|
||||
MaxCurrent float64 // Max allowed current. Physically ensured by the charger
|
||||
|
|
@ -157,6 +154,14 @@ func NewLoadPointFromConfig(log *util.Logger, cp configProvider, other map[strin
|
|||
lp.SoC.Poll.Mode = pollConnected
|
||||
}
|
||||
|
||||
if lp.OnIdentify_ != nil {
|
||||
lp.log.WARN.Printf("loadpoint.onIdentify is deprecated and will be removed in a future release. Use vehicle.onIdentify instead.")
|
||||
}
|
||||
|
||||
if lp.OnDisconnect_ != nil {
|
||||
lp.log.WARN.Printf("loadpoint.onDisconnect is deprecated and will be removed in a future release. Use loadpoint.resetOnDisconnect instead.")
|
||||
}
|
||||
|
||||
// set vehicle polling interval
|
||||
if lp.SoC.Poll.Interval < pollInterval {
|
||||
if lp.SoC.Poll.Interval == 0 {
|
||||
|
|
@ -167,7 +172,7 @@ func NewLoadPointFromConfig(log *util.Logger, cp configProvider, other map[strin
|
|||
}
|
||||
|
||||
if lp.SoC.Target == 0 {
|
||||
lp.SoC.Target = lp.OnDisconnect.TargetSoC // use disconnect value as default soc
|
||||
lp.SoC.Target = lp.onDisconnect.TargetSoC // use disconnect value as default soc
|
||||
if lp.SoC.Target == 0 {
|
||||
lp.SoC.Target = 100
|
||||
}
|
||||
|
|
@ -181,6 +186,9 @@ func NewLoadPointFromConfig(log *util.Logger, cp configProvider, other map[strin
|
|||
lp.log.WARN.Println("maxCurrent must be larger than minCurrent")
|
||||
}
|
||||
|
||||
// store defaults
|
||||
lp.collectDefaults()
|
||||
|
||||
if lp.Meters.ChargeMeterRef != "" {
|
||||
lp.chargeMeter = cp.Meter(lp.Meters.ChargeMeterRef)
|
||||
}
|
||||
|
|
@ -242,6 +250,17 @@ func NewLoadPoint(log *util.Logger) *LoadPoint {
|
|||
return lp
|
||||
}
|
||||
|
||||
// collectDefaults collects default values for use on disconnect
|
||||
func (lp *LoadPoint) collectDefaults() {
|
||||
lp.onDisconnect = api.ActionConfig{
|
||||
Mode: lp.GetMode(),
|
||||
MinCurrent: lp.GetMinCurrent(),
|
||||
MaxCurrent: lp.GetMaxCurrent(),
|
||||
MinSoC: lp.GetMinSoC(),
|
||||
TargetSoC: lp.GetTargetSoC(),
|
||||
}
|
||||
}
|
||||
|
||||
// requestUpdate requests site to update this loadpoint
|
||||
func (lp *LoadPoint) requestUpdate() {
|
||||
select {
|
||||
|
|
@ -367,7 +386,9 @@ func (lp *LoadPoint) evVehicleDisconnectHandler() {
|
|||
}
|
||||
|
||||
// set default mode on disconnect
|
||||
lp.applyAction(lp.OnDisconnect)
|
||||
if lp.ResetOnDisconnect {
|
||||
lp.applyAction(lp.onDisconnect)
|
||||
}
|
||||
|
||||
// soc update reset
|
||||
lp.socUpdated = time.Time{}
|
||||
|
|
@ -399,12 +420,29 @@ func (lp *LoadPoint) evChargeCurrentWrappedMeterHandler(current float64) {
|
|||
}
|
||||
|
||||
// applyAction executes the action
|
||||
func (lp *LoadPoint) applyAction(action ActionConfig) {
|
||||
if action.Mode != "" && lp.GetMode() != api.ModeEmpty {
|
||||
func (lp *LoadPoint) applyAction(action api.ActionConfig) {
|
||||
if action.Mode != api.ModeEmpty {
|
||||
lp.SetMode(action.Mode)
|
||||
}
|
||||
if action.MinCurrent > 0 {
|
||||
lp.SetMinCurrent(action.MinCurrent)
|
||||
}
|
||||
if action.MaxCurrent > 0 {
|
||||
lp.SetMaxCurrent(action.MaxCurrent)
|
||||
}
|
||||
if action.MinSoC != 0 {
|
||||
// TODO deduplicate with SetMinSoC
|
||||
lp.Lock()
|
||||
lp.SoC.Min = action.MinSoC
|
||||
lp.publish("minSoC", action.MinSoC)
|
||||
lp.Unlock()
|
||||
}
|
||||
if action.TargetSoC != 0 {
|
||||
_ = lp.SetTargetSoC(action.TargetSoC)
|
||||
// TODO deduplicate with SetTargetSoC
|
||||
lp.Lock()
|
||||
lp.SoC.Target = action.TargetSoC
|
||||
lp.publish("targetSoC", action.TargetSoC)
|
||||
lp.Unlock()
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -647,11 +685,6 @@ func (lp *LoadPoint) identifyVehicle() {
|
|||
if vehicle := lp.selectVehicleByID(id); vehicle != nil {
|
||||
lp.setActiveVehicle(vehicle)
|
||||
}
|
||||
|
||||
if action, ok := lp.OnIdentify[id]; ok {
|
||||
lp.log.DEBUG.Println("running vehicle action:", action)
|
||||
lp.applyAction(action)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -659,14 +692,14 @@ func (lp *LoadPoint) identifyVehicle() {
|
|||
func (lp *LoadPoint) selectVehicleByID(id string) api.Vehicle {
|
||||
// find exact match
|
||||
for _, vehicle := range lp.vehicles {
|
||||
if vid, err := vehicle.Identify(); err == nil && vid == id {
|
||||
if funk.ContainsString(vehicle.Identifiers(), id) {
|
||||
return vehicle
|
||||
}
|
||||
}
|
||||
|
||||
// find placeholder match
|
||||
for _, vehicle := range lp.vehicles {
|
||||
if vid, err := vehicle.Identify(); err == nil && vid != "" {
|
||||
for _, vid := range vehicle.Identifiers() {
|
||||
re, err := regexp.Compile(strings.ReplaceAll(vid, "*", ".*?"))
|
||||
if err != nil {
|
||||
lp.log.ERROR.Printf("vehicle id: %v", err)
|
||||
|
|
@ -706,6 +739,8 @@ func (lp *LoadPoint) setActiveVehicle(vehicle api.Vehicle) {
|
|||
lp.publish("vehiclePresent", true)
|
||||
lp.publish("vehicleTitle", lp.vehicle.Title())
|
||||
lp.publish("vehicleCapacity", lp.vehicle.Capacity())
|
||||
|
||||
lp.applyAction(vehicle.OnIdentified())
|
||||
} else {
|
||||
lp.socEstimator = nil
|
||||
|
||||
|
|
@ -726,7 +761,8 @@ func (lp *LoadPoint) startVehicleDetection() {
|
|||
|
||||
// vehicleUnidentified checks if loadpoint has multiple vehicles associated and starts discovery period
|
||||
func (lp *LoadPoint) vehicleUnidentified() bool {
|
||||
res := len(lp.vehicles) > 1 && lp.clock.Since(lp.vehicleConnected) < vehicleDetectDuration
|
||||
res := len(lp.vehicles) > 1 && lp.vehicle == nil &&
|
||||
lp.clock.Since(lp.vehicleConnected) < vehicleDetectDuration
|
||||
|
||||
// request vehicle api refresh while waiting to identify
|
||||
if res {
|
||||
|
|
|
|||
|
|
@ -57,13 +57,13 @@ func (lp *LoadPoint) GetTargetSoC() int {
|
|||
|
||||
// SetTargetSoC sets loadpoint charge target soc
|
||||
func (lp *LoadPoint) SetTargetSoC(soc int) error {
|
||||
lp.Lock()
|
||||
defer lp.Unlock()
|
||||
|
||||
if lp.vehicle == nil {
|
||||
return api.ErrNotAvailable
|
||||
}
|
||||
|
||||
lp.Lock()
|
||||
defer lp.Unlock()
|
||||
|
||||
lp.log.INFO.Println("set target soc:", soc)
|
||||
|
||||
// apply immediately
|
||||
|
|
@ -85,13 +85,13 @@ func (lp *LoadPoint) GetMinSoC() int {
|
|||
|
||||
// SetMinSoC sets loadpoint charge minimum soc
|
||||
func (lp *LoadPoint) SetMinSoC(soc int) error {
|
||||
lp.Lock()
|
||||
defer lp.Unlock()
|
||||
|
||||
if lp.vehicle == nil {
|
||||
return api.ErrNotAvailable
|
||||
}
|
||||
|
||||
lp.Lock()
|
||||
defer lp.Unlock()
|
||||
|
||||
lp.log.INFO.Println("set min soc:", soc)
|
||||
|
||||
// apply immediately
|
||||
|
|
@ -108,7 +108,6 @@ func (lp *LoadPoint) SetMinSoC(soc int) error {
|
|||
func (lp *LoadPoint) GetPhases() int {
|
||||
lp.Lock()
|
||||
defer lp.Unlock()
|
||||
|
||||
return lp.Phases
|
||||
}
|
||||
|
||||
|
|
@ -178,6 +177,8 @@ func (lp *LoadPoint) SetMinCurrent(current float64) {
|
|||
lp.Lock()
|
||||
defer lp.Unlock()
|
||||
|
||||
lp.log.INFO.Println("set min current:", current)
|
||||
|
||||
if current != lp.MinCurrent {
|
||||
lp.MinCurrent = current
|
||||
lp.publish("minCurrent", lp.MinCurrent)
|
||||
|
|
@ -196,6 +197,8 @@ func (lp *LoadPoint) SetMaxCurrent(current float64) {
|
|||
lp.Lock()
|
||||
defer lp.Unlock()
|
||||
|
||||
lp.log.INFO.Println("set max current:", current)
|
||||
|
||||
if current != lp.MaxCurrent {
|
||||
lp.MaxCurrent = current
|
||||
lp.publish("maxCurrent", lp.MaxCurrent)
|
||||
|
|
|
|||
|
|
@ -456,16 +456,15 @@ func TestSetModeAndSocAtDisconnect(t *testing.T) {
|
|||
MinCurrent: minA,
|
||||
MaxCurrent: maxA,
|
||||
status: api.StatusC,
|
||||
OnDisconnect: struct {
|
||||
Mode api.ChargeMode `mapstructure:"mode"` // Charge mode to apply when car disconnected
|
||||
TargetSoC int `mapstructure:"targetSoC"` // Target SoC to apply when car disconnected
|
||||
}{
|
||||
Mode: api.ModeOff,
|
||||
TargetSoC: 70,
|
||||
Mode: api.ModeOff,
|
||||
SoC: SoCConfig{
|
||||
Target: 70,
|
||||
},
|
||||
ResetOnDisconnect: true,
|
||||
}
|
||||
|
||||
attachListeners(t, lp)
|
||||
lp.collectDefaults()
|
||||
|
||||
lp.enabled = true
|
||||
lp.chargeCurrent = float64(minA)
|
||||
|
|
@ -764,35 +763,35 @@ func TestVehicleDetectByID(t *testing.T) {
|
|||
}
|
||||
tc := []testcase{
|
||||
{"1/_/_->0", "1", "", "", nil, func(tc testcase) {
|
||||
v1.EXPECT().Identify().Return(tc.i1, nil)
|
||||
v2.EXPECT().Identify().Return(tc.i2, nil)
|
||||
v1.EXPECT().Identify().Return(tc.i1, nil)
|
||||
v2.EXPECT().Identify().Return(tc.i2, nil)
|
||||
v1.EXPECT().Identifiers().Return(nil)
|
||||
v2.EXPECT().Identifiers().Return(nil)
|
||||
v1.EXPECT().Identifiers().Return(nil)
|
||||
v2.EXPECT().Identifiers().Return(nil)
|
||||
}},
|
||||
{"1/1/2->1", "1", "1", "2", v1, func(tc testcase) {
|
||||
v1.EXPECT().Identify().Return(tc.i1, nil)
|
||||
v1.EXPECT().Identifiers().Return([]string{tc.i1})
|
||||
}},
|
||||
{"2/1/2->2", "2", "1", "2", v2, func(tc testcase) {
|
||||
v1.EXPECT().Identify().Return(tc.i1, nil)
|
||||
v2.EXPECT().Identify().Return(tc.i2, nil)
|
||||
v1.EXPECT().Identifiers().Return([]string{tc.i1})
|
||||
v2.EXPECT().Identifiers().Return([]string{tc.i2})
|
||||
}},
|
||||
{"11/1*/2->1", "11", "1*", "2", v1, func(tc testcase) {
|
||||
v1.EXPECT().Identify().Return(tc.i1, nil)
|
||||
v2.EXPECT().Identify().Return(tc.i2, nil)
|
||||
v1.EXPECT().Identify().Return(tc.i1, nil)
|
||||
// v2.EXPECT().Identify().Return(tc.i2, nil)
|
||||
v1.EXPECT().Identifiers().Return([]string{tc.i1})
|
||||
v2.EXPECT().Identifiers().Return([]string{tc.i2})
|
||||
v1.EXPECT().Identifiers().Return([]string{tc.i1})
|
||||
// v2.EXPECT().Identifiers().Return([]string{tc.i2})
|
||||
}},
|
||||
{"22/1*/2*->2", "22", "1*", "2*", v2, func(tc testcase) {
|
||||
v1.EXPECT().Identify().Return(tc.i1, nil)
|
||||
v2.EXPECT().Identify().Return(tc.i2, nil)
|
||||
v1.EXPECT().Identify().Return(tc.i1, nil)
|
||||
v2.EXPECT().Identify().Return(tc.i2, nil)
|
||||
v1.EXPECT().Identifiers().Return([]string{tc.i1})
|
||||
v2.EXPECT().Identifiers().Return([]string{tc.i2})
|
||||
v1.EXPECT().Identifiers().Return([]string{tc.i1})
|
||||
v2.EXPECT().Identifiers().Return([]string{tc.i2})
|
||||
}},
|
||||
{"2/_/*->2", "2", "", "*", v2, func(tc testcase) {
|
||||
v1.EXPECT().Identify().Return(tc.i1, nil)
|
||||
v2.EXPECT().Identify().Return(tc.i2, nil)
|
||||
v1.EXPECT().Identify().Return(tc.i1, nil)
|
||||
v2.EXPECT().Identify().Return(tc.i2, nil)
|
||||
v1.EXPECT().Identifiers().Return(nil)
|
||||
v2.EXPECT().Identifiers().Return([]string{tc.i2})
|
||||
v1.EXPECT().Identifiers().Return(nil)
|
||||
v2.EXPECT().Identifiers().Return([]string{tc.i2})
|
||||
}},
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -44,14 +44,16 @@ chargers:
|
|||
# name can be freely chosen and is used as reference when assigning vehicle to loadpoint
|
||||
# for examples see https://github.com/evcc-io/config#vehicles
|
||||
vehicles:
|
||||
- name: renault
|
||||
- name: car1
|
||||
type: renault
|
||||
title: Zoe
|
||||
capacity: 60 # kWh
|
||||
user: # user
|
||||
password: # password
|
||||
vin: WREN...
|
||||
cache: 5m
|
||||
onIdentify: # set defaults when vehicle is identified
|
||||
minSoC: 20 # charge to at least 20% independent of charge mode
|
||||
targetSoC: 90 # limit charge to 90%
|
||||
|
||||
# site describes the EVU connection, PV and home battery
|
||||
site:
|
||||
|
|
@ -75,6 +77,7 @@ loadpoints:
|
|||
# - ID.3
|
||||
# - e-Up
|
||||
mode: pv
|
||||
resetOnDisconnect: true # set defaults when vehicle disconnects
|
||||
soc:
|
||||
# polling defines usage of the vehicle APIs
|
||||
# Modifying the default settings it NOT recommended. It MAY deplete your vehicle's battery
|
||||
|
|
@ -90,13 +93,6 @@ loadpoints:
|
|||
min: 0 # immediately charge to 0% regardless of mode unless "off" (disabled)
|
||||
target: 100 # always charge to 100%
|
||||
estimate: false # set true to interpolate between api updates
|
||||
onDisconnect: # set defaults when vehicle disconnects
|
||||
mode: pv # switch back to pv mode
|
||||
targetSoC: 100 # charge to 100%
|
||||
onIdentify: # set defaults when vehicle is identified
|
||||
- e-Up:
|
||||
mode: pv # switch back to pv mode
|
||||
targetSoC: 100 # charge to 100%
|
||||
phases: 3 # ev phases (default 3)
|
||||
enable: # pv mode enable behavior
|
||||
delay: 1m # threshold must be exceeded for this long
|
||||
|
|
|
|||
|
|
@ -318,19 +318,32 @@ func (mr *MockVehicleMockRecorder) Capacity() *gomock.Call {
|
|||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Capacity", reflect.TypeOf((*MockVehicle)(nil).Capacity))
|
||||
}
|
||||
|
||||
// Identify mocks base method.
|
||||
func (m *MockVehicle) Identify() (string, error) {
|
||||
// Identifiers mocks base method.
|
||||
func (m *MockVehicle) Identifiers() []string {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "Identify")
|
||||
ret0, _ := ret[0].(string)
|
||||
ret1, _ := ret[1].(error)
|
||||
return ret0, ret1
|
||||
ret := m.ctrl.Call(m, "Identifiers")
|
||||
ret0, _ := ret[0].([]string)
|
||||
return ret0
|
||||
}
|
||||
|
||||
// Identify indicates an expected call of Identify.
|
||||
func (mr *MockVehicleMockRecorder) Identify() *gomock.Call {
|
||||
// Identifiers indicates an expected call of Identifiers.
|
||||
func (mr *MockVehicleMockRecorder) Identifiers() *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Identify", reflect.TypeOf((*MockVehicle)(nil).Identify))
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Identifiers", reflect.TypeOf((*MockVehicle)(nil).Identifiers))
|
||||
}
|
||||
|
||||
// OnIdentified mocks base method.
|
||||
func (m *MockVehicle) OnIdentified() api.ActionConfig {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "OnIdentified")
|
||||
ret0, _ := ret[0].(api.ActionConfig)
|
||||
return ret0
|
||||
}
|
||||
|
||||
// OnIdentified indicates an expected call of OnIdentified.
|
||||
func (mr *MockVehicleMockRecorder) OnIdentified() *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "OnIdentified", reflect.TypeOf((*MockVehicle)(nil).OnIdentified))
|
||||
}
|
||||
|
||||
// SoC mocks base method.
|
||||
|
|
|
|||
30
vehicle/embed.go
Normal file
30
vehicle/embed.go
Normal file
|
|
@ -0,0 +1,30 @@
|
|||
package vehicle
|
||||
|
||||
import "github.com/evcc-io/evcc/api"
|
||||
|
||||
type embed struct {
|
||||
Title_ string `mapstructure:"title"`
|
||||
Capacity_ int64 `mapstructure:"capacity"`
|
||||
Identifiers_ []string `mapstructure:"identifiers"`
|
||||
OnIdentify api.ActionConfig `mapstructure:"onIdentify"`
|
||||
}
|
||||
|
||||
// Title implements the api.Vehicle interface
|
||||
func (v *embed) Title() string {
|
||||
return v.Title_
|
||||
}
|
||||
|
||||
// Capacity implements the api.Vehicle interface
|
||||
func (v *embed) Capacity() int64 {
|
||||
return v.Capacity_
|
||||
}
|
||||
|
||||
// Identifiers implements the api.Identifier interface
|
||||
func (v *embed) Identifiers() []string {
|
||||
return v.Identifiers_
|
||||
}
|
||||
|
||||
// OnIdentified returns the identify action
|
||||
func (v *embed) OnIdentified() api.ActionConfig {
|
||||
return v.OnIdentify
|
||||
}
|
||||
|
|
@ -9,27 +9,6 @@ import (
|
|||
"github.com/evcc-io/evcc/util"
|
||||
)
|
||||
|
||||
type embed struct {
|
||||
Title_ string `mapstructure:"title"`
|
||||
Capacity_ int64 `mapstructure:"capacity"`
|
||||
Identifier_ string `mapstructure:"identifier"`
|
||||
}
|
||||
|
||||
// Title implements the api.Vehicle interface
|
||||
func (v *embed) Title() string {
|
||||
return v.Title_
|
||||
}
|
||||
|
||||
// Capacity implements the api.Vehicle interface
|
||||
func (v *embed) Capacity() int64 {
|
||||
return v.Capacity_
|
||||
}
|
||||
|
||||
// Identify implements the api.Identifier interface
|
||||
func (v *embed) Identify() (string, error) {
|
||||
return v.Identifier_, nil
|
||||
}
|
||||
|
||||
//go:generate go run ../cmd/tools/decorate.go -f decorateVehicle -b api.Vehicle -t "api.ChargeState,Status,func() (api.ChargeStatus, error)" -t "api.VehicleRange,Range,func() (int64, error)" -t "api.VehicleOdometer,Odometer,func() (float64, error)"
|
||||
|
||||
// Vehicle is an api.Vehicle implementation with configurable getters and setters.
|
||||
|
|
|
|||
|
|
@ -30,11 +30,18 @@ func (v *Wrapper) Capacity() int64 {
|
|||
return 0
|
||||
}
|
||||
|
||||
// Identify implements the api.Identifier interface
|
||||
func (v *Wrapper) Identify() (string, error) {
|
||||
return "", v.err
|
||||
// Identifiers implements the api.Vehicle interface
|
||||
func (v *Wrapper) Identifiers() []string {
|
||||
return nil
|
||||
}
|
||||
|
||||
// OnIdentified returns the identify action
|
||||
func (v *Wrapper) OnIdentified() api.ActionConfig {
|
||||
return api.ActionConfig{}
|
||||
}
|
||||
|
||||
var _ api.Battery = (*Wrapper)(nil)
|
||||
|
||||
// SoC implements the api.Battery interface
|
||||
func (v *Wrapper) SoC() (float64, error) {
|
||||
return 0, v.err
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue