opcua_client 0.0.2 → 0.0.5

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 6d5c48f943609a5cff626ade2e52495427fc8c3b3af175c2ac9b7231146209ec
4
- data.tar.gz: f0ec2ea830a866681352b2048950382a06d199ea1f53b47a31857b1f066a3c2f
3
+ metadata.gz: ae12c435f33aa7b57384562385a22dafb562fe3620573697b5d3bc57e1e58798
4
+ data.tar.gz: 29ac2ff75077f30b9c614103033c43e53186db66a428352f2bdfc369203715ae
5
5
  SHA512:
6
- metadata.gz: 54bacd20dd2f8f73c10dacd29f1b9ab3776ee1cec0933aa1c56423a9cc077161c5d1c0fb676111b679f80eae434640cbeb5cce2d12d254fd5877927d18142acc
7
- data.tar.gz: 270a0757c4d3469e7e3e6cbf57e874d4ec13911d221bc3befb4a257abf237b0b7307bed5f14960b5381d3ddcf74e4c77d014e778d09efb389c75a957056bb9f3
6
+ metadata.gz: 96f3ec5185c7072203f003ec35a3db4a984277333fe413f600248e7ffadcf92c815fbb6fc593f0b2b3d5fffd7983b71e0685188a0909ec826f63ad56ddb6a09a
7
+ data.tar.gz: f11e9e5101902d4a4715741c455762ba8bcd4eb28715e80eb8644319267d66d7e52d6c850d29234fd72a6938b18eaface6f20788bd613b3052fc96489c2ce2dd
data/README.md CHANGED
@@ -2,6 +2,8 @@
2
2
 
3
3
  Incomplete OPC-UA client library for Ruby. Wraps open62541: <https://open62541.org>.
4
4
 
