一、准备工作
1.请先创建工作空间
mkdir -p ws01_plumbing/src //创建工作空间
colcon build //在工作空间目录下编译
2.创建专门的接口功能包定义接口文件(需要注意的是,目前为止无法在Python功能包中定义接口文件),终端下进入工作空间的src
目录,执行如下命令:
ros2 pkg create --build-type ament_cmake base_interfaces_demo //该功能包将用于保存自定义的接口文件
3.终端下进入工作空间的src目录,调用如下两条命令分别创建C++功能包和Python功能包。
ros2 pkg create cpp01_topic --build-type ament_cmake --dependencies rclcpp std_msgs base_interfaces_demo
ros2 pkg create py01_topic --build-type ament_python --dependencies rclpy std_msgs base_interfaces_demo
二、话题通信之原生消息(C++)
1.发布方实现
功能包cpp01_topic的src目录下,新建C++文件demo01_talker_str.cpp.
/*
需求:以某个固定频率发送文本“hello world!”,文本后缀编号,每发送一条消息,编号递增1。
步骤:
1.包含头文件;
2.初始化 ROS2 客户端;
3.定义节点类;
3-1.创建发布方;
3-2.创建定时器;
3-3.组织消息并发布。
4.调用spin函数,并传入节点对象指针;
5.释放资源。
*/
// 1.包含头文件;
#include "rclcpp/rclcpp.hpp"
#include "std_msgs/msg/string.hpp"
using namespace std::chrono_literals;
// 3.定义节点类;
class MinimalPublisher : public rclcpp::Node
{
public:
MinimalPublisher()
: Node("minimal_publisher"), count_(0)
{
// 3-1.创建发布方;
publisher_ = this->create_publisher<std_msgs::msg::String>("topic", 10);
// 3-2.创建定时器;
timer_ = this->create_wall_timer(500ms, std::bind(&MinimalPublisher::timer_callback, this));
}
private:
void timer_callback()
{
// 3-3.组织消息并发布。
auto message = std_msgs::msg::String();
message.data = "Hello, world! " + std::to_string(count_++);
RCLCPP_INFO(this->get_logger(), "发布的消息:'%s'", message.data.c_str());
publisher_->publish(message);
}
rclcpp::TimerBase::SharedPtr timer_;
rclcpp::Publisher<std_msgs::msg::String>::SharedPtr publisher_;
size_t count_;
};
int main(int argc, char * argv[])
{
// 2.初始化 ROS2 客户端;
rclcpp::init(argc, argv);
// 4.调用spin函数,并传入节点对象指针。
rclcpp::spin(std::make_shared<MinimalPublisher>());
// 5.释放资源;
rclcpp::shutdown();
return 0;
}
2.订阅方实现
功能包cpp01_topic的src目录下,新建C++文件demo02_listener_str.cpp.
/*
需求:订阅发布方发布的消息,并输出到终端。
步骤:
1.包含头文件;
2.初始化 ROS2 客户端;
3.定义节点类;
3-1.创建订阅方;
3-2.处理订阅到的消息。
4.调用spin函数,并传入节点对象指针;
5.释放资源。
*/
// 1.包含头文件;
#include "rclcpp/rclcpp.hpp"
#include "std_msgs/msg/string.hpp"
using std::placeholders::_1;
// 3.定义节点类;
class MinimalSubscriber : public rclcpp::Node
{
public:
MinimalSubscriber()
: Node("minimal_subscriber")
{
// 3-1.创建订阅方;
subscription_ = this->create_subscription<std_msgs::msg::String>("topic", 10, std::bind(&MinimalSubscriber::topic_callback, this, _1));
}
private:
// 3-2.处理订阅到的消息;
void topic_callback(const std_msgs::msg::String & msg) const
{
RCLCPP_INFO(this->get_logger(), "订阅的消息: '%s'", msg.data.c_str());
}
rclcpp::Subscription<std_msgs::msg::String>::SharedPtr subscription_;
};
int main(int argc, char * argv[])
{
// 2.初始化 ROS2 客户端;
rclcpp::init(argc, argv);
// 4.调用spin函数,并传入节点对象指针。
rclcpp::spin(std::make_shared<MinimalSubscriber>());
// 5.释放资源;
rclcpp::shutdown();
return 0;
}
3.编辑配置文件
在C++功能包中,配置文件主要关注package.xml与CMakeLists.txt。
1.package.xml
在创建功能包时,所依赖的功能包已经自动配置了,配置内容如下:
<depend>rclcpp</depend>
<depend>std_msgs</depend>
<depend>base_interfaces_demo</depend> //在本案例中不是必须的
2.CMakeLists.txt
CMakeLists.txt中发布和订阅程序核心配置如下:
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)
ament_target_dependencies(
demo01_talker_str
"rclcpp"
"std_msgs"
)
add_executable(demo02_listener_str src/demo02_listener_str.cpp)
ament_target_dependencies(
demo02_listener_str
"rclcpp"
"std_msgs"
)
install(TARGETS
demo01_talker_str
demo02_listener_str
DESTINATION lib/${PROJECT_NAME})
4.编译
终端中进入当前工作空间,编译功能包:
colcon build --packages-select cpp01_topic
5.执行
当前工作空间下,启动两个终端,终端1执行发布程序,终端2执行订阅程序。
终端1输入如下指令:
. install/setup.bash
ros2 run cpp01_topic demo01_talker_str
终端2输入如下指令:
. install/setup.bash
ros2 run cpp01_topic demo02_listener_str
三、话题通信之原生消息(Python)
1.发布方实现
功能包py01_topic的py01_topic目录下,新建Python文件demo01_talker_str_py.py.
"""
需求:以某个固定频率发送文本“hello world!”,文本后缀编号,每发送一条消息,编号递增1。
步骤:
1.导包;
2.初始化 ROS2 客户端;
3.定义节点类;
3-1.创建发布方;
3-2.创建定时器;
3-3.组织消息并发布。
4.调用spin函数,并传入节点对象;
5.释放资源。
"""
# 1.导包;
import rclpy
from rclpy.node import Node
from std_msgs.msg import String
# 3.定义节点类;
class MinimalPublisher(Node):
def __init__(self):
super().__init__('minimal_publisher_py')
# 3-1.创建发布方;
self.publisher_ = self.create_publisher(String, 'topic', 10)
# 3-2.创建定时器;
timer_period = 0.5
self.timer = self.create_timer(timer_period, self.timer_callback)
self.i = 0
# 3-3.组织消息并发布。
def timer_callback(self):
msg = String()
msg.data = 'Hello World(py): %d' % self.i
self.publisher_.publish(msg)
self.get_logger().info('发布的消息: "%s"' % msg.data)
self.i += 1
def main(args=None):
# 2.初始化 ROS2 客户端;
rclpy.init(args=args)
# 4.调用spin函数,并传入节点对象;
minimal_publisher = MinimalPublisher()
rclpy.spin(minimal_publisher)
# 5.释放资源。
rclpy.shutdown()
if __name__ == '__main__':
main()
2.订阅方实现
功能包py01_topic的py01_topic目录下,新建Python文件demo02_listener_str_py.py.
"""
需求:订阅发布方发布的消息,并输出到终端。
步骤:
1.导包;
2.初始化 ROS2 客户端;
3.定义节点类;
3-1.创建订阅方;
3-2.处理订阅到的消息。
4.调用spin函数,并传入节点对象;
5.释放资源。
"""
# 1.导包;
import rclpy
from rclpy.node import Node
from std_msgs.msg import String
# 3.定义节点类;
class MinimalSubscriber(Node):
def __init__(self):
super().__init__('minimal_subscriber_py')
# 3-1.创建订阅方;
self.subscription = self.create_subscription(
String,
'topic',
self.listener_callback,
10)
self.subscription
# 3-2.处理订阅到的消息。
def listener_callback(self, msg):
self.get_logger().info('订阅的消息: "%s"' % msg.data)
def main(args=None):
# 2.初始化 ROS2 客户端;
rclpy.init(args=args)
# 4.调用spin函数,并传入节点对象;
minimal_subscriber = MinimalSubscriber()
rclpy.spin(minimal_subscriber)
# 5.释放资源。
rclpy.shutdown()
if __name__ == '__main__':
main()
3.编辑配置文件
在Python功能包中,配置文件主要关注package.xml与setup.py。
1.package.xml
在创建功能包时,所依赖的功能包已经自动配置了,配置内容如下:
<depend>rclcpp</depend>
<depend>std_msgs</depend>
<depend>base_interfaces_demo</depend>
2.setup.py
entry_points
字段的console_scripts
中添加如下内容:
entry_points={
'console_scripts': [
'demo01_talker_str_py = py01_topic.demo01_talker_str_py:main',
'demo02_listener_str_py = py01_topic.demo02_listener_str_py:main'
],
},
4.编译
终端中进入当前工作空间,编译功能包:
colcon build --packages-select py01_topic
5.执行
当前工作空间下,启动两个终端,终端1执行发布程序,终端2执行订阅程序。
终端1输入如下指令:
. install/setup.bash
ros2 run py01_topic demo01_talker_str_py
终端2输入如下指令:
. install/setup.bash
ros2 run py01_topic demo02_listener_str_py
四、话题通信自定义接口消息
自定义接口消息的流程与在功能包中编写可执行程序的流程类似,主要步骤如下:
1.创建并编辑
.msg
文件;2.编辑配置文件;
3.编译;
4.测试。
接下来,我们可以自定义一个文件,该文件中包含学生的姓名、年龄、身高等字段。
1.创建并编辑 .msg 文件
功能包base_interfaces_demo下新建 msg 文件夹,msg文件夹下新建Student.msg文件
string name
int32 age
float64 height
2.编辑配置文件
1.package.xml文件
在package.xml中需要添加一些依赖包,具体内容如下:
<build_depend>rosidl_default_generators</build_depend>
<exec_depend>rosidl_default_runtime</exec_depend>
<member_of_group>rosidl_interface_packages</member_of_group>
2.CMakeLists.txt文件
为了将.msg
文件转换成对应的C++和Python代码,还需要在CMakeLists.txt中添加如下配置:
find_package(rosidl_default_generators REQUIRED)
rosidl_generate_interfaces(${PROJECT_NAME}
"msg/Student.msg"
)
3.编译
终端中进入当前工作空间,编译功能包:
colcon build --packages-select base_interfaces_demo
4.测试
编译完成之后,在工作空间下的install目录下将生成Student.msg
文件对应的C++和Python文件,我们也可以在终端下进入工作空间,通过如下命令查看文件定义以及编译是否正常:
. install/setup.bash
ros2 interface show base_interfaces_demo/msg/Student
正常情况下,终端将会输出与Student.msg
文件一致的内容。
五、话题通信之自定义消息(C++)
准备工作
C++文件中包含自定义消息相关头文件时,可能会抛出异常,可以配置VSCode中c_cpp_properties.json文件,在文件中的 includePath属性下添加一行:
"${workspaceFolder}/install/base_interfaces_demo/include/**"
添加完毕后,包含相关头文件时,就不会抛出异常了,其他接口文件或接口包的使用也与此同理。
1.发布方实现
功能包cpp01_topic的src目录下,新建C++文件demo01_talker_stu.cpp.
/*
需求:以某个固定频率发送文本学生信息,包含学生的姓名、年龄、身高等数据。
*/
// 1.包含头文件;
#include "rclcpp/rclcpp.hpp"
#include "base_interfaces_demo/msg/student.hpp"
using namespace std::chrono_literals;
using base_interfaces_demo::msg::Student;
// 3.定义节点类;
class MinimalPublisher : public rclcpp::Node
{
public:
MinimalPublisher()
: Node("student_publisher"), count_(0)
{
// 3-1.创建发布方;
publisher_ = this->create_publisher<Student>("topic_stu", 10);
// 3-2.创建定时器;
timer_ = this->create_wall_timer(500ms, std::bind(&MinimalPublisher::timer_callback, this));
}
private:
void timer_callback()
{
// 3-3.组织消息并发布。
auto stu = Student();
stu.name = "张三";
stu.age = count_++;
stu.height = 1.65;
RCLCPP_INFO(this->get_logger(), "学生信息:name=%s,age=%d,height=%.2f", stu.name.c_str(),stu.age,stu.height);
publisher_->publish(stu);
}
rclcpp::TimerBase::SharedPtr timer_;
rclcpp::Publisher<Student>::SharedPtr publisher_;
size_t count_;
};
int main(int argc, char * argv[])
{
// 2.初始化 ROS2 客户端;
rclcpp::init(argc, argv);
// 4.调用spin函数,并传入节点对象指针。
rclcpp::spin(std::make_shared<MinimalPublisher>());
// 5.释放资源;
rclcpp::shutdown();
return 0;
}
2.订阅方实现
功能包cpp01_topic的src目录下,新建C++文件demo04_listener_stu.cpp.
/*
需求:订阅发布方发布的学生消息,并输出到终端。
*/
// 1.包含头文件;
#include "rclcpp/rclcpp.hpp"
#include "base_interfaces_demo/msg/student.hpp"
using std::placeholders::_1;
using base_interfaces_demo::msg::Student;
// 3.定义节点类;
class MinimalSubscriber : public rclcpp::Node
{
public:
MinimalSubscriber()
: Node("student_subscriber")
{
// 3-1.创建订阅方;
subscription_ = this->create_subscription<Student>("topic_stu", 10, std::bind(&MinimalSubscriber::topic_callback, this, _1));
}
private:
// 3-2.处理订阅到的消息;
void topic_callback(const Student & msg) const
{
RCLCPP_INFO(this->get_logger(), "订阅的学生消息:name=%s,age=%d,height=%.2f", msg.name.c_str(),msg.age, msg.height);
}
rclcpp::Subscription<Student>::SharedPtr subscription_;
};
int main(int argc, char * argv[])
{
// 2.初始化 ROS2 客户端;
rclcpp::init(argc, argv);
// 4.调用spin函数,并传入节点对象指针。
rclcpp::spin(std::make_shared<MinimalSubscriber>());
// 5.释放资源;
rclcpp::shutdown();
return 0;
}
3.编辑配置文件
package.xml无需修改,CMakeLists.txt文件需要添加如下内容:
add_executable(demo03_talker_stu src/demo03_talker_stu.cpp)
ament_target_dependencies(
demo03_talker_stu
"rclcpp"
"std_msgs"
"base_interfaces_demo"
)
add_executable(demo04_listener_stu src/demo04_listener_stu.cpp)
ament_target_dependencies(
demo04_listener_stu
"rclcpp"
"std_msgs"
"base_interfaces_demo"
)
文件中install修改为如下内容:
install(TARGETS
demo01_talker_str
demo02_listener_str
demo03_talker_stu
demo04_listener_stu
DESTINATION lib/${PROJECT_NAME})
4.编译
终端中进入当前工作空间,编译功能包:
colcon build --packages-select cpp01_topic
5.执行
当前工作空间下,启动两个终端,终端1执行发布程序,终端2执行订阅程序。
终端1输入如下指令:
. install/setup.bash
ros2 run cpp01_topic demo03_talker_stu
终端2输入如下指令:
. install/setup.bash
ros2 run cpp01_topic demo04_listener_stu
六、话题通信之自定义消息(Python)
准备工作
Python文件中导入自定义消息相关的包时,为了方便使用,可以配置VSCode中settings.json文件,在文件中的python.autoComplete.extraPaths和python.analysis.extraPaths属性下添加一行:
"${workspaceFolder}/install/base_interfaces_demo/local/lib/python3.10/dist-packages"
添加完毕后,代码可以高亮显示且可以自动补齐,其他接口文件或接口包的使用也与此同理。
1.发布方实现
功能包py01_topic的py01_topic目录下,新建Python文件demo03_talker_stu_py.py.
"""
需求:以某个固定频率发送文本学生信息,包含学生的姓名、年龄、身高等数据。
"""
# 1.导包;
import rclpy
from rclpy.node import Node
from base_interfaces_demo.msg import Student
# 3.定义节点类;
class MinimalPublisher(Node):
def __init__(self):
super().__init__('stu_publisher_py')
# 3-1.创建发布方;
self.publisher_ = self.create_publisher(Student, 'topic_stu', 10)
# 3-2.创建定时器;
timer_period = 0.5
self.timer = self.create_timer(timer_period, self.timer_callback)
self.i = 0
# 3-3.组织消息并发布。
def timer_callback(self):
stu = Student()
stu.name = "李四"
stu.age = self.i
stu.height = 1.70
self.publisher_.publish(stu)
self.get_logger().info('发布的学生消息(py): name=%s,age=%d,height=%.2f' % (stu.name, stu.age, stu.height))
self.i += 1
def main(args=None):
# 2.初始化 ROS2 客户端;
rclpy.init(args=args)
# 4.调用spin函数,并传入节点对象;
minimal_publisher = MinimalPublisher()
rclpy.spin(minimal_publisher)
# 5.释放资源。
rclpy.shutdown()
if __name__ == '__main__':
main()
2.订阅方实现
功能包py01_topic的py01_topic目录下,新建Python文件demo04_listener_stu_py.py.
"""
需求:订阅发布方发布的学生消息,并输出到终端。
"""
# 1.导包;
import rclpy
from rclpy.node import Node
from base_interfaces_demo.msg import Student
# 3.定义节点类;
class MinimalSubscriber(Node):
def __init__(self):
super().__init__('stu_subscriber_py')
# 3-1.创建订阅方;
self.subscription = self.create_subscription(
Student,
'topic_stu',
self.listener_callback,
10)
self.subscription
# 3-2.处理订阅到的消息。
def listener_callback(self, stu):
self.get_logger().info('订阅的消息(py): name=%s,age=%d,height=%.2f' % (stu.name, stu.age, stu.height))
def main(args=None):
# 2.初始化 ROS2 客户端;
rclpy.init(args=args)
# 4.调用spin函数,并传入节点对象;
minimal_subscriber = MinimalSubscriber()
rclpy.spin(minimal_subscriber)
# 5.释放资源。
rclpy.shutdown()
if __name__ == '__main__':
main()
3.编辑配置文件
package.xml无需修改,需要修改setup.py文件,entry_points
字段的console_scripts
中修改为如下内容:
entry_points={
'console_scripts': [
'demo01_talker_str_py = py01_topic.demo01_talker_str_py:main',
'demo02_listener_str_py = py01_topic.demo02_listener_str_py:main',
'demo03_talker_stu_py = py01_topic.demo03_talker_stu_py:main',
'demo04_listener_stu_py = py01_topic.demo04_listener_stu_py:main'
],
},
4.编译
终端中进入当前工作空间,编译功能包:
colcon build --packages-select py01_topic
5.执行
当前工作空间下,启动两个终端,终端1执行发布程序,终端2执行订阅程序。
终端1输入如下指令:
. install/setup.bash
ros2 run py01_topic demo03_talker_stu_py
终端2输入如下指令:
. install/setup.bash
ros2 run py01_topic demo04_listener_stu_py
下期服务通信见!