HEMS: add templates (#25832)

Co-authored-by: Michael Geers <michael@geers.tv>
Co-authored-by: premultiply <4681172+premultiply@users.noreply.github.com>
This commit is contained in:
andig 2026-06-27 14:32:13 +02:00 • committed by GitHub
parent 37e96867ab
commit 06f6eb1d26
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
35 changed files with 985 additions and 175 deletions

View file

@ -6,24 +6,26 @@ import (
"strings"
"github.com/evcc-io/evcc/core/site"
"github.com/evcc-io/evcc/hems/eebus"
"github.com/evcc-io/evcc/hems/fnn"
"github.com/evcc-io/evcc/hems/config"
_ "github.com/evcc-io/evcc/hems/eebus"
_ "github.com/evcc-io/evcc/hems/fnn"
"github.com/evcc-io/evcc/hems/hems"
"github.com/evcc-io/evcc/hems/relay"
_ "github.com/evcc-io/evcc/hems/relay"
)
// NewFromConfig creates new HEMS from config
var registry = config.Registry
// Types returns the list of types
func Types() []string {
return registry.Types()
}
// NewFromConfig creates hems from configuration
func NewFromConfig(ctx context.Context, typ string, other map[string]any, site site.API) (hems.API, error) {
switch strings.ToLower(typ) {
case "sma", "shm", "semp":
return nil, errors.New("breaking change: Sunny Home Manager integration is always on. See https://github.com/evcc-io/evcc/releases and https://docs.evcc.io/en/docs/integrations/sma-sunny-home-manager")
case "eebus":
return eebus.NewFromConfig(ctx, other, site)
case "fnn", "fnn-3":
return fnn.NewFromConfig(ctx, other, site)
case "relay":
return relay.NewFromConfig(ctx, other, site)
default:
return nil, errors.New("unknown hems: " + typ)
return config.NewFromConfig(ctx, typ, other, site)
}
}

35
hems/config/config.go Normal file
View file

@ -0,0 +1,35 @@
package config
import (
"context"
"fmt"
"strings"
"github.com/evcc-io/evcc/core/site"
"github.com/evcc-io/evcc/hems/hems"
reg "github.com/evcc-io/evcc/hems/registry"
)
var Registry = reg.New[hems.API]("hems")
// AddCtx registers a factory returning a concrete type that implements hems.API
func AddCtx[T hems.API](name string, factory func(context.Context, map[string]any, site.API) (T, error)) {
Registry.AddCtx(name, func(ctx context.Context, other map[string]any, site site.API) (hems.API, error) {
return factory(ctx, other, site)
})
}
// NewFromConfig creates hems from configuration
func NewFromConfig(ctx context.Context, typ string, other map[string]any, site site.API) (hems.API, error) {
factory, err := Registry.Get(strings.ToLower(typ))
if err != nil {
return nil, err
}
v, err := factory(ctx, other, site)
if err != nil {
return nil, fmt.Errorf("cannot create hems type '%s': %w", typ, err)
}
return v, nil
}

View file

@ -8,12 +8,17 @@ import (
ucapi "github.com/enbility/eebus-go/usecases/api"
"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/server/eebus"
"github.com/evcc-io/evcc/util"
)
func init() {
config.AddCtx("eebus", NewFromConfig)
}
type EEBus struct {
mux sync.RWMutex
log *util.Logger
@ -63,7 +68,10 @@ func NewFromConfig(ctx context.Context, other map[string]any, site site.API) (*E
Interval time.Duration
}{
Limits: Limits{
ContractualConsumptionNominalMax: 24800,
// contractual max power at the grid connection point reported to the control box
// (EEBus LPC, EMS device type). Default: standard 3x35A x 230V house connection.
// This is the connection capacity, not the SteuVE Pmin (see failsafe limit below).
ContractualConsumptionNominalMax: 24150, // 3 * 35A * 230V
FailsafeConsumptionActivePowerLimit: 4200,
ProductionNominalMax: 0,

View file

@ -9,11 +9,17 @@ import (
"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("fnn", NewFromConfig)
config.AddCtx("fnn-3", NewFromConfig)
}
// NewFromConfig creates an FNN HEMS from generic config.
func NewFromConfig(ctx context.Context, other map[string]any, site site.API) (*Fnn, error) {
cc := struct {

52
hems/registry/registry.go Normal file
View file

@ -0,0 +1,52 @@
package registry
import (
"context"
"fmt"
"maps"
"slices"
"github.com/evcc-io/evcc/core/site"
)
type (
// TODO check is this can be abstracted into a generic type parameter
factoryFunc[T any] func(context.Context, map[string]any, site.API) (T, error)
registry[T any] struct {
typ string
data map[string]factoryFunc[T]
}
)
func (r registry[T]) Add(name string, factory func(map[string]any, site.API) (T, error)) {
r.AddCtx(name, func(_ context.Context, cc map[string]any, site site.API) (T, error) {
return factory(cc, site)
})
}
func (r registry[T]) AddCtx(name string, factory factoryFunc[T]) {
if _, exists := r.data[name]; exists {
panic(fmt.Sprintf("cannot register duplicate %s type: %s", r.typ, name))
}
r.data[name] = factory
}
func (r registry[T]) Get(name string) (factoryFunc[T], error) {
factory, exists := r.data[name]
if !exists {
return nil, fmt.Errorf("invalid %s type: %s", r.typ, name)
}
return factory, nil
}
func (r registry[T]) Types() []string {
return slices.Sorted(maps.Keys(r.data))
}
func New[T any](typ string) registry[T] {
return registry[T]{
typ: typ,
data: make(map[string]factoryFunc[T]),
}
}

View file

@ -9,11 +9,16 @@ import (
"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

22
hems/template.go Normal file
View file

@ -0,0 +1,22 @@
package hems
import (
"context"
"github.com/evcc-io/evcc/core/site"
"github.com/evcc-io/evcc/hems/hems"
"github.com/evcc-io/evcc/util/templates"
)
func init() {
registry.AddCtx("template", NewHemsFromTemplateConfig)
}
func NewHemsFromTemplateConfig(ctx context.Context, other map[string]any, site site.API) (hems.API, error) {
instance, err := templates.RenderInstance(templates.Hems, other)
if err != nil {
return nil, err
}
return NewFromConfig(ctx, instance.Type, instance.Other, site)
}