当前位置:   article > 正文

ROS2 学习笔记(一)新建项目的基础流程_ros新建工程

ros新建工程

参考链接;

古月居的视频+网站

【古月居】古月·ROS2入门21讲 | 带你认识一个全新的机器人操作系统_哔哩哔哩_bilibili

ROS2入门教程

鱼香肉丝的帖子 

【ROS2机器人入门到实战】_ros2机器人编程实战 pdf-CSDN博客

网上直接搜的新建项目的起手式都比较雷同,一般是要从克隆一个小乌龟的例程开始,可这在正常的任务中是没啥用的。

这篇笔记内容是正常情况下应该怎么开始一个ros2的项目,用来给自己做备忘的。防止自己以后把这些完全忘了,所以写的可能有点罗里吧嗦。文中特地创建了和古月居不同的路径,以帮助以后的自己对比两者发现本质。

如果刚好能帮到您,麻烦给个赞。

一、创建工作空间

创建工作空间分三步:创建路径、编译工作空间、设置环境变量

1.1 创建路径

首先,打开终端,用普通的命令创建一个文件夹作为工作空间的路径,且文件夹下右一个src子文件夹。

(后续的功能包啥的都是放到src子文件夹里的,所以必须有这个src)

(因为这里只是普通的新建文件夹,所以不使用命令行,直接到目标位置右键新建文件夹也行。)

工作空间的名称可以随便取,一般为了方便辨认,会在命名时加上“ws”字样

具体操作:在终端里敲以下代码:

mkdir -p ~/Code/ROS2/MyRobot_ws/src

 这行代码里,-p表示自动创建目标目录的上层目录,即使该上层目录已经存在也不会报错。我将该工作空间创建到了我的~/Code/ROS2/目录下了,这个可以根据实际情况进行修改。

古月居的教程里,创建好路径后要去克隆一个小乌龟的例程,并且要自动安装小乌龟需要的依赖,本笔记只追求最简单的创建流程,所以不需要这样,直接进入下一步。

1.2 编译工作空间

编译的操作是在刚才新建好的工作空间目录下执行:

  1. cd ~/Code/ROS2/MyRobot_ws/
  2. colcon build

执行后,MyRobot_ws文件夹内出现了一些新的文件夹:build, install, log

这里的colcon不用管是啥意思,和ros1里的catkin一样有历史渊源,感兴趣的同学自己去搜一下。

不光是创建工作空间时需要编译,以后创建功能包后,修改代码后都要重新编译

 在古月居教程中,首先执行了安装命令,如图:

我感觉应该是不需要的,编译时如果有报错再安装吧。 

1.3 设置环境变量

编译完成后,还需要设置环境变量才能让系统知道这个工作空间内有哪些功能包和可执行文件,因此还需要设置环境变量。这一步的作用是初始化工作区

有两种操作,第一种操作使环境变量只在当前终端内生效,第二种操作使环境变量在所有终端生效。两种操作并不冲突,使用第二种后,也可以使用第一种手动刷新环境变量。

第一种:先进入工作空间路劲下,然后执行source

  1. cd ~/Code/ROS2/MyRobot_ws/
  2. source install/local_setup.sh

第二种:

echo " source ~/Code/ROS2/MyRobot_ws/install/local_setup.sh" >> ~/.bashrc

设置环境变量的操作在每次编译之后都要执行,编译后只要在所有终端上执行第一种设置环境变量的命令即可。若用第二种方法设置了环境变量,则打开新的终端时自动生效。

二、创建功能包 

功能包都在工作空间下的src文件夹内新建,区别于工作空间,不能用普通的右键新建文件夹或者mkdir命令直接新建,需要使用ros2 pkg create 命令新建。

按照古月居的教程的内容,一个功能包里最好只有一种编程语言编写的内容,可是神奇的chatgpt说可以有多种不同的语言,需要修改CMakeLists.txt的内容。

这里将建立编译的常规方法和同时存在两种语言的进阶方法都试一下。实际操作中建议还是怎么简单怎么来,不同语言分不同的功能包算了。

在c++写的功能包里有两个比较重要的文件

2.1 创建只有一种语言的功能包

2.1.1 创建

创建计划使用C++编写的功能包:

  1. cd ~/Code/ROS2/MyRobot_ws/src
  2. ros2 pkg create rbt_ctrl_c --build-type ament_cmake --dependencies rclcpp std_msgs

 package.xml 文件是ROS2项目的功能包管理文件。 CMakeLists.txt 是ROS2项目的编译配置文件

创建计划使用python编写的功能包:

  1. cd ~/Code/ROS2/MyRobot_ws/src
  2. ros2 pkg create rbt_ui_py --build-type ament_cmake --dependencies rclpy std_msgs

其中,依赖项可以先不写,可以在创建完成后在package.xml 中修改。

上面的依赖项中,std_msgs基本都能用上,所以写上去没啥坏处。 

2.1.2 编译

在创建好的功能包中,我们可以继续完成代码的编写,之后需要编译和配置环境变量,才能正常运行:

  1. cd ~/Code/ROS2/MyRobot_ws/
  2. colcon build # 编译工作空间所有功能包
  3. source install/local_setup.bash #配置环境初始化

2.2 创建有多种语言的功能包

占位---------暂时用不到,以后有空写。

三、编写具体的节点代码

3.1 代码存放位置

c++版的功能包,将代码文件新建到功能包文件夹下的src文件夹内。

Python版的功能包,将代码文件新建到功能包文件夹下的与功能包同名的文件夹下。

举例:上一节创建的两个功能包的代码分别放在:

~/Code/ROS2/MyRobot_ws/src/rbt_ctrl_cpp/src/
~/Code/ROS2/MyRobot_ws/src/rbt_ui_py/rbt_ui_py/

3.2 编写代码

3.2.1 工具配置

 我使用vscode写c++, 用pycharm写python。配置方法如下:

A. VSCode配置

参考:在Ubuntu22.04使用VSCODE搭建ROS2开发环境

如果只想通过VS Code进行代码编写,不在里面运行,则只需要在常规的VSCode的C++配置的基础上再配置一下文件告诉软件应该到哪里找ROS2的库。具体步骤如下:

0. 按正常步骤在VSCode里打开src文件夹(或者功能包所在文件夹,或者代码所在文件夹,无所谓打开哪个,只要包含你要运行的代码应该就可以。保险起见,打开src文件夹吧),然后新建c++文件。 如果此时你的.vscode路径下没有出现settings.json,就去百度怎么搞出来这个。

1. 修改.vscode下的配置文件。目的是让软件能够找到将ROS2的库路径。

找到的两篇帖子一个改了c_cpp_properties.json,一个改了settings.json。我试了一下,我是两个文件都要改,莫名其妙的。将下面这个路径分别添加到两个文件里:

"/opt/ros/foxy/include/**"

位置是c_cpp_properties.json里的“includePath”后面的方括号里;settings.json文件里的“C_CPP_Runner.includePaths”后面的方括号里。如果方括号里已经有了其他路径,则用逗号隔开。

这样就可以在VS Code里看到代码提示了。

B. Pycharm配置

同样不追求在pycharm里直接调试,只要它能在写代码时给我提示就行。

用pycharm打开功能包所在文件夹,或者直接在功能包里创建项目 ,选一个或建一个常用的Python解释器,

然后在设置里讲ROS2的Python库的路径放到项目的内容根里。就行了。

我的这个库路径是:/opt/ros/foxy/lib/python3.8/site-packages ,把它添加到设置-项目-项目结构-添加内容根  里,即可。

3.2.2 编写代码后的文件配置与运行

A. C++

我自己的代码还没写,这里先用古月居的例程代码做例子:至少能保证代码编写没问题,先确保流程没问题

(尴尬的是,古月居的c++订阅例程好像有点问题,我跑不起来)

文件路径:Code/ROS2/MyRobot_ws/src/rbt_ctrl_cpp/src/topic_helloworld_pub.cpp

  1. /***
  2. @作者: 古月居(www.guyuehome.com)
  3. @说明: ROS2话题示例-发布图像话题
  4. ***/
  5. #include <chrono>
  6. #include <functional>
  7. #include <memory>
  8. #include <string>
  9. #include "rclcpp/rclcpp.hpp" // ROS2 C++接口库
  10. #include "std_msgs/msg/string.hpp" // 字符串消息类型
  11. using namespace std::chrono_literals;
  12. class PublisherNode : public rclcpp::Node
  13. {
  14. public:
  15. PublisherNode()
  16. : Node("topic_helloworld_pub") // ROS2节点父类初始化
  17. {
  18. // 创建发布者对象(消息类型、话题名、队列长度)
  19. publisher_ = this->create_publisher<std_msgs::msg::String>("chatter", 10);
  20. // 创建一个定时器,定时执行回调函数
  21. timer_ = this->create_wall_timer(
  22. 500ms, std::bind(&PublisherNode::timer_callback, this));
  23. }
  24. private:
  25. // 创建定时器周期执行的回调函数
  26. void timer_callback()
  27. {
  28. // 创建一个String类型的消息对象
  29. auto msg = std_msgs::msg::String();
  30. // 填充消息对象中的消息数据
  31. msg.data = "Hello World";
  32. // 发布话题消息
  33. RCLCPP_INFO(this->get_logger(), "Publishing: '%s'", msg.data.c_str());
  34. // 输出日志信息,提示已经完成话题发布
  35. publisher_->publish(msg);
  36. }
  37. rclcpp::TimerBase::SharedPtr timer_; // 定时器指针
  38. rclcpp::Publisher<std_msgs::msg::String>::SharedPtr publisher_; // 发布者指针
  39. };
  40. // ROS2节点主入口main函数
  41. int main(int argc, char * argv[])
  42. {
  43. // ROS2 C++接口初始化
  44. rclcpp::init(argc, argv);
  45. // 创建ROS2节点对象并进行初始化
  46. rclcpp::spin(std::make_shared<PublisherNode>());
  47. // 关闭ROS2 C++接口
  48. rclcpp::shutdown();
  49. return 0;
  50. }
(1)修改CMakeLists.txt

通过add_executable() 确定 可执行文件的名字:topic_helloworld_pub,用来生成可执行文件的c++文件:src/topic_helloworld_pub.cpp;

通过ament_target_dependencies() 说明该可执行文件的依赖,第一个位置放可执行文件的名字,后面放依赖的名称,这里是:topic_helloworld_pub rclcpp std_msgs,空格隔开。注意,这里似乎只需要写与ROS2相关的依赖,其他已安装的C++第三方库不需要在这里写。

  1. # find dependencies
  2. find_package(ament_cmake REQUIRED)
  3. find_package(rclcpp REQUIRED)
  4. find_package(std_msgs REQUIRED)
  5. add_executable(topic_helloworld_pub src/topic_helloworld_pub.cpp)
  6. ament_target_dependencies(topic_helloworld_pub rclcpp std_msgs)
  7. install(TARGETS
  8. topic_helloworld_pub
  9. DESTINATION lib/${PROJECT_NAME}
  10. )

如果功能包里有多个包含节点的C++文件,则 在install上方添加add_executable()和ment_target_dependencies(),在install内部换行添加节点名称。例:

  1. # find dependencies
  2. find_package(ament_cmake REQUIRED)
  3. find_package(rclcpp REQUIRED)
  4. find_package(std_msgs REQUIRED)
  5. add_executable(topic_helloworld_pub src/topic_helloworld_pub.cpp)
  6. ament_target_dependencies(topic_helloworld_pub rclcpp std_msgs)
  7. add_executable(topic_helloworld_sub src/topic_helloworld_sub.cpp)
  8. ament_target_dependencies(topic_helloworld_sub rclcpp std_msgs)
  9. install(TARGETS
  10. topic_helloworld_pub
  11. topic_helloworld_sub
  12. DESTINATION lib/${PROJECT_NAME}
  13. )

