EEBus: fix invalid missing currents errors (#16223)

Fix a scenario were measurements were provided after the last limit change. But if the interval is set >15s and measurements even came in after 15s timeframe, this was considered to be invalid.

To fix this, every time measurements are provided and the limitUpdate timestamp is not zero, meaning a limit was set, consider data to available by setting limitUpdate to zero again.
This commit is contained in:
Andreas Linde 2024-09-20 03:19:43 +02:00 • committed by GitHub
parent 57e1de7a5d
commit 92c1aa36f4
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 106 additions and 13 deletions

View file

@ -39,8 +39,7 @@ type EEBus struct {
lp loadpoint.API
minMaxG func() (minMax, error)
limitUpdated time.Time // time of last limit change
currentsUpdated time.Time // time of last measurement
limitUpdated time.Time // time of last limit change
vasVW bool // wether the EVSE supports VW VAS with ISO15118-2
enabled bool
@ -127,8 +126,10 @@ func (c *EEBus) UseCaseEvent(device spineapi.DeviceRemoteInterface, entity spine
c.ev = nil
case evcem.DataUpdateCurrentPerPhase:
// do not use the timestamp of the measurement itself, as some devices don't provide it
c.currentsUpdated = time.Now()
// if there was a limit change before this measurement, set its update time to zero, as we got what we needed
if !c.limitUpdated.IsZero() {
c.limitUpdated = time.Time{}
}
}
}
@ -334,6 +335,9 @@ func (c *EEBus) writeCurrentLimitData(evEntity spineapi.EntityRemoteInterface, c
// if VAS VW is available, limits are completely covered by it
// this way evcc can fully control the charging behaviour
if c.writeLoadControlLimitsVASVW(evEntity, limits) {
c.mux.Lock()
defer c.mux.Unlock()
c.limitUpdated = time.Now()
return nil
}
@ -351,6 +355,9 @@ func (c *EEBus) writeCurrentLimitData(evEntity spineapi.EntityRemoteInterface, c
// set overload protection limits
_, err = c.uc.OpEV.WriteLoadControlLimits(evEntity, limits, nil)
if err == nil {
c.mux.Lock()
defer c.mux.Unlock()
c.limitUpdated = time.Now()
}
@ -567,20 +574,17 @@ func (c *EEBus) currents() (float64, float64, float64, error) {
}
c.mux.Lock()
ts := c.currentsUpdated
lu := c.limitUpdated
c.mux.Unlock()
// if there is no measurement data available within 15 seconds after the last limit change, return an error
// only consider it an error, if there is no measurement data since 15 seconds after the last limit change
// a measurement only being available before the last limit change is not an error, because that can happen often
if d := ts.Sub(c.limitUpdated); d > 15*time.Second && !c.limitUpdated.IsZero() {
// if the last limit update is not zero (meaning no measurement was provided yet)
// only consider this an error, if the last limit update is older than 15 seconds
// this covers the case where this function may be called shortly after setting a limit
// but too short for a measurement can even be received
if d := time.Now().Sub(lu); d > 15*time.Second && !lu.IsZero() {
return 0, 0, 0, api.ErrNotAvailable
}
// we got a valid measurement, reset the limitUpdate to zero
// otherwise frequent measurement updates will trigger an unwanted error as the time between those is > 15 seconds
c.limitUpdated = time.Time{}
res, err := c.uc.EvCem.CurrentPerPhase(evEntity)
if err != nil {
return 0, 0, 0, eebus.WrapError(err)

View file

@ -3,7 +3,9 @@ package charger
import (
"errors"
"testing"
"time"
evcemuc "github.com/enbility/eebus-go/usecases/cem/evcem"
"github.com/enbility/eebus-go/usecases/mocks"
spinemocks "github.com/enbility/spine-go/mocks"
"github.com/evcc-io/evcc/server/eebus"
@ -14,6 +16,93 @@ import (
"go.uber.org/mock/gomock"
)
// Test measurements updated after writing limits detction works
func TestEEBusNoCurrents(t *testing.T) {
evcc := mocks.NewCemEVCCInterface(t)
evcem := mocks.NewCemEVCEMInterface(t)
evEntity := spinemocks.NewEntityRemoteInterface(t)
eebus := &EEBus{
uc: &eebus.UseCasesEVSE{
EvCC: evcc,
EvCem: evcem,
},
ev: evEntity,
log: util.NewLogger("test"),
}
evcc.EXPECT().EVConnected(evEntity).Return(true)
evcem.EXPECT().IsScenarioAvailableAtEntity(evEntity, mock.Anything).Return(true)
// limit set 15:04:45, measurement receviced afterwards before calling currents
eebus.limitUpdated = time.Date(2024, 9, 16, 15, 4, 45, 0, time.UTC)
eebus.UseCaseEvent(nil, evEntity, evcemuc.DataUpdateCurrentPerPhase)
evcem.EXPECT().CurrentPerPhase(evEntity).Return([]float64{10.5, 10.5, 10.5}, nil).Once()
l1, l2, l3, err := eebus.currents()
require.NoError(t, err)
assert.Equal(t, 10.5, l1)
assert.Equal(t, 10.5, l2)
assert.Equal(t, 10.5, l3)
// limit set 15:05:09, measurement receviced afterwards before calling currents
eebus.limitUpdated = time.Date(2024, 9, 16, 15, 5, 9, 0, time.UTC)
eebus.UseCaseEvent(nil, evEntity, evcemuc.DataUpdateCurrentPerPhase)
evcem.EXPECT().CurrentPerPhase(evEntity).Return([]float64{6.6, 6.6, 6.6}, nil).Once()
l1, l2, l3, err = eebus.currents()
require.NoError(t, err)
assert.Equal(t, 6.6, l1)
assert.Equal(t, 6.6, l2)
assert.Equal(t, 6.6, l3)
// limit set 15:05:39, measurement received afterwards before calling currents
eebus.limitUpdated = time.Date(2024, 9, 16, 15, 5, 39, 0, time.UTC)
eebus.UseCaseEvent(nil, evEntity, evcemuc.DataUpdateCurrentPerPhase)
evcem.EXPECT().CurrentPerPhase(evEntity).Return([]float64{10.4, 10.5, 10.4}, nil).Once()
l1, l2, l3, err = eebus.currents()
require.NoError(t, err)
assert.Equal(t, 10.4, l1)
assert.Equal(t, 10.5, l2)
assert.Equal(t, 10.4, l3)
// limit set 15:06:09, measurement received afterwards before calling currents
eebus.limitUpdated = time.Date(2024, 9, 16, 15, 6, 9, 0, time.UTC)
eebus.UseCaseEvent(nil, evEntity, evcemuc.DataUpdateCurrentPerPhase)
evcem.EXPECT().CurrentPerPhase(evEntity).Return([]float64{10.4, 10.4, 10.4}, nil).Once()
l1, l2, l3, err = eebus.currents()
require.NoError(t, err)
assert.Equal(t, 10.4, l1)
assert.Equal(t, 10.4, l2)
assert.Equal(t, 10.4, l3)
// limit set 20 seconds ago, no measurement received yet
eebus.limitUpdated = time.Now().Add(-20 * time.Second)
l1, l2, l3, err = eebus.currents()
require.Error(t, err)
assert.Equal(t, 0.0, l1)
assert.Equal(t, 0.0, l2)
assert.Equal(t, 0.0, l3)
// now we got a measurement again
eebus.UseCaseEvent(nil, evEntity, evcemuc.DataUpdateCurrentPerPhase)
evcem.EXPECT().CurrentPerPhase(evEntity).Return([]float64{10.4, 10.4, 10.4}, nil).Once()
l1, l2, l3, err = eebus.currents()
require.NoError(t, err)
assert.Equal(t, 10.4, l1)
assert.Equal(t, 10.4, l2)
assert.Equal(t, 10.4, l3)
}
func TestEEBusIsCharging(t *testing.T) {
type limitStruct struct {
min, max, pause float64