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

RobotWebTools / rclnodejs / 23931694299

03 Apr 2026 02:55AM UTC coverage: 85.288% (-0.02%) from 85.304%
23931694299

Pull #1472

github

web-flow
Merge 6b6bf4bdd into 5b88d7830
Pull Request #1472: Add timer autostart and TimerInfo callback support

1546 of 1972 branches covered (78.4%)

Branch coverage included in aggregate %.

12 of 16 new or added lines in 1 file covered. (75.0%)

49 existing lines in 1 file now uncovered.

3144 of 3527 relevant lines covered (89.14%)

448.43 hits per line

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

86.8
/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 ParameterEventHandler = require('./parameter_event_handler.js');
26✔
44
const Publisher = require('./publisher.js');
26✔
45
const QoS = require('./qos.js');
26✔
46
const Rates = require('./rate.js');
26✔
47
const Service = require('./service.js');
26✔
48
const Subscription = require('./subscription.js');
26✔
49
const ObservableSubscription = require('./observable_subscription.js');
26✔
50
const MessageInfo = require('./message_info.js');
26✔
51
const {
52
  declareQosParameters,
53
  _resolveQoS,
54
} = require('./qos_overriding_options.js');
26✔
55
const TimeSource = require('./time_source.js');
26✔
56
const Timer = require('./timer.js');
26✔
57
const TypeDescriptionService = require('./type_description_service.js');
26✔
58
const Entity = require('./entity.js');
26✔
59
const { SubscriptionEventCallbacks } = require('../lib/event_handler.js');
26✔
60
const { PublisherEventCallbacks } = require('../lib/event_handler.js');
26✔
61
const { validateFullTopicName } = require('./validator.js');
26✔
62

63
// Parameter event publisher constants
64
const PARAMETER_EVENT_MSG_TYPE = 'rcl_interfaces/msg/ParameterEvent';
26✔
65
const PARAMETER_EVENT_TOPIC = 'parameter_events';
26✔
66

67
/**
68
 * @class - Class representing a Node in ROS
69
 */
70

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

91
    if (typeof nodeName !== 'string') {
939✔
92
      throw new TypeValidationError('nodeName', nodeName, 'string');
12✔
93
    }
94
    if (typeof namespace !== 'string') {
927✔
95
      throw new TypeValidationError('namespace', namespace, 'string');
10✔
96
    }
97

98
    this._init(nodeName, namespace, options, context, args, useGlobalArguments);
917✔
99
    debug(
906✔
100
      'Finish initializing node, name = %s and namespace = %s.',
101
      nodeName,
102
      namespace
103
    );
104
  }
105

106
  static _normalizeOptions(options) {
107
    if (options instanceof NodeOptions) {
917✔
108
      return options;
914✔
109
    }
110
    const defaults = NodeOptions.defaultOptions;
3✔
111
    return {
3✔
112
      startParameterServices:
113
        options.startParameterServices ?? defaults.startParameterServices,
5✔
114
      parameterOverrides:
115
        options.parameterOverrides ?? defaults.parameterOverrides,
5✔
116
      automaticallyDeclareParametersFromOverrides:
117
        options.automaticallyDeclareParametersFromOverrides ??
6✔
118
        defaults.automaticallyDeclareParametersFromOverrides,
119
      startTypeDescriptionService:
120
        options.startTypeDescriptionService ??
6✔
121
        defaults.startTypeDescriptionService,
122
      enableRosout: options.enableRosout ?? defaults.enableRosout,
5✔
123
      rosoutQos: options.rosoutQos ?? defaults.rosoutQos,
6✔
124
    };
125
  }
126

127
  _init(name, namespace, options, context, args, useGlobalArguments) {
128
    options = Node._normalizeOptions(options);
917✔
129

130
    this.handle = rclnodejs.createNode(
917✔
131
      name,
132
      namespace,
133
      context.handle,
134
      args,
135
      useGlobalArguments,
136
      options.rosoutQos
137
    );
138
    Object.defineProperty(this, 'handle', {
907✔
139
      configurable: false,
140
      writable: false,
141
    }); // make read-only
142

143
    this._context = context;
907✔
144
    this.context.onNodeCreated(this);
907✔
145

146
    this._publishers = [];
907✔
147
    this._subscriptions = [];
907✔
148
    this._clients = [];
907✔
149
    this._services = [];
907✔
150
    this._timers = [];
907✔
151
    this._guards = [];
907✔
152
    this._events = [];
907✔
153
    this._actionClients = [];
907✔
154
    this._actionServers = [];
907✔
155
    this._parameterClients = [];
907✔
156
    this._parameterWatchers = [];
907✔
157
    this._parameterEventHandlers = [];
907✔
158
    this._rateTimerServer = null;
907✔
159
    this._parameterDescriptors = new Map();
907✔
160
    this._parameters = new Map();
907✔
161
    this._parameterService = null;
907✔
162
    this._typeDescriptionService = null;
907✔
163
    this._parameterEventPublisher = null;
907✔
164
    this._preSetParametersCallbacks = [];
907✔
165
    this._setParametersCallbacks = [];
907✔
166
    this._postSetParametersCallbacks = [];
907✔
167
    this._logger = new Logging(rclnodejs.getNodeLoggerName(this.handle));
907✔
168
    this._spinning = false;
907✔
169
    this._enableRosout = options.enableRosout;
907✔
170

171
    if (this._enableRosout) {
907✔
172
      rclnodejs.initRosoutPublisherForNode(this.handle);
905✔
173
    }
174

175
    this._parameterEventPublisher = this.createPublisher(
907✔
176
      PARAMETER_EVENT_MSG_TYPE,
177
      PARAMETER_EVENT_TOPIC
178
    );
179

180
    // initialize _parameterOverrides from parameters defined on the commandline
181
    this._parameterOverrides = this._getNativeParameterOverrides();
907✔
182

183
    // override cli parameterOverrides with those specified in options
184
    if (options.parameterOverrides.length > 0) {
907✔
185
      for (const parameter of options.parameterOverrides) {
12✔
186
        if (!(parameter instanceof Parameter)) {
17✔
187
          throw new TypeValidationError(
1✔
188
            'parameterOverride',
189
            parameter,
190
            'Parameter instance',
191
            {
192
              nodeName: name,
193
            }
194
          );
195
        }
196
        this._parameterOverrides.set(parameter.name, parameter);
16✔
197
      }
198
    }
199

200
    // initialize _parameters from parameterOverrides
201
    if (options.automaticallyDeclareParametersFromOverrides) {
906✔
202
      for (const parameter of this._parameterOverrides.values()) {
5✔
203
        parameter.validate();
10✔
204
        const descriptor = ParameterDescriptor.fromParameter(parameter);
10✔
205
        this._parameters.set(parameter.name, parameter);
10✔
206
        this._parameterDescriptors.set(parameter.name, descriptor);
10✔
207
      }
208
    }
209

210
    // Clock that has support for ROS time.
211
    // Note: parameter overrides and parameter event publisher need to be ready at this point
212
    // to be able to declare 'use_sim_time' if it was not declared yet.
213
    this._clock = new Clock.ROSClock();
906✔
214
    this._timeSource = new TimeSource(this);
906✔
215
    this._timeSource.attachClock(this._clock);
906✔
216

217
    if (options.startParameterServices) {
906✔
218
      this._parameterService = new ParameterService(this);
899✔
219
      this._parameterService.start();
899✔
220
    }
221

222
    if (
906!
223
      DistroUtils.getDistroId() >= DistroUtils.getDistroId('jazzy') &&
1,812✔
224
      options.startTypeDescriptionService
225
    ) {
226
      this._typeDescriptionService = new TypeDescriptionService(this);
906✔
227
      this._typeDescriptionService.start();
906✔
228
    }
229
  }
230

231
  execute(handles) {
232
    let timersReady = this._timers.filter((timer) =>
4,942✔
233
      handles.includes(timer.handle)
4,148✔
234
    );
235
    let guardsReady = this._guards.filter((guard) =>
4,942✔
236
      handles.includes(guard.handle)
3✔
237
    );
238
    let subscriptionsReady = this._subscriptions.filter((subscription) =>
4,942✔
239
      handles.includes(subscription.handle)
420✔
240
    );
241
    let clientsReady = this._clients.filter((client) =>
4,942✔
242
      handles.includes(client.handle)
171✔
243
    );
244
    let servicesReady = this._services.filter((service) =>
4,942✔
245
      handles.includes(service.handle)
16,695✔
246
    );
247
    let actionClientsReady = this._actionClients.filter((actionClient) =>
4,942✔
248
      handles.includes(actionClient.handle)
205✔
249
    );
250
    let actionServersReady = this._actionServers.filter((actionServer) =>
4,942✔
251
      handles.includes(actionServer.handle)
203✔
252
    );
253
    let eventsReady = this._events.filter((event) =>
4,942✔
254
      handles.includes(event.handle)
4✔
255
    );
256

257
    timersReady.forEach((timer) => {
4,942✔
258
      if (timer.isReady()) {
4,139!
259
        let timerInfo;
260
        if (typeof rclnodejs.callTimerWithInfo === 'function') {
4,139!
261
          timerInfo = rclnodejs.callTimerWithInfo(timer.handle);
4,139✔
262
        } else {
NEW
263
          rclnodejs.callTimer(timer.handle);
×
264
        }
265
        timer.callback(timerInfo);
4,139✔
266
      }
267
    });
268

269
    eventsReady.forEach((event) => {
4,942✔
270
      event.takeData();
4✔
271
    });
272

273
    for (const subscription of subscriptionsReady) {
4,942✔
274
      if (subscription.isDestroyed()) continue;
401✔
275
      if (subscription.isRaw) {
392✔
276
        let rawMessage = rclnodejs.rclTakeRaw(subscription.handle);
1✔
277
        if (rawMessage) {
1!
278
          subscription.processResponse(rawMessage);
1✔
279
        }
280
        continue;
1✔
281
      }
282

283
      this._runWithMessageType(
391✔
284
        subscription.typeClass,
285
        (message, deserialize) => {
286
          if (subscription.wantsMessageInfo) {
391✔
287
            let rawInfo = rclnodejs.rclTakeWithInfo(
4✔
288
              subscription.handle,
289
              message
290
            );
291
            if (rawInfo) {
4!
292
              subscription.processResponse(
4✔
293
                deserialize(),
294
                new MessageInfo(rawInfo)
295
              );
296
            }
297
          } else {
298
            let success = rclnodejs.rclTake(subscription.handle, message);
387✔
299
            if (success) {
387✔
300
              subscription.processResponse(deserialize());
384✔
301
            }
302
          }
303
        }
304
      );
305
    }
306

307
    for (const guard of guardsReady) {
4,942✔
308
      if (guard.isDestroyed()) continue;
3!
309

310
      guard.callback();
3✔
311
    }
312

313
    for (const client of clientsReady) {
4,942✔
314
      if (client.isDestroyed()) continue;
87!
315
      this._runWithMessageType(
87✔
316
        client.typeClass.Response,
317
        (message, deserialize) => {
318
          let sequenceNumber = rclnodejs.rclTakeResponse(
87✔
319
            client.handle,
320
            message
321
          );
322
          if (sequenceNumber !== undefined) {
87✔
323
            client.processResponse(sequenceNumber, deserialize());
86✔
324
          }
325
        }
326
      );
327
    }
328

329
    for (const service of servicesReady) {
4,942✔
330
      if (service.isDestroyed()) continue;
124!
331
      this._runWithMessageType(
124✔
332
        service.typeClass.Request,
333
        (message, deserialize) => {
334
          let header = rclnodejs.rclTakeRequest(
124✔
335
            service.handle,
336
            this.handle,
337
            message
338
          );
339
          if (header) {
124✔
340
            service.processRequest(header, deserialize());
91✔
341
          }
342
        }
343
      );
344
    }
345

346
    for (const actionClient of actionClientsReady) {
4,942✔
347
      if (actionClient.isDestroyed()) continue;
126!
348

349
      const properties = actionClient.handle.properties;
126✔
350

351
      if (properties.isGoalResponseReady) {
126✔
352
        this._runWithMessageType(
56✔
353
          actionClient.typeClass.impl.SendGoalService.Response,
354
          (message, deserialize) => {
355
            let sequence = rclnodejs.actionTakeGoalResponse(
56✔
356
              actionClient.handle,
357
              message
358
            );
359
            if (sequence != undefined) {
56✔
360
              actionClient.processGoalResponse(sequence, deserialize());
49✔
361
            }
362
          }
363
        );
364
      }
365

366
      if (properties.isCancelResponseReady) {
126✔
367
        this._runWithMessageType(
5✔
368
          actionClient.typeClass.impl.CancelGoal.Response,
369
          (message, deserialize) => {
370
            let sequence = rclnodejs.actionTakeCancelResponse(
5✔
371
              actionClient.handle,
372
              message
373
            );
374
            if (sequence != undefined) {
5!
375
              actionClient.processCancelResponse(sequence, deserialize());
5✔
376
            }
377
          }
378
        );
379
      }
380

381
      if (properties.isResultResponseReady) {
126✔
382
        this._runWithMessageType(
32✔
383
          actionClient.typeClass.impl.GetResultService.Response,
384
          (message, deserialize) => {
385
            let sequence = rclnodejs.actionTakeResultResponse(
32✔
386
              actionClient.handle,
387
              message
388
            );
389
            if (sequence != undefined) {
32!
390
              actionClient.processResultResponse(sequence, deserialize());
32✔
391
            }
392
          }
393
        );
394
      }
395

396
      if (properties.isFeedbackReady) {
126✔
397
        this._runWithMessageType(
9✔
398
          actionClient.typeClass.impl.FeedbackMessage,
399
          (message, deserialize) => {
400
            let success = rclnodejs.actionTakeFeedback(
9✔
401
              actionClient.handle,
402
              message
403
            );
404
            if (success) {
9!
405
              actionClient.processFeedbackMessage(deserialize());
9✔
406
            }
407
          }
408
        );
409
      }
410

411
      if (properties.isStatusReady) {
126✔
412
        this._runWithMessageType(
72✔
413
          actionClient.typeClass.impl.GoalStatusArray,
414
          (message, deserialize) => {
415
            let success = rclnodejs.actionTakeStatus(
72✔
416
              actionClient.handle,
417
              message
418
            );
419
            if (success) {
72!
420
              actionClient.processStatusMessage(deserialize());
72✔
421
            }
422
          }
423
        );
424
      }
425
    }
426

427
    for (const actionServer of actionServersReady) {
4,942✔
428
      if (actionServer.isDestroyed()) continue;
95!
429

430
      const properties = actionServer.handle.properties;
95✔
431

432
      if (properties.isGoalRequestReady) {
95✔
433
        this._runWithMessageType(
54✔
434
          actionServer.typeClass.impl.SendGoalService.Request,
435
          (message, deserialize) => {
436
            const result = rclnodejs.actionTakeGoalRequest(
54✔
437
              actionServer.handle,
438
              message
439
            );
440
            if (result) {
54✔
441
              actionServer.processGoalRequest(result, deserialize());
49✔
442
            }
443
          }
444
        );
445
      }
446

447
      if (properties.isCancelRequestReady) {
95✔
448
        this._runWithMessageType(
5✔
449
          actionServer.typeClass.impl.CancelGoal.Request,
450
          (message, deserialize) => {
451
            const result = rclnodejs.actionTakeCancelRequest(
5✔
452
              actionServer.handle,
453
              message
454
            );
455
            if (result) {
5!
456
              actionServer.processCancelRequest(result, deserialize());
5✔
457
            }
458
          }
459
        );
460
      }
461

462
      if (properties.isResultRequestReady) {
95✔
463
        this._runWithMessageType(
32✔
464
          actionServer.typeClass.impl.GetResultService.Request,
465
          (message, deserialize) => {
466
            const result = rclnodejs.actionTakeResultRequest(
32✔
467
              actionServer.handle,
468
              message
469
            );
470
            if (result) {
32!
471
              actionServer.processResultRequest(result, deserialize());
32✔
472
            }
473
          }
474
        );
475
      }
476

477
      if (properties.isGoalExpired) {
95✔
478
        let numGoals = actionServer._goalHandles.size;
4✔
479
        if (numGoals > 0) {
4!
480
          let GoalInfoArray = ActionInterfaces.GoalInfo.ArrayType;
4✔
481
          let message = new GoalInfoArray(numGoals);
4✔
482
          let count = rclnodejs.actionExpireGoals(
4✔
483
            actionServer.handle,
484
            numGoals,
485
            message._refArray.buffer
486
          );
487
          if (count > 0) {
4!
488
            actionServer.processGoalExpired(message, count);
4✔
489
          }
490
          GoalInfoArray.freeArray(message);
4✔
491
        }
492
      }
493
    }
494

495
    // At this point it is safe to clear the cache of any
496
    // destroyed entity references
497
    Entity._gcHandles();
4,942✔
498
  }
499

500
  /**
501
   * Determine if this node is spinning.
502
   * @returns {boolean} - true when spinning; otherwise returns false.
503
   */
504
  get spinning() {
505
    return this._spinning;
4,635✔
506
  }
507

508
  /**
509
   * Trigger the event loop to continuously check for and route.
510
   * incoming events.
511
   * @param {Node} node - The node to be spun up.
512
   * @param {number} [timeout=10] - Timeout to wait in milliseconds. Block forever if negative. Don't wait if 0.
513
   * @throws {Error} If the node is already spinning.
514
   * @return {undefined}
515
   */
516
  spin(timeout = 10) {
26✔
517
    if (this.spinning) {
694!
UNCOV
518
      throw new Error('The node is already spinning.');
×
519
    }
520
    this.start(this.context.handle, timeout);
694✔
521
    this._spinning = true;
694✔
522
  }
523

524
  /**
525
   * Use spin().
526
   * @deprecated, since 0.18.0
527
   */
528
  startSpinning(timeout) {
UNCOV
529
    this.spin(timeout);
×
530
  }
531

532
  /**
533
   * Terminate spinning - no further events will be received.
534
   * @returns {undefined}
535
   */
536
  stop() {
537
    super.stop();
694✔
538
    this._spinning = false;
694✔
539
  }
540

541
  /**
542
   * Terminate spinning - no further events will be received.
543
   * @returns {undefined}
544
   * @deprecated since 0.18.0, Use stop().
545
   */
546
  stopSpinning() {
UNCOV
547
    super.stop();
×
548
    this._spinning = false;
×
549
  }
550

551
  /**
552
   * Spin the node and trigger the event loop to check for one incoming event. Thereafter the node
553
   * will not received additional events until running additional calls to spin() or spinOnce().
554
   * @param {Node} node - The node to be spun.
555
   * @param {number} [timeout=10] - Timeout to wait in milliseconds. Block forever if negative. Don't wait if 0.
556
   * @throws {Error} If the node is already spinning.
557
   * @return {undefined}
558
   */
559
  spinOnce(timeout = 10) {
2✔
560
    if (this.spinning) {
3,009✔
561
      throw new Error('The node is already spinning.');
2✔
562
    }
563
    super.spinOnce(this.context.handle, timeout);
3,007✔
564
  }
565

566
  _removeEntityFromArray(entity, array) {
567
    let index = array.indexOf(entity);
398✔
568
    if (index > -1) {
398✔
569
      array.splice(index, 1);
396✔
570
    }
571
  }
572

573
  _destroyEntity(entity, array, syncHandles = true) {
338✔
574
    if (entity['isDestroyed'] && entity.isDestroyed()) return;
350✔
575

576
    this._removeEntityFromArray(entity, array);
342✔
577
    if (syncHandles) {
342✔
578
      this.syncHandles();
332✔
579
    }
580

581
    if (entity['_destroy']) {
342✔
582
      entity._destroy();
336✔
583
    } else {
584
      // guards and timers
585
      entity.handle.release();
6✔
586
    }
587
  }
588

589
  _validateOptions(options) {
590
    if (
8,373✔
591
      options !== undefined &&
8,477✔
592
      (options === null || typeof options !== 'object')
593
    ) {
594
      throw new TypeValidationError('options', options, 'object', {
20✔
595
        nodeName: this.name(),
596
      });
597
    }
598

599
    if (options === undefined) {
8,353✔
600
      return Node.getDefaultOptions();
8,311✔
601
    }
602

603
    if (options.enableTypedArray === undefined) {
42✔
604
      options = Object.assign(options, { enableTypedArray: true });
28✔
605
    }
606

607
    if (options.qos === undefined) {
42✔
608
      options = Object.assign(options, { qos: QoS.profileDefault });
20✔
609
    }
610

611
    if (options.isRaw === undefined) {
42✔
612
      options = Object.assign(options, { isRaw: false });
31✔
613
    }
614

615
    if (options.serializationMode === undefined) {
42✔
616
      options = Object.assign(options, { serializationMode: 'default' });
25✔
617
    } else if (!isValidSerializationMode(options.serializationMode)) {
17✔
618
      throw new ValidationError(
1✔
619
        `Invalid serializationMode: ${options.serializationMode}. Valid modes are: 'default', 'plain', 'json'`,
620
        {
621
          code: 'INVALID_SERIALIZATION_MODE',
622
          argumentName: 'serializationMode',
623
          providedValue: options.serializationMode,
624
          expectedType: "'default' | 'plain' | 'json'",
625
          nodeName: this.name(),
626
        }
627
      );
628
    }
629

630
    return options;
41✔
631
  }
632

633
  /**
634
   * Create a Timer.
635
   * @param {bigint} period - The number representing period in nanoseconds.
636
   * @param {function} callback - The callback to be called when the timer fires.
637
   *   On distros with native support, the callback receives a `TimerInfo` object
638
   *   describing the expected and actual call time.
639
   * @param {object|Clock} [optionsOrClock] - Timer options or the clock which the timer gets time from.
640
   *   Supported options: `{ autostart?: boolean }`.
641
   * @param {Clock} [clock] - The clock which the timer gets time from when options are provided.
642
   * @return {Timer} - An instance of Timer.
643
   */
644
  createTimer(period, callback, optionsOrClock = null, clock = null) {
140✔
645
    let options = {};
71✔
646

647
    if (optionsOrClock instanceof Clock.Clock) {
71!
NEW
648
      clock = optionsOrClock;
×
649
    } else if (optionsOrClock === null || optionsOrClock === undefined) {
71✔
650
      // Keep the 4th argument as the clock when the 3rd argument is omitted or explicitly null.
651
    } else {
652
      if (typeof optionsOrClock !== 'object' || Array.isArray(optionsOrClock)) {
2!
NEW
653
        throw new TypeValidationError(
×
654
          'options',
655
          optionsOrClock,
656
          'object or Clock',
657
          {
658
            nodeName: this.name(),
659
          }
660
        );
661
      }
662
      options = optionsOrClock;
2✔
663
    }
664

665
    if (
71!
666
      arguments.length === 4 &&
71!
667
      clock !== null &&
668
      !(clock instanceof Clock.Clock)
669
    ) {
NEW
670
      throw new TypeValidationError('clock', clock, 'Clock', {
×
671
        nodeName: this.name(),
672
      });
673
    }
674

675
    if (typeof period !== 'bigint') {
71✔
676
      throw new TypeValidationError('period', period, 'bigint', {
1✔
677
        nodeName: this.name(),
678
      });
679
    }
680
    if (typeof callback !== 'function') {
70✔
681
      throw new TypeValidationError('callback', callback, 'function', {
1✔
682
        nodeName: this.name(),
683
      });
684
    }
685
    if (
69✔
686
      options.autostart !== undefined &&
71✔
687
      typeof options.autostart !== 'boolean'
688
    ) {
689
      throw new TypeValidationError(
1✔
690
        'options.autostart',
691
        options.autostart,
692
        'boolean',
693
        {
694
          nodeName: this.name(),
695
        }
696
      );
697
    }
698

699
    const timerClock = clock || this._clock;
68✔
700
    const autostart = options.autostart ?? true;
68✔
701
    let timerHandle = rclnodejs.createTimer(
68✔
702
      timerClock.handle,
703
      this.context.handle,
704
      period,
705
      autostart
706
    );
707
    let timer = new Timer(timerHandle, period, callback);
68✔
708
    debug('Finish creating timer, period = %d.', period);
68✔
709
    this._timers.push(timer);
68✔
710
    this.syncHandles();
68✔
711

712
    return timer;
68✔
713
  }
714

715
  /**
716
   * Create a Rate.
717
   *
718
   * @param {number} hz - The frequency of the rate timer; default is 1 hz.
719
   * @returns {Promise<Rate>} - Promise resolving to new instance of Rate.
720
   */
721
  async createRate(hz = 1) {
4✔
722
    if (typeof hz !== 'number') {
9!
UNCOV
723
      throw new TypeValidationError('hz', hz, 'number', {
×
724
        nodeName: this.name(),
725
      });
726
    }
727

728
    const MAX_RATE_HZ_IN_MILLISECOND = 1000.0;
9✔
729
    if (hz <= 0.0 || hz > MAX_RATE_HZ_IN_MILLISECOND) {
9✔
730
      throw new RangeValidationError(
2✔
731
        'hz',
732
        hz,
733
        `0.0 < hz <= ${MAX_RATE_HZ_IN_MILLISECOND}`,
734
        {
735
          nodeName: this.name(),
736
        }
737
      );
738
    }
739

740
    // lazy initialize rateTimerServer
741
    if (!this._rateTimerServer) {
7✔
742
      this._rateTimerServer = new Rates.RateTimerServer(this);
5✔
743
      await this._rateTimerServer.init();
5✔
744
    }
745

746
    const period = Math.round(1000 / hz);
7✔
747
    const timer = this._rateTimerServer.createTimer(BigInt(period) * 1000000n);
7✔
748
    const rate = new Rates.Rate(hz, timer);
7✔
749

750
    return rate;
7✔
751
  }
752

753
  /**
754
   * Create a Publisher.
755
   * @param {function|string|object} typeClass - The ROS message class,
756
        OR a string representing the message class, e.g. 'std_msgs/msg/String',
757
        OR an object representing the message class, e.g. {package: 'std_msgs', type: 'msg', name: 'String'}
758
   * @param {string} topic - The name of the topic.
759
   * @param {object} options - The options argument used to parameterize the publisher.
760
   * @param {boolean} options.enableTypedArray - The topic will use TypedArray if necessary, default: true.
761
   * @param {QoS} options.qos - ROS Middleware "quality of service" settings for the publisher, default: QoS.profileDefault.
762
   * @param {QoSOverridingOptions} [options.qosOverridingOptions] - If provided, declares read-only ROS parameters
763
   *  for the specified QoS policies (e.g. `qos_overrides./topic.publisher.depth`). These can be overridden at
764
   *  startup via `--ros-args -p` or `--params-file`. If qos is a profile string, it will be resolved to a
765
   *  mutable QoS object before overrides are applied.
766
   * @param {PublisherEventCallbacks} eventCallbacks - The event callbacks for the publisher.
767
   * @return {Publisher} - An instance of Publisher.
768
   */
769
  createPublisher(typeClass, topic, options, eventCallbacks) {
770
    return this._createPublisher(
1,286✔
771
      typeClass,
772
      topic,
773
      options,
774
      Publisher,
775
      eventCallbacks
776
    );
777
  }
778

779
  _createPublisher(typeClass, topic, options, publisherClass, eventCallbacks) {
780
    if (typeof typeClass === 'string' || typeof typeClass === 'object') {
1,289✔
781
      typeClass = loader.loadInterface(typeClass);
1,265✔
782
    }
783
    options = this._validateOptions(options);
1,282✔
784

785
    if (typeof typeClass !== 'function') {
1,282✔
786
      throw new TypeValidationError('typeClass', typeClass, 'function', {
8✔
787
        nodeName: this.name(),
788
        entityType: 'publisher',
789
      });
790
    }
791
    if (typeof topic !== 'string') {
1,274✔
792
      throw new TypeValidationError('topic', topic, 'string', {
12✔
793
        nodeName: this.name(),
794
        entityType: 'publisher',
795
      });
796
    }
797
    if (
1,262!
798
      eventCallbacks &&
1,264✔
799
      !(eventCallbacks instanceof PublisherEventCallbacks)
800
    ) {
UNCOV
801
      throw new TypeValidationError(
×
802
        'eventCallbacks',
803
        eventCallbacks,
804
        'PublisherEventCallbacks',
805
        {
806
          nodeName: this.name(),
807
          entityType: 'publisher',
808
          entityName: topic,
809
        }
810
      );
811
    }
812

813
    // Apply QoS overriding options if provided
814
    if (options.qosOverridingOptions) {
1,262✔
815
      const resolvedTopic = this.resolveTopicName(topic);
5✔
816
      if (typeof options.qos === 'string' || !(options.qos instanceof QoS)) {
5✔
817
        options.qos = _resolveQoS(options.qos);
2✔
818
      }
819
      declareQosParameters(
5✔
820
        'publisher',
821
        this,
822
        resolvedTopic,
823
        options.qos,
824
        options.qosOverridingOptions
825
      );
826
    }
827

828
    let publisher = publisherClass.createPublisher(
1,261✔
829
      this,
830
      typeClass,
831
      topic,
832
      options,
833
      eventCallbacks
834
    );
835
    debug('Finish creating publisher, topic = %s.', topic);
1,251✔
836
    this._publishers.push(publisher);
1,251✔
837
    return publisher;
1,251✔
838
  }
839

840
  /**
841
   * This callback is called when a message is published
842
   * @callback SubscriptionCallback
843
   * @param {Object} message - The message published
844
   * @see [Node.createSubscription]{@link Node#createSubscription}
845
   * @see [Node.createPublisher]{@link Node#createPublisher}
846
   * @see {@link Publisher}
847
   * @see {@link Subscription}
848
   */
849

850
  /**
851
   * Create a Subscription with optional content-filtering.
852
   * @param {function|string|object} typeClass - The ROS message class,
853
        OR a string representing the message class, e.g. 'std_msgs/msg/String',
854
        OR an object representing the message class, e.g. {package: 'std_msgs', type: 'msg', name: 'String'}
855
   * @param {string} topic - The name of the topic.
856
   * @param {object} options - The options argument used to parameterize the subscription.
857
   * @param {boolean} options.enableTypedArray - The topic will use TypedArray if necessary, default: true.
858
   * @param {QoS} options.qos - ROS Middleware "quality of service" settings for the subscription, default: QoS.profileDefault.
859
   * @param {boolean} options.isRaw - The topic is serialized when true, default: false.
860
   * @param {string} [options.serializationMode='default'] - Controls message serialization format:
861
   *  'default': Use native rclnodejs behavior (respects enableTypedArray setting),
862
   *  'plain': Convert TypedArrays to regular arrays,
863
   *  'json': Fully JSON-safe (handles TypedArrays, BigInt, etc.).
864
   * @param {object} [options.contentFilter=undefined] - The content-filter, default: undefined.
865
   *  Confirm that your RMW supports content-filtered topics before use. 
866
   * @param {string} options.contentFilter.expression - Specifies the criteria to select the data samples of
867
   *  interest. It is similar to the WHERE part of an SQL clause.
868
   * @param {string[]} [options.contentFilter.parameters=undefined] - Array of strings that give values to
869
   *  the ‘parameters’ (i.e., "%n" tokens) in the filter_expression. The number of supplied parameters must
870
   *  fit with the requested values in the filter_expression (i.e., the number of %n tokens). default: undefined.
871
   * @param {QoSOverridingOptions} [options.qosOverridingOptions] - If provided, declares read-only ROS parameters
872
   *  for the specified QoS policies (e.g. `qos_overrides./topic.subscription.depth`). These can be overridden at
873
   *  startup via `--ros-args -p` or `--params-file`. If qos is a profile string, it will be resolved to a
874
   *  mutable QoS object before overrides are applied.
875
   * @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.
876
   * @param {SubscriptionEventCallbacks} eventCallbacks - The event callbacks for the subscription.
877
   * @return {Subscription} - An instance of Subscription.
878
   * @throws {ERROR} - May throw an RMW error if content-filter is malformed. 
879
   * @see {@link SubscriptionCallback}
880
   * @see {@link https://www.omg.org/spec/DDS/1.4/PDF|Content-filter details at DDS 1.4 specification, Annex B}
881
   */
882
  createSubscription(typeClass, topic, options, callback, eventCallbacks) {
883
    if (typeof typeClass === 'string' || typeof typeClass === 'object') {
467✔
884
      typeClass = loader.loadInterface(typeClass);
458✔
885
    }
886

887
    if (typeof options === 'function') {
460✔
888
      callback = options;
343✔
889
      options = undefined;
343✔
890
    }
891
    options = this._validateOptions(options);
460✔
892

893
    if (typeof typeClass !== 'function') {
450✔
894
      throw new TypeValidationError('typeClass', typeClass, 'function', {
4✔
895
        nodeName: this.name(),
896
        entityType: 'subscription',
897
      });
898
    }
899
    if (typeof topic !== 'string') {
446✔
900
      throw new TypeValidationError('topic', topic, 'string', {
6✔
901
        nodeName: this.name(),
902
        entityType: 'subscription',
903
      });
904
    }
905
    if (typeof callback !== 'function') {
440!
UNCOV
906
      throw new TypeValidationError('callback', callback, 'function', {
×
907
        nodeName: this.name(),
908
        entityType: 'subscription',
909
        entityName: topic,
910
      });
911
    }
912
    if (
440!
913
      eventCallbacks &&
444✔
914
      !(eventCallbacks instanceof SubscriptionEventCallbacks)
915
    ) {
UNCOV
916
      throw new TypeValidationError(
×
917
        'eventCallbacks',
918
        eventCallbacks,
919
        'SubscriptionEventCallbacks',
920
        {
921
          nodeName: this.name(),
922
          entityType: 'subscription',
923
          entityName: topic,
924
        }
925
      );
926
    }
927

928
    // Apply QoS overriding options if provided
929
    if (options.qosOverridingOptions) {
440✔
930
      const resolvedTopic = this.resolveTopicName(topic);
2✔
931
      if (typeof options.qos === 'string' || !(options.qos instanceof QoS)) {
2!
UNCOV
932
        options.qos = _resolveQoS(options.qos);
×
933
      }
934
      declareQosParameters(
2✔
935
        'subscription',
936
        this,
937
        resolvedTopic,
938
        options.qos,
939
        options.qosOverridingOptions
940
      );
941
    }
942

943
    let subscription = Subscription.createSubscription(
440✔
944
      this,
945
      typeClass,
946
      topic,
947
      options,
948
      callback,
949
      eventCallbacks
950
    );
951
    debug('Finish creating subscription, topic = %s.', topic);
429✔
952
    this._subscriptions.push(subscription);
429✔
953
    this.syncHandles();
429✔
954

955
    return subscription;
429✔
956
  }
957

958
  /**
959
   * Create a Subscription that returns an RxJS Observable.
960
   * This allows using reactive programming patterns with ROS 2 messages.
961
   *
962
   * @param {function|string|object} typeClass - The ROS message class,
963
   *      OR a string representing the message class, e.g. 'std_msgs/msg/String',
964
   *      OR an object representing the message class, e.g. {package: 'std_msgs', type: 'msg', name: 'String'}
965
   * @param {string} topic - The name of the topic.
966
   * @param {object} [options] - The options argument used to parameterize the subscription.
967
   * @param {boolean} [options.enableTypedArray=true] - The topic will use TypedArray if necessary.
968
   * @param {QoS} [options.qos=QoS.profileDefault] - ROS Middleware "quality of service" settings.
969
   * @param {boolean} [options.isRaw=false] - The topic is serialized when true.
970
   * @param {string} [options.serializationMode='default'] - Controls message serialization format.
971
   * @param {object} [options.contentFilter] - The content-filter (if supported by RMW).
972
   * @param {SubscriptionEventCallbacks} [eventCallbacks] - The event callbacks for the subscription.
973
   * @return {ObservableSubscription} - An ObservableSubscription with an RxJS Observable.
974
   */
975
  createObservableSubscription(typeClass, topic, options, eventCallbacks) {
976
    let observableSubscription = null;
7✔
977

978
    const subscription = this.createSubscription(
7✔
979
      typeClass,
980
      topic,
981
      options,
982
      (message) => {
983
        if (observableSubscription) {
8!
984
          observableSubscription._emit(message);
8✔
985
        }
986
      },
987
      eventCallbacks
988
    );
989

990
    observableSubscription = new ObservableSubscription(subscription);
7✔
991
    return observableSubscription;
7✔
992
  }
993

994
  /**
995
   * Create a Client.
996
   * @param {function|string|object} typeClass - The ROS message class,
997
        OR a string representing the message class, e.g. 'std_msgs/msg/String',
998
        OR an object representing the message class, e.g. {package: 'std_msgs', type: 'msg', name: 'String'}
999
   * @param {string} serviceName - The service name to request.
1000
   * @param {object} options - The options argument used to parameterize the client.
1001
   * @param {boolean} options.enableTypedArray - The response will use TypedArray if necessary, default: true.
1002
   * @param {QoS} options.qos - ROS Middleware "quality of service" settings for the client, default: QoS.profileDefault.
1003
   * @return {Client} - An instance of Client.
1004
   */
1005
  createClient(typeClass, serviceName, options) {
1006
    if (typeof typeClass === 'string' || typeof typeClass === 'object') {
196✔
1007
      typeClass = loader.loadInterface(typeClass);
186✔
1008
    }
1009
    options = this._validateOptions(options);
189✔
1010

1011
    if (typeof typeClass !== 'function') {
189✔
1012
      throw new TypeValidationError('typeClass', typeClass, 'function', {
8✔
1013
        nodeName: this.name(),
1014
        entityType: 'client',
1015
      });
1016
    }
1017
    if (typeof serviceName !== 'string') {
181✔
1018
      throw new TypeValidationError('serviceName', serviceName, 'string', {
12✔
1019
        nodeName: this.name(),
1020
        entityType: 'client',
1021
      });
1022
    }
1023

1024
    let client = Client.createClient(
169✔
1025
      this.handle,
1026
      serviceName,
1027
      typeClass,
1028
      options
1029
    );
1030
    debug('Finish creating client, service = %s.', serviceName);
159✔
1031
    this._clients.push(client);
159✔
1032
    this.syncHandles();
159✔
1033

1034
    return client;
159✔
1035
  }
1036

1037
  /**
1038
   * This callback is called when a request is sent to service
1039
   * @callback RequestCallback
1040
   * @param {Object} request - The request sent to the service
1041
   * @param {Response} response - The response to client.
1042
        Use [response.send()]{@link Response#send} to send response object to client
1043
   * @return {undefined}
1044
   * @see [Node.createService]{@link Node#createService}
1045
   * @see [Client.sendRequest]{@link Client#sendRequest}
1046
   * @see {@link Client}
1047
   * @see {@link Service}
1048
   * @see {@link Response#send}
1049
   */
1050

1051
  /**
1052
   * Create a Service.
1053
   * @param {function|string|object} typeClass - The ROS message class,
1054
        OR a string representing the message class, e.g. 'std_msgs/msg/String',
1055
        OR an object representing the message class, e.g. {package: 'std_msgs', type: 'msg', name: 'String'}
1056
   * @param {string} serviceName - The service name to offer.
1057
   * @param {object} options - The options argument used to parameterize the service.
1058
   * @param {boolean} options.enableTypedArray - The request will use TypedArray if necessary, default: true.
1059
   * @param {QoS} options.qos - ROS Middleware "quality of service" settings for the service, default: QoS.profileDefault.
1060
   * @param {RequestCallback} callback - The callback to be called when receiving request.
1061
   * @return {Service} - An instance of Service.
1062
   * @see {@link RequestCallback}
1063
   */
1064
  createService(typeClass, serviceName, options, callback) {
1065
    if (typeof typeClass === 'string' || typeof typeClass === 'object') {
5,467✔
1066
      typeClass = loader.loadInterface(typeClass);
5,457✔
1067
    }
1068

1069
    if (typeof options === 'function') {
5,460✔
1070
      callback = options;
5,437✔
1071
      options = undefined;
5,437✔
1072
    }
1073
    options = this._validateOptions(options);
5,460✔
1074

1075
    if (typeof typeClass !== 'function') {
5,450✔
1076
      throw new TypeValidationError('typeClass', typeClass, 'function', {
4✔
1077
        nodeName: this.name(),
1078
        entityType: 'service',
1079
      });
1080
    }
1081
    if (typeof serviceName !== 'string') {
5,446✔
1082
      throw new TypeValidationError('serviceName', serviceName, 'string', {
6✔
1083
        nodeName: this.name(),
1084
        entityType: 'service',
1085
      });
1086
    }
1087
    if (typeof callback !== 'function') {
5,440!
UNCOV
1088
      throw new TypeValidationError('callback', callback, 'function', {
×
1089
        nodeName: this.name(),
1090
        entityType: 'service',
1091
        entityName: serviceName,
1092
      });
1093
    }
1094

1095
    let service = Service.createService(
5,440✔
1096
      this.handle,
1097
      serviceName,
1098
      typeClass,
1099
      options,
1100
      callback
1101
    );
1102
    debug('Finish creating service, service = %s.', serviceName);
5,430✔
1103
    this._services.push(service);
5,430✔
1104
    this.syncHandles();
5,430✔
1105

1106
    return service;
5,430✔
1107
  }
1108

1109
  /**
1110
   * Create a ParameterClient for accessing parameters on a remote node.
1111
   * @param {string} remoteNodeName - The name of the remote node whose parameters to access.
1112
   * @param {object} [options] - Options for parameter client.
1113
   * @param {number} [options.timeout=5000] - Default timeout in milliseconds for service calls.
1114
   * @return {ParameterClient} - An instance of ParameterClient.
1115
   */
1116
  createParameterClient(remoteNodeName, options = {}) {
50✔
1117
    if (typeof remoteNodeName !== 'string' || remoteNodeName.trim() === '') {
103!
UNCOV
1118
      throw new TypeError('Remote node name must be a non-empty string');
×
1119
    }
1120

1121
    const parameterClient = new ParameterClient(this, remoteNodeName, options);
103✔
1122
    debug(
103✔
1123
      'Finish creating parameter client for remote node = %s.',
1124
      remoteNodeName
1125
    );
1126
    this._parameterClients.push(parameterClient);
103✔
1127

1128
    return parameterClient;
103✔
1129
  }
1130

1131
  /**
1132
   * Create a ParameterWatcher for watching parameter changes on a remote node.
1133
   * @param {string} remoteNodeName - The name of the remote node whose parameters to watch.
1134
   * @param {string[]} parameterNames - Array of parameter names to watch.
1135
   * @param {object} [options] - Options for parameter watcher.
1136
   * @param {number} [options.timeout=5000] - Default timeout in milliseconds for service calls.
1137
   * @return {ParameterWatcher} - An instance of ParameterWatcher.
1138
   */
1139
  createParameterWatcher(remoteNodeName, parameterNames, options = {}) {
56✔
1140
    const watcher = new ParameterWatcher(
57✔
1141
      this,
1142
      remoteNodeName,
1143
      parameterNames,
1144
      options
1145
    );
1146
    debug(
53✔
1147
      'Finish creating parameter watcher for remote node = %s.',
1148
      remoteNodeName
1149
    );
1150
    this._parameterWatchers.push(watcher);
53✔
1151

1152
    return watcher;
53✔
1153
  }
1154

1155
  /**
1156
   * Create a guard condition.
1157
   * @param {Function} callback - The callback to be called when the guard condition is triggered.
1158
   * @return {GuardCondition} - An instance of GuardCondition.
1159
   */
1160
  createGuardCondition(callback) {
1161
    if (typeof callback !== 'function') {
3!
UNCOV
1162
      throw new TypeValidationError('callback', callback, 'function', {
×
1163
        nodeName: this.name(),
1164
        entityType: 'guard_condition',
1165
      });
1166
    }
1167

1168
    let guard = GuardCondition.createGuardCondition(callback, this.context);
3✔
1169
    debug('Finish creating guard condition');
3✔
1170
    this._guards.push(guard);
3✔
1171
    this.syncHandles();
3✔
1172

1173
    return guard;
3✔
1174
  }
1175

1176
  /**
1177
   * Destroy all resource allocated by this node, including
1178
   * <code>Timer</code>s/<code>Publisher</code>s/<code>Subscription</code>s
1179
   * /<code>Client</code>s/<code>Service</code>s
1180
   * @return {undefined}
1181
   */
1182
  destroy() {
1183
    if (this.spinning) {
930✔
1184
      this.stop();
602✔
1185
    }
1186

1187
    // Action servers/clients require manual destruction due to circular reference with goal handles.
1188
    this._actionClients.forEach((actionClient) => actionClient.destroy());
930✔
1189
    this._actionServers.forEach((actionServer) => actionServer.destroy());
930✔
1190

1191
    this._parameterClients.forEach((paramClient) => paramClient.destroy());
930✔
1192
    this._parameterWatchers.forEach((watcher) => watcher.destroy());
930✔
1193
    this._parameterEventHandlers.forEach((handler) => handler.destroy());
930✔
1194

1195
    this.context.onNodeDestroyed(this);
930✔
1196

1197
    if (this._enableRosout) {
930✔
1198
      rclnodejs.finiRosoutPublisherForNode(this.handle);
905✔
1199
      this._enableRosout = false;
905✔
1200
    }
1201

1202
    this.handle.release();
930✔
1203
    this._clock = null;
930✔
1204
    this._timers = [];
930✔
1205
    this._publishers = [];
930✔
1206
    this._subscriptions = [];
930✔
1207
    this._clients = [];
930✔
1208
    this._services = [];
930✔
1209
    this._guards = [];
930✔
1210
    this._actionClients = [];
930✔
1211
    this._actionServers = [];
930✔
1212
    this._parameterClients = [];
930✔
1213
    this._parameterWatchers = [];
930✔
1214
    this._parameterEventHandlers = [];
930✔
1215

1216
    if (this._rateTimerServer) {
930✔
1217
      this._rateTimerServer.shutdown();
5✔
1218
      this._rateTimerServer = null;
5✔
1219
    }
1220
  }
1221

1222
  /**
1223
   * Destroy a Publisher.
1224
   * @param {Publisher} publisher - The Publisher to be destroyed.
1225
   * @return {undefined}
1226
   */
1227
  destroyPublisher(publisher) {
1228
    if (!(publisher instanceof Publisher)) {
14✔
1229
      throw new TypeValidationError(
2✔
1230
        'publisher',
1231
        publisher,
1232
        'Publisher instance',
1233
        {
1234
          nodeName: this.name(),
1235
        }
1236
      );
1237
    }
1238
    if (publisher.events) {
12!
UNCOV
1239
      publisher.events.forEach((event) => {
×
1240
        this._destroyEntity(event, this._events);
×
1241
      });
UNCOV
1242
      publisher.events = [];
×
1243
    }
1244
    this._destroyEntity(publisher, this._publishers, false);
12✔
1245
  }
1246

1247
  /**
1248
   * Destroy a Subscription.
1249
   * @param {Subscription} subscription - The Subscription to be destroyed.
1250
   * @return {undefined}
1251
   */
1252
  destroySubscription(subscription) {
1253
    if (!(subscription instanceof Subscription)) {
111✔
1254
      throw new TypeValidationError(
2✔
1255
        'subscription',
1256
        subscription,
1257
        'Subscription instance',
1258
        {
1259
          nodeName: this.name(),
1260
        }
1261
      );
1262
    }
1263
    if (subscription.events) {
109✔
1264
      subscription.events.forEach((event) => {
1✔
1265
        this._destroyEntity(event, this._events);
1✔
1266
      });
1267
      subscription.events = [];
1✔
1268
    }
1269

1270
    this._destroyEntity(subscription, this._subscriptions);
109✔
1271
  }
1272

1273
  /**
1274
   * Destroy a Client.
1275
   * @param {Client} client - The Client to be destroyed.
1276
   * @return {undefined}
1277
   */
1278
  destroyClient(client) {
1279
    if (!(client instanceof Client)) {
117✔
1280
      throw new TypeValidationError('client', client, 'Client instance', {
2✔
1281
        nodeName: this.name(),
1282
      });
1283
    }
1284
    this._destroyEntity(client, this._clients);
115✔
1285
  }
1286

1287
  /**
1288
   * Destroy a Service.
1289
   * @param {Service} service - The Service to be destroyed.
1290
   * @return {undefined}
1291
   */
1292
  destroyService(service) {
1293
    if (!(service instanceof Service)) {
8✔
1294
      throw new TypeValidationError('service', service, 'Service instance', {
2✔
1295
        nodeName: this.name(),
1296
      });
1297
    }
1298
    this._destroyEntity(service, this._services);
6✔
1299
  }
1300

1301
  /**
1302
   * Destroy a ParameterClient.
1303
   * @param {ParameterClient} parameterClient - The ParameterClient to be destroyed.
1304
   * @return {undefined}
1305
   */
1306
  destroyParameterClient(parameterClient) {
1307
    if (!(parameterClient instanceof ParameterClient)) {
54!
UNCOV
1308
      throw new TypeError('Invalid argument');
×
1309
    }
1310
    this._removeEntityFromArray(parameterClient, this._parameterClients);
54✔
1311
    parameterClient.destroy();
54✔
1312
  }
1313

1314
  /**
1315
   * Destroy a ParameterWatcher.
1316
   * @param {ParameterWatcher} watcher - The ParameterWatcher to be destroyed.
1317
   * @return {undefined}
1318
   */
1319
  destroyParameterWatcher(watcher) {
1320
    if (!(watcher instanceof ParameterWatcher)) {
1!
UNCOV
1321
      throw new TypeError('Invalid argument');
×
1322
    }
1323
    this._removeEntityFromArray(watcher, this._parameterWatchers);
1✔
1324
    watcher.destroy();
1✔
1325
  }
1326

1327
  /**
1328
   * Create a ParameterEventHandler that monitors parameter changes on any node.
1329
   *
1330
   * Unlike {@link ParameterWatcher} which watches specific parameters on a single
1331
   * remote node, ParameterEventHandler can register callbacks for parameters on
1332
   * any node in the ROS 2 graph by subscribing to /parameter_events.
1333
   *
1334
   * @param {object} [options] - Options for the handler
1335
   * @param {object} [options.qos] - QoS profile for the parameter_events subscription
1336
   * @return {ParameterEventHandler} - An instance of ParameterEventHandler
1337
   * @see {@link ParameterEventHandler}
1338
   */
1339
  createParameterEventHandler(options = {}) {
18✔
1340
    const handler = new ParameterEventHandler(this, options);
18✔
1341
    debug('Created ParameterEventHandler on node=%s', this.name());
18✔
1342
    this._parameterEventHandlers.push(handler);
18✔
1343
    return handler;
18✔
1344
  }
1345

1346
  /**
1347
   * Destroy a ParameterEventHandler.
1348
   * @param {ParameterEventHandler} handler - The handler to be destroyed.
1349
   * @return {undefined}
1350
   */
1351
  destroyParameterEventHandler(handler) {
1352
    if (!(handler instanceof ParameterEventHandler)) {
1!
UNCOV
1353
      throw new TypeError('Invalid argument');
×
1354
    }
1355
    this._removeEntityFromArray(handler, this._parameterEventHandlers);
1✔
1356
    handler.destroy();
1✔
1357
  }
1358

1359
  /**
1360
   * Destroy a Timer.
1361
   * @param {Timer} timer - The Timer to be destroyed.
1362
   * @return {undefined}
1363
   */
1364
  destroyTimer(timer) {
1365
    if (!(timer instanceof Timer)) {
8✔
1366
      throw new TypeValidationError('timer', timer, 'Timer instance', {
2✔
1367
        nodeName: this.name(),
1368
      });
1369
    }
1370
    this._destroyEntity(timer, this._timers);
6✔
1371
  }
1372

1373
  /**
1374
   * Destroy a guard condition.
1375
   * @param {GuardCondition} guard - The guard condition to be destroyed.
1376
   * @return {undefined}
1377
   */
1378
  destroyGuardCondition(guard) {
1379
    if (!(guard instanceof GuardCondition)) {
3!
UNCOV
1380
      throw new TypeValidationError('guard', guard, 'GuardCondition instance', {
×
1381
        nodeName: this.name(),
1382
      });
1383
    }
1384
    this._destroyEntity(guard, this._guards);
3✔
1385
  }
1386

1387
  /**
1388
   * Get the name of the node.
1389
   * @return {string}
1390
   */
1391
  name() {
1392
    return rclnodejs.getNodeName(this.handle);
5,424✔
1393
  }
1394

1395
  /**
1396
   * Get the namespace of the node.
1397
   * @return {string}
1398
   */
1399
  namespace() {
1400
    return rclnodejs.getNamespace(this.handle);
5,042✔
1401
  }
1402

1403
  /**
1404
   * Get the context in which this node was created.
1405
   * @return {Context}
1406
   */
1407
  get context() {
1408
    return this._context;
6,516✔
1409
  }
1410

1411
  /**
1412
   * Get the nodes logger.
1413
   * @returns {Logger} - The logger for the node.
1414
   */
1415
  getLogger() {
1416
    return this._logger;
243✔
1417
  }
1418

1419
  /**
1420
   * Get the clock used by the node.
1421
   * @returns {Clock} - The nodes clock.
1422
   */
1423
  getClock() {
1424
    return this._clock;
116✔
1425
  }
1426

1427
  /**
1428
   * Get the current time using the node's clock.
1429
   * @returns {Timer} - The current time.
1430
   */
1431
  now() {
1432
    return this.getClock().now();
2✔
1433
  }
1434

1435
  /**
1436
   * Get the list of published topics discovered by the provided node for the remote node name.
1437
   * @param {string} nodeName - The name of the node.
1438
   * @param {string} namespace - The name of the namespace.
1439
   * @param {boolean} noDemangle - If true topic names and types returned will not be demangled, default: false.
1440
   * @return {Array<{name: string, types: Array<string>}>} - An array of the names and types.
1441
   */
1442
  getPublisherNamesAndTypesByNode(nodeName, namespace, noDemangle = false) {
2✔
1443
    return rclnodejs.getPublisherNamesAndTypesByNode(
2✔
1444
      this.handle,
1445
      nodeName,
1446
      namespace,
1447
      noDemangle
1448
    );
1449
  }
1450

1451
  /**
1452
   * Get the list of published topics discovered by the provided node for the remote node name.
1453
   * @param {string} nodeName - The name of the node.
1454
   * @param {string} namespace - The name of the namespace.
1455
   * @param {boolean} noDemangle - If true topic names and types returned will not be demangled, default: false.
1456
   * @return {Array<{name: string, types: Array<string>}>} - An array of the names and types.
1457
   */
1458
  getSubscriptionNamesAndTypesByNode(nodeName, namespace, noDemangle = false) {
×
UNCOV
1459
    return rclnodejs.getSubscriptionNamesAndTypesByNode(
×
1460
      this.handle,
1461
      nodeName,
1462
      namespace,
1463
      noDemangle
1464
    );
1465
  }
1466

1467
  /**
1468
   * Get service names and types for which a remote node has servers.
1469
   * @param {string} nodeName - The name of the node.
1470
   * @param {string} namespace - The name of the namespace.
1471
   * @return {Array<{name: string, types: Array<string>}>} - An array of the names and types.
1472
   */
1473
  getServiceNamesAndTypesByNode(nodeName, namespace) {
UNCOV
1474
    return rclnodejs.getServiceNamesAndTypesByNode(
×
1475
      this.handle,
1476
      nodeName,
1477
      namespace
1478
    );
1479
  }
1480

1481
  /**
1482
   * Get service names and types for which a remote node has clients.
1483
   * @param {string} nodeName - The name of the node.
1484
   * @param {string} namespace - The name of the namespace.
1485
   * @return {Array<{name: string, types: Array<string>}>} - An array of the names and types.
1486
   */
1487
  getClientNamesAndTypesByNode(nodeName, namespace) {
UNCOV
1488
    return rclnodejs.getClientNamesAndTypesByNode(
×
1489
      this.handle,
1490
      nodeName,
1491
      namespace
1492
    );
1493
  }
1494

1495
  /**
1496
   * Get the list of topics discovered by the provided node.
1497
   * @param {boolean} noDemangle - If true topic names and types returned will not be demangled, default: false.
1498
   * @return {Array<{name: string, types: Array<string>}>} - An array of the names and types.
1499
   */
1500
  getTopicNamesAndTypes(noDemangle = false) {
×
UNCOV
1501
    return rclnodejs.getTopicNamesAndTypes(this.handle, noDemangle);
×
1502
  }
1503

1504
  /**
1505
   * Get the list of services discovered by the provided node.
1506
   * @return {Array<{name: string, types: Array<string>}>} - An array of the names and types.
1507
   */
1508
  getServiceNamesAndTypes() {
1509
    return rclnodejs.getServiceNamesAndTypes(this.handle);
2✔
1510
  }
1511

1512
  /**
1513
   * Return a list of publishers on a given topic.
1514
   *
1515
   * The returned parameter is a list of TopicEndpointInfo objects, where each will contain
1516
   * the node name, node namespace, topic type, topic endpoint's GID, and its QoS profile.
1517
   *
1518
   * When the `no_mangle` parameter is `true`, the provided `topic` should be a valid
1519
   * topic name for the middleware (useful when combining ROS with native middleware (e.g. DDS)
1520
   * apps).  When the `no_mangle` parameter is `false`, the provided `topic` should
1521
   * follow ROS topic name conventions.
1522
   *
1523
   * `topic` may be a relative, private, or fully qualified topic name.
1524
   *  A relative or private topic will be expanded using this node's namespace and name.
1525
   *  The queried `topic` is not remapped.
1526
   *
1527
   * @param {string} topic - The topic on which to find the publishers.
1528
   * @param {boolean} [noDemangle=false] - If `true`, `topic` needs to be a valid middleware topic
1529
   *                               name, otherwise it should be a valid ROS topic name. Defaults to `false`.
1530
   * @returns {Array} - list of publishers
1531
   */
1532
  getPublishersInfoByTopic(topic, noDemangle = false) {
1✔
1533
    return rclnodejs.getPublishersInfoByTopic(
4✔
1534
      this.handle,
1535
      this._getValidatedTopic(topic, noDemangle),
1536
      noDemangle
1537
    );
1538
  }
1539

1540
  /**
1541
   * Return a list of subscriptions on a given topic.
1542
   *
1543
   * The returned parameter is a list of TopicEndpointInfo objects, where each will contain
1544
   * the node name, node namespace, topic type, topic endpoint's GID, and its QoS profile.
1545
   *
1546
   * When the `no_mangle` parameter is `true`, the provided `topic` should be a valid
1547
   * topic name for the middleware (useful when combining ROS with native middleware (e.g. DDS)
1548
   * apps).  When the `no_mangle` parameter is `false`, the provided `topic` should
1549
   * follow ROS topic name conventions.
1550
   *
1551
   * `topic` may be a relative, private, or fully qualified topic name.
1552
   *  A relative or private topic will be expanded using this node's namespace and name.
1553
   *  The queried `topic` is not remapped.
1554
   *
1555
   * @param {string} topic - The topic on which to find the subscriptions.
1556
   * @param {boolean} [noDemangle=false] -  If `true`, `topic` needs to be a valid middleware topic
1557
                                    name, otherwise it should be a valid ROS topic name. Defaults to `false`.
1558
   * @returns {Array} - list of subscriptions
1559
   */
1560
  getSubscriptionsInfoByTopic(topic, noDemangle = false) {
×
1561
    return rclnodejs.getSubscriptionsInfoByTopic(
2✔
1562
      this.handle,
1563
      this._getValidatedTopic(topic, noDemangle),
1564
      noDemangle
1565
    );
1566
  }
1567

1568
  /**
1569
   * Return a list of clients on a given service.
1570
   *
1571
   * The returned parameter is a list of ServiceEndpointInfo objects, where each will contain
1572
   * the node name, node namespace, service type, service endpoint's GID, and its QoS profile.
1573
   *
1574
   * When the `no_mangle` parameter is `true`, the provided `service` should be a valid
1575
   * service name for the middleware (useful when combining ROS with native middleware (e.g. DDS)
1576
   * apps).  When the `no_mangle` parameter is `false`, the provided `service` should
1577
   * follow ROS service name conventions.
1578
   *
1579
   * `service` may be a relative, private, or fully qualified service name.
1580
   *  A relative or private service will be expanded using this node's namespace and name.
1581
   *  The queried `service` is not remapped.
1582
   *
1583
   * @param {string} service - The service on which to find the clients.
1584
   * @param {boolean} [noDemangle=false] - If `true`, `service` needs to be a valid middleware service
1585
   *                               name, otherwise it should be a valid ROS service name. Defaults to `false`.
1586
   * @returns {Array} - list of clients
1587
   */
1588
  getClientsInfoByService(service, noDemangle = false) {
×
1589
    if (DistroUtils.getDistroId() < DistroUtils.DistroId.ROLLING) {
2!
UNCOV
1590
      console.warn(
×
1591
        'getClientsInfoByService is not supported by this version of ROS 2'
1592
      );
UNCOV
1593
      return null;
×
1594
    }
1595
    return rclnodejs.getClientsInfoByService(
2✔
1596
      this.handle,
1597
      this._getValidatedServiceName(service, noDemangle),
1598
      noDemangle
1599
    );
1600
  }
1601

1602
  /**
1603
   * Return a list of servers on a given service.
1604
   *
1605
   * The returned parameter is a list of ServiceEndpointInfo objects, where each will contain
1606
   * the node name, node namespace, service type, service endpoint's GID, and its QoS profile.
1607
   *
1608
   * When the `no_mangle` parameter is `true`, the provided `service` should be a valid
1609
   * service name for the middleware (useful when combining ROS with native middleware (e.g. DDS)
1610
   * apps).  When the `no_mangle` parameter is `false`, the provided `service` should
1611
   * follow ROS service name conventions.
1612
   *
1613
   * `service` may be a relative, private, or fully qualified service name.
1614
   *  A relative or private service will be expanded using this node's namespace and name.
1615
   *  The queried `service` is not remapped.
1616
   *
1617
   * @param {string} service - The service on which to find the servers.
1618
   * @param {boolean} [noDemangle=false] - If `true`, `service` needs to be a valid middleware service
1619
   *                               name, otherwise it should be a valid ROS service name. Defaults to `false`.
1620
   * @returns {Array} - list of servers
1621
   */
1622
  getServersInfoByService(service, noDemangle = false) {
×
1623
    if (DistroUtils.getDistroId() < DistroUtils.DistroId.ROLLING) {
2!
UNCOV
1624
      console.warn(
×
1625
        'getServersInfoByService is not supported by this version of ROS 2'
1626
      );
UNCOV
1627
      return null;
×
1628
    }
1629
    return rclnodejs.getServersInfoByService(
2✔
1630
      this.handle,
1631
      this._getValidatedServiceName(service, noDemangle),
1632
      noDemangle
1633
    );
1634
  }
1635

1636
  /**
1637
   * Get the list of nodes discovered by the provided node.
1638
   * @return {Array<string>} - An array of the names.
1639
   */
1640
  getNodeNames() {
1641
    return this.getNodeNamesAndNamespaces().map((item) => item.name);
41✔
1642
  }
1643

1644
  /**
1645
   * Get the list of nodes and their namespaces discovered by the provided node.
1646
   * @return {Array<{name: string, namespace: string}>} An array of the names and namespaces.
1647
   */
1648
  getNodeNamesAndNamespaces() {
1649
    return rclnodejs.getNodeNames(this.handle, /*getEnclaves=*/ false);
17✔
1650
  }
1651

1652
  /**
1653
   * Get the list of nodes and their namespaces with enclaves discovered by the provided node.
1654
   * @return {Array<{name: string, namespace: string, enclave: string}>} An array of the names, namespaces and enclaves.
1655
   */
1656
  getNodeNamesAndNamespacesWithEnclaves() {
1657
    return rclnodejs.getNodeNames(this.handle, /*getEnclaves=*/ true);
1✔
1658
  }
1659

1660
  /**
1661
   * Return the number of publishers on a given topic.
1662
   * @param {string} topic - The name of the topic.
1663
   * @returns {number} - Number of publishers on the given topic.
1664
   */
1665
  countPublishers(topic) {
1666
    let expandedTopic = rclnodejs.expandTopicName(
6✔
1667
      topic,
1668
      this.name(),
1669
      this.namespace()
1670
    );
1671
    rclnodejs.validateTopicName(expandedTopic);
6✔
1672

1673
    return rclnodejs.countPublishers(this.handle, expandedTopic);
6✔
1674
  }
1675

1676
  /**
1677
   * Return the number of subscribers on a given topic.
1678
   * @param {string} topic - The name of the topic.
1679
   * @returns {number} - Number of subscribers on the given topic.
1680
   */
1681
  countSubscribers(topic) {
1682
    let expandedTopic = rclnodejs.expandTopicName(
6✔
1683
      topic,
1684
      this.name(),
1685
      this.namespace()
1686
    );
1687
    rclnodejs.validateTopicName(expandedTopic);
6✔
1688

1689
    return rclnodejs.countSubscribers(this.handle, expandedTopic);
6✔
1690
  }
1691

1692
  /**
1693
   * Get the number of clients on a given service name.
1694
   * @param {string} serviceName - the service name
1695
   * @returns {Number}
1696
   */
1697
  countClients(serviceName) {
1698
    if (DistroUtils.getDistroId() <= DistroUtils.getDistroId('humble')) {
2!
UNCOV
1699
      console.warn('countClients is not supported by this version of ROS 2');
×
1700
      return null;
×
1701
    }
1702
    return rclnodejs.countClients(this.handle, serviceName);
2✔
1703
  }
1704

1705
  /**
1706
   * Get the number of services on a given service name.
1707
   * @param {string} serviceName - the service name
1708
   * @returns {Number}
1709
   */
1710
  countServices(serviceName) {
1711
    if (DistroUtils.getDistroId() <= DistroUtils.getDistroId('humble')) {
1!
UNCOV
1712
      console.warn('countServices is not supported by this version of ROS 2');
×
1713
      return null;
×
1714
    }
1715
    return rclnodejs.countServices(this.handle, serviceName);
1✔
1716
  }
1717

1718
  /**
1719
   * Get the list of parameter-overrides found on the commandline and
1720
   * in the NodeOptions.parameter_overrides property.
1721
   *
1722
   * @return {Array<Parameter>} - An array of Parameters.
1723
   */
1724
  getParameterOverrides() {
1725
    return Array.from(this._parameterOverrides.values());
8✔
1726
  }
1727

1728
  /**
1729
   * Declare a parameter.
1730
   *
1731
   * Internally, register a parameter and it's descriptor.
1732
   * If a parameter-override exists, it's value will replace that of the parameter
1733
   * unless ignoreOverride is true.
1734
   * If the descriptor is undefined, then a ParameterDescriptor will be inferred
1735
   * from the parameter's state.
1736
   *
1737
   * If a parameter by the same name has already been declared then an Error is thrown.
1738
   * A parameter must be undeclared before attempting to redeclare it.
1739
   *
1740
   * @param {Parameter} parameter - Parameter to declare.
1741
   * @param {ParameterDescriptor} [descriptor] - Optional descriptor for parameter.
1742
   * @param {boolean} [ignoreOverride] - When true disregard any parameter-override that may be present.
1743
   * @return {Parameter} - The newly declared parameter.
1744
   */
1745
  declareParameter(parameter, descriptor, ignoreOverride = false) {
2,466✔
1746
    const parameters = this.declareParameters(
2,467✔
1747
      [parameter],
1748
      descriptor ? [descriptor] : [],
2,467✔
1749
      ignoreOverride
1750
    );
1751
    return parameters.length == 1 ? parameters[0] : null;
2,467!
1752
  }
1753

1754
  /**
1755
   * Declare a list of parameters.
1756
   *
1757
   * Internally register parameters with their corresponding descriptor one by one
1758
   * in the order they are provided. This is an atomic operation. If an error
1759
   * occurs the process halts and no further parameters are declared.
1760
   * Parameters that have already been processed are undeclared.
1761
   *
1762
   * While descriptors is an optional parameter, when provided there must be
1763
   * a descriptor for each parameter; otherwise an Error is thrown.
1764
   * If descriptors is not provided then a descriptor will be inferred
1765
   * from each parameter's state.
1766
   *
1767
   * When a parameter-override is available, the parameter's value
1768
   * will be replaced with that of the parameter-override unless ignoreOverrides
1769
   * is true.
1770
   *
1771
   * If a parameter by the same name has already been declared then an Error is thrown.
1772
   * A parameter must be undeclared before attempting to redeclare it.
1773
   *
1774
   * Prior to declaring the parameters each SetParameterEventCallback registered
1775
   * using setOnParameterEventCallback() is called in succession with the parameters
1776
   * list. Any SetParameterEventCallback that retuns does not return a successful
1777
   * result will cause the entire operation to terminate with no changes to the
1778
   * parameters. When all SetParameterEventCallbacks return successful then the
1779
   * list of parameters is updated.
1780
   *
1781
   * @param {Parameter[]} parameters - The parameters to declare.
1782
   * @param {ParameterDescriptor[]} [descriptors] - Optional descriptors,
1783
   *    a 1-1 correspondence with parameters.
1784
   * @param {boolean} ignoreOverrides - When true, parameter-overrides are
1785
   *    not considered, i.e.,ignored.
1786
   * @return {Parameter[]} - The declared parameters.
1787
   */
1788
  declareParameters(parameters, descriptors = [], ignoreOverrides = false) {
×
1789
    if (!Array.isArray(parameters)) {
2,467!
UNCOV
1790
      throw new TypeValidationError('parameters', parameters, 'Array', {
×
1791
        nodeName: this.name(),
1792
      });
1793
    }
1794
    if (!Array.isArray(descriptors)) {
2,467!
UNCOV
1795
      throw new TypeValidationError('descriptors', descriptors, 'Array', {
×
1796
        nodeName: this.name(),
1797
      });
1798
    }
1799
    if (descriptors.length > 0 && parameters.length !== descriptors.length) {
2,467!
UNCOV
1800
      throw new ValidationError(
×
1801
        'Each parameter must have a corresponding ParameterDescriptor',
1802
        {
1803
          code: 'PARAMETER_DESCRIPTOR_MISMATCH',
1804
          argumentName: 'descriptors',
1805
          providedValue: descriptors.length,
1806
          expectedType: `Array with length ${parameters.length}`,
1807
          nodeName: this.name(),
1808
        }
1809
      );
1810
    }
1811

1812
    const declaredDescriptors = [];
2,467✔
1813
    const declaredParameters = [];
2,467✔
1814
    const declaredParameterCollisions = [];
2,467✔
1815
    for (let i = 0; i < parameters.length; i++) {
2,467✔
1816
      let parameter =
1817
        !ignoreOverrides && this._parameterOverrides.has(parameters[i].name)
2,467✔
1818
          ? this._parameterOverrides.get(parameters[i].name)
1819
          : parameters[i];
1820

1821
      // stop processing parameters that have already been declared
1822
      if (this._parameters.has(parameter.name)) {
2,467!
UNCOV
1823
        declaredParameterCollisions.push(parameter);
×
1824
        continue;
×
1825
      }
1826

1827
      // create descriptor for parameter if not provided
1828
      let descriptor =
1829
        descriptors.length > 0
2,467✔
1830
          ? descriptors[i]
1831
          : ParameterDescriptor.fromParameter(parameter);
1832

1833
      descriptor.validate();
2,467✔
1834

1835
      declaredDescriptors.push(descriptor);
2,467✔
1836
      declaredParameters.push(parameter);
2,467✔
1837
    }
1838

1839
    if (declaredParameterCollisions.length > 0) {
2,467!
1840
      const errorMsg =
UNCOV
1841
        declaredParameterCollisions.length == 1
×
1842
          ? `Parameter(${declaredParameterCollisions[0]}) already declared.`
1843
          : `Multiple parameters already declared, e.g., Parameter(${declaredParameterCollisions[0]}).`;
UNCOV
1844
      throw new Error(errorMsg);
×
1845
    }
1846

1847
    // register descriptor
1848
    for (const descriptor of declaredDescriptors) {
2,467✔
1849
      this._parameterDescriptors.set(descriptor.name, descriptor);
2,467✔
1850
    }
1851

1852
    const result = this._setParametersAtomically(declaredParameters, true);
2,467✔
1853
    if (!result.successful) {
2,467!
1854
      // unregister descriptors
UNCOV
1855
      for (const descriptor of declaredDescriptors) {
×
1856
        this._parameterDescriptors.delete(descriptor.name);
×
1857
      }
1858

UNCOV
1859
      throw new Error(result.reason);
×
1860
    }
1861

1862
    return this.getParameters(declaredParameters.map((param) => param.name));
2,467✔
1863
  }
1864

1865
  /**
1866
   * Undeclare a parameter.
1867
   *
1868
   * Readonly parameters can not be undeclared or updated.
1869
   * @param {string} name - Name of parameter to undeclare.
1870
   * @return {undefined} -
1871
   */
1872
  undeclareParameter(name) {
1873
    if (!this.hasParameter(name)) return;
1!
1874

1875
    const descriptor = this.getParameterDescriptor(name);
1✔
1876
    if (descriptor.readOnly) {
1!
UNCOV
1877
      throw new Error(
×
1878
        `${name} parameter is read-only and can not be undeclared`
1879
      );
1880
    }
1881

1882
    this._parameters.delete(name);
1✔
1883
    this._parameterDescriptors.delete(name);
1✔
1884
  }
1885

1886
  /**
1887
   * Determine if a parameter has been declared.
1888
   * @param {string} name - name of parameter
1889
   * @returns {boolean} - Return true if parameter is declared; false otherwise.
1890
   */
1891
  hasParameter(name) {
1892
    return this._parameters.has(name);
6,182✔
1893
  }
1894

1895
  /**
1896
   * Get a declared parameter by name.
1897
   *
1898
   * If unable to locate a declared parameter then a
1899
   * parameter with type == PARAMETER_NOT_SET is returned.
1900
   *
1901
   * @param {string} name - The name of the parameter.
1902
   * @return {Parameter} - The parameter.
1903
   */
1904
  getParameter(name) {
1905
    return this.getParameters([name])[0];
1,844✔
1906
  }
1907

1908
  /**
1909
   * Get a list of parameters.
1910
   *
1911
   * Find and return the declared parameters.
1912
   * If no names are provided return all declared parameters.
1913
   *
1914
   * If unable to locate a declared parameter then a
1915
   * parameter with type == PARAMETER_NOT_SET is returned in
1916
   * it's place.
1917
   *
1918
   * @param {string[]} [names] - The names of the declared parameters
1919
   *    to find or null indicating to return all declared parameters.
1920
   * @return {Parameter[]} - The parameters.
1921
   */
1922
  getParameters(names = []) {
12✔
1923
    let params = [];
4,349✔
1924

1925
    if (names.length == 0) {
4,349✔
1926
      // get all parameters
1927
      params = [...this._parameters.values()];
12✔
1928
      return params;
12✔
1929
    }
1930

1931
    for (const name of names) {
4,337✔
1932
      const param = this.hasParameter(name)
4,344✔
1933
        ? this._parameters.get(name)
1934
        : new Parameter(name, ParameterType.PARAMETER_NOT_SET);
1935

1936
      params.push(param);
4,344✔
1937
    }
1938

1939
    return params;
4,337✔
1940
  }
