diff --git a/charger/em2go-duo.go b/charger/em2go-duo.go index bc0eb503f..f14df296d 100644 --- a/charger/em2go-duo.go +++ b/charger/em2go-duo.go @@ -101,9 +101,32 @@ func NewEm2GoDuo(ctx context.Context, uri string, slaveID uint8, connector int) connector: connector, } + b, err := wb.conn.ReadHoldingRegisters(em2GoDuoRegCommTimeout, 1) + if err != nil { + return nil, fmt.Errorf("failsafe timeout: %w", err) + } + if u := binary.BigEndian.Uint16(b); u > 0 { + go wb.heartbeat(ctx, time.Duration(u)*time.Second/2) + } + return wb, nil } +// heartbeat keeps the Modbus connection alive to prevent the charger from +// entering its failsafe state when the configured communication timeout expires. +func (wb *Em2GoDuo) heartbeat(ctx context.Context, interval time.Duration) { + for tick := time.Tick(interval); ; { + select { + case <-tick: + if _, err := wb.conn.ReadHoldingRegisters(em2GoDuoRegSafeCurrent, 1); err != nil { + wb.log.ERROR.Println("heartbeat:", err) + } + case <-ctx.Done(): + return + } + } +} + // Status implements the api.Charger interface func (wb *Em2GoDuo) Status() (api.ChargeStatus, error) { b, err := wb.conn.ReadHoldingRegisters(em2GoDuoRegConnectorState, 1) diff --git a/charger/em2go.go b/charger/em2go.go index d11598f8c..8100dff3c 100644 --- a/charger/em2go.go +++ b/charger/em2go.go @@ -23,6 +23,7 @@ import ( "fmt" "time" + "github.com/cenkalti/backoff/v4" "github.com/evcc-io/evcc/api" "github.com/evcc-io/evcc/util" "github.com/evcc-io/evcc/util/modbus" @@ -108,7 +109,42 @@ func NewEm2Go(ctx context.Context, uri string, slaveID uint8) (api.Charger, erro current: 60, } - return wb.initialize() + bo := backoff.WithContext( + backoff.NewExponentialBackOff( + backoff.WithInitialInterval(2*time.Second), + backoff.WithMaxInterval(10*time.Second), + backoff.WithMaxElapsedTime(30*time.Second), + ), ctx) + + res, err := backoff.RetryWithData(wb.initialize, bo) + if err != nil { + return nil, err + } + + b, err := wb.conn.ReadHoldingRegisters(em2GoRegCommTimeout, 1) + if err != nil { + return nil, fmt.Errorf("failsafe timeout: %w", err) + } + if u := binary.BigEndian.Uint16(b); u > 0 { + go wb.heartbeat(ctx, time.Duration(u)*time.Second/2) + } + + return res, nil +} + +// heartbeat keeps the Modbus connection alive to prevent the charger from +// entering its failsafe state when the configured communication timeout expires. +func (wb *Em2Go) heartbeat(ctx context.Context, interval time.Duration) { + for tick := time.Tick(interval); ; { + select { + case <-tick: + if _, err := wb.conn.ReadHoldingRegisters(em2GoRegSafeCurrent, 1); err != nil { + wb.log.ERROR.Println("heartbeat:", err) + } + case <-ctx.Done(): + return + } + } } // initialize performs common initialization for both Em2Go models