Skip to content

Commit

Permalink
Add support for heartbeats on multiple local entities (#34)
Browse files Browse the repository at this point in the history
- Refactor to move the heartbeatmanager from the device to local entity
- timeout 0s will not create a heartbeat manager for the entity, e.g.
used by NodeMgmt local entity

Fixes #31
  • Loading branch information
DerAndereAndi committed Sep 15, 2024
2 parents b637b53 + 44dcb27 commit 575c4bb
Show file tree
Hide file tree
Showing 17 changed files with 120 additions and 113 deletions.
3 changes: 0 additions & 3 deletions api/device.go
Original file line number Diff line number Diff line change
Expand Up @@ -76,9 +76,6 @@ type DeviceLocalInterface interface {
// Send a notify message to remote device subscribing to a specific feature
NotifySubscribers(featureAddress *model.FeatureAddressType, cmd model.CmdType)

// Get the hearbeat manager
HeartbeatManager() HeartbeatManagerInterface

// Get the SPINE data structure for NodeManagementDetailDiscoveryData messages for this device
Information() *model.NodeManagementDetailedDiscoveryDeviceInformationType
}
Expand Down
3 changes: 3 additions & 0 deletions api/entity.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,9 @@ type EntityLocalInterface interface {
// Get the associated DeviceLocalInterface implementation
Device() DeviceLocalInterface

// Get the hearbeat manager for this entity
HeartbeatManager() HeartbeatManagerInterface

// Add a new feature with a given FeatureLocalInterface implementation
AddFeature(f FeatureLocalInterface)
// Get a FeatureLocalInterface implementation for a given feature type and role or create it if it does not exist yet and return it
Expand Down
4 changes: 2 additions & 2 deletions integration_tests/helper_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -101,8 +101,8 @@ func beforeTest(
fId uint, ftype model.FeatureTypeType,
frole model.RoleType) (api.DeviceLocalInterface, string, api.DeviceRemoteInterface, *WriteMessageHandler) {
sut := spine.NewDeviceLocal("TestBrandName", "TestDeviceModel", "TestSerialNumber", "TestDeviceCode",
"TestDeviceAddress", model.DeviceTypeTypeEnergyManagementSystem, model.NetworkManagementFeatureSetTypeSmart, time.Second*4)
localEntity := spine.NewEntityLocal(sut, model.EntityTypeTypeCEM, spine.NewAddressEntityType([]uint{1}))
"TestDeviceAddress", model.DeviceTypeTypeEnergyManagementSystem, model.NetworkManagementFeatureSetTypeSmart)
localEntity := spine.NewEntityLocal(sut, model.EntityTypeTypeCEM, spine.NewAddressEntityType([]uint{1}), time.Second*4)
sut.AddEntity(localEntity)
f := spine.NewFeatureLocal(fId, localEntity, ftype, frole)
localEntity.AddFeature(f)
Expand Down
4 changes: 2 additions & 2 deletions spine/binding_manager_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ type BindingManagerSuite struct {

func (s *BindingManagerSuite) BeforeTest(suiteName, testName string) {
s.localDevice = NewDeviceLocal("TestBrandName", "TestDeviceModel", "TestSerialNumber", "TestDeviceCode",
"TestDeviceAddress", model.DeviceTypeTypeEnergyManagementSystem, model.NetworkManagementFeatureSetTypeSmart, time.Second*4)
"TestDeviceAddress", model.DeviceTypeTypeEnergyManagementSystem, model.NetworkManagementFeatureSetTypeSmart)
remoteSki := "TestRemoteSki"

s.writeHandler = &WriteMessageHandler{}
Expand All @@ -40,7 +40,7 @@ func (s *BindingManagerSuite) BeforeTest(suiteName, testName string) {
}

func (suite *BindingManagerSuite) Test_Bindings() {
entity := NewEntityLocal(suite.localDevice, model.EntityTypeTypeCEM, []model.AddressEntityType{1})
entity := NewEntityLocal(suite.localDevice, model.EntityTypeTypeCEM, []model.AddressEntityType{1}, time.Second*4)
suite.localDevice.AddEntity(entity)

localFeature := entity.GetOrAddFeature(model.FeatureTypeTypeDeviceDiagnosis, model.RoleTypeServer)
Expand Down
12 changes: 2 additions & 10 deletions spine/device_local.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ import (
"reflect"
"slices"
"sync"
"time"

shipapi "github.com/enbility/ship-go/api"
"github.com/enbility/ship-go/logging"
Expand All @@ -20,7 +19,6 @@ type DeviceLocal struct {
entities []api.EntityLocalInterface
subscriptionManager api.SubscriptionManagerInterface
bindingManager api.BindingManagerInterface
heartbeatManager api.HeartbeatManagerInterface
nodeManagement *NodeManagement

remoteDevices map[string]api.DeviceRemoteInterface
Expand All @@ -41,8 +39,7 @@ type DeviceLocal struct {
func NewDeviceLocal(
brandName, deviceModel, serialNumber, deviceCode, deviceAddress string,
deviceType model.DeviceTypeType,
featureSet model.NetworkManagementFeatureSetType,
heartbeatTimeout time.Duration) *DeviceLocal {
featureSet model.NetworkManagementFeatureSetType) *DeviceLocal {
address := model.AddressDeviceType(deviceAddress)

var fSet *model.NetworkManagementFeatureSetType
Expand All @@ -61,7 +58,6 @@ func NewDeviceLocal(

res.subscriptionManager = NewSubscriptionManager(res)
res.bindingManager = NewBindingManager(res)
res.heartbeatManager = NewHeartbeatManager(res, res.subscriptionManager, heartbeatTimeout)

res.addDeviceInformation()
return res
Expand Down Expand Up @@ -420,10 +416,6 @@ func (r *DeviceLocal) BindingManager() api.BindingManagerInterface {
return r.bindingManager
}

func (r *DeviceLocal) HeartbeatManager() api.HeartbeatManagerInterface {
return r.heartbeatManager
}

func (r *DeviceLocal) Information() *model.NodeManagementDetailedDiscoveryDeviceInformationType {
res := model.NodeManagementDetailedDiscoveryDeviceInformationType{
Description: &model.NetworkManagementDeviceDescriptionDataType{
Expand Down Expand Up @@ -475,7 +467,7 @@ func (r *DeviceLocal) notifySubscribersOfEntity(entity api.EntityLocalInterface,

func (r *DeviceLocal) addDeviceInformation() {
entityType := model.EntityTypeTypeDeviceInformation
entity := NewEntityLocal(r, entityType, []model.AddressEntityType{model.AddressEntityType(DeviceInformationEntityId)})
entity := NewEntityLocal(r, entityType, []model.AddressEntityType{model.AddressEntityType(DeviceInformationEntityId)}, 0)

{
r.nodeManagement = NewNodeManagement(entity.NextFeatureId(), entity)
Expand Down
20 changes: 10 additions & 10 deletions spine/device_local_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ func (d *DeviceLocalTestSuite) WriteShipMessageWithPayload(msg []byte) {
}

func (d *DeviceLocalTestSuite) Test_RemoveRemoteDevice() {
sut := NewDeviceLocal("brand", "model", "serial", "code", "address", model.DeviceTypeTypeEnergyManagementSystem, model.NetworkManagementFeatureSetTypeSmart, time.Second*4)
sut := NewDeviceLocal("brand", "model", "serial", "code", "address", model.DeviceTypeTypeEnergyManagementSystem, model.NetworkManagementFeatureSetTypeSmart)

ski := "test"
_ = sut.SetupRemoteDevice(ski, d)
Expand All @@ -42,8 +42,8 @@ func (d *DeviceLocalTestSuite) Test_RemoveRemoteDevice() {
}

func (d *DeviceLocalTestSuite) Test_RemoteDevice() {
sut := NewDeviceLocal("brand", "model", "serial", "code", "address", model.DeviceTypeTypeEnergyManagementSystem, model.NetworkManagementFeatureSetTypeSmart, time.Second*4)
localEntity := NewEntityLocal(sut, model.EntityTypeTypeCEM, NewAddressEntityType([]uint{1}))
sut := NewDeviceLocal("brand", "model", "serial", "code", "address", model.DeviceTypeTypeEnergyManagementSystem, model.NetworkManagementFeatureSetTypeSmart)
localEntity := NewEntityLocal(sut, model.EntityTypeTypeCEM, NewAddressEntityType([]uint{1}), time.Second*4)
sut.AddEntity(localEntity)

f := NewFeatureLocal(1, localEntity, model.FeatureTypeTypeElectricalConnection, model.RoleTypeClient)
Expand Down Expand Up @@ -98,7 +98,7 @@ func (d *DeviceLocalTestSuite) Test_RemoteDevice() {
err := sut.SubscriptionManager().AddSubscription(remote, subscription)
assert.Nil(d.T(), err)

newSubEntity := NewEntityLocal(sut, model.EntityTypeTypeEV, NewAddressEntityType([]uint{1, 1}))
newSubEntity := NewEntityLocal(sut, model.EntityTypeTypeEV, NewAddressEntityType([]uint{1, 1}), time.Second*4)
f = NewFeatureLocal(1, newSubEntity, model.FeatureTypeTypeLoadControl, model.RoleTypeServer)
f.AddFunctionType(model.FunctionTypeLoadControlLimitListData, true, true)
newSubEntity.AddFeature(f)
Expand Down Expand Up @@ -133,8 +133,8 @@ func (d *DeviceLocalTestSuite) Test_RemoteDevice() {
}

func (d *DeviceLocalTestSuite) Test_ProcessCmd_NotifyError() {
sut := NewDeviceLocal("brand", "model", "serial", "code", "address", model.DeviceTypeTypeEnergyManagementSystem, model.NetworkManagementFeatureSetTypeSmart, time.Second*4)
localEntity := NewEntityLocal(sut, model.EntityTypeTypeCEM, NewAddressEntityType([]uint{1}))
sut := NewDeviceLocal("brand", "model", "serial", "code", "address", model.DeviceTypeTypeEnergyManagementSystem, model.NetworkManagementFeatureSetTypeSmart)
localEntity := NewEntityLocal(sut, model.EntityTypeTypeCEM, NewAddressEntityType([]uint{1}), time.Second*4)
localFeature := NewFeatureLocal(50, localEntity, model.FeatureTypeTypeSensing, model.RoleTypeClient)
localEntity.AddFeature(localFeature)
sut.AddEntity(localEntity)
Expand Down Expand Up @@ -178,8 +178,8 @@ func (d *DeviceLocalTestSuite) Test_ProcessCmd_NotifyError() {
}

func (d *DeviceLocalTestSuite) Test_ProcessCmd_Errors() {
sut := NewDeviceLocal("brand", "model", "serial", "code", "address", model.DeviceTypeTypeEnergyManagementSystem, model.NetworkManagementFeatureSetTypeSmart, time.Second*4)
localEntity := NewEntityLocal(sut, model.EntityTypeTypeCEM, NewAddressEntityType([]uint{1}))
sut := NewDeviceLocal("brand", "model", "serial", "code", "address", model.DeviceTypeTypeEnergyManagementSystem, model.NetworkManagementFeatureSetTypeSmart)
localEntity := NewEntityLocal(sut, model.EntityTypeTypeCEM, NewAddressEntityType([]uint{1}), time.Second*4)
sut.AddEntity(localEntity)

ski := "test"
Expand Down Expand Up @@ -229,8 +229,8 @@ func (d *DeviceLocalTestSuite) Test_ProcessCmd_Errors() {
}

func (d *DeviceLocalTestSuite) Test_ProcessCmd() {
sut := NewDeviceLocal("brand", "model", "serial", "code", "address", model.DeviceTypeTypeEnergyManagementSystem, model.NetworkManagementFeatureSetTypeSmart, time.Second*4)
localEntity := NewEntityLocal(sut, model.EntityTypeTypeCEM, NewAddressEntityType([]uint{1}))
sut := NewDeviceLocal("brand", "model", "serial", "code", "address", model.DeviceTypeTypeEnergyManagementSystem, model.NetworkManagementFeatureSetTypeSmart)
localEntity := NewEntityLocal(sut, model.EntityTypeTypeCEM, NewAddressEntityType([]uint{1}), time.Second*4)
sut.AddEntity(localEntity)

f1 := NewFeatureLocal(1, localEntity, model.FeatureTypeTypeElectricalConnection, model.RoleTypeClient)
Expand Down
3 changes: 1 addition & 2 deletions spine/device_remote_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ package spine

import (
"testing"
"time"

"github.com/enbility/spine-go/api"
"github.com/enbility/spine-go/model"
Expand Down Expand Up @@ -32,7 +31,7 @@ func (s *DeviceRemoteSuite) WriteShipMessageWithPayload([]byte) {}
func (s *DeviceRemoteSuite) SetupSuite() {}

func (s *DeviceRemoteSuite) BeforeTest(suiteName, testName string) {
s.localDevice = NewDeviceLocal("brand", "model", "serial", "code", "address", model.DeviceTypeTypeEnergyManagementSystem, model.NetworkManagementFeatureSetTypeSmart, time.Second*4)
s.localDevice = NewDeviceLocal("brand", "model", "serial", "code", "address", model.DeviceTypeTypeEnergyManagementSystem, model.NetworkManagementFeatureSetTypeSmart)

ski := "test"
sender := NewSender(s)
Expand Down
20 changes: 18 additions & 2 deletions spine/entity_local.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package spine

import (
"sync"
"time"

"github.com/enbility/spine-go/api"
"github.com/enbility/spine-go/model"
Expand All @@ -12,14 +13,25 @@ type EntityLocal struct {
device api.DeviceLocalInterface
features []api.FeatureLocalInterface

heartbeatManager api.HeartbeatManagerInterface

mux sync.Mutex
}

func NewEntityLocal(device api.DeviceLocalInterface, eType model.EntityTypeType, entityAddress []model.AddressEntityType) *EntityLocal {
return &EntityLocal{
func NewEntityLocal(device api.DeviceLocalInterface,
eType model.EntityTypeType,
entityAddress []model.AddressEntityType,
heartbeatTimeout time.Duration) *EntityLocal {
entity := &EntityLocal{
Entity: NewEntity(eType, device.Address(), entityAddress),
device: device,
}
// only needed if the entity address is not DeviceInformationEntityId
if len(entityAddress) > 0 && entityAddress[0] != model.AddressEntityType(DeviceInformationEntityId) {
entity.heartbeatManager = NewHeartbeatManager(entity, heartbeatTimeout)
}

return entity
}

var _ api.EntityLocalInterface = (*EntityLocal)(nil)
Expand All @@ -30,6 +42,10 @@ func (r *EntityLocal) Device() api.DeviceLocalInterface {
return r.device
}

func (r *EntityLocal) HeartbeatManager() api.HeartbeatManagerInterface {
return r.heartbeatManager
}

// Add a feature to the entity if it is not already added
func (r *EntityLocal) AddFeature(f api.FeatureLocalInterface) {
r.mux.Lock()
Expand Down
4 changes: 2 additions & 2 deletions spine/entity_local_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,8 @@ type EntityLocalTestSuite struct {
}

func (suite *EntityLocalTestSuite) Test_Entity() {
device := NewDeviceLocal("brand", "model", "serial", "code", "address", model.DeviceTypeTypeEnergyManagementSystem, model.NetworkManagementFeatureSetTypeSmart, time.Second*4)
entity := NewEntityLocal(device, model.EntityTypeTypeCEM, NewAddressEntityType([]uint{1}))
device := NewDeviceLocal("brand", "model", "serial", "code", "address", model.DeviceTypeTypeEnergyManagementSystem, model.NetworkManagementFeatureSetTypeSmart)
entity := NewEntityLocal(device, model.EntityTypeTypeCEM, NewAddressEntityType([]uint{1}), time.Second*4)
device.AddEntity(entity)

f := NewFeatureLocal(1, entity, model.FeatureTypeTypeElectricalConnection, model.RoleTypeClient)
Expand Down
2 changes: 1 addition & 1 deletion spine/feature_local.go
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,7 @@ func (r *FeatureLocal) AddFunctionType(function model.FunctionType, read, write
r.ftype == model.FeatureTypeTypeDeviceDiagnosis &&
function == model.FunctionTypeDeviceDiagnosisHeartbeatData {
// Update HeartbeatManager
r.Device().HeartbeatManager().SetLocalFeature(r.Entity(), r)
r.Entity().HeartbeatManager().SetLocalFeature(r.Entity(), r)
}
}

Expand Down
11 changes: 4 additions & 7 deletions spine/heartbeat_manager.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,28 +10,25 @@ import (
)

type HeartbeatManager struct {
localDevice api.DeviceLocalInterface
localEntity api.EntityLocalInterface
localFeature api.FeatureLocalInterface

heartBeatNum uint64 // see https://github.com/golang/go/issues/11891
stopHeartbeatC chan struct{}
stopMux sync.Mutex

subscriptionManager api.SubscriptionManagerInterface
heartBeatTimeout *model.DurationType
heartBeatTimeout *model.DurationType

mux sync.Mutex
}

var _ api.HeartbeatManagerInterface = (*HeartbeatManager)(nil)

// Create a new Heartbeat Manager which handles sending of heartbeats
func NewHeartbeatManager(localDevice api.DeviceLocalInterface, subscriptionManager api.SubscriptionManagerInterface, timeout time.Duration) *HeartbeatManager {
func NewHeartbeatManager(localEntity api.EntityLocalInterface, timeout time.Duration) *HeartbeatManager {
h := &HeartbeatManager{
localDevice: localDevice,
subscriptionManager: subscriptionManager,
heartBeatTimeout: model.NewDurationType(timeout),
localEntity: localEntity,
heartBeatTimeout: model.NewDurationType(timeout),
}

return h
Expand Down
Loading

0 comments on commit 575c4bb

Please sign in to comment.