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
@@ -17,6 +17,7 @@
17
17
  const EventEmitter = require('events');
18
18
  const { TypeValidationError, OperationError } = require('./errors');
19
19
  const { normalizeNodeName } = require('./utils');
20
+ const ParameterEventHandler = require('./parameter_event_handler.js');
20
21
  const debug = require('debug')('rclnodejs:parameter_watcher');
21
22
 
22
23
  /**
@@ -30,7 +31,7 @@ const debug = require('debug')('rclnodejs:parameter_watcher');
30
31
  class ParameterWatcher extends EventEmitter {
31
32
  #node;
32
33
  #paramClient;
33
- #subscription;
34
+ #eventHandler;
34
35
  #watchedParams;
35
36
  #remoteNodeName;
36
37
  #destroyed;
@@ -82,7 +83,7 @@ class ParameterWatcher extends EventEmitter {
82
83
  this.#paramClient = node.createParameterClient(remoteNodeName, options);
83
84
  // Cache the remote node name for error messages (in case paramClient is destroyed)
84
85
  this.#remoteNodeName = this.#paramClient.remoteNodeName;
85
- this.#subscription = null;
86
+ this.#eventHandler = null;
86
87
  this.#destroyed = false;
87
88
 
88
89
  debug(
@@ -133,14 +134,13 @@ class ParameterWatcher extends EventEmitter {
133
134
  return false;
134
135
  }
135
136
 
136
- if (!this.#subscription) {
137
- this.#subscription = this.#node.createSubscription(
138
- 'rcl_interfaces/msg/ParameterEvent',
139
- '/parameter_events',
140
- (event) => this.#handleParameterEvent(event)
137
+ if (!this.#eventHandler) {
138
+ this.#eventHandler = new ParameterEventHandler(this.#node);
139
+ this.#eventHandler.addParameterEventCallback((event) =>
140
+ this.#handleParameterEvent(event)
141
141
  );
142
142
 
143
- debug('Subscribed to /parameter_events');
143
+ debug('Subscribed to /parameter_events via ParameterEventHandler');
144
144
  }
145
145
 
146
146
  return true;
@@ -227,13 +227,13 @@ class ParameterWatcher extends EventEmitter {
227
227
 
228
228
  debug('Destroying ParameterWatcher for node=%s', this.remoteNodeName);
229
229
 
230
- if (this.#subscription) {
230
+ if (this.#eventHandler) {
231
231
  try {
232
- this.#node.destroySubscription(this.#subscription);
232
+ this.#eventHandler.destroy();
233
233
  } catch (error) {
234
- debug('Error destroying subscription: %s', error.message);
234
+ debug('Error destroying event handler: %s', error.message);
235
235
  }
236
- this.#subscription = null;
236
+ this.#eventHandler = null;
237
237
  }
238
238
 
239
239
  if (this.#paramClient) {
package/lib/service.js CHANGED
@@ -17,6 +17,7 @@
17
17
  const rclnodejs = require('./native_loader.js');
18
18
  const DistroUtils = require('./distro.js');
19
19
  const Entity = require('./entity.js');
20
+ const Logging = require('./logging.js');
20
21
  const debug = require('debug')('rclnodejs:service');
21
22
 
22
23
  /**
@@ -75,8 +76,8 @@ class Service extends Entity {
75
76
 
76
77
  const plainObj = request.toPlainObject(this.typedArrayEnabled);
77
78
  const response = new Response(this, headerHandle);
78
- Promise.resolve(this._callback(plainObj, response)).then(
79
- (responseToReturn) => {
79
+ Promise.resolve(this._callback(plainObj, response))
80
+ .then((responseToReturn) => {
80
81
  if (!response.sent && responseToReturn) {
81
82
  responseToReturn = new this._typeClass.Response(responseToReturn);
82
83
  const rawResponse = responseToReturn.serialize();
@@ -86,8 +87,11 @@ class Service extends Entity {
86
87
  debug(
87
88
  `Service has processed the ${this._serviceName} request and sent the response.`
88
89
  );
89
- }
90
- );
90
+ })
91
+ .catch((error) => {
92
+ const logger = Logging.getLogger('rclnodejs');
93
+ logger.error(`Error processing ${this._serviceName} request: ${error}`);
94
+ });
91
95
  }
92
96
 
93
97
  static createService(nodeHandle, serviceName, typeClass, options, callback) {
@@ -16,14 +16,16 @@
16
16
 
17
17
  const rclnodejs = require('./native_loader.js');
18
18
  const Entity = require('./entity.js');
19
+ const DistroUtils = require('./distro.js');
19
20
  const { applySerializationMode } = require('./message_serialization.js');
20
21
  const debug = require('debug')('rclnodejs:subscription');
21
22
 
22
23
  /**
23
24
  * @class - Class representing a ROS 2 Subscription
24
- * @hideconstructor
25
25
  * Includes support for content-filtering topics beginning with the
26
- * ROS Humble release. To learn more about content-filtering
26
+ * ROS Humble release. To learn more about content-filtering topics,
27
+ * see the references below.
28
+ * @hideconstructor
27
29
  * @see {@link Node#options}
28
30
  * @see {@link Node#createSubscription}
29
31
  * @see {@link https://www.omg.org/spec/DDS/1.4/PDF|DDS 1.4 specification, Annex B}
@@ -45,6 +47,7 @@ class Subscription extends Entity {
45
47
  this._isRaw = options.isRaw || false;
46
48
  this._serializationMode = options.serializationMode || 'default';
47
49
  this._node = node;
50
+ this._wantsMessageInfo = callback.length >= 2;
48
51
 
49
52
  if (node && eventCallbacks) {
50
53
  this._events = eventCallbacks.createEventHandlers(this.handle);
@@ -52,10 +55,24 @@ class Subscription extends Entity {
52
55
  }
53
56
  }
54
57
 
55
- processResponse(msg) {
58
+ /**
59
+ * Whether this subscription's callback wants MessageInfo as a second argument.
60
+ * Determined by callback.length >= 2.
61
+ * @type {boolean}
62
+ * @readonly
63
+ */
64
+ get wantsMessageInfo() {
65
+ return this._wantsMessageInfo;
66
+ }
67
+
68
+ processResponse(msg, messageInfo) {
56
69
  debug(`Message of topic ${this._topic} received.`);
57
70
  if (this._isRaw) {
58
- this._callback(msg);
71
+ if (this._wantsMessageInfo && messageInfo) {
72
+ this._callback(msg, messageInfo);
73
+ } else {
74
+ this._callback(msg);
75
+ }
59
76
  } else {
60
77
  let message = msg.toPlainObject(this.typedArrayEnabled);
61
78
 
@@ -63,7 +80,11 @@ class Subscription extends Entity {
63
80
  message = applySerializationMode(message, this._serializationMode);
64
81
  }
65
82
 
66
- this._callback(message);
83
+ if (this._wantsMessageInfo && messageInfo) {
84
+ this._callback(message, messageInfo);
85
+ } else {
86
+ this._callback(message);
87
+ }
67
88
  }
68
89
  }
