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 }