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

RobotWebTools / rclnodejs / 16044464595

03 Jul 2025 07:38AM UTC coverage: 84.526% (-0.1%) from 84.625%
16044464595

Pull #1158

github

web-flow
Merge 90720ad95 into 82bbc82ff
Pull Request #1158: Enable npm test on Windows workflow

775 of 1009 branches covered (76.81%)

Branch coverage included in aggregate %.

1907 of 2164 relevant lines covered (88.12%)

3222.81 hits per line

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

88.34
/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');
208✔
18

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

48
// Parameter event publisher constants
49
const PARAMETER_EVENT_MSG_TYPE = 'rcl_interfaces/msg/ParameterEvent';
208✔
50
const PARAMETER_EVENT_TOPIC = 'parameter_events';
208✔
51

52
/**
53
 * @class - Class representing a Node in ROS
54
 */
55

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

76
    if (typeof nodeName !== 'string' || typeof namespace !== 'string') {
4,910✔
77
      throw new TypeError('Invalid argument.');
176✔
78
    }
79

80
    this._init(nodeName, namespace, options, context, args, useGlobalArguments);
4,734✔
81
    debug(
4,662✔
82
      'Finish initializing node, name = %s and namespace = %s.',
83
      nodeName,
84
      namespace
85
    );
86
  }
87

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

101
    this._context = context;
4,662✔
102
    this.context.onNodeCreated(this);
4,662✔
103

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

123
    this._parameterEventPublisher = this.createPublisher(
4,662✔
124
      PARAMETER_EVENT_MSG_TYPE,
125
      PARAMETER_EVENT_TOPIC
126
    );
127

128
    // initialize _parameterOverrides from parameters defined on the commandline
129
    this._parameterOverrides = this._getNativeParameterOverrides();
4,662✔
130

131
    // override cli parameterOverrides with those specified in options
132
    if (options.parameterOverrides.length > 0) {
4,662✔
133
      for (const parameter of options.parameterOverrides) {
64✔
134
        if ((!parameter) instanceof Parameter) {
94!
135
          throw new TypeError(
×
136
            'Parameter-override must be an instance of Parameter.'
137
          );
138
        }
139
        this._parameterOverrides.set(parameter.name, parameter);
94✔
140
      }
141
    }
142

143
    // initialize _parameters from parameterOverrides
144
    if (options.automaticallyDeclareParametersFromOverrides) {
4,662✔
145
      for (const parameter of this._parameterOverrides.values()) {
40✔
146
        parameter.validate();
70✔
147
        const descriptor = ParameterDescriptor.fromParameter(parameter);
70✔
148
        this._parameters.set(parameter.name, parameter);
70✔
149
        this._parameterDescriptors.set(parameter.name, descriptor);
70✔
150
      }
151
    }
152

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

160
    if (options.startParameterServices) {
4,662✔
161
      this._parameterService = new ParameterService(this);
4,614✔
162
      this._parameterService.start();
4,614✔
163
    }
164

165
    if (
4,662✔
166
      DistroUtils.getDistroId() >= DistroUtils.getDistroId('jazzy') &&
8,184✔
167
      options.startTypeDescriptionService
168
    ) {
169
      this._typeDescriptionService = new TypeDescriptionService(this);
3,522✔
170
      this._typeDescriptionService.start();
3,522✔
171
    }
172
  }
173

174
  execute(handles) {
175
    let timersReady = this._timers.filter((timer) =>
38,140✔
176
      handles.includes(timer.handle)
32,807✔
177
    );
178
    let guardsReady = this._guards.filter((guard) =>
38,140✔
179
      handles.includes(guard.handle)
24✔
180
    );
181
    let subscriptionsReady = this._subscriptions.filter((subscription) =>
38,140✔
182
      handles.includes(subscription.handle)
3,641✔
183
    );
184
    let clientsReady = this._clients.filter((client) =>
38,140✔
185
      handles.includes(client.handle)
741✔
186
    );
187
    let servicesReady = this._services.filter((service) =>
38,140✔
188
      handles.includes(service.handle)
106,636✔
189
    );
190
    let actionClientsReady = this._actionClients.filter((actionClient) =>
38,140✔
191
      handles.includes(actionClient.handle)
1,247✔
192
    );
193
    let actionServersReady = this._actionServers.filter((actionServer) =>
38,140✔
194
      handles.includes(actionServer.handle)
1,247✔
195
    );
196
    let eventsReady = this._events.filter((event) =>
38,140✔
197
      handles.includes(event.handle)
24✔
198
    );
199

200
    timersReady.forEach((timer) => {
38,140✔
201
      if (timer.isReady()) {
32,721✔
202
        rclnodejs.callTimer(timer.handle);
30,803✔
203
        timer.callback();
30,803✔
204
      }
205
    });
206

207
    eventsReady.forEach((event) => {
38,140✔
208
      event.takeData();
24✔
209
    });
210

211
    for (const subscription of subscriptionsReady) {
38,140✔
212
      if (subscription.isDestroyed()) continue;
3,353✔
213
      if (subscription.isRaw) {
3,272✔
214
        let rawMessage = rclnodejs.rclTakeRaw(subscription.handle);
8✔
215
        if (rawMessage) {
8!
216
          subscription.processResponse(rawMessage);
8✔
217
        }
218
        continue;
8✔
219
      }
220

221
      this._runWithMessageType(
3,264✔
222
        subscription.typeClass,
223
        (message, deserialize) => {
224
          let success = rclnodejs.rclTake(subscription.handle, message);
3,264✔
225
          if (success) {
3,264✔
226
            subscription.processResponse(deserialize());
2,823✔
227
          }
228
        }
229
      );
230
    }
231

232
    for (const guard of guardsReady) {
38,140✔
233
      if (guard.isDestroyed()) continue;
24!
234

235
      guard.callback();
24✔
236
    }
237

238
    for (const client of clientsReady) {
38,140✔
239
      if (client.isDestroyed()) continue;
379!
240
      this._runWithMessageType(
379✔
241
        client.typeClass.Response,
242
        (message, deserialize) => {
243
          let sequenceNumber = rclnodejs.rclTakeResponse(
379✔
244
            client.handle,
245
            message
246
          );
247
          if (sequenceNumber !== undefined) {
379✔
248
            client.processResponse(sequenceNumber, deserialize());
232✔
249
          }
250
        }
251
      );
252
    }
253

254
    for (const service of servicesReady) {
38,140✔
255
      if (service.isDestroyed()) continue;
485!
256
      this._runWithMessageType(
485✔
257
        service.typeClass.Request,
258
        (message, deserialize) => {
259
          let header = rclnodejs.rclTakeRequest(
485✔
260
            service.handle,
261
            this.handle,
262
            message
263
          );
264
          if (header) {
485✔
265
            service.processRequest(header, deserialize());
252✔
266
          }
267
        }
268
      );
269
    }
270

271
    for (const actionClient of actionClientsReady) {
38,140✔
272
      if (actionClient.isDestroyed()) continue;
589!
273

274
      const properties = actionClient.handle.properties;
589✔
275

276
      if (properties.isGoalResponseReady) {
589✔
277
        this._runWithMessageType(
280✔
278
          actionClient.typeClass.impl.SendGoalService.Response,
279
          (message, deserialize) => {
280
            let sequence = rclnodejs.actionTakeGoalResponse(
280✔
281
              actionClient.handle,
282
              message
283
            );
284
            if (sequence != undefined) {
280✔
285
              actionClient.processGoalResponse(sequence, deserialize());
240✔
286
            }
287
          }
288
        );
289
      }
290

291
      if (properties.isCancelResponseReady) {
589✔
292
        this._runWithMessageType(
47✔
293
          actionClient.typeClass.impl.CancelGoal.Response,
294
          (message, deserialize) => {
295
            let sequence = rclnodejs.actionTakeCancelResponse(
47✔
296
              actionClient.handle,
297
              message
298
            );
299
            if (sequence != undefined) {
47✔
300
              actionClient.processCancelResponse(sequence, deserialize());
32✔
301
            }
302
          }
303
        );
304
      }
305

306
      if (properties.isResultResponseReady) {
589✔
307
        this._runWithMessageType(
125✔
308
          actionClient.typeClass.impl.GetResultService.Response,
309
          (message, deserialize) => {
310
            let sequence = rclnodejs.actionTakeResultResponse(
125✔
311
              actionClient.handle,
312
              message
313
            );
314
            if (sequence != undefined) {
125✔
315
              actionClient.processResultResponse(sequence, deserialize());
120✔
316
            }
317
          }
318
        );
319
      }
320

321
      if (properties.isFeedbackReady) {
589✔
322
        this._runWithMessageType(
62✔
323
          actionClient.typeClass.impl.FeedbackMessage,
324
          (message, deserialize) => {
325
            let success = rclnodejs.actionTakeFeedback(
62✔
326
              actionClient.handle,
327
              message
328
            );
329
            if (success) {
62✔
330
              actionClient.processFeedbackMessage(deserialize());
40✔
331
            }
332
          }
333
        );
334
      }
335

336
      if (properties.isStatusReady) {
589✔
337
        this._runWithMessageType(
314✔
338
          actionClient.typeClass.impl.GoalStatusArray,
339
          (message, deserialize) => {
340
            let success = rclnodejs.actionTakeStatus(
314✔
341
              actionClient.handle,
342
              message
343
            );
344
            if (success) {
314✔
345
              actionClient.processStatusMessage(deserialize());
268✔
346
            }
347
          }
348
        );
349
      }
350
    }
351

352
    for (const actionServer of actionServersReady) {
38,140✔
353
      if (actionServer.isDestroyed()) continue;
733!
354

355
      const properties = actionServer.handle.properties;
733✔
356

357
      if (properties.isGoalRequestReady) {
733✔
358
        this._runWithMessageType(
254✔
359
          actionServer.typeClass.impl.SendGoalService.Request,
360
          (message, deserialize) => {
361
            const result = rclnodejs.actionTakeGoalRequest(
254✔
362
              actionServer.handle,
363
              message
364
            );
365
            if (result) {
254✔
366
              actionServer.processGoalRequest(result, deserialize());
240✔
367
            }
368
          }
369
        );
370
      }
371

372
      if (properties.isCancelRequestReady) {
733✔
373
        this._runWithMessageType(
52✔
374
          actionServer.typeClass.impl.CancelGoal.Request,
375
          (message, deserialize) => {
376
            const result = rclnodejs.actionTakeCancelRequest(
52✔
377
              actionServer.handle,
378
              message
379
            );
380
            if (result) {
52✔
381
              actionServer.processCancelRequest(result, deserialize());
32✔
382
            }
383
          }
384
        );
385
      }
386

387
      if (properties.isResultRequestReady) {
733✔
388
        this._runWithMessageType(
183✔
389
          actionServer.typeClass.impl.GetResultService.Request,
390
          (message, deserialize) => {
391
            const result = rclnodejs.actionTakeResultRequest(
183✔
392
              actionServer.handle,
393
              message
394
            );
395
            if (result) {
183✔
396
              actionServer.processResultRequest(result, deserialize());
120✔
397
            }
398
          }
399
        );
400
      }
401

402
      if (properties.isGoalExpired) {
733✔
403
        let GoalInfoArray = ActionInterfaces.GoalInfo.ArrayType;
32✔
404
        let message = new GoalInfoArray(actionServer._goalHandles.size);
32✔
405
        let count = rclnodejs.actionExpireGoals(
32✔
406
          actionServer.handle,
407
          actionServer._goalHandles.size,
408
          message._refArray.buffer
409
        );
410
        if (count > 0) {
32✔
411
          actionServer.processGoalExpired(message, count);
24✔
412
        }
413
        GoalInfoArray.freeArray(message);
32✔
414
      }
415
    }
416

417
    // At this point it is safe to clear the cache of any
418
    // destroyed entity references
419
    Entity._gcHandles();
38,140✔
420
  }
