ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

ROS自用笔记

ROS自用笔记 启动节点ros2 run …ROS2 帮助ros2 --help识别问题ros2 doctor当前运行节点ros2 node list某节点详细信息ros node 节点名称 info查看话题的信息ros topic echo 话题名称查看消息类型的详细结构ros2 interface show 消息名称数据录制ros2 bag record 话题名称ros2 bag record -o 自定义名称 话题名称数据播放ros2 bag play 数据包名称以一半速度回放ros2 bag play my_bag --rate 0.5以两倍速度回放ros2 bag play my_bag --rate 2.0扫描src下所有包的package.xml安装apt依赖rosdep install --from-paths src --ignore-src -r -y创建包的时候直接指定依赖创建时添加cd ~/colcon_ws/srcros2 pkg create --build-type ament_python my_turtle_pkg --dependencies rclpy geometry_msgs turtlesim创建完包之后手动追加依赖事后添加Python 包ament_python需要修改 2 个文件package.xml和setup.pyC 包ament_cmake 需要修改 2 个文件package.xml和CMakeLists.txt修改完成后执行rosdep install --from-paths src --ignore-src -r -y编译功能包colcon buildsource 环境source install/setup.bash常见回调函数importrclpyfromrclpy.nodeimportNodefromrclpy.actionimportActionServerfromstd_msgs.msgimportStringfromexample_interfaces.srvimportSetBoolfromexample_interfaces.actionimportFibonacciclassMyCallbackNode(Node):def__init__(self):super().__init__(callback_demo_node)# # 1. 创建【话题回调】 (订阅者)# # 注册告诉ROS当 /chatter 有消息来时调用 topic_callbackself.subscriptionself.create_subscription(String,# 消息类型/chatter,# 话题名self.topic_callback,# 【指定回调函数】10# QoS深度)# # 2. 创建【定时器回调】# # 注册告诉ROS每隔1秒调用 timer_callbackself.timerself.create_timer(1.0,# 时间间隔秒self.timer_callback# 【指定回调函数】)# # 3. 创建【服务回调】 (服务端)# # 注册告诉ROS当收到 /reset 请求时调用 service_callbackself.srvself.create_service(SetBool,# 服务接口类型/reset,# 服务名self.service_callback# 【指定回调函数】)# # 4. 创建【动作回调】 (动作服务器)# # 注册告诉ROS当收到 /fibonacci 目标时调用 execute_callbackself.action_serverActionServer(self,Fibonacci,# 动作接口类型/fibonacci,# 动作名self.execute_callback# 【指定回调函数】执行函数)self.get_logger().info(所有回调已创建并注册完毕)# ---------- 以下是你需要【定义】的回调函数本体 ----------# 1. 话题回调接收一个参数消息本身deftopic_callback(self,msg):self.get_logger().info(f[话题回调] 收到:{msg.data})# 在这里写处理传感器/话题数据的逻辑# 2. 定时器回调没有参数定期执行deftimer_callback(self):self.get_logger().info([定时器回调] 嘀嗒... 执行周期性任务)# 在这里写发布状态、检查健康的逻辑# 3. 服务回调接收请求返回响应defservice_callback(self,request,response):self.get_logger().info(f[服务回调] 收到重置请求:{request.data})# 在这里写处理即时请求的逻辑ifrequest.data:response.successTrueresponse.message重置成功else:response.successFalseresponse.message忽略重置returnresponse# 注意必须返回响应# 4. 动作回调执行函数包含复杂的反馈和结果逻辑defexecute_callback(self,goal_handle):self.get_logger().info([动作回调] 开始执行长任务...)feedbackFibonacci.Feedback()resultFibonacci.Result()# 模拟长任务...foriinrange(5):ifgoal_handle.is_cancel_requested:goal_handle.canceled()returnresult feedback.partial_sequence[0,i]goal_handle.publish_feedback(feedback)# 发布反馈rclpy.sleep(1.0)goal_handle.succeed()result.sequence[0,1,2,3,4]returnresult# 返回最终结果# ---------- 主函数启动节点的“引擎” ----------defmain(argsNone):rclpy.init(argsargs)nodeMyCallbackNode()# 【重要】spin 会启动一个循环不断等待并驱动上述所有回调执行rclpy.spin(node)node.destroy_node()rclpy.shutdown()if__name____main__:main()列出当前系统所有正在运行的服务名ros2 service list#查询某个服务对应的接口类型srv 类型ros2 service type 服务名#根据服务接口类型反向查找有哪些服务在使用该类型ros2 service find srv类型话题信息格式要求float64 xfloat64 yfloat64 thetafloat64 linear_velocityfloat64 angular_velocity服务信息格式要求Service 服务用---三个减号分割 请求 (Request) 和 应答 (Response)# Request float32 x float32 y float32 theta string name --- # Response string name动作接口 .action目标反馈结果Action 动作使用两个 — 分割三段Goal 目标 / Feedback 反馈 / Result 结果# Goal 目标 float32 target_x float32 target_y --- # Feedback 过程反馈运行中不断上报 float32 current_x float32 current_y float32 progress_rate --- # Result 最终结果 bool success string message查看功能包接口ros2 interface package 包名列出节点上所有可用参数ros2 param list node_name获取指定参数的当前值ros2 param get node_name param_name在运行时动态修改参数值ros2 param set node_name param_name将节点所有当前参数保存为 .yaml 文件ros2 param dump node_name file_name从 .yaml 文件加载参数到正在运行的节点ros2 param load node_name file_name查看某个参数的描述信息ros2 param describe node_name param_name分布式通信1.保证双方可以ping通2.打开两台设备的终端输入相同的命令假设你们在同一个项目组编号设为 1export ROS_DOMAIN_ID1export ROS_DOMAIN_ID1 这条命令的核心作用是给当前这台设备发了一个“分组编号”的通行证。只有编号相同的设备才能在网络上互相“看见”并通信编号不同即使插在同一根网线上也会“视而不见”。3.执行相应的代码即可常用工具rviz2: 3D可视化平台用于直观显示机器人状态、传感器数据和坐标变换。它通过插件系统支持多种数据类型并可保存配置文件.rviz实现一键启动rqt: 模块化图形工具箱可自由组合插件Gazebo: ROS社区最流行的3D物理仿真平台。提供逼真的物理引擎和传感器模型支持复杂的机器人、传感器和环境交互。tf2: 坐标变换管理系统维护所有坐标系的树状关系在ROS中最常用、最强大的工具是 robot_localization 功能包自动导航和建图自动导航和建图的过程中都会涉及到/odom和/cmd_vel两个话题使用stm32小车上需要注意订阅或者其他方式对这两个话题进行处理实现小车的控制需要做的工作1.编写传感器的节点2.实现小车的tf树3.处理/odom和/cmd_vel两个话题基本概念ros 官网https://www.ros.org/ros2 humble安装https://docs.ros.org/en/humble/Installation/Ubuntu-Install-Debs.html动作、话题、服务对比MQTT和DDS区别这是两者最根本的架构差异。DDS去中心化的对等网络DDS 采用无代理brokerless架构节点间可直接通信。这种架构消除了单点故障提升了系统的鲁棒性和可扩展性。同时由于通信无需经过代理转发也减少了网络跳转从而能实现更低的延迟。MQTT中心化的代理架构MQTT 采用代理broker架构所有消息都通过中央代理转发。这种架构虽然便于管理和实现但也使代理成为潜在的性能瓶颈和单点故障风险。系统整体的可扩展性受限于代理的处理能力。创建节点如何在服务通信中的客户端节点 发送了异步请求但是他还需要执行其他的任务 是否会造成程序阻塞无法执行其他任务rclpy.spin_until_future_complete(self, self.future)使用 spin_until_future_complete 等待 —— 部分阻塞但能处理其他回调这是官方教程中最常见的写法。它会阻塞当前线程直到服务端返回响应。会阻塞什么程序会卡在这一行不会执行这一行之后的代码比如打印后续日志。不会阻塞什么在它等待的过程中ROS 后台仍然会处理其他话题的订阅回调、定时器回调。也就是说你的节点并没有“死机”它只是暂时停在这个函数里等着结果回来但雷达数据来了照样会去处理。IMU和odom结论是odom - base_link 是一个动态变换Dynamic Transform。虽然 IMU 确实安装在车体base_link上但odom坐标系的原点并不在小车上。为什么 odom 不在小车上根据 ROS 的标准规范 REP 105odom 被定义为一个 “世界固定world-fixed”坐标系。你可以把 odom 想象成机器人启动时在地面上用粉笔画的一个“十”字。这个“十”字odom 的原点固定在地面上不会随着机器人移动。机器人base_link则是从原点出发不断移动。那 odom - base_link 描述的是什么这个变换描述的正是机器人base_link相对于那个固定的起始点odom 原点在任意时刻的位置和姿态。当小车从原点向前走1米这个变换的平移部分就是 (x: 1.0, y: 0.0, z: 0.0)原地转个弯旋转部分也会相应变化。因为小车一直在动所以这个变换每时每刻都在变因此是动态的。那 IMU 数据到底用在哪里你的理解“IMU 肯定在小车上”完全正确。odom - base_link 这个动态变换正是基于小车上的IMU、轮式编码器等传感器数据计算出来的。轮式编码器测量轮子转了多少圈推算出小车的移动距离。IMU测量加速度和角速度推算出小车的姿态和位移。这些数据融合后例如通过 robot_localization 功能包就得到了小车相对于起始点的位姿然后以 odom - base_link 这个动态变换的形式发布出去。你的困惑主要源于将 odom 误认为是 base_link 上的一个点。实际上odom 是一个固定在空间中的参考点。odom - base_link 这个动态变换正是利用车上的 IMU 等传感器数据实时计算出小车相对于这个固定参考点的运动状态。小车每动一下这个变换都会更新所以它是动态的。相比之下像 base_link - laser_link 这种激光雷达在车身上安装位置不变的才是静态变换。MAP-odom️ 建图时map 坐标系的诞生当你第一次运行 SLAM 建图时算法会做一个“初始化”操作map 坐标系的起源算法通常会将机器人启动的那一刻、那个位置设定为 map 坐标系的原点 (0, 0, 0)。此后机器人绘制的所有地图、保存的所有路径点都基于这个原点。odom 坐标系的跟随在建图过程中odom 坐标系会不断地从 (0,0,0) 开始累积机器人的位移但会产生漂移。map - odom 变换的发布与此同时定位节点如 SLAM 算法会持续计算并发布 map 到 odom 的变换用 map 的精准位置实时修正 odom 的漂移从而得到机器人在地图中的真实位姿 (map - base_link)。建图完成后你会得到一张地图文件通常是 .pgm 图片和 .yaml 配置其中 map 坐标系的原点信息就保存在 .yaml 文件里。 关机再开后坐标系的“断裂”与“重建”当你把设备关机搬到另一个房间再开机一切就都变了map 坐标系不变的地图基准当你加载之前保存的地图时map 坐标系的原点和方向就被固定下来了。对系统来说地图和它的坐标系原点还在原来的地方并没有随机器人移动。odom 坐标系重置的局部起点机器人一开机里程计odom会从一个全新的起点开始这个点通常被称为 odom 原点。odom 坐标系会从这个新原点开始重新累积机器人的位移。由于机器人位置变了这个新的 odom 坐标系与原来的 map 坐标系之间的关系是断裂的、未知的。map - odom 变换待修正的桥梁系统目前只能通过里程计知道 odom - base_link 的关系但不知道机器人在 map 中的位置 (map - base_link)。因此map - odom 这个关键的修正变换此时是错误或缺失的。此时如果你直接下发导航目标机器人会因为不知道自己在哪里而无法行动。 关键一步如何“重定位”要恢复通信核心就是通过定位算法计算出新的 map - odom 变换。这通常通过以下方式实现手动重定位 (2D Pose Estimate)最常用的方法。在 RViz 中加载地图后手动点击“2D Pose Estimate”按钮在地图上大致点出机器人的当前位置和朝向。定位算法会以此为中心通过传感器数据匹配来精确定位。自动重定位 (Global Localization)更高级的方法。机器人开机后算法会自动将当前的传感器数据与整个地图进行匹配尝试“猜”出自己的位置。这要求算法更强大且计算资源更多。一旦定位成功系统就会持续发布 map - odom 变换至此整个通信链路 map - odom - base_link 就重新建立起来了。 总结整个流程可以概括为建图时map 坐标系原点被固定在机器人启动的位置并以此构建了整个地图的参考系。关机移动后map 坐标系地图保持不变而 odom 坐标系里程计则在一个新地点被重置。重定位需要通过手动或自动方式让机器人“找到”自己在新环境里、相对于旧地图的位置。恢复通信一旦找到位置系统就会重新建立起 map - odom 的变换让导航等功能恢复正常。移动小车中运动学正逆解的作用麦科姆轮的运动学模型https://blog.csdn.net/m0_55933541/article/details/1340407141. 逆运动学速度分解—— 把“语言”变成“动作”输入高层算法发来的标准速度指令 geometry_msgs/Twist比如“线速度 0.5 m/s角速度 0.2 rad/s”。输出左右电机的实际目标转速比如“左轮 300 RPM右轮 350 RPM”。为什么要算因为电机听不懂“米/秒”电机只听得懂“每秒转多少圈RPM”或者“给多少PWM电压”。如果不算逆解导航模块Nav2说“向右转弯”你直接让左轮转、右轮不转这只能叫“随机瞎转”。算逆解的目的是为了让车身的运动学模型轮距、半径精确地介入确保底盘实际画出的圆弧轨迹完全符合导航模块下发的圆弧轨迹。2. 正运动学里程计推算—— 把“动作”变成“位置”输入左右轮编码器反馈回来的实际转速比如“左轮跑了 310 RPM右轮跑了 340 RPM”。输出机器人当前在 odom 坐标系下的坐标 (x, y, θ)“我现在大概在 (1.5米, 0.2米)朝向 15度”。为什么要算因为轮子不知道自己滚到了哪里轮子只知道滚了多少圈。你必须通过左右轮的转动圈数和轮距利用几何公式正解推算出机器人整体相对起点的位移和角度。如果不算正解ROS 的 odom 话题永远是 0导航系统不知道机器人动了没有。最致命的问题你发指令让它直走 1 米但因为地面打滑或者电压不稳实际只走了 0.8 米。因为没有正解反馈系统还以为自己走了 1 米下次继续按照 1 米来规划结果越跑越偏根本无法闭环修正。3. 两者结合构成“闭环控制”的生命线移动小车的精髓在于闭环而正逆解是这个环上最关键的上下游下发期望逆解速度分解把 cmd_vel 转成电机目标转速。执行电机驱动板让轮子转起来。测量实际编码器测出轮子真实转了多少。反馈位姿正解里程计把真实转速换算成真实位移 (x,y,θ)反馈回上层。修正误差上层比较“目标位姿”和“真实位姿”如果走歪了下次下发新的 cmd_vel 修正。 一句话终极总结在移动小车中计算逆解是为了把“抽象的导航指令”翻译成“具体的电机电压”让车能动起来计算正解是为了把“枯燥的轮子圈数”翻译成“车身的位置姿态”让车知道动到哪了。多传感器数据融合使用 robot_localization这是ROS生态中久经考验的传感器融合“瑞士军刀”。它基于扩展卡尔曼滤波EKF 或无迹卡尔曼滤波UKF。简单来说ekf.yaml 是 robot_localization 功能包中 ekf_node 的配置文件。你可以把它理解为一份详尽的“说明书”它告诉 EKF 节点该听谁的数据来源、数据里哪部分可信状态向量以及最终要把结果发到哪里去。它的核心工作是运行一个扩展卡尔曼滤波器EKF将来自多个传感器的数据进行融合从而得到一个比任何单个传感器都更准确、更可靠的机器人状态估计。 数据从何而来ekf.yaml 通过 odom0、imu0 等参数指定了 EKF 监听的数据源话题。EKF 节点会订阅这些话题并等待数据到来。常见的数据源包括nav_msgs/Odometry: 来自轮式编码器的里程计数据。sensor_msgs/Imu: 来自IMU惯性测量单元 的数据。geometry_msgs/PoseWithCovarianceStamped: 来自视觉里程计或GPS等其他定位系统的位姿数据。⚙️ 它是如何工作的ekf_node 启动后会读取 ekf.yaml 的配置其核心是一个扩展卡尔曼滤波器EKF算法。它的工作流程可以简化为一个两步循环预测 (Prediction)EKF 根据机器人当前的状态位置、速度等和运动模型预测出下一时刻的状态。这一步很像“惯性导航”即使没有外部传感器数据它也能短暂地推算位置。更新 (Correction)当来自里程计、IMU等传感器的测量数据到达时EKF 会将其与预测值进行对比。然后根据 ekf.yaml 中配置的噪声协方差判断对两者的信任程度最终融合出一个最优的估计值。数据越可信其在融合中的权重就越大。这个预测-更新循环以 frequency 设定的频率不断运行持续输出最优估计。 发布话题与成果ekf_node 融合数据后主要会发布以下成果/odometry/filtered (话题): 这是 EKF 融合后的最终里程计结果消息类型是 nav_msgs/Odometry。它比原始的轮式里程计更平滑、更精确。/tf (坐标变换): EKF 节点会广播一个从 odom 坐标系到 base_link或 base_footprint坐标系的 TF 变换。其他节点如 AMCL就可以通过监听这个 TF 来获取机器人在地图中的位置。 总结ekf.yaml 是 robot_localization 功能的“总设计师”它通过配置将轮式里程计和 IMU 等传感器的原始数据融合成一个更高质量的 /odometry/filtered 话题和 /tf 坐标变换为机器人导航、定位等高级功能提供了坚实的数据基础。/odometry/filtered 和/odom直接回答你的问题 EKF 解决轮子漂移的方式并不是把滤波后的数据塞回 /odom 话题而是通过覆盖 TF 树坐标变换树 和提供新的数据源来实现的。即便滤波后的数据发到了 /odometry/filtered但机器人导航Nav2和定位AMCL依赖的主体是 /tf 坐标变换而不是 /odom 话题。为了让你彻底明白我分三层来拆解真正的“修正”是如何发生的靠 TF不靠话题没有 EKF 时你的编码器节点发布 odom - base_link 的 TF这个 TF 因为轮子打滑存在漂移误差。有了 EKF 后EKF 融合了 IMU 和编码器计算出更真实的位姿。然后EKF 节点直接向 TF 树广播了新的 odom - base_link 变换只要你在配置中开启 publish_tf: true。关键点来了Nav2导航栈、tf_echo、AMCL 这些节点它们根本不读取 /odom 话题的数据它们永远只读取 /tf 树里的 odom - base_link 变换。因此当你启动 EKF 后虽然 /odom 话题里的数据依然是带漂移的原始数据但 /tf 树里的坐标变换已经被 EKF 偷偷“掉包”成修正后的精准数据了。导航程序依赖 TF拿到的已经是修正后的位置所以漂移问题被完美解决了那 /odometry/filtered 话题是干什么用的给其他程序看的数据/odometry/filtered 话题是 EKF 提供的“数字读数”它的用途主要有两个给感知/规划节点用有些算法如路径规划的状态机、保存路径记录的工具不关心 TF只想要一个数字化的里程计消息来进行逻辑判断。给其他融合节点用如果你还有 GPS 或视觉定位要融合可以订阅 /odometry/filtered 作为另一个传感器输入配置成 odom1。
返回列表