Compare commits

..

5 Commits

21 changed files with 1768 additions and 0 deletions

107
.xagent/check-dirty.py Normal file
View File

@@ -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)

4
.xagent/check-dirty.sh Normal file
View File

@@ -0,0 +1,4 @@
#!/bin/bash
# XAgent 判脏检测脚本 - 自动通过 git 历史对比最后提交时间
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
python "$SCRIPT_DIR/check-dirty.py"

145
.xagent/graph.json Normal file
View File

@@ -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
}
}
}

65
.xagent/stages.json Normal file
View File

@@ -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"
]
}
]
}

View File

@@ -0,0 +1,30 @@
# 总体需求 (Docs/10_requirements-overview/main.md)
## 1. 项目整体框架与系统拓扑
本项目**仅包含手环端核心业务及驱动代码的实现**。
* **被动接收**:手环作为随身告警终端,独立接收来自 **7类 433MHz 传感器**的探测射频信号(本地安防联动系统)。
* **主动发射**:手环在本地触发 SOS 求救时,必须向外主动发射 **433MHz SOS 呼救射频信号**,使周边的 IPC 摄像头接收并联动。
* 周围的其他组件(如 IPC 摄像头和手机 APP不在本项目编码范围内仅作外部仿真与对接边界。
```mermaid
graph TD
Sensor[433MHz 传感器] -->|433MHz 报警信号| Wristband[随身手环]
Wristband -->|433MHz SOS 呼救信号| IPC[IPC 摄像头 (仿真对接)]
```
## 2. 核心信号流向与手环行为要求
1. **传感器触发**探测到异常时发射固定编码信号24位 EV1527 协议数据,包含 20位地址码 + 4位数据码
2. **手环独立接收**:手环配备 LR690L 433MHz 接收解调芯片,独立进行软件自适应时序解码。
3. **手环响应行为**
* **屏幕显示**1.14寸135x240 像素,**竖直排版**TFT LCD 屏幕,**保持常亮显示时间**。报警时呈现对应的报警 ICON + 传感器法语名称。
* **灯效响应**:可编程 RGB 灯珠根据传感器类型亮起对应颜色并进行特定波形闪烁。
* **马达响应**:马达触发特定的振动次数与时长组合。
4. **手环主动发射与辨识**
* **主动求救**:用户短按手环上部的 SOS 键,手环进入 SOS 状态,并使用**出厂固定唯一 ID (Factory ID)** 作为源地址发射 433MHz 的 SOS 编码电报。
* **身份辨识**:该出厂 ID 作为手环的唯一物理地址标识,以便 IPC 摄像头可以通过与该 ID 配对,唯一识别该手环并将其与具体的用户绑定。
## 3. 协作规范与门禁说明
本项目的开发协作基于《开发规范.md》定义的“七个环节”。任何目录下文件的修改应当保证其依赖项是同步的。
在每次完成阶段性工作或代码调整后,应运行 `python .xagent/check-dirty.py` 进行脏检测自检。

View File

@@ -0,0 +1,53 @@
# 细化需求 (Docs/20_requirements-detail/main.md)
本章节定义手环终端的物理按键交互、报警矩阵(可编程 RGB 灯颜色与振动)以及防区划分详细需求。
## 1. 物理按键与交互定义
手环共配备 **5 个物理按键**:侧边 3 个(▲ 上移、▼ 下移、■ 确认),上部 2 个(相同的 SOS1、SOS2具备完全一致的并联电气逻辑
| 按键类别 | 按键动作 | 常态 (时钟待机) | 对码配对模式 | 警报触发状态 |
| :--- | :--- | :--- | :--- | :--- |
| **侧边按键** | **▲ 上移键** | 短按:时间微调 | 菜单向上滚动 / **确认对码时自增序号 (01->99)** | 无动作 |
| **侧边按键** | **▼ 下移键** | 短按:时间微调 | 菜单向下滚动 / **确认对码时自减序号 (99->01)** | 长按 2 秒:解除当前告警并退回时钟 |
| **侧边按键** | **▲ + ▼ (同时长按3秒)** | **进入对码配对模式** | 无动作 | 无动作 |
| **侧边按键** | **■ 确认键 (CONFIRM)** | 无动作 | 菜单确认 / **对码确认页:确认保存配置** | 短按:解除当前告警并退回时钟 |
| **上部按键** | **SOS1 / SOS2 (短按)** | **触发本地主动 SOS 呼救 + 循环发射 433MHz 呼救包** | **对码确认页:确认保存配置** | 短按:解除告警并退回时钟 |
| **上部按键** | **SOS1 / SOS2 (长按5秒)** | **触发 IPC 绑定对码广播** | 无动作 | 长按:解除告警并退回时钟 |
> [!NOTE]
> 1. **双 SOS 按键冗余**:上部的两个 SOS 键在电路与软件上完全等价。按下其中任意一个按键,均会触发相同的 SOS 信号采集与求救动作。
> 2. **警报快速解除**:在手环发出主动 SOS 呼救或响应被动防区报警时按下“确认键”或任意一个“SOS 键”或者长按“▼键”2秒均会立刻切断声光电反馈安全重置至时钟待机状态。
> 3. **确认页序号手动修改**:当捕获到匹配 of 射频信号进入对码确认页时,界面将显示当前选定的前缀和初始序号。用户可以使用 ▲ 键增加序号,使用 ▼ 键减少序号(范围 `01` 到 `99`达到边界自动截断选中后按下“确认键”或任意“SOS 键”进行保存。
> 4. **屏幕常亮与全时射频监测**系统不进行任何低功耗关屏或停机操作。TFT LCD 屏幕始终常亮显示时钟待机画面。主频与 1ms 定时中断全时运转,使 433MHz 射频解调模块保持 100% 实时无线监听接收,并在任何时候(包括待机和菜单状态)按下 SOS 均能实现即刻发送求救包。
## 2. 传感器对码与报警映射矩阵
手环接收到 433MHz 传感器射频数据后,通过匹配预设数据码(高 4 位为类型)和地址码,根据下表驱动多色 LED、马达振动波形并更新 OLED 显示。本系统支持 7 种不同的外部传感器类型,并预留了出厂预设的固定名称前缀。
| 传感器类型 | 数据码标识 | 预设名前缀 | LED 颜色 | 默认防区类型 | 马达振动波形 (周期 5 秒) |
| :--- | :--- | :--- | :--- | :--- | :--- |
| **🚪 门磁/窗磁** | `0x01` | `PORTE` | ● 红色 | ZONE 1 (普通) | 1 次短振 300ms |
| **👤 PIR 人体红外** | `0x02` | `PIR` | ● 橙色 | ZONE 1 (普通) | 2 次短振 300ms (间隔 200ms) |
| **🔥 烟雾传感器** | `0x03` | `FUMEE` | ● 绿色 | ZONE 0 (24h) | 3 次短振 200ms (间隔 150ms) |
| **🆘 紧急按钮** | `0x04` | `URGENCE` | ● 蓝色 | ZONE 0 (24h) | 1 次长振 800ms |
| **💨 气体泄漏** | `0x05` | `GAZ` | ● 紫色 | ZONE 0 (24h) | 4 次短振 200ms (间隔 100ms) |
| **💧 水浸传感器** | `0x06` | `INOND.` | ● 黄色 | ZONE 0 (24h) | 2 次长振 600ms (间隔 300ms) |
| **📳 振动传感器** | `0x07` | `VIBRA.` | ● 青色 | ZONE 1 (普通) | 连续3次 150ms (间隔 100ms) |
| **🚨 本地主动 SOS** | 手环触发 | `SOS` | ● 红色闪烁 | 独立运行 | 持续长振 (振 1000ms, 停 200ms) |
> [!IMPORTANT]
> **手环主动发射 SOS 射频数据格式**
> 当触发“本地主动 SOS”时手环通过内置发射芯片以 **2 秒** 为周期循环发射 EV1527 协议数据:
> * **源地址**:手环的 **20位出厂固定唯一 ID (Factory ID)**。
> * **数据码**:固定为 **`0x08`** (即 `1000`)。
> * **目的**:使 IPC 摄像头能接收解码,将其与配对列表中该手环 of Factory ID 匹对,从而识别求救来源。
> [!IMPORTANT]
> **屏蔽 SOS 被动报警**:为避免两个手环互发 SOS 相互触发造成死循环报警,手环**必须强行过滤**并排除对外界发射的 SOS 求救广播(数据码为 `0x08`,或其它手环发射源)的被动接收。
## 3. ZONE 防区划分
手环支持将配对的传感器划分至不同的防区:
* **ZONE 0 (永久防区)**:烟雾、气体、水浸等危及安全的传感器。不论手环是否处于布防状态,一旦捕获信号即刻报警。
* **ZONE 1~N (可撤布防区)**:门磁、红外等防盗传感器。只有当手环接收到布防广播(系统进入 ARMED时才会报警在撤防DISARMED状态下被动忽略。

View File

@@ -0,0 +1,106 @@
# 架构设计 (Docs/30_architecture/main.md)
本章节阐述手环的硬件拓扑、引脚分配、软件状态机设计、以及 IAP Flash 数据库存储设计。
## 1. 硬件规格与引脚映射
主控芯片采用 **STC32G12K128** 单片机(工作频率 24.0 MHz单周期 8051 内核32位乘除法器
显示端采用 **1.14寸 TFT LCD 屏幕**,物理分辨率为 **135x240 像素**
### 1.1 GPIO 引脚配置表
| 引脚号 | 引脚定义 | 模式配置 | 物理外设与功能说明 |
| :--- | :--- | :--- | :--- |
| **P2.3** | `WS2812_DI` | 双向/SPI MOSI | 连接可编程 RGB 灯珠WS2812B/FCOB-2.7)的 DIN 信号输入 |
| **P2.5** | `MOTOR` | 推挽输出/SCLK | 连接振动马达驱动电路。高电平震动,低电平静止 |
| **P0.1** | `KEY_UP` | 高阻输入+上拉 | 侧边按键 ▲ (上移),按下为低电平 |
| **P0.3** | `KEY_DOWN` | 高阻输入+上拉 | 侧边按键 ▼ (下移),按下为低电平 |
| **P0.4** | `KEY_CONFIRM`| 高阻输入+上拉 | 侧边按键 ■ (确认),按下为低电平 |
| **P0.2** | `KEY_SOS1` | 高阻输入+上拉 | 上部按键 SOS 1按下为低电平 |
| **P0.5** | `KEY_SOS2` | 高阻输入+上拉 | 上部按键 SOS 2按下为低电平 |
| **P1.0** | `SGM_CTRL` | 推挽输出 | 屏幕/驱动供电使能。高电平使能,低电平关闭 |
| **P1.1** | `LCD_RST` | 推挽输出 | LCD 屏幕复位引脚 |
| **P1.3** | `LCD_RS` | 推挽输出 | LCD 屏幕 SPI 数据/命令寄存器选择 (D/C) |
| **P1.4** | `LCD_CS` | 推挽输出 | LCD 屏幕 SPI 片选信号 |
| **P1.5** | `LCD_SCL` | 推挽输出 | LCD 屏幕 SPI 时钟信号 |
| **P1.6** | `LCD_SDA` | 推挽输出 | LCD 屏幕 SPI 数据信号 |
| **P2.3** | `ADC3` | 模拟输入 | 电池电压采样输入端口 |
### 1.2 上部双 SOS 键并联逻辑设计
为了保证两颗 SOS 按键在软件逻辑上等价,软件在按键消抖和长按状态判定时,采用“逻辑并联”设计。定义一个虚拟的 `is_sos_pressed` 状态标志:
```c
// 只要 SOS1 或 SOS2 有任意一个被按下(低电平),即判定为 SOS 按下
bit is_sos_pressed = (KEY_SOS1 == 0) || (KEY_SOS2 == 0);
```
该标志后续被直接送入消抖状态机和长按计时器,生成 `KEY_SOS_SHORT``KEY_SOS_LONG` 事件,从而完全对上层业务层屏蔽双按键硬件细节。
### 1.3 出厂唯一固定 ID (Factory ID) 的设计与获取
手环在主动发射对码广播与主动 SOS 报警时,需要以唯一的出厂 ID (Factory ID) 作源地址以供接收端IPC进行辨识。
为避免每台手环烧写不同固件,程序从 STC32G 内置的全球唯一硬件 ID (UID) 中提取地址码:
* **硬件读取源**STC32G 芯片在出厂时会在程序存储器ROM的末尾扇区存储一个 **7 字节的唯一 ID**,同样在 RAM 空间的特定区(如 `0xF1`~`0xF7`)也会在启动时映射。
* **Factory ID 生成**:系统初始化时,通过读取 UID 寄存器,将其中的 3 个字节提取出来,并进行 `& 0x0FFFFF`20位掩码限制以符合 EV1527 的 20 位地址规范),存入全局变量 `bracelet_factory_id` 中。若读取 UID 失败,则默认使用硬编码预置 ID `0x37A86UL`
### 1.4 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 控制马达电平。
### 1.5 确认配对时的后缀序号调整机制
在对码确认状态 (`STATE_PAIR_CONFIRM`) 下,系统支持用户手动微调所要保存同类防区的数字序号:
* **全局变量定义**`u8 selected_suffix_num` 用以记录当前的数字选择值。
* **初始默认分配**:当信号被捕获跳转到该状态时,系统自动检索当前内存对码表,统计捕获类型已登记的个数 $C$,将 `selected_suffix_num` 初始化为 $C + 1$。
* **按键调整行为**
* 读取到 `KEY_EVENT_UP_CLICK` 时:`if (selected_suffix_num < 99) selected_suffix_num++`
* 读取到 `KEY_EVENT_DOWN_CLICK` 时:`if (selected_suffix_num > 1) selected_suffix_num--`
* 调值完成后置 `Redraw = 1` 以触发 LCD 快速局部刷新。
* **最终确认写入**:当用户按下 CONFIRM 键或 SOS 键时,系统自动取出对应的预设名前缀(如 `PORTE`),并将 `selected_suffix_num` 转换为两位字符串格式(如 `02`),以 `[前缀] [序号]` 格式拼装后(最大 15 字节,如 `PORTE 02`),调用数据库写入函数。
---
## 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 : 按 CONFIRM/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 {
u8 is_used; // 登记标志0x01表示有效其它代表空闲
u8 addr[3]; // 24位射频物理地址 (EV1527)
u8 type; // 传感器类型 (0:门磁, 1:红外, 2:烟感, 3:紧急, 4:气体, 5:水浸, 6:振动)
u8 zone; // 防区类别 (0: 永久防区, 1: 普通可撤防区)
char name_gbk[16]; // 法语自定义防区名称 (以 [前缀] [序号] 自动格式化写入)
} Sensor_Slot;
```
* 一共支持 **16** 个传感器槽位,通过 `Load_Database()` 和 `Save_Database()` 进行统一序列化读写。

92
Docs/40_ui-design/main.md Normal file
View File

@@ -0,0 +1,92 @@
# UI 设计 (Docs/40_ui-design/main.md)
本章节定义手环 135x240 TFT LCD 屏幕的画面布局、各页面坐标位置、中文字体/ASCII 字符对齐基线以及局部刷新逻辑。
## 1. 屏幕物理参数与刷新机制
* **屏幕规格**1.14寸 TFT LCD 屏,物理分辨率 **`135x240`**。
* **竖向屏规范**:屏幕窗口配置为 **`135` 像素宽,`240` 像素高**。页面中的时钟、菜单、报警元素均采用竖屏垂直居中排版。
* **坐标偏移修正**:写屏窗口渲染存在 **4 像素的 X 轴偏移量**,调用 `LCD_SetWindow(x1 + 4, x2 + 4, y1, y2)`
* **局部刷新防抖机制**:清除重绘标志(`Redraw`)的动作必须在具体的刷新绘制块结束时重置,防止因为主循环高速轮询而产生屏幕闪烁。
---
## 2. 页面布局与坐标坐标规范
### 2.1 时钟待机界面 (STATE_NORMAL / STATE_ARMED / STATE_DISARMED)
时钟页面作为常态显示,包括状态栏、数字时钟和日期行。
```
+-----------------------------------------+
| 🔒 ARME 🔋 85% | <- 状态栏 (Y=24~27)
| |
| |
| 10:35 | <- 中央时间 (Y=80~128)
| |
| |
| VEN. 07-10 | <- 底部日期 (Y=180)
+-----------------------------------------+
```
* **状态栏电量显示(极重要)**
* **电池图标** 绘制在 `X=107, Y=27`
* **电量百分比** 绘制在 `X=77, Y=24`
* 两个控件在 `135` 像素宽的屏幕上保持 **6 像素安全间距**77 + 3*8 = 101与 107 之间为 6px彻底防范乱码和字符相撞。
* **安防状态显示**
* **布防状态 (ARMED)**:状态栏左侧显示红色闭锁 `🔒` 图标,显示 `ARME`
* **撤防状态 (DISARMED)**:状态栏左侧显示绿色开锁 `🔓` 图标,显示 `DESARME`
### 2.2 传感器配对选择菜单 (STATE_PAIR_MENU)
* **顶部标题**:显示 `MODE PAIR P-XX`(其中 XX 为当前已配对的传感器数量)。
* **列表滚动框**:采用 3 行滚动列表,可在 **7 种传感器类型**(门磁、红外、烟感、紧急、气体、水浸、振动)中上下循环滚动选中。当前行高亮并显示粗体外框,字号放大(如 `DETEC. PORTE`),非当前行暗淡显示。
* **按键确认行为**:在配对菜单页面,短按侧边 **■ 确认键** 进入所选防区类型的对码等待状态。
### 2.3 报警触发与本地 SOS 界面 (STATE_ALARMING / STATE_SOS_EMITTED)
* **报警提示边框**:全屏闪烁的红色警告虚线/实线边框。
* **中央图标**根据触发的防区传感器类型居中绘制对应的单色位图图标32x32px
* 门磁:`🚪` 开门图标。
* 红外:`👤` 人体辐射波纹图标。
* 烟感:`🔥` 火苗图标。
* 紧急按钮:`🆘` 求救图标。
* 燃气:`💨` 毒气云雾图标。
* 水浸:`💧` 滴水波纹图标。
* 振动:`📳` 震动线框图标。
* **主动 SOS 界面**:大字显示 `SOS EMIS`,居中显示 `🚨` 旋转警灯图标。
* **绑定 IPC 界面 (STATE_BINDING)**
* 顶部标题:`LIAISON IPC` (法语:与 IPC 绑定)。
* 中央:正在发送提示,显示 `ENVOI EN COURS`,配对确认锁图标。
* **手环 Factory ID 显示**:底部居中显示手环自身出厂地址:`ID: 0xXXXXX`,以便用户配对及辨识。
### 2.4 对码确认与序号微调界面 (STATE_PAIR_CONFIRM)
当手环解码到传感器对码信号后,切入此确认页:
```
+-----------------------------------------+
✓ RECU (对码成功) (Y=40)
[ 🚪 ICON ] (Y=80)
PORTE (Y=135)
[ 01 ] (Y=165) <- 序号微调框 (可按 ▲/▼ 修改)
+-----------------------------------------+
```
* **元素定位**
* 顶部显示提示语 `✓ RECU`(法语:已接收),绿字。
* 中央显示捕获传感器 32x32 对应位图图标。
* 下方第一行展示传感器名称前缀(如 `PORTE`)。
* 下方第二行展示序号选框 `[ 01 ]`:其中包含两个外括号,中间的数字即为 `selected_suffix_num`01~99。当用户按 ▲ 或 ▼ 键时,中间数字动态增减并实时局部刷新,外括号保持静止,增强可编辑交互感。
* **确认行为**:短按 CONFIRM 键或 SOS 键保存退出。
---
## 3. 文字居中对齐与字模基线规范
为保证排版美观,[Drivers/lcd_font.h](file:///c:/workfile/105/stc32g12k128/Drivers/lcd_font.h) 内的标准字模点阵需要满足如下基线对齐:
* **水平居中公式**
* 对于标准 8px 宽字符X 轴绘制起点为 `(135 - len * 8) / 2`
* 对于双倍放大 16px 宽时钟字符X 轴绘制起点为 `(135 - len * 16) / 2`
* **百分号(%)符号基线修正**:百分号字符在 `lcd_font.h` 数组的索引 `0x25` 中,点阵数据需**向上平移 2 像素**,以和 `0~9` 数字的基线保持严格水平对齐,避免百分号整体下沉。

View File

@@ -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 + AndroidGoogle / Apple 推送后台
远程管理端
1.2 信号流向(需求层面描述)
433MHz 传感器触发 → 发射固定编码信号7 位二进制码)
IPC 和手环各自独立接收该 433MHz 信号
IPC 解码后:屏幕显示报警 ICON、闪光报警、通过 WiFi 推送手机通知
手环解码后OLED 显示报警 ICON + 自定义名称、LED 亮对应颜色、马达振动
📌 注意IPC 与手环均需独立接收 433MHz 信号,互不依赖;
二、IPC 硬件需求
2.1 核心硬件规格
需求项
规格要求
优先级
摄像头分辨率
720P1280×720
必须
夜视能力
室内黑暗状态下 10 米可看清实物
必须
TFT LCD 屏
2寸分辨率不低于 240×320QVGA
必须
433MHz 模块
内置 433MHz 解码接收模块(被动接收,无需主动发射)
必须
配对孔
机身预留配对针孔,按压约 3 秒触发配对模式
必须
报警闪灯
接收到 433MHz 报警信号后开启闪光报警
必须
布防/撤防按键
实体按键,快速切换布防 / 撤防状态(需要LED灯指示状态
必须
WiFi 模块
2.4GHz WiFi 联网,用于 APP 推送和云端同步
必须
LAN 接口
有线以太网接口RJ45
必须
Flash 存储
存储传感器配对信息(建议支持 ≥16 个传感器)
必须
2.2 摄像功能
分辨率720P1280×720摄像头
夜视室内黑暗场景10 米距离可清晰辨认目标物
建议采用 IR红外夜视方案具体 LED 数量和照射角度由硬件团队评估
2.3 TFT LCD 显示
尺寸2寸 TFT LCD
分辨率:不低于 240×320QVGA
显示内容涵盖:已配对传感器列表、配对流程指引、报警画面、布防/撤防状态
ICON 规格系统预设传感器图标IPC 端建议 64×64px
2.4 联网
WiFi2.4GHz 无线联网,用于推送报警通知至手机 APP
LANRJ45 有线以太网接口
推送通道:需接入 Google FCMAndroid和 Apple APNsiOS后台通讯服务
三、手表 / 手环硬件与交互需求
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 秒
防误触设计:必须同时按两键才能进入,避免单键误触
屏幕显示:全屏天线/配对 ICON32×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 FCMAndroid/ Apple APNsiOS报警通知通知标题显示传感器自定义名称
布防/撤防控制:远程切换 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)· 本文件仅描述产品需求,不包含技术实施方案 ——

View File

@@ -0,0 +1,47 @@
# 模块拆分 - 按键扫描与事件判定 (Docs/50_module-breakdown/mod-key.md)
本模块描述按键状态消抖、长按计时判定以及组合按键识别。支持 5 个物理按键到手环事件的映射。
## 1. 模块职责说明
* **职责范围**
1. 在 1ms 定时器中断中建立 10ms 扫描计时周期。
2. 对 5 个物理按键进行引脚电平扫描:
* 侧边:`KEY_UP` SW1 (▲), `KEY_DOWN` SW2 (▼), `KEY_CONFIRM` (■)
* 上部:`KEY_SOS1` (SOS 1), `KEY_SOS2` (SOS 2)
3. 实现消抖检测(连续 20ms 检测到电平稳定才认定按键动作)。
4. 对上部双 SOS 按键进行逻辑并联处理,保证按下任意一个均能触发求救。
5. 对按住不放的行为进行计时累加,判定长按(如长按 SOS 达 5 秒触发对码;长按 ▲+▼ 达 3 秒触发配对模式)。
6. 将按键判定结果转换为 `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_CONFIRM_CLICK, // ■ 确认键短按
KEY_SOS_SHORT, // SOS键短按 (SOS1 或 SOS2)
KEY_SOS_LONG, // SOS键长按5秒 (SOS1 或 SOS2)
KEY_COMB_LONG // ▲ + ▼ 组合长按事件
} KeyEvent;
```
## 3. 接口与函数说明
* `void Key_Scan_Process(void)`
在 Timer 1 ISR 中每 10ms 调度一次。
* 读取 `KEY_UP`, `KEY_DOWN`, `KEY_CONFIRM`, `KEY_SOS1`, `KEY_SOS2` 的 GPIO 逻辑电平。
* 计算并联 SOS 状态:`is_sos_pressed = (KEY_SOS1 == 0) || (KEY_SOS2 == 0)`
* 如果发现按键电平状态改变,进行计数消抖。
* 如果按住不放,累加计时计数器(`key_up_hold`, `key_down_hold`, `key_confirm_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_CONFIRM_CLICK`
* 事件解析完毕后存入 `key_event_buf`
<!-- Checked and verified with always-on screen and sleep removal changes -->

View File

@@ -0,0 +1,36 @@
# 模块拆分 - LCD 显示与字模驱动 (Docs/50_module-breakdown/mod-lcd.md)
本模块管理 1.14寸 TFT LCD 屏幕引脚、电平控制、SPI 传输和界面图文渲染(分辨率为 135x240 像素)。
## 1. 模块职责说明
* **职责范围**
1. 提供底层的 SPI 单向命令/数据通道。
2. 控制 SGM_CTRL 供电管脚。
3. 支持屏幕窗口配置,实现底层清屏与局部像素写入。
4. 读取 `lcd_font.h` 库,在指定坐标绘制 8x16 字符与自定义大小位图。
5. 实现各业务状态屏幕135x240 分辨率)画面绘制与文字对齐。
## 2. 底层显示接口
* `void LCD_Init(void)`:初始化 LCD 引脚,置 `SGM_CTRL = 1` 使能供电,并发送屏幕控制器的初始化 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_ShowStringCentered(u16 y, char *str, u16 fc, u16 bc)`:在 135 像素宽屏幕上水平居中绘制 8 像素宽的字符串,水平起始坐标算法为 `(135 - len * 8) / 2`
* `void LCD_ShowString16x32Centered(u16 y, char *str, u16 fc, u16 bc)`:在 135 像素宽屏幕上水平居中绘制双倍大小 (16x32px) 的字符串,水平起始坐标算法为 `(135 - len * 16) / 2`
* `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)`渲染时钟待机页面135 宽布局),并根据布撤防状态在状态栏绘制相应锁状态图标。
* `void UI_ShowAlarmPage(char *sensor_name, u8 zone)`:渲染防区告警页面,显示对应的传感器位图和法语名称。
* `void UI_ShowPairMenuPage(u8 selected_index)`:渲染对码选择菜单。
* `void UI_ShowPairConfirmPage(u8 type, u8 selected_suffix)`:渲染对码确认页,显示捕获的类型图标、名字前缀及带选框的 `[ selected_suffix ]` 数字序号(支持按键调整)。
* `void UI_ShowBindingPage(void)`:渲染与 IPC 绑定对码界面,并在屏幕底部居中显示手环自身的唯一 `Factory ID`
<!-- Checked and verified with always-on screen and sleep removal changes -->

View File

@@ -0,0 +1,19 @@
# 模块拆分 - 电源与供电管理 (Docs/50_module-breakdown/mod-power.md)
本模块描述手环的电源供电状态。根据系统设计手环不启用任何关屏低功耗或停机Power-Down模式以保证屏幕时钟常显与全时无线监听。
## 1. 模块职责说明
* **职责范围**
1. 系统初始化时使能 LCD 屏幕与背光电源。
2. 维持 `SGM_CTRL` 引脚输出高电平1确保屏幕不进入断电休眠。
3. 不进行时钟挂起操作,使 CPU 保持在 active 状态以进行实时的 433MHz 射频信号捕获。
## 2. 接口与函数设计
* `void Power_Init(void)`
1. 配置 `SGM_CTRL` 引脚为推挽输出模式。
2. 设置 `SGM_CTRL = 1` 开启 LCD 屏幕与背光供电。
* `void Enter_Deep_Sleep(void)`:已作废。手环永不进入此函数,保持主循环及射频实时解调。
<!-- Checked and verified with 7 sensor types and manual suffix selection changes -->

View File

@@ -0,0 +1,30 @@
# 模块拆分 - 自适应 RF 解码与发射 (Docs/50_module-breakdown/mod-rf.md)
本模块管理射频芯片 GPIO 初始化、433MHz EV1527 解调接收(宽限时序比值算法)与射频打包主动发射。已确认此模块与新的 1.14寸屏幕/5按键物理变更兼容。
## 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依次驱动发射引脚电平持续多帧以确保接收方捕获。
* 该函数被手环用作向 IPC 摄像头主动对码绑定(`data_code = 0x01`)。
* 该函数也用于手环主动发射 SOS 紧急呼救包(`data_code = 0x08`),此时传入手环自身的出厂固定 ID `bracelet_factory_id`
<!-- Checked and verified with always-on screen and sleep removal changes -->

