diff --git a/app/(tabs)/projects.tsx b/app/(tabs)/projects.tsx index cfaf4ab..404d72e 100644 --- a/app/(tabs)/projects.tsx +++ b/app/(tabs)/projects.tsx @@ -275,7 +275,7 @@ export default function ProjectsScreen() { const handleDeleteProject = (project: ProjectInfo) => { Alert.alert( '删除工程', - `确定删除工程「${project.name}」?\n测线数据将保留,但与工程的关联会断开。`, + `确定删除工程「${project.name}」?\n该工程下的所有测线和测点数据将被永久删除,不可恢复。`, [ { text: '取消', style: 'cancel' }, { diff --git a/docs/user-manual.md b/docs/user-manual.md new file mode 100644 index 0000000..b9c3070 --- /dev/null +++ b/docs/user-manual.md @@ -0,0 +1,485 @@ +# TEM Receiver 用户手册 + +> 版本:1.0.0 +> 最后更新:2026-06-20 + +--- + +## 目录 + +1. [简介](#一简介) +2. [安装](#二安装) +3. [首次使用](#三首次使用) +4. [连接设备](#四连接设备) +5. [参数配置](#五参数配置) +6. [数据采集](#六数据采集) +7. [波形查看](#七波形查看) +8. [测点管理](#八测点管理) +9. [剖面分析](#九剖面分析) +10. [工程管理](#十工程管理) +11. [数据导出与分享](#十一数据导出与分享) +12. [数据导入](#十二数据导入) +13. [主题切换](#十三主题切换) +14. [存储管理](#十四存储管理) +15. [常见问题](#十五常见问题) + +--- + +## 一、简介 + +TEM Receiver 是六通道瞬变电磁(TEM)接收机的手机端控制软件。通过 WiFi 连接接收机设备,可在手机上完成参数配置、数据采集、波形查看、剖面分析和数据管理全部操作。 + +### 主要功能 + +- 通过 WiFi 无线连接 TEM 接收机 +- 配置采集参数(通道数、频率、增益等) +- 单次采集和连续采集 +- 实时波形显示(支持全屏、缩放、拖拽) +- 工程-测线-测点三级数据管理 +- 数据导出(CSV 和 .tem 格式) +- 数据分享(微信、蓝牙、文件管理器等) +- 自动深色/浅色主题 +- 设备状态实时监控(电流、电池、温度、GPS、SD 卡) + +--- + +## 二、安装 + +### Android + +1. 将 `app-release.apk` 文件传输到手机 +2. 在手机上打开 APK 文件 +3. 如提示"未知来源",在设置中允许安装 +4. 安装完成后在桌面找到「TEM Receiver」图标 + +### iOS + +通过 TestFlight 安装(需开发者提供邀请链接)。 + +--- + +## 三、首次使用 + +首次打开 App 后: + +1. 所有页面会显示「请先创建工程」提示 +2. 点击「前往工程页」按钮 +3. 在工程页点击右上角「+ 新建」 +4. 输入工程名称(如:项目名称 + 日期) +5. 确认后 App 自动创建工程和第一条测线 +6. 返回波形页即可开始使用 + +> **重要**:所有采集数据必须归属于一个工程。没有工程时无法采集数据。 + +--- + +## 四、连接设备 + +### 4.1 连接 WiFi + +1. 打开 TEM 接收机电源 +2. 手机进入系统设置 → WiFi +3. 连接接收机的 WiFi 热点(通常名称包含设备编号) +4. 默认 IP 地址:`192.168.4.1`,端口:`4321` + +### 4.2 App 内连接 + +1. 点击顶部状态栏的「TCP 未连接」 +2. 弹出连接窗口,确认 IP 和端口 +3. 点击「连接」 +4. 连接成功后状态变为绿色「TCP 已连接」 + +### 4.3 连接状态说明 + +| 状态 | 颜色 | 说明 | +|------|------|------| +| 已连接 | 绿色 | 正常工作 | +| 连接中 | 黄色 | 正在连接 | +| 重连 | 黄色 | 断线后自动重连 | +| 未连接 | 灰色 | 未连接设备 | + +> App 会自动保持心跳(每 8 秒),连接断开时自动重连。 + +--- + +## 五、参数配置 + +### 5.1 打开配置面板 + +在波形页点击右下角的「⚙」设置按钮,弹出参数配置面板。 + +### 5.2 可配置参数 + +| 参数 | 说明 | 默认值 | +|------|------|--------| +| 通道数 | 采集通道数量,1-6 | 3 | +| 发射频率 | 发射机工作频率 | 16 Hz | +| 采样频率 | ADC 采样率 | 250 kHz | +| 采样深度 | 每通道采样点数 | 2000 | +| 叠加次数 | 信号叠加次数,提升信噪比 | 32 | +| 增益 | 放大倍数 | 1× | +| 补偿电阻 | 补偿电阻值 | 12 | +| 补偿去使能延时 | 延时参数 | 60 | +| 源模式 | 单源/双源/三源 | 单源 A | +| 反向叠加 | 123/456 通道反向叠加 | 关闭 | +| 文件前缀 | SD 卡存储文件名前缀 | 当天日期 | + +### 5.3 下发配置 + +1. 修改参数后,面板底部按钮变为蓝色「下发配置」 +2. 点击下发,等待设备回复 +3. 弹出「配置成功」表示参数已生效 +4. 弹出「配置超时」表示设备未响应,请检查连接 + +> **注意**:采集过程中不可下发配置,需先停止采集。 + +--- + +## 六、数据采集 + +### 6.1 单次采集 + +1. 确保已连接设备且已下发配置 +2. 点击波形页底部的「单次」按钮 +3. 设备完成一次采集后自动停止 +4. 波形图显示采集结果 + +### 6.2 连续采集 + +1. 点击「连续采集」按钮 +2. 设备按设定的发射频率持续采集 +3. 波形图实时更新 +4. 点击「停止」按钮结束采集 + +### 6.3 采集状态 + +| 状态 | 按钮显示 | 说明 | +|------|---------|------| +| 空闲 | 连续采集 / 单次 | 可以开始采集 | +| 运行中 | 停止 | 连续采集中 | +| 单次采集中 | 单次采集中… | 等待数据返回 | + +### 6.4 状态信息 + +波形图下方实时显示: + +**第一行**:源模式、发射电流(A)、电池电压(V)、温度(°C) + +**第二行**:Roll、Pitch、Yaw 姿态角、GPS 状态、SD 卡状态 + +GPS 状态含义: + +| 值 | 说明 | +|----|------| +| 未定位 | GPS 未锁定 | +| 非差分 | 普通定位 | +| 差分 | 差分定位 | +| 固定解 | RTK 固定解(最高精度) | +| 浮点解 | RTK 浮点解 | + +SD 卡状态: + +| 值 | 说明 | +|----|------| +| 正常 | SD 卡工作正常 | +| 未插卡 | 未检测到 SD 卡 | +| 异常 | SD 卡读写异常 | + +--- + +## 七、波形查看 + +### 7.1 通道选择 + +波形图上方有 CH1-CH6 通道按钮,点击可显示/隐藏对应通道。各通道颜色: + +- CH1 红色 / CH2 绿色 / CH3 蓝色 +- CH4 橙色 / CH5 紫色 / CH6 青色 + +### 7.2 显示模式 + +点击「LIN/LOG」按钮切换: + +- **LIN**(线性)— 默认模式,适合查看信号波形 +- **LOG**(对数)— 适合查看大动态范围信号 + +### 7.3 全屏查看 + +1. 点击通道栏右侧的「⛶」全屏按钮 +2. 进入全屏模式,支持横屏显示 +3. 手势操作: + - **单指拖拽** — 平移波形 + - **双指捏合** — 缩放波形 + - **双击** — 恢复原始视图 +4. 右上角缩放倍率显示(如 ×2.5) +5. 点击「✕」或按手机返回键退出全屏 + +--- + +## 八、测点管理 + +切换到底部「测点」Tab 页。 + +### 8.1 测线选择 + +页面顶部显示当前测线信息,点击可切换到当前工程下的其他测线。 + +> **注意**:切换测线会同时切换活跃采集目标,后续采集的数据将存入所选测线。 + +### 8.2 测点列表 + +以表格形式显示当前测线的所有测点: + +| 列 | 说明 | +|----|------| +| # | 测点编号 | +| 时间 | 采集时间(UTC) | +| GPS | 定位状态(✓/✗) | +| 经度 | GPS 经度 | +| 纬度 | GPS 纬度 | +| 叠加 | 叠加次数 | +| × | 删除按钮 | + +### 8.3 查看波形 + +点击任意测点行,弹出波形详情窗口,显示该测点的完整波形数据。 + +### 8.4 删除测点 + +点击测点行右侧的「×」按钮,确认后删除该测点及其波形数据。 + +### 8.5 导出 CSV + +点击「导出 CSV」按钮,导出当前测线的所有测点数据为 CSV 文件。 + +--- + +## 九、剖面分析 + +切换到底部「剖面」Tab 页。 + +### 9.1 测线选择 + +与测点页相同,顶部可切换测线查看不同测线的剖面。 + +### 9.2 剖面显示 + +自动加载所选测线的全部测点波形数据,生成剖面图。 + +### 9.3 门窗配置 + +点击设置按钮可配置门窗参数: + +| 参数 | 说明 | +|------|------| +| 起始时间 | 门窗起始时间(μs) | +| 结束时间 | 门窗结束时间(μs) | +| 门窗数量 | 门窗个数 | +| 间距模式 | 对数间距 / 线性间距 | + +--- + +## 十、工程管理 + +切换到底部「工程」Tab 页。 + +### 10.1 数据组织结构 + +``` +工程(Project) + └── 测线(Session) + └── 测点(Frame) + ├── 元数据(GPS、电流、温度…) + └── 波形数据(.bin 文件) +``` + +### 10.2 新建工程 + +1. 点击右上角「+ 新建」 +2. 输入工程名称 +3. 确认后自动创建工程和第一条测线 + +### 10.3 打开工程 + +点击工程卡片上的「打开」按钮,可选择: + +- **继续最近测线** — 恢复上次采集的测线 +- **新建测线** — 在该工程下创建新测线 + +### 10.4 管理测线 + +展开工程卡片可看到该工程下的所有测线: + +| 操作 | 说明 | +|------|------| +| 激活 | 切换到该测线(后续采集数据存入此测线) | +| 导出 | 导出该测线的 CSV 数据 | +| 删除 | 删除该测线及其所有测点数据 | +| + 新建测线 | 在该工程下新建一条测线 | + +> 当前活跃测线以蓝色高亮显示,且不可删除(需先切换到其他测线)。 + +### 10.5 其他操作 + +| 操作 | 说明 | +|------|------| +| .tem | 导出整个工程为 .tem 文件(包含所有测线和波形数据) | +| 改名 | 重命名工程 | +| 删除 | 删除工程及其所有数据(不可恢复) | + +--- + +## 十一、数据导出与分享 + +### 11.1 导出方式 + +导出时弹出选择: + +- **分享** — 打开系统分享面板,可发送到微信、蓝牙、邮件等 +- **保存到文件** — 选择手机上的目录保存文件(如下载目录、SD 卡) + +### 11.2 导出格式 + +| 格式 | 内容 | 适用场景 | +|------|------|---------| +| CSV | 测点元数据(GPS、电流、温度等) | Excel 查看、数据分析 | +| .tem | 完整工程数据(元数据 + 全部波形) | 数据备份、跨设备传输、导入恢复 | + +### 11.3 CSV 文件内容 + +``` +FrameID, UTC, Longitude, Latitude, Altitude, GPS, SD, +AmpRatio, Current, Temp, Battery, Roll, Pitch, Yaw, +CH1_peak_uV, CH2_peak_uV, CH3_peak_uV, ... +``` + +### 11.4 .tem 文件内容 + +ZIP 压缩包,包含: + +``` +project.json ← 工程信息 + 设备配置 +frames/ + 测线ID_1/ + 000000.bin ← 测点0 完整波形 + 000001.bin ← 测点1 完整波形 + ... + 测线ID_2/ + ... +``` + +--- + +## 十二、数据导入 + +### 12.1 导入 .tem 文件 + +1. 在工程页点击「导入 .tem」 +2. 选择 .tem 文件(从文件管理器、微信接收的文件等) +3. 等待导入完成 +4. 导入后自动创建新工程,保留所有测线和波形数据 + +### 12.2 注意事项 + +- 导入的工程会创建新的工程 ID,不会覆盖已有工程 +- 导入过程可能需要几秒到几分钟,取决于数据量 +- 导入期间请勿关闭 App + +--- + +## 十三、主题切换 + +App 自动跟随手机系统的深色/浅色模式设置。 + +- **Android**:设置 → 显示 → 深色模式 +- **iOS**:设置 → 显示与亮度 → 外观 + +无需在 App 内手动切换。 + +--- + +## 十四、存储管理 + +### 14.1 存储占用 + +连续采集时存储占用参考(3 通道 2000 点): + +| 时间 | 16Hz 连续采集 | +|------|-------------| +| 1 分钟 | ~46 MB | +| 10 分钟 | ~460 MB | +| 1 小时 | ~2.7 GB | + +### 14.2 空间不足处理 + +当手机剩余存储不足 100MB 时,App 会自动停止采集并弹窗提醒。 + +### 14.3 释放空间 + +1. 先导出需要保留的工程数据(.tem 文件) +2. 分享或保存到外部存储 +3. 在工程页删除已备份的工程 +4. 工程删除后,关联的所有测线、测点和波形文件将被永久删除 + +--- + +## 十五、常见问题 + +### Q: 连接不上设备 + +1. 确认手机已连接到设备的 WiFi 热点 +2. 确认 IP 地址和端口正确(默认 192.168.4.1:4321) +3. 确认没有其他设备(如 Qt 上位机)同时连接 +4. 尝试断开后重新连接 + +### Q: 配置下发超时 + +1. 检查 TCP 连接是否正常(顶部状态栏) +2. 确认不在采集过程中(需先停止采集) +3. 断开重连后重试 + +### Q: 波形显示为直线 + +1. 切换到 LIN(线性)模式查看 +2. 检查是否只显示了一个通道(其他通道可能在不同 Y 范围) +3. 确认增益设置是否正确 + +### Q: 连续采集无法停止 + +1. 多次点击停止按钮(App 会自动重试 3 次) +2. 如仍无法停止,断开 TCP 连接后重新连接 + +### Q: 波形全屏后无法操作 + +1. 确保使用单指拖拽、双指缩放 +2. 双击可恢复原始视图 +3. 点击右上角「✕」退出全屏 + +### Q: 采集数据未显示在测点页 + +1. 切换到正确的测线(使用顶部测线选择器) +2. 等待几秒让数据库加载完成 +3. 下拉刷新或切换 Tab 后重新进入 + +### Q: 导出的文件在微信中显示为 com.tencent.mm... + +这是正常的,系统分享面板中微信的图标可能显示为包名。点击即可正常发送文件。 + +### Q: 手机发热严重 + +连续采集时数据处理量大,属于正常现象。建议: +- 关闭不需要的后台应用 +- 避免在高温环境下长时间连续采集 +- Release 版比 Debug 版性能更好,发热更少 + +### Q: 数据丢失 + +- 采集数据实时存入数据库和 bin 文件,App 闪退不会丢失已保存的数据 +- 卸载 App 会清除所有数据,请先导出备份 +- 建议定期导出 .tem 文件到外部存储 + +--- + +## 技术支持 + +如遇到本手册未覆盖的问题,请联系开发团队。 diff --git a/src/services/DeviceService.ts b/src/services/DeviceService.ts index b79ffd8..59a28bf 100644 --- a/src/services/DeviceService.ts +++ b/src/services/DeviceService.ts @@ -95,6 +95,9 @@ export function handleIncomingFrame(frame: RawFrame) { let _lastUIUpdateTime = 0; const UI_THROTTLE_MS = 150; let _processing = false; +let _lastDiskCheckTime = 0; +const DISK_CHECK_INTERVAL_MS = 30000; +const MIN_FREE_BYTES = 100 * 1024 * 1024; // 100MB function processMeasurementPayload( payload: Uint8Array, @@ -126,6 +129,24 @@ function processMeasurementPayload( void StorageService.persistFrame(measurementFrame, dataStore.sessionId); + // Periodic disk space check during continuous acquisition + const now2 = Date.now(); + if (now2 - _lastDiskCheckTime > DISK_CHECK_INTERVAL_MS) { + _lastDiskCheckTime = now2; + void (async () => { + try { + const FileSystem = await import('expo-file-system/legacy'); + const free = await FileSystem.getFreeDiskStorageAsync(); + if (free < MIN_FREE_BYTES) { + const { Alert } = require('react-native'); + Alert.alert('存储空间不足', `剩余空间不足 100MB,已自动停止采集。\n请清理手机存储或导出数据后删除旧数据。`); + void deviceStop(); + devStore.setDeviceStatus('idle'); + } + } catch {} + })(); + } + const isSingle = devStore.deviceStatus === 'single'; const now = Date.now(); if (isSingle || now - _lastUIUpdateTime >= UI_THROTTLE_MS) {