Skip to content

Commit

Permalink
Vehicle: respect poll mode when querying climater (#7151)
Browse files Browse the repository at this point in the history
  • Loading branch information
andig committed Apr 1, 2023
1 parent 7226a38 commit c5040d2
Show file tree
Hide file tree
Showing 4 changed files with 71 additions and 60 deletions.
60 changes: 3 additions & 57 deletions core/loadpoint.go
Expand Up @@ -766,31 +766,10 @@ func (lp *Loadpoint) minSocNotReached() bool {
return minEnergy > 0 && lp.getChargedEnergy() < minEnergy
}

// climateActive checks if vehicle has active climate request
func (lp *Loadpoint) climateActive() bool {
if cl, ok := lp.vehicle.(api.VehicleClimater); ok {
active, err := cl.Climater()
if err == nil {
if active {
lp.log.DEBUG.Println("climater active")
}

lp.publish("climaterActive", active)
return active
}

if !errors.Is(err, api.ErrNotAvailable) {
lp.log.ERROR.Printf("climater: %v", err)
}
}

return false
}

// disableUnlessClimater disables the charger unless climate is active
func (lp *Loadpoint) disableUnlessClimater() error {
var current float64 // zero disables
if lp.climateActive() {
if lp.vehicleClimateActive() {
current = lp.GetMinCurrent()
}

Expand Down Expand Up @@ -1313,38 +1292,6 @@ func (lp *Loadpoint) publishChargeProgress() {
}
}

// socPollAllowed validates charging state against polling mode
func (lp *Loadpoint) socPollAllowed() bool {
// always update soc when charging
if lp.charging() {
lp.didChargeOnLastSocUpdate = true
return true
}

// update if connected and soc unknown
if lp.connected() && lp.socUpdated.IsZero() {
return true
}

remaining := lp.Soc.Poll.Interval - lp.clock.Since(lp.socUpdated)

honourUpdateInterval := lp.Soc.Poll.Mode == pollAlways ||
lp.connected() && (lp.Soc.Poll.Mode == pollConnected ||
// for mode charging allow one last soc update if did charge previously to not rely on soc estimator too much
lp.Soc.Poll.Mode == pollCharging && lp.didChargeOnLastSocUpdate)

if honourUpdateInterval {
if remaining > 0 {
lp.log.DEBUG.Printf("next soc poll remaining time: %v", remaining.Truncate(time.Second))
} else {
lp.didChargeOnLastSocUpdate = false
return true
}
}

return false
}

// publish state of charge, remaining charge duration and range
func (lp *Loadpoint) publishSocAndRange() {
soc, err := lp.chargerSoc()
Expand All @@ -1361,7 +1308,7 @@ func (lp *Loadpoint) publishSocAndRange() {
return
}

if err == nil || lp.socPollAllowed() {
if err == nil || lp.vehicleSocPollAllowed() {
lp.socUpdated = lp.clock.Now()

f, err := lp.socEstimator.Soc(lp.getChargedEnergy())
Expand Down Expand Up @@ -1535,7 +1482,6 @@ func (lp *Loadpoint) Update(sitePower float64, autoCharge, batteryBuffered bool)
lp.log.DEBUG.Printf("targetSoc reached: %.1f%% > %d%%", lp.vehicleSoc, lp.Soc.target)
err = lp.disableUnlessClimater()

// OCPP has priority over target charging
case lp.remoteControlled(loadpoint.RemoteHardDisable):
remoteDisabled = loadpoint.RemoteHardDisable
fallthrough
Expand Down Expand Up @@ -1565,7 +1511,7 @@ func (lp *Loadpoint) Update(sitePower float64, autoCharge, batteryBuffered bool)
targetCurrent := lp.pvMaxCurrent(mode, sitePower, batteryBuffered)

var required bool // false
if targetCurrent == 0 && lp.climateActive() {
if targetCurrent == 0 && lp.vehicleClimateActive() {
targetCurrent = lp.GetMinCurrent()
required = true
}
Expand Down
2 changes: 1 addition & 1 deletion core/loadpoint_test.go
Expand Up @@ -725,7 +725,7 @@ func TestSocPoll(t *testing.T) {
lp.Soc.Poll.Mode = tc.mode
lp.status = tc.status

res := lp.socPollAllowed()
res := lp.vehicleSocPollAllowed()
if res {
// mimic update outside of socPollAllowed
lp.socUpdated = clock.Now()
Expand Down
65 changes: 65 additions & 0 deletions core/loadpoint_vehicle.go
Expand Up @@ -313,3 +313,68 @@ func (lp *Loadpoint) vehicleOdometer() {
}
}
}

// vehicleClimatePollAllowed determines if polling depending on mode and connection status
func (lp *Loadpoint) vehicleClimatePollAllowed() bool {
switch {
case lp.Soc.Poll.Mode == pollCharging && lp.charging():
return true
case (lp.Soc.Poll.Mode == pollConnected || lp.Soc.Poll.Mode == pollAlways) && lp.connected():
return true
default:
return false
}
}

// vehicleSocPollAllowed validates charging state against polling mode
func (lp *Loadpoint) vehicleSocPollAllowed() bool {
// always update soc when charging
if lp.charging() {
lp.didChargeOnLastSocUpdate = true
return true
}

// update if connected and soc unknown
if lp.connected() && lp.socUpdated.IsZero() {
return true
}

remaining := lp.Soc.Poll.Interval - lp.clock.Since(lp.socUpdated)

honourUpdateInterval := lp.Soc.Poll.Mode == pollAlways ||
lp.connected() && (lp.Soc.Poll.Mode == pollConnected ||
// for mode charging allow one last soc update if did charge previously to not rely on soc estimator too much
lp.Soc.Poll.Mode == pollCharging && lp.didChargeOnLastSocUpdate)

if honourUpdateInterval {
if remaining > 0 {
lp.log.DEBUG.Printf("next soc poll remaining time: %v", remaining.Truncate(time.Second))
} else {
lp.didChargeOnLastSocUpdate = false
return true
}
}

return false
}

// vehicleClimateActive checks if vehicle has active climate request
func (lp *Loadpoint) vehicleClimateActive() bool {
if cl, ok := lp.vehicle.(api.VehicleClimater); ok && lp.vehicleClimatePollAllowed() {
active, err := cl.Climater()
if err == nil {
if active {
lp.log.DEBUG.Println("climater active")
}

lp.publish("climaterActive", active)
return active
}

if !errors.Is(err, api.ErrNotAvailable) {
lp.log.ERROR.Printf("climater: %v", err)
}
}

return false
}
4 changes: 2 additions & 2 deletions core/loadpoint_vehicle_test.go
Expand Up @@ -65,12 +65,12 @@ func TestPublishSocAndRange(t *testing.T) {
clck.Add(time.Hour)
lp.status = tc.status

assert.True(t, lp.socPollAllowed())
assert.True(t, lp.vehicleSocPollAllowed())
vehicle.EXPECT().Soc().Return(0.0, errors.New("foo"))
lp.publishSocAndRange()

clck.Add(time.Second)
assert.Equal(t, tc.allowed, lp.socPollAllowed())
assert.Equal(t, tc.allowed, lp.vehicleSocPollAllowed())
if tc.allowed {
vehicle.EXPECT().Soc().Return(0.0, errors.New("foo"))
}
Expand Down

0 comments on commit c5040d2

Please sign in to comment.