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

373 lines
16 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 "washpage.h"
#include "titlebar.h"
#include "alarmbanner.h"
#include "datacard.h"
#include "ledindicator.h"
#include "paramcontrol.h"
#include "car.h"
#include "confirmdialog.h"
#include <QVBoxLayout>
#include <QHBoxLayout>
#include <QPushButton>
#include <QLabel>
#include "global.h"
WashPage::WashPage(AppContext* ctx, Mode mode, QWidget* parent)
: BasePage(ctx, parent), m_mode(mode)
{
const bool clean = (mode == Clean);
QVBoxLayout* root = new QVBoxLayout(this);
root->setContentsMargins(0, 0, 0, 0);
root->setSpacing(0);
m_titleBar = new TitleBar(ctx, this);
m_titleBar->setTitle(clean ? "消除作业" : "消毒作业");
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(28);
// 左侧状态卡(底盘/机械臂/气体检测仪)
QWidget* left = new QWidget(this);
QVBoxLayout* leftLay = new QVBoxLayout(left);
leftLay->setContentsMargins(0, 0, 0, 0);
leftLay->setSpacing(10);
m_chassisCard = new DataCard("底盘", left);
m_chassisCard->addField("底盘电量SOC", "--");
m_chassisCard->addField("底盘电压U", "--");
m_armCard = new DataCard("机械臂", left);
m_armCard->addField("末端位置", "--");
m_armCard->addField("末端姿态", "--");
m_gasCard = new DataCard("气体检测仪", left);
m_gasCard->addField("主机状态", "--");
m_gasCard->addField("有毒物种类", "--");
m_gasCard->addField("有毒物等级", "--");
leftLay->addWidget(m_chassisCard, 1);
leftLay->addWidget(m_armCard, 1);
leftLay->addWidget(m_gasCard, 1);
left->setFixedWidth(600); // 需容纳最长的“末端位置:X:1200 Y:300 Z:800”(42px 字号约 523px)
main->addSpacing(60); // 左列整体右移的量;空白由中间拉伸列让出,卡片宽度不变
main->addWidget(left);
// 中央洗消卡
m_washCard = new DataCard("洗消", this);
m_fMode = m_washCard->addField("洗消模式", "--");
m_fLevelA = m_washCard->addField("A组分液位", "--");
m_fLevelFoam = m_washCard->addField("B组分液位", "--");
m_fFlowA = m_washCard->addField("A药剂流量", "--");
m_fFlowB = m_washCard->addField("B药剂流量", "--");
m_fGasFlow = m_washCard->addField("气体流量", "--");
m_fPress = m_washCard->addField("管路压力", "--");
m_fAirPress = m_washCard->addField("空压机压力", "--");
m_fFoam = m_washCard->addField("发泡倍数", "--");
main->addSpacing(60);
main->addWidget(m_washCard, 1);
// 右侧操作列
QWidget* right = new QWidget(this);
QVBoxLayout* rightLay = new QVBoxLayout(right);
rightLay->setContentsMargins(20, 20, 20, 10);
rightLay->setSpacing(60);
m_start = button("启动", "primary", right);
m_start->setFixedSize(180, 100);
connect(m_start, &QPushButton::clicked, this, &WashPage::onStart);
m_stop = button("停止", "danger", right);
m_stop->setFixedSize(180, 100);
connect(m_stop, &QPushButton::clicked, this, &WashPage::onStop);
rightLay->addWidget(m_start, 0, Qt::AlignHCenter);
rightLay->addWidget(m_stop, 0, Qt::AlignHCenter);
// 参数区:按模式不同
m_paramsLayout = new QVBoxLayout();
m_paramsLayout->setSpacing(8);
rightLay->addLayout(m_paramsLayout);
rightLay->addStretch(1);
if (clean) {
// 柱塞泵流量(出厂 20;协议 PlungerPump_flow 0-24 L/min)
m_pumpFlow = new ParamControl("柱塞泵流量", "L/min", 0.1, 0.0, 24.0, true, false, this);
m_pumpFlow->setValue(ctx->getDouble(ctx->keyPumpFlow(), 20));
connect(m_pumpFlow, &ParamControl::saveClicked, this, [this]() {
this->ctx()->setDouble(this->ctx()->keyPumpFlow(), m_pumpFlow->value());
this->ctx()->comm()->sendSetParam(this->ctx()->keyPumpFlow(), m_pumpFlow->value());
refreshPumpValues(); // 底部数值跟着更新
ConfirmDialog::toast(this, "已保存");
});
m_paramsLayout->addWidget(m_pumpFlow);
} else {
// 发泡倍数(出厂 6;协议 Expansion_ratioCmd 6-12 倍)
m_foamRatio = new ParamControl("发泡倍数", "倍", 0.1, 6.0, 12.0, true, false, this);
m_foamRatio->setValue(ctx->getDouble(ctx->keyFoamRatio(), 6));
connect(m_foamRatio, &ParamControl::saveClicked, this, [this]() {
this->ctx()->setDouble(this->ctx()->keyFoamRatio(), m_foamRatio->value());
this->ctx()->comm()->sendSetParam(this->ctx()->keyFoamRatio(), m_foamRatio->value());
ConfirmDialog::toast(this, "已保存");
});
m_paramsLayout->addWidget(m_foamRatio);
// 泡沫比例(出厂 3%;协议 Foam_ratio 0-10%)
m_foamPercent = new ParamControl("泡沫比例", "%", 0.1, 0.0, 10.0, true, false, this);
m_foamPercent->setValue(ctx->getDouble(ctx->keyFoamPercent(), 3));
connect(m_foamPercent, &ParamControl::saveClicked, this, [this]() {
this->ctx()->setDouble(this->ctx()->keyFoamPercent(), m_foamPercent->value());
this->ctx()->comm()->sendSetParam(this->ctx()->keyFoamPercent(), m_foamPercent->value());
ConfirmDialog::toast(this, "已保存");
});
m_paramsLayout->addWidget(m_foamPercent);
// 空压机流量(出厂 100;协议 AirComp_flow 0-340 L/min)
m_airFlow = new ParamControl("空压机流量", "L/min", 1, 0.0, 340.0, true, false, this);
m_airFlow->setValue(ctx->getDouble(ctx->keyAirFlow(), 100));
connect(m_airFlow, &ParamControl::saveClicked, this, [this]() {
this->ctx()->setDouble(this->ctx()->keyAirFlow(), m_airFlow->value());
this->ctx()->comm()->sendSetParam(this->ctx()->keyAirFlow(), m_airFlow->value());
refreshPumpValues(); // 底部数值跟着更新
ConfirmDialog::toast(this, "已保存");
});
m_paramsLayout->addWidget(m_airFlow);
}
right->setFixedWidth(500); // 参数行需容纳 输入框150 + 单位约101 + 保存140 + 两个间距24 ≈ 415px
main->addSpacing(60); // 洗消卡 ↔ 右列的间隙;空白由中间拉伸列让出,卡片文字不受影响
main->addWidget(right);
root->addLayout(main, 1);
// 底部条:左右两个按钮纵向占满,中间三排(泵数值 / 指示灯 / 两路液位低)居中。
// 高度由 160 加到 188:中间那列三排约 164px 再加 8+8 的上下边距,160 装不下第三排
QWidget* bottom = new QWidget(this);
bottom->setObjectName("WashBottom");
bottom->setFixedHeight(188);
// 用 #WashBottom 限定选择器:不带选择器的样式表会连子控件一起套上边框和底色
bottom->setStyleSheet(QString("QWidget#WashBottom{background-color:%1;border-top:1px solid %2;}")
.arg(C::PANEL, C::BORDER));
QHBoxLayout* bottomRoot = new QHBoxLayout(bottom);
bottomRoot->setContentsMargins(20, 8, 20, 8);
bottomRoot->setSpacing(12);
// 左:恢复出厂值(占满两行高度)
m_factory = button("恢复出厂值", "", bottom);
m_factory->setFixedSize(220, 100);
m_factory->setStyleSheet(cancelButtonStyle(40)); // 选中(悬停/按下/焦点)红底白字
connect(m_factory, &QPushButton::clicked, this, &WashPage::onFactory);
bottomRoot->addWidget(m_factory, 0, Qt::AlignVCenter);
// 中:上排三泵数值、中排指示灯、下排两路液位低,三排都水平居中
QVBoxLayout* midLay = new QVBoxLayout();
midLay->setSpacing(6);
QHBoxLayout* valueLay = new QHBoxLayout();
valueLay->setSpacing(40);
valueLay->addStretch(1);
auto addFlow = [&](const QString& name) {
valueLay->addWidget(label(name + ":", 40, C::TEXT_SUB, false, bottom));
QLabel* v = label("--", 40, C::TEXT, true, bottom);
valueLay->addWidget(v);
return v;
};
m_valPlunger = addFlow("柱塞泵");
m_valAir = addFlow("空压机");
m_valLeaf = addFlow("叶片泵");
refreshPumpValues(); // 开机先显示参数的出厂默认值
valueLay->addStretch(1);
QHBoxLayout* ledLay = new QHBoxLayout();
ledLay->setSpacing(10);
ledLay->addStretch(1);
// 灯要分两行放,故把目标行当参数传进来
auto makeLed = [&](QHBoxLayout* row, const QString& text) {
LedIndicator* led = new LedIndicator(text, bottom);
led->setTextPixelSize(40); // 字号与卡片标题一致
row->addWidget(led);
return led;
};
m_ledRun = makeLed(ledLay, "启停");
m_ledPlunger = makeLed(ledLay, "柱塞泵");
m_ledAir = makeLed(ledLay, "空压机");
m_ledLeaf = makeLed(ledLay, "叶片泵");
m_ledD1 = makeLed(ledLay, "D1");
m_ledD2 = makeLed(ledLay, "D2");
ledLay->addStretch(1);
// 下一行:液位低拆成 A / B 两路。原来那颗笼统的「液位低」和「系统故障」都撤了——
// 后者在洗消卡片的故障行里已经有了,底部再挂一颗是重复的
QHBoxLayout* levelLay = new QHBoxLayout();
levelLay->setSpacing(40);
levelLay->addStretch(1);
m_ledLevelA = makeLed(levelLay, "A组分/清水液位低");
m_ledLevelB = makeLed(levelLay, "B组分液位低");
levelLay->addStretch(1);
midLay->addStretch(1);
midLay->addLayout(valueLay);
midLay->addLayout(ledLay);
midLay->addLayout(levelLay);
midLay->addStretch(1);
bottomRoot->addLayout(midLay, 1);
// 右:返回(占满两行高度)
m_back = button("返回", "", bottom);
m_back->setFixedSize(170, 100);
m_back->setStyleSheet(cancelButtonStyle(40)); // 选中(悬停/按下/焦点)红底白字
connect(m_back, &QPushButton::clicked, this, &WashPage::onBack);
bottomRoot->addWidget(m_back, 0, Qt::AlignVCenter);
root->addWidget(bottom);
connect(ctx->comm(), &CarCommunication::telemetryUpdated, this, &WashPage::refresh);
updateGating();
refresh();
}
QString WashPage::washModeTag() const
{
return m_mode == Clean ? "clean" : "disinfect";
}
// 底部三个泵的数值:柱塞泵/空压机跟右侧参数的设定值(没改过就是出厂默认值),
// 叶片泵本页没有对应参数,先取遥测的实时流量
void WashPage::refreshPumpValues()
{
// 这里读的是配置值,一并按协议量程夹取(柱塞泵 0-24、空压机 0-340 L/min),
// 否则旧配置里超范围的历史值会与控件显示、实际上报的数值对不上
m_valPlunger->setText(QString("%1 L/min").arg(qBound(0.0, ctx()->getDouble(ctx()->keyPumpFlow(), 20), 24.0), 0, 'f', 1));
m_valAir->setText(QString("%1 L/min").arg(qBound(0.0, ctx()->getDouble(ctx()->keyAirFlow(), 100), 340.0), 0, 'f', 1));
m_valLeaf->setText(dashIf(ctx()->telemetry.hasWash, QString("%1 L/min").arg(ctx()->telemetry.flowB, 0, 'f', 1)));
}
void WashPage::onStart()
{
ctx()->comm()->sendWashStart(washModeTag());
// 同时把机器强制到机械臂模式(见 AppContext::setWashRunning),否则 Arm_enable=0,
// 末端那条俯仰摆动指令车端不会理
updateGating();
// 启停灯立刻跟着变,不等下一帧遥测——否则车端不回话时按钮看着像没反应
updateLeds();
}
void WashPage::onStop()
{
ctx()->comm()->sendWashStop();
ctx()->setWashRunning(false);
updateGating();
updateLeds();
}
void WashPage::onBack()
{
if (ctx()->isWashRunning()) {
ConfirmDialog::toast(this, "请先停止");
return;
}
emit navigate(PageJob);
}
void WashPage::onFactory()
{
if (ConfirmDialog::confirm(this, "恢复出厂值", "确定将参数恢复为出厂值?"))
resetFactory();
}
void WashPage::resetFactory()
{
if (m_mode == Clean) {
m_pumpFlow->setValue(20); // 出厂 20
ctx()->setDouble(ctx()->keyPumpFlow(), 20);
ctx()->comm()->sendSetParam(ctx()->keyPumpFlow(), 20);
} else {
m_foamRatio->setValue(6); // 出厂 6
m_foamPercent->setValue(3); // 出厂 3%
m_airFlow->setValue(100); // 出厂 100
ctx()->setDouble(ctx()->keyFoamRatio(), 6);
ctx()->setDouble(ctx()->keyFoamPercent(), 3);
ctx()->setDouble(ctx()->keyAirFlow(), 100);
ctx()->comm()->sendSetParam(ctx()->keyFoamRatio(), 6);
ctx()->comm()->sendSetParam(ctx()->keyFoamPercent(), 3);
ctx()->comm()->sendSetParam(ctx()->keyAirFlow(), 100);
}
refreshPumpValues(); // 底部数值跟着回到出厂值
ConfirmDialog::toast(this, "已恢复出厂值");
}
void WashPage::updateGating()
{
bool running = ctx()->isWashRunning();
m_start->setEnabled(!running);
m_stop->setEnabled(running);
m_back->setEnabled(!running);
}
void WashPage::refresh()
{
const Telemetry& t = ctx()->telemetry;
if (!t.commOk) m_banner->showAlarm("通信异常");
else m_banner->clear();
// 以泵状态同步运行状态
if (t.plunger || t.air || t.leaf) ctx()->setWashRunning(true);
else if (t.washMode == "--") ctx()->setWashRunning(false);
updateGating();
// 未收到对应报文的数值统一显示 "--"(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, 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(m_fMode, t.washMode);
m_washCard->setField(m_fLevelA, dashIf(t.hasWash, QString("%1L").arg(t.levelA, 0, 'f', 0)));
m_washCard->setField(m_fLevelFoam, dashIf(t.hasWash, QString("%1L").arg(t.levelFoam, 0, 'f', 0)));
m_washCard->setField(m_fFlowA, dashIf(t.hasWash, QString("%1 L/min").arg(t.flowA, 0, 'f', 1)));
m_washCard->setField(m_fFlowB, dashIf(t.hasWash, QString("%1 L/min").arg(t.flowB, 0, 'f', 1)));
m_washCard->setField(m_fGasFlow, dashIf(t.hasWash, QString("%1 L/min").arg(t.gasFlow, 0, 'f', 1)));
m_washCard->setField(m_fPress, dashIf(t.hasWashPress, QString("%1 MPa").arg(t.press, 0, 'f', 2)));
m_washCard->setField(m_fAirPress, dashIf(t.hasWashPress, QString("%1 MPa").arg(t.airPress, 0, 'f', 2)));
m_washCard->setField(m_fFoam, dashIf(t.hasWash, QString("%1").arg(t.foam, 0, 'f', 1)));
m_washCard->setAlarm(m_fLevelA, t.levelALow);
m_washCard->setAlarm(m_fLevelFoam, t.levelBLow);
m_washCard->setFaults(washCardFaults(t));
updateLeds();
// 底部三个泵的数值
refreshPumpValues();
}
// 底栏指示灯。单独拆出来是为了让 onStart/onStop 能直接调它:
// 遥测还没回来时 refresh() 里的“以泵状态同步运行状态”会把刚置上的 washRunning 又抹掉,
// 所以启停的即时反馈不能靠整个 refresh()。
void WashPage::updateLeds()
{
const Telemetry& t = ctx()->telemetry;
m_ledRun->setState(ctx()->isWashRunning() ? LedIndicator::Green : LedIndicator::Off);
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_ledD1->setState(t.d1open ? LedIndicator::Green : LedIndicator::Off);
m_ledD2->setState(t.d2open ? LedIndicator::Green : LedIndicator::Off);
// 液位低拆成 A / B 两路,各用各自的协议位(A_LiquidLevel_Low / B_LiquidLevel_Low);
// 不再按 levelA<10 猜阈值,免得卡片液位行红闪、这颗灯却不亮
m_ledLevelA->setState(t.levelALow ? LedIndicator::RedBlink : LedIndicator::Off);
m_ledLevelB->setState(t.levelBLow ? LedIndicator::RedBlink : LedIndicator::Off);
}