Skip to content

Commit ebd435f

Browse files
committed
vaillant-ebus and sensonet support a charger idle mode now.
1 parent 7a6ff05 commit ebd435f

File tree

10 files changed

+177
-37
lines changed

10 files changed

+177
-37
lines changed

charger/sensonet.go

+3
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import (
55

66
"github.com/evcc-io/evcc/api"
77
"github.com/evcc-io/evcc/charger/sensonet"
8+
"github.com/evcc-io/evcc/charger/vaillantEbus"
89
"github.com/evcc-io/evcc/util"
910
)
1011

@@ -105,6 +106,8 @@ func (c *Sensonet) ModeText() string {
105106
return " (Heating Quick Veto active. Ends " + c.conn.QuickVetoExpiresAt() + ")"
106107
}
107108
return " (Heating Quick Veto active)"
109+
case vaillantEbus.QUICKMODE_NOTHING:
110+
return " (charger running idle)"
108111
}
109112
return " (regular mode; hotwater temp. shown)"
110113
}

charger/sensonet/connection.go

+17-2
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ type Connection struct {
4242
cache time.Duration
4343
currentQuickmode string
4444
quickmodeStarted time.Time
45+
quickmodeStopped time.Time
4546
onoff bool
4647
quickVetoSetPoint float32
4748
quickVetoExpiresAt string
@@ -508,6 +509,15 @@ func (d *Connection) TargetTemp() (float64, error) {
508509
return float64(res.Hotwater.HotwaterTemperatureSetpoint), nil
509510
}
510511

512+
// CheckPVUseStrategy is called bei vaillant-ebus_vehicle.Soc()
513+
func (d *Connection) CheckPVUseStrategy(vehicleStrategy string) error {
514+
if d.pvUseStrategy != vehicleStrategy {
515+
d.log.INFO.Printf("Changing PVUseStrategy of charger from '%s' to '%s'", d.pvUseStrategy, vehicleStrategy)
516+
d.pvUseStrategy = vehicleStrategy
517+
}
518+
return nil
519+
}
520+
511521
func (d *Connection) Status() (api.ChargeStatus, error) {
512522
status := api.StatusB
513523
if time.Now().After(d.tokenExpiresAt) {
@@ -530,11 +540,16 @@ func (c *Connection) WhichQuickMode() (int, error) {
530540
//c.log.DEBUG.Println("PV Use Strategy = ", c.pvUseStrategy)
531541
c.log.DEBUG.Printf("Checking if hot water boost possible. Operation Mode = %s, temperature setpoint= %02.2f, live temperature= %02.2f", res.Hotwater.OperationMode, res.Hotwater.HotwaterTemperatureSetpoint, res.Hotwater.HotwaterLiveTemperature)
532542
hotWaterBoostPossible := false
533-
if (res.Hotwater.HotwaterLiveTemperature <= res.Hotwater.HotwaterTemperatureSetpoint-5 || c.pvUseStrategy == PVUSESTRATEGY_HOTWATER) &&
543+
// For pvUseStrategy='hotwater', a hotwater boost is possible when hotwater storage temperature is less than the temperature setpoint.
544+
// For other pvUseStrategies, a hotwater boost is possible when hotwater storage temperature is less than the temperature setpoint minus 5°C
545+
addOn := -5.0
546+
if c.pvUseStrategy == PVUSESTRATEGY_HOTWATER {
547+
addOn = 0.0
548+
}
549+
if res.Hotwater.HotwaterLiveTemperature < res.Hotwater.HotwaterTemperatureSetpoint+addOn &&
534550
res.Hotwater.OperationMode == OPERATIONMODE_TIME_CONTROLLED {
535551
hotWaterBoostPossible = true
536552
}
537-
538553
heatingQuickVetoPossible := false
539554
for _, z := range res.Zones {
540555
if z.Index == c.heatingZone {

charger/sensonet/switch.go

+39-8
Original file line numberDiff line numberDiff line change
@@ -74,20 +74,20 @@ func (sh *Switch) Enabled() (bool, error) {
7474
}
7575
}
7676
}
77-
if d.currentQuickmode == QUICKMODE_HOTWATER {
77+
switch d.currentQuickmode {
78+
case QUICKMODE_HOTWATER:
7879
d.log.DEBUG.Println("In Switch.Enabled: Hotwater quick mode:", res.Hotwater.CurrentQuickmode)
7980
if (res.Hotwater.CurrentQuickmode == "") || (res.Hotwater.CurrentQuickmode == "REGULAR") {
8081
d.log.DEBUG.Println("In Switch.Enabled: res.Hotwater.CurrentQuickmode should be active but is off")
8182
if d.quickmodeStarted.Add(5 * time.Minute).Before(time.Now()) {
8283
// When the reported hotwater.CurrentQuickmode has changed to "Regular" more then 5 minutes after the beginning of the charge session,
8384
// this means that the heat pump has stopped the hotwater boost itself
8485
d.currentQuickmode = ""
85-
d.quickmodeStarted = time.Now()
86+
d.quickmodeStopped = time.Now()
8687
d.onoff = false
8788
}
8889
}
89-
}
90-
if d.currentQuickmode == QUICKMODE_HEATING {
90+
case QUICKMODE_HEATING:
9191
for _, z := range res.Zones {
9292
if z.Index == d.heatingZone {
9393
d.log.DEBUG.Println("In Switch.Enabled: Zone quick mode:", z.CurrentQuickmode, ", Temperature Setpoint:", z.QuickVeto.TemperatureSetpoint, "(", d.quickVetoSetPoint, "), Expires at:", d.quickVetoExpiresAt)
@@ -97,12 +97,23 @@ func (sh *Switch) Enabled() (bool, error) {
9797
// When the reported z.CurrentQuickmode has changed to "NONE" more then 5 minutes after the beginning of the charge session,
9898
// this means that the zone quick veto ended or was stopped by other means as evcc
9999
d.currentQuickmode = ""
100-
d.quickmodeStarted = time.Now()
100+
d.quickmodeStopped = time.Now()
101101
d.onoff = false
102102
}
103103
}
104104
}
105105
}
106+
case QUICKMODE_NOTHING:
107+
if d.quickmodeStarted.Add(10 * time.Minute).Before(time.Now()) {
108+
d.log.DEBUG.Println("Idle charge mode for more than 10 minutes. Turning it off")
109+
d.currentQuickmode = ""
110+
d.quickmodeStopped = time.Now()
111+
d.onoff = false
112+
}
113+
case "":
114+
//Nothing to do
115+
default:
116+
d.log.ERROR.Println("Unknown quick mode in case statement:", d.currentQuickmode)
106117
}
107118
return d.onoff, nil
108119
}
@@ -117,6 +128,9 @@ func (sh *Switch) Enable(enable bool) error {
117128
err = fmt.Errorf("could not read status cache before hotwater boost: %s", err)
118129
return err
119130
}
131+
if d.currentQuickmode == "" && d.quickmodeStopped.After(d.quickmodeStarted) && d.quickmodeStopped.Add(2*time.Minute).After(time.Now()) {
132+
enable = false
133+
}
120134
if enable {
121135
whichQuickMode, err := d.WhichQuickMode()
122136
if err != nil {
@@ -140,9 +154,24 @@ func (sh *Switch) Enable(enable bool) error {
140154
d.log.DEBUG.Println("Starting zone quick veto")
141155
}
142156
default:
143-
d.log.INFO.Println("Enable called but no quick mode possible")
144-
//d.onoff = false
145-
//return err
157+
if d.currentQuickmode == QUICKMODE_HOTWATER {
158+
//if hotwater boost active, then stop it
159+
err = sh.stopHotWaterBoost(&res)
160+
if err == nil {
161+
d.log.DEBUG.Println("Stopping Hotwater Boost")
162+
}
163+
}
164+
if d.currentQuickmode == QUICKMODE_HEATING {
165+
//if zone quick veto active, then stop it
166+
err = sh.stopZoneQuickVeto()
167+
if err == nil {
168+
d.log.DEBUG.Println("Stopping Zone Quick Veto")
169+
}
170+
}
171+
d.currentQuickmode = QUICKMODE_NOTHING
172+
d.quickmodeStarted = time.Now()
173+
d.log.DEBUG.Println("Enable called but no quick mode possible. Starting idle mode")
174+
//d.log.INFO.Println("Enable called but no quick mode possible")
146175
}
147176
} else {
148177
switch d.currentQuickmode {
@@ -156,6 +185,8 @@ func (sh *Switch) Enable(enable bool) error {
156185
if err == nil {
157186
d.log.DEBUG.Println("Stopping Zone Quick Veto")
158187
}
188+
case QUICKMODE_NOTHING:
189+
d.log.DEBUG.Println("Stopping idle quick mode")
159190
default:
160191
d.log.DEBUG.Println("Nothing to do, no quick mode active")
161192
}

charger/sensonet/types.go

+1
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ const PVUSESTRATEGY_HEATING string = "heating"
88
const OPERATIONMODE_TIME_CONTROLLED string = "TIME_CONTROLLED"
99
const QUICKMODE_HOTWATER string = "Hotwater Boost"
1010
const QUICKMODE_HEATING string = "Heating Quick Veto"
11+
const QUICKMODE_NOTHING string = "Charger running idle"
1112

1213
const AUTH_BASE_URL string = "https://identity.vaillant-group.com/auth/realms"
1314
const LOGIN_URL string = AUTH_BASE_URL + "/%s/login-actions/authenticate"

charger/vaillantEbus/connection.go

+75-11
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ type Connection struct {
4040
quickVetoSetPoint float32
4141
quickVetoExpiresAt string
4242
relData VaillantRelDataStruct
43+
getSystemUpdateInterval time.Duration
4344
}
4445

4546
// Global variable SensoNetConn is used to make data available in vehicle vks (not needed without vehicle vks)
@@ -61,6 +62,7 @@ func NewConnection(ebusdAddress, pvUseStrategy string, heatingZone, phases int,
6162
conn.log = log
6263
conn.currentQuickmode = ""
6364
conn.quickmodeStarted = time.Now()
65+
conn.getSystemUpdateInterval = 2 * time.Minute
6466
VaillantEbusConn = conn //this is not needed without vehicle vaillant-ebus_vehicle
6567

6668
var err error
@@ -282,7 +284,7 @@ func (c *Connection) getSystem(relData *VaillantRelDataStruct, reset bool) error
282284
var findResult string
283285
var convertedValue float64
284286

285-
if !reset && time.Now().Add(time.Duration(-2*int64(time.Minute))).Before(c.lastGetSystemAt) {
287+
if !reset && time.Now().Add(c.getSystemUpdateInterval).Before(c.lastGetSystemAt) {
286288
// Use relData that are already present instead of reading current data from ebusd
287289
return nil
288290
}
@@ -300,13 +302,13 @@ func (c *Connection) getSystem(relData *VaillantRelDataStruct, reset bool) error
300302
c.ebusdReadBuffer = *bufio.NewReader(c.ebusdConn)
301303
//Getting Data for Hotwater
302304
findResult = ""
303-
for !slices.Contains([]string{"off", "auto", "manual", "ERR:"}, findResult) && err == nil {
305+
for !slices.Contains([]string{"off", "auto", "day", "ERR:"}, findResult) && err == nil {
304306
findResult, err = c.ebusdRead(EBUSDREAD_HOTWATER_OPMODE, -1)
305307
}
306308
if err != nil {
307309
return err
308310
} else {
309-
if slices.Contains([]string{"off", "auto", "manual"}, findResult) {
311+
if slices.Contains([]string{"off", "auto", "day"}, findResult) {
310312
relData.Hotwater.HwcOpMode = findResult
311313
} else {
312314
c.log.DEBUG.Printf("Value '%s' returnd from ebusd for %s invalid and therefore ignored", findResult, EBUSDREAD_HOTWATER_OPMODE)
@@ -374,7 +376,26 @@ func (c *Connection) getSystem(relData *VaillantRelDataStruct, reset bool) error
374376
if err != nil {
375377
return err
376378
} else {
377-
relData.Status.CurrentConsumedPower, _ = strconv.ParseFloat(findResult, 64)
379+
convertedValue, err = strconv.ParseFloat(findResult, 64)
380+
if err != nil || convertedValue <= 0 {
381+
c.log.DEBUG.Printf("Value '%s' returnd from ebusd for %s invalid and therefore ignored", findResult, EBUSDREAD_STATUS_CURRENTCONSUMEDPOWER)
382+
} else {
383+
relData.Status.CurrentConsumedPower = convertedValue
384+
}
385+
}
386+
findResult, err = c.ebusdRead(EBUSDREAD_STATUS_STATUS01, -1)
387+
if err != nil {
388+
return err
389+
} else {
390+
relData.Status.Status01 = findResult
391+
c.log.DEBUG.Printf("Info: Value '%s' returnd from ebusd for %s", findResult, EBUSDREAD_STATUS_STATUS01)
392+
}
393+
findResult, err = c.ebusdRead(EBUSDREAD_STATUS_STATE, -1)
394+
if err != nil {
395+
return err
396+
} else {
397+
relData.Status.State = findResult
398+
c.log.DEBUG.Printf("Info: Value '%s' returnd from ebusd for %s", findResult, EBUSDREAD_STATUS_STATE)
378399
}
379400

380401
//Getting Zone Data
@@ -394,7 +415,7 @@ func (c *Connection) getSystem(relData *VaillantRelDataStruct, reset bool) error
394415
if err != nil {
395416
return err
396417
} else {
397-
if slices.Contains([]string{"off", "auto", "manual"}, findResult) {
418+
if slices.Contains([]string{"off", "auto", "day"}, findResult) {
398419
relData.Zones[i].OpMode = findResult
399420
} else {
400421
c.log.DEBUG.Printf("Value '%s' returnd from ebusd for %s invalid and therefore ignored", findResult, EBUSDREAD_ZONE_OPMODE)
@@ -404,7 +425,7 @@ func (c *Connection) getSystem(relData *VaillantRelDataStruct, reset bool) error
404425
if err != nil {
405426
return err
406427
} else {
407-
if slices.Contains([]string{"off", "auto", "manual"}, findResult) {
428+
if slices.Contains([]string{"auto", "veto"}, findResult) {
408429
relData.Zones[i].SFMode = findResult
409430
} else {
410431
c.log.DEBUG.Printf("Value '%s' returnd from ebusd for %s invalid and therefore ignored", findResult, EBUSDREAD_ZONE_SFMODE)
@@ -414,20 +435,45 @@ func (c *Connection) getSystem(relData *VaillantRelDataStruct, reset bool) error
414435
if err != nil {
415436
return err
416437
} else {
417-
relData.Zones[i].ActualRoomTempDesired, _ = strconv.ParseFloat(findResult, 64)
438+
convertedValue, err = strconv.ParseFloat(findResult, 64)
439+
if err != nil || convertedValue <= 0 {
440+
c.log.DEBUG.Printf("Value '%s' returnd from ebusd for %s invalid and therefore ignored", findResult, EBUSDREAD_ZONE_ACTUALROOMTEMPDESIRED)
441+
} else {
442+
relData.Zones[i].ActualRoomTempDesired = convertedValue
443+
}
418444
}
419445
findResult, err = c.ebusdRead(zonePrefix+EBUSDREAD_ZONE_ROOMTEMP, 180)
420446
if err != nil {
421447
return err
422448
} else {
423-
relData.Zones[i].RoomTemp, _ = strconv.ParseFloat(findResult, 64)
449+
convertedValue, err = strconv.ParseFloat(findResult, 64)
450+
if err != nil || convertedValue <= 0 {
451+
c.log.DEBUG.Printf("Value '%s' returnd from ebusd for %s invalid and therefore ignored", findResult, EBUSDREAD_ZONE_ROOMTEMP)
452+
} else {
453+
relData.Zones[i].RoomTemp = convertedValue
454+
}
424455
}
425456
findResult, err = c.ebusdRead(zonePrefix+EBUSDREAD_ZONE_QUICKVETOTEMP, 0)
426457
if err != nil {
427458
return err
428459
} else {
429460
relData.Zones[i].QuickVetoTemp, _ = strconv.ParseFloat(findResult, 64)
430461
}
462+
findResult, err = c.ebusdRead(zonePrefix+EBUSDREAD_ZONE_QUICKVETOENDDATE, -1)
463+
if err != nil {
464+
return err
465+
} else {
466+
relData.Zones[i].QuickVetoEndDate = findResult
467+
}
468+
findResult, err = c.ebusdRead(zonePrefix+EBUSDREAD_ZONE_QUICKVETOENDTIME, -1)
469+
if err != nil {
470+
return err
471+
} else {
472+
relData.Zones[i].QuickVetoEndTime = findResult
473+
if relData.Zones[i].SFMode == "veto" {
474+
c.quickVetoExpiresAt = relData.Zones[i].QuickVetoEndTime
475+
}
476+
}
431477

432478
//Set timestamp lastGetSystemAt and return nil error
433479
c.lastGetSystemAt = time.Now()
@@ -464,6 +510,19 @@ func (c *Connection) getSFMode(relData *VaillantRelDataStruct) error {
464510
} else {
465511
relData.Zones[i].SFMode = findResult
466512
}
513+
findResult, err = c.ebusdRead(zonePrefix+EBUSDREAD_ZONE_QUICKVETOENDDATE, 0)
514+
if err != nil {
515+
return err
516+
} else {
517+
relData.Zones[i].QuickVetoEndDate = findResult
518+
}
519+
findResult, err = c.ebusdRead(zonePrefix+EBUSDREAD_ZONE_QUICKVETOENDTIME, 0)
520+
if err != nil {
521+
return err
522+
} else {
523+
relData.Zones[i].QuickVetoEndTime = findResult
524+
}
525+
c.log.DEBUG.Println("Timestamp for end of zone quick veto: ", relData.Zones[i].QuickVetoEndDate+" "+relData.Zones[i].QuickVetoEndTime)
467526
return nil
468527
}
469528
func (d *Connection) Phases() int {
@@ -476,6 +535,7 @@ func (d *Connection) CurrentQuickmode() string {
476535

477536
func (d *Connection) QuickVetoExpiresAt() string {
478537
return d.quickVetoExpiresAt
538+
479539
}
480540

481541
// CurrentTemp is called bei Soc
@@ -551,9 +611,13 @@ func (c *Connection) WhichQuickMode() (int, error) {
551611
c.log.DEBUG.Printf("Checking if hot water boost possible. Operation Mode = %s, temperature setpoint= %02.2f, live temperature= %02.2f",
552612
c.relData.Hotwater.HwcOpMode, c.relData.Hotwater.HwcTempDesired, c.relData.Hotwater.HwcStorageTemp)
553613
hotWaterBoostPossible := false
554-
//if (c.relData.Hotwater.HwcStorageTemp <= c.relData.Hotwater.HwcTempDesired-5 || c.pvUseStrategy == PVUSESTRATEGY_HOTWATER) &&
555-
// c.relData.Hotwater.HwcOpMode == OPERATIONMODE_AUTO {
556-
if c.relData.Hotwater.HwcStorageTemp <= c.relData.Hotwater.HwcTempDesired-5 &&
614+
// For pvUseStrategy='hotwater', a hotwater boost is possible when hotwater storage temperature is less than the temperature setpoint.
615+
// For other pvUseStrategies, a hotwater boost is possible when hotwater storage temperature is less than the temperature setpoint minus 5°C
616+
addOn := -5.0
617+
if c.pvUseStrategy == PVUSESTRATEGY_HOTWATER {
618+
addOn = 0.0
619+
}
620+
if c.relData.Hotwater.HwcStorageTemp < c.relData.Hotwater.HwcTempDesired+addOn &&
557621
c.relData.Hotwater.HwcOpMode == OPERATIONMODE_AUTO {
558622
hotWaterBoostPossible = true
559623
}

0 commit comments

Comments
 (0)