69
90
 
@@ -124,6 +145,18 @@ class Subscription extends Entity {
124
145
  return this._serializationMode;
125
146
  }
126
147
 
148
+ /**
149
+ * Check if content filtering is supported for this subscription.
150
+ * Requires ROS 2 Rolling or later.
151
+ * @returns {boolean} True if the subscription instance supports content filtering; otherwise false.
152
+ */
153
+ isContentFilterSupported() {
154
+ if (DistroUtils.getDistroId() < DistroUtils.DistroId.ROLLING) {
155
+ return false;
156
+ }
157
+ return rclnodejs.isContentFilterSupported(this.handle);
158
+ }
159
+
127
160
  /**
128
161
  * Test if the RMW supports content-filtered topics and that this subscription
129
162
  * has an active wellformed content-filter.
@@ -45,26 +45,6 @@ class TimeSource {
45
45
  }
46
46
  }
47
47
 
48
- get isRosTimeActive() {
49
- return this._isRosTimeActive;
50
- }
51
-
52
- set isRosTimeActive(enabled) {
53
- if (this.isRosTimeActive === enabled) return;
54
-
55
- this._isRosTimeActive = enabled;
56
- for (const clock in this._associatedClocks) {
57
- clock.isRosTimeActive = enabled;
58
- }
59
-
60
- if (enabled) {
61
- this._subscribeToClockTopic();
62
- } else if (this._node && this._clockSubscription) {
63
- this._node.destroySubscription(this._clockSubscription);
64
- this._node._clockSubscription = null;
65
- }
66
- }
67
-
68
48
  /**
69
49
  * Return status that whether the ROS time is active.
70
50
  * @name TimeSource#get:isRosTimeActive
@@ -93,6 +73,9 @@ class TimeSource {
93
73
  });
94
74
  if (enabled) {
95
75
  this._subscribeToClockTopic();
76
+ } else if (this._node && this._clockSubscription) {
77
+ this._node.destroySubscription(this._clockSubscription);
78
+ this._clockSubscription = undefined;
96
79
  }
97
80
  }
98
81
 
package/lib/timer.js CHANGED
@@ -30,7 +30,8 @@ class Timer {
30
30
  }
31
31
 
32
32
  /**
33
- * @type {bigint} - The period of the timer in nanoseconds.
33
+ * The period of the timer in nanoseconds.
34
+ * @type {bigint}
34
35
  */
