rclnodejs 1.4.2 → 1.5.1

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 CHANGED
@@ -1,7 +1,9 @@
1
- # rclnodejs [![Linux](https://github.com/RobotWebTools/rclnodejs/actions/workflows/linux-x64-build-and-test.yml/badge.svg?branch=develop)](https://github.com/RobotWebTools/rclnodejs/actions/workflows/linux-x64-build-and-test.yml?query=branch%3Adevelop)[![Linux](https://github.com/RobotWebTools/rclnodejs/actions/workflows/linux-arm64-build-and-test.yml/badge.svg?branch=develop)](https://github.com/RobotWebTools/rclnodejs/actions/workflows/linux-arm64-build-and-test.yml?query=branch%3Adevelop)
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)
2
2
 
3
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.
4
4
 
5
+ \* rclnodejs development and maintenance is limited to all active ROS 2 LTS releases and the Rolling development branch
6
+
5
7
  Here's an example for how to create a ROS 2 node that publishes a string message in a few lines of JavaScript.
6
8
 
7
9
  ```JavaScript
@@ -14,38 +16,20 @@ rclnodejs.init().then(() => {
14
16
  });
15
17
  ```
16
18
 
17
- ## Prerequisites
18
-
19
- **Node.js**
20
-
21
- - [Node.js](https://nodejs.org/en/) version >= 16.13.0.
19
+ ## Installation
22
20
 
23
- **ROS 2 SDK**
21
+ ### Prerequisites
24
22
 
25
- - See the ROS 2 SDK [Installation Guide](https://docs.ros.org/en/kilted/Installation.html) for details.
26
- - **DON'T FORGET TO [SOURCE THE ROS 2 STARTUP FILES](https://docs.ros.org/en/kilted/Tutorials/Beginner-CLI-Tools/Configuring-ROS2-Environment.htmls)**
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)**
27
25
 
28
- ## Install rclnodejs
29
-
30
- Install the rclnodejs version that is compatible with your installed version of ROS 2 (see table below).
31
-
32
- Run the following command for the most current version of rclnodejs
26
+ ### Install rclnodejs
33
27
 
34
28
  ```bash
35
29
  npm i rclnodejs
36
30
  ```
37
31
 
38
- or to install a specific version of rclnodejs use
39
-
40
- ```bash
41
- npm i rclnodejs@x.y.z
42
- ```
43
-
44
- #### RCLNODEJS - ROS 2 Version Compatibility
45
-
46
- | RCLNODEJS Version | Compatible ROS 2 LTS |
47
- | :----------------------------------------------------------------------------------------: | :--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: |
48
- | latest version (currently [v1.4.2](https://github.com/RobotWebTools/rclnodejs/tree/1.4.2)) | [Kilted](https://github.com/RobotWebTools/rclnodejs/tree/kilted)<br>[Jazzy](https://github.com/RobotWebTools/rclnodejs/tree/jazzy)<br>[Humble](https://github.com/RobotWebTools/rclnodejs/tree/humble-hawksbill) |
32
+ - **Note:** to install rclnodejs from GitHub: add `"rclnodejs":"RobotWebTools/rclnodejs#<branch>"` to your `package.json` dependency section.
49
33
 
50
34
  ## Documentation
51
35
 
@@ -53,98 +37,23 @@ API [documentation](https://robotwebtools.github.io/rclnodejs/docs/index.html) i
53
37
 
54
38
  ## JavaScript Examples
55
39
 
56
- The source for the following examples and many others can be found [here](https://github.com/RobotWebTools/rclnodejs/tree/develop/example).
57
-
58
- Use complex message
59
-
60
- ```JavaScript
61
- const publisher = node.createPublisher('sensor_msgs/msg/JointState', 'topic');
62
- publisher.publish({
63
- header: {
64
- stamp: {
65
- sec: 123456,
66
- nanosec: 789,
67
- },
68
- frame_id: 'main frame',
69
- },
70
- name: ['Tom', 'Jerry'],
71
- position: [1, 2],
72
- velocity: [2, 3],
73
- effort: [4, 5, 6],
74
- });
75
-
76
- ```
77
-
78
- Create a subscription
79
-
80
- ```JavaScript
81
- const rclnodejs = require('../index.js');
82
-
83
- // Create a ROS node and then print out the string message received from publishers
84
- rclnodejs.init().then(() => {
85
- const node = rclnodejs.createNode('subscription_example_node');
86
-
87
- node.createSubscription('std_msgs/msg/String', 'topic', (msg) => {
88
- console.log(`Received message: ${typeof msg}`, msg);
89
- });
90
-
91
- rclnodejs.spin(node);
92
- });
93
- ```
94
-
95
- Create a service
96
-
97
- ```JavaScript
98
- node.createService('example_interfaces/srv/AddTwoInts', 'add_two_ints', (request, response) => {
99
- console.log(`Incoming request: ${typeof request}`, request);
100
- let result = response.template;
101
- result.sum = request.a + request.b;
102
- console.log(`Sending response: ${typeof result}`, result, '\n--');
103
- response.send(result);
104
- });
105
-
106
- ```
107
-
108
- Send a request in a client
109
-
110
- ```JavaScript
111
- const client = node.createClient('example_interfaces/srv/AddTwoInts', 'add_two_ints');
112
- const request = {
113
- a: Math.floor(Math.random() * 100),
114
- b: Math.floor(Math.random() * 100),
115
- };
116
-
117
- console.log(`Sending: ${typeof request}`, request);
118
- client.sendRequest(request, (response) => {
119
- console.log(`Result: ${typeof response}`, response);
120
- });
121
-
122
- ```
123
-
124
- Check out more [examples](https://github.com/RobotWebTools/rclnodejs/tree/develop/example).
40
+ Try the [examples](https://github.com/RobotWebTools/rclnodejs/tree/develop/example) to get started.
125
41
 
126
42
  ## Using rclnodejs with TypeScript
127
43
 
128
- In your node project install the rclnodejs package as described above. You will also need the TypeScript compiler and node typings installed.
129
-
130
- ```
131
- npm install typescript @types/node -D
132
- ```
133
-
134
- In your project's tsconfig.json file include the following compiler options:
44
+ TypeScript declaration files are included in the `types/` folder. Configure your `tsconfig.json`:
135
45
 
136
46
  ```jsonc
137
47
  {
138
48
  "compilerOptions": {
139
49
  "module": "commonjs",
140
50
  "moduleResolution": "node",
141
- "target": "es6",
142
- ...
143
- }
51
+ "target": "es2020",
52
+ },
144
53
  }
145
54
  ```
146
55
 
147
- Here's an earlier JavaScript example reimplemented in TypeScript.
56
+ TypeScript example:
148
57
 
149
58
  ```typescript
150
59
  import * as rclnodejs from 'rclnodejs';
@@ -156,21 +65,17 @@ rclnodejs.init().then(() => {
156
65
  });
157
66
  ```
158
67
 
159
- Type-aliases for the ROS2 messages can be found in the `types/interfaces.d.ts` file. To use a message type-alias follow the naming pattern <pkg_name>.[msg|srv].<type>, e.g., sensor_msgs.msg.LaserScan or the std_msgs.msg.String as shown below.
68
+ See [TypeScript demos](https://github.com/RobotWebTools/rclnodejs/tree/develop/ts_demo) for more examples.
160
69
 
161
- ```typescript
162
- const msg: rclnodejs.std_msgs.msg.String = {
163
- data: 'hello ROS2 from rclnodejs',
164
- };
165
- ```
70
+ **Note** that the interface.d.ts file is updated each time the generate_messages.js script is run.
166
71
 
167
- Check out more TypeScript [demos](https://github.com/RobotWebTools/rclnodejs/tree/develop/ts_demo).
72
+ ## Electron-based Visualization
168
73
 
169
- **Note** that the interface.d.ts file is updated each time the generate_messages.js script is run.
74
+ 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.
170
75
 
171
- ## Using rclnodejs with Electron
76
+ Try the `electron_demo/turtle_tf2` demo for real-time coordinate frame visualization with dynamic transforms and keyboard-controlled turtle movement. More examples in [electron_demo](https://github.com/RobotWebTools/rclnodejs/tree/develop/electron_demo).
172
77
 
173
- Check out [demos](https://github.com/RobotWebTools/rclnodejs/tree/develop/electron_demo).
78
+ ![demo screenshot](https://github.com/RobotWebTools/rclnodejs/blob/develop/electron_demo/turtle_tf2/turtle-tf2-demo.gif?raw=true)
174
79
 
175
80
  ## License
176
81
 
package/index.js CHANGED
@@ -57,6 +57,7 @@ const {
57
57
  serializeMessage,
58
58
  deserializeMessage,
59
59
  } = require('./lib/serialization.js');
60
+ const { spawn } = require('child_process');
60
61
 
61
62
  /**
62
63
  * Get the version of the generator that was used for the currently present interfaces.
@@ -89,6 +90,82 @@ async function getCurrentGeneratorVersion() {
89
90
 
90
91
  let _rosVersionChecked = false;
91
92
 
93
+ /**
94
+ * Run a ROS2 package executable using 'ros2 run' command.
95
+ * @param {string} packageName - The name of the ROS2 package.
96
+ * @param {string} executableName - The name of the executable to run.
97
+ * @param {string[]} [args=[]] - Additional arguments to pass to the executable.
98
+ * @return {Promise<{process: ChildProcess}>} A Promise that resolves with the process.
99
+ */
100
+ function ros2Run(packageName, executableName, args = []) {
101
+ return new Promise((resolve, reject) => {
102
+ if (typeof packageName !== 'string' || !packageName.trim()) {
103
+ reject(new Error('Package name must be a non-empty string'));
104
+ return;
105
+ }
106
+
107
+ if (typeof executableName !== 'string' || !executableName.trim()) {
108
+ reject(new Error('Executable name must be a non-empty string'));
109
+ return;
110
+ }
111
+
112
+ if (!Array.isArray(args)) {
113
+ reject(new Error('Arguments must be an array'));
114
+ return;
115
+ }
116
+
117
+ const command = 'ros2';
118
+ const cmdArgs = ['run', packageName, executableName, ...args];
119
+ const childProcess = spawn(command, cmdArgs);
120
+
121
+ childProcess.on('error', (error) => {
122
+ reject(new Error(`Failed to start ros2 run: ${error.message}`));
123
+ });
124
+ childProcess.on('spawn', () => {
125
+ resolve({
126
+ process: childProcess,
127
+ });
128
+ });
129
+ });
130
+ }
131
+
132
+ /**
133
+ * Run a ROS2 launch file using 'ros2 launch' command.
134
+ * @param {string} packageName - The name of the ROS2 package.
135
+ * @param {string} launchFile - The name of the launch file to run.
136
+ * @param {string[]} [args=[]] - Additional arguments to pass to the launch file.
137
+ * @return {Promise<{process: ChildProcess}>} A Promise that resolves with the process.
138
+ */
139
+ function ros2Launch(packageName, launchFile, args = []) {
140
+ return new Promise((resolve, reject) => {
141
+ if (typeof packageName !== 'string' || !packageName.trim()) {
142
+ reject(new Error('Package name must be a non-empty string'));
143
+ return;
144
+ }
145
+ if (typeof launchFile !== 'string' || !launchFile.trim()) {
146
+ reject(new Error('Launch file name must be a non-empty string'));
147
+ return;
148
+ }
149
+ if (!Array.isArray(args)) {
150
+ reject(new Error('Arguments must be an array'));
151
+ return;
152
+ }
153
+ const command = 'ros2';
154
+ const cmdArgs = ['launch', packageName, launchFile, ...args];
155
+ const childProcess = spawn(command, cmdArgs);
156
+
157
+ childProcess.on('error', (error) => {
158
+ reject(new Error(`Failed to start ros2 launch: ${error.message}`));
159
+ });
160
+
161
+ childProcess.on('spawn', () => {
162
+ resolve({
163
+ process: childProcess,
164
+ });
165
+ });
166
+ });
167
+ }
168
+
92
169
  /**
93
170
  * A module that exposes the rclnodejs interfaces.
94
171
  * @exports rclnodejs
@@ -444,6 +521,24 @@ let rcl = {
444
521
  // this will not throw even if the handler is already removed
445
522
  process.removeListener('SIGINT', _sigHandler);
446
523
  },
524
+
525
+ /**
526
+ * Run a ROS2 package executable using 'ros2 run' command.
527
+ * @param {string} packageName - The name of the ROS2 package.
528
+ * @param {string} executableName - The name of the executable to run.
529
+ * @param {string[]} [args=[]] - Additional arguments to pass to the executable.
530
+ * @return {Promise<{process: ChildProcess}>} A Promise that resolves with the process.
531
+ */
532
+ ros2Run: ros2Run,
533
+
534
+ /**
535
+ * Run a ROS2 launch file using 'ros2 launch' command.
536
+ * @param {string} packageName - The name of the ROS2 package.
537
+ * @param {string} launchFile - The name of the launch file to run.
538
+ * @param {string[]} [args=[]] - Additional arguments to pass to the launch file.
539
+ * @return {Promise<{process: ChildProcess}>} A Promise that resolves with the process.
540
+ */
541
+ ros2Launch: ros2Launch,
447
542
  };
448
543
 
449
544
  const _sigHandler = () => {
package/lib/client.js CHANGED
@@ -157,6 +157,14 @@ class Client extends Entity {
157
157
  introspectionState
158
158
  );
159
159
  }
160
+
161
+ /**
162
+ * Get the logger name for this client.
163
+ * @returns {string} The logger name for this client.
164
+ */
165
+ get loggerName() {
166
+ return rclnodejs.getNodeLoggerName(this._nodeHandle);
167
+ }
160
168
  }
161
169
 
162
170
  module.exports = Client;
@@ -90,6 +90,95 @@ let interfaceLoader = {
90
90
  return pkg;
91
91
  },
92
92
 
93
+ _isRos2InstallationPath(pkgPath) {
94
+ // Use "which ros2" to dynamically find the ROS2 installation root
95
+ try {
96
+ const whichResult = require('child_process').spawnSync(
97
+ 'which',
98
+ ['ros2'],
99
+ {
100
+ encoding: 'utf8',
101
+ timeout: 5000,
102
+ }
103
+ );
104
+
105
+ if (whichResult.status === 0 && whichResult.stdout) {
106
+ const ros2BinPath = whichResult.stdout.trim();
107
+ // Get the ROS2 installation root (typically /opt/ros/<distro> or similar)
108
+ const ros2Root = path.dirname(path.dirname(ros2BinPath));
109
+
110
+ return pkgPath.includes(ros2Root);
111
+ }
112
+ } catch (err) {
113
+ console.error('Error running which ros2:', err.message);
114
+ // If "which ros2" fails, fall back to hardcoded check
115
+ return pkgPath.includes('ros2-linux');
116
+ }
117
+
118
+ return false;
119
+ },
120
+
121
+ _searchAndGenerateInterface(packageName, type, messageName, filePath) {
122
+ // Check if it's a valid package
123
+ for (const pkgPath of generator.getInstalledPackagePaths()) {
124
+ // We are going to ignore the path where ROS2 is installed.
125
+ if (this._isRos2InstallationPath(pkgPath)) {
126
+ continue;
127
+ }
128
+
129
+ // Recursively search for files named messageName.* under pkgPath/
130
+ if (fs.existsSync(pkgPath)) {
131
+ // Recursive function to search for files
132
+ function searchForFile(dir) {
133
+ try {
134
+ const items = fs.readdirSync(dir, { withFileTypes: true });
135
+ for (const item of items) {
136
+ const fullPath = path.join(dir, item.name);
137
+
138
+ if (item.isFile()) {
139
+ const baseName = path.parse(item.name).name;
140
+ // Check if the base filename matches messageName
141
+ if (baseName === messageName) {
142
+ return fullPath;
143
+ }
144
+ } else if (item.isDirectory()) {
145
+ // Recursively search subdirectories
146
+ const result = searchForFile(fullPath);
147
+ if (result) {
148
+ return result;
149
+ }
150
+ }
151
+ }
152
+ } catch (err) {
153
+ // Skip directories we can't read
154
+ console.error('Error reading directory:', dir, err.message);
155
+ }
156
+ return null;
157
+ }
158
+
159
+ const foundFilePath = searchForFile(
160
+ path.join(pkgPath, 'share', packageName)
161
+ );
162
+
163
+ if (foundFilePath && foundFilePath.length > 0) {
164
+ // Use worker thread to generate interfaces synchronously
165
+ try {
166
+ generator.generateInPathSyncWorker(pkgPath);
167
+ // Now try to load the interface again from the generated files
168
+ if (fs.existsSync(filePath)) {
169
+ return require(filePath);
170
+ }
171
+ } catch (err) {
172
+ console.error('Error in interface generation:', err);
173
+ }
174
+ }
175
+ }
176
+ }
177
+ throw new Error(
178
+ `The message required does not exist: ${packageName}, ${type}, ${messageName} at ${generator.generatedRoot}`
179
+ );
180
+ },
181
+
93
182
  loadInterface(packageName, type, messageName) {
94
183
  if (arguments.length === 1) {
95
184
  const type = arguments[0];
@@ -100,7 +189,6 @@ let interfaceLoader = {
100
189
  }
101
190
  throw new Error(`The message required does not exist: ${type}`);
102
191
  }
103
-
104
192
  if (packageName && type && messageName) {
105
193
  let filePath = path.join(
106
194
  generator.generatedRoot,
@@ -110,8 +198,16 @@ let interfaceLoader = {
110
198
 
111
199
  if (fs.existsSync(filePath)) {
112
200
  return require(filePath);
201
+ } else {
202
+ return this._searchAndGenerateInterface(
203
+ packageName,
204
+ type,
205
+ messageName,
206
+ filePath
207
+ );
113
208
  }
114
209
  }
210
+ // We cannot parse `packageName`, `type` and `messageName` from the string passed.
115
211
  throw new Error(
116
212
  `The message required does not exist: ${packageName}, ${type}, ${messageName} at ${generator.generatedRoot}`
117
213
  );
package/lib/publisher.js CHANGED
@@ -26,6 +26,7 @@ const Entity = require('./entity.js');
26
26
  class Publisher extends Entity {
27
27
  constructor(handle, typeClass, topic, options, node, eventCallbacks) {
28
28
  super(handle, typeClass, options);
29
+ this._node = node;
29
30
  if (node && eventCallbacks) {
30
31
  this._events = eventCallbacks.createEventHandlers(this.handle);
31
32
  node._events.push(...this._events);
@@ -126,6 +127,14 @@ class Publisher extends Entity {
126
127
  set events(events) {
127
128
  this._events = events;
128
129
  }
130
+
131
+ /**
132
+ * Get the logger name for this publisher.
133
+ * @returns {string} The logger name for this publisher.
134
+ */
135
+ get loggerName() {
136
+ return rclnodejs.getNodeLoggerName(this._node.handle);
137
+ }
129
138
  }
130
139
 
131
140
  module.exports = Publisher;
package/lib/service.js CHANGED
@@ -149,6 +149,14 @@ class Service extends Entity {
149
149
  getOptions() {
150
150
  return rclnodejs.getOptions(this._handle);
151
151
  }
152
+
153
+ /**
154
+ * Get the logger name for this service.
155
+ * @returns {string} The logger name for this service.
156
+ */
157
+ get loggerName() {
158
+ return rclnodejs.getNodeLoggerName(this._nodeHandle);
159
+ }
152
160
  }
153
161
 
154
162
  module.exports = Service;
@@ -42,6 +42,7 @@ class Subscription extends Entity {
42
42
  this._topic = topic;
43
43
  this._callback = callback;
44
44
  this._isRaw = options.isRaw || false;
45
+ this._node = node;
45
46
 
46
47
  if (node && eventCallbacks) {
47
48
  this._events = eventCallbacks.createEventHandlers(this.handle);
@@ -168,6 +169,14 @@ class Subscription extends Entity {
168
169
  set events(events) {
169
170
  this._events = events;
170
171
  }
172
+
173
+ /**
174
+ * Get the logger name for this subscription.
175
+ * @returns {string} The logger name for this subscription.
176
+ */
177
+ get loggerName() {
178
+ return rclnodejs.getNodeLoggerName(this._node.handle);
179
+ }
171
180
  }
172
181
 
173
182
  module.exports = Subscription;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "rclnodejs",
3
- "version": "1.4.2",
3
+ "version": "1.5.1",
4
4
  "description": "ROS2.0 JavaScript client with Node.js",
5
5
  "main": "index.js",
6
6
  "types": "types/index.d.ts",
@@ -0,0 +1,63 @@
1
+ // Copyright (c) 2025, 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
+ const fse = require('fs-extra');
16
+ const generateJSStructFromIDL = require('./idl_generator.js');
17
+ const packages = require('./packages.js');
18
+ const path = require('path');
19
+ const idlConvertor = require('../rosidl_convertor/idl_convertor.js');
20
+
21
+ const generatedRoot = path.join(__dirname, '../generated/');
22
+ const idlPath = path.join(generatedRoot, 'share');
23
+ const useIDL = !!process.argv.find((arg) => arg === '--idl');
24
+
25
+ // Get target path from environment variable instead of workerData
26
+ const targetPath = process.env.WORKER_TARGET_PATH;
27
+
28
+ async function generateInPath(targetPath) {
29
+ let pkgsInfo = null;
30
+ if (!useIDL) {
31
+ pkgsInfo = Array.from(
32
+ (await packages.findPackagesInDirectory(targetPath)).values()
33
+ );
34
+ } else {
35
+ const idlPkgs = await packages.findPackagesInDirectory(targetPath, useIDL);
36
+ await fse.ensureDir(idlPath);
37
+ const promises = [];
38
+ idlPkgs.forEach((pkg) => {
39
+ pkg.idls.forEach((idl) => {
40
+ promises.push(idlConvertor(idl.pkgName, idl.filePath, idlPath));
41
+ });
42
+ });
43
+ await Promise.all(promises);
44
+ const pkgsFromIdl = await packages.findPackagesInDirectory(idlPath, false);
45
+ pkgsInfo = Array.from(pkgsFromIdl.values());
46
+ }
47
+
48
+ await Promise.all(
49
+ pkgsInfo.map((pkgInfo) => generateJSStructFromIDL(pkgInfo, generatedRoot))
50
+ );
51
+ }
52
+
53
+ async function main() {
54
+ try {
55
+ await generateInPath(targetPath);
56
+ process.exit(0);
57
+ } catch (error) {
58
+ console.error('Worker generation failed:', error.message);
59
+ process.exit(1);
60
+ }
61
+ }
62
+
63
+ main();
@@ -53,6 +53,35 @@ async function generateInPath(path) {
53
53
  );
54
54
  }
55
55
 
56
+ function generateInPathSyncWorker(targetPath) {
57
+ try {
58
+ // Use child_process.spawnSync for truly synchronous execution
59
+ const result = require('child_process').spawnSync(
60
+ 'node',
61
+ [path.join(__dirname, 'generate_worker.js')],
62
+ {
63
+ env: { ...process.env, WORKER_TARGET_PATH: targetPath },
64
+ encoding: 'utf8',
65
+ timeout: 30000,
66
+ }
67
+ );
68
+
69
+ if (result.error) {
70
+ throw result.error;
71
+ }
72
+
73
+ if (result.status !== 0) {
74
+ throw new Error(
75
+ `Worker process exited with code ${result.status}. stderr: ${result.stderr}`
76
+ );
77
+ }
78
+
79
+ return result.stdout;
80
+ } catch (error) {
81
+ throw error;
82
+ }
83
+ }
84
+
56
85
  async function generateAll(forcedGenerating) {
57
86
  // If we want to create the JavaScript files compulsively (|forcedGenerating| equals to true)
58
87
  // or the JavaScript files have not been created (|exist| equals to false),
@@ -86,7 +115,9 @@ const generator = {
86
115
 
87
116
  generateAll,
88
117
  generateInPath,
118
+ generateInPathSyncWorker,
89
119
  generatedRoot,
120
+ getInstalledPackagePaths,
90
121
  };
91
122
 
92
123
  module.exports = generator;
package/types/client.d.ts CHANGED
@@ -52,6 +52,11 @@ declare module 'rclnodejs' {
52
52
  serviceEventPubQOS: QoS,
53
53
  introspectionState: ServiceIntrospectionStates
54
54
  ): void;
55
+
56
+ /**
57
+ * Get the logger name for this client.
58
+ */
59
+ readonly loggerName: string;
55
60
  }
56
61
 
57
62
  namespace Client {
package/types/index.d.ts CHANGED
@@ -1,5 +1,7 @@
1
1
  /// <reference path="./base.d.ts" />
2
2
 
3
+ import { ChildProcess } from 'child_process';
4
+
3
5
  declare module 'rclnodejs' {
4
6
  type Class = new (...args: any[]) => any;
5
7
 
@@ -207,4 +209,30 @@ declare module 'rclnodejs' {
207
209
  * @returns An Object representing the deserialized message.
208
210
  */
209
211
  function deserializeMessage(buffer: Buffer, typeClass: Class): object;
212
+
213
+ /**
214
+ * Run a ROS2 package executable using 'ros2 run' command.
215
+ * @param {string} packageName - The name of the ROS2 package.
216
+ * @param {string} executableName - The name of the executable to run.
217
+ * @param {string[]} [args=[]] - Additional arguments to pass to the executable.
218
+ * @return {Promise<{process: ChildProcess}>} A Promise that resolves with the process.
219
+ */
220
+ function ros2Run(
221
+ packageName: string,
222
+ executableName: string,
223
+ args: string[]
224
+ ): Promise<{ process: ChildProcess }>;
225
+
226
+ /**
227
+ * Run a ROS2 launch file using 'ros2 launch' command.
228
+ * @param {string} packageName - The name of the ROS2 package.
229
+ * @param {string} launchFile - The name of the launch file to run.
230
+ * @param {string[]} [args=[]] - Additional arguments to pass to the launch file.
231
+ * @return {Promise<{process: ChildProcess}>} A Promise that resolves with the process.
232
+ */
233
+ function ros2Launch(
234
+ packageName: string,
235
+ launchFile: string,
236
+ args: string[]
237
+ ): Promise<{ process: ChildProcess }>;
210
238
  }
@@ -37,5 +37,10 @@ declare module 'rclnodejs' {
37
37
  * @return {boolean} `true` if all published message data is acknowledged before the timeout, otherwise `false`.
38
38
  */
39
39
  waitForAllAcked(timeout: bigint): boolean;
40
+
41
+ /**
42
+ * Get the logger name for this publisher.
43
+ */
44
+ readonly loggerName: string;
40
45
  }
41
46
  }
@@ -94,5 +94,10 @@ declare module 'rclnodejs' {
94
94
  * @return The options of this service.
95
95
  */
96
96
  getOptions(): object;
97
+
98
+ /**
99
+ * Get the logger name for this service.
100
+ */
101
+ readonly loggerName: string;
97
102
  }
98
103
  }
@@ -72,5 +72,10 @@ declare module 'rclnodejs' {
72
72
  * @returns The number of publishers
73
73
  */
74
74
  publisherCount(): number;
75
+
76
+ /**
77
+ * Get the logger name for this subscription.
78
+ */
79
+ readonly loggerName: string;
75
80
  }
76
81
  }