Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 4 additions & 4 deletions api/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,16 +9,16 @@ 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,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 energy in kWh
type MeterImport interface {
ImportTotal() (float64, error)
}

// PhaseCurrents provides per-phase current A
Expand Down
44 changes: 22 additions & 22 deletions api/capable_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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) ImportTotal() (float64, error) {
return 99.0, nil
}

Expand All @@ -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.ImportTotal()
assert.NoError(t, err)
assert.Equal(t, 99.0, energy)
}
Expand All @@ -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.ImportTotal()
assert.NoError(t, err)
assert.Equal(t, 99.0, energy)

Expand All @@ -81,21 +81,21 @@ 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{},
},
}

// extract Meter from decorated source (slow path: from caps registry)
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 {
Expand All @@ -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.ImportTotal()
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.ImportTotal()
assert.NoError(t, err)
assert.Equal(t, 99.0, energy)
}
38 changes: 19 additions & 19 deletions api/mock.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 3 additions & 3 deletions charger/_blueprint.go
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
// ImportTotal implements the api.MeterImport interface
func (wb *Blueprint) ImportTotal() (float64, error) {
return 0, api.ErrNotAvailable
}

Expand Down
2 changes: 1 addition & 1 deletion charger/abb.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
6 changes: 3 additions & 3 deletions charger/abl-em4.go
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
// ImportTotal implements the api.MeterImport interface
func (wb *AblEm4) ImportTotal() (float64, error) {
b, err := wb.conn.ReadHoldingRegisters(wb.base+abl4RegEnergy, 2)
if err != nil {
return 0, err
Expand Down
6 changes: 3 additions & 3 deletions charger/alfen.go
Original file line number Diff line number Diff line change
Expand Up @@ -213,10 +213,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) {
// ImportTotal implements the api.MeterImport interface
func (wb *Alfen) ImportTotal() (float64, error) {
b, err := wb.conn.ReadHoldingRegisters(alfenRegEnergy, 4)
if err != nil {
return 0, err
Expand Down
6 changes: 3 additions & 3 deletions charger/alpitronic.go
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
// ImportTotal implements the api.MeterImport interface
func (wb *AlpitronicHYC) ImportTotal() (float64, error) {
b, err := wb.conn.ReadInputRegisters(wb.reg(hycRegTotalChargedEnergy), 4)
if err != nil {
return 0, err
Expand Down
6 changes: 3 additions & 3 deletions charger/amperfied.go
Original file line number Diff line number Diff line change
Expand Up @@ -256,10 +256,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) {
// ImportTotal implements the api.MeterImport interface
func (wb *Amperfied) ImportTotal() (float64, error) {
b, err := wb.conn.ReadInputRegisters(ampRegEnergy, 2)
if err != nil {
return 0, err
Expand Down
16 changes: 8 additions & 8 deletions charger/bender.go
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ const (
bendRegChargePointState = 122 // Vehicle (Control Pilot) state
bendRegPhaseEnergy = 200 // Phase energy from primary meter (Wh)
bendRegCurrents = 212 // Currents from primary meter (mA)
bendRegTotalEnergy = 218 // Total Energy from primary meter (Wh)
bendRegImportTotal = 218 // Total Energy from primary meter (Wh)
bendRegActivePower = 220 // Active Power from primary meter (W)
bendRegVoltages = 222 // Voltages of the ocpp meter (V)
bendRegUserID = 720 // User ID (OCPP IdTag) from the current session. Bytes 0 to 19.
Expand Down Expand Up @@ -112,7 +112,7 @@ func NewBenderCCFromConfig(ctx context.Context, other map[string]any) (api.Charg

// NewBenderCC creates BenderCC charger
//
//go:generate go tool decorate -f decorateBenderCC -b *BenderCC -r api.Charger -t api.Meter,api.PhaseCurrents,api.PhaseVoltages,api.MeterEnergy,api.Battery,api.Identifier,api.ChargerEx,api.PhaseSwitcher,api.PhaseGetter
//go:generate go tool decorate -f decorateBenderCC -b *BenderCC -r api.Charger -t api.Meter,api.PhaseCurrents,api.PhaseVoltages,api.MeterImport,api.Battery,api.Identifier,api.ChargerEx,api.PhaseSwitcher,api.PhaseGetter
func NewBenderCC(ctx context.Context, uri string, id uint8, cache time.Duration) (api.Charger, error) {
conn, err := modbus.NewConnection(ctx, uri, "", "", 0, modbus.Tcp, id)
if err != nil {
Expand Down Expand Up @@ -142,7 +142,7 @@ func NewBenderCC(ctx context.Context, uri string, id uint8, cache time.Duration)
currentPower func() (float64, error)
currents func() (float64, float64, float64, error)
voltages func() (float64, float64, float64, error)
totalEnergy func() (float64, error)
importTotal func() (float64, error)
soc func() (float64, error)
identify func() (string, error)
maxCurrentMillis func(float64) error
Expand All @@ -159,7 +159,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 {
currentPower = wb.currentPower
currents = wb.currents
totalEnergy = wb.totalEnergy
importTotal = wb.importTotal

// check presence of "ocpp meter"
if b, err := wb.conn.ReadHoldingRegisters(bendRegVoltages, 2); err == nil && binary.BigEndian.Uint32(b) > 0 {
Expand Down Expand Up @@ -205,7 +205,7 @@ func NewBenderCC(ctx context.Context, uri string, id uint8, cache time.Duration)
identify = wb.identify
}

return decorateBenderCC(wb, currentPower, currents, voltages, totalEnergy, soc, identify, maxCurrentMillis, phases1p3p, getPhases), nil
return decorateBenderCC(wb, currentPower, currents, voltages, importTotal, soc, identify, maxCurrentMillis, phases1p3p, getPhases), nil
}

// heartbeat ensures that SEMP device control updates are sent about once per minute
Expand Down Expand Up @@ -387,8 +387,8 @@ 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
func (wb *BenderCC) totalEnergy() (float64, error) {
// importTotal implements the api.MeterImport interface
func (wb *BenderCC) importTotal() (float64, error) {
if wb.legacy {
b, err := wb.conn.ReadHoldingRegisters(bendRegPhaseEnergy, 6)
if err != nil {
Expand All @@ -403,7 +403,7 @@ func (wb *BenderCC) totalEnergy() (float64, error) {
return total, nil
}

b, err := wb.conn.ReadHoldingRegisters(bendRegTotalEnergy, 2)
b, err := wb.conn.ReadHoldingRegisters(bendRegImportTotal, 2)
if err != nil {
return 0, err
}
Expand Down
14 changes: 7 additions & 7 deletions charger/bender_decorators.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading