diff --git a/charger/charger.go b/charger/charger.go index bf7a6f1cb..5e47a74da 100644 --- a/charger/charger.go +++ b/charger/charger.go @@ -42,6 +42,8 @@ func NewConfigurableFromConfig(ctx context.Context, other map[string]any) (api.C measurement.Temperature `mapstructure:",squash"` // optional, for heating devices measurement.Energy `mapstructure:",squash"` // optional meter.Phases `mapstructure:",squash"` // optional + meter.Dimmer `mapstructure:",squash"` // optional + meter.Curtailer `mapstructure:",squash"` // optional } if err := util.DecodeOther(other, &cc); err != nil { @@ -166,6 +168,14 @@ func NewConfigurableFromConfig(ctx context.Context, other map[string]any) (api.C } implement.May(c, implement.VehicleFinishTimer(finishTime)) + // dim/curtail + if err := cc.Dimmer.Implement(ctx, c); err != nil { + return nil, err + } + if err := cc.Curtailer.Implement(ctx, c); err != nil { + return nil, err + } + return c, nil } diff --git a/charger/heatpump.go b/charger/heatpump.go index 40b64caf3..0eb44b2ec 100644 --- a/charger/heatpump.go +++ b/charger/heatpump.go @@ -24,6 +24,7 @@ import ( "github.com/evcc-io/evcc/api/implement" "github.com/evcc-io/evcc/charger/measurement" "github.com/evcc-io/evcc/core/loadpoint" + meter "github.com/evcc-io/evcc/meter/measurement" "github.com/evcc-io/evcc/plugin" "github.com/evcc-io/evcc/util" ) @@ -47,9 +48,10 @@ func NewHeatpumpFromConfig(ctx context.Context, other map[string]any) (api.Charg cc := struct { embed `mapstructure:",squash"` SetMaxPower plugin.Config - GetMaxPower *plugin.Config // optional - measurement.Temperature `mapstructure:",squash"` - measurement.Energy `mapstructure:",squash"` + GetMaxPower *plugin.Config // optional + measurement.Temperature `mapstructure:",squash"` // optional + measurement.Energy `mapstructure:",squash"` // optional + meter.Dimmer `mapstructure:",squash"` // optional }{ embed: embed{ Icon_: "heatpump", @@ -94,6 +96,10 @@ func NewHeatpumpFromConfig(ctx context.Context, other map[string]any) (api.Charg implement.May(res, implement.Battery(tempG)) implement.May(res, implement.SocLimiter(limitTempG)) + if err := cc.Dimmer.Implement(ctx, res); err != nil { + return nil, err + } + return res, nil } diff --git a/meter/measurement/action.go b/meter/measurement/action.go new file mode 100644 index 000000000..1dddbb226 --- /dev/null +++ b/meter/measurement/action.go @@ -0,0 +1,86 @@ +package measurement + +import ( + "context" + "errors" + "fmt" + + "github.com/evcc-io/evcc/api/implement" + "github.com/evcc-io/evcc/plugin" +) + +type Dimmer struct { + Dim *plugin.Config // optional + Dimmed *plugin.Config // optional +} + +func (cc *Dimmer) Configure(ctx context.Context) ( + func(bool) error, + func() (bool, error), + error, +) { + dimS, err := cc.Dim.BoolSetter(ctx, "dim") + if err != nil { + return nil, nil, fmt.Errorf("dim: %w", err) + } + + dimmedG, err := cc.Dimmed.BoolGetter(ctx) + if err != nil { + return nil, nil, fmt.Errorf("dimmed: %w", err) + } + + if (dimS == nil) != (dimmedG == nil) { + return nil, nil, errors.New("must have dim and dimmed or none of both") + } + + return dimS, dimmedG, nil +} + +func (cc *Dimmer) Implement(ctx context.Context, i implement.Caps) error { + dimS, dimmedG, err := cc.Configure(ctx) + if err != nil { + return err + } + + implement.May(i, implement.Dimmer(dimS, dimmedG)) + + return nil +} + +type Curtailer struct { + Curtail *plugin.Config // optional + Curtailed *plugin.Config // optional +} + +func (cc *Curtailer) Configure(ctx context.Context) ( + func(bool) error, + func() (bool, error), + error, +) { + curtailS, err := cc.Curtail.BoolSetter(ctx, "curtail") + if err != nil { + return nil, nil, fmt.Errorf("curtail: %w", err) + } + + curtailedG, err := cc.Curtailed.BoolGetter(ctx) + if err != nil { + return nil, nil, fmt.Errorf("curtailed: %w", err) + } + + if (curtailS == nil) != (curtailedG == nil) { + return nil, nil, errors.New("must have curtail and curtailed or none of both") + } + + return curtailS, curtailedG, nil +} + +func (cc *Curtailer) Implement(ctx context.Context, i implement.Caps) error { + curtailS, curtailedG, err := cc.Configure(ctx) + if err != nil { + return err + } + + implement.May(i, implement.Curtailer(curtailS, curtailedG)) + + return nil +} diff --git a/meter/meter.go b/meter/meter.go index 21b265c4a..a7f959933 100644 --- a/meter/meter.go +++ b/meter/meter.go @@ -18,8 +18,10 @@ func init() { // 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.Energy `mapstructure:",squash"` // energy optional + measurement.Phases `mapstructure:",squash"` // optional + measurement.Dimmer `mapstructure:",squash"` // optional + measurement.Curtailer `mapstructure:",squash"` // optional // pv pvMaxACPower `mapstructure:",squash"` @@ -95,6 +97,14 @@ func NewConfigurableFromConfig(ctx context.Context, other map[string]any) (api.M implement.May(m, implement.PhasePowers(powersG)) implement.May(m, implement.MaxACPowerGetter(cc.pvMaxACPower.Decorator())) + // 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 + } + return m, nil }