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.
Files changed (84) hide show
  1. package/binding.gyp +2 -0
  2. package/index.js +152 -0
  3. package/lib/action/client.js +109 -10
  4. package/lib/action/deferred.js +8 -2
  5. package/lib/action/server.js +10 -1
  6. package/lib/action/uuid.js +4 -1
  7. package/lib/client.js +218 -4
  8. package/lib/clock.js +182 -1
  9. package/lib/clock_change.js +49 -0
  10. package/lib/clock_event.js +88 -0
  11. package/lib/context.js +12 -2
  12. package/lib/duration.js +37 -12
  13. package/lib/errors.js +621 -0
  14. package/lib/event_handler.js +21 -4
  15. package/lib/interface_loader.js +52 -12
  16. package/lib/lifecycle.js +8 -2
  17. package/lib/logging.js +90 -3
  18. package/lib/message_introspector.js +123 -0
  19. package/lib/message_serialization.js +10 -2
  20. package/lib/message_validation.js +512 -0
  21. package/lib/native_loader.js +9 -4
  22. package/lib/node.js +403 -50
  23. package/lib/node_options.js +40 -1
  24. package/lib/observable_subscription.js +105 -0
  25. package/lib/parameter.js +172 -35
  26. package/lib/parameter_client.js +506 -0
  27. package/lib/parameter_watcher.js +309 -0
  28. package/lib/publisher.js +56 -1
  29. package/lib/qos.js +79 -5
  30. package/lib/rate.js +6 -1
  31. package/lib/serialization.js +7 -2
  32. package/lib/subscription.js +8 -0
  33. package/lib/time.js +136 -21
  34. package/lib/time_source.js +13 -4
  35. package/lib/timer.js +42 -0
  36. package/lib/utils.js +27 -1
  37. package/lib/validator.js +74 -19
  38. package/package.json +4 -2
  39. package/prebuilds/linux-arm64/humble-jammy-arm64-rclnodejs.node +0 -0
  40. package/prebuilds/linux-arm64/jazzy-noble-arm64-rclnodejs.node +0 -0
  41. package/prebuilds/linux-arm64/kilted-noble-arm64-rclnodejs.node +0 -0
  42. package/prebuilds/linux-x64/humble-jammy-x64-rclnodejs.node +0 -0
  43. package/prebuilds/linux-x64/jazzy-noble-x64-rclnodejs.node +0 -0
  44. package/prebuilds/linux-x64/kilted-noble-x64-rclnodejs.node +0 -0
  45. package/rosidl_gen/message_translator.js +0 -61
  46. package/scripts/config.js +1 -0
  47. package/src/addon.cpp +2 -0
  48. package/src/clock_event.cpp +268 -0
  49. package/src/clock_event.hpp +62 -0
  50. package/src/macros.h +2 -4
  51. package/src/rcl_action_server_bindings.cpp +21 -3
  52. package/src/rcl_bindings.cpp +59 -0
  53. package/src/rcl_context_bindings.cpp +5 -0
  54. package/src/rcl_graph_bindings.cpp +73 -0
  55. package/src/rcl_logging_bindings.cpp +158 -0
  56. package/src/rcl_node_bindings.cpp +14 -2
  57. package/src/rcl_publisher_bindings.cpp +12 -0
  58. package/src/rcl_service_bindings.cpp +7 -6
  59. package/src/rcl_subscription_bindings.cpp +51 -14
  60. package/src/rcl_time_point_bindings.cpp +135 -0
  61. package/src/rcl_timer_bindings.cpp +140 -0
  62. package/src/rcl_utilities.cpp +103 -2
  63. package/src/rcl_utilities.h +7 -1
  64. package/types/action_client.d.ts +27 -2
  65. package/types/base.d.ts +6 -0
  66. package/types/client.d.ts +65 -1
  67. package/types/clock.d.ts +86 -0
  68. package/types/clock_change.d.ts +27 -0
  69. package/types/clock_event.d.ts +51 -0
  70. package/types/errors.d.ts +496 -0
  71. package/types/index.d.ts +10 -0
  72. package/types/logging.d.ts +32 -0
  73. package/types/message_introspector.d.ts +75 -0
  74. package/types/message_validation.d.ts +183 -0
  75. package/types/node.d.ts +107 -0
  76. package/types/node_options.d.ts +13 -0
  77. package/types/observable_subscription.d.ts +39 -0
  78. package/types/parameter_client.d.ts +252 -0
  79. package/types/parameter_watcher.d.ts +104 -0
  80. package/types/publisher.d.ts +28 -1
  81. package/types/qos.d.ts +18 -0
  82. package/types/subscription.d.ts +6 -0
  83. package/types/timer.d.ts +18 -0
  84. 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 resopnse is sent back from service
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 repsonse.
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 TypeError('Invalid argument');
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 TypeError('Invalid argument, must be type of Time');
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 Error('Node must be defined to add to Context');
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 Error('Can not add a Node to a Context that is shutdown');
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)) {