rclnodejs 1.8.2 → 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 (112) hide show
  1. package/README.md +46 -37
  2. package/index.js +62 -23
  3. package/lib/action/client.js +67 -3
  4. package/lib/action/server.js +1 -3
  5. package/lib/distro.js +2 -1
  6. package/lib/lifecycle_publisher.js +2 -2
  7. package/lib/message_info.js +94 -0
  8. package/lib/node.js +90 -14
  9. package/lib/parameter.js +5 -9
  10. package/lib/parameter_event_handler.js +468 -0
  11. package/lib/parameter_watcher.js +12 -12
  12. package/lib/service.js +8 -4
  13. package/lib/subscription.js +38 -5
  14. package/lib/time_source.js +3 -20
  15. package/lib/timer.js +2 -1
  16. package/lib/wait_for_message.js +111 -0
  17. package/package.json +7 -4
  18. package/prebuilds/linux-arm64/humble-jammy-arm64-rclnodejs.node +0 -0
  19. package/prebuilds/linux-arm64/jazzy-noble-arm64-rclnodejs.node +0 -0
  20. package/prebuilds/linux-arm64/kilted-noble-arm64-rclnodejs.node +0 -0
  21. package/prebuilds/linux-x64/humble-jammy-x64-rclnodejs.node +0 -0
  22. package/prebuilds/linux-x64/jazzy-noble-x64-rclnodejs.node +0 -0
  23. package/prebuilds/linux-x64/kilted-noble-x64-rclnodejs.node +0 -0
  24. package/rosidl_gen/generate_worker.js +3 -13
  25. package/rosidl_gen/idl_generator.js +210 -0
  26. package/rosidl_gen/index.js +3 -12
  27. package/rosidl_gen/packages.js +1 -3
  28. package/rosidl_gen/primitive_types.js +2 -2
  29. package/rosidl_parser/idl_parser.py +437 -0
  30. package/rosidl_parser/parser.py +2 -4
  31. package/rosidl_parser/rosidl_parser.js +27 -0
  32. package/scripts/run_asan_test.sh +118 -0
  33. package/src/executor.cpp +37 -2
  34. package/src/executor.h +11 -0
  35. package/src/macros.h +2 -2
  36. package/src/rcl_action_client_bindings.cpp +88 -12
  37. package/src/rcl_action_server_bindings.cpp +24 -13
  38. package/src/rcl_client_bindings.cpp +13 -5
  39. package/src/rcl_context_bindings.cpp +10 -11
  40. package/src/rcl_graph_bindings.cpp +2 -2
  41. package/src/rcl_guard_condition_bindings.cpp +12 -3
  42. package/src/rcl_lifecycle_bindings.cpp +34 -15
  43. package/src/rcl_node_bindings.cpp +11 -4
  44. package/src/rcl_publisher_bindings.cpp +12 -3
  45. package/src/rcl_service_bindings.cpp +12 -3
  46. package/src/rcl_subscription_bindings.cpp +92 -21
  47. package/src/rcl_timer_bindings.cpp +24 -9
  48. package/src/rcl_type_description_service_bindings.cpp +9 -1
  49. package/src/rcl_utilities.cpp +2 -2
  50. package/tools/jsdoc/Makefile +5 -0
  51. package/tools/jsdoc/README.md +96 -0
  52. package/tools/jsdoc/build-index.js +610 -0
  53. package/tools/jsdoc/publish.js +854 -0
  54. package/tools/jsdoc/regenerate-published-docs.js +605 -0
  55. package/tools/jsdoc/static/fonts/OpenSans-Bold-webfont.eot +0 -0
  56. package/tools/jsdoc/static/fonts/OpenSans-Bold-webfont.svg +1830 -0
  57. package/tools/jsdoc/static/fonts/OpenSans-Bold-webfont.woff +0 -0
  58. package/tools/jsdoc/static/fonts/OpenSans-BoldItalic-webfont.eot +0 -0
  59. package/tools/jsdoc/static/fonts/OpenSans-BoldItalic-webfont.svg +1830 -0
  60. package/tools/jsdoc/static/fonts/OpenSans-BoldItalic-webfont.woff +0 -0
  61. package/tools/jsdoc/static/fonts/OpenSans-Italic-webfont.eot +0 -0
  62. package/tools/jsdoc/static/fonts/OpenSans-Italic-webfont.svg +1830 -0
  63. package/tools/jsdoc/static/fonts/OpenSans-Italic-webfont.woff +0 -0
  64. package/tools/jsdoc/static/fonts/OpenSans-Light-webfont.eot +0 -0
  65. package/tools/jsdoc/static/fonts/OpenSans-Light-webfont.svg +1831 -0
  66. package/tools/jsdoc/static/fonts/OpenSans-Light-webfont.woff +0 -0
  67. package/tools/jsdoc/static/fonts/OpenSans-LightItalic-webfont.eot +0 -0
  68. package/tools/jsdoc/static/fonts/OpenSans-LightItalic-webfont.svg +1835 -0
  69. package/tools/jsdoc/static/fonts/OpenSans-LightItalic-webfont.woff +0 -0
  70. package/tools/jsdoc/static/fonts/OpenSans-Regular-webfont.eot +0 -0
  71. package/tools/jsdoc/static/fonts/OpenSans-Regular-webfont.svg +1831 -0
  72. package/tools/jsdoc/static/fonts/OpenSans-Regular-webfont.woff +0 -0
  73. package/tools/jsdoc/static/scripts/linenumber.js +25 -0
  74. package/tools/jsdoc/static/scripts/prettify/Apache-License-2.0.txt +202 -0
  75. package/tools/jsdoc/static/scripts/prettify/lang-css.js +36 -0
  76. package/tools/jsdoc/static/scripts/prettify/prettify.js +738 -0
  77. package/tools/jsdoc/static/styles/jsdoc-default.css +1012 -0
  78. package/tools/jsdoc/static/styles/prettify-jsdoc.css +111 -0
  79. package/tools/jsdoc/static/styles/prettify-tomorrow.css +132 -0
  80. package/tools/jsdoc/tmpl/augments.tmpl +10 -0
  81. package/tools/jsdoc/tmpl/container.tmpl +193 -0
  82. package/tools/jsdoc/tmpl/details.tmpl +143 -0
  83. package/tools/jsdoc/tmpl/example.tmpl +2 -0
  84. package/tools/jsdoc/tmpl/examples.tmpl +13 -0
  85. package/tools/jsdoc/tmpl/exceptions.tmpl +17 -0
  86. package/tools/jsdoc/tmpl/layout.tmpl +83 -0
  87. package/tools/jsdoc/tmpl/mainpage.tmpl +163 -0
  88. package/tools/jsdoc/tmpl/members.tmpl +43 -0
  89. package/tools/jsdoc/tmpl/method.tmpl +124 -0
  90. package/tools/jsdoc/tmpl/params.tmpl +133 -0
  91. package/tools/jsdoc/tmpl/properties.tmpl +110 -0
  92. package/tools/jsdoc/tmpl/returns.tmpl +12 -0
  93. package/tools/jsdoc/tmpl/source.tmpl +8 -0
  94. package/tools/jsdoc/tmpl/tutorial.tmpl +19 -0
  95. package/tools/jsdoc/tmpl/type.tmpl +7 -0
  96. package/types/action_client.d.ts +8 -0
  97. package/types/index.d.ts +34 -0
  98. package/types/message_info.d.ts +72 -0
  99. package/types/node.d.ts +21 -0
  100. package/types/parameter_event_handler.d.ts +139 -0
  101. package/types/subscription.d.ts +14 -2
  102. package/rosidl_convertor/README.md +0 -298
  103. package/rosidl_convertor/idl_convertor.js +0 -50
  104. package/rosidl_convertor/idl_convertor.py +0 -1250
  105. package/test_data_integrity.js +0 -108
  106. package/test_repro_exact.js +0 -57
  107. package/test_repro_hz.js +0 -86
  108. package/test_repro_pub.js +0 -36
  109. package/test_repro_stress.js +0 -83
  110. package/test_repro_sub.js +0 -64
  111. package/test_xproc_data.js +0 -64
  112. 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 {
@@ -85,7 +88,7 @@ async function getCurrentGeneratorVersion() {
85
88
  const jsonFilePath = path.join(generator.generatedRoot, 'generator.json');
86
89
 
87
90
  return new Promise((resolve, reject) => {
88
- fs.open(jsonFilePath, 'r', (err) => {
91
+ fs.readFile(jsonFilePath, 'utf8', (err, data) => {
89
92
  if (err) {
90
93
  if (err.code === 'ENOENT') {
91
94
  resolve(null);
@@ -93,13 +96,11 @@ async function getCurrentGeneratorVersion() {
93
96
  reject(err);
94
97
  }
95
98
  } else {
96
- fs.readFile(jsonFilePath, 'utf8', (err, data) => {
97
- if (err) {
98
- reject(err);
99
- } else {
100
- resolve(JSON.parse(data).version);
101
- }
102
- });
99
+ try {
100
+ resolve(JSON.parse(data).version);
101
+ } catch (parseErr) {
102
+ reject(parseErr);
103
+ }
103
104
  }
104
105
  });
105
106
  });
@@ -245,6 +246,12 @@ let rcl = {
245
246
  /** {@link ParameterWatcher} class */
246
247
  ParameterWatcher: ParameterWatcher,
247
248
 
249
+ /** {@link ParameterEventHandler} class */
250
+ ParameterEventHandler: ParameterEventHandler,
251
+
252
+ /** {@link MessageInfo} class */
253
+ MessageInfo: MessageInfo,
254
+
248
255
  /** {@link ObservableSubscription} class */
249
256
  ObservableSubscription: ObservableSubscription,
250
257
 
@@ -389,23 +396,35 @@ let rcl = {
389
396
 
390
397
  rclnodejs.init(context.handle, argv, context._domainId);
391
398
 
392
- if (_rosVersionChecked) {
393
- // no further processing required
394
- return;
395
- }
399
+ try {
400
+ if (_rosVersionChecked) {
401
+ // no further processing required
402
+ return;
403
+ }
396
404
 
397
- const version = await getCurrentGeneratorVersion();
398
- const forced =
399
- version === null || compareVersions(version, generator.version(), '<');
400
- if (forced) {
401
- debug(
402
- 'The generator will begin to create JavaScript code from ROS IDL files...'
403
- );
404
- }
405
+ const version = await getCurrentGeneratorVersion();
406
+ const forced =
407
+ version === null || compareVersions(version, generator.version(), '<');
408
+ if (forced) {
409
+ debug(
410
+ 'The generator will begin to create JavaScript code from ROS IDL files...'
411
+ );
412
+ }
405
413
 
406
- await generator.generateAll(forced);
407
- // TODO determine if tsd generateAll() should be here
408
- _rosVersionChecked = true;
414
+ await generator.generateAll(forced);
415
+ // TODO determine if tsd generateAll() should be here
416
+ _rosVersionChecked = true;
417
+ } catch (error) {
418
+ try {
419
+ context.tryShutdown();
420
+ } catch (shutdownError) {
421
+ const initError =
422
+ error instanceof Error ? error : new Error(String(error));
423
+ initError.message += ` (rollback also failed: ${shutdownError.message})`;
424
+ throw initError;
425
+ }
426
+ throw error;
427
+ }
409
428
  },
410
429
 
411
430
  /**
@@ -448,6 +467,26 @@ let rcl = {
448
467
  node.spinOnce(timeout);
449
468
  },
450
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
+
451
490
  /**
452
491
  * Shutdown an RCL environment identified by a context. The shutdown process will
453
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,13 @@ class ActionClient extends Entity {
126
141
  }
127
142
 
128
143
  this._goalHandles.set(uuid, goalHandle);
144
+ this._feedbackSubFilterAddGoalId(goalHandle.goalId);
145
+ } else {
146
+ // Clean up feedback callback for rejected goals
147
+ let uuid = ActionUuid.fromMessage(
148
+ this._sequenceNumberGoalIdMap.get(sequence)
149
+ ).toString();
150
+ this._feedbackCallbacks.delete(uuid);
129
151
  }
130
152
 
131
153
  this._pendingGoalRequests.get(sequence).setResult(goalHandle);
@@ -199,6 +221,9 @@ class ActionClient extends Entity {
199
221
  status === ActionInterfaces.GoalStatus.STATUS_ABORTED
200
222
  ) {
201
223
  this._goalHandles.delete(uuid);
224
+ this._feedbackSubFilterRemoveGoalId(
225
+ statusMessage.goal_info.goal_id
226
+ );
202
227
  }
203
228
  }
204
229
  } else {
@@ -387,6 +412,8 @@ class ActionClient extends Entity {
387
412
  this._removePendingCancelRequest(sequenceNumber)
388
413
  );
389
414
 
415
+ this._feedbackSubFilterRemoveGoalId(goalHandle.goalId);
416
+
390
417
  return deferred.promise;
391
418
  }
392
419
 
@@ -436,9 +463,10 @@ class ActionClient extends Entity {
436
463
  goalHandle.status = result.status;
437
464
  return result.result;
438
465
  });
439
- deferred.setDoneCallback(() =>
440
- this._removePendingResultRequest(sequenceNumber)
441
- );
466
+ deferred.setDoneCallback(() => {
467
+ this._removePendingResultRequest(sequenceNumber);
468
+ this._feedbackSubFilterRemoveGoalId(goalHandle.goalId);
469
+ });
442
470
 
443
471
  this._pendingResultRequests.set(sequenceNumber, deferred);
444
472
 
@@ -458,6 +486,42 @@ class ActionClient extends Entity {
458
486
  this._pendingCancelRequests.delete(sequenceNumber);
459
487
  }
460
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
+
461
525
  /**
462
526
  * Destroy the underlying action client handle.
463
527
  * @return {undefined}
@@ -437,10 +437,8 @@ class ActionServer extends Entity {
437
437
 
438
438
  _executeExpiredGoals(result, count) {
439
439
  for (let i = 0; i < count; i++) {
440
- const goal = result.data[i];
441
-
442
440
  const goalInfo = new ActionInterfaces.GoalInfo();
443
- goalInfo.deserialize(goal.refObject);
441
+ goalInfo.deserialize(result._refArray[i]);
444
442
 
445
443
  let uuid = ActionUuid.fromBytes(goalInfo.goal_id.uuid).toString();
446
444
  this._goalHandles.delete(uuid);
package/lib/distro.js CHANGED
@@ -65,7 +65,8 @@ const DistroUtils = {
65
65
  return process.env.ROS_DISTRO;
66
66
  }
67
67
 
68
- return [...DistroNameIdMap].find(([, val]) => val == distroId)[0];
68
+ const result = [...DistroNameIdMap].find(([, val]) => val == distroId);
69
+ return result ? result[0] : undefined;
69
70
  },
70
71
 
71
72
  getKnownDistroNames: function () {
@@ -30,7 +30,7 @@ class LifecyclePublisher extends Publisher {
30
30
  super(handle, typeClass, /*topic=*/ '', options);
31
31
 
32
32
  this._enabled = false;
33
- this._loggger = Logging.getLogger('LifecyclePublisher');
33
+ this._logger = Logging.getLogger('LifecyclePublisher');
34
34
  }
35
35
 
36
36
  /**
@@ -42,7 +42,7 @@ class LifecyclePublisher extends Publisher {
42
42
  */
43
43
  publish(message) {
44
44
  if (!this._enabled) {
45
- this._loggger.warn(
45
+ this._logger.warn(
46
46
  `Trying to publish message on the topic ${this.topic}, but the publisher is not activated`
47
47
  );
48
48
 
@@ -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;