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

RobotWebTools / rclnodejs / #958

06 Mar 2025 04:45AM UTC coverage: 85.075% (-5.4%) from 90.471%
#958

push

web-flow
Update .npmignore (#1062)

This patch updates `.npmignore` to exclude files not needed in the npm package.

Fix: #1061

706 of 920 branches covered (76.74%)

Branch coverage included in aggregate %.

1728 of 1941 relevant lines covered (89.03%)

343.48 hits per line

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

83.93
/lib/lifecycle.js
1
// Copyright (c) 2020 Wayne Parrott. 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');
26✔
18
const LifecyclePublisher = require('./lifecycle_publisher.js');
26✔
19
const loader = require('./interface_loader.js');
26✔
20
const Context = require('./context.js');
26✔
21
const Node = require('./node.js');
26✔
22
const NodeOptions = require('./node_options.js');
26✔
23
const Service = require('./service.js');
26✔
24

25
const SHUTDOWN_TRANSITION_LABEL =
26
  rclnodejs.getLifecycleShutdownTransitionLabel();
26✔
27

28
// An instance of State message constructor used for accessing State
29
// state machine constants. This interface is lazy initialized at runtime.
30
let StateInterface;
31

32
// An instance of Transition message constructor used for accessing Transition
33
// state machine constants. This interface is lazy initialized at runtime.
34
let TransitionInterface;
35

36
function getStateInterface() {
37
  if (!StateInterface) {
8✔
38
    StateInterface = loader.loadInterface('lifecycle_msgs/msg/State');
1✔
39
  }
40
  return StateInterface;
8✔
41
}
42

43
function getTransitionInterface() {
44
  if (!TransitionInterface) {
51✔
45
    TransitionInterface = loader.loadInterface('lifecycle_msgs/msg/Transition');
1✔
46
  }
47
  return TransitionInterface;
51✔
48
}
49

50
/**
51
 * @typedef SerializedState
52
 * @type {object}
53
 * @property {number} id - code identifying the type of this state.
54
 * @property {string} label - readable name of this state.
55
 */
56

57
/**
58
 * The state of the lifecycle state model.
59
 */
60
class State {
61
  /**
62
   * Create a state.
63
   * @param {number} id - The id value.
64
   * @param {string} label - The label value.
65
   */
66
  constructor(id, label) {
67
    this.id = id;
92✔
68
    this.label = label;
92✔
69
  }
70

71
  /**
72
   * Create a State from a SerializedState
73
   * @param {SerializedState} aSerializedState - The state object.
74
   * @returns {State} The State converted from a SerializdState
75
   * @private
76
   */
77
  static fromSerializedState(aSerializedState) {
78
    return new State(aSerializedState.id, aSerializedState.label);
55✔
79
  }
80
}
81

82
/**
83
 * The intermediate state of the lifecycle state model during a state
84
 * transition.
85
 */
86
class Transition extends State {}
87

88
/**
89
 * Describes a state transition.
90
 */
91
class TransitionDescription {
92
  /**
93
   * Create a transition description.
94
   *
95
   * @param {Transition} transition - The transition
96
   * @param {State} startState - The initial
97
   * @param {State} goalState - The target state of a transition activity
98
   */
99
  constructor(transition, startState, goalState) {
100
    this.transition = transition;
5✔
101
    this.startState = startState;
5✔
102
    this.goalState = goalState;
5✔
103
  }
104
}
105

106
/**
107
 * The values returned by TransitionCallback.
108
 * @readonly
109
 * @enum {number}
110
 */
111
const CallbackReturnCode = {
26✔
112
  get SUCCESS() {
113
    return getTransitionInterface().TRANSITION_CALLBACK_SUCCESS;
23✔
114
  },
115
  get FAILURE() {
116
    return getTransitionInterface().TRANSITION_CALLBACK_FAILURE;
×
117
  },
118
  get ERROR() {
119
    return getTransitionInterface().TRANSITION_CALLBACK_ERROR;
16✔
120
  },
121
};
122
Object.freeze(CallbackReturnCode);
26✔
123

124
/**
125
 * A ValueHolder for a CallbackReturnCode.
126
 */
127
class CallbackReturnValue {
128
  /**
129
   * Creates a new instance.
130
   *
131
   * @param {number} [value=CallbackReturnCode.SUCCESS] - The value property.
132
   */
133
  constructor(value = CallbackReturnCode.SUCCESS) {
1✔
134
    this._value = value;
1✔
135
    this._errorMsg = null;
1✔
136
  }
137

138
  /**
139
   * Access the callbackReturnCode.
140
   * @returns {number} The CallbackReturnCode.
141
   */
142
  get value() {
143
    return this._value;
1✔
144
  }
145

146
  set value(value) {
147
    this._value = value;
1✔
148
  }
149

150
  /**
151
   * Access an optional error message when value is not SUCCESS.
152
   */
153
  get errorMsg() {
154
    return this._errorMsg;
×
155
  }
156

157
  /**
158
   * Assign the error message.
159
   * @param {string} msg - The error message.
160
   * @returns {unknown} void.
161
   */
162
  set errorMsg(msg) {
163
    this._errorMsg = msg;
×
164
  }
165

166
  /**
167
   * Overrides Object.valueOf() to return the 'value' property.
168
   * @returns {number} The property value.
169
   */
170
  valueOf() {
171
    return this.value;
×
172
  }
173

174
  /**
175
   * A predicate to test if the value is SUCCESS.
176
   * @returns {boolean} Return true if the value is SUCCESS; otherwise return false.
177
   */
178
  isSuccess() {
179
    return this.value === CallbackReturnCode.SUCCESS;
1✔
180
  }
181

182
  /**
183
   * A predicate to test if the value is FAILURE.
184
   * @returns {boolean} Return true if the value is FAILURE; otherwise return false.
185
   */
186
  isFailure() {
187
    return this.value === CallbackReturnCode.FAILURE;
×
188
  }
189

190
  /**
191
   * A predicate to test if the value is ERROR.
192
   * @returns {boolean} Return true if the value is ERROR; otherwise return false.
193
   */
194
  isError() {
195
    return this.value === CallbackReturnCode.ERROR;
×
196
  }
197

198
  /**
199
   * A predicate to test if an error message has been assigned.
200
   * @returns {boolean} Return true if an error message has been assigned; otherwise return false.
201
   */
202
  hasErrorMsg() {
203
    return !this.isSuccess() && this._errorMsg;
×
204
  }
205
}
206

207
/**
208
 * This callback is invoked when LifecycleNode is transitioning to a new state.
209
 * @callback TransitionCallback
210
 * @param {State} previousState - The previous node lifecycle state.
211
 * @return {CallbackReturnCode} - The result of the callback.
212
 *
213
 * @see [LifecycleNode.registerOnConfigure]{@link LifecycleNode#registerOnConfigure}
214
 * @see [LifecycleNode.registerOnCleanup]{@link LifecycleNode#registerOnCleanup}
215
 * @see [LifecycleNode.registerOnActivate]{@link LifecycleNode#registerOnActivate}
216
 * @see [LifecycleNode.registerOnDeactivate]{@link LifecycleNode#registerOnDeactivate}
217
 * @see [LifecycleNode.registerOnShutdown]{@link LifecycleNode#registerOnShutdown}
218
 * @see [LifecycleNode.registerOnError]{@link LifecycleNode#registerOnError}
219
 */
220

221
/**
222
 * A ROS2 managed Node that implements a well-defined life-cycle state model using the
223
 * {@link https://github.com/ros2/rcl/tree/master/rcl_lifecycle|ros2 client library (rcl) lifecyle api}.
224
 * @extends Node
225
 *
226
 * This class implments the ROS2 life-cycle state-machine defined by the
227
 * {@link https://github.com/ros2/rclcpp/tree/master/rclcpp_lifecycle}|ROS2 Managed Nodes Design}
228
 * and parallels the {@link https://github.com/ros2/rclcpp/tree/master/rclcpp_lifecycle|rclcpp lifecycle node }
229
 * implementation.
230
 *
231
 * The design consists of four primary lifecycle states:
232
 *   UNCONFIGURED
233
 *   INACTIVE
234
 *   ACTIVE
235
 *   FINALIZED.
236
 *
237
 * Transitioning between states is accomplished using an action api:
238
 *   configure()
239
 *   activate()
240
 *   deactivate(),
241
 *   cleanup()
242
 *   shutdown()
243
 *
244
 * During a state transition, the state-machine is in one of the
245
 * intermediate transitioning states:
246
 *   CONFIGURING
247
 *   ACTIVATING
248
 *   DEACTIVATING
249
 *   CLEANINGUP
250
 *   SHUTTING_DOWN
251
 *   ERROR_PROCESSING
252
 *
253
 * Messaging:
254
 * State changes are published on the '<node_name>/transition_event' topic.
255
 * Lifecycle service interfaces are also implemented.
256
 *
257
 * You can introduce your own state specific behaviors in the form of a
258
 * {@link TransitionCallback} functions that you register using:
259
 *   registerOnConfigure(cb)
260
 *   registerOnActivate(cb)
261
 *   registerOnDeactivate(cb)
262
 *   registerOnCleanup(cb)
263
 *   registerOnShutdown(cb)
264
 *   registerOnError(cb)
265
 */
266

267
class LifecycleNode extends Node {
268
  /**
269
   * Create a managed Node that implements a well-defined life-cycle state
270
   * model using the {@link https://github.com/ros2/rcl/tree/master/rcl_lifecycle|ros2 client library (rcl) lifecyle api}.
271
   * @param {string} nodeName - The name used to register in ROS.
272
   * @param {string} [namespace=''] - The namespace used in ROS.
273
   * @param {Context} [context=Context.defaultContext()] - The context to create the node in.
274
   * @param {NodeOptions} [options=NodeOptions.defaultOptions] - The options to configure the new node behavior.
275
   * @param {boolean} [enableCommunicationsInterface=true] - Enable lifecycle service interfaces, e.g., GetState.
276
   * @throws {Error} If the given context is not registered.
277
   */
278
  constructor(
279
    nodeName,
280
    namespace = '',
1✔
281
    context = Context.defaultContext(),
1✔
282
    options = NodeOptions.defaultOptions,
1✔
283
    enableCommunicationInterface = true
×
284
  ) {
285
    super(nodeName, namespace, context, options);
18✔
286
    this.init(enableCommunicationInterface);
18✔
287
  }
288

289
  init(enableCommunicationInterface) {
290
    // initialize native handle to rcl_lifecycle_state_machine
291
    this._stateMachineHandle = rclnodejs.createLifecycleStateMachine(
18✔
292
      this.handle,
293
      enableCommunicationInterface
294
    );
295

296
    // initialize Map<transitionId,TransitionCallback>
297
    this._callbackMap = new Map();
18✔
298

299
    if (!enableCommunicationInterface) {
18✔
300
      // do not create lifecycle services
301
      return;
1✔
302
    }
303

304
    // Setup and register the 4 native rcl lifecycle services thar are
305
    // part of _stateMachineHandle.
306
    let srvHandleObj = rclnodejs.getLifecycleSrvNameAndHandle(
17✔
307
      this._stateMachineHandle,
308
      'srv_get_state'
309
    );
310
    let service = new Service(
17✔
311
      this.handle,
312
      srvHandleObj.handle,
313
      srvHandleObj.name,
314
      loader.loadInterface('lifecycle_msgs/srv/GetState'),
315
      this._validateOptions(undefined),
316
      (request, response) => this._onGetState(request, response)
1✔
317
    );
318
    this._services.push(service);
17✔
319

320
    srvHandleObj = rclnodejs.getLifecycleSrvNameAndHandle(
17✔
321
      this._stateMachineHandle,
322
      'srv_get_available_states'
323
    );
324
    service = new Service(
17✔
325
      this.handle,
326
      srvHandleObj.handle,
327
      srvHandleObj.name,
328
      loader.loadInterface('lifecycle_msgs/srv/GetAvailableStates'),
329
      this._validateOptions(undefined),
330
      (request, response) => this._onGetAvailableStates(request, response)
1✔
331
    );
332
    this._services.push(service);
17✔
333

334
    srvHandleObj = rclnodejs.getLifecycleSrvNameAndHandle(
17✔
335
      this._stateMachineHandle,
336
      'srv_get_available_transitions'
337
    );
338
    service = new Service(
17✔
339
      this.handle,
340
      srvHandleObj.handle,
341
      srvHandleObj.name,
342
      loader.loadInterface('lifecycle_msgs/srv/GetAvailableTransitions'),
343
      this._validateOptions(undefined),
344
      (request, response) => this._onGetAvailableTransitions(request, response)
1✔
345
    );
346
    this._services.push(service);
17✔
347

348
    srvHandleObj = rclnodejs.getLifecycleSrvNameAndHandle(
17✔
349
      this._stateMachineHandle,
350
      'srv_change_state'
351
    );
352
    service = new Service(
17✔
353
      this.handle,
354
      srvHandleObj.handle,
355
      srvHandleObj.name,
356
      loader.loadInterface('lifecycle_msgs/srv/ChangeState'),
357
      this._validateOptions(undefined),
358
      (request, response) => this._onChangeState(request, response)
1✔
359
    );
360
    this._services.push(service);
17✔
361
    this.syncHandles();
17✔
362
  }
363

364
  /**
365
   * Create a LifecyclePublisher.
366
   * @param {function|string|object} typeClass - The ROS message class,
367
        OR a string representing the message class, e.g. 'std_msgs/msg/String',
368
        OR an object representing the message class, e.g. {package: 'std_msgs', type: 'msg', name: 'String'}
369
   * @param {string} topic - The name of the topic.
370
   * @param {object} options - The options argument used to parameterize the publisher.
371
   * @param {boolean} options.enableTypedArray - The topic will use TypedArray if necessary, default: true.
372
   * @param {QoS} options.qos - ROS Middleware "quality of service" settings for the publisher, default: QoS.profileDefault.
373
   * @return {LifecyclePublisher} - An instance of LifecyclePublisher.
374
   */
375
  createLifecyclePublisher(typeClass, topic, options) {
376
    return this._createPublisher(typeClass, topic, options, LifecyclePublisher);
3✔
377
  }
378

379
  /**
380
   * Access the current lifecycle state.
381
   * @returns {State} The current state.
382
   */
383
  get currentState() {
384
    let currentStateObj = rclnodejs.getCurrentLifecycleState(
24✔
385
      this._stateMachineHandle
386
    );
387
    return State.fromSerializedState(currentStateObj);
24✔
388
  }
389

390
  /**
391
   * Retrieve all registered states of the state-machine.
392
   * @returns {State[]} The states.
393
   */
394
  get availableStates() {
395
    let stateObjs = rclnodejs.getLifecycleStates(this._stateMachineHandle);
2✔
396
    let states = stateObjs.map(
2✔
397
      (stateObj) => new State(stateObj.id, stateObj.label)
22✔
398
    );
399
    return states;
2✔
400
  }
401

402
  /**
403
   * Retrieve all registered transitions of the state-machine.
404
   *
405
   * @returns {TransitionDescription[]} The registered TransitionDescriptions.
406
   */
407
  get transitions() {
408
    let transitionObjs = rclnodejs.getLifecycleTransitions(
×
409
      this._stateMachineHandle
410
    );
411
    let transitions = transitionObjs.map((transitionDescObj) => {
×
412
      let transition = new Transition(
×
413
        transitionDescObj.transition.id,
414
        transitionDescObj.transition.label
415
      );
416
      let startState = new State(
×
417
        transitionDescObj.start_state.id,
418
        transitionDescObj.start_state.label
419
      );
420
      let goalState = new State(
×
421
        transitionDescObj.goal_state.id,
422
        transitionDescObj.goal_state.label
423
      );
424
      return new TransitionDescription(transition, startState, goalState);
×
425
    });
426
    return transitions;
×
427
  }
428

429
  /**
430
   * Retrieve the valid transitions available from the current state of the
431
   * state-machine.
432
   *
433
   * @returns {TransitionDescription[]} The available TransitionDescriptions.
434
   */
435
  get availableTransitions() {
436
    let transitionObjs = rclnodejs.getAvailableLifecycleTransitions(
2✔
437
      this._stateMachineHandle
438
    );
439
    let transitions = transitionObjs.map((transitionDescObj) => {
2✔
440
      let transition = new Transition(
5✔
441
        transitionDescObj.transition.id,
442
        transitionDescObj.transition.label
443
      );
444
      let startState = new State(
5✔
445
        transitionDescObj.start_state.id,
446
        transitionDescObj.start_state.label
447
      );
448
      let goalState = new State(
5✔
449
        transitionDescObj.goal_state.id,
450
        transitionDescObj.goal_state.label
451
      );
452
      return new TransitionDescription(transition, startState, goalState);
5✔
453
    });
454
    return transitions;
2✔
455
  }
456

457
  /**
458
   * Register a callback function to be invoked during the configure() action.
459
   * @param {TransitionCallback} cb - The callback function to invoke.
460
   * @returns {unknown} void.
461
   */
462
  registerOnConfigure(cb) {
463
    this._callbackMap.set(getStateInterface().TRANSITION_STATE_CONFIGURING, cb);
2✔
464
  }
465

466
  /**
467
   * Register a callback function to be invoked during the activate() action.
468
   * @param {TransitionCallback} cb - The callback function to invoke.
469
   * @returns {unknown} void.
470
   */
471
  registerOnActivate(cb) {
472
    this._callbackMap.set(getStateInterface().TRANSITION_STATE_ACTIVATING, cb);
1✔
473
  }
474

475
  /**
476
   * Register a callback function to be invoked during the deactivate() action.
477
   * @param {TransitionCallback} cb - The callback function to invoke.
478
   * @returns {unknown} void.
479
   */
480
  registerOnDeactivate(cb) {
481
    this._callbackMap.set(
1✔
482
      getStateInterface().TRANSITION_STATE_DEACTIVATING,
483
      cb
484
    );
485
  }
486

487
  /**
488
   * Register a callback function to be invoked during the cleanup() action.
489
   * @param {TransitionCallback} cb - The callback function to invoke.
490
   * @returns {unknown} void.
491
   */
492
  registerOnCleanup(cb) {
493
    this._callbackMap.set(getStateInterface().TRANSITION_STATE_CLEANINGUP, cb);
1✔
494
  }
495

496
  /**
497
   * Register a callback function to be invoked during the shutdown() action.
498
   * @param {TransitionCallback} cb - The callback function to invoke.
499
   * @returns {unknown} void
500
   */
501
  registerOnShutdown(cb) {
502
    this._callbackMap.set(
1✔
503
      getStateInterface().TRANSITION_STATE_SHUTTINGDOWN,
504
      cb
505
    );
506
  }
507

508
  /**
509
   * Register a callback function to be invoked when an error occurs during a
510
   * state transition.
511
   * @param {TransitionCallback} cb - The callback function to invoke.
512
   * @returns {unknown} void.
513
   */
514
  registerOnError(cb) {
515
    this._callbackMap.set(
2✔
516
      getStateInterface().TRANSITION_STATE_ERRORPROCESSING,
517
      cb
518
    );
519
  }
520

521
  /**
522
   * Initiate a transition from the UNCONFIGURED state to the INACTIVE state.
523
   * If an onConfigure callback has been registered it will be invoked.
524
   *
525
   * @param {CallbackReturnValue?} callbackReturnValue - value holder for the CallbackReturnCode returned from the callback.
526
   * @returns {State} The new state, should be INACTIVE.
527
   * @throws {Error} If transition is invalid for the current state.
528
   */
529
  configure(callbackReturnValue) {
530
    return this._changeState(
6✔
531
      getTransitionInterface().TRANSITION_CONFIGURE,
532
      callbackReturnValue
533
    );
534
  }
535

536
  /**
537
   * Initiate a transition from the INACTIVE state to the ACTIVE state.
538
   * If an onActivate callback has been registered it will be invoked.
539
   *
540
   * @param {CallbackReturnValue?} callbackReturnValue - value holder for the CallbackReturnCode returned from the callback.
541
   * @returns {State} The new state, should be ACTIVE.
542
   * @throws {Error} If transition is invalid for the current state.
543
   */
544
  activate(callbackReturnValue) {
545
    return this._changeState(
3✔
546
      getTransitionInterface().TRANSITION_ACTIVATE,
547
      callbackReturnValue
548
    );
549
  }
550

551
  /**
552
   * Initiate a transition from the ACTIVE state to the INACTIVE state.
553
   * If an onDeactivate callback has been registered it will be invoked.
554
   *
555
   * @param {CallbackReturnValue?} callbackReturnValue - value holder for the CallbackReturnCode returned from the callback.
556
   * @returns {State} The new state, should be INACTIVE.
557
   * @throws {Error} If transition is invalid for the current state.
558
   */
559
  deactivate(callbackReturnValue) {
560
    return this._changeState(
2✔
561
      getTransitionInterface().TRANSITION_DEACTIVATE,
562
      callbackReturnValue
563
    );
564
  }
565

566
  /**
567
   * Initiate a transition from the INACTIVE state to the UNCONFIGURED state.
568
   * If an onCleanup callback has been registered it will be invoked.
569
   *
570
   * @param {CallbackReturnValue?} callbackReturnValue - value holder for the CallbackReturnCode returned from the callback.
571
   * @returns {State} The new state, should be INACTIVE.
572
   * @throws {Error} If transition is invalid for the current state.
573
   */
574
  cleanup(callbackReturnValue) {
575
    return this._changeState(
2✔
576
      getTransitionInterface().TRANSITION_CLEANUP,
577
      callbackReturnValue
578
    );
579
  }
580

581
  /**
582
   * Initiate a transition to the FINALIZED state from any of the following
583
   * states: UNCONFIGURED, INACTIVE or ACTIVE state. If an onConfigure
584
   * callback has been registered it will be invoked.
585
   *
586
   * @param {CallbackReturnValue?} callbackReturnValue - value holder for the CallbackReturnCode returned from the callback.
587
   * @returns {State} The new state, should be FINALIZED.
588
   * @throws {Error} If transition is invalid for the current state.
589
   */
590
  shutdown(callbackReturnValue) {
591
    return this._changeState(SHUTDOWN_TRANSITION_LABEL, callbackReturnValue);
2✔
592
  }
593

594
  /**
595
   * The GetState service handler.
596
   * @param {Object} request - The GetState service request.
597
   * @param {Object} response - The GetState service response.
598
   * @returns {unknown} void.
599
   * @private
600
   */
601
  _onGetState(request, response) {
602
    let result = response.template;
1✔
603

604
    result.current_state = this.currentState;
1✔
605

606
    response.send(result);
1✔
607
  }
608

609
  /**
610
   * The GetAvailableStates service handler.
611
   * @param {Object} request - The GetAvailableStates service request.
612
   * @param {Object} response - The GetAvailableStates service response.
613
   * @returns {unknown} void.
614
   * @private
615
   */
616
  _onGetAvailableStates(request, response) {
617
    let result = response.template;
1✔
618

619
    result.available_states = this.availableStates;
1✔
620

621
    response.send(result);
1✔
622
  }
623

624
  /**
625
   * The GetAvailableTransitions service handler.
626
   * @param {Object} request - The GetAvailableTransitions service request
627
   * @param {Object} response - The GetAvailableTranactions service response.
628
   * @returns {unknown} void.
629
   */
630
  _onGetAvailableTransitions(request, response) {
631
    let result = response.template;
1✔
632

633
    result.available_transitions = this.availableTransitions;
1✔
634

635
    response.send(result);
1✔
636
  }
637

638
  /**
639
   * The ChangeState service handler.
640
   * @param {Object} request - The ChangeState service request.
641
   * @param {Object} response - The ChangeState service response
642
   * @returns {unknown} void.
643
   * @private
644
   */
645
  _onChangeState(request, response) {
646
    let result = response.template;
1✔
647

648
    let transitionId = request.transition.id;
1✔
649
    if (request.transition.label) {
1!
650
      let transitionObj = rclnodejs.getLifecycleTransitionByLabel(
1✔
651
        this._stateMachineHandle,
652
        request.transition.label
653
      );
654
      if (transitionObj.id) {
1!
655
        transitionId = transitionObj.id;
1✔
656
      } else {
657
        result.success = false;
×
658
        response.send(result);
×
659
        return;
×
660
      }
661
    }
662

663
    let callbackReturnValue = new CallbackReturnValue();
1✔
664
    this._changeState(transitionId, callbackReturnValue);
1✔
665

666
    result.success = callbackReturnValue.isSuccess();
1✔
667
    response.send(result);
1✔
668
  }
669

670
  /**
671
   * Transition to a new lifecycle state.
672
   * @param {number|string} transitionIdOrLabel - The id or label of the target transition.
673
   * @param {CallbackReturnValue} callbackReturnValue - An out parameter that holds the CallbackReturnCode.
674
   * @returns {State} The new state.
675
   * @throws {Error} If transition is invalid for the current state.
676
   * @private
677
   */
678
  _changeState(transitionIdOrLabel, callbackReturnValue) {
679
    let initialState = this.currentState;
15✔
680
    let newStateObj =
681
      typeof transitionIdOrLabel === 'number'
15✔
682
        ? rclnodejs.triggerLifecycleTransitionById(
683
            this._stateMachineHandle,
684
            transitionIdOrLabel
685
          )
686
        : rclnodejs.triggerLifecycleTransitionByLabel(
687
            this._stateMachineHandle,
688
            transitionIdOrLabel
689
          );
690

691
    if (!newStateObj) {
15!
692
      throw new Error(
×
693
        `No transition available from state ${transitionIdOrLabel}.`
694
      );
695
    }
696

697
    let newState = State.fromSerializedState(newStateObj);
15✔
698

699
    let cbResult = this._executeCallback(newState, initialState);
15✔
700
    if (callbackReturnValue) callbackReturnValue.value = cbResult;
15✔
701

702
    let transitioningLabel = this._transitionId2Label(cbResult);
15✔
703
    newState = State.fromSerializedState(
15✔
704
      rclnodejs.triggerLifecycleTransitionByLabel(
705
        this._stateMachineHandle,
706
        transitioningLabel
707
      )
708
    );
709

710
    if (cbResult == CallbackReturnCode.ERROR) {
15✔
711
      cbResult = this._executeCallback(this.currentState, initialState);
1✔
712
      if (callbackReturnValue) callbackReturnValue.value = cbResult;
1!
713

714
      transitioningLabel = this._transitionId2Label(cbResult);
1✔
715
      newState = State.fromSerializedState(
1✔
716
        rclnodejs.triggerLifecycleTransitionByLabel(
717
          this._stateMachineHandle,
718
          transitioningLabel
719
        )
720
      );
721
    }
722

723
    return newState;
15✔
724
  }
725

726
  /**
727
   * Execute the callback function registered with a transition action,
728
   * e.g. registerOnConfigure(cb).
729
   * @param {State} state - The state to which the callback is
730
   * @param {State} prevState - The start state of the transition.
731
   * @returns {CallbackReturnCode} The callback return code.
732
   * @private
733
   */
734
  _executeCallback(state, prevState) {
735
    let result = CallbackReturnCode.SUCCESS;
16✔
736
    let callback = this._callbackMap.get(state.id);
16✔
737

738
    if (callback) {
16✔
739
      try {
7✔
740
        result = callback(prevState);
7✔
741
      } catch (err) {
742
        console.log('CB exception occured: ', err);
×
743
        result = CallbackReturnCode.ERROR;
×
744
      }
745
    }
746

747
    return result;
16✔
748
  }
749

750
  /**
751
   * Find the label for the transition with id == transitionId.
752
   * @param {number} transitionId - A transition id.
753
   * @returns {string} The label of the transition with id.
754
   * @private
755
   */
756
  _transitionId2Label(transitionId) {
757
    return rclnodejs.getLifecycleTransitionIdToLabel(transitionId);
16✔
758
  }
759
}
760

761
const Lifecycle = {
26✔
762
  CallbackReturnCode,
763
  CallbackReturnValue,
764
  LifecycleNode,
765
  State,
766
  Transition,
767
  TransitionDescription,
768
};
769

770
module.exports = Lifecycle;
26✔
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc