diff --git a/.vscode/settings.json b/.vscode/settings.json index 01b09ad..84548da 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -3,7 +3,50 @@ "cmath": "cpp", "array": "cpp", "string": "cpp", - "string_view": "cpp" + "string_view": "cpp", + "new": "cpp", + "atomic": "cpp", + "cctype": "cpp", + "clocale": "cpp", + "cstdarg": "cpp", + "cstddef": "cpp", + "cstdint": "cpp", + "cstdio": "cpp", + "cstdlib": "cpp", + "cstring": "cpp", + "ctime": "cpp", + "cwchar": "cpp", + "cwctype": "cpp", + "deque": "cpp", + "unordered_map": "cpp", + "unordered_set": "cpp", + "vector": "cpp", + "exception": "cpp", + "algorithm": "cpp", + "functional": "cpp", + "iterator": "cpp", + "map": "cpp", + "memory": "cpp", + "memory_resource": "cpp", + "numeric": "cpp", + "optional": "cpp", + "random": "cpp", + "system_error": "cpp", + "tuple": "cpp", + "type_traits": "cpp", + "utility": "cpp", + "fstream": "cpp", + "initializer_list": "cpp", + "iomanip": "cpp", + "iosfwd": "cpp", + "istream": "cpp", + "limits": "cpp", + "ostream": "cpp", + "sstream": "cpp", + "stdexcept": "cpp", + "streambuf": "cpp", + "cinttypes": "cpp", + "typeinfo": "cpp" }, "C_Cpp.errorSquiggles": "disabled" } \ No newline at end of file diff --git a/src/Radar.cpp b/src/Radar.cpp new file mode 100644 index 0000000..e69de29 diff --git a/src/Radar.h b/src/Radar.h new file mode 100644 index 0000000..416b754 --- /dev/null +++ b/src/Radar.h @@ -0,0 +1,36 @@ +#ifndef RADAR_H +#define RADAR_H +// 在main.cpp顶部添加R60ABD1协议相关定义 +#define FRAME_HEADER1 0x53 // 帧头字节1 +#define FRAME_HEADER2 0x59 // 帧头字节2 +#define FRAME_TAIL1 0x54 // 帧尾字节1 +#define FRAME_TAIL2 0x43 // 帧尾字节2 + +// 控制字定义 +#define CTRL_PRESENCE 0x80 // 人体存在检测 +#define CTRL_BREATH 0x81 // 呼吸检测 +#define CTRL_SLEEP 0x84 // 睡眠监测 +#define CTRL_HEARTRATE 0x85 // 心率监测 + +// 命令字定义 +#define CMD_REPORT 0x80 // 主动上报 +#define CMD_QUERY 0x81 // 查询命令 +#define CMD_SET 0x82 // 设置命令 + +// 定义R60ABD1数据结构 +typedef struct { + uint8_t present; // 有人/无人状态 (DP1) + uint16_t distance; // 人体距离 (DP3) 单位cm + uint8_t heartRate; // 心率 (DP6) 单位BPM + uint8_t breathRate; // 呼吸率 (DP8) 单位次/分钟 + uint8_t heartWave; // 心率波形 (DP7) 数值+128 + uint8_t breathWave; // 呼吸波形 (DP10) 数值+128 + uint8_t sleepState; // 睡眠状态 (DP12) + uint32_t sleepTime; // 睡眠时长 (DP13) 单位秒 + uint8_t sleepScore; // 睡眠质量评分 (DP14) + uint8_t bedEntry; // 入床/离床状态 (DP11) + uint8_t abnormal; // 异常状态 (DP18) +} R60ABD1Data; + + +#endif diff --git a/src/main.cpp b/src/main.cpp index 327cec7..478b01c 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -12,6 +12,99 @@ #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个数据点 // 环形缓冲区结构 @@ -25,10 +118,9 @@ struct CircularBuffer { float sum; // 当前数据总和 }; -CircularBuffer heartRateBuffer = {{0}, {0}, 0, 0, 0, false, 0}; -CircularBuffer breathRateBuffer = {{0}, {0}, 0, 0, 0, false, 0}; -Preferences preferences; + +Preferences preferences;// 声明一个Preferences对象 // 设备ID变量 uint16_t currentDeviceId = 0000; // 默认设备ID为0000 @@ -38,9 +130,12 @@ 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 "4fafc201-1fb5-459e-8fcc-c5c9c331914b" +#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; @@ -50,10 +145,16 @@ String completeData = ""; unsigned long lastReceiveTime = 0; WiFiClient client; -char ssid[32] = "13-205"; -char password[64] = "12345678"; +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=="; @@ -66,38 +167,81 @@ 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; - float heart_rate; - int rssi; - uint8_t breath_valid; - uint8_t heart_valid; - uint8_t presence; - uint8_t motion; - int heartbeat_waveform; - int breathing_waveform; - int raw_signal; + float breath_rate; // 呼吸率 (DP8) 单位:次/分钟 + float heart_rate; // 心率 (DP6) 单位:BPM + uint8_t breath_valid; // 呼吸率有效性 + uint8_t heart_valid; // 心率有效性 + uint8_t presence ; // 存在检测 (DP1) 0=无人, 1=有人 + uint8_t motion = 0; // 运动状态 (DP2) 0=无, 1=静止, 2=活跃 + int heartbeat_waveform; // 心跳波形 (DP7) 数值+128 + int breathing_waveform; // 呼吸波形 (DP10) 数值+128 + + // R60ABD1新增字段 + uint16_t distance; // 人体距离 (DP3) 单位:cm + uint8_t body_movement; // 体动幅度 (DP4) 0-100 + uint8_t breath_status = 04; // 呼吸信息 (DP9) 01=正常, 02=呼吸过高, 03=呼吸过低, 04=无 + uint8_t sleep_state = 3; // 睡眠状态 (DP12) 0=深睡, 1=浅睡, 2=清醒, 3=无睡眠 + uint32_t sleep_time; // 睡眠时长 (DP13) 单位:秒 + uint8_t sleep_score; // 睡眠质量评分 (DP14) 0-100 + uint8_t sleep_grade; //睡眠质量评级 + uint8_t bed_entry; // 入床/离床状态 (DP11) 0=离床, 1=入床 + uint8_t abnormal_state; // 异常状态 (DP18) 0=正常, 1=异常 + uint8_t avg_heart_rate; // 平均心率 (DP15) + uint8_t avg_breath_rate; // 平均呼吸率 (DP15) + uint8_t turn_count; // 翻身次数 (DP15) + uint8_t large_move_ratio;// 大幅度体动占比 (DP15) + uint8_t small_move_ratio;// 小幅度体动占比 (DP15) + + // 根据技术文档新增字段 + int16_t pos_x; // 人体X坐标 (DP5) + int16_t pos_y; // 人体Y坐标 (DP5) + int16_t pos_z; // 人体Z坐标 (DP5) + int8_t breath_waveform[5]; // 呼吸波形数据 (DP10) + int8_t heart_waveform[5]; // 心率波形数据 (DP7) + uint16_t deep_sleep_time; // 深睡时长 (DP15) 单位:分钟 + uint16_t light_sleep_time; // 浅睡时长 (DP14) 单位:分钟 + uint16_t awake_time; // 清醒时长 (DP13) 单位:分钟 + uint16_t sleep_total_time; // 睡眠总时长 (DP18) 单位:分钟 + uint8_t deep_sleep_ratio; // 深睡占比 (DP16) + uint8_t light_sleep_ratio; // 浅睡占比 (DP16) + uint8_t awake_ratio; // 清醒占比 (DP16) + uint8_t turnover_count; // 翻身次数 (DP16) + uint8_t struggle_alert; // 挣扎报警 (DP21) + uint8_t no_one_alert; // 无人计时报警 (DP22) + uint8_t bed_status = 02; //入床状态 (DP11) + uint8_t bed_Out_Time; // 离床时间 (DP11) + uint8_t apnea_count; // 呼吸暂停次数 } SensorData; -SensorData sensorData = {0}; +SensorData sensorData ; -HardwareSerial mySerial2(2); -const int BAUD_RATE = 576000; -const int UART2_RX = 16; -const int UART2_TX = 17; -String rxBuffer = ""; +// 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; -// 注意:bleSendTaskHandle现在用于蓝牙发送任务 +TaskHandle_t uartProcessTaskHandle = NULL; // 添加串口处理任务句柄 #define QUEUE_SIZE 50 #define TASK_STACK_SIZE 8192 #define TASK_PRIORITY 1 -#define UART_RX_QUEUE_SIZE 100 // 串口接收队列大小 typedef struct { int heartbeat_waveform; @@ -109,9 +253,53 @@ typedef struct { float breath_rate; uint8_t presence; uint8_t motion; - int rssi; + 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: @@ -171,23 +359,41 @@ public: // 全局变量用于控制持续发送 bool continuousSendEnabled = false; -unsigned long continuousSendInterval = 1000; // 默认1秒发送一次 +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配置命令 -void processRadarData(); // 处理雷达数据 -bool parseSensorLine(String line); // 解析传感器数据行 -// void sendRadarCommand(String cmd); // 发送雷达命令 -void updateStatusFlags(); // 更新状态标志 + +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); // 处理停止持续发送命令 @@ -196,17 +402,60 @@ void sendDataInChunks(const String& data); // 分包发送函数 void sendJSONDataToBLE(const String& jsonData); // 发送JSON数据到BLE(使用纯数据分包发送,不带包头) bool sendCustomJSONData(const String& jsonType, const String& jsonString); // 发送自定义JSON数据到BLE -float addDataAndCalculateAverage(CircularBuffer* buffer, float newData); // 计算平均值并添加新数据到缓冲区 +// 根据协议ID获取对应的字段名 +String getFieldNameByProtocolId(int protocolId); +// 根据协议ID获取对应的字段名 +String getFieldNameByProtocolId(int protocolId) { + switch(protocolId) { + case PROTOCOL_HEART_RATE: + return "heartRate"; + case PROTOCOL_BREATH_RATE: + return "breathingRate"; + case PROTOCOL_PERSON_DETECTED: + return "personDetected"; + case PROTOCOL_HUMAN_ACTIVITY: + return "humanActivity"; + case PROTOCOL_HUMAN_DISTANCE: + return "humanDistance"; + case PROTOCOL_HUMAN_POSITION: + return "humanPosition"; + case PROTOCOL_SLEEP_STATE: + return "sleepState"; + default: + return "unknown"; + } +} // FreeRTOS任务函数声明 void bleSendTask(void *parameter); void vitalSendTask(void *parameter); void radarDataTask(void *parameter); +void uartProcessTask(void *parameter); // 添加串口处理任务声明 + + +// 使用正确的函数签名 - 不带参数的回调函数 +void IRAM_ATTR serialRxCallback() { + // 直接使用全局变量mySerial1和uartQueue + if (uartQueue != NULL) { + // 读取所有可用数据 + while(mySerial1.available()) { + char c = mySerial1.read(); + + // 将字符放入队列(从中断安全函数) + BaseType_t xHigherPriorityTaskWoken = pdFALSE; + xQueueSendFromISR(uartQueue, &c, &xHigherPriorityTaskWoken); + + if(xHigherPriorityTaskWoken) { + portYIELD_FROM_ISR(); + } + } + } +} class MyServerCallbacks: public BLEServerCallbacks { void onConnect(BLEServer* pServer) { deviceConnected = true; - Serial.println("BLE客户端已连接"); + Serial.println("✅ [BLE] 客户端已连接"); // 发送连接状态信息 sendStatusToBLE(); @@ -214,25 +463,25 @@ class MyServerCallbacks: public BLEServerCallbacks { void onDisconnect(BLEServer* pServer) { deviceConnected = false; - Serial.println("BLE客户端已断开"); + Serial.println("🔴 [BLE] 客户端已断开"); // 重置持续发送状态 continuousSendEnabled = false; - Serial.println("重置持续发送状态"); + Serial.println("🔄 重置持续发送状态"); } }; class MyCallbacks: public BLECharacteristicCallbacks { void onWrite(BLECharacteristic *pCharacteristic) { std::string value = pCharacteristic->getValue(); - Serial.printf("🔵 收到BLE写入数据,长度: %d 字节\n", value.length()); + 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("📄 接收数据片段: %s\n", fragment.c_str()); + Serial.printf("📄 [BLE] 接收数据片段: %s\n", fragment.c_str()); completeData += fragment; lastReceiveTime = millis(); @@ -245,7 +494,7 @@ class MyCallbacks: public BLECharacteristicCallbacks { String jsonData = completeData.substring(openBrace, closeBrace + 1); completeData = completeData.substring(closeBrace + 1); - Serial.printf("📥 完整JSON数据: %s\n", jsonData.c_str()); + Serial.printf("📥 [BLE] 完整JSON数据: %s\n", jsonData.c_str()); // 将JSON数据存储到receivedData变量中供后续处理 receivedData = jsonData; @@ -255,113 +504,269 @@ class MyCallbacks: public BLECharacteristicCallbacks { }; void setup() { - Serial.begin(115200); - //delay(10000); // 关键:等待雷达完全启动 - sensorData.breath_rate = 0; - sensorData.heart_rate = 0; - sensorData.rssi = 0; - sensorData.breath_valid = 0; - sensorData.heart_valid = 0; + 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,输出模式 - esp_task_wdt_init(30, true); - esp_task_wdt_add(NULL); + // 初始化配置清除指示灯为LOW(正常运行状态) + digitalWrite(CONFIG_CLEAR_PIN, LOW); + digitalWrite(GPIO8, LOW); + digitalWrite(GPIO9, LOW); + // 初始状态设置 + digitalWrite(NETWORK_LED_PIN, LOW); // 网络状态LED,输出模式,输出低电平 + digitalWrite(CONFIG_CLEAR_PIN, LOW); - // 增加串口缓冲区大小到4096字节以处理大量雷达数据 - mySerial2.setRxBufferSize(4096); - mySerial2.begin(BAUD_RATE, SERIAL_8N1, UART2_RX, UART2_TX); - Serial.println("UART2配置完成,缓冲区大小: 4096字节"); + // 设置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 - delay(1000); - - // String startCmd = "{\"cmd\":\"setMonitor\",\"para\":1}\n"; - // mySerial2.print(startCmd); - // delay(1000); - // Serial.println("启动雷达数据传输"); - - preferences.begin("radar_data", false); - - // 加载设备ID和WiFi配置 - loadDeviceId(); - loadWiFiConfig(); - - // 创建FreeRTOS队列 - phaseDataQueue = xQueueCreate(QUEUE_SIZE, sizeof(PhaseData)); - vitalDataQueue = xQueueCreate(QUEUE_SIZE, sizeof(VitalData)); - - if (phaseDataQueue == NULL || vitalDataQueue == NULL) { - Serial.println("❌ 队列创建失败"); - } else { - Serial.println("✅ FreeRTOS队列创建成功"); - } - - // 创建FreeRTOS任务 - xTaskCreatePinnedToCore( - bleSendTask, - "BleSendTask", - TASK_STACK_SIZE, - NULL, - 3, // 提高优先级到3 - &bleSendTaskHandle, - 1 - ); - - xTaskCreatePinnedToCore( - vitalSendTask, - "VitalSendTask", - TASK_STACK_SIZE, - NULL, - 2, // 中等优先级 - &vitalSendTaskHandle, - 1 - ); - - // 创建专门的雷达数据处理任务(最高优先级) - xTaskCreatePinnedToCore( - radarDataTask, - "RadarProcessTask", - 4096, - NULL, - 4, // 最高优先级 - NULL, - 1 - ); + // 注册WiFi事件 + WiFi.onEvent(WiFiEvent); - Serial.println("✅ FreeRTOS任务创建成功"); - - BLEDevice::init("ESP32-Radar"); - 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("BLE已启动,设备名称: ESP32-Radar"); - - if (wifiConfigured) { - Serial.println("检测到已保存的WiFi配置,尝试连接..."); - if (connectWiFi()) { - Serial.println("WiFi连接成功!"); + // 设置初始网络状态 + setNetworkStatus(NET_INITIAL); + + esp_task_wdt_init(30, true); // 初始化任务看门狗,30秒超时 + esp_task_wdt_add(NULL); + + // 增加串口缓冲区大小以处理R60ABD1数据 + mySerial1.setRxBufferSize(UART_RX_BUFFER_SIZE); + mySerial1.begin(BAUD_RATE, SERIAL_8N1, UART1_RX, UART1_TX); + + Serial.println("UART1配置完成,缓冲区大小: 4096字节"); + 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("WiFi连接失败,请通过BLE重新配置"); - wifiConfigured = false; + Serial.println("✅ FreeRTOS队列创建成功"); + } + + // 设置串口中断回调函数 + mySerial1.onReceive(serialRxCallback); + Serial.println("✅ 串口中断回调已设置"); + + Serial.println("🏃 创建FreeRTOS任务..."); + // 创建FreeRTOS任务 + xTaskCreatePinnedToCore( + bleSendTask, + "BleSendTask", + TASK_STACK_SIZE, + NULL, + 3, // 提高优先级到3 + &bleSendTaskHandle, + 1 + ); + + xTaskCreatePinnedToCore( + vitalSendTask, + "VitalSendTask", + TASK_STACK_SIZE, + NULL, + 2, // 中等优先级 + &vitalSendTaskHandle, + 1 + ); + + // 创建专门的雷达数据处理任务 + xTaskCreatePinnedToCore( + radarDataTask, + "RadarProcessTask", + 4096, + NULL, + 4, // 最高优先级 + NULL, + 1 + ); + + // 创建R60ABD1串口数据处理任务 + xTaskCreatePinnedToCore( + uartProcessTask, // R60ABD1串口处理任务 + "UartProcessTask", + 4096, + NULL, + 5, // 最高优先级 + &uartProcessTaskHandle, + 1 + ); + + // 创建配置清除指示灯控制任务 + xTaskCreate( + configClearLedTask, // 任务函数 + "Config Clear LED Task", // 任务名称 + 2048, // 堆栈大小 + NULL, // 参数 + 1, // 优先级 + NULL // 任务句柄 + ); + + // 创建BOOT按钮监控任务 + xTaskCreate( + bootButtonMonitorTask, // 任务函数 + "Boot Button Monitor Task", // 任务名称 + 2048, // 堆栈大小 + NULL, // 参数 + 1, // 优先级 + NULL // 任务句柄 + ); + + // 创建LED控制任务 + xTaskCreate( + ledControlTask, // 任务函数 + "LED Control Task", // 任务名称 + 2048, // 堆栈大小 + NULL, // 参数 + 1, // 优先级 + NULL // 任务句柄 + ); + + // 创建WiFi监控任务 + xTaskCreate( + wifiMonitorTask, // 任务函数 + "WiFi Monitor Task", // 任务名称 + 4096, // 堆栈大小 + NULL, // 参数 + 2, // 优先级 + NULL // 任务句柄 + ); + + Serial.println("✅ FreeRTOS任务创建成功"); + + Serial.println("📶 初始化BLE服务..."); + String deviceName = "Radar_" + String(currentDeviceId); + BLEDevice::init(deviceName.c_str()); + pServer = BLEDevice::createServer(); + pServer->setCallbacks(new MyServerCallbacks()); + + BLEService *pService = pServer->createService(SERVICE_UUID); + pCharacteristic = pService->createCharacteristic( + CHARACTERISTIC_UUID, + BLECharacteristic::PROPERTY_READ | + BLECharacteristic::PROPERTY_WRITE | + BLECharacteristic::PROPERTY_NOTIFY + ); + pCharacteristic->setCallbacks(new MyCallbacks()); + pCharacteristic->addDescriptor(new BLE2902()); + + pService->start(); + BLEAdvertising *pAdvertising = BLEDevice::getAdvertising(); + pAdvertising->addServiceUUID(SERVICE_UUID); + pAdvertising->setScanResponse(true); + pAdvertising->setMinPreferred(0x06); + pAdvertising->setMinPreferred(0x12); + BLEDevice::startAdvertising(); + + Serial.println(String("BLE已启动,设备名称: Radar_") + String(currentDeviceId)); + + Serial.println("🌐 检查WiFi配置..."); + if (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(); } - } } void loop() { @@ -376,19 +781,44 @@ void loop() { if (deviceConnected && !oldDeviceConnected) { oldDeviceConnected = deviceConnected; } - - processBLEConfig(); + // 使用非阻塞方式轮询发送一系列查询命令,间隔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} // 睡眠质量评级 + }; - // 移除WiFi连接检查,使雷达数据处理不依赖WiFi连接 + 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(); - // 雷达数据处理已移至专用任务 radarDataTask - // processRadarData(); esp_task_wdt_reset(); - updateStatusFlags(); - - // 注意:持续发送雷达数据的逻辑已移至FreeRTOS任务处理 - // 主循环不再直接处理蓝牙数据发送,避免影响雷达数据接收和解析 + // updateStatusFlags(); + // 在主循环中添加更频繁的看门狗重置 + esp_task_wdt_reset(); delay(1); } @@ -415,9 +845,10 @@ bool processQueryStatus(JsonDocument& doc) { return true; } return false; -} +} -// 处理查询雷达数据命令 +// 处理查询雷达数据命令(蓝牙) +//这个函数是一个响应式的数据发送函数,仅在接收到查询请求时才会发送当前的雷达传感器数据 bool processQueryRadarData(JsonDocument& doc) { const char* command = doc["command"]; if (command != nullptr && strcmp(command, "queryRadarData") == 0) { @@ -426,16 +857,25 @@ bool processQueryRadarData(JsonDocument& doc) { // 发送最新的雷达数据 if (deviceConnected) { String radarDataMsg = String("{\"type\":\"radarData\",\"success\":true") + - String(",\"deviceId\":") + String(currentDeviceId) + - String(",\"timestamp\":") + String(millis()) + + 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(",\"rssi\":") + String(sensorData.rssi) + - String(",\"heartbeatWaveform\":") + String(sensorData.heartbeat_waveform) + - String(",\"breathingWaveform\":") + String(sensorData.breathing_waveform) + - String(",\"rawSignal\":") + String(sensorData.raw_signal) + + 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数据 @@ -641,14 +1081,14 @@ void processBLEConfig() { receivedData = ""; bleData.trim(); - Serial.printf("⚙️ [处理] 准备解析JSON: %s\n", bleData.c_str()); + 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("[错误] JSON解析失败: ") + String(error.c_str()); + String errorMsg = String("❌ [BLE] JSON解析失败: ") + String(error.c_str()); Serial.println(errorMsg); if (deviceConnected) { String responseMsg = String("{\"type\":\"error\",\"message\":\"配置格式错误,请使用JSON格式\"}"); @@ -657,7 +1097,7 @@ void processBLEConfig() { sendJSONDataToBLE(responseMsg); } } else { - Serial.println("✅ JSON解析成功"); + Serial.println("✅ [BLE] JSON解析成功"); // 处理各种命令 bool processed = false; @@ -682,7 +1122,7 @@ void processBLEConfig() { // 如果没有处理任何命令,发送错误响应 if (!processed) { - Serial.println("❓ 未知命令"); + Serial.println("❓[BLE] 未知命令"); if (deviceConnected) { String responseMsg = String("{\"type\":\"error\",\"message\":\"未知命令\"}"); @@ -692,17 +1132,16 @@ void processBLEConfig() { } } } else { - Serial.println("📥 接收到非JSON数据"); + Serial.println("📥 [BLE] 接收到非JSON数据"); } } } - - - // FreeRTOS任务:蓝牙数据发送(精简格式以节省空间) -void bleSendTask(void *parameter) { - Serial.println("🔁 蓝牙数据发送任务启动"); +//主动发送:这是一个持续运行的FreeRTOS任务,不断地从队列中获取数据并主动发送到蓝牙,用于实时数据传输 +void bleSendTask(void *parameter) +{ + Serial.println("🔁 R60ABD1蓝牙数据发送任务启动"); while (1) { PhaseData phaseData; @@ -711,30 +1150,37 @@ void bleSendTask(void *parameter) { if (xQueueReceive(phaseDataQueue, &phaseData, portMAX_DELAY) == pdTRUE) { esp_task_wdt_reset(); - // 蓝牙传输 - 构造并发送完整的雷达数据(包含所有7个字段,无标识符以节省空间) - // 修改逻辑:无论是否检测到人都发送蓝牙数据 + // 蓝牙传输 - 构造并发送完整的R60ABD1雷达数据 if (deviceConnected && continuousSendEnabled) { String radarDataCore; // 检查是否检测到人 - if (sensorData.presence > 0) { - // 检测到人时,发送实际数据 + if (sensorData.presence > 0) + { + // 检测到人时,发送实际数据,包含R60ABD1扩展功能 radarDataCore = String(sensorData.heart_rate, 1) + String("|") + String(sensorData.breath_rate, 1) + String("|") + - String(phaseData.heartbeat_waveform) + String("|") + - String(phaseData.breathing_waveform) + 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.rssi); - } else { - // 未检测到人时,发送零数据 + // 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(sensorData.rssi); + // String("0") + String("|") + // 距离 + String("0"); // 睡眠状态 + // String("0") + String("|") + // 睡眠时长 + // String("0"); // 睡眠评分 } // 计算CRC校验和 @@ -754,7 +1200,7 @@ void bleSendTask(void *parameter) { // 添加校验和到数据末尾 String radarDataMsg = radarDataCore + String("|") + String(crc, HEX); - Serial.printf("📤 通过蓝牙发送完整雷达数据: %s\n", radarDataMsg.c_str()); + Serial.printf("📤 通过蓝牙发送R60ABD1雷达数据: %s\n", radarDataMsg.c_str()); // 检查数据长度决定发送方式 const int MAX_BLE_PACKET_SIZE = 20; // BLE最大包大小 @@ -762,10 +1208,10 @@ void bleSendTask(void *parameter) { // 数据较短,直接发送 pCharacteristic->setValue(radarDataMsg.c_str()); pCharacteristic->notify(); - Serial.println("✅ 完整雷达数据蓝牙发送成功"); + Serial.println("✅ R60ABD1雷达数据蓝牙发送成功"); } else { // 数据较长,使用分包发送 - Serial.println("🔄 雷达数据较长,使用分包发送"); + Serial.println("🔄 R60ABD1雷达数据较长,使用分包发送"); sendDataInChunks(radarDataMsg); } } @@ -774,12 +1220,17 @@ void bleSendTask(void *parameter) { } vTaskDelay(1 / portTICK_PERIOD_MS); // 减少延迟以提高实时性 + // 在BLE任务循环中重置看门狗 + esp_task_wdt_reset(); } } - -// FreeRTOS任务:发送生命体征数据 +// FreeRTOS任务:发送生命体征数据到数据库 void vitalSendTask(void *parameter) { - Serial.println("🔁 生命体征数据发送任务启动(WiFi数据库传输)"); + Serial.println("🔁🔁 生命体征数据发送任务启动(WiFi数据库传输)"); + + // 记录上次发送睡眠数据的时间 + unsigned long lastSleepDataTime = 0; + const unsigned long SLEEP_DATA_INTERVAL = 5000; // 5秒 = 5000毫秒 while (1) { VitalData vitalData; @@ -788,102 +1239,227 @@ void vitalSendTask(void *parameter) { if (xQueueReceive(vitalDataQueue, &vitalData, portMAX_DELAY) == pdTRUE) { esp_task_wdt_reset(); - // WiFi数据库传输 - 一直发送生命体征数据(不通过蓝牙) + // WiFi数据库传输 - 按照Python脚本的双模块架构发送数据 if (WiFi.status() == WL_CONNECTED) { - HTTPClient http; - http.setTimeout(2000); + // 发送日常数据(高频实时监测数据)- daily_data + // 构造日常数据行协议 + String dailyDataLine = "daily_data,deviceId=" + String(currentDeviceId) + ",dataType=daily "; - String url = String("http://") + String(influxDBHost) + ":" + String(influxDBPort) + "/api/v2/write?org=" + String(influxDBOrg) + "&bucket=" + String(influxDBBucket) + "&precision=ns"; - - http.begin(url); - http.addHeader("Authorization", String("Token ") + String(influxDBToken)); - http.addHeader("Content-Type", "text/plain"); - http.setReuse(true); - - // 使用可变的设备ID - String deviceId = String(currentDeviceId); - - String vitalDataStr = ""; + // 添加各个实时监测字段 bool firstField = true; - // 修改发送逻辑: - // 当检测到人体存在时(presence > 0),发送完整的雷达数据(不包含相位数据) - // 当检测到人体不存在时(presence == 0),只发送检测是否有人的数据 - if (vitalData.presence > 0) { - // 人体存在时,发送完整数据 - - // 心率数据 - 使用浮点数格式 - if (vitalData.heart_rate > 0) { - if (!firstField) vitalDataStr += ","; - vitalDataStr += "heartRate="; - vitalDataStr += String(vitalData.heart_rate / 1.0, 1); - firstField = false; - } - - // 呼吸率数据 - 使用浮点数格式 - if (vitalData.breath_rate > 0) { - if (!firstField) vitalDataStr += ","; - vitalDataStr += "breathingRate="; - vitalDataStr += String(vitalData.breath_rate / 1.0, 1); - firstField = false; - } - - // 人检数据 - 确保值为0或1,使用整数格式 - if (!firstField) vitalDataStr += ","; - vitalDataStr += "personDetected="; - // 确保值为0或1 - int presenceValue = (vitalData.presence > 0) ? 1 : 0; - vitalDataStr += String(presenceValue) + "i"; // 整数格式 - firstField = false; - - // 人体活动数据 - 确保值为0或1,使用整数格式 - if (!firstField) vitalDataStr += ","; - vitalDataStr += "humanActivity="; - // 确保值为0或1 - int motionValue = (vitalData.motion > 0) ? 1 : 0; - vitalDataStr += String(motionValue) + "i"; // 整数格式 - firstField = false; - - // RSSI数据 - 使用整数格式 - if (vitalData.rssi != 0) { - if (!firstField) vitalDataStr += ","; - vitalDataStr += "rssi="; - vitalDataStr += String(vitalData.rssi) + "i"; // 整数格式 - firstField = false; - } - } else { - // 人体不存在时,只发送人检数据以减少芯片压力 - vitalDataStr += "personDetected="; - // 确保值为0 - vitalDataStr += String(0) + "i"; // 整数格式 + if (vitalData.heart_rate > 0) { + if (!firstField) dailyDataLine += ","; + dailyDataLine += "heartRate=" + String(vitalData.heart_rate, 1);// 心率 firstField = false; } - // 如果有数据要发送,则构造完整的行协议字符串 - if (vitalDataStr.length() > 0) { - vitalDataStr = "device_data,deviceId=" + deviceId + " " + vitalDataStr; - Serial.println("📤 发送生命体征数据到数据库: " + vitalDataStr); - - int httpResponseCode = http.POST(vitalDataStr); - - if (httpResponseCode == 204) { - Serial.println("✅ 生命体征数据发送到数据库成功"); - } else { - String errorMsg = String("❌ 生命体征数据发送到数据库失败: ") + String(httpResponseCode); - Serial.println(errorMsg); - } + if (vitalData.breath_rate > 0) { + if (!firstField) dailyDataLine += ","; + dailyDataLine += "breathingRate=" + String(vitalData.breath_rate, 1);// 呼吸率 + firstField = false; } - http.end(); + 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未连接,无法发送生命体征数据到数据库"); + 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(5 / portTICK_PERIOD_MS); + 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(); } // 雷达数据处理任务 - 高优先级 @@ -891,168 +1467,712 @@ void radarDataTask(void *parameter) { Serial.println("🔁 雷达数据处理任务启动(最高优先级)"); while (1) { - // 处理雷达数据 - processRadarData(); + // 雷达数据处理已移至专用任务处理,这里不需要做任何事情 + // 串口数据现在由uartProcessTask处理 // 短暂延迟以允许其他任务运行 - vTaskDelay(1 / portTICK_PERIOD_MS); + vTaskDelay(100 / portTICK_PERIOD_MS); } } -void updateStatusFlags() { - static unsigned long lastStatusUpdate = 0; - static int lastPresence = -1; // 记录上一次的presence状态 - - // 检查presence状态是否发生变化 - if (lastPresence != -1 && lastPresence != sensorData.presence) { - // 状态发生变化时重置滤波器缓冲区 - memset(&heartRateBuffer, 0, sizeof(CircularBuffer)); - memset(&breathRateBuffer, 0, sizeof(CircularBuffer)); - // Serial.println("🔄 检测状态变化,重置滤波器缓冲区"); - } - lastPresence = sensorData.presence; - - if (millis() - lastStatusUpdate > 5000) { - // Serial.printf("系统状态: 心率=%.1f, 呼吸=%.1f, 有人=%d, 运动=%d, RSSI=%d\n", - // sensorData.heart_rate, sensorData.breath_rate, - // sensorData.presence, sensorData.motion, sensorData.rssi); - lastStatusUpdate = millis(); - } -} -bool parseSensorLine(String line) { - // if (!line.startsWith("HBR01")) { - // Serial.println("❌ 数据行不以HBR01开头"); - // return false; - // } +// 修改串口处理任务以支持二进制协议 +//存储数据:将解析后的数据分别存储到两个结构体中:vitalData,phaseData +void uartProcessTask(void *parameter) { + uint8_t buffer[256]; // 接收缓冲区 + int bufferIndex = 0; + bool inFrame = false; + uint8_t prevByte = 0; - int colonIndex = line.indexOf(':'); - // if (colonIndex == -1) { - // Serial.println("❌ 数据行缺少冒号分隔符"); - // return false; - // } + Serial.println("✅ R60ABD1串口数据处理任务启动"); - String dataPart = line.substring(colonIndex + 1); - dataPart.trim(); - - // Serial.printf("🔍 解析数据部分: %s\n", dataPart.c_str()); - - int values[8]; - int startIndex = 0; - int commaIndex; - int fieldCount = 0; - - for (int i = 0; i < 8; i++) { - commaIndex = dataPart.indexOf(',', startIndex); + 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; + } - if (commaIndex == -1 && i == 7) { - String valueStr = dataPart.substring(startIndex); - valueStr.trim(); - values[i] = valueStr.toInt(); - fieldCount++; - // Serial.printf(" 字段%d: %s -> %d\n", i+1, valueStr.c_str(), values[i]); - } else if (commaIndex != -1) { - String valueStr = dataPart.substring(startIndex, commaIndex); - valueStr.trim(); - values[i] = valueStr.toInt(); - startIndex = commaIndex + 1; - fieldCount++; - // Serial.printf(" 字段%d: %s -> %d\n", i+1, valueStr.c_str(), values[i]); - } else { - // Serial.printf("❌ 解析错误: 字段%d缺失,总共找到%d个字段\n", i+1, fieldCount); - return false; + // 允许其他任务运行 + 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(); } } - - sensorData.presence = values[0]; - - // 当检测到人时才进行滤波,否则使用原始数据 - if (sensorData.presence > 0) { - // 应用滤波器到心率和呼吸速率数据 - sensorData.heart_rate = addDataAndCalculateAverage(&heartRateBuffer, (float)values[1]); - sensorData.breath_rate = addDataAndCalculateAverage(&breathRateBuffer, (float)values[2]); - // Serial.println("📈 使用滤波数据"); - } else { - // 无人时使用原始数据 - sensorData.heart_rate = (float)values[1]; - sensorData.breath_rate = (float)values[2]; - // Serial.println("📉 使用原始数据(无人状态)"); + if(checksum != frame[frameLen-3]) { // 校验和在倒数第三个字节 + Serial.println("❌ R60ABD1帧校验和错误"); + return false; } - sensorData.motion = values[3]; - sensorData.rssi = values[4]; - sensorData.heartbeat_waveform = values[5]; - sensorData.breathing_waveform = values[6]; - sensorData.raw_signal = values[7]; - - // 如果雷达强度信号小于625,强制将检测结果设为无人 - if (sensorData.rssi < 625) { - sensorData.presence = 0; + 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]); - sensorData.heart_valid = (sensorData.heart_rate > 0 && sensorData.heart_rate < 200); - sensorData.breath_valid = (sensorData.breath_rate >= 0.1f && sensorData.breath_rate <= 60.0f); + // 提取帧内容 + 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(); - // 添加您要求保留的打印语句 - Serial.printf("Heart_Rate_RAW:%.2f\n", sensorData.heart_rate); - Serial.printf("Breath_Rate_RAW:%.2f\n", sensorData.breath_rate); - - // Serial.printf("✅ 解析成功: 有人=%d, 心率=%.1f, 呼吸=%.1f, 运动=%d, 波形=%d/%d\n", - // sensorData.presence, sensorData.heart_rate, - // sensorData.breath_rate, sensorData.motion, - // sensorData.heartbeat_waveform, sensorData.breathing_waveform); + // 验证数据有效性 + 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); - preferences.putString("password", password); - preferences.putBool("configured", true); + 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); - if (wifiConfigured) { - preferences.getString("ssid", ssid, sizeof(ssid)); - preferences.getString("password", password, sizeof(password)); + 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] 正在尝试连接..."); - int attempts = 0; - while (WiFi.status() != WL_CONNECTED && attempts < 20) { + 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()); - attempts++; + // 在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.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()); + 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(String cmd) { -// if (!cmd.endsWith("\n")) { -// cmd += "\n"; -// } -// mySerial2.print(cmd); -// Serial.println("📤 [雷达命令] " + cmd); -// } +// 修正后的发送雷达命令函数 +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) { @@ -1099,13 +2219,13 @@ bool processSetDeviceId(JsonDocument& doc) { // 加载设备ID void loadDeviceId() { - currentDeviceId = preferences.getUShort("deviceId", 1001); + currentDeviceId = preferences.getUShort("deviceId", 1001);// 从Flash加载设备ID Serial.printf("从Flash加载设备ID: %u\n", currentDeviceId); } // 保存设备ID void saveDeviceId() { - preferences.putUShort("deviceId", currentDeviceId); + preferences.putUShort("deviceId", currentDeviceId);// 保存设备ID到Flash Serial.printf("设备ID已保存到Flash: %u\n", currentDeviceId); } @@ -1131,11 +2251,14 @@ void sendStatusToBLE() { 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'; // 确保字符串结束符 @@ -1146,6 +2269,7 @@ bool processWiFiConfigCommand(JsonDocument& doc) { saveWiFiConfig(); // 尝试连接WiFi + Serial.println("🔄 [WiFi] 尝试连接到新配置的网络..."); bool connected = connectWiFi(); // 发送配置结果 @@ -1157,17 +2281,18 @@ bool processWiFiConfigCommand(JsonDocument& doc) { String("\"}"); // 使用新的分包发送函数发送JSON数据 sendJSONDataToBLE(resultMsg); - Serial.println("已发送WiFi配置结果"); + 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("WiFi配置参数不完整"); + Serial.println("📱 [BLE] 发送WiFi配置错误信息"); } return true; // 已处理该命令(虽然失败了) } @@ -1175,142 +2300,419 @@ bool processWiFiConfigCommand(JsonDocument& doc) { return false; // 未处理该命令 } - - - - -/** - * 添加新数据并计算一分钟内的平均值 - * @param buffer 环形缓冲区指针 - * @param newData 新输入的数据 - * @return 一分钟内数据的平均值,如果没有数据返回0 - */ -float addDataAndCalculateAverage(CircularBuffer* buffer, float newData) { - - // 只有在检测到人的情况下才进行滤波处理 - if (sensorData.presence == 0) { - return newData; // 没有检测到人时,直接返回原始数据 - } - - unsigned long currentTime = millis(); - unsigned long oneMinuteAgo = currentTime - 60000; // 一分钟前的时间 +// 在setup()函数中添加R60ABD1初始化 +void initR60ABD1() { + Serial.println("🔧 初始化R60ABD1雷达模组..."); - // 移除过期的旧数据(一分钟前的数据) - while (buffer->count > 0 && buffer->timestamps[buffer->tail] < oneMinuteAgo) { - buffer->sum -= buffer->data[buffer->tail]; - buffer->tail = (buffer->tail + 1) % BUFFER_SIZE; - buffer->count--; - buffer->isFull = (buffer->count == BUFFER_SIZE); - } + // 发送查询指令以激活数据上报 + 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("📡 开启核心监测功能..."); + // 发送多次人体存在监测开启命令以确保生效 - // 添加新数据 - if (buffer->count < BUFFER_SIZE) { - // 缓冲区未满,直接添加 - buffer->head = (buffer->head + 1) % BUFFER_SIZE; - if (buffer->count == 0) buffer->tail = buffer->head; - buffer->data[buffer->head] = newData; - buffer->timestamps[buffer->head] = currentTime; - buffer->sum += newData; - buffer->count++; - buffer->isFull = (buffer->count == BUFFER_SIZE); + 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 { - // 缓冲区已满,替换最旧数据 - buffer->sum -= buffer->data[buffer->tail]; // 移除最旧数据 - buffer->tail = (buffer->tail + 1) % BUFFER_SIZE; // 移动尾指针 - buffer->head = (buffer->head + 1) % BUFFER_SIZE; // 移动头指针 - buffer->data[buffer->head] = newData; // 存储新数据 - buffer->timestamps[buffer->head] = currentTime; - buffer->sum += newData; // 添加新数据到总和 + Serial.println("✅ Boot按钮未按下,正常启动"); } - - // 计算并返回平均值 - if (buffer->count > 0) { - return buffer->sum / buffer->count; - } else { - return 0; +} + +// 清除所有存储的配置(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(); } } - -// 修改后的processRadarData函数 - 直接从串口读取数据 -void processRadarData() { - static unsigned long lastDataTime = 0; - static unsigned long noDataWarningTime = 0; - static uint32_t totalLinesReceived = 0; - static uint32_t validLinesReceived = 0; - static uint32_t phasePacketCounter = 0; - static uint32_t vitalPacketCounter = 0; - - // 直接从串口读取数据 - while (mySerial2.available() > 0) { - char c = mySerial2.read(); - lastDataTime = millis(); +// 配置清除指示灯控制任务 +void configClearLedTask(void *parameter) { + while (1) { + switch (currentConfigClearStatus) { + case CONFIG_NORMAL: // 正常运行 - LOW + analogWrite(CONFIG_CLEAR_PIN, 0); // 关闭LED + break; - rxBuffer += c; + case CONFIG_PREPARING: // 准备清除 - HIGH + analogWrite(CONFIG_CLEAR_PIN, 255); // 开启LED + break; - // 增加缓冲区大小检查,防止溢出 - if (rxBuffer.length() > 1000) { - rxBuffer = ""; + 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; - if (c == '\n' || c == '\r') { - if (rxBuffer.length() > 0) { - totalLinesReceived++; - - bool parseResult = parseSensorLine(rxBuffer); - if (parseResult) { - validLinesReceived++; - - // 更新全局传感器数据 - lastSensorUpdate = millis(); - - // 使用FreeRTOS队列发送数据到任务 - // 只有在检测到人的情况下才将数据放入队列 - if (sensorData.presence > 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) { - // Serial.println("📤 相位数据已加入发送队列"); - } 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.rssi = sensorData.rssi; - - if (xQueueSend(vitalDataQueue, &vitalData, 0) == pdTRUE) { - Serial.println("📤 生命体征数据已加入发送队列"); - } else { - Serial.println("❌ 生命体征数据队列已满,数据丢失"); - } - vitalPacketCounter = 0; - } - } + 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; // 重置计数器 } - rxBuffer = ""; + 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检查一次 } - - // 检查数据超时 - if (millis() - lastDataTime > 5000 && millis() - noDataWarningTime > 5000) { - Serial.println("⚠️ [警告] 超过5秒未收到串口数据!"); - Serial.printf(" 检查项: 1) 雷达是否通电 2) 串口连接(RX=%d,TX=%d) 3) 波特率=%d\n", - UART2_RX, UART2_TX, BAUD_RATE); - noDataWarningTime = millis(); - } -} +} \ No newline at end of file diff --git a/tcp-output(1).py b/tcp-output(1).py new file mode 100644 index 0000000..9bd9178 --- /dev/null +++ b/tcp-output(1).py @@ -0,0 +1,330 @@ +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/tcp-output(5).py b/tcp-output(5).py deleted file mode 100644 index 518f737..0000000 --- a/tcp-output(5).py +++ /dev/null @@ -1,243 +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 = 1002 - -# 分配的设备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: - # 创建数据点 - data_point = { - "measurement": "device_data", - "tags": { - "deviceId": assigned_device_id - }, - "time": datetime.utcnow().isoformat() + "Z", - "fields": {} - } - - # 根据协议ID确定字段名 - field_mapping = { - 1: "heartRate", - 2: "breathingRate", - 3: "heartPhase", - 4: "breathingPhase", - 13: "personDetected", - 14: "humanActivity" - } - - if protocol_id in field_mapping: - field_name = field_mapping[protocol_id] - - # 对特定字段进行数值处理 - if protocol_id in [1, 2]: # 心率和呼吸频率需要除以10 - data_point["fields"][field_name] = float(data_value) / 10.0 - elif protocol_id in [3, 4]: # 相位数据需要除以1000 - data_point["fields"][field_name] = float(data_value) / 1000.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]}") - else: - data_point["fields"][field_name] = data_value - - print(f"📊 准备保存数据: 协议ID={protocol_id}, 字段={field_name}, 值={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]: - # 对于人检/活动数据,使用整数格式 - line_protocol = f"device_data,deviceId={assigned_device_id} {field_name}={int(data_point['fields'][field_name])}i" - else: - # 对于其他数据,使用浮点数格式 - line_protocol = f"device_data,deviceId={assigned_device_id} {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 main(): - global assigned_device_id, phase_counter - - try: - # 第一步:注册设备获取设备ID - if not register_device(): - print("❌ 设备注册失败,程序退出") - return - - print(f"🎯 开始使用设备ID {assigned_device_id} 发送数据...") - - # 第二步:开始发送数据 - while True: - # 初始化数据值 - data_value = 0 - - # 每发送100个相位数据后发送其他数据 - if phase_counter < 2: - # 发送相位数据 (心率相位或呼吸相位) - protocol_id = random.choice([3, 4]) # 3=心率相位, 4=呼吸相位 - data_value = random.randint(-10000, 10000) - phase_counter += 1 - else: - # 重置计数器 - phase_counter = 0 - - # 发送其他数据 - protocol_id = random.choice([1, 2, 13, 14]) # 1=心跳, 2=呼吸, 13=检测到人, 14=人体活动 - - 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([0, 1]) - elif protocol_id == 14: # 人体活动(1活动,0静止) - data_value = random.choice([0, 1]) - - # 直接发送数据到InfluxDB - save_data_to_influxdb(protocol_id, data_value) - - # 设置发送间隔 - time.sleep(0.1) # 每100ms发送一次数据 - - 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/app.cpp b/test/app.cpp deleted file mode 100644 index df570ad..0000000 --- a/test/app.cpp +++ /dev/null @@ -1,1177 +0,0 @@ -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#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; // 当前数据总和 -}; - -CircularBuffer heartRateBuffer = {{0}, {0}, 0, 0, 0, false, 0}; - -// 滤波器定义 -#define FILTER_SIZE 5 // 滤波器窗口大小 - -Preferences preferences; - -// 设备ID变量 -uint16_t currentDeviceId = 1001; // 默认设备ID为1001 -const uint16_t MIN_DEVICE_ID = 1000; // 设备ID最小值 -const uint16_t MAX_DEVICE_ID = 1999; // 设备ID最大值 - -const uint32_t PHASE_SEND_INTERVAL = 1; -const uint32_t VITAL_SEND_INTERVAL = 10; - -#define SERVICE_UUID "4fafc201-1fb5-459e-8fcc-c5c9c331914b" -#define CHARACTERISTIC_UUID "beb5483e-36e1-4688-b7f5-ea07361b26a8" - -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] = "13-205"; -char password[64] = "12345678"; -bool wifiConfigured = false; - -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; - -typedef struct { - float breath_rate; - float heart_rate; - int rssi; - uint8_t breath_valid; - uint8_t heart_valid; - uint8_t presence; - uint8_t motion; - int heartbeat_waveform; - int breathing_waveform; - int raw_signal; -} SensorData; - -SensorData sensorData = {0}; - -// 呼吸率滤波相关变量定义 -// 添加滤波器数组和索引(仅用于呼吸率) -float breathRateFilter[FILTER_SIZE] = {0}; -int filterIndex = 0; -bool filterInitialized = false; - - -HardwareSerial mySerial2(2); -const int BAUD_RATE = 576000; -const int UART2_RX = 16; -const int UART2_TX = 17; -String rxBuffer = ""; - -// FreeRTOS任务和队列定义 -QueueHandle_t phaseDataQueue; -QueueHandle_t vitalDataQueue; -TaskHandle_t bleSendTaskHandle = NULL; -TaskHandle_t vitalSendTaskHandle = NULL; -// 注意:bleSendTaskHandle现在用于蓝牙发送任务 - -#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; - int rssi; -} VitalData; - -// 添加流量控制类 -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 = 1000; // 默认1秒发送一次 -BLEFlowController bleFlow(500); // 提高到500 B/s限制,进一步提高数据传输速率 - -void processBLEConfig(); -void saveWiFiConfig(); -void loadWiFiConfig(); -bool connectWiFi(); -void processRadarData(); -bool parseSensorLine(String line); -void sendRadarCommand(String cmd); -void updateStatusFlags(); -void loadDeviceId(); // 加载设备ID -void saveDeviceId(); // 保存设备ID -bool processSetDeviceId(JsonDocument& doc); // 处理设置设备ID命令 -void sendStatusToBLE(); // 发送状态信息到BLE -bool processQueryStatus(JsonDocument& doc); // 处理查询状态命令 -bool processQueryRadarData(JsonDocument& doc); // 处理查询雷达数据命令 -bool processStartContinuousSend(JsonDocument& doc); // 处理开始持续发送命令 -bool processStopContinuousSend(JsonDocument& doc); // 处理停止持续发送命令 -void sendRadarDataToBLE(); // 发送雷达数据到BLE -void sendDataInChunks(const String& data); // 分包发送函数 -float addDataAndCalculateAverage(float newData); // 计算平均值并添加新数据 - -// FreeRTOS任务函数声明 -void bleSendTask(void *parameter); -void vitalSendTask(void *parameter); - -class MyServerCallbacks: public BLEServerCallbacks { - void onConnect(BLEServer* pServer) { - deviceConnected = true; - // 注释掉这行以减少串口输出 - // Serial.println("BLE客户端已连接"); - - // 发送连接状态信息 - sendStatusToBLE(); - }; - - void onDisconnect(BLEServer* pServer) { - deviceConnected = false; - // 注释掉这行以减少串口输出 - // Serial.println("BLE客户端已断开"); - - // 重置持续发送状态 - continuousSendEnabled = false; - // 注释掉这行以减少串口输出 - // Serial.println("重置持续发送状态"); - } -}; - -class MyCallbacks: public BLECharacteristicCallbacks { - void onWrite(BLECharacteristic *pCharacteristic) { - std::string value = pCharacteristic->getValue(); - // 注释掉这行以减少串口输出 - // Serial.printf("🔵 收到BLE写入数据,长度: %d 字节\n", value.length()); - - if (value.length() > 0) { - String fragment = ""; - for (int i = 0; i < value.length(); i++) - fragment += value[i]; - - // 注释掉这行以减少串口输出 - // Serial.printf("📄 接收数据片段: %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("📥 完整JSON数据: %s\n", jsonData.c_str()); - - // 将JSON数据存储到receivedData变量中供后续处理 - receivedData = jsonData; - } - } - } -}; - -void setup() { - // 启用串口通信以便观察滤波后的数据 - Serial.begin(115200); - - sensorData.breath_rate = 0; - sensorData.heart_rate = 0; - sensorData.rssi = 0; - sensorData.breath_valid = 0; - sensorData.heart_valid = 0; - - esp_task_wdt_init(30, true); - esp_task_wdt_add(NULL); - - // 增加串口缓冲区大小到4096字节以处理大量雷达数据 - mySerial2.setRxBufferSize(4096); - mySerial2.begin(BAUD_RATE, SERIAL_8N1, UART2_RX, UART2_TX); - // 注释掉这行以减少串口输出 - // Serial.println("UART2配置完成,缓冲区大小: 4096字节"); - - delay(1000); - - String startCmd = "{\"cmd\":\"setMonitor\",\"para\":1}\n"; - mySerial2.print(startCmd); - delay(1000); - // 注释掉这行以减少串口输出 - // Serial.println("启动雷达数据传输"); - - preferences.begin("radar_data", false); - - // 加载设备ID和WiFi配置 - loadDeviceId(); - loadWiFiConfig(); - - // 创建FreeRTOS队列 - phaseDataQueue = xQueueCreate(QUEUE_SIZE, sizeof(PhaseData)); - vitalDataQueue = xQueueCreate(QUEUE_SIZE, sizeof(VitalData)); - // bleDataQueue = xQueueCreate(QUEUE_SIZE, sizeof(BLEData)); // 移除:不再需要创建蓝牙数据队列 - - // 注释掉这行以减少串口输出 - /* - if (phaseDataQueue == NULL || vitalDataQueue == NULL) { // 移除:不再检查蓝牙数据队列 - Serial.println("❌ 队列创建失败"); - } else { - Serial.println("✅ FreeRTOS队列创建成功"); - } - */ - - // 创建FreeRTOS任务 - xTaskCreatePinnedToCore( - bleSendTask, - "BleSendTask", - TASK_STACK_SIZE, - NULL, - TASK_PRIORITY, - &bleSendTaskHandle, - 1 - ); - - xTaskCreatePinnedToCore( - vitalSendTask, - "VitalSendTask", - TASK_STACK_SIZE, - NULL, - TASK_PRIORITY, - &vitalSendTaskHandle, - 1 - ); - - - // 注释掉这行以减少串口输出 - // Serial.println("✅ FreeRTOS任务创建成功"); - - BLEDevice::init("ESP32-Radar"); - 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("BLE已启动,设备名称: ESP32-Radar"); - - if (wifiConfigured) { - // 注释掉这行以减少串口输出 - // Serial.println("检测到已保存的WiFi配置,尝试连接..."); - if (connectWiFi()) { - // 注释掉这行以减少串口输出 - // Serial.println("WiFi连接成功!"); - } else { - // 注释掉这行以减少串口输出 - // Serial.println("WiFi连接失败,请通过BLE重新配置"); - wifiConfigured = false; - } - } -} - -void loop() { - esp_task_wdt_reset(); - - if (!deviceConnected && oldDeviceConnected) { - delay(500); - pServer->startAdvertising(); - // 注释掉这行以减少串口输出 - // Serial.println("开始BLE广播"); - oldDeviceConnected = deviceConnected; - } - if (deviceConnected && !oldDeviceConnected) { - oldDeviceConnected = deviceConnected; - } - - processBLEConfig(); - - // 移除WiFi连接检查,使雷达数据处理不依赖WiFi连接 - esp_task_wdt_reset(); - processRadarData(); - esp_task_wdt_reset(); - updateStatusFlags(); - - // 注意:持续发送雷达数据的逻辑已移至FreeRTOS任务处理 - // 主循环不再直接处理蓝牙数据发送,避免影响雷达数据接收和解析 - - delay(1); -} - -// 处理查询状态命令 -bool processQueryStatus(JsonDocument& doc) { - const char* command = doc["command"]; - if (command != nullptr && strcmp(command, "queryStatus") == 0) { - // 发送设备状态信息 - if (deviceConnected) { - String statusMsg = String("{\"type\":\"deviceStatus\",\"success\":true,\"deviceId\":") + - String(currentDeviceId) + - String(",\"wifiConfigured\":") + - String(wifiConfigured ? "true" : "false") + - String(",\"wifiConnected\":") + - String(WiFi.status() == WL_CONNECTED ? "true" : "false") + - String(",\"ipAddress\":\"") + - (WiFi.status() == WL_CONNECTED ? WiFi.localIP().toString() : "") + - String("\"}"); - - // 直接发送状态查询响应数据(不使用队列,因为这是即时响应) - pCharacteristic->setValue(statusMsg.c_str()); - pCharacteristic->notify(); - 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) + - 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(",\"rssi\":") + String(sensorData.rssi) + - String(",\"heartbeatWaveform\":") + String(sensorData.heartbeat_waveform) + - String(",\"breathingWaveform\":") + String(sensorData.breathing_waveform) + - String(",\"rawSignal\":") + String(sensorData.raw_signal) + - String("}"); - - // 直接发送查询响应数据(不使用队列,因为这是即时响应) - pCharacteristic->setValue(radarDataMsg.c_str()); - pCharacteristic->notify(); - 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.containsKey("interval")) { - continuousSendInterval = doc["interval"]; - // 限制最小间隔为100ms,最大间隔为10000ms - if (continuousSendInterval < 100) continuousSendInterval = 100; - if (continuousSendInterval > 10000) continuousSendInterval = 10000; - } - - continuousSendEnabled = true; - bleFlow.reset(); // 重置流量控制器 - - Serial.printf("⚙️ 启动持续发送模式,间隔: %lu ms\n", continuousSendInterval); - - // 发送清晰格式的确认消息 - if (deviceConnected) { - String confirmMsg = String("[START]Interval:") + String(continuousSendInterval); - - // 直接发送确认消息 - pCharacteristic->setValue(confirmMsg.c_str()); - pCharacteristic->notify(); - 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("🛑 停止持续发送模式"); - - // 发送清晰格式的确认消息 - if (deviceConnected) { - String confirmMsg = String("[STOP]ContinuousSend"); - - // 直接发送确认消息 - pCharacteristic->setValue(confirmMsg.c_str()); - pCharacteristic->notify(); - 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("📦 分包发送完成"); -} - -// 发送雷达数据到BLE(已移至FreeRTOS任务处理) -void sendRadarDataToBLE() { - // 此函数已废弃,雷达数据发送现在由FreeRTOS任务处理 - // 保留此函数以防其他代码引用 - Serial.println("ℹ️ 雷达数据发送已移至FreeRTOS任务处理"); -} - -// 处理BLE配置和命令 -void processBLEConfig() { - // 增强超时处理机制 - if (completeData.length() > 0 && (millis() - lastReceiveTime > 3000)) { - completeData = ""; - } - - if (receivedData.length() > 0) { - String bleData = receivedData; - receivedData = ""; - bleData.trim(); - - if (bleData.startsWith("{") && bleData.endsWith("}")) { - JsonDocument doc; - DeserializationError error = deserializeJson(doc, bleData); - - if (error) { - if (deviceConnected) { - String responseMsg = String("{\"type\":\"error\",\"message\":\"配置格式错误,请使用JSON格式\"}"); - - // 使用分包发送函数发送错误消息 - sendDataInChunks(responseMsg); - } - } else { - // 处理各种命令 - bool processed = false; - - // 处理设置设备ID命令 - if (!processed) processed = processSetDeviceId(doc); - - // 处理查询状态命令 - if (!processed) processed = processQueryStatus(doc); - - // 处理开始持续发送命令 - if (!processed) processed = processStartContinuousSend(doc); - - // 处理停止持续发送命令 - if (!processed) processed = processStopContinuousSend(doc); - - // 如果没有处理任何命令,发送错误响应 - if (!processed) { - if (deviceConnected) { - String responseMsg = String("{\"type\":\"error\",\"message\":\"未知命令\"}"); - - // 使用分包发送函数发送错误消息 - sendDataInChunks(responseMsg); - } - } - } - } else { - } - } -} - -void processRadarData() { - static unsigned long lastDataTime = 0; - static unsigned long noDataWarningTime = 0; - static uint32_t totalLinesReceived = 0; - static uint32_t validLinesReceived = 0; - static uint32_t phasePacketCounter = 0; - static uint32_t vitalPacketCounter = 0; - static bool processingData = false; - - if (processingData) { - return; - } - - int available = mySerial2.available(); - if (available > 0) { - processingData = true; - lastDataTime = millis(); - - bool lineProcessed = false; - while (mySerial2.available() > 0 && !lineProcessed) { - char c = mySerial2.read(); - rxBuffer += c; - - // 增加缓冲区大小检查,防止溢出 - if (rxBuffer.length() > 1000) { - rxBuffer = ""; - } - - if (c == '\n' || c == '\r') { - if (rxBuffer.length() > 0) { - totalLinesReceived++; - bool parseResult = parseSensorLine(rxBuffer); - if (parseResult) { - validLinesReceived++; - - // 更新全局传感器数据 - lastSensorUpdate = millis(); - - // 使用FreeRTOS队列发送数据到任务 - 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 { - } - 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.rssi = sensorData.rssi; - - if (xQueueSend(vitalDataQueue, &vitalData, 0) == pdTRUE) { - } else { - } - vitalPacketCounter = 0; - } - } else { - } - - rxBuffer = ""; - lineProcessed = true; - } - } - - esp_task_wdt_reset(); - } - - if (!lineProcessed && available > 0) { - } - - processingData = false; - } else { - if (millis() - lastDataTime > 5000 && millis() - noDataWarningTime > 5000) { - noDataWarningTime = millis(); - } - } -} - - -// FreeRTOS任务:蓝牙数据发送(精简格式以节省空间) -void bleSendTask(void *parameter) { - while (1) { - PhaseData phaseData; - - // 从队列接收相位数据用于蓝牙发送 - if (xQueueReceive(phaseDataQueue, &phaseData, portMAX_DELAY) == pdTRUE) { - esp_task_wdt_reset(); - - // 蓝牙传输 - 构造并发送完整的雷达数据(包含所有7个字段,无标识符以节省空间) - if (deviceConnected && continuousSendEnabled) { - // 构造完整雷达数据格式(去掉"radar"标识以节省空间) - // 格式: "heartRate|breathRate|heartbeatWaveform|breathingWaveform|presence|motion|rssi|checksum" - String radarDataCore = String(sensorData.heart_rate, 1) + String("|") + - String(sensorData.breath_rate, 1) + String("|") + - String(phaseData.heartbeat_waveform) + String("|") + - String(phaseData.breathing_waveform) + String("|") + - String(sensorData.presence) + String("|") + - String(sensorData.motion) + String("|") + - String(sensorData.rssi); - - // 计算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("📤 通过蓝牙发送完整雷达数据: %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("✅ 完整雷达数据蓝牙发送成功"); - } else { - // 数据较长,使用分包发送 - // 注释掉这行以减少串口输出 - // Serial.println("🔄 雷达数据较长,使用分包发送"); - sendDataInChunks(radarDataMsg); - } - } - - esp_task_wdt_reset(); - } - - vTaskDelay(1 / portTICK_PERIOD_MS); // 减少延迟以提高实时性 - } -} - -// FreeRTOS任务:发送生命体征数据 -void vitalSendTask(void *parameter) { - while (1) { - VitalData vitalData; - - // 从队列接收生命体征数据用于WiFi数据库发送 - if (xQueueReceive(vitalDataQueue, &vitalData, portMAX_DELAY) == pdTRUE) { - esp_task_wdt_reset(); - - // WiFi数据库传输 - 一直发送生命体征数据(不通过蓝牙) - if (WiFi.status() == WL_CONNECTED) { - HTTPClient http; - http.setTimeout(2000); - - String url = String("http://") + String(influxDBHost) + ":" + String(influxDBPort) + "/api/v2/write?org=" + String(influxDBOrg) + "&bucket=" + String(influxDBBucket) + "&precision=ns"; - - http.begin(url); - http.addHeader("Authorization", String("Token ") + String(influxDBToken)); - http.addHeader("Content-Type", "text/plain"); - http.setReuse(true); - - // 使用可变的设备ID - String deviceId = String(currentDeviceId); - - String vitalDataStr = ""; - bool firstField = true; - - // 心率数据 - 使用浮点数格式 - if (vitalData.heart_rate > 0) { - if (!firstField) vitalDataStr += ","; - vitalDataStr += "heartRate="; - vitalDataStr += String(vitalData.heart_rate / 1.0, 1); - firstField = false; - } - - // 呼吸率数据 - 使用浮点数格式 - if (vitalData.breath_rate > 0) { - if (!firstField) vitalDataStr += ","; - vitalDataStr += "breathingRate="; - vitalDataStr += String(vitalData.breath_rate / 1.0, 1); - firstField = false; - } - - // 人检数据 - 确保值为0或1,使用整数格式 - if (true) { // 总是发送人检数据 - if (!firstField) vitalDataStr += ","; - vitalDataStr += "personDetected="; - // 确保值为0或1 - int presenceValue = (vitalData.presence > 0) ? 1 : 0; - vitalDataStr += String(presenceValue) + "i"; // 整数格式 - firstField = false; - } - - // 人体活动数据 - 确保值为0或1,使用整数格式 - if (true) { // 总是发送人体活动数据 - if (!firstField) vitalDataStr += ","; - vitalDataStr += "humanActivity="; - // 确保值为0或1 - int motionValue = (vitalData.motion > 0) ? 1 : 0; - vitalDataStr += String(motionValue) + "i"; // 整数格式 - firstField = false; - } - - // RSSI数据 - 使用整数格式 - if (vitalData.rssi != 0) { - if (!firstField) vitalDataStr += ","; - vitalDataStr += "rssi="; - vitalDataStr += String(vitalData.rssi) + "i"; // 整数格式 - firstField = false; - } - - // 如果有数据要发送,则构造完整的行协议字符串 - if (vitalDataStr.length() > 0) { - vitalDataStr = "device_data,deviceId=" + deviceId + " " + vitalDataStr; - //Serial.println("📤 发送生命体征数据到数据库: " + vitalDataStr); - - int httpResponseCode = http.POST(vitalDataStr); - - if (httpResponseCode == 204) { - //Serial.println("✅ 生命体征数据发送到数据库成功"); - } else { - String errorMsg = String("❌ 生命体征数据发送到数据库失败: ") + String(httpResponseCode); - Serial.println(errorMsg); - } - } - - http.end(); - } else { - Serial.println("❌ WiFi未连接,无法发送生命体征数据到数据库"); - } - - esp_task_wdt_reset(); - } - - vTaskDelay(5 / portTICK_PERIOD_MS); - } -} - -void updateStatusFlags() { - static unsigned long lastStatusUpdate = 0; -} - -bool parseSensorLine(String line) { - if (!line.startsWith("HBR01")) { - // 注释掉这行以减少串口输出 - // Serial.println("❌ 数据行不以HBR01开头"); - return false; - } - - int colonIndex = line.indexOf(':'); - if (colonIndex == -1) { - // 注释掉这行以减少串口输出 - // Serial.println("❌ 数据行缺少冒号分隔符"); - return false; - } - - String dataPart = line.substring(colonIndex + 1); - dataPart.trim(); - - // 注释掉这行以减少串口输出 - // Serial.printf("🔍 解析数据部分: %s\n", dataPart.c_str()); - - int values[8]; - int startIndex = 0; - int commaIndex; - int fieldCount = 0; - - for (int i = 0; i < 8; i++) { - commaIndex = dataPart.indexOf(',', startIndex); - - if (commaIndex == -1 && i == 7) { - String valueStr = dataPart.substring(startIndex); - valueStr.trim(); - values[i] = valueStr.toInt(); - fieldCount++; - // 注释掉这行以减少串口输出 - // Serial.printf(" 字段%d: %s -> %d\n", i+1, valueStr.c_str(), values[i]); - } else if (commaIndex != -1) { - String valueStr = dataPart.substring(startIndex, commaIndex); - valueStr.trim(); - values[i] = valueStr.toInt(); - startIndex = commaIndex + 1; - fieldCount++; - // 注释掉这行以减少串口输出 - // Serial.printf(" 字段%d: %s -> %d\n", i+1, valueStr.c_str(), values[i]); - } else { - // 注释掉这行以减少串口输出 - // Serial.printf("❌ 解析错误: 字段%d缺失,总共找到%d个字段\n", i+1, fieldCount); - return false; - } - } - - sensorData.presence = values[0]; - sensorData.heart_rate = (float)values[1]; - sensorData.breath_rate = (float)values[2]; - sensorData.motion = values[3]; - sensorData.rssi = values[4]; - sensorData.heartbeat_waveform = values[5]; - sensorData.breathing_waveform = values[6]; - sensorData.raw_signal = values[7]; - - 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.presence > 0) { - sensorData.heart_rate = addDataAndCalculateAverage(sensorData.heart_rate); - } - - // 只有在检测到人的情况下才进行呼吸率滤波处理 - if (sensorData.presence > 0) { - // 对呼吸率继续使用原有的移动平均滤波器 - breathRateFilter[filterIndex] = sensorData.breath_rate; - - // 计算滤波后的呼吸率值(移动平均) - float filteredBreathRate = 0; - int count = filterInitialized ? FILTER_SIZE : (filterIndex + 1); - - for (int i = 0; i < count; i++) { - filteredBreathRate += breathRateFilter[i]; - } - - filteredBreathRate /= count; - - // 更新滤波器索引 - filterIndex = (filterIndex + 1) % FILTER_SIZE; - if (filterIndex == 0) { - filterInitialized = true; - } - - // 使用滤波后的呼吸率值 - sensorData.breath_rate = filteredBreathRate; - } - - lastSensorUpdate = millis(); - - // 按照指定格式输出心率和呼吸率的原始数据 - Serial.printf("Heart_Rate_RAW:%.2f\n", sensorData.heart_rate); - Serial.printf("Breath_Rate_RAW:%.2f\n", sensorData.breath_rate); - - /* - // 注释掉原来的详细输出 - Serial.printf("✅ 解析成功: 有人=%d, 心率=%.1f, 呼吸=%.1f, 运动=%d, 波形=%d/%d\n", - sensorData.presence, sensorData.heart_rate, - sensorData.breath_rate, sensorData.motion, - sensorData.heartbeat_waveform, sensorData.breathing_waveform); - */ - - return true; -} - -void saveWiFiConfig() { - preferences.putString("ssid", ssid); - preferences.putString("password", password); - preferences.putBool("configured", true); - Serial.println("WiFi配置已保存到Flash"); -} - -void loadWiFiConfig() { - wifiConfigured = preferences.getBool("configured", false); - if (wifiConfigured) { - preferences.getString("ssid", ssid, sizeof(ssid)); - preferences.getString("password", password, sizeof(password)); - Serial.printf("从Flash加载WiFi配置 - SSID: %s\n", ssid); - } -} - -bool connectWiFi() { - WiFi.mode(WIFI_STA); - WiFi.begin(ssid, password); - - Serial.println("[WiFi] 正在尝试连接..."); - int attempts = 0; - while (WiFi.status() != WL_CONNECTED && attempts < 20) { - delay(500); - Serial.printf("[WiFi] 尝试连接中,当前状态: %d\n", WiFi.status()); - attempts++; - } - - if (WiFi.status() == WL_CONNECTED) { - Serial.println("[WiFi] 连接成功!"); - Serial.printf("[WiFi] 分配的IP地址: %s\n", WiFi.localIP().toString().c_str()); - return true; - } else { - Serial.println("[WiFi] 连接超时,未能成功连接到WiFi。"); - Serial.printf("[WiFi] 最终状态码: %d\n", WiFi.status()); - return false; - } -} - -void sendRadarCommand(String cmd) { - if (!cmd.endsWith("\n")) { - cmd += "\n"; - } - mySerial2.print(cmd); - Serial.println("📤 [雷达命令] " + cmd); -} - -// 处理设置设备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) + "\"}"; - - // 直接发送错误响应数据 - pCharacteristic->setValue(errorMsg.c_str()); - pCharacteristic->notify(); - } - 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) + "}"; - - // 直接发送设置设备ID响应数据 - pCharacteristic->setValue(confirmMsg.c_str()); - pCharacteristic->notify(); - - // 发送更新后的状态信息 - sendStatusToBLE(); - } - return true; - } - return false; -} - -// 加载设备ID -void loadDeviceId() { - currentDeviceId = preferences.getUShort("deviceId", 1001); - Serial.printf("从Flash加载设备ID: %u\n", currentDeviceId); -} - -// 保存设备ID -void saveDeviceId() { - preferences.putUShort("deviceId", currentDeviceId); - 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) + "}"; - - // 直接发送状态信息数据 - pCharacteristic->setValue(statusMsg.c_str()); - pCharacteristic->notify(); - Serial.println("已发送连接状态信息"); - } -} - - -float addDataAndCalculateAverage(float newData) { - // 只有在检测到人的情况下才进行滤波处理 - if (sensorData.presence == 0) { - return newData; // 没有检测到人时,直接返回原始数据 - } - - unsigned long currentTime = millis(); - unsigned long oneMinuteAgo = currentTime - 60000; // 一分钟前的时间 - - // 移除过期的旧数据(一分钟前的数据) - while (heartRateBuffer.count > 0 && heartRateBuffer.timestamps[heartRateBuffer.tail] < oneMinuteAgo) { - heartRateBuffer.sum -= heartRateBuffer.data[heartRateBuffer.tail]; - heartRateBuffer.tail = (heartRateBuffer.tail + 1) % BUFFER_SIZE; - heartRateBuffer.count--; - heartRateBuffer.isFull = (heartRateBuffer.count == BUFFER_SIZE); - } - - // 添加新数据 - if (heartRateBuffer.count < BUFFER_SIZE) { - // 缓冲区未满,直接添加 - heartRateBuffer.head = (heartRateBuffer.head + 1) % BUFFER_SIZE; - if (heartRateBuffer.count == 0) heartRateBuffer.tail = heartRateBuffer.head; - heartRateBuffer.data[heartRateBuffer.head] = newData; - heartRateBuffer.timestamps[heartRateBuffer.head] = currentTime; - heartRateBuffer.sum += newData; - heartRateBuffer.count++; - heartRateBuffer.isFull = (heartRateBuffer.count == BUFFER_SIZE); - } else { - // 缓冲区已满,替换最旧数据 - heartRateBuffer.sum -= heartRateBuffer.data[heartRateBuffer.tail]; // 移除最旧数据 - heartRateBuffer.tail = (heartRateBuffer.tail + 1) % BUFFER_SIZE; // 移动尾指针 - heartRateBuffer.head = (heartRateBuffer.head + 1) % BUFFER_SIZE; // 移动头指针 - heartRateBuffer.data[heartRateBuffer.head] = newData; // 存储新数据 - heartRateBuffer.timestamps[heartRateBuffer.head] = currentTime; - heartRateBuffer.sum += newData; // 添加新数据到总和 - } - - // 计算并返回平均值 - if (heartRateBuffer.count > 0) { - return heartRateBuffer.sum / heartRateBuffer.count; - } else { - return 0; - } -}