787 lines
32 KiB
C++
787 lines
32 KiB
C++
#include "ILedDisplayDevice.h"
|
||
|
||
#include <QApplication>
|
||
#include <QButtonGroup>
|
||
#include <QComboBox>
|
||
#include <QDateTime>
|
||
#include <QDoubleSpinBox>
|
||
#include <QFormLayout>
|
||
#include <QGridLayout>
|
||
#include <QGroupBox>
|
||
#include <QHBoxLayout>
|
||
#include <QHeaderView>
|
||
#include <QLabel>
|
||
#include <QLineEdit>
|
||
#include <QMainWindow>
|
||
#include <QMessageBox>
|
||
#include <QPlainTextEdit>
|
||
#include <QPushButton>
|
||
#include <QScrollArea>
|
||
#include <QSpinBox>
|
||
#include <QTableWidget>
|
||
#include <QTextCodec>
|
||
#include <QTimer>
|
||
#include <QVBoxLayout>
|
||
|
||
#include <memory>
|
||
#include <string>
|
||
#include <vector>
|
||
|
||
namespace {
|
||
|
||
struct GuideStateItem
|
||
{
|
||
int code;
|
||
const char* name;
|
||
const char* description;
|
||
bool danger = false;
|
||
};
|
||
|
||
const std::vector<GuideStateItem> kGuideStates = {
|
||
{1, "START", "开始引导"},
|
||
{2, "CAPTURE", "目标捕获"},
|
||
{5, "CENTER", "对中引导"},
|
||
{6, "SLOW", "减速提示"},
|
||
{7, "AZIMUTH", "方位引导"},
|
||
{8, "STOP", "到位停止", true},
|
||
{10, "TOO FAR", "停止过远"},
|
||
{15, "ID FAIL", "识别失败", true},
|
||
{16, "GATE BLOCK", "入口遮挡"},
|
||
{17, "VIEW BLOCK", "视野遮挡"},
|
||
{20, "STOP", "停止状态", true},
|
||
{21, "CHOCK ON", "轮挡到位"},
|
||
{22, "ERROR", "系统错误", true},
|
||
};
|
||
|
||
QString StatusName(ELedDisplayStatus status)
|
||
{
|
||
switch (status) {
|
||
case ELedDisplayStatus::Disconnected:
|
||
return QStringLiteral("未连接");
|
||
case ELedDisplayStatus::Connected:
|
||
return QStringLiteral("已连接");
|
||
case ELedDisplayStatus::Error:
|
||
return QStringLiteral("异常");
|
||
}
|
||
return QStringLiteral("未知");
|
||
}
|
||
|
||
class LedDisplayTestWindow final : public QMainWindow
|
||
{
|
||
public:
|
||
LedDisplayTestWindow()
|
||
{
|
||
setWindowTitle(QStringLiteral("LED 显示屏协议调试"));
|
||
setMinimumSize(1000, 700);
|
||
resize(1220, 820);
|
||
|
||
ILedDisplayDevice* rawDevice = nullptr;
|
||
if (ILedDisplayDevice::CreateObject(&rawDevice) == 0 && rawDevice) {
|
||
m_device.reset(rawDevice);
|
||
m_device->SetStatusCallback(
|
||
[](ELedDisplayStatus status,
|
||
const std::string& message,
|
||
void* user) {
|
||
auto* self = static_cast<LedDisplayTestWindow*>(user);
|
||
if (self) {
|
||
self->handleDeviceStatus(
|
||
status, QString::fromUtf8(message.c_str()));
|
||
}
|
||
},
|
||
this);
|
||
}
|
||
|
||
buildUi();
|
||
applyStyle();
|
||
setConnected(false);
|
||
appendLog(QStringLiteral("LED 协议调试工具已启动"));
|
||
}
|
||
|
||
~LedDisplayTestWindow() override
|
||
{
|
||
m_demoTimer.stop();
|
||
if (m_device) {
|
||
m_device->SetStatusCallback({}, nullptr);
|
||
m_device->Close();
|
||
}
|
||
}
|
||
|
||
private:
|
||
void buildUi()
|
||
{
|
||
auto* central = new QWidget(this);
|
||
auto* rootLayout = new QVBoxLayout(central);
|
||
rootLayout->setContentsMargins(20, 16, 20, 20);
|
||
rootLayout->setSpacing(14);
|
||
|
||
auto* headerLayout = new QHBoxLayout();
|
||
auto* titleLayout = new QVBoxLayout();
|
||
auto* title = new QLabel(QStringLiteral("LED 显示屏协议调试"), central);
|
||
title->setObjectName(QStringLiteral("pageTitle"));
|
||
auto* subtitle = new QLabel(
|
||
QStringLiteral("独立连接控制器并发送停机引导显示状态"), central);
|
||
subtitle->setObjectName(QStringLiteral("pageSubtitle"));
|
||
titleLayout->addWidget(title);
|
||
titleLayout->addWidget(subtitle);
|
||
headerLayout->addLayout(titleLayout);
|
||
headerLayout->addStretch();
|
||
|
||
m_statusDot = new QLabel(central);
|
||
m_statusDot->setFixedSize(12, 12);
|
||
m_statusText = new QLabel(QStringLiteral("未连接"), central);
|
||
m_statusText->setObjectName(QStringLiteral("connectionStatus"));
|
||
headerLayout->addWidget(m_statusDot);
|
||
headerLayout->addWidget(m_statusText);
|
||
rootLayout->addLayout(headerLayout);
|
||
|
||
auto* contentLayout = new QHBoxLayout();
|
||
contentLayout->setSpacing(14);
|
||
contentLayout->addWidget(buildSettingsPanel(), 0);
|
||
contentLayout->addWidget(buildCommandPanel(), 1);
|
||
rootLayout->addLayout(contentLayout, 1);
|
||
setCentralWidget(central);
|
||
}
|
||
|
||
QWidget* buildSettingsPanel()
|
||
{
|
||
auto* scrollArea = new QScrollArea(this);
|
||
scrollArea->setObjectName(QStringLiteral("settingsScroll"));
|
||
scrollArea->setWidgetResizable(true);
|
||
scrollArea->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
|
||
scrollArea->setFixedWidth(340);
|
||
|
||
auto* panel = new QWidget(scrollArea);
|
||
auto* layout = new QVBoxLayout(panel);
|
||
layout->setContentsMargins(0, 0, 8, 0);
|
||
layout->setSpacing(12);
|
||
|
||
auto* connectionGroup = new QGroupBox(QStringLiteral("连接配置"), panel);
|
||
auto* connectionForm = new QFormLayout(connectionGroup);
|
||
connectionForm->setFieldGrowthPolicy(QFormLayout::AllNonFixedFieldsGrow);
|
||
connectionForm->setLabelAlignment(Qt::AlignRight | Qt::AlignVCenter);
|
||
|
||
m_ipEdit = new QLineEdit(QStringLiteral("192.168.1.199"), connectionGroup);
|
||
m_portSpin = makeSpinBox(1, 65535, 5005, connectionGroup);
|
||
m_timeoutSpin = makeSpinBox(100, 60000, 1000, connectionGroup);
|
||
m_timeoutSpin->setSuffix(QStringLiteral(" ms"));
|
||
m_slaveIdEdit = new QLineEdit(QStringLiteral("0xFFFE"), connectionGroup);
|
||
m_controllerTypeEdit = new QLineEdit(QStringLiteral("0xFE"), connectionGroup);
|
||
|
||
connectionForm->addRow(QStringLiteral("控制器 IP"), m_ipEdit);
|
||
connectionForm->addRow(QStringLiteral("TCP 端口"), m_portSpin);
|
||
connectionForm->addRow(QStringLiteral("响应超时"), m_timeoutSpin);
|
||
connectionForm->addRow(QStringLiteral("从机地址"), m_slaveIdEdit);
|
||
connectionForm->addRow(QStringLiteral("控制器类型"), m_controllerTypeEdit);
|
||
|
||
auto* connectionButtons = new QHBoxLayout();
|
||
m_connectButton = new QPushButton(QStringLiteral("连接"), connectionGroup);
|
||
m_connectButton->setObjectName(QStringLiteral("primaryButton"));
|
||
m_disconnectButton = new QPushButton(QStringLiteral("断开"), connectionGroup);
|
||
connectionButtons->addWidget(m_connectButton);
|
||
connectionButtons->addWidget(m_disconnectButton);
|
||
connectionForm->addRow(connectionButtons);
|
||
layout->addWidget(connectionGroup);
|
||
|
||
auto* areaGroup = new QGroupBox(QStringLiteral("显示区域与动画"), panel);
|
||
auto* areaForm = new QFormLayout(areaGroup);
|
||
areaForm->setLabelAlignment(Qt::AlignRight | Qt::AlignVCenter);
|
||
m_areaXSpin = makeSpinBox(0, 32767, 0, areaGroup);
|
||
m_areaYSpin = makeSpinBox(0, 32767, 0, areaGroup);
|
||
m_areaWidthSpin = makeSpinBox(1, 32767, 64, areaGroup);
|
||
m_areaHeightSpin = makeSpinBox(1, 32767, 32, areaGroup);
|
||
m_startAddressSpin = makeSpinBox(0, 7, 0, areaGroup);
|
||
m_dynamicTimeoutSpin = makeSpinBox(0, 65535, 10, areaGroup);
|
||
m_dynamicTimeoutSpin->setSuffix(QStringLiteral(" s"));
|
||
m_modeSpin = makeSpinBox(1, 7, 1, areaGroup);
|
||
m_speedSpin = makeSpinBox(0, 24, 2, areaGroup);
|
||
m_staySpin = makeSpinBox(0, 255, 10, areaGroup);
|
||
|
||
areaForm->addRow(QStringLiteral("区域 X"), m_areaXSpin);
|
||
areaForm->addRow(QStringLiteral("区域 Y"), m_areaYSpin);
|
||
areaForm->addRow(QStringLiteral("区域宽度"), m_areaWidthSpin);
|
||
areaForm->addRow(QStringLiteral("区域高度"), m_areaHeightSpin);
|
||
areaForm->addRow(QStringLiteral("动态区编号"), m_startAddressSpin);
|
||
areaForm->addRow(QStringLiteral("动态超时"), m_dynamicTimeoutSpin);
|
||
areaForm->addRow(QStringLiteral("显示模式"), m_modeSpin);
|
||
areaForm->addRow(QStringLiteral("动画速度"), m_speedSpin);
|
||
areaForm->addRow(QStringLiteral("停留时间"), m_staySpin);
|
||
layout->addWidget(areaGroup);
|
||
layout->addStretch();
|
||
scrollArea->setWidget(panel);
|
||
|
||
connect(m_connectButton, &QPushButton::clicked,
|
||
this, [this]() { connectDevice(); });
|
||
connect(m_disconnectButton, &QPushButton::clicked,
|
||
this, [this]() { disconnectDevice(); });
|
||
return scrollArea;
|
||
}
|
||
|
||
QWidget* buildCommandPanel()
|
||
{
|
||
auto* panel = new QWidget(this);
|
||
auto* layout = new QVBoxLayout(panel);
|
||
layout->setContentsMargins(0, 0, 0, 0);
|
||
layout->setSpacing(12);
|
||
|
||
auto* dataGroup = new QGroupBox(QStringLiteral("测试数据"), panel);
|
||
auto* previewGroup = new QGroupBox(QStringLiteral("Screen Preview (one full-screen area)"), panel);
|
||
auto* previewLayout = new QVBoxLayout(previewGroup);
|
||
m_screenPreview = new QLabel(QStringLiteral("NO DATA"), previewGroup);
|
||
m_screenPreview->setObjectName(QStringLiteral("screenPreview"));
|
||
m_screenPreview->setAlignment(Qt::AlignCenter);
|
||
m_screenPreview->setWordWrap(true);
|
||
m_screenPreview->setMinimumHeight(190);
|
||
previewLayout->addWidget(m_screenPreview);
|
||
layout->addWidget(previewGroup);
|
||
|
||
auto* dataGrid = new QGridLayout(dataGroup);
|
||
m_modelEdit = new QLineEdit(QStringLiteral("A320"), dataGroup);
|
||
m_distanceSpin = makeDoubleSpinBox(-1000000.0, 1000000.0, 12500.0,
|
||
QStringLiteral(" mm"), dataGroup);
|
||
m_offsetSpin = makeDoubleSpinBox(-100000.0, 100000.0, 0.0,
|
||
QStringLiteral(" mm"), dataGroup);
|
||
m_angleSpin = makeDoubleSpinBox(-360.0, 360.0, 0.0,
|
||
QStringLiteral("°"), dataGroup);
|
||
m_errorCodeSpin = makeSpinBox(-999999, 999999, 22, dataGroup);
|
||
|
||
dataGrid->addWidget(new QLabel(QStringLiteral("机型"), dataGroup), 0, 0);
|
||
dataGrid->addWidget(m_modelEdit, 0, 1);
|
||
dataGrid->addWidget(new QLabel(QStringLiteral("距离"), dataGroup), 0, 2);
|
||
dataGrid->addWidget(m_distanceSpin, 0, 3);
|
||
dataGrid->addWidget(new QLabel(QStringLiteral("横向偏移"), dataGroup), 1, 0);
|
||
dataGrid->addWidget(m_offsetSpin, 1, 1);
|
||
dataGrid->addWidget(new QLabel(QStringLiteral("角度"), dataGroup), 1, 2);
|
||
dataGrid->addWidget(m_angleSpin, 1, 3);
|
||
dataGrid->addWidget(new QLabel(QStringLiteral("错误码"), dataGroup), 2, 0);
|
||
dataGrid->addWidget(m_errorCodeSpin, 2, 1);
|
||
dataGrid->setColumnStretch(1, 1);
|
||
dataGrid->setColumnStretch(3, 1);
|
||
layout->addWidget(dataGroup);
|
||
|
||
auto* stateGroup = new QGroupBox(QStringLiteral("标准引导状态"), panel);
|
||
auto* stateGrid = new QGridLayout(stateGroup);
|
||
stateGrid->setSpacing(8);
|
||
for (int index = 0; index < static_cast<int>(kGuideStates.size()); ++index) {
|
||
const GuideStateItem& item = kGuideStates[static_cast<size_t>(index)];
|
||
auto* button = new QPushButton(
|
||
QStringLiteral("%1\n%2")
|
||
.arg(QString::fromLatin1(item.name),
|
||
QString::fromUtf8(item.description)),
|
||
stateGroup);
|
||
button->setProperty("stateButton", true);
|
||
button->setProperty("danger", item.danger);
|
||
button->setMinimumHeight(54);
|
||
m_sendControls.push_back(button);
|
||
connect(button, &QPushButton::clicked, this,
|
||
[this, code = item.code]() { sendState(code); });
|
||
stateGrid->addWidget(button, index / 4, index % 4);
|
||
}
|
||
layout->addWidget(stateGroup);
|
||
|
||
auto* customGroup = new QGroupBox(QStringLiteral("自定义与批量测试"), panel);
|
||
auto* customLayout = new QGridLayout(customGroup);
|
||
m_customTextEdit = new QLineEdit(customGroup);
|
||
m_customTextEdit->setPlaceholderText(QStringLiteral("输入任意显示文本"));
|
||
m_sendTextButton = new QPushButton(QStringLiteral("发送文本"), customGroup);
|
||
m_blankButton = new QPushButton(QStringLiteral("清屏"), customGroup);
|
||
m_invalidButton = new QPushButton(QStringLiteral("发送 NO DATA"), customGroup);
|
||
m_demoButton = new QPushButton(QStringLiteral("启动演示"), customGroup);
|
||
m_screenOnButton = new QPushButton(QStringLiteral("开屏"), customGroup);
|
||
m_screenOffButton = new QPushButton(QStringLiteral("关屏"), customGroup);
|
||
m_demoButton->setObjectName(QStringLiteral("accentButton"));
|
||
m_demoIntervalSpin = makeSpinBox(100, 60000, 1000, customGroup);
|
||
m_demoIntervalSpin->setSuffix(QStringLiteral(" ms"));
|
||
|
||
customLayout->addWidget(m_customTextEdit, 0, 0, 1, 3);
|
||
customLayout->addWidget(m_sendTextButton, 0, 3);
|
||
customLayout->addWidget(m_blankButton, 1, 0);
|
||
customLayout->addWidget(m_invalidButton, 1, 1);
|
||
customLayout->addWidget(m_demoIntervalSpin, 1, 2);
|
||
customLayout->addWidget(m_demoButton, 1, 3);
|
||
customLayout->addWidget(m_screenOnButton, 2, 0);
|
||
customLayout->addWidget(m_screenOffButton, 2, 1);
|
||
layout->addWidget(customGroup);
|
||
|
||
m_sendControls.push_back(m_sendTextButton);
|
||
m_sendControls.push_back(m_blankButton);
|
||
m_sendControls.push_back(m_invalidButton);
|
||
m_sendControls.push_back(m_screenOnButton);
|
||
m_sendControls.push_back(m_screenOffButton);
|
||
m_sendControls.push_back(m_demoButton);
|
||
connect(m_sendTextButton, &QPushButton::clicked,
|
||
this, [this]() { sendCustomText(); });
|
||
connect(m_blankButton, &QPushButton::clicked,
|
||
this, [this]() { sendBlank(); });
|
||
connect(m_invalidButton, &QPushButton::clicked,
|
||
this, [this]() { sendInvalid(); });
|
||
connect(m_screenOnButton, &QPushButton::clicked,
|
||
this, [this]() { setScreenPower(true); });
|
||
connect(m_screenOffButton, &QPushButton::clicked,
|
||
this, [this]() { setScreenPower(false); });
|
||
connect(m_demoButton, &QPushButton::clicked,
|
||
this, [this]() { toggleDemo(); });
|
||
connect(&m_demoTimer, &QTimer::timeout,
|
||
this, [this]() { sendNextDemoState(); });
|
||
|
||
auto* logGroup = new QGroupBox(QStringLiteral("协议响应日志"), panel);
|
||
auto* logLayout = new QVBoxLayout(logGroup);
|
||
m_logEdit = new QPlainTextEdit(logGroup);
|
||
m_logEdit->setReadOnly(true);
|
||
m_logEdit->setMaximumBlockCount(1000);
|
||
auto* clearLogButton = new QPushButton(QStringLiteral("清空日志"), logGroup);
|
||
clearLogButton->setFixedWidth(96);
|
||
auto* logToolbar = new QHBoxLayout();
|
||
logToolbar->addStretch();
|
||
logToolbar->addWidget(clearLogButton);
|
||
logLayout->addLayout(logToolbar);
|
||
logLayout->addWidget(m_logEdit, 1);
|
||
connect(clearLogButton, &QPushButton::clicked,
|
||
m_logEdit, &QPlainTextEdit::clear);
|
||
layout->addWidget(logGroup, 1);
|
||
return panel;
|
||
}
|
||
|
||
static QSpinBox* makeSpinBox(int minimum,
|
||
int maximum,
|
||
int value,
|
||
QWidget* parent)
|
||
{
|
||
auto* spin = new QSpinBox(parent);
|
||
spin->setRange(minimum, maximum);
|
||
spin->setValue(value);
|
||
spin->setAccelerated(true);
|
||
return spin;
|
||
}
|
||
|
||
static QDoubleSpinBox* makeDoubleSpinBox(double minimum,
|
||
double maximum,
|
||
double value,
|
||
const QString& suffix,
|
||
QWidget* parent)
|
||
{
|
||
auto* spin = new QDoubleSpinBox(parent);
|
||
spin->setRange(minimum, maximum);
|
||
spin->setDecimals(1);
|
||
spin->setValue(value);
|
||
spin->setSuffix(suffix);
|
||
spin->setAccelerated(true);
|
||
return spin;
|
||
}
|
||
|
||
void applyStyle()
|
||
{
|
||
setStyleSheet(QStringLiteral(R"(
|
||
QMainWindow, QWidget { background: #f4f6f8; color: #20252b; font-family: "Microsoft YaHei UI"; font-size: 13px; }
|
||
QLabel#pageTitle { font-size: 24px; font-weight: 700; color: #182028; }
|
||
QLabel#pageSubtitle { color: #68737d; font-size: 13px; }
|
||
QLabel#connectionStatus { font-size: 14px; font-weight: 600; }
|
||
QGroupBox { background: #ffffff; border: 1px solid #d7dde3; border-radius: 6px; margin-top: 14px; padding: 14px 12px 12px 12px; font-weight: 600; }
|
||
QGroupBox::title { subcontrol-origin: margin; left: 12px; padding: 0 5px; color: #35404a; }
|
||
QLineEdit, QSpinBox, QDoubleSpinBox, QComboBox { min-height: 32px; background: #ffffff; border: 1px solid #bfc8d0; border-radius: 4px; padding: 0 8px; selection-background-color: #2474a8; }
|
||
QLineEdit:focus, QSpinBox:focus, QDoubleSpinBox:focus { border: 1px solid #2474a8; }
|
||
QPushButton { min-height: 34px; background: #eef1f3; border: 1px solid #bfc8d0; border-radius: 4px; padding: 0 12px; font-weight: 600; }
|
||
QPushButton:hover { background: #e2e7eb; }
|
||
QPushButton:pressed { background: #d5dce2; }
|
||
QPushButton:disabled { color: #9aa3aa; background: #f1f3f4; border-color: #dce1e5; }
|
||
QPushButton#primaryButton { color: white; background: #2474a8; border-color: #2474a8; }
|
||
QPushButton#primaryButton:hover { background: #1d628f; }
|
||
QPushButton#accentButton { color: white; background: #2d7d56; border-color: #2d7d56; }
|
||
QPushButton#accentButton:hover { background: #246746; }
|
||
QPushButton[stateButton="true"] { text-align: left; padding: 5px 10px; background: #f8fafb; }
|
||
QPushButton[stateButton="true"]:hover { background: #e8f1f6; border-color: #6d9dbb; }
|
||
QPushButton[stateButton="true"][danger="true"] { background: #fff6f4; border-color: #dfafa6; color: #8c3024; }
|
||
QPushButton[stateButton="true"][danger="true"]:hover { background: #fde8e4; }
|
||
QPlainTextEdit { background: #171b1f; color: #d9e2e8; border: 1px solid #2c3339; border-radius: 4px; font-family: Consolas; font-size: 12px; padding: 8px; }
|
||
QLabel#screenPreview { background: #101820; color: #67f3a1; border: 2px solid #344452; border-radius: 8px; font-family: Consolas; font-size: 28px; font-weight: 700; padding: 18px; }
|
||
QScrollArea#settingsScroll { border: none; background: transparent; }
|
||
QScrollArea#settingsScroll > QWidget > QWidget { background: transparent; }
|
||
)"));
|
||
}
|
||
|
||
bool parseHexField(QLineEdit* edit,
|
||
const QString& fieldName,
|
||
int maximum,
|
||
int& value)
|
||
{
|
||
bool ok = false;
|
||
value = edit->text().trimmed().toInt(&ok, 0);
|
||
if (!ok || value < 0 || value > maximum) {
|
||
QMessageBox::warning(
|
||
this,
|
||
QStringLiteral("参数错误"),
|
||
QStringLiteral("%1 必须是 0 到 0x%2 之间的十进制或十六进制数。")
|
||
.arg(fieldName)
|
||
.arg(maximum, 0, 16));
|
||
edit->setFocus();
|
||
return false;
|
||
}
|
||
return true;
|
||
}
|
||
|
||
bool readConfig(LedDisplayConfig& config)
|
||
{
|
||
int slaveId = 0;
|
||
int controllerType = 0;
|
||
if (m_ipEdit->text().trimmed().isEmpty()) {
|
||
QMessageBox::warning(this, QStringLiteral("参数错误"),
|
||
QStringLiteral("请输入控制器 IP。"));
|
||
m_ipEdit->setFocus();
|
||
return false;
|
||
}
|
||
if (!parseHexField(m_slaveIdEdit, QStringLiteral("从机地址"),
|
||
0xFFFF, slaveId) ||
|
||
!parseHexField(m_controllerTypeEdit, QStringLiteral("控制器类型"),
|
||
0xFF, controllerType)) {
|
||
return false;
|
||
}
|
||
|
||
config.enabled = true;
|
||
config.ip = m_ipEdit->text().trimmed().toStdString();
|
||
config.port = m_portSpin->value();
|
||
config.timeoutMs = m_timeoutSpin->value();
|
||
config.slaveId = slaveId;
|
||
config.controllerType = controllerType;
|
||
config.startAddress = m_startAddressSpin->value();
|
||
config.areaX = m_areaXSpin->value();
|
||
config.areaY = m_areaYSpin->value();
|
||
config.areaWidth = m_areaWidthSpin->value();
|
||
config.areaHeight = m_areaHeightSpin->value();
|
||
config.dynamicTimeoutSec = m_dynamicTimeoutSpin->value();
|
||
config.displayMode = m_modeSpin->value();
|
||
config.speed = m_speedSpin->value();
|
||
config.stayTime = m_staySpin->value();
|
||
return true;
|
||
}
|
||
|
||
void connectDevice()
|
||
{
|
||
if (!m_device) {
|
||
QMessageBox::critical(this, QStringLiteral("初始化失败"),
|
||
QStringLiteral("无法创建设备对象。"));
|
||
return;
|
||
}
|
||
|
||
LedDisplayConfig config;
|
||
if (!readConfig(config)) {
|
||
return;
|
||
}
|
||
appendLog(QStringLiteral("连接 %1:%2,超时 %3 ms")
|
||
.arg(QString::fromStdString(config.ip))
|
||
.arg(config.port)
|
||
.arg(config.timeoutMs));
|
||
QApplication::setOverrideCursor(Qt::WaitCursor);
|
||
const int result = m_device->Open(config);
|
||
QApplication::restoreOverrideCursor();
|
||
const bool connected = result == 0 && m_device->IsConnected();
|
||
setConnected(connected);
|
||
if (connected) {
|
||
m_screenPoweredOn = true;
|
||
if (m_screenPreview) {
|
||
m_screenPreview->setText(m_lastPreviewText);
|
||
}
|
||
}
|
||
appendLog(QStringLiteral("OPEN return=%1,connected=%2")
|
||
.arg(result)
|
||
.arg(connected ? QStringLiteral("yes")
|
||
: QStringLiteral("no")));
|
||
}
|
||
|
||
void disconnectDevice()
|
||
{
|
||
stopDemo();
|
||
if (m_device) {
|
||
m_device->Close();
|
||
}
|
||
setConnected(false);
|
||
appendLog(QStringLiteral("连接已断开"));
|
||
}
|
||
|
||
LedDisplayResult makeResult(int stateCode) const
|
||
{
|
||
LedDisplayResult result;
|
||
result.valid = true;
|
||
result.guideCode = stateCode == 22 ? 2 : 1;
|
||
result.guideStateCode = stateCode;
|
||
result.errorCode = m_errorCodeSpin->value();
|
||
result.modelType = m_modelEdit->text().trimmed().toStdString();
|
||
result.distance = static_cast<float>(m_distanceSpin->value());
|
||
result.lateralOffset = static_cast<float>(m_offsetSpin->value());
|
||
result.angle = static_cast<float>(m_angleSpin->value());
|
||
return result;
|
||
}
|
||
|
||
void sendState(int stateCode)
|
||
{
|
||
sendResult(makeResult(stateCode),
|
||
QStringLiteral("state=%1").arg(stateCode));
|
||
}
|
||
|
||
QString previewText(const LedDisplayResult& result) const
|
||
{
|
||
if (result.blank) {
|
||
return QString();
|
||
}
|
||
if (!result.valid) {
|
||
return QStringLiteral("NO DATA");
|
||
}
|
||
if (!result.stateText.empty() && result.guideStateCode == 0) {
|
||
return QString::fromLocal8Bit(result.stateText.data(),
|
||
static_cast<int>(result.stateText.size()));
|
||
}
|
||
|
||
switch (result.guideStateCode) {
|
||
case 1: return QStringLiteral("START\n%1").arg(QString::fromStdString(result.modelType));
|
||
case 2: return QStringLiteral("CAPTURE\n%1").arg(QString::fromStdString(result.modelType));
|
||
case 5: return QStringLiteral("CENTER\n%1m\n%2")
|
||
.arg(result.distance / 1000.0, 0, 'f', 1)
|
||
.arg(QString::fromStdString(result.modelType));
|
||
case 6: return QStringLiteral("SLOW\n%1m")
|
||
.arg(result.distance / 1000.0, 0, 'f', 1);
|
||
case 7: return QStringLiteral("AZIMUTH\n%1m\nL:%2")
|
||
.arg(result.distance / 1000.0, 0, 'f', 1)
|
||
.arg(result.lateralOffset, 0, 'f', 0);
|
||
case 8:
|
||
case 20: return QStringLiteral("STOP");
|
||
case 10: return QStringLiteral("TOO FAR");
|
||
case 15: return QStringLiteral("STOP\nID FAIL");
|
||
case 16: return QStringLiteral("WAIT\nGATE BLOCK");
|
||
case 17: return QStringLiteral("WAIT\nVIEW BLOCK");
|
||
case 21: return QStringLiteral("CHOCK ON");
|
||
case 22: return QStringLiteral("ERROR\nE:%1").arg(result.errorCode);
|
||
default:
|
||
return result.stateText.empty()
|
||
? QStringLiteral("OK")
|
||
: QString::fromLocal8Bit(result.stateText.data(),
|
||
static_cast<int>(result.stateText.size()));
|
||
}
|
||
}
|
||
|
||
void sendCustomText()
|
||
{
|
||
const QString text = m_customTextEdit->text();
|
||
if (text.isEmpty()) {
|
||
QMessageBox::information(this, QStringLiteral("自定义文本"),
|
||
QStringLiteral("请先输入要显示的文本。"));
|
||
return;
|
||
}
|
||
LedDisplayResult result = makeResult(0);
|
||
QTextCodec* gbk = QTextCodec::codecForName("GBK");
|
||
const QByteArray encoded = gbk ? gbk->fromUnicode(text)
|
||
: text.toLocal8Bit();
|
||
result.stateText.assign(encoded.constData(),
|
||
static_cast<size_t>(encoded.size()));
|
||
sendResult(result, QStringLiteral("text=\"%1\"").arg(text));
|
||
}
|
||
|
||
void sendBlank()
|
||
{
|
||
LedDisplayResult result;
|
||
result.valid = true;
|
||
result.blank = true;
|
||
sendResult(result, QStringLiteral("blank"));
|
||
}
|
||
|
||
void sendInvalid()
|
||
{
|
||
sendResult(LedDisplayResult(), QStringLiteral("invalid / NO DATA"));
|
||
}
|
||
|
||
void setScreenPower(bool on)
|
||
{
|
||
if (!m_device || !m_device->IsConnected()) {
|
||
setConnected(false);
|
||
appendLog(QStringLiteral("Screen power command cancelled: device is not connected"));
|
||
return;
|
||
}
|
||
|
||
QApplication::setOverrideCursor(Qt::WaitCursor);
|
||
const int returnCode = on ? m_device->OpenScreen()
|
||
: m_device->CloseScreen();
|
||
QApplication::restoreOverrideCursor();
|
||
if (returnCode == 0) {
|
||
m_screenPoweredOn = on;
|
||
if (m_screenPreview) {
|
||
m_screenPreview->setText(on ? m_lastPreviewText
|
||
: QStringLiteral("SCREEN OFF"));
|
||
}
|
||
appendLog(on ? QStringLiteral("Screen ON accepted")
|
||
: QStringLiteral("Screen OFF accepted"));
|
||
} else {
|
||
appendLog(QStringLiteral("Screen power command failed, return=%1")
|
||
.arg(returnCode));
|
||
if (!m_device->IsConnected()) {
|
||
setConnected(false);
|
||
}
|
||
}
|
||
}
|
||
|
||
bool sendResult(const LedDisplayResult& result, const QString& description)
|
||
{
|
||
// The simulator models the physical screen as one full-screen
|
||
// dynamic area. Keep it useful even before a controller is connected.
|
||
m_lastPreviewText = previewText(result);
|
||
if (m_screenPreview && m_screenPoweredOn) {
|
||
m_screenPreview->setText(m_lastPreviewText);
|
||
}
|
||
if (!m_device || !m_device->IsConnected()) {
|
||
setConnected(false);
|
||
appendLog(QStringLiteral("发送已取消:设备未连接"));
|
||
return false;
|
||
}
|
||
|
||
appendLog(QStringLiteral("SEND %1").arg(description));
|
||
QApplication::setOverrideCursor(Qt::WaitCursor);
|
||
const int returnCode = m_device->SendResult(result);
|
||
QApplication::restoreOverrideCursor();
|
||
const bool success = returnCode == 0 && m_device->IsConnected();
|
||
appendLog(QStringLiteral("%1 return=%2,connected=%3")
|
||
.arg(success ? QStringLiteral("控制器已接收(实际显示需现场确认)")
|
||
: QStringLiteral("FAILED"))
|
||
.arg(returnCode)
|
||
.arg(success ? QStringLiteral("yes")
|
||
: QStringLiteral("no")));
|
||
if (!success) {
|
||
stopDemo();
|
||
setConnected(false);
|
||
}
|
||
return success;
|
||
}
|
||
|
||
void toggleDemo()
|
||
{
|
||
if (m_demoTimer.isActive()) {
|
||
stopDemo();
|
||
appendLog(QStringLiteral("自动演示已停止"));
|
||
return;
|
||
}
|
||
if (!m_device || !m_device->IsConnected()) {
|
||
setConnected(false);
|
||
return;
|
||
}
|
||
m_demoIndex = 0;
|
||
m_demoTimer.setInterval(m_demoIntervalSpin->value());
|
||
m_demoTimer.start();
|
||
m_demoButton->setText(QStringLiteral("停止演示"));
|
||
appendLog(QStringLiteral("自动演示已启动,间隔 %1 ms")
|
||
.arg(m_demoTimer.interval()));
|
||
sendNextDemoState();
|
||
}
|
||
|
||
void sendNextDemoState()
|
||
{
|
||
if (kGuideStates.empty()) {
|
||
return;
|
||
}
|
||
const GuideStateItem& item =
|
||
kGuideStates[static_cast<size_t>(m_demoIndex)];
|
||
if (!sendResult(makeResult(item.code),
|
||
QStringLiteral("demo state=%1").arg(item.code))) {
|
||
return;
|
||
}
|
||
m_demoIndex = (m_demoIndex + 1) % static_cast<int>(kGuideStates.size());
|
||
}
|
||
|
||
void stopDemo()
|
||
{
|
||
m_demoTimer.stop();
|
||
if (m_demoButton) {
|
||
m_demoButton->setText(QStringLiteral("启动演示"));
|
||
}
|
||
}
|
||
|
||
void handleDeviceStatus(ELedDisplayStatus status, const QString& message)
|
||
{
|
||
appendLog(QStringLiteral("STATUS %1%2")
|
||
.arg(StatusName(status),
|
||
message.isEmpty()
|
||
? QString()
|
||
: QStringLiteral(":%1").arg(message)));
|
||
}
|
||
|
||
void setConnected(bool connected)
|
||
{
|
||
m_connected = connected;
|
||
m_statusText->setText(connected ? QStringLiteral("已连接")
|
||
: QStringLiteral("未连接"));
|
||
m_statusDot->setStyleSheet(
|
||
connected
|
||
? QStringLiteral("background: #2d8a5f; border-radius: 6px;")
|
||
: QStringLiteral("background: #a6afb7; border-radius: 6px;"));
|
||
m_connectButton->setEnabled(!connected);
|
||
m_disconnectButton->setEnabled(connected);
|
||
for (QPushButton* button : m_sendControls) {
|
||
button->setEnabled(connected);
|
||
}
|
||
if (!connected) {
|
||
stopDemo();
|
||
}
|
||
}
|
||
|
||
void appendLog(const QString& message)
|
||
{
|
||
if (!m_logEdit) {
|
||
return;
|
||
}
|
||
m_logEdit->appendPlainText(
|
||
QStringLiteral("[%1] %2")
|
||
.arg(QDateTime::currentDateTime().toString(
|
||
QStringLiteral("HH:mm:ss.zzz")),
|
||
message));
|
||
}
|
||
|
||
private:
|
||
std::unique_ptr<ILedDisplayDevice> m_device;
|
||
bool m_connected = false;
|
||
QLabel* m_statusDot = nullptr;
|
||
QLabel* m_statusText = nullptr;
|
||
QLineEdit* m_ipEdit = nullptr;
|
||
QSpinBox* m_portSpin = nullptr;
|
||
QSpinBox* m_timeoutSpin = nullptr;
|
||
QLineEdit* m_slaveIdEdit = nullptr;
|
||
QLineEdit* m_controllerTypeEdit = nullptr;
|
||
QSpinBox* m_areaXSpin = nullptr;
|
||
QSpinBox* m_areaYSpin = nullptr;
|
||
QSpinBox* m_areaWidthSpin = nullptr;
|
||
QSpinBox* m_areaHeightSpin = nullptr;
|
||
QSpinBox* m_startAddressSpin = nullptr;
|
||
QSpinBox* m_dynamicTimeoutSpin = nullptr;
|
||
QSpinBox* m_modeSpin = nullptr;
|
||
QSpinBox* m_speedSpin = nullptr;
|
||
QSpinBox* m_staySpin = nullptr;
|
||
QLineEdit* m_modelEdit = nullptr;
|
||
QDoubleSpinBox* m_distanceSpin = nullptr;
|
||
QDoubleSpinBox* m_offsetSpin = nullptr;
|
||
QDoubleSpinBox* m_angleSpin = nullptr;
|
||
QSpinBox* m_errorCodeSpin = nullptr;
|
||
QLineEdit* m_customTextEdit = nullptr;
|
||
QSpinBox* m_demoIntervalSpin = nullptr;
|
||
QPushButton* m_connectButton = nullptr;
|
||
QPushButton* m_disconnectButton = nullptr;
|
||
QPushButton* m_sendTextButton = nullptr;
|
||
QPushButton* m_blankButton = nullptr;
|
||
QPushButton* m_invalidButton = nullptr;
|
||
QPushButton* m_screenOnButton = nullptr;
|
||
QPushButton* m_screenOffButton = nullptr;
|
||
QPushButton* m_demoButton = nullptr;
|
||
QPlainTextEdit* m_logEdit = nullptr;
|
||
QLabel* m_screenPreview = nullptr;
|
||
QString m_lastPreviewText = QStringLiteral("NO DATA");
|
||
bool m_screenPoweredOn = true;
|
||
std::vector<QPushButton*> m_sendControls;
|
||
QTimer m_demoTimer;
|
||
int m_demoIndex = 0;
|
||
};
|
||
|
||
} // namespace
|
||
|
||
int main(int argc, char* argv[])
|
||
{
|
||
QApplication app(argc, argv);
|
||
QApplication::setApplicationName(QStringLiteral("LedDisplayDeviceTest"));
|
||
QApplication::setApplicationDisplayName(QStringLiteral("LED 显示屏协议调试"));
|
||
|
||
LedDisplayTestWindow window;
|
||
window.show();
|
||
return app.exec();
|
||
}
|