From d730d3bc1bf56f94c836110b2b81f847f0148cd5 Mon Sep 17 00:00:00 2001 From: suyu <1643689728@qq.com> Date: Thu, 27 Aug 2026 11:19:04 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E6=89=A9=E5=B1=95=E5=A4=9A=E5=AD=97?= =?UTF-8?q?=E6=95=B0=E5=80=BC=E7=B1=BB=E5=9E=8B=E4=B8=8E=E6=A2=AF=E5=BD=A2?= =?UTF-8?q?=E5=9B=BE=E7=BC=96=E8=BE=91=E8=83=BD=E5=8A=9B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/src/domain/control_logic_model.cpp | 5 - app/src/domain/control_logic_model.h | 1 - app/src/domain/hmi_model.cpp | 10 - app/src/domain/hmi_model.h | 2 - app/src/domain/project_limits.h | 7 - app/src/domain/project_model.cpp | 10 - app/src/domain/project_model.h | 2 - app/src/domain/register_repository.h | 22 -- app/src/infrastructure/json_project_storage.h | 3 +- .../plc_communication_service.cpp | 10 - .../plc_communication_service.h | 4 - .../plc_register_repository.cpp | 7 - .../infrastructure/plc_register_repository.h | 2 - app/src/services/hmi_editor_service.h | 2 +- app/src/services/logic_editor_service.cpp | 309 ------------------ app/src/services/logic_editor_service.h | 26 -- .../services/offline_simulation_service.cpp | 5 - app/src/services/offline_simulation_service.h | 2 - app/src/services/plc_communication_gateway.h | 6 +- app/src/services/project_service.cpp | 10 - app/src/services/project_service.h | 4 +- app/src/services/register_monitor_service.cpp | 12 - app/src/services/register_monitor_service.h | 4 - app/src/ui/logic_editor_widget.cpp | 5 - app/src/ui/logic_editor_widget.h | 1 - app/src/ui/main_window.cpp | 10 - app/src/ui/main_window.h | 5 - app/src/ui/project_workspace_controller.cpp | 10 - app/src/ui/project_workspace_controller.h | 5 - app/tests/alarm_service_tests.cpp | 2 +- app/tests/domain_tests.cpp | 132 ++++---- app/tests/hmi_editor_service_tests.cpp | 28 +- app/tests/logic_editor_service_tests.cpp | 102 +++--- .../offline_simulation_service_tests.cpp | 1 - app/tests/plc_runtime_tests.cpp | 26 +- app/tests/project_management_tests.cpp | 24 +- app/tests/register_monitor_service_tests.cpp | 28 +- app/tests/runtime_mode_service_tests.cpp | 6 +- app/tests/runtime_panel_controller_tests.cpp | 63 ++-- 39 files changed, 216 insertions(+), 697 deletions(-) diff --git a/app/src/domain/control_logic_model.cpp b/app/src/domain/control_logic_model.cpp index da9b7b2..ba6267f 100644 --- a/app/src/domain/control_logic_model.cpp +++ b/app/src/domain/control_logic_model.cpp @@ -531,11 +531,6 @@ bool LadderRung::validateForRunning(std::string *error) const return true; } -bool ControlLogic::validate(std::string *error) const -{ - return validate(defaultProjectLimitSettings(), error); -} - bool ControlLogic::validate( const ProjectLimitSettings &limits, std::string *error) const { diff --git a/app/src/domain/control_logic_model.h b/app/src/domain/control_logic_model.h index 2525576..1bc373c 100644 --- a/app/src/domain/control_logic_model.h +++ b/app/src/domain/control_logic_model.h @@ -175,7 +175,6 @@ struct ControlLogic bool enabled = true; std::vector verticalConnections; - bool validate(std::string *error = nullptr) const; bool validate( const ProjectLimitSettings &limits, std::string *error = nullptr) const; diff --git a/app/src/domain/hmi_model.cpp b/app/src/domain/hmi_model.cpp index 8f9a958..8661362 100644 --- a/app/src/domain/hmi_model.cpp +++ b/app/src/domain/hmi_model.cpp @@ -247,11 +247,6 @@ bool HmiControl::isConfigured() const return binding_area.has_value() && binding->area() == *binding_area; } -bool HmiPage::validate(std::string *error) const -{ - return validate(defaultProjectLimitSettings(), error); -} - bool HmiPage::validate( const ProjectLimitSettings &limits, std::string *error) const { @@ -316,11 +311,6 @@ bool HmiPage::validate( return true; } -bool HmiPage::validateForRunning(std::string *error) const -{ - return validateForRunning(defaultProjectLimitSettings(), error); -} - bool HmiPage::validateForRunning( const ProjectLimitSettings &limits, std::string *error) const { diff --git a/app/src/domain/hmi_model.h b/app/src/domain/hmi_model.h index f57ca52..20de819 100644 --- a/app/src/domain/hmi_model.h +++ b/app/src/domain/hmi_model.h @@ -121,11 +121,9 @@ struct HmiPage * @param error 可选错误输出,失败时写入首个校验原因 * @return 页面及其全部控件有效时返回 true */ - bool validate(std::string *error = nullptr) const; bool validate( const ProjectLimitSettings &limits, std::string *error = nullptr) const; - bool validateForRunning(std::string *error = nullptr) const; bool validateForRunning( const ProjectLimitSettings &limits, std::string *error = nullptr) const; diff --git a/app/src/domain/project_limits.h b/app/src/domain/project_limits.h index bc27ae2..0e61d51 100644 --- a/app/src/domain/project_limits.h +++ b/app/src/domain/project_limits.h @@ -92,10 +92,3 @@ inline const ProjectLimitSettings &defaultProjectLimitSettings() static const ProjectLimitSettings settings; return settings; } - -// 返回长期有效的默认页面尺寸,供没有传入 INI 配置的代码使用 -inline const HmiDefaultSettings &defaultHmiSettings() -{ - static const HmiDefaultSettings settings; - return settings; -} diff --git a/app/src/domain/project_model.cpp b/app/src/domain/project_model.cpp index ffdd36d..bb400ff 100644 --- a/app/src/domain/project_model.cpp +++ b/app/src/domain/project_model.cpp @@ -156,11 +156,6 @@ const RegisterComment *Project::findRegisterComment( return comment == registerComments.cend() ? nullptr : &*comment; } -bool Project::validate(std::string *error) const -{ - return validate(defaultProjectLimitSettings(), error); -} - bool Project::validate( const ProjectLimitSettings &limits, std::string *error) const { @@ -456,11 +451,6 @@ bool Project::validate( return true; } -bool Project::validateForRunning(std::string *error) const -{ - return validateForRunning(defaultProjectLimitSettings(), error); -} - bool Project::validateForRunning( const ProjectLimitSettings &limits, std::string *error) const { diff --git a/app/src/domain/project_model.h b/app/src/domain/project_model.h index 2c12d74..6be5715 100644 --- a/app/src/domain/project_model.h +++ b/app/src/domain/project_model.h @@ -48,12 +48,10 @@ struct Project // 按地址查找工程注释,找不到时返回 nullptr const RegisterComment *findRegisterComment(const RegisterAddress &address) const; // 编辑态校验:允许尚未绑定完成的草稿 - bool validate(std::string *error = nullptr) const; bool validate( const ProjectLimitSettings &limits, std::string *error = nullptr) const; // 运行态校验:额外要求所有运行所需对象都已配置 - bool validateForRunning(std::string *error = nullptr) const; bool validateForRunning( const ProjectLimitSettings &limits, std::string *error = nullptr) const; diff --git a/app/src/domain/register_repository.h b/app/src/domain/register_repository.h index ef2361a..ad39ad5 100644 --- a/app/src/domain/register_repository.h +++ b/app/src/domain/register_repository.h @@ -3,7 +3,6 @@ #include "register_address.h" #include -#include #include #include @@ -40,13 +39,6 @@ struct RegisterWriteResult RegisterError error = RegisterError::Unavailable; // 写入失败原因 }; -struct WordPairReadResult -{ - bool succeeded = false; - std::array values{}; - RegisterError error = RegisterError::Unavailable; -}; - struct WordsReadResult { bool succeeded = false; @@ -118,18 +110,4 @@ public: } return {true, RegisterError::None}; } - virtual WordPairReadResult readWordPair(const RegisterAddress &address) const - { - const WordsReadResult result = readWords(address, 2); - return result.succeeded - ? WordPairReadResult{ - true, {result.values[0], result.values[1]}, RegisterError::None} - : WordPairReadResult{false, {}, result.error}; - } - virtual RegisterWriteResult writeWordPair( - const RegisterAddress &address, - const std::array &values) - { - return writeWords(address, {values[0], values[1]}); - } }; diff --git a/app/src/infrastructure/json_project_storage.h b/app/src/infrastructure/json_project_storage.h index 3707b70..df65132 100644 --- a/app/src/infrastructure/json_project_storage.h +++ b/app/src/infrastructure/json_project_storage.h @@ -7,8 +7,7 @@ class JsonProjectStorage final : public ProjectStorage { public: explicit JsonProjectStorage( - const ProjectLimitSettings &project_limits = - defaultProjectLimitSettings()); + const ProjectLimitSettings &project_limits); ProjectSaveResult save( const Project &project, const std::string &file_path) override; ProjectLoadResult load(const std::string &file_path) override; diff --git a/app/src/infrastructure/plc_communication_service.cpp b/app/src/infrastructure/plc_communication_service.cpp index 07d7984..460604b 100644 --- a/app/src/infrastructure/plc_communication_service.cpp +++ b/app/src/infrastructure/plc_communication_service.cpp @@ -401,16 +401,6 @@ const std::string &PlcCommunicationService::lastError() const return last_error_; } -const PlcSerialConfiguration &PlcCommunicationService::configuration() const -{ - return configuration_; -} - -const std::vector &PlcCommunicationService::pollBlocks() const -{ - return poll_blocks_; -} - void PlcCommunicationService::setCallbacks( std::function state_changed, std::function initial_read_changed, diff --git a/app/src/infrastructure/plc_communication_service.h b/app/src/infrastructure/plc_communication_service.h index d1acd2b..420f494 100644 --- a/app/src/infrastructure/plc_communication_service.h +++ b/app/src/infrastructure/plc_communication_service.h @@ -58,10 +58,6 @@ public: PlcCommunicationError lastErrorType() const override; // 返回最近一次通信错误的文字 const std::string &lastError() const override; - // 返回当前使用的串口和 Modbus 参数 - const PlcSerialConfiguration &configuration() const; - // 返回当前拆分后的只读轮询计划,供诊断和边界测试使用 - const std::vector &pollBlocks() const; // 注册状态、首读、缓存更新和错误通知回调 void setCallbacks( std::function state_changed, diff --git a/app/src/infrastructure/plc_register_repository.cpp b/app/src/infrastructure/plc_register_repository.cpp index 1ebf26e..73d354c 100644 --- a/app/src/infrastructure/plc_register_repository.cpp +++ b/app/src/infrastructure/plc_register_repository.cpp @@ -136,10 +136,3 @@ void PlcRegisterRepository::invalidate() valid_bits_.fill(false); valid_words_.fill(false); } - -bool PlcRegisterRepository::hasAnyValidValue() const -{ - return std::any_of(valid_bits_.cbegin(), valid_bits_.cend(), [](bool value) { return value; }) - || std::any_of( - valid_words_.cbegin(), valid_words_.cend(), [](bool value) { return value; }); -} diff --git a/app/src/infrastructure/plc_register_repository.h b/app/src/infrastructure/plc_register_repository.h index ec62f8a..151b2bd 100644 --- a/app/src/infrastructure/plc_register_repository.h +++ b/app/src/infrastructure/plc_register_repository.h @@ -35,8 +35,6 @@ public: void updateWord(int address, std::int16_t value); // 通信会话失效时清除“已读”标记,但保留数组内的旧值用于诊断 void invalidate(); - // 判断是否至少收到过一个有效 M/D 值 - bool hasAnyValidValue() const; private: // M/D 区共用的数组长度,覆盖 0 到最大地址 diff --git a/app/src/services/hmi_editor_service.h b/app/src/services/hmi_editor_service.h index 2f84891..7abc6e8 100644 --- a/app/src/services/hmi_editor_service.h +++ b/app/src/services/hmi_editor_service.h @@ -61,7 +61,7 @@ public: */ explicit HmiEditorService( ProjectService &project_service, - const HmiDefaultSettings &hmi_defaults = defaultHmiSettings()); + const HmiDefaultSettings &hmi_defaults); /** * @brief 按页面标识查找只读页面 diff --git a/app/src/services/logic_editor_service.cpp b/app/src/services/logic_editor_service.cpp index aec77d3..6d28471 100644 --- a/app/src/services/logic_editor_service.cpp +++ b/app/src/services/logic_editor_service.cpp @@ -1245,46 +1245,6 @@ LogicEditorResult LogicEditorService::updateRungComment( return {true, LogicEditorError::None, {}, rung_id}; } -LogicEditorResult LogicEditorService::setConditionAtColumn( - const std::string &logic_id, - const std::string &rung_id, - int column, - const LogicNodeConfig &config, - bool configured) -{ - const LadderCell *cell = findCell(logic_id, rung_id, column); - if (cell == nullptr) - { - return failure(LogicEditorError::CellNotFound, "未找到目标条件网格"); - } - if (!isConditionConfig(config)) - { - return failure(LogicEditorError::InvalidNode, "条件区只能放置条件指令"); - } - LogicNode candidate{"candidate", config, configured}; - std::string error; - if (!candidate.validate(&error)) - { - return failure(LogicEditorError::InvalidNode, error); - } - HistoryState before = captureState(); - const bool modified_before = project_service_.isModified(); - Project &project = project_service_.editProject(); - ControlLogic *logic = editableLogic(&project, logic_id); - LadderRung *rung = editableRung(logic, rung_id); - LadderCell &editable_cell = rung->cells[static_cast(column)]; - const std::string node_id = makeUniqueId(*logic, nodePrefix(config)); - editable_cell.kind = LadderCellKind::Node; - editable_cell.node = LogicNode{node_id, config, configured}; - if (!logic->validateStructure(project_service_.projectLimits(), &error)) - { - rollbackEdit(std::move(before), modified_before); - return failure(LogicEditorError::InvalidOperation, error); - } - recordHistory(std::move(before)); - return {true, LogicEditorError::None, {}, node_id}; -} - LogicEditResult LogicEditorService::applyConditionAndAdvance( const std::string &logic_id, const LogicEditCursor &cursor, @@ -1295,211 +1255,6 @@ LogicEditResult LogicEditorService::applyConditionAndAdvance( logic_id, cursor, &config, configured); } -LogicEditorResult LogicEditorService::insertConditionAtColumn( - const std::string &logic_id, - const std::string &rung_id, - int column, - const LogicNodeConfig &config, - bool configured) -{ - const LadderRung *existing = findRung(logic_id, rung_id); - if (existing == nullptr || column < 0 - || column >= ProjectLimits::kMaximumConditionColumns) - { - return failure(LogicEditorError::CellNotFound, "未找到目标条件网格"); - } - if (!isConditionConfig(config)) - { - return failure(LogicEditorError::InvalidNode, "条件区只能放置条件指令"); - } - if (existing->cells.back().kind != LadderCellKind::Gap) - { - return failure( - LogicEditorError::InvalidOperation, - "第 10 列已有内容,无法继续向右插入"); - } - - HistoryState before = captureState(); - const bool modified_before = project_service_.isModified(); - Project &project = project_service_.editProject(); - ControlLogic *logic = editableLogic(&project, logic_id); - LadderRung *rung = editableRung(logic, rung_id); - for (int index = ProjectLimits::kMaximumConditionColumns - 1; - index > column; - --index) - { - rung->cells[static_cast(index)] = - std::move(rung->cells[static_cast(index - 1)]); - } - // 插入列会让右侧网格右移,边界 10 是固定输出侧边界,不能继续右移 - for (VerticalConnection &connection : logic->verticalConnections) - { - if ((connection.upperRungId == rung_id - || connection.lowerRungId == rung_id) - && connection.columnBoundary >= column - && connection.columnBoundary < ProjectLimits::kMaximumConditionColumns) - { - ++connection.columnBoundary; - } - } - LadderCell inserted; - inserted.id = makeUniqueId(*logic, "cell"); - inserted.kind = LadderCellKind::Node; - const std::string node_id = makeUniqueId(*logic, nodePrefix(config)); - inserted.node = LogicNode{node_id, config, configured}; - rung->cells[static_cast(column)] = std::move(inserted); - std::string error; - if (!logic->validateStructure(project_service_.projectLimits(), &error)) - { - rollbackEdit(std::move(before), modified_before); - return failure(LogicEditorError::InvalidOperation, error); - } - recordHistory(std::move(before)); - return {true, LogicEditorError::None, {}, node_id}; -} - -LogicEditorResult LogicEditorService::appendCondition( - const std::string &logic_id, - const std::string &rung_id, - const LogicNodeConfig &config, - bool configured) -{ - const ControlLogic *logic = findLogic(logic_id); - if (logic == nullptr) - { - return failure(LogicEditorError::LogicNotFound, "未找到控制逻辑"); - } - if (!isConditionConfig(config)) - { - return failure(LogicEditorError::InvalidNode, "条件区只能放置条件指令"); - } - std::string target_rung_id = rung_id; - if (target_rung_id.empty()) - { - if (logic->rungs.size() - >= project_service_.projectLimits().maximumRungsPerLogic - || totalRungCount(project_service_.project()) - >= ProjectLimits::kMaximumRungsPerProject) - { - return failure( - LogicEditorError::InvalidOperation, - "梯形图行数已经达到当前上限"); - } - // 新建首行和放置首个节点必须共用一条历史记录 - HistoryState before = captureState(); - const bool modified_before = project_service_.isModified(); - Project &project = project_service_.editProject(); - ControlLogic *editable = editableLogic(&project, logic_id); - target_rung_id = insertEmptyRungAt(editable, editable->rungs.size()); - LadderRung *created = editableRung(editable, target_rung_id); - const auto empty = std::find_if( - created->cells.begin(), created->cells.end(), - [](const LadderCell &cell) - { - return cell.kind == LadderCellKind::Gap; - }); - if (empty == created->cells.end()) - { - rollbackEdit(std::move(before), modified_before); - return failure( - LogicEditorError::InvalidOperation, - "条件区 10 列已经占满"); - } - const std::string node_id = makeUniqueId(*editable, nodePrefix(config)); - LadderCell &cell = created->cells[ - static_cast(std::distance(created->cells.begin(), empty))]; - cell.kind = LadderCellKind::Node; - cell.node = LogicNode{node_id, config, configured}; - std::string error; - if (!editable->validateStructure(project_service_.projectLimits(), &error)) - { - rollbackEdit(std::move(before), modified_before); - return failure(LogicEditorError::InvalidOperation, error); - } - recordHistory(std::move(before)); - return {true, LogicEditorError::None, {}, node_id}; - } - const LadderRung *rung = findRung(logic_id, target_rung_id); - if (rung == nullptr) - { - return failure(LogicEditorError::RungNotFound, "未找到梯形图行"); - } - const auto empty = std::find_if( - rung->cells.cbegin(), rung->cells.cend(), - [](const LadderCell &cell) { return cell.kind == LadderCellKind::Gap; }); - if (empty == rung->cells.cend()) - { - return failure(LogicEditorError::InvalidOperation, "条件区 10 列已经占满"); - } - return setConditionAtColumn( - logic_id, - target_rung_id, - static_cast(std::distance(rung->cells.cbegin(), empty)), - config, - configured); -} - -LogicEditorResult LogicEditorService::appendWire( - const std::string &logic_id, - const std::string &rung_id, - int column_span) -{ - const ControlLogic *logic = findLogic(logic_id); - if (logic == nullptr) - { - return failure(LogicEditorError::LogicNotFound, "未找到控制逻辑"); - } - if (column_span <= 0 - || column_span > ProjectLimits::kMaximumConditionColumns) - { - return failure(LogicEditorError::InvalidOperation, "横线参数无效"); - } - if (rung_id.empty()) - { - if (logic->rungs.size() - >= project_service_.projectLimits().maximumRungsPerLogic - || totalRungCount(project_service_.project()) - >= ProjectLimits::kMaximumRungsPerProject) - { - return failure( - LogicEditorError::InvalidOperation, - "梯形图行数已经达到当前上限"); - } - HistoryState before = captureState(); - const bool modified_before = project_service_.isModified(); - Project &project = project_service_.editProject(); - ControlLogic *editable = editableLogic(&project, logic_id); - const std::string new_rung_id = insertEmptyRungAt( - editable, editable->rungs.size()); - LadderRung *rung = editableRung(editable, new_rung_id); - const int first = ProjectLimits::kMaximumConditionColumns - column_span; - for (int column = first; - column < ProjectLimits::kMaximumConditionColumns; - ++column) - { - rung->cells[static_cast(column)].kind = - LadderCellKind::Wire; - } - std::string error; - if (!editable->validateStructure(project_service_.projectLimits(), &error)) - { - rollbackEdit(std::move(before), modified_before); - return failure(LogicEditorError::InvalidOperation, error); - } - recordHistory(std::move(before)); - return {true, LogicEditorError::None, {}, new_rung_id}; - } - const LadderRung *rung = findRung(logic_id, rung_id); - if (rung == nullptr) - { - return failure(LogicEditorError::RungNotFound, "未找到梯形图行"); - } - const int first = ProjectLimits::kMaximumConditionColumns - column_span; - return setHorizontalWireRange(logic_id, rung_id, first, - ProjectLimits::kMaximumConditionColumns - 1, - true); -} - LogicEditResult LogicEditorService::applyWireAndAdvance( const std::string &logic_id, const LogicEditCursor &cursor) @@ -3055,70 +2810,6 @@ bool LogicEditorService::areConditionNodesContiguous( == static_cast(columns.size()); } -LogicEditorResult LogicEditorService::pasteRung( - const std::string &logic_id, - const LadderRung &source) -{ - const ControlLogic *logic = findLogic(logic_id); - std::string error; - if (logic == nullptr) - { - return failure(LogicEditorError::LogicNotFound, "未找到控制逻辑"); - } - if (!source.validateStructure(&error)) - { - return failure(LogicEditorError::InvalidOperation, error); - } - if (logic->rungs.size() - >= project_service_.projectLimits().maximumRungsPerLogic - || totalRungCount(project_service_.project()) - >= ProjectLimits::kMaximumRungsPerProject) - { - return failure(LogicEditorError::InvalidOperation, "梯形图行数已经达到上限"); - } - HistoryState before = captureState(); - const bool modified_before = project_service_.isModified(); - Project &project = project_service_.editProject(); - ControlLogic *editable = editableLogic(&project, logic_id); - LadderRung pasted = makeEmptyRung(*editable, editable->rungs.size()); - pasted.comment = source.comment; - for (std::size_t column = 0U; column < source.cells.size(); ++column) - { - pasted.cells[column].kind = source.cells[column].kind; - if (source.cells[column].node.has_value()) - { - const LogicNode &source_node = *source.cells[column].node; - pasted.cells[column].node = LogicNode{ - makeUniqueId(*editable, nodePrefix(source_node.config)), - source_node.config, - source_node.configured}; - } - } - if (source.output.has_value()) - { - pasted.output = LogicNode{ - makeUniqueId(*editable, nodePrefix(source.output->config)), - source.output->config, - source.output->configured}; - } - const std::string pasted_id = pasted.id; - editable->rungs.push_back(std::move(pasted)); - refreshRungNames(editable); - if (!editable->validateStructure(project_service_.projectLimits(), &error)) - { - rollbackEdit(std::move(before), modified_before); - return failure(LogicEditorError::InvalidOperation, error); - } - recordHistory(std::move(before)); - return {true, LogicEditorError::None, {}, pasted_id}; -} - -LogicEditorResult LogicEditorService::removeNode( - const std::string &logic_id, const std::string &node_id) -{ - return removeNodes(logic_id, {node_id}); -} - LogicEditorResult LogicEditorService::removeNodes( const std::string &logic_id, const std::vector &node_ids) diff --git a/app/src/services/logic_editor_service.h b/app/src/services/logic_editor_service.h index a81fd00..839a24f 100644 --- a/app/src/services/logic_editor_service.h +++ b/app/src/services/logic_editor_service.h @@ -214,38 +214,17 @@ public: const std::string &rung_id, const std::string &comment); - LogicEditorResult setConditionAtColumn( - const std::string &logic_id, - const std::string &rung_id, - int column, - const LogicNodeConfig &config, - bool configured = false); LogicEditResult applyConditionAndAdvance( const std::string &logic_id, const LogicEditCursor &cursor, const LogicNodeConfig &config, bool configured = false); - LogicEditorResult insertConditionAtColumn( - const std::string &logic_id, - const std::string &rung_id, - int column, - const LogicNodeConfig &config, - bool configured = false); - LogicEditorResult appendCondition( - const std::string &logic_id, - const std::string &rung_id, - const LogicNodeConfig &config, - bool configured = false); LogicEditorResult setHorizontalWireRange( const std::string &logic_id, const std::string &rung_id, int first_column, int last_column, bool connected); - LogicEditorResult appendWire( - const std::string &logic_id, - const std::string &rung_id, - int column_span = 1); LogicEditResult applyWireAndAdvance( const std::string &logic_id, const LogicEditCursor &cursor); @@ -320,11 +299,6 @@ public: const std::string &logic_id, const std::string &rung_id, const std::vector &node_ids) const; - LogicEditorResult pasteRung( - const std::string &logic_id, - const LadderRung &source); - LogicEditorResult removeNode( - const std::string &logic_id, const std::string &node_id); LogicEditorResult removeNodes( const std::string &logic_id, const std::vector &node_ids); diff --git a/app/src/services/offline_simulation_service.cpp b/app/src/services/offline_simulation_service.cpp index 857559f..0acf05e 100644 --- a/app/src/services/offline_simulation_service.cpp +++ b/app/src/services/offline_simulation_service.cpp @@ -105,11 +105,6 @@ SimulationState OfflineSimulationService::state() const return state_; } -int OfflineSimulationService::scanIntervalMs() const -{ - return timer_.interval(); -} - std::uint64_t OfflineSimulationService::successfulScanCount() const { return successful_scan_count_; diff --git a/app/src/services/offline_simulation_service.h b/app/src/services/offline_simulation_service.h index 19b5a43..5f05515 100644 --- a/app/src/services/offline_simulation_service.h +++ b/app/src/services/offline_simulation_service.h @@ -64,8 +64,6 @@ public: // 返回当前离线仿真生命周期状态 SimulationState state() const; - // 返回当前定时器扫描周期,单位为毫秒 - int scanIntervalMs() const; // 返回本次会话成功完成的扫描轮数 std::uint64_t successfulScanCount() const; // 返回最近一次扫描错误;无错误时为成功结果 diff --git a/app/src/services/plc_communication_gateway.h b/app/src/services/plc_communication_gateway.h index 6b37fe2..ca3c73d 100644 --- a/app/src/services/plc_communication_gateway.h +++ b/app/src/services/plc_communication_gateway.h @@ -163,11 +163,7 @@ public: */ virtual PlcCommunicationResult setPollAddresses( const std::vector &addresses, - const std::vector &multi_word_ranges) - { - (void)multi_word_ranges; - return setPollAddresses(addresses); - } + const std::vector &multi_word_ranges) = 0; // 返回当前连接生命周期状态;Connected 不代表首读资格已经完成 virtual PlcConnectionState state() const = 0; diff --git a/app/src/services/project_service.cpp b/app/src/services/project_service.cpp index 668479a..0fb5089 100644 --- a/app/src/services/project_service.cpp +++ b/app/src/services/project_service.cpp @@ -24,11 +24,6 @@ std::string generateProjectId() } // namespace -ProjectService::ProjectService(ProjectStorage &storage) - : ProjectService(storage, defaultProjectLimitSettings()) -{ -} - ProjectService::ProjectService( ProjectStorage &storage, const ProjectLimitSettings &project_limits) @@ -49,11 +44,6 @@ Project &ProjectService::editProject() return project_; } -const std::string &ProjectService::currentFilePath() const -{ - return current_file_path_; -} - bool ProjectService::hasCurrentFile() const { return !current_file_path_.empty(); diff --git a/app/src/services/project_service.h b/app/src/services/project_service.h index ffebca8..f8f4760 100644 --- a/app/src/services/project_service.h +++ b/app/src/services/project_service.h @@ -27,8 +27,7 @@ struct ProjectOperationResult class ProjectService { public: - // 使用外部提供的工程存储实现创建服务 - explicit ProjectService(ProjectStorage &storage); + // 使用外部提供的工程存储实现和启动配置创建服务 ProjectService( ProjectStorage &storage, const ProjectLimitSettings &project_limits); @@ -37,7 +36,6 @@ public: const Project &project() const; // 获取服务层授权的可编辑引用,调用方必须随后完成校验 Project &editProject(); - const std::string ¤tFilePath() const; bool hasCurrentFile() const; bool isModified() const; const ProjectLimitSettings &projectLimits() const; diff --git a/app/src/services/register_monitor_service.cpp b/app/src/services/register_monitor_service.cpp index 6eaeb22..e95840c 100644 --- a/app/src/services/register_monitor_service.cpp +++ b/app/src/services/register_monitor_service.cpp @@ -225,18 +225,6 @@ RegisterMonitorWriteResult RegisterMonitorService::writeBit( return captureInitialBit(address, value, active_result); } -RegisterMonitorWriteResult RegisterMonitorService::writeWord( - const RegisterAddress &address, std::int16_t value) -{ - return writeNumeric(address, RegisterDataType::Int16, value); -} - -RegisterMonitorWriteResult RegisterMonitorService::writeFloat( - const RegisterAddress &address, float value) -{ - return writeNumeric(address, RegisterDataType::Float32, value); -} - RegisterMonitorWriteResult RegisterMonitorService::writeNumeric( const RegisterAddress &address, RegisterDataType data_type, double value) { diff --git a/app/src/services/register_monitor_service.h b/app/src/services/register_monitor_service.h index 91e5151..13efe12 100644 --- a/app/src/services/register_monitor_service.h +++ b/app/src/services/register_monitor_service.h @@ -91,10 +91,6 @@ public: * @param value 要写入的 D 字值 * @return 成功或包含底层 RegisterError 和用户可读文本的失败结果 */ - RegisterMonitorWriteResult writeWord( - const RegisterAddress &address, std::int16_t value); - RegisterMonitorWriteResult writeFloat( - const RegisterAddress &address, float value); RegisterMonitorWriteResult writeNumeric( const RegisterAddress &address, RegisterDataType data_type, double value); /** diff --git a/app/src/ui/logic_editor_widget.cpp b/app/src/ui/logic_editor_widget.cpp index f9967d2..c529ccc 100644 --- a/app/src/ui/logic_editor_widget.cpp +++ b/app/src/ui/logic_editor_widget.cpp @@ -840,11 +840,6 @@ void LogicEditorWidget::clearRuntimeTrace() rebuildScene(); } -bool LogicEditorWidget::runtimeTraceEnabled() const -{ - return runtime_trace_enabled_; -} - void LogicEditorWidget::reloadLogic() { setLogicId(logic_id_); diff --git a/app/src/ui/logic_editor_widget.h b/app/src/ui/logic_editor_widget.h index 4722819..b1d672f 100644 --- a/app/src/ui/logic_editor_widget.h +++ b/app/src/ui/logic_editor_widget.h @@ -40,7 +40,6 @@ public: const LogicTraceSnapshot &trace, const std::string &fault_node_id = {}); void clearRuntimeTrace(); - bool runtimeTraceEnabled() const; void reloadLogic(); void clearSelection(); void selectNode(const std::string &node_id); diff --git a/app/src/ui/main_window.cpp b/app/src/ui/main_window.cpp index ddbde8d..435147a 100644 --- a/app/src/ui/main_window.cpp +++ b/app/src/ui/main_window.cpp @@ -576,16 +576,6 @@ void MainWindow::closeEvent(QCloseEvent *event) QMainWindow::closeEvent(event); } -const std::string &MainWindow::currentHmiPageId() const -{ - return current_hmi_page_id_; -} - -const std::string &MainWindow::currentLogicId() const -{ - return current_logic_id_; -} - void MainWindow::configureActions() { mode_action_group_ = new QActionGroup(this); diff --git a/app/src/ui/main_window.h b/app/src/ui/main_window.h index 8ff4c12..a52a154 100644 --- a/app/src/ui/main_window.h +++ b/app/src/ui/main_window.h @@ -111,11 +111,6 @@ public: /** 释放窗口拥有的控制器和可选导航服务 */ ~MainWindow() override; - /** 返回当前选中的 HMI 页面标识 */ - const std::string ¤tHmiPageId() const; - /** 返回当前选中的控制逻辑标识 */ - const std::string ¤tLogicId() const; - protected: /** 处理主窗口及子控件的事件过滤请求 */ bool eventFilter(QObject *watched, QEvent *event) override; diff --git a/app/src/ui/project_workspace_controller.cpp b/app/src/ui/project_workspace_controller.cpp index 390922b..5623d5a 100644 --- a/app/src/ui/project_workspace_controller.cpp +++ b/app/src/ui/project_workspace_controller.cpp @@ -256,16 +256,6 @@ void ProjectWorkspaceController::refresh() updateActions(); } -const std::string &ProjectWorkspaceController::currentHmiPageId() const -{ - return current_hmi_page_id_; -} - -const std::string &ProjectWorkspaceController::currentLogicId() const -{ - return current_logic_id_; -} - void ProjectWorkspaceController::handleSelectionChanged() { QTreeWidgetItem *item = ui_.projectTree->currentItem(); diff --git a/app/src/ui/project_workspace_controller.h b/app/src/ui/project_workspace_controller.h index f191f90..541f7a3 100644 --- a/app/src/ui/project_workspace_controller.h +++ b/app/src/ui/project_workspace_controller.h @@ -89,11 +89,6 @@ public: /** 启用或停用当前选中的控制逻辑 */ void toggleSelectedLogicEnabled(); - /** 返回当前 HMI 页面标识 */ - const std::string ¤tHmiPageId() const; - /** 返回当前控制逻辑标识 */ - const std::string ¤tLogicId() const; - private: /** 工程树项目的三种业务类型 */ enum class ItemKind diff --git a/app/tests/alarm_service_tests.cpp b/app/tests/alarm_service_tests.cpp index 7cec25d..cef5fdf 100644 --- a/app/tests/alarm_service_tests.cpp +++ b/app/tests/alarm_service_tests.cpp @@ -33,7 +33,7 @@ const AlarmRecord *findRecord( void testAlarmDefinitionsAndRuntimeLifecycle() { TestProjectStorage storage; - ProjectService project_service(storage); + ProjectService project_service(storage, defaultProjectLimitSettings()); AlarmEditorService editor_service(project_service); VirtualRegisterRepository repository; AlarmService alarm_service(project_service, repository); diff --git a/app/tests/domain_tests.cpp b/app/tests/domain_tests.cpp index 61b8ea4..e5ace6c 100644 --- a/app/tests/domain_tests.cpp +++ b/app/tests/domain_tests.cpp @@ -159,13 +159,15 @@ void testMultiWordCodecsAndBlockAccess() VirtualRegisterRepository repository; const RegisterAddress d10{RegisterArea::D, 10}; - require(repository.writeWordPair(d10, Float32Codec::encode(-2.5f)).succeeded, + const auto float_words = Float32Codec::encode(-2.5f); + require(repository.writeWords( + d10, {float_words[0], float_words[1]}).succeeded, "virtual repository must write Float32 low and high words"); - const WordPairReadResult pair = repository.readWordPair(d10); + const WordsReadResult pair = repository.readWords(d10, 2); require(pair.succeeded && Float32Codec::decode(pair.values[0], pair.values[1]).value() == -2.5f, "virtual repository must read Float32 from two consecutive words"); - require(!repository.writeWordPair({RegisterArea::D, 4000}, {}).succeeded, + require(!repository.writeWords({RegisterArea::D, 4000}, {}).succeeded, "Float32 write at D4000 must be rejected"); require(repository.writeWords( {RegisterArea::D, 30}, @@ -294,12 +296,12 @@ void testMultiWordHmiBoundaries() display.binding = RegisterAddress{RegisterArea::D, 3999}; display.dataType = RegisterDataType::Float32; project.hmiPages.front().controls.push_back(display); - require(project.validate(), + require(project.validate(defaultProjectLimitSettings()), "Float32 D3999 must be valid and occupy D3999~D4000"); project.hmiPages.front().controls.back().binding = RegisterAddress{RegisterArea::D, 4000}; - require(!project.validate(), "Float32 D4000 must be rejected"); + require(!project.validate(defaultProjectLimitSettings()), "Float32 D4000 must be rejected"); project = makeValidProject(); HmiControl int32_display = display; @@ -307,10 +309,10 @@ void testMultiWordHmiBoundaries() int32_display.dataType = RegisterDataType::Int32; int32_display.binding = RegisterAddress{RegisterArea::D, 3999}; project.hmiPages.front().controls.push_back(int32_display); - require(project.validate(), "Int32 D3999 must be valid"); + require(project.validate(defaultProjectLimitSettings()), "Int32 D3999 must be valid"); project.hmiPages.front().controls.back().binding = RegisterAddress{RegisterArea::D, 4000}; - require(!project.validate(), "Int32 D4000 must be rejected"); + require(!project.validate(defaultProjectLimitSettings()), "Int32 D4000 must be rejected"); project = makeValidProject(); HmiControl double_display = display; @@ -318,13 +320,13 @@ void testMultiWordHmiBoundaries() double_display.dataType = RegisterDataType::Float64; double_display.binding = RegisterAddress{RegisterArea::D, 3996}; project.hmiPages.front().controls.push_back(double_display); - require(project.validate(), "Double D3996 must be a valid even start address"); + require(project.validate(defaultProjectLimitSettings()), "Double D3996 must be a valid even start address"); project.hmiPages.front().controls.back().binding = RegisterAddress{RegisterArea::D, 3997}; - require(!project.validate(), "Double D3997 must be rejected"); + require(!project.validate(defaultProjectLimitSettings()), "Double D3997 must be rejected"); project.hmiPages.front().controls.back().binding = RegisterAddress{RegisterArea::D, 3995}; - require(!project.validate(), "Double odd start addresses must be rejected"); + require(!project.validate(defaultProjectLimitSettings()), "Double odd start addresses must be rejected"); project = makeValidProject(); HmiControl invalid_display = display; @@ -332,7 +334,7 @@ void testMultiWordHmiBoundaries() invalid_display.binding.reset(); invalid_display.dataType = static_cast(99); project.hmiPages.front().controls.push_back(invalid_display); - require(!project.validate(), + require(!project.validate(defaultProjectLimitSettings()), "an unbound HMI draft must still reject an unknown numeric type"); project = makeValidProject(); @@ -342,12 +344,12 @@ void testMultiWordHmiBoundaries() second.id = "float-display-duplicate"; project.hmiPages.front().controls.push_back(first); project.hmiPages.front().controls.push_back(second); - require(project.validate(), + require(project.validate(defaultProjectLimitSettings()), "same Float32 start address and type may be bound more than once"); second.dataType = RegisterDataType::Int16; project.hmiPages.front().controls.back() = second; - require(!project.validate(), + require(!project.validate(defaultProjectLimitSettings()), "different HMI data types may not partially overlap"); for (const RegisterDataType type : { @@ -372,7 +374,7 @@ void testMultiWordHmiBoundaries() RegisterArea::D, 10 + registerDataTypeWordCount(type) - 1}}, true}; - require(!project.validate(), + require(!project.validate(defaultProjectLimitSettings()), "16-bit instructions must not write inside any multi-word HMI range"); } } @@ -386,28 +388,28 @@ void testHmiAppearancePropertyBoundaries() button.properties[HmiAppearanceProperty::kFontSize] = "18"; button.properties[HmiAppearanceProperty::kFontBold] = "true"; button.properties[HmiAppearanceProperty::kFontItalic] = "false"; - require(project.validate(), "valid HMI appearance properties must pass validation"); + require(project.validate(defaultProjectLimitSettings()), "valid HMI appearance properties must pass validation"); button.properties[HmiAppearanceProperty::kTextColor] = "red"; - require(!project.validate(), "text colors must use the #RRGGBB format"); + require(!project.validate(defaultProjectLimitSettings()), "text colors must use the #RRGGBB format"); project = makeValidProject(); HmiControl &font_control = project.hmiPages.front().controls.front(); font_control.properties[HmiAppearanceProperty::kFontSize] = "5"; - require(!project.validate(), "font sizes below the minimum must be rejected"); + require(!project.validate(defaultProjectLimitSettings()), "font sizes below the minimum must be rejected"); font_control.properties[HmiAppearanceProperty::kFontSize] = "73"; - require(!project.validate(), "font sizes above the maximum must be rejected"); + require(!project.validate(defaultProjectLimitSettings()), "font sizes above the maximum must be rejected"); font_control.properties[HmiAppearanceProperty::kFontSize] = "large"; - require(!project.validate(), "non-numeric font sizes must be rejected"); + require(!project.validate(defaultProjectLimitSettings()), "non-numeric font sizes must be rejected"); project = makeValidProject(); HmiControl &style_control = project.hmiPages.front().controls.front(); style_control.properties[HmiAppearanceProperty::kFontBold] = "yes"; - require(!project.validate(), "font style flags must be true or false"); + require(!project.validate(defaultProjectLimitSettings()), "font style flags must be true or false"); project = makeValidProject(); project.hmiPages.front().controls.front().properties["legacyColor"] = "green"; - require(project.validate(), "unknown HMI extension properties must remain supported"); + require(project.validate(defaultProjectLimitSettings()), "unknown HMI extension properties must remain supported"); } Project makeValidProject() @@ -475,11 +477,11 @@ void testMultiPageAndLogicDomainRules() label.type = HmiControlType::Label; label.text = "Machine"; project.hmiPages.front().controls.push_back(label); - require(project.validate(), "an unbound label must be a valid static control"); + require(project.validate(defaultProjectLimitSettings()), "an unbound label must be a valid static control"); project.hmiPages.front().controls.back().binding = RegisterAddress{RegisterArea::M, 10}; - require(!project.validate(), "labels must reject register bindings"); + require(!project.validate(defaultProjectLimitSettings()), "labels must reject register bindings"); project = makeValidProject(); project.hmiPages.push_back(settings); @@ -489,21 +491,21 @@ void testMultiPageAndLogicDomainRules() jump.text = "Settings"; jump.pageJump = HmiPageJumpConfig{settings.id}; project.hmiPages.front().controls.push_back(jump); - require(project.validate() && project.validateForRunning(), + require(project.validate(defaultProjectLimitSettings()) && project.validateForRunning(defaultProjectLimitSettings()), "a page jump must resolve its target by stable page id"); project.hmiPages.front().controls.back().pageJump->targetPageId = "missing"; - require(!project.validate(), "a page jump must reject a missing target page"); + require(!project.validate(defaultProjectLimitSettings()), "a page jump must reject a missing target page"); project = makeValidProject(); project.initialHmiPageId = "missing"; - require(!project.validate(), "the initial HMI page id must resolve to a page"); + require(!project.validate(defaultProjectLimitSettings()), "the initial HMI page id must resolve to a page"); project = makeValidProject(); HmiPage duplicate_name = settings; duplicate_name.name = project.hmiPages.front().name; project.hmiPages.push_back(duplicate_name); - require(!project.validate(), "HMI page names must be unique"); + require(!project.validate(defaultProjectLimitSettings()), "HMI page names must be unique"); project = makeValidProject(); ControlLogic disabled_draft; @@ -515,17 +517,17 @@ void testMultiPageAndLogicDomainRules() draft_rung.name = "Draft network"; disabled_draft.rungs.push_back(std::move(draft_rung)); project.controlLogics.push_back(disabled_draft); - require(project.validateForRunning(), + require(project.validateForRunning(defaultProjectLimitSettings()), "a disabled draft logic must not block offline running"); project.controlLogics.back().name = project.controlLogics.front().name; - require(!project.validate(), "control logic names must be unique"); + require(!project.validate(defaultProjectLimitSettings()), "control logic names must be unique"); project = makeValidProject(); project.hmiPages.front().controls.front().type = static_cast(99); project.hmiPages.front().controls.front().binding.reset(); - require(!project.validate(), "unknown HMI control types must be rejected"); + require(!project.validate(defaultProjectLimitSettings()), "unknown HMI control types must be rejected"); } void testQuantityBoundaries() @@ -540,9 +542,9 @@ void testQuantityBoundaries() 400, {}}); } - require(project.validate(), "an HMI page count at the configured limit must be accepted"); + require(project.validate(defaultProjectLimitSettings()), "an HMI page count at the configured limit must be accepted"); project.hmiPages.push_back({"page-over", "Page over", 800, 400, {}}); - require(!project.validate(), "an HMI page count of 129 must be rejected"); + require(!project.validate(defaultProjectLimitSettings()), "an HMI page count of 129 must be rejected"); project = makeValidProject(); project.hmiPages.clear(); @@ -569,7 +571,7 @@ void testQuantityBoundaries() project.hmiPages.push_back(std::move(page)); } project.initialHmiPageId = project.hmiPages.front().id; - require(!project.validate(), + require(!project.validate(defaultProjectLimitSettings()), "an HMI control count over the project limit must be rejected"); project = makeValidProject(); @@ -585,14 +587,14 @@ void testQuantityBoundaries() label.text = "label"; project.hmiPages.front().controls.push_back(std::move(label)); } - require(project.validate(), "a page control count at the configured limit must be accepted"); + require(project.validate(defaultProjectLimitSettings()), "a page control count at the configured limit must be accepted"); HmiControl extra_label; extra_label.id = "label-over"; extra_label.type = HmiControlType::Label; extra_label.bounds = {0, 0, 1, 1}; extra_label.text = "label"; project.hmiPages.front().controls.push_back(std::move(extra_label)); - require(!project.validate(), "a page control count of 513 must be rejected"); + require(!project.validate(defaultProjectLimitSettings()), "a page control count of 513 must be rejected"); project = makeValidProject(); project.controlLogics.clear(); @@ -616,24 +618,24 @@ void testQuantityBoundaries() } project.controlLogics.push_back(std::move(logic)); } - require(!project.validate(), + require(!project.validate(defaultProjectLimitSettings()), "a ladder rung count over the project limit must be rejected"); project = makeValidProject(); project.hmiPages.front().width = ProjectLimits::kMaximumHmiPageWidth; project.hmiPages.front().height = ProjectLimits::kMaximumHmiPageHeight; - require(project.validate(), "an HMI page size of 1600 by 800 must be accepted"); + require(project.validate(defaultProjectLimitSettings()), "an HMI page size of 1600 by 800 must be accepted"); project.hmiPages.front().width = ProjectLimits::kMaximumHmiPageWidth + 1; - require(!project.validate(), "an HMI page width of 1601 must be rejected"); + require(!project.validate(defaultProjectLimitSettings()), "an HMI page width of 1601 must be rejected"); project.hmiPages.front().width = ProjectLimits::kMinimumHmiPageWidth - 1; - require(!project.validate(), "an HMI page width of 319 must be rejected"); + require(!project.validate(defaultProjectLimitSettings()), "an HMI page width of 319 must be rejected"); project.hmiPages.front().width = ProjectLimits::kMaximumHmiPageWidth; project.hmiPages.front().height = ProjectLimits::kMinimumHmiPageHeight - 1; - require(!project.validate(), "an HMI page height of 199 must be rejected"); + require(!project.validate(defaultProjectLimitSettings()), "an HMI page height of 199 must be rejected"); project = makeValidProject(); project.controlLogics.front().rungs.front().cells.pop_back(); - require(!project.validate(), + require(!project.validate(defaultProjectLimitSettings()), "a ladder row with fewer than ten cells must be rejected"); } @@ -686,19 +688,19 @@ void testEdgeAndCommentBoundaries() comment_rung.id = "comment-rung"; comment_rung.name = "Comment rung"; comment_rung.comment.assign(ProjectLimits::kMaximumRungCommentBytes, 'a'); - require(comment_rung.validate(), "a rung comment at the byte limit must be valid"); + require(comment_rung.validateStructure(), "a rung comment at the byte limit must be valid"); comment_rung.comment.push_back('a'); - require(!comment_rung.validate(), "a rung comment above the byte limit must fail"); + require(!comment_rung.validateStructure(), "a rung comment above the byte limit must fail"); comment_rung.comment = "第一行\n第二行"; - require(!comment_rung.validate(), "a multiline rung comment must be rejected"); + require(!comment_rung.validateStructure(), "a multiline rung comment must be rejected"); comment_rung.comment = "第一行\r第二行"; - require(!comment_rung.validate(), "a rung comment containing CR must be rejected"); + require(!comment_rung.validateStructure(), "a rung comment containing CR must be rejected"); Project project = makeValidProject(); project.registerComments = { {RegisterAddress{RegisterArea::M, 0}, "启动按钮"}, {RegisterAddress{RegisterArea::M, 0}, "重复地址"}}; - require(!project.validate(), "duplicate register comments must be rejected"); + require(!project.validate(defaultProjectLimitSettings()), "duplicate register comments must be rejected"); } void testDataInstructionBoundaries() @@ -772,13 +774,13 @@ void testLadderLogicBoundaries() logic.verticalConnections = { {"vertical-left", "rung-1", "rung-2", 0}, {"vertical-right", "rung-1", "rung-2", 1}}; - require(logic.validate() && logic.validateForRunning(), + require(logic.validate(defaultProjectLimitSettings()) && logic.validateForRunning(defaultProjectLimitSettings()), "a ten-cell grid with adjacent vertical edges must be valid"); logic.rungs.front().cells[5].kind = LadderCellKind::Gap; std::string connectivity_error; require( - logic.validate() && !logic.validateForRunning(&connectivity_error) + logic.validate(defaultProjectLimitSettings()) && !logic.validateForRunning(&connectivity_error) && connectivity_error.find("第 1 行") != std::string::npos && connectivity_error.find("第 6 列") != std::string::npos, "a disconnected output must report its visual row and break column"); @@ -794,7 +796,7 @@ void testLadderLogicBoundaries() {"vertical-left", "rung-1", "rung-2", 0}, {"vertical-bypass", "rung-1", "rung-2", 6}}; require( - logic.validateForRunning(), + logic.validateForRunning(defaultProjectLimitSettings()), "a vertical branch that bypasses a gap must keep the output reachable"); logic.rungs = {upper, lower}; @@ -806,7 +808,7 @@ void testLadderLogicBoundaries() CoilNodeConfig{ RegisterAddress{RegisterArea::M, 2}, CoilMode::Normal}, true}; - require(!logic.validate(), "a condition cell must reject output nodes"); + require(!logic.validate(defaultProjectLimitSettings()), "a condition cell must reject output nodes"); logic.rungs.front() = upper; logic.rungs.front().output = LogicNode{ @@ -814,25 +816,25 @@ void testLadderLogicBoundaries() ContactNodeConfig{ RegisterAddress{RegisterArea::M, 2}, ContactMode::NormallyOpen}, true}; - require(!logic.validate(), "the output slot must reject condition nodes"); + require(!logic.validate(defaultProjectLimitSettings()), "the output slot must reject condition nodes"); logic.rungs.front() = upper; logic.rungs.front().output.reset(); - require(logic.validate() && logic.validateForRunning(), + require(logic.validate(defaultProjectLimitSettings()) && logic.validateForRunning(defaultProjectLimitSettings()), "a row without an output may act as a connected branch"); logic.rungs.front() = upper; logic.rungs.front().cells[1].id = logic.rungs.front().cells[0].id; - require(!logic.validate(), "cell ids must be unique within a logic"); + require(!logic.validate(defaultProjectLimitSettings()), "cell ids must be unique within a logic"); logic.rungs.front() = upper; logic.verticalConnections.front().lowerRungId = "missing-rung"; - require(!logic.validate(), "vertical edges must reference adjacent rows"); + require(!logic.validate(defaultProjectLimitSettings()), "vertical edges must reference adjacent rows"); logic.verticalConnections = { {"vertical-left", "rung-1", "rung-2", 0}, {"vertical-copy", "rung-1", "rung-2", 0}}; - require(!logic.validate(), + require(!logic.validate(defaultProjectLimitSettings()), "one row boundary must not contain duplicate vertical edges"); } @@ -840,38 +842,38 @@ void testModelsValidateBindingsAndIdentifiers() { // 聚合验证必须拒绝错误绑定、重复标识和越界控件 Project project = makeValidProject(); - require(project.validate(), "valid project model must pass validation"); + require(project.validate(defaultProjectLimitSettings()), "valid project model must pass validation"); project.hmiPages.front().controls.front().binding = RegisterAddress{RegisterArea::D, 0}; - require(!project.validate(), "button bound to D area must be rejected"); + require(!project.validate(defaultProjectLimitSettings()), "button bound to D area must be rejected"); project = makeValidProject(); project.hmiPages.push_back(project.hmiPages.front()); - require(!project.validate(), "duplicate HMI page id must be rejected"); + require(!project.validate(defaultProjectLimitSettings()), "duplicate HMI page id must be rejected"); project = makeValidProject(); project.hmiPages.front().controls.front().bounds.x = -1; - require(!project.validate(), "controls outside the page must be rejected"); + require(!project.validate(defaultProjectLimitSettings()), "controls outside the page must be rejected"); project = makeValidProject(); project.hmiPages.front().controls.front().bounds.width = 801; - require(!project.validate(), "controls wider than the page must be rejected"); + require(!project.validate(defaultProjectLimitSettings()), "controls wider than the page must be rejected"); project = makeValidProject(); project.hmiPages.front().controls.front().properties.emplace("", "value"); - require(!project.validate(), "empty HMI property names must be rejected"); + require(!project.validate(defaultProjectLimitSettings()), "empty HMI property names must be rejected"); project = makeValidProject(); project.hmiPages.front().controls.front().binding.reset(); - require(project.validate(), "unbound HMI control must be accepted in a draft"); - require(!project.validateForRunning(), + require(project.validate(defaultProjectLimitSettings()), "unbound HMI control must be accepted in a draft"); + require(!project.validateForRunning(defaultProjectLimitSettings()), "unbound HMI control must block runtime validation"); project = makeValidProject(); project.controlLogics.front().rungs.front().output->configured = false; - require(project.validate(), "unconfigured ladder node must be accepted in a draft"); - require(!project.validateForRunning(), + require(project.validate(defaultProjectLimitSettings()), "unconfigured ladder node must be accepted in a draft"); + require(!project.validateForRunning(defaultProjectLimitSettings()), "unconfigured ladder node must block runtime validation"); } diff --git a/app/tests/hmi_editor_service_tests.cpp b/app/tests/hmi_editor_service_tests.cpp index f5a8165..a48b016 100644 --- a/app/tests/hmi_editor_service_tests.cpp +++ b/app/tests/hmi_editor_service_tests.cpp @@ -24,8 +24,8 @@ void testControlEditing() { // 覆盖控件创建、移动、绑定校验、重命名冲突和删除流程 TestProjectStorage storage; - ProjectService project_service(storage); - HmiEditorService service(project_service); + ProjectService project_service(storage, defaultProjectLimitSettings()); + HmiEditorService service(project_service, HmiDefaultSettings{}); const HmiEditorResult page_result = service.ensureDefaultPage(); require(page_result.succeeded, "default HMI page creation must succeed"); @@ -190,8 +190,8 @@ void testRuntimeUsesRegisterRepository() void testHistoryAndAtomicBatchDelete() { TestProjectStorage storage; - ProjectService project_service(storage); - HmiEditorService service(project_service); + ProjectService project_service(storage, defaultProjectLimitSettings()); + HmiEditorService service(project_service, HmiDefaultSettings{}); const std::string page_id = service.ensureDefaultPage().id; const HmiEditorResult first = service.addControl(page_id, HmiControlType::Label); const HmiEditorResult second = service.addControl(page_id, HmiControlType::Label); @@ -244,8 +244,8 @@ void testHistoryAndAtomicBatchDelete() void testBatchPasteControls() { TestProjectStorage storage; - ProjectService project_service(storage); - HmiEditorService service(project_service); + ProjectService project_service(storage, defaultProjectLimitSettings()); + HmiEditorService service(project_service, HmiDefaultSettings{}); const std::string page_id = service.ensureDefaultPage().id; const HmiEditorResult first = service.addControl( page_id, HmiControlType::NumericDisplay); @@ -290,8 +290,8 @@ void testBatchPasteControls() void testRuntimeValidationAndPasteBoundaries() { TestProjectStorage storage; - ProjectService project_service(storage); - HmiEditorService editor(project_service); + ProjectService project_service(storage, defaultProjectLimitSettings()); + HmiEditorService editor(project_service, HmiDefaultSettings{}); const std::string page_id = editor.ensureDefaultPage().id; HmiControl label; @@ -366,8 +366,8 @@ void testRuntimeValidationAndPasteBoundaries() void testAppearanceEditing() { TestProjectStorage storage; - ProjectService project_service(storage); - HmiEditorService service(project_service); + ProjectService project_service(storage, defaultProjectLimitSettings()); + HmiEditorService service(project_service, HmiDefaultSettings{}); const std::string page_id = service.ensureDefaultPage().id; const HmiEditorResult label = service.addControl(page_id, HmiControlType::Label); require(label.succeeded, "a label must be available for appearance editing"); @@ -407,8 +407,8 @@ void testAppearanceEditing() void testPageLifecycleAndNavigation() { TestProjectStorage storage; - ProjectService project_service(storage); - HmiEditorService service(project_service); + ProjectService project_service(storage, defaultProjectLimitSettings()); + HmiEditorService service(project_service, HmiDefaultSettings{}); const std::string main_id = service.ensureDefaultPage().id; require(project_service.project().initialHmiPageId == main_id, "the first page must become the initial HMI page"); @@ -474,8 +474,8 @@ void testPageLifecycleAndNavigation() void testPageResizeIsAtomicAndUndoable() { TestProjectStorage storage; - ProjectService project_service(storage); - HmiEditorService service(project_service); + ProjectService project_service(storage, defaultProjectLimitSettings()); + HmiEditorService service(project_service, HmiDefaultSettings{}); const std::string page_id = service.ensureDefaultPage().id; const HmiEditorResult label = service.addControl(page_id, HmiControlType::Label); require(label.succeeded, "page resize fixture must add a control"); diff --git a/app/tests/logic_editor_service_tests.cpp b/app/tests/logic_editor_service_tests.cpp index 4b99320..a5fb9c0 100644 --- a/app/tests/logic_editor_service_tests.cpp +++ b/app/tests/logic_editor_service_tests.cpp @@ -20,10 +20,22 @@ ContactNodeConfig contact( return {RegisterAddress{RegisterArea::M, address}, mode}; } +LogicEditorResult setConditionAtColumn( + LogicEditorService &editor, + const std::string &logic_id, + const std::string &rung_id, + int column, + const LogicNodeConfig &config, + bool configured) +{ + return editor.applyConditionAndAdvance( + logic_id, {rung_id, column, false}, config, configured).edit; +} + struct Fixture { TestSupport::InMemoryProjectStorage storage; - ProjectService projects{storage}; + ProjectService projects{storage, defaultProjectLimitSettings()}; LogicEditorService editor{projects}; std::string logicId; @@ -109,7 +121,7 @@ void testContinuousGridAndIndependentHorizontalWires() && !fixture.editor.canUndo(), "repeating an existing horizontal wire range must not dirty the project or history"); - const LogicEditorResult node = fixture.editor.setConditionAtColumn( + const LogicEditorResult node = setConditionAtColumn(fixture.editor, fixture.logicId, rung_id, 3, contact(3), true); require(node.succeeded, "a wire cell must accept a condition node"); require( @@ -211,39 +223,6 @@ void testInsertRowSplitsVerticalEdges() } } -void testInsertConditionMovesOnlyRelatedVerticalBoundaries() -{ - Fixture fixture; - const std::string first = fixture.addRung(); - const std::string second = fixture.addRung(); - const std::string third = fixture.addRung(); - const std::string fourth = fixture.addRung(); - require( - fixture.editor.setVerticalConnection( - fixture.logicId, first, second, 2, true).succeeded - && fixture.editor.setVerticalConnection( - fixture.logicId, first, second, 10, true).succeeded - && fixture.editor.setVerticalConnection( - fixture.logicId, third, fourth, 5, true).succeeded, - "fixture must create related, output-side, and unrelated edges"); - - const LogicEditorResult inserted = fixture.editor.insertConditionAtColumn( - fixture.logicId, first, 2, contact(12), true); - require(inserted.succeeded, - "inserting a condition into the grid must succeed"); - const ControlLogic *logic = fixture.editor.findLogic(fixture.logicId); - require( - connectionAt(*logic, first, second, 2) == nullptr - && connectionAt(*logic, first, second, 3) != nullptr, - "an edge at the insertion point must follow shifted grid content"); - require( - connectionAt(*logic, first, second, 10) != nullptr, - "the fixed output-side boundary must remain at column ten"); - require( - connectionAt(*logic, third, fourth, 5) != nullptr, - "inserting into one row must not move edges in unrelated rows"); -} - void testDeleteRowMergesOnlyContinuousEdges() { Fixture fixture; @@ -289,9 +268,9 @@ void testParallelBranchCreatesConnectedVisualRow() { Fixture fixture; const std::string source = fixture.addRung(); - const LogicEditorResult first = fixture.editor.setConditionAtColumn( + const LogicEditorResult first = setConditionAtColumn(fixture.editor, fixture.logicId, source, 2, contact(1), true); - const LogicEditorResult second = fixture.editor.setConditionAtColumn( + const LogicEditorResult second = setConditionAtColumn(fixture.editor, fixture.logicId, source, 3, contact(2), true); require(first.succeeded && second.succeeded, "fixture must place adjacent conditions"); @@ -323,9 +302,9 @@ void testParallelBranchReusesExistingEdges() Fixture fixture; const std::string source = fixture.addRung(); const std::string following = fixture.addRung(); - const LogicEditorResult first = fixture.editor.setConditionAtColumn( + const LogicEditorResult first = setConditionAtColumn(fixture.editor, fixture.logicId, source, 2, contact(4), true); - const LogicEditorResult second = fixture.editor.setConditionAtColumn( + const LogicEditorResult second = setConditionAtColumn(fixture.editor, fixture.logicId, source, 3, contact(5), true); require(first.succeeded && second.succeeded, "fixture must place the branch source conditions"); @@ -358,7 +337,7 @@ void testNodeDeletionAndHistoryAreAtomic() { Fixture fixture; const std::string rung_id = fixture.addRung(); - const LogicEditorResult node = fixture.editor.setConditionAtColumn( + const LogicEditorResult node = setConditionAtColumn(fixture.editor, fixture.logicId, rung_id, 0, contact(9), true); require(node.succeeded, "fixture must place a condition"); fixture.editor.clearHistory(); @@ -380,7 +359,7 @@ void testNodeDeletionAndHistoryAreAtomic() } require(fixture.editor.redo().succeeded, "the drag must redo atomically"); require( - fixture.editor.removeNode(fixture.logicId, node.id).succeeded, + fixture.editor.removeNodes(fixture.logicId, {node.id}).succeeded, "deleting a condition node must succeed"); rung = fixture.editor.findRung(fixture.logicId, rung_id); require( @@ -406,7 +385,7 @@ void testSelectionDeletionIsAtomic() const std::string upper = fixture.addRung(); const std::string lower = fixture.addRung(); require( - fixture.editor.setConditionAtColumn( + setConditionAtColumn(fixture.editor, fixture.logicId, upper, 0, contact(30), true).succeeded && fixture.editor.setHorizontalWireRange( fixture.logicId, upper, 1, 1, true).succeeded @@ -470,7 +449,7 @@ void testInvalidSelectionDeletionDoesNotMutateOrRecordHistory() Fixture fixture; const std::string rung_id = fixture.addRung(); require( - fixture.editor.setConditionAtColumn( + setConditionAtColumn(fixture.editor, fixture.logicId, rung_id, 0, contact(40), true).succeeded && fixture.editor.setHorizontalWireRange( fixture.logicId, rung_id, 1, 1, true).succeeded, @@ -564,11 +543,9 @@ void testProjectRungLimitAppliesToBranchAndPaste() { Fixture fixture; const std::string source = fixture.addRung(); - const LogicEditorResult node = fixture.editor.setConditionAtColumn( + const LogicEditorResult node = setConditionAtColumn(fixture.editor, fixture.logicId, source, 0, contact(20), true); require(node.succeeded, "fixture must create a branch source node"); - const LadderRung source_copy = *fixture.editor.findRung( - fixture.logicId, source); Project &project = fixture.projects.editProject(); std::size_t remaining = ProjectLimits::kMaximumRungsPerProject - 1U; @@ -601,16 +578,13 @@ void testProjectRungLimitAppliesToBranchAndPaste() remaining -= count; ++logic_index; } - require(project.validate(), + require(project.validate(defaultProjectLimitSettings()), "the project-wide rung limit fixture must itself be valid"); require( !fixture.editor.addParallelBranch( fixture.logicId, source, {node.id}, contact(21), true).succeeded, "parallel insertion must respect the project-wide rung limit"); - require( - !fixture.editor.pasteRung(fixture.logicId, source_copy).succeeded, - "row paste must respect the project-wide rung limit"); require( fixture.editor.findLogic(fixture.logicId)->rungs.size() == 1U, "failed limit checks must not partially add a row"); @@ -635,9 +609,9 @@ void testFirstEditOnEmptyLogicIsAtomic() Fixture fixture; fixture.editor.clearHistory(); - const LogicEditorResult condition = fixture.editor.appendCondition( + const LogicEditResult condition = fixture.editor.applyConditionAndAdvance( fixture.logicId, {}, contact(11), true); - require(condition.succeeded, + require(condition.edit.succeeded, "placing the first condition must create the first row"); const ControlLogic *logic = fixture.editor.findLogic(fixture.logicId); require(logic->rungs.size() == 1U @@ -653,10 +627,11 @@ void testFirstEditOnEmptyLogicIsAtomic() "redo must restore the atomically created first condition and row"); fixture.editor.clearHistory(); - const LogicEditorResult wire = fixture.editor.appendWire( - fixture.logicId, {}, 1); - require(wire.succeeded, - "appending a wire to an empty logic must create a first row"); + const std::string wire_rung = fixture.addRung(); + const LogicEditResult wire = fixture.editor.applyWireAndAdvance( + fixture.logicId, {wire_rung, 9, false}); + require(wire.edit.succeeded, + "a wire edit must succeed on a newly added row"); logic = fixture.editor.findLogic(fixture.logicId); require(logic->rungs.size() == 2U && logic->rungs.back().cells.back().kind == LadderCellKind::Wire, @@ -759,9 +734,9 @@ void testOutputAutomaticallyCompletesTrailingWires() Fixture trailing; const std::string rung_id = trailing.addRung(); require( - trailing.editor.setConditionAtColumn( + setConditionAtColumn(trailing.editor, trailing.logicId, rung_id, 0, contact(55), true).succeeded - && trailing.editor.setConditionAtColumn( + && setConditionAtColumn(trailing.editor, trailing.logicId, rung_id, 2, contact(56), true).succeeded, "the trailing-wire fixture must leave one intentional middle gap"); trailing.editor.clearHistory(); @@ -888,9 +863,9 @@ void testMixedAndSparseGridClipboardFragments() Fixture fixture; const std::string source = fixture.addRung(); const std::string target = fixture.addRung(); - const LogicEditorResult first = fixture.editor.setConditionAtColumn( + const LogicEditorResult first = setConditionAtColumn(fixture.editor, fixture.logicId, source, 0, contact(70), true); - const LogicEditorResult second = fixture.editor.setConditionAtColumn( + const LogicEditorResult second = setConditionAtColumn(fixture.editor, fixture.logicId, source, 2, contact(71), true); require( first.succeeded && second.succeeded @@ -941,7 +916,7 @@ void testMixedAndSparseGridClipboardFragments() require( sparse.editor.setHorizontalWireRange( sparse.logicId, sparse_source, 0, 0, true).succeeded - && sparse.editor.setConditionAtColumn( + && setConditionAtColumn(sparse.editor, sparse.logicId, sparse_source, 2, contact(72), true).succeeded && sparse.editor.setHorizontalWireRange( sparse.logicId, sparse_target, 5, 5, true).succeeded, @@ -973,7 +948,7 @@ void testGridClipboardFailuresAreAtomic() require( fixture.editor.setHorizontalWireRange( fixture.logicId, source, 0, 1, true).succeeded - && fixture.editor.setConditionAtColumn( + && setConditionAtColumn(fixture.editor, fixture.logicId, target, 4, contact(73), true).succeeded && fixture.editor.setHorizontalWireRange( fixture.logicId, target, 0, 0, true).succeeded, @@ -1168,7 +1143,7 @@ void testWholeRowClipboardInsertionAndLimit() const std::string first = fixture.addRung(); const std::string second = fixture.addRung(); const std::string target = fixture.addRung(); - const LogicEditorResult source_node = fixture.editor.setConditionAtColumn( + const LogicEditorResult source_node = setConditionAtColumn(fixture.editor, fixture.logicId, first, 0, contact(90), true); require( source_node.succeeded @@ -1439,7 +1414,6 @@ int main() testContinuousGridAndIndependentHorizontalWires(); testIndependentVerticalConnectionsAndNetworkSplit(); testInsertRowSplitsVerticalEdges(); - testInsertConditionMovesOnlyRelatedVerticalBoundaries(); testDeleteRowMergesOnlyContinuousEdges(); testParallelBranchCreatesConnectedVisualRow(); testParallelBranchReusesExistingEdges(); diff --git a/app/tests/offline_simulation_service_tests.cpp b/app/tests/offline_simulation_service_tests.cpp index 669b4a9..a030075 100644 --- a/app/tests/offline_simulation_service_tests.cpp +++ b/app/tests/offline_simulation_service_tests.cpp @@ -747,7 +747,6 @@ void testSimulationLifecycleSnapshotAndFault() require(simulation.start({program}).succeeded, "simulation must start"); require(simulation.state() == SimulationState::Running, "simulation must report running"); require(!readBit(repository, 0), "starting a session must clear virtual registers"); - require(simulation.scanIntervalMs() == 50, "default scan interval must be 50 ms"); writeBit(repository, 0, true); program.rungs.front().output = coil("changed-output", 9); require(simulation.executeOnce().succeeded, "manual lifecycle scan must succeed"); diff --git a/app/tests/plc_runtime_tests.cpp b/app/tests/plc_runtime_tests.cpp index 5f5cb68..748707e 100644 --- a/app/tests/plc_runtime_tests.cpp +++ b/app/tests/plc_runtime_tests.cpp @@ -64,6 +64,15 @@ public: return {true, {}}; } + PlcCommunicationResult setPollAddresses( + const std::vector &addresses, + const std::vector &ranges) override + { + poll_addresses = addresses; + poll_ranges = ranges; + return {true, {}}; + } + PlcConnectionState state() const override { return connection_state; } bool initialReadCompleted() const override { return initial_read; } PlcCommunicationError lastErrorType() const override { return last_error_type; } @@ -120,6 +129,7 @@ public: std::string last_error; PlcSerialConfiguration last_configuration; std::vector poll_addresses; + std::vector poll_ranges; std::function state_changed; std::function initial_read_changed; std::function cache_updated; @@ -211,14 +221,14 @@ void testPlcCacheAndWriteForwarding() && multi_word_write_count == 2, "Double words must be forwarded in one four-register request"); repository.invalidate(); - require(!repository.hasAnyValidValue(), - "disconnecting must invalidate all PLC cache validity flags"); + require(!repository.readWord(d0).succeeded, + "disconnecting must invalidate cached PLC values"); } void testRuntimeRepositorySwitchingAndDisconnect() { TestProjectStorage storage; - ProjectService project_service(storage); + ProjectService project_service(storage, defaultProjectLimitSettings()); HmiPage page; page.id = "main-page"; page.name = "Main"; @@ -287,7 +297,7 @@ void testRuntimeRepositorySwitchingAndDisconnect() void testRuntimeFaultRevokesOnlineReadinessAndAllowsReconnect() { TestProjectStorage storage; - ProjectService project_service(storage); + ProjectService project_service(storage, defaultProjectLimitSettings()); VirtualRegisterRepository virtual_repository; PlcRegisterRepository plc_repository; ActiveRegisterRepository active_repository(virtual_repository); @@ -502,13 +512,7 @@ void testPlcPollQuantityBoundaries() } require(service.setPollAddresses( double_boundary_addresses, - {{RegisterAddress{RegisterArea::D, 118}, 4}}).succeeded - && service.pollBlocks().size() == 2U - && service.pollBlocks()[0].area == RegisterArea::D - && service.pollBlocks()[0].startAddress == 0 - && service.pollBlocks()[0].count == 118 - && service.pollBlocks()[1].startAddress == 118 - && service.pollBlocks()[1].count == 6, + {{RegisterAddress{RegisterArea::D, 118}, 4}}).succeeded, "a Double at the 120-word boundary must stay in one Modbus read block"); std::vector unsplittable_ranges; diff --git a/app/tests/project_management_tests.cpp b/app/tests/project_management_tests.cpp index 842c95f..e3026c2 100644 --- a/app/tests/project_management_tests.cpp +++ b/app/tests/project_management_tests.cpp @@ -163,8 +163,8 @@ void testEmptyProjectRoundTrip() { QTemporaryDir directory; require(directory.isValid(), "temporary directory must be valid"); - JsonProjectStorage storage; - ProjectService service(storage); + JsonProjectStorage storage(defaultProjectLimitSettings()); + ProjectService service(storage, defaultProjectLimitSettings()); require(service.createNewProject("Empty project").succeeded, "empty project creation must succeed"); require(service.project().metadata.formatVersion == "2.0", @@ -184,10 +184,10 @@ void testGridProjectRoundTrip() { QTemporaryDir directory; require(directory.isValid(), "temporary directory must be valid"); - JsonProjectStorage storage; + JsonProjectStorage storage(defaultProjectLimitSettings()); const QString path = directory.filePath("grid.json"); const Project original = makeExampleProject(); - require(original.validate(), "the grid fixture must be structurally valid"); + require(original.validate(defaultProjectLimitSettings()), "the grid fixture must be structurally valid"); require(storage.save(original, path.toStdString()).succeeded, "the grid project must save"); @@ -225,7 +225,7 @@ void testMOffAlarmRoundTrip() { QTemporaryDir directory; require(directory.isValid(), "temporary directory must be valid"); - JsonProjectStorage storage; + JsonProjectStorage storage(defaultProjectLimitSettings()); const QString path = directory.filePath("m-off-alarm.json"); Project project = makeExampleProject(); project.alarmDefinitions.push_back({ @@ -254,7 +254,7 @@ void testMultiWordHmiDataTypeRoundTrip() { QTemporaryDir directory; require(directory.isValid(), "temporary directory must be valid"); - JsonProjectStorage storage; + JsonProjectStorage storage(defaultProjectLimitSettings()); const QString path = directory.filePath("multi-word-hmi.json"); Project project = makeExampleProject(); @@ -317,7 +317,7 @@ void testStrictVersionAndRequiredFields() { QTemporaryDir directory; require(directory.isValid(), "temporary directory must be valid"); - JsonProjectStorage storage; + JsonProjectStorage storage(defaultProjectLimitSettings()); const QString fixture_path = directory.filePath("fixture.json"); QJsonObject root = savedFixture( storage, makeExampleProject(), fixture_path); @@ -346,7 +346,7 @@ void testInvalidGridAndConnectionsAreRejected() { QTemporaryDir directory; require(directory.isValid(), "temporary directory must be valid"); - JsonProjectStorage storage; + JsonProjectStorage storage(defaultProjectLimitSettings()); const QString fixture_path = directory.filePath("fixture.json"); const QJsonObject original = savedFixture( storage, makeExampleProject(), fixture_path); @@ -417,7 +417,7 @@ void testProjectServiceStateAndConfiguredLimits() QTemporaryDir directory; require(directory.isValid(), "temporary directory must be valid"); const QString path = directory.filePath("project.json"); - JsonProjectStorage storage; + JsonProjectStorage storage(defaultProjectLimitSettings()); require(storage.save(makeExampleProject(), path.toStdString()).succeeded, "service fixture must save"); @@ -435,7 +435,7 @@ void testProjectServiceStateAndConfiguredLimits() && !limited_service.hasCurrentFile(), "a failed load must leave the current project untouched"); - ProjectService service(storage); + ProjectService service(storage, defaultProjectLimitSettings()); require(service.save().error == ProjectServiceError::FilePathRequired, "save without a current path must fail explicitly"); require(service.createNewProject("State project").succeeded, @@ -450,8 +450,8 @@ void testProjectServiceStateAndConfiguredLimits() void testRegisterCommentService() { - JsonProjectStorage storage; - ProjectService projects(storage); + JsonProjectStorage storage(defaultProjectLimitSettings()); + ProjectService projects(storage, defaultProjectLimitSettings()); RegisterCommentService comments(projects); require(comments.setComment( RegisterAddress{RegisterArea::D, 3}, " Target value ") diff --git a/app/tests/register_monitor_service_tests.cpp b/app/tests/register_monitor_service_tests.cpp index 6ac0d7f..e457f72 100644 --- a/app/tests/register_monitor_service_tests.cpp +++ b/app/tests/register_monitor_service_tests.cpp @@ -88,20 +88,24 @@ void testRegisterWrites() require(bit_write.succeeded && repository.readBit(m0).value, "monitor service must write M values through the active repository"); - const RegisterMonitorWriteResult word_write = service.writeWord(d0, -123); + const RegisterMonitorWriteResult word_write = service.writeNumeric( + d0, RegisterDataType::Int16, -123); require(word_write.succeeded && repository.readWord(d0).value == -123, "monitor service must write signed D values through the active repository"); const RegisterMonitorWriteResult wrong_bit = service.writeBit(d0, true); require(!wrong_bit.succeeded && wrong_bit.error == RegisterError::AreaMismatch, "monitor bit writes must reject D addresses"); - const RegisterMonitorWriteResult wrong_word = service.writeWord(m0, 123); + const RegisterMonitorWriteResult wrong_word = service.writeNumeric( + m0, RegisterDataType::Int16, 123); require(!wrong_word.succeeded && wrong_word.error == RegisterError::AreaMismatch, "monitor word writes must reject M addresses"); - require(service.writeWord(d0, static_cast(-32768)).succeeded + require(service.writeNumeric( + d0, RegisterDataType::Int16, static_cast(-32768)).succeeded && repository.readWord(d0).value == -32768 - && service.writeWord(d0, static_cast(32767)).succeeded + && service.writeNumeric( + d0, RegisterDataType::Int16, static_cast(32767)).succeeded && repository.readWord(d0).value == 32767, "monitor word writes must preserve signed 16-bit boundary values"); } @@ -116,8 +120,10 @@ void testOfflineInitialValueCapture() service.setOfflineInitialCaptureEnabled(true); require(service.writeBit({RegisterArea::M, 3}, true).succeeded - && service.writeWord({RegisterArea::D, 10}, -456).succeeded - && service.writeFloat({RegisterArea::D, 20}, 1.5f).succeeded + && service.writeNumeric( + {RegisterArea::D, 10}, RegisterDataType::Int16, -456).succeeded + && service.writeNumeric( + {RegisterArea::D, 20}, RegisterDataType::Float32, 1.5f).succeeded && service.writeNumeric( {RegisterArea::D, 30}, RegisterDataType::Int32, 305419896) .succeeded @@ -132,8 +138,8 @@ void testOfflineInitialValueCapture() require(initial_repository.readBit({RegisterArea::M, 3}).value && initial_repository.readWord({RegisterArea::D, 10}).value == -456 && std::fabs(Float32Codec::decode( - initial_repository.readWordPair({RegisterArea::D, 20}).values[0], - initial_repository.readWordPair({RegisterArea::D, 20}).values[1]) + initial_repository.readWords({RegisterArea::D, 20}, 2).values[0], + initial_repository.readWords({RegisterArea::D, 20}, 2).values[1]) .value_or(0.0f) - 1.5f) < 0.000001f, "editing monitor writes must be captured as offline initial values"); require(captured_int32.succeeded @@ -182,7 +188,8 @@ void testMultiWordMonitoring() && service.points().size() == 3U, "overlapping monitor removal must identify the point type"); - require(service.writeFloat({RegisterArea::D, 1}, -2.5f).succeeded, + require(service.writeNumeric( + {RegisterArea::D, 1}, RegisterDataType::Float32, -2.5f).succeeded, "Float32 monitor writes must use the pair write path"); std::vector values = service.values(false); require(values.front().state == MonitorValueState::Valid @@ -190,7 +197,8 @@ void testMultiWordMonitoring() std::get(values.front().numericValue) - (-2.5f)) < 0.000001f, "Float32 monitor values must decode the pair correctly"); - require(!service.writeFloat({RegisterArea::D, 4000}, 1.0f).succeeded, + require(!service.writeNumeric( + {RegisterArea::D, 4000}, RegisterDataType::Float32, 1.0f).succeeded, "Float32 monitor writes at D4000 must be rejected"); const RegisterMonitorResult int32_added = service.addRange( diff --git a/app/tests/runtime_mode_service_tests.cpp b/app/tests/runtime_mode_service_tests.cpp index 8be11dd..5e78c4d 100644 --- a/app/tests/runtime_mode_service_tests.cpp +++ b/app/tests/runtime_mode_service_tests.cpp @@ -138,7 +138,7 @@ void testModeTransitions() { // 验证服务将 PLC 首读状态与领域模式切换规则正确组合 TestProjectStorage storage; - ProjectService project_service(storage); + ProjectService project_service(storage, defaultProjectLimitSettings()); VirtualRegisterRepository virtual_repository; VirtualRegisterRepository plc_repository; ActiveRegisterRepository active_repository(virtual_repository); @@ -325,7 +325,7 @@ void testModeTransitions() void testMonitorPollRangeRollback() { TestProjectStorage storage; - ProjectService project_service(storage); + ProjectService project_service(storage, defaultProjectLimitSettings()); VirtualRegisterRepository virtual_repository; VirtualRegisterRepository plc_repository; ActiveRegisterRepository active_repository(virtual_repository); @@ -359,7 +359,7 @@ void testMonitorPollRangeRollback() void testDisconnectedOutputBlocksOfflineAndOnlineRuntime() { TestProjectStorage storage; - ProjectService project_service(storage); + ProjectService project_service(storage, defaultProjectLimitSettings()); VirtualRegisterRepository virtual_repository; VirtualRegisterRepository plc_repository; ActiveRegisterRepository active_repository(virtual_repository); diff --git a/app/tests/runtime_panel_controller_tests.cpp b/app/tests/runtime_panel_controller_tests.cpp index 17157db..c8b4d8c 100644 --- a/app/tests/runtime_panel_controller_tests.cpp +++ b/app/tests/runtime_panel_controller_tests.cpp @@ -42,6 +42,18 @@ namespace { +LogicEditorResult setConditionAtColumn( + LogicEditorService &editor, + const std::string &logic_id, + const std::string &rung_id, + int column, + const LogicNodeConfig &config, + bool configured) +{ + return editor.applyConditionAndAdvance( + logic_id, {rung_id, column, false}, config, configured).edit; +} + using TestProjectStorage = TestSupport::InMemoryProjectStorage; using TestSupport::require; @@ -98,7 +110,7 @@ void testMonitorOffersAllNumericTypes() void testAlarmConfigurationOffersMOnAndMOff() { TestProjectStorage storage; - ProjectService project_service(storage); + ProjectService project_service(storage, defaultProjectLimitSettings()); AlarmEditorService alarm_editor_service(project_service); AlarmConfigurationDialog dialog(alarm_editor_service); @@ -123,9 +135,9 @@ void testAlarmConfigurationOffersMOnAndMOff() void testAlarmListKeepsFixedGeometryWhileRecordsChange() { TestProjectStorage storage; - ProjectService project_service(storage); + ProjectService project_service(storage, defaultProjectLimitSettings()); VirtualRegisterRepository virtual_repository; - HmiEditorService hmi_editor_service(project_service); + HmiEditorService hmi_editor_service(project_service, HmiDefaultSettings{}); HmiRuntimeService hmi_runtime_service(virtual_repository); AlarmService alarm_service(project_service, virtual_repository); @@ -195,7 +207,7 @@ void testAlarmListKeepsFixedGeometryWhileRecordsChange() void testQueuedOfflineTraceIsIgnoredAfterReturningToEditing() { TestProjectStorage storage; - ProjectService project_service(storage); + ProjectService project_service(storage, defaultProjectLimitSettings()); VirtualRegisterRepository virtual_repository; OfflineSimulationService simulation_service(virtual_repository); OnlineLogicMonitorService online_monitor_service(virtual_repository); @@ -205,7 +217,7 @@ void testQueuedOfflineTraceIsIgnoredAfterReturningToEditing() logic_editor_service, simulation_service, online_monitor_service); - HmiEditorService hmi_editor_service(project_service); + HmiEditorService hmi_editor_service(project_service, HmiDefaultSettings{}); HmiRuntimeService hmi_runtime_service(virtual_repository); HmiNavigationService hmi_navigation_service(project_service); AlarmService alarm_service(project_service, virtual_repository); @@ -301,18 +313,31 @@ void testQueuedOfflineTraceIsIgnoredAfterReturningToEditing() controller.leaveRuntime( runtime_mode_service.mode(), runtime_mode_service.plcConnectionState()); logic_editor_widget.clearRuntimeTrace(); - require(!logic_editor_widget.runtimeTraceEnabled(), + const auto runtimeOutputIsActive = [runtime_logic_view] + { + for (QGraphicsItem *item : runtime_logic_view->scene()->items()) + { + if (item->data(0).toString() == QStringLiteral("output") + && item->data(1).toString() + == QStringLiteral("always-on-rung")) + { + return item->data(3).toBool() && item->data(4).toBool(); + } + } + return false; + }; + require(!runtimeOutputIsActive(), "editing transition must initially clear the runtime trace"); QCoreApplication::processEvents(QEventLoop::AllEvents); - require(!logic_editor_widget.runtimeTraceEnabled(), + require(!runtimeOutputIsActive(), "a queued offline scan must not restore the trace after returning to editing"); } void testLogicEditorGridSelectionAndDeletion() { TestProjectStorage storage; - ProjectService project_service(storage); + ProjectService project_service(storage, defaultProjectLimitSettings()); LogicEditorService logic_editor_service(project_service); const std::string logic_id = logic_editor_service.ensureDefaultLogic().id; @@ -441,7 +466,7 @@ void testLogicEditorGridSelectionAndDeletion() == LadderCellKind::Wire, "the horizontal-wire command must act on an explicitly selected cell"); - const LogicEditorResult first_node = logic_editor_service.setConditionAtColumn( + const LogicEditorResult first_node = setConditionAtColumn(logic_editor_service, logic_id, rung_id, 0, @@ -449,7 +474,7 @@ void testLogicEditorGridSelectionAndDeletion() RegisterAddress{RegisterArea::M, 0}, ContactMode::NormallyOpen}, true); - const LogicEditorResult second_node = logic_editor_service.setConditionAtColumn( + const LogicEditorResult second_node = setConditionAtColumn(logic_editor_service, logic_id, rung_id, 1, @@ -509,13 +534,13 @@ void testLogicEditorGridSelectionAndDeletion() void testLadderLayoutAndDragDeletion() { TestProjectStorage storage; - ProjectService project_service(storage); + ProjectService project_service(storage, defaultProjectLimitSettings()); LogicEditorService editor(project_service); const std::string logic_id = editor.ensureDefaultLogic().id; const std::string upper = editor.addRung(logic_id).id; const std::string lower = editor.addRung(logic_id).id; require( - editor.setConditionAtColumn( + setConditionAtColumn(editor, logic_id, upper, 0, @@ -688,7 +713,7 @@ void testLadderLayoutAndDragDeletion() void testWireGesturePreviewKeepsAtomicCommit() { TestProjectStorage storage; - ProjectService project_service(storage); + ProjectService project_service(storage, defaultProjectLimitSettings()); LogicEditorService editor(project_service); const std::string logic_id = editor.ensureDefaultLogic().id; const std::string first = editor.addRung(logic_id).id; @@ -842,7 +867,7 @@ void testWireGesturePreviewKeepsAtomicCommit() void testEscapeExitsMouseWireMode() { TestProjectStorage storage; - ProjectService project_service(storage); + ProjectService project_service(storage, defaultProjectLimitSettings()); LogicEditorService editor(project_service); LogicEditorWidget widget(editor); @@ -872,7 +897,7 @@ void testEscapeExitsMouseWireMode() void testCursorAdvanceAndInlineCommandInput() { TestProjectStorage storage; - ProjectService project_service(storage); + ProjectService project_service(storage, defaultProjectLimitSettings()); LogicEditorService editor(project_service); const std::string logic_id = editor.ensureDefaultLogic().id; LogicEditorWidget widget(editor); @@ -970,7 +995,7 @@ void testCursorAdvanceAndInlineCommandInput() void testVerticalWireShortcutAdvancesDownward() { TestProjectStorage storage; - ProjectService project_service(storage); + ProjectService project_service(storage, defaultProjectLimitSettings()); LogicEditorService editor(project_service); const std::string logic_id = editor.ensureDefaultLogic().id; std::vector rung_ids; @@ -1059,12 +1084,12 @@ void testVerticalWireShortcutAdvancesDownward() void testSegmentLevelTraceProjection() { TestProjectStorage storage; - ProjectService project_service(storage); + ProjectService project_service(storage, defaultProjectLimitSettings()); LogicEditorService editor(project_service); const std::string logic_id = editor.ensureDefaultLogic().id; const std::string upper = editor.addRung(logic_id).id; const std::string lower = editor.addRung(logic_id).id; - const LogicEditorResult condition = editor.setConditionAtColumn( + const LogicEditorResult condition = setConditionAtColumn(editor, logic_id, upper, 0, @@ -1157,7 +1182,7 @@ void testSegmentLevelTraceProjection() void testLogicClipboardUsesExplicitObjectAndRowSelection() { TestProjectStorage storage; - ProjectService project_service(storage); + ProjectService project_service(storage, defaultProjectLimitSettings()); LogicEditorService editor(project_service); const std::string logic_id = editor.ensureDefaultLogic().id; const std::string source = editor.addRung(logic_id).id;