• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

RobotWebTools / rclnodejs / 22750012479

06 Mar 2026 05:07AM UTC coverage: 85.827% (+0.3%) from 85.501%
22750012479

push

github

minggangw
Fix: correct bugs in test files (#1418)

### Summary

Fixed 6 bugs across 6 test files discovered during code review. All issues were in test code only — no library changes.

### Changes

#### 1. `test/client_setup.js` — Remove reference to undefined `timer`

The `SIGINT` handler called `timer.cancel()`, but `timer` is not defined in this scope. Removed the call to prevent a `ReferenceError` if the signal is received.

#### 2. `test/test-lifecycle.js` — Fix duplicate transition index

Two consecutive assertions both checked `transitions[1]`, meaning `transitions[2]` was never validated. Changed the second check from `transitions[1]` to `transitions[2]`.

#### 3. `test/test-parameter-service.js` — Fix wrong parameter name

`Parameter.fromParameterMessage` was called with `name: 'p1'` but the test was validating parameter `A.p3`. Changed to `name: 'A.p3'`.

#### 4. `test/test-rate.js` — Add missing assertion

The test computed an average sleep time but never asserted on it. Added an assertion that the average is within 5x the expected period.

#### 5. `test/test-utils.js` — Remove empty duplicate test

An empty test body `it('should valid ensureDirSync works correctly', function () {})` duplicated the immediately following test `it('should verify ensureDirSync works correctly', ...)`. Removed the empty duplicate.

#### 6. `test/types/index.test-d.ts` — Remove misplaced type assertion

`expectType<boolean>(client.isServiceServerAvailable())` was in the **ActionClient** section but tested `client` (the service client variable). The same assertion already exists in the correct Client section. Removed the misplaced copy.

### Files changed

| File | Change |
|------|--------|
| `test/client_setup.js` | Remove undefined `timer.cancel()` |
| `test/test-lifecycle.js` | `transitions[1]` → `transitions[2]` in second assertion |
| `test/test-parameter-service.js` | `'p1'` → `'A.p3'` |
| `test/test-rate.js` | Add avg sleep time assertion |
| `test/test-utils.js` |... (continued)

1388 of 1756 branches covered (79.04%)

Branch coverage included in aggregate %.

2869 of 3204 relevant lines covered (89.54%)

448.35 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

87.29
/lib/node.js
1
// Copyright (c) 2017 Intel Corporation. All rights reserved.
2
//
3
// Licensed under the Apache License, Version 2.0 (the "License");
4
// you may not use this file except in compliance with the License.
5
// You may obtain a copy of the License at
6
//
7
//     http://www.apache.org/licenses/LICENSE-2.0
8
//
9
// Unless required by applicable law or agreed to in writing, software
10
// distributed under the License is distributed on an "AS IS" BASIS,
11
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
// See the License for the specific language governing permissions and
13
// limitations under the License.
14

15
'use strict';
16

17
const rclnodejs = require('./native_loader.js');
26✔
18

19
const ActionInterfaces = require('./action/interfaces.js');
26✔
20
const Client = require('./client.js');
26✔
21
const Clock = require('./clock.js');
26✔
22
const Context = require('./context.js');
26✔
23
const debug = require('debug')('rclnodejs:node');
26✔
24
const DistroUtils = require('./distro.js');
26✔
25
const GuardCondition = require('./guard_condition.js');
26✔
26
const loader = require('./interface_loader.js');
26✔
27
const Logging = require('./logging.js');
26✔
28
const NodeOptions = require('./node_options.js');
26✔
29
const {
30
  ParameterType,
31
  Parameter,
32
  ParameterDescriptor,
33
} = require('./parameter.js');
26✔
34
const { isValidSerializationMode } = require('./message_serialization.js');
26✔
35
const {
36
  TypeValidationError,
37
  RangeValidationError,
38
  ValidationError,
39
} = require('./errors.js');
26✔
40
const ParameterService = require('./parameter_service.js');
26✔
41
const ParameterClient = require('./parameter_client.js');
26✔
42
const ParameterWatcher = require('./parameter_watcher.js');
26✔
43
const Publisher = require('./publisher.js');
26✔
44
const QoS = require('./qos.js');
26✔
45
const Rates = require('./rate.js');
26✔
46
const Service = require('./service.js');
26✔
47
const Subscription = require('./subscription.js');
26✔
48
const ObservableSubscription = require('./observable_subscription.js');
26✔
49
const TimeSource = require('./time_source.js');
26✔
50
const Timer = require('./timer.js');
26✔
51
const TypeDescriptionService = require('./type_description_service.js');
26✔
52
const Entity = require('./entity.js');
26✔
53
const { SubscriptionEventCallbacks } = require('../lib/event_handler.js');
26✔
54
const { PublisherEventCallbacks } = require('../lib/event_handler.js');
26✔
55
const { validateFullTopicName } = require('./validator.js');
26✔
56

57
// Parameter event publisher constants
58
const PARAMETER_EVENT_MSG_TYPE = 'rcl_interfaces/msg/ParameterEvent';
26✔
59
const PARAMETER_EVENT_TOPIC = 'parameter_events';
26✔
60

61
/**
62
 * @class - Class representing a Node in ROS
63
 */
64

65
class Node extends rclnodejs.ShadowNode {
66
  /**
67
   * Create a ROS2Node.
68
   * model using the {@link https://github.com/ros2/rcl/tree/master/rcl_lifecycle|ros2 client library (rcl) lifecyle api}.
69
   * @param {string} nodeName - The name used to register in ROS.
70
   * @param {string} [namespace=''] - The namespace used in ROS.
71
   * @param {Context} [context=Context.defaultContext()] - The context to create the node in.
72
   * @param {NodeOptions} [options=NodeOptions.defaultOptions] - The options to configure the new node behavior.
73
   * @throws {Error} If the given context is not registered.
74
   */
75
  constructor(
76
    nodeName,
77
    namespace = '',
44✔
78
    context = Context.defaultContext(),
79✔
79
    options = NodeOptions.defaultOptions,
81✔
80
    args = [],
100✔
81
    useGlobalArguments = true
100✔
82
  ) {
83
    super();
840✔
84

85
    if (typeof nodeName !== 'string') {
840✔
86
      throw new TypeValidationError('nodeName', nodeName, 'string');
12✔
87
    }
88
    if (typeof namespace !== 'string') {
828✔
89
      throw new TypeValidationError('namespace', namespace, 'string');
10✔
90
    }
91

92
    this._init(nodeName, namespace, options, context, args, useGlobalArguments);
818✔
93
    debug(
807✔
94
      'Finish initializing node, name = %s and namespace = %s.',
95
      nodeName,
96
      namespace
97
    );
98
  }
99

100
  _init(name, namespace, options, context, args, useGlobalArguments) {
101
    this.handle = rclnodejs.createNode(
818✔
102
      name,
103
      namespace,
104
      context.handle,
105
      args,
106
      useGlobalArguments,
107
      options.rosoutQos
108
    );
109
    Object.defineProperty(this, 'handle', {
808✔
110
      configurable: false,
111
      writable: false,
112
    }); // make read-only
113

114
    this._context = context;
808✔
115
    this.context.onNodeCreated(this);
808✔
116

117
    this._publishers = [];
808✔
118
    this._subscriptions = [];
808✔
119
    this._clients = [];
808✔
120
    this._services = [];
808✔
121
    this._timers = [];
808✔
122
    this._guards = [];
808✔
123
    this._events = [];
808✔
124
    this._actionClients = [];
808✔
125
    this._actionServers = [];
808✔
126
    this._parameterClients = [];
808✔
127
    this._parameterWatchers = [];
808✔
128
    this._rateTimerServer = null;
808✔
129
    this._parameterDescriptors = new Map();
808✔
130
    this._parameters = new Map();
808✔
131
    this._parameterService = null;
808✔
132
    this._typeDescriptionService = null;
808✔
133
    this._parameterEventPublisher = null;
808✔
134
    this._setParametersCallbacks = [];
808✔
135
    this._logger = new Logging(rclnodejs.getNodeLoggerName(this.handle));
808✔
136
    this._spinning = false;
808✔
137
    this._enableRosout = options.enableRosout;
808✔
138

139
    if (this._enableRosout) {
808✔
140
      rclnodejs.initRosoutPublisherForNode(this.handle);
807✔
141
    }
142

143
    this._parameterEventPublisher = this.createPublisher(
808✔
144
      PARAMETER_EVENT_MSG_TYPE,
145
      PARAMETER_EVENT_TOPIC
146
    );
147

148
    // initialize _parameterOverrides from parameters defined on the commandline
149
    this._parameterOverrides = this._getNativeParameterOverrides();
808✔
150

151
    // override cli parameterOverrides with those specified in options
152
    if (options.parameterOverrides.length > 0) {
808✔
153
      for (const parameter of options.parameterOverrides) {
11✔
154
        if (!(parameter instanceof Parameter)) {
16✔
155
          throw new TypeValidationError(
1✔
156
            'parameterOverride',
157
            parameter,
158
            'Parameter instance',
159
            {
160
              nodeName: name,
161
            }
162
          );
163
        }
164
        this._parameterOverrides.set(parameter.name, parameter);
15✔
165
      }
166
    }
167

168
    // initialize _parameters from parameterOverrides
169
    if (options.automaticallyDeclareParametersFromOverrides) {
807✔
170
      for (const parameter of this._parameterOverrides.values()) {
5✔
171
        parameter.validate();
10✔
172
        const descriptor = ParameterDescriptor.fromParameter(parameter);
10✔
173
        this._parameters.set(parameter.name, parameter);
10✔
174
        this._parameterDescriptors.set(parameter.name, descriptor);
10✔
175
      }
176
    }
177

178
    // Clock that has support for ROS time.
179
    // Note: parameter overrides and parameter event publisher need to be ready at this point
180
    // to be able to declare 'use_sim_time' if it was not declared yet.
181
    this._clock = new Clock.ROSClock();
807✔
182
    this._timeSource = new TimeSource(this);
807✔
183
    this._timeSource.attachClock(this._clock);
807✔
184

185
    if (options.startParameterServices) {
807✔
186
      this._parameterService = new ParameterService(this);
801✔
187
      this._parameterService.start();
801✔
188
    }
189

190
    if (
807!
191
      DistroUtils.getDistroId() >= DistroUtils.getDistroId('jazzy') &&
1,614✔
192
      options.startTypeDescriptionService
193
    ) {
194
      this._typeDescriptionService = new TypeDescriptionService(this);
807✔
195
      this._typeDescriptionService.start();
807✔
196
    }
197
  }
198

199
  execute(handles) {
200
    let timersReady = this._timers.filter((timer) =>
5,483✔
201
      handles.includes(timer.handle)
4,514✔
202
    );
203
    let guardsReady = this._guards.filter((guard) =>
5,483✔
204
      handles.includes(guard.handle)
3✔
205
    );
206
    let subscriptionsReady = this._subscriptions.filter((subscription) =>
5,483✔
207
      handles.includes(subscription.handle)
493✔
208
    );
209
    let clientsReady = this._clients.filter((client) =>
5,483✔
210
      handles.includes(client.handle)
321✔
211
    );
212
    let servicesReady = this._services.filter((service) =>
5,483✔
213
      handles.includes(service.handle)
20,374✔
214
    );
215
    let actionClientsReady = this._actionClients.filter((actionClient) =>
5,483✔
216
      handles.includes(actionClient.handle)
161✔
217
    );
218
    let actionServersReady = this._actionServers.filter((actionServer) =>
5,483✔
219
      handles.includes(actionServer.handle)
156✔
220
    );
221
    let eventsReady = this._events.filter((event) =>
5,483✔
222
      handles.includes(event.handle)
4✔
223
    );
224

225
    timersReady.forEach((timer) => {
5,483✔
226
      if (timer.isReady()) {
4,500✔
227
        rclnodejs.callTimer(timer.handle);
4,412✔
228
        timer.callback();
4,412✔
229
      }
230
    });
231

232
    eventsReady.forEach((event) => {
5,483✔
233
      event.takeData();
4✔
234
    });
235

236
    for (const subscription of subscriptionsReady) {
5,483✔
237
      if (subscription.isDestroyed()) continue;
454✔
238
      if (subscription.isRaw) {
450✔
239
        let rawMessage = rclnodejs.rclTakeRaw(subscription.handle);
1✔
240
        if (rawMessage) {
1!
241
          subscription.processResponse(rawMessage);
1✔
242
        }
243
        continue;
1✔
244
      }
245

246
      this._runWithMessageType(
449✔
247
        subscription.typeClass,
248
        (message, deserialize) => {
249
          let success = rclnodejs.rclTake(subscription.handle, message);
449✔
250
          if (success) {
449✔
251
            subscription.processResponse(deserialize());
372✔
252
          }
253
        }
254
      );
255
    }
256

257
    for (const guard of guardsReady) {
5,483✔
258
      if (guard.isDestroyed()) continue;
3!
259

260
      guard.callback();
3✔
261
    }
262

263
    for (const client of clientsReady) {
5,483✔
264
      if (client.isDestroyed()) continue;
165!
265
      this._runWithMessageType(
165✔
266
        client.typeClass.Response,
267
        (message, deserialize) => {
268
          let sequenceNumber = rclnodejs.rclTakeResponse(
165✔
269
            client.handle,
270
            message
271
          );
272
          if (sequenceNumber !== undefined) {
165✔
273
            client.processResponse(sequenceNumber, deserialize());
86✔
274
          }
275
        }
276
      );
277
    }
278

279
    for (const service of servicesReady) {
5,483✔
280
      if (service.isDestroyed()) continue;
211!
281
      this._runWithMessageType(
211✔
282
        service.typeClass.Request,
283
        (message, deserialize) => {
284
          let header = rclnodejs.rclTakeRequest(
211✔
285
            service.handle,
286
            this.handle,
287
            message
288
          );
289
          if (header) {
211✔
290
            service.processRequest(header, deserialize());
91✔
291
          }
292
        }
293
      );
294
    }
295

296
    for (const actionClient of actionClientsReady) {
5,483✔
297
      if (actionClient.isDestroyed()) continue;
78!
298

299
      const properties = actionClient.handle.properties;
78✔
300

301
      if (properties.isGoalResponseReady) {
78✔
302
        this._runWithMessageType(
40✔
303
          actionClient.typeClass.impl.SendGoalService.Response,
304
          (message, deserialize) => {
305
            let sequence = rclnodejs.actionTakeGoalResponse(
40✔
306
              actionClient.handle,
307
              message
308
            );
309
            if (sequence != undefined) {
40✔
310
              actionClient.processGoalResponse(sequence, deserialize());
30✔
311
            }
312
          }
313
        );
314
      }
315

316
      if (properties.isCancelResponseReady) {
78✔
317
        this._runWithMessageType(
5✔
318
          actionClient.typeClass.impl.CancelGoal.Response,
319
          (message, deserialize) => {
320
            let sequence = rclnodejs.actionTakeCancelResponse(
5✔
321
              actionClient.handle,
322
              message
323
            );
324
            if (sequence != undefined) {
5✔
325
              actionClient.processCancelResponse(sequence, deserialize());
4✔
326
            }
327
          }
328
        );
329
      }
330

331
      if (properties.isResultResponseReady) {
78✔
332
        this._runWithMessageType(
15✔
333
          actionClient.typeClass.impl.GetResultService.Response,
334
          (message, deserialize) => {
335
            let sequence = rclnodejs.actionTakeResultResponse(
15✔
336
              actionClient.handle,
337
              message
338
            );
339
            if (sequence != undefined) {
15!
340
              actionClient.processResultResponse(sequence, deserialize());
15✔
341
            }
342
          }
343
        );
344
      }
345

346
      if (properties.isFeedbackReady) {
78✔
347
        this._runWithMessageType(
10✔
348
          actionClient.typeClass.impl.FeedbackMessage,
349
          (message, deserialize) => {
350
            let success = rclnodejs.actionTakeFeedback(
10✔
351
              actionClient.handle,
352
              message
353
            );
354
            if (success) {
10✔
355
              actionClient.processFeedbackMessage(deserialize());
5✔
356
            }
357
          }
358
        );
359
      }
360

361
      if (properties.isStatusReady) {
78✔
362
        this._runWithMessageType(
42✔
363
          actionClient.typeClass.impl.GoalStatusArray,
364
          (message, deserialize) => {
365
            let success = rclnodejs.actionTakeStatus(
42✔
366
              actionClient.handle,
367
              message
368
            );
369
            if (success) {
42✔
370
              actionClient.processStatusMessage(deserialize());
33✔
371
            }
372
          }
373
        );
374
      }
375
    }
376

377
    for (const actionServer of actionServersReady) {
5,483✔
378
      if (actionServer.isDestroyed()) continue;
92!
379

380
      const properties = actionServer.handle.properties;
92✔
381

382
      if (properties.isGoalRequestReady) {
92✔
383
        this._runWithMessageType(
31✔
384
          actionServer.typeClass.impl.SendGoalService.Request,
385
          (message, deserialize) => {
386
            const result = rclnodejs.actionTakeGoalRequest(
31✔
387
              actionServer.handle,
388
              message
389
            );
390
            if (result) {
31✔
391
              actionServer.processGoalRequest(result, deserialize());
30✔
392
            }
393
          }
394
        );
395
      }
396

397
      if (properties.isCancelRequestReady) {
92✔
398
        this._runWithMessageType(
7✔
399
          actionServer.typeClass.impl.CancelGoal.Request,
400
          (message, deserialize) => {
401
            const result = rclnodejs.actionTakeCancelRequest(
7✔
402
              actionServer.handle,
403
              message
404
            );
405
            if (result) {
7✔
406
              actionServer.processCancelRequest(result, deserialize());
4✔
407
            }
408
          }
409
        );
410
      }
411

412
      if (properties.isResultRequestReady) {
92✔
413
        this._runWithMessageType(
21✔
414
          actionServer.typeClass.impl.GetResultService.Request,
415
          (message, deserialize) => {
416
            const result = rclnodejs.actionTakeResultRequest(
21✔
417
              actionServer.handle,
418
              message
419
            );
420
            if (result) {
21✔
421
              actionServer.processResultRequest(result, deserialize());
15✔
422
            }
423
          }
424
        );
425
      }
426

427
      if (properties.isGoalExpired) {
92✔
428
        let numGoals = actionServer._goalHandles.size;
4✔
429
        if (numGoals > 0) {
4✔
430
          let GoalInfoArray = ActionInterfaces.GoalInfo.ArrayType;
3✔
431
          let message = new GoalInfoArray(numGoals);
3✔
432
          let count = rclnodejs.actionExpireGoals(
3✔
433
            actionServer.handle,
434
            numGoals,
435
            message._refArray.buffer
436
          );
437
          if (count > 0) {
3!
438
            actionServer.processGoalExpired(message, count);
3✔
439
          }
440
          GoalInfoArray.freeArray(message);
3✔
441
        }
442
      }
443
    }
444

445
    // At this point it is safe to clear the cache of any
446
    // destroyed entity references
447
    Entity._gcHandles();
5,483✔
448
  }
449

450
  /**
451
   * Determine if this node is spinning.
452
   * @returns {boolean} - true when spinning; otherwise returns false.
453
   */
454
  get spinning() {
455
    return this._spinning;
4,465✔
456
  }
457

458
  /**
459
   * Trigger the event loop to continuously check for and route.
460
   * incoming events.
461
   * @param {Node} node - The node to be spun up.
462
   * @param {number} [timeout=10] - Timeout to wait in milliseconds. Block forever if negative. Don't wait if 0.
463
   * @throws {Error} If the node is already spinning.
464
   * @return {undefined}
465
   */
466
  spin(timeout = 10) {
19✔
467
    if (this.spinning) {
623!
468
      throw new Error('The node is already spinning.');
×
469
    }
470
    this.start(this.context.handle, timeout);
623✔
471
    this._spinning = true;
623✔
472
  }
473

474
  /**
475
   * Use spin().
476
   * @deprecated, since 0.18.0
477
   */
478
  startSpinning(timeout) {
479
    this.spin(timeout);
×
480
  }
481

482
  /**
483
   * Terminate spinning - no further events will be received.
484
   * @returns {undefined}
485
   */
486
  stop() {
487
    super.stop();
623✔
488
    this._spinning = false;
623✔
489
  }
490

491
  /**
492
   * Terminate spinning - no further events will be received.
493
   * @returns {undefined}
494
   * @deprecated since 0.18.0, Use stop().
495
   */
496
  stopSpinning() {
497
    super.stop();
×
498
    this._spinning = false;
×
499
  }
500

501
  /**
502
   * Spin the node and trigger the event loop to check for one incoming event. Thereafter the node
503
   * will not received additional events until running additional calls to spin() or spinOnce().
504
   * @param {Node} node - The node to be spun.
505
   * @param {number} [timeout=10] - Timeout to wait in milliseconds. Block forever if negative. Don't wait if 0.
506
   * @throws {Error} If the node is already spinning.
507
   * @return {undefined}
508
   */
509
  spinOnce(timeout = 10) {
2✔
510
    if (this.spinning) {
3,009✔
511
      throw new Error('The node is already spinning.');
2✔
512
    }
513
    super.spinOnce(this.context.handle, timeout);
3,007✔
514
  }
515

516
  _removeEntityFromArray(entity, array) {
517
    let index = array.indexOf(entity);
337✔
518
    if (index > -1) {
337✔
519
      array.splice(index, 1);
335✔
520
    }
521
  }
522

523
  _destroyEntity(entity, array, syncHandles = true) {
283✔
524
    if (entity['isDestroyed'] && entity.isDestroyed()) return;
290✔
525

526
    this._removeEntityFromArray(entity, array);
282✔
527
    if (syncHandles) {
282✔
528
      this.syncHandles();
277✔
529
    }
530

531
    if (entity['_destroy']) {
282✔
532
      entity._destroy();
276✔
533
    } else {
534
      // guards and timers
535
      entity.handle.release();
6✔
536
    }
537
  }
538

539
  _validateOptions(options) {
540
    if (
7,532✔
541
      options !== undefined &&
7,606✔
542
      (options === null || typeof options !== 'object')
543
    ) {
544
      throw new TypeValidationError('options', options, 'object', {
20✔
545
        nodeName: this.name(),
546
      });
547
    }
548

549
    if (options === undefined) {
7,512✔
550
      return Node.getDefaultOptions();
7,485✔
551
    }
552

553
    if (options.enableTypedArray === undefined) {
27✔
554
      options = Object.assign(options, { enableTypedArray: true });
13✔
555
    }
556

557
    if (options.qos === undefined) {
27✔
558
      options = Object.assign(options, { qos: QoS.profileDefault });
13✔
559
    }
560

561
    if (options.isRaw === undefined) {
27✔
562
      options = Object.assign(options, { isRaw: false });
16✔
563
    }
564

565
    if (options.serializationMode === undefined) {
27✔
566
      options = Object.assign(options, { serializationMode: 'default' });
10✔
567
    } else if (!isValidSerializationMode(options.serializationMode)) {
17✔
568
      throw new ValidationError(
1✔
569
        `Invalid serializationMode: ${options.serializationMode}. Valid modes are: 'default', 'plain', 'json'`,
570
        {
571
          code: 'INVALID_SERIALIZATION_MODE',
572
          argumentName: 'serializationMode',
573
          providedValue: options.serializationMode,
574
          expectedType: "'default' | 'plain' | 'json'",
575
          nodeName: this.name(),
576
        }
577
      );
578
    }
579

580
    return options;
26✔
581
  }
582

583
  /**
584
   * Create a Timer.
585
   * @param {bigint} period - The number representing period in nanoseconds.
586
   * @param {function} callback - The callback to be called when timeout.
587
   * @param {Clock} [clock] - The clock which the timer gets time from.
588
   * @return {Timer} - An instance of Timer.
589
   */
590
  createTimer(period, callback, clock = null) {
67✔
591
    if (arguments.length === 3 && !(arguments[2] instanceof Clock)) {
67!
592
      clock = null;
×
593
    } else if (arguments.length === 4) {
67!
594
      clock = arguments[3];
×
595
    }
596

597
    if (typeof period !== 'bigint') {
67✔
598
      throw new TypeValidationError('period', period, 'bigint', {
1✔
599
        nodeName: this.name(),
600
      });
601
    }
602
    if (typeof callback !== 'function') {
66✔
603
      throw new TypeValidationError('callback', callback, 'function', {
1✔
604
        nodeName: this.name(),
605
      });
606
    }
607

608
    const timerClock = clock || this._clock;
65✔
609
    let timerHandle = rclnodejs.createTimer(
65✔
610
      timerClock.handle,
611
      this.context.handle,
612
      period
613
    );
614
    let timer = new Timer(timerHandle, period, callback);
65✔
615
    debug('Finish creating timer, period = %d.', period);
65✔
616
    this._timers.push(timer);
65✔
617
    this.syncHandles();
65✔
618

619
    return timer;
65✔
620
  }
621

622
  /**
623
   * Create a Rate.
624
   *
625
   * @param {number} hz - The frequency of the rate timer; default is 1 hz.
626
   * @returns {Promise<Rate>} - Promise resolving to new instance of Rate.
627
   */
628
  async createRate(hz = 1) {
4✔
629
    if (typeof hz !== 'number') {
9!
630
      throw new TypeValidationError('hz', hz, 'number', {
×
631
        nodeName: this.name(),
632
      });
633
    }
634

635
    const MAX_RATE_HZ_IN_MILLISECOND = 1000.0;
9✔
636
    if (hz <= 0.0 || hz > MAX_RATE_HZ_IN_MILLISECOND) {
9✔
637
      throw new RangeValidationError(
2✔
638
        'hz',
639
        hz,
640
        `0.0 < hz <= ${MAX_RATE_HZ_IN_MILLISECOND}`,
641
        {
642
          nodeName: this.name(),
643
        }
644
      );
645
    }
646

647
    // lazy initialize rateTimerServer
648
    if (!this._rateTimerServer) {
7✔
649
      this._rateTimerServer = new Rates.RateTimerServer(this);
5✔
650
      await this._rateTimerServer.init();
5✔
651
    }
652

653
    const period = Math.round(1000 / hz);
7✔
654
    const timer = this._rateTimerServer.createTimer(BigInt(period) * 1000000n);
7✔
655
    const rate = new Rates.Rate(hz, timer);
7✔
656

657
    return rate;
7✔
658
  }
659

660
  /**
661
   * Create a Publisher.
662
   * @param {function|string|object} typeClass - The ROS message class,
663
        OR a string representing the message class, e.g. 'std_msgs/msg/String',
664
        OR an object representing the message class, e.g. {package: 'std_msgs', type: 'msg', name: 'String'}
665
   * @param {string} topic - The name of the topic.
666
   * @param {object} options - The options argument used to parameterize the publisher.
667
   * @param {boolean} options.enableTypedArray - The topic will use TypedArray if necessary, default: true.
668
   * @param {QoS} options.qos - ROS Middleware "quality of service" settings for the publisher, default: QoS.profileDefault.
669
   * @param {PublisherEventCallbacks} eventCallbacks - The event callbacks for the publisher.
670
   * @return {Publisher} - An instance of Publisher.
671
   */
672
  createPublisher(typeClass, topic, options, eventCallbacks) {
673
    return this._createPublisher(
1,171✔
674
      typeClass,
675
      topic,
676
      options,
677
      Publisher,
678
      eventCallbacks
679
    );
680
  }
681

682
  _createPublisher(typeClass, topic, options, publisherClass, eventCallbacks) {
683
    if (typeof typeClass === 'string' || typeof typeClass === 'object') {
1,174✔
684
      typeClass = loader.loadInterface(typeClass);
1,150✔
685
    }
686
    options = this._validateOptions(options);
1,167✔
687

688
    if (typeof typeClass !== 'function') {
1,167✔
689
      throw new TypeValidationError('typeClass', typeClass, 'function', {
8✔
690
        nodeName: this.name(),
691
        entityType: 'publisher',
692
      });
693
    }
694
    if (typeof topic !== 'string') {
1,159✔
695
      throw new TypeValidationError('topic', topic, 'string', {
12✔
696
        nodeName: this.name(),
697
        entityType: 'publisher',
698
      });
699
    }
700
    if (
1,147!
701
      eventCallbacks &&
1,149✔
702
      !(eventCallbacks instanceof PublisherEventCallbacks)
703
    ) {
704
      throw new TypeValidationError(
×
705
        'eventCallbacks',
706
        eventCallbacks,
707
        'PublisherEventCallbacks',
708
        {
709
          nodeName: this.name(),
710
          entityType: 'publisher',
711
          entityName: topic,
712
        }
713
      );
714
    }
715

716
    let publisher = publisherClass.createPublisher(
1,147✔
717
      this,
718
      typeClass,
719
      topic,
720
      options,
721
      eventCallbacks
722
    );
723
    debug('Finish creating publisher, topic = %s.', topic);
1,137✔
724
    this._publishers.push(publisher);
1,137✔
725
    return publisher;
1,137✔
726
  }
727

728
  /**
729
   * This callback is called when a message is published
730
   * @callback SubscriptionCallback
731
   * @param {Object} message - The message published
732
   * @see [Node.createSubscription]{@link Node#createSubscription}
733
   * @see [Node.createPublisher]{@link Node#createPublisher}
734
   * @see {@link Publisher}
735
   * @see {@link Subscription}
736
   */
737

738
  /**
739
   * Create a Subscription with optional content-filtering.
740
   * @param {function|string|object} typeClass - The ROS message class,
741
        OR a string representing the message class, e.g. 'std_msgs/msg/String',
742
        OR an object representing the message class, e.g. {package: 'std_msgs', type: 'msg', name: 'String'}
743
   * @param {string} topic - The name of the topic.
744
   * @param {object} options - The options argument used to parameterize the subscription.
745
   * @param {boolean} options.enableTypedArray - The topic will use TypedArray if necessary, default: true.
746
   * @param {QoS} options.qos - ROS Middleware "quality of service" settings for the subscription, default: QoS.profileDefault.
747
   * @param {boolean} options.isRaw - The topic is serialized when true, default: false.
748
   * @param {string} [options.serializationMode='default'] - Controls message serialization format:
749
   *  'default': Use native rclnodejs behavior (respects enableTypedArray setting),
750
   *  'plain': Convert TypedArrays to regular arrays,
751
   *  'json': Fully JSON-safe (handles TypedArrays, BigInt, etc.).
752
   * @param {object} [options.contentFilter=undefined] - The content-filter, default: undefined.
753
   *  Confirm that your RMW supports content-filtered topics before use. 
754
   * @param {string} options.contentFilter.expression - Specifies the criteria to select the data samples of
755
   *  interest. It is similar to the WHERE part of an SQL clause.
756
   * @param {string[]} [options.contentFilter.parameters=undefined] - Array of strings that give values to
757
   *  the ‘parameters’ (i.e., "%n" tokens) in the filter_expression. The number of supplied parameters must
758
   *  fit with the requested values in the filter_expression (i.e., the number of %n tokens). default: undefined.
759
   * @param {SubscriptionCallback} callback - The callback to be call when receiving the topic subscribed. The topic will be an instance of null-terminated Buffer when options.isRaw is true.
760
   * @param {SubscriptionEventCallbacks} eventCallbacks - The event callbacks for the subscription.
761
   * @return {Subscription} - An instance of Subscription.
762
   * @throws {ERROR} - May throw an RMW error if content-filter is malformed. 
763
   * @see {@link SubscriptionCallback}
764
   * @see {@link https://www.omg.org/spec/DDS/1.4/PDF|Content-filter details at DDS 1.4 specification, Annex B}
765
   */
766
  createSubscription(typeClass, topic, options, callback, eventCallbacks) {
767
    if (typeof typeClass === 'string' || typeof typeClass === 'object') {
428✔
768
      typeClass = loader.loadInterface(typeClass);
419✔
769
    }
770

771
    if (typeof options === 'function') {
421✔
772
      callback = options;
373✔
773
      options = undefined;
373✔
774
    }
775
    options = this._validateOptions(options);
421✔
776

777
    if (typeof typeClass !== 'function') {
411✔
778
      throw new TypeValidationError('typeClass', typeClass, 'function', {
4✔
779
        nodeName: this.name(),
780
        entityType: 'subscription',
781
      });
782
    }
783
    if (typeof topic !== 'string') {
407✔
784
      throw new TypeValidationError('topic', topic, 'string', {
6✔
785
        nodeName: this.name(),
786
        entityType: 'subscription',
787
      });
788
    }
789
    if (typeof callback !== 'function') {
401!
790
      throw new TypeValidationError('callback', callback, 'function', {
×
791
        nodeName: this.name(),
792
        entityType: 'subscription',
793
        entityName: topic,
794
      });
795
    }
796
    if (
401!
797
      eventCallbacks &&
405✔
798
      !(eventCallbacks instanceof SubscriptionEventCallbacks)
799
    ) {
800
      throw new TypeValidationError(
×
801
        'eventCallbacks',
802
        eventCallbacks,
803
        'SubscriptionEventCallbacks',
804
        {
805
          nodeName: this.name(),
806
          entityType: 'subscription',
807
          entityName: topic,
808
        }
809
      );
810
    }
811

812
    let subscription = Subscription.createSubscription(
401✔
813
      this,
814
      typeClass,
815
      topic,
816
      options,
817
      callback,
818
      eventCallbacks
819
    );
820
    debug('Finish creating subscription, topic = %s.', topic);
390✔
821
    this._subscriptions.push(subscription);
390✔
822
    this.syncHandles();
390✔
823

824
    return subscription;
390✔
825
  }
826

827
  /**
828
   * Create a Subscription that returns an RxJS Observable.
829
   * This allows using reactive programming patterns with ROS 2 messages.
830
   *
831
   * @param {function|string|object} typeClass - The ROS message class,
832
   *      OR a string representing the message class, e.g. 'std_msgs/msg/String',
833
   *      OR an object representing the message class, e.g. {package: 'std_msgs', type: 'msg', name: 'String'}
834
   * @param {string} topic - The name of the topic.
835
   * @param {object} [options] - The options argument used to parameterize the subscription.
836
   * @param {boolean} [options.enableTypedArray=true] - The topic will use TypedArray if necessary.
837
   * @param {QoS} [options.qos=QoS.profileDefault] - ROS Middleware "quality of service" settings.
838
   * @param {boolean} [options.isRaw=false] - The topic is serialized when true.
839
   * @param {string} [options.serializationMode='default'] - Controls message serialization format.
840
   * @param {object} [options.contentFilter] - The content-filter (if supported by RMW).
841
   * @param {SubscriptionEventCallbacks} [eventCallbacks] - The event callbacks for the subscription.
842
   * @return {ObservableSubscription} - An ObservableSubscription with an RxJS Observable.
843
   */
844
  createObservableSubscription(typeClass, topic, options, eventCallbacks) {
845
    let observableSubscription = null;
7✔
846

847
    const subscription = this.createSubscription(
7✔
848
      typeClass,
849
      topic,
850
      options,
851
      (message) => {
852
        if (observableSubscription) {
8!
853
          observableSubscription._emit(message);
8✔
854
        }
855
      },
856
      eventCallbacks
857
    );
858

859
    observableSubscription = new ObservableSubscription(subscription);
7✔
860
    return observableSubscription;
7✔
861
  }
862

863
  /**
864
   * Create a Client.
865
   * @param {function|string|object} typeClass - The ROS message class,
866
        OR a string representing the message class, e.g. 'std_msgs/msg/String',
867
        OR an object representing the message class, e.g. {package: 'std_msgs', type: 'msg', name: 'String'}
868
   * @param {string} serviceName - The service name to request.
869
   * @param {object} options - The options argument used to parameterize the client.
870
   * @param {boolean} options.enableTypedArray - The response will use TypedArray if necessary, default: true.
871
   * @param {QoS} options.qos - ROS Middleware "quality of service" settings for the client, default: QoS.profileDefault.
872
   * @return {Client} - An instance of Client.
873
   */
874
  createClient(typeClass, serviceName, options) {
875
    if (typeof typeClass === 'string' || typeof typeClass === 'object') {
196✔
876
      typeClass = loader.loadInterface(typeClass);
186✔
877
    }
878
    options = this._validateOptions(options);
189✔
879

880
    if (typeof typeClass !== 'function') {
189✔
881
      throw new TypeValidationError('typeClass', typeClass, 'function', {
8✔
882
        nodeName: this.name(),
883
        entityType: 'client',
884
      });
885
    }
886
    if (typeof serviceName !== 'string') {
181✔
887
      throw new TypeValidationError('serviceName', serviceName, 'string', {
12✔
888
        nodeName: this.name(),
889
        entityType: 'client',
890
      });
891
    }
892

893
    let client = Client.createClient(
169✔
894
      this.handle,
895
      serviceName,
896
      typeClass,
897
      options
898
    );
899
    debug('Finish creating client, service = %s.', serviceName);
159✔
900
    this._clients.push(client);
159✔
901
    this.syncHandles();
159✔
902

903
    return client;
159✔
904
  }
905

906
  /**
907
   * This callback is called when a request is sent to service
908
   * @callback RequestCallback
909
   * @param {Object} request - The request sent to the service
910
   * @param {Response} response - The response to client.
911
        Use [response.send()]{@link Response#send} to send response object to client
912
   * @return {undefined}
913
   * @see [Node.createService]{@link Node#createService}
914
   * @see [Client.sendRequest]{@link Client#sendRequest}
915
   * @see {@link Client}
916
   * @see {@link Service}
917
   * @see {@link Response#send}
918
   */
919

920
  /**
921
   * Create a Service.
922
   * @param {function|string|object} typeClass - The ROS message class,
923
        OR a string representing the message class, e.g. 'std_msgs/msg/String',
924
        OR an object representing the message class, e.g. {package: 'std_msgs', type: 'msg', name: 'String'}
925
   * @param {string} serviceName - The service name to offer.
926
   * @param {object} options - The options argument used to parameterize the service.
927
   * @param {boolean} options.enableTypedArray - The request will use TypedArray if necessary, default: true.
928
   * @param {QoS} options.qos - ROS Middleware "quality of service" settings for the service, default: QoS.profileDefault.
929
   * @param {RequestCallback} callback - The callback to be called when receiving request.
930
   * @return {Service} - An instance of Service.
931
   * @see {@link RequestCallback}
932
   */
933
  createService(typeClass, serviceName, options, callback) {
934
    if (typeof typeClass === 'string' || typeof typeClass === 'object') {
4,879✔
935
      typeClass = loader.loadInterface(typeClass);
4,869✔
936
    }
937

938
    if (typeof options === 'function') {
4,872✔
939
      callback = options;
4,849✔
940
      options = undefined;
4,849✔
941
    }
942
    options = this._validateOptions(options);
4,872✔
943

944
    if (typeof typeClass !== 'function') {
4,862✔
945
      throw new TypeValidationError('typeClass', typeClass, 'function', {
4✔
946
        nodeName: this.name(),
947
        entityType: 'service',
948
      });
949
    }
950
    if (typeof serviceName !== 'string') {
4,858✔
951
      throw new TypeValidationError('serviceName', serviceName, 'string', {
6✔
952
        nodeName: this.name(),
953
        entityType: 'service',
954
      });
955
    }
956
    if (typeof callback !== 'function') {
4,852!
957
      throw new TypeValidationError('callback', callback, 'function', {
×
958
        nodeName: this.name(),
959
        entityType: 'service',
960
        entityName: serviceName,
961
      });
962
    }
963

964
    let service = Service.createService(
4,852✔
965
      this.handle,
966
      serviceName,
967
      typeClass,
968
      options,
969
      callback
970
    );
971
    debug('Finish creating service, service = %s.', serviceName);
4,842✔
972
    this._services.push(service);
4,842✔
973
    this.syncHandles();
4,842✔
974

975
    return service;
4,842✔
976
  }
977

978
  /**
979
   * Create a ParameterClient for accessing parameters on a remote node.
980
   * @param {string} remoteNodeName - The name of the remote node whose parameters to access.
981
   * @param {object} [options] - Options for parameter client.
982
   * @param {number} [options.timeout=5000] - Default timeout in milliseconds for service calls.
983
   * @return {ParameterClient} - An instance of ParameterClient.
984
   */
985
  createParameterClient(remoteNodeName, options = {}) {
50✔
986
    if (typeof remoteNodeName !== 'string' || remoteNodeName.trim() === '') {
103!
987
      throw new TypeError('Remote node name must be a non-empty string');
×
988
    }
989

990
    const parameterClient = new ParameterClient(this, remoteNodeName, options);
103✔
991
    debug(
103✔
992
      'Finish creating parameter client for remote node = %s.',
993
      remoteNodeName
994
    );
995
    this._parameterClients.push(parameterClient);
103✔
996

997
    return parameterClient;
103✔
998
  }
999

1000
  /**
1001
   * Create a ParameterWatcher for watching parameter changes on a remote node.
1002
   * @param {string} remoteNodeName - The name of the remote node whose parameters to watch.
1003
   * @param {string[]} parameterNames - Array of parameter names to watch.
1004
   * @param {object} [options] - Options for parameter watcher.
1005
   * @param {number} [options.timeout=5000] - Default timeout in milliseconds for service calls.
1006
   * @return {ParameterWatcher} - An instance of ParameterWatcher.
1007
   */
1008
  createParameterWatcher(remoteNodeName, parameterNames, options = {}) {
56✔
1009
    const watcher = new ParameterWatcher(
57✔
1010
      this,
1011
      remoteNodeName,
1012
      parameterNames,
1013
      options
1014
    );
1015
    debug(
53✔
1016
      'Finish creating parameter watcher for remote node = %s.',
1017
      remoteNodeName
1018
    );
1019
    this._parameterWatchers.push(watcher);
53✔
1020

1021
    return watcher;
53✔
1022
  }
1023

1024
  /**
1025
   * Create a guard condition.
1026
   * @param {Function} callback - The callback to be called when the guard condition is triggered.
1027
   * @return {GuardCondition} - An instance of GuardCondition.
1028
   */
1029
  createGuardCondition(callback) {
1030
    if (typeof callback !== 'function') {
3!
1031
      throw new TypeValidationError('callback', callback, 'function', {
×
1032
        nodeName: this.name(),
1033
        entityType: 'guard_condition',
1034
      });
1035
    }
1036

1037
    let guard = GuardCondition.createGuardCondition(callback, this.context);
3✔
1038
    debug('Finish creating guard condition');
3✔
1039
    this._guards.push(guard);
3✔
1040
    this.syncHandles();
3✔
1041

1042
    return guard;
3✔
1043
  }
1044

1045
  /**
1046
   * Destroy all resource allocated by this node, including
1047
   * <code>Timer</code>s/<code>Publisher</code>s/<code>Subscription</code>s
1048
   * /<code>Client</code>s/<code>Service</code>s
1049
   * @return {undefined}
1050
   */
1051
  destroy() {
1052
    if (this.spinning) {
831✔
1053
      this.stop();
538✔
1054
    }
1055

1056
    // Action servers/clients require manual destruction due to circular reference with goal handles.
1057
    this._actionClients.forEach((actionClient) => actionClient.destroy());
831✔
1058
    this._actionServers.forEach((actionServer) => actionServer.destroy());
831✔
1059

1060
    this._parameterClients.forEach((paramClient) => paramClient.destroy());
831✔
1061
    this._parameterWatchers.forEach((watcher) => watcher.destroy());
831✔
1062

1063
    this.context.onNodeDestroyed(this);
831✔
1064

1065
    if (this._enableRosout) {
831✔
1066
      rclnodejs.finiRosoutPublisherForNode(this.handle);
807✔
1067
      this._enableRosout = false;
807✔
1068
    }
1069

1070
    this.handle.release();
831✔
1071
    this._clock = null;
831✔
1072
    this._timers = [];
831✔
1073
    this._publishers = [];
831✔
1074
    this._subscriptions = [];
831✔
1075
    this._clients = [];
831✔
1076
    this._services = [];
831✔
1077
    this._guards = [];
831✔
1078
    this._actionClients = [];
831✔
1079
    this._actionServers = [];
831✔
1080
    this._parameterClients = [];
831✔
1081
    this._parameterWatchers = [];
831✔
1082

1083
    if (this._rateTimerServer) {
831✔
1084
      this._rateTimerServer.shutdown();
5✔
1085
      this._rateTimerServer = null;
5✔
1086
    }
1087
  }
1088

1089
  /**
1090
   * Destroy a Publisher.
1091
   * @param {Publisher} publisher - The Publisher to be destroyed.
1092
   * @return {undefined}
1093
   */
1094
  destroyPublisher(publisher) {
1095
    if (!(publisher instanceof Publisher)) {
9✔
1096
      throw new TypeValidationError(
2✔
1097
        'publisher',
1098
        publisher,
1099
        'Publisher instance',
1100
        {
1101
          nodeName: this.name(),
1102
        }
1103
      );
1104
    }
1105
    if (publisher.events) {
7!
1106
      publisher.events.forEach((event) => {
×
1107
        this._destroyEntity(event, this._events);
×
1108
      });
1109
      publisher.events = [];
×
1110
    }
1111
    this._destroyEntity(publisher, this._publishers, false);
7✔
1112
  }
1113

1114
  /**
1115
   * Destroy a Subscription.
1116
   * @param {Subscription} subscription - The Subscription to be destroyed.
1117
   * @return {undefined}
1118
   */
1119
  destroySubscription(subscription) {
1120
    if (!(subscription instanceof Subscription)) {
78✔
1121
      throw new TypeValidationError(
2✔
1122
        'subscription',
1123
        subscription,
1124
        'Subscription instance',
1125
        {
1126
          nodeName: this.name(),
1127
        }
1128
      );
1129
    }
1130
    if (subscription.events) {
76✔
1131
      subscription.events.forEach((event) => {
1✔
1132
        this._destroyEntity(event, this._events);
1✔
1133
      });
1134
      subscription.events = [];
1✔
1135
    }
1136

1137
    this._destroyEntity(subscription, this._subscriptions);
76✔
1138
  }
1139

1140
  /**
1141
   * Destroy a Client.
1142
   * @param {Client} client - The Client to be destroyed.
1143
   * @return {undefined}
1144
   */
1145
  destroyClient(client) {
1146
    if (!(client instanceof Client)) {
117✔
1147
      throw new TypeValidationError('client', client, 'Client instance', {
2✔
1148
        nodeName: this.name(),
1149
      });
1150
    }
1151
    this._destroyEntity(client, this._clients);
115✔
1152
  }
1153

1154
  /**
1155
   * Destroy a Service.
1156
   * @param {Service} service - The Service to be destroyed.
1157
   * @return {undefined}
1158
   */
1159
  destroyService(service) {
1160
    if (!(service instanceof Service)) {
8✔
1161
      throw new TypeValidationError('service', service, 'Service instance', {
2✔
1162
        nodeName: this.name(),
1163
      });
1164
    }
1165
    this._destroyEntity(service, this._services);
6✔
1166
  }
1167

1168
  /**
1169
   * Destroy a ParameterClient.
1170
   * @param {ParameterClient} parameterClient - The ParameterClient to be destroyed.
1171
   * @return {undefined}
1172
   */
1173
  destroyParameterClient(parameterClient) {
1174
    if (!(parameterClient instanceof ParameterClient)) {
54!
1175
      throw new TypeError('Invalid argument');
×
1176
    }
1177
    this._removeEntityFromArray(parameterClient, this._parameterClients);
54✔
1178
    parameterClient.destroy();
54✔
1179
  }
1180

1181
  /**
1182
   * Destroy a ParameterWatcher.
1183
   * @param {ParameterWatcher} watcher - The ParameterWatcher to be destroyed.
1184
   * @return {undefined}
1185
   */
1186
  destroyParameterWatcher(watcher) {
1187
    if (!(watcher instanceof ParameterWatcher)) {
1!
1188
      throw new TypeError('Invalid argument');
×
1189
    }
1190
    this._removeEntityFromArray(watcher, this._parameterWatchers);
1✔
1191
    watcher.destroy();
1✔
1192
  }
1193

1194
  /**
1195
   * Destroy a Timer.
1196
   * @param {Timer} timer - The Timer to be destroyed.
1197
   * @return {undefined}
1198
   */
1199
  destroyTimer(timer) {
1200
    if (!(timer instanceof Timer)) {
8✔
1201
      throw new TypeValidationError('timer', timer, 'Timer instance', {
2✔
1202
        nodeName: this.name(),
1203
      });
1204
    }
1205
    this._destroyEntity(timer, this._timers);
6✔
1206
  }
1207

1208
  /**
1209
   * Destroy a guard condition.
1210
   * @param {GuardCondition} guard - The guard condition to be destroyed.
1211
   * @return {undefined}
1212
   */
1213
  destroyGuardCondition(guard) {
1214
    if (!(guard instanceof GuardCondition)) {
3!
1215
      throw new TypeValidationError('guard', guard, 'GuardCondition instance', {
×
1216
        nodeName: this.name(),
1217
      });
1218
    }
1219
    this._destroyEntity(guard, this._guards);
3✔
1220
  }
1221

1222
  /**
1223
   * Get the name of the node.
1224
   * @return {string}
1225
   */
1226
  name() {
1227
    return rclnodejs.getNodeName(this.handle);
4,765✔
1228
  }
1229

1230
  /**
1231
   * Get the namespace of the node.
1232
   * @return {string}
1233
   */
1234
  namespace() {
1235
    return rclnodejs.getNamespace(this.handle);
4,478✔
1236
  }
1237

1238
  /**
1239
   * Get the context in which this node was created.
1240
   * @return {Context}
1241
   */
1242
  get context() {
1243
    return this._context;
6,145✔
1244
  }
1245

1246
  /**
1247
   * Get the nodes logger.
1248
   * @returns {Logger} - The logger for the node.
1249
   */
1250
  getLogger() {
1251
    return this._logger;
146✔
1252
  }
1253

1254
  /**
1255
   * Get the clock used by the node.
1256
   * @returns {Clock} - The nodes clock.
1257
   */
1258
  getClock() {
1259
    return this._clock;
86✔
1260
  }
1261

1262
  /**
1263
   * Get the current time using the node's clock.
1264
   * @returns {Timer} - The current time.
1265
   */
1266
  now() {
1267
    return this.getClock().now();
2✔
1268
  }
1269

1270
  /**
1271
   * Get the list of published topics discovered by the provided node for the remote node name.
1272
   * @param {string} nodeName - The name of the node.
1273
   * @param {string} namespace - The name of the namespace.
1274
   * @param {boolean} noDemangle - If true topic names and types returned will not be demangled, default: false.
1275
   * @return {Array<{name: string, types: Array<string>}>} - An array of the names and types.
1276
   */
1277
  getPublisherNamesAndTypesByNode(nodeName, namespace, noDemangle = false) {
2✔
1278
    return rclnodejs.getPublisherNamesAndTypesByNode(
2✔
1279
      this.handle,
1280
      nodeName,
1281
      namespace,
1282
      noDemangle
1283
    );
1284
  }
1285

1286
  /**
1287
   * Get the list of published topics discovered by the provided node for the remote node name.
1288
   * @param {string} nodeName - The name of the node.
1289
   * @param {string} namespace - The name of the namespace.
1290
   * @param {boolean} noDemangle - If true topic names and types returned will not be demangled, default: false.
1291
   * @return {Array<{name: string, types: Array<string>}>} - An array of the names and types.
1292
   */
1293
  getSubscriptionNamesAndTypesByNode(nodeName, namespace, noDemangle = false) {
×
1294
    return rclnodejs.getSubscriptionNamesAndTypesByNode(
×
1295
      this.handle,
1296
      nodeName,
1297
      namespace,
1298
      noDemangle
1299
    );
1300
  }
1301

1302
  /**
1303
   * Get service names and types for which a remote node has servers.
1304
   * @param {string} nodeName - The name of the node.
1305
   * @param {string} namespace - The name of the namespace.
1306
   * @return {Array<{name: string, types: Array<string>}>} - An array of the names and types.
1307
   */
1308
  getServiceNamesAndTypesByNode(nodeName, namespace) {
1309
    return rclnodejs.getServiceNamesAndTypesByNode(
×
1310
      this.handle,
1311
      nodeName,
1312
      namespace
1313
    );
1314
  }
1315

1316
  /**
1317
   * Get service names and types for which a remote node has clients.
1318
   * @param {string} nodeName - The name of the node.
1319
   * @param {string} namespace - The name of the namespace.
1320
   * @return {Array<{name: string, types: Array<string>}>} - An array of the names and types.
1321
   */
1322
  getClientNamesAndTypesByNode(nodeName, namespace) {
1323
    return rclnodejs.getClientNamesAndTypesByNode(
×
1324
      this.handle,
1325
      nodeName,
1326
      namespace
1327
    );
1328
  }
1329

1330
  /**
1331
   * Get the list of topics discovered by the provided node.
1332
   * @param {boolean} noDemangle - If true topic names and types returned will not be demangled, default: false.
1333
   * @return {Array<{name: string, types: Array<string>}>} - An array of the names and types.
1334
   */
1335
  getTopicNamesAndTypes(noDemangle = false) {
×
1336
    return rclnodejs.getTopicNamesAndTypes(this.handle, noDemangle);
×
1337
  }
1338

1339
  /**
1340
   * Get the list of services discovered by the provided node.
1341
   * @return {Array<{name: string, types: Array<string>}>} - An array of the names and types.
1342
   */
1343
  getServiceNamesAndTypes() {
1344
    return rclnodejs.getServiceNamesAndTypes(this.handle);
2✔
1345
  }
1346

1347
  /**
1348
   * Return a list of publishers on a given topic.
1349
   *
1350
   * The returned parameter is a list of TopicEndpointInfo objects, where each will contain
1351
   * the node name, node namespace, topic type, topic endpoint's GID, and its QoS profile.
1352
   *
1353
   * When the `no_mangle` parameter is `true`, the provided `topic` should be a valid
1354
   * topic name for the middleware (useful when combining ROS with native middleware (e.g. DDS)
1355
   * apps).  When the `no_mangle` parameter is `false`, the provided `topic` should
1356
   * follow ROS topic name conventions.
1357
   *
1358
   * `topic` may be a relative, private, or fully qualified topic name.
1359
   *  A relative or private topic will be expanded using this node's namespace and name.
1360
   *  The queried `topic` is not remapped.
1361
   *
1362
   * @param {string} topic - The topic on which to find the publishers.
1363
   * @param {boolean} [noDemangle=false] - If `true`, `topic` needs to be a valid middleware topic
1364
   *                               name, otherwise it should be a valid ROS topic name. Defaults to `false`.
1365
   * @returns {Array} - list of publishers
1366
   */
1367
  getPublishersInfoByTopic(topic, noDemangle = false) {
1✔
1368
    return rclnodejs.getPublishersInfoByTopic(
4✔
1369
      this.handle,
1370
      this._getValidatedTopic(topic, noDemangle),
1371
      noDemangle
1372
    );
1373
  }
1374

1375
  /**
1376
   * Return a list of subscriptions on a given topic.
1377
   *
1378
   * The returned parameter is a list of TopicEndpointInfo objects, where each will contain
1379
   * the node name, node namespace, topic type, topic endpoint's GID, and its QoS profile.
1380
   *
1381
   * When the `no_mangle` parameter is `true`, the provided `topic` should be a valid
1382
   * topic name for the middleware (useful when combining ROS with native middleware (e.g. DDS)
1383
   * apps).  When the `no_mangle` parameter is `false`, the provided `topic` should
1384
   * follow ROS topic name conventions.
1385
   *
1386
   * `topic` may be a relative, private, or fully qualified topic name.
1387
   *  A relative or private topic will be expanded using this node's namespace and name.
1388
   *  The queried `topic` is not remapped.
1389
   *
1390
   * @param {string} topic - The topic on which to find the subscriptions.
1391
   * @param {boolean} [noDemangle=false] -  If `true`, `topic` needs to be a valid middleware topic
1392
                                    name, otherwise it should be a valid ROS topic name. Defaults to `false`.
1393
   * @returns {Array} - list of subscriptions
1394
   */
1395
  getSubscriptionsInfoByTopic(topic, noDemangle = false) {
×
1396
    return rclnodejs.getSubscriptionsInfoByTopic(
2✔
1397
      this.handle,
1398
      this._getValidatedTopic(topic, noDemangle),
1399
      noDemangle
1400
    );
1401
  }
1402

1403
  /**
1404
   * Return a list of clients on a given service.
1405
   *
1406
   * The returned parameter is a list of ServiceEndpointInfo objects, where each will contain
1407
   * the node name, node namespace, service type, service endpoint's GID, and its QoS profile.
1408
   *
1409
   * When the `no_mangle` parameter is `true`, the provided `service` should be a valid
1410
   * service name for the middleware (useful when combining ROS with native middleware (e.g. DDS)
1411
   * apps).  When the `no_mangle` parameter is `false`, the provided `service` should
1412
   * follow ROS service name conventions.
1413
   *
1414
   * `service` may be a relative, private, or fully qualified service name.
1415
   *  A relative or private service will be expanded using this node's namespace and name.
1416
   *  The queried `service` is not remapped.
1417
   *
1418
   * @param {string} service - The service on which to find the clients.
1419
   * @param {boolean} [noDemangle=false] - If `true`, `service` needs to be a valid middleware service
1420
   *                               name, otherwise it should be a valid ROS service name. Defaults to `false`.
1421
   * @returns {Array} - list of clients
1422
   */
1423
  getClientsInfoByService(service, noDemangle = false) {
×
1424
    if (DistroUtils.getDistroId() < DistroUtils.DistroId.ROLLING) {
2!
1425
      console.warn(
×
1426
        'getClientsInfoByService is not supported by this version of ROS 2'
1427
      );
1428
      return null;
×
1429
    }
1430
    return rclnodejs.getClientsInfoByService(
2✔
1431
      this.handle,
1432
      this._getValidatedServiceName(service, noDemangle),
1433
      noDemangle
1434
    );
1435
  }
1436

1437
  /**
1438
   * Return a list of servers on a given service.
1439
   *
1440
   * The returned parameter is a list of ServiceEndpointInfo objects, where each will contain
1441
   * the node name, node namespace, service type, service endpoint's GID, and its QoS profile.
1442
   *
1443
   * When the `no_mangle` parameter is `true`, the provided `service` should be a valid
1444
   * service name for the middleware (useful when combining ROS with native middleware (e.g. DDS)
1445
   * apps).  When the `no_mangle` parameter is `false`, the provided `service` should
1446
   * follow ROS service name conventions.
1447
   *
1448
   * `service` may be a relative, private, or fully qualified service name.
1449
   *  A relative or private service will be expanded using this node's namespace and name.
1450
   *  The queried `service` is not remapped.
1451
   *
1452
   * @param {string} service - The service on which to find the servers.
1453
   * @param {boolean} [noDemangle=false] - If `true`, `service` needs to be a valid middleware service
1454
   *                               name, otherwise it should be a valid ROS service name. Defaults to `false`.
1455
   * @returns {Array} - list of servers
1456
   */
1457
  getServersInfoByService(service, noDemangle = false) {
×
1458
    if (DistroUtils.getDistroId() < DistroUtils.DistroId.ROLLING) {
2!
1459
      console.warn(
×
1460
        'getServersInfoByService is not supported by this version of ROS 2'
1461
      );
1462
      return null;
×
1463
    }
1464
    return rclnodejs.getServersInfoByService(
2✔
1465
      this.handle,
1466
      this._getValidatedServiceName(service, noDemangle),
1467
      noDemangle
1468
    );
1469
  }
1470

1471
  /**
1472
   * Get the list of nodes discovered by the provided node.
1473
   * @return {Array<string>} - An array of the names.
1474
   */
1475
  getNodeNames() {
1476
    return this.getNodeNamesAndNamespaces().map((item) => item.name);
41✔
1477
  }
1478

1479
  /**
1480
   * Get the list of nodes and their namespaces discovered by the provided node.
1481
   * @return {Array<{name: string, namespace: string}>} An array of the names and namespaces.
1482
   */
1483
  getNodeNamesAndNamespaces() {
1484
    return rclnodejs.getNodeNames(this.handle, /*getEnclaves=*/ false);
17✔
1485
  }
1486

1487
  /**
1488
   * Get the list of nodes and their namespaces with enclaves discovered by the provided node.
1489
   * @return {Array<{name: string, namespace: string, enclave: string}>} An array of the names, namespaces and enclaves.
1490
   */
1491
  getNodeNamesAndNamespacesWithEnclaves() {
1492
    return rclnodejs.getNodeNames(this.handle, /*getEnclaves=*/ true);
1✔
1493
  }
1494

1495
  /**
1496
   * Return the number of publishers on a given topic.
1497
   * @param {string} topic - The name of the topic.
1498
   * @returns {number} - Number of publishers on the given topic.
1499
   */
1500
  countPublishers(topic) {
1501
    let expandedTopic = rclnodejs.expandTopicName(
6✔
1502
      topic,
1503
      this.name(),
1504
      this.namespace()
1505
    );
1506
    rclnodejs.validateTopicName(expandedTopic);
6✔
1507

1508
    return rclnodejs.countPublishers(this.handle, expandedTopic);
6✔
1509
  }
1510

1511
  /**
1512
   * Return the number of subscribers on a given topic.
1513
   * @param {string} topic - The name of the topic.
1514
   * @returns {number} - Number of subscribers on the given topic.
1515
   */
1516
  countSubscribers(topic) {
1517
    let expandedTopic = rclnodejs.expandTopicName(
6✔
1518
      topic,
1519
      this.name(),
1520
      this.namespace()
1521
    );
1522
    rclnodejs.validateTopicName(expandedTopic);
6✔
1523

1524
    return rclnodejs.countSubscribers(this.handle, expandedTopic);
6✔
1525
  }
1526

1527
  /**
1528
   * Get the number of clients on a given service name.
1529
   * @param {string} serviceName - the service name
1530
   * @returns {Number}
1531
   */
1532
  countClients(serviceName) {
1533
    if (DistroUtils.getDistroId() <= DistroUtils.getDistroId('humble')) {
2!
1534
      console.warn('countClients is not supported by this version of ROS 2');
×
1535
      return null;
×
1536
    }
1537
    return rclnodejs.countClients(this.handle, serviceName);
2✔
1538
  }
1539

1540
  /**
1541
   * Get the number of services on a given service name.
1542
   * @param {string} serviceName - the service name
1543
   * @returns {Number}
1544
   */
1545
  countServices(serviceName) {
1546
    if (DistroUtils.getDistroId() <= DistroUtils.getDistroId('humble')) {
1!
1547
      console.warn('countServices is not supported by this version of ROS 2');
×
1548
      return null;
×
1549
    }
1550
    return rclnodejs.countServices(this.handle, serviceName);
1✔
1551
  }
1552

1553
  /**
1554
   * Get the list of parameter-overrides found on the commandline and
1555
   * in the NodeOptions.parameter_overrides property.
1556
   *
1557
   * @return {Array<Parameter>} - An array of Parameters.
1558
   */
1559
  getParameterOverrides() {
1560
    return Array.from(this._parameterOverrides.values());
8✔
1561
  }
1562

1563
  /**
1564
   * Declare a parameter.
1565
   *
1566
   * Internally, register a parameter and it's descriptor.
1567
   * If a parameter-override exists, it's value will replace that of the parameter
1568
   * unless ignoreOverride is true.
1569
   * If the descriptor is undefined, then a ParameterDescriptor will be inferred
1570
   * from the parameter's state.
1571
   *
1572
   * If a parameter by the same name has already been declared then an Error is thrown.
1573
   * A parameter must be undeclared before attempting to redeclare it.
1574
   *
1575
   * @param {Parameter} parameter - Parameter to declare.
1576
   * @param {ParameterDescriptor} [descriptor] - Optional descriptor for parameter.
1577
   * @param {boolean} [ignoreOverride] - When true disregard any parameter-override that may be present.
1578
   * @return {Parameter} - The newly declared parameter.
1579
   */
1580
  declareParameter(parameter, descriptor, ignoreOverride = false) {
2,196✔
1581
    const parameters = this.declareParameters(
2,197✔
1582
      [parameter],
1583
      descriptor ? [descriptor] : [],
2,197✔
1584
      ignoreOverride
1585
    );
1586
    return parameters.length == 1 ? parameters[0] : null;
2,197!
1587
  }
1588

1589
  /**
1590
   * Declare a list of parameters.
1591
   *
1592
   * Internally register parameters with their corresponding descriptor one by one
1593
   * in the order they are provided. This is an atomic operation. If an error
1594
   * occurs the process halts and no further parameters are declared.
1595
   * Parameters that have already been processed are undeclared.
1596
   *
1597
   * While descriptors is an optional parameter, when provided there must be
1598
   * a descriptor for each parameter; otherwise an Error is thrown.
1599
   * If descriptors is not provided then a descriptor will be inferred
1600
   * from each parameter's state.
1601
   *
1602
   * When a parameter-override is available, the parameter's value
1603
   * will be replaced with that of the parameter-override unless ignoreOverrides
1604
   * is true.
1605
   *
1606
   * If a parameter by the same name has already been declared then an Error is thrown.
1607
   * A parameter must be undeclared before attempting to redeclare it.
1608
   *
1609
   * Prior to declaring the parameters each SetParameterEventCallback registered
1610
   * using setOnParameterEventCallback() is called in succession with the parameters
1611
   * list. Any SetParameterEventCallback that retuns does not return a successful
1612
   * result will cause the entire operation to terminate with no changes to the
1613
   * parameters. When all SetParameterEventCallbacks return successful then the
1614
   * list of parameters is updated.
1615
   *
1616
   * @param {Parameter[]} parameters - The parameters to declare.
1617
   * @param {ParameterDescriptor[]} [descriptors] - Optional descriptors,
1618
   *    a 1-1 correspondence with parameters.
1619
   * @param {boolean} ignoreOverrides - When true, parameter-overrides are
1620
   *    not considered, i.e.,ignored.
1621
   * @return {Parameter[]} - The declared parameters.
1622
   */
1623
  declareParameters(parameters, descriptors = [], ignoreOverrides = false) {
×
1624
    if (!Array.isArray(parameters)) {
2,197!
1625
      throw new TypeValidationError('parameters', parameters, 'Array', {
×
1626
        nodeName: this.name(),
1627
      });
1628
    }
1629
    if (!Array.isArray(descriptors)) {
2,197!
1630
      throw new TypeValidationError('descriptors', descriptors, 'Array', {
×
1631
        nodeName: this.name(),
1632
      });
1633
    }
1634
    if (descriptors.length > 0 && parameters.length !== descriptors.length) {
2,197!
1635
      throw new ValidationError(
×
1636
        'Each parameter must have a corresponding ParameterDescriptor',
1637
        {
1638
          code: 'PARAMETER_DESCRIPTOR_MISMATCH',
1639
          argumentName: 'descriptors',
1640
          providedValue: descriptors.length,
1641
          expectedType: `Array with length ${parameters.length}`,
1642
          nodeName: this.name(),
1643
        }
1644
      );
1645
    }
1646

1647
    const declaredDescriptors = [];
2,197✔
1648
    const declaredParameters = [];
2,197✔
1649
    const declaredParameterCollisions = [];
2,197✔
1650
    for (let i = 0; i < parameters.length; i++) {
2,197✔
1651
      let parameter =
1652
        !ignoreOverrides && this._parameterOverrides.has(parameters[i].name)
2,197✔
1653
          ? this._parameterOverrides.get(parameters[i].name)
1654
          : parameters[i];
1655

1656
      // stop processing parameters that have already been declared
1657
      if (this._parameters.has(parameter.name)) {
2,197!
1658
        declaredParameterCollisions.push(parameter);
×
1659
        continue;
×
1660
      }
1661

1662
      // create descriptor for parameter if not provided
1663
      let descriptor =
1664
        descriptors.length > 0
2,197✔
1665
          ? descriptors[i]
1666
          : ParameterDescriptor.fromParameter(parameter);
1667

1668
      descriptor.validate();
2,197✔
1669

1670
      declaredDescriptors.push(descriptor);
2,197✔
1671
      declaredParameters.push(parameter);
2,197✔
1672
    }
1673

1674
    if (declaredParameterCollisions.length > 0) {
2,197!
1675
      const errorMsg =
1676
        declaredParameterCollisions.length == 1
×
1677
          ? `Parameter(${declaredParameterCollisions[0]}) already declared.`
1678
          : `Multiple parameters already declared, e.g., Parameter(${declaredParameterCollisions[0]}).`;
1679
      throw new Error(errorMsg);
×
1680
    }
1681

1682
    // register descriptor
1683
    for (const descriptor of declaredDescriptors) {
2,197✔
1684
      this._parameterDescriptors.set(descriptor.name, descriptor);
2,197✔
1685
    }
1686

1687
    const result = this._setParametersAtomically(declaredParameters, true);
2,197✔
1688
    if (!result.successful) {
2,197!
1689
      // unregister descriptors
1690
      for (const descriptor of declaredDescriptors) {
×
1691
        this._parameterDescriptors.delete(descriptor.name);
×
1692
      }
1693

1694
      throw new Error(result.reason);
×
1695
    }
1696

1697
    return this.getParameters(declaredParameters.map((param) => param.name));
2,197✔
1698
  }
1699

1700
  /**
1701
   * Undeclare a parameter.
1702
   *
1703
   * Readonly parameters can not be undeclared or updated.
1704
   * @param {string} name - Name of parameter to undeclare.
1705
   * @return {undefined} -
1706
   */
1707
  undeclareParameter(name) {
1708
    if (!this.hasParameter(name)) return;
1!
1709

1710
    const descriptor = this.getParameterDescriptor(name);
1✔
1711
    if (descriptor.readOnly) {
1!
1712
      throw new Error(
×
1713
        `${name} parameter is read-only and can not be undeclared`
1714
      );
1715
    }
1716

1717
    this._parameters.delete(name);
1✔
1718
    this._parameterDescriptors.delete(name);
1✔
1719
  }
1720

1721
  /**
1722
   * Determine if a parameter has been declared.
1723
   * @param {string} name - name of parameter
1724
   * @returns {boolean} - Return true if parameter is declared; false otherwise.
1725
   */
1726
  hasParameter(name) {
1727
    return this._parameters.has(name);
5,499✔
1728
  }
1729

1730
  /**
1731
   * Get a declared parameter by name.
1732
   *
1733
   * If unable to locate a declared parameter then a
1734
   * parameter with type == PARAMETER_NOT_SET is returned.
1735
   *
1736
   * @param {string} name - The name of the parameter.
1737
   * @return {Parameter} - The parameter.
1738
   */
1739
  getParameter(name) {
1740
    return this.getParameters([name])[0];
1,636✔
1741
  }
1742

1743
  /**
1744
   * Get a list of parameters.
1745
   *
1746
   * Find and return the declared parameters.
1747
   * If no names are provided return all declared parameters.
1748
   *
1749
   * If unable to locate a declared parameter then a
1750
   * parameter with type == PARAMETER_NOT_SET is returned in
1751
   * it's place.
1752
   *
1753
   * @param {string[]} [names] - The names of the declared parameters
1754
   *    to find or null indicating to return all declared parameters.
1755
   * @return {Parameter[]} - The parameters.
1756
   */
1757
  getParameters(names = []) {
12✔
1758
    let params = [];
3,871✔
1759

1760
    if (names.length == 0) {
3,871✔
1761
      // get all parameters
1762
      params = [...this._parameters.values()];
12✔
1763
      return params;
12✔
1764
    }
1765

1766
    for (const name of names) {
3,859✔
1767
      const param = this.hasParameter(name)
3,866✔
1768
        ? this._parameters.get(name)
1769
        : new Parameter(name, ParameterType.PARAMETER_NOT_SET);
1770

1771
      params.push(param);
3,866✔
1772
    }
1773

1774
    return params;
3,859✔
1775
  }
1776

1777
  /**
1778
   * Get the types of given parameters.
1779
   *
1780
   * Return the types of given parameters.
1781
   *
1782
   * @param {string[]} [names] - The names of the declared parameters.
1783
   * @return {Uint8Array} - The types.
1784
   */
1785
  getParameterTypes(names = []) {
×
1786
    let types = [];
3✔
1787

1788
    for (const name of names) {
3✔
1789
      const descriptor = this._parameterDescriptors.get(name);
7✔
1790
      if (descriptor) {
7!
1791
        types.push(descriptor.type);
7✔
1792
      }
1793
    }
1794
    return types;
3✔
1795
  }
1796

1797
  /**
1798
   * Get the names of all declared parameters.
1799
   *
1800
   * @return {Array<string>} - The declared parameter names or empty array if
1801
   *    no parameters have been declared.
1802
   */
1803
  getParameterNames() {
1804
    return this.getParameters().map((param) => param.name);
53✔
1805
  }
1806

1807
  /**
1808
   * Determine if a parameter descriptor exists.
1809
   *
1810
   * @param {string} name - The name of a descriptor to for.
1811
   * @return {boolean} - true if a descriptor has been declared; otherwise false.
1812
   */
1813
  hasParameterDescriptor(name) {
1814
    return !!this.getParameterDescriptor(name);
2,222✔
1815
  }
1816

1817
  /**
1818
   * Get a declared parameter descriptor by name.
1819
   *
1820
   * If unable to locate a declared parameter descriptor then a
1821
   * descriptor with type == PARAMETER_NOT_SET is returned.
1822
   *
1823
   * @param {string} name - The name of the parameter descriptor to find.
1824
   * @return {ParameterDescriptor} - The parameter descriptor.
1825
   */
1826
  getParameterDescriptor(name) {
1827
    return this.getParameterDescriptors([name])[0];
4,445✔
1828
  }
1829

1830
  /**
1831
   * Find a list of declared ParameterDescriptors.
1832
   *
1833
   * If no names are provided return all declared descriptors.
1834
   *
1835
   * If unable to locate a declared descriptor then a
1836
   * descriptor with type == PARAMETER_NOT_SET is returned in
1837
   * it's place.
1838
   *
1839
   * @param {string[]} [names] - The names of the declared parameter
1840
   *    descriptors to find or null indicating to return all declared descriptors.
1841
   * @return {ParameterDescriptor[]} - The parameter descriptors.
1842
   */
1843
  getParameterDescriptors(names = []) {
×
1844
    let descriptors = [];
4,448✔
1845

1846
    if (names.length == 0) {
4,448!
1847
      // get all parameters
1848
      descriptors = [...this._parameterDescriptors.values()];
×
1849
      return descriptors;
×
1850
    }
1851

1852
    for (const name of names) {
4,448✔
1853
      let descriptor = this._parameterDescriptors.get(name);
4,450✔
1854
      if (!descriptor) {
4,450!
1855
        descriptor = new ParameterDescriptor(
×
1856
          name,
1857
          ParameterType.PARAMETER_NOT_SET
1858
        );
1859
      }
1860
      descriptors.push(descriptor);
4,450✔
1861
    }
1862

1863
    return descriptors;
4,448✔
1864
  }
1865

1866
  /**
1867
   * Replace a declared parameter.
1868
   *
1869
   * The parameter being replaced must be a declared parameter who's descriptor
1870
   * is not readOnly; otherwise an Error is thrown.
1871
   *
1872
   * @param {Parameter} parameter - The new parameter.
1873
   * @return {rclnodejs.rcl_interfaces.msg.SetParameterResult} - The result of the operation.
1874
   */
1875
  setParameter(parameter) {
1876
    const results = this.setParameters([parameter]);
10✔
1877
    return results[0];
10✔
1878
  }
1879

1880
  /**
1881
   * Replace a list of declared parameters.
1882
   *
1883
   * Declared parameters are replaced in the order they are provided and
1884
   * a ParameterEvent is published for each individual parameter change.
1885
   *
1886
   * Prior to setting the parameters each SetParameterEventCallback registered
1887
   * using setOnParameterEventCallback() is called in succession with the parameters
1888
   * list. Any SetParameterEventCallback that retuns does not return a successful
1889
   * result will cause the entire operation to terminate with no changes to the
1890
   * parameters. When all SetParameterEventCallbacks return successful then the
1891
   * list of parameters is updated.
1892
   *
1893
   * If an error occurs, the process is stopped and returned. Parameters
1894
   * set before an error remain unchanged.
1895
   *
1896
   * @param {Parameter[]} parameters - The parameters to set.
1897
   * @return {rclnodejs.rcl_interfaces.msg.SetParameterResult[]} - A list of SetParameterResult, one for each parameter that was set.
1898
   */
1899
  setParameters(parameters = []) {
×
1900
    return parameters.map((parameter) =>
21✔
1901
      this.setParametersAtomically([parameter])
24✔
1902
    );
1903
  }
1904

1905
  /**
1906
   * Repalce a list of declared parameters atomically.
1907
   *
1908
   * Declared parameters are replaced in the order they are provided.
1909
   * A single ParameterEvent is published collectively for all changed
1910
   * parameters.
1911
   *
1912
   * Prior to setting the parameters each SetParameterEventCallback registered
1913
   * using setOnParameterEventCallback() is called in succession with the parameters
1914
   * list. Any SetParameterEventCallback that retuns does not return a successful
1915
   * result will cause the entire operation to terminate with no changes to the
1916
   * parameters. When all SetParameterEventCallbacks return successful then the
1917
   * list of parameters is updated.d
1918
   *
1919
   * If an error occurs, the process stops immediately. All parameters updated to
1920
   * the point of the error are reverted to their previous state.
1921
   *
1922
   * @param {Parameter[]} parameters - The parameters to set.
1923
   * @return {rclnodejs.rcl_interfaces.msg.SetParameterResult} - describes the result of setting 1 or more parameters.
1924
   */
1925
  setParametersAtomically(parameters = []) {
×
1926
    return this._setParametersAtomically(parameters);
25✔
1927
  }
1928

1929
  /**
1930
   * Internal method for updating parameters atomically.
1931
   *
1932
   * Prior to setting the parameters each SetParameterEventCallback registered
1933
   * using setOnParameterEventCallback() is called in succession with the parameters
1934
   * list. Any SetParameterEventCallback that retuns does not return a successful
1935
   * result will cause the entire operation to terminate with no changes to the
1936
   * parameters. When all SetParameterEventCallbacks return successful then the
1937
   * list of parameters is updated.
1938
   *
1939
   * @param {Paramerter[]} parameters - The parameters to update.
1940
   * @param {boolean} declareParameterMode - When true parameters are being declared;
1941
   *    otherwise they are being changed.
1942
   * @return {SetParameterResult} - A single collective result.
1943
   */
1944
  _setParametersAtomically(parameters = [], declareParameterMode = false) {
25!
1945
    let result = this._validateParameters(parameters, declareParameterMode);
2,222✔
1946
    if (!result.successful) {
2,222!
1947
      return result;
×
1948
    }
1949

1950
    // give all SetParametersCallbacks a chance to veto this change
1951
    for (const callback of this._setParametersCallbacks) {
2,222✔
1952
      result = callback(parameters);
1,421✔
1953
      if (!result.successful) {
1,421✔
1954
        // a callback has vetoed a parameter change
1955
        return result;
1✔
1956
      }
1957
    }
1958

1959
    // collectively track updates to parameters for use
1960
    // when publishing a ParameterEvent
1961
    const newParameters = [];
2,221✔
1962
    const changedParameters = [];
2,221✔
1963
    const deletedParameters = [];
2,221✔
1964

1965
    for (const parameter of parameters) {
2,221✔
1966
      if (parameter.type == ParameterType.PARAMETER_NOT_SET) {
2,221✔
1967
        this.undeclareParameter(parameter.name);
1✔
1968
        deletedParameters.push(parameter);
1✔
1969
      } else {
1970
        this._parameters.set(parameter.name, parameter);
2,220✔
1971
        if (declareParameterMode) {
2,220✔
1972
          newParameters.push(parameter);
2,197✔
1973
        } else {
1974
          changedParameters.push(parameter);
23✔
1975
        }
1976
      }
1977
    }
1978

1979
    // create ParameterEvent
1980
    const parameterEvent = new (loader.loadInterface(
2,221✔
1981
      PARAMETER_EVENT_MSG_TYPE
1982
    ))();
1983

1984
    const { seconds, nanoseconds } = this._clock.now().secondsAndNanoseconds;
2,221✔
1985
    parameterEvent.stamp = {
2,221✔
1986
      sec: Number(seconds),
1987
      nanosec: Number(nanoseconds),
1988
    };
1989

1990
    parameterEvent.node =
2,221✔
1991
      this.namespace() === '/'
2,221✔
1992
        ? this.namespace() + this.name()
1993
        : this.namespace() + '/' + this.name();
1994

1995
    if (newParameters.length > 0) {
2,221✔
1996
      parameterEvent['new_parameters'] = newParameters.map((parameter) =>
2,197✔
1997
        parameter.toParameterMessage()
2,197✔
1998
      );
1999
    }
2000
    if (changedParameters.length > 0) {
2,221✔
2001
      parameterEvent['changed_parameters'] = changedParameters.map(
23✔
2002
        (parameter) => parameter.toParameterMessage()
23✔
2003
      );
2004
    }
2005
    if (deletedParameters.length > 0) {
2,221✔
2006
      parameterEvent['deleted_parameters'] = deletedParameters.map(
1✔
2007
        (parameter) => parameter.toParameterMessage()
1✔
2008
      );
2009
    }
2010

2011
    // Publish ParameterEvent.
2012
    this._parameterEventPublisher.publish(parameterEvent);
2,221✔
2013

2014
    return {
2,221✔
2015
      successful: true,
2016
      reason: '',
2017
    };
2018
  }
2019

2020
  /**
2021
   * This callback is called when declaring a parameter or setting a parameter.
2022
   * The callback is provided a list of parameters and returns a SetParameterResult
2023
   * to indicate approval or veto of the operation.
2024
   *
2025
   * @callback SetParametersCallback
2026
   * @param {Parameter[]} parameters - The message published
2027
   * @returns {rcl_interfaces.msg.SetParameterResult} -
2028
   *
2029
   * @see [Node.addOnSetParametersCallback]{@link Node#addOnSetParametersCallback}
2030
   * @see [Node.removeOnSetParametersCallback]{@link Node#removeOnSetParametersCallback}
2031
   */
2032

2033
  /**
2034
   * Add a callback to the front of the list of callbacks invoked for parameter declaration
2035
   * and setting. No checks are made for duplicate callbacks.
2036
   *
2037
   * @param {SetParametersCallback} callback - The callback to add.
2038
   * @returns {undefined}
2039
   */
2040
  addOnSetParametersCallback(callback) {
2041
    this._setParametersCallbacks.unshift(callback);
825✔
2042
  }
2043

2044
  /**
2045
   * Remove a callback from the list of SetParametersCallbacks.
2046
   * If the callback is not found the process is a nop.
2047
   *
2048
   * @param {SetParametersCallback} callback - The callback to be removed
2049
   * @returns {undefined}
2050
   */
2051
  removeOnSetParametersCallback(callback) {
2052
    const idx = this._setParametersCallbacks.indexOf(callback);
2✔
2053
    if (idx > -1) {
2!
2054
      this._setParametersCallbacks.splice(idx, 1);
2✔
2055
    }
2056
  }
2057

2058
  /**
2059
   * Get the fully qualified name of the node.
2060
   *
2061
   * @returns {string} - String containing the fully qualified name of the node.
2062
   */
2063
  getFullyQualifiedName() {
2064
    return rclnodejs.getFullyQualifiedName(this.handle);
1✔
2065
  }
2066

2067
  /**
2068
   * Get the RMW implementation identifier
2069
   * @returns {string} - The RMW implementation identifier.
2070
   */
2071
  getRMWImplementationIdentifier() {
2072
    return rclnodejs.getRMWImplementationIdentifier();
1✔
2073
  }
2074

2075
  /**
2076
   * Return a topic name expanded and remapped.
2077
   * @param {string} topicName - Topic name to be expanded and remapped.
2078
   * @param {boolean} [onlyExpand=false] - If `true`, remapping rules won't be applied.
2079
   * @returns {string} - A fully qualified topic name.
2080
   */
2081
  resolveTopicName(topicName, onlyExpand = false) {
2✔
2082
    if (typeof topicName !== 'string') {
3!
2083
      throw new TypeValidationError('topicName', topicName, 'string', {
×
2084
        nodeName: this.name(),
2085
      });
2086
    }
2087
    return rclnodejs.resolveName(
3✔
2088
      this.handle,
2089
      topicName,
2090
      onlyExpand,
2091
      /*isService=*/ false
2092
    );
2093
  }
2094

2095
  /**
2096
   * Return a service name expanded and remapped.
2097
   * @param {string} service - Service name to be expanded and remapped.
2098
   * @param {boolean} [onlyExpand=false] - If `true`, remapping rules won't be applied.
2099
   * @returns {string} - A fully qualified service name.
2100
   */
2101
  resolveServiceName(service, onlyExpand = false) {
6✔
2102
    if (typeof service !== 'string') {
7!
2103
      throw new TypeValidationError('service', service, 'string', {
×
2104
        nodeName: this.name(),
2105
      });
2106
    }
2107
    return rclnodejs.resolveName(
7✔
2108
      this.handle,
2109
      service,
2110
      onlyExpand,
2111
      /*isService=*/ true
2112
    );
2113
  }
2114

2115
  // returns on 1st error or result {successful, reason}
2116
  _validateParameters(parameters = [], declareParameterMode = false) {
×
2117
    for (const parameter of parameters) {
2,222✔
2118
      // detect invalid parameter
2119
      try {
2,222✔
2120
        parameter.validate();
2,222✔
2121
      } catch {
2122
        return {
×
2123
          successful: false,
2124
          reason: `Invalid ${parameter.name}`,
2125
        };
2126
      }
2127

2128
      // detect undeclared parameter
2129
      if (!this.hasParameterDescriptor(parameter.name)) {
2,222!
2130
        return {
×
2131
          successful: false,
2132
          reason: `Parameter ${parameter.name} has not been declared`,
2133
        };
2134
      }
2135

2136
      // detect readonly parameter that can not be updated
2137
      const descriptor = this.getParameterDescriptor(parameter.name);
2,222✔
2138
      if (!declareParameterMode && descriptor.readOnly) {
2,222!
2139
        return {
×
2140
          successful: false,
2141
          reason: `Parameter ${parameter.name} is readonly`,
2142
        };
2143
      }
2144

2145
      // validate parameter against descriptor if not an undeclare action
2146
      if (parameter.type != ParameterType.PARAMETER_NOT_SET) {
2,222✔
2147
        try {
2,221✔
2148
          descriptor.validateParameter(parameter);
2,221✔
2149
        } catch {
2150
          return {
×
2151
            successful: false,
2152
            reason: `Parameter ${parameter.name} does not  readonly`,
2153
          };
2154
        }
2155
      }
2156
    }
2157

2158
    return {
2,222✔
2159
      successful: true,
2160
      reason: null,
2161
    };
2162
  }
2163

2164
  // Get a Map(nodeName->Parameter[]) of CLI parameter args that
2165
  // apply to 'this' node, .e.g., -p mynode:foo:=bar -p hello:=world
2166
  _getNativeParameterOverrides() {
2167
    const overrides = new Map();
808✔
2168

2169
    // Get native parameters from rcl context->global_arguments.
2170
    // rclnodejs returns an array of objects, 1 for each node e.g., -p my_node:foo:=bar,
2171
    // and a node named '/**' for global parameter rules,
2172
    // i.e., does not include a node identifier, e.g., -p color:=red
2173
    // {
2174
    //   name: string // node name
2175
    //   parameters[] = {
2176
    //     name: string
2177
    //     type: uint
2178
    //     value: object
2179
    // }
2180
    const cliParamOverrideData = rclnodejs.getParameterOverrides(
808✔
2181
      this.context.handle
2182
    );
2183

2184
    // convert native CLI parameterOverrides to Map<nodeName,Array<ParameterOverride>>
2185
    const cliParamOverrides = new Map();
808✔
2186
    if (cliParamOverrideData) {
808✔
2187
      for (let nodeParamData of cliParamOverrideData) {
8✔
2188
        const nodeName = nodeParamData.name;
12✔
2189
        const nodeParamOverrides = [];
12✔
2190
        for (let paramData of nodeParamData.parameters) {
12✔
2191
          const paramOverride = new Parameter(
17✔
2192
            paramData.name,
2193
            paramData.type,
2194
            paramData.value
2195
          );
2196
          nodeParamOverrides.push(paramOverride);
17✔
2197
        }
2198
        cliParamOverrides.set(nodeName, nodeParamOverrides);
12✔
2199
      }
2200
    }
2201

2202
    // collect global CLI global parameters, name == /**
2203
    let paramOverrides = cliParamOverrides.get('/**'); // array of ParameterOverrides
808✔
2204
    if (paramOverrides) {
808✔
2205
      for (const parameter of paramOverrides) {
5✔
2206
        overrides.set(parameter.name, parameter);
6✔
2207
      }
2208
    }
2209

2210
    // merge CLI node parameterOverrides with global parameterOverrides, replace existing
2211
    paramOverrides = cliParamOverrides.get(this.name()); // array of ParameterOverrides
808✔
2212
    if (paramOverrides) {
808✔
2213
      for (const parameter of paramOverrides) {
5✔
2214
        overrides.set(parameter.name, parameter);
7✔
2215
      }
2216
    }
2217

2218
    return overrides;
808✔
2219
  }
2220

2221
  /**
2222
   * Invokes the callback with a raw message of the given type. After the callback completes
2223
   * the message will be destroyed.
2224
   * @param {function} Type - Message type to create.
2225
   * @param {function} callback - Callback to invoke. First parameter will be the raw message,
2226
   * and the second is a function to retrieve the deserialized message.
2227
   * @returns {undefined}
2228
   */
2229
  _runWithMessageType(Type, callback) {
2230
    let message = new Type();
996✔
2231

2232
    callback(message.toRawROS(), () => {
996✔
2233
      let result = new Type();
685✔
2234
      result.deserialize(message.refObject);
685✔
2235

2236
      return result;
685✔
2237
    });
2238

2239
    Type.destroyRawROS(message);
996✔
2240
  }
2241

2242
  _addActionClient(actionClient) {
2243
    this._actionClients.push(actionClient);
41✔
2244
    this.syncHandles();
41✔
2245
  }
2246

2247
  _addActionServer(actionServer) {
2248
    this._actionServers.push(actionServer);
41✔
2249
    this.syncHandles();
41✔
2250
  }
2251

2252
  _getValidatedTopic(topicName, noDemangle) {
2253
    if (noDemangle) {
6!
2254
      return topicName;
×
2255
    }
2256
    const fqTopicName = rclnodejs.expandTopicName(
6✔
2257
      topicName,
2258
      this.name(),
2259
      this.namespace()
2260
    );
2261
    validateFullTopicName(fqTopicName);
6✔
2262
    return rclnodejs.remapTopicName(this.handle, fqTopicName);
6✔
2263
  }
2264

2265
  _getValidatedServiceName(serviceName, noDemangle) {
2266
    if (typeof serviceName !== 'string') {
4!
2267
      throw new TypeValidationError('serviceName', serviceName, 'string', {
×
2268
        nodeName: this.name(),
2269
      });
2270
    }
2271

2272
    if (noDemangle) {
4!
2273
      return serviceName;
×
2274
    }
2275

2276
    const resolvedServiceName = this.resolveServiceName(serviceName);
4✔
2277
    rclnodejs.validateTopicName(resolvedServiceName);
4✔
2278
    return resolvedServiceName;
4✔
2279
  }
2280
}
2281

2282
/**
2283
 * Create an Options instance initialized with default values.
2284
 * @returns {Options} - The new initialized instance.
2285
 * @static
2286
 * @example
2287
 * {
2288
 *   enableTypedArray: true,
2289
 *   isRaw: false,
2290
 *   qos: QoS.profileDefault,
2291
 *   contentFilter: undefined,
2292
 *   serializationMode: 'default',
2293
 * }
2294
 */
2295
Node.getDefaultOptions = function () {
26✔
2296
  return {
7,496✔
2297
    enableTypedArray: true,
2298
    isRaw: false,
2299
    qos: QoS.profileDefault,
2300
    contentFilter: undefined,
2301
    serializationMode: 'default',
2302
  };
2303
};
2304

2305
module.exports = Node;
26✔
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc