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

RobotWebTools / rclnodejs / 19030223619

03 Nov 2025 09:41AM UTC coverage: 82.737% (+0.7%) from 82.083%
19030223619

Pull #1318

github

web-flow
Merge 14ed2c5f1 into 7c306aa60
Pull Request #1318: feat: add ParameterClient for external parameter access

997 of 1326 branches covered (75.19%)

Branch coverage included in aggregate %.

148 of 179 new or added lines in 3 files covered. (82.68%)

2 existing lines in 1 file now uncovered.

2262 of 2613 relevant lines covered (86.57%)

516.54 hits per line

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

88.05
/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 ParameterService = require('./parameter_service.js');
26✔
36
const ParameterClient = require('./parameter_client.js');
26✔
37
const Publisher = require('./publisher.js');
26✔
38
const QoS = require('./qos.js');
26✔
39
const Rates = require('./rate.js');
26✔
40
const Service = require('./service.js');
26✔
41
const Subscription = require('./subscription.js');
26✔
42
const TimeSource = require('./time_source.js');
26✔
43
const Timer = require('./timer.js');
26✔
44
const TypeDescriptionService = require('./type_description_service.js');
26✔
45
const Entity = require('./entity.js');
26✔
46
const { SubscriptionEventCallbacks } = require('../lib/event_handler.js');
26✔
47
const { PublisherEventCallbacks } = require('../lib/event_handler.js');
26✔
48
const { validateFullTopicName } = require('./validator.js');
26✔
49

50
// Parameter event publisher constants
51
const PARAMETER_EVENT_MSG_TYPE = 'rcl_interfaces/msg/ParameterEvent';
26✔
52
const PARAMETER_EVENT_TOPIC = 'parameter_events';
26✔
53

54
/**
55
 * @class - Class representing a Node in ROS
56
 */
57

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

78
    if (typeof nodeName !== 'string' || typeof namespace !== 'string') {
721✔
79
      throw new TypeError('Invalid argument.');
22✔
80
    }
81

82
    this._init(nodeName, namespace, options, context, args, useGlobalArguments);
699✔
83
    debug(
690✔
84
      'Finish initializing node, name = %s and namespace = %s.',
85
      nodeName,
86
      namespace
87
    );
88
  }
89

90
  _init(name, namespace, options, context, args, useGlobalArguments) {
91
    this.handle = rclnodejs.createNode(
699✔
92
      name,
93
      namespace,
94
      context.handle,
95
      args,
96
      useGlobalArguments
97
    );
98
    Object.defineProperty(this, 'handle', {
690✔
99
      configurable: false,
100
      writable: false,
101
    }); // make read-only
102

103
    this._context = context;
690✔
104
    this.context.onNodeCreated(this);
690✔
105

106
    this._publishers = [];
690✔
107
    this._subscriptions = [];
690✔
108
    this._clients = [];
690✔
109
    this._services = [];
690✔
110
    this._timers = [];
690✔
111
    this._guards = [];
690✔
112
    this._events = [];
690✔
113
    this._actionClients = [];
690✔
114
    this._actionServers = [];
690✔
115
    this._parameterClients = [];
690✔
116
    this._rateTimerServer = null;
690✔
117
    this._parameterDescriptors = new Map();
690✔
118
    this._parameters = new Map();
690✔
119
    this._parameterService = null;
690✔
120
    this._typeDescriptionService = null;
690✔
121
    this._parameterEventPublisher = null;
690✔
122
    this._setParametersCallbacks = [];
690✔
123
    this._logger = new Logging(rclnodejs.getNodeLoggerName(this.handle));
690✔
124
    this._spinning = false;
690✔
125

126
    this._parameterEventPublisher = this.createPublisher(
690✔
127
      PARAMETER_EVENT_MSG_TYPE,
128
      PARAMETER_EVENT_TOPIC
129
    );
130

131
    // initialize _parameterOverrides from parameters defined on the commandline
132
    this._parameterOverrides = this._getNativeParameterOverrides();
690✔
133

134
    // override cli parameterOverrides with those specified in options
135
    if (options.parameterOverrides.length > 0) {
690✔
136
      for (const parameter of options.parameterOverrides) {
8✔
137
        if ((!parameter) instanceof Parameter) {
13!
138
          throw new TypeError(
×
139
            'Parameter-override must be an instance of Parameter.'
140
          );
141
        }
142
        this._parameterOverrides.set(parameter.name, parameter);
13✔
143
      }
144
    }
145

146
    // initialize _parameters from parameterOverrides
147
    if (options.automaticallyDeclareParametersFromOverrides) {
690✔
148
      for (const parameter of this._parameterOverrides.values()) {
5✔
149
        parameter.validate();
10✔
150
        const descriptor = ParameterDescriptor.fromParameter(parameter);
10✔
151
        this._parameters.set(parameter.name, parameter);
10✔
152
        this._parameterDescriptors.set(parameter.name, descriptor);
10✔
153
      }
154
    }
155

156
    // Clock that has support for ROS time.
157
    // Note: parameter overrides and parameter event publisher need to be ready at this point
158
    // to be able to declare 'use_sim_time' if it was not declared yet.
159
    this._clock = new Clock.ROSClock();
690✔
160
    this._timeSource = new TimeSource(this);
690✔
161
    this._timeSource.attachClock(this._clock);
690✔
162

163
    if (options.startParameterServices) {
690✔
164
      this._parameterService = new ParameterService(this);
684✔
165
      this._parameterService.start();
684✔
166
    }
167

168
    if (
690!
169
      DistroUtils.getDistroId() >= DistroUtils.getDistroId('jazzy') &&
1,380✔
170
      options.startTypeDescriptionService
171
    ) {
172
      this._typeDescriptionService = new TypeDescriptionService(this);
690✔
173
      this._typeDescriptionService.start();
690✔
174
    }
175
  }
176

177
  execute(handles) {
178
    let timersReady = this._timers.filter((timer) =>
8,178✔
179
      handles.includes(timer.handle)
7,287✔
180
    );
181
    let guardsReady = this._guards.filter((guard) =>
8,178✔
182
      handles.includes(guard.handle)
3✔
183
    );
184
    let subscriptionsReady = this._subscriptions.filter((subscription) =>
8,178✔
185
      handles.includes(subscription.handle)
467✔
186
    );
187
    let clientsReady = this._clients.filter((client) =>
8,178✔
188
      handles.includes(client.handle)
287✔
189
    );
190
    let servicesReady = this._services.filter((service) =>
8,178✔
191
      handles.includes(service.handle)
39,413✔
192
    );
193
    let actionClientsReady = this._actionClients.filter((actionClient) =>
8,178✔
194
      handles.includes(actionClient.handle)
158✔
195
    );
196
    let actionServersReady = this._actionServers.filter((actionServer) =>
8,178✔
197
      handles.includes(actionServer.handle)
158✔
198
    );
199
    let eventsReady = this._events.filter((event) =>
8,178✔
200
      handles.includes(event.handle)
4✔
201
    );
202

203
    timersReady.forEach((timer) => {
8,178✔
204
      if (timer.isReady()) {
7,277✔
205
        rclnodejs.callTimer(timer.handle);
7,219✔
206
        timer.callback();
7,219✔
207
      }
208
    });
209

210
    eventsReady.forEach((event) => {
8,178✔
211
      event.takeData();
4✔
212
    });
213

214
    for (const subscription of subscriptionsReady) {
8,178✔
215
      if (subscription.isDestroyed()) continue;
425✔
216
      if (subscription.isRaw) {
414✔
217
        let rawMessage = rclnodejs.rclTakeRaw(subscription.handle);
1✔
218
        if (rawMessage) {
1!
219
          subscription.processResponse(rawMessage);
1✔
220
        }
221
        continue;
1✔
222
      }
223

224
      this._runWithMessageType(
413✔
225
        subscription.typeClass,
226
        (message, deserialize) => {
227
          let success = rclnodejs.rclTake(subscription.handle, message);
413✔
228
          if (success) {
413✔
229
            subscription.processResponse(deserialize());
356✔
230
          }
231
        }
232
      );
233
    }
234

235
    for (const guard of guardsReady) {
8,178✔
236
      if (guard.isDestroyed()) continue;
3!
237

238
      guard.callback();
3✔
239
    }
240

241
    for (const client of clientsReady) {
8,178✔
242
      if (client.isDestroyed()) continue;
155!
243
      this._runWithMessageType(
155✔
244
        client.typeClass.Response,
245
        (message, deserialize) => {
246
          let sequenceNumber = rclnodejs.rclTakeResponse(
155✔
247
            client.handle,
248
            message
249
          );
250
          if (sequenceNumber !== undefined) {
155✔
251
            client.processResponse(sequenceNumber, deserialize());
84✔
252
          }
253
        }
254
      );
255
    }
256

257
    for (const service of servicesReady) {
8,178✔
258
      if (service.isDestroyed()) continue;
172!
259
      this._runWithMessageType(
172✔
260
        service.typeClass.Request,
261
        (message, deserialize) => {
262
          let header = rclnodejs.rclTakeRequest(
172✔
263
            service.handle,
264
            this.handle,
265
            message
266
          );
267
          if (header) {
172✔
268
            service.processRequest(header, deserialize());
89✔
269
          }
270
        }
271
      );
272
    }
273

274
    for (const actionClient of actionClientsReady) {
8,178✔
275
      if (actionClient.isDestroyed()) continue;
74!
276

277
      const properties = actionClient.handle.properties;
74✔
278

279
      if (properties.isGoalResponseReady) {
74✔
280
        this._runWithMessageType(
35✔
281
          actionClient.typeClass.impl.SendGoalService.Response,
282
          (message, deserialize) => {
283
            let sequence = rclnodejs.actionTakeGoalResponse(
35✔
284
              actionClient.handle,
285
              message
286
            );
287
            if (sequence != undefined) {
35✔
288
              actionClient.processGoalResponse(sequence, deserialize());
30✔
289
            }
290
          }
291
        );
292
      }
293

294
      if (properties.isCancelResponseReady) {
74✔
295
        this._runWithMessageType(
4✔
296
          actionClient.typeClass.impl.CancelGoal.Response,
297
          (message, deserialize) => {
298
            let sequence = rclnodejs.actionTakeCancelResponse(
4✔
299
              actionClient.handle,
300
              message
301
            );
302
            if (sequence != undefined) {
4!
303
              actionClient.processCancelResponse(sequence, deserialize());
4✔
304
            }
305
          }
306
        );
307
      }
308

309
      if (properties.isResultResponseReady) {
74✔
310
        this._runWithMessageType(
15✔
311
          actionClient.typeClass.impl.GetResultService.Response,
312
          (message, deserialize) => {
313
            let sequence = rclnodejs.actionTakeResultResponse(
15✔
314
              actionClient.handle,
315
              message
316
            );
317
            if (sequence != undefined) {
15!
318
              actionClient.processResultResponse(sequence, deserialize());
15✔
319
            }
320
          }
321
        );
322
      }
323

324
      if (properties.isFeedbackReady) {
74✔
325
        this._runWithMessageType(
7✔
326
          actionClient.typeClass.impl.FeedbackMessage,
327
          (message, deserialize) => {
328
            let success = rclnodejs.actionTakeFeedback(
7✔
329
              actionClient.handle,
330
              message
331
            );
332
            if (success) {
7✔
333
              actionClient.processFeedbackMessage(deserialize());
5✔
334
            }
335
          }
336
        );
337
      }
338

339
      if (properties.isStatusReady) {
74✔
340
        this._runWithMessageType(
38✔
341
          actionClient.typeClass.impl.GoalStatusArray,
342
          (message, deserialize) => {
343
            let success = rclnodejs.actionTakeStatus(
38✔
344
              actionClient.handle,
345
              message
346
            );
347
            if (success) {
38✔
348
              actionClient.processStatusMessage(deserialize());
34✔
349
            }
350
          }
351
        );
352
      }
353
    }
354

355
    for (const actionServer of actionServersReady) {
8,178✔
356
      if (actionServer.isDestroyed()) continue;
94!
357

358
      const properties = actionServer.handle.properties;
94✔
359

360
      if (properties.isGoalRequestReady) {
94✔
361
        this._runWithMessageType(
31✔
362
          actionServer.typeClass.impl.SendGoalService.Request,
363
          (message, deserialize) => {
364
            const result = rclnodejs.actionTakeGoalRequest(
31✔
365
              actionServer.handle,
366
              message
367
            );
368
            if (result) {
31✔
369
              actionServer.processGoalRequest(result, deserialize());
30✔
370
            }
371
          }
372
        );
373
      }
374

375
      if (properties.isCancelRequestReady) {
94✔
376
        this._runWithMessageType(
5✔
377
          actionServer.typeClass.impl.CancelGoal.Request,
378
          (message, deserialize) => {
379
            const result = rclnodejs.actionTakeCancelRequest(
5✔
380
              actionServer.handle,
381
              message
382
            );
383
            if (result) {
5✔
384
              actionServer.processCancelRequest(result, deserialize());
4✔
385
            }
386
          }
387
        );
388
      }
389

390
      if (properties.isResultRequestReady) {
94✔
391
        this._runWithMessageType(
21✔
392
          actionServer.typeClass.impl.GetResultService.Request,
393
          (message, deserialize) => {
394
            const result = rclnodejs.actionTakeResultRequest(
21✔
395
              actionServer.handle,
396
              message
397
            );
398
            if (result) {
21✔
399
              actionServer.processResultRequest(result, deserialize());
15✔
400
            }
401
          }
402
        );
403
      }
404

405
      if (properties.isGoalExpired) {
94✔
406
        let GoalInfoArray = ActionInterfaces.GoalInfo.ArrayType;
6✔
407
        let message = new GoalInfoArray(actionServer._goalHandles.size);
6✔
408
        let count = rclnodejs.actionExpireGoals(
6✔
409
          actionServer.handle,
410
          actionServer._goalHandles.size,
411
          message._refArray.buffer
412
        );
413
        if (count > 0) {
6✔
414
          actionServer.processGoalExpired(message, count);
3✔
415
        }
416
        GoalInfoArray.freeArray(message);
6✔
417
      }
418
    }
419

420
    // At this point it is safe to clear the cache of any
421
    // destroyed entity references
422
    Entity._gcHandles();
8,178✔
423
  }
424

425
  /**
426
   * Determine if this node is spinning.
427
   * @returns {boolean} - true when spinning; otherwise returns false.
428
   */
429
  get spinning() {
430
    return this._spinning;
4,252✔
431
  }
432

433
  /**
434
   * Trigger the event loop to continuously check for and route.
435
   * incoming events.
436
   * @param {Node} node - The node to be spun up.
437
   * @param {number} [timeout=10] - Timeout to wait in milliseconds. Block forever if negative. Don't wait if 0.
438
   * @throws {Error} If the node is already spinning.
439
   * @return {undefined}
440
   */
441
  spin(timeout = 10) {
15✔
442
    if (this.spinning) {
529!
443
      throw new Error('The node is already spinning.');
×
444
    }
445
    this.start(this.context.handle, timeout);
529✔
446
    this._spinning = true;
529✔
447
  }
448

449
  /**
450
   * Use spin().
451
   * @deprecated, since 0.18.0
452
   */
453
  startSpinning(timeout) {
454
    this.spin(timeout);
×
455
  }
456

457
  /**
458
   * Terminate spinning - no further events will be received.
459
   * @returns {undefined}
460
   */
461
  stop() {
462
    super.stop();
529✔
463
    this._spinning = false;
529✔
464
  }
465

466
  /**
467
   * Terminate spinning - no further events will be received.
468
   * @returns {undefined}
469
   * @deprecated since 0.18.0, Use stop().
470
   */
471
  stopSpinning() {
472
    super.stop();
×
473
    this._spinning = false;
×
474
  }
475

476
  /**
477
   * Spin the node and trigger the event loop to check for one incoming event. Thereafter the node
478
   * will not received additional events until running additional calls to spin() or spinOnce().
479
   * @param {Node} node - The node to be spun.
480
   * @param {number} [timeout=10] - Timeout to wait in milliseconds. Block forever if negative. Don't wait if 0.
481
   * @throws {Error} If the node is already spinning.
482
   * @return {undefined}
483
   */
484
  spinOnce(timeout = 10) {
2✔
485
    if (this.spinning) {
3,009✔
486
      throw new Error('The node is already spinning.');
2✔
487
    }
488
    super.spinOnce(this.context.handle, timeout);
3,007✔
489
  }
490

491
  _removeEntityFromArray(entity, array) {
492
    let index = array.indexOf(entity);
201✔
493
    if (index > -1) {
201✔
494
      array.splice(index, 1);
199✔
495
    }
496
  }
497

498
  _destroyEntity(entity, array, syncHandles = true) {
201✔
499
    if (entity['isDestroyed'] && entity.isDestroyed()) return;
208✔
500

501
    this._removeEntityFromArray(entity, array);
200✔
502
    if (syncHandles) {
200✔
503
      this.syncHandles();
195✔
504
    }
505

506
    if (entity['_destroy']) {
200✔
507
      entity._destroy();
194✔
508
    } else {
509
      // guards and timers
510
      entity.handle.release();
6✔
511
    }
512
  }
513

514
  _validateOptions(options) {
515
    if (
6,504✔
516
      options !== undefined &&
6,570✔
517
      (options === null || typeof options !== 'object')
518
    ) {
519
      throw new TypeError('Invalid argument of options');
20✔
520
    }
521

522
    if (options === undefined) {
6,484✔
523
      return Node.getDefaultOptions();
6,461✔
524
    }
525

526
    if (options.enableTypedArray === undefined) {
23✔
527
      options = Object.assign(options, { enableTypedArray: true });
11✔
528
    }
529

530
    if (options.qos === undefined) {
23✔
531
      options = Object.assign(options, { qos: QoS.profileDefault });
12✔
532
    }
533

534
    if (options.isRaw === undefined) {
23✔
535
      options = Object.assign(options, { isRaw: false });
13✔
536
    }
537

538
    if (options.serializationMode === undefined) {
23✔
539
      options = Object.assign(options, { serializationMode: 'default' });
7✔
540
    } else if (!isValidSerializationMode(options.serializationMode)) {
16✔
541
      throw new TypeError(
1✔
542
        `Invalid serializationMode: ${options.serializationMode}. Valid modes are: 'default', 'plain', 'json'`
543
      );
544
    }
545

546
    return options;
22✔
547
  }
548

549
  /**
550
   * Create a Timer.
551
   * @param {bigint} period - The number representing period in nanoseconds.
552
   * @param {function} callback - The callback to be called when timeout.
553
   * @param {Clock} [clock] - The clock which the timer gets time from.
554
   * @return {Timer} - An instance of Timer.
555
   */
556
  createTimer(period, callback, clock = null) {
58✔
557
    if (arguments.length === 3 && !(arguments[2] instanceof Clock)) {
58!
558
      clock = null;
×
559
    } else if (arguments.length === 4) {
58!
560
      clock = arguments[3];
×
561
    }
562

563
    if (typeof period !== 'bigint' || typeof callback !== 'function') {
58✔
564
      throw new TypeError('Invalid argument');
2✔
565
    }
566

567
    const timerClock = clock || this._clock;
56✔
568
    let timerHandle = rclnodejs.createTimer(
56✔
569
      timerClock.handle,
570
      this.context.handle,
571
      period
572
    );
573
    let timer = new Timer(timerHandle, period, callback);
56✔
574
    debug('Finish creating timer, period = %d.', period);
56✔
575
    this._timers.push(timer);
56✔
576
    this.syncHandles();
56✔
577

578
    return timer;
56✔
579
  }
580

581
  /**
582
   * Create a Rate.
583
   *
584
   * @param {number} hz - The frequency of the rate timer; default is 1 hz.
585
   * @returns {Promise<Rate>} - Promise resolving to new instance of Rate.
586
   */
587
  async createRate(hz = 1) {
4✔
588
    if (typeof hz !== 'number') {
9!
589
      throw new TypeError('Invalid argument');
×
590
    }
591

592
    const MAX_RATE_HZ_IN_MILLISECOND = 1000.0;
9✔
593
    if (hz <= 0.0 || hz > MAX_RATE_HZ_IN_MILLISECOND) {
9✔
594
      throw new RangeError(
2✔
595
        `Hz must be between 0.0 and ${MAX_RATE_HZ_IN_MILLISECOND}`
596
      );
597
    }
598

599
    // lazy initialize rateTimerServer
600
    if (!this._rateTimerServer) {
7✔
601
      this._rateTimerServer = new Rates.RateTimerServer(this);
5✔
602
      await this._rateTimerServer.init();
5✔
603
    }
604

605
    const period = Math.round(1000 / hz);
7✔
606
    const timer = this._rateTimerServer.createTimer(BigInt(period) * 1000000n);
7✔
607
    const rate = new Rates.Rate(hz, timer);
7✔
608

609
    return rate;
7✔
610
  }
611

612
  /**
613
   * Create a Publisher.
614
   * @param {function|string|object} typeClass - The ROS message class,
615
        OR a string representing the message class, e.g. 'std_msgs/msg/String',
616
        OR an object representing the message class, e.g. {package: 'std_msgs', type: 'msg', name: 'String'}
617
   * @param {string} topic - The name of the topic.
618
   * @param {object} options - The options argument used to parameterize the publisher.
619
   * @param {boolean} options.enableTypedArray - The topic will use TypedArray if necessary, default: true.
620
   * @param {QoS} options.qos - ROS Middleware "quality of service" settings for the publisher, default: QoS.profileDefault.
621
   * @param {PublisherEventCallbacks} eventCallbacks - The event callbacks for the publisher.
622
   * @return {Publisher} - An instance of Publisher.
623
   */
624
  createPublisher(typeClass, topic, options, eventCallbacks) {
625
    return this._createPublisher(
1,051✔
626
      typeClass,
627
      topic,
628
      options,
629
      Publisher,
630
      eventCallbacks
631
    );
632
  }
633

634
  _createPublisher(typeClass, topic, options, publisherClass, eventCallbacks) {
635
    if (typeof typeClass === 'string' || typeof typeClass === 'object') {
1,054✔
636
      typeClass = loader.loadInterface(typeClass);
1,030✔
637
    }
638
    options = this._validateOptions(options);
1,047✔
639

640
    if (
1,047✔
641
      typeof typeClass !== 'function' ||
3,115✔
642
      typeof topic !== 'string' ||
643
      (eventCallbacks && !(eventCallbacks instanceof PublisherEventCallbacks))
644
    ) {
645
      throw new TypeError('Invalid argument');
20✔
646
    }
647

648
    let publisher = publisherClass.createPublisher(
1,027✔
649
      this,
650
      typeClass,
651
      topic,
652
      options,
653
      eventCallbacks
654
    );
655
    debug('Finish creating publisher, topic = %s.', topic);
1,017✔
656
    this._publishers.push(publisher);
1,017✔
657
    return publisher;
1,017✔
658
  }
659

660
  /**
661
   * This callback is called when a message is published
662
   * @callback SubscriptionCallback
663
   * @param {Object} message - The message published
664
   * @see [Node.createSubscription]{@link Node#createSubscription}
665
   * @see [Node.createPublisher]{@link Node#createPublisher}
666
   * @see {@link Publisher}
667
   * @see {@link Subscription}
668
   */
669

670
  /**
671
   * Create a Subscription with optional content-filtering.
672
   * @param {function|string|object} typeClass - The ROS message class,
673
        OR a string representing the message class, e.g. 'std_msgs/msg/String',
674
        OR an object representing the message class, e.g. {package: 'std_msgs', type: 'msg', name: 'String'}
675
   * @param {string} topic - The name of the topic.
676
   * @param {object} options - The options argument used to parameterize the subscription.
677
   * @param {boolean} options.enableTypedArray - The topic will use TypedArray if necessary, default: true.
678
   * @param {QoS} options.qos - ROS Middleware "quality of service" settings for the subscription, default: QoS.profileDefault.
679
   * @param {boolean} options.isRaw - The topic is serialized when true, default: false.
680
   * @param {string} [options.serializationMode='default'] - Controls message serialization format:
681
   *  'default': Use native rclnodejs behavior (respects enableTypedArray setting),
682
   *  'plain': Convert TypedArrays to regular arrays,
683
   *  'json': Fully JSON-safe (handles TypedArrays, BigInt, etc.).
684
   * @param {object} [options.contentFilter=undefined] - The content-filter, default: undefined.
685
   *  Confirm that your RMW supports content-filtered topics before use. 
686
   * @param {string} options.contentFilter.expression - Specifies the criteria to select the data samples of
687
   *  interest. It is similar to the WHERE part of an SQL clause.
688
   * @param {string[]} [options.contentFilter.parameters=undefined] - Array of strings that give values to
689
   *  the ‘parameters’ (i.e., "%n" tokens) in the filter_expression. The number of supplied parameters must
690
   *  fit with the requested values in the filter_expression (i.e., the number of %n tokens). default: undefined.
691
   * @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.
692
   * @param {SubscriptionEventCallbacks} eventCallbacks - The event callbacks for the subscription.
693
   * @return {Subscription} - An instance of Subscription.
694
   * @throws {ERROR} - May throw an RMW error if content-filter is malformed. 
695
   * @see {@link SubscriptionCallback}
696
   * @see {@link https://www.omg.org/spec/DDS/1.4/PDF|Content-filter details at DDS 1.4 specification, Annex B}
697
   */
698
  createSubscription(typeClass, topic, options, callback, eventCallbacks) {
699
    if (typeof typeClass === 'string' || typeof typeClass === 'object') {
386✔
700
      typeClass = loader.loadInterface(typeClass);
377✔
701
    }
702

703
    if (typeof options === 'function') {
379✔
704
      callback = options;
339✔
705
      options = undefined;
339✔
706
    }
707
    options = this._validateOptions(options);
379✔
708

709
    if (
369✔
710
      typeof typeClass !== 'function' ||
1,456✔
711
      typeof topic !== 'string' ||
712
      typeof callback !== 'function' ||
713
      (eventCallbacks &&
714
        !(eventCallbacks instanceof SubscriptionEventCallbacks))
715
    ) {
716
      throw new TypeError('Invalid argument');
10✔
717
    }
718

719
    let subscription = Subscription.createSubscription(
359✔
720
      this,
721
      typeClass,
722
      topic,
723
      options,
724
      callback,
725
      eventCallbacks
726
    );
727
    debug('Finish creating subscription, topic = %s.', topic);
348✔
728
    this._subscriptions.push(subscription);
348✔
729
    this.syncHandles();
348✔
730

731
    return subscription;
348✔
732
  }
733

734
  /**
735
   * Create a Client.
736
   * @param {function|string|object} typeClass - The ROS message class,
737
        OR a string representing the message class, e.g. 'std_msgs/msg/String',
738
        OR an object representing the message class, e.g. {package: 'std_msgs', type: 'msg', name: 'String'}
739
   * @param {string} serviceName - The service name to request.
740
   * @param {object} options - The options argument used to parameterize the client.
741
   * @param {boolean} options.enableTypedArray - The response will use TypedArray if necessary, default: true.
742
   * @param {QoS} options.qos - ROS Middleware "quality of service" settings for the client, default: QoS.profileDefault.
743
   * @return {Client} - An instance of Client.
744
   */
745
  createClient(typeClass, serviceName, options) {
746
    if (typeof typeClass === 'string' || typeof typeClass === 'object') {
150✔
747
      typeClass = loader.loadInterface(typeClass);
140✔
748
    }
749
    options = this._validateOptions(options);
143✔
750

751
    if (typeof typeClass !== 'function' || typeof serviceName !== 'string') {
143✔
752
      throw new TypeError('Invalid argument');
20✔
753
    }
754

755
    let client = Client.createClient(
123✔
756
      this.handle,
757
      serviceName,
758
      typeClass,
759
      options
760
    );
761
    debug('Finish creating client, service = %s.', serviceName);
113✔
762
    this._clients.push(client);
113✔
763
    this.syncHandles();
113✔
764

765
    return client;
113✔
766
  }
767

768
  /**
769
   * This callback is called when a request is sent to service
770
   * @callback RequestCallback
771
   * @param {Object} request - The request sent to the service
772
   * @param {Response} response - The response to client.
773
        Use [response.send()]{@link Response#send} to send response object to client
774
   * @return {undefined}
775
   * @see [Node.createService]{@link Node#createService}
776
   * @see [Client.sendRequest]{@link Client#sendRequest}
777
   * @see {@link Client}
778
   * @see {@link Service}
779
   * @see {@link Response#send}
780
   */
781

782
  /**
783
   * Create a Service.
784
   * @param {function|string|object} typeClass - The ROS message class,
785
        OR a string representing the message class, e.g. 'std_msgs/msg/String',
786
        OR an object representing the message class, e.g. {package: 'std_msgs', type: 'msg', name: 'String'}
787
   * @param {string} serviceName - The service name to offer.
788
   * @param {object} options - The options argument used to parameterize the service.
789
   * @param {boolean} options.enableTypedArray - The request will use TypedArray if necessary, default: true.
790
   * @param {QoS} options.qos - ROS Middleware "quality of service" settings for the service, default: QoS.profileDefault.
791
   * @param {RequestCallback} callback - The callback to be called when receiving request.
792
   * @return {Service} - An instance of Service.
793
   * @see {@link RequestCallback}
794
   */
795
  createService(typeClass, serviceName, options, callback) {
796
    if (typeof typeClass === 'string' || typeof typeClass === 'object') {
4,176✔
797
      typeClass = loader.loadInterface(typeClass);
4,166✔
798
    }
799

800
    if (typeof options === 'function') {
4,169✔
801
      callback = options;
4,146✔
802
      options = undefined;
4,146✔
803
    }
804
    options = this._validateOptions(options);
4,169✔
805

806
    if (
4,159✔
807
      typeof typeClass !== 'function' ||
12,463✔
808
      typeof serviceName !== 'string' ||
809
      typeof callback !== 'function'
810
    ) {
811
      throw new TypeError('Invalid argument');
10✔
812
    }
813

814
    let service = Service.createService(
4,149✔
815
      this.handle,
816
      serviceName,
817
      typeClass,
818
      options,
819
      callback
820
    );
821
    debug('Finish creating service, service = %s.', serviceName);
4,139✔
822
    this._services.push(service);
4,139✔
823
    this.syncHandles();
4,139✔
824

825
    return service;
4,139✔
826
  }
827

828
  /**
829
   * Create a ParameterClient for accessing parameters on a remote node.
830
   * @param {string} remoteNodeName - The name of the remote node whose parameters to access.
831
   * @param {object} [options] - Options for parameter client.
832
   * @param {number} [options.timeout=5000] - Default timeout in milliseconds for service calls.
833
   * @return {ParameterClient} - An instance of ParameterClient.
834
   */
835
  createParameterClient(remoteNodeName, options = {}) {
50✔
836
    if (typeof remoteNodeName !== 'string' || remoteNodeName.trim() === '') {
50!
NEW
837
      throw new TypeError('Remote node name must be a non-empty string');
×
838
    }
839

840
    const parameterClient = new ParameterClient(this, remoteNodeName, options);
50✔
841
    debug(
50✔
842
      'Finish creating parameter client for remote node = %s.',
843
      remoteNodeName
844
    );
845
    this._parameterClients.push(parameterClient);
50✔
846

847
    return parameterClient;
50✔
848
  }
849

850
  /**
851
   * Create a guard condition.
852
   * @param {Function} callback - The callback to be called when the guard condition is triggered.
853
   * @return {GuardCondition} - An instance of GuardCondition.
854
   */
855
  createGuardCondition(callback) {
856
    if (typeof callback !== 'function') {
3!
857
      throw new TypeError('Invalid argument');
×
858
    }
859

860
    let guard = GuardCondition.createGuardCondition(callback, this.context);
3✔
861
    debug('Finish creating guard condition');
3✔
862
    this._guards.push(guard);
3✔
863
    this.syncHandles();
3✔
864

865
    return guard;
3✔
866
  }
867

868
  /**
869
   * Destroy all resource allocated by this node, including
870
   * <code>Timer</code>s/<code>Publisher</code>s/<code>Subscription</code>s
871
   * /<code>Client</code>s/<code>Service</code>s
872
   * @return {undefined}
873
   */
874
  destroy() {
875
    if (this.spinning) {
712✔
876
      this.stop();
526✔
877
    }
878

879
    // Action servers/clients require manual destruction due to circular reference with goal handles.
880
    this._actionClients.forEach((actionClient) => actionClient.destroy());
712✔
881
    this._actionServers.forEach((actionServer) => actionServer.destroy());
712✔
882

883
    this._parameterClients.forEach((paramClient) => paramClient.destroy());
712✔
884

885
    this.context.onNodeDestroyed(this);
712✔
886

887
    this.handle.release();
712✔
888
    this._clock = null;
712✔
889
    this._timers = [];
712✔
890
    this._publishers = [];
712✔
891
    this._subscriptions = [];
712✔
892
    this._clients = [];
712✔
893
    this._services = [];
712✔
894
    this._guards = [];
712✔
895
    this._actionClients = [];
712✔
896
    this._actionServers = [];
712✔
897
    this._parameterClients = [];
712✔
898

899
    if (this._rateTimerServer) {
712✔
900
      this._rateTimerServer.shutdown();
5✔
901
      this._rateTimerServer = null;
5✔
902
    }
903
  }
904

905
  /**
906
   * Destroy a Publisher.
907
   * @param {Publisher} publisher - The Publisher to be destroyed.
908
   * @return {undefined}
909
   */
910
  destroyPublisher(publisher) {
911
    if (!(publisher instanceof Publisher)) {
9✔
912
      throw new TypeError('Invalid argument');
2✔
913
    }
914
    if (publisher.events) {
7!
915
      publisher.events.forEach((event) => {
×
916
        this._destroyEntity(event, this._events);
×
917
      });
918
      publisher.events = [];
×
919
    }
920
    this._destroyEntity(publisher, this._publishers, false);
7✔
921
  }
922

923
  /**
924
   * Destroy a Subscription.
925
   * @param {Subscription} subscription - The Subscription to be destroyed.
926
   * @return {undefined}
927
   */
928
  destroySubscription(subscription) {
929
    if (!(subscription instanceof Subscription)) {
41✔
930
      throw new TypeError('Invalid argument');
2✔
931
    }
932
    if (subscription.events) {
39✔
933
      subscription.events.forEach((event) => {
1✔
934
        this._destroyEntity(event, this._events);
1✔
935
      });
936
      subscription.events = [];
1✔
937
    }
938

939
    this._destroyEntity(subscription, this._subscriptions);
39✔
940
  }
941

942
  /**
943
   * Destroy a Client.
944
   * @param {Client} client - The Client to be destroyed.
945
   * @return {undefined}
946
   */
947
  destroyClient(client) {
948
    if (!(client instanceof Client)) {
72✔
949
      throw new TypeError('Invalid argument');
2✔
950
    }
951
    this._destroyEntity(client, this._clients);
70✔
952
  }
953

954
  /**
955
   * Destroy a Service.
956
   * @param {Service} service - The Service to be destroyed.
957
   * @return {undefined}
958
   */
959
  destroyService(service) {
960
    if (!(service instanceof Service)) {
8✔
961
      throw new TypeError('Invalid argument');
2✔
962
    }
963
    this._destroyEntity(service, this._services);
6✔
964
  }
965

966
  /**
967
   * Destroy a ParameterClient.
968
   * @param {ParameterClient} parameterClient - The ParameterClient to be destroyed.
969
   * @return {undefined}
970
   */
971
  destroyParameterClient(parameterClient) {
972
    if (!(parameterClient instanceof ParameterClient)) {
1!
NEW
973
      throw new TypeError('Invalid argument');
×
974
    }
975
    this._removeEntityFromArray(parameterClient, this._parameterClients);
1✔
976
    parameterClient.destroy();
1✔
977
  }
978

979
  /**
980
   * Destroy a Timer.
981
   * @param {Timer} timer - The Timer to be destroyed.
982
   * @return {undefined}
983
   */
984
  destroyTimer(timer) {
985
    if (!(timer instanceof Timer)) {
8✔
986
      throw new TypeError('Invalid argument');
2✔
987
    }
988
    this._destroyEntity(timer, this._timers);
6✔
989
  }
990

991
  /**
992
   * Destroy a guard condition.
993
   * @param {GuardCondition} guard - The guard condition to be destroyed.
994
   * @return {undefined}
995
   */
996
  destroyGuardCondition(guard) {
997
    if (!(guard instanceof GuardCondition)) {
3!
998
      throw new TypeError('Invalid argument');
×
999
    }
1000
    this._destroyEntity(guard, this._guards);
3✔
1001
  }
1002

1003
  /**
1004
   * Get the name of the node.
1005
   * @return {string}
1006
   */
1007
  name() {
1008
    return rclnodejs.getNodeName(this.handle);
3,952✔
1009
  }
1010

1011
  /**
1012
   * Get the namespace of the node.
1013
   * @return {string}
1014
   */
1015
  namespace() {
1016
    return rclnodejs.getNamespace(this.handle);
3,749✔
1017
  }
1018

1019
  /**
1020
   * Get the context in which this node was created.
1021
   * @return {Context}
1022
   */
1023
  get context() {
1024
    return this._context;
5,687✔
1025
  }
1026

1027
  /**
1028
   * Get the nodes logger.
1029
   * @returns {Logger} - The logger for the node.
1030
   */
1031
  getLogger() {
1032
    return this._logger;
141✔
1033
  }
1034

1035
  /**
1036
   * Get the clock used by the node.
1037
   * @returns {Clock} - The nodes clock.
1038
   */
1039
  getClock() {
1040
    return this._clock;
86✔
1041
  }
1042

1043
  /**
1044
   * Get the current time using the node's clock.
1045
   * @returns {Timer} - The current time.
1046
   */
1047
  now() {
1048
    return this.getClock().now();
2✔
1049
  }
1050

1051
  /**
1052
   * Get the list of published topics discovered by the provided node for the remote node name.
1053
   * @param {string} nodeName - The name of the node.
1054
   * @param {string} namespace - The name of the namespace.
1055
   * @param {boolean} noDemangle - If true topic names and types returned will not be demangled, default: false.
1056
   * @return {Array<{name: string, types: Array<string>}>} - An array of the names and types.
1057
   */
1058
  getPublisherNamesAndTypesByNode(nodeName, namespace, noDemangle = false) {
×
1059
    return rclnodejs.getPublisherNamesAndTypesByNode(
×
1060
      this.handle,
1061
      nodeName,
1062
      namespace,
1063
      noDemangle
1064
    );
1065
  }
1066

1067
  /**
1068
   * Get the list of published topics discovered by the provided node for the remote node name.
1069
   * @param {string} nodeName - The name of the node.
1070
   * @param {string} namespace - The name of the namespace.
1071
   * @param {boolean} noDemangle - If true topic names and types returned will not be demangled, default: false.
1072
   * @return {Array<{name: string, types: Array<string>}>} - An array of the names and types.
1073
   */
1074
  getSubscriptionNamesAndTypesByNode(nodeName, namespace, noDemangle = false) {
×
1075
    return rclnodejs.getSubscriptionNamesAndTypesByNode(
×
1076
      this.handle,
1077
      nodeName,
1078
      namespace,
1079
      noDemangle
1080
    );
1081
  }
1082

1083
  /**
1084
   * Get service names and types for which a remote node has servers.
1085
   * @param {string} nodeName - The name of the node.
1086
   * @param {string} namespace - The name of the namespace.
1087
   * @return {Array<{name: string, types: Array<string>}>} - An array of the names and types.
1088
   */
1089
  getServiceNamesAndTypesByNode(nodeName, namespace) {
1090
    return rclnodejs.getServiceNamesAndTypesByNode(
×
1091
      this.handle,
1092
      nodeName,
1093
      namespace
1094
    );
1095
  }
1096

1097
  /**
1098
   * Get service names and types for which a remote node has clients.
1099
   * @param {string} nodeName - The name of the node.
1100
   * @param {string} namespace - The name of the namespace.
1101
   * @return {Array<{name: string, types: Array<string>}>} - An array of the names and types.
1102
   */
1103
  getClientNamesAndTypesByNode(nodeName, namespace) {
1104
    return rclnodejs.getClientNamesAndTypesByNode(
×
1105
      this.handle,
1106
      nodeName,
1107
      namespace
1108
    );
1109
  }
1110

1111
  /**
1112
   * Get the list of topics discovered by the provided node.
1113
   * @param {boolean} noDemangle - If true topic names and types returned will not be demangled, default: false.
1114
   * @return {Array<{name: string, types: Array<string>}>} - An array of the names and types.
1115
   */
1116
  getTopicNamesAndTypes(noDemangle = false) {
×
1117
    return rclnodejs.getTopicNamesAndTypes(this.handle, noDemangle);
×
1118
  }
1119

1120
  /**
1121
   * Get the list of services discovered by the provided node.
1122
   * @return {Array<{name: string, types: Array<string>}>} - An array of the names and types.
1123
   */
1124
  getServiceNamesAndTypes() {
1125
    return rclnodejs.getServiceNamesAndTypes(this.handle);
2✔
1126
  }
1127

1128
  /**
1129
   * Return a list of publishers on a given topic.
1130
   *
1131
   * The returned parameter is a list of TopicEndpointInfo objects, where each will contain
1132
   * the node name, node namespace, topic type, topic endpoint's GID, and its QoS profile.
1133
   *
1134
   * When the `no_mangle` parameter is `true`, the provided `topic` should be a valid
1135
   * topic name for the middleware (useful when combining ROS with native middleware (e.g. DDS)
1136
   * apps).  When the `no_mangle` parameter is `false`, the provided `topic` should
1137
   * follow ROS topic name conventions.
1138
   *
1139
   * `topic` may be a relative, private, or fully qualified topic name.
1140
   *  A relative or private topic will be expanded using this node's namespace and name.
1141
   *  The queried `topic` is not remapped.
1142
   *
1143
   * @param {string} topic - The topic on which to find the publishers.
1144
   * @param {boolean} [noDemangle=false] - If `true`, `topic` needs to be a valid middleware topic
1145
   *                               name, otherwise it should be a valid ROS topic name. Defaults to `false`.
1146
   * @returns {Array} - list of publishers
1147
   */
1148
  getPublishersInfoByTopic(topic, noDemangle = false) {
×
1149
    return rclnodejs.getPublishersInfoByTopic(
3✔
1150
      this.handle,
1151
      this._getValidatedTopic(topic, noDemangle),
1152
      noDemangle
1153
    );
1154
  }
1155

1156
  /**
1157
   * Return a list of subscriptions on a given topic.
1158
   *
1159
   * The returned parameter is a list of TopicEndpointInfo objects, where each will contain
1160
   * the node name, node namespace, topic type, topic endpoint's GID, and its QoS profile.
1161
   *
1162
   * When the `no_mangle` parameter is `true`, the provided `topic` should be a valid
1163
   * topic name for the middleware (useful when combining ROS with native middleware (e.g. DDS)
1164
   * apps).  When the `no_mangle` parameter is `false`, the provided `topic` should
1165
   * follow ROS topic name conventions.
1166
   *
1167
   * `topic` may be a relative, private, or fully qualified topic name.
1168
   *  A relative or private topic will be expanded using this node's namespace and name.
1169
   *  The queried `topic` is not remapped.
1170
   *
1171
   * @param {string} topic - The topic on which to find the subscriptions.
1172
   * @param {boolean} [noDemangle=false] -  If `true`, `topic` needs to be a valid middleware topic
1173
                                    name, otherwise it should be a valid ROS topic name. Defaults to `false`.
1174
   * @returns {Array} - list of subscriptions
1175
   */
1176
  getSubscriptionsInfoByTopic(topic, noDemangle = false) {
×
1177
    return rclnodejs.getSubscriptionsInfoByTopic(
2✔
1178
      this.handle,
1179
      this._getValidatedTopic(topic, noDemangle),
1180
      noDemangle
1181
    );
1182
  }
1183

1184
  /**
1185
   * Get the list of nodes discovered by the provided node.
1186
   * @return {Array<string>} - An array of the names.
1187
   */
1188
  getNodeNames() {
1189
    return this.getNodeNamesAndNamespaces().map((item) => item.name);
41✔
1190
  }
1191

1192
  /**
1193
   * Get the list of nodes and their namespaces discovered by the provided node.
1194
   * @return {Array<{name: string, namespace: string}>} An array of the names and namespaces.
1195
   */
1196
  getNodeNamesAndNamespaces() {
1197
    return rclnodejs.getNodeNames(this.handle, /*getEnclaves=*/ false);
17✔
1198
  }
1199

1200
  /**
1201
   * Get the list of nodes and their namespaces with enclaves discovered by the provided node.
1202
   * @return {Array<{name: string, namespace: string, enclave: string}>} An array of the names, namespaces and enclaves.
1203
   */
1204
  getNodeNamesAndNamespacesWithEnclaves() {
1205
    return rclnodejs.getNodeNames(this.handle, /*getEnclaves=*/ true);
1✔
1206
  }
1207

1208
  /**
1209
   * Return the number of publishers on a given topic.
1210
   * @param {string} topic - The name of the topic.
1211
   * @returns {number} - Number of publishers on the given topic.
1212
   */
1213
  countPublishers(topic) {
1214
    let expandedTopic = rclnodejs.expandTopicName(
6✔
1215
      topic,
1216
      this.name(),
1217
      this.namespace()
1218
    );
1219
    rclnodejs.validateTopicName(expandedTopic);
6✔
1220

1221
    return rclnodejs.countPublishers(this.handle, expandedTopic);
6✔
1222
  }
1223

1224
  /**
1225
   * Return the number of subscribers on a given topic.
1226
   * @param {string} topic - The name of the topic.
1227
   * @returns {number} - Number of subscribers on the given topic.
1228
   */
1229
  countSubscribers(topic) {
1230
    let expandedTopic = rclnodejs.expandTopicName(
6✔
1231
      topic,
1232
      this.name(),
1233
      this.namespace()
1234
    );
1235
    rclnodejs.validateTopicName(expandedTopic);
6✔
1236

1237
    return rclnodejs.countSubscribers(this.handle, expandedTopic);
6✔
1238
  }
1239

1240
  /**
1241
   * Get the number of clients on a given service name.
1242
   * @param {string} serviceName - the service name
1243
   * @returns {Number}
1244
   */
1245
  countClients(serviceName) {
1246
    if (DistroUtils.getDistroId() <= DistroUtils.getDistroId('humble')) {
2!
1247
      console.warn('countClients is not supported by this version of ROS 2');
×
1248
      return null;
×
1249
    }
1250
    return rclnodejs.countClients(this.handle, serviceName);
2✔
1251
  }
1252

1253
  /**
1254
   * Get the number of services on a given service name.
1255
   * @param {string} serviceName - the service name
1256
   * @returns {Number}
1257
   */
1258
  countServices(serviceName) {
1259
    if (DistroUtils.getDistroId() <= DistroUtils.getDistroId('humble')) {
1!
1260
      console.warn('countServices is not supported by this version of ROS 2');
×
1261
      return null;
×
1262
    }
1263
    return rclnodejs.countServices(this.handle, serviceName);
1✔
1264
  }
1265

1266
  /**
1267
   * Get the list of parameter-overrides found on the commandline and
1268
   * in the NodeOptions.parameter_overrides property.
1269
   *
1270
   * @return {Array<Parameter>} - An array of Parameters.
1271
   */
1272
  getParameterOverrides() {
1273
    return Array.from(this._parameterOverrides.values());
8✔
1274
  }
1275

1276
  /**
1277
   * Declare a parameter.
1278
   *
1279
   * Internally, register a parameter and it's descriptor.
1280
   * If a parameter-override exists, it's value will replace that of the parameter
1281
   * unless ignoreOverride is true.
1282
   * If the descriptor is undefined, then a ParameterDescriptor will be inferred
1283
   * from the parameter's state.
1284
   *
1285
   * If a parameter by the same name has already been declared then an Error is thrown.
1286
   * A parameter must be undeclared before attempting to redeclare it.
1287
   *
1288
   * @param {Parameter} parameter - Parameter to declare.
1289
   * @param {ParameterDescriptor} [descriptor] - Optional descriptor for parameter.
1290
   * @param {boolean} [ignoreOverride] - When true disregard any parameter-override that may be present.
1291
   * @return {Parameter} - The newly declared parameter.
1292
   */
1293
  declareParameter(parameter, descriptor, ignoreOverride = false) {
1,841✔
1294
    const parameters = this.declareParameters(
1,842✔
1295
      [parameter],
1296
      descriptor ? [descriptor] : [],
1,842✔
1297
      ignoreOverride
1298
    );
1299
    return parameters.length == 1 ? parameters[0] : null;
1,842!
1300
  }
1301

1302
  /**
1303
   * Declare a list of parameters.
1304
   *
1305
   * Internally register parameters with their corresponding descriptor one by one
1306
   * in the order they are provided. This is an atomic operation. If an error
1307
   * occurs the process halts and no further parameters are declared.
1308
   * Parameters that have already been processed are undeclared.
1309
   *
1310
   * While descriptors is an optional parameter, when provided there must be
1311
   * a descriptor for each parameter; otherwise an Error is thrown.
1312
   * If descriptors is not provided then a descriptor will be inferred
1313
   * from each parameter's state.
1314
   *
1315
   * When a parameter-override is available, the parameter's value
1316
   * will be replaced with that of the parameter-override unless ignoreOverrides
1317
   * is true.
1318
   *
1319
   * If a parameter by the same name has already been declared then an Error is thrown.
1320
   * A parameter must be undeclared before attempting to redeclare it.
1321
   *
1322
   * Prior to declaring the parameters each SetParameterEventCallback registered
1323
   * using setOnParameterEventCallback() is called in succession with the parameters
1324
   * list. Any SetParameterEventCallback that retuns does not return a successful
1325
   * result will cause the entire operation to terminate with no changes to the
1326
   * parameters. When all SetParameterEventCallbacks return successful then the
1327
   * list of parameters is updated.
1328
   *
1329
   * @param {Parameter[]} parameters - The parameters to declare.
1330
   * @param {ParameterDescriptor[]} [descriptors] - Optional descriptors,
1331
   *    a 1-1 correspondence with parameters.
1332
   * @param {boolean} ignoreOverrides - When true, parameter-overrides are
1333
   *    not considered, i.e.,ignored.
1334
   * @return {Parameter[]} - The declared parameters.
1335
   */
1336
  declareParameters(parameters, descriptors = [], ignoreOverrides = false) {
×
1337
    if (!Array.isArray(parameters)) {
1,842!
1338
      throw new TypeError('Invalid parameter: expected array of Parameter');
×
1339
    }
1340
    if (!Array.isArray(descriptors)) {
1,842!
1341
      throw new TypeError(
×
1342
        'Invalid parameters: expected array of ParameterDescriptor'
1343
      );
1344
    }
1345
    if (descriptors.length > 0 && parameters.length !== descriptors.length) {
1,842!
1346
      throw new TypeError(
×
1347
        'Each parameter must have a cooresponding ParameterDescriptor'
1348
      );
1349
    }
1350

1351
    const declaredDescriptors = [];
1,842✔
1352
    const declaredParameters = [];
1,842✔
1353
    const declaredParameterCollisions = [];
1,842✔
1354
    for (let i = 0; i < parameters.length; i++) {
1,842✔
1355
      let parameter =
1356
        !ignoreOverrides && this._parameterOverrides.has(parameters[i].name)
1,842✔
1357
          ? this._parameterOverrides.get(parameters[i].name)
1358
          : parameters[i];
1359

1360
      // stop processing parameters that have already been declared
1361
      if (this._parameters.has(parameter.name)) {
1,842!
1362
        declaredParameterCollisions.push(parameter);
×
1363
        continue;
×
1364
      }
1365

1366
      // create descriptor for parameter if not provided
1367
      let descriptor =
1368
        descriptors.length > 0
1,842✔
1369
          ? descriptors[i]
1370
          : ParameterDescriptor.fromParameter(parameter);
1371

1372
      descriptor.validate();
1,842✔
1373

1374
      declaredDescriptors.push(descriptor);
1,842✔
1375
      declaredParameters.push(parameter);
1,842✔
1376
    }
1377

1378
    if (declaredParameterCollisions.length > 0) {
1,842!
1379
      const errorMsg =
1380
        declaredParameterCollisions.length == 1
×
1381
          ? `Parameter(${declaredParameterCollisions[0]}) already declared.`
1382
          : `Multiple parameters already declared, e.g., Parameter(${declaredParameterCollisions[0]}).`;
1383
      throw new Error(errorMsg);
×
1384
    }
1385

1386
    // register descriptor
1387
    for (const descriptor of declaredDescriptors) {
1,842✔
1388
      this._parameterDescriptors.set(descriptor.name, descriptor);
1,842✔
1389
    }
1390

1391
    const result = this._setParametersAtomically(declaredParameters, true);
1,842✔
1392
    if (!result.successful) {
1,842!
1393
      // unregister descriptors
1394
      for (const descriptor of declaredDescriptors) {
×
1395
        this._parameterDescriptors.delete(descriptor.name);
×
1396
      }
1397

1398
      throw new Error(result.reason);
×
1399
    }
1400

1401
    return this.getParameters(declaredParameters.map((param) => param.name));
1,842✔
1402
  }
1403

1404
  /**
1405
   * Undeclare a parameter.
1406
   *
1407
   * Readonly parameters can not be undeclared or updated.
1408
   * @param {string} name - Name of parameter to undeclare.
1409
   * @return {undefined} -
1410
   */
1411
  undeclareParameter(name) {
1412
    if (!this.hasParameter(name)) return;
1!
1413

1414
    const descriptor = this.getParameterDescriptor(name);
1✔
1415
    if (descriptor.readOnly) {
1!
1416
      throw new Error(
×
1417
        `${name} parameter is read-only and can not be undeclared`
1418
      );
1419
    }
1420

1421
    this._parameters.delete(name);
1✔
1422
    this._parameterDescriptors.delete(name);
1✔
1423
  }
1424

1425
  /**
1426
   * Determine if a parameter has been declared.
1427
   * @param {string} name - name of parameter
1428
   * @returns {boolean} - Return true if parameter is declared; false otherwise.
1429
   */
1430
  hasParameter(name) {
1431
    return this._parameters.has(name);
4,652✔
1432
  }
1433

1434
  /**
1435
   * Get a declared parameter by name.
1436
   *
1437
   * If unable to locate a declared parameter then a
1438
   * parameter with type == PARAMETER_NOT_SET is returned.
1439
   *
1440
   * @param {string} name - The name of the parameter.
1441
   * @return {Parameter} - The parameter.
1442
   */
1443
  getParameter(name) {
1444
    return this.getParameters([name])[0];
1,392✔
1445
  }
1446

1447
  /**
1448
   * Get a list of parameters.
1449
   *
1450
   * Find and return the declared parameters.
1451
   * If no names are provided return all declared parameters.
1452
   *
1453
   * If unable to locate a declared parameter then a
1454
   * parameter with type == PARAMETER_NOT_SET is returned in
1455
   * it's place.
1456
   *
1457
   * @param {string[]} [names] - The names of the declared parameters
1458
   *    to find or null indicating to return all declared parameters.
1459
   * @return {Parameter[]} - The parameters.
1460
   */
1461
  getParameters(names = []) {
12✔
1462
    let params = [];
3,270✔
1463

1464
    if (names.length == 0) {
3,270✔
1465
      // get all parameters
1466
      params = [...this._parameters.values()];
12✔
1467
      return params;
12✔
1468
    }
1469

1470
    for (const name of names) {
3,258✔
1471
      const param = this.hasParameter(name)
3,263✔
1472
        ? this._parameters.get(name)
1473
        : new Parameter(name, ParameterType.PARAMETER_NOT_SET);
1474

1475
      params.push(param);
3,263✔
1476
    }
1477

1478
    return params;
3,258✔
1479
  }
1480

1481
  /**
1482
   * Get the types of given parameters.
1483
   *
1484
   * Return the types of given parameters.
1485
   *
1486
   * @param {string[]} [names] - The names of the declared parameters.
1487
   * @return {Uint8Array} - The types.
1488
   */
1489
  getParameterTypes(names = []) {
×
1490
    let types = [];
3✔
1491

1492
    for (const name of names) {
3✔
1493
      const descriptor = this._parameterDescriptors.get(name);
7✔
1494
      if (descriptor) {
7!
1495
        types.push(descriptor.type);
7✔
1496
      }
1497
    }
1498
    return types;
3✔
1499
  }
1500

1501
  /**
1502
   * Get the names of all declared parameters.
1503
   *
1504
   * @return {Array<string>} - The declared parameter names or empty array if
1505
   *    no parameters have been declared.
1506
   */
1507
  getParameterNames() {
1508
    return this.getParameters().map((param) => param.name);
53✔
1509
  }
1510

1511
  /**
1512
   * Determine if a parameter descriptor exists.
1513
   *
1514
   * @param {string} name - The name of a descriptor to for.
1515
   * @return {boolean} - true if a descriptor has been declared; otherwise false.
1516
   */
1517
  hasParameterDescriptor(name) {
1518
    return !!this.getParameterDescriptor(name);
1,859✔
1519
  }
1520

1521
  /**
1522
   * Get a declared parameter descriptor by name.
1523
   *
1524
   * If unable to locate a declared parameter descriptor then a
1525
   * descriptor with type == PARAMETER_NOT_SET is returned.
1526
   *
1527
   * @param {string} name - The name of the parameter descriptor to find.
1528
   * @return {ParameterDescriptor} - The parameter descriptor.
1529
   */
1530
  getParameterDescriptor(name) {
1531
    return this.getParameterDescriptors([name])[0];
3,719✔
1532
  }
1533

1534
  /**
1535
   * Find a list of declared ParameterDescriptors.
1536
   *
1537
   * If no names are provided return all declared descriptors.
1538
   *
1539
   * If unable to locate a declared descriptor then a
1540
   * descriptor with type == PARAMETER_NOT_SET is returned in
1541
   * it's place.
1542
   *
1543
   * @param {string[]} [names] - The names of the declared parameter
1544
   *    descriptors to find or null indicating to return all declared descriptors.
1545
   * @return {ParameterDescriptor[]} - The parameter descriptors.
1546
   */
1547
  getParameterDescriptors(names = []) {
×
1548
    let descriptors = [];
3,722✔
1549

1550
    if (names.length == 0) {
3,722!
1551
      // get all parameters
1552
      descriptors = [...this._parameterDescriptors.values()];
×
1553
      return descriptors;
×
1554
    }
1555

1556
    for (const name of names) {
3,722✔
1557
      let descriptor = this._parameterDescriptors.get(name);
3,724✔
1558
      if (!descriptor) {
3,724!
1559
        descriptor = new ParameterDescriptor(
×
1560
          name,
1561
          ParameterType.PARAMETER_NOT_SET
1562
        );
1563
      }
1564
      descriptors.push(descriptor);
3,724✔
1565
    }
1566

1567
    return descriptors;
3,722✔
1568
  }
1569

1570
  /**
1571
   * Replace a declared parameter.
1572
   *
1573
   * The parameter being replaced must be a declared parameter who's descriptor
1574
   * is not readOnly; otherwise an Error is thrown.
1575
   *
1576
   * @param {Parameter} parameter - The new parameter.
1577
   * @return {rclnodejs.rcl_interfaces.msg.SetParameterResult} - The result of the operation.
1578
   */
1579
  setParameter(parameter) {
1580
    const results = this.setParameters([parameter]);
4✔
1581
    return results[0];
4✔
1582
  }
1583

1584
  /**
1585
   * Replace a list of declared parameters.
1586
   *
1587
   * Declared parameters are replaced in the order they are provided and
1588
   * a ParameterEvent is published for each individual parameter change.
1589
   *
1590
   * Prior to setting the parameters each SetParameterEventCallback registered
1591
   * using setOnParameterEventCallback() is called in succession with the parameters
1592
   * list. Any SetParameterEventCallback that retuns does not return a successful
1593
   * result will cause the entire operation to terminate with no changes to the
1594
   * parameters. When all SetParameterEventCallbacks return successful then the
1595
   * list of parameters is updated.
1596
   *
1597
   * If an error occurs, the process is stopped and returned. Parameters
1598
   * set before an error remain unchanged.
1599
   *
1600
   * @param {Parameter[]} parameters - The parameters to set.
1601
   * @return {rclnodejs.rcl_interfaces.msg.SetParameterResult[]} - A list of SetParameterResult, one for each parameter that was set.
1602
   */
1603
  setParameters(parameters = []) {
×
1604
    return parameters.map((parameter) =>
14✔
1605
      this.setParametersAtomically([parameter])
16✔
1606
    );
1607
  }
1608

1609
  /**
1610
   * Repalce a list of declared parameters atomically.
1611
   *
1612
   * Declared parameters are replaced in the order they are provided.
1613
   * A single ParameterEvent is published collectively for all changed
1614
   * parameters.
1615
   *
1616
   * Prior to setting the parameters each SetParameterEventCallback registered
1617
   * using setOnParameterEventCallback() is called in succession with the parameters
1618
   * list. Any SetParameterEventCallback that retuns does not return a successful
1619
   * result will cause the entire operation to terminate with no changes to the
1620
   * parameters. When all SetParameterEventCallbacks return successful then the
1621
   * list of parameters is updated.d
1622
   *
1623
   * If an error occurs, the process stops immediately. All parameters updated to
1624
   * the point of the error are reverted to their previous state.
1625
   *
1626
   * @param {Parameter[]} parameters - The parameters to set.
1627
   * @return {rclnodejs.rcl_interfaces.msg.SetParameterResult} - describes the result of setting 1 or more parameters.
1628
   */
1629
  setParametersAtomically(parameters = []) {
×
1630
    return this._setParametersAtomically(parameters);
17✔
1631
  }
1632

1633
  /**
1634
   * Internal method for updating parameters atomically.
1635
   *
1636
   * Prior to setting the parameters each SetParameterEventCallback registered
1637
   * using setOnParameterEventCallback() is called in succession with the parameters
1638
   * list. Any SetParameterEventCallback that retuns does not return a successful
1639
   * result will cause the entire operation to terminate with no changes to the
1640
   * parameters. When all SetParameterEventCallbacks return successful then the
1641
   * list of parameters is updated.
1642
   *
1643
   * @param {Paramerter[]} parameters - The parameters to update.
1644
   * @param {boolean} declareParameterMode - When true parameters are being declared;
1645
   *    otherwise they are being changed.
1646
   * @return {SetParameterResult} - A single collective result.
1647
   */
1648
  _setParametersAtomically(parameters = [], declareParameterMode = false) {
17!
1649
    let result = this._validateParameters(parameters, declareParameterMode);
1,859✔
1650
    if (!result.successful) {
1,859!
1651
      return result;
×
1652
    }
1653

1654
    // give all SetParametersCallbacks a chance to veto this change
1655
    for (const callback of this._setParametersCallbacks) {
1,859✔
1656
      result = callback(parameters);
1,175✔
1657
      if (!result.successful) {
1,175✔
1658
        // a callback has vetoed a parameter change
1659
        return result;
1✔
1660
      }
1661
    }
1662

1663
    // collectively track updates to parameters for use
1664
    // when publishing a ParameterEvent
1665
    const newParameters = [];
1,858✔
1666
    const changedParameters = [];
1,858✔
1667
    const deletedParameters = [];
1,858✔
1668

1669
    for (const parameter of parameters) {
1,858✔
1670
      if (parameter.type == ParameterType.PARAMETER_NOT_SET) {
1,858✔
1671
        this.undeclareParameter(parameter.name);
1✔
1672
        deletedParameters.push(parameter);
1✔
1673
      } else {
1674
        this._parameters.set(parameter.name, parameter);
1,857✔
1675
        if (declareParameterMode) {
1,857✔
1676
          newParameters.push(parameter);
1,842✔
1677
        } else {
1678
          changedParameters.push(parameter);
15✔
1679
        }
1680
      }
1681
    }
1682

1683
    // create ParameterEvent
1684
    const parameterEvent = new (loader.loadInterface(
1,858✔
1685
      PARAMETER_EVENT_MSG_TYPE
1686
    ))();
1687

1688
    const { seconds, nanoseconds } = this._clock.now().secondsAndNanoseconds;
1,858✔
1689
    parameterEvent.stamp = {
1,858✔
1690
      sec: Number(seconds),
1691
      nanosec: Number(nanoseconds),
1692
    };
1693

1694
    parameterEvent.node =
1,858✔
1695
      this.namespace() === '/'
1,858✔
1696
        ? this.namespace() + this.name()
1697
        : this.namespace() + '/' + this.name();
1698

1699
    if (newParameters.length > 0) {
1,858✔
1700
      parameterEvent['new_parameters'] = newParameters.map((parameter) =>
1,842✔
1701
        parameter.toParameterMessage()
1,842✔
1702
      );
1703
    }
1704
    if (changedParameters.length > 0) {
1,858✔
1705
      parameterEvent['changed_parameters'] = changedParameters.map(
15✔
1706
        (parameter) => parameter.toParameterMessage()
15✔
1707
      );
1708
    }
1709
    if (deletedParameters.length > 0) {
1,858✔
1710
      parameterEvent['deleted_parameters'] = deletedParameters.map(
1✔
1711
        (parameter) => parameter.toParameterMessage()
1✔
1712
      );
1713
    }
1714

1715
    // Publish ParameterEvent.
1716
    this._parameterEventPublisher.publish(parameterEvent);
1,858✔
1717

1718
    return {
1,858✔
1719
      successful: true,
1720
      reason: '',
1721
    };
1722
  }
1723

1724
  /**
1725
   * This callback is called when declaring a parameter or setting a parameter.
1726
   * The callback is provided a list of parameters and returns a SetParameterResult
1727
   * to indicate approval or veto of the operation.
1728
   *
1729
   * @callback SetParametersCallback
1730
   * @param {Parameter[]} parameters - The message published
1731
   * @returns {rcl_interfaces.msg.SetParameterResult} -
1732
   *
1733
   * @see [Node.addOnSetParametersCallback]{@link Node#addOnSetParametersCallback}
1734
   * @see [Node.removeOnSetParametersCallback]{@link Node#removeOnSetParametersCallback}
1735
   */
1736

1737
  /**
1738
   * Add a callback to the front of the list of callbacks invoked for parameter declaration
1739
   * and setting. No checks are made for duplicate callbacks.
1740
   *
1741
   * @param {SetParametersCallback} callback - The callback to add.
1742
   * @returns {undefined}
1743
   */
1744
  addOnSetParametersCallback(callback) {
1745
    this._setParametersCallbacks.unshift(callback);
698✔
1746
  }
1747

1748
  /**
1749
   * Remove a callback from the list of SetParametersCallbacks.
1750
   * If the callback is not found the process is a nop.
1751
   *
1752
   * @param {SetParametersCallback} callback - The callback to be removed
1753
   * @returns {undefined}
1754
   */
1755
  removeOnSetParametersCallback(callback) {
1756
    const idx = this._setParametersCallbacks.indexOf(callback);
2✔
1757
    if (idx > -1) {
2!
1758
      this._setParametersCallbacks.splice(idx, 1);
2✔
1759
    }
1760
  }
1761

1762
  /**
1763
   * Get the fully qualified name of the node.
1764
   *
1765
   * @returns {string} - String containing the fully qualified name of the node.
1766
   */
1767
  getFullyQualifiedName() {
1768
    return rclnodejs.getFullyQualifiedName(this.handle);
1✔
1769
  }
1770

1771
  /**
1772
   * Get the RMW implementation identifier
1773
   * @returns {string} - The RMW implementation identifier.
1774
   */
1775
  getRMWImplementationIdentifier() {
1776
    return rclnodejs.getRMWImplementationIdentifier();
1✔
1777
  }
1778

1779
  /**
1780
   * Return a topic name expanded and remapped.
1781
   * @param {string} topicName - Topic name to be expanded and remapped.
1782
   * @param {boolean} [onlyExpand=false] - If `true`, remapping rules won't be applied.
1783
   * @returns {string} - A fully qualified topic name.
1784
   */
1785
  resolveTopicName(topicName, onlyExpand = false) {
2✔
1786
    if (typeof topicName !== 'string') {
3!
1787
      throw new TypeError('Invalid argument: expected string');
×
1788
    }
1789
    return rclnodejs.resolveName(
3✔
1790
      this.handle,
1791
      topicName,
1792
      onlyExpand,
1793
      /*isService=*/ false
1794
    );
1795
  }
1796

1797
  /**
1798
   * Return a service name expanded and remapped.
1799
   * @param {string} service - Service name to be expanded and remapped.
1800
   * @param {boolean} [onlyExpand=false] - If `true`, remapping rules won't be applied.
1801
   * @returns {string} - A fully qualified service name.
1802
   */
1803
  resolveServiceName(service, onlyExpand = false) {
2✔
1804
    if (typeof service !== 'string') {
3!
1805
      throw new TypeError('Invalid argument: expected string');
×
1806
    }
1807
    return rclnodejs.resolveName(
3✔
1808
      this.handle,
1809
      service,
1810
      onlyExpand,
1811
      /*isService=*/ true
1812
    );
1813
  }
1814

1815
  // returns on 1st error or result {successful, reason}
1816
  _validateParameters(parameters = [], declareParameterMode = false) {
×
1817
    for (const parameter of parameters) {
1,859✔
1818
      // detect invalid parameter
1819
      try {
1,859✔
1820
        parameter.validate();
1,859✔
1821
      } catch {
1822
        return {
×
1823
          successful: false,
1824
          reason: `Invalid ${parameter.name}`,
1825
        };
1826
      }
1827

1828
      // detect undeclared parameter
1829
      if (!this.hasParameterDescriptor(parameter.name)) {
1,859!
1830
        return {
×
1831
          successful: false,
1832
          reason: `Parameter ${parameter.name} has not been declared`,
1833
        };
1834
      }
1835

1836
      // detect readonly parameter that can not be updated
1837
      const descriptor = this.getParameterDescriptor(parameter.name);
1,859✔
1838
      if (!declareParameterMode && descriptor.readOnly) {
1,859!
1839
        return {
×
1840
          successful: false,
1841
          reason: `Parameter ${parameter.name} is readonly`,
1842
        };
1843
      }
1844

1845
      // validate parameter against descriptor if not an undeclare action
1846
      if (parameter.type != ParameterType.PARAMETER_NOT_SET) {
1,859✔
1847
        try {
1,858✔
1848
          descriptor.validateParameter(parameter);
1,858✔
1849
        } catch {
1850
          return {
×
1851
            successful: false,
1852
            reason: `Parameter ${parameter.name} does not  readonly`,
1853
          };
1854
        }
1855
      }
1856
    }
1857

1858
    return {
1,859✔
1859
      successful: true,
1860
      reason: null,
1861
    };
1862
  }
1863

1864
  // Get a Map(nodeName->Parameter[]) of CLI parameter args that
1865
  // apply to 'this' node, .e.g., -p mynode:foo:=bar -p hello:=world
1866
  _getNativeParameterOverrides() {
1867
    const overrides = new Map();
690✔
1868

1869
    // Get native parameters from rcl context->global_arguments.
1870
    // rclnodejs returns an array of objects, 1 for each node e.g., -p my_node:foo:=bar,
1871
    // and a node named '/**' for global parameter rules,
1872
    // i.e., does not include a node identifier, e.g., -p color:=red
1873
    // {
1874
    //   name: string // node name
1875
    //   parameters[] = {
1876
    //     name: string
1877
    //     type: uint
1878
    //     value: object
1879
    // }
1880
    const cliParamOverrideData = rclnodejs.getParameterOverrides(
690✔
1881
      this.context.handle
1882
    );
1883

1884
    // convert native CLI parameterOverrides to Map<nodeName,Array<ParameterOverride>>
1885
    const cliParamOverrides = new Map();
690✔
1886
    if (cliParamOverrideData) {
690✔
1887
      for (let nodeParamData of cliParamOverrideData) {
8✔
1888
        const nodeName = nodeParamData.name;
12✔
1889
        const nodeParamOverrides = [];
12✔
1890
        for (let paramData of nodeParamData.parameters) {
12✔
1891
          const paramOverride = new Parameter(
17✔
1892
            paramData.name,
1893
            paramData.type,
1894
            paramData.value
1895
          );
1896
          nodeParamOverrides.push(paramOverride);
17✔
1897
        }
1898
        cliParamOverrides.set(nodeName, nodeParamOverrides);
12✔
1899
      }
1900
    }
1901

1902
    // collect global CLI global parameters, name == /**
1903
    let paramOverrides = cliParamOverrides.get('/**'); // array of ParameterOverrides
690✔
1904
    if (paramOverrides) {
690✔
1905
      for (const parameter of paramOverrides) {
5✔
1906
        overrides.set(parameter.name, parameter);
6✔
1907
      }
1908
    }
1909

1910
    // merge CLI node parameterOverrides with global parameterOverrides, replace existing
1911
    paramOverrides = cliParamOverrides.get(this.name()); // array of ParameterOverrides
690✔
1912
    if (paramOverrides) {
690✔
1913
      for (const parameter of paramOverrides) {
5✔
1914
        overrides.set(parameter.name, parameter);
7✔
1915
      }
1916
    }
1917

1918
    return overrides;
690✔
1919
  }
1920

1921
  /**
1922
   * Invokes the callback with a raw message of the given type. After the callback completes
1923
   * the message will be destroyed.
1924
   * @param {function} Type - Message type to create.
1925
   * @param {function} callback - Callback to invoke. First parameter will be the raw message,
1926
   * and the second is a function to retrieve the deserialized message.
1927
   * @returns {undefined}
1928
   */
1929
  _runWithMessageType(Type, callback) {
1930
    let message = new Type();
896✔
1931

1932
    callback(message.toRawROS(), () => {
896✔
1933
      let result = new Type();
666✔
1934
      result.deserialize(message.refObject);
666✔
1935

1936
      return result;
666✔
1937
    });
1938

1939
    Type.destroyRawROS(message);
896✔
1940
  }
1941

1942
  _addActionClient(actionClient) {
1943
    this._actionClients.push(actionClient);
41✔
1944
    this.syncHandles();
41✔
1945
  }
1946

1947
  _addActionServer(actionServer) {
1948
    this._actionServers.push(actionServer);
41✔
1949
    this.syncHandles();
41✔
1950
  }
1951

1952
  _getValidatedTopic(topicName, noDemangle) {
1953
    if (noDemangle) {
5!
1954
      return topicName;
×
1955
    }
1956
    const fqTopicName = rclnodejs.expandTopicName(
5✔
1957
      topicName,
1958
      this.name(),
1959
      this.namespace()
1960
    );
1961
    validateFullTopicName(fqTopicName);
5✔
1962
    return rclnodejs.remapTopicName(this.handle, fqTopicName);
5✔
1963
  }
1964
}
1965

1966
/**
1967
 * Create an Options instance initialized with default values.
1968
 * @returns {Options} - The new initialized instance.
1969
 * @static
1970
 * @example
1971
 * {
1972
 *   enableTypedArray: true,
1973
 *   isRaw: false,
1974
 *   qos: QoS.profileDefault,
1975
 *   contentFilter: undefined,
1976
 *   serializationMode: 'default',
1977
 * }
1978
 */
1979
Node.getDefaultOptions = function () {
26✔
1980
  return {
6,471✔
1981
    enableTypedArray: true,
1982
    isRaw: false,
1983
    qos: QoS.profileDefault,
1984
    contentFilter: undefined,
1985
    serializationMode: 'default',
1986
  };
1987
};
1988

1989
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