Skip to content

Commit 62108f5

Browse files
committed
Only add scenarios when they're supported
This commit also adds some fixes to allow leaving unsupported config parameters to NewMPC as nil and a test for those scenarios.
1 parent c70256a commit 62108f5

File tree

2 files changed

+249
-67
lines changed

2 files changed

+249
-67
lines changed

usecases/mu/mpc/usecase.go

Lines changed: 98 additions & 67 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,19 @@ type MPC struct {
3131
acFrequency *model.MeasurementIdType
3232
}
3333

34+
// creates a new MPC usecase instance for a MonitoredUnit entity
35+
//
36+
// parameters:
37+
// - localEntity: the local entity for which to construct an MPC instance
38+
// - eventCB: the callback to notify about events for this usecase
39+
// - monitorPowerConfig: (required) configuration parameters for MPC scenario 1
40+
// - monitorEnergyConfig: (optional) configuration parameters for MPC scenario 2, nil if not supported
41+
// - monitorCurrentConfig: (optional) configuration parameters for MPC scenario 3, nil if not supported
42+
// - monitorVoltageConfig: (optional) configuration parameters for MPC scenario 4, nil if not supported
43+
// - monitorFrequencyConfig: (optional) configuration parameters for MPC scenario, nil if not supported
44+
//
45+
// possible errors:
46+
// - if required fields in parameters are unset
3447
func NewMPC(
3548
localEntity spineapi.EntityLocalInterface,
3649
eventCB api.EntityEventCallback,
@@ -54,38 +67,50 @@ func NewMPC(
5467
model.FeatureTypeTypeMeasurement,
5568
},
5669
},
57-
{
70+
}
71+
72+
if monitorEnergyConfig != nil {
73+
useCaseScenarios = append(useCaseScenarios, api.UseCaseScenario{
5874
Scenario: model.UseCaseScenarioSupportType(2),
5975
Mandatory: false,
6076
ServerFeatures: []model.FeatureTypeType{
6177
model.FeatureTypeTypeElectricalConnection,
6278
model.FeatureTypeTypeMeasurement,
6379
},
64-
},
65-
{
80+
})
81+
}
82+
83+
if monitorCurrentConfig != nil {
84+
useCaseScenarios = append(useCaseScenarios, api.UseCaseScenario{
6685
Scenario: model.UseCaseScenarioSupportType(3),
6786
Mandatory: false,
6887
ServerFeatures: []model.FeatureTypeType{
6988
model.FeatureTypeTypeElectricalConnection,
7089
model.FeatureTypeTypeMeasurement,
7190
},
72-
},
73-
{
91+
})
92+
}
93+
94+
if monitorVoltageConfig != nil {
95+
useCaseScenarios = append(useCaseScenarios, api.UseCaseScenario{
7496
Scenario: model.UseCaseScenarioSupportType(4),
7597
Mandatory: false,
7698
ServerFeatures: []model.FeatureTypeType{
7799
model.FeatureTypeTypeElectricalConnection,
78100
model.FeatureTypeTypeMeasurement,
79101
},
80-
},
81-
{
102+
})
103+
}
104+
105+
if monitorFrequencyConfig != nil {
106+
useCaseScenarios = append(useCaseScenarios, api.UseCaseScenario{
82107
Scenario: model.UseCaseScenarioSupportType(5),
83108
Mandatory: false,
84109
ServerFeatures: []model.FeatureTypeType{
85110
model.FeatureTypeTypeElectricalConnection,
86111
model.FeatureTypeTypeMeasurement,
87112
},
88-
},
113+
})
89114
}
90115

91116
u := usecase.NewUseCaseBase(
@@ -175,74 +200,80 @@ func (e *MPC) AddFeatures() {
175200
}
176201
}
177202

