rclnodejs 1.8.3 → 1.9.0-alpha.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (85) hide show
  1. package/README.md +46 -37
  2. package/index.js +29 -0
  3. package/lib/action/client.js +61 -3
  4. package/lib/message_info.js +94 -0
  5. package/lib/node.js +53 -3
  6. package/lib/parameter_event_handler.js +468 -0
  7. package/lib/parameter_watcher.js +12 -12
  8. package/lib/subscription.js +38 -5
  9. package/lib/timer.js +2 -1
  10. package/lib/wait_for_message.js +111 -0
  11. package/package.json +6 -3
  12. package/prebuilds/linux-arm64/humble-jammy-arm64-rclnodejs.node +0 -0
  13. package/prebuilds/linux-arm64/jazzy-noble-arm64-rclnodejs.node +0 -0
  14. package/prebuilds/linux-arm64/kilted-noble-arm64-rclnodejs.node +0 -0
  15. package/prebuilds/linux-x64/humble-jammy-x64-rclnodejs.node +0 -0
  16. package/prebuilds/linux-x64/jazzy-noble-x64-rclnodejs.node +0 -0
  17. package/prebuilds/linux-x64/kilted-noble-x64-rclnodejs.node +0 -0
  18. package/scripts/run_asan_test.sh +118 -0
  19. package/src/executor.cpp +36 -2
  20. package/src/executor.h +11 -0
  21. package/src/rcl_action_client_bindings.cpp +70 -1
  22. package/src/rcl_context_bindings.cpp +3 -3
  23. package/src/rcl_graph_bindings.cpp +2 -2
  24. package/src/rcl_subscription_bindings.cpp +70 -2
  25. package/src/rcl_utilities.cpp +2 -2
  26. package/tools/jsdoc/Makefile +5 -0
  27. package/tools/jsdoc/README.md +96 -0
  28. package/tools/jsdoc/build-index.js +610 -0
  29. package/tools/jsdoc/publish.js +854 -0
  30. package/tools/jsdoc/regenerate-published-docs.js +605 -0
  31. package/tools/jsdoc/static/fonts/OpenSans-Bold-webfont.eot +0 -0
  32. package/tools/jsdoc/static/fonts/OpenSans-Bold-webfont.svg +1830 -0
  33. package/tools/jsdoc/static/fonts/OpenSans-Bold-webfont.woff +0 -0
  34. package/tools/jsdoc/static/fonts/OpenSans-BoldItalic-webfont.eot +0 -0
  35. package/tools/jsdoc/static/fonts/OpenSans-BoldItalic-webfont.svg +1830 -0
  36. package/tools/jsdoc/static/fonts/OpenSans-BoldItalic-webfont.woff +0 -0
  37. package/tools/jsdoc/static/fonts/OpenSans-Italic-webfont.eot +0 -0
  38. package/tools/jsdoc/static/fonts/OpenSans-Italic-webfont.svg +1830 -0
  39. package/tools/jsdoc/static/fonts/OpenSans-Italic-webfont.woff +0 -0
  40. package/tools/jsdoc/static/fonts/OpenSans-Light-webfont.eot +0 -0
  41. package/tools/jsdoc/static/fonts/OpenSans-Light-webfont.svg +1831 -0
  42. package/tools/jsdoc/static/fonts/OpenSans-Light-webfont.woff +0 -0
  43. package/tools/jsdoc/static/fonts/OpenSans-LightItalic-webfont.eot +0 -0
  44. package/tools/jsdoc/static/fonts/OpenSans-LightItalic-webfont.svg +1835 -0
  45. package/tools/jsdoc/static/fonts/OpenSans-LightItalic-webfont.woff +0 -0
  46. package/tools/jsdoc/static/fonts/OpenSans-Regular-webfont.eot +0 -0
  47. package/tools/jsdoc/static/fonts/OpenSans-Regular-webfont.svg +1831 -0
  48. package/tools/jsdoc/static/fonts/OpenSans-Regular-webfont.woff +0 -0
  49. package/tools/jsdoc/static/scripts/linenumber.js +25 -0
  50. package/tools/jsdoc/static/scripts/prettify/Apache-License-2.0.txt +202 -0
  51. package/tools/jsdoc/static/scripts/prettify/lang-css.js +36 -0
  52. package/tools/jsdoc/static/scripts/prettify/prettify.js +738 -0
  53. package/tools/jsdoc/static/styles/jsdoc-default.css +1012 -0
  54. package/tools/jsdoc/static/styles/prettify-jsdoc.css +111 -0
  55. package/tools/jsdoc/static/styles/prettify-tomorrow.css +132 -0
  56. package/tools/jsdoc/tmpl/augments.tmpl +10 -0
  57. package/tools/jsdoc/tmpl/container.tmpl +193 -0
  58. package/tools/jsdoc/tmpl/details.tmpl +143 -0
  59. package/tools/jsdoc/tmpl/example.tmpl +2 -0
  60. package/tools/jsdoc/tmpl/examples.tmpl +13 -0
  61. package/tools/jsdoc/tmpl/exceptions.tmpl +17 -0
  62. package/tools/jsdoc/tmpl/layout.tmpl +83 -0
  63. package/tools/jsdoc/tmpl/mainpage.tmpl +163 -0
  64. package/tools/jsdoc/tmpl/members.tmpl +43 -0
  65. package/tools/jsdoc/tmpl/method.tmpl +124 -0
  66. package/tools/jsdoc/tmpl/params.tmpl +133 -0
  67. package/tools/jsdoc/tmpl/properties.tmpl +110 -0
  68. package/tools/jsdoc/tmpl/returns.tmpl +12 -0
  69. package/tools/jsdoc/tmpl/source.tmpl +8 -0
  70. package/tools/jsdoc/tmpl/tutorial.tmpl +19 -0
  71. package/tools/jsdoc/tmpl/type.tmpl +7 -0
  72. package/types/action_client.d.ts +8 -0
  73. package/types/index.d.ts +34 -0
  74. package/types/message_info.d.ts +72 -0
  75. package/types/node.d.ts +21 -0
  76. package/types/parameter_event_handler.d.ts +139 -0
  77. package/types/subscription.d.ts +14 -2
  78. package/test_data_integrity.js +0 -108
  79. package/test_repro_exact.js +0 -57
  80. package/test_repro_hz.js +0 -86
  81. package/test_repro_pub.js +0 -36
  82. package/test_repro_stress.js +0 -83
  83. package/test_repro_sub.js +0 -64
  84. package/test_xproc_data.js +0 -64
  85. package/types/interfaces.d.ts +0 -8895
