ROS 入门教程(含开发调试配置)
本文最后更新于 451 天前,内容如有失效请评论区留言。

由于安装 ROS 确实很 easy,官方文档已经是保姆级的教程了,这里不在赘述。本文的实验环境是 Ubuntu 20.04 + ROS Noetic Ninjemys

运行第一个 ROS 包

sudo apt install ros-noetic-rqt-robot-steering
sudo apt install ros-noetic-turtlesim
roscore
rosrun rqt_robot_steering rqt_robot_steering
rosrun turtlesim turtlesim_node

image-20240111215342376

image-20240111215609321

从 Github 下载软件包编译

mkdir catkin_ws
cd catkin_ws
mkdir src
cd src
git clone https://github.com/6-robot/wpr_simulation.git
cd wpr_simulation/scripts
./install_for_noetic.sh

然后返回 ~/catkin_ws 目录下,

catkin_make
cd devel
source ~/catkin_ws/devel/setup.zsh
roslaunch wpr_simulation wpb_simple.launch
rosrun rqt_robot_steering rqt_robot_steering

image-20240111232331871

source ~/catkin_ws/devel/setup.zsh 添加到 ~/.zshrc 中。

image-20240112001510160

VSCode 配置开发环境

在 VSCode 中打开 ~/catkin_ws/src 文件夹,安装 ROS 和 CMake Tools 插件

image-20240111234738068

按住 Ctrl + Shift + B,选择 catkin_make: build 选项,就可以编译代码了。

此外,点击 catkin_make: build 旁边的设置齿轮图标,进入 task.json,修改 group 字段后内容如下:

{
"version": "2.0.0",
"tasks": [
{
"type": "catkin_make",
"args": [
"--directory",
"/home/lrl52/catkin_ws",
"-DCMAKE_BUILD_TYPE=RelWithDebInfo"
],
"problemMatcher": [
"$catkin-gcc"
],
"group": {
"kind": "build",
"isDefault": true
},
"label": "catkin_make: build"
}
]
}

此后直接按住 Ctrl + Shift + B 快捷键就可以编译了。

关闭 CMake 插件的自动配置,

image-20240111235649335

创建第一个 Node 结点

catkin_create_pkg ssr_pkg rospy roscpp std_msgs

image-20240111235923631

创建后得到如下文件夹和文件,

image-20240112000126195

image-20240112000402123

image-20240112000213100

创建 src/chao_node.cpp,编写如下代码:

#include <ros/ros.h>
int main(int argc, char *argv[]) {
printf("hello, ros world!\n");
return 0;
}

在 CMakeLists.txt 里添加 add_executable(chao_node src/chao_node.cpp)

快捷键编译,然后

roscore
rosrun ssr_pkg chao_node

可以看到终端输出了 hello, ros world!

完善上述代码,

#include <ros/ros.h>
int main(int argc, char *argv[]) {
ros::init(argc, argv, "chao_node");
printf("Hello, ros world!\n");
while (ros::ok()) {
printf("ROS is running\n");
}
return 0;
}

在 CMakeLists.txt 里添加 target_link_libraries(chao_node ${catkin_LIBRARIES})。完整 CMakeLists.txt 如下:

cmake_minimum_required(VERSION 3.0.2)
project(ssr_pkg)
find_package(catkin REQUIRED COMPONENTS
roscpp
rospy
std_msgs
)
catkin_package(
# INCLUDE_DIRS include
# LIBRARIES ssr_pkg
# CATKIN_DEPENDS roscpp rospy std_msgs
# DEPENDS system_lib
)
include_directories(
# include
${catkin_INCLUDE_DIRS}
)
add_executable(chao_node src/chao_node.cpp)
target_link_libraries(chao_node
${catkin_LIBRARIES}
)

再次编译运行,得到如下结果

image-20240112002509957

Topic 与 Message

std_msgs – ROS Wiki

Publisher 实现

chao_node.cpp 中编写如下代码,

#include <ros/ros.h>
#include <std_msgs/String.h>
int main(int argc, char *argv[]) {
ros::init(argc, argv, "chao_node");
ros::NodeHandle nh;
ros::Publisher pub = nh.advertise<std_msgs::String>("topic_name", 25); // ("话题名称", 缓冲区长度)
ros::Rate loop_rate(25); // 每秒 25 次
printf("Hello, ros world!\n");
while (ros::ok()) {
std_msgs::String msg;
msg.data = "message from chao_node";
pub.publish(msg); // 发布话题
// printf("ROS is running\n");
loop_rate.sleep();
}
return 0;
}

编译,然后

rosrun
rosrun ssr_pkg chao_node

使用 rostopic list 可以查看当前 ROS 有哪些已经发布的 topic,

image-20240112092750253

使用 rostopic echo /topic_name 查看对于 topic 里的 message。rostopic hz /topic_name 可以查看 topic 的发送频率。

image-20240112092918154

Subscriber 实现

catkin_create_pkg atr_pkg rospy roscpp std_msgs 创建一个新的 package。编写如下代码:

#include <ros/ros.h>
#include <std_msgs/String.h>
#include <iostream>
void chao_callback(const std_msgs::String &msg) {
// std::cout << msg.data << "\n";
ROS_INFO(msg.data.c_str());
ROS_WARN(msg.data.c_str());
}
int main(int argc, char *argv[]) {
ros::init(argc, argv, "ma_node");
ros::NodeHandle nh;
ros::Subscriber sub = nh.subscribe("topic_name", 10, chao_callback);
while (ros::ok()) {
ros::spinOnce();
}
return 0;
}

补全 CMakelists.txt 的内容(同上),接着

roscore
src rosrun ssr_pkg chao_node
src rosrun atr_pkg ma_node

可以看到效果如下图

image-20240112100640249

时间戳是从 1970 年 1 月 1 日开始的 UNIX 时间戳,即当前距离 1970 年 1 月 1 日的秒数。

使用 rqt_graph 可以图形化显示 topic 通讯关系,

image-20240112101008321

另外注意,话题并不属于发布者或者订阅者,而是由 ROS 系统创建管理的,只要向 NodeHandle 提出了话题订阅需求或者话题发布需求,话题就会自动创建。

添加一个新的 Publisher 节点

ssr_pkg/src 下新建 yao_node.cpp,编写如下代码:

#include <ros/ros.h>
#include <std_msgs/String.h>
int main(int argc, char *argv[]) {
ros::init(argc, argv, "yao_node");
ros::NodeHandle nh;
ros::Publisher pub = nh.advertise<std_msgs::String>("yao_topic", 25); // ("话题名称", 缓冲区长度)
ros::Rate loop_rate(25); // 每秒 25 次
printf("Hello, ros world!\n");
while (ros::ok()) {
std_msgs::String msg;
msg.data = "message from yao_node";
pub.publish(msg); // 发布话题
// printf("ROS is running\n");
loop_rate.sleep();
}
return 0;
}

更改 atr_pkg/src/ma_node.cpp 中的代码为如下:

#include <ros/ros.h>
#include <std_msgs/String.h>
#include <iostream>
void chao_callback(const std_msgs::String &msg) {
ROS_INFO(msg.data.c_str());
}
void yao_callback(const std_msgs::String &msg) {
ROS_WARN(msg.data.c_str());
}
int main(int argc, char *argv[]) {
ros::init(argc, argv, "ma_node");
ros::NodeHandle nh;
ros::Subscriber sub1 = nh.subscribe("chao_topic", 10, chao_callback);
ros::Subscriber sub2 = nh.subscribe("yao_topic", 10, yao_callback);
while (ros::ok()) {
ros::spinOnce();
}
return 0;
}

同时在 ssr_pkg/CMakeLists.txt 中添加

add_executable(yao_node src/yao_node.cpp)
target_link_libraries(yao_node
${catkin_LIBRARIES}
)

运行效果如下:

image-20240112170458344

image-20240112170518314

Python 实现 Publisher 和 Subscriber

chao_node 为例,

catkin_create_pkg ssr_pypkg rospy roscpp std_msgs
cd ..
catkin_make # py 本身不需要编译,这里只是为了让 ROS 能够找到这个包

然后新建 scripts 文件夹,添加 py 文件,

image-20240112181243820

编写如下 chao_node.py 代码:

#!/usr/bin/env python3
#coding=utf-8
import rospy
from std_msgs.msg import String
if __name__ == "__main__":
rospy.init_node("chao_node")
rospy.logwarn("Hello, ros world from python!")
pub = rospy.Publisher("chao_room", String, queue_size=10)
rate = rospy.Rate(15)
while not rospy.is_shutdown():
rospy.loginfo("hahaha")
msg = String()
msg.data = "message from chao_node"
pub.publish(msg)
rate.sleep()

yao_node.py 也是同理,这里略去。再创建 atr_pypkg 包,

catkin_create_pkg atr_pypkg rospy roscpp std_msgs
cd ..
catkin_make

编写如下 ma_node.py 代码:

#!/usr/bin/env python3
#coding=utf-8
import rospy
from std_msgs.msg import String
def chao_callback(msg : String) -> None:
rospy.loginfo(msg.data)
def yao_callback(msg : String) -> None:
rospy.logwarn(msg.data)
if __name__ == "__main__":
rospy.init_node("ma_node")
sub1 = rospy.Subscriber("chao_room", String, chao_callback, queue_size=10)
sub2 = rospy.Subscriber("yao_room", String, yao_callback, queue_size=10)
rospy.spin()

使用 launch 文件启动多个 ROS 节点

递归创建 atr_pkg/launch/start.launch,编写如下启动 XML 文件,

<launch>
<node pkg="ssr_pkg" type="yao_node" name="yao_node"/>
<node pkg="ssr_pkg" type="chao_node" name="chao_node" launch-prefix="gnome-terminal -e"/>
<node pkg="atr_pkg" type="ma_node" name="ma_node" output="screen"/>
</launch>

相关解释如下:

image-20240112164300674

效果如下图

image-20240112172000567

机器人运动控制

ROS Index – geometry_msgs

运动控制使用的是 geometry_msgs/Twist Message 速度消息包,可发送六轴数据

image-20240114164701083

运动控制的话题名称一般是 /cmd_vel

image-20240114164908485

实现思路如下:

image-20240114170144027

catkin_create_pkg vel_pkg rospy roscpp geometry_msgs
cd ..
catkin_make

C++ 版本

vel_pkg/src 中创建 vel_node.cpp,编写如下代码:

#include <ros/ros.h>
#include <geometry_msgs/Twist.h>
int main(int argc, char *argv[]) {
ros::init(argc, argv, "vel_node");
ros::NodeHandle n;
ros::Publisher vel_pub = n.advertise<geometry_msgs::Twist>("/cmd_vel", 10);
geometry_msgs::Twist vel_msg;
vel_msg.linear.x = 0.1;
vel_msg.linear.y = 0;
vel_msg.linear.z = 0;
vel_msg.angular.x = 0;
vel_msg.angular.y = 0;
vel_msg.angular.z = 0;
ros::Rate rate(50);
while (ros::ok()) {
vel_pub.publish(vel_msg);
rate.sleep();
}
return 0;
}

在 CMakeLists.txt 添加如下规则:

add_executable(vel_node src/vel_node.cpp)
target_link_libraries(vel_node
${catkin_LIBRARIES}
)

然后

roscore
roslaunch wpr_simulation wpb_simple.launch
rosrun vel_pkg vel_node

image-20240114172849627

Python 版本

vel_pkg/scripts 中创建 vel_node.py,编写如下代码:

#!/usr/bin/env python3
# coding=utf-8
import rospy
from geometry_msgs.msg import Twist
if __name__ == "__main__":
rospy.init_node("vel_node")
vel_pub = rospy.Publisher("/cmd_vel", Twist, queue_size=10)
vel_msg = Twist()
vel_msg.linear.x = -0.1
rate = rospy.Rate(50)
while not rospy.is_shutdown():
vel_pub.publish(vel_msg)
rate.sleep()

运行方式同上,效果同上。

有趣的是,我发现这个 topic 名称填写 /cmd_velcmd_vel 都可以。

image-20240114180657780

激光雷达与使用 RViz 观测传感器数据

  • Gazebo 是模拟真是机器人发出传感器数据的工具。

  • RViz 是接收传感器数据并负责进行显示的工具。

image-20240115162527924

简单操作流程图

image-20240115164805701

image-20240115164921214

然后同样的方式添加 LazerScan,把左侧的 LazerScan 的 Topic 设置为 /scan,同时可以调整 Size,

image-20240115165344858

image-20240115165854803

保存配置文件,

image-20240115170143154

之后再重新打开即可。

获取激光雷达数据

ROS Index – sensor_msgs

[sensor_msgs/LaserScan Message](sensor_msgs/LaserScan Message)

image-20240115171620412

image-20240115172245756

C++ 版本

image-20240115173222523

image-20240115173508009

查看 LazerScan 消息包:

rostopic echo /scan --noarr

image-20240115175133352

catkin_create_pkg lidar_pkg roscpp rospy sensor_msgs

src/lidar_node.cpp 中编写如下代码:

