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

RobotWebTools / rclnodejs / 19690312781

26 Nov 2025 02:15AM UTC coverage: 82.843% (+1.1%) from 81.767%
19690312781

push

github

minggangw
Pump to 1.7.0 (#1329)

1074 of 1420 branches covered (75.63%)

Branch coverage included in aggregate %.

2446 of 2829 relevant lines covered (86.46%)

488.08 hits per line

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

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

15
'use strict';
16

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

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

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

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

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

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

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

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

112
    this._context = context;
773✔
113
    this.context.onNodeCreated(this);
773✔
114

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

136
    this._parameterEventPublisher = this.createPublisher(
773✔
137
      PARAMETER_EVENT_MSG_TYPE,
138
      PARAMETER_EVENT_TOPIC
139
    );
140

141
    // initialize _parameterOverrides from parameters defined on the commandline
142
    this._parameterOverrides = this._getNativeParameterOverrides();
773✔
143

144
    // override cli parameterOverrides with those specified in options
145
    if (options.parameterOverrides.length > 0) {
773✔
146
      for (const parameter of options.parameterOverrides) {
8✔
147
        if ((!parameter) instanceof Parameter) {
13!
148
          throw new TypeValidationError(
×
149
            'parameterOverride',
150
            parameter,
151
            'Parameter instance',
152
            {
153
              nodeName: name,
154
            }
155
          );
156
        }
157
        this._parameterOverrides.set(parameter.name, parameter);
13✔
158
      }
159
    }
160

161
    // initialize _parameters from parameterOverrides
162
    if (options.automaticallyDeclareParametersFromOverrides) {
773✔
163
      for (const parameter of this._parameterOverrides.values()) {
5✔
164
        parameter.validate();
10✔
165
        const descriptor = ParameterDescriptor.fromParameter(parameter);
10✔
166
        this._parameters.set(parameter.name, parameter);
10✔
167
        this._parameterDescriptors.set(parameter.name, descriptor);
10✔
168
      }
169
    }
170

171
    // Clock that has support for ROS time.
172
    // Note: parameter overrides and parameter event publisher need to be ready at this point
173
    // to be able to declare 'use_sim_time' if it was not declared yet.
174
    this._clock = new Clock.ROSClock();
773✔
175
    this._timeSource = new TimeSource(this);
773✔
176
    this._timeSource.attachClock(this._clock);
773✔
177

178
    if (options.startParameterServices) {
773✔
179
      this._parameterService = new ParameterService(this);
767✔
180
      this._parameterService.start();
767✔
181
    }
182

183
    if (
773!
184
      DistroUtils.getDistroId() >= DistroUtils.getDistroId('jazzy') &&
1,546✔
185
      options.startTypeDescriptionService
186
    ) {
187
      this._typeDescriptionService = new TypeDescriptionService(this);
773✔
188
      this._typeDescriptionService.start();
773✔
189
    }
190
  }
191

192
  execute(handles) {
193
    let timersReady = this._timers.filter((timer) =>
5,479✔
194
      handles.includes(timer.handle)
4,560✔
195
    );
196
    let guardsReady = this._guards.filter((guard) =>
5,479✔
197
      handles.includes(guard.handle)
3✔
198
    );
199
    let subscriptionsReady = this._subscriptions.filter((subscription) =>
5,479✔
200
      handles.includes(subscription.handle)
490✔
201
    );
202
    let clientsReady = this._clients.filter((client) =>
5,479✔
203
      handles.includes(client.handle)
313✔
204
    );
205
    let servicesReady = this._services.filter((service) =>
5,479✔
206
      handles.includes(service.handle)
20,508✔
207
    );
208
    let actionClientsReady = this._actionClients.filter((actionClient) =>
5,479✔
209
      handles.includes(actionClient.handle)
157✔
210
    );
211
    let actionServersReady = this._actionServers.filter((actionServer) =>
5,479✔
212
      handles.includes(actionServer.handle)
157✔
213
    );
214
    let eventsReady = this._events.filter((event) =>
5,479✔
215
      handles.includes(event.handle)
4✔
216
    );
217

218
    timersReady.forEach((timer) => {
5,479✔
219
      if (timer.isReady()) {
4,549✔
220
        rclnodejs.callTimer(timer.handle);
4,484✔
221
        timer.callback();
4,484✔
222
      }
223
    });
224

225
    eventsReady.forEach((event) => {
5,479✔
226
      event.takeData();
4✔
227
    });
228

229
    for (const subscription of subscriptionsReady) {
5,479✔
230
      if (subscription.isDestroyed()) continue;
445✔
231
      if (subscription.isRaw) {
435✔
232
        let rawMessage = rclnodejs.rclTakeRaw(subscription.handle);
1✔
233
        if (rawMessage) {
1!
234
          subscription.processResponse(rawMessage);
1✔
235
        }
236
        continue;
1✔
237
      }
238

239
      this._runWithMessageType(
434✔
240
        subscription.typeClass,
241
        (message, deserialize) => {
242
          let success = rclnodejs.rclTake(subscription.handle, message);
434✔
243
          if (success) {
434✔
244
            subscription.processResponse(deserialize());
371✔
245
          }
246
        }
247
      );
248
    }
249

250
    for (const guard of guardsReady) {
5,479✔
251
      if (guard.isDestroyed()) continue;
3!
252

253
      guard.callback();
3✔
254
    }
255

256
    for (const client of clientsReady) {
5,479✔
257
      if (client.isDestroyed()) continue;
159!
258
      this._runWithMessageType(
159✔
259
        client.typeClass.Response,
260
        (message, deserialize) => {
261
          let sequenceNumber = rclnodejs.rclTakeResponse(
159✔
262
            client.handle,
263
            message
264
          );
265
          if (sequenceNumber !== undefined) {
159✔
266
            client.processResponse(sequenceNumber, deserialize());
86✔
267
          }
268
        }
269
      );
270
    }
271

272
    for (const service of servicesReady) {
5,479✔
273
      if (service.isDestroyed()) continue;
177!
274
      this._runWithMessageType(
177✔
275
        service.typeClass.Request,
276
        (message, deserialize) => {
277
          let header = rclnodejs.rclTakeRequest(
177✔
278
            service.handle,
279
            this.handle,
280
            message
281
          );
282
          if (header) {
177✔
283
            service.processRequest(header, deserialize());
91✔
284
          }
285
        }
286
      );
287
    }
288

289
    for (const actionClient of actionClientsReady) {
5,479✔
290
      if (actionClient.isDestroyed()) continue;
74!
291

292
      const properties = actionClient.handle.properties;
74✔
293

294
      if (properties.isGoalResponseReady) {
74✔
295
        this._runWithMessageType(
34✔
296
          actionClient.typeClass.impl.SendGoalService.Response,
297
          (message, deserialize) => {
298
            let sequence = rclnodejs.actionTakeGoalResponse(
34✔
299
              actionClient.handle,
300
              message
301
            );
302
            if (sequence != undefined) {
34✔
303
              actionClient.processGoalResponse(sequence, deserialize());
30✔
304
            }
305
          }
306
        );
307
      }
308

309
      if (properties.isCancelResponseReady) {
74✔
310
        this._runWithMessageType(
6✔
311
          actionClient.typeClass.impl.CancelGoal.Response,
312
          (message, deserialize) => {
313
            let sequence = rclnodejs.actionTakeCancelResponse(
6✔
314
              actionClient.handle,
315
              message
316
            );
317
            if (sequence != undefined) {
6✔
318
              actionClient.processCancelResponse(sequence, deserialize());
4✔
319
            }
320
          }
321
        );
322
      }
323

324
      if (properties.isResultResponseReady) {
74✔
325
        this._runWithMessageType(
15✔
326
          actionClient.typeClass.impl.GetResultService.Response,
327
          (message, deserialize) => {
328
            let sequence = rclnodejs.actionTakeResultResponse(
15✔
329
              actionClient.handle,
330
              message
331
            );
332
            if (sequence != undefined) {
15!
333
              actionClient.processResultResponse(sequence, deserialize());
15✔
334
            }
335
          }
336
        );
337
      }
338

339
      if (properties.isFeedbackReady) {
74✔
340
        this._runWithMessageType(
7✔
341
          actionClient.typeClass.impl.FeedbackMessage,
342
          (message, deserialize) => {
343
            let success = rclnodejs.actionTakeFeedback(
7✔
344
              actionClient.handle,
345
              message
346
            );
347
            if (success) {
7✔
348
              actionClient.processFeedbackMessage(deserialize());
5✔
349
            }
350
          }
351
        );
352
      }
353

354
      if (properties.isStatusReady) {
74✔
355
        this._runWithMessageType(
36✔
356
          actionClient.typeClass.impl.GoalStatusArray,
357
          (message, deserialize) => {
358
            let success = rclnodejs.actionTakeStatus(
36✔
359
              actionClient.handle,
360
              message
361
            );
362
            if (success) {
36✔
363
              actionClient.processStatusMessage(deserialize());
33✔
364
            }
365
          }
366
        );
367
      }
368
    }
369

370
    for (const actionServer of actionServersReady) {
5,479✔
371
      if (actionServer.isDestroyed()) continue;
92!
372

373
      const properties = actionServer.handle.properties;
92✔
374

375
      if (properties.isGoalRequestReady) {
92✔
376
        this._runWithMessageType(
34✔
377
          actionServer.typeClass.impl.SendGoalService.Request,
378
          (message, deserialize) => {
379
            const result = rclnodejs.actionTakeGoalRequest(
34✔
380
              actionServer.handle,
381
              message
382
            );
383
            if (result) {
34✔
384
              actionServer.processGoalRequest(result, deserialize());
30✔
385
            }
386
          }
387
        );
388
      }
389

390
      if (properties.isCancelRequestReady) {
92✔
391
        this._runWithMessageType(
6✔
392
          actionServer.typeClass.impl.CancelGoal.Request,
393
          (message, deserialize) => {
394
            const result = rclnodejs.actionTakeCancelRequest(
6✔
395
              actionServer.handle,
396
              message
397
            );
398
            if (result) {
6✔
399
              actionServer.processCancelRequest(result, deserialize());
4✔
400
            }
401
          }
402
        );
403
      }
404

405
      if (properties.isResultRequestReady) {
92✔
406
        this._runWithMessageType(
23✔
407
          actionServer.typeClass.impl.GetResultService.Request,
408
          (message, deserialize) => {
409
            const result = rclnodejs.actionTakeResultRequest(
23✔
410
              actionServer.handle,
411
              message
412
            );
413
            if (result) {
23✔
414
              actionServer.processResultRequest(result, deserialize());
15✔
415
            }
416
          }
417
        );
418
      }
419

420
      if (properties.isGoalExpired) {
92✔
421
        let GoalInfoArray = ActionInterfaces.GoalInfo.ArrayType;
4✔
422
        let message = new GoalInfoArray(actionServer._goalHandles.size);
4✔
423
        let count = rclnodejs.actionExpireGoals(
4✔
424
          actionServer.handle,
425
          actionServer._goalHandles.size,
426
          message._refArray.buffer
427
        );
428
        if (count > 0) {
4✔
429
          actionServer.processGoalExpired(message, count);
3✔
430
        }
431
        GoalInfoArray.freeArray(message);
4✔
432
      }
433
    }
434

435
    // At this point it is safe to clear the cache of any
436
    // destroyed entity references
437
    Entity._gcHandles();
5,479✔
438
  }
