3、ROS2基础与开发环境搭建:ROS2 Humble安装,工作空间创建,Topic/Service通信,Launch文件编写,Rviz2可视化
各位同学,欢迎来到第三章。说实话,ROS2这块内容,我当年刚接触时也踩了不少坑。尤其是从ROS1迁移过来那会儿,很多习惯都得改。但别担心,这一章我会把最实用的东西拎出来,咱们一步步搭好环境,把基础打牢。
3.1 ROS2 Humble 安装——别走弯路
ROS2的版本很多,为什么我推荐Humble?因为它是个长期支持版(LTS),稳定性好。我在项目里用过Foxy、Galactic,最后还是觉得Humble最省心。你想想看,做SLAM建图,系统稳定性是第一位的,谁也不想跑着跑着节点崩了。
安装前,先确认你的Ubuntu版本。Humble对应的是Ubuntu 22.04。如果你用的是20.04,那就装Foxy或者Galactic。别搞混了,否则依赖会出问题。
安装步骤其实很简单,核心就这几条命令:
# 设置locale
sudo apt update && sudo apt install locales
sudo locale-gen en_US en_US.UTF-8
sudo update-locale LC_ALL=en_US.UTF-8 LANG=en_US.UTF-8
# 添加ROS2源
sudo apt install software-properties-common
sudo add-apt-repository universe
sudo apt update && sudo apt install curl -y
sudo curl -sSL https://raw.githubusercontent.com/ros/rosdistro/master/ros.key -o /usr/share/keyrings/ros-archive-keyring.gpg
echo "deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/ros-archive-keyring.gpg] http://packages.ros.org/ros2/ubuntu $(. /etc/os-release && echo $UBUNTU_CODENAME) main" | sudo tee /etc/apt/sources.list.d/ros2.list > /dev/null
# 安装ROS2 Humble桌面版(推荐)
sudo apt update
sudo apt install ros-humble-desktop
# 安装开发工具
sudo apt install ros-dev-tools
装完之后,别忘了配置环境变量。我个人习惯把它写到 ~/.bashrc 里:
echo "source /opt/ros/humble/setup.bash" >> ~/.bashrc
source ~/.bashrc
验证一下:ros2 --version。如果看到版本号,恭喜你,环境搭好了。
alias ros1='source /opt/ros/noetic/setup.bash',这样切换起来方便。
3.2 工作空间创建——你的代码仓库
工作空间,说白了就是放代码的地方。ROS2里我们用的是colcon来编译,跟ROS1的catkin不太一样。我第一次用colcon时还觉得别扭,用习惯了才发现,真香。
创建一个工作空间很简单:
mkdir -p ~/ros2_ws/src
cd ~/ros2_ws
colcon build
编译完成后,你会看到 build、install、log 三个文件夹。记得每次编译完都要source一下:
source install/setup.bash
你也可以把它加到.bashrc里,省得每次手动敲。不过我个人建议,项目多的时候还是手动source比较好,避免环境冲突。
3.3 Topic/Service通信——ROS2的灵魂
ROS2的通信机制,说白了就两种:Topic和Service。Topic是发布/订阅模式,Service是请求/响应模式。做SLAM时,激光雷达数据就是通过Topic发布的,而调用地图保存功能则用Service。
3.3.1 Topic通信
先看一个最简单的发布者。假设我们要发布一个字符串消息:
// talker.cpp
#include "rclcpp/rclcpp.hpp"
#include "std_msgs/msg/string.hpp"
int main(int argc, char *argv[]) {
rclcpp::init(argc, argv);
auto node = std::make_shared<rclcpp::Node>("talker");
auto publisher = node->create_publisher<std_msgs::msg::String>("topic", 10);
rclcpp::WallRate loop_rate(1);
while (rclcpp::ok()) {
auto msg = std_msgs::msg::String();
msg.data = "Hello, SLAM!";
publisher->publish(msg);
rclcpp::spin_some(node);
loop_rate.sleep();
}
rclcpp::shutdown();
return 0;
}
对应的订阅者:
// listener.cpp
#include "rclcpp/rclcpp.hpp"
#include "std_msgs/msg/string.hpp"
void topic_callback(const std_msgs::msg::String::SharedPtr msg) {
RCLCPP_INFO(rclcpp::get_logger("rclcpp"), "I heard: '%s'", msg->data.c_str());
}
int main(int argc, char *argv[]) {
rclcpp::init(argc, argv);
auto node = std::make_shared<rclcpp::Node>("listener");
auto subscription = node->create_subscription<std_msgs::msg::String>("topic", 10, topic_callback);
rclcpp::spin(node);
rclcpp::shutdown();
return 0;
}
编译时别忘了在CMakeLists.txt里加上:
find_package(rclcpp REQUIRED)
find_package(std_msgs REQUIRED)
add_executable(talker src/talker.cpp)
ament_target_dependencies(talker rclcpp std_msgs)
add_executable(listener src/listener.cpp)
ament_target_dependencies(listener rclcpp std_msgs)
install(TARGETS talker listener DESTINATION lib/${PROJECT_NAME})
rclcpp::spin_some(node),结果消息发不出去。ROS2的spin机制跟ROS1不太一样,一定要记得调用。
3.3.2 Service通信
Service适合那种一问一答的场景。比如你问机器人“当前位置在哪?”,它返回一个坐标。定义Service接口:
# GetPosition.srv
float32 x
float32 y
---
float32 position_x
float32 position_y
服务端代码:
# server.py
import rclpy
from rclpy.node import Node
from std_srvs.srv import Trigger
class PositionServer(Node):
def __init__(self):
super().__init__('position_server')
self.srv = self.create_service(Trigger, 'get_position', self.callback)
def callback(self, request, response):
response.success = True
response.message = "x: 1.0, y: 2.0"
return response
def main():
rclpy.init()
node = PositionServer()
rclpy.spin(node)
rclpy.shutdown()
客户端调用:
# client.py
import rclpy
from rclpy.node import Node
from std_srvs.srv import Trigger
class PositionClient(Node):
def __init__(self):
super().__init__('position_client')
self.cli = self.create_client(Trigger, 'get_position')
while not self.cli.wait_for_service(timeout_sec=1.0):
self.get_logger().info('service not available, waiting...')
self.req = Trigger.Request()
def send_request(self):
self.future = self.cli.call_async(self.req)
rclpy.spin_until_future_complete(self, self.future)
return self.future.result()
def main():
rclpy.init()
client = PositionClient()
response = client.send_request()
client.get_logger().info(f'Result: {response.message}')
rclpy.shutdown()
3.4 Launch文件编写——一键启动所有节点
做SLAM时,你往往要同时启动好几个节点:激光雷达驱动、里程计、建图算法...一个个手动敲命令?太累了。Launch文件就是干这个的。
ROS2的Launch文件用Python写,比ROS1的XML灵活多了。看个例子:
# slam_launch.py
from launch import LaunchDescription
from launch_ros.actions import Node
def generate_launch_description():
return LaunchDescription([
Node(
package='slam_toolbox',
executable='async_slam_toolbox_node',
name='slam_node',
output='screen',
parameters=[{'use_sim_time': False}]
),
Node(
package='rviz2',
executable='rviz2',
name='rviz2',
arguments=['-d', '/path/to/config.rviz']
)
])
启动方式:
ros2 launch your_package slam_launch.py
3.5 Rviz2可视化——让数据看得见
做SLAM,没有可视化工具就像闭着眼睛开车。Rviz2就是ROS2里的可视化利器。启动方式很简单:
ros2 run rviz2 rviz2
进去之后,你需要手动添加显示项。常用的有:
- LaserScan:显示激光雷达数据
- Map:显示建好的栅格地图
- TF:显示坐标系变换关系
- Path:显示机器人轨迹
我个人习惯把配置保存下来,下次直接加载。点击左上角的 File → Save Config As,保存为 .rviz 文件。然后在Launch文件里指定这个配置,省得每次重新调。
好了,这一章的内容就到这里。环境搭好了,通信机制理解了,Launch文件会写了,Rviz2也能用了。下一章我们开始真正接触激光雷达数据,看看怎么把原始点云变成有用的信息。