5
+ ![ci-badge](https://github.com/mak-it/opcua-client-ruby/actions/workflows/build.yml/badge.svg)
6
+
5
7
  ## Installation
6
8
 
7
9
  Add it to your Gemfile:
@@ -53,15 +55,21 @@ end
53
55
  All methods raise OPCUAClient::Error if unsuccessful.
54
56
 
55
57
  * ```client.read_int16(Fixnum ns, String name) => Fixnum```
58
+ * ```client.read_uint16(Fixnum ns, String name) => Fixnum```
56
59
  * ```client.read_int32(Fixnum ns, String name) => Fixnum```
60
+ * ```client.read_uint32(Fixnum ns, String name) => Fixnum```
57
61
  * ```client.read_float(Fixnum ns, String name) => Float```
58
62
  * ```client.read_boolean(Fixnum ns, String name) => true/false```
59
63
  * ```client.write_int16(Fixnum ns, String name, Fixnum value)```
64
+ * ```client.write_uint16(Fixnum ns, String name, Fixnum value)```
60
65
  * ```client.write_int32(Fixnum ns, String name, Fixnum value)```
66
+ * ```client.write_uint32(Fixnum ns, String name, Fixnum value)```
61
67
  * ```client.write_float(Fixnum ns, String name, Float value)```
62
68
  * ```client.write_boolean(Fixnum ns, String name, bool value)```
63
69
  * ```client.multi_write_int16(Fixnum ns, Array[String] names, Array[Fixnum] values)```
70
+ * ```client.multi_write_uint16(Fixnum ns, Array[String] names, Array[Fixnum] values)```
64
71
  * ```client.multi_write_int32(Fixnum ns, Array[String] names, Array[Fixnum] values)```
72
+ * ```client.multi_write_uint32(Fixnum ns, Array[String] names, Array[Fixnum] values)```
65
73
  * ```client.multi_write_float(Fixnum ns, Array[String] names, Array[Float] values)```
66
74
  * ```client.multi_write_boolean(Fixnum ns, Array[String] names, Array[bool] values)```
67
75
 
@@ -118,7 +126,15 @@ bundle
118
126
  ### Try out changes
119
127
 
120
128
  ```console
121
- $ rake compile
129
+ $ bin/rake compile
122
130
  $ bin/console
123
131
  pry> client = OPCUAClient::Client.new
132
+ pry> client.connect("opc.tcp://127.0.0.1:4840")
133
+ ```
134
+
135
+ ### Test it
136
+
137
+ ```console
138
+ $ bin/rake compile
139
+ $ bin/rake spec
124
140
  ```
@@ -34,29 +34,29 @@ handler_dataChanged(UA_Client *client, UA_UInt32 subId, void *subContext,
34
34
  struct OpcuaClientContext *ctx = UA_Client_getContext(client);
35
35
  VALUE self = ctx->rubyClientInstance;
36
36
  VALUE callback = rb_ivar_get(self, rb_intern("@callback_after_data_changed"));
37
-
37
+
38
38
  if (NIL_P(callback)) {
39
39
  return;
40
40
  }
41
-
41
+
42
42
  VALUE v_serverTime = Qnil;
43
43
  if (value->hasServerTimestamp) {
44
44
  v_serverTime = toRubyTime(value->serverTimestamp);
45
45
  }
46
-
46
+
47
47
  VALUE v_sourceTime = Qnil;
48
48
  if (value->hasSourceTimestamp) {
49
49
  v_sourceTime = toRubyTime(value->sourceTimestamp);
50
50
  }
51
-
51
+
52
52
  VALUE params = rb_ary_new();
53
53
  rb_ary_push(params, UINT2NUM(subId));
54
54
  rb_ary_push(params, UINT2NUM(monId));
55
55
  rb_ary_push(params, v_serverTime);
56
56
  rb_ary_push(params, v_sourceTime);
57
-
57
+
58
58
  VALUE v_newValue = Qnil;
59
-
59
+
60
60
  if(UA_Variant_hasScalarType(&value->value, &UA_TYPES[UA_TYPES_DATETIME])) {
61
61
  UA_DateTime raw_date = *(UA_DateTime *) value->value.data;
62
62
  v_newValue = toRubyTime(raw_date);
@@ -73,7 +73,7 @@ handler_dataChanged(UA_Client *client, UA_UInt32 subId, void *subContext,
73
73
  UA_Float dbl = *(UA_Float *) value->value.data;
74
74
  v_newValue = DBL2NUM(dbl);
75
75
  }
76
-
76
+
77
77
  rb_ary_push(params, v_newValue);
78
78
  rb_proc_call(callback, params);
79
79
  }
@@ -91,7 +91,7 @@ subscriptionInactivityCallback(UA_Client *client, UA_UInt32 subscriptionId, void
91
91
  static void
92
92
  stateCallback (UA_Client *client, UA_ClientState clientState) {
93
93
  struct OpcuaClientContext *ctx = UA_Client_getContext(client);
94
-
94
+
95
95
  switch(clientState) {
96
96
  case UA_CLIENTSTATE_DISCONNECTED:
97
97
  ; // printf("%s\n", "The client is disconnected");
@@ -105,14 +105,14 @@ stateCallback (UA_Client *client, UA_ClientState clientState) {
105
105
  case UA_CLIENTSTATE_SESSION:
106
106
  ; // printf("%s\n", "A new session was created!");
107
107
  VALUE self = ctx->rubyClientInstance;
108
-
108
+
109
109
  VALUE callback = rb_ivar_get(self, rb_intern("@callback_after_session_created"));
110
110
  if (!NIL_P(callback)) {
111
111
  VALUE params = rb_ary_new();
112
112
  rb_ary_push(params, self);
113
113
  rb_proc_call(callback, params); // rescue?
114
114
  }
115
-
115
+
116
116
  break;
117
117
  case UA_CLIENTSTATE_SESSION_RENEWED:
118
118
  /* The session was renewed. We don't need to recreate the subscription. */
@@ -134,7 +134,7 @@ static VALUE raise_ua_status_error(UA_StatusCode status) {
134
134
  static void UA_Client_free(void *self) {
135
135
  // printf("free client\n");
136
136
  struct UninitializedClient *uclient = self;
137
-
137
+
138
138
  if (uclient->client) {
139
139
  struct OpcuaClientContext *ctx = UA_Client_getContext(uclient->client);
140
140
  xfree(ctx);
@@ -154,26 +154,26 @@ static VALUE allocate(VALUE klass) {
154
154
  // printf("allocate client\n");
155
155
  struct UninitializedClient *uclient = ALLOC(struct UninitializedClient);
156
156
  *uclient = (const struct UninitializedClient){ 0 };
157
-
157
+
158
158
  return TypedData_Wrap_Struct(klass, &UA_Client_Type, uclient);
159
159
  }
160
160
 
161
161
  static VALUE rb_initialize(VALUE self) {
162
162
  struct UninitializedClient * uclient;
163
163
  TypedData_Get_Struct(self, struct UninitializedClient, &UA_Client_Type, uclient);
164
-
164
+
165
165
  UA_ClientConfig customConfig = UA_ClientConfig_default;
166
166
  customConfig.stateCallback = stateCallback;
167
167
  customConfig.subscriptionInactivityCallback = subscriptionInactivityCallback;
168
-
168
+
169
169
  struct OpcuaClientContext *ctx = ALLOC(struct OpcuaClientContext);
170
170
  *ctx = (const struct OpcuaClientContext){ 0 };
171
-
171
+
172
172
  ctx->rubyClientInstance = self;
173
173
  customConfig.clientContext = ctx;
174
-
174
+
175
175
  uclient->client = UA_Client_new(customConfig);
176
-
176
+
177
177
  return Qnil;
178
178
  }
179
179
 
@@ -181,15 +181,15 @@ static VALUE rb_connect(VALUE self, VALUE v_connectionString) {
181
181
  if (RB_TYPE_P(v_connectionString, T_STRING) != 1) {
182
182
  return raise_invalid_arguments_error();
183
183
  }
184
-
184
+
185
185
  char *connectionString = StringValueCStr(v_connectionString);
186
-
186
+
187
187
  struct UninitializedClient * uclient;
188
188
  TypedData_Get_Struct(self, struct UninitializedClient, &UA_Client_Type, uclient);
189
189
  UA_Client *client = uclient->client;
190
-
190
+
191
191
  UA_StatusCode status = UA_Client_connect(client, connectionString);
192
-
192
+
193
193
  if (status == UA_STATUSCODE_GOOD) {
194
194
  return Qnil;
195
195
  } else {
@@ -201,10 +201,10 @@ static VALUE rb_createSubscription(VALUE self) {
201
201
  struct UninitializedClient * uclient;
202
202
  TypedData_Get_Struct(self, struct UninitializedClient, &UA_Client_Type, uclient);
203
203
  UA_Client *client = uclient->client;
204
-
204
+
205
205
  UA_CreateSubscriptionRequest request = UA_CreateSubscriptionRequest_default();
206
206
  UA_CreateSubscriptionResponse response = UA_Client_Subscriptions_create(client, request, NULL, NULL, deleteSubscriptionCallback);
207
-
207
+
208
208
  if (response.responseHeader.serviceResult == UA_STATUSCODE_GOOD) {
209
209
  UA_UInt32 subscriptionId = response.subscriptionId;
210
210
  return UINT2NUM(subscriptionId);
@@ -217,13 +217,13 @@ static VALUE rb_addMonitoredItem(VALUE self, VALUE v_subscriptionId, VALUE v_mon
217
217
  struct UninitializedClient * uclient;
218
218
  TypedData_Get_Struct(self, struct UninitializedClient, &UA_Client_Type, uclient);
219
219
  UA_Client *client = uclient->client;
220
-
220
+
221
221
  UA_UInt32 subscriptionId = NUM2UINT(v_subscriptionId); // TODO: check type
222
222
  UA_UInt16 monNsIndex = NUM2USHORT(v_monNsIndex); // TODO: check type
223
223
  char* monNsName = StringValueCStr(v_monNsName); // TODO: check type
224
224
 
225
225
  UA_MonitoredItemCreateRequest monRequest = UA_MonitoredItemCreateRequest_default(UA_NODEID_STRING(monNsIndex, monNsName));
226
-
226
+
227
227
  UA_MonitoredItemCreateResult monResponse =
228
228
  UA_Client_MonitoredItems_createDataChange(client, subscriptionId,
229
229
  UA_TIMESTAMPSTORETURN_BOTH,
@@ -242,18 +242,78 @@ static VALUE rb_disconnect(VALUE self) {
242
242
  struct UninitializedClient * uclient;
243
243
  TypedData_Get_Struct(self, struct UninitializedClient, &UA_Client_Type, uclient);
244
244
  UA_Client *client = uclient->client;
245
-
245
+
246
246
  UA_StatusCode status = UA_Client_disconnect(client);
247
247
  return RB_UINT2NUM(status);
248
248
  }
249
249
 
250
+ static UA_StatusCode multiRead(UA_Client *client, const UA_NodeId *nodeId, UA_Variant *out, const long varsCount) {
251
+
252
+ UA_UInt16 rvSize = UA_TYPES[UA_TYPES_READVALUEID].memSize;
253
+ UA_ReadValueId *rValues = UA_calloc(varsCount, rvSize);
254
+
255
+ for (int i=0; i<varsCount; i++) {
256
+ UA_ReadValueId *readItem = &rValues[i];
257
+ readItem->nodeId = nodeId[i];
258
+ readItem->attributeId = UA_ATTRIBUTEID_VALUE;
259
+ }
260
+
261
+ UA_ReadRequest request;
262
+ UA_ReadRequest_init(&request);
263
+ request.nodesToRead = rValues;
264
+ request.nodesToReadSize = varsCount;
265
+
266
+ UA_ReadResponse response = UA_Client_Service_read(client, request);
267
+ UA_StatusCode retval = response.responseHeader.serviceResult;
268
+ if(retval == UA_STATUSCODE_GOOD) {
269
+ if(response.resultsSize == varsCount)
270
+ retval = response.results[0].status;
271
+ else
272
+ retval = UA_STATUSCODE_BADUNEXPECTEDERROR;
273
+ }
274
+
275
+ if(retval != UA_STATUSCODE_GOOD) {
276
+ UA_ReadResponse_deleteMembers(&response);
277
+ UA_free(rValues);
278
+ return retval;
279
+ }
280
+
281
+ /* Set the StatusCode */
282
+ UA_DataValue *results = response.results;
283
+
284
+ if (response.resultsSize != varsCount) {
285
+ retval = UA_STATUSCODE_BADUNEXPECTEDERROR;
286
+ UA_ReadResponse_deleteMembers(&response);
287
+ UA_free(rValues);
288
+ return retval;
289
+ }
290
+
291
+ for (int i=0; i<varsCount; i++) {
292
+ if ((results[i].hasStatus && results[i].status != UA_STATUSCODE_GOOD) || !results[i].hasValue) {
293
+ retval = UA_STATUSCODE_BADUNEXPECTEDERROR;
294
+ UA_ReadResponse_deleteMembers(&response);
295
+ UA_free(rValues);
296
+ return retval;
297
+ }
298
+ }
299
+
300
+ for (int i=0; i<varsCount; i++) {
301
+ out[i] = results[i].value;
302
+ UA_Variant_init(&results[i].value);
303
+ }
304
+
305
+ UA_ReadResponse_deleteMembers(&response);
306
+ UA_free(rValues);
307
+ return retval;
308
+ }
309
+
250
310
  static UA_StatusCode multiWrite(UA_Client *client, const UA_NodeId *nodeId, const UA_Variant *in, const long varsSize) {
251
311
  UA_AttributeId attributeId = UA_ATTRIBUTEID_VALUE;
252
-
312
+
253
313
  UA_UInt16 wvSize = UA_TYPES[UA_TYPES_WRITEVALUE].memSize;
254
-
314
+
255
315
  UA_WriteValue *wValues = UA_calloc(varsSize, wvSize);
256
-
316
+
257
317
  for (int i=0; i<varsSize; i++) {
258
318
  UA_WriteValue *wValue = &wValues[i];
259
319
  wValue->attributeId = attributeId;
@@ -261,7 +321,7 @@ static UA_StatusCode multiWrite(UA_Client *client, const UA_NodeId *nodeId, cons
261
321
  wValue->value.value = in[i];
262
322
  wValue->value.hasValue = true;
263
323
  }
264
-
324
+
265
325
  UA_WriteRequest wReq;
266
326
  UA_WriteRequest_init(&wReq);
267
327
  wReq.nodesToWrite = wValues;
@@ -273,7 +333,7 @@ static UA_StatusCode multiWrite(UA_Client *client, const UA_NodeId *nodeId, cons
273
333
  if(retval == UA_STATUSCODE_GOOD) {
274
334
  if(wResp.resultsSize == varsSize) {
275
335
  retval = wResp.results[0];
276
-
336
+
277
337
  for (int i=0; i<wResp.resultsSize; i++) {
278
338
  if (wResp.results[i] != UA_STATUSCODE_GOOD) {
279
339
  retval = wResp.results[i];
@@ -281,7 +341,7 @@ static UA_StatusCode multiWrite(UA_Client *client, const UA_NodeId *nodeId, cons
281
341
  break;
282
342
  }
283
343
  }
284
-
344
+
285
345
  if (retval == UA_STATUSCODE_GOOD) {
286
346
  // printf("%s\n", "multiWrite: all vars written");
287
347
  }
@@ -295,66 +355,168 @@ static UA_StatusCode multiWrite(UA_Client *client, const UA_NodeId *nodeId, cons
295
355
 
296
356
  UA_WriteResponse_deleteMembers(&wResp);
297
357
  UA_free(wValues);
298
-
358
+
299
359
  return retval;
300
360
  }
301
361
 
362
+ static VALUE rb_readUaValues(VALUE self, VALUE v_nsIndex, VALUE v_aryNames) {
363
+ if (RB_TYPE_P(v_nsIndex, T_FIXNUM) != 1) {
364
+ return raise_invalid_arguments_error();
365
+ }
366
+
367
+ Check_Type(v_aryNames, T_ARRAY);
368
+ const long namesCount = RARRAY_LEN(v_aryNames);
369
+
370
+ int nsIndex = FIX2INT(v_nsIndex);
371
+
372
+ struct UninitializedClient * uclient;
373
+ TypedData_Get_Struct(self, struct UninitializedClient, &UA_Client_Type, uclient);
374
+ UA_Client *client = uclient->client;
375
+
376
+ UA_UInt16 nidSize = UA_TYPES[UA_TYPES_NODEID].memSize;
377
+ UA_UInt16 variantSize = UA_TYPES[UA_TYPES_VARIANT].memSize;
378
+
379
+ UA_NodeId *nodes = UA_calloc(namesCount, nidSize);
380
+ UA_Variant *readValues = UA_calloc(namesCount, variantSize);
381
+
382
+ for (int i=0; i<namesCount; i++) {
383
+ VALUE v_name = rb_ary_entry(v_aryNames, i);
384
+
385
+ if (RB_TYPE_P(v_name, T_STRING) != 1) {
386
+ return raise_invalid_arguments_error();
387
+ }
388
+
389
+ char *name = StringValueCStr(v_name);
390
+ nodes[i] = UA_NODEID_STRING(nsIndex, name);
391
+ }
392
+
393
+ UA_StatusCode status = multiRead(client, nodes, readValues, namesCount);
394
+
395
+ VALUE resultArray = Qnil;
396
+
397
+ if (status == UA_STATUSCODE_GOOD) {
398
+ // printf("%s\n", "value read successful");
399
+
400
+ resultArray = rb_ary_new2(namesCount);
401
+
402
+ for (int i=0; i<namesCount; i++) {
403
+ // printf("the value is: %i\n", val);
404
+
405
+ VALUE rubyVal = Qnil;
406
+
407
+ if (UA_Variant_hasScalarType(&readValues[i], &UA_TYPES[UA_TYPES_INT16])) {
408
+ UA_Int16 val = *(UA_Int16*)readValues[i].data;
409
+ rubyVal = INT2FIX(val);
410
+ } else if (UA_Variant_hasScalarType(&readValues[i], &UA_TYPES[UA_TYPES_UINT16])) {
411
+ UA_UInt16 val = *(UA_UInt16*)readValues[i].data;
412
+ rubyVal = INT2FIX(val);
413
+ } else if (UA_Variant_hasScalarType(&readValues[i], &UA_TYPES[UA_TYPES_INT32])) {
414
+ UA_Int32 val = *(UA_Int32*)readValues[i].data;
415
+ rubyVal = INT2FIX(val);
416
+ } else if (UA_Variant_hasScalarType(&readValues[i], &UA_TYPES[UA_TYPES_UINT32])) {
417
+ UA_UInt32 val = *(UA_UInt32*)readValues[i].data;
418
+ rubyVal = INT2FIX(val);
419
+ } else if (UA_Variant_hasScalarType(&readValues[i], &UA_TYPES[UA_TYPES_BOOLEAN])) {
420
+ UA_Boolean val = *(UA_Boolean*)readValues[i].data;
421
+ rubyVal = val ? Qtrue : Qfalse;
422
+ } else if (UA_Variant_hasScalarType(&readValues[i], &UA_TYPES[UA_TYPES_FLOAT])) {
423
+ UA_Float val = *(UA_Float*)readValues[i].data;
424
+ rubyVal = DBL2NUM(val);
425
+ } else {
426
+ rubyVal = Qnil; // unsupported
427
+ }
428
+
429
+ rb_ary_push(resultArray, rubyVal);
430
+ }
431
+ } else {
432
+ /* Clean up */
433
+ for (int i=0; i<namesCount; i++) {
434
+ UA_Variant_deleteMembers(&readValues[i]);
435
+ }
436
+ UA_free(nodes);
437
+ UA_free(readValues);
438
+
439
+ return raise_ua_status_error(status);
440
+ }
441
+
442
+ /* Clean up */
443
+ for (int i=0; i<namesCount; i++) {
444
+ UA_Variant_deleteMembers(&readValues[i]);
445
+ }
446
+ UA_free(nodes);
447
+ UA_free(readValues);
448
+
449
+ return resultArray;
450
+ }
451
+
302
452
  static VALUE rb_writeUaValues(VALUE self, VALUE v_nsIndex, VALUE v_aryNames, VALUE v_aryNewValues, int uaType) {
303
453
  if (RB_TYPE_P(v_nsIndex, T_FIXNUM) != 1) {
304
454
  return raise_invalid_arguments_error();
305
455
  }
306
-
456
+
307
457
  Check_Type(v_aryNames, T_ARRAY);
308
458
  Check_Type(v_aryNewValues, T_ARRAY);
309
-
459
+
310
460
  const long namesCount = RARRAY_LEN(v_aryNames);
311
461
  const long valuesCount = RARRAY_LEN(v_aryNewValues);
312
-
462
+
313
463
  if (namesCount != valuesCount) {
314
464
  return raise_invalid_arguments_error();
315
465
  }
316
-
466
+
317
467
  int nsIndex = FIX2INT(v_nsIndex);
318
-
468
+
319
469
  struct UninitializedClient * uclient;
320
470
  TypedData_Get_Struct(self, struct UninitializedClient, &UA_Client_Type, uclient);
321
471
  UA_Client *client = uclient->client;
322
-
472
+
323
473
  UA_UInt16 nidSize = UA_TYPES[UA_TYPES_NODEID].memSize;
324
474
  UA_UInt16 variantSize = UA_TYPES[UA_TYPES_VARIANT].memSize;
325
-
475
+
326
476
  UA_NodeId *nodes = UA_calloc(namesCount, nidSize);
327
477
  UA_Variant *values = UA_calloc(namesCount, variantSize);
328
-
478
+
329
479
  for (int i=0; i<namesCount; i++) {
330
480
  VALUE v_name = rb_ary_entry(v_aryNames, i);
331
481
  VALUE v_newValue = rb_ary_entry(v_aryNewValues, i);
332
-
482
+
333
483
  if (RB_TYPE_P(v_name, T_STRING) != 1) {
334
484
  return raise_invalid_arguments_error();
335
485
  }
336
-
486
+
337
487
  char *name = StringValueCStr(v_name);
338
488
  nodes[i] = UA_NODEID_STRING(nsIndex, name);
339
-
340
- if (uaType == UA_TYPES_INT16) {
489
+
490
+ if (uaType == UA_TYPES_UINT16) {
491
+ Check_Type(v_newValue, T_FIXNUM);
492
+ UA_UInt16 newValue = NUM2USHORT(v_newValue);
493
+ values[i].data = UA_malloc(sizeof(UA_UInt16));
494
+ *(UA_UInt16*)values[i].data = newValue;
495
+ values[i].type = &UA_TYPES[uaType];
496
+ } else if (uaType == UA_TYPES_INT16) {
341
497
  Check_Type(v_newValue, T_FIXNUM);
342
498
  UA_Int16 newValue = NUM2SHORT(v_newValue);
343
499
  values[i].data = UA_malloc(sizeof(UA_Int16));
344
500
  *(UA_Int16*)values[i].data = newValue;
345
- values[i].type = &UA_TYPES[UA_TYPES_INT16];
501
+ values[i].type = &UA_TYPES[uaType];
502
+ } else if (uaType == UA_TYPES_UINT32) {
503
+ Check_Type(v_newValue, T_FIXNUM);
504
+ UA_UInt32 newValue = NUM2UINT(v_newValue);
505
+ values[i].data = UA_malloc(sizeof(UA_UInt32));
506
+ *(UA_UInt32*)values[i].data = newValue;
507
+ values[i].type = &UA_TYPES[uaType];
346
508
  } else if (uaType == UA_TYPES_INT32) {
347
509
  Check_Type(v_newValue, T_FIXNUM);
348
510
  UA_Int32 newValue = NUM2INT(v_newValue);
349
511
  values[i].data = UA_malloc(sizeof(UA_Int32));
350
512
  *(UA_Int32*)values[i].data = newValue;
351
- values[i].type = &UA_TYPES[UA_TYPES_INT32];
513
+ values[i].type = &UA_TYPES[uaType];
352
514
  } else if (uaType == UA_TYPES_FLOAT) {
353
515
  Check_Type(v_newValue, T_FLOAT);
354
516
  UA_Float newValue = NUM2DBL(v_newValue);
355
517
  values[i].data = UA_malloc(sizeof(UA_Float));
356
518
  *(UA_Float*)values[i].data = newValue;
357
- values[i].type = &UA_TYPES[UA_TYPES_FLOAT];
519
+ values[i].type = &UA_TYPES[uaType];
358
520
  } else if (uaType == UA_TYPES_BOOLEAN) {
359
521
  if (RB_TYPE_P(v_newValue, T_TRUE) != 1 && RB_TYPE_P(v_newValue, T_FALSE) != 1) {
360
522
  return raise_invalid_arguments_error();
@@ -367,9 +529,9 @@ static VALUE rb_writeUaValues(VALUE self, VALUE v_nsIndex, VALUE v_aryNames, VAL
367
529
  rb_raise(cError, "Unsupported type");
368
530
  }
369
531
  }
370
-
532
+
371
533
  UA_StatusCode status = multiWrite(client, nodes, values, namesCount);
372
-
534
+
373
535
  if (status == UA_STATUSCODE_GOOD) {
374
536
  // printf("%s\n", "value write successful");
375
537
  } else {
@@ -379,17 +541,17 @@ static VALUE rb_writeUaValues(VALUE self, VALUE v_nsIndex, VALUE v_aryNames, VAL
379
541
  }
380
542
  UA_free(nodes);
381
543
  UA_free(values);
382
-
544
+
383
545
  return raise_ua_status_error(status);
384
546
  }
385
-
547
+
386
548
  /* Clean up */
387
549
  for (int i=0; i<namesCount; i++) {
388
550
  UA_Variant_deleteMembers(&values[i]);
389
551
  }
390
552
  UA_free(nodes);
391
553
  UA_free(values);
392
-
554
+
393
555
  return Qnil;
394
556
  }
395
557
 
@@ -397,35 +559,45 @@ static VALUE rb_writeUaValue(VALUE self, VALUE v_nsIndex, VALUE v_name, VALUE v_
397
559
  if (RB_TYPE_P(v_name, T_STRING) != 1) {
398
560
  return raise_invalid_arguments_error();
399
561
  }
400
-
562
+
401
563
  if (RB_TYPE_P(v_nsIndex, T_FIXNUM) != 1) {
402
564
  return raise_invalid_arguments_error();
403
565
  }
404
-
566
+
405
567
  if (uaType == UA_TYPES_INT16 && RB_TYPE_P(v_newValue, T_FIXNUM) != 1) {
406
568
  return raise_invalid_arguments_error();
407
569
  }
408
-
570
+
409
571
  char *name = StringValueCStr(v_name);
410
572
  int nsIndex = FIX2INT(v_nsIndex);
411
-
573
+
412
574
  struct UninitializedClient * uclient;
413
575
  TypedData_Get_Struct(self, struct UninitializedClient, &UA_Client_Type, uclient);
414
576
  UA_Client *client = uclient->client;
415
-
577
+
416
578
  UA_Variant value;
417
579
  UA_Variant_init(&value);
418
-
580
+
419
581
  if (uaType == UA_TYPES_INT16) {
420
582
  UA_Int16 newValue = NUM2SHORT(v_newValue);
421
583
  value.data = UA_malloc(sizeof(UA_Int16));
422
584
  *(UA_Int16*)value.data = newValue;
423
585
  value.type = &UA_TYPES[UA_TYPES_INT16];
586
+ } else if (uaType == UA_TYPES_UINT16) {
587
+ UA_UInt16 newValue = NUM2USHORT(v_newValue);
588
+ value.data = UA_malloc(sizeof(UA_UInt16));
589
+ *(UA_UInt16*)value.data = newValue;
590
+ value.type = &UA_TYPES[UA_TYPES_UINT16];
424
591
  } else if (uaType == UA_TYPES_INT32) {
425
592
  UA_Int32 newValue = NUM2INT(v_newValue);
426
593
  value.data = UA_malloc(sizeof(UA_Int32));
427
594
  *(UA_Int32*)value.data = newValue;
428
595
  value.type = &UA_TYPES[UA_TYPES_INT32];
596
+ } else if (uaType == UA_TYPES_UINT32) {
597
+ UA_UInt32 newValue = NUM2UINT(v_newValue);
598
+ value.data = UA_malloc(sizeof(UA_UInt32));
599
+ *(UA_UInt32*)value.data = newValue;
600
+ value.type = &UA_TYPES[UA_TYPES_UINT32];
429
601
  } else if (uaType == UA_TYPES_FLOAT) {
430
602
  UA_Float newValue = NUM2DBL(v_newValue);
431
603
  value.data = UA_malloc(sizeof(UA_Float));
@@ -441,7 +613,7 @@ static VALUE rb_writeUaValue(VALUE self, VALUE v_nsIndex, VALUE v_name, VALUE v_
441
613
  }
442
614
 
443
615
  UA_StatusCode status = UA_Client_writeValueAttribute(client, UA_NODEID_STRING(nsIndex, name), &value);
444
-
616
+
445
617
  if (status == UA_STATUSCODE_GOOD) {
446
618
  // printf("%s\n", "value write successful");
447
619
  } else {
@@ -449,13 +621,21 @@ static VALUE rb_writeUaValue(VALUE self, VALUE v_nsIndex, VALUE v_name, VALUE v_
449
621
  UA_Variant_deleteMembers(&value);
450
622
  return raise_ua_status_error(status);
451
623
  }
452
-
624
+
453
625
  /* Clean up */
454
626
  UA_Variant_deleteMembers(&value);
455
-
627
+
456
628
  return Qnil;
457
629
  }
458
630
 
631
+ static VALUE rb_writeUInt16Value(VALUE self, VALUE v_nsIndex, VALUE v_name, VALUE v_newValue) {
632
+ return rb_writeUaValue(self, v_nsIndex, v_name, v_newValue, UA_TYPES_UINT16);
633
+ }
634
+
635
+ static VALUE rb_writeUInt16Values(VALUE self, VALUE v_nsIndex, VALUE v_aryNames, VALUE v_aryNewValues) {
636
+ return rb_writeUaValues(self, v_nsIndex, v_aryNames, v_aryNewValues, UA_TYPES_UINT16);
637
+ }
638
+
459
639
  static VALUE rb_writeInt16Value(VALUE self, VALUE v_nsIndex, VALUE v_name, VALUE v_newValue) {
460
640
  return rb_writeUaValue(self, v_nsIndex, v_name, v_newValue, UA_TYPES_INT16);
461
641
  }
@@ -472,6 +652,14 @@ static VALUE rb_writeInt32Values(VALUE self, VALUE v_nsIndex, VALUE v_aryNames,
472
652
  return rb_writeUaValues(self, v_nsIndex, v_aryNames, v_aryNewValues, UA_TYPES_INT32);
473
653
  }
474
654
 
655
+ static VALUE rb_writeUInt32Value(VALUE self, VALUE v_nsIndex, VALUE v_name, VALUE v_newValue) {
656
+ return rb_writeUaValue(self, v_nsIndex, v_name, v_newValue, UA_TYPES_UINT32);
657
+ }
658
+
659
+ static VALUE rb_writeUInt32Values(VALUE self, VALUE v_nsIndex, VALUE v_aryNames, VALUE v_aryNewValues) {
660
+ return rb_writeUaValues(self, v_nsIndex, v_aryNames, v_aryNewValues, UA_TYPES_UINT32);
661
+ }
662
+
475
663
  static VALUE rb_writeBooleanValue(VALUE self, VALUE v_nsIndex, VALUE v_name, VALUE v_newValue) {
476
664
  return rb_writeUaValue(self, v_nsIndex, v_name, v_newValue, UA_TYPES_BOOLEAN);
477
665
  }
@@ -492,22 +680,22 @@ static VALUE rb_readUaValue(VALUE self, VALUE v_nsIndex, VALUE v_name, int type)
492
680
  if (RB_TYPE_P(v_name, T_STRING) != 1) {
493
681
  return raise_invalid_arguments_error();
494
682
  }
495
-
683
+
496
684
  if (RB_TYPE_P(v_nsIndex, T_FIXNUM) != 1) {
497
685
  return raise_invalid_arguments_error();
498
686
  }
499
-
687
+
500
688
  char *name = StringValueCStr(v_name);
501
689
  int nsIndex = FIX2INT(v_nsIndex);
502
-
690
+
503
691
  struct UninitializedClient * uclient;
504
692
  TypedData_Get_Struct(self, struct UninitializedClient, &UA_Client_Type, uclient);
505
693
  UA_Client *client = uclient->client;
506
-
694
+
507
695
  UA_Variant value;
508
696
  UA_Variant_init(&value);
509
697
  UA_StatusCode status = UA_Client_readValueAttribute(client, UA_NODEID_STRING(nsIndex, name), &value);
510
-
698
+
511
699
  if (status == UA_STATUSCODE_GOOD) {
512
700
  // printf("%s\n", "value read successful");
513
701
  } else {
@@ -515,16 +703,23 @@ static VALUE rb_readUaValue(VALUE self, VALUE v_nsIndex, VALUE v_name, int type)
515
703
  UA_Variant_deleteMembers(&value);
516
704
  return raise_ua_status_error(status);
517
705
  }
518
-
706
+
519
707
  VALUE result = Qnil;
520
-
708
+
521
709
  if (type == UA_TYPES_INT16 && UA_Variant_hasScalarType(&value, &UA_TYPES[UA_TYPES_INT16])) {
522
710
  UA_Int16 val =*(UA_Int16*)value.data;
523
711
  // printf("the value is: %i\n", val);
524
712
  result = INT2FIX(val);
713
+ } else if (type == UA_TYPES_UINT16 && UA_Variant_hasScalarType(&value, &UA_TYPES[UA_TYPES_UINT16])) {
714
+ UA_UInt16 val =*(UA_UInt16*)value.data;
715
+ // printf("the value is: %i\n", val);
716
+ result = INT2FIX(val);
525
717
  } else if (type == UA_TYPES_INT32 && UA_Variant_hasScalarType(&value, &UA_TYPES[UA_TYPES_INT32])) {
526
718
  UA_Int32 val =*(UA_Int32*)value.data;
527
719
  result = INT2FIX(val);
720
+ } else if (type == UA_TYPES_UINT32 && UA_Variant_hasScalarType(&value, &UA_TYPES[UA_TYPES_UINT32])) {
721
+ UA_UInt32 val =*(UA_UInt32*)value.data;
722
+ result = INT2FIX(val);
528
723
  } else if (type == UA_TYPES_BOOLEAN && UA_Variant_hasScalarType(&value, &UA_TYPES[UA_TYPES_BOOLEAN])) {
529
724
  UA_Boolean val =*(UA_Boolean*)value.data;
530
725
  result = val ? Qtrue : Qfalse;
@@ -532,13 +727,13 @@ static VALUE rb_readUaValue(VALUE self, VALUE v_nsIndex, VALUE v_name, int type)
532
727
  UA_Float val =*(UA_Float*)value.data;
533
728
  result = DBL2NUM(val);
534
729
  } else {
535
- rb_raise(cError, "Not an int16");
730
+ rb_raise(cError, "UA type mismatch");
536
731
  return Qnil;
537
732
  }
538
-
733
+
539
734
  /* Clean up */
540
735
  UA_Variant_deleteMembers(&value);
541
-
736
+
542
737
  return result;
543
738
  }
544
739
 
@@ -546,10 +741,18 @@ static VALUE rb_readInt16Value(VALUE self, VALUE v_nsIndex, VALUE v_name) {
546
741
  return rb_readUaValue(self, v_nsIndex, v_name, UA_TYPES_INT16);
547
742
  }
548
743
 
744
+ static VALUE rb_readUInt16Value(VALUE self, VALUE v_nsIndex, VALUE v_name) {
745
+ return rb_readUaValue(self, v_nsIndex, v_name, UA_TYPES_UINT16);
746
+ }
747
+
549
748
  static VALUE rb_readInt32Value(VALUE self, VALUE v_nsIndex, VALUE v_name) {
550
749
  return rb_readUaValue(self, v_nsIndex, v_name, UA_TYPES_INT32);
551
750
  }
552
751
 
752
+ static VALUE rb_readUInt32Value(VALUE self, VALUE v_nsIndex, VALUE v_name) {
753
+ return rb_readUaValue(self, v_nsIndex, v_name, UA_TYPES_UINT32);
754
+ }
755
+
553
756
  static VALUE rb_readBooleanValue(VALUE self, VALUE v_nsIndex, VALUE v_name) {
554
757
  return rb_readUaValue(self, v_nsIndex, v_name, UA_TYPES_BOOLEAN);
555
758
  }
@@ -572,7 +775,7 @@ static VALUE rb_run_single_monitoring_cycle(VALUE self) {
572
775
  struct UninitializedClient * uclient;
573
776
  TypedData_Get_Struct(self, struct UninitializedClient, &UA_Client_Type, uclient);
574
777
  UA_Client *client = uclient->client;
575
-
778
+
576
779
  UA_StatusCode status = UA_Client_runAsync(client, 1000);
577
780
  return UINT2NUM(status);
578
781
  }
@@ -581,13 +784,13 @@ static VALUE rb_run_single_monitoring_cycle_bang(VALUE self) {
581
784
  struct UninitializedClient * uclient;
582
785
  TypedData_Get_Struct(self, struct UninitializedClient, &UA_Client_Type, uclient);
583
786
  UA_Client *client = uclient->client;
584
-
787
+
585
788
  UA_StatusCode status = UA_Client_runAsync(client, 1000);
586
-
789
+
587
790
  if (status != UA_STATUSCODE_GOOD) {
588
791
  return raise_ua_status_error(status);
589
792
  }
590
-
793
+
591
794
  return Qnil;
592
795
  }
593
796
 
@@ -595,7 +798,7 @@ static VALUE rb_state(VALUE self) {
595
798
  struct UninitializedClient * uclient;
596
799
  TypedData_Get_Struct(self, struct UninitializedClient, &UA_Client_Type, uclient);
597
800
  UA_Client *client = uclient->client;
598
-
801
+
599
802
  UA_ClientState state = UA_Client_getState(client);
600
803
  return INT2NUM(state);
601
804
  }
@@ -613,47 +816,58 @@ void Init_opcua_client()
613
816
  #ifdef UA_ENABLE_SUBSCRIPTIONS
614
817
  // printf("%s\n", "ok! opcua-client-ruby built with subscriptions enabled.");
615
818
  #endif
616
-
819
+
617
820
  mOPCUAClient = rb_const_get(rb_cObject, rb_intern("OPCUAClient"));
821
+ rb_global_variable(&mOPCUAClient);
618
822
  defineStateContants(mOPCUAClient);
619
-
823
+
620
824
  cError = rb_define_class_under(mOPCUAClient, "Error", rb_eStandardError);
825
+ rb_global_variable(&cError);
621
826
  cClient = rb_define_class_under(mOPCUAClient, "Client", rb_cObject);
622
-
827
+ rb_global_variable(&cClient);
828
+
623
829
  rb_define_alloc_func(cClient, allocate);
624
-
830
+
625
831
  rb_define_method(cClient, "initialize", rb_initialize, 0);
626
-
832
+
627
833
  rb_define_method(cClient, "run_single_monitoring_cycle", rb_run_single_monitoring_cycle, 0);
628
834
  rb_define_method(cClient, "run_mon_cycle", rb_run_single_monitoring_cycle, 0);
629
835
  rb_define_method(cClient, "do_mon_cycle", rb_run_single_monitoring_cycle, 0);
630
-
836
+
631
837
  rb_define_method(cClient, "run_single_monitoring_cycle!", rb_run_single_monitoring_cycle_bang, 0);
632
838
  rb_define_method(cClient, "run_mon_cycle!", rb_run_single_monitoring_cycle_bang, 0);
633
839
  rb_define_method(cClient, "do_mon_cycle!", rb_run_single_monitoring_cycle_bang, 0);
634
-
840
+
635
841
  rb_define_method(cClient, "connect", rb_connect, 1);
636
842
  rb_define_method(cClient, "disconnect", rb_disconnect, 0);
637
843
  rb_define_method(cClient, "state", rb_state, 0);
638
-
844
+
639
845
  rb_define_method(cClient, "read_int16", rb_readInt16Value, 2);
846
+ rb_define_method(cClient, "read_uint16", rb_readUInt16Value, 2);
640
847
  rb_define_method(cClient, "read_int32", rb_readInt32Value, 2);
848
+ rb_define_method(cClient, "read_uint32", rb_readUInt32Value, 2);
641
849
  rb_define_method(cClient, "read_float", rb_readFloatValue, 2);
642
850
  rb_define_method(cClient, "read_boolean", rb_readBooleanValue, 2);
643
851
  rb_define_method(cClient, "read_bool", rb_readBooleanValue, 2);
644
-
852
+
645
853
  rb_define_method(cClient, "write_int16", rb_writeInt16Value, 3);
854
+ rb_define_method(cClient, "write_uint16", rb_writeUInt16Value, 3);
646
855
  rb_define_method(cClient, "write_int32", rb_writeInt32Value, 3);
856
+ rb_define_method(cClient, "write_uint32", rb_writeUInt32Value, 3);
647
857
  rb_define_method(cClient, "write_float", rb_writeFloatValue, 3);
648
858
  rb_define_method(cClient, "write_boolean", rb_writeBooleanValue, 3);
649
859
  rb_define_method(cClient, "write_bool", rb_writeBooleanValue, 3);
650
-
860
+
651
861
  rb_define_method(cClient, "multi_write_int16", rb_writeInt16Values, 3);
862
+ rb_define_method(cClient, "multi_write_uint16", rb_writeUInt16Values, 3);
652
863
  rb_define_method(cClient, "multi_write_int32", rb_writeInt32Values, 3);
864
+ rb_define_method(cClient, "multi_write_uint32", rb_writeUInt32Values, 3);
653
865
  rb_define_method(cClient, "multi_write_float", rb_writeFloatValues, 3);
654
866
  rb_define_method(cClient, "multi_write_boolean", rb_writeBooleanValues, 3);
655
867
  rb_define_method(cClient, "multi_write_bool", rb_writeBooleanValues, 3);
656
-
868
+
869
+ rb_define_method(cClient, "multi_read", rb_readUaValues, 2);
870
+
657
871
  rb_define_method(cClient, "create_subscription", rb_createSubscription, 0);
658
872
  rb_define_method(cClient, "add_monitored_item", rb_addMonitoredItem, 3);
659
873
 
@@ -1,3 +1,3 @@
1
1
  module OPCUAClient
2
- VERSION = "0.0.2".freeze
2
+ VERSION = "0.0.5".freeze
3
3
  end
data/spec/core_spec.rb ADDED
@@ -0,0 +1,15 @@
1
+ require 'spec_helper'
2
+
3
+ RSpec.describe OPCUAClient::Client do
4
+ context "unconnected" do
5
+ it "allows disconnect for unconnected clients" do
6
+ result = new_client(connect: false).disconnect
7
+ expect(result).to eq(0)
8
+ end
9
+
10
+ it "returns 0 state" do
11
+ state = new_client(connect: false).state
12
+ expect(state).to eq(0)
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,22 @@
1
+ require 'rspec'
2
+ require 'opcua_client'
3
+
4
+ # https://github.com/brianmario/mysql2/commit/0ee20536501848a354f1c3a007333167120c7457
5
+ if GC.respond_to?(:verify_compaction_references)
6
+ # This method was added in Ruby 3.0.0. Calling it this way asks the GC to
7
+ # move objects around, helping to find object movement bugs.
8
+ GC.verify_compaction_references(double_heap: true, toward: :empty)
9
+ end
10
+
11
+ def new_client(connect: true)
12
+ client = OPCUAClient::Client.new
13
+
14
+ if connect
15
+ # TODO
16
+ end
17
+
18
+ client
19
+ end
20
+
21
+ RSpec.configure do |config|
22
+ end
metadata CHANGED
@@ -1,16 +1,16 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: opcua_client
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.2
4
+ version: 0.0.5
5
5
  platform: ruby
6
6
  authors:
7
7
  - Ritvars Rundzans
8
- autorequire:
8
+ autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2020-02-04 00:00:00.000000000 Z
11
+ date: 2022-08-25 00:00:00.000000000 Z
12
12
  dependencies: []
13
- description:
13
+ description:
14
14
  email:
15
15
  - ritvars.rundzans@makit.lv
16
16
  executables: []
@@ -26,11 +26,13 @@ files:
26
26
  - lib/opcua_client.rb
27
27
  - lib/opcua_client/client.rb
28
28
  - lib/opcua_client/version.rb
29
+ - spec/core_spec.rb
30
+ - spec/spec_helper.rb
29
31
  homepage: https://github.com/mak-it/opcua-client-ruby
30
32
  licenses:
31
33
  - MIT
32
34
  metadata: {}
33
- post_install_message:
35
+ post_install_message:
34
36
  rdoc_options:
35
37
  - "--charset=UTF-8"
36
38
  require_paths:
@@ -46,10 +48,11 @@ required_rubygems_version: !ruby/object:Gem::Requirement
46
48
  - !ruby/object:Gem::Version
47
49
  version: '0'
48
50
  requirements: []
49
- rubyforge_project:
50
- rubygems_version: 2.7.9
51
- signing_key:
51
+ rubygems_version: 3.3.7
52
+ signing_key:
52
53
  specification_version: 4
53
54
  summary: Basic OPC-UA client library for Ruby. Uses open62541 (https://open62541.org)
54
55
  under the hood.
55
- test_files: []
56
+ test_files:
57
+ - spec/core_spec.rb
58
+ - spec/spec_helper.rb