From fed478ac01279d614bb65f5a9e1dbef27fab2076 Mon Sep 17 00:00:00 2001 From: Andreas Linde Date: Wed, 20 Nov 2024 19:50:08 +0100 Subject: [PATCH] Add missing MPC and MGPC cases --- usecases/ma/mgcp/public.go | 65 +++++++++++++++++++--- usecases/ma/mgcp/public_test.go | 51 ++++++++++++++++++ usecases/ma/mpc/public.go | 50 ++++++++++++++++- usecases/ma/mpc/public_test.go | 96 +++++++++++++++++++++++++++++++++ 4 files changed, 253 insertions(+), 9 deletions(-) diff --git a/usecases/ma/mgcp/public.go b/usecases/ma/mgcp/public.go index f966e3f3..357ad52e 100644 --- a/usecases/ma/mgcp/public.go +++ b/usecases/ma/mgcp/public.go @@ -15,18 +15,14 @@ import ( // return the current power limitation factor // // possible errors: -// - ErrDataNotAvailable if no such limit is (yet) available +// - ErrDataNotAvailable if no such value is (yet) available +// - ErrDataInvalid if the currently available data is invalid and should be ignored // - and others func (e *MGCP) PowerLimitationFactor(entity spineapi.EntityRemoteInterface) (float64, error) { if !e.IsCompatibleEntityType(entity) { return 0, api.ErrNoCompatibleEntity } - measurement, err := client.NewMeasurement(e.LocalEntity, entity) - if err != nil || measurement == nil { - return 0, err - } - keyname := model.DeviceConfigurationKeyNameTypePvCurtailmentLimitFactor deviceConfiguration, err := client.NewDeviceConfiguration(e.LocalEntity, entity) @@ -58,6 +54,11 @@ func (e *MGCP) PowerLimitationFactor(entity spineapi.EntityRemoteInterface) (flo // // - positive values are used for consumption // - negative values are used for production +// +// possible errors: +// - ErrDataNotAvailable if no such value is (yet) available +// - ErrDataInvalid if the currently available data is invalid and should be ignored +// - and others func (e *MGCP) Power(entity spineapi.EntityRemoteInterface) (float64, error) { if !e.IsCompatibleEntityType(entity) { return 0, api.ErrNoCompatibleEntity @@ -69,7 +70,11 @@ func (e *MGCP) Power(entity spineapi.EntityRemoteInterface) (float64, error) { ScopeType: util.Ptr(model.ScopeTypeTypeACPowerTotal), } data, err := internal.MeasurementPhaseSpecificDataForFilter(e.LocalEntity, entity, filter, model.EnergyDirectionTypeConsume, nil) - if err != nil || len(data) != 1 { + if err != nil { + return 0, err + } + + if len(data) != 1 { return 0, api.ErrDataNotAvailable } @@ -81,6 +86,11 @@ func (e *MGCP) Power(entity spineapi.EntityRemoteInterface) (float64, error) { // return the total feed in energy at the grid connection point // // - negative values are used for production +// +// possible errors: +// - ErrDataNotAvailable if no such value is (yet) available +// - ErrDataInvalid if the currently available data is invalid and should be ignored +// - and others func (e *MGCP) EnergyFeedIn(entity spineapi.EntityRemoteInterface) (float64, error) { if !e.IsCompatibleEntityType(entity) { return 0, api.ErrNoCompatibleEntity @@ -100,6 +110,13 @@ func (e *MGCP) EnergyFeedIn(entity spineapi.EntityRemoteInterface) (float64, err if err != nil || len(result) == 0 || result[0].Value == nil { return 0, api.ErrDataNotAvailable } + + // if the value state is set and not normal, the value is not valid and should be ignored + // therefore we return an error + if result[0].ValueState != nil && *result[0].ValueState != model.MeasurementValueStateTypeNormal { + return 0, api.ErrDataInvalid + } + return result[0].Value.GetValue(), nil } @@ -108,6 +125,11 @@ func (e *MGCP) EnergyFeedIn(entity spineapi.EntityRemoteInterface) (float64, err // return the total consumption energy at the grid connection point // // - positive values are used for consumption +// +// possible errors: +// - ErrDataNotAvailable if no such value is (yet) available +// - ErrDataInvalid if the currently available data is invalid and should be ignored +// - and others func (e *MGCP) EnergyConsumed(entity spineapi.EntityRemoteInterface) (float64, error) { if !e.IsCompatibleEntityType(entity) { return 0, api.ErrNoCompatibleEntity @@ -127,6 +149,13 @@ func (e *MGCP) EnergyConsumed(entity spineapi.EntityRemoteInterface) (float64, e if err != nil || len(result) == 0 || result[0].Value == nil { return 0, api.ErrDataNotAvailable } + + // if the value state is set and not normal, the value is not valid and should be ignored + // therefore we return an error + if result[0].ValueState != nil && *result[0].ValueState != model.MeasurementValueStateTypeNormal { + return 0, api.ErrDataInvalid + } + return result[0].Value.GetValue(), nil } @@ -136,6 +165,11 @@ func (e *MGCP) EnergyConsumed(entity spineapi.EntityRemoteInterface) (float64, e // // - positive values are used for consumption // - negative values are used for production +// +// possible errors: +// - ErrDataNotAvailable if no such value is (yet) available +// - ErrDataInvalid if the currently available data is invalid and should be ignored +// - and others func (e *MGCP) CurrentPerPhase(entity spineapi.EntityRemoteInterface) ([]float64, error) { if !e.IsCompatibleEntityType(entity) { return nil, api.ErrNoCompatibleEntity @@ -152,6 +186,11 @@ func (e *MGCP) CurrentPerPhase(entity spineapi.EntityRemoteInterface) ([]float64 // Scenario 6 // return the voltage phase details at the grid connection point +// +// possible errors: +// - ErrDataNotAvailable if no such value is (yet) available +// - ErrDataInvalid if the currently available data is invalid and should be ignored +// - and others func (e *MGCP) VoltagePerPhase(entity spineapi.EntityRemoteInterface) ([]float64, error) { if !e.IsCompatibleEntityType(entity) { return nil, api.ErrNoCompatibleEntity @@ -168,6 +207,11 @@ func (e *MGCP) VoltagePerPhase(entity spineapi.EntityRemoteInterface) ([]float64 // Scenario 7 // return frequency at the grid connection point +// +// possible errors: +// - ErrDataNotAvailable if no such value is (yet) available +// - ErrDataInvalid if the currently available data is invalid and should be ignored +// - and others func (e *MGCP) Frequency(entity spineapi.EntityRemoteInterface) (float64, error) { if !e.IsCompatibleEntityType(entity) { return 0, api.ErrNoCompatibleEntity @@ -187,5 +231,12 @@ func (e *MGCP) Frequency(entity spineapi.EntityRemoteInterface) (float64, error) if err != nil || len(result) == 0 || result[0].Value == nil { return 0, api.ErrDataNotAvailable } + + // if the value state is set and not normal, the value is not valid and should be ignored + // therefore we return an error + if result[0].ValueState != nil && *result[0].ValueState != model.MeasurementValueStateTypeNormal { + return 0, api.ErrDataInvalid + } + return result[0].Value.GetValue(), nil } diff --git a/usecases/ma/mgcp/public_test.go b/usecases/ma/mgcp/public_test.go index 4ded2704..436af816 100644 --- a/usecases/ma/mgcp/public_test.go +++ b/usecases/ma/mgcp/public_test.go @@ -173,6 +173,23 @@ func (s *GcpMGCPSuite) Test_EnergyFeedIn() { data, err = s.sut.EnergyFeedIn(s.smgwEntity) assert.Nil(s.T(), err) assert.Equal(s.T(), 10.0, data) + + measData = &model.MeasurementListDataType{ + MeasurementData: []model.MeasurementDataType{ + { + MeasurementId: util.Ptr(model.MeasurementIdType(0)), + Value: model.NewScaledNumberType(10), + ValueState: util.Ptr(model.MeasurementValueStateTypeError), + }, + }, + } + + _, fErr = rFeature.UpdateData(true, model.FunctionTypeMeasurementListData, measData, nil, nil) + assert.Nil(s.T(), fErr) + + data, err = s.sut.EnergyFeedIn(s.smgwEntity) + assert.NotNil(s.T(), err) + assert.Equal(s.T(), 0.0, data) } func (s *GcpMGCPSuite) Test_EnergyConsumed() { @@ -218,6 +235,23 @@ func (s *GcpMGCPSuite) Test_EnergyConsumed() { data, err = s.sut.EnergyConsumed(s.smgwEntity) assert.Nil(s.T(), err) assert.Equal(s.T(), 10.0, data) + + measData = &model.MeasurementListDataType{ + MeasurementData: []model.MeasurementDataType{ + { + MeasurementId: util.Ptr(model.MeasurementIdType(0)), + Value: model.NewScaledNumberType(10), + ValueState: util.Ptr(model.MeasurementValueStateTypeError), + }, + }, + } + + _, fErr = rFeature.UpdateData(true, model.FunctionTypeMeasurementListData, measData, nil, nil) + assert.Nil(s.T(), fErr) + + data, err = s.sut.EnergyConsumed(s.smgwEntity) + assert.NotNil(s.T(), err) + assert.Equal(s.T(), 0.0, data) } func (s *GcpMGCPSuite) Test_CurrentPerPhase() { @@ -461,4 +495,21 @@ func (s *GcpMGCPSuite) Test_Frequency() { data, err = s.sut.Frequency(s.smgwEntity) assert.Nil(s.T(), err) assert.Equal(s.T(), 50.0, data) + + measData = &model.MeasurementListDataType{ + MeasurementData: []model.MeasurementDataType{ + { + MeasurementId: util.Ptr(model.MeasurementIdType(0)), + Value: model.NewScaledNumberType(50), + ValueState: util.Ptr(model.MeasurementValueStateTypeError), + }, + }, + } + + _, fErr = rFeature.UpdateData(true, model.FunctionTypeMeasurementListData, measData, nil, nil) + assert.Nil(s.T(), fErr) + + data, err = s.sut.Frequency(s.smgwEntity) + assert.NotNil(s.T(), err) + assert.Equal(s.T(), 0.0, data) } diff --git a/usecases/ma/mpc/public.go b/usecases/ma/mpc/public.go index 29a22c82..6df7fbfc 100644 --- a/usecases/ma/mpc/public.go +++ b/usecases/ma/mpc/public.go @@ -15,7 +15,8 @@ import ( // return the momentary active power consumption or production // // possible errors: -// - ErrDataNotAvailable if no such limit is (yet) available +// - ErrDataNotAvailable if no such value is (yet) available +// - ErrDataInvalid if the currently available data is invalid and should be ignored // - and others func (e *MPC) Power(entity spineapi.EntityRemoteInterface) (float64, error) { if !e.IsCompatibleEntityType(entity) { @@ -34,13 +35,15 @@ func (e *MPC) Power(entity spineapi.EntityRemoteInterface) (float64, error) { if len(values) != 1 { return 0, api.ErrDataNotAvailable } + return values[0], nil } // return the momentary active phase specific power consumption or production per phase // // possible errors: -// - ErrDataNotAvailable if no such limit is (yet) available +// - ErrDataNotAvailable if no such value is (yet) available +// - ErrDataInvalid if the currently available data is invalid and should be ignored // - and others func (e *MPC) PowerPerPhase(entity spineapi.EntityRemoteInterface) ([]float64, error) { if !e.IsCompatibleEntityType(entity) { @@ -60,6 +63,11 @@ func (e *MPC) PowerPerPhase(entity spineapi.EntityRemoteInterface) ([]float64, e // return the total consumption energy // // - positive values are used for consumption +// +// possible errors: +// - ErrDataNotAvailable if no such value is (yet) available +// - ErrDataInvalid if the currently available data is invalid and should be ignored +// - and others func (e *MPC) EnergyConsumed(entity spineapi.EntityRemoteInterface) (float64, error) { if !e.IsCompatibleEntityType(entity) { return 0, api.ErrNoCompatibleEntity @@ -86,12 +94,23 @@ func (e *MPC) EnergyConsumed(entity spineapi.EntityRemoteInterface) (float64, er return 0, api.ErrDataNotAvailable } + // if the value state is set and not normal, the value is not valid and should be ignored + // therefore we return an error + if values[0].ValueState != nil && *values[0].ValueState != model.MeasurementValueStateTypeNormal { + return 0, api.ErrDataInvalid + } + return value.GetValue(), nil } // return the total feed in energy // // - negative values are used for production +// +// possible errors: +// - ErrDataNotAvailable if no such value is (yet) available +// - ErrDataInvalid if the currently available data is invalid and should be ignored +// - and others func (e *MPC) EnergyProduced(entity spineapi.EntityRemoteInterface) (float64, error) { if !e.IsCompatibleEntityType(entity) { return 0, api.ErrNoCompatibleEntity @@ -118,6 +137,12 @@ func (e *MPC) EnergyProduced(entity spineapi.EntityRemoteInterface) (float64, er return 0, api.ErrDataNotAvailable } + // if the value state is set and not normal, the value is not valid and should be ignored + // therefore we return an error + if values[0].ValueState != nil && *values[0].ValueState != model.MeasurementValueStateTypeNormal { + return 0, api.ErrDataInvalid + } + return value.GetValue(), nil } @@ -127,6 +152,11 @@ func (e *MPC) EnergyProduced(entity spineapi.EntityRemoteInterface) (float64, er // // - positive values are used for consumption // - negative values are used for production +// +// possible errors: +// - ErrDataNotAvailable if no such value is (yet) available +// - ErrDataInvalid if the currently available data is invalid and should be ignored +// - and others func (e *MPC) CurrentPerPhase(entity spineapi.EntityRemoteInterface) ([]float64, error) { if !e.IsCompatibleEntityType(entity) { return nil, api.ErrNoCompatibleEntity @@ -143,6 +173,11 @@ func (e *MPC) CurrentPerPhase(entity spineapi.EntityRemoteInterface) ([]float64, // Scenario 4 // return the phase specific voltage details +// +// possible errors: +// - ErrDataNotAvailable if no such value is (yet) available +// - ErrDataInvalid if the currently available data is invalid and should be ignored +// - and others func (e *MPC) VoltagePerPhase(entity spineapi.EntityRemoteInterface) ([]float64, error) { if !e.IsCompatibleEntityType(entity) { return nil, api.ErrNoCompatibleEntity @@ -159,6 +194,11 @@ func (e *MPC) VoltagePerPhase(entity spineapi.EntityRemoteInterface) ([]float64, // Scenario 5 // return frequency +// +// possible errors: +// - ErrDataNotAvailable if no such value is (yet) available +// - ErrDataInvalid if the currently available data is invalid and should be ignored +// - and others func (e *MPC) Frequency(entity spineapi.EntityRemoteInterface) (float64, error) { if !e.IsCompatibleEntityType(entity) { return 0, api.ErrNoCompatibleEntity @@ -179,6 +219,12 @@ func (e *MPC) Frequency(entity spineapi.EntityRemoteInterface) (float64, error) return 0, api.ErrDataNotAvailable } + // if the value state is set and not normal, the value is not valid and should be ignored + // therefore we return an error + if data[0].ValueState != nil && *data[0].ValueState != model.MeasurementValueStateTypeNormal { + return 0, api.ErrDataInvalid + } + // take the first item value := data[0].Value diff --git a/usecases/ma/mpc/public_test.go b/usecases/ma/mpc/public_test.go index cb6a8708..45622fe2 100644 --- a/usecases/ma/mpc/public_test.go +++ b/usecases/ma/mpc/public_test.go @@ -218,6 +218,21 @@ func (s *MaMPCSuite) Test_EnergyConsumed() { assert.Equal(s.T(), 0.0, data) measData := &model.MeasurementListDataType{ + MeasurementData: []model.MeasurementDataType{ + { + MeasurementId: util.Ptr(model.MeasurementIdType(0)), + }, + }, + } + + _, fErr = rFeature.UpdateData(true, model.FunctionTypeMeasurementListData, measData, nil, nil) + assert.Nil(s.T(), fErr) + + data, err = s.sut.EnergyConsumed(s.monitoredEntity) + assert.NotNil(s.T(), err) + assert.Equal(s.T(), 0.0, data) + + measData = &model.MeasurementListDataType{ MeasurementData: []model.MeasurementDataType{ { MeasurementId: util.Ptr(model.MeasurementIdType(0)), @@ -232,6 +247,23 @@ func (s *MaMPCSuite) Test_EnergyConsumed() { data, err = s.sut.EnergyConsumed(s.monitoredEntity) assert.Nil(s.T(), err) assert.Equal(s.T(), 10.0, data) + + measData = &model.MeasurementListDataType{ + MeasurementData: []model.MeasurementDataType{ + { + MeasurementId: util.Ptr(model.MeasurementIdType(0)), + Value: model.NewScaledNumberType(10), + ValueState: util.Ptr(model.MeasurementValueStateTypeError), + }, + }, + } + + _, fErr = rFeature.UpdateData(true, model.FunctionTypeMeasurementListData, measData, nil, nil) + assert.Nil(s.T(), fErr) + + data, err = s.sut.EnergyConsumed(s.monitoredEntity) + assert.NotNil(s.T(), err) + assert.Equal(s.T(), 0.0, data) } func (s *MaMPCSuite) Test_EnergyProduced() { @@ -263,6 +295,21 @@ func (s *MaMPCSuite) Test_EnergyProduced() { assert.Equal(s.T(), 0.0, data) measData := &model.MeasurementListDataType{ + MeasurementData: []model.MeasurementDataType{ + { + MeasurementId: util.Ptr(model.MeasurementIdType(0)), + }, + }, + } + + _, fErr = rFeature.UpdateData(true, model.FunctionTypeMeasurementListData, measData, nil, nil) + assert.Nil(s.T(), fErr) + + data, err = s.sut.EnergyProduced(s.monitoredEntity) + assert.NotNil(s.T(), err) + assert.Equal(s.T(), 0.0, data) + + measData = &model.MeasurementListDataType{ MeasurementData: []model.MeasurementDataType{ { MeasurementId: util.Ptr(model.MeasurementIdType(0)), @@ -277,6 +324,23 @@ func (s *MaMPCSuite) Test_EnergyProduced() { data, err = s.sut.EnergyProduced(s.monitoredEntity) assert.Nil(s.T(), err) assert.Equal(s.T(), 10.0, data) + + measData = &model.MeasurementListDataType{ + MeasurementData: []model.MeasurementDataType{ + { + MeasurementId: util.Ptr(model.MeasurementIdType(0)), + Value: model.NewScaledNumberType(10), + ValueState: util.Ptr(model.MeasurementValueStateTypeError), + }, + }, + } + + _, fErr = rFeature.UpdateData(true, model.FunctionTypeMeasurementListData, measData, nil, nil) + assert.Nil(s.T(), fErr) + + data, err = s.sut.EnergyProduced(s.monitoredEntity) + assert.NotNil(s.T(), err) + assert.Equal(s.T(), 0.0, data) } func (s *MaMPCSuite) Test_CurrentPerPhase() { @@ -506,6 +570,21 @@ func (s *MaMPCSuite) Test_Frequency() { assert.Equal(s.T(), 0.0, data) measData := &model.MeasurementListDataType{ + MeasurementData: []model.MeasurementDataType{ + { + MeasurementId: util.Ptr(model.MeasurementIdType(0)), + }, + }, + } + + _, fErr = rFeature.UpdateData(true, model.FunctionTypeMeasurementListData, measData, nil, nil) + assert.Nil(s.T(), fErr) + + data, err = s.sut.Frequency(s.monitoredEntity) + assert.NotNil(s.T(), err) + assert.Equal(s.T(), 0.0, data) + + measData = &model.MeasurementListDataType{ MeasurementData: []model.MeasurementDataType{ { MeasurementId: util.Ptr(model.MeasurementIdType(0)), @@ -520,4 +599,21 @@ func (s *MaMPCSuite) Test_Frequency() { data, err = s.sut.Frequency(s.monitoredEntity) assert.Nil(s.T(), err) assert.Equal(s.T(), 50.0, data) + + measData = &model.MeasurementListDataType{ + MeasurementData: []model.MeasurementDataType{ + { + MeasurementId: util.Ptr(model.MeasurementIdType(0)), + Value: model.NewScaledNumberType(50), + ValueState: util.Ptr(model.MeasurementValueStateTypeError), + }, + }, + } + + _, fErr = rFeature.UpdateData(true, model.FunctionTypeMeasurementListData, measData, nil, nil) + assert.Nil(s.T(), fErr) + + data, err = s.sut.Frequency(s.monitoredEntity) + assert.NotNil(s.T(), err) + assert.Equal(s.T(), 0.0, data) }