898 lines
37 KiB
C++

#include "MainWindow.h"
#include <array>
#include <QAbstractItemView>
#include <QComboBox>
#include <QDateTime>
#include <QGridLayout>
#include <QHBoxLayout>
#include <QJsonArray>
#include <QJsonObject>
#include <QJsonValue>
#include <QLabel>
#include <QLineEdit>
#include <QMessageBox>
#include <QPushButton>
#include <QRegularExpression>
#include <QRegularExpressionValidator>
#include <QResizeEvent>
#include <QScroller>
#include <QScrollerProperties>
#include <QSignalBlocker>
#include <QStatusBar>
#include <QTimer>
#include <QVariant>
#include <QVBoxLayout>
#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<int>(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<int>::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<GuidePresentation, 24> 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<int>(states.size())) {
stateCode = 12;
}
return states[static_cast<size_t>(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<int>(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<qint64>(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);
}