UABaseNode.ts 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282
  1. import type { NamespaceTable } from "./NameSpaceTable";
  2. import { coerceNodeId, NodeId } from "./NodeId";
  3. import { UAReference } from "./UAReference";
  4. import { assert } from "@/util/assert";
  5. import { XMLElem, type IToXML } from "@/util/XmlElem";
  6. import { UARolePermission } from "./UARolePermission";
  7. import { type IAddressSpace } from "./IAddressSpace";
  8. import { ReferenceTypeIds } from "./opcua_node_ids";
  9. import { UALocalizedText } from "./UALocalizedText";
  10. export class UABaseNode implements IToXML{
  11. public static nullBaseNode=new UABaseNode({browseName: "", addressSpace: {} as IAddressSpace, nodeId: NodeId.nullNodeId});
  12. public nodeId!: NodeId;
  13. public nodeClass="UABaseNode";
  14. public browseName!: string;
  15. public addressSpace!: IAddressSpace
  16. public displayName: UALocalizedText[]=[];
  17. public description: UALocalizedText[]=[];
  18. public symbolicName?: string;
  19. public releaseStatus!: string;
  20. public hasNoPermissions!: boolean;
  21. public writeMask!: number;
  22. public userWriteMask!: number;
  23. public category: string[]=[];
  24. public documentation?: string;
  25. public accessRestriction?: number;
  26. public rolePermissions: UARolePermission[]=[];
  27. public references: UAReference[]=[];
  28. constructor(options: UABaseNodeOptions) {
  29. Object.assign(this, options);
  30. }
  31. reIndex(nst: NamespaceTable, onst: NamespaceTable) {
  32. const nsName=onst.getUri(this.nodeId.namespace);
  33. assert(nsName!=undefined)
  34. const newIndex=nst.getIndex(nsName);
  35. assert(newIndex!=undefined)
  36. this.nodeId.namespace=newIndex;
  37. for(const uaref of this.references) {
  38. uaref.reIndex(nst, onst);
  39. }
  40. }
  41. getParent(): UABaseNode|null {
  42. const ref=this.getParentRef();
  43. if(ref?.fromNode===this)
  44. return ref.toNode;
  45. if(ref?.toNode===this)
  46. return ref.fromNode;
  47. return null;
  48. }
  49. setParent(newParentNode: UABaseNode, newRefType: string) {
  50. const hierReferences=this.addressSpace.getSubTreeAsList("ns=0;i="+ReferenceTypeIds.HierarchicalReferences);
  51. let href;
  52. for(const r of this.references) { //find current href in references
  53. href=hierReferences.find((n) => {n.browseName===r.referenceType})
  54. }
  55. for(let i=this.references.length;i--;i>=0) { //remove current href from references
  56. const r=this.references[i];
  57. if(r.referenceType==href?.browseName)
  58. this.references.splice(i,1);
  59. }
  60. const newRefA=new UAReference(this.nodeId, newRefType, newParentNode.nodeId, true);
  61. newRefA.fromNode=this;
  62. newRefA.toNode=newParentNode;
  63. this.references.push(newRefA);
  64. const newRefB=new UAReference(newParentNode.nodeId, newRefType, this.nodeId, false);
  65. newRefB.fromNode=this;
  66. newRefB.toNode=newParentNode;
  67. this.references.push(newRefB);
  68. }
  69. getParentRef(): UAReference|null{
  70. for(const ref of this.references) {
  71. switch(ref.referenceType) {
  72. case 'HasComponent':
  73. case 'HasOrderedComponent':
  74. case 'Organizes':
  75. case 'HasProperty':
  76. case 'HasSubtype':
  77. case 'HasAddIn':
  78. if(ref.fromNode===this&&ref.isForward===false)
  79. return ref;
  80. if(ref.toNode===this&&ref.isForward===true)
  81. return ref;
  82. }
  83. }
  84. return null;
  85. }
  86. getChildren(): UABaseNode[] {
  87. const children: UABaseNode[]=[];
  88. for(const ref of this.references) {
  89. switch(ref.referenceType) {
  90. case 'HasComponent':
  91. case 'HasOrderedComponent':
  92. case 'Organizes':
  93. case 'HasProperty':
  94. case 'HasSubtype':
  95. case 'HasAddIn':
  96. if(ref.isForward&&ref.fromNode===this) {
  97. if(!children.includes(ref.toNode))
  98. children.push(ref.toNode);
  99. }
  100. if(!ref.isForward&&ref.toNode===this) {
  101. if(!children.includes(ref.fromNode))
  102. children.push(ref.fromNode);
  103. }
  104. break;
  105. }
  106. }
  107. return children;
  108. }
  109. getModellingRule(): string|undefined{
  110. for(const ref of this.references) {
  111. if(ref.referenceType == "HasModellingRule"){
  112. return ref.toNode.browseName;
  113. }
  114. }
  115. return undefined;
  116. }
  117. //TODO: check name, function, etc. or delete
  118. getInstanceDecl(nid: string, mrType: string){
  119. const aggregates = (this.addressSpace?.getSubTreeAsList("i="+ReferenceTypeIds.Aggregates)||[]);
  120. const aggStrings:String[] = [];
  121. aggregates.forEach((item) => {
  122. aggStrings.push(item.browseName);
  123. })
  124. const node = (this.addressSpace.findNode(nid)) as UABaseNode;
  125. const res:string[] = [];
  126. for(const item of node.references) {
  127. if(aggStrings.includes(item.referenceType) && item.isForward){
  128. const mr = item.toNode.getModellingRule()||"";
  129. if(mr == mrType){
  130. res.push(item.toNode.browseName);
  131. }
  132. }
  133. }
  134. return res;
  135. }
  136. getNodeVersion(nid:string){
  137. const node = this.addressSpace.findNode(nid);
  138. if(!node)
  139. return "";
  140. const refs = node.references;
  141. refs.forEach((ref)=>{
  142. if(ref.toNode.browseName == "NodeVersion" && ref.isForward){
  143. return ref.toNode.nodeId.toString()
  144. }else if(ref.fromNode.browseName == "NodeVersion" && !ref.isForward){
  145. return ref.fromNode.nodeId.toString()
  146. }
  147. })
  148. }
  149. resolveReferences(nm: Map<string, UABaseNode>) {
  150. for(const ref of this.references) {
  151. const fromNode=nm.get(ref.fromRef.toString())
  152. if(fromNode)
  153. ref.fromNode=fromNode;
  154. const toNode=nm.get(ref.toRef.toString())
  155. if(!toNode)
  156. continue; //TODO: if we cant find the node; the parser is still incomplete or the nodeset is broken
  157. ref.toNode=toNode;
  158. if(ref.fromNode.nodeId.toString()===this.nodeId.toString()){ //add this reference to referenced node
  159. //Bug? when loading from filedrop; fromNode is a proxy; this is not.
  160. if(!ref.toNode.references.includes(ref))
  161. ref.toNode.references.push(ref);
  162. }
  163. }
  164. }
  165. static fromXML(xmlObject: any, addressSpace: IAddressSpace): UABaseNode{
  166. const references:UAReference[]=[];
  167. const nodeId=coerceNodeId(xmlObject['@_NodeId']);
  168. for(const xmlref of xmlObject['References']?.['Reference']||[]) {
  169. references.push(UAReference.fromXML(xmlref, nodeId));
  170. }
  171. const displayNames:UALocalizedText[]=[];
  172. for(const xmldn of xmlObject['DisplayName']||[]) {
  173. displayNames.push(UALocalizedText.fromXML("DisplayName", xmldn));
  174. }
  175. const descriptions:UALocalizedText[]=[];
  176. for(const desc of xmlObject['Description']||[]) {
  177. descriptions.push(UALocalizedText.fromXML("Description", desc));
  178. }
  179. const permissions:UARolePermission[]=[];
  180. for(const perm of xmlObject['RolePermissions']?.['RolePermission']||[]) {
  181. permissions.push(UARolePermission.fromXML(perm));
  182. }
  183. const ua=new UABaseNode({addressSpace: addressSpace,
  184. nodeId: nodeId,
  185. browseName: xmlObject['@_BrowseName'],
  186. writeMask: Number(xmlObject['@_WriteMask']||0),
  187. userWriteMask: Number(xmlObject['@_UserWriteMask']||0),
  188. accessRestriction: xmlObject['@_AccessRestrictions'],
  189. hasNoPermissions: xmlObject['@_HasNoPermissions']||false,
  190. symbolicName: xmlObject['@_SymbolicName'],
  191. releaseStatus: xmlObject['@_ReleaseStatus']||"Released",
  192. displayName: displayNames,
  193. description: descriptions,
  194. category: xmlObject['Category'],
  195. documentation: xmlObject['Documentation']?.['#text'],
  196. references: references,
  197. rolePermissions: permissions,
  198. });
  199. return ua;
  200. }
  201. toXML(lnst: NamespaceTable, gnst: NamespaceTable): XMLElem {
  202. const nid=UABaseNode.localNodeId(this.nodeId, lnst, gnst);
  203. const elem =new XMLElem('UABaseNode');
  204. elem.attr('NodeId', nid.toString())
  205. .attr('BrowseName', this.browseName)
  206. .attr('WriteMask', this.writeMask.toString())
  207. .attr('UserWriteMask', this.userWriteMask.toString())
  208. .attr('AccessRestriction', this.accessRestriction?.toString())
  209. .attr('HasNoPermissions', this.hasNoPermissions)
  210. .attr('SymbolicName', this.symbolicName)
  211. .attr('ReleaseStatus',this.releaseStatus)
  212. .attr('Documentation', this.documentation);
  213. for(const c in this.category) {
  214. elem.attr('Category', c);
  215. }
  216. const refs=new XMLElem('References');
  217. for(const ref of this.references) {
  218. if(ref.fromNode.nodeId.toString()==this.nodeId.toString()) //on load resolveReferences() duplicates references to both sides. skip them for export
  219. refs.add(ref.toXML(lnst, gnst));
  220. }
  221. if(refs.elements.length>0)
  222. elem.add(refs);
  223. for(const dn of this.displayName)
  224. elem.add(dn.toXML());
  225. for(const desc of this.description)
  226. elem.add(desc.toXML());
  227. const perms=new XMLElem('RolePermissions')
  228. for(const perm of this.rolePermissions)
  229. perms.add(perm.toXML());
  230. if(perms.elements.length>0)
  231. elem.add(perms);
  232. return elem;
  233. }
  234. static localNodeId(nodeId: NodeId, lnst: NamespaceTable, gnst: NamespaceTable) {
  235. const ns_uri=gnst.getUri(nodeId.namespace);
  236. assert(ns_uri);
  237. const nsIdx=lnst.getIndex(ns_uri);
  238. assert(nsIdx);
  239. const tmpNode= coerceNodeId(nodeId.toString());
  240. tmpNode.namespace=nsIdx;
  241. return tmpNode;
  242. }
  243. }
  244. export interface UABaseNodeOptions {
  245. addressSpace: IAddressSpace;
  246. nodeId: NodeId;
  247. browseName: string;
  248. writeMask?: number;
  249. userWriteMask?: number;
  250. accessRestriction?: number;
  251. hasNoPermissions?: boolean;
  252. symbolicName?: string;
  253. releaseStatus?: string;
  254. displayName?: UALocalizedText[];
  255. description?: UALocalizedText[];
  256. category?: string[];
  257. documentation?: string;
  258. references?: UAReference[];
  259. rolePermissions?: UARolePermission[];
  260. }