Skip to content

Commit

Permalink
Refactor usecase handling
Browse files Browse the repository at this point in the history
- Remove UseCaseManager implementation
- Instead usecases are now set on the entity
- Use data factory instead of additional data storage only for use cases
- Remove all supported usecases if an entity is removed
- Add and update tests
- DataFactory now also support NodeManagement features
  • Loading branch information
DerAndereAndi committed Jan 8, 2024
1 parent a063168 commit 47a958b
Show file tree
Hide file tree
Showing 15 changed files with 463 additions and 395 deletions.
13 changes: 3 additions & 10 deletions spine/device.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,9 @@ package spine
import "github.com/enbility/eebus-go/spine/model"

type DeviceImpl struct {
address *model.AddressDeviceType
dType *model.DeviceTypeType
featureSet *model.NetworkManagementFeatureSetType
useCaseManager *UseCaseManager
address *model.AddressDeviceType
dType *model.DeviceTypeType
featureSet *model.NetworkManagementFeatureSetType
}

// Initialize a new device
Expand All @@ -15,8 +14,6 @@ type DeviceImpl struct {
func NewDeviceImpl(address *model.AddressDeviceType, dType *model.DeviceTypeType, featureSet *model.NetworkManagementFeatureSetType) *DeviceImpl {
deviceImpl := &DeviceImpl{}

deviceImpl.useCaseManager = NewUseCaseManager(deviceImpl)

if dType != nil {
deviceImpl.dType = dType
}
Expand All @@ -36,10 +33,6 @@ func (r *DeviceImpl) Address() *model.AddressDeviceType {
return r.address
}

func (r *DeviceImpl) UseCaseManager() *UseCaseManager {
return r.useCaseManager
}

func (r *DeviceImpl) DeviceType() *model.DeviceTypeType {
return r.dType
}
Expand Down
1 change: 1 addition & 0 deletions spine/device_local.go
Original file line number Diff line number Diff line change
Expand Up @@ -281,6 +281,7 @@ func (r *DeviceLocalImpl) AddEntity(entity *EntityLocalImpl) {
}

func (r *DeviceLocalImpl) RemoveEntity(entity *EntityLocalImpl) {
entity.RemoveAllUseCaseSupports()
entity.RemoveAllSubscriptions()
entity.RemoveAllBindings()

Expand Down
15 changes: 7 additions & 8 deletions spine/device_remote.go
Original file line number Diff line number Diff line change
Expand Up @@ -243,15 +243,18 @@ func (d *DeviceRemoteImpl) VerifyUseCaseScenariosAndFeaturesSupport(
scenarios []model.UseCaseScenarioSupportType,
serverFeatures []model.FeatureTypeType,
) bool {
remoteUseCaseManager := d.UseCaseManager()
entity := d.Entity(DeviceInformationAddressEntity)

usecases := remoteUseCaseManager.UseCaseInformation()
if len(usecases) == 0 {
nodemgmt := d.FeatureByEntityTypeAndRole(entity, model.FeatureTypeTypeNodeManagement, model.RoleTypeSpecial)

usecases := nodemgmt.Data(model.FunctionTypeNodeManagementUseCaseData).(*model.NodeManagementUseCaseDataType)

if usecases == nil || len(usecases.UseCaseInformation) == 0 {
return false
}

usecaseAndScenariosFound := false
for _, usecase := range usecases {
for _, usecase := range usecases.UseCaseInformation {
if usecase.Actor == nil || *usecase.Actor != usecaseActor {
continue
}
Expand All @@ -261,10 +264,6 @@ func (d *DeviceRemoteImpl) VerifyUseCaseScenariosAndFeaturesSupport(
continue
}

if support.UseCaseAvailable == nil || !*support.UseCaseAvailable {
continue
}

var foundScenarios []model.UseCaseScenarioSupportType
for _, scenario := range support.ScenarioSupport {
if slices.Contains(scenarios, scenario) {
Expand Down
92 changes: 73 additions & 19 deletions spine/device_remote_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,15 @@ import (
"time"

"github.com/enbility/eebus-go/spine/model"
"github.com/enbility/eebus-go/util"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/suite"
)

const (
nm_usecaseinformationlistdata_recv_reply_file_path = "../spine/testdata/nm_usecaseinformationlistdata_recv_reply.json"
)

func TestDeviceRemoteSuite(t *testing.T) {
suite.Run(t, new(DeviceRemoteSuite))
}
Expand All @@ -18,6 +23,7 @@ type DeviceRemoteSuite struct {

localDevice *DeviceLocalImpl
remoteDevice *DeviceRemoteImpl
remoteEntity *EntityRemoteImpl
}

func (s *DeviceRemoteSuite) WriteSpineMessage([]byte) {}
Expand All @@ -30,14 +36,15 @@ func (s *DeviceRemoteSuite) BeforeTest(suiteName, testName string) {
ski := "test"
sender := NewSender(s)
s.remoteDevice = NewDeviceRemoteImpl(s.localDevice, ski, sender)
s.remoteDevice.address = util.Ptr(model.AddressDeviceType("test"))
s.localDevice.AddRemoteDevice(ski, s)

entity := NewEntityRemoteImpl(s.remoteDevice, model.EntityTypeTypeEVSE, []model.AddressEntityType{1})
s.remoteEntity = NewEntityRemoteImpl(s.remoteDevice, model.EntityTypeTypeEVSE, []model.AddressEntityType{1})

feature := NewFeatureRemoteImpl(0, entity, model.FeatureTypeTypeDeviceDiagnosis, model.RoleTypeServer)
entity.AddFeature(feature)
feature := NewFeatureRemoteImpl(0, s.remoteEntity, model.FeatureTypeTypeDeviceDiagnosis, model.RoleTypeServer)
s.remoteEntity.AddFeature(feature)

s.remoteDevice.AddEntity(entity)
s.remoteDevice.AddEntity(s.remoteEntity)
}

func (s *DeviceRemoteSuite) Test_RemoveByAddress() {
Expand Down Expand Up @@ -73,6 +80,26 @@ func (s *DeviceRemoteSuite) Test_FeatureByEntityTypeAndRole() {
assert.Nil(s.T(), feature)
}

func (s *DeviceRemoteSuite) Test_VerifyUseCaseScenariosAndFeaturesSupport_ElliJSON() {
_, _ = s.remoteDevice.HandleIncomingSpineMesssage(loadFileData(s.T(), nm_usecaseinformationlistdata_recv_reply_file_path))

result := s.remoteDevice.VerifyUseCaseScenariosAndFeaturesSupport(
model.UseCaseActorTypeBatterySystem,
model.UseCaseNameTypeControlOfBattery,
[]model.UseCaseScenarioSupportType{},
[]model.FeatureTypeType{},
)
assert.Equal(s.T(), false, result)

result = s.remoteDevice.VerifyUseCaseScenariosAndFeaturesSupport(
model.UseCaseActorTypeEVSE,
model.UseCaseNameTypeEVSECommissioningAndConfiguration,
[]model.UseCaseScenarioSupportType{},
[]model.FeatureTypeType{},
)
assert.Equal(s.T(), true, result)
}

func (s *DeviceRemoteSuite) Test_VerifyUseCaseScenariosAndFeaturesSupport() {
result := s.remoteDevice.VerifyUseCaseScenariosAndFeaturesSupport(
model.UseCaseActorTypeEVSE,
Expand All @@ -82,87 +109,114 @@ func (s *DeviceRemoteSuite) Test_VerifyUseCaseScenariosAndFeaturesSupport() {
)
assert.Equal(s.T(), false, result)

s.remoteDevice.UseCaseManager().Add(
nodeMgmtEntity := s.remoteDevice.Entity(DeviceInformationAddressEntity)
nodeMgmt := nodeMgmtEntity.Feature(util.Ptr(model.AddressFeatureType(NodeManagementFeatureId)))

// initialize with empty data
newData := &model.NodeManagementUseCaseDataType{
UseCaseInformation: []model.UseCaseInformationDataType{},
}
nodeMgmt.UpdateData(model.FunctionTypeNodeManagementUseCaseData, newData, nil, nil)

data := nodeMgmt.Data(model.FunctionTypeNodeManagementUseCaseData).(*model.NodeManagementUseCaseDataType)

address := model.FeatureAddressType{
Device: s.remoteDevice.address,
Entity: s.remoteEntity.address.Entity,
}

data.AddUseCaseSupport(
address,
model.UseCaseActorTypeBatterySystem,
model.UseCaseNameTypeControlOfBattery,
model.SpecificationVersionType("1.0.0"),
"",
true,
[]model.UseCaseScenarioSupportType{1},
)
nodeMgmt.SetData(model.FunctionTypeNodeManagementUseCaseData, data)
data = nodeMgmt.Data(model.FunctionTypeNodeManagementUseCaseData).(*model.NodeManagementUseCaseDataType)

result = s.remoteDevice.VerifyUseCaseScenariosAndFeaturesSupport(
model.UseCaseActorTypeEVSE,
model.UseCaseNameTypeEVSECommissioningAndConfiguration,
[]model.UseCaseScenarioSupportType{},
[]model.FeatureTypeType{},
nil,
nil,
)
assert.Equal(s.T(), false, result)

s.remoteDevice.UseCaseManager().Add(
data.AddUseCaseSupport(
address,
model.UseCaseActorTypeEVSE,
model.UseCaseNameTypeEVCommissioningAndConfiguration,
model.SpecificationVersionType("1.0.0"),
"",
true,
[]model.UseCaseScenarioSupportType{1},
)
nodeMgmt.SetData(model.FunctionTypeNodeManagementUseCaseData, data)
data = nodeMgmt.Data(model.FunctionTypeNodeManagementUseCaseData).(*model.NodeManagementUseCaseDataType)

result = s.remoteDevice.VerifyUseCaseScenariosAndFeaturesSupport(
model.UseCaseActorTypeEVSE,
model.UseCaseNameTypeEVSECommissioningAndConfiguration,
[]model.UseCaseScenarioSupportType{},
[]model.FeatureTypeType{},
nil,
nil,
)
assert.Equal(s.T(), false, result)

s.remoteDevice.UseCaseManager().Add(
data.AddUseCaseSupport(
address,
model.UseCaseActorTypeEVSE,
model.UseCaseNameTypeEVSECommissioningAndConfiguration,
model.SpecificationVersionType("1.0.0"),
"",
false,
[]model.UseCaseScenarioSupportType{1},
)
nodeMgmt.SetData(model.FunctionTypeNodeManagementUseCaseData, data)
data = nodeMgmt.Data(model.FunctionTypeNodeManagementUseCaseData).(*model.NodeManagementUseCaseDataType)

result = s.remoteDevice.VerifyUseCaseScenariosAndFeaturesSupport(
model.UseCaseActorTypeEVSE,
model.UseCaseNameTypeEVSECommissioningAndConfiguration,
[]model.UseCaseScenarioSupportType{},
[]model.FeatureTypeType{},
nil,
nil,
)
assert.Equal(s.T(), false, result)
assert.Equal(s.T(), true, result)

s.remoteDevice.UseCaseManager().Add(
data.AddUseCaseSupport(
address,
model.UseCaseActorTypeEVSE,
model.UseCaseNameTypeEVSECommissioningAndConfiguration,
model.SpecificationVersionType("1.0.0"),
"",
true,
[]model.UseCaseScenarioSupportType{1},
)
nodeMgmt.SetData(model.FunctionTypeNodeManagementUseCaseData, data)

result = s.remoteDevice.VerifyUseCaseScenariosAndFeaturesSupport(
model.UseCaseActorTypeEVSE,
model.UseCaseNameTypeEVSECommissioningAndConfiguration,
[]model.UseCaseScenarioSupportType{},
[]model.FeatureTypeType{},
nil,
nil,
)
assert.Equal(s.T(), true, result)

result = s.remoteDevice.VerifyUseCaseScenariosAndFeaturesSupport(
model.UseCaseActorTypeEVSE,
model.UseCaseNameTypeEVSECommissioningAndConfiguration,
[]model.UseCaseScenarioSupportType{2},
[]model.FeatureTypeType{},
nil,
)
assert.Equal(s.T(), false, result)

result = s.remoteDevice.VerifyUseCaseScenariosAndFeaturesSupport(
model.UseCaseActorTypeEVSE,
model.UseCaseNameTypeEVSECommissioningAndConfiguration,
[]model.UseCaseScenarioSupportType{1},
[]model.FeatureTypeType{},
nil,
)
assert.Equal(s.T(), true, result)

Expand Down
57 changes: 55 additions & 2 deletions spine/entity_local.go
Original file line number Diff line number Diff line change
Expand Up @@ -84,14 +84,67 @@ func (r *EntityLocalImpl) Feature(addressFeature *model.AddressFeatureType) Feat
}

func (r *EntityLocalImpl) Information() *model.NodeManagementDetailedDiscoveryEntityInformationType {
res := model.NodeManagementDetailedDiscoveryEntityInformationType{
res := &model.NodeManagementDetailedDiscoveryEntityInformationType{
Description: &model.NetworkManagementEntityDescriptionDataType{
EntityAddress: r.Address(),
EntityType: &r.eType,
},
}

return &res
return res
}

// add a new usecase
func (r *EntityLocalImpl) AddUseCaseSupport(
actor model.UseCaseActorType,
useCaseName model.UseCaseNameType,
useCaseVersion model.SpecificationVersionType,
useCaseDocumemtSubRevision string,
useCaseAvailable bool,
scenarios []model.UseCaseScenarioSupportType,
) {
nodeMgmt := r.device.nodeManagement

data := nodeMgmt.DataCopy(model.FunctionTypeNodeManagementUseCaseData).(*model.NodeManagementUseCaseDataType)

address := model.FeatureAddressType{
Device: r.address.Device,
Entity: r.address.Entity,
}

data.AddUseCaseSupport(address, actor, useCaseName, useCaseVersion, useCaseDocumemtSubRevision, useCaseAvailable, scenarios)

nodeMgmt.SetData(model.FunctionTypeNodeManagementUseCaseData, data)
}

// Remove a usecase with a given actor ans usecase name
func (r *EntityLocalImpl) RemoveUseCaseSupport(
actor model.UseCaseActorType,
useCaseName model.UseCaseNameType,
) {
nodeMgmt := r.device.nodeManagement

data := nodeMgmt.DataCopy(model.FunctionTypeNodeManagementUseCaseData).(*model.NodeManagementUseCaseDataType)

if data == nil {
return
}

address := model.FeatureAddressType{
Device: r.address.Device,
Entity: r.address.Entity,
}

data.RemoveUseCaseSupport(address, actor, useCaseName)

nodeMgmt.SetData(model.FunctionTypeNodeManagementUseCaseData, data)
}

// Remove all usecases
func (r *EntityLocalImpl) RemoveAllUseCaseSupports() {
r.RemoveUseCaseSupport("", "")
}

// Remove all subscriptions
func (r *EntityLocalImpl) RemoveAllSubscriptions() {
for _, item := range r.features {
Expand Down
3 changes: 3 additions & 0 deletions spine/feature_local.go
Original file line number Diff line number Diff line change
Expand Up @@ -190,6 +190,9 @@ func (r *FeatureLocalImpl) RequestAndFetchData(

// Subscribe to a remote feature
func (r *FeatureLocalImpl) Subscribe(remoteAddress *model.FeatureAddressType) (*model.MsgCounterType, *model.ErrorType) {
if remoteAddress.Device == nil {
return nil, model.NewErrorTypeFromString("device not found")
}
remoteDevice := r.entity.device.RemoteDeviceForAddress(*remoteAddress.Device)
if remoteDevice == nil {
return nil, model.NewErrorTypeFromString("device not found")
Expand Down
14 changes: 10 additions & 4 deletions spine/function_data_factory.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,17 +7,23 @@ import (
)

func CreateFunctionData[F any](featureType model.FeatureTypeType) []F {
if featureType == model.FeatureTypeTypeNodeManagement {
return []F{} // NodeManagement implementation is not using function data
}

// Some devices use generic for everything (e.g. Vaillant Arotherm heatpump)
// or for some things like the SMA HM 2.0 or Elli Wallbox, which uses Generic feature
// for Heartbeats, even though that should go into FeatureTypeTypeDeviceDiagnosis
// Hence we add everything to the Generic feature, as we don't know what might be needed

var result []F

if featureType == model.FeatureTypeTypeNodeManagement {
result = []F{
createFunctionData[model.NodeManagementDestinationListDataType, F](model.FunctionTypeNodeManagementDestinationListData),
createFunctionData[model.NodeManagementDetailedDiscoveryDataType, F](model.FunctionTypeNodeManagementDetailedDiscoveryData),
createFunctionData[model.NodeManagementUseCaseDataType, F](model.FunctionTypeNodeManagementUseCaseData),
}

return result
}

if featureType == model.FeatureTypeTypeActuatorLevel || featureType == model.FeatureTypeTypeGeneric {
result = append(result, []F{
createFunctionData[model.ActuatorLevelDataType, F](model.FunctionTypeActuatorLevelData),
Expand Down
2 changes: 1 addition & 1 deletion spine/function_data_factory_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,7 @@ func TestFunctionDataFactory_FunctionDataCmd(t *testing.T) {

func TestFunctionDataFactory_NodeMgmtFeatureType(t *testing.T) {
result := CreateFunctionData[FunctionDataCmd](model.FeatureTypeTypeNodeManagement)
assert.Equal(t, 0, len(result))
assert.Equal(t, 3, len(result))
}

func TestFunctionDataFactory_unknownFunctionDataType(t *testing.T) {
Expand Down
Loading

0 comments on commit 47a958b

Please sign in to comment.