178-
if e.energyConfig.ValueSourceConsumption != nil {
179-
e.acEnergyConsumed = measurements.AddDescription(model.MeasurementDescriptionDataType{
180-
MeasurementType: util.Ptr(model.MeasurementTypeTypeEnergy),
181-
CommodityType: util.Ptr(model.CommodityTypeTypeElectricity),
182-
Unit: util.Ptr(model.UnitOfMeasurementTypeWh),
183-
ScopeType: util.Ptr(model.ScopeTypeTypeACEnergyConsumed),
184-
})
185-
if e.energyConfig.ValueConstraintsConsumption != nil {
186-
e.energyConfig.ValueConstraintsConsumption.MeasurementId = e.acEnergyConsumed
187-
constraints = append(constraints, *e.energyConfig.ValueConstraintsConsumption)
188-
}
189-
}
190-
191-
if e.energyConfig.ValueSourceProduction != nil {
192-
e.acEnergyProduced = measurements.AddDescription(model.MeasurementDescriptionDataType{
193-
MeasurementType: util.Ptr(model.MeasurementTypeTypeEnergy),
194-
CommodityType: util.Ptr(model.CommodityTypeTypeElectricity),
195-
Unit: util.Ptr(model.UnitOfMeasurementTypeWh),
196-
ScopeType: util.Ptr(model.ScopeTypeTypeACEnergyProduced),
197-
})
198-
if e.energyConfig.ValueConstraintsProduction != nil {
199-
e.energyConfig.ValueConstraintsProduction.MeasurementId = e.acEnergyProduced
200-
constraints = append(constraints, *e.energyConfig.ValueConstraintsProduction)
203+
if e.energyConfig != nil {
204+
if e.energyConfig.ValueSourceConsumption != nil {
205+
e.acEnergyConsumed = measurements.AddDescription(model.MeasurementDescriptionDataType{
206+
MeasurementType: util.Ptr(model.MeasurementTypeTypeEnergy),
207+
CommodityType: util.Ptr(model.CommodityTypeTypeElectricity),
208+
Unit: util.Ptr(model.UnitOfMeasurementTypeWh),
209+
ScopeType: util.Ptr(model.ScopeTypeTypeACEnergyConsumed),
210+
})
211+
if e.energyConfig.ValueConstraintsConsumption != nil {
212+
e.energyConfig.ValueConstraintsConsumption.MeasurementId = e.acEnergyConsumed
213+
constraints = append(constraints, *e.energyConfig.ValueConstraintsConsumption)
214+
}
201215
}
202-
}
203216

204-
acCurrentConstraints := []*model.MeasurementConstraintsDataType{
205-
e.currentConfig.ValueConstraintsPhaseA,
206-
e.currentConfig.ValueConstraintsPhaseB,
207-
e.currentConfig.ValueConstraintsPhaseC,
208-
}
209-
for id := 0; id < len(e.acCurrent); id++ {
210-
if e.powerConfig.SupportsPhases(phases[id]) {
211-
e.acCurrent[id] = measurements.AddDescription(model.MeasurementDescriptionDataType{
212-
MeasurementType: util.Ptr(model.MeasurementTypeTypeCurrent),
217+
if e.energyConfig.ValueSourceProduction != nil {
218+
e.acEnergyProduced = measurements.AddDescription(model.MeasurementDescriptionDataType{
219+
MeasurementType: util.Ptr(model.MeasurementTypeTypeEnergy),
213220
CommodityType: util.Ptr(model.CommodityTypeTypeElectricity),
214-
Unit: util.Ptr(model.UnitOfMeasurementTypeA),
215-
ScopeType: util.Ptr(model.ScopeTypeTypeACCurrent),
221+
Unit: util.Ptr(model.UnitOfMeasurementTypeWh),
222+
ScopeType: util.Ptr(model.ScopeTypeTypeACEnergyProduced),
216223
})
217-
if acCurrentConstraints[id] != nil {
218-
acCurrentConstraints[id].MeasurementId = e.acCurrent[id]
219-
constraints = append(constraints, *acCurrentConstraints[id])
224+
if e.energyConfig.ValueConstraintsProduction != nil {
225+
e.energyConfig.ValueConstraintsProduction.MeasurementId = e.acEnergyProduced
226+
constraints = append(constraints, *e.energyConfig.ValueConstraintsProduction)
220227
}
221228
}
222229
}
223230

224-
acVoltageConstraints := []*model.MeasurementConstraintsDataType{
225-
e.voltageConfig.ValueConstraintsPhaseA,
226-
e.voltageConfig.ValueConstraintsPhaseB,
227-
e.voltageConfig.ValueConstraintsPhaseC,
228-
e.voltageConfig.ValueConstraintsPhaseAToB,
229-
e.voltageConfig.ValueConstraintsPhaseBToC,
230-
e.voltageConfig.ValueConstraintsPhaseCToA,
231-
}
232-
for id := 0; id < len(e.acVoltage); id++ {
233-
if e.powerConfig.SupportsPhases(phases[id]) {
234-
if len(phases[id]) == 2 && !e.voltageConfig.SupportPhaseToPhase {
235-
continue
231+
if e.currentConfig != nil {
232+
acCurrentConstraints := []*model.MeasurementConstraintsDataType{
233+
e.currentConfig.ValueConstraintsPhaseA,
234+
e.currentConfig.ValueConstraintsPhaseB,
235+
e.currentConfig.ValueConstraintsPhaseC,
236+
}
237+
for id := 0; id < len(e.acCurrent); id++ {
238+
if e.powerConfig.SupportsPhases(phases[id]) {
239+
e.acCurrent[id] = measurements.AddDescription(model.MeasurementDescriptionDataType{
240+
MeasurementType: util.Ptr(model.MeasurementTypeTypeCurrent),
241+
CommodityType: util.Ptr(model.CommodityTypeTypeElectricity),
242+
Unit: util.Ptr(model.UnitOfMeasurementTypeA),
243+
ScopeType: util.Ptr(model.ScopeTypeTypeACCurrent),
244+
})
245+
if acCurrentConstraints[id] != nil {
246+
acCurrentConstraints[id].MeasurementId = e.acCurrent[id]
247+
constraints = append(constraints, *acCurrentConstraints[id])
248+
}
236249
}
237-
e.acVoltage[id] = measurements.AddDescription(model.MeasurementDescriptionDataType{
238-
MeasurementType: util.Ptr(model.MeasurementTypeTypeVoltage),
239-
CommodityType: util.Ptr(model.CommodityTypeTypeElectricity),
240-
Unit: util.Ptr(model.UnitOfMeasurementTypeV),
241-
ScopeType: util.Ptr(model.ScopeTypeTypeACVoltage),
242-
})
243-
if acVoltageConstraints[id] != nil {
244-
acVoltageConstraints[id].MeasurementId = e.acVoltage[id]
245-
constraints = append(constraints, *acVoltageConstraints[id])
250+
}
251+
}
252+
253+
if e.voltageConfig != nil {
254+
acVoltageConstraints := []*model.MeasurementConstraintsDataType{
255+
e.voltageConfig.ValueConstraintsPhaseA,
256+
e.voltageConfig.ValueConstraintsPhaseB,
257+
e.voltageConfig.ValueConstraintsPhaseC,
258+
e.voltageConfig.ValueConstraintsPhaseAToB,
259+
e.voltageConfig.ValueConstraintsPhaseBToC,
260+
e.voltageConfig.ValueConstraintsPhaseCToA,
261+
}
262+
for id := 0; id < len(e.acVoltage); id++ {
263+
if e.powerConfig.SupportsPhases(phases[id]) {
264+
if len(phases[id]) == 2 && !e.voltageConfig.SupportPhaseToPhase {
265+
continue
266+
}
267+
e.acVoltage[id] = measurements.AddDescription(model.MeasurementDescriptionDataType{
268+
MeasurementType: util.Ptr(model.MeasurementTypeTypeVoltage),
269+
CommodityType: util.Ptr(model.CommodityTypeTypeElectricity),
270+
Unit: util.Ptr(model.UnitOfMeasurementTypeV),
271+
ScopeType: util.Ptr(model.ScopeTypeTypeACVoltage),
272+
})
273+
if acVoltageConstraints[id] != nil {
274+
acVoltageConstraints[id].MeasurementId = e.acVoltage[id]
275+
constraints = append(constraints, *acVoltageConstraints[id])
276+
}
246277
}
247278
}
248279
}

usecases/mu/mpc/usecase_test.go

Lines changed: 151 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,151 @@
1+
package mpc
2+
3+
import (
4+
"testing"
5+
"time"
6+
7+
"github.com/enbility/eebus-go/api"
8+
"github.com/enbility/eebus-go/mocks"
9+
"github.com/enbility/eebus-go/service"
10+
shipapi "github.com/enbility/ship-go/api"
11+
"github.com/enbility/ship-go/cert"
12+
spineapi "github.com/enbility/spine-go/api"
13+
spinemocks "github.com/enbility/spine-go/mocks"
14+
"github.com/enbility/spine-go/model"
15+
"github.com/enbility/spine-go/util"
16+
"github.com/stretchr/testify/assert"
17+
"github.com/stretchr/testify/mock"
18+
"github.com/stretchr/testify/suite"
19+
)
20+
21+
func TestBasicSuite(t *testing.T) {
22+
suite.Run(t, new(BasicSuite))
23+
}
24+
25+
type BasicSuite struct {
26+
suite.Suite
27+
28+
service api.ServiceInterface
29+
30+
remoteDevice spineapi.DeviceRemoteInterface
31+
mockRemoteEntity *spinemocks.EntityRemoteInterface
32+
monitoredEntity spineapi.EntityRemoteInterface
33+
loadControlFeature,
34+
deviceDiagnosisFeature,
35+
deviceConfigurationFeature spineapi.FeatureLocalInterface
36+
37+
eventCalled bool
38+
}
39+
40+
func (s *BasicSuite) Event(ski string, device spineapi.DeviceRemoteInterface, entity spineapi.EntityRemoteInterface, event api.EventType) {
41+
s.eventCalled = true
42+
}
43+
44+
func (s *BasicSuite) BeforeTest(suiteName, testName string) {
45+
s.eventCalled = false
46+
cert, _ := cert.CreateCertificate("test", "test", "DE", "test")
47+
configuration, _ := api.NewConfiguration(
48+
"test", "test", "test", "test",
49+
[]shipapi.DeviceCategoryType{shipapi.DeviceCategoryTypeEnergyManagementSystem},
50+
model.DeviceTypeTypeEnergyManagementSystem,
51+
[]model.EntityTypeType{model.EntityTypeTypeInverter},
52+
9999, cert, time.Second*4)
53+
54+
serviceHandler := mocks.NewServiceReaderInterface(s.T())
55+
serviceHandler.EXPECT().ServicePairingDetailUpdate(mock.Anything, mock.Anything).Return().Maybe()
56+
57+
s.service = service.NewService(configuration, serviceHandler)
58+
_ = s.service.Setup()
59+
60+
mockRemoteDevice := spinemocks.NewDeviceRemoteInterface(s.T())
61+
s.mockRemoteEntity = spinemocks.NewEntityRemoteInterface(s.T())
62+
mockRemoteFeature := spinemocks.NewFeatureRemoteInterface(s.T())
63+
mockRemoteDevice.EXPECT().FeatureByEntityTypeAndRole(mock.Anything, mock.Anything, mock.Anything).Return(mockRemoteFeature).Maybe()
64+
mockRemoteDevice.EXPECT().Ski().Return(remoteSki).Maybe()
65+
s.mockRemoteEntity.EXPECT().Device().Return(mockRemoteDevice).Maybe()
66+
s.mockRemoteEntity.EXPECT().EntityType().Return(mock.Anything).Maybe()
67+
entityAddress := &model.EntityAddressType{}
68+
s.mockRemoteEntity.EXPECT().Address().Return(entityAddress).Maybe()
69+
mockRemoteFeature.EXPECT().DataCopy(mock.Anything).Return(mock.Anything).Maybe()
70+
mockRemoteFeature.EXPECT().Address().Return(&model.FeatureAddressType{}).Maybe()
71+
mockRemoteFeature.EXPECT().Operations().Return(nil).Maybe()
72+
}
73+
74+
func (s *BasicSuite) Test_MpcOptionalParameters() {
75+
localEntity := s.service.LocalDevice().EntityForType(model.EntityTypeTypeInverter)
76+
77+
// required
78+
var monitorPowerConfig = MonitorPowerConfig{
79+
ConnectedPhases: ConnectedPhasesABC,
80+
ValueSourceTotal: util.Ptr(model.MeasurementValueSourceTypeMeasuredValue),
81+
ValueSourcePhaseA: util.Ptr(model.MeasurementValueSourceTypeMeasuredValue),
82+
ValueSourcePhaseB: util.Ptr(model.MeasurementValueSourceTypeMeasuredValue),
83+
ValueSourcePhaseC: util.Ptr(model.MeasurementValueSourceTypeMeasuredValue),
84+
}
85+
86+
// the following 4 parameters are optional and can be nil
87+
var monitorEnergyConfig = MonitorEnergyConfig{
88+
ValueSourceProduction: util.Ptr(model.MeasurementValueSourceTypeMeasuredValue),
89+
ValueSourceConsumption: util.Ptr(model.MeasurementValueSourceTypeMeasuredValue),
90+
}
91+
var monitorCurrentConfig = MonitorCurrentConfig{
92+
ValueSourcePhaseA: util.Ptr(model.MeasurementValueSourceTypeMeasuredValue),
93+
ValueSourcePhaseB: util.Ptr(model.MeasurementValueSourceTypeMeasuredValue),
94+
ValueSourcePhaseC: util.Ptr(model.MeasurementValueSourceTypeMeasuredValue),
95+
}
96+
var monitorVoltageConfig = MonitorVoltageConfig{
97+
SupportPhaseToPhase: true,
98+
ValueSourcePhaseA: util.Ptr(model.MeasurementValueSourceTypeMeasuredValue),
99+
ValueSourcePhaseB: util.Ptr(model.MeasurementValueSourceTypeMeasuredValue),
100+
ValueSourcePhaseC: util.Ptr(model.MeasurementValueSourceTypeMeasuredValue),
101+
ValueSourcePhaseAToB: util.Ptr(model.MeasurementValueSourceTypeMeasuredValue),
102+
ValueSourcePhaseBToC: util.Ptr(model.MeasurementValueSourceTypeMeasuredValue),
103+
ValueSourcePhaseCToA: util.Ptr(model.MeasurementValueSourceTypeMeasuredValue),
104+
}
105+
var monitorFrequencyConfig = MonitorFrequencyConfig{
106+
ValueSource: util.Ptr(model.MeasurementValueSourceTypeMeasuredValue),
107+
ValueConstraints: util.Ptr(model.MeasurementConstraintsDataType{
108+
ValueRangeMin: model.NewScaledNumberType(0),
109+
ValueRangeMax: model.NewScaledNumberType(100),
110+
ValueStepSize: model.NewScaledNumberType(1),
111+
}),
112+
}
113+
114+
numOptionalParams := 4
115+
116+
// iterate over all permutations of nil/set
117+
for i := 0; i < (1 << numOptionalParams); i++ {
118+
// Determine which parameters to set
119+
var optEnergyConfig *MonitorEnergyConfig
120+
var optCurrentConfig *MonitorCurrentConfig
121+
var optVoltageConfig *MonitorVoltageConfig
122+
var optFrequencyConfig *MonitorFrequencyConfig
123+
if i&1 != 0 {
124+
optEnergyConfig = &monitorEnergyConfig
125+
}
126+
if i&2 != 0 {
127+
optCurrentConfig = &monitorCurrentConfig
128+
}
129+
if i&4 != 0 {
130+
optVoltageConfig = &monitorVoltageConfig
131+
}
132+
if i&8 != 0 {
133+
optFrequencyConfig = &monitorFrequencyConfig
134+
}
135+
136+
mpc, err := NewMPC(
137+
localEntity,
138+
s.Event,
139+
&monitorPowerConfig,
140+
optEnergyConfig,
141+
optCurrentConfig,
142+
optVoltageConfig,
143+
optFrequencyConfig,
144+
)
145+
146+
assert.Nil(s.T(), err)
147+
148+
mpc.AddFeatures()
149+
mpc.AddUseCase()
150+
}
151+
}

0 commit comments

Comments
 (0)