Fix calculating remaining charge duration

This commit is contained in:
andig 2020-07-30 11:14:31 +02:00
parent a598cd56ab
commit e2c0ea9fcf
4 changed files with 97 additions and 31 deletions

View file

@ -2,7 +2,7 @@ package api
import "time"
//go:generate mockgen -package mock -destination ../mock/mock_api.go github.com/andig/evcc/api Charger,Meter,MeterEnergy
//go:generate mockgen -package mock -destination ../mock/mock_api.go github.com/andig/evcc/api Charger,Meter,MeterEnergy,Vehicle
// ChargeMode are charge modes modeled after OpenWB
type ChargeMode string

View file

@ -542,8 +542,15 @@ func (lp *LoadPoint) remainingChargeDuration(chargePercent float64) time.Duratio
}
if lp.chargePower > 0 && lp.vehicle != nil {
whTotal := int64(lp.TargetSoC) * lp.vehicle.Capacity()
whRemaining := (1 - chargePercent/100.0) * 1e3 * float64(whTotal)
chargePercent = chargePercent / 100.0
targetPercent := float64(lp.TargetSoC) / 100
if chargePercent >= targetPercent {
return 0
}
whTotal := float64(lp.vehicle.Capacity()) * 1e3
whRemaining := (targetPercent - chargePercent) * whTotal
return time.Duration(float64(time.Hour) * whRemaining / lp.chargePower).Round(time.Second)
}

View file

@ -167,33 +167,6 @@ func TestUpdate(t *testing.T) {
}
}
// func TestConsumedPower(t *testing.T) {
// tc := []struct {
// grid, pv, battery, consumed float64
// }{
// {0, 0, 0, 0}, // silent night
// {1, 0, 0, 1}, // grid import
// {0, 1, 0, 1}, // pv sign ignored
// {0, -1, 0, 1}, // pv sign ignored
// {1, 1, 0, 2}, // grid import + pv, pv sign ignored
// {1, -1, 0, 2}, // grid import + pv, pv sign ignored
// {0, 0, 1, 1}, // battery discharging
// {0, 0, -1, -1}, // battery charging -> negative result cannot occur in reality
// {1, -3, 1, 5}, // grid import + pv + battery discharging
// {1, -3, -1, 3}, // grid import + pv + battery charging -> should not happen in reality
// {0, -3, -1, 2}, // pv + battery charging
// {-1, -4, -1, 2}, // grid export + pv + battery charging
// {-1, -4, 0, 3}, // grid export + pv
// }
// for _, tc := range tc {
// res := consumedPower(tc.pv, tc.battery, tc.grid)
// if res != tc.consumed {
// t.Errorf("consumedPower wanted %.f, got %.f", tc.consumed, res)
// }
// }
// }
func TestPVHysteresisForStatusC(t *testing.T) {
dt := time.Minute
type se struct {
@ -380,3 +353,23 @@ func TestPVHysteresisForStatusOtherThanC(t *testing.T) {
ctrl.Finish()
}
func TestRemainingChargeDuration(t *testing.T) {
lp := NewLoadPoint(util.NewLogger("foo"))
ctrl := gomock.NewController(t)
vehicle := mock.NewMockVehicle(ctrl)
lp.vehicle = vehicle
lp.charging = true
soc := 20.0
lp.TargetSoC = 80
lp.chargePower = 1000
vehicle.EXPECT().Capacity().Return(int64(10))
if remaining := lp.remainingChargeDuration(soc); remaining != 6*time.Hour {
t.Error("wrong remaining charge duration")
}
}

View file

@ -1,5 +1,5 @@
// Code generated by MockGen. DO NOT EDIT.
// Source: github.com/andig/evcc/api (interfaces: Charger,Meter,MeterEnergy)
// Source: github.com/andig/evcc/api (interfaces: Charger,Meter,MeterEnergy,Vehicle)
// Package mock is a generated GoMock package.
package mock
@ -166,3 +166,69 @@ func (mr *MockMeterEnergyMockRecorder) TotalEnergy() *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "TotalEnergy", reflect.TypeOf((*MockMeterEnergy)(nil).TotalEnergy))
}
// MockVehicle is a mock of Vehicle interface
type MockVehicle struct {
ctrl *gomock.Controller
recorder *MockVehicleMockRecorder
}
// MockVehicleMockRecorder is the mock recorder for MockVehicle
type MockVehicleMockRecorder struct {
mock *MockVehicle
}
// NewMockVehicle creates a new mock instance
func NewMockVehicle(ctrl *gomock.Controller) *MockVehicle {
mock := &MockVehicle{ctrl: ctrl}
mock.recorder = &MockVehicleMockRecorder{mock}
return mock
}
// EXPECT returns an object that allows the caller to indicate expected use
func (m *MockVehicle) EXPECT() *MockVehicleMockRecorder {
return m.recorder
}
// Capacity mocks base method
func (m *MockVehicle) Capacity() int64 {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "Capacity")
ret0, _ := ret[0].(int64)
return ret0
}
// Capacity indicates an expected call of Capacity
func (mr *MockVehicleMockRecorder) Capacity() *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Capacity", reflect.TypeOf((*MockVehicle)(nil).Capacity))
}
// ChargeState mocks base method
func (m *MockVehicle) ChargeState() (float64, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "ChargeState")
ret0, _ := ret[0].(float64)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// ChargeState indicates an expected call of ChargeState
func (mr *MockVehicleMockRecorder) ChargeState() *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ChargeState", reflect.TypeOf((*MockVehicle)(nil).ChargeState))
}
// Title mocks base method
func (m *MockVehicle) Title() string {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "Title")
ret0, _ := ret[0].(string)
return ret0
}
// Title indicates an expected call of Title
func (mr *MockVehicleMockRecorder) Title() *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Title", reflect.TypeOf((*MockVehicle)(nil).Title))
}