Skip to content

Commit 59e7016

Browse files
Properly handle invalid measurement data (#143)
If a measurement is provided with an ValueState != Normal, then the value should be ignored by the application. To handle that, the public API will then report an ErrDataInvalid error, so the application knows that the currently no valid data is available. This is the case for MPC and MGPC use cases
2 parents 8366d85 + fed478a commit 59e7016

File tree

7 files changed

+296
-9
lines changed

7 files changed

+296
-9
lines changed

api/errors.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,9 @@ var ErrMetadataNotAvailable = errors.New("meta data not available")
99
// ErrDataNotAvailable indicates that no data set is yet available
1010
var ErrDataNotAvailable = errors.New("data not available")
1111

12+
// ErrDataInvalid indicates that the currently available data is not valid and should be ignored
13+
var ErrDataInvalid = errors.New("data not valid")
14+
1215
// ErrDataForMetadataKeyNotFound indicates that no data item is found for the given key
1316
var ErrDataForMetadataKeyNotFound = errors.New("data for key not found")
1417

usecases/internal/measurement.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,12 @@ func MeasurementPhaseSpecificDataForFilter(
6262
}
6363
}
6464

65+
// if the value state is set and not normal, the value is not valid and should be ignored
66+
// therefore we return an error
67+
if item.ValueState != nil && *item.ValueState != model.MeasurementValueStateTypeNormal {
68+
return nil, api.ErrDataInvalid
69+
}
70+
6571
value := item.Value.GetValue()
6672

6773
result = append(result, value)

usecases/internal/measurement_test.go

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -161,4 +161,38 @@ func (s *InternalSuite) Test_MeasurementPhaseSpecificDataForFilter() {
161161
)
162162
assert.Nil(s.T(), err)
163163
assert.Equal(s.T(), []float64{10, 10, 10}, data)
164+
165+
measData = &model.MeasurementListDataType{
166+
MeasurementData: []model.MeasurementDataType{
167+
{
168+
MeasurementId: util.Ptr(model.MeasurementIdType(10)),
169+
},
170+
{
171+
MeasurementId: util.Ptr(model.MeasurementIdType(0)),
172+
Value: model.NewScaledNumberType(10),
173+
ValueState: util.Ptr(model.MeasurementValueStateTypeError),
174+
},
175+
{
176+
MeasurementId: util.Ptr(model.MeasurementIdType(1)),
177+
Value: model.NewScaledNumberType(10),
178+
},
179+
{
180+
MeasurementId: util.Ptr(model.MeasurementIdType(2)),
181+
Value: model.NewScaledNumberType(10),
182+
},
183+
},
184+
}
185+
186+
_, fErr = rFeature.UpdateData(true, model.FunctionTypeMeasurementListData, measData, nil, nil)
187+
assert.Nil(s.T(), fErr)
188+
189+
data, err = MeasurementPhaseSpecificDataForFilter(
190+
s.localEntity,
191+
s.monitoredEntity,
192+
filter,
193+
energyDirection,
194+
ucapi.PhaseNameMapping,
195+
)
196+
assert.NotNil(s.T(), err)
197+
assert.Nil(s.T(), data)
164198
}

usecases/ma/mgcp/public.go

