多次交互失败告终。

第一次

以下是基于ROS Kinetic在蓝桥ROS云课中设计差速导航机器人项目的详细实践流程,分为环境准备、仿真搭建、导航配置和测试验证四个阶段:


一、环境准备

  1. 登录蓝桥云课环境

    • 确认已分配Kinetic版本ROS环境
    • 检查基础依赖:sudo apt-get install ros-kinetic-navigation ros-kinetic-stage
  2. 创建工作空间

    
      

    bash

    mkdir -p ~/diff_drive_ws/src
    cd ~/diff_drive_ws/src
    catkin_init_workspace
  3. 获取必要源码包

    
      

    bash

    # 获取TEB规划器示例(若未预装)
    git clone https://github.com/ros-planning/navigation_tutorials.git
    # 或直接下载teb_local_planner_tutorials包

二、机器人模型配置

  1. 创建URDF模型

    
      

    xml

    <!-- diff_drive.urdf -->
    <robot name="diff_drive_robot">
    <link name="base_link">
    <visual><geometry><box size="0.3 0.2 0.1"/></geometry></visual>
    </link>
    <joint name="left_wheel" type="continuous">
    <parent link="base_link"/><child link="left_wheel_link"/>
    <axis xyz="0 1 0"/>
    </joint>
    <joint name="right_wheel" type="continuous">
    <parent link="base_link"/><child link="right_wheel_link"/>
    <axis xyz="0 -1 0"/>
    </joint>
    </robot>
  2. 配置差速驱动控制器

    
      

    yaml

    # config/diff_drive_controller.yaml
    diff_drive_controller:
    wheels:
    - name: left_wheel
    joint: left_wheel
    - name: right_wheel
    joint: right_wheel
    wheel_radius: 0.033
    wheel_separation: 0.16

三、导航参数配置

  1. 代价地图配置

    
      

    yaml

    # config/costmap_common_params.yaml
    obstacle_range: 2.5
    raytrace_range: 3.0
    inflation_radius: 0.55
  2. TEB局部规划器配置

    
      

    yaml

    # config/teb_local_planner_params.yaml
    TebLocalPlannerROS:
    max_vel_x: 0.3
    min_vel_x: 0.1
    acc_lim_theta: 3.2
    trajectory_planner_ros:
    yaw_goal_tolerance: 0.1
  3. 导航核心配置

    
      

    yaml

    # config/nav_core_params.yaml
    base_global_planner: "navfn/NavfnROS"
    base_local_planner: "teb_local_planner/TebLocalPlannerROS"

四、Stage仿真环境搭建

  1. 创建仿真世界文件

    
      

    world

    # stage/diff_drive.world
    define floorplan model
    (
    size [10 10 0.1]
    gui_nose 0
    color "gray30"
    )
    define robot position
    (
    pose [0 0 0 0]
    name "robot"
    color "blue"
    model "diff_drive"
    )
  2. 启动仿真与导航

    
      

    bash

    # 新终端1:启动Stage仿真
    roslaunch stage_ros diff_drive.launch
    # 新终端2:启动导航节点
    roslaunch teb_local_planner_tutorials robot_diff_drive_in_stage.launch

五、测试与验证

  1. 设置导航目标

    
      

    bash

    rostopic pub /move_base_simple/goal geometry_msgs/PoseStamped "header:
    seq: 0
    stamp:
    secs: 0
    nsecs: 0
    frame_id: 'map'
    pose:
    position:
    x: 3.0
    y: 3.0
    z: 0.0
    orientation:
    x: 0.0
    y: 0.0
    z: 0.0
    w: 1.0"
  2. 可视化监控

    • 使用RViz查看路径规划:
      
          

      bash

      rosrun rviz rviz -d `rospack find teb_local_planner_tutorials`/rviz/teb.rviz
  3. 关键调试点

    • 检查TF树:rosrun rqt_tf_tree rqt_tf_tree
    • 查看导航状态:rostopic echo /move_base/status
    • 调整代价地图参数避免碰撞

六、扩展优化方向

  1. 传感器融合
    • 添加激光雷达模拟:sudo apt-get install ros-kinetic-sicktoolbox-wrapper
    • 配置深度相机:roslaunch openni2_launch openni2.launch
  2. 动态避障
    • 在Stage中添加动态障碍物:
      
          

      world

      define moving_obstacle model
      (
      block(
      points 4
      x [-0.2 0.2 0.2 -0.2]
      z [1 1 0.8 0.8]
      color "red"
      )
      velocity [0.2 0 0 0 0 0]
      )
  3. 参数调优
    • 调整recovery_behaviors避免局部极小
    • 优化max_vel_theta提升旋转速度

注意事项:

  1. Kinetic版本需使用catkin_make_isolated编译
  2. 确保所有launch文件正确引用参数文件路径
  3. 仿真环境坐标系需与导航栈map坐标系对齐

完整项目代码可参考http://wiki.ros.org/navigation/Tutorials的Kinetic适配版本。


第二次

以下是转换后的C++实现代码,包含详细注释和ROS Kinetic兼容配置:


cpp

#include <ros/ros.h>
#include <geometry_msgs/PoseStamped.h>
#include <vector>
int main(int argc, char** argv) {
// 初始化ROS节点
ros::init(argc, argv, "patrol_robot_cpp");
ros::NodeHandle nh;
// 创建目标点发布器
ros::Publisher goal_pub = nh.advertise<geometry_msgs::PoseStamped>("/move_base_simple/goal", 10);
// 定义巡逻路径(使用vector存储多个目标点)
std::vector<geometry_msgs::PoseStamped> waypoints = {
geometry_msgs::PoseStamped(),
geometry_msgs::PoseStamped()
};
// 配置第一个目标点
waypoints[0].header.frame_id = "map";
waypoints[0].header.stamp = ros::Time::now();
waypoints[0].pose.position.x = 5.0;
waypoints[0].pose.position.y = 2.0;
waypoints[0].pose.orientation.w = 1.0;
// 配置第二个目标点
waypoints[1].header.frame_id = "map";
waypoints[1].header.stamp = ros::Time::now();
waypoints[1].pose.position.x = 2.5;
waypoints[1].pose.position.y = 7.5;
waypoints[1].pose.orientation.w = 1.0;
// 设置循环频率(这里使用固定间隔)
ros::Rate loop_rate(1.0/60.0); // 每60秒执行一次循环
try {
while (ros::ok()) {
// 遍历所有目标点
for (const auto& waypoint : waypoints) {
// 发布目标点
ROS_INFO("Sending goal to: (%.2f, %.2f)",
waypoint.pose.position.x,
waypoint.pose.position.y);
goal_pub.publish(waypoint);
// 等待60秒(实际等待时间取决于循环频率)
ros::Duration(60.0).sleep();
}
}
} catch (const ros::Exception& e) {
ROS_ERROR("ROS exception: %s", e.what());
}
return 0;
}

配套CMakeLists.txt配置:


cmake

cmake_minimum_required(VERSION 2.8.3)
project(patrol_robot_cpp)
find_package(catkin REQUIRED COMPONENTS
roscpp
geometry_msgs
)
catkin_package()
include_directories(
${catkin_INCLUDE_DIRS}
)
add_executable(patrol_robot_cpp src/patrol_robot.cpp)
target_link_libraries(patrol_robot_cpp
${catkin_LIBRARIES}
)

实现说明:

  1. 节点初始化
    • 使用ros::init初始化节点,创建ros::NodeHandle
    • 通过advertise创建目标点发布器,队列大小设为10
  2. 目标点配置
    • 使用std::vector存储多个geometry_msgs::PoseStamped对象
    • 每个目标点包含:
      • header:设置frame_id为"map",时间戳为当前时间
      • pose:设置x/y坐标和朝向(四元数w=1表示无旋转)
  3. 主循环逻辑
    • 使用ros::Rate控制循环频率(每60秒执行一次完整巡逻)
    • 遍历所有目标点,依次发布到/move_base_simple/goal话题
    • 使用ros::Duration实现精确的60秒等待
  4. 异常处理
    • 捕获ROS异常并输出错误信息
    • 通过ros::ok()持续检查节点状态

编译运行步骤:

  1. 创建功能包:

bash

catkin_create_pkg patrol_robot_cpp roscpp geometry_msgs
  1. 将代码放入src/目录,CMakeLists.txt放入功能包根目录

  2. 编译工作空间:


bash

catkin_make
source devel/setup.bash
  1. 运行节点:

bash

rosrun patrol_robot_cpp patrol_robot_cpp

扩展建议:

  1. 动态路径加载

    
      

    cpp

    // 从参数服务器加载路径
    std::vector<geometry_msgs::PoseStamped> waypoints;
    nh.getParam("/patrol_path", waypoints);
  2. 添加路径可视化

    
      

    cpp

    #include <visualization_msgs/Marker.h>
    ros::Publisher marker_pub = nh.advertise<visualization_msgs::Marker>("patrol_path", 10);
  3. 异常恢复机制

    
      

    cpp

    // 添加异常恢复行为
    if (!ros::ok()) {
    ROS_WARN("Node shutdown requested, stopping patrol");
    break;
    }

该实现完整保留了Python版本的巡逻逻辑,同时通过C++的类型安全和性能优势,更适合在资源受限的嵌入式系统上运行。

第三次

以下是为上述ROS程序使用Qt 4.8.7编写的图形化界面实现方案,包含核心代码和关键说明:


cpp

// patrol_gui.h
#ifndef PATROL_GUI_H
#define PATROL_GUI_H
#include <QMainWindow>
#include <ros/ros.h>
#include <geometry_msgs/PoseStamped.h>
#include <QTimer>
#include <QTextEdit>
#include <QPushButton>
#include <QLabel>
class PatrolGUI : public QMainWindow
{
Q_OBJECT
public:
PatrolGUI(int argc, char** argv, QWidget *parent = 0);
~PatrolGUI();
private slots:
void togglePatrol();
void publishWaypoint();
void logCallback(const QString &message);
private:
void setupUI();
void setupROS(int argc, char** argv);
void setupWaypoint(geometry_msgs::PoseStamped &waypoint,
double x, double y, const QString &name);
ros::NodeHandle nh_;
ros::Publisher goal_pub_;
ros::AsyncSpinner spinner_;
QTimer *patrol_timer_;
QTextEdit *log_display_;
QPushButton *control_btn_;
QLabel *status_label_;
std::vector<geometry_msgs::PoseStamped> waypoints_;
bool is_patrolling_;
};
#endif // PATROL_GUI_H

cpp

