Loadpoint: fix charge meter method decoration (#29912)
This commit is contained in:
parent
394553c198
commit
f41ad70ac4
3 changed files with 80 additions and 21 deletions
|
|
@ -5,6 +5,7 @@ import (
|
|||
"testing"
|
||||
|
||||
"github.com/evcc-io/evcc/api"
|
||||
"github.com/evcc-io/evcc/api/implement"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
|
|
@ -81,11 +82,7 @@ func TestCapsWrapping(t *testing.T) {
|
|||
var m api.Meter
|
||||
|
||||
if mt, ok := api.Cap[api.Meter](c); ok {
|
||||
if c, ok := c.(api.Capable); ok {
|
||||
m = &capableMeter{Meter: mt, Capable: c}
|
||||
} else {
|
||||
m = mt
|
||||
}
|
||||
m = &capableMeter{Meter: mt, source: c}
|
||||
}
|
||||
|
||||
{
|
||||
|
|
@ -104,3 +101,42 @@ func TestCapsWrapping(t *testing.T) {
|
|||
assert.True(t, api.HasCap[api.Battery](m), "missing battery cap")
|
||||
}
|
||||
}
|
||||
|
||||
// staticPhaseCharger emulates a charger like DaheimLaden: it embeds an
|
||||
// implement.Caps registry (so it satisfies api.Capable) but exposes
|
||||
// api.Meter and api.PhaseCurrents as static struct methods rather than
|
||||
// registering them in the registry.
|
||||
type staticPhaseCharger struct {
|
||||
implement.Caps
|
||||
}
|
||||
|
||||
var (
|
||||
_ api.Capable = (*staticPhaseCharger)(nil)
|
||||
_ api.Meter = (*staticPhaseCharger)(nil)
|
||||
_ api.PhaseCurrents = (*staticPhaseCharger)(nil)
|
||||
)
|
||||
|
||||
func (*staticPhaseCharger) CurrentPower() (float64, error) { return 0, nil }
|
||||
func (*staticPhaseCharger) Currents() (float64, float64, float64, error) { return 1, 2, 3, nil }
|
||||
|
||||
// TestCapableMeterStaticInterface guards against the regression in
|
||||
// https://github.com/evcc-io/evcc/issues/29877: a charger that embeds
|
||||
// implement.Caps but implements PhaseCurrents as a static method must
|
||||
// still expose that capability through the capableMeter wrapper.
|
||||
func TestCapableMeterStaticInterface(t *testing.T) {
|
||||
c := &staticPhaseCharger{Caps: implement.New()}
|
||||
|
||||
var m api.Meter
|
||||
if mt, ok := api.Cap[api.Meter](c); ok {
|
||||
m = &capableMeter{Meter: mt, source: c}
|
||||
}
|
||||
|
||||
assert.True(t, api.HasCap[api.PhaseCurrents](m),
|
||||
"PhaseCurrents must remain discoverable on capableMeter when implemented statically")
|
||||
|
||||
pc, ok := api.Cap[api.PhaseCurrents](m)
|
||||
assert.True(t, ok)
|
||||
i1, i2, i3, err := pc.Currents()
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, []float64{1, 2, 3}, []float64{i1, i2, i3})
|
||||
}
|
||||
|
|
|
|||
32
core/caps.go
Normal file
32
core/caps.go
Normal file
|
|
@ -0,0 +1,32 @@
|
|||
package core
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
|
||||
"github.com/evcc-io/evcc/api"
|
||||
)
|
||||
|
||||
// capableMeter wraps a meter with capability lookup from its source.
|
||||
// This preserves capability checks (like MeterEnergy, PhaseCurrents, PhaseVoltages) when
|
||||
// the meter was extracted from a decorated charger's capability registry.
|
||||
type capableMeter struct {
|
||||
api.Meter
|
||||
source any
|
||||
}
|
||||
|
||||
// Capability implements the api.Capable interface. It first consults the
|
||||
// source's capability registry (for decorated capabilities), then falls back
|
||||
// to a direct type assertion on the source so statically-implemented
|
||||
// interfaces (e.g. PhaseCurrents on the DaheimLaden charger) remain
|
||||
// discoverable through the wrapper (https://github.com/evcc-io/evcc/issues/29877).
|
||||
func (m *capableMeter) Capability(typ reflect.Type) (any, bool) {
|
||||
if c, ok := m.source.(api.Capable); ok {
|
||||
if impl, ok := c.Capability(typ); ok {
|
||||
return impl, true
|
||||
}
|
||||
}
|
||||
if v := reflect.ValueOf(m.source); v.IsValid() && v.Type().Implements(typ) {
|
||||
return m.source, true
|
||||
}
|
||||
return nil, false
|
||||
}
|
||||
|
|
@ -402,14 +402,6 @@ func (lp *Loadpoint) requestUpdate() {
|
|||
}
|
||||
}
|
||||
|
||||
// capableMeter wraps a meter with capability lookup from its source.
|
||||
// This preserves capability checks (like MeterEnergy, PhaseCurrents, PhaseVoltages) when
|
||||
// the meter was extracted from a decorated charger's capability registry.
|
||||
type capableMeter struct {
|
||||
api.Meter
|
||||
api.Capable
|
||||
}
|
||||
|
||||
// configureChargerType ensures that chargeMeter, Rate and Timer can use charger capabilities
|
||||
func (lp *Loadpoint) configureChargerType(charger api.Charger) {
|
||||
var integrated bool
|
||||
|
|
@ -419,14 +411,13 @@ func (lp *Loadpoint) configureChargerType(charger api.Charger) {
|
|||
integrated = true
|
||||
|
||||
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)
|
||||
// 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}
|
||||
} else {
|
||||
lp.chargeMeter = mt
|
||||
}
|
||||
// preserve charger's capability registry and static interface
|
||||
// implementations so that subsequent capability checks on
|
||||
// chargeMeter (e.g. MeterEnergy, PhaseCurrents) still work for
|
||||
// decorated chargers (https://github.com/evcc-io/evcc/issues/28915)
|
||||
// and for chargers that statically implement these interfaces
|
||||
// (https://github.com/evcc-io/evcc/issues/29877).
|
||||
lp.chargeMeter = &capableMeter{Meter: mt, source: charger}
|
||||
} else {
|
||||
mt := new(wrapper.ChargeMeter)
|
||||
_ = lp.bus.Subscribe(evChargeCurrent, lp.evChargeCurrentWrappedMeterHandler)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue