Refactor charge current calculation (#223)
This commit is contained in:
parent
985a16a91d
commit
eb66f6d911
9 changed files with 156 additions and 208 deletions
|
|
@ -11,6 +11,18 @@ import (
|
|||
"github.com/benbjohnson/clock"
|
||||
)
|
||||
|
||||
//go:generate mockgen -package mock -destination ../mock/mock_chargerhandler.go github.com/andig/evcc/core Handler
|
||||
|
||||
// Handler is the charger handler responsible for enabled state, target current and guard durations
|
||||
type Handler interface {
|
||||
Prepare()
|
||||
SyncEnabled()
|
||||
Enabled() bool
|
||||
Status() (api.ChargeStatus, error)
|
||||
TargetCurrent() int64
|
||||
Ramp(int64, ...bool) error
|
||||
}
|
||||
|
||||
// HandlerConfig contains the public configuration for the ChargerHandler
|
||||
type HandlerConfig struct {
|
||||
Sensitivity int64 // Step size of current change
|
||||
|
|
@ -74,8 +86,8 @@ func (lp *ChargerHandler) Prepare() {
|
|||
lp.bus.Publish(evChargeCurrent, lp.MinCurrent)
|
||||
}
|
||||
|
||||
// SyncSettings synchronizes charger settings to expected state
|
||||
func (lp *ChargerHandler) SyncSettings() {
|
||||
// SyncEnabled synchronizes charger settings to expected state
|
||||
func (lp *ChargerHandler) SyncEnabled() {
|
||||
enabled, err := lp.charger.Enabled()
|
||||
if err == nil && enabled != lp.enabled {
|
||||
lp.log.DEBUG.Printf("sync enabled state to %s", status[lp.enabled])
|
||||
|
|
|
|||
|
|
@ -14,8 +14,13 @@ var (
|
|||
)
|
||||
|
||||
// powerToCurrent is a helper function to convert power to per-phase current
|
||||
func powerToCurrent(power, voltage float64, phases int64) int64 {
|
||||
return int64(power / (float64(phases) * voltage))
|
||||
func powerToCurrent(power float64, phases int64) int64 {
|
||||
return int64(power / (float64(phases) * Voltage))
|
||||
}
|
||||
|
||||
// currentToPower is a helper function to convert phase current to total power
|
||||
func currentToPower(current, phases int64) float64 {
|
||||
return float64(current*phases) * Voltage
|
||||
}
|
||||
|
||||
// consumedPower estimates how much power the charger might have consumed given it was the only load
|
||||
|
|
|
|||
|
|
@ -29,18 +29,6 @@ type ThresholdConfig struct {
|
|||
Threshold float64
|
||||
}
|
||||
|
||||
//go:generate mockgen -package mock -destination ../mock/mock_chargerhandler.go github.com/andig/evcc/core Handler
|
||||
|
||||
// Handler is the charger handler responsible for enabled state, target current and guard durations
|
||||
type Handler interface {
|
||||
Prepare()
|
||||
SyncSettings()
|
||||
Enabled() bool
|
||||
Status() (api.ChargeStatus, error)
|
||||
TargetCurrent() int64
|
||||
Ramp(int64, ...bool) error
|
||||
}
|
||||
|
||||
// LoadPoint is responsible for controlling charge depending on
|
||||
// SoC needs and power availability.
|
||||
type LoadPoint struct {
|
||||
|
|
@ -73,7 +61,6 @@ type LoadPoint struct {
|
|||
status api.ChargeStatus // Charger status
|
||||
charging bool // Charging cycle
|
||||
chargePower float64 // Charging power
|
||||
sitePower float64 // Available power from site
|
||||
|
||||
pvTimer time.Time
|
||||
}
|
||||
|
|
@ -303,13 +290,11 @@ func (lp *LoadPoint) detectPhases() {
|
|||
}
|
||||
}
|
||||
|
||||
func (lp *LoadPoint) maxCurrent(mode api.ChargeMode) int64 {
|
||||
// grid meter will always be available, if as wrapped pv meter
|
||||
targetPower := lp.chargePower - lp.sitePower
|
||||
lp.log.DEBUG.Printf("target power: %.0fW = %.0fW charge - %.0fW available", targetPower, lp.chargePower, lp.sitePower)
|
||||
|
||||
// get max charge current
|
||||
targetCurrent := clamp(powerToCurrent(targetPower, Voltage, lp.Phases), 0, lp.MaxCurrent)
|
||||
// maxCurrent calculates the maximum target current for PV mode
|
||||
func (lp *LoadPoint) maxCurrent(mode api.ChargeMode, sitePower float64) int64 {
|
||||
// calculate target charge current from delta power and actual current
|
||||
deltaCurrent := powerToCurrent(-sitePower, lp.Phases)
|
||||
targetCurrent := clamp(lp.handler.TargetCurrent()+deltaCurrent, 0, lp.MaxCurrent)
|
||||
|
||||
// in MinPV mode return at least minCurrent
|
||||
if mode == api.ModeMinPV && targetCurrent < lp.MinCurrent {
|
||||
|
|
@ -332,8 +317,8 @@ func (lp *LoadPoint) maxCurrent(mode api.ChargeMode) int64 {
|
|||
|
||||
if mode == api.ModePV && enabled && targetCurrent < lp.MinCurrent {
|
||||
// kick off disable sequence
|
||||
if lp.sitePower >= lp.Disable.Threshold {
|
||||
lp.log.DEBUG.Printf("site power %.0f >= disable threshold %.0f", lp.sitePower, lp.Disable.Threshold)
|
||||
if sitePower >= lp.Disable.Threshold {
|
||||
lp.log.DEBUG.Printf("site power %.0f >= disable threshold %.0f", sitePower, lp.Disable.Threshold)
|
||||
|
||||
if lp.pvTimer.IsZero() {
|
||||
lp.log.DEBUG.Println("start pv disable timer")
|
||||
|
|
@ -355,8 +340,8 @@ func (lp *LoadPoint) maxCurrent(mode api.ChargeMode) int64 {
|
|||
if mode == api.ModePV && !enabled {
|
||||
// kick off enable sequence
|
||||
if targetCurrent >= lp.MinCurrent ||
|
||||
(lp.Enable.Threshold != 0 && lp.sitePower <= lp.Enable.Threshold) {
|
||||
lp.log.DEBUG.Printf("site power %.0f < enable threshold %.0f", lp.sitePower, lp.Enable.Threshold)
|
||||
(lp.Enable.Threshold != 0 && sitePower <= lp.Enable.Threshold) {
|
||||
lp.log.DEBUG.Printf("site power %.0f < enable threshold %.0f", sitePower, lp.Enable.Threshold)
|
||||
|
||||
if lp.pvTimer.IsZero() {
|
||||
lp.log.DEBUG.Println("start pv enable timer")
|
||||
|
|
@ -382,40 +367,25 @@ func (lp *LoadPoint) maxCurrent(mode api.ChargeMode) int64 {
|
|||
return targetCurrent
|
||||
}
|
||||
|
||||
// updateMeter updates and publishes single meter
|
||||
func (lp *LoadPoint) updateMeter(name string, meter api.Meter, power *float64) error {
|
||||
value, err := meter.CurrentPower()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
*power = value // update value if no error
|
||||
|
||||
lp.log.DEBUG.Printf("%s power: %.1fW", name, *power)
|
||||
lp.publish(name+"Power", *power)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// updateMeter updates and publishes single meter
|
||||
func (lp *LoadPoint) updateMeters() (err error) {
|
||||
retryMeter := func(s string, m api.Meter, f *float64) {
|
||||
if m != nil {
|
||||
e := retry.Do(func() error {
|
||||
return lp.updateMeter(s, m, f)
|
||||
}, retryOptions...)
|
||||
|
||||
if e != nil {
|
||||
err = errors.Wrapf(e, "updating %s meter", s)
|
||||
lp.log.ERROR.Printf("%v", err)
|
||||
}
|
||||
// updateChargeMete updates and publishes single meter
|
||||
func (lp *LoadPoint) updateChargeMeter() {
|
||||
err := retry.Do(func() error {
|
||||
value, err := lp.chargeMeter.CurrentPower()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
lp.chargePower = value // update value if no error
|
||||
lp.log.DEBUG.Printf("charge power: %.1fW", value)
|
||||
lp.publish("chargePower", value)
|
||||
|
||||
return nil
|
||||
}, retryOptions...)
|
||||
|
||||
if err != nil {
|
||||
err = errors.Wrapf(err, "updating charge meter")
|
||||
lp.log.ERROR.Printf("%v", err)
|
||||
}
|
||||
|
||||
// read PV meter before charge meter
|
||||
retryMeter("charge", lp.chargeMeter, &lp.chargePower)
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
// chargeDuration returns for how long the charge cycle has been running
|
||||
|
|
@ -480,11 +450,9 @@ func (lp *LoadPoint) publishSoC() {
|
|||
}
|
||||
|
||||
// Update is the main control function. It reevaluates meters and charger state
|
||||
func (lp *LoadPoint) Update(mode api.ChargeMode, sitePower float64) float64 {
|
||||
func (lp *LoadPoint) Update(mode api.ChargeMode, sitePower float64) {
|
||||
// read and publish meters first
|
||||
_ = lp.updateMeters()
|
||||
|
||||
lp.sitePower = sitePower
|
||||
lp.updateChargeMeter()
|
||||
|
||||
// update ChargeRater here to make sure initial meter update is caught
|
||||
lp.bus.Publish(evChargeCurrent, lp.handler.TargetCurrent())
|
||||
|
|
@ -497,7 +465,7 @@ func (lp *LoadPoint) Update(mode api.ChargeMode, sitePower float64) float64 {
|
|||
// read and publish status
|
||||
if err := retry.Do(lp.updateChargeStatus, retryOptions...); err != nil {
|
||||
lp.log.ERROR.Printf("charge controller error: %v", err)
|
||||
return lp.chargePower
|
||||
return
|
||||
}
|
||||
|
||||
lp.publish("connected", lp.connected())
|
||||
|
|
@ -505,7 +473,7 @@ func (lp *LoadPoint) Update(mode api.ChargeMode, sitePower float64) float64 {
|
|||
|
||||
// sync settings with charger
|
||||
if lp.status != api.StatusA {
|
||||
lp.handler.SyncSettings()
|
||||
lp.handler.SyncEnabled()
|
||||
}
|
||||
|
||||
// phase detection - run only when actually charging
|
||||
|
|
@ -530,7 +498,7 @@ func (lp *LoadPoint) Update(mode api.ChargeMode, sitePower float64) float64 {
|
|||
err = lp.handler.Ramp(current, true)
|
||||
|
||||
case api.ModeMinPV, api.ModePV:
|
||||
targetCurrent := lp.maxCurrent(mode)
|
||||
targetCurrent := lp.maxCurrent(mode, sitePower)
|
||||
if !lp.connected() {
|
||||
// ensure minimum current when not connected
|
||||
// https://github.com/andig/evcc/issues/105
|
||||
|
|
@ -544,6 +512,4 @@ func (lp *LoadPoint) Update(mode api.ChargeMode, sitePower float64) float64 {
|
|||
if err != nil {
|
||||
lp.log.ERROR.Println(err)
|
||||
}
|
||||
|
||||
return lp.chargePower
|
||||
}
|
||||
|
|
|
|||
|
|
@ -146,10 +146,14 @@ func TestUpdate(t *testing.T) {
|
|||
attachListeners(lp)
|
||||
|
||||
handler.EXPECT().Status().Return(tc.status, nil)
|
||||
handler.EXPECT().TargetCurrent().Return(int64(-1))
|
||||
handler.EXPECT().TargetCurrent().Return(int64(0))
|
||||
|
||||
if tc.status != api.StatusA {
|
||||
handler.EXPECT().SyncSettings()
|
||||
handler.EXPECT().SyncEnabled()
|
||||
}
|
||||
|
||||
if tc.mode == api.ModeMinPV || tc.mode == api.ModePV {
|
||||
handler.EXPECT().TargetCurrent().Return(int64(0))
|
||||
}
|
||||
|
||||
if tc.expect != nil {
|
||||
|
|
@ -326,11 +330,12 @@ func TestPVHysteresisForStatusC(t *testing.T) {
|
|||
|
||||
for step, se := range tc.series {
|
||||
clck.Set(start.Add(se.delay))
|
||||
lp.sitePower = se.site
|
||||
|
||||
// maxCurrent will read enabled state in PV mode
|
||||
// maxCurrent will read actual current and enabled state in PV mode
|
||||
handler.EXPECT().TargetCurrent().Return(int64(0))
|
||||
handler.EXPECT().Enabled().Return(tc.enabled)
|
||||
current := lp.maxCurrent(api.ModePV)
|
||||
|
||||
current := lp.maxCurrent(api.ModePV, se.site)
|
||||
|
||||
if current != se.current {
|
||||
t.Errorf("step %d: wanted %d, got %d", step, se.current, current)
|
||||
|
|
@ -342,7 +347,6 @@ func TestPVHysteresisForStatusC(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestPVHysteresisForStatusOtherThanC(t *testing.T) {
|
||||
|
||||
clck := clock.NewMock()
|
||||
ctrl := gomock.NewController(t)
|
||||
handler := mock.NewMockHandler(ctrl)
|
||||
|
|
@ -362,9 +366,12 @@ func TestPVHysteresisForStatusOtherThanC(t *testing.T) {
|
|||
// not connected, test PV mode logic short-circuited
|
||||
lp.status = api.StatusA
|
||||
|
||||
// maxCurrent will read actual current in PV mode
|
||||
handler.EXPECT().TargetCurrent().Return(int64(0))
|
||||
|
||||
// maxCurrent will read enabled state in PV mode
|
||||
lp.sitePower = -float64(minA*lp.Phases)*Voltage + 1 // 1W below min power
|
||||
current := lp.maxCurrent(api.ModePV)
|
||||
sitePower := -float64(minA*lp.Phases)*Voltage + 1 // 1W below min power
|
||||
current := lp.maxCurrent(api.ModePV, sitePower)
|
||||
|
||||
if current != 0 {
|
||||
t.Errorf("PV mode could not disable charger as expected. Expected 0, got %d", current)
|
||||
|
|
|
|||
63
core/site.go
63
core/site.go
|
|
@ -12,11 +12,11 @@ import (
|
|||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
//go:generate mockgen -package mock -destination ../mock/mock_loadpoint.go github.com/andig/evcc/core LoadPointer
|
||||
//go:generate mockgen -package mock -destination ../mock/mock_loadpoint.go github.com/andig/evcc/core Updater
|
||||
|
||||
// LoadPointer abstracts the LoadPoint implementation for testing
|
||||
type LoadPointer interface {
|
||||
Update(api.ChargeMode, float64) float64
|
||||
// Updater abstracts the LoadPoint implementation for testing
|
||||
type Updater interface {
|
||||
Update(api.ChargeMode, float64)
|
||||
}
|
||||
|
||||
// Site is the main configuration container. A site can host multiple loadpoints.
|
||||
|
|
@ -38,7 +38,7 @@ type Site struct {
|
|||
pvMeter api.Meter // PV generation meter
|
||||
batteryMeter api.Meter // Battery charging meter
|
||||
|
||||
loadpoints []LoadPointer // Loadpoints
|
||||
loadpoints []*LoadPoint // Loadpoints
|
||||
|
||||
// cached state
|
||||
gridPower float64 // Grid power
|
||||
|
|
@ -136,11 +136,7 @@ func (lp *LoadPoint) hasChargeMeter() bool {
|
|||
|
||||
// LoadPoints returns the array of associated loadpoints
|
||||
func (site *Site) LoadPoints() []*LoadPoint {
|
||||
res := make([]*LoadPoint, 0, len(site.loadpoints))
|
||||
for _, lp := range site.loadpoints {
|
||||
res = append(res, lp.(*LoadPoint))
|
||||
}
|
||||
return res
|
||||
return site.loadpoints
|
||||
}
|
||||
|
||||
// Configuration returns meter configuration
|
||||
|
|
@ -153,9 +149,7 @@ func (site *Site) Configuration() SiteConfiguration {
|
|||
BatteryMeter: site.batteryMeter != nil,
|
||||
}
|
||||
|
||||
for _, lptr := range site.loadpoints {
|
||||
lp := lptr.(*LoadPoint)
|
||||
|
||||
for _, lp := range site.loadpoints {
|
||||
lpc := LoadpointConfiguration{
|
||||
Title: lp.Name(),
|
||||
Phases: lp.Phases,
|
||||
|
|
@ -184,8 +178,7 @@ func (site *Site) DumpConfig() {
|
|||
site.log.INFO.Printf(" pv %s", presence[site.pvMeter != nil])
|
||||
site.log.INFO.Printf(" battery %s", presence[site.batteryMeter != nil])
|
||||
|
||||
for i, lptr := range site.loadpoints {
|
||||
lp := lptr.(*LoadPoint)
|
||||
for i, lp := range site.loadpoints {
|
||||
lp.log.INFO.Printf("loadpoint %d config:", i)
|
||||
|
||||
lp.log.INFO.Printf(" vehicle %s", presence[lp.vehicle != nil])
|
||||
|
|
@ -302,30 +295,20 @@ func (site *Site) sitePower() (float64, error) {
|
|||
return sitePower, nil
|
||||
}
|
||||
|
||||
func (site *Site) update() error {
|
||||
func (site *Site) update(lp Updater) {
|
||||
mode := site.GetMode()
|
||||
site.publish("mode", string(mode))
|
||||
|
||||
sitePower, err := site.sitePower()
|
||||
if err != nil {
|
||||
return err
|
||||
if sitePower, err := site.sitePower(); err == nil {
|
||||
lp.Update(mode, sitePower)
|
||||
}
|
||||
|
||||
for idx, lp := range site.loadpoints {
|
||||
usedPower := lp.Update(mode, sitePower)
|
||||
remainingPower := sitePower + usedPower
|
||||
site.log.DEBUG.Printf("lp-%d remaining power: %.0fW = %.0fW - %.0fW", idx+1, remainingPower, sitePower, usedPower)
|
||||
sitePower = remainingPower
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Prepare attaches communication channels to site and loadpoints
|
||||
func (site *Site) Prepare(uiChan chan<- util.Param, pushChan chan<- push.Event) {
|
||||
site.uiChan = uiChan
|
||||
|
||||
for id, loadPoint := range site.loadpoints {
|
||||
for id, lp := range site.loadpoints {
|
||||
lpUIChan := make(chan util.Param)
|
||||
lpPushChan := make(chan push.Event)
|
||||
|
||||
|
|
@ -343,7 +326,16 @@ func (site *Site) Prepare(uiChan chan<- util.Param, pushChan chan<- push.Event)
|
|||
}
|
||||
}(id)
|
||||
|
||||
loadPoint.(*LoadPoint).Prepare(lpUIChan, lpPushChan)
|
||||
lp.Prepare(lpUIChan, lpPushChan)
|
||||
}
|
||||
}
|
||||
|
||||
// loopLoadpoints keeps iterating across loadpoints sending the next to the given channel
|
||||
func (site *Site) loopLoadpoints(next chan<- Updater) {
|
||||
for {
|
||||
for _, lp := range site.loadpoints {
|
||||
next <- lp
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -354,14 +346,17 @@ func (site *Site) Run(interval time.Duration) {
|
|||
ticker := time.NewTicker(interval)
|
||||
site.triggerChan <- struct{}{} // start immediately
|
||||
|
||||
loadpointChan := make(chan Updater)
|
||||
go site.loopLoadpoints(loadpointChan)
|
||||
|
||||
for {
|
||||
select {
|
||||
case <-ticker.C:
|
||||
if site.update() != nil {
|
||||
site.triggerChan <- struct{}{} // restart immediately
|
||||
}
|
||||
site.update(<-loadpointChan)
|
||||
case <-site.triggerChan:
|
||||
_ = site.update()
|
||||
for range site.loadpoints {
|
||||
site.update(<-loadpointChan)
|
||||
}
|
||||
ticker.Stop()
|
||||
ticker = time.NewTicker(interval)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,11 +2,6 @@ package core
|
|||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/andig/evcc/api"
|
||||
"github.com/andig/evcc/mock"
|
||||
"github.com/andig/evcc/util"
|
||||
"github.com/golang/mock/gomock"
|
||||
)
|
||||
|
||||
func TestSitePower(t *testing.T) {
|
||||
|
|
@ -29,32 +24,3 @@ func TestSitePower(t *testing.T) {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestSiteUpdateMultipleLoadpoints(t *testing.T) {
|
||||
ctrl := gomock.NewController(t)
|
||||
defer ctrl.Finish()
|
||||
|
||||
sitePower := -1000.0
|
||||
lps := []float64{300.0, 400.0, 500.0}
|
||||
|
||||
gm := mock.NewMockMeter(ctrl)
|
||||
gm.EXPECT().CurrentPower().Return(sitePower, nil)
|
||||
|
||||
site := &Site{
|
||||
log: util.NewLogger("foo"),
|
||||
Mode: api.ModePV,
|
||||
gridMeter: gm,
|
||||
}
|
||||
|
||||
availablePower := sitePower
|
||||
|
||||
for _, usedPower := range lps {
|
||||
lp := mock.NewMockLoadPointer(ctrl)
|
||||
site.loadpoints = append(site.loadpoints, lp)
|
||||
|
||||
lp.EXPECT().Update(api.ModePV, availablePower).Return(usedPower)
|
||||
availablePower += usedPower
|
||||
}
|
||||
|
||||
_ = site.update()
|
||||
}
|
||||
|
|
|
|||
|
|
@ -93,16 +93,16 @@ func (mr *MockHandlerMockRecorder) Status() *gomock.Call {
|
|||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Status", reflect.TypeOf((*MockHandler)(nil).Status))
|
||||
}
|
||||
|
||||
// SyncSettings mocks base method
|
||||
func (m *MockHandler) SyncSettings() {
|
||||
// SyncEnabled mocks base method
|
||||
func (m *MockHandler) SyncEnabled() {
|
||||
m.ctrl.T.Helper()
|
||||
m.ctrl.Call(m, "SyncSettings")
|
||||
m.ctrl.Call(m, "SyncEnabled")
|
||||
}
|
||||
|
||||
// SyncSettings indicates an expected call of SyncSettings
|
||||
func (mr *MockHandlerMockRecorder) SyncSettings() *gomock.Call {
|
||||
// SyncEnabled indicates an expected call of SyncEnabled
|
||||
func (mr *MockHandlerMockRecorder) SyncEnabled() *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SyncSettings", reflect.TypeOf((*MockHandler)(nil).SyncSettings))
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SyncEnabled", reflect.TypeOf((*MockHandler)(nil).SyncEnabled))
|
||||
}
|
||||
|
||||
// TargetCurrent mocks base method
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
// Code generated by MockGen. DO NOT EDIT.
|
||||
// Source: github.com/andig/evcc/core (interfaces: LoadPointer)
|
||||
// Source: github.com/andig/evcc/core (interfaces: Updater)
|
||||
|
||||
// Package mock is a generated GoMock package.
|
||||
package mock
|
||||
|
|
@ -10,39 +10,37 @@ import (
|
|||
reflect "reflect"
|
||||
)
|
||||
|
||||
// MockLoadPointer is a mock of LoadPointer interface
|
||||
type MockLoadPointer struct {
|
||||
// MockUpdater is a mock of Updater interface
|
||||
type MockUpdater struct {
|
||||
ctrl *gomock.Controller
|
||||
recorder *MockLoadPointerMockRecorder
|
||||
recorder *MockUpdaterMockRecorder
|
||||
}
|
||||
|
||||
// MockLoadPointerMockRecorder is the mock recorder for MockLoadPointer
|
||||
type MockLoadPointerMockRecorder struct {
|
||||
mock *MockLoadPointer
|
||||
// MockUpdaterMockRecorder is the mock recorder for MockUpdater
|
||||
type MockUpdaterMockRecorder struct {
|
||||
mock *MockUpdater
|
||||
}
|
||||
|
||||
// NewMockLoadPointer creates a new mock instance
|
||||
func NewMockLoadPointer(ctrl *gomock.Controller) *MockLoadPointer {
|
||||
mock := &MockLoadPointer{ctrl: ctrl}
|
||||
mock.recorder = &MockLoadPointerMockRecorder{mock}
|
||||
// NewMockUpdater creates a new mock instance
|
||||
func NewMockUpdater(ctrl *gomock.Controller) *MockUpdater {
|
||||
mock := &MockUpdater{ctrl: ctrl}
|
||||
mock.recorder = &MockUpdaterMockRecorder{mock}
|
||||
return mock
|
||||
}
|
||||
|
||||
// EXPECT returns an object that allows the caller to indicate expected use
|
||||
func (m *MockLoadPointer) EXPECT() *MockLoadPointerMockRecorder {
|
||||
func (m *MockUpdater) EXPECT() *MockUpdaterMockRecorder {
|
||||
return m.recorder
|
||||
}
|
||||
|
||||
// Update mocks base method
|
||||
func (m *MockLoadPointer) Update(arg0 api.ChargeMode, arg1 float64) float64 {
|
||||
func (m *MockUpdater) Update(arg0 api.ChargeMode, arg1 float64) {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "Update", arg0, arg1)
|
||||
ret0, _ := ret[0].(float64)
|
||||
return ret0
|
||||
m.ctrl.Call(m, "Update", arg0, arg1)
|
||||
}
|
||||
|
||||
// Update indicates an expected call of Update
|
||||
func (mr *MockLoadPointerMockRecorder) Update(arg0, arg1 interface{}) *gomock.Call {
|
||||
func (mr *MockUpdaterMockRecorder) Update(arg0, arg1 interface{}) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Update", reflect.TypeOf((*MockLoadPointer)(nil).Update), arg0, arg1)
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Update", reflect.TypeOf((*MockUpdater)(nil).Update), arg0, arg1)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3625,47 +3625,46 @@ u5/Ve679nt1z7X+G+k8AAAD//9FsvQIdNQAA
|
|||
"/js/app.js": {
|
||||
name: "app.js",
|
||||
local: "../assets/js/app.js",
|
||||
size: 7032,
|
||||
size: 6792,
|
||||
modtime: 1566640112,
|
||||
compressed: `
|
||||
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
|
||||
Pz54GwAA
|
||||
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
|
||||
JiAeF0hccFuj/x0AAP//sWHskogaAAA=
|
||||
`,
|
||||
},
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue