文章目录
安装
强烈建议使用鱼香ROS的一键安装功能,对于我们这些小白真的是福音。
http://fishros.com/#/fish_home
先安装Ubuntu22.04.1(据说win10下也可以装ROS2,但是十分折腾,还是在Ubuntu下安装方便一些)(Ubuntu22.04才有humble版本的ros2,Ubuntu20.04没有humble)
然后在命令行中执行鱼香ROS的一键安装代码
wget http://fishros.com/install -O fishros && . fishros
然后选择安装ros,选择humble版本。
在Qt中调用api
参考鱼香ros的 使用g++编译ROS2节点时,假如是直接用g++编译cpp文件,他那样子是没问题;但是假如是在Qt中调用ros的库时,需要将lib目录下所有的so包含进去
INCLUDEPATH += /opt/ros/humble/include/rclcpp \
/opt/ros/humble/include/rcl/ \
/opt/ros/humble/include/rcutils/ \
/opt/ros/humble/include/rmw \
/opt/ros/humble/include/rcl_yaml_param_parser/ \
/opt/ros/humble/include/rosidl_runtime_c \
/opt/ros/humble/include/rosidl_typesupport_interface \
/opt/ros/humble/include/rcpputils \
/opt/ros/humble/include/builtin_interfaces \
/opt/ros/humble/include/rosidl_runtime_cpp \
/opt/ros/humble/include/tracetools \
/opt/ros/humble/include/rcl_interfaces \
/opt/ros/humble/include/libstatistics_collector \
/opt/ros/humble/include/statistics_msgs
# 这样不行,不知道为啥
#LIBS += -L/opt/ros/humble/lib/ \
#-lrclcpp -lrcutils
LIBS += -L/opt/ros/humble/lib/
LIBS += /opt/ros/humble/lib/*.so
rclcpp::spin的作用
简单地说,只有调用了这个函数,节点对应的回调函数才会被执行。否则会一直被挂起在队列中(假如队列满了,旧的会被挤掉)
参考 :
https://blog.csdn.net/xihuanzhi1854/article/details/101025747
在Qt中使用QtConcurrent::run处理多线程时,后面的线程无法启动的问题
ROS2中的节点需要调用阻塞函数rclcpp::spin之后,节点对应的相关回调函数(接收话题的回调函数、服务请求处理函数、服务回应处理函数等)才能被成功调用;或者在while循环中,调用rclcpp::spin_some。总之,都涉及到线程阻塞的问题。
目前我对于这些问题的处理方式是将对应的节点对象放到子线程中进行rclcpp::spin,代码如下:
void createServer()
{
serviceServer = std::make_shared<ServiceServer01>("service_server_01");
rclcpp::spin(serviceServer);
}
int main(int argc, char *argv[])
{
...
QtConcurrent::run(createServer);
...
}
但是,我是在虚拟机进行操作的,虚拟机只分配了两个核,因此,我在利用 QtConcurrent::run启动了2个节点后,从第三个开始,它就启动不了了。
经过查找资料发现,这个和QThreadPool有关系。简单地说就是每次调用QtConcurrent::run时,都会从线程池QThreadPool::globalInstance()里面取个线程出来用,而QThreadPool::globalInstance()里面的默认可用线程个数是有限的,可以查询QThreadPool::globalInstance()->maxThreadCount()得到;而这个maxThreadCount的默认值为QThread::idealThreadCount(),而 QThread::idealThreadCount()是与cpu的核心数有关系的:
所以,一种办法是可以修改你虚拟机的CPU核心数;或者,直接修改QThreadPool::globalInstance()的maxThreadCount:
int main(int argc, char *argv[])
{
// 直接修改最大可用线程数,但是暂时不清楚这样做会不会有什么副作用
QThreadPool::globalInstance()->setMaxThreadCount(10);
...
QtConcurrent::run(createServer);
...
}
OpenCV的快速安装
opencv的安装
参考这里,可以先尝试从仓库安装,也就是:
sudo apt update
sudo apt install libopencv-dev python3-opencv
然后在Qt里面的.pro文件使用时,就这样写(一定要这样添加库,假如用注释的那个方法添加的话,会报 undefined symbol: _ZdlPvm, version Qt_5 的错误)
...
INCLUDEPATH += /usr/include/opencv4 \
/usr/include/opencv4/opencv2
#LIBS += -L/usr/lib/x86_64-linux-gnu \
#-lopencv_core -lopencv_imgproc -lopencv_video -lopencv_imgcodecs -lopencv_highgui
LIBS += /usr/lib/x86_64-linux-gnu/libopencv_*.so
...
发布图像类型的主题
图像类型的主题用到的消息类型可以参考
https://blog.csdn.net/mengfanji_5995/article/details/127526564
在发布后,假如通过rqt查看发布的图像主题时,报下面的错:
Unable to load plugin for transport 'image_transport/image_sub', error string:
According to the loaded plugin descriptions the class image_transport/image_sub with base class type image_transport::SubscriberPlugin does not exist. Declared types are image_transport/compressedDepth_sub image_transport/compressed_sub image_transport/raw_sub image_transport/theora_sub
那就要更改一下发布的主题名称,改成compressed结尾的
将图像类型主题的数据转cv::Mat
#include <cv_bridge/cv_bridge.h>
...
// 将ROS图像消息转换为OpenCV格式
cv_bridge::CvImagePtr cv_ptr;
try
{
// cv_ptr = cv_bridge::toCvCopy(msg, sensor_msgs::image_encodings::TYPE_16UC1);
cv_ptr = cv_bridge::toCvCopy(msg);
}
catch (cv_bridge::Exception& e)
{
qDebug() << "cv_bridge exception: %s" << e.what();
return;
}
// rgb8
// qDebug() << "rgb cv_ptr encoding:" << cv_ptr->encoding.data();
cv::Mat mat = cv_ptr->image;
参数相关
鱼香ROS的教程中对参数的读写都是周期轮询的,没有给到用回调函数的具体例子。因此可以参考这里。
https://www.jianshu.com/p/d31b514ee5d2
参数的监听可以这样操作,包括监听节点本身及其他节点
// 会监控本节点以及其他节点的参数的改变?不能单纯监控自己?
// Create a parameter subscriber that can be used to monitor parameter changes
// (for this node's parameters as well as other nodes' parameters)
param_subscriber_ = std::make_shared<rclcpp::ParameterEventHandler>(this);
// Set a callback for this node's integer parameter, "an_int_param"
auto cb = [this](const rclcpp::Parameter & p) {
RCLCPP_INFO(
this->get_logger(), "cb: Received an update to parameter \"%s\" of type %s: \"%ld\"",
p.get_name().c_str(),
p.get_type_name().c_str(),
p.as_int());
};
// 要注意名称要和前面对应,因此这个是只监控本节点的参数
param_cb_handle_ = param_subscriber_->add_parameter_callback("rcl_log_level", cb);
// // 监控别的节点的参数可以这样做,也就是使用add_parameter_callback的第三个参数
// auto remote_node_name = std::string("parameter_blackboard");
// auto remote_param_name = std::string("a_double_param");
// auto cb_handle2_ = param_subscriber_->add_parameter_callback(remote_param_name, cb2, remote_node_name);
至于参数的读写,要分两种情况:1.读写本节点的参数;2.读写其他节点的参数。
读写本节点的参数比较简单,直接调用对应的函数就行:
this->get_parameter("rcl_log_level", log_level); // 获取参数
this->set_parameter(rclcpp::Parameter("rcl_log_level", 20)); // 设置参数
读写其他节点的话,从这里看到的资料,只能利用service来进行了。
service的调用可以按照教程来操作。
当启动一个节点后,该节点自动会创建以下的服务(此节点名为parameters_basic;这些服务的名称都是这样:/节点名/通用服务名)
/parameters_basic/describe_parameters
/parameters_basic/get_parameter_types
/parameters_basic/get_parameters
/parameters_basic/list_parameters
/parameters_basic/set_parameters
/parameters_basic/set_parameters_atomically
这里的parameters_basic节点是按照鱼香ROS的教程创建的节点,里面声明了一个int类型的参数:rcl_log_level。
通过服务读取该参数可以这样操作
int readParam()
{
// 根据你的节点的名称来确定具体的字符串
auto client_ = parameterNode->create_client<rcl_interfaces::srv::GetParameters>("/parameters_basic/get_parameters");
// 1.等待服务端上线
while (!client_->wait_for_service(std::chrono::seconds(1))){
// 等待时,检测rclcpp的状态
if(!rclcpp::ok()){
return -1;
}
qDebug() << "等待服务端上线中";
}
// 2.构造请求
auto request = std::make_shared<rcl_interfaces::srv::GetParameters_Request>();
std::vector<std::string> names;
names.push_back("rcl_log_level");
request->names = names;
// 3.发送异步请求。发送异步请求有两个重载函数可以调用,一个是带回调函数,一个是不带的。我这里直接用不带回调函数的,然后等待返回
auto future = client_->async_send_request(request);
future.wait();
auto result = future.future.get();
auto value = result->values.front();
qDebug() << "----the rcl_log_level:" << value.type << value.integer_value << value.double_value;
return 0;
}
通过服务写入该参数可以这样操作
int writeParam(int value)
{
auto client_ = parameterNode->create_client<rcl_interfaces::srv::SetParameters>("/parameters_basic/set_parameters");
// 1.等待服务端上线
while (!client_->wait_for_service(std::chrono::seconds(1))){
// 等待时,检测rclcpp的状态
if(!rclcpp::ok()){
return -1;
}
qDebug() << "等待服务端上线中";
}
// 2.构造请求
auto request = std::make_shared<rcl_interfaces::srv::SetParameters_Request>();
std::vector<rcl_interfaces::msg::Parameter> parameters;
rcl_interfaces::msg::Parameter para1;
rcl_interfaces::msg::ParameterValue val1;
val1.integer_value = value;
val1.type = rcl_interfaces::msg::ParameterType::PARAMETER_INTEGER;
para1.name = "rcl_log_level";
para1.value = val1;
parameters.push_back(para1);
qDebug() << "value:" << val1.integer_value << val1.type;
request->parameters = parameters;
// // 3.发送异步请求,然后等待返回,返回时调用回调函数
auto future = client_->async_send_request(request);
future.wait();
return 0;
}
安装rqt的相应插件后,在plugin中找不到的问题
在按照鱼香ros的教程安装ros-humble-rqt-tf-tree后,发现我无法在rqt里面找到这个插件。在网上搜索了之后发现,有人也遇到这个问题,并且解决了。
rm ~/.config/ros.org/rqt_gui.ini
MoveIt2
最好从源码编译安装。同时,有条件的准备个梯子,有时候会用到。
安装/编译步骤
colcon build 时提示找不到ompl头文件
在编译源码的过程中提示找不到ompl头文件的话,可以参考这里
sudo ln -s /opt/ros/humble/include/ompl-1.6/ompl /opt/ros/humble/include/ompl
colcon build 时崩溃闪退
在执行 “colcon build --cmake-args -DCMAKE_BUILD_TYPE=Release --merge-install” 之后,一直时不时就直接退出命令行控制台窗口,啥提示都没有。
但是经过观察,每次崩溃的位置是不同的,每次都会往前面前进一点,因此多次反复执行之后,就可以编译完了,不知道是不是多线程编译的问题。
后来又发现,可能和内存太小有关。我原来设置虚拟机的内存为4GB,经常闪退,但是改成6G之后,就不闪退了。
而且,从下图也可以看出,在编译时,colcon会把内存吃干抹尽的。
Moveit colcon mixin update default失败的办法
按照官网的教程操作时,mixin update default的解决办法
不过就个人而言,不太建议用这个mixin来编译,因为我的moveit是使用指令
“colcon build --cmake-args -DCMAKE_BUILD_TYPE=Release”
来编译的,假如按照官方的来编译,那就相当于重新再编译一次了。我还是用于上述的这个指令来编译。
rviz的Qt版本问题
自己编译rviz,给自己的项目用
https://github.com/ros2/rviz
Gazebo相关问题
例程,
安装好ros2的插件
sudo apt install ros-humble-gazebo-ros
然后就可以在
/opt/ros/humble/share/gazebo_ros2_control_demos
里面看到一些例程,可以参考下面的命令执行
ros2 launch gazebo_ros2_control_demos gripper_mimic_joint_example.launch.py