opcua 0.12 → 0.13

Sign up to get free protection for your applications and to get access to all the features.
Files changed (41) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +71 -4
  3. data/cert/cert.h +52 -52
  4. data/cert/cert_key.h +101 -101
  5. data/example/array/Makefile +3 -0
  6. data/example/array/clienttest.c +168 -0
  7. data/example/array/open62541.h +27464 -0
  8. data/example/array/servertest.c +142 -0
  9. data/example/bug5.rb +22 -0
  10. data/example/client_method.rb +7 -3
  11. data/example/server.rb +3 -3
  12. data/ext/opcua/client/client.c +38 -13
  13. data/ext/opcua/client/client.h +2 -1
  14. data/ext/opcua/client/finders.c +1 -0
  15. data/ext/opcua/client/finders.h +1 -0
  16. data/ext/opcua/client/log_none.c +1 -0
  17. data/ext/opcua/client/log_none.h +1 -0
  18. data/ext/opcua/client/strnautocat.c +1 -0
  19. data/ext/opcua/client/strnautocat.h +1 -0
  20. data/ext/opcua/client/values.c +1 -0
  21. data/ext/opcua/client/values.h +1 -0
  22. data/ext/opcua/helpers/finders.c +142 -0
  23. data/ext/opcua/helpers/finders.h +13 -0
  24. data/ext/opcua/{log_none.h → helpers/log_none.c} +2 -0
  25. data/ext/opcua/helpers/log_none.h +12 -0
  26. data/ext/opcua/{strnautocat.h → helpers/strnautocat.c} +2 -0
  27. data/ext/opcua/helpers/strnautocat.h +8 -0
  28. data/ext/opcua/{values.h → helpers/values.c} +64 -25
  29. data/ext/opcua/helpers/values.h +12 -0
  30. data/ext/opcua/server/finders.c +1 -0
  31. data/ext/opcua/server/finders.h +1 -0
  32. data/ext/opcua/server/log_none.c +1 -0
  33. data/ext/opcua/server/log_none.h +1 -0
  34. data/ext/opcua/server/server.c +70 -57
  35. data/ext/opcua/server/server.h +4 -2
  36. data/ext/opcua/server/strnautocat.c +1 -0
  37. data/ext/opcua/server/strnautocat.h +1 -0
  38. data/ext/opcua/server/values.c +1 -0
  39. data/ext/opcua/server/values.h +1 -0
  40. data/opcua.gemspec +1 -1
  41. metadata +32 -6
