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