rclnodejs 1.7.0 → 1.8.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/binding.gyp +2 -0
- package/index.js +93 -0
- package/lib/action/client.js +54 -1
- package/lib/client.js +66 -1
- package/lib/clock.js +178 -0
- package/lib/clock_change.js +49 -0
- package/lib/clock_event.js +88 -0
- package/lib/errors.js +50 -0
- package/lib/logging.js +78 -0
- package/lib/message_introspector.js +123 -0
- package/lib/message_validation.js +512 -0
- package/lib/node.js +133 -1
- package/lib/node_options.js +40 -1
- package/lib/observable_subscription.js +105 -0
- package/lib/publisher.js +56 -1
- package/lib/qos.js +57 -0
- package/lib/subscription.js +8 -0
- package/lib/timer.js +42 -0
- package/lib/validator.js +63 -7
- package/package.json +4 -2
- 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/message_translator.js +0 -61
- package/scripts/config.js +1 -0
- package/src/addon.cpp +2 -0
- package/src/clock_event.cpp +268 -0
- package/src/clock_event.hpp +62 -0
- package/src/macros.h +2 -4
- package/src/rcl_action_server_bindings.cpp +21 -3
- package/src/rcl_bindings.cpp +59 -0
- package/src/rcl_context_bindings.cpp +5 -0
- package/src/rcl_graph_bindings.cpp +73 -0
- package/src/rcl_logging_bindings.cpp +158 -0
- package/src/rcl_node_bindings.cpp +14 -2
- package/src/rcl_publisher_bindings.cpp +12 -0
- package/src/rcl_service_bindings.cpp +7 -6
- package/src/rcl_subscription_bindings.cpp +51 -14
- package/src/rcl_time_point_bindings.cpp +135 -0
- package/src/rcl_timer_bindings.cpp +140 -0
- package/src/rcl_utilities.cpp +103 -2
- package/src/rcl_utilities.h +7 -1
- package/types/action_client.d.ts +27 -2
- package/types/base.d.ts +3 -0
- package/types/client.d.ts +29 -1
- package/types/clock.d.ts +86 -0
- package/types/clock_change.d.ts +27 -0
- package/types/clock_event.d.ts +51 -0
- package/types/errors.d.ts +49 -0
- package/types/index.d.ts +10 -0
- package/types/interfaces.d.ts +1 -1910
- package/types/logging.d.ts +32 -0
- package/types/message_introspector.d.ts +75 -0
- package/types/message_validation.d.ts +183 -0
- package/types/node.d.ts +67 -0
- package/types/node_options.d.ts +13 -0
- package/types/observable_subscription.d.ts +39 -0
- package/types/publisher.d.ts +28 -1
- package/types/qos.d.ts +18 -0
- package/types/subscription.d.ts +6 -0
- package/types/timer.d.ts +18 -0
- package/types/validator.d.ts +86 -0
package/lib/errors.js
CHANGED
|
@@ -162,6 +162,55 @@ class RangeValidationError extends ValidationError {
|
|
|
162
162
|
}
|
|
163
163
|
}
|
|
164
164
|
|
|
165
|
+
/**
|
|
166
|
+
* Message validation error for ROS message structure/type issues
|
|
167
|
+
* @class
|
|
168
|
+
* @extends ValidationError
|
|
169
|
+
*/
|
|
170
|
+
class MessageValidationError extends ValidationError {
|
|
171
|
+
/**
|
|
172
|
+
* @param {string} messageType - The ROS message type (e.g., 'std_msgs/msg/String')
|
|
173
|
+
* @param {Array<object>} issues - Array of validation issues
|
|
174
|
+
* @param {string} issues[].field - Field path where issue occurred
|
|
175
|
+
* @param {string} issues[].problem - Problem type (UNKNOWN_FIELD, TYPE_MISMATCH, etc.)
|
|
176
|
+
* @param {string} [issues[].expected] - Expected type or value
|
|
177
|
+
* @param {any} [issues[].received] - Actual value received
|
|
178
|
+
* @param {object} [options] - Additional options
|
|
179
|
+
*/
|
|
180
|
+
constructor(messageType, issues, options = {}) {
|
|
181
|
+
const issuesSummary = issues
|
|
182
|
+
.map((i) => `${i.field}: ${i.problem}`)
|
|
183
|
+
.join(', ');
|
|
184
|
+
super(`Invalid message for '${messageType}': ${issuesSummary}`, {
|
|
185
|
+
code: 'MESSAGE_VALIDATION_ERROR',
|
|
186
|
+
entityType: 'message',
|
|
187
|
+
entityName: messageType,
|
|
188
|
+
details: { issues },
|
|
189
|
+
...options,
|
|
190
|
+
});
|
|
191
|
+
this.messageType = messageType;
|
|
192
|
+
this.issues = issues;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
/**
|
|
196
|
+
* Get issues filtered by problem type
|
|
197
|
+
* @param {string} problemType - Problem type to filter by
|
|
198
|
+
* @returns {Array<object>} Filtered issues
|
|
199
|
+
*/
|
|
200
|
+
getIssuesByType(problemType) {
|
|
201
|
+
return this.issues.filter((i) => i.problem === problemType);
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
/**
|
|
205
|
+
* Check if a specific field has validation issues
|
|
206
|
+
* @param {string} fieldPath - Field path to check
|
|
207
|
+
* @returns {boolean} True if field has issues
|
|
208
|
+
*/
|
|
209
|
+
hasFieldIssue(fieldPath) {
|
|
210
|
+
return this.issues.some((i) => i.field === fieldPath);
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
|
|
165
214
|
/**
|
|
166
215
|
* ROS name validation error (topics, nodes, services)
|
|
167
216
|
* @class
|
|
@@ -541,6 +590,7 @@ module.exports = {
|
|
|
541
590
|
ValidationError,
|
|
542
591
|
TypeValidationError,
|
|
543
592
|
RangeValidationError,
|
|
593
|
+
MessageValidationError,
|
|
544
594
|
NameValidationError,
|
|
545
595
|
|
|
546
596
|
// Operation errors
|
package/lib/logging.js
CHANGED
|
@@ -17,6 +17,7 @@
|
|
|
17
17
|
const path = require('path');
|
|
18
18
|
const rclnodejs = require('./native_loader.js');
|
|
19
19
|
const { TypeValidationError } = require('./errors.js');
|
|
20
|
+
const Context = require('./context.js');
|
|
20
21
|
|
|
21
22
|
/**
|
|
22
23
|
* Enum for LoggingSeverity
|
|
@@ -89,6 +90,8 @@ class Caller {
|
|
|
89
90
|
class Logging {
|
|
90
91
|
constructor(name) {
|
|
91
92
|
this._name = name;
|
|
93
|
+
this._parentName = null;
|
|
94
|
+
this._subName = null;
|
|
92
95
|
}
|
|
93
96
|
|
|
94
97
|
/**
|
|
@@ -203,6 +206,50 @@ class Logging {
|
|
|
203
206
|
return this._name;
|
|
204
207
|
}
|
|
205
208
|
|
|
209
|
+
/**
|
|
210
|
+
* Create a child logger.
|
|
211
|
+
* @param {string} name - name of the child logger.
|
|
212
|
+
* @function
|
|
213
|
+
* @return {Logging} Return the child logger object.
|
|
214
|
+
*/
|
|
215
|
+
getChild(name) {
|
|
216
|
+
if (typeof name !== 'string' || !name) {
|
|
217
|
+
throw new Error('Child logger name must be a non-empty string.');
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
let fullname = name;
|
|
221
|
+
if (this._name) {
|
|
222
|
+
fullname = this._name + '.' + name;
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
const logger = new Logging(fullname);
|
|
226
|
+
if (this._name) {
|
|
227
|
+
if (
|
|
228
|
+
rclnodejs.addRosoutSublogger &&
|
|
229
|
+
rclnodejs.addRosoutSublogger(this._name, name)
|
|
230
|
+
) {
|
|
231
|
+
logger._parentName = this._name;
|
|
232
|
+
logger._subName = name;
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
return logger;
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
/**
|
|
239
|
+
* Destroy the logger and remove it from the parent logger if it is a child logger.
|
|
240
|
+
* @function
|
|
241
|
+
* @return {undefined}
|
|
242
|
+
*/
|
|
243
|
+
destroy() {
|
|
244
|
+
if (this._parentName && this._subName) {
|
|
245
|
+
if (rclnodejs.removeRosoutSublogger) {
|
|
246
|
+
rclnodejs.removeRosoutSublogger(this._parentName, this._subName);
|
|
247
|
+
}
|
|
248
|
+
this._parentName = null;
|
|
249
|
+
this._subName = null;
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
|
|
206
253
|
/**
|
|
207
254
|
* Create a logger by name.
|
|
208
255
|
* @param {string} name - name of the logger.
|
|
@@ -217,6 +264,37 @@ class Logging {
|
|
|
217
264
|
}
|
|
218
265
|
return new Logging(name);
|
|
219
266
|
}
|
|
267
|
+
|
|
268
|
+
/**
|
|
269
|
+
* Configure the logging system with the given context.
|
|
270
|
+
* @param {Context} context - The context to configure logging for.
|
|
271
|
+
* @function
|
|
272
|
+
* @return {undefined}
|
|
273
|
+
*/
|
|
274
|
+
static configure(context) {
|
|
275
|
+
if (!(context instanceof Context)) {
|
|
276
|
+
throw new TypeValidationError('context', context, 'Context');
|
|
277
|
+
}
|
|
278
|
+
rclnodejs.loggingConfigure(context.handle);
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
/**
|
|
282
|
+
* Shutdown the logging system.
|
|
283
|
+
* @function
|
|
284
|
+
* @return {undefined}
|
|
285
|
+
*/
|
|
286
|
+
static shutdown() {
|
|
287
|
+
rclnodejs.loggingFini();
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
/**
|
|
291
|
+
* Get the logging directory.
|
|
292
|
+
* @function
|
|
293
|
+
* @return {string} - The logging directory.
|
|
294
|
+
*/
|
|
295
|
+
static getLoggingDirectory() {
|
|
296
|
+
return rclnodejs.getLoggingDirectory();
|
|
297
|
+
}
|
|
220
298
|
}
|
|
221
299
|
|
|
222
300
|
module.exports = Logging;
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
// Copyright (c) 2025 Mahmoud Alghalayini. All rights reserved.
|
|
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 loader = require('./interface_loader.js');
|
|
18
|
+
const { toPlainObject } = require('../rosidl_gen/message_translator.js');
|
|
19
|
+
const { TypeValidationError } = require('./errors.js');
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* A utility class for inspecting ROS 2 message structure without using loader.loadInterface directly.
|
|
23
|
+
* Provides access to message schema, field names, and default values.
|
|
24
|
+
*/
|
|
25
|
+
class MessageIntrospector {
|
|
26
|
+
#typeClass;
|
|
27
|
+
#typeName;
|
|
28
|
+
#defaultsCache;
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Create a new MessageIntrospector for a ROS 2 message type.
|
|
32
|
+
* @param {string} typeName - The full message type name (e.g., 'geometry_msgs/msg/Twist')
|
|
33
|
+
* @throws {TypeValidationError} If typeName is not a non-empty string
|
|
34
|
+
* @throws {Error} If the message type cannot be loaded
|
|
35
|
+
*/
|
|
36
|
+
constructor(typeName) {
|
|
37
|
+
if (!typeName || typeof typeName !== 'string') {
|
|
38
|
+
throw new TypeValidationError('typeName', typeName, 'non-empty string', {
|
|
39
|
+
entityType: 'MessageIntrospector',
|
|
40
|
+
});
|
|
41
|
+
}
|
|
42
|
+
this.#typeName = typeName;
|
|
43
|
+
this.#typeClass = loader.loadInterface(typeName);
|
|
44
|
+
this.#defaultsCache = null;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Get the full message type name.
|
|
49
|
+
* @returns {string} The message type name (e.g., 'geometry_msgs/msg/Twist')
|
|
50
|
+
*/
|
|
51
|
+
get typeName() {
|
|
52
|
+
return this.#typeName;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Get the underlying ROS message class.
|
|
57
|
+
* @returns {Function} The message type class constructor
|
|
58
|
+
*/
|
|
59
|
+
get typeClass() {
|
|
60
|
+
return this.#typeClass;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Get the field names of the message.
|
|
65
|
+
* @returns {string[]} Array of field names
|
|
66
|
+
*/
|
|
67
|
+
get fields() {
|
|
68
|
+
const def = this.#typeClass.ROSMessageDef;
|
|
69
|
+
return def.fields.map((f) => f.name);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Get the ROSMessageDef schema for the message type.
|
|
74
|
+
* @returns {object} The message definition schema
|
|
75
|
+
*/
|
|
76
|
+
get schema() {
|
|
77
|
+
return this.#typeClass.ROSMessageDef;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Get the default values for all fields.
|
|
82
|
+
* Creates a new instance of the message and converts it to a plain object.
|
|
83
|
+
* Result is cached for performance.
|
|
84
|
+
* @returns {object} A plain object with all default values
|
|
85
|
+
*/
|
|
86
|
+
get defaults() {
|
|
87
|
+
if (this.#defaultsCache === null) {
|
|
88
|
+
const instance = new this.#typeClass();
|
|
89
|
+
this.#defaultsCache = toPlainObject(instance);
|
|
90
|
+
}
|
|
91
|
+
return this.#deepClone(this.#defaultsCache);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* Deep clone an object.
|
|
96
|
+
* @param {any} obj - Object to clone
|
|
97
|
+
* @returns {any} Cloned object
|
|
98
|
+
* @private
|
|
99
|
+
*/
|
|
100
|
+
#deepClone(obj) {
|
|
101
|
+
if (obj === null || typeof obj !== 'object') {
|
|
102
|
+
return obj;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
if (Array.isArray(obj)) {
|
|
106
|
+
return obj.map((item) => this.#deepClone(item));
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
if (ArrayBuffer.isView(obj) && !(obj instanceof DataView)) {
|
|
110
|
+
return obj.slice();
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
const cloned = {};
|
|
114
|
+
for (const key in obj) {
|
|
115
|
+
if (Object.prototype.hasOwnProperty.call(obj, key)) {
|
|
116
|
+
cloned[key] = this.#deepClone(obj[key]);
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
return cloned;
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
module.exports = MessageIntrospector;
|