Heating: add generic heatpump charger (#19008)

This commit is contained in:
andig 2025-02-24 22:57:26 +01:00 • committed by GitHub
parent af8a57544a
commit 30ab9a45cc
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
14 changed files with 620 additions and 107 deletions

View file

@ -6,7 +6,8 @@ import (
"fmt"
"github.com/evcc-io/evcc/api"
"github.com/evcc-io/evcc/meter"
"github.com/evcc-io/evcc/charger/measurement"
meter "github.com/evcc-io/evcc/meter/measurement"
"github.com/evcc-io/evcc/plugin"
"github.com/evcc-io/evcc/util"
)
@ -36,12 +37,8 @@ func NewConfigurableFromConfig(ctx context.Context, other map[string]interface{}
Wakeup *plugin.Config
Soc *plugin.Config
Tos bool
// optional measurements
Power *plugin.Config
Energy *plugin.Config
Currents, Voltages []plugin.Config
measurement.Energy `mapstructure:",squash"` // optional
meter.Phases `mapstructure:",squash"` // optional
}
if err := util.DecodeOther(other, &cc); err != nil {
@ -123,12 +120,12 @@ func NewConfigurableFromConfig(ctx context.Context, other map[string]interface{}
}
// decorate measurements
powerG, energyG, err := meter.BuildMeasurements(ctx, cc.Power, cc.Energy)
powerG, energyG, err := cc.Energy.Configure(ctx)
if err != nil {
return nil, err
}
currentsG, voltagesG, _, err := meter.BuildPhaseMeasurements(ctx, cc.Currents, cc.Voltages, nil)
currentsG, voltagesG, _, err := cc.Phases.Configure(ctx)
if err != nil {
return nil, err
}

159
charger/heatpump.go Normal file
View file

@ -0,0 +1,159 @@
package charger
// LICENSE
// Copyright (c) 2024 andig
// This module is NOT covered by the MIT license. All rights reserved.
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
import (
"context"
"github.com/evcc-io/evcc/api"
"github.com/evcc-io/evcc/charger/measurement"
"github.com/evcc-io/evcc/plugin"
"github.com/evcc-io/evcc/util"
)
// Heatpump charger implementation
type Heatpump struct {
*embed
phases int
power int64
maxPowerG func() (int64, error)
maxPowerS func(int64) error
}
func init() {
registry.AddCtx("heatpump", NewHeatpumpFromConfig)
}
//go:generate go tool decorate -f decorateHeatpump -b *Heatpump -r api.Charger -t "api.Meter,CurrentPower,func() (float64, error)" -t "api.MeterEnergy,TotalEnergy,func() (float64, error)" -t "api.Battery,Soc,func() (float64, error)" -t "api.SocLimiter,GetLimitSoc,func() (int64, error)"
// NewHeatpumpFromConfig creates heatpump configurable charger from generic config
func NewHeatpumpFromConfig(ctx context.Context, other map[string]interface{}) (api.Charger, error) {
cc := struct {
embed `mapstructure:",squash"`
SetMaxPower plugin.Config
GetMaxPower *plugin.Config // optional
measurement.Temperature `mapstructure:",squash"`
measurement.Energy `mapstructure:",squash"`
Phases int
}{
embed: embed{
Icon_: "heatpump",
Features_: []api.Feature{api.Heating, api.IntegratedDevice},
},
Phases: 1,
}
if err := util.DecodeOther(other, &cc); err != nil {
return nil, err
}
maxPowerG, err := cc.GetMaxPower.IntGetter(ctx)
if err != nil {
return nil, err
}
maxPowerS, err := cc.SetMaxPower.IntSetter(ctx, "maxpower")
if err != nil {
return nil, err
}
// if !sponsor.IsAuthorized() {
// return nil, api.ErrSponsorRequired
// }
res, err := NewHeatpump(ctx, &cc.embed, maxPowerS, maxPowerG, cc.Phases)
if err != nil {
return nil, err
}
powerG, energyG, err := cc.Energy.Configure(ctx)
if err != nil {
return nil, err
}
tempG, limitTempG, err := cc.Temperature.Configure(ctx)
if err != nil {
return nil, err
}
return decorateHeatpump(res, powerG, energyG, tempG, limitTempG), nil
}
// NewHeatpump creates heatpump charger
func NewHeatpump(ctx context.Context, embed *embed, maxPowerS func(int64) error, maxPowerG func() (int64, error), phases int) (*Heatpump, error) {
res := &Heatpump{
embed: embed,
maxPowerG: maxPowerG,
maxPowerS: maxPowerS,
phases: phases,
}
return res, nil
}
func (wb *Heatpump) getMaxPower() (int64, error) {
if wb.maxPowerG == nil {
return wb.power, nil
}
return wb.maxPowerG()
}
func (wb *Heatpump) setMaxPower(power int64) error {
err := wb.maxPowerS(power)
if err == nil {
wb.power = power
}
return err
}
// Status implements the api.Charger interface
func (wb *Heatpump) Status() (api.ChargeStatus, error) {
power, err := wb.getMaxPower()
if err != nil {
return api.StatusNone, err
}
status := map[bool]api.ChargeStatus{false: api.StatusB, true: api.StatusC}
return status[power > 0], nil
}
// Enabled implements the api.Charger interface
func (wb *Heatpump) Enabled() (bool, error) {
power, err := wb.getMaxPower()
return power > 0, err
}
// Enable implements the api.Charger interface
func (wb *Heatpump) Enable(enable bool) error {
var power int64
if enable {
power = wb.power
}
return wb.setMaxPower(power)
}
// MaxCurrent implements the api.Charger interface
func (wb *Heatpump) MaxCurrent(current int64) error {
return wb.MaxCurrentEx(float64(current))
}
// MaxCurrent implements the api.Charger interface
func (wb *Heatpump) MaxCurrentEx(current float64) error {
return wb.setMaxPower(int64(230 * current * float64(wb.phases)))
}

View file

@ -0,0 +1,221 @@
package charger
// Code generated by github.com/evcc-io/evcc/cmd/tools/decorate.go. DO NOT EDIT.
import (
"github.com/evcc-io/evcc/api"
)
func decorateHeatpump(base *Heatpump, meter func() (float64, error), meterEnergy func() (float64, error), battery func() (float64, error), socLimiter func() (int64, error)) api.Charger {
switch {
case battery == nil && meter == nil && socLimiter == nil:
return base
case battery == nil && meter != nil && meterEnergy == nil && socLimiter == nil:
return &struct {
*Heatpump
api.Meter
}{
Heatpump: base,
Meter: &decorateHeatpumpMeterImpl{
meter: meter,
},
}
case battery == nil && meter != nil && meterEnergy != nil && socLimiter == nil:
return &struct {
*Heatpump
api.Meter
api.MeterEnergy
}{
Heatpump: base,
Meter: &decorateHeatpumpMeterImpl{
meter: meter,
},
MeterEnergy: &decorateHeatpumpMeterEnergyImpl{
meterEnergy: meterEnergy,
},
}
case battery != nil && meter == nil && socLimiter == nil:
return &struct {
*Heatpump
api.Battery
}{
Heatpump: base,
Battery: &decorateHeatpumpBatteryImpl{
battery: battery,
},
}
case battery != nil && meter != nil && meterEnergy == nil && socLimiter == nil:
return &struct {
*Heatpump
api.Battery
api.Meter
}{
Heatpump: base,
Battery: &decorateHeatpumpBatteryImpl{
battery: battery,
},
Meter: &decorateHeatpumpMeterImpl{
meter: meter,
},
}
case battery != nil && meter != nil && meterEnergy != nil && socLimiter == nil:
return &struct {
*Heatpump
api.Battery
api.Meter
api.MeterEnergy
}{
Heatpump: base,
Battery: &decorateHeatpumpBatteryImpl{
battery: battery,
},
Meter: &decorateHeatpumpMeterImpl{
meter: meter,
},
MeterEnergy: &decorateHeatpumpMeterEnergyImpl{
meterEnergy: meterEnergy,
},
}
case battery == nil && meter == nil && socLimiter != nil:
return &struct {
*Heatpump
api.SocLimiter
}{
Heatpump: base,
SocLimiter: &decorateHeatpumpSocLimiterImpl{
socLimiter: socLimiter,
},
}
case battery == nil && meter != nil && meterEnergy == nil && socLimiter != nil:
return &struct {
*Heatpump
api.Meter
api.SocLimiter
}{
Heatpump: base,
Meter: &decorateHeatpumpMeterImpl{
meter: meter,
},
SocLimiter: &decorateHeatpumpSocLimiterImpl{
socLimiter: socLimiter,
},
}
case battery == nil && meter != nil && meterEnergy != nil && socLimiter != nil:
return &struct {
*Heatpump
api.Meter
api.MeterEnergy
api.SocLimiter
}{
Heatpump: base,
Meter: &decorateHeatpumpMeterImpl{
meter: meter,
},
MeterEnergy: &decorateHeatpumpMeterEnergyImpl{
meterEnergy: meterEnergy,
},
SocLimiter: &decorateHeatpumpSocLimiterImpl{
socLimiter: socLimiter,
},
}
case battery != nil && meter == nil && socLimiter != nil:
return &struct {
*Heatpump
api.Battery
api.SocLimiter
}{
Heatpump: base,
Battery: &decorateHeatpumpBatteryImpl{
battery: battery,
},
SocLimiter: &decorateHeatpumpSocLimiterImpl{
socLimiter: socLimiter,
},
}
case battery != nil && meter != nil && meterEnergy == nil && socLimiter != nil:
return &struct {
*Heatpump
api.Battery
api.Meter
api.SocLimiter
}{
Heatpump: base,
Battery: &decorateHeatpumpBatteryImpl{
battery: battery,
},
Meter: &decorateHeatpumpMeterImpl{
meter: meter,
},
SocLimiter: &decorateHeatpumpSocLimiterImpl{
socLimiter: socLimiter,
},
}
case battery != nil && meter != nil && meterEnergy != nil && socLimiter != nil:
return &struct {
*Heatpump
api.Battery
api.Meter
api.MeterEnergy
api.SocLimiter
}{
Heatpump: base,
Battery: &decorateHeatpumpBatteryImpl{
battery: battery,
},
Meter: &decorateHeatpumpMeterImpl{
meter: meter,
},
MeterEnergy: &decorateHeatpumpMeterEnergyImpl{
meterEnergy: meterEnergy,
},
SocLimiter: &decorateHeatpumpSocLimiterImpl{
socLimiter: socLimiter,
},
}
}
return nil
}
type decorateHeatpumpBatteryImpl struct {
battery func() (float64, error)
}
func (impl *decorateHeatpumpBatteryImpl) Soc() (float64, error) {
return impl.battery()
}
type decorateHeatpumpMeterImpl struct {
meter func() (float64, error)
}
func (impl *decorateHeatpumpMeterImpl) CurrentPower() (float64, error) {
return impl.meter()
}
type decorateHeatpumpMeterEnergyImpl struct {
meterEnergy func() (float64, error)
}
func (impl *decorateHeatpumpMeterEnergyImpl) TotalEnergy() (float64, error) {
return impl.meterEnergy()
}
type decorateHeatpumpSocLimiterImpl struct {
socLimiter func() (int64, error)
}
func (impl *decorateHeatpumpSocLimiterImpl) GetLimitSoc() (int64, error) {
return impl.socLimiter()
}

View file

@ -0,0 +1,31 @@
package measurement
import (
"context"
"fmt"
"github.com/evcc-io/evcc/plugin"
)
type Energy struct {
Power *plugin.Config // optional
Energy *plugin.Config // optional
}
func (cc *Energy) Configure(ctx context.Context) (
func() (float64, error),
func() (float64, error),
error,
) {
powerG, err := cc.Power.FloatGetter(ctx)
if err != nil {
return nil, nil, fmt.Errorf("power: %w", err)
}
energyG, err := cc.Energy.FloatGetter(ctx)
if err != nil {
return nil, nil, fmt.Errorf("energy: %w", err)
}
return powerG, energyG, nil
}

View file

@ -0,0 +1,31 @@
package measurement
import (
"context"
"fmt"
"github.com/evcc-io/evcc/plugin"
)
type Temperature struct {
Temp *plugin.Config // optional
LimitTemp *plugin.Config // optional
}
func (cc *Temperature) Configure(ctx context.Context) (
func() (float64, error),
func() (int64, error),
error,
) {
tempG, err := cc.Temp.FloatGetter(ctx)
if err != nil {
return nil, nil, fmt.Errorf("temp: %w", err)
}
limitTempG, err := cc.LimitTemp.IntGetter(ctx)
if err != nil {
return nil, nil, fmt.Errorf("limit temp: %w", err)
}
return tempG, limitTempG, nil
}

View file

@ -20,9 +20,9 @@ package charger
import (
"context"
"errors"
"fmt"
"github.com/evcc-io/evcc/api"
"github.com/evcc-io/evcc/charger/measurement"
"github.com/evcc-io/evcc/plugin"
"github.com/evcc-io/evcc/util"
)
@ -30,11 +30,9 @@ import (
// SgReady charger implementation
type SgReady struct {
*embed
_mode int64
phases int
get func() (int64, error)
set func(int64) error
maxPower func(int64) error
mode int64
modeS func(int64) error
modeG func() (int64, error)
}
func init() {
@ -53,15 +51,12 @@ const (
// NewSgReadyFromConfig creates an SG Ready configurable charger from generic config
func NewSgReadyFromConfig(ctx context.Context, other map[string]interface{}) (api.Charger, error) {
cc := struct {
embed `mapstructure:",squash"`
SetMode plugin.Config
GetMode *plugin.Config // optional
MaxPower *plugin.Config // optional
Power *plugin.Config // optional
Energy *plugin.Config // optional
Temp *plugin.Config // optional
LimitTemp *plugin.Config // optional
Phases int
embed `mapstructure:",squash"`
SetMode plugin.Config
GetMode *plugin.Config // optional
measurement.Temperature `mapstructure:",squash"`
measurement.Energy `mapstructure:",squash"`
Phases int
}{
embed: embed{
Icon_: "heatpump",
@ -74,76 +69,56 @@ func NewSgReadyFromConfig(ctx context.Context, other map[string]interface{}) (ap
return nil, err
}
set, err := cc.SetMode.IntSetter(ctx, "mode")
modeS, err := cc.SetMode.IntSetter(ctx, "mode")
if err != nil {
return nil, err
}
get, err := cc.GetMode.IntGetter(ctx)
modeG, err := cc.GetMode.IntGetter(ctx)
if err != nil {
return nil, err
}
maxPower, err := cc.MaxPower.IntSetter(ctx, "maxpower")
res, err := NewSgReady(ctx, &cc.embed, modeS, modeG)
if err != nil {
return nil, err
}
res, err := NewSgReady(ctx, &cc.embed, set, get, maxPower, cc.Phases)
powerG, energyG, err := cc.Energy.Configure(ctx)
if err != nil {
return nil, err
}
// decorate power
powerG, err := cc.Power.FloatGetter(ctx)
tempG, limitTempG, err := cc.Temperature.Configure(ctx)
if err != nil {
return nil, fmt.Errorf("power: %w", err)
}
// decorate energy
energyG, err := cc.Energy.FloatGetter(ctx)
if err != nil {
return nil, fmt.Errorf("energy: %w", err)
}
// decorate temp
tempG, err := cc.Temp.FloatGetter(ctx)
if err != nil {
return nil, fmt.Errorf("temp: %w", err)
}
limitTempG, err := cc.LimitTemp.IntGetter(ctx)
if err != nil {
return nil, fmt.Errorf("limit temp: %w", err)
return nil, err
}
return decorateSgReady(res, powerG, energyG, tempG, limitTempG), nil
}
// NewSgReady creates SG Ready charger
func NewSgReady(ctx context.Context, embed *embed, set func(int64) error, get func() (int64, error), maxPower func(int64) error, phases int) (*SgReady, error) {
func NewSgReady(ctx context.Context, embed *embed, modeS func(int64) error, modeG func() (int64, error)) (*SgReady, error) {
res := &SgReady{
embed: embed,
_mode: Normal,
set: set,
get: get,
maxPower: maxPower,
phases: phases,
embed: embed,
mode: Normal,
modeS: modeS,
modeG: modeG,
}
return res, nil
}
func (wb *SgReady) mode() (int64, error) {
if wb.get == nil {
return wb._mode, nil
func (wb *SgReady) getMode() (int64, error) {
if wb.modeG == nil {
return wb.mode, nil
}
return wb.get()
return wb.modeG()
}
// Status implements the api.Charger interface
func (wb *SgReady) Status() (api.ChargeStatus, error) {
mode, err := wb.mode()
mode, err := wb.getMode()
if err != nil {
return api.StatusNone, err
}
@ -152,36 +127,27 @@ func (wb *SgReady) Status() (api.ChargeStatus, error) {
return api.StatusNone, errors.New("stop mode")
}
status := map[int64]api.ChargeStatus{Boost: api.StatusC, Normal: api.StatusB}[mode]
return status, nil
status := map[int64]api.ChargeStatus{Boost: api.StatusC, Normal: api.StatusB}
return status[mode], nil
}
// Enabled implements the api.Charger interface
func (wb *SgReady) Enabled() (bool, error) {
mode, err := wb.mode()
mode, err := wb.getMode()
return mode == Boost, err
}
// Enable implements the api.Charger interface
func (wb *SgReady) Enable(enable bool) error {
mode := map[bool]int64{false: Normal, true: Boost}[enable]
err := wb.set(mode)
err := wb.modeS(mode)
if err == nil {
wb._mode = mode
wb.mode = mode
}
return err
}
// MaxCurrent implements the api.Charger interface
func (wb *SgReady) MaxCurrent(current int64) error {
return wb.MaxCurrentEx(float64(current))
}
// MaxCurrent implements the api.Charger interface
func (wb *SgReady) MaxCurrentEx(current float64) error {
if wb.maxPower == nil {
return nil
}
return wb.maxPower(int64(230 * current * float64(wb.phases)))
return nil
}

View file

@ -112,7 +112,7 @@ func NewVaillantFromConfig(ctx context.Context, other map[string]interface{}) (a
}
}
sgr, err := NewSgReady(ctx, &cc.embed, set, nil, nil, cc.Phases)
sgr, err := NewSgReady(ctx, &cc.embed, set, nil)
if err != nil {
return nil, err
}

View file

@ -7,6 +7,7 @@ import (
"time"
"github.com/evcc-io/evcc/api"
"github.com/evcc-io/evcc/meter/measurement"
"github.com/evcc-io/evcc/util"
"github.com/evcc-io/evcc/util/modbus"
"github.com/volkszaehler/mbmd/meters"
@ -155,7 +156,7 @@ func (m *ModbusMbmd) buildPhaseProviders(readings []string) (func() (float64, fl
}
}
return collectPhaseProviders(phases), nil
return measurement.CombinePhases(phases), nil
}
// floatGetter executes configured modbus read operation and implements func() (float64, error)

View file

@ -0,0 +1,31 @@
package measurement
import (
"context"
"fmt"
"github.com/evcc-io/evcc/plugin"
)
type Energy struct {
Power plugin.Config
Energy *plugin.Config // optional
}
func (cc *Energy) Configure(ctx context.Context) (
func() (float64, error),
func() (float64, error),
error,
) {
powerG, err := cc.Power.FloatGetter(ctx)
if err != nil {
return nil, nil, fmt.Errorf("power: %w", err)
}
energyG, err := cc.Energy.FloatGetter(ctx)
if err != nil {
return nil, nil, fmt.Errorf("energy: %w", err)
}
return powerG, energyG, nil
}

View file

@ -1,4 +1,4 @@
package meter
package measurement
import (
"context"
@ -8,39 +8,27 @@ import (
"github.com/evcc-io/evcc/plugin"
)
// BuildMeasurements returns typical meter measurement getters from config
func BuildMeasurements(ctx context.Context, power, energy *plugin.Config) (func() (float64, error), func() (float64, error), error) {
powerG, err := power.FloatGetter(ctx)
if err != nil {
return nil, nil, fmt.Errorf("power: %w", err)
}
energyG, err := energy.FloatGetter(ctx)
if err != nil {
return nil, nil, fmt.Errorf("energy: %w", err)
}
return powerG, energyG, nil
type Phases struct {
Currents, Voltages, Powers []plugin.Config // optional
}
// BuildPhaseMeasurements returns typical meter measurement getters from config
func BuildPhaseMeasurements(ctx context.Context, currents, voltages, powers []plugin.Config) (
func (cc *Phases) Configure(ctx context.Context) (
func() (float64, float64, float64, error),
func() (float64, float64, float64, error),
func() (float64, float64, float64, error),
error,
) {
currentsG, err := buildPhaseProviders(ctx, currents)
currentsG, err := buildPhaseProviders(ctx, cc.Currents)
if err != nil {
return nil, nil, nil, fmt.Errorf("currents: %w", err)
}
voltagesG, err := buildPhaseProviders(ctx, voltages)
voltagesG, err := buildPhaseProviders(ctx, cc.Voltages)
if err != nil {
return nil, nil, nil, fmt.Errorf("voltages: %w", err)
}
powersG, err := buildPhaseProviders(ctx, powers)
powersG, err := buildPhaseProviders(ctx, cc.Powers)
if err != nil {
return nil, nil, nil, fmt.Errorf("powers: %w", err)
}
@ -68,11 +56,11 @@ func buildPhaseProviders(ctx context.Context, providers []plugin.Config) (func()
phases[idx] = c
}
return collectPhaseProviders(phases), nil
return CombinePhases(phases), nil
}
// collectPhaseProviders combines phase getters into combined api function
func collectPhaseProviders(g [3]func() (float64, error)) func() (float64, float64, float64, error) {
// CombinePhases combines phase getters into combined api function
func CombinePhases(g [3]func() (float64, error)) func() (float64, float64, float64, error) {
return func() (float64, float64, float64, error) {
var res [3]float64
for idx, currentG := range g {

View file

@ -5,6 +5,7 @@ import (
"fmt"
"github.com/evcc-io/evcc/api"
"github.com/evcc-io/evcc/meter/measurement"
"github.com/evcc-io/evcc/plugin"
"github.com/evcc-io/evcc/util"
)
@ -18,11 +19,8 @@ func init() {
// NewConfigurableFromConfig creates api.Meter from config
func NewConfigurableFromConfig(ctx context.Context, other map[string]interface{}) (api.Meter, error) {
cc := struct {
Power plugin.Config
Energy *plugin.Config // optional
Currents []plugin.Config // optional
Voltages []plugin.Config // optional
Powers []plugin.Config // optional
measurement.Energy `mapstructure:",squash"` // energy optional
measurement.Phases `mapstructure:",squash"` // optional
// battery
capacity `mapstructure:",squash"`
@ -42,12 +40,12 @@ func NewConfigurableFromConfig(ctx context.Context, other map[string]interface{}
return nil, err
}
powerG, energyG, err := BuildMeasurements(ctx, &cc.Power, cc.Energy)
powerG, energyG, err := cc.Energy.Configure(ctx)
if err != nil {
return nil, err
}
currentsG, voltagesG, powersG, err := BuildPhaseMeasurements(ctx, cc.Currents, cc.Voltages, cc.Powers)
currentsG, voltagesG, powersG, err := cc.Phases.Configure(ctx)
if err != nil {
return nil, err
}

View file

@ -8,6 +8,7 @@ import (
"github.com/evcc-io/evcc/api"
"github.com/evcc-io/evcc/charger/openwb"
"github.com/evcc-io/evcc/meter/measurement"
"github.com/evcc-io/evcc/plugin"
"github.com/evcc-io/evcc/plugin/mqtt"
"github.com/evcc-io/evcc/util"
@ -73,7 +74,7 @@ func NewOpenWBFromConfig(other map[string]interface{}) (api.Meter, error) {
curr[i] = current
}
currents = collectPhaseProviders(curr)
currents = measurement.CombinePhases(curr)
case "pv":
// first pv

View file

@ -0,0 +1,53 @@
template: idm
products:
- brand: IDM
group: heating
requirements:
# evcc: ["sponsorship"]
params:
- name: modbus
choice: ["tcpip"]
- name: tempsource
type: choice
choice: ["", "warmwater", "buffer"]
description:
de: "Temperaturquelle"
en: "Temperature source"
- name: phases
type: int
advanced: true
render: |
type: heatpump
phases: {{ .phases }}
setmaxpower:
source: modbus
{{- include "modbus" . | indent 2 }}
register:
address: 74 # PV Überschussleistung
type: holding
decode: float32s
scale: 0.001
power:
source: modbus
{{- include "modbus" . | indent 2 }}
register:
address: 4122 # aktuelle Aufnahmeleistung der WP
type: holding
decode: float32s
scale: 1000
energy:
source: modbus
{{- include "modbus" . | indent 2 }}
register:
address: 4128
type: holding
decode: float32s
{{- if .tempsource }}
temp:
source: modbus
{{- include "modbus" . | indent 2 }}
register:
address: {{ if eq .tempsource "warmwater" -}} 1014 {{ else }} 1008 {{- end }} # 1014 Trinkwasser, 1008 Wärmespeicher
type: holding
decode: float32s
{{- end }}

View file

@ -0,0 +1,36 @@
template: lambda-zewotherm
products:
- brand: Lambda
description:
de: EU-L Serie
en: EU-L Series
- brand: Zewotherm
description:
de: EU-L Serie
en: EU-L Series
group: heating
requirements:
# evcc: ["sponsorship"]
params:
- name: modbus
choice: ["tcpip"]
- name: phases
type: int
advanced: true
render: |
type: heatpump
phases: {{ .phases }}
setmaxpower:
source: modbus
{{- include "modbus" . | indent 2 }}
register:
address: 103 # PV Überschussleistung
type: holding
decode: int16
power:
source: modbus
{{- include "modbus" . | indent 2 }}
register:
address: 103 # aktuelle Aufnahmeleistung der WP
type: holding
decode: int16