diff --git a/core/loadpoint.go b/core/loadpoint.go index 26f71d895..6b6b23f3b 100644 --- a/core/loadpoint.go +++ b/core/loadpoint.go @@ -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} } diff --git a/core/loadpoint_status_test.go b/core/loadpoint_status_test.go index 55e7980e1..0e15de47b 100644 --- a/core/loadpoint_status_test.go +++ b/core/loadpoint_status_test.go @@ -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) + } +}