Skip to content

Commit

Permalink
lifecycle api, types, tests & example (RobotWebTools#722)
Browse files Browse the repository at this point in the history
lifecycle api, types, tests & example.
  • Loading branch information
wayneparrott authored Nov 23, 2020
1 parent 4ff5957 commit 25b6076
Show file tree
Hide file tree
Showing 18 changed files with 2,260 additions and 42 deletions.
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

0 comments on commit 25b6076

Please sign in to comment.