1941

1942
  /**
1943
   * Get the types of given parameters.
1944
   *
1945
   * Return the types of given parameters.
1946
   *
1947
   * @param {string[]} [names] - The names of the declared parameters.
1948
   * @return {Uint8Array} - The types.
1949
   */
1950
  getParameterTypes(names = []) {
×
1951
    let types = [];
3✔
1952

1953
    for (const name of names) {
3✔
1954
      const descriptor = this._parameterDescriptors.get(name);
7✔
1955
      if (descriptor) {
7!
1956
        types.push(descriptor.type);
7✔
1957
      }
1958
    }
1959
    return types;
3✔
1960
  }
1961

1962
  /**
1963
   * Get the names of all declared parameters.
1964
   *
1965
   * @return {Array<string>} - The declared parameter names or empty array if
1966
   *    no parameters have been declared.
1967
   */
1968
  getParameterNames() {
1969
    return this.getParameters().map((param) => param.name);
53✔
1970
  }
1971

1972
  /**
1973
   * Determine if a parameter descriptor exists.
1974
   *
1975
   * @param {string} name - The name of a descriptor to for.
1976
   * @return {boolean} - true if a descriptor has been declared; otherwise false.
1977
   */
1978
  hasParameterDescriptor(name) {
1979
    return !!this.getParameterDescriptor(name);
2,506✔
1980
  }
1981

1982
  /**
1983
   * Get a declared parameter descriptor by name.
1984
   *
1985
   * If unable to locate a declared parameter descriptor then a
1986
   * descriptor with type == PARAMETER_NOT_SET is returned.
1987
   *
1988
   * @param {string} name - The name of the parameter descriptor to find.
1989
   * @return {ParameterDescriptor} - The parameter descriptor.
1990
   */
1991
  getParameterDescriptor(name) {
1992
    return this.getParameterDescriptors([name])[0];
5,013✔
1993
  }
1994

1995
  /**
1996
   * Find a list of declared ParameterDescriptors.
1997
   *
1998
   * If no names are provided return all declared descriptors.
1999
   *
2000
   * If unable to locate a declared descriptor then a
2001
   * descriptor with type == PARAMETER_NOT_SET is returned in
2002
   * it's place.
2003
   *
2004
   * @param {string[]} [names] - The names of the declared parameter
2005
   *    descriptors to find or null indicating to return all declared descriptors.
2006
   * @return {ParameterDescriptor[]} - The parameter descriptors.
2007
   */
2008
  getParameterDescriptors(names = []) {
×
2009
    let descriptors = [];
5,016✔
2010

2011
    if (names.length == 0) {
5,016!
2012
      // get all parameters
UNCOV
2013
      descriptors = [...this._parameterDescriptors.values()];
×
2014
      return descriptors;
×
2015
    }
2016

2017
    for (const name of names) {
5,016✔
2018
      let descriptor = this._parameterDescriptors.get(name);
5,018✔
2019
      if (!descriptor) {
5,018!
UNCOV
2020
        descriptor = new ParameterDescriptor(
×
2021
          name,
2022
          ParameterType.PARAMETER_NOT_SET
2023
        );
2024
      }
2025
      descriptors.push(descriptor);
5,018✔
2026
    }
2027

2028
    return descriptors;
5,016✔
2029
  }
2030

2031
  /**
2032
   * Replace a declared parameter.
2033
   *
2034
   * The parameter being replaced must be a declared parameter who's descriptor
2035
   * is not readOnly; otherwise an Error is thrown.
2036
   *
2037
   * @param {Parameter} parameter - The new parameter.
2038
   * @return {rclnodejs.rcl_interfaces.msg.SetParameterResult} - The result of the operation.
2039
   */
2040
  setParameter(parameter) {
2041
    const results = this.setParameters([parameter]);
26✔
2042
    return results[0];
26✔
2043
  }
2044

2045
  /**
2046
   * Replace a list of declared parameters.
2047
   *
2048
   * Declared parameters are replaced in the order they are provided and
2049
   * a ParameterEvent is published for each individual parameter change.
2050
   *
2051
   * Prior to setting the parameters each SetParameterEventCallback registered
2052
   * using setOnParameterEventCallback() is called in succession with the parameters
2053
   * list. Any SetParameterEventCallback that retuns does not return a successful
2054
   * result will cause the entire operation to terminate with no changes to the
2055
   * parameters. When all SetParameterEventCallbacks return successful then the
2056
   * list of parameters is updated.
2057
   *
2058
   * If an error occurs, the process is stopped and returned. Parameters
2059
   * set before an error remain unchanged.
2060
   *
2061
   * @param {Parameter[]} parameters - The parameters to set.
2062
   * @return {rclnodejs.rcl_interfaces.msg.SetParameterResult[]} - A list of SetParameterResult, one for each parameter that was set.
2063
   */
2064
  setParameters(parameters = []) {
×
2065
    return parameters.map((parameter) =>
37✔
2066
      this.setParametersAtomically([parameter])
40✔
2067
    );
2068
  }
2069

2070
  /**
2071
   * Repalce a list of declared parameters atomically.
2072
   *
2073
   * Declared parameters are replaced in the order they are provided.
2074
   * A single ParameterEvent is published collectively for all changed
2075
   * parameters.
2076
   *
2077
   * Prior to setting the parameters each SetParameterEventCallback registered
2078
   * using setOnParameterEventCallback() is called in succession with the parameters
2079
   * list. Any SetParameterEventCallback that retuns does not return a successful
2080
   * result will cause the entire operation to terminate with no changes to the
2081
   * parameters. When all SetParameterEventCallbacks return successful then the
2082
   * list of parameters is updated.d
2083
   *
2084
   * If an error occurs, the process stops immediately. All parameters updated to
2085
   * the point of the error are reverted to their previous state.
2086
   *
2087
   * @param {Parameter[]} parameters - The parameters to set.
2088
   * @return {rclnodejs.rcl_interfaces.msg.SetParameterResult} - describes the result of setting 1 or more parameters.
2089
   */
2090
  setParametersAtomically(parameters = []) {
×
2091
    return this._setParametersAtomically(parameters);
41✔
2092
  }
2093

2094
  /**
2095
   * Internal method for updating parameters atomically.
2096
   *
2097
   * Prior to setting the parameters each SetParameterEventCallback registered
2098
   * using setOnParameterEventCallback() is called in succession with the parameters
2099
   * list. Any SetParameterEventCallback that retuns does not return a successful
2100
   * result will cause the entire operation to terminate with no changes to the
2101
   * parameters. When all SetParameterEventCallbacks return successful then the
2102
   * list of parameters is updated.
2103
   *
2104
   * @param {Paramerter[]} parameters - The parameters to update.
2105
   * @param {boolean} declareParameterMode - When true parameters are being declared;
2106
   *    otherwise they are being changed.
2107
   * @return {SetParameterResult} - A single collective result.
2108
   */
2109
  _setParametersAtomically(parameters = [], declareParameterMode = false) {
41!
2110
    // 1) PRE callbacks — pipeline: each callback receives the output of the previous
2111
    if (this._preSetParametersCallbacks.length > 0) {
2,508✔
2112
      for (const callback of this._preSetParametersCallbacks) {
6✔
2113
        const result = callback(parameters);
7✔
2114
        if (!Array.isArray(result)) {
7!
UNCOV
2115
          return {
×
2116
            successful: false,
2117
            reason:
2118
              'pre-set parameters callback must return an array of Parameters',
2119
          };
2120
        }
2121
        parameters = result;
7✔
2122
        if (parameters.length === 0) {
7✔
2123
          return {
2✔
2124
            successful: false,
2125
            reason:
2126
              'parameter list is empty after pre-set callback; set rejected',
2127
          };
2128
        }
2129
      }
2130
    }
2131

2132
    // 2) Validate
2133
    let result = this._validateParameters(parameters, declareParameterMode);
2,506✔
2134
    if (!result.successful) {
2,506!
UNCOV
2135
      return result;
×
2136
    }
2137

2138
    // give all SetParametersCallbacks a chance to veto this change
2139
    for (const callback of this._setParametersCallbacks) {
2,506✔
2140
      result = callback(parameters);
1,607✔
2141
      if (!result.successful) {
1,607✔
2142
        // a callback has vetoed a parameter change
2143
        return result;
3✔
2144
      }
2145
    }
2146

2147
    // collectively track updates to parameters for use
2148
    // when publishing a ParameterEvent
2149
    const newParameters = [];
2,503✔
2150
    const changedParameters = [];
2,503✔
2151
    const deletedParameters = [];
2,503✔
2152

2153
    for (const parameter of parameters) {
2,503✔
2154
      if (parameter.type == ParameterType.PARAMETER_NOT_SET) {
2,503✔
2155
        this.undeclareParameter(parameter.name);
1✔
2156
        deletedParameters.push(parameter);
1✔
2157
      } else {
2158
        this._parameters.set(parameter.name, parameter);
2,502✔
2159
        if (declareParameterMode) {
2,502✔
2160
          newParameters.push(parameter);
2,467✔
2161
        } else {
2162
          changedParameters.push(parameter);
35✔
2163
        }
2164
      }
2165
    }
2166

2167
    // create ParameterEvent
2168
    const parameterEvent = new (loader.loadInterface(
2,503✔
2169
      PARAMETER_EVENT_MSG_TYPE
2170
    ))();
2171

2172
    const { seconds, nanoseconds } = this._clock.now().secondsAndNanoseconds;
2,503✔
2173
    parameterEvent.stamp = {
2,503✔
2174
      sec: Number(seconds),
2175
      nanosec: Number(nanoseconds),
2176
    };
2177

2178
    parameterEvent.node =
2,503✔
2179
      this.namespace() === '/'
2,503✔
2180
        ? this.namespace() + this.name()
2181
        : this.namespace() + '/' + this.name();
2182

2183
    if (newParameters.length > 0) {
2,503✔
2184
      parameterEvent['new_parameters'] = newParameters.map((parameter) =>
2,467✔
2185
        parameter.toParameterMessage()
2,467✔
2186
      );
2187
    }
2188
    if (changedParameters.length > 0) {
2,503✔
2189
      parameterEvent['changed_parameters'] = changedParameters.map(
35✔
2190
        (parameter) => parameter.toParameterMessage()
35✔
2191
      );
2192
    }
2193
    if (deletedParameters.length > 0) {
2,503✔
2194
      parameterEvent['deleted_parameters'] = deletedParameters.map(
1✔
2195
        (parameter) => parameter.toParameterMessage()
1✔
2196
      );
2197
    }
2198

2199
    // Publish ParameterEvent.
2200
    this._parameterEventPublisher.publish(parameterEvent);
2,503✔
2201

2202
    // POST callbacks — for side effects after successful set
2203
    for (const callback of this._postSetParametersCallbacks) {
2,503✔
2204
      callback(parameters);
4✔
2205
    }
2206

2207
    return {
2,503✔
2208
      successful: true,
2209
      reason: '',
2210
    };
2211
  }
2212

2213
  /**
2214
   * This callback is called when declaring a parameter or setting a parameter.
2215
   * The callback is provided a list of parameters and returns a SetParameterResult
2216
   * to indicate approval or veto of the operation.
2217
   *
2218
   * @callback SetParametersCallback
2219
   * @param {Parameter[]} parameters - The message published
2220
   * @returns {rcl_interfaces.msg.SetParameterResult} -
2221
   *
2222
   * @see [Node.addOnSetParametersCallback]{@link Node#addOnSetParametersCallback}
2223
   * @see [Node.removeOnSetParametersCallback]{@link Node#removeOnSetParametersCallback}
2224
   */
2225

2226
  /**
2227
   * Add a callback to the front of the list of callbacks invoked for parameter declaration
2228
   * and setting. No checks are made for duplicate callbacks.
2229
   *
2230
   * @param {SetParametersCallback} callback - The callback to add.
2231
   * @returns {undefined}
2232
   */
2233
  addOnSetParametersCallback(callback) {
2234
    this._setParametersCallbacks.unshift(callback);
928✔
2235
  }
2236

2237
  /**
2238
   * Remove a callback from the list of SetParametersCallbacks.
2239
   * If the callback is not found the process is a nop.
2240
   *
2241
   * @param {SetParametersCallback} callback - The callback to be removed
2242
   * @returns {undefined}
2243
   */
2244
  removeOnSetParametersCallback(callback) {
2245
    const idx = this._setParametersCallbacks.indexOf(callback);
2✔
2246
    if (idx > -1) {
2!
2247
      this._setParametersCallbacks.splice(idx, 1);
2✔
2248
    }
2249
  }
