ROS action simple usage example

1. Why use action

In ros, we use the mechanism of publisher and subscriber to process the information in most cases, that is, the mechanism of topic. It can handle some data processing that does not return with status. When it comes to data interaction between nodes that require data return, the first choice is to use the servicer mechanism for processing and return of results.

But there is a situation where the above two methods are not very suitable: when one node requests another node to process data, but the other node needs to spend a long time waiting for processing the request. At this time, if the previous node has been in a waiting situation and cannot know the processing situation of the other party, it will be very bad. An extreme situation is that the node processing the data falls into an infinite loop, and the requesting node never receives the result, and the program will be stuck.

ROS action makes up for this problem to a certain extent. Compared with service, it is more suitable for handling such long-term processing situations. First of all, it can bring feedback feedback in action, and can know the running degree to a certain extent. . Secondly, in the action, the currently executing action can be canceled through cancel. For example, in service, when we request a program to run, if we find that the running track deviates from the expected midway, we cannot request to stop, and we need to let it actively return failure. But in cation, if we find that the result of the operation deviates from expectations, we can request to cancel and continue the execution. Therefore, the modification mechanism is more suitable than topic and service in many scenarios.

2. Create an action message

I have used msg and service before. In fact, there are similar things in many places. A little difference may be that msg has only one class, while service has two classes: the request part of the request and the returned response. The action consists of three parts: the requested goal, the returned result, and the feedback during execution

Simply write an action as follows:

First create an action file:

catkin_create_pkg action_test roscpp rospy message_generation
cd action_test
mkdir action
cd cation
gedit Actiontest.action

Then enter in Actiontest.action:

#goal definition
int32 numb
---
#result definition
bool result
---
#feedback
int32 temp_sum

Also modify the cmakelist file and package.xml file.

For the cmakelist file, three places need to be modified:

1. Add the last two lines to find_package

find_package(catkin REQUIRED COMPONENTS
  roscpp
  rospy
  tf
  actionlib_msgs
  actionlib
)

2. Add the name of the action in add_action_files

 add_action_files(
   FILES
   XunJiePhoto.action
   #DIRECTORY
 )

3. Add actionlib_msgs to generate_messages

 generate_messages(
   DEPENDENCIES
   actionlib_msgs
 )

For the package.xml file, you need to add the following content:

  <build_depend>actionlib</build_depend>
  <build_depend>actionlib_msgs</build_depend>
  <build_export_depend>actionlib</build_export_depend>
  <build_export_depend>actionlib_msgs</build_export_depend>
  <exec_depend>actionlib</exec_depend>
  <exec_depend>actionlib_msgs</exec_depend>
  <exec_depend>message_runtime</exec_depend>
  <build_depend>message_generation</build_depend>

However, it seems that I compiled the above six lines successfully without adding them. I don’t know if it will affect the execution. I suggest adding them just to be on the safe side.

After completing the above parts, catkin_make compiles to generate the corresponding .h file:
insert image description here

3. How to use an action

The action mechanism is similar to the service, which also requires a client and a server. First, let's write a self-added server:

3.1, action server

We also use C++ to complete this part of the code:

#include "ros/ros.h"
#include "action_test/ActiontestAction.h"
#include "action_test/ActiontestGoal.h"
#include <actionlib/server/simple_action_server.h>
#include <actionlib/client/simple_action_client.h>
class action_server
{
    
    
private:
    /* data */
    ros::NodeHandle n;
    action_test::ActiontestResult add_sum;
    action_test::ActiontestFeedback add_feedback;
    actionlib::SimpleActionServer<action_test::ActiontestAction> as_;
public:
    action_server(/* args */);
    ~action_server();
    void actioncb(const actionlib::SimpleActionServer<action_test::ActiontestAction>::GoalConstPtr& goal);
};

action_server::action_server(/* args */):
as_(n, "test_add", boost::bind(&action_server::actioncb, this, _1),false)
{
    
    
    as_.start();
}

void action_server::actioncb(const actionlib::SimpleActionServer<action_test::ActiontestAction>::GoalConstPtr& goal)
{
    
    
    int numb = goal->numb;
    int sum = 0;
    for(int i=0;i<numb;i++)
    {
    
    
        sum+=i;
        add_feedback.temp_sum = sum;
        as_.publishFeedback(add_feedback);
        if (as_.isPreemptRequested())
        {
    
    
            add_sum.result = sum;
            as_.setPreempted(add_sum);
			return;
        }
        ros::Duration(0.2).sleep();
    }
    add_sum.result = sum;
    as_.setSucceeded(add_sum);
}
action_server::~action_server()
{
    
    
}
int main(int argc, char **argv)
{
    
    
    ros::init(argc, argv,"action_server");
    action_server action_server;
    ros::spin();  
    return 0;
}

First of all, we need to add several header files at the top, which are a little more than service or topic:

#include "action_test/ActiontestAction.h"
#include "action_test/ActiontestGoal.h"
#include <actionlib/server/simple_action_server.h>
#include <actionlib/client/simple_action_client.h>

here:

#include "action_test/ActiontestGoal.h"

It seems unnecessary.
Then declare a class, and you need to declare your action in the class function:

actionlib::SimpleActionServer<action_test::ActiontestAction> as_;

The front is the fixed format <> is the action message type. And need a corresponding processing function:

void actioncb(const actionlib::SimpleActionServer<action_test::ActiontestAction>::GoalConstPtr& goal);

In addition, two variables are declared here, of course, if they are not used, they will not be affected.

    action_test::ActiontestResult add_sum;
    action_test::ActiontestFeedback add_feedback;

The upper one is the final return value result, and the lower one is the feedback value feedback.

Next we need to start the action:

action_server::action_server(/* args */):
as_(n, "test_add", boost::bind(&action_server::actioncb, this, _1),false)
{
    
    
    as_.start();
}

Note:

as_(n, "test_add", boost::bind(&action_server::actioncb, this, _1),false)

It is written outside the "{}". I didn't understand it at first, but after reading this blog , I still don't understand it very well, but it should be written like this, otherwise it will not pass the compilation.

Finally, let's take a look at some of the things used in the handler function. The logic itself is very simple, starting from 0 and adding every 0.2 seconds until it reaches a given value. Finally, it returns through the setSucceeded function. But in it we need to pay attention to two functions:

publishFeedback(add_feedback)

as well as

isPreemptRequested()

We mentioned earlier that the difference between action and service is that action has feedback and can be stopped midway. The action feedback is realized through the publishFeedback function. Earlier we defined an action_test::ActiontestFeedback add_feedback, so when using it, assign a value to add_feedback and then publish it through the publishFeedback function.

The following isPreemptRequested is used to suspend, the suspension of the action function is executed by sending the canceled message from the client, and the judgment of the server is judged by isPreemptRequested. isPreemptRequested is true when an abort command is received. We can then use the setPreempted(add_sum) function to return the current result and exit the loop.

After compiling the above code and running it, a total of 5 topics will be generated:
insert image description here
cancel is used to cancel the current action, and goal is used to receive cation. These two are received by the client publishing server. Feedback is used to feed back the current situation in real time, and status is also a kind of state, representing the current action state. 1 when running, 2 when canceled, 3 when idle, and 4 when aborted. result is to return the final result.

We can use rqt to test the server. Add the topic of goal in rqt. The goal is 100, as follows:
insert image description hereWhen we execute the topic, several topics can be echoed to get the corresponding data: like publishFeedback, because the callback function is cyclically published, it will be updated all the time:
insert image description here
and the result is set to be published once at the end, so when It will be published at the end of the program:
insert image description here
In addition, note that the cliect side can also be used to control the action to close at a specific time. For example, when the operation is halfway through, and we do not want to continue executing, we can stop the current loop through the topic of cancel. In isPreemptRequested( ) function will enter the function processing when it receives the cancel request. When testing rqt, it should be noted that the goal_id of cancel must be the same as above, otherwise it will not take effect. For example, if we execute cancel while the program is running,
insert image description herewe will find that the output in the terminal is as follows:

insert image description here
It can be seen that the result should have been 4950, but it exited early due to the execution of cancel, and the final result was released through setPreempted(add_sum).

3.1, action client

The client side is a bit cleaner than the server side because it has no callback function:

#include "ros/ros.h"
#include "action_test/ActiontestAction.h"
#include "action_test/ActiontestGoal.h"
#include <actionlib/server/simple_action_server.h>
#include <actionlib/client/simple_action_client.h>
using namespace std;
class action_client
{
    
    
 private:
    /* data */
    ros::NodeHandle n;
 public:
    action_client();
    ~action_client();
    void action_call();
    actionlib::SimpleActionClient<action_test::ActiontestAction> action_test_;
};
 
action_client::action_client():
action_test_("test_add",true)
{
    
    
}
void action_client::action_call()
{
    
    
   cout<<"call server"<<endl;
   action_test::ActiontestGoal addgoal;
   addgoal.numb = 100;
   action_test_.sendGoal(addgoal);
   //action_test_.sendGoalAndWait(addgoal);
}
action_client::~action_client()
{
    
    
}
int main(int argc,char **argv)
{
    
    
   ros::init(argc, argv,"action_client");
   action_client action_client;
   ros::spinOnce();
   action_client.action_test_.waitForServer();
   action_client.action_call();
   ros::spin();  
   return 0; 
}

This code is relatively simple, you can refer to the explanation on the server side. Its effect is to request a goal to the server, which can have the same effect as the above rqt. Note that this line:

action_client.action_test_.waitForServer();

It is quite special, if you do not add this on the server side, it seems that there will be bugs, that is, the server and the client are opened normally, but the server cannot receive the Goal sent by the client. So add it just to be on the safe side.

https://docs.ros.org/en/kinetic/api/actionlib/html/classactionlib_1_1SimpleActionServer.html
https://docs.ros.org/en/kinetic/api/actionlib/html/classactionlib_1_1SimpleActionClient.html
https://blog.csdn.net/qq_42145185/article/details/107508640

Guess you like

Origin blog.csdn.net/YiYeZhiNian/article/details/128212506