| @@ -170,8 +170,13 @@ void PlcCommunicationService::disconnectDevice() | |||
| void PlcCommunicationService::setPollAddresses( | |||
| const std::vector<RegisterAddress> &addresses) | |||
| { | |||
| poll_addresses_ = addresses; | |||
| rebuildPollBlocks(); | |||
| if (pending_reply_ != nullptr) | |||
| { | |||
| pending_poll_addresses_ = addresses; | |||
| poll_update_pending_ = true; | |||
| return; | |||
| } | |||
| applyPollAddresses(addresses); | |||
| } | |||
| PlcConnectionState PlcCommunicationService::state() const | |||
| @@ -248,8 +253,27 @@ void PlcCommunicationService::rebuildPollBlocks() | |||
| } | |||
| } | |||
| next_poll_block_ = 0; | |||
| initial_blocks_read_.assign(poll_blocks_.size(), false); | |||
| initial_read_completed_ = false; | |||
| if (!initial_read_completed_) | |||
| { | |||
| initial_blocks_read_.assign(poll_blocks_.size(), false); | |||
| } | |||
| else | |||
| { | |||
| initial_blocks_read_.clear(); | |||
| } | |||
| } | |||
| void PlcCommunicationService::applyPollAddresses( | |||
| const std::vector<RegisterAddress> &addresses) | |||
| { | |||
| poll_addresses_ = addresses; | |||
| rebuildPollBlocks(); | |||
| poll_update_pending_ = false; | |||
| pending_poll_addresses_.clear(); | |||
| if (state_ == PlcConnectionState::Connected && pending_reply_ == nullptr) | |||
| { | |||
| pollNextBlock(); | |||
| } | |||
| } | |||
| void PlcCommunicationService::pollNextBlock() | |||
| @@ -278,6 +302,7 @@ void PlcCommunicationService::pollNextBlock() | |||
| handleReadFinished(reply, block); | |||
| if (state_ == PlcConnectionState::Connected | |||
| && reply->error() == QModbusDevice::NoError | |||
| && !poll_update_pending_ | |||
| && block_index < initial_blocks_read_.size()) | |||
| { | |||
| initial_blocks_read_[block_index] = true; | |||
| @@ -300,6 +325,11 @@ void PlcCommunicationService::pollNextBlock() | |||
| pending_reply_ = nullptr; | |||
| } | |||
| reply->deleteLater(); | |||
| if (poll_update_pending_) | |||
| { | |||
| const std::vector<RegisterAddress> addresses = pending_poll_addresses_; | |||
| applyPollAddresses(addresses); | |||
| } | |||
| }); | |||
| } | |||
| @@ -54,6 +54,7 @@ private: | |||
| }; | |||
| void rebuildPollBlocks(); | |||
| void applyPollAddresses(const std::vector<RegisterAddress> &addresses); | |||
| void pollNextBlock(); | |||
| void handleReadFinished(QModbusReply *reply, PollBlock block); | |||
| RegisterWriteResult sendBitWrite(const RegisterAddress &address, bool value); | |||
| @@ -67,9 +68,11 @@ private: | |||
| QTimer poll_timer_; | |||
| PlcSerialConfiguration configuration_; | |||
| std::vector<RegisterAddress> poll_addresses_; | |||
| std::vector<RegisterAddress> pending_poll_addresses_; | |||
| std::vector<PollBlock> poll_blocks_; | |||
| std::size_t next_poll_block_ = 0; | |||
| QModbusReply *pending_reply_ = nullptr; | |||
| bool poll_update_pending_ = false; | |||
| PlcConnectionState state_ = PlcConnectionState::Disconnected; | |||
| bool initial_read_completed_ = false; | |||
| std::vector<bool> initial_blocks_read_; | |||
| @@ -178,7 +178,25 @@ PlcCommunicationResult RuntimeModeService::connectPlc( | |||
| { | |||
| return {false, "PLC 通信服务尚未配置"}; | |||
| } | |||
| std::vector<RegisterAddress> addresses; | |||
| refreshPlcPollAddresses(); | |||
| setInitialPlcReadCompleted(false); | |||
| return plc_gateway_->connectDevice(configuration); | |||
| } | |||
| void RuntimeModeService::setMonitorAddresses( | |||
| const std::vector<RegisterAddress> &addresses) | |||
| { | |||
| monitor_addresses_ = addresses; | |||
| refreshPlcPollAddresses(); | |||
| } | |||
| void RuntimeModeService::refreshPlcPollAddresses() | |||
| { | |||
| if (plc_gateway_ == nullptr) | |||
| { | |||
| return; | |||
| } | |||
| std::vector<RegisterAddress> addresses = monitor_addresses_; | |||
| const Project &project = project_service_.project(); | |||
| for (const HmiPage &page : project.hmiPages) | |||
| { | |||
| @@ -211,9 +229,23 @@ PlcCommunicationResult RuntimeModeService::connectPlc( | |||
| } | |||
| } | |||
| } | |||
| addresses.erase( | |||
| std::remove_if( | |||
| addresses.begin(), addresses.end(), | |||
| [](const RegisterAddress &address) { return !address.isValid(); }), | |||
| addresses.end()); | |||
| std::sort( | |||
| addresses.begin(), addresses.end(), | |||
| [](const RegisterAddress &left, const RegisterAddress &right) | |||
| { | |||
| if (left.area() != right.area()) | |||
| { | |||
| return left.area() == RegisterArea::M; | |||
| } | |||
| return left.index() < right.index(); | |||
| }); | |||
| addresses.erase(std::unique(addresses.begin(), addresses.end()), addresses.end()); | |||
| plc_gateway_->setPollAddresses(addresses); | |||
| setInitialPlcReadCompleted(false); | |||
| return plc_gateway_->connectDevice(configuration); | |||
| } | |||
| void RuntimeModeService::disconnectPlc() | |||
| @@ -14,6 +14,7 @@ | |||
| #include <cstdint> | |||
| #include <functional> | |||
| #include <vector> | |||
| class ProjectService; | |||
| class ActiveRegisterRepository; | |||
| @@ -74,6 +75,8 @@ public: | |||
| RegisterRepository &virtual_repository, | |||
| RegisterRepository &plc_repository); | |||
| PlcCommunicationResult connectPlc(const PlcSerialConfiguration &configuration); | |||
| void setMonitorAddresses(const std::vector<RegisterAddress> &addresses); | |||
| void refreshPlcPollAddresses(); | |||
| void disconnectPlc(); | |||
| PlcConnectionState plcConnectionState() const; | |||
| const std::string &plcError() const; | |||
| @@ -90,4 +93,5 @@ private: | |||
| RegisterRepository *virtual_repository_ = nullptr; | |||
| RegisterRepository *plc_repository_ = nullptr; | |||
| std::function<void()> plc_status_changed_callback_; | |||
| std::vector<RegisterAddress> monitor_addresses_; | |||
| }; | |||
| @@ -178,6 +178,15 @@ void testRuntimeRepositorySwitchingAndDisconnect() | |||
| {"COM9", 2, 19200, 8, 2, 1, 1000, 2, 200}); | |||
| require(connected.succeeded && gateway.poll_addresses.size() == 1U, | |||
| "PLC connection must receive the project address poll set"); | |||
| service.setMonitorAddresses({ | |||
| RegisterAddress{RegisterArea::M, 5}, | |||
| RegisterAddress{RegisterArea::D, 8}}); | |||
| require(gateway.poll_addresses.size() == 2U | |||
| && gateway.poll_addresses.front() | |||
| == RegisterAddress{RegisterArea::M, 5} | |||
| && gateway.poll_addresses.back() | |||
| == RegisterAddress{RegisterArea::D, 8}, | |||
| "monitor addresses must merge with and deduplicate project poll addresses"); | |||
| require(service.enterOnlineRunning().error | |||
| == ModeTransitionError::InitialPlcReadRequired, | |||
| "online mode must wait for the first valid PLC read"); | |||
| @@ -13,6 +13,7 @@ SOURCES += \ | |||
| ../src/domain/register_address.cpp \ | |||
| ../src/domain/register_repository.cpp \ | |||
| ../src/domain/active_register_repository.cpp \ | |||
| ../src/domain/register_monitor_model.cpp \ | |||
| ../src/domain/hmi_model.cpp \ | |||
| ../src/domain/control_logic_model.cpp \ | |||
| ../src/domain/project_model.cpp \ | |||
| @@ -27,6 +28,7 @@ HEADERS += \ | |||
| ../src/domain/register_address.h \ | |||
| ../src/domain/register_repository.h \ | |||
| ../src/domain/active_register_repository.h \ | |||
| ../src/domain/register_monitor_model.h \ | |||
| ../src/domain/hmi_model.h \ | |||
| ../src/domain/control_logic_model.h \ | |||
| ../src/domain/project_model.h \ | |||
| @@ -13,6 +13,7 @@ SOURCES += \ | |||
| ../src/domain/register_address.cpp \ | |||
| ../src/domain/register_repository.cpp \ | |||
| ../src/domain/active_register_repository.cpp \ | |||
| ../src/domain/register_monitor_model.cpp \ | |||
| ../src/domain/hmi_model.cpp \ | |||
| ../src/domain/control_logic_model.cpp \ | |||
| ../src/domain/project_model.cpp \ | |||
| @@ -26,6 +27,7 @@ HEADERS += \ | |||
| ../src/domain/register_address.h \ | |||
| ../src/domain/register_repository.h \ | |||
| ../src/domain/active_register_repository.h \ | |||
| ../src/domain/register_monitor_model.h \ | |||
| ../src/domain/hmi_model.h \ | |||
| ../src/domain/control_logic_model.h \ | |||
| ../src/domain/project_model.h \ | |||