Преглед на файлове

added dynamic node specification

dstrutzenberger преди 10 месеца
родител
ревизия
38ea3b77b6
променени са 12 файла, в които са добавени 360 реда и са изтрити 62 реда
  1. 144 40
      src/components/TheDynamics.vue
  2. 1 2
      src/ua/AddressSpace.ts
  3. 66 0
      src/ua/DynamicNode.ts
  4. 20 2
      src/ua/UABaseNode.ts
  5. 10 0
      src/ua/UAMethod.ts
  6. 7 9
      src/ua/UAObject.ts
  7. 17 0
      src/ua/UAObjectType.ts
  8. 4 9
      src/ua/UAVariable.ts
  9. 8 0
      src/ua/UAVariableType.ts
  10. 1 0
      src/util/aggregates.ts
  11. 77 0
      src/util/sconfig.ts
  12. 5 0
      src/util/store.ts

+ 144 - 40
src/components/TheDynamics.vue

@@ -1,7 +1,7 @@
 <script setup lang="ts">
 import { useStore } from '@/util/store'
 import { computed, ref } from 'vue';
-import type { UABaseNode } from '@/ua/UABaseNode';
+import { UABaseNode } from '@/ua/UABaseNode';
 import { ReferenceTypeIds } from '@/ua/opcua_node_ids';
 import type { UAReferenceType } from '@/ua/UAReferenceType';
 import { ObjectTypeIds } from '@/ua/opcua_node_ids';
@@ -11,23 +11,41 @@ import type { UAVariableType } from '@/ua/UAVariableType';
 import { UAReference } from '@/ua/UAReference';
 import { UAObject } from '@/ua/UAObject';
 import { UAVariable } from '@/ua/UAVariable';
-import {sconfig} from "@/util/ServerConfig"
-const store = useStore()
+import { DynamicNode, ComponentPair} from '@/ua/DynamicNode';
+import { storeToRefs } from 'pinia';
+import { isNullOrUndefined } from 'util';
+const store = useStore();
+
 
 const parentDialogOpen = ref(false);
-const node = computed(() => {
-  return store.selectedNode;
+const { selectedNode} = storeToRefs(store)
+
+
+
+const nameSpaceName = computed(() => {
+  if(!selectedNode.value)
+    return "";
+  let nsIdx=selectedNode.value.nodeId.namespace;
+  return store.addressSpace?.nst.getUri(nsIdx);
+})
+
+let opt = {
+  ident: "MyDyn",
+  namespaceUri:"MyNamespace",
+  parentNodeId: "MyNodeId",
+  checkInterval:100,
+  nodeClass:"Object",
+  typeNodeId:"ns=0;i=88",
+  name:"Dynamic",
+  startIndex:0
+};
+
+let dynNode = new DynamicNode(opt);
 
-});
 
