From a19a30470bfd2d62d7d610829c1702971de8a8c2 Mon Sep 17 00:00:00 2001 From: Admin Date: Sat, 28 Feb 2026 14:52:02 +0800 Subject: [PATCH] Update project with radar and wifi manager modules --- .trae/documents/plan_20260203_063543.md | 58 + BLE_API.md | 516 ++++ src/Radar.cpp | 0 src/Radar.h | 36 - src/main.cpp | 2963 ++++------------------- src/main_backup.cpp.bak | 2582 ++++++++++++++++++++ src/radar_manager.cpp | 1721 +++++++++++++ src/radar_manager.h | 235 ++ src/wifi_manager.cpp | 572 +++++ src/wifi_manager.h | 121 + 10 files changed, 6237 insertions(+), 2567 deletions(-) create mode 100644 .trae/documents/plan_20260203_063543.md create mode 100644 BLE_API.md delete mode 100644 src/Radar.cpp delete mode 100644 src/Radar.h create mode 100644 src/main_backup.cpp.bak create mode 100644 src/radar_manager.cpp create mode 100644 src/radar_manager.h create mode 100644 src/wifi_manager.cpp create mode 100644 src/wifi_manager.h diff --git a/.trae/documents/plan_20260203_063543.md b/.trae/documents/plan_20260203_063543.md new file mode 100644 index 0000000..0da299c --- /dev/null +++ b/.trae/documents/plan_20260203_063543.md @@ -0,0 +1,58 @@ +# 修改蓝牙发送雷达数据模式 + +## 1. 需求分析 +- 修改蓝牙发送机制,实现基于数据变化的发送 +- 当心率、呼吸、存在检测、体动状态、睡眠状态发生变化时才发送 +- 根据小程序设定的时间间隔进行定时检测 +- 如果检测到变化,立即更新数据 +- 按设定间隔检测数据更新,有变化则发送全部数据 + +## 2. 实现方案 + +### 2.1 添加数据存储结构 +- 在 `radar_manager.h` 中添加 `LastSentData` 结构体,用于存储上一次发送的数据 +- 包含需要检测变化的字段:心率、呼吸率、存在状态、运动状态、睡眠状态 + +### 2.2 修改全局变量 +- 在 `radar_manager.cpp` 中添加 `lastSentData` 全局变量 +- 添加 `lastCheckTime` 变量用于记录上次检测时间 + +### 2.3 实现数据变化检测函数 +- 添加 `isDataChanged()` 函数,比较当前数据与上次发送数据 +- 实现阈值判断,避免微小波动导致频繁发送 + +### 2.4 重写 BLE 数据发送任务 +- 修改 `bleSendTask` 任务,实现以下逻辑: + 1. 定时检测数据变化(基于 `continuousSendInterval`) + 2. 检测到变化时立即发送数据 + 3. 发送后更新 `lastSentData` 为当前数据 + 4. 保持与现有命令处理的兼容性 + +### 2.5 保持命令处理逻辑 +- 保留 `processStartContinuousSend` 和 `processStopContinuousSend` 函数 +- 确保小程序可以正常控制发送模式和间隔 + +## 3. 代码修改点 + +### 3.1 radar_manager.h +- 添加 `LastSentData` 结构体定义 +- 在全局变量声明中添加 `lastSentData` 和相关变量 + +### 3.2 radar_manager.cpp +- 初始化 `lastSentData` 变量 +- 实现 `isDataChanged()` 函数 +- 重写 `bleSendTask` 任务函数 +- 确保数据发送格式与现有代码保持一致 + +## 4. 技术要点 +- 使用阈值检测避免微小数据波动导致的频繁发送 +- 保持定时检测机制,确保数据及时性 +- 维持与现有 BLE API 的兼容性 +- 按照现有代码的注释格式编写新代码 +- 确保内存使用合理,避免内存泄漏 + +## 5. 预期效果 +- 减少不必要的蓝牙数据传输 +- 只在数据发生实际变化时发送 +- 保持数据的实时性和准确性 +- 与现有小程序控制逻辑完全兼容 \ 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/src/Radar.cpp b/src/Radar.cpp deleted file mode 100644 index e69de29..0000000 diff --git a/src/Radar.h b/src/Radar.h deleted file mode 100644 index 416b754..0000000 --- a/src/Radar.h +++ /dev/null @@ -1,36 +0,0 @@ -#ifndef RADAR_H -#define RADAR_H -// 在main.cpp顶部添加R60ABD1协议相关定义 -#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; - - -#endif diff --git a/src/main.cpp b/src/main.cpp index 478b01c..f7f3330 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -3,35 +3,17 @@ #include #include #include -#include -#include -#include -#include -#include -#include -#include -#include -#include // 添加这个头文件以支持memset函数 -#include -#include // 用于数学函数 +#include "wifi_manager.h" +#include "radar_manager.h" // ESP32 GPIO控制演示 #define BOOT_BUTTON_PIN 0 // Boot按钮引脚 -#define NETWORK_LED_PIN 5 // 网络状态LED指示灯 +#define NETWORK_LED_PIN 48 // 网络状态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 // 断开连接 - 慢闪 -}; +uint8_t WiFi_Connect_First_bit = 1; // WiFi首次连接标志位 // 配置清除指示灯状态枚举 enum ConfigClearStatus { @@ -41,660 +23,474 @@ enum ConfigClearStatus { CONFIG_COMPLETED // 清除完成 - 快速闪烁3次 }; -// 网络状态全局变量 -NetworkStatus currentNetworkStatus = NET_INITIAL; -unsigned long lastBlinkTime = 0; -bool ledState = false; -int breatheValue = 0; -bool breatheIncreasing = true; +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; -bool configLedState = false; -int configBreatheValue = 0; -bool configBreatheIncreasing = true; +ConfigClearStatus currentConfigClearStatus = CONFIG_NORMAL; // 当前配置清除状态 +unsigned long lastConfigBlinkTime = 0; // 上次配置清除LED闪烁时间 +bool configLedState = false; // 配置清除LED状态 +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; // 呼吸灯步进值 +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; // 呼吸灯亮度步进值 -// 协议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; // 睡眠状态 +const uint16_t MIN_DEVICE_ID = 1000; // 最小设备ID +const uint16_t MAX_DEVICE_ID = 1999; // 最大设备ID -// 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; // 呼吸暂停次数 +const unsigned long CLEAR_CONFIG_DURATION = 3000; // 清除配置持续时间(毫秒) -bool clearConfigRequested = false; +Preferences preferences; // Flash存储对象 +WiFiManager wifiManager; // WiFi管理器对象 -unsigned long bootButtonPressTime = 0; +uint16_t currentDeviceId = 0000; // 当前设备ID +bool clearConfigRequested = false; // 清除配置请求标志 +bool forceLedOff = false; // 强制关闭LED标志 -const unsigned long CLEAR_CONFIG_DURATION = 3000; // 长按3秒清除配置 +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(); -#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;// 声明一个Preferences对象 - -// 设备ID变量 -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; - -WiFiClient client; -char ssid[32] = ""; -char password[64] = ""; -bool wifiConfigured = false; - -// WiFi监控相关变量 -unsigned long lastWiFiCheckTime = 0; -const unsigned long WIFI_CHECK_INTERVAL = 500; // 每0.5秒检查一次WiFi状态 -int wifiReconnectAttempts = 0; -const int MAX_RECONNECT_ATTEMPTS = 10;// 最大重连尝试次数 - -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 = 2; // UART1的RX引脚,配合原理图 -// const int UART1_TX = 3; // UART1的TX引脚,配合原理图 - -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(); // 处理BLE配置命令 -void saveWiFiConfig(); // 保存WiFi配置 -void loadWiFiConfig(); // 加载WiFi配置 -bool connectWiFi(); // 连接WiFi -bool processWiFiConfigCommand(JsonDocument& doc); // 处理WiFi配置命令 - -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获取对应的字段名 + +/** + * @brief 根据协议ID获取字段名称 + * 将协议ID映射到对应的字段名称,用于数据序列化和反序列化 + * @param protocolId 协议ID + * @return 对应的字段名称字符串 + */ String getFieldNameByProtocolId(int protocolId) { switch(protocolId) { - case PROTOCOL_HEART_RATE: + case 1: return "heartRate"; - case PROTOCOL_BREATH_RATE: + case 2: return "breathingRate"; - case PROTOCOL_PERSON_DETECTED: + case 13: return "personDetected"; - case PROTOCOL_HUMAN_ACTIVITY: + case 14: return "humanActivity"; - case PROTOCOL_HUMAN_DISTANCE: + case 15: return "humanDistance"; - case PROTOCOL_HUMAN_POSITION: + case 16: return "humanPosition"; - case PROTOCOL_SLEEP_STATE: + case 17: 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(); - } +/** + * @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按钮未按下,正常启动"); } } -class MyServerCallbacks: public BLEServerCallbacks { - void onConnect(BLEServer* pServer) { - deviceConnected = true; - Serial.println("✅ [BLE] 客户端已连接"); - - // 发送连接状态信息 - sendStatusToBLE(); - }; +/** + * @brief 加载设备ID + * 从Flash中读取保存的设备ID + */ +void loadDeviceId() { + currentDeviceId = preferences.getUShort("deviceId", 1001); + Serial.printf("从Flash加载设备ID: %u\n", currentDeviceId); +} - void onDisconnect(BLEServer* pServer) { - deviceConnected = false; - Serial.println("🔴 [BLE] 客户端已断开"); - - // 重置持续发送状态 - continuousSendEnabled = false; - Serial.println("🔄 重置持续发送状态"); +/** + * @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(); } -}; +} -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]; +/** + * @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; - Serial.printf("📄 [BLE] 接收数据片段: %s\n", fragment.c_str()); + case CONFIG_PREPARING: + analogWrite(CONFIG_CLEAR_PIN, 255); + break; - 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); + case CONFIG_CLEARING: + if (millis() - lastConfigBlinkTime >= BREATHE_INTERVAL) { + analogWrite(CONFIG_CLEAR_PIN, configBreatheValue); - Serial.printf("📥 [BLE] 完整JSON数据: %s\n", jsonData.c_str()); - - // 将JSON数据存储到receivedData变量中供后续处理 - receivedData = jsonData; + 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); - // 第一步:检查Boot按钮是否被按下 checkBootButton(); - // Serial.println("⏳ 等待雷达启动 (30秒),配置清除指示灯正在进行呼吸灯闪烁..."); - // unsigned long startTime = millis(); - // const unsigned long waitDuration = 30000; // 30秒 + analogWrite(CONFIG_CLEAR_PIN, 0); - // while (millis() - startTime < waitDuration) { - // unsigned long elapsed = millis() - startTime; - // float progress = (float)(elapsed % 2000) / 2000.0; // 每2秒一个周期 - - // // 使用正弦波函数创建平滑的呼吸效果 - // float brightness = (sin(progress * 2 * PI) + 1) / 2; // 0到1之间 - // int pwmValue = (int)(brightness * 255); - - // // 使用analogWrite设置亮度 - // analogWrite(CONFIG_CLEAR_PIN, pwmValue); - - // // 短暂延时以允许其他任务运行 - // delay(50); - - // // 每隔一秒打印一次倒计时 - // if (elapsed / 1000 != (elapsed - 50) / 1000) { - // Serial.print(waitDuration/1000 - elapsed/1000); - // Serial.print("..."); - // } - // } - // Serial.println(); - - // 确保结束后恢复到正常状态 - 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,输出模式 + pinMode(BOOT_BUTTON_PIN, INPUT); + pinMode(NETWORK_LED_PIN, OUTPUT); + pinMode(CONFIG_CLEAR_PIN, OUTPUT); + pinMode(GPIO8, OUTPUT); + pinMode(GPIO9, OUTPUT); - // 初始化配置清除指示灯为LOW(正常运行状态) digitalWrite(CONFIG_CLEAR_PIN, LOW); digitalWrite(GPIO8, LOW); digitalWrite(GPIO9, LOW); - // 初始状态设置 - digitalWrite(NETWORK_LED_PIN, LOW); // 网络状态LED,输出模式,输出低电平 - digitalWrite(CONFIG_CLEAR_PIN, LOW); + digitalWrite(NETWORK_LED_PIN, LOW); + 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 + ledcSetup(0, 5000, 8); + ledcSetup(1, 5000, 8); + ledcAttachPin(NETWORK_LED_PIN, 0); + ledcAttachPin(CONFIG_CLEAR_PIN, 1); - // 注册WiFi事件 WiFi.onEvent(WiFiEvent); - - // 设置初始网络状态 setNetworkStatus(NET_INITIAL); - esp_task_wdt_init(30, true); // 初始化任务看门狗,30秒超时 + esp_task_wdt_init(30, true); 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字节"); - delay(1000); + preferences.begin("radar_data", false); - preferences.begin("radar_data", false); // 在setup()中打开命名空间 + wifiManager.begin(); - // 加载设备ID和WiFi配置 Serial.println("💾 加载设备配置..."); - loadDeviceId();// 加载设备ID - loadWiFiConfig();// 加载WiFi配置 + loadDeviceId(); - Serial.println("🏗️ 创建FreeRTOS队列..."); - // 创建FreeRTOS队列 - phaseDataQueue = xQueueCreate(QUEUE_SIZE, sizeof(PhaseData)); - vitalDataQueue = xQueueCreate(QUEUE_SIZE, sizeof(VitalData)); - uartQueue = xQueueCreate(2048, sizeof(char)); // 创建串口数据队列 + Serial.println("🏗️ 初始化雷达管理器..."); + initRadarManager(); - 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, + xTaskCreate( + configClearLedTask, + "Config Clear LED Task", + 2048, NULL, - 3, // 提高优先级到3 - &bleSendTaskHandle, - 1 + 1, + NULL ); - xTaskCreatePinnedToCore( - vitalSendTask, - "VitalSendTask", - TASK_STACK_SIZE, + xTaskCreate( + bootButtonMonitorTask, + "Boot Button Monitor Task", + 2048, NULL, - 2, // 中等优先级 - &vitalSendTaskHandle, - 1 + 1, + NULL ); - // 创建专门的雷达数据处理任务 - xTaskCreatePinnedToCore( - radarDataTask, - "RadarProcessTask", + xTaskCreate( + ledControlTask, + "LED Control Task", + 2048, + NULL, + 1, + NULL + ); + + xTaskCreate( + wifiMonitorTask, + "WiFi Monitor Task", 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 // 任务句柄 + 2, + NULL ); Serial.println("✅ FreeRTOS任务创建成功"); @@ -726,20 +522,17 @@ void setup() { Serial.println(String("BLE已启动,设备名称: Radar_") + String(currentDeviceId)); Serial.println("🌐 检查WiFi配置..."); - if (wifiConfigured) { - Serial.println("💾 检测到已保存的WiFi配置,尝试连接..."); - loadWiFiConfig();// 加载WiFi配置 - if (connectWiFi()) { + if (wifiManager.getSavedNetworkCount() > 0) { + Serial.printf("💾 检测到 %d 个已保存的WiFi配置,尝试连接...\n", wifiManager.getSavedNetworkCount()); + if (wifiManager.initializeWiFi()) { Serial.println("✅ WiFi连接成功!"); } else { Serial.println("❌ WiFi连接失败,请通过BLE重新配置"); - wifiConfigured = false; } } 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); @@ -749,31 +542,39 @@ void setup() { if(WiFi_Connect_First_bit == 0) { - while (WiFi.status() != WL_CONNECTED) { // 等待WiFi连接 - delay(1000); - Serial.println("等待WiFi连接..."); + 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连接成功!"); - // 初始化R60ABD1雷达模组 - initR60ABD1(); // 调用R60ABD1初始化函数 + initR60ABD1(); Serial.println("🎉 系统初始化完成,等待雷达数据..."); - // 启动时发送一次睡眠数据 if (WiFi.status() == WL_CONNECTED) { Serial.println("🌅 启动时发送睡眠数据到数据库"); sendSleepDataToInfluxDB(); } } +/** + * @brief 主循环函数 + * 处理BLE连接状态和定期发送雷达命令 + */ void loop() { esp_task_wdt_reset(); if (!deviceConnected && oldDeviceConnected) { - delay(500); + vTaskDelay(500 / portTICK_PERIOD_MS); pServer->startAdvertising(); Serial.println("开始BLE广播"); oldDeviceConnected = deviceConnected; @@ -781,30 +582,28 @@ void loop() { 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} // 睡眠质量评级 + {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秒 + 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++; @@ -815,1904 +614,6 @@ void loop() { processBLEConfig(); esp_task_wdt_reset(); esp_task_wdt_reset(); - // updateStatusFlags(); - // 在主循环中添加更频繁的看门狗重置 esp_task_wdt_reset(); - delay(1); } - -// 处理查询状态命令 -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(wifiConfigured ? "true" : "false") + - String(",\"wifiConnected\":") + - String(WiFi.status() == WL_CONNECTED ? "true" : "false") + - String(",\"ipAddress\":\"") + - (WiFi.status() == WL_CONNECTED ? WiFi.localIP().toString() : "") + - String("\"}"); - - // 使用新的分包发送函数发送JSON数据 - 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); - delay(20); // 进一步减少等待时间以提高实时性 - } - } - - 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); - delay(10); // 减少等待时间以提高实时性 - } - } - - 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) { - String responseMsg = String("{\"type\":\"error\",\"message\":\"配置格式错误,请使用JSON格式\"}"); - - // 使用新的分包发送函数发送JSON数据 - sendJSONDataToBLE(responseMsg); - } - } 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); - - // 如果没有处理任何命令,发送错误响应 - if (!processed) { - Serial.println("❓[BLE] 未知命令"); - if (deviceConnected) { - String responseMsg = String("{\"type\":\"error\",\"message\":\"未知命令\"}"); - - // 使用新的分包发送函数发送JSON数据 - sendJSONDataToBLE(responseMsg); - } - } - } - } 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(), wifiConfigured ? "是" : "否"); - 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 saveWiFiConfig() { - preferences.putString("ssid", ssid);// 保存SSID到Flash - preferences.putString("password", password);// 保存密码到Flash - preferences.putBool("configured", true);// 保存配置标志到Flash - Serial.println("WiFi配置已保存到Flash"); -} - -void loadWiFiConfig() { - wifiConfigured = preferences.getBool("configured", false);// 从Flash加载配置标志 - if (wifiConfigured) - { - preferences.getString("ssid", ssid, sizeof(ssid));// 从Flash加载SSID - preferences.getString("password", password, sizeof(password));// 从Flash加载密码 - Serial.printf("从Flash加载WiFi配置 - SSID: %s\n", ssid); - } -} - -bool connectWiFi() { - Serial.println("🌐 [WiFi] 开始连接到网络..."); - Serial.printf("🌐 [WiFi] 尝试连接到 SSID: %s\n", ssid); - - // 设置网络状态为连接中 - setNetworkStatus(NET_CONNECTING); - - WiFi.mode(WIFI_STA); - WiFi.begin(ssid, password); - - Serial.println("[WiFi] 正在尝试连接,超时时间: 15秒..."); - - // 记录开始时间,用于15秒超时检测 - unsigned long startTime = millis(); - const unsigned long WIFI_CONNECT_TIMEOUT = 15000; // 15秒超时 - - while (WiFi.status() != WL_CONNECTED && (millis() - startTime) < WIFI_CONNECT_TIMEOUT) { - delay(500); - Serial.printf("[WiFi] 尝试连接中,当前状态: %d\n", WiFi.status()); - // 在WiFi连接过程中重置看门狗 - esp_task_wdt_reset(); - } - - // 检查连接结果 - if (WiFi.status() == WL_CONNECTED) { - Serial.println("✅ [WiFi] 连接成功!"); - Serial.printf("🌐 [WiFi] 分配的IP地址: %s\n", WiFi.localIP().toString().c_str()); - Serial.printf("🔒 [WiFi] 信号强度: %d dBm\n", WiFi.RSSI()); - - // 设置网络状态为已连接 - setNetworkStatus(NET_CONNECTED); - - // 将首次连接标志置0并保存到Flash,以后开机不再阻塞等待连接 - WiFi_Connect_First_bit = 0; - preferences.putBytes("wifi_first", &WiFi_Connect_First_bit, sizeof(WiFi_Connect_First_bit)); - Serial.println("[WiFi] 已将 WiFi_Connect_First_bit 置零并保存到Flash"); - - return true; - } else { - Serial.println("❌ [WiFi] 连接超时,未能成功连接到WiFi。"); - Serial.printf("⚠️ [WiFi] 最终状态码: %d\n", WiFi.status()); - - // 检查是否是因为超时导致的连接失败,如果是,发送蓝牙通知 - if ((millis() - startTime) >= WIFI_CONNECT_TIMEOUT) { - Serial.println("⏰ [WiFi] 连接超时,15秒内未能连接成功"); - bool connected = connectWiFi(); - if (deviceConnected) { - String resultMsg = String("{\"type\":\"wifiConfigResult\",\"success\":") + - String(connected ? "true" : "false") + - String(",\"message\":\"") + - String(connected ? "WiFi配置成功" : "WiFi配置失败") + - String("\"}"); - // 使用新的分包发送函数发送JSON数据 - sendJSONDataToBLE(resultMsg); - } - } - - // 设置网络状态为断开 - setNetworkStatus(NET_DISCONNECTED); - - return false; - } -} - -// 修正后的发送雷达命令函数 -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(wifiConfigured ? "true" : "false") + - String(",\"wifiConnected\":") + - String(WiFi.status() == WL_CONNECTED ? "true" : "false") + - String(",\"ipAddress\":\"") + - (WiFi.status() == WL_CONNECTED ? WiFi.localIP().toString() : "") + "\"" + - String(",\"deviceId\":") + - String(currentDeviceId) + "}"; - - // 使用新的分包发送函数发送JSON数据 - sendJSONDataToBLE(statusMsg); - Serial.println("已发送连接状态信息"); - } -} - -// 处理WiFi配置命令 -bool processWiFiConfigCommand(JsonDocument& doc) { - const char* command = doc["command"]; - if (command != nullptr && strcmp(command, "setWiFiConfig") == 0) { - Serial.println("📱 [BLE-WiFi] 收到WiFi配置命令"); - // 获取WiFi配置参数 - const char* newSSID = doc["ssid"]; - const char* newPassword = doc["password"]; - - if (newSSID != nullptr && newPassword != nullptr) { - Serial.printf("📱 [BLE-WiFi] 接收到新的WiFi配置: SSID='%s', Password='[HIDDEN]'", newSSID); - - // 更新WiFi配置 - strncpy(ssid, newSSID, sizeof(ssid)-1); - ssid[sizeof(ssid)-1] = '\0'; // 确保字符串结束符 - strncpy(password, newPassword, sizeof(password)-1); - password[sizeof(password)-1] = '\0'; // 确保字符串结束符 - - // 保存WiFi配置到Flash - saveWiFiConfig(); - - // 尝试连接WiFi - Serial.println("🔄 [WiFi] 尝试连接到新配置的网络..."); - bool connected = connectWiFi(); - - // 发送配置结果 - if (deviceConnected) { - String resultMsg = String("{\"type\":\"wifiConfigResult\",\"success\":") + - String(connected ? "true" : "false") + - String(",\"message\":\"") + - String(connected ? "WiFi配置成功" : "WiFi配置失败") + - String("\"}"); - // 使用新的分包发送函数发送JSON数据 - sendJSONDataToBLE(resultMsg); - Serial.printf("✅ [BLE] 发送WiFi配置结果: %s\n", connected ? "成功" : "失败"); - } - - return true; // 已处理该命令 - } else { - // 参数不完整 - Serial.println("❌ [BLE-WiFi] WiFi配置参数不完整"); - if (deviceConnected) { - String errorMsg = String("{\"type\":\"error\",\"message\":\"WiFi配置参数不完整,需要ssid和password字段\"}"); - // 使用新的分包发送函数发送JSON数据 - sendJSONDataToBLE(errorMsg); - Serial.println("📱 [BLE] 发送WiFi配置错误信息"); - } - 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按钮未按下,正常启动"); - } -} - -// 清除所有存储的配置(Flash 与内存副本) -void clearStoredConfig() { - Serial.println("🧹 开始清除存储的配置..."); - - // 打开Preferences命名空间 - preferences.begin("radar_data", false); - - // 记录当前配置(用于显示) - uint16_t oldDeviceId = preferences.getUShort("deviceId", 0); - String oldSSID = preferences.getString("ssid", ""); - - // 清除所有配置项(从Flash中移除) - preferences.remove("deviceId"); - preferences.remove("ssid"); - preferences.remove("password"); - preferences.remove("configured"); - preferences.remove("wifi_first");// 清除首次连接标志位 - - preferences.end(); - - // 显示清除结果 - Serial.println("✅ 配置已清除完成"); - Serial.printf("🗑️ 被清除的设备ID: %u\n", oldDeviceId); - Serial.printf("🗑️ 被清除的WiFi SSID: %s\n", oldSSID.c_str()); - Serial.println("🎯 系统将恢复出厂设置"); - - // 重置全局变量(内存副本) - currentDeviceId = 1001; // 恢复默认设备ID - ssid[0] = '\0'; - password[0] = '\0'; - wifiConfigured = false; - WiFi_Connect_First_bit = 1; // 标记首次连接标志位 - - // 立即断开WiFi并重置连接相关计数/状态,确保清除立即生效 - WiFi.disconnect(true); // 立即断开并清除驱动层缓存的凭证 - wifiReconnectAttempts = 0; - setNetworkStatus(NET_DISCONNECTED); - - Serial.println("🔄 已清除Flash与内存中的配置,请重新配置WiFi和设备ID"); - - // 若有BLE连接,通知客户端当前状态 - 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; - } -} - -// WiFi监控任务 -void wifiMonitorTask(void *parameter) { - Serial.println("📡 WiFi监控任务启动"); - - while(1) { - // 定期检查WiFi连接状态 - if(millis() - lastWiFiCheckTime >= WIFI_CHECK_INTERVAL) { - if(WiFi.status() != WL_CONNECTED) { - // WiFi已断开,尝试重连 - Serial.println("⚠️ 检测到WiFi连接断开,尝试重连..."); - loadWiFiConfig(); // 加载WiFi配置 - // 如果重连尝试次数超过限制,稍等更长时间再试 - if(wifiReconnectAttempts >= MAX_RECONNECT_ATTEMPTS) { - Serial.println("🔄 达到最大重连尝试次数,等待更长时间再试..."); - delay(250); - wifiReconnectAttempts = 0; // 重置计数器 - } - - if(connectWiFi()) { - Serial.println("✅ WiFi重连成功!"); - wifiReconnectAttempts = 0; // 重置重连尝试计数 - } else { - Serial.println("❌ WiFi重连失败"); - wifiReconnectAttempts++; - - // 指数退避:每次失败后等待更长时间 - int delayTime = (int)(pow(2, wifiReconnectAttempts) * 1000); - if(delayTime > 2000) delayTime = 2000; // 限制最大延迟时间为2秒 - Serial.printf("⏳ 等待 %d 秒后再次尝试重连...\n", delayTime/1000); - delay(delayTime); - } - } else { - // WiFi连接正常,重置重连尝试计数 - if(wifiReconnectAttempts > 0) { - wifiReconnectAttempts = 0; - } - } - - lastWiFiCheckTime = millis(); - } - - vTaskDelay(500 / portTICK_PERIOD_MS); // 每500ms检查一次 - } -} \ No newline at end of file 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..fb14ced --- /dev/null +++ b/src/radar_manager.cpp @@ -0,0 +1,1721 @@ +#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) { + String responseMsg = String("{\"type\":\"error\",\"message\":\"未知命令\",\"receivedData\":\"") + bleData + String("\"}"); + + 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..4e12bbc --- /dev/null +++ b/src/wifi_manager.cpp @@ -0,0 +1,572 @@ +#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; +} + +/** + * @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); + String configStr = preferences.getString(key.c_str(), ""); + + if (configStr.length() > 0) { + JsonDocument doc; + DeserializationError error = deserializeJson(doc, configStr); + + if (!error) { + const char* ssid = doc["ssid"]; + const char* password = doc["password"]; + + if (ssid && password) { + strncpy(savedNetworks[savedNetworkCount].ssid, ssid, 31); + 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++) { + 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); + + String key = "wifi_" + String(i); + preferences.putString(key.c_str(), configStr); + + Serial.printf("🔄 更新WiFi配置: %s\n", ssid); + return true; + } + } + + // 添加新配置 + if (savedNetworkCount < MAX_WIFI_NETWORKS) { + strncpy(savedNetworks[savedNetworkCount].ssid, ssid, 31); + 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); + 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); + + return true; + } else { + Serial.println("❌ [WiFi] 连接超时"); + currentState = WIFI_DISCONNECTED; + setNetworkStatus(NET_DISCONNECTED); + return false; + } +} + +/** + * @brief 扫描并匹配WiFi网络 + * 扫描附近的WiFi网络,并尝试匹配已保存的配置 + * 如果找到多个匹配的网络,优先连接信号强度最强的那一组 + * @return 是否成功连接到匹配的网络 + */ +bool WiFiManager::scanAndMatchNetworks() { + Serial.println("🔍 [WiFi] 开始扫描WiFi网络..."); + currentState = WIFI_SCANNING; + + WiFi.disconnect(true); + vTaskDelay(100 / portTICK_PERIOD_MS); + WiFi.mode(WIFI_STA); + vTaskDelay(100 / portTICK_PERIOD_MS); + + int n = WiFi.scanNetworks(); + Serial.printf("🔍 扫描到 %d 个WiFi网络\n", n); + + if (n <= 0) { + Serial.println("❌ 未扫描到任何WiFi网络或扫描失败"); + currentState = WIFI_DISCONNECTED; + return false; + } + + // 收集所有匹配的、信号强度符合要求的网络 + struct { + const char* ssid; + const char* password; + int rssi; + } bestNetwork = {nullptr, nullptr, -1000}; // 初始化为非常弱的信号 + + // 遍历已保存的网络,寻找匹配的网络 + 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 (rssi > bestNetwork.rssi) { + bestNetwork.ssid = savedNetworks[i].ssid; + bestNetwork.password = savedNetworks[i].password; + bestNetwork.rssi = rssi; + Serial.printf("📈 更新最佳网络: %s, 信号: %d dBm\n", + bestNetwork.ssid, bestNetwork.rssi); + } + } else { + Serial.printf("⚠️ 信号强度过低,跳过\n"); + } + } + } + } + + // 如果找到最佳网络,尝试连接 + if (bestNetwork.ssid != nullptr) { + Serial.printf("✅ 选择信号最强的网络: %s, 信号: %d dBm\n", + bestNetwork.ssid, bestNetwork.rssi); + if (connectToNetwork(bestNetwork.ssid, bestNetwork.password)) { + return true; + } + } + + Serial.println("❌ 未找到匹配的WiFi网络或信号过弱"); + currentState = WIFI_DISCONNECTED; + 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() { + Serial.println("📱 [BLE-WiFi] 开始WiFi扫描..."); + + WiFi.disconnect(true); + vTaskDelay(100 / portTICK_PERIOD_MS); + WiFi.mode(WIFI_STA); + vTaskDelay(100 / portTICK_PERIOD_MS); + + int n = WiFi.scanNetworks(); + Serial.printf("🔍 扫描到 %d 个WiFi网络\n", n); + + if (n <= 0) { + Serial.println("❌ 未扫描到任何WiFi网络或扫描失败"); + 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); + + 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; + } + + // 先扫描WiFi网络,检查是否存在匹配的网络 + Serial.println("🔍 [WiFi] 扫描WiFi网络,检查是否存在匹配的网络..."); + int n = WiFi.scanNetworks(); + Serial.printf("🔍 扫描到 %d 个WiFi网络\n", n); + + if (n == 0) { + Serial.println("❌ 未扫描到任何WiFi网络"); + + 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网络"); + } + + if (deviceConnected) { + sendJSONDataToBLE(errorMsg); + } + return false; + } + + // 尝试连接到指定网络 + 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配置失败"); + + 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连接断开"); + } + + // 处理重连逻辑 + 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..bd95c64 --- /dev/null +++ b/src/wifi_manager.h @@ -0,0 +1,121 @@ +#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 15000 + +/** + * @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 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