# DTU-HMI 系统架构设计文档

## 1. 文档目的
本文档用于描述 `DTU-HMI` 工程的整体架构、核心模块职责、关键数据流、线程与通信模型、构建与测试体系，作为后续开发、联调、测试与维护的统一基线。
## 2. 系统概述
`DTU-HMI` 是一个基于纯 C 实现的 PC 端 HMI 菜单逻辑模拟系统，目标是复现现场 DTU 设备的人机界面行为。  
系统支持本地菜单交互与远程显示协议（RemoDispBus）通信，主要包含：
- 菜单树构建与路由（多级菜单、同级循环导航）
- LCD 显存与绘制（160x160 单色像素缓冲）
- 按键输入（本地/远程按键注入）
- TCP 服务（远程显示数据交互）
- 跨平台线程与网络适配（Windows/Linux）
## 3. 架构目标与设计原则
- **可移植性**：通过 `tcp.c`、`thread_utils.c` 封装平台差异。
- **可维护性**：按模块职责划分（菜单/显示/网络/线程/输入）。
	- 系统进行严格的分层设计
		- 分布规划：
			- 应用层
			- 服务层
			- 设备驱动层 （LCD 屏幕，TCP/IP）
		- 分层规律
			- 单向依赖
			- 禁止跨层调用
			- 职责单一
			- 接口标准化
	- 全局变量零容忍
		- 实时性高的数据采用**观察者模式**进行处理
		- 实时性低的数据通过**消息队列**的方式处理
- **可测试性**：优先抽取并覆盖纯逻辑函数，逐步推进集成测试。
- **低耦合高内聚**：上层业务通过明确接口调用下层能力。
- **错误检查**：对可预见的错误进行检查
	- **断言检查**：增加调试编译条件下的断言检查，快速定位一般情况下的错误。
	- **错误码处理**
## 4. 系统分层架构
<img src="https://lsky.bitnasdaq.vip/8CaeLa.png" style="display: block; margin: auto; max-width: 90%;">
**目录结构：**
```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/                        # LCD 显示驱动
│       │   ├── ascii.c/h               # ASCII 字库
│       │   ├── 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    # LCD 渲染 HAL 接口
│           └── menu/                   # 📋 Menu 页面的 MVP 实现
│               ├── 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
```

### 4.1 LCD 驱动层
- 文件夹：`src/Drv/lcd`
- 职责：提供屏幕的显示
```mermaid
block-beta
columns 1
  block:menu
	  a["LCD"]
  end
  
  block:textdraw
  columns 2
	  a1["文字显示"] a2["图像显示"]
  end
  
  block:draw
  columns 2
	block:text_group
	columns 2
		block:left_text
        columns 1
            b["Lcd_ShowStr"]
            block:showstr
            columns 2
            s1["Lcd_Pub_Ascii"]
            s2["Lcd_Pub_UTF8"]
            end
        end
        b1["utf8_next"]
	end 
	
	block:draw_group
      columns 4
      c1["Lcd_LineH"]
      c2["Lcd_LineV"]
      c3["Lcd_Invert"]
      c4["Lcd_FillRect"]
    end
    
  end
  
block:lcd
  lcda["Lcd_Init"] lcdb["Lcd_SetPixel"] lcdc["Lcd_GetPixel"]
end
```

### 4.2 按键驱动层

- 文件：`src/Drv/menu.c`、`src/Drv/menu.h`
- 职责：
    - 基于静态菜单模型构建运行时菜单树
    - 处理按键驱动的菜单状态迁移
    - 执行菜单显示坐标计算与多级菜单渲染调度
### 4.3 多页面管理层设计
#### 4.3.1 页面管理器设计
主要是通过页面管理器对不同页面之间切换进行调度。
<img src="https://lsky.bitnasdaq.vip/cRml1o.png" style="display: block; margin: auto; max-width: 90%;">
**多页面目录结构设计：**
```txt
src/
└── Drv/
    └── pages/                    # 所有页面模块根目录
        ├── page_manager.c        # 页面管理实现
        ├── page_manager.h        # 页面管理头文件
        ├── global/               # 全局状态管理器 + 页面交互中间件
        │   ├── renderer_lcd.c    # 抽象LCD底层，统一渲染接口
        │   └── renderer_lcd.h    # 抽象LCD底层，统一渲染接口
        ├── menu/                 # 菜单页面（MVP架构）
        │   ├── model.c
        │   ├── model.h
        │   ├── view.c
        │   ├── view.h
        │   ├── presenter.c
        │   ├── presenter.h
        │   ├── page.c
        │   └── page.h
        └── (other pages)         # 其他页面模块
```
###### 数据结构设计
```c
typedef struct
{
    page_t *page_stack[MAX_PAGE_STACK_DEPTH]; /* 页面导航栈 */
    int8_t stack_top;                         /* 栈顶索引（-1 表示空栈） */
    page_t *page_registry[MAX_PAGE_COUNT];    /* 页面注册表 */
    uint8_t page_count;                       /* 注册页面数量 */
} page_manager_t;

/* 页面导航栈，记录着页面进入的顺序 */
page_stack = [page_id0, page_id1, page_id2, page_id3]
page_registry /*存储被页面管理器的页面*/
```
##### 函数设计
页面管理器 Page Manager 主要函数：
* `void PageManager_Init(void)`
* `int PageManager_Register(page_t *page)`:将页面对象注册到页面管理器的注册表中，供后续按 page_id 查找与导航。
* `int PageManager_Navigate(page_id_t pageId)`: 根据目标 page_id 执行页面导航：先在注册表中查找页面，再将其压入页面栈。
	1. 在`page_registry`中找到对应的页面 `pageId`
	2. 找到`page_stack` 的栈顶页面 `currentTop = PageManager_GetTop()`
	3. 栈顶页面执行退出 `currentTop->on_exit(currentTop)`
	4. 将 `pageId` 放到 `page_stack` 顶部
	5. 如果页面没有创建则执行创建页面 `newPage->on_create(newPage)`
	6. 执行页面进入 `newPage->on_enter(newPage)`
* `void PageManager_Loop(void)`: 驱动当前栈顶页面的循环回调（on_loop），用于执行周期性任务。
* `void PageManager_DispatchEvent(input_event_t *event)`: 将输入事件分发到当前栈顶页面，并在页面未处理时执行全局兜底处理。
* `static void PageManager_GlobalEventHandle(input_event_t *event)`: 页面管理器的全局事件兜底处理入口，用于处理未被页面消费的通用按键逻辑。
* `page_t *PageManager_Find(page_id_t pageId)`: 在管理器的页面注册表中按 page_id 线性查找，返回对应的 page_t 指针。
* `page_t *PageManager_GetTop(void)`: 返回当前页面栈栈顶元素，即当前“前台/可见”页面对应的 page_t 指针。
* `int PageManager_Push(page_t *newPage)`: 将指定页面压入页面栈并切换为当前页：必要时触发旧页 on_exit、新页 on_create/on_enter。
* `int PageManager_Pop(void)`: 弹出当前栈顶页面并回到下一层：触发当前页 on_exit，非缓存页可 on_destroy，
#### 4.3.2 标准化页面抽象（Page 接口层）
这是整个系统的基础单元，**每个独立业务页面对应一个 Page 实例，每个 Page 内部封装一套完全独立的 MVP 三元组**，对外仅暴露标准接口，页面管理器仅通过标准接口与页面交互，完全不关心页面内部的 MVP 实现，实现页面间的彻底解耦。
```c
/* -------------------------------------------------------------------------
 * 枚举名: page_id_t
 * 作用:
 *   定义系统内可导航页面的逻辑标识（Page ID）。
 *
 * 取值说明:
 *   PAGE_ID_NONE     - 无效页面 ID / 未初始化占位值
 *   PAGE_ID_MENU     - 菜单页 ID（当前主运行页）
 *   PAGE_ID_APP_INFO - 预留页面 ID（当前版本可注册与否由上层决定）
 *   PAGE_ID_MAX      - 上界哨兵，不可作为有效页面 ID 使用
 *
 * 使用约束:
 *   - PageManager_Register() 会校验 page_id，PAGE_ID_NONE 与 >= PAGE_ID_MAX
 *     均视为非法值。
 * ------------------------------------------------------------------------- */
typedef enum
{
    PAGE_ID_NONE = 0,
    PAGE_ID_MENU = 1,
    PAGE_ID_APP_INFO = 2, /* 预留ID：当前版本未注册运行 */
    PAGE_ID_MAX
} page_id_t;

/* -------------------------------------------------------------------------
 * 枚举名: page_event_type_t
 * 作用:
 *   定义页面层可分发的事件类型。
 *
 * 当前约定:
 *   PAGE_EVENT_KEY - 按键输入事件
 *
 * 扩展说明:
 *   - 后续可按需扩展触摸、定时器、通信消息等事件类型。
 * ------------------------------------------------------------------------- */
typedef enum
{
    PAGE_EVENT_KEY = 1
} page_event_type_t;

/* -------------------------------------------------------------------------
 * 结构体名: input_event_t
 * 作用:
 *   页面事件分发的数据载体，由 PageManager_DispatchEvent() 传入页面 on_event。
 *
 * 字段说明:
 *   type   - 事件类型，取值来自 page_event_type_t
 *   keyVal - 按键值（当 type 为 PAGE_EVENT_KEY 时有效）
 *
 * 使用约束:
 *   - 调用方应保证 type/keyVal 与事件来源一致；
 *   - 页面 on_event 可基于该结构返回 EVENT_HANDLED / EVENT_UNHANDLED。
 * ------------------------------------------------------------------------- */
typedef struct
{
    uint8_t type;
    uint8_t keyVal;
} input_event_t;

/* -------------------------------------------------------------------------
 * 枚举名: event_result_t
 * 作用:
 *   定义页面事件处理结果，用于控制事件链是否继续兜底处理。
 *
 * 取值说明:
 *   EVENT_UNHANDLED - 页面未消费事件；允许交由管理器执行全局兜底逻辑
 *   EVENT_HANDLED   - 页面已消费事件；事件链终止
 * ------------------------------------------------------------------------- */
typedef enum
{
    EVENT_UNHANDLED = 0,
    EVENT_HANDLED = 1
} event_result_t;

/* 前置声明：支持在回调签名中使用 page_t* */
typedef struct page_t page_t;

/* -------------------------------------------------------------------------
 * 结构体名: page_t
 * 作用:
 *   页面抽象基元，描述“一个可被 PageManager 管理的页面实例”。
 *
 * 字段分组:
 *   1) 基础元信息
 *      page_id    - 页面逻辑标识
 *      is_cached  - 是否缓存页（1: Pop 后不销毁；0: Pop 后可销毁）
 *      is_created - 是否已执行过 on_create（由管理器维护）
 *
 *   2) 生命周期与事件回调
 *      on_create  - 首次创建时调用
 *      on_enter   - 页面进入前台时调用
 *      on_exit    - 页面离开前台时调用
 *      on_destroy - 页面销毁时调用（常用于非缓存页）
 *      on_event   - 输入事件处理回调
 *      on_loop    - 周期循环回调
 *
 *   3) 三层对象挂载指针（MVP）
 *      presenter / view / model - 页面内部层对象地址（可为空，按页面实现决定）
 *
 * 生命周期约定（由 PageManager 驱动）:
 *   - Push 到新页面时：旧页 on_exit -> 新页（必要时 on_create）-> 新页 on_enter
 *   - Pop 回退时：当前页 on_exit ->（非缓存页可 on_destroy）-> 新栈顶 on_enter
 * ------------------------------------------------------------------------- */
struct page_t
{
    page_id_t page_id;
    uint8_t is_cached;
    uint8_t is_created;

    void (*on_create)(page_t *page);
    void (*on_enter)(page_t *page);
    void (*on_exit)(page_t *page);
    void (*on_destroy)(page_t *page);
    event_result_t (*on_event)(page_t *page, input_event_t *event);
    void (*on_loop)(page_t *page);

    void *presenter;
    void *view;
    void *model;
};



**菜单页面首次进入流程图：**
```txt
[系统启动]
   |
   v
MenuPage_GetInstance()
   |
   v
返回 &s_menuPage 给 PageManager_Register(...)
   |
   v
PageManager_Navigate(PAGE_ID_MENU)
   |
   v
PageManager_Push(s_menuPage)
   |
   +--> (首次进入) MenuPage_OnCreate(page)
   |         |
   |         +--> MenuModel_Init(&s_model)       // 1. 模型初始化
   |         +--> MenuView_Init(&s_view)         // 2. 视图初始化
   |         +--> MenuPresenter_Init(...)        // 3. 主持器初始化
   |         +--> page/s_menuPage 绑定 model/view/presenter
   |
   +--> MenuPage_OnEnter(page)
             |
             +--> s_presenter.dspCtrl.bFirst = 1
             +--> MenuPresenter_Refresh(&s_presenter)   // 首帧刷新
```
#### 4.3.3 页面标准接口与生命周期定义
嵌入式场景下，生命周期钩子的设计必须严格对应资源的申请 / 释放时机，避免 RAM 浪费和低功耗异常，每个钩子的执行时机、职责完全固定，禁止越权操作。
```c
/* -------------------------------------------------------------------------
 * 模块内静态对象说明:
 *   s_model     - 菜单页 Model 实例（菜单数据与运行时结构）
 *   s_view      - 菜单页 View 实例（布局与渲染能力）
 *   s_presenter - 菜单页 Presenter 实例（输入处理与状态驱动）
 *   s_menuPage  - 页面管理器可注册的 page_t 描述对象
 *
 * 说明:
 *   - 以上对象均为文件内静态单例，生命周期覆盖进程运行期。
 *   - 通过 MenuPage_GetInstance() 暴露 s_menuPage 给 PageManager 注册。
 * ------------------------------------------------------------------------- */
static menu_model_t s_model;
static menu_view_t s_view;
static menu_presenter_t s_presenter;
static page_t s_menuPage;


/* -------------------------------------------------------------------------
 * 函数名: MenuPage_OnEnter
 * 功能:
 *   页面进入回调：将菜单渲染标记为“首帧全量刷新”，并立即触发一次刷新。
 *
 * 参数:
 *   page - 当前页面对象指针（本实现未直接使用）
 *
 * 边界处理:
 *   - 本函数不依赖 page 内容，统一转为 (void)page 消除未使用告警。
 *
 * 说明:
 *   - 通过 dspCtrl.bFirst = 1 告知 Presenter 下一次刷新走首帧路径。
 *   - 进入页面后立即刷新，确保界面可见状态与内部状态同步。
 *
 * 返回值:
 *   - 无
 * ------------------------------------------------------------------------- */
static void MenuPage_OnEnter(page_t *page)
{
    (void)page;
    s_presenter.dspCtrl.bFirst = 1;
    MenuPresenter_Refresh(&s_presenter);
}

/* -------------------------------------------------------------------------
 * 函数名: MenuPage_OnExit
 * 功能:
 *   页面退出回调占位点；当前版本无额外退出动作。
 *
 * 参数:
 *   page - 当前页面对象指针（本实现未直接使用）
 *
 * 边界处理:
 *   - 使用 (void)page 防止未使用参数告警。
 *
 * 说明:
 *   - 预留给后续扩展（如停止定时任务、冻结动画、保存瞬时 UI 状态等）。
 *
 * 返回值:
 *   - 无
 * ------------------------------------------------------------------------- */
static void MenuPage_OnExit(page_t *page)
{
    (void)page;
}

/* -------------------------------------------------------------------------
 * 函数名: MenuPage_OnDestroy
 * 功能:
 *   页面销毁回调：清空菜单页内部三层对象（Model/View/Presenter）状态。
 *
 * 参数:
 *   page - 当前页面对象指针（本实现未直接使用）
 *
 * 边界处理:
 *   - 使用 memset 全量清零静态对象，避免残留状态影响后续重建。
 *
 * 说明:
 *   - 与 is_cached 策略配合：当页面被标记为非缓存并弹栈销毁时，该函数用于复位。
 *   - page_t 元信息不在此函数复位，由页面生命周期创建阶段重新赋值。
 *
 * 返回值:
 *   - 无
 * ------------------------------------------------------------------------- */
static void MenuPage_OnDestroy(page_t *page)
{
    (void)page;
    memset(&s_model, 0, sizeof(s_model));
    memset(&s_view, 0, sizeof(s_view));
    memset(&s_presenter, 0, sizeof(s_presenter));
}

/* -------------------------------------------------------------------------
 * 函数名: MenuPage_OnEvent
 * 功能:
 *   页面事件回调：校验输入事件后，将按键转交 Presenter 处理。
 *
 * 参数:
 *   page  - 当前页面对象指针（本实现未直接使用）
 *   event - 输入事件指针
 *
 * 边界处理:
 *   - event 为 NULL 时返回 EVENT_UNHANDLED。
 *   - 仅处理 PAGE_EVENT_KEY 事件类型，其它类型返回 EVENT_UNHANDLED。
 *   - keyVal 为 0 视为无效按键，返回 EVENT_UNHANDLED。
 *
 * 说明:
 *   - 事件有效时调用 MenuPresenter_HandleInput() 执行业务输入流转。
 *   - 返回 EVENT_HANDLED，表示该事件已被菜单页消费，不再交给上层页面逻辑。
 *
 * 返回值:
 *   - EVENT_HANDLED   : 事件已处理
 *   - EVENT_UNHANDLED : 事件无效或不属于本页处理范围
 * ------------------------------------------------------------------------- */
static event_result_t MenuPage_OnEvent(page_t *page, input_event_t *event)
{
    (void)page;
    if ((event == NULL) || (event->type != PAGE_EVENT_KEY) || (event->keyVal == 0))
    {
        return EVENT_UNHANDLED;
    }

    MenuPresenter_HandleInput(&s_presenter, event->keyVal);
    return EVENT_HANDLED;
}

/* -------------------------------------------------------------------------
 * 函数名: MenuPage_OnLoop
 * 功能:
 *   页面循环回调：周期性驱动 Presenter 执行刷新逻辑。
 *
 * 参数:
 *   page - 当前页面对象指针（本实现未直接使用）
 *
 * 边界处理:
 *   - 使用 (void)page 防止未使用参数告警。
 *
 * 说明:
 *   - 实际刷新策略（全量/增量）由 Presenter 内部状态控制。
 *   - 该函数通常由 PageManager_Loop() 在主循环节拍中调用。
 *
 * 返回值:
 *   - 无
 * ------------------------------------------------------------------------- */
static void MenuPage_OnLoop(page_t *page)
{
    (void)page;
    MenuPresenter_Refresh(&s_presenter);
}

/* -------------------------------------------------------------------------
 * 函数名: MenuPage_OnCreate
 * 功能:
 *   页面创建回调：按 Model -> View -> Presenter 顺序完成菜单页运行时装配，
 *   并初始化 page_t 描述对象字段。
 *
 * 参数:
 *   page - 当前页面对象指针（由 PageManager 传入）
 *
 * 边界处理:
 *   - 先清零 s_menuPage，再统一重建其元信息与回调绑定，避免脏状态遗留。
 *   - 假定 page 非空且来自 PageManager 生命周期调用链。
 *
 * 说明:
 *   - 初始化顺序固定：
 *       1) MenuModel_Init(&s_model)
 *       2) MenuView_Init(&s_view)
 *       3) MenuPresenter_Init(&s_presenter, &s_model, &s_view)
 *   - 将 model/presenter/view 回填到 page 与 s_menuPage，便于调试与统一访问。
 *   - s_menuPage 作为静态页面实例，对外由 MenuPage_GetInstance() 返回。
 *
 * 返回值:
 *   - 无
 * ------------------------------------------------------------------------- */
static void MenuPage_OnCreate(page_t *page)
{
    memset(&s_menuPage, 0, sizeof(s_menuPage));
    /* 1) model init */
    MenuModel_Init(&s_model);

    /* 2) view init */
    MenuView_Init(&s_view);

    /* 3) presenter setup + runtime build */
    MenuPresenter_Init(&s_presenter, &s_model, &s_view);

    
    page->model = &s_model;
    page->presenter = &s_presenter;
    page->view = &s_view;

    s_menuPage.presenter = &s_presenter;
    s_menuPage.view = &s_view;
    s_menuPage.model = &s_model;

    s_menuPage.page_id = PAGE_ID_MENU;
    s_menuPage.is_cached = 1;
    s_menuPage.on_create = MenuPage_OnCreate;
    s_menuPage.on_enter = MenuPage_OnEnter;
    s_menuPage.on_exit = MenuPage_OnExit;
    s_menuPage.on_destroy = MenuPage_OnDestroy;
    s_menuPage.on_event = MenuPage_OnEvent;
    s_menuPage.on_loop = MenuPage_OnLoop;
}

/* -------------------------------------------------------------------------
 * 函数名: MenuPage_GetInstance
 * 功能:
 *   获取菜单页静态实例指针，供 PageManager_Register() 注册使用。
 *
 * 参数:
 *   无
 *
 * 边界处理:
 *   - 返回文件内静态对象地址，无空指针分支。
 *
 * 说明:
 *   - 该函数仅暴露页面入口，不负责初始化；初始化由 on_create 生命周期完成。
 *
 * 返回值:
 *   - 指向静态页面对象 s_menuPage 的 page_t* 指针
 * ------------------------------------------------------------------------- */
page_t *MenuPage_GetInstance(void)
{
    return &s_menuPage;
}
```
### 菜单页面设计
<img src="https://lsky.bitnasdaq.vip/uGb3n2.png" style="display: block; margin: auto; max-width: 90%;">
**设计思想：**
基于经典的 MVP 范式，彻底解耦**数据、视图、控制逻辑**，解决复杂 UI 与业务逻辑的协同维护问题。
- **Model（模型层）**：纯业务数据与状态管理，负责系统参数、设备状态、采集数据的读写、校验、存储，与 UI 完全无关。
- **View（视图层）**：纯渲染显示，仅根据 Model 的数据绘制菜单界面、焦点高亮、弹窗、动画，不处理任何业务逻辑。
- **Presenter（控制层）**：核心调度中枢，接收输入事件、更新 Model 数据、控制 View 刷新、处理菜单跳转逻辑，是 Model 与 View 的唯一桥梁。
<img src="https://lsky.bitnasdaq.vip/reEG2w.png" style="display: block; margin: auto; max-width: 90%;">
**菜单页面的目录设计：**
```txt
菜单页面的目录结构设计：
src/
└── Drv/
    └── pages/
        └── menu/                # 项目菜单逻辑根目录
            ├── model.c          # 菜单模型层实现
            ├── model.h          # 菜单模型层头文件
            ├── view.c           # 菜单视图层实现
            ├── view.h           # 菜单视图层头文件
            ├── presenter.c      # 菜单控制层实现
            ├── presenter.h      # 菜单控制层头文件
            ├── page.c           # 菜单页面逻辑实现
            └── page.h           # 菜单页面逻辑头文件
```
当有外部的事件产生时，MVP 架构的数据流图如下：
<img src="https://lsky.bitnasdaq.vip/yK2625.png" style="display: block; margin: auto; max-width: 90%;">
### 4.4 输入层
- 文件：`src/Drv/key.c`、`src/Drv/key.h`
- 职责：
    - 提供按键读写状态控制（消费式读取）
    - 接收远程模块写入的按键事件并供菜单模块读取
### 4.5 远程显示通信层

- 文件：`src/remoteDisplay.c`、`src/remoteDisplay.h`
- 职责：
    - 实现 RemoDispBus 协议解析与回复
    - 提供 TCP 服务器线程入口与启动逻辑
    - 处理保活、初始化、按键下发、显存上传等命令

### 4.6 平台适配层

- 文件：`src/TCP/tcp.c`、`src/thread_utils.c`
- 职责：
    - 提供跨平台 socket 与线程封装
    - 隔离 Windows/Linux API 差异

## 5. 核心数据结构设计

### 5.1 菜单模型与菜单树

- 静态菜单模型：`tagMenuModel`（定义于 `display.h`，数据在 `display.c`）
- 运行时菜单项：`tagMenuItem`（定义于 `menu.c` 内）
- 全局控制：`g_tMenuCtrl`、`g_tDspCtrl`

关键关系：

- `ptHigher` / `ptLower`：父子层级关系
- `ptBefore` / `ptBehind`：同级双向关系（首尾成环）
- `ptRoute[]`：当前路径缓存（0~3 级）

### 5.2 显示控制结构

- `tagScreenControl g_tCVsr`：
    - 显存缓冲 `pwbyLCDMemory`
    - 前景/背景色
    - ASCII 与汉字字体参数

### 5.3 远程按键结构

- `tagRKeyCtrl g_tRemoteKey`：
    - `byKeyValid`：是否有新按键
    - `byKeyValue`：按键值
    - `bUseRkey`：远程按键开关（当前实现中初始化为启用）

## 6. 关键业务流程

### 6.1 主循环流程

```text
[系统初始化]
      |
      v
[Menu_Route]
      |
      v
[Sleep 20ms]
      |
      v
[计数器累加]
      |
      v
[是否到刷新周期?] --否--> [Menu_Route]
      |
      +--是--> [Menu_Show_Proc] --> [Menu_Route]
```

### 6.2 菜单交互流程

- 输入来源：`Key_Read()`（含远程写入按键）
- 行为：
    - 上/下：同级循环移动
    - 左/ESC：回退上级或退回主层
    - 右/确认：进入子级或执行叶子回调
- 渲染：
    - `Menu_Show_Proc` 根据路径增量刷新或全量刷新

### 6.3 远程显示协议流程

```text
[Accept客户端]
      |
      v
[接收缓冲区累积]
      |
      v
[parse_frame 校验解析]
      |
      +--成功--------> [按 cmd 分发] --> [send_reply 回包] --> [接收缓冲区累积]
      |
      +--失败/不完整--> [接收缓冲区累积]
```

命令语义（RemoDispBus）：

- `CMD_INIT`：返回 LCD 宽高与显存尺寸
- `CMD_LCDMEM`：返回显存数据（支持起始地址）
- `CMD_KEY`：注入远程按键到 `g_tRemoteKey`
- `CMD_KEEPLIVE`：保活响应

## 7. 并发与线程模型

- 主线程：
    - 负责菜单路由与本地显示刷新
- TCP 服务器线程：
    - 监听连接、解析协议、处理远程请求

共享状态：

- `g_tCVsr.pwbyLCDMemory`（远程读取 + 本地写入）
- `g_tRemoteKey`（远程写入 + 菜单读取）

当前实现未使用锁机制，依赖业务访问模式降低冲突风险。  
后续若并发复杂度提升，建议引入细粒度互斥或无锁缓冲策略。

## 8. 构建与运行架构

- 构建系统：CMake（`C_STANDARD 99`）
- 可执行目标：`DTU-HMI`
- 平台链接：
    - Windows：`ws2_32`
    - Linux/macOS：`pthread`
- 可选调试：`ENABLE_DEBUG=ON` 自动定义 `DEBUG` 宏

## 9. 测试架构
测试目录：`tests/`
- 框架：`ctest + 自定义断言宏`
- 分层策略：
    - P0：纯逻辑单元测试（协议解析、UTF-8 解析、字库查找）
    - P1：状态/计算单测（按键、菜单、LCD 基础像素操作）
    - P2：集成测试（TCP 回环）
建议执行命令：
```bash
cmake -S . -B build
cmake --build build
ctest --test-dir build -C Debug --output-on-failure
```
维护命令：
```bash
tasklist /FI "IMAGENAME eq DTU-HMI.exe" #查找程序 PID
taskkill /PID 66464 /F  #根据 PID 关闭程序
```
## 10. 模块依赖关系（代码级）

- `main.c` 依赖：`menu`、`key`、`remoteDisplay`、`tcp`、`thread_utils`
- `menu.c` 依赖：`lcd`、`display`、`key`
- `remoteDisplay.c` 依赖：`lcd`、`key`、`tcp`、`thread_utils`
- `lcd.c` 依赖：`ascii`
- `display.c` 提供：静态菜单表（被 `menu.c` 使用）

## 11. 已知风险与改进建议

- **并发一致性风险**：远程线程与主线程共享状态无锁访问。
    - 建议：为显存快照与按键事件引入互斥保护或双缓冲。
- **协议缓冲鲁棒性**：当前异常数据采用清空缓冲策略，存在丢包窗口。
    - 建议：增加更精细的帧边界恢复策略与统计日志。
- **可测试性边界**：部分逻辑仍与全局状态耦合较深。
    - 建议：逐步引入接口注入（如 `TcpOps`、`delay_ms`）降低耦合。

## 12. 版本与维护

- 文档版本：v1.0
- 适配代码基线：当前 `DTU-HMI` 仓库主干实现
- 维护建议：
    - 每次新增模块或调整主流程时同步更新本文档
    - 测试策略更新需同步维护“第 9 章 测试架构”

