From b2b4483bf95e99429491e541d20b009f5dba2a5a Mon Sep 17 00:00:00 2001 From: MarkusGH <34029447+MarkusGH@users.noreply.github.com> Date: Tue, 20 Jun 2023 21:58:07 +0200 Subject: [PATCH] Don't disable for 1p3p- let charger handle session stop/restart (#8320) --- charger/charger.go | 6 ++++++ charger/easee.go | 7 ++++++- core/loadpoint.go | 38 +++++++++++++++++++++----------------- core/loadpoint_phases.go | 2 +- 4 files changed, 34 insertions(+), 19 deletions(-) diff --git a/charger/charger.go b/charger/charger.go index 06417d9f5..9cdb117e0 100644 --- a/charger/charger.go +++ b/charger/charger.go @@ -1,6 +1,7 @@ package charger import ( + "errors" "fmt" "github.com/evcc-io/evcc/api" @@ -30,6 +31,7 @@ func NewConfigurableFromConfig(other map[string]interface{}) (api.Charger, error Status, Enable, Enabled, MaxCurrent provider.Config Identify, Phases1p3p *provider.Config Wakeup *provider.Config + Tos bool } if err := util.DecodeOther(other, &cc); err != nil { @@ -66,6 +68,10 @@ func NewConfigurableFromConfig(other map[string]interface{}) (api.Charger, error // decorate phases var phases1p3p func(int) error if cc.Phases1p3p != nil { + if !cc.Tos { + return nil, errors.New("1p3p does no longer handle disable/enable. Use tos: true to confirm you understand the consequences") + } + phases1p3pS, err := provider.NewIntSetterFromConfig("phases", *cc.Phases1p3p) if err != nil { return nil, fmt.Errorf("phases: %w", err) diff --git a/charger/easee.go b/charger/easee.go index e9a0556b4..85e690857 100644 --- a/charger/easee.go +++ b/charger/easee.go @@ -550,7 +550,12 @@ func (c *Easee) Phases1p3p(phases int) error { uri := fmt.Sprintf("%s/chargers/%s/settings", easee.API, c.charger) - err = c.postJSONAndWait(uri, data) + if err = c.postJSONAndWait(uri, data); err != nil { + return err + } + + // disable charger to activate changed settings (loadpoint will reenable it) + err = c.Enable(false) } } diff --git a/core/loadpoint.go b/core/loadpoint.go index a7c14fe15..fd0af15f9 100644 --- a/core/loadpoint.go +++ b/core/loadpoint.go @@ -52,7 +52,9 @@ const ( minActiveCurrent = 1.0 // minimum current at which a phase is treated as active minActiveVoltage = 208 // minimum voltage at which a phase is treated as active - guardGracePeriod = 60 * time.Second // allow out of sync during this timespan + guardGracePeriod = 60 * time.Second // allow out of sync during this timespan + phaseSwitchCommandTimeout = 30 * time.Second // do not sync charger enabled/disabled state during this timespan + phaseSwitchDuration = 60 * time.Second // do not measure phases during this timespan ) // elapsed is the time an expired timer will be set to @@ -131,6 +133,7 @@ type Loadpoint struct { guardUpdated time.Time // Charger enabled/disabled timestamp socUpdated time.Time // Soc updated timestamp (poll: connected) vehicleDetect time.Time // Vehicle connected timestamp + phasesSwitched time.Time // Phase switch timestamp vehicleDetectTicker *clock.Ticker vehicleIdentifier string @@ -630,9 +633,9 @@ func (lp *Loadpoint) syncCharger() error { return err } - if enabled != lp.enabled { + if (enabled != lp.enabled) && (!lp.enabled || lp.phaseSwitchCommandTimeoutElapsed()) { // ignore disabled state if vehicle was disconnected ^(lp.enabled && ^lp.connected) - if lp.guardGracePeriodElapsed() && (!lp.enabled || lp.connected()) { + if lp.guardGracePeriodElapsed() && lp.phaseSwitchCompleted() && (!lp.enabled || lp.connected()) { lp.log.WARN.Printf("charger out of sync: expected %vd, got %vd", status[lp.enabled], status[enabled]) } return lp.charger.Enable(lp.enabled) @@ -929,21 +932,16 @@ func (lp *Loadpoint) scalePhases(phases int) error { } if lp.GetPhases() != phases { - // disable charger - this will also stop the car charging using the api if available - if err := lp.setLimit(0, true); err != nil { - return err - } - // switch phases if err := cp.Phases1p3p(phases); err != nil { return fmt.Errorf("switch phases: %w", err) } + // prevent premature measurement of active phases + lp.phasesSwitched = lp.clock.Now() + // update setting and reset timer lp.setPhases(phases) - - // allow pv mode to re-enable charger right away - lp.elapsePVTimer() } return nil @@ -1071,11 +1069,7 @@ func (lp *Loadpoint) pvMaxCurrent(mode api.ChargeMode, sitePower float64, batter // switch phases up/down if _, ok := lp.charger.(api.PhaseSwitcher); ok { availablePower := -sitePower + lp.chargePower - - // in case of scaling, keep charger disabled for this cycle - if lp.pvScalePhases(availablePower, minCurrent, maxCurrent) { - return 0 - } + _ = lp.pvScalePhases(availablePower, minCurrent, maxCurrent) } // calculate target charge current from delta power and actual current @@ -1210,7 +1204,7 @@ func (lp *Loadpoint) updateChargeCurrents() { lp.log.DEBUG.Printf("charge currents: %.3gA", lp.chargeCurrents) lp.publish("chargeCurrents", lp.chargeCurrents) - if lp.charging() { + if lp.charging() && lp.phaseSwitchCompleted() { var phases int for _, i := range lp.chargeCurrents { if i > minActiveCurrent { @@ -1419,6 +1413,16 @@ func (lp *Loadpoint) guardGracePeriodElapsed() bool { return time.Since(lp.guardUpdated) > guardGracePeriod } +// phaseSwitchCommandTimeoutElapsed returns true if phase switch command should be already processed by the charger +func (lp *Loadpoint) phaseSwitchCommandTimeoutElapsed() bool { + return time.Since(lp.phasesSwitched) > phaseSwitchCommandTimeout +} + +// phaseSwitchCompleted returns true if phase switch has completed +func (lp *Loadpoint) phaseSwitchCompleted() bool { + return time.Since(lp.phasesSwitched) > phaseSwitchDuration +} + // Update is the main control function. It reevaluates meters and charger state func (lp *Loadpoint) Update(sitePower float64, autoCharge, batteryBuffered, batteryStart bool, greenShare float64, effPrice, effCo2 *float64) { lp.processTasks() diff --git a/core/loadpoint_phases.go b/core/loadpoint_phases.go index 40e79a9b4..3b7d6a931 100644 --- a/core/loadpoint_phases.go +++ b/core/loadpoint_phases.go @@ -85,7 +85,7 @@ func (lp *Loadpoint) activePhases() int { active := min(expect(vehicle), expect(physical), expect(measured)) // sanity check - we should not assume less active phases than actually measured - if measured > 0 && active < measured && lp.guardGracePeriodElapsed() { + if measured > 0 && active < measured { lp.log.WARN.Printf("phase mismatch between %dp measured for %dp vehicle and %dp charger", measured, vehicle, physical) }