Skip to content

Commit 33db181

Browse files
qiuzhongMinggang Wang
authored andcommitted
Add actionlib tutorial
Move example/README.md to tutorials/actionlib.md
1 parent d0738c0 commit 33db181

File tree

3 files changed

+152
-46
lines changed

3 files changed

+152
-46
lines changed

example/README.md

Lines changed: 0 additions & 46 deletions
This file was deleted.

scripts/compile_tests.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,7 @@ function copyFile(platform, srcFile, destFile) {
6262
function copyAll(fileList, dest) {
6363
fileList.forEach((file) => {
6464
copyFile(os.platform(), file, path.join(dest, path.basename(file)));
65+
console.log(`cpp executables ${file} is copied to test/cpp.`);
6566
});
6667
}
6768

tutorials/actionlib.md

Lines changed: 151 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,151 @@
1+
# Introduction
2+
Actionlib tutorial based on `rclnodejs`.
3+
4+
## Actionlib background
5+
To continue the tutorial, you should have some basic understanding of the `action` term in ROS. If you're not familar with it, please refer to the [description](http://wiki.ros.org/actionlib/DetailedDescription).
6+
7+
## Precondition
8+
You should prepare an action text file. The content is like this:
9+
10+
```
11+
# Define the goal
12+
uint32 dishwasher_id # Specify which dishwasher we want to use
13+
---
14+
# Define the result
15+
uint32 total_dishes_cleaned
16+
---
17+
# Define a feedback message
18+
float32 percent_complete
19+
# Use a message from another package, to prove that it works
20+
sensor_msgs/Image image
21+
```
22+
23+
Fortunately, there is a `dodishes.action` file that is already available in the `test/ros1_actions` directory.
24+
25+
## Basic steps of processing action files for rclnodejs
26+
For `rclnodejs`, the basic steps of process an action file is separated into 2 steps:
27+
* Generate several msg files and the js message package from the action file.
28+
* Build the shared library from the msg files that will be used in running actionlib-feature-related code.
29+
30+
### Generate msg files from the action file
31+
The `generated` directory contains the generated js message package for `rclnodejs`. If you have run `npm install`, then it should
32+
exist after `npm install` was done. However, since your `AMENT_PREFIX_PATH` may not include the directory path of the action file, the `ros1_actions` package may not be generated. So you need remove the whole `generated` directory and regenerate them.
33+
```
34+
$ rm -fr generated
35+
$ export AMENT_PREFIX_PATH=$AMENT_PREFIX_PATH:$(pwd)/test/ros1_actions
36+
$ node scripts/generate_messages.js
37+
```
38+
39+
### Build the shared library from the msg files
40+
```
41+
$ cd test/ros1_action
42+
$ colcon build
43+
```
44+
45+
## Run the action example
46+
After the build, you can run the action example. The action example contains two parts, one is the [action server](./example/action-server-example.js), another is the [action client](./example/action-client-example.js). When running the example, you should launch the server first, and then launch the client.
47+
48+
* Launch a terminal session, load ROS2 environment and go to `rclnodejs` directory.
49+
```
50+
$ source test/ros1_actions/install/local_setup.bash
51+
$ node example/action-server-example.js
52+
```
53+
54+
* Launch another terminal session, load ROS2 environment and go to `rclnodejs` directory.
55+
```
56+
$ source test/ros1_actions/install/local_setup.bash
57+
$ node example/action-client-example.js
58+
```
59+
60+
Here is the action client output:
61+
```
62+
The goal was sent, the goal id is 7c28f24a-5ce8-4b13-a2aa-7aa62128fd03
63+
70% of the task has been completed.
64+
The goal, whose id is 7c28f24a-5ce8-4b13-a2aa-7aa62128fd03, has been executed successfully.
65+
10 dishes have been cleaned.
66+
The goal, whose id is 7c28f24a-5ce8-4b13-a2aa-7aa62128fd03, has been executed successfully.
67+
```
68+
69+
And here is the action server output:
70+
```
71+
A goal, whose id is 7c28f24a-5ce8-4b13-a2aa-7aa62128fd03, was received.
72+
```
73+
74+
## Explaination of the action example:
75+
1. Action server
76+
For the action server, the skeleton code is like this:
77+
```
78+
rclnodejs.init().then(() => {
79+
const as = new rclnodejs.ActionLib.ActionServer({
80+
type: 'ros1_actions/msg/DoDishes',
81+
actionServer: 'dishes',
82+
rclnodejs: rclnodejs
83+
});
84+
85+
as.on('goal', function(goal) {
86+
goal.setAccepted('goal accepted');
87+
goal.publishFeedback(feedback);
88+
setTimeout(() => {
89+
goal.setSucceeded({total_dishes_cleaned: 10}, 'done');
90+
}, 500);
91+
});
92+
93+
as.on('cancel', (goalHandle) => {
94+
// cancel handler code
95+
});
96+
97+
as.start();
98+
}).catch((err) => {
99+
console.error(err);
100+
});
101+
```
102+
103+
First, you should new an `ActionServer` with the `type` and the `actionServer`. The `type` is the action package name and the `actionServer` is the name of the action server, which should be the same as the action client request to in future.
104+
105+
The `ActionServer` can emit 2 type events: `goal` and `cancel` events.
106+
* `goal` event: triggered when an action client sends an goal to the action server by calling its `sendGoal()` method. In the handler of the this event, you can accept the goal by calling `goal.setAccepted()`. During executing the goal, you can send a feedback to the action client by calling `goal.publishFeedback()`. Once the goal is completed, you should set the goal status by calling `goal.setSucceeded()`, which will trigger the `result` event for the action client.
107+
* `cancel` event: triggered when an action client cancels the the goal after a goal was sent to the action server but not completed yet.
108+
109+
The `start()` method must be called to start the action server.
110+
111+
2. Action client
112+
For the action client, the skeleton code is like this:
113+
```
114+
rclnodejs.init().then(() => {
115+
const GoalStatus = rclnodejs.require('actionlib_msgs/msg/GoalStatus');
116+
117+
const ac = new rclnodejs.ActionClientInterface({
118+
type: 'ros1_actions/msg/DoDishes',
119+
actionServer: 'dishes',
120+
rclnodejs: rclnodejs
121+
});
122+
123+
let goal = ac.sendGoal({ goal: {dishwasher_id: 1}});
124+
125+
ac.on('feedback', (feedback) => {
126+
// feedback handler
127+
});
128+
129+
ac.on('status', (status) => {
130+
status.status_list.forEach((s) =>{
131+
if (s.goal_id.id === goal.goal_id.id &&
132+
s.status === GoalStatus.SUCCEEDED) {
133+
console.log(`The goal, whose id is ${s.goal_id.id}, has been executed successfully.`);
134+
}
135+
});
136+
});
137+
138+
ac.on('result', (result) => {
139+
// result handler
140+
});
141+
}).catch((err) => {
142+
console.error(err);
143+
});
144+
```
145+
146+
To construct an action client, use `new rclnodejs.ActionClientInterface()`. The action client can emit 3 type events:
147+
* `status` event: tirggered when the action server sends messages to the action client during the goal is executing.
148+
* `feedback` event: triggered after the action server called `publishFeedback`.
149+
* `result` event: triggered after the action server called `setSucceeded`.
150+
151+
**Notice**, the action state transitions must obey some specific order, for more details please refer to [this articile](http://wiki.ros.org/actionlib/DetailedDescription)

0 commit comments

Comments
 (0)