Lines changed: 58 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -15,18 +15,14 @@ import (
1515
// return the current power limitation factor
1616
//
1717
// possible errors:
18-
// - ErrDataNotAvailable if no such limit is (yet) available
18+
// - ErrDataNotAvailable if no such value is (yet) available
19+
// - ErrDataInvalid if the currently available data is invalid and should be ignored
1920
// - and others
2021
func (e *MGCP) PowerLimitationFactor(entity spineapi.EntityRemoteInterface) (float64, error) {
2122
if !e.IsCompatibleEntityType(entity) {
2223
return 0, api.ErrNoCompatibleEntity
2324
}
2425

25-
measurement, err := client.NewMeasurement(e.LocalEntity, entity)
26-
if err != nil || measurement == nil {
27-
return 0, err
28-
}
29-
3026
keyname := model.DeviceConfigurationKeyNameTypePvCurtailmentLimitFactor
3127

3228
deviceConfiguration, err := client.NewDeviceConfiguration(e.LocalEntity, entity)
@@ -58,6 +54,11 @@ func (e *MGCP) PowerLimitationFactor(entity spineapi.EntityRemoteInterface) (flo
5854
//
5955
// - positive values are used for consumption
6056
// - negative values are used for production
57+
//
58+
// possible errors:
59+
// - ErrDataNotAvailable if no such value is (yet) available
60+
// - ErrDataInvalid if the currently available data is invalid and should be ignored
61+
// - and others
6162
func (e *MGCP) Power(entity spineapi.EntityRemoteInterface) (float64, error) {
6263
if !e.IsCompatibleEntityType(entity) {
6364
return 0, api.ErrNoCompatibleEntity
@@ -69,7 +70,11 @@ func (e *MGCP) Power(entity spineapi.EntityRemoteInterface) (float64, error) {
6970
ScopeType: util.Ptr(model.ScopeTypeTypeACPowerTotal),
7071
}
7172
data, err := internal.MeasurementPhaseSpecificDataForFilter(e.LocalEntity, entity, filter, model.EnergyDirectionTypeConsume, nil)
72-
if err != nil || len(data) != 1 {
73+
if err != nil {
74+
return 0, err
75+
}
76+
77+
if len(data) != 1 {
7378
return 0, api.ErrDataNotAvailable
7479
}
7580

@@ -81,6 +86,11 @@ func (e *MGCP) Power(entity spineapi.EntityRemoteInterface) (float64, error) {
8186
// return the total feed in energy at the grid connection point
8287
//
8388
// - negative values are used for production
89+
//
90+
// possible errors:
91+
// - ErrDataNotAvailable if no such value is (yet) available
92+
// - ErrDataInvalid if the currently available data is invalid and should be ignored
93+
// - and others
8494
func (e *MGCP) EnergyFeedIn(entity spineapi.EntityRemoteInterface) (float64, error) {
8595
if !e.IsCompatibleEntityType(entity) {
8696
return 0, api.ErrNoCompatibleEntity
@@ -100,6 +110,13 @@ func (e *MGCP) EnergyFeedIn(entity spineapi.EntityRemoteInterface) (float64, err
100110
if err != nil || len(result) == 0 || result[0].Value == nil {
101111
return 0, api.ErrDataNotAvailable
102112
}
113+
114+
// if the value state is set and not normal, the value is not valid and should be ignored
115+
// therefore we return an error
116+
if result[0].ValueState != nil && *result[0].ValueState != model.MeasurementValueStateTypeNormal {
117+
return 0, api.ErrDataInvalid
118+
}
119+
103120
return result[0].Value.GetValue(), nil
104121
}
105122

@@ -108,6 +125,11 @@ func (e *MGCP) EnergyFeedIn(entity spineapi.EntityRemoteInterface) (float64, err
108125
// return the total consumption energy at the grid connection point
109126
//
110127
// - positive values are used for consumption
128+
//
129+
// possible errors:
130+
// - ErrDataNotAvailable if no such value is (yet) available
131+
// - ErrDataInvalid if the currently available data is invalid and should be ignored
132+
// - and others
111133
func (e *MGCP) EnergyConsumed(entity spineapi.EntityRemoteInterface) (float64, error) {
112134
if !e.IsCompatibleEntityType(entity) {
113135
return 0, api.ErrNoCompatibleEntity
@@ -127,6 +149,13 @@ func (e *MGCP) EnergyConsumed(entity spineapi.EntityRemoteInterface) (float64, e
127149
if err != nil || len(result) == 0 || result[0].Value == nil {
128150
return 0, api.ErrDataNotAvailable
129151
}
152+
153+
// if the value state is set and not normal, the value is not valid and should be ignored
154+
// therefore we return an error
155+
if result[0].ValueState != nil && *result[0].ValueState != model.MeasurementValueStateTypeNormal {
156+
return 0, api.ErrDataInvalid
157+
}
158+
130159
return result[0].Value.GetValue(), nil
131160
}
132161

@@ -136,6 +165,11 @@ func (e *MGCP) EnergyConsumed(entity spineapi.EntityRemoteInterface) (float64, e
136165
//
137166
// - positive values are used for consumption
138167
// - negative values are used for production
168+
//
169+
// possible errors:
170+
// - ErrDataNotAvailable if no such value is (yet) available
171+
// - ErrDataInvalid if the currently available data is invalid and should be ignored
172+
// - and others
139173
func (e *MGCP) CurrentPerPhase(entity spineapi.EntityRemoteInterface) ([]float64, error) {
140174
if !e.IsCompatibleEntityType(entity) {
141175
return nil, api.ErrNoCompatibleEntity
@@ -152,6 +186,11 @@ func (e *MGCP) CurrentPerPhase(entity spineapi.EntityRemoteInterface) ([]float64
152186
// Scenario 6
153187

154188
// return the voltage phase details at the grid connection point
189+
//
190+
// possible errors:
191+
// - ErrDataNotAvailable if no such value is (yet) available
192+
// - ErrDataInvalid if the currently available data is invalid and should be ignored
193+
// - and others
155194
func (e *MGCP) VoltagePerPhase(entity spineapi.EntityRemoteInterface) ([]float64, error) {
156195
if !e.IsCompatibleEntityType(entity) {
157196
return nil, api.ErrNoCompatibleEntity
@@ -168,6 +207,11 @@ func (e *MGCP) VoltagePerPhase(entity spineapi.EntityRemoteInterface) ([]float64
168207
// Scenario 7
169208

170209
// return frequency at the grid connection point
210+
//
211+
// possible errors:
212+
// - ErrDataNotAvailable if no such value is (yet) available
213+
// - ErrDataInvalid if the currently available data is invalid and should be ignored
214+
// - and others
171215
func (e *MGCP) Frequency(entity spineapi.EntityRemoteInterface) (float64, error) {
172216
if !e.IsCompatibleEntityType(entity) {
173217
return 0, api.ErrNoCompatibleEntity
@@ -187,5 +231,12 @@ func (e *MGCP) Frequency(entity spineapi.EntityRemoteInterface) (float64, error)
187231
if err != nil || len(result) == 0 || result[0].Value == nil {
188232
return 0, api.ErrDataNotAvailable
189233
}
234+
235+
// if the value state is set and not normal, the value is not valid and should be ignored
236+
// therefore we return an error
237+
if result[0].ValueState != nil && *result[0].ValueState != model.MeasurementValueStateTypeNormal {
238+
return 0, api.ErrDataInvalid
239+
}
240+
190241
return result[0].Value.GetValue(), nil
191242
}

usecases/ma/mgcp/public_test.go

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -173,6 +173,23 @@ func (s *GcpMGCPSuite) Test_EnergyFeedIn() {
173173
data, err = s.sut.EnergyFeedIn(s.smgwEntity)
174174
assert.Nil(s.T(), err)
175175
assert.Equal(s.T(), 10.0, data)
176+
177+
measData = &model.MeasurementListDataType{
178+
MeasurementData: []model.MeasurementDataType{
179+
{
180+
MeasurementId: util.Ptr(model.MeasurementIdType(0)),
181+
Value: model.NewScaledNumberType(10),
182+
ValueState: util.Ptr(model.MeasurementValueStateTypeError),
183+
},
184+
},
185+
}
186+
187+
_, fErr = rFeature.UpdateData(true, model.FunctionTypeMeasurementListData, measData, nil, nil)
188+
assert.Nil(s.T(), fErr)
189+
190+
data, err = s.sut.EnergyFeedIn(s.smgwEntity)
191+
assert.NotNil(s.T(), err)
192+
assert.Equal(s.T(), 0.0, data)
176193
}
177194

178195
func (s *GcpMGCPSuite) Test_EnergyConsumed() {
@@ -218,6 +235,23 @@ func (s *GcpMGCPSuite) Test_EnergyConsumed() {
218235
data, err = s.sut.EnergyConsumed(s.smgwEntity)
219236
assert.Nil(s.T(), err)
220237
assert.Equal(s.T(), 10.0, data)
238+
239+
measData = &model.MeasurementListDataType{
240+
MeasurementData: []model.MeasurementDataType{
241+
{
242+
MeasurementId: util.Ptr(model.MeasurementIdType(0)),
243+
Value: model.NewScaledNumberType(10),
244+
ValueState: util.Ptr(model.MeasurementValueStateTypeError),
245+
},
246+
},
247+
}
248+
249+
_, fErr = rFeature.UpdateData(true, model.FunctionTypeMeasurementListData, measData, nil, nil)
250+
assert.Nil(s.T(), fErr)
251+
252+
data, err = s.sut.EnergyConsumed(s.smgwEntity)
253+
assert.NotNil(s.T(), err)
254+
assert.Equal(s.T(), 0.0, data)
221255
}
222256

223257
func (s *GcpMGCPSuite) Test_CurrentPerPhase() {
@@ -461,4 +495,21 @@ func (s *GcpMGCPSuite) Test_Frequency() {
461495
data, err = s.sut.Frequency(s.smgwEntity)
462496
assert.Nil(s.T(), err)
463497
assert.Equal(s.T(), 50.0, data)
498+
499+
measData = &model.MeasurementListDataType{
500+
MeasurementData: []model.MeasurementDataType{
501+
{
502+
MeasurementId: util.Ptr(model.MeasurementIdType(0)),
503+
Value: model.NewScaledNumberType(50),
504+
ValueState: util.Ptr(model.MeasurementValueStateTypeError),
505+
},
506+
},
507+
}
508+
509+
_, fErr = rFeature.UpdateData(true, model.FunctionTypeMeasurementListData, measData, nil, nil)
510+
assert.Nil(s.T(), fErr)
511+
512+
data, err = s.sut.Frequency(s.smgwEntity)
513+
assert.NotNil(s.T(), err)
514+
assert.Equal(s.T(), 0.0, data)
464515
}

0 commit comments

Comments
 (0)