github.com/enbility/spine-go@v0.7.0/spine/nodemanagement_detaileddiscovery.go (about)

     1  package spine
     2  
     3  import (
     4  	"errors"
     5  	"fmt"
     6  	"slices"
     7  
     8  	"github.com/enbility/spine-go/api"
     9  	"github.com/enbility/spine-go/model"
    10  )
    11  
    12  // request detailed discovery data from a remote device
    13  func (r *NodeManagement) RequestDetailedDiscovery(remoteDeviceSki string, remoteDeviceAddress *model.AddressDeviceType, sender api.SenderInterface) (*model.MsgCounterType, *model.ErrorType) {
    14  	rfAddress := featureAddressType(NodeManagementFeatureId, EntityAddressType(remoteDeviceAddress, DeviceInformationAddressEntity))
    15  	cmd := model.CmdType{
    16  		NodeManagementDetailedDiscoveryData: &model.NodeManagementDetailedDiscoveryDataType{},
    17  	}
    18  	return r.RequestRemoteDataBySenderAddress(cmd, sender, remoteDeviceSki, rfAddress, defaultMaxResponseDelay)
    19  }
    20  
    21  // handle incoming detailed discovery read call
    22  func (r *NodeManagement) processReadDetailedDiscoveryData(deviceRemote api.DeviceRemoteInterface, requestHeader *model.HeaderType) error {
    23  	if deviceRemote == nil {
    24  		return errors.New("nodemanagement.readDetailedDiscoveryData: invalid deviceRemote")
    25  	}
    26  
    27  	var entityInformation []model.NodeManagementDetailedDiscoveryEntityInformationType
    28  	var featureInformation []model.NodeManagementDetailedDiscoveryFeatureInformationType
    29  
    30  	for _, e := range r.Device().Entities() {
    31  		entityInformation = append(entityInformation, *e.Information())
    32  
    33  		for _, f := range e.Features() {
    34  			featureInformation = append(featureInformation, *f.Information())
    35  		}
    36  	}
    37  
    38  	cmd := model.CmdType{
    39  		NodeManagementDetailedDiscoveryData: &model.NodeManagementDetailedDiscoveryDataType{
    40  			SpecificationVersionList: &model.NodeManagementSpecificationVersionListType{
    41  				SpecificationVersion: []model.SpecificationVersionDataType{model.SpecificationVersionDataType(SpecificationVersion)},
    42  			},
    43  			DeviceInformation:  r.Device().Information(),
    44  			EntityInformation:  entityInformation,
    45  			FeatureInformation: featureInformation,
    46  		},
    47  	}
    48  
    49  	return deviceRemote.Sender().Reply(requestHeader, r.Address(), cmd)
    50  }
    51  
    52  // handle incoming detailed discovery reply data
    53  func (r *NodeManagement) processReplyDetailedDiscoveryData(message *api.Message, data *model.NodeManagementDetailedDiscoveryDataType) error {
    54  	remoteDevice := message.DeviceRemote
    55  
    56  	deviceDescription := data.DeviceInformation.Description
    57  	if deviceDescription == nil {
    58  		return errors.New("nodemanagement.replyDetailedDiscoveryData: invalid DeviceInformation.Description")
    59  	}
    60  
    61  	remoteDevice.UpdateDevice(deviceDescription)
    62  	entities, err := remoteDevice.AddEntityAndFeatures(true, data)
    63  	if err != nil {
    64  		return err
    65  	}
    66  
    67  	// publish event for remote device added
    68  	payload := api.EventPayload{
    69  		Ski:        remoteDevice.Ski(),
    70  		EventType:  api.EventTypeDeviceChange,
    71  		ChangeType: api.ElementChangeAdd,
    72  		Device:     remoteDevice,
    73  		Feature:    message.FeatureRemote,
    74  		Data:       data,
    75  	}
    76  	Events.Publish(payload)
    77  
    78  	// publish event for each added remote entity
    79  	for _, entity := range entities {
    80  		payload := api.EventPayload{
    81  			Ski:        remoteDevice.Ski(),
    82  			EventType:  api.EventTypeEntityChange,
    83  			ChangeType: api.ElementChangeAdd,
    84  			Device:     remoteDevice,
    85  			Entity:     entity,
    86  			Data:       data,
    87  		}
    88  		Events.Publish(payload)
    89  	}
    90  
    91  	return nil
    92  }
    93  
    94  // check if an AddressEntity slice exists in a list of AddressEntity slices
    95  func (r *NodeManagement) addressEntityListContainsAddressEntity(list [][]model.AddressEntityType, entityAddress []model.AddressEntityType) bool {
    96  	for _, entityList := range list {
    97  		if slices.Equal(entityList, entityAddress) {
    98  			return true
    99  		}
   100  	}
   101  
   102  	return false
   103  }
   104  
   105  // process incoming detailed discovery notify with full data
   106  // and return the data diff
   107  func (r *NodeManagement) provideDetailedDiscoveryDiffForFullNotify(message *api.Message, data *model.NodeManagementDetailedDiscoveryDataType) *model.NodeManagementDetailedDiscoveryDataType {
   108  	remoteDevice := message.FeatureRemote.Device()
   109  
   110  	var existingEntities, addedEntities [][]model.AddressEntityType
   111  
   112  	var updatedEntityInformation []model.NodeManagementDetailedDiscoveryEntityInformationType
   113  
   114  	// search for new entities
   115  	for _, entity := range data.EntityInformation {
   116  		if entity.Description == nil || entity.Description.EntityAddress == nil {
   117  			continue
   118  		}
   119  
   120  		// check if the entity already exists
   121  		address := entity.Description.EntityAddress
   122  		if remoteDevice.Entity(address.Entity) == nil {
   123  			// does not exists
   124  			added := model.NetworkManagementStateChangeTypeAdded
   125  			entity.Description.LastStateChange = &added
   126  			addedEntities = append(addedEntities, address.Entity)
   127  			updatedEntityInformation = append(updatedEntityInformation, entity)
   128  		} else {
   129  			// exists
   130  			existingEntities = append(existingEntities, address.Entity)
   131  		}
   132  	}
   133  
   134  	// seach for removed entites
   135  	for _, entity := range remoteDevice.Entities() {
   136  		address := entity.Address()
   137  		if !r.addressEntityListContainsAddressEntity(existingEntities, address.Entity) {
   138  			// does not exists
   139  			removed := model.NetworkManagementStateChangeTypeRemoved
   140  			entityType := entity.EntityType()
   141  
   142  			removedEntityDescription := model.NodeManagementDetailedDiscoveryEntityInformationType{
   143  				Description: &model.NetworkManagementEntityDescriptionDataType{
   144  					EntityAddress:   address,
   145  					EntityType:      &entityType,
   146  					LastStateChange: &removed,
   147  				},
   148  			}
   149  
   150  			updatedEntityInformation = append(updatedEntityInformation, removedEntityDescription)
   151  		}
   152  	}
   153  
   154  	data.EntityInformation = updatedEntityInformation
   155  
   156  	// update the feature information
   157  	var updatedFeatureInformation []model.NodeManagementDetailedDiscoveryFeatureInformationType
   158  	for _, feature := range data.FeatureInformation {
   159  		if feature.Description == nil || feature.Description.FeatureAddress == nil {
   160  			continue
   161  		}
   162  
   163  		address := feature.Description.FeatureAddress
   164  		// if the entity of the feature was added, add it
   165  		// if the entity of the feature already existed, do not add it
   166  		if r.addressEntityListContainsAddressEntity(addedEntities, address.Entity) {
   167  			updatedFeatureInformation = append(updatedFeatureInformation, feature)
   168  		}
   169  	}
   170  
   171  	data.FeatureInformation = updatedFeatureInformation
   172  
   173  	return data
   174  }
   175  
   176  // handle incoming detailed discovery notify data
   177  func (r *NodeManagement) processNotifyDetailedDiscoveryData(message *api.Message, data *model.NodeManagementDetailedDiscoveryDataType) error {
   178  	// is this a partial request?
   179  	if message.FilterPartial == nil {
   180  		data = r.provideDetailedDiscoveryDiffForFullNotify(message, data)
   181  	}
   182  
   183  	if len(data.EntityInformation) == 0 {
   184  		return errors.New("nodemanagement.notifyDetailedDiscoveryData: invalid EntityInformation")
   185  	}
   186  
   187  	for _, entity := range data.EntityInformation {
   188  		if entity.Description == nil ||
   189  			entity.Description.EntityAddress == nil ||
   190  			entity.Description.LastStateChange == nil {
   191  			return errors.New("nodemanagement.notifyDetailedDiscoveryData: invalid EntityInformation.Description")
   192  		}
   193  
   194  		lastStateChange := *entity.Description.LastStateChange
   195  		remoteDevice := message.FeatureRemote.Device()
   196  
   197  		// addition example:
   198  		// {"data":[{"header":[{"protocolId":"ee1.0"}]},{"payload":{"datagram":[{"header":[{"specificationVersion":"1.1.1"},{"addressSource":[{"device":"d:_i:19667_PorscheEVSE-00016544"},{"entity":[0]},{"feature":0}]},{"addressDestination":[{"device":"EVCC_HEMS"},{"entity":[0]},{"feature":0}]},{"msgCounter":926685},{"cmdClassifier":"notify"}]},{"payload":[{"cmd":[[{"function":"nodeManagementDetailedDiscoveryData"},{"filter":[[{"cmdControl":[{"partial":[]}]}]]},{"nodeManagementDetailedDiscoveryData":[{"deviceInformation":[{"description":[{"deviceAddress":[{"device":"d:_i:19667_PorscheEVSE-00016544"}]}]}]},{"entityInformation":[[{"description":[{"entityAddress":[{"entity":[1,1]}]},{"entityType":"EV"},{"lastStateChange":"added"},{"description":"Electric Vehicle"}]}]]},{"featureInformation":[[{"description":[{"featureAddress":[{"entity":[1,1]},{"feature":1}]},{"featureType":"LoadControl"},{"role":"server"},{"supportedFunction":[[{"function":"loadControlLimitDescriptionListData"},{"possibleOperations":[{"read":[]}]}],[{"function":"loadControlLimitListData"},{"possibleOperations":[{"read":[]},{"write":[]}]}]]},{"description":"Load Control"}]}],[{"description":[{"featureAddress":[{"entity":[1,1]},{"feature":2}]},{"featureType":"ElectricalConnection"},{"role":"server"},{"supportedFunction":[[{"function":"electricalConnectionParameterDescriptionListData"},{"possibleOperations":[{"read":[]}]}],[{"function":"electricalConnectionDescriptionListData"},{"possibleOperations":[{"read":[]}]}],[{"function":"electricalConnectionPermittedValueSetListData"},{"possibleOperations":[{"read":[]}]}]]},{"description":"Electrical Connection"}]}],[{"description":[{"featureAddress":[{"entity":[1,1]},{"feature":3}]},{"featureType":"Measurement"},{"specificUsage":["Electrical"]},{"role":"server"},{"supportedFunction":[[{"function":"measurementListData"},{"possibleOperations":[{"read":[]}]}],[{"function":"measurementDescriptionListData"},{"possibleOperations":[{"read":[]}]}]]},{"description":"Measurements"}]}],[{"description":[{"featureAddress":[{"entity":[1,1]},{"feature":5}]},{"featureType":"DeviceConfiguration"},{"role":"server"},{"supportedFunction":[[{"function":"deviceConfigurationKeyValueDescriptionListData"},{"possibleOperations":[{"read":[]}]}],[{"function":"deviceConfigurationKeyValueListData"},{"possibleOperations":[{"read":[]}]}]]},{"description":"Device Configuration EV"}]}],[{"description":[{"featureAddress":[{"entity":[1,1]},{"feature":6}]},{"featureType":"DeviceClassification"},{"role":"server"},{"supportedFunction":[[{"function":"deviceClassificationManufacturerData"},{"possibleOperations":[{"read":[]}]}]]},{"description":"Device Classification for EV"}]}],[{"description":[{"featureAddress":[{"entity":[1,1]},{"feature":7}]},{"featureType":"TimeSeries"},{"role":"server"},{"supportedFunction":[[{"function":"timeSeriesConstraintsListData"},{"possibleOperations":[{"read":[]}]}],[{"function":"timeSeriesDescriptionListData"},{"possibleOperations":[{"read":[]}]}],[{"function":"timeSeriesListData"},{"possibleOperations":[{"read":[]},{"write":[]}]}]]},{"description":"Time Series"}]}],[{"description":[{"featureAddress":[{"entity":[1,1]},{"feature":8}]},{"featureType":"IncentiveTable"},{"role":"server"},{"supportedFunction":[[{"function":"incentiveTableConstraintsData"},{"possibleOperations":[{"read":[]}]}],[{"function":"incentiveTableData"},{"possibleOperations":[{"read":[]},{"write":[]}]}],[{"function":"incentiveTableDescriptionData"},{"possibleOperations":[{"read":[]},{"write":[]}]}]]},{"description":"Incentive Table"}]}],[{"description":[{"featureAddress":[{"entity":[1,1]},{"feature":9}]},{"featureType":"DeviceDiagnosis"},{"role":"server"},{"supportedFunction":[[{"function":"deviceDiagnosisStateData"},{"possibleOperations":[{"read":[]}]}]]},{"description":"Device Diagnosis EV"}]}],[{"description":[{"featureAddress":[{"entity":[1,1]},{"feature":10}]},{"featureType":"Identification"},{"role":"server"},{"supportedFunction":[[{"function":"identificationListData"},{"possibleOperations":[{"read":[]}]}]]},{"description":"Identification for EV"}]}]]}]}]]}]}]}}]}
   199  		// {
   200  		// 	"cmd":[[
   201  		// 		{"function":"nodeManagementDetailedDiscoveryData"},
   202  		// 		{"filter":[[{"cmdControl":[{"partial":[]}]}]]},
   203  		// 		{"nodeManagementDetailedDiscoveryData":[
   204  		// 			{"deviceInformation":[{"description":[{"deviceAddress":[{"device":"d:_i:19667_PorscheEVSE-00016544"}]}]}]},
   205  		// 			{"entityInformation":[[
   206  		// 				{"description":[
   207  		// 					{"entityAddress":[{"entity":[1,1]}]},
   208  		// 					{"entityType":"EV"},
   209  		// 					{"lastStateChange":"added"},
   210  		// 					{"description":"Electric Vehicle"}
   211  		// 				]}
   212  		// 			]]},
   213  		// 			{"featureInformation":[
   214  		// 				[{"description":[
   215  		// 					{"featureAddress":[{"entity":[1,1]},{"feature":1}]},
   216  		// 					{"featureType":"LoadControl"},
   217  		// 					{"role":"server"},
   218  		// 					{"supportedFunction":[
   219  		// 						[{"function":"loadControlLimitDescriptionListData"},{"possibleOperations":[{"read":[]}]}],
   220  		// 						[{"function":"loadControlLimitListData"},{"possibleOperations":[{"read":[]},{"write":[]}]}]
   221  		// 					]},
   222  		// 					{"description":"Load Control"}
   223  		// 				]}],
   224  		// ...
   225  
   226  		// is this addition?
   227  		if lastStateChange == model.NetworkManagementStateChangeTypeAdded {
   228  			entities, err := remoteDevice.AddEntityAndFeatures(false, data)
   229  			if err != nil {
   230  				return err
   231  			}
   232  
   233  			// publish event for each added remote entity
   234  			for _, entity := range entities {
   235  				payload := api.EventPayload{
   236  					Ski:        remoteDevice.Ski(),
   237  					EventType:  api.EventTypeEntityChange,
   238  					ChangeType: api.ElementChangeAdd,
   239  					Device:     remoteDevice,
   240  					Entity:     entity,
   241  					Data:       data,
   242  				}
   243  				Events.Publish(payload)
   244  			}
   245  		}
   246  
   247  		// removal example:
   248  		// {"data":[{"header":[{"protocolId":"ee1.0"}]},{"payload":{"datagram":[{"header":[{"specificationVersion":"1.1.1"},{"addressSource":[{"device":"d:_i:19667_PorscheEVSE-00016544"},{"entity":[0]},{"feature":0}]},{"addressDestination":[{"device":"EVCC_HEMS"},{"entity":[0]},{"feature":0}]},{"msgCounter":4835},{"cmdClassifier":"notify"}]},{"payload":[{"cmd":[[{"function":"nodeManagementDetailedDiscoveryData"},{"filter":[[{"cmdControl":[{"partial":[]}]}]]},{"nodeManagementDetailedDiscoveryData":[{"deviceInformation":[{"description":[{"deviceAddress":[{"device":"d:_i:19667_PorscheEVSE-00016544"}]}]}]},{"entityInformation":[[{"description":[{"entityAddress":[{"entity":[1,1]}]},{"lastStateChange":"removed"}]}]]}]}]]}]}]}}]}
   249  		// {
   250  		// 	"cmd": [[
   251  		// 			{"function": "nodeManagementDetailedDiscoveryData"},
   252  		// 			{"filter": [[{"cmdControl": [{"partial": []}]}]]},
   253  		// 			{"nodeManagementDetailedDiscoveryData": [
   254  		// 					{"deviceInformation": [{"description": [{"deviceAddress": [{"device": "d:_i:19667_PorscheEVSE-00016544"}]}]}]},
   255  		// 					{"entityInformation": [[
   256  		// 							{
   257  		// 								"description": [
   258  		// 									{"entityAddress": [{"entity": [1,1]}]},
   259  		// 									{"lastStateChange": "removed"}
   260  		// ...
   261  
   262  		// is this removal?
   263  		if lastStateChange == model.NetworkManagementStateChangeTypeRemoved {
   264  			for _, ei := range data.EntityInformation {
   265  				if err := remoteDevice.CheckEntityInformation(false, ei); err != nil {
   266  					return err
   267  				}
   268  
   269  				entityAddress := ei.Description.EntityAddress.Entity
   270  				removedEntity := remoteDevice.RemoveEntityByAddress(entityAddress)
   271  
   272  				// only continue if the entity existed
   273  				if removedEntity == nil {
   274  					continue
   275  				}
   276  
   277  				payload := api.EventPayload{
   278  					Ski:        remoteDevice.Ski(),
   279  					EventType:  api.EventTypeEntityChange,
   280  					ChangeType: api.ElementChangeRemove,
   281  					Device:     remoteDevice,
   282  					Entity:     removedEntity,
   283  					Data:       data,
   284  				}
   285  				Events.Publish(payload)
   286  
   287  				// remove all subscriptions for this entity
   288  				subscriptionMgr := r.Device().SubscriptionManager()
   289  				subscriptionMgr.RemoveSubscriptionsForEntity(removedEntity)
   290  
   291  				// remove all bindings for this entity
   292  				bindingMgr := r.Device().BindingManager()
   293  				bindingMgr.RemoveBindingsForEntity(removedEntity)
   294  
   295  				// remove all feature caches for this entity
   296  				r.Device().CleanRemoteEntityCaches(removedEntity.Address())
   297  			}
   298  		}
   299  	}
   300  
   301  	return nil
   302  }
   303  
   304  // func (f *NodeManagement) announceFeatureDiscovery(e Entity) error {
   305  // 	entity := f.Entity()
   306  // 	if entity == nil {
   307  // 		return errors.New("announceFeatureDiscovery: entity not found")
   308  // 	}
   309  // 	device := entity.Device()
   310  // 	if device == nil {
   311  // 		return errors.New("announceFeatureDiscovery: device not found")
   312  // 	}
   313  // 	entities := device.Entities()
   314  // 	if entities == nil {
   315  // 		return errors.New("announceFeatureDiscovery: entities not found")
   316  // 	}
   317  
   318  // 	for _, le := range entities {
   319  // 		for _, lf := range le.Features() {
   320  
   321  // 			// connect client to server features
   322  // 			for _, rf := range e.Features() {
   323  // 				lr := lf.Role()
   324  // 				rr := rf.Role()
   325  // 				rolesValid := (lr == model.RoleTypeSpecial && rr == model.RoleTypeSpecial) || (lr == model.RoleTypeClient && rr == model.RoleTypeServer)
   326  // 				if lf.Type() == rf.Type() && rolesValid {
   327  // 					if cf, ok := lf.(ClientFeature); ok {
   328  // 						if err := cf.ServerFound(rf); err != nil {
   329  // 							return err
   330  // 						}
   331  // 					}
   332  // 				}
   333  // 			}
   334  // 		}
   335  // 	}
   336  
   337  // 	return nil
   338  // }
   339  
   340  func (r *NodeManagement) handleMsgDetailedDiscoveryData(message *api.Message, data *model.NodeManagementDetailedDiscoveryDataType) error {
   341  	switch message.CmdClassifier {
   342  	case model.CmdClassifierTypeRead:
   343  		return r.processReadDetailedDiscoveryData(message.DeviceRemote, message.RequestHeader)
   344  
   345  	case model.CmdClassifierTypeReply:
   346  		return r.processReplyDetailedDiscoveryData(message, data)
   347  
   348  	case model.CmdClassifierTypeNotify:
   349  		return r.processNotifyDetailedDiscoveryData(message, data)
   350  
   351  	default:
   352  		return fmt.Errorf("nodemanagement.handleDetailedDiscoveryData: NodeManagementDetailedDiscoveryData CmdClassifierType not implemented: %s", message.CmdClassifier)
   353  	}
   354  }