新版布局

This commit is contained in:
xu
2026-10-16 09:06:03 +08:00
parent e5efdea06e
commit 1ec93e42cb
37 changed files with 1785 additions and 977 deletions
+132 -55
View File
@@ -9,8 +9,13 @@
#include <QHBoxLayout>
#include <QPushButton>
#include <QLabel>
#include <QDateTime>
#include "global.h"
// 球阀“开启/关闭故障”的判据:下发指令后这么久还等不到对应到位反馈就判故障。
// 取 8s,与④自检里球阀四项的等待时长(kValveWaitTicks 18 拍 × 450ms ≈ 8s)一致。
static const qint64 kValveFaultMs = 8000;
DebugMaintainPage::DebugMaintainPage(AppContext* ctx, QWidget* parent)
: BasePage(ctx, parent)
{
@@ -36,8 +41,42 @@ DebugMaintainPage::DebugMaintainPage(AppContext* ctx, QWidget* parent)
QVBoxLayout* leftLay = new QVBoxLayout(left);
leftLay->setContentsMargins(20, 10, 10, 10);
leftLay->setSpacing(60);
leftLay->addWidget(makeValveGroup(1));
leftLay->addWidget(makeValveGroup(2));
// 故障提示作为球阀盒子的**上一条**:常占位(无故障时清空文字、留出这一条的高度),
// 出现故障不会把下面的盒子顶下去
auto addValve = [&](int valve) {
QWidget* box = new QWidget(left);
QVBoxLayout* boxLay = new QVBoxLayout(box);
boxLay->setContentsMargins(0, 0, 0, 0);
boxLay->setSpacing(8);
// 不能走 label():那个辅助函数会把 color 写进行内样式,而控件自身的样式表优先级高于
// 应用级样式表,会把 QSS 里 [alert] / [blink] 的红色和明暗闪烁一起盖掉(只剩常亮红字)。
// 这里只给字号字重,颜色交给应用级样式表的告警规则。
QLabel* fault = new QLabel(box);
fault->setAlignment(Qt::AlignCenter);
fault->setProperty("alert", true);
fault->setStyleSheet("font-size:28px;font-weight:700;");
fault->setFixedHeight(40); // 常占位:把这一条的高度钉死
boxLay->addWidget(fault);
boxLay->addWidget(makeValveGroup(valve));
leftLay->addWidget(box);
return fault;
};
m_valveFault1 = addValve(1);
m_valveFault2 = addValve(2);
// 三个泵的上电指示灯放在 D2 球阀下方的空位里,纵向居中排列。
// 指示规则不变(上电=绿、否则灭),仍由 refresh() 按车端反馈刷新。
auto addPumpLed = [&](const QString& text) {
LedIndicator* led = new LedIndicator(text, left);
led->setTextPixelSize(40);
// 圆点在前(LedIndicator 的默认):竖排下来像一张状态清单
leftLay->addWidget(led, 0, Qt::AlignHCenter);
return led;
};
m_ledPlunger = addPumpLed("柱塞泵");
m_ledAir = addPumpLed("空压机");
m_ledLeaf = addPumpLed("叶片泵");
leftLay->addStretch(1);
left->setFixedWidth(300);
main->addWidget(left);
@@ -54,27 +93,33 @@ DebugMaintainPage::DebugMaintainPage(AppContext* ctx, QWidget* parent)
centerLeftLay->setSpacing(10);
m_chassisCard = new DataCard("底盘", centerLeft);
m_chassisCard->addField("电量", "85%");
m_chassisCard->addField("电压", "360V");
m_chassisCard->addField("底盘电量SOC", "--");
m_chassisCard->addField("底盘电压U", "--");
m_armCard = new DataCard("机械臂", centerLeft);
m_armCard->addField("末端位置", "XYZ");
m_armCard->addField("姿态", "RX/RY/RZ");
// 这一列只有 474px,位置行在 42px 下约 530px,单独把数值字号收到 34(约 463px)
m_armCard->setValueFontPx(34);
m_armCard->addField("末端位置", "--");
m_armCard->addField("末端姿态", "--");
m_gasCard = new DataCard("气体检测仪", centerLeft);
m_gasCard->addField("主机状态", "待机");
m_gasCard->addField("浓度", "0 ppb");
m_gasCard->addField("主机状态", "--");
m_gasCard->addField("有毒物种类", "--");
m_gasCard->addField("有毒物等级", "--");
centerLeftLay->addWidget(m_chassisCard, 1);
centerLeftLay->addWidget(m_armCard, 1);
centerLeftLay->addWidget(m_gasCard, 1);
centerLay->addWidget(centerLeft, 1);
// 与消除/消毒作业页的洗消卡同一套字段(本页只多了右侧的泵控制)
m_washCard = new DataCard("洗消参数", center);
m_washCard->addField("洗消模式", "--");
m_washCard->addField("A组分液位", "100L");
m_washCard->addField("A药剂流量", "0 L/min");
m_washCard->addField("管路压力", "0 MPa");
m_washCard->addField("发泡倍数", "0");
m_washCard->addField("空压机故障代码", "");
m_washCard->addField("柱塞泵故障代码", "");
m_washCard->addField("A组分液位", "--");
m_washCard->addField("B组分液位", "--");
m_washCard->addField("A药剂流量", "--");
m_washCard->addField("B药剂流量", "--");
m_washCard->addField("气体流量", "--");
m_washCard->addField("管路压力", "--");
m_washCard->addField("空压机压力", "--");
m_washCard->addField("发泡倍数", "--");
centerLay->addWidget(m_washCard, 1);
main->addWidget(center, 1);
@@ -101,8 +146,6 @@ DebugMaintainPage::DebugMaintainPage(AppContext* ctx, QWidget* parent)
bottom->setStyleSheet(QString("QWidget#DebugBottom{background-color:%1;border-top:1px solid %2;}")
.arg(C::PANEL, C::BORDER));
QHBoxLayout* bottomLay = new QHBoxLayout(bottom);
// 左起 364 = 36(页面边距) + 300(球阀列) + 28(列间距),与中央“底盘”列左边缘对齐
bottomLay->setContentsMargins(364, 0, 24, 0);
bottomLay->setSpacing(48);
auto makeLed = [&](const QString& text) {
LedIndicator* led = new LedIndicator(text, bottom);
@@ -111,10 +154,14 @@ DebugMaintainPage::DebugMaintainPage(AppContext* ctx, QWidget* parent)
bottomLay->addWidget(led);
return led;
};
m_ledPlunger = makeLed("柱塞泵");
m_ledAir = makeLed("空压机");
m_ledLeaf = makeLed("叶片泵");
m_ledLevel = makeLed("液位低");
// 三个泵的指示灯已经搬进各自的泵组里(放在按钮行左侧),底栏只留两路液位低
m_ledLevelA = makeLed("A组分/清水液位低");
m_ledLevelB = makeLed("B组分液位低");
// 这两盏灯居中于中央卡片区:卡片区 x ∈ [364, 1436](36 页边距 + 300 球阀列 + 28 列间距
// 起,宽 1072),中点 900。用 sizeHint 量出两盏灯连间距的实际宽度再定位,改字号也不会偏。
const int levelW = m_ledLevelA->sizeHint().width() + bottomLay->spacing()
+ m_ledLevelB->sizeHint().width();
bottomLay->setContentsMargins((364 + 1436) / 2 - levelW / 2, 0, 24, 0);
bottomLay->addStretch(1);
QPushButton* back = button("返回", "", bottom);
back->setFixedSize(160, 76);
@@ -159,7 +206,7 @@ QWidget* DebugMaintainPage::makeValveGroup(int valve)
QLabel* title = label(QString("D%1 球阀").arg(valve), 30, C::TEXT, true, grp);
lay->addWidget(title);
// 控制开/关(toggle):文字固定,底色表示状态——未选中=关闭(红)、选中=开启(绿)
// 控制开/关(toggle):文字固定,底色表示状态——未选中=关闭(灰)、选中=开启(绿)
QPushButton* ctrl = button(QString("D%1 控制").arg(valve), "", grp);
ctrl->setCheckable(true);
ctrl->setFixedSize(180, 64); // 窄按钮,与泵组的“上电”一致
@@ -167,16 +214,19 @@ QWidget* DebugMaintainPage::makeValveGroup(int valve)
ctrl->setStyleSheet(QString("QPushButton{background-color:%1;color:#FFFFFF;border:none;}"
"QPushButton:checked{background-color:%2;color:#FFFFFF;border:none;}"
"QPushButton:focus{border:4px solid #8ABFFF;}")
.arg(C::DANGER).arg(C::SUCCESS));
.arg(C::DISABLED).arg(C::SUCCESS));
connect(ctrl, &QPushButton::toggled, this, [this, valve](bool on) {
ctx()->comm()->sendBallValve(valve, on);
// 记下这一拍想要哪个阀位并开始等反馈;超时还没到位就在 refresh() 里判故障
if (valve == 1) { m_v1WantOpen = on ? 1 : 0; m_v1CmdMs = QDateTime::currentMSecsSinceEpoch(); m_v1Fault = false; }
else { m_v2WantOpen = on ? 1 : 0; m_v2CmdMs = QDateTime::currentMSecsSinceEpoch(); m_v2Fault = false; }
});
lay->addWidget(ctrl, 0, Qt::AlignHCenter);
// 开启到位 / 关闭到位(同一行,居中)。未收到车端遥测时保持缺省显示
// (开启绿 / 关闭红),首次收到遥测后由 refresh() 按实际到位状态刷新
// 开启到位 / 关闭到位(同一行,居中)。缺省状态按“阀体默认关着”给:
// 开启到位灭(灰)、关闭到位红。首次收到遥测后由 refresh() 按实际到位状态刷新
LedIndicator* open = new LedIndicator("开启", grp);
open->setState(LedIndicator::Green);
open->setState(LedIndicator::Off);
LedIndicator* close = new LedIndicator("关闭", grp);
close->setState(LedIndicator::Red);
QHBoxLayout* ioRow = new QHBoxLayout();
@@ -187,23 +237,16 @@ QWidget* DebugMaintainPage::makeValveGroup(int valve)
ioRow->addStretch(1);
lay->addLayout(ioRow);
// 故障提示
QLabel* fault = label("", 16, C::DANGER, true, grp);
fault->setAlignment(Qt::AlignCenter);
fault->setProperty("alert", true);
fault->hide();
lay->addWidget(fault);
// 故障提示不在盒子里——它是盒子**上方**独立的一条,由构造函数的 addValve 建好
if (valve == 1) {
m_valve1Btn = ctrl;
m_v1Open = open;
m_v1Close = close;
m_valveFault1 = fault;
} else {
m_valve2Btn = ctrl;
m_v2Open = open;
m_v2Close = close;
m_valveFault2 = fault;
}
return grp;
}
@@ -257,6 +300,7 @@ QWidget* DebugMaintainPage::makePumpGroup(const QString& pump, const QString& na
start->setChecked(false);
ctx()->comm()->sendPumpPower(pump, false);
});
// 圆点+名称不在这里了——三个泵的上电指示灯已经搬到左列 D2 球阀下方(见构造函数)
QHBoxLayout* pwrRow = new QHBoxLayout();
pwrRow->setSpacing(12);
pwrRow->addStretch(1);
@@ -265,9 +309,9 @@ QWidget* DebugMaintainPage::makePumpGroup(const QString& pump, const QString& na
pwrRow->addStretch(1);
lay->addLayout(pwrRow);
if (pump == "air") { m_airRpm = rpm; m_airStart = start; m_airStop = stop; }
if (pump == "air") { m_airRpm = rpm; m_airStart = start; m_airStop = stop; }
else if (pump == "plunger") { m_plungerRpm = rpm; m_plungerStart = start; m_plungerStop = stop; }
else { m_leafRpm = rpm; m_leafStart = start; m_leafStop = stop; }
else { m_leafRpm = rpm; m_leafStart = start; m_leafStop = stop; }
return grp;
}
@@ -275,22 +319,32 @@ void DebugMaintainPage::refresh()
{
const Telemetry& t = ctx()->telemetry;
m_chassisCard->setField(0, QString("%1%").arg(t.soc));
m_chassisCard->setField(1, QString("%1V").arg(t.volt, 0, 'f', 0));
// 未收到对应报文的数值统一显示 "--"(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_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));
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_washCard->setField(0, t.washMode);
m_washCard->setField(1, QString("%1L").arg(t.levelA, 0, 'f', 0));
m_washCard->setField(2, QString("%1 L/min").arg(t.flowA, 0, 'f', 1));
m_washCard->setField(3, QString("%1 MPa").arg(t.press, 0, 'f', 2));
m_washCard->setField(4, QString("%1").arg(t.foam, 0, 'f', 1));
m_washCard->setField(5, t.pumpFaults.value(0));
m_washCard->setField(6, t.pumpFaults.value(0));
m_washCard->setField(1, dashIf(t.hasWash, QString("%1L").arg(t.levelA, 0, 'f', 0)));
m_washCard->setField(2, dashIf(t.hasWash, QString("%1L").arg(t.levelFoam, 0, 'f', 0)));
m_washCard->setField(3, dashIf(t.hasWash, QString("%1 L/min").arg(t.flowA, 0, 'f', 1)));
m_washCard->setField(4, dashIf(t.hasWash, QString("%1 L/min").arg(t.flowB, 0, 'f', 1)));
m_washCard->setField(5, dashIf(t.hasWash, QString("%1 L/min").arg(t.gasFlow, 0, 'f', 1)));
m_washCard->setField(6, dashIf(t.hasWashPress, QString("%1 MPa").arg(t.press, 0, 'f', 2)));
m_washCard->setField(7, dashIf(t.hasWashPress, QString("%1 MPa").arg(t.airPress, 0, 'f', 2)));
m_washCard->setField(8, dashIf(t.hasWash, QString("%1").arg(t.foam, 0, 'f', 1)));
m_washCard->setAlarm(1, t.levelALow);
m_washCard->setAlarm(2, t.levelBLow);
m_washCard->setFaults(washCardFaults(t));
// 球阀到位反馈:未收到过车端遥测时不动,保留构造时的缺省显示(开启绿 / 关闭红)
// 球阀到位反馈:未收到过车端遥测时不动,保留构造时的缺省显示(开启灰 / 关闭红)
if (m_telemetrySeen) {
m_v1Open->setState(t.d1open ? LedIndicator::Green : LedIndicator::Off);
m_v1Close->setState(t.d1close ? LedIndicator::Red : LedIndicator::Off);
@@ -298,22 +352,45 @@ void DebugMaintainPage::refresh()
m_v2Close->setState(t.d2close ? LedIndicator::Red : LedIndicator::Off);
}
// 球阀故障提示
bool v1Fault = t.valveFault.contains("D1");
bool v2Fault = t.valveFault.contains("D2");
if (v1Fault) { m_valveFault1->setText(t.valveFault); m_valveFault1->show(); startBlink(m_valveFault1, "blink"); }
else { stopBlink(m_valveFault1, "blink"); m_valveFault1->hide(); }
if (v2Fault) { m_valveFault2->setText(t.valveFault); m_valveFault2->show(); startBlink(m_valveFault2, "blink"); }
else { stopBlink(m_valveFault2, "blink"); m_valveFault2->hide(); }
// 球阀故障:协议里没有“D1开启故障/关闭故障”这种信号,只能自己推——
// 指令下发后 kValveFaultMs 内 MBV_Dn_Sta 还没变成想要的样子,就判该方向故障;
// 反馈一旦对上(到位)就解除。cmdMs 为 0 表示没有待确认的指令,不判。
const qint64 now = QDateTime::currentMSecsSinceEpoch();
auto judgeValve = [&](qint64& cmdMs, int wantOpen, bool fbOpen, bool& faulted,
QLabel* lab, int valve) {
if (cmdMs != 0) {
if (fbOpen == (wantOpen != 0)) { // 到位:解除等待
cmdMs = 0;
faulted = false;
} else if (now - cmdMs > kValveFaultMs) {
faulted = true;
}
}
// 这一条是**常占位**的:只切文字与闪烁,不动显隐,免得盒子上下跳。
// "当前有没有报故障"就看文字空不空——省掉再存一个状态。
if (faulted != !lab->text().isEmpty())
return;
if (faulted) {
lab->setText(QString("D%1球阀 %2 故障").arg(valve).arg(wantOpen ? "开启" : "关闭"));
startBlink(lab, "blink");
} else {
stopBlink(lab, "blink");
lab->clear();
}
};
judgeValve(m_v1CmdMs, m_v1WantOpen, t.d1open, m_v1Fault, m_valveFault1, 1);
judgeValve(m_v2CmdMs, m_v2WantOpen, t.d2open, m_v2Fault, m_valveFault2, 2);
// 泵启停状态同步(互斥组内先置“启动”再置“停止”,两者互补,结果总是一选中一未选)
if (m_airStart) { m_airStart->setChecked(t.air); m_airStop->setChecked(!t.air); }
if (m_plungerStart){ m_plungerStart->setChecked(t.plunger); m_plungerStop->setChecked(!t.plunger); }
if (m_leafStart) { m_leafStart->setChecked(t.leaf); m_leafStop->setChecked(!t.leaf); }
// 底部指示灯
// 底部指示灯:泵的三个已经挪进泵组,这里只剩两路液位低。
// 用协议位(A_LiquidLevel_Low / B_LiquidLevel_Low),不按 levelA<10 猜阈值
m_ledPlunger->setState(t.plunger ? LedIndicator::Green : LedIndicator::Off);
m_ledAir->setState(t.air ? LedIndicator::Green : LedIndicator::Off);
m_ledLeaf->setState(t.leaf ? LedIndicator::Green : LedIndicator::Off);
m_ledLevel->setState(t.levelA < 10 ? LedIndicator::RedBlink : LedIndicator::Off);
m_ledLevelA->setState(t.levelALow ? LedIndicator::RedBlink : LedIndicator::Off);
m_ledLevelB->setState(t.levelBLow ? LedIndicator::RedBlink : LedIndicator::Off);
}