diff --git a/assets/js/components/Config/defaultYaml/messaging.yaml b/assets/js/components/Config/defaultYaml/messaging.yaml index 989427ff0..cfc4da90b 100644 --- a/assets/js/components/Config/defaultYaml/messaging.yaml +++ b/assets/js/components/Config/defaultYaml/messaging.yaml @@ -17,6 +17,9 @@ # guest: # title: Unknown vehicle # msg: Unknown vehicle, guest connected? +# asleep: +# title: Vehicle asleep +# msg: Charge release, vehicle {{ if .vehicleTitle }}{{ .vehicleTitle }} {{ end }}not charging. #services: #- type: pushover diff --git a/core/loadpoint.go b/core/loadpoint.go index f9003cdc8..667a8730c 100644 --- a/core/loadpoint.go +++ b/core/loadpoint.go @@ -40,6 +40,7 @@ const ( evVehicleDisconnect = "disconnect" // vehicle disconnected evVehicleSoc = "soc" // vehicle soc progress evVehicleUnidentified = "guest" // vehicle unidentified + evVehicleAsleep = "asleep" // vehicle doesn't charge pvTimer = "pv" pvEnable = "enable" @@ -1220,6 +1221,9 @@ func (lp *Loadpoint) scalePhases(phases int) error { // update setting and reset timer lp.SetPhases(phases) + + // some vehicles may hang on phase switch + lp.startWakeUpTimer() } return nil @@ -1972,8 +1976,13 @@ func (lp *Loadpoint) Update(sitePower, batteryBoostPower float64, consumption, f // Wake-up checks if lp.enabled && lp.status == api.StatusB && // TODO take vehicle api limits into account - int(lp.vehicleSoc) < lp.EffectiveLimitSoc() && lp.wakeUpTimer.Expired() { - lp.wakeUpVehicle() + !lp.chargerHasFeature(api.IntegratedDevice) && int(lp.vehicleSoc) < lp.EffectiveLimitSoc() { + switch lp.wakeUpTimer.Elapsed() { + case WakeUpTimerElapsed: + lp.wakeUpVehicle() + case WakeUpTimerFinished: + lp.pushEvent(evVehicleAsleep) + } } // effective disabled status diff --git a/core/loadpoint_phases_test.go b/core/loadpoint_phases_test.go index 6fdd78cf5..eefb6f6d4 100644 --- a/core/loadpoint_phases_test.go +++ b/core/loadpoint_phases_test.go @@ -399,6 +399,7 @@ func TestPvScalePhasesTimer(t *testing.T) { lp := &Loadpoint{ log: util.NewLogger("foo"), + wakeUpTimer: NewTimer(), clock: clock, charger: charger, minCurrent: minA, @@ -453,8 +454,9 @@ func TestScalePhasesIfAvailable(t *testing.T) { phaseCharger := api.NewMockPhaseSwitcher(ctrl) lp := &Loadpoint{ - log: util.NewLogger("foo"), - clock: clock.NewMock(), + log: util.NewLogger("foo"), + clock: clock.NewMock(), + wakeUpTimer: NewTimer(), charger: struct { *api.MockCharger *api.MockPhaseSwitcher diff --git a/core/loadpoint_test.go b/core/loadpoint_test.go index fe8afb9cf..66b2c5db5 100644 --- a/core/loadpoint_test.go +++ b/core/loadpoint_test.go @@ -741,11 +741,12 @@ func TestPVHysteresisAfterPhaseSwitch(t *testing.T) { Voltage = 100 lp := &Loadpoint{ - log: util.NewLogger("foo"), - clock: clock, - charger: charger, - minCurrent: minA, - maxCurrent: maxA, + log: util.NewLogger("foo"), + wakeUpTimer: NewTimer(), + clock: clock, + charger: charger, + minCurrent: minA, + maxCurrent: maxA, Disable: loadpoint.ThresholdConfig{ Delay: dt, }, diff --git a/core/timer.go b/core/timer.go index 59befe191..419603613 100644 --- a/core/timer.go +++ b/core/timer.go @@ -7,9 +7,18 @@ import ( "github.com/benbjohnson/clock" ) -const wakeupTimeout = 30 * time.Second +const ( + wakeupTimeout = 30 * time.Second + wakeupAttempts = 6 +) -const wakeupAttempts = 6 // wakeupAttempts is the count of wakeup attempts +type WakeUpEvent int + +const ( + WakeUpTimerInactive WakeUpEvent = iota + WakeUpTimerElapsed + WakeUpTimerFinished +) // Timer measures active time between start and stop events type Timer struct { @@ -48,20 +57,22 @@ func (m *Timer) Stop() { m.started = time.Time{} } -// Expired checks if the timer has elapsed and if resets its status -func (m *Timer) Expired() bool { +// Elapsed checks if the timer has elapsed and if resets its status +func (m *Timer) Elapsed() WakeUpEvent { m.Lock() defer m.Unlock() - res := !m.started.IsZero() && (m.clck.Since(m.started) >= wakeupTimeout) - if res { - m.wakeupAttemptsLeft-- - if m.wakeupAttemptsLeft == 0 { - m.started = time.Time{} - } else { - m.started = m.clck.Now() - } + if m.started.IsZero() || m.clck.Since(m.started) < wakeupTimeout { + return WakeUpTimerInactive } - return res + if m.wakeupAttemptsLeft == 0 { + m.started = time.Time{} + return WakeUpTimerFinished + } + + m.wakeupAttemptsLeft-- + + m.started = m.clck.Now() + return WakeUpTimerElapsed } diff --git a/core/timer_test.go b/core/timer_test.go index 32a227b65..8b981853f 100644 --- a/core/timer_test.go +++ b/core/timer_test.go @@ -16,15 +16,22 @@ func TestTimer(t *testing.T) { // start at.Start() + + // maximum 2 attempts + at.wakeupAttemptsLeft = 2 + clck.Add(10 * time.Second) - require.False(t, at.Expired()) + require.Equal(t, WakeUpTimerInactive, at.Elapsed()) // wait another 20 sec to expire the timer - this will reset the timer as well clck.Add(wakeupTimeout + 10*time.Second) - require.True(t, at.Expired()) + require.Equal(t, WakeUpTimerElapsed, at.Elapsed()) - // start - at.Start() + // elapse clck.Add(time.Minute) - require.True(t, at.Expired()) + require.Equal(t, WakeUpTimerElapsed, at.Elapsed()) + + // elapse + clck.Add(time.Minute) + require.Equal(t, WakeUpTimerFinished, at.Elapsed()) } diff --git a/evcc.dist.yaml b/evcc.dist.yaml index b77481ed9..07a5f035f 100644 --- a/evcc.dist.yaml +++ b/evcc.dist.yaml @@ -214,6 +214,9 @@ messaging: guest: # vehicle could not be identified title: Unknown vehicle msg: Unknown vehicle, guest connected? + asleep: # vehicle doesn't start charging + title: Vehicle asleep + msg: Charge release, vehicle {{ if .vehicleTitle }}{{ .vehicleTitle }} {{ end }}not charging. services: # - type: pushover # app: # app id