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

RobotWebTools / rclnodejs / 14851952674

06 May 2025 04:56AM UTC coverage: 84.578% (-0.5%) from 85.042%
14851952674

push

github

minggangw
Pump to 1.0.0 (#1120)

711 of 934 branches covered (76.12%)

Branch coverage included in aggregate %.

1746 of 1971 relevant lines covered (88.58%)

1005.7 hits per line

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

89.0
/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('bindings')('rclnodejs');
78✔
18

19
const ActionInterfaces = require('./action/interfaces.js');
78✔
20
const Client = require('./client.js');
78✔
21
const Clock = require('./clock.js');
78✔
22
const Context = require('./context.js');
78✔
23
const debug = require('debug')('rclnodejs:node');
78✔
24
const DistroUtils = require('./distro.js');
78✔
25
const GuardCondition = require('./guard_condition.js');
78✔
26
const loader = require('./interface_loader.js');
78✔
27
const Logging = require('./logging.js');
78✔
28
const NodeOptions = require('./node_options.js');
78✔
29
const {
30
  ParameterType,
31
  Parameter,
32
  ParameterDescriptor,
33
} = require('./parameter.js');
78✔
34
const ParameterService = require('./parameter_service.js');
78✔
35
const Publisher = require('./publisher.js');
78✔
36
const QoS = require('./qos.js');
78✔
37
const Rates = require('./rate.js');
78✔
38
const Service = require('./service.js');
78✔
39
const Subscription = require('./subscription.js');
78✔
40
const TimeSource = require('./time_source.js');
78✔
41
const Timer = require('./timer.js');
78✔
42
const Entity = require('./entity.js');
78✔
43

44
// Parameter event publisher constants
45
const PARAMETER_EVENT_MSG_TYPE = 'rcl_interfaces/msg/ParameterEvent';
78✔
46
const PARAMETER_EVENT_TOPIC = 'parameter_events';
78✔
47

48
/**
49
 * @class - Class representing a Node in ROS
50
 */
51

52
class Node extends rclnodejs.ShadowNode {
53
  /**
54
   * Create a ROS2Node.
55
   * model using the {@link https://github.com/ros2/rcl/tree/master/rcl_lifecycle|ros2 client library (rcl) lifecyle api}.
56
   * @param {string} nodeName - The name used to register in ROS.
57
   * @param {string} [namespace=''] - The namespace used in ROS.
58
   * @param {Context} [context=Context.defaultContext()] - The context to create the node in.
59
   * @param {NodeOptions} [options=NodeOptions.defaultOptions] - The options to configure the new node behavior.
60
   * @throws {Error} If the given context is not registered.
61
   */
62
  constructor(
63
    nodeName,
64
    namespace = '',
93✔
65
    context = Context.defaultContext(),
198✔
66
    options = NodeOptions.defaultOptions
204✔
67
  ) {
68
    super();
1,788✔
69

70
    if (typeof nodeName !== 'string' || typeof namespace !== 'string') {
1,788✔
71
      throw new TypeError('Invalid argument.');
66✔
72
    }
73

74
    this._init(nodeName, namespace, options, context);
1,722✔
75
    debug(
1,701✔
76
      'Finish initializing node, name = %s and namespace = %s.',
77
      nodeName,
78
      namespace
79
    );
80
  }
81

82
  _init(name, namespace, options, context) {
83
    this.handle = rclnodejs.createNode(name, namespace, context.handle);
1,722✔
84
    Object.defineProperty(this, 'handle', {
1,701✔
85
      configurable: false,
86
      writable: false,
87
    }); // make read-only
88

89
    this._context = context;
1,701✔
90
    this.context.onNodeCreated(this);
1,701✔
91

92
    this._publishers = [];
1,701✔
93
    this._subscriptions = [];
1,701✔
94
    this._clients = [];
1,701✔
95
    this._services = [];
1,701✔
96
    this._timers = [];
1,701✔
97
    this._guards = [];
1,701✔
98
    this._actionClients = [];
1,701✔
99
    this._actionServers = [];
1,701✔
100
    this._rateTimerServer = null;
1,701✔
101
    this._parameterDescriptors = new Map();
1,701✔
102
    this._parameters = new Map();
1,701✔
103
    this._parameterService = null;
1,701✔
104
    this._parameterEventPublisher = null;
1,701✔
105
    this._setParametersCallbacks = [];
1,701✔
106
    this._logger = new Logging(rclnodejs.getNodeLoggerName(this.handle));
1,701✔
107
    this._spinning = false;
1,701✔
108

109
    this._parameterEventPublisher = this.createPublisher(
1,701✔
110
      PARAMETER_EVENT_MSG_TYPE,
111
      PARAMETER_EVENT_TOPIC
112
    );
113

114
    // initialize _parameterOverrides from parameters defined on the commandline
115
    this._parameterOverrides = this._getNativeParameterOverrides();
1,701✔
116

117
    // override cli parameterOverrides with those specified in options
118
    if (options.parameterOverrides.length > 0) {
1,701✔
119
      for (const parameter of options.parameterOverrides) {
24✔
120
        if ((!parameter) instanceof Parameter) {
24!
121
          throw new TypeError(
×
122
            'Parameter-override must be an instance of Parameter.'
123
          );
124
        }
125
        this._parameterOverrides.set(parameter.name, parameter);
24✔
126
      }
127
    }
128

129
    // initialize _parameters from parameterOverrides
130
    if (options.automaticallyDeclareParametersFromOverrides) {
1,701✔
131
      for (const parameter of this._parameterOverrides.values()) {
15✔
132
        parameter.validate();
15✔
133
        const descriptor = ParameterDescriptor.fromParameter(parameter);
15✔
134
        this._parameters.set(parameter.name, parameter);
15✔
135
        this._parameterDescriptors.set(parameter.name, descriptor);
15✔
136
      }
137
    }
138

139
    // Clock that has support for ROS time.
140
    // Note: parameter overrides and parameter event publisher need to be ready at this point
141
    // to be able to declare 'use_sim_time' if it was not declared yet.
142
    this._clock = new Clock.ROSClock();
1,701✔
143
    this._timeSource = new TimeSource(this);
1,701✔
144
    this._timeSource.attachClock(this._clock);
1,701✔
145

146
    if (options.startParameterServices) {
1,701✔
147
      this._parameterService = new ParameterService(this);
1,683✔
148
      this._parameterService.start();
1,683✔
149
    }
150
  }
151

152
  execute(handles) {
153
    let timersReady = this._timers.filter((timer) =>
11,352✔
154
      handles.includes(timer.handle)
9,345✔
155
    );
156
    let guardsReady = this._guards.filter((guard) =>
11,352✔
157
      handles.includes(guard.handle)
9✔
158
    );
159
    let subscriptionsReady = this._subscriptions.filter((subscription) =>
11,352✔
160
      handles.includes(subscription.handle)
1,431✔
161
    );
162
    let clientsReady = this._clients.filter((client) =>
11,352✔
163
      handles.includes(client.handle)
298✔
164
    );
165
    let servicesReady = this._services.filter((service) =>
11,352✔
166
      handles.includes(service.handle)
14,397✔
167
    );
168
    let actionClientsReady = this._actionClients.filter((actionClient) =>
11,352✔
169
      handles.includes(actionClient.handle)
475✔
170
    );
171
    let actionServersReady = this._actionServers.filter((actionServer) =>
11,352✔
172
      handles.includes(actionServer.handle)
475✔
173
    );
174

175
    timersReady.forEach((timer) => {
11,352✔
176
      if (timer.isReady()) {
9,314✔
177
        rclnodejs.callTimer(timer.handle);
9,191✔
178
        timer.callback();
9,191✔
179
      }
180
    });
181

182
    for (const subscription of subscriptionsReady) {
11,352✔
183
      if (subscription.isDestroyed()) continue;
1,318✔
184
      if (subscription.isRaw) {
1,288✔
185
        let rawMessage = rclnodejs.rclTakeRaw(subscription.handle);
3✔
186
        if (rawMessage) {
3!
187
          subscription.processResponse(rawMessage);
3✔
188
        }
189
        continue;
3✔
190
      }
191

192
      this._runWithMessageType(
1,285✔
193
        subscription.typeClass,
194
        (message, deserialize) => {
195
          let success = rclnodejs.rclTake(subscription.handle, message);
1,285✔
196
          if (success) {
1,285✔
197
            subscription.processResponse(deserialize());
1,065✔
198
          }
199
        }
200
      );
201
    }
202

203
    for (const guard of guardsReady) {
11,352✔
204
      if (guard.isDestroyed()) continue;
9!
205

206
      guard.callback();
9✔
207
    }
208

209
    for (const client of clientsReady) {
11,352✔
210
      if (client.isDestroyed()) continue;
150!
211
      this._runWithMessageType(
150✔
212
        client.typeClass.Response,
213
        (message, deserialize) => {
214
          let sequenceNumber = rclnodejs.rclTakeResponse(
150✔
215
            client.handle,
216
            message
217
          );
218
          if (sequenceNumber !== undefined) {
150✔
219
            client.processResponse(sequenceNumber, deserialize());
90✔
220
          }
221
        }
222
      );
223
    }
224

225
    for (const service of servicesReady) {
11,352✔
226
      if (service.isDestroyed()) continue;
178!
227
      this._runWithMessageType(
178✔
228
        service.typeClass.Request,
229
        (message, deserialize) => {
230
          let header = rclnodejs.rclTakeRequest(
178✔
231
            service.handle,
232
            this.handle,
233
            message
234
          );
235
          if (header) {
178✔
236
            service.processRequest(header, deserialize());
93✔
237
          }
238
        }
239
      );
240
    }
241

242
    for (const actionClient of actionClientsReady) {
11,352✔
243
      if (actionClient.isDestroyed()) continue;
223!
244

245
      const properties = actionClient.handle.properties;
223✔
246

247
      if (properties.isGoalResponseReady) {
223✔
248
        this._runWithMessageType(
105✔
249
          actionClient.typeClass.impl.SendGoalService.Response,
250
          (message, deserialize) => {
251
            let sequence = rclnodejs.actionTakeGoalResponse(
105✔
252
              actionClient.handle,
253
              message
254
            );
255
            if (sequence != undefined) {
105✔
256
              actionClient.processGoalResponse(sequence, deserialize());
90✔
257
            }
258
          }
259
        );
260
      }
261

262
      if (properties.isCancelResponseReady) {
223✔
263
        this._runWithMessageType(
16✔
264
          actionClient.typeClass.impl.CancelGoal.Response,
265
          (message, deserialize) => {
266
            let sequence = rclnodejs.actionTakeCancelResponse(
16✔
267
              actionClient.handle,
268
              message
269
            );
270
            if (sequence != undefined) {
16✔
271
              actionClient.processCancelResponse(sequence, deserialize());
12✔
272
            }
273
          }
274
        );
275
      }
276

277
      if (properties.isResultResponseReady) {
223✔
278
        this._runWithMessageType(
47✔
279
          actionClient.typeClass.impl.GetResultService.Response,
280
          (message, deserialize) => {
281
            let sequence = rclnodejs.actionTakeResultResponse(
47✔
282
              actionClient.handle,
283
              message
284
            );
285
            if (sequence != undefined) {
47✔
286
              actionClient.processResultResponse(sequence, deserialize());
45✔
287
            }
288
          }
289
        );
290
      }
291

292
      if (properties.isFeedbackReady) {
223✔
293
        this._runWithMessageType(
21✔
294
          actionClient.typeClass.impl.FeedbackMessage,
295
          (message, deserialize) => {
296
            let success = rclnodejs.actionTakeFeedback(
21✔
297
              actionClient.handle,
298
              message
299
            );
300
            if (success) {
21✔
301
              actionClient.processFeedbackMessage(deserialize());
15✔
302
            }
303
          }
304
        );
305
      }
306

307
      if (properties.isStatusReady) {
223✔
308
        this._runWithMessageType(
115✔
309
          actionClient.typeClass.impl.GoalStatusArray,
310
          (message, deserialize) => {
311
            let success = rclnodejs.actionTakeStatus(
115✔
312
              actionClient.handle,
313
              message
314
            );
315
            if (success) {
115✔
316
              actionClient.processStatusMessage(deserialize());
101✔
317
            }
318
          }
319
        );
320
      }
321
    }
322

323
    for (const actionServer of actionServersReady) {
11,352✔
324
      if (actionServer.isDestroyed()) continue;
280!
325

326
      const properties = actionServer.handle.properties;
280✔
327

328
      if (properties.isGoalRequestReady) {
280✔
329
        this._runWithMessageType(
96✔
330
          actionServer.typeClass.impl.SendGoalService.Request,
331
          (message, deserialize) => {
332
            const result = rclnodejs.actionTakeGoalRequest(
96✔
333
              actionServer.handle,
334
              message
335
            );
336
            if (result) {
96✔
337
              actionServer.processGoalRequest(result, deserialize());
90✔
338
            }
339
          }
340
        );
341
      }
342

343
      if (properties.isCancelRequestReady) {
280✔
344
        this._runWithMessageType(
17✔
345
          actionServer.typeClass.impl.CancelGoal.Request,
346
          (message, deserialize) => {
347
            const result = rclnodejs.actionTakeCancelRequest(
17✔
348
              actionServer.handle,
349
              message
350
            );
351
            if (result) {
17✔
352
              actionServer.processCancelRequest(result, deserialize());
12✔
353
            }
354
          }
355
        );
356
      }
357

358
      if (properties.isResultRequestReady) {
280✔
359
        this._runWithMessageType(
68✔
360
          actionServer.typeClass.impl.GetResultService.Request,
361
          (message, deserialize) => {
362
            const result = rclnodejs.actionTakeResultRequest(
68✔
363
              actionServer.handle,
364
              message
365
            );
366
            if (result) {
68✔
367
              actionServer.processResultRequest(result, deserialize());
45✔
368
            }
369
          }
370
        );
371
      }
372

373
      if (properties.isGoalExpired) {
280✔
374
        let GoalInfoArray = ActionInterfaces.GoalInfo.ArrayType;
17✔
375
        let message = new GoalInfoArray(actionServer._goalHandles.size);
17✔
376
        let count = rclnodejs.actionExpireGoals(
17✔
377
          actionServer.handle,
378
          actionServer._goalHandles.size,
379
          message._refArray.buffer
380
        );
381
        if (count > 0) {
17✔
382
          actionServer.processGoalExpired(message, count);
10✔
383
        }
384
        GoalInfoArray.freeArray(message);
17✔
385
      }
386
    }
387

388
    // At this point it is safe to clear the cache of any
389
    // destroyed entity references
390
    Entity._gcHandles();
11,352✔
391
  }
392

393
  /**
394
   * Determine if this node is spinning.
395
   * @returns {boolean} - true when spinning; otherwise returns false.
396
   */
397
  get spinning() {
398
    return this._spinning;
12,060✔
399
  }
400

401
  /**
402
   * Trigger the event loop to continuously check for and route.
403
   * incoming events.
404
   * @param {Node} node - The node to be spun up.
405
   * @param {number} [timeout=10] - Timeout to wait in milliseconds. Block forever if negative. Don't wait if 0.
406
   * @throws {Error} If the node is already spinning.
407
   * @return {undefined}
408
   */
409
  spin(timeout = 10) {
45✔
410
    if (this.spinning) {
1,260!
411
      throw new Error('The node is already spinning.');
×
412
    }
413
    this.start(this.context.handle, timeout);
1,260✔
414
    this._spinning = true;
1,260✔
415
  }
416

417
  /**
418
   * Use spin().
419
   * @deprecated, since 0.18.0
420
   */
421
  startSpinning(timeout) {
422
    this.spin(timeout);
×
423
  }
424

425
  /**
426
   * Terminate spinning - no further events will be received.
427
   * @returns {undefined}
428
   */
429
  stop() {
430
    super.stop();
1,260✔
431
    this._spinning = false;
1,260✔
432
  }
433

434
  /**
435
   * Terminate spinning - no further events will be received.
436
   * @returns {undefined}
437
   * @deprecated since 0.18.0, Use stop().
438
   */
439
  stopSpinning() {
440
    super.stop();
×
441
    this._spinning = false;
×
442
  }
443

444
  /**
445
   * Spin the node and trigger the event loop to check for one incoming event. Thereafter the node
446
   * will not received additional events until running additional calls to spin() or spinOnce().
447
   * @param {Node} node - The node to be spun.
448
   * @param {number} [timeout=10] - Timeout to wait in milliseconds. Block forever if negative. Don't wait if 0.
449
   * @throws {Error} If the node is already spinning.
450
   * @return {undefined}
451
   */
452
  spinOnce(timeout = 10) {
6✔
453
    if (this.spinning) {
9,027✔
454
      throw new Error('The node is already spinning.');
6✔
455
    }
456
    super.spinOnce(this.context.handle, timeout);
9,021✔
457
  }
458

459
  _removeEntityFromArray(entity, array) {
460
    let index = array.indexOf(entity);
324✔
461
    if (index > -1) {
324✔
462
      array.splice(index, 1);
318✔
463
    }
464
  }
465

466
  _destroyEntity(entity, array, syncHandles = true) {
330✔
467
    if (entity['isDestroyed'] && entity.isDestroyed()) return;
348✔
468

469
    this._removeEntityFromArray(entity, array);
324✔
470
    if (syncHandles) {
324✔
471
      this.syncHandles();
312✔
472
    }
473

474
    if (entity['_destroy']) {
324✔
475
      entity._destroy();
306✔
476
    } else {
477
      // guards and timers
478
      entity.handle.release();
18✔
479
    }
480
  }
481

482
  _validateOptions(options) {
483
    if (
14,574✔
484
      options !== undefined &&
14,718✔
485
      (options === null || typeof options !== 'object')
486
    ) {
487
      throw new TypeError('Invalid argument of options');
60✔
488
    }
489

490
    if (options === undefined) {
14,514✔
491
      return Node.getDefaultOptions();
14,472✔
492
    }
493

494
    if (options.enableTypedArray === undefined) {
42✔
495
      options = Object.assign(options, { enableTypedArray: true });
6✔
496
    }
497

498
    if (options.qos === undefined) {
42✔
499
      options = Object.assign(options, { qos: QoS.profileDefault });
12✔
500
    }
501

502
    if (options.isRaw === undefined) {
42✔
503
      options = Object.assign(options, { isRaw: false });
12✔
504
    }
505

506
    return options;
42✔
507
  }
508

509
  /**
510
   * Create a Timer.
511
   * @param {bigint} period - The number representing period in nanoseconds.
512
   * @param {function} callback - The callback to be called when timeout.
513
   * @param {Clock} [clock] - The clock which the timer gets time from.
514
   * @return {Timer} - An instance of Timer.
515
   */
516
  createTimer(period, callback, clock = null) {
174✔
517
    if (arguments.length === 3 && !(arguments[2] instanceof Clock)) {
174!
518
      clock = null;
×
519
    } else if (arguments.length === 4) {
174!
520
      clock = arguments[3];
×
521
    }
522

523
    if (typeof period !== 'bigint' || typeof callback !== 'function') {
174✔
524
      throw new TypeError('Invalid argument');
6✔
525
    }
526

527
    const timerClock = clock || this._clock;
168✔
528
    let timerHandle = rclnodejs.createTimer(
168✔
529
      timerClock.handle,
530
      this.context.handle,
531
      period
532
    );
533
    let timer = new Timer(timerHandle, period, callback);
168✔
534
    debug('Finish creating timer, period = %d.', period);
168✔
535
    this._timers.push(timer);
168✔
536
    this.syncHandles();
168✔
537

538
    return timer;
168✔
539
  }
540

541
  /**
542
   * Create a Rate.
543
   *
544
   * @param {number} hz - The frequency of the rate timer; default is 1 hz.
545
   * @returns {Promise<Rate>} - Promise resolving to new instance of Rate.
546
   */
547
  async createRate(hz = 1) {
12✔
548
    if (typeof hz !== 'number') {
27!
549
      throw new TypeError('Invalid argument');
×
550
    }
551

552
    const MAX_RATE_HZ_IN_MILLISECOND = 1000.0;
27✔
553
    if (hz <= 0.0 || hz > MAX_RATE_HZ_IN_MILLISECOND) {
27✔
554
      throw new RangeError(
6✔
555
        `Hz must be between 0.0 and ${MAX_RATE_HZ_IN_MILLISECOND}`
556
      );
557
    }
558

559
    // lazy initialize rateTimerServer
560
    if (!this._rateTimerServer) {
21✔
561
      this._rateTimerServer = new Rates.RateTimerServer(this);
15✔
562
      await this._rateTimerServer.init();
15✔
563
    }
564

565
    const period = Math.round(1000 / hz);
21✔
566
    const timer = this._rateTimerServer.createTimer(BigInt(period) * 1000000n);
21✔
567
    const rate = new Rates.Rate(hz, timer);
21✔
568

569
    return rate;
21✔
570
  }
571

572
  /**
573
   * Create a Publisher.
574
   * @param {function|string|object} typeClass - The ROS message class,
575
        OR a string representing the message class, e.g. 'std_msgs/msg/String',
576
        OR an object representing the message class, e.g. {package: 'std_msgs', type: 'msg', name: 'String'}
577
   * @param {string} topic - The name of the topic.
578
   * @param {object} options - The options argument used to parameterize the publisher.
579
   * @param {boolean} options.enableTypedArray - The topic will use TypedArray if necessary, default: true.
580
   * @param {QoS} options.qos - ROS Middleware "quality of service" settings for the publisher, default: QoS.profileDefault.
581
   * @return {Publisher} - An instance of Publisher.
582
   */
583
  createPublisher(typeClass, topic, options) {
584
    return this._createPublisher(typeClass, topic, options, Publisher);
2,766✔
585
  }
586

587
  _createPublisher(typeClass, topic, options, publisherClass) {
588
    if (typeof typeClass === 'string' || typeof typeClass === 'object') {
2,775✔
589
      typeClass = loader.loadInterface(typeClass);
2,703✔
590
    }
591
    options = this._validateOptions(options);
2,754✔
592

593
    if (typeof typeClass !== 'function' || typeof topic !== 'string') {
2,754✔
594
      throw new TypeError('Invalid argument');
60✔
595
    }
596

597
    let publisher = publisherClass.createPublisher(
2,694✔
598
      this.handle,
599
      typeClass,
600
      topic,
601
      options
602
    );
603
    debug('Finish creating publisher, topic = %s.', topic);
2,664✔
604
    this._publishers.push(publisher);
2,664✔
605
    return publisher;
2,664✔
606
  }
607

608
  /**
609
   * This callback is called when a message is published
610
   * @callback SubscriptionCallback
611
   * @param {Object} message - The message published
612
   * @see [Node.createSubscription]{@link Node#createSubscription}
613
   * @see [Node.createPublisher]{@link Node#createPublisher}
614
   * @see {@link Publisher}
615
   * @see {@link Subscription}
616
   */
617

618
  /**
619
   * Create a Subscription with optional content-filtering.
620
   * @param {function|string|object} typeClass - The ROS message class,
621
        OR a string representing the message class, e.g. 'std_msgs/msg/String',
622
        OR an object representing the message class, e.g. {package: 'std_msgs', type: 'msg', name: 'String'}
623
   * @param {string} topic - The name of the topic.
624
   * @param {object} options - The options argument used to parameterize the subscription.
625
   * @param {boolean} options.enableTypedArray - The topic will use TypedArray if necessary, default: true.
626
   * @param {QoS} options.qos - ROS Middleware "quality of service" settings for the subscription, default: QoS.profileDefault.
627
   * @param {boolean} options.isRaw - The topic is serialized when true, default: false.
628
   * @param {object} [options.contentFilter=undefined] - The content-filter, default: undefined.
629
   *  Confirm that your RMW supports content-filtered topics before use. 
630
   * @param {string} options.contentFilter.expression - Specifies the criteria to select the data samples of
631
   *  interest. It is similar to the WHERE part of an SQL clause.
632
   * @param {string[]} [options.contentFilter.parameters=undefined] - Array of strings that give values to
633
   *  the ‘parameters’ (i.e., "%n" tokens) in the filter_expression. The number of supplied parameters must
634
   *  fit with the requested values in the filter_expression (i.e., the number of %n tokens). default: undefined.
635
   * @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.
636
   * @return {Subscription} - An instance of Subscription.
637
   * @throws {ERROR} - May throw an RMW error if content-filter is malformed. 
638
   * @see {@link SubscriptionCallback}
639
   * @see {@link https://www.omg.org/spec/DDS/1.4/PDF|Content-filter details at DDS 1.4 specification, Annex B}
640
   */
641
  createSubscription(typeClass, topic, options, callback) {
642
    if (typeof typeClass === 'string' || typeof typeClass === 'object') {
1,122✔
643
      typeClass = loader.loadInterface(typeClass);
1,095✔
644
    }
645

646
    if (typeof options === 'function') {
1,101✔
647
      callback = options;
1,008✔
648
      options = undefined;
1,008✔
649
    }
650
    options = this._validateOptions(options);
1,101✔
651

652
    if (
1,071✔
653
      typeof typeClass !== 'function' ||
3,171✔
654
      typeof topic !== 'string' ||
655
      typeof callback !== 'function'
656
    ) {
657
      throw new TypeError('Invalid argument');
30✔
658
    }
659

660
    let subscription = Subscription.createSubscription(
1,041✔
661
      this.handle,
662
      typeClass,
663
      topic,
664
      options,
665
      callback
666
    );
667
    debug('Finish creating subscription, topic = %s.', topic);
1,008✔
668
    this._subscriptions.push(subscription);
1,008✔
669
    this.syncHandles();
1,008✔
670

671
    return subscription;
1,008✔
672
  }
673

674
  /**
675
   * Create a Client.
676
   * @param {function|string|object} typeClass - The ROS message class,
677
        OR a string representing the message class, e.g. 'std_msgs/msg/String',
678
        OR an object representing the message class, e.g. {package: 'std_msgs', type: 'msg', name: 'String'}
679
   * @param {string} serviceName - The service name to request.
680
   * @param {object} options - The options argument used to parameterize the client.
681
   * @param {boolean} options.enableTypedArray - The response will use TypedArray if necessary, default: true.
682
   * @param {QoS} options.qos - ROS Middleware "quality of service" settings for the client, default: QoS.profileDefault.
683
   * @return {Client} - An instance of Client.
684
   */
685
  createClient(typeClass, serviceName, options) {
686
    if (typeof typeClass === 'string' || typeof typeClass === 'object') {
249✔
687
      typeClass = loader.loadInterface(typeClass);
219✔
688
    }
689
    options = this._validateOptions(options);
228✔
690

691
    if (typeof typeClass !== 'function' || typeof serviceName !== 'string') {
228✔
692
      throw new TypeError('Invalid argument');
60✔
693
    }
694

695
    let client = Client.createClient(
168✔
696
      this.handle,
697
      serviceName,
698
      typeClass,
699
      options
700
    );
701
    debug('Finish creating client, service = %s.', serviceName);
138✔
702
    this._clients.push(client);
138✔
703
    this.syncHandles();
138✔
704

705
    return client;
138✔
706
  }
707

708
  /**
709
   * This callback is called when a request is sent to service
710
   * @callback RequestCallback
711
   * @param {Object} request - The request sent to the service
712
   * @param {Response} response - The response to client.
713
        Use [response.send()]{@link Response#send} to send response object to client
714
   * @return {undefined}
715
   * @see [Node.createService]{@link Node#createService}
716
   * @see [Client.sendRequest]{@link Client#sendRequest}
717
   * @see {@link Client}
718
   * @see {@link Service}
719
   * @see {@link Response#send}
720
   */
721

722
  /**
723
   * Create a Service.
724
   * @param {function|string|object} typeClass - The ROS message class,
725
        OR a string representing the message class, e.g. 'std_msgs/msg/String',
726
        OR an object representing the message class, e.g. {package: 'std_msgs', type: 'msg', name: 'String'}
727
   * @param {string} serviceName - The service name to offer.
728
   * @param {object} options - The options argument used to parameterize the service.
729
   * @param {boolean} options.enableTypedArray - The request will use TypedArray if necessary, default: true.
730
   * @param {QoS} options.qos - ROS Middleware "quality of service" settings for the service, default: QoS.profileDefault.
731
   * @param {RequestCallback} callback - The callback to be called when receiving request.
732
   * @return {Service} - An instance of Service.
733
   * @see {@link RequestCallback}
734
   */
735
  createService(typeClass, serviceName, options, callback) {
736
    if (typeof typeClass === 'string' || typeof typeClass === 'object') {
10,308✔
737
      typeClass = loader.loadInterface(typeClass);
10,278✔
738
    }
739

740
    if (typeof options === 'function') {
10,287✔
741
      callback = options;
10,221✔
742
      options = undefined;
10,221✔
743
    }
744
    options = this._validateOptions(options);
10,287✔
745

746
    if (
10,257✔
747
      typeof typeClass !== 'function' ||
30,729✔
748
      typeof serviceName !== 'string' ||
749
      typeof callback !== 'function'
750
    ) {
751
      throw new TypeError('Invalid argument');
30✔
752
    }
753

754
    let service = Service.createService(
10,227✔
755
      this.handle,
756
      serviceName,
757
      typeClass,
758
      options,
759
      callback
760
    );
761
    debug('Finish creating service, service = %s.', serviceName);
10,197✔
762
    this._services.push(service);
10,197✔
763
    this.syncHandles();
10,197✔
764

765
    return service;
10,197✔
766
  }
767

768
  /**
769
   * Create a guard condition.
770
   * @param {Function} callback - The callback to be called when the guard condition is triggered.
771
   * @return {GuardCondition} - An instance of GuardCondition.
772
   */
773
  createGuardCondition(callback) {
774
    if (typeof callback !== 'function') {
9!
775
      throw new TypeError('Invalid argument');
×
776
    }
777

778
    let guard = GuardCondition.createGuardCondition(callback, this.context);
9✔
779
    debug('Finish creating guard condition');
9✔
780
    this._guards.push(guard);
9✔
781
    this.syncHandles();
9✔
782

783
    return guard;
9✔
784
  }
785

786
  /**
787
   * Destroy all resource allocated by this node, including
788
   * <code>Timer</code>s/<code>Publisher</code>s/<code>Subscription</code>s
789
   * /<code>Client</code>s/<code>Service</code>s
790
   * @return {undefined}
791
   */
792
  destroy() {
793
    if (this.spinning) {
1,767✔
794
      this.stop();
1,257✔
795
    }
796

797
    // Action servers/clients require manual destruction due to circular reference with goal handles.
798
    this._actionClients.forEach((actionClient) => actionClient.destroy());
1,767✔
799
    this._actionServers.forEach((actionServer) => actionServer.destroy());
1,767✔
800

801
    this.context.onNodeDestroyed(this);
1,767✔
802

803
    this.handle.release();
1,767✔
804
    this._clock = null;
1,767✔
805
    this._timers = [];
1,767✔
806
    this._publishers = [];
1,767✔
807
    this._subscriptions = [];
1,767✔
808
    this._clients = [];
1,767✔
809
    this._services = [];
1,767✔
810
    this._guards = [];
1,767✔
811
    this._actionClients = [];
1,767✔
812
    this._actionServers = [];
1,767✔
813

814
    if (this._rateTimerServer) {
1,767✔
815
      this._rateTimerServer.shutdown();
15✔
816
      this._rateTimerServer = null;
15✔
817
    }
818
  }
819

820
  /**
821
   * Destroy a Publisher.
822
   * @param {Publisher} publisher - The Publisher to be destroyed.
823
   * @return {undefined}
824
   */
825
  destroyPublisher(publisher) {
826
    if (!(publisher instanceof Publisher)) {
24✔
827
      throw new TypeError('Invalid argument');
6✔
828
    }
829
    this._destroyEntity(publisher, this._publishers, false);
18✔
830
  }
831

832
  /**
833
   * Destroy a Subscription.
834
   * @param {Subscription} subscription - The Subscription to be destroyed.
835
   * @return {undefined}
836
   */
837
  destroySubscription(subscription) {
838
    if (!(subscription instanceof Subscription)) {
63✔
839
      throw new TypeError('Invalid argument');
6✔
840
    }
841
    this._destroyEntity(subscription, this._subscriptions);
57✔
842
  }
843

844
  /**
845
   * Destroy a Client.
846
   * @param {Client} client - The Client to be destroyed.
847
   * @return {undefined}
848
   */
849
  destroyClient(client) {
850
    if (!(client instanceof Client)) {
24✔
851
      throw new TypeError('Invalid argument');
6✔
852
    }
853
    this._destroyEntity(client, this._clients);
18✔
854
  }
855

856
  /**
857
   * Destroy a Service.
858
   * @param {Service} service - The Service to be destroyed.
859
   * @return {undefined}
860
   */
861
  destroyService(service) {
862
    if (!(service instanceof Service)) {
24✔
863
      throw new TypeError('Invalid argument');
6✔
864
    }
865
    this._destroyEntity(service, this._services);
18✔
866
  }
867

868
  /**
869
   * Destroy a Timer.
870
   * @param {Timer} timer - The Timer to be destroyed.
871
   * @return {undefined}
872
   */
873
  destroyTimer(timer) {
874
    if (!(timer instanceof Timer)) {
24✔
875
      throw new TypeError('Invalid argument');
6✔
876
    }
877
    this._destroyEntity(timer, this._timers);
18✔
878
  }
879

880
  /**
881
   * Destroy a guard condition.
882
   * @param {GuardCondition} guard - The guard condition to be destroyed.
883
   * @return {undefined}
884
   */
885
  destroyGuardCondition(guard) {
886
    if (!(guard instanceof GuardCondition)) {
9!
887
      throw new TypeError('Invalid argument');
×
888
    }
889
    this._destroyEntity(guard, this._guards);
9✔
890
  }
891

892
  /**
893
   * Get the name of the node.
894
   * @return {string}
895
   */
896
  name() {
897
    return rclnodejs.getNodeName(this.handle);
5,274✔
898
  }
899

900
  /**
901
   * Get the namespace of the node.
902
   * @return {string}
903
   */
904
  namespace() {
905
    return rclnodejs.getNamespace(this.handle);
3,723✔
906
  }
907

908
  /**
909
   * Get the context in which this node was created.
910
   * @return {Context}
911
   */
912
  get context() {
913
    return this._context;
15,627✔
914
  }
915

916
  /**
917
   * Get the nodes logger.
918
   * @returns {Logger} - The logger for the node.
919
   */
920
  getLogger() {
921
    return this._logger;
423✔
922
  }
923

924
  /**
925
   * Get the clock used by the node.
926
   * @returns {Clock} - The nodes clock.
927
   */
928
  getClock() {
929
    return this._clock;
243✔
930
  }
931

932
  /**
933
   * Get the current time using the node's clock.
934
   * @returns {Time} - The current time.
935
   */
936
  now() {
937
    return this.getClock().now();
6✔
938
  }
939

940
  /**
941
   * Get the list of published topics discovered by the provided node for the remote node name.
942
   * @param {string} nodeName - The name of the node.
943
   * @param {string} namespace - The name of the namespace.
944
   * @param {boolean} noDemangle - If true topic names and types returned will not be demangled, default: false.
945
   * @return {Array<{name: string, types: Array<string>}>} - An array of the names and types.
946
   */
947
  getPublisherNamesAndTypesByNode(nodeName, namespace, noDemangle = false) {
×
948
    return rclnodejs.getPublisherNamesAndTypesByNode(
×
949
      this.handle,
950
      nodeName,
951
      namespace,
952
      noDemangle
953
    );
954
  }
955

956
  /**
957
   * Get the list of published topics discovered by the provided node for the remote node name.
958
   * @param {string} nodeName - The name of the node.
959
   * @param {string} namespace - The name of the namespace.
960
   * @param {boolean} noDemangle - If true topic names and types returned will not be demangled, default: false.
961
   * @return {Array<{name: string, types: Array<string>}>} - An array of the names and types.
962
   */
963
  getSubscriptionNamesAndTypesByNode(nodeName, namespace, noDemangle = false) {
×
964
    return rclnodejs.getSubscriptionNamesAndTypesByNode(
×
965
      this.handle,
966
      nodeName,
967
      namespace,
968
      noDemangle
969
    );
970
  }
971

972
  /**
973
   * Get service names and types for which a remote node has servers.
974
   * @param {string} nodeName - The name of the node.
975
   * @param {string} namespace - The name of the namespace.
976
   * @return {Array<{name: string, types: Array<string>}>} - An array of the names and types.
977
   */
978
  getServiceNamesAndTypesByNode(nodeName, namespace) {
979
    return rclnodejs.getServiceNamesAndTypesByNode(
×
980
      this.handle,
981
      nodeName,
982
      namespace
983
    );
984
  }
985

986
  /**
987
   * Get service names and types for which a remote node has clients.
988
   * @param {string} nodeName - The name of the node.
989
   * @param {string} namespace - The name of the namespace.
990
   * @return {Array<{name: string, types: Array<string>}>} - An array of the names and types.
991
   */
992
  getClientNamesAndTypesByNode(nodeName, namespace) {
993
    return rclnodejs.getClientNamesAndTypesByNode(
×
994
      this.handle,
995
      nodeName,
996
      namespace
997
    );
998
  }
999

1000
  /**
1001
   * Get the list of topics discovered by the provided node.
1002
   * @param {boolean} noDemangle - If true topic names and types returned will not be demangled, default: false.
1003
   * @return {Array<{name: string, types: Array<string>}>} - An array of the names and types.
1004
   */
1005
  getTopicNamesAndTypes(noDemangle = false) {
×
1006
    return rclnodejs.getTopicNamesAndTypes(this.handle, noDemangle);
×
1007
  }
1008

1009
  /**
1010
   * Get the list of services discovered by the provided node.
1011
   * @return {Array<{name: string, types: Array<string>}>} - An array of the names and types.
1012
   */
1013
  getServiceNamesAndTypes() {
1014
    return rclnodejs.getServiceNamesAndTypes(this.handle);
6✔
1015
  }
1016

1017
  /**
1018
   * Get a list of publishers on a given topic.
1019
   * @param {string} topic - the topic name to get the publishers for.
1020
   * @param {boolean} noDemangle - if `true`, `topic_name` needs to be a valid middleware topic name,
1021
   *       otherwise it should be a valid ROS topic name.
1022
   * @returns {Array} - list of publishers
1023
   */
1024
  getPublishersInfoByTopic(topic, noDemangle) {
1025
    return rclnodejs.getPublishersInfoByTopic(this.handle, topic, noDemangle);
3✔
1026
  }
1027

1028
  /**
1029
   * Get a list of subscriptions on a given topic.
1030
   * @param {string} topic - the topic name to get the subscriptions for.
1031
   * @param {boolean} noDemangle - if `true`, `topic_name` needs to be a valid middleware topic name,
1032
   *       otherwise it should be a valid ROS topic name.
1033
   * @returns {Array} - list of subscriptions
1034
   */
1035
  getSubscriptionsInfoByTopic(topic, noDemangle) {
1036
    return rclnodejs.getSubscriptionsInfoByTopic(
3✔
1037
      this.handle,
1038
      topic,
1039
      noDemangle
1040
    );
1041
  }
1042

1043
  /**
1044
   * Get the list of nodes discovered by the provided node.
1045
   * @return {Array<string>} - An array of the names.
1046
   */
1047
  getNodeNames() {
1048
    return this.getNodeNamesAndNamespaces().map((item) => item.name);
120✔
1049
  }
1050

1051
  /**
1052
   * Get the list of nodes and their namespaces discovered by the provided node.
1053
   * @return {Array<{name: string, namespace: string}>} An array of the names and namespaces.
1054
   */
1055
  getNodeNamesAndNamespaces() {
1056
    return rclnodejs.getNodeNames(this.handle);
51✔
1057
  }
1058

1059
  /**
1060
   * Return the number of publishers on a given topic.
1061
   * @param {string} topic - The name of the topic.
1062
   * @returns {number} - Number of publishers on the given topic.
1063
   */
1064
  countPublishers(topic) {
1065
    let expandedTopic = rclnodejs.expandTopicName(
18✔
1066
      topic,
1067
      this.name(),
1068
      this.namespace()
1069
    );
1070
    rclnodejs.validateTopicName(expandedTopic);
18✔
1071

1072
    return rclnodejs.countPublishers(this.handle, expandedTopic);
18✔
1073
  }
1074

1075
  /**
1076
   * Return the number of subscribers on a given topic.
1077
   * @param {string} topic - The name of the topic.
1078
   * @returns {number} - Number of subscribers on the given topic.
1079
   */
1080
  countSubscribers(topic) {
1081
    let expandedTopic = rclnodejs.expandTopicName(
18✔
1082
      topic,
1083
      this.name(),
1084
      this.namespace()
1085
    );
1086
    rclnodejs.validateTopicName(expandedTopic);
18✔
1087

1088
    return rclnodejs.countSubscribers(this.handle, expandedTopic);
18✔
1089
  }
1090

1091
  /**
1092
   * Get the number of clients on a given service name.
1093
   * @param {string} serviceName - the service name
1094
   * @returns {Number}
1095
   */
1096
  countClients(serviceName) {
1097
    if (DistroUtils.getDistroId() <= DistroUtils.getDistroId('humble')) {
6!
1098
      console.warn('countClients is not supported by this version of ROS 2');
×
1099
      return null;
×
1100
    }
1101
    return rclnodejs.countClients(this.handle, serviceName);
6✔
1102
  }
1103

1104
  /**
1105
   * Get the number of services on a given service name.
1106
   * @param {string} serviceName - the service name
1107
   * @returns {Number}
1108
   */
1109
  countServices(serviceName) {
1110
    if (DistroUtils.getDistroId() <= DistroUtils.getDistroId('humble')) {
3!
1111
      console.warn('countServices is not supported by this version of ROS 2');
×
1112
      return null;
×
1113
    }
1114
    return rclnodejs.countServices(this.handle, serviceName);
3✔
1115
  }
1116

1117
  /**
1118
   * Get the list of parameter-overrides found on the commandline and
1119
   * in the NodeOptions.parameter_overrides property.
1120
   *
1121
   * @return {Array<Parameter>} - An array of Parameters.
1122
   */
1123
  getParameterOverrides() {
1124
    return Array.from(this._parameterOverrides.values());
24✔
1125
  }
1126

1127
  /**
1128
   * Declare a parameter.
1129
   *
1130
   * Internally, register a parameter and it's descriptor.
1131
   * If a parameter-override exists, it's value will replace that of the parameter
1132
   * unless ignoreOverride is true.
1133
   * If the descriptor is undefined, then a ParameterDescriptor will be inferred
1134
   * from the parameter's state.
1135
   *
1136
   * If a parameter by the same name has already been declared then an Error is thrown.
1137
   * A parameter must be undeclared before attempting to redeclare it.
1138
   *
1139
   * @param {Parameter} parameter - Parameter to declare.
1140
   * @param {ParameterDescriptor} [descriptor] - Optional descriptor for parameter.
1141
   * @param {boolean} [ignoreOveride] - When true disregard any parameter-override that may be present.
1142
   * @return {Parameter} - The newly declared parameter.
1143
   */
1144
  declareParameter(parameter, descriptor, ignoreOveride = false) {
1,803✔
1145
    const parameters = this.declareParameters(
1,806✔
1146
      [parameter],
1147
      descriptor ? [descriptor] : [],
1,806✔
1148
      ignoreOveride
1149
    );
1150
    return parameters.length == 1 ? parameters[0] : null;
1,806!
1151
  }
1152

1153
  /**
1154
   * Declare a list of parameters.
1155
   *
1156
   * Internally register parameters with their corresponding descriptor one by one
1157
   * in the order they are provided. This is an atomic operation. If an error
1158
   * occurs the process halts and no further parameters are declared.
1159
   * Parameters that have already been processed are undeclared.
1160
   *
1161
   * While descriptors is an optional parameter, when provided there must be
1162
   * a descriptor for each parameter; otherwise an Error is thrown.
1163
   * If descriptors is not provided then a descriptor will be inferred
1164
   * from each parameter's state.
1165
   *
1166
   * When a parameter-override is available, the parameter's value
1167
   * will be replaced with that of the parameter-override unless ignoreOverrides
1168
   * is true.
1169
   *
1170
   * If a parameter by the same name has already been declared then an Error is thrown.
1171
   * A parameter must be undeclared before attempting to redeclare it.
1172
   *
1173
   * Prior to declaring the parameters each SetParameterEventCallback registered
1174
   * using setOnParameterEventCallback() is called in succession with the parameters
1175
   * list. Any SetParameterEventCallback that retuns does not return a successful
1176
   * result will cause the entire operation to terminate with no changes to the
1177
   * parameters. When all SetParameterEventCallbacks return successful then the
1178
   * list of parameters is updated.
1179
   *
1180
   * @param {Parameter[]} parameters - The parameters to declare.
1181
   * @param {ParameterDescriptor[]} [descriptors] - Optional descriptors,
1182
   *    a 1-1 correspondence with parameters.
1183
   * @param {boolean} ignoreOverrides - When true, parameter-overrides are
1184
   *    not considered, i.e.,ignored.
1185
   * @return {Parameter[]} - The declared parameters.
1186
   */
1187
  declareParameters(parameters, descriptors = [], ignoreOverrides = false) {
×
1188
    if (!Array.isArray(parameters)) {
1,806!
1189
      throw new TypeError('Invalid parameter: expected array of Parameter');
×
1190
    }
1191
    if (!Array.isArray(descriptors)) {
1,806!
1192
      throw new TypeError(
×
1193
        'Invalid parameters: expected array of ParameterDescriptor'
1194
      );
1195
    }
1196
    if (descriptors.length > 0 && parameters.length !== descriptors.length) {
1,806!
1197
      throw new TypeError(
×
1198
        'Each parameter must have a cooresponding ParameterDescriptor'
1199
      );
1200
    }
1201

1202
    const declaredDescriptors = [];
1,806✔
1203
    const declaredParameters = [];
1,806✔
1204
    const declaredParameterCollisions = [];
1,806✔
1205
    for (let i = 0; i < parameters.length; i++) {
1,806✔
1206
      let parameter =
1207
        !ignoreOverrides && this._parameterOverrides.has(parameters[i].name)
1,806✔
1208
          ? this._parameterOverrides.get(parameters[i].name)
1209
          : parameters[i];
1210

1211
      // stop processing parameters that have already been declared
1212
      if (this._parameters.has(parameter.name)) {
1,806!
1213
        declaredParameterCollisions.push(parameter);
×
1214
        continue;
×
1215
      }
1216

1217
      // create descriptor for parameter if not provided
1218
      let descriptor =
1219
        descriptors.length > 0
1,806✔
1220
          ? descriptors[i]
1221
          : ParameterDescriptor.fromParameter(parameter);
1222

1223
      descriptor.validate();
1,806✔
1224

1225
      declaredDescriptors.push(descriptor);
1,806✔
1226
      declaredParameters.push(parameter);
1,806✔
1227
    }
1228

1229
    if (declaredParameterCollisions.length > 0) {
1,806!
1230
      const errorMsg =
1231
        declaredParameterCollisions.length == 1
×
1232
          ? `Parameter(${declaredParameterCollisions[0]}) already declared.`
1233
          : `Multiple parameters already declared, e.g., Parameter(${declaredParameterCollisions[0]}).`;
1234
      throw new Error(errorMsg);
×
1235
    }
1236

1237
    // register descriptor
1238
    for (const descriptor of declaredDescriptors) {
1,806✔
1239
      this._parameterDescriptors.set(descriptor.name, descriptor);
1,806✔
1240
    }
1241

1242
    const result = this._setParametersAtomically(declaredParameters, true);
1,806✔
1243
    if (!result.successful) {
1,806!
1244
      // unregister descriptors
1245
      for (const descriptor of declaredDescriptors) {
×
1246
        this._parameterDescriptors.delete(descriptor.name);
×
1247
      }
1248

1249
      throw new Error(result.reason);
×
1250
    }
1251

1252
    return this.getParameters(declaredParameters.map((param) => param.name));
1,806✔
1253
  }
1254

1255
  /**
1256
   * Undeclare a parameter.
1257
   *
1258
   * Readonly parameters can not be undeclared or updated.
1259
   * @param {string} name - Name of parameter to undeclare.
1260
   * @return {undefined} -
1261
   */
1262
  undeclareParameter(name) {
1263
    if (!this.hasParameter(name)) return;
3!
1264

1265
    const descriptor = this.getParameterDescriptor(name);
3✔
1266
    if (descriptor.readOnly) {
3!
1267
      throw new Error(
×
1268
        `${name} parameter is read-only and can not be undeclared`
1269
      );
1270
    }
1271

1272
    this._parameters.delete(name);
3✔
1273
    this._parameterDescriptors.delete(name);
3✔
1274
  }
1275

1276
  /**
1277
   * Determine if a parameter has been declared.
1278
   * @param {string} name - name of parameter
1279
   * @returns {boolean} - Return true if parameter is declared; false otherwise.
1280
   */
1281
  hasParameter(name) {
1282
    return this._parameters.has(name);
5,277✔
1283
  }
1284

1285
  /**
1286
   * Get a declared parameter by name.
1287
   *
1288
   * If unable to locate a declared parameter then a
1289
   * parameter with type == PARAMETER_NOT_SET is returned.
1290
   *
1291
   * @param {string} name - The name of the parameter.
1292
   * @return {Parameter} - The parameter.
1293
   */
1294
  getParameter(name) {
1295
    return this.getParameters([name])[0];
1,737✔
1296
  }
1297

1298
  /**
1299
   * Get a list of parameters.
1300
   *
1301
   * Find and return the declared parameters.
1302
   * If no names are provided return all declared parameters.
1303
   *
1304
   * If unable to locate a declared parameter then a
1305
   * parameter with type == PARAMETER_NOT_SET is returned in
1306
   * it's place.
1307
   *
1308
   * @param {string[]} [names] - The names of the declared parameters
1309
   *    to find or null indicating to return all declared parameters.
1310
   * @return {Parameter[]} - The parameters.
1311
   */
1312
  getParameters(names = []) {
24✔
1313
    let params = [];
3,570✔
1314

1315
    if (names.length == 0) {
3,570✔
1316
      // get all parameters
1317
      params = [...this._parameters.values()];
24✔
1318
      return params;
24✔
1319
    }
1320

1321
    for (const name of names) {
3,546✔
1322
      const param = this.hasParameter(name)
3,549!
1323
        ? this._parameters.get(name)
1324
        : new Parameter(name, ParameterType.PARAMETER_NOT_SET);
1325

1326
      params.push(param);
3,549✔
1327
    }
1328

1329
    return params;
3,546✔
1330
  }
1331

1332
  /**
1333
   * Get the types of given parameters.
1334
   *
1335
   * Return the types of given parameters.
1336
   *
1337
   * @param {string[]} [names] - The names of the declared parameters.
1338
   * @return {Uint8Array} - The types.
1339
   */
1340
  getParameterTypes(names = []) {
×
1341
    let types = [];
3✔
1342

1343
    for (const name of names) {
3✔
1344
      const descriptor = this._parameterDescriptors.get(name);
9✔
1345
      if (descriptor) {
9!
1346
        types.push(descriptor.type);
9✔
1347
      }
1348
    }
1349
    return types;
3✔
1350
  }
1351

1352
  /**
1353
   * Get the names of all declared parameters.
1354
   *
1355
   * @return {Array<string>} - The declared parameter names or empty array if
1356
   *    no parameters have been declared.
1357
   */
1358
  getParameterNames() {
1359
    return this.getParameters().map((param) => param.name);
45✔
1360
  }
1361

1362
  /**
1363
   * Determine if a parameter descriptor exists.
1364
   *
1365
   * @param {string} name - The name of a descriptor to for.
1366
   * @return {boolean} - True if a descriptor has been declared; otherwise false.
1367
   */
1368
  hasParameterDescriptor(name) {
1369
    return !!this.getParameterDescriptor(name);
1,824✔
1370
  }
1371

1372
  /**
1373
   * Get a declared parameter descriptor by name.
1374
   *
1375
   * If unable to locate a declared parameter descriptor then a
1376
   * descriptor with type == PARAMETER_NOT_SET is returned.
1377
   *
1378
   * @param {string} name - The name of the parameter descriptor to find.
1379
   * @return {ParameterDescriptor} - The parameter descriptor.
1380
   */
1381
  getParameterDescriptor(name) {
1382
    return this.getParameterDescriptors([name])[0];
3,651✔
1383
  }
1384

1385
  /**
1386
   * Find a list of declared ParameterDescriptors.
1387
   *
1388
   * If no names are provided return all declared descriptors.
1389
   *
1390
   * If unable to locate a declared descriptor then a
1391
   * descriptor with type == PARAMETER_NOT_SET is returned in
1392
   * it's place.
1393
   *
1394
   * @param {string[]} [names] - The names of the declared parameter
1395
   *    descriptors to find or null indicating to return all declared descriptors.
1396
   * @return {ParameterDescriptor[]} - The parameter descriptors.
1397
   */
1398
  getParameterDescriptors(names = []) {
×
1399
    let descriptors = [];
3,654✔
1400

1401
    if (names.length == 0) {
3,654!
1402
      // get all parameters
1403
      descriptors = [...this._parameterDescriptors.values()];
×
1404
      return descriptors;
×
1405
    }
1406

1407
    for (const name of names) {
3,654✔
1408
      let descriptor = this._parameterDescriptors.get(name);
3,657✔
1409
      if (!descriptor) {
3,657!
1410
        descriptor = new ParameterDescriptor(
×
1411
          name,
1412
          ParameterType.PARAMETER_NOT_SET
1413
        );
1414
      }
1415
      descriptors.push(descriptor);
3,657✔
1416
    }
1417

1418
    return descriptors;
3,654✔
1419
  }
1420

1421
  /**
1422
   * Replace a declared parameter.
1423
   *
1424
   * The parameter being replaced must be a declared parameter who's descriptor
1425
   * is not readOnly; otherwise an Error is thrown.
1426
   *
1427
   * @param {Parameter} parameter - The new parameter.
1428
   * @return {rclnodejs.rcl_interfaces.msg.SetParameterResult} - The result of the operation.
1429
   */
1430
  setParameter(parameter) {
1431
    const results = this.setParameters([parameter]);
12✔
1432
    return results[0];
12✔
1433
  }
1434

1435
  /**
1436
   * Replace a list of declared parameters.
1437
   *
1438
   * Declared parameters are replaced in the order they are provided and
1439
   * a ParameterEvent is published for each individual parameter change.
1440
   *
1441
   * Prior to setting the parameters each SetParameterEventCallback registered
1442
   * using setOnParameterEventCallback() is called in succession with the parameters
1443
   * list. Any SetParameterEventCallback that retuns does not return a successful
1444
   * result will cause the entire operation to terminate with no changes to the
1445
   * parameters. When all SetParameterEventCallbacks return successful then the
1446
   * list of parameters is updated.
1447
   *
1448
   * If an error occurs, the process is stopped and returned. Parameters
1449
   * set before an error remain unchanged.
1450
   *
1451
   * @param {Parameter[]} parameters - The parameters to set.
1452
   * @return {rclnodejs.rcl_interfaces.msg.SetParameterResult[]} - A list of SetParameterResult, one for each parameter that was set.
1453
   */
1454
  setParameters(parameters = []) {
×
1455
    return parameters.map((parameter) =>
15✔
1456
      this.setParametersAtomically([parameter])
15✔
1457
    );
1458
  }
1459

1460
  /**
1461
   * Repalce a list of declared parameters atomically.
1462
   *
1463
   * Declared parameters are replaced in the order they are provided.
1464
   * A single ParameterEvent is published collectively for all changed
1465
   * parameters.
1466
   *
1467
   * Prior to setting the parameters each SetParameterEventCallback registered
1468
   * using setOnParameterEventCallback() is called in succession with the parameters
1469
   * list. Any SetParameterEventCallback that retuns does not return a successful
1470
   * result will cause the entire operation to terminate with no changes to the
1471
   * parameters. When all SetParameterEventCallbacks return successful then the
1472
   * list of parameters is updated.d
1473
   *
1474
   * If an error occurs, the process stops immediately. All parameters updated to
1475
   * the point of the error are reverted to their previous state.
1476
   *
1477
   * @param {Parameter[]} parameters - The parameters to set.
1478
   * @return {rclnodejs.rcl_interfaces.msg.SetParameterResult} - describes the result of setting 1 or more parameters.
1479
   */
1480
  setParametersAtomically(parameters = []) {
×
1481
    return this._setParametersAtomically(parameters);
18✔
1482
  }
1483

1484
  /**
1485
   * Internal method for updating parameters atomically.
1486
   *
1487
   * Prior to setting the parameters each SetParameterEventCallback registered
1488
   * using setOnParameterEventCallback() is called in succession with the parameters
1489
   * list. Any SetParameterEventCallback that retuns does not return a successful
1490
   * result will cause the entire operation to terminate with no changes to the
1491
   * parameters. When all SetParameterEventCallbacks return successful then the
1492
   * list of parameters is updated.
1493
   *
1494
   * @param {Paramerter[]} parameters - The parameters to update.
1495
   * @param {boolean} declareParameterMode - When true parameters are being declared;
1496
   *    otherwise they are being changed.
1497
   * @return {SetParameterResult} - A single collective result.
1498
   */
1499
  _setParametersAtomically(parameters = [], declareParameterMode = false) {
18!
1500
    let result = this._validateParameters(parameters, declareParameterMode);
1,824✔
1501
    if (!result.successful) {
1,824!
1502
      return result;
×
1503
    }
1504

1505
    // give all SetParametersCallbacks a chance to veto this change
1506
    for (const callback of this._setParametersCallbacks) {
1,824✔
1507
      result = callback(parameters);
141✔
1508
      if (!result.successful) {
141✔
1509
        // a callback has vetoed a parameter change
1510
        return result;
3✔
1511
      }
1512
    }
1513

1514
    // collectively track updates to parameters for use
1515
    // when publishing a ParameterEvent
1516
    const newParameters = [];
1,821✔
1517
    const changedParameters = [];
1,821✔
1518
    const deletedParameters = [];
1,821✔
1519

1520
    for (const parameter of parameters) {
1,821✔
1521
      if (parameter.type == ParameterType.PARAMETER_NOT_SET) {
1,821✔
1522
        this.undeclareParameter(parameter.name);
3✔
1523
        deletedParameters.push(parameter);
3✔
1524
      } else {
1525
        this._parameters.set(parameter.name, parameter);
1,818✔
1526
        if (declareParameterMode) {
1,818✔
1527
          newParameters.push(parameter);
1,806✔
1528
        } else {
1529
          changedParameters.push(parameter);
12✔
1530
        }
1531
      }
1532
    }
1533

1534
    // create ParameterEvent
1535
    const parameterEvent = new (loader.loadInterface(
1,821✔
1536
      PARAMETER_EVENT_MSG_TYPE
1537
    ))();
1538

1539
    const { seconds, nanoseconds } = this._clock.now().secondsAndNanoseconds;
1,821✔
1540
    parameterEvent.stamp = {
1,821✔
1541
      sec: Number(seconds),
1542
      nanosec: Number(nanoseconds),
1543
    };
1544

1545
    parameterEvent.node =
1,821✔
1546
      this.namespace() === '/'
1,821✔
1547
        ? this.namespace() + this.name()
1548
        : this.namespace() + '/' + this.name();
1549

1550
    if (newParameters.length > 0) {
1,821✔
1551
      parameterEvent['new_parameters'] = newParameters.map((parameter) =>
1,806✔
1552
        parameter.toParameterMessage()
1,806✔
1553
      );
1554
    }
1555
    if (changedParameters.length > 0) {
1,821✔
1556
      parameterEvent['changed_parameters'] = changedParameters.map(
12✔
1557
        (parameter) => parameter.toParameterMessage()
12✔
1558
      );
1559
    }
1560
    if (deletedParameters.length > 0) {
1,821✔
1561
      parameterEvent['deleted_parameters'] = deletedParameters.map(
3✔
1562
        (parameter) => parameter.toParameterMessage()
3✔
1563
      );
1564
    }
1565

1566
    // Publish ParameterEvent.
1567
    this._parameterEventPublisher.publish(parameterEvent);
1,821✔
1568

1569
    return {
1,821✔
1570
      successful: true,
1571
      reason: '',
1572
    };
1573
  }
1574

1575
  /**
1576
   * This callback is called when declaring a parameter or setting a parameter.
1577
   * The callback is provided a list of parameters and returns a SetParameterResult
1578
   * to indicate approval or veto of the operation.
1579
   *
1580
   * @callback SetParametersCallback
1581
   * @param {Parameter[]} parameters - The message published
1582
   * @returns {rcl_interfaces.msg.SetParameterResult} -
1583
   *
1584
   * @see [Node.addOnSetParametersCallback]{@link Node#addOnSetParametersCallback}
1585
   * @see [Node.removeOnSetParametersCallback]{@link Node#removeOnSetParametersCallback}
1586
   */
1587

1588
  /**
1589
   * Add a callback to the front of the list of callbacks invoked for parameter declaration
1590
   * and setting. No checks are made for duplicate callbacks.
1591
   *
1592
   * @param {SetParametersCallback} callback - The callback to add.
1593
   * @returns {undefined}
1594
   */
1595
  addOnSetParametersCallback(callback) {
1596
    this._setParametersCallbacks.unshift(callback);
1,725✔
1597
  }
1598

1599
  /**
1600
   * Remove a callback from the list of SetParametersCallbacks.
1601
   * If the callback is not found the process is a nop.
1602
   *
1603
   * @param {SetParametersCallback} callback - The callback to be removed
1604
   * @returns {undefined}
1605
   */
1606
  removeOnSetParametersCallback(callback) {
1607
    const idx = this._setParametersCallbacks.indexOf(callback);
6✔
1608
    if (idx > -1) {
6!
1609
      this._setParametersCallbacks.splice(idx, 1);
6✔
1610
    }
1611
  }
1612

1613
  // returns on 1st error or result {successful, reason}
1614
  _validateParameters(parameters = [], declareParameterMode = false) {
×
1615
    for (const parameter of parameters) {
1,824✔
1616
      // detect invalid parameter
1617
      try {
1,824✔
1618
        parameter.validate();
1,824✔
1619
      } catch {
1620
        return {
×
1621
          successful: false,
1622
          reason: `Invalid ${parameter.name}`,
1623
        };
1624
      }
1625

1626
      // detect undeclared parameter
1627
      if (!this.hasParameterDescriptor(parameter.name)) {
1,824!
1628
        return {
×
1629
          successful: false,
1630
          reason: `Parameter ${parameter.name} has not been declared`,
1631
        };
1632
      }
1633

1634
      // detect readonly parameter that can not be updated
1635
      const descriptor = this.getParameterDescriptor(parameter.name);
1,824✔
1636
      if (!declareParameterMode && descriptor.readOnly) {
1,824!
1637
        return {
×
1638
          successful: false,
1639
          reason: `Parameter ${parameter.name} is readonly`,
1640
        };
1641
      }
1642

1643
      // validate parameter against descriptor if not an undeclare action
1644
      if (parameter.type != ParameterType.PARAMETER_NOT_SET) {
1,824✔
1645
        try {
1,821✔
1646
          descriptor.validateParameter(parameter);
1,821✔
1647
        } catch {
1648
          return {
×
1649
            successful: false,
1650
            reason: `Parameter ${parameter.name} does not  readonly`,
1651
          };
1652
        }
1653
      }
1654
    }
1655

1656
    return {
1,824✔
1657
      successful: true,
1658
      reason: null,
1659
    };
1660
  }
1661

1662
  // Get a Map(nodeName->Parameter[]) of CLI parameter args that
1663
  // apply to 'this' node, .e.g., -p mynode:foo:=bar -p hello:=world
1664
  _getNativeParameterOverrides() {
1665
    const overrides = new Map();
1,701✔
1666

1667
    // Get native parameters from rcl context->global_arguments.
1668
    // rclnodejs returns an array of objects, 1 for each node e.g., -p my_node:foo:=bar,
1669
    // and a node named '/**' for global parameter rules,
1670
    // i.e., does not include a node identifier, e.g., -p color:=red
1671
    // {
1672
    //   name: string // node name
1673
    //   parameters[] = {
1674
    //     name: string
1675
    //     type: uint
1676
    //     value: object
1677
    // }
1678
    const cliParamOverrideData = rclnodejs.getParameterOverrides(
1,701✔
1679
      this.context.handle
1680
    );
1681

1682
    // convert native CLI parameterOverrides to Map<nodeName,Array<ParameterOverride>>
1683
    const cliParamOverrides = new Map();
1,701✔
1684
    if (cliParamOverrideData) {
1,701✔
1685
      for (let nodeParamData of cliParamOverrideData) {
24✔
1686
        const nodeName = nodeParamData.name;
36✔
1687
        const nodeParamOverrides = [];
36✔
1688
        for (let paramData of nodeParamData.parameters) {
36✔
1689
          const paramOverride = new Parameter(
51✔
1690
            paramData.name,
1691
            paramData.type,
1692
            paramData.value
1693
          );
1694
          nodeParamOverrides.push(paramOverride);
51✔
1695
        }
1696
        cliParamOverrides.set(nodeName, nodeParamOverrides);
36✔
1697
      }
1698
    }
1699

1700
    // collect global CLI global parameters, name == /**
1701
    let paramOverrides = cliParamOverrides.get('/**'); // array of ParameterOverrides
1,701✔
1702
    if (paramOverrides) {
1,701✔
1703
      for (const parameter of paramOverrides) {
15✔
1704
        overrides.set(parameter.name, parameter);
18✔
1705
      }
1706
    }
1707

1708
    // merge CLI node parameterOverrides with global parameterOverrides, replace existing
1709
    paramOverrides = cliParamOverrides.get(this.name()); // array of ParameterOverrides
1,701✔
1710
    if (paramOverrides) {
1,701✔
1711
      for (const parameter of paramOverrides) {
15✔
1712
        overrides.set(parameter.name, parameter);
21✔
1713
      }
1714
    }
1715

1716
    return overrides;
1,701✔
1717
  }
1718

1719
  /**
1720
   * Invokes the callback with a raw message of the given type. After the callback completes
1721
   * the message will be destroyed.
1722
   * @param {function} Type - Message type to create.
1723
   * @param {function} callback - Callback to invoke. First parameter will be the raw message,
1724
   * and the second is a function to retrieve the deserialized message.
1725
   * @returns {undefined}
1726
   */
1727
  _runWithMessageType(Type, callback) {
1728
    let message = new Type();
2,098✔
1729

1730
    callback(message.toRawROS(), () => {
2,098✔
1731
      let result = new Type();
1,658✔
1732
      result.deserialize(message.refObject);
1,658✔
1733

1734
      return result;
1,658✔
1735
    });
1736

1737
    Type.destoryRawROS(message);
2,098✔
1738
  }
1739

1740
  _addActionClient(actionClient) {
1741
    this._actionClients.push(actionClient);
114✔
1742
    this.syncHandles();
114✔
1743
  }
1744

1745
  _addActionServer(actionServer) {
1746
    this._actionServers.push(actionServer);
114✔
1747
    this.syncHandles();
114✔
1748
  }
1749
}
1750

1751
/**
1752
 * Create an Options instance initialized with default values.
1753
 * @returns {Options} - The new initialized instance.
1754
 * @static
1755
 * @example
1756
 * {
1757
 *   enableTypedArray: true,
1758
 *   isRaw: false,
1759
 *   qos: QoS.profileDefault,
1760
 *   contentFilter: undefined,
1761
 * }
1762
 */
1763
Node.getDefaultOptions = function () {
78✔
1764
  return {
14,502✔
1765
    enableTypedArray: true,
1766
    isRaw: false,
1767
    qos: QoS.profileDefault,
1768
    contentFilter: undefined,
1769
  };
1770
};
1771

1772
module.exports = Node;
78✔
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

© 2025 Coveralls, Inc