From fb20a3b5858ce03895264d4d3d51eadab2989df0 Mon Sep 17 00:00:00 2001 From: andig Date: Mon, 10 Jul 2023 22:05:37 +0200 Subject: [PATCH] Fix taking vehicle priorities into account (#8884) --- core/loadpoint.go | 6 +++- core/loadpoint/api.go | 6 +++- core/loadpoint/mock.go | 40 +++++++++++++++-------- core/loadpoint_api.go | 25 +++++++++++--- core/prioritizer/prioritizer.go | 22 ++++++++++--- core/prioritizer/prioritizer_test.go | 8 +++-- core/site.go | 2 +- templates/definition/vehicle/offline.yaml | 28 +--------------- templates/docs/vehicle/offline_0.yaml | 4 --- 9 files changed, 81 insertions(+), 60 deletions(-) diff --git a/core/loadpoint.go b/core/loadpoint.go index 145f8a3af2..b9ee3080be 100644 --- a/core/loadpoint.go +++ b/core/loadpoint.go @@ -315,7 +315,7 @@ func (lp *Loadpoint) collectDefaults() { *actionCfg.MaxCurrent = lp.GetMaxCurrent() *actionCfg.MinSoc = lp.GetMinSoc() *actionCfg.TargetSoc = lp.GetTargetSoc() - *actionCfg.Priority = lp.Priority() + *actionCfg.Priority = lp.GetPriority() } else { lp.log.ERROR.Printf("error allocating action config: %v", err) } @@ -557,6 +557,9 @@ func (lp *Loadpoint) applyAction(actionCfg api.ActionConfig) { if actionCfg.TargetSoc != nil { lp.SetTargetSoc(*actionCfg.TargetSoc) } + if actionCfg.Priority != nil { + lp.SetPriority(*actionCfg.Priority) + } } // Prepare loadpoint configuration by adding missing helper elements @@ -603,6 +606,7 @@ func (lp *Loadpoint) Prepare(uiChan chan<- util.Param, pushChan chan<- push.Even } lp.publish("mode", lp.GetMode()) + lp.publish("priority", lp.GetPriority()) lp.publish(targetSoc, lp.GetTargetSoc()) lp.publish(minSoc, lp.GetMinSoc()) diff --git a/core/loadpoint/api.go b/core/loadpoint/api.go index 1e2a33b90f..226f505bb7 100644 --- a/core/loadpoint/api.go +++ b/core/loadpoint/api.go @@ -17,7 +17,6 @@ type Controller interface { type API interface { // Title returns the defined loadpoint title Title() string - Priority() int // // status @@ -30,6 +29,11 @@ type API interface { // settings // + // GetPriority returns the loadpoint priority + GetPriority() int + // SetPriority sets the loadpoint priority + SetPriority(int) + // GetMode returns the charge mode GetMode() api.ChargeMode // SetMode sets the charge mode diff --git a/core/loadpoint/mock.go b/core/loadpoint/mock.go index 5bc568705b..a2c2addb20 100644 --- a/core/loadpoint/mock.go +++ b/core/loadpoint/mock.go @@ -205,6 +205,20 @@ func (mr *MockAPIMockRecorder) GetPlan(arg0, arg1 interface{}) *gomock.Call { return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetPlan", reflect.TypeOf((*MockAPI)(nil).GetPlan), arg0, arg1) } +// GetPriority mocks base method. +func (m *MockAPI) GetPriority() int { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetPriority") + ret0, _ := ret[0].(int) + return ret0 +} + +// GetPriority indicates an expected call of GetPriority. +func (mr *MockAPIMockRecorder) GetPriority() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetPriority", reflect.TypeOf((*MockAPI)(nil).GetPriority)) +} + // GetRemainingDuration mocks base method. func (m *MockAPI) GetRemainingDuration() time.Duration { m.ctrl.T.Helper() @@ -317,20 +331,6 @@ func (mr *MockAPIMockRecorder) HasChargeMeter() *gomock.Call { return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "HasChargeMeter", reflect.TypeOf((*MockAPI)(nil).HasChargeMeter)) } -// Priority mocks base method. -func (m *MockAPI) Priority() int { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "Priority") - ret0, _ := ret[0].(int) - return ret0 -} - -// Priority indicates an expected call of Priority. -func (mr *MockAPIMockRecorder) Priority() *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Priority", reflect.TypeOf((*MockAPI)(nil).Priority)) -} - // RemoteControl mocks base method. func (m *MockAPI) RemoteControl(arg0 string, arg1 RemoteDemand) { m.ctrl.T.Helper() @@ -429,6 +429,18 @@ func (mr *MockAPIMockRecorder) SetPhases(arg0 interface{}) *gomock.Call { return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetPhases", reflect.TypeOf((*MockAPI)(nil).SetPhases), arg0) } +// SetPriority mocks base method. +func (m *MockAPI) SetPriority(arg0 int) { + m.ctrl.T.Helper() + m.ctrl.Call(m, "SetPriority", arg0) +} + +// SetPriority indicates an expected call of SetPriority. +func (mr *MockAPIMockRecorder) SetPriority(arg0 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetPriority", reflect.TypeOf((*MockAPI)(nil).SetPriority), arg0) +} + // SetTargetEnergy mocks base method. func (m *MockAPI) SetTargetEnergy(arg0 float64) { m.ctrl.T.Helper() diff --git a/core/loadpoint_api.go b/core/loadpoint_api.go index d313b5305b..075522648f 100644 --- a/core/loadpoint_api.go +++ b/core/loadpoint_api.go @@ -18,11 +18,6 @@ func (lp *Loadpoint) Title() string { return lp.Title_ } -// Priority returns the loadpoint priority -func (lp *Loadpoint) Priority() int { - return lp.Priority_ -} - // GetStatus returns the charging status func (lp *Loadpoint) GetStatus() api.ChargeStatus { lp.Lock() @@ -102,6 +97,26 @@ func (lp *Loadpoint) SetTargetEnergy(energy float64) { } } +// GetPriority returns the loadpoint priority +func (lp *Loadpoint) GetPriority() int { + lp.Lock() + defer lp.Unlock() + return lp.Priority_ +} + +// SetPriority sets the loadpoint priority +func (lp *Loadpoint) SetPriority(prio int) { + lp.Lock() + defer lp.Unlock() + + lp.log.DEBUG.Println("set priority:", prio) + + if lp.Priority_ != prio { + lp.Priority_ = prio + lp.publish("priority", prio) + } +} + // GetTargetSoc returns loadpoint charge target soc func (lp *Loadpoint) GetTargetSoc() int { lp.Lock() diff --git a/core/prioritizer/prioritizer.go b/core/prioritizer/prioritizer.go index 334dac202b..346bcfe735 100644 --- a/core/prioritizer/prioritizer.go +++ b/core/prioritizer/prioritizer.go @@ -1,15 +1,20 @@ package prioritizer import ( + "fmt" + "github.com/evcc-io/evcc/core/loadpoint" + "github.com/evcc-io/evcc/util" ) type Prioritizer struct { + log *util.Logger demand map[loadpoint.API]float64 } -func New() *Prioritizer { +func New(log *util.Logger) *Prioritizer { return &Prioritizer{ + log: log, demand: make(map[loadpoint.API]float64), } } @@ -21,14 +26,23 @@ func (p *Prioritizer) UpdateChargePowerFlexibility(lp loadpoint.API) { } func (p *Prioritizer) GetChargePowerFlexibility(lp loadpoint.API) float64 { - prio := lp.Priority() + prio := lp.GetPriority() + + var ( + reduceBy float64 + msg string + ) - var reduceBy float64 for lp, power := range p.demand { - if lp.Priority() < prio { + if lp.GetPriority() < prio { reduceBy += power + msg += fmt.Sprintf("%.0fW from %s at prio %d, ", power, lp.Title(), lp.GetPriority()) } } + if p.log != nil && reduceBy > 0 { + p.log.DEBUG.Printf("lp %s at prio %d gets additional %stotal %.0fW\n", lp.Title(), lp.GetPriority(), msg, reduceBy) + } + return reduceBy } diff --git a/core/prioritizer/prioritizer_test.go b/core/prioritizer/prioritizer_test.go index f3d322c6ad..cb53371880 100644 --- a/core/prioritizer/prioritizer_test.go +++ b/core/prioritizer/prioritizer_test.go @@ -11,13 +11,15 @@ import ( func TestPrioritzer(t *testing.T) { ctrl := gomock.NewController(t) - p := New() + p := New(nil) lo := loadpoint.NewMockAPI(ctrl) - lo.EXPECT().Priority().Return(0).AnyTimes() + lo.EXPECT().Title().AnyTimes() + lo.EXPECT().GetPriority().Return(0).AnyTimes() hi := loadpoint.NewMockAPI(ctrl) - hi.EXPECT().Priority().Return(1).AnyTimes() + hi.EXPECT().Title().AnyTimes() + hi.EXPECT().GetPriority().Return(1).AnyTimes() // no additional power available lo.EXPECT().GetChargePowerFlexibility().Return(300.0) diff --git a/core/site.go b/core/site.go index dd76a415f6..390d82b1f7 100644 --- a/core/site.go +++ b/core/site.go @@ -113,7 +113,7 @@ func NewSiteFromConfig( site.loadpoints = loadpoints site.tariffs = tariffs site.coordinator = coordinator.New(log, vehicles) - site.prioritizer = prioritizer.New() + site.prioritizer = prioritizer.New(log) site.savings = NewSavings(tariffs) site.restoreSettings() diff --git a/templates/definition/vehicle/offline.yaml b/templates/definition/vehicle/offline.yaml index 31ae4e9a52..9c7d9f1714 100644 --- a/templates/definition/vehicle/offline.yaml +++ b/templates/definition/vehicle/offline.yaml @@ -11,15 +11,6 @@ params: - name: capacity - name: phases advanced: true - - name: mode - advanced: true - - name: minCurrent - advanced: true - - name: maxCurrent - advanced: true - - name: identifiers - advanced: true - type: stringlist - preset: vehicle-identify render: | type: custom @@ -37,21 +28,4 @@ render: | soc: source: const value: 0 - onIdentify: - {{- if .mode }} - mode: {{ .mode }} - {{- end }} - minSoc: 0 # fixed - no soc available - targetSoc: 100 # fixed - no soc available - {{- if .minCurrent }} - minCurrent: {{ .minCurrent }} - {{- end }} - {{- if .maxCurrent }} - maxCurrent: {{ .maxCurrent }} - {{- end }} - {{- if (len .identifiers) }} - identifiers: - {{- range .identifiers }} - - {{ . }} - {{- end }} - {{- end }} + {{ include "vehicle-identify" . }} diff --git a/templates/docs/vehicle/offline_0.yaml b/templates/docs/vehicle/offline_0.yaml index d97bed5324..407e748fe4 100644 --- a/templates/docs/vehicle/offline_0.yaml +++ b/templates/docs/vehicle/offline_0.yaml @@ -15,10 +15,6 @@ render: capacity: 50 # Akkukapazität in kWh (Optional) phases: 3 # Die maximale Anzahl der Phasen welche genutzt werden können (Optional) mode: # Möglich sind Off, Now, MinPV und PV, oder leer wenn keiner definiert werden soll (Optional) - minCurrent: 6 # Definiert die minimale Stromstärke pro angeschlossener Phase mit welcher das Fahrzeug geladen werden soll (Optional) - maxCurrent: 16 # Definiert die maximale Stromstärke pro angeschlossener Phase mit welcher das Fahrzeug geladen werden soll (Optional) - identifiers: # Kann meist erst später eingetragen werden, siehe: https://docs.evcc.io/docs/guides/vehicles/#erkennung-des-fahrzeugs-an-der-wallbox (Optional) - mode: # Möglich sind Off, Now, MinPV und PV, oder leer wenn keiner definiert werden soll (Optional) minSoc: 25 # Ladung mit maximaler Geschwindigkeit bis zu dem angegeben Ladestand unabhängig PV-Erzeugung, wenn der Lademodus nicht auf 'Aus' steht (Optional) targetSoc: 80 # Bis zu welchem Ladestand (Soc) soll das Fahrzeug geladen werden (Optional) minCurrent: 6 # Definiert die minimale Stromstärke pro angeschlossener Phase mit welcher das Fahrzeug geladen werden soll (Optional)