ClientUtils.cs 47 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267
  1. using System;
  2. using System.Collections.Generic;
  3. using System.Drawing;
  4. using System.Linq;
  5. using System.Text;
  6. using System.Threading.Tasks;
  7. using Opc.Ua;
  8. using Opc.Ua.Client;
  9. namespace OpcUaHelper
  10. {
  11. /// <summary>
  12. /// Defines numerous re-useable utility functions.
  13. /// </summary>
  14. public partial class ClientUtils
  15. {
  16. /// <summary>
  17. /// Handles an exception.
  18. /// </summary>
  19. public static void HandleException( string caption, Exception e )
  20. {
  21. ExceptionDlg.Show( caption, e );
  22. }
  23. /// <summary>
  24. /// Returns the application icon.
  25. /// </summary>
  26. public static System.Drawing.Icon GetAppIcon()
  27. {
  28. try
  29. {
  30. return new Icon( "App.ico" );
  31. }
  32. catch (Exception)
  33. {
  34. return null;
  35. }
  36. }
  37. #region DisplayText Lookup
  38. /// <summary>
  39. /// Gets the display text for the access level attribute.
  40. /// </summary>
  41. /// <param name="accessLevel">The access level.</param>
  42. /// <returns>The access level formatted as a string.</returns>
  43. public static string GetAccessLevelDisplayText( byte accessLevel )
  44. {
  45. StringBuilder buffer = new StringBuilder( );
  46. if (accessLevel == AccessLevels.None)
  47. {
  48. buffer.Append( "None" );
  49. }
  50. if ((accessLevel & AccessLevels.CurrentRead) == AccessLevels.CurrentRead)
  51. {
  52. buffer.Append( "Read" );
  53. }
  54. if ((accessLevel & AccessLevels.CurrentWrite) == AccessLevels.CurrentWrite)
  55. {
  56. if (buffer.Length > 0)
  57. {
  58. buffer.Append( " | " );
  59. }
  60. buffer.Append( "Write" );
  61. }
  62. if ((accessLevel & AccessLevels.HistoryRead) == AccessLevels.HistoryRead)
  63. {
  64. if (buffer.Length > 0)
  65. {
  66. buffer.Append( " | " );
  67. }
  68. buffer.Append( "HistoryRead" );
  69. }
  70. if ((accessLevel & AccessLevels.HistoryWrite) == AccessLevels.HistoryWrite)
  71. {
  72. if (buffer.Length > 0)
  73. {
  74. buffer.Append( " | " );
  75. }
  76. buffer.Append( "HistoryWrite" );
  77. }
  78. if ((accessLevel & AccessLevels.SemanticChange) == AccessLevels.SemanticChange)
  79. {
  80. if (buffer.Length > 0)
  81. {
  82. buffer.Append( " | " );
  83. }
  84. buffer.Append( "SemanticChange" );
  85. }
  86. return buffer.ToString( );
  87. }
  88. /// <summary>
  89. /// Gets the display text for the event notifier attribute.
  90. /// </summary>
  91. /// <param name="eventNotifier">The event notifier.</param>
  92. /// <returns>The event notifier formatted as a string.</returns>
  93. public static string GetEventNotifierDisplayText( byte eventNotifier )
  94. {
  95. StringBuilder buffer = new StringBuilder( );
  96. if (eventNotifier == EventNotifiers.None)
  97. {
  98. buffer.Append( "None" );
  99. }
  100. if ((eventNotifier & EventNotifiers.SubscribeToEvents) == EventNotifiers.SubscribeToEvents)
  101. {
  102. buffer.Append( "Subscribe" );
  103. }
  104. if ((eventNotifier & EventNotifiers.HistoryRead) == EventNotifiers.HistoryRead)
  105. {
  106. if (buffer.Length > 0)
  107. {
  108. buffer.Append( " | " );
  109. }
  110. buffer.Append( "HistoryRead" );
  111. }
  112. if ((eventNotifier & EventNotifiers.HistoryWrite) == EventNotifiers.HistoryWrite)
  113. {
  114. if (buffer.Length > 0)
  115. {
  116. buffer.Append( " | " );
  117. }
  118. buffer.Append( "HistoryWrite" );
  119. }
  120. return buffer.ToString( );
  121. }
  122. /// <summary>
  123. /// Gets the display text for the value rank attribute.
  124. /// </summary>
  125. /// <param name="valueRank">The value rank.</param>
  126. /// <returns>The value rank formatted as a string.</returns>
  127. public static string GetValueRankDisplayText( int valueRank )
  128. {
  129. switch (valueRank)
  130. {
  131. case ValueRanks.Any: return "Any";
  132. case ValueRanks.Scalar: return "Scalar";
  133. case ValueRanks.ScalarOrOneDimension: return "ScalarOrOneDimension";
  134. case ValueRanks.OneOrMoreDimensions: return "OneOrMoreDimensions";
  135. case ValueRanks.OneDimension: return "OneDimension";
  136. case ValueRanks.TwoDimensions: return "TwoDimensions";
  137. }
  138. return valueRank.ToString( );
  139. }
  140. /// <summary>
  141. /// Gets the display text for the specified attribute.
  142. /// </summary>
  143. /// <param name="session">The currently active session.</param>
  144. /// <param name="attributeId">The id of the attribute.</param>
  145. /// <param name="value">The value of the attribute.</param>
  146. /// <returns>The attribute formatted as a string.</returns>
  147. public static string GetAttributeDisplayText( Session session, uint attributeId, Variant value )
  148. {
  149. if (value == Variant.Null)
  150. {
  151. return String.Empty;
  152. }
  153. switch (attributeId)
  154. {
  155. case Attributes.AccessLevel:
  156. case Attributes.UserAccessLevel:
  157. {
  158. byte? field = value.Value as byte?;
  159. if (field != null)
  160. {
  161. return GetAccessLevelDisplayText( field.Value );
  162. }
  163. break;
  164. }
  165. case Attributes.EventNotifier:
  166. {
  167. byte? field = value.Value as byte?;
  168. if (field != null)
  169. {
  170. return GetEventNotifierDisplayText( field.Value );
  171. }
  172. break;
  173. }
  174. case Attributes.DataType:
  175. {
  176. return session.NodeCache.GetDisplayText( value.Value as NodeId );
  177. }
  178. case Attributes.ValueRank:
  179. {
  180. int? field = value.Value as int?;
  181. if (field != null)
  182. {
  183. return GetValueRankDisplayText( field.Value );
  184. }
  185. break;
  186. }
  187. case Attributes.NodeClass:
  188. {
  189. int? field = value.Value as int?;
  190. if (field != null)
  191. {
  192. return ((NodeClass)field.Value).ToString( );
  193. }
  194. break;
  195. }
  196. case Attributes.NodeId:
  197. {
  198. NodeId field = value.Value as NodeId;
  199. if (!NodeId.IsNull( field ))
  200. {
  201. return field.ToString( );
  202. }
  203. return "Null";
  204. }
  205. }
  206. // check for byte strings.
  207. if (value.Value is byte[])
  208. {
  209. return Utils.ToHexString( value.Value as byte[] );
  210. }
  211. // use default format.
  212. return value.ToString( );
  213. }
  214. #endregion
  215. #region Browse
  216. /// <summary>
  217. /// Browses the address space and returns the references found.
  218. /// </summary>
  219. /// <param name="session">The session.</param>
  220. /// <param name="nodesToBrowse">The set of browse operations to perform.</param>
  221. /// <param name="throwOnError">if set to <c>true</c> a exception will be thrown on an error.</param>
  222. /// <returns>
  223. /// The references found. Null if an error occurred.
  224. /// </returns>
  225. public static ReferenceDescriptionCollection Browse( Session session, BrowseDescriptionCollection nodesToBrowse, bool throwOnError )
  226. {
  227. return Browse( session, null, nodesToBrowse, throwOnError );
  228. }
  229. /// <summary>
  230. /// Browses the address space and returns the references found.
  231. /// </summary>
  232. public static ReferenceDescriptionCollection Browse( Session session, ViewDescription view, BrowseDescriptionCollection nodesToBrowse, bool throwOnError )
  233. {
  234. try
  235. {
  236. ReferenceDescriptionCollection references = new ReferenceDescriptionCollection( );
  237. while (nodesToBrowse.Count > 0)
  238. {
  239. // start the browse operation.
  240. BrowseResultCollection results = null;
  241. DiagnosticInfoCollection diagnosticInfos = null;
  242. session.Browse(
  243. null,
  244. view,
  245. 0,
  246. nodesToBrowse,
  247. out results,
  248. out diagnosticInfos );
  249. ClientBase.ValidateResponse( results, nodesToBrowse );
  250. ClientBase.ValidateDiagnosticInfos( diagnosticInfos, nodesToBrowse );
  251. ByteStringCollection continuationPoints = new ByteStringCollection( );
  252. BrowseDescriptionCollection unprocessedOperations = new BrowseDescriptionCollection( );
  253. for (int ii = 0; ii < nodesToBrowse.Count; ii++)
  254. {
  255. // check for error.
  256. if (StatusCode.IsBad( results[ii].StatusCode ))
  257. {
  258. // this error indicates that the server does not have enough simultaneously active
  259. // continuation points. This request will need to be resent after the other operations
  260. // have been completed and their continuation points released.
  261. if (results[ii].StatusCode == StatusCodes.BadNoContinuationPoints)
  262. {
  263. unprocessedOperations.Add( nodesToBrowse[ii] );
  264. }
  265. continue;
  266. }
  267. // check if all references have been fetched.
  268. if (results[ii].References.Count == 0)
  269. {
  270. continue;
  271. }
  272. // save results.
  273. references.AddRange( results[ii].References );
  274. // check for continuation point.
  275. if (results[ii].ContinuationPoint != null)
  276. {
  277. continuationPoints.Add( results[ii].ContinuationPoint );
  278. }
  279. }
  280. // process continuation points.
  281. while (continuationPoints.Count > 0)
  282. {
  283. // continue browse operation.
  284. session.BrowseNext(
  285. null,
  286. false,
  287. continuationPoints,
  288. out results,
  289. out diagnosticInfos );
  290. ClientBase.ValidateResponse( results, continuationPoints );
  291. ClientBase.ValidateDiagnosticInfos( diagnosticInfos, continuationPoints );
  292. ByteStringCollection revisedContinuationPoints = new ByteStringCollection( );
  293. for (int ii = 0; ii < continuationPoints.Count; ii++)
  294. {
  295. // check for error.
  296. if (StatusCode.IsBad( results[ii].StatusCode ))
  297. {
  298. continue;
  299. }
  300. // check if all references have been fetched.
  301. if (results[ii].References.Count == 0)
  302. {
  303. continue;
  304. }
  305. // save results.
  306. references.AddRange( results[ii].References );
  307. // check for continuation point.
  308. if (results[ii].ContinuationPoint != null)
  309. {
  310. revisedContinuationPoints.Add( results[ii].ContinuationPoint );
  311. }
  312. }
  313. // check if browsing must continue;
  314. continuationPoints = revisedContinuationPoints;
  315. }
  316. // check if unprocessed results exist.
  317. nodesToBrowse = unprocessedOperations;
  318. }
  319. // return complete list.
  320. return references;
  321. }
  322. catch (Exception exception)
  323. {
  324. if (throwOnError)
  325. {
  326. throw new ServiceResultException( exception, StatusCodes.BadUnexpectedError );
  327. }
  328. return null;
  329. }
  330. }
  331. /// <summary>
  332. /// Browses the address space and returns the references found.
  333. /// </summary>
  334. /// <param name="session">The session.</param>
  335. /// <param name="nodeToBrowse">The NodeId for the starting node.</param>
  336. /// <param name="throwOnError">if set to <c>true</c> a exception will be thrown on an error.</param>
  337. /// <returns>
  338. /// The references found. Null if an error occurred.
  339. /// </returns>
  340. public static ReferenceDescriptionCollection Browse( Session session, BrowseDescription nodeToBrowse, bool throwOnError )
  341. {
  342. return Browse( session, null, nodeToBrowse, throwOnError );
  343. }
  344. /// <summary>
  345. /// Browses the address space and returns the references found.
  346. /// </summary>
  347. public static ReferenceDescriptionCollection Browse( Session session, ViewDescription view, BrowseDescription nodeToBrowse, bool throwOnError )
  348. {
  349. try
  350. {
  351. ReferenceDescriptionCollection references = new ReferenceDescriptionCollection( );
  352. // construct browse request.
  353. BrowseDescriptionCollection nodesToBrowse = new BrowseDescriptionCollection( );
  354. nodesToBrowse.Add( nodeToBrowse );
  355. // start the browse operation.
  356. BrowseResultCollection results = null;
  357. DiagnosticInfoCollection diagnosticInfos = null;
  358. session.Browse(
  359. null,
  360. view,
  361. 0,
  362. nodesToBrowse,
  363. out results,
  364. out diagnosticInfos );
  365. ClientBase.ValidateResponse( results, nodesToBrowse );
  366. ClientBase.ValidateDiagnosticInfos( diagnosticInfos, nodesToBrowse );
  367. do
  368. {
  369. // check for error.
  370. if (StatusCode.IsBad( results[0].StatusCode ))
  371. {
  372. throw new ServiceResultException( results[0].StatusCode );
  373. }
  374. // process results.
  375. for (int ii = 0; ii < results[0].References.Count; ii++)
  376. {
  377. references.Add( results[0].References[ii] );
  378. }
  379. // check if all references have been fetched.
  380. if (results[0].References.Count == 0 || results[0].ContinuationPoint == null)
  381. {
  382. break;
  383. }
  384. // continue browse operation.
  385. ByteStringCollection continuationPoints = new ByteStringCollection( );
  386. continuationPoints.Add( results[0].ContinuationPoint );
  387. session.BrowseNext(
  388. null,
  389. false,
  390. continuationPoints,
  391. out results,
  392. out diagnosticInfos );
  393. ClientBase.ValidateResponse( results, continuationPoints );
  394. ClientBase.ValidateDiagnosticInfos( diagnosticInfos, continuationPoints );
  395. }
  396. while (true);
  397. //return complete list.
  398. return references;
  399. }
  400. catch (Exception exception)
  401. {
  402. if (throwOnError)
  403. {
  404. throw new ServiceResultException( exception, StatusCodes.BadUnexpectedError );
  405. }
  406. return null;
  407. }
  408. }
  409. /// <summary>
  410. /// Browses the address space and returns all of the supertypes of the specified type node.
  411. /// </summary>
  412. /// <param name="session">The session.</param>
  413. /// <param name="typeId">The NodeId for a type node in the address space.</param>
  414. /// <param name="throwOnError">if set to <c>true</c> a exception will be thrown on an error.</param>
  415. /// <returns>
  416. /// The references found. Null if an error occurred.
  417. /// </returns>
  418. public static ReferenceDescriptionCollection BrowseSuperTypes( Session session, NodeId typeId, bool throwOnError )
  419. {
  420. ReferenceDescriptionCollection supertypes = new ReferenceDescriptionCollection( );
  421. try
  422. {
  423. // find all of the children of the field.
  424. BrowseDescription nodeToBrowse = new BrowseDescription( );
  425. nodeToBrowse.NodeId = typeId;
  426. nodeToBrowse.BrowseDirection = BrowseDirection.Inverse;
  427. nodeToBrowse.ReferenceTypeId = ReferenceTypeIds.HasSubtype;
  428. nodeToBrowse.IncludeSubtypes = false; // more efficient to use IncludeSubtypes=False when possible.
  429. nodeToBrowse.NodeClassMask = 0; // the HasSubtype reference already restricts the targets to Types.
  430. nodeToBrowse.ResultMask = (uint)BrowseResultMask.All;
  431. ReferenceDescriptionCollection references = Browse( session, nodeToBrowse, throwOnError );
  432. while (references != null && references.Count > 0)
  433. {
  434. // should never be more than one supertype.
  435. supertypes.Add( references[0] );
  436. // only follow references within this server.
  437. if (references[0].NodeId.IsAbsolute)
  438. {
  439. break;
  440. }
  441. // get the references for the next level up.
  442. nodeToBrowse.NodeId = (NodeId)references[0].NodeId;
  443. references = Browse( session, nodeToBrowse, throwOnError );
  444. }
  445. // return complete list.
  446. return supertypes;
  447. }
  448. catch (Exception exception)
  449. {
  450. if (throwOnError)
  451. {
  452. throw new ServiceResultException( exception, StatusCodes.BadUnexpectedError );
  453. }
  454. return null;
  455. }
  456. }
  457. /// <summary>
  458. /// Returns the node ids for a set of relative paths.
  459. /// </summary>
  460. /// <param name="session">An open session with the server to use.</param>
  461. /// <param name="startNodeId">The starting node for the relative paths.</param>
  462. /// <param name="namespacesUris">The namespace URIs referenced by the relative paths.</param>
  463. /// <param name="relativePaths">The relative paths.</param>
  464. /// <returns>A collection of local nodes.</returns>
  465. public static List<NodeId> TranslateBrowsePaths(
  466. Session session,
  467. NodeId startNodeId,
  468. NamespaceTable namespacesUris,
  469. params string[] relativePaths )
  470. {
  471. // build the list of browse paths to follow by parsing the relative paths.
  472. BrowsePathCollection browsePaths = new BrowsePathCollection( );
  473. if (relativePaths != null)
  474. {
  475. for (int ii = 0; ii < relativePaths.Length; ii++)
  476. {
  477. BrowsePath browsePath = new BrowsePath( );
  478. // The relative paths used indexes in the namespacesUris table. These must be
  479. // converted to indexes used by the server. An error occurs if the relative path
  480. // refers to a namespaceUri that the server does not recognize.
  481. // The relative paths may refer to ReferenceType by their BrowseName. The TypeTree object
  482. // allows the parser to look up the server's NodeId for the ReferenceType.
  483. browsePath.RelativePath = RelativePath.Parse(
  484. relativePaths[ii],
  485. session.TypeTree,
  486. namespacesUris,
  487. session.NamespaceUris );
  488. browsePath.StartingNode = startNodeId;
  489. browsePaths.Add( browsePath );
  490. }
  491. }
  492. // make the call to the server.
  493. BrowsePathResultCollection results;
  494. DiagnosticInfoCollection diagnosticInfos;
  495. ResponseHeader responseHeader = session.TranslateBrowsePathsToNodeIds(
  496. null,
  497. browsePaths,
  498. out results,
  499. out diagnosticInfos );
  500. // ensure that the server returned valid results.
  501. Session.ValidateResponse( results, browsePaths );
  502. Session.ValidateDiagnosticInfos( diagnosticInfos, browsePaths );
  503. // collect the list of node ids found.
  504. List<NodeId> nodes = new List<NodeId>( );
  505. for (int ii = 0; ii < results.Count; ii++)
  506. {
  507. // check if the start node actually exists.
  508. if (StatusCode.IsBad( results[ii].StatusCode ))
  509. {
  510. nodes.Add( null );
  511. continue;
  512. }
  513. // an empty list is returned if no node was found.
  514. if (results[ii].Targets.Count == 0)
  515. {
  516. nodes.Add( null );
  517. continue;
  518. }
  519. // Multiple matches are possible, however, the node that matches the type model is the
  520. // one we are interested in here. The rest can be ignored.
  521. BrowsePathTarget target = results[ii].Targets[0];
  522. if (target.RemainingPathIndex != UInt32.MaxValue)
  523. {
  524. nodes.Add( null );
  525. continue;
  526. }
  527. // The targetId is an ExpandedNodeId because it could be node in another server.
  528. // The ToNodeId function is used to convert a local NodeId stored in a ExpandedNodeId to a NodeId.
  529. nodes.Add( ExpandedNodeId.ToNodeId( target.TargetId, session.NamespaceUris ) );
  530. }
  531. // return whatever was found.
  532. return nodes;
  533. }
  534. #endregion
  535. #region Events
  536. /// <summary>
  537. /// Finds the type of the event for the notification.
  538. /// </summary>
  539. /// <param name="monitoredItem">The monitored item.</param>
  540. /// <param name="notification">The notification.</param>
  541. /// <returns>The NodeId of the EventType.</returns>
  542. public static NodeId FindEventType( MonitoredItem monitoredItem, EventFieldList notification )
  543. {
  544. EventFilter filter = monitoredItem.Status.Filter as EventFilter;
  545. if (filter != null)
  546. {
  547. for (int ii = 0; ii < filter.SelectClauses.Count; ii++)
  548. {
  549. SimpleAttributeOperand clause = filter.SelectClauses[ii];
  550. if (clause.BrowsePath.Count == 1 && clause.BrowsePath[0] == BrowseNames.EventType)
  551. {
  552. return notification.EventFields[ii].Value as NodeId;
  553. }
  554. }
  555. }
  556. return null;
  557. }
  558. /// <summary>
  559. /// Constructs an event object from a notification.
  560. /// </summary>
  561. /// <param name="session">The session.</param>
  562. /// <param name="monitoredItem">The monitored item that produced the notification.</param>
  563. /// <param name="notification">The notification.</param>
  564. /// <param name="knownEventTypes">The known event types.</param>
  565. /// <param name="eventTypeMappings">Mapping between event types and known event types.</param>
  566. /// <returns>
  567. /// The event object. Null if the notification is not a valid event type.
  568. /// </returns>
  569. public static BaseEventState ConstructEvent(
  570. Session session,
  571. MonitoredItem monitoredItem,
  572. EventFieldList notification,
  573. Dictionary<NodeId, Type> knownEventTypes,
  574. Dictionary<NodeId, NodeId> eventTypeMappings )
  575. {
  576. // find the event type.
  577. NodeId eventTypeId = FindEventType( monitoredItem, notification );
  578. if (eventTypeId == null)
  579. {
  580. return null;
  581. }
  582. // look up the known event type.
  583. Type knownType = null;
  584. NodeId knownTypeId = null;
  585. if (eventTypeMappings.TryGetValue( eventTypeId, out knownTypeId ))
  586. {
  587. knownType = knownEventTypes[knownTypeId];
  588. }
  589. // try again.
  590. if (knownType == null)
  591. {
  592. if (knownEventTypes.TryGetValue( eventTypeId, out knownType ))
  593. {
  594. knownTypeId = eventTypeId;
  595. eventTypeMappings.Add( eventTypeId, eventTypeId );
  596. }
  597. }
  598. // try mapping it to a known type.
  599. if (knownType == null)
  600. {
  601. // browse for the supertypes of the event type.
  602. ReferenceDescriptionCollection supertypes = ClientUtils.BrowseSuperTypes( session, eventTypeId, false );
  603. // can't do anything with unknown types.
  604. if (supertypes == null)
  605. {
  606. return null;
  607. }
  608. // find the first supertype that matches a known event type.
  609. for (int ii = 0; ii < supertypes.Count; ii++)
  610. {
  611. NodeId superTypeId = (NodeId)supertypes[ii].NodeId;
  612. if (knownEventTypes.TryGetValue( superTypeId, out knownType ))
  613. {
  614. knownTypeId = superTypeId;
  615. eventTypeMappings.Add( eventTypeId, superTypeId );
  616. }
  617. if (knownTypeId != null)
  618. {
  619. break;
  620. }
  621. }
  622. // can't do anything with unknown types.
  623. if (knownTypeId == null)
  624. {
  625. return null;
  626. }
  627. }
  628. // construct the event based on the known event type.
  629. BaseEventState e = (BaseEventState)Activator.CreateInstance( knownType, new object[] { (NodeState)null } );
  630. // get the filter which defines the contents of the notification.
  631. EventFilter filter = monitoredItem.Status.Filter as EventFilter;
  632. // initialize the event with the values in the notification.
  633. e.Update( session.SystemContext, filter.SelectClauses, notification );
  634. // save the orginal notification.
  635. e.Handle = notification;
  636. return e;
  637. }
  638. #endregion
  639. #region Type Model Browsing
  640. /// <summary>
  641. /// Collects the instance declarations for a type.
  642. /// </summary>
  643. public static List<InstanceDeclaration> CollectInstanceDeclarationsForType( Session session, NodeId typeId )
  644. {
  645. return CollectInstanceDeclarationsForType( session, typeId, true );
  646. }
  647. /// <summary>
  648. /// Collects the instance declarations for a type.
  649. /// </summary>
  650. public static List<InstanceDeclaration> CollectInstanceDeclarationsForType( Session session, NodeId typeId, bool includeSupertypes )
  651. {
  652. // process the types starting from the top of the tree.
  653. List<InstanceDeclaration> instances = new List<InstanceDeclaration>( );
  654. Dictionary<string, InstanceDeclaration> map = new Dictionary<string, InstanceDeclaration>( );
  655. // get the supertypes.
  656. if (includeSupertypes)
  657. {
  658. ReferenceDescriptionCollection supertypes = ClientUtils.BrowseSuperTypes( session, typeId, false );
  659. if (supertypes != null)
  660. {
  661. for (int ii = supertypes.Count - 1; ii >= 0; ii--)
  662. {
  663. CollectInstanceDeclarations( session, (NodeId)supertypes[ii].NodeId, null, instances, map );
  664. }
  665. }
  666. }
  667. // collect the fields for the selected type.
  668. CollectInstanceDeclarations( session, typeId, null, instances, map );
  669. // return the complete list.
  670. return instances;
  671. }
  672. /// <summary>
  673. /// Collects the fields for the instance node.
  674. /// </summary>
  675. private static void CollectInstanceDeclarations(
  676. Session session,
  677. NodeId typeId,
  678. InstanceDeclaration parent,
  679. List<InstanceDeclaration> instances,
  680. IDictionary<string, InstanceDeclaration> map )
  681. {
  682. // find the children.
  683. BrowseDescription nodeToBrowse = new BrowseDescription( );
  684. if (parent == null)
  685. {
  686. nodeToBrowse.NodeId = typeId;
  687. }
  688. else
  689. {
  690. nodeToBrowse.NodeId = parent.NodeId;
  691. }
  692. nodeToBrowse.BrowseDirection = BrowseDirection.Forward;
  693. nodeToBrowse.ReferenceTypeId = ReferenceTypeIds.HasChild;
  694. nodeToBrowse.IncludeSubtypes = true;
  695. nodeToBrowse.NodeClassMask = (uint)(NodeClass.Object | NodeClass.Variable | NodeClass.Method);
  696. nodeToBrowse.ResultMask = (uint)BrowseResultMask.All;
  697. // ignore any browsing errors.
  698. ReferenceDescriptionCollection references = ClientUtils.Browse( session, nodeToBrowse, false );
  699. if (references == null)
  700. {
  701. return;
  702. }
  703. // process the children.
  704. List<NodeId> nodeIds = new List<NodeId>( );
  705. List<InstanceDeclaration> children = new List<InstanceDeclaration>( );
  706. for (int ii = 0; ii < references.Count; ii++)
  707. {
  708. ReferenceDescription reference = references[ii];
  709. if (reference.NodeId.IsAbsolute)
  710. {
  711. continue;
  712. }
  713. // create a new declaration.
  714. InstanceDeclaration child = new InstanceDeclaration( );
  715. child.RootTypeId = typeId;
  716. child.NodeId = (NodeId)reference.NodeId;
  717. child.BrowseName = reference.BrowseName;
  718. child.NodeClass = reference.NodeClass;
  719. if (!LocalizedText.IsNullOrEmpty( reference.DisplayName ))
  720. {
  721. child.DisplayName = reference.DisplayName.Text;
  722. }
  723. else
  724. {
  725. child.DisplayName = reference.BrowseName.Name;
  726. }
  727. if (parent != null)
  728. {
  729. child.BrowsePath = new QualifiedNameCollection( parent.BrowsePath );
  730. child.BrowsePathDisplayText = Utils.Format( "{0}/{1}", parent.BrowsePathDisplayText, reference.BrowseName );
  731. child.DisplayPath = Utils.Format( "{0}/{1}", parent.DisplayPath, reference.DisplayName );
  732. }
  733. else
  734. {
  735. child.BrowsePath = new QualifiedNameCollection( );
  736. child.BrowsePathDisplayText = Utils.Format( "{0}", reference.BrowseName );
  737. child.DisplayPath = Utils.Format( "{0}", reference.DisplayName );
  738. }
  739. child.BrowsePath.Add( reference.BrowseName );
  740. // check if reading an overridden declaration.
  741. InstanceDeclaration overriden = null;
  742. if (map.TryGetValue( child.BrowsePathDisplayText, out overriden ))
  743. {
  744. child.OverriddenDeclaration = overriden;
  745. }
  746. map[child.BrowsePathDisplayText] = child;
  747. // add to list.
  748. children.Add( child );
  749. nodeIds.Add( child.NodeId );
  750. }
  751. // check if nothing more to do.
  752. if (children.Count == 0)
  753. {
  754. return;
  755. }
  756. // find the modelling rules.
  757. List<NodeId> modellingRules = FindTargetOfReference( session, nodeIds, Opc.Ua.ReferenceTypeIds.HasModellingRule, false );
  758. if (modellingRules != null)
  759. {
  760. for (int ii = 0; ii < nodeIds.Count; ii++)
  761. {
  762. children[ii].ModellingRule = modellingRules[ii];
  763. // if the modelling rule is null then the instance is not part of the type declaration.
  764. if (NodeId.IsNull( modellingRules[ii] ))
  765. {
  766. map.Remove( children[ii].BrowsePathDisplayText );
  767. }
  768. }
  769. }
  770. // update the descriptions.
  771. UpdateInstanceDescriptions( session, children, false );
  772. // recusively collect instance declarations for the tree below.
  773. for (int ii = 0; ii < children.Count; ii++)
  774. {
  775. if (!NodeId.IsNull( children[ii].ModellingRule ))
  776. {
  777. instances.Add( children[ii] );
  778. CollectInstanceDeclarations( session, typeId, children[ii], instances, map );
  779. }
  780. }
  781. }
  782. /// <summary>
  783. /// Finds the targets for the specified reference.
  784. /// </summary>
  785. private static List<NodeId> FindTargetOfReference( Session session, List<NodeId> nodeIds, NodeId referenceTypeId, bool throwOnError )
  786. {
  787. try
  788. {
  789. // construct browse request.
  790. BrowseDescriptionCollection nodesToBrowse = new BrowseDescriptionCollection( );
  791. for (int ii = 0; ii < nodeIds.Count; ii++)
  792. {
  793. BrowseDescription nodeToBrowse = new BrowseDescription( );
  794. nodeToBrowse.NodeId = nodeIds[ii];
  795. nodeToBrowse.BrowseDirection = BrowseDirection.Forward;
  796. nodeToBrowse.ReferenceTypeId = referenceTypeId;
  797. nodeToBrowse.IncludeSubtypes = false;
  798. nodeToBrowse.NodeClassMask = 0;
  799. nodeToBrowse.ResultMask = (uint)BrowseResultMask.None;
  800. nodesToBrowse.Add( nodeToBrowse );
  801. }
  802. // start the browse operation.
  803. BrowseResultCollection results = null;
  804. DiagnosticInfoCollection diagnosticInfos = null;
  805. session.Browse(
  806. null,
  807. null,
  808. 1,
  809. nodesToBrowse,
  810. out results,
  811. out diagnosticInfos );
  812. ClientBase.ValidateResponse( results, nodesToBrowse );
  813. ClientBase.ValidateDiagnosticInfos( diagnosticInfos, nodesToBrowse );
  814. List<NodeId> targetIds = new List<NodeId>( );
  815. ByteStringCollection continuationPoints = new ByteStringCollection( );
  816. for (int ii = 0; ii < nodeIds.Count; ii++)
  817. {
  818. targetIds.Add( null );
  819. // check for error.
  820. if (StatusCode.IsBad( results[ii].StatusCode ))
  821. {
  822. continue;
  823. }
  824. // check for continuation point.
  825. if (results[ii].ContinuationPoint != null && results[ii].ContinuationPoint.Length > 0)
  826. {
  827. continuationPoints.Add( results[ii].ContinuationPoint );
  828. }
  829. // get the node id.
  830. if (results[ii].References.Count > 0)
  831. {
  832. if (NodeId.IsNull( results[ii].References[0].NodeId ) || results[ii].References[0].NodeId.IsAbsolute)
  833. {
  834. continue;
  835. }
  836. targetIds[ii] = (NodeId)results[ii].References[0].NodeId;
  837. }
  838. }
  839. // release continuation points.
  840. if (continuationPoints.Count > 0)
  841. {
  842. session.BrowseNext(
  843. null,
  844. true,
  845. continuationPoints,
  846. out results,
  847. out diagnosticInfos );
  848. ClientBase.ValidateResponse( results, nodesToBrowse );
  849. ClientBase.ValidateDiagnosticInfos( diagnosticInfos, nodesToBrowse );
  850. }
  851. //return complete list.
  852. return targetIds;
  853. }
  854. catch (Exception exception)
  855. {
  856. if (throwOnError)
  857. {
  858. throw new ServiceResultException( exception, StatusCodes.BadUnexpectedError );
  859. }
  860. return null;
  861. }
  862. }
  863. /// <summary>
  864. /// Finds the targets for the specified reference.
  865. /// </summary>
  866. private static void UpdateInstanceDescriptions( Session session, List<InstanceDeclaration> instances, bool throwOnError )
  867. {
  868. try
  869. {
  870. ReadValueIdCollection nodesToRead = new ReadValueIdCollection( );
  871. for (int ii = 0; ii < instances.Count; ii++)
  872. {
  873. ReadValueId nodeToRead = new ReadValueId( );
  874. nodeToRead.NodeId = instances[ii].NodeId;
  875. nodeToRead.AttributeId = Attributes.Description;
  876. nodesToRead.Add( nodeToRead );
  877. nodeToRead = new ReadValueId( );
  878. nodeToRead.NodeId = instances[ii].NodeId;
  879. nodeToRead.AttributeId = Attributes.DataType;
  880. nodesToRead.Add( nodeToRead );
  881. nodeToRead = new ReadValueId( );
  882. nodeToRead.NodeId = instances[ii].NodeId;
  883. nodeToRead.AttributeId = Attributes.ValueRank;
  884. nodesToRead.Add( nodeToRead );
  885. }
  886. // start the browse operation.
  887. DataValueCollection results = null;
  888. DiagnosticInfoCollection diagnosticInfos = null;
  889. session.Read(
  890. null,
  891. 0,
  892. TimestampsToReturn.Neither,
  893. nodesToRead,
  894. out results,
  895. out diagnosticInfos );
  896. ClientBase.ValidateResponse( results, nodesToRead );
  897. ClientBase.ValidateDiagnosticInfos( diagnosticInfos, nodesToRead );
  898. // update the instances.
  899. for (int ii = 0; ii < nodesToRead.Count; ii += 3)
  900. {
  901. InstanceDeclaration instance = instances[ii / 3];
  902. instance.Description = results[ii].GetValue<LocalizedText>( LocalizedText.Null ).Text;
  903. instance.DataType = results[ii + 1].GetValue<NodeId>( NodeId.Null );
  904. instance.ValueRank = results[ii + 2].GetValue<int>( ValueRanks.Any );
  905. if (!NodeId.IsNull( instance.DataType ))
  906. {
  907. instance.BuiltInType = DataTypes.GetBuiltInType( instance.DataType, session.TypeTree );
  908. instance.DataTypeDisplayText = session.NodeCache.GetDisplayText( instance.DataType );
  909. if (instance.ValueRank >= 0)
  910. {
  911. instance.DataTypeDisplayText += "[]";
  912. }
  913. }
  914. }
  915. }
  916. catch (Exception exception)
  917. {
  918. if (throwOnError)
  919. {
  920. throw new ServiceResultException( exception, StatusCodes.BadUnexpectedError );
  921. }
  922. }
  923. }
  924. #endregion
  925. #region Private Methods
  926. /// <summary>
  927. /// Collects the fields for the type.
  928. /// </summary>
  929. /// <param name="session">The session.</param>
  930. /// <param name="typeId">The type id.</param>
  931. /// <param name="fields">The fields.</param>
  932. /// <param name="fieldNodeIds">The node id for the declaration of the field.</param>
  933. private static void CollectFieldsForType( Session session, NodeId typeId, SimpleAttributeOperandCollection fields, List<NodeId> fieldNodeIds )
  934. {
  935. // get the supertypes.
  936. ReferenceDescriptionCollection supertypes = ClientUtils.BrowseSuperTypes( session, typeId, false );
  937. if (supertypes == null)
  938. {
  939. return;
  940. }
  941. // process the types starting from the top of the tree.
  942. Dictionary<NodeId, QualifiedNameCollection> foundNodes = new Dictionary<NodeId, QualifiedNameCollection>( );
  943. QualifiedNameCollection parentPath = new QualifiedNameCollection( );
  944. for (int ii = supertypes.Count - 1; ii >= 0; ii--)
  945. {
  946. CollectFields( session, (NodeId)supertypes[ii].NodeId, parentPath, fields, fieldNodeIds, foundNodes );
  947. }
  948. // collect the fields for the selected type.
  949. CollectFields( session, typeId, parentPath, fields, fieldNodeIds, foundNodes );
  950. }
  951. /// <summary>
  952. /// Collects the fields for the instance.
  953. /// </summary>
  954. /// <param name="session">The session.</param>
  955. /// <param name="instanceId">The instance id.</param>
  956. /// <param name="fields">The fields.</param>
  957. /// <param name="fieldNodeIds">The node id for the declaration of the field.</param>
  958. private static void CollectFieldsForInstance( Session session, NodeId instanceId, SimpleAttributeOperandCollection fields, List<NodeId> fieldNodeIds )
  959. {
  960. Dictionary<NodeId, QualifiedNameCollection> foundNodes = new Dictionary<NodeId, QualifiedNameCollection>( );
  961. QualifiedNameCollection parentPath = new QualifiedNameCollection( );
  962. CollectFields( session, instanceId, parentPath, fields, fieldNodeIds, foundNodes );
  963. }
  964. /// <summary>
  965. /// Collects the fields for the instance node.
  966. /// </summary>
  967. /// <param name="session">The session.</param>
  968. /// <param name="nodeId">The node id.</param>
  969. /// <param name="parentPath">The parent path.</param>
  970. /// <param name="fields">The event fields.</param>
  971. /// <param name="fieldNodeIds">The node id for the declaration of the field.</param>
  972. /// <param name="foundNodes">The table of found nodes.</param>
  973. private static void CollectFields(
  974. Session session,
  975. NodeId nodeId,
  976. QualifiedNameCollection parentPath,
  977. SimpleAttributeOperandCollection fields,
  978. List<NodeId> fieldNodeIds,
  979. Dictionary<NodeId, QualifiedNameCollection> foundNodes )
  980. {
  981. // find all of the children of the field.
  982. BrowseDescription nodeToBrowse = new BrowseDescription( );
  983. nodeToBrowse.NodeId = nodeId;
  984. nodeToBrowse.BrowseDirection = BrowseDirection.Forward;
  985. nodeToBrowse.ReferenceTypeId = ReferenceTypeIds.Aggregates;
  986. nodeToBrowse.IncludeSubtypes = true;
  987. nodeToBrowse.NodeClassMask = (uint)(NodeClass.Object | NodeClass.Variable);
  988. nodeToBrowse.ResultMask = (uint)BrowseResultMask.All;
  989. ReferenceDescriptionCollection children = ClientUtils.Browse( session, nodeToBrowse, false );
  990. if (children == null)
  991. {
  992. return;
  993. }
  994. // process the children.
  995. for (int ii = 0; ii < children.Count; ii++)
  996. {
  997. ReferenceDescription child = children[ii];
  998. if (child.NodeId.IsAbsolute)
  999. {
  1000. continue;
  1001. }
  1002. // construct browse path.
  1003. QualifiedNameCollection browsePath = new QualifiedNameCollection( parentPath );
  1004. browsePath.Add( child.BrowseName );
  1005. // check if the browse path is already in the list.
  1006. int index = ContainsPath( fields, browsePath );
  1007. if (index < 0)
  1008. {
  1009. SimpleAttributeOperand field = new SimpleAttributeOperand( );
  1010. field.TypeDefinitionId = ObjectTypeIds.BaseEventType;
  1011. field.BrowsePath = browsePath;
  1012. field.AttributeId = (child.NodeClass == NodeClass.Variable) ? Attributes.Value : Attributes.NodeId;
  1013. fields.Add( field );
  1014. fieldNodeIds.Add( (NodeId)child.NodeId );
  1015. }
  1016. // recusively find all of the children.
  1017. NodeId targetId = (NodeId)child.NodeId;
  1018. // need to guard against loops.
  1019. if (!foundNodes.ContainsKey( targetId ))
  1020. {
  1021. foundNodes.Add( targetId, browsePath );
  1022. CollectFields( session, (NodeId)child.NodeId, browsePath, fields, fieldNodeIds, foundNodes );
  1023. }
  1024. }
  1025. }
  1026. /// <summary>
  1027. /// Determines whether the specified select clause contains the browse path.
  1028. /// </summary>
  1029. /// <param name="selectClause">The select clause.</param>
  1030. /// <param name="browsePath">The browse path.</param>
  1031. /// <returns>
  1032. /// <c>true</c> if the specified select clause contains path; otherwise, <c>false</c>.
  1033. /// </returns>
  1034. private static int ContainsPath( SimpleAttributeOperandCollection selectClause, QualifiedNameCollection browsePath )
  1035. {
  1036. for (int ii = 0; ii < selectClause.Count; ii++)
  1037. {
  1038. SimpleAttributeOperand field = selectClause[ii];
  1039. if (field.BrowsePath.Count != browsePath.Count)
  1040. {
  1041. continue;
  1042. }
  1043. bool match = true;
  1044. for (int jj = 0; jj < field.BrowsePath.Count; jj++)
  1045. {
  1046. if (field.BrowsePath[jj] != browsePath[jj])
  1047. {
  1048. match = false;
  1049. break;
  1050. }
  1051. }
  1052. if (match)
  1053. {
  1054. return ii;
  1055. }
  1056. }
  1057. return -1;
  1058. }
  1059. #endregion
  1060. }
  1061. }