opcua 0.12 → 0.13

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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
+ }