// patrol_gui.cpp
#include "patrol_gui.h"
#include <QVBoxLayout>
#include <QHBoxLayout>
#include <QGroupBox>
#include <QMessageBox>
PatrolGUI::PatrolGUI(int argc, char** argv, QWidget *parent)
: QMainWindow(parent), is_patrolling_(false)
{
setupROS(argc, argv);
setupUI();
spinner_.start();
}
PatrolGUI::~PatrolGUI()
{
spinner_.stop();
if(ros::isShuttingDown())
ros::shutdown();
}
void PatrolGUI::setupROS(int argc, char** argv)
{
ros::init(argc, argv, "patrol_gui");
goal_pub_ = nh_.advertise<geometry_msgs::PoseStamped>("/move_base_simple/goal", 10);
// 配置航点
waypoints_.resize(2);
setupWaypoint(waypoints_[0], 5.0, 2.0, "Waypoint 1");
setupWaypoint(waypoints_[1], 2.5, 7.5, "Waypoint 2");
}
void PatrolGUI::setupWaypoint(geometry_msgs::PoseStamped &waypoint,
double x, double y, const QString &name)
{
waypoint.header.frame_id = "map";
waypoint.pose.position.x = x;
waypoint.pose.position.y = y;
waypoint.pose.orientation.w = 1.0;
}
void PatrolGUI::setupUI()
{
QWidget *central_widget = new QWidget(this);
QVBoxLayout *main_layout = new QVBoxLayout(central_widget);
// 控制面板
QGroupBox *control_box = new QGroupBox("Control Panel");
QHBoxLayout *control_layout = new QHBoxLayout(control_box);
control_btn_ = new QPushButton("Start Patrol");
control_layout->addWidget(control_btn_);
// 状态显示
status_label_ = new QLabel("Status: Stopped");
main_layout->addWidget(control_box);
main_layout->addWidget(status_label_);
// 日志显示
log_display_ = new QTextEdit();
log_display_->setReadOnly(true);
main_layout->addWidget(log_display_);
setCentralWidget(central_widget);
resize(600, 400);
// 连接信号槽
connect(control_btn_, SIGNAL(clicked()), this, SLOT(togglePatrol()));
connect(this, SIGNAL(logMessage(const QString&)),
this, SLOT(logCallback(const QString&)));
}
void PatrolGUI::togglePatrol()
{
is_patrolling_ = !is_patrolling_;
if(is_patrolling_) {
control_btn_->setText("Stop Patrol");
status_label_->setText("Status: Patrolling");
patrol_timer_ = new QTimer(this);
connect(patrol_timer_, SIGNAL(timeout()), this, SLOT(publishWaypoint()));
patrol_timer_->start(60000); // 60秒间隔
emit logMessage("Patrol started");
} else {
control_btn_->setText("Start Patrol");
status_label_->setText("Status: Stopped");
if(patrol_timer_) {
patrol_timer_->stop();
delete patrol_timer_;
}
emit logMessage("Patrol stopped");
}
}
void PatrolGUI::publishWaypoint()
{
static int current_wp = 0;
if(current_wp >= waypoints_.size()) current_wp = 0;
goal_pub_.publish(waypoints_[current_wp]);
emit logMessage(QString("Sent waypoint %1: (%.2f, %.2f)")
.arg(current_wp+1)
.arg(waypoints_[current_wp].pose.position.x)
.arg(waypoints_[current_wp].pose.position.y));
current_wp++;
}
void PatrolGUI::logCallback(const QString &message)
{
log_display_->append(message);
// 保持滚动条在底部
QScrollBar *sb = log_display_->verticalScrollBar();
sb->setValue(sb->maximum());
}
// 重定向ROS日志到GUI
#include <ros/console.h>
void customLogger(const ros::console::LogLocation& loc,
ros::console::levels::Level level,
const std::string& msg)
{
static PatrolGUI* gui = nullptr;
if(!gui) {
// 查找主窗口实例(需要根据实际情况调整)
foreach(QWidget *widget, QApplication::topLevelWidgets()) {
gui = qobject_cast<PatrolGUI*>(widget);
if(gui) break;
}
}
if(gui) {
QString level_str;
switch(level) {
case ros::console::levels::Debug: level_str = "DEBUG"; break;
case ros::console::levels::Info: level_str = "INFO"; break;
case ros::console::levels::Warn: level_str = "WARN"; break;
case ros::console::levels::Error: level_str = "ERROR"; break;
case ros::console::levels::Fatal: level_str = "FATAL"; break;
default: level_str = "UNKNOWN";
}
QString message = QString("[%1] %2").arg(level_str).arg(msg.c_str());
QMetaObject::invokeMethod(gui, "logMessage", Qt::QueuedConnection,
Q_ARG(QString, message));
}
}
// 在main函数中设置日志重定向
int main(int argc, char *argv[])
{
// 设置ROS日志重定向
ros::console::set_logger_level(ros::console::Level::Debug);
ros::console::set_logger_callback(customLogger);
QApplication app(argc, argv);
PatrolGUI window(argc, argv);
window.show();
return app.exec();
}
#include "main.moc"