@@ -0,0 +1,142 @@
1
+ /* This work is licensed under a Creative Commons CCZero 1.0 Universal License.
2
+ * See http://creativecommons.org/publicdomain/zero/1.0/ for more information. */
3
+
4
+ /**
5
+ * Adding Variables to a Server
6
+ * ----------------------------
7
+ *
8
+ * This tutorial shows how to work with data types and how to add variable nodes
9
+ * to a server. First, we add a new variable to the server. Take a look at the
10
+ * definition of the ``UA_VariableAttributes`` structure to see the list of all
11
+ * attributes defined for VariableNodes.
12
+ *
13
+ * Note that the default settings have the AccessLevel of the variable value as
14
+ * read only. See below for making the variable writable.
15
+ */
16
+
17
+ #include <open62541.h>
18
+
19
+ #include <signal.h>
20
+ #include <stdlib.h>
21
+
22
+ static void
23
+ addVariable(UA_Server *server) {
24
+ /* Define the attribute of the myInteger variable node */
25
+ UA_VariableAttributes attr = UA_VariableAttributes_default;
26
+ UA_Int32 myInteger = 42;
27
+ UA_Variant_setScalar(&attr.value, &myInteger, &UA_TYPES[UA_TYPES_INT32]);
28
+ attr.description = UA_LOCALIZEDTEXT("en-US","the answer");
29
+ attr.displayName = UA_LOCALIZEDTEXT("en-US","the answer");
30
+ attr.dataType = UA_TYPES[UA_TYPES_INT32].typeId;
31
+ attr.accessLevel = UA_ACCESSLEVELMASK_READ | UA_ACCESSLEVELMASK_WRITE;
32
+
33
+ /* Add the variable node to the information model */
34
+ UA_NodeId myIntegerNodeId = UA_NODEID_STRING(1, "the.answer");
35
+ UA_QualifiedName myIntegerName = UA_QUALIFIEDNAME(1, "the answer");
36
+ UA_NodeId parentNodeId = UA_NODEID_NUMERIC(0, UA_NS0ID_OBJECTSFOLDER);
37
+ UA_NodeId parentReferenceNodeId = UA_NODEID_NUMERIC(0, UA_NS0ID_ORGANIZES);
38
+ UA_Server_addVariableNode(server, myIntegerNodeId, parentNodeId,
39
+ parentReferenceNodeId, myIntegerName,
40
+ UA_NODEID_NUMERIC(0, UA_NS0ID_BASEDATAVARIABLETYPE), attr, NULL, NULL);
41
+ }
42
+
43
+ static UA_NodeId pointTypeId;
44
+ static void addVariableType2DPoint(UA_Server *server) {
45
+ UA_VariableAttributes vtAttr = UA_VariableAttributes_default;
46
+ vtAttr.dataType = UA_TYPES[UA_TYPES_DOUBLE].typeId;
47
+ vtAttr.valueRank = UA_VALUERANK_ONE_DIMENSION;
48
+ UA_UInt32 arrayDims[1] = {5};
49
+ vtAttr.arrayDimensions = arrayDims;
50
+ vtAttr.arrayDimensionsSize = 1;
51
+ vtAttr.displayName = UA_LOCALIZEDTEXT("en-US", "2DPoint Type");
52
+ vtAttr.accessLevel = UA_ACCESSLEVELMASK_READ | UA_ACCESSLEVELMASK_WRITE;
53
+ UA_NodeId myArrayNodeId = UA_NODEID_STRING(1, "MyArray");
54
+ /* a matching default value is required */
55
+ UA_Double zero[2] = {0.0, 0.0};
56
+ UA_Variant_setArray(&vtAttr.value, zero, 5, &UA_TYPES[UA_TYPES_DOUBLE]);
57
+
58
+ UA_Server_addVariableNode(server, myArrayNodeId,
59
+ UA_NODEID_NUMERIC(0, UA_NS0ID_OBJECTSFOLDER),
60
+ UA_NODEID_NUMERIC(0, UA_NS0ID_HASSUBTYPE),
61
+ UA_QUALIFIEDNAME(1, "2DPoint Type"), UA_NODEID_NULL,
62
+ vtAttr, NULL, &pointTypeId);
63
+ }
64
+ /**
65
+ * Now we change the value with the write service. This uses the same service
66
+ * implementation that can also be reached over the network by an OPC UA client.
67
+ */
68
+
69
+ static void writeVariable(UA_Server *server) {
70
+ UA_NodeId myIntegerNodeId = UA_NODEID_STRING(1, "the.answer");
71
+
72
+ /* Write a different integer value */
73
+ UA_Int32 myInteger = 43;
74
+ UA_Variant myVar;
75
+ UA_Variant_init(&myVar);
76
+ UA_Variant_setScalar(&myVar, &myInteger, &UA_TYPES[UA_TYPES_INT32]);
77
+ UA_Server_writeValue(server, myIntegerNodeId, myVar);
78
+
79
+ /* Set the status code of the value to an error code. The function
80
+ * UA_Server_write provides access to the raw service. The above
81
+ * UA_Server_writeValue is syntactic sugar for writing a specific node
82
+ * attribute with the write service. */
83
+ UA_WriteValue wv;
84
+ UA_WriteValue_init(&wv);
85
+ wv.nodeId = myIntegerNodeId;
86
+ wv.attributeId = UA_ATTRIBUTEID_VALUE;
87
+ wv.value.status = UA_STATUSCODE_BADNOTCONNECTED;
88
+ wv.value.hasStatus = true;
89
+ UA_Server_write(server, &wv);
90
+
91
+ /* Reset the variable to a good statuscode with a value */
92
+ wv.value.hasStatus = false;
93
+ wv.value.value = myVar;
94
+ wv.value.hasValue = true;
95
+ UA_Server_write(server, &wv);
96
+ }
97
+
98
+ /**
99
+ * Note how we initially set the DataType attribute of the variable node to the
100
+ * NodeId of the Int32 data type. This forbids writing values that are not an
101
+ * Int32. The following code shows how this consistency check is performed for
102
+ * every write.
103
+ */
104
+
105
+ static void
106
+ writeWrongVariable(UA_Server *server) {
107
+ UA_NodeId myIntegerNodeId = UA_NODEID_STRING(1, "the.answer");
108
+
109
+ /* Write a string */
110
+ UA_String myString = UA_STRING("test");
111
+ UA_Variant myVar;
112
+ UA_Variant_init(&myVar);
113
+ UA_Variant_setScalar(&myVar, &myString, &UA_TYPES[UA_TYPES_STRING]);
114
+ UA_StatusCode retval = UA_Server_writeValue(server, myIntegerNodeId, myVar);
115
+ printf("Writing a string returned statuscode %s\n", UA_StatusCode_name(retval));
116
+ }
117
+
118
+ /** It follows the main server code, making use of the above definitions. */
119
+
120
+ UA_Boolean running = true;
121
+ static void stopHandler(int sign) {
122
+ UA_LOG_INFO(UA_Log_Stdout, UA_LOGCATEGORY_SERVER, "received ctrl-c");
123
+ running = false;
124
+ }
125
+
126
+ int main(void) {
127
+ signal(SIGINT, stopHandler);
128
+ signal(SIGTERM, stopHandler);
129
+ UA_ServerConfig *config = UA_ServerConfig_new_default();
130
+ UA_Server *server = UA_Server_new(config);
131
+
132
+
133
+ addVariable(server);
134
+ writeVariable(server);
135
+ writeWrongVariable(server);
136
+ addVariableType2DPoint(server);
137
+
138
+ UA_StatusCode retval = UA_Server_run(server, &running);
139
+
140
+ UA_Server_delete(server);
141
+ return retval == UA_STATUSCODE_GOOD ? EXIT_SUCCESS : EXIT_FAILURE;
142
+ }
@@ -0,0 +1,22 @@
1
+ #!/usr/bin/ruby
2
+ require_relative '../lib/opcua/server'
3
+
4
+ Daemonite.new do
5
+ server = OPCUA::Server.new
6
+ server.add_namespace "https://centurio.work/kelch"
7
+
8
+ mt = server.types.add_object_type(:MeasurementType).tap{ |t|
9
+ t.add_variable :SollWertX
10
+ t.add_variable :SollWertY
11
+ t.add_variable :SollWertZ
12
+ }
13
+ tt = server.types.add_object_type(:ToolType).tap{ |t|
14
+ t.add_object :M, mt, OPCUA::MANDATORY
15
+ }
16
+
17
+ tools = server.objects.instantiate(:KalimatC34, tt)
18
+
19
+ run do
20
+ sleep server.run
21
+ end
22
+ end.loop!
@@ -9,6 +9,10 @@ client = OPCUA::Client.new("opc.tcp://localhost:4840")
9
9
  client.debug = false
10
10
  client.default_ns = 2
11
11
 
12
- node = client.get '/KalimatC34/Tools/Tool3/testMethod'
13
- node.call 'abcde', Time.now
14
- client.disconnect
12
+ # node = client.get '/KalimatC34/Tools/Tool3/testMethod'
13
+ # p node.call 'abcde', Time.now
14
+ # client.disconnect
15
+
16
+ node = client.get 0, 11492
17
+ p node
18
+ p node.call 2
@@ -48,12 +48,12 @@ Daemonite.new do
48
48
  opts[:tn].description = 'test test'
49
49
  opts[:tn].value = [0,1]
50
50
  p opts[:tn].description
51
+ p opts[:tn].to_s
52
+ p opts[:tn].name
51
53
 
52
54
  measurments_t1 = t1.find(:Measurements)
53
55
  measurments_t1.manifest(:M1,mt)
54
- measurments_t1.manifest(:M2,mt)
55
-
56
- p opts['server'].namespaces
56
+ m2 = measurments_t1.manifest(:M2,mt)
57
57
  rescue => e
58
58
  puts e.message
59
59
  end
@@ -9,7 +9,7 @@ VALUE cVarNode = Qnil;
9
9
  VALUE cMethodNode = Qnil;
10
10
  VALUE cNode = Qnil;
11
11
 
12
- #include "../values.h"
12
+ #include "values.h"
13
13
 
14
14
  #include <signal.h>
15
15
 
@@ -64,7 +64,14 @@ static VALUE node_value_set(VALUE self, VALUE value) { //{{{
64
64
  if (!ns->master->started) rb_raise(rb_eRuntimeError, "Client disconnected.");
65
65
 
66
66
  UA_Variant variant;
67
- if (value_to_variant(value,&variant)) {
67
+ UA_NodeId pdt;
68
+ UA_Client_readDataTypeAttribute(ns->master->master, ns->id, &pdt);
69
+ UA_UInt32 proposal = -1;
70
+ if (pdt.namespaceIndex == 0) {
71
+ proposal = pdt.identifier.numeric - 1; // what a dirty hack TODO, translate node to value
72
+ }
73
+
74
+ if (value_to_variant(value,&variant,proposal)) {
68
75
  // printf("-----------------------------------------%ld\n",variant.arrayDimensionsSize);
69
76
  if (variant.arrayDimensionsSize > 0) {
70
77
  UA_Int32 ads = (UA_Int32) variant.arrayDimensionsSize;
@@ -96,7 +103,8 @@ static VALUE node_on_change(VALUE self) { //{{{
96
103
 
97
104
  return self;
98
105
  } //}}}
99
- static void node_on_value_change_handler(UA_Client *client, UA_UInt32 subId, void *subContext, UA_UInt32 monId, void *monContext, UA_DataValue *value) {
106
+
107
+ static void node_on_value_change_handler(UA_Client *client, UA_UInt32 subId, void *subContext, UA_UInt32 monId, void *monContext, UA_DataValue *value) { // {{{
100
108
  VALUE ins = (VALUE)monContext;
101
109
  VALUE blk = RARRAY_AREF(ins,1);
102
110
 
@@ -120,8 +128,8 @@ static void node_on_value_change_handler(UA_Client *client, UA_UInt32 subId, voi
120
128
  rb_ary_store(args,2,rb_ary_entry(ret,1));
121
129
  rb_proc_call(blk,args);
122
130
  }
123
- }
124
- static VALUE node_on_value_change(VALUE self) {
131
+ } // }}}
132
+ static VALUE node_on_value_change(VALUE self) { // {{{
125
133
  node_struct *ns;
126
134
  Data_Get_Struct(self, node_struct, ns);
127
135
  if (!ns->master->started) rb_raise(rb_eRuntimeError, "Client disconnected.");
@@ -171,7 +179,7 @@ static VALUE node_on_value_change(VALUE self) {
171
179
  UA_CreateSubscriptionRequest_clear(&sreq);
172
180
 
173
181
  return self;
174
- }
182
+ } // }}}
175
183
 
176
184
  /* -- */
177
185
  static void client_free(client_struct *pss) { //{{{
@@ -486,24 +494,40 @@ static VALUE node_to_s(VALUE self) { //{{{
486
494
 
487
495
  static VALUE node_call(int argc, VALUE* argv, VALUE self) { //{{{
488
496
  node_struct *ns;
497
+ UA_StatusCode retval;
489
498
 
490
499
  VALUE splat;
491
500
  rb_scan_args(argc, argv, "*", &splat);
492
501
 
493
502
  Data_Get_Struct(self, node_struct, ns);
494
503
 
504
+ UA_NodeId parent;
505
+ client_node_get_reference_by_type(ns->master->master, ns->id, UA_NODEID_NUMERIC(0,UA_NS0ID_HASCOMPONENT), &parent, true);
506
+
507
+ UA_NodeId ia;
508
+ client_node_get_reference_by_name(ns->master->master, ns->id, UA_QUALIFIEDNAME(0,"InputArguments"), &ia, false);
509
+ UA_Variant iaval;
510
+ UA_Variant_init(&iaval);
511
+ UA_Client_readValueAttribute(ns->master->master, ia, &iaval);
512
+
513
+ UA_UInt32 proposal[RARRAY_LEN(splat)];
514
+ for (int i=0; i < iaval.arrayLength; i++) {
515
+ UA_ExtensionObject* eoarg= (UA_ExtensionObject *)(iaval.data);
516
+ UA_Argument* arg = (UA_Argument *)(eoarg[i].content.decoded.data);
517
+
518
+ if (arg->dataType.namespaceIndex == 0) {
519
+ proposal[i] = arg->dataType.identifier.numeric - 1; // what a dirty hack TODO, translate node to value
520
+ }
521
+ }
522
+
495
523
  UA_Variant inputArguments[RARRAY_LEN(splat)];
496
524
  for (long i=0; i<RARRAY_LEN(splat); i++) {
497
- value_to_variant(RARRAY_AREF(splat, i),&inputArguments[i]);
525
+ value_to_variant(RARRAY_AREF(splat, i),&inputArguments[i],proposal[i]);
498
526
  }
499
527
 
500
- VALUE path = rb_str_new((const char *)ns->id.identifier.string.data,ns->id.identifier.string.length);
501
- VALUE pat = rb_str_new2("\\/[a-z0-9A-Z]+\\/?$");
502
- VALUE obj = rb_funcall(path,rb_intern("sub"),2,rb_reg_new_str(pat, 0),rb_str_new2(""));
503
-
504
- UA_StatusCode retval = UA_Client_call(
528
+ retval = UA_Client_call(
505
529
  ns->master->master,
506
- UA_NODEID_STRING(ns->id.namespaceIndex,StringValuePtr(obj)),
530
+ parent,
507
531
  ns->id,
508
532
  RARRAY_LEN(splat),
509
533
  (UA_Variant *)&inputArguments,
@@ -511,6 +535,7 @@ static VALUE node_call(int argc, VALUE* argv, VALUE self) { //{{{
511
535
  NULL
512
536
  );
513
537
 
538
+
514
539
  if(retval == UA_STATUSCODE_GOOD) {
515
540
  return Qtrue;
516
541
  } else {
@@ -1,7 +1,8 @@
1
1
  #include <ruby.h>
2
2
  #include <stdio.h>
3
3
  #include <open62541.h>
4
- #include "../log_none.h"
4
+ #include "log_none.h"
5
+ #include "finders.h"
5
6
 
6
7
  typedef struct client_struct {
7
8
  UA_ClientConfig *config;
@@ -0,0 +1 @@
1
+ ../helpers/finders.c
@@ -0,0 +1 @@
1
+ ../helpers/finders.h
@@ -0,0 +1 @@
1
+ ../helpers/log_none.c
@@ -0,0 +1 @@
1
+ ../helpers/log_none.h
@@ -0,0 +1 @@
1
+ ../helpers/strnautocat.c
@@ -0,0 +1 @@
1
+ ../helpers/strnautocat.h
@@ -0,0 +1 @@
1
+ ../helpers/values.c
@@ -0,0 +1 @@
1
+ ../helpers/values.h
@@ -0,0 +1,142 @@
1
+ #include "finders.h"
2
+
3
+ UA_BrowsePathResult node_browse_path(UA_Server *server, UA_NodeId relative, UA_NodeId ref, UA_QualifiedName mqn, bool inverse) {
4
+ UA_RelativePathElement rpe;
5
+ UA_RelativePathElement_init(&rpe);
6
+ rpe.referenceTypeId = ref;
7
+ rpe.isInverse = inverse;
8
+ rpe.includeSubtypes = false;
9
+ rpe.targetName = mqn;
10
+
11
+ UA_BrowsePath bp;
12
+ UA_BrowsePath_init(&bp);
13
+ bp.startingNode = relative;
14
+ bp.relativePath.elementsSize = 1;
15
+ bp.relativePath.elements = &rpe;
16
+
17
+ return UA_Server_translateBrowsePathToNodeIds(server, &bp);
18
+ }
19
+
20
+ bool server_node_get_reference(UA_Server *server, UA_NodeId parent, UA_NodeId *result, bool inverse) {
21
+ UA_BrowseDescription bDes;
22
+ UA_BrowseDescription_init(&bDes);
23
+ bDes.nodeId = parent;
24
+ bDes.resultMask = UA_BROWSERESULTMASK_ALL;
25
+ bDes.browseDirection = inverse ? 1 : 0;
26
+ UA_BrowseResult bRes = UA_Server_browse(server, 2, &bDes);
27
+
28
+ if (bRes.referencesSize > 0) {
29
+ UA_ReferenceDescription *ref = &(bRes.references[0]);
30
+
31
+ UA_NodeId_copy(&ref->nodeId.nodeId,result);
32
+
33
+ // UA_QualifiedName qn; UA_QualifiedName_init(&qn);
34
+ // UA_Server_readBrowseName(server, ref->nodeId.nodeId, &qn);
35
+ // printf("NS: %d ---> NodeId %u; %-16.*s\n",
36
+ // ref->nodeId.nodeId.namespaceIndex,
37
+ // ref->nodeId.nodeId.identifier.numeric,
38
+ // (int)qn.name.length,
39
+ // qn.name.data
40
+ // );
41
+
42
+ UA_BrowseResult_deleteMembers(&bRes);
43
+ UA_BrowseResult_clear(&bRes);
44
+ return true;
45
+ }
46
+ return false;
47
+ }
48
+
49
+ bool client_node_get_reference_by_name(UA_Client *client, UA_NodeId parent, UA_QualifiedName name, UA_NodeId *result, bool inverse) {
50
+ UA_BrowseRequest bReq;
51
+ UA_BrowseRequest_init(&bReq);
52
+ bReq.requestedMaxReferencesPerNode = 0;
53
+ bReq.nodesToBrowse = UA_BrowseDescription_new();
54
+ bReq.nodesToBrowseSize = 1;
55
+ bReq.nodesToBrowse[0].nodeId = parent;
56
+ bReq.nodesToBrowse[0].browseDirection = inverse ? 1 : 0;
57
+ bReq.nodesToBrowse[0].resultMask = UA_BROWSERESULTMASK_ALL;
58
+ UA_BrowseResponse bResp = UA_Client_Service_browse(client, bReq);
59
+
60
+ bool success = false;
61
+ for (int i=0; i < bResp.resultsSize && !success; i++) {
62
+ for (int j=0; j < bResp.results[i].referencesSize && !success; j++) {
63
+ UA_ReferenceDescription *ref = &(bResp.results[i].references[j]);
64
+
65
+ UA_QualifiedName qn; UA_QualifiedName_init(&qn);
66
+ UA_Client_readBrowseNameAttribute(client, ref->nodeId.nodeId, &qn);
67
+
68
+ if (UA_QualifiedName_equal(&qn,&name)) {
69
+ UA_NodeId_copy(&ref->nodeId.nodeId,result);
70
+ success = true;
71
+ }
72
+ }
73
+ }
74
+
75
+ UA_BrowseResponse_deleteMembers(&bResp);
76
+ UA_BrowseResponse_clear(&bResp);
77
+ return success;
78
+ }
79
+ bool client_node_get_reference_by_type(UA_Client *client, UA_NodeId parent, UA_NodeId type, UA_NodeId *result, bool inverse) {
80
+ UA_BrowseRequest bReq;
81
+ UA_BrowseRequest_init(&bReq);
82
+ bReq.requestedMaxReferencesPerNode = 0;
83
+ bReq.nodesToBrowse = UA_BrowseDescription_new();
84
+ bReq.nodesToBrowseSize = 1;
85
+ bReq.nodesToBrowse[0].nodeId = parent;
86
+ bReq.nodesToBrowse[0].browseDirection = inverse ? 1 : 0;
87
+ bReq.nodesToBrowse[0].resultMask = UA_BROWSERESULTMASK_ALL;
88
+ UA_BrowseResponse bResp = UA_Client_Service_browse(client, bReq);
89
+
90
+ bool success = false;
91
+ for (int i=0; i < bResp.resultsSize && !success; i++) {
92
+ for (int j=0; j < bResp.results[i].referencesSize && !success; j++) {
93
+ UA_ReferenceDescription *ref = &(bResp.results[i].references[j]);
94
+ if (UA_NodeId_equal(&ref->referenceTypeId,&type)) {
95
+ UA_NodeId_copy(&ref->nodeId.nodeId,result);
96
+ success = true;
97
+ }
98
+ }
99
+ }
100
+
101
+ UA_BrowseResponse_deleteMembers(&bResp);
102
+ UA_BrowseResponse_clear(&bResp);
103
+ return success;
104
+ }
105
+
106
+ bool client_node_list_references(UA_Client *client, UA_NodeId parent, bool inverse) {
107
+ UA_BrowseRequest bReq;
108
+ UA_BrowseRequest_init(&bReq);
109
+ bReq.requestedMaxReferencesPerNode = 0;
110
+ bReq.nodesToBrowse = UA_BrowseDescription_new();
111
+ bReq.nodesToBrowseSize = 1;
112
+ bReq.nodesToBrowse[0].nodeId = parent;
113
+ bReq.nodesToBrowse[0].browseDirection = inverse ? 1 : 0;
114
+ bReq.nodesToBrowse[0].resultMask = UA_BROWSERESULTMASK_ALL;
115
+ UA_BrowseResponse bResp = UA_Client_Service_browse(client, bReq);
116
+
117
+ for (int i=0; i < bResp.resultsSize; i++) {
118
+ for (int j=0; j < bResp.results[i].referencesSize; j++) {
119
+ UA_ReferenceDescription *ref = &(bResp.results[i].references[j]);
120
+
121
+ UA_QualifiedName qn; UA_QualifiedName_init(&qn);
122
+ UA_Client_readBrowseNameAttribute(client, ref->nodeId.nodeId, &qn);
123
+ UA_QualifiedName tqn; UA_QualifiedName_init(&tqn);
124
+ UA_Client_readBrowseNameAttribute(client, ref->referenceTypeId, &tqn);
125
+ printf("ns=%d;i=%u - %-16.*s(ns=%d;i=%u) -> %d:%-16.*s\n",
126
+ ref->nodeId.nodeId.namespaceIndex,
127
+ ref->nodeId.nodeId.identifier.numeric,
128
+ (int)tqn.name.length,
129
+ tqn.name.data,
130
+ ref->referenceTypeId.namespaceIndex,
131
+ ref->referenceTypeId.identifier.numeric,
132
+ qn.namespaceIndex,
133
+ (int)qn.name.length,
134
+ qn.name.data
135
+ );
136
+
137
+ }
138
+ }
139
+ UA_BrowseResponse_deleteMembers(&bResp);
140
+ UA_BrowseResponse_clear(&bResp);
141
+ return false;
142
+ }