Loadpoint: detect connection changes using connected duration (#24730)
This commit is contained in:
parent
d818bf1467
commit
c023b080d6
6 changed files with 201 additions and 50 deletions
|
|
@ -163,6 +163,7 @@ type Loadpoint struct {
|
|||
// charge progress
|
||||
vehicleSoc float64 // Vehicle or charger soc
|
||||
chargeDuration time.Duration // Charge duration
|
||||
connectedDuration time.Duration // Connection duration
|
||||
energyMetrics EnergyMetrics // Stats for charged energy by session
|
||||
chargeRemainingDuration time.Duration // Remaining charge duration
|
||||
chargeRemainingEnergy float64 // Remaining charge energy in kWh
|
||||
|
|
@ -1073,51 +1074,88 @@ func statusEvents(prevStatus, status api.ChargeStatus) []string {
|
|||
|
||||
// updateChargerStatus updates charger status and detects car connected/disconnected events
|
||||
func (lp *Loadpoint) updateChargerStatus() (bool, error) {
|
||||
var welcomeCharge bool
|
||||
|
||||
status, err := lp.charger.Status()
|
||||
if err != nil {
|
||||
return false, fmt.Errorf("charger status: %w", err)
|
||||
statusChanges, err := lp.getStatusChanges()
|
||||
if err != nil || len(statusChanges) == 0 {
|
||||
return false, err
|
||||
}
|
||||
|
||||
lp.log.DEBUG.Printf("charger status: %s", status)
|
||||
|
||||
if prevStatus := lp.GetStatus(); status != prevStatus {
|
||||
for _, status := range statusChanges {
|
||||
prevStatus := lp.GetStatus()
|
||||
lp.setStatus(status)
|
||||
|
||||
for _, ev := range statusEvents(prevStatus, status) {
|
||||
lp.bus.Publish(ev)
|
||||
|
||||
// send connect/disconnect events except during startup
|
||||
if prevStatus != api.StatusNone {
|
||||
switch ev {
|
||||
case evVehicleConnect:
|
||||
welcomeCharge = lp.chargerHasFeature(api.WelcomeCharge) || hasFeature(lp.defaultVehicle, api.WelcomeCharge)
|
||||
|
||||
// Enable charging on connect if any available vehicle requires it.
|
||||
// We're using the PV timer to disable after the welcome
|
||||
if !welcomeCharge && !lp.chargerHasFeature(api.IntegratedDevice) {
|
||||
for _, v := range lp.availableVehicles() {
|
||||
if slices.Contains(v.Features(), api.WelcomeCharge) {
|
||||
welcomeCharge = true
|
||||
lp.log.DEBUG.Printf("welcome charge: %s", v.GetTitle())
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
lp.pushEvent(evVehicleConnect)
|
||||
case evVehicleDisconnect:
|
||||
lp.pushEvent(evVehicleDisconnect)
|
||||
}
|
||||
if prevStatus != api.StatusNone && (ev == evVehicleConnect || ev == evVehicleDisconnect) {
|
||||
lp.pushEvent(ev)
|
||||
}
|
||||
}
|
||||
|
||||
// update whenever there is a state change
|
||||
lp.bus.Publish(evChargeCurrent, lp.offeredCurrent)
|
||||
}
|
||||
|
||||
return welcomeCharge, nil
|
||||
// update whenever there is a state change
|
||||
lp.bus.Publish(evChargeCurrent, lp.offeredCurrent)
|
||||
|
||||
return lp.needsWelcomeCharge(), nil
|
||||
}
|
||||
|
||||
// getStatusChanges checks charger status and returns a chronological list of status changes
|
||||
func (lp *Loadpoint) getStatusChanges() ([]api.ChargeStatus, error) {
|
||||
var res []api.ChargeStatus
|
||||
|
||||
status, err := lp.charger.Status()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("charger status: %w", err)
|
||||
}
|
||||
|
||||
lp.log.DEBUG.Printf("charger status: %s", status)
|
||||
|
||||
// detect if charger status changed
|
||||
if prevStatus := lp.GetStatus(); status != prevStatus {
|
||||
res = append(res, status)
|
||||
}
|
||||
|
||||
// check charger connection duration
|
||||
if ct, ok := lp.charger.(api.ConnectionTimer); ok {
|
||||
d, err := ct.ConnectionDuration()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("connection duration: %w", err)
|
||||
}
|
||||
|
||||
defer func() { lp.connectedDuration = d }()
|
||||
|
||||
// connection duration dropped while status unchanged, indicates intermediate disconnect
|
||||
if len(res) == 0 && d < lp.connectedDuration {
|
||||
lp.log.DEBUG.Printf("connection duration drop detected (%s -> %v)", lp.connectedDuration.Round(time.Second), d.Round(time.Second))
|
||||
res = append(res, api.StatusA, status)
|
||||
}
|
||||
}
|
||||
|
||||
return res, nil
|
||||
}
|
||||
|
||||
// needsWelcomeCharge checks if either the charger or a vehicle requires a welcome charge
|
||||
func (lp *Loadpoint) needsWelcomeCharge() bool {
|
||||
if !lp.connected() {
|
||||
return false
|
||||
}
|
||||
|
||||
if lp.chargerHasFeature(api.WelcomeCharge) || hasFeature(lp.defaultVehicle, api.WelcomeCharge) {
|
||||
return true
|
||||
}
|
||||
|
||||
// Enable charging on connect if any available vehicle requires it.
|
||||
// We're using the PV timer to disable after the welcome
|
||||
if !lp.chargerHasFeature(api.IntegratedDevice) {
|
||||
for _, v := range lp.availableVehicles() {
|
||||
if slices.Contains(v.Features(), api.WelcomeCharge) {
|
||||
lp.log.DEBUG.Printf("welcome charge: %s", v.GetTitle())
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
// effectiveCurrent returns the currently effective charging current
|
||||
|
|
|
|||
|
|
@ -764,3 +764,49 @@ func TestPVHysteresisAfterPhaseSwitch(t *testing.T) {
|
|||
ctrl.Finish()
|
||||
}
|
||||
}
|
||||
|
||||
func TestConnectionDurationDropDetection(t *testing.T) {
|
||||
clock := clock.NewMock()
|
||||
ctrl := gomock.NewController(t)
|
||||
ch := api.NewMockCharger(ctrl)
|
||||
ct := api.NewMockConnectionTimer(ctrl)
|
||||
|
||||
charger := struct {
|
||||
api.Charger
|
||||
api.ConnectionTimer
|
||||
}{
|
||||
ch, ct,
|
||||
}
|
||||
|
||||
ch.EXPECT().Status().Return(api.StatusC, nil)
|
||||
ch.EXPECT().Enabled().AnyTimes().Return(true, nil)
|
||||
ch.EXPECT().MaxCurrent(int64(minA)).Return(nil)
|
||||
|
||||
lp := &Loadpoint{
|
||||
log: util.NewLogger("foo"),
|
||||
bus: evbus.New(),
|
||||
clock: clock,
|
||||
charger: charger,
|
||||
minCurrent: minA,
|
||||
maxCurrent: maxA,
|
||||
chargeMeter: &Null{}, // silence nil panics
|
||||
chargeRater: &Null{}, // silence nil panics
|
||||
chargeTimer: &Null{}, // silence nil panics
|
||||
wakeUpTimer: NewTimer(), // silence nil panics
|
||||
}
|
||||
|
||||
attachListeners(t, lp)
|
||||
|
||||
connectedTime := clock.Now().Add(-10 * time.Minute)
|
||||
|
||||
lp.enabled = true
|
||||
lp.status = api.StatusC
|
||||
lp.connectedDuration = 10 * time.Minute
|
||||
lp.connectedTime = connectedTime
|
||||
|
||||
ct.EXPECT().ConnectionDuration().Return(0*time.Second, nil)
|
||||
lp.Update(500, 0, nil, nil, false, false, 0, nil, nil)
|
||||
ctrl.Finish()
|
||||
|
||||
assert.NotEqual(t, connectedTime, lp.connectedTime)
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue