Pi0具身智能与QT开发跨平台控制界面
最近在折腾机器人控制软件,发现很多朋友对如何给Pi0这类具身智能模型开发一个好看又好用的控制界面很感兴趣。今天就来聊聊怎么用QT框架搭建一个跨平台的机器人控制软件,从UI设计到多线程控制,再到数据可视化,一步步带你实现。
说实话,刚开始我也觉得这事儿挺复杂的,既要处理模型推理,又要做界面交互,还得考虑跨平台兼容。但用QT做下来发现,其实没想象中那么难。QT的跨平台特性真的帮了大忙,一套代码就能在Windows、Linux、macOS上跑,省去了很多重复工作。
1. 为什么选择QT做机器人控制软件?
如果你做过机器人开发,肯定知道控制软件有多重要。一个好的控制界面不仅要能实时显示机器人状态,还要能方便地发送指令、查看数据。QT在这方面有几个明显的优势:
跨平台能力:这是QT最大的卖点。你写一次代码,就能在多个操作系统上运行。对于机器人开发来说特别实用,因为实验室可能用Linux,演示用Windows,开发用macOS,QT能帮你省去适配不同平台的麻烦。
丰富的UI组件:QT自带了大量现成的UI控件,按钮、滑块、图表、表格应有尽有。做机器人控制界面需要的各种元素基本都能找到,不用从头造轮子。
强大的图形能力:QT的图形系统很强大,做数据可视化特别方便。比如实时显示机器人的关节角度、末端位置、力传感器数据,用QT的图表组件就能轻松实现。
成熟的信号槽机制:这是QT的核心特性,用起来特别顺手。简单说就是“事件驱动”,比如按钮点击触发某个函数,传感器数据更新自动刷新界面。这种机制让代码结构清晰,逻辑分明。
社区和生态成熟:QT有20多年的历史,社区活跃,文档齐全。遇到问题基本都能找到解决方案,第三方库和工具也很丰富。
我最近在用的Pi0具身智能模型,需要实时控制机械臂动作,同时还要显示摄像头画面、传感器数据。用QT做下来,整体体验很不错,开发效率比预想的高不少。
2. 环境搭建与项目初始化
先说说怎么把开发环境搭起来。QT的安装现在挺方便的,我推荐用官方提供的在线安装器,能按需选择组件,比较灵活。
2.1 安装QT开发环境
# 对于Ubuntu/Debian系统 sudo apt update sudo apt install qt6-base-dev qt6-charts-dev qt6-serialport-dev # 对于macOS brew install qt6 # Windows用户可以直接下载官方安装包 # 下载地址:https://www.qt.io/download安装完成后,建议再装几个有用的工具:
- QT Creator:官方的集成开发环境,对新手很友好
- QT Designer:可视化界面设计工具,拖拖拽拽就能做UI
- CMake:现在QT推荐用CMake管理项目,比qmake更灵活
2.2 创建QT项目
用CMake管理项目结构更清晰,这是我的项目目录结构:
robot_control_software/ ├── CMakeLists.txt # 项目配置文件 ├── src/ # 源代码目录 │ ├── main.cpp # 程序入口 │ ├── MainWindow.cpp # 主窗口实现 │ ├── MainWindow.h # 主窗口头文件 │ ├── RobotController.cpp # 机器人控制逻辑 │ └── RobotController.h ├── ui/ # 界面文件 │ └── mainwindow.ui # 主窗口界面设计 ├── resources/ # 资源文件 │ ├── icons/ # 图标 │ └── styles/ # 样式表 └── build/ # 编译输出目录对应的CMakeLists.txt配置:
cmake_minimum_required(VERSION 3.16) project(RobotControlSoftware VERSION 1.0.0 LANGUAGES CXX) # 设置C++标准 set(CMAKE_CXX_STANDARD 17) set(CMAKE_CXX_STANDARD_REQUIRED ON) # 查找QT库 find_package(Qt6 REQUIRED COMPONENTS Core Widgets Charts SerialPort) # 启用MOC(元对象编译器) set(CMAKE_AUTOMOC ON) set(CMAKE_AUTORCC ON) set(CMAKE_AUTOUIC ON) # 添加可执行文件 add_executable(RobotControlSoftware src/main.cpp src/MainWindow.cpp src/RobotController.cpp ui/mainwindow.ui ) # 链接QT库 target_link_libraries(RobotControlSoftware Qt6::Core Qt6::Widgets Qt6::Charts Qt6::SerialPort ) # 添加资源文件 qt_add_resources(RobotControlSoftware "resources" PREFIX "/" FILES resources/icons/robot.png resources/icons/play.png resources/icons/stop.png )3. 主界面设计与布局
控制软件的界面要既美观又实用。我的设计思路是:左侧放控制面板,中间显示实时数据,右侧显示状态信息。
3.1 使用QT Designer设计界面
QT Designer是可视化设计工具,用起来很方便。这是主窗口的基本布局:
<!-- ui/mainwindow.ui --> <?xml version="1.0" encoding="UTF-8"?> <ui version="4.0"> <class>MainWindow</class> <widget class="QMainWindow" name="MainWindow"> <property name="geometry"> <rect> <x>0</x> <y>0</y> <width>1200</width> <height>800</height> </rect> </property> <property name="windowTitle"> <string>Pi0机器人控制软件</string> </property> <!-- 中央部件 --> <widget class="QWidget" name="centralWidget"> <layout class="QHBoxLayout" name="horizontalLayout"> <!-- 左侧控制面板 --> <widget class="QGroupBox" name="controlPanel"> <property name="title"> <string>控制面板</string> </property> <layout class="QVBoxLayout" name="verticalLayout"> <!-- 这里放各种控制按钮和滑块 --> </layout> </widget> <!-- 中间数据展示区 --> <widget class="QTabWidget" name="dataTabWidget"> <property name="currentIndex"> <number>0</number> </property> <widget class="QWidget" name="realtimeTab"> <property name="title"> <string>实时数据</string> </property> <!-- 图表和数据显示 --> </widget> <widget class="QWidget" name="cameraTab"> <property name="title"> <string>摄像头画面</string> </property> </widget> </widget> <!-- 右侧状态面板 --> <widget class="QGroupBox" name="statusPanel"> <property name="title"> <string>系统状态</string> </property> <layout class="QVBoxLayout" name="verticalLayout_2"> <!-- 状态指示灯和日志 --> </layout> </widget> </layout> </widget> <!-- 菜单栏 --> <widget class="QMenuBar" name="menuBar"> <property name="geometry"> <rect> <x>0</x> <y>0</y> <width>1200</width> <height>25</height> </rect> </property> <widget class="QMenu" name="menuFile"> <property name="title"> <string>文件</string> </property> <addaction name="actionOpen"/> <addaction name="actionSave"/> <addaction name="actionExit"/> </widget> </widget> <!-- 状态栏 --> <widget class="QStatusBar" name="statusBar"/> </widget> <resources/> <connections/> </ui>3.2 代码中加载和使用界面
设计好界面后,在代码里这样加载:
// src/MainWindow.h #ifndef MAINWINDOW_H #define MAINWINDOW_H #include <QMainWindow> #include <QTimer> #include <QChartView> #include <QLineSeries> QT_CHARTS_USE_NAMESPACE namespace Ui { class MainWindow; } class MainWindow : public QMainWindow { Q_OBJECT public: explicit MainWindow(QWidget *parent = nullptr); ~MainWindow(); private slots: void onStartButtonClicked(); void onStopButtonClicked(); void updateRealtimeData(); void updateStatusDisplay(); private: Ui::MainWindow *ui; QTimer *dataUpdateTimer; QChart *jointAngleChart; QLineSeries *jointSeries[6]; // 6个关节的角度数据 void setupCharts(); void setupConnections(); }; #endif // MAINWINDOW_H// src/MainWindow.cpp #include "MainWindow.h" #include "ui_mainwindow.h" #include <QMessageBox> #include <QDateTime> MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent) , ui(new Ui::MainWindow) { ui->setupUi(this); // 设置窗口图标和标题 setWindowIcon(QIcon(":/icons/robot.png")); setWindowTitle("Pi0机器人控制软件 v1.0"); // 初始化图表 setupCharts(); // 设置定时器,每100ms更新一次数据 dataUpdateTimer = new QTimer(this); connect(dataUpdateTimer, &QTimer::timeout, this, &MainWindow::updateRealtimeData); // 连接信号槽 setupConnections(); // 初始状态显示 updateStatusDisplay(); } MainWindow::~MainWindow() { delete ui; } void MainWindow::setupCharts() { // 创建关节角度图表 jointAngleChart = new QChart(); jointAngleChart->setTitle("关节角度实时监控"); jointAngleChart->setAnimationOptions(QChart::SeriesAnimations); // 创建6个数据系列,对应6个关节 QPen pen; pen.setWidth(2); QStringList jointNames = {"关节1", "关节2", "关节3", "关节4", "关节5", "关节6"}; QList<QColor> colors = {Qt::red, Qt::green, Qt::blue, Qt::cyan, Qt::magenta, Qt::yellow}; for (int i = 0; i < 6; i++) { jointSeries[i] = new QLineSeries(); jointSeries[i]->setName(jointNames[i]); pen.setColor(colors[i]); jointSeries[i]->setPen(pen); // 添加一些初始数据点 for (int j = 0; j < 50; j++) { jointSeries[i]->append(j, 0); } jointAngleChart->addSeries(jointSeries[i]); } // 设置坐标轴 jointAngleChart->createDefaultAxes(); jointAngleChart->axisX()->setTitleText("时间 (×100ms)"); jointAngleChart->axisY()->setTitleText("角度 (°)"); // 将图表添加到界面 QChartView *chartView = new QChartView(jointAngleChart); chartView->setRenderHint(QPainter::Antialiasing); ui->realtimeTab->layout()->addWidget(chartView); } void MainWindow::setupConnections() { // 连接按钮点击信号 connect(ui->startButton, &QPushButton::clicked, this, &MainWindow::onStartButtonClicked); connect(ui->stopButton, &QPushButton::clicked, this, &MainWindow::onStopButtonClicked); // 连接滑块值变化信号 connect(ui->speedSlider, &QSlider::valueChanged, [this](int value) { ui->speedLabel->setText(QString("速度: %1%").arg(value)); }); } void MainWindow::onStartButtonClicked() { if (!dataUpdateTimer->isActive()) { dataUpdateTimer->start(100); // 100ms间隔 ui->statusBar->showMessage("已启动数据监控", 3000); ui->startButton->setEnabled(false); ui->stopButton->setEnabled(true); } } void MainWindow::onStopButtonClicked() { if (dataUpdateTimer->isActive()) { dataUpdateTimer->stop(); ui->statusBar->showMessage("已停止数据监控", 3000); ui->startButton->setEnabled(true); ui->stopButton->setEnabled(false); } } void MainWindow::updateRealtimeData() { // 这里模拟更新关节角度数据 static int timeIndex = 0; for (int i = 0; i < 6; i++) { // 模拟数据:正弦波加随机噪声 double angle = 30 * sin(timeIndex * 0.1 + i * 0.5) + (qrand() % 10 - 5); // 更新数据系列 QVector<QPointF> points = jointSeries[i]->pointsVector(); points.removeFirst(); points.append(QPointF(timeIndex, angle)); jointSeries[i]->replace(points); } timeIndex++; // 更新X轴范围,实现滚动效果 if (timeIndex > 50) { jointAngleChart->axisX()->setRange(timeIndex - 50, timeIndex); } // 更新状态显示 updateStatusDisplay(); } void MainWindow::updateStatusDisplay() { QString statusText = QString("最后更新: %1 | 数据点数: %2") .arg(QDateTime::currentDateTime().toString("hh:mm:ss")) .arg(jointSeries[0]->count()); ui->statusLabel->setText(statusText); }4. 多线程机器人控制实现
机器人控制最怕的就是界面卡顿。QT的多线程机制能很好地解决这个问题,让界面响应和后台控制分开执行。
4.1 创建机器人控制线程
// src/RobotController.h #ifndef ROBOTCONTROLLER_H #define ROBOTCONTROLLER_H #include <QObject> #include <QThread> #include <QMutex> #include <QWaitCondition> #include <atomic> class RobotController : public QObject { Q_OBJECT public: explicit RobotController(QObject *parent = nullptr); ~RobotController(); // 控制接口 void startControl(); void stopControl(); void setTargetPosition(double x, double y, double z); void setJointAngles(const QVector<double> &angles); // 状态获取 QVector<double> getCurrentJointAngles() const; QVector<double> getCurrentPosition() const; bool isRunning() const; signals: void dataUpdated(const QVector<double> &jointAngles); void positionUpdated(double x, double y, double z); void errorOccurred(const QString &error); void statusChanged(const QString &status); public slots: void controlLoop(); private: std::atomic<bool> m_running{false}; mutable QMutex m_mutex; QWaitCondition m_condition; // 机器人状态 QVector<double> m_targetJointAngles; QVector<double> m_currentJointAngles; QVector<double> m_targetPosition; QVector<double> m_currentPosition; // 模拟机器人运动 void simulateRobotMotion(); void updateJointAngles(); void updatePosition(); }; #endif // ROBOTCONTROLLER_H// src/RobotController.cpp #include "RobotController.h" #include <QDebug> #include <QElapsedTimer> #include <cmath> RobotController::RobotController(QObject *parent) : QObject(parent) , m_targetJointAngles(6, 0.0) , m_currentJointAngles(6, 0.0) , m_targetPosition(3, 0.0) , m_currentPosition(3, 0.0) { qDebug() << "RobotController created"; } RobotController::~RobotController() { stopControl(); qDebug() << "RobotController destroyed"; } void RobotController::startControl() { if (!m_running) { m_running = true; emit statusChanged("控制器已启动"); // 在单独的线程中启动控制循环 QThread *thread = new QThread; moveToThread(thread); connect(thread, &QThread::started, this, &RobotController::controlLoop); connect(this, &RobotController::finished, thread, &QThread::quit); connect(this, &RobotController::finished, this, &RobotController::deleteLater); connect(thread, &QThread::finished, thread, &QThread::deleteLater); thread->start(); } } void RobotController::stopControl() { if (m_running) { m_running = false; m_condition.wakeAll(); // 唤醒等待的线程 emit finished(); emit statusChanged("控制器已停止"); } } void RobotController::setTargetPosition(double x, double y, double z) { QMutexLocker locker(&m_mutex); m_targetPosition[0] = x; m_targetPosition[1] = y; m_targetPosition[2] = z; emit statusChanged(QString("设置目标位置: (%1, %2, %3)").arg(x).arg(y).arg(z)); } void RobotController::setJointAngles(const QVector<double> &angles) { if (angles.size() != 6) { emit errorOccurred("关节角度数量错误,需要6个角度值"); return; } QMutexLocker locker(&m_mutex); m_targetJointAngles = angles; emit statusChanged("设置目标关节角度"); } QVector<double> RobotController::getCurrentJointAngles() const { QMutexLocker locker(&m_mutex); return m_currentJointAngles; } QVector<double> RobotController::getCurrentPosition() const { QMutexLocker locker(&m_mutex); return m_currentPosition; } bool RobotController::isRunning() const { return m_running; } void RobotController::controlLoop() { qDebug() << "控制循环开始,线程ID:" << QThread::currentThreadId(); QElapsedTimer timer; timer.start(); while (m_running) { // 控制频率:50Hz(20ms周期) int elapsed = timer.restart(); int waitTime = 20 - elapsed; if (waitTime > 0) { QMutexLocker locker(&m_mutex); m_condition.wait(&m_mutex, waitTime); } if (!m_running) break; // 执行控制逻辑 simulateRobotMotion(); // 发送更新信号 emit dataUpdated(m_currentJointAngles); emit positionUpdated(m_currentPosition[0], m_currentPosition[1], m_currentPosition[2]); } qDebug() << "控制循环结束"; emit finished(); } void RobotController::simulateRobotMotion() { QMutexLocker locker(&m_mutex); // 模拟关节角度向目标值移动 for (int i = 0; i < 6; i++) { double error = m_targetJointAngles[i] - m_currentJointAngles[i]; m_currentJointAngles[i] += error * 0.1; // 简单的比例控制 } // 模拟末端位置更新(简单的正向运动学) // 这里使用简化的模型,实际应用中需要根据机器人DH参数计算 double theta1 = m_currentJointAngles[0] * M_PI / 180.0; double theta2 = m_currentJointAngles[1] * M_PI / 180.0; double theta3 = m_currentJointAngles[2] * M_PI / 180.0; // 简单的3自由度机械臂正向运动学 double l1 = 0.3, l2 = 0.25, l3 = 0.2; // 连杆长度 m_currentPosition[0] = l1 * cos(theta1) + l2 * cos(theta1 + theta2) + l3 * cos(theta1 + theta2 + theta3); m_currentPosition[1] = l1 * sin(theta1) + l2 * sin(theta1 + theta2) + l3 * sin(theta1 + theta2 + theta3); m_currentPosition[2] = 0.1; // 固定高度 }4.2 在主窗口中使用控制线程
// 在MainWindow.cpp中添加 #include "RobotController.h" // 在MainWindow类中添加成员变量 private: RobotController *robotController; QThread *controlThread; // 在构造函数中初始化 MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent) , ui(new Ui::MainWindow) , robotController(new RobotController) , controlThread(new QThread) { ui->setupUi(this); // 将控制器移到单独的线程 robotController->moveToThread(controlThread); // 连接控制器的信号 connect(robotController, &RobotController::dataUpdated, this, [this](const QVector<double> &angles) { // 更新界面显示 for (int i = 0; i < angles.size() && i < 6; i++) { ui->jointLabels[i]->setText(QString::number(angles[i], 'f', 2)); } }); connect(robotController, &RobotController::positionUpdated, this, [this](double x, double y, double z) { ui->positionLabel->setText(QString("位置: (%1, %2, %3)").arg(x, 0, 'f', 3).arg(y, 0, 'f', 3).arg(z, 0, 'f', 3)); }); connect(robotController, &RobotController::statusChanged, this, [this](const QString &status) { ui->statusBar->showMessage(status, 2000); }); // 启动控制线程 controlThread->start(); // 其他初始化代码... } // 在析构函数中清理 MainWindow::~MainWindow() { robotController->stopControl(); controlThread->quit(); controlThread->wait(); delete robotController; delete controlThread; delete ui; }5. 数据可视化与图表展示
好的数据可视化能让机器人状态一目了然。QT Charts模块提供了丰富的图表类型,用起来很方便。
5.1 实时数据曲线图
// 扩展MainWindow类,添加更多图表 void MainWindow::setupAllCharts() { // 1. 关节角度图表(前面已经实现) setupJointAngleChart(); // 2. 末端位置轨迹图表 setupTrajectoryChart(); // 3. 力传感器数据图表 setupForceChart(); // 4. 速度曲线图表 setupVelocityChart(); } void MainWindow::setupTrajectoryChart() { QChart *trajectoryChart = new QChart(); trajectoryChart->setTitle("末端执行器轨迹"); QScatterSeries *trajectorySeries = new QScatterSeries(); trajectorySeries->setName("轨迹点"); trajectorySeries->setMarkerSize(8); trajectorySeries->setColor(Qt::blue); // 添加一些示例点 for (int i = 0; i < 100; i++) { double angle = i * 2 * M_PI / 100; double x = 0.5 * cos(angle); double y = 0.5 * sin(angle); trajectorySeries->append(x, y); } trajectoryChart->addSeries(trajectorySeries); trajectoryChart->createDefaultAxes(); trajectoryChart->axisX()->setTitleText("X (m)"); trajectoryChart->axisY()->setTitleText("Y (m)"); QChartView *trajectoryView = new QChartView(trajectoryChart); trajectoryView->setRenderHint(QPainter::Antialiasing); ui->trajectoryTab->layout()->addWidget(trajectoryView); } void MainWindow::setupForceChart() { QChart *forceChart = new QChart(); forceChart->setTitle("力传感器数据"); QLineSeries *forceSeries[3]; QStringList forceNames = {"Fx", "Fy", "Fz"}; QList<QColor> forceColors = {Qt::red, Qt::green, Qt::blue}; for (int i = 0; i < 3; i++) { forceSeries[i] = new QLineSeries(); forceSeries[i]->setName(forceNames[i]); QPen pen; pen.setWidth(2); pen.setColor(forceColors[i]); forceSeries[i]->setPen(pen); // 模拟力数据 for (int j = 0; j < 50; j++) { forceSeries[i]->append(j, (qrand() % 100) / 10.0); } forceChart->addSeries(forceSeries[i]); } forceChart->createDefaultAxes(); forceChart->axisX()->setTitleText("时间 (×100ms)"); forceChart->axisY()->setTitleText("力 (N)"); QChartView *forceView = new QChartView(forceChart); forceView->setRenderHint(QPainter::Antialiasing); ui->forceTab->layout()->addWidget(forceView); }5.2 3D模型显示(使用QOpenGLWidget)
对于更高级的可视化,可以用QOpenGLWidget显示机器人的3D模型:
// Robot3DView.h #ifndef ROBOT3DVIEW_H #define ROBOT3DVIEW_H #include <QOpenGLWidget> #include <QOpenGLFunctions> #include <QOpenGLShaderProgram> #include <QOpenGLBuffer> #include <QMatrix4x4> #include <QVector3D> class Robot3DView : public QOpenGLWidget, protected QOpenGLFunctions { Q_OBJECT public: explicit Robot3DView(QWidget *parent = nullptr); ~Robot3DView(); void setJointAngles(const QVector<double> &angles); protected: void initializeGL() override; void resizeGL(int w, int h) override; void paintGL() override; void mousePressEvent(QMouseEvent *event) override; void mouseMoveEvent(QMouseEvent *event) override; void wheelEvent(QWheelEvent *event) override; private: QOpenGLShaderProgram *program; QOpenGLBuffer vbo; QMatrix4x4 projection; QMatrix4x4 view; QMatrix4x4 model; QPoint lastMousePos; float rotationX, rotationY; float zoomLevel; QVector<double> jointAngles; void drawRobot(); void drawLink(float length, float radius, const QVector3D &color); void drawJoint(float radius, const QVector3D &color); }; #endif // ROBOT3DVIEW_H6. 与Pi0模型集成
最后也是最重要的,如何把QT控制界面和Pi0具身智能模型结合起来。这里提供几种集成方式:
6.1 通过HTTP API通信
如果Pi0模型提供了HTTP接口,可以这样调用:
// Pi0ModelClient.h #ifndef PI0MODELCLIENT_H #define PI0MODELCLIENT_H #include <QObject> #include <QNetworkAccessManager> #include <QNetworkReply> #include <QJsonObject> #include <QJsonArray> class Pi0ModelClient : public QObject { Q_OBJECT public: explicit Pi0ModelClient(const QString &serverUrl, QObject *parent = nullptr); void predictAction(const QImage &image, const QString &instruction); void getRobotState(); void sendControlCommand(const QJsonObject &command); signals: void actionPredicted(const QJsonArray &actions); void stateReceived(const QJsonObject &state); void errorOccurred(const QString &error); private slots: void onReplyFinished(QNetworkReply *reply); private: QNetworkAccessManager *networkManager; QString baseUrl; QJsonObject createRequest(const QString &endpoint, const QJsonObject &data); }; #endif // PI0MODELCLIENT_H6.2 通过共享内存通信
对于需要高性能实时通信的场景,可以用共享内存:
// SharedMemoryBridge.h #ifndef SHAREDMEMORYBRIDGE_H #define SHAREDMEMORYBRIDGE_H #include <QObject> #include <QSharedMemory> #include <QSystemSemaphore> #include <QTimer> class SharedMemoryBridge : public QObject { Q_OBJECT public: explicit SharedMemoryBridge(const QString &key, QObject *parent = nullptr); ~SharedMemoryBridge(); bool writeData(const QByteArray &data); QByteArray readData(); bool isAttached() const; signals: void dataReady(); public slots: void startMonitoring(int intervalMs = 50); private slots: void checkForNewData(); private: QSharedMemory sharedMemory; QSystemSemaphore semaphore; QTimer *monitorTimer; QString memoryKey; bool lockForWrite(); bool unlockAfterWrite(); bool lockForRead(); bool unlockAfterRead(); }; #endif // SHAREDMEMORYBRIDGE_H6.3 完整的集成示例
// 在主窗口中使用Pi0客户端 void MainWindow::setupPi0Integration() { // 创建Pi0客户端 pi0Client = new Pi0ModelClient("http://localhost:8000", this); // 连接信号 connect(pi0Client, &Pi0ModelClient::actionPredicted, this, &MainWindow::onActionPredicted); connect(pi0Client, &Pi0ModelClient::stateReceived, this, &MainWindow::onStateReceived); // 定时获取状态 QTimer *stateTimer = new QTimer(this); connect(stateTimer, &QTimer::timeout, pi0Client, &Pi0ModelClient::getRobotState); stateTimer->start(100); // 100ms获取一次状态 // 连接控制按钮 connect(ui->predictButton, &QPushButton::clicked, this, [this]() { if (currentCameraImage.isNull()) { QMessageBox::warning(this, "警告", "请先获取摄像头图像"); return; } QString instruction = ui->instructionEdit->text(); if (instruction.isEmpty()) { instruction = "抓取前方的物体"; } pi0Client->predictAction(currentCameraImage, instruction); ui->statusBar->showMessage("正在预测动作...", 2000); }); } void MainWindow::onActionPredicted(const QJsonArray &actions) { // 解析Pi0模型预测的动作 QVector<double> jointAngles; for (const QJsonValue &value : actions) { if (value.isArray()) { QJsonArray actionArray = value.toArray(); for (const QJsonValue &angleValue : actionArray) { jointAngles.append(angleValue.toDouble()); } } } if (jointAngles.size() >= 6) { // 发送给机器人控制器执行 robotController->setJointAngles(jointAngles.mid(0, 6)); // 在界面上显示 ui->statusBar->showMessage("动作已执行", 2000); // 记录到日志 logAction(jointAngles); } } void MainWindow::onStateReceived(const QJsonObject &state) { // 更新界面显示 if (state.contains("joint_angles")) { QJsonArray angles = state["joint_angles"].toArray(); for (int i = 0; i < angles.size() && i < 6; i++) { jointSeries[i]->append(angles[i].toDouble()); } } if (state.contains("end_effector")) { QJsonObject ee = state["end_effector"].toObject(); double x = ee["x"].toDouble(); double y = ee["y"].toDouble(); double z = ee["z"].toDouble(); // 更新3D视图 if (robot3DView) { robot3DView->updatePosition(x, y, z); } } }7. 打包与部署
开发完成后,需要打包成可执行文件方便分发:
7.1 Windows平台打包
# 使用windeployqt工具自动收集依赖 cd build windeployqt --release RobotControlSoftware.exe # 创建安装包(使用Inno Setup或NSIS) # Inno Setup脚本示例: [Setup] AppName=Pi0机器人控制软件 AppVersion=1.0 DefaultDirName={pf}\RobotControl DefaultGroupName=机器人控制 OutputDir=installer OutputBaseFilename=RobotControlSetup [Files] Source: "build\*"; DestDir: "{app}"; Flags: ignoreversion recursesubdirs [Icons] Name: "{group}\机器人控制软件"; Filename: "{app}\RobotControlSoftware.exe"7.2 Linux平台打包
# 创建AppImage cd build linuxdeployqt RobotControlSoftware -appimage # 或者创建DEB包 mkdir -p robot-control-1.0/usr/bin mkdir -p robot-control-1.0/usr/share/applications mkdir -p robot-control-1.0/usr/share/icons/hicolor/256x256/apps cp RobotControlSoftware robot-control-1.0/usr/bin/ cp ../resources/icons/robot.png robot-control-1.0/usr/share/icons/hicolor/256x256/apps/ # 创建.desktop文件 cat > robot-control-1.0/usr/share/applications/robot-control.desktop << EOF [Desktop Entry] Type=Application Name=Pi0机器人控制软件 Comment=控制Pi0具身智能机器人的软件 Exec=/usr/bin/RobotControlSoftware Icon=robot Terminal=false Categories=Utility; EOF # 打包 dpkg-deb --build robot-control-1.07.3 macOS平台打包
# 创建.app bundle cd build macdeployqt RobotControlSoftware.app # 创建DMG安装包 hdiutil create -volname "Robot Control" -srcfolder RobotControlSoftware.app -ov -format UDZO RobotControl.dmg整体用下来,QT做机器人控制软件确实是个不错的选择。跨平台能力让开发和部署都很方便,丰富的UI组件和图表库能满足大部分可视化需求,多线程机制也能保证控制实时性。
当然实际项目中还会遇到各种具体问题,比如不同机器人硬件的接口差异、网络通信的稳定性、数据同步的时序问题等。但QT提供的工具和框架能帮你解决大部分基础问题,让你更专注于机器人控制逻辑本身。
如果你刚开始接触机器人软件开发,建议先从简单的界面和控制逻辑做起,慢慢添加复杂功能。QT的学习曲线相对平缓,社区资源丰富,遇到问题容易找到解决方案。最重要的是动手实践,多写代码,多调试,慢慢就能掌握这套工具了。
获取更多AI镜像
想探索更多AI镜像和应用场景?访问 CSDN星图镜像广场,提供丰富的预置镜像,覆盖大模型推理、图像生成、视频生成、模型微调等多个领域,支持一键部署。