以下是基于本次开发的完整总结,包含操作步骤、问题解决、文件结构及最终代码,适合作为博客发布:

标题:ROS与Qt结合开发CAN控制界面(发布truck_send_can1消息)
一、项目背景
本文记录如何使用ROS与Qt开发一个简单的CAN控制界面,实现通过GUI发送truck_msgs
功能包中的truck_send_can1
消息(包含油门、刹车、转向等控制指令)。
二、环境准备
- 系统:Ubuntu 20.04
- ROS版本:ROS Noetic
- Qt版本:Qt5(含Qt Creator)
- 依赖包 :
roscpp
、std_msgs
、truck_msgs
(自定义消息包)
三、开发步骤
1. 安装依赖
bash
# 安装Qt开发工具
sudo apt update
sudo apt install qtcreator qt5-default qtbase5-dev libqt5widgets5
# 安装ROS依赖
sudo apt install ros-noetic-catkin python3-catkin-tools
2. 创建工作空间与功能包
bash
# 创建工作空间
mkdir -p ~/can_ws/src
cd ~/can_ws
catkin_make
source devel/setup.bash
# 创建功能包(依赖ROS和Qt)
cd src
catkin_create_pkg can_control_gui roscpp std_msgs truck_msgs Qt5Widgets
cd ..
catkin_make
source devel/setup.bash
3. 创建项目文件
在~/can_ws/src/can_control_gui/src
目录下创建以下文件:
can_control_ui.ui
(Qt界面文件)can_control_node.h
(头文件)can_control_node.cpp
(源文件)main.cpp
(主函数)
4. 配置CMakeLists.txt
修改~/can_ws/src/can_control_gui/CMakeLists.txt
,确保Qt与ROS正确集成:
cmake
cmake_minimum_required(VERSION 3.0.2)
project(can_control_gui)
# 指定Qt5Widgets路径
set(Qt5Widgets_DIR "/usr/lib/x86_64-linux-gnu/cmake/Qt5Widgets")
# 查找依赖
find_package(catkin REQUIRED COMPONENTS
roscpp
std_msgs
truck_msgs
Qt5Widgets
)
# 声明catkin包信息
catkin_package(
CATKIN_DEPENDS roscpp std_msgs truck_msgs
)
# 包含头文件路径
include_directories(
${catkin_INCLUDE_DIRS}
${Qt5Widgets_INCLUDE_DIRS}
${CMAKE_CURRENT_BINARY_DIR} # 包含UI生成文件
)
# 源文件和UI文件
set(SOURCES
src/main.cpp
src/can_control_node.cpp
src/can_control_node.h
)
set(UIS
src/can_control_ui.ui
)
# 处理UI文件(生成.ui对应的头文件)
qt5_wrap_ui(UI_HEADERS ${UIS})
# 处理含Q_OBJECT的头文件(生成MOC代码)
qt5_wrap_cpp(MOC_SOURCES
src/can_control_node.h
)
# 生成可执行文件
add_executable(can_control_node
${SOURCES}
${UI_HEADERS}
${MOC_SOURCES}
)
# 链接依赖库
target_link_libraries(can_control_node
${catkin_LIBRARIES}
Qt5::Widgets
)
5. 编写代码
见下文"六、完整代码"部分。
6. 编译与运行
bash
# 编译
cd ~/can_ws
catkin_make
source devel/setup.bash
# 运行
# 终端1:启动ROS核心
roscore
# 终端2:运行控制界面
rosrun can_control_gui can_control_node
# 终端3:监听消息(验证)
rostopic echo /truck_send_can1
四、项目文件结构
~/can_ws/
├── build/ # 编译输出目录(自动生成)
├── devel/ # 开发空间(自动生成)
│ └── lib/can_control_gui/
│ └── can_control_node # 可执行文件
└── src/
└── can_control_gui/ # 功能包目录
├── CMakeLists.txt # 编译配置
├── package.xml # 功能包信息
└── src/
├── can_control_ui.ui # Qt界面设计
├── can_control_node.h # 界面类头文件
├── can_control_node.cpp # 界面类实现
└── main.cpp # 主函数
└── truck_msgs/ # 自定义消息包
├── msg/
│ └── truck_send_can1.msg # 消息定义
├── CMakeLists.txt
└── package.xml
五、常见问题与解决方案
-
找不到Qt5Widgets组件
-
错误:
Could NOT find Qt5Widgets
-
解决:在
CMakeLists.txt
中手动指定路径:cmakeset(Qt5Widgets_DIR "/usr/lib/x86_64-linux-gnu/cmake/Qt5Widgets")
-
-
catkin_package()包含非ROS依赖
- 错误:
CATKIN_DEPENDS on 'Qt5Widgets'
- 解决:
CATKIN_DEPENDS
只保留ROS包(如roscpp
、truck_msgs
),移除Qt5Widgets
。
- 错误:
-
消息字段不存在(如'data')
- 错误:
'truck_send_can1' has no member named 'data'
- 解决:查看
truck_send_can1.msg
的实际字段(如throttle
、brake
),修改代码中消息赋值逻辑。
- 错误:
-
Qt枚举未定义(如SkipEmptyParts)
- 错误:
'SkipEmptyParts' is not a member of 'Qt'
- 解决:使用
QString::SkipEmptyParts
并包含<QStringList>
头文件。
- 错误:
-
UI文件中setPlaceholderText错误
- 错误:
'QWidget' has no member named 'setPlaceholderText'
- 解决:确保该属性只应用于
QLineEdit
(输入框),而非顶层QWidget
。
- 错误:
六、完整代码
1. UI界面文件(can_control_ui.ui)
xml
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>CanControlUI</class>
<widget class="QWidget" name="CanControlUI">
<property name="geometry">
<rect>
<x>100</x>
<y>100</y>
<width>500</width>
<height>200</height>
</rect>
</property>
<property name="windowTitle">
<string>CAN 控制界面</string>
</property>
<widget class="QLineEdit" name="canDataEdit">
<property name="geometry">
<rect>
<x>50</x>
<y>50</y>
<width>400</width>
<height>30</height>
</rect>
</property>
<property name="placeholderText">
<string>输入格式:throttle brake steer_direction steer raise_arm drop_arm emergency_stop(空格分隔,例:50 0 1 30 0 1 0)</string>
</property>
</widget>
<widget class="QPushButton" name="publishBtn">
<property name="geometry">
<rect>
<x>200</x>
<y>110</y>
<width>100</width>
<height>30</height>
</rect>
</property>
<property name="text">
<string>发送CAN消息</string>
</property>
</widget>
</widget>
<resources/>
<connections/>
</ui>
2. 头文件(can_control_node.h)
cpp
#ifndef CAN_CONTROL_NODE_H
#define CAN_CONTROL_NODE_H
#include <QWidget>
#include <ros/ros.h>
#include "truck_msgs/truck_send_can1.h" // 自定义消息
#include "ui_can_control_ui.h" // UI生成的头文件
namespace can_control_gui {
class CanControlNode : public QWidget {
Q_OBJECT // 信号槽必须的宏
public:
CanControlNode(QWidget *parent = nullptr);
~CanControlNode();
private slots:
void onPublishBtnClicked(); // 发送按钮回调
private:
Ui::CanControlUI *ui; // UI界面对象
ros::NodeHandle nh; // ROS节点句柄
ros::Publisher can_pub; // 发布truck_send_can1消息
};
} // namespace can_control_gui
#endif // CAN_CONTROL_NODE_H
3. 源文件(can_control_node.cpp)
cpp
#include "can_control_node.h"
#include <QString>
#include <QStringList> // 用于分割输入字符串
namespace can_control_gui {
// 构造函数:初始化界面和ROS发布器
CanControlNode::CanControlNode(QWidget *parent) : QWidget(parent), ui(new Ui::CanControlUI) {
ui->setupUi(this);
// 初始化发布器:话题名truck_send_can1,消息类型truck_msgs::truck_send_can1
can_pub = nh.advertise<truck_msgs::truck_send_can1>("truck_send_can1", 10);
// 绑定按钮点击事件
connect(ui->publishBtn, &QPushButton::clicked, this, &CanControlNode::onPublishBtnClicked);
}
// 析构函数:释放UI资源
CanControlNode::~CanControlNode() {
delete ui;
}
// 按钮点击回调:发送CAN消息
void CanControlNode::onPublishBtnClicked() {
// 输入格式:throttle brake steer_direction steer raise_arm drop_arm emergency_stop
QString inputText = ui->canDataEdit->text();
if (inputText.isEmpty()) return;
// 分割输入为7个字段(跳过空值)
QStringList parts = inputText.split(" ", QString::SkipEmptyParts);
if (parts.size() != 7) return; // 必须输入7个值
// 构造消息并赋值
truck_msgs::truck_send_can1 msg;
msg.throttle = parts[0].toUInt(); // 油门
msg.brake = parts[1].toUInt(); // 刹车
msg.steer_direction = parts[2].toUInt(); // 转向方向
msg.steer = parts[3].toUInt(); // 转向角度
msg.raise_arm = parts[4].toUInt(); // 升臂
msg.drop_arm = parts[5].toUInt(); // 降臂
msg.emergency_stop = parts[6].toUInt(); // 急停
// 发布消息
can_pub.publish(msg);
ui->canDataEdit->clear(); // 清空输入框
}
} // namespace can_control_gui
4. 主函数(main.cpp)
cpp
#include <QApplication>
#include "can_control_node.h"
#include <ros/ros.h>
int main(int argc, char **argv) {
// 初始化ROS节点
ros::init(argc, argv, "can_control_gui_node");
// 初始化Qt应用
QApplication app(argc, argv);
// 创建并显示界面
can_control_gui::CanControlNode window;
window.show();
// 并行处理Qt事件和ROS循环
ros::Rate rate(10); // 10Hz
while (window.isVisible() && ros::ok()) {
app.processEvents(); // 处理Qt界面事件
ros::spinOnce(); // 处理ROS回调
rate.sleep();
}
return 0;
}
七、总结
本项目实现了一个基于Qt的ROS界面,用于发送自定义CAN控制消息。关键步骤包括:
- 正确配置CMakeLists.txt,处理Qt的UI文件和元对象代码(MOC)。
- 适配自定义消息结构,确保代码中字段名与
.msg
文件一致。 - 解决Qt与ROS集成的常见问题(如依赖路径、枚举定义等)。
通过扩展界面组件和消息处理逻辑,可进一步实现更复杂的机器人控制功能。