35
36
  get period() {
36
37
  return this._period;
@@ -0,0 +1,111 @@
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
+ const { TimeoutError } = require('./errors.js');
18
+
19
+ /**
20
+ * Wait for a single message on a topic.
21
+ *
22
+ * Creates a temporary subscription, waits for the first message to arrive,
23
+ * and returns it. The temporary subscription is always cleaned up, even on
24
+ * timeout or error. The node must be spinning before calling this function.
25
+ *
26
+ * This is the rclnodejs equivalent of rclpy's `wait_for_message`.
27
+ *
28
+ * @param {function|string|object} typeClass - The ROS message type class.
29
+ * @param {Node} node - The node to create the temporary subscription on.
30
+ * @param {string} topic - The topic name to listen on.
31
+ * @param {object} [options] - Options.
32
+ * @param {number} [options.timeout] - Timeout in milliseconds. If omitted, waits indefinitely.
33
+ * @param {object} [options.qos] - QoS profile for the subscription.
34
+ * @returns {Promise<object>} - Resolves with the received message.
35
+ * @throws {Error} If timeout expires before a message arrives.
36
+ *
37
+ * @example
38
+ * node.spin();
39
+ * const msg = await waitForMessage(
40
+ * 'std_msgs/msg/String',
41
+ * node,
42
+ * '/my_topic',
43
+ * { timeout: 5000 }
44
+ * );
45
+ * console.log('Received:', msg.data);
46
+ */
47
+ function waitForMessage(typeClass, node, topic, options = {}) {
48
+ return new Promise((resolve, reject) => {
49
+ let subscription = null;
50
+ let timer = null;
51
+ let settled = false;
52
+
53
+ const cleanup = () => {
54
+ if (timer) {
55
+ clearTimeout(timer);
56
+ timer = null;
57
+ }
58
+ if (subscription) {
59
+ try {
60
+ node.destroySubscription(subscription);
61
+ } catch {
62
+ // Subscription may already be destroyed if node is shutting down
63
+ }
64
+ subscription = null;
65
+ }
66
+ };
67
+
68
+ const settle = (err, msg) => {
69
+ if (settled) return;
70
+ settled = true;
71
+ cleanup();
72
+ if (err) {
73
+ reject(err);
74
+ } else {
75
+ resolve(msg);
76
+ }
77
+ };
78
+
79
+ try {
80
+ const subOptions = {};
81
+ if (options.qos) {
82
+ subOptions.qos = options.qos;
83
+ }
84
+
85
+ subscription = node.createSubscription(
86
+ typeClass,
87
+ topic,
88
+ subOptions,
89
+ (msg) => {
90
+ settle(null, msg);
91
+ }
92
+ );
93
+
94
+ if (options.timeout != null && options.timeout >= 0) {
95
+ timer = setTimeout(() => {
96
+ settle(
97
+ new TimeoutError(
98
+ `waitForMessage timed out after ${options.timeout}ms on topic '${topic}'`,
99
+ { entityType: 'topic', entityName: topic }
100
+ )
101
+ );
102
+ }, options.timeout);
103
+ }
104
+ } catch (err) {
105
+ cleanup();
106
+ reject(err);
107
+ }
108
+ });
109
+ }
110
+
111
+ module.exports = waitForMessage;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "rclnodejs",
3
- "version": "1.8.2",
3
+ "version": "1.9.0-alpha.0",
4
4
  "description": "ROS2.0 JavaScript client with Node.js",
5
5
  "main": "index.js",
6
6
  "types": "types/index.d.ts",
@@ -24,10 +24,13 @@
24
24
  "clean": "node-gyp clean && npx rimraf ./generated",
25
25
  "install": "node scripts/install.js",
26
26
  "postinstall": "npm run generate-messages",
27
- "docs": "cd docs && make",
27
+ "docs": "make -C tools/jsdoc",
28
+ "docs:gh-pages": "node tools/jsdoc/regenerate-published-docs.js --branch origin/gh-pages --preserve-published",
29
+ "docs:gh-pages:full": "node tools/jsdoc/regenerate-published-docs.js --branch origin/gh-pages --full-rebuild",
28
30
  "test": "nyc node --expose-gc ./scripts/run_test.js && tsd && npm install --no-save electron && node test/electron/run_test.js",
29
31
  "test-idl": "nyc node --expose-gc ./scripts/run_test.js --idl",
30
32
  "lint": "eslint && node ./scripts/cpplint.js",
33
+ "test:asan": "bash scripts/run_asan_test.sh",
31
34
  "format": "clang-format -i -style=file ./src/*.cpp ./src/*.h && npx --yes prettier --write \"{lib,rosidl_gen,rostsd_gen,rosidl_parser,types,example,test,scripts,benchmark,rostsd_gen}/**/*.{js,md,ts}\" ./*.{js,md,ts}",
32
35
  "prepare": "husky",
33
36
  "coverage": "cat ./coverage/lcov.info | ./node_modules/coveralls/bin/coveralls.js",
@@ -65,13 +68,13 @@
65
68
  "lint-staged": "^16.2.0",
66
69
  "mocha": "^11.0.2",
67
70
  "node-gyp": "^12.1.0",
68
- "nyc": "^17.1.0",
71
+ "nyc": "^18.0.0",
69
72
  "prebuildify": "^6.0.1",
70
73
  "rimraf": "^6.0.1",
71
74
  "sinon": "^21.0.0",
72
75
  "tree-kill": "^1.2.2",
73
76
  "tsd": "^0.33.0",
74
- "typescript": "^5.7.2"
77
+ "typescript": "^6.0.2"
75
78
  },
