rclnodejs 1.8.0 → 1.8.2
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.
- package/lib/lifecycle.js +2 -1
- package/lib/native_loader.js +11 -1
- package/lib/parameter.js +1 -1
- package/lib/time.js +2 -2
- package/lib/time_source.js +1 -1
- package/lib/utils.js +1 -1
- package/package.json +5 -5
- package/prebuilds/linux-arm64/humble-jammy-arm64-rclnodejs.node +0 -0
- package/prebuilds/linux-arm64/jazzy-noble-arm64-rclnodejs.node +0 -0
- package/prebuilds/linux-arm64/kilted-noble-arm64-rclnodejs.node +0 -0
- package/prebuilds/linux-x64/humble-jammy-x64-rclnodejs.node +0 -0
- package/prebuilds/linux-x64/jazzy-noble-x64-rclnodejs.node +0 -0
- package/prebuilds/linux-x64/kilted-noble-x64-rclnodejs.node +0 -0
- package/rosidl_gen/packages.js +7 -2
- package/src/rcl_bindings.cpp +1 -1
- package/src/rcl_lifecycle_bindings.cpp +24 -1
- package/src/rcl_subscription_bindings.cpp +99 -36
- package/test_data_integrity.js +108 -0
- package/test_repro_exact.js +57 -0
- package/test_repro_hz.js +86 -0
- package/test_repro_pub.js +36 -0
- package/test_repro_stress.js +83 -0
- package/test_repro_sub.js +64 -0
- package/test_xproc_data.js +64 -0
package/lib/lifecycle.js
CHANGED
|
@@ -291,7 +291,8 @@ class LifecycleNode extends Node {
|
|
|
291
291
|
// initialize native handle to rcl_lifecycle_state_machine
|
|
292
292
|
this._stateMachineHandle = rclnodejs.createLifecycleStateMachine(
|
|
293
293
|
this.handle,
|
|
294
|
-
enableCommunicationInterface
|
|
294
|
+
enableCommunicationInterface,
|
|
295
|
+
this._clock.handle
|
|
295
296
|
);
|
|
296
297
|
|
|
297
298
|
// initialize Map<transitionId,TransitionCallback>
|
package/lib/native_loader.js
CHANGED
|
@@ -175,4 +175,14 @@ function loadNativeAddon() {
|
|
|
175
175
|
}
|
|
176
176
|
}
|
|
177
177
|
|
|
178
|
-
|
|
178
|
+
const addon = loadNativeAddon();
|
|
179
|
+
|
|
180
|
+
// Export internal functions for testing purposes
|
|
181
|
+
if (process.env.NODE_ENV === 'test') {
|
|
182
|
+
addon.TestHelpers = {
|
|
183
|
+
customFallbackLoader,
|
|
184
|
+
loadNativeAddon,
|
|
185
|
+
};
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
module.exports = addon;
|
package/lib/parameter.js
CHANGED
package/lib/time.js
CHANGED
|
@@ -352,8 +352,8 @@ class Time {
|
|
|
352
352
|
toMsg() {
|
|
353
353
|
const secondsAndNanoseconds = this.secondsAndNanoseconds;
|
|
354
354
|
return {
|
|
355
|
-
sec: secondsAndNanoseconds.seconds,
|
|
356
|
-
nanosec: secondsAndNanoseconds.nanoseconds,
|
|
355
|
+
sec: Number(secondsAndNanoseconds.seconds),
|
|
356
|
+
nanosec: Number(secondsAndNanoseconds.nanoseconds),
|
|
357
357
|
};
|
|
358
358
|
}
|
|
359
359
|
|
package/lib/time_source.js
CHANGED
|
@@ -102,7 +102,7 @@ class TimeSource {
|
|
|
102
102
|
* @return {undefined}
|
|
103
103
|
*/
|
|
104
104
|
attachNode(node) {
|
|
105
|
-
if ((
|
|
105
|
+
if (!(node instanceof rclnodejs.ShadowNode)) {
|
|
106
106
|
throw new TypeValidationError('node', node, 'Node', {
|
|
107
107
|
entityType: 'time source',
|
|
108
108
|
});
|
package/lib/utils.js
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "rclnodejs",
|
|
3
|
-
"version": "1.8.
|
|
3
|
+
"version": "1.8.2",
|
|
4
4
|
"description": "ROS2.0 JavaScript client with Node.js",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"types": "types/index.d.ts",
|
|
@@ -25,7 +25,7 @@
|
|
|
25
25
|
"install": "node scripts/install.js",
|
|
26
26
|
"postinstall": "npm run generate-messages",
|
|
27
27
|
"docs": "cd docs && make",
|
|
28
|
-
"test": "nyc node --expose-gc ./scripts/run_test.js && tsd",
|
|
28
|
+
"test": "nyc node --expose-gc ./scripts/run_test.js && tsd && npm install --no-save electron && node test/electron/run_test.js",
|
|
29
29
|
"test-idl": "nyc node --expose-gc ./scripts/run_test.js --idl",
|
|
30
30
|
"lint": "eslint && node ./scripts/cpplint.js",
|
|
31
31
|
"format": "clang-format -i -style=file ./src/*.cpp ./src/*.h && npx --yes prettier --write \"{lib,rosidl_gen,rostsd_gen,rosidl_parser,types,example,test,scripts,benchmark,rostsd_gen}/**/*.{js,md,ts}\" ./*.{js,md,ts}",
|
|
@@ -48,7 +48,7 @@
|
|
|
48
48
|
"url": "git+https://github.com/RobotWebTools/rclnodejs.git"
|
|
49
49
|
},
|
|
50
50
|
"devDependencies": {
|
|
51
|
-
"@eslint/js": "^
|
|
51
|
+
"@eslint/js": "^10.0.1",
|
|
52
52
|
"@types/node": "^25.0.2",
|
|
53
53
|
"@typescript-eslint/eslint-plugin": "^8.18.0",
|
|
54
54
|
"@typescript-eslint/parser": "^8.18.0",
|
|
@@ -56,10 +56,10 @@
|
|
|
56
56
|
"commander": "^14.0.0",
|
|
57
57
|
"coveralls": "^3.1.1",
|
|
58
58
|
"deep-equal": "^2.2.3",
|
|
59
|
-
"eslint": "^
|
|
59
|
+
"eslint": "^10.0.2",
|
|
60
60
|
"eslint-config-prettier": "^10.0.2",
|
|
61
61
|
"eslint-plugin-prettier": "^5.2.1",
|
|
62
|
-
"globals": "^
|
|
62
|
+
"globals": "^17.0.0",
|
|
63
63
|
"husky": "^9.1.7",
|
|
64
64
|
"jsdoc": "^4.0.4",
|
|
65
65
|
"lint-staged": "^16.2.0",
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
package/rosidl_gen/packages.js
CHANGED
|
@@ -52,7 +52,12 @@ function getSubFolder(filePath, amentExecuted) {
|
|
|
52
52
|
}
|
|
53
53
|
|
|
54
54
|
if (amentExecuted) {
|
|
55
|
-
|
|
55
|
+
const match = filePath.match(/\w+\/share\/\w+\/([\w-]+)\//);
|
|
56
|
+
if (match) {
|
|
57
|
+
// Handle non-standard subfolder names (e.g., msg-common, msg-ros2)
|
|
58
|
+
// by extracting only the base interface type before any hyphen.
|
|
59
|
+
return match[1].split('-')[0];
|
|
60
|
+
}
|
|
56
61
|
}
|
|
57
62
|
// If the |amentExecuted| equals to false, the file's extension will be assigned as
|
|
58
63
|
// the name of sub folder.
|
|
@@ -223,7 +228,7 @@ async function findPackagesInDirectory(dir, useIDL) {
|
|
|
223
228
|
|
|
224
229
|
// If there is a folder named 'share' under the root path, we consider that
|
|
225
230
|
// the ament build tool has been executed and |amentExecuted| will be true.
|
|
226
|
-
fs.
|
|
231
|
+
fs.stat(path.join(dir, 'share'), (err) => {
|
|
227
232
|
if (err) {
|
|
228
233
|
amentExecuted = false;
|
|
229
234
|
}
|
package/src/rcl_bindings.cpp
CHANGED
|
@@ -71,7 +71,30 @@ Napi::Value CreateLifecycleStateMachine(const Napi::CallbackInfo& info) {
|
|
|
71
71
|
const rosidl_service_type_support_t* gs =
|
|
72
72
|
GetServiceTypeSupport("lifecycle_msgs", "GetState");
|
|
73
73
|
|
|
74
|
-
#if ROS_VERSION >=
|
|
74
|
+
#if ROS_VERSION >= 5000 // ROS2 Rolling
|
|
75
|
+
rcl_lifecycle_state_machine_options_t options =
|
|
76
|
+
rcl_lifecycle_get_default_state_machine_options();
|
|
77
|
+
options.enable_com_interface = info[1].As<Napi::Boolean>().Value();
|
|
78
|
+
|
|
79
|
+
RclHandle* clock_handle = RclHandle::Unwrap(info[2].As<Napi::Object>());
|
|
80
|
+
rcl_clock_t* clock = reinterpret_cast<rcl_clock_t*>(clock_handle->ptr());
|
|
81
|
+
|
|
82
|
+
THROW_ERROR_IF_NOT_EQUAL(
|
|
83
|
+
RCL_RET_OK,
|
|
84
|
+
rcl_lifecycle_state_machine_init(state_machine, node, clock, pn, cs, gs,
|
|
85
|
+
gas, gat, gtg, &options),
|
|
86
|
+
rcl_get_error_string().str);
|
|
87
|
+
|
|
88
|
+
auto js_obj = RclHandle::NewInstance(
|
|
89
|
+
env, state_machine, node_handle, [node, env](void* ptr) {
|
|
90
|
+
rcl_lifecycle_state_machine_t* state_machine =
|
|
91
|
+
reinterpret_cast<rcl_lifecycle_state_machine_t*>(ptr);
|
|
92
|
+
rcl_ret_t ret = rcl_lifecycle_state_machine_fini(state_machine, node);
|
|
93
|
+
free(ptr);
|
|
94
|
+
THROW_ERROR_IF_NOT_EQUAL_NO_RETURN(RCL_RET_OK, ret,
|
|
95
|
+
rcl_get_error_string().str);
|
|
96
|
+
});
|
|
97
|
+
#elif ROS_VERSION >= 2105
|
|
75
98
|
rcl_lifecycle_state_machine_options_t options =
|
|
76
99
|
rcl_lifecycle_get_default_state_machine_options();
|
|
77
100
|
options.enable_com_interface = info[1].As<Napi::Boolean>().Value();
|
|
@@ -38,9 +38,9 @@ Napi::Value RclTake(const Napi::CallbackInfo& info) {
|
|
|
38
38
|
rcl_ret_t ret = rcl_take(subscription, msg_taken, nullptr, nullptr);
|
|
39
39
|
|
|
40
40
|
if (ret != RCL_RET_OK && ret != RCL_RET_SUBSCRIPTION_TAKE_FAILED) {
|
|
41
|
+
std::string error_string = rcl_get_error_string().str;
|
|
41
42
|
rcl_reset_error();
|
|
42
|
-
Napi::Error::New(env,
|
|
43
|
-
.ThrowAsJavaScriptException();
|
|
43
|
+
Napi::Error::New(env, error_string).ThrowAsJavaScriptException();
|
|
44
44
|
return Napi::Boolean::New(env, false);
|
|
45
45
|
}
|
|
46
46
|
|
|
@@ -99,7 +99,7 @@ Napi::Value CreateSubscription(const Napi::CallbackInfo& info) {
|
|
|
99
99
|
for (int i = 0; i < argc; i++) {
|
|
100
100
|
std::string arg = jsArgv.Get(i).As<Napi::String>().Utf8Value();
|
|
101
101
|
int len = arg.length() + 1;
|
|
102
|
-
argv[i] = reinterpret_cast<char*>(malloc(len * sizeof(char
|
|
102
|
+
argv[i] = reinterpret_cast<char*>(malloc(len * sizeof(char)));
|
|
103
103
|
snprintf(argv[i], len, "%s", arg.c_str());
|
|
104
104
|
}
|
|
105
105
|
}
|
|
@@ -109,9 +109,9 @@ Napi::Value CreateSubscription(const Napi::CallbackInfo& info) {
|
|
|
109
109
|
expression.c_str(), argc, (const char**)argv, &subscription_ops);
|
|
110
110
|
|
|
111
111
|
if (ret != RCL_RET_OK) {
|
|
112
|
+
std::string error_string = rcl_get_error_string().str;
|
|
112
113
|
rcl_reset_error();
|
|
113
|
-
Napi::Error::New(env,
|
|
114
|
-
.ThrowAsJavaScriptException();
|
|
114
|
+
Napi::Error::New(env, error_string).ThrowAsJavaScriptException();
|
|
115
115
|
}
|
|
116
116
|
|
|
117
117
|
if (argc) {
|
|
@@ -120,6 +120,11 @@ Napi::Value CreateSubscription(const Napi::CallbackInfo& info) {
|
|
|
120
120
|
}
|
|
121
121
|
free(argv);
|
|
122
122
|
}
|
|
123
|
+
|
|
124
|
+
if (ret != RCL_RET_OK) {
|
|
125
|
+
free(subscription);
|
|
126
|
+
return env.Undefined();
|
|
127
|
+
}
|
|
123
128
|
}
|
|
124
129
|
}
|
|
125
130
|
|
|
@@ -127,11 +132,15 @@ Napi::Value CreateSubscription(const Napi::CallbackInfo& info) {
|
|
|
127
132
|
GetMessageTypeSupport(package_name, message_sub_folder, message_name);
|
|
128
133
|
|
|
129
134
|
if (ts) {
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
+
rcl_ret_t ret = rcl_subscription_init(subscription, node, ts, topic.c_str(),
|
|
136
|
+
&subscription_ops);
|
|
137
|
+
if (ret != RCL_RET_OK) {
|
|
138
|
+
std::string error_msg = rcl_get_error_string().str;
|
|
139
|
+
rcl_reset_error();
|
|
140
|
+
Napi::Error::New(env, error_msg).ThrowAsJavaScriptException();
|
|
141
|
+
free(subscription);
|
|
142
|
+
return env.Undefined();
|
|
143
|
+
}
|
|
135
144
|
|
|
136
145
|
auto js_obj = RclHandle::NewInstance(
|
|
137
146
|
env, subscription, node_handle, [node, env](void* ptr) {
|
|
@@ -139,14 +148,18 @@ Napi::Value CreateSubscription(const Napi::CallbackInfo& info) {
|
|
|
139
148
|
reinterpret_cast<rcl_subscription_t*>(ptr);
|
|
140
149
|
rcl_ret_t ret = rcl_subscription_fini(subscription, node);
|
|
141
150
|
free(ptr);
|
|
142
|
-
|
|
143
|
-
|
|
151
|
+
if (ret != RCL_RET_OK) {
|
|
152
|
+
std::string error_msg = rcl_get_error_string().str;
|
|
153
|
+
rcl_reset_error();
|
|
154
|
+
Napi::Error::New(env, error_msg).ThrowAsJavaScriptException();
|
|
155
|
+
}
|
|
144
156
|
});
|
|
145
157
|
|
|
146
158
|
return js_obj;
|
|
147
159
|
} else {
|
|
148
160
|
Napi::Error::New(env, GetErrorMessageAndClear())
|
|
149
161
|
.ThrowAsJavaScriptException();
|
|
162
|
+
free(subscription);
|
|
150
163
|
return env.Undefined();
|
|
151
164
|
}
|
|
152
165
|
}
|
|
@@ -235,7 +248,7 @@ Napi::Value SetContentFilter(const Napi::CallbackInfo& info) {
|
|
|
235
248
|
for (int i = 0; i < argc; i++) {
|
|
236
249
|
std::string arg = jsArgv.Get(i).As<Napi::String>().Utf8Value();
|
|
237
250
|
int len = arg.length() + 1;
|
|
238
|
-
argv[i] = reinterpret_cast<char*>(malloc(len * sizeof(char
|
|
251
|
+
argv[i] = reinterpret_cast<char*>(malloc(len * sizeof(char)));
|
|
239
252
|
snprintf(argv[i], len, "%s", arg.c_str());
|
|
240
253
|
}
|
|
241
254
|
}
|
|
@@ -245,15 +258,23 @@ Napi::Value SetContentFilter(const Napi::CallbackInfo& info) {
|
|
|
245
258
|
rcl_subscription_content_filter_options_t options =
|
|
246
259
|
rcl_get_zero_initialized_subscription_content_filter_options();
|
|
247
260
|
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
261
|
+
rcl_ret_t ret = rcl_subscription_content_filter_options_set(
|
|
262
|
+
subscription, expression.c_str(), argc, (const char**)argv, &options);
|
|
263
|
+
|
|
264
|
+
if (ret != RCL_RET_OK) {
|
|
265
|
+
if (argc) {
|
|
266
|
+
for (int i = 0; i < argc; i++) {
|
|
267
|
+
free(argv[i]);
|
|
268
|
+
}
|
|
269
|
+
free(argv);
|
|
270
|
+
}
|
|
271
|
+
std::string error_string = rcl_get_error_string().str;
|
|
272
|
+
rcl_reset_error();
|
|
273
|
+
Napi::Error::New(env, error_string).ThrowAsJavaScriptException();
|
|
274
|
+
return env.Undefined();
|
|
275
|
+
}
|
|
253
276
|
|
|
254
|
-
|
|
255
|
-
RCL_RET_OK, rcl_subscription_set_content_filter(subscription, &options),
|
|
256
|
-
rcl_get_error_string().str);
|
|
277
|
+
ret = rcl_subscription_set_content_filter(subscription, &options);
|
|
257
278
|
|
|
258
279
|
if (argc) {
|
|
259
280
|
for (int i = 0; i < argc; i++) {
|
|
@@ -262,6 +283,27 @@ Napi::Value SetContentFilter(const Napi::CallbackInfo& info) {
|
|
|
262
283
|
free(argv);
|
|
263
284
|
}
|
|
264
285
|
|
|
286
|
+
std::string error_string = "";
|
|
287
|
+
if (ret != RCL_RET_OK) {
|
|
288
|
+
error_string = rcl_get_error_string().str;
|
|
289
|
+
rcl_reset_error();
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
rcl_ret_t fini_ret =
|
|
293
|
+
rcl_subscription_content_filter_options_fini(subscription, &options);
|
|
294
|
+
|
|
295
|
+
if (ret != RCL_RET_OK) {
|
|
296
|
+
Napi::Error::New(env, error_string).ThrowAsJavaScriptException();
|
|
297
|
+
return env.Undefined();
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
if (fini_ret != RCL_RET_OK) {
|
|
301
|
+
error_string = rcl_get_error_string().str;
|
|
302
|
+
rcl_reset_error();
|
|
303
|
+
Napi::Error::New(env, error_string).ThrowAsJavaScriptException();
|
|
304
|
+
return env.Undefined();
|
|
305
|
+
}
|
|
306
|
+
|
|
265
307
|
return Napi::Boolean::New(env, true);
|
|
266
308
|
}
|
|
267
309
|
|
|
@@ -277,15 +319,33 @@ Napi::Value ClearContentFilter(const Napi::CallbackInfo& info) {
|
|
|
277
319
|
rcl_subscription_content_filter_options_t options =
|
|
278
320
|
rcl_get_zero_initialized_subscription_content_filter_options();
|
|
279
321
|
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
322
|
+
rcl_ret_t ret = rcl_subscription_content_filter_options_init(
|
|
323
|
+
subscription, "", 0, (const char**)nullptr, &options);
|
|
324
|
+
|
|
325
|
+
if (ret != RCL_RET_OK) {
|
|
326
|
+
std::string error_string = rcl_get_error_string().str;
|
|
327
|
+
rcl_reset_error();
|
|
328
|
+
Napi::Error::New(env, error_string).ThrowAsJavaScriptException();
|
|
329
|
+
return env.Undefined();
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
ret = rcl_subscription_set_content_filter(subscription, &options);
|
|
333
|
+
rcl_ret_t fini_ret =
|
|
334
|
+
rcl_subscription_content_filter_options_fini(subscription, &options);
|
|
335
|
+
|
|
336
|
+
if (ret != RCL_RET_OK) {
|
|
337
|
+
std::string error_string = rcl_get_error_string().str;
|
|
338
|
+
rcl_reset_error();
|
|
339
|
+
Napi::Error::New(env, error_string).ThrowAsJavaScriptException();
|
|
340
|
+
return env.Undefined();
|
|
341
|
+
}
|
|
285
342
|
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
343
|
+
if (fini_ret != RCL_RET_OK) {
|
|
344
|
+
std::string error_string = rcl_get_error_string().str;
|
|
345
|
+
rcl_reset_error();
|
|
346
|
+
Napi::Error::New(env, error_string).ThrowAsJavaScriptException();
|
|
347
|
+
return env.Undefined();
|
|
348
|
+
}
|
|
289
349
|
|
|
290
350
|
return Napi::Boolean::New(env, true);
|
|
291
351
|
}
|
|
@@ -303,9 +363,9 @@ Napi::Value GetContentFilter(const Napi::CallbackInfo& info) {
|
|
|
303
363
|
|
|
304
364
|
rcl_ret_t ret = rcl_subscription_get_content_filter(subscription, &options);
|
|
305
365
|
if (ret != RCL_RET_OK) {
|
|
306
|
-
|
|
307
|
-
.ThrowAsJavaScriptException();
|
|
366
|
+
std::string error_msg = rcl_get_error_string().str;
|
|
308
367
|
rcl_reset_error();
|
|
368
|
+
Napi::Error::New(env, error_msg).ThrowAsJavaScriptException();
|
|
309
369
|
return env.Undefined();
|
|
310
370
|
}
|
|
311
371
|
|
|
@@ -331,9 +391,9 @@ Napi::Value GetContentFilter(const Napi::CallbackInfo& info) {
|
|
|
331
391
|
rcl_ret_t fini_ret =
|
|
332
392
|
rcl_subscription_content_filter_options_fini(subscription, &options);
|
|
333
393
|
if (fini_ret != RCL_RET_OK) {
|
|
334
|
-
|
|
335
|
-
.ThrowAsJavaScriptException();
|
|
394
|
+
std::string error_msg = rcl_get_error_string().str;
|
|
336
395
|
rcl_reset_error();
|
|
396
|
+
Napi::Error::New(env, error_msg).ThrowAsJavaScriptException();
|
|
337
397
|
return env.Undefined();
|
|
338
398
|
}
|
|
339
399
|
|
|
@@ -347,9 +407,12 @@ Napi::Value GetPublisherCount(const Napi::CallbackInfo& info) {
|
|
|
347
407
|
RclHandle::Unwrap(info[0].As<Napi::Object>())->ptr());
|
|
348
408
|
|
|
349
409
|
size_t count = 0;
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
410
|
+
rcl_ret_t ret = rcl_subscription_get_publisher_count(subscription, &count);
|
|
411
|
+
if (ret != RCL_RET_OK) {
|
|
412
|
+
std::string error_msg = rcl_get_error_string().str;
|
|
413
|
+
rcl_reset_error();
|
|
414
|
+
Napi::Error::New(env, error_msg).ThrowAsJavaScriptException();
|
|
415
|
+
}
|
|
353
416
|
|
|
354
417
|
return Napi::Number::New(env, count);
|
|
355
418
|
}
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
// Data integrity + throughput test for subscription
|
|
2
|
+
'use strict';
|
|
3
|
+
|
|
4
|
+
const rclnodejs = require('./index.js');
|
|
5
|
+
|
|
6
|
+
const PUBLISH_HZ = 50;
|
|
7
|
+
const TEST_DURATION_SEC = 10;
|
|
8
|
+
|
|
9
|
+
async function main() {
|
|
10
|
+
await rclnodejs.init();
|
|
11
|
+
|
|
12
|
+
const pubNode = new rclnodejs.Node('data_pub_node');
|
|
13
|
+
const subNode = new rclnodejs.Node('data_sub_node');
|
|
14
|
+
|
|
15
|
+
const publisher = pubNode.createPublisher(
|
|
16
|
+
'std_msgs/msg/Float64MultiArray',
|
|
17
|
+
'/data_integrity_topic'
|
|
18
|
+
);
|
|
19
|
+
|
|
20
|
+
let msgCount = 0;
|
|
21
|
+
let errCount = 0;
|
|
22
|
+
let lastTs = null;
|
|
23
|
+
const hzSamples = [];
|
|
24
|
+
const errors = [];
|
|
25
|
+
|
|
26
|
+
subNode.createSubscription(
|
|
27
|
+
'std_msgs/msg/Float64MultiArray',
|
|
28
|
+
'/data_integrity_topic',
|
|
29
|
+
(msg) => {
|
|
30
|
+
const now = Date.now();
|
|
31
|
+
msgCount++;
|
|
32
|
+
|
|
33
|
+
// --- Data validation ---
|
|
34
|
+
// Each published message has data = [seqNo, seqNo*1.5, seqNo*2.5]
|
|
35
|
+
// Verify structure and values
|
|
36
|
+
if (!msg || !msg.data) {
|
|
37
|
+
errCount++;
|
|
38
|
+
errors.push(`msg#${msgCount}: missing data field, got: ${JSON.stringify(msg)}`);
|
|
39
|
+
} else if (!Array.isArray(msg.data) && !(msg.data instanceof Float64Array)) {
|
|
40
|
+
errCount++;
|
|
41
|
+
errors.push(`msg#${msgCount}: data is not array-like, type=${typeof msg.data}`);
|
|
42
|
+
} else if (msg.data.length !== 3) {
|
|
43
|
+
errCount++;
|
|
44
|
+
errors.push(`msg#${msgCount}: expected 3 elements, got ${msg.data.length}`);
|
|
45
|
+
} else {
|
|
46
|
+
const seqNo = msg.data[0];
|
|
47
|
+
const expectedB = seqNo * 1.5;
|
|
48
|
+
const expectedC = seqNo * 2.5;
|
|
49
|
+
|
|
50
|
+
if (Math.abs(msg.data[1] - expectedB) > 1e-9) {
|
|
51
|
+
errCount++;
|
|
52
|
+
errors.push(
|
|
53
|
+
`msg#${msgCount}: data[1] expected ${expectedB}, got ${msg.data[1]}`
|
|
54
|
+
);
|
|
55
|
+
}
|
|
56
|
+
if (Math.abs(msg.data[2] - expectedC) > 1e-9) {
|
|
57
|
+
errCount++;
|
|
58
|
+
errors.push(
|
|
59
|
+
`msg#${msgCount}: data[2] expected ${expectedC}, got ${msg.data[2]}`
|
|
60
|
+
);
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
if (lastTs) {
|
|
65
|
+
hzSamples.push(1000 / (now - lastTs));
|
|
66
|
+
}
|
|
67
|
+
lastTs = now;
|
|
68
|
+
}
|
|
69
|
+
);
|
|
70
|
+
|
|
71
|
+
pubNode.spin();
|
|
72
|
+
subNode.spin();
|
|
73
|
+
|
|
74
|
+
let pubSeq = 0;
|
|
75
|
+
const pubInterval = setInterval(() => {
|
|
76
|
+
pubSeq++;
|
|
77
|
+
publisher.publish({ data: [pubSeq, pubSeq * 1.5, pubSeq * 2.5] });
|
|
78
|
+
}, 1000 / PUBLISH_HZ);
|
|
79
|
+
|
|
80
|
+
setTimeout(() => {
|
|
81
|
+
clearInterval(pubInterval);
|
|
82
|
+
|
|
83
|
+
console.log(`\n=== Data Integrity Test Results ===`);
|
|
84
|
+
console.log(`Published: ${pubSeq} messages at ${PUBLISH_HZ} Hz`);
|
|
85
|
+
console.log(`Received: ${msgCount} messages`);
|
|
86
|
+
console.log(`Data errors: ${errCount}`);
|
|
87
|
+
|
|
88
|
+
if (errors.length > 0) {
|
|
89
|
+
console.log(`\nFirst 10 errors:`);
|
|
90
|
+
errors.slice(0, 10).forEach((e) => console.log(` ${e}`));
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
if (hzSamples.length > 0) {
|
|
94
|
+
const avgHz = hzSamples.reduce((a, b) => a + b, 0) / hzSamples.length;
|
|
95
|
+
console.log(`\nAvg Hz: ${avgHz.toFixed(2)}`);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
const pass = errCount === 0 && msgCount > 0;
|
|
99
|
+
console.log(`\nResult: ${pass ? 'PASS - all data correct' : 'FAIL'}`);
|
|
100
|
+
|
|
101
|
+
pubNode.stop();
|
|
102
|
+
subNode.stop();
|
|
103
|
+
rclnodejs.shutdown();
|
|
104
|
+
process.exit(pass ? 0 : 1);
|
|
105
|
+
}, TEST_DURATION_SEC * 1000);
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
main().catch(console.error);
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
// Thorough latency/throughput test matching the exact issue scenario
|
|
2
|
+
'use strict';
|
|
3
|
+
|
|
4
|
+
const rclnodejs = require('./index.js');
|
|
5
|
+
|
|
6
|
+
async function main() {
|
|
7
|
+
await rclnodejs.init();
|
|
8
|
+
|
|
9
|
+
const node = new rclnodejs.Node('test_node');
|
|
10
|
+
|
|
11
|
+
let lastTs;
|
|
12
|
+
let msgCount = 0;
|
|
13
|
+
const hzSamples = [];
|
|
14
|
+
|
|
15
|
+
node.createSubscription(
|
|
16
|
+
'std_msgs/msg/Float64MultiArray',
|
|
17
|
+
'/map_to_base_link_pose2d',
|
|
18
|
+
(msg) => {
|
|
19
|
+
const now = Date.now();
|
|
20
|
+
msgCount++;
|
|
21
|
+
if (lastTs) {
|
|
22
|
+
const hz = 1000 / (now - lastTs);
|
|
23
|
+
hzSamples.push(hz);
|
|
24
|
+
console.log('Raw Hz:', hz.toFixed(2));
|
|
25
|
+
}
|
|
26
|
+
lastTs = now;
|
|
27
|
+
}
|
|
28
|
+
);
|
|
29
|
+
|
|
30
|
+
rclnodejs.spin(node);
|
|
31
|
+
|
|
32
|
+
console.log('Waiting for messages on /map_to_base_link_pose2d at ~10Hz...');
|
|
33
|
+
console.log('Run this in another terminal:');
|
|
34
|
+
console.log(
|
|
35
|
+
' ros2 topic pub -r 10 /map_to_base_link_pose2d std_msgs/msg/Float64MultiArray "{data: [1.0, 2.0, 3.0]}"'
|
|
36
|
+
);
|
|
37
|
+
|
|
38
|
+
setTimeout(() => {
|
|
39
|
+
if (hzSamples.length > 0) {
|
|
40
|
+
const avgHz = hzSamples.reduce((a, b) => a + b, 0) / hzSamples.length;
|
|
41
|
+
console.log(`\n--- Summary ---`);
|
|
42
|
+
console.log(`Messages: ${msgCount}, Avg Hz: ${avgHz.toFixed(2)}`);
|
|
43
|
+
if (avgHz < 5) {
|
|
44
|
+
console.log('*** REGRESSION DETECTED ***');
|
|
45
|
+
} else {
|
|
46
|
+
console.log('Performance OK');
|
|
47
|
+
}
|
|
48
|
+
} else {
|
|
49
|
+
console.log('No messages received');
|
|
50
|
+
}
|
|
51
|
+
node.stop();
|
|
52
|
+
rclnodejs.shutdown();
|
|
53
|
+
process.exit(0);
|
|
54
|
+
}, 15000);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
main().catch(console.error);
|
package/test_repro_hz.js
ADDED
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
// Reprocer for https://github.com/RobotWebTools/rclnodejs/issues/1394
|
|
2
|
+
// Tests subscription throughput at ~10Hz publishing rate
|
|
3
|
+
'use strict';
|
|
4
|
+
|
|
5
|
+
const rclnodejs = require('./index.js');
|
|
6
|
+
|
|
7
|
+
const PUBLISH_HZ = 10;
|
|
8
|
+
const TEST_DURATION_SEC = 10;
|
|
9
|
+
|
|
10
|
+
async function main() {
|
|
11
|
+
await rclnodejs.init();
|
|
12
|
+
|
|
13
|
+
const pubNode = new rclnodejs.Node('test_pub_node');
|
|
14
|
+
const subNode = new rclnodejs.Node('test_sub_node');
|
|
15
|
+
|
|
16
|
+
const publisher = pubNode.createPublisher(
|
|
17
|
+
'std_msgs/msg/Float64MultiArray',
|
|
18
|
+
'/test_hz_topic'
|
|
19
|
+
);
|
|
20
|
+
|
|
21
|
+
let msgCount = 0;
|
|
22
|
+
let lastTs = null;
|
|
23
|
+
const hzSamples = [];
|
|
24
|
+
|
|
25
|
+
subNode.createSubscription(
|
|
26
|
+
'std_msgs/msg/Float64MultiArray',
|
|
27
|
+
'/test_hz_topic',
|
|
28
|
+
(msg) => {
|
|
29
|
+
const now = Date.now();
|
|
30
|
+
msgCount++;
|
|
31
|
+
if (lastTs) {
|
|
32
|
+
const hz = 1000 / (now - lastTs);
|
|
33
|
+
hzSamples.push(hz);
|
|
34
|
+
}
|
|
35
|
+
lastTs = now;
|
|
36
|
+
}
|
|
37
|
+
);
|
|
38
|
+
|
|
39
|
+
pubNode.spin();
|
|
40
|
+
subNode.spin();
|
|
41
|
+
|
|
42
|
+
// Publish at target Hz
|
|
43
|
+
let pubCount = 0;
|
|
44
|
+
const pubInterval = setInterval(() => {
|
|
45
|
+
publisher.publish({ data: [1.0, 2.0, 3.0] });
|
|
46
|
+
pubCount++;
|
|
47
|
+
}, 1000 / PUBLISH_HZ);
|
|
48
|
+
|
|
49
|
+
// Wait for test duration then report
|
|
50
|
+
setTimeout(() => {
|
|
51
|
+
clearInterval(pubInterval);
|
|
52
|
+
|
|
53
|
+
if (hzSamples.length > 0) {
|
|
54
|
+
const avgHz =
|
|
55
|
+
hzSamples.reduce((a, b) => a + b, 0) / hzSamples.length;
|
|
56
|
+
const minHz = Math.min(...hzSamples);
|
|
57
|
+
const maxHz = Math.max(...hzSamples);
|
|
58
|
+
|
|
59
|
+
console.log(`Published: ${pubCount} messages`);
|
|
60
|
+
console.log(`Received: ${msgCount} messages`);
|
|
61
|
+
console.log(`Avg Hz: ${avgHz.toFixed(2)}`);
|
|
62
|
+
console.log(`Min Hz: ${minHz.toFixed(2)}`);
|
|
63
|
+
console.log(`Max Hz: ${maxHz.toFixed(2)}`);
|
|
64
|
+
console.log(
|
|
65
|
+
`Expected: ~${PUBLISH_HZ} Hz`
|
|
66
|
+
);
|
|
67
|
+
|
|
68
|
+
if (avgHz < PUBLISH_HZ * 0.5) {
|
|
69
|
+
console.log(
|
|
70
|
+
`\n*** REGRESSION DETECTED: Average Hz (${avgHz.toFixed(2)}) is less than 50% of expected (${PUBLISH_HZ}) ***`
|
|
71
|
+
);
|
|
72
|
+
} else {
|
|
73
|
+
console.log('\nPerformance looks OK.');
|
|
74
|
+
}
|
|
75
|
+
} else {
|
|
76
|
+
console.log('No messages received!');
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
pubNode.stop();
|
|
80
|
+
subNode.stop();
|
|
81
|
+
rclnodejs.shutdown();
|
|
82
|
+
process.exit(0);
|
|
83
|
+
}, TEST_DURATION_SEC * 1000);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
main().catch(console.error);
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
// Publisher for repro test - runs in separate process
|
|
2
|
+
'use strict';
|
|
3
|
+
|
|
4
|
+
const rclnodejs = require('./index.js');
|
|
5
|
+
|
|
6
|
+
const PUBLISH_HZ = parseInt(process.argv[2] || '100');
|
|
7
|
+
|
|
8
|
+
async function main() {
|
|
9
|
+
await rclnodejs.init();
|
|
10
|
+
|
|
11
|
+
const node = new rclnodejs.Node('test_publisher_node');
|
|
12
|
+
const publisher = node.createPublisher(
|
|
13
|
+
'std_msgs/msg/Float64MultiArray',
|
|
14
|
+
'/test_hz_topic'
|
|
15
|
+
);
|
|
16
|
+
|
|
17
|
+
node.spin();
|
|
18
|
+
|
|
19
|
+
let pubCount = 0;
|
|
20
|
+
console.log(`Publishing at ${PUBLISH_HZ} Hz...`);
|
|
21
|
+
|
|
22
|
+
const pubInterval = setInterval(() => {
|
|
23
|
+
publisher.publish({ data: [1.0, 2.0, 3.0] });
|
|
24
|
+
pubCount++;
|
|
25
|
+
}, 1000 / PUBLISH_HZ);
|
|
26
|
+
|
|
27
|
+
setTimeout(() => {
|
|
28
|
+
clearInterval(pubInterval);
|
|
29
|
+
console.log(`Published ${pubCount} messages total`);
|
|
30
|
+
node.stop();
|
|
31
|
+
rclnodejs.shutdown();
|
|
32
|
+
process.exit(0);
|
|
33
|
+
}, 15000);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
main().catch(console.error);
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
// Multi-frequency stress test for subscription performance
|
|
2
|
+
'use strict';
|
|
3
|
+
|
|
4
|
+
const rclnodejs = require('./index.js');
|
|
5
|
+
|
|
6
|
+
const PUBLISH_HZ = parseInt(process.argv[2] || '100');
|
|
7
|
+
const TEST_DURATION_SEC = 10;
|
|
8
|
+
|
|
9
|
+
async function main() {
|
|
10
|
+
await rclnodejs.init();
|
|
11
|
+
|
|
12
|
+
const pubNode = new rclnodejs.Node('stress_pub_node');
|
|
13
|
+
const subNode = new rclnodejs.Node('stress_sub_node');
|
|
14
|
+
|
|
15
|
+
const publisher = pubNode.createPublisher(
|
|
16
|
+
'std_msgs/msg/Float64MultiArray',
|
|
17
|
+
'/stress_test_topic'
|
|
18
|
+
);
|
|
19
|
+
|
|
20
|
+
let msgCount = 0;
|
|
21
|
+
let lastTs = null;
|
|
22
|
+
const hzSamples = [];
|
|
23
|
+
|
|
24
|
+
subNode.createSubscription(
|
|
25
|
+
'std_msgs/msg/Float64MultiArray',
|
|
26
|
+
'/stress_test_topic',
|
|
27
|
+
(msg) => {
|
|
28
|
+
const now = Date.now();
|
|
29
|
+
msgCount++;
|
|
30
|
+
if (lastTs) {
|
|
31
|
+
const hz = 1000 / (now - lastTs);
|
|
32
|
+
hzSamples.push(hz);
|
|
33
|
+
}
|
|
34
|
+
lastTs = now;
|
|
35
|
+
}
|
|
36
|
+
);
|
|
37
|
+
|
|
38
|
+
pubNode.spin();
|
|
39
|
+
subNode.spin();
|
|
40
|
+
|
|
41
|
+
let pubCount = 0;
|
|
42
|
+
|
|
43
|
+
// Use high-resolution timer for more precise publishing
|
|
44
|
+
const intervalMs = 1000 / PUBLISH_HZ;
|
|
45
|
+
const pubInterval = setInterval(() => {
|
|
46
|
+
publisher.publish({ data: [1.0, 2.0, 3.0, 4.0, 5.0] });
|
|
47
|
+
pubCount++;
|
|
48
|
+
}, intervalMs);
|
|
49
|
+
|
|
50
|
+
setTimeout(() => {
|
|
51
|
+
clearInterval(pubInterval);
|
|
52
|
+
|
|
53
|
+
if (hzSamples.length > 0) {
|
|
54
|
+
const avgHz = hzSamples.reduce((a, b) => a + b, 0) / hzSamples.length;
|
|
55
|
+
const minHz = Math.min(...hzSamples);
|
|
56
|
+
const maxHz = Math.max(...hzSamples);
|
|
57
|
+
const dropRate = ((pubCount - msgCount) / pubCount * 100);
|
|
58
|
+
|
|
59
|
+
console.log(`Target: ${PUBLISH_HZ} Hz`);
|
|
60
|
+
console.log(`Published: ${pubCount}`);
|
|
61
|
+
console.log(`Received: ${msgCount}`);
|
|
62
|
+
console.log(`Avg Hz: ${avgHz.toFixed(2)}`);
|
|
63
|
+
console.log(`Min Hz: ${minHz.toFixed(2)}`);
|
|
64
|
+
console.log(`Max Hz: ${maxHz.toFixed(2)}`);
|
|
65
|
+
console.log(`Drop rate: ${dropRate.toFixed(1)}%`);
|
|
66
|
+
|
|
67
|
+
if (avgHz < PUBLISH_HZ * 0.5) {
|
|
68
|
+
console.log(`FAIL: Avg Hz significantly below target`);
|
|
69
|
+
} else {
|
|
70
|
+
console.log(`PASS`);
|
|
71
|
+
}
|
|
72
|
+
} else {
|
|
73
|
+
console.log('No messages received!');
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
pubNode.stop();
|
|
77
|
+
subNode.stop();
|
|
78
|
+
rclnodejs.shutdown();
|
|
79
|
+
process.exit(0);
|
|
80
|
+
}, TEST_DURATION_SEC * 1000);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
main().catch(console.error);
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
// Subscriber for repro test - runs in separate process
|
|
2
|
+
'use strict';
|
|
3
|
+
|
|
4
|
+
const rclnodejs = require('./index.js');
|
|
5
|
+
|
|
6
|
+
async function main() {
|
|
7
|
+
await rclnodejs.init();
|
|
8
|
+
|
|
9
|
+
const node = new rclnodejs.Node('test_subscriber_node');
|
|
10
|
+
|
|
11
|
+
let msgCount = 0;
|
|
12
|
+
let lastTs = null;
|
|
13
|
+
const hzSamples = [];
|
|
14
|
+
let startTime = null;
|
|
15
|
+
|
|
16
|
+
node.createSubscription(
|
|
17
|
+
'std_msgs/msg/Float64MultiArray',
|
|
18
|
+
'/test_hz_topic',
|
|
19
|
+
(msg) => {
|
|
20
|
+
const now = Date.now();
|
|
21
|
+
msgCount++;
|
|
22
|
+
if (!startTime) startTime = now;
|
|
23
|
+
if (lastTs) {
|
|
24
|
+
const hz = 1000 / (now - lastTs);
|
|
25
|
+
hzSamples.push(hz);
|
|
26
|
+
if (msgCount % 50 === 0) {
|
|
27
|
+
const recentSamples = hzSamples.slice(-50);
|
|
28
|
+
const recentAvg =
|
|
29
|
+
recentSamples.reduce((a, b) => a + b, 0) / recentSamples.length;
|
|
30
|
+
console.log(
|
|
31
|
+
`msg#${msgCount} recent avg Hz: ${recentAvg.toFixed(2)}`
|
|
32
|
+
);
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
lastTs = now;
|
|
36
|
+
}
|
|
37
|
+
);
|
|
38
|
+
|
|
39
|
+
node.spin();
|
|
40
|
+
|
|
41
|
+
setTimeout(() => {
|
|
42
|
+
if (hzSamples.length > 0) {
|
|
43
|
+
const avgHz =
|
|
44
|
+
hzSamples.reduce((a, b) => a + b, 0) / hzSamples.length;
|
|
45
|
+
const minHz = Math.min(...hzSamples);
|
|
46
|
+
const maxHz = Math.max(...hzSamples);
|
|
47
|
+
const elapsed = (Date.now() - startTime) / 1000;
|
|
48
|
+
|
|
49
|
+
console.log(`\n--- Results ---`);
|
|
50
|
+
console.log(`Received: ${msgCount} messages in ${elapsed.toFixed(1)}s`);
|
|
51
|
+
console.log(`Avg Hz: ${avgHz.toFixed(2)}`);
|
|
52
|
+
console.log(`Min Hz: ${minHz.toFixed(2)}`);
|
|
53
|
+
console.log(`Max Hz: ${maxHz.toFixed(2)}`);
|
|
54
|
+
} else {
|
|
55
|
+
console.log('No messages received!');
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
node.stop();
|
|
59
|
+
rclnodejs.shutdown();
|
|
60
|
+
process.exit(0);
|
|
61
|
+
}, 12000);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
main().catch(console.error);
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
// Cross-process data integrity test: validates data from external ROS2 publisher
|
|
2
|
+
'use strict';
|
|
3
|
+
|
|
4
|
+
const rclnodejs = require('./index.js');
|
|
5
|
+
|
|
6
|
+
const EXPECTED_DATA = [1.0, 2.0, 3.0];
|
|
7
|
+
|
|
8
|
+
async function main() {
|
|
9
|
+
await rclnodejs.init();
|
|
10
|
+
|
|
11
|
+
const node = new rclnodejs.Node('xproc_data_sub');
|
|
12
|
+
let msgCount = 0;
|
|
13
|
+
let errCount = 0;
|
|
14
|
+
const errors = [];
|
|
15
|
+
|
|
16
|
+
node.createSubscription(
|
|
17
|
+
'std_msgs/msg/Float64MultiArray',
|
|
18
|
+
'/xproc_data_topic',
|
|
19
|
+
(msg) => {
|
|
20
|
+
msgCount++;
|
|
21
|
+
|
|
22
|
+
if (!msg || !msg.data) {
|
|
23
|
+
errCount++;
|
|
24
|
+
errors.push(`msg#${msgCount}: missing data`);
|
|
25
|
+
} else {
|
|
26
|
+
const arr = Array.from(msg.data);
|
|
27
|
+
for (let i = 0; i < EXPECTED_DATA.length; i++) {
|
|
28
|
+
if (Math.abs(arr[i] - EXPECTED_DATA[i]) > 1e-9) {
|
|
29
|
+
errCount++;
|
|
30
|
+
errors.push(
|
|
31
|
+
`msg#${msgCount}: data[${i}] expected ${EXPECTED_DATA[i]}, got ${arr[i]}`
|
|
32
|
+
);
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
if (arr.length !== EXPECTED_DATA.length) {
|
|
36
|
+
errCount++;
|
|
37
|
+
errors.push(
|
|
38
|
+
`msg#${msgCount}: expected ${EXPECTED_DATA.length} elements, got ${arr.length}`
|
|
39
|
+
);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
);
|
|
44
|
+
|
|
45
|
+
rclnodejs.spin(node);
|
|
46
|
+
|
|
47
|
+
console.log('Listening on /xproc_data_topic for 10s...');
|
|
48
|
+
|
|
49
|
+
setTimeout(() => {
|
|
50
|
+
console.log(`\n=== Cross-Process Data Integrity ===`);
|
|
51
|
+
console.log(`Received: ${msgCount} messages`);
|
|
52
|
+
console.log(`Data errors: ${errCount}`);
|
|
53
|
+
if (errors.length > 0) {
|
|
54
|
+
errors.slice(0, 10).forEach((e) => console.log(` ${e}`));
|
|
55
|
+
}
|
|
56
|
+
const pass = errCount === 0 && msgCount > 0;
|
|
57
|
+
console.log(`Result: ${pass ? 'PASS' : 'FAIL'}`);
|
|
58
|
+
node.stop();
|
|
59
|
+
rclnodejs.shutdown();
|
|
60
|
+
process.exit(0);
|
|
61
|
+
}, 10000);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
main().catch(console.error);
|