439

440
  /**
441
   * Determine if this node is spinning.
442
   * @returns {boolean} - true when spinning; otherwise returns false.
443
   */
444
  get spinning() {
445
    return this._spinning;
4,416✔
446
  }
447

448
  /**
449
   * Trigger the event loop to continuously check for and route.
450
   * incoming events.
451
   * @param {Node} node - The node to be spun up.
452
   * @param {number} [timeout=10] - Timeout to wait in milliseconds. Block forever if negative. Don't wait if 0.
453
   * @throws {Error} If the node is already spinning.
454
   * @return {undefined}
455
   */
456
  spin(timeout = 10) {
15✔
457
    if (this.spinning) {
610!
458
      throw new Error('The node is already spinning.');
×
459
    }
460
    this.start(this.context.handle, timeout);
610✔
461
    this._spinning = true;
610✔
462
  }
463

464
  /**
465
   * Use spin().
466
   * @deprecated, since 0.18.0
467
   */
468
  startSpinning(timeout) {
469
    this.spin(timeout);
×
470
  }
471

472
  /**
473
   * Terminate spinning - no further events will be received.
474
   * @returns {undefined}
475
   */
476
  stop() {
477
    super.stop();
610✔
478
    this._spinning = false;
610✔
479
  }
480

481
  /**
482
   * Terminate spinning - no further events will be received.
483
   * @returns {undefined}
484
   * @deprecated since 0.18.0, Use stop().
485
   */
486
  stopSpinning() {
487
    super.stop();
×
488
    this._spinning = false;
×
489
  }
490

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

506
  _removeEntityFromArray(entity, array) {
507
    let index = array.indexOf(entity);
340✔
508
    if (index > -1) {
340✔
509
      array.splice(index, 1);
338✔
510
    }
511
  }
512

513
  _destroyEntity(entity, array, syncHandles = true) {
286✔
514
    if (entity['isDestroyed'] && entity.isDestroyed()) return;
293✔
515

516
    this._removeEntityFromArray(entity, array);
285✔
517
    if (syncHandles) {
285✔
518
      this.syncHandles();
280✔
519
    }
520

521
    if (entity['_destroy']) {
285✔
522
      entity._destroy();
279✔
523
    } else {
524
      // guards and timers
525
      entity.handle.release();
6✔
526
    }
527
  }
528

529
  _validateOptions(options) {
530
    if (
7,253✔
531
      options !== undefined &&
7,319✔
532
      (options === null || typeof options !== 'object')
533
    ) {
534
      throw new TypeValidationError('options', options, 'object', {
20✔
535
        nodeName: this.name(),
536
      });
537
    }
538

539
    if (options === undefined) {
7,233✔
540
      return Node.getDefaultOptions();
7,210✔
541
    }
542

543
    if (options.enableTypedArray === undefined) {
23✔
544
      options = Object.assign(options, { enableTypedArray: true });
11✔
545
    }
546

547
    if (options.qos === undefined) {
23✔
548
      options = Object.assign(options, { qos: QoS.profileDefault });
12✔
549
    }
550

551
    if (options.isRaw === undefined) {
23✔
552
      options = Object.assign(options, { isRaw: false });
13✔
553
    }
554

555
    if (options.serializationMode === undefined) {
23✔
556
      options = Object.assign(options, { serializationMode: 'default' });
7✔
557
    } else if (!isValidSerializationMode(options.serializationMode)) {
16✔
558
      throw new ValidationError(
1✔
559
        `Invalid serializationMode: ${options.serializationMode}. Valid modes are: 'default', 'plain', 'json'`,
560
        {
561
          code: 'INVALID_SERIALIZATION_MODE',
562
          argumentName: 'serializationMode',
563
          providedValue: options.serializationMode,
564
          expectedType: "'default' | 'plain' | 'json'",
565
          nodeName: this.name(),
566
        }
567
      );
568
    }
569

570
    return options;
22✔
571
  }
572

573
  /**
574
   * Create a Timer.
575
   * @param {bigint} period - The number representing period in nanoseconds.
576
   * @param {function} callback - The callback to be called when timeout.
577
   * @param {Clock} [clock] - The clock which the timer gets time from.
578
   * @return {Timer} - An instance of Timer.
579
   */
580
  createTimer(period, callback, clock = null) {
58✔
581
    if (arguments.length === 3 && !(arguments[2] instanceof Clock)) {
58!
582
      clock = null;
×
583
    } else if (arguments.length === 4) {
58!
584
      clock = arguments[3];
×
585
    }
586

587
    if (typeof period !== 'bigint') {
58✔
588
      throw new TypeValidationError('period', period, 'bigint', {
1✔
589
        nodeName: this.name(),
590
      });
591
    }
592
    if (typeof callback !== 'function') {
57✔
593
      throw new TypeValidationError('callback', callback, 'function', {
1✔
594
        nodeName: this.name(),
595
      });
596
    }
597

598
    const timerClock = clock || this._clock;
56✔
599
    let timerHandle = rclnodejs.createTimer(
56✔
600
      timerClock.handle,
601
      this.context.handle,
602
      period
603
    );
604
    let timer = new Timer(timerHandle, period, callback);
56✔
605
    debug('Finish creating timer, period = %d.', period);
56✔
606
    this._timers.push(timer);
56✔
607
    this.syncHandles();
56✔
608

609
    return timer;
56✔
610
  }
611

612
  /**
613
   * Create a Rate.
614
   *
615
   * @param {number} hz - The frequency of the rate timer; default is 1 hz.
616
   * @returns {Promise<Rate>} - Promise resolving to new instance of Rate.
617
   */
618
  async createRate(hz = 1) {
4✔
619
    if (typeof hz !== 'number') {
9!
620
      throw new TypeValidationError('hz', hz, 'number', {
×
621
        nodeName: this.name(),
622
      });
623
    }
624

625
    const MAX_RATE_HZ_IN_MILLISECOND = 1000.0;
9✔
626
    if (hz <= 0.0 || hz > MAX_RATE_HZ_IN_MILLISECOND) {
9✔
627
      throw new RangeValidationError(
2✔
628
        'hz',
629
        hz,
630
        `0.0 < hz <= ${MAX_RATE_HZ_IN_MILLISECOND}`,
631
        {
632
          nodeName: this.name(),
633
        }
634
      );
635
    }
636

637
    // lazy initialize rateTimerServer
638
    if (!this._rateTimerServer) {
7✔
639
      this._rateTimerServer = new Rates.RateTimerServer(this);
5✔
640
      await this._rateTimerServer.init();
5✔
641
    }
642

643
    const period = Math.round(1000 / hz);
7✔
644
    const timer = this._rateTimerServer.createTimer(BigInt(period) * 1000000n);
7✔
645
    const rate = new Rates.Rate(hz, timer);
7✔
646

647
    return rate;
7✔
648
  }
649

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