关键实现说明:

  1. 架构组成
  • 使用AsyncSpinner处理ROS回调,避免阻塞GUI线程
  • 采用QTimer实现定时任务,替代ROS的Rate机制
  • 自定义日志重定向,将ROS日志集成到GUI界面
  1. 核心功能
  • 开始/停止巡逻控制按钮
  • 实时状态显示(运行状态、当前航点)
  • 集成ROS日志显示窗口
  • 60秒间隔自动切换航点
  1. 线程安全
  • 使用QMetaObject::invokeMethod进行跨线程GUI更新
  • 通过Qt信号槽机制实现线程间通信
  1. 扩展性设计
  • 航点配置可通过XML/YAML文件加载
  • 可添加地图显示功能(集成rviz)
  • 支持动态添加/删除航点
  1. 使用注意事项
  • 需要安装Qt4开发库和ROS相关依赖
  • 编译时添加ROS和Qt4的库链接
  • 可通过qmake或CMake进行构建
  • 需要正确设置ROS环境变量

该实现方案提供了基本的巡逻控制功能,并展示了如何将ROS系统与Qt GUI进行集成。可根据实际需求进一步扩展地图显示、参数配置、异常处理等功能。


第四次

要将Qt与ROS结合并使用catkin_make编译,需按照以下步骤操作:


1. 创建ROS功能包


bash

cd ~/catkin_ws/src
catkin_create_pkg my_qt_ros_pkg roscpp std_msgs qt_build # 添加qt_build依赖

2. 配置 package.xml

在功能包的 package.xml 中添加Qt依赖:


xml

<build_depend>qtbase5-dev</build_depend>
<exec_depend>qtbase5</exec_depend>
<depend>roscpp</depend>
<depend>std_msgs</depend>

3. 编写 CMakeLists.txt


cmake

cmake_minimum_required(VERSION 3.0.2)
project(my_qt_ros_pkg)
## 查找Qt5和ROS依赖
find_package(catkin REQUIRED COMPONENTS
roscpp
std_msgs
)
find_package(Qt5 REQUIRED Core Widgets) # 根据需求添加其他模块(如Gui、Network等)
## 设置C++11标准(Qt信号槽需要)
set(CMAKE_CXX_STANDARD 11)
## 生成UI头文件(如果有.ui文件)
set(QT_UI_FILES
src/main_window.ui
)
qt5_wrap_ui(QT_UI_HEADERS ${QT_UI_FILES})
## 生成MOC头文件(处理Q_OBJECT宏)
set(QT_SOURCES
src/main_window.cpp
src/main.cpp
)
qt5_wrap_cpp(QT_MOC_HEADERS ${QT_SOURCES})
## 添加可执行文件
add_executable(${PROJECT_NAME}
${QT_SOURCES}
${QT_UI_HEADERS}
${QT_MOC_HEADERS}
)
## 链接库
target_link_libraries(${PROJECT_NAME}
Qt5::Core
Qt5::Widgets
${catkin_LIBRARIES}
)
## 添加安装规则(可选)
install(TARGETS ${PROJECT_NAME}
RUNTIME DESTINATION ${CATKIN_PACKAGE_BIN_DESTINATION}
)

4. 代码结构示例


my_qt_ros_pkg/
├── CMakeLists.txt
├── package.xml
└── src/
├── main.cpp # ROS节点主入口
├── main_window.cpp # Qt窗口类实现
├── main_window.h # Qt窗口类声明
└── main_window.ui # Qt Designer设计的UI文件(可选)

5. 示例代码片段

main.cpp(ROS节点入口)

cpp

#include <QApplication>
#include "main_window.h"
#include <ros/ros.h>
int main(int argc, char *argv[]) {
ros::init(argc, argv, "qt_ros_node");
QApplication app(argc, argv);
MainWindow w;
w.show();
ros::spin();
return app.exec();
}
main_window.h(Qt窗口类声明)

cpp

#include <QMainWindow>
#include <ros/ros.h>
namespace Ui {
class MainWindow;
}
class MainWindow : public QMainWindow {
Q_OBJECT
public:
explicit MainWindow(QWidget *parent = nullptr);
~MainWindow();
private slots:
void onButtonClicked(); // 示例槽函数
private:
Ui::MainWindow *ui;
ros::NodeHandle nh;
};

6. 编译与运行


bash

cd ~/catkin_ws
catkin_make
source devel/setup.bash
rosrun my_qt_ros_pkg my_qt_ros_pkg

常见问题排查

  1. Qt头文件找不到
    确保已安装 qtbase5-dev

    
      

    bash

    sudo apt install qtbase5-dev
  2. 链接错误
    检查 CMakeLists.txt 中是否漏掉 target_link_libraries 中的Qt模块。

  3. 信号槽未生效
    确认在类声明中添加了 Q_OBJECT 宏,并重新运行 catkin_make

  4. ROS通信问题
    确保ROS节点已正确初始化,并在Qt线程中正确处理ROS回调(避免跨线程问题)。


通过以上步骤,即可将Qt界面与ROS节点集成,并通过catkin_make编译运行。


第五次

要在Qt 4.8.7和ROS环境下使用catkin_make编译,需按以下步骤调整配置(注意Qt4与Qt5的差异):


1. 安装Qt4依赖


bash

sudo apt install libqt4-dev # Ubuntu/Debian

2. 创建ROS功能包


bash

cd ~/catkin_ws/src
catkin_create_pkg my_qt4_ros_pkg roscpp std_msgs qt_build

3. 配置 package.xml

添加Qt4依赖:


xml

<build_depend>libqt4-dev</build_depend>
<exec_depend>libqt4-gui</exec_depend>
<depend>roscpp</depend>
<depend>std_msgs</depend>

4. 编写 CMakeLists.txt


cmake

cmake_minimum_required(VERSION 2.8.3)
project(my_qt4_ros_pkg)
## 查找Qt4和ROS依赖
find_package(catkin REQUIRED COMPONENTS
roscpp
std_msgs
)
find_package(Qt4 REQUIRED QtCore QtGui) # Qt4模块名称不同
## 设置C++标准(Qt4兼容C++98)
set(CMAKE_CXX_FLAGS "-std=c++98 ${CMAKE_CXX_FLAGS}")
## 生成UI头文件(如果有.ui文件)
set(QT_UI_FILES
src/main_window.ui
)
QT4_WRAP_UI(QT_UI_HEADERS ${QT_UI_FILES}) # Qt4专用命令
## 生成MOC头文件(处理Q_OBJECT宏)
set(QT_SOURCES
src/main_window.cpp
src/main.cpp
)
QT4_WRAP_CPP(QT_MOC_HEADERS ${QT_SOURCES}) # Qt4专用命令
## 添加可执行文件
add_executable(${PROJECT_NAME}
${QT_SOURCES}
${QT_UI_HEADERS}
${QT_MOC_HEADERS}
)
## 链接库(Qt4库名称不同)
target_link_libraries(${PROJECT_NAME}
${QT_LIBRARIES}
${catkin_LIBRARIES}
)
## 添加安装规则(可选)
install(TARGETS ${PROJECT_NAME}
RUNTIME DESTINATION ${CATKIN_PACKAGE_BIN_DESTINATION}
)

5. 代码调整(Qt4兼容性)

  • 信号槽语法:使用Qt4的SIGNALSLOT宏:
    
      

    cpp

    connect(button, SIGNAL(clicked()), this, SLOT(onButtonClicked()));
  • 头文件包含:使用Qt4风格:
    
      

    cpp

    #include <QtGui/QApplication>
    #include <QtGui/QMainWindow>

6. 编译与运行


bash

cd ~/catkin_ws
catkin_make
source devel/setup.bash
rosrun my_qt4_ros_pkg my_qt4_ros_pkg

常见问题排查

  1. Qt4未找到
    确保已安装libqt4-dev,并在CMakeLists.txt中正确指定find_package(Qt4)

  2. 链接错误
    检查target_link_libraries是否包含${QT_LIBRARIES}

  3. UI文件未生成
    确认QT4_WRAP_UIQT4_WRAP_CPP命令正确,且.ui文件路径无误。

  4. ROS与Qt事件循环冲突
    在Qt主窗口类中调用ros::spinOnce()而非ros::spin(),避免阻塞UI线程:

    
      

    cpp

    // 在MainWindow构造函数中启动定时器
    QTimer *timer = new QTimer(this);
    connect(timer, SIGNAL(timeout()), this, SLOT(rosSpinOnce()));
    timer->start(100); // 每100ms触发一次ros::spinOnce()

通过以上步骤,即可在Qt4.8.7和ROS环境下完成编译集成。


第六次

以下是针对您提供的Qt4+ROS代码的编译实现步骤:


1. 创建ROS功能包结构


bash

cd ~/catkin_ws/src
catkin_create_pkg patrol_gui roscpp std_msgs qt_build
cd patrol_gui
mkdir -p src include

2. 文件组织


patrol_gui/
├── CMakeLists.txt
├── package.xml
├── include/
│ └── patrol_gui.h
├── src/
│ ├── patrol_gui.cpp
│ └── main.cpp # 包含main函数
└── ...

3. 配置 package.xml


xml

<build_depend>libqt4-dev</build_depend>
<exec_depend>libqt4-gui</exec_depend>
<depend>roscpp</depend>
<depend>std_msgs</depend>
<depend>geometry_msgs</depend>

4. 编写 CMakeLists.txt


cmake

cmake_minimum_required(VERSION 2.8.3)
project(patrol_gui)
find_package(catkin REQUIRED COMPONENTS
roscpp
std_msgs
geometry_msgs
)
find_package(Qt4 REQUIRED QtCore QtGui)
set(CMAKE_CXX_FLAGS "-std=c++98 ${CMAKE_CXX_FLAGS}")
# Qt相关设置
set(QT_SOURCES
src/patrol_gui.cpp
src/main.cpp
)
QT4_WRAP_CPP(QT_MOC_HEADERS ${QT_SOURCES})
# 添加可执行文件
add_executable(${PROJECT_NAME}
${QT_SOURCES}
${QT_MOC_HEADERS}
)
# 链接库
target_link_libraries(${PROJECT_NAME}
${QT_LIBRARIES}
${catkin_LIBRARIES}
)
# 安装规则
install(TARGETS ${PROJECT_NAME}
RUNTIME DESTINATION ${CATKIN_PACKAGE_BIN_DESTINATION}
)

5. 代码调整要点

main.cpp 示例:

cpp

#include "patrol_gui.h"
#include <QApplication>
int main(int argc, char *argv[])
{
// 设置ROS日志重定向
ros::console::set_logger_level(ros::console::Level::Debug);
ros::console::set_logger_callback(customLogger);
QApplication app(argc, argv);
PatrolGUI window(argc, argv);
window.show();
return app.exec();
}
#include "main.moc"
日志回调改进:

cpp

// 在PatrolGUI类中添加静态成员
class PatrolGUI : public QMainWindow {
Q_OBJECT
public:
static PatrolGUI* instance_; // 添加静态实例指针
// ...
};
// 在.cpp中初始化
PatrolGUI* PatrolGUI::instance_ = nullptr;
PatrolGUI::PatrolGUI(...) {
instance_ = this;
}
// 修改日志回调
void customLogger(...) {
if(PatrolGUI::instance_) {
QMetaObject::invokeMethod(PatrolGUI::instance_, "logMessage",
Qt::QueuedConnection,
Q_ARG(QString, message));
}
}

6. 编译运行


bash

cd ~/catkin_ws
catkin_make
source devel/setup.bash
rosrun patrol_gui patrol_gui

常见问题处理

  1. 找不到Qt4库
    确保已安装:

    
      

    bash

    sudo apt install libqt4-dev qt4-qmake
  2. ROS节点初始化失败
    检查ros::init是否在GUI线程中调用,避免多线程问题。

  3. 日志不显示

    • 确保PatrolGUI::instance_正确初始化
    • 检查QMetaObject::invokeMethod是否使用Qt::QueuedConnection
  4. 界面无响应
    将ROS操作放在独立线程中:

    
      

    cpp

    // 在PatrolGUI类中添加
    void PatrolGUI::rosSpin() {
    while(ros::ok()) {
    ros::spinOnce();
    QThread::msleep(100);
    }
    }
    // 启动时
    void PatrolGUI::setupROS(...) {
    // ...
    ros_thread_ = new QThread(this);
    worker_ = new RosWorker();
    worker_->moveToThread(ros_thread_);
    connect(ros_thread_, SIGNAL(started()), worker_, SLOT(spin()));
    ros_thread_->start();
    }

通过以上配置,您应该能成功编译并运行Qt4+ROS的巡逻GUI程序。如果遇到具体错误,请提供编译日志以便进一步诊断。


后续列举了,都用不了。

为了使代码兼容Qt 4.8.7和ROS Kinetic,需要调整以下部分:

1. 修改后的代码(主要调整析构函数和Qt4语法)


cpp

#include <QTextCodec>
#include <QObject>
#include <QApplication>
#include <QWidget>
#include <QPainter>
#include <QMouseEvent>
#include <QLineEdit>
#include <QPushButton>
#include <QVBoxLayout>
#include <QLabel>
#include <QMessageBox>
#include <ros/ros.h>
#include <geometry_msgs/Twist.h>
class JoystickWidget : public QWidget {
Q_OBJECT
public:
JoystickWidget(ros::NodeHandle* nh, QWidget* parent = nullptr)
: QWidget(parent), nh_(nh), pub_(nh->advertise<geometry_msgs::Twist>("/cmd_vel", 10)),
center_(width()/2, height()/2) {
setWindowTitle("ROS图形化仿真摇杆-1609-2018");
setFixedSize(600, 550);
// 创建控件
topicEdit_ = new QLineEdit(this);
topicEdit_->setText("/cmd_vel");
applyButton_ = new QPushButton("应用话题", this);
// 布局设置
QVBoxLayout* mainLayout = new QVBoxLayout(this);
QHBoxLayout* controlLayout = new QHBoxLayout();
controlLayout->addWidget(new QLabel("ROS话题:"));
controlLayout->addWidget(topicEdit_);
controlLayout->addWidget(applyButton_);
mainLayout->addLayout(controlLayout);
mainLayout->addStretch();
// 连接信号槽(Qt4语法)
connect(applyButton_, SIGNAL(clicked()), this, SLOT(onApplyTopic()));
}
virtual ~JoystickWidget() {} // 添加虚析构函数定义
protected:
void paintEvent(QPaintEvent*) {
QPainter painter(this);
painter.setRenderHint(QPainter::Antialiasing);
// 绘制摇杆背景
painter.setBrush(Qt::gray);
painter.drawEllipse(center_, 150, 150);
// 绘制摇杆手柄
painter.setBrush(Qt::blue);
painter.drawEllipse(joystick_pos_, 15, 15);
}
void mousePressEvent(QMouseEvent* event) {
if (event->button() == Qt::LeftButton) {
joystick_pos_ = event->pos();
update();
}
}
void mouseMoveEvent(QMouseEvent* event) {
if (event->buttons() & Qt::LeftButton) {
QPointF delta = event->pos() - center_;
qreal distance = sqrt(delta.x()*delta.x() + delta.y()*delta.y());
if (distance > 150) {
delta *= 150 / distance;
joystick_pos_ = center_ + delta.toPoint();
} else {
joystick_pos_ = event->pos();
}
update();
geometry_msgs::Twist msg;
msg.linear.x = delta.x() / 150.0 * 0.5;
msg.angular.z = delta.y() / 150.0 * 1.0;
pub_.publish(msg);
}
}
void mouseReleaseEvent(QMouseEvent*) {
joystick_pos_ = center_;
update();
pub_.publish(geometry_msgs::Twist());
}
private slots:
void onApplyTopic() {
std::string newTopic = topicEdit_->text().toStdString();
if (newTopic.empty()) {
QMessageBox::warning(this, "警告", "话题名称不能为空!");
return;
}
pub_ = nh_->advertise<geometry_msgs::Twist>(newTopic, 10);
QMessageBox::information(this, "成功", "已更新话题为:\n" + QString::fromStdString(newTopic));
}
private:
ros::NodeHandle* nh_;
ros::Publisher pub_;
QPointF center_;
QPointF joystick_pos_;
QLineEdit* topicEdit_;
QPushButton* applyButton_;
};
int main(int argc, char** argv) {
// 设置编码
QTextCodec::setCodecForCStrings(QTextCodec::codecForName("UTF-8"));
QTextCodec::setCodecForTr(QTextCodec::codecForName("UTF-8"));
// ROS初始化
ros::init(argc, argv, "joystick_ros");
ros::NodeHandle nh;
// Qt应用初始化
QApplication app(argc, argv);
app.setFont(QFont("SimSun"));
// 创建主界面
JoystickWidget widget(&nh);
widget.show();
// ROS异步 spinner
ros::AsyncSpinner spinner(1);
spinner.start();
return app.exec();
}

2. 修改后的CMakeLists.txt(关键部分)


cmake

cmake_minimum_required(VERSION 2.8.3)
project(joystick_control)
find_package(catkin REQUIRED COMPONENTS
roscpp
geometry_msgs
)
find_package(Qt4 REQUIRED QtCore QtGui) # Qt4组件
include(${QT_USE_FILE})
qt4_wrap_cpp(MOC_SOURCES
src/qt_joystick/main.cpp
)
catkin_package()
include_directories(
${catkin_INCLUDE_DIRS}
${QT_INCLUDES}
)
add_executable(joystick_node
src/qt_joystick/main.cpp
${MOC_SOURCES}
)
target_link_libraries(joystick_node
${catkin_LIBRARIES}
${QT_LIBRARIES}
)

关键修改说明:

  1. 虚析构函数
    • 添加了virtual ~JoystickWidget() {}定义,确保正确释放资源
  2. Qt4兼容性调整
    • 使用find_package(Qt4 REQUIRED QtCore QtGui)替代Qt5的QtWidgets
    • 使用qt4_wrap_cpp生成moc文件
    • 保持Qt4风格的信号槽连接语法(SIGNAL()/SLOT()
  3. 头文件调整
    • 确保所有Qt头文件使用Qt4格式(如<QtGui/QWidget>可简化为<QWidget>
  4. 构建系统优化
    • 通过qt4_wrap_cpp自动生成moc文件
    • 正确链接Qt4核心库(QtCore/QtGui)

常见问题排查:

  1. 如果仍出现vtable错误
    • 确保所有包含Q_OBJECT宏的类都通过qt4_wrap_cpp处理
    • 检查类继承关系是否正确(QWidget派生类需要虚析构函数)
  2. 字体显示问题
    • 如果中文字体显示异常,尝试安装wqy-microhei字体包:
      
          

      bash

      sudo apt-get install fonts-wqy-microhei
  3. ROS通信问题
    • 确保/cmd_vel话题的订阅者已正确运行
    • 检查ROS节点名称是否冲突(代码中使用了ros::init(argc, argv, "joystick_ros")

建议在编译前清理构建目录:


bash

catkin clean -y
catkin_make

以下是为代码添加的详细注释,逐部分解释功能:


cpp

#include <QTextCodec> // Qt文本编码支持
#include <QApplication> // Qt应用核心类
#include <QWidget> // 基础窗口部件
#include <QPainter> // 2D绘图工具
#include <QMouseEvent> // 鼠标事件处理
#include <ros/ros.h> // ROS核心库
#include <geometry_msgs/Twist.h> // ROS速度指令消息类型
// 自定义摇杆控件类,继承自QWidget
class JoystickWidget : public QWidget {
public:
// 构造函数,接收ROS发布者指针和父部件
JoystickWidget(ros::Publisher* pub, QWidget* parent = nullptr)
: QWidget(parent), pub_(pub), center_(width()/2, height()/2) {
setWindowTitle("ROS图形化仿真摇杆-1609-2018"); // 设置窗口标题
setFixedSize(600, 480); // 固定窗口尺寸
}
protected:
// 重写绘图事件,用于绘制摇杆界面
void paintEvent(QPaintEvent*) override {
QPainter painter(this); // 创建绘图对象
painter.setRenderHint(QPainter::Antialiasing); // 启用抗锯齿
// 绘制摇杆背景(灰色大圆)
painter.setBrush(Qt::gray); // 设置填充颜色
painter.drawEllipse(center_, 150, 150); // 绘制中心在(300,240)的圆,半径150
// 绘制摇杆手柄(蓝色小圆)
painter.setBrush(Qt::blue); // 设置填充颜色
painter.drawEllipse(joystick_pos_, 15, 15); // 绘制手柄,半径15
}
// 鼠标按下事件处理
void mousePressEvent(QMouseEvent* event) override {
if (event->button() == Qt::LeftButton) { // 仅处理左键
joystick_pos_ = event->pos(); // 记录按下位置
update(); // 触发重绘
}
}
// 鼠标移动事件处理
void mouseMoveEvent(QMouseEvent* event) override {
if (event->buttons() & Qt::LeftButton) { // 仅当左键按下时处理
// 计算当前位置与中心的偏移量
QPointF delta = event->pos() - center_;
// 限制摇杆移动范围(不超过半径150)
qreal distance = sqrt(delta.x()*delta.x() + delta.y()*delta.y());
if (distance > 150) {
delta *= 150 / distance; // 按比例缩放偏移量
joystick_pos_ = center_ + delta.toPoint(); // 更新手柄位置
} else {
joystick_pos_ = event->pos(); // 未超出范围时直接使用当前位置
}
update(); // 触发界面更新
// 计算并发布速度指令
geometry_msgs::Twist msg;
msg.linear.x = - delta.y() / 32.0; // 线速度(Y轴反向映射到前进/后退)
msg.angular.z = - delta.x() / 32.0; // 角速度(X轴反向映射到左转/右转)
pub_->publish(msg); // 发布到ROS话题
}
}
// 鼠标释放事件处理
void mouseReleaseEvent(QMouseEvent*) override {
joystick_pos_ = center_; // 重置手柄到中心
update(); // 触发界面更新
pub_->publish(geometry_msgs::Twist()); // 发布零速度指令
}
private:
ros::Publisher* pub_; // ROS发布者指针
QPointF center_; // 摇杆中心坐标(300,240)
QPointF joystick_pos_; // 摇杆手柄当前位置
};
int main(int argc, char** argv) {
// 设置Qt文本编码为UTF-8(解决中文乱码)
QTextCodec::setCodecForCStrings(QTextCodec::codecForName("UTF-8"));
QTextCodec::setCodecForTr(QTextCodec::codecForName("UTF-8"));
// 初始化ROS节点
ros::init(argc, argv, "joystick_ros");
ros::NodeHandle nh; // 创建节点句柄
ros::Publisher pub = nh.advertise<geometry_msgs::Twist>("/cmd_vel", 10); // 创建发布者
// 初始化Qt应用
QApplication app(argc, argv);
JoystickWidget widget(&pub); // 创建摇杆控件实例
widget.show(); // 显示窗口
// 创建异步Spinner(避免阻塞Qt事件循环)
ros::AsyncSpinner spinner(1); // 1个线程处理ROS回调
spinner.start(); // 启动Spinner
return app.exec(); // 进入Qt主事件循环
}

关键功能说明:

  1. 界面绘制
    • 使用QPainter绘制双层圆形(背景灰色,手柄蓝色)
    • 通过paintEvent实现动态重绘
  2. 鼠标交互
    • 左键按下时开始控制摇杆
    • 移动时通过向量计算限制在圆形范围内
    • 释放时自动归位并发送零速度指令
  3. 速度映射
    • Y轴偏移映射为线速度(前进/后退)
    • X轴偏移映射为角速度(左转/右转)
    • 通过/32.0系数降低速度值(可根据需求调整)
  4. ROS集成
    • 使用AsyncSpinner实现非阻塞ROS通信
    • 通过geometry_msgs/Twist消息类型发布速度指令
    • 默认发布到/cmd_vel话题(需与机器人订阅话题匹配)

扩展建议:

  • 可添加速度限制参数(如最大线速度/角速度)
  • 可增加摇杆死区(微小移动不产生指令)
  • 可添加视觉反馈(如速度值显示)
  • 可支持多摇杆配置(通过参数化话题名称)

在 Linux 终端中,你可以编写一个简单的 Bash 脚本来重复运行某个命令十次。假设你有一个名为 draw_circles_pro.dat 的可执行文件或脚本,你可以创建一个 Bash 脚本来实现这个功能。

以下是一个示例脚本:


bash

#!/bin/bash
# 脚本用于重复运行 ./draw_circles_pro.dat 十次
for i in {1..10}
do
echo "运行第 $i 次"
./draw_circles_pro.dat
# 如果需要在每次运行之间等待,可以取消下一行的注释
# sleep 1 # 等待1秒(根据需要调整时间)
done

使用说明:

  1. 创建脚本文件
    • 使用文本编辑器(如 nano 或 vi)创建一个新的脚本文件,例如 run_draw_circles.sh
  2. 复制脚本内容
    • 将上面的脚本内容复制并粘贴到 run_draw_circles.sh 文件中。
  3. 赋予执行权限
    • 在终端中,导航到脚本所在的目录,然后运行以下命令赋予脚本执行权限:
      
          

      bash

      chmod +x run_draw_circles.sh
  4. 运行脚本
    • 通过以下命令运行脚本:
      
          

      bash

      ./run_draw_circles.sh

注意事项:

  • 确保 draw_circles_pro.dat 文件在当前目录中是可执行的。如果不是,你可能需要先赋予它执行权限:

    
      

    bash

    chmod +x draw_circles_pro.dat
  • 如果 draw_circles_pro.dat 需要特定的输入参数或环境配置,请确保在运行脚本之前已经设置好。

  • 如果需要在每次运行之间暂停一段时间,可以取消 sleep 命令的注释,并根据需要调整等待时间。

本文介绍了在ROS Kinetic环境下实现差速导航机器人仿真与控制的完整流程,包含Qt4 GUI开发与ROS集成方案。主要内容包括:1. ROS差速导航机器人实现:涵盖URDF模型构建、TEB规划器配置、Stage仿真环境搭建及导航测试;2. C++巡逻控制程序开发:实现多点巡航功能,包含CMake配置和异常处理机制;3. Qt4.8.7与ROS集成方案:详细说明信号槽机制、日志重定向、线程安全处理等关键技术;4. 完整编译配置:提供Qt4+ROS的CMakeLists.txt和package.xml适配方案,解决常见兼容性问题。文中还包含Bash脚本控制方法,可自动重复执行测试任务。

Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