@@ -0,0 +1,19 @@
1
+ <section class="doc-section prose-section tutorial-section">
2
+
3
+ <header class="doc-page-header">
4
+ <?js if (children.length > 0) { ?>
5
+ <ul class="tutorial-children"><?js
6
+ var self = this;
7
+ children.forEach(function(t) { ?>
8
+ <li><?js= self.tutoriallink(t.name) ?></li>
9
+ <?js }); ?></ul>
10
+ <?js } ?>
11
+
12
+ <h2 class="doc-heading"><?js= header ?></h2>
13
+ </header>
14
+
15
+ <article class="prose-card">
16
+ <?js= content ?>
17
+ </article>
18
+
19
+ </section>
@@ -0,0 +1,7 @@
1
+ <?js
2
+ var data = obj;
3
+ var self = this;
4
+ data.forEach(function(name, i) { ?>
5
+ <span class="param-type"><?js= self.linkto(name, self.htmlsafe(name)) ?></span>
6
+ <?js if (i < data.length-1) { ?>|<?js } ?>
7
+ <?js }); ?>
@@ -142,6 +142,14 @@ declare module 'rclnodejs' {
142
142
  options?: Options<ActionQoS> & {
143
143
  validateGoals?: boolean;
144
144
  validationOptions?: MessageValidationOptions;
145
+ /**
146
+ * Enable feedback subscription content filter to optimize the handling
147
+ * of feedback messages. When enabled, the content filter is used to
148
+ * configure the goal ID for the subscription, avoiding reception of
149
+ * irrelevant feedback messages. An action client can handle up to 6
150
+ * goals simultaneously with this optimization. Default: false.
151
+ */
152
+ enableFeedbackMsgOptimization?: boolean;
145
153
  }
146
154
  );
147
155
 
package/types/index.d.ts CHANGED
@@ -2,6 +2,8 @@
2
2
  /// <reference path="./clock_event.d.ts" />
3
3
  /// <reference path="./clock_change.d.ts" />
4
4
  /// <reference path="./message_validation.d.ts" />
5
+ /// <reference path="./parameter_event_handler.d.ts" />
6
+ /// <reference path="./message_info.d.ts" />
5
7
 