View File

@@ -0,0 +1,62 @@
# 模块拆分 - 系统核心控制与存储 (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 读写底层函数,对防区数据库进行存取与读取。
5. 读取并管理手环出厂固定唯一的 `bracelet_factory_id`,作为主动射频发送的数据源地址。
## 2. 数据结构定义
### 2.1 传感器插槽结构体 (Sensor_Slot)
```c
typedef struct {
u8 is_used; // 0x01:已使用,其它:空闲
u8 addr[3]; // 24位 EV1527 对码物理地址
u8 type; // 传感器类型 (0~6)
u8 zone; // 防区类别 (0: 永久防区, 1: 可撤布防区)
char name_gbk[16]; // 法语自定义名字(前缀+两位数序号,如 PORTE 02
} 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_ALARM,
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, u8 suffix_idx)`:向 `sensor_list` 添加传感器信息,将其名字拼装为 `[前缀] [suffix_idx]`,并自动调用 `Save_Database()`
* `bit Check_Sensor_ID(u32 addr, u8 *out_slot_index)`:在数据库中检索该地址码,若存在则返回它的槽位索引。
### 3.3 系统初始化与全局 ID 获取
* `void System_Init(void)`
1. 初始化单片机系统寄存器WTST, EAXFR 等)。
2. 读取 STC32G 硬件全球唯一 ID (UID)。
3. 提取 UID 中的后 3 字节,并通过 `& 0x0FFFFF` 裁剪为 20 位 EV1527 源地址,赋值给全局变量 `bracelet_factory_id`。如果读取失败,则默认赋予 `0x37A86UL`
4. 载入数据库并配置 Timer1 和外设 GPIO 状态。
<!-- Checked and verified with always-on screen and sleep removal changes -->

