瀏覽代碼

fix: 修正PLC串口中断状态

main
suyu 1 月之前
父節點
當前提交
46e825cee3
共有 7 個檔案被更改,包括 54 行新增68 行删除
  1. +2
    -25
      app/src/infrastructure/plc_communication_error_classifier.cpp
  2. +0
    -2
      app/src/infrastructure/plc_communication_error_classifier.h
  3. +30
    -22
      app/src/infrastructure/plc_communication_service.cpp
  4. +1
    -1
      app/src/infrastructure/plc_communication_service.h
  5. +8
    -0
      app/src/ui/main_window.cpp
  6. +10
    -12
      app/tests/main_window_tests.cpp
  7. +3
    -6
      app/tests/plc_runtime_tests.cpp

+ 2
- 25
app/src/infrastructure/plc_communication_error_classifier.cpp 查看文件

@@ -8,18 +8,6 @@ QString displayPortName(const QString &port_name)
return trimmed.isEmpty() ? QStringLiteral("当前端口") : trimmed;
}

bool nativeErrorReportsRemovedDevice(const QString &native_error)
{
const QString error = native_error.toLower();
return error.contains(QStringLiteral("device has been disconnected"))
|| error.contains(QStringLiteral("device is not connected"))
|| error.contains(QStringLiteral("device not found"))
|| error.contains(QStringLiteral("no such file"))
|| error.contains(QStringLiteral("设备已断开"))
|| error.contains(QStringLiteral("设备不存在"))
|| error.contains(QStringLiteral("找不到指定的文件"));
}

} // namespace

PlcCommunicationFailure classifyPlcCommunicationError(
@@ -27,18 +15,6 @@ PlcCommunicationFailure classifyPlcCommunicationError(
const PlcCommunicationErrorContext &context)
{
const QString port_name = displayPortName(context.portName);
const bool adapter_removed = context.serialSessionOpened
&& (!context.portAvailable
|| nativeErrorReportsRemovedDevice(context.nativeError));
if (adapter_removed
&& error != QModbusDevice::ConfigurationError
&& error != QModbusDevice::ProtocolError)
{
return {
PlcCommunicationError::UsbSerialAdapterRemoved,
QStringLiteral("USB 转串口 %1 已从电脑移除;本地串口会话已失效")
.arg(port_name)};
}

switch (error)
{
@@ -54,7 +30,8 @@ PlcCommunicationFailure classifyPlcCommunicationError(
}
return {
PlcCommunicationError::SerialConnectionLost,
QStringLiteral("PLC 串口连接异常,本地端口 %1 仍存在;请重新连接")
QStringLiteral(
"PLC 串口 %1 连接已中断;请检查 USB 转串口是否被拔出或已经失效")
.arg(port_name)};
}
case QModbusDevice::TimeoutError:


+ 0
- 2
app/src/infrastructure/plc_communication_error_classifier.h 查看文件

@@ -8,9 +8,7 @@
struct PlcCommunicationErrorContext
{
QString portName;
QString nativeError;
bool serialSessionOpened = false;
bool portAvailable = false;
bool receivedValidResponse = false;
};



+ 30
- 22
app/src/infrastructure/plc_communication_service.cpp 查看文件

@@ -8,7 +8,6 @@
#include <QModbusReply>
#include <QModbusRtuSerialMaster>
#include <QSerialPort>
#include <QSerialPortInfo>
#include <QVariant>

#include <algorithm>
@@ -71,7 +70,11 @@ PlcCommunicationService::PlcCommunicationService(
{
serial_session_opened_ = false;
}
if (!disconnecting_ && state_ != PlcConnectionState::Faulted)
else if (serial_session_opened_)
{
handleUnexpectedDisconnect();
}
else if (state_ != PlcConnectionState::Faulted)
{
setState(PlcConnectionState::Disconnected);
}
@@ -152,6 +155,8 @@ void PlcCommunicationService::disconnectDevice()
received_valid_response_ = false;
repository_.invalidate();
updateInitialReadCompleted(false, true);
last_error_type_ = PlcCommunicationError::None;
last_error_.clear();
setState(PlcConnectionState::Disconnected);
disconnecting_ = false;
}
@@ -353,6 +358,7 @@ void PlcCommunicationService::handleReadFinished(QModbusReply *reply, PollBlock
}
}
received_valid_response_ = true;
last_error_type_ = PlcCommunicationError::None;
last_error_.clear();
emit cacheUpdated();
if (cache_updated_callback_)
@@ -427,23 +433,6 @@ RegisterWriteResult PlcCommunicationService::sendWordWrite(
return {true, RegisterError::None};
}

bool PlcCommunicationService::configuredPortAvailable() const
{
const QString configured_port = QString::fromStdString(
configuration_.portName).trimmed();
if (configured_port.isEmpty())
{
return false;
}
const QList<QSerialPortInfo> ports = QSerialPortInfo::availablePorts();
return std::any_of(
ports.cbegin(), ports.cend(),
[&configured_port](const QSerialPortInfo &port)
{
return port.portName().compare(configured_port, Qt::CaseInsensitive) == 0;
});
}

void PlcCommunicationService::updateInitialReadCompleted(
bool completed, bool force_notification)
{
@@ -459,20 +448,31 @@ void PlcCommunicationService::updateInitialReadCompleted(
}
}

void PlcCommunicationService::handleUnexpectedDisconnect()
{
serial_session_opened_ = false;
const QString port_name = QString::fromStdString(configuration_.portName).trimmed();
setError({
PlcCommunicationError::SerialConnectionLost,
QStringLiteral(
"PLC 串口 %1 连接已中断;请检查 USB 转串口是否被拔出或已经失效")
.arg(port_name)});
}

void PlcCommunicationService::handleModbusError(QModbusDevice::Error error)
{
if (disconnecting_
|| (error == QModbusDevice::ReplyAbortedError
&& state_ == PlcConnectionState::Disconnected)
|| (state_ == PlcConnectionState::Disconnected
&& last_error_type_ == PlcCommunicationError::SerialConnectionLost)
|| state_ == PlcConnectionState::Faulted)
{
return;
}
const PlcCommunicationErrorContext context{
QString::fromStdString(configuration_.portName),
master_->errorString(),
serial_session_opened_,
configuredPortAvailable(),
received_valid_response_};
setError(classifyPlcCommunicationError(error, context));
}
@@ -497,7 +497,15 @@ void PlcCommunicationService::setError(const PlcCommunicationFailure &failure)
last_error_ = toUtf8(failure.message);
poll_timer_.stop();
updateInitialReadCompleted(false);
setState(PlcConnectionState::Faulted);
const bool disconnected = failure.type == PlcCommunicationError::SerialPortOpenFailed
|| failure.type == PlcCommunicationError::SerialConnectionLost
|| failure.type == PlcCommunicationError::UsbSerialAdapterRemoved;
if (disconnected)
{
serial_session_opened_ = false;
}
setState(disconnected
? PlcConnectionState::Disconnected : PlcConnectionState::Faulted);
emit communicationError(failure.message);
if (error_reported_callback_)
{


+ 1
- 1
app/src/infrastructure/plc_communication_service.h 查看文件

@@ -64,8 +64,8 @@ private:
RegisterWriteResult sendBitWrite(const RegisterAddress &address, bool value);
RegisterWriteResult sendWordWrite(
const RegisterAddress &address, std::int16_t value);
bool configuredPortAvailable() const;
void updateInitialReadCompleted(bool completed, bool force_notification = false);
void handleUnexpectedDisconnect();
void handleModbusError(QModbusDevice::Error error);
void setState(PlcConnectionState state);
void setError(const PlcCommunicationFailure &failure);


+ 8
- 0
app/src/ui/main_window.cpp 查看文件

@@ -121,9 +121,17 @@ QString plcStatusText(const RuntimeModeService &service)
: MainWindow::tr("PLC 通信故障:%1").arg(error);
}
case PlcConnectionState::Disconnected:
{
const QString error = fromUtf8(service.plcError());
return error.isEmpty()
? MainWindow::tr("PLC 已断开")
: MainWindow::tr("PLC 已断开:%1").arg(error);
}
default:
{
return MainWindow::tr("PLC 已断开");
}
}
}

