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.
- package/README.md +46 -37
- package/index.js +62 -23
- package/lib/action/client.js +67 -3
- package/lib/action/server.js +1 -3
- package/lib/distro.js +2 -1
- package/lib/lifecycle_publisher.js +2 -2
- package/lib/message_info.js +94 -0
- package/lib/node.js +90 -14
- package/lib/parameter.js +5 -9
- package/lib/parameter_event_handler.js +468 -0
- package/lib/parameter_watcher.js +12 -12
- package/lib/service.js +8 -4
- package/lib/subscription.js +38 -5
- package/lib/time_source.js +3 -20
- package/lib/timer.js +2 -1
- package/lib/wait_for_message.js +111 -0
- package/package.json +7 -4
- 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/rosidl_gen/generate_worker.js +3 -13
- package/rosidl_gen/idl_generator.js +210 -0
- package/rosidl_gen/index.js +3 -12
- package/rosidl_gen/packages.js +1 -3
- package/rosidl_gen/primitive_types.js +2 -2
- package/rosidl_parser/idl_parser.py +437 -0
- package/rosidl_parser/parser.py +2 -4
- package/rosidl_parser/rosidl_parser.js +27 -0
- package/scripts/run_asan_test.sh +118 -0
- package/src/executor.cpp +37 -2
- package/src/executor.h +11 -0
- package/src/macros.h +2 -2
- package/src/rcl_action_client_bindings.cpp +88 -12
- package/src/rcl_action_server_bindings.cpp +24 -13
- package/src/rcl_client_bindings.cpp +13 -5
- package/src/rcl_context_bindings.cpp +10 -11
- package/src/rcl_graph_bindings.cpp +2 -2
- package/src/rcl_guard_condition_bindings.cpp +12 -3
- package/src/rcl_lifecycle_bindings.cpp +34 -15
- package/src/rcl_node_bindings.cpp +11 -4
- package/src/rcl_publisher_bindings.cpp +12 -3
- package/src/rcl_service_bindings.cpp +12 -3
- package/src/rcl_subscription_bindings.cpp +92 -21
- package/src/rcl_timer_bindings.cpp +24 -9
- package/src/rcl_type_description_service_bindings.cpp +9 -1
- 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/rosidl_convertor/README.md +0 -298
- package/rosidl_convertor/idl_convertor.js +0 -50
- package/rosidl_convertor/idl_convertor.py +0 -1250
- 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/lib/parameter_watcher.js
CHANGED
|
@@ -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
|
-
#
|
|
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.#
|
|
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.#
|
|
137
|
-
this.#
|
|
138
|
-
|
|
139
|
-
|
|
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.#
|
|
230
|
+
if (this.#eventHandler) {
|
|
231
231
|
try {
|
|
232
|
-
this.#
|
|
232
|
+
this.#eventHandler.destroy();
|
|
233
233
|
} catch (error) {
|
|
234
|
-
debug('Error destroying
|
|
234
|
+
debug('Error destroying event handler: %s', error.message);
|
|
235
235
|
}
|
|
236
|
-
this.#
|
|
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))
|
|
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) {
|
package/lib/subscription.js
CHANGED
|
@@ -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
|
-
|
|
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.
|
|
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.
|
|
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.
|
package/lib/time_source.js
CHANGED
|
@@ -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
|
@@ -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.
|
|
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": "
|
|
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": "^
|
|
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": "^
|
|
77
|
+
"typescript": "^6.0.2"
|
|
75
78
|
},
|
|
76
79
|
"dependencies": {
|
|
77
80
|
"@rclnodejs/ref-array-di": "^1.2.2",
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
@@ -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
|
-
|
|
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;
|
package/rosidl_gen/index.js
CHANGED
|
@@ -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
|
-
|
|
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(
|