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

RobotWebTools / rclnodejs / 14776329687

01 May 2025 01:37PM UTC coverage: 85.071% (+0.05%) from 85.019%
14776329687

Pull #1101

github

web-flow
Merge 3d4757832 into 5dc2276fc
Pull Request #1101: Update Windows action for rolling

711 of 928 branches covered (76.62%)

Branch coverage included in aggregate %.

1745 of 1959 relevant lines covered (89.08%)

1105.83 hits per line

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

89.63
/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 GuardCondition = require('./guard_condition.js');
78✔
25
const loader = require('./interface_loader.js');
78✔
26
const Logging = require('./logging.js');
78✔
27
const NodeOptions = require('./node_options.js');
78✔
28
const {
29
  ParameterType,
30
  Parameter,
31
  ParameterDescriptor,
32
} = require('./parameter.js');
78✔
33
const ParameterService = require('./parameter_service.js');
78✔
34
const Publisher = require('./publisher.js');
78✔
35
const QoS = require('./qos.js');
78✔
36
const Rates = require('./rate.js');
78✔
37
const Service = require('./service.js');
78✔
38
const Subscription = require('./subscription.js');
78✔
39
const TimeSource = require('./time_source.js');
78✔
40
const Timer = require('./timer.js');
78✔
41
const Entity = require('./entity.js');
78✔
42

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

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

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

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

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

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

88
    this._context = context;
1,698✔
89
    this.context.onNodeCreated(this);
1,698✔
90

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

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

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

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

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

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

145
    if (options.startParameterServices) {
1,698✔
146
      this._parameterService = new ParameterService(this);
1,680✔
147
      this._parameterService.start();
1,680✔
148
    }
149
  }
150

151
  execute(handles) {
152
    let timersReady = this._timers.filter((timer) =>
15,893✔
153
      handles.includes(timer.handle)
13,882✔
154
    );
155
    let guardsReady = this._guards.filter((guard) =>
15,893✔
156
      handles.includes(guard.handle)
9✔
157
    );
158
    let subscriptionsReady = this._subscriptions.filter((subscription) =>
15,893✔
159
      handles.includes(subscription.handle)
1,438✔
160
    );
161
    let clientsReady = this._clients.filter((client) =>
15,893✔
162
      handles.includes(client.handle)
295✔
163
    );
164
    let servicesReady = this._services.filter((service) =>
15,893✔
165
      handles.includes(service.handle)
41,704✔
166
    );
167
    let actionClientsReady = this._actionClients.filter((actionClient) =>
15,893✔
168
      handles.includes(actionClient.handle)
470✔
169
    );
170
    let actionServersReady = this._actionServers.filter((actionServer) =>
15,893✔
171
      handles.includes(actionServer.handle)
470✔
172
    );
173

174
    timersReady.forEach((timer) => {
15,893✔
175
      if (timer.isReady()) {
13,851✔
176
        rclnodejs.callTimer(timer.handle);
13,752✔
177
        timer.callback();
13,752✔
178
      }
179
    });
180

181
    for (const subscription of subscriptionsReady) {
15,893✔
182
      if (subscription.isDestroyed()) continue;
1,326✔
183
      if (subscription.isRaw) {
1,296✔
184
        let rawMessage = rclnodejs.rclTakeRaw(subscription.handle);
3✔
185
        if (rawMessage) {
3!
186
          subscription.processResponse(rawMessage);
3✔
187
        }
188
        continue;
3✔
189
      }
190

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

202
    for (const guard of guardsReady) {
15,893✔
203
      if (guard.isDestroyed()) continue;
9!
204

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

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

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

241
    for (const actionClient of actionClientsReady) {
15,893✔
242
      if (actionClient.isDestroyed()) continue;
223!
243

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

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

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

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

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

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

322
    for (const actionServer of actionServersReady) {
15,893✔
323
      if (actionServer.isDestroyed()) continue;
277!
324

325
      const properties = actionServer.handle.properties;
277✔
326

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

489
    if (options === undefined) {
14,490✔
490
      return Node.getDefaultOptions();
14,451✔
491
    }
492

493
    if (options.enableTypedArray === undefined) {
39✔
494
      options = Object.assign(options, { enableTypedArray: true });
3✔
495
    }
496

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

501
    if (options.isRaw === undefined) {
39✔
502
      options = Object.assign(options, { isRaw: false });
9✔
503
    }
504

505
    return options;
39✔
506
  }
507

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

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

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

537
    return timer;
168✔
538
  }
539

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

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

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

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

568
    return rate;
21✔
569
  }
570

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

586
  _createPublisher(typeClass, topic, options, publisherClass) {
587
    if (typeof typeClass === 'string' || typeof typeClass === 'object') {
2,772✔
588
      typeClass = loader.loadInterface(typeClass);
2,700✔
589
    }
590
    options = this._validateOptions(options);
2,751✔
591

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

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

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

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

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

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

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

670
    return subscription;
1,008✔
671
  }
672

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

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

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

704
    return client;
138✔
705
  }
706

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

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

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

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

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

764
    return service;
10,176✔
765
  }
766

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

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

782
    return guard;
9✔
783
  }
784

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

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

800
    this.context.onNodeDestroyed(this);
1,764✔
801

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

1090
  /**
1091
   * Get the number of clients on a given service name.
1092
   * @param {string} serviceName - the service name
1093
   * @returns {Number}
1094
   */
1095
  countClients(serviceName) {
1096
    return rclnodejs.countClients(this.handle, serviceName);
6✔
1097
  }
1098

1099
  /**
1100
   * Get the number of services on a given service name.
1101
   * @param {string} serviceName - the service name
1102
   * @returns {Number}
1103
   */
1104
  countServices(serviceName) {
1105
    return rclnodejs.countServices(this.handle, serviceName);
3✔
1106
  }
1107

1108
  /**
1109
   * Get the list of parameter-overrides found on the commandline and
1110
   * in the NodeOptions.parameter_overrides property.
1111
   *
1112
   * @return {Array<Parameter>} - An array of Parameters.
1113
   */
1114
  getParameterOverrides() {
1115
    return Array.from(this._parameterOverrides.values());
24✔
1116
  }
1117

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

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

1193
    const declaredDescriptors = [];
1,803✔
1194
    const declaredParameters = [];
1,803✔
1195
    const declaredParameterCollisions = [];
1,803✔
1196
    for (let i = 0; i < parameters.length; i++) {
1,803✔
1197
      let parameter =
1198
        !ignoreOverrides && this._parameterOverrides.has(parameters[i].name)
1,803✔
1199
          ? this._parameterOverrides.get(parameters[i].name)
1200
          : parameters[i];
1201

1202
      // stop processing parameters that have already been declared
1203
      if (this._parameters.has(parameter.name)) {
1,803!
1204
        declaredParameterCollisions.push(parameter);
×
1205
        continue;
×
1206
      }
1207

1208
      // create descriptor for parameter if not provided
1209
      let descriptor =
1210
        descriptors.length > 0
1,803✔
1211
          ? descriptors[i]
1212
          : ParameterDescriptor.fromParameter(parameter);
1213

1214
      descriptor.validate();
1,803✔
1215

1216
      declaredDescriptors.push(descriptor);
1,803✔
1217
      declaredParameters.push(parameter);
1,803✔
1218
    }
1219

1220
    if (declaredParameterCollisions.length > 0) {
1,803!
1221
      const errorMsg =
1222
        declaredParameterCollisions.length == 1
×
1223
          ? `Parameter(${declaredParameterCollisions[0]}) already declared.`
1224
          : `Multiple parameters already declared, e.g., Parameter(${declaredParameterCollisions[0]}).`;
1225
      throw new Error(errorMsg);
×
1226
    }
1227

1228
    // register descriptor
1229
    for (const descriptor of declaredDescriptors) {
1,803✔
1230
      this._parameterDescriptors.set(descriptor.name, descriptor);
1,803✔
1231
    }
1232

1233
    const result = this._setParametersAtomically(declaredParameters, true);
1,803✔
1234
    if (!result.successful) {
1,803!
1235
      // unregister descriptors
1236
      for (const descriptor of declaredDescriptors) {
×
1237
        this._parameterDescriptors.delete(descriptor.name);
×
1238
      }
1239

1240
      throw new Error(result.reason);
×
1241
    }
1242

1243
    return this.getParameters(declaredParameters.map((param) => param.name));
1,803✔
1244
  }
1245

1246
  /**
1247
   * Undeclare a parameter.
1248
   *
1249
   * Readonly parameters can not be undeclared or updated.
1250
   * @param {string} name - Name of parameter to undeclare.
1251
   * @return {undefined} -
1252
   */
1253
  undeclareParameter(name) {
1254
    if (!this.hasParameter(name)) return;
3!
1255

1256
    const descriptor = this.getParameterDescriptor(name);
3✔
1257
    if (descriptor.readOnly) {
3!
1258
      throw new Error(
×
1259
        `${name} parameter is read-only and can not be undeclared`
1260
      );
1261
    }
1262

1263
    this._parameters.delete(name);
3✔
1264
    this._parameterDescriptors.delete(name);
3✔
1265
  }
1266

1267
  /**
1268
   * Determine if a parameter has been declared.
1269
   * @param {string} name - name of parameter
1270
   * @returns {boolean} - Return true if parameter is declared; false otherwise.
1271
   */
1272
  hasParameter(name) {
1273
    return this._parameters.has(name);
5,268✔
1274
  }
1275

1276
  /**
1277
   * Get a declared parameter by name.
1278
   *
1279
   * If unable to locate a declared parameter then a
1280
   * parameter with type == PARAMETER_NOT_SET is returned.
1281
   *
1282
   * @param {string} name - The name of the parameter.
1283
   * @return {Parameter} - The parameter.
1284
   */
1285
  getParameter(name) {
1286
    return this.getParameters([name])[0];
1,734✔
1287
  }
1288

1289
  /**
1290
   * Get a list of parameters.
1291
   *
1292
   * Find and return the declared parameters.
1293
   * If no names are provided return all declared parameters.
1294
   *
1295
   * If unable to locate a declared parameter then a
1296
   * parameter with type == PARAMETER_NOT_SET is returned in
1297
   * it's place.
1298
   *
1299
   * @param {string[]} [names] - The names of the declared parameters
1300
   *    to find or null indicating to return all declared parameters.
1301
   * @return {Parameter[]} - The parameters.
1302
   */
1303
  getParameters(names = []) {
24✔
1304
    let params = [];
3,564✔
1305

1306
    if (names.length == 0) {
3,564✔
1307
      // get all parameters
1308
      params = [...this._parameters.values()];
24✔
1309
      return params;
24✔
1310
    }
1311

1312
    for (const name of names) {
3,540✔
1313
      const param = this.hasParameter(name)
3,543!
1314
        ? this._parameters.get(name)
1315
        : new Parameter(name, ParameterType.PARAMETER_NOT_SET);
1316

1317
      params.push(param);
3,543✔
1318
    }
1319

1320
    return params;
3,540✔
1321
  }
1322

1323
  /**
1324
   * Get the types of given parameters.
1325
   *
1326
   * Return the types of given parameters.
1327
   *
1328
   * @param {string[]} [names] - The names of the declared parameters.
1329
   * @return {Uint8Array} - The types.
1330
   */
1331
  getParameterTypes(names = []) {
×
1332
    let types = [];
3✔
1333

1334
    for (const name of names) {
3✔
1335
      const descriptor = this._parameterDescriptors.get(name);
9✔
1336
      if (descriptor) {
9!
1337
        types.push(descriptor.type);
9✔
1338
      }
1339
    }
1340
    return types;
3✔
1341
  }
1342

1343
  /**
1344
   * Get the names of all declared parameters.
1345
   *
1346
   * @return {Array<string>} - The declared parameter names or empty array if
1347
   *    no parameters have been declared.
1348
   */
1349
  getParameterNames() {
1350
    return this.getParameters().map((param) => param.name);
45✔
1351
  }
1352

1353
  /**
1354
   * Determine if a parameter descriptor exists.
1355
   *
1356
   * @param {string} name - The name of a descriptor to for.
1357
   * @return {boolean} - True if a descriptor has been declared; otherwise false.
1358
   */
1359
  hasParameterDescriptor(name) {
1360
    return !!this.getParameterDescriptor(name);
1,821✔
1361
  }
1362

1363
  /**
1364
   * Get a declared parameter descriptor by name.
1365
   *
1366
   * If unable to locate a declared parameter descriptor then a
1367
   * descriptor with type == PARAMETER_NOT_SET is returned.
1368
   *
1369
   * @param {string} name - The name of the parameter descriptor to find.
1370
   * @return {ParameterDescriptor} - The parameter descriptor.
1371
   */
1372
  getParameterDescriptor(name) {
1373
    return this.getParameterDescriptors([name])[0];
3,645✔
1374
  }
1375

1376
  /**
1377
   * Find a list of declared ParameterDescriptors.
1378
   *
1379
   * If no names are provided return all declared descriptors.
1380
   *
1381
   * If unable to locate a declared descriptor then a
1382
   * descriptor with type == PARAMETER_NOT_SET is returned in
1383
   * it's place.
1384
   *
1385
   * @param {string[]} [names] - The names of the declared parameter
1386
   *    descriptors to find or null indicating to return all declared descriptors.
1387
   * @return {ParameterDescriptor[]} - The parameter descriptors.
1388
   */
1389
  getParameterDescriptors(names = []) {
×
1390
    let descriptors = [];
3,648✔
1391

1392
    if (names.length == 0) {
3,648!
1393
      // get all parameters
1394
      descriptors = [...this._parameterDescriptors.values()];
×
1395
      return descriptors;
×
1396
    }
1397

1398
    for (const name of names) {
3,648✔
1399
      let descriptor = this._parameterDescriptors.get(name);
3,651✔
1400
      if (!descriptor) {
3,651!
1401
        descriptor = new ParameterDescriptor(
×
1402
          name,
1403
          ParameterType.PARAMETER_NOT_SET
1404
        );
1405
      }
1406
      descriptors.push(descriptor);
3,651✔
1407
    }
1408

1409
    return descriptors;
3,648✔
1410
  }
1411

1412
  /**
1413
   * Replace a declared parameter.
1414
   *
1415
   * The parameter being replaced must be a declared parameter who's descriptor
1416
   * is not readOnly; otherwise an Error is thrown.
1417
   *
1418
   * @param {Parameter} parameter - The new parameter.
1419
   * @return {rclnodejs.rcl_interfaces.msg.SetParameterResult} - The result of the operation.
1420
   */
1421
  setParameter(parameter) {
1422
    const results = this.setParameters([parameter]);
12✔
1423
    return results[0];
12✔
1424
  }
1425

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

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

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

1496
    // give all SetParametersCallbacks a chance to veto this change
1497
    for (const callback of this._setParametersCallbacks) {
1,821✔
1498
      result = callback(parameters);
141✔
1499
      if (!result.successful) {
141✔
1500
        // a callback has vetoed a parameter change
1501
        return result;
3✔
1502
      }
1503
    }
1504

1505
    // collectively track updates to parameters for use
1506
    // when publishing a ParameterEvent
1507
    const newParameters = [];
1,818✔
1508
    const changedParameters = [];
1,818✔
1509
    const deletedParameters = [];
1,818✔
1510

1511
    for (const parameter of parameters) {
1,818✔
1512
      if (parameter.type == ParameterType.PARAMETER_NOT_SET) {
1,818✔
1513
        this.undeclareParameter(parameter.name);
3✔
1514
        deletedParameters.push(parameter);
3✔
1515
      } else {
1516
        this._parameters.set(parameter.name, parameter);
1,815✔
1517
        if (declareParameterMode) {
1,815✔
1518
          newParameters.push(parameter);
1,803✔
1519
        } else {
1520
          changedParameters.push(parameter);
12✔
1521
        }
1522
      }
1523
    }
1524

1525
    // create ParameterEvent
1526
    const parameterEvent = new (loader.loadInterface(
1,818✔
1527
      PARAMETER_EVENT_MSG_TYPE
1528
    ))();
1529

1530
    const { seconds, nanoseconds } = this._clock.now().secondsAndNanoseconds;
1,818✔
1531
    parameterEvent.stamp = {
1,818✔
1532
      sec: Number(seconds),
1533
      nanosec: Number(nanoseconds),
1534
    };
1535

1536
    parameterEvent.node =
1,818✔
1537
      this.namespace() === '/'
1,818✔
1538
        ? this.namespace() + this.name()
1539
        : this.namespace() + '/' + this.name();
1540

1541
    if (newParameters.length > 0) {
1,818✔
1542
      parameterEvent['new_parameters'] = newParameters.map((parameter) =>
1,803✔
1543
        parameter.toParameterMessage()
1,803✔
1544
      );
1545
    }
1546
    if (changedParameters.length > 0) {
1,818✔
1547
      parameterEvent['changed_parameters'] = changedParameters.map(
12✔
1548
        (parameter) => parameter.toParameterMessage()
12✔
1549
      );
1550
    }
1551
    if (deletedParameters.length > 0) {
1,818✔
1552
      parameterEvent['deleted_parameters'] = deletedParameters.map(
3✔
1553
        (parameter) => parameter.toParameterMessage()
3✔
1554
      );
1555
    }
1556

1557
    // Publish ParameterEvent.
1558
    this._parameterEventPublisher.publish(parameterEvent);
1,818✔
1559

1560
    return {
1,818✔
1561
      successful: true,
1562
      reason: '',
1563
    };
1564
  }
1565

1566
  /**
1567
   * This callback is called when declaring a parameter or setting a parameter.
1568
   * The callback is provided a list of parameters and returns a SetParameterResult
1569
   * to indicate approval or veto of the operation.
1570
   *
1571
   * @callback SetParametersCallback
1572
   * @param {Parameter[]} parameters - The message published
1573
   * @returns {rcl_interfaces.msg.SetParameterResult} -
1574
   *
1575
   * @see [Node.addOnSetParametersCallback]{@link Node#addOnSetParametersCallback}
1576
   * @see [Node.removeOnSetParametersCallback]{@link Node#removeOnSetParametersCallback}
1577
   */
1578

1579
  /**
1580
   * Add a callback to the front of the list of callbacks invoked for parameter declaration
1581
   * and setting. No checks are made for duplicate callbacks.
1582
   *
1583
   * @param {SetParametersCallback} callback - The callback to add.
1584
   * @returns {undefined}
1585
   */
1586
  addOnSetParametersCallback(callback) {
1587
    this._setParametersCallbacks.unshift(callback);
1,722✔
1588
  }
1589

1590
  /**
1591
   * Remove a callback from the list of SetParametersCallbacks.
1592
   * If the callback is not found the process is a nop.
1593
   *
1594
   * @param {SetParametersCallback} callback - The callback to be removed
1595
   * @returns {undefined}
1596
   */
1597
  removeOnSetParametersCallback(callback) {
1598
    const idx = this._setParametersCallbacks.indexOf(callback);
6✔
1599
    if (idx > -1) {
6!
1600
      this._setParametersCallbacks.splice(idx, 1);
6✔
1601
    }
1602
  }
1603

1604
  // returns on 1st error or result {successful, reason}
1605
  _validateParameters(parameters = [], declareParameterMode = false) {
×
1606
    for (const parameter of parameters) {
1,821✔
1607
      // detect invalid parameter
1608
      try {
1,821✔
1609
        parameter.validate();
1,821✔
1610
      } catch {
1611
        return {
×
1612
          successful: false,
1613
          reason: `Invalid ${parameter.name}`,
1614
        };
1615
      }
1616

1617
      // detect undeclared parameter
1618
      if (!this.hasParameterDescriptor(parameter.name)) {
1,821!
1619
        return {
×
1620
          successful: false,
1621
          reason: `Parameter ${parameter.name} has not been declared`,
1622
        };
1623
      }
1624

1625
      // detect readonly parameter that can not be updated
1626
      const descriptor = this.getParameterDescriptor(parameter.name);
1,821✔
1627
      if (!declareParameterMode && descriptor.readOnly) {
1,821!
1628
        return {
×
1629
          successful: false,
1630
          reason: `Parameter ${parameter.name} is readonly`,
1631
        };
1632
      }
1633

1634
      // validate parameter against descriptor if not an undeclare action
1635
      if (parameter.type != ParameterType.PARAMETER_NOT_SET) {
1,821✔
1636
        try {
1,818✔
1637
          descriptor.validateParameter(parameter);
1,818✔
1638
        } catch {
1639
          return {
×
1640
            successful: false,
1641
            reason: `Parameter ${parameter.name} does not  readonly`,
1642
          };
1643
        }
1644
      }
1645
    }
1646

1647
    return {
1,821✔
1648
      successful: true,
1649
      reason: null,
1650
    };
1651
  }
1652

1653
  // Get a Map(nodeName->Parameter[]) of CLI parameter args that
1654
  // apply to 'this' node, .e.g., -p mynode:foo:=bar -p hello:=world
1655
  _getNativeParameterOverrides() {
1656
    const overrides = new Map();
1,698✔
1657

1658
    // Get native parameters from rcl context->global_arguments.
1659
    // rclnodejs returns an array of objects, 1 for each node e.g., -p my_node:foo:=bar,
1660
    // and a node named '/**' for global parameter rules,
1661
    // i.e., does not include a node identifier, e.g., -p color:=red
1662
    // {
1663
    //   name: string // node name
1664
    //   parameters[] = {
1665
    //     name: string
1666
    //     type: uint
1667
    //     value: object
1668
    // }
1669
    const cliParamOverrideData = rclnodejs.getParameterOverrides(
1,698✔
1670
      this.context.handle
1671
    );
1672

1673
    // convert native CLI parameterOverrides to Map<nodeName,Array<ParameterOverride>>
1674
    const cliParamOverrides = new Map();
1,698✔
1675
    if (cliParamOverrideData) {
1,698✔
1676
      for (let nodeParamData of cliParamOverrideData) {
24✔
1677
        const nodeName = nodeParamData.name;
36✔
1678
        const nodeParamOverrides = [];
36✔
1679
        for (let paramData of nodeParamData.parameters) {
36✔
1680
          const paramOverride = new Parameter(
51✔
1681
            paramData.name,
1682
            paramData.type,
1683
            paramData.value
1684
          );
1685
          nodeParamOverrides.push(paramOverride);
51✔
1686
        }
1687
        cliParamOverrides.set(nodeName, nodeParamOverrides);
36✔
1688
      }
1689
    }
1690

1691
    // collect global CLI global parameters, name == /**
1692
    let paramOverrides = cliParamOverrides.get('/**'); // array of ParameterOverrides
1,698✔
1693
    if (paramOverrides) {
1,698✔
1694
      for (const parameter of paramOverrides) {
15✔
1695
        overrides.set(parameter.name, parameter);
18✔
1696
      }
1697
    }
1698

1699
    // merge CLI node parameterOverrides with global parameterOverrides, replace existing
1700
    paramOverrides = cliParamOverrides.get(this.name()); // array of ParameterOverrides
1,698✔
1701
    if (paramOverrides) {
1,698✔
1702
      for (const parameter of paramOverrides) {
15✔
1703
        overrides.set(parameter.name, parameter);
21✔
1704
      }
1705
    }
1706

1707
    return overrides;
1,698✔
1708
  }
1709

1710
  /**
1711
   * Invokes the callback with a raw message of the given type. After the callback completes
1712
   * the message will be destroyed.
1713
   * @param {function} Type - Message type to create.
1714
   * @param {function} callback - Callback to invoke. First parameter will be the raw message,
1715
   * and the second is a function to retrieve the deserialized message.
1716
   * @returns {undefined}
1717
   */
1718
  _runWithMessageType(Type, callback) {
1719
    let message = new Type();
2,108✔
1720

1721
    callback(message.toRawROS(), () => {
2,108✔
1722
      let result = new Type();
1,665✔
1723
      result.deserialize(message.refObject);
1,665✔
1724

1725
      return result;
1,665✔
1726
    });
1727

1728
    Type.destoryRawROS(message);
2,108✔
1729
  }
1730

1731
  _addActionClient(actionClient) {
1732
    this._actionClients.push(actionClient);
114✔
1733
    this.syncHandles();
114✔
1734
  }
1735

1736
  _addActionServer(actionServer) {
1737
    this._actionServers.push(actionServer);
114✔
1738
    this.syncHandles();
114✔
1739
  }
1740
}
1741

1742
/**
1743
 * Create an Options instance initialized with default values.
1744
 * @returns {Options} - The new initialized instance.
1745
 * @static
1746
 * @example
1747
 * {
1748
 *   enableTypedArray: true,
1749
 *   isRaw: false,
1750
 *   qos: QoS.profileDefault,
1751
 *   contentFilter: undefined,
1752
 * }
1753
 */
1754
Node.getDefaultOptions = function () {
78✔
1755
  return {
14,481✔
1756
    enableTypedArray: true,
1757
    isRaw: false,
1758
    qos: QoS.profileDefault,
1759
    contentFilter: undefined,
1760
  };
1761
};
1762

1763
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

© 2026 Coveralls, Inc