-
Notifications
You must be signed in to change notification settings - Fork 23
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Implement the MPC use case for Monitored Units #108
base: dev
Are you sure you want to change the base?
Implement the MPC use case for Monitored Units #108
Conversation
186302a
to
47947ab
Compare
Thanks a lot for the PR! Some first thoughts, more will surely come: Having individual public functions for each scenario is what surely is expected and needed. But using those individually will result in a notify update message for each call, which I think is not nice. I do see 2 options here:
|
Regarding initialization: how about the possibility to define the supported scenarios, and for each scenario define a configuration item where items like amount of phases, measurement type, reference to etc. needs to be set. Scenario 1 is required, all other scenarios are option/recommended. And even within the scenarios there are optional items, e.g. phase specfiic power. Also I am wondering you only added setters, but maybe it is also helpful to have getters to e.g. validate the current storage? |
e8b7796
to
7e07acf
Compare
a82f86c
to
193fc5c
Compare
I've updated the PR with support for updating multiple measurements at the same time using the Update() function. For an example as to how to use the Update() function look at the Test_PowerPerPhase test. The mu/mpc use case is now also configurable for e.g. non-three-phase use cases by passing configuration parameters to the NewMpc function. The config types are located in usecases/mu/mpc/config.go. For an example as to how to use these configuration parameters, look at the MPC testhelper |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Awesome updates, I really like the way you solve the multiple datapoint update topic!
model.FeatureTypeTypeMeasurement, | ||
}, | ||
}, | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The scenarios should only be added, if the configuration for this scenario is provided and hence the application supports it. Right now this code would always report that all scenarios are supported.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should be fixed now, though it might be nice to have tests for it as well.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes. Test coverage still has potential to grow ;)
usecases/mu/mpc/usecase.go
Outdated
panic(err) | ||
} | ||
|
||
idEc1 := model.ElectricalConnectionIdType(0) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This will cause problems, as other use cases may use an eletrical connection in the entity, and they may not be identical.
Instead we should check if one exists with the properties defined in the spec and use that. Otherwise create one.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We added a GetOrAddIdForDescription function to the electricalConnection which checks if there's an ID we can reues and otherwise creates a new id
usecases/mu/mpc/testhelper_test.go
Outdated
s.sut.AddFeatures() | ||
s.sut.AddUseCase() | ||
|
||
//s.remoteDevice, s.monitoredEntity = setupDevices(s.service, s.T()) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I would love to see such tests as in CS/LPC to be added, not just using the mocks.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We updated the tests to use Filters to query the stored data representation for the usecases. This should test that querying the data works from the client perspective without needing to reimplement the ma/mpc use cases in the tests for mu/mpc.
6834d88
to
62108f5
Compare
I've updated the PR with fixes for the simple changes, will look over the rest when I have more time |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Great improvements already, thank you!
usecases/mu/mpc/usecase.go
Outdated
return uc, nil | ||
} | ||
|
||
func (e *MPC) AddFeatures() { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This method is getting quite loooooooong. How about splitting it up into individual functions for each config?
Also I am still not sure if using panics is the right approach. Maybe AddFeatures()
signature should be changed to AddFeatures() error
overall ?
Also the code looks quite repetitive, and maybe there is a good way to shrink this down, and thus making it also easire to improve the test coverage for all error scenarios?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We split the constituent measurement types into separate methods, and adjusted AddFeatures and AddUsecase to return error for all usecases
Co-authored-by: Simon Thelen <[email protected]>
Implements the new approach added by enbility#115 to allow setting measurement values independently or at the same time
This commit also adds some fixes to allow leaving unsupported config parameters to NewMPC as nil and a test for those scenarios.
…lConnectionId for a specific ElectricalConnectionDescription
62108f5
to
541ba0a
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We are getting there :)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This file contains some unused variables in BasicSuite
. Would be great to remove or comment them.
@@ -31,6 +31,48 @@ func NewElectricalConnection(localEntity spineapi.EntityLocalInterface) (*Electr | |||
return ec, nil | |||
} | |||
|
|||
func (e *ElectricalConnection) GetOrAddIdForDescription( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please add comments for this function to understand its usage
description.Label == electricalConnectionDescription.Label && | ||
description.Description == electricalConnectionDescription.Description { | ||
electricalConnectionId = description.ElectricalConnectionId | ||
break |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
How about returning here directly? That would remove the need for the if statement in line 62
// client features | ||
_ = e.LocalEntity.GetOrAddFeature(model.FeatureTypeTypeDeviceDiagnosis, model.RoleTypeClient) | ||
|
||
// server features | ||
f := e.LocalEntity.GetOrAddFeature(model.FeatureTypeTypeLoadControl, model.RoleTypeServer) | ||
f.AddFunctionType(model.FunctionTypeLoadControlLimitDescriptionListData, true, false) | ||
f.AddFunctionType(model.FunctionTypeLoadControlLimitListData, true, true) | ||
_ = f.AddWriteApprovalCallback(e.loadControlWriteCB) | ||
err := f.AddWriteApprovalCallback(e.loadControlWriteCB) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
How about (for consistency) use if err := ...; err != nil ...
?
@@ -204,6 +208,8 @@ func (e *LPC) AddFeatures() { | |||
}, | |||
} | |||
_ = lc.UpdateLimitDataForIds(newLimiData) | |||
} else { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
With these long if statements, it might be easier to read the code if the error and return are handled directly in the if statement.
So changing instances like this into
dcs, err := ...
if err != nil {
return err
}
That also removes the need for an if statement for success.
In addition I think we should check very early for feature existence and exit before adding any data if any of the required features are missing. This applies to all modified usecase.go implementations.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If would love to see added testcases for checking the error handling, e.g. by adding tests to usecase_test.go like:
func (s *CsLPCSuite) Test_AddFeatures() {
localEntity := spinemocks.NewEntityLocalInterface(s.T())
s.sut.LocalEntity = localEntity
mockLc := spinemocks.NewFeatureLocalInterface(s.T())
mockLc.EXPECT().AddFunctionType(mock.Anything, mock.Anything, mock.Anything).Return()
localEntity.EXPECT().GetOrAddFeature(model.FeatureTypeTypeDeviceDiagnosis, model.RoleTypeClient).Return(nil).Once()
localEntity.EXPECT().GetOrAddFeature(model.FeatureTypeTypeLoadControl, model.RoleTypeServer).Return(mockLc).Once()
expErr := errors.New("error")
mockLc.EXPECT().AddWriteApprovalCallback(mock.Anything).Return(expErr).Once()
err := s.sut.AddFeatures()
assert.NotNil(s.T(), err)
}
@@ -114,6 +117,10 @@ func (u *UseCaseBase) IsCompatibleEntityType(entity spineapi.EntityRemoteInterfa | |||
return false | |||
} | |||
|
|||
if u.allEntityTypesValid { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please add a test for this scenario, e..g by adding the following in usecase_test.go link 26:
s.uc.allEntityTypesValid = true
result = s.uc.IsCompatibleEntityType(payload.Entity)
assert.True(s.T(), result)
return nil | ||
} | ||
|
||
func (e *MPC) getMeasurementDataForId(id *model.MeasurementIdType) (float64, error) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please add tests for this function implementation
|
||
parameterDescriptionId := electricalConnection.AddParameterDescription(parameterDescription) | ||
if parameterDescriptionId == nil { | ||
return errors.New("could not add parameter description") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
These error cases could be tested by passing mocks of server.ElectricalConnection
and return an error on the function call.
Applies to all the other private functions as well.
for _, measurementDataForId := range updateData { | ||
if !measurementDataForId.Supported() { | ||
return measurementDataForId.NotSupportedError() | ||
} else { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The else
statement is not needed here, as we return in the other case.
So this code could be simplified to:
if !measurementDataForId.Supported() {
return measurementDataForId.NotSupportedError()
}
measurementDataForIds = append(measurementDataForIds, measurementDataForId.MeasurementData())
This adds an implementation of the MPC use case for Monitored Units.
The current implementation has 2 major restrictions so far:
I'm opening this as a Draft PR because while this UC works, it can still be improved and I'm looking for feedback and potential improvements. Some of the areas I'm explicitly looking for feedback in are: