Files
DTU-HMI/README.md

366 lines
10 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# DTU-HMI
`DTU-HMI` 是一个纯 C 实现的 PC 端 DTU 人机界面模拟工程用于在电脑上复现现场装置的菜单结构、按键交互、LCD 绘制与远程显示链路。当前代码已经从早期的过程式菜单实现演进为“页面管理器 + 菜单页 MVP 分层”结构,更适合作为后续页面扩展、联调和测试的基础。
## 项目特性
- 多级菜单树,支持父子层级跳转与同层循环导航
- 160×160 单色 LCD 模拟,支持边框、文字、反显等基础绘制
- 按键输入驱动菜单导航,支持确认、返回、上下左右等操作
- 基于 TCP 的 RemoDispBus 远程显示链路,默认端口 `7003`
- UTF-8 文本显示与菜单宽度自适应计算
- 基于 CTest 的单元测试体系已覆盖菜单、页面管理、按键、LCD、TCP 回环等模块
---
## 快速开始
### 构建
在仓库根目录执行:
```powershell
cmake -S . -B build
cmake --build build
```
生成产物:
- Windows: `build/DTU-HMI.exe`
### 运行
```powershell
.\build\DTU-HMI.exe
```
程序启动后会:
- 初始化页面管理器、LCD 和菜单页
- 启动 TCP 服务线程,监听 `7003`
- 进入主循环,周期性读取按键并刷新当前页面
### 运行测试
```powershell
cmake -S . -B build
cmake --build build
ctest --test-dir build -C Debug --output-on-failure
```
补充说明:
- 单配置生成器下即使没有单独的 `Debug` 目录,`ctest` 也可直接运行
- 多配置生成器下可先显式构建:`cmake --build build --config Debug`
---
## 环境要求
- CMake `3.10+`
- C99 编译器
- Windows 下默认适配 MSVC并自动启用 `/utf-8`
- 非 Windows 平台默认链接 `pthread`
---
## 目录结构
```text
DTU-HMI/
├── CMakeLists.txt
├── README.md
├── include/
│ └── types.h
├── src/
│ ├── main.c
│ ├── common/
│ │ └── utf8.c/h
│ ├── remoteDisplay.c/h
│ ├── thread_utils.c/h
│ ├── TCP/
│ │ └── tcp.c/h
│ └── Drv/
│ ├── key.c/h
│ ├── menu/app/
│ │ └── menu.c/h
│ ├── lcd/
│ │ ├── ascii.c/h
│ │ ├── lcd.c/h
│ │ ├── lcd_draw.c/h
│ │ └── lcd_text.c/h
│ └── pages/
│ ├── page.h
│ ├── page_manager.c/h
│ ├── global/
│ │ ├── global_state.c/h
│ │ └── renderer_lcd.c/h
│ └── menu/
│ ├── def.h
│ ├── model.c/h
│ ├── presenter.c/h
│ ├── view.c/h
│ └── page.c/h
└── tests/
├── CMakeLists.txt
├── test_p0_remote_display.c
├── test_p0_utf8_hz12_get.c
├── test_p1_key.c
├── test_p1_lcd_basic.c
├── test_p1_menu.c
├── test_p1_page_manager.c
└── test_p2_tcp_loopback.c
```
---
## 当前架构
### 总体运行流
当前主程序入口在 `src/main.c`,主流程可以概括为:
```text
main
├─ PageManager_Init()
├─ Lcd_Init()
├─ PageManager_Register(MenuPage_GetInstance())
├─ PageManager_Navigate(PAGE_ID_MENU)
├─ StartTcpServerThread(... port 7003 ...)
└─ while (1)
├─ Key_Read()
├─ PageManager_DispatchEvent(...)
└─ PageManager_Loop()
```
因此,当前项目的交互入口已经不是旧版 `menu.c` 直接驱动,而是统一通过页面系统调度。
### 页面管理器
`src/Drv/pages/page_manager.c` 负责维护“页面注册表 + 页面栈”两套状态,提供:
- `PageManager_Register()`:注册页面
- `PageManager_Navigate()`:按 `page_id` 导航
- `PageManager_Push()` / `PageManager_Pop()`:管理页面栈
- `PageManager_DispatchEvent()`:把事件分发给当前栈顶页面
- `PageManager_Loop()`:周期驱动当前页面
当前 `page_t` 生命周期约定:
- 首次进入页面:`on_create -> on_enter`
- 切页离开:`on_exit`
- 非缓存页出栈:`on_exit -> on_destroy`
- 周期调度:`on_loop`
这使得项目后续可以继续扩展成多页面系统,而不是只服务于一个菜单模块。
### 菜单页 MVP 分层
当前菜单页位于 `src/Drv/pages/menu/`,职责拆分如下:
- `model.c`:菜单静态表、树构建、显示名修饰、层级索引与布局计算
- `presenter.c`:按键路由、路径重建、刷新策略判定
- `view.c`:边框、文字、高亮、整页刷新与局部刷新
- `page.c`:把菜单页接入 `PageManager` 生命周期
数据流可以理解为:
```text
按键输入
-> MenuPage_OnEvent
-> MenuPresenter_HandleInput
-> MenuNavigator_ProcessKey
-> 更新 tagMenuCtrl
-> MenuPresenter_Refresh
-> MenuView_* 渲染接口
-> LCD 渲染端口
```
---
## 菜单数据模型
### 静态定义
菜单原始数据定义在 `src/Drv/pages/menu/model.c``menuTab[]` 中,每一项使用 `tagMenuModel` 描述:
- `byClass`:层级,`0` 表示顶层
- `byName`:菜单显示名
- `byTip`:提示文本
- `byAttrib`:业务属性
- `wPassword`:权限或密码
- `wPara`:动作参数
- `pfnWinProc`:叶子节点动作入口
### 运行时节点
运行时菜单节点类型为 `MenuItem`,由三部分组成:
- `links`:父、子、前、后四向链路
- `menuDef`:静态业务定义副本
- `rect`:菜单框位置、子项数量、当前项序号
关键字段含义:
- `links.higher`:父节点
- `links.lower`:第一个子节点
- `links.before` / `links.behind`:同层前后节点
- `rect.wPos`:当前节点在本层中的 `0` 基序号
- `rect.wNum`:当前节点的直接子节点数量
- `rect.wSPosX/Y``rect.wEPosX/Y`:子菜单框坐标
### 初始化流程
`MenuModel_Init()` 的核心步骤如下:
1. 统计 0 级菜单数量
2.`menuTab[]` 构建成运行时树
3. 为存在子菜单的项追加显示箭头 `\x10`
4. 计算每个节点的层内索引 `wPos` 和子节点数量 `wNum`
5. 计算各层菜单框的初始坐标
### 结构特点
- 同层链表最终会闭环,便于上下循环导航
- 子菜单框 X 坐标从父菜单右侧展开
- 子菜单 Y 坐标会根据当前项位置和屏幕边界做自适应修正
- 视图层只读取 `rect` 结果,不参与复杂布局推导
---
## 菜单导航与渲染
### 导航规则
导航逻辑集中在 `src/Drv/pages/menu/presenter.c`,主要按键语义如下:
- `KEY_U`:移动到同层前一个菜单
- `KEY_D`:移动到同层后一个菜单
- `KEY_R` / `KEY_ENT`:进入子菜单,或执行叶子节点动作
- `KEY_L` / `KEY_ESC`:返回上一级
Presenter 维护的核心状态包括:
- `ptCurrent`:当前选中项
- `ptRoute[]`:当前路径上的各层节点
- `ptCurBak`:上一次选中项
- `pt0Level`:当前顶层上下文
### 刷新策略
当前渲染不是每次都整页重画,而是根据状态差异选择不同路径:
- 首帧或跨顶层切换:`full_refresh`
- 同层同父切换:`update_selection_same_level`
- 进入更深层:`update_selection_new_level`
这种方式可以减少闪烁,也让 Presenter 和 View 的职责更清晰。
### 顶部状态栏
`src/Drv/pages/menu/def.h` 中定义了 `MenuMode`,用于顶部状态栏文案:
- `MODE_NONE`
- `MODE_OVERFLOW_PROTECTION`
- `MODE_LOCAL_FEEDER_SEGMENT`
- `MODE_LOCAL_FEEDER_CONTACT`
当前 `view.c` 已支持这些模式的显示,默认使用 `MODE_NONE`
---
## 远程显示与网络
项目通过 `src/remoteDisplay.c``src/TCP/tcp.c` 提供远程显示基础能力。
当前主程序会在独立线程中启动 TCP 服务:
- 默认监听端口:`7003`
- 用途:同步 LCD 内容,并为远程显示或上位机交互预留链路
如果需要联调网络链路,建议重点查看:
- `src/main.c`
- `src/remoteDisplay.c`
- `src/TCP/tcp.c`
---
## UTF-8 与 LCD 显示
项目中的文本显示链路大致如下:
```text
UTF-8 文本
-> utf8.c 解析字符
-> lcd_text / ascii 绘制字符
-> lcd / lcd_draw 写入像素缓冲
-> renderer_lcd 输出到页面渲染端口
```
菜单宽度计算不是按字节长度,而是按显示宽度计算:
- ASCII 字符记宽度 `1`
- 多字节 UTF-8 字符记宽度 `2`
对应实现主要在:
- `MenuModel_Utf8LenCal()`
- `MenuModel_GetMenuMaxDisplayLen()`
这也是菜单框宽度能够同时适配中文和英文标题的基础。
---
## 调试
可通过 CMake 选项打开 `DEBUG` 宏:
```powershell
cmake -S . -B build -DENABLE_DEBUG=ON
cmake --build build
```
建议调试入口:
- 菜单树构建问题:看 `src/Drv/pages/menu/model.c`
- 页面切换与事件分发问题:看 `src/Drv/pages/page_manager.c`
- 高亮与刷新异常:看 `src/Drv/pages/menu/presenter.c``src/Drv/pages/menu/view.c`
- TCP 联调问题:看 `src/main.c``src/remoteDisplay.c``src/TCP/tcp.c`
---
## 测试说明
当前 `tests/CMakeLists.txt` 已注册以下测试:
- `test_smoke`:测试框架可用性
- `test_p0_remote_display`:远程显示协议/链路核心逻辑
- `test_p0_utf8_hz12_get`:字库查找
- `test_p1_key`:按键模块
- `test_p1_lcd_basic`LCD 像素与基础绘制
- `test_p1_page_manager`:页面管理器生命周期、导航与事件分发
- `test_p1_menu`:菜单模型、视图初始化、页面入口绑定
- `test_p2_tcp_loopback`TCP 本机回环集成测试
其中比较值得关注的是:
- `test_p1_menu.c`:覆盖菜单树构建、显示名修饰、`wPos/wNum` 统计、菜单页实例绑定
- `test_p1_page_manager.c`:覆盖注册、导航、`ESC` 兜底回退、缓存页与非缓存页行为
建议门禁:
- 提交前至少执行一次 `ctest --test-dir build -C Debug --output-on-failure`
- 修改纯逻辑代码时优先补充或更新对应单元测试
---
## 后续扩展建议
从当前代码结构看,比较自然的演进方向包括:
-`pages/` 下继续新增业务页面,而不是把所有交互都塞进菜单页
-`MenuMode` 和顶部状态栏接入真实业务状态源
- 补充 RemoDispBus 交互说明和上位机联调文档
- 继续加强 `Presenter``View` 的细粒度测试覆盖