313 lines
16 KiB
C++

#include "MainWindow.h"
#include <array>
#include <QComboBox>
#include <QLabel>
#include <QPushButton>
#include <QResizeEvent>
#include <QSignalBlocker>
#include <QStatusBar>
#include "RemoteGuideClient.h"
#include "IVrUtils.h"
#include "Version.h"
#include "ui_MainWindow.h"
namespace {
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 == QStringLiteral("A320") ||
normalized == QStringLiteral("B737");
}
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("未知");
}
} // namespace
MainWindow::MainWindow(int discoveryPort, QWidget* parent)
: QMainWindow(parent)
, ui(new Ui::MainWindow)
{
ui->setupUi(this);
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]() {
m_commandPending = true;
UpdateControls();
m_client->StartGuidance(
ui->modelCombo->currentIndex() > 0
? ui->modelCombo->currentText()
: QString());
});
connect(ui->stopButton, &QPushButton::clicked, this, [this]() {
m_commandPending = true;
UpdateControls();
m_client->StopGuidance();
});
ApplyStatus(QJsonObject());
HandleConnectionState(false,
QStringLiteral("正在搜索停机引导设备……"),
QString(),
QString());
}
MainWindow::~MainWindow()
{
delete ui;
}
void MainWindow::ApplyStatus(const QJsonObject& status)
{
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 personCount = status.value("personCount").toInt(0);
ui->personLabel->setText(status.value("personInChockRegion").toBool()
? QStringLiteral("已检测")
: QStringLiteral("未检测"));
ui->personCountLabel->setText(QString::number(personCount));
SetMetric(ui->distanceLabel, status, "distanceToStop",
QStringLiteral("毫米"));
SetMetric(ui->offsetLabel, status, "lateralOffset",
QStringLiteral("毫米"));
SetMetric(ui->yawLabel, status, "bodyYawAngle", QStringLiteral(""));
SetMetric(ui->speedLabel, status, "aircraftSpeed", 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()
{
ui->modelCombo->setEnabled(m_connected &&
!m_guidanceRunning &&
!m_commandPending);
ui->startButton->setEnabled(m_connected &&
!m_guidanceRunning &&
!m_commandPending);
ui->stopButton->setEnabled(m_connected &&
m_guidanceRunning &&
!m_commandPending);
}
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) {
const QSignalBlocker blocker(ui->modelCombo);
ui->modelCombo->setCurrentIndex(0);
}
ui->deviceLabel->setText(connected
? QStringLiteral("已连接")
: QStringLiteral("未连接"));
UpdateControls();
}
void MainWindow::HandleCommandFinished(const QString& command,
bool ok,
const QString& message,
const QJsonObject& response)
{
Q_UNUSED(response);
Q_UNUSED(message);
m_commandPending = false;
if (ok && command == QStringLiteral("start")) {
m_guidanceRunning = true;
} 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);
}
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)];
}