165 lines
3.2 KiB
Go
165 lines
3.2 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/smartgrid"
|
|
"github.com/evcc-io/evcc/plugin"
|
|
"github.com/evcc-io/evcc/util"
|
|
)
|
|
|
|
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() {
|
|
for range time.Tick(c.interval) {
|
|
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)
|
|
}
|
|
|
|
// Curtailed implements api.HEMS. Relay does not curtail production and
|
|
// hence makes no statement.
|
|
func (c *Relay) Curtailed() *bool {
|
|
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
|
|
}
|