Skip to content

Commit 008fce4

Browse files
committed
Charger (FoxESS EVC, modbus-foxess): add heartbeat to maintain setpoint
The charger reverts to its fallback current if no Modbus command is received within the time validity window (60s). Add a goroutine that re-sends the last setpoint every 30s to keep the charger from going full power when evcc stops sending commands.
1 parent 4a45a81 commit 008fce4

1 file changed

Lines changed: 36 additions & 0 deletions

File tree

charger/modbus-foxess.go

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ import (
2323
"fmt"
2424
"math"
2525
"sync"
26+
"time"
2627

2728
"github.com/evcc-io/evcc/api"
2829
"github.com/evcc-io/evcc/api/implement"
@@ -122,6 +123,10 @@ func NewFoxESSEVC(ctx context.Context, uri string, slaveID uint8, pbox bool) (ap
122123
return nil, fmt.Errorf("time validity: %w", err)
123124
}
124125

126+
// the charger goes to fallback current if no setpoint is received within the
127+
// time validity window, so we must re-send the setpoint periodically
128+
go wb.heartbeat(ctx)
129+
125130
// phase switching is only available with an external phase-cutting box
126131
if pbox {
127132
implement.Has(wb, implement.PhaseSwitcher(wb.phases1p3p))
@@ -141,6 +146,37 @@ func (wb *FoxESSEVC) writeReg(reg, val uint16) error {
141146
return err
142147
}
143148

149+
// heartbeat re-sends the current setpoint to keep the charger from reverting
150+
// to its fallback current when the time validity window expires
151+
func (wb *FoxESSEVC) heartbeat(ctx context.Context) {
152+
for tick := time.Tick(foxTimeValidity / 2 * time.Second); ; {
153+
select {
154+
case <-tick:
155+
case <-ctx.Done():
156+
return
157+
}
158+
159+
wb.mu.Lock()
160+
cur := wb.current
161+
pbox := wb.pbox
162+
wb.mu.Unlock()
163+
164+
if cur == 0 {
165+
continue
166+
}
167+
168+
var err error
169+
if pbox {
170+
err = wb.writeReg(foxRegMaxCurrent, cur)
171+
} else {
172+
err = wb.writeReg(foxRegMaxPower, cur)
173+
}
174+
if err != nil {
175+
wb.log.ERROR.Println("heartbeat:", err)
176+
}
177+
}
178+
}
179+
144180
// readUint32 reads two consecutive registers as a big-endian uint32
145181
func (wb *FoxESSEVC) readUint32(reg uint16) (uint32, error) {
146182
b, err := wb.conn.ReadHoldingRegisters(reg, 2)

0 commit comments

Comments
 (0)