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/binding.gyp
CHANGED
|
@@ -26,6 +26,7 @@
|
|
|
26
26
|
'./src/rcl_action_server_bindings.cpp',
|
|
27
27
|
'./src/rcl_bindings.cpp',
|
|
28
28
|
'./src/rcl_client_bindings.cpp',
|
|
29
|
+
'./src/clock_event.cpp',
|
|
29
30
|
'./src/rcl_context_bindings.cpp',
|
|
30
31
|
'./src/rcl_graph_bindings.cpp',
|
|
31
32
|
'./src/rcl_guard_condition_bindings.cpp',
|
|
@@ -67,6 +68,7 @@
|
|
|
67
68
|
'-lrcl_action',
|
|
68
69
|
'-lrcl_lifecycle',
|
|
69
70
|
'-lrcutils',
|
|
71
|
+
'-lrcl_logging_interface',
|
|
70
72
|
'-lrcl_yaml_param_parser',
|
|
71
73
|
'-lrcpputils',
|
|
72
74
|
'-lrmw',
|
package/index.js
CHANGED
|
@@ -17,7 +17,9 @@
|
|
|
17
17
|
const DistroUtils = require('./lib/distro.js');
|
|
18
18
|
const RMWUtils = require('./lib/rmw.js');
|
|
19
19
|
const { Clock, ROSClock } = require('./lib/clock.js');
|
|
20
|
+
const ClockEvent = require('./lib/clock_event.js');
|
|
20
21
|
const ClockType = require('./lib/clock_type.js');
|
|
22
|
+
const ClockChange = require('./lib/clock_change.js');
|
|
21
23
|
const { compareVersions } = require('./lib/utils.js');
|
|
22
24
|
const Context = require('./lib/context.js');
|
|
23
25
|
const debug = require('debug')('rclnodejs');
|
|
@@ -61,7 +63,18 @@ const {
|
|
|
61
63
|
const ParameterClient = require('./lib/parameter_client.js');
|
|
62
64
|
const errors = require('./lib/errors.js');
|
|
63
65
|
const ParameterWatcher = require('./lib/parameter_watcher.js');
|
|
66
|
+
const MessageIntrospector = require('./lib/message_introspector.js');
|
|
67
|
+
const ObservableSubscription = require('./lib/observable_subscription.js');
|
|
64
68
|
const { spawn } = require('child_process');
|
|
69
|
+
const {
|
|
70
|
+
ValidationProblem,
|
|
71
|
+
getMessageSchema,
|
|
72
|
+
getFieldNames,
|
|
73
|
+
getFieldType,
|
|
74
|
+
validateMessage,
|
|
75
|
+
assertValidMessage,
|
|
76
|
+
createMessageValidator,
|
|
77
|
+
} = require('./lib/message_validation.js');
|
|
65
78
|
|
|
66
79
|
/**
|
|
67
80
|
* Get the version of the generator that was used for the currently present interfaces.
|
|
@@ -178,9 +191,15 @@ let rcl = {
|
|
|
178
191
|
/** {@link Clock} class */
|
|
179
192
|
Clock: Clock,
|
|
180
193
|
|
|
194
|
+
/** {@link ClockEvent} class */
|
|
195
|
+
ClockEvent: ClockEvent,
|
|
196
|
+
|
|
181
197
|
/** {@link ClockType} enum */
|
|
182
198
|
ClockType: ClockType,
|
|
183
199
|
|
|
200
|
+
/** {@link ClockChange} enum */
|
|
201
|
+
ClockChange: ClockChange,
|
|
202
|
+
|
|
184
203
|
/** {@link Context} class */
|
|
185
204
|
Context: Context,
|
|
186
205
|
|
|
@@ -226,6 +245,9 @@ let rcl = {
|
|
|
226
245
|
/** {@link ParameterWatcher} class */
|
|
227
246
|
ParameterWatcher: ParameterWatcher,
|
|
228
247
|
|
|
248
|
+
/** {@link ObservableSubscription} class */
|
|
249
|
+
ObservableSubscription: ObservableSubscription,
|
|
250
|
+
|
|
229
251
|
/** {@link QoS} class */
|
|
230
252
|
QoS: QoS,
|
|
231
253
|
|
|
@@ -386,6 +408,21 @@ let rcl = {
|
|
|
386
408
|
_rosVersionChecked = true;
|
|
387
409
|
},
|
|
388
410
|
|
|
411
|
+
/**
|
|
412
|
+
* Remove ROS-specific arguments from the given argument list.
|
|
413
|
+
* @param {string[]} argv - The argument list to process.
|
|
414
|
+
* @return {string[]} The argument list with ROS arguments removed.
|
|
415
|
+
*/
|
|
416
|
+
removeROSArgs(argv) {
|
|
417
|
+
if (!Array.isArray(argv)) {
|
|
418
|
+
throw new TypeError('argv must be an array.');
|
|
419
|
+
}
|
|
420
|
+
if (!argv.every((argument) => typeof argument === 'string')) {
|
|
421
|
+
throw new TypeError('argv elements must be strings (and not null).');
|
|
422
|
+
}
|
|
423
|
+
return rclnodejs.removeROSArgs(argv);
|
|
424
|
+
},
|
|
425
|
+
|
|
389
426
|
/**
|
|
390
427
|
* Start detection and processing of units of work.
|
|
391
428
|
* @param {Node} node - The node to be spun up.
|
|
@@ -566,6 +603,57 @@ let rcl = {
|
|
|
566
603
|
*/
|
|
567
604
|
toJSONString: toJSONString,
|
|
568
605
|
|
|
606
|
+
/** {@link ValidationProblem} - Enum of validation problem types */
|
|
607
|
+
ValidationProblem: ValidationProblem,
|
|
608
|
+
|
|
609
|
+
/**
|
|
610
|
+
* Get the schema definition for a message type
|
|
611
|
+
* @param {function|string|object} typeClass - Message type class or identifier
|
|
612
|
+
* @returns {object|null} Schema definition with fields and constants
|
|
613
|
+
*/
|
|
614
|
+
getMessageSchema: getMessageSchema,
|
|
615
|
+
|
|
616
|
+
/**
|
|
617
|
+
* Get field names for a message type
|
|
618
|
+
* @param {function|string|object} typeClass - Message type class or identifier
|
|
619
|
+
* @returns {string[]} Array of field names
|
|
620
|
+
*/
|
|
621
|
+
getFieldNames: getFieldNames,
|
|
622
|
+
|
|
623
|
+
/**
|
|
624
|
+
* Get type information for a specific field
|
|
625
|
+
* @param {function|string|object} typeClass - Message type class or identifier
|
|
626
|
+
* @param {string} fieldName - Name of the field
|
|
627
|
+
* @returns {object|null} Field type information or null if not found
|
|
628
|
+
*/
|
|
629
|
+
getFieldType: getFieldType,
|
|
630
|
+
|
|
631
|
+
/**
|
|
632
|
+
* Validate a message object against its schema
|
|
633
|
+
* @param {object} obj - Plain object to validate
|
|
634
|
+
* @param {function|string|object} typeClass - Message type class or identifier
|
|
635
|
+
* @param {object} [options] - Validation options
|
|
636
|
+
* @returns {{valid: boolean, issues: Array<object>}} Validation result
|
|
637
|
+
*/
|
|
638
|
+
validateMessage: validateMessage,
|
|
639
|
+
|
|
640
|
+
/**
|
|
641
|
+
* Validate a message and throw if invalid
|
|
642
|
+
* @param {object} obj - Plain object to validate
|
|
643
|
+
* @param {function|string|object} typeClass - Message type class or identifier
|
|
644
|
+
* @param {object} [options] - Validation options
|
|
645
|
+
* @throws {MessageValidationError} If validation fails
|
|
646
|
+
*/
|
|
647
|
+
assertValidMessage: assertValidMessage,
|
|
648
|
+
|
|
649
|
+
/**
|
|
650
|
+
* Create a validator function for a specific message type
|
|
651
|
+
* @param {function|string|object} typeClass - Message type class or identifier
|
|
652
|
+
* @param {object} [defaultOptions] - Default validation options
|
|
653
|
+
* @returns {function} Validator function
|
|
654
|
+
*/
|
|
655
|
+
createMessageValidator: createMessageValidator,
|
|
656
|
+
|
|
569
657
|
// Error classes for structured error handling
|
|
570
658
|
/** {@link RclNodeError} - Base error class for all rclnodejs errors */
|
|
571
659
|
RclNodeError: errors.RclNodeError,
|
|
@@ -576,6 +664,8 @@ let rcl = {
|
|
|
576
664
|
TypeValidationError: errors.TypeValidationError,
|
|
577
665
|
/** {@link RangeValidationError} - Range/value validation error */
|
|
578
666
|
RangeValidationError: errors.RangeValidationError,
|
|
667
|
+
/** {@link MessageValidationError} - Message structure/type validation error */
|
|
668
|
+
MessageValidationError: errors.MessageValidationError,
|
|
579
669
|
/** {@link NameValidationError} - ROS name validation error */
|
|
580
670
|
NameValidationError: errors.NameValidationError,
|
|
581
671
|
|
|
@@ -644,3 +734,6 @@ const Lifecycle = require('./lib/lifecycle.js');
|
|
|
644
734
|
|
|
645
735
|
/** Lifecycle namespace */
|
|
646
736
|
rcl.lifecycle = Lifecycle;
|
|
737
|
+
|
|
738
|
+
/** {@link MessageIntrospector} class */
|
|
739
|
+
rcl.MessageIntrospector = MessageIntrospector;
|
package/lib/action/client.js
CHANGED
|
@@ -28,6 +28,7 @@ const {
|
|
|
28
28
|
ActionError,
|
|
29
29
|
OperationError,
|
|
30
30
|
} = require('../errors.js');
|
|
31
|
+
const { assertValidMessage } = require('../message_validation.js');
|
|
31
32
|
|
|
32
33
|
/**
|
|
33
34
|
* @class - ROS Action client.
|
|
@@ -80,6 +81,12 @@ class ActionClient extends Entity {
|
|
|
80
81
|
this._options.qos.statusSubQosProfile =
|
|
81
82
|
this._options.qos.statusSubQosProfile || QoS.profileActionStatusDefault;
|
|
82
83
|
|
|
84
|
+
this._validateGoals = this._options.validateGoals || false;
|
|
85
|
+
this._validationOptions = this._options.validationOptions || {
|
|
86
|
+
strict: true,
|
|
87
|
+
checkTypes: true,
|
|
88
|
+
};
|
|
89
|
+
|
|
83
90
|
let type = this.typeClass.type();
|
|
84
91
|
|
|
85
92
|
this._handle = rclnodejs.actionCreateClient(
|
|
@@ -200,6 +207,34 @@ class ActionClient extends Entity {
|
|
|
200
207
|
}
|
|
201
208
|
}
|
|
202
209
|
|
|
210
|
+
/**
|
|
211
|
+
* Whether goals will be validated before sending.
|
|
212
|
+
* @type {boolean}
|
|
213
|
+
*/
|
|
214
|
+
get willValidateGoal() {
|
|
215
|
+
return this._validateGoals;
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
/**
|
|
219
|
+
* Enable or disable goal validation for this action client.
|
|
220
|
+
* @param {boolean} value - Whether to validate goals
|
|
221
|
+
*/
|
|
222
|
+
set willValidateGoal(value) {
|
|
223
|
+
this._validateGoals = value;
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
/**
|
|
227
|
+
* Set validation options for this action client.
|
|
228
|
+
* @param {object} options - Validation options
|
|
229
|
+
* @param {boolean} [options.strict=true] - Throw on unknown fields
|
|
230
|
+
* @param {boolean} [options.checkTypes=true] - Validate field types
|
|
231
|
+
*/
|
|
232
|
+
setValidation(options) {
|
|
233
|
+
if (options && Object.keys(options).length > 0) {
|
|
234
|
+
this._validationOptions = { ...this._validationOptions, ...options };
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
|
|
203
238
|
/**
|
|
204
239
|
* Send a goal and wait for the goal ACK asynchronously.
|
|
205
240
|
*
|
|
@@ -209,9 +244,27 @@ class ActionClient extends Entity {
|
|
|
209
244
|
* @param {object} goal - The goal request.
|
|
210
245
|
* @param {function} feedbackCallback - Callback function for feedback associated with the goal.
|
|
211
246
|
* @param {object} goalUuid - Universally unique identifier for the goal. If None, then a random UUID is generated.
|
|
247
|
+
* @param {object} [options] - Send options
|
|
248
|
+
* @param {boolean} [options.validate] - Override validateGoals setting for this call
|
|
212
249
|
* @returns {Promise} - A Promise to a goal handle that resolves when the goal request has been accepted or rejected.
|
|
250
|
+
* @throws {MessageValidationError} If validation is enabled and goal is invalid
|
|
213
251
|
*/
|
|
214
|
-
sendGoal(goal, feedbackCallback, goalUuid) {
|
|
252
|
+
sendGoal(goal, feedbackCallback, goalUuid, options = {}) {
|
|
253
|
+
const shouldValidate =
|
|
254
|
+
options.validate !== undefined ? options.validate : this._validateGoals;
|
|
255
|
+
|
|
256
|
+
if (shouldValidate) {
|
|
257
|
+
const GoalType = this._typeClass.impl.SendGoalService.Request;
|
|
258
|
+
const goalInstance = new GoalType();
|
|
259
|
+
if (goalInstance.goal && goalInstance.goal.constructor) {
|
|
260
|
+
assertValidMessage(
|
|
261
|
+
goal,
|
|
262
|
+
goalInstance.goal.constructor,
|
|
263
|
+
this._validationOptions
|
|
264
|
+
);
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
|
|
215
268
|
let request = new this._typeClass.impl.SendGoalService.Request();
|
|
216
269
|
request['goal_id'] = goalUuid || ActionUuid.randomMessage();
|
|
217
270
|
request.goal = goal;
|
package/lib/client.js
CHANGED
|
@@ -22,6 +22,7 @@ const {
|
|
|
22
22
|
TimeoutError,
|
|
23
23
|
AbortError,
|
|
24
24
|
} = require('./errors.js');
|
|
25
|
+
const { assertValidMessage } = require('./message_validation.js');
|
|
25
26
|
const debug = require('debug')('rclnodejs:client');
|
|
26
27
|
|
|
27
28
|
// Polyfill for AbortSignal.any() for Node.js <= 20.3.0
|
|
@@ -80,6 +81,39 @@ class Client extends Entity {
|
|
|
80
81
|
this._nodeHandle = nodeHandle;
|
|
81
82
|
this._serviceName = serviceName;
|
|
82
83
|
this._sequenceNumberToCallbackMap = new Map();
|
|
84
|
+
this._validateRequests = options.validateRequests || false;
|
|
85
|
+
this._validationOptions = options.validationOptions || {
|
|
86
|
+
strict: true,
|
|
87
|
+
checkTypes: true,
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* Whether requests will be validated before sending.
|
|
93
|
+
* @type {boolean}
|
|
94
|
+
*/
|
|
95
|
+
get willValidateRequest() {
|
|
96
|
+
return this._validateRequests;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* Enable or disable request validation for this client.
|
|
101
|
+
* @param {boolean} value - Whether to validate requests
|
|
102
|
+
*/
|
|
103
|
+
set willValidateRequest(value) {
|
|
104
|
+
this._validateRequests = value;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* Set validation options for this client.
|
|
109
|
+
* @param {object} options - Validation options
|
|
110
|
+
* @param {boolean} [options.strict=true] - Throw on unknown fields
|
|
111
|
+
* @param {boolean} [options.checkTypes=true] - Validate field types
|
|
112
|
+
*/
|
|
113
|
+
setValidation(options) {
|
|
114
|
+
if (options && Object.keys(options).length > 0) {
|
|
115
|
+
this._validationOptions = { ...this._validationOptions, ...options };
|
|
116
|
+
}
|
|
83
117
|
}
|
|
84
118
|
|
|
85
119
|
/**
|
|
@@ -96,10 +130,13 @@ class Client extends Entity {
|
|
|
96
130
|
* Send the request and will be notified asynchronously if receiving the response.
|
|
97
131
|
* @param {object} request - The request to be submitted.
|
|
98
132
|
* @param {ResponseCallback} callback - Thc callback function for receiving the server response.
|
|
133
|
+
* @param {object} [options] - Send options
|
|
134
|
+
* @param {boolean} [options.validate] - Override validateRequests setting for this call
|
|
99
135
|
* @return {undefined}
|
|
136
|
+
* @throws {MessageValidationError} If validation is enabled and request is invalid
|
|
100
137
|
* @see {@link ResponseCallback}
|
|
101
138
|
*/
|
|
102
|
-
sendRequest(request, callback) {
|
|
139
|
+
sendRequest(request, callback, options = {}) {
|
|
103
140
|
if (typeof callback !== 'function') {
|
|
104
141
|
throw new TypeValidationError('callback', callback, 'function', {
|
|
105
142
|
entityType: 'service',
|
|
@@ -107,6 +144,19 @@ class Client extends Entity {
|
|
|
107
144
|
});
|
|
108
145
|
}
|
|
109
146
|
|
|
147
|
+
const shouldValidate =
|
|
148
|
+
options.validate !== undefined
|
|
149
|
+
? options.validate
|
|
150
|
+
: this._validateRequests;
|
|
151
|
+
|
|
152
|
+
if (shouldValidate && !(request instanceof this._typeClass.Request)) {
|
|
153
|
+
assertValidMessage(
|
|
154
|
+
request,
|
|
155
|
+
this._typeClass.Request,
|
|
156
|
+
this._validationOptions
|
|
157
|
+
);
|
|
158
|
+
}
|
|
159
|
+
|
|
110
160
|
let requestToSend =
|
|
111
161
|
request instanceof this._typeClass.Request
|
|
112
162
|
? request
|
|
@@ -124,9 +174,11 @@ class Client extends Entity {
|
|
|
124
174
|
* @param {object} [options] - Optional parameters for the request.
|
|
125
175
|
* @param {number} [options.timeout] - Timeout in milliseconds for the request.
|
|
126
176
|
* @param {AbortSignal} [options.signal] - AbortSignal to cancel the request.
|
|
177
|
+
* @param {boolean} [options.validate] - Override validateRequests setting for this call
|
|
127
178
|
* @return {Promise<object>} Promise that resolves with the service response.
|
|
128
179
|
* @throws {module:rclnodejs.TimeoutError} If the request times out (when options.timeout is exceeded).
|
|
129
180
|
* @throws {module:rclnodejs.AbortError} If the request is manually aborted (via options.signal).
|
|
181
|
+
* @throws {module:rclnodejs.MessageValidationError} If validation is enabled and request is invalid.
|
|
130
182
|
* @throws {Error} If the request fails for other reasons.
|
|
131
183
|
*/
|
|
132
184
|
sendRequestAsync(request, options = {}) {
|
|
@@ -191,6 +243,19 @@ class Client extends Entity {
|
|
|
191
243
|
}
|
|
192
244
|
|
|
193
245
|
try {
|
|
246
|
+
const shouldValidate =
|
|
247
|
+
options.validate !== undefined
|
|
248
|
+
? options.validate
|
|
249
|
+
: this._validateRequests;
|
|
250
|
+
|
|
251
|
+
if (shouldValidate && !(request instanceof this._typeClass.Request)) {
|
|
252
|
+
assertValidMessage(
|
|
253
|
+
request,
|
|
254
|
+
this._typeClass.Request,
|
|
255
|
+
this._validationOptions
|
|
256
|
+
);
|
|
257
|
+
}
|
|
258
|
+
|
|
194
259
|
let requestToSend =
|
|
195
260
|
request instanceof this._typeClass.Request
|
|
196
261
|
? request
|
package/lib/clock.js
CHANGED
|
@@ -17,6 +17,9 @@
|
|
|
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 ClockChange = require('./clock_change.js');
|
|
21
|
+
const Context = require('./context.js');
|
|
22
|
+
const ClockEvent = require('./clock_event.js');
|
|
20
23
|
const { TypeValidationError } = require('./errors.js');
|
|
21
24
|
|
|
22
25
|
/**
|
|
@@ -48,6 +51,71 @@ class Clock {
|
|
|
48
51
|
return this._clockType;
|
|
49
52
|
}
|
|
50
53
|
|
|
54
|
+
/**
|
|
55
|
+
* Add a clock callback.
|
|
56
|
+
* @param {object} callbackObject - The object containing callback methods.
|
|
57
|
+
* @param {Function} [callbackObject._pre_callback] - Optional callback invoked before a time jump. Takes no arguments.
|
|
58
|
+
* @param {Function} [callbackObject._post_callback] - Optional callback invoked after a time jump.
|
|
59
|
+
* Receives a jumpInfo object with properties:
|
|
60
|
+
* - clock_change {number}: Type of clock change that occurred.
|
|
61
|
+
* - delta {bigint}: Time delta in nanoseconds.
|
|
62
|
+
* @param {boolean} onClockChange - Whether to call the callback on clock change.
|
|
63
|
+
* @param {bigint} minForward - Minimum forward jump in nanoseconds to trigger the callback.
|
|
64
|
+
* @param {bigint} minBackward - Minimum backward jump in nanoseconds to trigger the callback.
|
|
65
|
+
*/
|
|
66
|
+
addClockCallback(callbackObject, onClockChange, minForward, minBackward) {
|
|
67
|
+
if (typeof callbackObject !== 'object' || callbackObject === null) {
|
|
68
|
+
throw new TypeValidationError(
|
|
69
|
+
'callbackObject',
|
|
70
|
+
callbackObject,
|
|
71
|
+
'object',
|
|
72
|
+
{
|
|
73
|
+
entityType: 'clock',
|
|
74
|
+
}
|
|
75
|
+
);
|
|
76
|
+
}
|
|
77
|
+
if (typeof onClockChange !== 'boolean') {
|
|
78
|
+
throw new TypeValidationError('onClockChange', onClockChange, 'boolean', {
|
|
79
|
+
entityType: 'clock',
|
|
80
|
+
});
|
|
81
|
+
}
|
|
82
|
+
if (typeof minForward !== 'bigint') {
|
|
83
|
+
throw new TypeValidationError('minForward', minForward, 'bigint', {
|
|
84
|
+
entityType: 'clock',
|
|
85
|
+
});
|
|
86
|
+
}
|
|
87
|
+
if (typeof minBackward !== 'bigint') {
|
|
88
|
+
throw new TypeValidationError('minBackward', minBackward, 'bigint', {
|
|
89
|
+
entityType: 'clock',
|
|
90
|
+
});
|
|
91
|
+
}
|
|
92
|
+
rclnodejs.clockAddJumpCallback(
|
|
93
|
+
this._handle,
|
|
94
|
+
callbackObject,
|
|
95
|
+
onClockChange,
|
|
96
|
+
minForward,
|
|
97
|
+
minBackward
|
|
98
|
+
);
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* Remove a clock callback.
|
|
103
|
+
* @param {object} callbackObject - The callback object that was previously registered with addClockCallback().
|
|
104
|
+
*/
|
|
105
|
+
removeClockCallback(callbackObject) {
|
|
106
|
+
if (typeof callbackObject !== 'object' || callbackObject === null) {
|
|
107
|
+
throw new TypeValidationError(
|
|
108
|
+
'callbackObject',
|
|
109
|
+
callbackObject,
|
|
110
|
+
'object',
|
|
111
|
+
{
|
|
112
|
+
entityType: 'clock',
|
|
113
|
+
}
|
|
114
|
+
);
|
|
115
|
+
}
|
|
116
|
+
rclnodejs.clockRemoveJumpCallback(this._handle, callbackObject);
|
|
117
|
+
}
|
|
118
|
+
|
|
51
119
|
/**
|
|
52
120
|
* Return the current time.
|
|
53
121
|
* @return {Time} Return the current time.
|
|
@@ -56,6 +124,116 @@ class Clock {
|
|
|
56
124
|
const nowInNanosec = rclnodejs.clockGetNow(this._handle);
|
|
57
125
|
return new Time(0n, nowInNanosec, this._clockType);
|
|
58
126
|
}
|
|
127
|
+
|
|
128
|
+
/**
|
|
129
|
+
* Sleep until a specific time is reached on this Clock.
|
|
130
|
+
*
|
|
131
|
+
* When using a ROSClock, this may sleep forever if the TimeSource is misconfigured
|
|
132
|
+
* and the context is never shut down. ROS time being activated or deactivated causes
|
|
133
|
+
* this function to cease sleeping and return false.
|
|
134
|
+
*
|
|
135
|
+
* @param {Time} until - Time at which this function should stop sleeping.
|
|
136
|
+
* @param {Context} [context=null] - Context whose validity will be checked while sleeping.
|
|
137
|
+
* If context is null, then the default context is used. If the context is found to be
|
|
138
|
+
* shut down before or by the time the wait completes, the returned promise resolves to false.
|
|
139
|
+
* @return {Promise<boolean>} Promise that resolves to true if 'until' was reached without
|
|
140
|
+
* detecting a time jump or a shut down context, or false otherwise (for example, if a time
|
|
141
|
+
* jump occurred or the context is no longer valid when the wait completes).
|
|
142
|
+
* @throws {TypeError} if until is specified for a different type of clock than this one.
|
|
143
|
+
* @throws {Error} if context has not been initialized or is shutdown.
|
|
144
|
+
*/
|
|
145
|
+
async sleepUntil(until, context = null) {
|
|
146
|
+
if (!(until instanceof Time)) {
|
|
147
|
+
throw new TypeValidationError('until', until, 'Time', {
|
|
148
|
+
entityType: 'clock',
|
|
149
|
+
});
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
if (!context) {
|
|
153
|
+
context = Context.defaultContext();
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
if (!context.isValid()) {
|
|
157
|
+
throw new Error('Context is not initialized or has been shut down');
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
if (until.clockType !== this._clockType) {
|
|
161
|
+
throw new TypeError(
|
|
162
|
+
"until's clock type does not match this clock's type"
|
|
163
|
+
);
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
const event = new ClockEvent();
|
|
167
|
+
let timeSourceChanged = false;
|
|
168
|
+
|
|
169
|
+
// Callback to wake when time jumps and is past target time
|
|
170
|
+
const callbackObject = {
|
|
171
|
+
_pre_callback: () => {
|
|
172
|
+
// Optional pre-callback - no action needed
|
|
173
|
+
},
|
|
174
|
+
_post_callback: (jumpInfo) => {
|
|
175
|
+
// ROS time being activated or deactivated changes the epoch,
|
|
176
|
+
// so sleep time loses its meaning
|
|
177
|
+
timeSourceChanged =
|
|
178
|
+
timeSourceChanged ||
|
|
179
|
+
jumpInfo.clock_change === ClockChange.ROS_TIME_ACTIVATED ||
|
|
180
|
+
jumpInfo.clock_change === ClockChange.ROS_TIME_DEACTIVATED;
|
|
181
|
+
|
|
182
|
+
if (timeSourceChanged || this.now().nanoseconds >= until.nanoseconds) {
|
|
183
|
+
event.set();
|
|
184
|
+
}
|
|
185
|
+
},
|
|
186
|
+
};
|
|
187
|
+
|
|
188
|
+
// Setup jump callback with minimal forward threshold
|
|
189
|
+
this.addClockCallback(
|
|
190
|
+
callbackObject,
|
|
191
|
+
true, // onClockChange
|
|
192
|
+
1n, // minForward (1 nanosecond - any forward jump triggers)
|
|
193
|
+
0n // minBackward (0 disables backward threshold)
|
|
194
|
+
);
|
|
195
|
+
|
|
196
|
+
try {
|
|
197
|
+
// Wait based on clock type
|
|
198
|
+
if (this._clockType === ClockType.SYSTEM_TIME) {
|
|
199
|
+
await event.waitUntilSystem(this, until.nanoseconds);
|
|
200
|
+
} else if (this._clockType === ClockType.STEADY_TIME) {
|
|
201
|
+
await event.waitUntilSteady(this, until.nanoseconds);
|
|
202
|
+
} else if (this._clockType === ClockType.ROS_TIME) {
|
|
203
|
+
await event.waitUntilRos(this, until.nanoseconds);
|
|
204
|
+
}
|
|
205
|
+
} finally {
|
|
206
|
+
// Always clean up callback
|
|
207
|
+
this.removeClockCallback(callbackObject);
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
if (!context.isValid() || timeSourceChanged) {
|
|
211
|
+
return false;
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
return this.now().nanoseconds >= until.nanoseconds;
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
/**
|
|
218
|
+
* Sleep for a specified duration.
|
|
219
|
+
*
|
|
220
|
+
* Equivalent to: clock.sleepUntil(clock.now() + duration, context)
|
|
221
|
+
*
|
|
222
|
+
* When using a ROSClock, this may sleep forever if the TimeSource is misconfigured
|
|
223
|
+
* and the context is never shut down. ROS time being activated or deactivated causes
|
|
224
|
+
* this function to cease sleeping and return false.
|
|
225
|
+
*
|
|
226
|
+
* @param {Duration} duration - Duration of time to sleep for.
|
|
227
|
+
* @param {Context} [context=null] - Context which when shut down will cause this sleep to wake early.
|
|
228
|
+
* If context is null, then the default context is used.
|
|
229
|
+
* @return {Promise<boolean>} Promise that resolves to true if the full duration was slept,
|
|
230
|
+
* or false if it woke for another reason.
|
|
231
|
+
* @throws {Error} if context has not been initialized or is shutdown.
|
|
232
|
+
*/
|
|
233
|
+
async sleepFor(duration, context = null) {
|
|
234
|
+
const until = this.now().add(duration);
|
|
235
|
+
return this.sleepUntil(until, context);
|
|
236
|
+
}
|
|
59
237
|
}
|
|
60
238
|
|
|
61
239
|
/**
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
// Copyright (c) 2025 The Robot Web Tools Contributors. 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
|
+
/**
|
|
18
|
+
* Enum for ClockChange
|
|
19
|
+
* Represents the type of clock change that occurred during a time jump.
|
|
20
|
+
* @readonly
|
|
21
|
+
* @enum {number}
|
|
22
|
+
*/
|
|
23
|
+
const ClockChange = {
|
|
24
|
+
/**
|
|
25
|
+
* The source before and after the jump is ROS_TIME.
|
|
26
|
+
* @member {number}
|
|
27
|
+
*/
|
|
28
|
+
ROS_TIME_NO_CHANGE: 1,
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* The source switched to ROS_TIME from SYSTEM_TIME.
|
|
32
|
+
* @member {number}
|
|
33
|
+
*/
|
|
34
|
+
ROS_TIME_ACTIVATED: 2,
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* The source switched to SYSTEM_TIME from ROS_TIME.
|
|
38
|
+
* @member {number}
|
|
39
|
+
*/
|
|
40
|
+
ROS_TIME_DEACTIVATED: 3,
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* The source before and after the jump is SYSTEM_TIME.
|
|
44
|
+
* @member {number}
|
|
45
|
+
*/
|
|
46
|
+
SYSTEM_TIME_NO_CHANGE: 4,
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
module.exports = ClockChange;
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
// Copyright (c) 2025, The Robot Web Tools Contributors
|
|
2
|
+
//
|
|
3
|
+
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
// you may not use this file except in compliance with the License.
|
|
5
|
+
// You may obtain a copy of the License at
|
|
6
|
+
//
|
|
7
|
+
// http://www.apache.org/licenses/LICENSE-2.0
|
|
8
|
+
//
|
|
9
|
+
// Unless required by applicable law or agreed to in writing, software
|
|
10
|
+
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
11
|
+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
12
|
+
// See the License for the specific language governing permissions and
|
|
13
|
+
// limitations under the License.
|
|
14
|
+
|
|
15
|
+
'use strict';
|
|
16
|
+
|
|
17
|
+
const rclnodejs = require('./native_loader.js');
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* @class - Class representing a ClockEvent in ROS
|
|
21
|
+
*/
|
|
22
|
+
class ClockEvent {
|
|
23
|
+
constructor() {
|
|
24
|
+
this._handle = rclnodejs.createClockEvent();
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Wait until a time specified by a steady clock.
|
|
29
|
+
* @param {Clock} clock - The clock to use for time synchronization.
|
|
30
|
+
* @param {bigint} until - The time to wait until.
|
|
31
|
+
* @return {Promise<void>} - A promise that resolves when the time is reached.
|
|
32
|
+
*/
|
|
33
|
+
async waitUntilSteady(clock, until) {
|
|
34
|
+
return rclnodejs.clockEventWaitUntilSteady(
|
|
35
|
+
this._handle,
|
|
36
|
+
clock.handle,
|
|
37
|
+
until
|
|
38
|
+
);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Wait until a time specified by a system clock.
|
|
43
|
+
* @param {Clock} clock - The clock to use for time synchronization.
|
|
44
|
+
* @param {bigint} until - The time to wait until.
|
|
45
|
+
* @return {Promise<void>} - A promise that resolves when the time is reached.
|
|
46
|
+
*/
|
|
47
|
+
async waitUntilSystem(clock, until) {
|
|
48
|
+
return rclnodejs.clockEventWaitUntilSystem(
|
|
49
|
+
this._handle,
|
|
50
|
+
clock.handle,
|
|
51
|
+
until
|
|
52
|
+
);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Wait until a time specified by a ROS clock.
|
|
57
|
+
* @param {Clock} clock - The clock to use for time synchronization.
|
|
58
|
+
* @param {bigint} until - The time to wait until.
|
|
59
|
+
* @return {Promise<void>} - A promise that resolves when the time is reached.
|
|
60
|
+
*/
|
|
61
|
+
async waitUntilRos(clock, until) {
|
|
62
|
+
return rclnodejs.clockEventWaitUntilRos(this._handle, clock.handle, until);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Indicate if the ClockEvent is set.
|
|
67
|
+
* @return {boolean} - True if the ClockEvent is set.
|
|
68
|
+
*/
|
|
69
|
+
isSet() {
|
|
70
|
+
return rclnodejs.clockEventIsSet(this._handle);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Set the event.
|
|
75
|
+
*/
|
|
76
|
+
set() {
|
|
77
|
+
rclnodejs.clockEventSet(this._handle);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Clear the event.
|
|
82
|
+
*/
|
|
83
|
+
clear() {
|
|
84
|
+
rclnodejs.clockEventClear(this._handle);
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
module.exports = ClockEvent;
|