From 72b7e270570241713dbda2c59421acc5d7dcc750 Mon Sep 17 00:00:00 2001 From: andig Date: Wed, 19 Aug 2026 08:50:24 +0200 Subject: [PATCH] Loadpoint: sync charger phases on vehicle connect (#32962) --- core/loadpoint.go | 37 ++++++++--------------------------- core/loadpoint_phases.go | 42 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 50 insertions(+), 29 deletions(-) diff --git a/core/loadpoint.go b/core/loadpoint.go index ee0884870..69d21af59 100644 --- a/core/loadpoint.go +++ b/core/loadpoint.go @@ -561,6 +561,11 @@ func (lp *Loadpoint) evVehicleConnectHandler() { // soc update reset lp.socUpdated = time.Time{} + // charger may have reconfigured phases internally while disconnected + if err := lp.syncChargerPhases(); err != nil { + lp.log.ERROR.Println(err) + } + // set default or start detection if !lp.chargerHasFeature(api.IntegratedDevice) { lp.vehicleDefaultOrDetect() @@ -884,35 +889,9 @@ func (lp *Loadpoint) syncCharger() error { } // sync phases - _, isPs := api.Cap[api.PhaseSwitcher](lp.charger) - if phases := lp.GetPhases(); isPs && shouldBeConsistent && phases > 0 { - // fallback to active phases from measured phases - chargerPhases := lp.measuredPhases - if chargerPhases == 2 { - chargerPhases = 3 - } - - pg, isPg := api.Cap[api.PhaseGetter](lp.charger) - if isPg { - if chargerPhases, err = pg.GetPhases(); err == nil { - if chargerPhases > 0 && chargerPhases != phases { - lp.log.WARN.Printf("charger logic error: phases mismatch (got %d, expected %d)", chargerPhases, phases) - lp.SetPhases(chargerPhases) - } - } else { - if errors.Is(err, api.ErrNotAvailable) { - return nil - } - return fmt.Errorf("charger get phases: %w", err) - } - } - - // use measured phase currents for active phases as fallback if charger does not provide phases - if !isPg || errors.Is(err, api.ErrNotAvailable) { - if chargerPhases > phases { - lp.log.WARN.Printf("charger logic error: phases mismatch (got %d measured, expected %d)", chargerPhases, phases) - lp.SetPhases(chargerPhases) - } + if shouldBeConsistent { + if err := lp.syncChargerPhases(); err != nil { + return err } } diff --git a/core/loadpoint_phases.go b/core/loadpoint_phases.go index 207ef9234..50d9e8f2a 100644 --- a/core/loadpoint_phases.go +++ b/core/loadpoint_phases.go @@ -1,6 +1,9 @@ package core import ( + "errors" + "fmt" + "github.com/evcc-io/evcc/api" "github.com/evcc-io/evcc/core/keys" ) @@ -161,3 +164,42 @@ func (lp *Loadpoint) getChargerPhysicalPhases() int { func (lp *Loadpoint) hasPhaseSwitching() bool { return api.HasCap[api.PhaseSwitcher](lp.charger) } + +// syncChargerPhases synchronizes the assumed phase state with the charger's actual state. +// Chargers may reconfigure phases internally, i.e. when the vehicle is (dis)connected. +func (lp *Loadpoint) syncChargerPhases() error { + phases := lp.GetPhases() + if !lp.hasPhaseSwitching() || phases <= 0 { + return nil + } + + if pg, ok := api.Cap[api.PhaseGetter](lp.charger); ok { + chargerPhases, err := pg.GetPhases() + if err != nil { + if errors.Is(err, api.ErrNotAvailable) { + return nil + } + return fmt.Errorf("charger get phases: %w", err) + } + + if chargerPhases > 0 && chargerPhases != phases { + lp.log.WARN.Printf("charger logic error: phases mismatch (got %d, expected %d)", chargerPhases, phases) + lp.SetPhases(chargerPhases) + } + + return nil + } + + // use measured phase currents for active phases as fallback if charger does not provide phases + chargerPhases := lp.GetMeasuredPhases() + if chargerPhases == 2 { + chargerPhases = 3 + } + + if chargerPhases > phases { + lp.log.WARN.Printf("charger logic error: phases mismatch (got %d measured, expected %d)", chargerPhases, phases) + lp.SetPhases(chargerPhases) + } + + return nil +}