From b2e456b25a65004605c8ba0b4276ea9a3b9a79a7 Mon Sep 17 00:00:00 2001 From: andig Date: Sun, 10 May 2026 10:54:03 +0200 Subject: [PATCH 1/5] Reapply "api: split MeterEnergy into MeterImport and MeterExport (#29788)" This reverts commit d5e6253d35586d3f48c5c30366b67ca404ac2c61. --- api/api.go | 13 +++-- api/capable_test.go | 44 ++++++++--------- api/implement/implementations.go | 29 ++++++++--- api/mock.go | 77 ++++++++++++++++++++++-------- charger/_blueprint.go | 6 +-- charger/abb.go | 2 +- charger/abl-em4.go | 6 +-- charger/alfen.go | 6 +-- charger/alpitronic.go | 6 +-- charger/amperfied.go | 6 +-- charger/bender.go | 4 +- charger/cfos.go | 4 +- charger/charger.go | 4 +- charger/compleo.go | 2 +- charger/connectiq.go | 6 +-- charger/dadapower.go | 6 +-- charger/daheimladen.go | 6 +-- charger/e3dc.go | 6 +-- charger/easee.go | 6 +-- charger/easee_test.go | 12 ++--- charger/em2go-duo.go | 6 +-- charger/em2go.go | 6 +-- charger/eprowallbox.go | 6 +-- charger/etek.go | 4 +- charger/evecube.go | 6 +-- charger/evsemaster.go | 8 ++-- charger/evsewifi.go | 4 +- charger/evsewifi_test.go | 4 +- charger/fritzdect.go | 8 ++-- charger/go-e.go | 6 +-- charger/go-e_test.go | 4 +- charger/hardybarth-ecb1.go | 6 +-- charger/hardybarth-salia.go | 4 +- charger/heatpump.go | 4 +- charger/heidelberg-ec.go | 6 +-- charger/hesotec.go | 6 +-- charger/homeassistant.go | 2 +- charger/homematic.go | 8 ++-- charger/homewizard.go | 8 ++-- charger/innogy.go | 4 +- charger/kathrein.go | 6 +-- charger/keba-modbus.go | 4 +- charger/keba-udp.go | 4 +- charger/kse.go | 8 ++-- charger/lektrico.go | 6 +-- charger/measurement/energy.go | 26 ++++++++-- charger/mennekes-compact.go | 8 ++-- charger/mennekes-hcc3.go | 2 +- charger/nexblue.go | 6 +-- charger/nrgble_linux.go | 6 +-- charger/nrgconnect.go | 6 +-- charger/nrggen2.go | 6 +-- charger/ocpp.go | 2 +- charger/ocpp/connector.go | 2 +- charger/ocpp/connector_test.go | 18 +++---- charger/openevse.go | 6 +-- charger/openwb-2.0.go | 6 +-- charger/openwb-pro.go | 6 +-- charger/openwb.go | 6 +-- charger/pcelectric.go | 4 +- charger/peblar.go | 4 +- charger/phoenix-charx.go | 4 +- charger/phoenix-em-eth.go | 4 +- charger/phoenix-ev-eth.go | 4 +- charger/pulsatrix.go | 6 +-- charger/schneider-v3.go | 6 +-- charger/sgready-relay.go | 4 +- charger/sgready.go | 4 +- charger/shelly-topac.go | 6 +-- charger/shelly.go | 8 ++-- charger/sigenergy.go | 6 +-- charger/smaevcharger.go | 6 +-- charger/smart-evse.go | 4 +- charger/smartevse.go | 6 +-- charger/solax.go | 6 +-- charger/sungrow.go | 6 +-- charger/switchsocket.go | 2 +- charger/tasmota.go | 8 ++-- charger/tplink.go | 8 ++-- charger/versicharge.go | 6 +-- charger/vestel.go | 6 +-- charger/victron.go | 2 +- charger/warp-ws.go | 2 +- charger/warp2-mqtt.go | 4 +- charger/webasto-next.go | 6 +-- "charger/weidm\303\274ller.go" | 4 +- cmd/dumper.go | 13 +++-- cmd/implement/implement.go | 3 +- core/capable_test.go | 14 +++--- core/loadpoint.go | 6 +-- core/loadpoint_session.go | 4 +- core/loadpoint_session_test.go | 42 ++++++++-------- core/metrics/accumulator_test.go | 4 +- core/site.go | 23 ++++++--- core/wrapper/chargerater.go | 28 +++++------ core/wrapper/chargerater_test.go | 32 ++++++------- meter/_blueprint.go | 6 +-- meter/cfos.go | 6 +-- meter/danfoss.go | 2 +- meter/discovergy.go | 4 +- meter/dsmr.go | 4 +- meter/eebus.go | 4 +- meter/fritz/aha/aha.go | 6 +-- meter/fritz/api.go | 2 +- meter/fritz/smarthome/smarthome.go | 6 +-- meter/homeassistant.go | 2 +- meter/homematic.go | 10 ++-- meter/homematic/connection.go | 8 ++-- meter/homewizard.go | 8 ++-- meter/homewizard/connection.go | 4 +- meter/lgess.go | 4 +- meter/mbmd.go | 2 +- meter/measurement/energy.go | 34 +++++++++++-- meter/meter.go | 5 +- meter/meter_average.go | 17 +++++-- meter/powerwall.go | 10 ++-- meter/rct.go | 4 +- meter/shelly/connection.go | 2 +- meter/shelly/gen1.go | 2 +- meter/shelly/gen2.go | 4 +- meter/sma.go | 8 ++-- meter/tasmota.go | 8 ++-- meter/tasmota/connection.go | 4 +- meter/tplink/connection.go | 4 +- meter/tq-em.go | 4 +- meter/tq-em420.go | 4 +- plugin/meter.go | 10 ++-- server/http_config_helper.go | 7 ++- 128 files changed, 570 insertions(+), 435 deletions(-) diff --git a/api/api.go b/api/api.go index d92a7e65bd0..432b4c8ca91 100644 --- a/api/api.go +++ b/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 diff --git a/api/capable_test.go b/api/capable_test.go index 565dd596e80..457da528ce2 100644 --- a/api/capable_test.go +++ b/api/capable_test.go @@ -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) } diff --git a/api/implement/implementations.go b/api/implement/implementations.go index efe9cf65793..697e3a32e49 100644 --- a/api/implement/implementations.go +++ b/api/implement/implementations.go @@ -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 { diff --git a/api/mock.go b/api/mock.go index 4591c1e3866..8cd83f0d394 100644 --- a/api/mock.go +++ b/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. diff --git a/charger/_blueprint.go b/charger/_blueprint.go index f448cf711b1..b2e3fa624ce 100644 --- a/charger/_blueprint.go +++ b/charger/_blueprint.go @@ -95,10 +95,10 @@ func (wb *Blueprint) ChargedEnergy() (float64, error) { return 0, api.ErrNotAvailable } -var _ api.MeterEnergy = (*Blueprint)(nil) +var _ api.MeterImport = (*Blueprint)(nil) -// TotalEnergy implements the api.MeterEnergy interface -func (wb *Blueprint) TotalEnergy() (float64, error) { +// ImportEnergy implements the api.MeterImport interface +func (wb *Blueprint) ImportEnergy() (float64, error) { return 0, api.ErrNotAvailable } diff --git a/charger/abb.go b/charger/abb.go index 294d37a46b3..c698c2ab96f 100644 --- a/charger/abb.go +++ b/charger/abb.go @@ -207,7 +207,7 @@ func (wb *ABB) CurrentPower() (float64, error) { var _ api.ChargeRater = (*ABB)(nil) -// ChargedEnergy implements the api.MeterEnergy interface +// ChargedEnergy implements the api.MeterImport interface func (wb *ABB) ChargedEnergy() (float64, error) { b, err := wb.conn.ReadHoldingRegisters(abbRegEnergy, 2) if err != nil { diff --git a/charger/abl-em4.go b/charger/abl-em4.go index 38e93109fea..4c5b033a5cd 100644 --- a/charger/abl-em4.go +++ b/charger/abl-em4.go @@ -197,10 +197,10 @@ func (wb *AblEm4) CurrentPower() (float64, error) { return float64(binary.BigEndian.Uint32(b)), nil } -var _ api.MeterEnergy = (*AblEm4)(nil) +var _ api.MeterImport = (*AblEm4)(nil) -// totalEnergy implements the api.MeterEnergy interface -func (wb *AblEm4) TotalEnergy() (float64, error) { +// totalEnergy implements the api.MeterImport interface +func (wb *AblEm4) ImportEnergy() (float64, error) { b, err := wb.conn.ReadHoldingRegisters(wb.base+abl4RegEnergy, 2) if err != nil { return 0, err diff --git a/charger/alfen.go b/charger/alfen.go index 46d41ae39a0..1da47d51df4 100644 --- a/charger/alfen.go +++ b/charger/alfen.go @@ -210,10 +210,10 @@ func (wb *Alfen) CurrentPower() (float64, error) { return rs485.RTUIeee754ToFloat64(b), err } -var _ api.MeterEnergy = (*Alfen)(nil) +var _ api.MeterImport = (*Alfen)(nil) -// TotalEnergy implements the api.MeterEnergy interface -func (wb *Alfen) TotalEnergy() (float64, error) { +// ImportEnergy implements the api.MeterImport interface +func (wb *Alfen) ImportEnergy() (float64, error) { b, err := wb.conn.ReadHoldingRegisters(alfenRegEnergy, 4) if err != nil { return 0, err diff --git a/charger/alpitronic.go b/charger/alpitronic.go index 13f8e2e4009..1e5239411dc 100644 --- a/charger/alpitronic.go +++ b/charger/alpitronic.go @@ -211,10 +211,10 @@ func (wb *AlpitronicHYC) ChargedEnergy() (float64, error) { return float64(encoding.Uint16(b)) / 100, err } -var _ api.MeterEnergy = (*AlpitronicHYC)(nil) +var _ api.MeterImport = (*AlpitronicHYC)(nil) -// TotalEnergy implements the api.MeterEnergy interface -func (wb *AlpitronicHYC) TotalEnergy() (float64, error) { +// ImportEnergy implements the api.MeterImport interface +func (wb *AlpitronicHYC) ImportEnergy() (float64, error) { b, err := wb.conn.ReadInputRegisters(wb.reg(hycRegTotalChargedEnergy), 4) if err != nil { return 0, err diff --git a/charger/amperfied.go b/charger/amperfied.go index ab231775454..edc2c834f20 100644 --- a/charger/amperfied.go +++ b/charger/amperfied.go @@ -255,10 +255,10 @@ func (wb *Amperfied) CurrentPower() (float64, error) { return float64(binary.BigEndian.Uint16(b)), nil } -var _ api.MeterEnergy = (*Amperfied)(nil) +var _ api.MeterImport = (*Amperfied)(nil) -// TotalEnergy implements the api.MeterEnergy interface -func (wb *Amperfied) TotalEnergy() (float64, error) { +// ImportEnergy implements the api.MeterImport interface +func (wb *Amperfied) ImportEnergy() (float64, error) { b, err := wb.conn.ReadInputRegisters(ampRegEnergy, 2) if err != nil { return 0, err diff --git a/charger/bender.go b/charger/bender.go index 8c68186ea33..28438fce67b 100644 --- a/charger/bender.go +++ b/charger/bender.go @@ -148,7 +148,7 @@ func NewBenderCC(ctx context.Context, uri string, id uint8, cache time.Duration) if b, err := wb.conn.ReadHoldingRegisters(reg, 2); err == nil && binary.BigEndian.Uint32(b) != math.MaxUint32 { implement.Has(wb, implement.Meter(wb.currentPower)) implement.Has(wb, implement.PhaseCurrents(wb.currents)) - implement.Has(wb, implement.MeterEnergy(wb.totalEnergy)) + implement.Has(wb, implement.MeterImport(wb.totalEnergy)) // check presence of "ocpp meter" if b, err := wb.conn.ReadHoldingRegisters(bendRegVoltages, 2); err == nil && binary.BigEndian.Uint32(b) > 0 { @@ -374,7 +374,7 @@ func (wb *BenderCC) currentPower() (float64, error) { // removed: https://github.com/evcc-io/evcc/issues/13726 // var _ api.ChargeRater = (*BenderCC)(nil) -// TotalEnergy implements the api.MeterEnergy interface +// ImportEnergy implements the api.MeterImport interface func (wb *BenderCC) totalEnergy() (float64, error) { if wb.legacy { b, err := wb.conn.ReadHoldingRegisters(bendRegPhaseEnergy, 6) diff --git a/charger/cfos.go b/charger/cfos.go index 370ad3ce87c..959527b379e 100644 --- a/charger/cfos.go +++ b/charger/cfos.go @@ -77,7 +77,7 @@ func NewCfosPowerBrain(ctx context.Context, uri string, id uint8) (api.Charger, // decorate meter if b, err := wb.conn.ReadHoldingRegisters(cfosRegMeter, 1); err == nil && binary.BigEndian.Uint16(b) != 0 { implement.Has(wb, implement.Meter(wb.currentPower)) - implement.Has(wb, implement.MeterEnergy(wb.totalEnergy)) + implement.Has(wb, implement.MeterImport(wb.totalEnergy)) if b, err := wb.conn.ReadHoldingRegisters(cfosRegMeterFlags, 1); err == nil && binary.BigEndian.Uint16(b) != 0 { implement.Has(wb, implement.PhaseCurrents(wb.currents)) @@ -156,7 +156,7 @@ func (wb *CfosPowerBrain) currentPower() (float64, error) { return float64(binary.BigEndian.Uint32(b)), nil } -// totalEnergy implements the api.MeterEnergy interface +// totalEnergy implements the api.MeterImport interface func (wb *CfosPowerBrain) totalEnergy() (float64, error) { b, err := wb.conn.ReadHoldingRegisters(cfosRegEnergy, 4) if err != nil { diff --git a/charger/charger.go b/charger/charger.go index bf7a6f1cbb5..42257d9d36e 100644 --- a/charger/charger.go +++ b/charger/charger.go @@ -145,12 +145,12 @@ func NewConfigurableFromConfig(ctx context.Context, other map[string]any) (api.C implement.May(c, implement.SocLimiter(limitsoc)) // decorate measurements - powerG, energyG, err := cc.Energy.Configure(ctx) + powerG, importG, _, err := cc.Energy.Configure(ctx) if err != nil { return nil, err } implement.May(c, implement.Meter(powerG)) - implement.May(c, implement.MeterEnergy(energyG)) + implement.May(c, implement.MeterImport(importG)) currentsG, voltagesG, _, err := cc.Phases.Configure(ctx) if err != nil { diff --git a/charger/compleo.go b/charger/compleo.go index 16d955542c4..2ea3081358a 100644 --- a/charger/compleo.go +++ b/charger/compleo.go @@ -235,7 +235,7 @@ func (wb *Compleo) CurrentPower() (float64, error) { var _ api.ChargeRater = (*Compleo)(nil) -// ChargedEnergy implements the api.MeterEnergy interface +// ChargedEnergy implements the api.MeterImport interface func (wb *Compleo) ChargedEnergy() (float64, error) { b, err := wb.conn.ReadInputRegisters(wb.reg(compleoRegEnergy), 1) if err != nil { diff --git a/charger/connectiq.go b/charger/connectiq.go index 1c629e4a306..28e3006f966 100644 --- a/charger/connectiq.go +++ b/charger/connectiq.go @@ -162,10 +162,10 @@ func (wb *ConnectIq) CurrentPower() (float64, error) { return (res.Pow[0] + res.Pow[1] + res.Pow[2]) * 1e3, nil } -var _ api.MeterEnergy = (*ConnectIq)(nil) +var _ api.MeterImport = (*ConnectIq)(nil) -// TotalEnergy implements the api.MeterEnergy interface -func (wb *ConnectIq) TotalEnergy() (float64, error) { +// ImportEnergy implements the api.MeterImport interface +func (wb *ConnectIq) ImportEnergy() (float64, error) { var res connectiq.MeterRead uri := fmt.Sprintf("%s/meter/read", wb.uri) err := wb.GetJSON(uri, &res) diff --git a/charger/dadapower.go b/charger/dadapower.go index 20babd9fbd5..de881eb9d06 100644 --- a/charger/dadapower.go +++ b/charger/dadapower.go @@ -184,10 +184,10 @@ func (wb *Dadapower) CurrentPower() (float64, error) { return float64(binary.BigEndian.Uint32(b)), err } -var _ api.MeterEnergy = (*Dadapower)(nil) +var _ api.MeterImport = (*Dadapower)(nil) -// TotalEnergy implements the api.MeterEnergy interface -func (wb *Dadapower) TotalEnergy() (float64, error) { +// ImportEnergy implements the api.MeterImport interface +func (wb *Dadapower) ImportEnergy() (float64, error) { b, err := wb.conn.ReadInputRegisters(dadapowerRegEnergyImportTotal+wb.regOffset, 4) if err != nil { return 0, err diff --git a/charger/daheimladen.go b/charger/daheimladen.go index ad5f469e90a..5da18df3890 100644 --- a/charger/daheimladen.go +++ b/charger/daheimladen.go @@ -250,10 +250,10 @@ func (wb *DaheimLaden) CurrentPower() (float64, error) { return float64(binary.BigEndian.Uint32(b)), err } -var _ api.MeterEnergy = (*DaheimLaden)(nil) +var _ api.MeterImport = (*DaheimLaden)(nil) -// TotalEnergy implements the api.MeterEnergy interface -func (wb *DaheimLaden) TotalEnergy() (float64, error) { +// ImportEnergy implements the api.MeterImport interface +func (wb *DaheimLaden) ImportEnergy() (float64, error) { b, err := wb.conn.ReadHoldingRegisters(dlRegTotalEnergy, 2) if err != nil { return 0, err diff --git a/charger/e3dc.go b/charger/e3dc.go index f04b79d3ecc..ce1c2c688f9 100644 --- a/charger/e3dc.go +++ b/charger/e3dc.go @@ -362,9 +362,9 @@ func (wb *E3dc) CurrentPower() (float64, error) { return p1 + p2 + p3, nil } -var _ api.MeterEnergy = (*E3dc)(nil) +var _ api.MeterImport = (*E3dc)(nil) -// TotalEnergy implements the api.MeterEnergy interface +// ImportEnergy implements the api.MeterImport interface // // E3DC stores wallbox energy in two separate counters that must be added: // - DB_TEC_WALLBOX_ENERGYALL: Historical energy stored in the database (persisted) @@ -372,7 +372,7 @@ var _ api.MeterEnergy = (*E3dc)(nil) // // The sum of both values matches the total energy shown in the E3DC portal. // Testing showed: DB_TEC (8319 kWh) + WB_ENERGY (699 kWh) = 9018 kWh ≈ Portal (9019 kWh) -func (wb *E3dc) TotalEnergy() (float64, error) { +func (wb *E3dc) ImportEnergy() (float64, error) { // Query both energy sources sequentially res, err := wb.conn.Send(*rscp.NewMessage(rscp.DB_REQ_TEC_WALLBOX_VALUES, nil)) if err != nil { diff --git a/charger/easee.go b/charger/easee.go index 5c6f9cdf85f..e791a80c9ce 100644 --- a/charger/easee.go +++ b/charger/easee.go @@ -717,10 +717,10 @@ func (c *Easee) Currents() (float64, float64, float64, error) { return c.currentL1, c.currentL2, c.currentL3, nil } -var _ api.MeterEnergy = (*Easee)(nil) +var _ api.MeterImport = (*Easee)(nil) -// TotalEnergy implements the api.MeterEnergy interface -func (c *Easee) TotalEnergy() (float64, error) { +// ImportEnergy implements the api.MeterImport interface +func (c *Easee) ImportEnergy() (float64, error) { c.mux.RLock() defer c.mux.RUnlock() // updates for this are only sent once an hour, so inaccurate by design diff --git a/charger/easee_test.go b/charger/easee_test.go index a9905ad4294..f85fc950dc0 100644 --- a/charger/easee_test.go +++ b/charger/easee_test.go @@ -563,7 +563,7 @@ func TestChargeSessionStart_SetsFields(t *testing.T) { assert.Equal(t, 801, e.currentSessionID) - total, err := e.TotalEnergy() + total, err := e.ImportEnergy() assert.NoError(t, err) assert.Equal(t, 9141.414622, total) } @@ -581,7 +581,7 @@ func TestChargingSession_UpdatesBothWhenIdMatches(t *testing.T) { assert.NoError(t, err) assert.Equal(t, 16.2, charged) - total, err := e.TotalEnergy() + total, err := e.ImportEnergy() assert.NoError(t, err) assert.Equal(t, 9173.5, total) } @@ -600,7 +600,7 @@ func TestChargingSession_MismatchedId_ProtectsSessionEnergy(t *testing.T) { assert.NoError(t, err) assert.Equal(t, 5.0, charged) // sessionEnergy unchanged - total, err := e.TotalEnergy() + total, err := e.ImportEnergy() assert.NoError(t, err) assert.Equal(t, 9173.5, total) // totalEnergy updated } @@ -619,18 +619,18 @@ func TestChargingSession_AtStartup_ProtectsSessionEnergy(t *testing.T) { assert.NoError(t, err) assert.Equal(t, 0.0, charged) // sessionEnergy protected (Id 803 != 0) - total, err := e.TotalEnergy() + total, err := e.ImportEnergy() assert.NoError(t, err) assert.Equal(t, 9173.5, total) // totalEnergy updated } -func TestLifetimeEnergy_DoesNotDecreaseTotalEnergy(t *testing.T) { +func TestLifetimeEnergy_DoesNotDecreaseImportEnergy(t *testing.T) { e := newEasee() e.totalEnergy = 9173.5 e.ProductUpdate(createPayload(easee.LIFETIME_ENERGY, time.Now(), easee.Double, "9170.0")) - total, err := e.TotalEnergy() + total, err := e.ImportEnergy() assert.NoError(t, err) assert.Equal(t, 9173.5, total) } diff --git a/charger/em2go-duo.go b/charger/em2go-duo.go index f14df296ddb..d54d332511d 100644 --- a/charger/em2go-duo.go +++ b/charger/em2go-duo.go @@ -216,10 +216,10 @@ func (wb *Em2GoDuo) CurrentPower() (float64, error) { return float64(binary.BigEndian.Uint32(b)), nil } -var _ api.MeterEnergy = (*Em2GoDuo)(nil) +var _ api.MeterImport = (*Em2GoDuo)(nil) -// TotalEnergy implements the api.MeterEnergy interface -func (wb *Em2GoDuo) TotalEnergy() (float64, error) { +// ImportEnergy implements the api.MeterImport interface +func (wb *Em2GoDuo) ImportEnergy() (float64, error) { b, err := wb.conn.ReadHoldingRegisters(wb.base+em2GoDuoRegConEnergy, 2) if err != nil { return 0, err diff --git a/charger/em2go.go b/charger/em2go.go index d4bb5635e42..70c675ef22c 100644 --- a/charger/em2go.go +++ b/charger/em2go.go @@ -288,10 +288,10 @@ func (wb *Em2Go) CurrentPower() (float64, error) { return rs485.RTUUint32ToFloat64(b), nil } -var _ api.MeterEnergy = (*Em2Go)(nil) +var _ api.MeterImport = (*Em2Go)(nil) -// TotalEnergy implements the api.MeterEnergy interface -func (wb *Em2Go) TotalEnergy() (float64, error) { +// ImportEnergy implements the api.MeterImport interface +func (wb *Em2Go) ImportEnergy() (float64, error) { b, err := wb.conn.ReadHoldingRegisters(em2GoRegEnergy, 2) if err != nil { return 0, err diff --git a/charger/eprowallbox.go b/charger/eprowallbox.go index c857d5791d3..b9b412acdab 100644 --- a/charger/eprowallbox.go +++ b/charger/eprowallbox.go @@ -186,10 +186,10 @@ func (wb *EProWallbox) CurrentPower() (float64, error) { return l1 + l2 + l3, err } -var _ api.MeterEnergy = (*EProWallbox)(nil) +var _ api.MeterImport = (*EProWallbox)(nil) -// TotalEnergy implements the api.MeterEnergy interface -func (wb *EProWallbox) TotalEnergy() (float64, error) { +// ImportEnergy implements the api.MeterImport interface +func (wb *EProWallbox) ImportEnergy() (float64, error) { l1, l2, l3, err := wb.getPhaseValues(eproRegActiveEnergies, 1000) return -(l1 + l2 + l3), err } diff --git a/charger/etek.go b/charger/etek.go index 14e1b23855e..1c2d610b327 100644 --- a/charger/etek.go +++ b/charger/etek.go @@ -95,7 +95,7 @@ func NewEtekFromConfig(ctx context.Context, other map[string]any) (api.Charger, // Check energy register (95) if b, err := wb.conn.ReadHoldingRegisters(etekRegMeterEnergyAddr, 1); err == nil { if binary.BigEndian.Uint16(b) != etekRegInvalidMeterAddr { - implement.Has(wb, implement.MeterEnergy(wb.totalEnergy)) + implement.Has(wb, implement.MeterImport(wb.totalEnergy)) } } @@ -246,7 +246,7 @@ func (wb *Etek) currentPower() (float64, error) { return float64(binary.BigEndian.Uint16(b)), nil } -// totalEnergy implements the api.MeterEnergy interface +// totalEnergy implements the api.MeterImport interface func (wb *Etek) totalEnergy() (float64, error) { b, err := wb.conn.ReadHoldingRegisters(etekRegEnergy, 2) if err != nil { diff --git a/charger/evecube.go b/charger/evecube.go index 06582c1f997..69f94c3ea63 100644 --- a/charger/evecube.go +++ b/charger/evecube.go @@ -314,10 +314,10 @@ func (wb *EVECUBE) CurrentPower() (float64, error) { return status.Voltage * status.Current, nil } -var _ api.MeterEnergy = (*EVECUBE)(nil) +var _ api.MeterImport = (*EVECUBE)(nil) -// TotalEnergy implements the api.MeterEnergy interface -func (wb *EVECUBE) TotalEnergy() (float64, error) { +// ImportEnergy implements the api.MeterImport interface +func (wb *EVECUBE) ImportEnergy() (float64, error) { status, err := wb.getStatus() if err != nil { return 0, err diff --git a/charger/evsemaster.go b/charger/evsemaster.go index 7cd0ec44349..af454aa5370 100644 --- a/charger/evsemaster.go +++ b/charger/evsemaster.go @@ -45,7 +45,7 @@ const ( evsemasterConnectTimeout = 15 * time.Second ) -// EVSEMaster implements api.Charger (and api.Meter / api.MeterEnergy / +// EVSEMaster implements api.Charger (and api.Meter / api.MeterImport / // api.PhaseCurrents / api.PhaseVoltages) for charging stations that use the // EVSE Master UDP protocol – e.g. Sync EV and generic Chinese EVSE devices. // @@ -288,10 +288,10 @@ func (wb *EVSEMaster) CurrentPower() (float64, error) { return res.Power, nil } -var _ api.MeterEnergy = (*EVSEMaster)(nil) +var _ api.MeterImport = (*EVSEMaster)(nil) -// TotalEnergy implements the api.MeterEnergy interface. -func (wb *EVSEMaster) TotalEnergy() (float64, error) { +// ImportEnergy implements the api.MeterImport interface. +func (wb *EVSEMaster) ImportEnergy() (float64, error) { res, err := wb.data.Get() if err != nil { return 0, err diff --git a/charger/evsewifi.go b/charger/evsewifi.go index 498385146ca..691f4fae8ae 100644 --- a/charger/evsewifi.go +++ b/charger/evsewifi.go @@ -76,7 +76,7 @@ func NewEVSEWifiFromConfig(other map[string]any) (api.Charger, error) { } if cc.Meter.Energy { - implement.Has(wb, implement.MeterEnergy(wb.totalEnergy)) + implement.Has(wb, implement.MeterImport(wb.totalEnergy)) } if cc.Meter.Currents { @@ -210,7 +210,7 @@ func (wb *EVSEWifi) currentPower() (float64, error) { return 1000 * params.ActualPower, err } -// TotalEnergy implements the api.MeterEnergy interface +// ImportEnergy implements the api.MeterImport interface func (wb *EVSEWifi) totalEnergy() (float64, error) { params, err := wb.paramG.Get() return params.MeterReading, err diff --git a/charger/evsewifi_test.go b/charger/evsewifi_test.go index 03fd1cd2f2e..e2e4a276fe0 100644 --- a/charger/evsewifi_test.go +++ b/charger/evsewifi_test.go @@ -32,8 +32,8 @@ func TestEvseWifi(t *testing.T) { t.Error("missing api.Meter") } - if _, ok := api.Cap[api.MeterEnergy](wb); !ok { - t.Error("missing api.MeterEnergy") + if _, ok := api.Cap[api.MeterImport](wb); !ok { + t.Error("missing api.MeterImport") } if _, ok := api.Cap[api.PhaseCurrents](wb); !ok { diff --git a/charger/fritzdect.go b/charger/fritzdect.go index f49462bc1d6..e50d42181e8 100644 --- a/charger/fritzdect.go +++ b/charger/fritzdect.go @@ -92,9 +92,9 @@ func (c *FritzDECT) Enable(enable bool) error { return c.conn.SwitchOff() } -var _ api.MeterEnergy = (*FritzDECT)(nil) +var _ api.MeterImport = (*FritzDECT)(nil) -// TotalEnergy implements the api.MeterEnergy interface -func (c *FritzDECT) TotalEnergy() (float64, error) { - return c.conn.TotalEnergy() +// ImportEnergy implements the api.MeterImport interface +func (c *FritzDECT) ImportEnergy() (float64, error) { + return c.conn.ImportEnergy() } diff --git a/charger/go-e.go b/charger/go-e.go index 9597e2b36fa..058836b5b1d 100644 --- a/charger/go-e.go +++ b/charger/go-e.go @@ -207,10 +207,10 @@ func (c *GoE) Identify() (string, error) { return resp.Identify(), nil } -var _ api.MeterEnergy = (*GoE)(nil) +var _ api.MeterImport = (*GoE)(nil) -// totalEnergy implements the api.MeterEnergy interface - v2 only -func (c *GoE) TotalEnergy() (float64, error) { +// totalEnergy implements the api.MeterImport interface - v2 only +func (c *GoE) ImportEnergy() (float64, error) { resp, err := c.api.Status() if err != nil { return 0, err diff --git a/charger/go-e_test.go b/charger/go-e_test.go index ed77a96ff6f..2c0c4af4de2 100644 --- a/charger/go-e_test.go +++ b/charger/go-e_test.go @@ -78,8 +78,8 @@ func TestGoEV2(t *testing.T) { t.Error("missing Identifier api") } - if _, ok := api.Cap[api.MeterEnergy](wb); !ok { - t.Error("missing MeterEnergy api") + if _, ok := api.Cap[api.MeterImport](wb); !ok { + t.Error("missing MeterImport api") } if _, ok := api.Cap[api.PhaseSwitcher](wb); !ok { diff --git a/charger/hardybarth-ecb1.go b/charger/hardybarth-ecb1.go index fb5bdd75233..47bb74beaaa 100644 --- a/charger/hardybarth-ecb1.go +++ b/charger/hardybarth-ecb1.go @@ -206,10 +206,10 @@ func (wb *HardyBarth) CurrentPower() (float64, error) { return res.Data[obis.PowerConsumption], nil } -var _ api.MeterEnergy = (*HardyBarth)(nil) +var _ api.MeterImport = (*HardyBarth)(nil) -// TotalEnergy implements the api.MeterEnergy interface -func (wb *HardyBarth) TotalEnergy() (float64, error) { +// ImportEnergy implements the api.MeterImport interface +func (wb *HardyBarth) ImportEnergy() (float64, error) { res, err := wb.meterG() if err != nil { return 0, err diff --git a/charger/hardybarth-salia.go b/charger/hardybarth-salia.go index f5d5f66a048..6f7100e087f 100644 --- a/charger/hardybarth-salia.go +++ b/charger/hardybarth-salia.go @@ -141,7 +141,7 @@ func NewSalia(ctx context.Context, uri, user, password string, cache time.Durati if res.Secc.Port0.Metering.Meter.Available > 0 { implement.Has(wb, implement.Meter(wb.currentPower)) - implement.Has(wb, implement.MeterEnergy(wb.totalEnergy)) + implement.Has(wb, implement.MeterImport(wb.totalEnergy)) implement.Has(wb, implement.PhaseCurrents(wb.currents)) } @@ -266,7 +266,7 @@ func (wb *Salia) currentPower() (float64, error) { return res.Secc.Port0.Metering.Power.ActiveTotal.Actual / 10, err } -// totalEnergy implements the api.MeterEnergy interface +// totalEnergy implements the api.MeterImport interface func (wb *Salia) totalEnergy() (float64, error) { res, err := wb.apiG.Get() return res.Secc.Port0.Metering.Energy.ActiveImport.Actual / 1e3, err diff --git a/charger/heatpump.go b/charger/heatpump.go index 40b64caf314..5ca3d1b4930 100644 --- a/charger/heatpump.go +++ b/charger/heatpump.go @@ -80,12 +80,12 @@ func NewHeatpumpFromConfig(ctx context.Context, other map[string]any) (api.Charg return nil, err } - powerG, energyG, err := cc.Energy.Configure(ctx) + powerG, importG, _, err := cc.Energy.Configure(ctx) if err != nil { return nil, err } implement.May(res, implement.Meter(powerG)) - implement.May(res, implement.MeterEnergy(energyG)) + implement.May(res, implement.MeterImport(importG)) tempG, limitTempG, err := cc.Temperature.Configure(ctx) if err != nil { diff --git a/charger/heidelberg-ec.go b/charger/heidelberg-ec.go index cf00828981f..77a0e17ead2 100644 --- a/charger/heidelberg-ec.go +++ b/charger/heidelberg-ec.go @@ -251,10 +251,10 @@ func (wb *HeidelbergEC) CurrentPower() (float64, error) { return float64(binary.BigEndian.Uint16(b)), nil } -var _ api.MeterEnergy = (*HeidelbergEC)(nil) +var _ api.MeterImport = (*HeidelbergEC)(nil) -// TotalEnergy implements the api.MeterEnergy interface -func (wb *HeidelbergEC) TotalEnergy() (float64, error) { +// ImportEnergy implements the api.MeterImport interface +func (wb *HeidelbergEC) ImportEnergy() (float64, error) { b, err := wb.conn.ReadInputRegisters(hecRegEnergy, 2) if err != nil { return 0, err diff --git a/charger/hesotec.go b/charger/hesotec.go index 0cf78d86dee..bc4b607147d 100644 --- a/charger/hesotec.go +++ b/charger/hesotec.go @@ -165,10 +165,10 @@ func (wb *Hesotec) ChargeDuration() (time.Duration, error) { return time.Duration(binary.BigEndian.Uint32(b)) * time.Second, nil } -var _ api.MeterEnergy = (*Hesotec)(nil) +var _ api.MeterImport = (*Hesotec)(nil) -// TotalEnergy implements the api.MeterEnergy interface -func (wb *Hesotec) TotalEnergy() (float64, error) { +// ImportEnergy implements the api.MeterImport interface +func (wb *Hesotec) ImportEnergy() (float64, error) { b, err := wb.conn.ReadHoldingRegisters(hesotecRegEnergy, 2) if err != nil { return 0, err diff --git a/charger/homeassistant.go b/charger/homeassistant.go index 9da621a4f1a..17de107df25 100644 --- a/charger/homeassistant.go +++ b/charger/homeassistant.go @@ -79,7 +79,7 @@ func NewHomeAssistantFromConfig(other map[string]any) (api.Charger, error) { implement.Has(c, implement.Meter(func() (float64, error) { return conn.GetFloatState(cc.Power) })) } if cc.Energy != "" { - implement.Has(c, implement.MeterEnergy(func() (float64, error) { return conn.GetFloatState(cc.Energy) })) + implement.Has(c, implement.MeterImport(func() (float64, error) { return conn.GetFloatState(cc.Energy) })) } // phase currents (optional) diff --git a/charger/homematic.go b/charger/homematic.go index 80ad932cc89..f3b045b640c 100644 --- a/charger/homematic.go +++ b/charger/homematic.go @@ -67,9 +67,9 @@ func (c *CCU) Enable(enable bool) error { return c.conn.Enable(enable) } -var _ api.MeterEnergy = (*CCU)(nil) +var _ api.MeterImport = (*CCU)(nil) -// TotalEnergy implements the api.MeterEnergy interface -func (c *CCU) TotalEnergy() (float64, error) { - return c.conn.TotalEnergy() +// ImportEnergy implements the api.MeterImport interface +func (c *CCU) ImportEnergy() (float64, error) { + return c.conn.ImportEnergy() } diff --git a/charger/homewizard.go b/charger/homewizard.go index 48e3c8c3a96..057c2a64f91 100644 --- a/charger/homewizard.go +++ b/charger/homewizard.go @@ -72,9 +72,9 @@ func (c *HomeWizard) Enable(enable bool) error { return c.conn.Enable(enable) } -var _ api.MeterEnergy = (*HomeWizard)(nil) +var _ api.MeterImport = (*HomeWizard)(nil) -// TotalEnergy implements the api.MeterEnergy interface -func (c *HomeWizard) TotalEnergy() (float64, error) { - return c.conn.TotalEnergy() +// ImportEnergy implements the api.MeterImport interface +func (c *HomeWizard) ImportEnergy() (float64, error) { + return c.conn.ImportEnergy() } diff --git a/charger/innogy.go b/charger/innogy.go index 1626487b41e..f7d105adf0f 100644 --- a/charger/innogy.go +++ b/charger/innogy.go @@ -74,7 +74,7 @@ func NewInnogyFromConfig(ctx context.Context, other map[string]any) (api.Charger // check presence of energy meter & voltages registers if b, err := wb.conn.ReadInputRegisters(igyRegModbusTableVersion, 1); err == nil && binary.BigEndian.Uint16(b) >= 6 { - implement.Has(wb, implement.MeterEnergy(wb.totalEnergy)) + implement.Has(wb, implement.MeterImport(wb.totalEnergy)) implement.Has(wb, implement.PhaseVoltages(wb.voltages)) wb.hasVoltages = true } @@ -226,7 +226,7 @@ func (wb *Innogy) voltages() (float64, float64, float64, error) { return res[0], res[1], res[2], nil } -// totalEnergy implements the api.MeterEnergy interface +// totalEnergy implements the api.MeterImport interface func (wb *Innogy) totalEnergy() (float64, error) { b, err := wb.conn.ReadInputRegisters(igyRegEnergy, 2) if err != nil { diff --git a/charger/kathrein.go b/charger/kathrein.go index f74cd7b2555..e821c702571 100644 --- a/charger/kathrein.go +++ b/charger/kathrein.go @@ -314,10 +314,10 @@ func (wb *Kathrein) Voltages() (float64, float64, float64, error) { // removed since broken, see https://github.com/evcc-io/evcc/pull/25427 // var _ api.ChargeRater = (*Kathrein)(nil) -var _ api.MeterEnergy = (*Kathrein)(nil) +var _ api.MeterImport = (*Kathrein)(nil) -// TotalEnergy implements the api.MeterEnergy interface -func (wb *Kathrein) TotalEnergy() (float64, error) { +// ImportEnergy implements the api.MeterImport interface +func (wb *Kathrein) ImportEnergy() (float64, error) { b, err := wb.conn.ReadHoldingRegisters(kathreinRegTotalEnergy, 2) if err != nil { return 0, err diff --git a/charger/keba-modbus.go b/charger/keba-modbus.go index fbfa3028aa6..91aac97a1df 100644 --- a/charger/keba-modbus.go +++ b/charger/keba-modbus.go @@ -131,7 +131,7 @@ func NewKebaFromConfig(ctx context.Context, other map[string]any) (api.Charger, if hasEnergyMeter { implement.Has(wb, implement.Meter(wb.currentPower)) - implement.Has(wb, implement.MeterEnergy(wb.totalEnergy)) + implement.Has(wb, implement.MeterImport(wb.totalEnergy)) implement.Has(wb, implement.PhaseCurrents(wb.currents)) } @@ -329,7 +329,7 @@ func (wb *Keba) currentPower() (float64, error) { return float64(binary.BigEndian.Uint32(b)) / 1e3, nil } -// totalEnergy implements the api.MeterEnergy interface +// totalEnergy implements the api.MeterImport interface func (wb *Keba) totalEnergy() (float64, error) { b, err := wb.conn.ReadHoldingRegisters(kebaRegEnergy, 2) if err != nil { diff --git a/charger/keba-udp.go b/charger/keba-udp.go index 9e20b0cef0d..734f2617204 100644 --- a/charger/keba-udp.go +++ b/charger/keba-udp.go @@ -61,7 +61,7 @@ func NewKebaUdpFromConfig(other map[string]any) (api.Charger, error) { if energy > 0 { implement.Has(k, implement.Meter(k.currentPower)) - implement.Has(k, implement.MeterEnergy(k.totalEnergy)) + implement.Has(k, implement.MeterImport(k.totalEnergy)) implement.Has(k, implement.PhaseCurrents(k.currents)) } @@ -284,7 +284,7 @@ func (c *KebaUdp) currentPower() (float64, error) { return float64(kr.P) / 1e3, err } -// totalEnergy implements the api.MeterEnergy interface +// totalEnergy implements the api.MeterImport interface func (c *KebaUdp) totalEnergy() (float64, error) { var kr keba.Report3 err := c.roundtrip("report", 3, &kr) diff --git a/charger/kse.go b/charger/kse.go index 8a136a88876..64b3c6bcfab 100644 --- a/charger/kse.go +++ b/charger/kse.go @@ -181,7 +181,7 @@ func (wb *KSE) CurrentPower() (float64, error) { var _ api.ChargeRater = (*KSE)(nil) -// ChargedEnergy implements the api.MeterEnergy interface +// ChargedEnergy implements the api.MeterImport interface func (wb *KSE) ChargedEnergy() (float64, error) { b, err := wb.conn.ReadInputRegisters(kseRegCurrentLoadedEnergy, 1) if err != nil { @@ -191,10 +191,10 @@ func (wb *KSE) ChargedEnergy() (float64, error) { return float64(binary.BigEndian.Uint16(b)) / 100, err } -var _ api.MeterEnergy = (*KSE)(nil) +var _ api.MeterImport = (*KSE)(nil) -// TotalEnergy implements the api.MeterEnergy interface -func (wb *KSE) TotalEnergy() (float64, error) { +// ImportEnergy implements the api.MeterImport interface +func (wb *KSE) ImportEnergy() (float64, error) { b, err := wb.conn.ReadInputRegisters(kseRegEnergy, 2) if err != nil { return 0, err diff --git a/charger/lektrico.go b/charger/lektrico.go index 84878c7e0fd..def1e23aae3 100644 --- a/charger/lektrico.go +++ b/charger/lektrico.go @@ -251,10 +251,10 @@ func (wb *Lektrico) CurrentPower() (float64, error) { return res.InstantPower, err } -var _ api.MeterEnergy = (*Lektrico)(nil) +var _ api.MeterImport = (*Lektrico)(nil) -// TotalEnergy implements the api.MeterEnergy interface -func (wb *Lektrico) TotalEnergy() (float64, error) { +// ImportEnergy implements the api.MeterImport interface +func (wb *Lektrico) ImportEnergy() (float64, error) { res, err := wb.statusG.Get() return res.TotalChargedEnergy, err } diff --git a/charger/measurement/energy.go b/charger/measurement/energy.go index ed173643c6f..5b07ab6727c 100644 --- a/charger/measurement/energy.go +++ b/charger/measurement/energy.go @@ -9,23 +9,39 @@ import ( type Energy struct { Power *plugin.Config // optional - Energy *plugin.Config // optional + Import *plugin.Config // optional + Export *plugin.Config // optional + // Energy is a legacy alias for Import. Chargers always import energy, so + // the alias only feeds the Import getter (unlike meters, which alias to both). + Energy *plugin.Config } func (cc *Energy) Configure(ctx context.Context) ( + func() (float64, error), func() (float64, error), func() (float64, error), error, ) { powerG, err := cc.Power.FloatGetter(ctx) if err != nil { - return nil, nil, fmt.Errorf("power: %w", err) + return nil, nil, nil, fmt.Errorf("power: %w", err) + } + + // legacy: chargers always import, so 'energy' aliases to import only. + importCfg := cc.Import + if cc.Energy != nil && importCfg == nil { + importCfg = cc.Energy + } + + importG, err := importCfg.FloatGetter(ctx) + if err != nil { + return nil, nil, nil, fmt.Errorf("import: %w", err) } - energyG, err := cc.Energy.FloatGetter(ctx) + exportG, err := cc.Export.FloatGetter(ctx) if err != nil { - return nil, nil, fmt.Errorf("energy: %w", err) + return nil, nil, nil, fmt.Errorf("export: %w", err) } - return powerG, energyG, nil + return powerG, importG, exportG, nil } diff --git a/charger/mennekes-compact.go b/charger/mennekes-compact.go index f5e9d5b1aa3..03a4c4fbdf9 100644 --- a/charger/mennekes-compact.go +++ b/charger/mennekes-compact.go @@ -212,10 +212,10 @@ func (wb *MennekesCompact) CurrentPower() (float64, error) { return float64(encoding.Float32(b)), nil } -var _ api.MeterEnergy = (*MennekesCompact)(nil) +var _ api.MeterImport = (*MennekesCompact)(nil) -// TotalEnergy implements the api.MeterEnergy interface -func (wb *MennekesCompact) TotalEnergy() (float64, error) { +// ImportEnergy implements the api.MeterImport interface +func (wb *MennekesCompact) ImportEnergy() (float64, error) { b, err := wb.conn.ReadHoldingRegisters(mennekesRegChargedEnergyTotal, 2) if err != nil { return 0, err @@ -256,7 +256,7 @@ func (wb *MennekesCompact) getPhaseValues(reg uint16) (float64, float64, float64 /* var _ api.ChargeRater = (*MennekesCompact)(nil) -// ChargedEnergy implements the api.MeterEnergy interface +// ChargedEnergy implements the api.MeterImport interface func (wb *MennekesCompact) ChargedEnergy() (float64, error) { b, err := wb.conn.ReadHoldingRegisters(mennekesRegChargedEnergySession, 2) if err != nil { diff --git a/charger/mennekes-hcc3.go b/charger/mennekes-hcc3.go index 8a5fb11f834..ba86304c2be 100644 --- a/charger/mennekes-hcc3.go +++ b/charger/mennekes-hcc3.go @@ -161,7 +161,7 @@ func (wb *MennekesHcc3) CurrentPower() (float64, error) { var _ api.ChargeRater = (*MennekesHcc3)(nil) -// ChargedEnergy implements the api.MeterEnergy interface +// ChargedEnergy implements the api.MeterImport interface func (wb *MennekesHcc3) ChargedEnergy() (float64, error) { b, err := wb.conn.ReadInputRegisters(mennekesHcc3RegEnergy, 2) if err != nil { diff --git a/charger/nexblue.go b/charger/nexblue.go index b8685d9c596..cdfa67afdc6 100644 --- a/charger/nexblue.go +++ b/charger/nexblue.go @@ -254,10 +254,10 @@ func (wb *Nexblue) ChargedEnergy() (float64, error) { return res.Energy, err } -var _ api.MeterEnergy = (*Nexblue)(nil) +var _ api.MeterImport = (*Nexblue)(nil) -// TotalEnergy implements the api.MeterEnergy interface -func (wb *Nexblue) TotalEnergy() (float64, error) { +// ImportEnergy implements the api.MeterImport interface +func (wb *Nexblue) ImportEnergy() (float64, error) { res, err := wb.statusG.Get() return res.LifetimeEnergy, err } diff --git a/charger/nrgble_linux.go b/charger/nrgble_linux.go index 97371cc0157..3a8286a0a26 100644 --- a/charger/nrgble_linux.go +++ b/charger/nrgble_linux.go @@ -283,10 +283,10 @@ func (wb *NRGKickBLE) CurrentPower() (float64, error) { return float64(res.TotalPower) * 10, nil } -var _ api.MeterEnergy = (*NRGKickBLE)(nil) +var _ api.MeterImport = (*NRGKickBLE)(nil) -// TotalEnergy implements the api.MeterEnergy interface -func (wb *NRGKickBLE) TotalEnergy() (float64, error) { +// ImportEnergy implements the api.MeterImport interface +func (wb *NRGKickBLE) ImportEnergy() (float64, error) { var res ble.Energy if err := wb.read(ble.EnergyService, &res); err != nil { return 0, err diff --git a/charger/nrgconnect.go b/charger/nrgconnect.go index 77bbacbfa3b..a8260c5cb6f 100644 --- a/charger/nrgconnect.go +++ b/charger/nrgconnect.go @@ -184,10 +184,10 @@ func (nrg *NRGKickConnect) CurrentPower() (float64, error) { return res.ChargingPower * 1e3, err } -var _ api.MeterEnergy = (*NRGKickConnect)(nil) +var _ api.MeterImport = (*NRGKickConnect)(nil) -// TotalEnergy implements the api.MeterEnergy interface -func (nrg *NRGKickConnect) TotalEnergy() (float64, error) { +// ImportEnergy implements the api.MeterImport interface +func (nrg *NRGKickConnect) ImportEnergy() (float64, error) { res, err := nrg.measurementsG.Get() if err != nil { return 0, err diff --git a/charger/nrggen2.go b/charger/nrggen2.go index 21f81220789..c4f14fc6439 100644 --- a/charger/nrggen2.go +++ b/charger/nrggen2.go @@ -228,10 +228,10 @@ func (nrg *NRGKickGen2) CurrentPower() (float64, error) { return float64(encoding.Int32LswFirst(b)) * 1e-3, nil } -var _ api.MeterEnergy = (*NRGKickGen2)(nil) +var _ api.MeterImport = (*NRGKickGen2)(nil) -// TotalEnergy implements the api.MeterEnergy interface -func (nrg *NRGKickGen2) TotalEnergy() (float64, error) { +// ImportEnergy implements the api.MeterImport interface +func (nrg *NRGKickGen2) ImportEnergy() (float64, error) { b, err := nrg.conn.ReadHoldingRegisters(nrgKickGen2TotalChargedEnergy, 4) if err != nil { return 0, err diff --git a/charger/ocpp.go b/charger/ocpp.go index d0722c1c9f3..0798a190fb5 100644 --- a/charger/ocpp.go +++ b/charger/ocpp.go @@ -110,7 +110,7 @@ func NewOCPPFromConfig(ctx context.Context, other map[string]any) (api.Charger, } if c.cp.HasMeasurement(types.MeasurandEnergyActiveImportRegister) { - implement.Has(c, implement.MeterEnergy(c.conn.TotalEnergy)) + implement.Has(c, implement.MeterImport(c.conn.ImportEnergy)) } if c.cp.HasMeasurement(types.MeasurandCurrentImport) { diff --git a/charger/ocpp/connector.go b/charger/ocpp/connector.go index 9e5622b51db..96f0e61c3de 100644 --- a/charger/ocpp/connector.go +++ b/charger/ocpp/connector.go @@ -310,7 +310,7 @@ func (conn *Connector) CurrentPower() (float64, error) { return 0, api.ErrNotAvailable } -func (conn *Connector) TotalEnergy() (float64, error) { +func (conn *Connector) ImportEnergy() (float64, error) { if !conn.cp.Connected() { return 0, api.ErrTimeout } diff --git a/charger/ocpp/connector_test.go b/charger/ocpp/connector_test.go index 459c612ce16..3b67f8759da 100644 --- a/charger/ocpp/connector_test.go +++ b/charger/ocpp/connector_test.go @@ -69,8 +69,8 @@ func (suite *connTestSuite) TestConnectorNoMeasurements() { suite.Equal(api.ErrTimeout, err, "GetMaxCurrent") // api.ErrNotAvailable - _, err = suite.conn.TotalEnergy() - suite.Equal(api.ErrNotAvailable, err, "TotalEnergy") + _, err = suite.conn.ImportEnergy() + suite.Equal(api.ErrNotAvailable, err, "ImportEnergy") _, err = suite.conn.Soc() suite.Equal(api.ErrNotAvailable, err, "Soc") _, _, _, err = suite.conn.Voltages() @@ -98,9 +98,9 @@ func (suite *connTestSuite) TestConnectorMeasurementsNoTxn() { suite.Equal(api.ErrTimeout, err, "GetMaxCurrent") // keep old values - res, err = suite.conn.TotalEnergy() - suite.NoError(err, "TotalEnergy") - suite.Equal(res, 0.001, "TotalEnergy") + res, err = suite.conn.ImportEnergy() + suite.NoError(err, "ImportEnergy") + suite.Equal(res, 0.001, "ImportEnergy") res, err = suite.conn.Soc() suite.NoError(err, "Soc") suite.Equal(res, 1.0, "Soc") @@ -119,8 +119,8 @@ func (suite *connTestSuite) TestConnectorMeasurementsRunningTxnOutdated() { _, err := suite.conn.CurrentPower() suite.Equal(api.ErrTimeout, err, "CurrentPower") - _, err = suite.conn.TotalEnergy() - suite.Equal(api.ErrTimeout, err, "TotalEnergy") + _, err = suite.conn.ImportEnergy() + suite.Equal(api.ErrTimeout, err, "ImportEnergy") _, err = suite.conn.GetMaxCurrent() suite.Equal(api.ErrTimeout, err, "GetMaxCurrent") _, err = suite.conn.Soc() @@ -140,8 +140,8 @@ func (suite *connTestSuite) TestConnectorMeasurementsRunningTxn() { _, err := suite.conn.CurrentPower() suite.NoError(err, "CurrentPower") - _, err = suite.conn.TotalEnergy() - suite.NoError(err, "TotalEnergy") + _, err = suite.conn.ImportEnergy() + suite.NoError(err, "ImportEnergy") _, err = suite.conn.GetMaxCurrent() suite.NoError(err, "GetMaxCurrent") _, err = suite.conn.Soc() diff --git a/charger/openevse.go b/charger/openevse.go index c2939e9f69f..0618f51a303 100644 --- a/charger/openevse.go +++ b/charger/openevse.go @@ -213,10 +213,10 @@ func (c *OpenEVSE) ChargeDuration() (time.Duration, error) { return time.Duration(res.Elapsed) * time.Second, err } -var _ api.MeterEnergy = (*OpenEVSE)(nil) +var _ api.MeterImport = (*OpenEVSE)(nil) -// TotalEnergy implements the api.MeterEnergy interface -func (c *OpenEVSE) TotalEnergy() (float64, error) { +// ImportEnergy implements the api.MeterImport interface +func (c *OpenEVSE) ImportEnergy() (float64, error) { res, err := c.statusG.Get() if err != nil { return 0, err diff --git a/charger/openwb-2.0.go b/charger/openwb-2.0.go index 19dcbb10794..e118ee4aaa6 100644 --- a/charger/openwb-2.0.go +++ b/charger/openwb-2.0.go @@ -168,10 +168,10 @@ func (wb *OpenWB20) CurrentPower() (float64, error) { return float64(int32(binary.BigEndian.Uint32(b))), nil } -var _ api.MeterEnergy = (*OpenWB20)(nil) +var _ api.MeterImport = (*OpenWB20)(nil) -// TotalEnergy implements the api.MeterEnergy interface -func (wb *OpenWB20) TotalEnergy() (float64, error) { +// ImportEnergy implements the api.MeterImport interface +func (wb *OpenWB20) ImportEnergy() (float64, error) { b, err := wb.conn.ReadInputRegisters(wb.base+openwbRegImport, 2) if err != nil { return 0, err diff --git a/charger/openwb-pro.go b/charger/openwb-pro.go index 5844064e04b..50359ee8cd9 100644 --- a/charger/openwb-pro.go +++ b/charger/openwb-pro.go @@ -164,10 +164,10 @@ func (wb *OpenWBPro) CurrentPower() (float64, error) { return res.PowerAll, err } -var _ api.MeterEnergy = (*OpenWBPro)(nil) +var _ api.MeterImport = (*OpenWBPro)(nil) -// TotalEnergy implements the api.MeterEnergy interface -func (wb *OpenWBPro) TotalEnergy() (float64, error) { +// ImportEnergy implements the api.MeterImport interface +func (wb *OpenWBPro) ImportEnergy() (float64, error) { res, err := wb.statusG.Get() return res.Imported / 1e3, err } diff --git a/charger/openwb.go b/charger/openwb.go index d8fe23b8024..8302b4305b7 100644 --- a/charger/openwb.go +++ b/charger/openwb.go @@ -241,10 +241,10 @@ func (m *OpenWB) CurrentPower() (float64, error) { return m.currentPowerG() } -var _ api.MeterEnergy = (*OpenWB)(nil) +var _ api.MeterImport = (*OpenWB)(nil) -// TotalEnergy implements the api.MeterEnergy interface -func (m *OpenWB) TotalEnergy() (float64, error) { +// ImportEnergy implements the api.MeterImport interface +func (m *OpenWB) ImportEnergy() (float64, error) { return m.totalEnergyG() } diff --git a/charger/pcelectric.go b/charger/pcelectric.go index b0976135925..c0c36130aba 100644 --- a/charger/pcelectric.go +++ b/charger/pcelectric.go @@ -51,7 +51,7 @@ func NewPCElectricFromConfig(other map[string]any) (api.Charger, error) { var res pcelectric.MeterInfo if err := wb.GetJSON(wb.meter, &res); err == nil && res.MeterSerial != "" { implement.Has(wb, implement.Meter(wb.currentPower)) - implement.Has(wb, implement.MeterEnergy(wb.totalEnergy)) + implement.Has(wb, implement.MeterImport(wb.totalEnergy)) implement.Has(wb, implement.PhaseCurrents(wb.currents)) return wb, nil } @@ -248,7 +248,7 @@ func (wb *PCElectric) currentPower() (float64, error) { return 230 * (l1 + l2 + l3), err } -// TotalEnergy implements the api.MeterEnergy interface kwh +// ImportEnergy implements the api.MeterImport interface kwh func (wb *PCElectric) totalEnergy() (float64, error) { var res pcelectric.MeterInfo uri := fmt.Sprintf("%s/meterinfo/%s", wb.uri, wb.meter) diff --git a/charger/peblar.go b/charger/peblar.go index d406f378e6c..2f50ec74f84 100644 --- a/charger/peblar.go +++ b/charger/peblar.go @@ -204,8 +204,8 @@ func (wb *Peblar) CurrentPower() (float64, error) { // deliberately removed, see https://github.com/evcc-io/evcc/issues/25956 // var _ api.ChargeRater = (*Peblar)(nil) -// TotalEnergy implements the api.MeterEnergy interface -func (wb *Peblar) TotalEnergy() (float64, error) { +// ImportEnergy implements the api.MeterImport interface +func (wb *Peblar) ImportEnergy() (float64, error) { b, err := wb.conn.ReadInputRegisters(peblarRegEnergyTotal, 4) if err != nil { return 0, err diff --git a/charger/phoenix-charx.go b/charger/phoenix-charx.go index 26151e711bc..2814d3288dc 100644 --- a/charger/phoenix-charx.go +++ b/charger/phoenix-charx.go @@ -77,7 +77,7 @@ func NewPhoenixCharxFromConfig(ctx context.Context, other map[string]any) (api.C if meter > 0 && meter != 65535 { implement.Has(wb, implement.Meter(wb.currentPower)) - implement.Has(wb, implement.MeterEnergy(wb.totalEnergy)) + implement.Has(wb, implement.MeterImport(wb.totalEnergy)) implement.Has(wb, implement.PhaseCurrents(wb.currents)) implement.Has(wb, implement.PhaseVoltages(wb.voltages)) } @@ -229,7 +229,7 @@ func (wb *PhoenixCharx) currentPower() (float64, error) { return float64(encoding.Int32(b)) / 1e3, nil } -// totalEnergy implements the api.MeterEnergy interface +// totalEnergy implements the api.MeterImport interface func (wb *PhoenixCharx) totalEnergy() (float64, error) { b, err := wb.conn.ReadHoldingRegisters(wb.register(charxRegEnergy), 4) if err != nil { diff --git a/charger/phoenix-em-eth.go b/charger/phoenix-em-eth.go index 8ef5de97aa1..04db0802c82 100644 --- a/charger/phoenix-em-eth.go +++ b/charger/phoenix-em-eth.go @@ -54,7 +54,7 @@ func NewPhoenixEMEthFromConfig(ctx context.Context, other map[string]any) (api.C // check presence of meter by voltage on l1 if b, err := wb.conn.ReadInputRegisters(phxEMEthRegVoltages, 2); err == nil && encoding.Int32LswFirst(b) > 0 { implement.Has(wb, implement.Meter(wb.currentPower)) - implement.Has(wb, implement.MeterEnergy(wb.totalEnergy)) + implement.Has(wb, implement.MeterImport(wb.totalEnergy)) implement.Has(wb, implement.PhaseCurrents(wb.currents)) implement.Has(wb, implement.PhaseVoltages(wb.voltages)) } @@ -145,7 +145,7 @@ func (wb *PhoenixEMEth) currentPower() (float64, error) { return float64(encoding.Int32LswFirst(b)*1e3) * phxEMEthSF, nil } -// totalEnergy implements the api.MeterEnergy interface +// totalEnergy implements the api.MeterImport interface func (wb *PhoenixEMEth) totalEnergy() (float64, error) { b, err := wb.conn.ReadInputRegisters(phxEMEthRegEnergy, 2) if err != nil { diff --git a/charger/phoenix-ev-eth.go b/charger/phoenix-ev-eth.go index 1e3af59eb4a..b9016e879ef 100644 --- a/charger/phoenix-ev-eth.go +++ b/charger/phoenix-ev-eth.go @@ -99,7 +99,7 @@ func NewPhoenixEVEth(ctx context.Context, uri string, slaveID uint8) (api.Charge // check presence of meter by voltage on l1 if b, err := wb.conn.ReadInputRegisters(phxRegVoltages, 2); err == nil && encoding.Uint32LswFirst(b) > 0 { implement.May(wb, implement.Meter(wb.currentPower)) - implement.May(wb, implement.MeterEnergy(wb.totalEnergy)) + implement.May(wb, implement.MeterImport(wb.totalEnergy)) implement.May(wb, implement.PhaseCurrents(wb.currents)) implement.May(wb, implement.PhaseVoltages(wb.voltages)) } @@ -184,7 +184,7 @@ func (wb *PhoenixEVEth) currentPower() (float64, error) { return float64(encoding.Int32LswFirst(b)), nil } -// totalEnergy implements the api.MeterEnergy interface +// totalEnergy implements the api.MeterImport interface func (wb *PhoenixEVEth) totalEnergy() (float64, error) { if wb.isWallbe { b, err := wb.conn.ReadHoldingRegisters(phxRegEnergyWallbe, 4) diff --git a/charger/pulsatrix.go b/charger/pulsatrix.go index b38bf6836b2..431a954b93a 100644 --- a/charger/pulsatrix.go +++ b/charger/pulsatrix.go @@ -429,10 +429,10 @@ func (c *Pulsatrix) CurrentPower() (float64, error) { return res.LastActivePower, err } -var _ api.MeterEnergy = (*Pulsatrix)(nil) +var _ api.MeterImport = (*Pulsatrix)(nil) -// TotalEnergy implements the api.MeterEnergy interface -func (c *Pulsatrix) TotalEnergy() (float64, error) { +// ImportEnergy implements the api.MeterImport interface +func (c *Pulsatrix) ImportEnergy() (float64, error) { res, err := c.data.Get() return res.EnergyImported, err } diff --git a/charger/schneider-v3.go b/charger/schneider-v3.go index 66cf87e09ed..096b952942f 100644 --- a/charger/schneider-v3.go +++ b/charger/schneider-v3.go @@ -191,10 +191,10 @@ func (wb *Schneider) CurrentPower() (float64, error) { return float64(encoding.Float32LswFirst(b)) * 1e3, nil } -var _ api.MeterEnergy = (*Schneider)(nil) +var _ api.MeterImport = (*Schneider)(nil) -// TotalEnergy implements the api.MeterEnergy interface -func (wb *Schneider) TotalEnergy() (float64, error) { +// ImportEnergy implements the api.MeterImport interface +func (wb *Schneider) ImportEnergy() (float64, error) { b, err := wb.conn.ReadHoldingRegisters(schneiderRegEnergy, 4) if err != nil { return 0, err diff --git a/charger/sgready-relay.go b/charger/sgready-relay.go index 62e17cc4aa8..c281a2bb471 100644 --- a/charger/sgready-relay.go +++ b/charger/sgready-relay.go @@ -61,12 +61,12 @@ func NewSgReadyRelayFromConfig(ctx context.Context, other map[string]any) (api.C return nil, err } - powerG, energyG, err := cc.Energy.Configure(ctx) + powerG, importG, _, err := cc.Energy.Configure(ctx) if err != nil { return nil, err } implement.May(res, implement.Meter(powerG)) - implement.May(res, implement.MeterEnergy(energyG)) + implement.May(res, implement.MeterImport(importG)) tempG, limitTempG, err := cc.Temperature.Configure(ctx) if err != nil { diff --git a/charger/sgready.go b/charger/sgready.go index bc152b4f50e..909dd66e9aa 100644 --- a/charger/sgready.go +++ b/charger/sgready.go @@ -107,12 +107,12 @@ func NewSgReadyFromConfig(ctx context.Context, other map[string]any) (api.Charge return nil, err } - powerG, energyG, err := cc.Energy.Configure(ctx) + powerG, importG, _, err := cc.Energy.Configure(ctx) if err != nil { return nil, err } implement.May(res, implement.Meter(powerG)) - implement.May(res, implement.MeterEnergy(energyG)) + implement.May(res, implement.MeterImport(importG)) tempG, limitTempG, err := cc.Temperature.Configure(ctx) if err != nil { diff --git a/charger/shelly-topac.go b/charger/shelly-topac.go index 09257741879..aa1ae898dc9 100644 --- a/charger/shelly-topac.go +++ b/charger/shelly-topac.go @@ -183,10 +183,10 @@ func (c *ShellyTopAC) CurrentPower() (float64, error) { return phase.TotalPower * 1e3, nil } -var _ api.MeterEnergy = (*ShellyTopAC)(nil) +var _ api.MeterImport = (*ShellyTopAC)(nil) -// TotalEnergy implements the api.MeterEnergy interface -func (c *ShellyTopAC) TotalEnergy() (float64, error) { +// ImportEnergy implements the api.MeterImport interface +func (c *ShellyTopAC) ImportEnergy() (float64, error) { res, err := c.phaseG.Get() if err != nil { return 0, err diff --git a/charger/shelly.go b/charger/shelly.go index 52426680814..2884e519e68 100644 --- a/charger/shelly.go +++ b/charger/shelly.go @@ -93,9 +93,9 @@ func (c *Shelly) Enable(enable bool) error { } } -var _ api.MeterEnergy = (*Shelly)(nil) +var _ api.MeterImport = (*Shelly)(nil) -// TotalEnergy implements the api.MeterEnergy interface -func (c *Shelly) TotalEnergy() (float64, error) { - return c.conn.TotalEnergy() +// ImportEnergy implements the api.MeterImport interface +func (c *Shelly) ImportEnergy() (float64, error) { + return c.conn.ImportEnergy() } diff --git a/charger/sigenergy.go b/charger/sigenergy.go index 4aaa8be80a3..06644043989 100644 --- a/charger/sigenergy.go +++ b/charger/sigenergy.go @@ -159,10 +159,10 @@ func (wb *Sigenergy) CurrentPower() (float64, error) { return float64(binary.BigEndian.Uint32(b)), nil } -var _ api.MeterEnergy = (*Sigenergy)(nil) +var _ api.MeterImport = (*Sigenergy)(nil) -// TotalEnergy implements the api.MeterEnergy interface -func (wb *Sigenergy) TotalEnergy() (float64, error) { +// ImportEnergy implements the api.MeterImport interface +func (wb *Sigenergy) ImportEnergy() (float64, error) { b, err := wb.conn.ReadHoldingRegisters(regSigTotalEnergyConsumed, 2) if err != nil { return 0, err diff --git a/charger/smaevcharger.go b/charger/smaevcharger.go index 15c0aff5535..6399ca0b504 100644 --- a/charger/smaevcharger.go +++ b/charger/smaevcharger.go @@ -242,10 +242,10 @@ func (wb *Smaevcharger) MaxCurrentMillis(current float64) error { return wb.Send(value("Parameter.Inverter.AcALim", fmt.Sprintf("%.2f", current))) } -var _ api.MeterEnergy = (*Smaevcharger)(nil) +var _ api.MeterImport = (*Smaevcharger)(nil) -// TotalEnergy implements the api.MeterEnergy interface -func (wb *Smaevcharger) TotalEnergy() (float64, error) { +// ImportEnergy implements the api.MeterImport interface +func (wb *Smaevcharger) ImportEnergy() (float64, error) { res, err := wb.getMeasurement("Measurement.Metering.GridMs.TotWhIn") if _, ok := errors.AsType[*smaevcharger.ErrUnknownMeasurement](err); ok { res, err = wb.getMeasurement("Measurement.Metering.GridMs.TotWhIn.ChaSta") diff --git a/charger/smart-evse.go b/charger/smart-evse.go index 3c97d625240..1163397adf9 100644 --- a/charger/smart-evse.go +++ b/charger/smart-evse.go @@ -166,7 +166,7 @@ func NewSmartEVSE3(uri string, cache time.Duration, mode int) (api.Charger, erro // decorate optional EV meter if configured in SmartEVSE if res.EvMeter.Description != "" && res.EvMeter.Description != "Disabled" { implement.Has(wb, implement.Meter(wb.currentPower)) - implement.Has(wb, implement.MeterEnergy(wb.totalEnergy)) + implement.Has(wb, implement.MeterImport(wb.totalEnergy)) implement.Has(wb, implement.PhaseCurrents(wb.currents)) } @@ -315,7 +315,7 @@ func (wb *SmartEVSE3) currentPower() (float64, error) { return res.EvMeter.ImportActivePower, nil } -// totalEnergy implements the api.MeterEnergy interface +// totalEnergy implements the api.MeterImport interface func (wb *SmartEVSE3) totalEnergy() (float64, error) { res, err := wb.apiG.Get() if err != nil { diff --git a/charger/smartevse.go b/charger/smartevse.go index dc219ad903c..2dd3498ae2a 100644 --- a/charger/smartevse.go +++ b/charger/smartevse.go @@ -174,10 +174,10 @@ func (wb *smartEVSE) CurrentPower() (float64, error) { return v1*i1 + v2*i2 + v3*i3, nil } -var _ api.MeterEnergy = (*smartEVSE)(nil) +var _ api.MeterImport = (*smartEVSE)(nil) -// TotalEnergy implements the api.MeterEnergy interface -func (wb *smartEVSE) TotalEnergy() (float64, error) { +// ImportEnergy implements the api.MeterImport interface +func (wb *smartEVSE) ImportEnergy() (float64, error) { b, err := wb.conn.ReadInputRegisters(smartEVSERegEnergy, 2) if err != nil { return 0, err diff --git a/charger/solax.go b/charger/solax.go index 9b79b6ad414..b8f116a3ddc 100644 --- a/charger/solax.go +++ b/charger/solax.go @@ -225,10 +225,10 @@ func (wb *Solax) CurrentPower() (float64, error) { return float64(binary.BigEndian.Uint16(b)), err } -var _ api.MeterEnergy = (*Solax)(nil) +var _ api.MeterImport = (*Solax)(nil) -// TotalEnergy implements the api.MeterEnergy interface -func (wb *Solax) TotalEnergy() (float64, error) { +// ImportEnergy implements the api.MeterImport interface +func (wb *Solax) ImportEnergy() (float64, error) { b, err := wb.conn.ReadInputRegisters(solaxRegTotalEnergy, 2) if err != nil { return 0, err diff --git a/charger/sungrow.go b/charger/sungrow.go index 53bde214cd5..491ad6dba39 100644 --- a/charger/sungrow.go +++ b/charger/sungrow.go @@ -240,10 +240,10 @@ func (wb *Sungrow) Voltages() (float64, float64, float64, error) { return wb.getPhaseValues(sgRegVoltages, 10) } -var _ api.MeterEnergy = (*Sungrow)(nil) +var _ api.MeterImport = (*Sungrow)(nil) -// TotalEnergy implements the api.MeterEnergy interface -func (wb *Sungrow) TotalEnergy() (float64, error) { +// ImportEnergy implements the api.MeterImport interface +func (wb *Sungrow) ImportEnergy() (float64, error) { b, err := wb.conn.ReadInputRegisters(sgRegTotalEnergy, 2) if err != nil { return 0, err diff --git a/charger/switchsocket.go b/charger/switchsocket.go index 01294b77cf1..bd7f15b368b 100644 --- a/charger/switchsocket.go +++ b/charger/switchsocket.go @@ -62,7 +62,7 @@ func NewSwitchSocketFromConfig(ctx context.Context, other map[string]any) (api.C if err != nil { return nil, err } - implement.May(c, implement.MeterEnergy(energy)) + implement.May(c, implement.MeterImport(energy)) soc, err := cc.Soc.FloatGetter(ctx) if err != nil { diff --git a/charger/tasmota.go b/charger/tasmota.go index c22f6ce7642..2aa97ca2110 100644 --- a/charger/tasmota.go +++ b/charger/tasmota.go @@ -92,11 +92,11 @@ func (c *Tasmota) Enable(enable bool) error { return c.conn.Enable(enable) } -var _ api.MeterEnergy = (*Tasmota)(nil) +var _ api.MeterImport = (*Tasmota)(nil) -// TotalEnergy implements the api.MeterEnergy interface -func (c *Tasmota) TotalEnergy() (float64, error) { - return c.conn.TotalEnergy() +// ImportEnergy implements the api.MeterImport interface +func (c *Tasmota) ImportEnergy() (float64, error) { + return c.conn.ImportEnergy() } // Currents implements the api.PhaseCurrents interface diff --git a/charger/tplink.go b/charger/tplink.go index 27244f223cc..da37ed7bde0 100644 --- a/charger/tplink.go +++ b/charger/tplink.go @@ -92,9 +92,9 @@ func (c *TPLink) Enable(enable bool) error { return nil } -var _ api.MeterEnergy = (*TPLink)(nil) +var _ api.MeterImport = (*TPLink)(nil) -// TotalEnergy implements the api.MeterEnergy interface -func (c *TPLink) TotalEnergy() (float64, error) { - return c.conn.TotalEnergy() +// ImportEnergy implements the api.MeterImport interface +func (c *TPLink) ImportEnergy() (float64, error) { + return c.conn.ImportEnergy() } diff --git a/charger/versicharge.go b/charger/versicharge.go index 36d3cb68f00..1da0327ed77 100644 --- a/charger/versicharge.go +++ b/charger/versicharge.go @@ -167,10 +167,10 @@ func (wb *Versicharge) CurrentPower() (float64, error) { return sum, nil } -var _ api.MeterEnergy = (*Versicharge)(nil) +var _ api.MeterImport = (*Versicharge)(nil) -// TotalEnergy implements the api.MeterEnergy interface -func (wb *Versicharge) TotalEnergy() (float64, error) { +// ImportEnergy implements the api.MeterImport interface +func (wb *Versicharge) ImportEnergy() (float64, error) { b, err := wb.conn.ReadHoldingRegisters(versiRegTotalEnergy, 2) if err != nil { return 0, err diff --git a/charger/vestel.go b/charger/vestel.go index e268b54a0b6..dd03329e5c2 100644 --- a/charger/vestel.go +++ b/charger/vestel.go @@ -250,10 +250,10 @@ func (wb *Vestel) CurrentPower() (float64, error) { return float64(binary.BigEndian.Uint32(b)), err } -var _ api.MeterEnergy = (*Vestel)(nil) +var _ api.MeterImport = (*Vestel)(nil) -// TotalEnergy implements the api.MeterEnergy interface -func (wb *Vestel) TotalEnergy() (float64, error) { +// ImportEnergy implements the api.MeterImport interface +func (wb *Vestel) ImportEnergy() (float64, error) { b, err := wb.conn.ReadInputRegisters(vestelRegTotalEnergy, 2) if err != nil { return 0, err diff --git a/charger/victron.go b/charger/victron.go index f717edfbca5..9f5d3613df7 100644 --- a/charger/victron.go +++ b/charger/victron.go @@ -200,7 +200,7 @@ func (wb *Victron) CurrentPower() (float64, error) { var _ api.ChargeRater = (*Victron)(nil) -// ChargedEnergy implements the api.MeterEnergy interface +// ChargedEnergy implements the api.MeterImport interface func (wb *Victron) ChargedEnergy() (float64, error) { b, err := wb.conn.ReadHoldingRegisters(wb.regs.Energy, 1+cast.ToUint16(wb.regs.isGX)) if err != nil { diff --git a/charger/warp-ws.go b/charger/warp-ws.go index dd79ab2cc7f..86d61d6c23e 100644 --- a/charger/warp-ws.go +++ b/charger/warp-ws.go @@ -81,7 +81,7 @@ func NewWarpWSFromConfig(ctx context.Context, other map[string]any) (api.Charger // Feature: Meter -> Meter is legacy API, Meters is the new API if w.hasFeature(warp.FeatureMeter) || w.hasFeature(warp.FeatureMeters) { implement.Has(w, implement.Meter(w.currentPower)) - implement.Has(w, implement.MeterEnergy(w.totalEnergy)) + implement.Has(w, implement.MeterImport(w.totalEnergy)) } // Feature: Meters | MeterAllValues diff --git a/charger/warp2-mqtt.go b/charger/warp2-mqtt.go index 8ed3890a53a..b7cf8f10549 100644 --- a/charger/warp2-mqtt.go +++ b/charger/warp2-mqtt.go @@ -63,7 +63,7 @@ func NewWarp2FromConfig(other map[string]any) (api.Charger, error) { if wb.hasFeature(cc.Topic, warp.FeatureMeter, cc.Timeout) { implement.May(wb, implement.Meter(wb.currentPower)) - implement.May(wb, implement.MeterEnergy(wb.totalEnergy)) + implement.May(wb, implement.MeterImport(wb.totalEnergy)) } if wb.hasFeature(cc.Topic, warp.FeatureMeterPhases, cc.Timeout) { @@ -244,7 +244,7 @@ func (wb *Warp2) currentPower() (float64, error) { return res.Power, err } -// TotalEnergy implements the api.MeterEnergy interface +// ImportEnergy implements the api.MeterImport interface func (wb *Warp2) totalEnergy() (float64, error) { var res warp.MeterValues err := wb.meterG(&res) diff --git a/charger/webasto-next.go b/charger/webasto-next.go index ddce64f2895..cd0c1ffbb95 100644 --- a/charger/webasto-next.go +++ b/charger/webasto-next.go @@ -210,10 +210,10 @@ func (wb *WebastoNext) CurrentPower() (float64, error) { return float64(binary.BigEndian.Uint32(b)), nil } -var _ api.MeterEnergy = (*WebastoNext)(nil) +var _ api.MeterImport = (*WebastoNext)(nil) -// TotalEnergy implements the api.MeterEnergy interface -func (wb *WebastoNext) TotalEnergy() (float64, error) { +// ImportEnergy implements the api.MeterImport interface +func (wb *WebastoNext) ImportEnergy() (float64, error) { b, err := wb.conn.ReadHoldingRegisters(tqRegEnergyMeter, 2) if err != nil { return 0, err diff --git "a/charger/weidm\303\274ller.go" "b/charger/weidm\303\274ller.go" index 27b187a96d8..755012bfc4c 100644 --- "a/charger/weidm\303\274ller.go" +++ "b/charger/weidm\303\274ller.go" @@ -107,7 +107,7 @@ func NewWeidmüller(ctx context.Context, uri string, id uint8) (api.Charger, err // check presence of energy meter if b, err := wb.conn.ReadHoldingRegisters(wmRegTotalEnergy, 2); err == nil && binary.BigEndian.Uint32(b) > 0 { - implement.Has(wb, implement.MeterEnergy(wb.totalEnergy)) + implement.Has(wb, implement.MeterImport(wb.totalEnergy)) } return wb, nil @@ -218,7 +218,7 @@ func (wb *Weidmüller) CurrentPower() (float64, error) { return float64(encoding.Uint32LswFirst(b)) / 1e3, err } -// TotalEnergy implements the api.MeterEnergy interface +// ImportEnergy implements the api.MeterImport interface func (wb *Weidmüller) totalEnergy() (float64, error) { b, err := wb.conn.ReadHoldingRegisters(wmRegTotalEnergy, 2) if err != nil { diff --git a/cmd/dumper.go b/cmd/dumper.go index 6d6e1752180..dd01e28c8a9 100644 --- a/cmd/dumper.go +++ b/cmd/dumper.go @@ -85,9 +85,16 @@ func (d *dumper) Dump(name string, v any) { }) } - if v, ok := api.Cap[api.MeterEnergy](v); ok { - d.measureTime(w, "Energy", func() (string, error) { - energy, err := v.TotalEnergy() + if v, ok := api.Cap[api.MeterImport](v); ok { + d.measureTime(w, "Import", func() (string, error) { + energy, err := v.ImportEnergy() + return fmt.Sprintf("%.1fkWh", energy), err + }) + } + + if v, ok := api.Cap[api.MeterExport](v); ok { + d.measureTime(w, "Export", func() (string, error) { + energy, err := v.ExportEnergy() return fmt.Sprintf("%.1fkWh", energy), err }) } diff --git a/cmd/implement/implement.go b/cmd/implement/implement.go index a1681c4ecb0..addfd927284 100644 --- a/cmd/implement/implement.go +++ b/cmd/implement/implement.go @@ -71,7 +71,8 @@ func generate(out io.Writer) error { reflect.TypeFor[api.PhaseSwitcher](), reflect.TypeFor[api.Battery](), reflect.TypeFor[api.ChargeState](), - reflect.TypeFor[api.MeterEnergy](), + reflect.TypeFor[api.MeterImport](), + reflect.TypeFor[api.MeterExport](), reflect.TypeFor[api.PhaseCurrents](), reflect.TypeFor[api.PhaseVoltages](), reflect.TypeFor[api.MaxACPowerGetter](), diff --git a/core/capable_test.go b/core/capable_test.go index 40b9b9157e9..eacfbc060e5 100644 --- a/core/capable_test.go +++ b/core/capable_test.go @@ -34,9 +34,9 @@ func (c *charger) Capacity() float64 { return 0 } -var _ api.MeterEnergy = (*charger)(nil) +var _ api.MeterImport = (*charger)(nil) -func (c *charger) TotalEnergy() (float64, error) { +func (c *charger) ImportEnergy() (float64, error) { return 0, nil } @@ -69,7 +69,7 @@ func TestCapsWrapping(t *testing.T) { assert.True(t, ok) } { - _, ok := c.(api.MeterEnergy) + _, ok := c.(api.MeterImport) assert.True(t, ok) } { @@ -89,14 +89,14 @@ func TestCapsWrapping(t *testing.T) { } { - _, ok := m.(api.MeterEnergy) + _, ok := m.(api.MeterImport) assert.False(t, ok, "unexpected promoted energy") - assert.True(t, api.HasCap[api.MeterEnergy](m), "missing promoted energy cap") + assert.True(t, api.HasCap[api.MeterImport](m), "missing promoted energy cap") var mm any = m.(*capableMeter).Meter - _, ok = mm.(api.MeterEnergy) + _, ok = mm.(api.MeterImport) assert.True(t, ok, "missing embedded energy") - assert.True(t, api.HasCap[api.MeterEnergy](mm), "missing embedded energy cap") + assert.True(t, api.HasCap[api.MeterImport](mm), "missing embedded energy cap") } { _, ok := m.(api.Battery) diff --git a/core/loadpoint.go b/core/loadpoint.go index c0b88b78dda..77e0c985baa 100644 --- a/core/loadpoint.go +++ b/core/loadpoint.go @@ -403,7 +403,7 @@ func (lp *Loadpoint) requestUpdate() { } // capableMeter wraps a meter with capability lookup from its source. -// This preserves capability checks (like MeterEnergy, PhaseCurrents, PhaseVoltages) when +// This preserves capability checks (like MeterImport, MeterExport, PhaseCurrents, PhaseVoltages) when // the meter was extracted from a decorated charger's capability registry. type capableMeter struct { api.Meter @@ -420,7 +420,7 @@ func (lp *Loadpoint) configureChargerType(charger api.Charger) { if mt, ok := api.Cap[api.Meter](charger); ok { // preserve charger's capability registry so that subsequent - // capability checks on chargeMeter (e.g. MeterEnergy, PhaseCurrents) + // capability checks on chargeMeter (e.g. MeterImport, PhaseCurrents) // still work for decorated chargers (https://github.com/evcc-io/evcc/issues/28915) if c, ok := charger.(api.Capable); ok { lp.chargeMeter = &capableMeter{Meter: mt, Capable: c} @@ -1743,7 +1743,7 @@ func (lp *Loadpoint) publishChargeProgress() { // update energy, prefer totals var importTotal *float64 - if api.HasCap[api.MeterEnergy](lp.chargeMeter) { + if api.HasCap[api.MeterImport](lp.chargeMeter) { if f := lp.chargeMeterTotal(); f > 0 { lp.publish(keys.ChargeTotalImport, f) importTotal = &f diff --git a/core/loadpoint_session.go b/core/loadpoint_session.go index d30315ab852..6843f5ac376 100644 --- a/core/loadpoint_session.go +++ b/core/loadpoint_session.go @@ -13,12 +13,12 @@ import ( ) func (lp *Loadpoint) chargeMeterTotal() float64 { - m, ok := api.Cap[api.MeterEnergy](lp.chargeMeter) + m, ok := api.Cap[api.MeterImport](lp.chargeMeter) if !ok { return 0 } - f, err := m.TotalEnergy() + f, err := m.ImportEnergy() if err != nil { if !errors.Is(err, api.ErrNotAvailable) { lp.log.ERROR.Printf("charge total import: %v", err) diff --git a/core/loadpoint_session_test.go b/core/loadpoint_session_test.go index 83f6e8f1368..d8813447e15 100644 --- a/core/loadpoint_session_test.go +++ b/core/loadpoint_session_test.go @@ -36,14 +36,14 @@ func TestSession(t *testing.T) { defer ctrl.Finish() mm := api.NewMockMeter(ctrl) - me := api.NewMockMeterEnergy(ctrl) + me := api.NewMockMeterImport(ctrl) type EnergyDecorator struct { api.Meter - api.MeterEnergy + api.MeterImport } - cm := &EnergyDecorator{Meter: mm, MeterEnergy: me} + cm := &EnergyDecorator{Meter: mm, MeterImport: me} lp := &Loadpoint{ log: util.NewLogger("foo"), @@ -53,7 +53,7 @@ func TestSession(t *testing.T) { } // create session - me.EXPECT().TotalEnergy().Return(1.0, nil) + me.EXPECT().ImportEnergy().Return(1.0, nil) lp.createSession() assert.NotNil(t, lp.session) @@ -64,7 +64,7 @@ func TestSession(t *testing.T) { // stop charging clock.Add(time.Hour) lp.energyMetrics.Update(1.23) - me.EXPECT().TotalEnergy().Return(1.0+lp.getChargedEnergy()/1e3, nil) // match chargedEnergy + me.EXPECT().ImportEnergy().Return(1.0+lp.getChargedEnergy()/1e3, nil) // match chargedEnergy lp.stopSession() assert.NotNil(t, lp.session) @@ -79,7 +79,7 @@ func TestSession(t *testing.T) { // stop charging - 2nd leg clock.Add(time.Hour) lp.energyMetrics.Update(lp.getChargedEnergy() * 2) - me.EXPECT().TotalEnergy().Return(3.0, nil) // doesn't match chargedEnergy + me.EXPECT().ImportEnergy().Return(3.0, nil) // doesn't match chargedEnergy lp.stopSession() assert.NotNil(t, lp.session) @@ -213,14 +213,14 @@ func TestResetHeatingSession(t *testing.T) { }) mm := api.NewMockMeter(ctrl) - me := api.NewMockMeterEnergy(ctrl) + me := api.NewMockMeterImport(ctrl) type EnergyDecorator struct { api.Meter - api.MeterEnergy + api.MeterImport } - cm := &EnergyDecorator{Meter: mm, MeterEnergy: me} + cm := &EnergyDecorator{Meter: mm, MeterImport: me} lp := &Loadpoint{ log: util.NewLogger("foo"), @@ -231,7 +231,7 @@ func TestResetHeatingSession(t *testing.T) { } // create session - me.EXPECT().TotalEnergy().Return(1.0, nil) + me.EXPECT().ImportEnergy().Return(1.0, nil) lp.createSession() require.NotNil(t, lp.session) assert.True(t, lp.session.Created.IsZero()) @@ -241,7 +241,7 @@ func TestResetHeatingSession(t *testing.T) { assert.Equal(t, clock.Now(), lp.session.Created) clock.Add(36 * time.Hour) - me.EXPECT().TotalEnergy().Return(1.0, nil).MaxTimes(2) + me.EXPECT().ImportEnergy().Return(1.0, nil).MaxTimes(2) lp.resetHeatingSession() require.NotNil(t, lp.session) @@ -250,7 +250,7 @@ func TestResetHeatingSession(t *testing.T) { lp.updateSession(sessionStart(lp)) assert.Equal(t, clock.Now(), lp.session.Created) - me.EXPECT().TotalEnergy().Return(3.0, nil) + me.EXPECT().ImportEnergy().Return(3.0, nil) lp.stopSession() assert.NotNil(t, lp.session) @@ -260,7 +260,7 @@ func TestResetHeatingSession(t *testing.T) { } func TestFinalizeSessionEnergy(t *testing.T) { - setup := func(t *testing.T) (*Loadpoint, *api.MockMeterEnergy, *api.MockChargeRater) { + setup := func(t *testing.T) (*Loadpoint, *api.MockMeterImport, *api.MockChargeRater) { t.Helper() var err error serverdb.Instance, err = serverdb.New("sqlite", ":memory:") @@ -270,17 +270,17 @@ func TestFinalizeSessionEnergy(t *testing.T) { ctrl := gomock.NewController(t) mm := api.NewMockMeter(ctrl) - me := api.NewMockMeterEnergy(ctrl) + me := api.NewMockMeterImport(ctrl) rater := api.NewMockChargeRater(ctrl) type EnergyDecorator struct { api.Meter - api.MeterEnergy + api.MeterImport } cm := &EnergyDecorator{ Meter: mm, - MeterEnergy: me, + MeterImport: me, } lp := &Loadpoint{ @@ -296,17 +296,17 @@ func TestFinalizeSessionEnergy(t *testing.T) { t.Run("corrects session when ChargedEnergy increased", func(t *testing.T) { lp, me, rater := setup(t) - me.EXPECT().TotalEnergy().Return(9157.3, nil) + me.EXPECT().ImportEnergy().Return(9157.3, nil) lp.createSession() lp.session.Created = lp.clock.Now() lp.energyMetrics.Update(15.3) - me.EXPECT().TotalEnergy().Return(9164.0, nil) + me.EXPECT().ImportEnergy().Return(9164.0, nil) lp.stopSession() require.Equal(t, 15.3, lp.session.ChargedEnergy) rater.EXPECT().ChargedEnergy().Return(16.2, nil) - me.EXPECT().TotalEnergy().Return(9173.5, nil) + me.EXPECT().ImportEnergy().Return(9173.5, nil) lp.finalizeSessionEnergy() assert.Equal(t, 16.2, lp.session.ChargedEnergy) @@ -317,12 +317,12 @@ func TestFinalizeSessionEnergy(t *testing.T) { t.Run("no-op when ChargedEnergy unchanged", func(t *testing.T) { lp, me, rater := setup(t) - me.EXPECT().TotalEnergy().Return(9154.4, nil) + me.EXPECT().ImportEnergy().Return(9154.4, nil) lp.createSession() lp.session.Created = lp.clock.Now() lp.energyMetrics.Update(15.3) - me.EXPECT().TotalEnergy().Return(9164.0, nil) + me.EXPECT().ImportEnergy().Return(9164.0, nil) lp.stopSession() require.Equal(t, 15.3, lp.session.ChargedEnergy) diff --git a/core/metrics/accumulator_test.go b/core/metrics/accumulator_test.go index 62b4962142a..0de3ee1d477 100644 --- a/core/metrics/accumulator_test.go +++ b/core/metrics/accumulator_test.go @@ -9,7 +9,7 @@ import ( "github.com/stretchr/testify/assert" ) -func TestMeterEnergyMeterTotal(t *testing.T) { +func TestMeterImportMeterTotal(t *testing.T) { clock := clock.NewMock() clock.Set(now.BeginningOfDay()) @@ -23,7 +23,7 @@ func TestMeterEnergyMeterTotal(t *testing.T) { assert.Equal(t, 1.0, me.Imported()) } -func TestMeterEnergyAddPower(t *testing.T) { +func TestMeterImportAddPower(t *testing.T) { clock := clock.NewMock() clock.Set(now.BeginningOfDay()) diff --git a/core/site.go b/core/site.go index 8f3c39d1475..58b57667859 100644 --- a/core/site.go +++ b/core/site.go @@ -370,7 +370,7 @@ func meterCapabilities(name string, meter any) string { panic("not a meter: " + name) } - energy := api.HasCap[api.MeterEnergy](meter) + energy := api.HasCap[api.MeterImport](meter) || api.HasCap[api.MeterExport](meter) currents := api.HasCap[api.PhaseCurrents](meter) name += ":" @@ -455,7 +455,7 @@ func (site *Site) DumpConfig() { lp.log.INFO.Printf(" mode: %s", lp.GetMode()) _, power := api.Cap[api.Meter](lp.charger) - _, energy := api.Cap[api.MeterEnergy](lp.charger) + _, energy := api.Cap[api.MeterImport](lp.charger) _, currents := api.Cap[api.PhaseCurrents](lp.charger) _, phases := api.Cap[api.PhaseSwitcher](lp.charger) _, wakeup := api.Cap[api.Resurrector](lp.charger) @@ -516,10 +516,19 @@ func (site *Site) collectMeters(key string, meters []config.Device[api.Meter]) [ site.log.ERROR.Printf("%s %d power: %v", key, i+1, err) } - // energy (production) + // energy (production for pv/battery, consumption for aux/ext) var energy float64 - if m, ok := api.Cap[api.MeterEnergy](meter); err == nil && ok { - energy, err = m.TotalEnergy() + if err == nil { + switch key { + case "pv", "battery": + if m, ok := api.Cap[api.MeterExport](meter); ok { + energy, err = m.ExportEnergy() + } + default: + if m, ok := api.Cap[api.MeterImport](meter); ok { + energy, err = m.ImportEnergy() + } + } if err != nil { site.log.ERROR.Printf("%s %d energy: %v", key, i+1, err) } @@ -786,8 +795,8 @@ func (site *Site) updateGridMeter() error { // grid energy (import) var importEnergy *float64 - if energyMeter, ok := api.Cap[api.MeterEnergy](site.gridMeter); ok { - if f, err := energyMeter.TotalEnergy(); err == nil { + if energyMeter, ok := api.Cap[api.MeterImport](site.gridMeter); ok { + if f, err := energyMeter.ImportEnergy(); err == nil { mm.Energy = f importEnergy = &f } else { diff --git a/core/wrapper/chargerater.go b/core/wrapper/chargerater.go index 939f234f965..74a77a74165 100644 --- a/core/wrapper/chargerater.go +++ b/core/wrapper/chargerater.go @@ -12,7 +12,7 @@ import ( ) // ChargeRater is responsible for providing charged energy amount -// by implementing api.ChargeRater. It uses the charge meter's TotalEnergy or +// by implementing api.ChargeRater. It uses the charge meter's ImportEnergy or // keeps track of consumed energy by regularly updating consumed power. type ChargeRater struct { sync.Mutex @@ -39,19 +39,19 @@ func NewChargeRater(log *util.Logger, meter api.Meter) *ChargeRater { } } -// StartCharge records meter start energy. If meter does not supply TotalEnergy, +// StartCharge records meter start energy. If meter does not supply ImportEnergy, // start time is recorded and charged energy set to zero. func (cr *ChargeRater) StartCharge(continued bool) { cr.Lock() defer cr.Unlock() - // time is needed if MeterEnergy is not supported + // time is needed if MeterImport is not supported cr.start = cr.clck.Now() cr.startEnergy = nil // get end energy amount - if m, ok := api.Cap[api.MeterEnergy](cr.meter); ok { - if f, err := m.TotalEnergy(); err == nil { + if m, ok := api.Cap[api.MeterImport](cr.meter); ok { + if f, err := m.ImportEnergy(); err == nil { cr.startEnergy = &f cr.log.DEBUG.Printf("charge start energy: %.3fkWh", f) } else if !loadpoint.AcceptableError(err) { @@ -66,7 +66,7 @@ func (cr *ChargeRater) StartCharge(continued bool) { } } -// StopCharge records meter stop energy. If meter does not supply TotalEnergy, +// StopCharge records meter stop energy. If meter does not supply ImportEnergy, // stop time is recorded and accumulating energy though SetChargePower stopped. func (cr *ChargeRater) StopCharge() { cr.Lock() @@ -75,8 +75,8 @@ func (cr *ChargeRater) StopCharge() { cr.charging = false // get end energy amount - if m, ok := api.Cap[api.MeterEnergy](cr.meter); ok { - if f, err := m.TotalEnergy(); err == nil { + if m, ok := api.Cap[api.MeterImport](cr.meter); ok { + if f, err := m.ImportEnergy(); err == nil { if cr.startEnergy != nil { cr.chargedEnergy += f - *cr.startEnergy } @@ -95,8 +95,8 @@ func (cr *ChargeRater) ResetCharge() { defer cr.Unlock() // get end energy amount - if m, ok := api.Cap[api.MeterEnergy](cr.meter); ok { - if f, err := m.TotalEnergy(); err == nil { + if m, ok := api.Cap[api.MeterImport](cr.meter); ok { + if f, err := m.ImportEnergy(); err == nil { if cr.startEnergy != nil { cr.chargedEnergy += f - *cr.startEnergy cr.log.DEBUG.Printf("charge final energy: %.3fkWh", cr.chargedEnergy) @@ -122,7 +122,7 @@ func (cr *ChargeRater) SetChargePower(power float64) { } // update energy amount if not provided by meter - if !api.HasCap[api.MeterEnergy](cr.meter) { + if !api.HasCap[api.MeterImport](cr.meter) { // convert power to energy in kWh cr.chargedEnergy += power / 1e3 * float64(cr.clck.Since(cr.start)) / float64(time.Hour) // move timestamp @@ -142,13 +142,13 @@ func (cr *ChargeRater) ChargedEnergy() (float64, error) { } // get current energy amount - if m, ok := api.Cap[api.MeterEnergy](cr.meter); ok { - f, err := m.TotalEnergy() + if m, ok := api.Cap[api.MeterImport](cr.meter); ok { + f, err := m.ImportEnergy() if err != nil { return 0, fmt.Errorf("charge total import: %v", err) } - // late-latch baseline if StartCharge could not read TotalEnergy + // late-latch baseline if StartCharge could not read ImportEnergy // (e.g. OCPP transaction recovery before first MeterValues frame) if cr.startEnergy == nil { cr.startEnergy = &f diff --git a/core/wrapper/chargerater_test.go b/core/wrapper/chargerater_test.go index 9bc6c7628d8..29e12ee2a20 100644 --- a/core/wrapper/chargerater_test.go +++ b/core/wrapper/chargerater_test.go @@ -62,20 +62,20 @@ func TestWrappedMeter(t *testing.T) { defer ctrl.Finish() mm := api.NewMockMeter(ctrl) - me := api.NewMockMeterEnergy(ctrl) + me := api.NewMockMeterImport(ctrl) type EnergyDecorator struct { api.Meter - api.MeterEnergy + api.MeterImport } - cm := &EnergyDecorator{Meter: mm, MeterEnergy: me} + cm := &EnergyDecorator{Meter: mm, MeterImport: me} cr := NewChargeRater(util.NewLogger("foo"), cm) clck := clock.NewMock() cr.clck = clck - me.EXPECT().TotalEnergy().Return(2.0, nil) + me.EXPECT().ImportEnergy().Return(2.0, nil) cr.StartCharge(false) @@ -85,7 +85,7 @@ func TestWrappedMeter(t *testing.T) { clck.Add(time.Hour) cr.SetChargePower(0) - me.EXPECT().TotalEnergy().Return(3.0, nil) + me.EXPECT().ImportEnergy().Return(3.0, nil) cr.StopCharge() @@ -98,12 +98,12 @@ func TestWrappedMeter(t *testing.T) { cr.SetChargePower(1e3) // continue - me.EXPECT().TotalEnergy().Return(10.0, nil) + me.EXPECT().ImportEnergy().Return(10.0, nil) cr.StartCharge(true) clck.Add(time.Hour) // actual timing ignored as energy comes from meter - me.EXPECT().TotalEnergy().Return(12.0, nil) + me.EXPECT().ImportEnergy().Return(12.0, nil) cr.StopCharge() @@ -114,53 +114,53 @@ func TestWrappedMeter(t *testing.T) { // TestDeferredBaseline covers the OCPP transaction-recovery case: the meter is // not yet readable when StartCharge fires, so the baseline must be latched on -// the first successful TotalEnergy() read instead of defaulting to zero +// the first successful ImportEnergy() read instead of defaulting to zero // (which would cause the lifetime register to be reported as session energy). func TestDeferredBaseline(t *testing.T) { ctrl := gomock.NewController(t) defer ctrl.Finish() mm := api.NewMockMeter(ctrl) - me := api.NewMockMeterEnergy(ctrl) + me := api.NewMockMeterImport(ctrl) type EnergyDecorator struct { api.Meter - api.MeterEnergy + api.MeterImport } - cm := &EnergyDecorator{Meter: mm, MeterEnergy: me} + cm := &EnergyDecorator{Meter: mm, MeterImport: me} cr := NewChargeRater(util.NewLogger("foo"), cm) clck := clock.NewMock() cr.clck = clck // meter not yet available at StartCharge — recovered transaction before first MeterValues - me.EXPECT().TotalEnergy().Return(0.0, errors.New("not available")) + me.EXPECT().ImportEnergy().Return(0.0, errors.New("not available")) cr.StartCharge(true) // first read also fails — must surface the error, not a bogus delta - me.EXPECT().TotalEnergy().Return(0.0, errors.New("not available")) + me.EXPECT().ImportEnergy().Return(0.0, errors.New("not available")) if _, err := cr.ChargedEnergy(); err == nil { t.Errorf("expected error while meter unavailable") } // first successful read latches the baseline (lifetime register, e.g. 939 kWh) - me.EXPECT().TotalEnergy().Return(939.080, nil) + me.EXPECT().ImportEnergy().Return(939.080, nil) if f, err := cr.ChargedEnergy(); f != 0 || err != nil { t.Errorf("expected 0 on baseline-latch read, got %.3f %v", f, err) } // subsequent reads return delta against the latched baseline - me.EXPECT().TotalEnergy().Return(942.080, nil) + me.EXPECT().ImportEnergy().Return(942.080, nil) if f, err := cr.ChargedEnergy(); f != 3 || err != nil { t.Errorf("expected 3kWh delta, got %.3f %v", f, err) } - me.EXPECT().TotalEnergy().Return(944.080, nil) + me.EXPECT().ImportEnergy().Return(944.080, nil) cr.StopCharge() diff --git a/meter/_blueprint.go b/meter/_blueprint.go index 030b5b11daa..db3be03a2ef 100644 --- a/meter/_blueprint.go +++ b/meter/_blueprint.go @@ -49,10 +49,10 @@ func (m *Blueprint) CurrentPower() (float64, error) { return 0, api.ErrNotAvailable } -var _ api.MeterEnergy = (*Blueprint)(nil) +var _ api.MeterImport = (*Blueprint)(nil) -// TotalEnergy implements the api.MeterEnergy interface -func (m *Blueprint) TotalEnergy() (float64, error) { +// ImportEnergy implements the api.MeterImport interface +func (m *Blueprint) ImportEnergy() (float64, error) { return 0, api.ErrNotAvailable } diff --git a/meter/cfos.go b/meter/cfos.go index c350ef95fcd..57740754a73 100644 --- a/meter/cfos.go +++ b/meter/cfos.go @@ -67,10 +67,10 @@ func (wb *CfosPowerBrain) CurrentPower() (float64, error) { return float64(binary.BigEndian.Uint32(b)), err } -var _ api.MeterEnergy = (*CfosPowerBrain)(nil) +var _ api.MeterImport = (*CfosPowerBrain)(nil) -// TotalEnergy implements the api.MeterEnergy interface -func (wb *CfosPowerBrain) TotalEnergy() (float64, error) { +// ImportEnergy implements the api.MeterImport interface +func (wb *CfosPowerBrain) ImportEnergy() (float64, error) { b, err := wb.conn.ReadHoldingRegisters(cfosRegEnergy, 4) if err != nil { return 0, err diff --git a/meter/danfoss.go b/meter/danfoss.go index 75816e940ce..d3ac72f5883 100644 --- a/meter/danfoss.go +++ b/meter/danfoss.go @@ -103,7 +103,7 @@ func NewDanfossTLX(ctx context.Context, cfg comlynx.Config, maxACPower func() fl } if hasEnergy == nil { - implement.Has(m, implement.MeterEnergy(m.totalEnergy)) + implement.Has(m, implement.MeterImport(m.totalEnergy)) } if hasVoltages == nil { implement.Has(m, implement.PhaseVoltages(m.phaseVoltages)) diff --git a/meter/discovergy.go b/meter/discovergy.go index 1c4c28bdb7a..1336e309eba 100644 --- a/meter/discovergy.go +++ b/meter/discovergy.go @@ -100,9 +100,9 @@ func (m *Discovergy) CurrentPower() (float64, error) { return m.scale * float64(res.Values.Power) / 1e3, err } -var _ api.MeterEnergy = (*Discovergy)(nil) +var _ api.MeterImport = (*Discovergy)(nil) -func (m *Discovergy) TotalEnergy() (float64, error) { +func (m *Discovergy) ImportEnergy() (float64, error) { res, err := m.dataG() return float64(res.Values.Energy) / 1e10, err } diff --git a/meter/dsmr.go b/meter/dsmr.go index 696cb14d8a8..312a5cc5e5c 100644 --- a/meter/dsmr.go +++ b/meter/dsmr.go @@ -116,7 +116,7 @@ func NewDsmr(uri, energy string, timeout time.Duration) (api.Meter, error) { // decorate energy reading if energy != "" { - implement.Has(m, implement.MeterEnergy(m.totalEnergy)) + implement.Has(m, implement.MeterImport(m.totalEnergy)) } // decorate currents @@ -284,7 +284,7 @@ func (m *Dsmr) CurrentPower() (float64, error) { return (bezug - lief) * 1e3, errors.Join(err1, err2) } -// totalEnergy implements the api.MeterEnergy interface +// totalEnergy implements the api.MeterImport interface func (m *Dsmr) totalEnergy() (float64, error) { return m.get(m.energy) } diff --git a/meter/eebus.go b/meter/eebus.go index 383951c7b62..5be2f46e2e1 100644 --- a/meter/eebus.go +++ b/meter/eebus.go @@ -176,9 +176,9 @@ func (c *EEBus) CurrentPower() (float64, error) { return c.readValue(c.scenarios.power, c.mm.Power) } -var _ api.MeterEnergy = (*EEBus)(nil) +var _ api.MeterImport = (*EEBus)(nil) -func (c *EEBus) TotalEnergy() (float64, error) { +func (c *EEBus) ImportEnergy() (float64, error) { return c.readValue(c.scenarios.energy, c.mm.EnergyConsumed) } diff --git a/meter/fritz/aha/aha.go b/meter/fritz/aha/aha.go index 8341585b4ef..6d1cf3257d6 100644 --- a/meter/fritz/aha/aha.go +++ b/meter/fritz/aha/aha.go @@ -91,10 +91,10 @@ func (c *Connection) CurrentPower() (float64, error) { return power / 1000, err // mW ==> W } -var _ api.MeterEnergy = (*Connection)(nil) +var _ api.MeterImport = (*Connection)(nil) -// TotalEnergy implements the api.MeterEnergy interface -func (c *Connection) TotalEnergy() (float64, error) { +// ImportEnergy implements the api.MeterImport interface +func (c *Connection) ImportEnergy() (float64, error) { // Energy value in Wh (total switch energy, refresh approximately every 2 minutes) resp, err := c.ExecCmd("getswitchenergy") if err != nil { diff --git a/meter/fritz/api.go b/meter/fritz/api.go index 371f241aca2..b00d6eeb81e 100644 --- a/meter/fritz/api.go +++ b/meter/fritz/api.go @@ -3,7 +3,7 @@ package fritz // Meter defines the interface for Fritz connections (both legacy LUA and REST) type Meter interface { CurrentPower() (float64, error) - TotalEnergy() (float64, error) + ImportEnergy() (float64, error) } // Switch extends Meter with switch control capabilities diff --git a/meter/fritz/smarthome/smarthome.go b/meter/fritz/smarthome/smarthome.go index ef9db252aa1..f975a34802f 100644 --- a/meter/fritz/smarthome/smarthome.go +++ b/meter/fritz/smarthome/smarthome.go @@ -138,10 +138,10 @@ func (c *Connection) CurrentPower() (float64, error) { return 0, api.ErrNotAvailable } -var _ api.MeterEnergy = (*Connection)(nil) +var _ api.MeterImport = (*Connection)(nil) -// TotalEnergy implements the api.MeterEnergy interface -func (c *Connection) TotalEnergy() (float64, error) { +// ImportEnergy implements the api.MeterImport interface +func (c *Connection) ImportEnergy() (float64, error) { unit, err := c.unitG.Get() if err != nil { return 0, err diff --git a/meter/homeassistant.go b/meter/homeassistant.go index 58c14175b29..64fb3d95479 100644 --- a/meter/homeassistant.go +++ b/meter/homeassistant.go @@ -89,7 +89,7 @@ func NewHomeAssistantFromConfig(other map[string]any) (api.Meter, error) { powersG = func() (float64, float64, float64, error) { return conn.GetPhaseFloatStates(phases) } } - implement.May(m, implement.MeterEnergy(energyG)) + implement.May(m, implement.MeterImport(energyG)) if cc.Soc != "" { socG := func() (float64, error) { return conn.GetFloatState(cc.Soc) } diff --git a/meter/homematic.go b/meter/homematic.go index ef71a453de4..0ce059fc458 100644 --- a/meter/homematic.go +++ b/meter/homematic.go @@ -63,12 +63,12 @@ func (c *CCU) CurrentPower() (float64, error) { return c.conn.CurrentPower() } -var _ api.MeterEnergy = (*CCU)(nil) +var _ api.MeterImport = (*CCU)(nil) -// TotalEnergy implements the api.MeterEnergy interface -func (c *CCU) TotalEnergy() (float64, error) { +// ImportEnergy implements the api.MeterImport interface +func (c *CCU) ImportEnergy() (float64, error) { if c.usage == "grid" { - return c.conn.GridTotalEnergy() + return c.conn.GridImportEnergy() } - return c.conn.TotalEnergy() + return c.conn.ImportEnergy() } diff --git a/meter/homematic/connection.go b/meter/homematic/connection.go index 59083d1f869..7efbebb24b8 100644 --- a/meter/homematic/connection.go +++ b/meter/homematic/connection.go @@ -91,8 +91,8 @@ func (c *Connection) CurrentPower() (float64, error) { return res.FloatValue("POWER"), err } -// TotalEnergy reads the homematic HMIP-PSM meterchannel energy in Wh -func (c *Connection) TotalEnergy() (float64, error) { +// ImportEnergy reads the homematic HMIP-PSM meterchannel energy in Wh +func (c *Connection) ImportEnergy() (float64, error) { res, err := c.meterG.Get() return res.FloatValue("ENERGY_COUNTER") / 1e3, err } @@ -109,8 +109,8 @@ func (c *Connection) GridCurrentPower() (float64, error) { return res.FloatValue("IEC_POWER"), err } -// GridTotalEnergy reads the homematic HM-ES-TX-WM grid meterchannel energy in kWh -func (c *Connection) GridTotalEnergy() (float64, error) { +// GridImportEnergy reads the homematic HM-ES-TX-WM grid meterchannel energy in kWh +func (c *Connection) GridImportEnergy() (float64, error) { res, err := c.meterG.Get() return res.FloatValue("IEC_ENERGY_COUNTER"), err } diff --git a/meter/homewizard.go b/meter/homewizard.go index 7e72b7cd8c7..60b83cc0e96 100644 --- a/meter/homewizard.go +++ b/meter/homewizard.go @@ -56,11 +56,11 @@ func (c *HomeWizard) CurrentPower() (float64, error) { return c.conn.CurrentPower() } -var _ api.MeterEnergy = (*HomeWizard)(nil) +var _ api.MeterImport = (*HomeWizard)(nil) -// TotalEnergy implements the api.MeterEnergy interface -func (c *HomeWizard) TotalEnergy() (float64, error) { - return c.conn.TotalEnergy() +// ImportEnergy implements the api.MeterImport interface +func (c *HomeWizard) ImportEnergy() (float64, error) { + return c.conn.ImportEnergy() } var _ api.PhaseCurrents = (*HomeWizard)(nil) diff --git a/meter/homewizard/connection.go b/meter/homewizard/connection.go index 526712ef9c4..5683b9c29db 100644 --- a/meter/homewizard/connection.go +++ b/meter/homewizard/connection.go @@ -107,8 +107,8 @@ func (c *Connection) CurrentPower() (float64, error) { return res.ActivePowerW, err } -// TotalEnergy implements the api.MeterEnergy interface -func (c *Connection) TotalEnergy() (float64, error) { +// ImportEnergy implements the api.MeterImport interface +func (c *Connection) ImportEnergy() (float64, error) { res, err := c.dataG.Get() if c.usage == "pv" { return res.TotalPowerExportT1kWh + res.TotalPowerExportT2kWh + res.TotalPowerExportT3kWh + res.TotalPowerExportT4kWh, err diff --git a/meter/lgess.go b/meter/lgess.go index 2052530f904..f2710dd482f 100644 --- a/meter/lgess.go +++ b/meter/lgess.go @@ -77,7 +77,7 @@ func NewLgEss(uri, usage, registration, password string, cache time.Duration, ba implement.May(m, implement.BatteryPowerLimiter(batteryPowerLimits.Decorator())) if m.usage == "grid" && essType != lgpcs.LgEss15 { - implement.Has(m, implement.MeterEnergy(m.totalEnergy)) + implement.Has(m, implement.MeterImport(m.totalEnergy)) } if usage == "battery" { @@ -111,7 +111,7 @@ func (m *LgEss) CurrentPower() (float64, error) { } } -// totalEnergy implements the api.MeterEnergy interface +// totalEnergy implements the api.MeterImport interface func (m *LgEss) totalEnergy() (float64, error) { data, err := m.conn.Data() if err != nil { diff --git a/meter/mbmd.go b/meter/mbmd.go index be773b5b947..c6a4c176212 100644 --- a/meter/mbmd.go +++ b/meter/mbmd.go @@ -100,7 +100,7 @@ func NewMbmdFromConfig(ctx context.Context, other map[string]any) (api.Meter, er if err != nil { return nil, fmt.Errorf("invalid measurement for energy: %s", cc.Energy) } - implement.Has(m, implement.MeterEnergy(totalEnergy)) + implement.Has(m, implement.MeterImport(totalEnergy)) } // decorate soc diff --git a/meter/measurement/energy.go b/meter/measurement/energy.go index 658a91460ac..e4b41b29dc6 100644 --- a/meter/measurement/energy.go +++ b/meter/measurement/energy.go @@ -9,23 +9,47 @@ import ( type Energy struct { Power plugin.Config - Energy *plugin.Config // optional + Import *plugin.Config // optional + Export *plugin.Config // optional + // Energy is a legacy alias used when neither Import nor Export is set. + // For meters, the same value is wired to both interfaces and the site role + // (pv/battery → Export; grid/aux/ext → Import) decides which one is read. + Energy *plugin.Config } func (cc *Energy) Configure(ctx context.Context) ( + func() (float64, error), func() (float64, error), func() (float64, error), error, ) { powerG, err := cc.Power.FloatGetter(ctx) if err != nil { - return nil, nil, fmt.Errorf("power: %w", err) + return nil, nil, nil, fmt.Errorf("power: %w", err) + } + + // legacy: a single energy field is exposed as both import and export; + // the site role decides which interface is used. + importCfg := cc.Import + exportCfg := cc.Export + if cc.Energy != nil { + if importCfg == nil { + importCfg = cc.Energy + } + if exportCfg == nil { + exportCfg = cc.Energy + } + } + + importG, err := importCfg.FloatGetter(ctx) + if err != nil { + return nil, nil, nil, fmt.Errorf("import: %w", err) } - energyG, err := cc.Energy.FloatGetter(ctx) + exportG, err := exportCfg.FloatGetter(ctx) if err != nil { - return nil, nil, fmt.Errorf("energy: %w", err) + return nil, nil, nil, fmt.Errorf("export: %w", err) } - return powerG, energyG, nil + return powerG, importG, exportG, nil } diff --git a/meter/meter.go b/meter/meter.go index 21b265c4ac0..dae579cb1a8 100644 --- a/meter/meter.go +++ b/meter/meter.go @@ -42,13 +42,14 @@ func NewConfigurableFromConfig(ctx context.Context, other map[string]any) (api.M return nil, err } - powerG, energyG, err := cc.Energy.Configure(ctx) + powerG, importG, exportG, err := cc.Energy.Configure(ctx) if err != nil { return nil, err } m, _ := NewConfigurable(powerG) - implement.May(m, implement.MeterEnergy(energyG)) + implement.May(m, implement.MeterImport(importG)) + implement.May(m, implement.MeterExport(exportG)) // decorate soc socG, err := cc.Soc.FloatGetter(ctx) diff --git a/meter/meter_average.go b/meter/meter_average.go index f217f75ae99..5d1690dddbc 100644 --- a/meter/meter_average.go +++ b/meter/meter_average.go @@ -41,10 +41,16 @@ func NewMovingAverageFromConfig(ctx context.Context, other map[string]any) (api. meter, _ := NewConfigurable(mav.CurrentPower) - // decorate energy reading - var totalEnergy func() (float64, error) - if m, ok := api.Cap[api.MeterEnergy](m); ok { - totalEnergy = m.TotalEnergy + // decorate import reading + var importEnergy func() (float64, error) + if m, ok := api.Cap[api.MeterImport](m); ok { + importEnergy = m.ImportEnergy + } + + // decorate export reading + var exportEnergy func() (float64, error) + if m, ok := api.Cap[api.MeterExport](m); ok { + exportEnergy = m.ExportEnergy } // decorate battery reading @@ -71,7 +77,8 @@ func NewMovingAverageFromConfig(ctx context.Context, other map[string]any) (api. powers = m.Powers } - implement.May(meter, implement.MeterEnergy(totalEnergy)) + implement.May(meter, implement.MeterImport(importEnergy)) + implement.May(meter, implement.MeterExport(exportEnergy)) if batterySoc != nil { implement.Has(meter, implement.Battery(batterySoc)) diff --git a/meter/powerwall.go b/meter/powerwall.go index c84d1e2df41..6c36ba0f79e 100644 --- a/meter/powerwall.go +++ b/meter/powerwall.go @@ -142,8 +142,12 @@ func NewPowerWall(uri, usage, user, password string, cache time.Duration, refres m.energySite = energySite } - if m.usage == "load" || m.usage == "solar" { - implement.Has(m, implement.MeterEnergy(m.totalEnergy)) + if m.usage == "load" { + implement.Has(m, implement.MeterImport(m.totalEnergy)) + } + + if m.usage == "solar" { + implement.Has(m, implement.MeterExport(m.totalEnergy)) } if usage == "battery" { @@ -193,7 +197,7 @@ func (m *PowerWall) CurrentPower() (float64, error) { return 0, fmt.Errorf("invalid usage: %s", m.usage) } -// totalEnergy implements the api.MeterEnergy interface +// totalEnergy returns import (load) or export (solar) energy depending on usage func (m *PowerWall) totalEnergy() (float64, error) { res, err := m.meterG() if err != nil { diff --git a/meter/rct.go b/meter/rct.go index 1b58a4c4145..f1507f7bea1 100644 --- a/meter/rct.go +++ b/meter/rct.go @@ -100,7 +100,7 @@ func NewRCT(ctx context.Context, uri, usage string, batterySocLimits batterySocL } if usage == "grid" { - implement.Has(m, implement.MeterEnergy(m.totalEnergy)) + implement.Has(m, implement.MeterImport(m.totalEnergy)) } if usage == "pv" { @@ -264,7 +264,7 @@ func (m *RCT) CurrentPower() (float64, error) { } } -// totalEnergy implements the api.MeterEnergy interface +// totalEnergy implements the api.MeterImport interface func (m *RCT) totalEnergy() (float64, error) { switch m.usage { case "grid": diff --git a/meter/shelly/connection.go b/meter/shelly/connection.go index 1806140238d..c80dc6ff7f5 100644 --- a/meter/shelly/connection.go +++ b/meter/shelly/connection.go @@ -15,7 +15,7 @@ type Generation interface { CurrentPower() (float64, error) Enabled() (bool, error) Enable(bool) error - TotalEnergy() (float64, error) + ImportEnergy() (float64, error) } type Phases interface { diff --git a/meter/shelly/gen1.go b/meter/shelly/gen1.go index 512111f28c6..804b59657ba 100644 --- a/meter/shelly/gen1.go +++ b/meter/shelly/gen1.go @@ -106,7 +106,7 @@ func (c *gen1) Enable(enable bool) error { return c.GetJSON(uri, &res) } -func (c *gen1) TotalEnergy() (float64, error) { +func (c *gen1) ImportEnergy() (float64, error) { var energy float64 res, err := c.status.Get() if err != nil { diff --git a/meter/shelly/gen2.go b/meter/shelly/gen2.go index 58209b2e15c..f5f835160d9 100644 --- a/meter/shelly/gen2.go +++ b/meter/shelly/gen2.go @@ -219,8 +219,8 @@ func (c *gen2) Enable(enable bool) error { return c.execEnableCmd(c.switchchannel, "Switch.Set", enable, &res) } -// TotalEnergy implements the api.Meter interface -func (c *gen2) TotalEnergy() (float64, error) { +// ImportEnergy implements the api.MeterImport interface +func (c *gen2) ImportEnergy() (float64, error) { switch { case c.hasEM1Endpoint(): res, err := c.em1data() diff --git a/meter/sma.go b/meter/sma.go index 2ae5d59fecb..92df4b884d1 100644 --- a/meter/sma.go +++ b/meter/sma.go @@ -87,7 +87,7 @@ func NewSMA(uri, password, iface string, serial uint32, scale float64, usage str go sm.device.Run() if usage == "battery" { - implement.May(sm, implement.MeterEnergy(sm.TotalEnergy)) + implement.May(sm, implement.MeterExport(sm.ExportEnergy)) implement.Has(sm, implement.Battery(sm.soc)) implement.May(sm, implement.BatteryCapacity(capacity)) implement.May(sm, implement.BatterySocLimiter(batterySocLimits)) @@ -103,10 +103,10 @@ func (sm *SMA) CurrentPower() (float64, error) { return sm.scale * (sma.AsFloat(values[sunny.ActivePowerPlus]) - sma.AsFloat(values[sunny.ActivePowerMinus])), err } -var _ api.MeterEnergy = (*SMA)(nil) +var _ api.MeterExport = (*SMA)(nil) -// TotalEnergy implements the api.MeterEnergy interface -func (sm *SMA) TotalEnergy() (float64, error) { +// ExportEnergy implements the api.MeterExport interface +func (sm *SMA) ExportEnergy() (float64, error) { values, err := sm.device.Values() if sm.scale < 0 { return sma.AsFloat(values[sunny.ActiveEnergyMinus]) / 3600000, err diff --git a/meter/tasmota.go b/meter/tasmota.go index 58aa21aae38..45b9443b03f 100644 --- a/meter/tasmota.go +++ b/meter/tasmota.go @@ -89,11 +89,11 @@ func (c *Tasmota) CurrentPower() (float64, error) { return power, nil } -var _ api.MeterEnergy = (*Tasmota)(nil) +var _ api.MeterImport = (*Tasmota)(nil) -// TotalEnergy implements the api.MeterEnergy interface -func (c *Tasmota) TotalEnergy() (float64, error) { - return c.conn.TotalEnergy() +// ImportEnergy implements the api.MeterImport interface +func (c *Tasmota) ImportEnergy() (float64, error) { + return c.conn.ImportEnergy() } // powers implements the api.PhasePowers interface diff --git a/meter/tasmota/connection.go b/meter/tasmota/connection.go index 2339a0157cc..f9ba6395db2 100644 --- a/meter/tasmota/connection.go +++ b/meter/tasmota/connection.go @@ -222,8 +222,8 @@ func (c *Connection) CurrentPower() (float64, error) { return res, nil } -// TotalEnergy implements the api.MeterEnergy interface -func (c *Connection) TotalEnergy() (float64, error) { +// ImportEnergy implements the api.MeterImport interface +func (c *Connection) ImportEnergy() (float64, error) { res, err := c.statusSnsG.Get() if err != nil { return 0, err diff --git a/meter/tplink/connection.go b/meter/tplink/connection.go index cd7c2d3165e..c7917da0ee6 100644 --- a/meter/tplink/connection.go +++ b/meter/tplink/connection.go @@ -94,8 +94,8 @@ func (d *Connection) CurrentPower() (float64, error) { return power, nil } -// TotalEnergy implements the api.MeterEnergy interface -func (d *Connection) TotalEnergy() (float64, error) { +// ImportEnergy implements the api.MeterImport interface +func (d *Connection) ImportEnergy() (float64, error) { var res EmeterResponse if err := d.ExecCmd(`{"emeter":{"get_realtime":null}}`, &res); err != nil { return 0, err diff --git a/meter/tq-em.go b/meter/tq-em.go index 86885d0eead..dce9cd201d2 100644 --- a/meter/tq-em.go +++ b/meter/tq-em.go @@ -143,9 +143,9 @@ func (m *TqEm) CurrentPower() (float64, error) { return res.Obis1_4_0 - res.Obis2_4_0, err } -var _ api.MeterEnergy = (*TqEm)(nil) +var _ api.MeterImport = (*TqEm)(nil) -func (m *TqEm) TotalEnergy() (float64, error) { +func (m *TqEm) ImportEnergy() (float64, error) { res, err := m.dataG() return res.Obis1_8_0 / 1e3, err } diff --git a/meter/tq-em420.go b/meter/tq-em420.go index 450efd54726..092ec4aa2f6 100644 --- a/meter/tq-em420.go +++ b/meter/tq-em420.go @@ -146,9 +146,9 @@ func (m *TqEM420) CurrentPower() (float64, error) { return (res.SmartMeter.Values.ActivePowerP - res.SmartMeter.Values.ActivePowerM) / 1e3, err } -var _ api.MeterEnergy = (*TqEM420)(nil) +var _ api.MeterImport = (*TqEM420)(nil) -func (m *TqEM420) TotalEnergy() (float64, error) { +func (m *TqEM420) ImportEnergy() (float64, error) { res, err := m.dataG() return res.SmartMeter.Values.ActiveEnergyP / 1e6, err } diff --git a/plugin/meter.go b/plugin/meter.go index a59d89fa7d8..4ba89e20a56 100644 --- a/plugin/meter.go +++ b/plugin/meter.go @@ -55,7 +55,7 @@ func (o *meterPlugin) FloatGetter() (func() (float64, error), error) { switch o.method { case Energy: - if !api.HasCap[api.MeterEnergy](o.meter) { + if !api.HasCap[api.MeterImport](o.meter) && !api.HasCap[api.MeterExport](o.meter) { return nil, err } case Soc: @@ -67,8 +67,12 @@ func (o *meterPlugin) FloatGetter() (func() (float64, error), error) { return func() (float64, error) { switch o.method { case Energy: - m, _ := api.Cap[api.MeterEnergy](o.meter) - f, err := m.TotalEnergy() + if m, ok := api.Cap[api.MeterImport](o.meter); ok { + f, err := m.ImportEnergy() + return f * o.scale, err + } + m, _ := api.Cap[api.MeterExport](o.meter) + f, err := m.ExportEnergy() return f * o.scale, err case Soc: m, _ := api.Cap[api.Battery](o.meter) diff --git a/server/http_config_helper.go b/server/http_config_helper.go index 921449c556b..6570fc3918d 100644 --- a/server/http_config_helper.go +++ b/server/http_config_helper.go @@ -274,8 +274,11 @@ func testInstance(instance any) map[string]testResult { makeResult("power", val, err) } - if dev, ok := api.Cap[api.MeterEnergy](instance); ok { - val, err := dev.TotalEnergy() + if dev, ok := api.Cap[api.MeterImport](instance); ok { + val, err := dev.ImportEnergy() + makeResult("energy", val, err) + } else if dev, ok := api.Cap[api.MeterExport](instance); ok { + val, err := dev.ExportEnergy() makeResult("energy", val, err) } From f9fb65b1d178ae6847150ad159a6eb09cca29e0c Mon Sep 17 00:00:00 2001 From: andig Date: Sun, 10 May 2026 11:32:12 +0200 Subject: [PATCH 2/5] Add fallback --- charger/charger.go | 12 ++++-- charger/{measurement => heating}/heating.go | 2 +- charger/heatpump.go | 20 ++++++--- charger/measurement/energy.go | 47 --------------------- charger/sgready-relay.go | 20 ++++++--- charger/sgready.go | 21 ++++++--- meter/measurement/energy.go | 34 +++++++-------- meter/meter.go | 6 +++ 8 files changed, 73 insertions(+), 89 deletions(-) rename charger/{measurement => heating}/heating.go (96%) delete mode 100644 charger/measurement/energy.go diff --git a/charger/charger.go b/charger/charger.go index 42257d9d36e..2076717e500 100644 --- a/charger/charger.go +++ b/charger/charger.go @@ -7,7 +7,8 @@ import ( "github.com/evcc-io/evcc/api" "github.com/evcc-io/evcc/api/implement" - "github.com/evcc-io/evcc/charger/measurement" + "github.com/evcc-io/evcc/charger/heating" + "github.com/evcc-io/evcc/meter/measurement" meter "github.com/evcc-io/evcc/meter/measurement" "github.com/evcc-io/evcc/plugin" "github.com/evcc-io/evcc/util" @@ -39,9 +40,10 @@ func NewConfigurableFromConfig(ctx context.Context, other map[string]any) (api.C LimitSoc *plugin.Config FinishTime *plugin.Config Tos bool - measurement.Temperature `mapstructure:",squash"` // optional, for heating devices + heating.Temperature `mapstructure:",squash"` // optional, for heating devices measurement.Energy `mapstructure:",squash"` // optional meter.Phases `mapstructure:",squash"` // optional + LegacyEnergy *plugin.Config `mapstructure:"energy"` // TODO deprecated } if err := util.DecodeOther(other, &cc); err != nil { @@ -144,7 +146,11 @@ func NewConfigurableFromConfig(ctx context.Context, other map[string]any) (api.C implement.May(c, implement.Battery(soc)) implement.May(c, implement.SocLimiter(limitsoc)) - // decorate measurements + // TODO deprecated + if err := cc.Energy.AliasImport(cc.LegacyEnergy); err != nil { + return nil, err + } + powerG, importG, _, err := cc.Energy.Configure(ctx) if err != nil { return nil, err diff --git a/charger/measurement/heating.go b/charger/heating/heating.go similarity index 96% rename from charger/measurement/heating.go rename to charger/heating/heating.go index 3e865fa9057..57d803fbedc 100644 --- a/charger/measurement/heating.go +++ b/charger/heating/heating.go @@ -1,4 +1,4 @@ -package measurement +package heating import ( "context" diff --git a/charger/heatpump.go b/charger/heatpump.go index 5ca3d1b4930..f8b78e57752 100644 --- a/charger/heatpump.go +++ b/charger/heatpump.go @@ -22,8 +22,9 @@ import ( "github.com/evcc-io/evcc/api" "github.com/evcc-io/evcc/api/implement" - "github.com/evcc-io/evcc/charger/measurement" + "github.com/evcc-io/evcc/charger/heating" "github.com/evcc-io/evcc/core/loadpoint" + "github.com/evcc-io/evcc/meter/measurement" "github.com/evcc-io/evcc/plugin" "github.com/evcc-io/evcc/util" ) @@ -45,11 +46,13 @@ func init() { // NewHeatpumpFromConfig creates heatpump configurable charger from generic config func NewHeatpumpFromConfig(ctx context.Context, other map[string]any) (api.Charger, error) { cc := struct { - embed `mapstructure:",squash"` - SetMaxPower plugin.Config - GetMaxPower *plugin.Config // optional - measurement.Temperature `mapstructure:",squash"` - measurement.Energy `mapstructure:",squash"` + embed `mapstructure:",squash"` + SetMaxPower plugin.Config + GetMaxPower *plugin.Config // optional + heating.Temperature `mapstructure:",squash"` + measurement.Energy `mapstructure:",squash"` + LegacyEnergy *plugin.Config `mapstructure:"energy"` // TODO deprecated + }{ embed: embed{ Icon_: "heatpump", @@ -80,6 +83,11 @@ func NewHeatpumpFromConfig(ctx context.Context, other map[string]any) (api.Charg return nil, err } + // TODO deprecated + if err := cc.Energy.AliasImport(cc.LegacyEnergy); err != nil { + return nil, err + } + powerG, importG, _, err := cc.Energy.Configure(ctx) if err != nil { return nil, err diff --git a/charger/measurement/energy.go b/charger/measurement/energy.go deleted file mode 100644 index 5b07ab6727c..00000000000 --- a/charger/measurement/energy.go +++ /dev/null @@ -1,47 +0,0 @@ -package measurement - -import ( - "context" - "fmt" - - "github.com/evcc-io/evcc/plugin" -) - -type Energy struct { - Power *plugin.Config // optional - Import *plugin.Config // optional - Export *plugin.Config // optional - // Energy is a legacy alias for Import. Chargers always import energy, so - // the alias only feeds the Import getter (unlike meters, which alias to both). - Energy *plugin.Config -} - -func (cc *Energy) Configure(ctx context.Context) ( - func() (float64, error), - func() (float64, error), - func() (float64, error), - error, -) { - powerG, err := cc.Power.FloatGetter(ctx) - if err != nil { - return nil, nil, nil, fmt.Errorf("power: %w", err) - } - - // legacy: chargers always import, so 'energy' aliases to import only. - importCfg := cc.Import - if cc.Energy != nil && importCfg == nil { - importCfg = cc.Energy - } - - importG, err := importCfg.FloatGetter(ctx) - if err != nil { - return nil, nil, nil, fmt.Errorf("import: %w", err) - } - - exportG, err := cc.Export.FloatGetter(ctx) - if err != nil { - return nil, nil, nil, fmt.Errorf("export: %w", err) - } - - return powerG, importG, exportG, nil -} diff --git a/charger/sgready-relay.go b/charger/sgready-relay.go index c281a2bb471..6a9d04c4787 100644 --- a/charger/sgready-relay.go +++ b/charger/sgready-relay.go @@ -6,7 +6,9 @@ import ( "github.com/evcc-io/evcc/api" "github.com/evcc-io/evcc/api/implement" - "github.com/evcc-io/evcc/charger/measurement" + "github.com/evcc-io/evcc/charger/heating" + "github.com/evcc-io/evcc/meter/measurement" + "github.com/evcc-io/evcc/plugin" "github.com/evcc-io/evcc/util" "github.com/evcc-io/evcc/util/config" ) @@ -27,11 +29,12 @@ func init() { // NewSgReadyRelayFromConfig creates an SG Ready charger with boost/dim relays from generic config func NewSgReadyRelayFromConfig(ctx context.Context, other map[string]any) (api.Charger, error) { cc := struct { - embed `mapstructure:",squash"` - Boost config.Typed - Dim *config.Typed - measurement.Temperature `mapstructure:",squash"` - measurement.Energy `mapstructure:",squash"` + embed `mapstructure:",squash"` + Boost config.Typed + Dim *config.Typed + heating.Temperature `mapstructure:",squash"` + measurement.Energy `mapstructure:",squash"` + LegacyEnergy *plugin.Config `mapstructure:"energy"` // TODO deprecated }{ embed: embed{ Icon_: "heatpump", @@ -61,6 +64,11 @@ func NewSgReadyRelayFromConfig(ctx context.Context, other map[string]any) (api.C return nil, err } + // TODO deprecated + if err := cc.Energy.AliasImport(cc.LegacyEnergy); err != nil { + return nil, err + } + powerG, importG, _, err := cc.Energy.Configure(ctx) if err != nil { return nil, err diff --git a/charger/sgready.go b/charger/sgready.go index 909dd66e9aa..2223d1a4c88 100644 --- a/charger/sgready.go +++ b/charger/sgready.go @@ -22,8 +22,9 @@ import ( "github.com/evcc-io/evcc/api" "github.com/evcc-io/evcc/api/implement" - "github.com/evcc-io/evcc/charger/measurement" + "github.com/evcc-io/evcc/charger/heating" "github.com/evcc-io/evcc/core/loadpoint" + "github.com/evcc-io/evcc/meter/measurement" "github.com/evcc-io/evcc/plugin" "github.com/evcc-io/evcc/util" ) @@ -56,12 +57,13 @@ const ( // NewSgReadyFromConfig creates an SG Ready configurable charger from generic config func NewSgReadyFromConfig(ctx context.Context, other map[string]any) (api.Charger, error) { cc := struct { - embed `mapstructure:",squash"` - SetMode plugin.Config - GetMode *plugin.Config // optional - SetMaxPower *plugin.Config // optional - measurement.Temperature `mapstructure:",squash"` - measurement.Energy `mapstructure:",squash"` + embed `mapstructure:",squash"` + SetMode plugin.Config + GetMode *plugin.Config // optional + SetMaxPower *plugin.Config // optional + heating.Temperature `mapstructure:",squash"` + measurement.Energy `mapstructure:",squash"` + LegacyEnergy *plugin.Config `mapstructure:"energy"` // TODO deprecated }{ embed: embed{ Icon_: "heatpump", @@ -107,6 +109,11 @@ func NewSgReadyFromConfig(ctx context.Context, other map[string]any) (api.Charge return nil, err } + // TODO deprecated + if err := cc.Energy.AliasImport(cc.LegacyEnergy); err != nil { + return nil, err + } + powerG, importG, _, err := cc.Energy.Configure(ctx) if err != nil { return nil, err diff --git a/meter/measurement/energy.go b/meter/measurement/energy.go index e4b41b29dc6..e91f338eb63 100644 --- a/meter/measurement/energy.go +++ b/meter/measurement/energy.go @@ -2,6 +2,7 @@ package measurement import ( "context" + "errors" "fmt" "github.com/evcc-io/evcc/plugin" @@ -11,10 +12,6 @@ type Energy struct { Power plugin.Config Import *plugin.Config // optional Export *plugin.Config // optional - // Energy is a legacy alias used when neither Import nor Export is set. - // For meters, the same value is wired to both interfaces and the site role - // (pv/battery → Export; grid/aux/ext → Import) decides which one is read. - Energy *plugin.Config } func (cc *Energy) Configure(ctx context.Context) ( @@ -28,28 +25,27 @@ func (cc *Energy) Configure(ctx context.Context) ( return nil, nil, nil, fmt.Errorf("power: %w", err) } - // legacy: a single energy field is exposed as both import and export; - // the site role decides which interface is used. - importCfg := cc.Import - exportCfg := cc.Export - if cc.Energy != nil { - if importCfg == nil { - importCfg = cc.Energy - } - if exportCfg == nil { - exportCfg = cc.Energy - } - } - - importG, err := importCfg.FloatGetter(ctx) + importG, err := cc.Import.FloatGetter(ctx) if err != nil { return nil, nil, nil, fmt.Errorf("import: %w", err) } - exportG, err := exportCfg.FloatGetter(ctx) + exportG, err := cc.Export.FloatGetter(ctx) if err != nil { return nil, nil, nil, fmt.Errorf("export: %w", err) } return powerG, importG, exportG, nil } + +// AliasImport assigns the legacy energy field onto Import +func (cc *Energy) AliasImport(energy *plugin.Config) error { + if energy == nil { + return nil + } + if cc.Import != nil { + return errors.New("energy and import/export are mutually exclusive") + } + cc.Import = energy + return nil +} diff --git a/meter/meter.go b/meter/meter.go index dae579cb1a8..e7c5da720c9 100644 --- a/meter/meter.go +++ b/meter/meter.go @@ -20,6 +20,7 @@ func NewConfigurableFromConfig(ctx context.Context, other map[string]any) (api.M cc := struct { measurement.Energy `mapstructure:",squash"` // energy optional measurement.Phases `mapstructure:",squash"` // optional + LegacyEnergy *plugin.Config `mapstructure:"energy"` // TODO deprecated // pv pvMaxACPower `mapstructure:",squash"` @@ -42,6 +43,11 @@ func NewConfigurableFromConfig(ctx context.Context, other map[string]any) (api.M return nil, err } + // TODO deprecated + if err := cc.Energy.AliasImport(cc.LegacyEnergy); err != nil { + return nil, err + } + powerG, importG, exportG, err := cc.Energy.Configure(ctx) if err != nil { return nil, err From 8b1ececb9b99e0d9cdf70c54fd4b827e9bb6fc10 Mon Sep 17 00:00:00 2001 From: andig Date: Sun, 10 May 2026 11:47:29 +0200 Subject: [PATCH 3/5] wip --- charger/charger.go | 5 +- charger/heatpump.go | 15 +++--- charger/measurement/energy.go | 51 +++++++++++++++++++++ charger/{heating => measurement}/heating.go | 2 +- charger/sgready-relay.go | 15 +++--- charger/sgready.go | 17 ++++--- 6 files changed, 76 insertions(+), 29 deletions(-) create mode 100644 charger/measurement/energy.go rename charger/{heating => measurement}/heating.go (96%) diff --git a/charger/charger.go b/charger/charger.go index 2076717e500..3ef9a22b632 100644 --- a/charger/charger.go +++ b/charger/charger.go @@ -7,8 +7,7 @@ import ( "github.com/evcc-io/evcc/api" "github.com/evcc-io/evcc/api/implement" - "github.com/evcc-io/evcc/charger/heating" - "github.com/evcc-io/evcc/meter/measurement" + "github.com/evcc-io/evcc/charger/measurement" meter "github.com/evcc-io/evcc/meter/measurement" "github.com/evcc-io/evcc/plugin" "github.com/evcc-io/evcc/util" @@ -40,7 +39,7 @@ func NewConfigurableFromConfig(ctx context.Context, other map[string]any) (api.C LimitSoc *plugin.Config FinishTime *plugin.Config Tos bool - heating.Temperature `mapstructure:",squash"` // optional, for heating devices + measurement.Temperature `mapstructure:",squash"` // optional, for heating devices measurement.Energy `mapstructure:",squash"` // optional meter.Phases `mapstructure:",squash"` // optional LegacyEnergy *plugin.Config `mapstructure:"energy"` // TODO deprecated diff --git a/charger/heatpump.go b/charger/heatpump.go index f8b78e57752..bc102a28c86 100644 --- a/charger/heatpump.go +++ b/charger/heatpump.go @@ -22,9 +22,8 @@ import ( "github.com/evcc-io/evcc/api" "github.com/evcc-io/evcc/api/implement" - "github.com/evcc-io/evcc/charger/heating" + "github.com/evcc-io/evcc/charger/measurement" "github.com/evcc-io/evcc/core/loadpoint" - "github.com/evcc-io/evcc/meter/measurement" "github.com/evcc-io/evcc/plugin" "github.com/evcc-io/evcc/util" ) @@ -46,12 +45,12 @@ func init() { // NewHeatpumpFromConfig creates heatpump configurable charger from generic config func NewHeatpumpFromConfig(ctx context.Context, other map[string]any) (api.Charger, error) { cc := struct { - embed `mapstructure:",squash"` - SetMaxPower plugin.Config - GetMaxPower *plugin.Config // optional - heating.Temperature `mapstructure:",squash"` - measurement.Energy `mapstructure:",squash"` - LegacyEnergy *plugin.Config `mapstructure:"energy"` // TODO deprecated + embed `mapstructure:",squash"` + SetMaxPower plugin.Config + GetMaxPower *plugin.Config // optional + measurement.Temperature `mapstructure:",squash"` + measurement.Energy `mapstructure:",squash"` + LegacyEnergy *plugin.Config `mapstructure:"energy"` // TODO deprecated }{ embed: embed{ diff --git a/charger/measurement/energy.go b/charger/measurement/energy.go new file mode 100644 index 00000000000..196d57237c9 --- /dev/null +++ b/charger/measurement/energy.go @@ -0,0 +1,51 @@ +package measurement + +import ( + "context" + "errors" + "fmt" + + "github.com/evcc-io/evcc/plugin" +) + +type Energy struct { + Power *plugin.Config + Import *plugin.Config // optional + Export *plugin.Config // optional +} + +func (cc *Energy) Configure(ctx context.Context) ( + func() (float64, error), + func() (float64, error), + func() (float64, error), + error, +) { + powerG, err := cc.Power.FloatGetter(ctx) + if err != nil { + return nil, nil, nil, fmt.Errorf("power: %w", err) + } + + importG, err := cc.Import.FloatGetter(ctx) + if err != nil { + return nil, nil, nil, fmt.Errorf("import: %w", err) + } + + exportG, err := cc.Export.FloatGetter(ctx) + if err != nil { + return nil, nil, nil, fmt.Errorf("export: %w", err) + } + + return powerG, importG, exportG, nil +} + +// AliasImport assigns the legacy energy field onto Import +func (cc *Energy) AliasImport(energy *plugin.Config) error { + if energy == nil { + return nil + } + if cc.Import != nil { + return errors.New("energy and import/export are mutually exclusive") + } + cc.Import = energy + return nil +} diff --git a/charger/heating/heating.go b/charger/measurement/heating.go similarity index 96% rename from charger/heating/heating.go rename to charger/measurement/heating.go index 57d803fbedc..3e865fa9057 100644 --- a/charger/heating/heating.go +++ b/charger/measurement/heating.go @@ -1,4 +1,4 @@ -package heating +package measurement import ( "context" diff --git a/charger/sgready-relay.go b/charger/sgready-relay.go index 6a9d04c4787..c88ad224680 100644 --- a/charger/sgready-relay.go +++ b/charger/sgready-relay.go @@ -6,8 +6,7 @@ import ( "github.com/evcc-io/evcc/api" "github.com/evcc-io/evcc/api/implement" - "github.com/evcc-io/evcc/charger/heating" - "github.com/evcc-io/evcc/meter/measurement" + "github.com/evcc-io/evcc/charger/measurement" "github.com/evcc-io/evcc/plugin" "github.com/evcc-io/evcc/util" "github.com/evcc-io/evcc/util/config" @@ -29,12 +28,12 @@ func init() { // NewSgReadyRelayFromConfig creates an SG Ready charger with boost/dim relays from generic config func NewSgReadyRelayFromConfig(ctx context.Context, other map[string]any) (api.Charger, error) { cc := struct { - embed `mapstructure:",squash"` - Boost config.Typed - Dim *config.Typed - heating.Temperature `mapstructure:",squash"` - measurement.Energy `mapstructure:",squash"` - LegacyEnergy *plugin.Config `mapstructure:"energy"` // TODO deprecated + embed `mapstructure:",squash"` + Boost config.Typed + Dim *config.Typed + measurement.Temperature `mapstructure:",squash"` + measurement.Energy `mapstructure:",squash"` + LegacyEnergy *plugin.Config `mapstructure:"energy"` // TODO deprecated }{ embed: embed{ Icon_: "heatpump", diff --git a/charger/sgready.go b/charger/sgready.go index 2223d1a4c88..929eab33a25 100644 --- a/charger/sgready.go +++ b/charger/sgready.go @@ -22,9 +22,8 @@ import ( "github.com/evcc-io/evcc/api" "github.com/evcc-io/evcc/api/implement" - "github.com/evcc-io/evcc/charger/heating" + "github.com/evcc-io/evcc/charger/measurement" "github.com/evcc-io/evcc/core/loadpoint" - "github.com/evcc-io/evcc/meter/measurement" "github.com/evcc-io/evcc/plugin" "github.com/evcc-io/evcc/util" ) @@ -57,13 +56,13 @@ const ( // NewSgReadyFromConfig creates an SG Ready configurable charger from generic config func NewSgReadyFromConfig(ctx context.Context, other map[string]any) (api.Charger, error) { cc := struct { - embed `mapstructure:",squash"` - SetMode plugin.Config - GetMode *plugin.Config // optional - SetMaxPower *plugin.Config // optional - heating.Temperature `mapstructure:",squash"` - measurement.Energy `mapstructure:",squash"` - LegacyEnergy *plugin.Config `mapstructure:"energy"` // TODO deprecated + embed `mapstructure:",squash"` + SetMode plugin.Config + GetMode *plugin.Config // optional + SetMaxPower *plugin.Config // optional + measurement.Temperature `mapstructure:",squash"` + measurement.Energy `mapstructure:",squash"` + LegacyEnergy *plugin.Config `mapstructure:"energy"` // TODO deprecated }{ embed: embed{ Icon_: "heatpump", From aac251029ef19a527478afb34fa1009d60a95316 Mon Sep 17 00:00:00 2001 From: andig Date: Fri, 15 May 2026 11:32:44 +0200 Subject: [PATCH 4/5] Shelly: add export --- meter/shelly/connection.go | 1 + meter/shelly/gen1.go | 19 +++++++++++++++++++ meter/shelly/gen2.go | 20 ++++++++++++++++++++ 3 files changed, 40 insertions(+) diff --git a/meter/shelly/connection.go b/meter/shelly/connection.go index c80dc6ff7f5..04286f88dc3 100644 --- a/meter/shelly/connection.go +++ b/meter/shelly/connection.go @@ -16,6 +16,7 @@ type Generation interface { Enabled() (bool, error) Enable(bool) error ImportEnergy() (float64, error) + ExportEnergy() (float64, error) } type Phases interface { diff --git a/meter/shelly/gen1.go b/meter/shelly/gen1.go index 804b59657ba..bd6519e1579 100644 --- a/meter/shelly/gen1.go +++ b/meter/shelly/gen1.go @@ -125,6 +125,25 @@ func (c *gen1) ImportEnergy() (float64, error) { return c.energy(energy) / 1000, nil } +func (c *gen1) ExportEnergy() (float64, error) { + var energy float64 + res, err := c.status.Get() + if err != nil { + return 0, err + } + + switch { + case c.channel < len(res.Meters): + energy = res.Meters[c.channel].Total_Returned + case c.channel < len(res.EMeters): + energy = res.EMeters[c.channel].Total_Returned + default: + return 0, errors.New("invalid channel, missing power meter") + } + + return c.energy(energy) / 1000, nil +} + // gen1Energy in kWh func (c *gen1) energy(energy float64) float64 { // Gen 1 Shelly EM devices are providing Watt hours, Gen 1 Shelly PM devices are providing Watt minutes diff --git a/meter/shelly/gen2.go b/meter/shelly/gen2.go index f5f835160d9..e47c09b81a5 100644 --- a/meter/shelly/gen2.go +++ b/meter/shelly/gen2.go @@ -239,6 +239,26 @@ func (c *gen2) ImportEnergy() (float64, error) { } } +// ExportEnergy implements the api.MeterImport interface +func (c *gen2) ExportEnergy() (float64, error) { + switch { + case c.hasEM1Endpoint(): + res, err := c.em1data() + return res.TotalActRetEnergy / 1000, err + + case c.hasEMEndpoint(): + res, err := c.emdata() + return res.TotalActRet / 1000, err + + case c.hasSwitchEndpoint(): + res, err := c.switchstatus.Get() + return res.Ret_Aenergy.Total / 1000, err + + default: + return 0, fmt.Errorf("unknown shelly model: %s", c.model) + } +} + // Currents implements the api.PhaseCurrents interface func (c *gen2) Currents() (float64, float64, float64, error) { switch { From c64d0235695ccae1092215e675b25a04d2e66aa1 Mon Sep 17 00:00:00 2001 From: andig Date: Fri, 15 May 2026 16:57:10 +0200 Subject: [PATCH 5/5] Revert MeterEnergy rename, add MeterReturnEnergy - Restore MeterEnergy/TotalEnergy() interface (revert MeterImport/ImportEnergy()). - Add MeterReturnEnergy/ReturnEnergy() in place of MeterExport/ExportEnergy(). - Rename measurement.Energy fields Import/Export -> Energy/ReturnEnergy (YAML config keys: energy, returnEnergy). - Drop AliasImport / LegacyEnergy deprecation shims now that the legacy `energy:` YAML key remains the primary one. Co-Authored-By: Claude Opus 4.7 (1M context) --- api/api.go | 14 +++--- api/capable_test.go | 44 +++++++++--------- api/implement/implementations.go | 28 ++++++------ api/mock.go | 72 +++++++++++++++--------------- charger/_blueprint.go | 6 +-- charger/abb.go | 2 +- charger/abl-em4.go | 6 +-- charger/alfen.go | 6 +-- charger/alpitronic.go | 6 +-- charger/amperfied.go | 6 +-- charger/bender.go | 4 +- charger/cfos.go | 4 +- charger/charger.go | 11 ++--- charger/compleo.go | 2 +- charger/connectiq.go | 6 +-- charger/dadapower.go | 6 +-- charger/daheimladen.go | 6 +-- charger/e3dc.go | 6 +-- charger/easee.go | 6 +-- charger/easee_test.go | 10 ++--- charger/em2go-duo.go | 6 +-- charger/em2go.go | 6 +-- charger/eprowallbox.go | 6 +-- charger/etek.go | 4 +- charger/evecube.go | 6 +-- charger/evsemaster.go | 8 ++-- charger/evsewifi.go | 4 +- charger/evsewifi_test.go | 4 +- charger/fritzdect.go | 8 ++-- charger/go-e.go | 6 +-- charger/go-e_test.go | 4 +- charger/hardybarth-ecb1.go | 6 +-- charger/hardybarth-salia.go | 4 +- charger/heatpump.go | 11 ++--- charger/heidelberg-ec.go | 6 +-- charger/hesotec.go | 6 +-- charger/homeassistant.go | 2 +- charger/homematic.go | 8 ++-- charger/homewizard.go | 8 ++-- charger/innogy.go | 4 +- charger/kathrein.go | 6 +-- charger/keba-modbus.go | 4 +- charger/keba-udp.go | 4 +- charger/kse.go | 8 ++-- charger/lektrico.go | 6 +-- charger/measurement/energy.go | 29 ++++-------- charger/mennekes-compact.go | 8 ++-- charger/mennekes-hcc3.go | 2 +- charger/nexblue.go | 6 +-- charger/nrgble_linux.go | 6 +-- charger/nrgconnect.go | 6 +-- charger/nrggen2.go | 6 +-- charger/ocpp.go | 2 +- charger/ocpp/connector.go | 2 +- charger/ocpp/connector_test.go | 18 ++++---- charger/openevse.go | 6 +-- charger/openwb-2.0.go | 6 +-- charger/openwb-pro.go | 6 +-- charger/openwb.go | 6 +-- charger/pcelectric.go | 4 +- charger/peblar.go | 4 +- charger/phoenix-charx.go | 4 +- charger/phoenix-em-eth.go | 4 +- charger/phoenix-ev-eth.go | 4 +- charger/pulsatrix.go | 6 +-- charger/schneider-v3.go | 6 +-- charger/sgready-relay.go | 12 ++--- charger/sgready.go | 11 ++--- charger/shelly-topac.go | 6 +-- charger/shelly.go | 8 ++-- charger/sigenergy.go | 6 +-- charger/smaevcharger.go | 6 +-- charger/smart-evse.go | 4 +- charger/smartevse.go | 6 +-- charger/solax.go | 6 +-- charger/sungrow.go | 6 +-- charger/switchsocket.go | 2 +- charger/tasmota.go | 8 ++-- charger/tplink.go | 8 ++-- charger/versicharge.go | 6 +-- charger/vestel.go | 6 +-- charger/victron.go | 2 +- charger/warp-ws.go | 2 +- charger/warp2-mqtt.go | 4 +- charger/webasto-next.go | 6 +-- "charger/weidm\303\274ller.go" | 4 +- cmd/dumper.go | 8 ++-- cmd/implement/implement.go | 4 +- core/capable_test.go | 14 +++--- core/loadpoint.go | 6 +-- core/loadpoint_session.go | 4 +- core/loadpoint_session_test.go | 42 ++++++++--------- core/site.go | 16 +++---- core/wrapper/chargerater.go | 28 ++++++------ core/wrapper/chargerater_test.go | 32 ++++++------- meter/_blueprint.go | 6 +-- meter/cfos.go | 6 +-- meter/danfoss.go | 2 +- meter/discovergy.go | 4 +- meter/dsmr.go | 4 +- meter/eebus.go | 4 +- meter/fritz/aha/aha.go | 6 +-- meter/fritz/api.go | 2 +- meter/fritz/smarthome/smarthome.go | 6 +-- meter/homeassistant.go | 2 +- meter/homematic.go | 8 ++-- meter/homematic/connection.go | 4 +- meter/homewizard.go | 8 ++-- meter/homewizard/connection.go | 4 +- meter/lgess.go | 4 +- meter/mbmd.go | 2 +- meter/measurement/energy.go | 29 ++++-------- meter/meter.go | 12 ++--- meter/meter_average.go | 12 ++--- meter/powerwall.go | 4 +- meter/rct.go | 4 +- meter/shelly/connection.go | 4 +- meter/shelly/gen1.go | 4 +- meter/shelly/gen2.go | 8 ++-- meter/sma.go | 8 ++-- meter/tasmota.go | 8 ++-- meter/tasmota/connection.go | 4 +- meter/tplink/connection.go | 4 +- meter/tq-em.go | 4 +- meter/tq-em420.go | 4 +- plugin/meter.go | 10 ++--- server/http_config_helper.go | 8 ++-- 127 files changed, 478 insertions(+), 531 deletions(-) diff --git a/api/api.go b/api/api.go index 432b4c8ca91..989caf5d624 100644 --- a/api/api.go +++ b/api/api.go @@ -9,21 +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,MeterImport,MeterExport,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,MeterEnergy,MeterReturnEnergy,PhaseCurrents,Vehicle,ConnectionTimer,ChargeRater,Battery,BatteryController,BatterySocLimiter,Circuit,Dimmer,Tariff // Meter provides total active power in W type Meter interface { CurrentPower() (float64, error) } -// MeterImport provides total import in kWh -type MeterImport interface { - ImportEnergy() (float64, error) +// MeterEnergy provides total energy in kWh +type MeterEnergy interface { + TotalEnergy() (float64, error) } -// MeterExport provides total export in kWh -type MeterExport interface { - ExportEnergy() (float64, error) +// MeterReturnEnergy provides total returned/exported energy in kWh +type MeterReturnEnergy interface { + ReturnEnergy() (float64, error) } // PhaseCurrents provides per-phase current A diff --git a/api/capable_test.go b/api/capable_test.go index 457da528ce2..565dd596e80 100644 --- a/api/capable_test.go +++ b/api/capable_test.go @@ -19,9 +19,9 @@ func (d *decoratedMeter) Capability(typ reflect.Type) (any, bool) { return c, ok } -type testMeterImportImpl struct{} +type testMeterEnergyImpl struct{} -func (t *testMeterImportImpl) ImportEnergy() (float64, error) { +func (t *testMeterEnergyImpl) TotalEnergy() (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 MeterImport - impl := &testMeterImportImpl{} + // concrete type that directly implements MeterEnergy + impl := &testMeterEnergyImpl{} - me, ok := Cap[MeterImport](impl) + me, ok := Cap[MeterEnergy](impl) require.True(t, ok) - energy, err := me.ImportEnergy() + energy, err := me.TotalEnergy() 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[MeterImport](): &testMeterImportImpl{}, + reflect.TypeFor[MeterEnergy](): &testMeterEnergyImpl{}, }, } - // should find MeterImport via registry - me, ok := Cap[MeterImport](decorated) + // should find MeterEnergy via registry + me, ok := Cap[MeterEnergy](decorated) require.True(t, ok) - energy, err := me.ImportEnergy() + energy, err := me.TotalEnergy() 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[MeterImport] on the extracted value fails. + // subsequent Cap[MeterEnergy] on the extracted value fails. decorated := &decoratedCharger{ caps: map[reflect.Type]any{ reflect.TypeFor[Meter](): &testMeterImpl{}, - reflect.TypeFor[MeterImport](): &testMeterImportImpl{}, + reflect.TypeFor[MeterEnergy](): &testMeterEnergyImpl{}, }, } @@ -93,9 +93,9 @@ func TestCap_ExtractedCapabilityLosesRegistry(t *testing.T) { mt, ok := Cap[Meter](decorated) require.True(t, ok) - // 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") + // 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") // 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[MeterImport](wrapped) - require.True(t, ok, "wrapped meter should find MeterImport via Capable") + me, ok := Cap[MeterEnergy](wrapped) + require.True(t, ok, "wrapped meter should find MeterEnergy via Capable") - energy, err := me.ImportEnergy() + energy, err := me.TotalEnergy() assert.NoError(t, err) assert.Equal(t, 99.0, energy) } func TestCap_NilValue(t *testing.T) { - _, ok := Cap[MeterImport](nil) + _, ok := Cap[MeterEnergy](nil) assert.False(t, ok) } func TestCap_DirectTakesPrecedence(t *testing.T) { // type that both directly implements AND has registry type directAndCapable struct { - testMeterImportImpl + testMeterEnergyImpl caps map[reflect.Type]any //nolint:unused } v := &directAndCapable{} - me, ok := Cap[MeterImport](v) + me, ok := Cap[MeterEnergy](v) require.True(t, ok) - energy, err := me.ImportEnergy() + energy, err := me.TotalEnergy() assert.NoError(t, err) assert.Equal(t, 99.0, energy) } diff --git a/api/implement/implementations.go b/api/implement/implementations.go index f5cde7c4f76..c7c4776c212 100644 --- a/api/implement/implementations.go +++ b/api/implement/implementations.go @@ -258,34 +258,34 @@ func (i *iMeter) CurrentPower() (float64, error) { return i.meter0() } -func MeterImport(meterImport0 func() (float64, error)) api.MeterImport { - if meterImport0 == nil { +func MeterEnergy(meterEnergy0 func() (float64, error)) api.MeterEnergy { + if meterEnergy0 == nil { return nil } - return &iMeterImport{meterImport0} + return &iMeterEnergy{meterEnergy0} } -type iMeterImport struct { - meterImport0 func() (float64, error) +type iMeterEnergy struct { + meterEnergy0 func() (float64, error) } -func (i *iMeterImport) ImportEnergy() (float64, error) { - return i.meterImport0() +func (i *iMeterEnergy) TotalEnergy() (float64, error) { + return i.meterEnergy0() } -func MeterExport(meterExport0 func() (float64, error)) api.MeterExport { - if meterExport0 == nil { +func MeterReturnEnergy(meterReturnEnergy0 func() (float64, error)) api.MeterReturnEnergy { + if meterReturnEnergy0 == nil { return nil } - return &iMeterExport{meterExport0} + return &iMeterReturnEnergy{meterReturnEnergy0} } -type iMeterExport struct { - meterExport0 func() (float64, error) +type iMeterReturnEnergy struct { + meterReturnEnergy0 func() (float64, error) } -func (i *iMeterExport) ExportEnergy() (float64, error) { - return i.meterExport0() +func (i *iMeterReturnEnergy) ReturnEnergy() (float64, error) { + return i.meterReturnEnergy0() } func PhaseCurrents(phaseCurrents0 func() (float64, float64, float64, error)) api.PhaseCurrents { diff --git a/api/mock.go b/api/mock.go index 8cd83f0d394..f260aaa32cc 100644 --- a/api/mock.go +++ b/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,MeterImport,MeterExport,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,MeterEnergy,MeterReturnEnergy,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,MeterImport,MeterExport,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,MeterEnergy,MeterReturnEnergy,PhaseCurrents,Vehicle,ConnectionTimer,ChargeRater,Battery,BatteryController,BatterySocLimiter,Circuit,Dimmer,Tariff // // Package api is a generated GoMock package. @@ -409,82 +409,82 @@ func (mr *MockMeterMockRecorder) CurrentPower() *gomock.Call { return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CurrentPower", reflect.TypeOf((*MockMeter)(nil).CurrentPower)) } -// MockMeterImport is a mock of MeterImport interface. -type MockMeterImport struct { +// MockMeterEnergy is a mock of MeterEnergy interface. +type MockMeterEnergy struct { ctrl *gomock.Controller - recorder *MockMeterImportMockRecorder + recorder *MockMeterEnergyMockRecorder isgomock struct{} } -// MockMeterImportMockRecorder is the mock recorder for MockMeterImport. -type MockMeterImportMockRecorder struct { - mock *MockMeterImport +// MockMeterEnergyMockRecorder is the mock recorder for MockMeterEnergy. +type MockMeterEnergyMockRecorder struct { + mock *MockMeterEnergy } -// NewMockMeterImport creates a new mock instance. -func NewMockMeterImport(ctrl *gomock.Controller) *MockMeterImport { - mock := &MockMeterImport{ctrl: ctrl} - mock.recorder = &MockMeterImportMockRecorder{mock} +// NewMockMeterEnergy creates a new mock instance. +func NewMockMeterEnergy(ctrl *gomock.Controller) *MockMeterEnergy { + mock := &MockMeterEnergy{ctrl: ctrl} + mock.recorder = &MockMeterEnergyMockRecorder{mock} return mock } // EXPECT returns an object that allows the caller to indicate expected use. -func (m *MockMeterImport) EXPECT() *MockMeterImportMockRecorder { +func (m *MockMeterEnergy) EXPECT() *MockMeterEnergyMockRecorder { return m.recorder } -// ImportEnergy mocks base method. -func (m *MockMeterImport) ImportEnergy() (float64, error) { +// TotalEnergy mocks base method. +func (m *MockMeterEnergy) TotalEnergy() (float64, error) { m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "ImportEnergy") + ret := m.ctrl.Call(m, "TotalEnergy") ret0, _ := ret[0].(float64) ret1, _ := ret[1].(error) return ret0, ret1 } -// ImportEnergy indicates an expected call of ImportEnergy. -func (mr *MockMeterImportMockRecorder) ImportEnergy() *gomock.Call { +// TotalEnergy indicates an expected call of TotalEnergy. +func (mr *MockMeterEnergyMockRecorder) TotalEnergy() *gomock.Call { mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ImportEnergy", reflect.TypeOf((*MockMeterImport)(nil).ImportEnergy)) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "TotalEnergy", reflect.TypeOf((*MockMeterEnergy)(nil).TotalEnergy)) } -// MockMeterExport is a mock of MeterExport interface. -type MockMeterExport struct { +// MockMeterReturnEnergy is a mock of MeterReturnEnergy interface. +type MockMeterReturnEnergy struct { ctrl *gomock.Controller - recorder *MockMeterExportMockRecorder + recorder *MockMeterReturnEnergyMockRecorder isgomock struct{} } -// MockMeterExportMockRecorder is the mock recorder for MockMeterExport. -type MockMeterExportMockRecorder struct { - mock *MockMeterExport +// MockMeterReturnEnergyMockRecorder is the mock recorder for MockMeterReturnEnergy. +type MockMeterReturnEnergyMockRecorder struct { + mock *MockMeterReturnEnergy } -// NewMockMeterExport creates a new mock instance. -func NewMockMeterExport(ctrl *gomock.Controller) *MockMeterExport { - mock := &MockMeterExport{ctrl: ctrl} - mock.recorder = &MockMeterExportMockRecorder{mock} +// NewMockMeterReturnEnergy creates a new mock instance. +func NewMockMeterReturnEnergy(ctrl *gomock.Controller) *MockMeterReturnEnergy { + mock := &MockMeterReturnEnergy{ctrl: ctrl} + mock.recorder = &MockMeterReturnEnergyMockRecorder{mock} return mock } // EXPECT returns an object that allows the caller to indicate expected use. -func (m *MockMeterExport) EXPECT() *MockMeterExportMockRecorder { +func (m *MockMeterReturnEnergy) EXPECT() *MockMeterReturnEnergyMockRecorder { return m.recorder } -// ExportEnergy mocks base method. -func (m *MockMeterExport) ExportEnergy() (float64, error) { +// ReturnEnergy mocks base method. +func (m *MockMeterReturnEnergy) ReturnEnergy() (float64, error) { m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "ExportEnergy") + ret := m.ctrl.Call(m, "ReturnEnergy") ret0, _ := ret[0].(float64) ret1, _ := ret[1].(error) return ret0, ret1 } -// ExportEnergy indicates an expected call of ExportEnergy. -func (mr *MockMeterExportMockRecorder) ExportEnergy() *gomock.Call { +// ReturnEnergy indicates an expected call of ReturnEnergy. +func (mr *MockMeterReturnEnergyMockRecorder) ReturnEnergy() *gomock.Call { mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ExportEnergy", reflect.TypeOf((*MockMeterExport)(nil).ExportEnergy)) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ReturnEnergy", reflect.TypeOf((*MockMeterReturnEnergy)(nil).ReturnEnergy)) } // MockPhaseCurrents is a mock of PhaseCurrents interface. diff --git a/charger/_blueprint.go b/charger/_blueprint.go index b2e3fa624ce..f448cf711b1 100644 --- a/charger/_blueprint.go +++ b/charger/_blueprint.go @@ -95,10 +95,10 @@ func (wb *Blueprint) ChargedEnergy() (float64, error) { return 0, api.ErrNotAvailable } -var _ api.MeterImport = (*Blueprint)(nil) +var _ api.MeterEnergy = (*Blueprint)(nil) -// ImportEnergy implements the api.MeterImport interface -func (wb *Blueprint) ImportEnergy() (float64, error) { +// TotalEnergy implements the api.MeterEnergy interface +func (wb *Blueprint) TotalEnergy() (float64, error) { return 0, api.ErrNotAvailable } diff --git a/charger/abb.go b/charger/abb.go index c698c2ab96f..294d37a46b3 100644 --- a/charger/abb.go +++ b/charger/abb.go @@ -207,7 +207,7 @@ func (wb *ABB) CurrentPower() (float64, error) { var _ api.ChargeRater = (*ABB)(nil) -// ChargedEnergy implements the api.MeterImport interface +// ChargedEnergy implements the api.MeterEnergy interface func (wb *ABB) ChargedEnergy() (float64, error) { b, err := wb.conn.ReadHoldingRegisters(abbRegEnergy, 2) if err != nil { diff --git a/charger/abl-em4.go b/charger/abl-em4.go index 4c5b033a5cd..38e93109fea 100644 --- a/charger/abl-em4.go +++ b/charger/abl-em4.go @@ -197,10 +197,10 @@ func (wb *AblEm4) CurrentPower() (float64, error) { return float64(binary.BigEndian.Uint32(b)), nil } -var _ api.MeterImport = (*AblEm4)(nil) +var _ api.MeterEnergy = (*AblEm4)(nil) -// totalEnergy implements the api.MeterImport interface -func (wb *AblEm4) ImportEnergy() (float64, error) { +// totalEnergy implements the api.MeterEnergy interface +func (wb *AblEm4) TotalEnergy() (float64, error) { b, err := wb.conn.ReadHoldingRegisters(wb.base+abl4RegEnergy, 2) if err != nil { return 0, err diff --git a/charger/alfen.go b/charger/alfen.go index 1da47d51df4..46d41ae39a0 100644 --- a/charger/alfen.go +++ b/charger/alfen.go @@ -210,10 +210,10 @@ func (wb *Alfen) CurrentPower() (float64, error) { return rs485.RTUIeee754ToFloat64(b), err } -var _ api.MeterImport = (*Alfen)(nil) +var _ api.MeterEnergy = (*Alfen)(nil) -// ImportEnergy implements the api.MeterImport interface -func (wb *Alfen) ImportEnergy() (float64, error) { +// TotalEnergy implements the api.MeterEnergy interface +func (wb *Alfen) TotalEnergy() (float64, error) { b, err := wb.conn.ReadHoldingRegisters(alfenRegEnergy, 4) if err != nil { return 0, err diff --git a/charger/alpitronic.go b/charger/alpitronic.go index 1e5239411dc..13f8e2e4009 100644 --- a/charger/alpitronic.go +++ b/charger/alpitronic.go @@ -211,10 +211,10 @@ func (wb *AlpitronicHYC) ChargedEnergy() (float64, error) { return float64(encoding.Uint16(b)) / 100, err } -var _ api.MeterImport = (*AlpitronicHYC)(nil) +var _ api.MeterEnergy = (*AlpitronicHYC)(nil) -// ImportEnergy implements the api.MeterImport interface -func (wb *AlpitronicHYC) ImportEnergy() (float64, error) { +// TotalEnergy implements the api.MeterEnergy interface +func (wb *AlpitronicHYC) TotalEnergy() (float64, error) { b, err := wb.conn.ReadInputRegisters(wb.reg(hycRegTotalChargedEnergy), 4) if err != nil { return 0, err diff --git a/charger/amperfied.go b/charger/amperfied.go index edc2c834f20..ab231775454 100644 --- a/charger/amperfied.go +++ b/charger/amperfied.go @@ -255,10 +255,10 @@ func (wb *Amperfied) CurrentPower() (float64, error) { return float64(binary.BigEndian.Uint16(b)), nil } -var _ api.MeterImport = (*Amperfied)(nil) +var _ api.MeterEnergy = (*Amperfied)(nil) -// ImportEnergy implements the api.MeterImport interface -func (wb *Amperfied) ImportEnergy() (float64, error) { +// TotalEnergy implements the api.MeterEnergy interface +func (wb *Amperfied) TotalEnergy() (float64, error) { b, err := wb.conn.ReadInputRegisters(ampRegEnergy, 2) if err != nil { return 0, err diff --git a/charger/bender.go b/charger/bender.go index 28438fce67b..8c68186ea33 100644 --- a/charger/bender.go +++ b/charger/bender.go @@ -148,7 +148,7 @@ func NewBenderCC(ctx context.Context, uri string, id uint8, cache time.Duration) if b, err := wb.conn.ReadHoldingRegisters(reg, 2); err == nil && binary.BigEndian.Uint32(b) != math.MaxUint32 { implement.Has(wb, implement.Meter(wb.currentPower)) implement.Has(wb, implement.PhaseCurrents(wb.currents)) - implement.Has(wb, implement.MeterImport(wb.totalEnergy)) + implement.Has(wb, implement.MeterEnergy(wb.totalEnergy)) // check presence of "ocpp meter" if b, err := wb.conn.ReadHoldingRegisters(bendRegVoltages, 2); err == nil && binary.BigEndian.Uint32(b) > 0 { @@ -374,7 +374,7 @@ func (wb *BenderCC) currentPower() (float64, error) { // removed: https://github.com/evcc-io/evcc/issues/13726 // var _ api.ChargeRater = (*BenderCC)(nil) -// ImportEnergy implements the api.MeterImport interface +// TotalEnergy implements the api.MeterEnergy interface func (wb *BenderCC) totalEnergy() (float64, error) { if wb.legacy { b, err := wb.conn.ReadHoldingRegisters(bendRegPhaseEnergy, 6) diff --git a/charger/cfos.go b/charger/cfos.go index 959527b379e..370ad3ce87c 100644 --- a/charger/cfos.go +++ b/charger/cfos.go @@ -77,7 +77,7 @@ func NewCfosPowerBrain(ctx context.Context, uri string, id uint8) (api.Charger, // decorate meter if b, err := wb.conn.ReadHoldingRegisters(cfosRegMeter, 1); err == nil && binary.BigEndian.Uint16(b) != 0 { implement.Has(wb, implement.Meter(wb.currentPower)) - implement.Has(wb, implement.MeterImport(wb.totalEnergy)) + implement.Has(wb, implement.MeterEnergy(wb.totalEnergy)) if b, err := wb.conn.ReadHoldingRegisters(cfosRegMeterFlags, 1); err == nil && binary.BigEndian.Uint16(b) != 0 { implement.Has(wb, implement.PhaseCurrents(wb.currents)) @@ -156,7 +156,7 @@ func (wb *CfosPowerBrain) currentPower() (float64, error) { return float64(binary.BigEndian.Uint32(b)), nil } -// totalEnergy implements the api.MeterImport interface +// totalEnergy implements the api.MeterEnergy interface func (wb *CfosPowerBrain) totalEnergy() (float64, error) { b, err := wb.conn.ReadHoldingRegisters(cfosRegEnergy, 4) if err != nil { diff --git a/charger/charger.go b/charger/charger.go index b758109d55b..7d7fce0b236 100644 --- a/charger/charger.go +++ b/charger/charger.go @@ -44,7 +44,6 @@ func NewConfigurableFromConfig(ctx context.Context, other map[string]any) (api.C meter.Phases `mapstructure:",squash"` // optional meter.Dimmer `mapstructure:",squash"` // optional meter.Curtailer `mapstructure:",squash"` // optional - LegacyEnergy *plugin.Config `mapstructure:"energy"` // TODO deprecated } if err := util.DecodeOther(other, &cc); err != nil { @@ -147,17 +146,13 @@ func NewConfigurableFromConfig(ctx context.Context, other map[string]any) (api.C implement.May(c, implement.Battery(soc)) implement.May(c, implement.SocLimiter(limitsoc)) - // TODO deprecated - if err := cc.Energy.AliasImport(cc.LegacyEnergy); err != nil { - return nil, err - } - - powerG, importG, _, err := cc.Energy.Configure(ctx) + powerG, energyG, returnG, err := cc.Energy.Configure(ctx) if err != nil { return nil, err } implement.May(c, implement.Meter(powerG)) - implement.May(c, implement.MeterImport(importG)) + implement.May(c, implement.MeterEnergy(energyG)) + implement.May(c, implement.MeterReturnEnergy(returnG)) currentsG, voltagesG, _, err := cc.Phases.Configure(ctx) if err != nil { diff --git a/charger/compleo.go b/charger/compleo.go index 2ea3081358a..16d955542c4 100644 --- a/charger/compleo.go +++ b/charger/compleo.go @@ -235,7 +235,7 @@ func (wb *Compleo) CurrentPower() (float64, error) { var _ api.ChargeRater = (*Compleo)(nil) -// ChargedEnergy implements the api.MeterImport interface +// ChargedEnergy implements the api.MeterEnergy interface func (wb *Compleo) ChargedEnergy() (float64, error) { b, err := wb.conn.ReadInputRegisters(wb.reg(compleoRegEnergy), 1) if err != nil { diff --git a/charger/connectiq.go b/charger/connectiq.go index 28e3006f966..1c629e4a306 100644 --- a/charger/connectiq.go +++ b/charger/connectiq.go @@ -162,10 +162,10 @@ func (wb *ConnectIq) CurrentPower() (float64, error) { return (res.Pow[0] + res.Pow[1] + res.Pow[2]) * 1e3, nil } -var _ api.MeterImport = (*ConnectIq)(nil) +var _ api.MeterEnergy = (*ConnectIq)(nil) -// ImportEnergy implements the api.MeterImport interface -func (wb *ConnectIq) ImportEnergy() (float64, error) { +// TotalEnergy implements the api.MeterEnergy interface +func (wb *ConnectIq) TotalEnergy() (float64, error) { var res connectiq.MeterRead uri := fmt.Sprintf("%s/meter/read", wb.uri) err := wb.GetJSON(uri, &res) diff --git a/charger/dadapower.go b/charger/dadapower.go index de881eb9d06..20babd9fbd5 100644 --- a/charger/dadapower.go +++ b/charger/dadapower.go @@ -184,10 +184,10 @@ func (wb *Dadapower) CurrentPower() (float64, error) { return float64(binary.BigEndian.Uint32(b)), err } -var _ api.MeterImport = (*Dadapower)(nil) +var _ api.MeterEnergy = (*Dadapower)(nil) -// ImportEnergy implements the api.MeterImport interface -func (wb *Dadapower) ImportEnergy() (float64, error) { +// TotalEnergy implements the api.MeterEnergy interface +func (wb *Dadapower) TotalEnergy() (float64, error) { b, err := wb.conn.ReadInputRegisters(dadapowerRegEnergyImportTotal+wb.regOffset, 4) if err != nil { return 0, err diff --git a/charger/daheimladen.go b/charger/daheimladen.go index 5da18df3890..ad5f469e90a 100644 --- a/charger/daheimladen.go +++ b/charger/daheimladen.go @@ -250,10 +250,10 @@ func (wb *DaheimLaden) CurrentPower() (float64, error) { return float64(binary.BigEndian.Uint32(b)), err } -var _ api.MeterImport = (*DaheimLaden)(nil) +var _ api.MeterEnergy = (*DaheimLaden)(nil) -// ImportEnergy implements the api.MeterImport interface -func (wb *DaheimLaden) ImportEnergy() (float64, error) { +// TotalEnergy implements the api.MeterEnergy interface +func (wb *DaheimLaden) TotalEnergy() (float64, error) { b, err := wb.conn.ReadHoldingRegisters(dlRegTotalEnergy, 2) if err != nil { return 0, err diff --git a/charger/e3dc.go b/charger/e3dc.go index ce1c2c688f9..f04b79d3ecc 100644 --- a/charger/e3dc.go +++ b/charger/e3dc.go @@ -362,9 +362,9 @@ func (wb *E3dc) CurrentPower() (float64, error) { return p1 + p2 + p3, nil } -var _ api.MeterImport = (*E3dc)(nil) +var _ api.MeterEnergy = (*E3dc)(nil) -// ImportEnergy implements the api.MeterImport interface +// TotalEnergy implements the api.MeterEnergy interface // // E3DC stores wallbox energy in two separate counters that must be added: // - DB_TEC_WALLBOX_ENERGYALL: Historical energy stored in the database (persisted) @@ -372,7 +372,7 @@ var _ api.MeterImport = (*E3dc)(nil) // // The sum of both values matches the total energy shown in the E3DC portal. // Testing showed: DB_TEC (8319 kWh) + WB_ENERGY (699 kWh) = 9018 kWh ≈ Portal (9019 kWh) -func (wb *E3dc) ImportEnergy() (float64, error) { +func (wb *E3dc) TotalEnergy() (float64, error) { // Query both energy sources sequentially res, err := wb.conn.Send(*rscp.NewMessage(rscp.DB_REQ_TEC_WALLBOX_VALUES, nil)) if err != nil { diff --git a/charger/easee.go b/charger/easee.go index e791a80c9ce..5c6f9cdf85f 100644 --- a/charger/easee.go +++ b/charger/easee.go @@ -717,10 +717,10 @@ func (c *Easee) Currents() (float64, float64, float64, error) { return c.currentL1, c.currentL2, c.currentL3, nil } -var _ api.MeterImport = (*Easee)(nil) +var _ api.MeterEnergy = (*Easee)(nil) -// ImportEnergy implements the api.MeterImport interface -func (c *Easee) ImportEnergy() (float64, error) { +// TotalEnergy implements the api.MeterEnergy interface +func (c *Easee) TotalEnergy() (float64, error) { c.mux.RLock() defer c.mux.RUnlock() // updates for this are only sent once an hour, so inaccurate by design diff --git a/charger/easee_test.go b/charger/easee_test.go index f85fc950dc0..cc251f2ead9 100644 --- a/charger/easee_test.go +++ b/charger/easee_test.go @@ -563,7 +563,7 @@ func TestChargeSessionStart_SetsFields(t *testing.T) { assert.Equal(t, 801, e.currentSessionID) - total, err := e.ImportEnergy() + total, err := e.TotalEnergy() assert.NoError(t, err) assert.Equal(t, 9141.414622, total) } @@ -581,7 +581,7 @@ func TestChargingSession_UpdatesBothWhenIdMatches(t *testing.T) { assert.NoError(t, err) assert.Equal(t, 16.2, charged) - total, err := e.ImportEnergy() + total, err := e.TotalEnergy() assert.NoError(t, err) assert.Equal(t, 9173.5, total) } @@ -600,7 +600,7 @@ func TestChargingSession_MismatchedId_ProtectsSessionEnergy(t *testing.T) { assert.NoError(t, err) assert.Equal(t, 5.0, charged) // sessionEnergy unchanged - total, err := e.ImportEnergy() + total, err := e.TotalEnergy() assert.NoError(t, err) assert.Equal(t, 9173.5, total) // totalEnergy updated } @@ -619,7 +619,7 @@ func TestChargingSession_AtStartup_ProtectsSessionEnergy(t *testing.T) { assert.NoError(t, err) assert.Equal(t, 0.0, charged) // sessionEnergy protected (Id 803 != 0) - total, err := e.ImportEnergy() + total, err := e.TotalEnergy() assert.NoError(t, err) assert.Equal(t, 9173.5, total) // totalEnergy updated } @@ -630,7 +630,7 @@ func TestLifetimeEnergy_DoesNotDecreaseImportEnergy(t *testing.T) { e.ProductUpdate(createPayload(easee.LIFETIME_ENERGY, time.Now(), easee.Double, "9170.0")) - total, err := e.ImportEnergy() + total, err := e.TotalEnergy() assert.NoError(t, err) assert.Equal(t, 9173.5, total) } diff --git a/charger/em2go-duo.go b/charger/em2go-duo.go index d54d332511d..f14df296ddb 100644 --- a/charger/em2go-duo.go +++ b/charger/em2go-duo.go @@ -216,10 +216,10 @@ func (wb *Em2GoDuo) CurrentPower() (float64, error) { return float64(binary.BigEndian.Uint32(b)), nil } -var _ api.MeterImport = (*Em2GoDuo)(nil) +var _ api.MeterEnergy = (*Em2GoDuo)(nil) -// ImportEnergy implements the api.MeterImport interface -func (wb *Em2GoDuo) ImportEnergy() (float64, error) { +// TotalEnergy implements the api.MeterEnergy interface +func (wb *Em2GoDuo) TotalEnergy() (float64, error) { b, err := wb.conn.ReadHoldingRegisters(wb.base+em2GoDuoRegConEnergy, 2) if err != nil { return 0, err diff --git a/charger/em2go.go b/charger/em2go.go index 70c675ef22c..d4bb5635e42 100644 --- a/charger/em2go.go +++ b/charger/em2go.go @@ -288,10 +288,10 @@ func (wb *Em2Go) CurrentPower() (float64, error) { return rs485.RTUUint32ToFloat64(b), nil } -var _ api.MeterImport = (*Em2Go)(nil) +var _ api.MeterEnergy = (*Em2Go)(nil) -// ImportEnergy implements the api.MeterImport interface -func (wb *Em2Go) ImportEnergy() (float64, error) { +// TotalEnergy implements the api.MeterEnergy interface +func (wb *Em2Go) TotalEnergy() (float64, error) { b, err := wb.conn.ReadHoldingRegisters(em2GoRegEnergy, 2) if err != nil { return 0, err diff --git a/charger/eprowallbox.go b/charger/eprowallbox.go index b9b412acdab..c857d5791d3 100644 --- a/charger/eprowallbox.go +++ b/charger/eprowallbox.go @@ -186,10 +186,10 @@ func (wb *EProWallbox) CurrentPower() (float64, error) { return l1 + l2 + l3, err } -var _ api.MeterImport = (*EProWallbox)(nil) +var _ api.MeterEnergy = (*EProWallbox)(nil) -// ImportEnergy implements the api.MeterImport interface -func (wb *EProWallbox) ImportEnergy() (float64, error) { +// TotalEnergy implements the api.MeterEnergy interface +func (wb *EProWallbox) TotalEnergy() (float64, error) { l1, l2, l3, err := wb.getPhaseValues(eproRegActiveEnergies, 1000) return -(l1 + l2 + l3), err } diff --git a/charger/etek.go b/charger/etek.go index 1c2d610b327..14e1b23855e 100644 --- a/charger/etek.go +++ b/charger/etek.go @@ -95,7 +95,7 @@ func NewEtekFromConfig(ctx context.Context, other map[string]any) (api.Charger, // Check energy register (95) if b, err := wb.conn.ReadHoldingRegisters(etekRegMeterEnergyAddr, 1); err == nil { if binary.BigEndian.Uint16(b) != etekRegInvalidMeterAddr { - implement.Has(wb, implement.MeterImport(wb.totalEnergy)) + implement.Has(wb, implement.MeterEnergy(wb.totalEnergy)) } } @@ -246,7 +246,7 @@ func (wb *Etek) currentPower() (float64, error) { return float64(binary.BigEndian.Uint16(b)), nil } -// totalEnergy implements the api.MeterImport interface +// totalEnergy implements the api.MeterEnergy interface func (wb *Etek) totalEnergy() (float64, error) { b, err := wb.conn.ReadHoldingRegisters(etekRegEnergy, 2) if err != nil { diff --git a/charger/evecube.go b/charger/evecube.go index 69f94c3ea63..06582c1f997 100644 --- a/charger/evecube.go +++ b/charger/evecube.go @@ -314,10 +314,10 @@ func (wb *EVECUBE) CurrentPower() (float64, error) { return status.Voltage * status.Current, nil } -var _ api.MeterImport = (*EVECUBE)(nil) +var _ api.MeterEnergy = (*EVECUBE)(nil) -// ImportEnergy implements the api.MeterImport interface -func (wb *EVECUBE) ImportEnergy() (float64, error) { +// TotalEnergy implements the api.MeterEnergy interface +func (wb *EVECUBE) TotalEnergy() (float64, error) { status, err := wb.getStatus() if err != nil { return 0, err diff --git a/charger/evsemaster.go b/charger/evsemaster.go index af454aa5370..7cd0ec44349 100644 --- a/charger/evsemaster.go +++ b/charger/evsemaster.go @@ -45,7 +45,7 @@ const ( evsemasterConnectTimeout = 15 * time.Second ) -// EVSEMaster implements api.Charger (and api.Meter / api.MeterImport / +// EVSEMaster implements api.Charger (and api.Meter / api.MeterEnergy / // api.PhaseCurrents / api.PhaseVoltages) for charging stations that use the // EVSE Master UDP protocol – e.g. Sync EV and generic Chinese EVSE devices. // @@ -288,10 +288,10 @@ func (wb *EVSEMaster) CurrentPower() (float64, error) { return res.Power, nil } -var _ api.MeterImport = (*EVSEMaster)(nil) +var _ api.MeterEnergy = (*EVSEMaster)(nil) -// ImportEnergy implements the api.MeterImport interface. -func (wb *EVSEMaster) ImportEnergy() (float64, error) { +// TotalEnergy implements the api.MeterEnergy interface. +func (wb *EVSEMaster) TotalEnergy() (float64, error) { res, err := wb.data.Get() if err != nil { return 0, err diff --git a/charger/evsewifi.go b/charger/evsewifi.go index 691f4fae8ae..498385146ca 100644 --- a/charger/evsewifi.go +++ b/charger/evsewifi.go @@ -76,7 +76,7 @@ func NewEVSEWifiFromConfig(other map[string]any) (api.Charger, error) { } if cc.Meter.Energy { - implement.Has(wb, implement.MeterImport(wb.totalEnergy)) + implement.Has(wb, implement.MeterEnergy(wb.totalEnergy)) } if cc.Meter.Currents { @@ -210,7 +210,7 @@ func (wb *EVSEWifi) currentPower() (float64, error) { return 1000 * params.ActualPower, err } -// ImportEnergy implements the api.MeterImport interface +// TotalEnergy implements the api.MeterEnergy interface func (wb *EVSEWifi) totalEnergy() (float64, error) { params, err := wb.paramG.Get() return params.MeterReading, err diff --git a/charger/evsewifi_test.go b/charger/evsewifi_test.go index e2e4a276fe0..03fd1cd2f2e 100644 --- a/charger/evsewifi_test.go +++ b/charger/evsewifi_test.go @@ -32,8 +32,8 @@ func TestEvseWifi(t *testing.T) { t.Error("missing api.Meter") } - if _, ok := api.Cap[api.MeterImport](wb); !ok { - t.Error("missing api.MeterImport") + if _, ok := api.Cap[api.MeterEnergy](wb); !ok { + t.Error("missing api.MeterEnergy") } if _, ok := api.Cap[api.PhaseCurrents](wb); !ok { diff --git a/charger/fritzdect.go b/charger/fritzdect.go index e50d42181e8..f49462bc1d6 100644 --- a/charger/fritzdect.go +++ b/charger/fritzdect.go @@ -92,9 +92,9 @@ func (c *FritzDECT) Enable(enable bool) error { return c.conn.SwitchOff() } -var _ api.MeterImport = (*FritzDECT)(nil) +var _ api.MeterEnergy = (*FritzDECT)(nil) -// ImportEnergy implements the api.MeterImport interface -func (c *FritzDECT) ImportEnergy() (float64, error) { - return c.conn.ImportEnergy() +// TotalEnergy implements the api.MeterEnergy interface +func (c *FritzDECT) TotalEnergy() (float64, error) { + return c.conn.TotalEnergy() } diff --git a/charger/go-e.go b/charger/go-e.go index 058836b5b1d..9597e2b36fa 100644 --- a/charger/go-e.go +++ b/charger/go-e.go @@ -207,10 +207,10 @@ func (c *GoE) Identify() (string, error) { return resp.Identify(), nil } -var _ api.MeterImport = (*GoE)(nil) +var _ api.MeterEnergy = (*GoE)(nil) -// totalEnergy implements the api.MeterImport interface - v2 only -func (c *GoE) ImportEnergy() (float64, error) { +// totalEnergy implements the api.MeterEnergy interface - v2 only +func (c *GoE) TotalEnergy() (float64, error) { resp, err := c.api.Status() if err != nil { return 0, err diff --git a/charger/go-e_test.go b/charger/go-e_test.go index 2c0c4af4de2..ed77a96ff6f 100644 --- a/charger/go-e_test.go +++ b/charger/go-e_test.go @@ -78,8 +78,8 @@ func TestGoEV2(t *testing.T) { t.Error("missing Identifier api") } - if _, ok := api.Cap[api.MeterImport](wb); !ok { - t.Error("missing MeterImport api") + if _, ok := api.Cap[api.MeterEnergy](wb); !ok { + t.Error("missing MeterEnergy api") } if _, ok := api.Cap[api.PhaseSwitcher](wb); !ok { diff --git a/charger/hardybarth-ecb1.go b/charger/hardybarth-ecb1.go index 47bb74beaaa..fb5bdd75233 100644 --- a/charger/hardybarth-ecb1.go +++ b/charger/hardybarth-ecb1.go @@ -206,10 +206,10 @@ func (wb *HardyBarth) CurrentPower() (float64, error) { return res.Data[obis.PowerConsumption], nil } -var _ api.MeterImport = (*HardyBarth)(nil) +var _ api.MeterEnergy = (*HardyBarth)(nil) -// ImportEnergy implements the api.MeterImport interface -func (wb *HardyBarth) ImportEnergy() (float64, error) { +// TotalEnergy implements the api.MeterEnergy interface +func (wb *HardyBarth) TotalEnergy() (float64, error) { res, err := wb.meterG() if err != nil { return 0, err diff --git a/charger/hardybarth-salia.go b/charger/hardybarth-salia.go index 6f7100e087f..f5d5f66a048 100644 --- a/charger/hardybarth-salia.go +++ b/charger/hardybarth-salia.go @@ -141,7 +141,7 @@ func NewSalia(ctx context.Context, uri, user, password string, cache time.Durati if res.Secc.Port0.Metering.Meter.Available > 0 { implement.Has(wb, implement.Meter(wb.currentPower)) - implement.Has(wb, implement.MeterImport(wb.totalEnergy)) + implement.Has(wb, implement.MeterEnergy(wb.totalEnergy)) implement.Has(wb, implement.PhaseCurrents(wb.currents)) } @@ -266,7 +266,7 @@ func (wb *Salia) currentPower() (float64, error) { return res.Secc.Port0.Metering.Power.ActiveTotal.Actual / 10, err } -// totalEnergy implements the api.MeterImport interface +// totalEnergy implements the api.MeterEnergy interface func (wb *Salia) totalEnergy() (float64, error) { res, err := wb.apiG.Get() return res.Secc.Port0.Metering.Energy.ActiveImport.Actual / 1e3, err diff --git a/charger/heatpump.go b/charger/heatpump.go index 9c91708916e..f523c4de622 100644 --- a/charger/heatpump.go +++ b/charger/heatpump.go @@ -52,7 +52,6 @@ func NewHeatpumpFromConfig(ctx context.Context, other map[string]any) (api.Charg measurement.Temperature `mapstructure:",squash"` // optional measurement.Energy `mapstructure:",squash"` // optional meter.Dimmer `mapstructure:",squash"` // optional - LegacyEnergy *plugin.Config `mapstructure:"energy"` // TODO deprecated }{ embed: embed{ Icon_: "heatpump", @@ -83,17 +82,13 @@ func NewHeatpumpFromConfig(ctx context.Context, other map[string]any) (api.Charg return nil, err } - // TODO deprecated - if err := cc.Energy.AliasImport(cc.LegacyEnergy); err != nil { - return nil, err - } - - powerG, importG, _, err := cc.Energy.Configure(ctx) + powerG, energyG, returnG, err := cc.Energy.Configure(ctx) if err != nil { return nil, err } implement.May(res, implement.Meter(powerG)) - implement.May(res, implement.MeterImport(importG)) + implement.May(res, implement.MeterEnergy(energyG)) + implement.May(res, implement.MeterReturnEnergy(returnG)) tempG, limitTempG, err := cc.Temperature.Configure(ctx) if err != nil { diff --git a/charger/heidelberg-ec.go b/charger/heidelberg-ec.go index 77a0e17ead2..cf00828981f 100644 --- a/charger/heidelberg-ec.go +++ b/charger/heidelberg-ec.go @@ -251,10 +251,10 @@ func (wb *HeidelbergEC) CurrentPower() (float64, error) { return float64(binary.BigEndian.Uint16(b)), nil } -var _ api.MeterImport = (*HeidelbergEC)(nil) +var _ api.MeterEnergy = (*HeidelbergEC)(nil) -// ImportEnergy implements the api.MeterImport interface -func (wb *HeidelbergEC) ImportEnergy() (float64, error) { +// TotalEnergy implements the api.MeterEnergy interface +func (wb *HeidelbergEC) TotalEnergy() (float64, error) { b, err := wb.conn.ReadInputRegisters(hecRegEnergy, 2) if err != nil { return 0, err diff --git a/charger/hesotec.go b/charger/hesotec.go index bc4b607147d..0cf78d86dee 100644 --- a/charger/hesotec.go +++ b/charger/hesotec.go @@ -165,10 +165,10 @@ func (wb *Hesotec) ChargeDuration() (time.Duration, error) { return time.Duration(binary.BigEndian.Uint32(b)) * time.Second, nil } -var _ api.MeterImport = (*Hesotec)(nil) +var _ api.MeterEnergy = (*Hesotec)(nil) -// ImportEnergy implements the api.MeterImport interface -func (wb *Hesotec) ImportEnergy() (float64, error) { +// TotalEnergy implements the api.MeterEnergy interface +func (wb *Hesotec) TotalEnergy() (float64, error) { b, err := wb.conn.ReadHoldingRegisters(hesotecRegEnergy, 2) if err != nil { return 0, err diff --git a/charger/homeassistant.go b/charger/homeassistant.go index 17de107df25..9da621a4f1a 100644 --- a/charger/homeassistant.go +++ b/charger/homeassistant.go @@ -79,7 +79,7 @@ func NewHomeAssistantFromConfig(other map[string]any) (api.Charger, error) { implement.Has(c, implement.Meter(func() (float64, error) { return conn.GetFloatState(cc.Power) })) } if cc.Energy != "" { - implement.Has(c, implement.MeterImport(func() (float64, error) { return conn.GetFloatState(cc.Energy) })) + implement.Has(c, implement.MeterEnergy(func() (float64, error) { return conn.GetFloatState(cc.Energy) })) } // phase currents (optional) diff --git a/charger/homematic.go b/charger/homematic.go index f3b045b640c..80ad932cc89 100644 --- a/charger/homematic.go +++ b/charger/homematic.go @@ -67,9 +67,9 @@ func (c *CCU) Enable(enable bool) error { return c.conn.Enable(enable) } -var _ api.MeterImport = (*CCU)(nil) +var _ api.MeterEnergy = (*CCU)(nil) -// ImportEnergy implements the api.MeterImport interface -func (c *CCU) ImportEnergy() (float64, error) { - return c.conn.ImportEnergy() +// TotalEnergy implements the api.MeterEnergy interface +func (c *CCU) TotalEnergy() (float64, error) { + return c.conn.TotalEnergy() } diff --git a/charger/homewizard.go b/charger/homewizard.go index 057c2a64f91..48e3c8c3a96 100644 --- a/charger/homewizard.go +++ b/charger/homewizard.go @@ -72,9 +72,9 @@ func (c *HomeWizard) Enable(enable bool) error { return c.conn.Enable(enable) } -var _ api.MeterImport = (*HomeWizard)(nil) +var _ api.MeterEnergy = (*HomeWizard)(nil) -// ImportEnergy implements the api.MeterImport interface -func (c *HomeWizard) ImportEnergy() (float64, error) { - return c.conn.ImportEnergy() +// TotalEnergy implements the api.MeterEnergy interface +func (c *HomeWizard) TotalEnergy() (float64, error) { + return c.conn.TotalEnergy() } diff --git a/charger/innogy.go b/charger/innogy.go index f7d105adf0f..1626487b41e 100644 --- a/charger/innogy.go +++ b/charger/innogy.go @@ -74,7 +74,7 @@ func NewInnogyFromConfig(ctx context.Context, other map[string]any) (api.Charger // check presence of energy meter & voltages registers if b, err := wb.conn.ReadInputRegisters(igyRegModbusTableVersion, 1); err == nil && binary.BigEndian.Uint16(b) >= 6 { - implement.Has(wb, implement.MeterImport(wb.totalEnergy)) + implement.Has(wb, implement.MeterEnergy(wb.totalEnergy)) implement.Has(wb, implement.PhaseVoltages(wb.voltages)) wb.hasVoltages = true } @@ -226,7 +226,7 @@ func (wb *Innogy) voltages() (float64, float64, float64, error) { return res[0], res[1], res[2], nil } -// totalEnergy implements the api.MeterImport interface +// totalEnergy implements the api.MeterEnergy interface func (wb *Innogy) totalEnergy() (float64, error) { b, err := wb.conn.ReadInputRegisters(igyRegEnergy, 2) if err != nil { diff --git a/charger/kathrein.go b/charger/kathrein.go index e821c702571..f74cd7b2555 100644 --- a/charger/kathrein.go +++ b/charger/kathrein.go @@ -314,10 +314,10 @@ func (wb *Kathrein) Voltages() (float64, float64, float64, error) { // removed since broken, see https://github.com/evcc-io/evcc/pull/25427 // var _ api.ChargeRater = (*Kathrein)(nil) -var _ api.MeterImport = (*Kathrein)(nil) +var _ api.MeterEnergy = (*Kathrein)(nil) -// ImportEnergy implements the api.MeterImport interface -func (wb *Kathrein) ImportEnergy() (float64, error) { +// TotalEnergy implements the api.MeterEnergy interface +func (wb *Kathrein) TotalEnergy() (float64, error) { b, err := wb.conn.ReadHoldingRegisters(kathreinRegTotalEnergy, 2) if err != nil { return 0, err diff --git a/charger/keba-modbus.go b/charger/keba-modbus.go index 91aac97a1df..fbfa3028aa6 100644 --- a/charger/keba-modbus.go +++ b/charger/keba-modbus.go @@ -131,7 +131,7 @@ func NewKebaFromConfig(ctx context.Context, other map[string]any) (api.Charger, if hasEnergyMeter { implement.Has(wb, implement.Meter(wb.currentPower)) - implement.Has(wb, implement.MeterImport(wb.totalEnergy)) + implement.Has(wb, implement.MeterEnergy(wb.totalEnergy)) implement.Has(wb, implement.PhaseCurrents(wb.currents)) } @@ -329,7 +329,7 @@ func (wb *Keba) currentPower() (float64, error) { return float64(binary.BigEndian.Uint32(b)) / 1e3, nil } -// totalEnergy implements the api.MeterImport interface +// totalEnergy implements the api.MeterEnergy interface func (wb *Keba) totalEnergy() (float64, error) { b, err := wb.conn.ReadHoldingRegisters(kebaRegEnergy, 2) if err != nil { diff --git a/charger/keba-udp.go b/charger/keba-udp.go index 734f2617204..9e20b0cef0d 100644 --- a/charger/keba-udp.go +++ b/charger/keba-udp.go @@ -61,7 +61,7 @@ func NewKebaUdpFromConfig(other map[string]any) (api.Charger, error) { if energy > 0 { implement.Has(k, implement.Meter(k.currentPower)) - implement.Has(k, implement.MeterImport(k.totalEnergy)) + implement.Has(k, implement.MeterEnergy(k.totalEnergy)) implement.Has(k, implement.PhaseCurrents(k.currents)) } @@ -284,7 +284,7 @@ func (c *KebaUdp) currentPower() (float64, error) { return float64(kr.P) / 1e3, err } -// totalEnergy implements the api.MeterImport interface +// totalEnergy implements the api.MeterEnergy interface func (c *KebaUdp) totalEnergy() (float64, error) { var kr keba.Report3 err := c.roundtrip("report", 3, &kr) diff --git a/charger/kse.go b/charger/kse.go index 64b3c6bcfab..8a136a88876 100644 --- a/charger/kse.go +++ b/charger/kse.go @@ -181,7 +181,7 @@ func (wb *KSE) CurrentPower() (float64, error) { var _ api.ChargeRater = (*KSE)(nil) -// ChargedEnergy implements the api.MeterImport interface +// ChargedEnergy implements the api.MeterEnergy interface func (wb *KSE) ChargedEnergy() (float64, error) { b, err := wb.conn.ReadInputRegisters(kseRegCurrentLoadedEnergy, 1) if err != nil { @@ -191,10 +191,10 @@ func (wb *KSE) ChargedEnergy() (float64, error) { return float64(binary.BigEndian.Uint16(b)) / 100, err } -var _ api.MeterImport = (*KSE)(nil) +var _ api.MeterEnergy = (*KSE)(nil) -// ImportEnergy implements the api.MeterImport interface -func (wb *KSE) ImportEnergy() (float64, error) { +// TotalEnergy implements the api.MeterEnergy interface +func (wb *KSE) TotalEnergy() (float64, error) { b, err := wb.conn.ReadInputRegisters(kseRegEnergy, 2) if err != nil { return 0, err diff --git a/charger/lektrico.go b/charger/lektrico.go index def1e23aae3..84878c7e0fd 100644 --- a/charger/lektrico.go +++ b/charger/lektrico.go @@ -251,10 +251,10 @@ func (wb *Lektrico) CurrentPower() (float64, error) { return res.InstantPower, err } -var _ api.MeterImport = (*Lektrico)(nil) +var _ api.MeterEnergy = (*Lektrico)(nil) -// ImportEnergy implements the api.MeterImport interface -func (wb *Lektrico) ImportEnergy() (float64, error) { +// TotalEnergy implements the api.MeterEnergy interface +func (wb *Lektrico) TotalEnergy() (float64, error) { res, err := wb.statusG.Get() return res.TotalChargedEnergy, err } diff --git a/charger/measurement/energy.go b/charger/measurement/energy.go index 196d57237c9..56c86af8291 100644 --- a/charger/measurement/energy.go +++ b/charger/measurement/energy.go @@ -2,16 +2,15 @@ package measurement import ( "context" - "errors" "fmt" "github.com/evcc-io/evcc/plugin" ) type Energy struct { - Power *plugin.Config - Import *plugin.Config // optional - Export *plugin.Config // optional + Power *plugin.Config + Energy *plugin.Config // optional + ReturnEnergy *plugin.Config // optional } func (cc *Energy) Configure(ctx context.Context) ( @@ -25,27 +24,15 @@ func (cc *Energy) Configure(ctx context.Context) ( return nil, nil, nil, fmt.Errorf("power: %w", err) } - importG, err := cc.Import.FloatGetter(ctx) + energyG, err := cc.Energy.FloatGetter(ctx) if err != nil { - return nil, nil, nil, fmt.Errorf("import: %w", err) + return nil, nil, nil, fmt.Errorf("energy: %w", err) } - exportG, err := cc.Export.FloatGetter(ctx) + returnG, err := cc.ReturnEnergy.FloatGetter(ctx) if err != nil { - return nil, nil, nil, fmt.Errorf("export: %w", err) + return nil, nil, nil, fmt.Errorf("returnEnergy: %w", err) } - return powerG, importG, exportG, nil -} - -// AliasImport assigns the legacy energy field onto Import -func (cc *Energy) AliasImport(energy *plugin.Config) error { - if energy == nil { - return nil - } - if cc.Import != nil { - return errors.New("energy and import/export are mutually exclusive") - } - cc.Import = energy - return nil + return powerG, energyG, returnG, nil } diff --git a/charger/mennekes-compact.go b/charger/mennekes-compact.go index 03a4c4fbdf9..f5e9d5b1aa3 100644 --- a/charger/mennekes-compact.go +++ b/charger/mennekes-compact.go @@ -212,10 +212,10 @@ func (wb *MennekesCompact) CurrentPower() (float64, error) { return float64(encoding.Float32(b)), nil } -var _ api.MeterImport = (*MennekesCompact)(nil) +var _ api.MeterEnergy = (*MennekesCompact)(nil) -// ImportEnergy implements the api.MeterImport interface -func (wb *MennekesCompact) ImportEnergy() (float64, error) { +// TotalEnergy implements the api.MeterEnergy interface +func (wb *MennekesCompact) TotalEnergy() (float64, error) { b, err := wb.conn.ReadHoldingRegisters(mennekesRegChargedEnergyTotal, 2) if err != nil { return 0, err @@ -256,7 +256,7 @@ func (wb *MennekesCompact) getPhaseValues(reg uint16) (float64, float64, float64 /* var _ api.ChargeRater = (*MennekesCompact)(nil) -// ChargedEnergy implements the api.MeterImport interface +// ChargedEnergy implements the api.MeterEnergy interface func (wb *MennekesCompact) ChargedEnergy() (float64, error) { b, err := wb.conn.ReadHoldingRegisters(mennekesRegChargedEnergySession, 2) if err != nil { diff --git a/charger/mennekes-hcc3.go b/charger/mennekes-hcc3.go index ba86304c2be..8a5fb11f834 100644 --- a/charger/mennekes-hcc3.go +++ b/charger/mennekes-hcc3.go @@ -161,7 +161,7 @@ func (wb *MennekesHcc3) CurrentPower() (float64, error) { var _ api.ChargeRater = (*MennekesHcc3)(nil) -// ChargedEnergy implements the api.MeterImport interface +// ChargedEnergy implements the api.MeterEnergy interface func (wb *MennekesHcc3) ChargedEnergy() (float64, error) { b, err := wb.conn.ReadInputRegisters(mennekesHcc3RegEnergy, 2) if err != nil { diff --git a/charger/nexblue.go b/charger/nexblue.go index cdfa67afdc6..b8685d9c596 100644 --- a/charger/nexblue.go +++ b/charger/nexblue.go @@ -254,10 +254,10 @@ func (wb *Nexblue) ChargedEnergy() (float64, error) { return res.Energy, err } -var _ api.MeterImport = (*Nexblue)(nil) +var _ api.MeterEnergy = (*Nexblue)(nil) -// ImportEnergy implements the api.MeterImport interface -func (wb *Nexblue) ImportEnergy() (float64, error) { +// TotalEnergy implements the api.MeterEnergy interface +func (wb *Nexblue) TotalEnergy() (float64, error) { res, err := wb.statusG.Get() return res.LifetimeEnergy, err } diff --git a/charger/nrgble_linux.go b/charger/nrgble_linux.go index 3a8286a0a26..97371cc0157 100644 --- a/charger/nrgble_linux.go +++ b/charger/nrgble_linux.go @@ -283,10 +283,10 @@ func (wb *NRGKickBLE) CurrentPower() (float64, error) { return float64(res.TotalPower) * 10, nil } -var _ api.MeterImport = (*NRGKickBLE)(nil) +var _ api.MeterEnergy = (*NRGKickBLE)(nil) -// ImportEnergy implements the api.MeterImport interface -func (wb *NRGKickBLE) ImportEnergy() (float64, error) { +// TotalEnergy implements the api.MeterEnergy interface +func (wb *NRGKickBLE) TotalEnergy() (float64, error) { var res ble.Energy if err := wb.read(ble.EnergyService, &res); err != nil { return 0, err diff --git a/charger/nrgconnect.go b/charger/nrgconnect.go index a8260c5cb6f..77bbacbfa3b 100644 --- a/charger/nrgconnect.go +++ b/charger/nrgconnect.go @@ -184,10 +184,10 @@ func (nrg *NRGKickConnect) CurrentPower() (float64, error) { return res.ChargingPower * 1e3, err } -var _ api.MeterImport = (*NRGKickConnect)(nil) +var _ api.MeterEnergy = (*NRGKickConnect)(nil) -// ImportEnergy implements the api.MeterImport interface -func (nrg *NRGKickConnect) ImportEnergy() (float64, error) { +// TotalEnergy implements the api.MeterEnergy interface +func (nrg *NRGKickConnect) TotalEnergy() (float64, error) { res, err := nrg.measurementsG.Get() if err != nil { return 0, err diff --git a/charger/nrggen2.go b/charger/nrggen2.go index c4f14fc6439..21f81220789 100644 --- a/charger/nrggen2.go +++ b/charger/nrggen2.go @@ -228,10 +228,10 @@ func (nrg *NRGKickGen2) CurrentPower() (float64, error) { return float64(encoding.Int32LswFirst(b)) * 1e-3, nil } -var _ api.MeterImport = (*NRGKickGen2)(nil) +var _ api.MeterEnergy = (*NRGKickGen2)(nil) -// ImportEnergy implements the api.MeterImport interface -func (nrg *NRGKickGen2) ImportEnergy() (float64, error) { +// TotalEnergy implements the api.MeterEnergy interface +func (nrg *NRGKickGen2) TotalEnergy() (float64, error) { b, err := nrg.conn.ReadHoldingRegisters(nrgKickGen2TotalChargedEnergy, 4) if err != nil { return 0, err diff --git a/charger/ocpp.go b/charger/ocpp.go index 0798a190fb5..d0722c1c9f3 100644 --- a/charger/ocpp.go +++ b/charger/ocpp.go @@ -110,7 +110,7 @@ func NewOCPPFromConfig(ctx context.Context, other map[string]any) (api.Charger, } if c.cp.HasMeasurement(types.MeasurandEnergyActiveImportRegister) { - implement.Has(c, implement.MeterImport(c.conn.ImportEnergy)) + implement.Has(c, implement.MeterEnergy(c.conn.TotalEnergy)) } if c.cp.HasMeasurement(types.MeasurandCurrentImport) { diff --git a/charger/ocpp/connector.go b/charger/ocpp/connector.go index 96f0e61c3de..9e5622b51db 100644 --- a/charger/ocpp/connector.go +++ b/charger/ocpp/connector.go @@ -310,7 +310,7 @@ func (conn *Connector) CurrentPower() (float64, error) { return 0, api.ErrNotAvailable } -func (conn *Connector) ImportEnergy() (float64, error) { +func (conn *Connector) TotalEnergy() (float64, error) { if !conn.cp.Connected() { return 0, api.ErrTimeout } diff --git a/charger/ocpp/connector_test.go b/charger/ocpp/connector_test.go index 3b67f8759da..459c612ce16 100644 --- a/charger/ocpp/connector_test.go +++ b/charger/ocpp/connector_test.go @@ -69,8 +69,8 @@ func (suite *connTestSuite) TestConnectorNoMeasurements() { suite.Equal(api.ErrTimeout, err, "GetMaxCurrent") // api.ErrNotAvailable - _, err = suite.conn.ImportEnergy() - suite.Equal(api.ErrNotAvailable, err, "ImportEnergy") + _, err = suite.conn.TotalEnergy() + suite.Equal(api.ErrNotAvailable, err, "TotalEnergy") _, err = suite.conn.Soc() suite.Equal(api.ErrNotAvailable, err, "Soc") _, _, _, err = suite.conn.Voltages() @@ -98,9 +98,9 @@ func (suite *connTestSuite) TestConnectorMeasurementsNoTxn() { suite.Equal(api.ErrTimeout, err, "GetMaxCurrent") // keep old values - res, err = suite.conn.ImportEnergy() - suite.NoError(err, "ImportEnergy") - suite.Equal(res, 0.001, "ImportEnergy") + res, err = suite.conn.TotalEnergy() + suite.NoError(err, "TotalEnergy") + suite.Equal(res, 0.001, "TotalEnergy") res, err = suite.conn.Soc() suite.NoError(err, "Soc") suite.Equal(res, 1.0, "Soc") @@ -119,8 +119,8 @@ func (suite *connTestSuite) TestConnectorMeasurementsRunningTxnOutdated() { _, err := suite.conn.CurrentPower() suite.Equal(api.ErrTimeout, err, "CurrentPower") - _, err = suite.conn.ImportEnergy() - suite.Equal(api.ErrTimeout, err, "ImportEnergy") + _, err = suite.conn.TotalEnergy() + suite.Equal(api.ErrTimeout, err, "TotalEnergy") _, err = suite.conn.GetMaxCurrent() suite.Equal(api.ErrTimeout, err, "GetMaxCurrent") _, err = suite.conn.Soc() @@ -140,8 +140,8 @@ func (suite *connTestSuite) TestConnectorMeasurementsRunningTxn() { _, err := suite.conn.CurrentPower() suite.NoError(err, "CurrentPower") - _, err = suite.conn.ImportEnergy() - suite.NoError(err, "ImportEnergy") + _, err = suite.conn.TotalEnergy() + suite.NoError(err, "TotalEnergy") _, err = suite.conn.GetMaxCurrent() suite.NoError(err, "GetMaxCurrent") _, err = suite.conn.Soc() diff --git a/charger/openevse.go b/charger/openevse.go index 0618f51a303..c2939e9f69f 100644 --- a/charger/openevse.go +++ b/charger/openevse.go @@ -213,10 +213,10 @@ func (c *OpenEVSE) ChargeDuration() (time.Duration, error) { return time.Duration(res.Elapsed) * time.Second, err } -var _ api.MeterImport = (*OpenEVSE)(nil) +var _ api.MeterEnergy = (*OpenEVSE)(nil) -// ImportEnergy implements the api.MeterImport interface -func (c *OpenEVSE) ImportEnergy() (float64, error) { +// TotalEnergy implements the api.MeterEnergy interface +func (c *OpenEVSE) TotalEnergy() (float64, error) { res, err := c.statusG.Get() if err != nil { return 0, err diff --git a/charger/openwb-2.0.go b/charger/openwb-2.0.go index e118ee4aaa6..19dcbb10794 100644 --- a/charger/openwb-2.0.go +++ b/charger/openwb-2.0.go @@ -168,10 +168,10 @@ func (wb *OpenWB20) CurrentPower() (float64, error) { return float64(int32(binary.BigEndian.Uint32(b))), nil } -var _ api.MeterImport = (*OpenWB20)(nil) +var _ api.MeterEnergy = (*OpenWB20)(nil) -// ImportEnergy implements the api.MeterImport interface -func (wb *OpenWB20) ImportEnergy() (float64, error) { +// TotalEnergy implements the api.MeterEnergy interface +func (wb *OpenWB20) TotalEnergy() (float64, error) { b, err := wb.conn.ReadInputRegisters(wb.base+openwbRegImport, 2) if err != nil { return 0, err diff --git a/charger/openwb-pro.go b/charger/openwb-pro.go index 50359ee8cd9..5844064e04b 100644 --- a/charger/openwb-pro.go +++ b/charger/openwb-pro.go @@ -164,10 +164,10 @@ func (wb *OpenWBPro) CurrentPower() (float64, error) { return res.PowerAll, err } -var _ api.MeterImport = (*OpenWBPro)(nil) +var _ api.MeterEnergy = (*OpenWBPro)(nil) -// ImportEnergy implements the api.MeterImport interface -func (wb *OpenWBPro) ImportEnergy() (float64, error) { +// TotalEnergy implements the api.MeterEnergy interface +func (wb *OpenWBPro) TotalEnergy() (float64, error) { res, err := wb.statusG.Get() return res.Imported / 1e3, err } diff --git a/charger/openwb.go b/charger/openwb.go index 8302b4305b7..d8fe23b8024 100644 --- a/charger/openwb.go +++ b/charger/openwb.go @@ -241,10 +241,10 @@ func (m *OpenWB) CurrentPower() (float64, error) { return m.currentPowerG() } -var _ api.MeterImport = (*OpenWB)(nil) +var _ api.MeterEnergy = (*OpenWB)(nil) -// ImportEnergy implements the api.MeterImport interface -func (m *OpenWB) ImportEnergy() (float64, error) { +// TotalEnergy implements the api.MeterEnergy interface +func (m *OpenWB) TotalEnergy() (float64, error) { return m.totalEnergyG() } diff --git a/charger/pcelectric.go b/charger/pcelectric.go index c0c36130aba..b0976135925 100644 --- a/charger/pcelectric.go +++ b/charger/pcelectric.go @@ -51,7 +51,7 @@ func NewPCElectricFromConfig(other map[string]any) (api.Charger, error) { var res pcelectric.MeterInfo if err := wb.GetJSON(wb.meter, &res); err == nil && res.MeterSerial != "" { implement.Has(wb, implement.Meter(wb.currentPower)) - implement.Has(wb, implement.MeterImport(wb.totalEnergy)) + implement.Has(wb, implement.MeterEnergy(wb.totalEnergy)) implement.Has(wb, implement.PhaseCurrents(wb.currents)) return wb, nil } @@ -248,7 +248,7 @@ func (wb *PCElectric) currentPower() (float64, error) { return 230 * (l1 + l2 + l3), err } -// ImportEnergy implements the api.MeterImport interface kwh +// TotalEnergy implements the api.MeterEnergy interface kwh func (wb *PCElectric) totalEnergy() (float64, error) { var res pcelectric.MeterInfo uri := fmt.Sprintf("%s/meterinfo/%s", wb.uri, wb.meter) diff --git a/charger/peblar.go b/charger/peblar.go index 2f50ec74f84..d406f378e6c 100644 --- a/charger/peblar.go +++ b/charger/peblar.go @@ -204,8 +204,8 @@ func (wb *Peblar) CurrentPower() (float64, error) { // deliberately removed, see https://github.com/evcc-io/evcc/issues/25956 // var _ api.ChargeRater = (*Peblar)(nil) -// ImportEnergy implements the api.MeterImport interface -func (wb *Peblar) ImportEnergy() (float64, error) { +// TotalEnergy implements the api.MeterEnergy interface +func (wb *Peblar) TotalEnergy() (float64, error) { b, err := wb.conn.ReadInputRegisters(peblarRegEnergyTotal, 4) if err != nil { return 0, err diff --git a/charger/phoenix-charx.go b/charger/phoenix-charx.go index 2814d3288dc..26151e711bc 100644 --- a/charger/phoenix-charx.go +++ b/charger/phoenix-charx.go @@ -77,7 +77,7 @@ func NewPhoenixCharxFromConfig(ctx context.Context, other map[string]any) (api.C if meter > 0 && meter != 65535 { implement.Has(wb, implement.Meter(wb.currentPower)) - implement.Has(wb, implement.MeterImport(wb.totalEnergy)) + implement.Has(wb, implement.MeterEnergy(wb.totalEnergy)) implement.Has(wb, implement.PhaseCurrents(wb.currents)) implement.Has(wb, implement.PhaseVoltages(wb.voltages)) } @@ -229,7 +229,7 @@ func (wb *PhoenixCharx) currentPower() (float64, error) { return float64(encoding.Int32(b)) / 1e3, nil } -// totalEnergy implements the api.MeterImport interface +// totalEnergy implements the api.MeterEnergy interface func (wb *PhoenixCharx) totalEnergy() (float64, error) { b, err := wb.conn.ReadHoldingRegisters(wb.register(charxRegEnergy), 4) if err != nil { diff --git a/charger/phoenix-em-eth.go b/charger/phoenix-em-eth.go index 04db0802c82..8ef5de97aa1 100644 --- a/charger/phoenix-em-eth.go +++ b/charger/phoenix-em-eth.go @@ -54,7 +54,7 @@ func NewPhoenixEMEthFromConfig(ctx context.Context, other map[string]any) (api.C // check presence of meter by voltage on l1 if b, err := wb.conn.ReadInputRegisters(phxEMEthRegVoltages, 2); err == nil && encoding.Int32LswFirst(b) > 0 { implement.Has(wb, implement.Meter(wb.currentPower)) - implement.Has(wb, implement.MeterImport(wb.totalEnergy)) + implement.Has(wb, implement.MeterEnergy(wb.totalEnergy)) implement.Has(wb, implement.PhaseCurrents(wb.currents)) implement.Has(wb, implement.PhaseVoltages(wb.voltages)) } @@ -145,7 +145,7 @@ func (wb *PhoenixEMEth) currentPower() (float64, error) { return float64(encoding.Int32LswFirst(b)*1e3) * phxEMEthSF, nil } -// totalEnergy implements the api.MeterImport interface +// totalEnergy implements the api.MeterEnergy interface func (wb *PhoenixEMEth) totalEnergy() (float64, error) { b, err := wb.conn.ReadInputRegisters(phxEMEthRegEnergy, 2) if err != nil { diff --git a/charger/phoenix-ev-eth.go b/charger/phoenix-ev-eth.go index b9016e879ef..1e3af59eb4a 100644 --- a/charger/phoenix-ev-eth.go +++ b/charger/phoenix-ev-eth.go @@ -99,7 +99,7 @@ func NewPhoenixEVEth(ctx context.Context, uri string, slaveID uint8) (api.Charge // check presence of meter by voltage on l1 if b, err := wb.conn.ReadInputRegisters(phxRegVoltages, 2); err == nil && encoding.Uint32LswFirst(b) > 0 { implement.May(wb, implement.Meter(wb.currentPower)) - implement.May(wb, implement.MeterImport(wb.totalEnergy)) + implement.May(wb, implement.MeterEnergy(wb.totalEnergy)) implement.May(wb, implement.PhaseCurrents(wb.currents)) implement.May(wb, implement.PhaseVoltages(wb.voltages)) } @@ -184,7 +184,7 @@ func (wb *PhoenixEVEth) currentPower() (float64, error) { return float64(encoding.Int32LswFirst(b)), nil } -// totalEnergy implements the api.MeterImport interface +// totalEnergy implements the api.MeterEnergy interface func (wb *PhoenixEVEth) totalEnergy() (float64, error) { if wb.isWallbe { b, err := wb.conn.ReadHoldingRegisters(phxRegEnergyWallbe, 4) diff --git a/charger/pulsatrix.go b/charger/pulsatrix.go index 431a954b93a..b38bf6836b2 100644 --- a/charger/pulsatrix.go +++ b/charger/pulsatrix.go @@ -429,10 +429,10 @@ func (c *Pulsatrix) CurrentPower() (float64, error) { return res.LastActivePower, err } -var _ api.MeterImport = (*Pulsatrix)(nil) +var _ api.MeterEnergy = (*Pulsatrix)(nil) -// ImportEnergy implements the api.MeterImport interface -func (c *Pulsatrix) ImportEnergy() (float64, error) { +// TotalEnergy implements the api.MeterEnergy interface +func (c *Pulsatrix) TotalEnergy() (float64, error) { res, err := c.data.Get() return res.EnergyImported, err } diff --git a/charger/schneider-v3.go b/charger/schneider-v3.go index 096b952942f..66cf87e09ed 100644 --- a/charger/schneider-v3.go +++ b/charger/schneider-v3.go @@ -191,10 +191,10 @@ func (wb *Schneider) CurrentPower() (float64, error) { return float64(encoding.Float32LswFirst(b)) * 1e3, nil } -var _ api.MeterImport = (*Schneider)(nil) +var _ api.MeterEnergy = (*Schneider)(nil) -// ImportEnergy implements the api.MeterImport interface -func (wb *Schneider) ImportEnergy() (float64, error) { +// TotalEnergy implements the api.MeterEnergy interface +func (wb *Schneider) TotalEnergy() (float64, error) { b, err := wb.conn.ReadHoldingRegisters(schneiderRegEnergy, 4) if err != nil { return 0, err diff --git a/charger/sgready-relay.go b/charger/sgready-relay.go index c88ad224680..92336b2a417 100644 --- a/charger/sgready-relay.go +++ b/charger/sgready-relay.go @@ -7,7 +7,6 @@ import ( "github.com/evcc-io/evcc/api" "github.com/evcc-io/evcc/api/implement" "github.com/evcc-io/evcc/charger/measurement" - "github.com/evcc-io/evcc/plugin" "github.com/evcc-io/evcc/util" "github.com/evcc-io/evcc/util/config" ) @@ -33,7 +32,6 @@ func NewSgReadyRelayFromConfig(ctx context.Context, other map[string]any) (api.C Dim *config.Typed measurement.Temperature `mapstructure:",squash"` measurement.Energy `mapstructure:",squash"` - LegacyEnergy *plugin.Config `mapstructure:"energy"` // TODO deprecated }{ embed: embed{ Icon_: "heatpump", @@ -63,17 +61,13 @@ func NewSgReadyRelayFromConfig(ctx context.Context, other map[string]any) (api.C return nil, err } - // TODO deprecated - if err := cc.Energy.AliasImport(cc.LegacyEnergy); err != nil { - return nil, err - } - - powerG, importG, _, err := cc.Energy.Configure(ctx) + powerG, energyG, returnG, err := cc.Energy.Configure(ctx) if err != nil { return nil, err } implement.May(res, implement.Meter(powerG)) - implement.May(res, implement.MeterImport(importG)) + implement.May(res, implement.MeterEnergy(energyG)) + implement.May(res, implement.MeterReturnEnergy(returnG)) tempG, limitTempG, err := cc.Temperature.Configure(ctx) if err != nil { diff --git a/charger/sgready.go b/charger/sgready.go index 929eab33a25..d18012c6f80 100644 --- a/charger/sgready.go +++ b/charger/sgready.go @@ -62,7 +62,6 @@ func NewSgReadyFromConfig(ctx context.Context, other map[string]any) (api.Charge SetMaxPower *plugin.Config // optional measurement.Temperature `mapstructure:",squash"` measurement.Energy `mapstructure:",squash"` - LegacyEnergy *plugin.Config `mapstructure:"energy"` // TODO deprecated }{ embed: embed{ Icon_: "heatpump", @@ -108,17 +107,13 @@ func NewSgReadyFromConfig(ctx context.Context, other map[string]any) (api.Charge return nil, err } - // TODO deprecated - if err := cc.Energy.AliasImport(cc.LegacyEnergy); err != nil { - return nil, err - } - - powerG, importG, _, err := cc.Energy.Configure(ctx) + powerG, energyG, returnG, err := cc.Energy.Configure(ctx) if err != nil { return nil, err } implement.May(res, implement.Meter(powerG)) - implement.May(res, implement.MeterImport(importG)) + implement.May(res, implement.MeterEnergy(energyG)) + implement.May(res, implement.MeterReturnEnergy(returnG)) tempG, limitTempG, err := cc.Temperature.Configure(ctx) if err != nil { diff --git a/charger/shelly-topac.go b/charger/shelly-topac.go index aa1ae898dc9..09257741879 100644 --- a/charger/shelly-topac.go +++ b/charger/shelly-topac.go @@ -183,10 +183,10 @@ func (c *ShellyTopAC) CurrentPower() (float64, error) { return phase.TotalPower * 1e3, nil } -var _ api.MeterImport = (*ShellyTopAC)(nil) +var _ api.MeterEnergy = (*ShellyTopAC)(nil) -// ImportEnergy implements the api.MeterImport interface -func (c *ShellyTopAC) ImportEnergy() (float64, error) { +// TotalEnergy implements the api.MeterEnergy interface +func (c *ShellyTopAC) TotalEnergy() (float64, error) { res, err := c.phaseG.Get() if err != nil { return 0, err diff --git a/charger/shelly.go b/charger/shelly.go index 2884e519e68..52426680814 100644 --- a/charger/shelly.go +++ b/charger/shelly.go @@ -93,9 +93,9 @@ func (c *Shelly) Enable(enable bool) error { } } -var _ api.MeterImport = (*Shelly)(nil) +var _ api.MeterEnergy = (*Shelly)(nil) -// ImportEnergy implements the api.MeterImport interface -func (c *Shelly) ImportEnergy() (float64, error) { - return c.conn.ImportEnergy() +// TotalEnergy implements the api.MeterEnergy interface +func (c *Shelly) TotalEnergy() (float64, error) { + return c.conn.TotalEnergy() } diff --git a/charger/sigenergy.go b/charger/sigenergy.go index 06644043989..4aaa8be80a3 100644 --- a/charger/sigenergy.go +++ b/charger/sigenergy.go @@ -159,10 +159,10 @@ func (wb *Sigenergy) CurrentPower() (float64, error) { return float64(binary.BigEndian.Uint32(b)), nil } -var _ api.MeterImport = (*Sigenergy)(nil) +var _ api.MeterEnergy = (*Sigenergy)(nil) -// ImportEnergy implements the api.MeterImport interface -func (wb *Sigenergy) ImportEnergy() (float64, error) { +// TotalEnergy implements the api.MeterEnergy interface +func (wb *Sigenergy) TotalEnergy() (float64, error) { b, err := wb.conn.ReadHoldingRegisters(regSigTotalEnergyConsumed, 2) if err != nil { return 0, err diff --git a/charger/smaevcharger.go b/charger/smaevcharger.go index 6399ca0b504..15c0aff5535 100644 --- a/charger/smaevcharger.go +++ b/charger/smaevcharger.go @@ -242,10 +242,10 @@ func (wb *Smaevcharger) MaxCurrentMillis(current float64) error { return wb.Send(value("Parameter.Inverter.AcALim", fmt.Sprintf("%.2f", current))) } -var _ api.MeterImport = (*Smaevcharger)(nil) +var _ api.MeterEnergy = (*Smaevcharger)(nil) -// ImportEnergy implements the api.MeterImport interface -func (wb *Smaevcharger) ImportEnergy() (float64, error) { +// TotalEnergy implements the api.MeterEnergy interface +func (wb *Smaevcharger) TotalEnergy() (float64, error) { res, err := wb.getMeasurement("Measurement.Metering.GridMs.TotWhIn") if _, ok := errors.AsType[*smaevcharger.ErrUnknownMeasurement](err); ok { res, err = wb.getMeasurement("Measurement.Metering.GridMs.TotWhIn.ChaSta") diff --git a/charger/smart-evse.go b/charger/smart-evse.go index 1163397adf9..3c97d625240 100644 --- a/charger/smart-evse.go +++ b/charger/smart-evse.go @@ -166,7 +166,7 @@ func NewSmartEVSE3(uri string, cache time.Duration, mode int) (api.Charger, erro // decorate optional EV meter if configured in SmartEVSE if res.EvMeter.Description != "" && res.EvMeter.Description != "Disabled" { implement.Has(wb, implement.Meter(wb.currentPower)) - implement.Has(wb, implement.MeterImport(wb.totalEnergy)) + implement.Has(wb, implement.MeterEnergy(wb.totalEnergy)) implement.Has(wb, implement.PhaseCurrents(wb.currents)) } @@ -315,7 +315,7 @@ func (wb *SmartEVSE3) currentPower() (float64, error) { return res.EvMeter.ImportActivePower, nil } -// totalEnergy implements the api.MeterImport interface +// totalEnergy implements the api.MeterEnergy interface func (wb *SmartEVSE3) totalEnergy() (float64, error) { res, err := wb.apiG.Get() if err != nil { diff --git a/charger/smartevse.go b/charger/smartevse.go index 2dd3498ae2a..dc219ad903c 100644 --- a/charger/smartevse.go +++ b/charger/smartevse.go @@ -174,10 +174,10 @@ func (wb *smartEVSE) CurrentPower() (float64, error) { return v1*i1 + v2*i2 + v3*i3, nil } -var _ api.MeterImport = (*smartEVSE)(nil) +var _ api.MeterEnergy = (*smartEVSE)(nil) -// ImportEnergy implements the api.MeterImport interface -func (wb *smartEVSE) ImportEnergy() (float64, error) { +// TotalEnergy implements the api.MeterEnergy interface +func (wb *smartEVSE) TotalEnergy() (float64, error) { b, err := wb.conn.ReadInputRegisters(smartEVSERegEnergy, 2) if err != nil { return 0, err diff --git a/charger/solax.go b/charger/solax.go index b8f116a3ddc..9b79b6ad414 100644 --- a/charger/solax.go +++ b/charger/solax.go @@ -225,10 +225,10 @@ func (wb *Solax) CurrentPower() (float64, error) { return float64(binary.BigEndian.Uint16(b)), err } -var _ api.MeterImport = (*Solax)(nil) +var _ api.MeterEnergy = (*Solax)(nil) -// ImportEnergy implements the api.MeterImport interface -func (wb *Solax) ImportEnergy() (float64, error) { +// TotalEnergy implements the api.MeterEnergy interface +func (wb *Solax) TotalEnergy() (float64, error) { b, err := wb.conn.ReadInputRegisters(solaxRegTotalEnergy, 2) if err != nil { return 0, err diff --git a/charger/sungrow.go b/charger/sungrow.go index 491ad6dba39..53bde214cd5 100644 --- a/charger/sungrow.go +++ b/charger/sungrow.go @@ -240,10 +240,10 @@ func (wb *Sungrow) Voltages() (float64, float64, float64, error) { return wb.getPhaseValues(sgRegVoltages, 10) } -var _ api.MeterImport = (*Sungrow)(nil) +var _ api.MeterEnergy = (*Sungrow)(nil) -// ImportEnergy implements the api.MeterImport interface -func (wb *Sungrow) ImportEnergy() (float64, error) { +// TotalEnergy implements the api.MeterEnergy interface +func (wb *Sungrow) TotalEnergy() (float64, error) { b, err := wb.conn.ReadInputRegisters(sgRegTotalEnergy, 2) if err != nil { return 0, err diff --git a/charger/switchsocket.go b/charger/switchsocket.go index bd7f15b368b..01294b77cf1 100644 --- a/charger/switchsocket.go +++ b/charger/switchsocket.go @@ -62,7 +62,7 @@ func NewSwitchSocketFromConfig(ctx context.Context, other map[string]any) (api.C if err != nil { return nil, err } - implement.May(c, implement.MeterImport(energy)) + implement.May(c, implement.MeterEnergy(energy)) soc, err := cc.Soc.FloatGetter(ctx) if err != nil { diff --git a/charger/tasmota.go b/charger/tasmota.go index 2aa97ca2110..c22f6ce7642 100644 --- a/charger/tasmota.go +++ b/charger/tasmota.go @@ -92,11 +92,11 @@ func (c *Tasmota) Enable(enable bool) error { return c.conn.Enable(enable) } -var _ api.MeterImport = (*Tasmota)(nil) +var _ api.MeterEnergy = (*Tasmota)(nil) -// ImportEnergy implements the api.MeterImport interface -func (c *Tasmota) ImportEnergy() (float64, error) { - return c.conn.ImportEnergy() +// TotalEnergy implements the api.MeterEnergy interface +func (c *Tasmota) TotalEnergy() (float64, error) { + return c.conn.TotalEnergy() } // Currents implements the api.PhaseCurrents interface diff --git a/charger/tplink.go b/charger/tplink.go index da37ed7bde0..27244f223cc 100644 --- a/charger/tplink.go +++ b/charger/tplink.go @@ -92,9 +92,9 @@ func (c *TPLink) Enable(enable bool) error { return nil } -var _ api.MeterImport = (*TPLink)(nil) +var _ api.MeterEnergy = (*TPLink)(nil) -// ImportEnergy implements the api.MeterImport interface -func (c *TPLink) ImportEnergy() (float64, error) { - return c.conn.ImportEnergy() +// TotalEnergy implements the api.MeterEnergy interface +func (c *TPLink) TotalEnergy() (float64, error) { + return c.conn.TotalEnergy() } diff --git a/charger/versicharge.go b/charger/versicharge.go index 1da0327ed77..36d3cb68f00 100644 --- a/charger/versicharge.go +++ b/charger/versicharge.go @@ -167,10 +167,10 @@ func (wb *Versicharge) CurrentPower() (float64, error) { return sum, nil } -var _ api.MeterImport = (*Versicharge)(nil) +var _ api.MeterEnergy = (*Versicharge)(nil) -// ImportEnergy implements the api.MeterImport interface -func (wb *Versicharge) ImportEnergy() (float64, error) { +// TotalEnergy implements the api.MeterEnergy interface +func (wb *Versicharge) TotalEnergy() (float64, error) { b, err := wb.conn.ReadHoldingRegisters(versiRegTotalEnergy, 2) if err != nil { return 0, err diff --git a/charger/vestel.go b/charger/vestel.go index dd03329e5c2..e268b54a0b6 100644 --- a/charger/vestel.go +++ b/charger/vestel.go @@ -250,10 +250,10 @@ func (wb *Vestel) CurrentPower() (float64, error) { return float64(binary.BigEndian.Uint32(b)), err } -var _ api.MeterImport = (*Vestel)(nil) +var _ api.MeterEnergy = (*Vestel)(nil) -// ImportEnergy implements the api.MeterImport interface -func (wb *Vestel) ImportEnergy() (float64, error) { +// TotalEnergy implements the api.MeterEnergy interface +func (wb *Vestel) TotalEnergy() (float64, error) { b, err := wb.conn.ReadInputRegisters(vestelRegTotalEnergy, 2) if err != nil { return 0, err diff --git a/charger/victron.go b/charger/victron.go index 9f5d3613df7..f717edfbca5 100644 --- a/charger/victron.go +++ b/charger/victron.go @@ -200,7 +200,7 @@ func (wb *Victron) CurrentPower() (float64, error) { var _ api.ChargeRater = (*Victron)(nil) -// ChargedEnergy implements the api.MeterImport interface +// ChargedEnergy implements the api.MeterEnergy interface func (wb *Victron) ChargedEnergy() (float64, error) { b, err := wb.conn.ReadHoldingRegisters(wb.regs.Energy, 1+cast.ToUint16(wb.regs.isGX)) if err != nil { diff --git a/charger/warp-ws.go b/charger/warp-ws.go index 86d61d6c23e..dd79ab2cc7f 100644 --- a/charger/warp-ws.go +++ b/charger/warp-ws.go @@ -81,7 +81,7 @@ func NewWarpWSFromConfig(ctx context.Context, other map[string]any) (api.Charger // Feature: Meter -> Meter is legacy API, Meters is the new API if w.hasFeature(warp.FeatureMeter) || w.hasFeature(warp.FeatureMeters) { implement.Has(w, implement.Meter(w.currentPower)) - implement.Has(w, implement.MeterImport(w.totalEnergy)) + implement.Has(w, implement.MeterEnergy(w.totalEnergy)) } // Feature: Meters | MeterAllValues diff --git a/charger/warp2-mqtt.go b/charger/warp2-mqtt.go index b7cf8f10549..8ed3890a53a 100644 --- a/charger/warp2-mqtt.go +++ b/charger/warp2-mqtt.go @@ -63,7 +63,7 @@ func NewWarp2FromConfig(other map[string]any) (api.Charger, error) { if wb.hasFeature(cc.Topic, warp.FeatureMeter, cc.Timeout) { implement.May(wb, implement.Meter(wb.currentPower)) - implement.May(wb, implement.MeterImport(wb.totalEnergy)) + implement.May(wb, implement.MeterEnergy(wb.totalEnergy)) } if wb.hasFeature(cc.Topic, warp.FeatureMeterPhases, cc.Timeout) { @@ -244,7 +244,7 @@ func (wb *Warp2) currentPower() (float64, error) { return res.Power, err } -// ImportEnergy implements the api.MeterImport interface +// TotalEnergy implements the api.MeterEnergy interface func (wb *Warp2) totalEnergy() (float64, error) { var res warp.MeterValues err := wb.meterG(&res) diff --git a/charger/webasto-next.go b/charger/webasto-next.go index cd0c1ffbb95..ddce64f2895 100644 --- a/charger/webasto-next.go +++ b/charger/webasto-next.go @@ -210,10 +210,10 @@ func (wb *WebastoNext) CurrentPower() (float64, error) { return float64(binary.BigEndian.Uint32(b)), nil } -var _ api.MeterImport = (*WebastoNext)(nil) +var _ api.MeterEnergy = (*WebastoNext)(nil) -// ImportEnergy implements the api.MeterImport interface -func (wb *WebastoNext) ImportEnergy() (float64, error) { +// TotalEnergy implements the api.MeterEnergy interface +func (wb *WebastoNext) TotalEnergy() (float64, error) { b, err := wb.conn.ReadHoldingRegisters(tqRegEnergyMeter, 2) if err != nil { return 0, err diff --git "a/charger/weidm\303\274ller.go" "b/charger/weidm\303\274ller.go" index 755012bfc4c..27b187a96d8 100644 --- "a/charger/weidm\303\274ller.go" +++ "b/charger/weidm\303\274ller.go" @@ -107,7 +107,7 @@ func NewWeidmüller(ctx context.Context, uri string, id uint8) (api.Charger, err // check presence of energy meter if b, err := wb.conn.ReadHoldingRegisters(wmRegTotalEnergy, 2); err == nil && binary.BigEndian.Uint32(b) > 0 { - implement.Has(wb, implement.MeterImport(wb.totalEnergy)) + implement.Has(wb, implement.MeterEnergy(wb.totalEnergy)) } return wb, nil @@ -218,7 +218,7 @@ func (wb *Weidmüller) CurrentPower() (float64, error) { return float64(encoding.Uint32LswFirst(b)) / 1e3, err } -// ImportEnergy implements the api.MeterImport interface +// TotalEnergy implements the api.MeterEnergy interface func (wb *Weidmüller) totalEnergy() (float64, error) { b, err := wb.conn.ReadHoldingRegisters(wmRegTotalEnergy, 2) if err != nil { diff --git a/cmd/dumper.go b/cmd/dumper.go index dd01e28c8a9..39c5ff7cb6c 100644 --- a/cmd/dumper.go +++ b/cmd/dumper.go @@ -85,16 +85,16 @@ func (d *dumper) Dump(name string, v any) { }) } - if v, ok := api.Cap[api.MeterImport](v); ok { + if v, ok := api.Cap[api.MeterEnergy](v); ok { d.measureTime(w, "Import", func() (string, error) { - energy, err := v.ImportEnergy() + energy, err := v.TotalEnergy() return fmt.Sprintf("%.1fkWh", energy), err }) } - if v, ok := api.Cap[api.MeterExport](v); ok { + if v, ok := api.Cap[api.MeterReturnEnergy](v); ok { d.measureTime(w, "Export", func() (string, error) { - energy, err := v.ExportEnergy() + energy, err := v.ReturnEnergy() return fmt.Sprintf("%.1fkWh", energy), err }) } diff --git a/cmd/implement/implement.go b/cmd/implement/implement.go index da45d4e8636..ec034e1f4e1 100644 --- a/cmd/implement/implement.go +++ b/cmd/implement/implement.go @@ -74,8 +74,8 @@ func generate(out io.Writer) error { reflect.TypeFor[api.Identifier](), reflect.TypeFor[api.MaxACPowerGetter](), reflect.TypeFor[api.Meter](), - reflect.TypeFor[api.MeterImport](), - reflect.TypeFor[api.MeterExport](), + reflect.TypeFor[api.MeterEnergy](), + reflect.TypeFor[api.MeterReturnEnergy](), reflect.TypeFor[api.PhaseCurrents](), reflect.TypeFor[api.PhaseGetter](), reflect.TypeFor[api.PhasePowers](), diff --git a/core/capable_test.go b/core/capable_test.go index eacfbc060e5..40b9b9157e9 100644 --- a/core/capable_test.go +++ b/core/capable_test.go @@ -34,9 +34,9 @@ func (c *charger) Capacity() float64 { return 0 } -var _ api.MeterImport = (*charger)(nil) +var _ api.MeterEnergy = (*charger)(nil) -func (c *charger) ImportEnergy() (float64, error) { +func (c *charger) TotalEnergy() (float64, error) { return 0, nil } @@ -69,7 +69,7 @@ func TestCapsWrapping(t *testing.T) { assert.True(t, ok) } { - _, ok := c.(api.MeterImport) + _, ok := c.(api.MeterEnergy) assert.True(t, ok) } { @@ -89,14 +89,14 @@ func TestCapsWrapping(t *testing.T) { } { - _, ok := m.(api.MeterImport) + _, ok := m.(api.MeterEnergy) assert.False(t, ok, "unexpected promoted energy") - assert.True(t, api.HasCap[api.MeterImport](m), "missing promoted energy cap") + assert.True(t, api.HasCap[api.MeterEnergy](m), "missing promoted energy cap") var mm any = m.(*capableMeter).Meter - _, ok = mm.(api.MeterImport) + _, ok = mm.(api.MeterEnergy) assert.True(t, ok, "missing embedded energy") - assert.True(t, api.HasCap[api.MeterImport](mm), "missing embedded energy cap") + assert.True(t, api.HasCap[api.MeterEnergy](mm), "missing embedded energy cap") } { _, ok := m.(api.Battery) diff --git a/core/loadpoint.go b/core/loadpoint.go index dda85c5bab6..bba58de4b2c 100644 --- a/core/loadpoint.go +++ b/core/loadpoint.go @@ -403,7 +403,7 @@ func (lp *Loadpoint) requestUpdate() { } // capableMeter wraps a meter with capability lookup from its source. -// This preserves capability checks (like MeterImport, MeterExport, PhaseCurrents, PhaseVoltages) when +// This preserves capability checks (like MeterEnergy, MeterReturnEnergy, PhaseCurrents, PhaseVoltages) when // the meter was extracted from a decorated charger's capability registry. type capableMeter struct { api.Meter @@ -420,7 +420,7 @@ func (lp *Loadpoint) configureChargerType(charger api.Charger) { if mt, ok := api.Cap[api.Meter](charger); ok { // preserve charger's capability registry so that subsequent - // capability checks on chargeMeter (e.g. MeterImport, PhaseCurrents) + // capability checks on chargeMeter (e.g. MeterEnergy, PhaseCurrents) // still work for decorated chargers (https://github.com/evcc-io/evcc/issues/28915) if c, ok := charger.(api.Capable); ok { lp.chargeMeter = &capableMeter{Meter: mt, Capable: c} @@ -1745,7 +1745,7 @@ func (lp *Loadpoint) publishChargeProgress() { // update energy, prefer totals var importTotal *float64 - if api.HasCap[api.MeterImport](lp.chargeMeter) { + if api.HasCap[api.MeterEnergy](lp.chargeMeter) { if f := lp.chargeMeterTotal(); f > 0 { lp.publish(keys.ChargeTotalImport, f) importTotal = &f diff --git a/core/loadpoint_session.go b/core/loadpoint_session.go index 6843f5ac376..d30315ab852 100644 --- a/core/loadpoint_session.go +++ b/core/loadpoint_session.go @@ -13,12 +13,12 @@ import ( ) func (lp *Loadpoint) chargeMeterTotal() float64 { - m, ok := api.Cap[api.MeterImport](lp.chargeMeter) + m, ok := api.Cap[api.MeterEnergy](lp.chargeMeter) if !ok { return 0 } - f, err := m.ImportEnergy() + f, err := m.TotalEnergy() if err != nil { if !errors.Is(err, api.ErrNotAvailable) { lp.log.ERROR.Printf("charge total import: %v", err) diff --git a/core/loadpoint_session_test.go b/core/loadpoint_session_test.go index d8813447e15..83f6e8f1368 100644 --- a/core/loadpoint_session_test.go +++ b/core/loadpoint_session_test.go @@ -36,14 +36,14 @@ func TestSession(t *testing.T) { defer ctrl.Finish() mm := api.NewMockMeter(ctrl) - me := api.NewMockMeterImport(ctrl) + me := api.NewMockMeterEnergy(ctrl) type EnergyDecorator struct { api.Meter - api.MeterImport + api.MeterEnergy } - cm := &EnergyDecorator{Meter: mm, MeterImport: me} + cm := &EnergyDecorator{Meter: mm, MeterEnergy: me} lp := &Loadpoint{ log: util.NewLogger("foo"), @@ -53,7 +53,7 @@ func TestSession(t *testing.T) { } // create session - me.EXPECT().ImportEnergy().Return(1.0, nil) + me.EXPECT().TotalEnergy().Return(1.0, nil) lp.createSession() assert.NotNil(t, lp.session) @@ -64,7 +64,7 @@ func TestSession(t *testing.T) { // stop charging clock.Add(time.Hour) lp.energyMetrics.Update(1.23) - me.EXPECT().ImportEnergy().Return(1.0+lp.getChargedEnergy()/1e3, nil) // match chargedEnergy + me.EXPECT().TotalEnergy().Return(1.0+lp.getChargedEnergy()/1e3, nil) // match chargedEnergy lp.stopSession() assert.NotNil(t, lp.session) @@ -79,7 +79,7 @@ func TestSession(t *testing.T) { // stop charging - 2nd leg clock.Add(time.Hour) lp.energyMetrics.Update(lp.getChargedEnergy() * 2) - me.EXPECT().ImportEnergy().Return(3.0, nil) // doesn't match chargedEnergy + me.EXPECT().TotalEnergy().Return(3.0, nil) // doesn't match chargedEnergy lp.stopSession() assert.NotNil(t, lp.session) @@ -213,14 +213,14 @@ func TestResetHeatingSession(t *testing.T) { }) mm := api.NewMockMeter(ctrl) - me := api.NewMockMeterImport(ctrl) + me := api.NewMockMeterEnergy(ctrl) type EnergyDecorator struct { api.Meter - api.MeterImport + api.MeterEnergy } - cm := &EnergyDecorator{Meter: mm, MeterImport: me} + cm := &EnergyDecorator{Meter: mm, MeterEnergy: me} lp := &Loadpoint{ log: util.NewLogger("foo"), @@ -231,7 +231,7 @@ func TestResetHeatingSession(t *testing.T) { } // create session - me.EXPECT().ImportEnergy().Return(1.0, nil) + me.EXPECT().TotalEnergy().Return(1.0, nil) lp.createSession() require.NotNil(t, lp.session) assert.True(t, lp.session.Created.IsZero()) @@ -241,7 +241,7 @@ func TestResetHeatingSession(t *testing.T) { assert.Equal(t, clock.Now(), lp.session.Created) clock.Add(36 * time.Hour) - me.EXPECT().ImportEnergy().Return(1.0, nil).MaxTimes(2) + me.EXPECT().TotalEnergy().Return(1.0, nil).MaxTimes(2) lp.resetHeatingSession() require.NotNil(t, lp.session) @@ -250,7 +250,7 @@ func TestResetHeatingSession(t *testing.T) { lp.updateSession(sessionStart(lp)) assert.Equal(t, clock.Now(), lp.session.Created) - me.EXPECT().ImportEnergy().Return(3.0, nil) + me.EXPECT().TotalEnergy().Return(3.0, nil) lp.stopSession() assert.NotNil(t, lp.session) @@ -260,7 +260,7 @@ func TestResetHeatingSession(t *testing.T) { } func TestFinalizeSessionEnergy(t *testing.T) { - setup := func(t *testing.T) (*Loadpoint, *api.MockMeterImport, *api.MockChargeRater) { + setup := func(t *testing.T) (*Loadpoint, *api.MockMeterEnergy, *api.MockChargeRater) { t.Helper() var err error serverdb.Instance, err = serverdb.New("sqlite", ":memory:") @@ -270,17 +270,17 @@ func TestFinalizeSessionEnergy(t *testing.T) { ctrl := gomock.NewController(t) mm := api.NewMockMeter(ctrl) - me := api.NewMockMeterImport(ctrl) + me := api.NewMockMeterEnergy(ctrl) rater := api.NewMockChargeRater(ctrl) type EnergyDecorator struct { api.Meter - api.MeterImport + api.MeterEnergy } cm := &EnergyDecorator{ Meter: mm, - MeterImport: me, + MeterEnergy: me, } lp := &Loadpoint{ @@ -296,17 +296,17 @@ func TestFinalizeSessionEnergy(t *testing.T) { t.Run("corrects session when ChargedEnergy increased", func(t *testing.T) { lp, me, rater := setup(t) - me.EXPECT().ImportEnergy().Return(9157.3, nil) + me.EXPECT().TotalEnergy().Return(9157.3, nil) lp.createSession() lp.session.Created = lp.clock.Now() lp.energyMetrics.Update(15.3) - me.EXPECT().ImportEnergy().Return(9164.0, nil) + me.EXPECT().TotalEnergy().Return(9164.0, nil) lp.stopSession() require.Equal(t, 15.3, lp.session.ChargedEnergy) rater.EXPECT().ChargedEnergy().Return(16.2, nil) - me.EXPECT().ImportEnergy().Return(9173.5, nil) + me.EXPECT().TotalEnergy().Return(9173.5, nil) lp.finalizeSessionEnergy() assert.Equal(t, 16.2, lp.session.ChargedEnergy) @@ -317,12 +317,12 @@ func TestFinalizeSessionEnergy(t *testing.T) { t.Run("no-op when ChargedEnergy unchanged", func(t *testing.T) { lp, me, rater := setup(t) - me.EXPECT().ImportEnergy().Return(9154.4, nil) + me.EXPECT().TotalEnergy().Return(9154.4, nil) lp.createSession() lp.session.Created = lp.clock.Now() lp.energyMetrics.Update(15.3) - me.EXPECT().ImportEnergy().Return(9164.0, nil) + me.EXPECT().TotalEnergy().Return(9164.0, nil) lp.stopSession() require.Equal(t, 15.3, lp.session.ChargedEnergy) diff --git a/core/site.go b/core/site.go index 58b57667859..bf7ec0d8b87 100644 --- a/core/site.go +++ b/core/site.go @@ -370,7 +370,7 @@ func meterCapabilities(name string, meter any) string { panic("not a meter: " + name) } - energy := api.HasCap[api.MeterImport](meter) || api.HasCap[api.MeterExport](meter) + energy := api.HasCap[api.MeterEnergy](meter) || api.HasCap[api.MeterReturnEnergy](meter) currents := api.HasCap[api.PhaseCurrents](meter) name += ":" @@ -455,7 +455,7 @@ func (site *Site) DumpConfig() { lp.log.INFO.Printf(" mode: %s", lp.GetMode()) _, power := api.Cap[api.Meter](lp.charger) - _, energy := api.Cap[api.MeterImport](lp.charger) + _, energy := api.Cap[api.MeterEnergy](lp.charger) _, currents := api.Cap[api.PhaseCurrents](lp.charger) _, phases := api.Cap[api.PhaseSwitcher](lp.charger) _, wakeup := api.Cap[api.Resurrector](lp.charger) @@ -521,12 +521,12 @@ func (site *Site) collectMeters(key string, meters []config.Device[api.Meter]) [ if err == nil { switch key { case "pv", "battery": - if m, ok := api.Cap[api.MeterExport](meter); ok { - energy, err = m.ExportEnergy() + if m, ok := api.Cap[api.MeterReturnEnergy](meter); ok { + energy, err = m.ReturnEnergy() } default: - if m, ok := api.Cap[api.MeterImport](meter); ok { - energy, err = m.ImportEnergy() + if m, ok := api.Cap[api.MeterEnergy](meter); ok { + energy, err = m.TotalEnergy() } } if err != nil { @@ -795,8 +795,8 @@ func (site *Site) updateGridMeter() error { // grid energy (import) var importEnergy *float64 - if energyMeter, ok := api.Cap[api.MeterImport](site.gridMeter); ok { - if f, err := energyMeter.ImportEnergy(); err == nil { + if energyMeter, ok := api.Cap[api.MeterEnergy](site.gridMeter); ok { + if f, err := energyMeter.TotalEnergy(); err == nil { mm.Energy = f importEnergy = &f } else { diff --git a/core/wrapper/chargerater.go b/core/wrapper/chargerater.go index 74a77a74165..939f234f965 100644 --- a/core/wrapper/chargerater.go +++ b/core/wrapper/chargerater.go @@ -12,7 +12,7 @@ import ( ) // ChargeRater is responsible for providing charged energy amount -// by implementing api.ChargeRater. It uses the charge meter's ImportEnergy or +// by implementing api.ChargeRater. It uses the charge meter's TotalEnergy or // keeps track of consumed energy by regularly updating consumed power. type ChargeRater struct { sync.Mutex @@ -39,19 +39,19 @@ func NewChargeRater(log *util.Logger, meter api.Meter) *ChargeRater { } } -// StartCharge records meter start energy. If meter does not supply ImportEnergy, +// StartCharge records meter start energy. If meter does not supply TotalEnergy, // start time is recorded and charged energy set to zero. func (cr *ChargeRater) StartCharge(continued bool) { cr.Lock() defer cr.Unlock() - // time is needed if MeterImport is not supported + // time is needed if MeterEnergy is not supported cr.start = cr.clck.Now() cr.startEnergy = nil // get end energy amount - if m, ok := api.Cap[api.MeterImport](cr.meter); ok { - if f, err := m.ImportEnergy(); err == nil { + if m, ok := api.Cap[api.MeterEnergy](cr.meter); ok { + if f, err := m.TotalEnergy(); err == nil { cr.startEnergy = &f cr.log.DEBUG.Printf("charge start energy: %.3fkWh", f) } else if !loadpoint.AcceptableError(err) { @@ -66,7 +66,7 @@ func (cr *ChargeRater) StartCharge(continued bool) { } } -// StopCharge records meter stop energy. If meter does not supply ImportEnergy, +// StopCharge records meter stop energy. If meter does not supply TotalEnergy, // stop time is recorded and accumulating energy though SetChargePower stopped. func (cr *ChargeRater) StopCharge() { cr.Lock() @@ -75,8 +75,8 @@ func (cr *ChargeRater) StopCharge() { cr.charging = false // get end energy amount - if m, ok := api.Cap[api.MeterImport](cr.meter); ok { - if f, err := m.ImportEnergy(); err == nil { + if m, ok := api.Cap[api.MeterEnergy](cr.meter); ok { + if f, err := m.TotalEnergy(); err == nil { if cr.startEnergy != nil { cr.chargedEnergy += f - *cr.startEnergy } @@ -95,8 +95,8 @@ func (cr *ChargeRater) ResetCharge() { defer cr.Unlock() // get end energy amount - if m, ok := api.Cap[api.MeterImport](cr.meter); ok { - if f, err := m.ImportEnergy(); err == nil { + if m, ok := api.Cap[api.MeterEnergy](cr.meter); ok { + if f, err := m.TotalEnergy(); err == nil { if cr.startEnergy != nil { cr.chargedEnergy += f - *cr.startEnergy cr.log.DEBUG.Printf("charge final energy: %.3fkWh", cr.chargedEnergy) @@ -122,7 +122,7 @@ func (cr *ChargeRater) SetChargePower(power float64) { } // update energy amount if not provided by meter - if !api.HasCap[api.MeterImport](cr.meter) { + if !api.HasCap[api.MeterEnergy](cr.meter) { // convert power to energy in kWh cr.chargedEnergy += power / 1e3 * float64(cr.clck.Since(cr.start)) / float64(time.Hour) // move timestamp @@ -142,13 +142,13 @@ func (cr *ChargeRater) ChargedEnergy() (float64, error) { } // get current energy amount - if m, ok := api.Cap[api.MeterImport](cr.meter); ok { - f, err := m.ImportEnergy() + if m, ok := api.Cap[api.MeterEnergy](cr.meter); ok { + f, err := m.TotalEnergy() if err != nil { return 0, fmt.Errorf("charge total import: %v", err) } - // late-latch baseline if StartCharge could not read ImportEnergy + // late-latch baseline if StartCharge could not read TotalEnergy // (e.g. OCPP transaction recovery before first MeterValues frame) if cr.startEnergy == nil { cr.startEnergy = &f diff --git a/core/wrapper/chargerater_test.go b/core/wrapper/chargerater_test.go index 29e12ee2a20..9bc6c7628d8 100644 --- a/core/wrapper/chargerater_test.go +++ b/core/wrapper/chargerater_test.go @@ -62,20 +62,20 @@ func TestWrappedMeter(t *testing.T) { defer ctrl.Finish() mm := api.NewMockMeter(ctrl) - me := api.NewMockMeterImport(ctrl) + me := api.NewMockMeterEnergy(ctrl) type EnergyDecorator struct { api.Meter - api.MeterImport + api.MeterEnergy } - cm := &EnergyDecorator{Meter: mm, MeterImport: me} + cm := &EnergyDecorator{Meter: mm, MeterEnergy: me} cr := NewChargeRater(util.NewLogger("foo"), cm) clck := clock.NewMock() cr.clck = clck - me.EXPECT().ImportEnergy().Return(2.0, nil) + me.EXPECT().TotalEnergy().Return(2.0, nil) cr.StartCharge(false) @@ -85,7 +85,7 @@ func TestWrappedMeter(t *testing.T) { clck.Add(time.Hour) cr.SetChargePower(0) - me.EXPECT().ImportEnergy().Return(3.0, nil) + me.EXPECT().TotalEnergy().Return(3.0, nil) cr.StopCharge() @@ -98,12 +98,12 @@ func TestWrappedMeter(t *testing.T) { cr.SetChargePower(1e3) // continue - me.EXPECT().ImportEnergy().Return(10.0, nil) + me.EXPECT().TotalEnergy().Return(10.0, nil) cr.StartCharge(true) clck.Add(time.Hour) // actual timing ignored as energy comes from meter - me.EXPECT().ImportEnergy().Return(12.0, nil) + me.EXPECT().TotalEnergy().Return(12.0, nil) cr.StopCharge() @@ -114,53 +114,53 @@ func TestWrappedMeter(t *testing.T) { // TestDeferredBaseline covers the OCPP transaction-recovery case: the meter is // not yet readable when StartCharge fires, so the baseline must be latched on -// the first successful ImportEnergy() read instead of defaulting to zero +// the first successful TotalEnergy() read instead of defaulting to zero // (which would cause the lifetime register to be reported as session energy). func TestDeferredBaseline(t *testing.T) { ctrl := gomock.NewController(t) defer ctrl.Finish() mm := api.NewMockMeter(ctrl) - me := api.NewMockMeterImport(ctrl) + me := api.NewMockMeterEnergy(ctrl) type EnergyDecorator struct { api.Meter - api.MeterImport + api.MeterEnergy } - cm := &EnergyDecorator{Meter: mm, MeterImport: me} + cm := &EnergyDecorator{Meter: mm, MeterEnergy: me} cr := NewChargeRater(util.NewLogger("foo"), cm) clck := clock.NewMock() cr.clck = clck // meter not yet available at StartCharge — recovered transaction before first MeterValues - me.EXPECT().ImportEnergy().Return(0.0, errors.New("not available")) + me.EXPECT().TotalEnergy().Return(0.0, errors.New("not available")) cr.StartCharge(true) // first read also fails — must surface the error, not a bogus delta - me.EXPECT().ImportEnergy().Return(0.0, errors.New("not available")) + me.EXPECT().TotalEnergy().Return(0.0, errors.New("not available")) if _, err := cr.ChargedEnergy(); err == nil { t.Errorf("expected error while meter unavailable") } // first successful read latches the baseline (lifetime register, e.g. 939 kWh) - me.EXPECT().ImportEnergy().Return(939.080, nil) + me.EXPECT().TotalEnergy().Return(939.080, nil) if f, err := cr.ChargedEnergy(); f != 0 || err != nil { t.Errorf("expected 0 on baseline-latch read, got %.3f %v", f, err) } // subsequent reads return delta against the latched baseline - me.EXPECT().ImportEnergy().Return(942.080, nil) + me.EXPECT().TotalEnergy().Return(942.080, nil) if f, err := cr.ChargedEnergy(); f != 3 || err != nil { t.Errorf("expected 3kWh delta, got %.3f %v", f, err) } - me.EXPECT().ImportEnergy().Return(944.080, nil) + me.EXPECT().TotalEnergy().Return(944.080, nil) cr.StopCharge() diff --git a/meter/_blueprint.go b/meter/_blueprint.go index db3be03a2ef..030b5b11daa 100644 --- a/meter/_blueprint.go +++ b/meter/_blueprint.go @@ -49,10 +49,10 @@ func (m *Blueprint) CurrentPower() (float64, error) { return 0, api.ErrNotAvailable } -var _ api.MeterImport = (*Blueprint)(nil) +var _ api.MeterEnergy = (*Blueprint)(nil) -// ImportEnergy implements the api.MeterImport interface -func (m *Blueprint) ImportEnergy() (float64, error) { +// TotalEnergy implements the api.MeterEnergy interface +func (m *Blueprint) TotalEnergy() (float64, error) { return 0, api.ErrNotAvailable } diff --git a/meter/cfos.go b/meter/cfos.go index 57740754a73..c350ef95fcd 100644 --- a/meter/cfos.go +++ b/meter/cfos.go @@ -67,10 +67,10 @@ func (wb *CfosPowerBrain) CurrentPower() (float64, error) { return float64(binary.BigEndian.Uint32(b)), err } -var _ api.MeterImport = (*CfosPowerBrain)(nil) +var _ api.MeterEnergy = (*CfosPowerBrain)(nil) -// ImportEnergy implements the api.MeterImport interface -func (wb *CfosPowerBrain) ImportEnergy() (float64, error) { +// TotalEnergy implements the api.MeterEnergy interface +func (wb *CfosPowerBrain) TotalEnergy() (float64, error) { b, err := wb.conn.ReadHoldingRegisters(cfosRegEnergy, 4) if err != nil { return 0, err diff --git a/meter/danfoss.go b/meter/danfoss.go index d3ac72f5883..75816e940ce 100644 --- a/meter/danfoss.go +++ b/meter/danfoss.go @@ -103,7 +103,7 @@ func NewDanfossTLX(ctx context.Context, cfg comlynx.Config, maxACPower func() fl } if hasEnergy == nil { - implement.Has(m, implement.MeterImport(m.totalEnergy)) + implement.Has(m, implement.MeterEnergy(m.totalEnergy)) } if hasVoltages == nil { implement.Has(m, implement.PhaseVoltages(m.phaseVoltages)) diff --git a/meter/discovergy.go b/meter/discovergy.go index 1336e309eba..1c4c28bdb7a 100644 --- a/meter/discovergy.go +++ b/meter/discovergy.go @@ -100,9 +100,9 @@ func (m *Discovergy) CurrentPower() (float64, error) { return m.scale * float64(res.Values.Power) / 1e3, err } -var _ api.MeterImport = (*Discovergy)(nil) +var _ api.MeterEnergy = (*Discovergy)(nil) -func (m *Discovergy) ImportEnergy() (float64, error) { +func (m *Discovergy) TotalEnergy() (float64, error) { res, err := m.dataG() return float64(res.Values.Energy) / 1e10, err } diff --git a/meter/dsmr.go b/meter/dsmr.go index 312a5cc5e5c..696cb14d8a8 100644 --- a/meter/dsmr.go +++ b/meter/dsmr.go @@ -116,7 +116,7 @@ func NewDsmr(uri, energy string, timeout time.Duration) (api.Meter, error) { // decorate energy reading if energy != "" { - implement.Has(m, implement.MeterImport(m.totalEnergy)) + implement.Has(m, implement.MeterEnergy(m.totalEnergy)) } // decorate currents @@ -284,7 +284,7 @@ func (m *Dsmr) CurrentPower() (float64, error) { return (bezug - lief) * 1e3, errors.Join(err1, err2) } -// totalEnergy implements the api.MeterImport interface +// totalEnergy implements the api.MeterEnergy interface func (m *Dsmr) totalEnergy() (float64, error) { return m.get(m.energy) } diff --git a/meter/eebus.go b/meter/eebus.go index 5be2f46e2e1..383951c7b62 100644 --- a/meter/eebus.go +++ b/meter/eebus.go @@ -176,9 +176,9 @@ func (c *EEBus) CurrentPower() (float64, error) { return c.readValue(c.scenarios.power, c.mm.Power) } -var _ api.MeterImport = (*EEBus)(nil) +var _ api.MeterEnergy = (*EEBus)(nil) -func (c *EEBus) ImportEnergy() (float64, error) { +func (c *EEBus) TotalEnergy() (float64, error) { return c.readValue(c.scenarios.energy, c.mm.EnergyConsumed) } diff --git a/meter/fritz/aha/aha.go b/meter/fritz/aha/aha.go index 6d1cf3257d6..8341585b4ef 100644 --- a/meter/fritz/aha/aha.go +++ b/meter/fritz/aha/aha.go @@ -91,10 +91,10 @@ func (c *Connection) CurrentPower() (float64, error) { return power / 1000, err // mW ==> W } -var _ api.MeterImport = (*Connection)(nil) +var _ api.MeterEnergy = (*Connection)(nil) -// ImportEnergy implements the api.MeterImport interface -func (c *Connection) ImportEnergy() (float64, error) { +// TotalEnergy implements the api.MeterEnergy interface +func (c *Connection) TotalEnergy() (float64, error) { // Energy value in Wh (total switch energy, refresh approximately every 2 minutes) resp, err := c.ExecCmd("getswitchenergy") if err != nil { diff --git a/meter/fritz/api.go b/meter/fritz/api.go index b00d6eeb81e..371f241aca2 100644 --- a/meter/fritz/api.go +++ b/meter/fritz/api.go @@ -3,7 +3,7 @@ package fritz // Meter defines the interface for Fritz connections (both legacy LUA and REST) type Meter interface { CurrentPower() (float64, error) - ImportEnergy() (float64, error) + TotalEnergy() (float64, error) } // Switch extends Meter with switch control capabilities diff --git a/meter/fritz/smarthome/smarthome.go b/meter/fritz/smarthome/smarthome.go index f975a34802f..ef9db252aa1 100644 --- a/meter/fritz/smarthome/smarthome.go +++ b/meter/fritz/smarthome/smarthome.go @@ -138,10 +138,10 @@ func (c *Connection) CurrentPower() (float64, error) { return 0, api.ErrNotAvailable } -var _ api.MeterImport = (*Connection)(nil) +var _ api.MeterEnergy = (*Connection)(nil) -// ImportEnergy implements the api.MeterImport interface -func (c *Connection) ImportEnergy() (float64, error) { +// TotalEnergy implements the api.MeterEnergy interface +func (c *Connection) TotalEnergy() (float64, error) { unit, err := c.unitG.Get() if err != nil { return 0, err diff --git a/meter/homeassistant.go b/meter/homeassistant.go index 64fb3d95479..58c14175b29 100644 --- a/meter/homeassistant.go +++ b/meter/homeassistant.go @@ -89,7 +89,7 @@ func NewHomeAssistantFromConfig(other map[string]any) (api.Meter, error) { powersG = func() (float64, float64, float64, error) { return conn.GetPhaseFloatStates(phases) } } - implement.May(m, implement.MeterImport(energyG)) + implement.May(m, implement.MeterEnergy(energyG)) if cc.Soc != "" { socG := func() (float64, error) { return conn.GetFloatState(cc.Soc) } diff --git a/meter/homematic.go b/meter/homematic.go index 0ce059fc458..76f825b35b1 100644 --- a/meter/homematic.go +++ b/meter/homematic.go @@ -63,12 +63,12 @@ func (c *CCU) CurrentPower() (float64, error) { return c.conn.CurrentPower() } -var _ api.MeterImport = (*CCU)(nil) +var _ api.MeterEnergy = (*CCU)(nil) -// ImportEnergy implements the api.MeterImport interface -func (c *CCU) ImportEnergy() (float64, error) { +// TotalEnergy implements the api.MeterEnergy interface +func (c *CCU) TotalEnergy() (float64, error) { if c.usage == "grid" { return c.conn.GridImportEnergy() } - return c.conn.ImportEnergy() + return c.conn.TotalEnergy() } diff --git a/meter/homematic/connection.go b/meter/homematic/connection.go index 7efbebb24b8..c200f2a487d 100644 --- a/meter/homematic/connection.go +++ b/meter/homematic/connection.go @@ -91,8 +91,8 @@ func (c *Connection) CurrentPower() (float64, error) { return res.FloatValue("POWER"), err } -// ImportEnergy reads the homematic HMIP-PSM meterchannel energy in Wh -func (c *Connection) ImportEnergy() (float64, error) { +// TotalEnergy reads the homematic HMIP-PSM meterchannel energy in Wh +func (c *Connection) TotalEnergy() (float64, error) { res, err := c.meterG.Get() return res.FloatValue("ENERGY_COUNTER") / 1e3, err } diff --git a/meter/homewizard.go b/meter/homewizard.go index 60b83cc0e96..7e72b7cd8c7 100644 --- a/meter/homewizard.go +++ b/meter/homewizard.go @@ -56,11 +56,11 @@ func (c *HomeWizard) CurrentPower() (float64, error) { return c.conn.CurrentPower() } -var _ api.MeterImport = (*HomeWizard)(nil) +var _ api.MeterEnergy = (*HomeWizard)(nil) -// ImportEnergy implements the api.MeterImport interface -func (c *HomeWizard) ImportEnergy() (float64, error) { - return c.conn.ImportEnergy() +// TotalEnergy implements the api.MeterEnergy interface +func (c *HomeWizard) TotalEnergy() (float64, error) { + return c.conn.TotalEnergy() } var _ api.PhaseCurrents = (*HomeWizard)(nil) diff --git a/meter/homewizard/connection.go b/meter/homewizard/connection.go index 5683b9c29db..526712ef9c4 100644 --- a/meter/homewizard/connection.go +++ b/meter/homewizard/connection.go @@ -107,8 +107,8 @@ func (c *Connection) CurrentPower() (float64, error) { return res.ActivePowerW, err } -// ImportEnergy implements the api.MeterImport interface -func (c *Connection) ImportEnergy() (float64, error) { +// TotalEnergy implements the api.MeterEnergy interface +func (c *Connection) TotalEnergy() (float64, error) { res, err := c.dataG.Get() if c.usage == "pv" { return res.TotalPowerExportT1kWh + res.TotalPowerExportT2kWh + res.TotalPowerExportT3kWh + res.TotalPowerExportT4kWh, err diff --git a/meter/lgess.go b/meter/lgess.go index f2710dd482f..2052530f904 100644 --- a/meter/lgess.go +++ b/meter/lgess.go @@ -77,7 +77,7 @@ func NewLgEss(uri, usage, registration, password string, cache time.Duration, ba implement.May(m, implement.BatteryPowerLimiter(batteryPowerLimits.Decorator())) if m.usage == "grid" && essType != lgpcs.LgEss15 { - implement.Has(m, implement.MeterImport(m.totalEnergy)) + implement.Has(m, implement.MeterEnergy(m.totalEnergy)) } if usage == "battery" { @@ -111,7 +111,7 @@ func (m *LgEss) CurrentPower() (float64, error) { } } -// totalEnergy implements the api.MeterImport interface +// totalEnergy implements the api.MeterEnergy interface func (m *LgEss) totalEnergy() (float64, error) { data, err := m.conn.Data() if err != nil { diff --git a/meter/mbmd.go b/meter/mbmd.go index c6a4c176212..be773b5b947 100644 --- a/meter/mbmd.go +++ b/meter/mbmd.go @@ -100,7 +100,7 @@ func NewMbmdFromConfig(ctx context.Context, other map[string]any) (api.Meter, er if err != nil { return nil, fmt.Errorf("invalid measurement for energy: %s", cc.Energy) } - implement.Has(m, implement.MeterImport(totalEnergy)) + implement.Has(m, implement.MeterEnergy(totalEnergy)) } // decorate soc diff --git a/meter/measurement/energy.go b/meter/measurement/energy.go index e91f338eb63..f7f3d3cfdc0 100644 --- a/meter/measurement/energy.go +++ b/meter/measurement/energy.go @@ -2,16 +2,15 @@ package measurement import ( "context" - "errors" "fmt" "github.com/evcc-io/evcc/plugin" ) type Energy struct { - Power plugin.Config - Import *plugin.Config // optional - Export *plugin.Config // optional + Power plugin.Config + Energy *plugin.Config // optional + ReturnEnergy *plugin.Config // optional } func (cc *Energy) Configure(ctx context.Context) ( @@ -25,27 +24,15 @@ func (cc *Energy) Configure(ctx context.Context) ( return nil, nil, nil, fmt.Errorf("power: %w", err) } - importG, err := cc.Import.FloatGetter(ctx) + energyG, err := cc.Energy.FloatGetter(ctx) if err != nil { - return nil, nil, nil, fmt.Errorf("import: %w", err) + return nil, nil, nil, fmt.Errorf("energy: %w", err) } - exportG, err := cc.Export.FloatGetter(ctx) + returnG, err := cc.ReturnEnergy.FloatGetter(ctx) if err != nil { - return nil, nil, nil, fmt.Errorf("export: %w", err) + return nil, nil, nil, fmt.Errorf("returnEnergy: %w", err) } - return powerG, importG, exportG, nil -} - -// AliasImport assigns the legacy energy field onto Import -func (cc *Energy) AliasImport(energy *plugin.Config) error { - if energy == nil { - return nil - } - if cc.Import != nil { - return errors.New("energy and import/export are mutually exclusive") - } - cc.Import = energy - return nil + return powerG, energyG, returnG, nil } diff --git a/meter/meter.go b/meter/meter.go index 77b15c235fd..c2fb1d51f1a 100644 --- a/meter/meter.go +++ b/meter/meter.go @@ -22,7 +22,6 @@ func NewConfigurableFromConfig(ctx context.Context, other map[string]any) (api.M measurement.Phases `mapstructure:",squash"` // optional measurement.Dimmer `mapstructure:",squash"` // optional measurement.Curtailer `mapstructure:",squash"` // optional - LegacyEnergy *plugin.Config `mapstructure:"energy"` // TODO deprecated // pv pvMaxACPower `mapstructure:",squash"` @@ -45,19 +44,14 @@ func NewConfigurableFromConfig(ctx context.Context, other map[string]any) (api.M return nil, err } - // TODO deprecated - if err := cc.Energy.AliasImport(cc.LegacyEnergy); err != nil { - return nil, err - } - - powerG, importG, exportG, err := cc.Energy.Configure(ctx) + powerG, energyG, returnG, err := cc.Energy.Configure(ctx) if err != nil { return nil, err } m, _ := NewConfigurable(powerG) - implement.May(m, implement.MeterImport(importG)) - implement.May(m, implement.MeterExport(exportG)) + implement.May(m, implement.MeterEnergy(energyG)) + implement.May(m, implement.MeterReturnEnergy(returnG)) // decorate soc socG, err := cc.Soc.FloatGetter(ctx) diff --git a/meter/meter_average.go b/meter/meter_average.go index 5d1690dddbc..472e5a81b22 100644 --- a/meter/meter_average.go +++ b/meter/meter_average.go @@ -43,14 +43,14 @@ func NewMovingAverageFromConfig(ctx context.Context, other map[string]any) (api. // decorate import reading var importEnergy func() (float64, error) - if m, ok := api.Cap[api.MeterImport](m); ok { - importEnergy = m.ImportEnergy + if m, ok := api.Cap[api.MeterEnergy](m); ok { + importEnergy = m.TotalEnergy } // decorate export reading var exportEnergy func() (float64, error) - if m, ok := api.Cap[api.MeterExport](m); ok { - exportEnergy = m.ExportEnergy + if m, ok := api.Cap[api.MeterReturnEnergy](m); ok { + exportEnergy = m.ReturnEnergy } // decorate battery reading @@ -77,8 +77,8 @@ func NewMovingAverageFromConfig(ctx context.Context, other map[string]any) (api. powers = m.Powers } - implement.May(meter, implement.MeterImport(importEnergy)) - implement.May(meter, implement.MeterExport(exportEnergy)) + implement.May(meter, implement.MeterEnergy(importEnergy)) + implement.May(meter, implement.MeterReturnEnergy(exportEnergy)) if batterySoc != nil { implement.Has(meter, implement.Battery(batterySoc)) diff --git a/meter/powerwall.go b/meter/powerwall.go index 6c36ba0f79e..a1a905e0fca 100644 --- a/meter/powerwall.go +++ b/meter/powerwall.go @@ -143,11 +143,11 @@ func NewPowerWall(uri, usage, user, password string, cache time.Duration, refres } if m.usage == "load" { - implement.Has(m, implement.MeterImport(m.totalEnergy)) + implement.Has(m, implement.MeterEnergy(m.totalEnergy)) } if m.usage == "solar" { - implement.Has(m, implement.MeterExport(m.totalEnergy)) + implement.Has(m, implement.MeterReturnEnergy(m.totalEnergy)) } if usage == "battery" { diff --git a/meter/rct.go b/meter/rct.go index f1507f7bea1..1b58a4c4145 100644 --- a/meter/rct.go +++ b/meter/rct.go @@ -100,7 +100,7 @@ func NewRCT(ctx context.Context, uri, usage string, batterySocLimits batterySocL } if usage == "grid" { - implement.Has(m, implement.MeterImport(m.totalEnergy)) + implement.Has(m, implement.MeterEnergy(m.totalEnergy)) } if usage == "pv" { @@ -264,7 +264,7 @@ func (m *RCT) CurrentPower() (float64, error) { } } -// totalEnergy implements the api.MeterImport interface +// totalEnergy implements the api.MeterEnergy interface func (m *RCT) totalEnergy() (float64, error) { switch m.usage { case "grid": diff --git a/meter/shelly/connection.go b/meter/shelly/connection.go index 04286f88dc3..abe6d2b0b34 100644 --- a/meter/shelly/connection.go +++ b/meter/shelly/connection.go @@ -15,8 +15,8 @@ type Generation interface { CurrentPower() (float64, error) Enabled() (bool, error) Enable(bool) error - ImportEnergy() (float64, error) - ExportEnergy() (float64, error) + TotalEnergy() (float64, error) + ReturnEnergy() (float64, error) } type Phases interface { diff --git a/meter/shelly/gen1.go b/meter/shelly/gen1.go index bd6519e1579..3856a584021 100644 --- a/meter/shelly/gen1.go +++ b/meter/shelly/gen1.go @@ -106,7 +106,7 @@ func (c *gen1) Enable(enable bool) error { return c.GetJSON(uri, &res) } -func (c *gen1) ImportEnergy() (float64, error) { +func (c *gen1) TotalEnergy() (float64, error) { var energy float64 res, err := c.status.Get() if err != nil { @@ -125,7 +125,7 @@ func (c *gen1) ImportEnergy() (float64, error) { return c.energy(energy) / 1000, nil } -func (c *gen1) ExportEnergy() (float64, error) { +func (c *gen1) ReturnEnergy() (float64, error) { var energy float64 res, err := c.status.Get() if err != nil { diff --git a/meter/shelly/gen2.go b/meter/shelly/gen2.go index e47c09b81a5..102c2f0f1f4 100644 --- a/meter/shelly/gen2.go +++ b/meter/shelly/gen2.go @@ -219,8 +219,8 @@ func (c *gen2) Enable(enable bool) error { return c.execEnableCmd(c.switchchannel, "Switch.Set", enable, &res) } -// ImportEnergy implements the api.MeterImport interface -func (c *gen2) ImportEnergy() (float64, error) { +// TotalEnergy implements the api.MeterEnergy interface +func (c *gen2) TotalEnergy() (float64, error) { switch { case c.hasEM1Endpoint(): res, err := c.em1data() @@ -239,8 +239,8 @@ func (c *gen2) ImportEnergy() (float64, error) { } } -// ExportEnergy implements the api.MeterImport interface -func (c *gen2) ExportEnergy() (float64, error) { +// ReturnEnergy implements the api.MeterEnergy interface +func (c *gen2) ReturnEnergy() (float64, error) { switch { case c.hasEM1Endpoint(): res, err := c.em1data() diff --git a/meter/sma.go b/meter/sma.go index 92df4b884d1..8c8454bcb2a 100644 --- a/meter/sma.go +++ b/meter/sma.go @@ -87,7 +87,7 @@ func NewSMA(uri, password, iface string, serial uint32, scale float64, usage str go sm.device.Run() if usage == "battery" { - implement.May(sm, implement.MeterExport(sm.ExportEnergy)) + implement.May(sm, implement.MeterReturnEnergy(sm.ReturnEnergy)) implement.Has(sm, implement.Battery(sm.soc)) implement.May(sm, implement.BatteryCapacity(capacity)) implement.May(sm, implement.BatterySocLimiter(batterySocLimits)) @@ -103,10 +103,10 @@ func (sm *SMA) CurrentPower() (float64, error) { return sm.scale * (sma.AsFloat(values[sunny.ActivePowerPlus]) - sma.AsFloat(values[sunny.ActivePowerMinus])), err } -var _ api.MeterExport = (*SMA)(nil) +var _ api.MeterReturnEnergy = (*SMA)(nil) -// ExportEnergy implements the api.MeterExport interface -func (sm *SMA) ExportEnergy() (float64, error) { +// ReturnEnergy implements the api.MeterReturnEnergy interface +func (sm *SMA) ReturnEnergy() (float64, error) { values, err := sm.device.Values() if sm.scale < 0 { return sma.AsFloat(values[sunny.ActiveEnergyMinus]) / 3600000, err diff --git a/meter/tasmota.go b/meter/tasmota.go index 45b9443b03f..58aa21aae38 100644 --- a/meter/tasmota.go +++ b/meter/tasmota.go @@ -89,11 +89,11 @@ func (c *Tasmota) CurrentPower() (float64, error) { return power, nil } -var _ api.MeterImport = (*Tasmota)(nil) +var _ api.MeterEnergy = (*Tasmota)(nil) -// ImportEnergy implements the api.MeterImport interface -func (c *Tasmota) ImportEnergy() (float64, error) { - return c.conn.ImportEnergy() +// TotalEnergy implements the api.MeterEnergy interface +func (c *Tasmota) TotalEnergy() (float64, error) { + return c.conn.TotalEnergy() } // powers implements the api.PhasePowers interface diff --git a/meter/tasmota/connection.go b/meter/tasmota/connection.go index f9ba6395db2..2339a0157cc 100644 --- a/meter/tasmota/connection.go +++ b/meter/tasmota/connection.go @@ -222,8 +222,8 @@ func (c *Connection) CurrentPower() (float64, error) { return res, nil } -// ImportEnergy implements the api.MeterImport interface -func (c *Connection) ImportEnergy() (float64, error) { +// TotalEnergy implements the api.MeterEnergy interface +func (c *Connection) TotalEnergy() (float64, error) { res, err := c.statusSnsG.Get() if err != nil { return 0, err diff --git a/meter/tplink/connection.go b/meter/tplink/connection.go index c7917da0ee6..cd7c2d3165e 100644 --- a/meter/tplink/connection.go +++ b/meter/tplink/connection.go @@ -94,8 +94,8 @@ func (d *Connection) CurrentPower() (float64, error) { return power, nil } -// ImportEnergy implements the api.MeterImport interface -func (d *Connection) ImportEnergy() (float64, error) { +// TotalEnergy implements the api.MeterEnergy interface +func (d *Connection) TotalEnergy() (float64, error) { var res EmeterResponse if err := d.ExecCmd(`{"emeter":{"get_realtime":null}}`, &res); err != nil { return 0, err diff --git a/meter/tq-em.go b/meter/tq-em.go index dce9cd201d2..86885d0eead 100644 --- a/meter/tq-em.go +++ b/meter/tq-em.go @@ -143,9 +143,9 @@ func (m *TqEm) CurrentPower() (float64, error) { return res.Obis1_4_0 - res.Obis2_4_0, err } -var _ api.MeterImport = (*TqEm)(nil) +var _ api.MeterEnergy = (*TqEm)(nil) -func (m *TqEm) ImportEnergy() (float64, error) { +func (m *TqEm) TotalEnergy() (float64, error) { res, err := m.dataG() return res.Obis1_8_0 / 1e3, err } diff --git a/meter/tq-em420.go b/meter/tq-em420.go index 092ec4aa2f6..450efd54726 100644 --- a/meter/tq-em420.go +++ b/meter/tq-em420.go @@ -146,9 +146,9 @@ func (m *TqEM420) CurrentPower() (float64, error) { return (res.SmartMeter.Values.ActivePowerP - res.SmartMeter.Values.ActivePowerM) / 1e3, err } -var _ api.MeterImport = (*TqEM420)(nil) +var _ api.MeterEnergy = (*TqEM420)(nil) -func (m *TqEM420) ImportEnergy() (float64, error) { +func (m *TqEM420) TotalEnergy() (float64, error) { res, err := m.dataG() return res.SmartMeter.Values.ActiveEnergyP / 1e6, err } diff --git a/plugin/meter.go b/plugin/meter.go index 4ba89e20a56..ff1e3b40467 100644 --- a/plugin/meter.go +++ b/plugin/meter.go @@ -55,7 +55,7 @@ func (o *meterPlugin) FloatGetter() (func() (float64, error), error) { switch o.method { case Energy: - if !api.HasCap[api.MeterImport](o.meter) && !api.HasCap[api.MeterExport](o.meter) { + if !api.HasCap[api.MeterEnergy](o.meter) && !api.HasCap[api.MeterReturnEnergy](o.meter) { return nil, err } case Soc: @@ -67,12 +67,12 @@ func (o *meterPlugin) FloatGetter() (func() (float64, error), error) { return func() (float64, error) { switch o.method { case Energy: - if m, ok := api.Cap[api.MeterImport](o.meter); ok { - f, err := m.ImportEnergy() + if m, ok := api.Cap[api.MeterEnergy](o.meter); ok { + f, err := m.TotalEnergy() return f * o.scale, err } - m, _ := api.Cap[api.MeterExport](o.meter) - f, err := m.ExportEnergy() + m, _ := api.Cap[api.MeterReturnEnergy](o.meter) + f, err := m.ReturnEnergy() return f * o.scale, err case Soc: m, _ := api.Cap[api.Battery](o.meter) diff --git a/server/http_config_helper.go b/server/http_config_helper.go index 6570fc3918d..4a300e84eed 100644 --- a/server/http_config_helper.go +++ b/server/http_config_helper.go @@ -274,11 +274,11 @@ func testInstance(instance any) map[string]testResult { makeResult("power", val, err) } - if dev, ok := api.Cap[api.MeterImport](instance); ok { - val, err := dev.ImportEnergy() + if dev, ok := api.Cap[api.MeterEnergy](instance); ok { + val, err := dev.TotalEnergy() makeResult("energy", val, err) - } else if dev, ok := api.Cap[api.MeterExport](instance); ok { - val, err := dev.ExportEnergy() + } else if dev, ok := api.Cap[api.MeterReturnEnergy](instance); ok { + val, err := dev.ReturnEnergy() makeResult("energy", val, err) }