76
79
  "dependencies": {
77
80
  "@rclnodejs/ref-array-di": "^1.2.2",
@@ -12,14 +12,11 @@
12
12
  // See the License for the specific language governing permissions and
13
13
  // limitations under the License.
14
14
 
15
- const fse = require('../lib/utils.js');
16
15
  const generateJSStructFromIDL = require('./idl_generator.js');
17
16
  const packages = require('./packages.js');
18
17
  const path = require('path');
19
- const idlConvertor = require('../rosidl_convertor/idl_convertor.js');
20
18
 
21
19
  const generatedRoot = path.join(__dirname, '../generated/');
22
- const idlPath = path.join(generatedRoot, 'share');
23
20
  const useIDL = !!process.argv.find((arg) => arg === '--idl');
24
21
 
25
22
  // Get target path from environment variable instead of workerData
@@ -32,17 +29,10 @@ async function generateInPath(targetPath) {
32
29
  (await packages.findPackagesInDirectory(targetPath)).values()
33
30
  );
34
31
  } else {
32
+ // Direct IDL parsing: pass .idl files to the generator which uses
33
+ // rosidl_parser to parse them directly (no .msg/.srv/.action conversion).
35
34
  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());
35
+ pkgsInfo = Array.from(idlPkgs.values());
46
36
  }
47
37
 
