| @@ -134,6 +134,7 @@ int main(int argc, char *argv[]) | |||||
| RegisterMonitorService register_monitor_service(active_register_repository); | RegisterMonitorService register_monitor_service(active_register_repository); | ||||
| register_monitor_service.setOfflineInitialRepository(offline_initial_repository); | register_monitor_service.setOfflineInitialRepository(offline_initial_repository); | ||||
| register_monitor_service.setOfflineInitialCaptureEnabled(true); | register_monitor_service.setOfflineInitialCaptureEnabled(true); | ||||
| register_monitor_service.setEditingPlcRepository(plc_register_repository); | |||||
| // 用虚拟寄存器执行本地梯形图,实现不连接 PLC 的离线仿真 | // 用虚拟寄存器执行本地梯形图,实现不连接 PLC 的离线仿真 | ||||
| OfflineSimulationService offline_simulation_service( | OfflineSimulationService offline_simulation_service( | ||||
| virtual_register_repository, &offline_initial_repository); | virtual_register_repository, &offline_initial_repository); | ||||
| @@ -44,6 +44,44 @@ void RegisterMonitorService::setOfflineInitialCaptureEnabled(bool enabled) | |||||
| capture_offline_initial_values_ = enabled; | capture_offline_initial_values_ = enabled; | ||||
| } | } | ||||
| void RegisterMonitorService::setEditingPlcRepository( | |||||
| RegisterRepository &repository) | |||||
| { | |||||
| editing_plc_repository_ = &repository; | |||||
| } | |||||
| void RegisterMonitorService::setEditingPlcState( | |||||
| bool connected, bool initial_read_completed) | |||||
| { | |||||
| editing_plc_connected_ = connected; | |||||
| editing_plc_ready_ = connected && initial_read_completed; | |||||
| } | |||||
| bool RegisterMonitorService::editingPlcConnected() const | |||||
| { | |||||
| return usesEditingPlcRepository(); | |||||
| } | |||||
| bool RegisterMonitorService::editingPlcReady() const | |||||
| { | |||||
| return usesEditingPlcRepository() && editing_plc_ready_; | |||||
| } | |||||
| bool RegisterMonitorService::usesEditingPlcRepository() const | |||||
| { | |||||
| return editing_plc_connected_ && editing_plc_repository_ != nullptr; | |||||
| } | |||||
| RegisterRepository &RegisterMonitorService::accessRepository() | |||||
| { | |||||
| return usesEditingPlcRepository() ? *editing_plc_repository_ : repository_; | |||||
| } | |||||
| const RegisterRepository &RegisterMonitorService::accessRepository() const | |||||
| { | |||||
| return usesEditingPlcRepository() ? *editing_plc_repository_ : repository_; | |||||
| } | |||||
| RegisterMonitorResult RegisterMonitorService::addRange( | RegisterMonitorResult RegisterMonitorService::addRange( | ||||
| const std::string &start_address, int count) | const std::string &start_address, int count) | ||||
| { | { | ||||
| @@ -212,7 +250,11 @@ RegisterMonitorResult RegisterMonitorService::clear() | |||||
| RegisterMonitorWriteResult RegisterMonitorService::writeBit( | RegisterMonitorWriteResult RegisterMonitorService::writeBit( | ||||
| const RegisterAddress &address, bool value) | const RegisterAddress &address, bool value) | ||||
| { | { | ||||
| const RegisterWriteResult result = repository_.writeBit(address, value); | |||||
| if (usesEditingPlcRepository() && !editing_plc_ready_) | |||||
| { | |||||
| return {false, RegisterError::Unavailable, "PLC 尚未完成首次读取"}; | |||||
| } | |||||
| const RegisterWriteResult result = accessRepository().writeBit(address, value); | |||||
| const RegisterMonitorWriteResult active_result = result.succeeded | const RegisterMonitorWriteResult active_result = result.succeeded | ||||
| ? RegisterMonitorWriteResult{true, RegisterError::None, {}} | ? RegisterMonitorWriteResult{true, RegisterError::None, {}} | ||||
| : RegisterMonitorWriteResult{false, result.error, result.error == RegisterError::AreaMismatch | : RegisterMonitorWriteResult{false, result.error, result.error == RegisterError::AreaMismatch | ||||
| @@ -228,6 +270,10 @@ RegisterMonitorWriteResult RegisterMonitorService::writeBit( | |||||
| RegisterMonitorWriteResult RegisterMonitorService::writeNumeric( | RegisterMonitorWriteResult RegisterMonitorService::writeNumeric( | ||||
| const RegisterAddress &address, RegisterDataType data_type, double value) | const RegisterAddress &address, RegisterDataType data_type, double value) | ||||
| { | { | ||||
| if (usesEditingPlcRepository() && !editing_plc_ready_) | |||||
| { | |||||
| return {false, RegisterError::Unavailable, "PLC 尚未完成首次读取"}; | |||||
| } | |||||
| if (address.isValid() && address.area() != RegisterArea::D) | if (address.isValid() && address.area() != RegisterArea::D) | ||||
| { | { | ||||
| return {false, RegisterError::AreaMismatch, | return {false, RegisterError::AreaMismatch, | ||||
| @@ -247,7 +293,7 @@ RegisterMonitorWriteResult RegisterMonitorService::writeNumeric( | |||||
| std::string(registerDataTypeDescriptor(data_type).displayName) | std::string(registerDataTypeDescriptor(data_type).displayName) | ||||
| + " 写入值无效"}; | + " 写入值无效"}; | ||||
| } | } | ||||
| const RegisterWriteResult result = repository_.writeWords(address, *encoded); | |||||
| const RegisterWriteResult result = accessRepository().writeWords(address, *encoded); | |||||
| const RegisterMonitorWriteResult active_result = result.succeeded | const RegisterMonitorWriteResult active_result = result.succeeded | ||||
| ? RegisterMonitorWriteResult{true, RegisterError::None, {}} | ? RegisterMonitorWriteResult{true, RegisterError::None, {}} | ||||
| : RegisterMonitorWriteResult{false, result.error, | : RegisterMonitorWriteResult{false, result.error, | ||||
| @@ -265,7 +311,8 @@ RegisterMonitorWriteResult RegisterMonitorService::captureInitialBit( | |||||
| const RegisterAddress &address, bool value, | const RegisterAddress &address, bool value, | ||||
| const RegisterMonitorWriteResult &active_result) | const RegisterMonitorWriteResult &active_result) | ||||
| { | { | ||||
| if (!active_result.succeeded || !capture_offline_initial_values_ | |||||
| if (!active_result.succeeded || usesEditingPlcRepository() | |||||
| || !capture_offline_initial_values_ | |||||
| || offline_initial_repository_ == nullptr) | || offline_initial_repository_ == nullptr) | ||||
| { | { | ||||
| return active_result; | return active_result; | ||||
| @@ -279,7 +326,8 @@ RegisterMonitorWriteResult RegisterMonitorService::captureInitialWords( | |||||
| const std::vector<std::int16_t> &values, | const std::vector<std::int16_t> &values, | ||||
| const RegisterMonitorWriteResult &active_result) | const RegisterMonitorWriteResult &active_result) | ||||
| { | { | ||||
| if (!active_result.succeeded || !capture_offline_initial_values_ | |||||
| if (!active_result.succeeded || usesEditingPlcRepository() | |||||
| || !capture_offline_initial_values_ | |||||
| || offline_initial_repository_ == nullptr) | || offline_initial_repository_ == nullptr) | ||||
| { | { | ||||
| return active_result; | return active_result; | ||||
| @@ -305,9 +353,15 @@ std::vector<MonitorValue> RegisterMonitorService::values(bool communication_faul | |||||
| value.dataType = point.dataType; | value.dataType = point.dataType; | ||||
| value.endAddress = address.index() | value.endAddress = address.index() | ||||
| + registerDataTypeWordCount(point.dataType) - 1; | + registerDataTypeWordCount(point.dataType) - 1; | ||||
| if (usesEditingPlcRepository() && !editing_plc_ready_) | |||||
| { | |||||
| value.state = MonitorValueState::Unavailable; | |||||
| result.push_back(value); | |||||
| continue; | |||||
| } | |||||
| if (address.area() == RegisterArea::M) | if (address.area() == RegisterArea::M) | ||||
| { | { | ||||
| const BitReadResult read = repository_.readBit(address); | |||||
| const BitReadResult read = accessRepository().readBit(address); | |||||
| value.bitValue = read.value; | value.bitValue = read.value; | ||||
| value.state = read.succeeded | value.state = read.succeeded | ||||
| ? (communication_fault ? MonitorValueState::CommunicationFault | ? (communication_fault ? MonitorValueState::CommunicationFault | ||||
| @@ -316,7 +370,7 @@ std::vector<MonitorValue> RegisterMonitorService::values(bool communication_faul | |||||
| } | } | ||||
| else | else | ||||
| { | { | ||||
| const WordsReadResult read = repository_.readWords( | |||||
| const WordsReadResult read = accessRepository().readWords( | |||||
| address, registerDataTypeWordCount(point.dataType)); | address, registerDataTypeWordCount(point.dataType)); | ||||
| const std::optional<RegisterNumericValue> decoded = read.succeeded | const std::optional<RegisterNumericValue> decoded = read.succeeded | ||||
| ? decodeRegisterNumericValue(point.dataType, read.values) | ? decodeRegisterNumericValue(point.dataType, read.values) | ||||
| @@ -41,7 +41,7 @@ struct RegisterMonitorWriteResult | |||||
| /** | /** | ||||
| * @brief 编排自由监控地址列表和寄存器仓库读写 | * @brief 编排自由监控地址列表和寄存器仓库读写 | ||||
| * | * | ||||
| * 地址列表和离线初始值只属于当前运行会话,不写入工程文件;仓库由当前运行模式注入 | |||||
| * 地址列表和离线初始值只属于当前运行会话,不写入工程文件;编辑态连接 PLC 后可直接访问 PLC 缓存 | |||||
| */ | */ | ||||
| class RegisterMonitorService | class RegisterMonitorService | ||||
| { | { | ||||
| @@ -54,8 +54,16 @@ public: | |||||
| // 配置编辑态离线初始值仓库;该仓库只保存在当前进程内 | // 配置编辑态离线初始值仓库;该仓库只保存在当前进程内 | ||||
| void setOfflineInitialRepository(VirtualRegisterRepository &repository); | void setOfflineInitialRepository(VirtualRegisterRepository &repository); | ||||
| // 编辑态开启时,监控写入同时更新离线初始值 | |||||
| // 编辑态使用虚拟仓库时,监控写入同时更新离线初始值 | |||||
| void setOfflineInitialCaptureEnabled(bool enabled); | void setOfflineInitialCaptureEnabled(bool enabled); | ||||
| // 配置编辑态可直接访问的 PLC 缓存仓库 | |||||
| void setEditingPlcRepository(RegisterRepository &repository); | |||||
| // 更新编辑态 PLC 连接和首读状态 | |||||
| void setEditingPlcState(bool connected, bool initial_read_completed); | |||||
| // 编辑态是否已经连接到 PLC | |||||
| bool editingPlcConnected() const; | |||||
| // 编辑态 PLC 是否已完成首轮读取,可执行写入 | |||||
| bool editingPlcReady() const; | |||||
| /** | /** | ||||
| * @brief 从起始地址连续添加同一区域的多个地址 | * @brief 从起始地址连续添加同一区域的多个地址 | ||||
| @@ -118,6 +126,9 @@ public: | |||||
| const std::vector<RegisterWordRange> &)> validator); | const std::vector<RegisterWordRange> &)> validator); | ||||
| private: | private: | ||||
| RegisterRepository &accessRepository(); | |||||
| const RegisterRepository &accessRepository() const; | |||||
| bool usesEditingPlcRepository() const; | |||||
| RegisterMonitorWriteResult captureInitialBit( | RegisterMonitorWriteResult captureInitialBit( | ||||
| const RegisterAddress &address, bool value, | const RegisterAddress &address, bool value, | ||||
| const RegisterMonitorWriteResult &active_result); | const RegisterMonitorWriteResult &active_result); | ||||
| @@ -131,6 +142,9 @@ private: | |||||
| const std::vector<MonitorPoint> &points); | const std::vector<MonitorPoint> &points); | ||||
| RegisterRepository &repository_; // 当前运行模式的寄存器仓库,不由服务拥有 | RegisterRepository &repository_; // 当前运行模式的寄存器仓库,不由服务拥有 | ||||
| RegisterRepository *editing_plc_repository_ = nullptr; | |||||
| bool editing_plc_connected_ = false; | |||||
| bool editing_plc_ready_ = false; | |||||
| VirtualRegisterRepository *offline_initial_repository_ = nullptr; | VirtualRegisterRepository *offline_initial_repository_ = nullptr; | ||||
| bool capture_offline_initial_values_ = false; | bool capture_offline_initial_values_ = false; | ||||
| RegisterMonitorModel model_; // 当前会话的去重监控地址列表 | RegisterMonitorModel model_; // 当前会话的去重监控地址列表 | ||||
| @@ -106,8 +106,19 @@ void FreeMonitorWidget::refreshValues( | |||||
| bool communication_fault = false; | bool communication_fault = false; | ||||
| if (mode == ApplicationMode::Editing) | if (mode == ApplicationMode::Editing) | ||||
| { | { | ||||
| source = tr("数据源:离线初始值 · 虚拟 M/D"); | |||||
| if (service_.editingPlcConnected()) | |||||
| { | |||||
| source = service_.editingPlcReady() | |||||
| ? tr("数据源:PLC 缓存") | |||||
| : tr("数据源:PLC · 正在首读"); | |||||
| } | |||||
| else | |||||
| { | |||||
| source = tr("数据源:离线初始值 · 虚拟 M/D"); | |||||
| } | |||||
| values_available = true; | values_available = true; | ||||
| communication_fault = service_.editingPlcConnected() | |||||
| && !service_.editingPlcReady(); | |||||
| } | } | ||||
| else if (mode == ApplicationMode::OfflineRunning) | else if (mode == ApplicationMode::OfflineRunning) | ||||
| { | { | ||||
| @@ -138,8 +149,11 @@ void FreeMonitorWidget::refreshValues( | |||||
| if (value.state == MonitorValueState::Unavailable) | if (value.state == MonitorValueState::Unavailable) | ||||
| { | { | ||||
| value_item->setText(QStringLiteral("--")); | value_item->setText(QStringLiteral("--")); | ||||
| state_item->setText(mode == ApplicationMode::OnlineRunning | |||||
| ? tr("等待读取") : tr("不可用")); | |||||
| state_item->setText( | |||||
| mode == ApplicationMode::OnlineRunning | |||||
| || (mode == ApplicationMode::Editing | |||||
| && service_.editingPlcConnected()) | |||||
| ? tr("等待读取") : tr("不可用")); | |||||
| continue; | continue; | ||||
| } | } | ||||
| if (value.address.area() == RegisterArea::M) | if (value.address.area() == RegisterArea::M) | ||||
| @@ -1149,6 +1149,14 @@ void MainWindow::configureRuntimeMonitor() | |||||
| } | } | ||||
| }); | }); | ||||
| runtime_panel_controller_->configure(); | runtime_panel_controller_->configure(); | ||||
| runtime_panel_controller_->configureUserRuntimeMode( | |||||
| false, | |||||
| [this](int mode) | |||||
| { | |||||
| requestMode(static_cast<ApplicationMode>(mode)); | |||||
| }, | |||||
| {}, | |||||
| {}); | |||||
| runtime_monitor_widget_ = runtime_panel_controller_->runtimeMonitorWidget(); | runtime_monitor_widget_ = runtime_panel_controller_->runtimeMonitorWidget(); | ||||
| } | } | ||||
| @@ -1214,15 +1222,23 @@ void MainWindow::refreshDataMonitorUi() | |||||
| return; | return; | ||||
| } | } | ||||
| const ApplicationMode mode = runtime_mode_service_.mode(); | const ApplicationMode mode = runtime_mode_service_.mode(); | ||||
| const PlcConnectionState plc_state = runtime_mode_service_.plcConnectionState(); | |||||
| const bool editing_plc_connected = mode == ApplicationMode::Editing | |||||
| && plc_state != PlcConnectionState::Disconnected | |||||
| && plc_state != PlcConnectionState::Faulted; | |||||
| register_monitor_service_.setEditingPlcState( | |||||
| editing_plc_connected, | |||||
| editing_plc_connected && runtime_mode_service_.initialPlcReadCompleted()); | |||||
| const bool online_connected = mode == ApplicationMode::OnlineRunning | const bool online_connected = mode == ApplicationMode::OnlineRunning | ||||
| && runtime_mode_service_.plcConnectionState() | |||||
| == PlcConnectionState::Connected; | |||||
| && plc_state == PlcConnectionState::Connected; | |||||
| data_monitor_widget_->setWriteEnabled( | data_monitor_widget_->setWriteEnabled( | ||||
| mode == ApplicationMode::Editing | |||||
| (mode == ApplicationMode::Editing | |||||
| && (!editing_plc_connected | |||||
| || register_monitor_service_.editingPlcReady())) | |||||
| || mode == ApplicationMode::OfflineRunning | || mode == ApplicationMode::OfflineRunning | ||||
| || online_connected); | || online_connected); | ||||
| data_monitor_widget_->refreshValues( | data_monitor_widget_->refreshValues( | ||||
| mode, runtime_mode_service_.plcConnectionState()); | |||||
| mode, plc_state); | |||||
| } | } | ||||
| void MainWindow::configureProjectTree() | void MainWindow::configureProjectTree() | ||||
| @@ -2397,29 +2413,57 @@ bool MainWindow::requestMode(ApplicationMode requested_mode) | |||||
| // UI 动作只提出目标模式,所有前置条件和仓库切换由 RuntimeModeService 决定 | // UI 动作只提出目标模式,所有前置条件和仓库切换由 RuntimeModeService 决定 | ||||
| // UI 仅转发模式意图,合法性由服务层和领域状态机决定 | // UI 仅转发模式意图,合法性由服务层和领域状态机决定 | ||||
| ModeTransitionResult result; | ModeTransitionResult result; | ||||
| switch (requested_mode) | |||||
| { | |||||
| case ApplicationMode::Editing: | |||||
| { | |||||
| result = runtime_mode_service_.enterEditing(); | |||||
| break; | |||||
| } | |||||
| case ApplicationMode::OfflineRunning: | |||||
| { | |||||
| result = runtime_mode_service_.enterOfflineRunning(); | |||||
| break; | |||||
| } | |||||
| case ApplicationMode::OnlineRunning: | |||||
| { | |||||
| result = runtime_mode_service_.enterOnlineRunning(); | |||||
| break; | |||||
| const ApplicationMode current_mode = runtime_mode_service_.mode(); | |||||
| if ((requested_mode == ApplicationMode::OfflineRunning | |||||
| || requested_mode == ApplicationMode::OnlineRunning) | |||||
| && current_mode != ApplicationMode::Editing | |||||
| && current_mode != requested_mode) | |||||
| { | |||||
| // 运行模式切换统一经过编辑态;先检查真机首读,失败时保持原运行态 | |||||
| if (requested_mode == ApplicationMode::OnlineRunning | |||||
| && !runtime_mode_service_.initialPlcReadCompleted()) | |||||
| { | |||||
| result = {false, ModeTransitionError::InitialPlcReadRequired, {}}; | |||||
| } | |||||
| else | |||||
| { | |||||
| const ModeTransitionResult editing = runtime_mode_service_.enterEditing(); | |||||
| if (!editing.succeeded) | |||||
| { | |||||
| restoreCurrentModeAction(); | |||||
| return false; | |||||
| } | |||||
| result = requested_mode == ApplicationMode::OfflineRunning | |||||
| ? runtime_mode_service_.enterOfflineRunning() | |||||
| : runtime_mode_service_.enterOnlineRunning(); | |||||
| } | |||||
| } | } | ||||
| default: | |||||
| else | |||||
| { | { | ||||
| restoreCurrentModeAction(); | |||||
| statusBar()->showMessage(tr("不支持的运行模式"), 5000); | |||||
| return false; | |||||
| } | |||||
| switch (requested_mode) | |||||
| { | |||||
| case ApplicationMode::Editing: | |||||
| { | |||||
| result = runtime_mode_service_.enterEditing(); | |||||
| break; | |||||
| } | |||||
| case ApplicationMode::OfflineRunning: | |||||
| { | |||||
| result = runtime_mode_service_.enterOfflineRunning(); | |||||
| break; | |||||
| } | |||||
| case ApplicationMode::OnlineRunning: | |||||
| { | |||||
| result = runtime_mode_service_.enterOnlineRunning(); | |||||
| break; | |||||
| } | |||||
| default: | |||||
| { | |||||
| restoreCurrentModeAction(); | |||||
| statusBar()->showMessage(tr("不支持的运行模式"), 5000); | |||||
| return false; | |||||
| } | |||||
| } | |||||
| } | } | ||||
| const bool runtime_request = requested_mode == ApplicationMode::OfflineRunning | const bool runtime_request = requested_mode == ApplicationMode::OfflineRunning | ||||
| || requested_mode == ApplicationMode::OnlineRunning; | || requested_mode == ApplicationMode::OnlineRunning; | ||||
| @@ -2437,6 +2481,12 @@ bool MainWindow::requestMode(ApplicationMode requested_mode) | |||||
| if (!result.succeeded) | if (!result.succeeded) | ||||
| { | { | ||||
| restoreCurrentModeAction(); | restoreCurrentModeAction(); | ||||
| if (runtime_monitor_widget_ != nullptr && runtime_request) | |||||
| { | |||||
| runtime_monitor_widget_->setMode( | |||||
| runtime_mode_service_.mode(), | |||||
| runtime_mode_service_.plcConnectionState()); | |||||
| } | |||||
| QString message = transitionErrorText(result.error); | QString message = transitionErrorText(result.error); | ||||
| if (result.error == ModeTransitionError::InitialPlcReadRequired) | if (result.error == ModeTransitionError::InitialPlcReadRequired) | ||||
| { | { | ||||
| @@ -104,7 +104,7 @@ RuntimeMonitorWidget::RuntimeMonitorWidget( | |||||
| this, | this, | ||||
| [this](int index) | [this](int index) | ||||
| { | { | ||||
| if (user_runtime_mode_ && index >= 0) | |||||
| if (index >= 0) | |||||
| { | { | ||||
| emit modeChangeRequested(index == 0 | emit modeChangeRequested(index == 0 | ||||
| ? static_cast<int>(ApplicationMode::OfflineRunning) | ? static_cast<int>(ApplicationMode::OfflineRunning) | ||||
| @@ -177,11 +177,12 @@ void RuntimeMonitorWidget::setMode( | |||||
| offline ? tr("离线仿真 · 虚拟 M/D") | offline ? tr("离线仿真 · 虚拟 M/D") | ||||
| : online ? tr("真机运行 · PLC 缓存 · 本地推算轨迹") | : online ? tr("真机运行 · PLC 缓存 · 本地推算轨迹") | ||||
| : tr("当前未运行")); | : tr("当前未运行")); | ||||
| if (user_runtime_mode_) | |||||
| { | { | ||||
| const QSignalBlocker blocker(ui_->runtimeModeComboBox); | const QSignalBlocker blocker(ui_->runtimeModeComboBox); | ||||
| ui_->runtimeModeComboBox->setCurrentIndex( | |||||
| offline ? 0 : online ? 1 : -1); | |||||
| ui_->runtimeModeComboBox->setCurrentIndex(offline ? 0 : online ? 1 : -1); | |||||
| } | |||||
| if (user_runtime_mode_) | |||||
| { | |||||
| const bool plc_configurable = plc_state == PlcConnectionState::Disconnected | const bool plc_configurable = plc_state == PlcConnectionState::Disconnected | ||||
| || plc_state == PlcConnectionState::Faulted; | || plc_state == PlcConnectionState::Faulted; | ||||
| ui_->runtimePlcButton->setEnabled(plc_configurable); | ui_->runtimePlcButton->setEnabled(plc_configurable); | ||||
| @@ -194,7 +195,8 @@ void RuntimeMonitorWidget::setMode( | |||||
| void RuntimeMonitorWidget::setUserRuntimeMode(bool enabled) | void RuntimeMonitorWidget::setUserRuntimeMode(bool enabled) | ||||
| { | { | ||||
| user_runtime_mode_ = enabled; | user_runtime_mode_ = enabled; | ||||
| ui_->runtimeModeComboBox->setVisible(enabled); | |||||
| // 普通编程器和导出的运行程序都允许在运行窗口切换离线/真机 | |||||
| ui_->runtimeModeComboBox->setVisible(true); | |||||
| ui_->runtimePlcButton->setVisible(enabled); | ui_->runtimePlcButton->setVisible(enabled); | ||||
| ui_->runtimeDisconnectPlcButton->setVisible(enabled); | ui_->runtimeDisconnectPlcButton->setVisible(enabled); | ||||
| ui_->exitRuntimeButton->setToolTip( | ui_->exitRuntimeButton->setToolTip( | ||||
| @@ -77,6 +77,55 @@ void testSharedActiveRepositoryValues() | |||||
| "valid cached values must be marked stale during a communication fault"); | "valid cached values must be marked stale during a communication fault"); | ||||
| } | } | ||||
| void testEditingPlcMonitorAccess() | |||||
| { | |||||
| VirtualRegisterRepository virtual_repository; | |||||
| VirtualRegisterRepository plc_repository; | |||||
| ActiveRegisterRepository active_repository(virtual_repository); | |||||
| VirtualRegisterRepository initial_repository; | |||||
| RegisterMonitorService service(active_repository); | |||||
| service.setOfflineInitialRepository(initial_repository); | |||||
| service.setOfflineInitialCaptureEnabled(true); | |||||
| service.setEditingPlcRepository(plc_repository); | |||||
| require(service.addRange("M0", 1).succeeded | |||||
| && service.addRange("D0", 1).succeeded, | |||||
| "editing PLC monitor fixture must accept M and D points"); | |||||
| virtual_repository.writeBit({RegisterArea::M, 0}, false); | |||||
| virtual_repository.writeWord({RegisterArea::D, 0}, 11); | |||||
| plc_repository.writeBit({RegisterArea::M, 0}, true); | |||||
| plc_repository.writeWord({RegisterArea::D, 0}, 22); | |||||
| service.setEditingPlcState(true, false); | |||||
| std::vector<MonitorValue> values = service.values(false); | |||||
| require(values.size() == 2U | |||||
| && values[0].state == MonitorValueState::Unavailable | |||||
| && values[1].state == MonitorValueState::Unavailable | |||||
| && !service.writeBit({RegisterArea::M, 0}, false).succeeded, | |||||
| "editing PLC monitor must wait for the first read before access"); | |||||
| service.setEditingPlcState(true, true); | |||||
| values = service.values(false); | |||||
| require(values[0].state == MonitorValueState::Valid && values[0].bitValue | |||||
| && values[1].state == MonitorValueState::Valid | |||||
| && std::get<std::int16_t>(values[1].numericValue) == 22, | |||||
| "editing PLC monitor must display PLC cache values after first read"); | |||||
| require(service.writeBit({RegisterArea::M, 0}, false).succeeded | |||||
| && service.writeNumeric( | |||||
| {RegisterArea::D, 0}, RegisterDataType::Int16, 33).succeeded | |||||
| && !plc_repository.readBit({RegisterArea::M, 0}).value | |||||
| && plc_repository.readWord({RegisterArea::D, 0}).value == 33 | |||||
| && !initial_repository.readBit({RegisterArea::M, 0}).value | |||||
| && initial_repository.readWord({RegisterArea::D, 0}).value == 0, | |||||
| "editing PLC monitor writes must target PLC and skip offline capture"); | |||||
| service.setEditingPlcState(false, false); | |||||
| values = service.values(false); | |||||
| require(!values[0].bitValue | |||||
| && std::get<std::int16_t>(values[1].numericValue) == 11, | |||||
| "disconnecting PLC must restore the virtual editing source"); | |||||
| } | |||||
| void testRegisterWrites() | void testRegisterWrites() | ||||
| { | { | ||||
| VirtualRegisterRepository repository; | VirtualRegisterRepository repository; | ||||
| @@ -264,6 +313,7 @@ int main() | |||||
| { | { | ||||
| testRangeManagement(); | testRangeManagement(); | ||||
| testSharedActiveRepositoryValues(); | testSharedActiveRepositoryValues(); | ||||
| testEditingPlcMonitorAccess(); | |||||
| testRegisterWrites(); | testRegisterWrites(); | ||||
| testOfflineInitialValueCapture(); | testOfflineInitialValueCapture(); | ||||
| testMultiWordMonitoring(); | testMultiWordMonitoring(); | ||||