370 lines
15 KiB
C++
370 lines
15 KiB
C++
#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 <QTimer>
|
||
#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("电量", "85%");
|
||
m_chassisCard->addField("电压", "360V");
|
||
m_chassisCard->addField("故障", "无");
|
||
|
||
m_armCard = new DataCard("机械臂", left);
|
||
m_armCard->addField("末端位置", "XYZ");
|
||
m_armCard->addField("姿态", "RX/RY/RZ");
|
||
|
||
m_gasCard = new DataCard("气体检测仪", left);
|
||
m_gasCard->addField("主机状态", "待机");
|
||
m_gasCard->addField("浓度", "0 ppb");
|
||
|
||
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组分液位", "100L");
|
||
m_fLevelFoam = m_washCard->addField("泡沫箱液位", "30L");
|
||
m_fFlowA = m_washCard->addField("A药剂流量", "0 L/min");
|
||
m_fFlowB = m_washCard->addField("B药剂流量", "0 L/min");
|
||
m_fGasFlow = m_washCard->addField("气体流量", "0 L/min");
|
||
m_fPress = m_washCard->addField("管路压力", "0 MPa");
|
||
m_fAirPress = m_washCard->addField("空压机压力", "0 MPa");
|
||
m_fFoam = m_washCard->addField("发泡倍数", "0");
|
||
m_fAirFault = m_washCard->addField("空压机故障代码", "");
|
||
m_fPlungerFault = 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);
|
||
|
||
// 底部条:左右两个按钮纵向占满两行,中间上下两排(泵数值 / 指示灯)居中
|
||
QWidget* bottom = new QWidget(this);
|
||
bottom->setObjectName("WashBottom");
|
||
bottom->setFixedHeight(160);
|
||
// 用 #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); // 8 个灯 40px 字号后总宽约 1300px
|
||
ledLay->addStretch(1);
|
||
auto makeLed = [&](const QString& text) {
|
||
LedIndicator* led = new LedIndicator(text, bottom);
|
||
led->setTextPixelSize(40); // 字号与卡片标题一致
|
||
ledLay->addWidget(led);
|
||
return led;
|
||
};
|
||
m_ledRun = makeLed("启停");
|
||
m_ledPlunger = makeLed("柱塞泵");
|
||
m_ledAir = makeLed("空压机");
|
||
m_ledLeaf = makeLed("叶片泵");
|
||
m_ledLevel = makeLed("液位低");
|
||
m_ledD1 = makeLed("D1");
|
||
m_ledD2 = makeLed("D2");
|
||
m_ledFault = makeLed("系统故障");
|
||
ledLay->addStretch(1);
|
||
|
||
midLay->addStretch(1);
|
||
midLay->addLayout(valueLay);
|
||
midLay->addLayout(ledLay);
|
||
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);
|
||
|
||
// 故障代码轮流循环显示(2s/条)
|
||
m_faultTimer = new QTimer(this);
|
||
m_faultTimer->setInterval(2000);
|
||
connect(m_faultTimer, &QTimer::timeout, this, &WashPage::rotateFaults);
|
||
m_faultTimer->start();
|
||
|
||
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(QString("%1 L/min").arg(ctx()->telemetry.flowB, 0, 'f', 1));
|
||
}
|
||
|
||
void WashPage::onStart()
|
||
{
|
||
ctx()->comm()->sendWashStart(washModeTag());
|
||
ctx()->washRunning = true;
|
||
updateGating();
|
||
}
|
||
|
||
void WashPage::onStop()
|
||
{
|
||
ctx()->comm()->sendWashStop();
|
||
ctx()->washRunning = false;
|
||
updateGating();
|
||
}
|
||
|
||
void WashPage::onBack()
|
||
{
|
||
if (ctx()->washRunning) {
|
||
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()->washRunning;
|
||
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()->washRunning = true;
|
||
else if (t.washMode == "--") ctx()->washRunning = false;
|
||
updateGating();
|
||
|
||
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, t.chassisFaults.isEmpty() ? "无" : t.chassisFaults.join(";"));
|
||
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_washCard->setField(m_fMode, t.washMode);
|
||
m_washCard->setField(m_fLevelA, QString("%1L").arg(t.levelA, 0, 'f', 0));
|
||
m_washCard->setField(m_fLevelFoam, QString("%1L").arg(t.levelFoam, 0, 'f', 0));
|
||
m_washCard->setField(m_fFlowA, QString("%1 L/min").arg(t.flowA, 0, 'f', 1));
|
||
m_washCard->setField(m_fFlowB, QString("%1 L/min").arg(t.flowB, 0, 'f', 1));
|
||
m_washCard->setField(m_fGasFlow, QString("%1 L/min").arg(t.gasFlow, 0, 'f', 1));
|
||
m_washCard->setField(m_fPress, QString("%1 MPa").arg(t.press, 0, 'f', 2));
|
||
m_washCard->setField(m_fAirPress, QString("%1 MPa").arg(t.airPress, 0, 'f', 2));
|
||
m_washCard->setField(m_fFoam, QString("%1").arg(t.foam, 0, 'f', 1));
|
||
|
||
// 指示灯
|
||
m_ledRun->setState(ctx()->washRunning ? 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_ledLevel->setState(t.levelA < 10 ? LedIndicator::RedBlink : LedIndicator::Off);
|
||
m_ledD1->setState(t.d1open ? LedIndicator::Green : LedIndicator::Off);
|
||
m_ledD2->setState(t.d2open ? LedIndicator::Green : LedIndicator::Off);
|
||
m_ledFault->setState((!t.washFaults.isEmpty() || !t.pumpFaults.isEmpty())
|
||
? LedIndicator::RedBlink : LedIndicator::Off);
|
||
|
||
// 底部三个泵的数值
|
||
refreshPumpValues();
|
||
|
||
// 故障代码显示(静态更新,循环由 rotateFaults 驱动)
|
||
rotateFaults();
|
||
}
|
||
|
||
void WashPage::rotateFaults()
|
||
{
|
||
const Telemetry& t = ctx()->telemetry;
|
||
QStringList all = t.washFaults + t.pumpFaults;
|
||
if (all.isEmpty()) {
|
||
m_washCard->setField(m_fAirFault, "");
|
||
m_washCard->setField(m_fPlungerFault, "");
|
||
return;
|
||
}
|
||
m_faultIndex = m_faultIndex % all.size();
|
||
QString code = all[m_faultIndex];
|
||
m_washCard->setField(m_fAirFault, code);
|
||
m_washCard->setField(m_fPlungerFault, code);
|
||
m_faultIndex = (m_faultIndex + 1) % all.size();
|
||
}
|