文件中其他内容为自动生成的,完整文件内容为:

  1. cmake_minimum_required(VERSION 3.5)
  2. project(rbt_ctrl_cpp)
  3. # Default to C99
  4. if(NOT CMAKE_C_STANDARD)
  5. set(CMAKE_C_STANDARD 99)
  6. endif()
  7. # Default to C++14
  8. if(NOT CMAKE_CXX_STANDARD)
  9. set(CMAKE_CXX_STANDARD 14)
  10. endif()
  11. if(CMAKE_COMPILER_IS_GNUCXX OR CMAKE_CXX_COMPILER_ID MATCHES "Clang")
  12. add_compile_options(-Wall -Wextra -Wpedantic)
  13. endif()
  14. # find dependencies
  15. find_package(ament_cmake REQUIRED)
  16. find_package(rclcpp REQUIRED)
  17. find_package(std_msgs REQUIRED)
  18. add_executable(topic_helloworld_pub src/topic_helloworld_pub.cpp)
  19. ament_target_dependencies(topic_helloworld_pub rclcpp std_msgs)
  20. add_executable(topic_helloworld_sub src/topic_helloworld_sub.cpp)
  21. ament_target_dependencies(topic_helloworld_sub rclcpp std_msgs)
  22. install(TARGETS
  23. topic_helloworld_pub
  24. topic_helloworld_sub
  25. DESTINATION lib/${PROJECT_NAME}
  26. )
  27. if(BUILD_TESTING)
  28. find_package(ament_lint_auto REQUIRED)
  29. # the following line skips the linter which checks for copyrights
  30. # uncomment the line when a copyright and license is not present in all source files
  31. #set(ament_cmake_copyright_FOUND TRUE)
  32. # the following line skips cpplint (only works in a git repo)
  33. # uncomment the line when this package is not in a git repo
  34. #set(ament_cmake_cpplint_FOUND TRUE)
  35. ament_lint_auto_find_test_dependencies()
  36. endif()
  37. ament_package()

(2)修改packages.xml

在该文件内添加depend标签并将相关依赖写进去:

  1. <depend>rclcpp</depend>
  2. <depend>std_msgs</depend>

文件里的其他内容是创建功能包并编译之后自动生成的,完整内容为:

  1. <?xml version="1.0"?>
  2. <?xml-model href="http://download.ros.org/schema/package_format3.xsd" schematypens="http://www.w3.org/2001/XMLSchema"?>
  3. <package format="3">
  4. <name>rbt_ctrl_cpp</name>
  5. <version>0.0.0</version>
  6. <description>TODO: Package description</description>
  7. <maintainer email="zf@todo.todo">zf</maintainer>
  8. <license>TODO: License declaration</license>
  9. <buildtool_depend>ament_cmake</buildtool_depend>
  10. <depend>rclcpp</depend>
  11. <depend>std_msgs</depend>
  12. <test_depend>ament_lint_auto</test_depend>
  13. <test_depend>ament_lint_common</test_depend>
  14. <export>
  15. <build_type>ament_cmake</build_type>
  16. </export>
  17. </package>

(3)编译并运行

编译:到工作空间的路径下执行build。不知道正不正常,我这里不能按tab自动补全名称

  1. cd Code/ROS2/MyRobot_ws
  2. colcon build --packages-select rbt_ctrl_cpp

运行:编译后在当前终端或打开新终端,先source, 后run

  1. source install/setup.bash
  2. ros2 run rbt_ctrl_cpp topic_helloworld_pub

注:修改代码后必须重新build, source, 才能生效。

B. Python 

 同样使用古月居的例程代码:

文件路径:Code/ROS2/MyRobot_ws/src/rbt_ui_py/src/topic_helloworld_sub.py

  1. #!/usr/bin/env python3
  2. # -*- coding: utf-8 -*-
  3. """
  4. @作者: 古月居(www.guyuehome.com)
  5. @说明: ROS2话题示例-订阅“Hello World”话题消息
  6. """
  7. import rclpy # ROS2 Python接口库
  8. from rclpy.node import Node # ROS2 节点类
  9. from std_msgs.msg import String # ROS2标准定义的String消息
  10. """
  11. 创建一个订阅者节点
  12. """
  13. class SubscriberNode(Node):
  14. def __init__(self, name):
  15. super().__init__(name) # ROS2节点父类初始化
  16. self.sub = self.create_subscription(\
  17. String, "chatter", self.listener_callback, 10) # 创建订阅者对象(消息类型、话题名、订阅者回调函数、队列长度)
  18. def listener_callback(self, msg): # 创建回调函数,执行收到话题消息后对数据的处理
  19. self.get_logger().info('I heard: "%s"' % msg.data) # 输出日志信息,提示订阅收到的话题消息
  20. def main(args=None): # ROS2节点主入口main函数
  21. rclpy.init(args=args) # ROS2 Python接口初始化
  22. node = SubscriberNode("topic_helloworld_sub") # 创建ROS2节点对象并进行初始化
  23. rclpy.spin(node) # 循环等待ROS2退出
  24. node.destroy_node() # 销毁节点对象
  25. rclpy.shutdown() # 关闭ROS2 Python接口
(1)修改setup.py文件

作用是告诉ROS2节点的入口:

  1. entry_points={
  2. 'console_scripts': ["topic_helloworld_sub = rbt_ui_py.topic_helloworld_sub:main"
  3. ],

当存在多个要编译的节点时,在[]内用逗号隔开。

(2)编译与运行

鱼香肉丝的教程里这一步没有编译,大概是忘了 

编译:

  1. cd Code/ROS2/MyRobot_ws
  2. colcon build --packages-select rbt_ui_py

 运行:同样是在当前终端或新建终端都行,要先source

  1. source install/setup.bash
  2. ros2 run rbt_ui_py topic_helloworld_sub

 3.2.3 创建消息文件

这一步不是必须的,如果你要传递的消息类型比较简单,直接包含在标准库里,可以不用单独创建消息文件。这一步创建的消息文件可以类比成在c++中创建了一个类,只是这个类可以在ros2中的不同节点之间传递。

通常,消息文件被创建到相关的功能包内,(实测还是单独定义一个功能包放消息文件比较省事)

首先单独创建一个用来放消息文件的功能包,在功能包内新建一个msg文件夹,在msg文件夹内新建 .msg文件,在 .msg文件内定义消息结构,然后在功能包的package.xml内添加消息依赖,在cmakelist.txt文件内添加编译指令。然后到工作空间目录中用colcon编译。若使用Python写功能包,则在setup.py里添加编译指令。

若消息文件同时被好多功能包共用,也可以考虑为消息文件单独创建一个功能包。

注意:msg文件的命名必须以大写字母开头,且名字里只能有大写、小写、数字,以.msg作为后缀。 msg内部变量的名称必须以小写字母开头,变量名中只能有小写字母或数字下划线。

例子

A. 创建消息功能包 

UI和后台控制主程序之间通讯的消息,定义在主程序的功能包内。单独建一个消息功能包,并新建文件夹msg:

  1. cd Code/ROS2/MyRobot_ws/src
  2. ros2 pkg create --build-type ament_cmake rbt_msgs
  3. cd rbt_msgs
  4. mkdir msg
B. 创建消息文件 

 然后在文件夹msg内新建.msg文件(可以不新建msg文件夹,只要.msg文件在功能包内且编译文件里写对.msg文件的路径就行。但是这样做更有条理)。我在msg里新建的.msg文件名字是RbtState.msg。文件内容为:

  1. # rbt_state.msg
  2. # current state of each axis. get the value from series communication
  3. float32[6] axis_pos
  4. float32[6] axis_vel
  5. float32[6] axis_acc
  6. # current state of the robot platform. values are calculated by forward kinemics
  7. float32[6] platform_pos
  8. float32[6] platform_vel
  9. float32[6] platform_acc
  10. # current value of force sensors
  11. float32[6] force_sens

这里每个变量都是长度为6的数组所以都有“[6]”。如上文所言,注意文件和变量的命名规则。

C. 配置/编译

写好.msg文件后,在CMakeLists.txt里添加以下内容:

  1. find_package(rosidl_default_generators REQUIRED)
  2. rosidl_generate_interfaces(
  3. ${PROJECT_NAME}
  4. "msg/RbtState.msg"
  5. )

在package.xml里添加:

  1. <buildtool_depend>rosidl_default_generators</buildtool_depend>
  2. <exec_depend>rosidl_default_runtime</exec_depend>
  3. <member_of_group>rosidl_interface_packages</member_of_group>

然后回到工作空间目录进行编译即可:

  1. cd ~/Code/ROS2/MyRobot_ws
  2. colcon build --symlink-install --packages-select rbt_msgs

若出现以下警告:

 

  1. [6.241s] WARNING:colcon.colcon_core.package_selection:Some selected packages are already built in one or more underlay workspaces:
  2. 'rbt_msgs' is in: /home/zf/Code/ROS2/MyRobot_ws/install/rbt_msgs
  3. If a package in a merged underlay workspace is overridden and it installs headers, then all packages in the overlay must sort their include directories by workspace order. Failure to do so may result in build failures or undefined behavior at run time.
  4. If the overridden package is used by another package in any underlay, then the overriding package in the overlay must be API and ABI compatible or undefined behavior at run time may occur.
  5. If you understand the risks and want to override a package anyways, add the following to the command line:
  6. --allow-overriding rbt_msgs

那么,在编译时添加它提到的语句,完整命令是:

colcon build --symlink-install --packages-select rbt_msgs  --allow-overriding rbt_msgs

编译完成后可以在以下路径里分别看到该消息类型的.hpp和.py文件 :

  1. /Code/ROS2/MyRobot_ws/install/rbt_msgs/include/rbt_msgs/msg
  2. /Code/ROS2/MyRobot_ws/install/rbt_msgs/lib/python3.8/site-packages/rbt_msgs/msg

我在编译时因为各种命名错误报了好多次错误,还是要好好读文档呀。。。 

同时我也尝试了在Python编译的功能包里添加消息类型,需要在setup.py文件里添加消息文件的路径,结果编译后找不到.hpp和.py文件。赶时间,就不钻研Python的消息怎么编译了,大不了单独建一个c++编译的功能包专门用来编译消息文件。

D. 新消息类型的使用

例:在rbt_ctrl_cpp/src/robot_ctrl_main.cpp内调用该消息类型

(1)VSCode里的代码补全 

在vscode里写代码时,可以将/Code/ROS2/MyRobot_ws/install/rbt_msgs/include/放到c_cpp_properties.json文件夹里,以便写代码时获得自动补全:

  1. "includePath": [
  2. "${workspaceFolder}/**",
  3. "/opt/ros/foxy/include",
  4. "/home/zf/Code/ROS2/MyRobot_ws/install/rbt_msgs/include"
  5. ],

注意修改这里的引用路径并不会影响编译,因为我们的编译规则其实是靠CMakeLists.txt完成的。这里写这些只是为了方便写代码。

(2) 在cpp文件里调用

文件为robot_ctrl_main.cpp

#include "rbt_msgs/msg/rbt_state.hpp"

使用:

  1. // 下面两种声明方法看具体使用场景选一种就行了
  2. rbt_msgs::msg::RbtState rbt_sta; // 声明变量
  3. auto rbt_para2 = std::shared_ptr<rbt_msgs::msg::RbtState>();//创建共享指针
(3) 将消息类型添加到依赖项

CMakeLists.txt:

  1. find_package(rbt_msgs REQUIRED)
  2. add_executable(rbt_ctrl_main src/robot_ctrl_main.cpp)
  3. ament_target_dependencies(rbt_ctrl_main rclcpp std_msgs rbt_msgs)

package.xml:

  <depend>rbt_msgs</depend>

四、总结

后续可能根据经验进行增删修改。

加油! 

2024.03.13 修改了新建消息类型的相关内容

声明:本文内容由网友自发贡献,转载请注明出处:【wpsshop博客】
推荐阅读
  

闽ICP备14008679号