opcua_client 0.0.1
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.
- checksums.yaml +7 -0
- data/README.md +117 -0
- data/ext/opcua_client/extconf.rb +2 -0
- data/ext/opcua_client/opcua_client.c +493 -0
- data/ext/opcua_client/open62541.c +43978 -0
- data/ext/opcua_client/open62541.h +15587 -0
- data/lib/opcua_client.rb +18 -0
- data/lib/opcua_client/client.rb +22 -0
- data/lib/opcua_client/version.rb +3 -0
- metadata +55 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 7ece180c3aa8255995c232d04ab6edd8d698534a2e7a07f95feb67aee06dce5c
|
4
|
+
data.tar.gz: 1478ab14003bdc8c6da023fc53eef15b5d8e0e5f76850c1773b087563e0ae80d
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 39fcbe3246e1f1c416e3d4258e395d0d12da62dfd4c7c64b5151a6bc8b308c243baa880519161b8fb13e891bdcacad6f5da3e3fa9875973526ae1cfa300376c0
|
7
|
+
data.tar.gz: 3f0057136a51a9a7c08df5be42df6b95291810dea18cfc7e2369712e05fdf40ffba3d2ead4d159f94994d6995abcf6e96e81f1b3a5c6cfe9933b30e6641d1500
|
data/README.md
ADDED
@@ -0,0 +1,117 @@
|
|
1
|
+
# opcua-client-ruby
|
2
|
+
|
3
|
+
Incomplete OPC-UA client library for Ruby. Wraps open62541: <https://open62541.org>.
|
4
|
+
|
5
|
+
## Installation
|
6
|
+
|
7
|
+
Add it to your Gemfile:
|
8
|
+
|
9
|
+
```ruby
|
10
|
+
gem 'opcua_client'
|
11
|
+
```
|
12
|
+
|
13
|
+
## Basic usage
|
14
|
+
|
15
|
+
Use `start` helper to automatically close connections:
|
16
|
+
|
17
|
+
```ruby
|
18
|
+
require 'opcua_client'
|
19
|
+
|
20
|
+
OPCUAClient.start("opc.tcp://127.0.0.1:4840") do |client|
|
21
|
+
# write to ns=2;s=1
|
22
|
+
client.write_int16(2, "1", 888)
|
23
|
+
puts client.read_int16(2, "1")
|
24
|
+
end
|
25
|
+
```
|
26
|
+
|
27
|
+
Or handle connections manually:
|
28
|
+
|
29
|
+
```ruby
|
30
|
+
require 'opcua_client'
|
31
|
+
|
32
|
+
client = OPCUAClient::Client.new
|
33
|
+
begin
|
34
|
+
client.connect("opc.tcp://127.0.0.1:4840")
|
35
|
+
# write to ns=2;s=1
|
36
|
+
client.write_int16(2, "1", 888)
|
37
|
+
puts client.read_int16(2, "1")
|
38
|
+
ensure
|
39
|
+
client.disconnect
|
40
|
+
end
|
41
|
+
```
|
42
|
+
|
43
|
+
### Available methods - connection:
|
44
|
+
|
45
|
+
* ```client.connect(String url)``` - raises OPCUAClient::Error if unsuccessful
|
46
|
+
* ```client.disconnect => Fixnum``` - returns status
|
47
|
+
|
48
|
+
### Available methods - reads and writes:
|
49
|
+
|
50
|
+
All methods raise OPCUAClient::Error if unsuccessful.
|
51
|
+
|
52
|
+
* ```client.read_int16(Fixnum ns, String name) => Fixnum```
|
53
|
+
* ```client.read_int32(Fixnum ns, String name) => Fixnum```
|
54
|
+
* ```client.read_float(Fixnum ns, String name) => Float```
|
55
|
+
* ```client.read_boolean(Fixnum ns, String name) => true/false```
|
56
|
+
* ```client.write_int16(Fixnum ns, String name, Fixnum value)```
|
57
|
+
* ```client.write_int32(Fixnum ns, String name, Fixnum value)```
|
58
|
+
* ```client.write_float(Fixnum ns, String name, Float value)```
|
59
|
+
* ```client.write_boolean(Fixnum ns, String name, bool value)```
|
60
|
+
|
61
|
+
### Available methods - misc:
|
62
|
+
|
63
|
+
* ```client.state => Fixnum``` - client internal state
|
64
|
+
* ```client.human_state => String``` - human readable client internal state
|
65
|
+
* ```OPCUAClient::Client.human_status_code(Fixnum status) => String``` - returns human status for status
|
66
|
+
|
67
|
+
## Subscriptions and monitoring
|
68
|
+
|
69
|
+
```ruby
|
70
|
+
cli = OPCUAClient::Client.new
|
71
|
+
|
72
|
+
cli.after_session_created do |cli|
|
73
|
+
subscription_id = cli.create_subscription
|
74
|
+
ns_index = 1
|
75
|
+
node_name = "the.answer"
|
76
|
+
cli.add_monitored_item(subscription_id, ns_index, node_name)
|
77
|
+
end
|
78
|
+
|
79
|
+
cli.after_data_changed do |subscription_id, monitor_id, server_time, source_time, new_value|
|
80
|
+
puts("data changed: " + [subscription_id, monitor_id, server_time, source_time, new_value].inspect)
|
81
|
+
end
|
82
|
+
|
83
|
+
cli.connect("opc.tcp://127.0.0.1:4840")
|
84
|
+
|
85
|
+
loop do
|
86
|
+
cli.connect("opc.tcp://127.0.0.1:4840") # no-op if connected
|
87
|
+
cli.run_mon_cycle
|
88
|
+
sleep(0.2)
|
89
|
+
end
|
90
|
+
```
|
91
|
+
|
92
|
+
### Available methods:
|
93
|
+
|
94
|
+
* ```client.create_subscription => Fixnum``` - nil if error
|
95
|
+
* ```client.add_monitored_item(Fixnum subscription, Fixnum ns, String name) => Fixnum``` - nil if error
|
96
|
+
* ```client.run_mon_cycle``` - returns status
|
97
|
+
* ```client.run_mon_cycle!``` - raises OPCUAClient::Error if unsuccessful
|
98
|
+
|
99
|
+
### Available callbacks:
|
100
|
+
* ```after_session_created```
|
101
|
+
* ```after_data_changed```
|
102
|
+
|
103
|
+
## Contribute
|
104
|
+
|
105
|
+
### Set up
|
106
|
+
|
107
|
+
```console
|
108
|
+
bundle
|
109
|
+
```
|
110
|
+
|
111
|
+
### Try out changes
|
112
|
+
|
113
|
+
```console
|
114
|
+
$ rake compile
|
115
|
+
$ bin/console
|
116
|
+
pry> client = OPCUAClient::Client.new
|
117
|
+
```
|
@@ -0,0 +1,493 @@
|
|
1
|
+
#include <ruby.h>
|
2
|
+
#include "open62541.h"
|
3
|
+
|
4
|
+
VALUE cClient;
|
5
|
+
VALUE cError;
|
6
|
+
VALUE mOPCUAClient;
|
7
|
+
|
8
|
+
struct UninitializedClient {
|
9
|
+
UA_Client *client;
|
10
|
+
};
|
11
|
+
|
12
|
+
struct OpcuaClientContext {
|
13
|
+
VALUE rubyClientInstance;
|
14
|
+
};
|
15
|
+
|
16
|
+
static VALUE toRubyTime(UA_DateTime raw_date) {
|
17
|
+
UA_DateTimeStruct dts = UA_DateTime_toStruct(raw_date);
|
18
|
+
VALUE year = UINT2NUM(dts.year);
|
19
|
+
VALUE month = UINT2NUM(dts.month);
|
20
|
+
VALUE day = UINT2NUM(dts.day);
|
21
|
+
VALUE hour = UINT2NUM(dts.hour);
|
22
|
+
VALUE min = UINT2NUM(dts.min);
|
23
|
+
VALUE sec = UINT2NUM(dts.sec);
|
24
|
+
VALUE millis = UINT2NUM(dts.milliSec);
|
25
|
+
VALUE cDate = rb_const_get(rb_cObject, rb_intern("Time"));
|
26
|
+
VALUE rb_date = rb_funcall(cDate, rb_intern("gm"), 7, year, month, day, hour, min, sec, millis);
|
27
|
+
return rb_date;
|
28
|
+
}
|
29
|
+
|
30
|
+
static void
|
31
|
+
handler_dataChanged(UA_Client *client, UA_UInt32 subId, void *subContext,
|
32
|
+
UA_UInt32 monId, void *monContext, UA_DataValue *value) {
|
33
|
+
|
34
|
+
struct OpcuaClientContext *ctx = UA_Client_getContext(client);
|
35
|
+
VALUE self = ctx->rubyClientInstance;
|
36
|
+
VALUE callback = rb_ivar_get(self, rb_intern("@callback_after_data_changed"));
|
37
|
+
|
38
|
+
if (NIL_P(callback)) {
|
39
|
+
return;
|
40
|
+
}
|
41
|
+
|
42
|
+
VALUE v_serverTime = Qnil;
|
43
|
+
if (value->hasServerTimestamp) {
|
44
|
+
v_serverTime = toRubyTime(value->serverTimestamp);
|
45
|
+
}
|
46
|
+
|
47
|
+
VALUE v_sourceTime = Qnil;
|
48
|
+
if (value->hasSourceTimestamp) {
|
49
|
+
v_sourceTime = toRubyTime(value->sourceTimestamp);
|
50
|
+
}
|
51
|
+
|
52
|
+
VALUE params = rb_ary_new();
|
53
|
+
rb_ary_push(params, UINT2NUM(subId));
|
54
|
+
rb_ary_push(params, UINT2NUM(monId));
|
55
|
+
rb_ary_push(params, v_serverTime);
|
56
|
+
rb_ary_push(params, v_sourceTime);
|
57
|
+
|
58
|
+
VALUE v_newValue = Qnil;
|
59
|
+
|
60
|
+
if(UA_Variant_hasScalarType(&value->value, &UA_TYPES[UA_TYPES_DATETIME])) {
|
61
|
+
UA_DateTime raw_date = *(UA_DateTime *) value->value.data;
|
62
|
+
v_newValue = toRubyTime(raw_date);
|
63
|
+
} else if (UA_Variant_hasScalarType(&value->value, &UA_TYPES[UA_TYPES_INT32])) {
|
64
|
+
UA_Int32 number = *(UA_Int32 *) value->value.data;
|
65
|
+
v_newValue = INT2NUM(number);
|
66
|
+
} else if (UA_Variant_hasScalarType(&value->value, &UA_TYPES[UA_TYPES_INT16])) {
|
67
|
+
UA_Int16 number = *(UA_Int16 *) value->value.data;
|
68
|
+
v_newValue = INT2NUM(number);
|
69
|
+
} else if (UA_Variant_hasScalarType(&value->value, &UA_TYPES[UA_TYPES_BOOLEAN])) {
|
70
|
+
UA_Boolean b = *(UA_Boolean *) value->value.data;
|
71
|
+
v_newValue = b ? Qtrue : Qfalse;
|
72
|
+
} else if (UA_Variant_hasScalarType(&value->value, &UA_TYPES[UA_TYPES_FLOAT])) {
|
73
|
+
UA_Float dbl = *(UA_Float *) value->value.data;
|
74
|
+
v_newValue = DBL2NUM(dbl);
|
75
|
+
}
|
76
|
+
|
77
|
+
rb_ary_push(params, v_newValue);
|
78
|
+
rb_proc_call(callback, params);
|
79
|
+
}
|
80
|
+
|
81
|
+
static void
|
82
|
+
deleteSubscriptionCallback(UA_Client *client, UA_UInt32 subscriptionId, void *subscriptionContext) {
|
83
|
+
// printf("Subscription Id %u was deleted\n", subscriptionId);
|
84
|
+
}
|
85
|
+
|
86
|
+
static void
|
87
|
+
subscriptionInactivityCallback(UA_Client *client, UA_UInt32 subscriptionId, void *subContext) {
|
88
|
+
// printf("Inactivity for subscription %u", subscriptionId);
|
89
|
+
}
|
90
|
+
|
91
|
+
static void
|
92
|
+
stateCallback (UA_Client *client, UA_ClientState clientState) {
|
93
|
+
struct OpcuaClientContext *ctx = UA_Client_getContext(client);
|
94
|
+
|
95
|
+
switch(clientState) {
|
96
|
+
case UA_CLIENTSTATE_DISCONNECTED:
|
97
|
+
; // printf("%s\n", "The client is disconnected");
|
98
|
+
break;
|
99
|
+
case UA_CLIENTSTATE_CONNECTED:
|
100
|
+
; // printf("%s\n", "A TCP connection to the server is open");
|
101
|
+
break;
|
102
|
+
case UA_CLIENTSTATE_SECURECHANNEL:
|
103
|
+
; // printf("%s\n", "A SecureChannel to the server is open");
|
104
|
+
break;
|
105
|
+
case UA_CLIENTSTATE_SESSION:
|
106
|
+
; // printf("%s\n", "A new session was created!");
|
107
|
+
VALUE self = ctx->rubyClientInstance;
|
108
|
+
|
109
|
+
VALUE callback = rb_ivar_get(self, rb_intern("@callback_after_session_created"));
|
110
|
+
if (!NIL_P(callback)) {
|
111
|
+
VALUE params = rb_ary_new();
|
112
|
+
rb_ary_push(params, self);
|
113
|
+
rb_proc_call(callback, params); // rescue?
|
114
|
+
}
|
115
|
+
|
116
|
+
break;
|
117
|
+
case UA_CLIENTSTATE_SESSION_RENEWED:
|
118
|
+
/* The session was renewed. We don't need to recreate the subscription. */
|
119
|
+
break;
|
120
|
+
}
|
121
|
+
return;
|
122
|
+
}
|
123
|
+
|
124
|
+
static VALUE raise_invalid_arguments_error() {
|
125
|
+
rb_raise(cError, "Invalid arguments");
|
126
|
+
return Qnil;
|
127
|
+
}
|
128
|
+
|
129
|
+
static VALUE raise_ua_status_error(UA_StatusCode status) {
|
130
|
+
rb_raise(cError, "%u: %s", status, UA_StatusCode_name(status));
|
131
|
+
return Qnil;
|
132
|
+
}
|
133
|
+
|
134
|
+
static void UA_Client_free(void *self) {
|
135
|
+
// printf("free client\n");
|
136
|
+
struct UninitializedClient *uclient = self;
|
137
|
+
|
138
|
+
if (uclient->client) {
|
139
|
+
struct OpcuaClientContext *ctx = UA_Client_getContext(uclient->client);
|
140
|
+
xfree(ctx);
|
141
|
+
UA_Client_delete(uclient->client);
|
142
|
+
}
|
143
|
+
|
144
|
+
xfree(self);
|
145
|
+
}
|
146
|
+
|
147
|
+
static const rb_data_type_t UA_Client_Type = {
|
148
|
+
"UA_Uninitialized_Client",
|
149
|
+
{ 0, UA_Client_free, 0 },
|
150
|
+
0, 0, RUBY_TYPED_FREE_IMMEDIATELY,
|
151
|
+
};
|
152
|
+
|
153
|
+
static VALUE allocate(VALUE klass) {
|
154
|
+
// printf("allocate client\n");
|
155
|
+
struct UninitializedClient *uclient = ALLOC(struct UninitializedClient);
|
156
|
+
*uclient = (const struct UninitializedClient){ 0 };
|
157
|
+
|
158
|
+
return TypedData_Wrap_Struct(klass, &UA_Client_Type, uclient);
|
159
|
+
}
|
160
|
+
|
161
|
+
static VALUE rb_initialize(VALUE self) {
|
162
|
+
struct UninitializedClient * uclient;
|
163
|
+
TypedData_Get_Struct(self, struct UninitializedClient, &UA_Client_Type, uclient);
|
164
|
+
|
165
|
+
UA_ClientConfig customConfig = UA_ClientConfig_default;
|
166
|
+
customConfig.stateCallback = stateCallback;
|
167
|
+
customConfig.subscriptionInactivityCallback = subscriptionInactivityCallback;
|
168
|
+
|
169
|
+
struct OpcuaClientContext *ctx = ALLOC(struct OpcuaClientContext);
|
170
|
+
*ctx = (const struct OpcuaClientContext){ 0 };
|
171
|
+
|
172
|
+
ctx->rubyClientInstance = self;
|
173
|
+
customConfig.clientContext = ctx;
|
174
|
+
|
175
|
+
uclient->client = UA_Client_new(customConfig);
|
176
|
+
|
177
|
+
return Qnil;
|
178
|
+
}
|
179
|
+
|
180
|
+
static VALUE rb_connect(VALUE self, VALUE v_connectionString) {
|
181
|
+
if (RB_TYPE_P(v_connectionString, T_STRING) != 1) {
|
182
|
+
return raise_invalid_arguments_error();
|
183
|
+
}
|
184
|
+
|
185
|
+
char *connectionString = StringValueCStr(v_connectionString);
|
186
|
+
|
187
|
+
struct UninitializedClient * uclient;
|
188
|
+
TypedData_Get_Struct(self, struct UninitializedClient, &UA_Client_Type, uclient);
|
189
|
+
UA_Client *client = uclient->client;
|
190
|
+
|
191
|
+
UA_StatusCode status = UA_Client_connect(client, connectionString);
|
192
|
+
|
193
|
+
if (status == UA_STATUSCODE_GOOD) {
|
194
|
+
return Qnil;
|
195
|
+
} else {
|
196
|
+
return raise_ua_status_error(status);
|
197
|
+
}
|
198
|
+
}
|
199
|
+
|
200
|
+
static VALUE rb_createSubscription(VALUE self) {
|
201
|
+
struct UninitializedClient * uclient;
|
202
|
+
TypedData_Get_Struct(self, struct UninitializedClient, &UA_Client_Type, uclient);
|
203
|
+
UA_Client *client = uclient->client;
|
204
|
+
|
205
|
+
UA_CreateSubscriptionRequest request = UA_CreateSubscriptionRequest_default();
|
206
|
+
UA_CreateSubscriptionResponse response = UA_Client_Subscriptions_create(client, request, NULL, NULL, deleteSubscriptionCallback);
|
207
|
+
|
208
|
+
if (response.responseHeader.serviceResult == UA_STATUSCODE_GOOD) {
|
209
|
+
UA_UInt32 subscriptionId = response.subscriptionId;
|
210
|
+
return UINT2NUM(subscriptionId);
|
211
|
+
} else {
|
212
|
+
return Qnil;
|
213
|
+
}
|
214
|
+
}
|
215
|
+
|
216
|
+
static VALUE rb_addMonitoredItem(VALUE self, VALUE v_subscriptionId, VALUE v_monNsIndex, VALUE v_monNsName) {
|
217
|
+
struct UninitializedClient * uclient;
|
218
|
+
TypedData_Get_Struct(self, struct UninitializedClient, &UA_Client_Type, uclient);
|
219
|
+
UA_Client *client = uclient->client;
|
220
|
+
|
221
|
+
UA_UInt32 subscriptionId = NUM2UINT(v_subscriptionId); // TODO: check type
|
222
|
+
UA_UInt16 monNsIndex = NUM2USHORT(v_monNsIndex); // TODO: check type
|
223
|
+
char* monNsName = StringValueCStr(v_monNsName); // TODO: check type
|
224
|
+
|
225
|
+
UA_MonitoredItemCreateRequest monRequest = UA_MonitoredItemCreateRequest_default(UA_NODEID_STRING(monNsIndex, monNsName));
|
226
|
+
|
227
|
+
UA_MonitoredItemCreateResult monResponse =
|
228
|
+
UA_Client_MonitoredItems_createDataChange(client, subscriptionId,
|
229
|
+
UA_TIMESTAMPSTORETURN_BOTH,
|
230
|
+
monRequest, NULL, handler_dataChanged, NULL);
|
231
|
+
if (monResponse.statusCode == UA_STATUSCODE_GOOD) {
|
232
|
+
// printf("Request to monitor field %hu:%s successful, id %u\n", monNsIndex, monNsName, monResponse.monitoredItemId);
|
233
|
+
UA_UInt32 monitoredItemId = monResponse.monitoredItemId;
|
234
|
+
return UINT2NUM(monitoredItemId);
|
235
|
+
} else {
|
236
|
+
// printf("Request to monitor field failed: %s\n", UA_StatusCode_name(monResponse.statusCode));
|
237
|
+
return Qnil;
|
238
|
+
}
|
239
|
+
}
|
240
|
+
|
241
|
+
static VALUE rb_disconnect(VALUE self) {
|
242
|
+
struct UninitializedClient * uclient;
|
243
|
+
TypedData_Get_Struct(self, struct UninitializedClient, &UA_Client_Type, uclient);
|
244
|
+
UA_Client *client = uclient->client;
|
245
|
+
|
246
|
+
UA_StatusCode status = UA_Client_disconnect(client);
|
247
|
+
return RB_UINT2NUM(status);
|
248
|
+
}
|
249
|
+
|
250
|
+
static VALUE rb_writeUaValue(VALUE self, VALUE v_nsIndex, VALUE v_name, VALUE v_newValue, int uaType) {
|
251
|
+
if (RB_TYPE_P(v_name, T_STRING) != 1) {
|
252
|
+
return raise_invalid_arguments_error();
|
253
|
+
}
|
254
|
+
|
255
|
+
if (RB_TYPE_P(v_nsIndex, T_FIXNUM) != 1) {
|
256
|
+
return raise_invalid_arguments_error();
|
257
|
+
}
|
258
|
+
|
259
|
+
if (uaType == UA_TYPES_INT16 && RB_TYPE_P(v_newValue, T_FIXNUM) != 1) {
|
260
|
+
return raise_invalid_arguments_error();
|
261
|
+
}
|
262
|
+
|
263
|
+
char *name = StringValueCStr(v_name);
|
264
|
+
int nsIndex = FIX2INT(v_nsIndex);
|
265
|
+
|
266
|
+
struct UninitializedClient * uclient;
|
267
|
+
TypedData_Get_Struct(self, struct UninitializedClient, &UA_Client_Type, uclient);
|
268
|
+
UA_Client *client = uclient->client;
|
269
|
+
|
270
|
+
UA_Variant value;
|
271
|
+
UA_Variant_init(&value);
|
272
|
+
|
273
|
+
if (uaType == UA_TYPES_INT16) {
|
274
|
+
UA_Int16 newValue = NUM2SHORT(v_newValue);
|
275
|
+
value.data = UA_malloc(sizeof(UA_Int16));
|
276
|
+
*(UA_Int16*)value.data = newValue;
|
277
|
+
value.type = &UA_TYPES[UA_TYPES_INT16];
|
278
|
+
} else if (uaType == UA_TYPES_INT32) {
|
279
|
+
UA_Int32 newValue = NUM2INT(v_newValue);
|
280
|
+
value.data = UA_malloc(sizeof(UA_Int32));
|
281
|
+
*(UA_Int32*)value.data = newValue;
|
282
|
+
value.type = &UA_TYPES[UA_TYPES_INT32];
|
283
|
+
} else if (uaType == UA_TYPES_FLOAT) {
|
284
|
+
UA_Float newValue = NUM2DBL(v_newValue);
|
285
|
+
value.data = UA_malloc(sizeof(UA_Float));
|
286
|
+
*(UA_Float*)value.data = newValue;
|
287
|
+
value.type = &UA_TYPES[UA_TYPES_FLOAT];
|
288
|
+
} else if (uaType == UA_TYPES_BOOLEAN) {
|
289
|
+
UA_Boolean newValue = RTEST(v_newValue);
|
290
|
+
value.data = UA_malloc(sizeof(UA_Boolean));
|
291
|
+
*(UA_Boolean*)value.data = newValue;
|
292
|
+
value.type = &UA_TYPES[UA_TYPES_BOOLEAN];
|
293
|
+
} else {
|
294
|
+
rb_raise(cError, "Unsupported type");
|
295
|
+
}
|
296
|
+
|
297
|
+
UA_StatusCode status = UA_Client_writeValueAttribute(client, UA_NODEID_STRING(nsIndex, name), &value);
|
298
|
+
|
299
|
+
if (status == UA_STATUSCODE_GOOD) {
|
300
|
+
// printf("%s\n", "value write successful");
|
301
|
+
} else {
|
302
|
+
/* Clean up */
|
303
|
+
UA_Variant_deleteMembers(&value);
|
304
|
+
return raise_ua_status_error(status);
|
305
|
+
}
|
306
|
+
|
307
|
+
/* Clean up */
|
308
|
+
UA_Variant_deleteMembers(&value);
|
309
|
+
|
310
|
+
return Qnil;
|
311
|
+
}
|
312
|
+
|
313
|
+
static VALUE rb_writeInt16Value(VALUE self, VALUE v_nsIndex, VALUE v_name, VALUE v_newValue) {
|
314
|
+
return rb_writeUaValue(self, v_nsIndex, v_name, v_newValue, UA_TYPES_INT16);
|
315
|
+
}
|
316
|
+
|
317
|
+
static VALUE rb_writeInt32Value(VALUE self, VALUE v_nsIndex, VALUE v_name, VALUE v_newValue) {
|
318
|
+
return rb_writeUaValue(self, v_nsIndex, v_name, v_newValue, UA_TYPES_INT32);
|
319
|
+
}
|
320
|
+
|
321
|
+
static VALUE rb_writeBooleanValue(VALUE self, VALUE v_nsIndex, VALUE v_name, VALUE v_newValue) {
|
322
|
+
return rb_writeUaValue(self, v_nsIndex, v_name, v_newValue, UA_TYPES_BOOLEAN);
|
323
|
+
}
|
324
|
+
|
325
|
+
static VALUE rb_writeFloatValue(VALUE self, VALUE v_nsIndex, VALUE v_name, VALUE v_newValue) {
|
326
|
+
return rb_writeUaValue(self, v_nsIndex, v_name, v_newValue, UA_TYPES_FLOAT);
|
327
|
+
}
|
328
|
+
|
329
|
+
static VALUE rb_readUaValue(VALUE self, VALUE v_nsIndex, VALUE v_name, int type) {
|
330
|
+
if (RB_TYPE_P(v_name, T_STRING) != 1) {
|
331
|
+
return raise_invalid_arguments_error();
|
332
|
+
}
|
333
|
+
|
334
|
+
if (RB_TYPE_P(v_nsIndex, T_FIXNUM) != 1) {
|
335
|
+
return raise_invalid_arguments_error();
|
336
|
+
}
|
337
|
+
|
338
|
+
char *name = StringValueCStr(v_name);
|
339
|
+
int nsIndex = FIX2INT(v_nsIndex);
|
340
|
+
|
341
|
+
struct UninitializedClient * uclient;
|
342
|
+
TypedData_Get_Struct(self, struct UninitializedClient, &UA_Client_Type, uclient);
|
343
|
+
UA_Client *client = uclient->client;
|
344
|
+
|
345
|
+
UA_Variant value;
|
346
|
+
UA_Variant_init(&value);
|
347
|
+
UA_StatusCode status = UA_Client_readValueAttribute(client, UA_NODEID_STRING(nsIndex, name), &value);
|
348
|
+
|
349
|
+
if (status == UA_STATUSCODE_GOOD) {
|
350
|
+
// printf("%s\n", "value read successful");
|
351
|
+
} else {
|
352
|
+
/* Clean up */
|
353
|
+
UA_Variant_deleteMembers(&value);
|
354
|
+
return raise_ua_status_error(status);
|
355
|
+
}
|
356
|
+
|
357
|
+
VALUE result = Qnil;
|
358
|
+
|
359
|
+
if (type == UA_TYPES_INT16 && UA_Variant_hasScalarType(&value, &UA_TYPES[UA_TYPES_INT16])) {
|
360
|
+
UA_Int16 val =*(UA_Int16*)value.data;
|
361
|
+
// printf("the value is: %i\n", val);
|
362
|
+
result = INT2FIX(val);
|
363
|
+
} else if (type == UA_TYPES_INT32 && UA_Variant_hasScalarType(&value, &UA_TYPES[UA_TYPES_INT32])) {
|
364
|
+
UA_Int32 val =*(UA_Int32*)value.data;
|
365
|
+
result = INT2FIX(val);
|
366
|
+
} else if (type == UA_TYPES_BOOLEAN && UA_Variant_hasScalarType(&value, &UA_TYPES[UA_TYPES_BOOLEAN])) {
|
367
|
+
UA_Boolean val =*(UA_Boolean*)value.data;
|
368
|
+
result = val ? Qtrue : Qfalse;
|
369
|
+
} else if (type == UA_TYPES_FLOAT && UA_Variant_hasScalarType(&value, &UA_TYPES[UA_TYPES_FLOAT])) {
|
370
|
+
UA_Float val =*(UA_Float*)value.data;
|
371
|
+
result = DBL2NUM(val);
|
372
|
+
} else {
|
373
|
+
rb_raise(cError, "Not an int16");
|
374
|
+
return Qnil;
|
375
|
+
}
|
376
|
+
|
377
|
+
/* Clean up */
|
378
|
+
UA_Variant_deleteMembers(&value);
|
379
|
+
|
380
|
+
return result;
|
381
|
+
}
|
382
|
+
|
383
|
+
static VALUE rb_readInt16Value(VALUE self, VALUE v_nsIndex, VALUE v_name) {
|
384
|
+
return rb_readUaValue(self, v_nsIndex, v_name, UA_TYPES_INT16);
|
385
|
+
}
|
386
|
+
|
387
|
+
static VALUE rb_readInt32Value(VALUE self, VALUE v_nsIndex, VALUE v_name) {
|
388
|
+
return rb_readUaValue(self, v_nsIndex, v_name, UA_TYPES_INT32);
|
389
|
+
}
|
390
|
+
|
391
|
+
static VALUE rb_readBooleanValue(VALUE self, VALUE v_nsIndex, VALUE v_name) {
|
392
|
+
return rb_readUaValue(self, v_nsIndex, v_name, UA_TYPES_BOOLEAN);
|
393
|
+
}
|
394
|
+
|
395
|
+
static VALUE rb_readFloatValue(VALUE self, VALUE v_nsIndex, VALUE v_name) {
|
396
|
+
return rb_readUaValue(self, v_nsIndex, v_name, UA_TYPES_FLOAT);
|
397
|
+
}
|
398
|
+
|
399
|
+
static VALUE rb_get_human_UA_StatusCode(VALUE self, VALUE v_code) {
|
400
|
+
if (RB_TYPE_P(v_code, T_FIXNUM) == 1) {
|
401
|
+
unsigned int code = FIX2UINT(v_code);
|
402
|
+
const char* name = UA_StatusCode_name(code);
|
403
|
+
return rb_str_export_locale(rb_str_new_cstr(name));
|
404
|
+
} else {
|
405
|
+
return raise_invalid_arguments_error();
|
406
|
+
}
|
407
|
+
}
|
408
|
+
|
409
|
+
static VALUE rb_run_single_monitoring_cycle(VALUE self) {
|
410
|
+
struct UninitializedClient * uclient;
|
411
|
+
TypedData_Get_Struct(self, struct UninitializedClient, &UA_Client_Type, uclient);
|
412
|
+
UA_Client *client = uclient->client;
|
413
|
+
|
414
|
+
UA_StatusCode status = UA_Client_runAsync(client, 1000);
|
415
|
+
return UINT2NUM(status);
|
416
|
+
}
|
417
|
+
|
418
|
+
static VALUE rb_run_single_monitoring_cycle_bang(VALUE self) {
|
419
|
+
struct UninitializedClient * uclient;
|
420
|
+
TypedData_Get_Struct(self, struct UninitializedClient, &UA_Client_Type, uclient);
|
421
|
+
UA_Client *client = uclient->client;
|
422
|
+
|
423
|
+
UA_StatusCode status = UA_Client_runAsync(client, 1000);
|
424
|
+
|
425
|
+
if (status != UA_STATUSCODE_GOOD) {
|
426
|
+
return raise_ua_status_error(status);
|
427
|
+
}
|
428
|
+
|
429
|
+
return Qnil;
|
430
|
+
}
|
431
|
+
|
432
|
+
static VALUE rb_state(VALUE self) {
|
433
|
+
struct UninitializedClient * uclient;
|
434
|
+
TypedData_Get_Struct(self, struct UninitializedClient, &UA_Client_Type, uclient);
|
435
|
+
UA_Client *client = uclient->client;
|
436
|
+
|
437
|
+
UA_ClientState state = UA_Client_getState(client);
|
438
|
+
return INT2NUM(state);
|
439
|
+
}
|
440
|
+
|
441
|
+
static void defineStateContants(VALUE mOPCUAClient) {
|
442
|
+
rb_define_const(mOPCUAClient, "UA_CLIENTSTATE_DISCONNECTED", INT2NUM(UA_CLIENTSTATE_DISCONNECTED));
|
443
|
+
rb_define_const(mOPCUAClient, "UA_CLIENTSTATE_CONNECTED", INT2NUM(UA_CLIENTSTATE_CONNECTED));
|
444
|
+
rb_define_const(mOPCUAClient, "UA_CLIENTSTATE_SECURECHANNEL", INT2NUM(UA_CLIENTSTATE_SECURECHANNEL));
|
445
|
+
rb_define_const(mOPCUAClient, "UA_CLIENTSTATE_SESSION", INT2NUM(UA_CLIENTSTATE_SESSION));
|
446
|
+
rb_define_const(mOPCUAClient, "UA_CLIENTSTATE_SESSION_RENEWED", INT2NUM(UA_CLIENTSTATE_SESSION_RENEWED));
|
447
|
+
}
|
448
|
+
|
449
|
+
void Init_opcua_client()
|
450
|
+
{
|
451
|
+
#ifdef UA_ENABLE_SUBSCRIPTIONS
|
452
|
+
// printf("%s\n", "ok! opcua-client-ruby built with subscriptions enabled.");
|
453
|
+
#endif
|
454
|
+
|
455
|
+
mOPCUAClient = rb_const_get(rb_cObject, rb_intern("OPCUAClient"));
|
456
|
+
defineStateContants(mOPCUAClient);
|
457
|
+
|
458
|
+
cError = rb_define_class_under(mOPCUAClient, "Error", rb_eStandardError);
|
459
|
+
cClient = rb_define_class_under(mOPCUAClient, "Client", rb_cObject);
|
460
|
+
|
461
|
+
rb_define_alloc_func(cClient, allocate);
|
462
|
+
|
463
|
+
rb_define_method(cClient, "initialize", rb_initialize, 0);
|
464
|
+
|
465
|
+
rb_define_method(cClient, "run_single_monitoring_cycle", rb_run_single_monitoring_cycle, 0);
|
466
|
+
rb_define_method(cClient, "run_mon_cycle", rb_run_single_monitoring_cycle, 0);
|
467
|
+
rb_define_method(cClient, "do_mon_cycle", rb_run_single_monitoring_cycle, 0);
|
468
|
+
|
469
|
+
rb_define_method(cClient, "run_single_monitoring_cycle!", rb_run_single_monitoring_cycle_bang, 0);
|
470
|
+
rb_define_method(cClient, "run_mon_cycle!", rb_run_single_monitoring_cycle_bang, 0);
|
471
|
+
rb_define_method(cClient, "do_mon_cycle!", rb_run_single_monitoring_cycle_bang, 0);
|
472
|
+
|
473
|
+
rb_define_method(cClient, "connect", rb_connect, 1);
|
474
|
+
rb_define_method(cClient, "disconnect", rb_disconnect, 0);
|
475
|
+
rb_define_method(cClient, "state", rb_state, 0);
|
476
|
+
|
477
|
+
rb_define_method(cClient, "read_int16", rb_readInt16Value, 2);
|
478
|
+
rb_define_method(cClient, "read_int32", rb_readInt32Value, 2);
|
479
|
+
rb_define_method(cClient, "read_float", rb_readFloatValue, 2);
|
480
|
+
rb_define_method(cClient, "read_boolean", rb_readBooleanValue, 2);
|
481
|
+
rb_define_method(cClient, "read_bool", rb_readBooleanValue, 2);
|
482
|
+
|
483
|
+
rb_define_method(cClient, "write_int16", rb_writeInt16Value, 3);
|
484
|
+
rb_define_method(cClient, "write_int32", rb_writeInt32Value, 3);
|
485
|
+
rb_define_method(cClient, "write_float", rb_writeFloatValue, 3);
|
486
|
+
rb_define_method(cClient, "write_boolean", rb_writeBooleanValue, 3);
|
487
|
+
rb_define_method(cClient, "write_bool", rb_writeBooleanValue, 3);
|
488
|
+
|
489
|
+
rb_define_method(cClient, "create_subscription", rb_createSubscription, 0);
|
490
|
+
rb_define_method(cClient, "add_monitored_item", rb_addMonitoredItem, 3);
|
491
|
+
|
492
|
+
rb_define_singleton_method(mOPCUAClient, "human_status_code", rb_get_human_UA_StatusCode, 1);
|
493
|
+
}
|