Add EEBus HEMS for SteuVE (#14950)

This commit is contained in:
andig 2024-07-27 19:19:45 +02:00 • committed by GitHub
parent f9501e53de
commit 6c3ecdb6ff
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
16 changed files with 625 additions and 29 deletions

View file

@ -222,6 +222,7 @@ type Circuit interface {
SetTitle(string)
GetParent() Circuit
RegisterChild(child Circuit)
Wrap(parent Circuit) error
HasMeter() bool
GetMaxPower() float64
GetMaxCurrent() float64

View file

@ -963,3 +963,17 @@ func (mr *MockCircuitMockRecorder) ValidatePower(arg0, arg1 any) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ValidatePower", reflect.TypeOf((*MockCircuit)(nil).ValidatePower), arg0, arg1)
}
// Wrap mocks base method.
func (m *MockCircuit) Wrap(arg0 Circuit) error {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "Wrap", arg0)
ret0, _ := ret[0].(error)
return ret0
}
// Wrap indicates an expected call of Wrap.
func (mr *MockCircuitMockRecorder) Wrap(arg0 any) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Wrap", reflect.TypeOf((*MockCircuit)(nil).Wrap), arg0)
}

View file

@ -20,6 +20,7 @@ import (
"github.com/evcc-io/evcc/charger"
"github.com/evcc-io/evcc/cmd/shutdown"
"github.com/evcc-io/evcc/core"
"github.com/evcc-io/evcc/core/circuit"
"github.com/evcc-io/evcc/core/keys"
"github.com/evcc-io/evcc/hems"
"github.com/evcc-io/evcc/meter"
@ -160,7 +161,7 @@ NEXT:
}
log := util.NewLogger("circuit-" + cc.Name)
instance, err := core.NewCircuitFromConfig(log, cc.Other)
instance, err := circuit.NewFromConfig(log, cc.Other)
if err != nil {
return fmt.Errorf("cannot create circuit '%s': %w", cc.Name, err)
}

View file