6
8
  import { ChildProcess } from 'child_process';
7
9
 
@@ -85,6 +87,38 @@ declare module 'rclnodejs' {
85
87
  * @deprecated since 0.18.0, Use Node.spinOnce(timeout)*/
86
88
  function spinOnce(node: Node, timeout?: number): void;
87
89
 
90
+ /**
91
+ * Options for waitForMessage.
92
+ */
93
+ interface WaitForMessageOptions {
94
+ /** Timeout in milliseconds. If omitted, waits indefinitely. */
95
+ timeout?: number;
96
+ /** QoS profile for the temporary subscription. */
97
+ qos?: QoS;
98
+ }
99
+
100
+ /**
101
+ * Wait for a single message on a topic.
102
+ *
103
+ * Creates a temporary subscription, waits for the first message to arrive,
104
+ * and returns it. The node must be spinning before calling this function.
105
+ *
106
+ * This is the rclnodejs equivalent of rclpy's `wait_for_message`.
107
+ *
108
+ * @param typeClass - The ROS message type class.
109
+ * @param node - The node to create the temporary subscription on.
110
+ * @param topic - The topic name to listen on.
111
+ * @param options - Options including timeout and QoS.
112
+ * @returns Resolves with the received message.
113
+ * @throws Error if timeout expires before a message arrives.
114
+ */
115
+ function waitForMessage<T extends TypeClass<MessageTypeClassName>>(
116
+ typeClass: T,
117
+ node: Node,
118
+ topic: string,
119
+ options?: WaitForMessageOptions
120
+ ): Promise<MessageType<T>>;
121
+
88
122
  /**
89
123
  * Stop all activity, destroy all nodes and node components.
90
124
  *
@@ -0,0 +1,72 @@
1
+ // Copyright (c) 2026, The Robot Web Tools Contributors
2
+ //
3
+ // Licensed under the Apache License, Version 2.0 (the "License");
4
+ // you may not use this file except in compliance with the License.
5
+ // You may obtain a copy of the License at
6
+ //
7
+ // http://www.apache.org/licenses/LICENSE-2.0
8
+ //
9
+ // Unless required by applicable law or agreed to in writing, software
10
+ // distributed under the License is distributed on an "AS IS" BASIS,
11
+ // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ // See the License for the specific language governing permissions and
13
+ // limitations under the License.
14
+
15
+ declare module 'rclnodejs' {
16
+ /**
17
+ * Contains metadata about a received message, including timestamps,
18
+ * sequence numbers, and the publisher's globally unique identifier (GID).
19
+ *
20
+ * Passed as the second argument to subscription callbacks when the
21
+ * callback accepts two parameters.
22
+ *
23
+ * @example
24
+ * ```typescript
25
+ * node.createSubscription(
26
+ * 'std_msgs/msg/String',
27
+ * 'topic',
28
+ * (msg: rclnodejs.std_msgs.msg.String, info: MessageInfo) => {
29
+ * console.log('Source timestamp:', info.sourceTimestamp);
30
+ * }
31
+ * );
32
+ * ```
33
+ */
34
+ class MessageInfo {
35
+ /**
36
+ * The timestamp when the message was published (nanoseconds since epoch).
37
+ */
38
+ readonly sourceTimestamp: bigint;
39
+
40
+ /**
41
+ * The timestamp when the message was received by the subscription (nanoseconds since epoch).
42
+ */
43
+ readonly receivedTimestamp: bigint;
44
+
45
+ /**
46
+ * The publication sequence number assigned by the publisher.
47
+ */
48
+ readonly publicationSequenceNumber: bigint;
49
+
50
+ /**
51
+ * The reception sequence number assigned by the subscriber.
52
+ */
53
+ readonly receptionSequenceNumber: bigint;
54
+
55
+ /**
56
+ * The globally unique identifier (GID) of the publisher.
57
+ * A Buffer containing the raw GID bytes.
58
+ */
59
+ readonly publisherGid: Buffer;
60
+
61
+ /**
62
+ * Convert to a plain object representation.
63
+ */
64
+ toPlainObject(): {
65
+ sourceTimestamp: bigint;
66
+ receivedTimestamp: bigint;
67
+ publicationSequenceNumber: bigint;
68
+ receptionSequenceNumber: bigint;
69
+ publisherGid: Buffer;
70
+ };
71
+ }
72
+ }
package/types/node.d.ts CHANGED
@@ -511,6 +511,27 @@ declare module 'rclnodejs' {
511
511
  */
512
512
  destroyParameterWatcher(watcher: ParameterWatcher): void;
513
513
 
514
+ /**
515
+ * Create a ParameterEventHandler that monitors parameter changes on any node.
516
+ *
517
+ * Unlike ParameterWatcher which watches specific parameters on a single
518
+ * remote node, ParameterEventHandler can register callbacks for parameters
519
+ * on any node in the ROS 2 graph by subscribing to /parameter_events.
520
+ *
521
+ * @param options - Options for the handler.
522
+ * @returns An instance of ParameterEventHandler.
523
+ */
524
+ createParameterEventHandler(
525
+ options?: ParameterEventHandlerOptions
526
+ ): ParameterEventHandler;
527
+
528
+ /**
529
+ * Destroy a ParameterEventHandler.
530
+ *
531
+ * @param handler - ParameterEventHandler to be destroyed.
532
+ */
533
+ destroyParameterEventHandler(handler: ParameterEventHandler): void;
534
+
514
535
  /**
515
536
  * Destroy a Timer.
516
537
  *
@@ -0,0 +1,139 @@
1
+ // Copyright (c) 2026, The Robot Web Tools Contributors
2
+ //
3
+ // Licensed under the Apache License, Version 2.0 (the "License");
4
+ // you may not use this file except in compliance with the License.
5
+ // You may obtain a copy of the License at
6
+ //
7
+ // http://www.apache.org/licenses/LICENSE-2.0
8
+ //
9
+ // Unless required by applicable law or agreed to in writing, software
10
+ // distributed under the License is distributed on an "AS IS" BASIS,
11
+ // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ // See the License for the specific language governing permissions and
13
+ // limitations under the License.
14
+
15
+ declare module 'rclnodejs' {
16
+ /**
17
+ * Options for ParameterEventHandler constructor.
18
+ */
19
+ export interface ParameterEventHandlerOptions {
20
+ /**
21
+ * QoS profile for the parameter_events subscription.
22
+ */
23
+ qos?: QoS;
24
+ }
25
+
26
+ /**
27
+ * Opaque handle returned when adding a parameter callback.
28
+ * Used to remove the callback later via removeParameterCallback().
29
+ */
30
+ class ParameterCallbackHandle {
31
+ readonly parameterName: string;
32
+ readonly nodeName: string;
33
+ readonly callback: (parameter: any) => void;
34
+ }
35
+
36
+ /**
37
+ * Opaque handle returned when adding a parameter event callback.
38
+ * Used to remove the callback later via removeParameterEventCallback().
39
+ */
40
+ class ParameterEventCallbackHandle {
41
+ readonly callback: (event: any) => void;
42
+ }
43
+
44
+ /**
45
+ * ParameterEventHandler - Monitors and responds to parameter changes
46
+ * on any node in the ROS 2 graph.
47
+ *
48
+ * Subscribes to `/parameter_events` and dispatches callbacks when
49
+ * parameters are added, changed, or deleted on any node.
50
+ *
51
+ * Two types of callbacks:
52
+ * - **Parameter callbacks**: for a specific parameter on a specific node
53
+ * - **Event callbacks**: for every ParameterEvent message received
54
+ *
55
+ * @example
56
+ * ```typescript
57
+ * const handler = node.createParameterEventHandler();
58
+ *
59
+ * const handle = handler.addParameterCallback(
60
+ * 'my_param', '/my_node',
61
+ * (param) => console.log(`Changed: ${param.name}`)
62
+ * );
63
+ *
64
+ * handler.removeParameterCallback(handle);
65
+ * handler.destroy();
66
+ * ```
67
+ */
68
+ class ParameterEventHandler {
69
+ /**
70
+ * Add a callback for a specific parameter on a specific node.
71
+ *
72
+ * @param parameterName - Name of the parameter to monitor.
73
+ * @param nodeName - Fully qualified name of the node (e.g., '/my_node').
74
+ * @param callback - Called with the parameter message when it changes.
75
+ * @returns A handle for removing this callback later.
76
+ */
77
+ addParameterCallback(
78
+ parameterName: string,
79
+ nodeName: string,
80
+ callback: (parameter: any) => void
81
+ ): ParameterCallbackHandle;
82
+
83
+ /**
84
+ * Remove a previously added parameter callback.
85
+ *
86
+ * @param handle - The handle returned by addParameterCallback.
87
+ */
88
+ removeParameterCallback(handle: ParameterCallbackHandle): void;
89
+
90
+ /**
91
+ * Add a callback that is invoked for every parameter event.
92
+ *
93
+ * @param callback - Called with the full ParameterEvent message.
94
+ * @returns A handle for removing this callback later.
95
+ */
96
+ addParameterEventCallback(
97
+ callback: (event: any) => void
98
+ ): ParameterEventCallbackHandle;
99
+
100
+ /**
101
+ * Remove a previously added parameter event callback.
102
+ *
103
+ * @param handle - The handle returned by addParameterEventCallback.
104
+ */
105
+ removeParameterEventCallback(handle: ParameterEventCallbackHandle): void;
106
+
107
+ /**
108
+ * Check if the handler has been destroyed.
109
+ */
110
+ isDestroyed(): boolean;
111
+
112
+ /**
113
+ * Destroy the handler and clean up resources.
114
+ */
115
+ destroy(): void;
116
+
117
+ /**
118
+ * Get a specific parameter from a ParameterEvent message.
119
+ *
120
+ * @param event - A ParameterEvent message.
121
+ * @param parameterName - The parameter name to look for.
122
+ * @param nodeName - The node name to match.
123
+ * @returns The matching parameter message, or null.
124
+ */
125
+ static getParameterFromEvent(
126
+ event: any,
127
+ parameterName: string,
128
+ nodeName: string
129
+ ): any | null;
130
+
131
+ /**
132
+ * Get all parameters from a ParameterEvent message (new + changed).
133
+ *
134
+ * @param event - A ParameterEvent message.
135
+ * @returns Array of parameter messages.
136
+ */
137
+ static getParametersFromEvent(event: any): any[];
138
+ }
139
+ }
@@ -1,8 +1,11 @@
1
1
  declare module 'rclnodejs' {
2
2
  /**
3
3
  * A callback for receiving published messages.
4
+ * If the callback accepts two parameters, the second will be a MessageInfo
5
+ * containing metadata about the received message.
4
6
  *
5
7
  * @param message - The published message.
8
+ * @param messageInfo - Optional metadata about the message (timestamps, publisher GID, etc).
6
9
  *
7
10
  * @remarks
8
11
  * See {@link Node#createSubscription | Node.createSubscription}
@@ -13,12 +16,15 @@ declare module 'rclnodejs' {
13
16
  */
14
17
  type SubscriptionCallback<T extends TypeClass<MessageTypeClassName>> =
15
18
  // * @param message - The published message
16
- (message: MessageType<T>) => void;
19
+ (message: MessageType<T>, messageInfo?: MessageInfo) => void;
17
20
 
18
21
  /**
19
22
  * A callback for receiving published raw messages.
23
+ * If the callback accepts a second parameter, it will receive a MessageInfo
24
+ * containing metadata about the received message.
20
25
  *
21
26
  * @param message - The published message.
27
+ * @param messageInfo - Optional metadata about the message.
22
28
  *
23
29
  * @remarks
24
30
  * See {@link Node#createSubscription | Node.createSubscription}
@@ -29,7 +35,7 @@ declare module 'rclnodejs' {
29
35
  */
30
36
  type SubscriptionWithRawMessageCallback =
31
37
  // * @param message - The published raw message
32
- (message: Buffer) => void;
38
+ (message: Buffer, messageInfo?: MessageInfo) => void;
33
39
 
34
40
  /**
35
41
  * A ROS Subscription for published messages on a topic.
@@ -45,6 +51,12 @@ declare module 'rclnodejs' {
45
51
  */
46
52
  readonly isRaw: boolean;
47
53
 
54
+ /**
55
+ * Check if content filtering is supported for this subscription.
56
+ * @returns True if the subscription instance supports content filtering; otherwise false.
57
+ */
58
+ isContentFilterSupported(): boolean;
59
+
48
60
  /**
49
61
  * Test if the RMW supports content-filtered topics and that this subscription
50
62
  * is configured with a well formed content-filter.
@@ -1,108 +0,0 @@
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);
@@ -1,57 +0,0 @@
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 DELETED
@@ -1,86 +0,0 @@
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);
package/test_repro_pub.js DELETED
@@ -1,36 +0,0 @@
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);