#include <ros/ros.h>
#include <sensor_msgs/LaserScan.h>
void LidarCallback(const sensor_msgs::LaserScan msg) {
auto dis = msg.ranges[179];
ROS_INFO("前方测距 range[179] = %f 米", dis);
}
int main(int argc, char *argv[]) {
setlocale(LC_ALL, "");
ros::init(argc, argv, "lidar_node");
ros::NodeHandle nh;
ros::Subscriber sub = nh.subscribe<sensor_msgs::LaserScan>("/scan", 10, LidarCallback);
ros::spin();
return 0;
}

修改 CMakeLists.txt 文件,添加如下规则。(从这之后不再赘述了)

add_executable(lidar_node src/lidar_node.cpp)
target_link_libraries(lidar_node
${catkin_LIBRARIES}
)

效果如下:

image-20240115180805893

Python 版本

#!/usr/bin/env python3
#coding=utf-8
import rospy
from sensor_msgs.msg import LaserScan
def LidarCallback(msg : LaserScan):
dis = msg.ranges[180]
rospy.loginfo("前方测距 ranges[180] = {} 米".format(dis))
if __name__ == "__main__":
rospy.init_node("lidar_node")
sub = rospy.Subscriber("/scan", LaserScan, LidarCallback, queue_size=10)
rospy.spin()

激光雷达避障

image-20240116140243034

image-20240116140343030

C++ 版本

lidar_node.cpp 中完善代码:

#include <ros/ros.h>
#include <sensor_msgs/LaserScan.h>
#include <geometry_msgs/Twist.h>
ros::Publisher vel_pub;
static int nCount = 0;
void LidarCallback(const sensor_msgs::LaserScan msg) {
auto dis = msg.ranges[179];
ROS_INFO("前方测距 range[179] = %f 米", dis);
if (nCount > 0) {
nCount--;
return;
}
geometry_msgs::Twist vel_msg;
if (dis < 1.5f) {
vel_msg.angular.z = 0.5;
nCount = 50;
} else {
vel_msg.linear.x = 0.05;
}
vel_pub.publish(vel_msg);
}
int main(int argc, char *argv[]) {
setlocale(LC_ALL, "");
ros::init(argc, argv, "lidar_node");
ros::NodeHandle nh;
ros::Subscriber sub = nh.subscribe<sensor_msgs::LaserScan>("/scan", 10, LidarCallback);
vel_pub = nh.advertise<geometry_msgs::Twist>("/cmd_vel", 10);
ros::spin();
return 0;
}

但我有两个问题还不太能理解:

  1. 为什么 LaserScan 消息包的 scan_timetime_increment 字段是 0
  2. 在创建包的时候并没有引入 geometry_msgs 依赖,似乎没有什么影响

UP 主的补充回答:

  1. 仿真环境是这样的,只有真机跑起来才有时间

  2. 创建包的时候填写的依赖项,会被写在 CMakeLists.txt 和 package.xml 里。这个依赖项有点意思,我发现只要有 roscpp 就可以使用 ROS 内置的消息类型。猜想可能是 roscppConfig.cmake 的设置有关

image-20240116144056866

image-20240116143731598

Python 版本

lidar_node.py 中完善代码:

#!/usr/bin/env python3
#coding=utf-8
import rospy
from sensor_msgs.msg import LaserScan
from geometry_msgs.msg import Twist
nCount = 0
def LidarCallback(msg : LaserScan):
global nCount, vel_pub
dis = msg.ranges[180]
rospy.loginfo("前方测距 ranges[180] = {:.4f} 米".format(dis))
vel_msg = Twist()
if nCount > 0:
nCount -= 1
rospy.logwarn("持续转向 nCount = {}".format(nCount))
return
if dis < 1.5:
vel_msg.angular.z = 0.3
nCount = 50
else:
vel_msg.linear.x = 0.05
vel_pub.publish(vel_msg)
if __name__ == "__main__":
rospy.init_node("lidar_node")
sub = rospy.Subscriber("/scan", LaserScan, LidarCallback, queue_size=10)
vel_pub = rospy.Publisher("/cmd_vel", Twist, queue_size=10)
rospy.spin()

获取 IMU 数据

ROS Index – sensor_msgs

[sensor_msgs/Imu Message](sensor_msgs/Imu Message)

image-20240116160031817

image-20240116160308002

image-20240116160407573

catkin_create_pkg imu_pkg roscpp rospy sensor_msgs

C++ 版本

imu_pkg/src 中编写 imu_node.cpp

#include <ros/ros.h>
#include <sensor_msgs/Imu.h>
#include <tf/tf.h>
void IMUCallback(const sensor_msgs::Imu msg) {
if (msg.orientation_covariance[0] < 0.0) return;
tf::Quaternion q(
msg.orientation.x,
msg.orientation.y,
msg.orientation.z,
msg.orientation.w
);
double roll, pitch, yaw;
tf::Matrix3x3(q).getRPY(roll, pitch, yaw);
ROS_INFO("roll = %f, pitch = %f, yaw = %f", roll * 180 / M_PI, pitch * 180 / M_PI, yaw * 180 / M_PI);
}
int main(int argc, char *argv[]) {
setlocale(LC_ALL, "");
ros::init(argc, argv, "imu_data");
ros::NodeHandle nh;
ros::Subscriber sub = nh.subscribe("imu/data", 100, IMUCallback);
ros::spin();
return 0;
}

效果如下:

image-20240116165246920

Python 版本

#!/usr/bin/env python3
#coding=utf-8
import rospy
from sensor_msgs.msg import Imu
from tf.transformations import euler_from_quaternion
import math
def IMUCallback(msg : Imu):
if msg.orientation_covariance[0] < 0:
return
q = [msg.orientation.x, msg.orientation.y,
msg.orientation.z, msg.orientation.w]
roll, pitch, yaw = euler_from_quaternion(q)
rospy.loginfo("roll = {:.2f}, pitch = {:.2f}, yaw = {:.2f}".format(
roll * 180 / math.pi,
pitch * 180 / math.pi,
yaw * 180 / math.pi
))
if __name__ == "__main__":
rospy.init_node("imu_node")
imu_sub = rospy.Subscriber("/imu/data", Imu, IMUCallback, queue_size=10)
rospy.spin()

IMU 实现航向锁定

C++ 版本

继续在 imu_pkg/src 中完善 imu_node.cpp

#include <ros/ros.h>
#include <sensor_msgs/Imu.h>
#include <tf/tf.h>
#include <geometry_msgs/Twist.h>
ros::Publisher vel_pub;
void IMUCallback(const sensor_msgs::Imu msg) {
if (msg.orientation_covariance[0] < 0.0) return;
tf::Quaternion q(
msg.orientation.x,
msg.orientation.y,
msg.orientation.z,
msg.orientation.w
);
double roll, pitch, yaw;
tf::Matrix3x3(q).getRPY(roll, pitch, yaw);
ROS_INFO("roll = %f, pitch = %f, yaw = %f", roll * 180 / M_PI, pitch * 180 / M_PI, yaw * 180 / M_PI);
geometry_msgs::Twist vel_cmd;
double target_yaw = 90;
double diff_yaw = target_yaw - yaw * 180 / M_PI;
vel_cmd.angular.z = diff_yaw * 0.01;
vel_cmd.linear.x = 0.1;
vel_pub.publish(vel_cmd);
}
int main(int argc, char *argv[]) {
setlocale(LC_ALL, "");
ros::init(argc, argv, "imu_data");
ros::NodeHandle nh;
ros::Subscriber sub = nh.subscribe("imu/data", 100, IMUCallback);
vel_pub = nh.advertise<geometry_msgs::Twist>("/cmd_vel", 10);
ros::spin();
return 0;
}

Python 版本

#!/usr/bin/env python3
# coding=utf-8
import rospy
from sensor_msgs.msg import Imu
from tf.transformations import euler_from_quaternion
import math
from geometry_msgs.msg import Twist
# IMU 回调函数
def imu_callback(msg):
if msg.orientation_covariance[0] < 0:
return
# 四元数转成欧拉角
quaternion = [
msg.orientation.x,
msg.orientation.y,
msg.orientation.z,
msg.orientation.w
]
(roll,pitch,yaw) = euler_from_quaternion(quaternion)
# 弧度换算成角度
roll = roll*180/math.pi
pitch = pitch*180/math.pi
yaw = yaw*180/math.pi
rospy.loginfo("滚转= %.0f 俯仰= %.0f 朝向= %.0f", roll, pitch, yaw)
# 速度消息包
vel_cmd = Twist()
# 目标朝向角
target_yaw = 90
# 计算速度
diff_angle = target_yaw - yaw
vel_cmd.angular.z = diff_angle * 0.01
vel_cmd.linear.x = 0.1
global vel_pub
vel_pub.publish(vel_cmd)
# 主函数
if __name__ == "__main__":
rospy.init_node("imu_behavior")
# 订阅 IMU 的数据话题
imu_sub = rospy.Subscriber("/imu/data",Imu,imu_callback,queue_size=10)
# 发布机器人运动控制话题
vel_pub = rospy.Publisher("cmd_vel",Twist,queue_size=10)
rospy.spin()

ROS 中的消息包

标准消息包

image-20240117164554515

常用消息包

image-20240117165259977

几何消息包:可以看到里面一些消息包带了 Stamped 后缀关键词,这些消息包都是多了一个 Header,多了一个时间和空间坐标系 ID,将空间量和时间量进行了绑定,在一些预测和滤波算法里会用得着。

image-20240117171457233

传感器消息包:

image-20240117171717682

自定义消息包

创建自定义消息包

image-20240117173827148

生成自定义消息包的步骤:

image-20240117174141770

catkin_create_pkg qq_msgs roscpp rospy std_msgs message_generation message_runtime

创建 msg 文件夹和 Carry.msg

string grade
int64 star
string data

然后修改 CMakeLists.txt,重新编译。终端中输入:

rosmsg show qq_msgs/Carry

image-20240117180125496
可以看到新的自定义消息包诞生了。

C++ 版本

image-20240117180703284

修改 chao_node.cppma_node.cpp 以及对应的 CMakeLists.txt 和 package.xml 文件,最后运行效果如下:

image-20240117182758609

Python 版本

image-20240118101656160

Python 不需要在编译规则中添加 add_dependencies()

image-20240118101452469

💥未完待更~

ROS 中的栅格地图

ROS – Index:map_server

ROS – Wiki:map_server

image-20240118102748650

image-20240118102911361

image-20240118103902094

image-20240118104119540

下面使用在 ROS 中发布自定义地图,

C++ 版本

image-20240118142737538

catkin_create_pkg map_pkg rospy roscpp nav_msgs

map_pkg/src/map_pub_node.cpp 编写如下代码:

#include <ros/ros.h>
#include <nav_msgs/OccupancyGrid.h>
int main(int argc, char *argv[]) {
ros::init(argc, argv, "map_pub_node");
ros::NodeHandle nh;
ros::Publisher pub = nh.advertise<nav_msgs::OccupancyGrid>("/map", 10);
ros::Rate rate(10);
while (ros::ok()) {
nav_msgs::OccupancyGrid msg;
msg.header.frame_id = "map";
msg.header.stamp = ros::Time::now();
msg.info.origin.position.x = 2;
msg.info.origin.position.y = 3;
msg.info.resolution = 1.0;
msg.info.width = 4;
msg.info.height = 2;
msg.data.resize(msg.info.width * msg.info.height);
msg.data[0] = 100;
msg.data[1] = 100;
msg.data[2] = 0;
msg.data[3] = -1;
pub.publish(msg);
rate.sleep();
}
}

然后修改 CMakeLists.txt 添加编译规则,运行

rosrun map_pkg map_pub_node
rviz

在 RViz 中点击左下角的 Add,添加 Axes 和 Map,

image-20240118144918925

Map 话题名称设置为 /map

image-20240118144756787

Python 版本

#!/usr/bin/env python3
#coding=utf-8
import rospy
from nav_msgs.msg import OccupancyGrid
if __name__ == "__main__":
rospy.init_node("map_pub_node")
pub = rospy.Publisher("/map", OccupancyGrid, queue_size=10)
rate = rospy.Rate(10)
while not rospy.is_shutdown():
msg = OccupancyGrid()
msg.header.frame_id = "map"
msg.header.stamp = rospy.Time.now()
msg.info.origin.position.x = 0
msg.info.origin.position.y = 0
msg.info.resolution = 1.0
msg.info.width = 4
msg.info.height = 2
msg.data = [0] * msg.info.width * msg.info.height
msg.data[0] = 100
msg.data[1] = 100
msg.data[2] = 0
msg.data[3] = -1
pub.publish(msg)
rate.sleep()

Hector_Mapping 初体验

ROS – Index:hector_mapping

image-20240118164311767

roslaunch wpr_simulation wpb_stage_slam.launch
rosrun hector_mapping hector_mapping
rosrun rviz rviz
rosrun rqt_robot_steering rqt_robot_steering

image-20240118171111567

image-20240118171335066

通过 lanuch 文件启动

<launch>
<!-- 载入 机器人 和 SLAM 的仿真场景 -->
<include file="$(find wpr_simulation)/launch/wpb_stage_slam.launch"/>
<!-- Hector SLAM -->
<node pkg="hector_mapping" type="hector_mapping" name="hector_mapping"></node>
<!-- Rviz 显示 -->
<node pkg="rviz" type="rviz" name="rviz" args="-d $(find slam_pkg)/rviz/slam.rviz"/>
<!-- 运动控制 -->
<node pkg="rqt_robot_steering" type="rqt_robot_steering" name="rqt_robot_steering"/>
</launch>

可以给 hector_mapping 添加参数,增快建图速度。具体见 wpr_simulation/launch/wpb_hector_comparison.launch 文件。

roslaunch wpr_simulation wpb_hector_comparison.launch

image-20240118175500954

初识 ROS 中的 TF 系统

image-20240118175910463

image-20240118175949866

image-20240118180120896

tf2_msgs

roslaunch slam_pkg hector.launch
rostopic list
rostopic type /tf
rostopic echo /tf
rosrun rqt_tf_tree rqt_tf_tree

tf 的消息类型为 tf2_msgs/TFMessage,可以看到 tf 的消息如下:

---
transforms:
-
header:
seq: 0
stamp:
secs: 772
nsecs: 262000000
frame_id: "odom"
child_frame_id: "base_footprint"
transform:
translation:
x: 2.5370301864651945
y: 1.5186889059358941
z: 0.0
rotation:
x: 0.0
y: 0.0
z: 0.95114390602314
w: 0.30874790693224397
---

rqt_tf_tree 可以显示 TF 树,可以看到空间坐标系的父子关系。

初识里程计

image-20240119155654640

image-20240119160001097

这一节内容比较复杂,具体见 B站-什么是里程计。有讲 hector 建图和 gmapping 建图算法的区别。

使用 Gmapping 进行 SLAM 建图

roslaunch wpr_simulation wpb_stage_robocup.launch
rostopic echo /scan --noarr
rosrun gmapping slam_gmapping
rosrun rviz rviz
rosrun wpr_simulation keyboard_vel_ctrl

image-20240119164451924

由于撞到了东西,使参照物发生了移动,可以看到建图与原地图有初入。

image-20240119165327295

然呃因为 gazebo 在强制关闭后再启动报错,我尝试寻找解决办法,

killall gzserver
killall gzclient

这下启动依旧报错,甚至重启 ubuntu 都没法解决问题了😰。暂时没有找到原因和解决办法。

gmapping lanch 文件如下(还没测试过):

<launch>
<include file="$(find wpr_simulation)/launch/wpb_stage_robocup.launch"/>
<node pkg="gmapping" type="slam_gmapping" name="slam_gmapping"/>
<node pkg="rviz" type="rviz" name="rviz" args="-d $(find slam_pkg)/rviz/gmapping.rviz"/>
<node pkg="wpr_simulation" type="keyboard_ver_ctrl" name="keyboard_ver_ctrl"/>
</launch>

Gmapping 建图的参数设置

image-20240120194438132

image-20240120194824038

地图保存与加载

ROS – Index:map_server

地图的保存:

rosrun map_server map_saver -f mymap

保存后会得到 mymap.pgmmymap.yaml 两个文件。其中 mymap.pgm 是图像文件,可以直接打开看,后者 mymap.yaml 是地图配置文件,内容如下:

image: mymap.pgm
resolution: 0.025000 #地图分辨率
origin: [-12.812499, -12.812499, 0.000000] #地图左下角的坐标和地图偏转角度
negate: 0
occupied_thresh: 0.65
free_thresh: 0.196

image-20240120201623872

地图还原:

rosrun map_server map_server mymap.yaml

VSCode 配置 ROS 调试环境

配置 X11-Forwarding

编辑 /etc/ssh/sshd_config,确认 X11Forwarding 已经打开。

#AllowAgentForwarding yes
#AllowTcpForwarding yes
#GatewayPorts no
X11Forwarding yes
#X11DisplayOffset 10
#X11UseLocalhost yes
#PermitTTY yes

安装 VScode 关于 X11 的拓展,

image-20240122095326185

在 Remote X11 的拓展中,选择设置,删除以下环境变量(笔者在这里被这个扩展卡了一会):

LIBGL_ALWAYS_INDIRECT=1

然后关闭 VScode,重新打开一个新窗口,输入 SSH 连接命令,-X 选项表示启用 X11 转发。

ssh -X lrl52@192.168.217.130

保存配置,打开 ssh config,可以看到 ForwardX11 被设置为了 yes

image-20240122100400317

此时在 VSCode 的终端输入 echo $DISPLAY,可以发现 DISPLAY 环境变量已经被设置,

> echo $DISPLAY
localhost:10.0

回到虚拟机,输入同样的命令,

> echo $DISPLAY
:0

我希望在 VSCode 运行 GUI 程序时,GUI 程序能够在虚拟机正常运行显示,这样便于在 VScode 中启动节点进行调试。因此需要把 VSCode 终端中的 DISPLAY 的值修改为 :0

export DISPLAY=:0

然后在 VScode 终端中输入 rviz 测试,可以发现启动成功。

image-20240122101225080

为了避免每次打开都手动修改环境变量,编辑 ~/.zshrc 添加如下脚本:

# Check if VSCODE_INJECTION exists and equals to 1
if [[ $VSCODE_INJECTION = 1 ]]; then
export DISPLAY=:0
fi

这样在 VScode 中打开的终端就会自动设置该环境变量啦,其它的终端则不受影响。

配置调试

创建 launch.json 文件,添加 ROS: LaunchROS: Attach 模板配置。修改 launch 部分的启动路径:

{
// 使用 IntelliSense 了解相关属性。
// 悬停以查看现有属性的描述。
// 欲了解更多信息,请访问: https://go.microsoft.com/fwlink/?linkid=830387
"version": "0.2.0",
"configurations": [
{
"name": "ROS: Attach",
"type": "ros",
"request": "attach"
},
{
"name": "ROS: Launch",
"type": "ros",
"request": "launch",
"target": "/home/lrl52/shenlan_ws/src/grid_path_searcher/launch/demo.launch"
}
]
}

调试之前,删除之前的 build 和 devel 文件夹,重新构建项目,生成调试 symbol。执行:

catkin_make -DCMAKE_BUILD_TYPE=Debug -j

可以在 VSCode 中后台启动 ROS。确保调试时 ROS master 进行在运行中。

image-20240122102305705

尝试 ROS: Launch 调试,单步调试完美启动:

image-20240122102521722

但目前笔者尚不清楚 RViz 为什么不会在后台启动运行,即使去掉所有断点也不行。

这时候还可以尝试另一种要求更低,更通用的调试办法,那就是 GDB Attach

选择 ROS: Attach,选择 demo_node 进程:

image-20240122103008243

此时可能会提示

image-20240122103054217

笔者发现不管你选择 y or N 都没法进行调试,在这里又被卡了一会,原因是 Linux 默认系统内核参数 kernel.yama.ptrace 设置为1(True),此时普通用户进程是不能对其他进程进行 attach 操作的。更改办法见非root权限进行gdb attach调试。执行命令:

sudo sysctl kernel/yama/ptrace_scope=0

此时再次尝试 Attach 就可以成功调试了。Oh wowowowhahaha!🎉

(另外如果调试的时候,调试当前行上下乱跳,记着检查一下各个包的 CMakeLists.txt ,一定要把所有 O2, O3 优化都关掉)

image-20240122103447750

References

GitHub: vscode-ros

GitHub: vscode-ros-debug-support

What are CMAKE_BUILD_TYPE: Debug, Release, RelWithDebInfo and MinSizeRel?

使用 ssh 客户端的 X11-forwarding 在远程 linux 主机上运行 GUI 程序

非 root 权限进行 gdb attach 调试

B 站 – ROS 快速入门教程

暂无评论

发送评论 编辑评论


				
|´・ω・)ノ
ヾ(≧∇≦*)ゝ
(☆ω☆)
(╯‵□′)╯︵┴─┴
 ̄﹃ ̄
(/ω\)
∠( ᐛ 」∠)_
(๑•̀ㅁ•́ฅ)
→_→
୧(๑•̀⌄•́๑)૭
٩(ˊᗜˋ*)و
(ノ°ο°)ノ
(´இ皿இ`)
⌇●﹏●⌇
(ฅ´ω`ฅ)
(╯°A°)╯︵○○○
φ( ̄∇ ̄o)
ヾ(´・ ・`。)ノ"
( ง ᵒ̌皿ᵒ̌)ง⁼³₌₃
(ó﹏ò。)
Σ(っ °Д °;)っ
( ,,´・ω・)ノ"(´っω・`。)
╮(╯▽╰)╭
o(*////▽////*)q
>﹏<
( ๑´•ω•) "(ㆆᴗㆆ)
😂
😀
😅
😊
🙂
🙃
😌
😍
😘
😜
😝
😏
😒
🙄
😳
😡
😔
😫
😱
😭
💩
👻
🙌
🖕
👍
👫
👬
👭
🌚
🌝
🙈
💊
😶
🙏
🍦
🍉
😣
Source: github.com/k4yt3x/flowerhd
颜文字
Emoji
小恐龙
花!
上一篇
下一篇