Loadpoint: ignore charge interruption during phase switch (#33109)

This commit is contained in:
andig 2026-08-23 12:41:42 +02:00 • committed by GitHub
parent c1960b378a
commit 3159126248
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 58 additions and 2 deletions

View file

@ -1236,7 +1236,15 @@ func (lp *Loadpoint) getStatusChanges() ([]api.ChargeStatus, error) {
// detect if charger status changed
prevStatus := lp.GetStatus()
if status != prevStatus {
// ignore charge interruption while switching phases. Status is left unchanged,
// hence a real interruption is detected once the timespan has elapsed.
ignore := status == api.StatusB && prevStatus == api.StatusC && !lp.phaseSwitchCompleted()
switch {
case ignore:
lp.log.DEBUG.Println("ignoring charge interruption during phase switch")
case status != prevStatus:
res = []api.ChargeStatus{status}
}
@ -1250,7 +1258,7 @@ func (lp *Loadpoint) getStatusChanges() ([]api.ChargeStatus, error) {
defer func() { lp.connectedDuration = d }()
// connection duration dropped without disconnect status, indicates intermediate disconnect
if status != api.StatusA && prevStatus != api.StatusA && d < lp.connectedDuration {
if !ignore && status != api.StatusA && prevStatus != api.StatusA && d < lp.connectedDuration {
lp.log.DEBUG.Printf("connection duration drop detected (%s -> %v)", lp.connectedDuration.Round(time.Second), d.Round(time.Second))
res = []api.ChargeStatus{api.StatusA, status}
}

View file

@ -2,9 +2,13 @@ package core
import (
"testing"
"time"
"github.com/evcc-io/evcc/api"
"github.com/evcc-io/evcc/util"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"go.uber.org/mock/gomock"
)
func TestStatusEvents(t *testing.T) {
@ -31,3 +35,47 @@ func TestStatusEvents(t *testing.T) {
assert.Equalf(t, tc.events, ev, "from %s to %s got: %v", tc.from, tc.to, ev)
}
}
// TestPhaseSwitchInterruption verifies that a charge interruption right after a phase
// switch is ignored while a real interruption is still detected
func TestPhaseSwitchInterruption(t *testing.T) {
tc := []struct {
desc string
since time.Duration
connection time.Duration
expected []api.ChargeStatus
}{
// the connection duration drops on interruption, too, and must not surface as intermediate disconnect
{"during phase switch", phaseSwitchDuration - time.Second, time.Minute, nil},
{"after phase switch", phaseSwitchDuration + time.Second, 2 * time.Hour, []api.ChargeStatus{api.StatusB}},
}
for _, tc := range tc {
ctrl := gomock.NewController(t)
charger := api.NewMockCharger(ctrl)
charger.EXPECT().Status().Return(api.StatusB, nil)
timer := api.NewMockConnectionTimer(ctrl)
timer.EXPECT().ConnectionDuration().Return(tc.connection, nil)
lp := &Loadpoint{
log: util.NewLogger("foo"),
charger: struct {
*api.MockCharger
*api.MockConnectionTimer
}{charger, timer},
status: api.StatusC,
phasesSwitched: time.Now().Add(-tc.since),
connectedDuration: time.Hour,
}
res, err := lp.getStatusChanges()
require.NoError(t, err, tc.desc)
assert.Equal(t, tc.expected, res, tc.desc)
assert.Equal(t, api.StatusC, lp.GetStatus(), tc.desc)
// connection duration is tracked even while the interruption is ignored
assert.Equal(t, tc.connection, lp.connectedDuration, tc.desc)
}
}