rclnodejs 1.5.2 → 1.7.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/index.js +79 -3
- package/lib/action/client.js +55 -9
- package/lib/action/deferred.js +8 -2
- package/lib/action/server.js +10 -1
- package/lib/action/uuid.js +4 -1
- package/lib/client.js +152 -3
- package/lib/clock.js +4 -1
- package/lib/context.js +12 -2
- package/lib/duration.js +37 -12
- package/lib/errors.js +571 -0
- package/lib/event_handler.js +21 -4
- package/lib/interface_loader.js +52 -12
- package/lib/lifecycle.js +8 -2
- package/lib/logging.js +12 -3
- package/lib/message_serialization.js +179 -0
- package/lib/native_loader.js +9 -4
- package/lib/node.js +283 -47
- package/lib/parameter.js +176 -45
- package/lib/parameter_client.js +506 -0
- package/lib/parameter_watcher.js +309 -0
- package/lib/qos.js +22 -5
- package/lib/rate.js +6 -1
- package/lib/serialization.js +7 -2
- package/lib/subscription.js +16 -1
- package/lib/time.js +136 -21
- package/lib/time_source.js +13 -4
- package/lib/utils.js +313 -0
- package/lib/validator.js +11 -12
- package/package.json +2 -7
- 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_convertor/idl_convertor.js +3 -2
- package/rosidl_gen/generate_worker.js +1 -1
- package/rosidl_gen/idl_generator.js +11 -24
- package/rosidl_gen/index.js +1 -1
- package/rosidl_gen/templates/action-template.js +68 -0
- package/rosidl_gen/templates/message-template.js +1113 -0
- package/rosidl_gen/templates/service-event-template.js +31 -0
- package/rosidl_gen/templates/service-template.js +44 -0
- package/rosidl_parser/rosidl_parser.js +2 -2
- package/third_party/ref-napi/lib/ref.js +0 -45
- package/types/base.d.ts +3 -0
- package/types/client.d.ts +36 -0
- package/types/errors.d.ts +447 -0
- package/types/index.d.ts +17 -0
- package/types/interfaces.d.ts +1910 -1
- package/types/node.d.ts +56 -1
- package/types/parameter_client.d.ts +252 -0
- package/types/parameter_watcher.d.ts +104 -0
- package/rosidl_gen/templates/CMakeLists.dot +0 -40
- package/rosidl_gen/templates/action.dot +0 -50
- package/rosidl_gen/templates/message.dot +0 -851
- package/rosidl_gen/templates/package.dot +0 -16
- package/rosidl_gen/templates/service.dot +0 -26
- package/rosidl_gen/templates/service_event.dot +0 -10
package/index.js
CHANGED
|
@@ -18,7 +18,7 @@ const DistroUtils = require('./lib/distro.js');
|
|
|
18
18
|
const RMWUtils = require('./lib/rmw.js');
|
|
19
19
|
const { Clock, ROSClock } = require('./lib/clock.js');
|
|
20
20
|
const ClockType = require('./lib/clock_type.js');
|
|
21
|
-
const compareVersions = require('
|
|
21
|
+
const { compareVersions } = require('./lib/utils.js');
|
|
22
22
|
const Context = require('./lib/context.js');
|
|
23
23
|
const debug = require('debug')('rclnodejs');
|
|
24
24
|
const Duration = require('./lib/duration.js');
|
|
@@ -47,6 +47,7 @@ const ActionUuid = require('./lib/action/uuid.js');
|
|
|
47
47
|
const ClientGoalHandle = require('./lib/action/client_goal_handle.js');
|
|
48
48
|
const { CancelResponse, GoalResponse } = require('./lib/action/response.js');
|
|
49
49
|
const ServerGoalHandle = require('./lib/action/server_goal_handle.js');
|
|
50
|
+
const { toJSONSafe, toJSONString } = require('./lib/message_serialization.js');
|
|
50
51
|
const {
|
|
51
52
|
getActionClientNamesAndTypesByNode,
|
|
52
53
|
getActionServerNamesAndTypesByNode,
|
|
@@ -57,6 +58,9 @@ const {
|
|
|
57
58
|
serializeMessage,
|
|
58
59
|
deserializeMessage,
|
|
59
60
|
} = require('./lib/serialization.js');
|
|
61
|
+
const ParameterClient = require('./lib/parameter_client.js');
|
|
62
|
+
const errors = require('./lib/errors.js');
|
|
63
|
+
const ParameterWatcher = require('./lib/parameter_watcher.js');
|
|
60
64
|
const { spawn } = require('child_process');
|
|
61
65
|
|
|
62
66
|
/**
|
|
@@ -216,6 +220,12 @@ let rcl = {
|
|
|
216
220
|
/** {@link ParameterType} */
|
|
217
221
|
ParameterType: ParameterType,
|
|
218
222
|
|
|
223
|
+
/** {@link ParameterClient} class */
|
|
224
|
+
ParameterClient: ParameterClient,
|
|
225
|
+
|
|
226
|
+
/** {@link ParameterWatcher} class */
|
|
227
|
+
ParameterWatcher: ParameterWatcher,
|
|
228
|
+
|
|
219
229
|
/** {@link QoS} class */
|
|
220
230
|
QoS: QoS,
|
|
221
231
|
|
|
@@ -364,8 +374,7 @@ let rcl = {
|
|
|
364
374
|
|
|
365
375
|
const version = await getCurrentGeneratorVersion();
|
|
366
376
|
const forced =
|
|
367
|
-
version === null ||
|
|
368
|
-
compareVersions.compare(version, generator.version(), '<');
|
|
377
|
+
version === null || compareVersions(version, generator.version(), '<');
|
|
369
378
|
if (forced) {
|
|
370
379
|
debug(
|
|
371
380
|
'The generator will begin to create JavaScript code from ROS IDL files...'
|
|
@@ -539,6 +548,73 @@ let rcl = {
|
|
|
539
548
|
* @return {Promise<{process: ChildProcess}>} A Promise that resolves with the process.
|
|
540
549
|
*/
|
|
541
550
|
ros2Launch: ros2Launch,
|
|
551
|
+
|
|
552
|
+
/**
|
|
553
|
+
* Convert a message object to be JSON-safe by converting TypedArrays to regular arrays
|
|
554
|
+
* and handling BigInt, Infinity, NaN, etc. for JSON serialization.
|
|
555
|
+
* @param {*} obj - The message object to convert
|
|
556
|
+
* @returns {*} A JSON-safe version of the object
|
|
557
|
+
*/
|
|
558
|
+
toJSONSafe: toJSONSafe,
|
|
559
|
+
|
|
560
|
+
/**
|
|
561
|
+
* Convert a message object to a JSON string with proper handling of TypedArrays,
|
|
562
|
+
* BigInt, and other non-JSON-serializable values.
|
|
563
|
+
* @param {*} obj - The message object to convert
|
|
564
|
+
* @param {number} [space] - Space parameter for JSON.stringify formatting
|
|
565
|
+
* @returns {string} The JSON string representation
|
|
566
|
+
*/
|
|
567
|
+
toJSONString: toJSONString,
|
|
568
|
+
|
|
569
|
+
// Error classes for structured error handling
|
|
570
|
+
/** {@link RclNodeError} - Base error class for all rclnodejs errors */
|
|
571
|
+
RclNodeError: errors.RclNodeError,
|
|
572
|
+
|
|
573
|
+
/** {@link ValidationError} - Error thrown when validation fails */
|
|
574
|
+
ValidationError: errors.ValidationError,
|
|
575
|
+
/** {@link TypeValidationError} - Type validation error */
|
|
576
|
+
TypeValidationError: errors.TypeValidationError,
|
|
577
|
+
/** {@link RangeValidationError} - Range/value validation error */
|
|
578
|
+
RangeValidationError: errors.RangeValidationError,
|
|
579
|
+
/** {@link NameValidationError} - ROS name validation error */
|
|
580
|
+
NameValidationError: errors.NameValidationError,
|
|
581
|
+
|
|
582
|
+
/** {@link OperationError} - Base class for operation/runtime errors */
|
|
583
|
+
OperationError: errors.OperationError,
|
|
584
|
+
/** {@link TimeoutError} - Request timeout error */
|
|
585
|
+
TimeoutError: errors.TimeoutError,
|
|
586
|
+
/** {@link AbortError} - Request abortion error */
|
|
587
|
+
AbortError: errors.AbortError,
|
|
588
|
+
/** {@link ServiceNotFoundError} - Service not available error */
|
|
589
|
+
ServiceNotFoundError: errors.ServiceNotFoundError,
|
|
590
|
+
/** {@link NodeNotFoundError} - Remote node not found error */
|
|
591
|
+
NodeNotFoundError: errors.NodeNotFoundError,
|
|
592
|
+
|
|
593
|
+
/** {@link ParameterError} - Base error for parameter operations */
|
|
594
|
+
ParameterError: errors.ParameterError,
|
|
595
|
+
/** {@link ParameterNotFoundError} - Parameter not found error */
|
|
596
|
+
ParameterNotFoundError: errors.ParameterNotFoundError,
|
|
597
|
+
/** {@link ParameterTypeError} - Parameter type mismatch error */
|
|
598
|
+
ParameterTypeError: errors.ParameterTypeError,
|
|
599
|
+
/** {@link ReadOnlyParameterError} - Read-only parameter modification error */
|
|
600
|
+
ReadOnlyParameterError: errors.ReadOnlyParameterError,
|
|
601
|
+
|
|
602
|
+
/** {@link TopicError} - Base error for topic operations */
|
|
603
|
+
TopicError: errors.TopicError,
|
|
604
|
+
/** {@link PublisherError} - Publisher-specific error */
|
|
605
|
+
PublisherError: errors.PublisherError,
|
|
606
|
+
/** {@link SubscriptionError} - Subscription-specific error */
|
|
607
|
+
SubscriptionError: errors.SubscriptionError,
|
|
608
|
+
|
|
609
|
+
/** {@link ActionError} - Base error for action operations */
|
|
610
|
+
ActionError: errors.ActionError,
|
|
611
|
+
/** {@link GoalRejectedError} - Goal rejected by action server */
|
|
612
|
+
GoalRejectedError: errors.GoalRejectedError,
|
|
613
|
+
/** {@link ActionServerNotFoundError} - Action server not found */
|
|
614
|
+
ActionServerNotFoundError: errors.ActionServerNotFoundError,
|
|
615
|
+
|
|
616
|
+
/** {@link NativeError} - Wraps errors from native C++ layer */
|
|
617
|
+
NativeError: errors.NativeError,
|
|
542
618
|
};
|
|
543
619
|
|
|
544
620
|
const _sigHandler = () => {
|
package/lib/action/client.js
CHANGED
|
@@ -23,6 +23,11 @@ const DistroUtils = require('../distro.js');
|
|
|
23
23
|
const Entity = require('../entity.js');
|
|
24
24
|
const loader = require('../interface_loader.js');
|
|
25
25
|
const QoS = require('../qos.js');
|
|
26
|
+
const {
|
|
27
|
+
TypeValidationError,
|
|
28
|
+
ActionError,
|
|
29
|
+
OperationError,
|
|
30
|
+
} = require('../errors.js');
|
|
26
31
|
|
|
27
32
|
/**
|
|
28
33
|
* @class - ROS Action client.
|
|
@@ -103,7 +108,14 @@ class ActionClient extends Entity {
|
|
|
103
108
|
if (goalHandle.accepted) {
|
|
104
109
|
let uuid = ActionUuid.fromMessage(goalHandle.goalId).toString();
|
|
105
110
|
if (this._goalHandles.has(uuid)) {
|
|
106
|
-
throw new
|
|
111
|
+
throw new ActionError(
|
|
112
|
+
`Two goals were accepted with the same ID (${uuid})`,
|
|
113
|
+
this._actionName,
|
|
114
|
+
{
|
|
115
|
+
code: 'DUPLICATE_GOAL_ID',
|
|
116
|
+
details: { goalId: uuid },
|
|
117
|
+
}
|
|
118
|
+
);
|
|
107
119
|
}
|
|
108
120
|
|
|
109
121
|
this._goalHandles.set(uuid, goalHandle);
|
|
@@ -210,8 +222,14 @@ class ActionClient extends Entity {
|
|
|
210
222
|
);
|
|
211
223
|
|
|
212
224
|
if (this._pendingGoalRequests.has(sequenceNumber)) {
|
|
213
|
-
throw new
|
|
214
|
-
`Sequence (${sequenceNumber}) conflicts with pending goal request
|
|
225
|
+
throw new OperationError(
|
|
226
|
+
`Sequence (${sequenceNumber}) conflicts with pending goal request`,
|
|
227
|
+
{
|
|
228
|
+
code: 'SEQUENCE_CONFLICT',
|
|
229
|
+
entityType: 'action client',
|
|
230
|
+
entityName: this._actionName,
|
|
231
|
+
details: { sequenceNumber: sequenceNumber, requestType: 'goal' },
|
|
232
|
+
}
|
|
215
233
|
);
|
|
216
234
|
}
|
|
217
235
|
|
|
@@ -274,7 +292,15 @@ class ActionClient extends Entity {
|
|
|
274
292
|
*/
|
|
275
293
|
_cancelGoal(goalHandle) {
|
|
276
294
|
if (!(goalHandle instanceof ClientGoalHandle)) {
|
|
277
|
-
throw new
|
|
295
|
+
throw new TypeValidationError(
|
|
296
|
+
'goalHandle',
|
|
297
|
+
goalHandle,
|
|
298
|
+
'ClientGoalHandle',
|
|
299
|
+
{
|
|
300
|
+
entityType: 'action client',
|
|
301
|
+
entityName: this._actionName,
|
|
302
|
+
}
|
|
303
|
+
);
|
|
278
304
|
}
|
|
279
305
|
|
|
280
306
|
let request = new ActionInterfaces.CancelGoal.Request();
|
|
@@ -290,8 +316,14 @@ class ActionClient extends Entity {
|
|
|
290
316
|
request.serialize()
|
|
291
317
|
);
|
|
292
318
|
if (this._pendingCancelRequests.has(sequenceNumber)) {
|
|
293
|
-
throw new
|
|
294
|
-
`Sequence (${sequenceNumber}) conflicts with pending cancel request
|
|
319
|
+
throw new OperationError(
|
|
320
|
+
`Sequence (${sequenceNumber}) conflicts with pending cancel request`,
|
|
321
|
+
{
|
|
322
|
+
code: 'SEQUENCE_CONFLICT',
|
|
323
|
+
entityType: 'action client',
|
|
324
|
+
entityName: this._actionName,
|
|
325
|
+
details: { sequenceNumber: sequenceNumber, requestType: 'cancel' },
|
|
326
|
+
}
|
|
295
327
|
);
|
|
296
328
|
}
|
|
297
329
|
|
|
@@ -316,7 +348,15 @@ class ActionClient extends Entity {
|
|
|
316
348
|
*/
|
|
317
349
|
_getResult(goalHandle) {
|
|
318
350
|
if (!(goalHandle instanceof ClientGoalHandle)) {
|
|
319
|
-
throw new
|
|
351
|
+
throw new TypeValidationError(
|
|
352
|
+
'goalHandle',
|
|
353
|
+
goalHandle,
|
|
354
|
+
'ClientGoalHandle',
|
|
355
|
+
{
|
|
356
|
+
entityType: 'action client',
|
|
357
|
+
entityName: this._actionName,
|
|
358
|
+
}
|
|
359
|
+
);
|
|
320
360
|
}
|
|
321
361
|
|
|
322
362
|
let request = new this.typeClass.impl.GetResultService.Request();
|
|
@@ -327,8 +367,14 @@ class ActionClient extends Entity {
|
|
|
327
367
|
request.serialize()
|
|
328
368
|
);
|
|
329
369
|
if (this._pendingResultRequests.has(sequenceNumber)) {
|
|
330
|
-
throw new
|
|
331
|
-
`Sequence (${sequenceNumber}) conflicts with pending result request
|
|
370
|
+
throw new OperationError(
|
|
371
|
+
`Sequence (${sequenceNumber}) conflicts with pending result request`,
|
|
372
|
+
{
|
|
373
|
+
code: 'SEQUENCE_CONFLICT',
|
|
374
|
+
entityType: 'action client',
|
|
375
|
+
entityName: this._actionName,
|
|
376
|
+
details: { sequenceNumber: sequenceNumber, requestType: 'result' },
|
|
377
|
+
}
|
|
332
378
|
);
|
|
333
379
|
}
|
|
334
380
|
|
package/lib/action/deferred.js
CHANGED
|
@@ -14,6 +14,8 @@
|
|
|
14
14
|
|
|
15
15
|
'use strict';
|
|
16
16
|
|
|
17
|
+
const { TypeValidationError } = require('../errors.js');
|
|
18
|
+
|
|
17
19
|
/**
|
|
18
20
|
* @class - Wraps a promise allowing it to be resolved elsewhere.
|
|
19
21
|
* @ignore
|
|
@@ -42,7 +44,9 @@ class Deferred {
|
|
|
42
44
|
*/
|
|
43
45
|
beforeSetResultCallback(callback) {
|
|
44
46
|
if (typeof callback !== 'function') {
|
|
45
|
-
throw new
|
|
47
|
+
throw new TypeValidationError('callback', callback, 'function', {
|
|
48
|
+
entityType: 'deferred promise',
|
|
49
|
+
});
|
|
46
50
|
}
|
|
47
51
|
|
|
48
52
|
this._beforeSetResultCallback = callback;
|
|
@@ -70,7 +74,9 @@ class Deferred {
|
|
|
70
74
|
*/
|
|
71
75
|
setDoneCallback(callback) {
|
|
72
76
|
if (typeof callback !== 'function') {
|
|
73
|
-
throw new
|
|
77
|
+
throw new TypeValidationError('callback', callback, 'function', {
|
|
78
|
+
entityType: 'deferred promise',
|
|
79
|
+
});
|
|
74
80
|
}
|
|
75
81
|
|
|
76
82
|
this._promise.finally(() => callback(this._result));
|
package/lib/action/server.js
CHANGED
|
@@ -23,6 +23,7 @@ const { CancelResponse, GoalEvent, GoalResponse } = require('./response.js');
|
|
|
23
23
|
const loader = require('../interface_loader.js');
|
|
24
24
|
const QoS = require('../qos.js');
|
|
25
25
|
const ServerGoalHandle = require('./server_goal_handle.js');
|
|
26
|
+
const { TypeValidationError } = require('../errors.js');
|
|
26
27
|
|
|
27
28
|
/**
|
|
28
29
|
* Execute the goal.
|
|
@@ -219,7 +220,15 @@ class ActionServer extends Entity {
|
|
|
219
220
|
*/
|
|
220
221
|
registerExecuteCallback(executeCallback) {
|
|
221
222
|
if (typeof executeCallback !== 'function') {
|
|
222
|
-
throw new
|
|
223
|
+
throw new TypeValidationError(
|
|
224
|
+
'executeCallback',
|
|
225
|
+
executeCallback,
|
|
226
|
+
'function',
|
|
227
|
+
{
|
|
228
|
+
entityType: 'action server',
|
|
229
|
+
entityName: this._actionName,
|
|
230
|
+
}
|
|
231
|
+
);
|
|
223
232
|
}
|
|
224
233
|
|
|
225
234
|
this._callback = executeCallback;
|
package/lib/action/uuid.js
CHANGED
|
@@ -16,6 +16,7 @@
|
|
|
16
16
|
|
|
17
17
|
const ActionInterfaces = require('./interfaces.js');
|
|
18
18
|
const { randomUUID } = require('crypto');
|
|
19
|
+
const { TypeValidationError } = require('../errors.js');
|
|
19
20
|
|
|
20
21
|
/**
|
|
21
22
|
* @class - Represents a unique identifier used by actions.
|
|
@@ -31,7 +32,9 @@ class ActionUuid {
|
|
|
31
32
|
constructor(bytes) {
|
|
32
33
|
if (bytes) {
|
|
33
34
|
if (!(bytes instanceof Uint8Array)) {
|
|
34
|
-
throw new
|
|
35
|
+
throw new TypeValidationError('bytes', bytes, 'Uint8Array', {
|
|
36
|
+
entityType: 'action uuid',
|
|
37
|
+
});
|
|
35
38
|
}
|
|
36
39
|
|
|
37
40
|
this._bytes = bytes;
|
package/lib/client.js
CHANGED
|
@@ -17,8 +17,58 @@
|
|
|
17
17
|
const rclnodejs = require('./native_loader.js');
|
|
18
18
|
const DistroUtils = require('./distro.js');
|
|
19
19
|
const Entity = require('./entity.js');
|
|
20
|
+
const {
|
|
21
|
+
TypeValidationError,
|
|
22
|
+
TimeoutError,
|
|
23
|
+
AbortError,
|
|
24
|
+
} = require('./errors.js');
|
|
20
25
|
const debug = require('debug')('rclnodejs:client');
|
|
21
26
|
|
|
27
|
+
// Polyfill for AbortSignal.any() for Node.js <= 20.3.0
|
|
28
|
+
// AbortSignal.any() was added in Node.js 20.3.0
|
|
29
|
+
// See https://developer.mozilla.org/en-US/docs/Web/API/AbortSignal/any_static
|
|
30
|
+
if (!AbortSignal.any) {
|
|
31
|
+
AbortSignal.any = function (signals) {
|
|
32
|
+
// Filter out null/undefined values and validate inputs
|
|
33
|
+
const validSignals = Array.isArray(signals)
|
|
34
|
+
? signals.filter((signal) => signal != null)
|
|
35
|
+
: [];
|
|
36
|
+
|
|
37
|
+
// If no valid signals, return a never-aborting signal
|
|
38
|
+
if (validSignals.length === 0) {
|
|
39
|
+
return new AbortController().signal;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
const controller = new AbortController();
|
|
43
|
+
const listeners = [];
|
|
44
|
+
|
|
45
|
+
// Cleanup function to remove all event listeners
|
|
46
|
+
const cleanup = () => {
|
|
47
|
+
listeners.forEach(({ signal, listener }) => {
|
|
48
|
+
signal.removeEventListener('abort', listener);
|
|
49
|
+
});
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
for (const signal of validSignals) {
|
|
53
|
+
if (signal.aborted) {
|
|
54
|
+
cleanup();
|
|
55
|
+
controller.abort(signal.reason);
|
|
56
|
+
return controller.signal;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
const listener = () => {
|
|
60
|
+
cleanup();
|
|
61
|
+
controller.abort(signal.reason);
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
signal.addEventListener('abort', listener);
|
|
65
|
+
listeners.push({ signal, listener });
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
return controller.signal;
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
|
|
22
72
|
/**
|
|
23
73
|
* @class - Class representing a Client in ROS
|
|
24
74
|
* @hideconstructor
|
|
@@ -33,7 +83,7 @@ class Client extends Entity {
|
|
|
33
83
|
}
|
|
34
84
|
|
|
35
85
|
/**
|
|
36
|
-
* This callback is called when a
|
|
86
|
+
* This callback is called when a response is sent back from service
|
|
37
87
|
* @callback ResponseCallback
|
|
38
88
|
* @param {Object} response - The response sent from the service
|
|
39
89
|
* @see [Client.sendRequest]{@link Client#sendRequest}
|
|
@@ -43,7 +93,7 @@ class Client extends Entity {
|
|
|
43
93
|
*/
|
|
44
94
|
|
|
45
95
|
/**
|
|
46
|
-
* Send the request and will be notified asynchronously if receiving the
|
|
96
|
+
* Send the request and will be notified asynchronously if receiving the response.
|
|
47
97
|
* @param {object} request - The request to be submitted.
|
|
48
98
|
* @param {ResponseCallback} callback - Thc callback function for receiving the server response.
|
|
49
99
|
* @return {undefined}
|
|
@@ -51,7 +101,10 @@ class Client extends Entity {
|
|
|
51
101
|
*/
|
|
52
102
|
sendRequest(request, callback) {
|
|
53
103
|
if (typeof callback !== 'function') {
|
|
54
|
-
throw new
|
|
104
|
+
throw new TypeValidationError('callback', callback, 'function', {
|
|
105
|
+
entityType: 'service',
|
|
106
|
+
entityName: this._serviceName,
|
|
107
|
+
});
|
|
55
108
|
}
|
|
56
109
|
|
|
57
110
|
let requestToSend =
|
|
@@ -65,6 +118,102 @@ class Client extends Entity {
|
|
|
65
118
|
this._sequenceNumberToCallbackMap.set(sequenceNumber, callback);
|
|
66
119
|
}
|
|
67
120
|
|
|
121
|
+
/**
|
|
122
|
+
* Send the request and return a Promise that resolves with the response.
|
|
123
|
+
* @param {object} request - The request to be submitted.
|
|
124
|
+
* @param {object} [options] - Optional parameters for the request.
|
|
125
|
+
* @param {number} [options.timeout] - Timeout in milliseconds for the request.
|
|
126
|
+
* @param {AbortSignal} [options.signal] - AbortSignal to cancel the request.
|
|
127
|
+
* @return {Promise<object>} Promise that resolves with the service response.
|
|
128
|
+
* @throws {module:rclnodejs.TimeoutError} If the request times out (when options.timeout is exceeded).
|
|
129
|
+
* @throws {module:rclnodejs.AbortError} If the request is manually aborted (via options.signal).
|
|
130
|
+
* @throws {Error} If the request fails for other reasons.
|
|
131
|
+
*/
|
|
132
|
+
sendRequestAsync(request, options = {}) {
|
|
133
|
+
return new Promise((resolve, reject) => {
|
|
134
|
+
let sequenceNumber = null;
|
|
135
|
+
let isResolved = false;
|
|
136
|
+
let isTimeout = false;
|
|
137
|
+
|
|
138
|
+
const cleanup = () => {
|
|
139
|
+
if (sequenceNumber !== null) {
|
|
140
|
+
this._sequenceNumberToCallbackMap.delete(sequenceNumber);
|
|
141
|
+
}
|
|
142
|
+
isResolved = true;
|
|
143
|
+
};
|
|
144
|
+
|
|
145
|
+
let effectiveSignal = options.signal;
|
|
146
|
+
|
|
147
|
+
if (options.timeout !== undefined && options.timeout >= 0) {
|
|
148
|
+
const timeoutSignal = AbortSignal.timeout(options.timeout);
|
|
149
|
+
|
|
150
|
+
timeoutSignal.addEventListener('abort', () => {
|
|
151
|
+
isTimeout = true;
|
|
152
|
+
});
|
|
153
|
+
|
|
154
|
+
if (options.signal) {
|
|
155
|
+
effectiveSignal = AbortSignal.any([options.signal, timeoutSignal]);
|
|
156
|
+
} else {
|
|
157
|
+
effectiveSignal = timeoutSignal;
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
if (effectiveSignal) {
|
|
162
|
+
if (effectiveSignal.aborted) {
|
|
163
|
+
const error = isTimeout
|
|
164
|
+
? new TimeoutError('Service request', options.timeout, {
|
|
165
|
+
entityType: 'service',
|
|
166
|
+
entityName: this._serviceName,
|
|
167
|
+
})
|
|
168
|
+
: new AbortError('Service request', undefined, {
|
|
169
|
+
entityType: 'service',
|
|
170
|
+
entityName: this._serviceName,
|
|
171
|
+
});
|
|
172
|
+
reject(error);
|
|
173
|
+
return;
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
effectiveSignal.addEventListener('abort', () => {
|
|
177
|
+
if (!isResolved) {
|
|
178
|
+
cleanup();
|
|
179
|
+
const error = isTimeout
|
|
180
|
+
? new TimeoutError('Service request', options.timeout, {
|
|
181
|
+
entityType: 'service',
|
|
182
|
+
entityName: this._serviceName,
|
|
183
|
+
})
|
|
184
|
+
: new AbortError('Service request', undefined, {
|
|
185
|
+
entityType: 'service',
|
|
186
|
+
entityName: this._serviceName,
|
|
187
|
+
});
|
|
188
|
+
reject(error);
|
|
189
|
+
}
|
|
190
|
+
});
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
try {
|
|
194
|
+
let requestToSend =
|
|
195
|
+
request instanceof this._typeClass.Request
|
|
196
|
+
? request
|
|
197
|
+
: new this._typeClass.Request(request);
|
|
198
|
+
|
|
199
|
+
let rawRequest = requestToSend.serialize();
|
|
200
|
+
sequenceNumber = rclnodejs.sendRequest(this._handle, rawRequest);
|
|
201
|
+
|
|
202
|
+
debug(`Client has sent a ${this._serviceName} request (async).`);
|
|
203
|
+
|
|
204
|
+
this._sequenceNumberToCallbackMap.set(sequenceNumber, (response) => {
|
|
205
|
+
if (!isResolved) {
|
|
206
|
+
cleanup();
|
|
207
|
+
resolve(response);
|
|
208
|
+
}
|
|
209
|
+
});
|
|
210
|
+
} catch (error) {
|
|
211
|
+
cleanup();
|
|
212
|
+
reject(error);
|
|
213
|
+
}
|
|
214
|
+
});
|
|
215
|
+
}
|
|
216
|
+
|
|
68
217
|
processResponse(sequenceNumber, response) {
|
|
69
218
|
if (this._sequenceNumberToCallbackMap.has(sequenceNumber)) {
|
|
70
219
|
debug(`Client has received ${this._serviceName} response from service.`);
|
package/lib/clock.js
CHANGED
|
@@ -17,6 +17,7 @@
|
|
|
17
17
|
const rclnodejs = require('./native_loader.js');
|
|
18
18
|
const Time = require('./time.js');
|
|
19
19
|
const ClockType = require('./clock_type.js');
|
|
20
|
+
const { TypeValidationError } = require('./errors.js');
|
|
20
21
|
|
|
21
22
|
/**
|
|
22
23
|
* @class - Class representing a Clock in ROS
|
|
@@ -102,7 +103,9 @@ class ROSClock extends Clock {
|
|
|
102
103
|
|
|
103
104
|
set rosTimeOverride(time) {
|
|
104
105
|
if (!(time instanceof Time)) {
|
|
105
|
-
throw new
|
|
106
|
+
throw new TypeValidationError('time', time, 'Time', {
|
|
107
|
+
entityType: 'clock',
|
|
108
|
+
});
|
|
106
109
|
}
|
|
107
110
|
rclnodejs.setRosTimeOverride(this._handle, time._handle);
|
|
108
111
|
}
|
package/lib/context.js
CHANGED
|
@@ -15,6 +15,7 @@
|
|
|
15
15
|
'use strict';
|
|
16
16
|
|
|
17
17
|
const rclnodejs = require('./native_loader.js');
|
|
18
|
+
const { OperationError } = require('./errors.js');
|
|
18
19
|
|
|
19
20
|
let defaultContext = null;
|
|
20
21
|
|
|
@@ -184,11 +185,20 @@ class Context {
|
|
|
184
185
|
|
|
185
186
|
onNodeCreated(node) {
|
|
186
187
|
if (!node) {
|
|
187
|
-
throw new
|
|
188
|
+
throw new OperationError('Node must be defined to add to Context', {
|
|
189
|
+
code: 'NODE_UNDEFINED',
|
|
190
|
+
entityType: 'context',
|
|
191
|
+
});
|
|
188
192
|
}
|
|
189
193
|
|
|
190
194
|
if (this.isShutdown()) {
|
|
191
|
-
throw new
|
|
195
|
+
throw new OperationError(
|
|
196
|
+
'Can not add a Node to a Context that is shutdown',
|
|
197
|
+
{
|
|
198
|
+
code: 'CONTEXT_SHUTDOWN',
|
|
199
|
+
entityType: 'context',
|
|
200
|
+
}
|
|
201
|
+
);
|
|
192
202
|
}
|
|
193
203
|
|
|
194
204
|
if (this._nodes.includes(node)) {
|
package/lib/duration.js
CHANGED
|
@@ -15,6 +15,7 @@
|
|
|
15
15
|
'use strict';
|
|
16
16
|
|
|
17
17
|
const rclnodejs = require('./native_loader.js');
|
|
18
|
+
const { TypeValidationError, RangeValidationError } = require('./errors.js');
|
|
18
19
|
const S_TO_NS = 10n ** 9n;
|
|
19
20
|
|
|
20
21
|
/**
|
|
@@ -29,17 +30,31 @@ class Duration {
|
|
|
29
30
|
*/
|
|
30
31
|
constructor(seconds = 0n, nanoseconds = 0n) {
|
|
31
32
|
if (typeof seconds !== 'bigint') {
|
|
32
|
-
throw new
|
|
33
|
+
throw new TypeValidationError('seconds', seconds, 'bigint', {
|
|
34
|
+
entityType: 'duration',
|
|
35
|
+
});
|
|
33
36
|
}
|
|
34
37
|
|
|
35
38
|
if (typeof nanoseconds !== 'bigint') {
|
|
36
|
-
throw new
|
|
39
|
+
throw new TypeValidationError('nanoseconds', nanoseconds, 'bigint', {
|
|
40
|
+
entityType: 'duration',
|
|
41
|
+
});
|
|
37
42
|
}
|
|
38
43
|
|
|
39
44
|
const total = seconds * S_TO_NS + nanoseconds;
|
|
40
45
|
if (total >= 2n ** 63n) {
|
|
41
|
-
throw new
|
|
42
|
-
'
|
|
46
|
+
throw new RangeValidationError(
|
|
47
|
+
'total nanoseconds',
|
|
48
|
+
total,
|
|
49
|
+
'< 2^63 (max C duration)',
|
|
50
|
+
{
|
|
51
|
+
entityType: 'duration',
|
|
52
|
+
details: {
|
|
53
|
+
seconds: seconds,
|
|
54
|
+
nanoseconds: nanoseconds,
|
|
55
|
+
total: total,
|
|
56
|
+
},
|
|
57
|
+
}
|
|
43
58
|
);
|
|
44
59
|
}
|
|
45
60
|
|
|
@@ -67,9 +82,9 @@ class Duration {
|
|
|
67
82
|
if (other instanceof Duration) {
|
|
68
83
|
return this._nanoseconds === other.nanoseconds;
|
|
69
84
|
}
|
|
70
|
-
throw new
|
|
71
|
-
|
|
72
|
-
);
|
|
85
|
+
throw new TypeValidationError('other', other, 'Duration', {
|
|
86
|
+
entityType: 'duration',
|
|
87
|
+
});
|
|
73
88
|
}
|
|
74
89
|
|
|
75
90
|
/**
|
|
@@ -81,7 +96,9 @@ class Duration {
|
|
|
81
96
|
if (other instanceof Duration) {
|
|
82
97
|
return this._nanoseconds !== other.nanoseconds;
|
|
83
98
|
}
|
|
84
|
-
throw new
|
|
99
|
+
throw new TypeValidationError('other', other, 'Duration', {
|
|
100
|
+
entityType: 'duration',
|
|
101
|
+
});
|
|
85
102
|
}
|
|
86
103
|
|
|
87
104
|
/**
|
|
@@ -93,7 +110,9 @@ class Duration {
|
|
|
93
110
|
if (other instanceof Duration) {
|
|
94
111
|
return this._nanoseconds < other.nanoseconds;
|
|
95
112
|
}
|
|
96
|
-
throw new
|
|
113
|
+
throw new TypeValidationError('other', other, 'Duration', {
|
|
114
|
+
entityType: 'duration',
|
|
115
|
+
});
|
|
97
116
|
}
|
|
98
117
|
|
|
99
118
|
/**
|
|
@@ -105,7 +124,9 @@ class Duration {
|
|
|
105
124
|
if (other instanceof Duration) {
|
|
106
125
|
return this._nanoseconds <= other.nanoseconds;
|
|
107
126
|
}
|
|
108
|
-
throw new
|
|
127
|
+
throw new TypeValidationError('other', other, 'Duration', {
|
|
128
|
+
entityType: 'duration',
|
|
129
|
+
});
|
|
109
130
|
}
|
|
110
131
|
|
|
111
132
|
/**
|
|
@@ -117,7 +138,9 @@ class Duration {
|
|
|
117
138
|
if (other instanceof Duration) {
|
|
118
139
|
return this._nanoseconds > other.nanoseconds;
|
|
119
140
|
}
|
|
120
|
-
throw new
|
|
141
|
+
throw new TypeValidationError('other', other, 'Duration', {
|
|
142
|
+
entityType: 'duration',
|
|
143
|
+
});
|
|
121
144
|
}
|
|
122
145
|
|
|
123
146
|
/**
|
|
@@ -129,7 +152,9 @@ class Duration {
|
|
|
129
152
|
if (other instanceof Duration) {
|
|
130
153
|
return this._nanoseconds >= other.nanoseconds;
|
|
131
154
|
}
|
|
132
|
-
throw new
|
|
155
|
+
throw new TypeValidationError('other', other, 'Duration', {
|
|
156
|
+
entityType: 'duration',
|
|
157
|
+
});
|
|
133
158
|
}
|
|
134
159
|
}
|
|
135
160
|
|