Skip to content

Commit ff27ef8

Browse files
wayneparrottMinggang Wang
authored andcommitted
lifecycle api, types, tests & example (#722)
lifecycle api, types, tests & example.
1 parent 8ee2b11 commit ff27ef8

18 files changed

+2260
-42
lines changed

binding.gyp

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
'./src/rcl_action_bindings.cpp',
2121
'./src/rcl_bindings.cpp',
2222
'./src/rcl_handle.cpp',
23+
'./src/rcl_lifecycle_bindings.cpp',
2324
'./src/rcl_utilities.cpp',
2425
'./src/shadow_node.cpp',
2526
],
@@ -42,6 +43,7 @@
4243
'libraries': [
4344
'-lrcl',
4445
'-lrcl_action',
46+
'-lrcl_lifecycle',
4547
'-lrcutils',
4648
'-lrcl_yaml_param_parser',
4749
'-lrmw',

example/lifecycle-node-example.js

Lines changed: 121 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,121 @@
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('../index.js');
18+
19+
const NODE_NAME = 'test_node';
20+
const TOPIC = 'test';
21+
const COUNTD_DOWN = 5;
22+
23+
/**
24+
* This app demonstrates using a LifecycleNode to
25+
* publish a count down value from 10 - 0. A subscription
26+
* is created to watch for the counter reaching 0 at which
27+
* time it will deactivate and shutdown the node.
28+
*/
29+
class App {
30+
31+
constructor() {
32+
this._node = null;
33+
this._publisher = null;
34+
this._subscriber = null;
35+
this._timer = null;
36+
this._StateInterface = null;
37+
}
38+
39+
async init() {
40+
await rclnodejs.init();
41+
42+
this._count = COUNTD_DOWN;
43+
this._node = rclnodejs.createLifecycleNode(NODE_NAME);
44+
this._node.registerOnConfigure((prevState)=>this.onConfigure(prevState));
45+
this._node.registerOnActivate((prevState)=>this.onActivate(prevState));
46+
this._node.registerOnDeactivate((prevState)=>this.onDeactivate(prevState));
47+
this._node.registerOnShutdown((prevState)=>this.onShutdown(prevState));
48+
this._StateInterface = rclnodejs.createMessage('lifecycle_msgs/msg/State').constructor;
49+
50+
rclnodejs.spin(this._node);
51+
}
52+
53+
start() {
54+
this._node.configure();
55+
this._node.activate();
56+
}
57+
58+
stop() {
59+
this._node.deactivate();
60+
this._node.shutdown();
61+
rclnodejs.shutdown();
62+
process.exit(0);
63+
}
64+
65+
onConfigure() {
66+
console.log('Lifecycle: CONFIGURE');
67+
this._publisher =
68+
this._node.createLifecyclePublisher('std_msgs/msg/String', TOPIC);
69+
this._subscriber =
70+
this._node.createSubscription('std_msgs/msg/String', TOPIC,
71+
(msg) => {
72+
let cnt = parseInt(msg.data, 10);
73+
console.log(`countdown msg: ${cnt}`);
74+
if (cnt < 1) {
75+
this.stop();
76+
}
77+
});
78+
return rclnodejs.lifecycle.CallbackReturnCode.SUCCESS;
79+
}
80+
81+
onActivate() {
82+
console.log('Lifecycle: ACTIVATE');
83+
this._publisher.activate();
84+
this._timer = this._node.createTimer(1000, () => {
85+
this._publisher.publish(`${this._count--}`);
86+
});
87+
return rclnodejs.lifecycle.CallbackReturnCode.SUCCESS;
88+
}
89+
90+
onDeactivate() {
91+
console.log('Lifecycle: DEACTIVATE');
92+
this._publisher.deactivate();
93+
if (this._timer) {
94+
this._timer.cancel();
95+
this._timer = null;
96+
}
97+
return rclnodejs.lifecycle.CallbackReturnCode.SUCCESS;
98+
}
99+
100+
onShutdown(prevState) {
101+
console.log('Lifecycle: SHUTDOWN');
102+
let result = rclnodejs.lifecycle.CallbackReturnCode.SUCCESS;
103+
if (prevState.id === this._StateInterface.PRIMARY_STATE) {
104+
result = this.onDeactivate();
105+
this._publisher = null;
106+
this._subscriber = null;
107+
}
108+
109+
return result;
110+
}
111+
}
112+
113+
async function main() {
114+
let app = new App();
115+
await app.init();
116+
app.start();
117+
}
118+
119+
main();
120+
121+

index.js

Lines changed: 50 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@ const {
4949
getActionServerNamesAndTypesByNode,
5050
getActionNamesAndTypes,
5151
} = require('./lib/action/graph.js');
52+
const Lifecycle = require('./lib/lifecycle.js');
5253

5354
function inherits(target, source) {
5455
const properties = Object.getOwnPropertyNames(source.prototype);
@@ -123,6 +124,9 @@ let rcl = {
123124
/** {@link IntegerRange} class */
124125
IntegerRange: IntegerRange,
125126

127+
/** Lifecycle namespace */
128+
lifecycle: Lifecycle,
129+
126130
/** {@link Logging} class */
127131
logging: logging,
128132

@@ -192,33 +196,26 @@ let rcl = {
192196
context = Context.defaultContext(),
193197
options = NodeOptions.defaultOptions
194198
) {
195-
if (typeof nodeName !== 'string' || typeof namespace !== 'string') {
196-
throw new TypeError('Invalid argument.');
197-
}
198-
199-
if (!this._contextToNodeArrayMap.has(context)) {
200-
throw new Error(
201-
'Invalid context. Must call rclnodejs(context) before using the context'
202-
);
203-
}
199+
return _createNode(nodeName, namespace, context, options, rclnodejs.ShadowNode);
200+
},
204201

205-
const handle = rclnodejs.createNode(nodeName, namespace, context.handle);
206-
const node = new rclnodejs.ShadowNode();
207-
node.handle = handle;
208-
Object.defineProperty(node, 'handle', {
209-
configurable: false,
210-
writable: false,
211-
}); // make read-only
212-
node.context = context;
213-
node.init(nodeName, namespace, context, options);
214-
debug(
215-
'Finish initializing node, name = %s and namespace = %s.',
216-
nodeName,
217-
namespace
218-
);
219-
220-
this._contextToNodeArrayMap.get(context).push(node);
221-
return node;
202+
/**
203+
* Create a managed Node that implements a well-defined life-cycle state
204+
* model using the {@link https://github.com/ros2/rcl/tree/master/rcl_lifecycle|ros2 client library (rcl) lifecyle api}.
205+
* @param {string} nodeName - The name used to register in ROS.
206+
* @param {string} [namespace=''] - The namespace used in ROS.
207+
* @param {Context} [context=Context.defaultContext()] - The context to create the node in.
208+
* @param {NodeOptions} [options=NodeOptions.defaultOptions] - The options to configure the new node behavior.
209+
* @return {LifecycleNode} A new instance of the specified node.
210+
* @throws {Error} If the given context is not registered.
211+
*/
212+
createLifecycleNode(
213+
nodeName,
214+
namespace = '',
215+
context = Context.defaultContext(),
216+
options = NodeOptions.defaultOptions
217+
) {
218+
return _createNode(nodeName, namespace, context, options, Lifecycle.LifecycleNode);
222219
},
223220

224221
/**
@@ -442,3 +439,30 @@ const TimeSource = require('./lib/time_source.js');
442439
rcl.TimeSource = TimeSource;
443440

444441
inherits(rclnodejs.ShadowNode, Node);
442+
443+
function _createNode(
444+
nodeName,
445+
namespace = '',
446+
context = Context.defaultContext(),
447+
options = NodeOptions.defaultOptions,
448+
nodeClass
449+
) {
450+
if (typeof nodeName !== 'string' || typeof namespace !== 'string') {
451+
throw new TypeError('Invalid argument.');
452+
}
453+
454+
if (!rcl._contextToNodeArrayMap.has(context)) {
455+
throw new Error('Invalid context. Must call rclnodejs(context) before using the context');
456+
}
457+
458+
let handle = rclnodejs.createNode(nodeName, namespace, context.handle);
459+
let node = new nodeClass();
460+
node.handle = handle;
461+
Object.defineProperty(node, 'handle', { configurable: false, writable: false }); // make read-only
462+
node.context = context;
463+
node.init(nodeName, namespace, context, options);
464+
debug('Finish initializing node, name = %s and namespace = %s.', nodeName, namespace);
465+
466+
rcl._contextToNodeArrayMap.get(context).push(node);
467+
return node;
468+
}

0 commit comments

Comments
 (0)