From 4b21185219f452f15d4338e2e29ca2055a3f9353 Mon Sep 17 00:00:00 2001 From: andig Date: Fri, 8 May 2026 14:33:34 +0200 Subject: [PATCH] chore: simplify implementing decorators (#28576) --- api/implement/caps.go | 57 +++ api/implement/caps_test.go | 81 ++++ api/implement/implementations.go | 449 ++++++++++++++++++++++ charger/abl.go | 8 +- charger/abl_decorators.go | 56 --- charger/alfen.go | 15 +- charger/alfen_decorators.go | 56 --- charger/amperfied.go | 13 +- charger/amperfied_decorators.go | 56 --- charger/bender.go | 49 +-- charger/bender_decorators.go | 140 ------- charger/cfos.go | 20 +- charger/cfos_decorators.go | 80 ---- charger/charger.go | 40 +- charger/charger_decorators.go | 165 -------- charger/daheimladen.go | 13 +- charger/daheimladen_decorators.go | 56 --- charger/eebus.go | 11 +- charger/eebus_decorators.go | 68 ---- charger/em2go.go | 19 +- charger/em2go_decorators.go | 68 ---- charger/etek.go | 18 +- charger/etek_decorators.go | 68 ---- charger/evecube.go | 14 +- charger/evecube_decorators.go | 56 --- charger/evsedin.go | 11 +- charger/evsedin_decorators.go | 44 --- charger/evsewifi.go | 29 +- charger/evsewifi_decorators.go | 104 ----- charger/ghosteebus.go | 22 +- charger/ghosteebus_decorators.go | 92 ----- charger/ghosteebus_test.go | 20 +- charger/go-e.go | 16 +- charger/go-e_decorators.go | 56 --- charger/hardybarth-salia.go | 25 +- charger/hardybarth-salia_decorators.go | 92 ----- charger/heatpump.go | 11 +- charger/heatpump_decorators.go | 80 ---- charger/homeassistant.go | 30 +- charger/homeassistant_decorators.go | 104 ----- charger/innogy.go | 14 +- charger/innogy_decorators.go | 56 --- charger/keba-modbus.go | 31 +- charger/keba-modbus_decorators.go | 116 ------ charger/keba-udp.go | 9 +- charger/keba-udp_decorators.go | 68 ---- charger/kse.go | 21 +- charger/kse_decorators.go | 68 ---- charger/mennekes-compact.go | 10 +- charger/mennekes-compact_decorators.go | 44 --- charger/nexblue_decorators.go | 35 -- charger/nrggen2.go | 10 +- charger/nrggen2_decorators.go | 44 --- charger/ocpp.go | 28 +- charger/ocpp_decorators.go | 116 ------ charger/openevse.go | 10 +- charger/openevse_decorators.go | 44 --- charger/openwb-2.0.go | 13 +- charger/openwb-2.0_decorators.go | 56 --- charger/openwb-native_decorators_linux.go | 68 ---- charger/openwb-native_linux.go | 20 +- charger/openwb-pro.go | 10 +- charger/openwb-pro_decorators.go | 44 --- charger/openwb.go | 16 +- charger/openwb_decorators.go | 56 --- charger/pcelectric.go | 10 +- charger/pcelectric_decorators.go | 68 ---- charger/peblar.go | 14 +- charger/peblar_decorators.go | 56 --- charger/phoenix-charx.go | 10 +- charger/phoenix-charx_decorators.go | 80 ---- charger/phoenix-em-eth.go | 22 +- charger/phoenix-em-eth_decorators.go | 80 ---- charger/phoenix-ev-eth.go | 28 +- charger/phoenix-ev-eth_decorators.go | 104 ----- charger/pulsares.go | 10 +- charger/pulsares_decorators.go | 44 --- charger/semp.go | 19 +- charger/semp_decorators.go | 68 ---- charger/sgready-relay.go | 17 +- charger/sgready.go | 11 +- charger/sgready_decorators.go | 80 ---- charger/shelly.go | 14 +- charger/shelly_decorators.go | 68 ---- charger/smart-evse.go | 27 +- charger/smart-evse_decorators.go | 116 ------ charger/solax.go | 14 +- charger/solax_decorators.go | 56 --- charger/switchsocket.go | 29 +- charger/switchsocket_decorators.go | 56 --- charger/tasmota.go | 8 +- charger/tasmota_decorators.go | 56 --- charger/vaillant.go | 15 +- charger/vaillant_decorators.go | 56 --- charger/vestel.go | 19 +- charger/vestel_decorators.go | 68 ---- charger/victron.go | 15 +- charger/victron_decorators.go | 56 --- charger/warp-ws.go | 26 +- charger/warp-ws_decorators.go | 116 ------ charger/warp2-mqtt.go | 26 +- charger/warp2-mqtt_decorators.go | 116 ------ charger/weidmüller.go | 7 +- charger/weidmüller_decorators.go | 44 --- charger/zaptec.go | 10 +- charger/zaptec_decorators.go | 44 --- cmd/decorate/decorate.go | 348 ----------------- cmd/decorate/decorate.tpl | 48 --- cmd/decorate/decorate_decorators.go | 68 ---- cmd/decorate/header.tpl | 9 - cmd/implement/implement.go | 171 ++++++++ cmd/implement/implement.tpl | 32 ++ go.mod | 4 +- meter/bosch_bpts5_hybrid.go | 26 +- meter/danfoss.go | 22 +- meter/dsmr.go | 14 +- meter/dsmr_decorators.go | 56 --- meter/e3dc.go | 22 +- meter/e3dc_decorators.go | 104 ----- meter/ecoflow.go | 26 +- meter/goodwe-wifi.go | 16 +- meter/goodwe-wifi_decorators.go | 56 --- meter/homeassistant.go | 23 +- meter/lgess.go | 28 +- meter/lgess_decorators.go | 104 ----- meter/mbmd.go | 43 ++- meter/meter.go | 113 ++---- meter/meter_average.go | 13 +- meter/meter_decorators.go | 187 --------- meter/openwb.go | 9 +- meter/powerwall.go | 33 +- meter/powerwall_decorators.go | 104 ----- meter/rct.go | 40 +- meter/rct_decorators.go | 125 ------ meter/shelly.go | 14 +- meter/shelly_decorators.go | 68 ---- meter/sma.go | 28 +- meter/tasmota.go | 9 +- meter/tasmota_decorators.go | 68 ---- meter/tq-em.go | 7 +- meter/tq-em_decorators.go | 44 --- meter/zendure.go | 11 +- vehicle/homeassistant.go | 55 +-- vehicle/tronity.go | 16 +- vehicle/tronity_decorators.go | 68 ---- vehicle/vehicle.go | 77 ++-- vehicle/vehicle_decorators.go | 177 --------- vehicle/vehicle_decorators_test.go | 170 -------- 148 files changed, 1500 insertions(+), 6358 deletions(-) create mode 100644 api/implement/caps.go create mode 100644 api/implement/caps_test.go create mode 100644 api/implement/implementations.go delete mode 100644 charger/abl_decorators.go delete mode 100644 charger/alfen_decorators.go delete mode 100644 charger/amperfied_decorators.go delete mode 100644 charger/bender_decorators.go delete mode 100644 charger/cfos_decorators.go delete mode 100644 charger/charger_decorators.go delete mode 100644 charger/daheimladen_decorators.go delete mode 100644 charger/eebus_decorators.go delete mode 100644 charger/em2go_decorators.go delete mode 100644 charger/etek_decorators.go delete mode 100644 charger/evecube_decorators.go delete mode 100644 charger/evsedin_decorators.go delete mode 100644 charger/evsewifi_decorators.go delete mode 100644 charger/ghosteebus_decorators.go delete mode 100644 charger/go-e_decorators.go delete mode 100644 charger/hardybarth-salia_decorators.go delete mode 100644 charger/heatpump_decorators.go delete mode 100644 charger/homeassistant_decorators.go delete mode 100644 charger/innogy_decorators.go delete mode 100644 charger/keba-modbus_decorators.go delete mode 100644 charger/keba-udp_decorators.go delete mode 100644 charger/kse_decorators.go delete mode 100644 charger/mennekes-compact_decorators.go delete mode 100644 charger/nexblue_decorators.go delete mode 100644 charger/nrggen2_decorators.go delete mode 100644 charger/ocpp_decorators.go delete mode 100644 charger/openevse_decorators.go delete mode 100644 charger/openwb-2.0_decorators.go delete mode 100644 charger/openwb-native_decorators_linux.go delete mode 100644 charger/openwb-pro_decorators.go delete mode 100644 charger/openwb_decorators.go delete mode 100644 charger/pcelectric_decorators.go delete mode 100644 charger/peblar_decorators.go delete mode 100644 charger/phoenix-charx_decorators.go delete mode 100644 charger/phoenix-em-eth_decorators.go delete mode 100644 charger/phoenix-ev-eth_decorators.go delete mode 100644 charger/pulsares_decorators.go delete mode 100644 charger/semp_decorators.go delete mode 100644 charger/sgready_decorators.go delete mode 100644 charger/shelly_decorators.go delete mode 100644 charger/smart-evse_decorators.go delete mode 100644 charger/solax_decorators.go delete mode 100644 charger/switchsocket_decorators.go delete mode 100644 charger/tasmota_decorators.go delete mode 100644 charger/vaillant_decorators.go delete mode 100644 charger/vestel_decorators.go delete mode 100644 charger/victron_decorators.go delete mode 100644 charger/warp-ws_decorators.go delete mode 100644 charger/warp2-mqtt_decorators.go delete mode 100644 charger/weidmüller_decorators.go delete mode 100644 charger/zaptec_decorators.go delete mode 100644 cmd/decorate/decorate.go delete mode 100644 cmd/decorate/decorate.tpl delete mode 100644 cmd/decorate/decorate_decorators.go delete mode 100644 cmd/decorate/header.tpl create mode 100644 cmd/implement/implement.go create mode 100644 cmd/implement/implement.tpl delete mode 100644 meter/dsmr_decorators.go delete mode 100644 meter/e3dc_decorators.go delete mode 100644 meter/goodwe-wifi_decorators.go delete mode 100644 meter/lgess_decorators.go delete mode 100644 meter/meter_decorators.go delete mode 100644 meter/powerwall_decorators.go delete mode 100644 meter/rct_decorators.go delete mode 100644 meter/shelly_decorators.go delete mode 100644 meter/tasmota_decorators.go delete mode 100644 meter/tq-em_decorators.go delete mode 100644 vehicle/tronity_decorators.go delete mode 100644 vehicle/vehicle_decorators.go delete mode 100644 vehicle/vehicle_decorators_test.go diff --git a/api/implement/caps.go b/api/implement/caps.go new file mode 100644 index 000000000..5c2967c30 --- /dev/null +++ b/api/implement/caps.go @@ -0,0 +1,57 @@ +package implement + +import ( + "reflect" + + "github.com/evcc-io/evcc/api" +) + +// Has registers impl as a capability on c. It panics if impl is nil. +func Has[T any](c Caps, impl T) { + typ := reflect.TypeFor[T]() + if isNil(impl) { + panic("implement: nil " + typ.String()) + } + c.add(typ, impl) +} + +// May registers impl as a capability on c. If impl is nil, it is silently ignored. +func May[T any](c Caps, impl T) { + if isNil(impl) { + return + } + c.add(reflect.TypeFor[T](), impl) +} + +func isNil(v any) bool { + if v == nil { + return true + } + switch rv := reflect.ValueOf(v); rv.Kind() { + case reflect.Pointer, reflect.Interface, reflect.Map, reflect.Slice, reflect.Chan, reflect.Func: + return rv.IsNil() + } + return false +} + +type Caps interface { + api.Capable + add(typ reflect.Type, impl any) +} + +// New creates a capabilities store exposing the api.Capable interface +func New() Caps { + return make(caps) +} + +type caps map[reflect.Type]any + +// Capability implements the api.Capable interface +func (caps caps) Capability(typ reflect.Type) (any, bool) { + c, ok := caps[typ] + return c, ok +} + +func (caps caps) add(typ reflect.Type, impl any) { + caps[typ] = impl +} diff --git a/api/implement/caps_test.go b/api/implement/caps_test.go new file mode 100644 index 000000000..49031f693 --- /dev/null +++ b/api/implement/caps_test.go @@ -0,0 +1,81 @@ +package implement + +import ( + "testing" + + "github.com/evcc-io/evcc/api" + "github.com/stretchr/testify/assert" +) + +type isCapable struct { + Caps +} + +func TestHas(t *testing.T) { + s := &isCapable{ + Caps: New(), + } + + Has(s, Meter(func() (float64, error) { + return 0, nil + })) + + mm, ok := api.Cap[api.Meter](s) + assert.True(t, ok) + + v, err := mm.CurrentPower() + assert.NoError(t, err) + assert.Equal(t, 0.0, v) +} + +func TestHasPanicsOnNil(t *testing.T) { + s := &isCapable{ + Caps: New(), + } + + assert.Panics(t, func() { + Has[api.Meter](s, nil) + }) +} + +func TestMayIgnoresNil(t *testing.T) { + s := &isCapable{ + Caps: New(), + } + + assert.NotPanics(t, func() { + May[api.Meter](s, nil) + }) + + _, ok := api.Cap[api.Meter](s) + assert.False(t, ok) +} + +func TestMayRegistersNonNil(t *testing.T) { + s := &isCapable{ + Caps: New(), + } + + May(s, Meter(func() (float64, error) { + return 1.0, nil + })) + + mm, ok := api.Cap[api.Meter](s) + assert.True(t, ok) + + v, err := mm.CurrentPower() + assert.NoError(t, err) + assert.Equal(t, 1.0, v) +} + +func TestMayIgnoresNilFuncConstructor(t *testing.T) { + s := &isCapable{ + Caps: New(), + } + + var fn func() (float64, error) + May(s, Meter(fn)) + + _, ok := api.Cap[api.Meter](s) + assert.False(t, ok) +} diff --git a/api/implement/implementations.go b/api/implement/implementations.go new file mode 100644 index 000000000..efe9cf657 --- /dev/null +++ b/api/implement/implementations.go @@ -0,0 +1,449 @@ +package implement + +// Code generated by github.com/evcc-io/evcc/api/implement/caps.go. DO NOT EDIT. + +import ( + "time" + + "github.com/evcc-io/evcc/api" +) + +func Meter(meter0 func() (float64, error)) api.Meter { + if meter0 == nil { + return nil + } + return &iMeter{meter0} +} + +type iMeter struct { + meter0 func() (float64, error) +} + +func (i *iMeter) CurrentPower() (float64, error) { + return i.meter0() +} + +func BatteryCapacity(batteryCapacity0 func() float64) api.BatteryCapacity { + if batteryCapacity0 == nil { + return nil + } + return &iBatteryCapacity{batteryCapacity0} +} + +type iBatteryCapacity struct { + batteryCapacity0 func() float64 +} + +func (i *iBatteryCapacity) Capacity() float64 { + return i.batteryCapacity0() +} + +func SocLimiter(socLimiter0 func() (int64, error)) api.SocLimiter { + if socLimiter0 == nil { + return nil + } + return &iSocLimiter{socLimiter0} +} + +type iSocLimiter struct { + socLimiter0 func() (int64, error) +} + +func (i *iSocLimiter) GetLimitSoc() (int64, error) { + return i.socLimiter0() +} + +func BatteryController(batteryController0 func(api.BatteryMode) error) api.BatteryController { + if batteryController0 == nil { + return nil + } + return &iBatteryController{batteryController0} +} + +type iBatteryController struct { + batteryController0 func(api.BatteryMode) error +} + +func (i *iBatteryController) SetBatteryMode(p0 api.BatteryMode) error { + return i.batteryController0(p0) +} + +func BatterySocLimiter(batterySocLimiter0 func() (float64, float64)) api.BatterySocLimiter { + if batterySocLimiter0 == nil { + return nil + } + return &iBatterySocLimiter{batterySocLimiter0} +} + +type iBatterySocLimiter struct { + batterySocLimiter0 func() (float64, float64) +} + +func (i *iBatterySocLimiter) GetSocLimits() (float64, float64) { + return i.batterySocLimiter0() +} + +func BatteryPowerLimiter(batteryPowerLimiter0 func() (float64, float64)) api.BatteryPowerLimiter { + if batteryPowerLimiter0 == nil { + return nil + } + return &iBatteryPowerLimiter{batteryPowerLimiter0} +} + +type iBatteryPowerLimiter struct { + batteryPowerLimiter0 func() (float64, float64) +} + +func (i *iBatteryPowerLimiter) GetPowerLimits() (float64, float64) { + return i.batteryPowerLimiter0() +} + +func PhasePowers(phasePowers0 func() (float64, float64, float64, error)) api.PhasePowers { + if phasePowers0 == nil { + return nil + } + return &iPhasePowers{phasePowers0} +} + +type iPhasePowers struct { + phasePowers0 func() (float64, float64, float64, error) +} + +func (i *iPhasePowers) Powers() (float64, float64, float64, error) { + return i.phasePowers0() +} + +func PhaseGetter(phaseGetter0 func() (int, error)) api.PhaseGetter { + if phaseGetter0 == nil { + return nil + } + return &iPhaseGetter{phaseGetter0} +} + +type iPhaseGetter struct { + phaseGetter0 func() (int, error) +} + +func (i *iPhaseGetter) GetPhases() (int, error) { + return i.phaseGetter0() +} + +func ChargeController(chargeController0 func(bool) error) api.ChargeController { + if chargeController0 == nil { + return nil + } + return &iChargeController{chargeController0} +} + +type iChargeController struct { + chargeController0 func(bool) error +} + +func (i *iChargeController) ChargeEnable(p0 bool) error { + return i.chargeController0(p0) +} + +func CurrentController(currentController0 func(int64) error) api.CurrentController { + if currentController0 == nil { + return nil + } + return &iCurrentController{currentController0} +} + +type iCurrentController struct { + currentController0 func(int64) error +} + +func (i *iCurrentController) MaxCurrent(p0 int64) error { + return i.currentController0(p0) +} + +func PhaseSwitcher(phaseSwitcher0 func(int) error) api.PhaseSwitcher { + if phaseSwitcher0 == nil { + return nil + } + return &iPhaseSwitcher{phaseSwitcher0} +} + +type iPhaseSwitcher struct { + phaseSwitcher0 func(int) error +} + +func (i *iPhaseSwitcher) Phases1p3p(p0 int) error { + return i.phaseSwitcher0(p0) +} + +func Battery(battery0 func() (float64, error)) api.Battery { + if battery0 == nil { + return nil + } + return &iBattery{battery0} +} + +type iBattery struct { + battery0 func() (float64, error) +} + +func (i *iBattery) Soc() (float64, error) { + return i.battery0() +} + +func ChargeState(chargeState0 func() (api.ChargeStatus, error)) api.ChargeState { + if chargeState0 == nil { + return nil + } + return &iChargeState{chargeState0} +} + +type iChargeState struct { + chargeState0 func() (api.ChargeStatus, error) +} + +func (i *iChargeState) Status() (api.ChargeStatus, error) { + return i.chargeState0() +} + +func MeterEnergy(meterEnergy0 func() (float64, error)) api.MeterEnergy { + if meterEnergy0 == nil { + return nil + } + return &iMeterEnergy{meterEnergy0} +} + +type iMeterEnergy struct { + meterEnergy0 func() (float64, error) +} + +func (i *iMeterEnergy) TotalEnergy() (float64, error) { + return i.meterEnergy0() +} + +func PhaseCurrents(phaseCurrents0 func() (float64, float64, float64, error)) api.PhaseCurrents { + if phaseCurrents0 == nil { + return nil + } + return &iPhaseCurrents{phaseCurrents0} +} + +type iPhaseCurrents struct { + phaseCurrents0 func() (float64, float64, float64, error) +} + +func (i *iPhaseCurrents) Currents() (float64, float64, float64, error) { + return i.phaseCurrents0() +} + +func PhaseVoltages(phaseVoltages0 func() (float64, float64, float64, error)) api.PhaseVoltages { + if phaseVoltages0 == nil { + return nil + } + return &iPhaseVoltages{phaseVoltages0} +} + +type iPhaseVoltages struct { + phaseVoltages0 func() (float64, float64, float64, error) +} + +func (i *iPhaseVoltages) Voltages() (float64, float64, float64, error) { + return i.phaseVoltages0() +} + +func MaxACPowerGetter(maxACPowerGetter0 func() float64) api.MaxACPowerGetter { + if maxACPowerGetter0 == nil { + return nil + } + return &iMaxACPowerGetter{maxACPowerGetter0} +} + +type iMaxACPowerGetter struct { + maxACPowerGetter0 func() float64 +} + +func (i *iMaxACPowerGetter) MaxACPower() float64 { + return i.maxACPowerGetter0() +} + +func CurrentGetter(currentGetter0 func() (float64, error)) api.CurrentGetter { + if currentGetter0 == nil { + return nil + } + return &iCurrentGetter{currentGetter0} +} + +type iCurrentGetter struct { + currentGetter0 func() (float64, error) +} + +func (i *iCurrentGetter) GetMaxCurrent() (float64, error) { + return i.currentGetter0() +} + +func Curtailer(curtailer0 func(bool) error, curtailer1 func() (bool, error)) api.Curtailer { + if curtailer0 == nil || curtailer1 == nil { + return nil + } + return &iCurtailer{curtailer0, curtailer1} +} + +type iCurtailer struct { + curtailer0 func(bool) error + curtailer1 func() (bool, error) +} + +func (i *iCurtailer) Curtail(p0 bool) error { + return i.curtailer0(p0) +} + +func (i *iCurtailer) Curtailed() (bool, error) { + return i.curtailer1() +} + +func Resurrector(resurrector0 func() error) api.Resurrector { + if resurrector0 == nil { + return nil + } + return &iResurrector{resurrector0} +} + +type iResurrector struct { + resurrector0 func() error +} + +func (i *iResurrector) WakeUp() error { + return i.resurrector0() +} + +func VehicleOdometer(vehicleOdometer0 func() (float64, error)) api.VehicleOdometer { + if vehicleOdometer0 == nil { + return nil + } + return &iVehicleOdometer{vehicleOdometer0} +} + +type iVehicleOdometer struct { + vehicleOdometer0 func() (float64, error) +} + +func (i *iVehicleOdometer) Odometer() (float64, error) { + return i.vehicleOdometer0() +} + +func VehicleRange(vehicleRange0 func() (int64, error)) api.VehicleRange { + if vehicleRange0 == nil { + return nil + } + return &iVehicleRange{vehicleRange0} +} + +type iVehicleRange struct { + vehicleRange0 func() (int64, error) +} + +func (i *iVehicleRange) Range() (int64, error) { + return i.vehicleRange0() +} + +func VehicleClimater(vehicleClimater0 func() (bool, error)) api.VehicleClimater { + if vehicleClimater0 == nil { + return nil + } + return &iVehicleClimater{vehicleClimater0} +} + +type iVehicleClimater struct { + vehicleClimater0 func() (bool, error) +} + +func (i *iVehicleClimater) Climater() (bool, error) { + return i.vehicleClimater0() +} + +func VehicleFinishTimer(vehicleFinishTimer0 func() (time.Time, error)) api.VehicleFinishTimer { + if vehicleFinishTimer0 == nil { + return nil + } + return &iVehicleFinishTimer{vehicleFinishTimer0} +} + +type iVehicleFinishTimer struct { + vehicleFinishTimer0 func() (time.Time, error) +} + +func (i *iVehicleFinishTimer) FinishTime() (time.Time, error) { + return i.vehicleFinishTimer0() +} + +func VehiclePosition(vehiclePosition0 func() (float64, float64, error)) api.VehiclePosition { + if vehiclePosition0 == nil { + return nil + } + return &iVehiclePosition{vehiclePosition0} +} + +type iVehiclePosition struct { + vehiclePosition0 func() (float64, float64, error) +} + +func (i *iVehiclePosition) Position() (float64, float64, error) { + return i.vehiclePosition0() +} + +func Identifier(identifier0 func() (string, error)) api.Identifier { + if identifier0 == nil { + return nil + } + return &iIdentifier{identifier0} +} + +type iIdentifier struct { + identifier0 func() (string, error) +} + +func (i *iIdentifier) Identify() (string, error) { + return i.identifier0() +} + +func ChargerEx(chargerEx0 func(float64) error) api.ChargerEx { + if chargerEx0 == nil { + return nil + } + return &iChargerEx{chargerEx0} +} + +type iChargerEx struct { + chargerEx0 func(float64) error +} + +func (i *iChargerEx) MaxCurrentMillis(p0 float64) error { + return i.chargerEx0(p0) +} + +func ChargeRater(chargeRater0 func() (float64, error)) api.ChargeRater { + if chargeRater0 == nil { + return nil + } + return &iChargeRater{chargeRater0} +} + +type iChargeRater struct { + chargeRater0 func() (float64, error) +} + +func (i *iChargeRater) ChargedEnergy() (float64, error) { + return i.chargeRater0() +} + +func StatusReasoner(statusReasoner0 func() (api.Reason, error)) api.StatusReasoner { + if statusReasoner0 == nil { + return nil + } + return &iStatusReasoner{statusReasoner0} +} + +type iStatusReasoner struct { + statusReasoner0 func() (api.Reason, error) +} + +func (i *iStatusReasoner) StatusReason() (api.Reason, error) { + return i.statusReasoner0() +} diff --git a/charger/abl.go b/charger/abl.go index e921b2940..03e3dd41d 100644 --- a/charger/abl.go +++ b/charger/abl.go @@ -24,6 +24,7 @@ import ( "time" "github.com/evcc-io/evcc/api" + "github.com/evcc-io/evcc/api/implement" "github.com/evcc-io/evcc/util" "github.com/evcc-io/evcc/util/modbus" "github.com/evcc-io/evcc/util/sponsor" @@ -31,6 +32,7 @@ import ( // ABLeMH charger implementation type ABLeMH struct { + implement.Caps conn *modbus.Connection curr uint16 } @@ -96,8 +98,6 @@ func NewABLeMHFromConfig(ctx context.Context, other map[string]any) (api.Charger return NewABLeMH(ctx, cc.URI, cc.Device, cc.Comset, cc.Baudrate, cc.ID, cc.Timeout) } -//go:generate go tool decorate -f decorateABLeMH -b *ABLeMH -r api.Charger -t api.Meter,api.PhaseCurrents - // NewABLeMH creates ABLeMH charger func NewABLeMH(ctx context.Context, uri, device, comset string, baudrate int, slaveID uint8, timeout time.Duration) (api.Charger, error) { conn, err := modbus.NewConnection(ctx, uri, device, comset, baudrate, modbus.Ascii, slaveID) @@ -117,6 +117,7 @@ func NewABLeMH(ctx context.Context, uri, device, comset string, baudrate int, sl conn.Logger(log.TRACE) wb := &ABLeMH{ + Caps: implement.New(), conn: conn, curr: uint16(6 / 0.06), } @@ -125,7 +126,8 @@ func NewABLeMH(ctx context.Context, uri, device, comset string, baudrate int, sl // check presence of current sensor if err == nil && (b[3]&ablSensorPresent != 0) { - return decorateABLeMH(wb, wb.currentPower, wb.currents), nil + implement.Has(wb, implement.Meter(wb.currentPower)) + implement.Has(wb, implement.PhaseCurrents(wb.currents)) } return wb, nil diff --git a/charger/abl_decorators.go b/charger/abl_decorators.go deleted file mode 100644 index 18714b77d..000000000 --- a/charger/abl_decorators.go +++ /dev/null @@ -1,56 +0,0 @@ -package charger - -// Code generated by github.com/evcc-io/evcc/cmd/tools/decorate.go. DO NOT EDIT. - -import ( - "reflect" - - "github.com/evcc-io/evcc/api" -) - -func decorateABLeMH(base *ABLeMH, meter func() (float64, error), phaseCurrents func() (float64, float64, float64, error)) api.Charger { - caps := make(map[reflect.Type]any) - - if meter != nil { - caps[reflect.TypeFor[api.Meter]()] = &decorateABLeMHMeterImpl{meter: meter} - } - - if phaseCurrents != nil { - caps[reflect.TypeFor[api.PhaseCurrents]()] = &decorateABLeMHPhaseCurrentsImpl{phaseCurrents: phaseCurrents} - } - - if len(caps) == 0 { - return base - } - - return &decorateABLeMHCapable{ABLeMH: base, caps: caps} -} - -type decorateABLeMHCapable struct { - *ABLeMH - caps map[reflect.Type]any -} - -func (d *decorateABLeMHCapable) Capability(typ reflect.Type) (any, bool) { - c, ok := d.caps[typ] - if !ok && reflect.TypeOf(d).Implements(typ) { - return d, true - } - return c, ok -} - -type decorateABLeMHMeterImpl struct { - meter func() (float64, error) -} - -func (impl *decorateABLeMHMeterImpl) CurrentPower() (float64, error) { - return impl.meter() -} - -type decorateABLeMHPhaseCurrentsImpl struct { - phaseCurrents func() (float64, float64, float64, error) -} - -func (impl *decorateABLeMHPhaseCurrentsImpl) Currents() (float64, float64, float64, error) { - return impl.phaseCurrents() -} diff --git a/charger/alfen.go b/charger/alfen.go index 9f6b94fd6..46d41ae39 100644 --- a/charger/alfen.go +++ b/charger/alfen.go @@ -25,6 +25,7 @@ import ( "time" "github.com/evcc-io/evcc/api" + "github.com/evcc-io/evcc/api/implement" "github.com/evcc-io/evcc/util" "github.com/evcc-io/evcc/util/modbus" "github.com/evcc-io/evcc/util/sponsor" @@ -35,6 +36,7 @@ import ( // Alfen charger implementation type Alfen struct { + implement.Caps log *util.Logger conn *modbus.Connection mu sync.Mutex @@ -56,8 +58,6 @@ func init() { registry.AddCtx("alfen", NewAlfenFromConfig) } -//go:generate go tool decorate -f decorateAlfen -b *Alfen -r api.Charger -t api.PhaseSwitcher,api.PhaseGetter - // NewAlfenFromConfig creates a Alfen charger from generic config func NewAlfenFromConfig(ctx context.Context, other map[string]any) (api.Charger, error) { cc := modbus.TcpSettings{ @@ -86,6 +86,7 @@ func NewAlfen(ctx context.Context, uri string, slaveID uint8) (api.Charger, erro conn.Logger(log.TRACE) wb := &Alfen{ + Caps: implement.New(), log: log, conn: conn, } @@ -97,19 +98,15 @@ func NewAlfen(ctx context.Context, uri string, slaveID uint8) (api.Charger, erro return nil, err } - var ( - phasesS func(int) error - phasesG func() (int, error) - ) if v2 != 0 && v3 != 0 { wb.log.DEBUG.Println("detected 3p alfen") - phasesS = wb.phases1p3p - phasesG = wb.getPhases + implement.Has(wb, implement.PhaseSwitcher(wb.phases1p3p)) + implement.Has(wb, implement.PhaseGetter(wb.getPhases)) } else { wb.log.DEBUG.Println("detected 1p alfen") } - return decorateAlfen(wb, phasesS, phasesG), nil + return wb, nil } func (wb *Alfen) heartbeat(ctx context.Context) { diff --git a/charger/alfen_decorators.go b/charger/alfen_decorators.go deleted file mode 100644 index 610538e87..000000000 --- a/charger/alfen_decorators.go +++ /dev/null @@ -1,56 +0,0 @@ -package charger - -// Code generated by github.com/evcc-io/evcc/cmd/tools/decorate.go. DO NOT EDIT. - -import ( - "reflect" - - "github.com/evcc-io/evcc/api" -) - -func decorateAlfen(base *Alfen, phaseSwitcher func(int) error, phaseGetter func() (int, error)) api.Charger { - caps := make(map[reflect.Type]any) - - if phaseSwitcher != nil { - caps[reflect.TypeFor[api.PhaseSwitcher]()] = &decorateAlfenPhaseSwitcherImpl{phaseSwitcher: phaseSwitcher} - } - - if phaseGetter != nil { - caps[reflect.TypeFor[api.PhaseGetter]()] = &decorateAlfenPhaseGetterImpl{phaseGetter: phaseGetter} - } - - if len(caps) == 0 { - return base - } - - return &decorateAlfenCapable{Alfen: base, caps: caps} -} - -type decorateAlfenCapable struct { - *Alfen - caps map[reflect.Type]any -} - -func (d *decorateAlfenCapable) Capability(typ reflect.Type) (any, bool) { - c, ok := d.caps[typ] - if !ok && reflect.TypeOf(d).Implements(typ) { - return d, true - } - return c, ok -} - -type decorateAlfenPhaseGetterImpl struct { - phaseGetter func() (int, error) -} - -func (impl *decorateAlfenPhaseGetterImpl) GetPhases() (int, error) { - return impl.phaseGetter() -} - -type decorateAlfenPhaseSwitcherImpl struct { - phaseSwitcher func(int) error -} - -func (impl *decorateAlfenPhaseSwitcherImpl) Phases1p3p(p0 int) error { - return impl.phaseSwitcher(p0) -} diff --git a/charger/amperfied.go b/charger/amperfied.go index ce87919cf..ab2317754 100644 --- a/charger/amperfied.go +++ b/charger/amperfied.go @@ -25,6 +25,7 @@ import ( "time" "github.com/evcc-io/evcc/api" + "github.com/evcc-io/evcc/api/implement" "github.com/evcc-io/evcc/util" "github.com/evcc-io/evcc/util/modbus" "github.com/evcc-io/evcc/util/sponsor" @@ -32,6 +33,7 @@ import ( // Amperfied charger implementation type Amperfied struct { + implement.Caps log *util.Logger conn *modbus.Connection current uint16 @@ -59,8 +61,6 @@ func init() { registry.AddCtx("amperfied", NewAmperfiedFromConfig) } -//go:generate go tool decorate -f decorateAmperfied -b *Amperfied -r api.Charger -t api.PhaseSwitcher,api.PhaseGetter - // NewAmperfiedFromConfig creates a Amperfied charger from generic config func NewAmperfiedFromConfig(ctx context.Context, other map[string]any) (api.Charger, error) { cc := struct { @@ -94,6 +94,7 @@ func NewAmperfied(ctx context.Context, uri string, slaveID uint8, phases bool) ( conn.Logger(log.TRACE) wb := &Amperfied{ + Caps: implement.New(), log: log, conn: conn, current: 60, // assume min current @@ -108,14 +109,12 @@ func NewAmperfied(ctx context.Context, uri string, slaveID uint8, phases bool) ( go wb.heartbeat(ctx, time.Duration(u)*time.Millisecond/2) } - var phases1p3p func(int) error - var phasesG func() (int, error) if phases { - phases1p3p = wb.phases1p3p - phasesG = wb.getPhases + implement.Has(wb, implement.PhaseSwitcher(wb.phases1p3p)) + implement.Has(wb, implement.PhaseGetter(wb.getPhases)) } - return decorateAmperfied(wb, phases1p3p, phasesG), nil + return wb, nil } func (wb *Amperfied) heartbeat(ctx context.Context, timeout time.Duration) { diff --git a/charger/amperfied_decorators.go b/charger/amperfied_decorators.go deleted file mode 100644 index 67548cbd6..000000000 --- a/charger/amperfied_decorators.go +++ /dev/null @@ -1,56 +0,0 @@ -package charger - -// Code generated by github.com/evcc-io/evcc/cmd/tools/decorate.go. DO NOT EDIT. - -import ( - "reflect" - - "github.com/evcc-io/evcc/api" -) - -func decorateAmperfied(base *Amperfied, phaseSwitcher func(int) error, phaseGetter func() (int, error)) api.Charger { - caps := make(map[reflect.Type]any) - - if phaseSwitcher != nil { - caps[reflect.TypeFor[api.PhaseSwitcher]()] = &decorateAmperfiedPhaseSwitcherImpl{phaseSwitcher: phaseSwitcher} - } - - if phaseGetter != nil { - caps[reflect.TypeFor[api.PhaseGetter]()] = &decorateAmperfiedPhaseGetterImpl{phaseGetter: phaseGetter} - } - - if len(caps) == 0 { - return base - } - - return &decorateAmperfiedCapable{Amperfied: base, caps: caps} -} - -type decorateAmperfiedCapable struct { - *Amperfied - caps map[reflect.Type]any -} - -func (d *decorateAmperfiedCapable) Capability(typ reflect.Type) (any, bool) { - c, ok := d.caps[typ] - if !ok && reflect.TypeOf(d).Implements(typ) { - return d, true - } - return c, ok -} - -type decorateAmperfiedPhaseGetterImpl struct { - phaseGetter func() (int, error) -} - -func (impl *decorateAmperfiedPhaseGetterImpl) GetPhases() (int, error) { - return impl.phaseGetter() -} - -type decorateAmperfiedPhaseSwitcherImpl struct { - phaseSwitcher func(int) error -} - -func (impl *decorateAmperfiedPhaseSwitcherImpl) Phases1p3p(p0 int) error { - return impl.phaseSwitcher(p0) -} diff --git a/charger/bender.go b/charger/bender.go index 68029ceda..8c68186ea 100644 --- a/charger/bender.go +++ b/charger/bender.go @@ -32,6 +32,7 @@ import ( "time" "github.com/evcc-io/evcc/api" + "github.com/evcc-io/evcc/api/implement" "github.com/evcc-io/evcc/charger/semp" "github.com/evcc-io/evcc/util" "github.com/evcc-io/evcc/util/modbus" @@ -47,6 +48,7 @@ type sempHandler struct { // BenderCC charger implementation type BenderCC struct { + implement.Caps conn *modbus.Connection current uint16 regCurr uint16 @@ -111,8 +113,6 @@ 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 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 { @@ -127,6 +127,7 @@ func NewBenderCC(ctx context.Context, uri string, id uint8, cache time.Duration) conn.Logger(log.TRACE) wb := &BenderCC{ + Caps: implement.New(), conn: conn, current: 6, // assume min current regCurr: bendRegHemsCurrentLimit, @@ -138,18 +139,6 @@ func NewBenderCC(ctx context.Context, uri string, id uint8, cache time.Duration) wb.legacy = true } - var ( - currentPower func() (float64, error) - currents func() (float64, float64, float64, error) - voltages func() (float64, float64, float64, error) - totalEnergy func() (float64, error) - soc func() (float64, error) - identify func() (string, error) - maxCurrentMillis func(float64) error - phases1p3p func(int) error - getPhases func() (int, error) - ) - // check presence of metering reg := uint16(bendRegActivePower) if wb.legacy { @@ -157,41 +146,39 @@ 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 + implement.Has(wb, implement.Meter(wb.currentPower)) + implement.Has(wb, implement.PhaseCurrents(wb.currents)) + 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 { - voltages = wb.voltages + implement.Has(wb, implement.PhaseVoltages(wb.voltages)) } if !wb.legacy { if _, err := wb.conn.ReadHoldingRegisters(bendRegEVBatteryState, 1); err == nil { - soc = wb.soc + implement.Has(wb, implement.Battery(wb.soc)) } } } // check feature mA if _, err := wb.conn.ReadHoldingRegisters(bendRegHemsCurrentLimit10, 1); err == nil { - maxCurrentMillis = wb.maxCurrentMillis + implement.Has(wb, implement.ChargerEx(wb.maxCurrentMillis)) wb.regCurr = bendRegHemsCurrentLimit10 } - // check feature modbus power control/1p3p fpr Mennekes 4you / 4business chargers + // check feature modbus power control/1p3p for Mennekes 4you / 4business chargers if _, err := wb.conn.ReadHoldingRegisters(bendRegHemsPowerLimit, 1); err == nil { - phases1p3p = wb.phases1p3pMennekes - getPhases = wb.getPhasesMennekes - } - - // check feature semp phase switching - if phases1p3p == nil { + implement.Has(wb, implement.PhaseSwitcher(wb.phases1p3pMennekes)) + implement.Has(wb, implement.PhaseGetter(wb.getPhasesMennekes)) + } else { + // check feature semp phase switching if wb.supportsSEMPPhaseSwitching(uri, cache) { // set initial SEMP power limit to max so modbus control from 6 to 16 A is possible if err := wb.semp.conn.SendDeviceControl(wb.semp.deviceID, 0xffff); err == nil { - phases1p3p = wb.phases1p3pSEMP - getPhases = wb.getPhases + implement.Has(wb, implement.PhaseSwitcher(wb.phases1p3pSEMP)) + implement.Has(wb, implement.PhaseGetter(wb.getPhases)) // start heartbeat to keep connection alive go wb.heartbeat(ctx) } else { @@ -202,10 +189,10 @@ func NewBenderCC(ctx context.Context, uri string, id uint8, cache time.Duration) // check feature rfid if _, err := wb.identify(); err == nil { - identify = wb.identify + implement.Has(wb, implement.Identifier(wb.identify)) } - return decorateBenderCC(wb, currentPower, currents, voltages, totalEnergy, soc, identify, maxCurrentMillis, phases1p3p, getPhases), nil + return wb, nil } // heartbeat ensures that SEMP device control updates are sent about once per minute diff --git a/charger/bender_decorators.go b/charger/bender_decorators.go deleted file mode 100644 index 140fec094..000000000 --- a/charger/bender_decorators.go +++ /dev/null @@ -1,140 +0,0 @@ -package charger - -// Code generated by github.com/evcc-io/evcc/cmd/tools/decorate.go. DO NOT EDIT. - -import ( - "reflect" - - "github.com/evcc-io/evcc/api" -) - -func decorateBenderCC(base *BenderCC, meter func() (float64, error), phaseCurrents func() (float64, float64, float64, error), phaseVoltages func() (float64, float64, float64, error), meterEnergy func() (float64, error), battery func() (float64, error), identifier func() (string, error), chargerEx func(float64) error, phaseSwitcher func(int) error, phaseGetter func() (int, error)) api.Charger { - caps := make(map[reflect.Type]any) - - if meter != nil { - caps[reflect.TypeFor[api.Meter]()] = &decorateBenderCCMeterImpl{meter: meter} - } - - if phaseCurrents != nil { - caps[reflect.TypeFor[api.PhaseCurrents]()] = &decorateBenderCCPhaseCurrentsImpl{phaseCurrents: phaseCurrents} - } - - if phaseVoltages != nil { - caps[reflect.TypeFor[api.PhaseVoltages]()] = &decorateBenderCCPhaseVoltagesImpl{phaseVoltages: phaseVoltages} - } - - if meterEnergy != nil { - caps[reflect.TypeFor[api.MeterEnergy]()] = &decorateBenderCCMeterEnergyImpl{meterEnergy: meterEnergy} - } - - if battery != nil { - caps[reflect.TypeFor[api.Battery]()] = &decorateBenderCCBatteryImpl{battery: battery} - } - - if identifier != nil { - caps[reflect.TypeFor[api.Identifier]()] = &decorateBenderCCIdentifierImpl{identifier: identifier} - } - - if chargerEx != nil { - caps[reflect.TypeFor[api.ChargerEx]()] = &decorateBenderCCChargerExImpl{chargerEx: chargerEx} - } - - if phaseSwitcher != nil { - caps[reflect.TypeFor[api.PhaseSwitcher]()] = &decorateBenderCCPhaseSwitcherImpl{phaseSwitcher: phaseSwitcher} - } - - if phaseGetter != nil { - caps[reflect.TypeFor[api.PhaseGetter]()] = &decorateBenderCCPhaseGetterImpl{phaseGetter: phaseGetter} - } - - if len(caps) == 0 { - return base - } - - return &decorateBenderCCCapable{BenderCC: base, caps: caps} -} - -type decorateBenderCCCapable struct { - *BenderCC - caps map[reflect.Type]any -} - -func (d *decorateBenderCCCapable) Capability(typ reflect.Type) (any, bool) { - c, ok := d.caps[typ] - if !ok && reflect.TypeOf(d).Implements(typ) { - return d, true - } - return c, ok -} - -type decorateBenderCCBatteryImpl struct { - battery func() (float64, error) -} - -func (impl *decorateBenderCCBatteryImpl) Soc() (float64, error) { - return impl.battery() -} - -type decorateBenderCCChargerExImpl struct { - chargerEx func(float64) error -} - -func (impl *decorateBenderCCChargerExImpl) MaxCurrentMillis(p0 float64) error { - return impl.chargerEx(p0) -} - -type decorateBenderCCIdentifierImpl struct { - identifier func() (string, error) -} - -func (impl *decorateBenderCCIdentifierImpl) Identify() (string, error) { - return impl.identifier() -} - -type decorateBenderCCMeterImpl struct { - meter func() (float64, error) -} - -func (impl *decorateBenderCCMeterImpl) CurrentPower() (float64, error) { - return impl.meter() -} - -type decorateBenderCCMeterEnergyImpl struct { - meterEnergy func() (float64, error) -} - -func (impl *decorateBenderCCMeterEnergyImpl) TotalEnergy() (float64, error) { - return impl.meterEnergy() -} - -type decorateBenderCCPhaseCurrentsImpl struct { - phaseCurrents func() (float64, float64, float64, error) -} - -func (impl *decorateBenderCCPhaseCurrentsImpl) Currents() (float64, float64, float64, error) { - return impl.phaseCurrents() -} - -type decorateBenderCCPhaseGetterImpl struct { - phaseGetter func() (int, error) -} - -func (impl *decorateBenderCCPhaseGetterImpl) GetPhases() (int, error) { - return impl.phaseGetter() -} - -type decorateBenderCCPhaseSwitcherImpl struct { - phaseSwitcher func(int) error -} - -func (impl *decorateBenderCCPhaseSwitcherImpl) Phases1p3p(p0 int) error { - return impl.phaseSwitcher(p0) -} - -type decorateBenderCCPhaseVoltagesImpl struct { - phaseVoltages func() (float64, float64, float64, error) -} - -func (impl *decorateBenderCCPhaseVoltagesImpl) Voltages() (float64, float64, float64, error) { - return impl.phaseVoltages() -} diff --git a/charger/cfos.go b/charger/cfos.go index 69bda65ca..370ad3ce8 100644 --- a/charger/cfos.go +++ b/charger/cfos.go @@ -6,6 +6,7 @@ import ( "fmt" "github.com/evcc-io/evcc/api" + "github.com/evcc-io/evcc/api/implement" "github.com/evcc-io/evcc/util" "github.com/evcc-io/evcc/util/modbus" "github.com/evcc-io/evcc/util/sponsor" @@ -31,6 +32,7 @@ const ( // It uses Modbus TCP to communicate at modbus client id 1 and power meters at id 2 and 3. // https://www.cfos-emobility.de/en-gb/cfos-power-brain/modbus-registers.htm type CfosPowerBrain struct { + implement.Caps conn *modbus.Connection } @@ -38,8 +40,6 @@ func init() { registry.AddCtx("cfos", NewCfosPowerBrainFromConfig) } -//go:generate go tool decorate -f decorateCfos -b *CfosPowerBrain -r api.Charger -t api.Meter,api.MeterEnergy,api.PhaseCurrents,api.PhaseSwitcher - // NewCfosPowerBrainFromConfig creates a cFos charger from generic config func NewCfosPowerBrainFromConfig(ctx context.Context, other map[string]any) (api.Charger, error) { cc := modbus.TcpSettings{ @@ -70,30 +70,26 @@ func NewCfosPowerBrain(ctx context.Context, uri string, id uint8) (api.Charger, conn.Logger(log.TRACE) wb := &CfosPowerBrain{ + Caps: implement.New(), conn: conn, } // decorate meter - var ( - power, energy func() (float64, error) - currents func() (float64, float64, float64, error) - ) if b, err := wb.conn.ReadHoldingRegisters(cfosRegMeter, 1); err == nil && binary.BigEndian.Uint16(b) != 0 { - power = wb.currentPower - energy = wb.totalEnergy + implement.Has(wb, implement.Meter(wb.currentPower)) + implement.Has(wb, implement.MeterEnergy(wb.totalEnergy)) if b, err := wb.conn.ReadHoldingRegisters(cfosRegMeterFlags, 1); err == nil && binary.BigEndian.Uint16(b) != 0 { - currents = wb.currents + implement.Has(wb, implement.PhaseCurrents(wb.currents)) } } // decorate phases - var phases1p3p func(int) error if b, err := wb.conn.ReadHoldingRegisters(cfosRegSolarEnabled, 1); err == nil && binary.BigEndian.Uint16(b)&(1<<8) != 0 { - phases1p3p = wb.phases1p3p + implement.Has(wb, implement.PhaseSwitcher(wb.phases1p3p)) } - return decorateCfos(wb, power, energy, currents, phases1p3p), nil + return wb, nil } // Status implements the api.Charger interface diff --git a/charger/cfos_decorators.go b/charger/cfos_decorators.go deleted file mode 100644 index f58b588fe..000000000 --- a/charger/cfos_decorators.go +++ /dev/null @@ -1,80 +0,0 @@ -package charger - -// Code generated by github.com/evcc-io/evcc/cmd/tools/decorate.go. DO NOT EDIT. - -import ( - "reflect" - - "github.com/evcc-io/evcc/api" -) - -func decorateCfos(base *CfosPowerBrain, meter func() (float64, error), meterEnergy func() (float64, error), phaseCurrents func() (float64, float64, float64, error), phaseSwitcher func(int) error) api.Charger { - caps := make(map[reflect.Type]any) - - if meter != nil { - caps[reflect.TypeFor[api.Meter]()] = &decorateCfosMeterImpl{meter: meter} - } - - if meterEnergy != nil { - caps[reflect.TypeFor[api.MeterEnergy]()] = &decorateCfosMeterEnergyImpl{meterEnergy: meterEnergy} - } - - if phaseCurrents != nil { - caps[reflect.TypeFor[api.PhaseCurrents]()] = &decorateCfosPhaseCurrentsImpl{phaseCurrents: phaseCurrents} - } - - if phaseSwitcher != nil { - caps[reflect.TypeFor[api.PhaseSwitcher]()] = &decorateCfosPhaseSwitcherImpl{phaseSwitcher: phaseSwitcher} - } - - if len(caps) == 0 { - return base - } - - return &decorateCfosCapable{CfosPowerBrain: base, caps: caps} -} - -type decorateCfosCapable struct { - *CfosPowerBrain - caps map[reflect.Type]any -} - -func (d *decorateCfosCapable) Capability(typ reflect.Type) (any, bool) { - c, ok := d.caps[typ] - if !ok && reflect.TypeOf(d).Implements(typ) { - return d, true - } - return c, ok -} - -type decorateCfosMeterImpl struct { - meter func() (float64, error) -} - -func (impl *decorateCfosMeterImpl) CurrentPower() (float64, error) { - return impl.meter() -} - -type decorateCfosMeterEnergyImpl struct { - meterEnergy func() (float64, error) -} - -func (impl *decorateCfosMeterEnergyImpl) TotalEnergy() (float64, error) { - return impl.meterEnergy() -} - -type decorateCfosPhaseCurrentsImpl struct { - phaseCurrents func() (float64, float64, float64, error) -} - -func (impl *decorateCfosPhaseCurrentsImpl) Currents() (float64, float64, float64, error) { - return impl.phaseCurrents() -} - -type decorateCfosPhaseSwitcherImpl struct { - phaseSwitcher func(int) error -} - -func (impl *decorateCfosPhaseSwitcherImpl) Phases1p3p(p0 int) error { - return impl.phaseSwitcher(p0) -} diff --git a/charger/charger.go b/charger/charger.go index 6dd658286..bf7a6f1cb 100644 --- a/charger/charger.go +++ b/charger/charger.go @@ -4,9 +4,9 @@ import ( "context" "errors" "fmt" - "time" "github.com/evcc-io/evcc/api" + "github.com/evcc-io/evcc/api/implement" "github.com/evcc-io/evcc/charger/measurement" meter "github.com/evcc-io/evcc/meter/measurement" "github.com/evcc-io/evcc/plugin" @@ -16,6 +16,7 @@ import ( // Charger is an api.Charger implementation with configurable getters and setters. type Charger struct { *embed + implement.Caps statusG func() (string, error) enabledG func() (bool, error) enableS func(bool) error @@ -26,8 +27,6 @@ func init() { registry.AddCtx(api.Custom, NewConfigurableFromConfig) } -//go:generate go tool decorate -f decorateCustom -b *Charger -r api.Charger -t api.ChargerEx,api.Identifier,api.PhaseSwitcher,api.Resurrector,api.Battery,api.SocLimiter,api.Meter,api.MeterEnergy,api.PhaseCurrents,api.PhaseVoltages,api.VehicleFinishTimer - // NewConfigurableFromConfig creates a new charger from config func NewConfigurableFromConfig(ctx context.Context, other map[string]any) (api.Charger, error) { var cc struct { @@ -75,14 +74,15 @@ func NewConfigurableFromConfig(ctx context.Context, other map[string]any) (api.C } c.embed = &cc.embed + c.Caps = implement.New() maxcurrentmillis, err := cc.MaxCurrentMillis.FloatSetter(ctx, "maxcurrentmillis") if err != nil { return nil, fmt.Errorf("maxcurrentmillis: %w", err) } + implement.May(c, implement.ChargerEx(maxcurrentmillis)) // decorate phases - var phases1p3p func(int) error if cc.Phases1p3p != nil { if !cc.Tos { return nil, errors.New("1p3p does no longer handle disable/enable. Use tos: true to confirm you understand the consequences") @@ -93,9 +93,9 @@ func NewConfigurableFromConfig(ctx context.Context, other map[string]any) (api.C return nil, fmt.Errorf("phases: %w", err) } - phases1p3p = func(phases int) error { + implement.Has(c, implement.PhaseSwitcher(func(phases int) error { return phases1p3pS(int64(phases)) - } + })) } // decorate identifier @@ -103,18 +103,18 @@ func NewConfigurableFromConfig(ctx context.Context, other map[string]any) (api.C if err != nil { return nil, fmt.Errorf("identify: %w", err) } + implement.May(c, implement.Identifier(identify)) // decorate wakeup - var wakeup func() error if cc.Wakeup != nil { - set, err := cc.Wakeup.BoolSetter(ctx, "wakeup") + wakeup, err := cc.Wakeup.BoolSetter(ctx, "wakeup") if err != nil { return nil, fmt.Errorf("wakeup: %w", err) } - wakeup = func() error { - return set(true) - } + implement.Has(c, implement.Resurrector(func() error { + return wakeup(true) + })) } // decorate soc; for heating devices (api.Heating feature), the soc slot holds @@ -141,28 +141,32 @@ func NewConfigurableFromConfig(ctx context.Context, other map[string]any) (api.C if limitsoc == nil && limitTemp != nil { limitsoc = limitTemp } + implement.May(c, implement.Battery(soc)) + implement.May(c, implement.SocLimiter(limitsoc)) // decorate measurements powerG, energyG, err := cc.Energy.Configure(ctx) if err != nil { return nil, err } + implement.May(c, implement.Meter(powerG)) + implement.May(c, implement.MeterEnergy(energyG)) currentsG, voltagesG, _, err := cc.Phases.Configure(ctx) if err != nil { return nil, err } + implement.May(c, implement.PhaseCurrents(currentsG)) + implement.May(c, implement.PhaseVoltages(voltagesG)) // decorate finishtime - var finishTime func() (time.Time, error) - if cc.FinishTime != nil { - finishTime, err = cc.FinishTime.TimeGetter(ctx) - if err != nil { - return nil, fmt.Errorf("finishTime: %w", err) - } + finishTime, err := cc.FinishTime.TimeGetter(ctx) + if err != nil { + return nil, fmt.Errorf("finishTime: %w", err) } + implement.May(c, implement.VehicleFinishTimer(finishTime)) - return decorateCustom(c, maxcurrentmillis, identify, phases1p3p, wakeup, soc, limitsoc, powerG, energyG, currentsG, voltagesG, finishTime), nil + return c, nil } // NewConfigurable creates a new charger diff --git a/charger/charger_decorators.go b/charger/charger_decorators.go deleted file mode 100644 index a5387ed7a..000000000 --- a/charger/charger_decorators.go +++ /dev/null @@ -1,165 +0,0 @@ -package charger - -// Code generated by github.com/evcc-io/evcc/cmd/tools/decorate.go. DO NOT EDIT. - -import ( - "reflect" - "time" - - "github.com/evcc-io/evcc/api" -) - -func decorateCustom(base *Charger, chargerEx func(float64) error, identifier func() (string, error), phaseSwitcher func(int) error, resurrector func() error, battery func() (float64, error), socLimiter func() (int64, error), meter func() (float64, error), meterEnergy func() (float64, error), phaseCurrents func() (float64, float64, float64, error), phaseVoltages func() (float64, float64, float64, error), vehicleFinishTimer func() (time.Time, error)) api.Charger { - caps := make(map[reflect.Type]any) - - if chargerEx != nil { - caps[reflect.TypeFor[api.ChargerEx]()] = &decorateCustomChargerExImpl{chargerEx: chargerEx} - } - - if identifier != nil { - caps[reflect.TypeFor[api.Identifier]()] = &decorateCustomIdentifierImpl{identifier: identifier} - } - - if phaseSwitcher != nil { - caps[reflect.TypeFor[api.PhaseSwitcher]()] = &decorateCustomPhaseSwitcherImpl{phaseSwitcher: phaseSwitcher} - } - - if resurrector != nil { - caps[reflect.TypeFor[api.Resurrector]()] = &decorateCustomResurrectorImpl{resurrector: resurrector} - } - - if battery != nil { - caps[reflect.TypeFor[api.Battery]()] = &decorateCustomBatteryImpl{battery: battery} - } - - if socLimiter != nil { - caps[reflect.TypeFor[api.SocLimiter]()] = &decorateCustomSocLimiterImpl{socLimiter: socLimiter} - } - - if meter != nil { - caps[reflect.TypeFor[api.Meter]()] = &decorateCustomMeterImpl{meter: meter} - } - - if meterEnergy != nil { - caps[reflect.TypeFor[api.MeterEnergy]()] = &decorateCustomMeterEnergyImpl{meterEnergy: meterEnergy} - } - - if phaseCurrents != nil { - caps[reflect.TypeFor[api.PhaseCurrents]()] = &decorateCustomPhaseCurrentsImpl{phaseCurrents: phaseCurrents} - } - - if phaseVoltages != nil { - caps[reflect.TypeFor[api.PhaseVoltages]()] = &decorateCustomPhaseVoltagesImpl{phaseVoltages: phaseVoltages} - } - - if vehicleFinishTimer != nil { - caps[reflect.TypeFor[api.VehicleFinishTimer]()] = &decorateCustomVehicleFinishTimerImpl{vehicleFinishTimer: vehicleFinishTimer} - } - - if len(caps) == 0 { - return base - } - - return &decorateCustomCapable{Charger: base, caps: caps} -} - -type decorateCustomCapable struct { - *Charger - caps map[reflect.Type]any -} - -func (d *decorateCustomCapable) Capability(typ reflect.Type) (any, bool) { - c, ok := d.caps[typ] - if !ok && reflect.TypeOf(d).Implements(typ) { - return d, true - } - return c, ok -} - -type decorateCustomBatteryImpl struct { - battery func() (float64, error) -} - -func (impl *decorateCustomBatteryImpl) Soc() (float64, error) { - return impl.battery() -} - -type decorateCustomChargerExImpl struct { - chargerEx func(float64) error -} - -func (impl *decorateCustomChargerExImpl) MaxCurrentMillis(p0 float64) error { - return impl.chargerEx(p0) -} - -type decorateCustomIdentifierImpl struct { - identifier func() (string, error) -} - -func (impl *decorateCustomIdentifierImpl) Identify() (string, error) { - return impl.identifier() -} - -type decorateCustomMeterImpl struct { - meter func() (float64, error) -} - -func (impl *decorateCustomMeterImpl) CurrentPower() (float64, error) { - return impl.meter() -} - -type decorateCustomMeterEnergyImpl struct { - meterEnergy func() (float64, error) -} - -func (impl *decorateCustomMeterEnergyImpl) TotalEnergy() (float64, error) { - return impl.meterEnergy() -} - -type decorateCustomPhaseCurrentsImpl struct { - phaseCurrents func() (float64, float64, float64, error) -} - -func (impl *decorateCustomPhaseCurrentsImpl) Currents() (float64, float64, float64, error) { - return impl.phaseCurrents() -} - -type decorateCustomPhaseSwitcherImpl struct { - phaseSwitcher func(int) error -} - -func (impl *decorateCustomPhaseSwitcherImpl) Phases1p3p(p0 int) error { - return impl.phaseSwitcher(p0) -} - -type decorateCustomPhaseVoltagesImpl struct { - phaseVoltages func() (float64, float64, float64, error) -} - -func (impl *decorateCustomPhaseVoltagesImpl) Voltages() (float64, float64, float64, error) { - return impl.phaseVoltages() -} - -type decorateCustomResurrectorImpl struct { - resurrector func() error -} - -func (impl *decorateCustomResurrectorImpl) WakeUp() error { - return impl.resurrector() -} - -type decorateCustomSocLimiterImpl struct { - socLimiter func() (int64, error) -} - -func (impl *decorateCustomSocLimiterImpl) GetLimitSoc() (int64, error) { - return impl.socLimiter() -} - -type decorateCustomVehicleFinishTimerImpl struct { - vehicleFinishTimer func() (time.Time, error) -} - -func (impl *decorateCustomVehicleFinishTimerImpl) FinishTime() (time.Time, error) { - return impl.vehicleFinishTimer() -} diff --git a/charger/daheimladen.go b/charger/daheimladen.go index d30baeb96..ad5f469e9 100644 --- a/charger/daheimladen.go +++ b/charger/daheimladen.go @@ -24,12 +24,14 @@ import ( "time" "github.com/evcc-io/evcc/api" + "github.com/evcc-io/evcc/api/implement" "github.com/evcc-io/evcc/util" "github.com/evcc-io/evcc/util/modbus" ) // DaheimLaden charger implementation type DaheimLaden struct { + implement.Caps log *util.Logger conn *modbus.Connection curr uint16 @@ -65,8 +67,6 @@ func init() { registry.AddCtx("daheimladen", NewDaheimLadenFromConfig) } -//go:generate go tool decorate -f decorateDaheimLaden -b *DaheimLaden -r api.Charger -t api.PhaseSwitcher,api.PhaseGetter - // NewDaheimLadenFromConfig creates a DaheimLaden charger from generic config func NewDaheimLadenFromConfig(ctx context.Context, other map[string]any) (api.Charger, error) { cc := struct { @@ -96,6 +96,7 @@ func NewDaheimLaden(ctx context.Context, uri string, id uint8, phases bool) (api conn.Logger(log.TRACE) wb := &DaheimLaden{ + Caps: implement.New(), log: log, conn: conn, curr: 60, // assume min current @@ -120,14 +121,12 @@ func NewDaheimLaden(ctx context.Context, uri string, id uint8, phases bool) (api go wb.heartbeat(ctx, time.Duration(u)*time.Second/2) } - var phases1p3p func(int) error - var phasesG func() (int, error) if phases { - phases1p3p = wb.phases1p3p - phasesG = wb.getPhases + implement.Has(wb, implement.PhaseSwitcher(wb.phases1p3p)) + implement.Has(wb, implement.PhaseGetter(wb.getPhases)) } - return decorateDaheimLaden(wb, phases1p3p, phasesG), nil + return wb, nil } func (wb *DaheimLaden) heartbeat(ctx context.Context, timeout time.Duration) { diff --git a/charger/daheimladen_decorators.go b/charger/daheimladen_decorators.go deleted file mode 100644 index 4ab19bab3..000000000 --- a/charger/daheimladen_decorators.go +++ /dev/null @@ -1,56 +0,0 @@ -package charger - -// Code generated by github.com/evcc-io/evcc/cmd/tools/decorate.go. DO NOT EDIT. - -import ( - "reflect" - - "github.com/evcc-io/evcc/api" -) - -func decorateDaheimLaden(base *DaheimLaden, phaseSwitcher func(int) error, phaseGetter func() (int, error)) api.Charger { - caps := make(map[reflect.Type]any) - - if phaseSwitcher != nil { - caps[reflect.TypeFor[api.PhaseSwitcher]()] = &decorateDaheimLadenPhaseSwitcherImpl{phaseSwitcher: phaseSwitcher} - } - - if phaseGetter != nil { - caps[reflect.TypeFor[api.PhaseGetter]()] = &decorateDaheimLadenPhaseGetterImpl{phaseGetter: phaseGetter} - } - - if len(caps) == 0 { - return base - } - - return &decorateDaheimLadenCapable{DaheimLaden: base, caps: caps} -} - -type decorateDaheimLadenCapable struct { - *DaheimLaden - caps map[reflect.Type]any -} - -func (d *decorateDaheimLadenCapable) Capability(typ reflect.Type) (any, bool) { - c, ok := d.caps[typ] - if !ok && reflect.TypeOf(d).Implements(typ) { - return d, true - } - return c, ok -} - -type decorateDaheimLadenPhaseGetterImpl struct { - phaseGetter func() (int, error) -} - -func (impl *decorateDaheimLadenPhaseGetterImpl) GetPhases() (int, error) { - return impl.phaseGetter() -} - -type decorateDaheimLadenPhaseSwitcherImpl struct { - phaseSwitcher func(int) error -} - -func (impl *decorateDaheimLadenPhaseSwitcherImpl) Phases1p3p(p0 int) error { - return impl.phaseSwitcher(p0) -} diff --git a/charger/eebus.go b/charger/eebus.go index 7b66cb8ee..711ac9cae 100644 --- a/charger/eebus.go +++ b/charger/eebus.go @@ -13,6 +13,7 @@ import ( "github.com/enbility/eebus-go/usecases/cem/evcem" spineapi "github.com/enbility/spine-go/api" "github.com/evcc-io/evcc/api" + "github.com/evcc-io/evcc/api/implement" "github.com/evcc-io/evcc/core/loadpoint" "github.com/evcc-io/evcc/server/eebus" "github.com/evcc-io/evcc/util" @@ -29,6 +30,7 @@ type minMax struct { } type EEBus struct { + implement.Caps cem *eebus.CustomerEnergyManagement ev spineapi.EntityRemoteInterface @@ -69,8 +71,6 @@ func NewEEBusFromConfig(ctx context.Context, other map[string]any) (api.Charger, return NewEEBus(ctx, cc.Ski, cc.Ip, cc.Meter, hasChargedEnergy) } -//go:generate go tool decorate -f decorateEEBus -b *EEBus -r api.Charger -t api.Meter,api.PhaseCurrents,api.ChargeRater - // newEEBus creates and initializes a raw *EEBus charger. // It registers the device with the EEBus instance and waits for the connection. func newEEBus(ctx context.Context, ski, ip string) (*EEBus, error) { @@ -79,6 +79,7 @@ func newEEBus(ctx context.Context, ski, ip string) (*EEBus, error) { } c := &EEBus{ + Caps: implement.New(), log: util.NewLogger("eebus"), current: 6, cem: eebus.Instance.CustomerEnergyManagement(), @@ -113,11 +114,11 @@ func NewEEBus(ctx context.Context, ski, ip string, hasMeter, hasChargedEnergy bo } if hasMeter { - var energyG func() (float64, error) + implement.Has(c, implement.Meter(c.currentPower)) + implement.Has(c, implement.PhaseCurrents(c.currents)) if hasChargedEnergy { - energyG = c.chargedEnergy + implement.Has(c, implement.ChargeRater(c.chargedEnergy)) } - return decorateEEBus(c, c.currentPower, c.currents, energyG), nil } return c, nil diff --git a/charger/eebus_decorators.go b/charger/eebus_decorators.go deleted file mode 100644 index 207f27d17..000000000 --- a/charger/eebus_decorators.go +++ /dev/null @@ -1,68 +0,0 @@ -package charger - -// Code generated by github.com/evcc-io/evcc/cmd/tools/decorate.go. DO NOT EDIT. - -import ( - "reflect" - - "github.com/evcc-io/evcc/api" -) - -func decorateEEBus(base *EEBus, meter func() (float64, error), phaseCurrents func() (float64, float64, float64, error), chargeRater func() (float64, error)) api.Charger { - caps := make(map[reflect.Type]any) - - if meter != nil { - caps[reflect.TypeFor[api.Meter]()] = &decorateEEBusMeterImpl{meter: meter} - } - - if phaseCurrents != nil { - caps[reflect.TypeFor[api.PhaseCurrents]()] = &decorateEEBusPhaseCurrentsImpl{phaseCurrents: phaseCurrents} - } - - if chargeRater != nil { - caps[reflect.TypeFor[api.ChargeRater]()] = &decorateEEBusChargeRaterImpl{chargeRater: chargeRater} - } - - if len(caps) == 0 { - return base - } - - return &decorateEEBusCapable{EEBus: base, caps: caps} -} - -type decorateEEBusCapable struct { - *EEBus - caps map[reflect.Type]any -} - -func (d *decorateEEBusCapable) Capability(typ reflect.Type) (any, bool) { - c, ok := d.caps[typ] - if !ok && reflect.TypeOf(d).Implements(typ) { - return d, true - } - return c, ok -} - -type decorateEEBusChargeRaterImpl struct { - chargeRater func() (float64, error) -} - -func (impl *decorateEEBusChargeRaterImpl) ChargedEnergy() (float64, error) { - return impl.chargeRater() -} - -type decorateEEBusMeterImpl struct { - meter func() (float64, error) -} - -func (impl *decorateEEBusMeterImpl) CurrentPower() (float64, error) { - return impl.meter() -} - -type decorateEEBusPhaseCurrentsImpl struct { - phaseCurrents func() (float64, float64, float64, error) -} - -func (impl *decorateEEBusPhaseCurrentsImpl) Currents() (float64, float64, float64, error) { - return impl.phaseCurrents() -} diff --git a/charger/em2go.go b/charger/em2go.go index b85f68e40..d4bb5635e 100644 --- a/charger/em2go.go +++ b/charger/em2go.go @@ -25,6 +25,7 @@ import ( "github.com/cenkalti/backoff/v4" "github.com/evcc-io/evcc/api" + "github.com/evcc-io/evcc/api/implement" "github.com/evcc-io/evcc/util" "github.com/evcc-io/evcc/util/modbus" "github.com/volkszaehler/mbmd/meters/rs485" @@ -34,6 +35,7 @@ import ( // Em2Go charger implementation type Em2Go struct { + implement.Caps log *util.Logger conn *modbus.Connection current uint16 @@ -70,8 +72,6 @@ func init() { registry.AddCtx("em2go-home", NewEm2GoFromConfig) } -//go:generate go tool decorate -f decorateEm2Go -b *Em2Go -r api.Charger -t api.ChargerEx,api.PhaseSwitcher,api.PhaseGetter - // NewEm2GoFromConfig creates a Em2Go charger from generic config func NewEm2GoFromConfig(ctx context.Context, other map[string]any) (api.Charger, error) { cc := struct { @@ -104,6 +104,7 @@ func NewEm2Go(ctx context.Context, uri string, slaveID uint8) (api.Charger, erro conn.Logger(log.TRACE) wb := &Em2Go{ + Caps: implement.New(), log: log, conn: conn, current: 60, @@ -149,12 +150,6 @@ func (wb *Em2Go) heartbeat(ctx context.Context, interval time.Duration) { // initialize performs common initialization for both Em2Go models func (wb *Em2Go) initialize() (api.Charger, error) { - var ( - maxCurrent func(float64) error - phases1p3p func(int) error - phasesG func() (int, error) - ) - // test if workaround is needed (Home fw <1.3) if err := wb.maxCurrentMillis(6.1); err != nil { return nil, err @@ -169,15 +164,15 @@ func (wb *Em2Go) initialize() (api.Charger, error) { if chargerCurrent == 6 { wb.workaround = true } else { - maxCurrent = wb.maxCurrentMillis + implement.Has(wb, implement.ChargerEx(wb.maxCurrentMillis)) } if _, err := wb.conn.ReadHoldingRegisters(em2GoRegPhases, 1); err == nil { - phases1p3p = wb.phases1p3p - phasesG = wb.getPhases + implement.Has(wb, implement.PhaseSwitcher(wb.phases1p3p)) + implement.Has(wb, implement.PhaseGetter(wb.getPhases)) } - return decorateEm2Go(wb, maxCurrent, phases1p3p, phasesG), nil + return wb, nil } // Status implements the api.Charger interface diff --git a/charger/em2go_decorators.go b/charger/em2go_decorators.go deleted file mode 100644 index 73e0c9eea..000000000 --- a/charger/em2go_decorators.go +++ /dev/null @@ -1,68 +0,0 @@ -package charger - -// Code generated by github.com/evcc-io/evcc/cmd/tools/decorate.go. DO NOT EDIT. - -import ( - "reflect" - - "github.com/evcc-io/evcc/api" -) - -func decorateEm2Go(base *Em2Go, chargerEx func(float64) error, phaseSwitcher func(int) error, phaseGetter func() (int, error)) api.Charger { - caps := make(map[reflect.Type]any) - - if chargerEx != nil { - caps[reflect.TypeFor[api.ChargerEx]()] = &decorateEm2GoChargerExImpl{chargerEx: chargerEx} - } - - if phaseSwitcher != nil { - caps[reflect.TypeFor[api.PhaseSwitcher]()] = &decorateEm2GoPhaseSwitcherImpl{phaseSwitcher: phaseSwitcher} - } - - if phaseGetter != nil { - caps[reflect.TypeFor[api.PhaseGetter]()] = &decorateEm2GoPhaseGetterImpl{phaseGetter: phaseGetter} - } - - if len(caps) == 0 { - return base - } - - return &decorateEm2GoCapable{Em2Go: base, caps: caps} -} - -type decorateEm2GoCapable struct { - *Em2Go - caps map[reflect.Type]any -} - -func (d *decorateEm2GoCapable) Capability(typ reflect.Type) (any, bool) { - c, ok := d.caps[typ] - if !ok && reflect.TypeOf(d).Implements(typ) { - return d, true - } - return c, ok -} - -type decorateEm2GoChargerExImpl struct { - chargerEx func(float64) error -} - -func (impl *decorateEm2GoChargerExImpl) MaxCurrentMillis(p0 float64) error { - return impl.chargerEx(p0) -} - -type decorateEm2GoPhaseGetterImpl struct { - phaseGetter func() (int, error) -} - -func (impl *decorateEm2GoPhaseGetterImpl) GetPhases() (int, error) { - return impl.phaseGetter() -} - -type decorateEm2GoPhaseSwitcherImpl struct { - phaseSwitcher func(int) error -} - -func (impl *decorateEm2GoPhaseSwitcherImpl) Phases1p3p(p0 int) error { - return impl.phaseSwitcher(p0) -} diff --git a/charger/etek.go b/charger/etek.go index 6a928c102..14e1b2385 100644 --- a/charger/etek.go +++ b/charger/etek.go @@ -23,6 +23,7 @@ import ( "fmt" "github.com/evcc-io/evcc/api" + "github.com/evcc-io/evcc/api/implement" "github.com/evcc-io/evcc/util" "github.com/evcc-io/evcc/util/modbus" ) @@ -32,6 +33,7 @@ import ( // ETEK EKEPC2 charger implementation type Etek struct { + implement.Caps log *util.Logger conn *modbus.Connection } @@ -65,8 +67,6 @@ func init() { registry.AddCtx("etek", NewEtekFromConfig) } -//go:generate go tool decorate -f decorateEtek -b *Etek -r api.Charger -t api.Meter,api.MeterEnergy,api.PhaseVoltages - // NewEtekFromConfig creates an ETEK EKEPC2 charger from generic config func NewEtekFromConfig(ctx context.Context, other map[string]any) (api.Charger, error) { cc := modbus.Settings{ @@ -84,23 +84,18 @@ func NewEtekFromConfig(ctx context.Context, other map[string]any) (api.Charger, // Check if external meter is configured // If register value is 65535 (0xffff), no external meter is configured - var ( - currentPower func() (float64, error) - totalEnergy func() (float64, error) - voltages func() (float64, float64, float64, error) - ) // Check power register (94) if b, err := wb.conn.ReadHoldingRegisters(etekRegMeterPowerAddr, 1); err == nil { if binary.BigEndian.Uint16(b) != etekRegInvalidMeterAddr { - currentPower = wb.currentPower + implement.Has(wb, implement.Meter(wb.currentPower)) } } // Check energy register (95) if b, err := wb.conn.ReadHoldingRegisters(etekRegMeterEnergyAddr, 1); err == nil { if binary.BigEndian.Uint16(b) != etekRegInvalidMeterAddr { - totalEnergy = wb.totalEnergy + implement.Has(wb, implement.MeterEnergy(wb.totalEnergy)) } } @@ -110,11 +105,11 @@ func NewEtekFromConfig(ctx context.Context, other map[string]any) (api.Charger, l2 := binary.BigEndian.Uint16(b[2:4]) l3 := binary.BigEndian.Uint16(b[4:6]) if l1 != etekRegInvalidMeterAddr && l2 != etekRegInvalidMeterAddr && l3 != etekRegInvalidMeterAddr { - voltages = wb.voltages + implement.Has(wb, implement.PhaseVoltages(wb.voltages)) } } - return decorateEtek(wb, currentPower, totalEnergy, voltages), nil + return wb, nil } // NewEtek creates an ETEK EKEPC2 charger @@ -128,6 +123,7 @@ func NewEtek(ctx context.Context, uri, device, comset string, baudrate int, prot conn.Logger(log.TRACE) wb := &Etek{ + Caps: implement.New(), log: log, conn: conn, } diff --git a/charger/etek_decorators.go b/charger/etek_decorators.go deleted file mode 100644 index dfd008e7f..000000000 --- a/charger/etek_decorators.go +++ /dev/null @@ -1,68 +0,0 @@ -package charger - -// Code generated by github.com/evcc-io/evcc/cmd/tools/decorate.go. DO NOT EDIT. - -import ( - "reflect" - - "github.com/evcc-io/evcc/api" -) - -func decorateEtek(base *Etek, meter func() (float64, error), meterEnergy func() (float64, error), phaseVoltages func() (float64, float64, float64, error)) api.Charger { - caps := make(map[reflect.Type]any) - - if meter != nil { - caps[reflect.TypeFor[api.Meter]()] = &decorateEtekMeterImpl{meter: meter} - } - - if meterEnergy != nil { - caps[reflect.TypeFor[api.MeterEnergy]()] = &decorateEtekMeterEnergyImpl{meterEnergy: meterEnergy} - } - - if phaseVoltages != nil { - caps[reflect.TypeFor[api.PhaseVoltages]()] = &decorateEtekPhaseVoltagesImpl{phaseVoltages: phaseVoltages} - } - - if len(caps) == 0 { - return base - } - - return &decorateEtekCapable{Etek: base, caps: caps} -} - -type decorateEtekCapable struct { - *Etek - caps map[reflect.Type]any -} - -func (d *decorateEtekCapable) Capability(typ reflect.Type) (any, bool) { - c, ok := d.caps[typ] - if !ok && reflect.TypeOf(d).Implements(typ) { - return d, true - } - return c, ok -} - -type decorateEtekMeterImpl struct { - meter func() (float64, error) -} - -func (impl *decorateEtekMeterImpl) CurrentPower() (float64, error) { - return impl.meter() -} - -type decorateEtekMeterEnergyImpl struct { - meterEnergy func() (float64, error) -} - -func (impl *decorateEtekMeterEnergyImpl) TotalEnergy() (float64, error) { - return impl.meterEnergy() -} - -type decorateEtekPhaseVoltagesImpl struct { - phaseVoltages func() (float64, float64, float64, error) -} - -func (impl *decorateEtekPhaseVoltagesImpl) Voltages() (float64, float64, float64, error) { - return impl.phaseVoltages() -} diff --git a/charger/evecube.go b/charger/evecube.go index d99246e9f..06582c1f9 100644 --- a/charger/evecube.go +++ b/charger/evecube.go @@ -25,6 +25,7 @@ import ( "time" "github.com/evcc-io/evcc/api" + "github.com/evcc-io/evcc/api/implement" "github.com/evcc-io/evcc/util" "github.com/evcc-io/evcc/util/request" "github.com/evcc-io/evcc/util/sponsor" @@ -33,6 +34,7 @@ import ( // EVECUBE charger implementation type EVECUBE struct { + implement.Caps *request.Helper uri string connector int @@ -46,8 +48,6 @@ func init() { registry.Add("evecube", NewEVECUBEFromConfig) } -//go:generate go tool decorate -f decorateEVECUBE -b *EVECUBE -r api.Charger -t api.PhaseSwitcher,api.Identifier - // EVECUBEUnitConfig is the /api/admin/unitconfig response type EVECUBEUnitConfig struct { ForcePhaseCharging int `json:"ForcePhaseCharging"` @@ -135,15 +135,12 @@ func NewEVECUBEFromConfig(other map[string]any) (api.Charger, error) { // Phases1p3p and Identify APIs affect the entire charger, not individual connectors // Only enable these APIs if the charger has a single connector - var phases1p3p func(int) error - var identify func() (string, error) - if config.NumberOfConnectors == 1 { - phases1p3p = wb.phases1p3p - identify = wb.identify + implement.Has(wb, implement.PhaseSwitcher(wb.phases1p3p)) + implement.Has(wb, implement.Identifier(wb.identify)) } - return decorateEVECUBE(wb, phases1p3p, identify), nil + return wb, nil } // NewEVECUBE creates EVECUBE charger @@ -151,6 +148,7 @@ func NewEVECUBE(uri, user, password string, connector int, cache time.Duration) log := util.NewLogger("evecube") wb := &EVECUBE{ + Caps: implement.New(), Helper: request.NewHelper(log), uri: strings.TrimRight(uri, "/"), connector: connector, diff --git a/charger/evecube_decorators.go b/charger/evecube_decorators.go deleted file mode 100644 index a80f9a02a..000000000 --- a/charger/evecube_decorators.go +++ /dev/null @@ -1,56 +0,0 @@ -package charger - -// Code generated by github.com/evcc-io/evcc/cmd/tools/decorate.go. DO NOT EDIT. - -import ( - "reflect" - - "github.com/evcc-io/evcc/api" -) - -func decorateEVECUBE(base *EVECUBE, phaseSwitcher func(int) error, identifier func() (string, error)) api.Charger { - caps := make(map[reflect.Type]any) - - if phaseSwitcher != nil { - caps[reflect.TypeFor[api.PhaseSwitcher]()] = &decorateEVECUBEPhaseSwitcherImpl{phaseSwitcher: phaseSwitcher} - } - - if identifier != nil { - caps[reflect.TypeFor[api.Identifier]()] = &decorateEVECUBEIdentifierImpl{identifier: identifier} - } - - if len(caps) == 0 { - return base - } - - return &decorateEVECUBECapable{EVECUBE: base, caps: caps} -} - -type decorateEVECUBECapable struct { - *EVECUBE - caps map[reflect.Type]any -} - -func (d *decorateEVECUBECapable) Capability(typ reflect.Type) (any, bool) { - c, ok := d.caps[typ] - if !ok && reflect.TypeOf(d).Implements(typ) { - return d, true - } - return c, ok -} - -type decorateEVECUBEIdentifierImpl struct { - identifier func() (string, error) -} - -func (impl *decorateEVECUBEIdentifierImpl) Identify() (string, error) { - return impl.identifier() -} - -type decorateEVECUBEPhaseSwitcherImpl struct { - phaseSwitcher func(int) error -} - -func (impl *decorateEVECUBEPhaseSwitcherImpl) Phases1p3p(p0 int) error { - return impl.phaseSwitcher(p0) -} diff --git a/charger/evsedin.go b/charger/evsedin.go index ff75860c3..0d361f330 100644 --- a/charger/evsedin.go +++ b/charger/evsedin.go @@ -7,6 +7,7 @@ import ( "time" "github.com/evcc-io/evcc/api" + "github.com/evcc-io/evcc/api/implement" "github.com/evcc-io/evcc/util" "github.com/evcc-io/evcc/util/modbus" "github.com/volkszaehler/mbmd/encoding" @@ -14,6 +15,7 @@ import ( // EvseDIN charger implementation type EvseDIN struct { + implement.Caps conn *modbus.Connection current uint16 } @@ -46,8 +48,6 @@ func NewEvseDINFromConfig(ctx context.Context, other map[string]any) (api.Charge return NewEvseDIN(ctx, cc.URI, cc.Device, cc.Comset, cc.Baudrate, cc.Protocol(), cc.ID) } -//go:generate go tool decorate -f decorateEvseDIN -b *EvseDIN -r api.Charger -t api.ChargerEx - // NewEvseDIN creates EVSE DIN charger func NewEvseDIN(ctx context.Context, uri, device, comset string, baudrate int, proto modbus.Protocol, slaveID uint8) (api.Charger, error) { log := util.NewLogger("evse") @@ -61,12 +61,11 @@ func NewEvseDIN(ctx context.Context, uri, device, comset string, baudrate int, p conn.Delay(200 * time.Millisecond) evse := &EvseDIN{ + Caps: implement.New(), conn: conn, current: 6, // assume min current } - var maxCurrentMillis func(float64) error - // check firmware bFirmware, err := evse.conn.ReadHoldingRegisters(evseRegFirmware, 1) if err != nil { @@ -94,7 +93,7 @@ func NewEvseDIN(ctx context.Context, uri, device, comset string, baudrate int, p } if config&0x0080 != 0 { - maxCurrentMillis = evse.maxCurrentMillis + implement.Has(evse, implement.ChargerEx(evse.maxCurrentMillis)) evse.current = 600 // assume min current } } @@ -107,7 +106,7 @@ func NewEvseDIN(ctx context.Context, uri, device, comset string, baudrate int, p evse.current = current } - return decorateEvseDIN(evse, maxCurrentMillis), nil + return evse, nil } // Status implements the api.Charger interface diff --git a/charger/evsedin_decorators.go b/charger/evsedin_decorators.go deleted file mode 100644 index 8eacdecb2..000000000 --- a/charger/evsedin_decorators.go +++ /dev/null @@ -1,44 +0,0 @@ -package charger - -// Code generated by github.com/evcc-io/evcc/cmd/tools/decorate.go. DO NOT EDIT. - -import ( - "reflect" - - "github.com/evcc-io/evcc/api" -) - -func decorateEvseDIN(base *EvseDIN, chargerEx func(float64) error) api.Charger { - caps := make(map[reflect.Type]any) - - if chargerEx != nil { - caps[reflect.TypeFor[api.ChargerEx]()] = &decorateEvseDINChargerExImpl{chargerEx: chargerEx} - } - - if len(caps) == 0 { - return base - } - - return &decorateEvseDINCapable{EvseDIN: base, caps: caps} -} - -type decorateEvseDINCapable struct { - *EvseDIN - caps map[reflect.Type]any -} - -func (d *decorateEvseDINCapable) Capability(typ reflect.Type) (any, bool) { - c, ok := d.caps[typ] - if !ok && reflect.TypeOf(d).Implements(typ) { - return d, true - } - return c, ok -} - -type decorateEvseDINChargerExImpl struct { - chargerEx func(float64) error -} - -func (impl *decorateEvseDINChargerExImpl) MaxCurrentMillis(p0 float64) error { - return impl.chargerEx(p0) -} diff --git a/charger/evsewifi.go b/charger/evsewifi.go index 7838e1140..498385146 100644 --- a/charger/evsewifi.go +++ b/charger/evsewifi.go @@ -7,6 +7,7 @@ import ( "time" "github.com/evcc-io/evcc/api" + "github.com/evcc-io/evcc/api/implement" "github.com/evcc-io/evcc/charger/evse" "github.com/evcc-io/evcc/util" "github.com/evcc-io/evcc/util/request" @@ -15,6 +16,7 @@ import ( // EVSEWifi charger implementation type EVSEWifi struct { *request.Helper + implement.Caps uri string alwaysActive bool current int64 // current will always be the physical value sent to the API @@ -27,8 +29,6 @@ func init() { registry.Add("evsewifi", NewEVSEWifiFromConfig) } -//go:generate go tool decorate -f decorateEVSE -b *EVSEWifi -r api.Charger -t api.Meter,api.MeterEnergy,api.PhaseCurrents,api.PhaseVoltages,api.ChargerEx,api.Identifier - // NewEVSEWifiFromConfig creates a EVSEWifi charger from generic config func NewEVSEWifiFromConfig(other map[string]any) (api.Charger, error) { cc := struct { @@ -71,42 +71,32 @@ func NewEVSEWifiFromConfig(other map[string]any) (api.Charger, error) { wb.hires = true } - // decorate meter - var ( - power, energy func() (float64, error) - currents, voltages func() (float64, float64, float64, error) - ) - if cc.Meter.Power { - power = wb.currentPower + implement.Has(wb, implement.Meter(wb.currentPower)) } if cc.Meter.Energy { - energy = wb.totalEnergy + implement.Has(wb, implement.MeterEnergy(wb.totalEnergy)) } if cc.Meter.Currents { - currents = wb.currents + implement.Has(wb, implement.PhaseCurrents(wb.currents)) } if cc.Meter.Voltages { - voltages = wb.voltages + implement.Has(wb, implement.PhaseVoltages(wb.voltages)) } - // decorate Charger with MaxCurrentMillis - var maxCurrentMillis func(float64) error if wb.hires { - maxCurrentMillis = wb.maxCurrentMillis + implement.Has(wb, implement.ChargerEx(wb.maxCurrentMillis)) wb.current = 100 * wb.current } - // decorate Charger with Identifier - var identify func() (string, error) if params.RFIDUID != nil { - identify = wb.identify + implement.Has(wb, implement.Identifier(wb.identify)) } - return decorateEVSE(wb, power, energy, currents, voltages, maxCurrentMillis, identify), nil + return wb, nil } // NewEVSEWifi creates EVSEWifi charger @@ -115,6 +105,7 @@ func NewEVSEWifi(uri string, cache time.Duration) (*EVSEWifi, error) { wb := &EVSEWifi{ Helper: request.NewHelper(log), + Caps: implement.New(), uri: strings.TrimRight(uri, "/"), current: 6, // 6A defined value } diff --git a/charger/evsewifi_decorators.go b/charger/evsewifi_decorators.go deleted file mode 100644 index 0409faae6..000000000 --- a/charger/evsewifi_decorators.go +++ /dev/null @@ -1,104 +0,0 @@ -package charger - -// Code generated by github.com/evcc-io/evcc/cmd/tools/decorate.go. DO NOT EDIT. - -import ( - "reflect" - - "github.com/evcc-io/evcc/api" -) - -func decorateEVSE(base *EVSEWifi, meter func() (float64, error), meterEnergy func() (float64, error), phaseCurrents func() (float64, float64, float64, error), phaseVoltages func() (float64, float64, float64, error), chargerEx func(float64) error, identifier func() (string, error)) api.Charger { - caps := make(map[reflect.Type]any) - - if meter != nil { - caps[reflect.TypeFor[api.Meter]()] = &decorateEVSEMeterImpl{meter: meter} - } - - if meterEnergy != nil { - caps[reflect.TypeFor[api.MeterEnergy]()] = &decorateEVSEMeterEnergyImpl{meterEnergy: meterEnergy} - } - - if phaseCurrents != nil { - caps[reflect.TypeFor[api.PhaseCurrents]()] = &decorateEVSEPhaseCurrentsImpl{phaseCurrents: phaseCurrents} - } - - if phaseVoltages != nil { - caps[reflect.TypeFor[api.PhaseVoltages]()] = &decorateEVSEPhaseVoltagesImpl{phaseVoltages: phaseVoltages} - } - - if chargerEx != nil { - caps[reflect.TypeFor[api.ChargerEx]()] = &decorateEVSEChargerExImpl{chargerEx: chargerEx} - } - - if identifier != nil { - caps[reflect.TypeFor[api.Identifier]()] = &decorateEVSEIdentifierImpl{identifier: identifier} - } - - if len(caps) == 0 { - return base - } - - return &decorateEVSECapable{EVSEWifi: base, caps: caps} -} - -type decorateEVSECapable struct { - *EVSEWifi - caps map[reflect.Type]any -} - -func (d *decorateEVSECapable) Capability(typ reflect.Type) (any, bool) { - c, ok := d.caps[typ] - if !ok && reflect.TypeOf(d).Implements(typ) { - return d, true - } - return c, ok -} - -type decorateEVSEChargerExImpl struct { - chargerEx func(float64) error -} - -func (impl *decorateEVSEChargerExImpl) MaxCurrentMillis(p0 float64) error { - return impl.chargerEx(p0) -} - -type decorateEVSEIdentifierImpl struct { - identifier func() (string, error) -} - -func (impl *decorateEVSEIdentifierImpl) Identify() (string, error) { - return impl.identifier() -} - -type decorateEVSEMeterImpl struct { - meter func() (float64, error) -} - -func (impl *decorateEVSEMeterImpl) CurrentPower() (float64, error) { - return impl.meter() -} - -type decorateEVSEMeterEnergyImpl struct { - meterEnergy func() (float64, error) -} - -func (impl *decorateEVSEMeterEnergyImpl) TotalEnergy() (float64, error) { - return impl.meterEnergy() -} - -type decorateEVSEPhaseCurrentsImpl struct { - phaseCurrents func() (float64, float64, float64, error) -} - -func (impl *decorateEVSEPhaseCurrentsImpl) Currents() (float64, float64, float64, error) { - return impl.phaseCurrents() -} - -type decorateEVSEPhaseVoltagesImpl struct { - phaseVoltages func() (float64, float64, float64, error) -} - -func (impl *decorateEVSEPhaseVoltagesImpl) Voltages() (float64, float64, float64, error) { - return impl.phaseVoltages() -} diff --git a/charger/ghosteebus.go b/charger/ghosteebus.go index d6b21d174..9582e1a5f 100644 --- a/charger/ghosteebus.go +++ b/charger/ghosteebus.go @@ -8,6 +8,7 @@ import ( "github.com/enbility/spine-go/model" "github.com/evcc-io/evcc/api" + "github.com/evcc-io/evcc/api/implement" "github.com/evcc-io/evcc/charger/ghostone" "github.com/evcc-io/evcc/util" "github.com/evcc-io/evcc/util/request" @@ -49,8 +50,6 @@ func NewGhostEEBusFromConfig(ctx context.Context, other map[string]any) (api.Cha return NewGhostEEBus(ctx, cc.Ski, cc.Ip, cc.User, cc.Password, cc.Meter, hasChargedEnergy) } -//go:generate go tool decorate -f decorateGhostEEBus -b *GhostEEBus -r api.Charger -t api.Meter,api.PhaseCurrents,api.ChargeRater,api.PhaseSwitcher,api.PhaseGetter - // NewGhostEEBus creates a GhostEEBus charger combining EEBus with Ghost REST API func NewGhostEEBus(ctx context.Context, ski, ip, user, password string, hasMeter, hasChargedEnergy bool) (api.Charger, error) { eb, err := newEEBus(ctx, ski, ip) @@ -69,9 +68,6 @@ func NewGhostEEBus(ctx context.Context, ski, ip, user, password string, hasMeter } // REST API features require IP and credentials - var phasesS func(int) error - var phasesG func() (int, error) - if ip != "" && user != "" && password != "" { ts, err := ghostone.TokenSource(ctx, log, wb.uri, user, password) if err != nil { @@ -97,25 +93,21 @@ func NewGhostEEBus(ctx context.Context, ski, ip, user, password string, hasMeter // always wire up phase switching and RFID - the wallbox will reject // operations at runtime if the feature is disabled or not possible - phasesS = wb.phases1p3p - phasesG = wb.getPhases + implement.Has(wb, implement.PhaseSwitcher(wb.phases1p3p)) + implement.Has(wb, implement.PhaseGetter(wb.getPhases)) wb.hasRFID = true } // EEBus meter capabilities - var meter func() (float64, error) - var phaseCurrents func() (float64, float64, float64, error) - var chargeRater func() (float64, error) - if hasMeter { - meter = eb.currentPower - phaseCurrents = eb.currents + implement.Has(wb, implement.Meter(eb.currentPower)) + implement.Has(wb, implement.PhaseCurrents(eb.currents)) if hasChargedEnergy { - chargeRater = eb.chargedEnergy + implement.Has(wb, implement.ChargeRater(eb.chargedEnergy)) } } - return decorateGhostEEBus(wb, meter, phaseCurrents, chargeRater, phasesS, phasesG), nil + return wb, nil } var _ api.Identifier = (*GhostEEBus)(nil) diff --git a/charger/ghosteebus_decorators.go b/charger/ghosteebus_decorators.go deleted file mode 100644 index 6e60a779b..000000000 --- a/charger/ghosteebus_decorators.go +++ /dev/null @@ -1,92 +0,0 @@ -package charger - -// Code generated by github.com/evcc-io/evcc/cmd/tools/decorate.go. DO NOT EDIT. - -import ( - "reflect" - - "github.com/evcc-io/evcc/api" -) - -func decorateGhostEEBus(base *GhostEEBus, meter func() (float64, error), phaseCurrents func() (float64, float64, float64, error), chargeRater func() (float64, error), phaseSwitcher func(int) error, phaseGetter func() (int, error)) api.Charger { - caps := make(map[reflect.Type]any) - - if meter != nil { - caps[reflect.TypeFor[api.Meter]()] = &decorateGhostEEBusMeterImpl{meter: meter} - } - - if phaseCurrents != nil { - caps[reflect.TypeFor[api.PhaseCurrents]()] = &decorateGhostEEBusPhaseCurrentsImpl{phaseCurrents: phaseCurrents} - } - - if chargeRater != nil { - caps[reflect.TypeFor[api.ChargeRater]()] = &decorateGhostEEBusChargeRaterImpl{chargeRater: chargeRater} - } - - if phaseSwitcher != nil { - caps[reflect.TypeFor[api.PhaseSwitcher]()] = &decorateGhostEEBusPhaseSwitcherImpl{phaseSwitcher: phaseSwitcher} - } - - if phaseGetter != nil { - caps[reflect.TypeFor[api.PhaseGetter]()] = &decorateGhostEEBusPhaseGetterImpl{phaseGetter: phaseGetter} - } - - if len(caps) == 0 { - return base - } - - return &decorateGhostEEBusCapable{GhostEEBus: base, caps: caps} -} - -type decorateGhostEEBusCapable struct { - *GhostEEBus - caps map[reflect.Type]any -} - -func (d *decorateGhostEEBusCapable) Capability(typ reflect.Type) (any, bool) { - c, ok := d.caps[typ] - if !ok && reflect.TypeOf(d).Implements(typ) { - return d, true - } - return c, ok -} - -type decorateGhostEEBusChargeRaterImpl struct { - chargeRater func() (float64, error) -} - -func (impl *decorateGhostEEBusChargeRaterImpl) ChargedEnergy() (float64, error) { - return impl.chargeRater() -} - -type decorateGhostEEBusMeterImpl struct { - meter func() (float64, error) -} - -func (impl *decorateGhostEEBusMeterImpl) CurrentPower() (float64, error) { - return impl.meter() -} - -type decorateGhostEEBusPhaseCurrentsImpl struct { - phaseCurrents func() (float64, float64, float64, error) -} - -func (impl *decorateGhostEEBusPhaseCurrentsImpl) Currents() (float64, float64, float64, error) { - return impl.phaseCurrents() -} - -type decorateGhostEEBusPhaseGetterImpl struct { - phaseGetter func() (int, error) -} - -func (impl *decorateGhostEEBusPhaseGetterImpl) GetPhases() (int, error) { - return impl.phaseGetter() -} - -type decorateGhostEEBusPhaseSwitcherImpl struct { - phaseSwitcher func(int) error -} - -func (impl *decorateGhostEEBusPhaseSwitcherImpl) Phases1p3p(p0 int) error { - return impl.phaseSwitcher(p0) -} diff --git a/charger/ghosteebus_test.go b/charger/ghosteebus_test.go index b4cd44177..3f5f7f4e2 100644 --- a/charger/ghosteebus_test.go +++ b/charger/ghosteebus_test.go @@ -12,6 +12,7 @@ import ( spinemocks "github.com/enbility/spine-go/mocks" "github.com/enbility/spine-go/model" "github.com/evcc-io/evcc/api" + "github.com/evcc-io/evcc/api/implement" "github.com/evcc-io/evcc/charger/ghostone" "github.com/evcc-io/evcc/server/eebus" "github.com/evcc-io/evcc/util" @@ -26,8 +27,9 @@ func newTestGhostEEBusREST(t *testing.T) *GhostEEBus { t.Helper() return &GhostEEBus{ EEBus: &EEBus{ - log: util.NewLogger("test"), - cem: &eebus.CustomerEnergyManagement{}, + Caps: implement.New(), + log: util.NewLogger("test"), + cem: &eebus.CustomerEnergyManagement{}, }, Helper: request.NewHelper(util.NewLogger("test")), uri: "https://wallbox.local/api/v2", @@ -42,6 +44,7 @@ func newTestGhostEEBusWithEEBus(t *testing.T) (*GhostEEBus, *mocks.CemEVCCInterf evEntity := spinemocks.NewEntityRemoteInterface(t) eb := &EEBus{ + Caps: implement.New(), cem: &eebus.CustomerEnergyManagement{ EvCC: evccMock, }, @@ -285,13 +288,14 @@ func TestGhostEEBus_Decorator(t *testing.T) { wb := newTestGhostEEBusREST(t) // with phase switching - decorated := decorateGhostEEBus(wb, nil, nil, nil, wb.phases1p3p, wb.getPhases) - assert.True(t, api.HasCap[api.PhaseSwitcher](decorated), "expected PhaseSwitcher") - assert.True(t, api.HasCap[api.PhaseGetter](decorated), "expected PhaseGetter") + implement.May(wb, implement.PhaseSwitcher(wb.phases1p3p)) + implement.May(wb, implement.PhaseGetter(wb.getPhases)) + assert.True(t, api.HasCap[api.PhaseSwitcher](wb), "expected PhaseSwitcher") + assert.True(t, api.HasCap[api.PhaseGetter](wb), "expected PhaseGetter") - // without phase switching - decorated = decorateGhostEEBus(wb, nil, nil, nil, nil, nil) - assert.False(t, api.HasCap[api.PhaseSwitcher](decorated), "unexpected PhaseSwitcher") + // without phase switching — capabilities not registered, so expect false + wb2 := newTestGhostEEBusREST(t) + assert.False(t, api.HasCap[api.PhaseSwitcher](wb2), "unexpected PhaseSwitcher") } func TestGhostEEBus_Config(t *testing.T) { diff --git a/charger/go-e.go b/charger/go-e.go index e3b261893..9597e2b36 100644 --- a/charger/go-e.go +++ b/charger/go-e.go @@ -23,6 +23,7 @@ import ( "time" "github.com/evcc-io/evcc/api" + "github.com/evcc-io/evcc/api/implement" goe "github.com/evcc-io/evcc/charger/go-e" "github.com/evcc-io/evcc/util" "github.com/evcc-io/evcc/util/sponsor" @@ -34,6 +35,7 @@ import ( // GoE charger implementation type GoE struct { + implement.Caps api goe.API } @@ -46,8 +48,6 @@ func init() { }) } -//go:generate go tool decorate -f decorateGoE -b *GoE -r api.Charger -t api.ChargeRater,api.PhaseSwitcher - // newGoEFromConfig creates a go-e charger from generic config func newGoEFromConfig(v2 bool, other map[string]any) (api.Charger, error) { cc := struct { @@ -71,22 +71,22 @@ func newGoEFromConfig(v2 bool, other map[string]any) (api.Charger, error) { return nil, err } - var chargedEnergy func() (float64, error) if v2 { - chargedEnergy = c.chargedEnergy + implement.Has(c, implement.ChargeRater(c.chargedEnergy)) } - var phases1p3p func(int) error if c.api.IsV2() { - phases1p3p = c.phases1p3p + implement.Has(c, implement.PhaseSwitcher(c.phases1p3p)) } - return decorateGoE(c, chargedEnergy, phases1p3p), nil + return c, nil } // NewGoE creates GoE charger func NewGoE(uri, token string, cache time.Duration) (*GoE, error) { - c := &GoE{} + c := &GoE{ + Caps: implement.New(), + } log := util.NewLogger("go-e").Redact(token) diff --git a/charger/go-e_decorators.go b/charger/go-e_decorators.go deleted file mode 100644 index 8f2c9dd4c..000000000 --- a/charger/go-e_decorators.go +++ /dev/null @@ -1,56 +0,0 @@ -package charger - -// Code generated by github.com/evcc-io/evcc/cmd/tools/decorate.go. DO NOT EDIT. - -import ( - "reflect" - - "github.com/evcc-io/evcc/api" -) - -func decorateGoE(base *GoE, chargeRater func() (float64, error), phaseSwitcher func(int) error) api.Charger { - caps := make(map[reflect.Type]any) - - if chargeRater != nil { - caps[reflect.TypeFor[api.ChargeRater]()] = &decorateGoEChargeRaterImpl{chargeRater: chargeRater} - } - - if phaseSwitcher != nil { - caps[reflect.TypeFor[api.PhaseSwitcher]()] = &decorateGoEPhaseSwitcherImpl{phaseSwitcher: phaseSwitcher} - } - - if len(caps) == 0 { - return base - } - - return &decorateGoECapable{GoE: base, caps: caps} -} - -type decorateGoECapable struct { - *GoE - caps map[reflect.Type]any -} - -func (d *decorateGoECapable) Capability(typ reflect.Type) (any, bool) { - c, ok := d.caps[typ] - if !ok && reflect.TypeOf(d).Implements(typ) { - return d, true - } - return c, ok -} - -type decorateGoEChargeRaterImpl struct { - chargeRater func() (float64, error) -} - -func (impl *decorateGoEChargeRaterImpl) ChargedEnergy() (float64, error) { - return impl.chargeRater() -} - -type decorateGoEPhaseSwitcherImpl struct { - phaseSwitcher func(int) error -} - -func (impl *decorateGoEPhaseSwitcherImpl) Phases1p3p(p0 int) error { - return impl.phaseSwitcher(p0) -} diff --git a/charger/hardybarth-salia.go b/charger/hardybarth-salia.go index 11ce365b0..f5d5f66a0 100644 --- a/charger/hardybarth-salia.go +++ b/charger/hardybarth-salia.go @@ -28,6 +28,7 @@ import ( "github.com/cenkalti/backoff/v4" "github.com/evcc-io/evcc/api" + "github.com/evcc-io/evcc/api/implement" "github.com/evcc-io/evcc/charger/echarge" "github.com/evcc-io/evcc/charger/echarge/salia" "github.com/evcc-io/evcc/util" @@ -42,6 +43,7 @@ import ( // Salia charger implementation type Salia struct { *request.Helper + implement.Caps log *util.Logger uri string current int64 @@ -53,8 +55,6 @@ func init() { registry.AddCtx("hardybarth-salia", NewSaliaFromConfig) } -//go:generate go tool decorate -f decorateSalia -b *Salia -r api.Charger -t api.Meter,api.MeterEnergy,api.PhaseCurrents,api.PhaseSwitcher,api.PhaseGetter - // NewSaliaFromConfig creates a Salia cPH2 charger from generic config func NewSaliaFromConfig(ctx context.Context, other map[string]any) (api.Charger, error) { cc := struct { @@ -83,6 +83,7 @@ func NewSalia(ctx context.Context, uri, user, password string, cache time.Durati wb := &Salia{ log: log, Helper: request.NewHelper(log), + Caps: implement.New(), uri: util.DefaultScheme(uri, "http"), current: 6, } @@ -138,26 +139,18 @@ func NewSalia(ctx context.Context, uri, user, password string, cache time.Durati wb.pause(false) - var ( - currentPower func() (float64, error) - totalEnergy func() (float64, error) - currents func() (float64, float64, float64, error) - phasesG func() (int, error) - phasesS func(int) error - ) - if res.Secc.Port0.Metering.Meter.Available > 0 { - currentPower = wb.currentPower - totalEnergy = wb.totalEnergy - currents = wb.currents + implement.Has(wb, implement.Meter(wb.currentPower)) + implement.Has(wb, implement.MeterEnergy(wb.totalEnergy)) + implement.Has(wb, implement.PhaseCurrents(wb.currents)) } if res.Secc.Port0.Salia.PhaseSwitching.Actual > 0 { - phasesG = wb.getPhases - phasesS = wb.phases1p3p + implement.Has(wb, implement.PhaseSwitcher(wb.phases1p3p)) + implement.Has(wb, implement.PhaseGetter(wb.getPhases)) } - return decorateSalia(wb, currentPower, totalEnergy, currents, phasesS, phasesG), nil + return wb, nil } func (wb *Salia) heartbeat(ctx context.Context) { diff --git a/charger/hardybarth-salia_decorators.go b/charger/hardybarth-salia_decorators.go deleted file mode 100644 index 20188cbba..000000000 --- a/charger/hardybarth-salia_decorators.go +++ /dev/null @@ -1,92 +0,0 @@ -package charger - -// Code generated by github.com/evcc-io/evcc/cmd/tools/decorate.go. DO NOT EDIT. - -import ( - "reflect" - - "github.com/evcc-io/evcc/api" -) - -func decorateSalia(base *Salia, meter func() (float64, error), meterEnergy func() (float64, error), phaseCurrents func() (float64, float64, float64, error), phaseSwitcher func(int) error, phaseGetter func() (int, error)) api.Charger { - caps := make(map[reflect.Type]any) - - if meter != nil { - caps[reflect.TypeFor[api.Meter]()] = &decorateSaliaMeterImpl{meter: meter} - } - - if meterEnergy != nil { - caps[reflect.TypeFor[api.MeterEnergy]()] = &decorateSaliaMeterEnergyImpl{meterEnergy: meterEnergy} - } - - if phaseCurrents != nil { - caps[reflect.TypeFor[api.PhaseCurrents]()] = &decorateSaliaPhaseCurrentsImpl{phaseCurrents: phaseCurrents} - } - - if phaseSwitcher != nil { - caps[reflect.TypeFor[api.PhaseSwitcher]()] = &decorateSaliaPhaseSwitcherImpl{phaseSwitcher: phaseSwitcher} - } - - if phaseGetter != nil { - caps[reflect.TypeFor[api.PhaseGetter]()] = &decorateSaliaPhaseGetterImpl{phaseGetter: phaseGetter} - } - - if len(caps) == 0 { - return base - } - - return &decorateSaliaCapable{Salia: base, caps: caps} -} - -type decorateSaliaCapable struct { - *Salia - caps map[reflect.Type]any -} - -func (d *decorateSaliaCapable) Capability(typ reflect.Type) (any, bool) { - c, ok := d.caps[typ] - if !ok && reflect.TypeOf(d).Implements(typ) { - return d, true - } - return c, ok -} - -type decorateSaliaMeterImpl struct { - meter func() (float64, error) -} - -func (impl *decorateSaliaMeterImpl) CurrentPower() (float64, error) { - return impl.meter() -} - -type decorateSaliaMeterEnergyImpl struct { - meterEnergy func() (float64, error) -} - -func (impl *decorateSaliaMeterEnergyImpl) TotalEnergy() (float64, error) { - return impl.meterEnergy() -} - -type decorateSaliaPhaseCurrentsImpl struct { - phaseCurrents func() (float64, float64, float64, error) -} - -func (impl *decorateSaliaPhaseCurrentsImpl) Currents() (float64, float64, float64, error) { - return impl.phaseCurrents() -} - -type decorateSaliaPhaseGetterImpl struct { - phaseGetter func() (int, error) -} - -func (impl *decorateSaliaPhaseGetterImpl) GetPhases() (int, error) { - return impl.phaseGetter() -} - -type decorateSaliaPhaseSwitcherImpl struct { - phaseSwitcher func(int) error -} - -func (impl *decorateSaliaPhaseSwitcherImpl) Phases1p3p(p0 int) error { - return impl.phaseSwitcher(p0) -} diff --git a/charger/heatpump.go b/charger/heatpump.go index ed29ccd93..40b64caf3 100644 --- a/charger/heatpump.go +++ b/charger/heatpump.go @@ -21,6 +21,7 @@ import ( "context" "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/core/loadpoint" "github.com/evcc-io/evcc/plugin" @@ -30,6 +31,7 @@ import ( // Heatpump charger implementation type Heatpump struct { *embed + implement.Caps lp loadpoint.API power int64 maxPowerG func() (int64, error) @@ -40,8 +42,6 @@ func init() { registry.AddCtx("heatpump", NewHeatpumpFromConfig) } -//go:generate go tool decorate -f decorateHeatpump -b *Heatpump -r api.Charger -t api.Meter,api.MeterEnergy,api.Battery,api.SocLimiter - // NewHeatpumpFromConfig creates heatpump configurable charger from generic config func NewHeatpumpFromConfig(ctx context.Context, other map[string]any) (api.Charger, error) { cc := struct { @@ -84,19 +84,24 @@ func NewHeatpumpFromConfig(ctx context.Context, other map[string]any) (api.Charg if err != nil { return nil, err } + implement.May(res, implement.Meter(powerG)) + implement.May(res, implement.MeterEnergy(energyG)) tempG, limitTempG, err := cc.Temperature.Configure(ctx) if err != nil { return nil, err } + implement.May(res, implement.Battery(tempG)) + implement.May(res, implement.SocLimiter(limitTempG)) - return decorateHeatpump(res, powerG, energyG, tempG, limitTempG), nil + return res, nil } // NewHeatpump creates heatpump charger func NewHeatpump(ctx context.Context, embed *embed, maxPowerS func(int64) error, maxPowerG func() (int64, error)) (*Heatpump, error) { res := &Heatpump{ embed: embed, + Caps: implement.New(), maxPowerG: maxPowerG, maxPowerS: maxPowerS, } diff --git a/charger/heatpump_decorators.go b/charger/heatpump_decorators.go deleted file mode 100644 index 68c876185..000000000 --- a/charger/heatpump_decorators.go +++ /dev/null @@ -1,80 +0,0 @@ -package charger - -// Code generated by github.com/evcc-io/evcc/cmd/tools/decorate.go. DO NOT EDIT. - -import ( - "reflect" - - "github.com/evcc-io/evcc/api" -) - -func decorateHeatpump(base *Heatpump, meter func() (float64, error), meterEnergy func() (float64, error), battery func() (float64, error), socLimiter func() (int64, error)) api.Charger { - caps := make(map[reflect.Type]any) - - if meter != nil { - caps[reflect.TypeFor[api.Meter]()] = &decorateHeatpumpMeterImpl{meter: meter} - } - - if meterEnergy != nil { - caps[reflect.TypeFor[api.MeterEnergy]()] = &decorateHeatpumpMeterEnergyImpl{meterEnergy: meterEnergy} - } - - if battery != nil { - caps[reflect.TypeFor[api.Battery]()] = &decorateHeatpumpBatteryImpl{battery: battery} - } - - if socLimiter != nil { - caps[reflect.TypeFor[api.SocLimiter]()] = &decorateHeatpumpSocLimiterImpl{socLimiter: socLimiter} - } - - if len(caps) == 0 { - return base - } - - return &decorateHeatpumpCapable{Heatpump: base, caps: caps} -} - -type decorateHeatpumpCapable struct { - *Heatpump - caps map[reflect.Type]any -} - -func (d *decorateHeatpumpCapable) Capability(typ reflect.Type) (any, bool) { - c, ok := d.caps[typ] - if !ok && reflect.TypeOf(d).Implements(typ) { - return d, true - } - return c, ok -} - -type decorateHeatpumpBatteryImpl struct { - battery func() (float64, error) -} - -func (impl *decorateHeatpumpBatteryImpl) Soc() (float64, error) { - return impl.battery() -} - -type decorateHeatpumpMeterImpl struct { - meter func() (float64, error) -} - -func (impl *decorateHeatpumpMeterImpl) CurrentPower() (float64, error) { - return impl.meter() -} - -type decorateHeatpumpMeterEnergyImpl struct { - meterEnergy func() (float64, error) -} - -func (impl *decorateHeatpumpMeterEnergyImpl) TotalEnergy() (float64, error) { - return impl.meterEnergy() -} - -type decorateHeatpumpSocLimiterImpl struct { - socLimiter func() (int64, error) -} - -func (impl *decorateHeatpumpSocLimiterImpl) GetLimitSoc() (int64, error) { - return impl.socLimiter() -} diff --git a/charger/homeassistant.go b/charger/homeassistant.go index 2f31b6d63..9da621a4f 100644 --- a/charger/homeassistant.go +++ b/charger/homeassistant.go @@ -1,20 +1,19 @@ package charger -//go:generate go tool decorate -f decorateHomeAssistant -b *HomeAssistant -r api.Charger -t api.Meter,api.MeterEnergy,api.PhaseCurrents,api.PhaseVoltages,api.PhaseSwitcher,api.PhaseGetter -// -t api.CurrentGetter - import ( "errors" "fmt" "strconv" "github.com/evcc-io/evcc/api" + "github.com/evcc-io/evcc/api/implement" "github.com/evcc-io/evcc/util" "github.com/evcc-io/evcc/util/homeassistant" ) // HomeAssistant charger implementation type HomeAssistant struct { + implement.Caps conn *homeassistant.Connection status string enabled string @@ -68,6 +67,7 @@ func NewHomeAssistantFromConfig(other map[string]any) (api.Charger, error) { } c := &HomeAssistant{ + Caps: implement.New(), conn: conn, status: cc.Status, enabled: cc.Enabled, @@ -75,49 +75,43 @@ func NewHomeAssistantFromConfig(other map[string]any) (api.Charger, error) { maxcurrent: cc.MaxCurrent, } - // decorators for optional interfaces - var power, energy func() (float64, error) - var currents, voltages func() (float64, float64, float64, error) - var phases1p3p func(int) error - var phasesG func() (int, error) - if cc.Power != "" { - power = func() (float64, error) { return conn.GetFloatState(cc.Power) } + implement.Has(c, implement.Meter(func() (float64, error) { return conn.GetFloatState(cc.Power) })) } if cc.Energy != "" { - energy = func() (float64, error) { return conn.GetFloatState(cc.Energy) } + implement.Has(c, implement.MeterEnergy(func() (float64, error) { return conn.GetFloatState(cc.Energy) })) } // phase currents (optional) if phases, err := homeassistant.ValidatePhaseEntities(cc.Currents); len(phases) > 0 { - currents = func() (float64, float64, float64, error) { return conn.GetPhaseFloatStates(phases) } + implement.Has(c, implement.PhaseCurrents(func() (float64, float64, float64, error) { return conn.GetPhaseFloatStates(phases) })) } else if err != nil { return nil, fmt.Errorf("currents: %w", err) } // phase voltages (optional) if phases, err := homeassistant.ValidatePhaseEntities(cc.Voltages); len(phases) > 0 { - voltages = func() (float64, float64, float64, error) { return conn.GetPhaseFloatStates(phases) } + implement.Has(c, implement.PhaseVoltages(func() (float64, float64, float64, error) { return conn.GetPhaseFloatStates(phases) })) } else if err != nil { return nil, fmt.Errorf("voltages: %w", err) } // phase switching (optional) if cc.Phases != "" { - phases1p3p = func(phases int) error { + implement.Has(c, implement.PhaseSwitcher(func(phases int) error { return conn.CallSelectService(cc.Phases, strconv.Itoa(phases)) - } + })) - phasesG = func() (int, error) { + implement.Has(c, implement.PhaseGetter(func() (int, error) { val, err := conn.GetIntState(cc.Phases) if err != nil { return 0, err } return int(val), nil - } + })) } - return decorateHomeAssistant(c, power, energy, currents, voltages, phases1p3p, phasesG), nil + return c, nil } var _ api.Charger = (*HomeAssistant)(nil) diff --git a/charger/homeassistant_decorators.go b/charger/homeassistant_decorators.go deleted file mode 100644 index 4dac8fd14..000000000 --- a/charger/homeassistant_decorators.go +++ /dev/null @@ -1,104 +0,0 @@ -package charger - -// Code generated by github.com/evcc-io/evcc/cmd/tools/decorate.go. DO NOT EDIT. - -import ( - "reflect" - - "github.com/evcc-io/evcc/api" -) - -func decorateHomeAssistant(base *HomeAssistant, meter func() (float64, error), meterEnergy func() (float64, error), phaseCurrents func() (float64, float64, float64, error), phaseVoltages func() (float64, float64, float64, error), phaseSwitcher func(int) error, phaseGetter func() (int, error)) api.Charger { - caps := make(map[reflect.Type]any) - - if meter != nil { - caps[reflect.TypeFor[api.Meter]()] = &decorateHomeAssistantMeterImpl{meter: meter} - } - - if meterEnergy != nil { - caps[reflect.TypeFor[api.MeterEnergy]()] = &decorateHomeAssistantMeterEnergyImpl{meterEnergy: meterEnergy} - } - - if phaseCurrents != nil { - caps[reflect.TypeFor[api.PhaseCurrents]()] = &decorateHomeAssistantPhaseCurrentsImpl{phaseCurrents: phaseCurrents} - } - - if phaseVoltages != nil { - caps[reflect.TypeFor[api.PhaseVoltages]()] = &decorateHomeAssistantPhaseVoltagesImpl{phaseVoltages: phaseVoltages} - } - - if phaseSwitcher != nil { - caps[reflect.TypeFor[api.PhaseSwitcher]()] = &decorateHomeAssistantPhaseSwitcherImpl{phaseSwitcher: phaseSwitcher} - } - - if phaseGetter != nil { - caps[reflect.TypeFor[api.PhaseGetter]()] = &decorateHomeAssistantPhaseGetterImpl{phaseGetter: phaseGetter} - } - - if len(caps) == 0 { - return base - } - - return &decorateHomeAssistantCapable{HomeAssistant: base, caps: caps} -} - -type decorateHomeAssistantCapable struct { - *HomeAssistant - caps map[reflect.Type]any -} - -func (d *decorateHomeAssistantCapable) Capability(typ reflect.Type) (any, bool) { - c, ok := d.caps[typ] - if !ok && reflect.TypeOf(d).Implements(typ) { - return d, true - } - return c, ok -} - -type decorateHomeAssistantMeterImpl struct { - meter func() (float64, error) -} - -func (impl *decorateHomeAssistantMeterImpl) CurrentPower() (float64, error) { - return impl.meter() -} - -type decorateHomeAssistantMeterEnergyImpl struct { - meterEnergy func() (float64, error) -} - -func (impl *decorateHomeAssistantMeterEnergyImpl) TotalEnergy() (float64, error) { - return impl.meterEnergy() -} - -type decorateHomeAssistantPhaseCurrentsImpl struct { - phaseCurrents func() (float64, float64, float64, error) -} - -func (impl *decorateHomeAssistantPhaseCurrentsImpl) Currents() (float64, float64, float64, error) { - return impl.phaseCurrents() -} - -type decorateHomeAssistantPhaseGetterImpl struct { - phaseGetter func() (int, error) -} - -func (impl *decorateHomeAssistantPhaseGetterImpl) GetPhases() (int, error) { - return impl.phaseGetter() -} - -type decorateHomeAssistantPhaseSwitcherImpl struct { - phaseSwitcher func(int) error -} - -func (impl *decorateHomeAssistantPhaseSwitcherImpl) Phases1p3p(p0 int) error { - return impl.phaseSwitcher(p0) -} - -type decorateHomeAssistantPhaseVoltagesImpl struct { - phaseVoltages func() (float64, float64, float64, error) -} - -func (impl *decorateHomeAssistantPhaseVoltagesImpl) Voltages() (float64, float64, float64, error) { - return impl.phaseVoltages() -} diff --git a/charger/innogy.go b/charger/innogy.go index e827165fd..1626487b4 100644 --- a/charger/innogy.go +++ b/charger/innogy.go @@ -24,6 +24,7 @@ import ( "math" "github.com/evcc-io/evcc/api" + "github.com/evcc-io/evcc/api/implement" "github.com/evcc-io/evcc/util" "github.com/evcc-io/evcc/util/modbus" "github.com/evcc-io/evcc/util/sponsor" @@ -46,6 +47,7 @@ var igyRegMaxCurrents = []uint16{1012, 1014, 1016} // max current per phase // Innogy is an api.Charger implementation for Innogy eBox wallboxes. type Innogy struct { + implement.Caps conn *modbus.Connection curr float64 hasVoltages bool @@ -70,21 +72,16 @@ func NewInnogyFromConfig(ctx context.Context, other map[string]any) (api.Charger return nil, err } - var totalEnergy func() (float64, error) - var voltages func() (float64, float64, float64, error) - // check presence of energy meter & voltages registers if b, err := wb.conn.ReadInputRegisters(igyRegModbusTableVersion, 1); err == nil && binary.BigEndian.Uint16(b) >= 6 { - totalEnergy = wb.totalEnergy - voltages = wb.voltages + implement.Has(wb, implement.MeterEnergy(wb.totalEnergy)) + implement.Has(wb, implement.PhaseVoltages(wb.voltages)) wb.hasVoltages = true } - return decorateInnogy(wb, totalEnergy, voltages), nil + return wb, nil } -//go:generate go tool decorate -f decorateInnogy -b *Innogy -r api.Charger -t api.MeterEnergy,api.PhaseVoltages - // NewInnogy creates a Innogy charger func NewInnogy(ctx context.Context, uri string, id uint8) (*Innogy, error) { conn, err := modbus.NewConnection(ctx, uri, "", "", 0, modbus.Tcp, id) @@ -100,6 +97,7 @@ func NewInnogy(ctx context.Context, uri string, id uint8) (*Innogy, error) { conn.Logger(log.TRACE) wb := &Innogy{ + Caps: implement.New(), conn: conn, curr: 6, } diff --git a/charger/innogy_decorators.go b/charger/innogy_decorators.go deleted file mode 100644 index c73120052..000000000 --- a/charger/innogy_decorators.go +++ /dev/null @@ -1,56 +0,0 @@ -package charger - -// Code generated by github.com/evcc-io/evcc/cmd/tools/decorate.go. DO NOT EDIT. - -import ( - "reflect" - - "github.com/evcc-io/evcc/api" -) - -func decorateInnogy(base *Innogy, meterEnergy func() (float64, error), phaseVoltages func() (float64, float64, float64, error)) api.Charger { - caps := make(map[reflect.Type]any) - - if meterEnergy != nil { - caps[reflect.TypeFor[api.MeterEnergy]()] = &decorateInnogyMeterEnergyImpl{meterEnergy: meterEnergy} - } - - if phaseVoltages != nil { - caps[reflect.TypeFor[api.PhaseVoltages]()] = &decorateInnogyPhaseVoltagesImpl{phaseVoltages: phaseVoltages} - } - - if len(caps) == 0 { - return base - } - - return &decorateInnogyCapable{Innogy: base, caps: caps} -} - -type decorateInnogyCapable struct { - *Innogy - caps map[reflect.Type]any -} - -func (d *decorateInnogyCapable) Capability(typ reflect.Type) (any, bool) { - c, ok := d.caps[typ] - if !ok && reflect.TypeOf(d).Implements(typ) { - return d, true - } - return c, ok -} - -type decorateInnogyMeterEnergyImpl struct { - meterEnergy func() (float64, error) -} - -func (impl *decorateInnogyMeterEnergyImpl) TotalEnergy() (float64, error) { - return impl.meterEnergy() -} - -type decorateInnogyPhaseVoltagesImpl struct { - phaseVoltages func() (float64, float64, float64, error) -} - -func (impl *decorateInnogyPhaseVoltagesImpl) Voltages() (float64, float64, float64, error) { - return impl.phaseVoltages() -} diff --git a/charger/keba-modbus.go b/charger/keba-modbus.go index 65a3b44a1..fbfa3028a 100644 --- a/charger/keba-modbus.go +++ b/charger/keba-modbus.go @@ -27,6 +27,7 @@ import ( "time" "github.com/evcc-io/evcc/api" + "github.com/evcc-io/evcc/api/implement" "github.com/evcc-io/evcc/util" "github.com/evcc-io/evcc/util/modbus" "github.com/evcc-io/evcc/util/sponsor" @@ -39,6 +40,7 @@ import ( // Keba is an api.Charger implementation type Keba struct { *embed + implement.Caps log *util.Logger conn *modbus.Connection current uint16 @@ -72,8 +74,6 @@ func init() { registry.AddCtx("keba-modbus", NewKebaFromConfig) } -//go:generate go tool decorate -f decorateKeba -b *Keba -r api.Charger -t api.Meter,api.MeterEnergy,api.PhaseCurrents,api.Identifier,api.StatusReasoner,api.PhaseSwitcher,api.PhaseGetter - // NewKebaFromConfig creates a new Keba ModbusTCP charger func NewKebaFromConfig(ctx context.Context, other map[string]any) (api.Charger, error) { cc := struct { @@ -94,16 +94,6 @@ func NewKebaFromConfig(ctx context.Context, other map[string]any) (api.Charger, return nil, err } - // optional features - var ( - currentPower, totalEnergy func() (float64, error) - currents func() (float64, float64, float64, error) - identify func() (string, error) - reason func() (api.Reason, error) - phasesS func(int) error - phasesG func() (int, error) - ) - b, err := wb.conn.ReadHoldingRegisters(kebaRegProduct, 2) if err != nil { return nil, err @@ -140,21 +130,21 @@ func NewKebaFromConfig(ctx context.Context, other map[string]any) (api.Charger, } if hasEnergyMeter { - currentPower = wb.currentPower - totalEnergy = wb.totalEnergy - currents = wb.currents + implement.Has(wb, implement.Meter(wb.currentPower)) + implement.Has(wb, implement.MeterEnergy(wb.totalEnergy)) + implement.Has(wb, implement.PhaseCurrents(wb.currents)) } if hasRFID { - identify = wb.identify - reason = wb.statusReason + implement.Has(wb, implement.Identifier(wb.identify)) + implement.Has(wb, implement.StatusReasoner(wb.statusReason)) } // phases if b, err := wb.conn.ReadHoldingRegisters(kebaRegPhaseSource, 2); err == nil { if source := binary.BigEndian.Uint32(b); source == 3 { - phasesS = wb.phases1p3p - phasesG = wb.getPhases + implement.Has(wb, implement.PhaseSwitcher(wb.phases1p3p)) + implement.Has(wb, implement.PhaseGetter(wb.getPhases)) } } @@ -168,7 +158,7 @@ func NewKebaFromConfig(ctx context.Context, other map[string]any) (api.Charger, go wb.heartbeat(ctx, u) } - return decorateKeba(wb, currentPower, totalEnergy, currents, identify, reason, phasesS, phasesG), nil + return wb, nil } // NewKeba creates a new charger @@ -187,6 +177,7 @@ func NewKeba(ctx context.Context, embed embed, uri string, slaveID uint8) (*Keba wb := &Keba{ embed: &embed, + Caps: implement.New(), log: log, conn: conn, regEnable: kebaRegEnable, diff --git a/charger/keba-modbus_decorators.go b/charger/keba-modbus_decorators.go deleted file mode 100644 index 31aeda7ee..000000000 --- a/charger/keba-modbus_decorators.go +++ /dev/null @@ -1,116 +0,0 @@ -package charger - -// Code generated by github.com/evcc-io/evcc/cmd/tools/decorate.go. DO NOT EDIT. - -import ( - "reflect" - - "github.com/evcc-io/evcc/api" -) - -func decorateKeba(base *Keba, meter func() (float64, error), meterEnergy func() (float64, error), phaseCurrents func() (float64, float64, float64, error), identifier func() (string, error), statusReasoner func() (api.Reason, error), phaseSwitcher func(int) error, phaseGetter func() (int, error)) api.Charger { - caps := make(map[reflect.Type]any) - - if meter != nil { - caps[reflect.TypeFor[api.Meter]()] = &decorateKebaMeterImpl{meter: meter} - } - - if meterEnergy != nil { - caps[reflect.TypeFor[api.MeterEnergy]()] = &decorateKebaMeterEnergyImpl{meterEnergy: meterEnergy} - } - - if phaseCurrents != nil { - caps[reflect.TypeFor[api.PhaseCurrents]()] = &decorateKebaPhaseCurrentsImpl{phaseCurrents: phaseCurrents} - } - - if identifier != nil { - caps[reflect.TypeFor[api.Identifier]()] = &decorateKebaIdentifierImpl{identifier: identifier} - } - - if statusReasoner != nil { - caps[reflect.TypeFor[api.StatusReasoner]()] = &decorateKebaStatusReasonerImpl{statusReasoner: statusReasoner} - } - - if phaseSwitcher != nil { - caps[reflect.TypeFor[api.PhaseSwitcher]()] = &decorateKebaPhaseSwitcherImpl{phaseSwitcher: phaseSwitcher} - } - - if phaseGetter != nil { - caps[reflect.TypeFor[api.PhaseGetter]()] = &decorateKebaPhaseGetterImpl{phaseGetter: phaseGetter} - } - - if len(caps) == 0 { - return base - } - - return &decorateKebaCapable{Keba: base, caps: caps} -} - -type decorateKebaCapable struct { - *Keba - caps map[reflect.Type]any -} - -func (d *decorateKebaCapable) Capability(typ reflect.Type) (any, bool) { - c, ok := d.caps[typ] - if !ok && reflect.TypeOf(d).Implements(typ) { - return d, true - } - return c, ok -} - -type decorateKebaIdentifierImpl struct { - identifier func() (string, error) -} - -func (impl *decorateKebaIdentifierImpl) Identify() (string, error) { - return impl.identifier() -} - -type decorateKebaMeterImpl struct { - meter func() (float64, error) -} - -func (impl *decorateKebaMeterImpl) CurrentPower() (float64, error) { - return impl.meter() -} - -type decorateKebaMeterEnergyImpl struct { - meterEnergy func() (float64, error) -} - -func (impl *decorateKebaMeterEnergyImpl) TotalEnergy() (float64, error) { - return impl.meterEnergy() -} - -type decorateKebaPhaseCurrentsImpl struct { - phaseCurrents func() (float64, float64, float64, error) -} - -func (impl *decorateKebaPhaseCurrentsImpl) Currents() (float64, float64, float64, error) { - return impl.phaseCurrents() -} - -type decorateKebaPhaseGetterImpl struct { - phaseGetter func() (int, error) -} - -func (impl *decorateKebaPhaseGetterImpl) GetPhases() (int, error) { - return impl.phaseGetter() -} - -type decorateKebaPhaseSwitcherImpl struct { - phaseSwitcher func(int) error -} - -func (impl *decorateKebaPhaseSwitcherImpl) Phases1p3p(p0 int) error { - return impl.phaseSwitcher(p0) -} - -type decorateKebaStatusReasonerImpl struct { - statusReasoner func() (api.Reason, error) -} - -func (impl *decorateKebaStatusReasonerImpl) StatusReason() (api.Reason, error) { - return impl.statusReasoner() -} diff --git a/charger/keba-udp.go b/charger/keba-udp.go index d5d807b0b..9e20b0cef 100644 --- a/charger/keba-udp.go +++ b/charger/keba-udp.go @@ -8,6 +8,7 @@ import ( "time" "github.com/evcc-io/evcc/api" + "github.com/evcc-io/evcc/api/implement" "github.com/evcc-io/evcc/charger/keba" "github.com/evcc-io/evcc/util" ) @@ -20,6 +21,7 @@ const ( // KebaUdp is an api.Charger implementation type KebaUdp struct { + implement.Caps log *util.Logger conn string rfid keba.RFID @@ -32,8 +34,6 @@ func init() { registry.Add("keba-udp", NewKebaUdpFromConfig) } -//go:generate go tool decorate -f decorateKebaUdp -b *KebaUdp -r api.Charger -t api.Meter,api.MeterEnergy,api.PhaseCurrents - // NewKebaUdpFromConfig creates a new Keba UDP charger func NewKebaUdpFromConfig(other map[string]any) (api.Charger, error) { cc := struct { @@ -60,7 +60,9 @@ func NewKebaUdpFromConfig(other map[string]any) (api.Charger, error) { } if energy > 0 { - return decorateKebaUdp(k, k.currentPower, k.totalEnergy, k.currents), nil + implement.Has(k, implement.Meter(k.currentPower)) + implement.Has(k, implement.MeterEnergy(k.totalEnergy)) + implement.Has(k, implement.PhaseCurrents(k.currents)) } return k, err @@ -80,6 +82,7 @@ func NewKebaUdp(uri, serial string, rfid keba.RFID, timeout time.Duration) (*Keb sender, err := keba.NewSender(log, conn) c := &KebaUdp{ + Caps: implement.New(), log: log, conn: conn, rfid: rfid, diff --git a/charger/keba-udp_decorators.go b/charger/keba-udp_decorators.go deleted file mode 100644 index f3ae427ab..000000000 --- a/charger/keba-udp_decorators.go +++ /dev/null @@ -1,68 +0,0 @@ -package charger - -// Code generated by github.com/evcc-io/evcc/cmd/tools/decorate.go. DO NOT EDIT. - -import ( - "reflect" - - "github.com/evcc-io/evcc/api" -) - -func decorateKebaUdp(base *KebaUdp, meter func() (float64, error), meterEnergy func() (float64, error), phaseCurrents func() (float64, float64, float64, error)) api.Charger { - caps := make(map[reflect.Type]any) - - if meter != nil { - caps[reflect.TypeFor[api.Meter]()] = &decorateKebaUdpMeterImpl{meter: meter} - } - - if meterEnergy != nil { - caps[reflect.TypeFor[api.MeterEnergy]()] = &decorateKebaUdpMeterEnergyImpl{meterEnergy: meterEnergy} - } - - if phaseCurrents != nil { - caps[reflect.TypeFor[api.PhaseCurrents]()] = &decorateKebaUdpPhaseCurrentsImpl{phaseCurrents: phaseCurrents} - } - - if len(caps) == 0 { - return base - } - - return &decorateKebaUdpCapable{KebaUdp: base, caps: caps} -} - -type decorateKebaUdpCapable struct { - *KebaUdp - caps map[reflect.Type]any -} - -func (d *decorateKebaUdpCapable) Capability(typ reflect.Type) (any, bool) { - c, ok := d.caps[typ] - if !ok && reflect.TypeOf(d).Implements(typ) { - return d, true - } - return c, ok -} - -type decorateKebaUdpMeterImpl struct { - meter func() (float64, error) -} - -func (impl *decorateKebaUdpMeterImpl) CurrentPower() (float64, error) { - return impl.meter() -} - -type decorateKebaUdpMeterEnergyImpl struct { - meterEnergy func() (float64, error) -} - -func (impl *decorateKebaUdpMeterEnergyImpl) TotalEnergy() (float64, error) { - return impl.meterEnergy() -} - -type decorateKebaUdpPhaseCurrentsImpl struct { - phaseCurrents func() (float64, float64, float64, error) -} - -func (impl *decorateKebaUdpPhaseCurrentsImpl) Currents() (float64, float64, float64, error) { - return impl.phaseCurrents() -} diff --git a/charger/kse.go b/charger/kse.go index 5632c340d..8a136a888 100644 --- a/charger/kse.go +++ b/charger/kse.go @@ -23,6 +23,7 @@ import ( "fmt" "github.com/evcc-io/evcc/api" + "github.com/evcc-io/evcc/api/implement" "github.com/evcc-io/evcc/util" "github.com/evcc-io/evcc/util/modbus" "github.com/evcc-io/evcc/util/sponsor" @@ -30,6 +31,7 @@ import ( // KSE charger implementation type KSE struct { + implement.Caps log *util.Logger conn *modbus.Connection curr uint16 @@ -71,8 +73,6 @@ func NewKSEFromConfig(ctx context.Context, other map[string]any) (api.Charger, e return NewKSE(ctx, cc.URI, cc.Device, cc.Comset, cc.Baudrate, cc.ID) } -//go:generate go tool decorate -f decorateKSE -b *KSE -r api.Charger -t api.PhaseSwitcher,api.PhaseGetter,api.Identifier - // NewKSE creates KSE charger func NewKSE(ctx context.Context, uri, device, comset string, baudrate int, slaveID uint8) (api.Charger, error) { conn, err := modbus.NewConnection(ctx, uri, device, comset, baudrate, modbus.Rtu, slaveID) @@ -88,31 +88,26 @@ func NewKSE(ctx context.Context, uri, device, comset string, baudrate int, slave conn.Logger(log.TRACE) wb := &KSE{ + Caps: implement.New(), log: log, conn: conn, curr: 6, // assume min current } - var ( - phases1p3p func(int) error - getPhases func() (int, error) - identify func() (string, error) - ) - // check presence of 1p3p switching - if b, err := wb.conn.ReadInputRegisters(kseRegFirmwareVersion, 1); err == nil && b[0] >= 0x52 { // >= HW Rev „R“ + if b, err := wb.conn.ReadInputRegisters(kseRegFirmwareVersion, 1); err == nil && b[0] >= 0x52 { // >= HW Rev „R” wb.has1p3p = true - phases1p3p = wb.phases1p3p - getPhases = wb.getPhases + implement.Has(wb, implement.PhaseSwitcher(wb.phases1p3p)) + implement.Has(wb, implement.PhaseGetter(wb.getPhases)) } // check presence of rfid if b, err := wb.conn.ReadDiscreteInputs(kseRegRFIDinstalled, 1); err == nil && b[0] != 0 { wb.hasRfid = true - identify = wb.identify + implement.Has(wb, implement.Identifier(wb.identify)) } - return decorateKSE(wb, phases1p3p, getPhases, identify), nil + return wb, nil } // Status implements the api.Charger interface diff --git a/charger/kse_decorators.go b/charger/kse_decorators.go deleted file mode 100644 index ab78ee55e..000000000 --- a/charger/kse_decorators.go +++ /dev/null @@ -1,68 +0,0 @@ -package charger - -// Code generated by github.com/evcc-io/evcc/cmd/tools/decorate.go. DO NOT EDIT. - -import ( - "reflect" - - "github.com/evcc-io/evcc/api" -) - -func decorateKSE(base *KSE, phaseSwitcher func(int) error, phaseGetter func() (int, error), identifier func() (string, error)) api.Charger { - caps := make(map[reflect.Type]any) - - if phaseSwitcher != nil { - caps[reflect.TypeFor[api.PhaseSwitcher]()] = &decorateKSEPhaseSwitcherImpl{phaseSwitcher: phaseSwitcher} - } - - if phaseGetter != nil { - caps[reflect.TypeFor[api.PhaseGetter]()] = &decorateKSEPhaseGetterImpl{phaseGetter: phaseGetter} - } - - if identifier != nil { - caps[reflect.TypeFor[api.Identifier]()] = &decorateKSEIdentifierImpl{identifier: identifier} - } - - if len(caps) == 0 { - return base - } - - return &decorateKSECapable{KSE: base, caps: caps} -} - -type decorateKSECapable struct { - *KSE - caps map[reflect.Type]any -} - -func (d *decorateKSECapable) Capability(typ reflect.Type) (any, bool) { - c, ok := d.caps[typ] - if !ok && reflect.TypeOf(d).Implements(typ) { - return d, true - } - return c, ok -} - -type decorateKSEIdentifierImpl struct { - identifier func() (string, error) -} - -func (impl *decorateKSEIdentifierImpl) Identify() (string, error) { - return impl.identifier() -} - -type decorateKSEPhaseGetterImpl struct { - phaseGetter func() (int, error) -} - -func (impl *decorateKSEPhaseGetterImpl) GetPhases() (int, error) { - return impl.phaseGetter() -} - -type decorateKSEPhaseSwitcherImpl struct { - phaseSwitcher func(int) error -} - -func (impl *decorateKSEPhaseSwitcherImpl) Phases1p3p(p0 int) error { - return impl.phaseSwitcher(p0) -} diff --git a/charger/mennekes-compact.go b/charger/mennekes-compact.go index b0d7d7088..f5e9d5b1a 100644 --- a/charger/mennekes-compact.go +++ b/charger/mennekes-compact.go @@ -25,6 +25,7 @@ import ( "time" "github.com/evcc-io/evcc/api" + "github.com/evcc-io/evcc/api/implement" "github.com/evcc-io/evcc/util" "github.com/evcc-io/evcc/util/modbus" "github.com/evcc-io/evcc/util/sponsor" @@ -33,6 +34,7 @@ import ( // MennekesCompact is an api.Charger implementation type MennekesCompact struct { + implement.Caps log *util.Logger conn *modbus.Connection } @@ -87,8 +89,6 @@ func NewMennekesCompactFromConfig(ctx context.Context, other map[string]any) (ap return NewMennekesCompact(ctx, cc.URI, cc.Device, cc.Comset, cc.Baudrate, cc.Settings.Protocol(), cc.ID, cc.Timeout) } -//go:generate go tool decorate -f decorateMennekesCompact -b *MennekesCompact -r api.Charger -t api.PhaseSwitcher - // NewMennekesCompact creates Mennekes charger func NewMennekesCompact(ctx context.Context, uri, device, comset string, baudrate int, proto modbus.Protocol, slaveID uint8, timeout time.Duration) (api.Charger, error) { conn, err := modbus.NewConnection(ctx, uri, device, comset, baudrate, proto, slaveID) @@ -108,20 +108,20 @@ func NewMennekesCompact(ctx context.Context, uri, device, comset string, baudrat conn.Logger(log.TRACE) wb := &MennekesCompact{ + Caps: implement.New(), log: log, conn: conn, } // check phase switching support - var phasesS func(int) error if b, err := wb.conn.ReadHoldingRegisters(mennekesRegPhaseOptionsHW, 1); err == nil && encoding.Uint16(b) == 2 { - phasesS = wb.phases1p3p + implement.Has(wb, implement.PhaseSwitcher(wb.phases1p3p)) } // failsafe go wb.heartbeat(ctx, mennekesHeartbeatInterval) - return decorateMennekesCompact(wb, phasesS), nil + return wb, nil } func (wb *MennekesCompact) heartbeat(ctx context.Context, timeout time.Duration) { diff --git a/charger/mennekes-compact_decorators.go b/charger/mennekes-compact_decorators.go deleted file mode 100644 index 3da5e4095..000000000 --- a/charger/mennekes-compact_decorators.go +++ /dev/null @@ -1,44 +0,0 @@ -package charger - -// Code generated by github.com/evcc-io/evcc/cmd/tools/decorate.go. DO NOT EDIT. - -import ( - "reflect" - - "github.com/evcc-io/evcc/api" -) - -func decorateMennekesCompact(base *MennekesCompact, phaseSwitcher func(int) error) api.Charger { - caps := make(map[reflect.Type]any) - - if phaseSwitcher != nil { - caps[reflect.TypeFor[api.PhaseSwitcher]()] = &decorateMennekesCompactPhaseSwitcherImpl{phaseSwitcher: phaseSwitcher} - } - - if len(caps) == 0 { - return base - } - - return &decorateMennekesCompactCapable{MennekesCompact: base, caps: caps} -} - -type decorateMennekesCompactCapable struct { - *MennekesCompact - caps map[reflect.Type]any -} - -func (d *decorateMennekesCompactCapable) Capability(typ reflect.Type) (any, bool) { - c, ok := d.caps[typ] - if !ok && reflect.TypeOf(d).Implements(typ) { - return d, true - } - return c, ok -} - -type decorateMennekesCompactPhaseSwitcherImpl struct { - phaseSwitcher func(int) error -} - -func (impl *decorateMennekesCompactPhaseSwitcherImpl) Phases1p3p(p0 int) error { - return impl.phaseSwitcher(p0) -} diff --git a/charger/nexblue_decorators.go b/charger/nexblue_decorators.go deleted file mode 100644 index 1e7db7472..000000000 --- a/charger/nexblue_decorators.go +++ /dev/null @@ -1,35 +0,0 @@ -package charger - -// Code generated by github.com/evcc-io/evcc/cmd/tools/decorate.go. DO NOT EDIT. - -import ( - "github.com/evcc-io/evcc/api" -) - -func decorateNexblue(base *Nexblue, phaseSwitcher func(int) error) api.Charger { - switch { - case phaseSwitcher == nil: - return base - - case phaseSwitcher != nil: - return &struct { - *Nexblue - api.PhaseSwitcher - }{ - Nexblue: base, - PhaseSwitcher: &decorateNexbluePhaseSwitcherImpl{ - phaseSwitcher: phaseSwitcher, - }, - } - } - - return nil -} - -type decorateNexbluePhaseSwitcherImpl struct { - phaseSwitcher func(int) error -} - -func (impl *decorateNexbluePhaseSwitcherImpl) Phases1p3p(p0 int) error { - return impl.phaseSwitcher(p0) -} diff --git a/charger/nrggen2.go b/charger/nrggen2.go index 621010253..21f812207 100644 --- a/charger/nrggen2.go +++ b/charger/nrggen2.go @@ -7,6 +7,7 @@ import ( "math" "github.com/evcc-io/evcc/api" + "github.com/evcc-io/evcc/api/implement" "github.com/evcc-io/evcc/util" "github.com/evcc-io/evcc/util/modbus" "github.com/evcc-io/evcc/util/sponsor" @@ -18,6 +19,7 @@ import ( // NRGKickGen2 charger implementation type NRGKickGen2 struct { + implement.Caps conn *modbus.Connection } @@ -49,8 +51,6 @@ func init() { registry.AddCtx("nrggen2", NewNRGKickGen2FromConfig) } -//go:generate go tool decorate -f decorateNRGKickGen2 -b *NRGKickGen2 -r api.Charger -t api.PhaseSwitcher - // NewNRGKickGen2FromConfig creates a NRGKickGen2 charger from generic config func NewNRGKickGen2FromConfig(ctx context.Context, other map[string]any) (api.Charger, error) { cc := struct { @@ -72,17 +72,16 @@ func NewNRGKickGen2FromConfig(ctx context.Context, other map[string]any) (api.Ch return nil, err } - var phasesS func(int) error if cc.Phases1p3p { // user could have an adapter plug which doesn't support 3 phases if b, err := nrg.conn.ReadHoldingRegisters(nrgKickGen2MaxPhases, 1); err == nil { if maxPhases := encoding.Uint16(b); maxPhases > 1 { - phasesS = nrg.phases1p3p + implement.Has(nrg, implement.PhaseSwitcher(nrg.phases1p3p)) } } } - return decorateNRGKickGen2(nrg, phasesS), nil + return nrg, nil } // NewNRGKickGen2 creates NRGKickGen2 charger @@ -100,6 +99,7 @@ func NewNRGKickGen2(ctx context.Context, uri string, slaveID uint8) (*NRGKickGen conn.Logger(log.TRACE) nrg := &NRGKickGen2{ + Caps: implement.New(), conn: conn, } diff --git a/charger/nrggen2_decorators.go b/charger/nrggen2_decorators.go deleted file mode 100644 index c09c2d9c2..000000000 --- a/charger/nrggen2_decorators.go +++ /dev/null @@ -1,44 +0,0 @@ -package charger - -// Code generated by github.com/evcc-io/evcc/cmd/tools/decorate.go. DO NOT EDIT. - -import ( - "reflect" - - "github.com/evcc-io/evcc/api" -) - -func decorateNRGKickGen2(base *NRGKickGen2, phaseSwitcher func(int) error) api.Charger { - caps := make(map[reflect.Type]any) - - if phaseSwitcher != nil { - caps[reflect.TypeFor[api.PhaseSwitcher]()] = &decorateNRGKickGen2PhaseSwitcherImpl{phaseSwitcher: phaseSwitcher} - } - - if len(caps) == 0 { - return base - } - - return &decorateNRGKickGen2Capable{NRGKickGen2: base, caps: caps} -} - -type decorateNRGKickGen2Capable struct { - *NRGKickGen2 - caps map[reflect.Type]any -} - -func (d *decorateNRGKickGen2Capable) Capability(typ reflect.Type) (any, bool) { - c, ok := d.caps[typ] - if !ok && reflect.TypeOf(d).Implements(typ) { - return d, true - } - return c, ok -} - -type decorateNRGKickGen2PhaseSwitcherImpl struct { - phaseSwitcher func(int) error -} - -func (impl *decorateNRGKickGen2PhaseSwitcherImpl) Phases1p3p(p0 int) error { - return impl.phaseSwitcher(p0) -} diff --git a/charger/ocpp.go b/charger/ocpp.go index d979efcd3..9a1d4932b 100644 --- a/charger/ocpp.go +++ b/charger/ocpp.go @@ -26,6 +26,7 @@ import ( "time" "github.com/evcc-io/evcc/api" + "github.com/evcc-io/evcc/api/implement" "github.com/evcc-io/evcc/charger/ocpp" "github.com/evcc-io/evcc/core/loadpoint" "github.com/evcc-io/evcc/util" @@ -37,6 +38,7 @@ import ( // OCPP charger implementation type OCPP struct { + implement.Caps log *util.Logger cp *ocpp.CP conn *ocpp.Connector @@ -102,46 +104,37 @@ func NewOCPPFromConfig(ctx context.Context, other map[string]any) (api.Charger, return nil, api.ErrSponsorRequired } - var ( - powerG, totalEnergyG, socG func() (float64, error) - currentsG, voltagesG func() (float64, float64, float64, error) - ) - if c.cp.HasMeasurement(types.MeasurandPowerActiveImport) { - powerG = c.conn.CurrentPower + implement.Has(c, implement.Meter(c.conn.CurrentPower)) } if c.cp.HasMeasurement(types.MeasurandEnergyActiveImportRegister) { - totalEnergyG = c.conn.TotalEnergy + implement.Has(c, implement.MeterEnergy(c.conn.TotalEnergy)) } if c.cp.HasMeasurement(types.MeasurandCurrentImport) { - currentsG = c.conn.Currents + implement.Has(c, implement.PhaseCurrents(c.conn.Currents)) } if c.cp.HasMeasurement(types.MeasurandVoltage) { - voltagesG = c.conn.Voltages + implement.Has(c, implement.PhaseVoltages(c.conn.Voltages)) } if c.cp.HasMeasurement(types.MeasurandSoC) { - socG = c.conn.Soc + implement.Has(c, implement.Battery(c.conn.Soc)) } - var phasesS func(int) error if c.cp.PhaseSwitching { - phasesS = c.phases1p3p + implement.Has(c, implement.PhaseSwitcher(c.phases1p3p)) } - var currentG func() (float64, error) if c.cp.HasMeasurement(types.MeasurandCurrentOffered) { - currentG = c.conn.GetMaxCurrent + implement.Has(c, implement.CurrentGetter(c.conn.GetMaxCurrent)) } - return decorateOCPP(c, powerG, totalEnergyG, currentsG, voltagesG, currentG, phasesS, socG), nil + return c, nil } -//go:generate go tool decorate -f decorateOCPP -b *OCPP -r api.Charger -t api.Meter,api.MeterEnergy,api.PhaseCurrents,api.PhaseVoltages,api.CurrentGetter,api.PhaseSwitcher,api.Battery - // NewOCPP creates OCPP charger func NewOCPP(ctx context.Context, id string, connector int, idTag string, @@ -187,6 +180,7 @@ func NewOCPP(ctx context.Context, } c := &OCPP{ + Caps: implement.New(), log: log, cp: cp, conn: conn, diff --git a/charger/ocpp_decorators.go b/charger/ocpp_decorators.go deleted file mode 100644 index cb1a1939b..000000000 --- a/charger/ocpp_decorators.go +++ /dev/null @@ -1,116 +0,0 @@ -package charger - -// Code generated by github.com/evcc-io/evcc/cmd/tools/decorate.go. DO NOT EDIT. - -import ( - "reflect" - - "github.com/evcc-io/evcc/api" -) - -func decorateOCPP(base *OCPP, meter func() (float64, error), meterEnergy func() (float64, error), phaseCurrents func() (float64, float64, float64, error), phaseVoltages func() (float64, float64, float64, error), currentGetter func() (float64, error), phaseSwitcher func(int) error, battery func() (float64, error)) api.Charger { - caps := make(map[reflect.Type]any) - - if meter != nil { - caps[reflect.TypeFor[api.Meter]()] = &decorateOCPPMeterImpl{meter: meter} - } - - if meterEnergy != nil { - caps[reflect.TypeFor[api.MeterEnergy]()] = &decorateOCPPMeterEnergyImpl{meterEnergy: meterEnergy} - } - - if phaseCurrents != nil { - caps[reflect.TypeFor[api.PhaseCurrents]()] = &decorateOCPPPhaseCurrentsImpl{phaseCurrents: phaseCurrents} - } - - if phaseVoltages != nil { - caps[reflect.TypeFor[api.PhaseVoltages]()] = &decorateOCPPPhaseVoltagesImpl{phaseVoltages: phaseVoltages} - } - - if currentGetter != nil { - caps[reflect.TypeFor[api.CurrentGetter]()] = &decorateOCPPCurrentGetterImpl{currentGetter: currentGetter} - } - - if phaseSwitcher != nil { - caps[reflect.TypeFor[api.PhaseSwitcher]()] = &decorateOCPPPhaseSwitcherImpl{phaseSwitcher: phaseSwitcher} - } - - if battery != nil { - caps[reflect.TypeFor[api.Battery]()] = &decorateOCPPBatteryImpl{battery: battery} - } - - if len(caps) == 0 { - return base - } - - return &decorateOCPPCapable{OCPP: base, caps: caps} -} - -type decorateOCPPCapable struct { - *OCPP - caps map[reflect.Type]any -} - -func (d *decorateOCPPCapable) Capability(typ reflect.Type) (any, bool) { - c, ok := d.caps[typ] - if !ok && reflect.TypeOf(d).Implements(typ) { - return d, true - } - return c, ok -} - -type decorateOCPPBatteryImpl struct { - battery func() (float64, error) -} - -func (impl *decorateOCPPBatteryImpl) Soc() (float64, error) { - return impl.battery() -} - -type decorateOCPPCurrentGetterImpl struct { - currentGetter func() (float64, error) -} - -func (impl *decorateOCPPCurrentGetterImpl) GetMaxCurrent() (float64, error) { - return impl.currentGetter() -} - -type decorateOCPPMeterImpl struct { - meter func() (float64, error) -} - -func (impl *decorateOCPPMeterImpl) CurrentPower() (float64, error) { - return impl.meter() -} - -type decorateOCPPMeterEnergyImpl struct { - meterEnergy func() (float64, error) -} - -func (impl *decorateOCPPMeterEnergyImpl) TotalEnergy() (float64, error) { - return impl.meterEnergy() -} - -type decorateOCPPPhaseCurrentsImpl struct { - phaseCurrents func() (float64, float64, float64, error) -} - -func (impl *decorateOCPPPhaseCurrentsImpl) Currents() (float64, float64, float64, error) { - return impl.phaseCurrents() -} - -type decorateOCPPPhaseSwitcherImpl struct { - phaseSwitcher func(int) error -} - -func (impl *decorateOCPPPhaseSwitcherImpl) Phases1p3p(p0 int) error { - return impl.phaseSwitcher(p0) -} - -type decorateOCPPPhaseVoltagesImpl struct { - phaseVoltages func() (float64, float64, float64, error) -} - -func (impl *decorateOCPPPhaseVoltagesImpl) Voltages() (float64, float64, float64, error) { - return impl.phaseVoltages() -} diff --git a/charger/openevse.go b/charger/openevse.go index 8318bbfbf..c2939e9f6 100644 --- a/charger/openevse.go +++ b/charger/openevse.go @@ -9,6 +9,7 @@ import ( "time" "github.com/evcc-io/evcc/api" + "github.com/evcc-io/evcc/api/implement" "github.com/evcc-io/evcc/charger/openevse" "github.com/evcc-io/evcc/util" "github.com/evcc-io/evcc/util/request" @@ -18,6 +19,7 @@ import ( // OpenEVSE charger implementation type OpenEVSE struct { *request.Helper + implement.Caps uri string statusG util.Cacheable[openevse.Status] current int @@ -28,8 +30,6 @@ func init() { registry.Add("openevse", NewOpenEVSEFromConfig) } -//go:generate go tool decorate -f decorateOpenEVSE -b *OpenEVSE -r api.Charger -t api.PhaseSwitcher - // NewOpenEVSEFromConfig creates an OpenEVSE charger from generic config func NewOpenEVSEFromConfig(other map[string]any) (api.Charger, error) { cc := struct { @@ -59,6 +59,7 @@ func NewOpenEVSE(uri, user, password string, cache time.Duration) (api.Charger, c := &OpenEVSE{ Helper: request.NewHelper(log), + Caps: implement.New(), uri: util.DefaultScheme(strings.TrimSuffix(uri, "/"), "http"), } @@ -75,9 +76,8 @@ func NewOpenEVSE(uri, user, password string, cache time.Duration) (api.Charger, return res, err }, cache) - var phases1p3p func(int) error if err := c.hasPhaseSwitchCapabilities(); err == nil { - phases1p3p = c.phases1p3p + implement.Has(c, implement.PhaseSwitcher(c.phases1p3p)) // disable EVSE's own 1/3-phase auto-switching if err := c.rapiCommand("$S8 0"); err != nil { @@ -85,7 +85,7 @@ func NewOpenEVSE(uri, user, password string, cache time.Duration) (api.Charger, } } - return decorateOpenEVSE(c, phases1p3p), nil + return c, nil } func (c *OpenEVSE) setOverride() error { diff --git a/charger/openevse_decorators.go b/charger/openevse_decorators.go deleted file mode 100644 index 7e76f0dea..000000000 --- a/charger/openevse_decorators.go +++ /dev/null @@ -1,44 +0,0 @@ -package charger - -// Code generated by github.com/evcc-io/evcc/cmd/tools/decorate.go. DO NOT EDIT. - -import ( - "reflect" - - "github.com/evcc-io/evcc/api" -) - -func decorateOpenEVSE(base *OpenEVSE, phaseSwitcher func(int) error) api.Charger { - caps := make(map[reflect.Type]any) - - if phaseSwitcher != nil { - caps[reflect.TypeFor[api.PhaseSwitcher]()] = &decorateOpenEVSEPhaseSwitcherImpl{phaseSwitcher: phaseSwitcher} - } - - if len(caps) == 0 { - return base - } - - return &decorateOpenEVSECapable{OpenEVSE: base, caps: caps} -} - -type decorateOpenEVSECapable struct { - *OpenEVSE - caps map[reflect.Type]any -} - -func (d *decorateOpenEVSECapable) Capability(typ reflect.Type) (any, bool) { - c, ok := d.caps[typ] - if !ok && reflect.TypeOf(d).Implements(typ) { - return d, true - } - return c, ok -} - -type decorateOpenEVSEPhaseSwitcherImpl struct { - phaseSwitcher func(int) error -} - -func (impl *decorateOpenEVSEPhaseSwitcherImpl) Phases1p3p(p0 int) error { - return impl.phaseSwitcher(p0) -} diff --git a/charger/openwb-2.0.go b/charger/openwb-2.0.go index 7775d1f87..19dcbb107 100644 --- a/charger/openwb-2.0.go +++ b/charger/openwb-2.0.go @@ -6,12 +6,14 @@ import ( "fmt" "github.com/evcc-io/evcc/api" + "github.com/evcc-io/evcc/api/implement" "github.com/evcc-io/evcc/util" "github.com/evcc-io/evcc/util/modbus" ) // OpenWB20 charger implementation type OpenWB20 struct { + implement.Caps conn *modbus.Connection enabled bool curr uint16 @@ -41,8 +43,6 @@ func init() { // https://openwb.de/main/wp-content/uploads/2023/10/ModbusTCP-openWB-series2-Pro-1.pdf -//go:generate go tool decorate -f decorateOpenWB20 -b *OpenWB20 -r api.Charger -t api.PhaseSwitcher,api.Identifier - // NewOpenWB20FromConfig creates a OpenWB20 charger from generic config func NewOpenWB20FromConfig(ctx context.Context, other map[string]any) (api.Charger, error) { cc := struct { @@ -66,17 +66,15 @@ func NewOpenWB20FromConfig(ctx context.Context, other map[string]any) (api.Charg return nil, err } - var phases1p3p func(int) error if cc.Phases1p3p { - phases1p3p = wb.phases1p3p + implement.Has(wb, implement.PhaseSwitcher(wb.phases1p3p)) } - var identify func() (string, error) if cc.Identify { - identify = wb.identify + implement.Has(wb, implement.Identifier(wb.identify)) } - return decorateOpenWB20(wb, phases1p3p, identify), nil + return wb, nil } // NewOpenWB20 creates OpenWB20 charger @@ -92,6 +90,7 @@ func NewOpenWB20(ctx context.Context, uri string, slaveID uint8, connector uint1 conn.Logger(log.TRACE) wb := &OpenWB20{ + Caps: implement.New(), conn: conn, curr: 6 * 100, base: (connector - 1) * 100, diff --git a/charger/openwb-2.0_decorators.go b/charger/openwb-2.0_decorators.go deleted file mode 100644 index b990a7062..000000000 --- a/charger/openwb-2.0_decorators.go +++ /dev/null @@ -1,56 +0,0 @@ -package charger - -// Code generated by github.com/evcc-io/evcc/cmd/tools/decorate.go. DO NOT EDIT. - -import ( - "reflect" - - "github.com/evcc-io/evcc/api" -) - -func decorateOpenWB20(base *OpenWB20, phaseSwitcher func(int) error, identifier func() (string, error)) api.Charger { - caps := make(map[reflect.Type]any) - - if phaseSwitcher != nil { - caps[reflect.TypeFor[api.PhaseSwitcher]()] = &decorateOpenWB20PhaseSwitcherImpl{phaseSwitcher: phaseSwitcher} - } - - if identifier != nil { - caps[reflect.TypeFor[api.Identifier]()] = &decorateOpenWB20IdentifierImpl{identifier: identifier} - } - - if len(caps) == 0 { - return base - } - - return &decorateOpenWB20Capable{OpenWB20: base, caps: caps} -} - -type decorateOpenWB20Capable struct { - *OpenWB20 - caps map[reflect.Type]any -} - -func (d *decorateOpenWB20Capable) Capability(typ reflect.Type) (any, bool) { - c, ok := d.caps[typ] - if !ok && reflect.TypeOf(d).Implements(typ) { - return d, true - } - return c, ok -} - -type decorateOpenWB20IdentifierImpl struct { - identifier func() (string, error) -} - -func (impl *decorateOpenWB20IdentifierImpl) Identify() (string, error) { - return impl.identifier() -} - -type decorateOpenWB20PhaseSwitcherImpl struct { - phaseSwitcher func(int) error -} - -func (impl *decorateOpenWB20PhaseSwitcherImpl) Phases1p3p(p0 int) error { - return impl.phaseSwitcher(p0) -} diff --git a/charger/openwb-native_decorators_linux.go b/charger/openwb-native_decorators_linux.go deleted file mode 100644 index b714e329f..000000000 --- a/charger/openwb-native_decorators_linux.go +++ /dev/null @@ -1,68 +0,0 @@ -package charger - -// Code generated by github.com/evcc-io/evcc/cmd/tools/decorate.go. DO NOT EDIT. - -import ( - "reflect" - - "github.com/evcc-io/evcc/api" -) - -func decorateOpenWbNative(base *OpenWbNative, chargerEx func(float64) error, phaseSwitcher func(int) error, identifier func() (string, error)) api.Charger { - caps := make(map[reflect.Type]any) - - if chargerEx != nil { - caps[reflect.TypeFor[api.ChargerEx]()] = &decorateOpenWbNativeChargerExImpl{chargerEx: chargerEx} - } - - if phaseSwitcher != nil { - caps[reflect.TypeFor[api.PhaseSwitcher]()] = &decorateOpenWbNativePhaseSwitcherImpl{phaseSwitcher: phaseSwitcher} - } - - if identifier != nil { - caps[reflect.TypeFor[api.Identifier]()] = &decorateOpenWbNativeIdentifierImpl{identifier: identifier} - } - - if len(caps) == 0 { - return base - } - - return &decorateOpenWbNativeCapable{OpenWbNative: base, caps: caps} -} - -type decorateOpenWbNativeCapable struct { - *OpenWbNative - caps map[reflect.Type]any -} - -func (d *decorateOpenWbNativeCapable) Capability(typ reflect.Type) (any, bool) { - c, ok := d.caps[typ] - if !ok && reflect.TypeOf(d).Implements(typ) { - return d, true - } - return c, ok -} - -type decorateOpenWbNativeChargerExImpl struct { - chargerEx func(float64) error -} - -func (impl *decorateOpenWbNativeChargerExImpl) MaxCurrentMillis(p0 float64) error { - return impl.chargerEx(p0) -} - -type decorateOpenWbNativeIdentifierImpl struct { - identifier func() (string, error) -} - -func (impl *decorateOpenWbNativeIdentifierImpl) Identify() (string, error) { - return impl.identifier() -} - -type decorateOpenWbNativePhaseSwitcherImpl struct { - phaseSwitcher func(int) error -} - -func (impl *decorateOpenWbNativePhaseSwitcherImpl) Phases1p3p(p0 int) error { - return impl.phaseSwitcher(p0) -} diff --git a/charger/openwb-native_linux.go b/charger/openwb-native_linux.go index 03fe3b6d2..8e36d98da 100644 --- a/charger/openwb-native_linux.go +++ b/charger/openwb-native_linux.go @@ -6,6 +6,7 @@ import ( "time" "github.com/evcc-io/evcc/api" + "github.com/evcc-io/evcc/api/implement" "github.com/evcc-io/evcc/charger/openwb/native" "github.com/evcc-io/evcc/util" "github.com/evcc-io/evcc/util/modbus" @@ -24,6 +25,7 @@ type openWbGpioLines struct { // OpenWbNative charger implementation type OpenWbNative struct { api.Charger + implement.Caps log *util.Logger rfId native.RfIdContainer cpWait time.Duration @@ -42,8 +44,6 @@ func init() { registry.AddCtx("openwb-native", NewOpenWbNativeFromConfig) } -//go:generate go tool decorate -o openwb-native_decorators_linux.go -f decorateOpenWbNative -b *OpenWbNative -r api.Charger -t api.ChargerEx,api.PhaseSwitcher,api.Identifier - // NewOpenWbNativeFromConfig creates an OpenWbNative charger from generic config func NewOpenWbNativeFromConfig(ctx context.Context, other map[string]any) (api.Charger, error) { cc := struct { @@ -87,25 +87,20 @@ func NewOpenWbNative(ctx context.Context, uri, device, comset string, baudrate i wb := &OpenWbNative{ Charger: evse, + Caps: implement.New(), log: log, cpWait: cpWait, connector: connector, chargeState: api.StatusNone, } - var ( - phases1p3p func(int) error - identify func() (string, error) - maxCurrentMillis func(float64) error - ) - if ex, ok := api.Cap[api.ChargerEx](evse); ok { - maxCurrentMillis = ex.MaxCurrentMillis + implement.May(wb, implement.ChargerEx(ex.MaxCurrentMillis)) } // configure special external hardware features if hasPhases1p3p { - phases1p3p = wb.phases1p3p + implement.May(wb, implement.PhaseSwitcher(wb.phases1p3p)) } if rfIdVidPid != "" { @@ -113,8 +108,7 @@ func NewOpenWbNative(ctx context.Context, uri, device, comset string, baudrate i if err != nil { return nil, err } - - identify = wb.identify + implement.May(wb, implement.Identifier(wb.identify)) } // initialize GPIO lines and set pins to output @@ -141,7 +135,7 @@ func NewOpenWbNative(ctx context.Context, uri, device, comset string, baudrate i wb.gpio.ph3.Close() }() - return decorateOpenWbNative(wb, maxCurrentMillis, phases1p3p, identify), nil + return wb, nil } // Status implements the api.Charger interface diff --git a/charger/openwb-pro.go b/charger/openwb-pro.go index 13f5dcbbd..5844064e0 100644 --- a/charger/openwb-pro.go +++ b/charger/openwb-pro.go @@ -7,6 +7,7 @@ import ( "time" "github.com/evcc-io/evcc/api" + "github.com/evcc-io/evcc/api/implement" "github.com/evcc-io/evcc/charger/openwb/pro" "github.com/evcc-io/evcc/util" "github.com/evcc-io/evcc/util/request" @@ -16,12 +17,11 @@ func init() { registry.AddCtx("openwbpro", NewOpenWBProFromConfig) } -//go:generate go tool decorate -f decorateOpenWBPro -b *OpenWBPro -r api.Charger -t api.Resurrector - // https://openwb.de/main/?page_id=771 // OpenWBPro charger implementation type OpenWBPro struct { + implement.Caps *request.Helper uri string current float64 @@ -51,12 +51,11 @@ func NewOpenWBProFromConfig(ctx context.Context, other map[string]any) (api.Char return nil, err } - var wakeup func() error if status.Version >= 9 { - wakeup = wb.wakeup + implement.Has(wb, implement.Resurrector(wb.wakeup)) } - return decorateOpenWBPro(wb, wakeup), nil + return wb, nil } // NewOpenWBPro creates OpenWBPro charger @@ -64,6 +63,7 @@ func NewOpenWBPro(ctx context.Context, uri string, cache time.Duration) (*OpenWB log := util.NewLogger("owbpro") wb := &OpenWBPro{ + Caps: implement.New(), Helper: request.NewHelper(log), uri: strings.TrimRight(uri, "/"), current: 6, // 6A defined value diff --git a/charger/openwb-pro_decorators.go b/charger/openwb-pro_decorators.go deleted file mode 100644 index 41b9e4c90..000000000 --- a/charger/openwb-pro_decorators.go +++ /dev/null @@ -1,44 +0,0 @@ -package charger - -// Code generated by github.com/evcc-io/evcc/cmd/tools/decorate.go. DO NOT EDIT. - -import ( - "reflect" - - "github.com/evcc-io/evcc/api" -) - -func decorateOpenWBPro(base *OpenWBPro, resurrector func() error) api.Charger { - caps := make(map[reflect.Type]any) - - if resurrector != nil { - caps[reflect.TypeFor[api.Resurrector]()] = &decorateOpenWBProResurrectorImpl{resurrector: resurrector} - } - - if len(caps) == 0 { - return base - } - - return &decorateOpenWBProCapable{OpenWBPro: base, caps: caps} -} - -type decorateOpenWBProCapable struct { - *OpenWBPro - caps map[reflect.Type]any -} - -func (d *decorateOpenWBProCapable) Capability(typ reflect.Type) (any, bool) { - c, ok := d.caps[typ] - if !ok && reflect.TypeOf(d).Implements(typ) { - return d, true - } - return c, ok -} - -type decorateOpenWBProResurrectorImpl struct { - resurrector func() error -} - -func (impl *decorateOpenWBProResurrectorImpl) WakeUp() error { - return impl.resurrector() -} diff --git a/charger/openwb.go b/charger/openwb.go index 8da1b2459..d8fe23b80 100644 --- a/charger/openwb.go +++ b/charger/openwb.go @@ -7,6 +7,7 @@ import ( "time" "github.com/evcc-io/evcc/api" + "github.com/evcc-io/evcc/api/implement" "github.com/evcc-io/evcc/charger/openwb" "github.com/evcc-io/evcc/plugin" "github.com/evcc-io/evcc/plugin/mqtt" @@ -19,6 +20,7 @@ func init() { // OpenWB configures generic charger and charge meter for an openWB loadpoint type OpenWB struct { + implement.Caps current int64 enabled bool statusG func() (string, error) @@ -30,8 +32,6 @@ type OpenWB struct { authS func(string) error } -//go:generate go tool decorate -f decorateOpenWB -b *OpenWB -r api.Charger -t api.PhaseSwitcher,api.Battery - // NewOpenWBFromConfig creates a new configurable charger func NewOpenWBFromConfig(other map[string]any) (api.Charger, error) { cc := struct { @@ -145,6 +145,7 @@ func NewOpenWB(log *util.Logger, mqttconf mqtt.Config, id int, topic string, p1p } c := &OpenWB{ + Caps: implement.New(), currentS: currentS, statusG: statusG, currentPowerG: currentPowerG, @@ -171,7 +172,6 @@ func NewOpenWB(log *util.Logger, mqttconf mqtt.Config, id int, topic string, p1p // optional capabilities - var phases func(int) error if p1p3 { phasesTopic := openwb.SlavePhasesTopic if id == 2 { @@ -184,20 +184,20 @@ func NewOpenWB(log *util.Logger, mqttconf mqtt.Config, id int, topic string, p1p return nil, err } - phases = func(phases int) error { + implement.Has(c, implement.PhaseSwitcher(func(phases int) error { return phasesS(int64(phases)) - } + })) } - var soc func() (float64, error) if dc { - soc, err = to.FloatGetter(mq(openwb.VehicleSocTopic)) + soc, err := to.FloatGetter(mq(openwb.VehicleSocTopic)) if err != nil { return nil, err } + implement.Has(c, implement.Battery(soc)) } - return decorateOpenWB(c, phases, soc), nil + return c, nil } func (m *OpenWB) Enable(enable bool) error { diff --git a/charger/openwb_decorators.go b/charger/openwb_decorators.go deleted file mode 100644 index a88cac499..000000000 --- a/charger/openwb_decorators.go +++ /dev/null @@ -1,56 +0,0 @@ -package charger - -// Code generated by github.com/evcc-io/evcc/cmd/tools/decorate.go. DO NOT EDIT. - -import ( - "reflect" - - "github.com/evcc-io/evcc/api" -) - -func decorateOpenWB(base *OpenWB, phaseSwitcher func(int) error, battery func() (float64, error)) api.Charger { - caps := make(map[reflect.Type]any) - - if phaseSwitcher != nil { - caps[reflect.TypeFor[api.PhaseSwitcher]()] = &decorateOpenWBPhaseSwitcherImpl{phaseSwitcher: phaseSwitcher} - } - - if battery != nil { - caps[reflect.TypeFor[api.Battery]()] = &decorateOpenWBBatteryImpl{battery: battery} - } - - if len(caps) == 0 { - return base - } - - return &decorateOpenWBCapable{OpenWB: base, caps: caps} -} - -type decorateOpenWBCapable struct { - *OpenWB - caps map[reflect.Type]any -} - -func (d *decorateOpenWBCapable) Capability(typ reflect.Type) (any, bool) { - c, ok := d.caps[typ] - if !ok && reflect.TypeOf(d).Implements(typ) { - return d, true - } - return c, ok -} - -type decorateOpenWBBatteryImpl struct { - battery func() (float64, error) -} - -func (impl *decorateOpenWBBatteryImpl) Soc() (float64, error) { - return impl.battery() -} - -type decorateOpenWBPhaseSwitcherImpl struct { - phaseSwitcher func(int) error -} - -func (impl *decorateOpenWBPhaseSwitcherImpl) Phases1p3p(p0 int) error { - return impl.phaseSwitcher(p0) -} diff --git a/charger/pcelectric.go b/charger/pcelectric.go index 040b0a391..b09761359 100644 --- a/charger/pcelectric.go +++ b/charger/pcelectric.go @@ -6,6 +6,7 @@ import ( "strings" "github.com/evcc-io/evcc/api" + "github.com/evcc-io/evcc/api/implement" "github.com/evcc-io/evcc/charger/pcelectric" "github.com/evcc-io/evcc/util" "github.com/evcc-io/evcc/util/request" @@ -14,6 +15,7 @@ import ( // PCElectric charger implementation type PCElectric struct { + implement.Caps *request.Helper log *util.Logger @@ -30,8 +32,6 @@ func init() { registry.Add("pcelectric", NewPCElectricFromConfig) } -//go:generate go tool decorate -f decoratePCE -b *PCElectric -r api.Charger -t api.Meter,api.MeterEnergy,api.PhaseCurrents - // NewPCElectricFromConfig creates a PCElectric charger from generic config func NewPCElectricFromConfig(other map[string]any) (api.Charger, error) { cc := struct { @@ -50,7 +50,10 @@ func NewPCElectricFromConfig(other map[string]any) (api.Charger, error) { if err == nil && wb.slaveIndex == 0 { // Nur Master hat den Zähler...leider var res pcelectric.MeterInfo if err := wb.GetJSON(wb.meter, &res); err == nil && res.MeterSerial != "" { - return decoratePCE(wb, wb.currentPower, wb.totalEnergy, wb.currents), nil + implement.Has(wb, implement.Meter(wb.currentPower)) + implement.Has(wb, implement.MeterEnergy(wb.totalEnergy)) + implement.Has(wb, implement.PhaseCurrents(wb.currents)) + return wb, nil } wb.meter = "" @@ -69,6 +72,7 @@ func NewPCElectric(uri string, slaveIndex int, meter string) (*PCElectric, error } wb := &PCElectric{ + Caps: implement.New(), Helper: request.NewHelper(log), log: log, uri: uri, diff --git a/charger/pcelectric_decorators.go b/charger/pcelectric_decorators.go deleted file mode 100644 index e51d5e2bd..000000000 --- a/charger/pcelectric_decorators.go +++ /dev/null @@ -1,68 +0,0 @@ -package charger - -// Code generated by github.com/evcc-io/evcc/cmd/tools/decorate.go. DO NOT EDIT. - -import ( - "reflect" - - "github.com/evcc-io/evcc/api" -) - -func decoratePCE(base *PCElectric, meter func() (float64, error), meterEnergy func() (float64, error), phaseCurrents func() (float64, float64, float64, error)) api.Charger { - caps := make(map[reflect.Type]any) - - if meter != nil { - caps[reflect.TypeFor[api.Meter]()] = &decoratePCEMeterImpl{meter: meter} - } - - if meterEnergy != nil { - caps[reflect.TypeFor[api.MeterEnergy]()] = &decoratePCEMeterEnergyImpl{meterEnergy: meterEnergy} - } - - if phaseCurrents != nil { - caps[reflect.TypeFor[api.PhaseCurrents]()] = &decoratePCEPhaseCurrentsImpl{phaseCurrents: phaseCurrents} - } - - if len(caps) == 0 { - return base - } - - return &decoratePCECapable{PCElectric: base, caps: caps} -} - -type decoratePCECapable struct { - *PCElectric - caps map[reflect.Type]any -} - -func (d *decoratePCECapable) Capability(typ reflect.Type) (any, bool) { - c, ok := d.caps[typ] - if !ok && reflect.TypeOf(d).Implements(typ) { - return d, true - } - return c, ok -} - -type decoratePCEMeterImpl struct { - meter func() (float64, error) -} - -func (impl *decoratePCEMeterImpl) CurrentPower() (float64, error) { - return impl.meter() -} - -type decoratePCEMeterEnergyImpl struct { - meterEnergy func() (float64, error) -} - -func (impl *decoratePCEMeterEnergyImpl) TotalEnergy() (float64, error) { - return impl.meterEnergy() -} - -type decoratePCEPhaseCurrentsImpl struct { - phaseCurrents func() (float64, float64, float64, error) -} - -func (impl *decoratePCEPhaseCurrentsImpl) Currents() (float64, float64, float64, error) { - return impl.phaseCurrents() -} diff --git a/charger/peblar.go b/charger/peblar.go index dd3d16cc4..d406f378e 100644 --- a/charger/peblar.go +++ b/charger/peblar.go @@ -25,6 +25,7 @@ import ( "fmt" "github.com/evcc-io/evcc/api" + "github.com/evcc-io/evcc/api/implement" "github.com/evcc-io/evcc/util" "github.com/evcc-io/evcc/util/modbus" "github.com/evcc-io/evcc/util/sponsor" @@ -33,6 +34,7 @@ import ( // Peblar charger implementation type Peblar struct { + implement.Caps conn *modbus.Connection curr uint32 enabled bool @@ -70,8 +72,6 @@ func init() { registry.AddCtx("peblar", NewPeblarFromConfig) } -//go:generate go tool decorate -f decoratePeblar -b *Peblar -r api.Charger -t api.PhaseSwitcher,api.PhaseGetter - // NewPeblarFromConfig creates a Peblar charger from generic config func NewPeblarFromConfig(ctx context.Context, other map[string]any) (api.Charger, error) { cc := modbus.TcpSettings{ @@ -103,6 +103,7 @@ func NewPeblar(ctx context.Context, uri string, id uint8) (api.Charger, error) { } wb := &Peblar{ + Caps: implement.New(), conn: conn, curr: 6000, // assume min current phases: binary.BigEndian.Uint16(b), // required for retrieving the right amount of voltage/current registers @@ -113,15 +114,12 @@ func NewPeblar(ctx context.Context, uri string, id uint8) (api.Charger, error) { return nil, err } - var phasesS func(int) error - var phasesG func() (int, error) - if binary.BigEndian.Uint16(b) == 1 { - phasesS = wb.phases1p3p - phasesG = wb.getPhases + implement.Has(wb, implement.PhaseSwitcher(wb.phases1p3p)) + implement.Has(wb, implement.PhaseGetter(wb.getPhases)) } - return decoratePeblar(wb, phasesS, phasesG), nil + return wb, nil } // Status implements the api.Charger interface diff --git a/charger/peblar_decorators.go b/charger/peblar_decorators.go deleted file mode 100644 index 4146b7fe7..000000000 --- a/charger/peblar_decorators.go +++ /dev/null @@ -1,56 +0,0 @@ -package charger - -// Code generated by github.com/evcc-io/evcc/cmd/tools/decorate.go. DO NOT EDIT. - -import ( - "reflect" - - "github.com/evcc-io/evcc/api" -) - -func decoratePeblar(base *Peblar, phaseSwitcher func(int) error, phaseGetter func() (int, error)) api.Charger { - caps := make(map[reflect.Type]any) - - if phaseSwitcher != nil { - caps[reflect.TypeFor[api.PhaseSwitcher]()] = &decoratePeblarPhaseSwitcherImpl{phaseSwitcher: phaseSwitcher} - } - - if phaseGetter != nil { - caps[reflect.TypeFor[api.PhaseGetter]()] = &decoratePeblarPhaseGetterImpl{phaseGetter: phaseGetter} - } - - if len(caps) == 0 { - return base - } - - return &decoratePeblarCapable{Peblar: base, caps: caps} -} - -type decoratePeblarCapable struct { - *Peblar - caps map[reflect.Type]any -} - -func (d *decoratePeblarCapable) Capability(typ reflect.Type) (any, bool) { - c, ok := d.caps[typ] - if !ok && reflect.TypeOf(d).Implements(typ) { - return d, true - } - return c, ok -} - -type decoratePeblarPhaseGetterImpl struct { - phaseGetter func() (int, error) -} - -func (impl *decoratePeblarPhaseGetterImpl) GetPhases() (int, error) { - return impl.phaseGetter() -} - -type decoratePeblarPhaseSwitcherImpl struct { - phaseSwitcher func(int) error -} - -func (impl *decoratePeblarPhaseSwitcherImpl) Phases1p3p(p0 int) error { - return impl.phaseSwitcher(p0) -} diff --git a/charger/phoenix-charx.go b/charger/phoenix-charx.go index 9ef698f6c..26151e711 100644 --- a/charger/phoenix-charx.go +++ b/charger/phoenix-charx.go @@ -6,6 +6,7 @@ import ( "time" "github.com/evcc-io/evcc/api" + "github.com/evcc-io/evcc/api/implement" "github.com/evcc-io/evcc/util" "github.com/evcc-io/evcc/util/modbus" "github.com/volkszaehler/mbmd/encoding" @@ -38,6 +39,7 @@ const ( // PhoenixCharx is an api.Charger implementation for Phoenix CHARX controller type PhoenixCharx struct { + implement.Caps conn *modbus.Connection connector uint16 current uint16 @@ -47,8 +49,6 @@ func init() { registry.AddCtx("phoenix-charx", NewPhoenixCharxFromConfig) } -//go:generate go tool decorate -f decoratePhoenixCharx -b *PhoenixCharx -r api.Charger -t api.Meter,api.MeterEnergy,api.PhaseCurrents,api.PhaseVoltages - // NewPhoenixCharxFromConfig creates a Phoenix charger from generic config func NewPhoenixCharxFromConfig(ctx context.Context, other map[string]any) (api.Charger, error) { cc := struct { @@ -76,7 +76,10 @@ func NewPhoenixCharxFromConfig(ctx context.Context, other map[string]any) (api.C } if meter > 0 && meter != 65535 { - return decoratePhoenixCharx(wb, wb.currentPower, wb.totalEnergy, wb.currents, wb.voltages), nil + implement.Has(wb, implement.Meter(wb.currentPower)) + implement.Has(wb, implement.MeterEnergy(wb.totalEnergy)) + implement.Has(wb, implement.PhaseCurrents(wb.currents)) + implement.Has(wb, implement.PhaseVoltages(wb.voltages)) } return wb, nil @@ -93,6 +96,7 @@ func NewPhoenixCharx(ctx context.Context, uri string, id uint8, connector uint16 conn.Logger(log.TRACE) wb := &PhoenixCharx{ + Caps: implement.New(), conn: conn, connector: connector, current: 6, // assume min current diff --git a/charger/phoenix-charx_decorators.go b/charger/phoenix-charx_decorators.go deleted file mode 100644 index c36619a10..000000000 --- a/charger/phoenix-charx_decorators.go +++ /dev/null @@ -1,80 +0,0 @@ -package charger - -// Code generated by github.com/evcc-io/evcc/cmd/tools/decorate.go. DO NOT EDIT. - -import ( - "reflect" - - "github.com/evcc-io/evcc/api" -) - -func decoratePhoenixCharx(base *PhoenixCharx, meter func() (float64, error), meterEnergy func() (float64, error), phaseCurrents func() (float64, float64, float64, error), phaseVoltages func() (float64, float64, float64, error)) api.Charger { - caps := make(map[reflect.Type]any) - - if meter != nil { - caps[reflect.TypeFor[api.Meter]()] = &decoratePhoenixCharxMeterImpl{meter: meter} - } - - if meterEnergy != nil { - caps[reflect.TypeFor[api.MeterEnergy]()] = &decoratePhoenixCharxMeterEnergyImpl{meterEnergy: meterEnergy} - } - - if phaseCurrents != nil { - caps[reflect.TypeFor[api.PhaseCurrents]()] = &decoratePhoenixCharxPhaseCurrentsImpl{phaseCurrents: phaseCurrents} - } - - if phaseVoltages != nil { - caps[reflect.TypeFor[api.PhaseVoltages]()] = &decoratePhoenixCharxPhaseVoltagesImpl{phaseVoltages: phaseVoltages} - } - - if len(caps) == 0 { - return base - } - - return &decoratePhoenixCharxCapable{PhoenixCharx: base, caps: caps} -} - -type decoratePhoenixCharxCapable struct { - *PhoenixCharx - caps map[reflect.Type]any -} - -func (d *decoratePhoenixCharxCapable) Capability(typ reflect.Type) (any, bool) { - c, ok := d.caps[typ] - if !ok && reflect.TypeOf(d).Implements(typ) { - return d, true - } - return c, ok -} - -type decoratePhoenixCharxMeterImpl struct { - meter func() (float64, error) -} - -func (impl *decoratePhoenixCharxMeterImpl) CurrentPower() (float64, error) { - return impl.meter() -} - -type decoratePhoenixCharxMeterEnergyImpl struct { - meterEnergy func() (float64, error) -} - -func (impl *decoratePhoenixCharxMeterEnergyImpl) TotalEnergy() (float64, error) { - return impl.meterEnergy() -} - -type decoratePhoenixCharxPhaseCurrentsImpl struct { - phaseCurrents func() (float64, float64, float64, error) -} - -func (impl *decoratePhoenixCharxPhaseCurrentsImpl) Currents() (float64, float64, float64, error) { - return impl.phaseCurrents() -} - -type decoratePhoenixCharxPhaseVoltagesImpl struct { - phaseVoltages func() (float64, float64, float64, error) -} - -func (impl *decoratePhoenixCharxPhaseVoltagesImpl) Voltages() (float64, float64, float64, error) { - return impl.phaseVoltages() -} diff --git a/charger/phoenix-em-eth.go b/charger/phoenix-em-eth.go index 8f7a200b6..8ef5de97a 100644 --- a/charger/phoenix-em-eth.go +++ b/charger/phoenix-em-eth.go @@ -6,6 +6,7 @@ import ( "time" "github.com/evcc-io/evcc/api" + "github.com/evcc-io/evcc/api/implement" "github.com/evcc-io/evcc/util" "github.com/evcc-io/evcc/util/modbus" "github.com/volkszaehler/mbmd/encoding" @@ -27,6 +28,7 @@ const ( // PhoenixEMEth is an api.Charger implementation for Phoenix EM-CP-PP-ETH wallboxes. // It uses Modbus TCP to communicate with the wallbox at modbus client id 180. type PhoenixEMEth struct { + implement.Caps conn *modbus.Connection } @@ -34,8 +36,6 @@ func init() { registry.AddCtx("phoenix-em-eth", NewPhoenixEMEthFromConfig) } -//go:generate go tool decorate -f decoratePhoenixEMEth -b *PhoenixEMEth -r api.Charger -t api.Meter,api.MeterEnergy,api.PhaseCurrents,api.PhaseVoltages - // NewPhoenixEMEthFromConfig creates a Phoenix charger from generic config func NewPhoenixEMEthFromConfig(ctx context.Context, other map[string]any) (api.Charger, error) { cc := modbus.TcpSettings{ @@ -51,22 +51,15 @@ func NewPhoenixEMEthFromConfig(ctx context.Context, other map[string]any) (api.C return nil, err } - var ( - currentPower func() (float64, error) - totalEnergy func() (float64, error) - currents func() (float64, float64, float64, error) - voltages func() (float64, float64, float64, error) - ) - // check presence of meter by voltage on l1 if b, err := wb.conn.ReadInputRegisters(phxEMEthRegVoltages, 2); err == nil && encoding.Int32LswFirst(b) > 0 { - currentPower = wb.currentPower - totalEnergy = wb.totalEnergy - currents = wb.currents - voltages = wb.voltages + implement.Has(wb, implement.Meter(wb.currentPower)) + implement.Has(wb, implement.MeterEnergy(wb.totalEnergy)) + implement.Has(wb, implement.PhaseCurrents(wb.currents)) + implement.Has(wb, implement.PhaseVoltages(wb.voltages)) } - return decoratePhoenixEMEth(wb, currentPower, totalEnergy, currents, voltages), nil + return wb, nil } // NewPhoenixEMEth creates a Phoenix charger @@ -80,6 +73,7 @@ func NewPhoenixEMEth(ctx context.Context, uri string, slaveID uint8) (*PhoenixEM conn.Logger(log.TRACE) wb := &PhoenixEMEth{ + Caps: implement.New(), conn: conn, } diff --git a/charger/phoenix-em-eth_decorators.go b/charger/phoenix-em-eth_decorators.go deleted file mode 100644 index 780a9c36e..000000000 --- a/charger/phoenix-em-eth_decorators.go +++ /dev/null @@ -1,80 +0,0 @@ -package charger - -// Code generated by github.com/evcc-io/evcc/cmd/tools/decorate.go. DO NOT EDIT. - -import ( - "reflect" - - "github.com/evcc-io/evcc/api" -) - -func decoratePhoenixEMEth(base *PhoenixEMEth, meter func() (float64, error), meterEnergy func() (float64, error), phaseCurrents func() (float64, float64, float64, error), phaseVoltages func() (float64, float64, float64, error)) api.Charger { - caps := make(map[reflect.Type]any) - - if meter != nil { - caps[reflect.TypeFor[api.Meter]()] = &decoratePhoenixEMEthMeterImpl{meter: meter} - } - - if meterEnergy != nil { - caps[reflect.TypeFor[api.MeterEnergy]()] = &decoratePhoenixEMEthMeterEnergyImpl{meterEnergy: meterEnergy} - } - - if phaseCurrents != nil { - caps[reflect.TypeFor[api.PhaseCurrents]()] = &decoratePhoenixEMEthPhaseCurrentsImpl{phaseCurrents: phaseCurrents} - } - - if phaseVoltages != nil { - caps[reflect.TypeFor[api.PhaseVoltages]()] = &decoratePhoenixEMEthPhaseVoltagesImpl{phaseVoltages: phaseVoltages} - } - - if len(caps) == 0 { - return base - } - - return &decoratePhoenixEMEthCapable{PhoenixEMEth: base, caps: caps} -} - -type decoratePhoenixEMEthCapable struct { - *PhoenixEMEth - caps map[reflect.Type]any -} - -func (d *decoratePhoenixEMEthCapable) Capability(typ reflect.Type) (any, bool) { - c, ok := d.caps[typ] - if !ok && reflect.TypeOf(d).Implements(typ) { - return d, true - } - return c, ok -} - -type decoratePhoenixEMEthMeterImpl struct { - meter func() (float64, error) -} - -func (impl *decoratePhoenixEMEthMeterImpl) CurrentPower() (float64, error) { - return impl.meter() -} - -type decoratePhoenixEMEthMeterEnergyImpl struct { - meterEnergy func() (float64, error) -} - -func (impl *decoratePhoenixEMEthMeterEnergyImpl) TotalEnergy() (float64, error) { - return impl.meterEnergy() -} - -type decoratePhoenixEMEthPhaseCurrentsImpl struct { - phaseCurrents func() (float64, float64, float64, error) -} - -func (impl *decoratePhoenixEMEthPhaseCurrentsImpl) Currents() (float64, float64, float64, error) { - return impl.phaseCurrents() -} - -type decoratePhoenixEMEthPhaseVoltagesImpl struct { - phaseVoltages func() (float64, float64, float64, error) -} - -func (impl *decoratePhoenixEMEthPhaseVoltagesImpl) Voltages() (float64, float64, float64, error) { - return impl.phaseVoltages() -} diff --git a/charger/phoenix-ev-eth.go b/charger/phoenix-ev-eth.go index b807024da..1e3af59eb 100644 --- a/charger/phoenix-ev-eth.go +++ b/charger/phoenix-ev-eth.go @@ -28,6 +28,7 @@ import ( "fmt" "github.com/evcc-io/evcc/api" + "github.com/evcc-io/evcc/api/implement" "github.com/evcc-io/evcc/util" "github.com/evcc-io/evcc/util/modbus" "github.com/evcc-io/evcc/util/sponsor" @@ -35,6 +36,7 @@ import ( ) type PhoenixEVEth struct { + implement.Caps conn *modbus.Connection isWallbe bool } @@ -62,8 +64,6 @@ func init() { registry.AddCtx("phoenix-ev-eth", NewPhoenixEVEthFromConfig) } -//go:generate go tool decorate -f decoratePhoenixEVEth -b *PhoenixEVEth -r api.Charger -t api.Meter,api.MeterEnergy,api.PhaseCurrents,api.PhaseVoltages,api.ChargerEx,api.Identifier - // NewPhoenixEVEthFromConfig creates a PhoenixEVEth charger from generic config func NewPhoenixEVEthFromConfig(ctx context.Context, other map[string]any) (api.Charger, error) { cc := modbus.TcpSettings{ @@ -92,38 +92,30 @@ func NewPhoenixEVEth(ctx context.Context, uri string, slaveID uint8) (api.Charge conn.Logger(log.TRACE) wb := &PhoenixEVEth{ + Caps: implement.New(), conn: conn, } - var ( - currentPower func() (float64, error) - totalEnergy func() (float64, error) - currents func() (float64, float64, float64, error) - voltages func() (float64, float64, float64, error) - maxCurrentMillis func(float64) error - identify func() (string, error) - ) - // check presence of meter by voltage on l1 if b, err := wb.conn.ReadInputRegisters(phxRegVoltages, 2); err == nil && encoding.Uint32LswFirst(b) > 0 { - currentPower = wb.currentPower - totalEnergy = wb.totalEnergy - currents = wb.currents - voltages = wb.voltages + implement.May(wb, implement.Meter(wb.currentPower)) + implement.May(wb, implement.MeterEnergy(wb.totalEnergy)) + implement.May(wb, implement.PhaseCurrents(wb.currents)) + implement.May(wb, implement.PhaseVoltages(wb.voltages)) } // check card reader enabled if b, err := wb.conn.ReadCoils(phxRegCardEnabled, 1); err == nil && b[0] == 1 { - identify = wb.identify + implement.May(wb, implement.Identifier(wb.identify)) } // check presence of extended Wallbe firmware if b, err := wb.conn.ReadHoldingRegisters(phxRegMaxCurrent, 1); err == nil && encoding.Uint16(b) >= 60 { wb.isWallbe = true - maxCurrentMillis = wb.maxCurrentMillis + implement.May(wb, implement.ChargerEx(wb.maxCurrentMillis)) } - return decoratePhoenixEVEth(wb, currentPower, totalEnergy, currents, voltages, maxCurrentMillis, identify), nil + return wb, nil } // Status implements the api.Charger interface diff --git a/charger/phoenix-ev-eth_decorators.go b/charger/phoenix-ev-eth_decorators.go deleted file mode 100644 index dd6a6dbfb..000000000 --- a/charger/phoenix-ev-eth_decorators.go +++ /dev/null @@ -1,104 +0,0 @@ -package charger - -// Code generated by github.com/evcc-io/evcc/cmd/tools/decorate.go. DO NOT EDIT. - -import ( - "reflect" - - "github.com/evcc-io/evcc/api" -) - -func decoratePhoenixEVEth(base *PhoenixEVEth, meter func() (float64, error), meterEnergy func() (float64, error), phaseCurrents func() (float64, float64, float64, error), phaseVoltages func() (float64, float64, float64, error), chargerEx func(float64) error, identifier func() (string, error)) api.Charger { - caps := make(map[reflect.Type]any) - - if meter != nil { - caps[reflect.TypeFor[api.Meter]()] = &decoratePhoenixEVEthMeterImpl{meter: meter} - } - - if meterEnergy != nil { - caps[reflect.TypeFor[api.MeterEnergy]()] = &decoratePhoenixEVEthMeterEnergyImpl{meterEnergy: meterEnergy} - } - - if phaseCurrents != nil { - caps[reflect.TypeFor[api.PhaseCurrents]()] = &decoratePhoenixEVEthPhaseCurrentsImpl{phaseCurrents: phaseCurrents} - } - - if phaseVoltages != nil { - caps[reflect.TypeFor[api.PhaseVoltages]()] = &decoratePhoenixEVEthPhaseVoltagesImpl{phaseVoltages: phaseVoltages} - } - - if chargerEx != nil { - caps[reflect.TypeFor[api.ChargerEx]()] = &decoratePhoenixEVEthChargerExImpl{chargerEx: chargerEx} - } - - if identifier != nil { - caps[reflect.TypeFor[api.Identifier]()] = &decoratePhoenixEVEthIdentifierImpl{identifier: identifier} - } - - if len(caps) == 0 { - return base - } - - return &decoratePhoenixEVEthCapable{PhoenixEVEth: base, caps: caps} -} - -type decoratePhoenixEVEthCapable struct { - *PhoenixEVEth - caps map[reflect.Type]any -} - -func (d *decoratePhoenixEVEthCapable) Capability(typ reflect.Type) (any, bool) { - c, ok := d.caps[typ] - if !ok && reflect.TypeOf(d).Implements(typ) { - return d, true - } - return c, ok -} - -type decoratePhoenixEVEthChargerExImpl struct { - chargerEx func(float64) error -} - -func (impl *decoratePhoenixEVEthChargerExImpl) MaxCurrentMillis(p0 float64) error { - return impl.chargerEx(p0) -} - -type decoratePhoenixEVEthIdentifierImpl struct { - identifier func() (string, error) -} - -func (impl *decoratePhoenixEVEthIdentifierImpl) Identify() (string, error) { - return impl.identifier() -} - -type decoratePhoenixEVEthMeterImpl struct { - meter func() (float64, error) -} - -func (impl *decoratePhoenixEVEthMeterImpl) CurrentPower() (float64, error) { - return impl.meter() -} - -type decoratePhoenixEVEthMeterEnergyImpl struct { - meterEnergy func() (float64, error) -} - -func (impl *decoratePhoenixEVEthMeterEnergyImpl) TotalEnergy() (float64, error) { - return impl.meterEnergy() -} - -type decoratePhoenixEVEthPhaseCurrentsImpl struct { - phaseCurrents func() (float64, float64, float64, error) -} - -func (impl *decoratePhoenixEVEthPhaseCurrentsImpl) Currents() (float64, float64, float64, error) { - return impl.phaseCurrents() -} - -type decoratePhoenixEVEthPhaseVoltagesImpl struct { - phaseVoltages func() (float64, float64, float64, error) -} - -func (impl *decoratePhoenixEVEthPhaseVoltagesImpl) Voltages() (float64, float64, float64, error) { - return impl.phaseVoltages() -} diff --git a/charger/pulsares.go b/charger/pulsares.go index e89458f27..e85a7baa2 100644 --- a/charger/pulsares.go +++ b/charger/pulsares.go @@ -24,12 +24,14 @@ import ( "time" "github.com/evcc-io/evcc/api" + "github.com/evcc-io/evcc/api/implement" "github.com/evcc-io/evcc/util" "github.com/evcc-io/evcc/util/modbus" ) // Pulsares charger implementation type Pulsares struct { + implement.Caps log *util.Logger conn *modbus.Connection curr uint16 @@ -51,8 +53,6 @@ func init() { registry.AddCtx("pulsares", NewPulsaresFromConfig) } -//go:generate go tool decorate -f decoratePulsares -b *Pulsares -r api.Charger -t api.PhaseSwitcher - // NewPulsaresFromConfig creates a Pulsares charger from generic config func NewPulsaresFromConfig(ctx context.Context, other map[string]any) (api.Charger, error) { cc := modbus.Settings{ @@ -68,12 +68,11 @@ func NewPulsaresFromConfig(ctx context.Context, other map[string]any) (api.Charg return nil, err } - var phases1p3p func(int) error if wb.has1p3p() { - phases1p3p = wb.phases1p3p + implement.Has(wb, implement.PhaseSwitcher(wb.phases1p3p)) } - return decoratePulsares(wb, phases1p3p), nil + return wb, nil } // NewPulsares creates Pulsares charger @@ -87,6 +86,7 @@ func NewPulsares(ctx context.Context, uri, device, comset string, baudrate int, conn.Logger(log.TRACE) wb := &Pulsares{ + Caps: implement.New(), conn: conn, curr: 6000, } diff --git a/charger/pulsares_decorators.go b/charger/pulsares_decorators.go deleted file mode 100644 index fb252ee26..000000000 --- a/charger/pulsares_decorators.go +++ /dev/null @@ -1,44 +0,0 @@ -package charger - -// Code generated by github.com/evcc-io/evcc/cmd/tools/decorate.go. DO NOT EDIT. - -import ( - "reflect" - - "github.com/evcc-io/evcc/api" -) - -func decoratePulsares(base *Pulsares, phaseSwitcher func(int) error) api.Charger { - caps := make(map[reflect.Type]any) - - if phaseSwitcher != nil { - caps[reflect.TypeFor[api.PhaseSwitcher]()] = &decoratePulsaresPhaseSwitcherImpl{phaseSwitcher: phaseSwitcher} - } - - if len(caps) == 0 { - return base - } - - return &decoratePulsaresCapable{Pulsares: base, caps: caps} -} - -type decoratePulsaresCapable struct { - *Pulsares - caps map[reflect.Type]any -} - -func (d *decoratePulsaresCapable) Capability(typ reflect.Type) (any, bool) { - c, ok := d.caps[typ] - if !ok && reflect.TypeOf(d).Implements(typ) { - return d, true - } - return c, ok -} - -type decoratePulsaresPhaseSwitcherImpl struct { - phaseSwitcher func(int) error -} - -func (impl *decoratePulsaresPhaseSwitcherImpl) Phases1p3p(p0 int) error { - return impl.phaseSwitcher(p0) -} diff --git a/charger/semp.go b/charger/semp.go index 305065ffe..0dab8e12a 100644 --- a/charger/semp.go +++ b/charger/semp.go @@ -23,6 +23,7 @@ import ( "time" "github.com/evcc-io/evcc/api" + "github.com/evcc-io/evcc/api/implement" "github.com/evcc-io/evcc/charger/semp" "github.com/evcc-io/evcc/util" "github.com/evcc-io/evcc/util/sponsor" @@ -30,6 +31,7 @@ import ( // SEMP charger implementation type SEMP struct { + implement.Caps log *util.Logger conn *semp.Connection cache time.Duration @@ -44,8 +46,6 @@ type SEMP struct { hasStatusParam bool } -//go:generate go tool decorate -f decorateSEMP -b *SEMP -r api.Charger -t api.PhaseSwitcher,api.PhaseGetter,api.ChargeRater - func init() { registry.AddCtx("semp", NewSEMPFromConfig) } @@ -76,6 +76,7 @@ func NewSEMP(ctx context.Context, uri, deviceID string, cache time.Duration) (ap log := util.NewLogger("semp") wb := &SEMP{ + Caps: implement.New(), log: log, cache: cache, phases: 3, @@ -113,12 +114,6 @@ func NewSEMP(ctx context.Context, uri, deviceID string, cache time.Duration) (ap log.DEBUG.Printf("auto-detected device ID: %s", wb.deviceID) } - var ( - phases1p3p func(int) error - getPhases func() (int, error) - chargedEnergy func() (float64, error) - ) - // Check if device supports phase switching by checking power characteristics info, err := wb.getDeviceInfo() if err != nil { @@ -130,13 +125,13 @@ func NewSEMP(ctx context.Context, uri, deviceID string, cache time.Duration) (ap // Assume Phase switching support if MinPowerConsumption < 4140W and MaxPowerConsumption > 4600W if wb.minPower > 0 && wb.minPower < 4140 && wb.maxPower > 4600 { - phases1p3p = wb.phases1p3p - getPhases = wb.getPhases + implement.Has(wb, implement.PhaseSwitcher(wb.phases1p3p)) + implement.Has(wb, implement.PhaseGetter(wb.getPhases)) } // Check if device supports charged energy reporting via Parameters endpoint if _, err := wb.getParameter("Measurement.ChaSess.WhIn"); err == nil { - chargedEnergy = wb.chargedEnergy + implement.Has(wb, implement.ChargeRater(wb.chargedEnergy)) } // Check if device supports status reporting via Parameters endpoint @@ -151,7 +146,7 @@ func NewSEMP(ctx context.Context, uri, deviceID string, cache time.Duration) (ap go wb.heartbeat(ctx) - return decorateSEMP(wb, phases1p3p, getPhases, chargedEnergy), nil + return wb, nil } // heartbeat ensures that device control updates are sent at least once per minute diff --git a/charger/semp_decorators.go b/charger/semp_decorators.go deleted file mode 100644 index ed11668c4..000000000 --- a/charger/semp_decorators.go +++ /dev/null @@ -1,68 +0,0 @@ -package charger - -// Code generated by github.com/evcc-io/evcc/cmd/tools/decorate.go. DO NOT EDIT. - -import ( - "reflect" - - "github.com/evcc-io/evcc/api" -) - -func decorateSEMP(base *SEMP, phaseSwitcher func(int) error, phaseGetter func() (int, error), chargeRater func() (float64, error)) api.Charger { - caps := make(map[reflect.Type]any) - - if phaseSwitcher != nil { - caps[reflect.TypeFor[api.PhaseSwitcher]()] = &decorateSEMPPhaseSwitcherImpl{phaseSwitcher: phaseSwitcher} - } - - if phaseGetter != nil { - caps[reflect.TypeFor[api.PhaseGetter]()] = &decorateSEMPPhaseGetterImpl{phaseGetter: phaseGetter} - } - - if chargeRater != nil { - caps[reflect.TypeFor[api.ChargeRater]()] = &decorateSEMPChargeRaterImpl{chargeRater: chargeRater} - } - - if len(caps) == 0 { - return base - } - - return &decorateSEMPCapable{SEMP: base, caps: caps} -} - -type decorateSEMPCapable struct { - *SEMP - caps map[reflect.Type]any -} - -func (d *decorateSEMPCapable) Capability(typ reflect.Type) (any, bool) { - c, ok := d.caps[typ] - if !ok && reflect.TypeOf(d).Implements(typ) { - return d, true - } - return c, ok -} - -type decorateSEMPChargeRaterImpl struct { - chargeRater func() (float64, error) -} - -func (impl *decorateSEMPChargeRaterImpl) ChargedEnergy() (float64, error) { - return impl.chargeRater() -} - -type decorateSEMPPhaseGetterImpl struct { - phaseGetter func() (int, error) -} - -func (impl *decorateSEMPPhaseGetterImpl) GetPhases() (int, error) { - return impl.phaseGetter() -} - -type decorateSEMPPhaseSwitcherImpl struct { - phaseSwitcher func(int) error -} - -func (impl *decorateSEMPPhaseSwitcherImpl) Phases1p3p(p0 int) error { - return impl.phaseSwitcher(p0) -} diff --git a/charger/sgready-relay.go b/charger/sgready-relay.go index 3850d6988..62e17cc4a 100644 --- a/charger/sgready-relay.go +++ b/charger/sgready-relay.go @@ -5,6 +5,7 @@ import ( "fmt" "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/util" "github.com/evcc-io/evcc/util/config" @@ -55,11 +56,6 @@ func NewSgReadyRelayFromConfig(ctx context.Context, other map[string]any) (api.C } } - tempG, limitTempG, err := cc.Temperature.Configure(ctx) - if err != nil { - return nil, err - } - res, err := NewSgReadyRelay(ctx, &cc.embed, boost, dim) if err != nil { return nil, err @@ -69,8 +65,17 @@ func NewSgReadyRelayFromConfig(ctx context.Context, other map[string]any) (api.C if err != nil { return nil, err } + implement.May(res, implement.Meter(powerG)) + implement.May(res, implement.MeterEnergy(energyG)) - return decorateSgReady(res, powerG, energyG, tempG, limitTempG), nil + tempG, limitTempG, err := cc.Temperature.Configure(ctx) + if err != nil { + return nil, err + } + implement.May(res, implement.Battery(tempG)) + implement.May(res, implement.SocLimiter(limitTempG)) + + return res, nil } // NewSgReadyRelay creates SG Ready charger with boost relay diff --git a/charger/sgready.go b/charger/sgready.go index f0cd20902..bc152b4f5 100644 --- a/charger/sgready.go +++ b/charger/sgready.go @@ -21,6 +21,7 @@ import ( "context" "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/core/loadpoint" "github.com/evcc-io/evcc/plugin" @@ -30,6 +31,7 @@ import ( // SgReady charger implementation type SgReady struct { *embed + implement.Caps mode int64 modeS func(int64) error modeG func() (int64, error) @@ -51,8 +53,6 @@ const ( Boost // 3 ) -//go:generate go tool decorate -f decorateSgReady -b *SgReady -r api.Charger -t api.Meter,api.MeterEnergy,api.Battery,api.SocLimiter - // NewSgReadyFromConfig creates an SG Ready configurable charger from generic config func NewSgReadyFromConfig(ctx context.Context, other map[string]any) (api.Charger, error) { cc := struct { @@ -111,19 +111,24 @@ func NewSgReadyFromConfig(ctx context.Context, other map[string]any) (api.Charge if err != nil { return nil, err } + implement.May(res, implement.Meter(powerG)) + implement.May(res, implement.MeterEnergy(energyG)) tempG, limitTempG, err := cc.Temperature.Configure(ctx) if err != nil { return nil, err } + implement.May(res, implement.Battery(tempG)) + implement.May(res, implement.SocLimiter(limitTempG)) - return decorateSgReady(res, powerG, energyG, tempG, limitTempG), nil + return res, nil } // NewSgReady creates SG Ready charger func NewSgReady(ctx context.Context, embed *embed, modeS func(int64) error, modeG func() (int64, error), maxPowerS func(int64) error) (*SgReady, error) { res := &SgReady{ embed: embed, + Caps: implement.New(), mode: Normal, modeS: modeS, modeG: modeG, diff --git a/charger/sgready_decorators.go b/charger/sgready_decorators.go deleted file mode 100644 index 056a490a9..000000000 --- a/charger/sgready_decorators.go +++ /dev/null @@ -1,80 +0,0 @@ -package charger - -// Code generated by github.com/evcc-io/evcc/cmd/tools/decorate.go. DO NOT EDIT. - -import ( - "reflect" - - "github.com/evcc-io/evcc/api" -) - -func decorateSgReady(base *SgReady, meter func() (float64, error), meterEnergy func() (float64, error), battery func() (float64, error), socLimiter func() (int64, error)) api.Charger { - caps := make(map[reflect.Type]any) - - if meter != nil { - caps[reflect.TypeFor[api.Meter]()] = &decorateSgReadyMeterImpl{meter: meter} - } - - if meterEnergy != nil { - caps[reflect.TypeFor[api.MeterEnergy]()] = &decorateSgReadyMeterEnergyImpl{meterEnergy: meterEnergy} - } - - if battery != nil { - caps[reflect.TypeFor[api.Battery]()] = &decorateSgReadyBatteryImpl{battery: battery} - } - - if socLimiter != nil { - caps[reflect.TypeFor[api.SocLimiter]()] = &decorateSgReadySocLimiterImpl{socLimiter: socLimiter} - } - - if len(caps) == 0 { - return base - } - - return &decorateSgReadyCapable{SgReady: base, caps: caps} -} - -type decorateSgReadyCapable struct { - *SgReady - caps map[reflect.Type]any -} - -func (d *decorateSgReadyCapable) Capability(typ reflect.Type) (any, bool) { - c, ok := d.caps[typ] - if !ok && reflect.TypeOf(d).Implements(typ) { - return d, true - } - return c, ok -} - -type decorateSgReadyBatteryImpl struct { - battery func() (float64, error) -} - -func (impl *decorateSgReadyBatteryImpl) Soc() (float64, error) { - return impl.battery() -} - -type decorateSgReadyMeterImpl struct { - meter func() (float64, error) -} - -func (impl *decorateSgReadyMeterImpl) CurrentPower() (float64, error) { - return impl.meter() -} - -type decorateSgReadyMeterEnergyImpl struct { - meterEnergy func() (float64, error) -} - -func (impl *decorateSgReadyMeterEnergyImpl) TotalEnergy() (float64, error) { - return impl.meterEnergy() -} - -type decorateSgReadySocLimiterImpl struct { - socLimiter func() (int64, error) -} - -func (impl *decorateSgReadySocLimiterImpl) GetLimitSoc() (int64, error) { - return impl.socLimiter() -} diff --git a/charger/shelly.go b/charger/shelly.go index f6266fead..524266808 100644 --- a/charger/shelly.go +++ b/charger/shelly.go @@ -5,12 +5,14 @@ import ( "time" "github.com/evcc-io/evcc/api" + "github.com/evcc-io/evcc/api/implement" "github.com/evcc-io/evcc/meter/shelly" "github.com/evcc-io/evcc/util" ) // Shelly charger implementation type Shelly struct { + implement.Caps conn *shelly.Connection *switchSocket } @@ -19,8 +21,6 @@ func init() { registry.Add("shelly", NewShellyFromConfig) } -//go:generate go tool decorate -f decorateShelly -b *Shelly -r api.Charger -t api.PhaseVoltages,api.PhaseCurrents,api.PhasePowers - // NewShellyFromConfig creates a Shelly charger from generic config func NewShellyFromConfig(other map[string]any) (api.Charger, error) { cc := struct { @@ -44,14 +44,13 @@ func NewShellyFromConfig(other map[string]any) (api.Charger, error) { return nil, err } - var vol, cur, pow func() (float64, float64, float64, error) if phases, ok := c.conn.Generation.(shelly.Phases); ok { - vol = phases.Voltages - cur = phases.Currents - pow = phases.Powers + implement.Has(c, implement.PhaseVoltages(phases.Voltages)) + implement.Has(c, implement.PhaseCurrents(phases.Currents)) + implement.Has(c, implement.PhasePowers(phases.Powers)) } - return decorateShelly(c, vol, cur, pow), nil + return c, nil } // NewShelly creates Shelly charger @@ -62,6 +61,7 @@ func NewShelly(embed embed, uri, user, password string, channel int, standbypowe } c := &Shelly{ + Caps: implement.New(), conn: conn, } diff --git a/charger/shelly_decorators.go b/charger/shelly_decorators.go deleted file mode 100644 index 3c80eb071..000000000 --- a/charger/shelly_decorators.go +++ /dev/null @@ -1,68 +0,0 @@ -package charger - -// Code generated by github.com/evcc-io/evcc/cmd/tools/decorate.go. DO NOT EDIT. - -import ( - "reflect" - - "github.com/evcc-io/evcc/api" -) - -func decorateShelly(base *Shelly, phaseVoltages func() (float64, float64, float64, error), phaseCurrents func() (float64, float64, float64, error), phasePowers func() (float64, float64, float64, error)) api.Charger { - caps := make(map[reflect.Type]any) - - if phaseVoltages != nil { - caps[reflect.TypeFor[api.PhaseVoltages]()] = &decorateShellyPhaseVoltagesImpl{phaseVoltages: phaseVoltages} - } - - if phaseCurrents != nil { - caps[reflect.TypeFor[api.PhaseCurrents]()] = &decorateShellyPhaseCurrentsImpl{phaseCurrents: phaseCurrents} - } - - if phasePowers != nil { - caps[reflect.TypeFor[api.PhasePowers]()] = &decorateShellyPhasePowersImpl{phasePowers: phasePowers} - } - - if len(caps) == 0 { - return base - } - - return &decorateShellyCapable{Shelly: base, caps: caps} -} - -type decorateShellyCapable struct { - *Shelly - caps map[reflect.Type]any -} - -func (d *decorateShellyCapable) Capability(typ reflect.Type) (any, bool) { - c, ok := d.caps[typ] - if !ok && reflect.TypeOf(d).Implements(typ) { - return d, true - } - return c, ok -} - -type decorateShellyPhaseCurrentsImpl struct { - phaseCurrents func() (float64, float64, float64, error) -} - -func (impl *decorateShellyPhaseCurrentsImpl) Currents() (float64, float64, float64, error) { - return impl.phaseCurrents() -} - -type decorateShellyPhasePowersImpl struct { - phasePowers func() (float64, float64, float64, error) -} - -func (impl *decorateShellyPhasePowersImpl) Powers() (float64, float64, float64, error) { - return impl.phasePowers() -} - -type decorateShellyPhaseVoltagesImpl struct { - phaseVoltages func() (float64, float64, float64, error) -} - -func (impl *decorateShellyPhaseVoltagesImpl) Voltages() (float64, float64, float64, error) { - return impl.phaseVoltages() -} diff --git a/charger/smart-evse.go b/charger/smart-evse.go index 285cee3a6..3c97d6252 100644 --- a/charger/smart-evse.go +++ b/charger/smart-evse.go @@ -24,6 +24,7 @@ import ( "time" "github.com/evcc-io/evcc/api" + "github.com/evcc-io/evcc/api/implement" "github.com/evcc-io/evcc/util" "github.com/evcc-io/evcc/util/request" ) @@ -34,6 +35,7 @@ import ( // SmartEVSE3 charger implementation type SmartEVSE3 struct { *request.Helper + implement.Caps uri string curr int64 mode int @@ -106,8 +108,6 @@ func init() { registry.Add("smart-evse", NewSmartEVSE3FromConfig) } -//go:generate go tool decorate -f decorateSmartEVSE3 -b *SmartEVSE3 -r api.Charger -t api.Meter,api.MeterEnergy,api.PhaseCurrents,api.PhaseSwitcher,api.PhaseGetter,api.Identifier,api.StatusReasoner - // NewSmartEVSE3FromConfig creates a SmartEVSE-3.5 REST charger from generic config func NewSmartEVSE3FromConfig(other map[string]any) (api.Charger, error) { cc := struct { @@ -141,6 +141,7 @@ func NewSmartEVSE3(uri string, cache time.Duration, mode int) (api.Charger, erro wb := &SmartEVSE3{ Helper: request.NewHelper(log), + Caps: implement.New(), uri: strings.TrimRight(util.DefaultScheme(uri, "http"), "/"), curr: 60, // 6 A in 1/10 A mode: mode, @@ -163,31 +164,25 @@ func NewSmartEVSE3(uri string, cache time.Duration, mode int) (api.Charger, erro } // decorate optional EV meter if configured in SmartEVSE - var currentPower, totalEnergy func() (float64, error) - var currents func() (float64, float64, float64, error) if res.EvMeter.Description != "" && res.EvMeter.Description != "Disabled" { - currentPower = wb.currentPower - totalEnergy = wb.totalEnergy - currents = wb.currents + implement.Has(wb, implement.Meter(wb.currentPower)) + implement.Has(wb, implement.MeterEnergy(wb.totalEnergy)) + implement.Has(wb, implement.PhaseCurrents(wb.currents)) } // decorate optional 1P/3P phase switching via C2 contactor - var phases1p3p func(int) error - var getPhases func() (int, error) if res.Settings.EnableC2 != "" && res.Settings.EnableC2 != "Not present" { - phases1p3p = wb.phases1p3p - getPhases = wb.getPhases + implement.Has(wb, implement.PhaseSwitcher(wb.phases1p3p)) + implement.Has(wb, implement.PhaseGetter(wb.getPhases)) } // decorate optional RFID identification and status reason - var identify func() (string, error) - var statusReason func() (api.Reason, error) if res.Evse.RFIDReader != "" && res.Evse.RFIDReader != "Disabled" { - identify = wb.identify - statusReason = wb.statusReason + implement.Has(wb, implement.Identifier(wb.identify)) + implement.Has(wb, implement.StatusReasoner(wb.statusReason)) } - return decorateSmartEVSE3(wb, currentPower, totalEnergy, currents, phases1p3p, getPhases, identify, statusReason), nil + return wb, nil } // post issues a POST request to the SmartEVSE with given query parameters. diff --git a/charger/smart-evse_decorators.go b/charger/smart-evse_decorators.go deleted file mode 100644 index eab122fb7..000000000 --- a/charger/smart-evse_decorators.go +++ /dev/null @@ -1,116 +0,0 @@ -package charger - -// Code generated by github.com/evcc-io/evcc/cmd/tools/decorate.go. DO NOT EDIT. - -import ( - "reflect" - - "github.com/evcc-io/evcc/api" -) - -func decorateSmartEVSE3(base *SmartEVSE3, meter func() (float64, error), meterEnergy func() (float64, error), phaseCurrents func() (float64, float64, float64, error), phaseSwitcher func(int) error, phaseGetter func() (int, error), identifier func() (string, error), statusReasoner func() (api.Reason, error)) api.Charger { - caps := make(map[reflect.Type]any) - - if meter != nil { - caps[reflect.TypeFor[api.Meter]()] = &decorateSmartEVSE3MeterImpl{meter: meter} - } - - if meterEnergy != nil { - caps[reflect.TypeFor[api.MeterEnergy]()] = &decorateSmartEVSE3MeterEnergyImpl{meterEnergy: meterEnergy} - } - - if phaseCurrents != nil { - caps[reflect.TypeFor[api.PhaseCurrents]()] = &decorateSmartEVSE3PhaseCurrentsImpl{phaseCurrents: phaseCurrents} - } - - if phaseSwitcher != nil { - caps[reflect.TypeFor[api.PhaseSwitcher]()] = &decorateSmartEVSE3PhaseSwitcherImpl{phaseSwitcher: phaseSwitcher} - } - - if phaseGetter != nil { - caps[reflect.TypeFor[api.PhaseGetter]()] = &decorateSmartEVSE3PhaseGetterImpl{phaseGetter: phaseGetter} - } - - if identifier != nil { - caps[reflect.TypeFor[api.Identifier]()] = &decorateSmartEVSE3IdentifierImpl{identifier: identifier} - } - - if statusReasoner != nil { - caps[reflect.TypeFor[api.StatusReasoner]()] = &decorateSmartEVSE3StatusReasonerImpl{statusReasoner: statusReasoner} - } - - if len(caps) == 0 { - return base - } - - return &decorateSmartEVSE3Capable{SmartEVSE3: base, caps: caps} -} - -type decorateSmartEVSE3Capable struct { - *SmartEVSE3 - caps map[reflect.Type]any -} - -func (d *decorateSmartEVSE3Capable) Capability(typ reflect.Type) (any, bool) { - c, ok := d.caps[typ] - if !ok && reflect.TypeOf(d).Implements(typ) { - return d, true - } - return c, ok -} - -type decorateSmartEVSE3IdentifierImpl struct { - identifier func() (string, error) -} - -func (impl *decorateSmartEVSE3IdentifierImpl) Identify() (string, error) { - return impl.identifier() -} - -type decorateSmartEVSE3MeterImpl struct { - meter func() (float64, error) -} - -func (impl *decorateSmartEVSE3MeterImpl) CurrentPower() (float64, error) { - return impl.meter() -} - -type decorateSmartEVSE3MeterEnergyImpl struct { - meterEnergy func() (float64, error) -} - -func (impl *decorateSmartEVSE3MeterEnergyImpl) TotalEnergy() (float64, error) { - return impl.meterEnergy() -} - -type decorateSmartEVSE3PhaseCurrentsImpl struct { - phaseCurrents func() (float64, float64, float64, error) -} - -func (impl *decorateSmartEVSE3PhaseCurrentsImpl) Currents() (float64, float64, float64, error) { - return impl.phaseCurrents() -} - -type decorateSmartEVSE3PhaseGetterImpl struct { - phaseGetter func() (int, error) -} - -func (impl *decorateSmartEVSE3PhaseGetterImpl) GetPhases() (int, error) { - return impl.phaseGetter() -} - -type decorateSmartEVSE3PhaseSwitcherImpl struct { - phaseSwitcher func(int) error -} - -func (impl *decorateSmartEVSE3PhaseSwitcherImpl) Phases1p3p(p0 int) error { - return impl.phaseSwitcher(p0) -} - -type decorateSmartEVSE3StatusReasonerImpl struct { - statusReasoner func() (api.Reason, error) -} - -func (impl *decorateSmartEVSE3StatusReasonerImpl) StatusReason() (api.Reason, error) { - return impl.statusReasoner() -} diff --git a/charger/solax.go b/charger/solax.go index f7944b11c..9b79b6ad4 100644 --- a/charger/solax.go +++ b/charger/solax.go @@ -24,6 +24,7 @@ import ( "strings" "github.com/evcc-io/evcc/api" + "github.com/evcc-io/evcc/api/implement" "github.com/evcc-io/evcc/util" "github.com/evcc-io/evcc/util/modbus" "github.com/evcc-io/evcc/util/sponsor" @@ -32,6 +33,7 @@ import ( // Solax charger implementation type Solax struct { + implement.Caps log *util.Logger conn *modbus.Connection isLegacyHw bool @@ -75,8 +77,6 @@ func init() { registry.AddCtx("solax-g2", NewSolaxG2FromConfig) } -//go:generate go tool decorate -f decorateSolax -b *Solax -r api.Charger -t api.PhaseSwitcher,api.PhaseGetter - func NewSolaxG1FromConfig(ctx context.Context, other map[string]any) (api.Charger, error) { return NewSolaxFromConfig(ctx, other, true) } @@ -113,23 +113,21 @@ func NewSolax(ctx context.Context, uri, device, comset string, baudrate int, pro conn.Logger(log.TRACE) wb := &Solax{ + Caps: implement.New(), log: log, conn: conn, isLegacyHw: isLegacyHw, } - var phases1p3p func(int) error - var phasesG func() (int, error) - if b, err := wb.conn.ReadInputRegisters(solaxRegFirmwareVersion, 1); err == nil { v := encoding.Uint16(b) if !wb.isLegacyHw && v >= solaxFirmwarePhaseSwitching { - phases1p3p = wb.phases1p3p - phasesG = wb.getPhases + implement.Has(wb, implement.PhaseSwitcher(wb.phases1p3p)) + implement.Has(wb, implement.PhaseGetter(wb.getPhases)) } } - return decorateSolax(wb, phases1p3p, phasesG), nil + return wb, nil } // getPhaseValues returns 3 sequential register values diff --git a/charger/solax_decorators.go b/charger/solax_decorators.go deleted file mode 100644 index 40f10cd7a..000000000 --- a/charger/solax_decorators.go +++ /dev/null @@ -1,56 +0,0 @@ -package charger - -// Code generated by github.com/evcc-io/evcc/cmd/tools/decorate.go. DO NOT EDIT. - -import ( - "reflect" - - "github.com/evcc-io/evcc/api" -) - -func decorateSolax(base *Solax, phaseSwitcher func(int) error, phaseGetter func() (int, error)) api.Charger { - caps := make(map[reflect.Type]any) - - if phaseSwitcher != nil { - caps[reflect.TypeFor[api.PhaseSwitcher]()] = &decorateSolaxPhaseSwitcherImpl{phaseSwitcher: phaseSwitcher} - } - - if phaseGetter != nil { - caps[reflect.TypeFor[api.PhaseGetter]()] = &decorateSolaxPhaseGetterImpl{phaseGetter: phaseGetter} - } - - if len(caps) == 0 { - return base - } - - return &decorateSolaxCapable{Solax: base, caps: caps} -} - -type decorateSolaxCapable struct { - *Solax - caps map[reflect.Type]any -} - -func (d *decorateSolaxCapable) Capability(typ reflect.Type) (any, bool) { - c, ok := d.caps[typ] - if !ok && reflect.TypeOf(d).Implements(typ) { - return d, true - } - return c, ok -} - -type decorateSolaxPhaseGetterImpl struct { - phaseGetter func() (int, error) -} - -func (impl *decorateSolaxPhaseGetterImpl) GetPhases() (int, error) { - return impl.phaseGetter() -} - -type decorateSolaxPhaseSwitcherImpl struct { - phaseSwitcher func(int) error -} - -func (impl *decorateSolaxPhaseSwitcherImpl) Phases1p3p(p0 int) error { - return impl.phaseSwitcher(p0) -} diff --git a/charger/switchsocket.go b/charger/switchsocket.go index 7fd62c9ed..01294b77c 100644 --- a/charger/switchsocket.go +++ b/charger/switchsocket.go @@ -4,6 +4,7 @@ import ( "context" "github.com/evcc-io/evcc/api" + "github.com/evcc-io/evcc/api/implement" "github.com/evcc-io/evcc/core/loadpoint" "github.com/evcc-io/evcc/plugin" "github.com/evcc-io/evcc/util" @@ -14,13 +15,12 @@ func init() { } type SwitchSocket struct { + implement.Caps enable func(bool) error enabled func() (bool, error) *switchSocket } -//go:generate go tool decorate -f decorateSwitchSocket -b *SwitchSocket -r api.Charger -t api.MeterEnergy,api.Battery - func NewSwitchSocketFromConfig(ctx context.Context, other map[string]any) (api.Charger, error) { var cc struct { embed `mapstructure:",squash"` @@ -51,23 +51,26 @@ func NewSwitchSocketFromConfig(ctx context.Context, other map[string]any) (api.C return nil, err } - energy, err := cc.Energy.FloatGetter(ctx) - if err != nil { - return nil, err - } - - soc, err := cc.Soc.FloatGetter(ctx) - if err != nil { - return nil, err - } - c := &SwitchSocket{ + Caps: implement.New(), enabled: enabled, enable: enable, switchSocket: NewSwitchSocket(&cc.embed, enabled, power, cc.StandbyPower), } - return decorateSwitchSocket(c, energy, soc), nil + energy, err := cc.Energy.FloatGetter(ctx) + if err != nil { + return nil, err + } + implement.May(c, implement.MeterEnergy(energy)) + + soc, err := cc.Soc.FloatGetter(ctx) + if err != nil { + return nil, err + } + implement.May(c, implement.Battery(soc)) + + return c, nil } func (c *SwitchSocket) Enabled() (bool, error) { diff --git a/charger/switchsocket_decorators.go b/charger/switchsocket_decorators.go deleted file mode 100644 index 0e3e9d91c..000000000 --- a/charger/switchsocket_decorators.go +++ /dev/null @@ -1,56 +0,0 @@ -package charger - -// Code generated by github.com/evcc-io/evcc/cmd/tools/decorate.go. DO NOT EDIT. - -import ( - "reflect" - - "github.com/evcc-io/evcc/api" -) - -func decorateSwitchSocket(base *SwitchSocket, meterEnergy func() (float64, error), battery func() (float64, error)) api.Charger { - caps := make(map[reflect.Type]any) - - if meterEnergy != nil { - caps[reflect.TypeFor[api.MeterEnergy]()] = &decorateSwitchSocketMeterEnergyImpl{meterEnergy: meterEnergy} - } - - if battery != nil { - caps[reflect.TypeFor[api.Battery]()] = &decorateSwitchSocketBatteryImpl{battery: battery} - } - - if len(caps) == 0 { - return base - } - - return &decorateSwitchSocketCapable{SwitchSocket: base, caps: caps} -} - -type decorateSwitchSocketCapable struct { - *SwitchSocket - caps map[reflect.Type]any -} - -func (d *decorateSwitchSocketCapable) Capability(typ reflect.Type) (any, bool) { - c, ok := d.caps[typ] - if !ok && reflect.TypeOf(d).Implements(typ) { - return d, true - } - return c, ok -} - -type decorateSwitchSocketBatteryImpl struct { - battery func() (float64, error) -} - -func (impl *decorateSwitchSocketBatteryImpl) Soc() (float64, error) { - return impl.battery() -} - -type decorateSwitchSocketMeterEnergyImpl struct { - meterEnergy func() (float64, error) -} - -func (impl *decorateSwitchSocketMeterEnergyImpl) TotalEnergy() (float64, error) { - return impl.meterEnergy() -} diff --git a/charger/tasmota.go b/charger/tasmota.go index f6cbdeeba..c22f6ce76 100644 --- a/charger/tasmota.go +++ b/charger/tasmota.go @@ -4,6 +4,7 @@ import ( "time" "github.com/evcc-io/evcc/api" + "github.com/evcc-io/evcc/api/implement" "github.com/evcc-io/evcc/meter/tasmota" "github.com/evcc-io/evcc/util" ) @@ -15,6 +16,7 @@ import ( // Tasmota charger implementation type Tasmota struct { + implement.Caps conn *tasmota.Connection *switchSocket } @@ -23,8 +25,6 @@ func init() { registry.Add("tasmota", NewTasmotaFromConfig) } -//go:generate go tool decorate -f decorateTasmota -b *Tasmota -r api.Charger -t api.PhaseVoltages,api.PhaseCurrents - // NewTasmotaFromConfig creates a Tasmota charger from generic config func NewTasmotaFromConfig(other map[string]any) (api.Charger, error) { cc := struct { @@ -60,6 +60,7 @@ func NewTasmota(embed embed, uri, user, password, usage string, channels []int, } c := &Tasmota{ + Caps: implement.New(), conn: conn, } @@ -74,7 +75,8 @@ func NewTasmota(embed embed, uri, user, password, usage string, channels []int, } if hasPhases || len(channels) == 3 { - return decorateTasmota(c, c.voltages, c.currents), nil + implement.Has(c, implement.PhaseVoltages(c.voltages)) + implement.Has(c, implement.PhaseCurrents(c.currents)) } return c, nil diff --git a/charger/tasmota_decorators.go b/charger/tasmota_decorators.go deleted file mode 100644 index fdde72fa8..000000000 --- a/charger/tasmota_decorators.go +++ /dev/null @@ -1,56 +0,0 @@ -package charger - -// Code generated by github.com/evcc-io/evcc/cmd/tools/decorate.go. DO NOT EDIT. - -import ( - "reflect" - - "github.com/evcc-io/evcc/api" -) - -func decorateTasmota(base *Tasmota, phaseVoltages func() (float64, float64, float64, error), phaseCurrents func() (float64, float64, float64, error)) api.Charger { - caps := make(map[reflect.Type]any) - - if phaseVoltages != nil { - caps[reflect.TypeFor[api.PhaseVoltages]()] = &decorateTasmotaPhaseVoltagesImpl{phaseVoltages: phaseVoltages} - } - - if phaseCurrents != nil { - caps[reflect.TypeFor[api.PhaseCurrents]()] = &decorateTasmotaPhaseCurrentsImpl{phaseCurrents: phaseCurrents} - } - - if len(caps) == 0 { - return base - } - - return &decorateTasmotaCapable{Tasmota: base, caps: caps} -} - -type decorateTasmotaCapable struct { - *Tasmota - caps map[reflect.Type]any -} - -func (d *decorateTasmotaCapable) Capability(typ reflect.Type) (any, bool) { - c, ok := d.caps[typ] - if !ok && reflect.TypeOf(d).Implements(typ) { - return d, true - } - return c, ok -} - -type decorateTasmotaPhaseCurrentsImpl struct { - phaseCurrents func() (float64, float64, float64, error) -} - -func (impl *decorateTasmotaPhaseCurrentsImpl) Currents() (float64, float64, float64, error) { - return impl.phaseCurrents() -} - -type decorateTasmotaPhaseVoltagesImpl struct { - phaseVoltages func() (float64, float64, float64, error) -} - -func (impl *decorateTasmotaPhaseVoltagesImpl) Voltages() (float64, float64, float64, error) { - return impl.phaseVoltages() -} diff --git a/charger/vaillant.go b/charger/vaillant.go index ed3877527..f477ee9b9 100644 --- a/charger/vaillant.go +++ b/charger/vaillant.go @@ -26,6 +26,7 @@ import ( "github.com/WulfgarW/sensonet" "github.com/evcc-io/evcc/api" + "github.com/evcc-io/evcc/api/implement" "github.com/evcc-io/evcc/util" "github.com/evcc-io/evcc/util/request" "github.com/samber/lo" @@ -43,8 +44,6 @@ type Vaillant struct { systemId string } -//go:generate go tool decorate -f decorateVaillant -b *Vaillant -r api.Charger -t api.Meter,api.Battery - // NewVaillantFromConfig creates an Vaillant configurable charger from generic config func NewVaillantFromConfig(ctx context.Context, other map[string]any) (api.Charger, error) { cc := struct { @@ -155,14 +154,13 @@ func NewVaillantFromConfig(ctx context.Context, other map[string]any) (api.Charg SgReady: sgr, } - var power func() (float64, error) if devices, _ := conn.GetMpcData(systemId); len(devices) > 0 { - power = util.Cached(func() (float64, error) { + implement.Has(res, implement.Meter(util.Cached(func() (float64, error) { res, err := conn.GetMpcData(systemId) return lo.SumBy(res, func(d sensonet.MpcDevice) float64 { return d.CurrentPower }), err - }, cc.Cache) + }, cc.Cache))) } heatingTemp := func(zz []sensonet.StateZone) float64 { @@ -181,9 +179,8 @@ func NewVaillantFromConfig(ctx context.Context, other map[string]any) (api.Charg heatingTempSensor = heatingTemp(system.State.Zones) > 0 } - var temp func() (float64, error) if !heating || heatingTempSensor { - temp = util.Cached(func() (float64, error) { + implement.Has(res, implement.Battery(util.Cached(func() (float64, error) { system, err := conn.GetSystem(systemId) if err != nil { return 0, err @@ -202,10 +199,10 @@ func NewVaillantFromConfig(ctx context.Context, other map[string]any) (api.Charg default: return 0, api.ErrNotAvailable } - }, cc.Cache) + }, cc.Cache))) } - return decorateVaillant(res, power, temp), nil + return res, nil } func (v *Vaillant) print(chapter int, prefix string, zz ...any) { diff --git a/charger/vaillant_decorators.go b/charger/vaillant_decorators.go deleted file mode 100644 index 1e84858e9..000000000 --- a/charger/vaillant_decorators.go +++ /dev/null @@ -1,56 +0,0 @@ -package charger - -// Code generated by github.com/evcc-io/evcc/cmd/tools/decorate.go. DO NOT EDIT. - -import ( - "reflect" - - "github.com/evcc-io/evcc/api" -) - -func decorateVaillant(base *Vaillant, meter func() (float64, error), battery func() (float64, error)) api.Charger { - caps := make(map[reflect.Type]any) - - if meter != nil { - caps[reflect.TypeFor[api.Meter]()] = &decorateVaillantMeterImpl{meter: meter} - } - - if battery != nil { - caps[reflect.TypeFor[api.Battery]()] = &decorateVaillantBatteryImpl{battery: battery} - } - - if len(caps) == 0 { - return base - } - - return &decorateVaillantCapable{Vaillant: base, caps: caps} -} - -type decorateVaillantCapable struct { - *Vaillant - caps map[reflect.Type]any -} - -func (d *decorateVaillantCapable) Capability(typ reflect.Type) (any, bool) { - c, ok := d.caps[typ] - if !ok && reflect.TypeOf(d).Implements(typ) { - return d, true - } - return c, ok -} - -type decorateVaillantBatteryImpl struct { - battery func() (float64, error) -} - -func (impl *decorateVaillantBatteryImpl) Soc() (float64, error) { - return impl.battery() -} - -type decorateVaillantMeterImpl struct { - meter func() (float64, error) -} - -func (impl *decorateVaillantMeterImpl) CurrentPower() (float64, error) { - return impl.meter() -} diff --git a/charger/vestel.go b/charger/vestel.go index 362f3ae59..e268b54a0 100644 --- a/charger/vestel.go +++ b/charger/vestel.go @@ -25,6 +25,7 @@ import ( "time" "github.com/evcc-io/evcc/api" + "github.com/evcc-io/evcc/api/implement" "github.com/evcc-io/evcc/util" "github.com/evcc-io/evcc/util/modbus" "github.com/evcc-io/evcc/util/sponsor" @@ -59,6 +60,7 @@ var ( // Vestel is an api.Charger implementation for Vestel/Hymes wallboxes with Ethernet (SW modells). // It uses Modbus TCP to communicate with the wallbox at modbus client id 255. type Vestel struct { + implement.Caps log *util.Logger conn *modbus.Connection enabled bool @@ -69,8 +71,6 @@ func init() { registry.AddCtx("vestel", NewVestelFromConfig) } -//go:generate go tool decorate -f decorateVestel -b *Vestel -r api.Charger -t api.PhaseSwitcher,api.PhaseGetter,api.Identifier - // NewVestelFromConfig creates a Vestel charger from generic config func NewVestelFromConfig(ctx context.Context, other map[string]any) (api.Charger, error) { cc := modbus.TcpSettings{ @@ -99,23 +99,18 @@ func NewVestel(ctx context.Context, uri string, id uint8) (api.Charger, error) { conn.Logger(log.TRACE) wb := &Vestel{ + Caps: implement.New(), log: log, conn: conn, current: 6, } - var ( - phasesS func(int) error - phasesG func() (int, error) - ) if b, err := wb.conn.ReadInputRegisters(vestelRegNumberPhases, 1); err == nil && binary.BigEndian.Uint16(b) == 1 { - phasesS = wb.phases1p3p - phasesG = wb.getPhases + implement.Has(wb, implement.PhaseSwitcher(wb.phases1p3p)) + implement.Has(wb, implement.PhaseGetter(wb.getPhases)) } // compare firmware version to determine if RFID is available - var identify func() (string, error) - b, err := wb.conn.ReadInputRegisters(vestelRegFirmware, 50) if err != nil { return nil, fmt.Errorf("failed to read firmware version: %w", err) @@ -127,7 +122,7 @@ func NewVestel(ctx context.Context, uri string, id uint8) (api.Charger, error) { if v, err := version.NewSemver(fw); err == nil { if v.GreaterThanOrEqual(version.Must(version.NewSemver("3.156.0"))) { // firmware >= v3.156.0 supports RFID according to https://github.com/evcc-io/evcc/issues/21359 - identify = wb.identify + implement.Has(wb, implement.Identifier(wb.identify)) } } else { log.WARN.Printf("failed to parse firmware version %q: %v", string(b), err) @@ -150,7 +145,7 @@ func NewVestel(ctx context.Context, uri string, id uint8) (api.Charger, error) { } go wb.heartbeat(ctx, timeout) - return decorateVestel(wb, phasesS, phasesG, identify), nil + return wb, nil } func (wb *Vestel) heartbeat(ctx context.Context, timeout time.Duration) { diff --git a/charger/vestel_decorators.go b/charger/vestel_decorators.go deleted file mode 100644 index b498fbe90..000000000 --- a/charger/vestel_decorators.go +++ /dev/null @@ -1,68 +0,0 @@ -package charger - -// Code generated by github.com/evcc-io/evcc/cmd/tools/decorate.go. DO NOT EDIT. - -import ( - "reflect" - - "github.com/evcc-io/evcc/api" -) - -func decorateVestel(base *Vestel, phaseSwitcher func(int) error, phaseGetter func() (int, error), identifier func() (string, error)) api.Charger { - caps := make(map[reflect.Type]any) - - if phaseSwitcher != nil { - caps[reflect.TypeFor[api.PhaseSwitcher]()] = &decorateVestelPhaseSwitcherImpl{phaseSwitcher: phaseSwitcher} - } - - if phaseGetter != nil { - caps[reflect.TypeFor[api.PhaseGetter]()] = &decorateVestelPhaseGetterImpl{phaseGetter: phaseGetter} - } - - if identifier != nil { - caps[reflect.TypeFor[api.Identifier]()] = &decorateVestelIdentifierImpl{identifier: identifier} - } - - if len(caps) == 0 { - return base - } - - return &decorateVestelCapable{Vestel: base, caps: caps} -} - -type decorateVestelCapable struct { - *Vestel - caps map[reflect.Type]any -} - -func (d *decorateVestelCapable) Capability(typ reflect.Type) (any, bool) { - c, ok := d.caps[typ] - if !ok && reflect.TypeOf(d).Implements(typ) { - return d, true - } - return c, ok -} - -type decorateVestelIdentifierImpl struct { - identifier func() (string, error) -} - -func (impl *decorateVestelIdentifierImpl) Identify() (string, error) { - return impl.identifier() -} - -type decorateVestelPhaseGetterImpl struct { - phaseGetter func() (int, error) -} - -func (impl *decorateVestelPhaseGetterImpl) GetPhases() (int, error) { - return impl.phaseGetter() -} - -type decorateVestelPhaseSwitcherImpl struct { - phaseSwitcher func(int) error -} - -func (impl *decorateVestelPhaseSwitcherImpl) Phases1p3p(p0 int) error { - return impl.phaseSwitcher(p0) -} diff --git a/charger/victron.go b/charger/victron.go index f331612bb..f717edfbc 100644 --- a/charger/victron.go +++ b/charger/victron.go @@ -24,6 +24,7 @@ import ( "fmt" "github.com/evcc-io/evcc/api" + "github.com/evcc-io/evcc/api/implement" "github.com/evcc-io/evcc/util" "github.com/evcc-io/evcc/util/modbus" "github.com/spf13/cast" @@ -31,6 +32,7 @@ import ( // Victron charger implementation type Victron struct { + implement.Caps conn *modbus.Connection regs victronRegs } @@ -76,8 +78,6 @@ func init() { registry.AddCtx("victron-evcs", NewVictronEVCSFromConfig) } -//go:generate go tool decorate -f decorateVictron -b *Victron -r api.Charger -t api.PhaseSwitcher,api.PhaseGetter - // NewVictronGXFromConfig creates a Victron charger from generic config func NewVictronGXFromConfig(ctx context.Context, other map[string]any) (api.Charger, error) { return NewVictronFromConfig(ctx, other, victronGX) @@ -112,6 +112,7 @@ func NewVictron(ctx context.Context, uri string, slaveID uint8, regs victronRegs conn.Logger(log.TRACE) wb := &Victron{ + Caps: implement.New(), conn: conn, regs: regs, } @@ -125,10 +126,6 @@ func NewVictron(ctx context.Context, uri string, slaveID uint8, regs victronRegs return nil, errors.New("charger must be in manual mode") } - // phase switching (EVCS only, requires hardware modification with second contactor) - var phasesS func(int) error - var phasesG func() (int, error) - if !regs.isGX { b, err := wb.conn.ReadHoldingRegisters(wb.regs.ThreePhaseEnabled, 1) if err != nil { @@ -136,12 +133,12 @@ func NewVictron(ctx context.Context, uri string, slaveID uint8, regs victronRegs } if binary.BigEndian.Uint16(b) == 1 { - phasesS = wb.phases1p3p - phasesG = wb.getPhases + implement.Has(wb, implement.PhaseSwitcher(wb.phases1p3p)) + implement.Has(wb, implement.PhaseGetter(wb.getPhases)) } } - return decorateVictron(wb, phasesS, phasesG), nil + return wb, nil } // Status implements the api.Charger interface diff --git a/charger/victron_decorators.go b/charger/victron_decorators.go deleted file mode 100644 index 24573317a..000000000 --- a/charger/victron_decorators.go +++ /dev/null @@ -1,56 +0,0 @@ -package charger - -// Code generated by github.com/evcc-io/evcc/cmd/tools/decorate.go. DO NOT EDIT. - -import ( - "reflect" - - "github.com/evcc-io/evcc/api" -) - -func decorateVictron(base *Victron, phaseSwitcher func(int) error, phaseGetter func() (int, error)) api.Charger { - caps := make(map[reflect.Type]any) - - if phaseSwitcher != nil { - caps[reflect.TypeFor[api.PhaseSwitcher]()] = &decorateVictronPhaseSwitcherImpl{phaseSwitcher: phaseSwitcher} - } - - if phaseGetter != nil { - caps[reflect.TypeFor[api.PhaseGetter]()] = &decorateVictronPhaseGetterImpl{phaseGetter: phaseGetter} - } - - if len(caps) == 0 { - return base - } - - return &decorateVictronCapable{Victron: base, caps: caps} -} - -type decorateVictronCapable struct { - *Victron - caps map[reflect.Type]any -} - -func (d *decorateVictronCapable) Capability(typ reflect.Type) (any, bool) { - c, ok := d.caps[typ] - if !ok && reflect.TypeOf(d).Implements(typ) { - return d, true - } - return c, ok -} - -type decorateVictronPhaseGetterImpl struct { - phaseGetter func() (int, error) -} - -func (impl *decorateVictronPhaseGetterImpl) GetPhases() (int, error) { - return impl.phaseGetter() -} - -type decorateVictronPhaseSwitcherImpl struct { - phaseSwitcher func(int) error -} - -func (impl *decorateVictronPhaseSwitcherImpl) Phases1p3p(p0 int) error { - return impl.phaseSwitcher(p0) -} diff --git a/charger/warp-ws.go b/charger/warp-ws.go index 3cd5843a6..dd79ab2cc 100644 --- a/charger/warp-ws.go +++ b/charger/warp-ws.go @@ -16,6 +16,7 @@ import ( "github.com/cenkalti/backoff/v4" "github.com/coder/websocket" "github.com/evcc-io/evcc/api" + "github.com/evcc-io/evcc/api/implement" "github.com/evcc-io/evcc/charger/warp" "github.com/evcc-io/evcc/util" "github.com/evcc-io/evcc/util/request" @@ -23,6 +24,7 @@ import ( type WarpWS struct { *warp.Connection + implement.Caps pm *warp.Connection // separate Energy Manager // config @@ -54,8 +56,6 @@ func init() { registry.AddCtx("warp-ws", NewWarpWSFromConfig) } -//go:generate go tool decorate -f decorateWarpWS -b *WarpWS -r api.Charger -t api.Meter,api.MeterEnergy,api.PhaseCurrents,api.PhaseVoltages,api.Identifier,api.PhaseSwitcher,api.PhaseGetter - func NewWarpWSFromConfig(ctx context.Context, other map[string]any) (api.Charger, error) { var cc struct { URI string @@ -79,34 +79,29 @@ func NewWarpWSFromConfig(ctx context.Context, other map[string]any) (api.Charger } // Feature: Meter -> Meter is legacy API, Meters is the new API - var currentPower, totalEnergy func() (float64, error) if w.hasFeature(warp.FeatureMeter) || w.hasFeature(warp.FeatureMeters) { - currentPower = w.currentPower - totalEnergy = w.totalEnergy + implement.Has(w, implement.Meter(w.currentPower)) + implement.Has(w, implement.MeterEnergy(w.totalEnergy)) } // Feature: Meters | MeterAllValues - var currents, voltages func() (float64, float64, float64, error) if w.hasFeature(warp.FeatureMeters) || w.hasFeature(warp.FeatureMeterAllValues) { - currents = w.currents - voltages = w.voltages + implement.Has(w, implement.PhaseCurrents(w.currents)) + implement.Has(w, implement.PhaseVoltages(w.voltages)) } // Feature: NFC - var identify func() (string, error) if w.hasFeature(warp.FeatureNfc) { - identify = w.identify + implement.Has(w, implement.Identifier(w.identify)) } // Feature: Phase Switching // only setup phase switching methods if power manager endpoint is set - var phases func(int) error - var getPhases func() (int, error) if (w.hasFeature(warp.FeaturePhaseSwitch) || cc.EnergyManagerURI != "") && w.pm != nil { if res, err := w.ensurePmState(); err == nil && res.ExternalControl != warp.ExternalControlDeactivated { w.pmState = &res - phases = w.phases1p3p - getPhases = w.getPhases + implement.Has(w, implement.PhaseSwitcher(w.phases1p3p)) + implement.Has(w, implement.PhaseGetter(w.getPhases)) } } @@ -123,7 +118,7 @@ func NewWarpWSFromConfig(ctx context.Context, other map[string]any) (api.Charger w.log.TRACE.Println("disabled phase auto switching") } - return decorateWarpWS(w, currentPower, totalEnergy, currents, voltages, identify, phases, getPhases), nil + return w, nil } func NewWarpWS(ctx context.Context, uri, user, pass, emURI, emUser, emPass string, meterIndex uint) (*WarpWS, error) { @@ -131,6 +126,7 @@ func NewWarpWS(ctx context.Context, uri, user, pass, emURI, emUser, emPass strin w := &WarpWS{ Connection: warp.NewConnection(log, uri, user, pass), + Caps: implement.New(), log: log, meterIndex: meterIndex, meterMap: map[int]int{}, diff --git a/charger/warp-ws_decorators.go b/charger/warp-ws_decorators.go deleted file mode 100644 index 9c3df9117..000000000 --- a/charger/warp-ws_decorators.go +++ /dev/null @@ -1,116 +0,0 @@ -package charger - -// Code generated by github.com/evcc-io/evcc/cmd/tools/decorate.go. DO NOT EDIT. - -import ( - "reflect" - - "github.com/evcc-io/evcc/api" -) - -func decorateWarpWS(base *WarpWS, meter func() (float64, error), meterEnergy func() (float64, error), phaseCurrents func() (float64, float64, float64, error), phaseVoltages func() (float64, float64, float64, error), identifier func() (string, error), phaseSwitcher func(int) error, phaseGetter func() (int, error)) api.Charger { - caps := make(map[reflect.Type]any) - - if meter != nil { - caps[reflect.TypeFor[api.Meter]()] = &decorateWarpWSMeterImpl{meter: meter} - } - - if meterEnergy != nil { - caps[reflect.TypeFor[api.MeterEnergy]()] = &decorateWarpWSMeterEnergyImpl{meterEnergy: meterEnergy} - } - - if phaseCurrents != nil { - caps[reflect.TypeFor[api.PhaseCurrents]()] = &decorateWarpWSPhaseCurrentsImpl{phaseCurrents: phaseCurrents} - } - - if phaseVoltages != nil { - caps[reflect.TypeFor[api.PhaseVoltages]()] = &decorateWarpWSPhaseVoltagesImpl{phaseVoltages: phaseVoltages} - } - - if identifier != nil { - caps[reflect.TypeFor[api.Identifier]()] = &decorateWarpWSIdentifierImpl{identifier: identifier} - } - - if phaseSwitcher != nil { - caps[reflect.TypeFor[api.PhaseSwitcher]()] = &decorateWarpWSPhaseSwitcherImpl{phaseSwitcher: phaseSwitcher} - } - - if phaseGetter != nil { - caps[reflect.TypeFor[api.PhaseGetter]()] = &decorateWarpWSPhaseGetterImpl{phaseGetter: phaseGetter} - } - - if len(caps) == 0 { - return base - } - - return &decorateWarpWSCapable{WarpWS: base, caps: caps} -} - -type decorateWarpWSCapable struct { - *WarpWS - caps map[reflect.Type]any -} - -func (d *decorateWarpWSCapable) Capability(typ reflect.Type) (any, bool) { - c, ok := d.caps[typ] - if !ok && reflect.TypeOf(d).Implements(typ) { - return d, true - } - return c, ok -} - -type decorateWarpWSIdentifierImpl struct { - identifier func() (string, error) -} - -func (impl *decorateWarpWSIdentifierImpl) Identify() (string, error) { - return impl.identifier() -} - -type decorateWarpWSMeterImpl struct { - meter func() (float64, error) -} - -func (impl *decorateWarpWSMeterImpl) CurrentPower() (float64, error) { - return impl.meter() -} - -type decorateWarpWSMeterEnergyImpl struct { - meterEnergy func() (float64, error) -} - -func (impl *decorateWarpWSMeterEnergyImpl) TotalEnergy() (float64, error) { - return impl.meterEnergy() -} - -type decorateWarpWSPhaseCurrentsImpl struct { - phaseCurrents func() (float64, float64, float64, error) -} - -func (impl *decorateWarpWSPhaseCurrentsImpl) Currents() (float64, float64, float64, error) { - return impl.phaseCurrents() -} - -type decorateWarpWSPhaseGetterImpl struct { - phaseGetter func() (int, error) -} - -func (impl *decorateWarpWSPhaseGetterImpl) GetPhases() (int, error) { - return impl.phaseGetter() -} - -type decorateWarpWSPhaseSwitcherImpl struct { - phaseSwitcher func(int) error -} - -func (impl *decorateWarpWSPhaseSwitcherImpl) Phases1p3p(p0 int) error { - return impl.phaseSwitcher(p0) -} - -type decorateWarpWSPhaseVoltagesImpl struct { - phaseVoltages func() (float64, float64, float64, error) -} - -func (impl *decorateWarpWSPhaseVoltagesImpl) Voltages() (float64, float64, float64, error) { - return impl.phaseVoltages() -} diff --git a/charger/warp2-mqtt.go b/charger/warp2-mqtt.go index 04119bb8d..8ed3890a5 100644 --- a/charger/warp2-mqtt.go +++ b/charger/warp2-mqtt.go @@ -8,6 +8,7 @@ import ( "time" "github.com/evcc-io/evcc/api" + "github.com/evcc-io/evcc/api/implement" "github.com/evcc-io/evcc/charger/warp" "github.com/evcc-io/evcc/plugin" "github.com/evcc-io/evcc/plugin/mqtt" @@ -18,6 +19,7 @@ import ( // Warp2 is the Warp charger v2 firmware implementation type Warp2 struct { + implement.Caps log *util.Logger client *mqtt.Client features []string @@ -38,8 +40,6 @@ func init() { registry.Add("warp-fw2", NewWarp2FromConfig) // deprecated } -//go:generate go tool decorate -f decorateWarp2 -b *Warp2 -r api.Charger -t api.Meter,api.MeterEnergy,api.PhaseCurrents,api.PhaseVoltages,api.Identifier,api.PhaseSwitcher,api.PhaseGetter - // NewWarpFromConfig creates a new configurable charger func NewWarp2FromConfig(other map[string]any) (api.Charger, error) { cc := struct { @@ -61,33 +61,28 @@ func NewWarp2FromConfig(other map[string]any) (api.Charger, error) { return nil, err } - var currentPower, totalEnergy func() (float64, error) if wb.hasFeature(cc.Topic, warp.FeatureMeter, cc.Timeout) { - currentPower = wb.currentPower - totalEnergy = wb.totalEnergy + implement.May(wb, implement.Meter(wb.currentPower)) + implement.May(wb, implement.MeterEnergy(wb.totalEnergy)) } - var currents, voltages func() (float64, float64, float64, error) if wb.hasFeature(cc.Topic, warp.FeatureMeterPhases, cc.Timeout) { - currents = wb.currents - voltages = wb.voltages + implement.May(wb, implement.PhaseCurrents(wb.currents)) + implement.May(wb, implement.PhaseVoltages(wb.voltages)) } - var identity func() (string, error) if wb.hasFeature(cc.Topic, warp.FeatureNfc, cc.Timeout) { - identity = wb.identify + implement.May(wb, implement.Identifier(wb.identify)) } - var phases func(int) error - var getPhases func() (int, error) if cc.EnergyManager != "" { if res, err := wb.emState(); err == nil && res.ExternalControl != 1 { - phases = wb.phases1p3p - getPhases = wb.getPhases + implement.May(wb, implement.PhaseSwitcher(wb.phases1p3p)) + implement.May(wb, implement.PhaseGetter(wb.getPhases)) } } - return decorateWarp2(wb, currentPower, totalEnergy, currents, voltages, identity, phases, getPhases), nil + return wb, nil } // NewWarp2 creates a new configurable charger @@ -100,6 +95,7 @@ func NewWarp2(mqttconf mqtt.Config, topic, emTopic string, timeout time.Duration } wb := &Warp2{ + Caps: implement.New(), log: log, client: client, current: 6000, // mA diff --git a/charger/warp2-mqtt_decorators.go b/charger/warp2-mqtt_decorators.go deleted file mode 100644 index ecbd4ecb5..000000000 --- a/charger/warp2-mqtt_decorators.go +++ /dev/null @@ -1,116 +0,0 @@ -package charger - -// Code generated by github.com/evcc-io/evcc/cmd/tools/decorate.go. DO NOT EDIT. - -import ( - "reflect" - - "github.com/evcc-io/evcc/api" -) - -func decorateWarp2(base *Warp2, meter func() (float64, error), meterEnergy func() (float64, error), phaseCurrents func() (float64, float64, float64, error), phaseVoltages func() (float64, float64, float64, error), identifier func() (string, error), phaseSwitcher func(int) error, phaseGetter func() (int, error)) api.Charger { - caps := make(map[reflect.Type]any) - - if meter != nil { - caps[reflect.TypeFor[api.Meter]()] = &decorateWarp2MeterImpl{meter: meter} - } - - if meterEnergy != nil { - caps[reflect.TypeFor[api.MeterEnergy]()] = &decorateWarp2MeterEnergyImpl{meterEnergy: meterEnergy} - } - - if phaseCurrents != nil { - caps[reflect.TypeFor[api.PhaseCurrents]()] = &decorateWarp2PhaseCurrentsImpl{phaseCurrents: phaseCurrents} - } - - if phaseVoltages != nil { - caps[reflect.TypeFor[api.PhaseVoltages]()] = &decorateWarp2PhaseVoltagesImpl{phaseVoltages: phaseVoltages} - } - - if identifier != nil { - caps[reflect.TypeFor[api.Identifier]()] = &decorateWarp2IdentifierImpl{identifier: identifier} - } - - if phaseSwitcher != nil { - caps[reflect.TypeFor[api.PhaseSwitcher]()] = &decorateWarp2PhaseSwitcherImpl{phaseSwitcher: phaseSwitcher} - } - - if phaseGetter != nil { - caps[reflect.TypeFor[api.PhaseGetter]()] = &decorateWarp2PhaseGetterImpl{phaseGetter: phaseGetter} - } - - if len(caps) == 0 { - return base - } - - return &decorateWarp2Capable{Warp2: base, caps: caps} -} - -type decorateWarp2Capable struct { - *Warp2 - caps map[reflect.Type]any -} - -func (d *decorateWarp2Capable) Capability(typ reflect.Type) (any, bool) { - c, ok := d.caps[typ] - if !ok && reflect.TypeOf(d).Implements(typ) { - return d, true - } - return c, ok -} - -type decorateWarp2IdentifierImpl struct { - identifier func() (string, error) -} - -func (impl *decorateWarp2IdentifierImpl) Identify() (string, error) { - return impl.identifier() -} - -type decorateWarp2MeterImpl struct { - meter func() (float64, error) -} - -func (impl *decorateWarp2MeterImpl) CurrentPower() (float64, error) { - return impl.meter() -} - -type decorateWarp2MeterEnergyImpl struct { - meterEnergy func() (float64, error) -} - -func (impl *decorateWarp2MeterEnergyImpl) TotalEnergy() (float64, error) { - return impl.meterEnergy() -} - -type decorateWarp2PhaseCurrentsImpl struct { - phaseCurrents func() (float64, float64, float64, error) -} - -func (impl *decorateWarp2PhaseCurrentsImpl) Currents() (float64, float64, float64, error) { - return impl.phaseCurrents() -} - -type decorateWarp2PhaseGetterImpl struct { - phaseGetter func() (int, error) -} - -func (impl *decorateWarp2PhaseGetterImpl) GetPhases() (int, error) { - return impl.phaseGetter() -} - -type decorateWarp2PhaseSwitcherImpl struct { - phaseSwitcher func(int) error -} - -func (impl *decorateWarp2PhaseSwitcherImpl) Phases1p3p(p0 int) error { - return impl.phaseSwitcher(p0) -} - -type decorateWarp2PhaseVoltagesImpl struct { - phaseVoltages func() (float64, float64, float64, error) -} - -func (impl *decorateWarp2PhaseVoltagesImpl) Voltages() (float64, float64, float64, error) { - return impl.phaseVoltages() -} diff --git a/charger/weidmüller.go b/charger/weidmüller.go index 7a279e1a7..27b187a96 100644 --- a/charger/weidmüller.go +++ b/charger/weidmüller.go @@ -24,6 +24,7 @@ import ( "time" "github.com/evcc-io/evcc/api" + "github.com/evcc-io/evcc/api/implement" "github.com/evcc-io/evcc/util" "github.com/evcc-io/evcc/util/modbus" "github.com/evcc-io/evcc/util/sponsor" @@ -32,6 +33,7 @@ import ( // Weidmüller charger implementation type Weidmüller struct { + implement.Caps log *util.Logger conn *modbus.Connection curr uint16 @@ -70,8 +72,6 @@ func NewWeidmüllerFromConfig(ctx context.Context, other map[string]any) (api.Ch return NewWeidmüller(ctx, cc.URI, cc.ID) } -//go:generate go tool decorate -f decorateWeidmüller -b *Weidmüller -r api.Charger -t api.MeterEnergy - // NewWeidmüller creates Weidmüller charger func NewWeidmüller(ctx context.Context, uri string, id uint8) (api.Charger, error) { conn, err := modbus.NewConnection(ctx, uri, "", "", 0, modbus.Tcp, id) @@ -87,6 +87,7 @@ func NewWeidmüller(ctx context.Context, uri string, id uint8) (api.Charger, err conn.Logger(log.TRACE) wb := &Weidmüller{ + Caps: implement.New(), log: log, conn: conn, curr: 6, // assume min current @@ -106,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 { - return decorateWeidmüller(wb, wb.totalEnergy), nil + implement.Has(wb, implement.MeterEnergy(wb.totalEnergy)) } return wb, nil diff --git a/charger/weidmüller_decorators.go b/charger/weidmüller_decorators.go deleted file mode 100644 index 489556d45..000000000 --- a/charger/weidmüller_decorators.go +++ /dev/null @@ -1,44 +0,0 @@ -package charger - -// Code generated by github.com/evcc-io/evcc/cmd/tools/decorate.go. DO NOT EDIT. - -import ( - "reflect" - - "github.com/evcc-io/evcc/api" -) - -func decorateWeidmüller(base *Weidmüller, meterEnergy func() (float64, error)) api.Charger { - caps := make(map[reflect.Type]any) - - if meterEnergy != nil { - caps[reflect.TypeFor[api.MeterEnergy]()] = &decorateWeidmüllerMeterEnergyImpl{meterEnergy: meterEnergy} - } - - if len(caps) == 0 { - return base - } - - return &decorateWeidmüllerCapable{Weidmüller: base, caps: caps} -} - -type decorateWeidmüllerCapable struct { - *Weidmüller - caps map[reflect.Type]any -} - -func (d *decorateWeidmüllerCapable) Capability(typ reflect.Type) (any, bool) { - c, ok := d.caps[typ] - if !ok && reflect.TypeOf(d).Implements(typ) { - return d, true - } - return c, ok -} - -type decorateWeidmüllerMeterEnergyImpl struct { - meterEnergy func() (float64, error) -} - -func (impl *decorateWeidmüllerMeterEnergyImpl) TotalEnergy() (float64, error) { - return impl.meterEnergy() -} diff --git a/charger/zaptec.go b/charger/zaptec.go index c15516d5e..39f726bd4 100644 --- a/charger/zaptec.go +++ b/charger/zaptec.go @@ -28,6 +28,7 @@ import ( "time" "github.com/evcc-io/evcc/api" + "github.com/evcc-io/evcc/api/implement" "github.com/evcc-io/evcc/charger/zaptec" "github.com/evcc-io/evcc/util" "github.com/evcc-io/evcc/util/request" @@ -42,6 +43,7 @@ import ( // Zaptec charger implementation type Zaptec struct { *request.Helper + implement.Caps log *util.Logger statusG util.Cacheable[zaptec.StateResponse] instance zaptec.Charger @@ -56,8 +58,6 @@ func init() { registry.AddCtx("zaptec", NewZaptecFromConfig) } -//go:generate go tool decorate -f decorateZaptec -b *Zaptec -r api.Charger -t api.PhaseSwitcher - // NewZaptecFromConfig creates a Zaptec Pro charger from generic config func NewZaptecFromConfig(ctx context.Context, other map[string]any) (api.Charger, error) { cc := struct { @@ -91,6 +91,7 @@ func NewZaptec(ctx context.Context, user, password, id string, priority bool, pa c := &Zaptec{ Helper: request.NewHelper(log), + Caps: implement.New(), log: log, priority: priority, passive: passive, @@ -143,13 +144,12 @@ func NewZaptec(ctx context.Context, user, password, id string, priority bool, pa return nil, err } - var phases1p3p func(int) error if maxCurrent, err := c.getInstallationMaxCurrent(); err == nil { - phases1p3p = c.phases1p3p + implement.Has(c, implement.PhaseSwitcher(c.phases1p3p)) c.maxCurrent = maxCurrent } - return decorateZaptec(c, phases1p3p), nil + return c, nil } func (c *Zaptec) detectVersion() (int, error) { diff --git a/charger/zaptec_decorators.go b/charger/zaptec_decorators.go deleted file mode 100644 index eb486e4e6..000000000 --- a/charger/zaptec_decorators.go +++ /dev/null @@ -1,44 +0,0 @@ -package charger - -// Code generated by github.com/evcc-io/evcc/cmd/tools/decorate.go. DO NOT EDIT. - -import ( - "reflect" - - "github.com/evcc-io/evcc/api" -) - -func decorateZaptec(base *Zaptec, phaseSwitcher func(int) error) api.Charger { - caps := make(map[reflect.Type]any) - - if phaseSwitcher != nil { - caps[reflect.TypeFor[api.PhaseSwitcher]()] = &decorateZaptecPhaseSwitcherImpl{phaseSwitcher: phaseSwitcher} - } - - if len(caps) == 0 { - return base - } - - return &decorateZaptecCapable{Zaptec: base, caps: caps} -} - -type decorateZaptecCapable struct { - *Zaptec - caps map[reflect.Type]any -} - -func (d *decorateZaptecCapable) Capability(typ reflect.Type) (any, bool) { - c, ok := d.caps[typ] - if !ok && reflect.TypeOf(d).Implements(typ) { - return d, true - } - return c, ok -} - -type decorateZaptecPhaseSwitcherImpl struct { - phaseSwitcher func(int) error -} - -func (impl *decorateZaptecPhaseSwitcherImpl) Phases1p3p(p0 int) error { - return impl.phaseSwitcher(p0) -} diff --git a/cmd/decorate/decorate.go b/cmd/decorate/decorate.go deleted file mode 100644 index c9955e310..000000000 --- a/cmd/decorate/decorate.go +++ /dev/null @@ -1,348 +0,0 @@ -package main - -import ( - "bufio" - "bytes" - _ "embed" - "fmt" - "go/format" - "io" - "os" - "reflect" - "strconv" - "strings" - "text/template" - - "github.com/Masterminds/sprig/v3" - "github.com/evcc-io/evcc/api" - "github.com/spf13/pflag" - "golang.org/x/tools/imports" -) - -//go:generate go tool decorate -//evcc:function decorateTest -//evcc:basetype api.Charger -//evcc:types api.MeterEnergy,api.PhaseSwitcher,api.PhaseGetter - -//go:embed decorate.tpl -var srcTmpl string - -//go:embed header.tpl -var header string - -type funcStruct struct { - Signature, Function, VarName, ReturnTypes, BaseType, ShortType string - Params []string -} - -type typeStruct struct { - Type, ShortType string - Functions []funcStruct -} - -var interfaces = make(map[string]reflect.Type) - -func init() { - for _, typ := range []reflect.Type{ - reflect.TypeFor[api.BatteryCapacity](), - reflect.TypeFor[api.SocLimiter](), - reflect.TypeFor[api.BatteryController](), - reflect.TypeFor[api.BatterySocLimiter](), - reflect.TypeFor[api.BatteryPowerLimiter](), - reflect.TypeFor[api.PhasePowers](), - reflect.TypeFor[api.PhaseGetter](), - reflect.TypeFor[api.CurrentController](), - reflect.TypeFor[api.ChargeController](), - reflect.TypeFor[api.CurrentController](), - reflect.TypeFor[api.PhaseCurrents](), - reflect.TypeFor[api.PhaseSwitcher](), - reflect.TypeFor[api.Battery](), - reflect.TypeFor[api.ChargeState](), - reflect.TypeFor[api.MeterEnergy](), - reflect.TypeFor[api.PhaseCurrents](), - reflect.TypeFor[api.PhaseVoltages](), - reflect.TypeFor[api.MaxACPowerGetter](), - reflect.TypeFor[api.Meter](), - reflect.TypeFor[api.CurrentGetter](), - reflect.TypeFor[api.Curtailer](), - reflect.TypeFor[api.Resurrector](), - reflect.TypeFor[api.VehicleOdometer](), - reflect.TypeFor[api.VehicleRange](), - reflect.TypeFor[api.VehicleClimater](), - reflect.TypeFor[api.VehicleFinishTimer](), - reflect.TypeFor[api.VehiclePosition](), - reflect.TypeFor[api.Identifier](), - reflect.TypeFor[api.ChargerEx](), - reflect.TypeFor[api.ChargeRater](), - reflect.TypeFor[api.StatusReasoner](), - } { - interfaces[typ.String()] = typ - } -} - -func getTemplate(dtypes []reflect.Type, types map[string]typeStruct) *template.Template { - tmpl, err := template.New("gen").Funcs(sprig.FuncMap()).Funcs(template.FuncMap{ - // orderedParams returns a slice of funcStruct ordered by dynamicType - "orderedParams": func() []funcStruct { - orderedParams := make([]funcStruct, 0) - for _, t := range dtypes { - for _, f := range types[getTypeImport(t)].Functions { - orderedParams = append(orderedParams, f) - } - } - return orderedParams - }, - }).Parse(srcTmpl) - - if err != nil { - fmt.Printf("invalid template: %s", err) - os.Exit(2) - } - - return tmpl -} - -func getTypeImport(t reflect.Type) string { - n := t.Name() - if p := t.PkgPath(); p != "" { - if s := strings.Split(p, "github.com/evcc-io/evcc/"); len(s) == 2 { - return fmt.Sprintf("%s.%s", s[1], n) - } else { - return fmt.Sprintf("%s.%s", p, n) - } - } - return n -} - -func generate(out io.Writer, functionName, baseType string, dtypes []reflect.Type) error { - types := make(map[string]typeStruct) - - for _, t := range dtypes { - var funcs []funcStruct - lastPart := t.Name() - - for i := 0; i < t.NumMethod(); i++ { - m := t.Method(i) - - varName := strings.ToLower(lastPart[:1]) + lastPart[1:] - if t.NumMethod() > 1 { - varName += strconv.Itoa(i) - } - - var params []string - for input := range m.Type.Ins() { - params = append(params, getTypeImport(input)) - } - - var returns []string - for output := range m.Type.Outs() { - returns = append(returns, getTypeImport(output)) - } - - funcs = append(funcs, funcStruct{ - VarName: varName, - Signature: fmt.Sprintf("func(%s) (%s)", strings.Join(params, ", "), strings.Join(returns, ", ")), - Function: m.Name, - Params: params, - ReturnTypes: fmt.Sprintf("(%s)", strings.Join(returns, ",")), - BaseType: t.String(), - ShortType: t.Name(), - }) - } - - types[getTypeImport(t)] = typeStruct{ - Type: t.Name(), - ShortType: lastPart, - Functions: funcs, - } - } - - returnType := *ret - if returnType == "" { - returnType = baseType - } - - shortBase := strings.TrimLeft(baseType, "*") - if baseTypeParts := strings.SplitN(baseType, ".", 2); len(baseTypeParts) > 1 { - shortBase = baseTypeParts[1] - } - - vars := struct { - Function string - BaseType, ShortBase string - ReturnType string - Types map[string]typeStruct - Combinations [][]string - }{ - Function: functionName, - BaseType: baseType, - ShortBase: shortBase, - ReturnType: returnType, - Types: types, - } - - return getTemplate(dtypes, types).Execute(out, vars) -} - -type decorationSet struct { - function, base, ret, types string -} - -var ( - target = pflag.StringP("out", "o", "", "output file") - pkg = pflag.StringP("package", "p", "", "package name") - funcname = pflag.StringP("function", "f", "", "function name") - base = pflag.StringP("base", "b", "", "base type") - ret = pflag.StringP("return", "r", "", "return type") - types = pflag.StringP("type", "t", "", "comma-separated list of type definitions") -) - -// Usage prints flags usage -func Usage() { - fmt.Fprintf(os.Stderr, "Usage of decorate:\n") - fmt.Fprintf(os.Stderr, "\ndecorate [flags] -type interface,interface function,function signature\n") - fmt.Fprintf(os.Stderr, "\nFlags:\n") - pflag.PrintDefaults() -} - -func parseFile(file string) ([]decorationSet, error) { - var res []decorationSet - - f, err := os.Open(file) - if err != nil { - return nil, err - } - defer f.Close() - - var current decorationSet - - scanner := bufio.NewScanner(f) - for scanner.Scan() { - line := scanner.Text() - - if s, ok := strings.CutPrefix(line, "//evcc:"); ok { - segs := strings.SplitN(s, " ", 2) - if len(segs) != 2 { - panic("invalid segments: " + s) - } - - switch segs[0] { - case "function": - // must be first - if current.function != "" { - res = append(res, current) - current = decorationSet{} - } - current.function = segs[1] - case "basetype": - current.base = segs[1] - case "returntype": - current.ret = segs[1] - case "types": - current.types = segs[1] - default: - panic("invalid directive //evcc:" + segs[0]) - } - } - } - - if current.function != "" { - res = append(res, current) - } - - return res, scanner.Err() -} - -func main() { - pflag.Usage = Usage - pflag.Parse() - - // read target from go:generate - gofile, ok := os.LookupEnv("GOFILE") - if *target == "" && ok { - gofile := strings.TrimSuffix(gofile, ".go") + "_decorators.go" - target = &gofile - } - - // read target from go:generate - if gopkg, ok := os.LookupEnv("GOPACKAGE"); *pkg == "" && ok { - pkg = &gopkg - } - - sets := []decorationSet{{*funcname, *base, *ret, *types}} - - if *funcname == "" { - all, err := parseFile(gofile) - if err != nil { - fmt.Println(err) - os.Exit(2) - } - sets = all - } - - if *pkg == "" || len(sets) == 0 || sets[0].base == "" || len(sets[0].types) == 0 { - Usage() - os.Exit(2) - } - - var out io.Writer = os.Stdout - - var name string - if target != nil { - name = *target - if !strings.HasSuffix(name, ".go") { - name += ".go" - } - - dst, err := os.Create(name) - if err != nil { - fmt.Println(err) - os.Exit(2) - } - - defer dst.Close() - out = dst - } - - generated := new(bytes.Buffer) - fmt.Fprintln(generated, strings.ReplaceAll(header, "{{.Package}}", *pkg)) - - for _, set := range sets { - var types []reflect.Type - - for t := range strings.SplitSeq(set.types, ",") { - typ, ok := interfaces[t] - - if !ok { - fmt.Printf("don't know interface %s\n", t) - os.Exit(2) - } - - types = append(types, typ) - } - - var buf bytes.Buffer - if err := generate(&buf, set.function, set.base, types); err != nil { - fmt.Println(err) - os.Exit(2) - } - - fmt.Fprintln(generated, buf.String()) - } - - formatted, err := format.Source(generated.Bytes()) - if err != nil { - formatted = generated.Bytes() - } - - formatted, err = imports.Process(name, formatted, nil) - if err != nil { - fmt.Println(err) - os.Exit(3) - } - - if _, err := out.Write(formatted); err != nil { - fmt.Println(err) - os.Exit(2) - } -} diff --git a/cmd/decorate/decorate.tpl b/cmd/decorate/decorate.tpl deleted file mode 100644 index ca7e4660b..000000000 --- a/cmd/decorate/decorate.tpl +++ /dev/null @@ -1,48 +0,0 @@ -{{- $prefix := .Function}} -func {{.Function}}(base {{.BaseType}}{{range orderedParams}}, {{.VarName}} {{.Signature}}{{end}}) {{.ReturnType}} { - caps := make(map[reflect.Type]any) - - {{range orderedParams}} - if {{.VarName}} != nil { - caps[reflect.TypeFor[{{.BaseType}}]()] = &{{$prefix}}{{.ShortType}}Impl{ {{.VarName}}: {{.VarName}} } - } - {{end}} - - if len(caps) == 0 { - return base - } - - return &{{.Function}}Capable{ {{.ShortBase}}: base, caps: caps} -} - -type {{.Function}}Capable struct { - {{.BaseType}} - caps map[reflect.Type]any -} - -func (d *{{.Function}}Capable) Capability(typ reflect.Type) (any, bool) { - c, ok := d.caps[typ] - if !ok && reflect.TypeOf(d).Implements(typ) { - return d, true - } - return c, ok -} - -{{range $element := .Types -}} -type {{$prefix}}{{$element.ShortType}}Impl struct { - {{- range .Functions}} - {{.VarName}} {{.Signature}} - {{- end}} -} -{{range $element.Functions}} -func (impl *{{$prefix}}{{$element.ShortType}}Impl) {{.Function}}( - {{- range $idx, $param := .Params -}} - {{- if gt $idx 0}}, {{end -}} - p{{$idx}} {{ $param -}} - {{end}}){{ .ReturnTypes }} { - return impl.{{.VarName}}( - {{- range $idx, $param := .Params -}} - {{- if gt $idx 0}}, {{end}}p{{- $idx -}}{{end}}) -} -{{end}} -{{end}} diff --git a/cmd/decorate/decorate_decorators.go b/cmd/decorate/decorate_decorators.go deleted file mode 100644 index 2a3a16b4a..000000000 --- a/cmd/decorate/decorate_decorators.go +++ /dev/null @@ -1,68 +0,0 @@ -package main - -// Code generated by github.com/evcc-io/evcc/cmd/tools/decorate.go. DO NOT EDIT. - -import ( - "reflect" - - "github.com/evcc-io/evcc/api" -) - -func decorateTest(base api.Charger, meterEnergy func() (float64, error), phaseSwitcher func(int) error, phaseGetter func() (int, error)) api.Charger { - caps := make(map[reflect.Type]any) - - if meterEnergy != nil { - caps[reflect.TypeFor[api.MeterEnergy]()] = &decorateTestMeterEnergyImpl{meterEnergy: meterEnergy} - } - - if phaseSwitcher != nil { - caps[reflect.TypeFor[api.PhaseSwitcher]()] = &decorateTestPhaseSwitcherImpl{phaseSwitcher: phaseSwitcher} - } - - if phaseGetter != nil { - caps[reflect.TypeFor[api.PhaseGetter]()] = &decorateTestPhaseGetterImpl{phaseGetter: phaseGetter} - } - - if len(caps) == 0 { - return base - } - - return &decorateTestCapable{Charger: base, caps: caps} -} - -type decorateTestCapable struct { - api.Charger - caps map[reflect.Type]any -} - -func (d *decorateTestCapable) Capability(typ reflect.Type) (any, bool) { - c, ok := d.caps[typ] - if !ok && reflect.TypeOf(d).Implements(typ) { - return d, true - } - return c, ok -} - -type decorateTestMeterEnergyImpl struct { - meterEnergy func() (float64, error) -} - -func (impl *decorateTestMeterEnergyImpl) TotalEnergy() (float64, error) { - return impl.meterEnergy() -} - -type decorateTestPhaseGetterImpl struct { - phaseGetter func() (int, error) -} - -func (impl *decorateTestPhaseGetterImpl) GetPhases() (int, error) { - return impl.phaseGetter() -} - -type decorateTestPhaseSwitcherImpl struct { - phaseSwitcher func(int) error -} - -func (impl *decorateTestPhaseSwitcherImpl) Phases1p3p(p0 int) error { - return impl.phaseSwitcher(p0) -} diff --git a/cmd/decorate/header.tpl b/cmd/decorate/header.tpl deleted file mode 100644 index c242ec257..000000000 --- a/cmd/decorate/header.tpl +++ /dev/null @@ -1,9 +0,0 @@ -package {{.Package}} - -// Code generated by github.com/evcc-io/evcc/cmd/tools/decorate.go. DO NOT EDIT. - -import ( - "reflect" - - "github.com/evcc-io/evcc/api" -) diff --git a/cmd/implement/implement.go b/cmd/implement/implement.go new file mode 100644 index 000000000..a1681c4ec --- /dev/null +++ b/cmd/implement/implement.go @@ -0,0 +1,171 @@ +package main + +import ( + "bytes" + _ "embed" + "fmt" + "go/format" + "io" + "os" + "reflect" + "strconv" + "strings" + "text/template" + + "github.com/Masterminds/sprig/v3" + "github.com/evcc-io/evcc/api" + "golang.org/x/tools/imports" +) + +//go:generate go tool implement + +//go:embed implement.tpl +var srcTmpl string + +type paramStruct struct { + VarName, Signature string +} + +type funcStruct struct { + Signature, Function, VarName, ReturnTypes string + Params []paramStruct +} + +type typeStruct struct { + Type string + Functions []funcStruct +} + +func getTypeImport(t reflect.Type) string { + n := t.Name() + if p := t.PkgPath(); p != "" { + if s := strings.Split(p, "github.com/evcc-io/evcc/"); len(s) == 2 { + return fmt.Sprintf("%s.%s", s[1], n) + } else { + return fmt.Sprintf("%s.%s", p, n) + } + } + return n +} + +func generate(out io.Writer) error { + tmpl, err := template.New("gen").Funcs(sprig.FuncMap()).Parse(srcTmpl) + if err != nil { + fmt.Printf("invalid template: %s", err) + os.Exit(2) + } + + var types []typeStruct + + for _, typ := range []reflect.Type{ + reflect.TypeFor[api.Meter](), + reflect.TypeFor[api.BatteryCapacity](), + reflect.TypeFor[api.SocLimiter](), + reflect.TypeFor[api.BatteryController](), + reflect.TypeFor[api.BatterySocLimiter](), + reflect.TypeFor[api.BatteryPowerLimiter](), + reflect.TypeFor[api.PhasePowers](), + reflect.TypeFor[api.PhaseGetter](), + reflect.TypeFor[api.ChargeController](), + reflect.TypeFor[api.CurrentController](), + reflect.TypeFor[api.PhaseSwitcher](), + reflect.TypeFor[api.Battery](), + reflect.TypeFor[api.ChargeState](), + reflect.TypeFor[api.MeterEnergy](), + reflect.TypeFor[api.PhaseCurrents](), + reflect.TypeFor[api.PhaseVoltages](), + reflect.TypeFor[api.MaxACPowerGetter](), + reflect.TypeFor[api.CurrentGetter](), + reflect.TypeFor[api.Curtailer](), + reflect.TypeFor[api.Resurrector](), + reflect.TypeFor[api.VehicleOdometer](), + reflect.TypeFor[api.VehicleRange](), + reflect.TypeFor[api.VehicleClimater](), + reflect.TypeFor[api.VehicleFinishTimer](), + reflect.TypeFor[api.VehiclePosition](), + reflect.TypeFor[api.Identifier](), + reflect.TypeFor[api.ChargerEx](), + reflect.TypeFor[api.ChargeRater](), + reflect.TypeFor[api.StatusReasoner](), + } { + lastPart := typ.Name() + var functions []funcStruct + + for methodIndex := 0; methodIndex < typ.NumMethod(); methodIndex++ { + m := typ.Method(methodIndex) + + var params []paramStruct + for paramIndex := 0; paramIndex < m.Type.NumIn(); paramIndex++ { + p := m.Type.In(paramIndex) + + params = append(params, paramStruct{ + VarName: "p" + strconv.Itoa(paramIndex), + Signature: getTypeImport(p), + }) + } + + var parameters []string + for input := range m.Type.Ins() { + parameters = append(parameters, getTypeImport(input)) + } + + var returns []string + for output := range m.Type.Outs() { + returns = append(returns, getTypeImport(output)) + } + + functions = append(functions, funcStruct{ + VarName: strings.ToLower(lastPart[:1]) + lastPart[1:] + strconv.Itoa(methodIndex), + Signature: fmt.Sprintf("func(%s) (%s)", strings.Join(parameters, ", "), strings.Join(returns, ", ")), + Function: m.Name, + Params: params, + ReturnTypes: fmt.Sprintf("(%s)", strings.Join(returns, ",")), + }) + } + + types = append(types, typeStruct{ + Type: typ.Name(), + Functions: functions, + }) + } + + vars := struct { + Types []typeStruct + }{ + Types: types, + } + + return tmpl.Execute(out, vars) +} + +func main() { + name := "../../api/implement/implementations.go" + + generated := new(bytes.Buffer) + if err := generate(generated); err != nil { + fmt.Println(err) + os.Exit(2) + } + + formatted, err := format.Source(generated.Bytes()) + if err != nil { + formatted = generated.Bytes() + } + + formatted, err = imports.Process(name, formatted, nil) + if err != nil { + fmt.Println(err) + os.Exit(3) + } + + file, err := os.Create(name) + if err != nil { + fmt.Println(err) + os.Exit(2) + } + + if _, err := file.Write(formatted); err != nil { + fmt.Println(err) + os.Exit(2) + } +} diff --git a/cmd/implement/implement.tpl b/cmd/implement/implement.tpl new file mode 100644 index 000000000..bd8f244ab --- /dev/null +++ b/cmd/implement/implement.tpl @@ -0,0 +1,32 @@ +package implement + +// Code generated by github.com/evcc-io/evcc/api/implement/caps.go. DO NOT EDIT. + +import ( + "github.com/evcc-io/evcc/api" +) + + +{{range .Types}} +{{- $t := .Type}} + +func {{$t}}( {{range .Functions}} {{.VarName}} {{.Signature}}, {{end}} ) api.{{$t}} { + if {{range $i, $f := .Functions}}{{if $i}} || {{end}}{{$f.VarName}} == nil{{end}} { + return nil + } + return &i{{$t}}{ {{range .Functions}} {{.VarName}}, {{end}} } +} + +type i{{$t}} struct { + {{range .Functions}} + {{.VarName}} {{.Signature}} + {{- end}} +} + +{{range .Functions}} +func (i *i{{$t}}) {{.Function}}( {{range .Params}} {{.VarName}} {{.Signature}}, {{end}} ) {{.ReturnTypes}} { + return i.{{.VarName}}( {{range .Params}} {{.VarName}}, {{end}} ) +} +{{end}} + +{{end}} diff --git a/go.mod b/go.mod index b99d68df2..d7f3762ea 100644 --- a/go.mod +++ b/go.mod @@ -95,7 +95,6 @@ require ( github.com/spf13/cast v1.10.0 github.com/spf13/cobra v1.10.2 github.com/spf13/jwalterweatherman v1.1.0 - github.com/spf13/pflag v1.0.10 github.com/spf13/viper v1.21.0 github.com/stretchr/testify v1.11.1 github.com/teslamotors/vehicle-command v0.4.1 @@ -232,6 +231,7 @@ require ( github.com/shopspring/decimal v1.4.0 // indirect github.com/spali/go-slicereader v0.0.0-20201122145524-8e262e1a5127 // indirect github.com/spf13/afero v1.15.0 // indirect + github.com/spf13/pflag v1.0.10 // indirect github.com/stretchr/objx v0.5.3 // indirect github.com/subosito/gotenv v1.6.0 // indirect github.com/teivah/onecontext v1.3.0 // indirect @@ -257,7 +257,7 @@ require ( tool ( github.com/dmarkham/enumer - github.com/evcc-io/evcc/cmd/decorate + github.com/evcc-io/evcc/cmd/implement github.com/evcc-io/evcc/cmd/openapi github.com/evcc-io/openapi-mcp/cmd/openapi-mcp github.com/gokrazy/tools/cmd/gok diff --git a/meter/bosch_bpts5_hybrid.go b/meter/bosch_bpts5_hybrid.go index 4da116871..1984ec4c1 100644 --- a/meter/bosch_bpts5_hybrid.go +++ b/meter/bosch_bpts5_hybrid.go @@ -6,11 +6,13 @@ import ( "time" "github.com/evcc-io/evcc/api" + "github.com/evcc-io/evcc/api/implement" "github.com/evcc-io/evcc/meter/bosch" "github.com/evcc-io/evcc/util" ) type BoschBpts5Hybrid struct { + implement.Caps api *bosch.API usage string } @@ -38,23 +40,11 @@ func NewBoschBpts5HybridFromConfig(other map[string]any) (api.Meter, error) { return nil, errors.New("missing usage") } - m, err := NewBoschBpts5Hybrid(cc.URI, cc.Usage, cc.Cache) - if err != nil { - return nil, err - } - - if cc.Usage == "battery" { - return decorateMeterBattery( - m, nil, m.soc, cc.batteryCapacity.Decorator(), - cc.batterySocLimits.Decorator(), cc.batteryPowerLimits.Decorator(), nil, - ), nil - } - - return m, nil + return NewBoschBpts5Hybrid(cc.URI, cc.Usage, cc.Cache, cc.batteryCapacity.Decorator(), cc.batterySocLimits.Decorator(), cc.batteryPowerLimits.Decorator()) } // NewBoschBpts5Hybrid creates a Bosch BPT-S 5 Hybrid Meter -func NewBoschBpts5Hybrid(uri, usage string, cache time.Duration) (*BoschBpts5Hybrid, error) { +func NewBoschBpts5Hybrid(uri, usage string, cache time.Duration, capacity func() float64, batterySocLimits, batteryPowerLimits func() (float64, float64)) (*BoschBpts5Hybrid, error) { log := util.NewLogger("bosch-bpt") instance, exists := bosch.Instances.LoadOrStore(uri, bosch.NewLocal(log, uri, cache)) @@ -65,10 +55,18 @@ func NewBoschBpts5Hybrid(uri, usage string, cache time.Duration) (*BoschBpts5Hyb } m := &BoschBpts5Hybrid{ + Caps: implement.New(), api: instance.(*bosch.API), usage: strings.ToLower(usage), } + if usage == "battery" { + implement.Has(m, implement.Battery(m.soc)) + implement.May(m, implement.BatteryCapacity(capacity)) + implement.May(m, implement.BatterySocLimiter(batterySocLimits)) + implement.May(m, implement.BatteryPowerLimiter(batteryPowerLimits)) + } + return m, nil } diff --git a/meter/danfoss.go b/meter/danfoss.go index be1707ea9..75816e940 100644 --- a/meter/danfoss.go +++ b/meter/danfoss.go @@ -8,11 +8,13 @@ import ( comlynx "github.com/PanterSoft/comlynx-go" "github.com/evcc-io/evcc/api" + "github.com/evcc-io/evcc/api/implement" "github.com/evcc-io/evcc/util" ) // DanfossTLX is a PV meter for Danfoss TripleLynx TLX inverters via ComLynx RS485. type DanfossTLX struct { + implement.Caps conn *comlynx.Client powerFallback bool // some TLX variants don't support aggregate power; sum per-phase instead } @@ -79,7 +81,10 @@ func NewDanfossTLX(ctx context.Context, cfg comlynx.Config, maxACPower func() fl log.DEBUG.Printf("discovered inverter at %s", addr) } - m := &DanfossTLX{conn: conn} + m := &DanfossTLX{ + Caps: implement.New(), + conn: conn, + } // probe capabilities _, aggregatePowerErr := conn.Read(comlynx.ParamGridPowerTotal) @@ -97,29 +102,26 @@ func NewDanfossTLX(ctx context.Context, cfg comlynx.Config, maxACPower func() fl } } - // build decorator functions based on capabilities - var totalEnergy func() (float64, error) if hasEnergy == nil { - totalEnergy = m.totalEnergy + implement.Has(m, implement.MeterEnergy(m.totalEnergy)) } - - var voltages, currents, powers func() (float64, float64, float64, error) if hasVoltages == nil { - voltages = m.phaseVoltages + implement.Has(m, implement.PhaseVoltages(m.phaseVoltages)) } if hasCurrents == nil { - currents = m.phaseCurrents + implement.Has(m, implement.PhaseCurrents(m.phaseCurrents)) } if hasPowers == nil { - powers = m.phasePowers + implement.Has(m, implement.PhasePowers(m.phasePowers)) } + implement.May(m, implement.MaxACPowerGetter(maxACPower)) go func() { <-ctx.Done() _ = conn.Close() }() - return decorateMeter(m, totalEnergy, currents, voltages, powers, maxACPower), nil + return m, nil } func (m *DanfossTLX) CurrentPower() (float64, error) { diff --git a/meter/dsmr.go b/meter/dsmr.go index 0c9f73808..696cb14d8 100644 --- a/meter/dsmr.go +++ b/meter/dsmr.go @@ -16,6 +16,7 @@ import ( "github.com/basvdlei/gotsmart/dsmr" "github.com/cenkalti/backoff/v4" "github.com/evcc-io/evcc/api" + "github.com/evcc-io/evcc/api/implement" "github.com/evcc-io/evcc/util" "github.com/evcc-io/evcc/util/request" ) @@ -54,6 +55,7 @@ import ( // Dsmr meter implementation type Dsmr struct { + implement.Caps mu sync.Mutex addr string energy string @@ -71,8 +73,6 @@ func init() { registry.Add("dsmr", NewDsmrFromConfig) } -//go:generate go tool decorate -f decorateDsmr -b api.Meter -t api.MeterEnergy,api.PhaseCurrents - // NewDsmrFromConfig creates a DSMR meter from generic config func NewDsmrFromConfig(other map[string]any) (api.Meter, error) { cc := struct { @@ -93,6 +93,7 @@ func NewDsmrFromConfig(other map[string]any) (api.Meter, error) { // NewDsmr creates DSMR meter func NewDsmr(uri, energy string, timeout time.Duration) (api.Meter, error) { m := &Dsmr{ + Caps: implement.New(), addr: uri, energy: energy, timeout: timeout, @@ -114,14 +115,11 @@ func NewDsmr(uri, energy string, timeout time.Duration) (api.Meter, error) { } // decorate energy reading - var totalEnergy func() (float64, error) if energy != "" { - totalEnergy = m.totalEnergy + implement.Has(m, implement.MeterEnergy(m.totalEnergy)) } // decorate currents - var currents func() (float64, float64, float64, error) - for _, obis := range currentObis { _, err = m.get(obis) if err != nil { @@ -130,10 +128,10 @@ func NewDsmr(uri, energy string, timeout time.Duration) (api.Meter, error) { } if err == nil { - currents = m.currents + implement.Has(m, implement.PhaseCurrents(m.currents)) } - return decorateDsmr(m, totalEnergy, currents), nil + return m, nil } // based on https://github.com/basvdlei/gotsmart/blob/master/gotsmart.go diff --git a/meter/dsmr_decorators.go b/meter/dsmr_decorators.go deleted file mode 100644 index be00f203c..000000000 --- a/meter/dsmr_decorators.go +++ /dev/null @@ -1,56 +0,0 @@ -package meter - -// Code generated by github.com/evcc-io/evcc/cmd/tools/decorate.go. DO NOT EDIT. - -import ( - "reflect" - - "github.com/evcc-io/evcc/api" -) - -func decorateDsmr(base api.Meter, meterEnergy func() (float64, error), phaseCurrents func() (float64, float64, float64, error)) api.Meter { - caps := make(map[reflect.Type]any) - - if meterEnergy != nil { - caps[reflect.TypeFor[api.MeterEnergy]()] = &decorateDsmrMeterEnergyImpl{meterEnergy: meterEnergy} - } - - if phaseCurrents != nil { - caps[reflect.TypeFor[api.PhaseCurrents]()] = &decorateDsmrPhaseCurrentsImpl{phaseCurrents: phaseCurrents} - } - - if len(caps) == 0 { - return base - } - - return &decorateDsmrCapable{Meter: base, caps: caps} -} - -type decorateDsmrCapable struct { - api.Meter - caps map[reflect.Type]any -} - -func (d *decorateDsmrCapable) Capability(typ reflect.Type) (any, bool) { - c, ok := d.caps[typ] - if !ok && reflect.TypeOf(d).Implements(typ) { - return d, true - } - return c, ok -} - -type decorateDsmrMeterEnergyImpl struct { - meterEnergy func() (float64, error) -} - -func (impl *decorateDsmrMeterEnergyImpl) TotalEnergy() (float64, error) { - return impl.meterEnergy() -} - -type decorateDsmrPhaseCurrentsImpl struct { - phaseCurrents func() (float64, float64, float64, error) -} - -func (impl *decorateDsmrPhaseCurrentsImpl) Currents() (float64, float64, float64, error) { - return impl.phaseCurrents() -} diff --git a/meter/e3dc.go b/meter/e3dc.go index 925d9560f..b17fa1ac8 100644 --- a/meter/e3dc.go +++ b/meter/e3dc.go @@ -8,6 +8,7 @@ import ( "time" "github.com/evcc-io/evcc/api" + "github.com/evcc-io/evcc/api/implement" "github.com/evcc-io/evcc/util" "github.com/evcc-io/evcc/util/request" "github.com/evcc-io/evcc/util/templates" @@ -17,6 +18,7 @@ import ( ) type E3dc struct { + implement.Caps mu sync.Mutex dischargeLimit uint32 externalPower bool // whether to include power of external sources @@ -29,8 +31,6 @@ func init() { registry.Add("e3dc-rscp", NewE3dcFromConfig) } -//go:generate go tool decorate -f decorateE3dc -b *E3dc -r api.Meter -t api.Battery,api.BatteryCapacity,api.BatteryController,api.BatterySocLimiter,api.BatteryPowerLimiter,api.MaxACPowerGetter - func NewE3dcFromConfig(other map[string]any) (api.Meter, error) { cc := struct { batteryCapacity `mapstructure:",squash"` @@ -89,6 +89,7 @@ func NewE3dc(cfg rscp.ClientConfig, usage templates.Usage, dischargeLimit uint32 } m := &E3dc{ + Caps: implement.New(), usage: usage, conn: conn, externalPower: externalPower, @@ -101,20 +102,17 @@ func NewE3dc(cfg rscp.ClientConfig, usage templates.Usage, dischargeLimit uint32 return err } - // decorate battery - var ( - batteryCapacity func() float64 - batterySoc func() (float64, error) - batteryMode func(api.BatteryMode) error - ) + implement.May(m, implement.BatterySocLimiter(batterySocLimits)) + implement.May(m, implement.BatteryPowerLimiter(batteryPowerLimits)) + implement.May(m, implement.MaxACPowerGetter(maxacpower)) if usage == templates.UsageBattery { - batteryCapacity = capacity - batterySoc = m.batterySoc - batteryMode = m.setBatteryMode + implement.May(m, implement.BatteryCapacity(capacity)) + implement.Has(m, implement.Battery(m.batterySoc)) + implement.Has(m, implement.BatteryController(m.setBatteryMode)) } - return decorateE3dc(m, batterySoc, batteryCapacity, batteryMode, batterySocLimits, batteryPowerLimits, maxacpower), nil + return m, nil } // retryMessage executes a single message request with retry diff --git a/meter/e3dc_decorators.go b/meter/e3dc_decorators.go deleted file mode 100644 index 82d479a3e..000000000 --- a/meter/e3dc_decorators.go +++ /dev/null @@ -1,104 +0,0 @@ -package meter - -// Code generated by github.com/evcc-io/evcc/cmd/tools/decorate.go. DO NOT EDIT. - -import ( - "reflect" - - "github.com/evcc-io/evcc/api" -) - -func decorateE3dc(base *E3dc, battery func() (float64, error), batteryCapacity func() float64, batteryController func(api.BatteryMode) error, batterySocLimiter func() (float64, float64), batteryPowerLimiter func() (float64, float64), maxACPowerGetter func() float64) api.Meter { - caps := make(map[reflect.Type]any) - - if battery != nil { - caps[reflect.TypeFor[api.Battery]()] = &decorateE3dcBatteryImpl{battery: battery} - } - - if batteryCapacity != nil { - caps[reflect.TypeFor[api.BatteryCapacity]()] = &decorateE3dcBatteryCapacityImpl{batteryCapacity: batteryCapacity} - } - - if batteryController != nil { - caps[reflect.TypeFor[api.BatteryController]()] = &decorateE3dcBatteryControllerImpl{batteryController: batteryController} - } - - if batterySocLimiter != nil { - caps[reflect.TypeFor[api.BatterySocLimiter]()] = &decorateE3dcBatterySocLimiterImpl{batterySocLimiter: batterySocLimiter} - } - - if batteryPowerLimiter != nil { - caps[reflect.TypeFor[api.BatteryPowerLimiter]()] = &decorateE3dcBatteryPowerLimiterImpl{batteryPowerLimiter: batteryPowerLimiter} - } - - if maxACPowerGetter != nil { - caps[reflect.TypeFor[api.MaxACPowerGetter]()] = &decorateE3dcMaxACPowerGetterImpl{maxACPowerGetter: maxACPowerGetter} - } - - if len(caps) == 0 { - return base - } - - return &decorateE3dcCapable{E3dc: base, caps: caps} -} - -type decorateE3dcCapable struct { - *E3dc - caps map[reflect.Type]any -} - -func (d *decorateE3dcCapable) Capability(typ reflect.Type) (any, bool) { - c, ok := d.caps[typ] - if !ok && reflect.TypeOf(d).Implements(typ) { - return d, true - } - return c, ok -} - -type decorateE3dcBatteryImpl struct { - battery func() (float64, error) -} - -func (impl *decorateE3dcBatteryImpl) Soc() (float64, error) { - return impl.battery() -} - -type decorateE3dcBatteryCapacityImpl struct { - batteryCapacity func() float64 -} - -func (impl *decorateE3dcBatteryCapacityImpl) Capacity() float64 { - return impl.batteryCapacity() -} - -type decorateE3dcBatteryControllerImpl struct { - batteryController func(api.BatteryMode) error -} - -func (impl *decorateE3dcBatteryControllerImpl) SetBatteryMode(p0 api.BatteryMode) error { - return impl.batteryController(p0) -} - -type decorateE3dcBatteryPowerLimiterImpl struct { - batteryPowerLimiter func() (float64, float64) -} - -func (impl *decorateE3dcBatteryPowerLimiterImpl) GetPowerLimits() (float64, float64) { - return impl.batteryPowerLimiter() -} - -type decorateE3dcBatterySocLimiterImpl struct { - batterySocLimiter func() (float64, float64) -} - -func (impl *decorateE3dcBatterySocLimiterImpl) GetSocLimits() (float64, float64) { - return impl.batterySocLimiter() -} - -type decorateE3dcMaxACPowerGetterImpl struct { - maxACPowerGetter func() float64 -} - -func (impl *decorateE3dcMaxACPowerGetterImpl) MaxACPower() float64 { - return impl.maxACPowerGetter() -} diff --git a/meter/ecoflow.go b/meter/ecoflow.go index 59992009b..6109dbfc9 100644 --- a/meter/ecoflow.go +++ b/meter/ecoflow.go @@ -7,6 +7,7 @@ import ( "time" "github.com/evcc-io/evcc/api" + "github.com/evcc-io/evcc/api/implement" "github.com/evcc-io/evcc/util" "github.com/evcc-io/evcc/util/request" "github.com/spf13/cast" @@ -15,6 +16,7 @@ import ( // EcoFlow represents the EcoFlow meter type EcoFlow struct { + implement.Caps usage string serial string cache time.Duration @@ -70,27 +72,16 @@ func NewEcoFlowFromConfig(other map[string]any) (api.Meter, error) { return nil, fmt.Errorf("invalid region: %s", cc.Region) } - m, err := NewEcoFlow(cc.AccessKey, cc.SecretKey, cc.Serial, cc.Usage, uri, cc.Power, cc.Soc, cc.Cache) - if err != nil { - return nil, err - } - - if cc.Usage == "battery" { - return decorateMeterBattery( - m, nil, m.soc, cc.batteryCapacity.Decorator(), - cc.batterySocLimits.Decorator(), cc.batteryPowerLimits.Decorator(), nil, - ), nil - } - - return m, nil + return NewEcoFlow(cc.AccessKey, cc.SecretKey, cc.Serial, cc.Usage, uri, cc.Power, cc.Soc, cc.Cache, cc.batteryCapacity.Decorator(), cc.batterySocLimits.Decorator(), cc.batteryPowerLimits.Decorator()) } // NewEcoFlow constructs the EcoFlow struct func NewEcoFlow(accessKey, secretKey, serial, usage, uri string, - power, soc string, cache time.Duration) (*EcoFlow, error) { + power, soc string, cache time.Duration, capacity func() float64, batterySocLimits, batteryPowerLimits func() (float64, float64)) (*EcoFlow, error) { log := util.NewLogger("ecoflow").Redact(accessKey, secretKey, serial) m := &EcoFlow{ + Caps: implement.New(), serial: serial, usage: usage, cache: cache, @@ -104,6 +95,13 @@ func NewEcoFlow(accessKey, secretKey, serial, usage, uri string, m.dataG = util.Cached(m.getData, cache) + if usage == "battery" { + implement.Has(m, implement.Battery(m.soc)) + implement.May(m, implement.BatteryCapacity(capacity)) + implement.May(m, implement.BatterySocLimiter(batterySocLimits)) + implement.May(m, implement.BatteryPowerLimiter(batteryPowerLimits)) + } + return m, nil } diff --git a/meter/goodwe-wifi.go b/meter/goodwe-wifi.go index c4419a7a4..4a72007c0 100644 --- a/meter/goodwe-wifi.go +++ b/meter/goodwe-wifi.go @@ -4,12 +4,14 @@ import ( "time" "github.com/evcc-io/evcc/api" + "github.com/evcc-io/evcc/api/implement" "github.com/evcc-io/evcc/meter/goodwe" "github.com/evcc-io/evcc/util" "github.com/evcc-io/evcc/util/request" ) type goodWeWiFi struct { + implement.Caps usage string inverter *util.Monitor[goodwe.Inverter] } @@ -18,8 +20,6 @@ func init() { registry.Add("goodwe-wifi", NewGoodWeWifiFromConfig) } -//go:generate go tool decorate -f decorateGoodWeWifi -b *goodWeWiFi -r api.Meter -t api.Battery,api.BatteryCapacity - // TODO deprecated func NewGoodWeWifiFromConfig(other map[string]any) (api.Meter, error) { @@ -50,21 +50,17 @@ func NewGoodWeWiFi(uri, usage string, capacity func() float64, timeout time.Dura } res := &goodWeWiFi{ + Caps: implement.New(), usage: usage, inverter: inverter, } - // decorate battery - var ( - batterySoc func() (float64, error) - batteryCapacity func() float64 - ) if usage == "battery" { - batterySoc = res.batterySoc - batteryCapacity = capacity + implement.Has(res, implement.Battery(res.batterySoc)) + implement.May(res, implement.BatteryCapacity(capacity)) } - return decorateGoodWeWifi(res, batterySoc, batteryCapacity), nil + return res, nil } func (m *goodWeWiFi) CurrentPower() (float64, error) { diff --git a/meter/goodwe-wifi_decorators.go b/meter/goodwe-wifi_decorators.go deleted file mode 100644 index b0d41e2c7..000000000 --- a/meter/goodwe-wifi_decorators.go +++ /dev/null @@ -1,56 +0,0 @@ -package meter - -// Code generated by github.com/evcc-io/evcc/cmd/tools/decorate.go. DO NOT EDIT. - -import ( - "reflect" - - "github.com/evcc-io/evcc/api" -) - -func decorateGoodWeWifi(base *goodWeWiFi, battery func() (float64, error), batteryCapacity func() float64) api.Meter { - caps := make(map[reflect.Type]any) - - if battery != nil { - caps[reflect.TypeFor[api.Battery]()] = &decorateGoodWeWifiBatteryImpl{battery: battery} - } - - if batteryCapacity != nil { - caps[reflect.TypeFor[api.BatteryCapacity]()] = &decorateGoodWeWifiBatteryCapacityImpl{batteryCapacity: batteryCapacity} - } - - if len(caps) == 0 { - return base - } - - return &decorateGoodWeWifiCapable{goodWeWiFi: base, caps: caps} -} - -type decorateGoodWeWifiCapable struct { - *goodWeWiFi - caps map[reflect.Type]any -} - -func (d *decorateGoodWeWifiCapable) Capability(typ reflect.Type) (any, bool) { - c, ok := d.caps[typ] - if !ok && reflect.TypeOf(d).Implements(typ) { - return d, true - } - return c, ok -} - -type decorateGoodWeWifiBatteryImpl struct { - battery func() (float64, error) -} - -func (impl *decorateGoodWeWifiBatteryImpl) Soc() (float64, error) { - return impl.battery() -} - -type decorateGoodWeWifiBatteryCapacityImpl struct { - batteryCapacity func() float64 -} - -func (impl *decorateGoodWeWifiBatteryCapacityImpl) Capacity() float64 { - return impl.batteryCapacity() -} diff --git a/meter/homeassistant.go b/meter/homeassistant.go index c6bcdcaf0..58c14175b 100644 --- a/meter/homeassistant.go +++ b/meter/homeassistant.go @@ -5,6 +5,7 @@ import ( "fmt" "github.com/evcc-io/evcc/api" + "github.com/evcc-io/evcc/api/implement" "github.com/evcc-io/evcc/util" "github.com/evcc-io/evcc/util/homeassistant" ) @@ -88,18 +89,22 @@ 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)) + if cc.Soc != "" { socG := func() (float64, error) { return conn.GetFloatState(cc.Soc) } - return m.DecorateBattery( - energyG, - socG, cc.batteryCapacity.Decorator(), - cc.batterySocLimits.Decorator(), cc.batteryPowerLimits.Decorator(), - nil, - ), nil + implement.Has(m, implement.Battery(socG)) + implement.May(m, implement.BatteryCapacity(cc.batteryCapacity.Decorator())) + implement.May(m, implement.BatterySocLimiter(cc.batterySocLimits.Decorator())) + implement.May(m, implement.BatteryPowerLimiter(cc.batteryPowerLimits.Decorator())) + return m, nil } - return m.Decorate( - energyG, currentsG, voltagesG, powersG, cc.pvMaxACPower.Decorator(), - ), nil + implement.May(m, implement.PhaseCurrents(currentsG)) + implement.May(m, implement.PhaseVoltages(voltagesG)) + implement.May(m, implement.PhasePowers(powersG)) + implement.May(m, implement.MaxACPowerGetter(cc.pvMaxACPower.Decorator())) + + return m, nil } diff --git a/meter/lgess.go b/meter/lgess.go index 246a77bd9..2052530f9 100644 --- a/meter/lgess.go +++ b/meter/lgess.go @@ -7,12 +7,14 @@ import ( "time" "github.com/evcc-io/evcc/api" + "github.com/evcc-io/evcc/api/implement" "github.com/evcc-io/evcc/meter/lgpcs" "github.com/evcc-io/evcc/util" ) // LgEss implements the api.Meter interface type LgEss struct { + implement.Caps usage string // grid, pv, battery conn *lgpcs.Com // communication with the lgpcs device } @@ -22,8 +24,6 @@ func init() { registry.Add("lgess15", NewLgEss15FromConfig) } -//go:generate go tool decorate -f decorateLgEss -b *LgEss -r api.Meter -t api.MeterEnergy,api.BatteryCapacity,api.Battery,api.BatterySocLimiter,api.BatteryPowerLimiter,api.BatteryController - func NewLgEss8FromConfig(other map[string]any) (api.Meter, error) { return NewLgEssFromConfig(other, lgpcs.LgEss8) } @@ -57,41 +57,39 @@ func NewLgEssFromConfig(other map[string]any, essType lgpcs.Model) (api.Meter, e return nil, errors.New("missing usage") } - return NewLgEss(cc.URI, cc.Usage, cc.Registration, cc.Password, cc.Cache, cc.batteryCapacity.Decorator(), cc.batterySocLimits, cc.batteryPowerLimits, essType) + return NewLgEss(cc.URI, cc.Usage, cc.Registration, cc.Password, cc.Cache, cc.batteryCapacity, cc.batterySocLimits, cc.batteryPowerLimits, essType) } // NewLgEss creates an LgEss Meter -func NewLgEss(uri, usage, registration, password string, cache time.Duration, capacity func() float64, batterySocLimits batterySocLimits, batteryPowerLimits batteryPowerLimits, essType lgpcs.Model) (api.Meter, error) { +func NewLgEss(uri, usage, registration, password string, cache time.Duration, batteryCapacity batteryCapacity, batterySocLimits batterySocLimits, batteryPowerLimits batteryPowerLimits, essType lgpcs.Model) (api.Meter, error) { conn, err := lgpcs.GetInstance(uri, registration, password, cache, essType) if err != nil { return nil, err } m := &LgEss{ + Caps: implement.New(), usage: strings.ToLower(usage), conn: conn, } - // decorate api.MeterEnergy - var totalEnergy func() (float64, error) + implement.May(m, implement.BatteryCapacity(batteryCapacity.Decorator())) + implement.May(m, implement.BatteryPowerLimiter(batteryPowerLimits.Decorator())) + if m.usage == "grid" && essType != lgpcs.LgEss15 { - totalEnergy = m.totalEnergy + implement.Has(m, implement.MeterEnergy(m.totalEnergy)) } - // decorate battery - var batterySoc func() (float64, error) - var batterySocLimiter func() (float64, float64) - var setBatteryMode func(api.BatteryMode) error if usage == "battery" { - batterySoc = m.batterySoc - batterySocLimiter = batterySocLimits.Decorator() + implement.Has(m, implement.Battery(m.batterySoc)) + implement.May(m, implement.BatterySocLimiter(batterySocLimits.Decorator())) if version, err := conn.GetFirmwareVersion(); err == nil && version >= 7430 { - setBatteryMode = m.batteryMode(batterySocLimits) + implement.Has(m, implement.BatteryController(m.batteryMode(batterySocLimits))) } } - return decorateLgEss(m, totalEnergy, capacity, batterySoc, batterySocLimiter, batteryPowerLimits.Decorator(), setBatteryMode), nil + return m, nil } // CurrentPower implements the api.Meter interface diff --git a/meter/lgess_decorators.go b/meter/lgess_decorators.go deleted file mode 100644 index 4c607cb6b..000000000 --- a/meter/lgess_decorators.go +++ /dev/null @@ -1,104 +0,0 @@ -package meter - -// Code generated by github.com/evcc-io/evcc/cmd/tools/decorate.go. DO NOT EDIT. - -import ( - "reflect" - - "github.com/evcc-io/evcc/api" -) - -func decorateLgEss(base *LgEss, meterEnergy func() (float64, error), batteryCapacity func() float64, battery func() (float64, error), batterySocLimiter func() (float64, float64), batteryPowerLimiter func() (float64, float64), batteryController func(api.BatteryMode) error) api.Meter { - caps := make(map[reflect.Type]any) - - if meterEnergy != nil { - caps[reflect.TypeFor[api.MeterEnergy]()] = &decorateLgEssMeterEnergyImpl{meterEnergy: meterEnergy} - } - - if batteryCapacity != nil { - caps[reflect.TypeFor[api.BatteryCapacity]()] = &decorateLgEssBatteryCapacityImpl{batteryCapacity: batteryCapacity} - } - - if battery != nil { - caps[reflect.TypeFor[api.Battery]()] = &decorateLgEssBatteryImpl{battery: battery} - } - - if batterySocLimiter != nil { - caps[reflect.TypeFor[api.BatterySocLimiter]()] = &decorateLgEssBatterySocLimiterImpl{batterySocLimiter: batterySocLimiter} - } - - if batteryPowerLimiter != nil { - caps[reflect.TypeFor[api.BatteryPowerLimiter]()] = &decorateLgEssBatteryPowerLimiterImpl{batteryPowerLimiter: batteryPowerLimiter} - } - - if batteryController != nil { - caps[reflect.TypeFor[api.BatteryController]()] = &decorateLgEssBatteryControllerImpl{batteryController: batteryController} - } - - if len(caps) == 0 { - return base - } - - return &decorateLgEssCapable{LgEss: base, caps: caps} -} - -type decorateLgEssCapable struct { - *LgEss - caps map[reflect.Type]any -} - -func (d *decorateLgEssCapable) Capability(typ reflect.Type) (any, bool) { - c, ok := d.caps[typ] - if !ok && reflect.TypeOf(d).Implements(typ) { - return d, true - } - return c, ok -} - -type decorateLgEssBatteryImpl struct { - battery func() (float64, error) -} - -func (impl *decorateLgEssBatteryImpl) Soc() (float64, error) { - return impl.battery() -} - -type decorateLgEssBatteryCapacityImpl struct { - batteryCapacity func() float64 -} - -func (impl *decorateLgEssBatteryCapacityImpl) Capacity() float64 { - return impl.batteryCapacity() -} - -type decorateLgEssBatteryControllerImpl struct { - batteryController func(api.BatteryMode) error -} - -func (impl *decorateLgEssBatteryControllerImpl) SetBatteryMode(p0 api.BatteryMode) error { - return impl.batteryController(p0) -} - -type decorateLgEssBatteryPowerLimiterImpl struct { - batteryPowerLimiter func() (float64, float64) -} - -func (impl *decorateLgEssBatteryPowerLimiterImpl) GetPowerLimits() (float64, float64) { - return impl.batteryPowerLimiter() -} - -type decorateLgEssBatterySocLimiterImpl struct { - batterySocLimiter func() (float64, float64) -} - -func (impl *decorateLgEssBatterySocLimiterImpl) GetSocLimits() (float64, float64) { - return impl.batterySocLimiter() -} - -type decorateLgEssMeterEnergyImpl struct { - meterEnergy func() (float64, error) -} - -func (impl *decorateLgEssMeterEnergyImpl) TotalEnergy() (float64, error) { - return impl.meterEnergy() -} diff --git a/meter/mbmd.go b/meter/mbmd.go index 5dfe382ed..be773b5b9 100644 --- a/meter/mbmd.go +++ b/meter/mbmd.go @@ -8,6 +8,7 @@ import ( "time" "github.com/evcc-io/evcc/api" + "github.com/evcc-io/evcc/api/implement" "github.com/evcc-io/evcc/meter/measurement" "github.com/evcc-io/evcc/util" "github.com/evcc-io/evcc/util/modbus" @@ -91,16 +92,30 @@ func NewMbmdFromConfig(ctx context.Context, other map[string]any) (api.Meter, er if err != nil { return nil, fmt.Errorf("invalid measurement for power: %s", cc.Power) } + m, _ := NewConfigurable(powerG) // decorate energy - var totalEnergy func() (float64, error) if cc.Energy != "" { - g, err := mbmd.deviceOp(ops, cc.Energy) + totalEnergy, err := mbmd.deviceOp(ops, cc.Energy) if err != nil { return nil, fmt.Errorf("invalid measurement for energy: %s", cc.Energy) } + implement.Has(m, implement.MeterEnergy(totalEnergy)) + } - totalEnergy = g + // decorate soc + if cc.Soc != "" { + soc, err := mbmd.deviceOp(ops, cc.Soc) + if err != nil { + return nil, fmt.Errorf("invalid measurement for soc: %s", cc.Soc) + } + implement.Has(m, implement.Battery(soc)) + + implement.May(m, implement.BatteryCapacity(cc.batteryCapacity.Decorator())) + implement.May(m, implement.BatterySocLimiter(cc.batterySocLimits.Decorator())) + implement.May(m, implement.BatteryPowerLimiter(cc.batteryPowerLimits.Decorator())) + + return m, nil } // decorate currents @@ -108,37 +123,23 @@ func NewMbmdFromConfig(ctx context.Context, other map[string]any) (api.Meter, er if err != nil { return nil, fmt.Errorf("currents: %w", err) } + implement.May(m, implement.PhaseCurrents(currentsG)) // decorate voltages voltagesG, err := mbmd.buildPhaseProviders(ops, cc.Voltages) if err != nil { return nil, fmt.Errorf("voltages: %w", err) } + implement.May(m, implement.PhaseVoltages(voltagesG)) // decorate powers powersG, err := mbmd.buildPhaseProviders(ops, cc.Powers) if err != nil { return nil, fmt.Errorf("powers: %w", err) } + implement.May(m, implement.PhasePowers(powersG)) - // decorate soc - var soc func() (float64, error) - if cc.Soc != "" { - g, err := mbmd.deviceOp(ops, cc.Soc) - if err != nil { - return nil, fmt.Errorf("invalid measurement for soc: %s", cc.Soc) - } - - soc = g - } - - m, _ := NewConfigurable(powerG) - - if soc != nil { - return m.DecorateBattery(totalEnergy, soc, cc.batteryCapacity.Decorator(), cc.batterySocLimits.Decorator(), cc.batteryPowerLimits.Decorator(), nil), nil - } - - return m.Decorate(totalEnergy, currentsG, voltagesG, powersG, nil), nil + return m, nil } // deviceOp checks is RS485 device supports operation diff --git a/meter/meter.go b/meter/meter.go index 6194f2018..21b265c4a 100644 --- a/meter/meter.go +++ b/meter/meter.go @@ -5,6 +5,7 @@ import ( "fmt" "github.com/evcc-io/evcc/api" + "github.com/evcc-io/evcc/api/implement" "github.com/evcc-io/evcc/meter/measurement" "github.com/evcc-io/evcc/plugin" "github.com/evcc-io/evcc/util" @@ -14,16 +15,6 @@ func init() { registry.AddCtx(api.Custom, NewConfigurableFromConfig) } -//go:generate go tool decorate - -//evcc:function decorateMeter -//evcc:basetype api.Meter -//evcc:types api.MeterEnergy,api.PhaseCurrents,api.PhaseVoltages,api.PhasePowers,api.MaxACPowerGetter - -//evcc:function decorateMeterBattery -//evcc:basetype api.Meter -//evcc:types api.MeterEnergy,api.Battery,api.BatteryCapacity,api.BatterySocLimiter,api.BatteryPowerLimiter,api.BatteryController - // NewConfigurableFromConfig creates a new meter from config func NewConfigurableFromConfig(ctx context.Context, other map[string]any) (api.Meter, error) { cc := struct { @@ -56,12 +47,8 @@ func NewConfigurableFromConfig(ctx context.Context, other map[string]any) (api.M return nil, err } - currentsG, voltagesG, powersG, err := cc.Phases.Configure(ctx) - if err != nil { - return nil, err - } - m, _ := NewConfigurable(powerG) + implement.May(m, implement.MeterEnergy(energyG)) // decorate soc socG, err := cc.Soc.FloatGetter(ctx) @@ -69,45 +56,52 @@ func NewConfigurableFromConfig(ctx context.Context, other map[string]any) (api.M return nil, fmt.Errorf("battery soc: %w", err) } - var batModeS func(api.BatteryMode) error - - switch { - case cc.Soc != nil && cc.LimitSoc != nil: - limitSocS, err := cc.LimitSoc.FloatSetter(ctx, "limitSoc") - if err != nil { - return nil, fmt.Errorf("battery limit soc: %w", err) - } - - batModeS = cc.batterySocLimits.LimitController(socG, limitSocS) - - case cc.BatteryMode != nil: - modeS, err := cc.BatteryMode.IntSetter(ctx, "batteryMode") - if err != nil { - return nil, fmt.Errorf("battery mode: %w", err) - } - - batModeS = func(mode api.BatteryMode) error { - return modeS(int64(mode)) - } - } - if socG != nil { - return m.DecorateBattery( - energyG, - socG, cc.batteryCapacity.Decorator(), - cc.batterySocLimits.Decorator(), cc.batteryPowerLimits.Decorator(), - batModeS, - ), nil + implement.Has(m, implement.Battery(socG)) + implement.May(m, implement.BatteryCapacity(cc.batteryCapacity.Decorator())) + implement.May(m, implement.BatterySocLimiter(cc.batterySocLimits.Decorator())) + implement.May(m, implement.BatteryPowerLimiter(cc.batteryPowerLimits.Decorator())) + + switch { + case cc.Soc != nil && cc.LimitSoc != nil: + limitSocS, err := cc.LimitSoc.FloatSetter(ctx, "limitSoc") + if err != nil { + return nil, fmt.Errorf("battery limit soc: %w", err) + } + + implement.Has(m, implement.BatteryController(cc.batterySocLimits.LimitController(socG, limitSocS))) + + case cc.BatteryMode != nil: + modeS, err := cc.BatteryMode.IntSetter(ctx, "batteryMode") + if err != nil { + return nil, fmt.Errorf("battery mode: %w", err) + } + + implement.Has(m, implement.BatteryController(func(mode api.BatteryMode) error { + return modeS(int64(mode)) + })) + } + + return m, nil } - return m.Decorate( - energyG, currentsG, voltagesG, powersG, cc.pvMaxACPower.Decorator(), - ), nil + currentsG, voltagesG, powersG, err := cc.Phases.Configure(ctx) + if err != nil { + return nil, err + } + + implement.May(m, implement.PhaseCurrents(currentsG)) + implement.May(m, implement.PhaseVoltages(voltagesG)) + implement.May(m, implement.PhasePowers(powersG)) + implement.May(m, implement.MaxACPowerGetter(cc.pvMaxACPower.Decorator())) + + return m, nil } // NewConfigurable creates a new meter func NewConfigurable(currentPowerG func() (float64, error)) (*Meter, error) { m := &Meter{ + Caps: implement.New(), currentPowerG: currentPowerG, } return m, nil @@ -115,35 +109,10 @@ func NewConfigurable(currentPowerG func() (float64, error)) (*Meter, error) { // Meter is an api.Meter implementation with configurable getters and setters. type Meter struct { + implement.Caps currentPowerG func() (float64, error) } -// Decorate attaches additional capabilities to the base meter -func (m *Meter) Decorate( - totalEnergy func() (float64, error), - currents, voltages, powers func() (float64, float64, float64, error), - maxACPower func() float64, -) api.Meter { - return decorateMeter(m, - totalEnergy, currents, voltages, powers, - maxACPower, - ) -} - -func (m *Meter) DecorateBattery( - totalEnergy func() (float64, error), - soc func() (float64, error), capacity func() float64, - socLimits, powerLimits func() (float64, float64), - setMode func(api.BatteryMode) error, -) api.Meter { - return decorateMeterBattery(m, - totalEnergy, - soc, capacity, - socLimits, powerLimits, - setMode, - ) -} - // CurrentPower implements the api.Meter interface func (m *Meter) CurrentPower() (float64, error) { return m.currentPowerG() diff --git a/meter/meter_average.go b/meter/meter_average.go index e0dd97d6f..f217f75ae 100644 --- a/meter/meter_average.go +++ b/meter/meter_average.go @@ -4,6 +4,7 @@ import ( "context" "github.com/evcc-io/evcc/api" + "github.com/evcc-io/evcc/api/implement" "github.com/evcc-io/evcc/util" ) @@ -70,11 +71,19 @@ func NewMovingAverageFromConfig(ctx context.Context, other map[string]any) (api. powers = m.Powers } + implement.May(meter, implement.MeterEnergy(totalEnergy)) + if batterySoc != nil { - return meter.DecorateBattery(totalEnergy, batterySoc, cc.Meter.batteryCapacity.Decorator(), nil, nil, nil), nil + implement.Has(meter, implement.Battery(batterySoc)) + implement.May(meter, implement.BatteryCapacity(cc.Meter.batteryCapacity.Decorator())) + return meter, nil } - return meter.Decorate(totalEnergy, currents, voltages, powers, nil), nil + implement.May(meter, implement.PhaseCurrents(currents)) + implement.May(meter, implement.PhaseVoltages(voltages)) + implement.May(meter, implement.PhasePowers(powers)) + + return meter, nil } type MovingAverage struct { diff --git a/meter/meter_decorators.go b/meter/meter_decorators.go deleted file mode 100644 index 71e1399c7..000000000 --- a/meter/meter_decorators.go +++ /dev/null @@ -1,187 +0,0 @@ -package meter - -// Code generated by github.com/evcc-io/evcc/cmd/tools/decorate.go. DO NOT EDIT. - -import ( - "reflect" - - "github.com/evcc-io/evcc/api" -) - -func decorateMeter(base api.Meter, meterEnergy func() (float64, error), phaseCurrents func() (float64, float64, float64, error), phaseVoltages func() (float64, float64, float64, error), phasePowers func() (float64, float64, float64, error), maxACPowerGetter func() float64) api.Meter { - caps := make(map[reflect.Type]any) - - if meterEnergy != nil { - caps[reflect.TypeFor[api.MeterEnergy]()] = &decorateMeterMeterEnergyImpl{meterEnergy: meterEnergy} - } - - if phaseCurrents != nil { - caps[reflect.TypeFor[api.PhaseCurrents]()] = &decorateMeterPhaseCurrentsImpl{phaseCurrents: phaseCurrents} - } - - if phaseVoltages != nil { - caps[reflect.TypeFor[api.PhaseVoltages]()] = &decorateMeterPhaseVoltagesImpl{phaseVoltages: phaseVoltages} - } - - if phasePowers != nil { - caps[reflect.TypeFor[api.PhasePowers]()] = &decorateMeterPhasePowersImpl{phasePowers: phasePowers} - } - - if maxACPowerGetter != nil { - caps[reflect.TypeFor[api.MaxACPowerGetter]()] = &decorateMeterMaxACPowerGetterImpl{maxACPowerGetter: maxACPowerGetter} - } - - if len(caps) == 0 { - return base - } - - return &decorateMeterCapable{Meter: base, caps: caps} -} - -type decorateMeterCapable struct { - api.Meter - caps map[reflect.Type]any -} - -func (d *decorateMeterCapable) Capability(typ reflect.Type) (any, bool) { - c, ok := d.caps[typ] - if !ok && reflect.TypeOf(d).Implements(typ) { - return d, true - } - return c, ok -} - -type decorateMeterMaxACPowerGetterImpl struct { - maxACPowerGetter func() float64 -} - -func (impl *decorateMeterMaxACPowerGetterImpl) MaxACPower() float64 { - return impl.maxACPowerGetter() -} - -type decorateMeterMeterEnergyImpl struct { - meterEnergy func() (float64, error) -} - -func (impl *decorateMeterMeterEnergyImpl) TotalEnergy() (float64, error) { - return impl.meterEnergy() -} - -type decorateMeterPhaseCurrentsImpl struct { - phaseCurrents func() (float64, float64, float64, error) -} - -func (impl *decorateMeterPhaseCurrentsImpl) Currents() (float64, float64, float64, error) { - return impl.phaseCurrents() -} - -type decorateMeterPhasePowersImpl struct { - phasePowers func() (float64, float64, float64, error) -} - -func (impl *decorateMeterPhasePowersImpl) Powers() (float64, float64, float64, error) { - return impl.phasePowers() -} - -type decorateMeterPhaseVoltagesImpl struct { - phaseVoltages func() (float64, float64, float64, error) -} - -func (impl *decorateMeterPhaseVoltagesImpl) Voltages() (float64, float64, float64, error) { - return impl.phaseVoltages() -} - -func decorateMeterBattery(base api.Meter, meterEnergy func() (float64, error), battery func() (float64, error), batteryCapacity func() float64, batterySocLimiter func() (float64, float64), batteryPowerLimiter func() (float64, float64), batteryController func(api.BatteryMode) error) api.Meter { - caps := make(map[reflect.Type]any) - - if meterEnergy != nil { - caps[reflect.TypeFor[api.MeterEnergy]()] = &decorateMeterBatteryMeterEnergyImpl{meterEnergy: meterEnergy} - } - - if battery != nil { - caps[reflect.TypeFor[api.Battery]()] = &decorateMeterBatteryBatteryImpl{battery: battery} - } - - if batteryCapacity != nil { - caps[reflect.TypeFor[api.BatteryCapacity]()] = &decorateMeterBatteryBatteryCapacityImpl{batteryCapacity: batteryCapacity} - } - - if batterySocLimiter != nil { - caps[reflect.TypeFor[api.BatterySocLimiter]()] = &decorateMeterBatteryBatterySocLimiterImpl{batterySocLimiter: batterySocLimiter} - } - - if batteryPowerLimiter != nil { - caps[reflect.TypeFor[api.BatteryPowerLimiter]()] = &decorateMeterBatteryBatteryPowerLimiterImpl{batteryPowerLimiter: batteryPowerLimiter} - } - - if batteryController != nil { - caps[reflect.TypeFor[api.BatteryController]()] = &decorateMeterBatteryBatteryControllerImpl{batteryController: batteryController} - } - - if len(caps) == 0 { - return base - } - - return &decorateMeterBatteryCapable{Meter: base, caps: caps} -} - -type decorateMeterBatteryCapable struct { - api.Meter - caps map[reflect.Type]any -} - -func (d *decorateMeterBatteryCapable) Capability(typ reflect.Type) (any, bool) { - c, ok := d.caps[typ] - if !ok && reflect.TypeOf(d).Implements(typ) { - return d, true - } - return c, ok -} - -type decorateMeterBatteryBatteryImpl struct { - battery func() (float64, error) -} - -func (impl *decorateMeterBatteryBatteryImpl) Soc() (float64, error) { - return impl.battery() -} - -type decorateMeterBatteryBatteryCapacityImpl struct { - batteryCapacity func() float64 -} - -func (impl *decorateMeterBatteryBatteryCapacityImpl) Capacity() float64 { - return impl.batteryCapacity() -} - -type decorateMeterBatteryBatteryControllerImpl struct { - batteryController func(api.BatteryMode) error -} - -func (impl *decorateMeterBatteryBatteryControllerImpl) SetBatteryMode(p0 api.BatteryMode) error { - return impl.batteryController(p0) -} - -type decorateMeterBatteryBatteryPowerLimiterImpl struct { - batteryPowerLimiter func() (float64, float64) -} - -func (impl *decorateMeterBatteryBatteryPowerLimiterImpl) GetPowerLimits() (float64, float64) { - return impl.batteryPowerLimiter() -} - -type decorateMeterBatteryBatterySocLimiterImpl struct { - batterySocLimiter func() (float64, float64) -} - -func (impl *decorateMeterBatteryBatterySocLimiterImpl) GetSocLimits() (float64, float64) { - return impl.batterySocLimiter() -} - -type decorateMeterBatteryMeterEnergyImpl struct { - meterEnergy func() (float64, error) -} - -func (impl *decorateMeterBatteryMeterEnergyImpl) TotalEnergy() (float64, error) { - return impl.meterEnergy() -} diff --git a/meter/openwb.go b/meter/openwb.go index 2f6292a09..4021db29d 100644 --- a/meter/openwb.go +++ b/meter/openwb.go @@ -7,6 +7,7 @@ import ( "time" "github.com/evcc-io/evcc/api" + "github.com/evcc-io/evcc/api/implement" "github.com/evcc-io/evcc/charger/openwb" "github.com/evcc-io/evcc/meter/measurement" "github.com/evcc-io/evcc/plugin" @@ -140,8 +141,12 @@ func NewOpenWBFromConfig(other map[string]any) (api.Meter, error) { } if strings.ToLower(cc.Usage) == "battery" { - return m.DecorateBattery(nil, soc, capacity, nil, nil, nil), nil + implement.Has(m, implement.Battery(soc)) + implement.May(m, implement.BatteryCapacity(capacity)) + return m, nil } - return m.Decorate(nil, currents, nil, nil, nil), nil + implement.May(m, implement.PhaseCurrents(currents)) + + return m, nil } diff --git a/meter/powerwall.go b/meter/powerwall.go index f8fd4ed1f..c84d1e2df 100644 --- a/meter/powerwall.go +++ b/meter/powerwall.go @@ -13,6 +13,7 @@ import ( "github.com/andig/go-powerwall" "github.com/bogosj/tesla" "github.com/evcc-io/evcc/api" + "github.com/evcc-io/evcc/api/implement" "github.com/evcc-io/evcc/util" "github.com/evcc-io/evcc/util/request" "golang.org/x/oauth2" @@ -20,6 +21,7 @@ import ( // PowerWall is the tesla powerwall meter type PowerWall struct { + implement.Caps usage string client *powerwall.Client meterG func() (map[string]powerwall.MeterAggregatesData, error) @@ -31,8 +33,6 @@ func init() { registry.Add("powerwall", NewPowerWallFromConfig) } -//go:generate go tool decorate -f decoratePowerWall -b *PowerWall -r api.Meter -t api.MeterEnergy,api.Battery,api.BatteryCapacity,api.BatterySocLimiter,api.BatteryPowerLimiter,api.BatteryController - // NewPowerWallFromConfig creates a PowerWall Powerwall Meter from generic config func NewPowerWallFromConfig(other map[string]any) (api.Meter, error) { cc := struct { @@ -92,6 +92,7 @@ func NewPowerWall(uri, usage, user, password string, cache time.Duration, refres } m := &PowerWall{ + Caps: implement.New(), client: client, usage: strings.ToLower(usage), meterG: util.Cached(client.GetMetersAggregates, cache), @@ -141,37 +142,27 @@ func NewPowerWall(uri, usage, user, password string, cache time.Duration, refres m.energySite = energySite } - // decorate api.MeterEnergy - var totalEnergy func() (float64, error) if m.usage == "load" || m.usage == "solar" { - totalEnergy = m.totalEnergy + implement.Has(m, implement.MeterEnergy(m.totalEnergy)) } - // decorate battery - var batteryCapacity func() float64 - var batterySoc func() (float64, error) - var batterySocLimiter func() (float64, float64) - var batteryPowerLimiter func() (float64, float64) - if usage == "battery" { - batterySoc = m.batterySoc - batterySocLimiter = batterySocLimits.Decorator() - batteryPowerLimiter = batteryPowerLimits.Decorator() + implement.Has(m, implement.Battery(m.batterySoc)) + implement.May(m, implement.BatterySocLimiter(batterySocLimits.Decorator())) + implement.May(m, implement.BatteryPowerLimiter(batteryPowerLimits.Decorator())) res, err := m.client.GetSystemStatus() if err != nil { return nil, err } - batteryCapacity = func() float64 { + implement.Has(m, implement.BatteryCapacity(func() float64 { return res.NominalFullPackEnergy / 1e3 - } + })) } - // decorate api.BatteryController - var batModeS func(api.BatteryMode) error if batteryControl { - batModeS = batterySocLimits.LimitController(m.socG, func(limit float64) error { + implement.May(m, implement.BatteryController(batterySocLimits.LimitController(m.socG, func(limit float64) error { // Handle Tesla firmware 25.18.4 restrictions: // Values between 81-99% are not allowed, only ≤80% or exactly 100% limitUint := uint64(limit) @@ -180,10 +171,10 @@ func NewPowerWall(uri, usage, user, password string, cache time.Duration, refres limitUint = 80 } return m.energySite.SetBatteryReserve(limitUint) - }) + }))) } - return decoratePowerWall(m, totalEnergy, batterySoc, batteryCapacity, batterySocLimiter, batteryPowerLimiter, batModeS), nil + return m, nil } var _ api.Meter = (*PowerWall)(nil) diff --git a/meter/powerwall_decorators.go b/meter/powerwall_decorators.go deleted file mode 100644 index 12c7cefbf..000000000 --- a/meter/powerwall_decorators.go +++ /dev/null @@ -1,104 +0,0 @@ -package meter - -// Code generated by github.com/evcc-io/evcc/cmd/tools/decorate.go. DO NOT EDIT. - -import ( - "reflect" - - "github.com/evcc-io/evcc/api" -) - -func decoratePowerWall(base *PowerWall, meterEnergy func() (float64, error), battery func() (float64, error), batteryCapacity func() float64, batterySocLimiter func() (float64, float64), batteryPowerLimiter func() (float64, float64), batteryController func(api.BatteryMode) error) api.Meter { - caps := make(map[reflect.Type]any) - - if meterEnergy != nil { - caps[reflect.TypeFor[api.MeterEnergy]()] = &decoratePowerWallMeterEnergyImpl{meterEnergy: meterEnergy} - } - - if battery != nil { - caps[reflect.TypeFor[api.Battery]()] = &decoratePowerWallBatteryImpl{battery: battery} - } - - if batteryCapacity != nil { - caps[reflect.TypeFor[api.BatteryCapacity]()] = &decoratePowerWallBatteryCapacityImpl{batteryCapacity: batteryCapacity} - } - - if batterySocLimiter != nil { - caps[reflect.TypeFor[api.BatterySocLimiter]()] = &decoratePowerWallBatterySocLimiterImpl{batterySocLimiter: batterySocLimiter} - } - - if batteryPowerLimiter != nil { - caps[reflect.TypeFor[api.BatteryPowerLimiter]()] = &decoratePowerWallBatteryPowerLimiterImpl{batteryPowerLimiter: batteryPowerLimiter} - } - - if batteryController != nil { - caps[reflect.TypeFor[api.BatteryController]()] = &decoratePowerWallBatteryControllerImpl{batteryController: batteryController} - } - - if len(caps) == 0 { - return base - } - - return &decoratePowerWallCapable{PowerWall: base, caps: caps} -} - -type decoratePowerWallCapable struct { - *PowerWall - caps map[reflect.Type]any -} - -func (d *decoratePowerWallCapable) Capability(typ reflect.Type) (any, bool) { - c, ok := d.caps[typ] - if !ok && reflect.TypeOf(d).Implements(typ) { - return d, true - } - return c, ok -} - -type decoratePowerWallBatteryImpl struct { - battery func() (float64, error) -} - -func (impl *decoratePowerWallBatteryImpl) Soc() (float64, error) { - return impl.battery() -} - -type decoratePowerWallBatteryCapacityImpl struct { - batteryCapacity func() float64 -} - -func (impl *decoratePowerWallBatteryCapacityImpl) Capacity() float64 { - return impl.batteryCapacity() -} - -type decoratePowerWallBatteryControllerImpl struct { - batteryController func(api.BatteryMode) error -} - -func (impl *decoratePowerWallBatteryControllerImpl) SetBatteryMode(p0 api.BatteryMode) error { - return impl.batteryController(p0) -} - -type decoratePowerWallBatteryPowerLimiterImpl struct { - batteryPowerLimiter func() (float64, float64) -} - -func (impl *decoratePowerWallBatteryPowerLimiterImpl) GetPowerLimits() (float64, float64) { - return impl.batteryPowerLimiter() -} - -type decoratePowerWallBatterySocLimiterImpl struct { - batterySocLimiter func() (float64, float64) -} - -func (impl *decoratePowerWallBatterySocLimiterImpl) GetSocLimits() (float64, float64) { - return impl.batterySocLimiter() -} - -type decoratePowerWallMeterEnergyImpl struct { - meterEnergy func() (float64, error) -} - -func (impl *decoratePowerWallMeterEnergyImpl) TotalEnergy() (float64, error) { - return impl.meterEnergy() -} diff --git a/meter/rct.go b/meter/rct.go index bdc4e2354..1b58a4c41 100644 --- a/meter/rct.go +++ b/meter/rct.go @@ -12,6 +12,7 @@ import ( "github.com/cenkalti/backoff/v4" "github.com/evcc-io/evcc/api" + "github.com/evcc-io/evcc/api/implement" "github.com/evcc-io/evcc/util" "github.com/evcc-io/rct" "golang.org/x/sync/errgroup" @@ -19,6 +20,7 @@ import ( // RCT implements the api.Meter interface type RCT struct { + implement.Caps conn *rct.Connection // connection with the RCT device usage string // grid, pv, battery externalPower bool // whether to query external power @@ -34,8 +36,6 @@ func init() { registry.AddCtx("rct", NewRCTFromConfig) } -//go:generate go tool decorate -f decorateRCT -b *RCT -r api.Meter -t api.MeterEnergy,api.Curtailer,api.Battery,api.BatterySocLimiter,api.BatteryPowerLimiter,api.BatteryController,api.BatteryCapacity - // NewRCTFromConfig creates an RCT from generic config func NewRCTFromConfig(ctx context.Context, other map[string]any) (api.Meter, error) { cc := struct { @@ -93,22 +93,18 @@ func NewRCT(ctx context.Context, uri, usage string, batterySocLimits batterySocL rctMu.Unlock() m := &RCT{ + Caps: implement.New(), usage: strings.ToLower(usage), conn: conn, externalPower: externalPower, } - // decorate api.MeterEnergy - var totalEnergy func() (float64, error) if usage == "grid" { - totalEnergy = m.totalEnergy + implement.Has(m, implement.MeterEnergy(m.totalEnergy)) } - // decorate api.Curtailer - var curtail func(bool) error - var curtailed func() (bool, error) if usage == "pv" { - curtail = func(b bool) error { + curtail := func(b bool) error { var r float64 if !b { r = 1.0 @@ -116,18 +112,13 @@ func NewRCT(ctx context.Context, uri, usage string, batterySocLimits batterySocL return m.conn.Write(rct.BufVControlPowerReduction, floatVal(r)) } - curtailed = func() (bool, error) { + curtailed := func() (bool, error) { r, err := m.queryFloat(rct.BufVControlPowerReduction) return r != 1, err } - } - // decorate api.Battery - var batterySoc func() (float64, error) - var batterySocLimiter func() (float64, float64) - var batteryPowerLimiter func() (float64, float64) - var batteryCapacity func() float64 - var batteryMode func(api.BatteryMode) error + implement.Has(m, implement.Curtailer(curtail, curtailed)) + } if usage == "battery" { // validate capacity configuration for dual battery setups @@ -135,7 +126,7 @@ func NewRCT(ctx context.Context, uri, usage string, batterySocLimits batterySocL return nil, errors.New("missing first battery capacity") } - batterySoc = func() (float64, error) { + batterySoc := func() (float64, error) { soc, err := m.queryFloat(rct.BatterySoC) if err != nil { return 0, err @@ -149,14 +140,15 @@ func NewRCT(ctx context.Context, uri, usage string, batterySocLimits batterySocL return (soc*capacity + soc2*capacity2) / (capacity + capacity2) * 100, err } - batterySocLimiter = batterySocLimits.Decorator() - batteryPowerLimiter = batteryPowerLimits.Decorator() + implement.Has(m, implement.Battery(batterySoc)) + implement.May(m, implement.BatterySocLimiter(batterySocLimits.Decorator())) + implement.May(m, implement.BatteryPowerLimiter(batteryPowerLimits.Decorator())) if capacity != 0 { - batteryCapacity = func() float64 { return capacity + capacity2 } + implement.Has(m, implement.BatteryCapacity(func() float64 { return capacity + capacity2 })) } - batteryMode = func(mode api.BatteryMode) error { + batteryMode := func(mode api.BatteryMode) error { if mode != api.BatteryNormal { batStatus, err := m.queryInt32(rct.BatteryStatus2) if err != nil { @@ -224,9 +216,11 @@ func NewRCT(ctx context.Context, uri, usage string, batterySocLimits batterySocL return eg.Wait() } + + implement.Has(m, implement.BatteryController(batteryMode)) } - return decorateRCT(m, totalEnergy, curtail, curtailed, batterySoc, batterySocLimiter, batteryPowerLimiter, batteryMode, batteryCapacity), nil + return m, nil } // CurrentPower implements the api.Meter interface diff --git a/meter/rct_decorators.go b/meter/rct_decorators.go deleted file mode 100644 index 44857b73c..000000000 --- a/meter/rct_decorators.go +++ /dev/null @@ -1,125 +0,0 @@ -package meter - -// Code generated by github.com/evcc-io/evcc/cmd/tools/decorate.go. DO NOT EDIT. - -import ( - "reflect" - - "github.com/evcc-io/evcc/api" -) - -func decorateRCT(base *RCT, meterEnergy func() (float64, error), curtailer0 func(bool) error, curtailer1 func() (bool, error), battery func() (float64, error), batterySocLimiter func() (float64, float64), batteryPowerLimiter func() (float64, float64), batteryController func(api.BatteryMode) error, batteryCapacity func() float64) api.Meter { - caps := make(map[reflect.Type]any) - - if meterEnergy != nil { - caps[reflect.TypeFor[api.MeterEnergy]()] = &decorateRCTMeterEnergyImpl{meterEnergy: meterEnergy} - } - - if curtailer0 != nil { - caps[reflect.TypeFor[api.Curtailer]()] = &decorateRCTCurtailerImpl{curtailer0: curtailer0} - } - - if curtailer1 != nil { - caps[reflect.TypeFor[api.Curtailer]()] = &decorateRCTCurtailerImpl{curtailer1: curtailer1} - } - - if battery != nil { - caps[reflect.TypeFor[api.Battery]()] = &decorateRCTBatteryImpl{battery: battery} - } - - if batterySocLimiter != nil { - caps[reflect.TypeFor[api.BatterySocLimiter]()] = &decorateRCTBatterySocLimiterImpl{batterySocLimiter: batterySocLimiter} - } - - if batteryPowerLimiter != nil { - caps[reflect.TypeFor[api.BatteryPowerLimiter]()] = &decorateRCTBatteryPowerLimiterImpl{batteryPowerLimiter: batteryPowerLimiter} - } - - if batteryController != nil { - caps[reflect.TypeFor[api.BatteryController]()] = &decorateRCTBatteryControllerImpl{batteryController: batteryController} - } - - if batteryCapacity != nil { - caps[reflect.TypeFor[api.BatteryCapacity]()] = &decorateRCTBatteryCapacityImpl{batteryCapacity: batteryCapacity} - } - - if len(caps) == 0 { - return base - } - - return &decorateRCTCapable{RCT: base, caps: caps} -} - -type decorateRCTCapable struct { - *RCT - caps map[reflect.Type]any -} - -func (d *decorateRCTCapable) Capability(typ reflect.Type) (any, bool) { - c, ok := d.caps[typ] - if !ok && reflect.TypeOf(d).Implements(typ) { - return d, true - } - return c, ok -} - -type decorateRCTBatteryImpl struct { - battery func() (float64, error) -} - -func (impl *decorateRCTBatteryImpl) Soc() (float64, error) { - return impl.battery() -} - -type decorateRCTBatteryCapacityImpl struct { - batteryCapacity func() float64 -} - -func (impl *decorateRCTBatteryCapacityImpl) Capacity() float64 { - return impl.batteryCapacity() -} - -type decorateRCTBatteryControllerImpl struct { - batteryController func(api.BatteryMode) error -} - -func (impl *decorateRCTBatteryControllerImpl) SetBatteryMode(p0 api.BatteryMode) error { - return impl.batteryController(p0) -} - -type decorateRCTBatteryPowerLimiterImpl struct { - batteryPowerLimiter func() (float64, float64) -} - -func (impl *decorateRCTBatteryPowerLimiterImpl) GetPowerLimits() (float64, float64) { - return impl.batteryPowerLimiter() -} - -type decorateRCTBatterySocLimiterImpl struct { - batterySocLimiter func() (float64, float64) -} - -func (impl *decorateRCTBatterySocLimiterImpl) GetSocLimits() (float64, float64) { - return impl.batterySocLimiter() -} - -type decorateRCTCurtailerImpl struct { - curtailer0 func(bool) error - curtailer1 func() (bool, error) -} - -func (impl *decorateRCTCurtailerImpl) Curtail(p0 bool) error { - return impl.curtailer0(p0) -} - -func (impl *decorateRCTCurtailerImpl) Curtailed() (bool, error) { - return impl.curtailer1() -} - -type decorateRCTMeterEnergyImpl struct { - meterEnergy func() (float64, error) -} - -func (impl *decorateRCTMeterEnergyImpl) TotalEnergy() (float64, error) { - return impl.meterEnergy() -} diff --git a/meter/shelly.go b/meter/shelly.go index 617a8cce0..38a18e171 100644 --- a/meter/shelly.go +++ b/meter/shelly.go @@ -6,12 +6,14 @@ import ( "time" "github.com/evcc-io/evcc/api" + "github.com/evcc-io/evcc/api/implement" "github.com/evcc-io/evcc/meter/shelly" "github.com/evcc-io/evcc/util" ) // Shelly meter considering usage type Shelly struct { + implement.Caps shelly.Connection usage string } @@ -21,8 +23,6 @@ func init() { registry.Add("shelly", NewShellyFromConfig) } -//go:generate go tool decorate -f decorateShelly -b *Shelly -r api.Meter -t api.PhaseVoltages,api.PhaseCurrents,api.PhasePowers - // NewShellyFromConfig creates a Shelly charger from generic config func NewShellyFromConfig(other map[string]any) (api.Meter, error) { cc := struct { @@ -45,14 +45,13 @@ func NewShellyFromConfig(other map[string]any) (api.Meter, error) { return nil, err } - var vol, cur, pow func() (float64, float64, float64, error) if phases, ok := c.Connection.Generation.(shelly.Phases); ok { - vol = phases.Voltages - cur = phases.Currents - pow = phases.Powers + implement.Has(c, implement.PhaseVoltages(phases.Voltages)) + implement.Has(c, implement.PhaseCurrents(phases.Currents)) + implement.Has(c, implement.PhasePowers(phases.Powers)) } - return decorateShelly(c, vol, cur, pow), nil + return c, nil } // NewShelly creates Shelly meter @@ -62,6 +61,7 @@ func NewShelly(uri, user, password, usage string, channel int, cache time.Durati return nil, err } c := &Shelly{ + Caps: implement.New(), Connection: *conn, usage: usage, } diff --git a/meter/shelly_decorators.go b/meter/shelly_decorators.go deleted file mode 100644 index cc2e48ff7..000000000 --- a/meter/shelly_decorators.go +++ /dev/null @@ -1,68 +0,0 @@ -package meter - -// Code generated by github.com/evcc-io/evcc/cmd/tools/decorate.go. DO NOT EDIT. - -import ( - "reflect" - - "github.com/evcc-io/evcc/api" -) - -func decorateShelly(base *Shelly, phaseVoltages func() (float64, float64, float64, error), phaseCurrents func() (float64, float64, float64, error), phasePowers func() (float64, float64, float64, error)) api.Meter { - caps := make(map[reflect.Type]any) - - if phaseVoltages != nil { - caps[reflect.TypeFor[api.PhaseVoltages]()] = &decorateShellyPhaseVoltagesImpl{phaseVoltages: phaseVoltages} - } - - if phaseCurrents != nil { - caps[reflect.TypeFor[api.PhaseCurrents]()] = &decorateShellyPhaseCurrentsImpl{phaseCurrents: phaseCurrents} - } - - if phasePowers != nil { - caps[reflect.TypeFor[api.PhasePowers]()] = &decorateShellyPhasePowersImpl{phasePowers: phasePowers} - } - - if len(caps) == 0 { - return base - } - - return &decorateShellyCapable{Shelly: base, caps: caps} -} - -type decorateShellyCapable struct { - *Shelly - caps map[reflect.Type]any -} - -func (d *decorateShellyCapable) Capability(typ reflect.Type) (any, bool) { - c, ok := d.caps[typ] - if !ok && reflect.TypeOf(d).Implements(typ) { - return d, true - } - return c, ok -} - -type decorateShellyPhaseCurrentsImpl struct { - phaseCurrents func() (float64, float64, float64, error) -} - -func (impl *decorateShellyPhaseCurrentsImpl) Currents() (float64, float64, float64, error) { - return impl.phaseCurrents() -} - -type decorateShellyPhasePowersImpl struct { - phasePowers func() (float64, float64, float64, error) -} - -func (impl *decorateShellyPhasePowersImpl) Powers() (float64, float64, float64, error) { - return impl.phasePowers() -} - -type decorateShellyPhaseVoltagesImpl struct { - phaseVoltages func() (float64, float64, float64, error) -} - -func (impl *decorateShellyPhaseVoltagesImpl) Voltages() (float64, float64, float64, error) { - return impl.phaseVoltages() -} diff --git a/meter/sma.go b/meter/sma.go index 7055c28cb..2ae5d59fe 100644 --- a/meter/sma.go +++ b/meter/sma.go @@ -8,6 +8,7 @@ import ( "text/tabwriter" "github.com/evcc-io/evcc/api" + "github.com/evcc-io/evcc/api/implement" "github.com/evcc-io/evcc/plugin/sma" "github.com/evcc-io/evcc/util" "gitlab.com/bboehmke/sunny" @@ -15,6 +16,7 @@ import ( // SMA supporting SMA Home Manager 2.0, SMA Energy Meter 30 and SMA inverter type SMA struct { + implement.Caps uri string scale float64 device *sma.Device @@ -43,25 +45,13 @@ func NewSMAFromConfig(other map[string]any) (api.Meter, error) { return nil, err } - sm, err := NewSMA(cc.URI, cc.Password, cc.Interface, cc.Serial, cc.Scale) - if err != nil { - return nil, err - } - - if cc.Usage == "battery" { - return decorateMeterBattery( - sm, sm.TotalEnergy, - sm.soc, cc.batteryCapacity.Decorator(), - cc.batterySocLimits.Decorator(), cc.batteryPowerLimits.Decorator(), nil, - ), nil - } - - return sm, nil + return NewSMA(cc.URI, cc.Password, cc.Interface, cc.Serial, cc.Scale, cc.Usage, cc.batteryCapacity.Decorator(), cc.batterySocLimits.Decorator(), cc.batteryPowerLimits.Decorator()) } // NewSMA creates an SMA meter -func NewSMA(uri, password, iface string, serial uint32, scale float64) (*SMA, error) { +func NewSMA(uri, password, iface string, serial uint32, scale float64, usage string, capacity func() float64, batterySocLimits, batteryPowerLimits func() (float64, float64)) (*SMA, error) { sm := &SMA{ + Caps: implement.New(), uri: uri, scale: scale, } @@ -96,6 +86,14 @@ func NewSMA(uri, password, iface string, serial uint32, scale float64) (*SMA, er // start update loop manually to get values as fast as possible go sm.device.Run() + if usage == "battery" { + implement.May(sm, implement.MeterEnergy(sm.TotalEnergy)) + implement.Has(sm, implement.Battery(sm.soc)) + implement.May(sm, implement.BatteryCapacity(capacity)) + implement.May(sm, implement.BatterySocLimiter(batterySocLimits)) + implement.May(sm, implement.BatteryPowerLimiter(batteryPowerLimits)) + } + return sm, nil } diff --git a/meter/tasmota.go b/meter/tasmota.go index a205e2c6e..58aa21aae 100644 --- a/meter/tasmota.go +++ b/meter/tasmota.go @@ -6,12 +6,14 @@ import ( "time" "github.com/evcc-io/evcc/api" + "github.com/evcc-io/evcc/api/implement" "github.com/evcc-io/evcc/meter/tasmota" "github.com/evcc-io/evcc/util" ) // Tasmota meter implementation type Tasmota struct { + implement.Caps conn *tasmota.Connection usage string } @@ -21,8 +23,6 @@ func init() { registry.Add("tasmota", NewTasmotaFromConfig) } -//go:generate go tool decorate -f decorateTasmota -b *Tasmota -r api.Meter -t api.PhaseVoltages,api.PhaseCurrents,api.PhasePowers - // NewTasmotaFromConfig creates a Tasmota meter from generic config func NewTasmotaFromConfig(other map[string]any) (api.Meter, error) { cc := struct { @@ -52,6 +52,7 @@ func NewTasmota(uri, user, password, usage string, channels []int, cache time.Du } c := &Tasmota{ + Caps: implement.New(), conn: conn, usage: usage, } @@ -65,7 +66,9 @@ func NewTasmota(uri, user, password, usage string, channels []int, cache time.Du } if hasPhases || len(channels) == 3 { - return decorateTasmota(c, c.voltages, c.currents, c.powers), nil + implement.Has(c, implement.PhaseVoltages(c.voltages)) + implement.Has(c, implement.PhaseCurrents(c.currents)) + implement.Has(c, implement.PhasePowers(c.powers)) } return c, nil diff --git a/meter/tasmota_decorators.go b/meter/tasmota_decorators.go deleted file mode 100644 index 0ad3cc223..000000000 --- a/meter/tasmota_decorators.go +++ /dev/null @@ -1,68 +0,0 @@ -package meter - -// Code generated by github.com/evcc-io/evcc/cmd/tools/decorate.go. DO NOT EDIT. - -import ( - "reflect" - - "github.com/evcc-io/evcc/api" -) - -func decorateTasmota(base *Tasmota, phaseVoltages func() (float64, float64, float64, error), phaseCurrents func() (float64, float64, float64, error), phasePowers func() (float64, float64, float64, error)) api.Meter { - caps := make(map[reflect.Type]any) - - if phaseVoltages != nil { - caps[reflect.TypeFor[api.PhaseVoltages]()] = &decorateTasmotaPhaseVoltagesImpl{phaseVoltages: phaseVoltages} - } - - if phaseCurrents != nil { - caps[reflect.TypeFor[api.PhaseCurrents]()] = &decorateTasmotaPhaseCurrentsImpl{phaseCurrents: phaseCurrents} - } - - if phasePowers != nil { - caps[reflect.TypeFor[api.PhasePowers]()] = &decorateTasmotaPhasePowersImpl{phasePowers: phasePowers} - } - - if len(caps) == 0 { - return base - } - - return &decorateTasmotaCapable{Tasmota: base, caps: caps} -} - -type decorateTasmotaCapable struct { - *Tasmota - caps map[reflect.Type]any -} - -func (d *decorateTasmotaCapable) Capability(typ reflect.Type) (any, bool) { - c, ok := d.caps[typ] - if !ok && reflect.TypeOf(d).Implements(typ) { - return d, true - } - return c, ok -} - -type decorateTasmotaPhaseCurrentsImpl struct { - phaseCurrents func() (float64, float64, float64, error) -} - -func (impl *decorateTasmotaPhaseCurrentsImpl) Currents() (float64, float64, float64, error) { - return impl.phaseCurrents() -} - -type decorateTasmotaPhasePowersImpl struct { - phasePowers func() (float64, float64, float64, error) -} - -func (impl *decorateTasmotaPhasePowersImpl) Powers() (float64, float64, float64, error) { - return impl.phasePowers() -} - -type decorateTasmotaPhaseVoltagesImpl struct { - phaseVoltages func() (float64, float64, float64, error) -} - -func (impl *decorateTasmotaPhaseVoltagesImpl) Voltages() (float64, float64, float64, error) { - return impl.phaseVoltages() -} diff --git a/meter/tq-em.go b/meter/tq-em.go index b7bf0ecd2..86885d0ee 100644 --- a/meter/tq-em.go +++ b/meter/tq-em.go @@ -10,6 +10,7 @@ import ( "time" "github.com/evcc-io/evcc/api" + "github.com/evcc-io/evcc/api/implement" "github.com/evcc-io/evcc/util" "github.com/evcc-io/evcc/util/request" ) @@ -51,11 +52,10 @@ type tqemData struct { } type TqEm struct { + implement.Caps dataG func() (tqemData, error) } -//go:generate go tool decorate -f decorateTqEm -b api.Meter -t api.PhaseCurrents - // NewTqEmFromConfig creates a new configurable meter func NewTqEmFromConfig(other map[string]any) (api.Meter, error) { cc := struct { @@ -122,6 +122,7 @@ func NewTqEmFromConfig(other map[string]any) (api.Meter, error) { }, cc.Cache) m := &TqEm{ + Caps: implement.New(), dataG: dataG, } @@ -131,7 +132,7 @@ func NewTqEmFromConfig(other map[string]any) (api.Meter, error) { } if res.Obis31_4_0 != nil { - return decorateTqEm(m, m.currents), nil + implement.Has(m, implement.PhaseCurrents(m.currents)) } return m, nil diff --git a/meter/tq-em_decorators.go b/meter/tq-em_decorators.go deleted file mode 100644 index e4424c290..000000000 --- a/meter/tq-em_decorators.go +++ /dev/null @@ -1,44 +0,0 @@ -package meter - -// Code generated by github.com/evcc-io/evcc/cmd/tools/decorate.go. DO NOT EDIT. - -import ( - "reflect" - - "github.com/evcc-io/evcc/api" -) - -func decorateTqEm(base api.Meter, phaseCurrents func() (float64, float64, float64, error)) api.Meter { - caps := make(map[reflect.Type]any) - - if phaseCurrents != nil { - caps[reflect.TypeFor[api.PhaseCurrents]()] = &decorateTqEmPhaseCurrentsImpl{phaseCurrents: phaseCurrents} - } - - if len(caps) == 0 { - return base - } - - return &decorateTqEmCapable{Meter: base, caps: caps} -} - -type decorateTqEmCapable struct { - api.Meter - caps map[reflect.Type]any -} - -func (d *decorateTqEmCapable) Capability(typ reflect.Type) (any, bool) { - c, ok := d.caps[typ] - if !ok && reflect.TypeOf(d).Implements(typ) { - return d, true - } - return c, ok -} - -type decorateTqEmPhaseCurrentsImpl struct { - phaseCurrents func() (float64, float64, float64, error) -} - -func (impl *decorateTqEmPhaseCurrentsImpl) Currents() (float64, float64, float64, error) { - return impl.phaseCurrents() -} diff --git a/meter/zendure.go b/meter/zendure.go index eb9fc8a6d..984bd6931 100644 --- a/meter/zendure.go +++ b/meter/zendure.go @@ -6,6 +6,7 @@ import ( "time" "github.com/evcc-io/evcc/api" + "github.com/evcc-io/evcc/api/implement" "github.com/evcc-io/evcc/meter/zendure" "github.com/evcc-io/evcc/util" ) @@ -15,6 +16,7 @@ func init() { } type Zendure struct { + implement.Caps usage string conn *zendure.Connection } @@ -42,16 +44,17 @@ func NewZendureFromConfig(other map[string]any) (api.Meter, error) { } m := &Zendure{ + Caps: implement.New(), usage: cc.Usage, conn: conn, } // decorate battery if cc.Usage == "battery" { - return decorateMeterBattery( - m, nil, m.soc, cc.batteryCapacity.Decorator(), - cc.batterySocLimits.Decorator(), cc.batteryPowerLimits.Decorator(), nil, - ), nil + implement.Has(m, implement.Battery(m.soc)) + implement.May(m, implement.BatteryCapacity(cc.batteryCapacity.Decorator())) + implement.May(m, implement.BatterySocLimiter(cc.batterySocLimits.Decorator())) + implement.May(m, implement.BatteryPowerLimiter(cc.batteryPowerLimits.Decorator())) } return m, nil diff --git a/vehicle/homeassistant.go b/vehicle/homeassistant.go index 23d0be438..4c05caf8e 100644 --- a/vehicle/homeassistant.go +++ b/vehicle/homeassistant.go @@ -6,12 +6,14 @@ import ( "time" "github.com/evcc-io/evcc/api" + "github.com/evcc-io/evcc/api/implement" "github.com/evcc-io/evcc/util" "github.com/evcc-io/evcc/util/homeassistant" ) type HomeAssistant struct { *embed + implement.Caps conn *homeassistant.Connection soc string } @@ -62,48 +64,37 @@ func NewHomeAssistantVehicleFromConfig(other map[string]any) (api.Vehicle, error res := &HomeAssistant{ embed: &cc.embed, + Caps: implement.New(), conn: conn, soc: cc.Sensors.Soc, } - // optional features - var ( - limitSoc func() (int64, error) - status func() (api.ChargeStatus, error) - rng func() (int64, error) - odo func() (float64, error) - climater func() (bool, error) - finish func() (time.Time, error) - enable func(bool) error - wakeup func() error - maxcurrent func(int64) error - ) - if cc.Sensors.LimitSoc != "" { - limitSoc = func() (int64, error) { + implement.Has(res, implement.SocLimiter(func() (int64, error) { f, err := conn.GetFloatState(cc.Sensors.LimitSoc) return int64(f), err - } + })) } if cc.Sensors.Status != "" { - status = func() (api.ChargeStatus, error) { return conn.GetChargeStatus(cc.Sensors.Status) } + implement.Has(res, implement.ChargeState(func() (api.ChargeStatus, error) { return conn.GetChargeStatus(cc.Sensors.Status) })) } if cc.Sensors.Range != "" { - rng = func() (int64, error) { + implement.Has(res, implement.VehicleRange(func() (int64, error) { f, err := conn.GetFloatState(cc.Sensors.Range) return int64(f), err - } + })) } if cc.Sensors.Odometer != "" { - odo = func() (float64, error) { return conn.GetFloatState(cc.Sensors.Odometer) } + implement.Has(res, implement.VehicleOdometer(func() (float64, error) { return conn.GetFloatState(cc.Sensors.Odometer) })) } if cc.Sensors.Climater != "" { - climater = func() (bool, error) { return conn.GetBoolState(cc.Sensors.Climater) } + implement.Has(res, implement.VehicleClimater(func() (bool, error) { return conn.GetBoolState(cc.Sensors.Climater) })) } if cc.Sensors.FinishTime != "" { - finish = func() (time.Time, error) { return conn.GetTimeState(cc.Sensors.FinishTime) } + implement.Has(res, implement.VehicleFinishTimer(func() (time.Time, error) { return conn.GetTimeState(cc.Sensors.FinishTime) })) } + var enable func(bool) error if cc.Services.Start != "" && cc.Services.Stop != "" { enable = func(enable bool) error { if enable { @@ -114,30 +105,16 @@ func NewHomeAssistantVehicleFromConfig(other map[string]any) (api.Vehicle, error } else if strings.HasPrefix(cc.Services.Start, "switch") { enable = func(enable bool) error { return conn.CallSwitchService(cc.Services.Start, enable) } } + implement.May(res, implement.ChargeController(enable)) if cc.Services.Wakeup != "" { - wakeup = func() error { return conn.CallSwitchService(cc.Services.Wakeup, true) } + implement.Has(res, implement.Resurrector(func() error { return conn.CallSwitchService(cc.Services.Wakeup, true) })) } if cc.Services.SetMaxCurrent != "" { - maxcurrent = func(current int64) error { return conn.CallNumberService(cc.Services.SetMaxCurrent, float64(current)) } + implement.Has(res, implement.CurrentController(func(current int64) error { return conn.CallNumberService(cc.Services.SetMaxCurrent, float64(current)) })) } - // decorate all features - return decorateVehicle( - res, - limitSoc, - status, - rng, - odo, - climater, - maxcurrent, - nil, - finish, - wakeup, - enable, - nil, - nil, - ), nil + return res, nil } func (v *HomeAssistant) Soc() (float64, error) { diff --git a/vehicle/tronity.go b/vehicle/tronity.go index f94a57954..50776447d 100644 --- a/vehicle/tronity.go +++ b/vehicle/tronity.go @@ -26,6 +26,7 @@ import ( "time" "github.com/evcc-io/evcc/api" + "github.com/evcc-io/evcc/api/implement" "github.com/evcc-io/evcc/util" "github.com/evcc-io/evcc/util/request" "github.com/evcc-io/evcc/util/sponsor" @@ -37,6 +38,7 @@ import ( type Tronity struct { *embed *request.Helper + implement.Caps log *util.Logger oc *oauth2.Config vid string @@ -47,8 +49,6 @@ func init() { registry.Add("tronity", NewTronityFromConfig) } -//go:generate go tool decorate -f decorateTronity -b *Tronity -r api.Vehicle -t api.ChargeState,api.VehicleOdometer,api.ChargeController - // NewTronityFromConfig creates a new vehicle func NewTronityFromConfig(other map[string]any) (api.Vehicle, error) { cc := struct { @@ -85,6 +85,7 @@ func NewTronityFromConfig(other map[string]any) (api.Vehicle, error) { log: log, embed: &cc.embed, Helper: request.NewHelper(log), + Caps: implement.New(), oc: oc, } @@ -120,22 +121,19 @@ func NewTronityFromConfig(other map[string]any) (api.Vehicle, error) { v.vid = vehicle.ID v.bulkG = util.Cached(v.bulk, cc.Cache) - var status func() (api.ChargeStatus, error) if slices.Contains(vehicle.Scopes, tronity.ReadCharge) { - status = v.status + implement.Has(v, implement.ChargeState(v.status)) } - var odometer func() (float64, error) if slices.Contains(vehicle.Scopes, tronity.ReadOdometer) { - odometer = v.odometer + implement.Has(v, implement.VehicleOdometer(v.odometer)) } - var chargeEnable func(bool) error if slices.Contains(vehicle.Scopes, tronity.WriteChargeStartStop) { - chargeEnable = v.chargeEnable + implement.Has(v, implement.ChargeController(v.chargeEnable)) } - return decorateTronity(v, status, odometer, chargeEnable), nil + return v, nil } // vehicles implements the vehicles api diff --git a/vehicle/tronity_decorators.go b/vehicle/tronity_decorators.go deleted file mode 100644 index 43d96e5e1..000000000 --- a/vehicle/tronity_decorators.go +++ /dev/null @@ -1,68 +0,0 @@ -package vehicle - -// Code generated by github.com/evcc-io/evcc/cmd/tools/decorate.go. DO NOT EDIT. - -import ( - "reflect" - - "github.com/evcc-io/evcc/api" -) - -func decorateTronity(base *Tronity, chargeState func() (api.ChargeStatus, error), vehicleOdometer func() (float64, error), chargeController func(bool) error) api.Vehicle { - caps := make(map[reflect.Type]any) - - if chargeState != nil { - caps[reflect.TypeFor[api.ChargeState]()] = &decorateTronityChargeStateImpl{chargeState: chargeState} - } - - if vehicleOdometer != nil { - caps[reflect.TypeFor[api.VehicleOdometer]()] = &decorateTronityVehicleOdometerImpl{vehicleOdometer: vehicleOdometer} - } - - if chargeController != nil { - caps[reflect.TypeFor[api.ChargeController]()] = &decorateTronityChargeControllerImpl{chargeController: chargeController} - } - - if len(caps) == 0 { - return base - } - - return &decorateTronityCapable{Tronity: base, caps: caps} -} - -type decorateTronityCapable struct { - *Tronity - caps map[reflect.Type]any -} - -func (d *decorateTronityCapable) Capability(typ reflect.Type) (any, bool) { - c, ok := d.caps[typ] - if !ok && reflect.TypeOf(d).Implements(typ) { - return d, true - } - return c, ok -} - -type decorateTronityChargeControllerImpl struct { - chargeController func(bool) error -} - -func (impl *decorateTronityChargeControllerImpl) ChargeEnable(p0 bool) error { - return impl.chargeController(p0) -} - -type decorateTronityChargeStateImpl struct { - chargeState func() (api.ChargeStatus, error) -} - -func (impl *decorateTronityChargeStateImpl) Status() (api.ChargeStatus, error) { - return impl.chargeState() -} - -type decorateTronityVehicleOdometerImpl struct { - vehicleOdometer func() (float64, error) -} - -func (impl *decorateTronityVehicleOdometerImpl) Odometer() (float64, error) { - return impl.vehicleOdometer() -} diff --git a/vehicle/vehicle.go b/vehicle/vehicle.go index 5d3c9e3d4..6315262d6 100644 --- a/vehicle/vehicle.go +++ b/vehicle/vehicle.go @@ -4,18 +4,17 @@ import ( "context" "errors" "fmt" - "time" "github.com/evcc-io/evcc/api" + "github.com/evcc-io/evcc/api/implement" "github.com/evcc-io/evcc/plugin" "github.com/evcc-io/evcc/util" ) -//go:generate go tool decorate -f decorateVehicle -b api.Vehicle -t api.SocLimiter,api.ChargeState,api.VehicleRange,api.VehicleOdometer,api.VehicleClimater,api.CurrentController,api.CurrentGetter,api.VehicleFinishTimer,api.Resurrector,api.ChargeController,api.ChargeRater,api.VehiclePosition - // Vehicle is an api.Vehicle implementation with configurable getters and setters. type Vehicle struct { *embed + implement.Caps socG func() (float64, error) } @@ -56,29 +55,30 @@ func NewConfigurableFromConfig(ctx context.Context, other map[string]any) (api.V v := &Vehicle{ embed: &cc.embed, + Caps: implement.New(), socG: socG, } - // decorate range + // decorate limitSoc limitSoc, err := cc.LimitSoc.IntGetter(ctx) if err != nil { return nil, fmt.Errorf("limitSoc: %w", err) } + implement.May(v, implement.SocLimiter(limitSoc)) // decorate status - var status func() (api.ChargeStatus, error) - if cc.Status != nil { - get, err := cc.Status.StringGetter(ctx) - if err != nil { - return nil, fmt.Errorf("status: %w", err) - } - status = func() (api.ChargeStatus, error) { - s, err := get() + status, err := cc.Status.StringGetter(ctx) + if err != nil { + return nil, fmt.Errorf("status: %w", err) + } + if status != nil { + implement.Has(v, implement.ChargeState(func() (api.ChargeStatus, error) { + s, err := status() if err != nil { return api.StatusNone, err } return api.ChargeStatusString(s) - } + })) } // decorate range @@ -86,33 +86,37 @@ func NewConfigurableFromConfig(ctx context.Context, other map[string]any) (api.V if err != nil { return nil, fmt.Errorf("range: %w", err) } + implement.May(v, implement.VehicleRange(rng)) // decorate odometer odo, err := cc.Odometer.FloatGetter(ctx) if err != nil { return nil, fmt.Errorf("odometer: %w", err) } + implement.May(v, implement.VehicleOdometer(odo)) // decorate climater climater, err := cc.Climater.BoolGetter(ctx) if err != nil { return nil, fmt.Errorf("climater: %w", err) } + implement.May(v, implement.VehicleClimater(climater)) // decorate maxCurrent maxCurrent, err := cc.MaxCurrent.IntSetter(ctx, "maxcurrent") if err != nil { return nil, fmt.Errorf("maxCurrent: %w", err) } + implement.May(v, implement.CurrentController(maxCurrent)) // decorate getMaxCurrent getMaxCurrent, err := cc.GetMaxCurrent.FloatGetter(ctx) if err != nil { return nil, fmt.Errorf("getMaxCurrent: %w", err) } + implement.May(v, implement.CurrentGetter(getMaxCurrent)) // decorate position - var position func() (float64, float64, error) if cc.Position != nil { latG, err := cc.Position.Latitude.FloatGetter(ctx) if err != nil { @@ -122,7 +126,7 @@ func NewConfigurableFromConfig(ctx context.Context, other map[string]any) (api.V if err != nil { return nil, fmt.Errorf("longitude: %w", err) } - position = func() (float64, float64, error) { + implement.Has(v, implement.VehiclePosition(func() (float64, float64, error) { lat, err := latG() if err != nil { return 0, 0, err @@ -136,28 +140,25 @@ func NewConfigurableFromConfig(ctx context.Context, other map[string]any) (api.V return 0, 0, api.ErrNotAvailable } return lat, lon, nil - } + })) } // decorate finishtime - var finishTime func() (time.Time, error) - if cc.FinishTime != nil { - finishTime, err = cc.FinishTime.TimeGetter(ctx) - if err != nil { - return nil, fmt.Errorf("finishTime: %w", err) - } + finishTime, err := cc.FinishTime.TimeGetter(ctx) + if err != nil { + return nil, fmt.Errorf("finishTime: %w", err) } + implement.May(v, implement.VehicleFinishTimer(finishTime)) // decorate wakeup - var wakeup func() error - if cc.Wakeup != nil { - set, err := cc.Wakeup.BoolSetter(ctx, "wakeup") - if err != nil { - return nil, fmt.Errorf("wakeup: %w", err) - } - wakeup = func() error { - return set(true) - } + wakeup, err := cc.Wakeup.BoolSetter(ctx, "wakeup") + if err != nil { + return nil, fmt.Errorf("wakeup: %w", err) + } + if wakeup != nil { + implement.Has(v, implement.Resurrector(func() error { + return wakeup(true) + })) } // decorate chargeEnable @@ -165,16 +166,14 @@ func NewConfigurableFromConfig(ctx context.Context, other map[string]any) (api.V if err != nil { return nil, fmt.Errorf("chargeEnable: %w", err) } + implement.May(v, implement.ChargeController(chargeEnable)) // decorate chargedenergy - var chargedEnergy func() (float64, error) - if cc.ChargedEnergy != nil { - var err error - chargedEnergy, err = cc.ChargedEnergy.FloatGetter(ctx) - if err != nil { - return nil, fmt.Errorf("charged energy: %w", err) - } + chargedEnergy, err := cc.ChargedEnergy.FloatGetter(ctx) + if err != nil { + return nil, fmt.Errorf("charged energy: %w", err) } + implement.May(v, implement.ChargeRater(chargedEnergy)) switch { case maxCurrent == nil && getMaxCurrent != nil: @@ -185,7 +184,7 @@ func NewConfigurableFromConfig(ctx context.Context, other map[string]any) (api.V return nil, errors.New("cannot have charge control without status") } - return decorateVehicle(v, limitSoc, status, rng, odo, climater, maxCurrent, getMaxCurrent, finishTime, wakeup, chargeEnable, chargedEnergy, position), nil + return v, nil } // Soc implements the api.Vehicle interface diff --git a/vehicle/vehicle_decorators.go b/vehicle/vehicle_decorators.go deleted file mode 100644 index aff3271a9..000000000 --- a/vehicle/vehicle_decorators.go +++ /dev/null @@ -1,177 +0,0 @@ -package vehicle - -// Code generated by github.com/evcc-io/evcc/cmd/tools/decorate.go. DO NOT EDIT. - -import ( - "reflect" - "time" - - "github.com/evcc-io/evcc/api" -) - -func decorateVehicle(base api.Vehicle, socLimiter func() (int64, error), chargeState func() (api.ChargeStatus, error), vehicleRange func() (int64, error), vehicleOdometer func() (float64, error), vehicleClimater func() (bool, error), currentController func(int64) error, currentGetter func() (float64, error), vehicleFinishTimer func() (time.Time, error), resurrector func() error, chargeController func(bool) error, chargeRater func() (float64, error), vehiclePosition func() (float64, float64, error)) api.Vehicle { - caps := make(map[reflect.Type]any) - - if socLimiter != nil { - caps[reflect.TypeFor[api.SocLimiter]()] = &decorateVehicleSocLimiterImpl{socLimiter: socLimiter} - } - - if chargeState != nil { - caps[reflect.TypeFor[api.ChargeState]()] = &decorateVehicleChargeStateImpl{chargeState: chargeState} - } - - if vehicleRange != nil { - caps[reflect.TypeFor[api.VehicleRange]()] = &decorateVehicleVehicleRangeImpl{vehicleRange: vehicleRange} - } - - if vehicleOdometer != nil { - caps[reflect.TypeFor[api.VehicleOdometer]()] = &decorateVehicleVehicleOdometerImpl{vehicleOdometer: vehicleOdometer} - } - - if vehicleClimater != nil { - caps[reflect.TypeFor[api.VehicleClimater]()] = &decorateVehicleVehicleClimaterImpl{vehicleClimater: vehicleClimater} - } - - if currentController != nil { - caps[reflect.TypeFor[api.CurrentController]()] = &decorateVehicleCurrentControllerImpl{currentController: currentController} - } - - if currentGetter != nil { - caps[reflect.TypeFor[api.CurrentGetter]()] = &decorateVehicleCurrentGetterImpl{currentGetter: currentGetter} - } - - if vehicleFinishTimer != nil { - caps[reflect.TypeFor[api.VehicleFinishTimer]()] = &decorateVehicleVehicleFinishTimerImpl{vehicleFinishTimer: vehicleFinishTimer} - } - - if resurrector != nil { - caps[reflect.TypeFor[api.Resurrector]()] = &decorateVehicleResurrectorImpl{resurrector: resurrector} - } - - if chargeController != nil { - caps[reflect.TypeFor[api.ChargeController]()] = &decorateVehicleChargeControllerImpl{chargeController: chargeController} - } - - if chargeRater != nil { - caps[reflect.TypeFor[api.ChargeRater]()] = &decorateVehicleChargeRaterImpl{chargeRater: chargeRater} - } - - if vehiclePosition != nil { - caps[reflect.TypeFor[api.VehiclePosition]()] = &decorateVehicleVehiclePositionImpl{vehiclePosition: vehiclePosition} - } - - if len(caps) == 0 { - return base - } - - return &decorateVehicleCapable{Vehicle: base, caps: caps} -} - -type decorateVehicleCapable struct { - api.Vehicle - caps map[reflect.Type]any -} - -func (d *decorateVehicleCapable) Capability(typ reflect.Type) (any, bool) { - c, ok := d.caps[typ] - if !ok && reflect.TypeOf(d).Implements(typ) { - return d, true - } - return c, ok -} - -type decorateVehicleChargeControllerImpl struct { - chargeController func(bool) error -} - -func (impl *decorateVehicleChargeControllerImpl) ChargeEnable(p0 bool) error { - return impl.chargeController(p0) -} - -type decorateVehicleChargeRaterImpl struct { - chargeRater func() (float64, error) -} - -func (impl *decorateVehicleChargeRaterImpl) ChargedEnergy() (float64, error) { - return impl.chargeRater() -} - -type decorateVehicleChargeStateImpl struct { - chargeState func() (api.ChargeStatus, error) -} - -func (impl *decorateVehicleChargeStateImpl) Status() (api.ChargeStatus, error) { - return impl.chargeState() -} - -type decorateVehicleCurrentControllerImpl struct { - currentController func(int64) error -} - -func (impl *decorateVehicleCurrentControllerImpl) MaxCurrent(p0 int64) error { - return impl.currentController(p0) -} - -type decorateVehicleCurrentGetterImpl struct { - currentGetter func() (float64, error) -} - -func (impl *decorateVehicleCurrentGetterImpl) GetMaxCurrent() (float64, error) { - return impl.currentGetter() -} - -type decorateVehicleResurrectorImpl struct { - resurrector func() error -} - -func (impl *decorateVehicleResurrectorImpl) WakeUp() error { - return impl.resurrector() -} - -type decorateVehicleSocLimiterImpl struct { - socLimiter func() (int64, error) -} - -func (impl *decorateVehicleSocLimiterImpl) GetLimitSoc() (int64, error) { - return impl.socLimiter() -} - -type decorateVehicleVehicleClimaterImpl struct { - vehicleClimater func() (bool, error) -} - -func (impl *decorateVehicleVehicleClimaterImpl) Climater() (bool, error) { - return impl.vehicleClimater() -} - -type decorateVehicleVehicleFinishTimerImpl struct { - vehicleFinishTimer func() (time.Time, error) -} - -func (impl *decorateVehicleVehicleFinishTimerImpl) FinishTime() (time.Time, error) { - return impl.vehicleFinishTimer() -} - -type decorateVehicleVehicleOdometerImpl struct { - vehicleOdometer func() (float64, error) -} - -func (impl *decorateVehicleVehicleOdometerImpl) Odometer() (float64, error) { - return impl.vehicleOdometer() -} - -type decorateVehicleVehiclePositionImpl struct { - vehiclePosition func() (float64, float64, error) -} - -func (impl *decorateVehicleVehiclePositionImpl) Position() (float64, float64, error) { - return impl.vehiclePosition() -} - -type decorateVehicleVehicleRangeImpl struct { - vehicleRange func() (int64, error) -} - -func (impl *decorateVehicleVehicleRangeImpl) Range() (int64, error) { - return impl.vehicleRange() -} diff --git a/vehicle/vehicle_decorators_test.go b/vehicle/vehicle_decorators_test.go deleted file mode 100644 index 9c3a9b535..000000000 --- a/vehicle/vehicle_decorators_test.go +++ /dev/null @@ -1,170 +0,0 @@ -package vehicle - -import ( - "testing" - "time" - - "github.com/evcc-io/evcc/api" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" -) - -func baseVehicle() *Vehicle { - return &Vehicle{ - embed: &embed{Title_: "test"}, - socG: func() (float64, error) { return 42.0, nil }, - } -} - -func TestDecorateVehicle_NoCapabilities(t *testing.T) { - v := decorateVehicle(baseVehicle(), nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil) - - // base vehicle interface works - soc, err := v.Soc() - assert.NoError(t, err) - assert.Equal(t, 42.0, soc) - - // no optional capabilities - assert.False(t, api.HasCap[api.ChargeState](v)) - assert.False(t, api.HasCap[api.VehicleRange](v)) - assert.False(t, api.HasCap[api.Resurrector](v)) -} - -func TestDecorateVehicle_AllCapabilities(t *testing.T) { - v := decorateVehicle(baseVehicle(), - func() (int64, error) { return 80, nil }, // socLimiter - func() (api.ChargeStatus, error) { return api.StatusC, nil }, // chargeState - func() (int64, error) { return 300, nil }, // vehicleRange - func() (float64, error) { return 12345.0, nil }, // vehicleOdometer - func() (bool, error) { return true, nil }, // vehicleClimater - func(int64) error { return nil }, // currentController - func() (float64, error) { return 16.0, nil }, // currentGetter - func() (time.Time, error) { return time.Date(2026, 1, 1, 0, 0, 0, 0, time.UTC), nil }, // vehicleFinishTimer - func() error { return nil }, // resurrector - func(bool) error { return nil }, // chargeController - nil, // chargeRater - func() (float64, float64, error) { return 50.1234, 8.5678, nil }, // vehiclePosition - ) - - // base interface - soc, err := v.Soc() - require.NoError(t, err) - assert.Equal(t, 42.0, soc) - - // SocLimiter - sl, ok := api.Cap[api.SocLimiter](v) - require.True(t, ok, "SocLimiter") - limit, err := sl.GetLimitSoc() - assert.NoError(t, err) - assert.Equal(t, int64(80), limit) - - // ChargeState - cs, ok := api.Cap[api.ChargeState](v) - require.True(t, ok, "ChargeState") - status, err := cs.Status() - assert.NoError(t, err) - assert.Equal(t, api.StatusC, status) - - // VehicleRange - vr, ok := api.Cap[api.VehicleRange](v) - require.True(t, ok, "VehicleRange") - rng, err := vr.Range() - assert.NoError(t, err) - assert.Equal(t, int64(300), rng) - - // VehicleOdometer - vo, ok := api.Cap[api.VehicleOdometer](v) - require.True(t, ok, "VehicleOdometer") - odo, err := vo.Odometer() - assert.NoError(t, err) - assert.Equal(t, 12345.0, odo) - - // VehicleClimater - vc, ok := api.Cap[api.VehicleClimater](v) - require.True(t, ok, "VehicleClimater") - active, err := vc.Climater() - assert.NoError(t, err) - assert.True(t, active) - - // CurrentController - cc, ok := api.Cap[api.CurrentController](v) - require.True(t, ok, "CurrentController") - assert.NoError(t, cc.MaxCurrent(16)) - - // CurrentGetter - cg, ok := api.Cap[api.CurrentGetter](v) - require.True(t, ok, "CurrentGetter") - cur, err := cg.GetMaxCurrent() - assert.NoError(t, err) - assert.Equal(t, 16.0, cur) - - // VehicleFinishTimer - ft, ok := api.Cap[api.VehicleFinishTimer](v) - require.True(t, ok, "VehicleFinishTimer") - fin, err := ft.FinishTime() - assert.NoError(t, err) - assert.Equal(t, 2026, fin.Year()) - - // Resurrector - r, ok := api.Cap[api.Resurrector](v) - require.True(t, ok, "Resurrector") - assert.NoError(t, r.WakeUp()) - - // VehiclePosition - vp, ok := api.Cap[api.VehiclePosition](v) - require.True(t, ok, "VehiclePosition") - lat, lon, err := vp.Position() - assert.NoError(t, err) - assert.Equal(t, 50.1234, lat) - assert.Equal(t, 8.5678, lon) - - // ChargeController - chc, ok := api.Cap[api.ChargeController](v) - require.True(t, ok, "ChargeController") - assert.NoError(t, chc.ChargeEnable(true)) -} - -func TestDecorateVehicle_PositionNotAvailable(t *testing.T) { - v := decorateVehicle(baseVehicle(), - nil, // socLimiter - nil, // chargeState - nil, // vehicleRange - nil, // vehicleOdometer - nil, // vehicleClimater - nil, // currentController - nil, // currentGetter - nil, // vehicleFinishTimer - nil, // resurrector - nil, // chargeController - nil, // chargeRater - func() (float64, float64, error) { return 0, 0, api.ErrNotAvailable }, // vehiclePosition - ) - - vp, ok := api.Cap[api.VehiclePosition](v) - require.True(t, ok, "VehiclePosition") - _, _, err := vp.Position() - assert.ErrorIs(t, err, api.ErrNotAvailable) -} - -func TestDecorateVehicle_PartialCapabilities(t *testing.T) { - v := decorateVehicle(baseVehicle(), - func() (int64, error) { return 80, nil }, // socLimiter - nil, // chargeState - func() (int64, error) { return 300, nil }, // vehicleRange - nil, // vehicleOdometer - nil, // vehicleClimater - nil, // currentController - nil, // currentGetter - nil, // vehicleFinishTimer - func() error { return nil }, // resurrector - nil, // chargeController - nil, // chargeRater - nil, // vehiclePosition - ) - - assert.True(t, api.HasCap[api.SocLimiter](v), "SocLimiter should be present") - assert.True(t, api.HasCap[api.VehicleRange](v), "VehicleRange should be present") - assert.True(t, api.HasCap[api.Resurrector](v), "Resurrector should be present") - assert.False(t, api.HasCap[api.ChargeState](v), "ChargeState should not be present") - assert.False(t, api.HasCap[api.VehicleOdometer](v), "VehicleOdometer should not be present") -}