2250

2251
  /**
2252
   * A callback invoked before parameter validation and setting.
2253
   * It receives the parameter list and must return a (possibly modified) parameter list.
2254
   *
2255
   * @callback PreSetParametersCallback
2256
   * @param {Parameter[]} parameters - The parameters about to be set.
2257
   * @returns {Parameter[]} - The modified parameter list to proceed with.
2258
   *
2259
   * @see [Node.addPreSetParametersCallback]{@link Node#addPreSetParametersCallback}
2260
   * @see [Node.removePreSetParametersCallback]{@link Node#removePreSetParametersCallback}
2261
   */
2262

2263
  /**
2264
   * Add a callback invoked before parameter validation.
2265
   * The callback receives the parameter list and must return a (possibly modified)
2266
   * parameter list. This can be used to coerce, add, or remove parameters before
2267
   * they are validated and applied. If any pre-set callback returns an empty list,
2268
   * the set is rejected.
2269
   *
2270
   * @param {PreSetParametersCallback} callback - The callback to add.
2271
   * @returns {undefined}
2272
   */
2273
  addPreSetParametersCallback(callback) {
2274
    this._preSetParametersCallbacks.unshift(callback);
8✔
2275
  }
2276

2277
  /**
2278
   * Remove a pre-set parameters callback.
2279
   *
2280
   * @param {PreSetParametersCallback} callback - The callback to remove.
2281
   * @returns {undefined}
2282
   */
2283
  removePreSetParametersCallback(callback) {
2284
    const idx = this._preSetParametersCallbacks.indexOf(callback);
1✔
2285
    if (idx > -1) {
1!
2286
      this._preSetParametersCallbacks.splice(idx, 1);
1✔
2287
    }
2288
  }
2289

2290
  /**
2291
   * A callback invoked after parameters have been successfully set.
2292
   * It receives the final parameter list. For side effects only (return value is ignored).
2293
   *
2294
   * @callback PostSetParametersCallback
2295
   * @param {Parameter[]} parameters - The parameters that were set.
2296
   * @returns {undefined}
2297
   *
2298
   * @see [Node.addPostSetParametersCallback]{@link Node#addPostSetParametersCallback}
2299
   * @see [Node.removePostSetParametersCallback]{@link Node#removePostSetParametersCallback}
2300
   */
2301

2302
  /**
2303
   * Add a callback invoked after parameters are successfully set.
2304
   * The callback receives the final parameter list. Useful for triggering
2305
   * side effects (e.g., reconfiguring a component when a parameter changes).
2306
   *
2307
   * @param {PostSetParametersCallback} callback - The callback to add.
2308
   * @returns {undefined}
2309
   */
2310
  addPostSetParametersCallback(callback) {
2311
    this._postSetParametersCallbacks.unshift(callback);
8✔
2312
  }
2313

2314
  /**
2315
   * Remove a post-set parameters callback.
2316
   *
2317
   * @param {PostSetParametersCallback} callback - The callback to remove.
2318
   * @returns {undefined}
2319
   */
2320
  removePostSetParametersCallback(callback) {
2321
    const idx = this._postSetParametersCallbacks.indexOf(callback);
1✔
2322
    if (idx > -1) {
1!
2323
      this._postSetParametersCallbacks.splice(idx, 1);
1✔
2324
    }
2325
  }
2326

2327
  /**
2328
   * Get the fully qualified name of the node.
2329
   *
2330
   * @returns {string} - String containing the fully qualified name of the node.
2331
   */
2332
  getFullyQualifiedName() {
2333
    return rclnodejs.getFullyQualifiedName(this.handle);
1✔
2334
  }
2335

2336
  /**
2337
   * Get the RMW implementation identifier
2338
   * @returns {string} - The RMW implementation identifier.
2339
   */
2340
  getRMWImplementationIdentifier() {
2341
    return rclnodejs.getRMWImplementationIdentifier();
1✔
2342
  }
2343

2344
  /**
2345
   * Return a topic name expanded and remapped.
2346
   * @param {string} topicName - Topic name to be expanded and remapped.
2347
   * @param {boolean} [onlyExpand=false] - If `true`, remapping rules won't be applied.
2348
   * @returns {string} - A fully qualified topic name.
2349
   */
2350
  resolveTopicName(topicName, onlyExpand = false) {
14✔
2351
    if (typeof topicName !== 'string') {
15!
UNCOV
2352
      throw new TypeValidationError('topicName', topicName, 'string', {
×
2353
        nodeName: this.name(),
2354
      });
2355
    }
2356
    return rclnodejs.resolveName(
15✔
2357
      this.handle,
2358
      topicName,
2359
      onlyExpand,
2360
      /*isService=*/ false
2361
    );
2362
  }
2363

2364
  /**
2365
   * Return a service name expanded and remapped.
2366
   * @param {string} service - Service name to be expanded and remapped.
2367
   * @param {boolean} [onlyExpand=false] - If `true`, remapping rules won't be applied.
2368
   * @returns {string} - A fully qualified service name.
2369
   */
2370
  resolveServiceName(service, onlyExpand = false) {
6✔
2371
    if (typeof service !== 'string') {
7!
UNCOV
2372
      throw new TypeValidationError('service', service, 'string', {
×
2373
        nodeName: this.name(),
2374
      });
2375
    }
2376
    return rclnodejs.resolveName(
7✔
2377
      this.handle,
2378
      service,
2379
      onlyExpand,
2380
      /*isService=*/ true
2381
    );
2382
  }
2383

2384
  // returns on 1st error or result {successful, reason}
2385
  _validateParameters(parameters = [], declareParameterMode = false) {
×
2386
    for (const parameter of parameters) {
2,506✔
2387
      // detect invalid parameter
2388
      try {
2,506✔
2389
        parameter.validate();
2,506✔
2390
      } catch {
UNCOV
2391
        return {
×
2392
          successful: false,
2393
          reason: `Invalid ${parameter.name}`,
2394
        };
2395
      }
2396

2397
      // detect undeclared parameter
2398
      if (!this.hasParameterDescriptor(parameter.name)) {
2,506!
UNCOV
2399
        return {
×
2400
          successful: false,
2401
          reason: `Parameter ${parameter.name} has not been declared`,
2402
        };
2403
      }
2404

2405
      // detect readonly parameter that can not be updated
2406
      const descriptor = this.getParameterDescriptor(parameter.name);
2,506✔
2407
      if (!declareParameterMode && descriptor.readOnly) {
2,506!
UNCOV
2408
        return {
×
2409
          successful: false,
2410
          reason: `Parameter ${parameter.name} is readonly`,
2411
        };
2412
      }
2413

2414
      // validate parameter against descriptor if not an undeclare action
2415
      if (parameter.type != ParameterType.PARAMETER_NOT_SET) {
2,506✔
2416
        try {
2,505✔
2417
          descriptor.validateParameter(parameter);
2,505✔
2418
        } catch {
UNCOV
2419
          return {
×
2420
            successful: false,
2421
            reason: `Parameter ${parameter.name} does not  readonly`,
2422
          };
2423
        }
2424
      }
2425
    }
2426

2427
    return {
2,506✔
2428
      successful: true,
2429
      reason: null,
2430
    };
2431
  }
2432

2433
  // Get a Map(nodeName->Parameter[]) of CLI parameter args that
2434
  // apply to 'this' node, .e.g., -p mynode:foo:=bar -p hello:=world
2435
  _getNativeParameterOverrides() {
2436
    const overrides = new Map();
907✔
2437

2438
    // Get native parameters from rcl context->global_arguments.
2439
    // rclnodejs returns an array of objects, 1 for each node e.g., -p my_node:foo:=bar,
2440
    // and a node named '/**' for global parameter rules,
2441
    // i.e., does not include a node identifier, e.g., -p color:=red
2442
    // {
2443
    //   name: string // node name
2444
    //   parameters[] = {
2445
    //     name: string
2446
    //     type: uint
2447
    //     value: object
2448
    // }
2449
    const cliParamOverrideData = rclnodejs.getParameterOverrides(
907✔
2450
      this.context.handle
2451
    );
2452

2453
    // convert native CLI parameterOverrides to Map<nodeName,Array<ParameterOverride>>
2454
    const cliParamOverrides = new Map();
907✔
2455
    if (cliParamOverrideData) {
907✔
2456
      for (let nodeParamData of cliParamOverrideData) {
8✔
2457
        const nodeName = nodeParamData.name;
12✔
2458
        const nodeParamOverrides = [];
12✔
2459
        for (let paramData of nodeParamData.parameters) {
12✔
2460
          const paramOverride = new Parameter(
17✔
2461
            paramData.name,
2462
            paramData.type,
2463
            paramData.value
2464
          );
2465
          nodeParamOverrides.push(paramOverride);
17✔
2466
        }
2467
        cliParamOverrides.set(nodeName, nodeParamOverrides);
12✔
2468
      }
2469
    }
2470

2471
    // collect global CLI global parameters, name == /**
2472
    let paramOverrides = cliParamOverrides.get('/**'); // array of ParameterOverrides
907✔
2473
    if (paramOverrides) {
907✔
2474
      for (const parameter of paramOverrides) {
5✔
2475
        overrides.set(parameter.name, parameter);
6✔
2476
      }
2477
    }
2478

2479
    // merge CLI node parameterOverrides with global parameterOverrides, replace existing
2480
    paramOverrides = cliParamOverrides.get(this.name()); // array of ParameterOverrides
907✔
2481
    if (paramOverrides) {
907✔
2482
      for (const parameter of paramOverrides) {
5✔
2483
        overrides.set(parameter.name, parameter);
7✔
2484
      }
2485
    }
2486

2487
    return overrides;
907✔
2488
  }
2489

2490
  /**
2491
   * Invokes the callback with a raw message of the given type. After the callback completes
2492
   * the message will be destroyed.
2493
   * @param {function} Type - Message type to create.
2494
   * @param {function} callback - Callback to invoke. First parameter will be the raw message,
2495
   * and the second is a function to retrieve the deserialized message.
2496
   * @returns {undefined}
2497
   */
2498
  _runWithMessageType(Type, callback) {
2499
    let message = new Type();
867✔
2500

2501
    callback(message.toRawROS(), () => {
867✔
2502
      let result = new Type();
818✔
2503
      result.deserialize(message.refObject);
818✔
2504

2505
      return result;
818✔
2506
    });
2507

2508
    Type.destroyRawROS(message);
867✔
2509
  }
2510

2511
  _addActionClient(actionClient) {
2512
    this._actionClients.push(actionClient);
52✔
2513
    this.syncHandles();
52✔
2514
  }
2515

2516
  _addActionServer(actionServer) {
2517
    this._actionServers.push(actionServer);
52✔
2518
    this.syncHandles();
52✔
2519
  }
2520

2521
  _getValidatedTopic(topicName, noDemangle) {
2522
    if (noDemangle) {
6!
UNCOV
2523
      return topicName;
×
2524
    }
2525
    const fqTopicName = rclnodejs.expandTopicName(
6✔
2526
      topicName,
2527
      this.name(),
2528
      this.namespace()
2529
    );
2530
    validateFullTopicName(fqTopicName);
6✔
2531
    return rclnodejs.remapTopicName(this.handle, fqTopicName);
6✔
2532
  }
2533

2534
  _getValidatedServiceName(serviceName, noDemangle) {
2535
    if (typeof serviceName !== 'string') {
4!
UNCOV
2536
      throw new TypeValidationError('serviceName', serviceName, 'string', {
×
2537
        nodeName: this.name(),
2538
      });
2539
    }
2540

2541
    if (noDemangle) {
4!
UNCOV
2542
      return serviceName;
×
2543
    }
2544

2545
    const resolvedServiceName = this.resolveServiceName(serviceName);
4✔
2546
    rclnodejs.validateTopicName(resolvedServiceName);
4✔
2547
    return resolvedServiceName;
4✔
2548
  }
2549
}
2550

2551
/**
2552
 * Create an Options instance initialized with default values.
2553
 * @returns {Options} - The new initialized instance.
2554
 * @static
2555
 * @example
2556
 * {
2557
 *   enableTypedArray: true,
2558
 *   isRaw: false,
2559
 *   qos: QoS.profileDefault,
2560
 *   contentFilter: undefined,
2561
 *   serializationMode: 'default',
2562
 * }
2563
 */
2564
Node.getDefaultOptions = function () {
26✔
2565
  return {
8,322✔
2566
    enableTypedArray: true,
2567
    isRaw: false,
2568
    qos: QoS.profileDefault,
2569
    contentFilter: undefined,
2570
    serializationMode: 'default',
2571
  };
2572
};
2573

2574
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