787 lines
32 KiB
C++
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#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();
}