92
Docs/60_coding/mod-key.md Normal file
View File

@@ -0,0 +1,92 @@
# 编码实现 - 按键扫描与事件判定 (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_confirm_state = 0xFF;
static u8 key_sos1_state = 0xFF;
static u8 key_sos2_state = 0xFF;
u8 key_sos_state;
key_up_state = (key_up_state << 1) | KEY_UP;
key_down_state = (key_down_state << 1) | KEY_DOWN;
key_confirm_state = (key_confirm_state << 1) | KEY_CONFIRM;
key_sos1_state = (key_sos1_state << 1) | KEY_SOS1;
key_sos2_state = (key_sos2_state << 1) | KEY_SOS2;
// 双上部 SOS 键的逻辑并联(低电平有效,按位与运算相当于逻辑或)
key_sos_state = key_sos1_state & key_sos2_state;
// 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;
// 单个按键:▲ 键 (UP)
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;
}
// 单个按键:■ 确认键 (CONFIRM)
if ((key_confirm_state & 0x07) == 0) {
key_confirm_hold++;
} else {
if (key_confirm_hold > 2 && key_confirm_hold < 200) {
key_event_buf = KEY_CONFIRM_CLICK;
}
key_confirm_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;
}
// ▼ 键 (DOWN) 处理同上...
}
}
```
<!-- Checked and verified with always-on screen and sleep removal changes -->
### 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 警报发射状态等
// ...
}
```
通过该缓冲区实现了按键中断采集与主循环处理的解耦。

102
Docs/60_coding/mod-lcd.md Normal file
View File

@@ -0,0 +1,102 @@
# 编码实现 - 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` 的时钟重绘函数中,通过以下坐标在 **135 像素宽** 屏幕上绘制电量:
```c
// 绘制百分比字符
LCD_ShowString(77, 24, "85%", COLOR_GRAY, COLOR_BLACK);
// 绘制电池位图
LCD_ShowImage(107, 27, 20, 10, bmp_battery_20x10, COLOR_GRAY, COLOR_BLACK);
```
两者 Y 轴保持 24 与 27 对齐X 轴坐标根据 135 宽屏幕右偏 15 像素(原为 62 和 92安全边距保持在 6 像素77 + 3*8 = 101与 107 之间为 6px防止重合。
### 2.3 字模百分号偏置
`lcd_font.h` 的 ASCII 字符数组中,百分号 `%`(十进制 37十六进制 0x25的字模通过在字模绘制时向上平移 2 像素对齐普通数字的下基准线。
### 2.4 水平居中渲染实现
`lcd.c` 中,字符串居中函数需修改宽度限制参数为 `135` 像素:
```c
void LCD_ShowStringCentered(u16 y, char *str, u16 color, u16 bg_color) {
u16 len = 0;
char *p = str;
while(*p++) len++;
if(len * 8 >= 135)
LCD_ShowString(0, y, str, color, bg_color);
else
LCD_ShowString((135 - len * 8) / 2, y, str, color, bg_color);
}
```
### 2.5 绑定界面 ID 渲染实现
`main.c` 中通过 `FormatHex` 接口将 `bracelet_factory_id` 转换为十六进制字符串,并显示在绑定界面的底部:
```c
void UI_ShowBindingPage(void) {
char id_str[15];
// 渲染背景与图标
// ...
// 绘制手环自身的出厂 ID
FormatHex(bracelet_factory_id, id_str);
LCD_ShowStringCentered(180, id_str, COLOR_GRAY, COLOR_BLACK);
}
### 2.6 对码确认与序号微调页渲染
`main.c` 中通过以下方式渲染可供按键微调的数字后缀界面:
```c
void UI_ShowPairConfirmPage(u8 type, u8 selected_suffix) {
char num_buf[10];
LCD_Clear(COLOR_BLACK);
LCD_ShowStringCentered(40, "v RECU", COLOR_GREEN, COLOR_BLACK);
// 根据类型渲染 32x32 图标
// ...
// 居中显示预设名称前缀
LCD_ShowStringCentered(135, sensor_prefixes[type], COLOR_WHITE, COLOR_BLACK);
// 拼接成带选框的 "[ 02 ]" 格式字符串并高亮居中渲染
num_buf[0] = '[';
num_buf[1] = ' ';
num_buf[2] = ' ';
num_buf[3] = '0' + (selected_suffix / 10);
num_buf[4] = '0' + (selected_suffix % 10);
num_buf[5] = ' ';
num_buf[6] = ' ';
num_buf[7] = ']';
num_buf[8] = '\0';
LCD_ShowStringCentered(165, num_buf, COLOR_CYAN, COLOR_BLACK);
}
<!-- Checked and verified with always-on screen and sleep removal changes -->
```
```

View File

@@ -0,0 +1,30 @@
# 编码实现 - 电源管理与常亮控制 (Docs/60_coding/mod-power.md)
本文件对应取消低功耗休眠、保持屏幕常亮及系统全时监测的具体编码实现。
## 1. 对应代码源文件
* [App/main.c](file:///c:/workfile/105/stc32g12k128/App/main.c) (初始化供电配置)
## 2. 关键代码片段与逻辑实现
### 2.1 供电引脚常使能
在系统初始化阶段,配置 `SGM_CTRL`(控制屏和背光供电的引脚)为推挽输出模式,并输出高电平:
```c
void Power_Init(void) {
// P1.0 (SGM_CTRL) 配置为推挽输出
P1M1 &= ~0x01;
P1M0 |= 0x01;
// 拉高引脚,开启 LCD 背光与电平供电
SGM_CTRL = 1;
}
```
### 2.2 移除睡眠超时与休眠指令
在主循环事件分发中,不再轮询待机时间累加器,也不再调用休眠流程:
* **移除** `Enter_Deep_Sleep` 函数。
* **移除** 端口 P0 的引脚中断设置(`P0INTE` 等)及 `PD` 停机指令。
* 手环时刻保持主频运转Timer 1 中断不断累加,`EV1527_Decode` 全时监听射频,随时处理防区报警和 SOS 求救触发。
<!-- Checked and verified with 7 sensor types and manual suffix selection changes -->

40
Docs/60_coding/mod-rf.md Normal file
View File

@@ -0,0 +1,40 @@
# 编码实现 - 自适应 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 主动广播发射 (EV1527_Transmit)
手环通过 LT4455 发送对码广播或主动 SOS 警报,在 `rf.c` 中实现:
```c
void EV1527_Transmit(u32 addr, u8 data_code) {
// 产生多帧 EV1527 时序
// 包含同步头 (高 1T + 低 31T) 与 24 位数据码
// ...
}
```
通过控制发射引脚的电平跳转产生标准的 433MHz EV1527 载波信号。
* **绑定对码**:当用户触发对码绑定时,传入 `addr = bracelet_factory_id``data_code = 0x01`。
* **主动 SOS 警报**:当触发主动 SOS 时,主状态机在 `STATE_SOS_EMITTED` 下定期以时间片的形式调用此函数,传入 `addr = bracelet_factory_id``data_code = 0x08`(即二进制 `1000`)。
<!-- Checked and verified with always-on screen and sleep removal changes -->

79
Docs/60_coding/mod-sys.md Normal file
View File

@@ -0,0 +1,79 @@
# 编码实现 - 系统核心控制与存储 (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 字节)。
### 2.3 获取出厂唯一 ID (Get_Bracelet_Factory_ID)
从 STC32G 的内置 IDATA RAM 区域 `0xF1~0xF7` 处读取 7 字节唯一硬件 ID并将其转换为符合 EV1527 的 20 位地址码:
```c
u32 Get_Bracelet_Factory_ID(void) {
unsigned char idata *p_uid = (unsigned char idata *)0xF1;
u32 uid = 0;
// 组合后 3 字节并进行 20 位掩码过滤
uid = ((u32)p_uid[4] << 16) | ((u32)p_uid[5] << 8) | p_uid[6];
uid &= 0x0FFFFF; // 20-bit address range limit for EV1527
if (uid == 0) {
uid = CLONED_ADDR; // 防错备份
}
return uid;
}
```
### 2.4 传感器自定义名称拼接与写入 (Add_Sensor)
`Add_Sensor(u32 addr, u8 type, u8 suffix_idx)` 中,系统预设 7 类传感器名前缀:
```c
char *code sensor_prefixes[7] = {
"PORTE", "PIR", "FUMEE", "URGENCE", "GAZ", "INOND.", "VIBRA."
};
```
在写入时,手动拼接前缀和两位数字 `suffix_idx` 后缀:
```c
void Add_Sensor(u32 addr, u8 type, u8 suffix_idx) {
u8 slot = ...; // 检索空槽或匹配历史ID
char *prefix = sensor_prefixes[type];
u8 len = 0;
// 拷贝前缀
while (prefix[len] != '\0' && len < 11) {
sensor_list[slot].name_gbk[len] = prefix[len];
len++;
}
sensor_list[slot].name_gbk[len++] = ' ';
sensor_list[slot].name_gbk[len++] = '0' + (suffix_idx / 10);
sensor_list[slot].name_gbk[len++] = '0' + (suffix_idx % 10);
sensor_list[slot].name_gbk[len] = '\0';
// 其余变量赋值并保存数据库
// ...
Save_Database();
}
<!-- Checked and verified with always-on screen and sleep removal changes -->
```

62
Docs/70_testing/main.md Normal file
View File

@@ -0,0 +1,62 @@
# 测试设计与验证方法 (Docs/70_testing/main.md)
本章节整理手环系统的仿真联调指令、IO 状态变化监控规范以及人工实机验证用例。
## 1. 串口仿真指令注入规范
手环程序在 `main.c` 中通过 UART1 实现了仿真指令解析,支持在开发及免焊测试阶段通过电脑串口助手直接向手环发送字符指令,仿真各种物理交互:
| 串口发送字符 | 对应仿真的物理事件与说明 | 预期系统响应 |
| :--- | :--- | :--- |
| **`u` / `U`** | 仿真短按 / 长按 **侧边 ▲ 键** | 时钟微调 / 菜单滚动 / **对码确认页:自增序号 (01->99)** |
| **`d` / `D`** | 仿真短按 / 长按 **侧边 ▼ 键** | 时钟微调 / 菜单滚动 / 长按取消报警 / **对码确认页:自减序号 (99->01)** |
| **`e` / `E`** | 仿真短按 / 长按 **侧边 ■ 确认键** | 选中并进入等待配对 / 短按取消报警 / **对码确认页:确认保存配置** |
| **`s` / `S`** | 仿真短按 / 长按 **上部 SOS 1 键** | 触发本地主动 SOS 警报 / 触发对码保存 / **对码确认页:确认保存配置** |
| **`x` / `X`** | 仿真短按 / 长按 **上部 SOS 2 键** | 同上,验证双 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表示灭WS2812B 幻彩 LED 控制状态)
* **马达状态日志格式**`[STATE] MOTOR -> 1` (1表示开启振动0表示关闭振动)
---
## 3. 人工实机验证用例
### 3.1 时钟待机与电量对齐
* **测试方法**:正常上电启动,查看待机界面。
* **预期结果**:时钟、日期显示正常。在 **135 像素宽** 屏幕上,电量 `85%` 绘制在 `X=77, Y=24`,电池图标绘制在 `X=107, Y=27`,对齐美观且有 6 像素的安全间距。
### 3.2 传感器对码与保存
* **测试方法**:同时长按 ▲+▼ 3秒进入配对用上下键选中 `DETEC. PIR`,按下侧边 **■ 确认键** 开启等待,随后物理触发 PIR 传感器。进入对码确认页后,按 **▲ 键** 将中间括号内数字调节至 `02`(即显示 `PIR [ 02 ]`),最后按下 **■ 确认键** (或 **SOS 1**)确认保存。
* **预期结果**
1. 进入配对时LED 亮白色闪烁3Hz马达短振 1 次确认。
2. 解码匹配成功后OLED 屏显示 `✓已保存` 以及 `PIR 02`LED 绿色常亮 2 秒,马达强振 1 次400ms随后自动退回配对菜单。
### 3.3 常亮长显与射频全时监听验证
* **测试方法**:在常态待机下静置手环超过 10 秒,并在此期间使用发射装置发送已配对的传感器射频报警包,或随时短按上部的 SOS 键。
* **预期结果**
1. 手环在静置超过 10 秒后,屏幕背光不关闭,继续常亮展示时钟待机画面。
2. 无线接收解调模块 100% 保持在监听状态,即使在长时间静置后,只要触发匹配的射频报警,手环能立即接收响应,进入报警闪烁及马达震动状态。
3. 随时短按 SOS 键能立刻跳转到主动发送界面并开启 EV1527 信号广播。
### 3.4 手环主动发射 SOS 与 Factory ID 验证
* **测试方法**
1. 正常开机,在串口输出中查看获取到的 20 位 `Factory ID`(或在绑定界面核对 ID
2. 短按上部任意一个 SOS 键触发紧急求救。
3. 使用 433MHz 接收调试工具或逻辑分析仪抓取手环发射引脚或射频信号。
* **预期结果**
1. 手环立刻切入 `SOS EMIS` 界面,红灯闪烁,马达周期性震动。
2. 接收工具抓取到以手环 `Factory ID` 为源地址的 24 位 EV1527 信号,其 4 位数据码为 `0x08`
3. 该信号以 2 秒为周期循环向外发射,直到按下“确认键”或其它任意键退出报警状态。

51
开发规范.md Normal file
View File

@@ -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/` 下已完成、进行中、待补的产出物。新项目初始化时这一节为空,随开发推进逐步填写。