@ -1,4 +1,4 @@
package core
package circuit
import (
"fmt"
@ -34,8 +34,8 @@ type Circuit struct {
powerUpdated time.Time
}
// NewCircuitFromConfig creates a new Circuit
func NewCircuitFromConfig(log *util.Logger, other map[string]interface{}) (api.Circuit, error) {
// NewFromConfig creates a new Circuit
func NewFromConfig(log *util.Logger, other map[string]interface{}) (api.Circuit, error) {
cc := struct {
Title string `mapstructure:"title"` // title
ParentRef string `mapstructure:"parent"` // parent circuit reference
@ -60,7 +60,7 @@ func NewCircuitFromConfig(log *util.Logger, other map[string]interface{}) (api.C
meter = dev.Instance()
}
circuit, err := NewCircuit(log, cc.Title, cc.MaxCurrent, cc.MaxPower, meter, cc.Timeout)
circuit, err := New(log, cc.Title, cc.MaxCurrent, cc.MaxPower, meter, cc.Timeout)
if err != nil {
return nil, err
}
@ -70,14 +70,14 @@ func NewCircuitFromConfig(log *util.Logger, other map[string]interface{}) (api.C
if err != nil {
return nil, err
}
circuit.SetParent(dev.Instance())
circuit.setParent(dev.Instance())
}
return circuit, err
}
// NewCircuit creates a circuit
func NewCircuit(log *util.Logger, title string, maxCurrent, maxPower float64, meter api.Meter, timeout time.Duration) (*Circuit, error) {
// New creates a circuit
func New(log *util.Logger, title string, maxCurrent, maxPower float64, meter api.Meter, timeout time.Duration) (*Circuit, error) {
c := &Circuit{
log: log,
title: title,
@ -119,14 +119,24 @@ func (c *Circuit) GetParent() api.Circuit {
return c.parent
}
// SetParent set parent circuit
func (c *Circuit) SetParent(parent api.Circuit) {
// setParent set parent circuit
func (c *Circuit) setParent(parent api.Circuit) error {
c.mu.Lock()
defer c.mu.Unlock()
if c.parent != nil {
return fmt.Errorf("circuit already has a parent")
}
c.parent = parent
if parent != nil {
parent.RegisterChild(c)
}
return nil
}
// Wrap wraps circuit with parent, keeping the original meter
func (c *Circuit) Wrap(parent api.Circuit) error {
parent.(*Circuit).meter = c.meter
return c.setParent(parent)
}
// HasMeter returns the max power setting

View file

@ -1,4 +1,4 @@
package core
package circuit
import (
"testing"
@ -61,7 +61,7 @@ func TestCircuitPower(t *testing.T) {
circ := func(t *testing.T, ctrl *gomock.Controller, maxP float64) (*Circuit, *api.MockMeter) {
m := api.NewMockMeter(ctrl)
c, err := NewCircuit(log, "foo", 0, maxP, m, 0)
c, err := New(log, "foo", 0, maxP, m, 0)
require.NoError(t, err)
return c, m
}
@ -100,7 +100,7 @@ func TestCircuitCurrents(t *testing.T) {
api.NewMockMeter(ctrl),
api.NewMockPhaseCurrents(ctrl),
}
c, err := NewCircuit(log, "foo", maxC, 0, m, 0)
c, err := New(log, "foo", maxC, 0, m, 0)
require.NoError(t, err)
return c, m
}

View file

@ -19,6 +19,7 @@ import (
"github.com/evcc-io/evcc/core/planner"
"github.com/evcc-io/evcc/core/prioritizer"
"github.com/evcc-io/evcc/core/session"
"github.com/evcc-io/evcc/core/site"
"github.com/evcc-io/evcc/core/soc"
"github.com/evcc-io/evcc/core/vehicle"
"github.com/evcc-io/evcc/push"
@ -54,6 +55,8 @@ type batteryMeasurement struct {
Controllable bool `json:"controllable"`
}
var _ site.API = (*Site)(nil)
// Site is the main configuration container. A site can host multiple loadpoints.
type Site struct {
uiChan chan<- util.Param // client push messages

View file

@ -11,9 +11,6 @@ type API interface {
Loadpoints() []loadpoint.API
Vehicles() Vehicles
// GetCircuit returns the assigned circuit
GetCircuit() api.Circuit
// Meta
GetTitle() string
SetTitle(string)
@ -26,6 +23,10 @@ type API interface {
GetBatteryMeterRefs() []string
SetBatteryMeterRefs([]string)
// circuits
GetCircuit() api.Circuit
SetCircuit(api.Circuit)
//
// battery
//

View file

@ -137,15 +137,20 @@ func (site *Site) Vehicles() site.Vehicles {
return &vehicles{log: site.log}
}
// GetCircuit returns the circuit
// GetCircuit returns the root circuit
func (site *Site) GetCircuit() api.Circuit {
if site.circuit == nil {
// return untyped nil
return nil
}
site.RLock()
defer site.RUnlock()
return site.circuit
}
// SetCircuit sets the root circuit
func (site *Site) SetCircuit(circuit api.Circuit) {
site.Lock()
defer site.Unlock()
site.circuit = circuit
}
// GetPrioritySoc returns the PrioritySoc
func (site *Site) GetPrioritySoc() float64 {
site.RLock()

View file

@ -5,6 +5,7 @@ import (
"strings"
"github.com/evcc-io/evcc/core/site"
"github.com/evcc-io/evcc/hems/eebus"
"github.com/evcc-io/evcc/hems/semp"
"github.com/evcc-io/evcc/server"
)
@ -19,8 +20,8 @@ func NewFromConfig(typ string, other map[string]interface{}, site site.API, http
switch strings.ToLower(typ) {
case "sma", "shm", "semp":
return semp.New(other, site, httpd)
// case "ocpp":
// return ocpp.New(other, site)
case "eebus":
return eebus.New(other, site)
default:
return nil, errors.New("unknown hems: " + typ)
}

183
hems/eebus/eebus.go Normal file
View file

@ -0,0 +1,183 @@
package eebus
import (
"errors"
"sync"
"time"
ucapi "github.com/enbility/eebus-go/usecases/api"
"github.com/evcc-io/evcc/api"
"github.com/evcc-io/evcc/core/circuit"
"github.com/evcc-io/evcc/core/site"
"github.com/evcc-io/evcc/provider"
"github.com/evcc-io/evcc/server/eebus"
"github.com/evcc-io/evcc/util"
)
type EEBus struct {
mux sync.RWMutex
log *util.Logger
*eebus.Connector
uc *eebus.UseCasesCS
root api.Circuit
status status
statusUpdated time.Time
limit *ucapi.LoadLimit // LPC-041
failsafeLimit float64
failsafeDuration time.Duration
heartbeat *provider.Value[struct{}]
}
// New creates an EEBus HEMS from generic config
func New(other map[string]interface{}, site site.API) (*EEBus, error) {
var cc struct {
Ski string
}
if err := util.DecodeOther(other, &cc); err != nil {
return nil, err
}
// get root circuit
root := circuit.Root()
if root == nil {
return nil, errors.New("hems requires load management- please configure root circuit")
}
if !root.HasMeter() {
return nil, errors.New("hems requires root circuit to have meter")
}
// create new root circuit for LPC
lpc, err := circuit.New(util.NewLogger("lpc"), "eebus", 0, 0, nil, time.Minute)
if err != nil {
return nil, err
}
// wrap old root with new pc parent
if err := root.Wrap(lpc); err != nil {
return nil, err
}
site.SetCircuit(lpc)
return NewEEBus(cc.Ski, lpc)
}
// NewEEBus creates EEBus charger
func NewEEBus(ski string, root api.Circuit) (*EEBus, error) {
if eebus.Instance == nil {
return nil, errors.New("eebus not configured")
}
c := &EEBus{
log: util.NewLogger("eebus"),
root: root,
uc: eebus.Instance.ControllableSystem(),
Connector: eebus.NewConnector(nil),
heartbeat: provider.NewValue[struct{}](2 * time.Minute), // LPC-031
}
if err := eebus.Instance.RegisterDevice(ski, c); err != nil {
return nil, err
}
if err := c.Wait(90 * time.Second); err != nil {
return c, err
}
for _, s := range c.uc.LPC.RemoteEntitiesScenarios() {
c.log.DEBUG.Println("LPC RemoteEntitiesScenarios:", s.Scenarios)
}
for _, s := range c.uc.LPP.RemoteEntitiesScenarios() {
c.log.DEBUG.Println("LPP RemoteEntitiesScenarios:", s.Scenarios)
}
for _, s := range c.uc.MGCP.RemoteEntitiesScenarios() {
c.log.DEBUG.Println("MGCP RemoteEntitiesScenarios:", s.Scenarios)
}
return c, nil
}
func (c *EEBus) Run() {
for range time.Tick(10 * time.Second) {
if err := c.run(); err != nil {
c.log.ERROR.Println(err)
}
}
}
// TODO check state machine against spec
func (c *EEBus) run() error {
c.mux.RLock()
defer c.mux.RUnlock()
c.log.TRACE.Println("status:", c.status)
// check heartbeat
_, heartbeatErr := c.heartbeat.Get()
if heartbeatErr != nil && c.status != StatusFailsafe {
// LPC-914/2
c.log.WARN.Println("missing heartbeat- entering failsafe mode")
c.setStatusAndLimit(StatusFailsafe, c.failsafeLimit)
return nil
}
// TODO
// status init
// status Unlimited/controlled
// status Unlimited/autonomous
switch c.status {
case StatusUnlimited:
// LPC-914/1
if c.limit != nil && c.limit.IsActive {
c.log.WARN.Println("active consumption limit")
c.setStatusAndLimit(StatusLimited, c.limit.Value)
}
case StatusLimited:
// limit updated?
if !c.limit.IsActive {
c.log.WARN.Println("inactive consumption limit")
c.setStatusAndLimit(StatusUnlimited, 0)
break
}
c.setLimit(c.limit.Value)
// LPC-914/1
if d := c.limit.Duration; d > 0 && time.Since(c.statusUpdated) > d {
c.limit = nil
c.log.DEBUG.Println("limit duration exceeded- return to normal")
c.setStatusAndLimit(StatusUnlimited, 0)
}
case StatusFailsafe:
// LPC-914/2
if d := c.failsafeDuration; heartbeatErr == nil && time.Since(c.statusUpdated) > d {
c.log.DEBUG.Println("heartbeat returned and failsafe duration exceeded- return to normal")
c.setStatusAndLimit(StatusUnlimited, 0)
}
}
return nil
}
func (c *EEBus) setStatusAndLimit(status status, limit float64) {
c.status = status
c.statusUpdated = time.Now()
c.setLimit(limit)
}
func (c *EEBus) setLimit(limit float64) {
c.root.SetMaxPower(limit)
}

158
hems/eebus/events.go Normal file
View file

@ -0,0 +1,158 @@
package eebus
import (
eebusapi "github.com/enbility/eebus-go/api"
"github.com/enbility/eebus-go/usecases/cs/lpc"
spineapi "github.com/enbility/spine-go/api"
"github.com/evcc-io/evcc/server/eebus"
)
var _ eebus.Device = (*EEBus)(nil)
// UseCaseEvent implements the eebus.Device interface
func (c *EEBus) UseCaseEvent(_ spineapi.DeviceRemoteInterface, entity spineapi.EntityRemoteInterface, event eebusapi.EventType) {
switch event {
// Load control obligation limit data update received
//
// Use `ConsumptionLimit` to get the current data
//
// Use Case LPC, Scenario 1
case lpc.DataUpdateLimit:
c.dataUpdateLimit()
// An incoming load control obligation limit needs to be approved or denied
//
// Use `PendingConsumptionLimits` to get the currently pending write approval requests
// and invoke `ApproveOrDenyConsumptionLimit` for each
//
// Use Case LPC, Scenario 1
case lpc.WriteApprovalRequired:
c.writeApprovalRequired()
// Failsafe limit for the consumed active (real) power of the
// Controllable System data update received
//
// Use `FailsafeConsumptionActivePowerLimit` to get the current data
//
// Use Case LPC, Scenario 2
case lpc.DataUpdateFailsafeConsumptionActivePowerLimit:
c.dataUpdateFailsafeConsumptionActivePowerLimit()
// Minimum time the Controllable System remains in "failsafe state" unless conditions
// specified in this Use Case permit leaving the "failsafe state" data update received
//
// Use `FailsafeDurationMinimum` to get the current data
//
// Use Case LPC, Scenario 2
case lpc.DataUpdateFailsafeDurationMinimum:
c.dataUpdateFailsafeDurationMinimum()
// Indicates a notify heartbeat event the application should care of.
// E.g. going into or out of the Failsafe state
//
// Use Case LPC, Scenario 3
case lpc.DataUpdateHeartbeat:
c.dataUpdateHeartbeat()
// // Load control obligation limit data update received
// //
// // Use `ProductionLimit` to get the current data
// //
// // Use Case LPC, Scenario 1
// case lpp.DataUpdateLimit:
// c.dataUpdateLimit()
// // An incoming load control obligation limit needs to be approved or denied
// //
// // Use `PendingProductionLimits` to get the currently pending write approval requests
// // and invoke `ApproveOrDenyProductionLimit` for each
// //
// // Use Case LPC, Scenario 1
// case lpp.WriteApprovalRequired:
// c.writeApprovalRequired()
// // Failsafe limit for the produced active (real) power of the
// // Controllable System data update received
// //
// // Use `FailsafeProductionActivePowerLimit` to get the current data
// //
// // Use Case LPC, Scenario 2
// case lpp.DataUpdateFailsafeProductionActivePowerLimit:
// c.dataUpdateFailsafeProductionActivePowerLimit()
// // Minimum time the Controllable System remains in "failsafe state" unless conditions
// // specified in this Use Case permit leaving the "failsafe state" data update received
// //
// // Use `FailsafeDurationMinimum` to get the current data
// //
// // Use Case LPC, Scenario 2
// case lpp.DataUpdateFailsafeDurationMinimum:
// c.dataUpdateFailsafeDurationMinimum()
// // Indicates a notify heartbeat event the application should care of.
// // E.g. going into or out of the Failsafe state
// //
// // Use Case LPP, Scenario 3
// case lpp.DataUpdateHeartbeat:
// c.dataUpdateHeartbeat()
}
}
func (c *EEBus) dataUpdateLimit() {
limit, err := c.uc.LPC.ConsumptionLimit()
if err != nil {
c.log.ERROR.Println("LPC.ConsumptionLimit:", err)
return
}
c.mux.Lock()
defer c.mux.Unlock()
c.limit = &limit
}
func (c *EEBus) writeApprovalRequired() {
for k, v := range c.uc.LPC.PendingConsumptionLimits() {
c.log.DEBUG.Println("LPC.PendingConsumptionLimit:", k, v)
c.uc.LPC.ApproveOrDenyConsumptionLimit(k, false, "not implemented")
}
}
func (c *EEBus) dataUpdateFailsafeConsumptionActivePowerLimit() {
limit, _, err := c.uc.LPC.FailsafeConsumptionActivePowerLimit()
if err != nil {
c.log.ERROR.Println("LPC.FailsafeConsumptionActivePowerLimit:", err)
return
}
c.mux.Lock()
defer c.mux.Unlock()
c.failsafeLimit = limit
}
func (c *EEBus) dataUpdateFailsafeDurationMinimum() {
duration, _, err := c.uc.LPC.FailsafeDurationMinimum()
if err != nil {
c.log.ERROR.Println("LPC.FailsafeDurationMinimum:", err)
return
}
c.mux.Lock()
defer c.mux.Unlock()
c.failsafeDuration = duration
}
func (c *EEBus) dataUpdateHeartbeat() {
c.mux.Lock()
defer c.mux.Unlock()
c.heartbeat.Set(struct{}{})
}
// func (c *EEBus)dataUpdateLimit(){}
// func (c *EEBus)writeApprovalRequired(){}
// func (c *EEBus)dataUpdateFailsafeProductionActivePowerLimit(){}
// func (c *EEBus)dataUpdateFailsafeDurationMinimum(){}
// func (c *EEBus)dataUpdateHeartbeat(){}

9
hems/eebus/types.go Normal file
View file

@ -0,0 +1,9 @@
package eebus
type status int
const (
StatusUnlimited status = iota
StatusLimited
StatusFailsafe
)

153
meter/eebus.go Normal file
View file

@ -0,0 +1,153 @@
package meter
import (
"errors"
"time"
eebusapi "github.com/enbility/eebus-go/api"
"github.com/enbility/eebus-go/usecases/ma/mgcp"
spineapi "github.com/enbility/spine-go/api"
"github.com/evcc-io/evcc/api"
"github.com/evcc-io/evcc/provider"
"github.com/evcc-io/evcc/server/eebus"
"github.com/evcc-io/evcc/util"
)
type EEBus struct {
log *util.Logger
*eebus.Connector
uc *eebus.UseCasesCS
power, energy *provider.Value[float64]
voltages, currents *provider.Value[[]float64]
}
func init() {
registry.Add("eebus", NewEEBusFromConfig)
}
// New creates an EEBus HEMS from generic config
func NewEEBusFromConfig(other map[string]interface{}) (api.Meter, error) {
cc := struct {
Ski string
Timeout time.Duration
}{
Timeout: 10 * time.Second,
}
if err := util.DecodeOther(other, &cc); err != nil {
return nil, err
}
return NewEEBus(cc.Ski, cc.Timeout)
}
// NewEEBus creates EEBus charger
func NewEEBus(ski string, timeout time.Duration) (*EEBus, error) {
if eebus.Instance == nil {
return nil, errors.New("eebus not configured")
}
c := &EEBus{
log: util.NewLogger("eebus"),
uc: eebus.Instance.ControllableSystem(),
Connector: eebus.NewConnector(nil),
power: provider.NewValue[float64](timeout),
energy: provider.NewValue[float64](timeout),
voltages: provider.NewValue[[]float64](timeout),
currents: provider.NewValue[[]float64](timeout),
}
if err := eebus.Instance.RegisterDevice(ski, c); err != nil {
return nil, err
}
if err := c.Wait(90 * time.Second); err != nil {
return c, err
}
return c, nil
}
var _ eebus.Device = (*EEBus)(nil)
// UseCaseEvent implements the eebus.Device interface
func (c *EEBus) UseCaseEvent(_ spineapi.DeviceRemoteInterface, entity spineapi.EntityRemoteInterface, event eebusapi.EventType) {
switch event {
case mgcp.DataUpdatePower:
c.dataUpdatePower(entity)
case mgcp.DataUpdateEnergyConsumed:
c.dataUpdateEnergyConsumed(entity)
case mgcp.DataUpdateCurrentPerPhase:
c.dataUpdateCurrentPerPhase(entity)
case mgcp.DataUpdateVoltagePerPhase:
c.dataUpdateVoltagePerPhase(entity)
}
}
func (c *EEBus) dataUpdatePower(entity spineapi.EntityRemoteInterface) {
data, err := c.uc.MGCP.Power(entity)
if err != nil {
c.log.ERROR.Println("MGCP.Power:", err)
return
}
c.power.Set(data)
}
func (c *EEBus) dataUpdateEnergyConsumed(entity spineapi.EntityRemoteInterface) {
data, err := c.uc.MGCP.EnergyConsumed(entity)
if err != nil {
c.log.ERROR.Println("MGCP.EnergyConsumed:", err)
return
}
c.energy.Set(data)
}
func (c *EEBus) dataUpdateCurrentPerPhase(entity spineapi.EntityRemoteInterface) {
data, err := c.uc.MGCP.CurrentPerPhase(entity)
if err != nil {
c.log.ERROR.Println("MGCP.CurrentPerPhase:", err)
return
}
c.currents.Set(data)
}
func (c *EEBus) dataUpdateVoltagePerPhase(entity spineapi.EntityRemoteInterface) {
data, err := c.uc.MGCP.VoltagePerPhase(entity)
if err != nil {
c.log.ERROR.Println("MGCP.VoltagePerPhase:", err)
return
}
c.voltages.Set(data)
}
func (c *EEBus) CurrentPower() (float64, error) {
return c.power.Get()
}
func (c *EEBus) TotalEnergy() (float64, error) {
return c.energy.Get()
}
func (c *EEBus) PhaseCurrents() (float64, float64, float64, error) {
res, err := c.currents.Get()
if err == nil && len(res) != 3 {
err = errors.New("invalid phase currents")
}
if err != nil {
return 0, 0, 0, err
}
return res[0], res[1], res[2], nil
}
func (c *EEBus) PhaseVoltages() (float64, float64, float64, error) {
res, err := c.voltages.Get()
if err == nil && len(res) != 3 {
err = errors.New("invalid phase voltages")
}
if err != nil {
return 0, 0, 0, err
}
return res[0], res[1], res[2], nil
}

View file

@ -105,3 +105,39 @@ func (c *cached[T]) shouldRetryWithBackoff() bool {
return false
}
// Value is a cacheable value that can expire
type Value[T any] struct {
mux sync.RWMutex
clock clock.Clock
updated time.Time
cache time.Duration
val T
}
func NewValue[T any](cache time.Duration) *Value[T] {
return &Value[T]{
clock: clock.New(),
cache: cache,
}
}
func (v *Value[T]) Get() (T, error) {
v.mux.RLock()
defer v.mux.RUnlock()
if v.clock.Since(v.updated) > v.cache {
var zero T
return zero, api.ErrTimeout
}
return v.val, nil
}
func (v *Value[T]) Set(val T) {
v.mux.Lock()
defer v.mux.Unlock()
v.val = val
v.updated = v.clock.Now()
}

View file

@ -19,6 +19,9 @@ import (
"github.com/enbility/eebus-go/usecases/cem/evsoc"
"github.com/enbility/eebus-go/usecases/cem/opev"
"github.com/enbility/eebus-go/usecases/cem/oscev"
"github.com/enbility/eebus-go/usecases/cs/lpc"
"github.com/enbility/eebus-go/usecases/cs/lpp"
"github.com/enbility/eebus-go/usecases/ma/mgcp"
shipapi "github.com/enbility/ship-go/api"
"github.com/enbility/ship-go/mdns"
shiputil "github.com/enbility/ship-go/util"
@ -42,11 +45,17 @@ type UseCasesEVSE struct {
OpEV ucapi.CemOPEVInterface
OscEV ucapi.CemOSCEVInterface
}
type UseCasesCS struct {
LPC ucapi.CsLPCInterface
LPP ucapi.CsLPPInterface
MGCP ucapi.MaMGCPInterface
}
type EEBus struct {
service eebusapi.ServiceInterface
evseUC *UseCasesEVSE
evseUC UseCasesEVSE
csUC UseCasesCS
mux sync.Mutex
log *util.Logger
@ -133,7 +142,7 @@ func NewServer(other Config) (*EEBus, error) {
localEntity := c.service.LocalDevice().EntityForType(model.EntityTypeTypeCEM)
// evse
c.evseUC = &UseCasesEVSE{
c.evseUC = UseCasesEVSE{
EvseCC: evsecc.NewEVSECC(localEntity, c.ucCallback),
EvCC: evcc.NewEVCC(c.service, localEntity, c.ucCallback),
EvCem: evcem.NewEVCEM(c.service, localEntity, c.ucCallback),
@ -142,11 +151,19 @@ func NewServer(other Config) (*EEBus, error) {
EvSoc: evsoc.NewEVSOC(localEntity, c.ucCallback),
}
// controllable system
c.csUC = UseCasesCS{
LPC: lpc.NewLPC(localEntity, c.ucCallback),
LPP: lpp.NewLPP(localEntity, c.ucCallback),
MGCP: mgcp.NewMGCP(localEntity, c.ucCallback),
}
// register use cases
for _, uc := range []eebusapi.UseCaseInterface{
c.evseUC.EvseCC, c.evseUC.EvCC,
c.evseUC.EvCem, c.evseUC.OpEV,
c.evseUC.OscEV, c.evseUC.EvSoc,
c.csUC.LPC, c.csUC.LPP, c.csUC.MGCP,
} {
c.service.AddUseCase(uc)
}
@ -172,7 +189,11 @@ func (c *EEBus) RegisterDevice(ski string, device Device) error {
}
func (c *EEBus) Evse() *UseCasesEVSE {
return c.evseUC
return &c.evseUC
}
func (c *EEBus) ControllableSystem() *UseCasesCS {
return &c.csUC
}
func (c *EEBus) Run() {

View file

@ -8,7 +8,7 @@ import (
"github.com/evcc-io/evcc/api"
"github.com/evcc-io/evcc/charger"
"github.com/evcc-io/evcc/core"
"github.com/evcc-io/evcc/core/circuit"
"github.com/evcc-io/evcc/meter"
"github.com/evcc-io/evcc/util"
"github.com/evcc-io/evcc/util/config"
@ -233,7 +233,7 @@ func newDeviceHandler(w http.ResponseWriter, r *http.Request) {
case templates.Circuit:
conf, err = newDevice(class, req, func(_ string, other map[string]interface{}) (api.Circuit, error) {
return core.NewCircuitFromConfig(util.NewLogger("circuit"), other)
return circuit.NewFromConfig(util.NewLogger("circuit"), other)
}, config.Circuits())
}
@ -304,7 +304,7 @@ func updateDeviceHandler(w http.ResponseWriter, r *http.Request) {
case templates.Circuit:
err = updateDevice(id, class, req, func(_ string, other map[string]interface{}) (api.Circuit, error) {
return core.NewCircuitFromConfig(util.NewLogger("circuit"), other)
return circuit.NewFromConfig(util.NewLogger("circuit"), other)
}, config.Circuits())
}