Files
QT_wuhan_YT/pages/chassis.cpp
T
2026-10-14 09:59:42 +08:00

249 lines
9.6 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 "chassis.h"
#include "titlebar.h"
#include "statusstrip.h"
#include "alarmbanner.h"
#include "datacard.h"
#include "paramcontrol.h"
#include "car.h"
#include "confirmdialog.h"
#include <QVBoxLayout>
#include <QHBoxLayout>
#include <QPushButton>
#include <QTimer>
#include "global.h"
ChassisPage::ChassisPage(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(0); // 各段间距改用显式 addSpacing,便于单独调整(底盘列位置见各段注释)
// 左控制列(垂直居中 + 平均分布)
QWidget* left = new QWidget(this);
QVBoxLayout* leftLay = new QVBoxLayout(left);
leftLay->setContentsMargins(20, 20, 0, 0);
leftLay->setSpacing(30);
QPushButton* fwd = makeDirButton("前进", "fwd");
QPushButton* back = makeDirButton("后退", "back");
QPushButton* leftT = makeDirButton("左转向", "left");
QPushButton* rightT = makeDirButton("右转向", "right");
m_dirButtons << fwd << back << leftT << rightT;
leftLay->addStretch(1);
leftLay->addWidget(fwd);
leftLay->addStretch(1);
leftLay->addWidget(back);
leftLay->addStretch(1);
leftLay->addWidget(leftT);
leftLay->addStretch(1);
leftLay->addWidget(rightT);
leftLay->addStretch(1);
left->setFixedWidth(180);
main->addWidget(left);
main->addSpacing(32);
// 中央卡片区(洗消/机械臂/气体检测仪)
QWidget* center = new QWidget(this);
QVBoxLayout* centerLay = new QVBoxLayout(center);
centerLay->setContentsMargins(0, 0, 0, 0);
centerLay->setSpacing(12);
m_washCard = new DataCard("洗消", center);
m_washCard->addField("A液位", "100L");
m_washCard->addField("泡沫液位", "30L");
m_armCard = new DataCard("机械臂", center);
m_armCard->addField("末端位置", "XYZ");
m_armCard->addField("姿态", "RX/RY/RZ");
m_gasCard = new DataCard("气体检测仪", center);
m_gasCard->addField("主机状态", "待机");
m_gasCard->addField("浓度", "0 ppb");
centerLay->addWidget(m_washCard, 1);
centerLay->addWidget(m_armCard, 1);
centerLay->addWidget(m_gasCard, 1);
// 中间区域由「卡片列」与「底盘状态列」平均分配(两列拉伸因子都为 1)
main->addWidget(center, 1);
main->addSpacing(47); // 中间卡片列 ↔ 底盘列:由 32 加大到 47
// 底盘状态列
QWidget* chassisCol = new QWidget(this);
QVBoxLayout* chassisLay = new QVBoxLayout(chassisCol);
chassisLay->setContentsMargins(0, 0, 0, 0);
chassisLay->setSpacing(12);
m_chassisCard = new DataCard("底盘状态", chassisCol);
m_chassisCard->addField("电量SOC", "85%");
m_chassisCard->addField("电压U", "360V");
m_chassisCard->addField("车速V", "3.45 km/h");
m_chassisCard->addField("手刹", "有效");
m_chassisFieldFault = m_chassisCard->addField("故障", "无");
chassisLay->addWidget(m_chassisCard, 1);
main->addWidget(chassisCol, 1); // 与中间卡片列平均分配中间区域
main->addSpacing(17); // 底盘列 ↔ 车速设置列:由 32 减小到 17(底盘列整体右移 15px)
// 车速设置列(垂直居中)
QWidget* right = new QWidget(this);
QVBoxLayout* rightLay = new QVBoxLayout(right);
rightLay->setContentsMargins(0, 0, 0, 0);
rightLay->setSpacing(12);
double speed = ctx->getDouble(ctx->keySpeed(), 4.5);
// 协议 Vehi_TargetSpd:精度 0.01、0~500 → 物理范围 0~5.00 km/h
// 每档 = 显示精度的最小单位(1 位小数 → 0.1),比协议的 0.01 粗,够用
m_speed = new ParamControl("车速设置", "km/h", 0.1, 0.0, 5.0, false, false, right);
m_speed->setValue(speed);
m_speed->setStyleSheet("ParamControl{background:transparent;}"); // 只作用于本控件,避免影响子控件
connect(m_speed, &ParamControl::valueChanged, this, &ChassisPage::onSpeedChanged);
rightLay->addStretch(1);
rightLay->addWidget(m_speed);
rightLay->addStretch(1);
right->setFixedWidth(380);
main->addWidget(right);
root->addLayout(main, 1);
m_statusStrip = new StatusStrip(ctx, this);
m_statusStrip->setModeVisible(false); // 本页固定行车模式,底部不显示模式徽标
connect(m_statusStrip, &StatusStrip::backClicked, this, [this]() { emit navigate(PageJob); });
root->addWidget(m_statusStrip);
// 车速自动保存(1s 防抖)
m_saveTimer = new QTimer(this);
m_saveTimer->setSingleShot(true);
m_saveTimer->setInterval(1000);
connect(m_saveTimer, &QTimer::timeout, this, &ChassisPage::onSpeedSaved);
connect(ctx->comm(), &CarCommunication::telemetryUpdated, this, &ChassisPage::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(); });
updateModeGating();
refresh();
}
void ChassisPage::showEvent(QShowEvent* ev)
{
BasePage::showEvent(ev);
// 每次进入底盘页自动切换到行车模式;遥控器在线时模式由遥控器报文决定,不抢
if (!ctx()->rc()->linkOk())
ctx()->setMode(Mode::Drive);
}
QPushButton* ChassisPage::makeDirButton(const QString& text, const QString& dir)
{
QPushButton* b = button(text, "", this);
b->setFixedSize(140, 100);
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, dir]() {
if (ctx()->rcDriving()) {
ConfirmDialog::toast(this, "遥控器操作中");
return;
}
if (ctx()->mode != Mode::Drive) {
ConfirmDialog::toast(this, "请切换至行车模式");
return;
}
m_heldDirs.insert(dir);
sendDirCommand();
});
connect(b, &QPushButton::released, this, [this, dir]() {
m_heldDirs.remove(dir);
sendDirCommand();
});
return b;
}
// 同时按住前进/后退与左/右转向时一并下发;两者都松开才停车
void ChassisPage::sendDirCommand()
{
const QString dir = m_heldDirs.contains("fwd") ? "fwd"
: m_heldDirs.contains("back") ? "back" : QString("stop");
const QString turn = m_heldDirs.contains("right") ? "right"
: m_heldDirs.contains("left") ? "left" : QString();
if (dir == "stop" && turn.isEmpty()) {
ctx()->comm()->sendChassisMove("stop", 0);
return;
}
// 仅转向(档位回中)时发 stop + turn,由车载端决定是否原地转向
const double spd = (dir == "stop") ? 0.0 : ctx()->getDouble(ctx()->keySpeed(), 4.5);
ctx()->comm()->sendChassisMove(dir, spd, turn);
}
void ChassisPage::updateModeGating()
{
// 非行车模式、急停触发或遥控器正在操作时禁用方向按钮(遥控器优先)
const bool drive = (ctx()->mode == Mode::Drive) && !ctx()->estopTriggered && !ctx()->rcDriving();
for (QPushButton* b : m_dirButtons)
b->setEnabled(drive);
// 让位时清掉本地按住状态:按钮被禁用后不会再收到 released,否则方向会被一直挂住。
// 让位给遥控器时不补发 stop,否则会覆盖遥控器刚下发的那条指令。
if (!drive && !m_heldDirs.isEmpty()) {
m_heldDirs.clear();
if (!ctx()->rcDriving())
ctx()->comm()->sendChassisMove("stop", 0);
}
}
void ChassisPage::refresh()
{
const Telemetry& t = ctx()->telemetry;
// 机械臂未回位 / 通信异常告警
if (!t.commOk) {
m_banner->showAlarm("通信异常");
} else if (!t.armHomed && t.returnState != "idle") {
m_banner->showAlarm("机械臂未回位,请谨慎操作");
} else {
m_banner->clear();
}
m_chassisCard->setField(0, QString("%1%").arg(t.soc));
m_chassisCard->setField(1, QString("%1V").arg(t.volt, 0, 'f', 0));
m_chassisCard->setField(2, QString("%1 km/h").arg(t.speed, 0, 'f', 2));
m_chassisCard->setField(3, t.handbrake ? "有效" : "无效");
m_chassisCard->setField(m_chassisFieldFault, t.chassisFaults.isEmpty() ? "无" : t.chassisFaults.join(";"));
m_chassisCard->setAlarm(m_chassisFieldFault, !t.chassisFaults.isEmpty());
m_washCard->setField(0, QString("%1L").arg(t.levelA, 0, 'f', 0));
m_washCard->setField(1, QString("%1L").arg(t.levelFoam, 0, 'f', 0));
m_armCard->setField(0, t.pos);
m_armCard->setField(1, t.pose);
m_gasCard->setField(0, t.gasHost == "detecting" ? "检测中" : (t.gasHost == "fault" ? "故障" : "待机"));
m_gasCard->setField(1, QString("%1 ppb").arg(t.gasConc, 0, 'f', 0));
}
void ChassisPage::onSpeedChanged(double v)
{
// 1s 防抖后自动保存
m_saveTimer->start();
ctx()->comm()->sendSetParam(ctx()->keySpeed(), v);
}
void ChassisPage::onSpeedSaved()
{
ctx()->setDouble(ctx()->keySpeed(), m_speed->value());
ConfirmDialog::toast(this, "车速已保存");
}