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.
- package/README.md +46 -37
- package/index.js +29 -0
- package/lib/action/client.js +61 -3
- package/lib/message_info.js +94 -0
- package/lib/node.js +53 -3
- package/lib/parameter_event_handler.js +468 -0
- package/lib/parameter_watcher.js +12 -12
- package/lib/subscription.js +38 -5
- package/lib/timer.js +2 -1
- package/lib/wait_for_message.js +111 -0
- package/package.json +6 -3
- 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/scripts/run_asan_test.sh +118 -0
- package/src/executor.cpp +36 -2
- package/src/executor.h +11 -0
- package/src/rcl_action_client_bindings.cpp +70 -1
- package/src/rcl_context_bindings.cpp +3 -3
- package/src/rcl_graph_bindings.cpp +2 -2
- package/src/rcl_subscription_bindings.cpp +70 -2
- package/src/rcl_utilities.cpp +2 -2
- package/tools/jsdoc/Makefile +5 -0
- package/tools/jsdoc/README.md +96 -0
- package/tools/jsdoc/build-index.js +610 -0
- package/tools/jsdoc/publish.js +854 -0
- package/tools/jsdoc/regenerate-published-docs.js +605 -0
- package/tools/jsdoc/static/fonts/OpenSans-Bold-webfont.eot +0 -0
- package/tools/jsdoc/static/fonts/OpenSans-Bold-webfont.svg +1830 -0
- package/tools/jsdoc/static/fonts/OpenSans-Bold-webfont.woff +0 -0
- package/tools/jsdoc/static/fonts/OpenSans-BoldItalic-webfont.eot +0 -0
- package/tools/jsdoc/static/fonts/OpenSans-BoldItalic-webfont.svg +1830 -0
- package/tools/jsdoc/static/fonts/OpenSans-BoldItalic-webfont.woff +0 -0
- package/tools/jsdoc/static/fonts/OpenSans-Italic-webfont.eot +0 -0
- package/tools/jsdoc/static/fonts/OpenSans-Italic-webfont.svg +1830 -0
- package/tools/jsdoc/static/fonts/OpenSans-Italic-webfont.woff +0 -0
- package/tools/jsdoc/static/fonts/OpenSans-Light-webfont.eot +0 -0
- package/tools/jsdoc/static/fonts/OpenSans-Light-webfont.svg +1831 -0
- package/tools/jsdoc/static/fonts/OpenSans-Light-webfont.woff +0 -0
- package/tools/jsdoc/static/fonts/OpenSans-LightItalic-webfont.eot +0 -0
- package/tools/jsdoc/static/fonts/OpenSans-LightItalic-webfont.svg +1835 -0
- package/tools/jsdoc/static/fonts/OpenSans-LightItalic-webfont.woff +0 -0
- package/tools/jsdoc/static/fonts/OpenSans-Regular-webfont.eot +0 -0
- package/tools/jsdoc/static/fonts/OpenSans-Regular-webfont.svg +1831 -0
- package/tools/jsdoc/static/fonts/OpenSans-Regular-webfont.woff +0 -0
- package/tools/jsdoc/static/scripts/linenumber.js +25 -0
- package/tools/jsdoc/static/scripts/prettify/Apache-License-2.0.txt +202 -0
- package/tools/jsdoc/static/scripts/prettify/lang-css.js +36 -0
- package/tools/jsdoc/static/scripts/prettify/prettify.js +738 -0
- package/tools/jsdoc/static/styles/jsdoc-default.css +1012 -0
- package/tools/jsdoc/static/styles/prettify-jsdoc.css +111 -0
- package/tools/jsdoc/static/styles/prettify-tomorrow.css +132 -0
- package/tools/jsdoc/tmpl/augments.tmpl +10 -0
- package/tools/jsdoc/tmpl/container.tmpl +193 -0
- package/tools/jsdoc/tmpl/details.tmpl +143 -0
- package/tools/jsdoc/tmpl/example.tmpl +2 -0
- package/tools/jsdoc/tmpl/examples.tmpl +13 -0
- package/tools/jsdoc/tmpl/exceptions.tmpl +17 -0
- package/tools/jsdoc/tmpl/layout.tmpl +83 -0
- package/tools/jsdoc/tmpl/mainpage.tmpl +163 -0
- package/tools/jsdoc/tmpl/members.tmpl +43 -0
- package/tools/jsdoc/tmpl/method.tmpl +124 -0
- package/tools/jsdoc/tmpl/params.tmpl +133 -0
- package/tools/jsdoc/tmpl/properties.tmpl +110 -0
- package/tools/jsdoc/tmpl/returns.tmpl +12 -0
- package/tools/jsdoc/tmpl/source.tmpl +8 -0
- package/tools/jsdoc/tmpl/tutorial.tmpl +19 -0
- package/tools/jsdoc/tmpl/type.tmpl +7 -0
- package/types/action_client.d.ts +8 -0
- package/types/index.d.ts +34 -0
- package/types/message_info.d.ts +72 -0
- package/types/node.d.ts +21 -0
- package/types/parameter_event_handler.d.ts +139 -0
- package/types/subscription.d.ts +14 -2
- package/test_data_integrity.js +0 -108
- package/test_repro_exact.js +0 -57
- package/test_repro_hz.js +0 -86
- package/test_repro_pub.js +0 -36
- package/test_repro_stress.js +0 -83
- package/test_repro_sub.js +0 -64
- package/test_xproc_data.js +0 -64
- package/types/interfaces.d.ts +0 -8895
package/README.md
CHANGED
|
@@ -1,27 +1,33 @@
|
|
|
1
|
-
# rclnodejs
|
|
1
|
+
# rclnodejs
|
|
2
2
|
|
|
3
|
-
`rclnodejs` is a Node.js client for
|
|
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
|
-
|
|
5
|
+
Supported ROS 2 distributions include Humble, Jazzy, Kilted, and Rolling.
|
|
6
6
|
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
```JavaScript
|
|
7
|
+
```javascript
|
|
10
8
|
const rclnodejs = require('rclnodejs');
|
|
11
9
|
rclnodejs.init().then(() => {
|
|
12
|
-
const node = rclnodejs.
|
|
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
|
-
|
|
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)
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
57
|
+
Installations outside this prebuilt matrix automatically fall back to building from source.
|
|
46
58
|
|
|
47
|
-
|
|
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 [
|
|
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
|
-
##
|
|
75
|
+
## Message Generation
|
|
59
76
|
|
|
60
|
-
|
|
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
|
|
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.
|
|
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
|
-
|
|
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. |  |
|
|
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. |  |
|
|
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
|
package/lib/action/client.js
CHANGED
|
@@ -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
|
-
|
|
273
|
-
|
|
274
|
-
|
|
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.
|