diff --git a/features/internal/deviceconfiguration.go b/features/internal/deviceconfiguration.go index a8ea257d..13850e97 100644 --- a/features/internal/deviceconfiguration.go +++ b/features/internal/deviceconfiguration.go @@ -51,7 +51,7 @@ func (d *DeviceConfigurationCommon) CheckEventPayloadDataForFilter(payloadData a for _, item := range data.DeviceConfigurationKeyValueData { if item.KeyId != nil && - *item.KeyId == *desc.KeyId || + *item.KeyId == *desc.KeyId && item.Value != nil { return true } diff --git a/usecases/eg/lpc/events.go b/usecases/eg/lpc/events.go index ef2a89bb..3e13824b 100644 --- a/usecases/eg/lpc/events.go +++ b/usecases/eg/lpc/events.go @@ -133,8 +133,11 @@ func (e *LPC) loadControlLimitDataUpdate(payload spineapi.EventPayload) { LimitDirection: util.Ptr(model.EnergyDirectionTypeConsume), ScopeType: util.Ptr(model.ScopeTypeTypeActivePowerLimit), } - if lc.CheckEventPayloadDataForFilter(payload.Data, filter) && e.EventCB != nil { - e.EventCB(payload.Ski, payload.Device, payload.Entity, DataUpdateLimit) + if lc.CheckEventPayloadDataForFilter(payload.Data, filter) { + // Only fire event if public method succeeds (data is valid and retrievable) + if _, err := e.ConsumptionLimit(payload.Entity); err == nil && e.EventCB != nil { + e.EventCB(payload.Ski, payload.Device, payload.Entity, DataUpdateLimit) + } } } } @@ -155,12 +158,18 @@ func (e *LPC) configurationDataUpdate(payload spineapi.EventPayload) { filter := model.DeviceConfigurationKeyValueDescriptionDataType{ KeyName: util.Ptr(model.DeviceConfigurationKeyNameTypeFailsafeConsumptionActivePowerLimit), } - if dc.CheckEventPayloadDataForFilter(payload.Data, filter) && e.EventCB != nil { - e.EventCB(payload.Ski, payload.Device, payload.Entity, DataUpdateFailsafeConsumptionActivePowerLimit) + if dc.CheckEventPayloadDataForFilter(payload.Data, filter) { + // Only fire event if public method succeeds (data is valid and retrievable) + if _, err := e.FailsafeConsumptionActivePowerLimit(payload.Entity); err == nil && e.EventCB != nil { + e.EventCB(payload.Ski, payload.Device, payload.Entity, DataUpdateFailsafeConsumptionActivePowerLimit) + } } filter.KeyName = util.Ptr(model.DeviceConfigurationKeyNameTypeFailsafeDurationMinimum) - if dc.CheckEventPayloadDataForFilter(payload.Data, filter) && e.EventCB != nil { - e.EventCB(payload.Ski, payload.Device, payload.Entity, DataUpdateFailsafeDurationMinimum) + if dc.CheckEventPayloadDataForFilter(payload.Data, filter) { + // Only fire event if public method succeeds (data is valid and retrievable) + if _, err := e.FailsafeDurationMinimum(payload.Entity); err == nil && e.EventCB != nil { + e.EventCB(payload.Ski, payload.Device, payload.Entity, DataUpdateFailsafeDurationMinimum) + } } } } diff --git a/usecases/eg/lpc/events_test.go b/usecases/eg/lpc/events_test.go index 023d5c6f..c8681299 100644 --- a/usecases/eg/lpc/events_test.go +++ b/usecases/eg/lpc/events_test.go @@ -1,6 +1,8 @@ package lpc import ( + "time" + spineapi "github.com/enbility/spine-go/api" "github.com/enbility/spine-go/model" "github.com/enbility/spine-go/util" @@ -124,14 +126,23 @@ func (s *EgLPCSuite) Test_loadControlLimitDataUpdate() { data = &model.LoadControlLimitListDataType{ LoadControlLimitData: []model.LoadControlLimitDataType{ { - LimitId: util.Ptr(model.LoadControlLimitIdType(0)), - Value: model.NewScaledNumberType(16), + LimitId: util.Ptr(model.LoadControlLimitIdType(0)), + IsLimitChangeable: util.Ptr(true), + IsLimitActive: util.Ptr(false), + Value: model.NewScaledNumberType(6000), + TimePeriod: &model.TimePeriodType{ + EndTime: model.NewAbsoluteOrRelativeTimeType("PT2H"), + }, }, }, } payload.Data = data + // Update the feature with the data so it's actually stored + _, fErr = rFeature.UpdateData(true, model.FunctionTypeLoadControlLimitListData, data, nil, nil) + assert.Nil(s.T(), fErr) + s.sut.loadControlLimitDataUpdate(payload) assert.True(s.T(), s.eventCalled) } @@ -148,12 +159,14 @@ func (s *EgLPCSuite) Test_configurationDataUpdate() { descData := &model.DeviceConfigurationKeyValueDescriptionListDataType{ DeviceConfigurationKeyValueDescriptionData: []model.DeviceConfigurationKeyValueDescriptionDataType{ { - KeyId: util.Ptr(model.DeviceConfigurationKeyIdType(1)), - KeyName: util.Ptr(model.DeviceConfigurationKeyNameTypeFailsafeConsumptionActivePowerLimit), + KeyId: util.Ptr(model.DeviceConfigurationKeyIdType(1)), + KeyName: util.Ptr(model.DeviceConfigurationKeyNameTypeFailsafeConsumptionActivePowerLimit), + ValueType: util.Ptr(model.DeviceConfigurationKeyValueTypeTypeScaledNumber), }, { - KeyId: util.Ptr(model.DeviceConfigurationKeyIdType(2)), - KeyName: util.Ptr(model.DeviceConfigurationKeyNameTypeFailsafeDurationMinimum), + KeyId: util.Ptr(model.DeviceConfigurationKeyIdType(2)), + KeyName: util.Ptr(model.DeviceConfigurationKeyNameTypeFailsafeDurationMinimum), + ValueType: util.Ptr(model.DeviceConfigurationKeyValueTypeTypeDuration), }, }, } @@ -178,17 +191,25 @@ func (s *EgLPCSuite) Test_configurationDataUpdate() { DeviceConfigurationKeyValueData: []model.DeviceConfigurationKeyValueDataType{ { KeyId: util.Ptr(model.DeviceConfigurationKeyIdType(1)), - Value: &model.DeviceConfigurationKeyValueValueType{}, + Value: &model.DeviceConfigurationKeyValueValueType{ + ScaledNumber: model.NewScaledNumberType(6000), + }, }, { KeyId: util.Ptr(model.DeviceConfigurationKeyIdType(2)), - Value: &model.DeviceConfigurationKeyValueValueType{}, + Value: &model.DeviceConfigurationKeyValueValueType{ + Duration: model.NewDurationType(time.Hour * 10), + }, }, }, } payload.Data = data + // Update the feature with the data so it's actually stored + _, fErr = rFeature.UpdateData(true, model.FunctionTypeDeviceConfigurationKeyValueListData, data, nil, nil) + assert.Nil(s.T(), fErr) + s.sut.configurationDataUpdate(payload) assert.True(s.T(), s.eventCalled) } diff --git a/usecases/ma/mgcp/events.go b/usecases/ma/mgcp/events.go index 7f88ac64..9a33949e 100644 --- a/usecases/ma/mgcp/events.go +++ b/usecases/ma/mgcp/events.go @@ -108,8 +108,11 @@ func (e *MGCP) gridConfigurationDataUpdate(payload spineapi.EventPayload) { filter := model.DeviceConfigurationKeyValueDescriptionDataType{ KeyName: util.Ptr(model.DeviceConfigurationKeyNameTypePvCurtailmentLimitFactor), } - if dc.CheckEventPayloadDataForFilter(payload.Data, filter) && e.EventCB != nil { - e.EventCB(payload.Ski, payload.Device, payload.Entity, DataUpdatePowerLimitationFactor) + if dc.CheckEventPayloadDataForFilter(payload.Data, filter) { + // Only fire event if public method succeeds (data is valid and retrievable) + if _, err := e.PowerLimitationFactor(payload.Entity); err == nil && e.EventCB != nil { + e.EventCB(payload.Ski, payload.Device, payload.Entity, DataUpdatePowerLimitationFactor) + } } } } @@ -131,38 +134,56 @@ func (e *MGCP) gridMeasurementDataUpdate(payload spineapi.EventPayload) { filter := model.MeasurementDescriptionDataType{ ScopeType: util.Ptr(model.ScopeTypeTypeACPowerTotal), } - if measurement.CheckEventPayloadDataForFilter(payload.Data, filter) && e.EventCB != nil { - e.EventCB(payload.Ski, payload.Device, payload.Entity, DataUpdatePower) + if measurement.CheckEventPayloadDataForFilter(payload.Data, filter) { + // Only fire event if public method succeeds (data is valid and retrievable) + if _, err := e.Power(payload.Entity); err == nil && e.EventCB != nil { + e.EventCB(payload.Ski, payload.Device, payload.Entity, DataUpdatePower) + } } // Scenario 3 filter.ScopeType = util.Ptr(model.ScopeTypeTypeGridFeedIn) - if measurement.CheckEventPayloadDataForFilter(payload.Data, filter) && e.EventCB != nil { - e.EventCB(payload.Ski, payload.Device, payload.Entity, DataUpdateEnergyFeedIn) + if measurement.CheckEventPayloadDataForFilter(payload.Data, filter) { + // Only fire event if public method succeeds (data is valid and retrievable) + if _, err := e.EnergyFeedIn(payload.Entity); err == nil && e.EventCB != nil { + e.EventCB(payload.Ski, payload.Device, payload.Entity, DataUpdateEnergyFeedIn) + } } // Scenario 4 filter.ScopeType = util.Ptr(model.ScopeTypeTypeGridConsumption) - if measurement.CheckEventPayloadDataForFilter(payload.Data, filter) && e.EventCB != nil { - e.EventCB(payload.Ski, payload.Device, payload.Entity, DataUpdateEnergyConsumed) + if measurement.CheckEventPayloadDataForFilter(payload.Data, filter) { + // Only fire event if public method succeeds (data is valid and retrievable) + if _, err := e.EnergyConsumed(payload.Entity); err == nil && e.EventCB != nil { + e.EventCB(payload.Ski, payload.Device, payload.Entity, DataUpdateEnergyConsumed) + } } // Scenario 5 filter.ScopeType = util.Ptr(model.ScopeTypeTypeACCurrent) - if measurement.CheckEventPayloadDataForFilter(payload.Data, filter) && e.EventCB != nil { - e.EventCB(payload.Ski, payload.Device, payload.Entity, DataUpdateCurrentPerPhase) + if measurement.CheckEventPayloadDataForFilter(payload.Data, filter) { + // Only fire event if public method succeeds (data is valid and retrievable) + if _, err := e.CurrentPerPhase(payload.Entity); err == nil && e.EventCB != nil { + e.EventCB(payload.Ski, payload.Device, payload.Entity, DataUpdateCurrentPerPhase) + } } // Scenario 6 filter.ScopeType = util.Ptr(model.ScopeTypeTypeACVoltage) - if measurement.CheckEventPayloadDataForFilter(payload.Data, filter) && e.EventCB != nil { - e.EventCB(payload.Ski, payload.Device, payload.Entity, DataUpdateVoltagePerPhase) + if measurement.CheckEventPayloadDataForFilter(payload.Data, filter) { + // Only fire event if public method succeeds (data is valid and retrievable) + if _, err := e.VoltagePerPhase(payload.Entity); err == nil && e.EventCB != nil { + e.EventCB(payload.Ski, payload.Device, payload.Entity, DataUpdateVoltagePerPhase) + } } // Scenario 7 filter.ScopeType = util.Ptr(model.ScopeTypeTypeACFrequency) - if measurement.CheckEventPayloadDataForFilter(payload.Data, filter) && e.EventCB != nil { - e.EventCB(payload.Ski, payload.Device, payload.Entity, DataUpdateFrequency) + if measurement.CheckEventPayloadDataForFilter(payload.Data, filter) { + // Only fire event if public method succeeds (data is valid and retrievable) + if _, err := e.Frequency(payload.Entity); err == nil && e.EventCB != nil { + e.EventCB(payload.Ski, payload.Device, payload.Entity, DataUpdateFrequency) + } } } } diff --git a/usecases/ma/mgcp/events_test.go b/usecases/ma/mgcp/events_test.go index 63a5d8e7..c96497fc 100644 --- a/usecases/ma/mgcp/events_test.go +++ b/usecases/ma/mgcp/events_test.go @@ -89,6 +89,10 @@ func (s *GcpMGCPSuite) Test_gridConfigurationDataUpdate() { payload.Data = keyData + // Update the feature with the data so it's actually stored + _, fErr = rFeature.UpdateData(true, model.FunctionTypeDeviceConfigurationKeyValueListData, keyData, nil, nil) + assert.Nil(s.T(), fErr) + s.sut.gridConfigurationDataUpdate(payload) assert.True(s.T(), s.eventCalled) } @@ -105,28 +109,40 @@ func (s *GcpMGCPSuite) Test_gridMeasurementDataUpdate() { descData := &model.MeasurementDescriptionListDataType{ MeasurementDescriptionData: []model.MeasurementDescriptionDataType{ { - MeasurementId: util.Ptr(model.MeasurementIdType(0)), - ScopeType: util.Ptr(model.ScopeTypeTypeACPowerTotal), + MeasurementId: util.Ptr(model.MeasurementIdType(0)), + MeasurementType: util.Ptr(model.MeasurementTypeTypePower), + CommodityType: util.Ptr(model.CommodityTypeTypeElectricity), + ScopeType: util.Ptr(model.ScopeTypeTypeACPowerTotal), }, { - MeasurementId: util.Ptr(model.MeasurementIdType(1)), - ScopeType: util.Ptr(model.ScopeTypeTypeGridFeedIn), + MeasurementId: util.Ptr(model.MeasurementIdType(1)), + MeasurementType: util.Ptr(model.MeasurementTypeTypeEnergy), + CommodityType: util.Ptr(model.CommodityTypeTypeElectricity), + ScopeType: util.Ptr(model.ScopeTypeTypeGridFeedIn), }, { - MeasurementId: util.Ptr(model.MeasurementIdType(2)), - ScopeType: util.Ptr(model.ScopeTypeTypeGridConsumption), + MeasurementId: util.Ptr(model.MeasurementIdType(2)), + MeasurementType: util.Ptr(model.MeasurementTypeTypeEnergy), + CommodityType: util.Ptr(model.CommodityTypeTypeElectricity), + ScopeType: util.Ptr(model.ScopeTypeTypeGridConsumption), }, { - MeasurementId: util.Ptr(model.MeasurementIdType(3)), - ScopeType: util.Ptr(model.ScopeTypeTypeACCurrent), + MeasurementId: util.Ptr(model.MeasurementIdType(3)), + MeasurementType: util.Ptr(model.MeasurementTypeTypeCurrent), + CommodityType: util.Ptr(model.CommodityTypeTypeElectricity), + ScopeType: util.Ptr(model.ScopeTypeTypeACCurrent), }, { - MeasurementId: util.Ptr(model.MeasurementIdType(4)), - ScopeType: util.Ptr(model.ScopeTypeTypeACVoltage), + MeasurementId: util.Ptr(model.MeasurementIdType(4)), + MeasurementType: util.Ptr(model.MeasurementTypeTypeVoltage), + CommodityType: util.Ptr(model.CommodityTypeTypeElectricity), + ScopeType: util.Ptr(model.ScopeTypeTypeACVoltage), }, { - MeasurementId: util.Ptr(model.MeasurementIdType(5)), - ScopeType: util.Ptr(model.ScopeTypeTypeACFrequency), + MeasurementId: util.Ptr(model.MeasurementIdType(5)), + MeasurementType: util.Ptr(model.MeasurementTypeTypeFrequency), + CommodityType: util.Ptr(model.CommodityTypeTypeElectricity), + ScopeType: util.Ptr(model.ScopeTypeTypeACFrequency), }, }, } @@ -135,6 +151,52 @@ func (s *GcpMGCPSuite) Test_gridMeasurementDataUpdate() { _, fErr := rFeature.UpdateData(true, model.FunctionTypeMeasurementDescriptionListData, descData, nil, nil) assert.Nil(s.T(), fErr) + // Add electrical connection setup for complete validation + elDescData := &model.ElectricalConnectionDescriptionListDataType{ + ElectricalConnectionDescriptionData: []model.ElectricalConnectionDescriptionDataType{ + { + ElectricalConnectionId: util.Ptr(model.ElectricalConnectionIdType(0)), + PositiveEnergyDirection: util.Ptr(model.EnergyDirectionTypeConsume), + }, + }, + } + + rElFeature := s.remoteDevice.FeatureByEntityTypeAndRole(s.smgwEntity, model.FeatureTypeTypeElectricalConnection, model.RoleTypeServer) + _, fErr = rElFeature.UpdateData(true, model.FunctionTypeElectricalConnectionDescriptionListData, elDescData, nil, nil) + assert.Nil(s.T(), fErr) + + elParamData := &model.ElectricalConnectionParameterDescriptionListDataType{ + ElectricalConnectionParameterDescriptionData: []model.ElectricalConnectionParameterDescriptionDataType{ + { + ElectricalConnectionId: util.Ptr(model.ElectricalConnectionIdType(0)), + MeasurementId: util.Ptr(model.MeasurementIdType(0)), + }, + { + ElectricalConnectionId: util.Ptr(model.ElectricalConnectionIdType(0)), + MeasurementId: util.Ptr(model.MeasurementIdType(1)), + }, + { + ElectricalConnectionId: util.Ptr(model.ElectricalConnectionIdType(0)), + MeasurementId: util.Ptr(model.MeasurementIdType(2)), + }, + { + ElectricalConnectionId: util.Ptr(model.ElectricalConnectionIdType(0)), + MeasurementId: util.Ptr(model.MeasurementIdType(3)), + }, + { + ElectricalConnectionId: util.Ptr(model.ElectricalConnectionIdType(0)), + MeasurementId: util.Ptr(model.MeasurementIdType(4)), + }, + { + ElectricalConnectionId: util.Ptr(model.ElectricalConnectionIdType(0)), + MeasurementId: util.Ptr(model.MeasurementIdType(5)), + }, + }, + } + + _, fErr = rElFeature.UpdateData(true, model.FunctionTypeElectricalConnectionParameterDescriptionListData, elParamData, nil, nil) + assert.Nil(s.T(), fErr) + s.sut.gridMeasurementDataUpdate(payload) assert.False(s.T(), s.eventCalled) @@ -142,33 +204,49 @@ func (s *GcpMGCPSuite) Test_gridMeasurementDataUpdate() { MeasurementData: []model.MeasurementDataType{ { MeasurementId: util.Ptr(model.MeasurementIdType(0)), + ValueType: util.Ptr(model.MeasurementValueTypeTypeValue), Value: model.NewScaledNumberType(10), + ValueSource: util.Ptr(model.MeasurementValueSourceTypeMeasuredValue), }, { MeasurementId: util.Ptr(model.MeasurementIdType(1)), + ValueType: util.Ptr(model.MeasurementValueTypeTypeValue), Value: model.NewScaledNumberType(10), + ValueSource: util.Ptr(model.MeasurementValueSourceTypeMeasuredValue), }, { MeasurementId: util.Ptr(model.MeasurementIdType(2)), + ValueType: util.Ptr(model.MeasurementValueTypeTypeValue), Value: model.NewScaledNumberType(10), + ValueSource: util.Ptr(model.MeasurementValueSourceTypeMeasuredValue), }, { MeasurementId: util.Ptr(model.MeasurementIdType(3)), + ValueType: util.Ptr(model.MeasurementValueTypeTypeValue), Value: model.NewScaledNumberType(10), + ValueSource: util.Ptr(model.MeasurementValueSourceTypeMeasuredValue), }, { MeasurementId: util.Ptr(model.MeasurementIdType(4)), + ValueType: util.Ptr(model.MeasurementValueTypeTypeValue), Value: model.NewScaledNumberType(10), + ValueSource: util.Ptr(model.MeasurementValueSourceTypeMeasuredValue), }, { MeasurementId: util.Ptr(model.MeasurementIdType(5)), + ValueType: util.Ptr(model.MeasurementValueTypeTypeValue), Value: model.NewScaledNumberType(10), + ValueSource: util.Ptr(model.MeasurementValueSourceTypeMeasuredValue), }, }, } payload.Data = data + // Update the feature with the data so it's actually stored + _, fErr = rFeature.UpdateData(true, model.FunctionTypeMeasurementListData, data, nil, nil) + assert.Nil(s.T(), fErr) + s.sut.gridMeasurementDataUpdate(payload) assert.True(s.T(), s.eventCalled) } diff --git a/usecases/ma/mpc/events.go b/usecases/ma/mpc/events.go index 05a2da62..a8df0bc8 100644 --- a/usecases/ma/mpc/events.go +++ b/usecases/ma/mpc/events.go @@ -90,42 +90,63 @@ func (e *MPC) deviceMeasurementDataUpdate(payload spineapi.EventPayload) { filter := model.MeasurementDescriptionDataType{ ScopeType: util.Ptr(model.ScopeTypeTypeACPowerTotal), } - if measurement.CheckEventPayloadDataForFilter(payload.Data, filter) && e.EventCB != nil { - e.EventCB(payload.Ski, payload.Device, payload.Entity, DataUpdatePower) + if measurement.CheckEventPayloadDataForFilter(payload.Data, filter) { + // Only fire event if public method succeeds (data is valid and retrievable) + if _, err := e.Power(payload.Entity); err == nil && e.EventCB != nil { + e.EventCB(payload.Ski, payload.Device, payload.Entity, DataUpdatePower) + } } filter.ScopeType = util.Ptr(model.ScopeTypeTypeACPower) - if measurement.CheckEventPayloadDataForFilter(payload.Data, filter) && e.EventCB != nil { - e.EventCB(payload.Ski, payload.Device, payload.Entity, DataUpdatePowerPerPhase) + if measurement.CheckEventPayloadDataForFilter(payload.Data, filter) { + // Only fire event if public method succeeds (data is valid and retrievable) + if _, err := e.PowerPerPhase(payload.Entity); err == nil && e.EventCB != nil { + e.EventCB(payload.Ski, payload.Device, payload.Entity, DataUpdatePowerPerPhase) + } } // Scenario 2 filter.ScopeType = util.Ptr(model.ScopeTypeTypeACEnergyConsumed) - if measurement.CheckEventPayloadDataForFilter(payload.Data, filter) && e.EventCB != nil { - e.EventCB(payload.Ski, payload.Device, payload.Entity, DataUpdateEnergyConsumed) + if measurement.CheckEventPayloadDataForFilter(payload.Data, filter) { + // Only fire event if public method succeeds (data is valid and retrievable) + if _, err := e.EnergyConsumed(payload.Entity); err == nil && e.EventCB != nil { + e.EventCB(payload.Ski, payload.Device, payload.Entity, DataUpdateEnergyConsumed) + } } filter.ScopeType = util.Ptr(model.ScopeTypeTypeACEnergyProduced) - if measurement.CheckEventPayloadDataForFilter(payload.Data, filter) && e.EventCB != nil { - e.EventCB(payload.Ski, payload.Device, payload.Entity, DataUpdateEnergyProduced) + if measurement.CheckEventPayloadDataForFilter(payload.Data, filter) { + // Only fire event if public method succeeds (data is valid and retrievable) + if _, err := e.EnergyProduced(payload.Entity); err == nil && e.EventCB != nil { + e.EventCB(payload.Ski, payload.Device, payload.Entity, DataUpdateEnergyProduced) + } } // Scenario 3 filter.ScopeType = util.Ptr(model.ScopeTypeTypeACCurrent) - if measurement.CheckEventPayloadDataForFilter(payload.Data, filter) && e.EventCB != nil { - e.EventCB(payload.Ski, payload.Device, payload.Entity, DataUpdateCurrentsPerPhase) + if measurement.CheckEventPayloadDataForFilter(payload.Data, filter) { + // Only fire event if public method succeeds (data is valid and retrievable) + if _, err := e.CurrentPerPhase(payload.Entity); err == nil && e.EventCB != nil { + e.EventCB(payload.Ski, payload.Device, payload.Entity, DataUpdateCurrentsPerPhase) + } } // Scenario 4 filter.ScopeType = util.Ptr(model.ScopeTypeTypeACVoltage) - if measurement.CheckEventPayloadDataForFilter(payload.Data, filter) && e.EventCB != nil { - e.EventCB(payload.Ski, payload.Device, payload.Entity, DataUpdateVoltagePerPhase) + if measurement.CheckEventPayloadDataForFilter(payload.Data, filter) { + // Only fire event if public method succeeds (data is valid and retrievable) + if _, err := e.VoltagePerPhase(payload.Entity); err == nil && e.EventCB != nil { + e.EventCB(payload.Ski, payload.Device, payload.Entity, DataUpdateVoltagePerPhase) + } } // Scenario 5 filter.ScopeType = util.Ptr(model.ScopeTypeTypeACFrequency) - if measurement.CheckEventPayloadDataForFilter(payload.Data, filter) && e.EventCB != nil { - e.EventCB(payload.Ski, payload.Device, payload.Entity, DataUpdateFrequency) + if measurement.CheckEventPayloadDataForFilter(payload.Data, filter) { + // Only fire event if public method succeeds (data is valid and retrievable) + if _, err := e.Frequency(payload.Entity); err == nil && e.EventCB != nil { + e.EventCB(payload.Ski, payload.Device, payload.Entity, DataUpdateFrequency) + } } } } diff --git a/usecases/ma/mpc/events_test.go b/usecases/ma/mpc/events_test.go index cca469f7..82e5db48 100644 --- a/usecases/ma/mpc/events_test.go +++ b/usecases/ma/mpc/events_test.go @@ -57,32 +57,46 @@ func (s *MaMPCSuite) Test_deviceMeasurementDataUpdate() { descData := &model.MeasurementDescriptionListDataType{ MeasurementDescriptionData: []model.MeasurementDescriptionDataType{ { - MeasurementId: util.Ptr(model.MeasurementIdType(0)), - ScopeType: util.Ptr(model.ScopeTypeTypeACPowerTotal), + MeasurementId: util.Ptr(model.MeasurementIdType(0)), + MeasurementType: util.Ptr(model.MeasurementTypeTypePower), + CommodityType: util.Ptr(model.CommodityTypeTypeElectricity), + ScopeType: util.Ptr(model.ScopeTypeTypeACPowerTotal), }, { - MeasurementId: util.Ptr(model.MeasurementIdType(1)), - ScopeType: util.Ptr(model.ScopeTypeTypeACPower), + MeasurementId: util.Ptr(model.MeasurementIdType(1)), + MeasurementType: util.Ptr(model.MeasurementTypeTypePower), + CommodityType: util.Ptr(model.CommodityTypeTypeElectricity), + ScopeType: util.Ptr(model.ScopeTypeTypeACPower), }, { - MeasurementId: util.Ptr(model.MeasurementIdType(2)), - ScopeType: util.Ptr(model.ScopeTypeTypeACEnergyConsumed), + MeasurementId: util.Ptr(model.MeasurementIdType(2)), + MeasurementType: util.Ptr(model.MeasurementTypeTypeEnergy), + CommodityType: util.Ptr(model.CommodityTypeTypeElectricity), + ScopeType: util.Ptr(model.ScopeTypeTypeACEnergyConsumed), }, { - MeasurementId: util.Ptr(model.MeasurementIdType(3)), - ScopeType: util.Ptr(model.ScopeTypeTypeACEnergyProduced), + MeasurementId: util.Ptr(model.MeasurementIdType(3)), + MeasurementType: util.Ptr(model.MeasurementTypeTypeEnergy), + CommodityType: util.Ptr(model.CommodityTypeTypeElectricity), + ScopeType: util.Ptr(model.ScopeTypeTypeACEnergyProduced), }, { - MeasurementId: util.Ptr(model.MeasurementIdType(4)), - ScopeType: util.Ptr(model.ScopeTypeTypeACCurrent), + MeasurementId: util.Ptr(model.MeasurementIdType(4)), + MeasurementType: util.Ptr(model.MeasurementTypeTypeCurrent), + CommodityType: util.Ptr(model.CommodityTypeTypeElectricity), + ScopeType: util.Ptr(model.ScopeTypeTypeACCurrent), }, { - MeasurementId: util.Ptr(model.MeasurementIdType(5)), - ScopeType: util.Ptr(model.ScopeTypeTypeACVoltage), + MeasurementId: util.Ptr(model.MeasurementIdType(5)), + MeasurementType: util.Ptr(model.MeasurementTypeTypeVoltage), + CommodityType: util.Ptr(model.CommodityTypeTypeElectricity), + ScopeType: util.Ptr(model.ScopeTypeTypeACVoltage), }, { - MeasurementId: util.Ptr(model.MeasurementIdType(6)), - ScopeType: util.Ptr(model.ScopeTypeTypeACFrequency), + MeasurementId: util.Ptr(model.MeasurementIdType(6)), + MeasurementType: util.Ptr(model.MeasurementTypeTypeFrequency), + CommodityType: util.Ptr(model.CommodityTypeTypeElectricity), + ScopeType: util.Ptr(model.ScopeTypeTypeACFrequency), }, }, } @@ -91,6 +105,56 @@ func (s *MaMPCSuite) Test_deviceMeasurementDataUpdate() { _, fErr := rFeature.UpdateData(true, model.FunctionTypeMeasurementDescriptionListData, descData, nil, nil) assert.Nil(s.T(), fErr) + // Add electrical connection setup for complete validation + elDescData := &model.ElectricalConnectionDescriptionListDataType{ + ElectricalConnectionDescriptionData: []model.ElectricalConnectionDescriptionDataType{ + { + ElectricalConnectionId: util.Ptr(model.ElectricalConnectionIdType(0)), + PositiveEnergyDirection: util.Ptr(model.EnergyDirectionTypeConsume), + }, + }, + } + + rElFeature := s.remoteDevice.FeatureByEntityTypeAndRole(s.monitoredEntity, model.FeatureTypeTypeElectricalConnection, model.RoleTypeServer) + _, fErr = rElFeature.UpdateData(true, model.FunctionTypeElectricalConnectionDescriptionListData, elDescData, nil, nil) + assert.Nil(s.T(), fErr) + + elParamData := &model.ElectricalConnectionParameterDescriptionListDataType{ + ElectricalConnectionParameterDescriptionData: []model.ElectricalConnectionParameterDescriptionDataType{ + { + ElectricalConnectionId: util.Ptr(model.ElectricalConnectionIdType(0)), + MeasurementId: util.Ptr(model.MeasurementIdType(0)), + }, + { + ElectricalConnectionId: util.Ptr(model.ElectricalConnectionIdType(0)), + MeasurementId: util.Ptr(model.MeasurementIdType(1)), + }, + { + ElectricalConnectionId: util.Ptr(model.ElectricalConnectionIdType(0)), + MeasurementId: util.Ptr(model.MeasurementIdType(2)), + }, + { + ElectricalConnectionId: util.Ptr(model.ElectricalConnectionIdType(0)), + MeasurementId: util.Ptr(model.MeasurementIdType(3)), + }, + { + ElectricalConnectionId: util.Ptr(model.ElectricalConnectionIdType(0)), + MeasurementId: util.Ptr(model.MeasurementIdType(4)), + }, + { + ElectricalConnectionId: util.Ptr(model.ElectricalConnectionIdType(0)), + MeasurementId: util.Ptr(model.MeasurementIdType(5)), + }, + { + ElectricalConnectionId: util.Ptr(model.ElectricalConnectionIdType(0)), + MeasurementId: util.Ptr(model.MeasurementIdType(6)), + }, + }, + } + + _, fErr = rElFeature.UpdateData(true, model.FunctionTypeElectricalConnectionParameterDescriptionListData, elParamData, nil, nil) + assert.Nil(s.T(), fErr) + s.sut.deviceMeasurementDataUpdate(payload) assert.False(s.T(), s.eventCalled) @@ -98,37 +162,55 @@ func (s *MaMPCSuite) Test_deviceMeasurementDataUpdate() { MeasurementData: []model.MeasurementDataType{ { MeasurementId: util.Ptr(model.MeasurementIdType(0)), + ValueType: util.Ptr(model.MeasurementValueTypeTypeValue), Value: model.NewScaledNumberType(10), + ValueSource: util.Ptr(model.MeasurementValueSourceTypeMeasuredValue), }, { MeasurementId: util.Ptr(model.MeasurementIdType(1)), + ValueType: util.Ptr(model.MeasurementValueTypeTypeValue), Value: model.NewScaledNumberType(10), + ValueSource: util.Ptr(model.MeasurementValueSourceTypeMeasuredValue), }, { MeasurementId: util.Ptr(model.MeasurementIdType(2)), + ValueType: util.Ptr(model.MeasurementValueTypeTypeValue), Value: model.NewScaledNumberType(10), + ValueSource: util.Ptr(model.MeasurementValueSourceTypeMeasuredValue), }, { MeasurementId: util.Ptr(model.MeasurementIdType(3)), + ValueType: util.Ptr(model.MeasurementValueTypeTypeValue), Value: model.NewScaledNumberType(10), + ValueSource: util.Ptr(model.MeasurementValueSourceTypeMeasuredValue), }, { MeasurementId: util.Ptr(model.MeasurementIdType(4)), + ValueType: util.Ptr(model.MeasurementValueTypeTypeValue), Value: model.NewScaledNumberType(10), + ValueSource: util.Ptr(model.MeasurementValueSourceTypeMeasuredValue), }, { MeasurementId: util.Ptr(model.MeasurementIdType(5)), + ValueType: util.Ptr(model.MeasurementValueTypeTypeValue), Value: model.NewScaledNumberType(10), + ValueSource: util.Ptr(model.MeasurementValueSourceTypeMeasuredValue), }, { MeasurementId: util.Ptr(model.MeasurementIdType(6)), + ValueType: util.Ptr(model.MeasurementValueTypeTypeValue), Value: model.NewScaledNumberType(10), + ValueSource: util.Ptr(model.MeasurementValueSourceTypeMeasuredValue), }, }, } payload.Data = data + // Update the feature with the data so it's actually stored + _, fErr = rFeature.UpdateData(true, model.FunctionTypeMeasurementListData, data, nil, nil) + assert.Nil(s.T(), fErr) + s.sut.deviceMeasurementDataUpdate(payload) assert.True(s.T(), s.eventCalled) }