-function clickNode(clickedNode: UABaseNode) {
-  //TODO: replace parent references
-  // node.value?.setParent(clickedNode, store.addressSpace?.findNode("ns=0;i="+ ReferenceTypeIds.HierarchicalReferences));
-  parentDialogOpen.value=false;
-}
 
 function filter(fnode: UABaseNode) {
-  if(node.value?.nodeClass==="Variable") {
+  if(selectedNode.value?.nodeClass==="Variable") {
     if(fnode.nodeClass!="Object")
       return true;
   }
@@ -59,13 +77,13 @@ function defType(flag:any ):String{
     return lab;
 }
 
-
-
-function getInstanceDecl(nid="ns=0;i=58"){
-  if(nid == "") nid = "ns=0;i=58";
+function getInstanceDecl(nid="ns=0;i=84"){
+  if(nid==""){nid="ns=0;i=84"}
   let component_refs:String[] = [];
   let res_man:any[] = [];
   let res_opt:any[] = [];
+      dynNode.mandatory = [];
+      dynNode.optionals = [];
   let res:any[] = [];
   let node =  (store.addressSpace?.findNode(nid)) as UABaseNode;
   let refs = node.references;
@@ -78,42 +96,94 @@ function getInstanceDecl(nid="ns=0;i=58"){
       let mr = "";
       if(item.toNode.nodeClass == "Object"){
         let i = item.toNode as UAObject;
-        mr = i.getModellingRule();
+        mr = i.getModellingRule()||"";
       }else if(item.toNode.nodeClass == "Variable"){
         let i = item.toNode as UAVariable;
-        mr = i.getModellingRule();
+        mr = i.getModellingRule()||"";
       }
       if(mr == "Mandatory"){
-        const newItem = { ref: item, checked: true, modellingRule:mr };
-        res_man.push(newItem);
+        const newItem = new ComponentPair(item.toNode.displayName , true);
+        res_man.push(item.toNode.displayName);
       }else{
-        const newItem = { ref: item, checked: false, modellingRule:mr };
-        res_opt.push(newItem);
+        const newItem = new ComponentPair(item.toNode.displayName , false);
+        res_opt.push(item.toNode.displayName);
       }
-      res.push(res_man);
-      res.push(res_opt);
+      dynNode.mandatory = res_man;
+      dynNode.optionals = res_opt;
+      dynNode.o_check = new Array(res_opt.length).fill(false);
     }
   })
   return res;
 }
 
+
+function getComponents(nid:string):DynamicNode{
+  getInstanceDecl(nid);
+  return dynNode;
+}
+
+function getNodeVersion(nid:string, dynNode:DynamicNode){
+  let node =  (store.addressSpace?.findNode(nid)) as UABaseNode;
+  let refs = node.references;
+  let res = "";
+  refs.forEach((ref)=>{
+    if(ref.toNode.displayName == "NodeVersion"){
+      res= ref.toNode.nodeId.toString()
+      dynNode.nodeVersionId= res;
+      return res;
+      
+    }else if(ref.fromNode.displayName == "NodeVersion"){
+      res= ref.fromNode.nodeId.toString()
+      dynNode.nodeVersionId= res;
+      return res;
+    }
+  })
+  dynNode.nodeVersionId= res;
+  return res;
+}
+
+
+
 async function createDynamic() {
+  //dynNode.namespaceUri = nameSpaceName.value;
+  //dynNode.parentNodeId = selectedNode.value?.nodeId.toString();
+  dynNode.typeNodeId = typedef.value.toString();
+
+ // console.log(dynNode);
+  //console.log(c_man);
+  //console.log(c_opt.value);
+  console.log(dynNode.optionals)
+  console.log(dynNode.o_check)
+  //dynNode = new DynamicNode(opt);
+
+  /*
   const blob = await store.addressSpace?.exportProject();
   if ('dynamics' in sconfig){
     sconfig.dynamics.push("hello");
   }else{
     sconfig.dynamics = [];
   }
-   
+   */
+
+
+
 }
 
 
 
 
-const type_def = ref("")
+
+
+
 const checked = ref("")
-const type = ref("")
-const components = ref("")
+const c_man = ref([])
+const c_opt = ref([])
+const typedef = ref("")
+
+
+
+
+
 
 
 
@@ -121,15 +191,42 @@ const components = ref("")
 
 <template>
   <div class="card">
-    <div class="card-body" v-if="node != null">
+    <div class="card-body" v-if="selectedNode != null && dynNode != null">
       <h5 class="card-title">Dynamic Node</h5>
       <div class="card-text">
         <div class="input-group mb-3">
           <div class="input-group-prepend">
             <span class="input-group-text" id="inputGroup-sizing-default">Parent Node</span>
           </div>
-          <input readonly type="text" class="form-control" aria-label="Default"
-            aria-describedby="inputGroup-sizing-default" :value="node?.nodeId"> 
+          <input type="text" class="form-control" aria-label="Default"
+            aria-describedby="inputGroup-sizing-default" v-model="selectedNode.nodeId" disabled> 
+        </div>
+        <div class="input-group mb-3">
+          <div class="input-group-prepend">
+            <span class="input-group-text" id="inputGroup-sizing-default">Namespace</span>
+            </div>
+            <input type="text" class="form-control" aria-label="Default" aria-describedby="inputGroup-sizing-default" v-model="nameSpaceName" disabled>
+        </div>
+        <div class="input-group mb-3">
+          <div class="input-group-prepend">
+            <span class="input-group-text" id="inputGroup-sizing-default">Name Dynamic Definition</span>
+          </div>
+          <input type="text" class="form-control" aria-label="Default"
+            aria-describedby="inputGroup-sizing-default" v-model="dynNode.name" v-bind:key="'...'"> 
+        </div>
+        <div class="input-group mb-3">
+          <div class="input-group-prepend">
+            <span class="input-group-text" id="inputGroup-sizing-default">Update Interval in ms</span>
+          </div>
+          <input type="text" class="form-control" aria-label="Default"
+            aria-describedby="inputGroup-sizing-default" v-model="dynNode.checkInterval" v-bind:key="1000"> 
+        </div>
+        <div class="input-group mb-3">
+          <div class="input-group-prepend">
+            <span class="input-group-text" id="inputGroup-sizing-default">NodeVersion</span>
+          </div>
+          <input type="text" class="form-control" aria-label="Default"
+            aria-describedby="inputGroup-sizing-default" :value="getNodeVersion(selectedNode.nodeId.toString(), dynNode)" disabled> 
         </div>
         <div class="input-group mb-3">
           <div class="input-group-prepend">
@@ -141,6 +238,13 @@ const components = ref("")
             </option>
           </select>
         </div>
+        <div class="input-group mb-3">
+          <div class="input-group-prepend">
+            <span class="input-group-text" id="inputGroup-sizing-default">Ident</span>
+          </div>
+          <input type="text" class="form-control" aria-label="Default"
+            aria-describedby="inputGroup-sizing-default" v-model="dynNode.ident" v-bind:key="'...'"> 
+        </div>
         <div class="input-group mb-3">
           <div class="input-group-prepend">
             <input type="checkbox" id="checkbox" v-model="checked" />
@@ -151,25 +255,25 @@ const components = ref("")
           <div class="input-group-prepend">
             <span class="input-group-text" id="inputGroup-sizing-default">Type Definition</span>
           </div>
-          <select v-model="type" class="form-select" aria-label="Default select example">
+          <select v-model="typedef" class="form-select" aria-label="Default select example" >
             <option v-for="option1 in getObjTypes(checked)" :value="option1.nodeId.toString()" v-bind:key="option1.nodeId.toString()">
               {{ option1.displayName }}
             </option>
           </select>
         </div>
         <div class="input-group mb-3">
-          <ul class="no-bullets">Mandatory Components
-            <li v-for="todo in getInstanceDecl(type)[0]">
-              <input type="checkbox" id="checkbox-{{ todo.ref.toNode.displayName }}" v-model="todo.checked" :disabled="true">
-              <label for="checkbox-{{ todo.ref.toNode.displayName }}">{{ todo.ref.toNode.displayName }}</label>
+          <ul class="no-bullets"><b>Mandatory Components</b>
+            <li v-for="item of getComponents(typedef).mandatory">
+              <input type="checkbox" id="checkbox-digg-{{ item }}" :checked="true" :disabled="true">
+              <label for="checkbox-digg-{{ item}}">{{ item}}</label>
             </li>
           </ul>
         </div>
         <div>
-          <ul class="no-bullets">Optional Components
-            <li v-for="todo1 in (getInstanceDecl(type)[1])">
-              <input type="checkbox" id="checkbox-digg" v-model="todo1.checked">
-              <label for="checkbox-digg">{{ todo1.ref.toNode.displayName }}</label>
+          <ul class="no-bullets"><b>Optional Components</b>
+            <li v-for="(item1,index) of getComponents(typedef).optionals" :key="typedef">
+              <input type="checkbox" id="checkbox-digg-{{ item1 }}" v-model="dynNode.o_check![index]" :value="item1" :checked="dynNode.o_check![index]">
+              <label for="checkbox-digg-{{ item1 }}">{{ item1 }}</label>
             </li>
           </ul>
         </div>

+ 1 - 2
src/ua/AddressSpace.ts

@@ -3,10 +3,9 @@ import { UABaseNode } from "./UABaseNode";
 import { NamespaceTable } from "./NameSpaceTable";
 import YAML from 'yaml'
 import JSZip from "jszip";
-import { ReferenceTypeIds } from "./opcua_node_ids";
-import type { NodeId } from "./NodeId";
 import {sconfig} from "@/util/ServerConfig"
 
+
 export class AddressSpace{
 
     nodeMap:  Map<string, UABaseNode>;

+ 66 - 0
src/ua/DynamicNode.ts

@@ -0,0 +1,66 @@
+
+
+
+export class DynamicNode {
+    public ident: string;
+    public namespaceUri: string;
+    public parentNodeId : string;
+    public checkInterval :number;
+    public nodeClass : string;
+    public typeNodeId : string;
+    public name : string;
+    public startIndex:number;
+    public mandatory:  string[]=[];
+    public optionals?: string[]=[];
+    public o_check?: boolean[]=[];
+    public nodeVersionId?: string;
+
+
+    constructor(options: DynamicNodeOptions) {
+        this.ident = options.ident;
+        this.namespaceUri =  options.namespaceUri;
+        this.parentNodeId = options.parentNodeId;
+        this.checkInterval = options.checkInterval;
+        this.nodeClass =  options.nodeClass;
+        this.typeNodeId = options.typeNodeId;
+        this.name =  options.name;
+        this.startIndex = options.startIndex;  
+    }
+
+    toPlainObject(){
+        return { 
+            ident: this.ident,
+            namespaceUri: this.namespaceUri,
+            parentNodeId : this.parentNodeId,
+            checkInterval : this.checkInterval,
+            nodeClass : this.nodeClass,
+            typeNodeId : this.typeNodeId,
+            name : this.name,
+            startIndex: this.startIndex,
+            mandatory: this.mandatory,
+            optionals: this.optionals,
+            nodeVersionId: this.nodeVersionId
+        }
+    }
+}
+
+export class ComponentPair{
+    public name: string; 
+    public flag: boolean;
+
+    constructor(name = "MyName", flag = false){
+        this.name = name;
+        this.flag = flag;
+    }
+}
+
+export interface DynamicNodeOptions {
+    ident:string;
+    namespaceUri:string;
+    parentNodeId:string;
+    checkInterval:number;
+    nodeClass:string;
+    typeNodeId:string;
+    name:string;
+    startIndex:number;
+}

+ 20 - 2
src/ua/UABaseNode.ts

@@ -9,6 +9,8 @@ import { UAUserWriteMask } from "./UAUserWriteMask"; //TODO
 import { UAWriteMask } from "./UAWriteMask"; //TODO
 import { UAAccessRestriction } from "./UAAccessRestriction"; //TODO
 
+
+
 export class UABaseNode implements IToXML{
 
     public nodeId: NodeId;
@@ -27,12 +29,14 @@ export class UABaseNode implements IToXML{
     public rolePermissions: UARolePermission[]=[]; 
     public extensions: UAExtension[]=[];
     public references: UAReference[]=[];
+    public opts: string[]=[];
+    
     
     constructor(options: UABaseNodeOptions) {
         this.nodeId=options.nodeId;
         this.browseName=options.browseName;
         this.displayName=options.displayName;
-        this.references=options.references||[];
+        this.references=options.references||[];     
     }
 
     public static nullBaseNode=new UABaseNode({browseName: "", nodeId: NodeId.nullNodeId});
@@ -79,6 +83,8 @@ export class UABaseNode implements IToXML{
         }
         return null;
     }
+    
+
 
     getChildren(): UABaseNode[] {
         const children: UABaseNode[]=[];
@@ -132,7 +138,9 @@ export class UABaseNode implements IToXML{
         const ua=new UABaseNode({nodeId: nodeId,
                                 browseName: xmlObject['@_BrowseName'], 
                                 displayName: xmlObject['DisplayName']['#text'], 
-                                references: references});
+                                references: references}); 
+
+
         return ua;
     }
 
@@ -149,6 +157,16 @@ export class UABaseNode implements IToXML{
         tmpNode.namespace=nsIdx;
         return tmpNode;
     }
+
+    getModellingRule(){
+        let res:any = "";
+        this.references.forEach((ref)=>{
+            if(ref.referenceType == "HasModellingRule"){
+                res = ref.toNode.displayName;
+            }
+        })
+        return res;
+    }    
 }
 
 

+ 10 - 0
src/ua/UAMethod.ts

@@ -29,6 +29,16 @@ export class UAMethod extends UABaseNode {
         }
         return elem;
     }
