evcc-io/meter/meter.go

153 lines
4 KiB
Go

package meter
import (
"context"
"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"
)
func init() {
registry.AddCtx(api.Custom, NewConfigurableFromConfig)
}
// NewConfigurableFromConfig creates a new meter from config
func NewConfigurableFromConfig(ctx context.Context, other map[string]any) (api.Meter, error) {
cc := struct {
measurement.Energy `mapstructure:",squash"` // energy optional
measurement.Phases `mapstructure:",squash"` // optional
measurement.Dimmer `mapstructure:",squash"` // optional
measurement.Curtailer `mapstructure:",squash"` // optional
// pv
pvMaxACPowerCtx `mapstructure:",squash"`
// battery
batteryCapacityCtx `mapstructure:",squash"`
batterySocLimitsCtx `mapstructure:",squash"`
batteryPowerLimitsCtx `mapstructure:",squash"`
Soc *plugin.Config // optional
LimitSoc *plugin.Config // optional
BatteryMode *plugin.Config // optional
}{}
if err := util.DecodeOther(other, &cc); err != nil {
return nil, err
}
// default soc limits (nil-preset avoids mapstructure coercing plugin config into the default's type)
if cc.batterySocLimitsCtx.MinSoc == nil {
cc.batterySocLimitsCtx.MinSoc = 20
}
if cc.batterySocLimitsCtx.MaxSoc == nil {
cc.batterySocLimitsCtx.MaxSoc = 95
}
powerG, energyG, returnG, err := cc.Energy.Configure(ctx)
if err != nil {
return nil, err
}
m, _ := NewConfigurable(powerG)
implement.May(m, implement.MeterEnergy(energyG))
implement.May(m, implement.MeterReturnEnergy(returnG))
// dim/curtail
if err := cc.Dimmer.Implement(ctx, m); err != nil {
return nil, err
}
if err := cc.Curtailer.Implement(ctx, m); err != nil {
return nil, err
}
// decorate soc
socG, err := cc.Soc.FloatGetter(ctx)
if err != nil {
return nil, fmt.Errorf("battery soc: %w", err)
}
if socG != nil {
capacity, err := cc.batteryCapacityCtx.Decorator(ctx)
if err != nil {
return nil, err
}
socLimiter, err := cc.batterySocLimitsCtx.Decorator(ctx)
if err != nil {
return nil, err
}
powerLimiter, err := cc.batteryPowerLimitsCtx.Decorator(ctx)
if err != nil {
return nil, err
}
implement.Has(m, implement.Battery(socG))
implement.May(m, implement.BatteryCapacity(capacity))
implement.May(m, implement.BatterySocLimiter(socLimiter))
implement.May(m, implement.BatteryPowerLimiter(powerLimiter))
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)
}
limitController, err := cc.batterySocLimitsCtx.LimitController(ctx, socG, limitSocS)
if err != nil {
return nil, err
}
implement.Has(m, implement.BatteryController(limitController))
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
}
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.pvMaxACPowerCtx.Decorator(ctx)))
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
}
// Meter is an api.Meter implementation with configurable getters and setters.
type Meter struct {
implement.Caps
currentPowerG func() (float64, error)
}
// CurrentPower implements the api.Meter interface
func (m *Meter) CurrentPower() (float64, error) {
return m.currentPowerG()
}