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

RobotWebTools / rclnodejs / 15923128939

27 Jun 2025 09:34AM UTC coverage: 84.526% (-0.1%) from 84.625%
15923128939

Pull #1158

github

web-flow
Merge dfb3b243e 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%)

3223.07 hits per line

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

84.12
/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');
208✔
18
const LifecyclePublisher = require('./lifecycle_publisher.js');
208✔
19
const loader = require('./interface_loader.js');
208✔
20
const Context = require('./context.js');
208✔
21
const Node = require('./node.js');
208✔
22
const NodeOptions = require('./node_options.js');
208✔
23
const Service = require('./service.js');
208✔
24

25
const SHUTDOWN_TRANSITION_LABEL =
26
  rclnodejs.getLifecycleShutdownTransitionLabel();
208✔
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) {
64✔
38
    StateInterface = loader.loadInterface('lifecycle_msgs/msg/State');
8✔
39
  }
40
  return StateInterface;
64✔
41
}
42

43
function getTransitionInterface() {
44
  if (!TransitionInterface) {
408✔
45
    TransitionInterface = loader.loadInterface('lifecycle_msgs/msg/Transition');
8✔
46
  }
47
  return TransitionInterface;
408✔
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;
736✔
68
    this.label = label;
736✔
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);
440✔
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;
40✔
101
    this.startState = startState;
40✔
102
    this.goalState = goalState;
40✔
103
  }
104
}
105

106
/**
107
 * The values returned by TransitionCallback.
108
 * @readonly
109
 * @enum {number}
110
 */
111
const CallbackReturnCode = {
208✔
112
  get SUCCESS() {
113
    return getTransitionInterface().TRANSITION_CALLBACK_SUCCESS;
184✔
114
  },
115
  get FAILURE() {
116
    return getTransitionInterface().TRANSITION_CALLBACK_FAILURE;
×
117
  },
118
  get ERROR() {
119
    return getTransitionInterface().TRANSITION_CALLBACK_ERROR;
128✔
120
  },
121
};
122
Object.freeze(CallbackReturnCode);
208✔
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) {
8✔
134
    this._value = value;
8✔
135
    this._errorMsg = null;
8✔
136
  }
137

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

146
  set value(value) {
147
    this._value = value;
8✔
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;
8✔
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 = '',
8✔
281
    context = Context.defaultContext(),
8✔
282
    options = NodeOptions.defaultOptions,
8✔
283
    enableCommunicationInterface = true
×
284
  ) {
285
    super(nodeName, namespace, context, options);
152✔
286
    this.init(enableCommunicationInterface);
152✔
287
  }
288

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

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

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

304
    // Setup and register the 4 native rcl lifecycle services thar are
305
    // part of _stateMachineHandle.
306
    let srvHandleObj = rclnodejs.getLifecycleSrvNameAndHandle(
144✔
307
      this._stateMachineHandle,
308
      'srv_get_state'
309
    );
310
    let service = new Service(
144✔
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)
8✔
317
    );
318
    this._services.push(service);
144✔
319

320
    srvHandleObj = rclnodejs.getLifecycleSrvNameAndHandle(
144✔
321
      this._stateMachineHandle,
322
      'srv_get_available_states'
323
    );
324
    service = new Service(
144✔
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)
8✔
331
    );
332
    this._services.push(service);
144✔
333

334
    srvHandleObj = rclnodejs.getLifecycleSrvNameAndHandle(
144✔
335
      this._stateMachineHandle,
336
      'srv_get_available_transitions'
337
    );
338
    service = new Service(
144✔
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)
8✔
345
    );
346
    this._services.push(service);
144✔
347

348
    srvHandleObj = rclnodejs.getLifecycleSrvNameAndHandle(
144✔
349
      this._stateMachineHandle,
350
      'srv_change_state'
351
    );
352
    service = new Service(
144✔
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)
8✔
359
    );
360
    this._services.push(service);
144✔
361
    this.syncHandles();
144✔
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);
24✔
377
  }
378

379
  /**
380
   * Access the current lifecycle state.
381
   * @returns {State} The current state.
382
   */
383
  get currentState() {
384
    let currentStateObj = rclnodejs.getCurrentLifecycleState(
192✔
385
      this._stateMachineHandle
386
    );
387
    return State.fromSerializedState(currentStateObj);
192✔
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);
16✔
396
    let states = stateObjs.map(
16✔
397
      (stateObj) => new State(stateObj.id, stateObj.label)
176✔
398
    );
399
    return states;
16✔
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(
16✔
437
      this._stateMachineHandle
438
    );
439
    let transitions = transitionObjs.map((transitionDescObj) => {
16✔
440
      let transition = new Transition(
40✔
441
        transitionDescObj.transition.id,
442
        transitionDescObj.transition.label
443
      );
444
      let startState = new State(
40✔
445
        transitionDescObj.start_state.id,
446
        transitionDescObj.start_state.label
447
      );
448
      let goalState = new State(
40✔
449
        transitionDescObj.goal_state.id,
450
        transitionDescObj.goal_state.label
451
      );
452
      return new TransitionDescription(transition, startState, goalState);
40✔
453
    });
454
    return transitions;
16✔
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);
16✔
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);
8✔
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(
8✔
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);
8✔
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(
8✔
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(
16✔
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(
48✔
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(
24✔
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(
16✔
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(
16✔
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);
16✔
592
  }
593

594
  /**
595
   * Check if state machine is initialized.
596
   *
597
   * @returns {boolean} true if the state machine is initialized; otherwise false.
598
   */
599
  get isInitialized() {
600
    return rclnodejs.isInitialized(this._stateMachineHandle);
8✔
601
  }
602

603
  /**
604
   * Log the state machine data.
605
   *
606
   * @returns {undefined} void.
607
   */
608
  print() {
609
    rclnodejs.print(this._stateMachineHandle);
8✔
610
  }
611

612
  /**
613
   * The GetState service handler.
614
   * @param {Object} request - The GetState service request.
615
   * @param {Object} response - The GetState service response.
616
   * @returns {unknown} void.
617
   * @private
618
   */
619
  _onGetState(request, response) {
620
    let result = response.template;
8✔
621

622
    result.current_state = this.currentState;
8✔
623

624
    response.send(result);
8✔
625
  }
626

627
  /**
628
   * The GetAvailableStates service handler.
629
   * @param {Object} request - The GetAvailableStates service request.
630
   * @param {Object} response - The GetAvailableStates service response.
631
   * @returns {unknown} void.
632
   * @private
633
   */
634
  _onGetAvailableStates(request, response) {
635
    let result = response.template;
8✔
636

637
    result.available_states = this.availableStates;
8✔
638

639
    response.send(result);
8✔
640
  }
641

642
  /**
643
   * The GetAvailableTransitions service handler.
644
   * @param {Object} request - The GetAvailableTransitions service request
645
   * @param {Object} response - The GetAvailableTranactions service response.
646
   * @returns {unknown} void.
647
   */
648
  _onGetAvailableTransitions(request, response) {
649
    let result = response.template;
8✔
650

651
    result.available_transitions = this.availableTransitions;
8✔
652

653
    response.send(result);
8✔
654
  }
655

656
  /**
657
   * The ChangeState service handler.
658
   * @param {Object} request - The ChangeState service request.
659
   * @param {Object} response - The ChangeState service response
660
   * @returns {unknown} void.
661
   * @private
662
   */
663
  _onChangeState(request, response) {
664
    let result = response.template;
8✔
665

666
    let transitionId = request.transition.id;
8✔
667
    if (request.transition.label) {
8!
668
      let transitionObj = rclnodejs.getLifecycleTransitionByLabel(
8✔
669
        this._stateMachineHandle,
670
        request.transition.label
671
      );
672
      if (transitionObj.id) {
8!
673
        transitionId = transitionObj.id;
8✔
674
      } else {
675
        result.success = false;
×
676
        response.send(result);
×
677
        return;
×
678
      }
679
    }
680

681
    let callbackReturnValue = new CallbackReturnValue();
8✔
682
    this._changeState(transitionId, callbackReturnValue);
8✔
683

684
    result.success = callbackReturnValue.isSuccess();
8✔
685
    response.send(result);
8✔
686
  }
687

688
  /**
689
   * Transition to a new lifecycle state.
690
   * @param {number|string} transitionIdOrLabel - The id or label of the target transition.
691
   * @param {CallbackReturnValue} callbackReturnValue - An out parameter that holds the CallbackReturnCode.
692
   * @returns {State} The new state.
693
   * @throws {Error} If transition is invalid for the current state.
694
   * @private
695
   */
696
  _changeState(transitionIdOrLabel, callbackReturnValue) {
697
    let initialState = this.currentState;
120✔
698
    let newStateObj =
699
      typeof transitionIdOrLabel === 'number'
120✔
700
        ? rclnodejs.triggerLifecycleTransitionById(
701
            this._stateMachineHandle,
702
            transitionIdOrLabel
703
          )
704
        : rclnodejs.triggerLifecycleTransitionByLabel(
705
            this._stateMachineHandle,
706
            transitionIdOrLabel
707
          );
708

709
    if (!newStateObj) {
120!
710
      throw new Error(
×
711
        `No transition available from state ${transitionIdOrLabel}.`
712
      );
713
    }
714

715
    let newState = State.fromSerializedState(newStateObj);
120✔
716

717
    let cbResult = this._executeCallback(newState, initialState);
120✔
718
    if (callbackReturnValue) callbackReturnValue.value = cbResult;
120✔
719

720
    let transitioningLabel = this._transitionId2Label(cbResult);
120✔
721
    newState = State.fromSerializedState(
120✔
722
      rclnodejs.triggerLifecycleTransitionByLabel(
723
        this._stateMachineHandle,
724
        transitioningLabel
725
      )
726
    );
727

728
    if (cbResult == CallbackReturnCode.ERROR) {
120✔
729
      cbResult = this._executeCallback(this.currentState, initialState);
8✔
730
      if (callbackReturnValue) callbackReturnValue.value = cbResult;
8!
731

732
      transitioningLabel = this._transitionId2Label(cbResult);
8✔
733
      newState = State.fromSerializedState(
8✔
734
        rclnodejs.triggerLifecycleTransitionByLabel(
735
          this._stateMachineHandle,
736
          transitioningLabel
737
        )
738
      );
739
    }
740

741
    return newState;
120✔
742
  }
743

744
  /**
745
   * Execute the callback function registered with a transition action,
746
   * e.g. registerOnConfigure(cb).
747
   * @param {State} state - The state to which the callback is
748
   * @param {State} prevState - The start state of the transition.
749
   * @returns {CallbackReturnCode} The callback return code.
750
   * @private
751
   */
752
  _executeCallback(state, prevState) {
753
    let result = CallbackReturnCode.SUCCESS;
128✔
754
    let callback = this._callbackMap.get(state.id);
128✔
755

756
    if (callback) {
128✔
757
      try {
56✔
758
        result = callback(prevState);
56✔
759
      } catch (err) {
760
        console.log('CB exception occured: ', err);
×
761
        result = CallbackReturnCode.ERROR;
×
762
      }
763
    }
764

765
    return result;
128✔
766
  }
767

768
  /**
769
   * Find the label for the transition with id == transitionId.
770
   * @param {number} transitionId - A transition id.
771
   * @returns {string} The label of the transition with id.
772
   * @private
773
   */
774
  _transitionId2Label(transitionId) {
775
    return rclnodejs.getLifecycleTransitionIdToLabel(transitionId);
128✔
776
  }
777
}
778

779
const Lifecycle = {
208✔
780
  CallbackReturnCode,
781
  CallbackReturnValue,
782
  LifecycleNode,
783
  State,
784
  Transition,
785
  TransitionDescription,
786
};
787

788
module.exports = Lifecycle;
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

© 2026 Coveralls, Inc