# 综合平台编程器代码功能阅读清单 > 适用代码状态:2026-08-29 > > 这份文档用于按功能逐章阅读当前代码。它不是开发计划,也不是需求清单。这里列出的功能都能在当前源码中找到实现;每章最后的复选框用于记录阅读进度。 ## 1. 先看结论 当前代码可以按 **17 个功能模块** 阅读。前 4 章建立公共模型和工程基础,第 5~12 章是编辑功能,第 13~16 章是运行与 PLC 链路,第 17 章是用户运行程序交付链路。 | 顺序 | 功能模块 | 主要解决的问题 | | ---: | --- | --- | | 1 | 程序启动与应用配置 | 程序如何创建对象、加载配置并决定启动为编程器还是用户运行版 | | 2 | M/D 寄存器公共模型 | 地址、四种 D 数据类型、编解码和统一仓库接口如何工作 | | 3 | 工程聚合、数量边界与校验 | 一个工程保存什么,以及“可保存”和“可运行”的区别 | | 4 | 工程新建、保存、加载与 JSON | 工程操作如何原子完成,严格 JSON `4.0` 如何读写 | | 5 | 主窗口、工程树与编辑会话 | UI 如何组织页面、逻辑、属性面板和动作状态 | | 6 | HMI 页面管理 | 页面新增、改名、排序、尺寸、初始页和安全删除 | | 7 | HMI 控件编辑 | 8 类控件的添加、属性、绑定、移动、对齐、删除和历史 | | 8 | HMI 运行交互与页面导航 | 控件如何读取/写入活动仓库,按钮、状态文本和页面跳转如何运行 | | 9 | 报警、报警列表与地址注释 | 报警定义/记录以及 M/D 地址备注如何编辑和显示 | | 10 | 连续梯形图模型与逻辑组 | 10 个条件格、输出槽、竖线、网络和多逻辑组的数据结构 | | 11 | 梯形图画布与编辑操作 | 行、横竖线、框选、删除、复制粘贴、撤销重做和网络注释 | | 12 | 梯形图指令、命令语与检查 | 触点、线圈、比较、MOVE、ADD/SUB、连续输入和语法检查 | | 13 | 软件逻辑执行与离线仿真 | 扫描语义、边沿状态、运行轨迹、定时扫描和离线初始值 | | 14 | 数据监控 | 编辑态/运行态监控、批量地址、四种 D 类型和数据源显式切换 | | 15 | PLC 配置、搜索与 Modbus RTU | 串口参数、自动搜索、异步轮询、缓存、写入、故障和恢复 | | 16 | 运行模式与运行监控窗口 | 编辑/离线/真机切换、活动仓库、真机本地推算轨迹和运行大屏 | | 17 | 用户运行程序导出与启动 | HMI 工程裁剪、exe 尾部封装、依赖复制、自动连接和自动重连 | 推荐总顺序就是表格中的 `1 -> 17`。不要一上来通读 `main_window.cpp`:它是窗口级协调层,里面同时出现多数功能入口,缺少前面的模型和服务背景时很容易看乱。 ## 2. 全局阅读规则 ### 2.1 每个功能固定按这个方向看 ```text 领域模型/规则 -> 服务用例 -> 基础设施实现(若有) -> UI 入口/投影 -> 对应测试 ``` - `domain`:数据长什么样、什么情况算合法,不依赖 Qt、串口或文件系统 - `services`:一次完整业务操作怎么执行、失败如何回滚、需要哪些外部契约 - `infrastructure`:JSON、INI、exe 封装和 Qt Modbus 等技术实现 - `ui`:收集用户输入、调用服务、刷新画面,不应重新实现业务规则 - `tests`:当前代码真正承诺了哪些边界和失败行为 ### 2.2 每章怎样才算看完 每章至少做到下面四件事,再勾选章节完成项: 1. 能说出这个功能保存或操作的核心数据结构 2. 能从一个 UI 动作追到服务,再追到领域对象或基础设施 3. 能说出一个成功路径和两个主要失败路径 4. 能在对应测试中找到证明这些行为的用例 ### 2.3 开始前先读的三个入口 - [ ] 阅读 `docs/ai/handoff.md`,了解工作区当前状态和最近验证 - [ ] 阅读 `docs/architecture.md`,建立分层、数据流和运行模式概念 - [ ] 浏览 `app/src/main.cpp`,只看对象有哪些,先不要深入每个对象 --- ## 3. 功能 1:程序启动与应用配置 ### 功能范围 - 普通编程器读取可执行文件旁的 `config/application.ini` - HMI 专用运行版读取自己的 `config/runtime.ini` - 配置不存在时创建默认文件;严格校验失败时整份回退默认值 - 启动时识别当前 exe 是否封装了工程,决定进入普通编程器或用户运行版 - 在 `main.cpp` 中创建所有领域仓库、服务、基础设施和主窗口,并完成依赖注入 ### 建议阅读顺序 1. `app/src/services/application_settings.h` 2. `app/src/infrastructure/application_settings_loader.h/.cpp` 3. `app/src/infrastructure/runtime_settings_loader.h/.cpp` 4. `app/src/main.cpp` 5. `docs/用户使用/应用配置说明.md` ### 重点调用链 ```text main() -> RuntimeProjectBundleService::load() -> ApplicationSettingsLoader::load() 或 RuntimeSettingsLoader::load() -> 构造 ProjectService / 编辑服务 / 寄存器仓库 / 运行服务 -> RuntimeModeService::configurePlc() -> MainWindow -> QApplication::exec() ``` ### 阅读检查 - [ ] 能解释为什么用户运行版不加载完整的 `application.ini` - [ ] 能找到 `ProjectLimitSettings`、`HmiDefaultSettings` 和 PLC 默认参数的注入位置 - [ ] 能解释 `ActiveRegisterRepository`、虚拟仓库和 PLC 缓存为何都是独立对象 - [ ] 能解释配置警告为什么不阻止主窗口创建 - [ ] 查看 `application_settings_tests` 和 `runtime_settings_tests` --- ## 4. 功能 2:M/D 寄存器公共模型 ### 功能范围 - 支持 `M0~M4000` 位地址和 `D0~D4000` 字地址 - Modbus 地址使用从 `0` 开始的原始地址 - D 数值支持 `Int16`、`Int32`、`Float32`、`Float64` - 多字值按低地址低字顺序编码,分别占 1、2、2、4 个 D 字 - `Float64` 起始地址要求偶数;所有多字范围不能越过 D4000 - 虚拟仓库保存离线值,PLC 仓库保存最新读回缓存,活动仓库代理当前运行数据源 ### 建议阅读顺序 1. `app/src/domain/register_address.h/.cpp` 2. `app/src/domain/register_value_type.h/.cpp` 3. `app/src/domain/register_repository.h` 4. `app/src/domain/virtual_register_repository.h/.cpp` 5. `app/src/infrastructure/plc_register_repository.h/.cpp` 6. `app/src/domain/active_register_repository.h/.cpp` 7. `docs/二次开发/信捷D寄存器与浮点扩展说明.md` ### 重点调用链 ```text HMI / 监控 / 软件执行器 -> RegisterRepository -> VirtualRegisterRepository(离线) -> PlcRegisterRepository(真机读回缓存 + 异步写回调) -> ActiveRegisterRepository(按模式转发) ``` ### 阅读检查 - [ ] 能解释 `RegisterAddress` 如何区分 M/D 并校验 `0~4000` - [ ] 能手算 `D10` 上一个 Int32 或 Float32 会占用哪些字 - [ ] 能解释 `Int32Codec`、`Float32Codec`、`Float64Codec` 的低字在前规则 - [ ] 能解释 NaN/Inf 为什么被拒绝 - [ ] 能解释 PLC 写入成功提交后为什么不立即修改缓存 - [ ] 在 `domain_tests` 中查看地址边界和四种数据类型编解码测试 --- ## 5. 功能 3:工程聚合、数量边界与校验 ### 功能范围 - `Project` 聚合元数据、HMI 页面、初始页、报警、地址注释和控制逻辑 - 稳定 ID 用于引用,容器顺序决定页面和逻辑的显示/扫描顺序 - 所有硬上限集中在 `project_limits.h`,INI 只能收紧部分上限 - `validate()` 允许保存未配置完成的编辑草稿 - `validateForRunning()` 进一步拒绝未绑定控件、未配置节点和不可运行结构 - 工程级校验还负责跨对象规则:页面跳转目标、D 多字占用冲突、16 位指令写入保护、PLC 去重轮询地址上限 ### 建议阅读顺序 1. `app/src/domain/project_limits.h` 2. `app/src/domain/project_model.h/.cpp` 3. `app/src/domain/hmi_model.h/.cpp` 4. `app/src/domain/alarm_model.h/.cpp` 5. `app/src/domain/control_logic_model.h/.cpp` 6. `docs/用户使用/数量边界确认方案.md` ### 当前主要硬边界 | 项目 | 上限 | | --- | ---: | | HMI 页面 | 32 | | 每页 HMI 控件 | 128 | | 全工程 HMI 控件 | 2048 | | 报警定义 | 256 | | 控制逻辑组 | 32 | | 每组梯形图行 | 256 | | 全工程梯形图行 | 2048 | | 每行条件格 | 固定 10 | | 状态文本 D 区间 | 16 | | PLC 去重轮询地址 | 256 | | PLC 轮询块 | 8 | | 单个工程文件 | 16 MiB | ### 阅读检查 - [ ] 画出 `Project -> HmiPage -> HmiControl` 和 `Project -> ControlLogic -> LadderRung` 两棵对象树 - [ ] 能说清 `validate()` 与 `validateForRunning()` 的使用时机 - [ ] 找到 HMI 多字 D 范围重叠检查 - [ ] 找到 MOVE/ADD/SUB 不能写入多字 HMI 中间字的保护 - [ ] 找到工程引用地址去重后最多 256 个的校验 - [ ] 查看 `domain_tests` 中的模型、数量边界和运行可达性测试 --- ## 6. 功能 4:工程新建、保存、加载与 JSON ### 功能范围 - 新建工程、保存、另存为、加载和导出 JSON - 当前只接受严格 `formatVersion: "4.0"` - 旧 `1.0/2.0/3.0` 和未知版本直接拒绝,不迁移、不兼容 - 加载先解析到临时对象,全部成功后才替换当前工程 - 保存先做领域校验,再使用临时文件和原子提交 - 保存成功后更新当前文件路径和“未保存修改”状态;失败保持原状态 ### 建议阅读顺序 1. `app/src/domain/project_storage.h` 2. `app/src/services/project_service.h/.cpp` 3. `app/src/infrastructure/json_project_storage.h/.cpp` 4. `docs/工程格式说明.md` 5. `docs/二次开发/工程新建保存加载与JSON说明.md` 6. `app/tests/project_management_tests.cpp` ### 重点调用链 ```text MainWindow::saveProject()/loadProject() -> ProjectService::save()/load() -> Project::validate() -> JsonProjectStorage::save()/load() -> 文件系统 ``` ### 阅读检查 - [ ] 能追踪一次“另存为”从 QAction 到 JSON 文件的完整链路 - [ ] 能解释加载非法文件时为什么不会污染当前工程 - [ ] 能解释 `editProject()`、`isModified()` 和 `restoreModifiedState()` 的关系 - [ ] 能在 JSON 代码中找到 8 类 HMI 控件、报警、注释和连续梯形图字段 - [ ] 能找到版本检查和原子写入实现 - [ ] 查看 `project_management_tests` 的 JSON 往返、非法文件和修改状态测试 --- ## 7. 功能 5:主窗口、工程树与编辑会话 ### 功能范围 - Qt Designer 管理主窗口静态布局 - 工程树管理 HMI 页面和控制逻辑的当前选择 - 编辑区包含“HMI 页面”“控制逻辑”“数据监控”三个页签 - 属性面板根据当前页面、控件、逻辑或节点切换表单 - `ModePolicy` 统一控制编辑态和运行态动作是否可用 - 状态栏和输出面板显示操作、校验、仿真和通信消息 ### 建议阅读顺序 1. 用 Qt Designer 打开 `app/src/ui/main_window.ui` 2. `app/src/ui/main_window.h` 3. `app/src/ui/project_workspace_controller.h/.cpp` 4. `app/src/ui/property_panel_controller.h/.cpp` 5. `app/src/ui/main_window.cpp` 中的构造、`configure*` 和 `update*Ui` 部分 6. `app/src/ui/toolbar_icon_factory.h/.cpp` ### UI 职责分工 | 类 | 职责 | | --- | --- | | `MainWindow` | 窗口级动作、文件对话框、控制器组合、模式请求和运行版导出 | | `ProjectWorkspaceController` | 工程树、当前页面/逻辑、增删改排序和刷新编辑器 | | `PropertyPanelController` | 展示并提交页面、控件和节点属性 | | `RuntimePanelController` | 运行窗口生命周期和运行数据投影 | ### 阅读检查 - [ ] 能找到三个编辑页签和左右/底部面板的创建位置 - [ ] 能解释工程树选择如何切换 HMI 或梯形图编辑器当前对象 - [ ] 能解释为什么当前页面/当前逻辑不写入工程 JSON - [ ] 能找到 QAction 如何连接到控制器或服务 - [ ] 能找到 `ModePolicy` 如何禁用运行期间的编辑入口 - [ ] 暂时不要逐行阅读整个 `main_window.cpp`,后续章节再回来看对应函数 --- ## 8. 功能 6:HMI 页面管理 ### 功能范围 - 没有页面时创建默认页面 - 新增、改名、调整尺寸、上移、下移和删除页面 - 设置进入运行态时的初始页面 - 页面名称和 ID 唯一 - 至少保留一个页面;初始页不能直接删除 - 仍被页面跳转控件引用的页面不能删除 - 缩小页面时不能让已有控件越界 ### 建议阅读顺序 1. `HmiPage`:`app/src/domain/hmi_model.h/.cpp` 2. `HmiEditorService` 的页面方法:`app/src/services/hmi_editor_service.h/.cpp` 3. `ProjectWorkspaceController` 的页面操作 4. `PropertyPanelController::showPageProperties()` 及页面属性提交 5. `MainWindow` 中页面 QAction 的连接 ### 重点服务方法 ```text ensureDefaultPage() addPage() renamePage() resizePage() movePage() setInitialPage() removePage() ``` ### 阅读检查 - [ ] 能追踪“新增 HMI 页面”的完整调用链 - [ ] 能解释删除页面前检查初始页和页面跳转引用的原因 - [ ] 能解释页面排序为何会影响运行版页面顺序 - [ ] 能找到页面操作如何进入 HMI 撤销历史 - [ ] 查看 `hmi_editor_service_tests` 中的页面管理测试 --- ## 9. 功能 7:HMI 控件编辑 ### 功能范围 当前共有 8 类 HMI 控件: | 控件 | 绑定/职责 | | --- | --- | | 按钮 `Button` | 固定绑定 M,写入 M 位 | | 指示灯 `Indicator` | 固定绑定 M,只读显示 | | 数值显示 `NumericDisplay` | 固定绑定 D,支持四种数值类型 | | 数值输入 `NumericInput` | 固定绑定 D,支持四种数值类型和写入 | | 文本 `Label` | 固定文字,不绑定寄存器 | | 状态文本 `StatusText` | M OFF/ON 文本或 D 连续区间映射,只读 | | 页面跳转 `PageJump` | 跳转到指定 HMI 页面 | | 报警列表 `AlarmList` | 显示当前会话报警记录 | 共同编辑能力包括添加、自动分配绑定地址、选择、移动、调整尺寸、编辑文字和外观、删除、六方向批量对齐、撤销和重做。控件必须完整位于页面边界内,一次批量操作要么全部成功,要么完全不修改。 ### 建议阅读顺序 1. `app/src/domain/hmi_control_registry.h/.cpp` 2. `HmiControl` 和各专用配置:`app/src/domain/hmi_model.h/.cpp` 3. `app/src/services/hmi_editor_service.h/.cpp` 4. `app/src/ui/hmi_editor_widget.h/.cpp` 5. `app/src/ui/property_panel_controller.h/.cpp` 6. `app/src/ui/status_text_dialog.ui/.h/.cpp` 7. `app/src/ui/button_extension_dialog.ui/.h/.cpp` 8. `docs/用户使用/HMI控件绑定说明.md` 9. `docs/用户使用/状态文本控件说明.md` 10. `docs/用户使用/按钮启用条件说明.md` ### 重点规则 - M 类和 D 类控件新增时,从该区域已占用的最大地址后自动分配 - 多字 D 控件按真实字数推进,并寻找合法对齐起点 - 属性面板只修改地址编号,按钮/指示灯区域固定为 M,普通数值控件固定为 D - 状态文本在专用对话框选择 M 或 D - D 状态区间使用 `[下限, 上限)`,必须连续覆盖整个数值域,最多 16 段 - 按钮支持置 ON、置 OFF、翻转、点动,以及可选的 M/D 启用条件 - 外观属性包括文字颜色、字号、粗体和斜体 ### 阅读检查 - [ ] 能解释 8 类控件的描述为何集中在 `hmi_control_registry` - [ ] 能追踪“添加数值输入”以及默认 D 地址分配 - [ ] 能追踪一次画布拖动如何调用服务并形成历史记录 - [ ] 能解释六种对齐的参考对象和原子失败行为 - [ ] 能解释状态文本、按钮扩展为何使用独立 Qt Designer 对话框 - [ ] 能找到属性面板针对不同控件隐藏/显示哪些字段 - [ ] 查看 `hmi_editor_service_tests` 的控件编辑、对齐、历史和四种 D 类型测试 --- ## 10. 功能 8:HMI 运行交互与页面导航 ### 功能范围 - 运行画布从当前活动寄存器仓库读取控件值 - 按钮按配置写 M;点动按钮按下写 1、释放写 0 - 按钮启用条件支持 M 期望值或 D 四类型六种比较 - 指示灯读取 M,数值显示读取 D,数值输入写 D - 状态文本将 M 或 D 当前值映射为文字;无效、不可用和非有限数统一显示 `--` - 页面跳转在运行会话内改变当前 HMI 页面 - 未运行、真机未首读、通信不可用或条件不满足时拒绝写入 ### 建议阅读顺序 1. `app/src/services/hmi_runtime_service.h/.cpp` 2. `app/src/services/hmi_navigation_service.h/.cpp` 3. `app/src/ui/hmi_editor_widget.cpp` 中运行态绘制、鼠标事件和值刷新部分 4. `app/src/ui/runtime_monitor_widget.h/.cpp` 5. 回看 `app/src/domain/active_register_repository.h/.cpp` ### 重点调用链 ```text 运行画布上的按钮事件 -> HmiRuntimeService::evaluateButtonEnabled() -> HmiRuntimeService::operateButton() -> ActiveRegisterRepository -> VirtualRegisterRepository 或 PlcRegisterRepository ``` ### 阅读检查 - [ ] 能解释按钮 `Pressed/Released` 对四种操作的不同处理 - [ ] 能解释 UI 置灰与服务层二次拒绝为什么都需要 - [ ] 能解释多字数值读取/写入如何转为连续 D 字 - [ ] 能解释状态文本为什么没有写入接口 - [ ] 能追踪页面跳转控件到 `HmiNavigationService::navigateTo()` - [ ] 查看 `hmi_editor_service_tests` 中的 HMI 运行读写、状态文本和导航测试 --- ## 11. 功能 9:报警、报警列表与地址注释 ### 功能范围 - 报警定义支持 `MOn`、`MOff`、`DHigh`、`DLow` - 报警配置支持新增、修改和删除 - 运行服务周期评估当前活动仓库,维护会话级报警记录 - 当前报警可以确认;条件解除后记录移除;记录不写入工程 - HMI 报警列表控件显示当前记录并支持确认 - M/D 地址注释是工程级元数据,可新增、更新和删除 - 地址注释不保存当前值,也不会单独让地址进入 PLC 轮询 - 梯形图网络注释属于行/网络功能,在第 11 章阅读 ### 建议阅读顺序 1. `app/src/domain/alarm_model.h/.cpp` 2. `app/src/services/alarm_editor_service.h/.cpp` 3. `app/src/services/alarm_service.h/.cpp` 4. `app/src/ui/alarm_configuration_dialog.ui/.h/.cpp` 5. `app/src/services/register_comment_service.h/.cpp` 6. `app/src/ui/register_comment_dialog.ui/.h/.cpp` 7. `app/src/ui/runtime_monitor_widget.cpp` 和 HMI 报警列表投影 ### 阅读检查 - [ ] 能解释四种报警条件和地址区域的匹配规则 - [ ] 能解释报警定义与 `AlarmRecord` 的生命周期差异 - [ ] 能解释已确认报警持续触发时为什么保留确认状态 - [ ] 能追踪报警列表上的确认操作 - [ ] 能解释地址注释为什么不进入寄存器仓库和 PLC 轮询 - [ ] 查看 `alarm_service_tests`,并查看工程管理测试中的报警/注释 JSON 往返 --- ## 12. 功能 10:连续梯形图模型与逻辑组 ### 功能范围 - 工程可包含多组有序控制逻辑,每组可以启用或禁用 - 每组逻辑由视觉行、固定 10 个条件格、独立输出槽和竖线组成 - 条件格类型为 `Gap`、`Wire` 或 `Node` - 一段竖线只连接相邻两行的某个 `0~10` 列边界,长竖线由多段组成 - 网络不是单独对象,而是由横向导通与竖线连通关系计算出来 - `networkHeadIndex()` 是查找网络首行和网络注释归属的统一入口 ### 建议阅读顺序 1. `app/src/domain/control_logic_model.h` 2. `app/src/domain/control_logic_model.cpp` 的结构校验和网络计算 3. `app/src/services/logic_editor_service.h` 的查询与逻辑组管理接口 4. `LogicEditorService` 中 `ensureDefaultLogic/addLogic/renameLogic/removeLogic/moveLogic/setLogicEnabled` 5. `ProjectWorkspaceController` 中控制逻辑操作 6. `docs/用户使用/连续梯形图网格说明.md` ### 核心对象关系 ```text ControlLogic |- rungs[] | |- cells[10]: Gap / Wire / Node | `- output: optional LogicNode `- verticalConnections[]: 相邻行之间的竖线段 ``` ### 阅读检查 - [ ] 能解释为什么 `LadderRung` 不是一个独立电气网络 - [ ] 能解释列边界 `0~10` 与 10 个条件格、输出槽的关系 - [ ] 能解释稳定 ID 和视觉顺序各自承担什么职责 - [ ] 能找到网络首行和网络连通分组的计算方式 - [ ] 能解释禁用的控制逻辑为何可以保留草稿但不参与运行扫描 - [ ] 查看 `domain_tests` 中连续网格和竖线可达性测试 --- ## 13. 功能 11:梯形图画布与编辑操作 ### 功能范围 - 新增行、上方/下方插入行、删除行 - 鼠标横向画线/删线、纵向画线/删线,拖动过程只显示预览,释放时一次提交 - 工具栏和快捷键逐格插入横线、竖线并自动推进光标 - 单击、Ctrl 追加、框选和行号整行选择 - 对节点、横线、输出和竖线进行原子批量删除 - 对普通对象片段或连续整行执行复制粘贴 - 编辑网络注释,并在网络合并/拆分时维护唯一归属 - 全部有效编辑支持撤销/重做;失败操作不产生脏状态和空历史 ### 建议阅读顺序 1. `app/src/services/editor_history.h` 2. `app/src/services/logic_editor_service.h` 中行、线、选择、剪贴板和历史的数据结构 3. `app/src/services/logic_editor_service.cpp` 对应方法 4. `app/src/ui/logic_editor_widget.h/.cpp` 的布局、命中、选择、拖动预览和绘制 5. `app/src/ui/main_window.cpp` 中梯形图 QAction 和快捷键连接 6. `docs/用户使用/鼠标画线与删线说明.md` 7. `docs/用户使用/连续梯形图网格说明.md` ### 建议分四轮阅读 1. 行操作:`addRung/insertRungAbove/insertRungBelow/removeRung` 2. 线操作:`setHorizontalWireRange/setVerticalConnectionRange` 和两个 `apply*AndAdvance` 3. 选择与删除:`deleteSelection/removeNodes/removeVerticalConnections` 4. 剪贴板与历史:`copySelection/pasteClipboard/undo/redo` ### 阅读检查 - [ ] 能解释插入行如何拆分竖线、删除行何时合并上下竖线 - [ ] 能解释一次拖动为何只形成一条历史记录 - [ ] 能区分当前光标、对象选择和左侧行号整行选择 - [ ] 能解释普通片段的“透明空洞”和整行片段的差异 - [ ] 能解释粘贴冲突时如何恢复模型和工程修改状态 - [ ] 能解释两个带不同注释的网络为什么拒绝合并 - [ ] 查看 `logic_editor_service_tests` 的网格、行、竖线、注释、剪贴板和历史测试 - [ ] 查看 `runtime_panel_controller_tests` 的画布框选、对象/整行剪贴板和连续光标测试 --- ## 14. 功能 12:梯形图指令、命令语与检查 ### 当前指令能力 条件指令: | 类型 | 语义 | | --- | --- | | 常开触点 | M 为 ON 时导通 | | 常闭触点 | M 为 OFF 时导通 | | 上升沿触点 | M 从 OFF 变 ON 的一次扫描脉冲 | | 下降沿触点 | M 从 ON 变 OFF 的一次扫描脉冲 | | D 比较 | D 的 Int16 值与常量执行六种比较 | 输出和数据指令: | 类型 | 语义 | | --- | --- | | 普通线圈 OUT | 每次扫描把网络结果写入 M | | 置位线圈 SET | 网络成立时将 M 置 ON | | 复位线圈 RST | 网络成立时将 M 置 OFF | | MOVE | 常量或 D 源写入一个 D 目标 | | ADD | 两个常量/D 操作数相加,Int16 饱和 | | SUB | 两个常量/D 操作数相减,Int16 饱和 | 编辑器还支持画布内命令语输入与补全:`LD/LDI/LDP/LDF`、比较、`AND/ANI`、`OR/ORI`、`OUT/SET/RST/MOV/ADD/SUB`。语法检查会规整不参与完整输出路径的残线并定位断路输出;双线圈检查是独立动作。 ### 建议阅读顺序 1. `LogicNodeConfig` 各 variant:`app/src/domain/control_logic_model.h/.cpp` 2. `app/src/services/logic_command_service.h/.cpp` 3. `LogicEditorService` 的节点、输出、并联、语法检查和双线圈方法 4. `app/src/ui/logic_instruction_dialog.ui/.h/.cpp` 5. `app/src/ui/logic_editor_widget.cpp` 中内嵌命令输入部分 6. `docs/用户使用/命令语输入说明.md` ### 阅读检查 - [ ] 能说明为什么不同指令配置使用 `std::variant` 独立类型 - [ ] 能从一条 `LD M0` 命令追到条件格里的 `ContactNodeConfig` - [ ] 能从 `ADD D0 K1 D1` 追到 `ArithmeticNodeConfig` - [ ] 能解释输出提交时的自动补尾线和自动追加下一空行 - [ ] 能解释显式中间 `Gap` 为什么不会被自动补通 - [ ] 能解释普通语法检查和双线圈检查为什么分开 - [ ] 查看 `logic_editor_service_tests` 中连续输入、并联、语法规整和双线圈测试 --- ## 15. 功能 13:软件逻辑执行与离线仿真 ### 功能范围 - 按控制逻辑顺序和视觉行顺序执行所有已启用逻辑 - 每列边界先合并竖线连通分量,再向右传播电源 - 同一扫描中,前面输出写入的 M/D 对后续网络立即可见 - 边沿触点按“逻辑 ID + 节点 ID”保存跨扫描状态 - 普通、SET、RST 线圈及 MOVE、ADD、SUB 按各自语义执行 - ADD/SUB 超出 Int16 时饱和并记录 overflow,不让整轮扫描中止 - 生成网格输入/输出、节点、竖线、行末和数据指令结果的完整轨迹 - 离线服务复制逻辑快照,按 50 ms 默认周期执行;失败进入 Faulted - 每次新离线会话清空上一轮运行值并恢复编辑态确认的离线初始值 ### 建议阅读顺序 1. `app/src/services/software_logic_executor.h/.cpp` 2. `app/src/services/offline_simulation_service.h/.cpp` 3. `app/src/domain/virtual_register_repository.h/.cpp` 4. `app/src/ui/logic_editor_widget.cpp` 中运行轨迹绘制 5. `app/src/ui/runtime_panel_controller.cpp` 中离线刷新 6. `app/tests/offline_simulation_service_tests.cpp` ### 重点调用链 ```text RuntimeModeService::enterOfflineRunning() -> 运行前工程校验/语法检查 -> OfflineSimulationService::start(logic snapshot) -> QTimer -> SoftwareLogicExecutor::executeScan() -> VirtualRegisterRepository -> LogicTraceSnapshot -> RuntimePanelController -> LogicEditorWidget ``` ### 阅读检查 - [ ] 能手推一个常开触点驱动普通线圈的两轮扫描 - [ ] 能手推上升沿和下降沿触点的跨扫描状态 - [ ] 能解释并联竖线网络如何按列传播电源 - [ ] 能解释前面网络输出为何能影响后面网络 - [ ] 能解释扫描失败如何停止定时器并保存错误定位 - [ ] 能解释轨迹为何按逻辑 ID 分区,最后只在画布投影一次 - [ ] 查看 `offline_simulation_service_tests` 的扫描语义、初始值、轨迹和故障测试 --- ## 16. 功能 14:数据监控 ### 功能范围 - 主窗口编辑态提供独立“数据监控”页签 - 运行窗口也复用同一会话级监控列表 - 批量添加 M/D 地址,去重、删除、清空和单点写入 - M 按 ON/OFF 读取;D 支持四种数据类型和连续多字写入 - 监控列表最多 64 行;加入工程轮询后还受 256 个去重地址限制 - 编辑态明确选择“离线 M/D”或“真机 PLC M/D”,连接 PLC 不自动切换 - 切到真机要求已连接并首读;断线后保持真机选择但显示不可用并禁写 - 离线编辑值同步保存为当前进程的下一次离线仿真初始值 - 运行态数据源由运行模式决定,不受编辑态选择影响 ### 建议阅读顺序 1. `app/src/domain/register_monitor_model.h/.cpp` 2. `app/src/services/register_monitor_service.h/.cpp` 3. `app/src/ui/free_monitor_widget.ui/.h/.cpp` 4. `app/src/ui/main_window.cpp` 中数据监控配置与刷新 5. `app/src/ui/runtime_monitor_widget.cpp` 中运行监控接入 6. `docs/用户使用/数据监控与离线初始值说明.md` 7. `app/tests/register_monitor_service_tests.cpp` ### 阅读检查 - [ ] 能解释监控列表为什么不属于 `Project`、不写入 JSON - [ ] 能解释批量添加四种 D 类型时步长分别是多少 - [ ] 能解释一个 Float64 监控点为什么消耗 4 个 PLC 轮询地址 - [ ] 能追踪编辑态写离线值如何同时更新初始值仓库 - [ ] 能解释选择真机后断线为什么不能静默回退并显示离线数据 - [ ] 能解释多字真机写入为何必须使用一次连续寄存器请求 - [ ] 查看 `register_monitor_service_tests` 的地址、数据类型、初始值和数据源切换测试 --- ## 17. 功能 15:PLC 配置、搜索与 Modbus RTU ### 功能范围 - 串口参数:端口、站号、波特率、数据位、校验位、停止位、超时、重试和轮询周期 - PLC 配置对话框列出串口并支持自动搜索 - 自动搜索使用独立串口会话,不污染正式连接状态机 - `QModbusRtuSerialMaster` 异步连接,不阻塞 UI - 汇总 HMI、报警、梯形图和自由监控引用,排序、去重、分区并合并读块 - 单次最多 120 个数据项,一轮最多 8 块、256 个去重地址 - 多字 D 范围不能在轮询拆块边界中间被切开 - M 写单线圈;D Int16 写单寄存器;多字 D 使用一次多寄存器写入 - 写请求不乐观更新缓存,等待后续轮询读回 - 处理未响应、超时、协议错误、USB 串口拔出、异常断开和恢复首读 ### 建议阅读顺序 1. `app/src/services/plc_communication_gateway.h` 2. `app/src/infrastructure/plc_register_repository.h/.cpp` 3. `app/src/infrastructure/plc_communication_error_classifier.h/.cpp` 4. `app/src/infrastructure/plc_communication_service.h/.cpp` 5. `app/src/services/plc_discovery_gateway.h/.cpp` 6. `app/src/infrastructure/plc_discovery_service.h/.cpp` 7. `app/src/ui/plc_connection_dialog.ui/.h/.cpp` 8. `docs/architecture.md` 的“Modbus RTU 通信” 9. `docs/XDH-60T4-E指令与Modbus要点.md` ### 通信数据流 ```text PLC <-> PlcCommunicationService(异步 Modbus RTU、轮询、恢复) <-> PlcRegisterRepository(最新读回缓存、写请求转交) <-> ActiveRegisterRepository <-> HMI / 数据监控 ``` ### 阅读检查 - [ ] 能解释 `PlcCommunicationGateway` 为什么放在 services 而具体 Qt 实现在 infrastructure - [ ] 能追踪工程引用地址如何汇总为轮询块 - [ ] 能解释一次完整轮询与“首读完成”的判定 - [ ] 能解释缓存失效后为什么撤销真机运行资格 - [ ] 能解释恢复状态与重新连接的差异 - [ ] 能解释写请求排队和后续轮询确认 - [ ] 查看 `plc_connection_dialog_tests` 的端口刷新和自动搜索流程 - [ ] 查看 `plc_runtime_tests` 的缓存、拆块、多字写入、错误恢复和 Fake gateway 测试 > 真机代码阅读不等于真机操作。需要实际写 PLC 时,必须按项目约定先读原值、在 STOP 状态验证、测试后恢复并再次读回。 --- ## 18. 功能 16:运行模式与运行监控窗口 ### 功能范围 - 三种模式:编辑态、离线运行、真机运行 - 离线和真机不能直接切换,必须先经过编辑态 - 进入任何运行态前执行工程运行校验和已启用逻辑语法检查 - 离线使用虚拟仓库并运行软件扫描 - 真机 HMI/监控使用 PLC 缓存;本地梯形图只在临时虚拟仓库推算轨迹 - 真机本地输出绝不写回 PLC,下一轮重新从最新 PLC 缓存开始 - 普通编程器运行时打开唯一顶层运行监控窗口,主窗口保留在后面 - 运行大屏组合 HMI、当前逻辑轨迹、自由监控、报警和模式切换 - 关闭运行大屏的系统关闭路径被忽略,必须通过“返回编辑”退出运行会话 ### 建议阅读顺序 1. `app/src/domain/runtime_state.h/.cpp` 2. `app/src/services/runtime_mode_service.h/.cpp` 3. `app/src/services/online_logic_monitor_service.h/.cpp` 4. `app/src/ui/runtime_monitor_window.ui/.h/.cpp` 5. `app/src/ui/runtime_monitor_widget.ui/.h/.cpp` 6. `app/src/ui/runtime_panel_controller.h/.cpp` 7. `app/src/ui/main_window.cpp` 的 `requestMode()` 和 `updateModeUi()` 8. 回看 `docs/architecture.md` 的“数据源与运行模式” ### 模式矩阵 | 模式 | 工程编辑 | HMI/监控数据源 | 本地逻辑 | | --- | --- | --- | --- | | 编辑态 | 允许 | 数据监控由用户显式选离线或 PLC | 停止 | | 离线运行 | 禁止 | 虚拟 M/D | 执行并写虚拟 M/D | | 真机运行 | 禁止 | PLC 读回缓存 | 用缓存副本推算,只写临时仓库 | ### 阅读检查 - [ ] 能画出三种模式的合法状态转换图 - [ ] 能解释 `ModePolicy` 如何同时约束编辑、数据源和执行器 - [ ] 能追踪一次“进入真机运行”的所有前置检查 - [ ] 能解释 `OnlineLogicMonitorService::copyPlcSnapshot()` 的安全意义 - [ ] 能解释为什么本地绿色轨迹不是 PLC 内部真实程序轨迹 - [ ] 能解释运行监控窗口为何只允许存在一个实例 - [ ] 查看 `runtime_mode_service_tests` 的仓库切换、首读和断路工程拦截测试 - [ ] 查看 `runtime_panel_controller_tests` 的轨迹投影、排队回调和运行窗口测试 --- ## 19. 功能 17:用户运行程序导出与启动 ### 功能范围 - 从当前工程复制一份 HMI 运行快照 - 只保留 HMI 页面、报警和导航数据,移除控制逻辑和地址注释 - 对裁剪后的工程执行运行校验并写入临时 JSON - 把 JSON 追加到已编译 exe 尾部,保存魔数、版本、长度和 SHA-256 - 复制 Qt、MinGW 运行库、平台插件、样式和最小 `runtime.ini` - 导出使用当前已编译 exe 作为模板,不调用 qmake 或编译器 - 用户运行版启动时从自身尾部加载工程,只显示 HMI - 自动按 `runtime.ini` 连接真实 PLC,首读完成后才放行 HMI 写入 - 串口拔出、PLC 无响应或超时后自动重连并重新首读;恢复期间不切到虚拟数据 ### 建议阅读顺序 1. `ProjectService::exportHmiRuntimeAs()`:`app/src/services/project_service.cpp` 2. `app/src/infrastructure/runtime_project_bundle.h/.cpp` 3. `app/src/infrastructure/runtime_settings_loader.h/.cpp` 4. `app/src/ui/main_window.cpp` 的 `exportRuntimeProgram()` 及辅助流程 5. `app/src/main.cpp` 的封装工程识别和临时加载流程 6. `MainWindow` 的用户运行版构造、自动连接和重连定时器 7. `scripts/package_qt_app.ps1`,理解编程器发布打包与“导出运行版”的区别 8. `docs/用户使用/用户运行程序导出说明.md` ### 重点调用链 ```text 编程器导出 -> ProjectService::exportHmiRuntimeAs() -> JsonProjectStorage -> RuntimeProjectBundleService::write() -> 复制运行依赖和 runtime.ini 导出 exe 启动 -> RuntimeProjectBundleService::load(current exe) -> RuntimeSettingsLoader::load() -> ProjectService::load(temporary json) -> HMI-only MainWindow -> 自动连接 PLC -> 首读 -> 开放 HMI 写入 ``` ### 阅读检查 - [ ] 能解释为什么运行版不包含控制逻辑和地址注释 - [ ] 能解释封装完整性检查能发现哪些损坏 - [ ] 能区分维护者发布编程器与工程师导出用户运行版 - [ ] 能解释运行版为什么绝不能提供离线模式回退 - [ ] 能追踪启动自动连接、故障重连和重新首读 - [ ] 查看 `runtime_project_bundle_tests` 和 `runtime_settings_tests` - [ ] 查看 `project_management_tests` 中 HMI 运行快照裁剪和校验测试 --- ## 20. 最后统一看测试、构建和示例工程 这部分不是第 18 个业务功能,而是看完 17 个功能后用于串联全项目。 ### 自动化测试对应关系 | 测试目标 | 主要覆盖 | | --- | --- | | `domain_tests` | 地址、模型、边界、编解码和梯形图可达性 | | `application_settings_tests` | 普通编程器配置 | | `runtime_settings_tests` | 用户运行版最小配置 | | `alarm_service_tests` | 报警定义和运行记录 | | `hmi_editor_service_tests` | HMI 页面、控件、历史、导航和运行读写 | | `logic_editor_service_tests` | 连续梯形图全部编辑用例 | | `offline_simulation_service_tests` | 软件扫描、离线仿真和真机缓存副本推算 | | `project_management_tests` | JSON、工程服务和非法文件 | | `register_monitor_service_tests` | 数据监控和离线初始值 | | `runtime_project_bundle_tests` | exe 工程封装 | | `runtime_mode_service_tests` | 三种模式和仓库切换 | | `runtime_panel_controller_tests` | 运行窗口、轨迹投影和关键 UI 交互 | | `plc_connection_dialog_tests` | PLC 配置与自动搜索 UI | | `plc_runtime_tests` | PLC 缓存、轮询、写入和恢复状态机 | ### 建议最后执行 ```powershell pwsh -NoLogo -NoProfile -File .\scripts\run_qt_tests.ps1 -Configuration Release pwsh -NoLogo -NoProfile -File .\scripts\run_qt_tests.ps1 -Configuration Release -Suite Performance pwsh -NoLogo -NoProfile -File .\scripts\build_and_run_qt.ps1 git diff --check ``` ### 最终串联练习 - [ ] 打开 `json/motor_forward_reverse.json`,从 JSON 找到 HMI 控件、报警和梯形图对象 - [ ] 从 HMI 按钮的 M 绑定追到离线扫描中的触点和线圈 - [ ] 在数据监控中写离线初始值,追踪到下一次离线会话恢复 - [ ] 从进入真机运行追踪轮询地址收集、PLC 缓存和本地轨迹副本 - [ ] 从导出菜单追踪到裁剪 JSON、exe 封装和运行版自动连接 - [ ] 能不看文档画出完整依赖方向:`UI -> Services -> Domain`,`Infrastructure -> Services contracts + Domain` --- ## 21. 当前明确不包含的功能 阅读时遇到下面内容,不要继续在代码里找,因为当前项目明确不实现: - 不生成、编译或下载 PLC 程序 - 不读取 PLC 内部程序或真实网络轨迹 - 真机模式的本地梯形图结果不写回 PLC - 不实现完整 XDPPro 指令集 - 当前没有 T/C 触点、TON、CTU、CTD - 不支持任意像素自由画线;梯形图只能使用固定网格和相邻行竖线 - 不兼容旧工程 JSON `1.0/2.0/3.0` - 数据监控列表和离线初始值不持久化到工程 - 报警运行记录不持久化到工程 - 应用配置只在启动时读取,不热更新 --- ## 22. 阅读进度总表 每完成一章,建议在“备注”里写下仍没想明白的问题或关键调用链,后续回看会很省时间。 | 完成 | 章节 | 备注 | | --- | --- | --- | | [ ] | 1. 程序启动与应用配置 | | | [ ] | 2. M/D 寄存器公共模型 | | | [ ] | 3. 工程聚合、数量边界与校验 | | | [ ] | 4. 工程新建、保存、加载与 JSON | | | [ ] | 5. 主窗口、工程树与编辑会话 | | | [ ] | 6. HMI 页面管理 | | | [ ] | 7. HMI 控件编辑 | | | [ ] | 8. HMI 运行交互与页面导航 | | | [ ] | 9. 报警、报警列表与地址注释 | | | [ ] | 10. 连续梯形图模型与逻辑组 | | | [ ] | 11. 梯形图画布与编辑操作 | | | [ ] | 12. 梯形图指令、命令语与检查 | | | [ ] | 13. 软件逻辑执行与离线仿真 | | | [ ] | 14. 数据监控 | | | [ ] | 15. PLC 配置、搜索与 Modbus RTU | | | [ ] | 16. 运行模式与运行监控窗口 | | | [ ] | 17. 用户运行程序导出与启动 | | 全部完成后,你应该能够从任意一个界面动作出发,快速判断它属于哪个控制器、调用哪个服务、修改哪个领域对象、是否经过基础设施,以及由哪个测试证明。