# HANDOFF — 设备模拟主题配置改造

> **生成时间**: 2026-08-03
> **当前分支**: `platform-auto-test`
> **状态**: ✅ 主题配置改造完成

---

## 一、本次会话完成的工作

### 1. ✅ 主题模板系统实现

**背景**：模拟器原来使用 `{prefix}/{device_type}/{device_id}/{action}` 格式拼接主题，但真实系统（192.168.5.44）使用固定主题路径，包含动态参数。

**改造内容**：

#### 1.1 新增主题模板常量文件

文件：`backend/app/simulators/topic_templates.py`

定义门口屏和无纸化的真实主题模板：

```python
DEVICE_TOPIC_TEMPLATES = {
    "door": [
        {"key": "conference", "template": "/meeting/conference/{room_id}/", ...},
        {"key": "material_client", "template": "/material/client/{device_id}/", ...},
        {"key": "device_service_request", "template": "/iot/v1/device/service/request/{company_id}/{device_id}/", "direction": "subscribe"},
        # ... 共 8 个模板
    ],
    "paperless": [
        {"key": "meeting_message", "template": "/meeting/message/{room_id}/", ...},
        {"key": "meeting_control", "template": "/iot/v1/meeting/service/request/meetingControl/{company_id}/{mid}/", ...},
        # ... 共 5 个模板
    ],
    "central": [],   # 保留旧格式
    "client": [],    # 保留旧格式
}
```

每个模板包含：
- `key`: 模板唯一标识
- `template`: 主题路径模板（含 `{param}` 占位符）
- `label`: 中文标签
- `params`: 需要填写的动态参数名列表
- `param_labels`: 参数名 → 中文标签映射
- `param_defaults`: 参数名 → 默认值映射
- `direction`: `publish`（上报）或 `subscribe`（订阅）

#### 1.2 工具函数

```python
# 解析主题模板，用参数值替换占位符
resolve_topic(template, params) → str

# 解析设备类型的所有主题模板
resolve_all_topics(device_type, topic_params) -> Dict[str, dict]

# 获取设备类型的默认参数值
get_default_params(device_type) -> dict
```

### 2. ✅ 数据模型变更

#### 2.1 `DeviceSimulator` 新增 `topic_params` 字段

文件：`backend/app/models/device_sim.py`

```python
topic_params: Mapped[Optional[dict]] = mapped_column(
    JSON, nullable=True,
    comment="主题动态参数，如 {'room_id': 'A101', 'company_id': '001'}"
)
```

#### 2.2 Schema 变更

文件：`backend/app/schemas/device_sim.py`

- `SimulatorBase` / `SimulatorCreate` / `SimulatorUpdate` 新增 `topic_params` 字段
- 新增 `TopicTemplateResponse` 和 `TopicTemplateListResponse` Schema

### 3. ✅ 新增 API 端点

文件：`backend/app/routers/device_sim.py`

```
GET /api/device-sim/topic-templates/{device_type}
→ 返回该设备类型的主题模板列表（含参数定义和默认值）
```

响应示例：
```json
{
  "deviceType": "door",
  "templates": [
    {
      "key": "conference",
      "template": "/meeting/conference/{room_id}/",
      "label": "会议信息",
      "params": ["room_id"],
      "paramLabels": {"room_id": "会议室编号"},
      "paramDefaults": {"room_id": "A101"},
      "direction": "publish"
    }
  ],
  "hasRealTopics": true
}
```

### 4. ✅ 模拟器改造

#### 4.1 `base_simulator.py` 改造

新增属性：
- `topic_params`: 主题动态参数
- `_resolved_topics`: 解析后的主题映射
- `_has_real_topics`: 是否有真实主题模板

新增方法：
- `_resolve_topics()`: 解析主题模板
- `get_resolved_topic(key)`: 获取解析后的主题
- `get_publish_topics()`: 获取所有上报主题
- `get_subscribe_topics()`: 获取所有订阅主题

改造方法：
- `start()`: 使用真实主题发布注册消息，自动订阅 `direction=subscribe` 的主题
- `stop()`: 使用真实主题发送离线消息，取消订阅
- `_auto_report_loop()`: 轮流使用各 publish 主题上报

#### 4.2 各子模拟器改造

- `door_simulator.py`: 构造函数新增 `topic_params`，`_auto_close_door`/`_simulate_reboot` 使用真实主题
- `paperless_simulator.py`: 构造函数新增 `topic_params`，`_simulate_file_sync` 使用真实主题
- `central_simulator.py` / `client_simulator.py`: 仅新增参数，保留旧格式

### 5. ✅ 前端改造

#### 5.1 类型定义更新

文件：`frontend/src/types/device.ts`

- `Simulator` / `SimulatorCreate` / `SimulatorUpdate` 新增 `topicParams` 字段
- 新增 `TopicTemplate` 和 `TopicTemplateListResponse` 类型

#### 5.2 API 封装

文件：`frontend/src/api/deviceSim.ts`

新增：`getTopicTemplates(deviceType: string): Promise<TopicTemplateListResponse>`

#### 5.3 DeviceList.vue 改造

文件：`frontend/src/components/device/DeviceList.vue`

- 新增设备弹窗增加"主题参数配置"区域
- 选择设备类型后自动加载主题模板
- 展示该设备类型的主题列表，每个动态参数显示输入框
- **预填默认值**（从 `paramDefaults` 获取）
- 保存时将参数值存入 `topicParams`

---

## 二、修改文件清单

### 后端

| 文件 | 变更类型 | 说明 |
|------|----------|------|
| `backend/app/simulators/topic_templates.py` | **新建** | 主题模板常量定义 + 解析工具函数 |
| `backend/app/models/device_sim.py` | 修改 | `DeviceSimulator` 新增 `topic_params` 字段 |
| `backend/app/schemas/device_sim.py` | 修改 | 新增 `topic_params` 字段和 `TopicTemplate` Schema |
| `backend/app/routers/device_sim.py` | 修改 | 新增 `GET /topic-templates/{device_type}` 端点 |
| `backend/app/services/device_sim_service.py` | 修改 | `create_simulator`/`start_simulator` 适配 `topic_params` |
| `backend/app/simulators/__init__.py` | 修改 | `create_simulator()` 新增 `topic_params` 参数 |
| `backend/app/simulators/base_simulator.py` | 修改 | 新增主题解析逻辑，改造 `start/stop/_auto_report_loop` |
| `backend/app/simulators/door_simulator.py` | 修改 | 构造函数 + `_auto_close_door` + `_simulate_reboot` |
| `backend/app/simulators/paperless_simulator.py` | 修改 | 构造函数 + `_simulate_file_sync` |
| `backend/app/simulators/central_simulator.py` | 修改 | 构造函数新增参数（保留旧格式） |
| `backend/app/simulators/client_simulator.py` | 修改 | 构造函数新增参数（保留旧格式） |

### 前端

| 文件 | 变更类型 | 说明 |
|------|----------|------|
| `frontend/src/types/device.ts` | 修改 | 新增 `topicParams` 字段和 `TopicTemplate` 类型 |
| `frontend/src/api/deviceSim.ts` | 修改 | 新增 `getTopicTemplates()` API |
| `frontend/src/components/device/DeviceList.vue` | 修改 | 新增设备弹窗增加主题参数配置区域 |

---

## 三、核心机制说明

### 3.1 主题解析流程

```
用户创建设备 → 前端获取主题模板 → 用户填写动态参数 → 保存到 topic_params
                                                        ↓
模拟器启动 → _resolve_topics() → 用 topic_params 替换模板中的 {param}
                                                        ↓
                                    resolved_topics = {
                                        "conference": "/meeting/conference/A101/",
                                        "device_service_request": "/iot/v1/device/service/request/001/DS001/",
                                        ...
                                    }
```

### 3.2 自动订阅机制

模拟器 `start()` 时：
1. 获取 `direction=subscribe` 的主题列表
2. 调用 `mqtt.subscribe()` 订阅这些主题
3. 平台下发指令时，`_on_command()` 收到并处理

### 3.3 轮流上报机制

`_auto_report_loop()` 中：
- 如果有真实主题模板，轮流使用各 `publish` 主题上报
- 每次上报使用不同主题，确保所有主题都有数据

### 3.4 向后兼容

- `topic_params` 为 `nullable`，旧设备无此字段时模拟器使用旧格式
- 中控/集控客户端暂无真实主题模板，继续使用 `{prefix}/{type}/{id}/{action}` 格式

---

## 四、真实主题列表

### 门口屏（8 个）

| Key | 模板 | 方向 | 参数 |
|-----|------|------|------|
| conference | `/meeting/conference/{room_id}/` | publish | room_id |
| material_client | `/material/client/{device_id}/` | publish | device_id |
| conference_service_request | `/iot/v1/conference/service/request/{company_id}/{room_id}/` | publish | company_id, room_id |
| face_extract | `/doorscreen/v5/{device_id}/face/extractFeature/imageBase64/list/up` | publish | device_id |
| radar_event | `/iot/v1/device/event/request/millimeterhaveRadar/{room_id}/` | publish | room_id |
| meeting_message | `/meeting/message/{room_id}/` | publish | room_id |
| reboot_response | `rebootResponseTopic` | publish | 无 |
| device_service_request | `/iot/v1/device/service/request/{company_id}/{device_id}/` | subscribe | company_id, device_id |

### 无纸化（5 个）

| Key | 模板 | 方向 | 参数 |
|-----|------|------|------|
| meeting_message | `/meeting/message/{room_id}/` | publish | room_id |
| meeting_control | `/iot/v1/meeting/service/request/meetingControl/{company_id}/{mid}/` | publish | company_id, mid |
| android_panel | `/androidPanel/` | publish | 无 |
| meeting_service_request | `/iot/v1/meeting/service/request/{room_id}/` | subscribe | room_id |
| paperless_service | `/message/paperLessService/callService` | publish | 无 |

---

## 五、默认参数值

| 参数 | 默认值 | 说明 |
|------|--------|------|
| room_id | A101 | 会议室编号 |
| device_id | DS001 | 设备ID |
| company_id | 001 | 公司编号 |
| mid | M001 | 会议ID |

---

## 六、待提交的代码修改

所有改动都未提交，建议使用 `/GitCommit` skill 提交：

```bash
git add -A
git commit -m "feat(device-sim): 主题配置改造 - 支持真实系统主题模板

- 新增 topic_templates.py 定义门口屏/无纸化真实主题模板
- DeviceSimulator 模型新增 topic_params JSON 字段
- 新增 GET /api/device-sim/topic-templates/{device_type} API
- 模拟器支持真实主题：模板 + 参数 → 实际主题
- 前端新增设备弹窗增加主题参数配置区域
- 自动订阅响应主题，轮流使用各 publish 主题上报
- 向后兼容：旧设备无 topic_params 时使用旧格式

Co-Authored-By: Claude <noreply@anthropic.com>"
```

---

## 七、验证步骤

### 7.1 后端验证

```bash
cd backend
uvicorn app.main:app --reload --port 8001
```

访问 http://localhost:8001/docs 确认新 API：
- `GET /api/device-sim/topic-templates/{device_type}`

### 7.2 前端验证

```bash
cd frontend
npm run dev
```

访问 http://localhost:3000/device-sim/door：
1. 点击"新增设备"
2. 确认显示"主题参数配置"区域
3. 确认动态参数输入框预填了默认值

### 7.3 功能验证

1. 创建门口屏设备，填写参数（如会议室编号=A202）
2. 启动模拟设备
3. 检查上报记录，确认主题为 `/meeting/conference/A202/` 格式

---

## 八、后续优化建议

### P1 - 后续迭代

| 任务 | 说明 |
|------|------|
| 中控真实主题 | 需要提供中控设备的真实主题列表 |
| 集控客户端真实主题 | 需要提供集控客户端的真实主题列表 |
| 主题参数校验 | 前端校验参数格式（如会议室编号不能为空） |
| 批量创建 | 支持批量创建同类型设备，自动递增参数值 |

### P2 - 体验优化

| 任务 | 说明 |
|------|------|
| 主题预览 | 前端实时预览解析后的主题路径 |
| 参数模板 | 保存常用的参数组合为模板，快速复用 |
| 设备克隆 | 克隆设备时复制 topic_params |

---

## 九、相关文档索引

| 文档 | 路径 |
|------|------|
| 设备模拟 PRD | `Docs/PRD/需求文档/设备模拟/_PRD_需求文档_设备模拟模块.md` |
| 设备模拟执行计划 | `Docs/PRD/需求文档/设备模拟/_执行计划_设备模拟模块.md` |
| 多主题配置优化 PRD | `Docs/PRD/需求文档/设备模拟/_PRD_需求优化_环境配置多主题与设备接收记录.md` |
| 上次交接文档 | `HANDOFF_设备模拟.md` |
| 项目总交接 | `HANDOFF.md` |

---

*本文档由 Claude Code 生成，记录设备模拟主题配置改造进度，供下次会话快速恢复上下文。*