diff --git a/usecases/README.md b/usecases/README.md index 47a977b1..88166c85 100644 --- a/usecases/README.md +++ b/usecases/README.md @@ -34,3 +34,8 @@ Actors: Use Cases: - `mpc`: Monitoring of Power Consumption - `mgcp`: Monitoring of Grid Connection Point + +- `mu`: Monitored Unit + + Use Cases: + - `mpc`: Monitoring of Power Consumption diff --git a/usecases/api/mu_mpc.go b/usecases/api/mu_mpc.go new file mode 100644 index 00000000..c4d38947 --- /dev/null +++ b/usecases/api/mu_mpc.go @@ -0,0 +1,69 @@ +package api + +import ( + "github.com/enbility/eebus-go/api" +) + +// Actor: Monitored Unit +// UseCase: Monitoring of Power Consumption +type MuMPCInterface interface { + api.UseCaseInterface + + // Scenario 1 + + // set the momentary active power consumption or production + // + // parameters: + // - power: the active power + SetPower(power float64) error + + // set the momentary active phase specific power consumption or production per phase + // + // parameters: + // - phaseA: the active power of phase A + // - phaseB: the active power of phase B + // - phaseC: the active power of phase C + SetPowerPerPhase(phaseA, phaseB, phaseC float64) error + + // Scenario 2 + + // set the total consumption energy + // + // parameters: + // - consumed: the total consumption energy + SetEnergyConsumed(consumed float64) error + + // set the total feed in energy + // + // parameters: + // - produced: the total feed in energy + SetEnergyProduced(produced float64) error + + // Scenario 3 + + // set the momentary phase specific current consumption or production + // + // parameters: + // - phaseA: the current of phase A + // - phaseB: the current of phase B + // - phaseC: the current of phase C + SetCurrentPerPhase(phaseA, phaseB, phaseC float64) error + + // Scenario 4 + + // set the phase specific voltage details + // + // parameters: + // - phaseA: the voltage of phase A + // - phaseB: the voltage of phase B + // - phaseC: the voltage of phase C + SetVoltagePerPhase(phaseA, phaseB, phaseC float64) error + + // Scenario 5 + + // set frequency + // + // parameters: + // - frequency: the frequency + SetFrequency(frequency float64) error +} diff --git a/usecases/mu/mpc/events.go b/usecases/mu/mpc/events.go new file mode 100644 index 00000000..c05e05aa --- /dev/null +++ b/usecases/mu/mpc/events.go @@ -0,0 +1,10 @@ +package mpc + +import ( + spineapi "github.com/enbility/spine-go/api" +) + +// handle SPINE events +func (e *MPC) HandleEvent(payload spineapi.EventPayload) { + // No events supported +} diff --git a/usecases/mu/mpc/public.go b/usecases/mu/mpc/public.go new file mode 100644 index 00000000..46f95935 --- /dev/null +++ b/usecases/mu/mpc/public.go @@ -0,0 +1,160 @@ +package mpc + +import ( + "github.com/enbility/eebus-go/api" +) + +// Scenario 1 + +// set the momentary active power consumption or production +// +// possible errors: +// - ErrMissingData if the id is not available +// - and others +func (e *MPC) SetPower(power float64) error { + if e.acPowerTotal == nil { + return api.ErrMissingData + } + + err := e.setMeasurementDataForId(e.acPowerTotal, power) + if err != nil { + return err + } + + return nil +} + +// set the momentary active power consumption or production per phase +// +// possible errors: +// - ErrMissingData if the id is not available +// - and others +func (e *MPC) SetPowerPerPhase(phaseA, phaseB, phaseC float64) error { + if e.acPower[0] == nil || e.acPower[1] == nil || e.acPower[2] == nil { + return api.ErrMissingData + } + + err := e.setMeasurementDataForId(e.acPower[0], phaseA) + if err != nil { + return err + } + + err = e.setMeasurementDataForId(e.acPower[1], phaseB) + if err != nil { + return err + } + + err = e.setMeasurementDataForId(e.acPower[2], phaseC) + if err != nil { + return err + } + + return nil +} + +// Scenario 2 + +// set the total consumption energy +// +// - positive values are used for consumption +func (e *MPC) SetEnergyConsumed(energy float64) error { + if e.acEnergyConsumed == nil { + return api.ErrMissingData + } + + err := e.setMeasurementDataForId(e.acEnergyConsumed, energy) + if err != nil { + return err + } + + return nil +} + +// set the total feed in energy +// +// - negative values are used for production +func (e *MPC) SetEnergyProduced(energy float64) error { + if e.acEnergyProduced == nil { + return api.ErrMissingData + } + + err := e.setMeasurementDataForId(e.acEnergyProduced, energy) + if err != nil { + return err + } + + return nil +} + +// Scenario 3 + +// set the momentary phase specific current consumption or production +// +// - positive values are used for consumption +// - negative values are used for production +func (e *MPC) SetCurrentPerPhase(phaseA, phaseB, phaseC float64) error { + if e.acCurrent[0] == nil || e.acCurrent[1] == nil || e.acCurrent[2] == nil { + return api.ErrMissingData + } + + err := e.setMeasurementDataForId(e.acCurrent[0], phaseA) + if err != nil { + return err + } + + err = e.setMeasurementDataForId(e.acCurrent[1], phaseB) + if err != nil { + return err + } + + err = e.setMeasurementDataForId(e.acCurrent[2], phaseC) + if err != nil { + return err + } + + return nil +} + +// Scenario 4 + +// set the phase specific voltage details +func (e *MPC) SetVoltagePerPhase(phaseA, phaseB, phaseC float64) error { + for _, id := range e.acVoltage { + if id == nil { + return api.ErrMissingData + } + } + + err := e.setMeasurementDataForId(e.acVoltage[0], phaseA) + if err != nil { + return err + } + + err = e.setMeasurementDataForId(e.acVoltage[1], phaseB) + if err != nil { + return err + } + + err = e.setMeasurementDataForId(e.acVoltage[2], phaseC) + if err != nil { + return err + } + + return nil +} + +// Scenario 5 + +// SetFrequency set frequency +func (e *MPC) SetFrequency(frequency float64) error { + if e.acFrequency == nil { + return api.ErrMissingData + } + + err := e.setMeasurementDataForId(e.acFrequency, frequency) + if err != nil { + return err + } + + return nil +} diff --git a/usecases/mu/mpc/public_test.go b/usecases/mu/mpc/public_test.go new file mode 100644 index 00000000..decee094 --- /dev/null +++ b/usecases/mu/mpc/public_test.go @@ -0,0 +1,40 @@ +package mpc + +import ( + "github.com/stretchr/testify/assert" +) + +func (s *MuMPCSuite) Test_Power() { + err := s.sut.SetPower(5.0) + assert.Nil(s.T(), err) +} + +func (s *MuMPCSuite) Test_PowerPerPhase() { + err := s.sut.SetPowerPerPhase(5.0, 5.0, 5.0) + assert.Nil(s.T(), err) +} + +func (s *MuMPCSuite) Test_EnergyConsumed() { + err := s.sut.SetEnergyConsumed(5.0) + assert.Nil(s.T(), err) +} + +func (s *MuMPCSuite) Test_EnergyProduced() { + err := s.sut.SetEnergyProduced(5.0) + assert.Nil(s.T(), err) +} + +func (s *MuMPCSuite) Test_CurrentPerPhase() { + err := s.sut.SetCurrentPerPhase(5.0, 5.0, 5.0) + assert.Nil(s.T(), err) +} + +func (s *MuMPCSuite) Test_VoltagePerPhase() { + err := s.sut.SetVoltagePerPhase(5.0, 5.0, 5.0) + assert.Nil(s.T(), err) +} + +func (s *MuMPCSuite) Test_Frequency() { + err := s.sut.SetFrequency(5.0) + assert.Nil(s.T(), err) +} diff --git a/usecases/mu/mpc/testhelper_test.go b/usecases/mu/mpc/testhelper_test.go new file mode 100644 index 00000000..f5c3c572 --- /dev/null +++ b/usecases/mu/mpc/testhelper_test.go @@ -0,0 +1,78 @@ +package mpc + +import ( + "github.com/enbility/eebus-go/api" + "github.com/enbility/eebus-go/mocks" + "github.com/enbility/eebus-go/service" + "github.com/enbility/ship-go/cert" + spineapi "github.com/enbility/spine-go/api" + spinemocks "github.com/enbility/spine-go/mocks" + "github.com/enbility/spine-go/model" + "github.com/stretchr/testify/mock" + "github.com/stretchr/testify/suite" + "testing" + "time" +) + +const remoteSki string = "testremoteski" + +func TestMuMPCSuite(t *testing.T) { + suite.Run(t, new(MuMPCSuite)) +} + +type MuMPCSuite struct { + suite.Suite + + sut *MPC + + service api.ServiceInterface + + remoteDevice spineapi.DeviceRemoteInterface + mockRemoteEntity *spinemocks.EntityRemoteInterface + monitoredEntity spineapi.EntityRemoteInterface + loadControlFeature, + deviceDiagnosisFeature, + deviceConfigurationFeature spineapi.FeatureLocalInterface + + eventCalled bool +} + +func (s *MuMPCSuite) Event(ski string, device spineapi.DeviceRemoteInterface, entity spineapi.EntityRemoteInterface, event api.EventType) { + s.eventCalled = true +} + +func (s *MuMPCSuite) BeforeTest(suiteName, testName string) { + s.eventCalled = false + cert, _ := cert.CreateCertificate("test", "test", "DE", "test") + configuration, _ := api.NewConfiguration( + "test", "test", "test", "test", + model.DeviceTypeTypeEnergyManagementSystem, + []model.EntityTypeType{model.EntityTypeTypeInverter}, + 9999, cert, time.Second*4) + + serviceHandler := mocks.NewServiceReaderInterface(s.T()) + serviceHandler.EXPECT().ServicePairingDetailUpdate(mock.Anything, mock.Anything).Return().Maybe() + + s.service = service.NewService(configuration, serviceHandler) + _ = s.service.Setup() + + mockRemoteDevice := spinemocks.NewDeviceRemoteInterface(s.T()) + s.mockRemoteEntity = spinemocks.NewEntityRemoteInterface(s.T()) + mockRemoteFeature := spinemocks.NewFeatureRemoteInterface(s.T()) + mockRemoteDevice.EXPECT().FeatureByEntityTypeAndRole(mock.Anything, mock.Anything, mock.Anything).Return(mockRemoteFeature).Maybe() + mockRemoteDevice.EXPECT().Ski().Return(remoteSki).Maybe() + s.mockRemoteEntity.EXPECT().Device().Return(mockRemoteDevice).Maybe() + s.mockRemoteEntity.EXPECT().EntityType().Return(mock.Anything).Maybe() + entityAddress := &model.EntityAddressType{} + s.mockRemoteEntity.EXPECT().Address().Return(entityAddress).Maybe() + mockRemoteFeature.EXPECT().DataCopy(mock.Anything).Return(mock.Anything).Maybe() + mockRemoteFeature.EXPECT().Address().Return(&model.FeatureAddressType{}).Maybe() + mockRemoteFeature.EXPECT().Operations().Return(nil).Maybe() + + localEntity := s.service.LocalDevice().EntityForType(model.EntityTypeTypeInverter) + s.sut = NewMPC(localEntity, s.Event) + s.sut.AddFeatures() + s.sut.AddUseCase() + + //s.remoteDevice, s.monitoredEntity = setupDevices(s.service, s.T()) +} diff --git a/usecases/mu/mpc/types.go b/usecases/mu/mpc/types.go new file mode 100644 index 00000000..67f2d3f0 --- /dev/null +++ b/usecases/mu/mpc/types.go @@ -0,0 +1,10 @@ +package mpc + +import "github.com/enbility/eebus-go/api" + +const ( + // Update of the list of remote entities supporting the Use Case + // + // Use `RemoteEntities` to get the current data + UseCaseSupportUpdate api.EventType = "mu-mpc-UseCaseSupportUpdate" +) diff --git a/usecases/mu/mpc/usecase.go b/usecases/mu/mpc/usecase.go new file mode 100644 index 00000000..e027ada9 --- /dev/null +++ b/usecases/mu/mpc/usecase.go @@ -0,0 +1,400 @@ +package mpc + +import ( + "github.com/enbility/eebus-go/api" + "github.com/enbility/eebus-go/features/server" + ucapi "github.com/enbility/eebus-go/usecases/api" + "github.com/enbility/eebus-go/usecases/usecase" + spineapi "github.com/enbility/spine-go/api" + "github.com/enbility/spine-go/model" + "github.com/enbility/spine-go/spine" + "github.com/enbility/spine-go/util" + "time" +) + +type MPC struct { + *usecase.UseCaseBase + + acPowerTotal *model.MeasurementIdType + acPower [3]*model.MeasurementIdType + acEnergyConsumed *model.MeasurementIdType + acEnergyProduced *model.MeasurementIdType + acCurrent [3]*model.MeasurementIdType + acVoltage [3]*model.MeasurementIdType // Phase to phase voltages are not supported (yet) + acFrequency *model.MeasurementIdType +} + +var _ ucapi.MuMPCInterface = (*MPC)(nil) + +// At the moment the MPC use case configures itself as a 3-phase meter by default (ABC). +func NewMPC(localEntity spineapi.EntityLocalInterface, eventCB api.EntityEventCallback) *MPC { + validActorTypes := []model.UseCaseActorType{model.UseCaseActorTypeMonitoringAppliance} + var validEntityTypes []model.EntityTypeType = nil // all entity types are valid + useCaseScenarios := []api.UseCaseScenario{ + { + Scenario: model.UseCaseScenarioSupportType(1), + Mandatory: true, + ServerFeatures: []model.FeatureTypeType{ + model.FeatureTypeTypeElectricalConnection, + model.FeatureTypeTypeMeasurement, + }, + }, + { + Scenario: model.UseCaseScenarioSupportType(2), + Mandatory: false, + ServerFeatures: []model.FeatureTypeType{ + model.FeatureTypeTypeElectricalConnection, + model.FeatureTypeTypeMeasurement, + }, + }, + { + Scenario: model.UseCaseScenarioSupportType(3), + Mandatory: false, + ServerFeatures: []model.FeatureTypeType{ + model.FeatureTypeTypeElectricalConnection, + model.FeatureTypeTypeMeasurement, + }, + }, + { + Scenario: model.UseCaseScenarioSupportType(4), + Mandatory: false, + ServerFeatures: []model.FeatureTypeType{ + model.FeatureTypeTypeElectricalConnection, + model.FeatureTypeTypeMeasurement, + }, + }, + { + Scenario: model.UseCaseScenarioSupportType(5), + Mandatory: false, + ServerFeatures: []model.FeatureTypeType{ + model.FeatureTypeTypeElectricalConnection, + model.FeatureTypeTypeMeasurement, + }, + }, + } + + u := usecase.NewUseCaseBase( + localEntity, + model.UseCaseActorTypeMonitoredUnit, + model.UseCaseNameTypeMonitoringOfPowerConsumption, + "1.0.0", + "release", + useCaseScenarios, + eventCB, + UseCaseSupportUpdate, + validActorTypes, + validEntityTypes) + + uc := &MPC{ + UseCaseBase: u, + } + + _ = spine.Events.Subscribe(uc) + + return uc +} + +func (e *MPC) AddFeatures() { + // server features + f := e.LocalEntity.GetOrAddFeature(model.FeatureTypeTypeElectricalConnection, model.RoleTypeServer) + f.AddFunctionType(model.FunctionTypeElectricalConnectionDescriptionListData, true, false) + f.AddFunctionType(model.FunctionTypeElectricalConnectionParameterDescriptionListData, true, false) + + f = e.LocalEntity.GetOrAddFeature(model.FeatureTypeTypeMeasurement, model.RoleTypeServer) + f.AddFunctionType(model.FunctionTypeMeasurementDescriptionListData, true, false) + f.AddFunctionType(model.FunctionTypeMeasurementConstraintsListData, true, false) + f.AddFunctionType(model.FunctionTypeMeasurementListData, true, false) + + measurements, err := server.NewMeasurement(e.LocalEntity) + if err != nil { + panic(err) + } + + e.acPowerTotal = measurements.AddDescription(model.MeasurementDescriptionDataType{ + MeasurementType: util.Ptr(model.MeasurementTypeTypePower), + CommodityType: util.Ptr(model.CommodityTypeTypeElectricity), + Unit: util.Ptr(model.UnitOfMeasurementTypeW), + ScopeType: util.Ptr(model.ScopeTypeTypeACPowerTotal), + }) + + for id := 0; id < len(e.acPower); id++ { + e.acPower[id] = measurements.AddDescription(model.MeasurementDescriptionDataType{ + MeasurementType: util.Ptr(model.MeasurementTypeTypePower), + CommodityType: util.Ptr(model.CommodityTypeTypeElectricity), + Unit: util.Ptr(model.UnitOfMeasurementTypeW), + ScopeType: util.Ptr(model.ScopeTypeTypeACPower), + }) + } + + e.acEnergyConsumed = measurements.AddDescription(model.MeasurementDescriptionDataType{ + MeasurementType: util.Ptr(model.MeasurementTypeTypeEnergy), + CommodityType: util.Ptr(model.CommodityTypeTypeElectricity), + Unit: util.Ptr(model.UnitOfMeasurementTypeWh), + ScopeType: util.Ptr(model.ScopeTypeTypeACEnergyConsumed), + }) + + e.acEnergyProduced = measurements.AddDescription(model.MeasurementDescriptionDataType{ + MeasurementType: util.Ptr(model.MeasurementTypeTypeEnergy), + CommodityType: util.Ptr(model.CommodityTypeTypeElectricity), + Unit: util.Ptr(model.UnitOfMeasurementTypeWh), + ScopeType: util.Ptr(model.ScopeTypeTypeACEnergyProduced), + }) + + for id := 0; id < len(e.acCurrent); id++ { + e.acCurrent[id] = measurements.AddDescription(model.MeasurementDescriptionDataType{ + MeasurementType: util.Ptr(model.MeasurementTypeTypeCurrent), + CommodityType: util.Ptr(model.CommodityTypeTypeElectricity), + Unit: util.Ptr(model.UnitOfMeasurementTypeA), + ScopeType: util.Ptr(model.ScopeTypeTypeACCurrent), + }) + } + + for id := 0; id < len(e.acVoltage); id++ { + e.acVoltage[id] = measurements.AddDescription(model.MeasurementDescriptionDataType{ + MeasurementType: util.Ptr(model.MeasurementTypeTypeVoltage), + CommodityType: util.Ptr(model.CommodityTypeTypeElectricity), + Unit: util.Ptr(model.UnitOfMeasurementTypeV), + ScopeType: util.Ptr(model.ScopeTypeTypeACVoltage), + }) + } + + e.acFrequency = measurements.AddDescription(model.MeasurementDescriptionDataType{ + MeasurementType: util.Ptr(model.MeasurementTypeTypeFrequency), + CommodityType: util.Ptr(model.CommodityTypeTypeElectricity), + Unit: util.Ptr(model.UnitOfMeasurementTypeHz), + ScopeType: util.Ptr(model.ScopeTypeTypeACFrequency), + }) + + electricalConnection, err := server.NewElectricalConnection(e.LocalEntity) + if err != nil { + panic(err) + } + + idEc1 := model.ElectricalConnectionIdType(0) + ec1 := model.ElectricalConnectionDescriptionDataType{ + ElectricalConnectionId: util.Ptr(idEc1), + PowerSupplyType: util.Ptr(model.ElectricalConnectionVoltageTypeTypeAc), + PositiveEnergyDirection: util.Ptr(model.EnergyDirectionTypeConsume), + } + if err := electricalConnection.AddDescription(ec1); err != nil { + panic(err) + } + + p1 := model.ElectricalConnectionParameterDescriptionDataType{ + ElectricalConnectionId: util.Ptr(idEc1), + MeasurementId: e.acPowerTotal, + VoltageType: util.Ptr(model.ElectricalConnectionVoltageTypeTypeAc), + AcMeasuredPhases: util.Ptr(model.ElectricalConnectionPhaseNameTypeAbc), + AcMeasuredInReferenceTo: util.Ptr(model.ElectricalConnectionPhaseNameTypeNeutral), + AcMeasurementType: util.Ptr(model.ElectricalConnectionAcMeasurementTypeTypeReal), + AcMeasurementVariant: util.Ptr(model.ElectricalConnectionMeasurandVariantTypeRms), + } + idP1 := electricalConnection.AddParameterDescription(p1) + if idP1 == nil { + panic("error adding parameter description") + } + + p21 := model.ElectricalConnectionParameterDescriptionDataType{ + ElectricalConnectionId: util.Ptr(idEc1), + MeasurementId: e.acPower[0], + VoltageType: util.Ptr(model.ElectricalConnectionVoltageTypeTypeAc), + AcMeasuredPhases: util.Ptr(model.ElectricalConnectionPhaseNameTypeA), + AcMeasuredInReferenceTo: util.Ptr(model.ElectricalConnectionPhaseNameTypeNeutral), + AcMeasurementType: util.Ptr(model.ElectricalConnectionAcMeasurementTypeTypeReal), + AcMeasurementVariant: util.Ptr(model.ElectricalConnectionMeasurandVariantTypeRms), + } + idP21 := electricalConnection.AddParameterDescription(p21) + if idP21 == nil { + panic("error adding parameter description") + } + + p22 := model.ElectricalConnectionParameterDescriptionDataType{ + ElectricalConnectionId: util.Ptr(idEc1), + MeasurementId: e.acPower[1], + VoltageType: util.Ptr(model.ElectricalConnectionVoltageTypeTypeAc), + AcMeasuredPhases: util.Ptr(model.ElectricalConnectionPhaseNameTypeB), + AcMeasuredInReferenceTo: util.Ptr(model.ElectricalConnectionPhaseNameTypeNeutral), + AcMeasurementType: util.Ptr(model.ElectricalConnectionAcMeasurementTypeTypeReal), + AcMeasurementVariant: util.Ptr(model.ElectricalConnectionMeasurandVariantTypeRms), + } + idP22 := electricalConnection.AddParameterDescription(p22) + if idP22 == nil { + panic("error adding parameter description") + } + + p23 := model.ElectricalConnectionParameterDescriptionDataType{ + ElectricalConnectionId: util.Ptr(idEc1), + MeasurementId: e.acPower[2], + VoltageType: util.Ptr(model.ElectricalConnectionVoltageTypeTypeAc), + AcMeasuredPhases: util.Ptr(model.ElectricalConnectionPhaseNameTypeC), + AcMeasuredInReferenceTo: util.Ptr(model.ElectricalConnectionPhaseNameTypeNeutral), + AcMeasurementType: util.Ptr(model.ElectricalConnectionAcMeasurementTypeTypeReal), + AcMeasurementVariant: util.Ptr(model.ElectricalConnectionMeasurandVariantTypeRms), + } + idP23 := electricalConnection.AddParameterDescription(p23) + if idP23 == nil { + panic("error adding parameter description") + } + + p3 := model.ElectricalConnectionParameterDescriptionDataType{ + ElectricalConnectionId: util.Ptr(idEc1), + MeasurementId: e.acEnergyConsumed, + VoltageType: util.Ptr(model.ElectricalConnectionVoltageTypeTypeAc), + AcMeasurementType: util.Ptr(model.ElectricalConnectionAcMeasurementTypeTypeReal), + } + idP3 := electricalConnection.AddParameterDescription(p3) + if idP3 == nil { + panic("error adding parameter description") + } + + p4 := model.ElectricalConnectionParameterDescriptionDataType{ + ElectricalConnectionId: util.Ptr(idEc1), + MeasurementId: e.acEnergyProduced, + VoltageType: util.Ptr(model.ElectricalConnectionVoltageTypeTypeAc), + AcMeasurementType: util.Ptr(model.ElectricalConnectionAcMeasurementTypeTypeReal), + } + idP4 := electricalConnection.AddParameterDescription(p4) + if idP4 == nil { + panic("error adding parameter description") + } + + p51 := model.ElectricalConnectionParameterDescriptionDataType{ + ElectricalConnectionId: util.Ptr(idEc1), + MeasurementId: e.acCurrent[0], + VoltageType: util.Ptr(model.ElectricalConnectionVoltageTypeTypeAc), + AcMeasuredPhases: util.Ptr(model.ElectricalConnectionPhaseNameTypeA), + AcMeasurementType: util.Ptr(model.ElectricalConnectionAcMeasurementTypeTypeReal), + AcMeasurementVariant: util.Ptr(model.ElectricalConnectionMeasurandVariantTypeRms), + } + idP51 := electricalConnection.AddParameterDescription(p51) + if idP51 == nil { + panic("error adding parameter description") + } + + p52 := model.ElectricalConnectionParameterDescriptionDataType{ + ElectricalConnectionId: util.Ptr(idEc1), + MeasurementId: e.acCurrent[1], + VoltageType: util.Ptr(model.ElectricalConnectionVoltageTypeTypeAc), + AcMeasuredPhases: util.Ptr(model.ElectricalConnectionPhaseNameTypeB), + AcMeasurementType: util.Ptr(model.ElectricalConnectionAcMeasurementTypeTypeReal), + AcMeasurementVariant: util.Ptr(model.ElectricalConnectionMeasurandVariantTypeRms), + } + idP52 := electricalConnection.AddParameterDescription(p52) + if idP52 == nil { + panic("error adding parameter description") + } + + p53 := model.ElectricalConnectionParameterDescriptionDataType{ + ElectricalConnectionId: util.Ptr(idEc1), + MeasurementId: e.acCurrent[2], + VoltageType: util.Ptr(model.ElectricalConnectionVoltageTypeTypeAc), + AcMeasuredPhases: util.Ptr(model.ElectricalConnectionPhaseNameTypeC), + AcMeasurementType: util.Ptr(model.ElectricalConnectionAcMeasurementTypeTypeReal), + AcMeasurementVariant: util.Ptr(model.ElectricalConnectionMeasurandVariantTypeRms), + } + idP53 := electricalConnection.AddParameterDescription(p53) + if idP53 == nil { + panic("error adding parameter description") + } + + p61 := model.ElectricalConnectionParameterDescriptionDataType{ + ElectricalConnectionId: util.Ptr(idEc1), + MeasurementId: e.acVoltage[0], + VoltageType: util.Ptr(model.ElectricalConnectionVoltageTypeTypeAc), + AcMeasuredPhases: util.Ptr(model.ElectricalConnectionPhaseNameTypeA), + AcMeasuredInReferenceTo: util.Ptr(model.ElectricalConnectionPhaseNameTypeNeutral), + AcMeasurementType: util.Ptr(model.ElectricalConnectionAcMeasurementTypeTypeApparent), + AcMeasurementVariant: util.Ptr(model.ElectricalConnectionMeasurandVariantTypeRms), + } + idP61 := electricalConnection.AddParameterDescription(p61) + if idP61 == nil { + panic("error adding parameter description") + } + + p62 := model.ElectricalConnectionParameterDescriptionDataType{ + ElectricalConnectionId: util.Ptr(idEc1), + MeasurementId: e.acVoltage[1], + VoltageType: util.Ptr(model.ElectricalConnectionVoltageTypeTypeAc), + AcMeasuredPhases: util.Ptr(model.ElectricalConnectionPhaseNameTypeB), + AcMeasuredInReferenceTo: util.Ptr(model.ElectricalConnectionPhaseNameTypeNeutral), + AcMeasurementType: util.Ptr(model.ElectricalConnectionAcMeasurementTypeTypeApparent), + AcMeasurementVariant: util.Ptr(model.ElectricalConnectionMeasurandVariantTypeRms), + } + idP62 := electricalConnection.AddParameterDescription(p62) + if idP62 == nil { + panic("error adding parameter description") + } + + p63 := model.ElectricalConnectionParameterDescriptionDataType{ + ElectricalConnectionId: util.Ptr(idEc1), + MeasurementId: e.acVoltage[2], + VoltageType: util.Ptr(model.ElectricalConnectionVoltageTypeTypeAc), + AcMeasuredPhases: util.Ptr(model.ElectricalConnectionPhaseNameTypeC), + AcMeasuredInReferenceTo: util.Ptr(model.ElectricalConnectionPhaseNameTypeNeutral), + AcMeasurementType: util.Ptr(model.ElectricalConnectionAcMeasurementTypeTypeApparent), + AcMeasurementVariant: util.Ptr(model.ElectricalConnectionMeasurandVariantTypeRms), + } + idP63 := electricalConnection.AddParameterDescription(p63) + if idP63 == nil { + panic("error adding parameter description") + } + + p7 := model.ElectricalConnectionParameterDescriptionDataType{ + ElectricalConnectionId: util.Ptr(idEc1), + MeasurementId: e.acFrequency, + VoltageType: util.Ptr(model.ElectricalConnectionVoltageTypeTypeAc), + } + idP7 := electricalConnection.AddParameterDescription(p7) + if idP7 == nil { + panic("error adding parameter description") + } + + for _, id := range []*model.MeasurementIdType{ + e.acPowerTotal, + e.acPower[0], e.acPower[1], e.acPower[2], + e.acEnergyConsumed, + e.acEnergyProduced, + e.acCurrent[0], e.acCurrent[1], e.acCurrent[2], + e.acVoltage[0], e.acVoltage[1], e.acVoltage[2], + e.acFrequency} { + if err := e.setMeasurementDataForId(id, 0); err != nil { + panic(err) + } + } +} + +func (e *MPC) setMeasurementDataForId(id *model.MeasurementIdType, measurementData float64) error { + measurements, err := server.NewMeasurement(e.LocalEntity) + if err != nil { + return err + } + + err = measurements.UpdateDataForId(model.MeasurementDataType{ + MeasurementId: id, + ValueType: util.Ptr(model.MeasurementValueTypeTypeValue), + Timestamp: model.NewAbsoluteOrRelativeTimeTypeFromTime(time.Now()), + Value: model.NewScaledNumberType(measurementData), + ValueSource: util.Ptr(model.MeasurementValueSourceTypeMeasuredValue), + ValueState: util.Ptr(model.MeasurementValueStateTypeNormal), + }, nil, *id) + + return err +} + +func (e *MPC) getMeasurementDataForId(id *model.MeasurementIdType) (float64, error) { + measurements, err := server.NewMeasurement(e.LocalEntity) + if err != nil { + return 0, err + } + + data, err := measurements.GetDataForId(*id) + if err != nil { + return 0, err + } + + if data == nil { + return 0, api.ErrDataNotAvailable + } + + return data.Value.GetValue(), nil +} diff --git a/usecases/usecase/events.go b/usecases/usecase/events.go index 0bab5b49..b5351350 100644 --- a/usecases/usecase/events.go +++ b/usecases/usecase/events.go @@ -81,7 +81,7 @@ func (u *UseCaseBase) useCaseDataUpdate( } for _, entity := range entitiesToCheck { - if !slices.Contains(u.validEntityTypes, entity.EntityType()) { + if u.validEntityTypes != nil && !slices.Contains(u.validEntityTypes, entity.EntityType()) { continue } diff --git a/usecases/usecase/usecase.go b/usecases/usecase/usecase.go index 863f7b05..2b92a554 100644 --- a/usecases/usecase/usecase.go +++ b/usecases/usecase/usecase.go @@ -26,7 +26,7 @@ type UseCaseBase struct { availableEntityScenarios []api.RemoteEntityScenarios // map of scenarios and their availability for each compatible remote entity validActorTypes []model.UseCaseActorType // valid remote actor types for this use case - validEntityTypes []model.EntityTypeType // valid remote entity types for this use case + validEntityTypes []model.EntityTypeType // valid remote entity types for this use case (if nil all are valid) mux sync.Mutex } @@ -91,6 +91,10 @@ func (u *UseCaseBase) IsCompatibleEntityType(entity spineapi.EntityRemoteInterfa return false } + if u.validEntityTypes == nil { + return true + } + return slices.Contains(u.validEntityTypes, entity.EntityType()) }