#pragma once #include "domain/control_logic_model.h" #include "domain/register_repository.h" #include #include #include #include #include #include #include // 一轮软件逻辑扫描可能返回的错误 enum class LogicScanError { None, // 扫描成功 InvalidLogic, // 逻辑结构或运行配置无效 ConflictingOutput, // 多个输出以不兼容的线圈模式写入同一地址 RegisterReadFailed, // 扫描读取寄存器失败 RegisterWriteFailed // 扫描写入寄存器失败 }; // 扫描结果;失败时携带出错的逻辑、网络和节点 ID struct LogicScanResult { bool succeeded = false; // 本轮扫描是否完成 LogicScanError error = LogicScanError::None; // 失败时的分类 std::string message; // UTF-8 可读结果或失败原因 std::string logicId; // 失败关联的控制逻辑 ID std::string rungId; // 失败关联的网络 ID std::string nodeId; // 失败关联的节点 ID }; // TON 节点在最近一轮扫描中的可视化状态 struct TonTraceValue { bool input = false; // 本轮 TON 输入状态 bool done = false; // 当前是否已经到达预置时间 std::int64_t elapsedMs = 0; // 已累计时间,单位为毫秒 int presetMs = 0; // 预置时间,单位为毫秒 }; // CTU/CTD 节点在最近一轮扫描中的可视化状态 struct CounterTraceValue { bool input = false; // 本轮计数输入状态 bool reset = false; // 本轮复位输入状态 bool done = false; // 当前计数值是否达到预置值 std::int16_t value = 0; // 当前计数值 std::int16_t preset = 0; // 计数预置值 }; // MOVE/ADD/SUB 节点的结果值和溢出标记 struct WordTraceValue { std::int16_t value = 0; // MOVE/ADD/SUB 的结果值 bool overflow = false; // ADD/SUB 是否发生饱和溢出 }; // 一条控制逻辑的运行轨迹快照;各映射使用节点、表达式或网络 ID 作为键 struct LogicTraceValues { std::unordered_map nodeValues; // 节点最终逻辑值 std::unordered_map nodePowerValues; // 节点输入电源状态 std::unordered_map expressionValues; // 条件表达式最终值 std::unordered_map expressionInputValues; // 表达式输入状态 std::unordered_map expressionPowerValues; // 表达式输出电源状态 std::unordered_map rungValues; // 网络最终逻辑值 std::unordered_map tonValues; // TON 节点状态 std::unordered_map counterValues; // CTU/CTD 节点状态 std::unordered_map wordValues; // MOVE/ADD/SUB 节点状态 // 清除当前逻辑或网络的全部轨迹 void clear(); }; // 全工程轨迹;logicValues 按逻辑 ID 隔离,避免不同逻辑中的节点 ID 互相覆盖 struct LogicTraceSnapshot : LogicTraceValues { std::unordered_map logicValues; // 按逻辑 ID 保存的轨迹 // 清除全工程轨迹及按逻辑分组的轨迹 void clear(); // 返回指定逻辑的轨迹投影;不存在时返回空轨迹 LogicTraceSnapshot forLogic(const std::string &logic_id) const; }; // 按工程顺序执行受限梯形图的一次确定性扫描 class SoftwareLogicExecutor { public: using Clock = std::chrono::steady_clock; using TimePoint = Clock::time_point; /** * @brief 检查逻辑是否满足软件扫描的运行要求 * @param logics 待检查的控制逻辑集合 * @return 成功或包含逻辑、网络、节点定位信息的失败结果 * * 检查逻辑结构、启用逻辑的运行配置、共享输出冲突以及 T/C 资源引用 */ LogicScanResult validate(const std::vector &logics) const; /** * @brief 清除所有跨扫描运行状态 * * 重置边沿触发、TON 定时器和 CTU/CTD 计数器;不修改寄存器仓库和轨迹对象 */ void resetRuntime(); /** * @brief 使用当前 steady_clock 执行一轮完整扫描 * @param logics 按工程顺序扫描的控制逻辑集合 * @param repository 扫描读取和写入的寄存器仓库 * @param trace 可选的轨迹输出;非空时会先清空再写入本轮结果 * @return 扫描结果,失败时不会返回部分成功状态作为成功结果 */ LogicScanResult executeScan( const std::vector &logics, RegisterRepository &repository, LogicTraceSnapshot *trace = nullptr); /** * @brief 使用指定时间执行一轮扫描 * @param logics 按工程顺序扫描的控制逻辑集合 * @param repository 扫描读取和写入的寄存器仓库 * @param now 本轮扫描使用的单调时钟时间点 * @param trace 可选的轨迹输出;适合测试 TON 等时间相关逻辑 * @return 扫描结果 */ LogicScanResult executeScanAt( const std::vector &logics, RegisterRepository &repository, TimePoint now, LogicTraceSnapshot *trace = nullptr); private: // 单个 TON 定时器跨扫描保存的计时状态 struct TonRuntimeState { bool timing = false; // 是否正在累计输入有效时间 bool done = false; // 是否已经完成预置时间 TimePoint startedAt{}; // 本次计时开始时间 std::chrono::milliseconds elapsed{0}; // 最近一次计算的累计时间 }; /** * @brief 计算单个条件节点的导通结果 * @param logic_id 所属控制逻辑 ID,用于隔离边沿触点的跨扫描状态 * @param node 待读取和计算的条件节点 * @param repository 提供 M/D 值的寄存器仓库;T/C 条件读取执行器内部状态 * @param value 输出节点导通结果,不能为空 * @return 成功结果,或包含节点 ID 的逻辑/寄存器读取失败结果 */ LogicScanResult evaluateCondition( const std::string &logic_id, const LogicNode &node, RegisterRepository &repository, bool *value); /** * @brief 递归计算串联、并联或叶子条件表达式 * @param logic_id 所属控制逻辑 ID,用于传递给叶子节点状态计算 * @param expression 待读取的条件表达式树 * @param repository 提供 M/D 值的寄存器仓库 * @param trace 可选的轨迹输出,用于记录表达式值、电源输入和电源输出 * @param input_power 进入当前表达式的电源状态 * @param value 输出表达式最终值,不能为空 * @return 成功结果,或包含失败节点 ID 的扫描错误 */ LogicScanResult evaluateExpression( const std::string &logic_id, const ConditionExpression &expression, RegisterRepository &repository, LogicTraceValues *trace, bool input_power, bool *value); /** * @brief 在网络结果驱动下执行单个输出节点 * @param logic_id 所属控制逻辑 ID,用于隔离计数器等跨扫描状态 * @param node 待读取配置并执行的输出节点 * @param rung_value 当前网络的逻辑结果,决定输出是否动作 * @param repository 提供输出读写所需的 M/D 寄存器仓库 * @param now 本轮扫描的单调时钟时间点,TON 使用它计算累计时间 * @param trace 可选的轨迹输出,用于记录 TON、计数器和字操作结果 * @param output_value 输出节点最终逻辑值,不能为空 * @return 成功结果,或包含节点 ID 的逻辑/寄存器读写失败结果 */ LogicScanResult executeOutput( const std::string &logic_id, const LogicNode &node, bool rung_value, RegisterRepository &repository, TimePoint now, LogicTraceValues *trace, bool *output_value); /** * @brief 解析并读取一个字操作数 * @param operand 常量或 D 寄存器操作数配置 * @param repository 读取寄存器操作数时使用的仓库 * @param value 输出解析后的 int16 数值,不能为空 * @param node_id 关联节点 ID,用于构造可定位的失败结果 * @return 常量读取成功,或 D 寄存器读取/操作数类型校验失败 */ LogicScanResult readWordOperand( const WordOperand &operand, RegisterRepository &repository, std::int16_t *value, const std::string &node_id) const; // 按逻辑 ID 和节点 ID 保存上一轮边沿输入 std::map, bool> previous_edge_inputs_; // 按逻辑 ID 和节点 ID 保存上一轮计数输入 std::map, bool> previous_counter_inputs_; // 按定时器编号保存跨扫描状态 std::unordered_map ton_states_; // 单个计数器跨扫描保存的完成状态 struct CounterRuntimeState { bool done = false; // 是否已经达到预置值 }; std::unordered_map counter_states_; // 按计数器编号保存状态 };