#include "MainWindow.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "RemoteGuideClient.h" #include "dialogcontrolpassword.h" #include "IVrUtils.h" #include "Version.h" #include "ui_MainWindow.h" namespace { void EnableComboBoxTouchScroll(QComboBox* combo) { if (!combo) { return; } combo->setMaxVisibleItems(8); QAbstractItemView* view = combo->view(); if (!view) { return; } view->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOn); view->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); view->setVerticalScrollMode(QAbstractItemView::ScrollPerPixel); view->setTextElideMode(Qt::ElideNone); QWidget* viewport = view->viewport(); viewport->setAttribute(Qt::WA_AcceptTouchEvents, true); QScroller::grabGesture(viewport, QScroller::LeftMouseButtonGesture); QScroller* scroller = QScroller::scroller(viewport); if (scroller) { QScrollerProperties properties = scroller->scrollerProperties(); properties.setScrollMetric( QScrollerProperties::VerticalOvershootPolicy, QVariant(static_cast(QScrollerProperties::OvershootAlwaysOff))); scroller->setScrollerProperties(properties); } } QString NonEmpty(const QString& value, const QString& fallback) { const QString trimmed = value.trimmed(); return trimmed.isEmpty() ? fallback : trimmed; } bool IsKnownAircraftModel(const QString& modelType) { const QString normalized = modelType.trimmed().toUpper(); return !normalized.isEmpty() && normalized != QStringLiteral("未知") && normalized != QStringLiteral("----"); } QString FormatModelVerification(const QString& expectedModel, const QString& detectedModel, bool modelMatched) { const QString expected = expectedModel.trimmed().toUpper(); const QString detected = detectedModel.trimmed().toUpper(); if (!IsKnownAircraftModel(detected)) { return QStringLiteral("未知"); } if (!IsKnownAircraftModel(expected)) { return QStringLiteral("实测(%1)").arg(detected); } if (modelMatched) { return QStringLiteral("匹配(%1)").arg(detected); } if (detected.compare(expected, Qt::CaseInsensitive) != 0) { return QStringLiteral("不匹配(%1)").arg(detected); } return QStringLiteral("未知"); } void ApplyActivatedLabel(QLabel* label, bool activated) { if (!label) { return; } label->setText(activated ? QStringLiteral("系统已激活") : QStringLiteral("未激活")); label->setStyleSheet(activated ? QStringLiteral("color: rgb(0, 255, 0); font-size: 26px; font-weight: 700;") : QStringLiteral("color: rgb(172, 181, 194); font-size: 26px; font-weight: 700;")); } QJsonObject RuntimeStatusObject(const QJsonObject& root) { const QJsonValue value = root.value(QStringLiteral("status")); return value.isObject() ? value.toObject() : QJsonObject(); } bool ActivatedOf(const QJsonObject& root) { const QJsonObject status = RuntimeStatusObject(root); if (status.contains(QStringLiteral("activated"))) { return status.value("activated").toBool(); } return root.value("activated").toBool(); } QString WorkModeOf(const QJsonObject& root) { const QJsonObject status = RuntimeStatusObject(root); if (status.contains(QStringLiteral("workMode"))) { return status.value("workMode").toString(); } return root.value("workMode").toString(); } QString FormatProtocolTime(const QJsonValue& value) { if (value.isString()) { const QString text = value.toString().trimmed(); return text.isEmpty() ? QStringLiteral("--") : text; } const qint64 epoch = value.toVariant().toLongLong(); if (epoch <= 0) { return QStringLiteral("--"); } return QDateTime::fromMSecsSinceEpoch(epoch).toString( QStringLiteral("yyyy-MM-dd HH:mm:ss")); } } // namespace MainWindow::MainWindow(int discoveryPort, QWidget* parent) : QMainWindow(parent) , ui(new Ui::MainWindow) { ui->setupUi(this); EnableComboBoxTouchScroll(ui->modelCombo); setWindowTitle(QStringLiteral("停机引导远程显示 %1") .arg(QString::fromLatin1( PARKINGSPACEGUIDEVIEW_FULL_VERSION_STRING))); const QString versionWithBuildTime = QStringLiteral("%1_%2%3%4%5%6%7") .arg(QString::fromLatin1( PARKINGSPACEGUIDEVIEW_FULL_VERSION_STRING)) .arg(YEAR) .arg(MONTH, 2, 10, QChar('0')) .arg(DAY, 2, 10, QChar('0')) .arg(HOUR, 2, 10, QChar('0')) .arg(MINUTE, 2, 10, QChar('0')) .arg(SECOND, 2, 10, QChar('0')); ui->versionLabel->setText(versionWithBuildTime); statusBar()->addPermanentWidget(ui->versionLabel); connect(ui->windowButton, &QPushButton::clicked, this, &QWidget::showMinimized); connect(ui->closeWindowButton, &QPushButton::clicked, this, &QWidget::close); m_client = new RemoteGuideClient(discoveryPort, this); connect(m_client, &RemoteGuideClient::ConnectionStateChanged, this, &MainWindow::HandleConnectionState); connect(m_client, &RemoteGuideClient::StatusReceived, this, &MainWindow::ApplyStatus); connect(m_client, &RemoteGuideClient::GuidanceRunningChanged, this, [this](bool running) { const bool wasRunning = m_guidanceRunning; m_guidanceRunning = running; if (wasRunning && !running) { const QSignalBlocker blocker(ui->modelCombo); ui->modelCombo->setCurrentIndex(0); } UpdateControls(); }); connect(m_client, &RemoteGuideClient::CommandFinished, this, &MainWindow::HandleCommandFinished); connect(ui->modelCombo, QOverload::of(&QComboBox::currentIndexChanged), this, [this](int index) { if (index > 0 && m_client && m_client->IsConnected()) { m_commandPending = true; UpdateControls(); m_client->SelectModel(ui->modelCombo->currentText()); } UpdateControls(); }); connect(ui->startButton, &QPushButton::clicked, this, [this]() { const QString password = PromptControlPassword(QStringLiteral("开始引导")); if (m_requireLogin && password.isEmpty()) { return; } m_commandPending = true; UpdateControls(); m_client->StartGuidance( ui->modelCombo->currentIndex() > 0 ? ui->modelCombo->currentText() : QString(), password); }); connect(ui->stopButton, &QPushButton::clicked, this, [this]() { const QString password = PromptControlPassword(QStringLiteral("停止引导")); if (m_requireLogin && password.isEmpty()) { return; } m_commandPending = true; UpdateControls(); m_client->StopGuidance(password); }); AddStatusRow(QStringLiteral("系统状态"), m_activatedLabel); AddStatusRow(QStringLiteral("自检"), m_selfCheckLabel); AddStatusRow(QStringLiteral("工作模式"), m_workModeLabel); AddStatusRow(QStringLiteral("人员轮挡"), m_personChockLabel); AddStatusRow(QStringLiteral("上轮挡"), m_chockOnLabel); AddStatusRow(QStringLiteral("下轮挡"), m_chockOffLabel); auto* passwordLayout = new QHBoxLayout(); m_passwordEdit = new QLineEdit(ui->sidePanel); m_passwordEdit->setEchoMode(QLineEdit::Password); m_passwordEdit->setMaxLength(DialogControlPassword::kPasswordLength); m_passwordEdit->setValidator(new QRegularExpressionValidator( QRegularExpression(QStringLiteral("^[0-9]{0,6}$")), m_passwordEdit)); m_passwordEdit->setInputMethodHints(Qt::ImhDigitsOnly); m_passwordEdit->setPlaceholderText(QStringLiteral("6位数字密码")); m_passwordEdit->setStyleSheet( QStringLiteral("color: rgb(239, 241, 245); background-color: rgb(37, 38, 42);" "border: 1px solid rgb(92, 95, 104); padding-left: 12px;" "font-size: 18px; min-height: 42px;")); m_loginButton = new QPushButton(QStringLiteral("登录"), ui->sidePanel); m_loginButton->setMinimumHeight(42); passwordLayout->addWidget(m_passwordEdit, 1); passwordLayout->addWidget(m_loginButton); ui->sideLayout->insertLayout(ui->sideLayout->count() - 1, passwordLayout); auto* actionLayout = new QHBoxLayout(); m_maintenanceButton = new QPushButton(QStringLiteral("维护模式"), ui->sidePanel); actionLayout->addWidget(m_maintenanceButton); ui->sideLayout->insertLayout(ui->sideLayout->count() - 1, actionLayout); const QString chockStyle = QStringLiteral("QPushButton { color: rgb(239, 241, 245); border: none; " "border-radius: 4px; font-size: 16px; }" "QPushButton:disabled { background-color: rgb(65, 67, 73); " "color: rgb(130, 132, 138); }"); m_chockOnButton = new QPushButton(QStringLiteral("上轮挡"), ui->header); m_chockOnButton->setGeometry(1525, 112, 80, 80); m_chockOnButton->setToolTip(QStringLiteral("上轮挡")); m_chockOnButton->setStyleSheet( chockStyle + QStringLiteral("QPushButton { background-color: rgb(60, 125, 80); }")); m_chockOffButton = new QPushButton(QStringLiteral("下轮挡"), ui->header); m_chockOffButton->setGeometry(1645, 112, 80, 80); m_chockOffButton->setToolTip(QStringLiteral("下轮挡")); m_chockOffButton->setStyleSheet( chockStyle + QStringLiteral("QPushButton { background-color: rgb(70, 110, 160); }")); m_chockOnButton->show(); m_chockOffButton->show(); auto* replayLayout = new QHBoxLayout(); m_sessionCombo = new QComboBox(ui->sidePanel); m_sessionCombo->addItem(QStringLiteral("选择历史会话")); m_sessionCombo->setStyleSheet( QStringLiteral("color: rgb(239, 241, 245); background-color: rgb(37, 38, 42);" "border: 1px solid rgb(92, 95, 104); padding-left: 12px;" "font-size: 16px; min-height: 42px;")); m_replayButton = new QPushButton(QStringLiteral("回放"), ui->sidePanel); m_replayButton->setMinimumHeight(42); replayLayout->addWidget(m_sessionCombo, 1); replayLayout->addWidget(m_replayButton); ui->sideLayout->insertLayout(ui->sideLayout->count() - 1, replayLayout); m_replayTimer = new QTimer(this); m_replayTimer->setSingleShot(true); connect(m_replayTimer, &QTimer::timeout, this, &MainWindow::PlayNextReplaySample); m_passwordExpireTimer = new QTimer(this); m_passwordExpireTimer->setSingleShot(true); connect(m_passwordExpireTimer, &QTimer::timeout, this, [this]() { ExpirePasswordSession(); }); connect(m_loginButton, &QPushButton::clicked, this, [this]() { if (!m_client || !m_client->IsConnected()) { return; } m_commandPending = true; UpdateControls(); m_client->Login(m_passwordEdit->text()); }); connect(m_maintenanceButton, &QPushButton::clicked, this, [this]() { if (m_guidanceRunning) { return; } const QString password = PromptControlPassword( m_maintenanceMode ? QStringLiteral("退出维护模式") : QStringLiteral("进入维护模式")); if (m_requireLogin && password.isEmpty()) { return; } m_commandPending = true; UpdateControls(); m_client->SetMaintenanceMode(!m_maintenanceMode, password); }); connect(m_chockOnButton, &QPushButton::clicked, this, [this]() { RecordChockWithPassword(true); }); connect(m_chockOffButton, &QPushButton::clicked, this, [this]() { RecordChockWithPassword(false); }); connect(m_replayButton, &QPushButton::clicked, this, [this]() { if (m_replaying) { StopReplay(); UpdateControls(); return; } const int index = m_sessionCombo->currentIndex(); const QString sessionUuid = m_sessionCombo->itemData(index).toString(); if (sessionUuid.isEmpty()) { return; } const QString password = PromptControlPassword(QStringLiteral("回放")); if (m_requireLogin && password.isEmpty()) { return; } m_commandPending = true; UpdateControls(); m_client->RequestSessionSamples(sessionUuid, password); }); ApplyStatus(QJsonObject()); HandleConnectionState(false, QStringLiteral("正在搜索停机引导设备……"), QString(), QString()); } MainWindow::~MainWindow() { delete ui; } void MainWindow::ApplyStatus(const QJsonObject& status) { if (m_replaying && m_guidanceRunning) { StopReplay(); } if (m_replaying) { return; } ui->stateImage->SetStatus(status); int stateCode = status.value("guideStateCode").toInt(12); if (stateCode < 1 || stateCode > 24) { stateCode = 12; } ApplyGuideState(stateCode, status.value("guideText").toString()); ui->targetIdLabel->setText(NonEmpty( status.value("targetId").toString(), QStringLiteral("T001"))); ui->parkIdLabel->setText(NonEmpty( status.value("parkId").toString(), QStringLiteral("未知"))); QString expectedModel = status.value("expectedModelType") .toString().trimmed(); if (m_guidanceRunning && IsKnownAircraftModel(expectedModel) && ui->modelCombo->currentText() != expectedModel) { const QSignalBlocker blocker(ui->modelCombo); ui->modelCombo->setCurrentText(expectedModel); } if (expectedModel.isEmpty() && ui->modelCombo->currentIndex() > 0) { expectedModel = ui->modelCombo->currentText(); } const QString detectedModel = NonEmpty( status.value("modelType").toString(), QStringLiteral("未知")); ui->modelMatchLabel->setText(FormatModelVerification( expectedModel, detectedModel, status.value("modelMatched").toBool())); const int personState = status.value("guideStateCode").toInt(0); const QString personChockTime = FormatProtocolTime(status.value("personChockTime")); ui->personLabel->setText( personState == 21 || personChockTime != QStringLiteral("--") ? QStringLiteral("已确认") : QStringLiteral("--")); ui->personCountLabel->setText(QStringLiteral("--")); SetMetric(ui->distanceLabel, status, "distanceToStop", QStringLiteral("毫米"), 0); SetMetric(ui->offsetLabel, status, "lateralOffset", QStringLiteral("毫米"), 0); SetMetric(ui->yawLabel, status, "bodyYawAngle", QStringLiteral("度")); SetMetric(ui->speedLabel, status, "aircraftSpeed", QStringLiteral("米/秒")); const bool activated = ActivatedOf(status); ApplyActivatedLabel(m_activatedLabel, activated); const QJsonObject selfCheck = status.value("selfCheck").toObject(); if (m_selfCheckLabel) { if (selfCheck.value("running").toBool()) { m_selfCheckLabel->setText(QStringLiteral("自检中")); } else if (selfCheck.isEmpty()) { m_selfCheckLabel->setText(QStringLiteral("--")); } else { m_selfCheckLabel->setText(NonEmpty( selfCheck.value("message").toString(), selfCheck.value("ok").toBool() ? QStringLiteral("通过") : QStringLiteral("失败"))); } } const QString workMode = WorkModeOf(status); m_maintenanceMode = workMode == QStringLiteral("maintenance"); if (m_workModeLabel) { m_workModeLabel->setText(m_maintenanceMode ? QStringLiteral("维护模式") : QStringLiteral("正常工作模式")); } if (m_personChockLabel) { m_personChockLabel->setText( FormatProtocolTime(status.value("personChockTime"))); } if (m_chockOnLabel) { m_chockOnLabel->setText( FormatProtocolTime(status.value("chockOnTime"))); } if (m_chockOffLabel) { m_chockOffLabel->setText( FormatProtocolTime(status.value("chockOffTime"))); } if (m_maintenanceButton) { m_maintenanceButton->setText(m_maintenanceMode ? QStringLiteral("退出维护") : QStringLiteral("维护模式")); } } void MainWindow::ApplyGuideState(int stateCode, const QString& guideText) { const GuidePresentation& presentation = PresentationForState(stateCode); ui->stateNameLabel->setText(presentation.name); QString stateText = QStringLiteral("%1 %2") .arg(stateCode) .arg(presentation.name); const QString trimmedGuideText = guideText.trimmed(); if (!trimmedGuideText.isEmpty()) { stateText += QStringLiteral(" / %1").arg(trimmedGuideText); } ui->guideStateLabel->setText(stateText); if (stateCode != m_currentStateCode) { m_currentStateCode = stateCode; ui->stateCodeLabel->setText( QStringLiteral("状态 #%1").arg(stateCode, 2, 10, QChar('0'))); } } void MainWindow::UpdateControls() { const bool canOperate = m_connected && m_loggedIn && !m_commandPending; ui->modelCombo->setEnabled(canOperate && !m_guidanceRunning && !m_replaying); ui->startButton->setEnabled(canOperate && !m_guidanceRunning && !m_replaying); ui->stopButton->setEnabled(canOperate && m_guidanceRunning); if (m_passwordEdit) { m_passwordEdit->setEnabled(m_connected && m_requireLogin && !m_loggedIn && !m_commandPending); } if (m_loginButton) { m_loginButton->setEnabled(m_connected && m_requireLogin && !m_loggedIn && !m_commandPending); } if (m_maintenanceButton) { m_maintenanceButton->setEnabled(canOperate && !m_guidanceRunning); } if (m_chockOnButton) { m_chockOnButton->setEnabled(m_connected && !m_commandPending); } if (m_chockOffButton) { m_chockOffButton->setEnabled(m_connected && !m_commandPending); } if (m_sessionCombo) { m_sessionCombo->setEnabled(canOperate && !m_guidanceRunning); } if (m_replayButton) { m_replayButton->setEnabled(canOperate && !m_guidanceRunning); m_replayButton->setText(m_replaying ? QStringLiteral("停止") : QStringLiteral("回放")); } } void MainWindow::SetMetric(QLabel* label, const QJsonObject& status, const char* field, const QString& unit, int precision, double divisor) { const double safeDivisor = divisor == 0.0 ? 1.0 : divisor; const double value = status.value(QString::fromLatin1(field)) .toDouble(0.0) / safeDivisor; label->setText(QString::number(value, 'f', precision) + unit); } void MainWindow::HandleConnectionState(bool connected, const QString& message, const QString& deviceName, const QString& address) { Q_UNUSED(message); Q_UNUSED(deviceName); Q_UNUSED(address); m_connected = connected; m_commandPending = false; m_guidanceRunning = connected && m_client ? m_client->IsGuidanceRunning() : false; if (!connected) { StopReplay(); ExpirePasswordSession(); m_requireLogin = true; const QSignalBlocker blocker(ui->modelCombo); ui->modelCombo->clear(); ui->modelCombo->addItem(QStringLiteral("请选择机型")); if (m_sessionCombo) { const QSignalBlocker sessionBlocker(m_sessionCombo); m_sessionCombo->clear(); m_sessionCombo->addItem(QStringLiteral("选择历史会话")); } if (m_passwordEdit) { m_passwordEdit->clear(); } } ui->deviceLabel->setText(connected ? QStringLiteral("已连接") : QStringLiteral("未连接")); UpdateControls(); } void MainWindow::HandleCommandFinished(const QString& command, bool ok, const QString& message, const QJsonObject& response) { Q_UNUSED(message); m_commandPending = false; if (ok && command == QStringLiteral("start")) { m_guidanceRunning = true; StopReplay(); } else if (ok && command == QStringLiteral("stop")) { m_guidanceRunning = false; const QSignalBlocker blocker(ui->modelCombo); ui->modelCombo->setCurrentIndex(0); } else if (!ok && command == QStringLiteral("select_model")) { const QSignalBlocker blocker(ui->modelCombo); ui->modelCombo->setCurrentIndex(0); } else if (command == QStringLiteral("login")) { if (ok) { BeginPasswordSession(); m_client->RequestSessions(50); } else { ExpirePasswordSession(); if (m_passwordEdit) { m_passwordEdit->clear(); } } } else if (ok && command == QStringLiteral("get_panel_config")) { ApplyPanelConfig(response.value("panel").toObject()); } else if (ok && command == QStringLiteral("list_sessions")) { ApplySessionList(response.value("sessions").toArray()); } else if (ok && command == QStringLiteral("get_session_samples")) { StartReplay(response.value("samples").toArray()); } else if (ok && command == QStringLiteral("set_maintenance")) { if (response.contains(QStringLiteral("workMode"))) { m_maintenanceMode = response.value("workMode").toString() == QStringLiteral("maintenance"); } else { m_maintenanceMode = response.value("maintenanceMode").toBool(); } } if (m_client && m_client->IsConnected()) { m_guidanceRunning = m_client->IsGuidanceRunning(); } UpdateControls(); } void MainWindow::resizeEvent(QResizeEvent* event) { QMainWindow::resizeEvent(event); } const MainWindow::GuidePresentation& MainWindow::PresentationForState(int stateCode) { static const std::array states = {{ {QStringLiteral("泊位引导启动"), QStringLiteral("START-OF-DOCKING"), QStringLiteral("WAIT"), QStringLiteral("系统已进入泊位引导等待状态。"), QStringLiteral("等待运行箭头或接近率条出现,并确认机型正确。"), QStringLiteral("warn")}, {QStringLiteral("捕获"), QStringLiteral("CAPTURE"), QStringLiteral("CAPTURE"), QStringLiteral("运行箭头和飞机类型表示系统正在搜索接近中的飞机。"), QStringLiteral("确认机型正确,沿引导线低速滑行。"), QStringLiteral("good")}, {QStringLiteral("跟踪"), QStringLiteral("TRACKING"), QStringLiteral("TRACKING"), QStringLiteral("中心线和转向箭头提示飞机位置及修正方向。"), QStringLiteral("按红色箭头小幅修正方向,保持低速。"), QStringLiteral("good")}, {QStringLiteral("接近率"), QStringLiteral("CLOSING RATE"), QStringLiteral("CLOSING"), QStringLiteral("数字和接近率条显示距停止位置的剩余距离。"), QStringLiteral("保持低速并按箭头修正,准备在 STOP 出现时停车。"), QStringLiteral("good")}, {QStringLiteral("对准中心线"), QStringLiteral("ALIGNED TO CENTRE"), QStringLiteral("ALIGNED"), QStringLiteral("显示距离但无转向箭头,表示飞机已对准中心线。"), QStringLiteral("继续低速向停止位滑行。"), QStringLiteral("good")}, {QStringLiteral("慢速"), QStringLiteral("SLOW"), QStringLiteral("SLOW"), QStringLiteral("飞机接近速度高于当前区域的允许速度。"), QStringLiteral("立即减速,观察是否恢复正常接近率显示。"), QStringLiteral("warn")}, {QStringLiteral("方位引导"), QStringLiteral("AZIMUTH GUIDANCE"), QStringLiteral("GUIDANCE"), QStringLiteral("位置箭头和转向箭头共同提示方位修正。"), QStringLiteral("按转向箭头修正并保持低速。"), QStringLiteral("good")}, {QStringLiteral("到达停止位置"), QStringLiteral("STOP POSITION"), QStringLiteral("STOP"), QStringLiteral("飞机已经到达正确停止位置。"), QStringLiteral("立即停车,等待地面确认。"), QStringLiteral("stop")}, {QStringLiteral("泊位引导完成"), QStringLiteral("DOCKING COMPLETED"), QStringLiteral("OK"), QStringLiteral("泊位引导流程已经完成。"), QStringLiteral("保持停稳,等待地面服务。"), QStringLiteral("good")}, {QStringLiteral("越位"), QStringLiteral("OVERSHOOT"), QStringLiteral("TOO FAR"), QStringLiteral("飞机已经越过目标停止位置。"), QStringLiteral("立即停止,等待人工处置。"), QStringLiteral("stop")}, {QStringLiteral("提前停止"), QStringLiteral("STOP SHORT"), QStringLiteral("STOP OK"), QStringLiteral("飞机已静止,但尚未到达预定停止位置。"), QStringLiteral("等待地面确认是否继续或结束。"), QStringLiteral("warn")}, {QStringLiteral("等待"), QStringLiteral("WAIT"), QStringLiteral("WAIT"), QStringLiteral("系统等待视野恢复或重新检测到飞机。"), QStringLiteral("保持等待,在接近率条恢复前不要继续。"), QStringLiteral("warn")}, {QStringLiteral("异常慢速-恶劣天气"), QStringLiteral("SLOW - WEATHER"), QStringLiteral("SLOW"), QStringLiteral("天气原因导致系统进入异常慢速捕获模式。"), QStringLiteral("低速等待接近率条出现。"), QStringLiteral("warn")}, {QStringLiteral("异常慢速-飞机丢失"), QStringLiteral("SLOW - LOST"), QStringLiteral("SLOW"), QStringLiteral("引导过程中暂时丢失飞机目标。"), QStringLiteral("低速等待系统重新检测飞机。"), QStringLiteral("warn")}, {QStringLiteral("飞机验证失败"), QStringLiteral("ID FAIL"), QStringLiteral("STOP ID FAIL"), QStringLiteral("检测机型与选择机型验证失败。"), QStringLiteral("停止飞机,等待人工复核,不得自行继续。"), QStringLiteral("stop")}, {QStringLiteral("机位受阻"), QStringLiteral("GATE BLOCKED"), QStringLiteral("WAIT GATE BLOCK"), QStringLiteral("机位前方或进近视野存在障碍。"), QStringLiteral("等待障碍物清除,未恢复前不要继续。"), QStringLiteral("warn")}, {QStringLiteral("视野受阻"), QStringLiteral("VIEW BLOCKED"), QStringLiteral("WAIT VIEW BLOCK"), QStringLiteral("传感器视野受阻,当前无法可靠检测飞机。"), QStringLiteral("等待视野恢复,必要时切换人工引导。"), QStringLiteral("warn")}, {QStringLiteral("SBU停止"), QStringLiteral("SBU STOP"), QStringLiteral("STOP SBU"), QStringLiteral("自动引导因不可恢复错误进入安全备份状态。"), QStringLiteral("立即停止并使用人工引导,不得自行恢复。"), QStringLiteral("stop")}, {QStringLiteral("速度过快"), QStringLiteral("TOO FAST"), QStringLiteral("STOP TOO FAST"), QStringLiteral("飞机速度超出系统可以安全处理的范围。"), QStringLiteral("立即停止飞机,等待系统重启或人工引导。"), QStringLiteral("stop")}, {QStringLiteral("紧急停止"), QStringLiteral("EMERGENCY STOP"), QStringLiteral("STOP"), QStringLiteral("现场急停或急停线路断开已经触发。"), QStringLiteral("立即停止,等待现场确认和处置。"), QStringLiteral("stop")}, {QStringLiteral("挡轮挡"), QStringLiteral("CHOCKS ON"), QStringLiteral("CHOCK ON"), QStringLiteral("轮挡区域人员检测已完成。"), QStringLiteral("保持停稳,进入地面保障流程。"), QStringLiteral("good")}, {QStringLiteral("系统错误"), QStringLiteral("ERROR"), QStringLiteral("ERROR"), QStringLiteral("系统发生错误,自动引导不可继续。"), QStringLiteral("停止自动引导并通知维护人员。"), QStringLiteral("stop")}, {QStringLiteral("系统故障"), QStringLiteral("SYSTEM BREAKDOWN"), QStringLiteral("BLACK"), QStringLiteral("系统发生严重故障,自动引导不可用。"), QStringLiteral("使用人工备份程序。"), QStringLiteral("maint")}, {QStringLiteral("电源故障"), QStringLiteral("POWER FAILURE"), QStringLiteral("BLACK"), QStringLiteral("显示和引导系统电源中断。"), QStringLiteral("使用人工备份程序并检查供电。"), QStringLiteral("maint")} }}; if (stateCode < 1 || stateCode > static_cast(states.size())) { stateCode = 12; } return states[static_cast(stateCode - 1)]; } void MainWindow::AddStatusRow(const QString& caption, QLabel*& valueLabel) { const int row = ui->resultGrid->rowCount(); auto* captionLabel = new QLabel(caption, ui->sidePanel); captionLabel->setStyleSheet( QStringLiteral("color: rgb(153, 164, 178); font-size: 24px;")); valueLabel = new QLabel(ui->sidePanel); valueLabel->setAlignment(Qt::AlignRight | Qt::AlignVCenter); valueLabel->setTextInteractionFlags(Qt::TextSelectableByMouse); valueLabel->setStyleSheet( QStringLiteral("color: rgb(239, 242, 246); font-size: 26px; font-weight: 500;")); valueLabel->setText(QStringLiteral("--")); ui->resultGrid->addWidget(captionLabel, row, 0); ui->resultGrid->addWidget(valueLabel, row, 1); } void MainWindow::ApplyPanelConfig(const QJsonObject& panel) { m_requireLogin = panel.value("requireLogin").toBool(true); m_passwordValidSeconds = panel.value("passwordValidSeconds").toInt(120); if (m_passwordValidSeconds < 1) { m_passwordValidSeconds = 120; } if (panel.contains(QStringLiteral("workMode"))) { m_maintenanceMode = panel.value("workMode").toString() == QStringLiteral("maintenance"); } else { m_maintenanceMode = panel.value("maintenanceMode").toBool(); } if (!m_requireLogin) { m_loggedIn = true; if (m_client) { m_client->RequestSessions(50); } } RebuildModelCombo(panel.value("models").toArray()); if (panel.contains(QStringLiteral("activated"))) { ApplyActivatedLabel(m_activatedLabel, panel.value("activated").toBool()); } if (m_workModeLabel) { m_workModeLabel->setText(m_maintenanceMode ? QStringLiteral("维护模式") : QStringLiteral("正常工作模式")); } UpdateControls(); } void MainWindow::RebuildModelCombo(const QJsonArray& models) { const QString current = ui->modelCombo->currentText(); const QSignalBlocker blocker(ui->modelCombo); ui->modelCombo->clear(); ui->modelCombo->addItem(QStringLiteral("请选择机型")); for (const QJsonValue& value : models) { const QString name = value.isObject() ? value.toObject().value("name").toString().trimmed() : value.toString().trimmed(); if (!name.isEmpty()) { ui->modelCombo->addItem(name); } } if (ui->modelCombo->count() <= 1) { ui->modelCombo->addItem(QStringLiteral("A320")); ui->modelCombo->addItem(QStringLiteral("B737")); ui->modelCombo->addItem(QStringLiteral("其他")); } const int index = ui->modelCombo->findText(current); ui->modelCombo->setCurrentIndex(index > 0 ? index : 0); } void MainWindow::ApplySessionList(const QJsonArray& sessions) { if (!m_sessionCombo) { return; } const QSignalBlocker blocker(m_sessionCombo); m_sessionCombo->clear(); m_sessionCombo->addItem(QStringLiteral("选择历史会话")); for (const QJsonValue& value : sessions) { const QJsonObject object = value.toObject(); const QString uuid = object.value("sessionUuid").toString(); if (uuid.isEmpty()) { continue; } const QString model = NonEmpty( object.value("expectedModelType").toString(), NonEmpty(object.value("modelType").toString(), NonEmpty(object.value("expectedModel").toString(), object.value("recognizedModel").toString()))); const QString label = QStringLiteral("%1 %2") .arg(FormatProtocolTime(object.value("startTime")), NonEmpty(model, QStringLiteral("未选机型"))); m_sessionCombo->addItem(label, uuid); } } void MainWindow::StartReplay(const QJsonArray& samples) { StopReplay(); m_replaySamples = samples; m_replayIndex = 0; if (m_replaySamples.isEmpty()) { return; } m_replaying = true; PlayNextReplaySample(); } void MainWindow::StopReplay() { m_replaying = false; m_replaySamples = QJsonArray(); m_replayIndex = 0; if (m_replayTimer) { m_replayTimer->stop(); } } void MainWindow::PlayNextReplaySample() { if (!m_replaying || m_replayIndex >= m_replaySamples.size()) { StopReplay(); UpdateControls(); return; } const QJsonObject sample = m_replaySamples.at(m_replayIndex).toObject(); m_replaying = false; ApplyStatus(sample); m_replaying = true; ++m_replayIndex; if (m_replayIndex >= m_replaySamples.size()) { StopReplay(); UpdateControls(); return; } const qint64 currentTs = sample.value("timestamp").toVariant().toLongLong(); const qint64 nextTs = m_replaySamples.at(m_replayIndex) .toObject() .value("timestamp") .toVariant() .toLongLong(); int intervalMs = static_cast(nextTs - currentTs); if (intervalMs < 50) { intervalMs = 50; } else if (intervalMs > 1000) { intervalMs = 1000; } m_replayTimer->start(intervalMs); } QString MainWindow::PromptControlPassword(const QString& title) { if (!m_requireLogin) { return QStringLiteral(""); } if (IsPasswordSessionValid() && m_client) { const QString cached = m_client->SessionPassword(); if (!cached.isEmpty()) { return cached; } } DialogControlPassword dialog(this); dialog.SetTitle(title); if (m_client) { const QString expected = m_client->SessionPassword(); if (!expected.isEmpty()) { dialog.SetPasswordChecker([expected](const QString& password) { return password == expected; }); } } if (dialog.exec() != QDialog::Accepted) { return QString(); } if (m_client) { m_client->SetSessionPassword(dialog.Password()); } BeginPasswordSession(); return dialog.Password(); } bool MainWindow::IsPasswordSessionValid() const { return m_loggedIn && m_passwordValidUntilMs > QDateTime::currentMSecsSinceEpoch(); } void MainWindow::BeginPasswordSession() { // 从本次成功输入起计算有效期,到期后必须重新输入密码。 m_loggedIn = true; const int validSeconds = m_passwordValidSeconds < 1 ? 120 : m_passwordValidSeconds; m_passwordValidUntilMs = QDateTime::currentMSecsSinceEpoch() + static_cast(validSeconds) * 1000; if (m_passwordExpireTimer) { m_passwordExpireTimer->start(validSeconds * 1000); } UpdateControls(); } void MainWindow::ExpirePasswordSession() { m_loggedIn = false; m_passwordValidUntilMs = 0; if (m_passwordExpireTimer) { m_passwordExpireTimer->stop(); } if (m_client) { m_client->ClearSessionPassword(); } if (m_passwordEdit) { m_passwordEdit->clear(); } UpdateControls(); } void MainWindow::RecordChockWithPassword(bool chockOn) { if (!m_client || !m_client->IsConnected()) { return; } const QString title = chockOn ? QStringLiteral("上轮挡") : QStringLiteral("下轮挡"); const QString password = PromptControlPassword(title); if (m_requireLogin && password.isEmpty()) { return; } m_commandPending = true; UpdateControls(); m_client->RecordChock(chockOn, password); }