Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions binding.gyp
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
'./src/rcl_action_bindings.cpp',
'./src/rcl_bindings.cpp',
'./src/rcl_handle.cpp',
'./src/rcl_lifecycle_bindings.cpp',
'./src/rcl_utilities.cpp',
'./src/shadow_node.cpp',
],
Expand All @@ -42,6 +43,7 @@
'libraries': [
'-lrcl',
'-lrcl_action',
'-lrcl_lifecycle',
'-lrcutils',
'-lrcl_yaml_param_parser',
'-lrmw',
Expand Down
121 changes: 121 additions & 0 deletions example/lifecycle-node-example.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
// Copyright (c) 2020 Wayne Parrott. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

'use strict';

const rclnodejs = require('../index.js');

const NODE_NAME = 'test_node';
const TOPIC = 'test';
const COUNTD_DOWN = 5;

/**
* This app demonstrates using a LifecycleNode to
* publish a count down value from 10 - 0. A subscription
* is created to watch for the counter reaching 0 at which
* time it will deactivate and shutdown the node.
*/
class App {

constructor() {
this._node = null;
this._publisher = null;
this._subscriber = null;
this._timer = null;
this._StateInterface = null;
}

async init() {
await rclnodejs.init();

this._count = COUNTD_DOWN;
this._node = rclnodejs.createLifecycleNode(NODE_NAME);
this._node.registerOnConfigure((prevState)=>this.onConfigure(prevState));
this._node.registerOnActivate((prevState)=>this.onActivate(prevState));
this._node.registerOnDeactivate((prevState)=>this.onDeactivate(prevState));
this._node.registerOnShutdown((prevState)=>this.onShutdown(prevState));
this._StateInterface = rclnodejs.createMessage('lifecycle_msgs/msg/State').constructor;

rclnodejs.spin(this._node);
}

start() {
this._node.configure();
this._node.activate();
}

stop() {
this._node.deactivate();
this._node.shutdown();
rclnodejs.shutdown();
process.exit(0);
}

onConfigure() {
console.log('Lifecycle: CONFIGURE');
this._publisher =
this._node.createLifecyclePublisher('std_msgs/msg/String', TOPIC);
this._subscriber =
this._node.createSubscription('std_msgs/msg/String', TOPIC,
(msg) => {
let cnt = parseInt(msg.data, 10);
console.log(`countdown msg: ${cnt}`);
if (cnt < 1) {
this.stop();
}
});
return rclnodejs.lifecycle.CallbackReturnCode.SUCCESS;
}

onActivate() {
console.log('Lifecycle: ACTIVATE');
this._publisher.activate();
this._timer = this._node.createTimer(1000, () => {
this._publisher.publish(`${this._count--}`);
});
return rclnodejs.lifecycle.CallbackReturnCode.SUCCESS;
}

onDeactivate() {
console.log('Lifecycle: DEACTIVATE');
this._publisher.deactivate();
if (this._timer) {
this._timer.cancel();
this._timer = null;
}
return rclnodejs.lifecycle.CallbackReturnCode.SUCCESS;
}

onShutdown(prevState) {
console.log('Lifecycle: SHUTDOWN');
let result = rclnodejs.lifecycle.CallbackReturnCode.SUCCESS;
if (prevState.id === this._StateInterface.PRIMARY_STATE) {
result = this.onDeactivate();
this._publisher = null;
this._subscriber = null;
}

return result;
}
}

async function main() {
let app = new App();
await app.init();
app.start();
}

main();


76 changes: 50 additions & 26 deletions index.js
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ const {
getActionServerNamesAndTypesByNode,
getActionNamesAndTypes,
} = require('./lib/action/graph.js');
const Lifecycle = require('./lib/lifecycle.js');

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

/** Lifecycle namespace */
lifecycle: Lifecycle,

/** {@link Logging} class */
logging: logging,

Expand Down Expand Up @@ -192,33 +196,26 @@ let rcl = {
context = Context.defaultContext(),
options = NodeOptions.defaultOptions
) {
if (typeof nodeName !== 'string' || typeof namespace !== 'string') {
throw new TypeError('Invalid argument.');
}

if (!this._contextToNodeArrayMap.has(context)) {
throw new Error(
'Invalid context. Must call rclnodejs(context) before using the context'
);
}
return _createNode(nodeName, namespace, context, options, rclnodejs.ShadowNode);
},

const handle = rclnodejs.createNode(nodeName, namespace, context.handle);
const node = new rclnodejs.ShadowNode();
node.handle = handle;
Object.defineProperty(node, 'handle', {
configurable: false,
writable: false,
}); // make read-only
node.context = context;
node.init(nodeName, namespace, context, options);
debug(
'Finish initializing node, name = %s and namespace = %s.',
nodeName,
namespace
);

this._contextToNodeArrayMap.get(context).push(node);
return node;
/**
* Create a managed Node that implements a well-defined life-cycle state
* model using the {@link https://github.com/ros2/rcl/tree/master/rcl_lifecycle|ros2 client library (rcl) lifecyle api}.
* @param {string} nodeName - The name used to register in ROS.
* @param {string} [namespace=''] - The namespace used in ROS.
* @param {Context} [context=Context.defaultContext()] - The context to create the node in.
* @param {NodeOptions} [options=NodeOptions.defaultOptions] - The options to configure the new node behavior.
* @return {LifecycleNode} A new instance of the specified node.
* @throws {Error} If the given context is not registered.
*/
createLifecycleNode(
nodeName,
namespace = '',
context = Context.defaultContext(),
options = NodeOptions.defaultOptions
) {
return _createNode(nodeName, namespace, context, options, Lifecycle.LifecycleNode);
},

/**
Expand Down Expand Up @@ -442,3 +439,30 @@ const TimeSource = require('./lib/time_source.js');
rcl.TimeSource = TimeSource;

inherits(rclnodejs.ShadowNode, Node);

function _createNode(
nodeName,
namespace = '',
context = Context.defaultContext(),
options = NodeOptions.defaultOptions,
nodeClass
) {
if (typeof nodeName !== 'string' || typeof namespace !== 'string') {
throw new TypeError('Invalid argument.');
}

if (!rcl._contextToNodeArrayMap.has(context)) {
throw new Error('Invalid context. Must call rclnodejs(context) before using the context');
}

let handle = rclnodejs.createNode(nodeName, namespace, context.handle);
let node = new nodeClass();
node.handle = handle;
Object.defineProperty(node, 'handle', { configurable: false, writable: false }); // make read-only
node.context = context;
node.init(nodeName, namespace, context, options);
debug('Finish initializing node, name = %s and namespace = %s.', nodeName, namespace);

rcl._contextToNodeArrayMap.get(context).push(node);
return node;
}
Loading