Add bufferSoc (#1469)
This commit is contained in:
parent
39c805eb92
commit
6c97801523
5 changed files with 38 additions and 34 deletions
|
|
@ -953,7 +953,7 @@ func (lp *LoadPoint) pvScalePhases(availablePower, minCurrent, maxCurrent float6
|
|||
}
|
||||
|
||||
// pvMaxCurrent calculates the maximum target current for PV mode
|
||||
func (lp *LoadPoint) pvMaxCurrent(mode api.ChargeMode, sitePower float64) float64 {
|
||||
func (lp *LoadPoint) pvMaxCurrent(mode api.ChargeMode, sitePower float64, batteryBuffered bool) float64 {
|
||||
// read only once to simplify testing
|
||||
minCurrent := lp.GetMinCurrent()
|
||||
maxCurrent := lp.GetMaxCurrent()
|
||||
|
|
@ -976,7 +976,7 @@ func (lp *LoadPoint) pvMaxCurrent(mode api.ChargeMode, sitePower float64) float6
|
|||
}
|
||||
|
||||
// in MinPV mode return at least minCurrent
|
||||
if mode == api.ModeMinPV && targetCurrent < minCurrent {
|
||||
if (mode == api.ModeMinPV || batteryBuffered) && targetCurrent < minCurrent {
|
||||
return minCurrent
|
||||
}
|
||||
|
||||
|
|
@ -1206,7 +1206,7 @@ func (lp *LoadPoint) publishSoCAndRange() {
|
|||
}
|
||||
|
||||
// Update is the main control function. It reevaluates meters and charger state
|
||||
func (lp *LoadPoint) Update(sitePower float64, cheap bool) {
|
||||
func (lp *LoadPoint) Update(sitePower float64, cheap bool, batteryBuffered bool) {
|
||||
mode := lp.GetMode()
|
||||
lp.publish("mode", mode)
|
||||
|
||||
|
|
@ -1302,7 +1302,7 @@ func (lp *LoadPoint) Update(sitePower float64, cheap bool) {
|
|||
err = lp.setLimit(targetCurrent, true)
|
||||
|
||||
case mode == api.ModeMinPV || mode == api.ModePV:
|
||||
targetCurrent := lp.pvMaxCurrent(mode, sitePower)
|
||||
targetCurrent := lp.pvMaxCurrent(mode, sitePower, batteryBuffered)
|
||||
lp.log.DEBUG.Printf("pv max charge current: %.3gA", targetCurrent)
|
||||
|
||||
var required bool // false
|
||||
|
|
|
|||
|
|
@ -168,7 +168,7 @@ func TestUpdatePowerZero(t *testing.T) {
|
|||
}
|
||||
|
||||
lp.Mode = tc.mode
|
||||
lp.Update(0, false) // sitePower 0
|
||||
lp.Update(0, false, false) // sitePower 0
|
||||
|
||||
ctrl.Finish()
|
||||
}
|
||||
|
|
@ -324,7 +324,7 @@ func TestPVHysteresis(t *testing.T) {
|
|||
// charger.EXPECT().Enabled().Return(tc.enabled, nil)
|
||||
|
||||
lp.enabled = tc.enabled
|
||||
current := lp.pvMaxCurrent(api.ModePV, se.site)
|
||||
current := lp.pvMaxCurrent(api.ModePV, se.site, false)
|
||||
|
||||
if current != se.current {
|
||||
t.Errorf("step %d: wanted %.1f, got %.1f", step, se.current, current)
|
||||
|
|
@ -355,7 +355,7 @@ func TestPVHysteresisForStatusOtherThanC(t *testing.T) {
|
|||
|
||||
// maxCurrent will read enabled state in PV mode
|
||||
sitePower := -float64(lp.Phases)*minA*Voltage + 1 // 1W below min power
|
||||
current := lp.pvMaxCurrent(api.ModePV, sitePower)
|
||||
current := lp.pvMaxCurrent(api.ModePV, sitePower, false)
|
||||
|
||||
if current != 0 {
|
||||
t.Errorf("PV mode could not disable charger as expected. Expected 0, got %.f", current)
|
||||
|
|
@ -406,7 +406,7 @@ func TestDisableAndEnableAtTargetSoC(t *testing.T) {
|
|||
charger.EXPECT().Status().Return(api.StatusC, nil)
|
||||
charger.EXPECT().Enabled().Return(lp.enabled, nil)
|
||||
charger.EXPECT().MaxCurrent(int64(maxA)).Return(nil)
|
||||
lp.Update(500, false)
|
||||
lp.Update(500, false, false)
|
||||
|
||||
t.Log("charging above target - soc deactivates charger")
|
||||
clock.Add(5 * time.Minute)
|
||||
|
|
@ -414,20 +414,20 @@ func TestDisableAndEnableAtTargetSoC(t *testing.T) {
|
|||
charger.EXPECT().Status().Return(api.StatusC, nil)
|
||||
charger.EXPECT().Enabled().Return(lp.enabled, nil)
|
||||
charger.EXPECT().Enable(false).Return(nil)
|
||||
lp.Update(500, false)
|
||||
lp.Update(500, false, false)
|
||||
|
||||
t.Log("deactivated charger changes status to B")
|
||||
clock.Add(5 * time.Minute)
|
||||
vehicle.EXPECT().SoC().Return(95.0, nil)
|
||||
charger.EXPECT().Status().Return(api.StatusB, nil)
|
||||
charger.EXPECT().Enabled().Return(lp.enabled, nil)
|
||||
lp.Update(-5000, false)
|
||||
lp.Update(-5000, false, false)
|
||||
|
||||
t.Log("soc has fallen below target - soc update prevented by timer")
|
||||
clock.Add(5 * time.Minute)
|
||||
charger.EXPECT().Status().Return(api.StatusB, nil)
|
||||
charger.EXPECT().Enabled().Return(lp.enabled, nil)
|
||||
lp.Update(-5000, false)
|
||||
lp.Update(-5000, false, false)
|
||||
|
||||
t.Log("soc has fallen below target - soc update timer expired")
|
||||
clock.Add(pollInterval)
|
||||
|
|
@ -435,7 +435,7 @@ func TestDisableAndEnableAtTargetSoC(t *testing.T) {
|
|||
charger.EXPECT().Status().Return(api.StatusB, nil)
|
||||
charger.EXPECT().Enabled().Return(lp.enabled, nil)
|
||||
charger.EXPECT().Enable(true).Return(nil)
|
||||
lp.Update(-5000, false)
|
||||
lp.Update(-5000, false, false)
|
||||
|
||||
ctrl.Finish()
|
||||
}
|
||||
|
|
@ -475,14 +475,14 @@ func TestSetModeAndSocAtDisconnect(t *testing.T) {
|
|||
charger.EXPECT().Enabled().Return(lp.enabled, nil)
|
||||
charger.EXPECT().Status().Return(api.StatusC, nil)
|
||||
charger.EXPECT().MaxCurrent(int64(maxA)).Return(nil)
|
||||
lp.Update(500, false)
|
||||
lp.Update(500, false, false)
|
||||
|
||||
t.Log("switch off when disconnected")
|
||||
clock.Add(5 * time.Minute)
|
||||
charger.EXPECT().Enabled().Return(lp.enabled, nil)
|
||||
charger.EXPECT().Status().Return(api.StatusA, nil)
|
||||
charger.EXPECT().Enable(false).Return(nil)
|
||||
lp.Update(-3000, false)
|
||||
lp.Update(-3000, false, false)
|
||||
|
||||
if lp.Mode != api.ModeOff {
|
||||
t.Error("unexpected mode", lp.Mode)
|
||||
|
|
@ -543,14 +543,14 @@ func TestChargedEnergyAtDisconnect(t *testing.T) {
|
|||
rater.EXPECT().ChargedEnergy().Return(0.0, nil)
|
||||
charger.EXPECT().Enabled().Return(lp.enabled, nil)
|
||||
charger.EXPECT().Status().Return(api.StatusC, nil)
|
||||
lp.Update(-1, false)
|
||||
lp.Update(-1, false, false)
|
||||
|
||||
t.Log("at 1:00h charging at 5 kWh")
|
||||
clock.Add(time.Hour)
|
||||
rater.EXPECT().ChargedEnergy().Return(5.0, nil)
|
||||
charger.EXPECT().Enabled().Return(lp.enabled, nil)
|
||||
charger.EXPECT().Status().Return(api.StatusC, nil)
|
||||
lp.Update(-1, false)
|
||||
lp.Update(-1, false, false)
|
||||
expectCache("chargedEnergy", 5000.0)
|
||||
|
||||
t.Log("at 1:00h stop charging at 5 kWh")
|
||||
|
|
@ -558,7 +558,7 @@ func TestChargedEnergyAtDisconnect(t *testing.T) {
|
|||
rater.EXPECT().ChargedEnergy().Return(5.0, nil)
|
||||
charger.EXPECT().Enabled().Return(lp.enabled, nil)
|
||||
charger.EXPECT().Status().Return(api.StatusB, nil)
|
||||
lp.Update(-1, false)
|
||||
lp.Update(-1, false, false)
|
||||
expectCache("chargedEnergy", 5000.0)
|
||||
|
||||
t.Log("at 1:00h restart charging at 5 kWh")
|
||||
|
|
@ -566,7 +566,7 @@ func TestChargedEnergyAtDisconnect(t *testing.T) {
|
|||
rater.EXPECT().ChargedEnergy().Return(5.0, nil)
|
||||
charger.EXPECT().Enabled().Return(lp.enabled, nil)
|
||||
charger.EXPECT().Status().Return(api.StatusC, nil)
|
||||
lp.Update(-1, false)
|
||||
lp.Update(-1, false, false)
|
||||
expectCache("chargedEnergy", 5000.0)
|
||||
|
||||
t.Log("at 1:30h continue charging at 7.5 kWh")
|
||||
|
|
@ -574,7 +574,7 @@ func TestChargedEnergyAtDisconnect(t *testing.T) {
|
|||
rater.EXPECT().ChargedEnergy().Return(7.5, nil)
|
||||
charger.EXPECT().Enabled().Return(lp.enabled, nil)
|
||||
charger.EXPECT().Status().Return(api.StatusC, nil)
|
||||
lp.Update(-1, false)
|
||||
lp.Update(-1, false, false)
|
||||
expectCache("chargedEnergy", 7500.0)
|
||||
|
||||
t.Log("at 2:00h stop charging at 10 kWh")
|
||||
|
|
@ -582,7 +582,7 @@ func TestChargedEnergyAtDisconnect(t *testing.T) {
|
|||
rater.EXPECT().ChargedEnergy().Return(10.0, nil)
|
||||
charger.EXPECT().Enabled().Return(lp.enabled, nil)
|
||||
charger.EXPECT().Status().Return(api.StatusB, nil)
|
||||
lp.Update(-1, false)
|
||||
lp.Update(-1, false, false)
|
||||
expectCache("chargedEnergy", 10000.0)
|
||||
|
||||
ctrl.Finish()
|
||||
|
|
|
|||
21
core/site.go
21
core/site.go
|
|
@ -14,11 +14,9 @@ import (
|
|||
"github.com/evcc-io/evcc/util"
|
||||
)
|
||||
|
||||
//go:generate mockgen -package mock -destination ../mock/mock_loadpoint.go github.com/evcc-io/evcc/core Updater
|
||||
|
||||
// Updater abstracts the LoadPoint implementation for testing
|
||||
type Updater interface {
|
||||
Update(float64, bool)
|
||||
Update(availablePower float64, cheapRate bool, batteryBuffered bool)
|
||||
}
|
||||
|
||||
// Site is the main configuration container. A site can host multiple loadpoints.
|
||||
|
|
@ -37,6 +35,7 @@ type Site struct {
|
|||
ResidualPower float64 `mapstructure:"residualPower"` // PV meter only: household usage. Grid meter: household safety margin
|
||||
Meters MetersConfig // Meter references
|
||||
PrioritySoC float64 `mapstructure:"prioritySoC"` // prefer battery up to this SoC
|
||||
BufferSoC float64 `mapstructure:"bufferSoC"` // ignore battery above this SoC
|
||||
|
||||
// meters
|
||||
gridMeter api.Meter // Grid usage meter
|
||||
|
|
@ -47,9 +46,10 @@ type Site struct {
|
|||
loadpoints []*LoadPoint // Loadpoints
|
||||
|
||||
// cached state
|
||||
gridPower float64 // Grid power
|
||||
pvPower float64 // PV power
|
||||
batteryPower float64 // Battery charge power
|
||||
gridPower float64 // Grid power
|
||||
pvPower float64 // PV power
|
||||
batteryPower float64 // Battery charge power
|
||||
batteryBuffered bool // Battery buffer active
|
||||
}
|
||||
|
||||
// MetersConfig contains the loadpoint's meter configuration
|
||||
|
|
@ -323,11 +323,14 @@ func (site *Site) sitePower() (float64, error) {
|
|||
site.Lock()
|
||||
defer site.Unlock()
|
||||
|
||||
// if battery is charging give it priority
|
||||
// if battery is charging below prioritySoC give it priority
|
||||
if soc < site.PrioritySoC && batteryPower < 0 {
|
||||
site.log.DEBUG.Printf("giving priority to battery at soc: %.0f", soc)
|
||||
site.log.DEBUG.Printf("giving priority to battery charging at soc: %.0f", soc)
|
||||
batteryPower = 0
|
||||
}
|
||||
|
||||
// if battery is discharging above bufferSoC ignore it
|
||||
site.batteryBuffered = batteryPower > 0 && site.BufferSoC > 0 && soc > site.BufferSoC
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -346,7 +349,7 @@ func (site *Site) update(lp Updater) {
|
|||
}
|
||||
|
||||
if sitePower, err := site.sitePower(); err == nil {
|
||||
lp.Update(sitePower, cheap)
|
||||
lp.Update(sitePower, cheap, site.batteryBuffered)
|
||||
site.Health.Update()
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -61,7 +61,8 @@ site:
|
|||
pvs:
|
||||
- pv # list of pv inverters/ meters
|
||||
battery: battery # battery meter
|
||||
prioritySoC: 60 # give home battery priority up to this soc (0 to disable)
|
||||
prioritySoC: # give home battery priority up to this soc (empty to disable)
|
||||
bufferSoC: # ignore home battery discharge above soc (empty to disable)
|
||||
|
||||
# loadpoint describes the charger, charge meter and connected vehicle
|
||||
loadpoints:
|
||||
|
|
|
|||
|
|
@ -34,13 +34,13 @@ func (m *MockUpdater) EXPECT() *MockUpdaterMockRecorder {
|
|||
}
|
||||
|
||||
// Update mocks base method.
|
||||
func (m *MockUpdater) Update(arg0 float64, arg1 bool) {
|
||||
func (m *MockUpdater) Update(arg0 float64, arg1, arg2 bool) {
|
||||
m.ctrl.T.Helper()
|
||||
m.ctrl.Call(m, "Update", arg0, arg1)
|
||||
m.ctrl.Call(m, "Update", arg0, arg1, arg2)
|
||||
}
|
||||
|
||||
// Update indicates an expected call of Update.
|
||||
func (mr *MockUpdaterMockRecorder) Update(arg0, arg1 interface{}) *gomock.Call {
|
||||
func (mr *MockUpdaterMockRecorder) Update(arg0, arg1, arg2 interface{}) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Update", reflect.TypeOf((*MockUpdater)(nil).Update), arg0, arg1)
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Update", reflect.TypeOf((*MockUpdater)(nil).Update), arg0, arg1, arg2)
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue