commit 0fa483f450867f7cbdff94473c23642c7fee1126 Author: userName Date: Wed Mar 11 10:33:16 2026 +0800 初始提交:雷达系统代码,包含WiFi管理和雷达数据处理功能 diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..e6de87d --- /dev/null +++ b/.gitignore @@ -0,0 +1,7 @@ +.pio +.vscode/.browse.c_cpp.db* +.vscode/c_cpp_properties.json +.vscode/launch.json +.vscode/ipch +Git提交方法.md +.trae/ diff --git a/.vscode/extensions.json b/.vscode/extensions.json new file mode 100644 index 0000000..080e70d --- /dev/null +++ b/.vscode/extensions.json @@ -0,0 +1,10 @@ +{ + // See http://go.microsoft.com/fwlink/?LinkId=827846 + // for the documentation about the extensions.json format + "recommendations": [ + "platformio.platformio-ide" + ], + "unwantedRecommendations": [ + "ms-vscode.cpptools-extension-pack" + ] +} diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..84548da --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,52 @@ +{ + "files.associations": { + "cmath": "cpp", + "array": "cpp", + "string": "cpp", + "string_view": "cpp", + "new": "cpp", + "atomic": "cpp", + "cctype": "cpp", + "clocale": "cpp", + "cstdarg": "cpp", + "cstddef": "cpp", + "cstdint": "cpp", + "cstdio": "cpp", + "cstdlib": "cpp", + "cstring": "cpp", + "ctime": "cpp", + "cwchar": "cpp", + "cwctype": "cpp", + "deque": "cpp", + "unordered_map": "cpp", + "unordered_set": "cpp", + "vector": "cpp", + "exception": "cpp", + "algorithm": "cpp", + "functional": "cpp", + "iterator": "cpp", + "map": "cpp", + "memory": "cpp", + "memory_resource": "cpp", + "numeric": "cpp", + "optional": "cpp", + "random": "cpp", + "system_error": "cpp", + "tuple": "cpp", + "type_traits": "cpp", + "utility": "cpp", + "fstream": "cpp", + "initializer_list": "cpp", + "iomanip": "cpp", + "iosfwd": "cpp", + "istream": "cpp", + "limits": "cpp", + "ostream": "cpp", + "sstream": "cpp", + "stdexcept": "cpp", + "streambuf": "cpp", + "cinttypes": "cpp", + "typeinfo": "cpp" + }, + "C_Cpp.errorSquiggles": "disabled" +} \ No newline at end of file diff --git a/BLE_API.md b/BLE_API.md new file mode 100644 index 0000000..5b7c715 --- /dev/null +++ b/BLE_API.md @@ -0,0 +1,516 @@ +# ESP32 BLE API 文档 + +## 概述 + +本文档描述了 ESP32 雷达设备的 BLE(蓝牙低功耗)通信 API,用于通过蓝牙连接配置和管理设备。 + +## 通信协议 + +- **传输方式**: BLE (Bluetooth Low Energy) +- **数据格式**: JSON +- **编码**: UTF-8 +- **分包大小**: 20 字节/包 +- **特征值 UUID**: `beb5483e-36e1-4688-b7f5-ea07361b26a8` +- **服务 UUID**: `a8c1e5c0-3d5d-4a9d-8d5e-7c8b6a4e2f1a` + +## 通用响应格式 + +所有响应都遵循以下 JSON 格式: + +```json +{ + "type": "响应类型", + "success": true/false, + "message": "可选的消息描述", + ... +} +``` + +## API 列表 + +### 1. WiFi 扫描 + +扫描周围可用的 WiFi 网络。 + +#### 请求 + +```json +{ + "command": "scanWiFi" +} +``` + +#### 响应 + +成功时: + +```json +{ + "type": "scanWiFiResult", + "success": true, + "count": 3, + "networks": [ + { + "ssid": "WiFi名称1", + "rssi": -45, + "channel": 6, + "encryption": 3 + }, + { + "ssid": "WiFi名称2", + "rssi": -60, + "channel": 11, + "encryption": 4 + } + ] +} +``` + +失败时: + +```json +{ + "type": "scanWiFiResult", + "success": false, + "message": "未扫描到任何WiFi网络", + "networks": [], + "count": 0 +} +``` + +#### 字段说明 + +| 字段 | 类型 | 说明 | +|------|------|------| +| `ssid` | string | WiFi 网络名称 | +| `rssi` | number | 信号强度(dBm),值越大信号越强 | +| `channel` | number | WiFi 通道号(1-13) | +| `encryption` | number | 加密类型:
0 = 开放
1 = WEP
2 = WPA_PSK
3 = WPA2_PSK
4 = WPA_WPA2_PSK | + +#### 示例 + +```javascript +// 发送扫描命令 +const scanCommand = JSON.stringify({ command: "scanWiFi" }); +await characteristic.writeValue(scanCommand); + +// 接收响应 +characteristic.addEventListener('characteristicvaluechanged', (event) => { + const response = JSON.parse(event.target.value); + if (response.type === "scanWiFiResult") { + console.log(`扫描到 ${response.count} 个网络`); + response.networks.forEach(network => { + console.log(`SSID: ${network.ssid}, 信号: ${network.rssi} dBm`); + }); + } +}); +``` + +--- + +### 2. 获取已保存的 WiFi 网络 + +获取设备中已保存的 WiFi 网络列表。 + +#### 请求 + +```json +{ + "command": "getSavedNetworks" +} +``` + +#### 响应 + +成功时: + +```json +{ + "type": "savedNetworks", + "success": true, + "count": 2, + "networks": [ + { + "ssid": "MyWiFi" + }, + { + "ssid": "OfficeWiFi" + } + ] +} +``` + +无保存网络时: + +```json +{ + "type": "savedNetworks", + "success": true, + "count": 0, + "networks": [] +} +``` + +#### 字段说明 + +| 字段 | 类型 | 说明 | +|------|------|------| +| `ssid` | string | 已保存的 WiFi 网络名称(不包含密码) | + +#### 示例 + +```javascript +// 发送获取命令 +const getCommand = JSON.stringify({ command: "getSavedNetworks" }); +await characteristic.writeValue(getCommand); + +// 接收响应 +characteristic.addEventListener('characteristicvaluechanged', (event) => { + const response = JSON.parse(event.target.value); + if (response.type === "savedNetworks") { + console.log(`已保存 ${response.count} 个网络`); + response.networks.forEach(network => { + console.log(`SSID: ${network.ssid}`); + }); + } +}); +``` + +--- + +### 3. 配置 WiFi + +设置 WiFi 网络配置。 + +#### 请求 + +```json +{ + "command": "setWiFiConfig", + "ssid": "WiFi名称", + "password": "WiFi密码" +} +``` + +#### 响应 + +成功时: + +```json +{ + "type": "wifiConfigResult", + "success": true, + "message": "WiFi配置成功" +} +``` + +失败时: + +```json +{ + "type": "wifiConfigResult", + "success": false, + "message": "错误描述" +} +``` + +#### 错误说明 + +| 错误信息 | 原因 | 解决方案 | +|---------|--------|---------| +| `未扫描到任何WiFi网络,请检查设备位置` | 设备扫描不到任何 WiFi 网络 | 将设备移到更靠近路由器的位置 | +| `未找到目标WiFi网络,请检查WiFi名称是否正确` | 扫描结果中不存在目标 WiFi | 检查 WiFi 名称是否拼写正确,确保设备在 WiFi 覆盖范围内 | +| `目标WiFi信号过弱,请将设备靠近路由器` | 目标 WiFi 信号强度低于阈值(-200 dBm) | 将设备移到更靠近路由器的位置 | +| `WiFi配置失败,请检查密码是否正确` | 密码错误或连接超时 | 检查 WiFi 密码是否正确,确保设备在 WiFi 覆盖范围内 | + +#### 字段说明 + +| 字段 | 类型 | 必需 | 说明 | +|------|------|--------|------| +| `ssid` | string | ✅ | WiFi 网络名称(最多 31 字符) | +| `password` | string | ✅ | WiFi 密码(最多 63 字符) | + +#### 示例 + +```javascript +// 发送配置命令 +const configCommand = JSON.stringify({ + command: "setWiFiConfig", + ssid: "MyWiFi", + password: "mypassword" +}); +await characteristic.writeValue(configCommand); + +// 接收响应 +characteristic.addEventListener('characteristicvaluechanged', (event) => { + const response = JSON.parse(event.target.value); + if (response.type === "wifiConfigResult") { + if (response.success) { + console.log("WiFi 配置成功"); + } else { + console.log("WiFi 配置失败:", response.message); + } + } +}); +``` + +--- + +### 4. 查询设备状态 + +查询设备的当前状态,包括 WiFi 连接状态、设备 ID 等。 + +#### 请求 + +```json +{ + "command": "queryStatus" +} +``` + +#### 响应 + +```json +{ + "type": "deviceStatus", + "success": true, + "deviceId": 1001, + "wifiConfigured": true, + "wifiConnected": true, + "ipAddress": "192.168.137.241" +} +``` + +#### 字段说明 + +| 字段 | 类型 | 说明 | +|------|------|------| +| `deviceId` | number | 设备 ID(1000-1999) | +| `wifiConfigured` | boolean | 是否已配置 WiFi | +| `wifiConnected` | boolean | WiFi 是否已连接 | +| `ipAddress` | string | 设备 IP 地址(已连接时) | + +#### 示例 + +```javascript +// 发送查询命令 +const queryCommand = JSON.stringify({ command: "queryStatus" }); +await characteristic.writeValue(queryCommand); + +// 接收响应 +characteristic.addEventListener('characteristicvaluechanged', (event) => { + const response = JSON.parse(event.target.value); + if (response.type === "deviceStatus") { + console.log(`设备 ID: ${response.deviceId}`); + console.log(`WiFi 已配置: ${response.wifiConfigured}`); + console.log(`WiFi 已连接: ${response.wifiConnected}`); + console.log(`IP 地址: ${response.ipAddress}`); + } +}); +``` + +--- + +### 5. 设置设备 ID + +设置设备的唯一标识 ID。 + +#### 请求 + +```json +{ + "command": "setDeviceId", + "newDeviceId": 1001 +} +``` + +#### 响应 + +成功时: + +```json +{ + "type": "setDeviceIdResult", + "success": true, + "message": "设备ID设置成功", + "newDeviceId": 1001 +} +``` + +失败时: + +```json +{ + "type": "setDeviceIdResult", + "success": false, + "message": "设备ID超出范围,有效范围: 1000-1999" +} +``` + +#### 字段说明 + +| 字段 | 类型 | 必需 | 说明 | +|------|------|--------|------| +| `newDeviceId` | number | ✅ | 新设备 ID(1000-1999) | + +--- + +### 6. 启动持续发送 + +启动雷达数据的持续发送模式。 + +#### 请求 + +```json +{ + "command": "startContinuousSend", + "interval": 200 +} +``` + +#### 响应 + +```json +{ + "type": "startContinuousSendResult", + "success": true, + "message": "已启动持续发送模式", + "interval": 200 +} +``` + +#### 字段说明 + +| 字段 | 类型 | 必需 | 说明 | +|------|------|--------|------| +| `interval` | number | ❌ | 发送间隔(毫秒),默认 200ms,范围 100-10000 | + +--- + +### 7. 停止持续发送 + +停止雷达数据的持续发送模式。 + +#### 请求 + +```json +{ + "command": "stopContinuousSend" +} +``` + +#### 响应 + +```json +{ + "type": "stopContinuousSendResult", + "success": true, + "message": "已停止持续发送模式" +} +``` + +--- + +### 8. 查询雷达数据 + +查询当前的雷达传感器数据。 + +#### 请求 + +```json +{ + "command": "queryRadarData" +} +``` + +#### 响应 + +```json +{ + "type": "radarData", + "success": true, + "deviceId": 1001, + "timestamp": 1234567890, + "presence": 1, + "heartRate": 75.5, + "breathRate": 16.2, + "motion": 0, + "heartbeatWaveform": 120, + "breathingWaveform": 85, + "sleepState": 0 +} +``` + +#### 字段说明 + +| 字段 | 类型 | 说明 | +|------|------|------| +| `timestamp` | number | 时间戳(毫秒) | +| `presence` | number | 是否检测到人体(0=无,1=有) | +| `heartRate` | number | 心率(次/分钟) | +| `breathRate` | number | 呼吸率(次/分钟) | +| `motion` | number | 运动状态(0=静止,1=轻微,2=明显) | +| `heartbeatWaveform` | number | 心跳波形值 | +| `breathingWaveform` | number | 呼吸波形值 | +| `sleepState` | number | 睡眠状态(0=清醒,1=浅睡,2=深睡,3=REM) | + +--- + +### 9. 回显测试 + +用于测试 BLE 连接是否正常。 + +#### 请求 + +```json +{ + "command": "echo", + "content": "测试内容" +} +``` + +#### 响应 + +```json +{ + "type": "echoResponse", + "originalContent": "测试内容", + "receivedSuccessfully": true +} +``` + +--- + +## 错误响应 + +当请求失败时,设备会返回错误响应: + +```json +{ + "type": "error", + "message": "错误描述", + "receivedData": "原始请求数据" +} +``` + +## 连接流程 + +1. 扫描并连接 BLE 设备 +2. 开启 notify 订阅 +3. 发送 `queryStatus` 命令获取设备状态 +4. 根据需要配置 WiFi 或查询雷达数据 + +## 注意事项 + +1. **分包传输**: 所有 JSON 数据都会被分包发送(每包 20 字节),客户端需要正确拼接 +2. **UTF-8 编码**: 确保使用 UTF-8 编码处理中文字符 +3. **超时处理**: 建议为每个命令设置超时时间(推荐 5-10 秒) +4. **设备 ID 范围**: 有效范围为 1000-1999 +5. **WiFi 限制**: 仅支持 2.4GHz WiFi 网络 +6. **最大保存网络数**: 最多保存 10 个 WiFi 配置 + +## 版本历史 + +| 版本 | 日期 | 说明 | +|------|------|------| +| 1.0 | 2025-02-03 | 初始版本,包含所有基础 API | diff --git a/LVEDO.cpp b/LVEDO.cpp new file mode 100644 index 0000000..e69de29 diff --git a/README.md b/README.md new file mode 100644 index 0000000..b6d4823 --- /dev/null +++ b/README.md @@ -0,0 +1,259 @@ +# Rader_Success_5 - ESP32 雷达数据采集与传输系统 + +## 项目简介 + +本项目是一个基于 ESP32 的智能雷达数据采集与传输系统,通过 R60ABD1 雷达传感器采集人体生命体征数据,并通过蓝牙(BLE)与小程序交互,同时支持 WiFi 连接将数据上传到云服务器数据库。 + +## 主要功能 + +### 1. 雷达数据采集 +- **人体存在检测**:实时检测区域内是否有人 +- **心率监测**:监测人体心率(BPM) +- **呼吸率监测**:监测人体呼吸频率 +- **睡眠监测**:监测睡眠状态(清醒/浅睡/深睡/REM) +- **运动检测**:检测人体运动状态 +- **距离测量**:测量人体与设备的距离 +- **睡眠质量评分**:提供睡眠质量评估 + +### 2. 蓝牙通信(BLE) +- 设备配置与管理 +- WiFi 网络扫描与配置 +- 设备状态查询 +- 雷达数据实时查询 +- 持续数据发送模式 +- 设备 ID 设置 + +### 3. WiFi 连接 +- 自动 WiFi 连接与重连 +- 支持多 WiFi 网络配置(最多 10 个) +- 网络信号强度检测 +- 网络状态监控 + +### 4. 数据上传 +- 将雷达数据上传到 InfluxDB 云数据库 +- 支持每日数据汇总 +- 支持睡眠数据专项上传 + +## 硬件配置 + +### 主控芯片 +- **ESP32**:双核微控制器,支持 WiFi 和 BLE + +### 雷达传感器 +- **型号**:R60ABD1 +- **通信接口**:UART +- **检测范围**:人体存在、心率、呼吸率、睡眠状态等 + +### 引脚定义 +| 引脚 | 功能 | 说明 | +|------|------|------| +| GPIO 0 | Boot 按钮 | 配置清除按钮 | +| GPIO 48 | 网络状态 LED | 指示网络连接状态 | +| GPIO 4 | 配置清除 LED | 指示配置清除状态 | +| GPIO 8 | 自定义 GPIO | 用户自定义功能 | +| GPIO 9 | 自定义 GPIO | 用户自定义功能 | + +### LED 状态指示 +| 状态 | LED 行为 | 说明 | +|------|----------|------| +| 初始化/未连接 | 慢闪(1秒间隔) | 正在初始化或未连接网络 | +| 连接中 | 快闪(200ms间隔) | 正在连接 WiFi | +| 已连接 | 呼吸灯 | WiFi 已连接 | +| 断开连接 | 慢闪(1秒间隔) | WiFi 断开连接 | + +## 软件架构 + +### 核心模块 + +#### 1. RadarManager(雷达管理器) +- 雷达数据采集与解析 +- UART 通信管理 +- 数据队列管理 +- BLE 数据发送 + +#### 2. WiFiManager(WiFi 管理器) +- WiFi 网络扫描 +- WiFi 连接管理 +- 配置存储与加载 +- 自动重连机制 + +#### 3. 主程序(main.cpp) +- 任务调度与管理 +- LED 状态控制 +- 按钮监控 +- 系统初始化 + +### FreeRTOS 任务 +| 任务名称 | 功能 | 优先级 | +|----------|------|--------| +| uartProcessTask | UART 数据处理 | 高 | +| radarDataTask | 雷达数据处理 | 高 | +| bleSendTask | BLE 数据发送 | 中 | +| vitalSendTask | 生命体征数据发送 | 中 | +| wifiMonitorTask | WiFi 状态监控 | 中 | +| ledControlTask | LED 状态控制 | 低 | +| bootButtonMonitorTask | 按钮监控 | 低 | +| configClearLedTask | 配置清除 LED 控制 | 低 | + +## BLE API 文档 + +详细的 BLE API 文档请参考 [BLE_API.md](BLE_API.md) + +### 主要 API 命令 +- `scanWiFi` - 扫描 WiFi 网络 +- `getSavedNetworks` - 获取已保存的 WiFi 网络 +- `setWiFiConfig` - 配置 WiFi 网络 +- `queryStatus` - 查询设备状态 +- `setDeviceId` - 设置设备 ID +- `startContinuousSend` - 启动持续发送 +- `stopContinuousSend` - 停止持续发送 +- `queryRadarData` - 查询雷达数据 +- `echo` - 回显测试 + +### BLE 连接参数 +- **服务 UUID**: `a8c1e5c0-3d5d-4a9d-8d5e-7c8b6a4e2f1a` +- **特征值 UUID**: `beb5483e-36e1-4688-b7f5-ea07361b26a8` +- **分包大小**: 20 字节/包 +- **数据格式**: JSON(UTF-8 编码) + +## 数据结构 + +### 传感器数据(SensorData) +```cpp +typedef struct { + float breath_rate; // 呼吸率 + float heart_rate; // 心率 + uint8_t breath_valid; // 呼吸率有效标志 + uint8_t heart_valid; // 心率有效标志 + uint8_t presence; // 存在状态 + uint8_t motion; // 运动状态 + int heartbeat_waveform; // 心跳波形 + int breathing_waveform; // 呼吸波形 + uint16_t distance; // 距离 + uint8_t sleep_state; // 睡眠状态 + uint32_t sleep_time; // 睡眠时长 + uint8_t sleep_score; // 睡眠评分 + // ... 更多字段 +} SensorData; +``` + +### 睡眠状态定义 +| 值 | 状态 | 说明 | +|----|------|------| +| 0 | 清醒 | 处于清醒状态 | +| 1 | 浅睡 | 处于浅度睡眠 | +| 2 | 深睡 | 处于深度睡眠 | +| 3 | REM | 快速眼动睡眠 | + +## 配置管理 + +### 设备 ID +- **有效范围**:1000-1999 +- **存储位置**:ESP32 Flash(Preferences) +- **默认值**:0000(未设置) + +### WiFi 配置 +- **最大保存数量**:10 个网络 +- **最小信号强度**:-200 dBm +- **连接超时**:15 秒 +- **重连间隔**:2 秒 + +### 配置清除 +- **触发方式**:启动时按住 Boot 按钮 3 秒 +- **清除内容**:所有 WiFi 配置和设备 ID +- **LED 指示**:呼吸灯模式 + +## 编译与烧录 + +### 开发环境 +- **IDE**:Arduino IDE 2.x +- **框架**:Arduino ESP32 +- **编译器**:GCC for ESP32 + +### 依赖库 +- `Arduino.h` - Arduino 核心库 +- `WiFi.h` - WiFi 功能库 +- `BLEDevice.h` - BLE 功能库 +- `ArduinoJson.h` - JSON 处理库 +- `Preferences.h` - Flash 存储库 +- `HTTPClient.h` - HTTP 客户端库 + +### 编译步骤 +1. 使用 platformIO ,在vscode中打开项目 +3. 选择正确的串口 +4. 点击"上传"按钮编译并烧录 + +## 使用说明 + +### 首次使用 +1. 上电启动设备 +2. 通过手机蓝牙连接设备 +3. 使用小程序扫描 WiFi 网络 +4. 配置 WiFi 连接信息 +5. 设置设备 ID(可选) +6. 设备自动连接 WiFi 并开始上传数据 + +### 日常使用 +1. 设备自动连接已保存的 WiFi +2. 雷达数据实时采集 +3. 数据自动上传到云服务器 +4. 可通过 BLE 查询设备状态和雷达数据 + +### 配置清除 +1. 断电重启设备 +2. 按住 Boot 按钮不放 +3. 上电后保持按住 3 秒 +4. 配置清除 LED 呼吸闪烁 +5. 释放按钮,配置已清除 + +## 项目结构 + +``` +Rader_Success_5/ +├── src/ +│ ├── main.cpp # 主程序 +│ ├── radar_manager.cpp # 雷达管理器实现 +│ ├── radar_manager.h # 雷达管理器头文件 +│ ├── wifi_manager.cpp # WiFi 管理器实现 +│ └── wifi_manager.h # WiFi 管理器头文件 +├── BLE_API.md # BLE API 文档 +├── 传感器数据.txt # 传感器数据说明 +└── README.md # 项目说明文档 +``` + +## 技术特点 + +1. **模块化设计**:雷达管理和 WiFi 管理分离,便于维护和扩展 +2. **多任务处理**:使用 FreeRTOS 实现多任务并发处理 +3. **队列通信**:使用 FreeRTOS 队列实现任务间数据传递 +4. **流控机制**:BLE 数据发送采用流控,避免数据拥塞 +5. **自动重连**:WiFi 断开后自动重连,提高系统稳定性 +6. **Flash 存储**:使用 Preferences 持久化存储配置信息 +7. **LED 状态指示**:直观的 LED 状态显示,便于用户了解设备状态 + +## 注意事项 + +1. **WiFi 限制**:仅支持 2.4GHz WiFi 网络 +2. **BLE 连接**:一次只能连接一个 BLE 客户端 +3. **数据上传**:需要确保 WiFi 连接正常才能上传数据 +4. **设备 ID**:必须在有效范围内(1000-1999) +5. **配置清除**:清除配置后需要重新配置 WiFi 和设备 ID +6. **雷达安装**:建议安装在床铺正上方,距离 1-2 米 + +## 版本历史 + +| 版本 | 日期 | 说明 | +|------|------|------| +| 5.0 | 2025-02-28 | 第5版,重构代码结构,添加雷达和 WiFi 管理器模块 | + +## 许可证 + +本项目仅供学习和研究使用。 + +## 联系方式 + +如有问题或建议,请联系项目维护者。 + +--- + +**项目地址**:http://lmhrt.cn:6771/ming/Rader_Success_5.git diff --git a/include/README b/include/README new file mode 100644 index 0000000..49819c0 --- /dev/null +++ b/include/README @@ -0,0 +1,37 @@ + +This directory is intended for project header files. + +A header file is a file containing C declarations and macro definitions +to be shared between several project source files. You request the use of a +header file in your project source file (C, C++, etc) located in `src` folder +by including it, with the C preprocessing directive `#include'. + +```src/main.c + +#include "header.h" + +int main (void) +{ + ... +} +``` + +Including a header file produces the same results as copying the header file +into each source file that needs it. Such copying would be time-consuming +and error-prone. With a header file, the related declarations appear +in only one place. If they need to be changed, they can be changed in one +place, and programs that include the header file will automatically use the +new version when next recompiled. The header file eliminates the labor of +finding and changing all the copies as well as the risk that a failure to +find one copy will result in inconsistencies within a program. + +In C, the convention is to give header files names that end with `.h'. + +Read more about using header files in official GCC documentation: + +* Include Syntax +* Include Operation +* Once-Only Headers +* Computed Includes + +https://gcc.gnu.org/onlinedocs/cpp/Header-Files.html diff --git a/lib/README b/lib/README new file mode 100644 index 0000000..9379397 --- /dev/null +++ b/lib/README @@ -0,0 +1,46 @@ + +This directory is intended for project specific (private) libraries. +PlatformIO will compile them to static libraries and link into the executable file. + +The source code of each library should be placed in a separate directory +("lib/your_library_name/[Code]"). + +For example, see the structure of the following example libraries `Foo` and `Bar`: + +|--lib +| | +| |--Bar +| | |--docs +| | |--examples +| | |--src +| | |- Bar.c +| | |- Bar.h +| | |- library.json (optional. for custom build options, etc) https://docs.platformio.org/page/librarymanager/config.html +| | +| |--Foo +| | |- Foo.c +| | |- Foo.h +| | +| |- README --> THIS FILE +| +|- platformio.ini +|--src + |- main.c + +Example contents of `src/main.c` using Foo and Bar: +``` +#include +#include + +int main (void) +{ + ... +} + +``` + +The PlatformIO Library Dependency Finder will find automatically dependent +libraries by scanning project source files. + +More information about PlatformIO Library Dependency Finder +- https://docs.platformio.org/page/librarymanager/ldf.html diff --git a/platformio.ini b/platformio.ini new file mode 100644 index 0000000..83d4f5f --- /dev/null +++ b/platformio.ini @@ -0,0 +1,17 @@ +; PlatformIO Project Configuration File +; +; Build options: build flags, source filter +; Upload options: custom upload port, speed and extra flags +; Library options: dependencies, extra library storages +; Advanced options: extra scripting +; +; Please visit documentation for the other options and examples +; https://docs.platformio.org/page/projectconf.html + +[env:freenove_esp32_s3_wroom] +platform = espressif32 +board = freenove_esp32_s3_wroom +framework = arduino +lib_deps = + bblanchon/ArduinoJson@^7.4.2 + emelianov/modbus-esp8266@^4.1.0 diff --git a/src/main.cpp b/src/main.cpp new file mode 100644 index 0000000..7b1aa5c --- /dev/null +++ b/src/main.cpp @@ -0,0 +1,620 @@ +#include +#include +#include +#include +#include +#include "wifi_manager.h" +#include "radar_manager.h" + +// ESP32 GPIO控制演示 +#define BOOT_BUTTON_PIN 0 // Boot按钮引脚 +#define NETWORK_LED_PIN 5 // 网络状态LED指示灯开发板48引脚,雷达板5引脚 +#define CONFIG_CLEAR_PIN 4 // 配置清除指示灯 +#define GPIO8 8 // 自定义GPIO8 +#define GPIO9 9 // 自定义GPIO9 + +uint8_t WiFi_Connect_First_bit = 1; // WiFi首次连接标志位 + +// 配置清除指示灯状态枚举 +enum ConfigClearStatus { + CONFIG_NORMAL, // 正常运行 - LOW + CONFIG_PREPARING, // 准备清除 - HIGH + CONFIG_CLEARING, // 清除过程中 - 呼吸灯 + CONFIG_COMPLETED // 清除完成 - 快速闪烁3次 +}; + +NetworkStatus currentNetworkStatus = NET_INITIAL; // 当前网络状态 +unsigned long lastBlinkTime = 0; // 上次LED闪烁时间 +bool ledState = false; // LED状态 +int breatheValue = 0; // 呼吸灯当前亮度值 +bool breatheIncreasing = true; // 呼吸灯是否在增加亮度 + +ConfigClearStatus currentConfigClearStatus = CONFIG_NORMAL; // 当前配置清除状态 +unsigned long lastConfigBlinkTime = 0; // 上次配置清除LED闪烁时间 +bool configLedState = false; // 配置清除LED状态 +int configBreatheValue = 0; // 配置清除呼吸灯当前亮度值 +bool configBreatheIncreasing = true; // 配置清除呼吸灯是否在增加亮度 + +const int SLOW_BLINK_INTERVAL = 1000; // 慢闪间隔(毫秒) +const int FAST_BLINK_INTERVAL = 200; // 快闪间隔(毫秒) +const int BREATHE_INTERVAL = 40; // 呼吸灯更新间隔(毫秒) +const int BREATHE_MIN = 0; // 呼吸灯最小亮度值 +const int BREATHE_MAX = 155; // 呼吸灯最大亮度值 +const int BREATHE_STEP = 5; // 呼吸灯亮度步进值 + +const uint16_t MIN_DEVICE_ID = 1000; // 最小设备ID +const uint16_t MAX_DEVICE_ID = 1999; // 最大设备ID + +const unsigned long CLEAR_CONFIG_DURATION = 3000; // 清除配置持续时间(毫秒) + +Preferences preferences; // Flash存储对象 +WiFiManager wifiManager; // WiFi管理器对象 + +uint16_t currentDeviceId = 0000; // 当前设备ID +bool clearConfigRequested = false; // 清除配置请求标志 +bool forceLedOff = false; // 强制关闭LED标志 + +void configClearLedTask(void *parameter); +void bootButtonMonitorTask(void *parameter); +void checkBootButton(); +void clearStoredConfig(); +void ledControlTask(void *parameter); +void setNetworkStatus(NetworkStatus status); +void wifiMonitorTask(void *parameter); +void WiFiEvent(WiFiEvent_t event); +void loadDeviceId(); +void saveDeviceId(); + +String getFieldNameByProtocolId(int protocolId); + +/** + * @brief 根据协议ID获取字段名称 + * 将协议ID映射到对应的字段名称,用于数据序列化和反序列化 + * @param protocolId 协议ID + * @return 对应的字段名称字符串 + */ +String getFieldNameByProtocolId(int protocolId) { + switch(protocolId) { + case 1: + return "heartRate"; + case 2: + return "breathingRate"; + case 13: + return "personDetected"; + case 14: + return "humanActivity"; + case 15: + return "humanDistance"; + case 16: + return "humanPosition"; + case 17: + return "sleepState"; + default: + return "unknown"; + } +} + +/** + * @brief 检查Boot按钮状态 + * 在启动时检查Boot按钮是否被按下,如果按下则进入配置清除流程 + */ +void checkBootButton() { + Serial.println("🔍 检查Boot按钮状态..."); + + pinMode(BOOT_BUTTON_PIN, INPUT_PULLUP); + + delay(10); + + int buttonState = digitalRead(BOOT_BUTTON_PIN); + Serial.printf("📊 Boot按钮状态: %s\n", buttonState == LOW ? "按下" : "释放"); + + if (buttonState == LOW) { + Serial.println("⚠️ 检测到Boot按钮按下,请释放按钮后继续启动"); + Serial.println("⏰ 等待按钮释放..."); + + while (digitalRead(BOOT_BUTTON_PIN) == LOW) { + delay(100); + } + + Serial.println("✅ Boot按钮已释放,正常启动"); + } else { + Serial.println("✅ Boot按钮未按下,正常启动"); + } +} + +/** + * @brief 加载设备ID + * 从Flash中读取保存的设备ID + */ +void loadDeviceId() { + currentDeviceId = preferences.getUShort("deviceId", 1001); + Serial.printf("从Flash加载设备ID: %u\n", currentDeviceId); +} + +/** + * @brief 保存设备ID + * 将设备ID保存到Flash中 + */ +void saveDeviceId() { + preferences.putUShort("deviceId", currentDeviceId); + Serial.printf("设备ID已保存到Flash: %u\n", currentDeviceId); +} + +/** + * @brief 清除存储的配置 + * 清除Flash中保存的所有配置,包括设备ID和WiFi配置 + */ +void clearStoredConfig() { + Serial.println("🧹 开始清除存储的配置..."); + + uint16_t oldDeviceId = preferences.getUShort("deviceId", 0); + + preferences.remove("deviceId"); + preferences.remove("wifi_first"); + + wifiManager.clearAllConfigs(); + + Serial.println("✅ 配置已清除完成"); + Serial.printf("🗑️ 被清除的设备ID: %u\n", oldDeviceId); + + currentDeviceId = 1001; + WiFi_Connect_First_bit = 1; + + WiFi.disconnect(true); + setNetworkStatus(NET_DISCONNECTED); + + Serial.println("🔄 已清除Flash与内存中的配置,请重新配置WiFi和设备ID"); + + if (deviceConnected) { + sendStatusToBLE(); + } +} + +/** + * @brief 配置清除LED控制任务 + * 根据配置清除状态控制CONFIG_CLEAR_PIN引脚的LED显示 + * @param parameter 任务参数(未使用) + */ +void configClearLedTask(void *parameter) { + while (1) { + switch (currentConfigClearStatus) { + case CONFIG_NORMAL: + analogWrite(CONFIG_CLEAR_PIN, 0); + break; + + case CONFIG_PREPARING: + analogWrite(CONFIG_CLEAR_PIN, 255); + break; + + case CONFIG_CLEARING: + if (millis() - lastConfigBlinkTime >= BREATHE_INTERVAL) { + analogWrite(CONFIG_CLEAR_PIN, configBreatheValue); + + if (configBreatheIncreasing) { + configBreatheValue += 5; + if (configBreatheValue >= BREATHE_MAX) { + configBreatheValue = BREATHE_MAX; + configBreatheIncreasing = false; + } + } else { + configBreatheValue -= 5; + if (configBreatheValue <= BREATHE_MIN) { + configBreatheValue = BREATHE_MIN; + configBreatheIncreasing = true; + } + } + lastConfigBlinkTime = millis(); + } + break; + + case CONFIG_COMPLETED: + if (millis() - lastConfigBlinkTime >= FAST_BLINK_INTERVAL) { + configLedState = !configLedState; + digitalWrite(CONFIG_CLEAR_PIN, configLedState ? HIGH : LOW); + lastConfigBlinkTime = millis(); + + static int blinkCount = 0; + blinkCount++; + + if (blinkCount >= 6) { + blinkCount = 0; + currentConfigClearStatus = CONFIG_NORMAL; + digitalWrite(CONFIG_CLEAR_PIN, LOW); + } + } + break; + } + + vTaskDelay(10 / portTICK_PERIOD_MS); + } +} + +/** + * @brief BOOT按钮监控任务 + * 持续监控BOOT按钮状态,检测长按3秒事件并触发配置清除 + * @param parameter 任务参数(未使用) + */ +void bootButtonMonitorTask(void *parameter) { + Serial.println("🔍 启动BOOT按钮监控任务..."); + + pinMode(BOOT_BUTTON_PIN, INPUT_PULLUP); + + unsigned long buttonPressStartTime = 0; + bool buttonPressed = false; + + while (1) { + int buttonState = digitalRead(BOOT_BUTTON_PIN); + + if (buttonState == LOW && !buttonPressed) { + buttonPressed = true; + buttonPressStartTime = millis(); + Serial.println("⚠️ 检测到BOOT按钮按下,长按3秒将清除配置"); + + currentConfigClearStatus = CONFIG_PREPARING; + } + else if (buttonState == HIGH && buttonPressed) { + if (!clearConfigRequested) { + currentConfigClearStatus = CONFIG_NORMAL; + Serial.println("❌ 按钮释放,取消清除操作"); + } + buttonPressed = false; + } + + if (buttonPressed && (millis() - buttonPressStartTime >= CLEAR_CONFIG_DURATION)) { + if (!clearConfigRequested) { + clearConfigRequested = true; + forceLedOff = true; + ledcWrite(0, 0); + Serial.println("✅ 长按3秒确认,将清除配置"); + Serial.println("💡 网络LED已强制熄灭"); + + clearStoredConfig(); + + Serial.println("🔄 配置清除完成,LED将闪烁3次表示完成..."); + + analogWrite(CONFIG_CLEAR_PIN, 0); + Serial.println("🔄 系统即将重启..."); + + vTaskDelay(1000 / portTICK_PERIOD_MS); + ESP.restart(); + } + } + + vTaskDelay(50 / portTICK_PERIOD_MS); + + esp_task_wdt_reset(); + } +} + +/** + * @brief LED控制任务 + * 根据网络状态控制NETWORK_LED_PIN引脚的LED显示 + * 支持慢闪、快闪和呼吸灯效果 + * @param parameter 任务参数(未使用) + */ +void ledControlTask(void *parameter) { + while (1) { + if (forceLedOff) { + ledcWrite(0, 0); + vTaskDelay(10 / portTICK_PERIOD_MS); + continue; + } + + switch (currentNetworkStatus) { + case NET_INITIAL: + case NET_DISCONNECTED: + if (millis() - lastBlinkTime >= SLOW_BLINK_INTERVAL) { + ledState = !ledState; + if(ledState) { + ledcWrite(0, 255); + } else { + ledcWrite(0, 0); + } + lastBlinkTime = millis(); + } + break; + + case NET_CONNECTING: + if (millis() - lastBlinkTime >= FAST_BLINK_INTERVAL) { + ledState = !ledState; + if(ledState) { + ledcWrite(0, 255); + } else { + ledcWrite(0, 0); + } + lastBlinkTime = millis(); + } + break; + + case NET_CONNECTED: + if (millis() - lastBlinkTime >= BREATHE_INTERVAL) { + ledcWrite(0, breatheValue); + + if (breatheIncreasing) { + breatheValue += BREATHE_STEP; + if (breatheValue >= BREATHE_MAX) { + breatheValue = BREATHE_MAX; + breatheIncreasing = false; + } + } else { + breatheValue -= BREATHE_STEP; + if (breatheValue <= BREATHE_MIN) { + breatheValue = BREATHE_MIN; + breatheIncreasing = true; + } + } + lastBlinkTime = millis(); + } + break; + } + + vTaskDelay(10 / portTICK_PERIOD_MS); + } +} + +/** + * @brief 设置网络状态 + * 更新当前网络状态,并重置呼吸灯参数 + * @param status 网络状态 + */ +void setNetworkStatus(NetworkStatus status) { + currentNetworkStatus = status; + + if (status == NET_CONNECTED) { + breatheValue = BREATHE_MIN; + breatheIncreasing = true; + } +} + +/** + * @brief WiFi事件处理函数 + * 处理WiFi连接状态变化事件,更新网络状态和LED显示 + * @param event WiFi事件类型 + */ +void WiFiEvent(WiFiEvent_t event) { + switch (event) { + case ARDUINO_EVENT_WIFI_STA_START: + setNetworkStatus(NET_INITIAL); + break; + + case ARDUINO_EVENT_WIFI_STA_CONNECTED: + setNetworkStatus(NET_CONNECTING); + break; + + case ARDUINO_EVENT_WIFI_STA_GOT_IP: + setNetworkStatus(NET_CONNECTED); + break; + + case ARDUINO_EVENT_WIFI_STA_DISCONNECTED: + setNetworkStatus(NET_DISCONNECTED); + break; + + case ARDUINO_EVENT_WIFI_STA_STOP: + setNetworkStatus(NET_DISCONNECTED); + break; + } +} + +/** + * @brief WiFi监控任务 + * 定期更新WiFi管理器状态,处理WiFi重连等逻辑 + * @param parameter 任务参数(未使用) + */ +void wifiMonitorTask(void *parameter) { + Serial.println("📡 WiFi监控任务启动"); + + while(1) { + wifiManager.update(); + vTaskDelay(500 / portTICK_PERIOD_MS); + } +} + +/** + * @brief 系统初始化函数 + * 初始化所有硬件外设、任务和通信模块 + */ +void setup() { + Serial.begin(115200); + + checkBootButton(); + + analogWrite(CONFIG_CLEAR_PIN, 0); + + Serial.println("🚀 ESP32-R60ABD1系统启动"); + Serial.println("🔧 初始化系统组件..."); + + pinMode(BOOT_BUTTON_PIN, INPUT); + pinMode(NETWORK_LED_PIN, OUTPUT); + pinMode(CONFIG_CLEAR_PIN, OUTPUT); + pinMode(GPIO8, OUTPUT); + pinMode(GPIO9, OUTPUT); + + digitalWrite(CONFIG_CLEAR_PIN, LOW); + digitalWrite(GPIO8, LOW); + digitalWrite(GPIO9, LOW); + digitalWrite(NETWORK_LED_PIN, LOW); + digitalWrite(CONFIG_CLEAR_PIN, LOW); + + ledcSetup(0, 5000, 8); + ledcSetup(1, 5000, 8); + ledcAttachPin(NETWORK_LED_PIN, 0); + ledcAttachPin(CONFIG_CLEAR_PIN, 1); + + WiFi.onEvent(WiFiEvent); + + setNetworkStatus(NET_INITIAL); + + esp_task_wdt_init(30, true); + esp_task_wdt_add(NULL); + + preferences.begin("radar_data", false); + + wifiManager.begin(); + + Serial.println("💾 加载设备配置..."); + loadDeviceId(); + + Serial.println("🏗️ 初始化雷达管理器..."); + initRadarManager(); + + xTaskCreate( + configClearLedTask, + "Config Clear LED Task", + 2048, + NULL, + 1, + NULL + ); + + xTaskCreate( + bootButtonMonitorTask, + "Boot Button Monitor Task", + 2048, + NULL, + 1, + NULL + ); + + xTaskCreate( + ledControlTask, + "LED Control Task", + 2048, + NULL, + 1, + NULL + ); + + xTaskCreate( + wifiMonitorTask, + "WiFi Monitor Task", + 4096, + NULL, + 2, + NULL + ); + + Serial.println("✅ FreeRTOS任务创建成功"); + + Serial.println("📶 初始化BLE服务..."); + String deviceName = "Radar_" + String(currentDeviceId); + BLEDevice::init(deviceName.c_str()); + pServer = BLEDevice::createServer(); + pServer->setCallbacks(new MyServerCallbacks()); + + BLEService *pService = pServer->createService(SERVICE_UUID); + pCharacteristic = pService->createCharacteristic( + CHARACTERISTIC_UUID, + BLECharacteristic::PROPERTY_READ | + BLECharacteristic::PROPERTY_WRITE | + BLECharacteristic::PROPERTY_NOTIFY + ); + pCharacteristic->setCallbacks(new MyCallbacks()); + pCharacteristic->addDescriptor(new BLE2902()); + + pService->start(); + BLEAdvertising *pAdvertising = BLEDevice::getAdvertising(); + pAdvertising->addServiceUUID(SERVICE_UUID); + pAdvertising->setScanResponse(true); + pAdvertising->setMinPreferred(0x06); + pAdvertising->setMinPreferred(0x12); + BLEDevice::startAdvertising(); + + Serial.println(String("BLE已启动,设备名称: Radar_") + String(currentDeviceId)); + + Serial.println("🌐 检查WiFi配置..."); + if (wifiManager.getSavedNetworkCount() > 0) { + Serial.printf("💾 检测到 %d 个已保存的WiFi配置,尝试连接...\n", wifiManager.getSavedNetworkCount()); + if (wifiManager.initializeWiFi()) { + Serial.println("✅ WiFi连接成功!"); + } else { + Serial.println("❌ WiFi连接失败,请通过BLE重新配置"); + } + } else { + Serial.println("⚠️ 未检测到WiFi配置,请通过BLE进行网络配置"); + } + + size_t wifi_first_len = preferences.getBytes("wifi_first", &WiFi_Connect_First_bit, sizeof(WiFi_Connect_First_bit)); + if (wifi_first_len == sizeof(WiFi_Connect_First_bit)) { + Serial.printf("从Flash读取 WiFi_Connect_First_bit: %u\n", WiFi_Connect_First_bit); + } else { + Serial.println("Flash中无 wifi_first 条目,保留内存中原始值"); + } + + if(WiFi_Connect_First_bit == 0) + { + unsigned long wifiWaitStart = millis(); + unsigned long lastWifiWaitPrint = 0; + const unsigned long WIFI_WAIT_TIMEOUT = 15000; + while (WiFi.status() != WL_CONNECTED && (millis() - wifiWaitStart) < WIFI_WAIT_TIMEOUT) { + if (millis() - lastWifiWaitPrint >= 1000) { + Serial.println("等待WiFi连接..."); + lastWifiWaitPrint = millis(); + } + yield(); + vTaskDelay(10 / portTICK_PERIOD_MS); + } + } + + Serial.println("WiFi连接成功!"); + initR60ABD1(); + + Serial.println("🎉 系统初始化完成,等待雷达数据..."); + + if (WiFi.status() == WL_CONNECTED) { + Serial.println("🌅 启动时发送睡眠数据到数据库"); + sendSleepDataToInfluxDB(); + } +} + +/** + * @brief 主循环函数 + * 处理BLE连接状态和定期发送雷达命令 + */ +void loop() { + esp_task_wdt_reset(); + + if (!deviceConnected && oldDeviceConnected) { + vTaskDelay(500 / portTICK_PERIOD_MS); + pServer->startAdvertising(); + Serial.println("开始BLE广播"); + oldDeviceConnected = deviceConnected; + } + if (deviceConnected && !oldDeviceConnected) { + oldDeviceConnected = deviceConnected; + } + { + static const uint8_t radar_cmds[][3] = { + {0x84, 0x81, 0x0F}, + {0x84, 0x8D, 0x0F}, + {0x84, 0x8F, 0x0F}, + {0x84, 0x8E, 0x0F}, + {0x84, 0x91, 0x0F}, + {0x84, 0x92, 0x0F}, + {0x84, 0x83, 0x0F}, + {0x84, 0x84, 0x0F}, + {0x84, 0x85, 0x0F}, + {0x84, 0x86, 0x0F}, + {0x84, 0x90, 0x0F} + }; + + const size_t cmdCount = sizeof(radar_cmds) / sizeof(radar_cmds[0]); + static size_t cmdIndex = 0; + static unsigned long lastCmdMillis = 0; + const unsigned long CMD_INTERVAL = 2000UL; + + unsigned long now = millis(); + if (now - lastCmdMillis >= CMD_INTERVAL) { + sendRadarCommand(radar_cmds[cmdIndex][0], radar_cmds[cmdIndex][1], radar_cmds[cmdIndex][2]); + lastCmdMillis = now; + cmdIndex++; + if (cmdIndex >= cmdCount) cmdIndex = 0; + } + } + + processBLEConfig(); + esp_task_wdt_reset(); + esp_task_wdt_reset(); + + esp_task_wdt_reset(); +} + diff --git a/src/main_backup.cpp.bak b/src/main_backup.cpp.bak new file mode 100644 index 0000000..b493864 --- /dev/null +++ b/src/main_backup.cpp.bak @@ -0,0 +1,2582 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "wifi_manager.h" + +// ESP32 GPIO控制演示 +#define BOOT_BUTTON_PIN 0 // Boot按钮引脚 +#define NETWORK_LED_PIN 5 // 网络状态LED指示灯裸板48引脚,应用板5引脚 +#define CONFIG_CLEAR_PIN 4 // 配置清除指示灯 +#define GPIO8 8 // 自定义GPIO8 +#define GPIO9 9 // 自定义GPIO9 + +uint8_t presence_Bit = 1;//标志位 +uint8_t WiFi_Connect_First_bit = 1; //WiFi第一次连接标志位 + +// 网络状态枚举 +enum NetworkStatus { + NET_INITIAL, // 初始化/未连接 - 慢闪 + NET_CONNECTING, // 连接中 - 快闪 + NET_CONNECTED, // 已连接 - 呼吸灯 + NET_DISCONNECTED // 断开连接 - 慢闪 +}; + +// 配置清除指示灯状态枚举 +enum ConfigClearStatus { + CONFIG_NORMAL, // 正常运行 - LOW + CONFIG_PREPARING, // 准备清除 - HIGH + CONFIG_CLEARING, // 清除过程中 - 呼吸灯 + CONFIG_COMPLETED // 清除完成 - 快速闪烁3次 +}; + +// 网络状态全局变量 +NetworkStatus currentNetworkStatus = NET_INITIAL; +unsigned long lastBlinkTime = 0; +bool ledState = false; +int breatheValue = 0; +bool breatheIncreasing = true; + +// 配置清除指示灯状态变量 +ConfigClearStatus currentConfigClearStatus = CONFIG_NORMAL; +unsigned long lastConfigBlinkTime = 0; +bool configLedState = false; +int configBreatheValue = 0; +bool configBreatheIncreasing = true; + +// LED控制相关参数 +const int SLOW_BLINK_INTERVAL = 1000; // 慢闪间隔 1秒 +const int FAST_BLINK_INTERVAL = 200; // 快闪间隔 0.2秒 +const int BREATHE_INTERVAL = 20; // 呼吸灯更新间隔 +const int BREATHE_MIN = 0; // 呼吸灯最小值 +const int BREATHE_MAX = 155; // 呼吸灯最大值 +const int BREATHE_STEP = 5; // 呼吸灯步进值 + +// 协议ID映射表 - 对应Python脚本中的field_mapping +const int PROTOCOL_HEART_RATE = 1; // 心率 +const int PROTOCOL_BREATH_RATE = 2; // 呼吸率 +const int PROTOCOL_PERSON_DETECTED = 13; // 人检 +const int PROTOCOL_HUMAN_ACTIVITY = 14; // 人体活动 +const int PROTOCOL_HUMAN_DISTANCE = 15; // 人体距离 +const int PROTOCOL_HUMAN_POSITION = 16; // 人体方位 +const int PROTOCOL_SLEEP_STATE = 17; // 睡眠状态 + +// R60ABD1新增协议ID +const int PROTOCOL_BODY_MOVEMENT = 18; // 体动幅度 +const int PROTOCOL_BREATH_STATUS = 19; // 呼吸信息 +const int PROTOCOL_SLEEP_TIME = 20; // 睡眠时长 +const int PROTOCOL_SLEEP_SCORE = 21; // 睡眠质量评分 +const int PROTOCOL_BED_ENTRY = 22; // 入床/离床状态 +const int PROTOCOL_ABNORMAL_STATE = 23; // 异常状态 +const int PROTOCOL_AVG_HEART_RATE = 24; // 平均心率 +const int PROTOCOL_AVG_BREATH_RATE = 25; // 平均呼吸率 +const int PROTOCOL_TURN_COUNT = 26; // 翻身次数 +const int PROTOCOL_LARGE_MOVE_RATIO = 27; // 大幅度体动占比 +const int PROTOCOL_SMALL_MOVE_RATIO = 28; // 小幅度体动占比 +const int PROTOCOL_POS_X = 29; // 人体X坐标 +const int PROTOCOL_POS_Y = 30; // 人体Y坐标 +const int PROTOCOL_POS_Z = 31; // 人体Z坐标 +const int PROTOCOL_DEEP_SLEEP_TIME = 32; // 深睡时长 +const int PROTOCOL_LIGHT_SLEEP_TIME = 33; // 浅睡时长 +const int PROTOCOL_AWAKE_TIME = 34; // 清醒时长 +const int PROTOCOL_SLEEP_TOTAL_TIME = 35; // 睡眠总时长 +const int PROTOCOL_DEEP_SLEEP_RATIO = 36; // 深睡占比 +const int PROTOCOL_LIGHT_SLEEP_RATIO = 37; // 浅睡占比 +const int PROTOCOL_AWAKE_RATIO = 38; // 清醒占比 +const int PROTOCOL_TURNOVER_COUNT = 39; // 翻身次数 +const int PROTOCOL_STRUGGLE_ALERT = 40; // 挣扎报警 +const int PROTOCOL_NO_ONE_ALERT = 41; // 无人计时报警 +const int PROTOCOL_BED_STATUS = 42; // 入床状态 +const int PROTOCOL_APNEA_COUNT = 43; // 呼吸暂停次数 + +bool clearConfigRequested = false; + +unsigned long bootButtonPressTime = 0; + +const unsigned long CLEAR_CONFIG_DURATION = 3000; // 长按3秒清除配置 + +#define BUFFER_SIZE 2000 // 固定存储2000个数据点 +// 环形缓冲区结构 +struct CircularBuffer { + float data[BUFFER_SIZE]; + unsigned long timestamps[BUFFER_SIZE]; // 存储时间戳 + int head; // 最新数据位置 + int tail; // 最旧数据位置 + int count; // 当前有效数据数量 + bool isFull; // 缓冲区是否已满 + float sum; // 当前数据总和 +}; + + + +Preferences preferences; + +WiFiManager wifiManager; + +uint16_t currentDeviceId = 0000; // 默认设备ID为0000 +const uint16_t MIN_DEVICE_ID = 1000; // 设备ID最小值 +const uint16_t MAX_DEVICE_ID = 1999; // 设备ID最大值 + +const uint32_t PHASE_SEND_INTERVAL = 1;// 每1毫秒发送一次相位数据 +const uint32_t VITAL_SEND_INTERVAL = 10;// 每10毫秒发送一次生命体征数据 + +#define SERVICE_UUID "a8c1e5c0-3d5d-4a9d-8d5e-7c8b6a4e2f1a" +// #define SERVICE_UUID "4fafc201-1fb5-459e-8fcc-c5c9c331914b" +#define CHARACTERISTIC_UUID "beb5483e-36e1-4688-b7f5-ea07361b26a8" + +#define UART_RX_BUFFER_SIZE 4096 + +BLEServer* pServer = NULL; +BLECharacteristic* pCharacteristic = NULL; + + +bool deviceConnected = false; +bool oldDeviceConnected = false; +String receivedData = ""; +String completeData = ""; +unsigned long lastReceiveTime = 0; + +const char* influxDBHost = "8.134.11.76"; +const int influxDBPort = 8086; +const char* influxDBToken = "KuTa5ZsqoHIhi2IglOO06zExUYw1_mJ6K0mcA9X1y6O6CJDog3_Cgr8mUw1SwpuCCKRElqxa6wAhrrhsYPytkg=="; +const char* influxDBOrg = "gzlg"; +const char* influxDBBucket = "gzlg"; + +const unsigned long SENSOR_TIMEOUT = 40000; +static uint32_t packetCounter = 0; +static bool shouldSendOtherData = false; + +unsigned long lastSensorUpdate = 0; + +// 睡眠数据发送相关 +unsigned long lastSleepDataTime = 0; +const unsigned long SLEEP_DATA_INTERVAL = 5000; // 5秒 = 5000毫秒 + +// 更新SensorData结构体以支持R60ABD1的全部功能点 +typedef struct { + float breath_rate; // 呼吸率 (DP8) 单位:次/分钟 + float heart_rate; // 心率 (DP6) 单位:BPM + uint8_t breath_valid; // 呼吸率有效性 + uint8_t heart_valid; // 心率有效性 + uint8_t presence ; // 存在检测 (DP1) 0=无人, 1=有人 + uint8_t motion = 0; // 运动状态 (DP2) 0=无, 1=静止, 2=活跃 + int heartbeat_waveform; // 心跳波形 (DP7) 数值+128 + int breathing_waveform; // 呼吸波形 (DP10) 数值+128 + + // R60ABD1新增字段 + uint16_t distance; // 人体距离 (DP3) 单位:cm + uint8_t body_movement; // 体动幅度 (DP4) 0-100 + uint8_t breath_status = 04; // 呼吸信息 (DP9) 01=正常, 02=呼吸过高, 03=呼吸过低, 04=无 + uint8_t sleep_state = 3; // 睡眠状态 (DP12) 0=深睡, 1=浅睡, 2=清醒, 3=无睡眠 + uint32_t sleep_time; // 睡眠时长 (DP13) 单位:秒 + uint8_t sleep_score; // 睡眠质量评分 (DP14) 0-100 + uint8_t sleep_grade; //睡眠质量评级 + uint8_t bed_entry; // 入床/离床状态 (DP11) 0=离床, 1=入床 + uint8_t abnormal_state; // 异常状态 (DP18) 0=正常, 1=异常 + uint8_t avg_heart_rate; // 平均心率 (DP15) + uint8_t avg_breath_rate; // 平均呼吸率 (DP15) + uint8_t turn_count; // 翻身次数 (DP15) + uint8_t large_move_ratio;// 大幅度体动占比 (DP15) + uint8_t small_move_ratio;// 小幅度体动占比 (DP15) + + // 根据技术文档新增字段 + int16_t pos_x; // 人体X坐标 (DP5) + int16_t pos_y; // 人体Y坐标 (DP5) + int16_t pos_z; // 人体Z坐标 (DP5) + int8_t breath_waveform[5]; // 呼吸波形数据 (DP10) + int8_t heart_waveform[5]; // 心率波形数据 (DP7) + uint16_t deep_sleep_time; // 深睡时长 (DP15) 单位:分钟 + uint16_t light_sleep_time; // 浅睡时长 (DP14) 单位:分钟 + uint16_t awake_time; // 清醒时长 (DP13) 单位:分钟 + uint16_t sleep_total_time; // 睡眠总时长 (DP18) 单位:分钟 + uint8_t deep_sleep_ratio; // 深睡占比 (DP16) + uint8_t light_sleep_ratio; // 浅睡占比 (DP16) + uint8_t awake_ratio; // 清醒占比 (DP16) + uint8_t turnover_count; // 翻身次数 (DP16) + uint8_t struggle_alert; // 挣扎报警 (DP21) + uint8_t no_one_alert; // 无人计时报警 (DP22) + uint8_t bed_status = 02; //入床状态 (DP11) + uint8_t bed_Out_Time; // 离床时间 (DP11) + uint8_t apnea_count; // 呼吸暂停次数 +} SensorData; + +SensorData sensorData ; + +HardwareSerial mySerial1(1); // 使用UART1 +const int BAUD_RATE = 115200; +const int UART1_RX = 3; // UART1的RX引脚,配合原理图 +const int UART1_TX = 2; // UART1的TX引脚,配合原理图 + +// FreeRTOS任务和队列定义 +QueueHandle_t phaseDataQueue; +QueueHandle_t vitalDataQueue; +QueueHandle_t uartQueue = NULL; // 添加串口数据队列 +TaskHandle_t bleSendTaskHandle = NULL; +TaskHandle_t vitalSendTaskHandle = NULL; +TaskHandle_t uartProcessTaskHandle = NULL; // 添加串口处理任务句柄 + +#define QUEUE_SIZE 50 +#define TASK_STACK_SIZE 8192 +#define TASK_PRIORITY 1 + +typedef struct { + int heartbeat_waveform; + int breathing_waveform; +} PhaseData; + +typedef struct { + float heart_rate; + float breath_rate; + uint8_t presence; + uint8_t motion; + uint16_t distance; // 人体距离 + uint8_t sleep_state = 3; // 睡眠状态 + uint8_t sleep_score; // 睡眠质量评分 + uint8_t body_movement; // 体动幅度 + uint8_t breath_status; // 呼吸信息 + uint32_t sleep_time; // 睡眠时长 + uint8_t bed_entry; // 入床/离床状态 + uint8_t abnormal_state; // 异常状态 + uint8_t avg_heart_rate; // 平均心率 + uint8_t avg_breath_rate; // 平均呼吸率 + uint8_t turn_count; // 翻身次数 + uint8_t large_move_ratio;// 大幅度体动占比 + uint8_t small_move_ratio;// 小幅度体动占比 + int16_t pos_x; // 人体X坐标 + int16_t pos_y; // 人体Y坐标 + int16_t pos_z; // 人体Z坐标 + uint16_t deep_sleep_time; // 深睡时长 + uint16_t light_sleep_time; // 浅睡时长 + uint16_t awake_time; // 清醒时长 + uint16_t sleep_total_time; // 睡眠总时长 + uint8_t deep_sleep_ratio; // 深睡占比 + uint8_t light_sleep_ratio; // 浅睡占比 + uint8_t awake_ratio; // 清醒占比 + uint8_t turnover_count; // 翻身次数 + uint8_t struggle_alert; // 挣扎报警 + uint8_t no_one_alert; // 无人计时报警 + uint8_t bed_status; // 入床状态 + uint8_t apnea_count; // 呼吸暂停次数 + int heartbeat_waveform; // 心跳波形 + int breathing_waveform; // 呼吸波形 +} VitalData; + +// 睡眠数据结构 - 对应Python脚本中的sleep_data +typedef struct { + uint8_t sleepQualityScore; // 睡眠质量评分 (0~100) + uint16_t totalSleepDuration; // 睡眠总时长 (0~65535 分钟) + uint8_t awakeDurationRatio; // 清醒时长占比 (0~100) + uint8_t lightSleepRatio; // 浅睡时长占比 (0~100) + uint8_t deepSleepRatio; // 深睡时长占比 (0~100) + uint8_t outOfBedDuration; // 离床时长 (0~255) + uint8_t outOfBedCount; // 离床次数 (0~255) + uint8_t turnCount; // 翻身次数 (0~255) + uint8_t avgBreathingRate; // 平均呼吸 (0~25) + uint8_t avgHeartRate; // 平均心跳 (0~100) + uint8_t apneaCount; // 呼吸暂停次数 (0~10) +} SleepData; + +// 添加流量控制类 +class BLEFlowController { +private: + size_t maxBytesPerSecond; + size_t bytesSent; + unsigned long lastResetTime; + unsigned long lastSendTime; + +public: + BLEFlowController(size_t maxBps) : maxBytesPerSecond(maxBps), bytesSent(0) { + lastResetTime = millis(); + lastSendTime = 0; + } + + bool canSend(size_t dataSize) { + unsigned long currentTime = millis(); + + // 每秒重置计数器 + if(currentTime - lastResetTime >= 1000) { + bytesSent = 0; + lastResetTime = currentTime; + } + + // 检查速率限制(放宽限制) + if((bytesSent + dataSize) > maxBytesPerSecond) { + return false; + } + + // 最小发送间隔控制(重要!) + if(currentTime - lastSendTime < 5) { // 进一步减小最小间隔到5ms + return false; + } + + return true; + } + + bool check() { + // 这是一个简化版本的检查方法,只检查最小发送间隔 + unsigned long currentTime = millis(); + if(currentTime - lastSendTime < 5) { // 最小间隔5ms + return false; + } + return true; + } + + void recordSend(size_t dataSize) { + bytesSent += dataSize; + lastSendTime = millis(); + } + + void reset() { + bytesSent = 0; + lastResetTime = millis(); + lastSendTime = 0; + } +}; + +// 全局变量用于控制持续发送 +bool continuousSendEnabled = false; +unsigned long continuousSendInterval = 500; // 默认1秒发送一次 +BLEFlowController bleFlow(500); // 提高到500 B/s限制,进一步提高数据传输速率 + +void configClearLedTask(void *parameter); // 配置清除指示灯控制任务 +void bootButtonMonitorTask(void *parameter); // BOOT按钮监控任务 +void checkBootButton(); // 检查Boot按钮状态 +void clearStoredConfig(); // 清除所有存储的配置 +void ledControlTask(void *parameter); // LED控制任务 +void setNetworkStatus(NetworkStatus status); // 设置网络状态 + +void processBLEConfig(); +bool processWiFiConfigCommand(JsonDocument& doc); +bool processScanWiFi(JsonDocument& doc); +bool processEchoRequest(JsonDocument& doc); +void sendRawEchoResponse(const String& rawData); + +bool parseR60ABD1Frame(uint8_t *frame, uint16_t frameLen); // 解析传感器数据行 +int16_t parseSignedCoordinate(uint16_t raw_value); // 解析有符号坐标值 +void initR60ABD1(); // 初始化R60ABD1雷达 +void sendRadarCommand(uint8_t ctrl, uint8_t cmd, uint8_t value); // 发送雷达命令 + +void wifiMonitorTask(void *parameter); // WiFi监控任务 +void WiFiEvent(WiFiEvent_t event); // WiFi事件处理 +void loadDeviceId(); // 加载设备ID +void saveDeviceId(); // 保存设备ID +bool processSetDeviceId(JsonDocument& doc); // 处理设置设备ID命令 +void sendStatusToBLE(); // 发送状态信息到BLE +bool processQueryStatus(JsonDocument& doc); // 处理查询状态命令 + +// 发送日常数据到InfluxDB - 高频实时监测数据 +void sendDailyDataToInfluxDB(String dailyDataLine); + +// 发送睡眠数据到InfluxDB - 低频汇总数据 +void sendSleepDataToInfluxDB(); + +bool processQueryRadarData(JsonDocument& doc); // 处理查询雷达数据命令 +bool processStartContinuousSend(JsonDocument& doc); // 处理开始持续发送命令 +bool processStopContinuousSend(JsonDocument& doc); // 处理停止持续发送命令 +void sendRadarDataToBLE(); // 发送雷达数据到BLE +void sendDataInChunks(const String& data); // 分包发送函数 +void sendJSONDataToBLE(const String& jsonData); // 发送JSON数据到BLE(使用纯数据分包发送,不带包头) +bool sendCustomJSONData(const String& jsonType, const String& jsonString); // 发送自定义JSON数据到BLE + +// 根据协议ID获取对应的字段名 +String getFieldNameByProtocolId(int protocolId); +// 根据协议ID获取对应的字段名 +String getFieldNameByProtocolId(int protocolId) { + switch(protocolId) { + case PROTOCOL_HEART_RATE: + return "heartRate"; + case PROTOCOL_BREATH_RATE: + return "breathingRate"; + case PROTOCOL_PERSON_DETECTED: + return "personDetected"; + case PROTOCOL_HUMAN_ACTIVITY: + return "humanActivity"; + case PROTOCOL_HUMAN_DISTANCE: + return "humanDistance"; + case PROTOCOL_HUMAN_POSITION: + return "humanPosition"; + case PROTOCOL_SLEEP_STATE: + return "sleepState"; + default: + return "unknown"; + } +} + +// FreeRTOS任务函数声明 +void bleSendTask(void *parameter); +void vitalSendTask(void *parameter); +void radarDataTask(void *parameter); +void uartProcessTask(void *parameter); // 添加串口处理任务声明 + + +// 使用正确的函数签名 - 不带参数的回调函数 +void IRAM_ATTR serialRxCallback() { + // 直接使用全局变量mySerial1和uartQueue + if (uartQueue != NULL) { + // 读取所有可用数据 + while(mySerial1.available()) { + char c = mySerial1.read(); + + // 将字符放入队列(从中断安全函数) + BaseType_t xHigherPriorityTaskWoken = pdFALSE; + xQueueSendFromISR(uartQueue, &c, &xHigherPriorityTaskWoken); + + if(xHigherPriorityTaskWoken) { + portYIELD_FROM_ISR(); + } + } + } +} + +class MyServerCallbacks: public BLEServerCallbacks { + void onConnect(BLEServer* pServer) { + deviceConnected = true; + Serial.println("✅ [BLE] 客户端已连接"); + + // 发送连接状态信息 + sendStatusToBLE(); + }; + + void onDisconnect(BLEServer* pServer) { + deviceConnected = false; + Serial.println("🔴 [BLE] 客户端已断开"); + + // 重置持续发送状态 + continuousSendEnabled = false; + Serial.println("🔄 重置持续发送状态"); + } +}; + +class MyCallbacks: public BLECharacteristicCallbacks { + void onWrite(BLECharacteristic *pCharacteristic) { + std::string value = pCharacteristic->getValue(); + Serial.printf("🔵 [BLE] 收到写入数据,长度: %d 字节\n", value.length()); + + if (value.length() > 0) { + String fragment = ""; + for (int i = 0; i < value.length(); i++) + fragment += value[i]; + + Serial.printf("📄 [BLE] 接收数据片段: %s\n", fragment.c_str()); + + completeData += fragment; + lastReceiveTime = millis(); + + // 检查是否收到完整的JSON数据 + int openBrace = completeData.indexOf('{'); + int closeBrace = completeData.lastIndexOf('}'); + + if (openBrace >= 0 && closeBrace > openBrace) { + String jsonData = completeData.substring(openBrace, closeBrace + 1); + completeData = completeData.substring(closeBrace + 1); + + Serial.printf("📥 [BLE] 完整JSON数据: %s\n", jsonData.c_str()); + + // 将JSON数据存储到receivedData变量中供后续处理 + receivedData = jsonData; + } + } + } +}; + + + +bool processScanWiFi(JsonDocument& doc) { + const char* command = doc["command"]; + if (command != nullptr && strcmp(command, "scanWiFi") == 0) { + Serial.println("📱 [BLE-WiFi] 收到WiFi扫描命令"); + wifiManager.scanAndSendResults(); + return true; + } + return false; +} + +// 处理回显命令 - 将收到的任何数据原样返回 +bool processEchoRequest(JsonDocument& doc) { + const char* command = doc["command"]; + if (command != nullptr && strcmp(command, "echo") == 0) { + Serial.println("📱 [BLE] 收到回显请求"); + + // 获取要回显的内容 + const char* echoContent = doc["content"]; + if (echoContent != nullptr) { + String echoResponse = String("{\"type\":\"echoResponse\",\"originalContent\":\"") + + echoContent + String("\",\"receivedSuccessfully\":true}"); + + if (deviceConnected) { + sendJSONDataToBLE(echoResponse); + Serial.printf("📤 [BLE] 回显响应已发送: %s\n", echoContent); + } + } else { + // 如果没有内容参数,至少回应已收到 + String echoResponse = String("{\"type\":\"echoResponse\",\"receivedSuccessfully\":true,\"message\":\"Echo command received\"}"); + + if (deviceConnected) { + sendJSONDataToBLE(echoResponse); + Serial.println("📤 [BLE] 简单回显响应已发送"); + } + } + + return true; + } + return false; +} + +// 通用回显函数 - 将收到的任何原始数据返回 +void sendRawEchoResponse(const String& rawData) { + if (deviceConnected) { + String echoResponse = String("{\"type\":\"rawEchoResponse\",\"originalData\":\"") + + rawData + String("\",\"received\":true}"); + + sendJSONDataToBLE(echoResponse); + Serial.printf("📤 [BLE] 原始数据回显已发送: %s\n", rawData.c_str()); + } +} + +// 处理查询状态命令 +bool processQueryStatus(JsonDocument& doc) { + const char* command = doc["command"]; + if (command != nullptr && strcmp(command, "queryStatus") == 0) { + if (deviceConnected) { + String statusMsg = String("{\"type\":\"deviceStatus\",\"success\":true,\"deviceId\":") + + String(currentDeviceId) + + String(",\"wifiConfigured\":") + + String(wifiManager.getSavedNetworkCount() > 0 ? "true" : "false") + + String(",\"wifiConnected\":") + + String(WiFi.status() == WL_CONNECTED ? "true" : "false") + + String(",\"ipAddress\":\"") + + (WiFi.status() == WL_CONNECTED ? WiFi.localIP().toString() : "") + + String("\"}"); + + sendJSONDataToBLE(statusMsg); + Serial.println("已发送设备状态信息"); + } + return true; + } + return false; +} + +// 处理查询雷达数据命令(蓝牙) +//这个函数是一个响应式的数据发送函数,仅在接收到查询请求时才会发送当前的雷达传感器数据 +bool processQueryRadarData(JsonDocument& doc) { + const char* command = doc["command"]; + if (command != nullptr && strcmp(command, "queryRadarData") == 0) { + Serial.println("收到查询雷达数据命令"); + + // 发送最新的雷达数据 + if (deviceConnected) { + String radarDataMsg = String("{\"type\":\"radarData\",\"success\":true") + + String(",\"deviceId\":") + String(currentDeviceId) + // 设备ID + String(",\"timestamp\":") + String(millis()) + // 时间戳 + String(",\"presence\":") + String(sensorData.presence) + + String(",\"heartRate\":") + String(sensorData.heart_rate, 1) + + String(",\"breathRate\":") + String(sensorData.breath_rate, 1) + + String(",\"motion\":") + String(sensorData.motion) + + String(",\"heartbeatWaveform\":") + String((int)sensorData.breath_waveform[0]) + + String(",\"breathingWaveform\":") + String((int)sensorData.heart_waveform[0]) + + String(",\"distance\":") + String(sensorData.distance) + + String(",\"bodyMovement\":") + String(sensorData.body_movement) + + String(",\"breathStatus\":") + String(sensorData.breath_status) + + String(",\"sleepState\":") + String(sensorData.sleep_state) + + String(",\"sleepTime\":") + String(sensorData.sleep_time) + + String(",\"sleepScore\":") + String(sensorData.sleep_score) + + String(",\"avgHeartRate\":") + String(sensorData.avg_heart_rate) + + String(",\"avgBreathRate\":") + String(sensorData.avg_breath_rate) + + String(",\"turnCount\":") + String(sensorData.turn_count) + + String(",\"largeMoveRatio\":") + String(sensorData.large_move_ratio) + + String(",\"smallMoveRatio\":") + String(sensorData.small_move_ratio) + + String("}"); + + // 使用新的分包发送函数发送JSON数据 + sendJSONDataToBLE(radarDataMsg); + Serial.println("已发送雷达数据"); + Serial.printf("发送的数据: %s\n", radarDataMsg.c_str()); + } else { + Serial.println("BLE未连接,无法发送雷达数据"); + } + return true; + } + return false; +} + +// 处理开始持续发送命令 +bool processStartContinuousSend(JsonDocument& doc) { + const char* command = doc["command"]; + if (command != nullptr && strcmp(command, "startContinuousSend") == 0) { + // 获取发送间隔参数(可选,默认1000ms) + if (doc["interval"].is()) { + continuousSendInterval = doc["interval"].as(); + // 限制最小间隔为100ms,最大间隔为10000ms + if (continuousSendInterval < 100) continuousSendInterval = 100; + if (continuousSendInterval > 10000) continuousSendInterval = 10000; + } + + continuousSendEnabled = true; + bleFlow.reset(); // 重置流量控制器 + + Serial.printf("⚙️ 启动持续发送模式,间隔: %lu ms\n", continuousSendInterval); + + // 发送JSON格式的确认消息 + if (deviceConnected) { + String confirmMsg = String("{\"type\":\"startContinuousSendResult\",\"success\":true,\"message\":\"已启动持续发送模式\",\"interval\":") + + String(continuousSendInterval) + "}"; + + // 使用新的分包发送函数发送JSON数据 + sendJSONDataToBLE(confirmMsg); + Serial.println("✅ 启动确认消息发送成功"); + + delay(5); // 减少延迟以提高实时性 + Serial.println("🚀 已启动持续发送模式"); + } else { + Serial.println("❌ BLE未连接,无法发送确认消息"); + } + return true; + } + return false; +} + +// 处理停止持续发送命令 +bool processStopContinuousSend(JsonDocument& doc) { + const char* command = doc["command"]; + if (command != nullptr && strcmp(command, "stopContinuousSend") == 0) { + continuousSendEnabled = false; + + Serial.println("🛑 停止持续发送模式"); + + // 发送JSON格式的确认消息 + if (deviceConnected) { + String confirmMsg = String("{\"type\":\"stopContinuousSendResult\",\"success\":true,\"message\":\"已停止持续发送模式\"}"); + + // 使用新的分包发送函数发送JSON数据 + sendJSONDataToBLE(confirmMsg); + Serial.println("✅ 停止确认消息发送成功"); + + delay(5); // 减少延迟以提高实时性 + Serial.println("⏹️ 已停止持续发送模式"); + } else { + Serial.println("❌ BLE未连接,无法发送确认消息"); + } + return true; + } + return false; +} + +// 分包发送函数 - 优化版本,解决分包混乱和数据截断问题 +void sendDataInChunks(const String& data) { + const int MAX_PACKET_SIZE = 20; // BLE最大包大小 + const int HEADER_SIZE = 6; // 包头大小 "[N/M]" + const int CHUNK_SIZE = MAX_PACKET_SIZE - HEADER_SIZE; // 实际数据大小 + + int totalLength = data.length(); + int numChunks = (totalLength + CHUNK_SIZE - 1) / CHUNK_SIZE; + + // 只有在数据较长需要分包时才显示分包信息 + Serial.printf("📦 开始分包发送,总长度: %d, 分包数: %d\n", totalLength, numChunks); + + for(int i = 0; i < numChunks; i++) { + int start = i * CHUNK_SIZE; + int chunkLength = min(CHUNK_SIZE, totalLength - start); + String chunk = data.substring(start, start + chunkLength); + + // 构造简单包头: "[序号/总数]",确保总长度不超过6字符 + // 例如: "[1/5]" = 4字符 + String packetHeader = String("[") + String(i+1) + String("/") + String(numChunks) + String("]"); + + // 确保总包大小不超过20字节 + int maxDataLength = MAX_PACKET_SIZE - packetHeader.length(); + if (chunk.length() > maxDataLength) { + chunk = chunk.substring(0, maxDataLength); + } + + String packet = packetHeader + chunk; + + Serial.printf("📤 发送分包 %s: %s\n", packetHeader.c_str(), chunk.c_str()); + + // 检查BLE连接状态 + if (!deviceConnected) { + Serial.println("❌ BLE未连接,无法发送数据"); + return; + } + + // 发送数据包 + pCharacteristic->setValue(packet.c_str()); + pCharacteristic->notify(); + Serial.println("✅ 分包发送成功"); + + // 等待一段时间确保接收端处理完成,避免数据丢失 + if (i < numChunks - 1) { + Serial.printf("⏳ 等待接收端处理第%d个包...\n", i+1); + vTaskDelay(20 / portTICK_PERIOD_MS); // 非阻塞延时,允许调度 + } + } + + Serial.println("📦 分包发送完成"); +} + +// 新增:发送JSON数据到BLE(使用纯数据分包发送,不带包头) +void sendJSONDataToBLE(const String& jsonData) { + Serial.printf("📤 准备发送JSON数据: %s\n", jsonData.c_str()); + + // 检查BLE连接状态 + if (!deviceConnected) { + Serial.println("❌ BLE未连接,无法发送JSON数据"); + return; + } + + // 直接发送JSON数据,使用纯数据分包(不带包头) + const int MAX_PACKET_SIZE = 20; // BLE最大包大小 + int totalLength = jsonData.length(); + int numChunks = (totalLength + MAX_PACKET_SIZE - 1) / MAX_PACKET_SIZE; + + Serial.printf("📦 开始分包发送JSON,总长度: %d, 分包数: %d\n", totalLength, numChunks); + + for(int i = 0; i < numChunks; i++) { + int start = i * MAX_PACKET_SIZE; + int chunkLength = min(MAX_PACKET_SIZE, totalLength - start); + String chunk = jsonData.substring(start, start + chunkLength); + + Serial.printf("📤 发送JSON分包 %d/%d: %s\n", i+1, numChunks, chunk.c_str()); + + // 发送数据包 + pCharacteristic->setValue(chunk.c_str()); + pCharacteristic->notify(); + Serial.println("✅ JSON分包发送成功"); + + // 等待一段时间确保接收端处理完成,避免数据丢失 + if (i < numChunks - 1) { + Serial.printf("⏳ 等待接收端处理第%d个包...\n", i+1); + vTaskDelay(10 / portTICK_PERIOD_MS); // 非阻塞延时,允许调度 + } + } + + Serial.println("📦 JSON分包发送完成"); +} + +// 新增:发送自定义JSON数据到BLE +bool sendCustomJSONData(const String& jsonType, const String& jsonString) { + if (!deviceConnected) { + Serial.println("❌ BLE未连接,无法发送自定义JSON数据"); + return false; + } + + // 构造完整的JSON数据 + String fullJSON = String("{\"type\":\"") + jsonType + String("\",") + jsonString + String("}"); + + Serial.printf("📤 发送自定义JSON数据类型 '%s': %s\n", jsonType.c_str(), fullJSON.c_str()); + + // 使用纯数据分包发送函数发送JSON数据 + sendJSONDataToBLE(fullJSON); + + return true; +} + +// 发送雷达数据到BLE(已移至FreeRTOS任务处理) +void sendRadarDataToBLE() { + // 此函数已废弃,雷达数据发送现在由FreeRTOS任务处理 + // 保留此函数以防其他代码引用 + Serial.println("ℹ️ 雷达数据发送已移至FreeRTOS任务处理"); +} + +// 处理BLE配置和命令 +void processBLEConfig() { + // 增强超时处理机制 + if (completeData.length() > 0 && (millis() - lastReceiveTime > 3000)) { + Serial.println("⏰ [超时] 数据接收超时3秒,清空缓冲区"); + completeData = ""; + } + + if (receivedData.length() > 0) { + String bleData = receivedData; + receivedData = ""; + bleData.trim(); + + Serial.printf("⚙️ [BLE] 准备解析JSON: %s\n", bleData.c_str()); + + if (bleData.startsWith("{") && bleData.endsWith("}")) { + JsonDocument doc; + DeserializationError error = deserializeJson(doc, bleData); + + if (error) { + String errorMsg = String("❌ [BLE] JSON解析失败: ") + String(error.c_str()); + Serial.println(errorMsg); + if (deviceConnected) { + // 即使JSON解析失败,也发送原始数据的回显 + String responseMsg = String("{\"type\":\"error\",\"message\":\"配置格式错误,请使用JSON格式\",\"originalData\":\"") + bleData + String("\"}"); + + // 使用新的分包发送函数发送JSON数据 + sendJSONDataToBLE(responseMsg); + + // 同时发送原始数据回显 + sendRawEchoResponse(bleData); + } + } else { + Serial.println("✅ [BLE] JSON解析成功"); + + // 处理各种命令 + bool processed = false; + + // 处理设置设备ID命令 + if (!processed) processed = processSetDeviceId(doc); + + // 处理WiFi配置命令 + if (!processed) processed = processWiFiConfigCommand(doc); + + // 处理查询状态命令 + if (!processed) processed = processQueryStatus(doc); + + // 处理查询雷达数据命令 + if (!processed) processed = processQueryRadarData(doc); + + // 处理开始持续发送命令 + if (!processed) processed = processStartContinuousSend(doc); + + // 处理停止持续发送命令 + if (!processed) processed = processStopContinuousSend(doc); + + // 处理WiFi扫描命令 + if (!processed) processed = processScanWiFi(doc); + + // 处理回显请求 + if (!processed) processed = processEchoRequest(doc); + + // 如果没有处理任何命令,发送错误响应 + if (!processed) { + Serial.println("❓[BLE] 未知命令"); + if (deviceConnected) { + String responseMsg = String("{\"type\":\"error\",\"message\":\"未知命令\",\"receivedData\":\"") + bleData + String("\"}"); + + // 使用新的分包发送函数发送JSON数据 + sendJSONDataToBLE(responseMsg); + + // 同时发送原始数据回显 + sendRawEchoResponse(bleData); + } + } else { + // 如果命令被处理了,也发送回显确认 + sendRawEchoResponse(bleData); + } + } + } else { + Serial.println("📥 [BLE] 接收到非JSON数据"); + } + } +} + +// FreeRTOS任务:蓝牙数据发送(精简格式以节省空间) +//主动发送:这是一个持续运行的FreeRTOS任务,不断地从队列中获取数据并主动发送到蓝牙,用于实时数据传输 +void bleSendTask(void *parameter) +{ + Serial.println("🔁 R60ABD1蓝牙数据发送任务启动"); + + while (1) { + PhaseData phaseData; + + // 从队列接收相位数据用于蓝牙发送 + if (xQueueReceive(phaseDataQueue, &phaseData, portMAX_DELAY) == pdTRUE) { + esp_task_wdt_reset(); + + // 蓝牙传输 - 构造并发送完整的R60ABD1雷达数据 + if (deviceConnected && continuousSendEnabled) { + String radarDataCore; + + // 检查是否检测到人 + if (sensorData.presence > 0) + { + // 检测到人时,发送实际数据,包含R60ABD1扩展功能 + radarDataCore = String(sensorData.heart_rate, 1) + String("|") + + String(sensorData.breath_rate, 1) + String("|") + + String((int)sensorData.heart_waveform[0]) + String("|") + + String((int)sensorData.breath_waveform[0]) + String("|") + + String(sensorData.presence) + String("|") + + String(sensorData.motion) + String("|") + + // String(sensorData.distance) + String("|") + // R60ABD1距离 + String(sensorData.sleep_state); // R60ABD1睡眠状态 + // String(sensorData.sleep_time) + String("|") + // R60ABD1睡眠时长 + // String(sensorData.sleep_score); // R60ABD1睡眠评分 + } + else { + // 未检测到人时,发送零数据,但仍然包含实际的RSSI值 + radarDataCore = String("0.0") + String("|") + + String("0.0") + String("|") + + String("0") + String("|") + + String("0") + String("|") + + String("0") + String("|") + + String("0") + String("|") + + // String("0") + String("|") + // 距离 + String("0"); // 睡眠状态 + // String("0") + String("|") + // 睡眠时长 + // String("0"); // 睡眠评分 + } + + // 计算CRC校验和 + unsigned int crc = 0xFFFF; + for (int i = 0; i < radarDataCore.length(); i++) { + crc ^= (unsigned int)radarDataCore.charAt(i); + for (int j = 0; j < 8; j++) { + if (crc & 0x0001) { + crc >>= 1; + crc ^= 0xA001; + } else { + crc >>= 1; + } + } + } + + // 添加校验和到数据末尾 + String radarDataMsg = radarDataCore + String("|") + String(crc, HEX); + + Serial.printf("📤 通过蓝牙发送R60ABD1雷达数据: %s\n", radarDataMsg.c_str()); + + // 检查数据长度决定发送方式 + const int MAX_BLE_PACKET_SIZE = 20; // BLE最大包大小 + if (radarDataMsg.length() <= MAX_BLE_PACKET_SIZE) { + // 数据较短,直接发送 + pCharacteristic->setValue(radarDataMsg.c_str()); + pCharacteristic->notify(); + Serial.println("✅ R60ABD1雷达数据蓝牙发送成功"); + } else { + // 数据较长,使用分包发送 + Serial.println("🔄 R60ABD1雷达数据较长,使用分包发送"); + sendDataInChunks(radarDataMsg); + } + } + + esp_task_wdt_reset(); + } + + vTaskDelay(1 / portTICK_PERIOD_MS); // 减少延迟以提高实时性 + // 在BLE任务循环中重置看门狗 + esp_task_wdt_reset(); + } +} +// FreeRTOS任务:发送生命体征数据到数据库 +void vitalSendTask(void *parameter) { + Serial.println("🔁🔁 生命体征数据发送任务启动(WiFi数据库传输)"); + + // 记录上次发送睡眠数据的时间 + unsigned long lastSleepDataTime = 0; + const unsigned long SLEEP_DATA_INTERVAL = 5000; // 5秒 = 5000毫秒 + + while (1) { + VitalData vitalData; + + // 从队列接收生命体征数据用于WiFi数据库发送 + if (xQueueReceive(vitalDataQueue, &vitalData, portMAX_DELAY) == pdTRUE) { + esp_task_wdt_reset(); + + // WiFi数据库传输 - 按照Python脚本的双模块架构发送数据 + if (WiFi.status() == WL_CONNECTED) { + // 发送日常数据(高频实时监测数据)- daily_data + // 构造日常数据行协议 + String dailyDataLine = "daily_data,deviceId=" + String(currentDeviceId) + ",dataType=daily "; + + // 添加各个实时监测字段 + bool firstField = true; + + if (vitalData.heart_rate > 0) { + if (!firstField) dailyDataLine += ","; + dailyDataLine += "heartRate=" + String(vitalData.heart_rate, 1);// 心率 + firstField = false; + } + + if (vitalData.breath_rate > 0) { + if (!firstField) dailyDataLine += ","; + dailyDataLine += "breathingRate=" + String(vitalData.breath_rate, 1);// 呼吸率 + firstField = false; + } + + if (!firstField) dailyDataLine += ","; + dailyDataLine += "personDetected=" + String(vitalData.presence) + "i";// 人检 + firstField = false; + + if (!firstField) dailyDataLine += ","; + dailyDataLine += "humanActivity=" + String(vitalData.motion) + "i";// 活动 + firstField = false; + + if (vitalData.distance > 0) { + if (!firstField) dailyDataLine += ","; + dailyDataLine += "humanDistance=" + String(vitalData.distance) + "i";// 距离 + firstField = false; + } + + if (vitalData.sleep_state >= 0) { + if (!firstField) dailyDataLine += ","; + dailyDataLine += "sleepState=" + String(vitalData.sleep_state) + "i";// 睡眠状态 + firstField = false; + } + + // 添加人体坐标数据 (DP5) - X坐标 + if (!firstField) dailyDataLine += ","; + dailyDataLine += "humanPositionX=" + String(vitalData.pos_x) + "i";// X坐标 + firstField = false; + + // 添加人体坐标数据 (DP5) - Y坐标 + if (!firstField) dailyDataLine += ","; + dailyDataLine += "humanPositionY=" + String(vitalData.pos_y) + "i";// Y坐标 + firstField = false; + + // 添加人体坐标数据 (DP5) - Z坐标 + if (!firstField) dailyDataLine += ","; + dailyDataLine += "humanPositionZ=" + String(vitalData.pos_z) + "i";// Z坐标 + firstField = false; + + // 添加波形数据 + if (!firstField) dailyDataLine += ","; + dailyDataLine += "heartbeatWaveform=" + String((int)sensorData.heart_waveform[0]) + "i";// 心跳波形 + firstField = false; + + if (!firstField) dailyDataLine += ","; + dailyDataLine += "breathingWaveform=" + String((int)sensorData.breath_waveform[0]) + "i";// 呼吸波形 + firstField = false; + + // 添加异常状态 + if (!firstField) dailyDataLine += ","; + dailyDataLine += "abnormalState=" + String(vitalData.abnormal_state) + "i";// 异常状态 + firstField = false; + + // 添加入床状态 + if (!firstField) dailyDataLine += ","; + dailyDataLine += "bedStatus=" + String(vitalData.bed_status) + "i";// 入床状态 + firstField = false; + + // 挣扎警报和无人警报 + if (!firstField) dailyDataLine += ","; + dailyDataLine += "struggleAlert=" + String(vitalData.struggle_alert) + "i";// 挣扎警报 + firstField = false; + + if (!firstField) dailyDataLine += ","; + dailyDataLine += "noOneAlert=" + String(vitalData.no_one_alert) + "i";// 无人警报 + firstField = false; + + // 发送日常数据到数据库 + if (!dailyDataLine.endsWith(" ")) { // 确保有数据要发送 + sendDailyDataToInfluxDB(dailyDataLine); + esp_task_wdt_reset(); + delay(10); + } + + // 检查是否需要发送睡眠数据(低频汇总数据)- sleep_data + unsigned long currentTime = millis(); + if (currentTime - lastSleepDataTime >= SLEEP_DATA_INTERVAL) { + sendSleepDataToInfluxDB(); + lastSleepDataTime = currentTime; + Serial.println("⏰ 睡眠数据定时发送完成"); + } + } + } else { + Serial.println("❌❌ WiFi未连接,无法发送雷达数据到数据库"); + + // 定期打印WiFi状态以便调试 + static unsigned long lastWifiCheck = 0; + if (millis() - lastWifiCheck > 10000) { + Serial.printf("📶📶 WiFi状态: %d, 已配置: %s\n", + WiFi.status(), wifiManager.getSavedNetworkCount() > 0 ? "是" : "否"); + lastWifiCheck = millis(); + } + } + + esp_task_wdt_reset(); + } + + vTaskDelay(10 / portTICK_PERIOD_MS); // 适当延迟 + } + + +// 发送日常数据到InfluxDB - 高频实时监测数据 +void sendDailyDataToInfluxDB(String dailyDataLine) { + if (WiFi.status() != WL_CONNECTED) { + Serial.println("❌ WiFi未连接,无法发送日常数据到数据库"); + return; + } + + HTTPClient http; + http.setTimeout(2000); + + String url = String("http://") + String(influxDBHost) + ":" + String(influxDBPort) + "/api/v2/write?org=" + String(influxDBOrg) + "&bucket=" + String(influxDBBucket); + + http.begin(url); + http.addHeader("Authorization", String("Token ") + String(influxDBToken)); + http.addHeader("Content-Type", "text/plain; charset=utf-8"); + http.setReuse(true); + + Serial.println(String("📊 发送日常数据到InfluxDB: ") + dailyDataLine); + + int httpResponseCode = http.POST(dailyDataLine);// 发送日常数据到InfluxDB + + if (httpResponseCode == 204) { + Serial.println("✅ 日常数据发送成功"); + } else { + Serial.println(String("❌ 发送日常数据失败: ") + String(httpResponseCode) + " - " + http.getString()); + } + + http.end(); +} + +// 发送睡眠数据到InfluxDB - 模仿Python脚本的低频发送模式 +//这个函数是专门用于发送睡眠数据到InfluxDB数据库的 +void sendSleepDataToInfluxDB() { + if (WiFi.status() != WL_CONNECTED) { + Serial.println("❌ WiFi未连接,无法发送睡眠数据到数据库"); + return; + } + + // 检查总睡眠时长是否为0,如果为0则不上传数据 + if (sensorData.sleep_total_time == 0) { + Serial.println("😴 总睡眠时长为0,跳过上传睡眠数据"); + return; + } + + HTTPClient http; + http.setTimeout(2000); + + String url = String("http://") + String(influxDBHost) + ":" + String(influxDBPort) + "/api/v2/write?org=" + String(influxDBOrg) + "&bucket=" + String(influxDBBucket); + + http.begin(url); + http.addHeader("Authorization", String("Token ") + String(influxDBToken)); + http.addHeader("Content-Type", "text/plain; charset=utf-8"); + http.setReuse(true); + + // 构造睡眠数据的行协议格式 + String lineProtocol = String("sleep_data,deviceId=") + String(currentDeviceId) + ",dataType=sleep "; + + // 按顺序添加各个字段 - 构建睡眠分析数据 + String fields = ""; + fields += String("sleepQualityScore=") + String((int)sensorData.sleep_score) + "i";//睡眠评分 + fields += ",sleepQualityGrade=" + String((int)sensorData.sleep_grade) + "i";//睡眠质量评级 + fields += ",totalSleepDuration=" + String((int)sensorData.sleep_total_time) + "i";// 总睡眠时长 + fields += ",awakeDurationRatio=" + String((int)sensorData.awake_ratio) + "i";// 清醒时长占比 + fields += ",lightSleepRatio=" + String((int)sensorData.light_sleep_ratio) + "i";// 浅睡时长占比 + fields += ",deepSleepRatio=" + String((int)sensorData.deep_sleep_ratio) + "i";// 深睡时长占比 + fields += ",outOfBedDuration=" + String((int)sensorData.bed_Out_Time) + "i";// 离床时长 + fields += ",outOfBedCount=" + String((int)sensorData.turn_count) + "i";// 离床次数 + fields += ",turnCount=" + String((int)sensorData.turnover_count) + "i";// 转身次数 + fields += ",avgBreathingRate=" + String((int)sensorData.avg_breath_rate) + "i";// 平均呼吸率 + fields += ",avgHeartRate=" + String((int)sensorData.avg_heart_rate) + "i";// 平均心率 + fields += ",apneaCount=" + String((int)sensorData.apnea_count) + "i";// 呼吸暂停次数 + fields += ",abnormalState=" + String((int)sensorData.abnormal_state) + "i";// 睡眠异常状态 + fields += ",bodyMovement=" + String((int)sensorData.body_movement) + "i";// 体动幅度 + fields += ",breathStatus=" + String((int)sensorData.breath_status) + "i";// 呼吸状态 + fields += ",sleepState=" + String((int)sensorData.sleep_state) + "i";// 睡眠状态 + fields += ",largeMoveRatio=" + String((int)sensorData.large_move_ratio) + "i";// 大幅度体动占比 + fields += ",smallMoveRatio=" + String((int)sensorData.small_move_ratio) + "i";// 小幅度体动占比 + fields += ",struggleAlert=" + String((int)sensorData.struggle_alert) + "i";// 挣扎警报 + fields += ",noOneAlert=" + String((int)sensorData.no_one_alert) + "i";// 无人警报 + fields += ",awakeDuration=" + String((int)sensorData.awake_time) + "i";// 清醒时长 + fields += ",lightSleepDuration=" + String((int)sensorData.light_sleep_time) + "i";// 浅睡时长 + fields += ",deepSleepDuration=" + String((int)sensorData.deep_sleep_time) + "i";// 深睡时长 + // // 添加波形数组数据(前3个点) + // fields += ",breathWaveform1=" + String((int)sensorData.breath_waveform[0]) + "i";// 呼吸波形点1 + // fields += ",breathWaveform2=" + String((int)sensorData.breath_waveform[1]) + "i";// 呼吸波形点2 + // fields += ",breathWaveform3=" + String((int)sensorData.breath_waveform[2]) + "i";// 呼吸波形点3 + // fields += ",heartWaveform1=" + String((int)sensorData.heart_waveform[0]) + "i";// 心率波形点1 + // fields += ",heartWaveform2=" + String((int)sensorData.heart_waveform[1]) + "i";// 心率波形点2 + // fields += ",heartWaveform3=" + String((int)sensorData.heart_waveform[2]) + "i";// 心率波形点3 + + lineProtocol += fields; + + Serial.println(String("🌙 发送睡眠数据到InfluxDB: ") + lineProtocol); + + int httpResponseCode = http.POST(lineProtocol);// 发送睡眠数据到InfluxDB + + if (httpResponseCode == 204) { + Serial.println(String("✅ 睡眠数据已保存到InfluxDB设备") + String(currentDeviceId) + "上"); + } else { + Serial.println(String("❌ 保存睡眠数据到InfluxDB失败: ") + String(httpResponseCode) + " - " + http.getString()); + } + + http.end(); +} + +// 雷达数据处理任务 - 高优先级 +void radarDataTask(void *parameter) { + Serial.println("🔁 雷达数据处理任务启动(最高优先级)"); + + while (1) { + // 雷达数据处理已移至专用任务处理,这里不需要做任何事情 + // 串口数据现在由uartProcessTask处理 + + // 短暂延迟以允许其他任务运行 + vTaskDelay(100 / portTICK_PERIOD_MS); + } +} + + +// 修改串口处理任务以支持二进制协议 +//存储数据:将解析后的数据分别存储到两个结构体中:vitalData,phaseData +void uartProcessTask(void *parameter) { + uint8_t buffer[256]; // 接收缓冲区 + int bufferIndex = 0; + bool inFrame = false; + uint8_t prevByte = 0; + + Serial.println("✅ R60ABD1串口数据处理任务启动"); + + while(1) { + uint8_t c; + // 从队列接收字节(最多等待10ms) + if(xQueueReceive(uartQueue, &c, 10 / portTICK_PERIOD_MS) == pdTRUE) { + // 重置看门狗,防止超时 + esp_task_wdt_reset(); + if(!inFrame) { + // 寻找帧头 + if(prevByte == FRAME_HEADER1 && c == FRAME_HEADER2) { + // 找到帧头,开始接收帧 + buffer[0] = FRAME_HEADER1; + buffer[1] = FRAME_HEADER2; + bufferIndex = 2; + inFrame = true; + Serial.println("🔍 检测到R60ABD1帧头"); + } + } else { + // 在帧中接收数据 + if(bufferIndex < sizeof(buffer)) { + buffer[bufferIndex++] = c; + + // 检查是否到达帧尾 + if(bufferIndex >= 8 && // 至少包含基本帧头信息 + buffer[bufferIndex-2] == FRAME_TAIL1 && + buffer[bufferIndex-1] == FRAME_TAIL2) { + // 完整帧接收完成,进行解析 + if(parseR60ABD1Frame(buffer, bufferIndex)) { + // 数据解析成功,更新统计 + static uint32_t frameCounter = 0; + frameCounter++; + + // 更新全局传感器数据 + lastSensorUpdate = millis(); + + // 使用FreeRTOS队列发送数据到任务 + static uint32_t phasePacketCounter = 0; + static uint32_t vitalPacketCounter = 0; + + phasePacketCounter++; + if (phasePacketCounter >= PHASE_SEND_INTERVAL) { + PhaseData phaseData; + phaseData.heartbeat_waveform = sensorData.heartbeat_waveform;// 心率波形数据 + phaseData.breathing_waveform = sensorData.breathing_waveform;// 呼吸波形数据 + + if (xQueueSend(phaseDataQueue, &phaseData, 0) == pdTRUE) { + // 成功发送到队列 + } else { + Serial.println("❌ 相位数据队列已满,数据丢失"); + } + phasePacketCounter = 0; + } + + vitalPacketCounter++; + if (vitalPacketCounter >= VITAL_SEND_INTERVAL) { + VitalData vitalData; + vitalData.heart_rate = sensorData.heart_rate;// 心率数据 + vitalData.breath_rate = sensorData.breath_rate;// 呼吸率数据 + vitalData.presence = sensorData.presence;// 人员检测数据 + vitalData.motion = sensorData.motion;// 运动数据 + vitalData.distance = sensorData.distance;// 距离数据 + vitalData.sleep_state = sensorData.sleep_state;// 睡眠状态数据 + vitalData.sleep_score = sensorData.sleep_score;// 睡眠评分数据(无) + vitalData.body_movement = sensorData.body_movement;// 体动数据 + vitalData.breath_status = sensorData.breath_status;// 呼吸状态数据(去) + vitalData.sleep_time = sensorData.sleep_time;// 睡眠时间数据 + vitalData.bed_status = sensorData.bed_status;//入床状态数据 + vitalData.abnormal_state = sensorData.abnormal_state;// 异常状态数据 + vitalData.avg_heart_rate = sensorData.avg_heart_rate;// 平均心率数据 + vitalData.avg_breath_rate = sensorData.avg_breath_rate;// 平均呼吸率数据 + vitalData.turn_count = sensorData.turn_count;// 离床次数数据 + vitalData.large_move_ratio = sensorData.large_move_ratio;// 大动作比例数据 + vitalData.small_move_ratio = sensorData.small_move_ratio;// 小动作比例数据 + vitalData.pos_x = sensorData.pos_x;// X坐标数据 + vitalData.pos_y = sensorData.pos_y;// Y坐标数据 + vitalData.pos_z = sensorData.pos_z;// Z坐标数据 + vitalData.deep_sleep_time = sensorData.deep_sleep_time;// 深睡时间数据 + vitalData.light_sleep_time = sensorData.light_sleep_time;// 浅睡时间数据 + vitalData.awake_time = sensorData.awake_time;// 唤醒时间数据 + vitalData.sleep_total_time = sensorData.sleep_total_time;// 睡眠总时间数据(去) + vitalData.deep_sleep_ratio = sensorData.deep_sleep_ratio;// 深睡比例数据(去) + vitalData.light_sleep_ratio = sensorData.light_sleep_ratio;// 浅睡比例数据(去) + vitalData.awake_ratio = sensorData.awake_ratio;// 清醒比例数据(去) + vitalData.turnover_count = sensorData.turnover_count;// 离床次数数据(去) + vitalData.struggle_alert = sensorData.struggle_alert;// 挤压警报数据 + vitalData.no_one_alert = sensorData.no_one_alert;// 无人警报数据 + vitalData.apnea_count = sensorData.apnea_count;// 呼吸暂停次数数据(去) + vitalData.heartbeat_waveform = sensorData.heartbeat_waveform;// 心率波形数据 + vitalData.breathing_waveform = sensorData.breathing_waveform;// 呼吸波形数据 + + if (xQueueSend(vitalDataQueue, &vitalData, 0) == pdTRUE) { + Serial.println("📤 生命体征数据已加入发送队列"); + } else { + Serial.println("❌ 生命体征数据队列已满,数据丢失"); + } + vitalPacketCounter = 0; + } + + if(frameCounter % 100 == 0) { + Serial.printf("📈 已处理 %d 个R60ABD1数据帧\n", frameCounter); + } + + } + + // 重置状态,准备接收下一帧 + inFrame = false; + } + } else { + // 缓冲区溢出,重置状态 + Serial.println("⚠️ R60ABD1帧缓冲区溢出,重置接收状态"); + inFrame = false; + } + } + + prevByte = c; + } + + // 允许其他任务运行 + vTaskDelay(1 / portTICK_PERIOD_MS); + // 在循环中定期重置看门狗 + esp_task_wdt_reset(); + } +} + +// 替换现有的parseSensorLine函数 +bool parseR60ABD1Frame(uint8_t *frame, uint16_t frameLen) { + if(frameLen < 8) return false; // 最小帧长度检查 + + // 验证帧头和帧尾 + if(frame[0] != FRAME_HEADER1 || frame[1] != FRAME_HEADER2 || + frame[frameLen-2] != FRAME_TAIL1 || frame[frameLen-1] != FRAME_TAIL2) { + return false; + } + + // 验证校验和 + uint8_t checksum = 0; + for(int i = 0; i < frameLen-3; i++) { // 不包括校验和字节和帧尾 + checksum += frame[i]; + // 在长循环中重置看门狗,防止超时 + if(i % 50 == 0) { + esp_task_wdt_reset(); + } + } + if(checksum != frame[frameLen-3]) { // 校验和在倒数第三个字节 + Serial.println("❌ R60ABD1帧校验和错误"); + return false; + } + + for(int i = 0; i < frameLen && i < 20; i++) { // 显示前20个字节 + Serial.printf("%02X ", frame[i]); + } + if(frameLen > 20) Serial.print("... "); + Serial.printf("| 校验:0x%02X\n", frame[frameLen-3]); + + // 提取帧内容 + uint8_t ctrlByte = frame[2]; // 控制字 + uint8_t cmdByte = frame[3]; // 命令字 + uint16_t dataLen = (frame[4] << 8) | frame[5]; // 数据长度 + + // 根据控制字和命令字处理不同类型的数据 + switch(ctrlByte) + { + case CTRL_PRESENCE: + // 根据技术文档,0x80控制字的不同命令字对应不同的数据点 + switch(cmdByte) + { + // 开关人体存在监测 + case 0x00: + case 0x80: + if(dataLen >= 1) { + if(frame[6] == 0x01) { + Serial.println("🔄 人体存在监测功能已开启"); + sendRadarCommand(0x84, 0x00, 0x01); // 睡眠监测 + delay(50); + } else { + Serial.println("🔄 人体存在监测功能已关闭"); + } + } + break; + + // DP1: 存在信息主动上报 + case 0x01: + if(dataLen >= 1) { + sensorData.presence = frame[6]; // 0:无人, 1:有人 + Serial.printf("👤 人体存在: %s\n", sensorData.presence ? "有人" : "无人"); + } + break; + + // DP2: 运动信息上报 + case 0x02: + if(dataLen >= 1) { + sensorData.motion = frame[6]; // 0:无, 1:静止, 2:活跃 + const char* states[] = {"无", "静止", "活跃"}; + Serial.printf("🏃 运动状态: %s\n", states[sensorData.motion]); + } + break; + + // DP3: 体动参数 + case 0x03: + if(dataLen >= 1) { + sensorData.body_movement = frame[6]; // 0-100 + Serial.printf("📊体动参数: %d\n", sensorData.body_movement); + } + break; + + // DP4: 人体距离 + case 0x04: + if(dataLen >= 2) { + sensorData.distance = ((uint16_t)frame[6] << 8) | frame[7]; // 单位:厘米 + Serial.printf("📏人体距离: %d cm\n", sensorData.distance); + } + break; + + // DP5: 人体方位坐标 - 修正版 + case 0x05: + if(dataLen >= 6) { + // 6字节: X(2B), Y(2B), Z(2B) + // 从frame[6]开始是数据域 + + // 解析X坐标 + uint16_t x_raw = ((uint16_t)frame[6] << 8) | frame[7]; + sensorData.pos_x = parseSignedCoordinate(x_raw); + + // 解析Y坐标 + uint16_t y_raw = ((uint16_t)frame[8] << 8) | frame[9]; + sensorData.pos_y = parseSignedCoordinate(y_raw); + + // 解析Z坐标 + uint16_t z_raw = ((uint16_t)frame[10] << 8) | frame[11]; + sensorData.pos_z = parseSignedCoordinate(z_raw); + + // 调试输出 + Serial.printf("📍方位坐标 - 原始: X=0x%04X, Y=0x%04X, Z=0x%04X\n", + x_raw, y_raw, z_raw); + Serial.printf(" 解析后: X=%d, Y=%d, Z=%d cm\n", + sensorData.pos_x, sensorData.pos_y, sensorData.pos_z); + } + break; + + default: + Serial.printf("❓未知的0x80命令字: 0x%02X\n", cmdByte); + break; + } + break; + + case CTRL_BREATH: + // 根据技术文档,0x81控制字的不同命令字对应不同的呼吸相关数据点 + switch(cmdByte) { + // 开关呼吸监测功能 + case 0x00: + case 0x80: + if(dataLen >= 1) { + if(frame[6] == 0x01) + Serial.println("🔄 呼吸监测功能已开启"); + else + Serial.println("🔄 呼吸监测功能已关闭"); + } + break; + + // DP9: 呼吸信息 + case 0x01: + if(dataLen >= 1) { + sensorData.breath_status = frame[6]; + const char* info_str[] = {"", "正常", "呼吸过高(>25)", "呼吸过低(<10)", "无"}; + if(sensorData.breath_status >= 1 && sensorData.breath_status <= 4) { + Serial.printf("🔍 呼吸信息: %s\n", info_str[sensorData.breath_status]); + } else { + Serial.printf("🔍 呼吸信息: 未知状态(0x%02X)\n", sensorData.breath_status); + } + } + break; + + // DP8: 呼吸数值上报 + case 0x02: + if(dataLen >= 1) { + sensorData.breath_rate = (float)frame[6]; // 0-35次/分钟 + sensorData.breath_valid = (sensorData.breath_rate >= 0.0f && + sensorData.breath_rate <= 35.0f); + Serial.printf("💨 呼吸率: %.1f 次/分\n", sensorData.breath_rate); + } + break; + + // DP10: 呼吸波形 + case 0x05: + if(dataLen >= 5) { + // 1秒上报5个点,每个点需要减去128 + for(int i = 0; i < 5 && i < dataLen; i++) { + sensorData.breath_waveform[i] = (int8_t)(frame[6+i] - 128); + } + // 示例:打印第一个点 + Serial.printf("📈 呼吸波形: %d\n", sensorData.breath_waveform[0]); + } + break; + + default: + Serial.printf("❓未知的0x81命令字: 0x%02X\n", cmdByte); + break; + } + break; + + case CTRL_HEARTRATE: + // 根据技术文档,0x85控制字的不同命令字对应不同的心率相关数据点 + switch(cmdByte) { + // 开关心率监测功能 + case 0x00: + case 0x80: + if(dataLen >= 1) { + if(frame[6] == 0x01) + Serial.println("🔄 心率监测功能已开启"); + else + Serial.println("🔄 心率监测功能已关闭"); + } + break; + // DP6: 心跳数值 + case 0x02: + if(dataLen >= 1) { + sensorData.heart_rate = (float)frame[6]; // 60-120次/分钟 + sensorData.heart_valid = (sensorData.heart_rate >= 60.0f && + sensorData.heart_rate <= 120.0f); + Serial.printf("❤️ 心率: %.1f 次/分\n", sensorData.heart_rate); + } + break; + + // DP7: 心率波形 + case 0x05: + if(dataLen >= 5) { + // 1秒上报5个点,每个点需要减去128 + for(int i = 0; i < 5 && i < dataLen; i++) { + sensorData.heart_waveform[i] = (int8_t)(frame[6+i] - 128); + } + Serial.printf("📈 心率波形: %d\n", sensorData.heart_waveform[0]); + } + break; + + default: + Serial.printf("❓未知的0x85命令字: 0x%02X\n", cmdByte); + break; + } + break; + + case CTRL_SLEEP: + // 根据技术文档,0x84控制字的不同命令字对应不同的睡眠相关数据点 + switch(cmdByte) { + // DP10: 开关睡眠监测功能 + case 0x00: + case 0x80: + if(dataLen >= 1) { + if(frame[6] == 0x01) + Serial.println("🔄 睡眠监测功能已开启"); + else + Serial.println("🔄 睡眠监测功能已关闭"); + } + break; + + // DP11: 入床/离床状态 + case 0x01: + case 0x81: + if(dataLen >= 1) { + sensorData.bed_status = frame[6]; // 0x00:离床, 0x01:入床, 0x02:无 + const char* status_str[] = {"离床", "入床", "无"}; + Serial.printf("🛏️ 床状态: %s\n", status_str[sensorData.bed_status]); + } + break; + + // DP13: 清醒时长 (2字节,单位:分钟) + case 0x03: + case 0x83: + if(dataLen >= 2) { + sensorData.awake_time = ((uint16_t)frame[6] << 8) | (uint16_t)frame[7]; + Serial.printf("⏰ 清醒时长: %d 分钟\n", sensorData.awake_time); + } + break; + + // DP14: 浅睡时长 (2字节,单位:分钟) + case 0x04: + case 0x84: + if(dataLen >= 2) { + sensorData.light_sleep_time = ((uint16_t)frame[6] << 8) | (uint16_t)frame[7]; + Serial.printf("😪 浅睡时长: %d 分钟\n", sensorData.light_sleep_time); + } + break; + + // DP15: 深睡时长 (2字节,单位:分钟) + case 0x05: + case 0x85: + if(dataLen >= 2) { + sensorData.deep_sleep_time = ((uint16_t)frame[6] << 8) | (uint16_t)frame[7]; + Serial.printf("💤 深睡时长: %d 分钟\n", sensorData.deep_sleep_time); + } + break; + + // DP16: 睡眠质量评分 (1字节,0-100分) + case 0x06: + //case 0x86: + if(dataLen >= 1) { + sensorData.sleep_score = frame[6]; + Serial.printf("⭐ 睡眠质量评分: %d 分\n", sensorData.sleep_score); + } + break; + + case 0x86: + if(dataLen >= 2) { + //sensorData.sleep_score = ((uint16_t)frame[6] << 8) | (uint16_t)frame[7]; + sensorData.sleep_score = frame[6]; + Serial.printf("⭐ 睡眠质量评分: %d 分\n", sensorData.sleep_score); + } + break; + + + + // DP17: 睡眠综合状态 (8字节) + case 0x0C: + case 0x8D: + if(dataLen >= 8) { + sensorData.presence = frame[6]; // 存在状态: 1有人, 0无人 + sensorData.sleep_state = frame[7]; // 睡眠状态: 3离床, 2清醒, 1浅睡, 0深睡 + sensorData.avg_breath_rate = frame[8]; // 平均呼吸率 + sensorData.avg_heart_rate = frame[9]; // 平均心率 + sensorData.turnover_count = frame[10]; // 翻身次数 + sensorData.large_move_ratio = frame[11]; // 大幅度体动占比(0-100) + sensorData.small_move_ratio = frame[12]; // 小幅度体动占比(0-100) + sensorData.apnea_count = frame[13]; // 呼吸暂停次数 + + Serial.printf("📊 睡眠综合状态 - 存在:%d, 睡眠状态:%d, 睡眠平均呼吸:%d, 睡眠平均心率:%d, 翻身次数:%d, 大动占比:%d%%, 小动占比:%d%%, 呼吸暂停次数:%d\n", + sensorData.presence, sensorData.sleep_state, + sensorData.avg_breath_rate, sensorData.avg_heart_rate, + sensorData.turnover_count, sensorData.large_move_ratio, + sensorData.small_move_ratio, sensorData.apnea_count); + } + break; + + // DP18: 睡眠质量分析报告 (12字节) + case 0x0D: + case 0x8F: + if(dataLen >= 12) { + sensorData.sleep_score = frame[6]; // 1B 睡眠评分 + sensorData.sleep_total_time = ((uint16_t)frame[7] << 8) | (uint16_t)frame[8]; // 2B 总时长(分钟) + + // 修正:以下三项为1字节的百分比,非2字节的绝对时长 + sensorData.awake_ratio = frame[9]; // 1B 清醒时长占比 + sensorData.light_sleep_ratio = frame[10]; // 1B 浅睡时长占比 + sensorData.deep_sleep_ratio = frame[11]; // 1B 深睡时长占比 + + sensorData.bed_Out_Time = frame[12]; // 1B 离床时长 + sensorData.turn_count = frame[13]; // 1B 离床次数 + sensorData.turnover_count = frame[14]; // 1B 翻身次数 + sensorData.avg_breath_rate = frame[15]; // 1B 平均呼吸率 + sensorData.avg_heart_rate = frame[16]; // 1B 平均心跳 + sensorData.apnea_count = frame[17]; // 1B 呼吸暂停次数 + + // 打印日志也应相应修改 + Serial.printf("📈 睡眠分析报告 - 评分:%d, 总时长:%d分, 清醒占比:%d%%, 浅睡占比:%d%%, 深睡占比:%d%%, 离床时长:%d, 离床次数:%d, 翻身:%d次, 平均呼吸:%d, 平均心跳:%d\n", + sensorData.sleep_score, + sensorData.sleep_total_time, + sensorData.awake_ratio, + sensorData.light_sleep_ratio, + sensorData.deep_sleep_ratio, + sensorData.bed_Out_Time, + sensorData.turn_count, + sensorData.turnover_count, + sensorData.avg_breath_rate, + sensorData.avg_heart_rate); + } + break; + + // DP19: 睡眠异常上报 + case 0x0E: + case 0x8E: + if(dataLen >= 1) { + sensorData.abnormal_state = frame[6]; + const char* abnormal_str[] = { + "睡眠时长不足4小时", "睡眠时长大于12小时", "长时间异常无人" + }; + if(sensorData.abnormal_state < 3) { + Serial.printf("⚠️ 睡眠异常: %s\n", abnormal_str[sensorData.abnormal_state]); + } + } + break; + + // DP20: 睡眠质量评级 + case 0x10: + case 0x90: + if(dataLen >= 1) { + sensorData.sleep_grade = frame[6]; + const char* rating_str[] = {"无", "睡眠质量良好", "睡眠质量一般", "睡眠质量较差"}; + if(sensorData.sleep_grade < 4) { + Serial.printf("🏆 睡眠质量评级: %s\n", rating_str[sensorData.sleep_grade]); + } + } + break; + + // DP21: 异常挣扎状态 + case 0x11: + case 0x91: + if(dataLen >= 1) { + sensorData.struggle_alert = frame[6]; // 0x00:无, 0x01:正常, 0x02:异常挣扎 + const char* struggle_str[] = {"无", "正常", "异常挣扎"}; + if(sensorData.struggle_alert < 3) { + Serial.printf("⚠️ 挣扎状态: %s\n", struggle_str[sensorData.struggle_alert]); + } + } + break; + + // DP22: 无人计时状态 + case 0x12: + case 0x92: + if(dataLen >= 1) { + sensorData.no_one_alert = frame[6]; // 0x00:无, 0x01:正常, 0x02:异常 + const char* no_one_str[] = {"无", "正常", "异常"}; + if(sensorData.no_one_alert < 3) { + Serial.printf("⏰ 无人计时状态: %s\n", no_one_str[sensorData.no_one_alert]); + } + } + break; + + default: + Serial.printf("❓未知的0x84命令字: 0x%02X\n", cmdByte); + break; + } + break; + + // case 0x01: // 心跳包标识 + // Serial.println("💓 心跳包"); + // break; + + case 0x07: // 雷达探测范围信息 + if(dataLen >= 1) { + if(frame[6] == 0x00) + Serial.println("雷达探测范围外"); + else + Serial.println("雷达探测范围内"); + } + break; + + default: + Serial.printf("❓未知控制字: 0x%02X\n", ctrlByte); + break; + } + + + + + // 更新传感器时间戳 + lastSensorUpdate = millis(); + + // 验证数据有效性 + sensorData.heart_valid = (sensorData.heart_rate > 0 && sensorData.heart_rate < 200); + sensorData.breath_valid = (sensorData.breath_rate >= 0.1f && sensorData.breath_rate <= 60.0f); + + if( sensorData.heart_valid ==1 && sensorData.heart_valid == 1 && presence_Bit == 1 ) + { + sensorData.presence = 1; + presence_Bit = 0; + } + + return true; +} + +// 解析有符号坐标值 +int16_t parseSignedCoordinate(uint16_t raw_value) { + // 方法1:手动解析符号位 + bool is_negative = (raw_value & 0x8000) != 0; // 检查最高位 + uint16_t magnitude = raw_value & 0x7FFF; // 取低15位数值 + + int16_t result = (int16_t)magnitude; + if (is_negative) { + result = -result; // 如果是负数,加上负号 + } + + return result; +} + +// 修正后的发送雷达命令函数 +void sendRadarCommand(uint8_t ctrl, uint8_t cmd, uint8_t value) { + uint8_t command[10]; + command[0] = 0x53; // 帧头1 + command[1] = 0x59; // 帧头2 + command[2] = ctrl; // 控制字 + command[3] = cmd; // 命令字 + command[4] = 0x00; // 长度高字节 + command[5] = 0x01; // 长度低字节 + command[6] = value; // 数据 + + // 修正校验和计算:帧头(2) + 控制字(1) + 命令字(1) + 长度(2) + 数据(1) = 前7个字节 + uint8_t checksum = 0; + for(int i = 0; i < 7; i++) { // 计算前7个字节的和 + checksum += command[i]; + } + command[7] = checksum; // 校验码 + + // 帧尾 + command[8] = 0x54; // 帧尾1 + command[9] = 0x43; // 帧尾2 + + // 发送命令 + mySerial1.write(command, 10); + + // 调试输出 + Serial.printf("📤 发送: "); + for(int i = 0; i < 10; i++) { + Serial.printf("%02X ", command[i]); + } + Serial.println(); + + Serial.printf(" 控制字=0x%02X, 命令字=0x%02X, 值=0x%02X, 校验和=0x%02X\n", + ctrl, cmd, value, checksum); +} + +// 处理设置设备ID命令 +bool processSetDeviceId(JsonDocument& doc) { + const char* command = doc["command"]; + if (command != nullptr && strcmp(command, "setDeviceId") == 0) { + // 处理设置设备ID的命令 + uint16_t newDeviceId = doc["newDeviceId"]; + + //验证设备ID范围 + if (newDeviceId < MIN_DEVICE_ID || newDeviceId > MAX_DEVICE_ID){ + Serial.printf("[错误] 设备ID超出范围,有效范围: %d-%d\n", MIN_DEVICE_ID, MAX_DEVICE_ID); + if (deviceConnected) { + String errorMsg = String("{\"type\":\"error\",\"message\":\"设备ID超出范围,有效范围: ") + + String(MIN_DEVICE_ID) + "-" + String(MAX_DEVICE_ID) + "\"}"; + + // 使用新的分包发送函数发送JSON数据 + sendJSONDataToBLE(errorMsg); + } + return true; // 已处理该命令 + } + + currentDeviceId = newDeviceId; + + Serial.printf("[设备ID] 已设置新的设备ID: %u\n", currentDeviceId); + + // 保存设备ID到Preferences + saveDeviceId(); + + // 发送确认消息 + if (deviceConnected) { + String confirmMsg = String("{\"type\":\"setDeviceIdResult\",\"success\":true,\"message\":\"设备ID设置成功\",\"newDeviceId\":") + + String(newDeviceId) + "}"; + + // 使用新的分包发送函数发送JSON数据 + sendJSONDataToBLE(confirmMsg); + + // 发送更新后的状态信息 + sendStatusToBLE(); + } + return true; + } + return false; +} + +// 加载设备ID +void loadDeviceId() { + currentDeviceId = preferences.getUShort("deviceId", 1001);// 从Flash加载设备ID + Serial.printf("从Flash加载设备ID: %u\n", currentDeviceId); +} + +// 保存设备ID +void saveDeviceId() { + preferences.putUShort("deviceId", currentDeviceId);// 保存设备ID到Flash + Serial.printf("设备ID已保存到Flash: %u\n", currentDeviceId); +} + +// 发送状态信息到BLE +void sendStatusToBLE() { + if (deviceConnected) { + String statusMsg = String("{\"type\":\"status\",\"wifiConfigured\":") + + String(wifiManager.getSavedNetworkCount() > 0 ? "true" : "false") + + String(",\"wifiConnected\":") + + String(WiFi.status() == WL_CONNECTED ? "true" : "false") + + String(",\"ipAddress\":\"") + + (WiFi.status() == WL_CONNECTED ? WiFi.localIP().toString() : "") + "\"" + + String(",\"deviceId\":") + + String(currentDeviceId) + "}"; + + sendJSONDataToBLE(statusMsg); + Serial.println("已发送连接状态信息"); + } +} + +bool processWiFiConfigCommand(JsonDocument& doc) { + const char* command = doc["command"]; + if (command != nullptr && strcmp(command, "setWiFiConfig") == 0) { + Serial.println("📱 [BLE-WiFi] 收到WiFi配置命令"); + const char* newSSID = doc["ssid"]; + const char* newPassword = doc["password"]; + + if (newSSID != nullptr && newPassword != nullptr) { + return wifiManager.handleConfigurationData(newSSID, newPassword); + } else { + Serial.println("❌ [BLE-WiFi] WiFi配置参数不完整"); + if (deviceConnected) { + String errorMsg = String("{\"type\":\"error\",\"message\":\"WiFi配置参数不完整,需要ssid和password字段\"}"); + sendJSONDataToBLE(errorMsg); + } + return true; + } + } + return false; +} + +// 在setup()函数中添加R60ABD1初始化 +void initR60ABD1() { + Serial.println("🔧 初始化R60ABD1雷达模组..."); + + // 发送查询指令以激活数据上报 + Serial.println("📡 发送查询指令以激活数据上报..."); + // 查询存在信息: 53 59 80 81 00 01 00 7D 54 43 + uint8_t queryPresenceCmd[] = {0x53, 0x59, 0x80, 0x81, 0x00, 0x01, 0x00, 0x7D, 0x54, 0x43}; + mySerial1.write(queryPresenceCmd, sizeof(queryPresenceCmd)); + + // 1. 确认开启核心功能 + Serial.println("📡 开启核心监测功能..."); + // 发送多次人体存在监测开启命令以确保生效 + + sendRadarCommand(0x80, 0x00, 0x01); // 人体存在 + delay(50); + sendRadarCommand(0x81, 0x00, 0x01); // 呼吸监测 + delay(50); + sendRadarCommand(0x85, 0x00, 0x01); // 心率监测 + delay(50); + sendRadarCommand(0x84, 0x00, 0x01); // 睡眠监测 + delay(50); + + // 2. 波形数据开启(需要确认命令字) + Serial.println("📡 尝试开启波形数据..."); + sendRadarCommand(0x81, 0x0C, 0x01); // 呼吸波形 + delay(50); + sendRadarCommand(0x85, 0x0A, 0x01); // 心率波形 + delay(50); + + //确认开启特殊功能 + + sendRadarCommand(0x84, 0x13, 0x01); // 异常挣扎状态开关设置 + delay(50); + sendRadarCommand(0x84, 0x14, 0x01); // 无人计时功能开关设置 + delay(50); + + // 3. 验证初始化结果 + Serial.println("🔍 查询当前状态..."); + sendRadarCommand(0x80, 0x80, 0x0F); // 查询人体存在状态 + delay(50); + sendRadarCommand(0x81, 0x80, 0x0F); // 查询呼吸监测状态 + delay(50); + sendRadarCommand(0x85, 0x80, 0x0F); // 查询心率监测状态 + delay(50); + sendRadarCommand(0x84, 0x80, 0x0F); // 查询睡眠监测状态 + + Serial.println("✅ R60ABD1雷达初始化完成"); + Serial.println("📋 串口将输出解析后的雷达数据,包括:\n - 生命体征数据(心率、呼吸率)\n - 睡眠监测数据(状态、评分、时长)\n - 人体检测数据(存在、距离、运动)\n - 活动监测数据(体动幅度、翻身次数)"); +} + +// 检查Boot按钮状态 +void checkBootButton() { + Serial.println("🔍 检查Boot按钮状态..."); + + // 配置Boot引脚为输入模式(内部上拉) + pinMode(BOOT_BUTTON_PIN, INPUT_PULLUP); + + // 短暂延时确保引脚稳定 + delay(10); + + int buttonState = digitalRead(BOOT_BUTTON_PIN); + Serial.printf("📊 Boot按钮状态: %s\n", buttonState == LOW ? "按下" : "释放"); + + if (buttonState == LOW) { + // 检测到Boot按钮按下,设置指示灯为准备清除状态 + currentConfigClearStatus = CONFIG_PREPARING; + Serial.println("⚠️ 检测到Boot按钮按下,长按3秒将清除配置"); + Serial.println("⏰ 倒计时开始..."); + + // 检测长按3秒 + bootButtonPressTime = millis(); + while (digitalRead(BOOT_BUTTON_PIN) == LOW) { + // 打印倒计时 + unsigned long pressedTime = millis() - bootButtonPressTime; + unsigned long remaining = (CLEAR_CONFIG_DURATION - pressedTime) / 1000; + + if (remaining <= 3 && remaining > 0) { + Serial.printf("⏳ 继续按住 %lu 秒将清除配置...\n", remaining); + } + + if (pressedTime >= CLEAR_CONFIG_DURATION) { + clearConfigRequested = true; + Serial.println("✅ 长按3秒确认,将清除配置"); + // 设置指示灯为清除过程中状态(呼吸灯) + currentConfigClearStatus = CONFIG_CLEARING; + break; + } + + delay(1000); // 每秒检查一次 + } + + if (!clearConfigRequested) { + // 按钮释放,恢复正常状态 + currentConfigClearStatus = CONFIG_NORMAL; + Serial.println("❌ 按钮释放,取消清除操作"); + } + } else { + Serial.println("✅ Boot按钮未按下,正常启动"); + } +} + +void clearStoredConfig() { + Serial.println("🧹 开始清除存储的配置..."); + + uint16_t oldDeviceId = preferences.getUShort("deviceId", 0); + + preferences.remove("deviceId"); + preferences.remove("wifi_first"); + + wifiManager.clearAllConfigs(); + + Serial.println("✅ 配置已清除完成"); + Serial.printf("🗑️ 被清除的设备ID: %u\n", oldDeviceId); + + currentDeviceId = 1001; + WiFi_Connect_First_bit = 1; + + WiFi.disconnect(true); + setNetworkStatus(NET_DISCONNECTED); + + Serial.println("🔄 已清除Flash与内存中的配置,请重新配置WiFi和设备ID"); + + if (deviceConnected) { + sendStatusToBLE(); + } +} + + +// 配置清除指示灯控制任务 +void configClearLedTask(void *parameter) { + while (1) { + switch (currentConfigClearStatus) { + case CONFIG_NORMAL: // 正常运行 - LOW + analogWrite(CONFIG_CLEAR_PIN, 0); // 关闭LED + break; + + case CONFIG_PREPARING: // 准备清除 - HIGH + analogWrite(CONFIG_CLEAR_PIN, 255); // 开启LED + break; + + case CONFIG_CLEARING: // 清除过程中 - 呼吸灯效果 + if (millis() - lastConfigBlinkTime >= BREATHE_INTERVAL) { + // 呼吸灯效果 + analogWrite(CONFIG_CLEAR_PIN, configBreatheValue); + + // 更新呼吸灯值 + if (configBreatheIncreasing) { + configBreatheValue += 5; // 使用固定步进值 + if (configBreatheValue >= BREATHE_MAX) { + configBreatheValue = BREATHE_MAX; + configBreatheIncreasing = false; + } + } else { + configBreatheValue -= 5; // 使用固定步进值 + if (configBreatheValue <= BREATHE_MIN) { + configBreatheValue = BREATHE_MIN; + configBreatheIncreasing = true; + } + } + lastConfigBlinkTime = millis(); + } + break; + + case CONFIG_COMPLETED: // 清除完成 - 快速闪烁3次 + if (millis() - lastConfigBlinkTime >= FAST_BLINK_INTERVAL) { + configLedState = !configLedState; + digitalWrite(CONFIG_CLEAR_PIN, configLedState ? HIGH : LOW); + lastConfigBlinkTime = millis(); + + // 计算闪烁次数并切换回正常状态 + static int blinkCount = 0; + blinkCount++; + + if (blinkCount >= 6) { // 闪烁3次 (HIGH-LOW为1次) + blinkCount = 0; + currentConfigClearStatus = CONFIG_NORMAL; + digitalWrite(CONFIG_CLEAR_PIN, LOW); // 确保回到LOW状态 + } + } + break; + } + + vTaskDelay(10 / portTICK_PERIOD_MS); + } +} + +// BOOT按钮监控任务 +void bootButtonMonitorTask(void *parameter) { + Serial.println("🔍 启动BOOT按钮监控任务..."); + + // 配置Boot引脚为输入模式(内部上拉) + pinMode(BOOT_BUTTON_PIN, INPUT_PULLUP); + + unsigned long buttonPressStartTime = 0; + bool buttonPressed = false; + + while (1) { + int buttonState = digitalRead(BOOT_BUTTON_PIN); + + if (buttonState == LOW && !buttonPressed) { + // 按钮刚被按下 + buttonPressed = true; + buttonPressStartTime = millis(); + Serial.println("⚠️ 检测到BOOT按钮按下,长按3秒将清除配置"); + + // 设置指示灯为准备清除状态 + currentConfigClearStatus = CONFIG_PREPARING; + } + else if (buttonState == HIGH && buttonPressed) { + // 按钮被释放 + if (!clearConfigRequested) { + // 如果还没有确认清除,则取消操作 + currentConfigClearStatus = CONFIG_NORMAL; + Serial.println("❌ 按钮释放,取消清除操作"); + } + buttonPressed = false; + } + + // 检查是否长按了3秒 + if (buttonPressed && (millis() - buttonPressStartTime >= CLEAR_CONFIG_DURATION)) { + if (!clearConfigRequested) { + clearConfigRequested = true; + analogWrite(NETWORK_LED_PIN, 0); // 关闭网络状态LED + Serial.println("✅ 长按3秒确认,将清除配置"); + + // 设置指示灯为清除过程中状态(呼吸灯) + //currentConfigClearStatus = CONFIG_CLEARING; + + // 清除配置 + clearStoredConfig(); + + Serial.println("🔄 配置清除完成,LED将闪烁3次表示完成..."); + + // 设置指示灯为清除完成状态(快速闪烁3次) + // currentConfigClearStatus = CONFIG_COMPLETED; + + // 等待闪烁完成 + // while(currentConfigClearStatus == CONFIG_COMPLETED) { + // vTaskDelay(100 / portTICK_PERIOD_MS); + // esp_task_wdt_reset(); + // } + analogWrite(CONFIG_CLEAR_PIN, 0); // 关闭LED + // analogWrite(NETWORK_LED_PIN, 0); // 关闭网络状态LED + Serial.println("🔄 系统即将重启..."); + + // 短暂延迟后重启 + vTaskDelay(1000 / portTICK_PERIOD_MS); + ESP.restart(); + } + } + + vTaskDelay(50 / portTICK_PERIOD_MS); // 每50ms检查一次 + + // 重置看门狗 + esp_task_wdt_reset(); + } +} + +// LED控制任务 +void ledControlTask(void *parameter) { + while (1) { + switch (currentNetworkStatus) { + case NET_INITIAL: // 未连接 - 慢闪 + case NET_DISCONNECTED: // 断开连接 - 慢闪 + if (millis() - lastBlinkTime >= SLOW_BLINK_INTERVAL) { + ledState = !ledState; + if(ledState) { + ledcWrite(0, 255); // 设置为最大亮度 + } else { + ledcWrite(0, 0); // 关闭LED + } + lastBlinkTime = millis(); + } + break; + + case NET_CONNECTING: // 连接中 - 快闪 + if (millis() - lastBlinkTime >= FAST_BLINK_INTERVAL) { + ledState = !ledState; + if(ledState) { + ledcWrite(0, 255); // 设置为最大亮度 + } else { + ledcWrite(0, 0); // 关闭LED + } + lastBlinkTime = millis(); + } + break; + + case NET_CONNECTED: // 已连接 - 呼吸灯效果 + if (millis() - lastBlinkTime >= BREATHE_INTERVAL) { + // 呼吸灯效果 + ledcWrite(0, breatheValue); // 使用ledcWrite替代analogWrite + + // 更新呼吸灯值 + if (breatheIncreasing) { + breatheValue += BREATHE_STEP; + if (breatheValue >= BREATHE_MAX) { + breatheValue = BREATHE_MAX; + breatheIncreasing = false; + } + } else { + breatheValue -= BREATHE_STEP; + if (breatheValue <= BREATHE_MIN) { + breatheValue = BREATHE_MIN; + breatheIncreasing = true; + } + } + lastBlinkTime = millis(); + } + break; + } + + vTaskDelay(10 / portTICK_PERIOD_MS); + } +} + +// 设置网络状态 +void setNetworkStatus(NetworkStatus status) { + currentNetworkStatus = status; + + // 切换到呼吸灯模式时,重置呼吸灯参数 + if (status == NET_CONNECTED) { + breatheValue = BREATHE_MIN; + breatheIncreasing = true; + } +} + +// WiFi事件处理 +void WiFiEvent(WiFiEvent_t event) { + switch (event) { + case ARDUINO_EVENT_WIFI_STA_START: + setNetworkStatus(NET_INITIAL); + break; + + case ARDUINO_EVENT_WIFI_STA_CONNECTED: + setNetworkStatus(NET_CONNECTING); + break; + + case ARDUINO_EVENT_WIFI_STA_GOT_IP: + setNetworkStatus(NET_CONNECTED); + break; + + case ARDUINO_EVENT_WIFI_STA_DISCONNECTED: + setNetworkStatus(NET_DISCONNECTED); + break; + + case ARDUINO_EVENT_WIFI_STA_STOP: + setNetworkStatus(NET_DISCONNECTED); + break; + } +} + +void wifiMonitorTask(void *parameter) { + Serial.println("📡 WiFi监控任务启动"); + + while(1) { + wifiManager.update(); + vTaskDelay(500 / portTICK_PERIOD_MS); + } +} +void setup() { + Serial.begin(115200); + + // 第一步:检查Boot按钮是否被按下 + checkBootButton(); + + // 确保结束后恢复到正常状态 + analogWrite(CONFIG_CLEAR_PIN, 0); // 关闭呼吸灯 + Serial.println("稳定期结束,开始工作。"); + + // 如果请求清除配置,这里会先执行清除操作 + if (clearConfigRequested) { + clearStoredConfig(); + // 清除后重启系统 + Serial.println("🔄 系统即将重启..."); + delay(1000); + ESP.restart(); + } + + // 添加启动信息 + Serial.println("🚀 ESP32-R60ABD1系统启动"); + Serial.println("🔧 初始化系统组件..."); + + // 初始化GPIO模式 + pinMode(BOOT_BUTTON_PIN, INPUT); // Boot按钮引脚,输入模式,不启用内部上拉 + pinMode(NETWORK_LED_PIN, OUTPUT); // 网络状态LED,输出模式 + pinMode(CONFIG_CLEAR_PIN, OUTPUT); // 配置清除指示灯,输出模式 + pinMode(GPIO8, OUTPUT); // 自定义GPIO8,输出模式 + pinMode(GPIO9, OUTPUT); // 自定义GPIO9,输出模式 + + // 初始化配置清除指示灯为LOW(正常运行状态) + digitalWrite(CONFIG_CLEAR_PIN, LOW); + digitalWrite(GPIO8, LOW); + digitalWrite(GPIO9, LOW); + // 初始状态设置 + digitalWrite(NETWORK_LED_PIN, LOW); // 网络状态LED,输出模式,输出低电平 + digitalWrite(CONFIG_CLEAR_PIN, LOW); + + // 设置PWM通道用于呼吸灯效果 + ledcSetup(0, 5000, 8); // PWM通道0, 5kHz, 8位分辨率 + ledcSetup(1, 5000, 8); // PWM通道1, 5kHz, 8位分辨率 + ledcAttachPin(NETWORK_LED_PIN, 0); // 网络状态LED使用通道0 + ledcAttachPin(CONFIG_CLEAR_PIN, 1); // 配置清除LED使用通道1 + + // 注册WiFi事件 + WiFi.onEvent(WiFiEvent); + + + // 设置初始网络状态 + setNetworkStatus(NET_INITIAL); + + esp_task_wdt_init(30, true); // 初始化任务看门狗,30秒超时 + esp_task_wdt_add(NULL); + + // 增加串口缓冲区大小以处理R60ABD1数据 + mySerial1.setRxBufferSize(UART_RX_BUFFER_SIZE); + mySerial1.begin(BAUD_RATE, SERIAL_8N1, UART1_RX, UART1_TX); + + Serial.println("UART1配置完成,缓冲区大小: 4096字节"); + + preferences.begin("radar_data", false); + + wifiManager.begin(); + + Serial.println("💾 加载设备配置..."); + loadDeviceId(); + + Serial.println("🏗️ 创建FreeRTOS队列..."); + // 创建FreeRTOS队列 + phaseDataQueue = xQueueCreate(QUEUE_SIZE, sizeof(PhaseData)); + vitalDataQueue = xQueueCreate(QUEUE_SIZE, sizeof(VitalData)); + uartQueue = xQueueCreate(2048, sizeof(char)); // 创建串口数据队列 + + if (phaseDataQueue == NULL || vitalDataQueue == NULL || uartQueue == NULL) { + Serial.println("❌ 队列创建失败"); + } else { + Serial.println("✅ FreeRTOS队列创建成功"); + } + + // 设置串口中断回调函数 + mySerial1.onReceive(serialRxCallback); + Serial.println("✅ 串口中断回调已设置"); + + Serial.println("🏃 创建FreeRTOS任务..."); + // 创建FreeRTOS任务 + xTaskCreatePinnedToCore( + bleSendTask, + "BleSendTask", + TASK_STACK_SIZE, + NULL, + 3, // 提高优先级到3 + &bleSendTaskHandle, + 1 + ); + + xTaskCreatePinnedToCore( + vitalSendTask, + "VitalSendTask", + TASK_STACK_SIZE, + NULL, + 2, // 中等优先级 + &vitalSendTaskHandle, + 1 + ); + + // 创建专门的雷达数据处理任务 + xTaskCreatePinnedToCore( + radarDataTask, + "RadarProcessTask", + 4096, + NULL, + 4, // 最高优先级 + NULL, + 1 + ); + + // 创建R60ABD1串口数据处理任务 + xTaskCreatePinnedToCore( + uartProcessTask, // R60ABD1串口处理任务 + "UartProcessTask", + 4096, + NULL, + 5, // 最高优先级 + &uartProcessTaskHandle, + 1 + ); + + // 创建配置清除指示灯控制任务 + xTaskCreate( + configClearLedTask, // 任务函数 + "Config Clear LED Task", // 任务名称 + 2048, // 堆栈大小 + NULL, // 参数 + 1, // 优先级 + NULL // 任务句柄 + ); + + // 创建BOOT按钮监控任务 + xTaskCreate( + bootButtonMonitorTask, // 任务函数 + "Boot Button Monitor Task", // 任务名称 + 2048, // 堆栈大小 + NULL, // 参数 + 1, // 优先级 + NULL // 任务句柄 + ); + + // 创建LED控制任务 + xTaskCreate( + ledControlTask, // 任务函数 + "LED Control Task", // 任务名称 + 2048, // 堆栈大小 + NULL, // 参数 + 1, // 优先级 + NULL // 任务句柄 + ); + + // 创建WiFi监控任务 + xTaskCreate( + wifiMonitorTask, // 任务函数 + "WiFi Monitor Task", // 任务名称 + 4096, // 堆栈大小 + NULL, // 参数 + 2, // 优先级 + NULL // 任务句柄 + ); + + Serial.println("✅ FreeRTOS任务创建成功"); + + Serial.println("📶 初始化BLE服务..."); + String deviceName = "Radar_" + String(currentDeviceId); + BLEDevice::init(deviceName.c_str()); + pServer = BLEDevice::createServer(); + pServer->setCallbacks(new MyServerCallbacks()); + + BLEService *pService = pServer->createService(SERVICE_UUID); + pCharacteristic = pService->createCharacteristic( + CHARACTERISTIC_UUID, + BLECharacteristic::PROPERTY_READ | + BLECharacteristic::PROPERTY_WRITE | + BLECharacteristic::PROPERTY_NOTIFY + ); + pCharacteristic->setCallbacks(new MyCallbacks()); + pCharacteristic->addDescriptor(new BLE2902()); + + pService->start(); + BLEAdvertising *pAdvertising = BLEDevice::getAdvertising(); + pAdvertising->addServiceUUID(SERVICE_UUID); + pAdvertising->setScanResponse(true); + pAdvertising->setMinPreferred(0x06); + pAdvertising->setMinPreferred(0x12); + BLEDevice::startAdvertising(); + + Serial.println(String("BLE已启动,设备名称: Radar_") + String(currentDeviceId)); + + Serial.println("🌐 检查WiFi配置..."); + if (wifiManager.getSavedNetworkCount() > 0) { + Serial.printf("💾 检测到 %d 个已保存的WiFi配置,尝试连接...\n", wifiManager.getSavedNetworkCount()); + if (wifiManager.initializeWiFi()) { + Serial.println("✅ WiFi连接成功!"); + } else { + Serial.println("❌ WiFi连接失败,请通过BLE重新配置"); + } + } else { + Serial.println("⚠️ 未检测到WiFi配置,请通过BLE进行网络配置"); + } + + // 从Flash加载WiFi首次连接标志(模仿getString行为:若Flash无该键,则保持当前变量值不变) + size_t wifi_first_len = preferences.getBytes("wifi_first", &WiFi_Connect_First_bit, sizeof(WiFi_Connect_First_bit)); + if (wifi_first_len == sizeof(WiFi_Connect_First_bit)) { + Serial.printf("从Flash读取 WiFi_Connect_First_bit: %u\n", WiFi_Connect_First_bit); + } else { + Serial.println("Flash中无 wifi_first 条目,保留内存中原始值"); + } + + if(WiFi_Connect_First_bit == 0) + { + unsigned long wifiWaitStart = millis(); + unsigned long lastWifiWaitPrint = 0; + const unsigned long WIFI_WAIT_TIMEOUT = 15000; // 最多等待15秒 + // 使用非阻塞方式等待WiFi连接,期间允许任务调度运行 + while (WiFi.status() != WL_CONNECTED && (millis() - wifiWaitStart) < WIFI_WAIT_TIMEOUT) { + if (millis() - lastWifiWaitPrint >= 1000) { + Serial.println("等待WiFi连接..."); + lastWifiWaitPrint = millis(); + } + yield(); + vTaskDelay(10 / portTICK_PERIOD_MS); + } + } + + // 系统初始化函数 + Serial.println("WiFi连接成功!"); + // 初始化R60ABD1雷达模组 + initR60ABD1(); // 调用R60ABD1初始化函数 + + Serial.println("🎉 系统初始化完成,等待雷达数据..."); + + // 启动时发送一次睡眠数据 + if (WiFi.status() == WL_CONNECTED) { + Serial.println("🌅 启动时发送睡眠数据到数据库"); + sendSleepDataToInfluxDB(); + } +} + +void loop() { + esp_task_wdt_reset(); + + if (!deviceConnected && oldDeviceConnected) { + vTaskDelay(500 / portTICK_PERIOD_MS); + pServer->startAdvertising(); + Serial.println("开始BLE广播"); + oldDeviceConnected = deviceConnected; + } + if (deviceConnected && !oldDeviceConnected) { + oldDeviceConnected = deviceConnected; + } + // 使用非阻塞方式轮询发送一系列查询命令,间隔2000ms + { + static const uint8_t radar_cmds[][3] = { + {0x84, 0x81, 0x0F}, // 入床/离床状态查询 + {0x84, 0x8D, 0x0F}, // 睡眠综合状态查询 + {0x84, 0x8F, 0x0F}, // 睡眠统计查询 + {0x84, 0x8E, 0x0F}, // 睡眠异常查询 + {0x84, 0x91, 0x0F}, // 异常挣扎查询 + {0x84, 0x92, 0x0F}, // 无人计时查询 + {0x84, 0x83, 0x0F}, // 清醒时长查询 + {0x84, 0x84, 0x0F}, // 浅睡时长查询 + {0x84, 0x85, 0x0F}, // 深睡时长查询 + {0x84, 0x86, 0x0F}, // 睡眠质量评分查询 + {0x84, 0x90, 0x0F} // 睡眠质量评级 + }; + + const size_t cmdCount = sizeof(radar_cmds) / sizeof(radar_cmds[0]); + static size_t cmdIndex = 0; + static unsigned long lastCmdMillis = 0; + const unsigned long CMD_INTERVAL = 2000UL; // 2秒 + + unsigned long now = millis(); + if (now - lastCmdMillis >= CMD_INTERVAL) { + // 发送当前命令 + sendRadarCommand(radar_cmds[cmdIndex][0], radar_cmds[cmdIndex][1], radar_cmds[cmdIndex][2]); + lastCmdMillis = now; + cmdIndex++; + if (cmdIndex >= cmdCount) cmdIndex = 0; + } + } + + processBLEConfig(); + esp_task_wdt_reset(); + esp_task_wdt_reset(); + // updateStatusFlags(); + + // 在主循环中添加更频繁的看门狗重置 + esp_task_wdt_reset(); + delay(1); +} \ No newline at end of file diff --git a/src/radar_manager.cpp b/src/radar_manager.cpp new file mode 100644 index 0000000..a6c38ab --- /dev/null +++ b/src/radar_manager.cpp @@ -0,0 +1,1727 @@ +#include "radar_manager.h" +#include "wifi_manager.h" +#include +#include + +extern uint16_t currentDeviceId; // 当前设备ID +extern Preferences preferences; // Flash存储对象 +extern WiFiManager wifiManager; // WiFi管理器对象 + +const int BAUD_RATE = 115200; // 串口波特率 +const int UART1_RX = 3; // UART1接收引脚 +const int UART1_TX = 2; // UART1发送引脚 + +const uint32_t PHASE_SEND_INTERVAL = 1; // 相位数据发送间隔(毫秒) +const uint32_t VITAL_SEND_INTERVAL = 10; // 生命体征数据发送间隔(毫秒) + +const unsigned long SENSOR_TIMEOUT = 40000; // 传感器超时时间(毫秒) +static uint32_t packetCounter = 0; // 数据包计数器 +static bool shouldSendOtherData = false; // 是否发送其他数据标志 + +unsigned long lastSleepDataTime = 0; // 上次发送睡眠数据时间 +const unsigned long SLEEP_DATA_INTERVAL = 5000; // 睡眠数据发送间隔(毫秒) + +const char* influxDBHost = "8.134.11.76"; // InfluxDB服务器地址 +const int influxDBPort = 8086; // InfluxDB服务器端口 +const char* influxDBToken = "KuTa5ZsqoHIhi2IglOO06zExUYw1_mJ6K0mcA9X1y6O6CJDog3_Cgr8mUw1SwpuCCKRElqxa6wAhrrhsYPytkg=="; // InfluxDB访问令牌 +const char* influxDBOrg = "gzlg"; // InfluxDB组织名称 +const char* influxDBBucket = "gzlg"; // InfluxDB存储桶名称 + +uint8_t presence_Bit = 1; // 存在标志位 + +SensorData sensorData; // 传感器数据结构体 +HardwareSerial mySerial1(1); // 硬件串口1对象 + +QueueHandle_t phaseDataQueue; // 相位数据队列句柄 +QueueHandle_t vitalDataQueue; // 生命体征数据队列句柄 +QueueHandle_t uartQueue; // UART数据队列句柄 +TaskHandle_t bleSendTaskHandle = NULL; // BLE发送任务句柄 +TaskHandle_t vitalSendTaskHandle = NULL; // 生命体征发送任务句柄 +TaskHandle_t uartProcessTaskHandle = NULL; // UART处理任务句柄 + +BLEServer* pServer = NULL; // BLE服务器指针 +BLECharacteristic* pCharacteristic = NULL; // BLE特征值指针 + +bool deviceConnected = false; // 设备连接状态 +bool oldDeviceConnected = false; // 旧设备连接状态 +String receivedData = ""; // 接收到的数据 +String completeData = ""; // 完整数据 +unsigned long lastReceiveTime = 0; // 上次接收数据时间 + +bool continuousSendEnabled = false; // 持续发送使能标志 +unsigned long continuousSendInterval = 500; // 持续发送间隔(毫秒) +BLEFlowController bleFlow(500); // BLE流控制器对象 + +unsigned long lastSensorUpdate = 0; // 上次传感器更新时间 +LastSentData lastSentData = {0}; // 上次发送的数据,初始化为0 +unsigned long lastCheckTime = 0; // 上次检测时间,初始化为0 + +/** + * @brief BLE流控制器构造函数 + * 初始化BLE数据流控制参数,限制数据发送速率 + * @param maxBps 最大每秒发送字节数 + */ +BLEFlowController::BLEFlowController(size_t maxBps) : maxBytesPerSecond(maxBps), bytesSent(0) { + lastResetTime = millis(); + lastSendTime = 0; +} + +/** + * @brief 检查是否可以发送数据 + * 检查当前是否满足发送条件,包括速率限制和时间间隔 + * @param dataSize 要发送的数据大小 + * @return 是否可以发送数据 + */ +bool BLEFlowController::canSend(size_t dataSize) { + unsigned long currentTime = millis(); + + if(currentTime - lastResetTime >= 1000) { + bytesSent = 0; + lastResetTime = currentTime; + } + + if((bytesSent + dataSize) > maxBytesPerSecond) { + return false; + } + + if(currentTime - lastSendTime < 5) { + return false; + } + + return true; +} + +/** + * @brief 检查发送时间间隔 + * 检查距离上次发送是否满足最小时间间隔 + * @return 是否满足发送条件 + */ +bool BLEFlowController::check() { + unsigned long currentTime = millis(); + if(currentTime - lastSendTime < 5) { + return false; + } + return true; +} + +/** + * @brief 记录数据发送 + * 更新已发送字节数和最后发送时间 + * @param dataSize 已发送的数据大小 + */ +void BLEFlowController::recordSend(size_t dataSize) { + bytesSent += dataSize; + lastSendTime = millis(); +} + +/** + * @brief 重置流控制器 + * 重置已发送字节数和时间戳 + */ +void BLEFlowController::reset() { + bytesSent = 0; + lastResetTime = millis(); + lastSendTime = 0; +} + +/** + * @brief BLE服务器连接回调 + * 当客户端连接时触发 + * @param pServer BLE服务器指针 + */ +void MyServerCallbacks::onConnect(BLEServer* pServer) { + deviceConnected = true; + Serial.println("✅ [BLE] 客户端已连接"); +} + +/** + * @brief BLE服务器断开连接回调 + * 当客户端断开连接时触发 + * @param pServer BLE服务器指针 + */ +void MyServerCallbacks::onDisconnect(BLEServer* pServer) { + deviceConnected = false; + Serial.println("🔴 [BLE] 客户端已断开"); + continuousSendEnabled = false; + Serial.println("🔄 重置持续发送状态"); +} + +/** + * @brief BLE特征值写入回调 + * 当客户端写入数据时触发 + * @param pCharacteristic BLE特征值指针 + */ +void MyCallbacks::onWrite(BLECharacteristic *pCharacteristic) { + std::string value = pCharacteristic->getValue(); + Serial.printf("🔵 [BLE] 收到写入数据,长度: %d 字节\n", value.length()); + + if (value.length() > 0) { + String fragment = ""; + for (int i = 0; i < value.length(); i++) + fragment += value[i]; + + Serial.printf("📄 [BLE] 接收数据片段: %s\n", fragment.c_str()); + + completeData += fragment; + lastReceiveTime = millis(); + + int openBrace = completeData.indexOf('{'); + + if (openBrace >= 0) { + int depth = 0; + int closeBrace = -1; + + for (int i = openBrace; i < completeData.length(); i++) { + char c = completeData.charAt(i); + if (c == '{') { + depth++; + } else if (c == '}') { + depth--; + if (depth == 0) { + closeBrace = i; + break; + } + } + } + + if (closeBrace > openBrace) { + String jsonData = completeData.substring(openBrace, closeBrace + 1); + completeData = completeData.substring(closeBrace + 1); + + Serial.printf("📥 [BLE] 完整JSON数据: %s\n", jsonData.c_str()); + receivedData = jsonData; + } + } + } +} + +/** + * @brief 初始化雷达管理器 + * 初始化串口、队列和FreeRTOS任务 + */ +void initRadarManager() { + Serial.println("🔧 初始化雷达管理器..."); + mySerial1.setRxBufferSize(UART_RX_BUFFER_SIZE); + mySerial1.begin(BAUD_RATE, SERIAL_8N1, UART1_RX, UART1_TX); + Serial.println("UART1配置完成,缓冲区大小: 4096字节"); + + phaseDataQueue = xQueueCreate(QUEUE_SIZE, sizeof(PhaseData)); + vitalDataQueue = xQueueCreate(QUEUE_SIZE, sizeof(VitalData)); + uartQueue = xQueueCreate(2048, sizeof(char)); + + if (phaseDataQueue == NULL || vitalDataQueue == NULL || uartQueue == NULL) { + Serial.println("❌ 队列创建失败"); + } else { + Serial.println("✅ FreeRTOS队列创建成功"); + } + + mySerial1.onReceive(serialRxCallback); + Serial.println("✅ 串口中断回调已设置"); + + xTaskCreatePinnedToCore( + bleSendTask, + "BleSendTask", + TASK_STACK_SIZE, + NULL, + 3, + &bleSendTaskHandle, + 1 + ); + + xTaskCreatePinnedToCore( + vitalSendTask, + "VitalSendTask", + TASK_STACK_SIZE, + NULL, + 2, + &vitalSendTaskHandle, + 1 + ); + + xTaskCreatePinnedToCore( + radarDataTask, + "RadarProcessTask", + 4096, + NULL, + 4, + NULL, + 1 + ); + + xTaskCreatePinnedToCore( + uartProcessTask, + "UartProcessTask", + 4096, + NULL, + 5, + &uartProcessTaskHandle, + 1 + ); + + Serial.println("✅ 雷达管理器初始化完成"); +} + +/** + * @brief 初始化R60ABD1雷达模组 + * 发送初始化命令激活雷达数据上报功能 + */ +void initR60ABD1() { + Serial.println("🔧 初始化R60ABD1雷达模组..."); + + Serial.println("📡 发送查询指令以激活数据上报..."); + uint8_t queryPresenceCmd[] = {0x53, 0x59, 0x80, 0x81, 0x00, 0x01, 0x00, 0x7D, 0x54, 0x43}; + mySerial1.write(queryPresenceCmd, sizeof(queryPresenceCmd)); + + Serial.println("📡 开启核心监测功能..."); + + sendRadarCommand(0x80, 0x00, 0x01); + delay(50); + sendRadarCommand(0x81, 0x00, 0x01); + delay(50); + sendRadarCommand(0x85, 0x00, 0x01); + delay(50); + sendRadarCommand(0x84, 0x00, 0x01); + delay(50); + + Serial.println("📡 尝试开启波形数据..."); + sendRadarCommand(0x81, 0x0C, 0x01); + delay(50); + sendRadarCommand(0x85, 0x0A, 0x01); + delay(50); + + sendRadarCommand(0x84, 0x13, 0x01); + delay(50); + sendRadarCommand(0x84, 0x14, 0x01); + delay(50); + + Serial.println("🔍 查询当前状态..."); + sendRadarCommand(0x80, 0x80, 0x0F); + delay(50); + sendRadarCommand(0x81, 0x80, 0x0F); + delay(50); + sendRadarCommand(0x85, 0x80, 0x0F); + delay(50); + sendRadarCommand(0x84, 0x80, 0x0F); + + Serial.println("✅ R60ABD1雷达初始化完成"); +} + +/** + * @brief 解析R60ABD1雷达数据帧 + * 解析雷达返回的数据帧并提取传感器数据 + * @param frame 数据帧指针 + * @param frameLen 数据帧长度 + * @return 是否解析成功 + */ +bool parseR60ABD1Frame(uint8_t *frame, uint16_t frameLen) { + if(frameLen < 8) return false; + + if(frame[0] != FRAME_HEADER1 || frame[1] != FRAME_HEADER2 || + frame[frameLen-2] != FRAME_TAIL1 || frame[frameLen-1] != FRAME_TAIL2) { + return false; + } + + uint8_t checksum = 0; + for(int i = 0; i < frameLen-3; i++) { + checksum += frame[i]; + if(i % 50 == 0) { + esp_task_wdt_reset(); + } + } + if(checksum != frame[frameLen-3]) { + Serial.println("❌ R60ABD1帧校验和错误"); + return false; + } + + for(int i = 0; i < frameLen && i < 20; i++) { + Serial.printf("%02X ", frame[i]); + } + if(frameLen > 20) Serial.print("... "); + Serial.printf("| 校验:0x%02X\n", frame[frameLen-3]); + + uint8_t ctrlByte = frame[2]; + uint8_t cmdByte = frame[3]; + uint16_t dataLen = (frame[4] << 8) | frame[5]; + + switch(ctrlByte) { + case CTRL_PRESENCE: + switch(cmdByte) { + case 0x00: + case 0x80: + if(dataLen >= 1) { + if(frame[6] == 0x01) { + Serial.println("🔄 人体存在监测功能已开启"); + sendRadarCommand(0x84, 0x00, 0x01); + } else { + Serial.println("🔄 人体存在监测功能已关闭"); + } + } + break; + + case 0x01: + if(dataLen >= 1) { + sensorData.presence = frame[6]; + Serial.printf("👤 人体存在: %s\n", sensorData.presence ? "有人" : "无人"); + } + break; + + case 0x02: + if(dataLen >= 1) { + sensorData.motion = frame[6]; + const char* states[] = {"无", "静止", "活跃"}; + Serial.printf("🏃 运动状态: %s\n", states[sensorData.motion]); + } + break; + + case 0x03: + if(dataLen >= 1) { + sensorData.body_movement = frame[6]; + Serial.printf("📊体动参数: %d\n", sensorData.body_movement); + } + break; + + case 0x04: + if(dataLen >= 2) { + sensorData.distance = ((uint16_t)frame[6] << 8) | frame[7]; + Serial.printf("📏人体距离: %d cm\n", sensorData.distance); + } + break; + + case 0x05: + if(dataLen >= 6) { + uint16_t x_raw = ((uint16_t)frame[6] << 8) | frame[7]; + sensorData.pos_x = parseSignedCoordinate(x_raw); + + uint16_t y_raw = ((uint16_t)frame[8] << 8) | frame[9]; + sensorData.pos_y = parseSignedCoordinate(y_raw); + + uint16_t z_raw = ((uint16_t)frame[10] << 8) | frame[11]; + sensorData.pos_z = parseSignedCoordinate(z_raw); + + Serial.printf("📍方位坐标 - 原始: X=0x%04X, Y=0x%04X, Z=0x%04X\n", + x_raw, y_raw, z_raw); + Serial.printf(" 解析后: X=%d, Y=%d, Z=%d cm\n", + sensorData.pos_x, sensorData.pos_y, sensorData.pos_z); + } + break; + + default: + Serial.printf("❓未知的0x80命令字: 0x%02X\n", cmdByte); + break; + } + break; + + case CTRL_BREATH: + switch(cmdByte) { + case 0x00: + case 0x80: + if(dataLen >= 1) { + if(frame[6] == 0x01) + Serial.println("🔄 呼吸监测功能已开启"); + else + Serial.println("🔄 呼吸监测功能已关闭"); + } + break; + + case 0x01: + if(dataLen >= 1) { + sensorData.breath_status = frame[6]; + const char* info_str[] = {"", "正常", "呼吸过高(>25)", "呼吸过低(<10)", "无"}; + if(sensorData.breath_status >= 1 && sensorData.breath_status <= 4) { + Serial.printf("🔍 呼吸信息: %s\n", info_str[sensorData.breath_status]); + } else { + Serial.printf("🔍 呼吸信息: 未知状态(0x%02X)\n", sensorData.breath_status); + } + } + break; + + case 0x02: + if(dataLen >= 1) { + sensorData.breath_rate = (float)frame[6]; + sensorData.breath_valid = (sensorData.breath_rate >= 0.0f && + sensorData.breath_rate <= 35.0f); + Serial.printf("💨 呼吸率: %.1f 次/分\n", sensorData.breath_rate); + } + break; + + case 0x05: + if(dataLen >= 5) { + for(int i = 0; i < 5 && i < dataLen; i++) { + sensorData.breath_waveform[i] = (int8_t)(frame[6+i] - 128); + } + Serial.printf("📈 呼吸波形: %d\n", sensorData.breath_waveform[0]); + } + break; + + default: + Serial.printf("❓未知的0x81命令字: 0x%02X\n", cmdByte); + break; + } + break; + + case CTRL_HEARTRATE: + switch(cmdByte) { + case 0x00: + case 0x80: + if(dataLen >= 1) { + if(frame[6] == 0x01) + Serial.println("🔄 心率监测功能已开启"); + else + Serial.println("🔄 心率监测功能已关闭"); + } + break; + case 0x02: + if(dataLen >= 1) { + sensorData.heart_rate = (float)frame[6]; + sensorData.heart_valid = (sensorData.heart_rate >= 60.0f && + sensorData.heart_rate <= 120.0f); + Serial.printf("❤️ 心率: %.1f 次/分\n", sensorData.heart_rate); + } + break; + + case 0x05: + if(dataLen >= 5) { + for(int i = 0; i < 5 && i < dataLen; i++) { + sensorData.heart_waveform[i] = (int8_t)(frame[6+i] - 128); + } + Serial.printf("📈 心率波形: %d\n", sensorData.heart_waveform[0]); + } + break; + + default: + Serial.printf("❓未知的0x85命令字: 0x%02X\n", cmdByte); + break; + } + break; + + case CTRL_SLEEP: + switch(cmdByte) { + case 0x00: + case 0x80: + if(dataLen >= 1) { + if(frame[6] == 0x01) + Serial.println("🔄 睡眠监测功能已开启"); + else + Serial.println("🔄 睡眠监测功能已关闭"); + } + break; + + case 0x01: + case 0x81: + if(dataLen >= 1) { + sensorData.bed_status = frame[6]; + const char* status_str[] = {"离床", "入床", "无"}; + Serial.printf("🛏️ 床状态: %s\n", status_str[sensorData.bed_status]); + } + break; + + case 0x03: + case 0x83: + if(dataLen >= 2) { + sensorData.awake_time = ((uint16_t)frame[6] << 8) | (uint16_t)frame[7]; + Serial.printf("⏰ 清醒时长: %d 分钟\n", sensorData.awake_time); + } + break; + + case 0x04: + case 0x84: + if(dataLen >= 2) { + sensorData.light_sleep_time = ((uint16_t)frame[6] << 8) | (uint16_t)frame[7]; + Serial.printf("😪 浅睡时长: %d 分钟\n", sensorData.light_sleep_time); + } + break; + + case 0x05: + case 0x85: + if(dataLen >= 2) { + sensorData.deep_sleep_time = ((uint16_t)frame[6] << 8) | (uint16_t)frame[7]; + Serial.printf("💤 深睡时长: %d 分钟\n", sensorData.deep_sleep_time); + } + break; + + case 0x06: + if(dataLen >= 1) { + sensorData.sleep_score = frame[6]; + Serial.printf("⭐ 睡眠质量评分: %d 分\n", sensorData.sleep_score); + } + break; + + case 0x86: + if(dataLen >= 2) { + sensorData.sleep_score = frame[6]; + Serial.printf("⭐ 睡眠质量评分: %d 分\n", sensorData.sleep_score); + } + break; + + case 0x0C: + case 0x8D: + if(dataLen >= 8) { + sensorData.presence = frame[6]; + sensorData.sleep_state = frame[7]; + sensorData.avg_breath_rate = frame[8]; + sensorData.avg_heart_rate = frame[9]; + sensorData.turnover_count = frame[10]; + sensorData.large_move_ratio = frame[11]; + sensorData.small_move_ratio = frame[12]; + sensorData.apnea_count = frame[13]; + + Serial.printf("📊 睡眠综合状态 - 存在:%d, 睡眠状态:%d, 睡眠平均呼吸:%d, 睡眠平均心率:%d, 翻身次数:%d, 大动占比:%d%%, 小动占比:%d%%, 呼吸暂停次数:%d\n", + sensorData.presence, sensorData.sleep_state, + sensorData.avg_breath_rate, sensorData.avg_heart_rate, + sensorData.turnover_count, sensorData.large_move_ratio, + sensorData.small_move_ratio, sensorData.apnea_count); + } + break; + + case 0x0D: + case 0x8F: + if(dataLen >= 12) { + sensorData.sleep_score = frame[6]; + sensorData.sleep_total_time = ((uint16_t)frame[7] << 8) | (uint16_t)frame[8]; + + sensorData.awake_ratio = frame[9]; + sensorData.light_sleep_ratio = frame[10]; + sensorData.deep_sleep_ratio = frame[11]; + + sensorData.bed_Out_Time = frame[12]; + sensorData.turn_count = frame[13]; + sensorData.turnover_count = frame[14]; + sensorData.avg_breath_rate = frame[15]; + sensorData.avg_heart_rate = frame[16]; + sensorData.apnea_count = frame[17]; + + Serial.printf("📈 睡眠分析报告 - 评分:%d, 总时长:%d分, 清醒占比:%d%%, 浅睡占比:%d%%, 深睡占比:%d%%, 离床时长:%d, 离床次数:%d, 翻身:%d次, 平均呼吸:%d, 平均心跳:%d\n", + sensorData.sleep_score, + sensorData.sleep_total_time, + sensorData.awake_ratio, + sensorData.light_sleep_ratio, + sensorData.deep_sleep_ratio, + sensorData.bed_Out_Time, + sensorData.turn_count, + sensorData.turnover_count, + sensorData.avg_breath_rate, + sensorData.avg_heart_rate); + } + break; + + case 0x0E: + case 0x8E: + if(dataLen >= 1) { + sensorData.abnormal_state = frame[6]; + const char* abnormal_str[] = { + "睡眠时长不足4小时", "睡眠时长大于12小时", "长时间异常无人" + }; + if(sensorData.abnormal_state < 3) { + Serial.printf("⚠️ 睡眠异常: %s\n", abnormal_str[sensorData.abnormal_state]); + } + } + break; + + case 0x10: + case 0x90: + if(dataLen >= 1) { + sensorData.sleep_grade = frame[6]; + const char* rating_str[] = {"无", "睡眠质量良好", "睡眠质量一般", "睡眠质量较差"}; + if(sensorData.sleep_grade < 4) { + Serial.printf("🏆 睡眠质量评级: %s\n", rating_str[sensorData.sleep_grade]); + } + } + break; + + case 0x11: + case 0x91: + if(dataLen >= 1) { + sensorData.struggle_alert = frame[6]; + const char* struggle_str[] = {"无", "正常", "异常挣扎"}; + if(sensorData.struggle_alert < 3) { + Serial.printf("⚠️ 挣扎状态: %s\n", struggle_str[sensorData.struggle_alert]); + } + } + break; + + case 0x12: + case 0x92: + if(dataLen >= 1) { + sensorData.no_one_alert = frame[6]; + const char* no_one_str[] = {"无", "正常", "异常"}; + if(sensorData.no_one_alert < 3) { + Serial.printf("⏰ 无人计时状态: %s\n", no_one_str[sensorData.no_one_alert]); + } + } + break; + + default: + Serial.printf("❓未知的0x84命令字: 0x%02X\n", cmdByte); + break; + } + break; + + case 0x07: + if(dataLen >= 1) { + if(frame[6] == 0x00) + Serial.println("雷达探测范围外"); + else + Serial.println("雷达探测范围内"); + } + break; + + default: + Serial.printf("❓未知控制字: 0x%02X\n", ctrlByte); + break; + } + + lastSensorUpdate = millis(); + + sensorData.heart_valid = (sensorData.heart_rate > 0 && sensorData.heart_rate < 200); + sensorData.breath_valid = (sensorData.breath_rate >= 0.1f && sensorData.breath_rate <= 60.0f); + + if( sensorData.heart_valid ==1 && sensorData.heart_valid == 1 && presence_Bit == 1 ) + { + sensorData.presence = 1; + presence_Bit = 0; + } + + return true; +} + +int16_t parseSignedCoordinate(uint16_t raw_value) { + bool is_negative = (raw_value & 0x8000) != 0; + uint16_t magnitude = raw_value & 0x7FFF; + + int16_t result = (int16_t)magnitude; + if (is_negative) { + result = -result; + } + + return result; +} + +/** + * @brief 发送雷达命令 + * 构造并发送控制命令到雷达模组 + * @param ctrl 控制字节 + * @param cmd 命令字节 + * @param value 值字节 + */ +void sendRadarCommand(uint8_t ctrl, uint8_t cmd, uint8_t value) { + uint8_t command[10]; + command[0] = 0x53; + command[1] = 0x59; + command[2] = ctrl; + command[3] = cmd; + command[4] = 0x00; + command[5] = 0x01; + command[6] = value; + + uint8_t checksum = 0; + for(int i = 0; i < 7; i++) { + checksum += command[i]; + } + command[7] = checksum; + + command[8] = 0x54; + command[9] = 0x43; + + mySerial1.write(command, 10); + + Serial.printf("📤 发送: "); + for(int i = 0; i < 10; i++) { + Serial.printf("%02X ", command[i]); + } + Serial.println(); + + Serial.printf(" 控制字=0x%02X, 命令字=0x%02X, 值=0x%02X, 校验和=0x%02X\n", + ctrl, cmd, value, checksum); +} + +void IRAM_ATTR serialRxCallback() { + if (uartQueue != NULL) { + while(mySerial1.available()) { + char c = mySerial1.read(); + + BaseType_t xHigherPriorityTaskWoken = pdFALSE; + xQueueSendFromISR(uartQueue, &c, &xHigherPriorityTaskWoken); + + if(xHigherPriorityTaskWoken) { + portYIELD_FROM_ISR(); + } + } + } +} + +/** + * @brief 检查数据是否发生变化 + * 比较当前传感器数据与上次发送的数据,判断是否需要发送更新 + * @return 是否发生变化 + */ +bool isDataChanged() { + // 心率变化阈值:0.5 + if (fabs(sensorData.heart_rate - lastSentData.heart_rate) > 0.5) { + return true; + } + + // 呼吸率变化阈值:0.5 + if (fabs(sensorData.breath_rate - lastSentData.breath_rate) > 0.5) { + return true; + } + + // 存在状态变化 + if (sensorData.presence != lastSentData.presence) { + return true; + } + + // 运动状态变化 + if (sensorData.motion != lastSentData.motion) { + return true; + } + + // 睡眠状态变化 + if (sensorData.sleep_state != lastSentData.sleep_state) { + return true; + } + + return false; +} + +/** + * @brief BLE数据发送任务 + * 实现基于数据变化和定时检测的发送机制 + * 1. 定时检测数据变化(基于continuousSendInterval) + * 2. 检测到变化时立即发送数据 + * 3. 发送后更新lastSentData为当前数据 + * @param parameter 任务参数(未使用) + */ +void bleSendTask(void *parameter) { + Serial.println("🔁 R60ABD1蓝牙数据发送任务启动"); + + while (1) { + esp_task_wdt_reset(); + + // 检查是否需要进行数据检测 + if (continuousSendEnabled && deviceConnected) { + unsigned long currentTime = millis(); + + // 按照设定的时间间隔进行检测 + if (currentTime - lastCheckTime >= continuousSendInterval) { + lastCheckTime = currentTime; + + // 检查数据是否发生变化 + if (isDataChanged()) { + // 构建雷达数据字符串 + String radarDataCore; + + if (sensorData.presence > 0) { + radarDataCore = String(sensorData.heart_rate, 1) + String("|") + + String(sensorData.breath_rate, 1) + String("|") + + String((int)sensorData.heart_waveform[0]) + String("|") + + String((int)sensorData.breath_waveform[0]) + String("|") + + String(sensorData.presence) + String("|") + + String(sensorData.motion) + String("|") + + String(sensorData.sleep_state); + } else { + radarDataCore = String("0.0") + String("|") + + String("0.0") + String("|") + + String("0") + String("|") + + String("0") + String("|") + + String("0") + String("|") + + String("0") + String("|") + + String("0"); + } + + // 计算CRC校验 + unsigned int crc = 0xFFFF; + for (int i = 0; i < radarDataCore.length(); i++) { + crc ^= (unsigned int)radarDataCore.charAt(i); + for (int j = 0; j < 8; j++) { + if (crc & 0x0001) { + crc >>= 1; + crc ^= 0xA001; + } else { + crc >>= 1; + } + } + } + + String radarDataMsg = radarDataCore + String("|") + String(crc, HEX); + + Serial.printf("📤 通过蓝牙发送R60ABD1雷达数据: %s\n", radarDataMsg.c_str()); + + const int MAX_BLE_PACKET_SIZE = 20; + if (radarDataMsg.length() <= MAX_BLE_PACKET_SIZE) { + pCharacteristic->setValue(radarDataMsg.c_str()); + pCharacteristic->notify(); + Serial.println("✅ R60ABD1雷达数据蓝牙发送成功"); + } else { + Serial.println("🔄 R60ABD1雷达数据较长,使用分包发送"); + sendDataInChunks(radarDataMsg); + } + + // 更新上次发送的数据 + lastSentData.heart_rate = sensorData.heart_rate; + lastSentData.breath_rate = sensorData.breath_rate; + lastSentData.presence = sensorData.presence; + lastSentData.motion = sensorData.motion; + lastSentData.sleep_state = sensorData.sleep_state; + + Serial.println("📊 数据已更新,上次发送数据已保存"); + } + } + } + + vTaskDelay(10 / portTICK_PERIOD_MS); + esp_task_wdt_reset(); + } +} + +/** + * @brief 生命体征数据发送任务 + * 从队列中获取生命体征数据并发送到InfluxDB数据库 + * @param parameter 任务参数(未使用) + */ +void vitalSendTask(void *parameter) { + Serial.println("🔁🔁 生命体征数据发送任务启动(WiFi数据库传输)"); + + unsigned long lastSleepDataTime = 0; + const unsigned long SLEEP_DATA_INTERVAL = 5000; + + while (1) { + VitalData vitalData; + + if (xQueueReceive(vitalDataQueue, &vitalData, portMAX_DELAY) == pdTRUE) { + esp_task_wdt_reset(); + + if (WiFi.status() == WL_CONNECTED) { + // 检查心率和呼吸率是否都为0,如果是则跳过发送 + if (vitalData.heart_rate == 0 || vitalData.breath_rate == 0) { + Serial.println("⚠️ 心率和呼吸率都为0,跳过发送数据到数据库"); + continue; + } + + String dailyDataLine = "daily_data,deviceId=" + String(currentDeviceId) + ",dataType=daily "; + + bool firstField = true; + + if (vitalData.heart_rate > 0) { + if (!firstField) dailyDataLine += ","; + dailyDataLine += "heartRate=" + String(vitalData.heart_rate, 1); + firstField = false; + } + + if (vitalData.breath_rate > 0) { + if (!firstField) dailyDataLine += ","; + dailyDataLine += "breathingRate=" + String(vitalData.breath_rate, 1); + firstField = false; + } + + if (!firstField) dailyDataLine += ","; + dailyDataLine += "personDetected=" + String(vitalData.presence) + "i"; + firstField = false; + + if (!firstField) dailyDataLine += ","; + dailyDataLine += "humanActivity=" + String(vitalData.motion) + "i"; + firstField = false; + + if (vitalData.distance > 0) { + if (!firstField) dailyDataLine += ","; + dailyDataLine += "humanDistance=" + String(vitalData.distance) + "i"; + firstField = false; + } + + if (vitalData.sleep_state >= 0) { + if (!firstField) dailyDataLine += ","; + dailyDataLine += "sleepState=" + String(vitalData.sleep_state) + "i"; + firstField = false; + } + + if (!firstField) dailyDataLine += ","; + dailyDataLine += "humanPositionX=" + String(vitalData.pos_x) + "i"; + firstField = false; + + if (!firstField) dailyDataLine += ","; + dailyDataLine += "humanPositionY=" + String(vitalData.pos_y) + "i"; + firstField = false; + + if (!firstField) dailyDataLine += ","; + dailyDataLine += "humanPositionZ=" + String(vitalData.pos_z) + "i"; + firstField = false; + + if (!firstField) dailyDataLine += ","; + dailyDataLine += "heartbeatWaveform=" + String((int)sensorData.heart_waveform[0]) + "i"; + firstField = false; + + if (!firstField) dailyDataLine += ","; + dailyDataLine += "breathingWaveform=" + String((int)sensorData.breath_waveform[0]) + "i"; + firstField = false; + + if (!firstField) dailyDataLine += ","; + dailyDataLine += "abnormalState=" + String(vitalData.abnormal_state) + "i"; + firstField = false; + + if (!firstField) dailyDataLine += ","; + dailyDataLine += "bedStatus=" + String(vitalData.bed_status) + "i"; + firstField = false; + + if (!firstField) dailyDataLine += ","; + dailyDataLine += "struggleAlert=" + String(vitalData.struggle_alert) + "i"; + firstField = false; + + if (!firstField) dailyDataLine += ","; + dailyDataLine += "noOneAlert=" + String(vitalData.no_one_alert) + "i"; + firstField = false; + + if (!dailyDataLine.endsWith(" ")) { + sendDailyDataToInfluxDB(dailyDataLine); + esp_task_wdt_reset(); + } + + unsigned long currentTime = millis(); + if (currentTime - lastSleepDataTime >= SLEEP_DATA_INTERVAL) { + sendSleepDataToInfluxDB(); + lastSleepDataTime = currentTime; + Serial.println("⏰ 睡眠数据定时发送完成"); + } + } else { + Serial.println("❌❌ WiFi未连接,无法发送雷达数据到数据库"); + + static unsigned long lastWifiCheck = 0; + if (millis() - lastWifiCheck > 10000) { + Serial.printf("📶📶 WiFi状态: %d\n", WiFi.status()); + lastWifiCheck = millis(); + } + } + + esp_task_wdt_reset(); + } + + vTaskDelay(10 / portTICK_PERIOD_MS); + } +} + +/** + * @brief 发送日常数据到InfluxDB数据库 + * 通过HTTP协议将数据写入InfluxDB时序数据库 + * @param dailyDataLine 数据行字符串 + */ +void sendDailyDataToInfluxDB(String dailyDataLine) { + if (WiFi.status() != WL_CONNECTED) { + Serial.println("❌ WiFi未连接,无法发送日常数据到数据库"); + return; + } + + HTTPClient http; + http.setTimeout(2000); + + String url = String("http://") + String(influxDBHost) + ":" + String(influxDBPort) + "/api/v2/write?org=" + String(influxDBOrg) + "&bucket=" + String(influxDBBucket); + + http.begin(url); + http.addHeader("Authorization", String("Token ") + String(influxDBToken)); + http.addHeader("Content-Type", "text/plain; charset=utf-8"); + http.setReuse(true); + + Serial.println(String("📊 发送日常数据到InfluxDB: ") + dailyDataLine); + + int httpResponseCode = http.POST(dailyDataLine); + + if (httpResponseCode == 204) { + Serial.println("✅ 日常数据发送成功"); + } else { + Serial.println(String("❌ 发送日常数据失败: ") + String(httpResponseCode) + " - " + http.getString()); + } + + http.end(); +} + +/** + * @brief 发送睡眠数据到InfluxDB数据库 + * 将睡眠相关的统计数据发送到InfluxDB时序数据库 + */ +void sendSleepDataToInfluxDB() { + if (WiFi.status() != WL_CONNECTED) { + Serial.println("❌ WiFi未连接,无法发送睡眠数据到数据库"); + return; + } + + if (sensorData.sleep_total_time == 0) { + Serial.println("😴 总睡眠时长为0,跳过上传睡眠数据"); + return; + } + + HTTPClient http; + http.setTimeout(2000); + + String url = String("http://") + String(influxDBHost) + ":" + String(influxDBPort) + "/api/v2/write?org=" + String(influxDBOrg) + "&bucket=" + String(influxDBBucket); + + http.begin(url); + http.addHeader("Authorization", String("Token ") + String(influxDBToken)); + http.addHeader("Content-Type", "text/plain; charset=utf-8"); + http.setReuse(true); + + String lineProtocol = String("sleep_data,deviceId=") + String(currentDeviceId) + ",dataType=sleep "; + + String fields = ""; + fields += String("sleepQualityScore=") + String((int)sensorData.sleep_score) + "i"; + fields += ",sleepQualityGrade=" + String((int)sensorData.sleep_grade) + "i"; + fields += ",totalSleepDuration=" + String((int)sensorData.sleep_total_time) + "i"; + fields += ",awakeDurationRatio=" + String((int)sensorData.awake_ratio) + "i"; + fields += ",lightSleepRatio=" + String((int)sensorData.light_sleep_ratio) + "i"; + fields += ",deepSleepRatio=" + String((int)sensorData.deep_sleep_ratio) + "i"; + fields += ",outOfBedDuration=" + String((int)sensorData.bed_Out_Time) + "i"; + fields += ",outOfBedCount=" + String((int)sensorData.turn_count) + "i"; + fields += ",turnCount=" + String((int)sensorData.turnover_count) + "i"; + fields += ",avgBreathingRate=" + String((int)sensorData.avg_breath_rate) + "i"; + fields += ",avgHeartRate=" + String((int)sensorData.avg_heart_rate) + "i"; + fields += ",apneaCount=" + String((int)sensorData.apnea_count) + "i"; + fields += ",abnormalState=" + String((int)sensorData.abnormal_state) + "i"; + fields += ",bodyMovement=" + String((int)sensorData.body_movement) + "i"; + fields += ",breathStatus=" + String((int)sensorData.breath_status) + "i"; + fields += ",sleepState=" + String((int)sensorData.sleep_state) + "i"; + fields += ",largeMoveRatio=" + String((int)sensorData.large_move_ratio) + "i"; + fields += ",smallMoveRatio=" + String((int)sensorData.small_move_ratio) + "i"; + fields += ",struggleAlert=" + String((int)sensorData.struggle_alert) + "i"; + fields += ",noOneAlert=" + String((int)sensorData.no_one_alert) + "i"; + fields += ",awakeDuration=" + String((int)sensorData.awake_time) + "i"; + fields += ",lightSleepDuration=" + String((int)sensorData.light_sleep_time) + "i"; + fields += ",deepSleepDuration=" + String((int)sensorData.deep_sleep_time) + "i"; + + lineProtocol += fields; + + Serial.println(String("🌙 发送睡眠数据到InfluxDB: ") + lineProtocol); + + int httpResponseCode = http.POST(lineProtocol); + + if (httpResponseCode == 204) { + Serial.println(String("✅ 睡眠数据已保存到InfluxDB设备") + String(currentDeviceId) + "上"); + } else { + Serial.println(String("❌ 保存睡眠数据到InfluxDB失败: ") + String(httpResponseCode) + " - " + http.getString()); + } + + http.end(); +} + +/** + * @brief 雷达数据处理任务 + * 处理雷达数据并分发到相应的队列 + * @param parameter 任务参数(未使用) + */ +void radarDataTask(void *parameter) { + Serial.println("🔁 雷达数据处理任务启动(最高优先级)"); + + while (1) { + vTaskDelay(100 / portTICK_PERIOD_MS); + } +} + +/** + * @brief UART数据处理任务 + * 从UART队列中读取数据并解析雷达数据帧 + * @param parameter 任务参数(未使用) + */ +void uartProcessTask(void *parameter) { + uint8_t buffer[256]; + int bufferIndex = 0; + bool inFrame = false; + uint8_t prevByte = 0; + + Serial.println("✅ R60ABD1串口数据处理任务启动"); + + while(1) { + uint8_t c; + if(xQueueReceive(uartQueue, &c, 10 / portTICK_PERIOD_MS) == pdTRUE) { + esp_task_wdt_reset(); + if(!inFrame) { + if(prevByte == FRAME_HEADER1 && c == FRAME_HEADER2) { + buffer[0] = FRAME_HEADER1; + buffer[1] = FRAME_HEADER2; + bufferIndex = 2; + inFrame = true; + Serial.println("🔍 检测到R60ABD1帧头"); + } + } else { + if(bufferIndex < sizeof(buffer)) { + buffer[bufferIndex++] = c; + + if(bufferIndex >= 8 && + buffer[bufferIndex-2] == FRAME_TAIL1 && + buffer[bufferIndex-1] == FRAME_TAIL2) { + if(parseR60ABD1Frame(buffer, bufferIndex)) { + static uint32_t frameCounter = 0; + frameCounter++; + + lastSensorUpdate = millis(); + + static uint32_t phasePacketCounter = 0; + static uint32_t vitalPacketCounter = 0; + + phasePacketCounter++; + if (phasePacketCounter >= PHASE_SEND_INTERVAL) { + PhaseData phaseData; + phaseData.heartbeat_waveform = sensorData.heartbeat_waveform; + phaseData.breathing_waveform = sensorData.breathing_waveform; + + if (xQueueSend(phaseDataQueue, &phaseData, 0) == pdTRUE) { + } else { + Serial.println("❌ 相位数据队列已满,数据丢失"); + } + phasePacketCounter = 0; + } + + vitalPacketCounter++; + if (vitalPacketCounter >= VITAL_SEND_INTERVAL) { + VitalData vitalData; + vitalData.heart_rate = sensorData.heart_rate; + vitalData.breath_rate = sensorData.breath_rate; + vitalData.presence = sensorData.presence; + vitalData.motion = sensorData.motion; + vitalData.distance = sensorData.distance; + vitalData.sleep_state = sensorData.sleep_state; + vitalData.sleep_score = sensorData.sleep_score; + vitalData.body_movement = sensorData.body_movement; + vitalData.breath_status = sensorData.breath_status; + vitalData.sleep_time = sensorData.sleep_time; + vitalData.bed_status = sensorData.bed_status; + vitalData.abnormal_state = sensorData.abnormal_state; + vitalData.avg_heart_rate = sensorData.avg_heart_rate; + vitalData.avg_breath_rate = sensorData.avg_breath_rate; + vitalData.turn_count = sensorData.turn_count; + vitalData.large_move_ratio = sensorData.large_move_ratio; + vitalData.small_move_ratio = sensorData.small_move_ratio; + vitalData.pos_x = sensorData.pos_x; + vitalData.pos_y = sensorData.pos_y; + vitalData.pos_z = sensorData.pos_z; + vitalData.deep_sleep_time = sensorData.deep_sleep_time; + vitalData.light_sleep_time = sensorData.light_sleep_time; + vitalData.awake_time = sensorData.awake_time; + vitalData.sleep_total_time = sensorData.sleep_total_time; + vitalData.deep_sleep_ratio = sensorData.deep_sleep_ratio; + vitalData.light_sleep_ratio = sensorData.light_sleep_ratio; + vitalData.awake_ratio = sensorData.awake_ratio; + vitalData.turnover_count = sensorData.turnover_count; + vitalData.struggle_alert = sensorData.struggle_alert; + vitalData.no_one_alert = sensorData.no_one_alert; + vitalData.apnea_count = sensorData.apnea_count; + vitalData.heartbeat_waveform = sensorData.heartbeat_waveform; + vitalData.breathing_waveform = sensorData.breathing_waveform; + + if (xQueueSend(vitalDataQueue, &vitalData, 0) == pdTRUE) { + Serial.println("📤 生命体征数据已加入发送队列"); + } else { + Serial.println("❌ 生命体征数据队列已满,数据丢失"); + } + vitalPacketCounter = 0; + } + + if(frameCounter % 100 == 0) { + Serial.printf("📈 已处理 %d 个R60ABD1数据帧\n", frameCounter); + } + } + + inFrame = false; + } + } else { + Serial.println("⚠️ R60ABD1帧缓冲区溢出,重置接收状态"); + inFrame = false; + } + } + + prevByte = c; + } + + vTaskDelay(1 / portTICK_PERIOD_MS); + esp_task_wdt_reset(); + } +} + +/** + * @brief 分块发送数据 + * 将大数据分块发送,避免BLE MTU限制 + * @param data 要发送的数据字符串 + */ +void sendDataInChunks(const String& data) { + const int MAX_PACKET_SIZE = 20; + const int HEADER_SIZE = 6; + const int CHUNK_SIZE = MAX_PACKET_SIZE - HEADER_SIZE; + + int totalLength = data.length(); + int numChunks = (totalLength + CHUNK_SIZE - 1) / CHUNK_SIZE; + + Serial.printf("📦 开始分包发送,总长度: %d, 分包数: %d\n", totalLength, numChunks); + + for(int i = 0; i < numChunks; i++) { + int start = i * CHUNK_SIZE; + int chunkLength = min(CHUNK_SIZE, totalLength - start); + String chunk = data.substring(start, start + chunkLength); + + String packetHeader = String("[") + String(i+1) + String("/") + String(numChunks) + String("]"); + + int maxDataLength = MAX_PACKET_SIZE - packetHeader.length(); + if (chunk.length() > maxDataLength) { + chunk = chunk.substring(0, maxDataLength); + } + + String packet = packetHeader + chunk; + + Serial.printf("📤 发送分包 %s: %s\n", packetHeader.c_str(), chunk.c_str()); + + if (!deviceConnected) { + Serial.println("❌ BLE未连接,无法发送数据"); + return; + } + + pCharacteristic->setValue(packet.c_str()); + pCharacteristic->notify(); + Serial.println("✅ 分包发送成功"); + + if (i < numChunks - 1) { + Serial.printf("⏳ 等待接收端处理第%d个包...\n", i+1); + vTaskDelay(20 / portTICK_PERIOD_MS); + } + } + + Serial.println("📦 分包发送完成"); +} + +/** + * @brief 发送JSON数据到BLE + * 将JSON格式数据通过BLE发送给客户端 + * @param jsonData JSON格式数据字符串 + */ +void sendJSONDataToBLE(const String& jsonData) { + Serial.printf("📤 准备发送JSON数据: %s\n", jsonData.c_str()); + + if (!deviceConnected) { + Serial.println("❌ BLE未连接,无法发送JSON数据"); + return; + } + + const int MAX_PACKET_SIZE = 20; + int totalLength = jsonData.length(); + int numChunks = (totalLength + MAX_PACKET_SIZE - 1) / MAX_PACKET_SIZE; + + Serial.printf("📦 开始分包发送JSON,总长度: %d, 分包数: %d\n", totalLength, numChunks); + + for(int i = 0; i < numChunks; i++) { + int start = i * MAX_PACKET_SIZE; + int chunkLength = min(MAX_PACKET_SIZE, totalLength - start); + String chunk = jsonData.substring(start, start + chunkLength); + + Serial.printf("📤 发送JSON分包 %d/%d: %s\n", i+1, numChunks, chunk.c_str()); + + pCharacteristic->setValue(chunk.c_str()); + pCharacteristic->notify(); + Serial.println("✅ JSON分包发送成功"); + + if (i < numChunks - 1) { + Serial.printf("⏳ 等待接收端处理第%d个包...\n", i+1); + vTaskDelay(10 / portTICK_PERIOD_MS); + } + } + + Serial.println("📦 JSON分包发送完成"); +} + +/** + * @brief 发送自定义JSON数据到BLE + * 构造自定义JSON格式数据并通过BLE发送 + * @param jsonType JSON类型 + * @param jsonString JSON内容字符串 + * @return 是否发送成功 + */ +bool sendCustomJSONData(const String& jsonType, const String& jsonString) { + if (!deviceConnected) { + Serial.println("❌ BLE未连接,无法发送自定义JSON数据"); + return false; + } + + String fullJSON = String("{\"type\":\"") + jsonType + String("\",") + jsonString + String("}"); + + Serial.printf("📤 发送自定义JSON数据类型 '%s': %s\n", jsonType.c_str(), fullJSON.c_str()); + + sendJSONDataToBLE(fullJSON); + + return true; +} + +/** + * @brief 发送雷达数据到BLE + * 发送雷达传感器数据(已移至FreeRTOS任务处理) + */ +void sendRadarDataToBLE() { + Serial.println("ℹ️ 雷达数据发送已移至FreeRTOS任务处理"); +} + +/** + * @brief 处理查询雷达数据命令 + * 处理来自BLE的雷达数据查询请求 + * @param doc JSON文档对象 + * @return 是否处理成功 + */ +bool processQueryRadarData(JsonDocument& doc) { + const char* command = doc["command"]; + if (command != nullptr && strcmp(command, "queryRadarData") == 0) { + Serial.println("收到查询雷达数据命令"); + + if (deviceConnected) { + String radarDataMsg = String("{\"type\":\"radarData\",\"success\":true") + + String(",\"deviceId\":") + String(currentDeviceId) + + String(",\"timestamp\":") + String(millis()) + + String(",\"presence\":") + String(sensorData.presence) + + String(",\"heartRate\":") + String(sensorData.heart_rate, 1) + + String(",\"breathRate\":") + String(sensorData.breath_rate, 1) + + String(",\"motion\":") + String(sensorData.motion) + + String(",\"heartbeatWaveform\":") + String((int)sensorData.breath_waveform[0]) + + String(",\"breathingWaveform\":") + String((int)sensorData.heart_waveform[0]) + + String(",\"distance\":") + String(sensorData.distance) + + String(",\"bodyMovement\":") + String(sensorData.body_movement) + + String(",\"breathStatus\":") + String(sensorData.breath_status) + + String(",\"sleepState\":") + String(sensorData.sleep_state) + + String(",\"sleepTime\":") + String(sensorData.sleep_time) + + String(",\"sleepScore\":") + String(sensorData.sleep_score) + + String(",\"avgHeartRate\":") + String(sensorData.avg_heart_rate) + + String(",\"avgBreathRate\":") + String(sensorData.avg_breath_rate) + + String(",\"turnCount\":") + String(sensorData.turn_count) + + String(",\"largeMoveRatio\":") + String(sensorData.large_move_ratio) + + String(",\"smallMoveRatio\":") + String(sensorData.small_move_ratio) + + String("}"); + + sendJSONDataToBLE(radarDataMsg); + Serial.println("已发送雷达数据"); + Serial.printf("发送的数据: %s\n", radarDataMsg.c_str()); + } else { + Serial.println("BLE未连接,无法发送雷达数据"); + } + return true; + } + return false; +} + +/** + * @brief 处理启动持续发送命令 + * 处理来自BLE的启动持续发送请求 + * @param doc JSON文档对象 + * @return 是否处理成功 + */ +bool processStartContinuousSend(JsonDocument& doc) { + const char* command = doc["command"]; + if (command != nullptr && strcmp(command, "startContinuousSend") == 0) { + if (doc["interval"].is()) { + continuousSendInterval = doc["interval"].as(); + if (continuousSendInterval < 100) continuousSendInterval = 100; + if (continuousSendInterval > 10000) continuousSendInterval = 10000; + } + + continuousSendEnabled = true; + bleFlow.reset(); + + Serial.printf("⚙️ 启动持续发送模式,间隔: %lu ms\n", continuousSendInterval); + + if (deviceConnected) { + String confirmMsg = String("{\"type\":\"startContinuousSendResult\",\"success\":true,\"message\":\"已启动持续发送模式\",\"interval\":") + + String(continuousSendInterval) + "}"; + + sendJSONDataToBLE(confirmMsg); + Serial.println("✅ 启动确认消息发送成功"); + Serial.println("🚀 已启动持续发送模式"); + } else { + Serial.println("❌ BLE未连接,无法发送确认消息"); + } + return true; + } + return false; +} + +/** + * @brief 处理停止持续发送命令 + * 处理来自BLE的停止持续发送请求 + * @param doc JSON文档对象 + * @return 是否处理成功 + */ +bool processStopContinuousSend(JsonDocument& doc) { + const char* command = doc["command"]; + if (command != nullptr && strcmp(command, "stopContinuousSend") == 0) { + continuousSendEnabled = false; + + Serial.println("🛑 停止持续发送模式"); + + if (deviceConnected) { + String confirmMsg = String("{\"type\":\"stopContinuousSendResult\",\"success\":true,\"message\":\"已停止持续发送模式\"}"); + + sendJSONDataToBLE(confirmMsg); + Serial.println("✅ 停止确认消息发送成功"); + Serial.println("⏹️ 已停止持续发送模式"); + } else { + Serial.println("❌ BLE未连接,无法发送确认消息"); + } + return true; + } + return false; +} + +/** + * @brief 处理BLE配置数据 + * 处理从BLE接收到的配置数据,解析JSON命令并执行相应操作 + */ +void processBLEConfig() { + if (completeData.length() > 0 && (millis() - lastReceiveTime > 3000)) { + Serial.println("⏰ [超时] 数据接收超时3秒,自动当作接收完成"); + + if (receivedData.length() == 0) { + Serial.println("📥 [BLE] 超时后将completeData当作接收数据进行处理"); + receivedData = completeData; + } + + completeData = ""; + } + + if (receivedData.length() > 0) { + String bleData = receivedData; + receivedData = ""; + bleData.trim(); + + Serial.printf("⚙️ [BLE] 准备解析JSON: %s\n", bleData.c_str()); + + if (bleData.startsWith("{") && bleData.endsWith("}")) { + JsonDocument doc; + DeserializationError error = deserializeJson(doc, bleData); + + if (error) { + String errorMsg = String("❌ [BLE] JSON解析失败: ") + String(error.c_str()); + Serial.println(errorMsg); + if (deviceConnected) { + String responseMsg = String("{\"type\":\"error\",\"message\":\"配置格式错误,请使用JSON格式\",\"originalData\":\"") + bleData + String("\"}"); + + sendJSONDataToBLE(responseMsg); + } + } else { + Serial.println("✅ [BLE] JSON解析成功"); + + bool processed = false; + + if (!processed) processed = processSetDeviceId(doc); + + if (!processed) processed = processWiFiConfigCommand(doc); + + if (!processed) processed = processQueryStatus(doc); + + if (!processed) processed = processQueryRadarData(doc); + + if (!processed) processed = processStartContinuousSend(doc); + + if (!processed) processed = processStopContinuousSend(doc); + + if (!processed) processed = processScanWiFi(doc); + + if (!processed) processed = processGetSavedNetworks(doc); + + if (!processed) processed = processEchoRequest(doc); + + if (!processed) { + Serial.println("❓[BLE] 未知命令"); + if (deviceConnected) { + JsonDocument errorDoc; + errorDoc["type"] = "error"; + errorDoc["message"] = "未知命令"; + errorDoc["receivedData"] = bleData; // 自动转义特殊字符 + + String responseMsg; + serializeJson(errorDoc, responseMsg); + + sendJSONDataToBLE(responseMsg); + } + } + } + } else { + Serial.println("📥 [BLE] 接收到非JSON数据"); + } + } +} + +/** + * @brief 处理设置设备ID命令 + * 处理来自BLE的设置设备ID请求 + * @param doc JSON文档对象 + * @return 是否处理成功 + */ +bool processSetDeviceId(JsonDocument& doc) { + const char* command = doc["command"]; + if (command != nullptr && strcmp(command, "setDeviceId") == 0) { + uint16_t newDeviceId = doc["newDeviceId"]; + + if (newDeviceId < 1000 || newDeviceId > 1999){ + Serial.printf("[错误] 设备ID超出范围,有效范围: 1000-1999\n"); + if (deviceConnected) { + String errorMsg = String("{\"type\":\"error\",\"message\":\"设备ID超出范围,有效范围: 1000-1999\"}"); + + sendJSONDataToBLE(errorMsg); + } + return true; + } + + currentDeviceId = newDeviceId; + + Serial.printf("[设备ID] 已设置新的设备ID: %u\n", currentDeviceId); + + saveDeviceId(); + Serial.printf("设备ID已保存到Flash: %u\n", currentDeviceId); + + if (deviceConnected) { + String confirmMsg = String("{\"type\":\"setDeviceIdResult\",\"success\":true,\"message\":\"设备ID设置成功\",\"newDeviceId\":") + + String(newDeviceId) + "}"; + + sendJSONDataToBLE(confirmMsg); + + sendStatusToBLE(); + } + return true; + } + return false; +} + +/** + * @brief 发送设备状态到BLE + * 将当前设备状态信息通过BLE发送给客户端 + */ +void sendStatusToBLE() { + if (deviceConnected) { + String statusMsg = String("{\"type\":\"status\",\"wifiConfigured\":") + + String(wifiManager.getSavedNetworkCount() > 0 ? "true" : "false") + + String(",\"wifiConnected\":") + + String(WiFi.status() == WL_CONNECTED ? "true" : "false") + + String(",\"ipAddress\":\"") + + (WiFi.status() == WL_CONNECTED ? WiFi.localIP().toString() : "") + "\"" + + String(",\"deviceId\":") + + String(currentDeviceId) + "}"; + + sendJSONDataToBLE(statusMsg); + Serial.println("已发送连接状态信息"); + } +} + +/** + * @brief 处理查询状态命令 + * 处理来自BLE的查询设备状态请求 + * @param doc JSON文档对象 + * @return 是否处理成功 + */ +bool processQueryStatus(JsonDocument& doc) { + const char* command = doc["command"]; + if (command != nullptr && strcmp(command, "queryStatus") == 0) { + if (deviceConnected) { + String statusMsg = String("{\"type\":\"deviceStatus\",\"success\":true,\"deviceId\":") + + String(currentDeviceId) + + String(",\"wifiConfigured\":") + + String(wifiManager.getSavedNetworkCount() > 0 ? "true" : "false") + + String(",\"wifiConnected\":") + + String(WiFi.status() == WL_CONNECTED ? "true" : "false") + + String(",\"ipAddress\":\"") + + (WiFi.status() == WL_CONNECTED ? WiFi.localIP().toString() : "") + + String("\"}"); + + sendJSONDataToBLE(statusMsg); + Serial.println("已发送设备状态信息"); + } + return true; + } + return false; +} + +/** + * @brief 处理WiFi配置命令 + * 处理来自BLE的WiFi配置请求 + * @param doc JSON文档对象 + * @return 是否处理成功 + */ +bool processWiFiConfigCommand(JsonDocument& doc) { + const char* command = doc["command"]; + if (command != nullptr && strcmp(command, "setWiFiConfig") == 0) { + Serial.println("📱 [BLE-WiFi] 收到WiFi配置命令"); + const char* newSSID = doc["ssid"]; + const char* newPassword = doc["password"]; + + if (newSSID != nullptr && newPassword != nullptr) { + return wifiManager.handleConfigurationData(newSSID, newPassword); + } else { + Serial.println("❌ [BLE-WiFi] WiFi配置参数不完整"); + if (deviceConnected) { + String errorMsg = String("{\"type\":\"error\",\"message\":\"WiFi配置参数不完整,需要ssid和password字段\"}"); + sendJSONDataToBLE(errorMsg); + } + return true; + } + } + return false; +} + +/** + * @brief 处理扫描WiFi命令 + * 处理来自BLE的WiFi扫描请求 + * @param doc JSON文档对象 + * @return 是否处理成功 + */ +bool processScanWiFi(JsonDocument& doc) { + const char* command = doc["command"]; + if (command != nullptr && strcmp(command, "scanWiFi") == 0) { + Serial.println("📱 [BLE-WiFi] 收到WiFi扫描命令"); + wifiManager.scanAndSendResults(); + return true; + } + return false; +} + +bool processGetSavedNetworks(JsonDocument& doc) { + const char* command = doc["command"]; + if (command != nullptr && strcmp(command, "getSavedNetworks") == 0) { + Serial.println("📱 [BLE-WiFi] 收到获取已保存WiFi网络命令"); + wifiManager.getSavedNetworks(); + return true; + } + return false; +} + +/** + * @brief 处理回显请求命令 + * 处理来自BLE的回显测试请求 + * @param doc JSON文档对象 + * @return 是否处理成功 + */ +bool processEchoRequest(JsonDocument& doc) { + const char* command = doc["command"]; + if (command != nullptr && strcmp(command, "echo") == 0) { + Serial.println("📱 [BLE] 收到回显请求"); + + const char* echoContent = doc["content"]; + if (echoContent != nullptr) { + String echoResponse = String("{\"type\":\"echoResponse\",\"originalContent\":\"") + + echoContent + String("\",\"receivedSuccessfully\":true}"); + + if (deviceConnected) { + sendJSONDataToBLE(echoResponse); + Serial.printf("📤 [BLE] 回显响应已发送: %s\n", echoContent); + } + } else { + String echoResponse = String("{\"type\":\"echoResponse\",\"receivedSuccessfully\":true,\"message\":\"Echo command received\"}"); + + if (deviceConnected) { + sendJSONDataToBLE(echoResponse); + Serial.println("📤 [BLE] 简单回显响应已发送"); + } + } + + return true; + } + return false; +} + +/** + * @brief 发送原始数据回显响应 + * 将接收到的原始数据通过BLE回显给客户端 + * @param rawData 原始数据字符串 + */ +void sendRawEchoResponse(const String& rawData) { + if (deviceConnected) { + String echoResponse = String("{\"type\":\"rawEchoResponse\",\"originalData\":\"") + + rawData + String("\",\"received\":true}"); + + sendJSONDataToBLE(echoResponse); + Serial.printf("📤 [BLE] 原始数据回显已发送: %s\n", rawData.c_str()); + } +} diff --git a/src/radar_manager.h b/src/radar_manager.h new file mode 100644 index 0000000..cb30051 --- /dev/null +++ b/src/radar_manager.h @@ -0,0 +1,235 @@ +#ifndef RADAR_MANAGER_H +#define RADAR_MANAGER_H + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +class WiFiManager; + +#define SERVICE_UUID "a8c1e5c0-3d5d-4a9d-8d5e-7c8b6a4e2f1a" // BLE服务UUID +#define CHARACTERISTIC_UUID "beb5483e-36e1-4688-b7f5-ea07361b26a8" // BLE特征值UUID +#define UART_RX_BUFFER_SIZE 4096 // UART接收缓冲区大小 +#define QUEUE_SIZE 50 // 队列大小 +#define TASK_STACK_SIZE 8192 // 任务堆栈大小 + +#define FRAME_HEADER1 0x53 // 帧头字节1 +#define FRAME_HEADER2 0x59 // 帧头字节2 +#define FRAME_TAIL1 0x54 // 帧尾字节1 +#define FRAME_TAIL2 0x43 // 帧尾字节2 + +// 控制字定义 +#define CTRL_PRESENCE 0x80 // 人体存在检测 +#define CTRL_BREATH 0x81 // 呼吸检测 +#define CTRL_SLEEP 0x84 // 睡眠监测 +#define CTRL_HEARTRATE 0x85 // 心率监测 + +// 命令字定义 +#define CMD_REPORT 0x80 // 主动上报 +#define CMD_QUERY 0x81 // 查询命令 +#define CMD_SET 0x82 // 设置命令 + +// 定义R60ABD1数据结构 +typedef struct { + uint8_t present; // 有人/无人状态 (DP1) + uint16_t distance; // 人体距离 (DP3) 单位cm + uint8_t heartRate; // 心率 (DP6) 单位BPM + uint8_t breathRate; // 呼吸率 (DP8) 单位次/分钟 + uint8_t heartWave; // 心率波形 (DP7) 数值+128 + uint8_t breathWave; // 呼吸波形 (DP10) 数值+128 + uint8_t sleepState; // 睡眠状态 (DP12) + uint32_t sleepTime; // 睡眠时长 (DP13) 单位秒 + uint8_t sleepScore; // 睡眠质量评分 (DP14) + uint8_t bedEntry; // 入床/离床状态 (DP11) + uint8_t abnormal; // 异常状态 (DP18) +} R60ABD1Data; // R60ABD1雷达数据结构体 + +typedef struct { // 传感器数据结构体 + float breath_rate; // 呼吸率 + float heart_rate; // 心率 + uint8_t breath_valid; // 呼吸率有效标志 + uint8_t heart_valid; // 心率有效标志 + uint8_t presence; // 存在状态 + uint8_t motion; // 运动状态 + int heartbeat_waveform; // 心跳波形 + int breathing_waveform; // 呼吸波形 + uint16_t distance; // 距离 + uint8_t body_movement; // 身体运动 + uint8_t breath_status; // 呼吸状态 + uint8_t sleep_state; // 睡眠状态 + uint32_t sleep_time; // 睡眠时长 + uint8_t sleep_score; // 睡眠评分 + uint8_t sleep_grade; // 睡眠等级 + uint8_t bed_entry; // 入床状态 + uint8_t abnormal_state; // 异常状态 + uint8_t avg_heart_rate; // 平均心率 + uint8_t avg_breath_rate; // 平均呼吸率 + uint8_t turn_count; // 翻身次数 + uint8_t large_move_ratio; // 大幅运动比例 + uint8_t small_move_ratio; // 小幅运动比例 + int16_t pos_x; // X坐标 + int16_t pos_y; // Y坐标 + int16_t pos_z; // Z坐标 + int8_t breath_waveform[5]; // 呼吸波形数组 + int8_t heart_waveform[5]; // 心跳波形数组 + uint16_t deep_sleep_time; // 深度睡眠时长 + uint16_t light_sleep_time; // 浅度睡眠时长 + uint16_t awake_time; // 清醒时长 + uint16_t sleep_total_time; // 总睡眠时长 + uint8_t deep_sleep_ratio; // 深度睡眠比例 + uint8_t light_sleep_ratio; // 浅度睡眠比例 + uint8_t awake_ratio; // 清醒比例 + uint8_t turnover_count; // 翻身计数 + uint8_t struggle_alert; // 挣扎警报 + uint8_t no_one_alert; // 无人警报 + uint8_t bed_status; // 床状态 + uint8_t bed_Out_Time; // 离床时间 + uint8_t apnea_count; // 呼吸暂停次数 +} SensorData; // 传感器数据结构体 + +typedef struct { // 相位数据结构体 + int heartbeat_waveform; // 心跳波形 + int breathing_waveform; // 呼吸波形 +} PhaseData; // 相位数据结构体 + +typedef struct { // 生命体征数据结构体 + float heart_rate; // 心率 + float breath_rate; // 呼吸率 + uint8_t presence; // 存在状态 + uint8_t motion; // 运动状态 + uint16_t distance; // 距离 + uint8_t sleep_state; // 睡眠状态 + uint8_t sleep_score; // 睡眠评分 + uint8_t body_movement; // 身体运动 + uint8_t breath_status; // 呼吸状态 + uint32_t sleep_time; // 睡眠时长 + uint8_t bed_entry; // 入床状态 + uint8_t abnormal_state; // 异常状态 + uint8_t avg_heart_rate; // 平均心率 + uint8_t avg_breath_rate; // 平均呼吸率 + uint8_t turn_count; // 翻身次数 + uint8_t large_move_ratio; // 大幅运动比例 + uint8_t small_move_ratio; // 小幅运动比例 + int16_t pos_x; // X坐标 + int16_t pos_y; // Y坐标 + int16_t pos_z; // Z坐标 + uint16_t deep_sleep_time; // 深度睡眠时长 + uint16_t light_sleep_time; // 浅度睡眠时长 + uint16_t awake_time; // 清醒时长 + uint16_t sleep_total_time; // 总睡眠时长 + uint8_t deep_sleep_ratio; // 深度睡眠比例 + uint8_t light_sleep_ratio; // 浅度睡眠比例 + uint8_t awake_ratio; // 清醒比例 + uint8_t turnover_count; // 翻身计数 + uint8_t struggle_alert; // 挣扎警报 + uint8_t no_one_alert; // 无人警报 + uint8_t bed_status; // 床状态 + uint8_t apnea_count; // 呼吸暂停次数 + int heartbeat_waveform; // 心跳波形 + int breathing_waveform; // 呼吸波形 +} VitalData; // 生命体征数据结构体 + +typedef struct { // 上次发送数据结构体 + float heart_rate; // 心率 + float breath_rate; // 呼吸率 + uint8_t presence; // 存在状态 + uint8_t motion; // 运动状态 + uint8_t sleep_state; // 睡眠状态 +} LastSentData; // 上次发送数据结构体 + +class BLEFlowController { // BLE流控制器类 +private: + size_t maxBytesPerSecond; // 最大每秒发送字节数 + size_t bytesSent; // 已发送字节数 + unsigned long lastResetTime; // 上次重置时间 + unsigned long lastSendTime; // 上次发送时间 + +public: + BLEFlowController(size_t maxBps); + bool canSend(size_t dataSize); + bool check(); + void recordSend(size_t dataSize); + void reset(); +}; + +extern SensorData sensorData; // 传感器数据 +extern HardwareSerial mySerial1; // 硬件串口1 +extern QueueHandle_t phaseDataQueue; // 相位数据队列 +extern QueueHandle_t vitalDataQueue; // 生命体征数据队列 +extern QueueHandle_t uartQueue; // UART数据队列 +extern TaskHandle_t bleSendTaskHandle; // BLE发送任务句柄 +extern TaskHandle_t vitalSendTaskHandle; // 生命体征发送任务句柄 +extern TaskHandle_t uartProcessTaskHandle; // UART处理任务句柄 +extern BLEServer* pServer; // BLE服务器指针 +extern BLECharacteristic* pCharacteristic; // BLE特征值指针 +extern bool deviceConnected; // 设备连接状态 +extern bool oldDeviceConnected; // 旧设备连接状态 +extern String receivedData; // 接收到的数据 +extern String completeData; // 完整数据 +extern unsigned long lastReceiveTime; // 上次接收数据时间 +extern bool continuousSendEnabled; // 持续发送使能标志 +extern unsigned long continuousSendInterval; // 持续发送间隔 +extern BLEFlowController bleFlow; // BLE流控制器 +extern unsigned long lastSensorUpdate; // 上次传感器更新时间 +extern LastSentData lastSentData; // 上次发送的数据 +extern unsigned long lastCheckTime; // 上次检测时间 + +extern uint16_t currentDeviceId; // 当前设备ID +extern Preferences preferences; // Flash存储对象 +extern WiFiManager wifiManager; // WiFi管理器 + +void initRadarManager(); +void initR60ABD1(); +bool parseR60ABD1Frame(uint8_t *frame, uint16_t frameLen); +int16_t parseSignedCoordinate(uint16_t raw_value); +void sendRadarCommand(uint8_t ctrl, uint8_t cmd, uint8_t value); +void IRAM_ATTR serialRxCallback(); + +void bleSendTask(void *parameter); +void vitalSendTask(void *parameter); +void radarDataTask(void *parameter); +void uartProcessTask(void *parameter); + +void sendDataInChunks(const String& data); +void sendJSONDataToBLE(const String& jsonData); +bool sendCustomJSONData(const String& jsonType, const String& jsonString); +void sendRadarDataToBLE(); + +bool processQueryRadarData(JsonDocument& doc); +bool processStartContinuousSend(JsonDocument& doc); +bool processStopContinuousSend(JsonDocument& doc); +void processBLEConfig(); + +bool processSetDeviceId(JsonDocument& doc); +bool processQueryStatus(JsonDocument& doc); +bool processWiFiConfigCommand(JsonDocument& doc); +bool processScanWiFi(JsonDocument& doc); +bool processGetSavedNetworks(JsonDocument& doc); +bool processEchoRequest(JsonDocument& doc); +void sendRawEchoResponse(const String& rawData); +void sendStatusToBLE(); + +void loadDeviceId(); +void saveDeviceId(); + +void sendDailyDataToInfluxDB(String dailyDataLine); +void sendSleepDataToInfluxDB(); + +class MyServerCallbacks: public BLEServerCallbacks { + void onConnect(BLEServer* pServer); + void onDisconnect(BLEServer* pServer); +}; + +class MyCallbacks: public BLECharacteristicCallbacks { + void onWrite(BLECharacteristic *pCharacteristic); +}; + +#endif diff --git a/src/wifi_manager.cpp b/src/wifi_manager.cpp new file mode 100644 index 0000000..8db9142 --- /dev/null +++ b/src/wifi_manager.cpp @@ -0,0 +1,782 @@ +#include "wifi_manager.h" + +// 外部变量和函数声明 +extern bool deviceConnected; +void sendJSONDataToBLE(const String& jsonData); +void setNetworkStatus(NetworkStatus status); + +/** + * @brief WiFi管理器构造函数 + * 初始化WiFi管理器的成员变量 + */ +WiFiManager::WiFiManager() { + savedNetworkCount = 0; + currentState = WIFI_IDLE; + lastReconnectAttempt = 0; + isScanning = false; +} + +/** + * @brief 初始化WiFi管理器 + * 开启Preferences存储,并加载保存的WiFi配置 + */ +void WiFiManager::begin() { + preferences.begin("wifi_manager", false); + loadWiFiConfigs(); +} + +/** + * @brief 加载保存的WiFi配置 + * 从Flash中读取之前保存的WiFi网络配置 + * @return 是否成功加载到配置 + */ +bool WiFiManager::loadWiFiConfigs() { + savedNetworkCount = 0; + + for (int i = 0; i < MAX_WIFI_NETWORKS; i++) { + String key = "wifi_" + String(i);// 构建WiFi配置键名 + String configStr = preferences.getString(key.c_str(), "");// 从Preferences中读取字符串 + + if (configStr.length() > 0) { + JsonDocument doc; + DeserializationError error = deserializeJson(doc, configStr);// 反序列化JSON字符串 + + if (!error) { + const char* ssid = doc["ssid"]; + const char* password = doc["password"]; + + if (ssid && password) { + strncpy(savedNetworks[savedNetworkCount].ssid, ssid, 31);// 保存SSID + savedNetworks[savedNetworkCount].ssid[31] = '\0'; + strncpy(savedNetworks[savedNetworkCount].password, password, 63); + savedNetworks[savedNetworkCount].password[63] = '\0'; + savedNetworkCount++; + + Serial.printf("📶 加载WiFi配置 %d: %s\n", savedNetworkCount, ssid); + } + } + } + } + + Serial.printf("✅ 共加载 %d 个WiFi配置\n", savedNetworkCount); + return savedNetworkCount > 0; +} + +/** + * @brief 保存WiFi配置 + * 将WiFi网络配置保存到Flash中 + * @param ssid WiFi网络名称 + * @param password WiFi网络密码 + * @return 是否保存成功 + */ +bool WiFiManager::saveWiFiConfig(const char* ssid, const char* password) { + // 检查是否已存在该网络配置 + for (int i = 0; i < savedNetworkCount; i++) { + // 已存在相同的SSID + if (strcmp(savedNetworks[i].ssid, ssid) == 0) { + // 更新现有配置 + strncpy(savedNetworks[i].password, password, 63);// 更新密码 + savedNetworks[i].password[63] = '\0'; + + JsonDocument doc; + doc["ssid"] = ssid; + doc["password"] = password; + String configStr; + serializeJson(doc, configStr);// 序列化JSON文档 + + String key = "wifi_" + String(i); + preferences.putString(key.c_str(), configStr);// 保存到Preferences + + Serial.printf("🔄 更新WiFi配置: %s\n", ssid); + return true; + } + } + + // 未找到相同的SSID,添加新的配置 + if (savedNetworkCount < MAX_WIFI_NETWORKS) { + strncpy(savedNetworks[savedNetworkCount].ssid, ssid, 31);// 保存SSID + savedNetworks[savedNetworkCount].ssid[31] = '\0'; + strncpy(savedNetworks[savedNetworkCount].password, password, 63);// 保存密码 + savedNetworks[savedNetworkCount].password[63] = '\0'; + + JsonDocument doc; + doc["ssid"] = ssid; + doc["password"] = password; + String configStr; + serializeJson(doc, configStr); + + String key = "wifi_" + String(savedNetworkCount); + preferences.putString(key.c_str(), configStr); + savedNetworkCount++; + + Serial.printf("➕ 新增WiFi配置: %s (总计: %d)\n", ssid, savedNetworkCount); + return true; + } + + Serial.println("❌ WiFi配置已满,无法添加"); + return false; +} + +/** + * @brief 连接到指定WiFi网络 + * 尝试连接到给定的WiFi网络 + * @param ssid WiFi网络名称 + * @param password WiFi网络密码 + * @return 是否连接成功 + */ +bool WiFiManager::connectToNetwork(const char* ssid, const char* password) { + Serial.printf("🌐 [WiFi] 尝试连接到 SSID: %s\n", ssid); + + currentState = WIFI_CONNECTING; + setNetworkStatus(NET_CONNECTING); + + WiFi.mode(WIFI_STA); + vTaskDelay(200 / portTICK_PERIOD_MS); + WiFi.begin(ssid, password); + + unsigned long startTime = millis(); + unsigned long lastStatusPrint = 0; + + while (WiFi.status() != WL_CONNECTED && (millis() - startTime) < WIFI_CONNECT_TIMEOUT) { + if (millis() - lastStatusPrint >= 500) { + Serial.printf("[WiFi] 连接中,状态: %d\n", WiFi.status()); + lastStatusPrint = millis(); + } + yield(); + vTaskDelay(50 / portTICK_PERIOD_MS); + } + + if (WiFi.status() == WL_CONNECTED) { + Serial.println("✅ [WiFi] 连接成功!"); + Serial.printf("🌐 IP地址: %s\n", WiFi.localIP().toString().c_str()); + Serial.printf("🔒 信号强度: %d dBm\n", WiFi.RSSI()); + + currentState = WIFI_CONNECTED; + setNetworkStatus(NET_CONNECTED); + + // 向蓝牙发送当前连接的WiFi配置信息 + if (deviceConnected) { + JsonDocument doc; + doc["type"] = "wifiConnected"; + doc["success"] = true; + doc["ssid"] = ssid; + doc["password"] = password; + doc["ip"] = WiFi.localIP().toString(); + doc["rssi"] = WiFi.RSSI(); + + String jsonStr; + serializeJson(doc, jsonStr); + sendJSONDataToBLE(jsonStr); + + Serial.printf("📱 [BLE] 发送WiFi连接信息: %s\n", jsonStr.c_str()); + } + + return true; + } else { + Serial.println("❌ [WiFi] 连接超时"); + currentState = WIFI_DISCONNECTED; + setNetworkStatus(NET_DISCONNECTED); + + // 向蓝牙发送连接失败信息 + if (deviceConnected) { + JsonDocument doc; + doc["type"] = "wifiConnected"; + doc["success"] = false; + doc["ssid"] = ssid; + doc["message"] = "WiFi连接失败,请检查密码是否正确"; + + String jsonStr; + serializeJson(doc, jsonStr); + sendJSONDataToBLE(jsonStr); + + Serial.printf("📱 [BLE] 发送WiFi连接失败信息: %s\n", jsonStr.c_str()); + } + + return false; + } +} + +/** + * @brief 扫描并匹配WiFi网络 + * 扫描附近的WiFi网络,并尝试匹配已保存的配置 + * 如果找到多个匹配的网络,按信号强度从强到弱依次尝试连接 + * @return 是否成功连接到匹配的网络 + */ +bool WiFiManager::scanAndMatchNetworks() { + if (isScanning) { + Serial.println("⏳ [WiFi] 正在扫描中,等待扫描完成..."); + int waitCount = 0; + while (isScanning && waitCount < 50) { + vTaskDelay(100 / portTICK_PERIOD_MS); + waitCount++; + } + + if (isScanning) { + Serial.println("⚠️ [WiFi] 等待超时,跳过本次扫描"); + return false; + } + + Serial.println("✅ [WiFi] 扫描已完成,开始新的扫描"); + } + + Serial.println("🔍 [WiFi] 开始扫描WiFi网络..."); + currentState = WIFI_SCANNING; + isScanning = true; + + if (WiFi.status() == WL_CONNECTED) { + Serial.println("📶 WiFi已连接,断开后扫描"); + WiFi.disconnect(false); + vTaskDelay(200 / portTICK_PERIOD_MS); + } + + WiFi.mode(WIFI_STA); + vTaskDelay(200 / portTICK_PERIOD_MS); + + int n = WiFi.scanNetworks(); + Serial.printf("🔍 扫描到 %d 个WiFi网络\n", n); + + if (n <= 0) { + Serial.println("❌ 未扫描到任何WiFi网络或扫描失败"); + currentState = WIFI_DISCONNECTED; + isScanning = false; + return false; + } + + // 收集所有匹配的、信号强度符合要求的网络 + struct CandidateNetwork { + const char* ssid; + const char* password; + int rssi; + }; + + CandidateNetwork availableNetworks[MAX_WIFI_NETWORKS]; + int availableCount = 0; + + // 遍历已保存的网络,寻找匹配的网络 + for (int i = 0; i < savedNetworkCount; i++) { + for (int j = 0; j < n; j++) { + if (WiFi.SSID(j) == String(savedNetworks[i].ssid)) { + int rssi = WiFi.RSSI(j); + Serial.printf("📶 找到匹配网络: %s, 信号: %d dBm\n", + savedNetworks[i].ssid, rssi); + + // 检查信号强度是否符合要求 + if (rssi >= MIN_RSSI_THRESHOLD) { + // 添加到可用网络列表 + if (availableCount < MAX_WIFI_NETWORKS) { + availableNetworks[availableCount].ssid = savedNetworks[i].ssid; + availableNetworks[availableCount].password = savedNetworks[i].password; + availableNetworks[availableCount].rssi = rssi; + availableCount++; + Serial.printf("✅ 添加到候选列表: %s, 信号: %d dBm\n", + savedNetworks[i].ssid, rssi); + } + } else { + Serial.printf("⚠️ 信号强度过低,跳过\n"); + } + break; // 找到匹配后跳出内层循环 + } + } + } + + // 如果没有找到任何可用网络 + if (availableCount == 0) { + Serial.println("❌ 未找到匹配的WiFi网络或信号过弱"); + WiFi.scanDelete(); + currentState = WIFI_DISCONNECTED; + isScanning = false; + return false; + } + + // 按信号强度从强到弱排序(冒泡排序) + for (int i = 0; i < availableCount - 1; i++) { + for (int j = 0; j < availableCount - i - 1; j++) { + if (availableNetworks[j].rssi < availableNetworks[j + 1].rssi) { + CandidateNetwork temp = availableNetworks[j]; + availableNetworks[j] = availableNetworks[j + 1]; + availableNetworks[j + 1] = temp; + } + } + } + + Serial.printf("📋 找到 %d 个可用网络,按信号强度排序:\n", availableCount); + for (int i = 0; i < availableCount; i++) { + Serial.printf(" %d. %s (信号: %d dBm)\n", i + 1, + availableNetworks[i].ssid, availableNetworks[i].rssi); + } + + WiFi.scanDelete(); + + // 依次尝试连接所有可用网络 + for (int i = 0; i < availableCount; i++) { + Serial.printf("🔄 [%d/%d] 尝试连接: %s (信号: %d dBm)\n", + i + 1, availableCount, + availableNetworks[i].ssid, + availableNetworks[i].rssi); + + vTaskDelay(300 / portTICK_PERIOD_MS); + + if (connectToNetwork(availableNetworks[i].ssid, availableNetworks[i].password)) { + Serial.printf("✅ 成功连接到: %s\n", availableNetworks[i].ssid); + isScanning = false; + return true; + } + + // 连接失败,继续尝试下一个 + Serial.printf("❌ %s 连接失败\n", availableNetworks[i].ssid); + + // 如果不是最后一个,准备尝试下一个 + if (i < availableCount - 1) { + Serial.println("➡️ 尝试下一个网络..."); + } + } + + Serial.println("❌ 所有可用网络均连接失败"); + currentState = WIFI_DISCONNECTED; + isScanning = false; + + // 向蓝牙发送所有WiFi连接失败信息 + if (deviceConnected) { + String wifiList = "{\"type\":\"wifiConnected\",\"success\":false,\"message\":\"所有保存的WiFi均连接失败\",\"networks\":["; + bool first = true; + for (int i = 0; i < availableCount; i++) { + if (!first) wifiList += ","; + wifiList += "{\"ssid\":\"" + String(availableNetworks[i].ssid) + "\",\"rssi\":" + String(availableNetworks[i].rssi) + "}"; + first = false; + } + wifiList += "],\"count\":" + String(availableCount) + "}"; + + sendJSONDataToBLE(wifiList); + Serial.printf("📱 [BLE] 发送所有WiFi连接失败信息: %s\n", wifiList.c_str()); + } + + return false; +} + +/** + * @brief 初始化WiFi连接 + * 启动WiFi初始化过程,尝试连接到已保存的网络 + * @return 是否初始化成功 + */ +bool WiFiManager::initializeWiFi() { + Serial.println("🚀 [WiFi] 初始化WiFi连接..."); + + if (savedNetworkCount == 0) { + Serial.println("⚠️ 未保存的WiFi配置"); + currentState = WIFI_IDLE; + return false; + } + + if (scanAndMatchNetworks()) { + Serial.println("✅ WiFi初始化成功"); + return true; + } else { + Serial.println("❌ WiFi初始化失败"); + currentState = WIFI_DISCONNECTED; + return false; + } +} + +/** + * @brief 扫描WiFi网络并发送结果 + * 扫描附近的WiFi网络,过滤信号弱的网络,将结果通过BLE发送给客户端 + */ +void WiFiManager::scanAndSendResults() { + if (isScanning) { + Serial.println("⏳ [WiFi] 正在扫描中,等待扫描完成..."); + int waitCount = 0; + while (isScanning && waitCount < 50) { + vTaskDelay(100 / portTICK_PERIOD_MS); + waitCount++; + } + + if (isScanning) { + Serial.println("⚠️ [WiFi] 等待超时,跳过本次扫描"); + if (deviceConnected) { + String errorMsg = String("{\"type\":\"scanWiFiResult\",\"success\":false,\"message\":\"等待扫描超时,请稍后再试\",\"networks\":[],\"count\":0}"); + sendJSONDataToBLE(errorMsg); + } + return; + } + + Serial.println("✅ [WiFi] 扫描已完成,开始新的扫描"); + } + + Serial.println("📱 [BLE-WiFi] 开始WiFi扫描..."); + isScanning = true; + + // 确保完全断开WiFi + if (WiFi.status() == WL_CONNECTED) { + Serial.println("📶 WiFi已连接,断开后扫描"); + WiFi.disconnect(true); + vTaskDelay(500 / portTICK_PERIOD_MS); + } + + // 更完整的WiFi初始化 + WiFi.mode(WIFI_OFF); + vTaskDelay(100 / portTICK_PERIOD_MS); + + WiFi.mode(WIFI_STA); + vTaskDelay(500 / portTICK_PERIOD_MS); + + // 尝试多次扫描,提高成功率 + int n = 0; + int retryCount = 3; + + while (n <= 0 && retryCount > 0) { + Serial.printf("🔍 扫描WiFi网络 (尝试 %d/3)...\n", 4 - retryCount); + // 使用更可靠的扫描参数:被动扫描,包含隐藏SSID,更长的扫描时间 + n = WiFi.scanNetworks(false, true, true, 5000); // 5000ms扫描时间 + + if (n <= 0) { + Serial.printf("❌ 扫描失败,返回值: %d, 剩余重试: %d\n", n, retryCount - 1); + retryCount--; + vTaskDelay(1000 / portTICK_PERIOD_MS); + } + } + + Serial.printf("🔍 最终扫描到 %d 个WiFi网络\n", n); + + if (n <= 0) { + Serial.println("❌ 未扫描到任何WiFi网络或扫描失败"); + isScanning = false; + if (deviceConnected) { + String errorMsg = String("{\"type\":\"scanWiFiResult\",\"success\":false,\"message\":\"未扫描到任何WiFi网络或扫描失败\",\"networks\":[],\"count\":0}"); + sendJSONDataToBLE(errorMsg); + } + return; + } + + // 构建WiFi网络列表的JSON数据 + String wifiList = String("{\"type\":\"scanWiFiResult\",\"success\":true,\"count\":") + String(n) + String(",\"networks\":["); + + bool first = true; + for (int i = 0; i < n; ++i) { + if (WiFi.RSSI(i) >= MIN_RSSI_THRESHOLD) { + if (!first) { + wifiList += ","; + } + wifiList += String("{\"ssid\":\"") + WiFi.SSID(i) + String("\",\"rssi\":") + + String(WiFi.RSSI(i)) + String(",\"channel\":") + + String(WiFi.channel(i)) + String(",\"encryption\":"); + + // 根据加密类型添加相应的描述 + switch (WiFi.encryptionType(i)) { + case WIFI_AUTH_OPEN: + wifiList += String("\"open\""); + break; + case WIFI_AUTH_WEP: + wifiList += String("\"WEP\""); + break; + case WIFI_AUTH_WPA_PSK: + wifiList += String("\"WPA\""); + break; + case WIFI_AUTH_WPA2_PSK: + wifiList += String("\"WPA2\""); + break; + case WIFI_AUTH_WPA_WPA2_PSK: + wifiList += String("\"WPA/WPA2\""); + break; + case WIFI_AUTH_WPA2_ENTERPRISE: + wifiList += String("\"WPA2-EAP\""); + break; + case WIFI_AUTH_WPA3_PSK: + wifiList += String("\"WPA3\""); + break; + case WIFI_AUTH_WPA2_WPA3_PSK: + wifiList += String("\"WPA2/WPA3\""); + break; + default: + wifiList += String("\"unknown\""); + break; + } + wifiList += "}"; + first = false; + } + } + + wifiList += "]}"; + + Serial.printf("✅ 发送WiFi扫描结果,包含 %d 个可用网络\n", first ? 0 : n); + + WiFi.scanDelete(); + isScanning = false; + + if (deviceConnected) { + sendJSONDataToBLE(wifiList); + } +} + +/** + * @brief 开始配网模式 + * 进入配网模式,扫描WiFi网络并发送结果给客户端 + * @return 是否成功进入配网模式 + */ +bool WiFiManager::startConfiguration() { + Serial.println("⚙️ [WiFi] 开始配网模式..."); + currentState = WIFI_CONFIGURING; + scanAndSendResults(); + return true; +} + +/** + * @brief 处理配网数据 + * 处理从客户端收到的WiFi配网信息,先扫描WiFi是否有匹配的网络,再尝试连接并保存 + * @param ssid WiFi网络名称 + * @param password WiFi网络密码 + * @return 是否配置成功 + */ +bool WiFiManager::handleConfigurationData(const char* ssid, const char* password) { + Serial.printf("📱 [BLE-WiFi] 收到配网信息: SSID='%s'\n", ssid); + + if (ssid == nullptr || password == nullptr || strlen(ssid) == 0) { + Serial.println("❌ 配网参数无效"); + return false; + } + + if (isScanning) { + Serial.println("⏳ [WiFi] 正在扫描中,等待扫描完成..."); + int waitCount = 0; + while (isScanning && waitCount < 50) { + vTaskDelay(100 / portTICK_PERIOD_MS); + waitCount++; + } + + if (isScanning) { + Serial.println("⚠️ [WiFi] 等待超时"); + if (deviceConnected) { + String errorMsg = String("{\"type\":\"wifiConfigResult\",\"success\":false,\"message\":\"等待扫描超时,请稍后再试\"}"); + sendJSONDataToBLE(errorMsg); + } + return false; + } + + Serial.println("✅ [WiFi] 扫描已完成,开始新的扫描"); + } + + // 先扫描WiFi网络,检查是否存在匹配的网络 + Serial.println("🔍 [WiFi] 扫描WiFi网络,检查是否存在匹配的网络..."); + currentState = WIFI_SCANNING; + isScanning = true; + + int n = WiFi.scanNetworks(); + Serial.printf("🔍 扫描到 %d 个WiFi网络\n", n); + + if (n == 0) { + Serial.println("❌ 未扫描到任何WiFi网络"); + WiFi.scanDelete(); + isScanning = false; + currentState = WIFI_DISCONNECTED; + + if (deviceConnected) { + String resultMsg = String("{\"type\":\"wifiConfigResult\",\"success\":false,\"message\":\"未扫描到任何WiFi网络,请检查设备位置\"}"); + sendJSONDataToBLE(resultMsg); + } + return false; + } + + bool networkFound = false; + bool signalTooWeak = false; + int foundRssi = 0; + + for (int i = 0; i < n; i++) { + if (WiFi.SSID(i) == String(ssid)) { + foundRssi = WiFi.RSSI(i); + Serial.printf("📶 找到匹配网络: %s, 信号: %d dBm\n", ssid, foundRssi); + + if (foundRssi >= MIN_RSSI_THRESHOLD) { + Serial.printf("✅ 信号强度符合要求,准备连接...\n"); + networkFound = true; + break; + } else { + Serial.printf("⚠️ 信号强度过低,跳过\n"); + signalTooWeak = true; + } + } + } + + if (!networkFound) { + String errorMsg; + + if (signalTooWeak) { + errorMsg = String("{\"type\":\"wifiConfigResult\",\"success\":false,\"message\":\"目标WiFi信号过弱,请将设备靠近路由器\"}"); + Serial.printf("❌ 目标WiFi信号过弱: %d dBm (阈值: %d dBm)\n", foundRssi, MIN_RSSI_THRESHOLD); + } else { + errorMsg = String("{\"type\":\"wifiConfigResult\",\"success\":false,\"message\":\"未找到目标WiFi网络,请检查WiFi名称是否正确\"}"); + Serial.println("❌ 未找到目标WiFi网络"); + } + + WiFi.scanDelete(); + isScanning = false; + currentState = WIFI_DISCONNECTED; + + if (deviceConnected) { + sendJSONDataToBLE(errorMsg); + } + return false; + } + + WiFi.scanDelete(); + isScanning = false; + vTaskDelay(300 / portTICK_PERIOD_MS); + + // 尝试连接到指定网络 + if (connectToNetwork(ssid, password)) { + // 连接成功后保存配置 + if (saveWiFiConfig(ssid, password)) { + Serial.println("✅ WiFi配置成功并已保存"); + + if (deviceConnected) { + String resultMsg = String("{\"type\":\"wifiConfigResult\",\"success\":true,\"message\":\"WiFi配置成功\"}"); + sendJSONDataToBLE(resultMsg); + } + return true; + } + } + + Serial.println("❌ WiFi配置失败"); + isScanning = false; + currentState = WIFI_DISCONNECTED; + + if (deviceConnected) { + String resultMsg = String("{\"type\":\"wifiConfigResult\",\"success\":false,\"message\":\"WiFi配置失败,请检查密码是否正确\"}"); + sendJSONDataToBLE(resultMsg); + } + return false; +} + +/** + * @brief 处理WiFi重连 + * 检查WiFi连接状态,当断开连接时尝试重连 + */ +void WiFiManager::handleReconnect() { + // 检查当前是否已连接 + if (currentState == WIFI_CONNECTED) { + if (WiFi.status() == WL_CONNECTED) { + return; + } + // 连接已断开 + currentState = WIFI_DISCONNECTED; + setNetworkStatus(NET_DISCONNECTED); + Serial.println("⚠️ WiFi连接断开"); + + // 扫描并发送WiFi列表到蓝牙 + scanAndSendResults(); + } + + // 处理重连逻辑 + if (currentState == WIFI_DISCONNECTED) { + unsigned long currentTime = millis(); + + // 按照设定的间隔尝试重连 + if (currentTime - lastReconnectAttempt >= WIFI_RECONNECT_INTERVAL) { + lastReconnectAttempt = currentTime; + + if (scanAndMatchNetworks()) { + Serial.println("✅ WiFi重连成功"); + } else { + Serial.println("❌ WiFi重连失败,2秒后重试"); + } + } + } +} + +/** + * @brief 获取当前WiFi状态 + * @return 当前的WiFi管理器状态 + */ +WiFiManagerState WiFiManager::getState() { + return currentState; +} + +/** + * @brief 检查WiFi是否已连接 + * @return WiFi是否已成功连接 + */ +bool WiFiManager::isConnected() { + return currentState == WIFI_CONNECTED && WiFi.status() == WL_CONNECTED; +} + +/** + * @brief 断开WiFi连接 + * 主动断开当前的WiFi连接 + */ +void WiFiManager::disconnect() { + WiFi.disconnect(true); + currentState = WIFI_DISCONNECTED; + setNetworkStatus(NET_DISCONNECTED); +} + +/** + * @brief 添加WiFi配置 + * 向保存的配置中添加新的WiFi网络 + * @param ssid WiFi网络名称 + * @param password WiFi网络密码 + * @return 是否添加成功 + */ +bool WiFiManager::addWiFiConfig(const char* ssid, const char* password) { + return saveWiFiConfig(ssid, password); +} + +/** + * @brief 清除所有WiFi配置 + * 删除所有保存的WiFi网络配置 + */ +void WiFiManager::clearAllConfigs() { + for (int i = 0; i < MAX_WIFI_NETWORKS; i++) { + String key = "wifi_" + String(i); + preferences.remove(key.c_str()); + } + savedNetworkCount = 0; + Serial.println("🗑️ 已清除所有WiFi配置"); +} + +/** + * @brief 获取已保存的网络数量 + * @return 已保存的WiFi网络配置数量 + */ +int WiFiManager::getSavedNetworkCount() { + return savedNetworkCount; +} + +/** + * @brief 获取已保存的WiFi网络列表 + * 将保存的WiFi网络配置通过BLE发送给客户端 + */ +void WiFiManager::getSavedNetworks() { + Serial.printf("📋 [WiFi] 获取已保存的WiFi网络列表,共 %d 个\n", savedNetworkCount); + + if (savedNetworkCount == 0) { + Serial.println("⚠️ 没有保存的WiFi网络"); + if (deviceConnected) { + String responseMsg = String("{\"type\":\"savedNetworks\",\"success\":true,\"count\":0,\"networks\":[]}"); + sendJSONDataToBLE(responseMsg); + } + return; + } + + String wifiList = String("{\"type\":\"savedNetworks\",\"success\":true,\"count\":") + String(savedNetworkCount) + String(",\"networks\":["); + + for (int i = 0; i < savedNetworkCount; i++) { + if (i > 0) { + wifiList += ","; + } + wifiList += String("{\"ssid\":\"") + String(savedNetworks[i].ssid) + String("\"}"); + } + + wifiList += "]}"; + + Serial.printf("📤 [WiFi] 发送已保存的WiFi网络列表: %s\n", wifiList.c_str()); + + if (deviceConnected) { + sendJSONDataToBLE(wifiList); + } +} + +/** + * @brief 更新WiFi管理器状态 + * 定期调用此函数,处理WiFi重连等状态管理 + */ +void WiFiManager::update() { + handleReconnect(); +} \ No newline at end of file diff --git a/src/wifi_manager.h b/src/wifi_manager.h new file mode 100644 index 0000000..aeaffa5 --- /dev/null +++ b/src/wifi_manager.h @@ -0,0 +1,122 @@ +#ifndef WIFI_MANAGER_H +#define WIFI_MANAGER_H + +#include +#include +#include +#include + +/** + * @brief 最大WiFi网络配置数量 + * 定义设备可以保存的WiFi网络配置的最大数量 + */ +#define MAX_WIFI_NETWORKS 10 + +/** + * @brief 最小信号强度阈值 + * 定义WiFi网络信号强度的最低要求,低于此值的网络会被过滤 + * 单位:dBm + */ +#define MIN_RSSI_THRESHOLD -200 + +/** + * @brief WiFi连接超时时间 + * 定义WiFi连接的最大等待时间,超过此时间认为连接失败 + * 单位:毫秒 + */ +#define WIFI_CONNECT_TIMEOUT 3000 + +/** + * @brief WiFi重连间隔时间 + * 定义WiFi断开后,尝试重新连接的时间间隔 + * 单位:毫秒 + */ +#define WIFI_RECONNECT_INTERVAL 2000 + +/** + * @brief WiFi网络信息结构 + * 存储WiFi网络的详细信息,用于扫描和显示 + */ +typedef struct { + char ssid[32]; // WiFi网络名称 + char password[64]; // WiFi网络密码 + int rssi; // 信号强度,单位:dBm + uint8_t channel; // WiFi通道 + uint8_t encryption; // 加密类型 +} WiFiNetworkInfo; + +/** + * @brief WiFi配置结构 + * 存储WiFi网络的配置信息,用于保存和加载 + */ +typedef struct { + char ssid[32]; // WiFi网络名称 + char password[64]; // WiFi网络密码 +} WiFiConfig; + +/** + * @brief WiFi管理器状态枚举 + * 定义WiFi管理器的不同工作状态 + */ +enum WiFiManagerState { + WIFI_IDLE, // 空闲状态 + WIFI_SCANNING, // 扫描网络中 + WIFI_CONNECTING, // 连接网络中 + WIFI_CONNECTED, // 已连接 + WIFI_DISCONNECTED, // 断开连接 + WIFI_CONFIGURING // 配网模式 +}; + +/** + * @brief 网络状态枚举 + * 定义网络的不同状态,用于LED控制 + */ +enum NetworkStatus { + NET_INITIAL, // 初始化/未连接 - 慢闪 + NET_CONNECTING, // 连接中 - 快闪 + NET_CONNECTED, // 已连接 - 呼吸灯 + NET_DISCONNECTED // 断开连接 - 慢闪 +}; + +/** + * @brief WiFi管理器类 + * 负责WiFi网络的扫描、连接、配置和管理 + */ +class WiFiManager { +private: + Preferences preferences; // 用于存储WiFi配置的Preferences对象 + WiFiConfig savedNetworks[MAX_WIFI_NETWORKS]; // 保存的WiFi网络配置数组 + int savedNetworkCount; // 已保存的网络数量 + WiFiManagerState currentState; // 当前WiFi管理器状态 + unsigned long lastReconnectAttempt; // 上次尝试重连的时间 + bool isScanning; // 是否正在扫描 + + bool scanAndMatchNetworks(); // 扫描并匹配网络 + bool connectToNetwork(const char* ssid, const char* password); // 连接到指定网络 + void sendScanResultsViaBLE(); // 发送扫描结果到BLE + bool saveWiFiConfig(const char* ssid, const char* password); // 保存WiFi配置 + bool loadWiFiConfigs(); // 加载WiFi配置 + +public: + WiFiManager(); // 构造函数 + void begin(); // 初始化WiFi管理器 + + bool initializeWiFi(); // 初始化WiFi连接 + bool startConfiguration(); // 开始配网模式 + bool handleConfigurationData(const char* ssid, const char* password); // 处理配网数据 + void handleReconnect(); // 处理重连 + + WiFiManagerState getState(); // 获取当前状态 + bool isConnected(); // 检查是否已连接 + void disconnect(); // 断开连接 + + void scanAndSendResults(); // 扫描并发送结果 + bool addWiFiConfig(const char* ssid, const char* password); // 添加WiFi配置 + void clearAllConfigs(); // 清除所有配置 + int getSavedNetworkCount(); // 获取已保存的网络数量 + void getSavedNetworks(); // 获取已保存的WiFi网络列表 + + void update(); // 更新WiFi管理器状态 +}; + +#endif \ No newline at end of file diff --git a/test/README b/test/README new file mode 100644 index 0000000..9b1e87b --- /dev/null +++ b/test/README @@ -0,0 +1,11 @@ + +This directory is intended for PlatformIO Test Runner and project tests. + +Unit Testing is a software testing method by which individual units of +source code, sets of one or more MCU program modules together with associated +control data, usage procedures, and operating procedures, are tested to +determine whether they are fit for use. Unit testing finds problems early +in the development cycle. + +More information about PlatformIO Unit Testing: +- https://docs.platformio.org/en/latest/advanced/unit-testing/index.html diff --git a/test_json_parser.js b/test_json_parser.js new file mode 100644 index 0000000..7e518f3 --- /dev/null +++ b/test_json_parser.js @@ -0,0 +1,82 @@ +// JSON分包接收处理函数 +let streamBuffer = "" + +function addLog(message) { + console.log(message); +} + +function handleParsedJson(obj) { + console.log("处理解析后的JSON对象:", obj); +} + +function processChunk(fragment){ + addLog('进入processChunk') + streamBuffer += fragment + let braceCount = 0 + let jsonStart = -1 + for(let i = 0; i < streamBuffer.length; i++){ + const ch = streamBuffer[i] + + if(ch === "{"){ + if(braceCount === 0){ + jsonStart = i + } + braceCount++ + }else if(ch === "}"){ + braceCount-- + if(braceCount === 0 && jsonStart !== -1){ + const jsonStr = streamBuffer.substring(jsonStart, i + 1) + addLog("收到完整JSON:" + jsonStr) + try{ + const obj = JSON.parse(jsonStr) + handleParsedJson(obj) + }catch(e){ + addLog("JSON解析失败:" + e) + } + streamBuffer = streamBuffer.substring(i + 1) + i = -1 + jsonStart = -1 + } + } + } + //缓冲区过大时清理防越界 + if (streamBuffer.length > 20000) { + addLog("⚠ 清空超大缓冲区(异常保护)") + streamBuffer = "" + braceCount = 0 + jsonStart = -1 + } +} + +// 模拟分包发送JSON数据 +function simulateJSONSending() { + const jsonData = { + type: "radarData", + deviceId: 1001, + timestamp: Date.now(), + presence: 1, + heartRate: 72.5, + breathRate: 16.2, + motion: 0, + rssi: -45, + heartbeatWaveform: 120, + breathingWaveform: 45, + rawSignal: -25 + }; + + const jsonString = JSON.stringify(jsonData); + console.log("原始JSON数据:", jsonString); + + // 模拟分包发送 + const packetSize = 15; // 每包15个字符 + for (let i = 0; i < jsonString.length; i += packetSize) { + const packet = jsonString.substring(i, Math.min(i + packetSize, jsonString.length)); + console.log(`发送分包 ${Math.floor(i/packetSize)+1}:`, packet); + processChunk(packet); + } +} + +// 测试函数 +console.log("=== 开始测试JSON分包接收处理 ==="); +simulateJSONSending(); +console.log("=== 测试完成 ==="); \ No newline at end of file