ROS2——⼿把⼿教你编写⼀个话题
ROS2——⼿把⼿教你编写⼀个话题
话题简介
ROS2将复杂的机器⼈系统拆解成许多模块节点,⽽这些节点之间则是通过⼀个⾄关重要的通道完成数据交换的,这个通道就是“话题”。
⼀个节点可以通过多个话题向外发布数据,也可以同时订阅多个其他节点发布的话题,相当于话题是⼀个多对多的订阅/发布模型。
可见,话题是节点之间实现数据传输的重要途径,也是机器⼈各个⼦系统之间交换数据的重要⽅式。
下⾯, 我们将从⼀个实例出发, ⼿把⼿写⼀段话题程序东西南北英文
案例描述
来看⼀个案例:
有⼀家KFC和⼀个饥肠辘辘的Customer.
Customer给KFC10元钱就能买到⼀个汉堡, 这个Customer饿得很快, 每秒都要吃⼀个汉堡.
KFC收到Customer发来的10元钱, 向Customer发送汉堡, 为了推销⼤鸡腿, 每5秒发布⼀条⼴告.
以上案例实际上就实现了⼀个话题, KFC和Customer是两个节点Node, 付钱/发送汉堡/发送⼴告即为发布者, 收钱/接收汉堡/收取⼴告即为订阅者.
程序实现
护理学毕业论文下⾯来编写这段程序
新建⼯作空间
mkdir -p ros2_ws/src
cd ros2_ws/src
mkdir -p: 递归创建⽬录,即使上级⽬录不存在,会按⽬录层级⾃动创建⽬录
新建功能包
ros2 pkg create customer_and_kfc --build-type ament_cmake --dependencies rclcpp std_msgs
使⽤ament_cmake作为编译类型, 并使⽤依赖rclcpp和std_msgs
在ros2_ws/src/customer_and_kfc/src下创建KFC.cpp和Customer.cpp
编写KFC节点
直接献出程序, 每句都有注释, 看不懂你打我
// rclcpp库
#include"rclcpp/rclcpp.hpp"
// 基本消息类型库
#include"std_msgs/msg/string.hpp"
#include"std_msgs/msg/u_int32.hpp"
// 这样在下⽂可以使⽤1000ms这种表⽰⽅式
using namespace std::chrono_literals;
// 占位符,下⾯会详细说
using std::placeholders::_1;竹筒倒豆子
// 创建⼀个类节点,起名叫做KFCNode,继承⾃Node,这样就能使⽤Node所有的功能了
class KFCNode :public rclcpp::Node
{
public:
// 构造函数,第⼀个参数为节点名称, 并初始化count为1
KFCNode(std::string name):Node(name),count(1)
{
// 打印KFC的⾃我介绍
// c_str()函数是string类的⼀个函数,作⽤是把string类型转化为char类型(%s要求是⼀个字符串)
RCLCPP_INFO(this->get_logger(),"⼤家好, 我是%s的服务员.",name.c_str());
// 创建发布者, 发布hamburger, 发布的消息类型为<std_msgs::msg::String>
// 格式: 发布者名字 = this->create_publisher<;要发布的话题类型>("要发布的话题名称", 通信Qos);
pub_hamburger =this->create_publisher<std_msgs::msg::String>("hamburger",10);
// 创建发布者, 发布advertiment
pub_advertiment =this->create_publisher<std_msgs::msg::String>("advertiment",10);
// 创建定时器,每5000ms发布⼀个⼴告
/
/ 格式: 定时器名字 = his->create_wall_timer(1000ms, std::bind(&定时器回调函数, this));
// 格式: 定时器名字 = his->create_wall_timer(1000ms, std::bind(&定时器回调函数, this));
advertiment_timer =this->create_wall_timer(5000ms, std::bind(&KFCNode::advertiment_timer_callback,this));
// 创建订阅者,订阅money
// 格式: 订阅者名字 = this->create_subscription<;要订阅的话题类型>("要订阅的话题名称", 通信Qos, std::bind(&订阅者回调函数, this, _1));
// std::bind()是⼲啥的呢? 举个例⼦:
// auto f = std::bind(fun, placeholders::_2, placeholders::_1, 80);
// f(60,70) 等效于 fun(70, 60, 80)
// 还记得前⽂提到的占位符吗,placeholders::_1 就是f(60,70) 中的那个参数"1"
sub_money =this->create_subscription<std_msgs::msg::UInt32>("money_of_hamburger",10, std::bi
nd(&KFCNode::money_callback,this, _1));
}
private:
// 定义⼀个汉堡售出计数器
// 在32位系统中size_t是4字节的,在64位系统中,size_t是8字节的,这样利⽤该类型可以增加程序移植性。
size_t count;
// 声明⼀个定时器
rclcpp::TimerBa::SharedPtr advertiment_timer;
// 声明⼀个发布者,⽤于发布汉堡
rclcpp::Publisher<std_msgs::msg::String>::SharedPtr pub_hamburger;
// 声明⼀个订阅者,⽤于收钱
rclcpp::Subscription<std_msgs::msg::UInt32>::SharedPtr sub_money;
// 声明⼀个发布者,⽤于发布⼴告
rclcpp::Publisher<std_msgs::msg::String>::SharedPtr pub_advertiment;
// ⼴告定时器回调函数(⽆参数)
void advertiment_timer_callback()
{
// 定义⼀个String类型的字符串, 其中字符串存在.data中, %s使⽤时别忘了使⽤.c_str()转换为char类型.
auto str_advertiment = std_msgs::msg::String();
str_advertiment.data ="⼤鸡腿降价啦";
杨千嬅RCLCPP_INFO(this->get_logger(),"KFC发布了⼀个⼴告:%s", str_advertiment.data.c_str());
pub_advertiment->publish(str_advertiment);
}
// 收钱订阅者回调函数(有参数, 参数类型跟上⾯订阅者订阅的参数类型相同, 注意要加上::SharedPtr, 因为传进来的是⼀个指针)
void money_callback(const std_msgs::msg::UInt32::SharedPtr msg)
{
// 如果收到了⼗元钱,才发布汉堡. 订阅的信息在msg->data中
if(msg->data ==10)
{
RCLCPP_INFO(this->get_logger(),"收款 %d 元", msg->data);
// 字符串流
auto str_hamburger_num = std_msgs::msg::String();
str_hamburger_num.data ="第"+ std::to_string(count++)+"个汉堡";
RCLCPP_INFO(this->get_logger(),"这是我卖出的%s", str_hamburger_num.data.c_str());
// 发布字符串流
// 发布就这么写 "发布器->publish(要发布的);", 简单吧
pub_hamburger->publish(str_hamburger_num);
}
}
};
int main(int argc,char**argv)
{
// 初始化rclcpp
rclcpp::init(argc, argv);
/
/ 产⽣⼀个KFC的节点
auto node = std::make_shared<KFCNode>("KFC");
// spin函数: ⼀旦进⼊spin函数,相当于它在⾃⼰的函数⾥⾯死循环了。只要回调函数队列⾥⾯有callback函数在,它就会马上去执⾏callback函数。如果没有
// spin函数: ⼀旦进⼊spin函数,相当于它在⾃⼰的函数⾥⾯死循环了。只要回调函数队列⾥⾯有callback函数在,它就会马上去执⾏callback函数。如果没有的话,它就会阻塞,不会占⽤CPU。注意不要再spin后⾯放其他东西, 他们都不会执⾏的
rclcpp::spin(node);
// 检测退出信号(ctrl+c)
rclcpp::shutdown();
工作要求return0;
}
编写Customer节点
此段程序与上⾯相同的语句不再解释, 请读者⾃⾏类⽐
#include"rclcpp/rclcpp.hpp"
#include"std_msgs/msg/string.hpp"
#include"std_msgs/msg/u_int32.hpp"
海日生残夜
// 这样就能使⽤1000ms这种表⽰⽅式
using namespace std::chrono_literals;
//占位符
using std::placeholders::_1;
using std::placeholders::_2;
// 创建⼀个类节点,名字叫做CustomerNode,继承⾃Node.
class CustomerNode :public rclcpp::Node
{
public:
// 构造函数,第⼀个参数为节点名称
CustomerNode(std::string name):Node(name)
{
// 打印Customer的⾃我介绍
RCLCPP_INFO(this->get_logger(),"⼤家好,我是⼀个%s.",name.c_str());
// 创建订阅者,订阅hamburger
// 占位符还记得吗? 复习⼀下, 此处的_1 表⽰const std_msgs::msg::String::SharedPtr msg
sub_hamburger =this->create_subscription<std_msgs::msg::String>("hamburger",10, std::bind(&CustomerNode::hamburger_callback,this, _1));
// 创建订阅者,订阅advertiment
sub_advertiment =this->create_subscription<std_msgs::msg::String>("advertiment",10, std::bind(&CustomerNode::advertiment_callback,this, _1));
// 创建定时器,每1000ms饿⼀次
hungry_timer =this->create_wall_timer(1000ms, std::bind(&CustomerNode::hungry_timer_callback,this));
// 创建发布者,发布money
pub_money =this->create_publisher<std_msgs::msg::UInt32>("money_of_hamburger",10);
// 给money赋值
money.data =10;
//第⼀次给钱
pub_money->publish(money);
RCLCPP_INFO(this->get_logger(),"我饿了, 我要吃汉堡! 付款 %d 元", money.data);
}
private:
// 新建⼀张钱
std_msgs::msg::UInt32 money;
// 声明⼀个定时器
rclcpp::TimerBa::SharedPtr hungry_timer;
// 声明⼀个订阅者,⽤于订阅发出的汉堡
rclcpp::Subscription<std_msgs::msg::String>::SharedPtr sub_hamburger;
// 声明⼀个发布者,⽤于给KFC钱
rclcpp::Publisher<std_msgs::msg::UInt32>::SharedPtr pub_money;
/
/ 声明⼀个订阅者,⽤于订阅⼴告
rclcpp::Subscription<std_msgs::msg::String>::SharedPtr sub_advertiment;
// 汉堡订阅者回调函数
void hamburger_callback(const std_msgs::msg::String::SharedPtr msg)
{
RCLCPP_INFO(this->get_logger(),"这是我吃的 %s ", msg->data.c_str());
}
人教版初一数学// 饥饿定时器回调函数
void hungry_timer_callback()
{
RCLCPP_INFO(this->get_logger(),"我⼜饿了, 还想再吃⼀个! 付款 %d 元", money.data);
日出英文pub_money->publish(money);
}
// ⼴告订阅者回调函数
void advertiment_callback(const std_msgs::msg::String::SharedPtr msg)
{
RCLCPP_INFO(this->get_logger(),"我收到了⼀条⼴告: %s ", msg->data.c_str());
}
};
int main(int argc,char**argv)
{
//初始化rclcpp
rclcpp::init(argc, argv);
//产⽣⼀个Customer的节点
auto node = std::make_shared<CustomerNode>("Customer");
//运⾏节点,并检测退出信号
rclcpp::spin(node);
rclcpp::shutdown();
return0;
}
<
如果新建功能包的时候没有加--dependencies rclcpp std_msgs等功能包, 则需要⼿动添加: (任意位置均可)
find_package(rclcpp REQUIRED)
find_package(std_msgs REQUIRED)
下⾯两对代码的作⽤是:
add_executable() 让编译器编译Customer.cpp和KFC.cpp这两个⽂件. 并⽣成可执⾏⽂件Customer_node和KFC_node ament_target_dependencies 添加编译的依赖
add_executable(Customer_node src/Customer.cpp)
ament_target_dependencies(Customer_node rclcpp std_msgs)
add_executable(KFC_node src/KFC.cpp)
ament_target_dependencies(KFC_node rclcpp std_msgs)
将编译好的⽂件安装到install/customer_and_kfc/lib/customer_and_kfc下