1.创建工作空间
先创建工作空间ws01_plumbing,
终端下进入工作空间的src
目录,执行如下命令:
ros2 pkg create --build-type ament_cmake base_interfaces_demo
2.话题通信
话题通信是ROS中使用频率最高的一种通信模式,话题通信是基于发布订阅模式的,也即:一个节点发布消息,另一个节点订阅该消息。
话题通信是一种以发布订阅的方式实现不同节点之间数据传输的通信模型。数据发布对象称为发布方,数据订阅对象称之为订阅方,发布方和订阅方通过话题相关联,发布方将消息发布在话题上,订阅方则从该话题订阅消息,消息的流向是单向的。
话题通信的发布方与订阅方是一种多对多的关系,也即,同一话题下可以存在多个发布方,也可以存在多个订阅方,这意味着数据会出现交叉传输的情况,当然如果没有订阅方,数据传输也会出现丢失的情况。
2.1话题通信案例
终端下进入工作空间的src目录,调用如下两条命令分别创建C++功能包和Python功能包。
ros2 pkg create cpp01_topic --build-type ament_cmake --dependencies rclcpp std_msgs base_interfaces_demo --node-name demo01_talker_str
ros2 pkg create py01_topic --build-type ament_python --dependencies rclpy std_msgs base_interfaces_demo --node-name demo01_talker_str_py
2.1.1 发送方案例(C++)
/* 需求:订阅发布方发布的消息,并输出到终端。步骤:1.包含头文件;2.初始化 ROS2 客户端;3.定义节点类;3-1.创建订阅方;3-2.定时器。3-3.组织并发布消息4.调用spin函数,并传入节点对象指针;5.释放资源。
*/#include "rclcpp/rclcpp.hpp"
#include "std_msgs/msg/string.hpp"using namespace std::chrono_literals; //使用chrono库中的时间单位//自定义节点类
class Talker:public rclcpp::Node{public:Talker():Node("Talker_Node_cpp"),count(0){//初始化count=0RCLCPP_INFO(this->get_logger(),"发布节点");// 3-1.创建订阅方;publisher_=this->create_publisher<std_msgs::msg::String>("chatter",10);//创建一个名为"chater"的主题,队列长度为10// 3-2.定时器。// 参数:时间间隔,毁掉函数,返回值:当前对象指针timer_=this->create_wall_timer(1s,std::bind(&Talker::on_timer,this));//创建一个定时器,每隔1秒调用一次on_timer函数}private:size_t count;//定义一个计数器,用于记录发布的消息数量void on_timer(){// 3-3.组织并发布消息auto msg=std_msgs::msg::String();//创建一个String类型的消息对象msg.data="Hello ROS2 C++! "+std::to_string(count++);//给消息对象赋值RCLCPP_INFO(this->get_logger(),"发布消息:%s",msg.data.c_str());//输出日志信息publisher_->publish(msg);}rclcpp::Publisher<std_msgs::msg::String>::SharedPtr publisher_;//定义一个发布者指针,类型为std_msgs::msg::Stringrclcpp::TimerBase::SharedPtr timer_;//定义一个定时器指针
};int main(int argc,char** argv){//初始化rclcpp::init(argc,argv);//调用spin,挂起当前rclcpp::spin(std::make_shared<Talker>());//释放rclcpp::shutdown();
}
在终端运行
colcon build --packages-select cpp01_topic
. install/setup.bash
ros2 run cpp01_topic demo01_talker_str
可以另外一个终端去验证chatter主题下是否有内容
. install/setup.bash
ros2 topic echo /chatter
2.1.2 订阅方案例(C++)
/* 需求:订阅发布方发布的消息,并输出到终端。步骤:1.包含头文件;2.初始化 ROS2 客户端;3.定义节点类;3-1.创建订阅方;3-2.定时器。3-3.组织并发布消息4.调用spin函数,并传入节点对象指针;5.释放资源。
*/#include "rclcpp/rclcpp.hpp"
#include "std_msgs/msg/string.hpp"using namespace std::chrono_literals; //使用chrono库中的时间单位//自定义节点类
class Talker:public rclcpp::Node{public:Talker():Node("Talker_Node_cpp"),count(0){//初始化count=0RCLCPP_INFO(this->get_logger(),"发布节点");// 3-1.创建订阅方;publisher_=this->create_publisher<std_msgs::msg::String>("chatter",10);//创建一个名为"chater"的主题,队列长度为10// 3-2.定时器。// 参数:时间间隔,毁掉函数,返回值:当前对象指针timer_=this->create_wall_timer(1s,std::bind(&Talker::on_timer,this));//创建一个定时器,每隔1秒调用一次on_timer函数}private:size_t count;//定义一个计数器,用于记录发布的消息数量void on_timer(){// 3-3.组织并发布消息auto msg=std_msgs::msg::String();//创建一个String类型的消息对象msg.data="Hello ROS2 C++! "+std::to_string(count++);//给消息对象赋值RCLCPP_INFO(this->get_logger(),"发布消息:%s",msg.data.c_str());//输出日志信息publisher_->publish(msg);}rclcpp::Publisher<std_msgs::msg::String>::SharedPtr publisher_;//定义一个发布者指针,类型为std_msgs::msg::Stringrclcpp::TimerBase::SharedPtr timer_;//定义一个定时器指针
};int main(int argc,char** argv){//初始化rclcpp::init(argc,argv);//调用spin,挂起当前rclcpp::spin(std::make_shared<Talker>());//释放rclcpp::shutdown();
}
需要去CMakeLists.txt三处添加demo02_listener_str
在终端运行(要先启动发送方,订阅方才有信息)
colcon build --packages-select cpp01_topic
. install/setup.bash
ros2 run cpp01_topic demo02_listener_str
2.1.3 发送方案例(python)
# 需求:订阅发布方发布的消息,并输出到终端。# 步骤:# 1.包含头文件;# 2.初始化 ROS2 客户端;# 3.定义节点类;# 3-1.创建订阅方;# 3-2.定时器。# 3-3.组织并发布消息# 4.调用spin函数,并传入节点对象指针;# 5.释放资源。import rclpy
from rclpy.node import Node
from std_msgs.msg import String# 定义节点类
class Puublisher(Node):def __init__(self):super().__init__('talker_node_py')self.get_logger().info('发布节点')# 3-1.创建发布方self.publisher_ = self.create_publisher(String, 'chatter', 10)# 3-2.定时器self.timer = self.create_timer(1.0, self.on_timer)self.count = 0def on_timer(self):# 3-3.组织并发布消息msg = String()msg.data = f"Hello ROS2 Python! {self.count}"self.get_logger().info(f'发布消息:{msg.data}')self.publisher_.publish(msg)self.count += 1def main():# 初始化 ROS2 客户端rclpy.init()# 创建节点对象node = Puublisher()# 调用 spin 函数,传入节点对象指针rclpy.spin(node)# 释放资源rclpy.shutdown()if __name__ == '__main__':main()
2.1.4 订阅方案例(python)
# 需求:订阅发布方发布的消息,并在终端输出# 流程:# 1.头文件# 2.初始化客户端# 3.自定义节点类# 3-1.创建订阅方# 3-2.解析并输出数据# 4.调用spin函数,传入节点对象指针# 5.释放资源import rclpy
from rclpy.node import Node
from std_msgs.msg import String# 自定义节点类
class Subscriber(Node):def __init__(self):super().__init__('listener_node_py')# 创建订阅方self.subscription=self.create_subscription(String,"chatter",self.listen_callback,10,)self.subscription # 订阅对象指针#处理订阅到的消息def listen_callback(self, msg):# 解析并输出数据self.get_logger().info(f'订阅到消息:{msg.data}') def main(args=None):# 初始化 ROS2 客户端rclpy.init(args=args)# 创建节点对象node = Subscriber()# 调用 spin 函数,传入节点对象指针rclpy.spin(node)# 释放资源rclpy.shutdown()if __name__ == '__main__':main()
订阅方创建好之后需要到setup.py文件下修改entry_points参数,将demo02加进去:
然后编译运行
colcon build --packages-select py01_topic
. install/setup.bash
ros2 run py01_topic demo01_talker_str_py//再重开一个终端运行
. install/setup.bash
ros2 run py01_topic demo02_listener_str_py
2.1.5 自定义接口消息
创建并编辑 .msg 文件
功能包base_interfaces_demo下新建 msg 文件夹,msg文件夹下新建Student.msg文件,文件中输入如下内容:
string name
int32 age
float64 height
在package.xml文件添加
如果有报错的直接删掉
在CMakeList.txt添加
在终端编译测试
colcon build --packages-select base_interfaces_demo. install/setup.bash
ros2 interface show base_interfaces_demo/msg/Student
输出内容和Student.msg一致
2.1.6 自定义发布方(C++)
/*需求:以固定频率发布学生信息。步骤:1.包含头文件;2.初始化 ROS2 客户端;3.定义节点类;3-1创建发布方3-2创建定时器3-3组织并发布消息4.调用spin函数,并传入节点对象指针;5.释放资源。
*/
#include <rclcpp/rclcpp.hpp>
#include "std_msgs/msg/string.hpp"
#include "base_interfaces_demo/msg/student.hpp" // 引入自定义消息类型using base_interfaces_demo::msg::Student; // 使用自定义消息类型的命名空间
using namespace std::chrono_literals; // 使用 chrono 的时间单位
// 自定义节点类
class TalkStu : public rclcpp::Node
{
public:TalkStu() : Node("TalkStu_node_cpp"){// 3-1创建发布方publisher_=this->create_publisher<Student>("chat_stu", 10); // 创建一个名为 "chat_stu" 的发布者,队列长度为 10// 3-2创建定时器timer_=this->create_wall_timer(1s,std::bind(&TalkStu::timer_callback, this)); // 每秒调用一次 timer_callback 函数 }private:int age=0;void timer_callback(){// 3-3组织并发布消息auto stu=Student();stu.name="张三";stu.age=age++;stu.height=1.75;age++;publisher_->publish(stu); // 发布消息RCLCPP_INFO(this->get_logger(), "发布学生信息: name=%s,age=%d,height=%.2f)",stu.name.c_str(), stu.age, stu.height);};rclcpp::Publisher<Student>::SharedPtr publisher_; // 定义发布者指针rclcpp::TimerBase::SharedPtr timer_; // 定义定时器指针};int main(int argc, char const *argv[])
{// 初始化rclcpp::init(argc, argv);// 调用spin,挂起当前rclcpp::spin(std::make_shared<TalkStu>());// 释放rclcpp::shutdown();return 0;
}
package.xml无需更改,需要在CMakeList.txt文件下添加这两个文件
2.1.7 自定义订阅方(C++)
/* 需求:订阅发布方发布的消息,并输出到终端。步骤:1.包含头文件;2.初始化 ROS2 客户端;3.定义节点类;3-1创建订阅方;3-2创建回调函数;订阅并解析数据4.调用spin函数,并传入节点对象指针;5.释放资源。
*/
#include <rclcpp/rclcpp.hpp>
#include "std_msgs/msg/string.hpp"
#include "base_interfaces_demo/msg/student.hpp"using base_interfaces_demo::msg::Student; // 使用自定义消息类型的命名空间//自定义节点类
class ListenerStu:public rclcpp::Node{
public:ListenerStu():Node("ListenerStu_node_cpp"){// 3-1创建订阅方;subscription_=this->create_subscription<Student>("chat_stu",10,std::bind(&ListenerStu::topic_callback,this,std::placeholders::_1));}
private:void topic_callback(const Student &stu){// 3-2创建回调函数;订阅并解析数据RCLCPP_INFO(this->get_logger(),"订阅信息: name=%s, age=%d, height=%.2f",stu.name.c_str(), stu.age, stu.height);};rclcpp::Subscription<Student>::SharedPtr subscription_; // 定义订阅者指针
};int main(int argc,char const *argv[]){//初始化rclcpp::init(argc,argv);//调用spin,挂起当前rclcpp::spin(std::make_shared<ListenerStu>());//释放rclcpp::shutdown();
}
在两个终端分别运行
colcon build --packages-select cpp01_topic
. install/setup.bash
ros2 run cpp01_topic demo03_talker_stu. install/setup.bash
ros2 run cpp01_topic demo04_listener_stu
2.1.8 自定义发布方(python)
"""
流程:1. 导包2. 初始化ROS2客户端3. 自定义节点类3-1. 创建发布者/订阅者3-2. 处理数据(回调函数)4. 调用 spin 函数,传入节点对象5. 资源释放
"""import rclpy
from rclpy.node import Node
from base_interfaces_demo.msg import Student # 可根据需要替换为其他消息类型class TalkerStu(Node):def __init__(self):super().__init__('my_node_py')self.get_logger().info("节点已启动:my_node_py")# 3-1. 创建发布者(可选)self.publisher_ = self.create_publisher(Student, 'chat_stu', 10)self.timer = self.create_timer(0.5, self.timer_callback) # 每0.5秒发布一次self.count=0# 3-2. 处理数据def timer_callback(self):msg = Student()msg.name = '张三'msg.age = self.countmsg.height=1.72self.publisher_.publish(msg)self.count+=1self.get_logger().info('发布消息: name=%s,age=%d,height=%.2f' % (msg.name, msg.age, msg.height))def main(args=None):# 2. 初始化ROS2客户端rclpy.init(args=args)# 3. 创建节点对象node = TalkerStu()# 4. 调用 spin,传入节点对象rclpy.spin(node)# 5. 释放资源rclpy.shutdown()if __name__ == '__main__':main()
from base_interfaces_demo.msg import Student # 可根据需要替换为其他消息类型
这一段需要crtl+shift+p搜索dettings.json
然后在文件种添加
{"python.autoComplete.extraPaths": ["/home/sun/ws01_pluming/install/base_interfaces_demo/local/lib/python3.10/dist-packages",],"python.analysis.extraPaths": ["/home/sun/ws01_pluming/install/base_interfaces_demo/local/lib/python3.10/dist-packages",]
}
然后Student才会生效。
之后进入到setup.py文件下添加
2.1.9 自定义订阅放(python)
"""
流程:1. 导包2. 初始化ROS2客户端3. 自定义节点类3-1. 创建发布者/订阅者3-2. 处理数据(回调函数)4. 调用 spin 函数,传入节点对象5. 资源释放
"""import rclpy
from rclpy.node import Node
from base_interfaces_demo.msg import Student # 可根据需要替换为其他消息类型class ListenerStu(Node):def __init__(self):super().__init__('my_node_py')self.get_logger().info('节点已启动:my_node_py')# 3-1. 创建订阅者(可选)self.subscription = self.create_subscription(Student,'chat_stu',self.listener_callback,10)self.subscription # 防止未使用警告# 3-2. 处理数据def listener_callback(self, msg):self.get_logger().info('收到消息: name=%s,age=%d,height=%.2f' % (msg.name, msg.age, msg.height))def main(args=None):# 2. 初始化ROS2客户端rclpy.init(args=args)# 3. 创建节点对象node = ListenerStu()# 4. 调用 spin,传入节点对象rclpy.spin(node)# 5. 释放资源rclpy.shutdown()if __name__ == '__main__':main()
然后在终端运行
colcon build --packages-select py01_topic
. install/setup.bash
ros2 run py03_topic demo03_talker_stu_py. install/setup.bash
ros2 run py04_topic demo04_listener_stu_py
2.1.10 同一个订阅主题下不同程序的发送和订阅
rqt可视化
新开一个终端输入rqt,在打开的窗口以此plugins->intrispection->node graph