Files
QT_wuhan_YT/pages/arm.cpp
T
2026-10-16 09:06:03 +08:00

266 lines
11 KiB
C++
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#include "arm.h"
#include "titlebar.h"
#include "statusstrip.h"
#include "alarmbanner.h"
#include "datacard.h"
#include "car.h"
#include <QVBoxLayout>
#include <QHBoxLayout>
//#include <QGridLayout> // 末端按钮原为 2×3 网格,改为竖列后暂不需要
#include <QPushButton>
#include <QLabel>
#include <QTimer>
#include "global.h"
ArmPage::ArmPage(AppContext* ctx, QWidget* parent)
: BasePage(ctx, parent)
{
QVBoxLayout* root = new QVBoxLayout(this);
root->setContentsMargins(0, 0, 0, 0);
root->setSpacing(0);
m_titleBar = new TitleBar(ctx, this);
m_titleBar->setTitle("机械臂");
root->addWidget(m_titleBar);
m_banner = new AlarmBanner(this);
root->addWidget(m_banner);
QHBoxLayout* main = new QHBoxLayout();
main->setContentsMargins(36, 16, 36, 16);
main->setSpacing(32);
// 左控制列(6 运动按钮 + 系统回位/暂停)
QWidget* left = new QWidget(this);
QVBoxLayout* leftLay = new QVBoxLayout(left);
leftLay->setContentsMargins(10,10,20,10);
leftLay->setSpacing(30);
auto addMove = [&](const QString& t, const QString& axis, int dir) {
QPushButton* b = makeMoveButton(t, axis, dir);
m_moveButtons << b;
leftLay->addWidget(b);
};
addMove("左平移", "x", -1);
addMove("右平移", "x", +1);
addMove("上升", "z", +1);
addMove("下降", "z", -1);
addMove("前平移", "y", +1);
addMove("后平移", "y", -1);
m_returnBtn = button("系统回位/\n暂停", "", left);
m_returnBtn->setCheckable(true);
m_returnBtn->setFixedSize(270, 120); // 两行文字,故加高
m_returnBtn->setStyleSheet(QString(
"QPushButton{font-size:40px;background-color:%1;color:%2;border:1px solid %3;border-radius:12px;}"
"QPushButton:hover,QPushButton:focus,QPushButton:checked"
"{background-color:%4;color:#FFFFFF;border:2px solid #8ABFFF;}")
.arg(C::PANEL_HI, C::TEXT, C::BORDER, C::PRIMARY));
connect(m_returnBtn, &QPushButton::toggled, this, &ArmPage::onReturnToggled);
leftLay->addWidget(m_returnBtn);
m_returnStatus = label("", 18, C::TEXT_SUB, false, left);
m_returnStatus->setAlignment(Qt::AlignCenter);
m_returnStatus->setWordWrap(true);
leftLay->addWidget(m_returnStatus);
leftLay->addStretch(1);
left->setFixedWidth(300); // 270 按钮 + 左右边距 30,避免按钮被裁
main->addWidget(left);
// 中央区:左侧三张状态卡 + 机械臂参数卡,并排放在中间
QWidget* center = new QWidget(this);
QHBoxLayout* centerLay = new QHBoxLayout(center);
centerLay->setContentsMargins(0, 0, 0, 0);
centerLay->setSpacing(12);
QWidget* cardsCol = new QWidget(center);
QVBoxLayout* cardsLay = new QVBoxLayout(cardsCol);
cardsLay->setContentsMargins(0, 0, 0, 0);
cardsLay->setSpacing(12);
m_chassisCard = new DataCard("底盘", cardsCol);
m_chassisCard->addField("底盘电量SOC", "--");
m_chassisCard->addField("底盘电压", "--");
m_washCard = new DataCard("洗消", cardsCol);
m_washCard->addField("A组分/清水液位", "--");
m_washCard->addField("泡沫箱液位", "--");
m_gasCard = new DataCard("气体检测仪", cardsCol);
m_gasCard->addField("主机状态", "--");
m_gasCard->addField("有毒物种类", "--");
m_gasCard->addField("有毒物等级", "--");
cardsLay->addWidget(m_chassisCard, 1);
cardsLay->addWidget(m_washCard, 1);
cardsLay->addWidget(m_gasCard, 1);
m_armCard = new DataCard("机械臂参数", center);
m_armCard->addField("末端位置", "--");
m_armCard->addField("末端姿态", "--");
m_armCard->addField("末端速度", "--");
m_armCard->addField("Joint1关节角", "--");
m_armCard->addField("Joint2关节角", "--");
m_armCard->addField("Joint3关节角", "--");
m_armCard->addField("Joint4关节角", "--");
m_armCard->addField("Joint5关节角", "--");
m_armCard->addField("Joint6关节角", "--");
centerLay->addWidget(cardsCol, 1);
centerLay->addWidget(m_armCard, 1);
main->addWidget(center, 1);
// 右控制列(末端 6 姿态按钮,与左侧按钮列对称)
QWidget* right = new QWidget(this);
QVBoxLayout* rightLay = new QVBoxLayout(right);
rightLay->setContentsMargins(10, 20, 20, 20);
rightLay->setSpacing(15);
auto addEnd = [&](const QString& t, const QString& axis, int dir) {
QPushButton* b = makeMoveButton(t, axis, dir);
m_endButtons << b;
rightLay->addWidget(b);
};
addEnd("末端仰", "pitch", +1);
addEnd("末端俯", "pitch", -1);
addEnd("末端正偏航", "yaw", +1);
addEnd("末端负偏航", "yaw", -1);
addEnd("末端左横滚", "roll", -1);
addEnd("末端右横滚", "roll", +1);
right->setFixedWidth(300); // 与左列同宽,按钮 270 不被裁
main->addWidget(right);
root->addLayout(main, 1);
m_statusStrip = new StatusStrip(ctx, this);
connect(m_statusStrip, &StatusStrip::backClicked, this, [this]() { emit navigate(PageJob); });
root->addWidget(m_statusStrip);
m_statusTimer = new QTimer(this);
m_statusTimer->setSingleShot(true);
m_statusTimer->setInterval(3000);
connect(m_statusTimer, &QTimer::timeout, this, [this]() {
m_returnStatus->setText("");
});
connect(ctx->comm(), &CarCommunication::telemetryUpdated, this, &ArmPage::refresh);
connect(ctx, &AppContext::modeChanged, this, [this](Mode) { updateModeGating(); });
connect(ctx, &AppContext::estopChanged, this, [this](bool) { updateModeGating(); });
connect(ctx, &AppContext::rcDrivingChanged, this, [this](bool) {
updateModeGating();
// 同底盘页:让位给遥控器期间按下的轴被忽略,遥控器松手后把按着的键重发一遍。
// 注意写 this->ctx():构造函数形参也叫 ctx,直接写 ctx() 会被它遮住
if (this->ctx()->rcDriving()) return;
for (QPushButton* b : m_moveButtons)
if (b->isDown())
this->ctx()->comm()->sendArmMove(b->property("armAxis").toString(),
b->property("armDir").toInt());
});
updateModeGating();
refresh();
}
void ArmPage::showEvent(QShowEvent* ev)
{
BasePage::showEvent(ev);
// 每次进入机械臂页自动切到机械臂模式:Arm_ControlCmd1 的 Arm_modeCmd/Arm_enable
// 挂在它上面,不切的话车端会忽略这里发出的动作指令。遥控器在线时模式由遥控器报文
// 决定,不抢——和底盘页进页面切行车模式是同一套处理。
if (!ctx()->rc()->linkOk())
ctx()->setMode(Mode::Arm);
}
QPushButton* ArmPage::makeMoveButton(const QString& text, const QString& axis, int dir)
{
QPushButton* b = button(text, "", this);
b->setFixedSize(270, 76);
// 记下这根按钮管哪个轴/往哪边,好让"遥控器松手后重发按住的键"找得到它
b->setProperty("armAxis", axis);
b->setProperty("armDir", dir);
b->setStyleSheet(QString(
"QPushButton{font-size:45px;background-color:%1;color:%2;border:1px solid %3;border-radius:12px;}"
"QPushButton:hover,QPushButton:focus,QPushButton:pressed"
"{background-color:%4;color:#FFFFFF;border:2px solid #8ABFFF;}")
.arg(C::PANEL_HI, C::TEXT, C::BORDER, C::PRIMARY));
connect(b, &QPushButton::pressed, this, [this, axis, dir]() {
ctx()->comm()->sendArmMove(axis, dir);
});
connect(b, &QPushButton::released, this, [this, axis]() {
ctx()->comm()->sendArmMove(axis, 0);
});
return b;
}
void ArmPage::updateModeGating()
{
// 和底盘页一样:现在只有**急停**会禁用动作按钮。模式不再限制(进页面即用),
// 遥控器也不再让位——面板和遥控器可以同时操作,两边各发各的。
const bool ok = !ctx()->estopTriggered;
for (QPushButton* b : m_moveButtons) b->setEnabled(ok);
for (QPushButton* b : m_endButtons) b->setEnabled(ok);
}
void ArmPage::onReturnToggled(bool on)
{
if (on) ctx()->comm()->sendArmReturn();
else ctx()->comm()->sendArmPause();
}
void ArmPage::refresh()
{
const Telemetry& t = ctx()->telemetry;
if (!t.commOk) {
m_banner->showAlarm("通信异常");
} else {
m_banner->clear();
}
// 未收到对应报文的数值统一显示 "--"(dashIf)
m_chassisCard->setField(0, dashIf(t.hasBattery, QString("%1%").arg(t.soc)));
m_chassisCard->setField(1, dashIf(t.hasBattery, QString("%1V").arg(t.volt, 0, 'f', 0)));
m_chassisCard->setFaults(t.chassisFaults);
m_washCard->setField(0, dashIf(t.hasWash, QString("%1L").arg(t.levelA, 0, 'f', 0)));
m_washCard->setField(1, dashIf(t.hasWash, QString("%1L").arg(t.levelFoam, 0, 'f', 0)));
m_washCard->setAlarm(0, t.levelALow);
m_washCard->setAlarm(1, t.levelBLow);
m_washCard->setFaults(washCardFaults(t));
m_gasCard->setField(0, gasHostLabel(t));
m_gasCard->setField(1, t.gasType); // 有毒物种类,未收到报文时本身就是 "--"
m_gasCard->setField(2, t.gasLevel); // 有毒物等级
m_gasCard->setAlarm(0, t.gasHost == "fault");
m_gasCard->setFaults(t.detectorFaults);
// 本卡没有故障行:机械臂那四帧只有位置/姿态/关节角,协议里没有机械臂故障信号
m_armCard->setField(0, t.pos);
m_armCard->setField(1, t.pose);
m_armCard->setField(2, dashIf(t.hasArmPose, QString("%1").arg(t.vel, 0, 'f', 1)));
for (int i = 0; i < 6 && i < t.js.size(); ++i)
m_armCard->setField(3 + i, dashIf(t.hasJoints, QString("%1°").arg(t.js[i], 0, 'f', 0)));
// 回位状态机
if (t.returnState != m_prevReturnState) {
m_prevReturnState = t.returnState;
if (t.returnState == "done") {
m_returnBtn->setChecked(false);
m_returnStatus->setText("机械臂系统完成回位");
m_returnStatus->setStyleSheet(QString("color:%1;font-size:22px;font-weight:700;").arg(C::SUCCESS));
m_statusTimer->start();
} else if (t.returnState == "fault") {
m_returnBtn->setChecked(false);
m_returnStatus->setText("机械臂系统回位异常");
m_returnStatus->setStyleSheet(QString("color:%1;font-size:22px;font-weight:700;").arg(C::DANGER));
} else if (t.returnState == "returning") {
m_returnStatus->setText("回位中…");
m_returnStatus->setStyleSheet(QString("color:%1;font-size:22px;font-weight:700;").arg(C::WARNING));
} else {
m_returnStatus->setText("");
}
}
}