<<TableOfContents(4)>>


== 创建一个行为消息 ==
开始编写一个行为之前,很重要的事是定义目标、结果和反馈消息。行为消息会自动从'''.action'''文件中生成,对于更多细节关于行为文件查看[[actionlib]]文档。这个文件定义了目标、结果和行为反馈话题的类型和格式。用你最喜欢的编辑器创建actionlib_tutorials/action/Averaging.action,然后用以下内容加载:

{{{
#goal definition
int32 samples
---
#result definition
float32 mean
float32 std_dev
---
#feedback
int32 sample
float32 data
float32 mean
float32 std_dev
}}}

为了从这个文件中手动生成消息:

{{{
$ roscd actionlib_tutorials
$ rosrun actionlib_msgs genaction.py -o msg/ action/Averaging.action
}}}

你将会看到:
 {{{
Generating for action Averaging
}}}

在编译过程中,自动生成消息文件,添加以下内容到CMakeLists.txt:
{{{
find_package(catkin REQUIRED COMPONENTS actionlib std_msgs message_generation) 
add_action_files(DIRECTORY action FILES Averaging.action)
generate_messages(DEPENDENCIES std_msgs actionlib_msgs)
}}}
然后运行:
 {{{
$ cd %TOP_DIR_YOUR_CATKIN_WORKSPACE%
$ catkin_make
}}}

== 编写一个简单的行为服务器 ==
首先,用你最喜欢的编辑器创建actionlib_tutorials/src/averaging_server.cpp,然后用添加以下内容:
=== 代码 ===
{{{
#!cplusplus block=action
#include <ros/ros.h>
#include <std_msgs/Float32.h>
#include <actionlib/server/simple_action_server.h>
#include <actionlib_tutorials/AveragingAction.h>

class AveragingAction
{
public:
    
  AveragingAction(std::string name) : 
    as_(nh_, name, false),
    action_name_(name)
  {
    //注册目标和反馈回调函数
    as_.registerGoalCallback(boost::bind(&AveragingAction::goalCB, this));
    as_.registerPreemptCallback(boost::bind(&AveragingAction::preemptCB, this));

    //订阅感兴趣的话题数据
    sub_ = nh_.subscribe("/random_number", 1, &AveragingAction::analysisCB, this);
    as_.start();
  }

  ~AveragingAction(void)
  {
  }

  void goalCB()
  {
    // 重置帮助变量
    data_count_ = 0;
    sum_ = 0;
    sum_sq_ = 0;
    // 接收新目标
    goal_ = as_.acceptNewGoal()->samples;
  }

  void preemptCB()
  {
    ROS_INFO("%s: Preempted", action_name_.c_str());
    // 设置行为状态为抢占(preempted)
    as_.setPreempted();
  }

  void analysisCB(const std_msgs::Float32::ConstPtr& msg)
  {
    // 确保行为还没有被取消
    if (!as_.isActive())
      return;
    
    data_count_++;
    feedback_.sample = data_count_;
    feedback_.data = msg->data;
    //处理std_dev和数据含义 
    sum_ += msg->data;
    feedback_.mean = sum_ / data_count_;
    sum_sq_ += pow(msg->data, 2);
    feedback_.std_dev = sqrt(fabs((sum_sq_/data_count_) - pow(feedback_.mean, 2)));
    as_.publishFeedback(feedback_);

    if(data_count_ > goal_) 
    {
      result_.mean = feedback_.mean;
      result_.std_dev = feedback_.std_dev;

      if(result_.mean < 5.0)
      {
        ROS_INFO("%s: Aborted", action_name_.c_str());
        //设置行为状态为崩溃(aborted)
        as_.setAborted(result_);
      }
      else 
      {
        ROS_INFO("%s: Succeeded", action_name_.c_str());
        // 设置行为状态为成功(succeeded)
        as_.setSucceeded(result_);
      }
    } 
  }

protected:
    
  ros::NodeHandle nh_;
  actionlib::SimpleActionServer<actionlib_tutorials::AveragingAction> as_;
  std::string action_name_;
  int data_count_, goal_;
  float sum_, sum_sq_;
  actionlib_tutorials::AveragingFeedback feedback_;
  actionlib_tutorials::AveragingResult result_;
  ros::Subscriber sub_;
};

int main(int argc, char** argv)
{
  ros::init(argc, argv, "averaging");

  AveragingAction averaging(ros::this_node::getName());
  ros::spin();

  return 0;
}
}}}

(查看[[https://github.com/ros/common_tutorials/blob/hydro-devel/actionlib_tutorials/src/averaging_server.cpp|存储库的代码]])

=== 代码解释 ===
现在,让我们分块解释代码。

<<CodeRef(action,1,3)>>
使用的actionlib/server/simple_action_server.h是行为库,从简单行为实现。

<<CodeRef(action,4,4)>>
这个包含从以上Averaging.action文件中生成的消息。这个头文件自动从[[http://www.ros.org/doc/api/actionlib_tutorials/html/msg/AveragingAction.html|AveragingAction.msg]]文件生成,更多关于消息定义的信息,查看[[msg|msg]]页面。

<<CodeRef(action,6,16)>>
在行为构造函数中,创建一个行为服务器。行为服务器加载一个节点句柄(node handle)、行为名称和可选的回调(executeCB)。在本示例中创建的行为服务器不需要回调(executeCB)参数。在行为服务器构造之后,等价替换成注册的目标(goal)和抢占(preempt)回调用于行为的构造函数。

<<CodeRef(action,18,21)>>
这里,建立一个数据回调,该回调会处理行为并且行为服务器开启。

<<CodeRef(action,27,35)>>
这里是一个目标(goalCB)回调函数,参考构造函数。这个回调函数不返回任何东西也不需要任何参数。当目标回调(goalCB)调用行为时,需要接收目标并且存储任何重要的信息。如果你需要在你接收它之前查看目标,查阅[[actionlib_tutorials/Tutorials/SimpleActionServer(ExecuteCallbackMethod)|SimpleActionServer(ExecuteCallbackMethod)]] 教程。

<<CodeRef(action,37,42)>>
行为事件声明,当回调发生,行为代码会运行,否则一个会创建一个抢占回调来保证行为响应及时来取消请求。回调函数不需要参数,并且会设置抢占(preempted)到行为服务器。

<<CodeRef(action,44,48)>>
这里模拟回调得到订阅数据通道的消息格式,并且在继续处理数据之前检查行为是否处于活跃状态。

<<CodeRef(action,50,58)>>
这里,把相关数据放到反馈变量中,然后在行为服务器提供的反馈通道发布出去。

<<CodeRef(action,60,78)>>
一旦收集到足够的数据,行为服务器会设置成功或失败。这将禁用行为服务器并且analysisCB函数会向之前描述的那样立即返回。

<<CodeRef(action,80,90)>>
这些是行为类的受保护的变量。在构造行为的过程中,构造节点句柄然后传递到行为服务器。构造的行为服务器正如以上描述那样。创建的反馈和结果消息用于在行为中发布。创建的订阅也会保存节点句柄。

<<CodeRef(action,92,100)>>
最后的main函数,创建行为并且循环节点。行为会运行和等待接收目标。

== 编译 & 运行行为 ==
在你的CMakeLists.txt文件中添加以下几行:
{{{
add_executable(averaging_server src/averaging_server.cpp)
target_link_libraries(averaging_server ${catkin_LIBRARIES})
}}}
在你使用`catkin_make`编译之后,在一个新终端开启一个roscore。
{{{
$ roscore
}}}
然后运行行为服务器:
{{{
$ rosrun actionlib_tutorials averaging_server
}}}
你会看到类似如下输出:
 {{{
[ INFO] 1250790662.410962000: Started node [/averaging], pid [29267], bound on [aqy], xmlrpc port [39746], tcpros port [49573], logging to [~/ros/ros/log/averaging_29267.log], using [real] time
}}}

检查你的行为运行正常,使用rostopic list命令查看发布情况:
{{{
$ rostopic list -v
}}}
你会看到类似如下输出:
 {{{
Published topics:
 * /averaging/status [actionlib_msgs/GoalStatusArray] 1 publisher
 * /averaging/result [actionlib_tutorials/AveragingActionResult] 1 publisher
 * /averaging/feedback [actionlib_tutorials/AveragingActionFeedback] 1 publisher
 * /rosout [roslib/Log] 1 publisher
 * /rosout_agg [roslib/Log] 1 publisher

Subscribed topics:
 * /time [unknown type] 2 subscribers
 * /rosout [roslib/Log] 1 subscriber
 * /averaging/goal [unknown type] 1 subscriber
 * /averaging/cancel [unknown type] 1 subscriber
}}}
另外你可以查看节点:
{{{
$ rosrun rqt_graph rqt_graph &
}}}

{{attachment:averaging_action_server.png||width=100%}}

这里显示了你的行为服务器发布反馈、状态和期望的结果通道,以及订阅目标和期望取消通道。行为服务器运行正常。 

== 发送目标到行为服务器 ==
对于下一步使用你的行为,你需要按下Ctrl-C行为服务器,然后 [[cn/actionlib_tutorials/Tutorials/SimpleActionClient(Threaded)|创建一个简单的行为客户端线程]]。