From 46e825cee38d73822bb2fc2f3ebf73e7b3b1e9c6 Mon Sep 17 00:00:00 2001 From: suyu <1643689728@qq.com> Date: Fri, 14 Aug 2026 10:49:26 +0800 Subject: [PATCH] =?UTF-8?q?fix:=20=E4=BF=AE=E6=AD=A3PLC=E4=B8=B2=E5=8F=A3?= =?UTF-8?q?=E4=B8=AD=E6=96=AD=E7=8A=B6=E6=80=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../plc_communication_error_classifier.cpp | 27 +--------- .../plc_communication_error_classifier.h | 2 - .../plc_communication_service.cpp | 52 +++++++++++-------- .../plc_communication_service.h | 2 +- app/src/ui/main_window.cpp | 8 +++ app/tests/main_window_tests.cpp | 22 ++++---- app/tests/plc_runtime_tests.cpp | 9 ++-- 7 files changed, 54 insertions(+), 68 deletions(-) diff --git a/app/src/infrastructure/plc_communication_error_classifier.cpp b/app/src/infrastructure/plc_communication_error_classifier.cpp index 227d6d3..93da1e0 100644 --- a/app/src/infrastructure/plc_communication_error_classifier.cpp +++ b/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: diff --git a/app/src/infrastructure/plc_communication_error_classifier.h b/app/src/infrastructure/plc_communication_error_classifier.h index d99c634..ceeefd1 100644 --- a/app/src/infrastructure/plc_communication_error_classifier.h +++ b/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; }; diff --git a/app/src/infrastructure/plc_communication_service.cpp b/app/src/infrastructure/plc_communication_service.cpp index eb664fb..8cd36ec 100644 --- a/app/src/infrastructure/plc_communication_service.cpp +++ b/app/src/infrastructure/plc_communication_service.cpp @@ -8,7 +8,6 @@ #include #include #include -#include #include #include @@ -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 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_) { diff --git a/app/src/infrastructure/plc_communication_service.h b/app/src/infrastructure/plc_communication_service.h index 9dbb0b2..6b11585 100644 --- a/app/src/infrastructure/plc_communication_service.h +++ b/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); diff --git a/app/src/ui/main_window.cpp b/app/src/ui/main_window.cpp index a365653..a94fde7 100644 --- a/app/src/ui/main_window.cpp +++ b/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) diff --git a/app/tests/main_window_tests.cpp b/app/tests/main_window_tests.cpp index 154051b..796b937 100644 --- a/app/tests/main_window_tests.cpp +++ b/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(window, "configurePlcAction"); QAction *disconnect_action = requiredChild(window, "disconnectPlcAction"); QListWidget *output = requiredChild(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(); diff --git a/app/tests/plc_runtime_tests.cpp b/app/tests/plc_runtime_tests.cpp index 4f9fcb6..2d83b44 100644 --- a/app/tests/plc_runtime_tests.cpp +++ b/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 协议异常")),