Vaillant: make hysteresis and reboost interval configurable (#31379)

This commit is contained in:
andig 2026-07-04 10:23:37 +02:00 • committed by GitHub
parent 2039cffb39
commit f11cf690dc
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 83 additions and 13 deletions

View file

@ -19,6 +19,7 @@ package charger
import (
"context"
"errors"
"fmt"
"reflect"
"strings"
@ -27,6 +28,7 @@ import (
"github.com/WulfgarW/sensonet"
"github.com/evcc-io/evcc/api"
"github.com/evcc-io/evcc/api/implement"
"github.com/evcc-io/evcc/core/loadpoint"
"github.com/evcc-io/evcc/util"
"github.com/evcc-io/evcc/util/request"
"github.com/samber/lo"
@ -41,6 +43,7 @@ type Vaillant struct {
*SgReady
log *util.Logger
conn *sensonet.Connection
lp loadpoint.API
systemId string
}
@ -53,14 +56,17 @@ func NewVaillantFromConfig(ctx context.Context, other map[string]any) (api.Charg
System string
HeatingZone int
HeatingSetpoint float32
Hysteresis float64
Reboost time.Duration
Cache time.Duration
}{
embed: embed{
Icon_: "heatpump",
Features_: []api.Feature{api.Continuous, api.Heating, api.IntegratedDevice, api.SwitchDevice},
},
Realm: sensonet.REALM_GERMANY,
Cache: time.Minute,
Realm: sensonet.REALM_GERMANY,
Reboost: 15 * time.Minute,
Cache: time.Minute,
}
if err := util.DecodeOther(other, &cc); err != nil {
@ -99,6 +105,45 @@ func NewVaillantFromConfig(ctx context.Context, other map[string]any) (api.Charg
wwCancel := func() {}
res := &Vaillant{
log: log,
conn: conn,
systemId: systemId,
}
// skipBoost reports whether the (re-)boost can be skipped because the measured
// temperature is already within the hysteresis band below the loadpoint limit
skipBoost := func() bool {
if res.lp == nil || cc.Hysteresis <= 0 {
return false
}
bat, ok := api.Cap[api.Battery](res)
if !ok {
return false
}
temp, err := bat.Soc()
if err != nil {
if !errors.Is(err, api.ErrNotAvailable) {
log.ERROR.Printf("temp: %v", err)
}
return false
}
limit := res.lp.GetLimitSoc()
if limit <= 0 {
return false
}
if hysteresis := float64(limit) - cc.Hysteresis; temp >= hysteresis {
log.DEBUG.Printf("temp: %.1f >= %.1f hysteresis", temp, hysteresis)
return true
}
return false
}
set := func(mode int64) error {
switch mode {
case Normal:
@ -114,8 +159,10 @@ func NewVaillantFromConfig(ctx context.Context, other map[string]any) (api.Charg
return conn.StartZoneQuickVeto(systemId, cc.HeatingZone, cc.HeatingSetpoint, 4) // hours
}
if err := conn.StartHotWaterBoost(systemId, sensonet.HOTWATERINDEX_DEFAULT); err != nil {
return err
if !skipBoost() {
if err := conn.StartHotWaterBoost(systemId, sensonet.HOTWATERINDEX_DEFAULT); err != nil {
return err
}
}
var wwCtx context.Context
@ -127,7 +174,11 @@ func NewVaillantFromConfig(ctx context.Context, other map[string]any) (api.Charg
select {
case <-wwCtx.Done():
return
case <-time.After(15 * time.Minute):
case <-time.After(cc.Reboost):
if skipBoost() {
continue
}
if err := conn.StartHotWaterBoost(systemId, sensonet.HOTWATERINDEX_DEFAULT); err != nil {
log.ERROR.Println("hot water boost:", err)
}
@ -142,18 +193,11 @@ func NewVaillantFromConfig(ctx context.Context, other map[string]any) (api.Charg
}
}
sgr, err := NewSgReady(ctx, &cc.embed, set, nil, nil)
res.SgReady, err = NewSgReady(ctx, &cc.embed, set, nil, nil)
if err != nil {
return nil, err
}
res := &Vaillant{
log: log,
conn: conn,
systemId: systemId,
SgReady: sgr,
}
if devices, _ := conn.GetMpcData(systemId); len(devices) > 0 {
implement.Has(res, implement.Meter(util.Cached(func() (float64, error) {
res, err := conn.GetMpcData(systemId)
@ -205,6 +249,13 @@ func NewVaillantFromConfig(ctx context.Context, other map[string]any) (api.Charg
return res, nil
}
var _ loadpoint.Controller = (*Vaillant)(nil)
// LoadpointControl implements loadpoint.Controller
func (wb *Vaillant) LoadpointControl(lp loadpoint.API) {
wb.lp = lp
}
func (v *Vaillant) print(chapter int, prefix string, zz ...any) {
var i int
for _, z := range zz {

View file

@ -78,6 +78,23 @@ params:
help:
de: Notwendig falls mehrere Anlagen im Account vorhanden sind
en: Required if multiple systems are present in the account
- name: reboost
type: duration
default: 15m
advanced: true
description:
de: Reboost-Intervall
en: Reboost interval
help:
de: Wartezeit, nach der Boost erneut gestartet wird.
en: Wait time after which Boost is started again.
- name: hysteresis
type: float
unit: °C
advanced: true
description:
de: Reboost Hysterese
en: Reboost Hysteresis
- name: phases
deprecated: true
render: |
@ -90,3 +107,5 @@ render: |
{{- end }}
heatingzone: {{ .zone }}
heatingsetpoint: {{ .setpoint }}
reboost: {{ .reboost }}
hysteresis: {{ .hysteresis }}