672
  _createPublisher(typeClass, topic, options, publisherClass, eventCallbacks) {
673
    if (typeof typeClass === 'string' || typeof typeClass === 'object') {
1,137✔
674
      typeClass = loader.loadInterface(typeClass);
1,113✔
675
    }
676
    options = this._validateOptions(options);
1,130✔
677

678
    if (typeof typeClass !== 'function') {
1,130✔
679
      throw new TypeValidationError('typeClass', typeClass, 'function', {
8✔
680
        nodeName: this.name(),
681
        entityType: 'publisher',
682
      });
683
    }
684
    if (typeof topic !== 'string') {
1,122✔
685
      throw new TypeValidationError('topic', topic, 'string', {
12✔
686
        nodeName: this.name(),
687
        entityType: 'publisher',
688
      });
689
    }
690
    if (
1,110!
691
      eventCallbacks &&
1,112✔
692
      !(eventCallbacks instanceof PublisherEventCallbacks)
693
    ) {
694
      throw new TypeValidationError(
×
695
        'eventCallbacks',
696
        eventCallbacks,
697
        'PublisherEventCallbacks',
698
        {
699
          nodeName: this.name(),
700
          entityType: 'publisher',
701
          entityName: topic,
702
        }
703
      );
704
    }
705

706
    let publisher = publisherClass.createPublisher(
1,110✔
707
      this,
708
      typeClass,
709
      topic,
710
      options,
711
      eventCallbacks
712
    );
713
    debug('Finish creating publisher, topic = %s.', topic);
1,100✔
714
    this._publishers.push(publisher);
1,100✔
715
    return publisher;
1,100✔
716
  }
717

718
  /**
719
   * This callback is called when a message is published
720
   * @callback SubscriptionCallback
721
   * @param {Object} message - The message published
722
   * @see [Node.createSubscription]{@link Node#createSubscription}
723
   * @see [Node.createPublisher]{@link Node#createPublisher}
724
   * @see {@link Publisher}
725
   * @see {@link Subscription}
726
   */
727

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

761
    if (typeof options === 'function') {
419✔
762
      callback = options;
379✔
763
      options = undefined;
379✔
764
    }
765
    options = this._validateOptions(options);
419✔
766

767
    if (typeof typeClass !== 'function') {
409✔
768
      throw new TypeValidationError('typeClass', typeClass, 'function', {
4✔
769
        nodeName: this.name(),
770
        entityType: 'subscription',
771
      });
772
    }
773
    if (typeof topic !== 'string') {
405✔
774
      throw new TypeValidationError('topic', topic, 'string', {
6✔
775
        nodeName: this.name(),
776
        entityType: 'subscription',
777
      });
778
    }
779
    if (typeof callback !== 'function') {
399!
780
      throw new TypeValidationError('callback', callback, 'function', {
×
781
        nodeName: this.name(),
782
        entityType: 'subscription',
783
        entityName: topic,
784
      });
785
    }
786
    if (
399!
787
      eventCallbacks &&
403✔
788
      !(eventCallbacks instanceof SubscriptionEventCallbacks)
789
    ) {
790
      throw new TypeValidationError(
×
791
        'eventCallbacks',
792
        eventCallbacks,
793
        'SubscriptionEventCallbacks',
794
        {
795
          nodeName: this.name(),
796
          entityType: 'subscription',
797
          entityName: topic,
798
        }
799
      );
800
    }
801

802
    let subscription = Subscription.createSubscription(
399✔
803
      this,
804
      typeClass,
805
      topic,
806
      options,
807
      callback,
808
      eventCallbacks
809
    );
810
    debug('Finish creating subscription, topic = %s.', topic);
388✔
811
    this._subscriptions.push(subscription);
388✔
812
    this.syncHandles();
388✔
813

814
    return subscription;
388✔
815
  }
816

817
  /**
818
   * Create a Client.
819
   * @param {function|string|object} typeClass - The ROS message class,
820
        OR a string representing the message class, e.g. 'std_msgs/msg/String',
821
        OR an object representing the message class, e.g. {package: 'std_msgs', type: 'msg', name: 'String'}
822
   * @param {string} serviceName - The service name to request.
823
   * @param {object} options - The options argument used to parameterize the client.
824
   * @param {boolean} options.enableTypedArray - The response will use TypedArray if necessary, default: true.
825
   * @param {QoS} options.qos - ROS Middleware "quality of service" settings for the client, default: QoS.profileDefault.
826
   * @return {Client} - An instance of Client.
827
   */
828
  createClient(typeClass, serviceName, options) {
829
    if (typeof typeClass === 'string' || typeof typeClass === 'object') {
195✔
830
      typeClass = loader.loadInterface(typeClass);
185✔
831
    }
832
    options = this._validateOptions(options);
188✔
833

834
    if (typeof typeClass !== 'function') {
188✔
835
      throw new TypeValidationError('typeClass', typeClass, 'function', {
8✔
836
        nodeName: this.name(),
837
        entityType: 'client',
838
      });
839
    }
840
    if (typeof serviceName !== 'string') {
180✔
841
      throw new TypeValidationError('serviceName', serviceName, 'string', {
12✔
842
        nodeName: this.name(),
843
        entityType: 'client',
844
      });
845
    }
846

847
    let client = Client.createClient(
168✔
848
      this.handle,
849
      serviceName,
850
      typeClass,
851
      options
852
    );
853
    debug('Finish creating client, service = %s.', serviceName);
158✔
854
    this._clients.push(client);
158✔
855
    this.syncHandles();
158✔
856

857
    return client;
158✔
858
  }
859

860
  /**
861
   * This callback is called when a request is sent to service
862
   * @callback RequestCallback
863
   * @param {Object} request - The request sent to the service
864
   * @param {Response} response - The response to client.
865
        Use [response.send()]{@link Response#send} to send response object to client
866
   * @return {undefined}
867
   * @see [Node.createService]{@link Node#createService}
868
   * @see [Client.sendRequest]{@link Client#sendRequest}
869
   * @see {@link Client}
870
   * @see {@link Service}
871
   * @see {@link Response#send}
872
   */
873

874
  /**
875
   * Create a Service.
876
   * @param {function|string|object} typeClass - The ROS message class,
877
        OR a string representing the message class, e.g. 'std_msgs/msg/String',
878
        OR an object representing the message class, e.g. {package: 'std_msgs', type: 'msg', name: 'String'}
879
   * @param {string} serviceName - The service name to offer.
880
   * @param {object} options - The options argument used to parameterize the service.
881
   * @param {boolean} options.enableTypedArray - The request will use TypedArray if necessary, default: true.
882
   * @param {QoS} options.qos - ROS Middleware "quality of service" settings for the service, default: QoS.profileDefault.
883
   * @param {RequestCallback} callback - The callback to be called when receiving request.
884
   * @return {Service} - An instance of Service.
885
   * @see {@link RequestCallback}
886
   */
887
  createService(typeClass, serviceName, options, callback) {
888
    if (typeof typeClass === 'string' || typeof typeClass === 'object') {
4,674✔
889
      typeClass = loader.loadInterface(typeClass);
4,664✔
890
    }
891

892
    if (typeof options === 'function') {
4,667✔
893
      callback = options;
4,644✔
894
      options = undefined;
4,644✔
895
    }
896
    options = this._validateOptions(options);
4,667✔
897

898
    if (typeof typeClass !== 'function') {
4,657✔
899
      throw new TypeValidationError('typeClass', typeClass, 'function', {
4✔
900
        nodeName: this.name(),
901
        entityType: 'service',
902
      });
903
    }
904
    if (typeof serviceName !== 'string') {
4,653✔
905
      throw new TypeValidationError('serviceName', serviceName, 'string', {
6✔
906
        nodeName: this.name(),
907
        entityType: 'service',
908
      });
909
    }
910
    if (typeof callback !== 'function') {
4,647!
911
      throw new TypeValidationError('callback', callback, 'function', {
×
912
        nodeName: this.name(),
913
        entityType: 'service',
914
        entityName: serviceName,
915
      });
916
    }
917

918
    let service = Service.createService(
4,647✔
919
      this.handle,
920
      serviceName,
921
      typeClass,
922
      options,
923
      callback
924
    );
925
    debug('Finish creating service, service = %s.', serviceName);
4,637✔
926
    this._services.push(service);
4,637✔
927
    this.syncHandles();
4,637✔
928

929
    return service;
4,637✔
930
  }
931

932
  /**
933
   * Create a ParameterClient for accessing parameters on a remote node.
934
   * @param {string} remoteNodeName - The name of the remote node whose parameters to access.
935
   * @param {object} [options] - Options for parameter client.
936
   * @param {number} [options.timeout=5000] - Default timeout in milliseconds for service calls.
937
   * @return {ParameterClient} - An instance of ParameterClient.
938
   */
939
  createParameterClient(remoteNodeName, options = {}) {
50✔
940
    if (typeof remoteNodeName !== 'string' || remoteNodeName.trim() === '') {
103!
941
      throw new TypeError('Remote node name must be a non-empty string');
×
942
    }
943

944
    const parameterClient = new ParameterClient(this, remoteNodeName, options);
103✔
945
    debug(
103✔
946
      'Finish creating parameter client for remote node = %s.',
947
      remoteNodeName
948
    );
949
    this._parameterClients.push(parameterClient);
103✔
950

951
    return parameterClient;
103✔
952
  }
953

954
  /**
955
   * Create a ParameterWatcher for watching parameter changes on a remote node.
956
   * @param {string} remoteNodeName - The name of the remote node whose parameters to watch.
957
   * @param {string[]} parameterNames - Array of parameter names to watch.
958
   * @param {object} [options] - Options for parameter watcher.
959
   * @param {number} [options.timeout=5000] - Default timeout in milliseconds for service calls.
960
   * @return {ParameterWatcher} - An instance of ParameterWatcher.
961
   */
962
  createParameterWatcher(remoteNodeName, parameterNames, options = {}) {
56✔
963
    const watcher = new ParameterWatcher(
57✔
964
      this,
965
      remoteNodeName,
966
      parameterNames,
967
      options
968
    );
969
    debug(
53✔
970
      'Finish creating parameter watcher for remote node = %s.',
971
      remoteNodeName
972
    );
973
    this._parameterWatchers.push(watcher);
53✔
974

975
    return watcher;
53✔
976
  }
977

978
  /**
979
   * Create a guard condition.
980
   * @param {Function} callback - The callback to be called when the guard condition is triggered.
981
   * @return {GuardCondition} - An instance of GuardCondition.
982
   */
983
  createGuardCondition(callback) {
984
    if (typeof callback !== 'function') {
3!
985
      throw new TypeValidationError('callback', callback, 'function', {
×
986
        nodeName: this.name(),
987
        entityType: 'guard_condition',
988
      });
989
    }
990

991
    let guard = GuardCondition.createGuardCondition(callback, this.context);
3✔
992
    debug('Finish creating guard condition');
3✔
993
    this._guards.push(guard);
3✔
994
    this.syncHandles();
3✔
995

996
    return guard;
3✔
997
  }
998

999
  /**
1000
   * Destroy all resource allocated by this node, including
1001
   * <code>Timer</code>s/<code>Publisher</code>s/<code>Subscription</code>s
1002
   * /<code>Client</code>s/<code>Service</code>s
1003
   * @return {undefined}
1004
   */
1005
  destroy() {
1006
    if (this.spinning) {
795✔
1007
      this.stop();
527✔
1008
    }
1009

1010
    // Action servers/clients require manual destruction due to circular reference with goal handles.
1011
    this._actionClients.forEach((actionClient) => actionClient.destroy());
795✔
1012
    this._actionServers.forEach((actionServer) => actionServer.destroy());
795✔
1013

1014
    this._parameterClients.forEach((paramClient) => paramClient.destroy());
795✔
1015
    this._parameterWatchers.forEach((watcher) => watcher.destroy());
795✔
1016

1017
    this.context.onNodeDestroyed(this);
795✔
1018

1019
    this.handle.release();
795✔
1020
    this._clock = null;
795✔
1021
    this._timers = [];
795✔
1022
    this._publishers = [];
795✔
1023
    this._subscriptions = [];
795✔
1024
    this._clients = [];
795✔
1025
    this._services = [];
795✔
1026
    this._guards = [];
795✔
1027
    this._actionClients = [];
795✔
1028
    this._actionServers = [];
795✔
1029
    this._parameterClients = [];
795✔
1030
    this._parameterWatchers = [];
795✔
1031

1032
    if (this._rateTimerServer) {
795✔
1033
      this._rateTimerServer.shutdown();
5✔
1034
      this._rateTimerServer = null;
5✔
1035
    }
1036
  }
1037

1038
  /**
1039
   * Destroy a Publisher.
1040
   * @param {Publisher} publisher - The Publisher to be destroyed.
1041
   * @return {undefined}
1042
   */
1043
  destroyPublisher(publisher) {
1044
    if (!(publisher instanceof Publisher)) {
9✔
1045
      throw new TypeValidationError(
2✔
1046
        'publisher',
1047
        publisher,
1048
        'Publisher instance',
1049
        {
1050
          nodeName: this.name(),
1051
        }
1052
      );
1053
    }
1054
    if (publisher.events) {
7!
1055
      publisher.events.forEach((event) => {
×
1056
        this._destroyEntity(event, this._events);
×
1057
      });
1058
      publisher.events = [];
×
1059
    }
1060
    this._destroyEntity(publisher, this._publishers, false);
7✔
1061
  }
1062

1063
  /**
1064
   * Destroy a Subscription.
1065
   * @param {Subscription} subscription - The Subscription to be destroyed.
1066
   * @return {undefined}
1067
   */
1068
  destroySubscription(subscription) {
1069
    if (!(subscription instanceof Subscription)) {
81✔
1070
      throw new TypeValidationError(
2✔
1071
        'subscription',
1072
        subscription,
1073
        'Subscription instance',
1074
        {
1075
          nodeName: this.name(),
1076
        }
1077
      );
1078
    }
1079
    if (subscription.events) {
79✔
1080
      subscription.events.forEach((event) => {
1✔
1081
        this._destroyEntity(event, this._events);
1✔
1082
      });
1083
      subscription.events = [];
1✔
1084
    }
1085

1086
    this._destroyEntity(subscription, this._subscriptions);
79✔
1087
  }
1088

1089
  /**
1090
   * Destroy a Client.
1091
   * @param {Client} client - The Client to be destroyed.
1092
   * @return {undefined}
1093
   */
1094
  destroyClient(client) {
1095
    if (!(client instanceof Client)) {
117✔
1096
      throw new TypeValidationError('client', client, 'Client instance', {
2✔
1097
        nodeName: this.name(),
1098
      });
1099
    }
1100
    this._destroyEntity(client, this._clients);
115✔
1101
  }
1102

1103
  /**
1104
   * Destroy a Service.
1105
   * @param {Service} service - The Service to be destroyed.
1106
   * @return {undefined}
1107
   */
1108
  destroyService(service) {
1109
    if (!(service instanceof Service)) {
8✔
1110
      throw new TypeValidationError('service', service, 'Service instance', {
2✔
1111
        nodeName: this.name(),
1112
      });
1113
    }
1114
    this._destroyEntity(service, this._services);
6✔
1115
  }
1116

1117
  /**
1118
   * Destroy a ParameterClient.
1119
   * @param {ParameterClient} parameterClient - The ParameterClient to be destroyed.
1120
   * @return {undefined}
1121
   */
1122
  destroyParameterClient(parameterClient) {
1123
    if (!(parameterClient instanceof ParameterClient)) {
54!
1124
      throw new TypeError('Invalid argument');
×
1125
    }
1126
    this._removeEntityFromArray(parameterClient, this._parameterClients);
54✔
1127
    parameterClient.destroy();
54✔
1128
  }
1129

1130
  /**
1131
   * Destroy a ParameterWatcher.
1132
   * @param {ParameterWatcher} watcher - The ParameterWatcher to be destroyed.
1133
   * @return {undefined}
1134
   */
1135
  destroyParameterWatcher(watcher) {
1136
    if (!(watcher instanceof ParameterWatcher)) {
1!
1137
      throw new TypeError('Invalid argument');
×
1138
    }
1139
    this._removeEntityFromArray(watcher, this._parameterWatchers);
1✔
1140
    watcher.destroy();
1✔
1141
  }
1142

1143
  /**
1144
   * Destroy a Timer.
1145
   * @param {Timer} timer - The Timer to be destroyed.
1146
   * @return {undefined}
1147
   */
1148
  destroyTimer(timer) {
1149
    if (!(timer instanceof Timer)) {
8✔
1150
      throw new TypeValidationError('timer', timer, 'Timer instance', {
2✔
1151
        nodeName: this.name(),
1152
      });
1153
    }
1154
    this._destroyEntity(timer, this._timers);
6✔
1155
  }
1156

1157
  /**
1158
   * Destroy a guard condition.
1159
   * @param {GuardCondition} guard - The guard condition to be destroyed.
1160
   * @return {undefined}
1161
   */
1162
  destroyGuardCondition(guard) {
1163
    if (!(guard instanceof GuardCondition)) {
3!
1164
      throw new TypeValidationError('guard', guard, 'GuardCondition instance', {
×
1165
        nodeName: this.name(),
1166
      });
1167
    }
1168
    this._destroyEntity(guard, this._guards);
3✔
1169
  }
1170

1171
  /**
1172
   * Get the name of the node.
1173
   * @return {string}
1174
   */
1175
  name() {
1176
    return rclnodejs.getNodeName(this.handle);
4,591✔
1177
  }
1178

1179
  /**
1180
   * Get the namespace of the node.
1181
   * @return {string}
1182
   */
1183
  namespace() {
1184
    return rclnodejs.getNamespace(this.handle);
4,339✔
1185
  }
1186

1187
  /**
1188
   * Get the context in which this node was created.
1189
   * @return {Context}
1190
   */
1191
  get context() {
1192
    return this._context;
6,017✔
1193
  }
1194

1195
  /**
1196
   * Get the nodes logger.
1197
   * @returns {Logger} - The logger for the node.
1198
   */
1199
  getLogger() {
1200
    return this._logger;
141✔
1201
  }
1202

1203
  /**
1204
   * Get the clock used by the node.
1205
   * @returns {Clock} - The nodes clock.
1206
   */
1207
  getClock() {
1208
    return this._clock;
86✔
1209
  }
1210

1211
  /**
1212
   * Get the current time using the node's clock.
1213
   * @returns {Timer} - The current time.
1214
   */
1215
  now() {
1216
    return this.getClock().now();
2✔
1217
  }
1218

1219
  /**
1220
   * Get the list of published topics discovered by the provided node for the remote node name.
1221
   * @param {string} nodeName - The name of the node.
1222
   * @param {string} namespace - The name of the namespace.
1223
   * @param {boolean} noDemangle - If true topic names and types returned will not be demangled, default: false.
1224
   * @return {Array<{name: string, types: Array<string>}>} - An array of the names and types.
1225
   */
1226
  getPublisherNamesAndTypesByNode(nodeName, namespace, noDemangle = false) {
×
1227
    return rclnodejs.getPublisherNamesAndTypesByNode(
×
1228
      this.handle,
1229
      nodeName,
1230
      namespace,
1231
      noDemangle
1232
    );
1233
  }
1234

1235
  /**
1236
   * Get the list of published topics discovered by the provided node for the remote node name.
1237
   * @param {string} nodeName - The name of the node.
1238
   * @param {string} namespace - The name of the namespace.
1239
   * @param {boolean} noDemangle - If true topic names and types returned will not be demangled, default: false.
1240
   * @return {Array<{name: string, types: Array<string>}>} - An array of the names and types.
1241
   */
1242
  getSubscriptionNamesAndTypesByNode(nodeName, namespace, noDemangle = false) {
×
1243
    return rclnodejs.getSubscriptionNamesAndTypesByNode(
×
1244
      this.handle,
1245
      nodeName,
1246
      namespace,
1247
      noDemangle
1248
    );
1249
  }
1250

1251
  /**
1252
   * Get service names and types for which a remote node has servers.
1253
   * @param {string} nodeName - The name of the node.
1254
   * @param {string} namespace - The name of the namespace.
1255
   * @return {Array<{name: string, types: Array<string>}>} - An array of the names and types.
1256
   */
1257
  getServiceNamesAndTypesByNode(nodeName, namespace) {
1258
    return rclnodejs.getServiceNamesAndTypesByNode(
×
1259
      this.handle,
1260
      nodeName,
1261
      namespace
1262
    );
1263
  }
1264

1265
  /**
1266
   * Get service names and types for which a remote node has clients.
1267
   * @param {string} nodeName - The name of the node.
1268
   * @param {string} namespace - The name of the namespace.
1269
   * @return {Array<{name: string, types: Array<string>}>} - An array of the names and types.
1270
   */
1271
  getClientNamesAndTypesByNode(nodeName, namespace) {
1272
    return rclnodejs.getClientNamesAndTypesByNode(
×
1273
      this.handle,
1274
      nodeName,
1275
      namespace
1276
    );
1277
  }
1278

1279
  /**
1280
   * Get the list of topics discovered by the provided node.
1281
   * @param {boolean} noDemangle - If true topic names and types returned will not be demangled, default: false.
1282
   * @return {Array<{name: string, types: Array<string>}>} - An array of the names and types.
1283
   */
1284
  getTopicNamesAndTypes(noDemangle = false) {
×
1285
    return rclnodejs.getTopicNamesAndTypes(this.handle, noDemangle);
×
1286
  }
1287

1288
  /**
1289
   * Get the list of services discovered by the provided node.
1290
   * @return {Array<{name: string, types: Array<string>}>} - An array of the names and types.
1291
   */
1292
  getServiceNamesAndTypes() {
1293
    return rclnodejs.getServiceNamesAndTypes(this.handle);
2✔
1294
  }
1295

1296
  /**
1297
   * Return a list of publishers on a given topic.
1298
   *
1299
   * The returned parameter is a list of TopicEndpointInfo objects, where each will contain
1300
   * the node name, node namespace, topic type, topic endpoint's GID, and its QoS profile.
1301
   *
1302
   * When the `no_mangle` parameter is `true`, the provided `topic` should be a valid
1303
   * topic name for the middleware (useful when combining ROS with native middleware (e.g. DDS)
1304
   * apps).  When the `no_mangle` parameter is `false`, the provided `topic` should
1305
   * follow ROS topic name conventions.
1306
   *
1307
   * `topic` may be a relative, private, or fully qualified topic name.
1308
   *  A relative or private topic will be expanded using this node's namespace and name.
1309
   *  The queried `topic` is not remapped.
1310
   *
1311
   * @param {string} topic - The topic on which to find the publishers.
1312
   * @param {boolean} [noDemangle=false] - If `true`, `topic` needs to be a valid middleware topic
1313
   *                               name, otherwise it should be a valid ROS topic name. Defaults to `false`.
1314
   * @returns {Array} - list of publishers
1315
   */
1316
  getPublishersInfoByTopic(topic, noDemangle = false) {
×
1317
    return rclnodejs.getPublishersInfoByTopic(
3✔
1318
      this.handle,
1319
      this._getValidatedTopic(topic, noDemangle),
1320
      noDemangle
1321
    );
1322
  }
1323

1324
  /**
1325
   * Return a list of subscriptions on a given topic.
1326
   *
1327
   * The returned parameter is a list of TopicEndpointInfo objects, where each will contain
1328
   * the node name, node namespace, topic type, topic endpoint's GID, and its QoS profile.
1329
   *
1330
   * When the `no_mangle` parameter is `true`, the provided `topic` should be a valid
1331
   * topic name for the middleware (useful when combining ROS with native middleware (e.g. DDS)
1332
   * apps).  When the `no_mangle` parameter is `false`, the provided `topic` should
1333
   * follow ROS topic name conventions.
1334
   *
1335
   * `topic` may be a relative, private, or fully qualified topic name.
1336
   *  A relative or private topic will be expanded using this node's namespace and name.
1337
   *  The queried `topic` is not remapped.
1338
   *
1339
   * @param {string} topic - The topic on which to find the subscriptions.
1340
   * @param {boolean} [noDemangle=false] -  If `true`, `topic` needs to be a valid middleware topic
1341
                                    name, otherwise it should be a valid ROS topic name. Defaults to `false`.
1342
   * @returns {Array} - list of subscriptions
1343
   */
1344
  getSubscriptionsInfoByTopic(topic, noDemangle = false) {
×
1345
    return rclnodejs.getSubscriptionsInfoByTopic(
2✔
1346
      this.handle,
1347
      this._getValidatedTopic(topic, noDemangle),
1348
      noDemangle
1349
    );
1350
  }
1351

1352
  /**
1353
   * Get the list of nodes discovered by the provided node.
1354
   * @return {Array<string>} - An array of the names.
1355
   */
1356
  getNodeNames() {
1357
    return this.getNodeNamesAndNamespaces().map((item) => item.name);
41✔
1358
  }
1359

1360
  /**
1361
   * Get the list of nodes and their namespaces discovered by the provided node.
1362
   * @return {Array<{name: string, namespace: string}>} An array of the names and namespaces.
1363
   */
1364
  getNodeNamesAndNamespaces() {
1365
    return rclnodejs.getNodeNames(this.handle, /*getEnclaves=*/ false);
17✔
1366
  }
1367

1368
  /**
1369
   * Get the list of nodes and their namespaces with enclaves discovered by the provided node.
1370
   * @return {Array<{name: string, namespace: string, enclave: string}>} An array of the names, namespaces and enclaves.
1371
   */
1372
  getNodeNamesAndNamespacesWithEnclaves() {
1373
    return rclnodejs.getNodeNames(this.handle, /*getEnclaves=*/ true);
1✔
1374
  }
1375

1376
  /**
1377
   * Return the number of publishers on a given topic.
1378
   * @param {string} topic - The name of the topic.
1379
   * @returns {number} - Number of publishers on the given topic.
1380
   */
1381
  countPublishers(topic) {
1382
    let expandedTopic = rclnodejs.expandTopicName(
6✔
1383
      topic,
1384
      this.name(),
1385
      this.namespace()
1386
    );
1387
    rclnodejs.validateTopicName(expandedTopic);
6✔
1388

1389
    return rclnodejs.countPublishers(this.handle, expandedTopic);
6✔
1390
  }
1391

1392
  /**
1393
   * Return the number of subscribers on a given topic.
1394
   * @param {string} topic - The name of the topic.
1395
   * @returns {number} - Number of subscribers on the given topic.
1396
   */
1397
  countSubscribers(topic) {
1398
    let expandedTopic = rclnodejs.expandTopicName(
6✔
1399
      topic,
1400
      this.name(),
1401
      this.namespace()
1402
    );
1403
    rclnodejs.validateTopicName(expandedTopic);
6✔
1404

1405
    return rclnodejs.countSubscribers(this.handle, expandedTopic);
6✔
1406
  }
1407

1408
  /**
1409
   * Get the number of clients on a given service name.
1410
   * @param {string} serviceName - the service name
1411
   * @returns {Number}
1412
   */
1413
  countClients(serviceName) {
1414
    if (DistroUtils.getDistroId() <= DistroUtils.getDistroId('humble')) {
2!
1415
      console.warn('countClients is not supported by this version of ROS 2');
×
1416
      return null;
×
1417
    }
1418
    return rclnodejs.countClients(this.handle, serviceName);
2✔
1419
  }
1420

1421
  /**
1422
   * Get the number of services on a given service name.
1423
   * @param {string} serviceName - the service name
1424
   * @returns {Number}
1425
   */
1426
  countServices(serviceName) {
1427
    if (DistroUtils.getDistroId() <= DistroUtils.getDistroId('humble')) {
1!
1428
      console.warn('countServices is not supported by this version of ROS 2');
×
1429
      return null;
×
1430
    }
1431
    return rclnodejs.countServices(this.handle, serviceName);
1✔
1432
  }
1433

1434
  /**
1435
   * Get the list of parameter-overrides found on the commandline and
1436
   * in the NodeOptions.parameter_overrides property.
1437
   *
1438
   * @return {Array<Parameter>} - An array of Parameters.
1439
   */
1440
  getParameterOverrides() {
1441
    return Array.from(this._parameterOverrides.values());
8✔
1442
  }
1443

1444
  /**
1445
   * Declare a parameter.
1446
   *
1447
   * Internally, register a parameter and it's descriptor.
1448
   * If a parameter-override exists, it's value will replace that of the parameter
1449
   * unless ignoreOverride is true.
1450
   * If the descriptor is undefined, then a ParameterDescriptor will be inferred
1451
   * from the parameter's state.
1452
   *
1453
   * If a parameter by the same name has already been declared then an Error is thrown.
1454
   * A parameter must be undeclared before attempting to redeclare it.
1455
   *
1456
   * @param {Parameter} parameter - Parameter to declare.
1457
   * @param {ParameterDescriptor} [descriptor] - Optional descriptor for parameter.
1458
   * @param {boolean} [ignoreOverride] - When true disregard any parameter-override that may be present.
1459
   * @return {Parameter} - The newly declared parameter.
1460
   */
1461
  declareParameter(parameter, descriptor, ignoreOverride = false) {
2,128✔
1462
    const parameters = this.declareParameters(
2,129✔
1463
      [parameter],
1464
      descriptor ? [descriptor] : [],
2,129✔
1465
      ignoreOverride
1466
    );
1467
    return parameters.length == 1 ? parameters[0] : null;
2,129!
1468
  }
1469

1470
  /**
1471
   * Declare a list of parameters.
1472
   *
1473
   * Internally register parameters with their corresponding descriptor one by one
1474
   * in the order they are provided. This is an atomic operation. If an error
1475
   * occurs the process halts and no further parameters are declared.
1476
   * Parameters that have already been processed are undeclared.
1477
   *
1478
   * While descriptors is an optional parameter, when provided there must be
1479
   * a descriptor for each parameter; otherwise an Error is thrown.
1480
   * If descriptors is not provided then a descriptor will be inferred
1481
   * from each parameter's state.
1482
   *
1483
   * When a parameter-override is available, the parameter's value
1484
   * will be replaced with that of the parameter-override unless ignoreOverrides
1485
   * is true.
1486
   *
1487
   * If a parameter by the same name has already been declared then an Error is thrown.
1488
   * A parameter must be undeclared before attempting to redeclare it.
1489
   *
1490
   * Prior to declaring the parameters each SetParameterEventCallback registered
1491
   * using setOnParameterEventCallback() is called in succession with the parameters
1492
   * list. Any SetParameterEventCallback that retuns does not return a successful
1493
   * result will cause the entire operation to terminate with no changes to the
1494
   * parameters. When all SetParameterEventCallbacks return successful then the
1495
   * list of parameters is updated.
1496
   *
1497
   * @param {Parameter[]} parameters - The parameters to declare.
1498
   * @param {ParameterDescriptor[]} [descriptors] - Optional descriptors,
1499
   *    a 1-1 correspondence with parameters.
1500
   * @param {boolean} ignoreOverrides - When true, parameter-overrides are
1501
   *    not considered, i.e.,ignored.
1502
   * @return {Parameter[]} - The declared parameters.
1503
   */
1504
  declareParameters(parameters, descriptors = [], ignoreOverrides = false) {
×
1505
    if (!Array.isArray(parameters)) {
2,129!
1506
      throw new TypeValidationError('parameters', parameters, 'Array', {
×
1507
        nodeName: this.name(),
1508
      });
1509
    }
1510
    if (!Array.isArray(descriptors)) {
2,129!
1511
      throw new TypeValidationError('descriptors', descriptors, 'Array', {
×
1512
        nodeName: this.name(),
1513
      });
1514
    }
1515
    if (descriptors.length > 0 && parameters.length !== descriptors.length) {
2,129!
1516
      throw new ValidationError(
×
1517
        'Each parameter must have a corresponding ParameterDescriptor',
1518
        {
1519
          code: 'PARAMETER_DESCRIPTOR_MISMATCH',
1520
          argumentName: 'descriptors',
1521
          providedValue: descriptors.length,
1522
          expectedType: `Array with length ${parameters.length}`,
1523
          nodeName: this.name(),
1524
        }
1525
      );
1526
    }
1527

1528
    const declaredDescriptors = [];
2,129✔
1529
    const declaredParameters = [];
2,129✔
1530
    const declaredParameterCollisions = [];
2,129✔
1531
    for (let i = 0; i < parameters.length; i++) {
2,129✔
1532
      let parameter =
1533
        !ignoreOverrides && this._parameterOverrides.has(parameters[i].name)
2,129✔
1534
          ? this._parameterOverrides.get(parameters[i].name)
1535
          : parameters[i];
1536

1537
      // stop processing parameters that have already been declared
1538
      if (this._parameters.has(parameter.name)) {
2,129!
1539
        declaredParameterCollisions.push(parameter);
×
1540
        continue;
×
1541
      }
1542

1543
      // create descriptor for parameter if not provided
1544
      let descriptor =
1545
        descriptors.length > 0
2,129✔
1546
          ? descriptors[i]
1547
          : ParameterDescriptor.fromParameter(parameter);
1548

1549
      descriptor.validate();
2,129✔
1550

1551
      declaredDescriptors.push(descriptor);
2,129✔
1552
      declaredParameters.push(parameter);
2,129✔
1553
    }
1554

1555
    if (declaredParameterCollisions.length > 0) {
2,129!
1556
      const errorMsg =
1557
        declaredParameterCollisions.length == 1
×
1558
          ? `Parameter(${declaredParameterCollisions[0]}) already declared.`
1559
          : `Multiple parameters already declared, e.g., Parameter(${declaredParameterCollisions[0]}).`;
1560
      throw new Error(errorMsg);
×
1561
    }
1562

1563
    // register descriptor
1564
    for (const descriptor of declaredDescriptors) {
2,129✔
1565
      this._parameterDescriptors.set(descriptor.name, descriptor);
2,129✔
1566
    }
1567

1568
    const result = this._setParametersAtomically(declaredParameters, true);
2,129✔
1569
    if (!result.successful) {
2,129!
1570
      // unregister descriptors
1571
      for (const descriptor of declaredDescriptors) {
×
1572
        this._parameterDescriptors.delete(descriptor.name);
×
1573
      }
1574

1575
      throw new Error(result.reason);
×
1576
    }
1577

1578
    return this.getParameters(declaredParameters.map((param) => param.name));
2,129✔
1579
  }
1580

1581
  /**
1582
   * Undeclare a parameter.
1583
   *
1584
   * Readonly parameters can not be undeclared or updated.
1585
   * @param {string} name - Name of parameter to undeclare.
1586
   * @return {undefined} -
1587
   */
1588
  undeclareParameter(name) {
1589
    if (!this.hasParameter(name)) return;
1!
1590

1591
    const descriptor = this.getParameterDescriptor(name);
1✔
1592
    if (descriptor.readOnly) {
1!
1593
      throw new Error(
×
1594
        `${name} parameter is read-only and can not be undeclared`
1595
      );
1596
    }
1597

1598
    this._parameters.delete(name);
1✔
1599
    this._parameterDescriptors.delete(name);
1✔
1600
  }
1601

1602
  /**
1603
   * Determine if a parameter has been declared.
1604
   * @param {string} name - name of parameter
1605
   * @returns {boolean} - Return true if parameter is declared; false otherwise.
1606
   */
1607
  hasParameter(name) {
1608
    return this._parameters.has(name);
5,275✔
1609
  }
1610

1611
  /**
1612
   * Get a declared parameter by name.
1613
   *
1614
   * If unable to locate a declared parameter then a
1615
   * parameter with type == PARAMETER_NOT_SET is returned.
1616
   *
1617
   * @param {string} name - The name of the parameter.
1618
   * @return {Parameter} - The parameter.
1619
   */
1620
  getParameter(name) {
1621
    return this.getParameters([name])[0];
1,558✔
1622
  }
1623

1624
  /**
1625
   * Get a list of parameters.
1626
   *
1627
   * Find and return the declared parameters.
1628
   * If no names are provided return all declared parameters.
1629
   *
1630
   * If unable to locate a declared parameter then a
1631
   * parameter with type == PARAMETER_NOT_SET is returned in
1632
   * it's place.
1633
   *
1634
   * @param {string[]} [names] - The names of the declared parameters
1635
   *    to find or null indicating to return all declared parameters.
1636
   * @return {Parameter[]} - The parameters.
1637
   */
1638
  getParameters(names = []) {
12✔
1639
    let params = [];
3,725✔
1640

1641
    if (names.length == 0) {
3,725✔
1642
      // get all parameters
1643
      params = [...this._parameters.values()];
12✔
1644
      return params;
12✔
1645
    }
1646

1647
    for (const name of names) {
3,713✔
1648
      const param = this.hasParameter(name)
3,720✔
1649
        ? this._parameters.get(name)
1650
        : new Parameter(name, ParameterType.PARAMETER_NOT_SET);
1651

1652
      params.push(param);
3,720✔
1653
    }
1654

1655
    return params;
3,713✔
1656
  }
1657

1658
  /**
1659
   * Get the types of given parameters.
1660
   *
1661
   * Return the types of given parameters.
1662
   *
1663
   * @param {string[]} [names] - The names of the declared parameters.
1664
   * @return {Uint8Array} - The types.
1665
   */
1666
  getParameterTypes(names = []) {
×
1667
    let types = [];
3✔
1668

1669
    for (const name of names) {
3✔
1670
      const descriptor = this._parameterDescriptors.get(name);
7✔
1671
      if (descriptor) {
7!
1672
        types.push(descriptor.type);
7✔
1673
      }
1674
    }
1675
    return types;
3✔
1676
  }
1677

1678
  /**
1679
   * Get the names of all declared parameters.
1680
   *
1681
   * @return {Array<string>} - The declared parameter names or empty array if
1682
   *    no parameters have been declared.
1683
   */
1684
  getParameterNames() {
1685
    return this.getParameters().map((param) => param.name);
53✔
1686
  }
1687

1688
  /**
1689
   * Determine if a parameter descriptor exists.
1690
   *
1691
   * @param {string} name - The name of a descriptor to for.
1692
   * @return {boolean} - true if a descriptor has been declared; otherwise false.
1693
   */
1694
  hasParameterDescriptor(name) {
1695
    return !!this.getParameterDescriptor(name);
2,154✔
1696
  }
1697

1698
  /**
1699
   * Get a declared parameter descriptor by name.
1700
   *
1701
   * If unable to locate a declared parameter descriptor then a
1702
   * descriptor with type == PARAMETER_NOT_SET is returned.
1703
   *
1704
   * @param {string} name - The name of the parameter descriptor to find.
1705
   * @return {ParameterDescriptor} - The parameter descriptor.
1706
   */
1707
  getParameterDescriptor(name) {
1708
    return this.getParameterDescriptors([name])[0];
4,309✔
1709
  }
1710

1711
  /**
1712
   * Find a list of declared ParameterDescriptors.
1713
   *
1714
   * If no names are provided return all declared descriptors.
1715
   *
1716
   * If unable to locate a declared descriptor then a
1717
   * descriptor with type == PARAMETER_NOT_SET is returned in
1718
   * it's place.
1719
   *
1720
   * @param {string[]} [names] - The names of the declared parameter
1721
   *    descriptors to find or null indicating to return all declared descriptors.
1722
   * @return {ParameterDescriptor[]} - The parameter descriptors.
1723
   */
1724
  getParameterDescriptors(names = []) {
×
1725
    let descriptors = [];
4,312✔
1726

1727
    if (names.length == 0) {
4,312!
1728
      // get all parameters
1729
      descriptors = [...this._parameterDescriptors.values()];
×
1730
      return descriptors;
×
1731
    }
1732

1733
    for (const name of names) {
4,312✔
1734
      let descriptor = this._parameterDescriptors.get(name);
4,314✔
1735
      if (!descriptor) {
4,314!
1736
        descriptor = new ParameterDescriptor(
×
1737
          name,
1738
          ParameterType.PARAMETER_NOT_SET
1739
        );
1740
      }
1741
      descriptors.push(descriptor);
4,314✔
1742
    }
1743

1744
    return descriptors;
4,312✔
1745
  }
1746

1747
  /**
1748
   * Replace a declared parameter.
1749
   *
1750
   * The parameter being replaced must be a declared parameter who's descriptor
1751
   * is not readOnly; otherwise an Error is thrown.
1752
   *
1753
   * @param {Parameter} parameter - The new parameter.
1754
   * @return {rclnodejs.rcl_interfaces.msg.SetParameterResult} - The result of the operation.
1755
   */
1756
  setParameter(parameter) {
1757
    const results = this.setParameters([parameter]);
10✔
1758
    return results[0];
10✔
1759
  }
1760

1761
  /**
1762
   * Replace a list of declared parameters.
1763
   *
1764
   * Declared parameters are replaced in the order they are provided and
1765
   * a ParameterEvent is published for each individual parameter change.
1766
   *
1767
   * Prior to setting the parameters each SetParameterEventCallback registered
1768
   * using setOnParameterEventCallback() is called in succession with the parameters
1769
   * list. Any SetParameterEventCallback that retuns does not return a successful
1770
   * result will cause the entire operation to terminate with no changes to the
1771
   * parameters. When all SetParameterEventCallbacks return successful then the
1772
   * list of parameters is updated.
1773
   *
1774
   * If an error occurs, the process is stopped and returned. Parameters
1775
   * set before an error remain unchanged.
1776
   *
1777
   * @param {Parameter[]} parameters - The parameters to set.
1778
   * @return {rclnodejs.rcl_interfaces.msg.SetParameterResult[]} - A list of SetParameterResult, one for each parameter that was set.
1779
   */
1780
  setParameters(parameters = []) {
×
1781
    return parameters.map((parameter) =>
21✔
1782
      this.setParametersAtomically([parameter])
24✔
1783
    );
1784
  }
1785

1786
  /**
1787
   * Repalce a list of declared parameters atomically.
1788
   *
1789
   * Declared parameters are replaced in the order they are provided.
1790
   * A single ParameterEvent is published collectively for all changed
1791
   * parameters.
1792
   *
1793
   * Prior to setting the parameters each SetParameterEventCallback registered
1794
   * using setOnParameterEventCallback() is called in succession with the parameters
1795
   * list. Any SetParameterEventCallback that retuns does not return a successful
1796
   * result will cause the entire operation to terminate with no changes to the
1797
   * parameters. When all SetParameterEventCallbacks return successful then the
1798
   * list of parameters is updated.d
1799
   *
1800
   * If an error occurs, the process stops immediately. All parameters updated to
1801
   * the point of the error are reverted to their previous state.
1802
   *
1803
   * @param {Parameter[]} parameters - The parameters to set.
1804
   * @return {rclnodejs.rcl_interfaces.msg.SetParameterResult} - describes the result of setting 1 or more parameters.
1805
   */
1806
  setParametersAtomically(parameters = []) {
×
1807
    return this._setParametersAtomically(parameters);
25✔
1808
  }
1809

1810
  /**
1811
   * Internal method for updating parameters atomically.
1812
   *
1813
   * Prior to setting the parameters each SetParameterEventCallback registered
1814
   * using setOnParameterEventCallback() is called in succession with the parameters
1815
   * list. Any SetParameterEventCallback that retuns does not return a successful
1816
   * result will cause the entire operation to terminate with no changes to the
1817
   * parameters. When all SetParameterEventCallbacks return successful then the
1818
   * list of parameters is updated.
1819
   *
1820
   * @param {Paramerter[]} parameters - The parameters to update.
1821
   * @param {boolean} declareParameterMode - When true parameters are being declared;
1822
   *    otherwise they are being changed.
1823
   * @return {SetParameterResult} - A single collective result.
1824
   */
1825
  _setParametersAtomically(parameters = [], declareParameterMode = false) {
25!
1826
    let result = this._validateParameters(parameters, declareParameterMode);
2,154✔
1827
    if (!result.successful) {
2,154!
1828
      return result;
×
1829
    }
1830

1831
    // give all SetParametersCallbacks a chance to veto this change
1832
    for (const callback of this._setParametersCallbacks) {
2,154✔
1833
      result = callback(parameters);
1,387✔
1834
      if (!result.successful) {
1,387✔
1835
        // a callback has vetoed a parameter change
1836
        return result;
1✔
1837
      }
1838
    }
1839

1840
    // collectively track updates to parameters for use
1841
    // when publishing a ParameterEvent
1842
    const newParameters = [];
2,153✔
1843
    const changedParameters = [];
2,153✔
1844
    const deletedParameters = [];
2,153✔
1845

1846
    for (const parameter of parameters) {
2,153✔
1847
      if (parameter.type == ParameterType.PARAMETER_NOT_SET) {
2,153✔
1848
        this.undeclareParameter(parameter.name);
1✔
1849
        deletedParameters.push(parameter);
1✔
1850
      } else {
1851
        this._parameters.set(parameter.name, parameter);
2,152✔
1852
        if (declareParameterMode) {
2,152✔
1853
          newParameters.push(parameter);
2,129✔
1854
        } else {
1855
          changedParameters.push(parameter);
23✔
1856
        }
1857
      }
1858
    }
1859

1860
    // create ParameterEvent
1861
    const parameterEvent = new (loader.loadInterface(
2,153✔
1862
      PARAMETER_EVENT_MSG_TYPE
1863
    ))();
1864

1865
    const { seconds, nanoseconds } = this._clock.now().secondsAndNanoseconds;
2,153✔
1866
    parameterEvent.stamp = {
2,153✔
1867
      sec: Number(seconds),
1868
      nanosec: Number(nanoseconds),
1869
    };
1870

1871
    parameterEvent.node =
2,153✔
1872
      this.namespace() === '/'
2,153✔
1873
        ? this.namespace() + this.name()
1874
        : this.namespace() + '/' + this.name();
1875

1876
    if (newParameters.length > 0) {
2,153✔
1877
      parameterEvent['new_parameters'] = newParameters.map((parameter) =>
2,129✔
1878
        parameter.toParameterMessage()
2,129✔
1879
      );
1880
    }
1881
    if (changedParameters.length > 0) {
2,153✔
1882
      parameterEvent['changed_parameters'] = changedParameters.map(
23✔
1883
        (parameter) => parameter.toParameterMessage()
23✔
1884
      );
1885
    }
1886
    if (deletedParameters.length > 0) {
2,153✔
1887
      parameterEvent['deleted_parameters'] = deletedParameters.map(
1✔
1888
        (parameter) => parameter.toParameterMessage()
1✔
1889
      );
1890
    }
1891

1892
    // Publish ParameterEvent.
1893
    this._parameterEventPublisher.publish(parameterEvent);
2,153✔
1894

1895
    return {
2,153✔
1896
      successful: true,
1897
      reason: '',
1898
    };
1899
  }
1900

1901
  /**
1902
   * This callback is called when declaring a parameter or setting a parameter.
1903
   * The callback is provided a list of parameters and returns a SetParameterResult
1904
   * to indicate approval or veto of the operation.
1905
   *
1906
   * @callback SetParametersCallback
1907
   * @param {Parameter[]} parameters - The message published
1908
   * @returns {rcl_interfaces.msg.SetParameterResult} -
1909
   *
1910
   * @see [Node.addOnSetParametersCallback]{@link Node#addOnSetParametersCallback}
1911
   * @see [Node.removeOnSetParametersCallback]{@link Node#removeOnSetParametersCallback}
1912
   */
1913

1914
  /**
1915
   * Add a callback to the front of the list of callbacks invoked for parameter declaration
1916
   * and setting. No checks are made for duplicate callbacks.
1917
   *
1918
   * @param {SetParametersCallback} callback - The callback to add.
1919
   * @returns {undefined}
1920
   */
1921
  addOnSetParametersCallback(callback) {
1922
    this._setParametersCallbacks.unshift(callback);
781✔
1923
  }
1924

1925
  /**
1926
   * Remove a callback from the list of SetParametersCallbacks.
1927
   * If the callback is not found the process is a nop.
1928
   *
1929
   * @param {SetParametersCallback} callback - The callback to be removed
1930
   * @returns {undefined}
1931
   */
1932
  removeOnSetParametersCallback(callback) {
1933
    const idx = this._setParametersCallbacks.indexOf(callback);
2✔
1934
    if (idx > -1) {
2!
1935
      this._setParametersCallbacks.splice(idx, 1);
2✔
1936
    }
1937
  }
1938

1939
  /**
1940
   * Get the fully qualified name of the node.
1941
   *
1942
   * @returns {string} - String containing the fully qualified name of the node.
1943
   */
1944
  getFullyQualifiedName() {
1945
    return rclnodejs.getFullyQualifiedName(this.handle);
1✔
1946
  }
1947

1948
  /**
1949
   * Get the RMW implementation identifier
1950
   * @returns {string} - The RMW implementation identifier.
1951
   */
1952
  getRMWImplementationIdentifier() {
1953
    return rclnodejs.getRMWImplementationIdentifier();
1✔
1954
  }
1955

1956
  /**
1957
   * Return a topic name expanded and remapped.
1958
   * @param {string} topicName - Topic name to be expanded and remapped.
1959
   * @param {boolean} [onlyExpand=false] - If `true`, remapping rules won't be applied.
1960
   * @returns {string} - A fully qualified topic name.
1961
   */
1962
  resolveTopicName(topicName, onlyExpand = false) {
2✔
1963
    if (typeof topicName !== 'string') {
3!
1964
      throw new TypeValidationError('topicName', topicName, 'string', {
×
1965
        nodeName: this.name(),
1966
      });
1967
    }
1968
    return rclnodejs.resolveName(
3✔
1969
      this.handle,
1970
      topicName,
1971
      onlyExpand,
1972
      /*isService=*/ false
1973
    );
1974
  }
1975

1976
  /**
1977
   * Return a service name expanded and remapped.
1978
   * @param {string} service - Service name to be expanded and remapped.
1979
   * @param {boolean} [onlyExpand=false] - If `true`, remapping rules won't be applied.
1980
   * @returns {string} - A fully qualified service name.
1981
   */
1982
  resolveServiceName(service, onlyExpand = false) {
2✔
1983
    if (typeof service !== 'string') {
3!
1984
      throw new TypeValidationError('service', service, 'string', {
×
1985
        nodeName: this.name(),
1986
      });
1987
    }
1988
    return rclnodejs.resolveName(
3✔
1989
      this.handle,
1990
      service,
1991
      onlyExpand,
1992
      /*isService=*/ true
1993
    );
1994
  }
1995

1996
  // returns on 1st error or result {successful, reason}
1997
  _validateParameters(parameters = [], declareParameterMode = false) {
×
1998
    for (const parameter of parameters) {
2,154✔
1999
      // detect invalid parameter
2000
      try {
2,154✔
2001
        parameter.validate();
2,154✔
2002
      } catch {
2003
        return {
×
2004
          successful: false,
2005
          reason: `Invalid ${parameter.name}`,
2006
        };
2007
      }
2008

2009
      // detect undeclared parameter
2010
      if (!this.hasParameterDescriptor(parameter.name)) {
2,154!
2011
        return {
×
2012
          successful: false,
2013
          reason: `Parameter ${parameter.name} has not been declared`,
2014
        };
2015
      }
2016

2017
      // detect readonly parameter that can not be updated
2018
      const descriptor = this.getParameterDescriptor(parameter.name);
2,154✔
2019
      if (!declareParameterMode && descriptor.readOnly) {
2,154!
2020
        return {
×
2021
          successful: false,
2022
          reason: `Parameter ${parameter.name} is readonly`,
2023
        };
2024
      }
2025

2026
      // validate parameter against descriptor if not an undeclare action
2027
      if (parameter.type != ParameterType.PARAMETER_NOT_SET) {
2,154✔
2028
        try {
2,153✔
2029
          descriptor.validateParameter(parameter);
2,153✔
2030
        } catch {
2031
          return {
×
2032
            successful: false,
2033
            reason: `Parameter ${parameter.name} does not  readonly`,
2034
          };
2035
        }
2036
      }
2037
    }
2038

2039
    return {
2,154✔
2040
      successful: true,
2041
      reason: null,
2042
    };
2043
  }
2044

2045
  // Get a Map(nodeName->Parameter[]) of CLI parameter args that
2046
  // apply to 'this' node, .e.g., -p mynode:foo:=bar -p hello:=world
2047
  _getNativeParameterOverrides() {
2048
    const overrides = new Map();
773✔
2049

2050
    // Get native parameters from rcl context->global_arguments.
2051
    // rclnodejs returns an array of objects, 1 for each node e.g., -p my_node:foo:=bar,
2052
    // and a node named '/**' for global parameter rules,
2053
    // i.e., does not include a node identifier, e.g., -p color:=red
2054
    // {
2055
    //   name: string // node name
2056
    //   parameters[] = {
2057
    //     name: string
2058
    //     type: uint
2059
    //     value: object
2060
    // }
2061
    const cliParamOverrideData = rclnodejs.getParameterOverrides(
773✔
2062
      this.context.handle
2063
    );
2064

2065
    // convert native CLI parameterOverrides to Map<nodeName,Array<ParameterOverride>>
2066
    const cliParamOverrides = new Map();
773✔
2067
    if (cliParamOverrideData) {
773✔
2068
      for (let nodeParamData of cliParamOverrideData) {
8✔
2069
        const nodeName = nodeParamData.name;
12✔
2070
        const nodeParamOverrides = [];
12✔
2071
        for (let paramData of nodeParamData.parameters) {
12✔
2072
          const paramOverride = new Parameter(
17✔
2073
            paramData.name,
2074
            paramData.type,
2075
            paramData.value
2076
          );
2077
          nodeParamOverrides.push(paramOverride);
17✔
2078
        }
2079
        cliParamOverrides.set(nodeName, nodeParamOverrides);
12✔
2080
      }
2081
    }
2082

2083
    // collect global CLI global parameters, name == /**
2084
    let paramOverrides = cliParamOverrides.get('/**'); // array of ParameterOverrides
773✔
2085
    if (paramOverrides) {
773✔
2086
      for (const parameter of paramOverrides) {
5✔
2087
        overrides.set(parameter.name, parameter);
6✔
2088
      }
2089
    }
2090

2091
    // merge CLI node parameterOverrides with global parameterOverrides, replace existing
2092
    paramOverrides = cliParamOverrides.get(this.name()); // array of ParameterOverrides
773✔
2093
    if (paramOverrides) {
773✔
2094
      for (const parameter of paramOverrides) {
5✔
2095
        overrides.set(parameter.name, parameter);
7✔
2096
      }
2097
    }
2098

2099
    return overrides;
773✔
2100
  }
2101

2102
  /**
2103
   * Invokes the callback with a raw message of the given type. After the callback completes
2104
   * the message will be destroyed.
2105
   * @param {function} Type - Message type to create.
2106
   * @param {function} callback - Callback to invoke. First parameter will be the raw message,
2107
   * and the second is a function to retrieve the deserialized message.
2108
   * @returns {undefined}
2109
   */
2110
  _runWithMessageType(Type, callback) {
2111
    let message = new Type();
931✔
2112

2113
    callback(message.toRawROS(), () => {
931✔
2114
      let result = new Type();
684✔
2115
      result.deserialize(message.refObject);
684✔
2116

2117
      return result;
684✔
2118
    });
2119

2120
    Type.destroyRawROS(message);
931✔
2121
  }
2122

2123
  _addActionClient(actionClient) {
2124
    this._actionClients.push(actionClient);
41✔
2125
    this.syncHandles();
41✔
2126
  }
2127

2128
  _addActionServer(actionServer) {
2129
    this._actionServers.push(actionServer);
41✔
2130
    this.syncHandles();
41✔
2131
  }
2132

2133
  _getValidatedTopic(topicName, noDemangle) {
2134
    if (noDemangle) {
5!
2135
      return topicName;
×
2136
    }
2137
    const fqTopicName = rclnodejs.expandTopicName(
5✔
2138
      topicName,
2139
      this.name(),
2140
      this.namespace()
2141
    );
2142
    validateFullTopicName(fqTopicName);
5✔
2143
    return rclnodejs.remapTopicName(this.handle, fqTopicName);
5✔
2144
  }
2145
}
2146

2147
/**
2148
 * Create an Options instance initialized with default values.
2149
 * @returns {Options} - The new initialized instance.
2150
 * @static
2151
 * @example
2152
 * {
2153
 *   enableTypedArray: true,
2154
 *   isRaw: false,
2155
 *   qos: QoS.profileDefault,
2156
 *   contentFilter: undefined,
2157
 *   serializationMode: 'default',
2158
 * }
2159
 */
2160
Node.getDefaultOptions = function () {
26✔
2161
  return {
7,220✔
2162
    enableTypedArray: true,
2163
    isRaw: false,
2164
    qos: QoS.profileDefault,
2165
    contentFilter: undefined,
2166
    serializationMode: 'default',
2167
  };
2168
};
2169

2170
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