资讯动态

ROS1框架

发布时间:2026/10/3 6:51:59 来源:尧图企业网站定制
ROS1 里真正区分“是不是串台”的不是“项目一/项目二”这个概念而是当前 ROS Master 里有哪些节点在发布哪些 topic。如果项目一和项目二恰好都使用/camera/image而且消息类型都一样例如sensor_msgs/Image那么只要它们同时连到同一个 ROS Master并且都在发布这个 topic订阅者就会同时收到两个 publisher 的消息。ROS 不会自动知道“这个属于项目一那个属于项目二”。所以确实有两种情况项目一没启动 → 不会串 项目一和项目二同时启动而且 topic 重名 → 会混在一起更准确地说ROS 里 topic 是全局通信名字不是“绑定某个工程”的私有名字。比如节点A项目一 发布 /odom 节点B项目二 也发布 /odom 节点C 订阅 /odom那么节点C会收到A和B两个 publisher 的消息。这就是为什么 ROS 工程里很重视namespace remap node name topic name来避免冲突。namespace是最常用的隔离方法。比如你有两架无人机无人机1 无人机2如果都用/odom就会冲突。所以可以设计成/uav1/odom /uav2/odom这就是 namespace 的作用。launch 里可以这么写group nsuav1 node pkgmy_pkg typeodom_node nameodom_node/ /group如果程序内部发布的是相对 topicodom那么最终会变成/uav1/odom另一组group nsuav2 node pkgmy_pkg typeodom_node nameodom_node/ /group最终就是/uav2/odom于是无人机1 → /uav1/odom 无人机2 → /uav2/odom互不干扰。remap则是“改接口名字”。比如 EGO 源码里写死订阅/grid_map/odom但你实际想让它用/vins_estimator/odometry你不想改 C 源码就在 launch 里写remap from/grid_map/odom to/vins_estimator/odometry/意思就是EGO内部以为自己订阅 /grid_map/odom ROS实际给它接到 /vins_estimator/odometry这就是 remap。你现在已经真实用过这个机制。再举一个更完整的例子。假设你同时跑“真实系统”和“仿真系统”。真实系统节点 /vins_estimator 发布 /real/odom仿真系统节点 /simulator 发布 /sim/odomEGO 有两套/real/ego_planner /sim/ego_planner那么 launch 可以设计成group nsreal node pkgego_planner typeego_planner_node nameego_planner remap from/grid_map/odom to/vins_estimator/odometry/ /node /group另一套group nssim node pkgego_planner typeego_planner_node nameego_planner remap from/grid_map/odom to/simulator/odometry/ /node /group最终运行时就可以区分/real/ego_planner /sim/ego_planner以及真实 /vins_estimator/odometry 仿真 /simulator/odometry这样系统就非常清晰。你可以这样理解项目/工作空间 只是代码放在哪里 真正运行时 ROS只关心 谁是 node 谁发布哪个 topic 谁订阅哪个 topic 消息类型是什么所以运行时结构和磁盘目录结构是两回事。一、先讲一个“最简单的 ROS1 项目”应该有什么我们先假设你要做一个最简单的功能每秒打印一句 “hello ros”。工作空间可以是~/catkin_ws目录catkin_ws/ ├── src/ │ └── hello_ros/ │ ├── CMakeLists.txt │ ├── package.xml │ └── src/ │ └── hello_node.cpp ├── build/ └── devel/最核心的其实就三个东西package.xml CMakeLists.txt src/hello_node.cpppackage.xml格式XML。作用描述这个 package 是谁、依赖谁。例如package format2 namehello_ros/name version0.0.0/version descriptionSimple ROS package/description maintainer emaillzxexample.comlzx/maintainer licenseMIT/license buildtool_dependcatkin/buildtool_depend dependroscpp/depend dependstd_msgs/depend /package你可以把它理解为功能包身份证 依赖清单它告诉 ROS我的名字 hello_ros 我需要 roscpp std_msgs它不是用来“列出所有子文件”的。CMakeLists.txt格式CMake 脚本。作用告诉编译系统要找哪些依赖 哪些 cpp 要编译 编译成什么可执行程序 链接哪些库一个最简单例子cmake_minimum_required(VERSION 3.0.2) project(hello_ros) find_package(catkin REQUIRED COMPONENTS roscpp std_msgs ) catkin_package() include_directories( ${catkin_INCLUDE_DIRS} ) add_executable(hello_node src/hello_node.cpp) target_link_libraries( hello_node ${catkin_LIBRARIES} )可以把它理解成“编译说明书”src/hello_node.cpp格式C 源码。作用真正实现功能。例如#include ros/ros.h int main(int argc, char **argv) { ros::init(argc, argv, hello_node); ros::NodeHandle nh; ros::Rate rate(1); while (ros::ok()) { ROS_INFO(hello ros); rate.sleep(); } return 0; }这才是真正执行逻辑的程序。编译以后ROS 会生成一个可执行节点。所以.cpp ↓ 编译 可执行程序 ↓ rosrun / roslaunch Node最简单 ROS1 package 的逻辑关系可以记成package.xml → 我是谁我依赖谁 CMakeLists.txt → 怎么编译我 src/*.cpp → 我真正干什么这三个就是一个最基础 C ROS package 的核心。二、一个典型 ROS1 package 里面会有什么真实项目一般会复杂很多例如my_robot/ ├── package.xml ├── CMakeLists.txt │ ├── src/ │ ├── camera_node.cpp │ ├── localization_node.cpp │ └── planner_node.cpp │ ├── include/ │ └── my_robot/ │ ├── planner.h │ └── localization.h │ ├── launch/ │ ├── robot.launch │ ├── sensor.launch │ └── planner.launch │ ├── config/ │ ├── camera.yaml │ ├── vins.yaml │ └── planner.yaml │ ├── scripts/ │ └── imu_convert.py │ ├── msg/ │ └── Target.msg │ ├── srv/ │ └── Reset.srv │ ├── action/ │ └── Move.action │ ├── rviz/ │ └── robot.rviz │ └── urdf/ └── robot.urdf下面分别解释。src/格式通常是.cpp .c作用真正的 C/C 程序源码。例如planner_node.cpp可能做订阅 odometry 订阅 depth 计算路径 发布 trajectoryinclude/格式通常是.h .hpp作用C 头文件。例如class Planner { public: void plan(); };常见结构include/ └── package_name/ └── planner.h这个目录主要是给函数声明 类定义 模板 公共接口用的。launch/格式.launch本质上是 XML。作用批量启动节点并且设置参数、remap、namespace。例如launch node pkgmy_robot typeplanner_node nameplanner/ /launch它不是程序逻辑本身。它更像“启动编排文件”比如你现在run_with_d435i.launch就是在做启动 ego_planner_node 启动 traj_server 启动 waypoint_generator 设置 topic remap 设置参数config/通常放.yaml .yml作用保存运行参数。例如max_velocity: 2.0 max_acceleration: 3.0 odom_topic: /vins_estimator/odometryyaml 是参数表不是程序。程序启动时读取它。scripts/通常放.py .sh例如imu_optical_to_body.py你已经在用了。作用Python 节点、辅助脚本、工具程序。例如 Python ROS node#!/usr/bin/env python3import rospyrospy.init_node(imu_converter)msg/格式.msg作用自定义 ROS 消息。例如Target.msg内容float64 x float64 y float64 z string name编译以后就可以在代码里用my_robot::Target类似于你平时看到sensor_msgs/Image nav_msgs/Odometry geometry_msgs/Pose这些其实都是别的 package 定义好的消息。srv/格式.srv作用定义 ROS Service。例如ResetMap.srv内容可能bool force --- bool success string message---上面是请求下面是响应。它适合请求一次 返回一次例如清空地图 重新初始化 切换模式action/格式.action作用定义长时间任务。例如飞到目标点 机械臂运动 导航到目标它比 service 多Goal Feedback Result适合耗时任务。rviz/通常.rviz作用保存 RViz 显示配置。包括Fixed Frame Display列表 Topic 颜色 点大小 视角你刚刚保存的d435i_real.rviz就是这种。urdf/通常.urdf .xacro作用描述机器人结构。包括机体 相机 IMU 激光雷达 轮子 各坐标系之间的位置关系比如base_link camera_link imu_link都可以在 URDF 里定义。一个典型 package 可以这样记package.xml → 身份和依赖 CMakeLists.txt → 编译规则 src/ → C程序 include/ → 头文件 launch/ → 启动编排 config/ → 参数 scripts/ → Python / shell脚本 msg/ → 自定义消息 srv/ → 请求-响应 action/ → 长任务 rviz/ → RViz显示配置 urdf/ → 机器人结构和坐标系不是每个 package 都必须有全部这些目录。三、一个完整 ROS1 workspace 里面可以有很多 package例如uav_ws/ ├── src/ │ │ ├── camera_driver/ │ ├── imu_driver/ │ ├── vins/ │ ├── mapping/ │ ├── ego_planner/ │ ├── controller/ │ └── visualization/ │ ├── build/ └── devel/这些 package 可以互相通信。真正运行时大概是camera_driver ↓ /camera/image imu_driver ↓ /imu/data ↓ VINS ↓ /vins/odometry ↓ mapping ↓ /map ↓ ego_planner ↓ /trajectory ↓ controller所以 ROS 的核心思想其实不是一个大程序而是很多小节点 通过通信拼成系统四、不同功能包之间怎么通信ROS1 主要有三种机制Topic Service Action最重要的是 Topic。1. Topic连续数据流例如camera package 发布 /camera/image另外一个 packagevision package 订阅 /camera/image只要满足Topic名字一样 消息类型一样 同一个ROS Master就能通信。例如 Publisherros::Publisher pub nh.advertisesensor_msgs::Image( /camera/image, 10 );Subscriberros::Subscriber sub nh.subscribe( /camera/image, 10, imageCallback );它们不需要#include 对方的 cpp 调用对方函数 知道对方在哪个 workspace完全不需要。只认topic message type ROS master这就是 ROS 的解耦。2. Service一次请求一次回答例如/map/resetClient“请清空地图”Server“清空成功”像问问题 → 等答案适合重置 开关设备 获取一次数据3. Action长任务例如“飞到坐标(10,5,2)”不能只用 service因为执行可能要 20 秒。Action 可以发目标 ↓ 不断反馈进度 ↓ 最后返回结果例如Goal: 飞到目标 Feedback: 已经完成 30% Result: 到达五、不同 package 之间怎么“引用”依赖这里要区分两种情况A只通过 Topic 通信例如camera package → /camera/image → planner package这种情况下它们甚至可以完全不知道彼此 package 名。只要topic名 消息类型一致即可。情况Bpackage A 用到了 package B 定义的消息/库例如#include my_msgs/Target.h那 A 就必须声明依赖 B。package.xmldependmy_msgs/dependCMakeLists.txtfind_package(catkin REQUIRED COMPONENTS roscpp my_msgs )这样编译系统才知道去哪里找。六、你最关心的“串台”问题假设项目一Node A 发布 /imu/data 类型 sensor_msgs/Imu项目二Node B 也发布 /imu/data 类型 sensor_msgs/Imu现在另一个节点Node C 订阅 /imu/data如果 A、B 同时运行Node C ↑ /imu/data ↑ ↑ Node A Node BC 会收到两边的数据。所以 ROS 不会说这是项目1的 这是项目2的ROS 根本没有“项目”这个运行时概念。怎么避免最常用的是 namespace。比如项目1 /uav1/imu/data 项目2 /uav2/imu/data这样就分开了。launch 里可以group nsuav1 ... /group就会自动加前缀。例如原来 /imu/data 进入 namespace uav1 后 /uav1/imu/data也可以 remap。比如程序内部写死/imu/datalaunch 中remap from/imu/data to/uav1/imu/data/不用改 C。你现在其实已经用了 remap。例如 EGO 内部可能订阅/grid_map/odom你在 launch 里 remap 成/vins_estimator/odometry这就是程序内部接口 ↓ remap 实际系统topicROS1 运行时真正的“世界观”你以后可以完全这样理解磁盘结构 Workspace → Package → 文件 运行结构 Node → Topic → Service → Action它们不是一回事。源码在package里。一旦运行起来ROS Master 看到的是Node A Node B Node C Topic X Topic Y Topic Z而不是这是workspace1 这是workspace2所以你现在排查系统时经常用rosnode list因为是在看现在谁真的运行着用rostopic list是在看现在有哪些通信通道用rostopic info /xxx是在看谁发布 谁订阅这三条其实就是 ROS1 系统排错最重要的命令之一。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