ROS2_Foxy学习6——进阶编程_C++


里面的例子参考 官方教程,然后附带一些解释和一些推荐的便于理解的文章。

1 编写 action

新建工作空间,并新建两个功能包:动作消息功能包和动作功能包。动作消息功能包中编写action文件,然后在动作功能包中使用前者的action接口。

1.1 自定义消息 action

1、创建:在动作消息功能包(这里包名是action_tutorials_interfaces)下,创建/action文件夹(与/src同级),并在其中建立.action消息文件,有关消息内容,详见本系列第三篇《ROS2_Foxy学习(三)核心概念》

Fibonacci.action

int32 order
---
int32[] sequence
---
int32[] partial_sequence

2、在package.xml文件中添加

	<buildtool_depend>rosidl_default_generators</buildtool_depend>
	<depend>action_msgs</depend>
	<member_of_group>rosidl_interface_packages</member_of_group>

注:action_msgs必要。
3、在CMakeLists.txt文件中添加

find_package(rosidl_default_generators REQUIRED)

rosidl_generate_interfaces(${PROJECT_NAME}
  "action/Fibonacci.action"
)

4、消息文件测试
功能包编译后(没有节点,没有运行),可以通过命令ros2 interface show来查看消息文件。

$ ros2 interface show action_tutorials_interfaces/action/Fibonacci

:下面创建的动作功能包名是action_tutorials_cpp,附带以下依赖,其中第一个就是刚刚创建的自定义消息功能包,可以在创建包时,通过参数dependencies添加,也可以手动修改package.xml(< depend >)和CMakeLists.txt(find_package)。

action_tutorials_interfaces 
rclcpp 
rclcpp_action 
rclcpp_components

1.2 动作服务端 action server

在动作功能包的/src目录下,编写动作服务端节点,fibonacci_action_server.cpp

#include <functional>
#include <memory>
#include <thread>

#include "action_tutorials_interfaces/action/fibonacci.hpp"
#include "rclcpp/rclcpp.hpp"
#include "rclcpp_action/rclcpp_action.hpp"
#include "rclcpp_components/register_node_macro.hpp"

//#include "action_tutorials_cpp/visibility_control.h"

namespace action_tutorials_cpp
{
class FibonacciActionServer : public rclcpp::Node
{
public:
	using Fibonacci = action_tutorials_interfaces::action::Fibonacci;
	using GoalHandleFibonacci = rclcpp_action::ServerGoalHandle<Fibonacci>;
	
	//构造函数:
	//1、初始化动作服务节点名 fibonacci_action_server
	//2、初始化动作服务器:消息类型Fibonacci,所属节点this,动作名fibonacci,回调函数
	explicit FibonacciActionServer(const rclcpp::NodeOptions & options = rclcpp::NodeOptions()) : Node("fibonacci_action_server", options)
	{
	using namespace std::placeholders;

	this->action_server_ = rclcpp_action::create_server<Fibonacci>(	//消息类型Fibonacci
	this,									//所属节点this,
	"fibonacci",								//动作名fibonacci
	std::bind(&FibonacciActionServer::handle_goal, this, _1, _2),	//回调函数:handle_goal  	接受目标
	std::bind(&FibonacciActionServer::handle_cancel, this, _1),		//回调函数:handle_cancel   	接受目标取消
	std::bind(&FibonacciActionServer::handle_accepted, this, _1));	//回调函数:handle_accepted 	接受新目标并处理
	}

private:
	rclcpp_action::Server<Fibonacci>::SharedPtr action_server_;

	//接受目标,同时告知client,这边知晓目标了,告知client的过程被自动处理了,下同
	rclcpp_action::GoalResponse handle_goal(const rclcpp_action::GoalUUID & uuid, std::shared_ptr<const Fibonacci::Goal> goal)
	{
		RCLCPP_INFO(this->get_logger(), "Received goal request with order %d", goal->order);
		(void)uuid;
		return rclcpp_action::GoalResponse::ACCEPT_AND_EXECUTE;
	}

	//接受目标取消,同时告知client,这边知道取消了
	rclcpp_action::CancelResponse handle_cancel(const std::shared_ptr<GoalHandleFibonacci> goal_handle)
	{
		RCLCPP_INFO(this->get_logger(), "Received request to cancel goal");
		(void)goal_handle;
		return rclcpp_action::CancelResponse::ACCEPT;
	}

	//接受新目标并处理
	void handle_accepted(const std::shared_ptr<GoalHandleFibonacci> goal_handle)
	{
		using namespace std::placeholders;
		
		//因为处理过程是一个耗时且长期运行的操作,因此将其移到另一线程处理
		std::thread{std::bind(&FibonacciActionServer::execute, this, _1), goal_handle}.detach();
	}

	//处理并反馈
	void execute(const std::shared_ptr<GoalHandleFibonacci> goal_handle)
	{
		RCLCPP_INFO(this->get_logger(), "Executing goal");
		
		//处理频率 1Hz
		rclcpp::Rate loop_rate(1);
		
		//获取目标
		const auto goal = goal_handle->get_goal();
		
		//定义反馈
		auto feedback = std::make_shared<Fibonacci::Feedback>();
		auto & sequence = feedback->partial_sequence;
		sequence.push_back(0);	//斐波那契数列的前两个值是 0 1	动作服务器将不断反馈当前的sequence到客户端
		sequence.push_back(1);
		
		//定义结果
		auto result = std::make_shared<Fibonacci::Result>();

		//计算
		for (int i = 1; (i < goal->order) && rclcpp::ok(); ++i) 
		{
			//检查目标是否被取消
			if (goal_handle->is_canceling()) 
			{
				result->sequence = sequence;	
				goal_handle->canceled(result);	
				RCLCPP_INFO(this->get_logger(), "Goal canceled");
				return;
			}
			
			//更新sequence
			sequence.push_back(sequence[i] + sequence[i - 1]);
			
			//反馈sequence
			goal_handle->publish_feedback(feedback);
			RCLCPP_INFO(this->get_logger(), "Publish feedback");

			loop_rate.sleep();
		}

		//目标达成
		if (rclcpp::ok()) 
		{
			result->sequence = sequence;
			goal_handle->succeed(result);
			RCLCPP_INFO(this->get_logger(), "Goal succeeded");
		}
	}
};  // class FibonacciActionServer

}  // namespace action_tutorials_cpp

RCLCPP_COMPONENTS_REGISTER_NODE(action_tutorials_cpp::FibonacciActionServer)


1、goal_handle:关于目标句柄goal_handle(也就是rclcpp_action::ServerGoalHandle),可以看下官方的帮助文档,这里刚学,还不是很理解,写多了也许就明白了。
2、std::thread:看这里,有关join和detach可以瞧一瞧这里
3、这里没有使用main函数,而是用下面这句,表明该节点可以在运行时动态加载,看看官方帮助文档,这里也不是很理解,后面再学习

RCLCPP_COMPONENTS_REGISTER_NODE(action_tutorials_cpp::FibonacciActionServer)

1.3 动作客户端 action client

在动作功能包的/src目录下,编写动作客户端节点,fibonacci_action_client.cpp

#include <functional>
#include <future>	//C++11 异步通信
#include <memory>
#include <string>
#include <sstream>	//C++标准库中 sstream 提供了比ANSI C的 stdio 更高级的一些功能,即单纯性、类型安全和可扩展性。

#include "action_tutorials_interfaces/action/fibonacci.hpp"

#include "rclcpp/rclcpp.hpp"
#include "rclcpp_action/rclcpp_action.hpp"
#include "rclcpp_components/register_node_macro.hpp"

namespace action_tutorials_cpp
{
class FibonacciActionClient : public rclcpp::Node
{
public:
	using Fibonacci = action_tutorials_interfaces::action::Fibonacci;
	using GoalHandleFibonacci = rclcpp_action::ClientGoalHandle<Fibonacci>;

	//构造函数:
	//1、初始化动作客户端节点名 fibonacci_action_client
	//2、初始化动作客户端:动作类型Fibonacci、所属节点this、动作名fibonacci
	//3、初始化定时器,每500ms发送一次目标
	explicit FibonacciActionClient(const rclcpp::NodeOptions & options) : Node("fibonacci_action_client", options)
	{
		this->client_ptr_ = rclcpp_action::create_client<Fibonacci>(
		this,
		"fibonacci");

		this->timer_ = this->create_wall_timer(std::chrono::milliseconds(500), std::bind(&FibonacciActionClient::send_goal, this));
	}

	//定时器中断函数
	void send_goal()
	{
		using namespace std::placeholders;

		//取消了定时,也就是说定时器中断函数只执行一次
		this->timer_->cancel();

		//然后,看看有没有在线的同名动作服务器
		if (!this->client_ptr_->wait_for_action_server()) 
		{
			RCLCPP_ERROR(this->get_logger(), "Action server not available after waiting");
			rclcpp::shutdown();
		}

		//定义目标
		auto goal_msg = Fibonacci::Goal();
		goal_msg.order = 10;

		RCLCPP_INFO(this->get_logger(), "Sending goal");

		//确定回调函数:请求后的响应、反馈的处理、结果的接收
		auto send_goal_options = rclcpp_action::Client<Fibonacci>::SendGoalOptions();
		
		send_goal_options.goal_response_callback =
		std::bind(&FibonacciActionClient::goal_response_callback, this, _1);
		
		send_goal_options.feedback_callback =
		std::bind(&FibonacciActionClient::feedback_callback, this, _1, _2);
		
		send_goal_options.result_callback =
		std::bind(&FibonacciActionClient::result_callback, this, _1);
		
		//发送目标给动作服务器
		this->client_ptr_->async_send_goal(goal_msg, send_goal_options);
	}

private:
	rclcpp_action::Client<Fibonacci>::SharedPtr client_ptr_;
	rclcpp::TimerBase::SharedPtr timer_;

	//请求后的响应
	void goal_response_callback(std::shared_future<GoalHandleFibonacci::SharedPtr> future)
	{
		auto goal_handle = future.get();
		if (!goal_handle) 
		{
			RCLCPP_ERROR(this->get_logger(), "Goal was rejected by server");
		} 
		else 
		{
			RCLCPP_INFO(this->get_logger(), "Goal accepted by server, waiting for result");
		}
	}

	//反馈的处理
	void feedback_callback(GoalHandleFibonacci::SharedPtr, const std::shared_ptr<const Fibonacci::Feedback> feedback)
	{
		std::stringstream ss;
		ss << "Next number in sequence received: ";
		for (auto number : feedback->partial_sequence) 
		{
			ss << number << " ";
		}
		RCLCPP_INFO(this->get_logger(), ss.str().c_str());
	}

	//结果的接收
	void result_callback(const GoalHandleFibonacci::WrappedResult & result)
	{
		switch (result.code) 
		{
			case rclcpp_action::ResultCode::SUCCEEDED: 	break;
			case rclcpp_action::ResultCode::ABORTED: 	RCLCPP_ERROR(this->get_logger(), "Goal was aborted");	return;
			case rclcpp_action::ResultCode::CANCELED:	RCLCPP_ERROR(this->get_logger(), "Goal was canceled");	return;
			default:					RCLCPP_ERROR(this->get_logger(), "Unknown result code");	return;
		}
		std::stringstream ss;
		ss << "Result received: ";
		for (auto number : result.result->sequence) 
		{
			ss << number << " ";
		}
		RCLCPP_INFO(this->get_logger(), ss.str().c_str());
		rclcpp::shutdown();
	}
};  // class FibonacciActionClient

}  // namespace action_tutorials_cpp

RCLCPP_COMPONENTS_REGISTER_NODE(action_tutorials_cpp::FibonacciActionClient)

注 std::future :看这里。std::thread是C++11中提供异步创建多线程的工具,但想要从线程中返回异步任务结果,一般需要依靠全局变量,从安全角度看,有些不妥,为此C++11提供了std::future类模板,future对象提供访问异步操作结果的机制,很轻松解决从异步任务中返回结果。

1.4 修改CMakelists.txt

在CMakelists.txt添加以下内容。

# 这里是生成共享库
add_library(action_server SHARED src/fibonacci_action_server.cpp)
add_library(action_client SHARED src/fibonacci_action_client.cpp)

target_include_directories(action_server PRIVATE
  $<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include>
  $<INSTALL_INTERFACE:include>)
target_include_directories(action_client PRIVATE
  $<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include>
  $<INSTALL_INTERFACE:include>)
  
target_compile_definitions(action_server PRIVATE "ACTION_TUTORIALS_CPP_BUILDING_DLL")
target_compile_definitions(action_client PRIVATE "ACTION_TUTORIALS_CPP_BUILDING_DLL")
  
ament_target_dependencies(action_server
  "action_tutorials_interfaces"
  "rclcpp"
  "rclcpp_action"
  "rclcpp_components")
ament_target_dependencies(action_client
  "action_tutorials_interfaces"
  "rclcpp"
  "rclcpp_action"
  "rclcpp_components")
  
rclcpp_components_register_node(action_server PLUGIN "action_tutorials_cpp::FibonacciActionServer" EXECUTABLE fibonacci_action_server)
rclcpp_components_register_node(action_client PLUGIN "action_tutorials_cpp::FibonacciActionClient" EXECUTABLE fibonacci_action_client)

install(TARGETS
  action_server
  action_client
  ARCHIVE DESTINATION lib
  LIBRARY DESTINATION lib
  RUNTIME DESTINATION bin)
  • 3
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值