421

422
  /**
423
   * Determine if this node is spinning.
424
   * @returns {boolean} - true when spinning; otherwise returns false.
425
   */
426
  get spinning() {
427
    return this._spinning;
32,352✔
428
  }
429

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

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

454
  /**
455
   * Terminate spinning - no further events will be received.
456
   * @returns {undefined}
457
   */
458
  stop() {
459
    super.stop();
3,426✔
460
    this._spinning = false;
3,426✔
461
  }
462

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

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

488
  _removeEntityFromArray(entity, array) {
489
    let index = array.indexOf(entity);
1,057✔
490
    if (index > -1) {
1,057✔
491
      array.splice(index, 1);
1,041✔
492
    }
493
  }
494

495
  _destroyEntity(entity, array, syncHandles = true) {
1,067✔
496
    if (entity['isDestroyed'] && entity.isDestroyed()) return;
1,121✔
497

498
    this._removeEntityFromArray(entity, array);
1,057✔
499
    if (syncHandles) {
1,057✔
500
      this.syncHandles();
1,019✔
501
    }
502

503
    if (entity['_destroy']) {
1,057✔
504
      entity._destroy();
1,009✔
505
    } else {
506
      // guards and timers
507
      entity.handle.release();
48✔
508
    }
509
  }
510

511
  _validateOptions(options) {
512
    if (
43,329✔
513
      options !== undefined &&
43,713✔
514
      (options === null || typeof options !== 'object')
515
    ) {
516
      throw new TypeError('Invalid argument of options');
160✔
517
    }
518

519
    if (options === undefined) {
43,169✔
520
      return Node.getDefaultOptions();
43,057✔
521
    }
522

523
    if (options.enableTypedArray === undefined) {
112✔
524
      options = Object.assign(options, { enableTypedArray: true });
16✔
525
    }
526

527
    if (options.qos === undefined) {
112✔
528
      options = Object.assign(options, { qos: QoS.profileDefault });
32✔
529
    }
530

531
    if (options.isRaw === undefined) {
112✔
532
      options = Object.assign(options, { isRaw: false });
32✔
533
    }
534

535
    return options;
112✔
536
  }
537

538
  /**
539
   * Create a Timer.
540
   * @param {bigint} period - The number representing period in nanoseconds.
541
   * @param {function} callback - The callback to be called when timeout.
542
   * @param {Clock} [clock] - The clock which the timer gets time from.
543
   * @return {Timer} - An instance of Timer.
544
   */
545
  createTimer(period, callback, clock = null) {
462✔
546
    if (arguments.length === 3 && !(arguments[2] instanceof Clock)) {
462!
547
      clock = null;
×
548
    } else if (arguments.length === 4) {
462!
549
      clock = arguments[3];
×
550
    }
551

552
    if (typeof period !== 'bigint' || typeof callback !== 'function') {
462✔
553
      throw new TypeError('Invalid argument');
16✔
554
    }
555

556
    const timerClock = clock || this._clock;
446✔
557
    let timerHandle = rclnodejs.createTimer(
446✔
558
      timerClock.handle,
559
      this.context.handle,
560
      period
561
    );
562
    let timer = new Timer(timerHandle, period, callback);
446✔
563
    debug('Finish creating timer, period = %d.', period);
446✔
564
    this._timers.push(timer);
446✔
565
    this.syncHandles();
446✔
566

567
    return timer;
446✔
568
  }
569

570
  /**
571
   * Create a Rate.
572
   *
573
   * @param {number} hz - The frequency of the rate timer; default is 1 hz.
574
   * @returns {Promise<Rate>} - Promise resolving to new instance of Rate.
575
   */
576
  async createRate(hz = 1) {
32✔
577
    if (typeof hz !== 'number') {
72!
578
      throw new TypeError('Invalid argument');
×
579
    }
580

581
    const MAX_RATE_HZ_IN_MILLISECOND = 1000.0;
72✔
582
    if (hz <= 0.0 || hz > MAX_RATE_HZ_IN_MILLISECOND) {
72✔
583
      throw new RangeError(
16✔
584
        `Hz must be between 0.0 and ${MAX_RATE_HZ_IN_MILLISECOND}`
585
      );
586
    }
587

588
    // lazy initialize rateTimerServer
589
    if (!this._rateTimerServer) {
56✔
590
      this._rateTimerServer = new Rates.RateTimerServer(this);
40✔
591
      await this._rateTimerServer.init();
40✔
592
    }
593

594
    const period = Math.round(1000 / hz);
56✔
595
    const timer = this._rateTimerServer.createTimer(BigInt(period) * 1000000n);
56✔
596
    const rate = new Rates.Rate(hz, timer);
56✔
597

598
    return rate;
56✔
599
  }
600

601
  /**
602
   * Create a Publisher.
603
   * @param {function|string|object} typeClass - The ROS message class,
604
        OR a string representing the message class, e.g. 'std_msgs/msg/String',
605
        OR an object representing the message class, e.g. {package: 'std_msgs', type: 'msg', name: 'String'}
606
   * @param {string} topic - The name of the topic.
607
   * @param {object} options - The options argument used to parameterize the publisher.
608
   * @param {boolean} options.enableTypedArray - The topic will use TypedArray if necessary, default: true.
609
   * @param {QoS} options.qos - ROS Middleware "quality of service" settings for the publisher, default: QoS.profileDefault.
610
   * @param {PublisherEventCallbacks} eventCallbacks - The event callbacks for the publisher.
611
   * @return {Publisher} - An instance of Publisher.
612
   */
613
  createPublisher(typeClass, topic, options, eventCallbacks) {
614
    return this._createPublisher(
7,534✔
615
      typeClass,
616
      topic,
617
      options,
618
      Publisher,
619
      eventCallbacks
620
    );
621
  }
622

623
  _createPublisher(typeClass, topic, options, publisherClass, eventCallbacks) {
624
    if (typeof typeClass === 'string' || typeof typeClass === 'object') {
7,558✔
625
      typeClass = loader.loadInterface(typeClass);
7,366✔
626
    }
627
    options = this._validateOptions(options);
7,502✔
628

629
    if (
7,502✔
630
      typeof typeClass !== 'function' ||
22,294✔
631
      typeof topic !== 'string' ||
632
      (eventCallbacks && !(eventCallbacks instanceof PublisherEventCallbacks))
633
    ) {
634
      throw new TypeError('Invalid argument');
160✔
635
    }
636

637
    let publisher = publisherClass.createPublisher(
7,342✔
638
      this,
639
      typeClass,
640
      topic,
641
      options,
642
      eventCallbacks
643
    );
644
    debug('Finish creating publisher, topic = %s.', topic);
7,262✔
645
    this._publishers.push(publisher);
7,262✔
646
    return publisher;
7,262✔
647
  }
648

649
  /**
650
   * This callback is called when a message is published
651
   * @callback SubscriptionCallback
652
   * @param {Object} message - The message published
653
   * @see [Node.createSubscription]{@link Node#createSubscription}
654
   * @see [Node.createPublisher]{@link Node#createPublisher}
655
   * @see {@link Publisher}
656
   * @see {@link Subscription}
657
   */
658

659
  /**
660
   * Create a Subscription with optional content-filtering.
661
   * @param {function|string|object} typeClass - The ROS message class,
662
        OR a string representing the message class, e.g. 'std_msgs/msg/String',
663
        OR an object representing the message class, e.g. {package: 'std_msgs', type: 'msg', name: 'String'}
664
   * @param {string} topic - The name of the topic.
665
   * @param {object} options - The options argument used to parameterize the subscription.
666
   * @param {boolean} options.enableTypedArray - The topic will use TypedArray if necessary, default: true.
667
   * @param {QoS} options.qos - ROS Middleware "quality of service" settings for the subscription, default: QoS.profileDefault.
668
   * @param {boolean} options.isRaw - The topic is serialized when true, default: false.
669
   * @param {object} [options.contentFilter=undefined] - The content-filter, default: undefined.
670
   *  Confirm that your RMW supports content-filtered topics before use. 
671
   * @param {string} options.contentFilter.expression - Specifies the criteria to select the data samples of
672
   *  interest. It is similar to the WHERE part of an SQL clause.
673
   * @param {string[]} [options.contentFilter.parameters=undefined] - Array of strings that give values to
674
   *  the ‘parameters’ (i.e., "%n" tokens) in the filter_expression. The number of supplied parameters must
675
   *  fit with the requested values in the filter_expression (i.e., the number of %n tokens). default: undefined.
676
   * @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.
677
   * @param {SubscriptionEventCallbacks} eventCallbacks - The event callbacks for the subscription.
678
   * @return {Subscription} - An instance of Subscription.
679
   * @throws {ERROR} - May throw an RMW error if content-filter is malformed. 
680
   * @see {@link SubscriptionCallback}
681
   * @see {@link https://www.omg.org/spec/DDS/1.4/PDF|Content-filter details at DDS 1.4 specification, Annex B}
682
   */
683
  createSubscription(typeClass, topic, options, callback, eventCallbacks) {
684
    if (typeof typeClass === 'string' || typeof typeClass === 'object') {
3,017✔
685
      typeClass = loader.loadInterface(typeClass);
2,945✔
686
    }
687

688
    if (typeof options === 'function') {
2,961✔
689
      callback = options;
2,683✔
690
      options = undefined;
2,683✔
691
    }
692
    options = this._validateOptions(options);
2,961✔
693

694
    if (
2,881✔
695
      typeof typeClass !== 'function' ||
11,356✔
696
      typeof topic !== 'string' ||
697
      typeof callback !== 'function' ||
698
      (eventCallbacks &&
699
        !(eventCallbacks instanceof SubscriptionEventCallbacks))
700
    ) {
701
      throw new TypeError('Invalid argument');
80✔
702
    }
703

704
    let subscription = Subscription.createSubscription(
2,801✔
705
      this,
706
      typeClass,
707
      topic,
708
      options,
709
      callback,
710
      eventCallbacks
711
    );
712
    debug('Finish creating subscription, topic = %s.', topic);
2,713✔
713
    this._subscriptions.push(subscription);
2,713✔
714
    this.syncHandles();
2,713✔
715

716
    return subscription;
2,713✔
717
  }
718

719
  /**
720
   * Create a Client.
721
   * @param {function|string|object} typeClass - The ROS message class,
722
        OR a string representing the message class, e.g. 'std_msgs/msg/String',
723
        OR an object representing the message class, e.g. {package: 'std_msgs', type: 'msg', name: 'String'}
724
   * @param {string} serviceName - The service name to request.
725
   * @param {object} options - The options argument used to parameterize the client.
726
   * @param {boolean} options.enableTypedArray - The response will use TypedArray if necessary, default: true.
727
   * @param {QoS} options.qos - ROS Middleware "quality of service" settings for the client, default: QoS.profileDefault.
728
   * @return {Client} - An instance of Client.
729
   */
730
  createClient(typeClass, serviceName, options) {
731
    if (typeof typeClass === 'string' || typeof typeClass === 'object') {
652✔
732
      typeClass = loader.loadInterface(typeClass);
572✔
733
    }
734
    options = this._validateOptions(options);
596✔
735

736
    if (typeof typeClass !== 'function' || typeof serviceName !== 'string') {
596✔
737
      throw new TypeError('Invalid argument');
160✔
738
    }
739

740
    let client = Client.createClient(
436✔
741
      this.handle,
742
      serviceName,
743
      typeClass,
744
      options
745
    );
746
    debug('Finish creating client, service = %s.', serviceName);
356✔
747
    this._clients.push(client);
356✔
748
    this.syncHandles();
356✔
749

750
    return client;
356✔
751
  }
752

753
  /**
754
   * This callback is called when a request is sent to service
755
   * @callback RequestCallback
756
   * @param {Object} request - The request sent to the service
757
   * @param {Response} response - The response to client.
758
        Use [response.send()]{@link Response#send} to send response object to client
759
   * @return {undefined}
760
   * @see [Node.createService]{@link Node#createService}
761
   * @see [Client.sendRequest]{@link Client#sendRequest}
762
   * @see {@link Client}
763
   * @see {@link Service}
764
   * @see {@link Response#send}
765
   */
766

767
  /**
768
   * Create a Service.
769
   * @param {function|string|object} typeClass - The ROS message class,
770
        OR a string representing the message class, e.g. 'std_msgs/msg/String',
771
        OR an object representing the message class, e.g. {package: 'std_msgs', type: 'msg', name: 'String'}
772
   * @param {string} serviceName - The service name to offer.
773
   * @param {object} options - The options argument used to parameterize the service.
774
   * @param {boolean} options.enableTypedArray - The request will use TypedArray if necessary, default: true.
775
   * @param {QoS} options.qos - ROS Middleware "quality of service" settings for the service, default: QoS.profileDefault.
776
   * @param {RequestCallback} callback - The callback to be called when receiving request.
777
   * @return {Service} - An instance of Service.
778
   * @see {@link RequestCallback}
779
   */
780
  createService(typeClass, serviceName, options, callback) {
781
    if (typeof typeClass === 'string' || typeof typeClass === 'object') {
28,228✔
782
      typeClass = loader.loadInterface(typeClass);
28,148✔
783
    }
784

785
    if (typeof options === 'function') {
28,172✔
786
      callback = options;
27,996✔
787
      options = undefined;
27,996✔
788
    }
789
    options = this._validateOptions(options);
28,172✔
790

791
    if (
28,092✔
792
      typeof typeClass !== 'function' ||
84,164✔
793
      typeof serviceName !== 'string' ||
794
      typeof callback !== 'function'
795
    ) {
796
      throw new TypeError('Invalid argument');
80✔
797
    }
798

799
    let service = Service.createService(
28,012✔
800
      this.handle,
801
      serviceName,
802
      typeClass,
803
      options,
804
      callback
805
    );
806
    debug('Finish creating service, service = %s.', serviceName);
27,932✔
807
    this._services.push(service);
27,932✔
808
    this.syncHandles();
27,932✔
809

810
    return service;
27,932✔
811
  }
812

813
  /**
814
   * Create a guard condition.
815
   * @param {Function} callback - The callback to be called when the guard condition is triggered.
816
   * @return {GuardCondition} - An instance of GuardCondition.
817
   */
818
  createGuardCondition(callback) {
819
    if (typeof callback !== 'function') {
24!
820
      throw new TypeError('Invalid argument');
×
821
    }
822

823
    let guard = GuardCondition.createGuardCondition(callback, this.context);
24✔
824
    debug('Finish creating guard condition');
24✔
825
    this._guards.push(guard);
24✔
826
    this.syncHandles();
24✔
827

828
    return guard;
24✔
829
  }
830

831
  /**
832
   * Destroy all resource allocated by this node, including
833
   * <code>Timer</code>s/<code>Publisher</code>s/<code>Subscription</code>s
834
   * /<code>Client</code>s/<code>Service</code>s
835
   * @return {undefined}
836
   */
837
  destroy() {
838
    if (this.spinning) {
4,838✔
839
      this.stop();
3,406✔
840
    }
841

842
    // Action servers/clients require manual destruction due to circular reference with goal handles.
843
    this._actionClients.forEach((actionClient) => actionClient.destroy());
4,838✔
844
    this._actionServers.forEach((actionServer) => actionServer.destroy());
4,838✔
845

846
    this.context.onNodeDestroyed(this);
4,838✔
847

848
    this.handle.release();
4,838✔
849
    this._clock = null;
4,838✔
850
    this._timers = [];
4,838✔
851
    this._publishers = [];
4,838✔
852
    this._subscriptions = [];
4,838✔
853
    this._clients = [];
4,838✔
854
    this._services = [];
4,838✔
855
    this._guards = [];
4,838✔
856
    this._actionClients = [];
4,838✔
857
    this._actionServers = [];
4,838✔
858

859
    if (this._rateTimerServer) {
4,838✔
860
      this._rateTimerServer.shutdown();
40✔
861
      this._rateTimerServer = null;
40✔
862
    }
863
  }
864

865
  /**
866
   * Destroy a Publisher.
867
   * @param {Publisher} publisher - The Publisher to be destroyed.
868
   * @return {undefined}
869
   */
870
  destroyPublisher(publisher) {
871
    if (!(publisher instanceof Publisher)) {
70✔
872
      throw new TypeError('Invalid argument');
16✔
873
    }
874
    if (publisher.events) {
54!
875
      publisher.events.forEach((event) => {
×
876
        this._destroyEntity(event, this._events);
×
877
      });
878
      publisher.events = [];
×
879
    }
880
    this._destroyEntity(publisher, this._publishers, false);
54✔
881
  }
882

883
  /**
884
   * Destroy a Subscription.
885
   * @param {Subscription} subscription - The Subscription to be destroyed.
886
   * @return {undefined}
887
   */
888
  destroySubscription(subscription) {
889
    if (!(subscription instanceof Subscription)) {
309✔
890
      throw new TypeError('Invalid argument');
16✔
891
    }
892
    if (subscription.events) {
293✔
893
      subscription.events.forEach((event) => {
6✔
894
        this._destroyEntity(event, this._events);
6✔
895
      });
896
      subscription.events = [];
6✔
897
    }
898

899
    this._destroyEntity(subscription, this._subscriptions);
293✔
900
  }
901

902
  /**
903
   * Destroy a Client.
904
   * @param {Client} client - The Client to be destroyed.
905
   * @return {undefined}
906
   */
907
  destroyClient(client) {
908
    if (!(client instanceof Client)) {
64✔
909
      throw new TypeError('Invalid argument');
16✔
910
    }
911
    this._destroyEntity(client, this._clients);
48✔
912
  }
913

914
  /**
915
   * Destroy a Service.
916
   * @param {Service} service - The Service to be destroyed.
917
   * @return {undefined}
918
   */
919
  destroyService(service) {
920
    if (!(service instanceof Service)) {
64✔
921
      throw new TypeError('Invalid argument');
16✔
922
    }
923
    this._destroyEntity(service, this._services);
48✔
924
  }
925

926
  /**
927
   * Destroy a Timer.
928
   * @param {Timer} timer - The Timer to be destroyed.
929
   * @return {undefined}
930
   */
931
  destroyTimer(timer) {
932
    if (!(timer instanceof Timer)) {
64✔
933
      throw new TypeError('Invalid argument');
16✔
934
    }
935
    this._destroyEntity(timer, this._timers);
48✔
936
  }
937

938
  /**
939
   * Destroy a guard condition.
940
   * @param {GuardCondition} guard - The guard condition to be destroyed.
941
   * @return {undefined}
942
   */
943
  destroyGuardCondition(guard) {
944
    if (!(guard instanceof GuardCondition)) {
24!
945
      throw new TypeError('Invalid argument');
×
946
    }
947
    this._destroyEntity(guard, this._guards);
24✔
948
  }
949

950
  /**
951
   * Get the name of the node.
952
   * @return {string}
953
   */
954
  name() {
955
    return rclnodejs.getNodeName(this.handle);
21,510✔
956
  }
957

958
  /**
959
   * Get the namespace of the node.
960
   * @return {string}
961
   */
962
  namespace() {
963
    return rclnodejs.getNamespace(this.handle);
17,210✔
964
  }
965

966
  /**
967
   * Get the context in which this node was created.
968
   * @return {Context}
969
   */
970
  get context() {
971
    return this._context;
42,114✔
972
  }
973

974
  /**
975
   * Get the nodes logger.
976
   * @returns {Logger} - The logger for the node.
977
   */
978
  getLogger() {
979
    return this._logger;
1,128✔
980
  }
981

982
  /**
983
   * Get the clock used by the node.
984
   * @returns {Clock} - The nodes clock.
985
   */
986
  getClock() {
987
    return this._clock;
656✔
988
  }
989

990
  /**
991
   * Get the current time using the node's clock.
992
   * @returns {Time} - The current time.
993
   */
994
  now() {
995
    return this.getClock().now();
16✔
996
  }
997

998
  /**
999
   * Get the list of published topics discovered by the provided node for the remote node name.
1000
   * @param {string} nodeName - The name of the node.
1001
   * @param {string} namespace - The name of the namespace.
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
  getPublisherNamesAndTypesByNode(nodeName, namespace, noDemangle = false) {
×
1006
    return rclnodejs.getPublisherNamesAndTypesByNode(
×
1007
      this.handle,
1008
      nodeName,
1009
      namespace,
1010
      noDemangle
1011
    );
1012
  }
1013

1014
  /**
1015
   * Get the list of published topics discovered by the provided node for the remote node name.
1016
   * @param {string} nodeName - The name of the node.
1017
   * @param {string} namespace - The name of the namespace.
1018
   * @param {boolean} noDemangle - If true topic names and types returned will not be demangled, default: false.
1019
   * @return {Array<{name: string, types: Array<string>}>} - An array of the names and types.
1020
   */
1021
  getSubscriptionNamesAndTypesByNode(nodeName, namespace, noDemangle = false) {
×
1022
    return rclnodejs.getSubscriptionNamesAndTypesByNode(
×
1023
      this.handle,
1024
      nodeName,
1025
      namespace,
1026
      noDemangle
1027
    );
1028
  }
1029

1030
  /**
1031
   * Get service names and types for which a remote node has servers.
1032
   * @param {string} nodeName - The name of the node.
1033
   * @param {string} namespace - The name of the namespace.
1034
   * @return {Array<{name: string, types: Array<string>}>} - An array of the names and types.
1035
   */
1036
  getServiceNamesAndTypesByNode(nodeName, namespace) {
1037
    return rclnodejs.getServiceNamesAndTypesByNode(
×
1038
      this.handle,
1039
      nodeName,
1040
      namespace
1041
    );
1042
  }
1043

1044
  /**
1045
   * Get service names and types for which a remote node has clients.
1046
   * @param {string} nodeName - The name of the node.
1047
   * @param {string} namespace - The name of the namespace.
1048
   * @return {Array<{name: string, types: Array<string>}>} - An array of the names and types.
1049
   */
1050
  getClientNamesAndTypesByNode(nodeName, namespace) {
1051
    return rclnodejs.getClientNamesAndTypesByNode(
×
1052
      this.handle,
1053
      nodeName,
1054
      namespace
1055
    );
1056
  }
1057

1058
  /**
1059
   * Get the list of topics discovered by the provided node.
1060
   * @param {boolean} noDemangle - If true topic names and types returned will not be demangled, default: false.
1061
   * @return {Array<{name: string, types: Array<string>}>} - An array of the names and types.
1062
   */
1063
  getTopicNamesAndTypes(noDemangle = false) {
×
1064
    return rclnodejs.getTopicNamesAndTypes(this.handle, noDemangle);
×
1065
  }
1066

1067
  /**
1068
   * Get the list of services discovered by the provided node.
1069
   * @return {Array<{name: string, types: Array<string>}>} - An array of the names and types.
1070
   */
1071
  getServiceNamesAndTypes() {
1072
    return rclnodejs.getServiceNamesAndTypes(this.handle);
16✔
1073
  }
1074

1075
  /**
1076
   * Return a list of publishers on a given topic.
1077
   *
1078
   * The returned parameter is a list of TopicEndpointInfo objects, where each will contain
1079
   * the node name, node namespace, topic type, topic endpoint's GID, and its QoS profile.
1080
   *
1081
   * When the `no_mangle` parameter is `true`, the provided `topic` should be a valid
1082
   * topic name for the middleware (useful when combining ROS with native middleware (e.g. DDS)
1083
   * apps).  When the `no_mangle` parameter is `false`, the provided `topic` should
1084
   * follow ROS topic name conventions.
1085
   *
1086
   * `topic` may be a relative, private, or fully qualified topic name.
1087
   *  A relative or private topic will be expanded using this node's namespace and name.
1088
   *  The queried `topic` is not remapped.
1089
   *
1090
   * @param {string} topic - The topic on which to find the publishers.
1091
   * @param {boolean} [noDemangle=false] - If `true`, `topic` needs to be a valid middleware topic
1092
   *                               name, otherwise it should be a valid ROS topic name. Defaults to `false`.
1093
   * @returns {Array} - list of publishers
1094
   */
1095
  getPublishersInfoByTopic(topic, noDemangle = false) {
×
1096
    return rclnodejs.getPublishersInfoByTopic(
22✔
1097
      this.handle,
1098
      this._getValidatedTopic(topic, noDemangle),
1099
      noDemangle
1100
    );
1101
  }
1102

1103
  /**
1104
   * Return a list of subscriptions on a given topic.
1105
   *
1106
   * The returned parameter is a list of TopicEndpointInfo objects, where each will contain
1107
   * the node name, node namespace, topic type, topic endpoint's GID, and its QoS profile.
1108
   *
1109
   * When the `no_mangle` parameter is `true`, the provided `topic` should be a valid
1110
   * topic name for the middleware (useful when combining ROS with native middleware (e.g. DDS)
1111
   * apps).  When the `no_mangle` parameter is `false`, the provided `topic` should
1112
   * follow ROS topic name conventions.
1113
   *
1114
   * `topic` may be a relative, private, or fully qualified topic name.
1115
   *  A relative or private topic will be expanded using this node's namespace and name.
1116
   *  The queried `topic` is not remapped.
1117
   *
1118
   * @param {string} topic - The topic on which to find the subscriptions.
1119
   * @param {boolean} [noDemangle=false] -  If `true`, `topic` needs to be a valid middleware topic
1120
                                    name, otherwise it should be a valid ROS topic name. Defaults to `false`.
1121
   * @returns {Array} - list of subscriptions
1122
   */
1123
  getSubscriptionsInfoByTopic(topic, noDemangle = false) {
×
1124
    return rclnodejs.getSubscriptionsInfoByTopic(
16✔
1125
      this.handle,
1126
      this._getValidatedTopic(topic, noDemangle),
1127
      noDemangle
1128
    );
1129
  }
1130

1131
  /**
1132
   * Get the list of nodes discovered by the provided node.
1133
   * @return {Array<string>} - An array of the names.
1134
   */
1135
  getNodeNames() {
1136
    return this.getNodeNamesAndNamespaces().map((item) => item.name);
320✔
1137
  }
1138

1139
  /**
1140
   * Get the list of nodes and their namespaces discovered by the provided node.
1141
   * @return {Array<{name: string, namespace: string}>} An array of the names and namespaces.
1142
   */
1143
  getNodeNamesAndNamespaces() {
1144
    return rclnodejs.getNodeNames(this.handle, /*getEnclaves=*/ false);
136✔
1145
  }
1146

1147
  /**
1148
   * Get the list of nodes and their namespaces with enclaves discovered by the provided node.
1149
   * @return {Array<{name: string, namespace: string, enclave: string}>} An array of the names, namespaces and enclaves.
1150
   */
1151
  getNodeNamesAndNamespacesWithEnclaves() {
1152
    return rclnodejs.getNodeNames(this.handle, /*getEnclaves=*/ true);
8✔
1153
  }
1154

1155
  /**
1156
   * Return the number of publishers on a given topic.
1157
   * @param {string} topic - The name of the topic.
1158
   * @returns {number} - Number of publishers on the given topic.
1159
   */
1160
  countPublishers(topic) {
1161
    let expandedTopic = rclnodejs.expandTopicName(
48✔
1162
      topic,
1163
      this.name(),
1164
      this.namespace()
1165
    );
1166
    rclnodejs.validateTopicName(expandedTopic);
48✔
1167

1168
    return rclnodejs.countPublishers(this.handle, expandedTopic);
48✔
1169
  }
1170

1171
  /**
1172
   * Return the number of subscribers on a given topic.
1173
   * @param {string} topic - The name of the topic.
1174
   * @returns {number} - Number of subscribers on the given topic.
1175
   */
1176
  countSubscribers(topic) {
1177
    let expandedTopic = rclnodejs.expandTopicName(
48✔
1178
      topic,
1179
      this.name(),
1180
      this.namespace()
1181
    );
1182
    rclnodejs.validateTopicName(expandedTopic);
48✔
1183

1184
    return rclnodejs.countSubscribers(this.handle, expandedTopic);
48✔
1185
  }
1186

1187
  /**
1188
   * Get the number of clients on a given service name.
1189
   * @param {string} serviceName - the service name
1190
   * @returns {Number}
1191
   */
1192
  countClients(serviceName) {
1193
    if (DistroUtils.getDistroId() <= DistroUtils.getDistroId('humble')) {
12!
1194
      console.warn('countClients is not supported by this version of ROS 2');
×
1195
      return null;
×
1196
    }
1197
    return rclnodejs.countClients(this.handle, serviceName);
12✔
1198
  }
1199

1200
  /**
1201
   * Get the number of services on a given service name.
1202
   * @param {string} serviceName - the service name
1203
   * @returns {Number}
1204
   */
1205
  countServices(serviceName) {
1206
    if (DistroUtils.getDistroId() <= DistroUtils.getDistroId('humble')) {
6!
1207
      console.warn('countServices is not supported by this version of ROS 2');
×
1208
      return null;
×
1209
    }
1210
    return rclnodejs.countServices(this.handle, serviceName);
6✔
1211
  }
1212

1213
  /**
1214
   * Get the list of parameter-overrides found on the commandline and
1215
   * in the NodeOptions.parameter_overrides property.
1216
   *
1217
   * @return {Array<Parameter>} - An array of Parameters.
1218
   */
1219
  getParameterOverrides() {
1220
    return Array.from(this._parameterOverrides.values());
64✔
1221
  }
1222

1223
  /**
1224
   * Declare a parameter.
1225
   *
1226
   * Internally, register a parameter and it's descriptor.
1227
   * If a parameter-override exists, it's value will replace that of the parameter
1228
   * unless ignoreOverride is true.
1229
   * If the descriptor is undefined, then a ParameterDescriptor will be inferred
1230
   * from the parameter's state.
1231
   *
1232
   * If a parameter by the same name has already been declared then an Error is thrown.
1233
   * A parameter must be undeclared before attempting to redeclare it.
1234
   *
1235
   * @param {Parameter} parameter - Parameter to declare.
1236
   * @param {ParameterDescriptor} [descriptor] - Optional descriptor for parameter.
1237
   * @param {boolean} [ignoreOveride] - When true disregard any parameter-override that may be present.
1238
   * @return {Parameter} - The newly declared parameter.
1239
   */
1240
  declareParameter(parameter, descriptor, ignoreOveride = false) {
8,426✔
1241
    const parameters = this.declareParameters(
8,434✔
1242
      [parameter],
1243
      descriptor ? [descriptor] : [],
8,434✔
1244
      ignoreOveride
1245
    );
1246
    return parameters.length == 1 ? parameters[0] : null;
8,434!
1247
  }
1248

1249
  /**
1250
   * Declare a list of parameters.
1251
   *
1252
   * Internally register parameters with their corresponding descriptor one by one
1253
   * in the order they are provided. This is an atomic operation. If an error
1254
   * occurs the process halts and no further parameters are declared.
1255
   * Parameters that have already been processed are undeclared.
1256
   *
1257
   * While descriptors is an optional parameter, when provided there must be
1258
   * a descriptor for each parameter; otherwise an Error is thrown.
1259
   * If descriptors is not provided then a descriptor will be inferred
1260
   * from each parameter's state.
1261
   *
1262
   * When a parameter-override is available, the parameter's value
1263
   * will be replaced with that of the parameter-override unless ignoreOverrides
1264
   * is true.
1265
   *
1266
   * If a parameter by the same name has already been declared then an Error is thrown.
1267
   * A parameter must be undeclared before attempting to redeclare it.
1268
   *
1269
   * Prior to declaring the parameters each SetParameterEventCallback registered
1270
   * using setOnParameterEventCallback() is called in succession with the parameters
1271
   * list. Any SetParameterEventCallback that retuns does not return a successful
1272
   * result will cause the entire operation to terminate with no changes to the
1273
   * parameters. When all SetParameterEventCallbacks return successful then the
1274
   * list of parameters is updated.
1275
   *
1276
   * @param {Parameter[]} parameters - The parameters to declare.
1277
   * @param {ParameterDescriptor[]} [descriptors] - Optional descriptors,
1278
   *    a 1-1 correspondence with parameters.
1279
   * @param {boolean} ignoreOverrides - When true, parameter-overrides are
1280
   *    not considered, i.e.,ignored.
1281
   * @return {Parameter[]} - The declared parameters.
1282
   */
1283
  declareParameters(parameters, descriptors = [], ignoreOverrides = false) {
×
1284
    if (!Array.isArray(parameters)) {
8,434!
1285
      throw new TypeError('Invalid parameter: expected array of Parameter');
×
1286
    }
1287
    if (!Array.isArray(descriptors)) {
8,434!
1288
      throw new TypeError(
×
1289
        'Invalid parameters: expected array of ParameterDescriptor'
1290
      );
1291
    }
1292
    if (descriptors.length > 0 && parameters.length !== descriptors.length) {
8,434!
1293
      throw new TypeError(
×
1294
        'Each parameter must have a cooresponding ParameterDescriptor'
1295
      );
1296
    }
1297

1298
    const declaredDescriptors = [];
8,434✔
1299
    const declaredParameters = [];
8,434✔
1300
    const declaredParameterCollisions = [];
8,434✔
1301
    for (let i = 0; i < parameters.length; i++) {
8,434✔
1302
      let parameter =
1303
        !ignoreOverrides && this._parameterOverrides.has(parameters[i].name)
8,434✔
1304
          ? this._parameterOverrides.get(parameters[i].name)
1305
          : parameters[i];
1306

1307
      // stop processing parameters that have already been declared
1308
      if (this._parameters.has(parameter.name)) {
8,434!
1309
        declaredParameterCollisions.push(parameter);
×
1310
        continue;
×
1311
      }
1312

1313
      // create descriptor for parameter if not provided
1314
      let descriptor =
1315
        descriptors.length > 0
8,434✔
1316
          ? descriptors[i]
1317
          : ParameterDescriptor.fromParameter(parameter);
1318

1319
      descriptor.validate();
8,434✔
1320

1321
      declaredDescriptors.push(descriptor);
8,434✔
1322
      declaredParameters.push(parameter);
8,434✔
1323
    }
1324

1325
    if (declaredParameterCollisions.length > 0) {
8,434!
1326
      const errorMsg =
1327
        declaredParameterCollisions.length == 1
×
1328
          ? `Parameter(${declaredParameterCollisions[0]}) already declared.`
1329
          : `Multiple parameters already declared, e.g., Parameter(${declaredParameterCollisions[0]}).`;
1330
      throw new Error(errorMsg);
×
1331
    }
1332

1333
    // register descriptor
1334
    for (const descriptor of declaredDescriptors) {
8,434✔
1335
      this._parameterDescriptors.set(descriptor.name, descriptor);
8,434✔
1336
    }
1337

1338
    const result = this._setParametersAtomically(declaredParameters, true);
8,434✔
1339
    if (!result.successful) {
8,434!
1340
      // unregister descriptors
1341
      for (const descriptor of declaredDescriptors) {
×
1342
        this._parameterDescriptors.delete(descriptor.name);
×
1343
      }
1344

1345
      throw new Error(result.reason);
×
1346
    }
1347

1348
    return this.getParameters(declaredParameters.map((param) => param.name));
8,434✔
1349
  }
1350

1351
  /**
1352
   * Undeclare a parameter.
1353
   *
1354
   * Readonly parameters can not be undeclared or updated.
1355
   * @param {string} name - Name of parameter to undeclare.
1356
   * @return {undefined} -
1357
   */
1358
  undeclareParameter(name) {
1359
    if (!this.hasParameter(name)) return;
8!
1360

1361
    const descriptor = this.getParameterDescriptor(name);
8✔
1362
    if (descriptor.readOnly) {
8!
1363
      throw new Error(
×
1364
        `${name} parameter is read-only and can not be undeclared`
1365
      );
1366
    }
1367

1368
    this._parameters.delete(name);
8✔
1369
    this._parameterDescriptors.delete(name);
8✔
1370
  }
1371

1372
  /**
1373
   * Determine if a parameter has been declared.
1374
   * @param {string} name - name of parameter
1375
   * @returns {boolean} - Return true if parameter is declared; false otherwise.
1376
   */
1377
  hasParameter(name) {
1378
    return this._parameters.has(name);
24,992✔
1379
  }
1380

1381
  /**
1382
   * Get a declared parameter by name.
1383
   *
1384
   * If unable to locate a declared parameter then a
1385
   * parameter with type == PARAMETER_NOT_SET is returned.
1386
   *
1387
   * @param {string} name - The name of the parameter.
1388
   * @return {Parameter} - The parameter.
1389
   */
1390
  getParameter(name) {
1391
    return this.getParameters([name])[0];
8,280✔
1392
  }
1393

1394
  /**
1395
   * Get a list of parameters.
1396
   *
1397
   * Find and return the declared parameters.
1398
   * If no names are provided return all declared parameters.
1399
   *
1400
   * If unable to locate a declared parameter then a
1401
   * parameter with type == PARAMETER_NOT_SET is returned in
1402
   * it's place.
1403
   *
1404
   * @param {string[]} [names] - The names of the declared parameters
1405
   *    to find or null indicating to return all declared parameters.
1406
   * @return {Parameter[]} - The parameters.
1407
   */
1408
  getParameters(names = []) {
70✔
1409
    let params = [];
16,798✔
1410

1411
    if (names.length == 0) {
16,798✔
1412
      // get all parameters
1413
      params = [...this._parameters.values()];
70✔
1414
      return params;
70✔
1415
    }
1416

1417
    for (const name of names) {
16,728✔
1418
      const param = this.hasParameter(name)
16,736!
1419
        ? this._parameters.get(name)
1420
        : new Parameter(name, ParameterType.PARAMETER_NOT_SET);
1421

1422
      params.push(param);
16,736✔
1423
    }
1424

1425
    return params;
16,728✔
1426
  }
1427

1428
  /**
1429
   * Get the types of given parameters.
1430
   *
1431
   * Return the types of given parameters.
1432
   *
1433
   * @param {string[]} [names] - The names of the declared parameters.
1434
   * @return {Uint8Array} - The types.
1435
   */
1436
  getParameterTypes(names = []) {
×
1437
    let types = [];
8✔
1438

1439
    for (const name of names) {
8✔
1440
      const descriptor = this._parameterDescriptors.get(name);
24✔
1441
      if (descriptor) {
24!
1442
        types.push(descriptor.type);
24✔
1443
      }
1444
    }
1445
    return types;
8✔
1446
  }
1447

1448
  /**
1449
   * Get the names of all declared parameters.
1450
   *
1451
   * @return {Array<string>} - The declared parameter names or empty array if
1452
   *    no parameters have been declared.
1453
   */
1454
  getParameterNames() {
1455
    return this.getParameters().map((param) => param.name);
150✔
1456
  }
1457

1458
  /**
1459
   * Determine if a parameter descriptor exists.
1460
   *
1461
   * @param {string} name - The name of a descriptor to for.
1462
   * @return {boolean} - true if a descriptor has been declared; otherwise false.
1463
   */
1464
  hasParameterDescriptor(name) {
1465
    return !!this.getParameterDescriptor(name);
8,482✔
1466
  }
1467

1468
  /**
1469
   * Get a declared parameter descriptor by name.
1470
   *
1471
   * If unable to locate a declared parameter descriptor then a
1472
   * descriptor with type == PARAMETER_NOT_SET is returned.
1473
   *
1474
   * @param {string} name - The name of the parameter descriptor to find.
1475
   * @return {ParameterDescriptor} - The parameter descriptor.
1476
   */
1477
  getParameterDescriptor(name) {
1478
    return this.getParameterDescriptors([name])[0];
16,972✔
1479
  }
1480

1481
  /**
1482
   * Find a list of declared ParameterDescriptors.
1483
   *
1484
   * If no names are provided return all declared descriptors.
1485
   *
1486
   * If unable to locate a declared descriptor then a
1487
   * descriptor with type == PARAMETER_NOT_SET is returned in
1488
   * it's place.
1489
   *
1490
   * @param {string[]} [names] - The names of the declared parameter
1491
   *    descriptors to find or null indicating to return all declared descriptors.
1492
   * @return {ParameterDescriptor[]} - The parameter descriptors.
1493
   */
1494
  getParameterDescriptors(names = []) {
×
1495
    let descriptors = [];
16,980✔
1496

1497
    if (names.length == 0) {
16,980!
1498
      // get all parameters
1499
      descriptors = [...this._parameterDescriptors.values()];
×
1500
      return descriptors;
×
1501
    }
1502

1503
    for (const name of names) {
16,980✔
1504
      let descriptor = this._parameterDescriptors.get(name);
16,988✔
1505
      if (!descriptor) {
16,988!
1506
        descriptor = new ParameterDescriptor(
×
1507
          name,
1508
          ParameterType.PARAMETER_NOT_SET
1509
        );
1510
      }
1511
      descriptors.push(descriptor);
16,988✔
1512
    }
1513

1514
    return descriptors;
16,980✔
1515
  }
1516

1517
  /**
1518
   * Replace a declared parameter.
1519
   *
1520
   * The parameter being replaced must be a declared parameter who's descriptor
1521
   * is not readOnly; otherwise an Error is thrown.
1522
   *
1523
   * @param {Parameter} parameter - The new parameter.
1524
   * @return {rclnodejs.rcl_interfaces.msg.SetParameterResult} - The result of the operation.
1525
   */
1526
  setParameter(parameter) {
1527
    const results = this.setParameters([parameter]);
32✔
1528
    return results[0];
32✔
1529
  }
1530

1531
  /**
1532
   * Replace a list of declared parameters.
1533
   *
1534
   * Declared parameters are replaced in the order they are provided and
1535
   * a ParameterEvent is published for each individual parameter change.
1536
   *
1537
   * Prior to setting the parameters each SetParameterEventCallback registered
1538
   * using setOnParameterEventCallback() is called in succession with the parameters
1539
   * list. Any SetParameterEventCallback that retuns does not return a successful
1540
   * result will cause the entire operation to terminate with no changes to the
1541
   * parameters. When all SetParameterEventCallbacks return successful then the
1542
   * list of parameters is updated.
1543
   *
1544
   * If an error occurs, the process is stopped and returned. Parameters
1545
   * set before an error remain unchanged.
1546
   *
1547
   * @param {Parameter[]} parameters - The parameters to set.
1548
   * @return {rclnodejs.rcl_interfaces.msg.SetParameterResult[]} - A list of SetParameterResult, one for each parameter that was set.
1549
   */
1550
  setParameters(parameters = []) {
×
1551
    return parameters.map((parameter) =>
40✔
1552
      this.setParametersAtomically([parameter])
40✔
1553
    );
1554
  }
1555

1556
  /**
1557
   * Repalce a list of declared parameters atomically.
1558
   *
1559
   * Declared parameters are replaced in the order they are provided.
1560
   * A single ParameterEvent is published collectively for all changed
1561
   * parameters.
1562
   *
1563
   * Prior to setting the parameters each SetParameterEventCallback registered
1564
   * using setOnParameterEventCallback() is called in succession with the parameters
1565
   * list. Any SetParameterEventCallback that retuns does not return a successful
1566
   * result will cause the entire operation to terminate with no changes to the
1567
   * parameters. When all SetParameterEventCallbacks return successful then the
1568
   * list of parameters is updated.d
1569
   *
1570
   * If an error occurs, the process stops immediately. All parameters updated to
1571
   * the point of the error are reverted to their previous state.
1572
   *
1573
   * @param {Parameter[]} parameters - The parameters to set.
1574
   * @return {rclnodejs.rcl_interfaces.msg.SetParameterResult} - describes the result of setting 1 or more parameters.
1575
   */
1576
  setParametersAtomically(parameters = []) {
×
1577
    return this._setParametersAtomically(parameters);
48✔
1578
  }
1579

1580
  /**
1581
   * Internal method for updating parameters atomically.
1582
   *
1583
   * Prior to setting the parameters each SetParameterEventCallback registered
1584
   * using setOnParameterEventCallback() is called in succession with the parameters
1585
   * list. Any SetParameterEventCallback that retuns does not return a successful
1586
   * result will cause the entire operation to terminate with no changes to the
1587
   * parameters. When all SetParameterEventCallbacks return successful then the
1588
   * list of parameters is updated.
1589
   *
1590
   * @param {Paramerter[]} parameters - The parameters to update.
1591
   * @param {boolean} declareParameterMode - When true parameters are being declared;
1592
   *    otherwise they are being changed.
1593
   * @return {SetParameterResult} - A single collective result.
1594
   */
1595
  _setParametersAtomically(parameters = [], declareParameterMode = false) {
48!
1596
    let result = this._validateParameters(parameters, declareParameterMode);
8,482✔
1597
    if (!result.successful) {
8,482!
1598
      return result;
×
1599
    }
1600

1601
    // give all SetParametersCallbacks a chance to veto this change
1602
    for (const callback of this._setParametersCallbacks) {
8,482✔
1603
      result = callback(parameters);
3,868✔
1604
      if (!result.successful) {
3,868✔
1605
        // a callback has vetoed a parameter change
1606
        return result;
8✔
1607
      }
1608
    }
1609

1610
    // collectively track updates to parameters for use
1611
    // when publishing a ParameterEvent
1612
    const newParameters = [];
8,474✔
1613
    const changedParameters = [];
8,474✔
1614
    const deletedParameters = [];
8,474✔
1615

1616
    for (const parameter of parameters) {
8,474✔
1617
      if (parameter.type == ParameterType.PARAMETER_NOT_SET) {
8,474✔
1618
        this.undeclareParameter(parameter.name);
8✔
1619
        deletedParameters.push(parameter);
8✔
1620
      } else {
1621
        this._parameters.set(parameter.name, parameter);
8,466✔
1622
        if (declareParameterMode) {
8,466✔
1623
          newParameters.push(parameter);
8,434✔
1624
        } else {
1625
          changedParameters.push(parameter);
32✔
1626
        }
1627
      }
1628
    }
1629

1630
    // create ParameterEvent
1631
    const parameterEvent = new (loader.loadInterface(
8,474✔
1632
      PARAMETER_EVENT_MSG_TYPE
1633
    ))();
1634

1635
    const { seconds, nanoseconds } = this._clock.now().secondsAndNanoseconds;
8,474✔
1636
    parameterEvent.stamp = {
8,474✔
1637
      sec: Number(seconds),
1638
      nanosec: Number(nanoseconds),
1639
    };
1640

1641
    parameterEvent.node =
8,474✔
1642
      this.namespace() === '/'
8,474✔
1643
        ? this.namespace() + this.name()
1644
        : this.namespace() + '/' + this.name();
1645

1646
    if (newParameters.length > 0) {
8,474✔
1647
      parameterEvent['new_parameters'] = newParameters.map((parameter) =>
8,434✔
1648
        parameter.toParameterMessage()
8,434✔
1649
      );
1650
    }
1651
    if (changedParameters.length > 0) {
8,474✔
1652
      parameterEvent['changed_parameters'] = changedParameters.map(
32✔
1653
        (parameter) => parameter.toParameterMessage()
32✔
1654
      );
1655
    }
1656
    if (deletedParameters.length > 0) {
8,474✔
1657
      parameterEvent['deleted_parameters'] = deletedParameters.map(
8✔
1658
        (parameter) => parameter.toParameterMessage()
8✔
1659
      );
1660
    }
1661

1662
    // Publish ParameterEvent.
1663
    this._parameterEventPublisher.publish(parameterEvent);
8,474✔
1664

1665
    return {
8,474✔
1666
      successful: true,
1667
      reason: '',
1668
    };
1669
  }
1670

1671
  /**
1672
   * This callback is called when declaring a parameter or setting a parameter.
1673
   * The callback is provided a list of parameters and returns a SetParameterResult
1674
   * to indicate approval or veto of the operation.
1675
   *
1676
   * @callback SetParametersCallback
1677
   * @param {Parameter[]} parameters - The message published
1678
   * @returns {rcl_interfaces.msg.SetParameterResult} -
1679
   *
1680
   * @see [Node.addOnSetParametersCallback]{@link Node#addOnSetParametersCallback}
1681
   * @see [Node.removeOnSetParametersCallback]{@link Node#removeOnSetParametersCallback}
1682
   */
1683

1684
  /**
1685
   * Add a callback to the front of the list of callbacks invoked for parameter declaration
1686
   * and setting. No checks are made for duplicate callbacks.
1687
   *
1688
   * @param {SetParametersCallback} callback - The callback to add.
1689
   * @returns {undefined}
1690
   */
1691
  addOnSetParametersCallback(callback) {
1692
    this._setParametersCallbacks.unshift(callback);
4,726✔
1693
  }
1694

1695
  /**
1696
   * Remove a callback from the list of SetParametersCallbacks.
1697
   * If the callback is not found the process is a nop.
1698
   *
1699
   * @param {SetParametersCallback} callback - The callback to be removed
1700
   * @returns {undefined}
1701
   */
1702
  removeOnSetParametersCallback(callback) {
1703
    const idx = this._setParametersCallbacks.indexOf(callback);
16✔
1704
    if (idx > -1) {
16!
1705
      this._setParametersCallbacks.splice(idx, 1);
16✔
1706
    }
1707
  }
1708

1709
  /**
1710
   * Get the fully qualified name of the node.
1711
   *
1712
   * @returns {string} - String containing the fully qualified name of the node.
1713
   */
1714
  getFullyQualifiedName() {
1715
    return rclnodejs.getFullyQualifiedName(this.handle);
8✔
1716
  }
1717

1718
  /**
1719
   * Get the RMW implementation identifier
1720
   * @returns {string} - The RMW implementation identifier.
1721
   */
1722
  getRMWImplementationIdentifier() {
1723
    return rclnodejs.getRMWImplementationIdentifier();
8✔
1724
  }
1725

1726
  /**
1727
   * Return a topic name expanded and remapped.
1728
   * @param {string} topicName - Topic name to be expanded and remapped.
1729
   * @param {boolean} [onlyExpand=false] - If `true`, remapping rules won't be applied.
1730
   * @returns {string} - A fully qualified topic name.
1731
   */
1732
  resolveTopicName(topicName, onlyExpand = false) {
16✔
1733
    if (typeof topicName !== 'string') {
24!
1734
      throw new TypeError('Invalid argument: expected string');
×
1735
    }
1736
    return rclnodejs.resolveName(
24✔
1737
      this.handle,
1738
      topicName,
1739
      onlyExpand,
1740
      /*isService=*/ false
1741
    );
1742
  }
1743

1744
  /**
1745
   * Return a service name expanded and remapped.
1746
   * @param {string} service - Service name to be expanded and remapped.
1747
   * @param {boolean} [onlyExpand=false] - If `true`, remapping rules won't be applied.
1748
   * @returns {string} - A fully qualified service name.
1749
   */
1750
  resolveServiceName(service, onlyExpand = false) {
16✔
1751
    if (typeof service !== 'string') {
24!
1752
      throw new TypeError('Invalid argument: expected string');
×
1753
    }
1754
    return rclnodejs.resolveName(
24✔
1755
      this.handle,
1756
      service,
1757
      onlyExpand,
1758
      /*isService=*/ true
1759
    );
1760
  }
1761

1762
  // returns on 1st error or result {successful, reason}
1763
  _validateParameters(parameters = [], declareParameterMode = false) {
×
1764
    for (const parameter of parameters) {
8,482✔
1765
      // detect invalid parameter
1766
      try {
8,482✔
1767
        parameter.validate();
8,482✔
1768
      } catch {
1769
        return {
×
1770
          successful: false,
1771
          reason: `Invalid ${parameter.name}`,
1772
        };
1773
      }
1774

1775
      // detect undeclared parameter
1776
      if (!this.hasParameterDescriptor(parameter.name)) {
8,482!
1777
        return {
×
1778
          successful: false,
1779
          reason: `Parameter ${parameter.name} has not been declared`,
1780
        };
1781
      }
1782

1783
      // detect readonly parameter that can not be updated
1784
      const descriptor = this.getParameterDescriptor(parameter.name);
8,482✔
1785
      if (!declareParameterMode && descriptor.readOnly) {
8,482!
1786
        return {
×
1787
          successful: false,
1788
          reason: `Parameter ${parameter.name} is readonly`,
1789
        };
1790
      }
1791

1792
      // validate parameter against descriptor if not an undeclare action
1793
      if (parameter.type != ParameterType.PARAMETER_NOT_SET) {
8,482✔
1794
        try {
8,474✔
1795
          descriptor.validateParameter(parameter);
8,474✔
1796
        } catch {
1797
          return {
×
1798
            successful: false,
1799
            reason: `Parameter ${parameter.name} does not  readonly`,
1800
          };
1801
        }
1802
      }
1803
    }
1804

1805
    return {
8,482✔
1806
      successful: true,
1807
      reason: null,
1808
    };
1809
  }
1810

1811
  // Get a Map(nodeName->Parameter[]) of CLI parameter args that
1812
  // apply to 'this' node, .e.g., -p mynode:foo:=bar -p hello:=world
1813
  _getNativeParameterOverrides() {
1814
    const overrides = new Map();
4,662✔
1815

1816
    // Get native parameters from rcl context->global_arguments.
1817
    // rclnodejs returns an array of objects, 1 for each node e.g., -p my_node:foo:=bar,
1818
    // and a node named '/**' for global parameter rules,
1819
    // i.e., does not include a node identifier, e.g., -p color:=red
1820
    // {
1821
    //   name: string // node name
1822
    //   parameters[] = {
1823
    //     name: string
1824
    //     type: uint
1825
    //     value: object
1826
    // }
1827
    const cliParamOverrideData = rclnodejs.getParameterOverrides(
4,662✔
1828
      this.context.handle
1829
    );
1830

1831
    // convert native CLI parameterOverrides to Map<nodeName,Array<ParameterOverride>>
1832
    const cliParamOverrides = new Map();
4,662✔
1833
    if (cliParamOverrideData) {
4,662✔
1834
      for (let nodeParamData of cliParamOverrideData) {
64✔
1835
        const nodeName = nodeParamData.name;
96✔
1836
        const nodeParamOverrides = [];
96✔
1837
        for (let paramData of nodeParamData.parameters) {
96✔
1838
          const paramOverride = new Parameter(
136✔
1839
            paramData.name,
1840
            paramData.type,
1841
            paramData.value
1842
          );
1843
          nodeParamOverrides.push(paramOverride);
136✔
1844
        }
1845
        cliParamOverrides.set(nodeName, nodeParamOverrides);
96✔
1846
      }
1847
    }
1848

1849
    // collect global CLI global parameters, name == /**
1850
    let paramOverrides = cliParamOverrides.get('/**'); // array of ParameterOverrides
4,662✔
1851
    if (paramOverrides) {
4,662✔
1852
      for (const parameter of paramOverrides) {
40✔
1853
        overrides.set(parameter.name, parameter);
48✔
1854
      }
1855
    }
1856

1857
    // merge CLI node parameterOverrides with global parameterOverrides, replace existing
1858
    paramOverrides = cliParamOverrides.get(this.name()); // array of ParameterOverrides
4,662✔
1859
    if (paramOverrides) {
4,662✔
1860
      for (const parameter of paramOverrides) {
40✔
1861
        overrides.set(parameter.name, parameter);
56✔
1862
      }
1863
    }
1864

1865
    return overrides;
4,662✔
1866
  }
1867

1868
  /**
1869
   * Invokes the callback with a raw message of the given type. After the callback completes
1870
   * the message will be destroyed.
1871
   * @param {function} Type - Message type to create.
1872
   * @param {function} callback - Callback to invoke. First parameter will be the raw message,
1873
   * and the second is a function to retrieve the deserialized message.
1874
   * @returns {undefined}
1875
   */
1876
  _runWithMessageType(Type, callback) {
1877
    let message = new Type();
5,445✔
1878

1879
    callback(message.toRawROS(), () => {
5,445✔
1880
      let result = new Type();
4,399✔
1881
      result.deserialize(message.refObject);
4,399✔
1882

1883
      return result;
4,399✔
1884
    });
1885

1886
    Type.destroyRawROS(message);
5,445✔
1887
  }
1888

1889
  _addActionClient(actionClient) {
1890
    this._actionClients.push(actionClient);
324✔
1891
    this.syncHandles();
324✔
1892
  }
1893

1894
  _addActionServer(actionServer) {
1895
    this._actionServers.push(actionServer);
324✔
1896
    this.syncHandles();
324✔
1897
  }
1898

1899
  _getValidatedTopic(topicName, noDemangle) {
1900
    if (noDemangle) {
38!
1901
      return topicName;
×
1902
    }
1903
    const fqTopicName = rclnodejs.expandTopicName(
38✔
1904
      topicName,
1905
      this.name(),
1906
      this.namespace()
1907
    );
1908
    validateFullTopicName(fqTopicName);
38✔
1909
    return rclnodejs.remapTopicName(this.handle, fqTopicName);
38✔
1910
  }
1911
}
1912

1913
/**
1914
 * Create an Options instance initialized with default values.
1915
 * @returns {Options} - The new initialized instance.
1916
 * @static
1917
 * @example
1918
 * {
1919
 *   enableTypedArray: true,
1920
 *   isRaw: false,
1921
 *   qos: QoS.profileDefault,
1922
 *   contentFilter: undefined,
1923
 * }
1924
 */
1925
Node.getDefaultOptions = function () {
208✔
1926
  return {
43,137✔
1927
    enableTypedArray: true,
1928
    isRaw: false,
1929
    qos: QoS.profileDefault,
1930
    contentFilter: undefined,
1931
  };
1932
};
1933

1934
module.exports = Node;
208✔
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