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/.trae/documents/plan_20260204_063627.md b/.trae/documents/plan_20260204_063627.md new file mode 100644 index 0000000..b08a0ee --- /dev/null +++ b/.trae/documents/plan_20260204_063627.md @@ -0,0 +1,37 @@ +## 实现BLE API规范计划 + +### 步骤1: 分析现有实现 +- 检查main_backup.cpp.bak中的BLE命令处理函数实现 +- 确认它们是否符合BLE_API.md中定义的API规范 +- 识别需要移植的函数 + +### 步骤2: 移植BLE命令处理函数 +- 将以下函数从main_backup.cpp.bak移植到main.cpp中: + - processWiFiConfigCommand() - 处理WiFi配置命令 + - processScanWiFi() - 处理WiFi扫描命令 + - processGetSavedNetworks() - 处理获取已保存网络命令 + - processEchoRequest() - 处理回显测试命令 + - processSetDeviceId() - 处理设置设备ID命令 + - processQueryStatus() - 处理查询状态命令 + - processQueryRadarData() - 处理查询雷达数据命令 + - processStartContinuousSend() - 处理启动持续发送命令 + - processStopContinuousSend() - 处理停止持续发送命令 + +### 步骤3: 更新processBLEConfig函数 +- 更新main.cpp中的processBLEConfig()函数,添加命令处理逻辑 +- 实现JSON解析和命令分发功能 +- 确保按照API规范处理所有支持的命令 + +### 步骤4: 添加必要的辅助函数 +- 添加sendRawEchoResponse()函数,处理回显测试响应 +- 确保所有函数都按照API规范返回正确的响应格式 + +### 步骤5: 验证实现 +- 编译项目,确保没有错误 +- 检查所有函数是否符合API规范 +- 确认响应格式是否与API文档一致 + +### 预期结果 +- 项目能够成功编译 +- BLE命令处理函数符合BLE_API.md中定义的API规范 +- 设备能够正确处理所有支持的BLE命令并返回符合规范的响应 \ No newline at end of file diff --git a/.trae/documents/修复编译错误计划.md b/.trae/documents/修复编译错误计划.md new file mode 100644 index 0000000..78c7b61 --- /dev/null +++ b/.trae/documents/修复编译错误计划.md @@ -0,0 +1,26 @@ +# 修复编译错误计划 + +## 问题分析 +编译时出现了多个错误,主要是因为删除了 `radar_manager.cpp` 和 `radar_manager.h` 文件后,`main.cpp` 中仍然引用了这些文件中定义的变量和函数。 + +## 修复步骤 + +### 1. 修复 SAMPLE_RATE 重复定义 +- 删除 `main.cpp` 中的 `SAMPLE_RATE` 定义,使用 `radar_vitals.h` 中的定义 + +### 2. 添加缺失的 BLE 相关代码 +- 添加 `MyServerCallbacks` 类定义 +- 添加 `MyCallbacks` 类定义 +- 添加 `processBLEConfig` 函数定义 +- 添加 `sendStatusToBLE` 函数定义 + +### 3. 修复其他未声明的变量和函数 +- 确保所有使用的变量和函数都有正确的声明 + +### 4. 测试编译 +- 运行 PlatformIO 编译命令,确保所有错误都已修复 + +## 预期结果 +- 编译成功,没有错误 +- 系统能够正常启动和运行 +- 新的雷达模块能够正常工作 \ No newline at end of file diff --git a/.trae/documents/删除无用代码优化项目.md b/.trae/documents/删除无用代码优化项目.md new file mode 100644 index 0000000..cead5a5 --- /dev/null +++ b/.trae/documents/删除无用代码优化项目.md @@ -0,0 +1,49 @@ +# 删除无用代码优化项目 + +## 目标 +在保持API功能不变的情况下,删除项目中没有用的代码部分,减少Flash占用,提高代码可读性。 + +## 分析结果 +通过对代码库的分析,我发现了以下可以删除的无用部分: + +### 1. main.cpp +- **I2S相关配置**:第17-18行的I2S配置定义,因为使用的是直接ADC读取方式 +- **setup_i2s()函数**:第127-131行,只是一个空函数,没有实际功能 +- **重复的雷达处理调用**:第166行的`radar_vitals_process(&sample, &vitals);`调用,因为已经在第163行调用了`radar_process(I_buf, Q_buf, DATA_LEN);` +- **冗余的WiFi连接成功消息**:第357行,总是会执行,即使WiFi连接失败 +- **注释掉的代码**:第361-364行,已经被注释掉,没有实际功能 + +### 2. radar_vitals.cpp +- **旧的信号处理函数**:第29-41行的`bp_breath`和`bp_heart`函数,使用的是新的处理方法 +- **重复的相位解缠函数**:第44-50行的`unwrap`函数,在`radar_process`中已经实现 +- **旧的BPM计算函数**:第53-80行的`calc_bpm`函数,使用的是新的频率估计方法 +- **旧的主处理函数**:第203-252行的`radar_vitals_process`函数,使用的是新的`radar_process`函数 +- **未使用的系统参数**:第12-15行的实时系统参数定义 +- **未使用的缓冲区大小**:第18行的`RADAR_BUFFER_SIZE`定义 +- **未使用的队列长度**:第19行的`QUEUE_LENGTH`定义 +- **未使用的变量**:第14-17行的`phase_buf`、`buf_idx`、`I_mean`、`Q_mean`、`prev_phase`变量 + +### 3. ble_api.cpp +- **未使用的函数**:第60-73行的`sendCustomJSONData`函数 +- **未使用的函数**:第194-197行的`sendRawEchoResponse`函数 +- **无效的超时处理**:第369-378行的超时处理代码,因为相关变量没有被设置 + +### 4. io_flash.h +- **重复的常量定义**:第40-50行的常量定义,已经在`io_flash.cpp`中定义 + +### 5. io_flash.cpp +- **重复的局部变量**:第15-23行的局部变量定义,在函数内部已经有局部定义 + +## 实施步骤 +1. **修改main.cpp**:删除I2S相关配置、setup_i2s()函数、重复的雷达处理调用、冗余的WiFi连接成功消息和注释掉的代码 +2. **修改radar_vitals.cpp**:删除旧的信号处理函数、重复的相位解缠函数、旧的BPM计算函数、旧的主处理函数、未使用的系统参数和变量 +3. **修改ble_api.cpp**:删除未使用的函数和无效的超时处理代码 +4. **修改io_flash.h**:删除重复的常量定义 +5. **修改io_flash.cpp**:删除重复的局部变量 +6. **编译项目**:确保项目能够成功编译,没有错误 +7. **验证功能**:确保API功能保持不变 + +## 预期结果 +- 减少Flash占用,提高代码可读性 +- 保持API功能不变,确保系统正常运行 +- 消除冗余代码,减少维护成本 \ No newline at end of file diff --git a/.trae/documents/备份并删除radar_manager.h文件.md b/.trae/documents/备份并删除radar_manager.h文件.md new file mode 100644 index 0000000..6777a7c --- /dev/null +++ b/.trae/documents/备份并删除radar_manager.h文件.md @@ -0,0 +1,22 @@ +## 备份并删除radar_manager.h文件计划 + +### 步骤1: 确认文件引用情况 +- 检查是否有其他文件引用了radar_manager.h +- 验证main.cpp中确实没有包含radar_manager.h + +### 步骤2: 备份文件 +- 将radar_manager.h文件重命名为radar_manager.h.bak作为备份 + +### 步骤3: 删除文件 +- 删除radar_manager.h文件 + +### 步骤4: 验证编译 +- 运行platformio编译命令,确保项目能够成功编译 +- 检查是否有任何编译错误 + +### 步骤5: 清理临时文件 +- 确认项目编译成功后,可以选择删除备份文件(如果需要) + +### 预期结果 +- 项目能够成功编译,没有任何错误 +- 证明radar_manager.h文件是冗余的,可以安全删除 \ No newline at end of file diff --git a/LVEDO.cpp b/LVEDO.cpp deleted file mode 100644 index e69de29..0000000 diff --git a/README.md b/README.md index dcadb13..d4a86e7 100644 Binary files a/README.md and b/README.md differ diff --git a/platformio.ini b/platformio.ini index 83d4f5f..9cca1e2 100644 --- a/platformio.ini +++ b/platformio.ini @@ -15,3 +15,5 @@ framework = arduino lib_deps = bblanchon/ArduinoJson@^7.4.2 emelianov/modbus-esp8266@^4.1.0 +build_flags = -Wno-redefinition +source_filter = +<*> - 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/io_flash.cpp b/src/io_flash.cpp new file mode 100644 index 0000000..c184d1c --- /dev/null +++ b/src/io_flash.cpp @@ -0,0 +1,288 @@ +#include "io_flash.h" +#include + +// 全局变量定义 +Preferences preferences; // 声明preferences变量 +uint16_t currentDeviceId; // 当前设备ID +bool clearConfigRequested = false; // 清除配置请求标志 +bool forceLedOff = false; // 强制关闭LED标志 +ConfigClearStatus currentConfigClearStatus = CONFIG_NORMAL; // 当前配置清除状态 + +// 常量定义 +const int SLOW_BLINK_INTERVAL = 1000; // 慢闪间隔(毫秒) +const int FAST_BLINK_INTERVAL = 200; // 快闪间隔(毫秒) +const int BREATHE_INTERVAL = 40; // 呼吸灯更新间隔(毫秒) +const int BREATHE_MIN = 0; // 呼吸灯最小亮度值 +const int BREATHE_MAX = 155; // 呼吸灯最大亮度值 +const int BREATHE_STEP = 5; // 呼吸灯亮度步进值 + +const unsigned long CLEAR_CONFIG_DURATION = 3000; // 清除配置持续时间(毫秒) + +/** + * @brief 检查Boot按钮状态 + * 在启动时检查Boot按钮是否被按下,如果按下则进入配置清除流程 + */ +void checkBootButton() { + Serial.println("🔍 检查Boot按钮状态..."); + + pinMode(BOOT_BUTTON_PIN, INPUT_PULLUP); + + delay(10); + + int buttonState = digitalRead(BOOT_BUTTON_PIN); + Serial.printf("📊 Boot按钮状态: %s\n", buttonState == LOW ? "按下" : "释放"); + + if (buttonState == LOW) { + Serial.println("⚠️ 检测到Boot按钮按下,请释放按钮后继续启动"); + Serial.println("⏰ 等待按钮释放..."); + + while (digitalRead(BOOT_BUTTON_PIN) == LOW) { + delay(100); + } + + Serial.println("✅ Boot按钮已释放,正常启动"); + } else { + Serial.println("✅ Boot按钮未按下,正常启动"); + } +} + +/** + * @brief 加载设备ID + * 从Flash中读取保存的设备ID + */ +void loadDeviceId() { + currentDeviceId = preferences.getUShort("deviceId", 1001); + Serial.printf("从Flash加载设备ID: %u\n", currentDeviceId); +} + +/** + * @brief 保存设备ID + * 将设备ID保存到Flash中 + */ +void saveDeviceId() { + preferences.putUShort("deviceId", currentDeviceId); + Serial.printf("设备ID已保存到Flash: %u\n", currentDeviceId); +} + +/** + * @brief 清除存储的配置 + * 清除Flash中保存的所有配置,包括设备ID + */ +void clearStoredConfig() { + Serial.println("🧹 开始清除存储的配置..."); + + uint16_t oldDeviceId = preferences.getUShort("deviceId", 0); + + preferences.remove("deviceId"); + + Serial.println("✅ 配置已清除完成"); + Serial.printf("🗑️ 被清除的设备ID: %u\n", oldDeviceId); + + currentDeviceId = 1001; + + Serial.println("🔄 已清除Flash与内存中的配置"); +} + + + +/** + * @brief 配置清除LED控制任务 + * 根据配置清除状态控制CONFIG_CLEAR_PIN引脚的LED显示 + * @param parameter 任务参数(未使用) + */ +void configClearLedTask(void *parameter) { + unsigned long lastConfigBlinkTime = 0; // 上次配置清除LED闪烁时间 + bool configLedState = false; // 配置清除LED状态 + int configBreatheValue = 0; // 配置清除呼吸灯当前亮度值 + bool configBreatheIncreasing = true; // 配置清除呼吸灯是否在增加亮度 + + while (1) { + switch (currentConfigClearStatus) { + case CONFIG_NORMAL: + analogWrite(CONFIG_CLEAR_PIN, 0); + break; + + case CONFIG_PREPARING: + analogWrite(CONFIG_CLEAR_PIN, 255); + break; + + case CONFIG_CLEARING: + if (millis() - lastConfigBlinkTime >= BREATHE_INTERVAL) { + analogWrite(CONFIG_CLEAR_PIN, configBreatheValue); + + if (configBreatheIncreasing) { + configBreatheValue += 5; + if (configBreatheValue >= BREATHE_MAX) { + configBreatheValue = BREATHE_MAX; + configBreatheIncreasing = false; + } + } else { + configBreatheValue -= 5; + if (configBreatheValue <= BREATHE_MIN) { + configBreatheValue = BREATHE_MIN; + configBreatheIncreasing = true; + } + } + lastConfigBlinkTime = millis(); + } + break; + + case CONFIG_COMPLETED: + if (millis() - lastConfigBlinkTime >= FAST_BLINK_INTERVAL) { + configLedState = !configLedState; + digitalWrite(CONFIG_CLEAR_PIN, configLedState ? HIGH : LOW); + lastConfigBlinkTime = millis(); + + static int blinkCount = 0; + blinkCount++; + + if (blinkCount >= 6) { + blinkCount = 0; + currentConfigClearStatus = CONFIG_NORMAL; + digitalWrite(CONFIG_CLEAR_PIN, LOW); + } + } + break; + } + + vTaskDelay(10 / portTICK_PERIOD_MS); + } +} + +/** + * @brief BOOT按钮监控任务 + * 持续监控BOOT按钮状态,检测长按3秒事件并触发配置清除 + * @param parameter 任务参数(未使用) + */ +void bootButtonMonitorTask(void *parameter) { + Serial.println("🔍 启动BOOT按钮监控任务..."); + + pinMode(BOOT_BUTTON_PIN, INPUT_PULLUP); + + unsigned long buttonPressStartTime = 0; + bool buttonPressed = false; + + while (1) { + int buttonState = digitalRead(BOOT_BUTTON_PIN); + + if (buttonState == LOW && !buttonPressed) { + buttonPressed = true; + buttonPressStartTime = millis(); + Serial.println("⚠️ 检测到BOOT按钮按下,长按3秒将清除配置"); + + currentConfigClearStatus = CONFIG_PREPARING; + } + else if (buttonState == HIGH && buttonPressed) { + if (!clearConfigRequested) { + currentConfigClearStatus = CONFIG_NORMAL; + Serial.println("❌ 按钮释放,取消清除操作"); + } + buttonPressed = false; + } + + if (buttonPressed && (millis() - buttonPressStartTime >= CLEAR_CONFIG_DURATION)) { + if (!clearConfigRequested) { + clearConfigRequested = true; + forceLedOff = true; + ledcWrite(0, 0); + Serial.println("✅ 长按3秒确认,将清除配置"); + Serial.println("💡 网络LED已强制熄灭"); + + clearStoredConfig(); + + Serial.println("🔄 配置清除完成,LED将闪烁3次表示完成..."); + + analogWrite(CONFIG_CLEAR_PIN, 0); + Serial.println("🔄 系统即将重启..."); + + vTaskDelay(1000 / portTICK_PERIOD_MS); + ESP.restart(); + } + } + + vTaskDelay(50 / portTICK_PERIOD_MS); + + esp_task_wdt_reset(); + } +} + +/** + * @brief LED控制任务 + * 控制NETWORK_LED_PIN引脚的LED显示,保持常亮 + * @param parameter 任务参数(未使用) + */ +void ledControlTask(void *parameter) { + while (1) { + if (forceLedOff) { + ledcWrite(0, 0); + } else { + ledcWrite(0, 255); // 保持LED常亮 + } + vTaskDelay(100 / portTICK_PERIOD_MS); + } +} + + + +/** + * @brief IO和Flash初始化函数 + * 初始化IO引脚、Flash存储和LED控制 + */ +void io_flash_init() { + // 初始化IO引脚 + pinMode(BOOT_BUTTON_PIN, INPUT); + pinMode(NETWORK_LED_PIN, OUTPUT); + pinMode(CONFIG_CLEAR_PIN, OUTPUT); + pinMode(GPIO8, OUTPUT); + pinMode(GPIO9, OUTPUT); + pinMode(Radar_Start, OUTPUT); // 初始化雷达启动引脚 + + // 设置初始状态 + digitalWrite(CONFIG_CLEAR_PIN, LOW); + digitalWrite(GPIO8, LOW); + digitalWrite(GPIO9, LOW); + digitalWrite(NETWORK_LED_PIN, LOW); + digitalWrite(CONFIG_CLEAR_PIN, LOW); + digitalWrite(Radar_Start, HIGH); // 拉高雷达启动引脚,启动雷达 + + // 初始化LED控制 + ledcSetup(0, 5000, 8); + ledcSetup(1, 5000, 8); + ledcAttachPin(NETWORK_LED_PIN, 0); + ledcAttachPin(CONFIG_CLEAR_PIN, 1); + + // 初始化Flash存储 + preferences.begin("radar_data", false); + + // 加载设备配置 + Serial.println("💾 加载设备配置..."); + loadDeviceId(); + + // 初始化任务 + xTaskCreate( + configClearLedTask, + "Config Clear LED Task", + 2048, + NULL, + 1, + NULL + ); + + xTaskCreate( + bootButtonMonitorTask, + "Boot Button Monitor Task", + 2048, + NULL, + 1, + NULL + ); + + xTaskCreate( + ledControlTask, + "LED Control Task", + 2048, + NULL, + 1, + NULL + ); +} diff --git a/src/io_flash.h b/src/io_flash.h new file mode 100644 index 0000000..33e3fac --- /dev/null +++ b/src/io_flash.h @@ -0,0 +1,42 @@ +#ifndef IO_FLASH_H +#define IO_FLASH_H + +#include +#include + +// 引脚定义 +#define BOOT_BUTTON_PIN 0 // Boot按钮引脚 +#define NETWORK_LED_PIN 35 // 网络状态LED指示灯开发板48引脚,雷达板35引脚 +#define CONFIG_CLEAR_PIN 4 // 配置清除指示灯 +#define Radar_Start 36 // 定义雷达启动引脚拉高启动 +#define GPIO8 8 // 自定义GPIO8 +#define GPIO9 9 // 自定义GPIO9 + +// 配置清除指示灯状态枚举 +enum ConfigClearStatus { + CONFIG_NORMAL, // 正常运行 - LOW + CONFIG_PREPARING, // 准备清除 - HIGH + CONFIG_CLEARING, // 清除过程中 - 呼吸灯 + CONFIG_COMPLETED // 清除完成 - 快速闪烁3次 +}; + +// 全局变量声明 +extern Preferences preferences; +extern uint16_t currentDeviceId; // 当前设备ID +extern bool clearConfigRequested; // 清除配置请求标志 +extern bool forceLedOff; // 强制关闭LED标志 +extern ConfigClearStatus currentConfigClearStatus; // 当前配置清除状态 + +// 函数声明 +void checkBootButton(); +void loadDeviceId(); +void saveDeviceId(); +void clearStoredConfig(); +void io_flash_init(); + +// 任务声明 +void configClearLedTask(void *parameter); +void bootButtonMonitorTask(void *parameter); +void ledControlTask(void *parameter); + +#endif // IO_FLASH_H diff --git a/src/main.cpp b/src/main.cpp index 478b01c..c7af429 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -1,2718 +1,108 @@ #include -#include #include -#include #include -#include -#include -#include -#include -#include -#include -#include -#include -#include // 添加这个头文件以支持memset函数 -#include -#include // 用于数学函数 - -// ESP32 GPIO控制演示 -#define BOOT_BUTTON_PIN 0 // Boot按钮引脚 -#define NETWORK_LED_PIN 5 // 网络状态LED指示灯 -#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; // 当前数据总和 -}; +#include +#include +#include "radar_vitals.h" +#include "io_flash.h" -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 +/** + * @brief 雷达数据采集任务 + * 定期读取ADC数据并调用radar_vitals_process()处理数据 + * @param parameter 任务参数(未使用) + */ +void radarDataTask(void *parameter) { + Serial.println("📡 雷达数据采集任务启动"); - // 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) + radar_sample_t sample; + radar_vitals_t vitals; + float I_buf[DATA_LEN]; + float Q_buf[DATA_LEN]; - // 根据技术文档新增字段 - 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获取对应的字段名 -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(); + while(1) { + // 采集DATA_LEN个样本 + for(int i=0; igetValue(); - 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; - } - } - } -}; - +/** + * @brief 系统初始化函数 + * 初始化所有硬件外设、任务和通信模块 + */ void setup() { Serial.begin(115200); - // 第一步:检查Boot按钮是否被按下 - checkBootButton(); - - // Serial.println("⏳ 等待雷达启动 (30秒),配置清除指示灯正在进行呼吸灯闪烁..."); - // unsigned long startTime = millis(); - // const unsigned long waitDuration = 30000; // 30秒 - - // 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,输出模式 - - // 初始化配置清除指示灯为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); + // 初始化IO和Flash + io_flash_init(); - esp_task_wdt_init(30, true); // 初始化任务看门狗,30秒超时 - esp_task_wdt_add(NULL); + // 初始化雷达 + radar_init(); - // 增加串口缓冲区大小以处理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); // 在setup()中打开命名空间 - - // 加载设备ID和WiFi配置 - Serial.println("💾 加载设备配置..."); - loadDeviceId();// 加载设备ID - loadWiFiConfig();// 加载WiFi配置 - - 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, + "Radar Data Task", + 8192, NULL, - 4, // 最高优先级 + 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 (wifiConfigured) { - Serial.println("💾 检测到已保存的WiFi配置,尝试连接..."); - loadWiFiConfig();// 加载WiFi配置 - if (connectWiFi()) { - 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); - } else { - Serial.println("Flash中无 wifi_first 条目,保留内存中原始值"); - } - - if(WiFi_Connect_First_bit == 0) - { - while (WiFi.status() != WL_CONNECTED) { // 等待WiFi连接 - delay(1000); - Serial.println("等待WiFi连接..."); - } - } - -// 系统初始化函数 - Serial.println("WiFi连接成功!"); - // 初始化R60ABD1雷达模组 - initR60ABD1(); // 调用R60ABD1初始化函数 - Serial.println("🎉 系统初始化完成,等待雷达数据..."); - - // 启动时发送一次睡眠数据 - if (WiFi.status() == WL_CONNECTED) { - Serial.println("🌅 启动时发送睡眠数据到数据库"); - sendSleepDataToInfluxDB(); - } } +/** + * @brief 主循环函数 + * 处理BLE连接状态 + */ void loop() { esp_task_wdt_reset(); - - if (!deviceConnected && oldDeviceConnected) { - delay(500); - 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); + vTaskDelay(1000 / portTICK_PERIOD_MS); } - -// 处理查询状态命令 -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/radar_vitals.cpp b/src/radar_vitals.cpp new file mode 100644 index 0000000..5170d8e --- /dev/null +++ b/src/radar_vitals.cpp @@ -0,0 +1,200 @@ +#include "radar_vitals.h" +#include +#include + +/******** 参数 ********/ +#define ADC_MAX 4095.0f +#define VREF 3.3f +#define TWO_PI 6.2831853f +#define RADAR_LAMBDA 0.0125f // 24GHz 波长(米) + +/******** 内部状态 ********/ +static float fs = 20.0f; + +// 状态变量 +static float phase[DATA_LEN]; +static float unwrap_phase[DATA_LEN]; +static human_state_t human_state = NO_PERSON; +static float resp_bpm = 0; +static float heart_bpm = 0; + +// 平滑处理相关变量 +static float resp_bpm_history[10] = {0}; +static float heart_bpm_history[10] = {0}; +static int history_index = 0; + + + + + + + +/******** 新的雷达处理函数 ********/ +void radar_process(float *I, float *Q, int len) +{ + /* 1. 去直流 (DC Removal) + * I'(n) = I(n) - mean(I) + * Q'(n) = Q(n) - mean(Q) + */ + float meanI = 0, meanQ = 0; + for(int i=0;i π → Δφ -= 2π + */ + unwrap_phase[0] = phase[0]; + for(int i=1;i M_PI) d -= 2*M_PI; + if(d < -M_PI) d += 2*M_PI; + unwrap_phase[i] = unwrap_phase[i-1] + d; + } + + /* 4. 空人判断:相位方差 + * Var(φ) = E[(φ-μ)^2] + */ + float mean = 0, var = 0; + for(int i=0;i 0.05){ + human_state = MOTION; + } else { + human_state = STATIC_HUMAN; + } + + /* 6. 呼吸 / 心率频率估计 + * 方法:频段能量最大值搜索(简化 DFT) + * Fs_eff = SAMPLE_RATE / decimation + */ + const float Fs_eff = 200.0f; // 4kHz / 20 + + // ---- 呼吸:0.1 ~ 0.5 Hz ---- + float max_resp_mag = 0; float resp_freq = 0; + for(float f=0.1f; f<=0.5f; f+=0.02f){ + float re=0, im=0; + for(int n=0;n max_resp_mag){ max_resp_mag = mag; resp_freq = f; } + } + float new_resp_bpm = resp_freq * 60.0f; + + // ---- 心率:0.8 ~ 2.5 Hz ---- + float max_hr_mag = 0; float hr_freq = 0; + for(float f=0.8f; f<=2.5f; f+=0.05f){ + float re=0, im=0; + for(int n=0;n max_hr_mag){ max_hr_mag = mag; hr_freq = f; } + } + float new_heart_bpm = hr_freq * 60.0f; + + // 异常值检测和过滤 + if(new_heart_bpm < 40 || new_heart_bpm > 180){ + new_heart_bpm = heart_bpm; // 使用上次值 + } + if(new_resp_bpm < 4 || new_resp_bpm > 40){ + new_resp_bpm = resp_bpm; // 使用上次值 + } + + // 移动平均滤波 + heart_bpm_history[history_index] = new_heart_bpm; + resp_bpm_history[history_index] = new_resp_bpm; + history_index = (history_index + 1) % 10; + + // 计算平均值 + float sum_hr = 0, sum_resp = 0; + for(int i=0; i<10; i++){ + sum_hr += heart_bpm_history[i]; + sum_resp += resp_bpm_history[i]; + } + heart_bpm = sum_hr / 10.0f; + resp_bpm = sum_resp / 10.0f; + + // 四舍五入到最接近的整数 + heart_bpm = roundf(heart_bpm); + resp_bpm = roundf(resp_bpm); +} + + +/******** 获取人体状态 ********/ +human_state_t radar_get_state(){ return human_state; } + +/******** 获取呼吸率 ********/ +float radar_get_resp_bpm(){ return resp_bpm; } + +/******** 获取心率 ********/ +float radar_get_heart_bpm(){ return heart_bpm; } + +/******** 初始化 ********/ +void radar_vitals_init(float fs_hz) +{ + fs = fs_hz; + human_state = NO_PERSON; + resp_bpm = 0; + heart_bpm = 0; + + // 初始化历史数据数组 + for(int i=0; i<10; i++){ + resp_bpm_history[i] = 0; + heart_bpm_history[i] = 0; + } + history_index = 0; +} + +/** + * @brief 雷达初始化函数 + * 初始化雷达相关的ADC和引脚 + */ +void radar_init() { + // 配置雷达I/Q引脚为ADC输入 + pinMode(PIN_I, INPUT); + pinMode(PIN_Q, INPUT); + + // 初始化ADC + adc1_config_width(ADC_WIDTH_BIT_12); + adc1_config_channel_atten(ADC1_CHANNEL_0, ADC_ATTEN_DB_12); // PIN_I (GPIO1) 对应 ADC1_CHANNEL_0 + adc1_config_channel_atten(ADC1_CHANNEL_1, ADC_ATTEN_DB_12); // PIN_Q (GPIO2) 对应 ADC1_CHANNEL_1 + + Serial.println("🏗️ 初始化雷达管理器..."); + radar_vitals_init(SAMPLE_RATE); +} + + diff --git a/src/radar_vitals.h b/src/radar_vitals.h new file mode 100644 index 0000000..b780996 --- /dev/null +++ b/src/radar_vitals.h @@ -0,0 +1,51 @@ +#ifndef RADAR_VITALS_H +#define RADAR_VITALS_H +#include +#include + +#define PIN_I 9 +#define PIN_Q 10 + +#define ADC_WIDTH ADC_WIDTH_BIT_12 // 配置ADC宽度为12位 +#define ADC_ATTEN ADC_ATTEN_DB_12 // 配置ADC衰减为12dB + + + +#define SAMPLE_RATE 4000 // 基础采样率(Hz) +#define DATA_LEN 256 // 数据长度 + +// 人体状态枚举 +typedef enum { + NO_PERSON = 0, + STATIC_HUMAN, + MOTION +} human_state_t; + +/******** 雷达采样 ********/ +typedef struct { + uint16_t i_value; // ADC 原始值 + uint16_t q_value; // ADC 原始值 + uint32_t timestamp; // ms +} radar_sample_t; + +/******** 输出结果 ********/ +typedef struct { + float heart_bpm; + float breath_bpm; + uint8_t valid; // 1: 有效 + human_state_t state; // 人体状态 +} radar_vitals_t; + +/******** 接口 ********/ +void radar_vitals_init(float fs_hz); +void radar_vitals_process( + const radar_sample_t *sample, + radar_vitals_t *out +); +void radar_process(float *I, float *Q, int len); +human_state_t radar_get_state(); +float radar_get_resp_bpm(); +float radar_get_heart_bpm(); +void radar_init(); + +#endif diff --git a/tcp-output(1).py b/tcp-output(1).py deleted file mode 100644 index 9bd9178..0000000 --- a/tcp-output(1).py +++ /dev/null @@ -1,330 +0,0 @@ -import time -import random -import requests -from datetime import datetime - -# 添加InfluxDB配置 -INFLUXDB_URL = 'http://8.134.11.76:8086' -INFLUXDB_TOKEN = 'KuTa5ZsqoHIhi2IglOO06zExUYw1_mJ6K0mcA9X1y6O6CJDog3_Cgr8mUw1SwpuCCKRElqxa6wAhrrhsYPytkg==' -INFLUXDB_ORG = 'gzlg' -INFLUXDB_BUCKET = 'gzlg' - -# 配置设备ID:设置为0则自动注册,设置为1000~1999则固定使用该ID -CONFIG_DEVICE_ID = 1003 - -# 分配的设备ID(初始为None,注册后更新) -assigned_device_id = None -# 相位计数器 -phase_counter = 0 - -def get_next_device_id(): - """从InfluxDB查询已注册的设备ID,并返回下一个可用的ID""" - try: - # 查询InfluxDB中已存在的设备ID - query = ''' - from(bucket: "{}") - |> range(start: -365d) - |> filter(fn: (r) => r["_measurement"] == "device_data") - |> keep(columns: ["deviceId"]) - |> distinct(column: "deviceId") - |> sort() - '''.format(INFLUXDB_BUCKET) - - url = f"{INFLUXDB_URL}/api/v2/query?org={INFLUXDB_ORG}" - headers = { - "Authorization": f"Token {INFLUXDB_TOKEN}", - "Content-Type": "application/json" - } - data = { - "query": query - } - - response = requests.post(url, headers=headers, json=data) - print(f"🔍 InfluxDB查询响应状态: {response.status_code}") - if response.status_code == 200: - print(f"🔍 InfluxDB查询响应内容: {response.text}") - # 解析响应数据 - device_ids = [] - lines = response.text.strip().split('\n') - for line in lines: - if line and not line.startswith('#') and 'deviceId' not in line: - try: - # 解析CSV格式的响应数据 - # 格式类似于: ,_result,0,1001,1001 - parts = line.split(',') - if len(parts) >= 5: - # deviceId在第4个位置(索引3) - device_id_str = parts[3].strip() - if device_id_str: - device_id = int(device_id_str) - if 1000 <= device_id <= 1999: # 只考虑1000-1999范围内的设备ID - device_ids.append(device_id) - print(f"📱 发现设备ID: {device_id}") - except (ValueError, IndexError) as e: - # 忽略解析错误的行 - print(f"⚠️ 忽略无法解析的行: {line}") - continue - - # 对设备ID进行排序 - device_ids.sort() - - # 找到下一个可用的ID - next_id = 1001 - for device_id in device_ids: - if device_id == next_id: - next_id += 1 - elif device_id > next_id: - break - - # 确保ID在有效范围内 - if next_id > 1999: - next_id = 1001 # 如果超出范围,从头开始 - - print(f"📊 查询到已注册设备: {device_ids}") - print(f"🆕 分配新设备ID: {next_id}") - return next_id - else: - print(f"❌ 查询InfluxDB失败: {response.status_code} - {response.text}") - # 如果查询失败,返回默认ID - return 1001 - except Exception as e: - print(f"❌ 查询设备ID时出错: {e}") - import traceback - traceback.print_exc() - # 如果查询失败,返回默认ID - return 1001 - -def register_device(): - """注册设备并获取设备ID""" - global assigned_device_id - - # 检查配置的设备ID - if CONFIG_DEVICE_ID == 0: - # 自动注册模式 - try: - # 获取下一个可用的设备ID - next_device_id = get_next_device_id() - assigned_device_id = next_device_id - - print(f"✅ 设备注册成功! 设备ID: {assigned_device_id} (0x{assigned_device_id:04X})") - return True - except Exception as e: - print(f"❌ 注册过程中发生错误: {e}") - return False - elif 1000 <= CONFIG_DEVICE_ID <= 1999: - # 固定设备ID模式 - assigned_device_id = CONFIG_DEVICE_ID - print(f"✅ 使用固定设备ID: {assigned_device_id} (0x{assigned_device_id:04X})") - return True - else: - # 配置的设备ID无效 - print(f"❌ 配置的设备ID {CONFIG_DEVICE_ID} 无效,请设置为0(自动注册)或1000~1999之间的值") - return False - -def save_data_to_influxdb(protocol_id, data_value): - """保存日常数据到InfluxDB""" - try: - # 根据协议ID确定字段名 - field_mapping = { - 1: "heartRate", - 2: "breathingRate", - 13: "personDetected", - 14: "humanActivity", - 15: "humanDistance", # 人体距离 (cm) - 16: "humanPosition", # 人体方位 (cm) - 17: "sleepState" # 睡眠状态 - } - - if protocol_id in field_mapping: - field_name = field_mapping[protocol_id] - - # 创建数据点 - 使用 "daily_data" 作为测量值名称 - data_point = { - "measurement": "daily_data", # 改为 daily_data 以区分日常数据 - "tags": { - "deviceId": assigned_device_id, - "dataType": "daily" # 标识这是日常数据 - }, - "time": datetime.utcnow().isoformat() + "Z", - "fields": {} - } - - # 对特定字段进行数值处理 - if protocol_id in [1, 2]: # 心率和呼吸频率需要除以10 - data_point["fields"][field_name] = float(data_value) / 10.0 - elif protocol_id in [13, 14]: # 人检/活动数据,确保是整数0或1 - # 强制转换为整数,确保只有0或1 - data_point["fields"][field_name] = int(data_value) - if data_point["fields"][field_name] not in [0, 1]: - print(f"⚠️ 警告: 人检/活动数据值异常: {data_value}, 强制转换为: {data_point['fields'][field_name]}") - elif protocol_id == 15: # 人体距离,范围0-65535 - data_point["fields"][field_name] = int(data_value) - elif protocol_id == 16: # 人体方位,可以是正负值 - data_point["fields"][field_name] = int(data_value) - elif protocol_id == 17: # 睡眠状态,使用预定义值 - data_point["fields"][field_name] = int(data_value) - else: - data_point["fields"][field_name] = data_value - - # 发送数据到InfluxDB - url = f"{INFLUXDB_URL}/api/v2/write?org={INFLUXDB_ORG}&bucket={INFLUXDB_BUCKET}" - headers = { - "Authorization": f"Token {INFLUXDB_TOKEN}", - "Content-Type": "text/plain; charset=utf-8" - } - - # 构造行协议格式的数据,明确指定数据类型 - if protocol_id in [13, 14, 15, 16, 17]: - # 对于人检/活动/距离/位置/睡眠状态数据,使用整数格式 - line_protocol = f"daily_data,deviceId={assigned_device_id},dataType=daily {field_name}={int(data_point['fields'][field_name])}i" - else: - # 对于其他数据,使用浮点数格式 - line_protocol = f"daily_data,deviceId={assigned_device_id},dataType=daily {field_name}={data_point['fields'][field_name]}" - - response = requests.post(url, headers=headers, data=line_protocol) - if response.status_code == 204: - print(f"✅ 日常数据已保存到InfluxDB设备{assigned_device_id}上: {field_name}={data_point['fields'][field_name]}") - else: - print(f"❌ 保存日常数据到InfluxDB失败: {response.status_code} - {response.text}") - else: - print(f"⚠️ 未知的协议ID: {protocol_id}") - - except Exception as e: - print(f"❌ 保存日常数据到InfluxDB时出错: {e}") - import traceback - traceback.print_exc() - -def save_sleep_data_to_influxdb(sleep_data): - """保存睡眠数据到InfluxDB""" - try: - # 构造睡眠数据的行协议格式 - 使用 "sleep_data" 作为测量值名称 - line_protocol = f"sleep_data,deviceId={assigned_device_id},dataType=sleep " - - # 按顺序添加各个字段 - fields = [] - fields.append(f"sleepQualityScore={int(sleep_data['sleepQualityScore'])}i") # 1B 睡眠质量评分 (0~100) - fields.append(f"totalSleepDuration={int(sleep_data['totalSleepDuration'])}i") # 2B 睡眠总时长 (0~65535 分钟) - fields.append(f"awakeDurationRatio={int(sleep_data['awakeDurationRatio'])}i") # 1B 清醒时长占比 (0~100) - fields.append(f"lightSleepRatio={int(sleep_data['lightSleepRatio'])}i") # 1B 浅睡时长占比 (0~100) - fields.append(f"deepSleepRatio={int(sleep_data['deepSleepRatio'])}i") # 1B 深睡时长占比 (0~100) - fields.append(f"outOfBedDuration={int(sleep_data['outOfBedDuration'])}i") # 1B 离床时长 (0~255) - fields.append(f"outOfBedCount={int(sleep_data['outOfBedCount'])}i") # 1B 离床次数 (0~255) - fields.append(f"turnCount={int(sleep_data['turnCount'])}i") # 1B 翻身次数 (0~255) - fields.append(f"avgBreathingRate={int(sleep_data['avgBreathingRate'])}i") # 1B 平均呼吸 (0~25) - fields.append(f"avgHeartRate={int(sleep_data['avgHeartRate'])}i") # 1B 平均心跳 (0~100) - fields.append(f"apneaCount={int(sleep_data['apneaCount'])}i") # 1B 呼吸暂停次数 (0~10) - - line_protocol += ",".join(fields) - - # 发送数据到InfluxDB - url = f"{INFLUXDB_URL}/api/v2/write?org={INFLUXDB_ORG}&bucket={INFLUXDB_BUCKET}" - headers = { - "Authorization": f"Token {INFLUXDB_TOKEN}", - "Content-Type": "text/plain; charset=utf-8" - } - - response = requests.post(url, headers=headers, data=line_protocol) - if response.status_code == 204: - print(f"✅ 睡眠数据已保存到InfluxDB设备{assigned_device_id}上") - else: - print(f"❌ 保存睡眠数据到InfluxDB失败: {response.status_code} - {response.text}") - except Exception as e: - print(f"❌ 保存睡眠数据到InfluxDB时出错: {e}") - import traceback - traceback.print_exc() - -def generate_random_sleep_data(): - """生成随机睡眠数据用于测试""" - sleep_data = { - "sleepQualityScore": random.randint(0, 100), # 1B 睡眠质量评分 (0~100) - "totalSleepDuration": random.randint(0, 65535), # 2B 睡眠总时长 (0~65535 分钟) - "awakeDurationRatio": random.randint(0, 100), # 1B 清醒时长占比 (0~100) - "lightSleepRatio": random.randint(0, 100), # 1B 浅睡时长占比 (0~100) - "deepSleepRatio": random.randint(0, 100), # 1B 深睡时长占比 (0~100) - "outOfBedDuration": random.randint(0, 255), # 1B 离床时长 (0~255) - "outOfBedCount": random.randint(0, 255), # 1B 离床次数 (0~255) - "turnCount": random.randint(0, 255), # 1B 翻身次数 (0~255) - "avgBreathingRate": random.randint(0, 25), # 1B 平均呼吸 (0~25) - "avgHeartRate": random.randint(0, 100), # 1B 平均心跳 (0~100) - "apneaCount": random.randint(0, 10), # 1B 呼吸暂停次数 (0~10) - } - - # 确保比例字段总和为100 - total_ratio = sleep_data["awakeDurationRatio"] + sleep_data["lightSleepRatio"] + sleep_data["deepSleepRatio"] - if total_ratio != 100: - # 调整浅睡时长占比以确保总和为100 - adjustment = 100 - total_ratio - sleep_data["lightSleepRatio"] = max(0, min(100, sleep_data["lightSleepRatio"] + adjustment)) - - # 再次检查总和 - total_ratio = sleep_data["awakeDurationRatio"] + sleep_data["lightSleepRatio"] + sleep_data["deepSleepRatio"] - if total_ratio != 100: - # 如果仍然不是100,则最后一次调整浅睡占比 - sleep_data["lightSleepRatio"] += (100 - total_ratio) - sleep_data["lightSleepRatio"] = max(0, min(100, sleep_data["lightSleepRatio"])) - - return sleep_data - -def main(): - global assigned_device_id - - try: - # 第一步:注册设备获取设备ID - if not register_device(): - print("❌ 设备注册失败,程序退出") - return - - print(f"🎯 开始使用设备ID {assigned_device_id} 发送数据...") - - # 启动时立即发送一次睡眠数据 - print("⏰ 启动时生成并发送睡眠数据...") - initial_sleep_data = generate_random_sleep_data() - save_sleep_data_to_influxdb(initial_sleep_data) - - # 记录上次发送睡眠数据的时间 - last_sleep_data_time = time.time() - - # 第二步:开始发送数据 - while True: - # 初始化数据值 - data_value = 0 - - # 发送其他数据 - protocol_id = random.choice([1, 2, 13, 14, 15, 16, 17]) # 1=心跳, 2=呼吸, 13=检测到人, 14=人体活动, 15=人体距离, 16=人体方位, 17=睡眠状态 - - if protocol_id == 1: # 心跳 - data_value = random.randint(600, 1000) - elif protocol_id == 2: # 呼吸 - data_value = random.randint(120, 200) - elif protocol_id == 13: # 检测到人(1检测到,0未检测到) - data_value = random.choice([1]) - elif protocol_id == 14: # 人体活动(1活动,0静止) - data_value = random.choice([0]) - elif protocol_id == 15: # 人体距离 (cm),范围0-65535 - data_value = random.randint(0, 65535) - elif protocol_id == 16: # 人体方位 (cm),可以有正负值 - data_value = random.randint(-32768, 32767) - elif protocol_id == 17: # 睡眠状态 (0x00=深睡, 0x01=浅睡, 0x02=清醒, 0x03=无) - data_value = random.choice([0, 1, 2, 3]) - - # 直接发送数据到InfluxDB - save_data_to_influxdb(protocol_id, data_value) - - # 每隔一段时间(例如30分钟)发送一次睡眠数据 - current_time = time.time() - if current_time - last_sleep_data_time >= 1800: # 30分钟 = 1800秒 - print("⏰ 生成并发送睡眠数据...") - sleep_data = generate_random_sleep_data() - save_sleep_data_to_influxdb(sleep_data) - last_sleep_data_time = current_time - - # 设置发送间隔 - time.sleep(0.4) # 每0.4秒发送一次数据 - - except KeyboardInterrupt: - print(f"\n🛑 设备 {assigned_device_id} 发送端已停止") - except Exception as e: - print(f"❌ 发生错误: {e}") - -if __name__ == "__main__": - main() \ No newline at end of file diff --git a/test_json_chunk_sender.py b/test_json_chunk_sender.py deleted file mode 100644 index 8f181a1..0000000 --- a/test_json_chunk_sender.py +++ /dev/null @@ -1,72 +0,0 @@ -import json -import time - -def simulate_json_chunk_sending(data, chunk_size=15): - """ - 模拟JSON数据分包发送 - """ - json_string = json.dumps(data) - print(f"原始JSON数据: {json_string}") - print(f"数据总长度: {len(json_string)} 字符") - - chunks = [] - # 分包 - for i in range(0, len(json_string), chunk_size): - chunk = json_string[i:i+chunk_size] - chunks.append(chunk) - - print(f"\n分包结果 ({len(chunks)} 个包):") - for i, chunk in enumerate(chunks): - print(f" 包 {i+1}: {chunk}") - - return chunks - -def create_sample_json_data(): - """ - 创建示例JSON数据 - """ - return { - "type": "radarData", - "deviceId": 1001, - "timestamp": int(time.time() * 1000), - "presence": 1, - "heartRate": 72.5, - "breathRate": 16.2, - "motion": 0, - "rssi": -45, - "heartbeatWaveform": 120, - "breathingWaveform": 45, - "rawSignal": -25 - } - -def create_status_json_data(): - """ - 创建状态JSON数据 - """ - return { - "type": "status", - "wifiConfigured": True, - "wifiConnected": True, - "ipAddress": "192.168.1.100", - "deviceId": 1001 - } - -def main(): - print("=== JSON分包发送模拟测试 ===\n") - - # 测试雷达数据 - print("1. 雷达数据分包测试:") - radar_data = create_sample_json_data() - simulate_json_chunk_sending(radar_data, 15) - - print("\n" + "="*50 + "\n") - - # 测试状态数据 - print("2. 状态数据分包测试:") - status_data = create_status_json_data() - simulate_json_chunk_sending(status_data, 15) - - print("\n=== 测试完成 ===") - -if __name__ == "__main__": - main() \ No newline at end of file diff --git a/test_json_parser.js b/test_json_parser.js deleted file mode 100644 index 7e518f3..0000000 --- a/test_json_parser.js +++ /dev/null @@ -1,82 +0,0 @@ -// JSON分包接收处理函数 -let streamBuffer = "" - -function addLog(message) { - console.log(message); -} - -function handleParsedJson(obj) { - console.log("处理解析后的JSON对象:", obj); -} - -function processChunk(fragment){ - addLog('进入processChunk') - streamBuffer += fragment - let braceCount = 0 - let jsonStart = -1 - for(let i = 0; i < streamBuffer.length; i++){ - const ch = streamBuffer[i] - - if(ch === "{"){ - if(braceCount === 0){ - jsonStart = i - } - braceCount++ - }else if(ch === "}"){ - braceCount-- - if(braceCount === 0 && jsonStart !== -1){ - const jsonStr = streamBuffer.substring(jsonStart, i + 1) - addLog("收到完整JSON:" + jsonStr) - try{ - const obj = JSON.parse(jsonStr) - handleParsedJson(obj) - }catch(e){ - addLog("JSON解析失败:" + e) - } - streamBuffer = streamBuffer.substring(i + 1) - i = -1 - jsonStart = -1 - } - } - } - //缓冲区过大时清理防越界 - if (streamBuffer.length > 20000) { - addLog("⚠ 清空超大缓冲区(异常保护)") - streamBuffer = "" - braceCount = 0 - jsonStart = -1 - } -} - -// 模拟分包发送JSON数据 -function simulateJSONSending() { - const jsonData = { - type: "radarData", - deviceId: 1001, - timestamp: Date.now(), - presence: 1, - heartRate: 72.5, - breathRate: 16.2, - motion: 0, - rssi: -45, - heartbeatWaveform: 120, - breathingWaveform: 45, - rawSignal: -25 - }; - - const jsonString = JSON.stringify(jsonData); - console.log("原始JSON数据:", jsonString); - - // 模拟分包发送 - const packetSize = 15; // 每包15个字符 - for (let i = 0; i < jsonString.length; i += packetSize) { - const packet = jsonString.substring(i, Math.min(i + packetSize, jsonString.length)); - console.log(`发送分包 ${Math.floor(i/packetSize)+1}:`, packet); - processChunk(packet); - } -} - -// 测试函数 -console.log("=== 开始测试JSON分包接收处理 ==="); -simulateJSONSending(); -console.log("=== 测试完成 ==="); \ No newline at end of file diff --git a/传感器数据.txt b/传感器数据.txt deleted file mode 100644 index ec75a0a..0000000 --- a/传感器数据.txt +++ /dev/null @@ -1,394 +0,0 @@ -HBR01:0,0,0,0,9,0,0,-30 -HBR01:0,0,0,0,9,0,0,-20 -HBR01:0,0,0,0,9,0,0,-21 -HBR01:0,0,0,0,9,0,0,-19 -HBR01:0,0,0,0,9,0,0,-22 -HBR01:0,0,0,0,9,0,0,-36 -HBR01:0,0,0,0,9,0,0,-35 -HBR01:0,0,0,0,9,0,0,-39 -HBR01:0,0,0,0,9,0,0,-40 -HBR01:0,0,0,0,9,0,0,-40 -HBR01:0,0,0,0,9,0,0,-40 -HBR01:0,0,0,0,9,0,0,-40 -HBR01:0,0,0,0,9,0,0,-23 -HBR01:0,0,0,0,9,0,0,0 -HBR01:0,0,0,0,9,0,0,0 -HBR01:0,0,0,0,9,0,0,-11 -HBR01:0,0,0,0,9,0,0,-19 -HBR01:0,0,0,0,9,0,0,-29 -HBR01:0,0,0,0,9,0,0,-34 -HBR01:0,0,0,0,9,0,0,-19 -HBR01:0,0,0,0,9,0,0,-22 -HBR01:0,0,0,0,9,0,0,-10 -HBR01:0,0,0,0,9,0,0,-9 -HBR01:0,0,0,0,9,0,0,-12 -HBR01:0,0,0,0,9,0,0,-13 -HBR01:0,0,0,0,9,0,0,-19 -HBR01:0,0,0,0,9,0,0,-23 -HBR01:0,0,0,0,9,0,0,-33 -HBR01:0,0,0,0,9,0,0,-33 -HBR01:0,0,0,0,9,0,0,-19 -HBR01:0,0,0,0,9,0,0,-3 -HBR01:0,0,0,0,9,0,0,-22 -HBR01:0,0,0,0,9,0,0,-13 -HBR01:0,0,0,0,9,0,0,-18 -HBR01:0,0,0,0,9,0,0,-12 -HBR01:0,0,0,0,9,0,0,0 -HBR01:0,0,0,0,9,0,0,-16 -HBR01:0,0,0,0,9,0,0,-27 -HBR01:0,0,0,0,9,0,0,-14 -HBR01:0,0,0,0,9,0,0,-23 -HBR01:0,0,0,0,9,0,0,-16 -HBR01:0,0,0,0,10,0,0,-40 -HBR01:0,0,0,0,10,0,0,-42 -HBR01:0,0,0,0,10,0,0,-38 -HBR01:0,0,0,0,10,0,0,-40 -HBR01:0,0,0,0,10,0,0,-43 -HBR01:0,0,0,0,10,0,0,0 -HBR01:0,0,0,0,10,0,0,0 -HBR01:0,0,0,0,10,0,0,0 -HBR01:0,0,0,0,10,0,0,1 -HBR01:0,0,0,0,10,0,0,0 -HBR01:0,0,0,0,10,0,0,0 -HBR01:0,0,0,0,10,0,0,0 -HBR01:0,0,0,0,10,0,0,-4 -HBR01:0,0,0,0,10,0,0,-22 -HBR01:0,0,0,0,10,0,0,-40 -HBR01:0,0,0,0,11,0,0,-57 -HBR01:0,0,0,0,11,0,0,-29 -HBR01:0,0,0,0,11,0,0,-22 -HBR01:0,0,0,0,11,0,0,-10 -HBR01:0,0,0,0,11,0,0,-11 -HBR01:0,0,0,0,11,0,0,-11 -HBR01:0,0,0,0,11,0,0,-10 -HBR01:0,0,0,0,11,0,0,4 -HBR01:0,0,0,0,11,0,0,-16 -HBR01:0,0,0,0,11,0,0,-59 -HBR01:0,0,0,0,11,0,0,-9 -HBR01:0,0,0,0,11,0,0,-15 -HBR01:0,0,0,0,11,0,0,-21 -HBR01:0,0,0,0,11,0,0,-26 -HBR01:0,0,0,0,11,0,0,-28 -HBR01:0,0,0,0,11,0,0,-26 -HBR01:0,0,0,0,11,0,0,-26 -HBR01:0,0,0,0,11,0,0,-26 -HBR01:0,0,0,0,11,0,0,-24 -HBR01:0,0,0,0,11,0,0,-24 -HBR01:0,0,0,0,11,0,0,-14 -HBR01:0,0,0,0,11,0,0,-28 -HBR01:0,0,0,0,11,0,0,-30 -HBR01:0,0,0,0,11,0,0,-9 -HBR01:0,0,0,0,11,0,0,-25 -HBR01:0,0,0,0,11,0,0,-29 -HBR01:0,0,0,0,11,0,0,-24 -HBR01:0,0,0,0,11,0,0,-23 -HBR01:0,0,0,0,11,0,0,-31 -HBR01:0,0,0,0,11,0,0,-26 -HBR01:0,0,0,0,11,0,0,-25 -HBR01:0,0,0,0,11,0,0,-21 -HBR01:0,0,0,0,11,0,0,-36 -HBR01:0,0,0,0,11,0,0,-25 -HBR01:0,0,0,0,11,0,0,-26 -HBR01:0,0,0,0,11,0,0,-39 -HBR01:0,0,0,0,11,0,0,-27 -HBR01:0,0,0,0,11,0,0,-24 -HBR01:0,0,0,0,11,0,0,-24 -HBR01:0,0,0,0,11,0,0,-27 -HBR01:0,0,0,0,11,0,0,-27 -HBR01:0,0,0,0,11,0,0,-26 -HBR01:0,0,0,0,11,0,0,-36 -HBR01:0,0,0,0,11,0,0,-40 -HBR01:0,0,0,0,11,0,0,-40 -HBR01:0,0,0,0,11,0,0,-40 -HBR01:0,0,0,0,11,0,0,-40 -HBR01:0,0,0,0,11,0,0,-40 -HBR01:0,0,0,0,11,0,0,-59 -HBR01:0,0,0,0,11,0,0,-41 -HBR01:0,0,0,0,11,0,0,-52 -HBR01:0,0,0,0,11,0,0,-39 -HBR01:0,0,0,0,11,0,0,-35 -HBR01:0,0,0,0,11,0,0,-43 -HBR01:0,0,0,0,11,0,0,-30 -HBR01:0,0,0,0,11,0,0,-17 -HBR01:0,0,0,0,11,0,0,-11 -HBR01:0,0,0,0,11,0,0,-20 -HBR01:0,0,0,0,11,0,0,-11 -HBR01:0,0,0,0,11,0,0,-16 -HBR01:0,0,0,0,11,0,0,-11 -HBR01:0,0,0,0,11,0,0,-12 -HBR01:0,0,0,0,11,0,0,-7 -HBR01:0,0,0,0,11,0,0,-9 -HBR01:0,0,0,0,11,0,0,-11 -HBR01:0,0,0,0,11,0,0,-10 -HBR01:0,0,0,0,11,0,0,-14 -HBR01:0,0,0,0,11,0,0,-24 -HBR01:0,0,0,0,11,0,0,-15 -HBR01:0,0,0,0,11,0,0,-20 -HBR01:0,0,0,0,11,0,0,-16 -HBR01:0,0,0,0,11,0,0,-11 -HBR01:0,0,0,0,11,0,0,-12 -HBR01:0,0,0,0,12,0,0,9 -HBR01:0,0,0,0,12,0,0,-11 -HBR01:0,0,0,0,12,0,0,-11 -HBR01:0,0,0,0,12,0,0,-16 -HBR01:0,0,0,0,12,0,0,11 -HBR01:0,0,0,0,12,0,0,11 -HBR01:0,0,0,0,12,0,0,1 -HBR01:0,0,0,0,12,0,0,-4 -HBR01:0,0,0,0,12,0,0,-13 -HBR01:0,0,0,0,12,0,0,-3 -HBR01:0,0,0,0,12,0,0,-6 -HBR01:0,0,0,0,12,0,0,3 -HBR01:0,0,0,0,12,0,0,-12 -HBR01:0,0,0,0,12,0,0,-23 -HBR01:0,0,0,0,12,0,0,-40 -HBR01:0,0,0,0,12,0,0,-22 -HBR01:0,0,0,0,12,0,0,-40 -HBR01:0,0,0,0,12,0,0,-20 -HBR01:0,0,0,0,12,0,0,-21 -HBR01:0,0,0,0,12,0,0,-42 -HBR01:0,0,0,0,12,0,0,-40 -HBR01:0,0,0,0,13,0,0,-51 -HBR01:0,0,0,0,13,0,0,-40 -HBR01:0,0,0,0,13,0,0,-40 -HBR01:0,0,0,0,13,0,0,-40 -HBR01:0,0,0,0,13,0,0,-45 -HBR01:0,0,0,0,13,0,0,-48 -HBR01:0,0,0,0,13,0,0,-35 -HBR01:0,0,0,0,13,0,0,-38 -HBR01:0,0,0,0,13,0,0,-48 -HBR01:0,0,0,0,13,0,0,-19 -HBR01:0,0,0,0,13,0,0,-12 -HBR01:0,0,0,0,13,0,0,-26 -HBR01:0,0,0,0,13,0,0,-23 -HBR01:0,0,0,0,13,0,0,-9 -HBR01:0,0,0,0,13,0,0,4 -HBR01:0,0,0,0,13,0,0,-1 -HBR01:0,0,0,0,13,0,0,0 -HBR01:0,0,0,0,13,0,0,0 -HBR01:0,0,0,0,13,0,0,-1 -HBR01:0,0,0,0,13,0,0,1 -HBR01:0,0,0,0,13,0,0,0 -HBR01:0,0,0,0,13,0,0,-1 -HBR01:0,0,0,0,13,0,0,0 -HBR01:0,0,0,0,13,0,0,21 -HBR01:0,0,0,0,13,0,0,15 -HBR01:0,0,0,0,13,0,0,0 -HBR01:0,0,0,0,13,0,0,21 -HBR01:0,0,0,0,14,0,0,18 -HBR01:0,0,0,0,14,0,0,0 -HBR01:0,0,0,0,14,0,0,2 -HBR01:0,0,0,0,14,0,0,4 -HBR01:0,0,0,0,14,0,0,-3 -HBR01:0,0,0,0,14,0,0,5 -HBR01:0,0,0,0,14,0,0,-1 -HBR01:0,0,0,0,14,0,0,-22 -HBR01:0,0,0,0,14,0,0,4 -HBR01:0,0,0,0,14,0,0,3 -HBR01:0,0,0,0,14,0,0,-18 -HBR01:0,0,0,0,14,0,0,-16 -HBR01:0,0,0,0,14,0,0,-6 -HBR01:0,0,0,0,14,0,0,-23 -HBR01:0,0,0,0,14,0,0,-28 -HBR01:0,0,0,0,14,0,0,-24 -HBR01:0,0,0,0,14,0,0,-24 -HBR01:0,0,0,0,14,0,0,-39 -HBR01:0,0,0,0,14,0,0,-24 -HBR01:0,0,0,0,14,0,0,-38 -HBR01:0,0,0,0,14,0,0,-40 -HBR01:0,0,0,0,14,0,0,-37 -HBR01:0,0,0,0,14,0,0,-40 -HBR01:0,0,0,0,14,0,0,-40 -HBR01:0,0,0,0,14,0,0,-40 -HBR01:0,0,0,0,15,0,0,-40 -HBR01:0,0,0,0,15,0,0,-45 -HBR01:0,0,0,0,15,0,0,-36 -HBR01:0,0,0,0,15,0,0,-29 -HBR01:0,0,0,0,15,0,0,-10 -HBR01:0,0,0,0,15,0,0,-43 -HBR01:0,0,0,0,15,0,0,-12 -HBR01:0,0,0,0,15,0,0,-40 -HBR01:0,0,0,0,15,0,0,-33 -HBR01:0,0,0,0,15,0,0,-37 -HBR01:0,0,0,0,15,0,0,-30 -HBR01:0,0,0,0,15,0,0,-35 -HBR01:0,0,0,0,15,0,0,-26 -HBR01:0,0,0,0,15,0,0,2 -HBR01:0,0,0,0,15,0,0,-30 -HBR01:0,0,0,0,15,0,0,0 -HBR01:0,0,0,0,15,0,0,-22 -HBR01:0,0,0,0,15,0,0,-23 -HBR01:0,0,0,0,15,0,0,-30 -HBR01:0,0,0,0,15,0,0,-33 -HBR01:0,0,0,0,15,0,0,-15 -HBR01:0,0,0,0,15,0,0,0 -HBR01:0,0,0,0,15,0,0,-12 -HBR01:0,0,0,0,15,0,0,-20 -HBR01:0,0,0,0,15,0,0,-20 -HBR01:0,0,0,0,15,0,0,-10 -HBR01:0,0,0,0,15,0,0,-25 -HBR01:0,0,0,0,15,0,0,-35 -HBR01:0,0,0,0,15,0,0,-40 -HBR01:0,0,0,0,15,0,0,-40 -HBR01:0,0,0,0,15,0,0,-40 -HBR01:0,0,0,0,15,0,0,-62 -HBR01:0,0,0,0,15,0,0,-33 -HBR01:0,0,0,0,15,0,0,-24 -HBR01:0,0,0,0,15,0,0,-30 -HBR01:0,0,0,0,15,0,0,-40 -HBR01:0,0,0,0,15,0,0,-45 -HBR01:0,0,0,0,15,0,0,-29 -HBR01:0,0,0,0,15,0,0,-31 -HBR01:0,0,0,0,15,0,0,-37 -HBR01:0,0,0,0,15,0,0,-43 -HBR01:0,0,0,0,15,0,0,-23 -HBR01:0,0,0,0,15,0,0,-18 -HBR01:0,0,0,0,15,0,0,-7 -HBR01:0,0,0,0,15,0,0,-21 -HBR01:0,0,0,0,15,0,0,0 -HBR01:0,0,0,0,15,0,0,-40 -HBR01:0,0,0,0,16,0,0,-69 -HBR01:0,0,0,0,16,0,0,-30 -HBR01:0,0,0,0,16,0,0,-34 -HBR01:0,0,0,0,16,0,0,-1 -HBR01:0,0,0,0,16,0,0,-1 -HBR01:0,0,0,0,16,0,0,-8 -HBR01:0,0,0,0,16,0,0,-26 -HBR01:0,0,0,0,16,0,0,-15 -HBR01:0,0,0,0,16,0,0,-16 -HBR01:0,0,0,0,16,0,0,-17 -HBR01:0,0,0,0,16,0,0,-26 -HBR01:0,0,0,0,16,0,0,-14 -HBR01:0,0,0,0,16,0,0,-16 -HBR01:0,0,0,0,16,0,0,-18 -HBR01:0,0,0,0,16,0,0,-14 -HBR01:0,0,0,0,16,0,0,-9 -HBR01:0,0,0,0,16,0,0,-9 -HBR01:0,0,0,0,16,0,0,-14 -HBR01:0,0,0,0,16,0,0,-16 -HBR01:0,0,0,0,16,0,0,-30 -HBR01:0,0,0,0,16,0,0,-40 -HBR01:0,0,0,0,15,0,0,-23 -HBR01:0,0,0,0,15,0,0,-27 -HBR01:0,0,0,0,15,0,0,-23 -HBR01:0,0,0,0,15,0,0,-11 -HBR01:0,0,0,0,15,0,0,-21 -HBR01:0,0,0,0,15,0,0,-22 -HBR01:0,0,0,0,15,0,0,-12 -HBR01:0,0,0,0,15,0,0,-16 -HBR01:0,0,0,0,15,0,0,-35 -HBR01:0,0,0,0,15,0,0,-18 -HBR01:0,0,0,0,15,0,0,-18 -HBR01:0,0,0,0,15,0,0,-40 -HBR01:0,0,0,0,15,0,0,-25 -HBR01:0,0,0,0,16,0,0,-1 -HBR01:0,0,0,0,16,0,0,0 -HBR01:0,0,0,0,16,0,0,0 -HBR01:0,0,0,0,16,0,0,-24 -HBR01:0,0,0,0,16,0,0,-16 -HBR01:0,0,0,0,16,0,0,-24 -HBR01:0,0,0,0,16,0,0,-22 -HBR01:0,0,0,0,16,0,0,-38 -HBR01:0,0,0,0,16,0,0,-32 -HBR01:0,0,0,0,16,0,0,-20 -HBR01:0,0,0,0,16,0,0,-21 -HBR01:0,0,0,0,16,0,0,-9 -HBR01:0,0,0,0,16,0,0,-19 -HBR01:0,0,0,0,16,0,0,-31 -HBR01:0,0,0,0,16,0,0,-34 -HBR01:0,0,0,0,15,0,0,-22 -HBR01:0,0,0,0,15,0,0,-34 -HBR01:0,0,0,0,15,0,0,-21 -HBR01:0,0,0,0,15,0,0,-26 -HBR01:0,0,0,0,15,0,0,-40 -HBR01:0,0,0,0,15,0,0,-46 -HBR01:0,0,0,0,15,0,0,-7 -HBR01:0,0,0,0,15,0,0,-4 -HBR01:0,0,0,0,15,0,0,-26 -HBR01:0,0,0,0,15,0,0,-30 -HBR01:0,0,0,0,15,0,0,-14 -HBR01:0,0,0,0,15,0,0,-19 -HBR01:0,0,0,0,15,0,0,-28 -HBR01:0,0,0,0,15,0,0,0 -HBR01:0,0,0,0,15,0,0,-9 -HBR01:0,0,0,0,15,0,0,-13 -HBR01:0,0,0,0,15,0,0,-27 -HBR01:0,0,0,0,15,0,0,-13 -HBR01:0,0,0,0,15,0,0,0 -HBR01:0,0,0,0,15,0,0,-29 -HBR01:0,0,0,0,15,0,0,-13 -HBR01:0,0,0,0,15,0,0,-25 -HBR01:0,0,0,0,15,0,0,-32 -HBR01:0,0,0,0,15,0,0,-25 -HBR01:0,0,0,0,15,0,0,-18 -HBR01:0,0,0,0,15,0,0,-18 -HBR01:0,0,0,0,15,0,0,-14 -HBR01:0,0,0,0,15,0,0,-17 -HBR01:0,0,0,0,15,0,0,-29 -HBR01:0,0,0,0,15,0,0,-31 -HBR01:0,0,0,0,15,0,0,-39 -HBR01:0,0,0,0,15,0,0,-11 -HBR01:0,0,0,0,15,0,0,-24 -HBR01:0,0,0,0,15,0,0,-40 -HBR01:0,0,0,0,15,0,0,-40 -HBR01:0,0,0,0,15,0,0,-33 -HBR01:0,0,0,0,15,0,0,-41 -HBR01:0,0,0,0,15,0,0,-32 -HBR01:0,0,0,0,15,0,0,-32 -HBR01:0,0,0,0,15,0,0,-32 -HBR01:0,0,0,0,15,0,0,-18 -HBR01:0,0,0,0,15,0,0,-28 -HBR01:0,0,0,0,15,0,0,-21 -HBR01:0,0,0,0,15,0,0,-18 -HBR01:0,0,0,0,15,0,0,-7 -HBR01:0,0,0,0,15,0,0,0 -HBR01:0,0,0,0,15,0,0,-14 -HBR01:0,0,0,0,15,0,0,-11 -HBR01:0,0,0,0,15,0,0,-23 -HBR01:0,0,0,0,15,0,0,-22 -HBR01:0,0,0,0,15,0,0,-14 -HBR01:0,0,0,0,15,0,0,-25 -HBR01:0,0,0,0,15,0,0,-15 -HBR01:0,0,0,0,15,0,0,-28 -HBR01:0,0,0,0,15,0,0,1 -HBR01:0,0,0,0,15,0,0,1 -HBR01:0,0,0,0,15,0,0,-20 -HBR01:0,0,0,0,15,0,0,-40 -HBR01:0,0,0,0,15,0,0,-25 -HBR01:0,0,0,0,15,0,0,0 -HBR01:0,0,0,0,15,0,0,0 -HBR01:0,0,0,0,15,0,0,-28 -HBR01:0,0,0,0,15,0,0,-20 -HBR01:0,0,0,0,15,0,0,-10 -HBR01:0,0,0,0,15,0,0,-16 -HBR01:0,0,0,0,15,0,0,-12 -HBR01:0,0,0,0,15,0,0,-26 -HBR01:0,0,0,0,15,0,0,-33 -HBR01:0,0,0,0,15,0,0,-21 -HBR01:0,0,0,0,15,0,0,-22 -HBR01:0,0,0,0,15,0,0,-29 -HBR01:0,0,0,0,15,0,0,-40 -HBR01:0,0,0,0,15,0,0,-33 -HBR01:0,0,0,0,15,0,0,-28 -HBR01:0,0,0,0,15,0,0,-19 -HBR01:0,0,0,0,15,0,0,-26 -HBR01:0,0,0,0,15,0,0,-30 -HBR01:0,0,0,0,15,0,0,-20 -HBR01:0,0,0,0,15,0,0,-29 -HBR01:0,0,0,0,15,0,0,-35 -HBR01:0,0,0,0,15,0,0,-25 -HBR01:0,0,0,0,15,0,0,-27 -HBR01:0,0,0,0,15,0,0,-38 -HBR01:0,0,0,0,15,0,0,-27 -HBR01:0,0,0,0,15,0,0,-27 -HBR01:0,0,0,0,15,0,0,-48 -HBR01:0,0,0,0,15,0,0,-31 -HBR01:0,0,0,0,15,0,0,-26 -HBR01:0,0,0,0,15,0,0,-16 -HBR01:0,0,0,0,15,0,0,-6 -HBR01:0,0,0,0,15,0,0,-13 -HBR01:0,0,0,0,15,0,0,-11 -HBR01:0,0,0,0,14,0,0,-20 -HBR01:0,0,0,0,14,0,0,-58 -HBR01:0,0,0,0,14,0,0,-25 -HBR01:0,0,0,0,14,0,0,-11