前言
学习ROS2之前要先学会安装ROS2的运行环境,ROS2需要安装Ubuntu系统,学会windos系统与Ubuntu系统的转换,如何区安装Ubuntu系统可以在b站上找视频,注意每个人的电脑配置都不一样,这会导致进入bios界面时会不一样,但是做法都是大致相同的。
安装完Ubuntu系统后我们进入Ubuntu系统,然后开始安装ROS2 这里我使用的是来自于鱼香ROS机器人的一件安装
Ctrl+Alt+T打开终端,然后输入
wget http://fishros.com/install -O fishros && . fishros
之后输入电脑密码,然后根据你的需求去安装。安装完ROS2后我们需要安装一个VS Code,再在里面下载一些插件,像C++,python之类的
一.ROS2初体验
ROS2之中含有很多的软件库和工具集
首先体验一个你听我说,一个人为发布者,另一个人为倾听者
启动发布者:Ctrl+Alt+T打开终端,然后输入ros2 run demo_nodes_py listener
启动倾听者:Ctrl+Alt+T打开终端,然后输入ros2 run demo_nodes_cpp talker
这样倾听者就能够受到发布者的信息了
在ROS2之中还有很多这种库存在,例如还有海龟模拟器,这些都是ROS2已经编辑好的文件,而我学习ROS2是学会编辑这些文件代码,并使用它。
二.ROS2节点
1.在ros2中每一个节点负责一个单独的模块化的功能
2.节点与节点之间是需要通信的,在ROS2中拥有4种通讯方式:话题(topics)
服务(services)
动作(Action)
参数(parameters)
3.启动节点的指令:
ros2 run <package_name> <executable_name>
意义:启动包下的节点
4.ROS2命令行
首先有两个概念要明确:CLI和GUI
CLI(Command-Line Interface)就是命令行界面了,我们所用的终端,黑框框就是命令行界面,没有图形化。
GUI(Graphical User Interface)就是平常我们说的图形用户界面,大家用的Windows是就是可视化的,我们可以通过鼠标点击按钮等图形化交互完成任务
ros2是属于CLI是通过指令去进行操作的
4.节点相关的CLI
这里列出几个常用的指令
运行节点
ros2 run <package_name> <executable_name>
查看节点列表
ros2 node list
查看节点信息
ros2 node info <node_name>
重映射节点名称
ros2 run turtlesim turtlesim_node --ros-args --remap __node:=my_turtle
运行节点时设置参数
ros2 run example_parameters_rclcpp parameters_basic --ros-args -p rcl_log_level:=10
三. ros2工作空间与功能包
1.工作空间就相当于是创建一个文件夹,创建一个目录
首先选中要创建文件夹的位置,先打开终端,然后输入
cd 文件夹位置
进入到文件夹内,然后创建工作空间,在终端中输入
mkdir -p 文件夹名字
示例:
cd d2lros2/chapt2/
mkdir -p chapt2_ws/src
这里的文件夹可以同时创建多个包含在内的
2.功能包
功能包是存放节点的地方,ROS2中功能包根据编译方式的不同分为三种类型
(1)ament_python,适用于python程序
(2)cmake,适用于C++
(3)ament_cmake,适用于C++程序,是cmake的增强版
功能包的安装
在终端中输入
sudo apt install ros-<version>-package_name
与功能包相关的指令ROS2 pkg
create Create a new ROS2 package
executables Output a list of package specific executables
list Output a list of available packages
prefix Output the prefix path of a package
xml Output the XML of the package manifest or a specific tag
创建一个功能包
ros2 pkg create <package-name> --build-type {cmake,ament_cmake,ament_python} --dependencies <依赖名字>
列出所有的包
ros2 pkg list
输出某个包在路径的前缀
ros2 pkg prefix <package-name>
四.ROS2的构建工具——Colcon
colcon是一个功能包的构建工具,它是用来编译代码的,在ROS2中是默认没有安装colcon的,所以需要手动安装一下,当然这个安装的过程是有点痛苦的。
首先打开终端然后输入
sudo apt-get install python3-colcon-common-extensions
安装完成后,打开终端输入colcon
即可看到其使用方法。
编个东西测试一下
首先创建一个工作区文件夹colcon_test_ws,在终端中输入
cd d2lros2/chapt2/
mkdir colcon_test_ws && cd colcon_test_ws
我们可以下载一个ROS2示例源代码测试一下,接着在终端中输入
git clone https://github.com/ros2/examples src/examples -b humble
由于这个网址是外网,我也没有梯子,所以会无相应
这个时候就需要用到鱼香ROS机器人的一个代理GitHub Proxy加速来改进,
我们可以按Ctrl+C结束运行,重新输入
git clone https://mirror.ghproxy.com/https://github.com/ros2/examples src/examples -b humble
这里同样需要等待,但是比外网的那个会快很多
安装完成后
当我们下载完成后我们可以编译工程
继续输入
colcon build
因为我们这里是全部编译,所以需要的时间会很久,在后面会使用到一个局部的编译,就不用等待那么长的时间
编译完成之后就可以运行一下,先source资源
source install/setup.bash
运行一个订者节点,你将看不到任何打印,因为没有发布者
ros2 run examples_rclcpp_minimal_subscriber subscriber_member_function
打开一个新的终端,先进入到相同的目录,再source,再运行一个发行者节点
source install/setup.bash
ros2 run examples_rclcpp_minimal_publisher publisher_member_function
然后就会有
五.使用RCLCPP编写一个节点
编写内容:创建一个李家村的王家wang2节点
首先要创建工作空间(即为文件夹)
打开终端输入
mkdir -p chapt2_ws/src
cd chapt2_ws/src
然后创建village_wang功能包,使用ament-cmake作为编译类型,并为其添加rclcpp依赖。
ros2 pkg create village_wang --build-type ament_cmake --dependencies rclcpp
进入VS Code 输入
code ./
创建一个节点
在village_wang/src的目录下右键新建文件,名字为wang2.cpp,然后就可以在这个文件里编写节点了
在编之前我们需要在C++的配置中添加rclcpp这个头文件,需要找到你的电脑中的rclcpp文件所在文件夹的位置
在输入完下面的一个头文件过程当中系统会报错,这时点击小灯泡进入IntelliSense配置,找到包含路径
在第二行加上上面的代码(基于rclcpp所在的文件位置) 就可以了
编写ros2节点一般步骤
1.导入库文件
2.初始化客户端库
3.新建节点对象
4.spin循环节点
5.关闭客户端库
#include "rclcpp/rclcpp.hpp"/*1.导入库文件*/
int main(int argc, char **argv)
{
/* 2.初始化rclcpp */
rclcpp::init(argc, argv);
/*3.产生一个wang2的节点*/
auto node = std::make_shared<rclcpp::Node>("wang2");
RCLCPP_INFO(node->get_logger(), "我是wang2");
/*4. 运行节点*/
rclcpp::spin(node);
/* 5.停止运行 */
rclcpp::shutdown();
return 0;
}
写完代码后还需要修改CmakeLists.txt,在最后面加上
add_executable(wang2_node src/wang2.cpp)
ament_target_dependencies(wang2_node rclcpp)
install(TARGETS
wang2_node
DESTINATION lib/${PROJECT_NAME}
)
这里的是一个固定的写法,如果你是其他的节点那么只需要修改节点的名字就好了
记得要Ctrl+S保存所有修改过的地方
接着直接在VS Code中打开终端,选中chapt2_ws文件夹右键点击打开终端,然后就直接进入了这个文件夹里,就可以开始运行程序了(要注意每次运行时都要在最外的文件夹中运行)
编译输入(这里进行局部的编译,可以节省时间)
colcon build --packages-select village_wang
source install/setup.bash
ros2 run village_wang wang2_node
运行结果
当节点运行起来后,可以再尝试使用ros2 node list
指令来查看现有的节点。
六.话题与服务
1.ROS话题入门
话题是ROS2中最常用的通信方式之一,话题通信采取的是订阅发布模型
一个节点发布数据到某个话题上,另外一个节点就可以通过订阅话题拿到数据。
除了这种形式之外,ROS2话题通信还可以是1对n,n对1,n对n的
(1)接口消息
为了方便发送者和接收者进行数据的交换,ROS2帮我们在数据传递时做好了消息的序列化和反序列化(有关消息序列化相关内容请参考本章基础篇),而且ROS2的消息序列化与反序列化通信是可以做到跨编程语言、跨平台和跨设备之间的。同一个话题,所有的发布者和接收者必须使用相同消息接口。
(2)ROS2话题工具
GUI工具:RQT工具之rqt_graph
ROS2作为一个强大的工具,在运行过程中,我们是可以通过命令来看到节点和节点之间的数据关系的。
依次打开三个终端,分别输入下面三个命令。
ros2 run demo_nodes_py listener
ros2 run demo_nodes_cpp talker
rqt_graph
这个图片可以让我们知道数据是怎么走的
CLI工具
ROS2支持很多强大的topic指令,下面是一些常用的指令
ros2 topic -h
返回系统中当前活动的所有主题的列表
ros2 topic list
增加消息类型
ros2 topic list -t
打印事实话题内容
ros2 topic echo /chatter
查看主题信息
ros2 topic info /chatter
查看消息类型
ros2 interface show std_msgs/msg/String
手动发布指令
ros2 topic pub /chatter std_msgs/msg/String 'data: "123"'
2.话题CPP实现
(1)创建节点
创建一个控制节点和被控节点
控制节点创建一个话题发布者,发布控制命令(command)话题,接口类型为字符串(string),控制接点通过发布者发布控制命令(前进、后退、左转、右转、停止)。
被控节点创建一个订阅者,订阅控制命令,收到控制命令后根据命令内容打印对应速度出来。
先创建工作空间,功能包等
mkdir -p d2lros2
cd d2lros2/
mkdir -p chapt3/chapt3_ws/src
cd chapt3/chapt3_ws/src
ros2 pkg create example_topic_rclcpp --build-type ament_cmake --dependencies rclcpp
touch example_topic_rclcpp/src/topic_publisher_01.cpp
创建完后进入VS Code 找到那个.cpp文件开始编写
接着采用面向对象方式写一个最简单的节点。
#include "rclcpp/rclcpp.hpp"
class TopicPublisher01 : public rclcpp::Node
{
public:
TopicPublisher01(std::string name) :Node(name)
{
RCLCPP_INFO(this->get_logger(),"%s节点已经启动",name.c_str());
}
private:
};
int main(int argc,char **argv)
{
rclcpp::init(argc,argv);
/*创建对应节点的共享指针对象*/
auto node=std::make_shared<TopicPublisher01>("topic_publisher01");
/* 运行节点,并检测退出信号*/
rclcpp::spin(node);
rclcpp::shutdown();
return 0;
}
然后修改CMakeLists.txt
add_executable(topic_publisher_01 src/topic_publisher_01.cpp)
ament_target_dependencies(topic_publisher_01 rclcpp)
install(TARGETS
topic_publisher_01
DESTINATION lib/${PROJECT_NAME}
)
之后就可以保存编译运行了
cd chapt3/chapt3_ws/
colcon build --packages-select example_topic_rclcpp
source install/setup.bash
ros2 run example_topic_rclcpp topic_publisher_01
结果
这样子发布者的节点已经创建好了,接下来就要进行发布者的编写了
(2)编写发布者
想要创建发布者,只需要调用node
的成员函数create_publisher
并传入对应的参数即可。
有关API文档详细内容可以访问:rclcpp: rclcpp: ROS Client Library for C++ (ros2.org)
导入消息接口
消息接口是ROS2通信时必须的一部分,通过消息接口ROS2才能完成消息的序列化和反序列化。ROS2为我们定义好了常用的消息接口,并生成了C++和Python的依赖文件,我们可以直接在程序中进行导入。
ament_cmake
类型功能包导入消息接口分为三步:
- 在
CMakeLists.txt
中导入,具体是先find_packages
再ament_target_dependencies
。 - 在
packages.xml
中导入,具体是添加depend
标签并将消息接口写入。 - 在代码中导入,C++中是
#include"消息功能包/xxx/xxx.hpp"
。
我们依次做完这三步后文件内容如下:
CMakeLists.txt
find_package(rclcpp REQUIRED)
find_package(std_msgs REQUIRED)
add_executable(topic_publisher_01 src/topic_publisher_01.cpp)
ament_target_dependencies(topic_publisher_01 rclcpp std_msgs)
packages.xml
<buildtool_depend>ament_cmake</buildtool_depend>
<depend>rclcpp</depend>
<depend>std_msgs</depend>
<test_depend>ament_lint_auto</test_depend>
<test_depend>ament_lint_common</test_depend>
代码文件topic_publisher_01.cpp
#include "rclcpp/rclcpp.hpp"
#include "std_msgs/msg/string.hpp"
class TopicPublisher01 : public rclcpp::Node
创建一个发布者
根据ROS2的RCLCPPAPI文档可以看出,我们需要提供消息接口、话题名称和服务质量Qos。
- 消息接口上面我们已经导入了,是
std_msgs/msg/string.h
。 - 话题名称(topic_name),我们就用
control_command
。 - Qos,Qos支持直接指定一个数字,这个数字对应的是
KeepLast
队列长度。一般设置成10,即如果一次性有100条消息,默认保留最新的10个,其余的都扔掉。
还需要使用定时器来发布数据
- period,回调函数调用周期。
- callback,回调函数。
- group,调用回调函数所在的回调组,默认为nullptr。
创建完后代码
#include "rclcpp/rclcpp.hpp"
#include "std_msgs/msg/string.hpp"
class TopicPublisher01 : public rclcpp::Node
{
public:
TopicPublisher01(std::string name) :Node(name)
{
RCLCPP_INFO(this->get_logger(),"%s节点已经启动",name.c_str());
// 创建发布者
command_publisher_=this->create_publisher<std_msgs::msg::String>("command",10);
//创建定时器,1s为周期,定时发布
timer_=this->create_wall_timer(std::chrono::seconds(1),std::bind(&TopicPublisher01::timer_callback,this))
}
private:
void timer_callback()
{
//创建消息
std_msgs::msg::String message;
message.data="forward";
//日志打印
RCLCPP_INFO(this->get_logger(),"Publisher:'%s'",message.data.c_str());
//发布消息
command_publisher_->publish(message);
}
//声明定时器指针
rclcpp::TimerBase::SharedPtr timer_;
// 声明话题发布者指针
rclcpp::Publisher<std_msgs::msg::String>::SharedPtr command_publisher_;
};
int main(int argc,char **argv)
{
rclcpp::init(argc,argv);
/*创建对应节点的共享指针对象*/
auto node=std::make_shared<TopicPublisher01>("topic_publisher01");
/* 运行节点,并检测退出信号*/
rclcpp::spin(node);
rclcpp::shutdown();
return 0;
}
讲解
定时器
定时器是ROS2中的另外一个常用功能,通过定时器可以实现按照一定周期调用某个函数以实现定时发布等逻辑。
定时器对应的类是 rclcpp::TimerBase
,调用create_wall_timer
将返回其共享指针。
创建定时器时传入了两个参数,这两个参数都利用了C++11的新特性。
std::chrono::milliseconds(500)
,代表500ms,chrono是c++ 11中的时间库,提供计时,时钟等功能。std::bind(&TopicPublisher01::timer_callback, this)
,bind() 函数的意义就像它的函数名一样,是用来绑定函数调用的某些参数的。
编译,source,运行
cd chapt3/chapt3_ws/
colcon build --packages-select example_topic_rclcpp
source install/setup.bash
ros2 run example_topic_rclcpp topic_publisher_01
测试
# 查看列表
ros2 topic list
# 输出内容
ros2 topic echo /command
(3)编写订阅者
先创建一个订阅者节点
cd chapt3_ws/src/example_topic_rclcpp
touch src/topic_subscribe_01.cpp
然后编写程序
#include "rclcpp/rclcpp.hpp"
#include "std_msgs/msg/string.hpp"
class TopicSubscribe01:public rclcpp::Node
{
public:
TopicSubscribe01(std::string name):Node(name)
{
RCLCPP_INFO(this->get_logger(),"大家好,我是%s",name.c_str());
//创建一个订阅者话题
command_subscribe_=this->create_subscription<std_msgs::msg::String>("command",10,std::bind(&TopicSubscribe01::command_callback,this,std::placeholders::_1));
}
private:
//声明一个订阅者
rclcpp::Subscription<std_msgs::msg::String>::SharedPtr command_subscribe_;
//收到话题数据回调函数
void command_callback(const std_msgs::msg::String::SharedPtr msg)
{
double speed=0.0f;
if(msg->data=="forward")
{
speed=0.2f;
}
RCLCPP_INFO(this->get_logger(),"收到[%s]指令,发送速度%f",msg->data.c_str(),speed);
}
};
int main(int argc,char **argv)
{
rclcpp::init(argc,argv);
auto node= std::make_shared<TopicSubscribe01>("topic_subscribe_01");
rclcpp::spin(node);
rclcpp::shutdown();
return 0;
}
依然的需要在CMakeLists.txt
添加下std_msgs
依赖
ament_target_dependencies(topic_subscribe_01 rclcpp std_msgs)
运行测试
cd chapt3/chapt3_ws/
colcon build --packages-select example_topic_rclcpp
source install/setup.bash
ros2 run example_topic_rclcpp topic_subscribe_01
手动发布数据测试订阅者
ros2 topic pub /command std_msgs/msg/String "{data: forward}"
最后可以两个同时运行
打开rqt为
2.ROS2服务入门
介绍
服务分为客户端和服务端,平时我们用的手机APP都可以成为客户端,而APP服务器对于软件来说就是服务端。
客户端发送请求给服务端,服务端可以根据客户端的请求做一些处理,然后返回结果给客户端。
注意事项
同一个服务(名称相同)有且只有一个节点来提供
同一个服务可以被多个客户端调用
ROS2服务常用指令
查看服务列表
ros2 service list
手动调用服务
ros2 service call /add_two_ints example_interfaces/srv/AddTwoInts "{a: 5,b: 10}"
查看服务接口类型
ros2 service type /add_two_ints
查找使用某一接口的服务
ros2 service find example_interfaces/srv/AddTwoInts
RCLCPP实现服务
创建功能包和节点
cd chapt3/chapt3_ws/src
ros2 pkg create example_service_rclcpp --build-type ament_cmake --dependencies rclcpp
touch example_service_rclcpp/src/service_server_01.cpp
touch example_service_rclcpp/src/service_client_01.cpp
一开始程序与前面大致相同
service_server_01.cpp
#include "rclcpp/rclcpp.hpp"
class ServiceServer01:public rclcpp::Node
{
public:
ServiceServer01(std::string name):Node(name)
{
RCLCPP_INFO(this->get_logger(),"节点%s已启动",name.c_str());
}
private:
};
int main(int agrc,char **agrv)
{
rclcpp::init(agrc,agrv);
auto node = std::make_shared<ServiceServer01>("service_server_01");
rclcpp::spin(node);
rclcpp::shutdown();
return 0;
}
service_client_01.cpp
#include "rclcpp/rclcpp.hpp"
class ServiceClient01:public rclcpp::Node
{
public:
ServiceClient01(std::string name):Node(name)
{
RCLCPP_INFO(this->get_logger(),"节点%s已启动",name.c_str());
}
private:
};
int main(int argc,char **argv)
{
rclcpp::init(argc,argv);
auto node = std::make_shared<ServiceClient01>("service_client_01");
rclcpp::spin(node);
rclcpp::shutdown();
return 0;
}
CMakeLists.txt
add_executable(service_server_01 src/service_server_01.cpp)
ament_target_dependencies(service_server_01 rclcpp)
add_executable(service_client_01 src/service_client_01.cpp)
ament_target_dependencies(service_client_01 rclcpp)
install(TARGETS
service_server_01
DESTINATION lib/${PROJECT_NAME}
)
install(TARGETS
service_client_01
DESTINATION lib/${PROJECT_NAME}
)
运行
cd chapt3/chapt3_ws/
colcon build --packages-select example_service_rclcpp
# 运行 service_server_01
source install/setup.bash
ros2 run example_service_rclcpp service_server_01
# 打开新终端运行 service_client_01
source install/setup.bash
ros2 run example_service_rclcpp service_client_01
服务端的实现
1.导入接口
ament_cmake
类型功能包导入消息接口分为三步:
- 在
CMakeLists.txt
中导入,具体是先find_packages
再ament_target_dependencies
。- 在
packages.xml
中导入,具体是添加depend
标签并将消息接口写入。- 在代码中导入,C++中是
#include"消息功能包/xxx/xxx.hpp"
。
继续修改CMakeLists.txt
# 这里我们一次性把服务端和客户端对example_interfaces的依赖都加上
find_package(example_interfaces REQUIRED)
add_executable(service_client_01 src/service_client_01.cpp)
ament_target_dependencies(service_client_01 rclcpp example_interfaces)
add_executable(service_server_01 src/service_server_01.cpp)
ament_target_dependencies(service_server_01 rclcpp example_interfaces)
修改packages.xml
<depend>example_interfaces</depend>
2.编写代码
service_server_01.cpp
#include "rclcpp/rclcpp.hpp"
#include "example_interfaces/srv/add_two_ints.hpp"
class ServiceServer01:public rclcpp::Node
{
public:
ServiceServer01(std::string name):Node(name)
{
RCLCPP_INFO(this->get_logger(),"节点%s已启动",name.c_str());
//创建服务
add_ints_server_=
this->create_service<example_interfaces::srv::AddTwoInts>(
"add_two_ints_srv",
std::bind(&ServiceServer01::handle_add_two_ints,this,
std::placeholders::_1,std::placeholders::_2));
}
private:
//声明一个服务
rclcpp::Service<example_interfaces::srv::AddTwoInts>::SharedPtr add_ints_server_;
//收到请求的处理函数
void handle_add_two_ints(
const std::shared_ptr<example_interfaces::srv::AddTwoInts::Request>request,
std::shared_ptr<example_interfaces::srv::AddTwoInts::Response>response
)
{
RCLCPP_INFO(this->get_logger(),"收到a:%ld b:%ld",request->a,request->b);
response->sum=request->a+request->b;
}
};
int main(int agrc,char **agrv)
{
rclcpp::init(agrc,agrv);
auto node = std::make_shared<ServiceServer01>("service_server_01");
rclcpp::spin(node);
rclcpp::shutdown();
return 0;
}
service_client_01.cpp
#include "rclcpp/rclcpp.hpp"
#include "example_interfaces/srv/add_two_ints.hpp"
class ServiceClient01:public rclcpp::Node
{
public:
ServiceClient01(std::string name):Node(name)
{
RCLCPP_INFO(this->get_logger(),"节点%s已启动",name.c_str());
//创建客户端
client_=this->create_client<example_interfaces::srv::AddTwoInts>("add_two_ints_srv");
}
void send_request(int a,int b)
{
RCLCPP_INFO(this->get_logger(),"计算%d+%d",a,b);
//1.等待服务端上线
while(!client_->wait_for_service(std::chrono::seconds(1))){
//等待时检测rclcpp的状态
if(!rclcpp::ok()){
RCLCPP_INFO(this->get_logger(),"等待服务的过程中被打断...");
return;
}
RCLCPP_INFO(this->get_logger(),"等待服务过程中被打断...");
}
//2. 构造请求的
auto request=
std::make_shared<example_interfaces::srv::AddTwoInts_Request>();
request->a=a;
request->b=b;
//3.发送异步请求,然后等待返回,返回时调用回调函数
client_->async_send_request(
request,std::bind(&ServiceClient01::result_callback_,this,
std::placeholders::_1));
};
private:
//声明客户端
rclcpp::Client<example_interfaces::srv::AddTwoInts>::SharedPtr client_;
void result_callback_(
rclcpp::Client<example_interfaces::srv::AddTwoInts>::SharedFuture result_future
)
{
auto response= result_future.get();
RCLCPP_INFO(this->get_logger(),"计算结果:%ld",response->sum);
}
};
int main(int argc,char **argv)
{
rclcpp::init(argc,argv);
auto node = std::make_shared<ServiceClient01>("service_client_01");
node->send_request(5,6);
rclcpp::spin(node);
rclcpp::shutdown();
return 0;
}
结果
到这里就已经完成了ROS2的话题与服务的一个入门,对于我刚刚接触ROS2来说,一开始我是直接看视频的,我看的视频是来自b站的鱼香ROS机器人,第一次看这些东西的时候犹如天书,听不懂,然后我还是把相关的视频看完了,然后我发现他给出学ROS的网站写的内容与视频中将的有点不一样,就像这些例子,两个将的都是不一样的,后面我觉得重复看视频的话要拉进度条,而且不一定一拉就能找到我i要看的地方,我选择使用他的课件来自学,然后纯手工照着代码边理解边敲,现在我已经能够理解大部分内容了,然后我就做出了这个笔记,这能够加强我的理解,对于一个刚刚入门的我来说最重要的是去扩展知识,了解一些语句的用法,在ROS2中有很多语法和语句,每一个的功能都是不一样的,在我看的课程中列出来的语法仅仅只是冰山一角,我还要加强自己的语言基础,就像我在之前学习的类和对象在ROS2的话题和服务过程中已经很好的能够体现出来了,希望自己接下来能够继续去深入学习ROS2.