rclnodejs 1.6.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 +152 -0
- package/lib/action/client.js +109 -10
- 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 +218 -4
- package/lib/clock.js +182 -1
- package/lib/clock_change.js +49 -0
- package/lib/clock_event.js +88 -0
- package/lib/context.js +12 -2
- package/lib/duration.js +37 -12
- package/lib/errors.js +621 -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 +90 -3
- package/lib/message_introspector.js +123 -0
- package/lib/message_serialization.js +10 -2
- package/lib/message_validation.js +512 -0
- package/lib/native_loader.js +9 -4
- package/lib/node.js +403 -50
- package/lib/node_options.js +40 -1
- package/lib/observable_subscription.js +105 -0
- package/lib/parameter.js +172 -35
- package/lib/parameter_client.js +506 -0
- package/lib/parameter_watcher.js +309 -0
- package/lib/publisher.js +56 -1
- package/lib/qos.js +79 -5
- package/lib/rate.js +6 -1
- package/lib/serialization.js +7 -2
- package/lib/subscription.js +8 -0
- package/lib/time.js +136 -21
- package/lib/time_source.js +13 -4
- package/lib/timer.js +42 -0
- package/lib/utils.js +27 -1
- package/lib/validator.js +74 -19
- 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 +6 -0
- package/types/client.d.ts +65 -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 +496 -0
- package/types/index.d.ts +10 -0
- 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 +107 -0
- package/types/node_options.d.ts +13 -0
- package/types/observable_subscription.d.ts +39 -0
- package/types/parameter_client.d.ts +252 -0
- package/types/parameter_watcher.d.ts +104 -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/client.js
CHANGED
|
@@ -17,8 +17,59 @@
|
|
|
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');
|
|
25
|
+
const { assertValidMessage } = require('./message_validation.js');
|
|
20
26
|
const debug = require('debug')('rclnodejs:client');
|
|
21
27
|
|
|
28
|
+
// Polyfill for AbortSignal.any() for Node.js <= 20.3.0
|
|
29
|
+
// AbortSignal.any() was added in Node.js 20.3.0
|
|
30
|
+
// See https://developer.mozilla.org/en-US/docs/Web/API/AbortSignal/any_static
|
|
31
|
+
if (!AbortSignal.any) {
|
|
32
|
+
AbortSignal.any = function (signals) {
|
|
33
|
+
// Filter out null/undefined values and validate inputs
|
|
34
|
+
const validSignals = Array.isArray(signals)
|
|
35
|
+
? signals.filter((signal) => signal != null)
|
|
36
|
+
: [];
|
|
37
|
+
|
|
38
|
+
// If no valid signals, return a never-aborting signal
|
|
39
|
+
if (validSignals.length === 0) {
|
|
40
|
+
return new AbortController().signal;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
const controller = new AbortController();
|
|
44
|
+
const listeners = [];
|
|
45
|
+
|
|
46
|
+
// Cleanup function to remove all event listeners
|
|
47
|
+
const cleanup = () => {
|
|
48
|
+
listeners.forEach(({ signal, listener }) => {
|
|
49
|
+
signal.removeEventListener('abort', listener);
|
|
50
|
+
});
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
for (const signal of validSignals) {
|
|
54
|
+
if (signal.aborted) {
|
|
55
|
+
cleanup();
|
|
56
|
+
controller.abort(signal.reason);
|
|
57
|
+
return controller.signal;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
const listener = () => {
|
|
61
|
+
cleanup();
|
|
62
|
+
controller.abort(signal.reason);
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
signal.addEventListener('abort', listener);
|
|
66
|
+
listeners.push({ signal, listener });
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
return controller.signal;
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
|
|
22
73
|
/**
|
|
23
74
|
* @class - Class representing a Client in ROS
|
|
24
75
|
* @hideconstructor
|
|
@@ -30,10 +81,43 @@ class Client extends Entity {
|
|
|
30
81
|
this._nodeHandle = nodeHandle;
|
|
31
82
|
this._serviceName = serviceName;
|
|
32
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
|
+
}
|
|
33
117
|
}
|
|
34
118
|
|
|
35
119
|
/**
|
|
36
|
-
* This callback is called when a
|
|
120
|
+
* This callback is called when a response is sent back from service
|
|
37
121
|
* @callback ResponseCallback
|
|
38
122
|
* @param {Object} response - The response sent from the service
|
|
39
123
|
* @see [Client.sendRequest]{@link Client#sendRequest}
|
|
@@ -43,15 +127,34 @@ class Client extends Entity {
|
|
|
43
127
|
*/
|
|
44
128
|
|
|
45
129
|
/**
|
|
46
|
-
* Send the request and will be notified asynchronously if receiving the
|
|
130
|
+
* Send the request and will be notified asynchronously if receiving the response.
|
|
47
131
|
* @param {object} request - The request to be submitted.
|
|
48
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
|
|
49
135
|
* @return {undefined}
|
|
136
|
+
* @throws {MessageValidationError} If validation is enabled and request is invalid
|
|
50
137
|
* @see {@link ResponseCallback}
|
|
51
138
|
*/
|
|
52
|
-
sendRequest(request, callback) {
|
|
139
|
+
sendRequest(request, callback, options = {}) {
|
|
53
140
|
if (typeof callback !== 'function') {
|
|
54
|
-
throw new
|
|
141
|
+
throw new TypeValidationError('callback', callback, 'function', {
|
|
142
|
+
entityType: 'service',
|
|
143
|
+
entityName: this._serviceName,
|
|
144
|
+
});
|
|
145
|
+
}
|
|
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
|
+
);
|
|
55
158
|
}
|
|
56
159
|
|
|
57
160
|
let requestToSend =
|
|
@@ -65,6 +168,117 @@ class Client extends Entity {
|
|
|
65
168
|
this._sequenceNumberToCallbackMap.set(sequenceNumber, callback);
|
|
66
169
|
}
|
|
67
170
|
|
|
171
|
+
/**
|
|
172
|
+
* Send the request and return a Promise that resolves with the response.
|
|
173
|
+
* @param {object} request - The request to be submitted.
|
|
174
|
+
* @param {object} [options] - Optional parameters for the request.
|
|
175
|
+
* @param {number} [options.timeout] - Timeout in milliseconds for the request.
|
|
176
|
+
* @param {AbortSignal} [options.signal] - AbortSignal to cancel the request.
|
|
177
|
+
* @param {boolean} [options.validate] - Override validateRequests setting for this call
|
|
178
|
+
* @return {Promise<object>} Promise that resolves with the service response.
|
|
179
|
+
* @throws {module:rclnodejs.TimeoutError} If the request times out (when options.timeout is exceeded).
|
|
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.
|
|
182
|
+
* @throws {Error} If the request fails for other reasons.
|
|
183
|
+
*/
|
|
184
|
+
sendRequestAsync(request, options = {}) {
|
|
185
|
+
return new Promise((resolve, reject) => {
|
|
186
|
+
let sequenceNumber = null;
|
|
187
|
+
let isResolved = false;
|
|
188
|
+
let isTimeout = false;
|
|
189
|
+
|
|
190
|
+
const cleanup = () => {
|
|
191
|
+
if (sequenceNumber !== null) {
|
|
192
|
+
this._sequenceNumberToCallbackMap.delete(sequenceNumber);
|
|
193
|
+
}
|
|
194
|
+
isResolved = true;
|
|
195
|
+
};
|
|
196
|
+
|
|
197
|
+
let effectiveSignal = options.signal;
|
|
198
|
+
|
|
199
|
+
if (options.timeout !== undefined && options.timeout >= 0) {
|
|
200
|
+
const timeoutSignal = AbortSignal.timeout(options.timeout);
|
|
201
|
+
|
|
202
|
+
timeoutSignal.addEventListener('abort', () => {
|
|
203
|
+
isTimeout = true;
|
|
204
|
+
});
|
|
205
|
+
|
|
206
|
+
if (options.signal) {
|
|
207
|
+
effectiveSignal = AbortSignal.any([options.signal, timeoutSignal]);
|
|
208
|
+
} else {
|
|
209
|
+
effectiveSignal = timeoutSignal;
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
if (effectiveSignal) {
|
|
214
|
+
if (effectiveSignal.aborted) {
|
|
215
|
+
const error = isTimeout
|
|
216
|
+
? new TimeoutError('Service request', options.timeout, {
|
|
217
|
+
entityType: 'service',
|
|
218
|
+
entityName: this._serviceName,
|
|
219
|
+
})
|
|
220
|
+
: new AbortError('Service request', undefined, {
|
|
221
|
+
entityType: 'service',
|
|
222
|
+
entityName: this._serviceName,
|
|
223
|
+
});
|
|
224
|
+
reject(error);
|
|
225
|
+
return;
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
effectiveSignal.addEventListener('abort', () => {
|
|
229
|
+
if (!isResolved) {
|
|
230
|
+
cleanup();
|
|
231
|
+
const error = isTimeout
|
|
232
|
+
? new TimeoutError('Service request', options.timeout, {
|
|
233
|
+
entityType: 'service',
|
|
234
|
+
entityName: this._serviceName,
|
|
235
|
+
})
|
|
236
|
+
: new AbortError('Service request', undefined, {
|
|
237
|
+
entityType: 'service',
|
|
238
|
+
entityName: this._serviceName,
|
|
239
|
+
});
|
|
240
|
+
reject(error);
|
|
241
|
+
}
|
|
242
|
+
});
|
|
243
|
+
}
|
|
244
|
+
|
|
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
|
+
|
|
259
|
+
let requestToSend =
|
|
260
|
+
request instanceof this._typeClass.Request
|
|
261
|
+
? request
|
|
262
|
+
: new this._typeClass.Request(request);
|
|
263
|
+
|
|
264
|
+
let rawRequest = requestToSend.serialize();
|
|
265
|
+
sequenceNumber = rclnodejs.sendRequest(this._handle, rawRequest);
|
|
266
|
+
|
|
267
|
+
debug(`Client has sent a ${this._serviceName} request (async).`);
|
|
268
|
+
|
|
269
|
+
this._sequenceNumberToCallbackMap.set(sequenceNumber, (response) => {
|
|
270
|
+
if (!isResolved) {
|
|
271
|
+
cleanup();
|
|
272
|
+
resolve(response);
|
|
273
|
+
}
|
|
274
|
+
});
|
|
275
|
+
} catch (error) {
|
|
276
|
+
cleanup();
|
|
277
|
+
reject(error);
|
|
278
|
+
}
|
|
279
|
+
});
|
|
280
|
+
}
|
|
281
|
+
|
|
68
282
|
processResponse(sequenceNumber, response) {
|
|
69
283
|
if (this._sequenceNumberToCallbackMap.has(sequenceNumber)) {
|
|
70
284
|
debug(`Client has received ${this._serviceName} response from service.`);
|
package/lib/clock.js
CHANGED
|
@@ -17,6 +17,10 @@
|
|
|
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');
|
|
23
|
+
const { TypeValidationError } = require('./errors.js');
|
|
20
24
|
|
|
21
25
|
/**
|
|
22
26
|
* @class - Class representing a Clock in ROS
|
|
@@ -47,6 +51,71 @@ class Clock {
|
|
|
47
51
|
return this._clockType;
|
|
48
52
|
}
|
|
49
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
|
+
|
|
50
119
|
/**
|
|
51
120
|
* Return the current time.
|
|
52
121
|
* @return {Time} Return the current time.
|
|
@@ -55,6 +124,116 @@ class Clock {
|
|
|
55
124
|
const nowInNanosec = rclnodejs.clockGetNow(this._handle);
|
|
56
125
|
return new Time(0n, nowInNanosec, this._clockType);
|
|
57
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
|
+
}
|
|
58
237
|
}
|
|
59
238
|
|
|
60
239
|
/**
|
|
@@ -102,7 +281,9 @@ class ROSClock extends Clock {
|
|
|
102
281
|
|
|
103
282
|
set rosTimeOverride(time) {
|
|
104
283
|
if (!(time instanceof Time)) {
|
|
105
|
-
throw new
|
|
284
|
+
throw new TypeValidationError('time', time, 'Time', {
|
|
285
|
+
entityType: 'clock',
|
|
286
|
+
});
|
|
106
287
|
}
|
|
107
288
|
rclnodejs.setRosTimeOverride(this._handle, time._handle);
|
|
108
289
|
}
|
|
@@ -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;
|
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)) {
|