From 7e9195d0e3c47ccd2d9a040625a8c916573a1419 Mon Sep 17 00:00:00 2001 From: andig Date: Tue, 30 Jun 2026 13:45:49 +0200 Subject: [PATCH] EEBus: add heating brands (BC) (#31344) --- charger/{eebus.go => eebus-evse.go} | 2 +- charger/eebus-ohpcf.go | 72 +++++++++++++++++-- charger/eebus-ohpcf_test.go | 3 + .../charger/{eebus.yaml => eebus-evse.yaml} | 4 +- .../charger/eebus-ohpcf-wolfvaillant.yaml | 38 ++++++++++ templates/definition/charger/eebus-ohpcf.yaml | 7 +- .../charger/elli-charger-connect.yaml | 1 + .../definition/charger/elli-charger-pro.yaml | 1 + templates/definition/charger/evbox-livo.yaml | 1 + .../definition/charger/porsche-pmcc.yaml | 1 + .../definition/charger/porsche-pmcp.yaml | 1 + templates/definition/meter/eebus-mgcp.yaml | 1 + templates/definition/meter/eebus-mpc.yaml | 1 + util/templates/includes/eebus.tpl | 1 - 14 files changed, 120 insertions(+), 14 deletions(-) rename charger/{eebus.go => eebus-evse.go} (99%) rename templates/definition/charger/{eebus.yaml => eebus-evse.yaml} (78%) create mode 100644 templates/definition/charger/eebus-ohpcf-wolfvaillant.yaml diff --git a/charger/eebus.go b/charger/eebus-evse.go similarity index 99% rename from charger/eebus.go rename to charger/eebus-evse.go index b47091209..b4a1d9200 100644 --- a/charger/eebus.go +++ b/charger/eebus-evse.go @@ -49,7 +49,7 @@ type EEBus struct { } func init() { - registry.AddCtx("eebus", NewEEBusFromConfig) + registry.AddCtx("eebus-evse", NewEEBusFromConfig) } // NewEEBusFromConfig creates an EEBus charger from generic config diff --git a/charger/eebus-ohpcf.go b/charger/eebus-ohpcf.go index 4a3360268..15d347e43 100644 --- a/charger/eebus-ohpcf.go +++ b/charger/eebus-ohpcf.go @@ -10,6 +10,7 @@ import ( eebusapi "github.com/enbility/eebus-go/api" ucapi "github.com/enbility/eebus-go/usecases/api" "github.com/enbility/eebus-go/usecases/cem/ohpcf" + "github.com/enbility/eebus-go/usecases/eg/lpc" "github.com/enbility/eebus-go/usecases/ma/mdt" "github.com/enbility/eebus-go/usecases/ma/mpc" spineapi "github.com/enbility/spine-go/api" @@ -30,17 +31,19 @@ type EEBusOHPCF struct { *embed cem *eebus.CustomerEnergyManagement ma *eebus.MonitoringAppliance + eg *eebus.EnergyGuard ctx context.Context reboost time.Duration - mu sync.RWMutex - log *util.Logger - compressor spineapi.EntityRemoteInterface - mpcEntity spineapi.EntityRemoteInterface - dhwEntity spineapi.EntityRemoteInterface - enabled bool - reboosting bool + mu sync.RWMutex + log *util.Logger + compressor spineapi.EntityRemoteInterface + mpcEntity spineapi.EntityRemoteInterface + dhwEntity spineapi.EntityRemoteInterface + egLpcEntity spineapi.EntityRemoteInterface + enabled bool + reboosting bool connector *eebus.Connector } @@ -86,6 +89,7 @@ func NewEEBusOHPCF(ctx context.Context, embed *embed, ski, ip string, reboost ti log: util.NewLogger("eebus-ohpcf"), cem: inst.CustomerEnergyManagement(), ma: inst.MonitoringAppliance(), + eg: inst.EnergyGuard(), connector: eebus.NewConnector(), ctx: ctx, reboost: reboost, @@ -125,6 +129,7 @@ func (c *EEBusOHPCF) Connect(connected bool) { c.compressor = nil c.mpcEntity = nil c.dhwEntity = nil + c.egLpcEntity = nil } // UseCaseEvent implements the eebus.Device interface @@ -162,6 +167,15 @@ func (c *EEBusOHPCF) UseCaseEvent(_ spineapi.DeviceRemoteInterface, entity spine c.mu.Lock() c.dhwEntity = entity c.mu.Unlock() + + // Energy Guard LPC carries the §14a/LPC consumption limit + case lpc.UseCaseSupportUpdate: + c.mu.Lock() + // use most specific selector + if c.egLpcEntity == nil || len(entity.Address().Entity) < len(c.egLpcEntity.Address().Entity) { + c.egLpcEntity = entity + } + c.mu.Unlock() } } @@ -360,6 +374,50 @@ func (c *EEBusOHPCF) MaxCurrent(int64) error { return c.apply() } +var _ api.Dimmer = (*EEBusOHPCF)(nil) + +// Dimmed implements the api.Dimmer interface, reporting whether a §14a/LPC +// consumption limit is currently active on the heat pump. +func (c *EEBusOHPCF) Dimmed() (bool, error) { + c.mu.RLock() + entity := c.egLpcEntity + c.mu.RUnlock() + + if entity == nil || !c.eg.EgLPCInterface.IsScenarioAvailableAtEntity(entity, eebus.LPCLimit) { + return false, api.ErrNotAvailable + } + + limit, err := c.eg.EgLPCInterface.ConsumptionLimit(entity) + if err != nil { + // scenario announced but no usable value yet + if errors.Is(err, eebusapi.ErrDataNotAvailable) || + errors.Is(err, eebusapi.ErrMetadataNotAvailable) || + errors.Is(err, eebusapi.ErrDataInvalid) { + return false, api.ErrNotAvailable + } + return false, err + } + + return limit.IsActive && limit.Value > 0, nil +} + +// Dim implements the api.Dimmer interface. It writes a §14a/LPC consumption +// limit (fixed 0W safe limit) to the heat pump while dimmed, releasing it otherwise. +func (c *EEBusOHPCF) Dim(dim bool) error { + c.mu.RLock() + entity := c.egLpcEntity + c.mu.RUnlock() + + if entity == nil || !c.eg.EgLPCInterface.IsScenarioAvailableAtEntity(entity, eebus.LPCLimit) { + return api.ErrNotAvailable + } + + // TODO: change api.Dimmer to make the limit configurable; use a fixed 0W safe limit for now + return c.await(func(cb func(model.ResultDataType)) (*model.MsgCounterType, error) { + return c.eg.EgLPCInterface.WriteConsumptionLimit(entity, ucapi.LoadLimit{Value: 0, IsActive: dim}, cb) + }) +} + // apply issues the command to align the optional consumption with the on/off // intent. It is idempotent: ohpcfControlAction only acts on a state transition. func (c *EEBusOHPCF) apply() error { diff --git a/charger/eebus-ohpcf_test.go b/charger/eebus-ohpcf_test.go index 7f34c5303..bcada9107 100644 --- a/charger/eebus-ohpcf_test.go +++ b/charger/eebus-ohpcf_test.go @@ -23,6 +23,9 @@ func TestEEBusOHPCFNotConnected(t *testing.T) { require.ErrorIs(t, c.Enable(true), errNotConnected) require.ErrorIs(t, c.MaxCurrent(16), errNotConnected) + + // dimming uses EG LPC, which is unavailable without an LPC entity + require.ErrorIs(t, c.Dim(true), api.ErrNotAvailable) } // status mapping: running is C, every other connected state (incl. completed diff --git a/templates/definition/charger/eebus.yaml b/templates/definition/charger/eebus-evse.yaml similarity index 78% rename from templates/definition/charger/eebus.yaml rename to templates/definition/charger/eebus-evse.yaml index 8efd412c8..a270a29b9 100644 --- a/templates/definition/charger/eebus.yaml +++ b/templates/definition/charger/eebus-evse.yaml @@ -1,4 +1,5 @@ -template: eebus +template: eebus-evse +covers: ["eebus"] products: - description: de: EEBUS kompatibel @@ -8,5 +9,6 @@ capabilities: ["mA", "meter", "dim"] params: - preset: eebus render: | + type: eebus-evse {{ include "eebus" . }} meter: true diff --git a/templates/definition/charger/eebus-ohpcf-wolfvaillant.yaml b/templates/definition/charger/eebus-ohpcf-wolfvaillant.yaml new file mode 100644 index 000000000..ccdcfd55a --- /dev/null +++ b/templates/definition/charger/eebus-ohpcf-wolfvaillant.yaml @@ -0,0 +1,38 @@ +template: eebus-ohpcf-wolfvaillant +products: + - brand: Wolf + description: + generic: CHA-Monoblock (07, 10, 16/20, 20/24 with EEBus) + - brand: Wolf + description: + generic: FHA-Monoblock (EEBus) + - brand: Wolf + description: + generic: EEBus + - brand: Vaillant + description: + generic: aroTHERM plus (EEBus) + - brand: Vaillant + description: + generic: EEBus +group: heating +capabilities: ["meter", "dim"] +params: + - preset: eebus + - name: reboost + type: duration + default: 10m + advanced: true + description: + de: Reboost-Intervall + en: Reboost interval + help: + de: Wartezeit, nach der eine neu angekündigte optionale Leistungsaufnahme erneut eingeplant wird. + en: Wait time after which a newly announced optional power consumption is scheduled again. +render: | + type: eebus-ohpcf + {{ include "eebus" . }} + reboost: {{ .reboost }} + features: + - heating + - integrateddevice diff --git a/templates/definition/charger/eebus-ohpcf.yaml b/templates/definition/charger/eebus-ohpcf.yaml index e933432d6..8594f698d 100644 --- a/templates/definition/charger/eebus-ohpcf.yaml +++ b/templates/definition/charger/eebus-ohpcf.yaml @@ -3,8 +3,8 @@ products: - description: de: EEBUS Wärmepumpe (OHPCF) en: EEBUS heat pump (OHPCF) -group: heating -capabilities: ["meter"] +group: heatinggeneric +capabilities: ["meter", "dim"] params: - preset: eebus - name: reboost @@ -19,8 +19,7 @@ params: en: Wait time after which a newly announced optional power consumption is scheduled again. render: | type: eebus-ohpcf - ski: {{ .ski }} - {{ if .ip }}ip: {{ .ip }}{{ end }} + {{ include "eebus" . }} reboost: {{ .reboost }} features: - heating diff --git a/templates/definition/charger/elli-charger-connect.yaml b/templates/definition/charger/elli-charger-connect.yaml index 5e32a7738..37d1db2f2 100644 --- a/templates/definition/charger/elli-charger-connect.yaml +++ b/templates/definition/charger/elli-charger-connect.yaml @@ -43,4 +43,5 @@ params: - preset: eebus - name: ip render: | + type: eebus-evse {{ include "eebus" . }} diff --git a/templates/definition/charger/elli-charger-pro.yaml b/templates/definition/charger/elli-charger-pro.yaml index 96953e89f..7b1addf48 100644 --- a/templates/definition/charger/elli-charger-pro.yaml +++ b/templates/definition/charger/elli-charger-pro.yaml @@ -39,6 +39,7 @@ params: - preset: eebus - name: ip render: | + type: eebus-evse {{ include "eebus" . }} meter: true chargedEnergy: false diff --git a/templates/definition/charger/evbox-livo.yaml b/templates/definition/charger/evbox-livo.yaml index 623f6a21d..dc01f056d 100644 --- a/templates/definition/charger/evbox-livo.yaml +++ b/templates/definition/charger/evbox-livo.yaml @@ -11,4 +11,5 @@ requirements: params: - preset: eebus render: | + type: eebus-evse {{ include "eebus" . }} diff --git a/templates/definition/charger/porsche-pmcc.yaml b/templates/definition/charger/porsche-pmcc.yaml index 898f359d9..ed431ecf0 100644 --- a/templates/definition/charger/porsche-pmcc.yaml +++ b/templates/definition/charger/porsche-pmcc.yaml @@ -7,5 +7,6 @@ capabilities: ["iso151182", "mA", "meter", "dim"] params: - preset: eebus render: | + type: eebus-evse {{ include "eebus" . }} meter: true diff --git a/templates/definition/charger/porsche-pmcp.yaml b/templates/definition/charger/porsche-pmcp.yaml index 0b3288ece..f9e9e29d4 100644 --- a/templates/definition/charger/porsche-pmcp.yaml +++ b/templates/definition/charger/porsche-pmcp.yaml @@ -7,5 +7,6 @@ capabilities: ["meter", "dim"] params: - preset: eebus render: | + type: eebus-evse {{ include "eebus" . }} meter: true diff --git a/templates/definition/meter/eebus-mgcp.yaml b/templates/definition/meter/eebus-mgcp.yaml index a38bece0e..f4c912e0c 100644 --- a/templates/definition/meter/eebus-mgcp.yaml +++ b/templates/definition/meter/eebus-mgcp.yaml @@ -14,5 +14,6 @@ params: choice: ["grid"] - preset: eebus render: | + type: eebus {{ include "eebus" . }} usage: {{ .usage }} diff --git a/templates/definition/meter/eebus-mpc.yaml b/templates/definition/meter/eebus-mpc.yaml index e6c660d69..10b1c324e 100644 --- a/templates/definition/meter/eebus-mpc.yaml +++ b/templates/definition/meter/eebus-mpc.yaml @@ -15,5 +15,6 @@ params: choice: ["charge"] - preset: eebus render: | + type: eebus {{ include "eebus" . }} usage: {{ .usage }} diff --git a/util/templates/includes/eebus.tpl b/util/templates/includes/eebus.tpl index db81f30cd..5e56538bd 100644 --- a/util/templates/includes/eebus.tpl +++ b/util/templates/includes/eebus.tpl @@ -1,5 +1,4 @@ {{ define "eebus" }} -type: eebus ski: {{ .ski }} {{ if .ip }}ip: {{ .ip }}{{ end }} {{- end}}