QString controlTypeText(HmiControlType type)


+ 10
- 12
app/tests/main_window_tests.cpp 查看文件

@@ -169,13 +169,12 @@ public:
}
}

void failCommunication(
PlcCommunicationError error_type, const std::string &message)
void loseSerialConnection(const std::string &message)
{
initial_read = false;
last_error_type = error_type;
last_error_type = PlcCommunicationError::SerialConnectionLost;
last_error = message;
connection_state = PlcConnectionState::Faulted;
connection_state = PlcConnectionState::Disconnected;
if (initial_read_changed)
{
initial_read_changed(false);
@@ -684,21 +683,20 @@ void testOnlineWorkspaceShowsHmiAndFreeMonitorOnly()
QAction *configure_action = requiredChild<QAction>(window, "configurePlcAction");
QAction *disconnect_action = requiredChild<QAction>(window, "disconnectPlcAction");
QListWidget *output = requiredChild<QListWidget>(window, "outputList");
gateway.failCommunication(
PlcCommunicationError::CommunicationTimeout,
"PLC 通信超时,本地串口 COM9 仍处于打开状态;请检查 PLC 供电和 RS-485 接线");
gateway.loseSerialConnection(
"PLC 串口 COM9 连接已中断;请检查 USB 转串口是否被拔出或已经失效");
QApplication::processEvents();

require(mode_service.mode() == ApplicationMode::Editing,
"a communication fault must return the UI from online running to editing");
require(configure_action->isEnabled()
&& configure_action->text() == QStringLiteral("PLC 重新配置"),
"faulted PLC state must expose direct reconfiguration");
require(disconnect_action->isEnabled(),
"faulted PLC state must still allow explicit serial session cleanup");
&& configure_action->text() == QStringLiteral("PLC 配置"),
"a lost serial connection must expose direct configuration");
require(!disconnect_action->isEnabled(),
"a lost serial connection must disable redundant disconnect actions");
require(output->count() > 0
&& output->item(output->count() - 1)->text().contains(
QStringLiteral("PLC 通信超时")),
QStringLiteral("连接已中断")),
"communication fault details must remain in the output log");

online_action->trigger();


+ 3
- 6
app/tests/plc_runtime_tests.cpp 查看文件

@@ -289,7 +289,6 @@ void testPlcCommunicationErrorClassification()
"connection failure before opening the port must be classified explicitly");

context.serialSessionOpened = true;
context.portAvailable = true;
failure = classifyPlcCommunicationError(QModbusDevice::TimeoutError, context);
require(failure.type == PlcCommunicationError::PlcNotResponding
&& failure.message.contains(QStringLiteral("PLC 未响应")),
@@ -301,13 +300,11 @@ void testPlcCommunicationErrorClassification()
&& failure.message.contains(QStringLiteral("仍处于打开状态")),
"timeout after valid traffic must report an open local serial session");

context.portAvailable = false;
failure = classifyPlcCommunicationError(QModbusDevice::ConnectionError, context);
require(failure.type == PlcCommunicationError::UsbSerialAdapterRemoved
&& failure.message.contains(QStringLiteral("已从电脑移除")),
"a missing port after connection must report USB serial adapter removal");
require(failure.type == PlcCommunicationError::SerialConnectionLost
&& failure.message.contains(QStringLiteral("连接已中断")),
"connection errors after opening the port must report a lost serial link");

context.portAvailable = true;
failure = classifyPlcCommunicationError(QModbusDevice::ProtocolError, context);
require(failure.type == PlcCommunicationError::ProtocolError
&& failure.message.contains(QStringLiteral("Modbus 协议异常")),


Loading…
取消
儲存