Loadpoint: tolerate api.ErrNotAvailable on phase switch (#29975)
This commit is contained in:
parent
e48887e04b
commit
177cb19d5d
3 changed files with 118 additions and 5 deletions
|
|
@ -198,7 +198,10 @@ func (wb *GhostEEBus) getPhases() (int, error) {
|
|||
if res.LimitationReason != "" {
|
||||
msg += fmt.Sprintf(" (%s)", res.LimitationReason)
|
||||
}
|
||||
return 0, errors.New(msg)
|
||||
// the phase state cannot be read - signal api.ErrNotAvailable so
|
||||
// callers treat it as a benign "not available" rather than an error
|
||||
wb.log.TRACE.Println(msg)
|
||||
return 0, api.ErrNotAvailable
|
||||
}
|
||||
|
||||
switch res.CurrentState {
|
||||
|
|
|
|||
|
|
@ -1307,7 +1307,9 @@ func (lp *Loadpoint) fastCharging() error {
|
|||
}
|
||||
}
|
||||
|
||||
if err := lp.scalePhasesIfAvailable(phases); err != nil {
|
||||
// ignore api.ErrNotAvailable: the phase switch could not be performed
|
||||
// right now, continue with the current phase configuration
|
||||
if err := lp.scalePhasesIfAvailable(phases); err != nil && !errors.Is(err, api.ErrNotAvailable) {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
|
@ -1353,7 +1355,13 @@ func (lp *Loadpoint) pvScalePhases(sitePower, minCurrent, maxCurrent float64) in
|
|||
|
||||
if elapsed := lp.clock.Since(lp.phaseTimer); elapsed >= lp.GetDisableDelay() {
|
||||
if err := lp.scalePhases(1); err != nil {
|
||||
lp.log.ERROR.Println(err)
|
||||
// a charger may report it cannot switch phases right now
|
||||
// (api.ErrNotAvailable); assume a failed switch and stay silent
|
||||
if !errors.Is(err, api.ErrNotAvailable) {
|
||||
lp.log.ERROR.Println(err)
|
||||
}
|
||||
// switch did not complete - phase count is unchanged
|
||||
return phases
|
||||
}
|
||||
return 1
|
||||
}
|
||||
|
|
@ -1382,7 +1390,13 @@ func (lp *Loadpoint) pvScalePhases(sitePower, minCurrent, maxCurrent float64) in
|
|||
|
||||
if elapsed := lp.clock.Since(lp.phaseTimer); elapsed >= lp.GetEnableDelay() {
|
||||
if err := lp.scalePhases(3); err != nil {
|
||||
lp.log.ERROR.Println(err)
|
||||
// a charger may report it cannot switch phases right now
|
||||
// (api.ErrNotAvailable); assume a failed switch and stay silent
|
||||
if !errors.Is(err, api.ErrNotAvailable) {
|
||||
lp.log.ERROR.Println(err)
|
||||
}
|
||||
// switch did not complete - phase count is unchanged
|
||||
return phases
|
||||
}
|
||||
return 3
|
||||
}
|
||||
|
|
@ -2026,7 +2040,13 @@ func (lp *Loadpoint) Update(sitePower, batteryBoostPower float64, consumption, f
|
|||
err = lp.setLimit(0)
|
||||
|
||||
case lp.scalePhasesRequired():
|
||||
err = lp.scalePhases(lp.phasesConfigured)
|
||||
if err = lp.scalePhases(lp.phasesConfigured); errors.Is(err, api.ErrNotAvailable) {
|
||||
// the charger cannot switch phases right now (e.g. EEBus charger
|
||||
// with an ISO 15118 vehicle). Adopt the configured phase count so
|
||||
// the switch is not re-attempted on every cycle (issue #29974).
|
||||
lp.SetPhases(lp.phasesConfigured)
|
||||
err = nil
|
||||
}
|
||||
|
||||
case mode == api.ModeOff:
|
||||
var current float64
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
package core
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
|
@ -480,6 +481,40 @@ func TestScalePhasesIfAvailable(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
// TestScalePhasesNotAvailable verifies that a charger reporting api.ErrNotAvailable
|
||||
// from Phases1p3p (e.g. an EEBus charger with an ISO 15118 vehicle, see issue #29974)
|
||||
// is treated as a failed switch: the error is api.ErrNotAvailable so callers can
|
||||
// suppress it, and the phase count is left unchanged.
|
||||
func TestScalePhasesNotAvailable(t *testing.T) {
|
||||
ctrl := gomock.NewController(t)
|
||||
defer ctrl.Finish()
|
||||
|
||||
plainCharger := api.NewMockCharger(ctrl)
|
||||
phaseCharger := api.NewMockPhaseSwitcher(ctrl)
|
||||
phaseCharger.EXPECT().Phases1p3p(3).Return(api.ErrNotAvailable)
|
||||
|
||||
lp := &Loadpoint{
|
||||
log: util.NewLogger("foo"),
|
||||
clock: clock.NewMock(),
|
||||
wakeUpTimer: NewTimer(),
|
||||
charger: struct {
|
||||
*api.MockCharger
|
||||
*api.MockPhaseSwitcher
|
||||
}{
|
||||
plainCharger,
|
||||
phaseCharger,
|
||||
},
|
||||
phases: 1, // current phase status, switch to 3p will be attempted
|
||||
}
|
||||
|
||||
err := lp.scalePhases(3)
|
||||
require.Error(t, err)
|
||||
require.True(t, errors.Is(err, api.ErrNotAvailable), "want api.ErrNotAvailable, got %v", err)
|
||||
|
||||
// switch did not complete - phase count unchanged
|
||||
require.Equal(t, 1, lp.GetPhases())
|
||||
}
|
||||
|
||||
func TestFastChargingCircuitBasedPhaseScaling(t *testing.T) {
|
||||
Voltage = 230
|
||||
|
||||
|
|
@ -550,3 +585,58 @@ func TestFastChargingCircuitBasedPhaseScaling(t *testing.T) {
|
|||
})
|
||||
}
|
||||
}
|
||||
|
||||
// TestUpdatePhaseSwitchNotAvailable verifies that, with a fixed phasesConfigured
|
||||
// and a charger that refuses phase switching (api.ErrNotAvailable, e.g. an EEBus
|
||||
// charger with an ISO 15118 vehicle), the configured phase count is adopted so
|
||||
// the switch is not re-attempted on every update cycle (issue #29974).
|
||||
func TestUpdatePhaseSwitchNotAvailable(t *testing.T) {
|
||||
clock := clock.NewMock()
|
||||
ctrl := gomock.NewController(t)
|
||||
defer ctrl.Finish()
|
||||
|
||||
plainCharger := api.NewMockCharger(ctrl)
|
||||
phaseCharger := api.NewMockPhaseSwitcher(ctrl)
|
||||
|
||||
plainCharger.EXPECT().Status().Return(api.StatusC, nil).AnyTimes()
|
||||
plainCharger.EXPECT().Enabled().Return(true, nil).AnyTimes()
|
||||
plainCharger.EXPECT().Enable(gomock.Any()).Return(nil).AnyTimes()
|
||||
plainCharger.EXPECT().MaxCurrent(gomock.Any()).Return(nil).AnyTimes()
|
||||
|
||||
// charger cannot switch phases - and must only be asked once
|
||||
phaseCharger.EXPECT().Phases1p3p(3).Return(api.ErrNotAvailable).Times(1)
|
||||
|
||||
lp := &Loadpoint{
|
||||
log: util.NewLogger("foo"),
|
||||
bus: evbus.New(),
|
||||
clock: clock,
|
||||
chargeMeter: &Null{},
|
||||
chargeRater: &Null{},
|
||||
chargeTimer: &Null{},
|
||||
progress: NewProgress(0, 10),
|
||||
wakeUpTimer: NewTimer(),
|
||||
mode: api.ModeNow,
|
||||
minCurrent: minA,
|
||||
maxCurrent: maxA,
|
||||
status: api.StatusC,
|
||||
charger: struct {
|
||||
*api.MockCharger
|
||||
*api.MockPhaseSwitcher
|
||||
}{
|
||||
plainCharger,
|
||||
phaseCharger,
|
||||
},
|
||||
phasesConfigured: 3, // fixed 3p
|
||||
phases: 0, // unknown
|
||||
}
|
||||
|
||||
attachListeners(t, lp)
|
||||
|
||||
// first cycle attempts the switch, gets api.ErrNotAvailable, adopts 3p
|
||||
lp.Update(0, 0, nil, nil, false, false, 0, nil, nil)
|
||||
require.Equal(t, 3, lp.GetPhases(), "configured phases should be adopted")
|
||||
|
||||
// second cycle must not attempt the switch again (Phases1p3p .Times(1))
|
||||
lp.Update(0, 0, nil, nil, false, false, 0, nil, nil)
|
||||
require.Equal(t, 3, lp.GetPhases())
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue