diff --git a/.xagent/check-dirty.py b/.xagent/check-dirty.py new file mode 100644 index 0000000..5431cfc --- /dev/null +++ b/.xagent/check-dirty.py @@ -0,0 +1,107 @@ +import os +import sys +import json +import subprocess +import time + +# Force stdout to be UTF-8 for Windows terminals +if hasattr(sys.stdout, 'reconfigure'): + sys.stdout.reconfigure(encoding='utf-8') + +NOW = time.time() + +def get_git_timestamp(filepath): + # Normalize paths to use forward slashes for git compatibility + git_path = filepath.replace('\\', '/') + + # 1. Check if the file exists on disk. + if not os.path.exists(filepath): + return 0 + + # 2. Check for local modifications (staged or unstaged) + try: + status_proc = subprocess.run( + ['git', 'status', '--porcelain', '--', git_path], + capture_output=True, text=True, check=True + ) + if status_proc.stdout.strip(): + # Locally modified files are considered "edited just now" + return NOW + except subprocess.SubprocessError: + pass + + # 3. Get the last commit timestamp + try: + log_proc = subprocess.run( + ['git', 'log', '-1', '--format=%ct', '--', git_path], + capture_output=True, text=True, check=True + ) + output = log_proc.stdout.strip() + if output.isdigit(): + return int(output) + except subprocess.SubprocessError: + pass + + # 4. Fallback to OS modification time if file exists but has no git commits yet + try: + return os.path.getmtime(filepath) + except OSError: + return 0 + +def check_dirty(): + graph_path = os.path.join(os.path.dirname(__file__), 'graph.json') + if not os.path.exists(graph_path): + print(f"Error: graph.json not found at {graph_path}") + sys.exit(1) + + with open(graph_path, 'r', encoding='utf-8') as f: + graph = json.load(f) + + files_dict = graph.get('files', {}) + timestamps = {} + + # Calculate timestamps for all files + for filepath in files_dict.keys(): + timestamps[filepath] = get_git_timestamp(filepath) + + dirty_files = {} + + # Check dependencies + for filepath, info in files_dict.items(): + depends_on = info.get('depends_on', []) + file_ts = timestamps[filepath] + + # If the file itself doesn't exist, it's not "dirty" in the sense of being out of date, + # but it is missing. However, to keep it clean, if it doesn't exist, we can skip or flag it. + # Let's say if a file exists, we check if its dependencies are newer. + if file_ts == 0: + # File is missing on disk + continue + + file_dirty_deps = [] + for dep in depends_on: + dep_ts = get_git_timestamp(dep) + if dep_ts > file_ts: + file_dirty_deps.append((dep, dep_ts, file_ts)) + + if file_dirty_deps: + dirty_files[filepath] = file_dirty_deps + + if dirty_files: + print("\n[DIRTY] 发现以下下游文件落后于其上游依赖:") + for filepath, deps in dirty_files.items(): + print(f"\n* {filepath}") + for dep, dep_ts, file_ts in deps: + dep_time = time.strftime('%Y-%m-%d %H:%M:%S', time.localtime(dep_ts)) + file_time = time.strftime('%Y-%m-%d %H:%M:%S', time.localtime(file_ts)) + print(f" [依赖于] {dep}") + print(f" (上游最后提交: {dep_time} > 下游最后提交: {file_time})") + print("\n请跟进并更新上述下游文件,重新 commit 即可刷新基线并自动消脏。\n") + return False + else: + print("\n[CLEAN] 所有文件均已与依赖同步,无脏文件。\n") + return True + +if __name__ == '__main__': + success = check_dirty() + sys.exit(0 if success else 1) diff --git a/.xagent/check-dirty.sh b/.xagent/check-dirty.sh new file mode 100644 index 0000000..c519d05 --- /dev/null +++ b/.xagent/check-dirty.sh @@ -0,0 +1,4 @@ +#!/bin/bash +# XAgent 判脏检测脚本 - 自动通过 git 历史对比最后提交时间 +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +python "$SCRIPT_DIR/check-dirty.py" diff --git a/.xagent/graph.json b/.xagent/graph.json new file mode 100644 index 0000000..64f671a --- /dev/null +++ b/.xagent/graph.json @@ -0,0 +1,145 @@ +{ + "files": { + "Docs/10_requirements-overview/main.md": { + "depends_on": [], + "lock": null + }, + "Docs/20_requirements-detail/main.md": { + "depends_on": [ + "Docs/10_requirements-overview/main.md" + ], + "lock": null + }, + "Docs/30_architecture/main.md": { + "depends_on": [ + "Docs/20_requirements-detail/main.md" + ], + "lock": null + }, + "Docs/40_ui-design/main.md": { + "depends_on": [ + "Docs/20_requirements-detail/main.md" + ], + "lock": null + }, + "Docs/50_module-breakdown/mod-sys.md": { + "depends_on": [ + "Docs/30_architecture/main.md", + "Docs/40_ui-design/main.md" + ], + "lock": null + }, + "Docs/50_module-breakdown/mod-lcd.md": { + "depends_on": [ + "Docs/30_architecture/main.md", + "Docs/40_ui-design/main.md" + ], + "lock": null + }, + "Docs/50_module-breakdown/mod-rf.md": { + "depends_on": [ + "Docs/30_architecture/main.md", + "Docs/40_ui-design/main.md" + ], + "lock": null + }, + "Docs/50_module-breakdown/mod-power.md": { + "depends_on": [ + "Docs/30_architecture/main.md", + "Docs/40_ui-design/main.md" + ], + "lock": null + }, + "Docs/50_module-breakdown/mod-key.md": { + "depends_on": [ + "Docs/30_architecture/main.md", + "Docs/40_ui-design/main.md" + ], + "lock": null + }, + "Docs/60_coding/mod-sys.md": { + "depends_on": [ + "Docs/50_module-breakdown/mod-sys.md" + ], + "lock": null + }, + "Docs/60_coding/mod-lcd.md": { + "depends_on": [ + "Docs/50_module-breakdown/mod-lcd.md" + ], + "lock": null + }, + "Docs/60_coding/mod-rf.md": { + "depends_on": [ + "Docs/50_module-breakdown/mod-rf.md" + ], + "lock": null + }, + "Docs/60_coding/mod-power.md": { + "depends_on": [ + "Docs/50_module-breakdown/mod-power.md" + ], + "lock": null + }, + "Docs/60_coding/mod-key.md": { + "depends_on": [ + "Docs/50_module-breakdown/mod-key.md" + ], + "lock": null + }, + "App/main.c": { + "depends_on": [ + "Docs/60_coding/mod-sys.md", + "Docs/60_coding/mod-key.md", + "Docs/60_coding/mod-power.md" + ], + "lock": null + }, + "App/config.h": { + "depends_on": [ + "Docs/60_coding/mod-sys.md" + ], + "lock": null + }, + "Drivers/lcd.c": { + "depends_on": [ + "Docs/60_coding/mod-lcd.md" + ], + "lock": null + }, + "Drivers/lcd.h": { + "depends_on": [ + "Docs/60_coding/mod-lcd.md" + ], + "lock": null + }, + "Drivers/lcd_font.h": { + "depends_on": [ + "Docs/60_coding/mod-lcd.md" + ], + "lock": null + }, + "Drivers/rf.c": { + "depends_on": [ + "Docs/60_coding/mod-rf.md" + ], + "lock": null + }, + "Drivers/rf.h": { + "depends_on": [ + "Docs/60_coding/mod-rf.md" + ], + "lock": null + }, + "Docs/70_testing/main.md": { + "depends_on": [ + "Docs/60_coding/mod-sys.md", + "Docs/60_coding/mod-lcd.md", + "Docs/60_coding/mod-rf.md", + "Docs/60_coding/mod-power.md", + "Docs/60_coding/mod-key.md" + ], + "lock": null + } + } +} diff --git a/.xagent/stages.json b/.xagent/stages.json new file mode 100644 index 0000000..d5b70a3 --- /dev/null +++ b/.xagent/stages.json @@ -0,0 +1,65 @@ +{ + "stages": [ + { + "id": "10_requirements-overview", + "path": "Docs/10_requirements-overview", + "role": "需求分析师", + "type": "single_document", + "default_depends_on": [] + }, + { + "id": "20_requirements-detail", + "path": "Docs/20_requirements-detail", + "role": "需求分析师", + "type": "single_document", + "default_depends_on": [ + "Docs/10_requirements-overview/main.md" + ] + }, + { + "id": "30_architecture", + "path": "Docs/30_architecture", + "role": "架构师", + "type": "single_document", + "default_depends_on": [ + "Docs/20_requirements-detail/main.md" + ] + }, + { + "id": "40_ui-design", + "path": "Docs/40_ui-design", + "role": "UI设计师", + "type": "single_document", + "default_depends_on": [ + "Docs/20_requirements-detail/main.md" + ] + }, + { + "id": "50_module-breakdown", + "path": "Docs/50_module-breakdown", + "role": "架构师", + "type": "parallel_tasks", + "default_depends_on": [ + "Docs/30_architecture/main.md", + "Docs/40_ui-design/main.md" + ] + }, + { + "id": "60_coding", + "path": "Docs/60_coding", + "role": "开发者", + "type": "parallel_tasks", + "default_depends_on": [], + "link_same_name_from": "Docs/50_module-breakdown" + }, + { + "id": "70_testing", + "path": "Docs/70_testing", + "role": "测试工程师", + "type": "single_document", + "default_depends_on": [ + "Docs/60_coding" + ] + } + ] +} diff --git a/Docs/10_requirements-overview/main.md b/Docs/10_requirements-overview/main.md new file mode 100644 index 0000000..881d9e4 --- /dev/null +++ b/Docs/10_requirements-overview/main.md @@ -0,0 +1,25 @@ +# 总体需求 (docs/10_requirements-overview/main.md) + +## 1. 项目整体框架与系统拓扑 + +本项目构建一套以 **IPC 摄像头**为中枢、**手环**为随身告警终端、多类 **433MHz 传感器**为探测前端的本地安防联动系统,同时通过 WiFi 连接云端,向手机 APP 发送推送通知。 + +```mermaid +graph TD + Sensor[433MHz 传感器] -->|433MHz 射频信号| IPC[IPC 摄像头] + Sensor -->|433MHz 射频信号| Wristband[随身手环] + IPC -->|WiFi / LAN| Cloud[云端推送服务] + Cloud -->|推送通知| App[手机 APP] +``` + +## 2. 核心信号流向与行为要求 + +1. **传感器触发**:探测到异常时(如门磁打开、烟雾浓度超标),发射固定编码信号(24位 EV1527 协议数据,包含 20位地址码 + 4位数据码)。 +2. **手环与 IPC 独立接收**:IPC 和手环各自配备 433MHz 接收解调芯片,分别独立解码,互不依赖。 +3. **终端响应行为**: + * **IPC 摄像头端**:解码成功后,TFT LCD 屏幕显示报警 ICON、闪光报警,并通过网关向云端推送手机通知。 + * **手环端**:解码成功后,OLED 屏幕显示报警 ICON + 传感器自定义名称,多色 LED 亮起对应颜色,且马达触发特定振动模式。 + +## 3. 协作规范与门禁说明 +本项目的开发协作基于《开发规范.md》定义的“七个环节”。任何目录下文件的修改应当保证其依赖项是同步的。 +在每次完成阶段性工作或代码调整后,应运行 `bash .xagent/check-dirty.sh` 进行脏检测自检。 diff --git a/Docs/20_requirements-detail/main.md b/Docs/20_requirements-detail/main.md new file mode 100644 index 0000000..3d967a4 --- /dev/null +++ b/Docs/20_requirements-detail/main.md @@ -0,0 +1,38 @@ +# 细化需求 (docs/20_requirements-detail/main.md) + +本章节定义手环终端的物理按键交互、报警矩阵(LED颜色与振动)以及防区划分详细需求。 + +## 1. 物理按键与交互定义 + +| 按键动作 | 常态 (时钟待机) | 对码配对模式 | 警报触发状态 | +| :--- | :--- | :--- | :--- | +| **▲ 上移键** | 短按:时间调整(或菜单浏览) | 向上滚动选择传感器类型 | 无动作 | +| **▼ 下移键** | 短按:时间调整(或菜单浏览) | 向下滚动选择传感器类型 | 长按 2 秒:解除当前告警并退回时钟 | +| **▲ + ▼ (同时长按3秒)** | **进入对码配对模式** | 无动作 | 无动作 | +| **SOS 键 (短按)** | **触发本地主动 SOS 呼救** | **确认保存当前配对** | 短按:解除告警并退回时钟 | +| **SOS 键 (长按5秒)** | **触发 IPC 绑定对码广播** | 无动作 | 长按:解除告警并退回时钟 | + +> [!NOTE] +> 在手环发出主动 SOS 呼救(马达长振、红灯闪烁)或响应防区报警时,长按物理按键(▲、▼、SOS 任意之一)2 秒均应立刻切断声光电反馈,安全重置至时钟待机状态。 + +## 2. 传感器对码与报警映射矩阵 + +手环接收到 433MHz 传感器射频数据后,通过匹配预设数据码(高 4 位为类型)和地址码,根据下表驱动多色 LED、马达振动波形并更新 OLED 显示。 + +| 传感器类型 | 数据码标识 | LED 颜色 | OLED 默认名称 (法语) | 马达振动波形 (周期 5 秒) | +| :--- | :--- | :--- | :--- | :--- | +| **🚪 门磁/窗磁** | `0x01` (A) | ● 红色 | `PORTE ENTREE` | 1 次短振 300ms | +| **👤 PIR 人体红外** | `0x02` (B) | ● 橙色 | `SALON PIR` | 2 次短振 300ms (间隔 200ms) | +| **🔥 烟雾传感器** | `0x03` (C) | ● 绿色 | `CUISINE FUMEE` | 3 次短振 200ms (间隔 150ms) | +| **💨 气体泄漏** | `0x05` (E) | ● 紫色 | `CUISINE GAZ` | 4 次短振 200ms (间隔 100ms) | +| **💧 水浸传感器** | `0x06` (F) | ● 黄色 | `SDB EAU` | 2 次长振 600ms (间隔 300ms) | +| **🚨 本地主动 SOS** | 手环触发 | ● 红色闪烁 | `SOS EMITTED` | 持续长振 (振 1000ms, 停 200ms) | + +> [!IMPORTANT] +> **屏蔽 SOS 被动报警**:为避免两个手环互发 SOS 相互触发造成死循环报警,手环**必须强行过滤**并排除对外界发射的 SOS 求救广播(数据码为 `0x04` 或特定紧急按钮码)的被动接收。 + +## 3. ZONE 防区划分 + +手环支持将配对的传感器划分至不同的防区: +* **ZONE 0 (永久防区)**:烟雾、气体、水浸等危及安全的传感器。不论手环是否处于布防状态,一旦捕获信号即刻报警。 +* **ZONE 1~N (可撤布防区)**:门磁、红外等防盗传感器。只有当手环接收到布防广播(系统进入 ARMED)时才会报警;在撤防(DISARMED)状态下被动忽略。 diff --git a/Docs/30_architecture/main.md b/Docs/30_architecture/main.md new file mode 100644 index 0000000..1f86593 --- /dev/null +++ b/Docs/30_architecture/main.md @@ -0,0 +1,75 @@ +# 架构设计 (docs/30_architecture/main.md) + +本章节阐述手环的硬件拓扑、引脚分配、软件状态机设计、IAP Flash 数据库存储设计以及低功耗待机机制。 + +## 1. 硬件规格与引脚映射 + +主控芯片采用 **STC32G12K128** 单片机(工作频率 24.0 MHz,单周期 8051 内核,32位乘除法器)。 + +### 1.1 GPIO 引脚配置表 + +| 引脚号 | 引脚定义 | 模式配置 | 物理外设与功能说明 | +| :--- | :--- | :--- | :--- | +| **P2.3** | `WS2812_DI` | 双向/SPI MOSI | 连接 FCOB-2.7 幻彩 RGB 灯条的 DIN 信号输入 | +| **P2.5** | `MOTOR` | 推挽输出/SCLK | 连接振动马达驱动电路。高电平震动,低电平静止 | +| **P0.1** | `KEY_UP` | 高阻输入+上拉 | 物理按键 SW1 (▲),按下为低电平 | +| **P0.2** | `KEY_SOS` | 高阻输入+上拉 | 物理按键 SW3 (SOS),按下为低电平 | +| **P0.3** | `KEY_DOWN` | 高阻输入+上拉 | 物理按键 SW2 (▼),按下为低电平 | +| **P1.0** | `SGM_CTRL` | 推挽输出 | SGM3833 电源使能。高电平使能输出正负 VELVDD & VAVDD | +| **P1.1** | `LCD_RST` | 推挽输出 | AMOLED 屏幕 RM69310 复位引脚 | +| **P1.3** | `LCD_RS` | 推挽输出 | AMOLED 屏幕 SPI 数据/命令寄存器选择 (D/C) | +| **P1.4** | `LCD_CS` | 推挽输出 | AMOLED 屏幕 SPI 片选信号 | +| **P1.5** | `LCD_SCL` | 推挽输出 | AMOLED 屏幕 SPI 时钟信号 | +| **P1.6** | `LCD_SDA` | 推挽输出 | AMOLED 屏幕 SPI 数据信号 | +| **P2.3** | `ADC3` | 模拟输入 | 电池电压采样输入端口 | + +### 1.2 SPI 复用与马达防噪硬件规避设计 + +由于 `P2.5` 同时作为马达引脚和硬件 SPI 的 SCLK 引脚,为了避免在高频发送 24-bit 灯效数据时引脚电平抖动导致马达异常鸣叫: +1. **发送前**:调用 `EA = 0` 关闭总中断。临时将 `P2.5` 引脚配置为 **高阻输入模式**。此时 SCLK 输出时钟不会使马达导通。 +2. **发送中**:通过硬件 SPI 直接串行写入 72 位转换数据(24位 RGB 经过 LUT 展宽)。 +3. **发送后**:将 `P2.5` 引脚改回 **推挽输出模式**。调用 `EA = 1` 开启中断,继续通过常规 IO 控制马达电平。 + +--- + +## 2. 核心软件业务状态机 (SystemState) + +系统处于主循环中,根据事件和计数器在以下状态(`SystemState`)间迁移: + +```mermaid +stateDiagram-v2 + [*] --> STATE_NORMAL + STATE_NORMAL --> STATE_ARMED : 接收到布防广播 + STATE_ARMED --> STATE_DISARMED : 接收到撤防广播 + STATE_DISARMED --> STATE_NORMAL : 自动恢复/按键返回 + + STATE_NORMAL --> STATE_PAIR_MENU : ▲ + ▼ 长按 3 秒 + STATE_PAIR_MENU --> STATE_PAIR_WAIT : 确认选中类型 + STATE_PAIR_WAIT --> STATE_PAIR_CONFIRM : 捕获 433MHz 匹配数据 + STATE_PAIR_CONFIRM --> STATE_PAIR_SUCCESS : 短按 SOS 保存 + STATE_PAIR_CONFIRM --> STATE_PAIR_FAIL : 超时/按▼取消 + STATE_PAIR_SUCCESS --> STATE_PAIR_MENU : 2秒后自动返回 + STATE_PAIR_FAIL --> STATE_PAIR_MENU : 2秒后自动返回 + + STATE_NORMAL --> STATE_SOS_EMITTED : 短按 SOS + STATE_ARMED --> STATE_ALARMING : 捕获已配对传感器报警 + STATE_ALARMING --> STATE_NORMAL : 按任意键/超时解除 + STATE_SOS_EMITTED --> STATE_NORMAL : 长按任意键/超时解除 +``` + +--- + +## 3. IAP Flash 传感器数据库设计 + +为了断电保存传感器配对信息,使用 STC32G 的 IAP 功能读写 Flash: +* **扇区基地址**:`0xFE0000` (末尾扇区)。 +* **存储结构**:`Sensor_Slot` 结构体: + ```c + typedef struct { + u32 addr; // 24位射频硬件地址 (EV1527) + u8 type; // 传感器类型 (0: 门磁, 1: 红外, 2: 烟感, 3: 燃气, 4: 水浸) + u8 zone; // 防区类别 (0: 永久防区, 1: 普通可撤防区) + char name[16]; // 自定义法语防区名称 + } Sensor_Slot; + ``` +* 一共支持 **16** 个传感器槽位,通过 `Load_Database()` 和 `Save_Database()` 进行统一序列化读写。 diff --git a/Docs/40_ui-design/main.md b/Docs/40_ui-design/main.md new file mode 100644 index 0000000..2e7c252 --- /dev/null +++ b/Docs/40_ui-design/main.md @@ -0,0 +1,64 @@ +# UI 设计 (docs/40_ui-design/main.md) + +本章节定义手环 120x240 AMOLED 屏幕的画面布局、各页面坐标位置、中文字体/ASCII 字符对齐基线以及局部刷新逻辑。 + +## 1. 屏幕物理参数与刷新机制 + +* **屏幕规格**:0.95寸 AMOLED 屏 (RM69310 控制芯片),物理分辨率 `120x240`。 +* **坐标偏移修正**:根据硬件布线,写屏窗口渲染存在 **4 像素的 X 轴偏移偏量**,软件在调用 `LCD_SetWindow()` 时必须强行追加该偏移。 +* **局部刷新防抖机制**:清除重绘标志(`Redraw`)的动作必须在具体的刷新绘制块结束时重置,防止因为主循环高速轮询而产生屏幕闪烁。 + +--- + +## 2. 页面布局与坐标坐标规范 + +### 2.1 时钟待机界面 (STATE_NORMAL / STATE_ARMED / STATE_DISARMED) + +时钟页面作为常态显示,包括状态栏、数字时钟和日期行。 + +``` ++---------------------------------------+ +| 🔒 ARMED 🔋 85% | <- 状态栏 (Y=24~27) +| | +| | +| 10:35 | <- 中央时间 (Y=80~128) +| | +| | +| VEN. 07-10 | <- 底部日期 (Y=180) ++---------------------------------------+ +``` + +* **状态栏电量显示(极重要)**: + * **电池图标** 绘制在 `X=92, Y=27`。 + * **电量百分比** 绘制在 `X=62, Y=24`。 + * 两个控件保持 **6 像素安全间距**,彻底防范乱码和字符相撞。 +* **安防状态显示**: + * **布防状态 (ARMED)**:状态栏左侧显示红色闭锁 `🔒` 图标,显示 `ARMED`。 + * **撤防状态 (DISARMED)**:状态栏左侧显示绿色开锁 `🔓` 图标,显示 `DISARMED`。 + +### 2.2 传感器配对选择菜单 (STATE_PAIR_MENU) + +* **顶部标题**:显示 `MODE PAIR P-XX`(其中 XX 为当前已配对的传感器数量)。 +* **列表滚动框**:采用 3 行滚动列表,当前行高亮并显示粗体外框,字号放大(如 `DETEC. PORTE`),非当前行暗淡显示。 + +### 2.3 报警触发与本地 SOS 界面 (STATE_ALARMING / STATE_SOS_EMITTED) + +* **报警提示边框**:全屏闪烁的红色警告虚线/实线边框。 +* **中央图标**:根据触发的防区传感器类型,居中绘制对应的单色位图图标(32x32px): + * 门磁:`🚪` 开门图标。 + * 红外:`👤` 人体辐射波纹图标。 + * 烟感:`🔥` 火苗图标。 + * 燃气:`💨` 毒气云雾图标。 + * 水浸:`💧` 滴水波纹图标。 + * 主动 SOS:`🚨` 旋转警灯闪烁图标。 +* **底部两行小字**: + * 第一行:传感器自定义法语名称(如 `PORTE ENTREE`)。 + * 第二行:防区信息与动作指示(如 `ZONE 1 | CRITIQUE`)。 + +--- + +## 3. 字模设计与基线修正规范 + +为保证排版美观,[Drivers/lcd_font.h](file:///c:/workfile/105/stc32g12k128/Drivers/lcd_font.h) 内的标准字模点阵需要满足如下基线对齐: +* **标准 ASCII 字符集**:8x16 像素点阵。 +* **百分号(%)符号基线修正**:百分号字符在 `lcd_font.h` 数组的索引 `0x25` 中,点阵数据需**向上平移 2 像素**,以和 `0~9` 数字的基线保持严格水平对齐,避免百分号整体下沉。 diff --git a/Docs/433MHz_IPC_产品需求规格说明书_V1.2.txt b/Docs/433MHz_IPC_产品需求规格说明书_V1.2.txt new file mode 100644 index 0000000..35c3686 --- /dev/null +++ b/Docs/433MHz_IPC_产品需求规格说明书_V1.2.txt @@ -0,0 +1,516 @@ +433MHz IPC 安防系统 +产品需求规格说明书 +(IPC + 手环 + 433MHz 传感器联动) +供开发工程师评审讨论 · 仅需求描述,不含实施方案 +版本:V1.2 整理日期:2026年5月 +一、项目整体框架与系统拓扑 +本项目构建一套以 IPC 摄像头为中枢、手表/手环为随身告警终端、多类 433MHz 传感器为探测前端的本地安防联动系统,同时通过 WiFi 连接云端,向手机 APP 发送推送通知。 +1.1 系统组成 +组件 +硬件规格摘要 +角色 +IPC 摄像头 +720P 分辨率 / TFT LCD 屏( 2 寸,240×320 QVGA 最低) +核心中枢 +手表 / 手环 +OLED 屏(1.6寸,128×64 建议)/ 2 块可充电纽扣电池 +随身接收端 +433MHz 传感器 +门磁、窗磁、PIR、烟感、气体、水浸、振动(7类+SOS) +外挂发射端 +手机 APP +iOS + Android,Google / Apple 推送后台 +远程管理端 +1.2 信号流向(需求层面描述) +433MHz 传感器触发 → 发射固定编码信号(7 位二进制码) +IPC 和手环各自独立接收该 433MHz 信号 +IPC 解码后:屏幕显示报警 ICON、闪光报警、通过 WiFi 推送手机通知 +手环解码后:OLED 显示报警 ICON + 自定义名称、LED 亮对应颜色、马达振动 +📌 注意:IPC 与手环均需独立接收 433MHz 信号,互不依赖; +二、IPC 硬件需求 +2.1 核心硬件规格 +需求项 +规格要求 +优先级 +摄像头分辨率 +720P(1280×720) +必须 +夜视能力 +室内黑暗状态下 10 米可看清实物 +必须 +TFT LCD 屏 +2寸,分辨率不低于 240×320(QVGA) +必须 +433MHz 模块 +内置 433MHz 解码接收模块(被动接收,无需主动发射) +必须 +配对孔 +机身预留配对针孔,按压约 3 秒触发配对模式 +必须 +报警闪灯 +接收到 433MHz 报警信号后开启闪光报警 +必须 +布防/撤防按键 +实体按键,快速切换布防 / 撤防状态(需要LED灯指示状态) +必须 +WiFi 模块 +2.4GHz WiFi 联网,用于 APP 推送和云端同步 +必须 +LAN 接口 +有线以太网接口(RJ45) +必须 +Flash 存储 +存储传感器配对信息(建议支持 ≥16 个传感器) +必须 +2.2 摄像功能 +分辨率:720P(1280×720),摄像头 +夜视:室内黑暗场景,10 米距离可清晰辨认目标物 +建议采用 IR(红外)夜视方案;具体 LED 数量和照射角度由硬件团队评估 +2.3 TFT LCD 显示 +尺寸:2寸 TFT LCD +分辨率:不低于 240×320(QVGA) +显示内容涵盖:已配对传感器列表、配对流程指引、报警画面、布防/撤防状态 +ICON 规格:系统预设传感器图标,IPC 端建议 64×64px +2.4 联网 +WiFi:2.4GHz 无线联网,用于推送报警通知至手机 APP +LAN:RJ45 有线以太网接口 +推送通道:需接入 Google FCM(Android)和 Apple APNs(iOS)后台通讯服务 +三、手表 / 手环硬件与交互需求 +3.1 硬件规格 +需求项 +规格要求 +优先级 +OLED 屏 +1.6 寸,建议 128×64 分辨率 +必须 +433MHz 模块 +内置 433MHz 接收解码模块 +必须 +上/下移动按键 +用于 ICON 列表滚动选择(配对时使用) +必须 +SOS 按键 +长按 5 秒 → IPC 绑定操作;短按 → 配对确认 / SOS 求救触发 +必须 +马达振动 +支持不同时长/频率振动模式(按传感器类型区分) +必须 +LED 指示灯 +支持多色(红/橙/绿/蓝/紫/黄/青),与传感器代码颜色对应 +必须 +电池 +1 块可充电纽扣电池组 +必须 +配对指示灯 +配对模式下白色快闪(~3Hz),区别于正常报警 +必须 +3.2 按键功能定义 +按键 +短按动作 +长按动作 +▲ 上移键 +配对列表向上滚动 / 字符切换 +同时长按▲+▼ 3秒 → 进入配对模式 +▼ 下移键 +配对列表向下滚动 / 字符切换 +长按 2 秒 → 取消当前操作并退出配对 +SOS 键 +配对流程中确认保存(短按)/ 正常模式下发送 SOS 求救信号 +长按 5 秒 → 与 IPC 绑定操作(待确认) +📌 注意:SOS 键的长按与短按功能需与开发确认优先级和防误触机制,避免日常使用中误发 SOS。 +3.3 马达振动模式(按传感器类型区分) +每个报警周期间隔统一为 5 秒,屏幕同时显示传感器名称 + ICON + 颜色(最终客户自己确认具体对应项目)。具体振动参数见下表: +编号 +传感器类型 +LED颜色 +振动次数 +单次时长 +间隔 +A +🚪 门磁/窗磁 +● 红色 +1次短振 +300ms +— +B +👤 PIR 人体感应 +● 橙色 +2次短振 +300ms +200ms +C +🔥 烟雾传感器 +● 绿色 +3次短振 +200ms +150ms +D +🆘 紧急按钮 +● 蓝色 +1次长振 +800ms +— +E +💨 气体传感器 +● 紫色 +4次短振 +200ms +100ms +F +💧 水浸传感器 +● 黄色 +2次长振 +600ms +300ms +G +📳 振动传感器 +● 青色 +连续3次 +150ms +100ms +SOS +🚨 SOS 求救 +● 红色闪烁 +持续长振 +1200ms循环 +— +📌 注意:以上振动时长为参考需求,最终数值需客户确认后锁定,填入固件开发规格。 +四、传感器类型 · ICON · 颜色 · 代码对照最终客户自己确认具体对应项目) +4.1 7类传感器完整映射表(含婴儿监控说明)—这些定义待定 +编号 +433码 +颜色 +图标类型 +传感器用途 +振动模式 +命名示例 +A +0001 +🔴 红色 #FF5252 +🚪 门磁 / 窗磁 +进出口防护,最高优先级报警 +1次短振 300ms +前门门磁 +B +0010 +🟠 橙色 #FF9800 +👤 人体感应 PIR +被动红外,检测移动人体 +2次短振 300ms+200ms间隔 +客厅PIR +C +0011 +🟢 绿色 #4CAF50 +🔥 烟雾传感器 +火灾早期预警 +3次短振 200ms+150ms间隔 +厨房烟感 +D +0100 +🔵 蓝色 #2196F3 +🆘 紧急按钮 +人工手动触发报警 +1次长振 800ms +卧室紧急 +E +0101 +🟣 紫色 #9C27B0 +💨 气体传感器 CO/天然气 +气体泄漏检测 +4次短振 200ms+100ms间隔 +厨房气体 +F +0110 +🟡 黄色 #FFC107 +💧 水浸传感器 +漏水积水检测 +2次长振 600ms+300ms间隔 +阳台水浸 +G +0111 +🩵 青色 #00BCD4 +📳 振动传感器 +门窗/柜体振动监控 +连续3次 150ms+100ms间隔 +门窗振动 +SOS +1000 +🔴 红色闪烁 #FF5252 +🚨 SOS 紧急求救(手环专用) +手环独占,最高优先级 +持续长振 1200ms循环 +系统固定,不可命名 +关于 婴儿监控/婴儿报警 的映射建议: +婴儿监控场景通常结合 PIR(人体感应,编号B)+ 声音探测,如需单独区分,可将某个传感器槽位保留给婴儿监控专用。 +建议在产品设计阶段确认:婴儿监控是否使用独立 433MHz 发射模块,还是复用现有 7 类之一(如 PIR)。 +4.2 ICON 设计规范要求 +图标 +描述 +IPC 尺寸 +手环尺寸 +格式 +🚪 门磁/窗磁 +门或窗户 + 磁感应符号,简洁线条风格 +64×64px +32×32px +PNG/SVG +👤 PIR 人体感应 +人形轮廓 + 辐射波纹 +64×64px +32×32px +PNG/SVG +🔥 烟雾传感器 +烟雾波纹 + 感应器图标 +64×64px +32×32px +PNG/SVG +🆘 紧急按钮 +SOS 文字 + 圆形按钮图形 +64×64px +32×32px +PNG/SVG +💨 气体传感器 +云状气体 + 感叹号 +64×64px +32×32px +PNG/SVG +💧 水浸传感器 +水滴 + 积水波纹 +64×64px +32×32px +PNG/SVG +📳 振动传感器 +窗户/门框 + 震动线 +64×64px +32×32px +PNG/SVG +🚨 SOS 手环专用 +紧急符号 + 手环图形(专属) +64×64px +32×32px +PNG/SVG +🍼 婴儿监控(扩展) +婴儿/摇篮图形,待确认是否独立 +64×64px +32×32px +PNG/SVG +📌 注意:图标颜色与代码绑定关系已锁定,不允许调换。UI 团队出具正式图标后需同步至 IPC 固件和手环固件资源包。 +五、布防 / 撤防功能需求 +5.1 ZONE 区域定义 +区域 +名称 +特性 +说明 +ZONE 0 +永久布防区 +不可撤防 +分配到此区的传感器永远处于激活状态,触发后无论主机是否撤防都会报警,适合门磁/烟感等关键点位 +ZONE 1~N +可控布防区 +可撤防/布防 +用户可通过手机 APP 或 IPC 实体按键手动切换布防/撤防状态,同一 ZONE 内所有传感器同步切换 +5.2 布防操作流程 +📌 注意:以下为需求流程描述,不包含具体实现方案。 +方式一:IPC 实体按键布防 +用户在 IPC 机身找到布防/撤防快捷键 +短按一次 → IPC TFT LCD 显示【布防确认】提示画面,列出即将布防的 ZONE 列表 +再次确认(按确认键)→ 系统进入布防状态 +IPC LCD 状态栏显示【已布防 🔒】标识,配对指示灯可常亮(颜色待定,建议红色) +已绑定手环通过 433MHz 接收布防状态广播,手环 OLED 同步显示【布防中】状态图标 +手机 APP 收到推送通知:【系统已布防】 +5.3 撤防操作流程 +方式一:IPC 实体按键撤防 +用户在 IPC 机身按撤防/布防快捷键 +IPC LCD 显示【撤防确认】提示,显示当前已布防的 ZONE 列表 +用户确认 → 系统撤防(ZONE 0 始终不受影响,界面中标注【永久 - 不可撤防】) +IPC LCD 状态栏切换为【已撤防 🔓】标识 +手环同步更新为撤防状态图标 +手机 APP 推送:【系统已撤防】 +特殊情况:报警触发时的撤防 +触发报警后,IPC LCD 显示【报警状态】页面(大字显示传感器名称 + ICON + 颜色闪烁) +用户可通过 IPC 实体键或 APP 发出撤防指令 +ZONE 1~N 的传感器报警:撤防后停止报警 +ZONE 0 的传感器报警:无法通过撤防停止,须手动解除(具体解除方式待与开发方确认) +5.4 布防/撤防状态显示规范 +状态 +IPC LCD 显示 +手环 OLED 显示 +指示灯 +APP 状态 +已布防 +状态栏:🔒 已布防 | ZONE 列表显示激活标志 +首页显示 🔒 + 已布防 +建议红色常亮 +安防状态:布防中 +已撤防 +状态栏:🔓 已撤防 | ZONE 列表显示撤防标志 +首页显示 🔓 + 已撤防 +熄灭或绿色 +安防状态:撤防中 +报警中 +全屏红色报警画面,显示传感器名称+ICON+颜色闪烁 +全屏 ICON + 名称 + LED 闪烁 + 振动 +红色快闪 +推送通知 + 报警页弹出 +ZONE 0 触发 +报警画面标注【永久区域 - 无法撤防】 +同报警中 +红色快闪持续 +推送通知带【高优先级】标签 +六、配对流程需求(详细步骤) +6.1 IPC 端配对流程(5 步) + Step 1 | 进入配对模式 +触发方式:用按压 IPC 机身配对孔,持续约 3 秒 +LCD 中央显示:扫描/天线动态大图标(建议 80×80px,带循环动画) +顶部状态栏显示:【配对模式】 | 右侧小字:按 CANCEL 退出 +提示文字:第一行【请触发外部传感器】,第二行【等待 433MHz 信号…】(省略号动态) +配对指示灯:蓝色快速闪烁(约 2Hz) +超时机制:30 秒内无信号 → 自动退出,返回主界面 + Step 2 | 信号捕获确认 +触发条件:外部传感器发射 433MHz 信号,IPC 解码成功 +LCD 中央显示对应传感器彩色 ICON(建议 100×100px)+ 颜色背景块 +顶部显示:已捕获二进制代码(等宽放大,字体颜色与传感器颜色一致) +ICON 下方显示:系统默认类型名(如【门磁/窗磁】)及代码 +操作提示:【✓ 确认键保存】 | 【← 返回重试】 +指示灯:从蓝色快闪切换为橙色常亮 +关键需求:如用户按返回键,则重新进入等待状态(不保存本次捕获) + Step 3 | ZONE 区域分配 +进入条件:用户按确认键后自动跳入 +LCD 显示:横向排列所有可用 ZONE 选项(如 ZONE 0 / ZONE 1 / ZONE 2 …) +当前选中项:高亮蓝色背景 +ZONE 0 特殊标注:红色字体显示【永久布防】标签 +ZONE 0 说明文字:★ ZONE 0 永久布防,不受撤防影响 +操作:IPC 实体左/右方向键切换 ZONE,确认键保存后进入命名步骤 + Step 4 | 自定义标识名称输入 +进入条件:ZONE 分配完成后自动跳入(可按返回键跳过) +LCD 顶部:显示已选 ICON(缩略)+ 类型名 + 代码 + ZONE 编号(紧凑一行) +名称输入框:矩形框,光标闪烁,支持字符逐个输入和退格删除 +字数限制:最多 8 个中文 / 16 个英文字母+数字;右下角实时显示剩余字数 +IPC 实体键仅支持英文/数字输入;中文名须通过手机 APP 输入后同步至 IPC +跳过命名:按返回键可跳过,设备以默认类型名保存,后续可随时通过 APP 或 IPC 主界面补命名 + Step 5 | 配对完成确认 +LCD 显示:大号绿色 ✓ 图标 + 【配对成功】文字 + 传感器名称 + ICON + 颜色 +指示灯:绿色常亮 3 秒后自动熄灭 +3 秒后提示:【继续配对下一个?】 — 确认键继续 / 返回键退出配对模式 +主界面设备列表更新:新增条目(主行:自定义名称;副行:类型+代码+ZONE) +联网时 APP 自动收到新设备信息;手环通过 433MHz 接收名称广播更新 +6.2 手环端配对流程(4 步),手表手环,手表时间。平时作为手表使用,显示数字时间。有报警状态显示报警ICON并闪对应光及马达震动 + Step 1 | 进入配对模式 +触发方式:同时长按 ▲ 上移键 + ▼ 下移键 约 3 秒 +防误触设计:必须同时按两键才能进入,避免单键误触 +屏幕显示:全屏天线/配对 ICON(32×32px),下方小字【配对模式 P-00】 +LED:白色快闪(约 3Hz),区别于正常报警颜色 +马达:短振 1 次(100ms),确认进入配对模式 + Step 2 | 上下键滚动选择传感器 ICON +屏幕布局:3 行 ICON 列表(上一个 / 当前选中高亮 / 下一个) +当前选中行:ICON 放大(建议 24×24px),主行显示名称,副行显示代码,右侧颜色指示条 +非选中行:ICON 缩小(16×16px),颜色降饱和 +▲ 向上 / ▼ 向下滚动,到达首尾时循环 +ICON 边框颜色与第四章对照表一致,选中时边框加粗 + Step 3 | 触发传感器,手环捕获代码 +用户触发目标 433MHz 传感器(如打开门磁、按下紧急按钮) +手环解码成功:屏幕选中 ICON 边框快速闪烁 2 次 +代码匹配(与选中 ICON 一致):绿色闪 2 次 → 进入 Step 4 +代码不匹配:红色闪 3 次 + 短振 → 提示重新选择或重试 +超时:30 秒内无捕获 → 自动退出配对,返回 Step 1 + Step 4 | 确认保存,等待名称同步 +操作:短按 SOS 键确认保存配对信息 +屏幕:绿色 ✓ 图标(32×32px)+ 下方【已保存】+ 当前类型名+代码+颜色点 +LED:绿色常亮 2 秒后熄灭 +马达:长振 1 次(400ms)确认成功 +名称同步:IPC 完成命名后,手环自动通过 433MHz 接收名称广播并替换类型名为自定义名称 +退出:成功后 3 秒返回 Step 1 等待继续配对;无操作 10 秒退出配对模式 +取消:任意步骤长按 ▼ 下移键 2 秒,取消当前操作并退出 +七、手机 APP 功能需求 +7.1 核心功能列表 +设备绑定:扫描 QR 码或手动输入设备 ID,将 IPC 绑定至用户账户 +实时监控:查看 IPC 720P 直播画面(需云端中转或 P2P 方案) +报警推送:接收 Google FCM(Android)/ Apple APNs(iOS)报警通知,通知标题显示传感器自定义名称 +布防/撤防控制:远程切换 ZONE 1~N 的布防/撤防状态;ZONE 0 仅展示,不可撤防 +传感器管理:查看已配对传感器列表(名称/类型/ZONE/在线状态) +自定义命名:在 APP 内为每个传感器编辑中文/英文名称,保存后推送至 IPC 和手环 +报警历史:记录历史报警事件(时间/传感器名称/类型/处理状态) +7.2 推送通知规范 +通知标题:传感器自定义名称(如【前门门磁】) +通知内容:报警类型 + 时间(如【门磁触发报警 - 14:35:22】) +ZONE 0 报警:在通知中附加【高优先级 / 无法撤防】标签 +SOS 求救:单独推送渠道,标题【紧急求救 SOS】,全量提醒(声音/振动不受静音影响) +八、待确认事项清单(供工程师评审) +序 +待确认事项 +影响范围 +建议处理方 +1 +7 种传感器的最终产品类型名称(当前占位符 A~G) +ICON 图案 + 默认名称 + 固件资源包 +客户确认 +2 +是否需要独立的【婴儿监控】传感器类型(还是复用 PIR/振动) +编码扩展 + ICON 新增 + 固件 +客户确认 +3 +各代码对应的震动时长(ms)具体数值最终确认 +手环固件振动驱动 +客户确认 +4 +手环 OLED 确切屏幕尺寸和分辨率 +ICON 大小 + 名称截断阈值 +硬件团队 +5 +IPC TFT LCD 确切分辨率 +字体和 ICON 像素规格 +硬件团队 +6 +手环配对确认键方案:SOS 键短按 还是 新增独立确认键 +交互设计 + 固件按键映射 +工程师评估 +7 +IPC 端中文名称输入:APP 输入后推送,还是 IPC 直接支持中文输入法 +固件输入法开发量 +工程师评估 +8 +手环 OLED 名称超宽:跑马灯横向滚动 还是 截断 + 省略号 +固件文字渲染逻辑 +工程师评估 +9 +IPC 最大传感器配对数量上限(建议 ≥16 个) +Flash 存储规划 +工程师评估 +10 +手环名称同步:IPC 433MHz 广播名称的频率和触发时机 +手环接收逻辑 +工程师评估 +11 +ZONE 最大支持数量(建议 ≥4 个,ZONE 0 固定) +UI 布防页设计 + 固件 +工程师评估 +12 +ZONE 0 触发后的报警解除机制 +报警管理逻辑 +需求确认 +13 +云端服务器区域及数据隐私合规要求(GDPR/国内数据合规等) +后端选型 +法务/产品确认 +14 +IPC 与手环的 433MHz 广播是否存在干扰/冲突风险(多台 IPC 场景) +RF 方案设计 +射频工程师 +15 +婴儿监控报警:是否需要摄像头音频侦测联动(哭声检测) +IPC 音频算法需求 +客户确认 +九、版本记录 +版本 +日期 +变更说明 +V1.0 +— +初始需求整理(433MHz IPC 原始文档) +V1.1 +2026年5月 +新增自定义标识名称功能;细化配对 LCD 步骤指引;完善手环震动模式表 +V1.2 +2026年5月 +重新梳理项目整体框架;补充婴儿监控需求说明;完善布防/撤防详细步骤;增加 APP 需求章节;增加更多待确认事项 +—— 文件终(V1.2)· 本文件仅描述产品需求,不包含技术实施方案 —— \ No newline at end of file diff --git a/Docs/50_module-breakdown/mod-key.md b/Docs/50_module-breakdown/mod-key.md new file mode 100644 index 0000000..b714a33 --- /dev/null +++ b/Docs/50_module-breakdown/mod-key.md @@ -0,0 +1,39 @@ +# 模块拆分 - 按键扫描与事件判定 (docs/50_module-breakdown/mod-key.md) + +本模块描述按键状态消抖、长按计时判定以及组合按键识别。 + +## 1. 模块职责说明 + +* **职责范围**: + 1. 在 1ms 定时器中断中建立 10ms 扫描计时周期。 + 2. 对三个物理按键(KEY_UP SW1, KEY_SOS SW3, KEY_DOWN SW2)进行状态扫描。 + 3. 实现消抖检测(连续 20ms 检测到电平稳定才认定按键动作)。 + 4. 对按住不放的行为进行计时累加,判定长按(如长按 SOS 达 5 秒触发对码;长按 ▲+▼ 达 3 秒触发配对模式)。 + 5. 将按键判定结果转换为 `KeyEvent` 枚举,并注入按键事件缓冲区 `key_event_buf` 供主循环异步读取。 + +## 2. 数据结构定义 + +### 按键事件枚举 (KeyEvent) +```c +typedef enum { + KEY_EVENT_NONE, + KEY_UP_SHORT, + KEY_UP_LONG, + KEY_DOWN_SHORT, + KEY_DOWN_LONG, + KEY_SOS_SHORT, + KEY_SOS_LONG, + KEY_COMB_LONG // ▲ + ▼ 组合长按事件 +} KeyEvent; +``` + +## 3. 接口与函数说明 + +* `void Key_Scan_Process(void)`: + 在 Timer 1 ISR 中每 10ms 调度一次。 + * 读取 `KEY_UP`, `KEY_DOWN`, `KEY_SOS` 的 GPIO 逻辑电平。 + * 如果发现按键电平状态改变,进行计数消抖。 + * 如果按住不放,累加计时计数器(`key_up_hold`, `key_down_hold`, `key_sos_hold`)。 + * 检测到 `key_up_hold >= 300` 且 `key_down_hold >= 300` 达 3 秒时,注入 `KEY_COMB_LONG` 事件。 + * 检测到 `key_sos_hold >= 500` 达 5 秒时,注入 `KEY_SOS_LONG` 事件;若释放时未达 5 秒但大于 20ms,注入 `KEY_SOS_SHORT`。 + * 事件解析完毕后存入 `key_event_buf`。 diff --git a/Docs/50_module-breakdown/mod-lcd.md b/Docs/50_module-breakdown/mod-lcd.md new file mode 100644 index 0000000..f06cc3a --- /dev/null +++ b/Docs/50_module-breakdown/mod-lcd.md @@ -0,0 +1,29 @@ +# 模块拆分 - LCD 显示与字模驱动 (docs/50_module-breakdown/mod-lcd.md) + +本模块管理 AMOLED 屏幕引脚、SGM3833 电源使能、SPI 传输和界面图文渲染。 + +## 1. 模块职责说明 + +* **职责范围**: + 1. 提供底层的 SPI 单向命令/数据通道。 + 2. 控制 SGM_CTRL 供电管脚。 + 3. 支持屏幕窗口配置,实现底层清屏与局部像素写入。 + 4. 读取 `lcd_font.h` 库,在指定坐标绘制 8x16 字符与自定义大小位图。 + 5. 实现各业务状态屏幕画面绘制。 + +## 2. 底层显示接口 + +* `void LCD_Init(void)`:初始化 LCD 引脚,置 `SGM_CTRL = 1` 使能正负高压供电,并发送 RM69310 的初始化 SPI 寄存器序列。 +* `void LCD_WriteCmd(u8 cmd)`:拉低 `LCD_RS` 发送 SPI 命令。 +* `void LCD_WriteData(u8 dat)`:拉高 `LCD_RS` 发送 SPI 数据。 +* `void LCD_SetWindow(u16 x1, u16 y1, u16 x2, u16 y2)`:设置局部像素绘制范围(强制处理 X 轴偏量 `+4`)。 +* `void LCD_Clear(u16 color)`:将整块屏幕刷写为特定背景色。 + +## 3. 图文绘制与 UI 渲染 + +* `void LCD_ShowChar(u16 x, u16 y, char c, u16 fc, u16 bc)`:在 (x, y) 坐标处绘制 ASCII 字符 `c`。 +* `void LCD_ShowString(u16 x, u16 y, char *s, u16 fc, u16 bc)`:循环打印字符串。 +* `void LCD_ShowImage(u16 x, u16 y, u16 w, u16 h, const u8 *img, u16 fc, u16 bc)`:在指定区域绘制单色位图。 +* `void UI_ShowClockPage(SystemState state, u8 hour, u8 min)`:渲染时钟待机页面,并根据布撤防状态在状态栏绘制相应锁状态图标。 +* `void UI_ShowAlarmPage(char *sensor_name, u8 zone)`:渲染防区告警页面,显示对应的传感器位图和法语名称。 +* `void UI_ShowPairMenuPage(u8 selected_index)`:渲染对码选择菜单。 diff --git a/Docs/50_module-breakdown/mod-power.md b/Docs/50_module-breakdown/mod-power.md new file mode 100644 index 0000000..7dd9c3a --- /dev/null +++ b/Docs/50_module-breakdown/mod-power.md @@ -0,0 +1,23 @@ +# 模块拆分 - 低功耗电源管理 (docs/50_module-breakdown/mod-power.md) + +本模块描述手环待机情况下的屏幕休眠与单片机停机唤醒机制,用于降低日常静态电流消耗。 + +## 1. 模块职责说明 + +* **职责范围**: + 1. 在系统无物理按键按下且无射频信号输入达 **10 秒** 后,触发低功耗睡眠动作。 + 2. 向 RM69310 屏幕控制器发送 Sleep In(命令 `0x10`)使 AMOLED 内部关闭。 + 3. 拉低 `SGM_CTRL = 0` 彻底切断 SGM3833 的正负压与 VAVDD 供电,将屏幕静态电流降至微安级。 + 4. 将按键引脚(KEY_UP P0.1, KEY_SOS P0.2, KEY_DOWN P0.3)配置为外部中断源。 + 5. 调用单片机 PD 停机控制寄存器,关闭主时钟进入 Power-Down 停机状态。 + 6. 在用户按压任意键时通过引脚电平下降沿产生外部中断,瞬时唤醒主控时钟并恢复供电。 + +## 2. 接口与函数设计 + +* `void Enter_Deep_Sleep(void)`: + 1. 通过 SPI 通道写指令 `0x10`。 + 2. 设置 `SGM_CTRL = 0`。 + 3. 清除外部中断标志,使能 P0 口外部中断输入。 + 4. 设置单片机的 `PCON` 寄存器使 `PD = 1`。主时钟停止。 +* `void Key_Wakeup_Isr(void)`: + 按键外部中断服务函数,唤醒后拉高 `SGM_CTRL = 1` 恢复屏幕高压供电,并发送 Wake Up 指令 `0x11` 重建屏幕显示。 diff --git a/Docs/50_module-breakdown/mod-rf.md b/Docs/50_module-breakdown/mod-rf.md new file mode 100644 index 0000000..c1b2cb8 --- /dev/null +++ b/Docs/50_module-breakdown/mod-rf.md @@ -0,0 +1,26 @@ +# 模块拆分 - 自适应 RF 解码与发射 (docs/50_module-breakdown/mod-rf.md) + +本模块管理射频芯片 GPIO 初始化、433MHz EV1527 解调接收(宽限时序比值算法)与射频打包主动发射。 + +## 1. 模块职责说明 + +* **职责范围**: + 1. 初始化射频接收和发射控制引脚。 + 2. 对射频输入引脚进行高低电平时序轮询解码,计算其高低电平比值,实现自适应 EV1527 24位数据解码。 + 3. 在特定系统请求下(例如发送 SOS 呼救或与 IPC 对码绑定),主动通过发射引脚发出 EV1527 编码帧。 +* **自适应比值解调机制**: + 不依赖死宽度的微秒延迟。检测同步头低电平(时间在 1500~60000us 之间)与高电平时间(时间在 50~2500us 之间),算出两者的时序比例。 + * 以 `31` 作为低电平对高电平的时钟倍率基准(高低比值支持在 `15` 到 `48` 倍范围)。 + * 计算出每 bit 的标准时钟周期 `T = low_time / 31`,根据此动态 `T` 进行后续 24 个数据位(高/低电平宽度)的比值反解。 + +## 2. 接口与函数说明 + +* `void RF_Init(void)`:配置射频芯片引脚方向,初始化控制端口电平。 +* `bit EV1527_Decode(u32 *out_addr, u8 *out_type)`: + 进行射频接收包解码尝试。 + * 首先通过计数检测大于 `1500us` 的同步低电平前导信号。 + * 自适应计算时钟基准 `T`。 + * 连续接收 24 位宽数据:如果某个数据位的高电平比低电平长,则认为该位为 `1`;反之为 `0`。 + * 解码成功将 20 位地址存入 `out_addr`,后 4 位数据存入 `out_type` 并返回 `1`;否则返回 `0`。 +* `void EV1527_Transmit(u32 addr, u8 data_code)`: + 主动发射一帧数据。生成同步头,并按 `1` 码(高电平 3T, 低电平 1T)或 `0` 码(高电平 1T, 低电平 3T)依次驱动发射引脚电平,持续多帧以确保接收方捕获。 diff --git a/Docs/50_module-breakdown/mod-sys.md b/Docs/50_module-breakdown/mod-sys.md new file mode 100644 index 0000000..cb314fb --- /dev/null +++ b/Docs/50_module-breakdown/mod-sys.md @@ -0,0 +1,55 @@ +# 模块拆分 - 系统核心控制与存储 (docs/50_module-breakdown/mod-sys.md) + +本模块定义手环系统的生命周期、时间计数器、定时中断调度器以及断电保存数据库模块。 + +## 1. 模块职责说明 + +* **职责范围**: + 1. 驱动系统主状态机运转(`current_state`)。 + 2. 提供 RTC 软时钟维护(`current_hour`, `current_min`, `current_sec`)。 + 3. 利用 Timer 1 产生 1ms 定时器中断,维护毫秒滴答计数器并作为其他外设时效的基础。 + 4. 实现 IAP 读写底层函数,对防区数据库进行存取与读取。 +* **模块依赖**: + * 依赖 `mod-lcd.md` 进行 UI 状态重绘。 + * 依赖 `mod-rf.md` 提供 RF 解码信号与数据包注入。 + * 依赖 `mod-key.md` 响应按键动作。 + +## 2. 数据结构定义 + +### 2.1 传感器插槽结构体 (Sensor_Slot) +```c +typedef struct { + u32 addr; // 24位 EV1527 地址 + u8 type; // 传感器类型 + u8 zone; // 防区类别 (0: 永久防区, 1: 可撤布防区) + char name[16]; // 法语自定义名称 +} Sensor_Slot; +``` + +### 2.2 系统状态枚举 (SystemState) +```c +typedef enum { + STATE_NORMAL, + STATE_ARMED, + STATE_DISARMED, + STATE_PAIR_MENU, + STATE_PAIR_WAIT, + STATE_PAIR_CONFIRM, + STATE_PAIR_SUCCESS, + STATE_PAIR_FAIL, + STATE_ALARMING, + STATE_SOS_EMITTED +} SystemState; +``` + +## 3. 模块接口说明 + +### 3.1 定时与时钟函数 +* `void Timer1_Init(void)`:配置 Timer 1 作为 1ms 中断源,并使能全局中断。 +* `void Timer1_Isr(void) interrupt 3`:ISR 中断处理,自增时间滴答、维护 LED 闪烁周期、马达振动波形定时,以及维护软件 RTC(自增 `current_sec` 并在满 60 秒时触发主界面分钟刷新)。 + +### 3.2 IAP 数据库存储接口 +* `void Load_Database(void)`:从 Flash 扇区 `0xFE0000` 读取 16 组传感器配置载入内存的 `sensor_list`。 +* `void Save_Database(void)`:擦除 Flash 扇区并将内存中的 `sensor_list` 写入 Flash。 +* `void Add_Sensor(u32 addr, u8 type)`:向 `sensor_list` 添加传感器信息并自动调用 `Save_Database()`。 +* `bit Check_Sensor_ID(u32 addr, u8 *out_slot_index)`:在数据库中检索该地址码,若存在则返回它的槽位索引。 diff --git a/Docs/60_coding/mod-key.md b/Docs/60_coding/mod-key.md new file mode 100644 index 0000000..afffee8 --- /dev/null +++ b/Docs/60_coding/mod-key.md @@ -0,0 +1,72 @@ +# 编码实现 - 按键扫描与事件判定 (docs/60_coding/mod-key.md) + +本文件对应按键状态消抖、长按计时判定以及组合按键识别的具体编码实现细节。 + +## 1. 对应代码源文件 + +* [App/main.c](file:///c:/workfile/105/stc32g12k128/App/main.c) (定时中断内的按键扫描、按键状态累加、主循环事件处理) + +## 2. 关键代码片段与逻辑实现 + +### 2.1 按键中断定时器扫描 (每 10ms 调度一次) +在 `Timer1_Isr` (1ms 滴答) 内,通过 `key_scan_timer` 进行 10ms 分频并调用 `Key_Scan_Process()`: +```c +void Key_Scan_Process(void) { + // 1. 读取引脚状态并消抖 + static u8 key_up_state = 0xFF; + static u8 key_down_state = 0xFF; + static u8 key_sos_state = 0xFF; + + key_up_state = (key_up_state << 1) | KEY_UP; + key_down_state = (key_down_state << 1) | KEY_DOWN; + key_sos_state = (key_sos_state << 1) | KEY_SOS; + + // 2. 长按与组合按键计时器累加 + if ((key_up_state & 0x07) == 0 && (key_down_state & 0x07) == 0) { + // ▲ + ▼ 组合键按住 + comb_hold++; + if (comb_hold == 300) { // 3秒 + key_event_buf = KEY_COMB_LONG; + } + } else { + comb_hold = 0; + + // 单个按键:▲ 键 + if ((key_up_state & 0x07) == 0) { + key_up_hold++; + } else { + if (key_up_hold > 2 && key_up_hold < 200) { + key_event_buf = KEY_UP_SHORT; + } + key_up_hold = 0; + } + + // 单个按键:SOS 键 + if ((key_sos_state & 0x07) == 0) { + key_sos_hold++; + if (key_sos_hold == 500) { // 5秒 + key_event_buf = KEY_SOS_LONG; + } + } else { + if (key_sos_hold > 2 && key_sos_hold < 500) { + key_event_buf = KEY_SOS_SHORT; + } + key_sos_hold = 0; + } + // ▼ 键处理同上... + } +} +``` + +### 2.2 事件异步读取与清除 +主循环在其周期内轮询 `key_event_buf`。一旦发现其非 `KEY_EVENT_NONE`,拷贝并立即重置该缓冲区为 `KEY_EVENT_NONE` 以防止重复触发: +```c +if (key_event_buf != KEY_EVENT_NONE) { + KeyEvent evt = key_event_buf; + key_event_buf = KEY_EVENT_NONE; // 清空事件缓冲区 + + // 执行具体事件动作,如在常态下短按 SOS 进入 SOS 警报发射状态等 + // ... +} +``` +通过该缓冲区实现了按键中断采集与主循环处理的解耦。 diff --git a/Docs/60_coding/mod-lcd.md b/Docs/60_coding/mod-lcd.md new file mode 100644 index 0000000..4f08000 --- /dev/null +++ b/Docs/60_coding/mod-lcd.md @@ -0,0 +1,44 @@ +# 编码实现 - LCD 显示与字模驱动 (docs/60_coding/mod-lcd.md) + +本文件对应 LCD 驱动与字模渲染的具体编码实现细节。 + +## 1. 对应代码源文件 + +* [Drivers/lcd.c](file:///c:/workfile/105/stc32g12k128/Drivers/lcd.c) (LCD 初始化、SPI 数据写、画图画字函数) +* [Drivers/lcd.h](file:///c:/workfile/105/stc32g12k128/Drivers/lcd.h) (管脚接口与画图画字声明) +* [Drivers/lcd_font.h](file:///c:/workfile/105/stc32g12k128/Drivers/lcd_font.h) (ASCII 8x16 点阵字模与图标数据) + +## 2. 关键代码片段与逻辑实现 + +### 2.1 局部窗口设置 (X轴偏移量修正) +在 `lcd.c` 中配置窗口时,需要向 X 轴坐标加上 4 像素偏差: +```c +void LCD_SetWindow(u16 x1, u16 y1, u16 x2, u16 y2) { + x1 += 4; + x2 += 4; + LCD_WriteCmd(0x2A); // Column Address Set + LCD_WriteData(x1 >> 8); + LCD_WriteData(x1 & 0xFF); + LCD_WriteData(x2 >> 8); + LCD_WriteData(x2 & 0xFF); + + LCD_WriteCmd(0x2B); // Row Address Set + LCD_WriteData(y1 >> 8); + LCD_WriteData(y1 & 0xFF); + LCD_WriteData(y2 >> 8); + LCD_WriteData(y2 & 0xFF); +} +``` + +### 2.2 电池图标与百分比坐标对齐 +在 `main.c` 的时钟重绘函数中,通过以下坐标绘制电量: +```c +// 绘制百分比字符 +LCD_ShowString(62, 24, "85%", WHITE, BLACK); +// 绘制电池位图 +LCD_ShowImage(92, 27, 24, 12, BATTERY_ICON, RED, BLACK); +``` +两者 Y 轴保持 24 与 27 对齐,X 轴保持 30 像素的安全边距(62 + 3 * 8 = 86,与 92 之间有 6 像素的安全间距),防止文字错乱或重合。 + +### 2.3 字模百分号偏置 +在 `lcd_font.h` 的 ASCII 字符数组中,百分号 `%`(十进制 37,十六进制 0x25)的字模通过在字模绘制时向上平移 2 像素对齐普通数字的下基准线。 diff --git a/Docs/60_coding/mod-power.md b/Docs/60_coding/mod-power.md new file mode 100644 index 0000000..b578a1d --- /dev/null +++ b/Docs/60_coding/mod-power.md @@ -0,0 +1,56 @@ +# 编码实现 - 低功耗电源管理 (docs/60_coding/mod-power.md) + +本文件对应低功耗待机与外部中断唤醒的具体编码实现细节。 + +## 1. 对应代码源文件 + +* [App/main.c](file:///c:/workfile/105/stc32g12k128/App/main.c) (休眠控制、外部中断初始化与服务函数) + +## 2. 关键代码片段与逻辑实现 + +### 2.1 屏幕休眠命令与供电切断 +在待机检测到达 10 秒超时后,调用进入睡眠流程: +```c +void Enter_Deep_Sleep(void) { + // 1. 发送屏幕休眠指令 (Sleep In) + LCD_WriteCmd(0x10); + Delay_ms(120); // 必须等待 120ms 供屏幕电容放电 + + // 2. 切断 SGM3833 正负高压供电,阻断 VELVDD 的泄露电流 + SGM_CTRL = 0; + + // 3. 配置 KEY 端口的中断触发(配置为下降沿触发) + P0IM0 = 0x00; // 下降沿 + P0IM1 = 0x00; + P0INTE = 0x0E; // 开启 P0.1, P0.2, P0.3 的端口中断 + + // 4. 调用单片机 PD 指令挂起主时钟 + PCON |= 0x02; // PD = 1 + _nop_(); + _nop_(); +} +``` + +### 2.2 端口中断唤醒与显示重建 +当按键被按下(P0.1~P0.3 电平跳变),触发外部端口中断,CPU 被唤醒并从停机处继续执行: +```c +void Port0_Isr(void) interrupt 37 { + u8 int_src = P0INTF; + P0INTF = 0x00; // 清除中断标志位 + + if (int_src & 0x0E) { + // 1. 恢复 SGM3833 供电 + SGM_CTRL = 1; + Delay_ms(20); // 等待供电稳定 + + // 2. 发送唤醒屏幕指令 (Sleep Out) + LCD_WriteCmd(0x11); + Delay_ms(120); + + // 3. 恢复工作状态与屏幕刷新 + current_state = STATE_NORMAL; + clock_updated = 1; + } +} +``` +通过中断直接退出 `PD` 模式,恢复手环的常规时钟显示。 diff --git a/Docs/60_coding/mod-rf.md b/Docs/60_coding/mod-rf.md new file mode 100644 index 0000000..e58df4c --- /dev/null +++ b/Docs/60_coding/mod-rf.md @@ -0,0 +1,36 @@ +# 编码实现 - 自适应 RF 解码与发射 (docs/60_coding/mod-rf.md) + +本文件对应自适应射频解码与主动发射的具体编码实现细节。 + +## 1. 对应代码源文件 + +* [Drivers/rf.c](file:///c:/workfile/105/stc32g12k128/Drivers/rf.c) (RF 初始化、EV1527 时序比值软解码、主动发射) +* [Drivers/rf.h](file:///c:/workfile/105/stc32g12k128/Drivers/rf.h) (芯片控制管脚与编解码函数声明) + +## 2. 关键代码片段与逻辑实现 + +### 2.1 自适应比值解码 (EV1527_Decode) +解码时,软件轮询检测接收管脚状态。 +1. **抓取同步头**:检测到一个大于 `1500us` 且小于 `60000us` 的低电平。 +2. **计算基准 T**:`T = low_time / 31`。 +3. **采集 24 个数据位**:对于每一位,检测它的高电平持续时间 `h_val` 和低电平持续时间 `l_val`: + ```c + // 解码一位数据 + if (h_val > l_val) { + res = (res << 1) | 1; + } else { + res = (res << 1) | 0; + } + ``` +4. **极速解析与 CPU 效率**:去除了之前在主循环中阻塞多帧解析的详细日志串口打印,仅在解析整帧成功后发送一行 Summary。单帧处理耗时控制在 **2ms** 以内,彻底避免了连续波形导致的丢包现象。 + +### 2.2 主动对码广播发射 +手环通过 LT4455 发送对码广播,在 `rf.c` 中实现: +```c +void EV1527_Transmit(u32 addr, u8 data_code) { + // 产生多帧 EV1527 时序 + // 包含同步头 (高 1T + 低 31T) 与 24 位数据码 + // ... +} +``` +通过控制发射引脚的电平跳转产生标准的 433MHz EV1527 载波信号。 diff --git a/Docs/60_coding/mod-sys.md b/Docs/60_coding/mod-sys.md new file mode 100644 index 0000000..362a0e3 --- /dev/null +++ b/Docs/60_coding/mod-sys.md @@ -0,0 +1,29 @@ +# 编码实现 - 系统核心控制与存储 (docs/60_coding/mod-sys.md) + +本文件对应系统控制与存储的具体编码实现细节。 + +## 1. 对应代码源文件 + +* [App/main.c](file:///c:/workfile/105/stc32g12k128/App/main.c) (系统主循环、定时器中断、IAP 存取) +* [App/config.h](file:///c:/workfile/105/stc32g12k128/App/config.h) (系统状态及数据结构定义) + +## 2. 关键代码片段与逻辑实现 + +### 2.1 定时器 1 中断服务 (Timer1_Isr) +定时器初始化配置为 1ms,在 `main.c` 中第 190~250 行左右实现。 +在 `Timer1_Isr` 里,我们累加 `ms_tick`。当累加到 1000 时,重置 `ms_tick = 0`,自增 `current_sec`。若秒达 60,则自增 `current_min` 并置位 `clock_updated = 1` 通知主循环在下一个周期重绘时钟画面。 + +### 2.2 IAP 读写细节 +在 `main.c` 中通过 IAP 寄存器控制读写。 +```c +void IAP_EraseSector(u16 addr) { + IAP_CMD = 3; // 扇区擦除 + IAP_ADDRL = addr & 0xFF; + IAP_ADDRH = addr >> 8; + IAP_TRIG = 0x5A; // 触发 + IAP_TRIG = 0xA5; + _nop_(); +} +``` +- 主动擦除与写字节前必须先调用使能,写完后调用 `IAP_Disable()` 避免误写入损坏配置。 +- 数据库保存在末尾扇区 `0xFE0000`(大小为 512 字节)。 diff --git a/Docs/70_testing/main.md b/Docs/70_testing/main.md new file mode 100644 index 0000000..90f7488 --- /dev/null +++ b/Docs/70_testing/main.md @@ -0,0 +1,47 @@ +# 测试设计与验证方法 (docs/70_testing/main.md) + +本章节整理手环系统的仿真联调指令、IO 状态变化监控规范以及人工实机验证用例。 + +## 1. 串口仿真指令注入规范 + +手环程序在 `main.c` 中通过 UART1 实现了仿真指令解析,支持在开发及免焊测试阶段通过电脑串口助手直接向手环发送字符指令,仿真各种物理交互: + +| 串口发送字符 | 对应仿真的物理事件与说明 | 预期系统响应 | +| :--- | :--- | :--- | +| **`u` / `U`** | 仿真短按 / 长按 **▲ 键** | 时钟界面切换 / 菜单滚动 | +| **`d` / `D`** | 仿真短按 / 长按 **▼ 键** | 时钟界面切换 / 菜单滚动 / 长按取消报警 | +| **`s` / `S`** | 仿真短按 / 长按 **SOS 键** | 触发本地主动 SOS 警报 / 触发对码保存 | +| **`c`** | 仿真同时长按 **▲ + ▼ 键 3 秒** | 系统直接进入配对模式(P-00) | +| **`a`** | 仿真捕获到**已配对门磁**报警信号 | 系统跳转到门磁报警界面,亮红灯,振动 1 次 | +| **`p`** | 仿真捕获到**已配对 PIR 红外**信号 | 系统跳转到 PIR 报警界面,亮橙灯,振动 2 次 | +| **`1`** | 强制切换系统状态为 **NORMAL** (撤防/常态) | 屏幕时钟状态栏显示 `NORMAL` | +| **`2`** | 强制切换系统状态为 **ARMED** (布防) | 屏幕时钟状态栏显示红色闭锁 `🔒` | +| **`3`** | 强制切换系统状态为 **DISARMED** (撤防) | 屏幕时钟状态栏显示绿色开锁 `🔓` | + +--- + +## 2. LED 与马达 IO 跳变日志 + +主循环对外设引脚的电平状态进行了跟踪,当引脚电平发生变化时,自动通过串口向外部终端打印输出日志。这可以在没有焊接实体马达或 LED 灯的情况下验证控制逻辑是否正确: +* **LED 状态日志格式**:`[STATE] LED status -> R:1 G:0 B:0` (0表示亮,1表示灭) +* **马达状态日志格式**:`[STATE] MOTOR -> 1` (1表示开启振动,0表示关闭振动) + +--- + +## 3. 人工实机验证用例 + +### 3.1 时钟待机与电量对齐 +* **测试方法**:正常上电启动,查看待机界面。 +* **预期结果**:时钟、日期显示正常。屏幕右上角的电量 `85%` 与电池图标对齐美观,无任何重叠或乱码(间距为 6 像素)。 + +### 3.2 传感器对码与保存 +* **测试方法**:同时长按 ▲+▼ 3秒进入配对,用上下键选中 `DETEC. PIR`,然后物理触发 PIR 传感器,随后按下 SOS 确认保存。 +* **预期结果**: + 1. 进入配对时,LED 亮白色闪烁(3Hz),马达短振 1 次确认。 + 2. 解码匹配成功后,OLED 屏显示 `✓已保存`,LED 绿色常亮 2 秒,马达强振 1 次(400ms),随后自动退回配对菜单。 + +### 3.3 低功耗休眠与按键唤醒 +* **测试方法**:在常态下静置手环 10 秒钟不作任何操作。 +* **预期结果**: + 1. 10秒后,屏幕全黑关闭,且 LED 熄灭,马达不鸣叫(`SGM_CTRL` 拉低为0)。 + 2. 按下任意一个物理按键(▲、▼、SOS),屏幕瞬间被点亮并重新恢复时钟待机画面。 diff --git a/开发规范.md b/开发规范.md new file mode 100644 index 0000000..f905e91 --- /dev/null +++ b/开发规范.md @@ -0,0 +1,51 @@ +# XAgent 项目协作流程(人工手动执行版) + +> 平台造出来之前,我们先人工按这套方法在这个仓库里干活。正式的结构化定义在 `.xagent/stages.json`(文件夹模板)和 `.xagent/graph.json`(依赖与写锁账本),本文档是操作层面的"人手动怎么做"。 +> +> **判脏已自动化**:依赖关系(`depends_on`)由人声明,但"谁因为上游变了而过时(脏)"不再手工记 commit hash——运行 `bash .xagent/check-dirty.sh` 由 git 历史自动算出。 + +## 1. 七个环节 = `docs/` 下七个目录 + +| 目录 | 环节 | 负责人设 | 类型 | +|---|---|---|---| +| `docs/10_requirements-overview/` | 总体需求 | 需求分析师 | 单文档 | +| `docs/20_requirements-detail/` | 细化需求 | 需求分析师 | 单文档 | +| `docs/30_architecture/` | 架构设计 | 架构师 | 单文档 | +| `docs/40_ui-design/` | UI 设计 | UI 设计师 | 单文档 | +| `docs/50_module-breakdown/` | 模块拆分 | 架构师 | 并行任务(一个模块一个文件) | +| `docs/60_coding/` + `src/**` | 编码实现 | 开发者 | 并行任务(一个模块一个文件 + 对应代码) | +| `docs/70_testing/` + `tests/**` | 测试 | 测试工程师 | 单文档 + 测试代码 | + +任何目录下的任何文件,任何人、任何 AI,任何时间都可以改,没有"上一环节没做完就不能碰下一环节"的门禁——具体原则见 `docs/10_requirements-overview/main.md` 第 2 节。 + +## 2. 新建一份产出物,手动怎么做 + +1. 确定它属于哪个环节,放进对应目录;并行任务类目录(模块拆分、编码)里,每个模块/任务单独一个文件,文件名用模块名(例如 `mod-auth.md`)。 +2. 打开 `.xagent/stages.json`,抄这个目录的 `default_depends_on` 作为这份文件依赖的起点,写进 `.xagent/graph.json` 的 `files` 里;并行任务类目录额外看 `link_same_name_from`,把同名的上游文件也加进 `depends_on`。只需填 `depends_on` 和 `lock: null`——**不需要**记任何 commit hash。 +3. 找对应人设(见第 1 节表格)的 AI 一起起草内容——现在还没有 `.xagent/roles/*.md` 提示词文件,人工凭这份表格,手动跟 AI 说清楚"请用 XX 的视角帮我写"。 +4. 人工审阅、编辑,确认后 `git add` + `git commit`。 +5. 提交后跑 `bash .xagent/check-dirty.sh` 自检:新文件首次提交后即有基线,脚本据 git 历史自动判脏,无需手工登记。 + +## 3. 修改一份已有产出物,手动怎么做(写锁 + 脏检查) + +现在没有软件强制加锁和自动判脏,这一步全靠人自觉遵守: + +1. **占用(手动加锁)**:开始改之前,把 `.xagent/graph.json` 里这份文件的 `lock` 字段填上 `{"holder_type": "human"/"ai", "holder_id": "...", "acquired_at": "..."}`,同时在群里说一声"我在改 XX"。别人看到 `lock` 非空,就不要同时改这份文件。 +2. **改内容**:跟对应人设的 AI 协作起草、人工审阅确认。 +3. **释放(手动解锁)**:改完提交后,把 `lock` 字段清成 `null`。 +4. **自动判脏**:改完提交后,跑 `bash .xagent/check-dirty.sh`。它会读 `depends_on`、用 git 历史列出"依赖你这份文件、且尚未跟进"的下游文件。把列出来的下游 @ 给相关负责人。 +5. 被提醒的人复核后,把自己的文件改好并 `git commit`——**提交即自动消脏**,不用改任何 hash。(若确认上游变动对自己无影响,直接提交一次即可刷新基线。) + +## 4. 现在靠人工代偿、以后平台要自动化的事情 + +| 现在怎么做(人工) | 以后平台怎么做 | +|---|---| +| 手动填 `graph.json` 的 `depends_on`(仅依赖关系) | 新建文件自动带入默认依赖 | +| 跑 `check-dirty.sh` 判脏(git 历史自动算,无需手记 hash) | 实时自动判定,dirty 状态直接在界面标红 | +| 手动在 `lock` 字段占位、口头通知 | 编辑器里实时显示占用状态,防止误改 | +| 凭表格说明手动跟 AI 说"用 XX 视角" | 按文件路径自动加载对应人设 prompt | +| 讨论散落在群聊里 | 讨论侧栏跟对应产出物直接关联 | + +## 5. 本项目当前进度 + +> 各项目自己维护这一节:按七环节结构列出 `docs/` 下已完成、进行中、待补的产出物。新项目初始化时这一节为空,随开发推进逐步填写。