diff --git a/charger/eebus.go b/charger/eebus.go index c13a23171..f53bbf64b 100644 --- a/charger/eebus.go +++ b/charger/eebus.go @@ -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) diff --git a/charger/eebus_test.go b/charger/eebus_test.go index 44aa34845..191b898c2 100644 --- a/charger/eebus_test.go +++ b/charger/eebus_test.go @@ -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