evcc-io/hems/relay/relay.go

171 lines
3.4 KiB
Go

package relay
import (
"context"
"errors"
"fmt"
"sync"
"time"
"github.com/evcc-io/evcc/api"
"github.com/evcc-io/evcc/core/site"
"github.com/evcc-io/evcc/hems/config"
"github.com/evcc-io/evcc/hems/smartgrid"
"github.com/evcc-io/evcc/plugin"
"github.com/evcc-io/evcc/util"
)
func init() {
config.AddCtx("relay", NewFromConfig)
}
type Relay struct {
mu sync.Mutex
log *util.Logger
site site.API
w1 func() (bool, error)
passthrough func(bool) error
publishFunc func()
smartgridID uint
limit *float64
maxPower float64
interval time.Duration
}
// NewFromConfig creates an Relay HEMS from generic config
func NewFromConfig(ctx context.Context, other map[string]any, site site.API) (*Relay, error) {
cc := struct {
MaxPower float64
Limit plugin.Config
Passthrough *plugin.Config
Interval time.Duration
}{
Interval: 10 * time.Second,
}
if err := util.DecodeOther(other, &cc); err != nil {
return nil, err
}
// limit getter
limitG, err := cc.Limit.BoolGetter(ctx)
if err != nil {
return nil, err
}
passthroughS, err := cc.Passthrough.BoolSetter(ctx, "dim")
if err != nil {
return nil, err
}
return NewRelay(site, limitG, passthroughS, cc.MaxPower, cc.Interval)
}
// NewRelay creates Relay HEMS
func NewRelay(site site.API, w1 func() (bool, error), passthrough func(bool) error, maxPower float64, interval time.Duration) (*Relay, error) {
c := &Relay{
log: util.NewLogger("relay"),
site: site,
passthrough: passthrough,
maxPower: maxPower,
w1: w1,
interval: interval,
}
if maxPower == 0 {
return nil, errors.New("missing power limit")
}
return c, nil
}
func (c *Relay) SetUpdated(f func()) {
c.mu.Lock()
defer c.mu.Unlock()
c.publishFunc = f
}
func (c *Relay) Run() {
// run immediately, then on every tick
for tick := time.Tick(c.interval); ; <-tick {
if err := c.run(); err != nil {
c.log.ERROR.Println(err)
}
if c.publishFunc != nil {
c.publishFunc()
}
}
}
func (c *Relay) run() error {
active, err := c.w1()
if err != nil {
return err
}
var limit float64
if active {
limit = c.maxPower
}
if err := c.setConsumptionLimit(limit); err != nil {
return err
}
if err := smartgrid.UpdateSession(&c.smartgridID, smartgrid.Dim, c.site.GetGridPower(), limit, active); err != nil {
return fmt.Errorf("smartgrid session: %v", err)
}
return nil
}
func (c *Relay) setConsumptionLimit(limit float64) error {
c.mu.Lock()
defer c.mu.Unlock()
c.limit = nil
if limit > 0 {
c.limit = new(limit)
}
if c.passthrough != nil {
if err := c.passthrough(limit > 0); err != nil {
return fmt.Errorf("passthrough failed: %w", err)
}
}
return nil
}
var _ api.HEMS = (*Relay)(nil)
// Dimmed implements api.HEMS, derived from the active consumption limit.
func (c *Relay) Dimmed() *bool {
c.mu.Lock()
defer c.mu.Unlock()
return new(c.limit != nil)
}
// CurtailedPercent implements api.HEMS. Relay does not curtail production and
// hence makes no statement.
func (c *Relay) CurtailedPercent() *int {
return nil
}
// MaxConsumptionPower implements api.HEMS, returning the active wattage cap.
func (c *Relay) MaxConsumptionPower() float64 {
c.mu.Lock()
defer c.mu.Unlock()
if c.limit == nil {
return 0
}
return *c.limit
}
// MaxProductionPower implements api.HEMS. Scaffolding only.
func (c *Relay) MaxProductionPower() *float64 {
return nil
}