+
+    getModellingRule(){
+        let res:any = "";
+        this.references.forEach((ref)=>{
+            if(ref.referenceType == "HasModellingRule"){
+                res = ref.toNode.displayName;
+            }
+        })
+        return res;
+    }
 }
 
 export interface UAMethodNodeOptions extends UABaseNodeOptions{

+ 7 - 9
src/ua/UAObject.ts

@@ -2,7 +2,13 @@ import { XMLElem } from "@/util/XmlElem";
 import { UABaseNode, type UABaseNodeOptions } from "./UABaseNode";
 import type { NamespaceTable } from "./NameSpaceTable";
 
+
+
+
+
 export class UAObject extends UABaseNode {
+
+
     constructor(options: UAObjectNodeOptions) {
                     super(options);
                     this.nodeClass="Object";
@@ -30,15 +36,7 @@ export class UAObject extends UABaseNode {
         return elem;
     }
 
-    getModellingRule(){
-        let res:any = "";
-        this.references.forEach((ref)=>{
-            if(ref.referenceType == "HasModellingRule"){
-                res = ref.toNode.displayName;
-            }
-        })
-        return res;
-    }
+
 }
 
 export interface UAObjectNodeOptions extends UABaseNodeOptions{

+ 17 - 0
src/ua/UAObjectType.ts

@@ -1,9 +1,15 @@
 import { XMLElem } from "@/util/XmlElem";
 import type { NamespaceTable } from "./NameSpaceTable";
 import { UABaseNode, type UABaseNodeOptions } from "./UABaseNode";
+import type { UAObject } from "./UAObject";
+import type { UAVariable } from "./UAVariable";
+
+
+
 
 export class UAObjectType extends UABaseNode{
     public isAbstract: boolean;
+  
 
     constructor(options: UAObjectTypeOptions) {
         super(options)
@@ -33,6 +39,17 @@ export class UAObjectType extends UABaseNode{
         }
         return elem;
     }
+
+    getModellingRule(){
+        let res:any = "";
+        this.references.forEach((ref)=>{
+            if(ref.referenceType == "HasModellingRule"){
+                res = ref.toNode.displayName;
+            }
+        })
+        return res;
+    }
+
 }
 
 export interface UAObjectTypeOptions extends UABaseNodeOptions{

+ 4 - 9
src/ua/UAVariable.ts

@@ -2,7 +2,11 @@ import { XMLElem } from "@/util/XmlElem";
 import { UABaseNode, type UABaseNodeOptions } from "./UABaseNode";
 import type { NamespaceTable } from "./NameSpaceTable";
 
+
+
 export class UAVariable extends UABaseNode {
+
+
     constructor(options: UAVariableNodeOptions) {
                     super(options);
                     this.nodeClass="Variable";
@@ -30,15 +34,6 @@ export class UAVariable extends UABaseNode {
         return elem;
     }
 
-    getModellingRule(){
-        let res:any = "";
-        this.references.forEach((ref)=>{
-            if(ref.referenceType == "HasModellingRule"){
-                res = ref.toNode.displayName;
-            }
-        })
-        return res;
-    }
 }
 
 export interface UAVariableNodeOptions extends UABaseNodeOptions{

+ 8 - 0
src/ua/UAVariableType.ts

@@ -1,13 +1,19 @@
 import { XMLElem } from "@/util/XmlElem";
 import type { NamespaceTable } from "./NameSpaceTable";
 import { UABaseNode, type UABaseNodeOptions } from "./UABaseNode";
+import {DynamicNode, ComponentPair} from "./DynamicNode";
+import { aggregates } from '@/util/aggregates'
+import { UAVariable } from "./UAVariable";
 
 export class UAVariableType extends UABaseNode{
     public isAbstract: boolean;
+    
 
     constructor(options: UAVariableTypeOptions) {
         super(options)
         this.isAbstract=options.isAbstract||false;
+       
+ 
     }
 
     static  fromXML(xmlObjType: any): UAVariableType{
@@ -33,6 +39,8 @@ export class UAVariableType extends UABaseNode{
         }
         return elem;
     }
+
+    
 }
 
 export interface UAVariableTypeOptions extends UABaseNodeOptions{

+ 1 - 0
src/util/aggregates.ts

@@ -0,0 +1 @@
+export const aggregates = ["HasComponent","HasProperty","HasAddIn","HasOrderedComponent","HasPhysicalComponent"];

+ 77 - 0
src/util/sconfig.ts

@@ -0,0 +1,77 @@
+import { DynamicNode} from '@/ua/DynamicNode';
+import JSZip from "jszip";
+
+
+export class ServerConfig {
+    public buildInfo:any = {
+		manufacturerName:"Emco GmbH",
+		productName: "EMCO OPC UA Server",
+		softwareVersion: "1.0.0"
+		};
+    public serverInfo:any = {
+				applicationUri: "EMCO_OPCUA",
+				productUri: "EMCO_OPCUA",
+				applicationName: { "text": "EMCO_OPCUA", "locale": "en" }
+			};
+
+    public dynamics:DynamicNode[]=[];
+    public nodesets:string[]=[];
+
+    constructor(options: ServerConfigOptions) {
+        this.buildInfo = options.buildInfo;
+        this.serverInfo =  options.serverInfo; 
+    }
+
+
+    addDynamic(dyn:DynamicNode){
+        this.dynamics.forEach((dyn, idx)=>{
+            if(dyn.ident == dyn.ident){
+                return false;
+            }
+        })
+        this.dynamics.push(dyn);
+        return true;
+    }
+
+    removeDynamic(ident:string){
+        this.dynamics.forEach((dyn, idx)=>{
+            if(dyn.ident == ident){
+                this.dynamics.splice(idx,1);
+                return true;
+            }
+        })
+        return false;
+    }
+
+    toPlainObject(){
+        let obj = {
+            buildinfo: this.buildInfo,
+            serverInfo: this.serverInfo,
+            dynamics: []
+        }    
+        this.dynamics.forEach((dyn)=>{
+            //obj.dynamics.push(dyn.toPlainObject());
+        })
+    }
+
+
+    public exportConfig() {
+        const zip = new JSZip();
+        zip.file("server_config.json", JSON.stringify(this.toPlainObject()));
+        return zip.generateAsync({type:'blob'});
+    }
+}
+
+
+export interface ServerConfigOptions {
+    buildInfo:{
+		manufacturerName:"Emco GmbH",
+		productName: "EMCO OPC UA Server",
+		softwareVersion: "1.0.0"
+		},
+	serverInfo: {
+				applicationUri: "EMCO_OPCUA",
+				productUri: "EMCO_OPCUA",
+				applicationName: { "text": "EMCO_OPCUA", "locale": "en" }
+			}
+}

+ 5 - 0
src/util/store.ts

@@ -3,12 +3,17 @@ import { UABaseNode } from '@/ua/UABaseNode'
 import type { UANodeSet } from '@/ua/UANodeSet'
 import { defineStore } from 'pinia'
 import { ref } from 'vue'
+import type { DynamicNode } from '@/ua/DynamicNode'
+import type { ServerConfig } from './sconfig'
+
 
 export const useStore = defineStore('user', {
   state: () => ({
       addressSpace: null as AddressSpace | null,
       rootNode: ref<UABaseNode | null>(null),
       selectedNode: ref<UABaseNode | null>(null),
+      dynNode: ref<DynamicNode | null>(null),
+      sConfig: ref<ServerConfig | null>(null)
   }),
   actions: {
     setAddressSpace(as: AddressSpace) {