Fix vehicle detection not started when default vehicle assigned on connect (#3957)

This commit is contained in:
andig 2022-07-27 10:27:07 +02:00 • committed by GitHub
parent 55910059ed
commit 5398850037
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
3 changed files with 156 additions and 60 deletions

View file

@ -4,6 +4,7 @@ import (
"errors" "errors"
"fmt" "fmt"
"math" "math"
"reflect"
"regexp" "regexp"
"strings" "strings"
"sync" "sync"
@ -114,15 +115,15 @@ type LoadPoint struct {
MaxCurrent float64 // Max allowed current. Physically ensured by the charger MaxCurrent float64 // Max allowed current. Physically ensured by the charger
GuardDuration time.Duration // charger enable/disable minimum holding time GuardDuration time.Duration // charger enable/disable minimum holding time
enabled bool // Charger enabled state enabled bool // Charger enabled state
phases int // Charger active phases, guarded by mutex phases int // Charger active phases, guarded by mutex
measuredPhases int // Charger physically measured phases measuredPhases int // Charger physically measured phases
chargeCurrent float64 // Charger current limit chargeCurrent float64 // Charger current limit
guardUpdated time.Time // Charger enabled/disabled timestamp guardUpdated time.Time // Charger enabled/disabled timestamp
socUpdated time.Time // SoC updated timestamp (poll: connected) socUpdated time.Time // SoC updated timestamp (poll: connected)
vehicleConnected time.Time // Vehicle connected timestamp vehicleDetect time.Time // Vehicle connected timestamp
vehicleConnectedTicker *clock.Ticker vehicleDetectTicker *clock.Ticker
vehicleID string vehicleID string
charger api.Charger charger api.Charger
chargeTimer api.ChargeTimer chargeTimer api.ChargeTimer
@ -414,21 +415,11 @@ func (lp *LoadPoint) evVehicleConnectHandler() {
lp.socEstimator.Reset() lp.socEstimator.Reset()
} }
// flush all vehicles before updating state // set default or start detection
lp.log.DEBUG.Println("vehicle api refresh") lp.vehicleDefaultOrDetect()
provider.ResetCached()
// start detection if we have associated vehicles
if lp.vehicle == lp.defaultVehicle {
lp.setActiveVehicle(lp.defaultVehicle)
} else if len(lp.vehicles) > 0 {
lp.startVehicleDetection()
}
// immediately allow pv mode activity // immediately allow pv mode activity
lp.elapsePVTimer() lp.elapsePVTimer()
lp.pushEvent(evVehicleConnect)
} }
// evVehicleDisconnectHandler sends external start event // evVehicleDisconnectHandler sends external start event
@ -550,9 +541,6 @@ func (lp *LoadPoint) Prepare(uiChan chan<- util.Param, pushChan chan<- push.Even
// activate default vehicle (allows poll mode: always) // activate default vehicle (allows poll mode: always)
if lp.defaultVehicle != nil { if lp.defaultVehicle != nil {
lp.setActiveVehicle(lp.defaultVehicle) lp.setActiveVehicle(lp.defaultVehicle)
} else if len(lp.vehicles) > 0 {
// start detection if we have associated vehicles
lp.startVehicleDetection()
} }
// read initial charger state to prevent immediately disabling charger // read initial charger state to prevent immediately disabling charger
@ -793,7 +781,8 @@ func (lp *LoadPoint) selectVehicleByID(id string) api.Vehicle {
return nil return nil
} }
// setActiveVehicle assigns currently active vehicle and configures soc estimator // setActiveVehicle assigns currently active vehicle, configures soc estimator
// and adds an odometer task
func (lp *LoadPoint) setActiveVehicle(vehicle api.Vehicle) { func (lp *LoadPoint) setActiveVehicle(vehicle api.Vehicle) {
lp.Lock() lp.Lock()
defer lp.Unlock() defer lp.Unlock()
@ -823,14 +812,12 @@ func (lp *LoadPoint) setActiveVehicle(vehicle api.Vehicle) {
lp.publish("vehicleTitle", lp.vehicle.Title()) lp.publish("vehicleTitle", lp.vehicle.Title())
lp.publish("vehicleCapacity", lp.vehicle.Capacity()) lp.publish("vehicleCapacity", lp.vehicle.Capacity())
// release lock to unblock api // unblock api
lp.Unlock() lp.Unlock()
lp.applyAction(vehicle.OnIdentified())
lp.Lock()
lp.addTask(lp.vehicleOdometer) lp.addTask(lp.vehicleOdometer)
lp.applyAction(vehicle.OnIdentified())
// re-apply lock to match defer above
lp.Lock()
lp.progress.Reset() lp.progress.Reset()
} else { } else {
@ -874,29 +861,15 @@ func (lp *LoadPoint) unpublishVehicle() {
lp.setRemainingDuration(-1) lp.setRemainingDuration(-1)
} }
// startVehicleDetection resets connection timer and starts api refresh timer
func (lp *LoadPoint) startVehicleDetection() {
lp.vehicleConnected = lp.clock.Now()
lp.vehicleConnectedTicker = lp.clock.Ticker(vehicleDetectInterval)
}
// stopVehicleDetection expires the connection timer and ticker
func (lp *LoadPoint) stopVehicleDetection() {
lp.vehicleConnected = time.Time{}
if lp.vehicleConnectedTicker != nil {
lp.vehicleConnectedTicker.Stop()
}
}
// vehicleUnidentified checks if there are associated vehicles and starts discovery period // vehicleUnidentified checks if there are associated vehicles and starts discovery period
func (lp *LoadPoint) vehicleUnidentified() bool { func (lp *LoadPoint) vehicleUnidentified() bool {
res := len(lp.vehicles) > 0 && lp.vehicle == nil && res := len(lp.vehicles) > 0 && lp.vehicle == nil &&
lp.clock.Since(lp.vehicleConnected) < vehicleDetectDuration lp.clock.Since(lp.vehicleDetect) < vehicleDetectDuration
// request vehicle api refresh while waiting to identify // request vehicle api refresh while waiting to identify
if res { if res {
select { select {
case <-lp.vehicleConnectedTicker.C: case <-lp.vehicleDetectTicker.C:
lp.log.DEBUG.Println("vehicle api refresh") lp.log.DEBUG.Println("vehicle api refresh")
provider.ResetCached() provider.ResetCached()
default: default:
@ -906,6 +879,35 @@ func (lp *LoadPoint) vehicleUnidentified() bool {
return res return res
} }
// vehicleDefaultOrDetect will assign and update default vehicle or start detection
func (lp *LoadPoint) vehicleDefaultOrDetect() {
if lp.defaultVehicle != nil {
if lp.vehicle != lp.defaultVehicle {
lp.setActiveVehicle(lp.defaultVehicle)
} else {
// default vehicle is already active, update odometer anyway
// need to do this here since setActiveVehicle would short-circuit
lp.addTask(lp.vehicleOdometer)
}
} else if len(lp.vehicles) > 0 {
// flush all vehicles before detection starts
lp.log.DEBUG.Println("vehicle api refresh")
provider.ResetCached()
// reset connection timer and starts api refresh timer
lp.vehicleDetect = lp.clock.Now()
lp.vehicleDetectTicker = lp.clock.Ticker(vehicleDetectInterval)
}
}
// stopVehicleDetection expires the connection timer and ticker
func (lp *LoadPoint) stopVehicleDetection() {
lp.vehicleDetect = time.Time{}
if lp.vehicleDetectTicker != nil {
lp.vehicleDetectTicker.Stop()
}
}
// identifyVehicleByStatus validates if the active vehicle is still connected to the loadpoint // identifyVehicleByStatus validates if the active vehicle is still connected to the loadpoint
func (lp *LoadPoint) identifyVehicleByStatus() { func (lp *LoadPoint) identifyVehicleByStatus() {
if len(lp.vehicles) == 0 { if len(lp.vehicles) == 0 {
@ -947,14 +949,18 @@ func (lp *LoadPoint) updateChargerStatus() error {
if prevStatus := lp.GetStatus(); status != prevStatus { if prevStatus := lp.GetStatus(); status != prevStatus {
lp.setStatus(status) lp.setStatus(status)
// changed from empty (initial startup) - set connected without sending message // changed to A - disconnected - don't send on startup
if prevStatus == api.StatusNone { if status == api.StatusA && prevStatus != api.StatusNone {
lp.connectedTime = lp.clock.Now() lp.bus.Publish(evVehicleDisconnect)
lp.publish("connectedDuration", time.Duration(0))
} }
// changed from A - connected // changed to B - connected - don't send on startup
if prevStatus == api.StatusA { if status == api.StatusB && prevStatus != api.StatusC {
if prevStatus != api.StatusNone {
// send connected message if not startup
lp.pushEvent(evVehicleConnect)
}
lp.bus.Publish(evVehicleConnect) lp.bus.Publish(evVehicleConnect)
} }
@ -965,11 +971,6 @@ func (lp *LoadPoint) updateChargerStatus() error {
lp.bus.Publish(evChargeStop) lp.bus.Publish(evChargeStop)
} }
// changed to A - disconnected - don't send on startup
if status == api.StatusA && prevStatus != api.StatusNone {
lp.bus.Publish(evVehicleDisconnect)
}
// update whenever there is a state change // update whenever there is a state change
lp.bus.Publish(evChargeCurrent, lp.chargeCurrent) lp.bus.Publish(evChargeCurrent, lp.chargeCurrent)
} }
@ -1451,11 +1452,20 @@ func (lp *LoadPoint) publishSoCAndRange() {
// addTask adds a single task to the queue // addTask adds a single task to the queue
func (lp *LoadPoint) addTask(task func()) { func (lp *LoadPoint) addTask(task func()) {
lp.tasks.Enqueue(task) // test guard
if lp.tasks != nil {
// don't add twice
if t, ok := lp.tasks.Peek(); ok &&
reflect.ValueOf(t).Pointer() == reflect.ValueOf(task).Pointer() {
return
}
lp.tasks.Enqueue(task)
}
} }
// processTasks executes a single task from the queue // processTasks executes a single task from the queue
func (lp *LoadPoint) processTasks() { func (lp *LoadPoint) processTasks() {
// test guard
if lp.tasks != nil { if lp.tasks != nil {
if task, ok := lp.tasks.Dequeue(); ok { if task, ok := lp.tasks.Dequeue(); ok {
task.(func())() task.(func())()

View file

@ -881,6 +881,16 @@ func TestDefaultVehicle(t *testing.T) {
t.Errorf("expected %v, got %v", title(dflt), title(lp.vehicle)) t.Errorf("expected %v, got %v", title(dflt), title(lp.vehicle))
} }
// set non-default vehicle during disconnect - should be default on connect
lp.tasks.Clear()
lp.evVehicleConnectHandler()
if lp.vehicle != dflt {
t.Errorf("expected %v, got %v", title(dflt), title(lp.vehicle))
}
if l := lp.tasks.Size(); l != 1 {
t.Error("expected task in queue, got none")
}
// guest connected // guest connected
lp.setActiveVehicle(nil) lp.setActiveVehicle(nil)
if lp.vehicle != nil { if lp.vehicle != nil {
@ -945,3 +955,75 @@ func TestApplyVehicleDefaults(t *testing.T) {
lp.evVehicleDisconnectHandler() lp.evVehicleDisconnectHandler()
assertConfig(lp, od) assertConfig(lp, od)
} }
func TestReconnectVehicle(t *testing.T) {
ctrl := gomock.NewController(t)
clck := clock.NewMock()
type vehicleT struct {
*mock.MockVehicle
*mock.MockChargeState
}
vehicle := &vehicleT{mock.NewMockVehicle(ctrl), mock.NewMockChargeState(ctrl)}
vehicle.MockVehicle.EXPECT().Title().Return("vehicle").AnyTimes()
vehicle.MockVehicle.EXPECT().Capacity().AnyTimes()
vehicle.MockVehicle.EXPECT().Phases().AnyTimes()
vehicle.MockVehicle.EXPECT().OnIdentified().AnyTimes()
vehicle.MockVehicle.EXPECT().SoC().Return(0.0, nil).AnyTimes()
charger := mock.NewMockCharger(ctrl)
charger.EXPECT().Status().Return(api.StatusB, nil).AnyTimes()
lp := &LoadPoint{
log: util.NewLogger("foo"),
bus: evbus.New(),
clock: clck,
charger: charger,
chargeMeter: &Null{}, // silence nil panics
chargeRater: &Null{}, // silence nil panics
chargeTimer: &Null{}, // silence nil panics
wakeUpTimer: NewTimer(),
MinCurrent: minA,
MaxCurrent: maxA,
phases: 1,
Mode: api.ModeNow,
vehicles: []api.Vehicle{vehicle},
}
attachListeners(t, lp)
// mode now
charger.EXPECT().MaxCurrent(int64(maxA))
// sync charger
charger.EXPECT().Enabled().Return(true, nil)
// vehicle not updated yet
vehicle.MockChargeState.EXPECT().Status().Return(api.StatusA, nil)
lp.Update(0, false, false)
ctrl.Finish()
// detection started
if lp.vehicleDetect != lp.clock.Now() {
t.Error("vehicle detection not started")
}
// vehicle not detected yet
if lp.vehicle != nil {
t.Error("vehicle should be <nil>")
}
// sync charger
charger.EXPECT().Enabled().Return(true, nil)
// vehicle not updated yet
vehicle.MockChargeState.EXPECT().Status().Return(api.StatusB, nil)
lp.Update(0, false, false)
ctrl.Finish()
// vehicle detected
if lp.vehicle != vehicle {
t.Error("vehicle should be detected")
}
}

View file

@ -13,7 +13,8 @@ func NewProgress(min, step float64) *Progress {
} }
func (p *Progress) NextStep(value float64) bool { func (p *Progress) NextStep(value float64) bool {
if value >= p.current { // test guard
if p != nil && value >= p.current {
for p.current <= value { for p.current <= value {
p.current += p.step p.current += p.step
} }
@ -25,5 +26,8 @@ func (p *Progress) NextStep(value float64) bool {
} }
func (p *Progress) Reset() { func (p *Progress) Reset() {
p.current = p.min // test guard
if p != nil {
p.current = p.min
}
} }