48
38
  await Promise.all(
@@ -305,7 +305,217 @@ async function generateJSStructFromIDL(pkg, dir) {
305
305
  pkg.actions.forEach((actionInfo) => {
306
306
  results.push(generateActionJSStruct(actionInfo, dir));
307
307
  });
308
+
309
+ // Handle .idl files directly (parsed via rosidl_parser, no .msg conversion)
310
+ if (pkg.idls) {
311
+ pkg.idls.forEach((idlInfo) => {
312
+ results.push(generateJSStructFromIdlFile(idlInfo, dir));
313
+ });
314
+ }
315
+
308
316
  await Promise.all(results);
309
317
  }
310
318
 
319
+ /**
320
+ * Parse an .idl file directly and generate the appropriate JS struct.
321
+ * This uses rosidl_parser to parse the IDL and produces the same JSON
322
+ * spec format, so the same templates can be used.
323
+ */
324
+ async function generateJSStructFromIdlFile(idlInfo, dir) {
325
+ const result = await parser.parseIdlFile(idlInfo.filePath);
326
+ const { type, spec } = result;
327
+
328
+ if (type === 'message') {
329
+ await generateMessageJSStructFromSpec(
330
+ {
331
+ pkgName: idlInfo.pkgName,
332
+ subFolder: idlInfo.subFolder,
333
+ interfaceName: idlInfo.interfaceName,
334
+ },
335
+ dir,
336
+ spec
337
+ );
338
+ } else if (type === 'service') {
339
+ // Generate request and response message JS structs
340
+ const requestMsg = generateMessageJSStructFromSpec(
341
+ {
342
+ pkgName: idlInfo.pkgName,
343
+ subFolder: idlInfo.subFolder,
344
+ interfaceName: `${idlInfo.interfaceName}_Request`,
345
+ },
346
+ dir,
347
+ spec.request
348
+ );
349
+
350
+ const responseMsg = generateMessageJSStructFromSpec(
351
+ {
352
+ pkgName: idlInfo.pkgName,
353
+ subFolder: idlInfo.subFolder,
354
+ interfaceName: `${idlInfo.interfaceName}_Response`,
355
+ },
356
+ dir,
357
+ spec.response
358
+ );
359
+
360
+ // Generate service JS struct
361
+ const serviceInfo = {
362
+ pkgName: idlInfo.pkgName,
363
+ subFolder: idlInfo.subFolder,
364
+ interfaceName: idlInfo.interfaceName,
365
+ };
366
+ const srv = generateServiceJSStruct(
367
+ serviceInfo,
368
+ dir,
369
+ /*isActionService=*/ false
370
+ );
371
+
372
+ await Promise.all([requestMsg, responseMsg, srv]);
373
+ } else if (type === 'action') {
374
+ // Generate goal, result, feedback message JS structs
375
+ const goalMsg = generateMessageJSStructFromSpec(
376
+ {
377
+ pkgName: idlInfo.pkgName,
378
+ subFolder: idlInfo.subFolder,
379
+ interfaceName: `${idlInfo.interfaceName}_Goal`,
380
+ },
381
+ dir,
382
+ spec.goal
383
+ );
384
+
385
+ const resultMsg = generateMessageJSStructFromSpec(
386
+ {
387
+ pkgName: idlInfo.pkgName,
388
+ subFolder: idlInfo.subFolder,
389
+ interfaceName: `${idlInfo.interfaceName}_Result`,
390
+ },
391
+ dir,
392
+ spec.result
393
+ );
394
+
395
+ const feedbackMsg = generateMessageJSStructFromSpec(
396
+ {
397
+ pkgName: idlInfo.pkgName,
398
+ subFolder: idlInfo.subFolder,
399
+ interfaceName: `${idlInfo.interfaceName}_Feedback`,
400
+ },
401
+ dir,
402
+ spec.feedback
403
+ );
404
+
405
+ // Generate derived action messages (SendGoal, GetResult, FeedbackMessage)
406
+ const sendGoalRequestSpec = actionMsgs.createSendGoalRequestSpec(
407
+ idlInfo.pkgName,
408
+ idlInfo.interfaceName
409
+ );
410
+ const sendGoalRequestMsg = generateMessageJSStructFromSpec(
411
+ {
412
+ pkgName: idlInfo.pkgName,
413
+ subFolder: idlInfo.subFolder,
414
+ interfaceName: `${idlInfo.interfaceName}_SendGoal_Request`,
415
+ },
416
+ dir,
417
+ sendGoalRequestSpec
418
+ );
419
+
420
+ const sendGoalResponseSpec = actionMsgs.createSendGoalResponseSpec(
421
+ idlInfo.pkgName,
422
+ idlInfo.interfaceName
423
+ );
424
+ const sendGoalResponseMsg = generateMessageJSStructFromSpec(
425
+ {
426
+ pkgName: idlInfo.pkgName,
427
+ subFolder: idlInfo.subFolder,
428
+ interfaceName: `${idlInfo.interfaceName}_SendGoal_Response`,
429
+ },
430
+ dir,
431
+ sendGoalResponseSpec
432
+ );
433
+
434
+ const sendGoalSrv = generateServiceJSStruct(
435
+ {
436
+ pkgName: idlInfo.pkgName,
437
+ subFolder: idlInfo.subFolder,
438
+ interfaceName: `${idlInfo.interfaceName}_SendGoal`,
439
+ },
440
+ dir
441
+ );
442
+
443
+ const getResultRequestSpec = actionMsgs.createGetResultRequestSpec(
444
+ idlInfo.pkgName,
445
+ idlInfo.interfaceName
446
+ );
447
+ const getResultRequestMsg = generateMessageJSStructFromSpec(
448
+ {
449
+ pkgName: idlInfo.pkgName,
450
+ subFolder: idlInfo.subFolder,
451
+ interfaceName: `${idlInfo.interfaceName}_GetResult_Request`,
452
+ },
453
+ dir,
454
+ getResultRequestSpec
455
+ );
456
+
457
+ const getResultResponseSpec = actionMsgs.createGetResultResponseSpec(
458
+ idlInfo.pkgName,
459
+ idlInfo.interfaceName
460
+ );
461
+ const getResultResponseMsg = generateMessageJSStructFromSpec(
462
+ {
463
+ pkgName: idlInfo.pkgName,
464
+ subFolder: idlInfo.subFolder,
465
+ interfaceName: `${idlInfo.interfaceName}_GetResult_Response`,
466
+ },
467
+ dir,
468
+ getResultResponseSpec
469
+ );
470
+
471
+ const getResultSrv = generateServiceJSStruct(
472
+ {
473
+ pkgName: idlInfo.pkgName,
474
+ subFolder: idlInfo.subFolder,
475
+ interfaceName: `${idlInfo.interfaceName}_GetResult`,
476
+ },
477
+ dir
478
+ );
479
+
480
+ const feedbackMessageSpec = actionMsgs.createFeedbackMessageSpec(
481
+ idlInfo.pkgName,
482
+ idlInfo.interfaceName
483
+ );
484
+ const feedbackMessageMsg = generateMessageJSStructFromSpec(
485
+ {
486
+ pkgName: idlInfo.pkgName,
487
+ subFolder: idlInfo.subFolder,
488
+ interfaceName: `${idlInfo.interfaceName}_FeedbackMessage`,
489
+ },
490
+ dir,
491
+ feedbackMessageSpec
492
+ );
493
+
494
+ const fileName =
495
+ idlInfo.pkgName +
496
+ '__' +
497
+ idlInfo.subFolder +
498
+ '__' +
499
+ idlInfo.interfaceName +
500
+ '.js';
501
+ const generatedCode = generateAction({ actionInfo: idlInfo });
502
+ const actionDir = path.join(dir, idlInfo.pkgName);
503
+ const action = writeGeneratedCode(actionDir, fileName, generatedCode);
504
+
505
+ await Promise.all([
506
+ goalMsg,
507
+ resultMsg,
508
+ feedbackMsg,
509
+ sendGoalRequestMsg,
510
+ sendGoalResponseMsg,
511
+ sendGoalSrv,
512
+ getResultRequestMsg,
513
+ getResultResponseMsg,
514
+ getResultSrv,
515
+ feedbackMessageMsg,
516
+ action,
517
+ ]);
518
+ }
519
+ }
520
+
311
521
  module.exports = generateJSStructFromIDL;
@@ -18,10 +18,8 @@ const fse = require('../lib/utils.js');
18
18
  const generateJSStructFromIDL = require('./idl_generator.js');
19
19
  const packages = require('./packages.js');
20
20
  const path = require('path');
21
- const idlConvertor = require('../rosidl_convertor/idl_convertor.js');
22
21
  const generatedRoot = path.join(__dirname, '../generated/');
23
22
  const serviceMsgPath = path.join(generatedRoot, 'srv_msg');
24
- const idlPath = path.join(generatedRoot, 'share');
25
23
  const useIDL = !!process.argv.find((arg) => arg === '--idl');
26
24
 
27
25
  function getInstalledPackagePaths() {
@@ -35,17 +33,10 @@ async function generateInPath(path) {
35
33
  (await packages.findPackagesInDirectory(path)).values()
36
34
  );
37
35
  } else {
36
+ // Direct IDL parsing: pass .idl files to the generator which uses
37
+ // rosidl_parser to parse them directly (no .msg/.srv/.action conversion).
38
38
  const idlPkgs = await packages.findPackagesInDirectory(path, useIDL);
39
- await fse.ensureDir(idlPath);
40
- const promises = [];
41
- idlPkgs.forEach((pkg) => {
42
- pkg.idls.forEach((idl) => {
43
- promises.push(idlConvertor(idl.pkgName, idl.filePath, idlPath));
44
- });
45
- });
46
- await Promise.all(promises);
47
- const pkgsFromIdl = await packages.findPackagesInDirectory(idlPath, false);
48
- pkgsInfo = Array.from(pkgsFromIdl.values());
39
+ pkgsInfo = Array.from(idlPkgs.values());
49
40
  }
50
41
 
51
42
  await Promise.all(