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
package/README.md CHANGED
@@ -1,27 +1,33 @@
1
- # rclnodejs [![Linux](https://github.com/RobotWebTools/rclnodejs/actions/workflows/linux-x64-push-test.yml/badge.svg?branch=develop)](https://github.com/RobotWebTools/rclnodejs/actions/workflows/linux-x64-push-test.yml?query=branch%3Adevelop)[![Linux](https://github.com/RobotWebTools/rclnodejs/actions/workflows/linux-arm64-push-test.yml/badge.svg?branch=develop)](https://github.com/RobotWebTools/rclnodejs/actions/workflows/linux-arm64-push-test.yml?query=branch%3Adevelop)
1
+ # rclnodejs
2
2
 
3
- `rclnodejs` is a Node.js client for the Robot Operating System (ROS 2). It provides a simple and easy JavaScript API for ROS 2 programming. TypeScript declarations are included to support use of rclnodejs in TypeScript projects.
3
+ `rclnodejs` is a Node.js client library for ROS 2 that provides JavaScript and TypeScript APIs for building ROS 2 applications.
4
4
 
5
- \* rclnodejs development and maintenance is limited to all active ROS 2 LTS releases and the Rolling development branch
5
+ Supported ROS 2 distributions include Humble, Jazzy, Kilted, and Rolling.
6
6
 
7
- Here's an example for how to create a ROS 2 node that publishes a string message in a few lines of JavaScript.
8
-
9
- ```JavaScript
7
+ ```javascript
10
8
  const rclnodejs = require('rclnodejs');
11
9
  rclnodejs.init().then(() => {
12
- const node = rclnodejs.createNode('publisher_example_node');
10
+ const node = new rclnodejs.Node('publisher_example_node');
13
11
  const publisher = node.createPublisher('std_msgs/msg/String', 'topic');
14
12
  publisher.publish(`Hello ROS 2 from rclnodejs`);
15
- rclnodejs.spin(node);
13
+ node.spin();
16
14
  });
17
15
  ```
18
16
 
17
+ This example assumes your ROS 2 environment is already sourced.
18
+
19
19
  ## Installation
20
20
 
21
21
  ### Prerequisites
22
22
 
23
23
  - [Node.js](https://nodejs.org/en/) version >= 16.13.0
24
- - [ROS 2 SDK](https://docs.ros.org/en/jazzy/Installation.html) - **Don't forget to [source the setup file](https://docs.ros.org/en/jazzy/Tutorials/Beginner-CLI-Tools/Configuring-ROS2-Environment.html#source-the-setup-files)**
24
+ - [ROS 2 SDK](https://docs.ros.org/en/jazzy/Installation.html)
25
+
26
+ Before installing or running rclnodejs, source your ROS 2 environment:
27
+
28
+ ```bash
29
+ source /opt/ros/<distro>/setup.bash
30
+ ```
25
31
 
26
32
  ### Install rclnodejs
27
33
 
@@ -29,11 +35,17 @@ rclnodejs.init().then(() => {
29
35
  npm i rclnodejs
30
36
  ```
31
37
 
32
- - **Note:** to install rclnodejs from GitHub: add `"rclnodejs":"RobotWebTools/rclnodejs#<branch>"` to your `package.json` dependency section.
38
+ To install from GitHub instead of npm, run:
39
+
40
+ ```bash
41
+ npm install RobotWebTools/rclnodejs#<branch>
42
+ ```
43
+
44
+ Or add `"rclnodejs":"RobotWebTools/rclnodejs#<branch>"` to your `package.json` dependencies.
33
45
 
34
46
  ### Prebuilt Binaries
35
47
 
36
- rclnodejs ships with prebuilt native binaries for common Linux configurations since `v1.5.2`, eliminating the need for compilation during installation. This significantly speeds up installation and reduces dependencies.
48
+ rclnodejs ships with prebuilt native binaries for common Linux configurations since `v1.5.2`, eliminating the need for compilation during installation. This applies to supported Linux environments when installing the published npm package.
37
49
 
38
50
  **Supported Platforms:**
39
51
 
@@ -42,26 +54,39 @@ rclnodejs ships with prebuilt native binaries for common Linux configurations si
42
54
  - **Architectures:** x64, arm64
43
55
  - **Node.js:** >= 16.20.2 (N-API compatible)
44
56
 
45
- **Force Building from Source:**
57
+ Installations outside this prebuilt matrix automatically fall back to building from source.
46
58
 
47
- If you need to build from source even when a prebuilt binary is available, set the environment variable:
59
+ **Force Building from Source:**
48
60
 
49
61
  ```bash
50
62
  export RCLNODEJS_FORCE_BUILD=1
51
63
  npm install rclnodejs
52
64
  ```
53
65
 
54
- ## Documentation
66
+ ## Documentation and Examples
55
67
 
56
- API [documentation](https://robotwebtools.github.io/rclnodejs/docs/index.html) is available online.
68
+ - API documentation: [robotwebtools.github.io/rclnodejs/docs](https://robotwebtools.github.io/rclnodejs/docs/index.html)
69
+ - Tutorials: [tutorials/](https://github.com/RobotWebTools/rclnodejs/tree/develop/tutorials)
70
+ - JavaScript examples: [example/](https://github.com/RobotWebTools/rclnodejs/tree/develop/example)
71
+ - TypeScript demos: [ts_demo/](https://github.com/RobotWebTools/rclnodejs/tree/develop/ts_demo)
72
+ - Electron demos: [electron_demo/](https://github.com/RobotWebTools/rclnodejs/tree/develop/electron_demo)
73
+ - Companion CLI: [rclnodejs-cli](https://github.com/RobotWebTools/rclnodejs-cli/)
57
74
 
58
- ## JavaScript Examples
75
+ ## Message Generation
59
76
 
60
- Try the [examples](https://github.com/RobotWebTools/rclnodejs/tree/develop/example) to get started.
77
+ rclnodejs generates JavaScript message interfaces and TypeScript declarations during installation for `rclnodejs > 1.5.0`. If you install additional ROS packages later, rerun the generator in your project:
78
+
79
+ ```bash
80
+ npx generate-ros-messages
81
+ ```
82
+
83
+ Generated files are written to `<your-project>/node_modules/rclnodejs/generated/`.
84
+
85
+ This step is only needed after adding ROS packages that were not present when rclnodejs was installed.
61
86
 
62
87
  ## Using rclnodejs with TypeScript
63
88
 
64
- TypeScript declaration files are included in the `types/` folder. Configure your `tsconfig.json`:
89
+ TypeScript declaration files are included in the package. In most projects, configuring your `tsconfig.json` is sufficient:
65
90
 
66
91
  ```jsonc
67
92
  {
@@ -73,33 +98,17 @@ TypeScript declaration files are included in the `types/` folder. Configure your
73
98
  }
74
99
  ```
75
100
 
76
- TypeScript example:
77
-
78
101
  ```typescript
79
102
  import * as rclnodejs from 'rclnodejs';
103
+
80
104
  rclnodejs.init().then(() => {
81
- const node = rclnodejs.createNode('publisher_example_node');
105
+ const node = new rclnodejs.Node('publisher_example_node');
82
106
  const publisher = node.createPublisher('std_msgs/msg/String', 'topic');
83
107
  publisher.publish(`Hello ROS 2 from rclnodejs`);
84
- rclnodejs.spin(node);
108
+ node.spin();
85
109
  });
86
110
  ```
87
111
 
88
- See [TypeScript demos](https://github.com/RobotWebTools/rclnodejs/tree/develop/ts_demo) for more examples.
89
-
90
- **Note** that the interface.d.ts file is updated each time the generate_messages.js script is run.
91
-
92
- ## Electron-based Visualization
93
-
94
- Create rich, interactive desktop applications using Electron and web technologies like Three.js. Build 3D visualizations, monitoring dashboards, and control interfaces that run on Windows, macOS, and Linux.
95
-
96
- | Demo | Description | Screenshot |
97
- | :-----------------------------------------------------------------------------------------------------: | :-----------------------------------------------------------------------------------------------------------------------------------: | :-----------------------------------------------------------------------------------------------------------------------------: |
98
- | **🐢 [turtle_tf2](https://github.com/RobotWebTools/rclnodejs/tree/develop/electron_demo/turtle_tf2)** | Real-time coordinate frame visualization with turtle control. Features TF2 transforms, keyboard control, and dynamic frame updates. | ![turtle_tf2](https://github.com/RobotWebTools/rclnodejs/blob/develop/electron_demo/turtle_tf2/turtle-tf2-demo.png?raw=true) |
99
- | **🦾 [manipulator](https://github.com/RobotWebTools/rclnodejs/tree/develop/electron_demo/manipulator)** | Interactive two-joint robotic arm simulation. Features 3D joint visualization, manual/automatic control, and visual movement markers. | ![manipulator](https://github.com/RobotWebTools/rclnodejs/blob/develop/electron_demo/manipulator/manipulator-demo.png?raw=true) |
100
-
101
- Explore more examples in [electron_demo](https://github.com/RobotWebTools/rclnodejs/tree/develop/electron_demo).
102
-
103
112
  ## License
104
113
 
105
114
  Apache License Version 2.0
package/index.js CHANGED
@@ -63,7 +63,10 @@ const {
63
63
  const ParameterClient = require('./lib/parameter_client.js');
64
64
  const errors = require('./lib/errors.js');
65
65
  const ParameterWatcher = require('./lib/parameter_watcher.js');
66
+ const ParameterEventHandler = require('./lib/parameter_event_handler.js');
67
+ const waitForMessage = require('./lib/wait_for_message.js');
66
68
  const MessageIntrospector = require('./lib/message_introspector.js');
69
+ const MessageInfo = require('./lib/message_info.js');
67
70
  const ObservableSubscription = require('./lib/observable_subscription.js');
68
71
  const { spawn } = require('child_process');
69
72
  const {
@@ -243,6 +246,12 @@ let rcl = {
243
246
  /** {@link ParameterWatcher} class */
244
247
  ParameterWatcher: ParameterWatcher,
245
248
 
249
+ /** {@link ParameterEventHandler} class */
250
+ ParameterEventHandler: ParameterEventHandler,
251
+
252
+ /** {@link MessageInfo} class */
253
+ MessageInfo: MessageInfo,
254
+
246
255
  /** {@link ObservableSubscription} class */
247
256
  ObservableSubscription: ObservableSubscription,
248
257
 
@@ -458,6 +467,26 @@ let rcl = {
458
467
  node.spinOnce(timeout);
459
468
  },
460
469
 
470
+ /**
471
+ * Wait for a single message on a topic.
472
+ *
473
+ * Creates a temporary subscription, waits for the first message to arrive,
474
+ * and returns it. The temporary subscription is always cleaned up, even on
475
+ * timeout or error. The node must be spinning before calling this function.
476
+ *
477
+ * This is the rclnodejs equivalent of rclpy's `wait_for_message`.
478
+ *
479
+ * @param {function|string|object} typeClass - The ROS message type class.
480
+ * @param {Node} node - The node to create the temporary subscription on.
481
+ * @param {string} topic - The topic name to listen on.
482
+ * @param {object} [options] - Options.
483
+ * @param {number} [options.timeout] - Timeout in milliseconds. If omitted, waits indefinitely.
484
+ * @param {object} [options.qos] - QoS profile for the subscription.
485
+ * @returns {Promise<object>} - Resolves with the received message.
486
+ * @throws {Error} If timeout expires before a message arrives.
487
+ */
488
+ waitForMessage: waitForMessage,
489
+
461
490
  /**
462
491
  * Shutdown an RCL environment identified by a context. The shutdown process will
463
492
  * destroy all nodes and related resources in the context. If no context is
@@ -51,6 +51,12 @@ class ActionClient extends Entity {
51
51
  * @param {QoS} options.qos.feedbackSubQosProfile - Quality of service option for the feedback subscription,
52
52
  * default: new QoS(QoS.HistoryPolicy.RMW_QOS_POLICY_HISTORY_SYSTEM_DEFAULT, 10).
53
53
  * @param {QoS} options.qos.statusSubQosProfile - Quality of service option for the status subscription, default: QoS.profileActionStatusDefault.
54
+ * @param {boolean} options.enableFeedbackMsgOptimization - Enable feedback subscription content filter to
55
+ * optimize the handling of feedback messages. When enabled, the content filter is used to configure
56
+ * the goal ID for the subscription, which helps avoid the reception of irrelevant feedback messages.
57
+ * An action client can handle up to 6 goals simultaneously with this optimization. If the number
58
+ * of goals exceeds the limit or the RMW doesn't support content filter, optimization is automatically
59
+ * disabled. Default: false.
54
60
  */
55
61
  constructor(node, typeClass, actionName, options) {
56
62
  super(null, null, options);
@@ -87,6 +93,15 @@ class ActionClient extends Entity {
87
93
  checkTypes: true,
88
94
  };
89
95
 
96
+ // Enable feedback subscription content filter optimization.
97
+ // Only supported on ROS2 Rolling and only effective when the native
98
+ // binding provides the required functions AND the RMW implementation
99
+ // actually supports content filtering on the feedback subscription.
100
+ this._enableFeedbackMsgOptimization =
101
+ this._options.enableFeedbackMsgOptimization === true &&
102
+ DistroUtils.getDistroId() >= DistroUtils.DistroId.ROLLING &&
103
+ typeof rclnodejs.actionConfigureFeedbackSubFilterAddGoalId === 'function';
104
+
90
105
  let type = this.typeClass.type();
91
106
 
92
107
  this._handle = rclnodejs.actionCreateClient(
@@ -126,6 +141,7 @@ class ActionClient extends Entity {
126
141
  }
127
142
 
128
143
  this._goalHandles.set(uuid, goalHandle);
144
+ this._feedbackSubFilterAddGoalId(goalHandle.goalId);
129
145
  } else {
130
146
  // Clean up feedback callback for rejected goals
131
147
  let uuid = ActionUuid.fromMessage(
@@ -205,6 +221,9 @@ class ActionClient extends Entity {
205
221
  status === ActionInterfaces.GoalStatus.STATUS_ABORTED
206
222
  ) {
207
223
  this._goalHandles.delete(uuid);
224
+ this._feedbackSubFilterRemoveGoalId(
225
+ statusMessage.goal_info.goal_id
226
+ );
208
227
  }
209
228
  }
210
229
  } else {
@@ -393,6 +412,8 @@ class ActionClient extends Entity {
393
412
  this._removePendingCancelRequest(sequenceNumber)
394
413
  );
395
414
 
415
+ this._feedbackSubFilterRemoveGoalId(goalHandle.goalId);
416
+
396
417
  return deferred.promise;
397
418
  }
398
419
 
@@ -442,9 +463,10 @@ class ActionClient extends Entity {
442
463
  goalHandle.status = result.status;
443
464
  return result.result;
444
465
  });
445
- deferred.setDoneCallback(() =>
446
- this._removePendingResultRequest(sequenceNumber)
447
- );
466
+ deferred.setDoneCallback(() => {
467
+ this._removePendingResultRequest(sequenceNumber);
468
+ this._feedbackSubFilterRemoveGoalId(goalHandle.goalId);
469
+ });
448
470
 
449
471
  this._pendingResultRequests.set(sequenceNumber, deferred);
450
472
 
@@ -464,6 +486,42 @@ class ActionClient extends Entity {
464
486
  this._pendingCancelRequests.delete(sequenceNumber);
465
487
  }
466
488
 
489
+ /**
490
+ * Add a goal ID to the feedback subscription content filter.
491
+ * @ignore
492
+ * @param {object} goalId - The goal UUID message.
493
+ */
494
+ _feedbackSubFilterAddGoalId(goalId) {
495
+ if (!this._enableFeedbackMsgOptimization) return;
496
+ try {
497
+ rclnodejs.actionConfigureFeedbackSubFilterAddGoalId(
498
+ this.handle,
499
+ Buffer.from(goalId.uuid)
500
+ );
501
+ } catch (e) {
502
+ this._enableFeedbackMsgOptimization = false;
503
+ this._node.getLogger().warn(`${e.message}`);
504
+ }
505
+ }
506
+
507
+ /**
508
+ * Remove a goal ID from the feedback subscription content filter.
509
+ * @ignore
510
+ * @param {object} goalId - The goal UUID message.
511
+ */
512
+ _feedbackSubFilterRemoveGoalId(goalId) {
513
+ if (!this._enableFeedbackMsgOptimization) return;
514
+ try {
515
+ rclnodejs.actionConfigureFeedbackSubFilterRemoveGoalId(
516
+ this.handle,
517
+ Buffer.from(goalId.uuid)
518
+ );
519
+ } catch (e) {
520
+ this._enableFeedbackMsgOptimization = false;
521
+ this._node.getLogger().warn(`${e.message}`);
522
+ }
523
+ }
524
+
467
525
  /**
468
526
  * Destroy the underlying action client handle.
469
527
  * @return {undefined}
@@ -0,0 +1,94 @@
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
+ 'use strict';
16
+
17
+ /**
18
+ * @class MessageInfo
19
+ *
20
+ * Contains metadata about a received message, including timestamps,
21
+ * sequence numbers, and the publisher's globally unique identifier (GID).
22
+ *
23
+ * This is the rclnodejs equivalent of rclpy's MessageInfo.
24
+ * It is passed as the second argument to subscription callbacks when the
25
+ * callback accepts two parameters.
26
+ *
27
+ * @example
28
+ * node.createSubscription(
29
+ * 'std_msgs/msg/String',
30
+ * 'topic',
31
+ * (msg, messageInfo) => {
32
+ * console.log('Source timestamp:', messageInfo.sourceTimestamp);
33
+ * console.log('Received at:', messageInfo.receivedTimestamp);
34
+ * console.log('Publisher GID:', messageInfo.publisherGid);
35
+ * }
36
+ * );
37
+ */
38
+ class MessageInfo {
39
+ /**
40
+ * Create a MessageInfo from a raw info object returned by the native layer.
41
+ *
42
+ * @param {object} rawInfo - Raw message info from rclTakeWithInfo
43
+ * @hideconstructor
44
+ */
45
+ constructor(rawInfo) {
46
+ /**
47
+ * The timestamp when the message was published (nanoseconds since epoch).
48
+ * @type {bigint}
49
+ */
50
+ this.sourceTimestamp = rawInfo.source_timestamp;
51
+
52
+ /**
53
+ * The timestamp when the message was received by the subscription (nanoseconds since epoch).
54
+ * @type {bigint}
55
+ */
56
+ this.receivedTimestamp = rawInfo.received_timestamp;
57
+
58
+ /**
59
+ * The publication sequence number assigned by the publisher.
60
+ * @type {bigint}
61
+ */
62
+ this.publicationSequenceNumber = rawInfo.publication_sequence_number;
63
+
64
+ /**
65
+ * The reception sequence number assigned by the subscriber.
66
+ * @type {bigint}
67
+ */
68
+ this.receptionSequenceNumber = rawInfo.reception_sequence_number;
69
+
70
+ /**
71
+ * The globally unique identifier (GID) of the publisher.
72
+ * A Buffer containing the raw GID bytes.
73
+ * @type {Buffer}
74
+ */
75
+ this.publisherGid = rawInfo.publisher_gid;
76
+ }
77
+
78
+ /**
79
+ * Convert to a plain object representation.
80
+ *
81
+ * @returns {object} Plain object with all metadata fields
82
+ */
83
+ toPlainObject() {
84
+ return {
85
+ sourceTimestamp: this.sourceTimestamp,
86
+ receivedTimestamp: this.receivedTimestamp,
87
+ publicationSequenceNumber: this.publicationSequenceNumber,
88
+ receptionSequenceNumber: this.receptionSequenceNumber,
89
+ publisherGid: this.publisherGid,
90
+ };
91
+ }
92
+ }
93
+
94
+ module.exports = MessageInfo;
package/lib/node.js CHANGED
@@ -40,12 +40,14 @@ const {
40
40
  const ParameterService = require('./parameter_service.js');
41
41
  const ParameterClient = require('./parameter_client.js');
42
42
  const ParameterWatcher = require('./parameter_watcher.js');
43
+ const ParameterEventHandler = require('./parameter_event_handler.js');
43
44
  const Publisher = require('./publisher.js');
44
45
  const QoS = require('./qos.js');
45
46
  const Rates = require('./rate.js');
46
47
  const Service = require('./service.js');
47
48
  const Subscription = require('./subscription.js');
48
49
  const ObservableSubscription = require('./observable_subscription.js');
50
+ const MessageInfo = require('./message_info.js');
49
51
  const TimeSource = require('./time_source.js');
50
52
  const Timer = require('./timer.js');
51
53
  const TypeDescriptionService = require('./type_description_service.js');
@@ -148,6 +150,7 @@ class Node extends rclnodejs.ShadowNode {
148
150
  this._actionServers = [];
149
151
  this._parameterClients = [];
150
152
  this._parameterWatchers = [];
153
+ this._parameterEventHandlers = [];
151
154
  this._rateTimerServer = null;
152
155
  this._parameterDescriptors = new Map();
153
156
  this._parameters = new Map();
@@ -269,9 +272,22 @@ class Node extends rclnodejs.ShadowNode {
269
272
  this._runWithMessageType(
270
273
  subscription.typeClass,
271
274
  (message, deserialize) => {
272
- let success = rclnodejs.rclTake(subscription.handle, message);
273
- if (success) {
274
- subscription.processResponse(deserialize());
275
+ if (subscription.wantsMessageInfo) {
276
+ let rawInfo = rclnodejs.rclTakeWithInfo(
277
+ subscription.handle,
278
+ message
279
+ );
280
+ if (rawInfo) {
281
+ subscription.processResponse(
282
+ deserialize(),
283
+ new MessageInfo(rawInfo)
284
+ );
285
+ }
286
+ } else {
287
+ let success = rclnodejs.rclTake(subscription.handle, message);
288
+ if (success) {
289
+ subscription.processResponse(deserialize());
290
+ }
275
291
  }
276
292
  }
277
293
  );
@@ -1082,6 +1098,7 @@ class Node extends rclnodejs.ShadowNode {
1082
1098
 
1083
1099
  this._parameterClients.forEach((paramClient) => paramClient.destroy());
1084
1100
  this._parameterWatchers.forEach((watcher) => watcher.destroy());
1101
+ this._parameterEventHandlers.forEach((handler) => handler.destroy());
1085
1102
 
1086
1103
  this.context.onNodeDestroyed(this);
1087
1104
 
@@ -1102,6 +1119,7 @@ class Node extends rclnodejs.ShadowNode {
1102
1119
  this._actionServers = [];
1103
1120
  this._parameterClients = [];
1104
1121
  this._parameterWatchers = [];
1122
+ this._parameterEventHandlers = [];
1105
1123
 
1106
1124
  if (this._rateTimerServer) {
1107
1125
  this._rateTimerServer.shutdown();
@@ -1214,6 +1232,38 @@ class Node extends rclnodejs.ShadowNode {
1214
1232
  watcher.destroy();
1215
1233
  }
1216
1234
 
1235
+ /**
1236
+ * Create a ParameterEventHandler that monitors parameter changes on any node.
1237
+ *
1238
+ * Unlike {@link ParameterWatcher} which watches specific parameters on a single
1239
+ * remote node, ParameterEventHandler can register callbacks for parameters on
1240
+ * any node in the ROS 2 graph by subscribing to /parameter_events.
1241
+ *
1242
+ * @param {object} [options] - Options for the handler
1243
+ * @param {object} [options.qos] - QoS profile for the parameter_events subscription
1244
+ * @return {ParameterEventHandler} - An instance of ParameterEventHandler
1245
+ * @see {@link ParameterEventHandler}
1246
+ */
1247
+ createParameterEventHandler(options = {}) {
1248
+ const handler = new ParameterEventHandler(this, options);
1249
+ debug('Created ParameterEventHandler on node=%s', this.name());
1250
+ this._parameterEventHandlers.push(handler);
1251
+ return handler;
1252
+ }
1253
+
1254
+ /**
1255
+ * Destroy a ParameterEventHandler.
1256
+ * @param {ParameterEventHandler} handler - The handler to be destroyed.
1257
+ * @return {undefined}
1258
+ */
1259
+ destroyParameterEventHandler(handler) {
1260
+ if (!(handler instanceof ParameterEventHandler)) {
1261
+ throw new TypeError('Invalid argument');
1262
+ }
1263
+ this._removeEntityFromArray(handler, this._parameterEventHandlers);
1264
+ handler.destroy();
1265
+ }
1266
+
1217
1267
  /**
1218
1268
  * Destroy a Timer.
1219
1269
  * @param {Timer} timer - The Timer to be destroyed.