ROS与Qt结合开发CAN控制界面(发布自定义的truck_send_can1消息)

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

标题: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)
  • 依赖包roscppstd_msgstruck_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
五、常见问题与解决方案
  1. 找不到Qt5Widgets组件

    • 错误:Could NOT find Qt5Widgets

    • 解决:在CMakeLists.txt中手动指定路径:

      cmake 复制代码
      set(Qt5Widgets_DIR "/usr/lib/x86_64-linux-gnu/cmake/Qt5Widgets")
  2. catkin_package()包含非ROS依赖

    • 错误:CATKIN_DEPENDS on 'Qt5Widgets'
    • 解决:CATKIN_DEPENDS只保留ROS包(如roscpptruck_msgs),移除Qt5Widgets
  3. 消息字段不存在(如'data')

    • 错误:'truck_send_can1' has no member named 'data'
    • 解决:查看truck_send_can1.msg的实际字段(如throttlebrake),修改代码中消息赋值逻辑。
  4. Qt枚举未定义(如SkipEmptyParts)

    • 错误:'SkipEmptyParts' is not a member of 'Qt'
    • 解决:使用QString::SkipEmptyParts并包含<QStringList>头文件。
  5. 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控制消息。关键步骤包括:

  1. 正确配置CMakeLists.txt,处理Qt的UI文件和元对象代码(MOC)。
  2. 适配自定义消息结构,确保代码中字段名与.msg文件一致。
  3. 解决Qt与ROS集成的常见问题(如依赖路径、枚举定义等)。

通过扩展界面组件和消息处理逻辑,可进一步实现更复杂的机器人控制功能。

相关推荐
java1234_小锋3 小时前
什么是Java三高架构?
java·开发语言·架构
如竟没有火炬3 小时前
全排列——交换的思想
开发语言·数据结构·python·算法·leetcode·深度优先
嵌入式小李.man4 小时前
C++第十三篇:继承
开发语言·c++
Bryce李小白4 小时前
Kotlin Flow 的使用
android·开发语言·kotlin
jarreyer4 小时前
python离线包安装方法总结
开发语言·python
李辰洋4 小时前
go tools安装
开发语言·后端·golang
wanfeng_094 小时前
go lang
开发语言·后端·golang
绛洞花主敏明4 小时前
go build -tags的其他用法
开发语言·后端·golang
ByteCraze4 小时前
秋招被问到的常见问题
开发语言·javascript·原型模式