api: split MeterEnergy into MeterImport and MeterExport (#29788)
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
37e5659936
commit
7a5b1e7560
128 changed files with 570 additions and 435 deletions
13
api/api.go
13
api/api.go
|
|
@ -9,16 +9,21 @@ import (
|
|||
"golang.org/x/oauth2"
|
||||
)
|
||||
|
||||
//go:generate go tool mockgen -package api -destination mock.go github.com/evcc-io/evcc/api Charger,ChargeState,CurrentLimiter,CurrentGetter,PhaseSwitcher,PhaseGetter,FeatureDescriber,Identifier,Meter,MeterEnergy,PhaseCurrents,Vehicle,ConnectionTimer,ChargeRater,Battery,BatteryController,BatterySocLimiter,Circuit,Dimmer,Tariff
|
||||
//go:generate go tool mockgen -package api -destination mock.go github.com/evcc-io/evcc/api Charger,ChargeState,CurrentLimiter,CurrentGetter,PhaseSwitcher,PhaseGetter,FeatureDescriber,Identifier,Meter,MeterImport,MeterExport,PhaseCurrents,Vehicle,ConnectionTimer,ChargeRater,Battery,BatteryController,BatterySocLimiter,Circuit,Dimmer,Tariff
|
||||
|
||||
// Meter provides total active power in W
|
||||
type Meter interface {
|
||||
CurrentPower() (float64, error)
|
||||
}
|
||||
|
||||
// MeterEnergy provides total energy in kWh
|
||||
type MeterEnergy interface {
|
||||
TotalEnergy() (float64, error)
|
||||
// MeterImport provides total import in kWh
|
||||
type MeterImport interface {
|
||||
ImportEnergy() (float64, error)
|
||||
}
|
||||
|
||||
// MeterExport provides total export in kWh
|
||||
type MeterExport interface {
|
||||
ExportEnergy() (float64, error)
|
||||
}
|
||||
|
||||
// PhaseCurrents provides per-phase current A
|
||||
|
|
|
|||
|
|
@ -19,9 +19,9 @@ func (d *decoratedMeter) Capability(typ reflect.Type) (any, bool) {
|
|||
return c, ok
|
||||
}
|
||||
|
||||
type testMeterEnergyImpl struct{}
|
||||
type testMeterImportImpl struct{}
|
||||
|
||||
func (t *testMeterEnergyImpl) TotalEnergy() (float64, error) {
|
||||
func (t *testMeterImportImpl) ImportEnergy() (float64, error) {
|
||||
return 99.0, nil
|
||||
}
|
||||
|
||||
|
|
@ -32,13 +32,13 @@ func (t *testMeterImpl) CurrentPower() (float64, error) {
|
|||
}
|
||||
|
||||
func TestCap_DirectTypeAssertion(t *testing.T) {
|
||||
// concrete type that directly implements MeterEnergy
|
||||
impl := &testMeterEnergyImpl{}
|
||||
// concrete type that directly implements MeterImport
|
||||
impl := &testMeterImportImpl{}
|
||||
|
||||
me, ok := Cap[MeterEnergy](impl)
|
||||
me, ok := Cap[MeterImport](impl)
|
||||
require.True(t, ok)
|
||||
|
||||
energy, err := me.TotalEnergy()
|
||||
energy, err := me.ImportEnergy()
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, 99.0, energy)
|
||||
}
|
||||
|
|
@ -49,15 +49,15 @@ func TestCap_CapableRegistryLookup(t *testing.T) {
|
|||
decorated := &decoratedMeter{
|
||||
Meter: base,
|
||||
caps: map[reflect.Type]any{
|
||||
reflect.TypeFor[MeterEnergy](): &testMeterEnergyImpl{},
|
||||
reflect.TypeFor[MeterImport](): &testMeterImportImpl{},
|
||||
},
|
||||
}
|
||||
|
||||
// should find MeterEnergy via registry
|
||||
me, ok := Cap[MeterEnergy](decorated)
|
||||
// should find MeterImport via registry
|
||||
me, ok := Cap[MeterImport](decorated)
|
||||
require.True(t, ok)
|
||||
|
||||
energy, err := me.TotalEnergy()
|
||||
energy, err := me.ImportEnergy()
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, 99.0, energy)
|
||||
|
||||
|
|
@ -81,11 +81,11 @@ func TestCap_ExtractedCapabilityLosesRegistry(t *testing.T) {
|
|||
// Reproduces https://github.com/evcc-io/evcc/issues/28915
|
||||
// When a Meter is extracted from a decorated charger via Cap[Meter],
|
||||
// the extracted impl does NOT carry the Capable interface, so
|
||||
// subsequent Cap[MeterEnergy] on the extracted value fails.
|
||||
// subsequent Cap[MeterImport] on the extracted value fails.
|
||||
decorated := &decoratedCharger{
|
||||
caps: map[reflect.Type]any{
|
||||
reflect.TypeFor[Meter](): &testMeterImpl{},
|
||||
reflect.TypeFor[MeterEnergy](): &testMeterEnergyImpl{},
|
||||
reflect.TypeFor[MeterImport](): &testMeterImportImpl{},
|
||||
},
|
||||
}
|
||||
|
||||
|
|
@ -93,9 +93,9 @@ func TestCap_ExtractedCapabilityLosesRegistry(t *testing.T) {
|
|||
mt, ok := Cap[Meter](decorated)
|
||||
require.True(t, ok)
|
||||
|
||||
// Bug: extracted meter cannot find MeterEnergy because it's a standalone impl
|
||||
_, ok = Cap[MeterEnergy](mt)
|
||||
assert.False(t, ok, "extracted meter should NOT have MeterEnergy capability")
|
||||
// Bug: extracted meter cannot find MeterImport because it's a standalone impl
|
||||
_, ok = Cap[MeterImport](mt)
|
||||
assert.False(t, ok, "extracted meter should NOT have MeterImport capability")
|
||||
|
||||
// Fix: wrapping extracted meter with source's Capable preserves registry
|
||||
type capableMeter struct {
|
||||
|
|
@ -104,32 +104,32 @@ func TestCap_ExtractedCapabilityLosesRegistry(t *testing.T) {
|
|||
}
|
||||
wrapped := &capableMeter{Meter: mt, Capable: decorated}
|
||||
|
||||
me, ok := Cap[MeterEnergy](wrapped)
|
||||
require.True(t, ok, "wrapped meter should find MeterEnergy via Capable")
|
||||
me, ok := Cap[MeterImport](wrapped)
|
||||
require.True(t, ok, "wrapped meter should find MeterImport via Capable")
|
||||
|
||||
energy, err := me.TotalEnergy()
|
||||
energy, err := me.ImportEnergy()
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, 99.0, energy)
|
||||
}
|
||||
|
||||
func TestCap_NilValue(t *testing.T) {
|
||||
_, ok := Cap[MeterEnergy](nil)
|
||||
_, ok := Cap[MeterImport](nil)
|
||||
assert.False(t, ok)
|
||||
}
|
||||
|
||||
func TestCap_DirectTakesPrecedence(t *testing.T) {
|
||||
// type that both directly implements AND has registry
|
||||
type directAndCapable struct {
|
||||
testMeterEnergyImpl
|
||||
testMeterImportImpl
|
||||
caps map[reflect.Type]any //nolint:unused
|
||||
}
|
||||
|
||||
v := &directAndCapable{}
|
||||
|
||||
me, ok := Cap[MeterEnergy](v)
|
||||
me, ok := Cap[MeterImport](v)
|
||||
require.True(t, ok)
|
||||
|
||||
energy, err := me.TotalEnergy()
|
||||
energy, err := me.ImportEnergy()
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, 99.0, energy)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -203,19 +203,34 @@ func (i *iChargeState) Status() (api.ChargeStatus, error) {
|
|||
return i.chargeState0()
|
||||
}
|
||||
|
||||
func MeterEnergy(meterEnergy0 func() (float64, error)) api.MeterEnergy {
|
||||
if meterEnergy0 == nil {
|
||||
func MeterImport(meterImport0 func() (float64, error)) api.MeterImport {
|
||||
if meterImport0 == nil {
|
||||
return nil
|
||||
}
|
||||
return &iMeterEnergy{meterEnergy0}
|
||||
return &iMeterImport{meterImport0}
|
||||
}
|
||||
|
||||
type iMeterEnergy struct {
|
||||
meterEnergy0 func() (float64, error)
|
||||
type iMeterImport struct {
|
||||
meterImport0 func() (float64, error)
|
||||
}
|
||||
|
||||
func (i *iMeterEnergy) TotalEnergy() (float64, error) {
|
||||
return i.meterEnergy0()
|
||||
func (i *iMeterImport) ImportEnergy() (float64, error) {
|
||||
return i.meterImport0()
|
||||
}
|
||||
|
||||
func MeterExport(meterExport0 func() (float64, error)) api.MeterExport {
|
||||
if meterExport0 == nil {
|
||||
return nil
|
||||
}
|
||||
return &iMeterExport{meterExport0}
|
||||
}
|
||||
|
||||
type iMeterExport struct {
|
||||
meterExport0 func() (float64, error)
|
||||
}
|
||||
|
||||
func (i *iMeterExport) ExportEnergy() (float64, error) {
|
||||
return i.meterExport0()
|
||||
}
|
||||
|
||||
func PhaseCurrents(phaseCurrents0 func() (float64, float64, float64, error)) api.PhaseCurrents {
|
||||
|
|
|
|||
77
api/mock.go
77
api/mock.go
|
|
@ -1,9 +1,9 @@
|
|||
// Code generated by MockGen. DO NOT EDIT.
|
||||
// Source: github.com/evcc-io/evcc/api (interfaces: Charger,ChargeState,CurrentLimiter,CurrentGetter,PhaseSwitcher,PhaseGetter,FeatureDescriber,Identifier,Meter,MeterEnergy,PhaseCurrents,Vehicle,ConnectionTimer,ChargeRater,Battery,BatteryController,BatterySocLimiter,Circuit,Dimmer,Tariff)
|
||||
// Source: github.com/evcc-io/evcc/api (interfaces: Charger,ChargeState,CurrentLimiter,CurrentGetter,PhaseSwitcher,PhaseGetter,FeatureDescriber,Identifier,Meter,MeterImport,MeterExport,PhaseCurrents,Vehicle,ConnectionTimer,ChargeRater,Battery,BatteryController,BatterySocLimiter,Circuit,Dimmer,Tariff)
|
||||
//
|
||||
// Generated by this command:
|
||||
//
|
||||
// mockgen -package api -destination mock.go github.com/evcc-io/evcc/api Charger,ChargeState,CurrentLimiter,CurrentGetter,PhaseSwitcher,PhaseGetter,FeatureDescriber,Identifier,Meter,MeterEnergy,PhaseCurrents,Vehicle,ConnectionTimer,ChargeRater,Battery,BatteryController,BatterySocLimiter,Circuit,Dimmer,Tariff
|
||||
// mockgen -package api -destination mock.go github.com/evcc-io/evcc/api Charger,ChargeState,CurrentLimiter,CurrentGetter,PhaseSwitcher,PhaseGetter,FeatureDescriber,Identifier,Meter,MeterImport,MeterExport,PhaseCurrents,Vehicle,ConnectionTimer,ChargeRater,Battery,BatteryController,BatterySocLimiter,Circuit,Dimmer,Tariff
|
||||
//
|
||||
|
||||
// Package api is a generated GoMock package.
|
||||
|
|
@ -409,43 +409,82 @@ func (mr *MockMeterMockRecorder) CurrentPower() *gomock.Call {
|
|||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CurrentPower", reflect.TypeOf((*MockMeter)(nil).CurrentPower))
|
||||
}
|
||||
|
||||
// MockMeterEnergy is a mock of MeterEnergy interface.
|
||||
type MockMeterEnergy struct {
|
||||
// MockMeterImport is a mock of MeterImport interface.
|
||||
type MockMeterImport struct {
|
||||
ctrl *gomock.Controller
|
||||
recorder *MockMeterEnergyMockRecorder
|
||||
recorder *MockMeterImportMockRecorder
|
||||
isgomock struct{}
|
||||
}
|
||||
|
||||
// MockMeterEnergyMockRecorder is the mock recorder for MockMeterEnergy.
|
||||
type MockMeterEnergyMockRecorder struct {
|
||||
mock *MockMeterEnergy
|
||||
// MockMeterImportMockRecorder is the mock recorder for MockMeterImport.
|
||||
type MockMeterImportMockRecorder struct {
|
||||
mock *MockMeterImport
|
||||
}
|
||||
|
||||
// NewMockMeterEnergy creates a new mock instance.
|
||||
func NewMockMeterEnergy(ctrl *gomock.Controller) *MockMeterEnergy {
|
||||
mock := &MockMeterEnergy{ctrl: ctrl}
|
||||
mock.recorder = &MockMeterEnergyMockRecorder{mock}
|
||||
// NewMockMeterImport creates a new mock instance.
|
||||
func NewMockMeterImport(ctrl *gomock.Controller) *MockMeterImport {
|
||||
mock := &MockMeterImport{ctrl: ctrl}
|
||||
mock.recorder = &MockMeterImportMockRecorder{mock}
|
||||
return mock
|
||||
}
|
||||
|
||||
// EXPECT returns an object that allows the caller to indicate expected use.
|
||||
func (m *MockMeterEnergy) EXPECT() *MockMeterEnergyMockRecorder {
|
||||
func (m *MockMeterImport) EXPECT() *MockMeterImportMockRecorder {
|
||||
return m.recorder
|
||||
}
|
||||
|
||||
// TotalEnergy mocks base method.
|
||||
func (m *MockMeterEnergy) TotalEnergy() (float64, error) {
|
||||
// ImportEnergy mocks base method.
|
||||
func (m *MockMeterImport) ImportEnergy() (float64, error) {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "TotalEnergy")
|
||||
ret := m.ctrl.Call(m, "ImportEnergy")
|
||||
ret0, _ := ret[0].(float64)
|
||||
ret1, _ := ret[1].(error)
|
||||
return ret0, ret1
|
||||
}
|
||||
|
||||
// TotalEnergy indicates an expected call of TotalEnergy.
|
||||
func (mr *MockMeterEnergyMockRecorder) TotalEnergy() *gomock.Call {
|
||||
// ImportEnergy indicates an expected call of ImportEnergy.
|
||||
func (mr *MockMeterImportMockRecorder) ImportEnergy() *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "TotalEnergy", reflect.TypeOf((*MockMeterEnergy)(nil).TotalEnergy))
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ImportEnergy", reflect.TypeOf((*MockMeterImport)(nil).ImportEnergy))
|
||||
}
|
||||
|
||||
// MockMeterExport is a mock of MeterExport interface.
|
||||
type MockMeterExport struct {
|
||||
ctrl *gomock.Controller
|
||||
recorder *MockMeterExportMockRecorder
|
||||
isgomock struct{}
|
||||
}
|
||||
|
||||
// MockMeterExportMockRecorder is the mock recorder for MockMeterExport.
|
||||
type MockMeterExportMockRecorder struct {
|
||||
mock *MockMeterExport
|
||||
}
|
||||
|
||||
// NewMockMeterExport creates a new mock instance.
|
||||
func NewMockMeterExport(ctrl *gomock.Controller) *MockMeterExport {
|
||||
mock := &MockMeterExport{ctrl: ctrl}
|
||||
mock.recorder = &MockMeterExportMockRecorder{mock}
|
||||
return mock
|
||||
}
|
||||
|
||||
// EXPECT returns an object that allows the caller to indicate expected use.
|
||||
func (m *MockMeterExport) EXPECT() *MockMeterExportMockRecorder {
|
||||
return m.recorder
|
||||
}
|
||||
|
||||
// ExportEnergy mocks base method.
|
||||
func (m *MockMeterExport) ExportEnergy() (float64, error) {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "ExportEnergy")
|
||||
ret0, _ := ret[0].(float64)
|
||||
ret1, _ := ret[1].(error)
|
||||
return ret0, ret1
|
||||
}
|
||||
|
||||
// ExportEnergy indicates an expected call of ExportEnergy.
|
||||
func (mr *MockMeterExportMockRecorder) ExportEnergy() *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ExportEnergy", reflect.TypeOf((*MockMeterExport)(nil).ExportEnergy))
|
||||
}
|
||||
|
||||
// MockPhaseCurrents is a mock of PhaseCurrents interface.
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue