一、ROS2通信机制简介
四部分组成:
1、节点(对应单一功能模块,例如雷达驱动节点、摄像头)
2、话题(具有相同话题的节点可以通信到一起)
3、通信模型(话题通信、服务通信、动作通信、服务通信)
4、接口(数据载体,是数据传输的特定格式 msg文件用于话题通信 srv文件用于服务通信 action文件用于动作通信)
首先创建工作空间
mkdir -p ws01_plumbing/src
然后进入工作空间
cd ws01_plumbling
最后编译即可创建完成
colcon build
1,创建自定义接口文件
cd src
ros2 pkg create --build-type ament_cmake base_interfaces_demo
创建msg,action,srv文件夹
文件夹中创建子文件,首字母要大写
编辑子文件
Student.msg
string name
int32 age
float64 height
Progress.action
int32 num
---
int32 sum
---
float64 progress
Addints.srv
int32 num1
int32 num2
---
int32 sum
编辑CMakeLists.txt
增加
find_package(rosidl_default_generators REQUIRED) ###新增
# 为接口文件生成源码
rosidl_generate_interfaces( ${PROJECT_NAME} ###新增
"msg/Student.msg"
"srv/Addints.srv"
"action/Progress.action"
)
编辑package.xml
msg和srv需要添加
rosidl_default_generators
rosidl_default_runtime
rosidl_interface_packages
action需要添加
rosidl_default_generators
action_msgs
进行编译
colcon build --packages-select base_interfaces_demo
调用命令进行检验
ros2 interface show base_interfaces_demo/msg/Student.msg
ros2 interface show base_interfaces_demo/action/Progress.action
ros2 interface show base_interfaces_demo/srv/Addints.srv
如果出现自己写的数据,即编译成功
2,准备工作
创建话题通信功能包cpp01_topic,服务通信cpp02_service,动作通信cpp03_action,参数通信cpp04_param
ros2 pkg create cpp01_topic --build-type ament_cmake --dependencies rclcpp std_msgs base_interfaces_demo --node-name demo01_talker_str
ros2 pkg create cpp02_service --build-type ament_cmake --dependencies rclcpp base_interfaces_demo --node-name demo01_serive
ros2 pkg create cpp01_action --build-type ament_cmake --dependencies rclcpp rclcpp_action base_interfaces_demo --node-name demo01_action_serive
ros2 pkg create cpp01_param --build-type ament_cmake --dependencies rclcpp --node-name demo01_param_serive
在各自的src中创建c++文件
例如在cpp01_topic/src中创建demo02_listener_str
编辑CMakeList.txt
例如cpp01_topic
cmake_minimum_required(VERSION 3.8)
project(cpp01_topic)
if(CMAKE_COMPILER_IS_GNUCXX OR CMAKE_CXX_COMPILER_ID MATCHES "Clang")
add_compile_options(-Wall -Wextra -Wpedantic)
endif()
#引用外部依赖包
# find dependencies
find_package(ament_cmake REQUIRED)
find_package(rclcpp REQUIRED)
find_package(std_msgs REQUIRED)
find_package(base_interfaces_demo REQUIRED)
add_executable(demo01_talker_str src/demo01_talker_str.cpp)
add_executable(demo02_listener_str src/demo02_listener_str.cpp)
target_include_directories(demo01_talker_str PUBLIC
$
$)
target_compile_features(demo01_talker_str PUBLIC c_std_99 cxx_std_17) # Require C99 and C++17
ament_target_dependencies(
demo01_talker_str
"rclcpp"
"std_msgs"
"base_interfaces_demo"
)
ament_target_dependencies(
demo02_listener_str
"rclcpp"
"std_msgs"
"base_interfaces_demo"
)
install(TARGETS
demo01_talker_str
demo02_listener_str
DESTINATION lib/${PROJECT_NAME})
if(BUILD_TESTING)
find_package(ament_lint_auto REQUIRED)
# the following line skips the linter which checks for copyrights
# comment the line when a copyright and license is added to all source files
set(ament_cmake_copyright_FOUND TRUE)
# the following line skips cpplint (only works in a git repo)
# comment the line when this package is in a git repo and when
# a copyright and license is added to all source files
set(ament_cmake_cpplint_FOUND TRUE)
ament_lint_auto_find_test_dependencies()
endif()
ament_package()
其实这个只用增加
#映射源文件与可执行文件
add_executable(demo02_listener_str src/demo02_listener_str.cpp)
#设置目标依赖库
ament_target_dependencies(
demo02_listener_str
"rclcpp"
"std_msgs"
"base_interfaces_demo"
)
#定义安装规则
install(TARGETS
demo01_talker_str
demo02_listener_str
DESTINATION lib/${PROJECT_NAME})
package.xml不需要编辑,因为新增的文件包不需要依赖其他的文件
3,话题通信
编写发布者
/*
需求:以某个固定频率发送学生信息“姓名,年龄,身高”,每发布一条,;年龄+1
流程:
1.包含头文件
2.初始化ROS2客户端
3.自定义节点类
3.1.创建消息发布方
3.2.创建定时器
3.3.组织并发布消息
4.调用spin函数,传入自定义类对象指针
5.释放资源
*/
//1.包含头文件
#include "rclcpp/rclcpp.hpp"
#include "base_interfaces_demo/msg/student.hpp"
using namespace std::chrono_literals;//可以在自变量后面跟上时间单位 用于后面定时器的定时设置1s
using base_interfaces_demo::msg::Student;
//3.自定义节点类
class TalkerStu: public rclcpp::Node{
public:
TalkerStu():Node("talkerstu_node_cpp"),count(0){
RCLCPP_INFO(this->get_logger(),"发布节点创建!");
// 3.1.创建消息发布方
/*
模板:被发布的消息类型;
参数:
1.话题名称;
2.QOS(消息队列长度)
3.默认值不用管
返回值:发布对象指针
*/
publisher_ = this->create_publisher("chatterstu",10);//10代表消息队列长度
// 3.2.创建定时器
/*
模板:可以不使用
参数:
1.时间间隔;
2.回调函数;
返回值:定时器对象指针
*/
timer_ = this->create_wall_timer(1s,std::bind(&TalkerStu::on_timer,this));
}
private:
void on_timer(){
// 3.3.组织并发布消息
auto message = Student();
message.name = "huluwa";
message.age = count;
count += 1;
message.height = 150.00;
RCLCPP_INFO(this->get_logger(),"学生信息:%s,%d,%.2f",message.name.c_str(),message.age,message.height);
publisher_->publish(message);
}
//SharePtr为创建对象指针类型的变量,可以存储这种指针类型的地址,例如 int*p;
rclcpp::Publisher::SharedPtr publisher_;
rclcpp::TimerBase::SharedPtr timer_;
size_t count;
};
int main(int argc, char ** argv)
{
//2.初始化ROS2客户端
rclcpp::init(argc,argv);
创建一个context容器
//4.调用spin函数,传入自定义类对象指针
//make_shared 是创建这种类型的指针,已经存储了初始化的指针
rclcpp::spin(std::make_shared());
//5.释放资源,删除context
rclcpp::shutdown();
return 0;
}
再编写订阅者
/*
需求:订阅发布方发布的消息,并在终端输出
流程:
1.包含头文件
2.初始化ROS2客户端
3.自定义节点类
3.1.创建订阅方
3.2.解析并输出数据
4.调用spin函数,并传入节点对象指针
5.资源释放
*/
//1.包含头文件
#include "rclcpp/rclcpp.hpp"
#include "base_interfaces_demo/msg/student.hpp"
using base_interfaces_demo::msg::Student;
//3.自定义节点类
class ListenerStu: public rclcpp::Node{
public:
ListenerStu():Node("listenerstu_node_cpp"){
RCLCPP_INFO(this->get_logger(),"订阅方创建!");
//3.1.创建订阅方
/*
模板:消息类型;
参数:
1.话题名称
2.QOS,队列长度
3.回调函数
返回值:订阅对象指针
*/
subscription_ = this->create_subscription("chatterstu",10,std::bind(&ListenerStu::do_cb,this,std::placeholders::_1));
}
private:
void do_cb(const Student &msg){
//3.2.解析并输出数据
RCLCPP_INFO(this->get_logger(),"学生信息:%s,%d,%.2f",msg.name.c_str(),msg.age,msg.height);
}
rclcpp::Subscription::SharedPtr subscription_;
};
int main(int argc, char const *argv[])
{
//2.初始化ROS2客户端
rclcpp::init(argc,argv);
//4.调用spin函数,并传入节点对象指针
rclcpp::spin(std::make_shared());
//5.资源释放
rclcpp::shutdown();
return 0;
}
补充
//SharePtr为创建对象指针类型的变量,可以存储这种指针类型的地址,例如 int*p;
//make_shared 是创建这种类型的指针,已经存储了初始化的指针
如果需要打印%s,需要c_str()
头文件引入:
rclcpp/rclcpp.hpp: ROS2 C++库的主要头文件。
std_msgs/msg/string.hpp: 包含用于发布字符串消息的头文件。
命名空间:
使用using namespace std::chrono_literals;引入chrono命名空间,用于后面设置定时器的时间间隔。
自定义节点类 Talker:
继承自rclcpp::Node类,表示这是一个ROS2节点。
构造函数中初始化节点,并创建了消息发布者和定时器。
消息发布者和定时器的创建:
create_publisher创建了一个发布者,用于发布字符串消息到名为 "chatter" 的话题,消息队列长度为 10。
create_wall_timer创建了一个定时器,每隔1秒触发一次,触发时调用on_timer成员函数。
on_timer 成员函数:
组织并发布带有编号的消息,编号每次递增。
使用RCLCPP_INFO输出日志,显示发布的消息内容。
main 函数:
初始化ROS2客户端。
创建Talker类的实例并传入spin函数,使节点进入主循环。
最后释放ROS2资源并返回0。
4,服务通信
编辑服务端
#include "rclcpp/rclcpp.hpp"
#include "base_interfaces_demo/srv/addints.hpp"
using base_interfaces_demo::srv::Addints;
//使用占位符
using std::placeholders::_1;
using std::placeholders::_2;
class AddintsSerive : public rclcpp::Node{
public:
AddintsSerive():Node("addint_serivr_node_cpp")
{
RCLCPP_INFO(this->get_logger(),"服务端创建");
server_ = this->create_service("addints",std::bind(&AddintsSerive::add,this,_1,_2));
}
//被封装,rep为接口文件里Request,res为Response指针
void add(const Addints::Request::SharedPtr req, const Addints::Response::SharedPtr res){
res->sum = req->num1 + req->num2;
RCLCPP_INFO(this->get_logger(),"收到数据:(%d,%d),进行响应:%d",req->num1,req->num2,res->sum);
}
private:
rclcpp::Service::SharedPtr server_;
};
int main(int argc,char ** argv)
{
rclcpp::init(argc,argv);
rclcpp::spin(std::make_shared());
rclcpp::shutdown();
return 0;
}
编辑客户端
#include "rclcpp/rclcpp.hpp"
#include "base_interfaces_demo/srv/addints.hpp"
using base_interfaces_demo::srv::Addints;
using namespace std::chrono_literals;
class AddintsCilent : public rclcpp::Node{
public:
AddintsCilent(): rclcpp::Node("add_int_client_node_cpp"){
RCLCPP_INFO(this->get_logger(),"客户端创建");
client_ = this->create_client("addints");
}
bool connect_server(){
//在指定超时时间内连接服务器,如果连接上了,那么返回true,否则返回false
// client_->wait_for_service(1s);
while(!client_->wait_for_service(1s))
{
//对ctrl+c进行特殊处理,如果不处理对于想要提前终止客户端的操作会异常
if(!rclcpp::ok())
{
RCLCPP_INFO(rclcpp::get_logger("rclcpp"),"强行终止客户端!");
return false;
}
RCLCPP_INFO(rclcpp::get_logger("rclcpp"),"服务连接中!");
}
return true;
}
rclcpp::Client::FutureAndRequestId send_request(int num1, int num2)
{
//组织请求数据
//发送
/*
rclcpp::Client::FutureAndRequestId
async_send_request(std::shared_ptr request)
*/
auto request = std::make_shared();
request->num1 = num1;
request->num2 = num2;
return client_->async_send_request(request);
}
private:
rclcpp::Client::SharedPtr client_;
};
int main(int argc,char ** argv)
{
if(argc!=3)
{
RCLCPP_INFO(rclcpp::get_logger("rclcpp"),"请提交两个整型数字!");
return 1;
}
rclcpp::init(argc,argv);
auto client = std::make_shared();
bool flag = client->connect_server();
if (!flag)
{
RCLCPP_INFO(rclcpp::get_logger("rclcpp"),"服务器连接失败,程序退出!");
return 0;
}
//调用请求提交函数,接受并处理响应结果
auto future = client->send_request(atoi(argv[1]),atoi(argv[2]));
//处理响应
if (rclcpp::spin_until_future_complete(client,future) == rclcpp::FutureReturnCode::SUCCESS)//成功
{
RCLCPP_INFO(client->get_logger(),"响应成功!sum = %d",future.get()->sum);
}
else //失败
{
RCLCPP_INFO(client->get_logger(),"响应失败!");
}
rclcpp::shutdown();
return 0;
}
//在编译时写的数据,通过argv[]来获取数据
5,动作通信
编辑服务端
#include "rclcpp/rclcpp.hpp"
#include "rclcpp_action/rclcpp_action.hpp"
#include "base_interfaces_demo/action/progress.hpp"
using std::placeholders::_1;
using std::placeholders::_2;
using base_interfaces_demo::action::Progress;
class ActionServer : public rclcpp::Node
{
public:
ActionServer(): Node("progress_action_server_node_cpp")
{
server_ = rclcpp_action::create_server
编辑客户端
#include "rclcpp/rclcpp.hpp"
#include "rclcpp_action/rclcpp_action.hpp"
#include "base_interfaces_demo/action/progress.hpp"
using std::placeholders::_1;
using std::placeholders::_2;
using namespace std::chrono_literals;
using base_interfaces_demo::action::Progress;
class ActionClient : public rclcpp::Node
{
public:
ActionClient(): Node("progress_action_client_node_cpp")
{
client_ = rclcpp_action::create_client(this,"get_sum");
}
void send_goal(int num)
{
if(!client_->wait_for_action_server(10s))
{
return;
}
auto goal =Progress::Goal();
goal.num = num ;
rclcpp_action::Client::SendGoalOptions option;
option.goal_response_callback = std::bind(&ActionClient::goal_response_callback,this,_1);
option.feedback_callback = std::bind(&ActionClient::feedback_callback,this,_1,_2);
option.result_callback = std::bind(&ActionClient::result_callback,this,_1);
auto future = client_->async_send_goal(goal,option);
//发送请求,里面要求的数据类型为Progress::Goal()和rclcpp_action::Client::SendGoalOptions,2.4.3 要设置里面的数据
}
void goal_response_callback(rclcpp_action::ClientGoalHandle::SharedPtr goal_handle){
if(!goal_handle)
{
RCLCPP_INFO(this->get_logger(),"NO");
}
else
{
RCLCPP_INFO(this->get_logger(),"YES");
}
}
void feedback_callback(rclcpp_action::ClientGoalHandle::SharedPtr goal_handle,const std::shared_ptr feedback){
(void)goal_handle;
double progress = feedback->progress;
RCLCPP_INFO(this->get_logger(),"%.2f",progress);
}
void result_callback(const rclcpp_action::ClientGoalHandle::WrappedResult & result){
if(result.code == rclcpp_action::ResultCode::SUCCEEDED)
{
RCLCPP_INFO(this->get_logger(),"%d",result.result->sum);
}
}
private:
rclcpp_action::Client::SharedPtr client_;
};
6,参数通信
编辑服务端
#include "rclcpp/rclcpp.hpp"
class ParamServer : public rclcpp::Node
{
public:
ParamServer():Node("param_server_node_cpp",rclcpp::NodeOptions().allow_undeclared_parameters(true))
{
RCLCPP_INFO(this->get_logger(),"服务端创建");
}
void declare_param(){
this->declare_parameter("car_name","tiger");
this->declare_parameter("width",1.55);
this->declare_parameter("wheels",5);
this->set_parameter(rclcpp::Parameter("high","10"));
}
void get_param(){
auto car = this->get_parameter("car_name");
RCLCPP_INFO(this->get_logger(),"%s=%s",car.get_name().c_str(),car.as_string().c_str());
}
void update_param(){
this->set_parameter(rclcpp::Parameter("width",6.50));
}
void del_param(){
this->undeclare_parameter("high");
}
};
int main(int argc, char ** argv)
{
rclcpp::init(argc,argv);
auto node = std::make_shared();
node->declare_param();
node->get_param();
node->update_param();
node->del_param();
rclcpp::spin(node);
rclcpp::shutdown();
return 0;
}
编辑客户端
#include "rclcpp/rclcpp.hpp"
using namespace std::chrono_literals;
class ParamClient : public rclcpp::Node
{
public:
ParamClient():Node("param_client_node_cpp")
{
RCLCPP_INFO(this->get_logger(),"客户端创建");
param_client = std::make_shared(this,"param_server_node_cpp");
}
bool connect_server(){
//在指定超时时间内连接服务器,如果连接上了,那么返回true,否则返回false
// client_->wait_for_service(1s);
while(!param_client->wait_for_service(1s))
{
//对ctrl+c进行特殊处理,如果不处理对于想要提前终止客户端的操作会异常
if(!rclcpp::ok())
{
RCLCPP_INFO(rclcpp::get_logger("rclcpp"),"强行终止客户端!");
return false;
}
RCLCPP_INFO(rclcpp::get_logger("rclcpp"),"服务连接中!");
}
return true;
}
void get_param(){
std::string car = param_client->get_parameter("car_name");
double qwe = param_client->get_parameter("width");
RCLCPP_INFO(this->get_logger(),"%s",car.c_str());
RCLCPP_INFO(this->get_logger(),"%.2f",qwe);
}
void update_param(){
param_client->set_parameters({rclcpp::Parameter("width",3.00)});
double cww = param_client->get_parameter("width");
RCLCPP_INFO(this->get_logger(),"%.2f",cww);
}
private:
rclcpp::SyncParametersClient::SharedPtr param_client;
};
int main(int argc, char ** argv)
{
rclcpp::init(argc,argv);
auto node = std::make_shared();
bool feed = node->connect_server();
if (!feed)
{
RCLCPP_INFO(rclcpp::get_logger("rclcpp"),"服务器连接失败,程序退出!");
return 0;
}
node->get_param();
node->update_param();
rclcpp::shutdown();
return 0;
}