1722 lines
66 KiB
C++
1722 lines
66 KiB
C++
|
|
#include "radar_manager.h"
|
|||
|
|
#include "wifi_manager.h"
|
|||
|
|
#include <Preferences.h>
|
|||
|
|
#include <esp_task_wdt.h>
|
|||
|
|
|
|||
|
|
extern uint16_t currentDeviceId; // 当前设备ID
|
|||
|
|
extern Preferences preferences; // Flash存储对象
|
|||
|
|
extern WiFiManager wifiManager; // WiFi管理器对象
|
|||
|
|
|
|||
|
|
const int BAUD_RATE = 115200; // 串口波特率
|
|||
|
|
const int UART1_RX = 3; // UART1接收引脚
|
|||
|
|
const int UART1_TX = 2; // UART1发送引脚
|
|||
|
|
|
|||
|
|
const uint32_t PHASE_SEND_INTERVAL = 1; // 相位数据发送间隔(毫秒)
|
|||
|
|
const uint32_t VITAL_SEND_INTERVAL = 10; // 生命体征数据发送间隔(毫秒)
|
|||
|
|
|
|||
|
|
const unsigned long SENSOR_TIMEOUT = 40000; // 传感器超时时间(毫秒)
|
|||
|
|
static uint32_t packetCounter = 0; // 数据包计数器
|
|||
|
|
static bool shouldSendOtherData = false; // 是否发送其他数据标志
|
|||
|
|
|
|||
|
|
unsigned long lastSleepDataTime = 0; // 上次发送睡眠数据时间
|
|||
|
|
const unsigned long SLEEP_DATA_INTERVAL = 5000; // 睡眠数据发送间隔(毫秒)
|
|||
|
|
|
|||
|
|
const char* influxDBHost = "8.134.11.76"; // InfluxDB服务器地址
|
|||
|
|
const int influxDBPort = 8086; // InfluxDB服务器端口
|
|||
|
|
const char* influxDBToken = "KuTa5ZsqoHIhi2IglOO06zExUYw1_mJ6K0mcA9X1y6O6CJDog3_Cgr8mUw1SwpuCCKRElqxa6wAhrrhsYPytkg=="; // InfluxDB访问令牌
|
|||
|
|
const char* influxDBOrg = "gzlg"; // InfluxDB组织名称
|
|||
|
|
const char* influxDBBucket = "gzlg"; // InfluxDB存储桶名称
|
|||
|
|
|
|||
|
|
uint8_t presence_Bit = 1; // 存在标志位
|
|||
|
|
|
|||
|
|
SensorData sensorData; // 传感器数据结构体
|
|||
|
|
HardwareSerial mySerial1(1); // 硬件串口1对象
|
|||
|
|
|
|||
|
|
QueueHandle_t phaseDataQueue; // 相位数据队列句柄
|
|||
|
|
QueueHandle_t vitalDataQueue; // 生命体征数据队列句柄
|
|||
|
|
QueueHandle_t uartQueue; // UART数据队列句柄
|
|||
|
|
TaskHandle_t bleSendTaskHandle = NULL; // BLE发送任务句柄
|
|||
|
|
TaskHandle_t vitalSendTaskHandle = NULL; // 生命体征发送任务句柄
|
|||
|
|
TaskHandle_t uartProcessTaskHandle = NULL; // UART处理任务句柄
|
|||
|
|
|
|||
|
|
BLEServer* pServer = NULL; // BLE服务器指针
|
|||
|
|
BLECharacteristic* pCharacteristic = NULL; // BLE特征值指针
|
|||
|
|
|
|||
|
|
bool deviceConnected = false; // 设备连接状态
|
|||
|
|
bool oldDeviceConnected = false; // 旧设备连接状态
|
|||
|
|
String receivedData = ""; // 接收到的数据
|
|||
|
|
String completeData = ""; // 完整数据
|
|||
|
|
unsigned long lastReceiveTime = 0; // 上次接收数据时间
|
|||
|
|
|
|||
|
|
bool continuousSendEnabled = false; // 持续发送使能标志
|
|||
|
|
unsigned long continuousSendInterval = 500; // 持续发送间隔(毫秒)
|
|||
|
|
BLEFlowController bleFlow(500); // BLE流控制器对象
|
|||
|
|
|
|||
|
|
unsigned long lastSensorUpdate = 0; // 上次传感器更新时间
|
|||
|
|
LastSentData lastSentData = {0}; // 上次发送的数据,初始化为0
|
|||
|
|
unsigned long lastCheckTime = 0; // 上次检测时间,初始化为0
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* @brief BLE流控制器构造函数
|
|||
|
|
* 初始化BLE数据流控制参数,限制数据发送速率
|
|||
|
|
* @param maxBps 最大每秒发送字节数
|
|||
|
|
*/
|
|||
|
|
BLEFlowController::BLEFlowController(size_t maxBps) : maxBytesPerSecond(maxBps), bytesSent(0) {
|
|||
|
|
lastResetTime = millis();
|
|||
|
|
lastSendTime = 0;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* @brief 检查是否可以发送数据
|
|||
|
|
* 检查当前是否满足发送条件,包括速率限制和时间间隔
|
|||
|
|
* @param dataSize 要发送的数据大小
|
|||
|
|
* @return 是否可以发送数据
|
|||
|
|
*/
|
|||
|
|
bool BLEFlowController::canSend(size_t dataSize) {
|
|||
|
|
unsigned long currentTime = millis();
|
|||
|
|
|
|||
|
|
if(currentTime - lastResetTime >= 1000) {
|
|||
|
|
bytesSent = 0;
|
|||
|
|
lastResetTime = currentTime;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if((bytesSent + dataSize) > maxBytesPerSecond) {
|
|||
|
|
return false;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if(currentTime - lastSendTime < 5) {
|
|||
|
|
return false;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
return true;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* @brief 检查发送时间间隔
|
|||
|
|
* 检查距离上次发送是否满足最小时间间隔
|
|||
|
|
* @return 是否满足发送条件
|
|||
|
|
*/
|
|||
|
|
bool BLEFlowController::check() {
|
|||
|
|
unsigned long currentTime = millis();
|
|||
|
|
if(currentTime - lastSendTime < 5) {
|
|||
|
|
return false;
|
|||
|
|
}
|
|||
|
|
return true;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* @brief 记录数据发送
|
|||
|
|
* 更新已发送字节数和最后发送时间
|
|||
|
|
* @param dataSize 已发送的数据大小
|
|||
|
|
*/
|
|||
|
|
void BLEFlowController::recordSend(size_t dataSize) {
|
|||
|
|
bytesSent += dataSize;
|
|||
|
|
lastSendTime = millis();
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* @brief 重置流控制器
|
|||
|
|
* 重置已发送字节数和时间戳
|
|||
|
|
*/
|
|||
|
|
void BLEFlowController::reset() {
|
|||
|
|
bytesSent = 0;
|
|||
|
|
lastResetTime = millis();
|
|||
|
|
lastSendTime = 0;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* @brief BLE服务器连接回调
|
|||
|
|
* 当客户端连接时触发
|
|||
|
|
* @param pServer BLE服务器指针
|
|||
|
|
*/
|
|||
|
|
void MyServerCallbacks::onConnect(BLEServer* pServer) {
|
|||
|
|
deviceConnected = true;
|
|||
|
|
Serial.println("✅ [BLE] 客户端已连接");
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* @brief BLE服务器断开连接回调
|
|||
|
|
* 当客户端断开连接时触发
|
|||
|
|
* @param pServer BLE服务器指针
|
|||
|
|
*/
|
|||
|
|
void MyServerCallbacks::onDisconnect(BLEServer* pServer) {
|
|||
|
|
deviceConnected = false;
|
|||
|
|
Serial.println("🔴 [BLE] 客户端已断开");
|
|||
|
|
continuousSendEnabled = false;
|
|||
|
|
Serial.println("🔄 重置持续发送状态");
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* @brief BLE特征值写入回调
|
|||
|
|
* 当客户端写入数据时触发
|
|||
|
|
* @param pCharacteristic BLE特征值指针
|
|||
|
|
*/
|
|||
|
|
void MyCallbacks::onWrite(BLECharacteristic *pCharacteristic) {
|
|||
|
|
std::string value = pCharacteristic->getValue();
|
|||
|
|
Serial.printf("🔵 [BLE] 收到写入数据,长度: %d 字节\n", value.length());
|
|||
|
|
|
|||
|
|
if (value.length() > 0) {
|
|||
|
|
String fragment = "";
|
|||
|
|
for (int i = 0; i < value.length(); i++)
|
|||
|
|
fragment += value[i];
|
|||
|
|
|
|||
|
|
Serial.printf("📄 [BLE] 接收数据片段: %s\n", fragment.c_str());
|
|||
|
|
|
|||
|
|
completeData += fragment;
|
|||
|
|
lastReceiveTime = millis();
|
|||
|
|
|
|||
|
|
int openBrace = completeData.indexOf('{');
|
|||
|
|
|
|||
|
|
if (openBrace >= 0) {
|
|||
|
|
int depth = 0;
|
|||
|
|
int closeBrace = -1;
|
|||
|
|
|
|||
|
|
for (int i = openBrace; i < completeData.length(); i++) {
|
|||
|
|
char c = completeData.charAt(i);
|
|||
|
|
if (c == '{') {
|
|||
|
|
depth++;
|
|||
|
|
} else if (c == '}') {
|
|||
|
|
depth--;
|
|||
|
|
if (depth == 0) {
|
|||
|
|
closeBrace = i;
|
|||
|
|
break;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if (closeBrace > openBrace) {
|
|||
|
|
String jsonData = completeData.substring(openBrace, closeBrace + 1);
|
|||
|
|
completeData = completeData.substring(closeBrace + 1);
|
|||
|
|
|
|||
|
|
Serial.printf("📥 [BLE] 完整JSON数据: %s\n", jsonData.c_str());
|
|||
|
|
receivedData = jsonData;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* @brief 初始化雷达管理器
|
|||
|
|
* 初始化串口、队列和FreeRTOS任务
|
|||
|
|
*/
|
|||
|
|
void initRadarManager() {
|
|||
|
|
Serial.println("🔧 初始化雷达管理器...");
|
|||
|
|
mySerial1.setRxBufferSize(UART_RX_BUFFER_SIZE);
|
|||
|
|
mySerial1.begin(BAUD_RATE, SERIAL_8N1, UART1_RX, UART1_TX);
|
|||
|
|
Serial.println("UART1配置完成,缓冲区大小: 4096字节");
|
|||
|
|
|
|||
|
|
phaseDataQueue = xQueueCreate(QUEUE_SIZE, sizeof(PhaseData));
|
|||
|
|
vitalDataQueue = xQueueCreate(QUEUE_SIZE, sizeof(VitalData));
|
|||
|
|
uartQueue = xQueueCreate(2048, sizeof(char));
|
|||
|
|
|
|||
|
|
if (phaseDataQueue == NULL || vitalDataQueue == NULL || uartQueue == NULL) {
|
|||
|
|
Serial.println("❌ 队列创建失败");
|
|||
|
|
} else {
|
|||
|
|
Serial.println("✅ FreeRTOS队列创建成功");
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
mySerial1.onReceive(serialRxCallback);
|
|||
|
|
Serial.println("✅ 串口中断回调已设置");
|
|||
|
|
|
|||
|
|
xTaskCreatePinnedToCore(
|
|||
|
|
bleSendTask,
|
|||
|
|
"BleSendTask",
|
|||
|
|
TASK_STACK_SIZE,
|
|||
|
|
NULL,
|
|||
|
|
3,
|
|||
|
|
&bleSendTaskHandle,
|
|||
|
|
1
|
|||
|
|
);
|
|||
|
|
|
|||
|
|
xTaskCreatePinnedToCore(
|
|||
|
|
vitalSendTask,
|
|||
|
|
"VitalSendTask",
|
|||
|
|
TASK_STACK_SIZE,
|
|||
|
|
NULL,
|
|||
|
|
2,
|
|||
|
|
&vitalSendTaskHandle,
|
|||
|
|
1
|
|||
|
|
);
|
|||
|
|
|
|||
|
|
xTaskCreatePinnedToCore(
|
|||
|
|
radarDataTask,
|
|||
|
|
"RadarProcessTask",
|
|||
|
|
4096,
|
|||
|
|
NULL,
|
|||
|
|
4,
|
|||
|
|
NULL,
|
|||
|
|
1
|
|||
|
|
);
|
|||
|
|
|
|||
|
|
xTaskCreatePinnedToCore(
|
|||
|
|
uartProcessTask,
|
|||
|
|
"UartProcessTask",
|
|||
|
|
4096,
|
|||
|
|
NULL,
|
|||
|
|
5,
|
|||
|
|
&uartProcessTaskHandle,
|
|||
|
|
1
|
|||
|
|
);
|
|||
|
|
|
|||
|
|
Serial.println("✅ 雷达管理器初始化完成");
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* @brief 初始化R60ABD1雷达模组
|
|||
|
|
* 发送初始化命令激活雷达数据上报功能
|
|||
|
|
*/
|
|||
|
|
void initR60ABD1() {
|
|||
|
|
Serial.println("🔧 初始化R60ABD1雷达模组...");
|
|||
|
|
|
|||
|
|
Serial.println("📡 发送查询指令以激活数据上报...");
|
|||
|
|
uint8_t queryPresenceCmd[] = {0x53, 0x59, 0x80, 0x81, 0x00, 0x01, 0x00, 0x7D, 0x54, 0x43};
|
|||
|
|
mySerial1.write(queryPresenceCmd, sizeof(queryPresenceCmd));
|
|||
|
|
|
|||
|
|
Serial.println("📡 开启核心监测功能...");
|
|||
|
|
|
|||
|
|
sendRadarCommand(0x80, 0x00, 0x01);
|
|||
|
|
delay(50);
|
|||
|
|
sendRadarCommand(0x81, 0x00, 0x01);
|
|||
|
|
delay(50);
|
|||
|
|
sendRadarCommand(0x85, 0x00, 0x01);
|
|||
|
|
delay(50);
|
|||
|
|
sendRadarCommand(0x84, 0x00, 0x01);
|
|||
|
|
delay(50);
|
|||
|
|
|
|||
|
|
Serial.println("📡 尝试开启波形数据...");
|
|||
|
|
sendRadarCommand(0x81, 0x0C, 0x01);
|
|||
|
|
delay(50);
|
|||
|
|
sendRadarCommand(0x85, 0x0A, 0x01);
|
|||
|
|
delay(50);
|
|||
|
|
|
|||
|
|
sendRadarCommand(0x84, 0x13, 0x01);
|
|||
|
|
delay(50);
|
|||
|
|
sendRadarCommand(0x84, 0x14, 0x01);
|
|||
|
|
delay(50);
|
|||
|
|
|
|||
|
|
Serial.println("🔍 查询当前状态...");
|
|||
|
|
sendRadarCommand(0x80, 0x80, 0x0F);
|
|||
|
|
delay(50);
|
|||
|
|
sendRadarCommand(0x81, 0x80, 0x0F);
|
|||
|
|
delay(50);
|
|||
|
|
sendRadarCommand(0x85, 0x80, 0x0F);
|
|||
|
|
delay(50);
|
|||
|
|
sendRadarCommand(0x84, 0x80, 0x0F);
|
|||
|
|
|
|||
|
|
Serial.println("✅ R60ABD1雷达初始化完成");
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* @brief 解析R60ABD1雷达数据帧
|
|||
|
|
* 解析雷达返回的数据帧并提取传感器数据
|
|||
|
|
* @param frame 数据帧指针
|
|||
|
|
* @param frameLen 数据帧长度
|
|||
|
|
* @return 是否解析成功
|
|||
|
|
*/
|
|||
|
|
bool parseR60ABD1Frame(uint8_t *frame, uint16_t frameLen) {
|
|||
|
|
if(frameLen < 8) return false;
|
|||
|
|
|
|||
|
|
if(frame[0] != FRAME_HEADER1 || frame[1] != FRAME_HEADER2 ||
|
|||
|
|
frame[frameLen-2] != FRAME_TAIL1 || frame[frameLen-1] != FRAME_TAIL2) {
|
|||
|
|
return false;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
uint8_t checksum = 0;
|
|||
|
|
for(int i = 0; i < frameLen-3; i++) {
|
|||
|
|
checksum += frame[i];
|
|||
|
|
if(i % 50 == 0) {
|
|||
|
|
esp_task_wdt_reset();
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
if(checksum != frame[frameLen-3]) {
|
|||
|
|
Serial.println("❌ R60ABD1帧校验和错误");
|
|||
|
|
return false;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
for(int i = 0; i < frameLen && i < 20; i++) {
|
|||
|
|
Serial.printf("%02X ", frame[i]);
|
|||
|
|
}
|
|||
|
|
if(frameLen > 20) Serial.print("... ");
|
|||
|
|
Serial.printf("| 校验:0x%02X\n", frame[frameLen-3]);
|
|||
|
|
|
|||
|
|
uint8_t ctrlByte = frame[2];
|
|||
|
|
uint8_t cmdByte = frame[3];
|
|||
|
|
uint16_t dataLen = (frame[4] << 8) | frame[5];
|
|||
|
|
|
|||
|
|
switch(ctrlByte) {
|
|||
|
|
case CTRL_PRESENCE:
|
|||
|
|
switch(cmdByte) {
|
|||
|
|
case 0x00:
|
|||
|
|
case 0x80:
|
|||
|
|
if(dataLen >= 1) {
|
|||
|
|
if(frame[6] == 0x01) {
|
|||
|
|
Serial.println("🔄 人体存在监测功能已开启");
|
|||
|
|
sendRadarCommand(0x84, 0x00, 0x01);
|
|||
|
|
} else {
|
|||
|
|
Serial.println("🔄 人体存在监测功能已关闭");
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
break;
|
|||
|
|
|
|||
|
|
case 0x01:
|
|||
|
|
if(dataLen >= 1) {
|
|||
|
|
sensorData.presence = frame[6];
|
|||
|
|
Serial.printf("👤 人体存在: %s\n", sensorData.presence ? "有人" : "无人");
|
|||
|
|
}
|
|||
|
|
break;
|
|||
|
|
|
|||
|
|
case 0x02:
|
|||
|
|
if(dataLen >= 1) {
|
|||
|
|
sensorData.motion = frame[6];
|
|||
|
|
const char* states[] = {"无", "静止", "活跃"};
|
|||
|
|
Serial.printf("🏃 运动状态: %s\n", states[sensorData.motion]);
|
|||
|
|
}
|
|||
|
|
break;
|
|||
|
|
|
|||
|
|
case 0x03:
|
|||
|
|
if(dataLen >= 1) {
|
|||
|
|
sensorData.body_movement = frame[6];
|
|||
|
|
Serial.printf("📊体动参数: %d\n", sensorData.body_movement);
|
|||
|
|
}
|
|||
|
|
break;
|
|||
|
|
|
|||
|
|
case 0x04:
|
|||
|
|
if(dataLen >= 2) {
|
|||
|
|
sensorData.distance = ((uint16_t)frame[6] << 8) | frame[7];
|
|||
|
|
Serial.printf("📏人体距离: %d cm\n", sensorData.distance);
|
|||
|
|
}
|
|||
|
|
break;
|
|||
|
|
|
|||
|
|
case 0x05:
|
|||
|
|
if(dataLen >= 6) {
|
|||
|
|
uint16_t x_raw = ((uint16_t)frame[6] << 8) | frame[7];
|
|||
|
|
sensorData.pos_x = parseSignedCoordinate(x_raw);
|
|||
|
|
|
|||
|
|
uint16_t y_raw = ((uint16_t)frame[8] << 8) | frame[9];
|
|||
|
|
sensorData.pos_y = parseSignedCoordinate(y_raw);
|
|||
|
|
|
|||
|
|
uint16_t z_raw = ((uint16_t)frame[10] << 8) | frame[11];
|
|||
|
|
sensorData.pos_z = parseSignedCoordinate(z_raw);
|
|||
|
|
|
|||
|
|
Serial.printf("📍方位坐标 - 原始: X=0x%04X, Y=0x%04X, Z=0x%04X\n",
|
|||
|
|
x_raw, y_raw, z_raw);
|
|||
|
|
Serial.printf(" 解析后: X=%d, Y=%d, Z=%d cm\n",
|
|||
|
|
sensorData.pos_x, sensorData.pos_y, sensorData.pos_z);
|
|||
|
|
}
|
|||
|
|
break;
|
|||
|
|
|
|||
|
|
default:
|
|||
|
|
Serial.printf("❓未知的0x80命令字: 0x%02X\n", cmdByte);
|
|||
|
|
break;
|
|||
|
|
}
|
|||
|
|
break;
|
|||
|
|
|
|||
|
|
case CTRL_BREATH:
|
|||
|
|
switch(cmdByte) {
|
|||
|
|
case 0x00:
|
|||
|
|
case 0x80:
|
|||
|
|
if(dataLen >= 1) {
|
|||
|
|
if(frame[6] == 0x01)
|
|||
|
|
Serial.println("🔄 呼吸监测功能已开启");
|
|||
|
|
else
|
|||
|
|
Serial.println("🔄 呼吸监测功能已关闭");
|
|||
|
|
}
|
|||
|
|
break;
|
|||
|
|
|
|||
|
|
case 0x01:
|
|||
|
|
if(dataLen >= 1) {
|
|||
|
|
sensorData.breath_status = frame[6];
|
|||
|
|
const char* info_str[] = {"", "正常", "呼吸过高(>25)", "呼吸过低(<10)", "无"};
|
|||
|
|
if(sensorData.breath_status >= 1 && sensorData.breath_status <= 4) {
|
|||
|
|
Serial.printf("🔍 呼吸信息: %s\n", info_str[sensorData.breath_status]);
|
|||
|
|
} else {
|
|||
|
|
Serial.printf("🔍 呼吸信息: 未知状态(0x%02X)\n", sensorData.breath_status);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
break;
|
|||
|
|
|
|||
|
|
case 0x02:
|
|||
|
|
if(dataLen >= 1) {
|
|||
|
|
sensorData.breath_rate = (float)frame[6];
|
|||
|
|
sensorData.breath_valid = (sensorData.breath_rate >= 0.0f &&
|
|||
|
|
sensorData.breath_rate <= 35.0f);
|
|||
|
|
Serial.printf("💨 呼吸率: %.1f 次/分\n", sensorData.breath_rate);
|
|||
|
|
}
|
|||
|
|
break;
|
|||
|
|
|
|||
|
|
case 0x05:
|
|||
|
|
if(dataLen >= 5) {
|
|||
|
|
for(int i = 0; i < 5 && i < dataLen; i++) {
|
|||
|
|
sensorData.breath_waveform[i] = (int8_t)(frame[6+i] - 128);
|
|||
|
|
}
|
|||
|
|
Serial.printf("📈 呼吸波形: %d\n", sensorData.breath_waveform[0]);
|
|||
|
|
}
|
|||
|
|
break;
|
|||
|
|
|
|||
|
|
default:
|
|||
|
|
Serial.printf("❓未知的0x81命令字: 0x%02X\n", cmdByte);
|
|||
|
|
break;
|
|||
|
|
}
|
|||
|
|
break;
|
|||
|
|
|
|||
|
|
case CTRL_HEARTRATE:
|
|||
|
|
switch(cmdByte) {
|
|||
|
|
case 0x00:
|
|||
|
|
case 0x80:
|
|||
|
|
if(dataLen >= 1) {
|
|||
|
|
if(frame[6] == 0x01)
|
|||
|
|
Serial.println("🔄 心率监测功能已开启");
|
|||
|
|
else
|
|||
|
|
Serial.println("🔄 心率监测功能已关闭");
|
|||
|
|
}
|
|||
|
|
break;
|
|||
|
|
case 0x02:
|
|||
|
|
if(dataLen >= 1) {
|
|||
|
|
sensorData.heart_rate = (float)frame[6];
|
|||
|
|
sensorData.heart_valid = (sensorData.heart_rate >= 60.0f &&
|
|||
|
|
sensorData.heart_rate <= 120.0f);
|
|||
|
|
Serial.printf("❤️ 心率: %.1f 次/分\n", sensorData.heart_rate);
|
|||
|
|
}
|
|||
|
|
break;
|
|||
|
|
|
|||
|
|
case 0x05:
|
|||
|
|
if(dataLen >= 5) {
|
|||
|
|
for(int i = 0; i < 5 && i < dataLen; i++) {
|
|||
|
|
sensorData.heart_waveform[i] = (int8_t)(frame[6+i] - 128);
|
|||
|
|
}
|
|||
|
|
Serial.printf("📈 心率波形: %d\n", sensorData.heart_waveform[0]);
|
|||
|
|
}
|
|||
|
|
break;
|
|||
|
|
|
|||
|
|
default:
|
|||
|
|
Serial.printf("❓未知的0x85命令字: 0x%02X\n", cmdByte);
|
|||
|
|
break;
|
|||
|
|
}
|
|||
|
|
break;
|
|||
|
|
|
|||
|
|
case CTRL_SLEEP:
|
|||
|
|
switch(cmdByte) {
|
|||
|
|
case 0x00:
|
|||
|
|
case 0x80:
|
|||
|
|
if(dataLen >= 1) {
|
|||
|
|
if(frame[6] == 0x01)
|
|||
|
|
Serial.println("🔄 睡眠监测功能已开启");
|
|||
|
|
else
|
|||
|
|
Serial.println("🔄 睡眠监测功能已关闭");
|
|||
|
|
}
|
|||
|
|
break;
|
|||
|
|
|
|||
|
|
case 0x01:
|
|||
|
|
case 0x81:
|
|||
|
|
if(dataLen >= 1) {
|
|||
|
|
sensorData.bed_status = frame[6];
|
|||
|
|
const char* status_str[] = {"离床", "入床", "无"};
|
|||
|
|
Serial.printf("🛏️ 床状态: %s\n", status_str[sensorData.bed_status]);
|
|||
|
|
}
|
|||
|
|
break;
|
|||
|
|
|
|||
|
|
case 0x03:
|
|||
|
|
case 0x83:
|
|||
|
|
if(dataLen >= 2) {
|
|||
|
|
sensorData.awake_time = ((uint16_t)frame[6] << 8) | (uint16_t)frame[7];
|
|||
|
|
Serial.printf("⏰ 清醒时长: %d 分钟\n", sensorData.awake_time);
|
|||
|
|
}
|
|||
|
|
break;
|
|||
|
|
|
|||
|
|
case 0x04:
|
|||
|
|
case 0x84:
|
|||
|
|
if(dataLen >= 2) {
|
|||
|
|
sensorData.light_sleep_time = ((uint16_t)frame[6] << 8) | (uint16_t)frame[7];
|
|||
|
|
Serial.printf("😪 浅睡时长: %d 分钟\n", sensorData.light_sleep_time);
|
|||
|
|
}
|
|||
|
|
break;
|
|||
|
|
|
|||
|
|
case 0x05:
|
|||
|
|
case 0x85:
|
|||
|
|
if(dataLen >= 2) {
|
|||
|
|
sensorData.deep_sleep_time = ((uint16_t)frame[6] << 8) | (uint16_t)frame[7];
|
|||
|
|
Serial.printf("💤 深睡时长: %d 分钟\n", sensorData.deep_sleep_time);
|
|||
|
|
}
|
|||
|
|
break;
|
|||
|
|
|
|||
|
|
case 0x06:
|
|||
|
|
if(dataLen >= 1) {
|
|||
|
|
sensorData.sleep_score = frame[6];
|
|||
|
|
Serial.printf("⭐ 睡眠质量评分: %d 分\n", sensorData.sleep_score);
|
|||
|
|
}
|
|||
|
|
break;
|
|||
|
|
|
|||
|
|
case 0x86:
|
|||
|
|
if(dataLen >= 2) {
|
|||
|
|
sensorData.sleep_score = frame[6];
|
|||
|
|
Serial.printf("⭐ 睡眠质量评分: %d 分\n", sensorData.sleep_score);
|
|||
|
|
}
|
|||
|
|
break;
|
|||
|
|
|
|||
|
|
case 0x0C:
|
|||
|
|
case 0x8D:
|
|||
|
|
if(dataLen >= 8) {
|
|||
|
|
sensorData.presence = frame[6];
|
|||
|
|
sensorData.sleep_state = frame[7];
|
|||
|
|
sensorData.avg_breath_rate = frame[8];
|
|||
|
|
sensorData.avg_heart_rate = frame[9];
|
|||
|
|
sensorData.turnover_count = frame[10];
|
|||
|
|
sensorData.large_move_ratio = frame[11];
|
|||
|
|
sensorData.small_move_ratio = frame[12];
|
|||
|
|
sensorData.apnea_count = frame[13];
|
|||
|
|
|
|||
|
|
Serial.printf("📊 睡眠综合状态 - 存在:%d, 睡眠状态:%d, 睡眠平均呼吸:%d, 睡眠平均心率:%d, 翻身次数:%d, 大动占比:%d%%, 小动占比:%d%%, 呼吸暂停次数:%d\n",
|
|||
|
|
sensorData.presence, sensorData.sleep_state,
|
|||
|
|
sensorData.avg_breath_rate, sensorData.avg_heart_rate,
|
|||
|
|
sensorData.turnover_count, sensorData.large_move_ratio,
|
|||
|
|
sensorData.small_move_ratio, sensorData.apnea_count);
|
|||
|
|
}
|
|||
|
|
break;
|
|||
|
|
|
|||
|
|
case 0x0D:
|
|||
|
|
case 0x8F:
|
|||
|
|
if(dataLen >= 12) {
|
|||
|
|
sensorData.sleep_score = frame[6];
|
|||
|
|
sensorData.sleep_total_time = ((uint16_t)frame[7] << 8) | (uint16_t)frame[8];
|
|||
|
|
|
|||
|
|
sensorData.awake_ratio = frame[9];
|
|||
|
|
sensorData.light_sleep_ratio = frame[10];
|
|||
|
|
sensorData.deep_sleep_ratio = frame[11];
|
|||
|
|
|
|||
|
|
sensorData.bed_Out_Time = frame[12];
|
|||
|
|
sensorData.turn_count = frame[13];
|
|||
|
|
sensorData.turnover_count = frame[14];
|
|||
|
|
sensorData.avg_breath_rate = frame[15];
|
|||
|
|
sensorData.avg_heart_rate = frame[16];
|
|||
|
|
sensorData.apnea_count = frame[17];
|
|||
|
|
|
|||
|
|
Serial.printf("📈 睡眠分析报告 - 评分:%d, 总时长:%d分, 清醒占比:%d%%, 浅睡占比:%d%%, 深睡占比:%d%%, 离床时长:%d, 离床次数:%d, 翻身:%d次, 平均呼吸:%d, 平均心跳:%d\n",
|
|||
|
|
sensorData.sleep_score,
|
|||
|
|
sensorData.sleep_total_time,
|
|||
|
|
sensorData.awake_ratio,
|
|||
|
|
sensorData.light_sleep_ratio,
|
|||
|
|
sensorData.deep_sleep_ratio,
|
|||
|
|
sensorData.bed_Out_Time,
|
|||
|
|
sensorData.turn_count,
|
|||
|
|
sensorData.turnover_count,
|
|||
|
|
sensorData.avg_breath_rate,
|
|||
|
|
sensorData.avg_heart_rate);
|
|||
|
|
}
|
|||
|
|
break;
|
|||
|
|
|
|||
|
|
case 0x0E:
|
|||
|
|
case 0x8E:
|
|||
|
|
if(dataLen >= 1) {
|
|||
|
|
sensorData.abnormal_state = frame[6];
|
|||
|
|
const char* abnormal_str[] = {
|
|||
|
|
"睡眠时长不足4小时", "睡眠时长大于12小时", "长时间异常无人"
|
|||
|
|
};
|
|||
|
|
if(sensorData.abnormal_state < 3) {
|
|||
|
|
Serial.printf("⚠️ 睡眠异常: %s\n", abnormal_str[sensorData.abnormal_state]);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
break;
|
|||
|
|
|
|||
|
|
case 0x10:
|
|||
|
|
case 0x90:
|
|||
|
|
if(dataLen >= 1) {
|
|||
|
|
sensorData.sleep_grade = frame[6];
|
|||
|
|
const char* rating_str[] = {"无", "睡眠质量良好", "睡眠质量一般", "睡眠质量较差"};
|
|||
|
|
if(sensorData.sleep_grade < 4) {
|
|||
|
|
Serial.printf("🏆 睡眠质量评级: %s\n", rating_str[sensorData.sleep_grade]);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
break;
|
|||
|
|
|
|||
|
|
case 0x11:
|
|||
|
|
case 0x91:
|
|||
|
|
if(dataLen >= 1) {
|
|||
|
|
sensorData.struggle_alert = frame[6];
|
|||
|
|
const char* struggle_str[] = {"无", "正常", "异常挣扎"};
|
|||
|
|
if(sensorData.struggle_alert < 3) {
|
|||
|
|
Serial.printf("⚠️ 挣扎状态: %s\n", struggle_str[sensorData.struggle_alert]);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
break;
|
|||
|
|
|
|||
|
|
case 0x12:
|
|||
|
|
case 0x92:
|
|||
|
|
if(dataLen >= 1) {
|
|||
|
|
sensorData.no_one_alert = frame[6];
|
|||
|
|
const char* no_one_str[] = {"无", "正常", "异常"};
|
|||
|
|
if(sensorData.no_one_alert < 3) {
|
|||
|
|
Serial.printf("⏰ 无人计时状态: %s\n", no_one_str[sensorData.no_one_alert]);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
break;
|
|||
|
|
|
|||
|
|
default:
|
|||
|
|
Serial.printf("❓未知的0x84命令字: 0x%02X\n", cmdByte);
|
|||
|
|
break;
|
|||
|
|
}
|
|||
|
|
break;
|
|||
|
|
|
|||
|
|
case 0x07:
|
|||
|
|
if(dataLen >= 1) {
|
|||
|
|
if(frame[6] == 0x00)
|
|||
|
|
Serial.println("雷达探测范围外");
|
|||
|
|
else
|
|||
|
|
Serial.println("雷达探测范围内");
|
|||
|
|
}
|
|||
|
|
break;
|
|||
|
|
|
|||
|
|
default:
|
|||
|
|
Serial.printf("❓未知控制字: 0x%02X\n", ctrlByte);
|
|||
|
|
break;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
lastSensorUpdate = millis();
|
|||
|
|
|
|||
|
|
sensorData.heart_valid = (sensorData.heart_rate > 0 && sensorData.heart_rate < 200);
|
|||
|
|
sensorData.breath_valid = (sensorData.breath_rate >= 0.1f && sensorData.breath_rate <= 60.0f);
|
|||
|
|
|
|||
|
|
if( sensorData.heart_valid ==1 && sensorData.heart_valid == 1 && presence_Bit == 1 )
|
|||
|
|
{
|
|||
|
|
sensorData.presence = 1;
|
|||
|
|
presence_Bit = 0;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
return true;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
int16_t parseSignedCoordinate(uint16_t raw_value) {
|
|||
|
|
bool is_negative = (raw_value & 0x8000) != 0;
|
|||
|
|
uint16_t magnitude = raw_value & 0x7FFF;
|
|||
|
|
|
|||
|
|
int16_t result = (int16_t)magnitude;
|
|||
|
|
if (is_negative) {
|
|||
|
|
result = -result;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
return result;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* @brief 发送雷达命令
|
|||
|
|
* 构造并发送控制命令到雷达模组
|
|||
|
|
* @param ctrl 控制字节
|
|||
|
|
* @param cmd 命令字节
|
|||
|
|
* @param value 值字节
|
|||
|
|
*/
|
|||
|
|
void sendRadarCommand(uint8_t ctrl, uint8_t cmd, uint8_t value) {
|
|||
|
|
uint8_t command[10];
|
|||
|
|
command[0] = 0x53;
|
|||
|
|
command[1] = 0x59;
|
|||
|
|
command[2] = ctrl;
|
|||
|
|
command[3] = cmd;
|
|||
|
|
command[4] = 0x00;
|
|||
|
|
command[5] = 0x01;
|
|||
|
|
command[6] = value;
|
|||
|
|
|
|||
|
|
uint8_t checksum = 0;
|
|||
|
|
for(int i = 0; i < 7; i++) {
|
|||
|
|
checksum += command[i];
|
|||
|
|
}
|
|||
|
|
command[7] = checksum;
|
|||
|
|
|
|||
|
|
command[8] = 0x54;
|
|||
|
|
command[9] = 0x43;
|
|||
|
|
|
|||
|
|
mySerial1.write(command, 10);
|
|||
|
|
|
|||
|
|
Serial.printf("📤 发送: ");
|
|||
|
|
for(int i = 0; i < 10; i++) {
|
|||
|
|
Serial.printf("%02X ", command[i]);
|
|||
|
|
}
|
|||
|
|
Serial.println();
|
|||
|
|
|
|||
|
|
Serial.printf(" 控制字=0x%02X, 命令字=0x%02X, 值=0x%02X, 校验和=0x%02X\n",
|
|||
|
|
ctrl, cmd, value, checksum);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
void IRAM_ATTR serialRxCallback() {
|
|||
|
|
if (uartQueue != NULL) {
|
|||
|
|
while(mySerial1.available()) {
|
|||
|
|
char c = mySerial1.read();
|
|||
|
|
|
|||
|
|
BaseType_t xHigherPriorityTaskWoken = pdFALSE;
|
|||
|
|
xQueueSendFromISR(uartQueue, &c, &xHigherPriorityTaskWoken);
|
|||
|
|
|
|||
|
|
if(xHigherPriorityTaskWoken) {
|
|||
|
|
portYIELD_FROM_ISR();
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* @brief 检查数据是否发生变化
|
|||
|
|
* 比较当前传感器数据与上次发送的数据,判断是否需要发送更新
|
|||
|
|
* @return 是否发生变化
|
|||
|
|
*/
|
|||
|
|
bool isDataChanged() {
|
|||
|
|
// 心率变化阈值:0.5
|
|||
|
|
if (fabs(sensorData.heart_rate - lastSentData.heart_rate) > 0.5) {
|
|||
|
|
return true;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 呼吸率变化阈值:0.5
|
|||
|
|
if (fabs(sensorData.breath_rate - lastSentData.breath_rate) > 0.5) {
|
|||
|
|
return true;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 存在状态变化
|
|||
|
|
if (sensorData.presence != lastSentData.presence) {
|
|||
|
|
return true;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 运动状态变化
|
|||
|
|
if (sensorData.motion != lastSentData.motion) {
|
|||
|
|
return true;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 睡眠状态变化
|
|||
|
|
if (sensorData.sleep_state != lastSentData.sleep_state) {
|
|||
|
|
return true;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
return false;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* @brief BLE数据发送任务
|
|||
|
|
* 实现基于数据变化和定时检测的发送机制
|
|||
|
|
* 1. 定时检测数据变化(基于continuousSendInterval)
|
|||
|
|
* 2. 检测到变化时立即发送数据
|
|||
|
|
* 3. 发送后更新lastSentData为当前数据
|
|||
|
|
* @param parameter 任务参数(未使用)
|
|||
|
|
*/
|
|||
|
|
void bleSendTask(void *parameter) {
|
|||
|
|
Serial.println("🔁 R60ABD1蓝牙数据发送任务启动");
|
|||
|
|
|
|||
|
|
while (1) {
|
|||
|
|
esp_task_wdt_reset();
|
|||
|
|
|
|||
|
|
// 检查是否需要进行数据检测
|
|||
|
|
if (continuousSendEnabled && deviceConnected) {
|
|||
|
|
unsigned long currentTime = millis();
|
|||
|
|
|
|||
|
|
// 按照设定的时间间隔进行检测
|
|||
|
|
if (currentTime - lastCheckTime >= continuousSendInterval) {
|
|||
|
|
lastCheckTime = currentTime;
|
|||
|
|
|
|||
|
|
// 检查数据是否发生变化
|
|||
|
|
if (isDataChanged()) {
|
|||
|
|
// 构建雷达数据字符串
|
|||
|
|
String radarDataCore;
|
|||
|
|
|
|||
|
|
if (sensorData.presence > 0) {
|
|||
|
|
radarDataCore = String(sensorData.heart_rate, 1) + String("|") +
|
|||
|
|
String(sensorData.breath_rate, 1) + String("|") +
|
|||
|
|
String((int)sensorData.heart_waveform[0]) + String("|") +
|
|||
|
|
String((int)sensorData.breath_waveform[0]) + String("|") +
|
|||
|
|
String(sensorData.presence) + String("|") +
|
|||
|
|
String(sensorData.motion) + String("|") +
|
|||
|
|
String(sensorData.sleep_state);
|
|||
|
|
} else {
|
|||
|
|
radarDataCore = String("0.0") + String("|") +
|
|||
|
|
String("0.0") + String("|") +
|
|||
|
|
String("0") + String("|") +
|
|||
|
|
String("0") + String("|") +
|
|||
|
|
String("0") + String("|") +
|
|||
|
|
String("0") + String("|") +
|
|||
|
|
String("0");
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 计算CRC校验
|
|||
|
|
unsigned int crc = 0xFFFF;
|
|||
|
|
for (int i = 0; i < radarDataCore.length(); i++) {
|
|||
|
|
crc ^= (unsigned int)radarDataCore.charAt(i);
|
|||
|
|
for (int j = 0; j < 8; j++) {
|
|||
|
|
if (crc & 0x0001) {
|
|||
|
|
crc >>= 1;
|
|||
|
|
crc ^= 0xA001;
|
|||
|
|
} else {
|
|||
|
|
crc >>= 1;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
String radarDataMsg = radarDataCore + String("|") + String(crc, HEX);
|
|||
|
|
|
|||
|
|
Serial.printf("📤 通过蓝牙发送R60ABD1雷达数据: %s\n", radarDataMsg.c_str());
|
|||
|
|
|
|||
|
|
const int MAX_BLE_PACKET_SIZE = 20;
|
|||
|
|
if (radarDataMsg.length() <= MAX_BLE_PACKET_SIZE) {
|
|||
|
|
pCharacteristic->setValue(radarDataMsg.c_str());
|
|||
|
|
pCharacteristic->notify();
|
|||
|
|
Serial.println("✅ R60ABD1雷达数据蓝牙发送成功");
|
|||
|
|
} else {
|
|||
|
|
Serial.println("🔄 R60ABD1雷达数据较长,使用分包发送");
|
|||
|
|
sendDataInChunks(radarDataMsg);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 更新上次发送的数据
|
|||
|
|
lastSentData.heart_rate = sensorData.heart_rate;
|
|||
|
|
lastSentData.breath_rate = sensorData.breath_rate;
|
|||
|
|
lastSentData.presence = sensorData.presence;
|
|||
|
|
lastSentData.motion = sensorData.motion;
|
|||
|
|
lastSentData.sleep_state = sensorData.sleep_state;
|
|||
|
|
|
|||
|
|
Serial.println("📊 数据已更新,上次发送数据已保存");
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
vTaskDelay(10 / portTICK_PERIOD_MS);
|
|||
|
|
esp_task_wdt_reset();
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* @brief 生命体征数据发送任务
|
|||
|
|
* 从队列中获取生命体征数据并发送到InfluxDB数据库
|
|||
|
|
* @param parameter 任务参数(未使用)
|
|||
|
|
*/
|
|||
|
|
void vitalSendTask(void *parameter) {
|
|||
|
|
Serial.println("🔁🔁 生命体征数据发送任务启动(WiFi数据库传输)");
|
|||
|
|
|
|||
|
|
unsigned long lastSleepDataTime = 0;
|
|||
|
|
const unsigned long SLEEP_DATA_INTERVAL = 5000;
|
|||
|
|
|
|||
|
|
while (1) {
|
|||
|
|
VitalData vitalData;
|
|||
|
|
|
|||
|
|
if (xQueueReceive(vitalDataQueue, &vitalData, portMAX_DELAY) == pdTRUE) {
|
|||
|
|
esp_task_wdt_reset();
|
|||
|
|
|
|||
|
|
if (WiFi.status() == WL_CONNECTED) {
|
|||
|
|
// 检查心率和呼吸率是否都为0,如果是则跳过发送
|
|||
|
|
if (vitalData.heart_rate == 0 || vitalData.breath_rate == 0) {
|
|||
|
|
Serial.println("⚠️ 心率和呼吸率都为0,跳过发送数据到数据库");
|
|||
|
|
continue;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
String dailyDataLine = "daily_data,deviceId=" + String(currentDeviceId) + ",dataType=daily ";
|
|||
|
|
|
|||
|
|
bool firstField = true;
|
|||
|
|
|
|||
|
|
if (vitalData.heart_rate > 0) {
|
|||
|
|
if (!firstField) dailyDataLine += ",";
|
|||
|
|
dailyDataLine += "heartRate=" + String(vitalData.heart_rate, 1);
|
|||
|
|
firstField = false;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if (vitalData.breath_rate > 0) {
|
|||
|
|
if (!firstField) dailyDataLine += ",";
|
|||
|
|
dailyDataLine += "breathingRate=" + String(vitalData.breath_rate, 1);
|
|||
|
|
firstField = false;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if (!firstField) dailyDataLine += ",";
|
|||
|
|
dailyDataLine += "personDetected=" + String(vitalData.presence) + "i";
|
|||
|
|
firstField = false;
|
|||
|
|
|
|||
|
|
if (!firstField) dailyDataLine += ",";
|
|||
|
|
dailyDataLine += "humanActivity=" + String(vitalData.motion) + "i";
|
|||
|
|
firstField = false;
|
|||
|
|
|
|||
|
|
if (vitalData.distance > 0) {
|
|||
|
|
if (!firstField) dailyDataLine += ",";
|
|||
|
|
dailyDataLine += "humanDistance=" + String(vitalData.distance) + "i";
|
|||
|
|
firstField = false;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if (vitalData.sleep_state >= 0) {
|
|||
|
|
if (!firstField) dailyDataLine += ",";
|
|||
|
|
dailyDataLine += "sleepState=" + String(vitalData.sleep_state) + "i";
|
|||
|
|
firstField = false;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if (!firstField) dailyDataLine += ",";
|
|||
|
|
dailyDataLine += "humanPositionX=" + String(vitalData.pos_x) + "i";
|
|||
|
|
firstField = false;
|
|||
|
|
|
|||
|
|
if (!firstField) dailyDataLine += ",";
|
|||
|
|
dailyDataLine += "humanPositionY=" + String(vitalData.pos_y) + "i";
|
|||
|
|
firstField = false;
|
|||
|
|
|
|||
|
|
if (!firstField) dailyDataLine += ",";
|
|||
|
|
dailyDataLine += "humanPositionZ=" + String(vitalData.pos_z) + "i";
|
|||
|
|
firstField = false;
|
|||
|
|
|
|||
|
|
if (!firstField) dailyDataLine += ",";
|
|||
|
|
dailyDataLine += "heartbeatWaveform=" + String((int)sensorData.heart_waveform[0]) + "i";
|
|||
|
|
firstField = false;
|
|||
|
|
|
|||
|
|
if (!firstField) dailyDataLine += ",";
|
|||
|
|
dailyDataLine += "breathingWaveform=" + String((int)sensorData.breath_waveform[0]) + "i";
|
|||
|
|
firstField = false;
|
|||
|
|
|
|||
|
|
if (!firstField) dailyDataLine += ",";
|
|||
|
|
dailyDataLine += "abnormalState=" + String(vitalData.abnormal_state) + "i";
|
|||
|
|
firstField = false;
|
|||
|
|
|
|||
|
|
if (!firstField) dailyDataLine += ",";
|
|||
|
|
dailyDataLine += "bedStatus=" + String(vitalData.bed_status) + "i";
|
|||
|
|
firstField = false;
|
|||
|
|
|
|||
|
|
if (!firstField) dailyDataLine += ",";
|
|||
|
|
dailyDataLine += "struggleAlert=" + String(vitalData.struggle_alert) + "i";
|
|||
|
|
firstField = false;
|
|||
|
|
|
|||
|
|
if (!firstField) dailyDataLine += ",";
|
|||
|
|
dailyDataLine += "noOneAlert=" + String(vitalData.no_one_alert) + "i";
|
|||
|
|
firstField = false;
|
|||
|
|
|
|||
|
|
if (!dailyDataLine.endsWith(" ")) {
|
|||
|
|
sendDailyDataToInfluxDB(dailyDataLine);
|
|||
|
|
esp_task_wdt_reset();
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
unsigned long currentTime = millis();
|
|||
|
|
if (currentTime - lastSleepDataTime >= SLEEP_DATA_INTERVAL) {
|
|||
|
|
sendSleepDataToInfluxDB();
|
|||
|
|
lastSleepDataTime = currentTime;
|
|||
|
|
Serial.println("⏰ 睡眠数据定时发送完成");
|
|||
|
|
}
|
|||
|
|
} else {
|
|||
|
|
Serial.println("❌❌ WiFi未连接,无法发送雷达数据到数据库");
|
|||
|
|
|
|||
|
|
static unsigned long lastWifiCheck = 0;
|
|||
|
|
if (millis() - lastWifiCheck > 10000) {
|
|||
|
|
Serial.printf("📶📶 WiFi状态: %d\n", WiFi.status());
|
|||
|
|
lastWifiCheck = millis();
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
esp_task_wdt_reset();
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
vTaskDelay(10 / portTICK_PERIOD_MS);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* @brief 发送日常数据到InfluxDB数据库
|
|||
|
|
* 通过HTTP协议将数据写入InfluxDB时序数据库
|
|||
|
|
* @param dailyDataLine 数据行字符串
|
|||
|
|
*/
|
|||
|
|
void sendDailyDataToInfluxDB(String dailyDataLine) {
|
|||
|
|
if (WiFi.status() != WL_CONNECTED) {
|
|||
|
|
Serial.println("❌ WiFi未连接,无法发送日常数据到数据库");
|
|||
|
|
return;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
HTTPClient http;
|
|||
|
|
http.setTimeout(2000);
|
|||
|
|
|
|||
|
|
String url = String("http://") + String(influxDBHost) + ":" + String(influxDBPort) + "/api/v2/write?org=" + String(influxDBOrg) + "&bucket=" + String(influxDBBucket);
|
|||
|
|
|
|||
|
|
http.begin(url);
|
|||
|
|
http.addHeader("Authorization", String("Token ") + String(influxDBToken));
|
|||
|
|
http.addHeader("Content-Type", "text/plain; charset=utf-8");
|
|||
|
|
http.setReuse(true);
|
|||
|
|
|
|||
|
|
Serial.println(String("📊 发送日常数据到InfluxDB: ") + dailyDataLine);
|
|||
|
|
|
|||
|
|
int httpResponseCode = http.POST(dailyDataLine);
|
|||
|
|
|
|||
|
|
if (httpResponseCode == 204) {
|
|||
|
|
Serial.println("✅ 日常数据发送成功");
|
|||
|
|
} else {
|
|||
|
|
Serial.println(String("❌ 发送日常数据失败: ") + String(httpResponseCode) + " - " + http.getString());
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
http.end();
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* @brief 发送睡眠数据到InfluxDB数据库
|
|||
|
|
* 将睡眠相关的统计数据发送到InfluxDB时序数据库
|
|||
|
|
*/
|
|||
|
|
void sendSleepDataToInfluxDB() {
|
|||
|
|
if (WiFi.status() != WL_CONNECTED) {
|
|||
|
|
Serial.println("❌ WiFi未连接,无法发送睡眠数据到数据库");
|
|||
|
|
return;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if (sensorData.sleep_total_time == 0) {
|
|||
|
|
Serial.println("😴 总睡眠时长为0,跳过上传睡眠数据");
|
|||
|
|
return;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
HTTPClient http;
|
|||
|
|
http.setTimeout(2000);
|
|||
|
|
|
|||
|
|
String url = String("http://") + String(influxDBHost) + ":" + String(influxDBPort) + "/api/v2/write?org=" + String(influxDBOrg) + "&bucket=" + String(influxDBBucket);
|
|||
|
|
|
|||
|
|
http.begin(url);
|
|||
|
|
http.addHeader("Authorization", String("Token ") + String(influxDBToken));
|
|||
|
|
http.addHeader("Content-Type", "text/plain; charset=utf-8");
|
|||
|
|
http.setReuse(true);
|
|||
|
|
|
|||
|
|
String lineProtocol = String("sleep_data,deviceId=") + String(currentDeviceId) + ",dataType=sleep ";
|
|||
|
|
|
|||
|
|
String fields = "";
|
|||
|
|
fields += String("sleepQualityScore=") + String((int)sensorData.sleep_score) + "i";
|
|||
|
|
fields += ",sleepQualityGrade=" + String((int)sensorData.sleep_grade) + "i";
|
|||
|
|
fields += ",totalSleepDuration=" + String((int)sensorData.sleep_total_time) + "i";
|
|||
|
|
fields += ",awakeDurationRatio=" + String((int)sensorData.awake_ratio) + "i";
|
|||
|
|
fields += ",lightSleepRatio=" + String((int)sensorData.light_sleep_ratio) + "i";
|
|||
|
|
fields += ",deepSleepRatio=" + String((int)sensorData.deep_sleep_ratio) + "i";
|
|||
|
|
fields += ",outOfBedDuration=" + String((int)sensorData.bed_Out_Time) + "i";
|
|||
|
|
fields += ",outOfBedCount=" + String((int)sensorData.turn_count) + "i";
|
|||
|
|
fields += ",turnCount=" + String((int)sensorData.turnover_count) + "i";
|
|||
|
|
fields += ",avgBreathingRate=" + String((int)sensorData.avg_breath_rate) + "i";
|
|||
|
|
fields += ",avgHeartRate=" + String((int)sensorData.avg_heart_rate) + "i";
|
|||
|
|
fields += ",apneaCount=" + String((int)sensorData.apnea_count) + "i";
|
|||
|
|
fields += ",abnormalState=" + String((int)sensorData.abnormal_state) + "i";
|
|||
|
|
fields += ",bodyMovement=" + String((int)sensorData.body_movement) + "i";
|
|||
|
|
fields += ",breathStatus=" + String((int)sensorData.breath_status) + "i";
|
|||
|
|
fields += ",sleepState=" + String((int)sensorData.sleep_state) + "i";
|
|||
|
|
fields += ",largeMoveRatio=" + String((int)sensorData.large_move_ratio) + "i";
|
|||
|
|
fields += ",smallMoveRatio=" + String((int)sensorData.small_move_ratio) + "i";
|
|||
|
|
fields += ",struggleAlert=" + String((int)sensorData.struggle_alert) + "i";
|
|||
|
|
fields += ",noOneAlert=" + String((int)sensorData.no_one_alert) + "i";
|
|||
|
|
fields += ",awakeDuration=" + String((int)sensorData.awake_time) + "i";
|
|||
|
|
fields += ",lightSleepDuration=" + String((int)sensorData.light_sleep_time) + "i";
|
|||
|
|
fields += ",deepSleepDuration=" + String((int)sensorData.deep_sleep_time) + "i";
|
|||
|
|
|
|||
|
|
lineProtocol += fields;
|
|||
|
|
|
|||
|
|
Serial.println(String("🌙 发送睡眠数据到InfluxDB: ") + lineProtocol);
|
|||
|
|
|
|||
|
|
int httpResponseCode = http.POST(lineProtocol);
|
|||
|
|
|
|||
|
|
if (httpResponseCode == 204) {
|
|||
|
|
Serial.println(String("✅ 睡眠数据已保存到InfluxDB设备") + String(currentDeviceId) + "上");
|
|||
|
|
} else {
|
|||
|
|
Serial.println(String("❌ 保存睡眠数据到InfluxDB失败: ") + String(httpResponseCode) + " - " + http.getString());
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
http.end();
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* @brief 雷达数据处理任务
|
|||
|
|
* 处理雷达数据并分发到相应的队列
|
|||
|
|
* @param parameter 任务参数(未使用)
|
|||
|
|
*/
|
|||
|
|
void radarDataTask(void *parameter) {
|
|||
|
|
Serial.println("🔁 雷达数据处理任务启动(最高优先级)");
|
|||
|
|
|
|||
|
|
while (1) {
|
|||
|
|
vTaskDelay(100 / portTICK_PERIOD_MS);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* @brief UART数据处理任务
|
|||
|
|
* 从UART队列中读取数据并解析雷达数据帧
|
|||
|
|
* @param parameter 任务参数(未使用)
|
|||
|
|
*/
|
|||
|
|
void uartProcessTask(void *parameter) {
|
|||
|
|
uint8_t buffer[256];
|
|||
|
|
int bufferIndex = 0;
|
|||
|
|
bool inFrame = false;
|
|||
|
|
uint8_t prevByte = 0;
|
|||
|
|
|
|||
|
|
Serial.println("✅ R60ABD1串口数据处理任务启动");
|
|||
|
|
|
|||
|
|
while(1) {
|
|||
|
|
uint8_t c;
|
|||
|
|
if(xQueueReceive(uartQueue, &c, 10 / portTICK_PERIOD_MS) == pdTRUE) {
|
|||
|
|
esp_task_wdt_reset();
|
|||
|
|
if(!inFrame) {
|
|||
|
|
if(prevByte == FRAME_HEADER1 && c == FRAME_HEADER2) {
|
|||
|
|
buffer[0] = FRAME_HEADER1;
|
|||
|
|
buffer[1] = FRAME_HEADER2;
|
|||
|
|
bufferIndex = 2;
|
|||
|
|
inFrame = true;
|
|||
|
|
Serial.println("🔍 检测到R60ABD1帧头");
|
|||
|
|
}
|
|||
|
|
} else {
|
|||
|
|
if(bufferIndex < sizeof(buffer)) {
|
|||
|
|
buffer[bufferIndex++] = c;
|
|||
|
|
|
|||
|
|
if(bufferIndex >= 8 &&
|
|||
|
|
buffer[bufferIndex-2] == FRAME_TAIL1 &&
|
|||
|
|
buffer[bufferIndex-1] == FRAME_TAIL2) {
|
|||
|
|
if(parseR60ABD1Frame(buffer, bufferIndex)) {
|
|||
|
|
static uint32_t frameCounter = 0;
|
|||
|
|
frameCounter++;
|
|||
|
|
|
|||
|
|
lastSensorUpdate = millis();
|
|||
|
|
|
|||
|
|
static uint32_t phasePacketCounter = 0;
|
|||
|
|
static uint32_t vitalPacketCounter = 0;
|
|||
|
|
|
|||
|
|
phasePacketCounter++;
|
|||
|
|
if (phasePacketCounter >= PHASE_SEND_INTERVAL) {
|
|||
|
|
PhaseData phaseData;
|
|||
|
|
phaseData.heartbeat_waveform = sensorData.heartbeat_waveform;
|
|||
|
|
phaseData.breathing_waveform = sensorData.breathing_waveform;
|
|||
|
|
|
|||
|
|
if (xQueueSend(phaseDataQueue, &phaseData, 0) == pdTRUE) {
|
|||
|
|
} else {
|
|||
|
|
Serial.println("❌ 相位数据队列已满,数据丢失");
|
|||
|
|
}
|
|||
|
|
phasePacketCounter = 0;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
vitalPacketCounter++;
|
|||
|
|
if (vitalPacketCounter >= VITAL_SEND_INTERVAL) {
|
|||
|
|
VitalData vitalData;
|
|||
|
|
vitalData.heart_rate = sensorData.heart_rate;
|
|||
|
|
vitalData.breath_rate = sensorData.breath_rate;
|
|||
|
|
vitalData.presence = sensorData.presence;
|
|||
|
|
vitalData.motion = sensorData.motion;
|
|||
|
|
vitalData.distance = sensorData.distance;
|
|||
|
|
vitalData.sleep_state = sensorData.sleep_state;
|
|||
|
|
vitalData.sleep_score = sensorData.sleep_score;
|
|||
|
|
vitalData.body_movement = sensorData.body_movement;
|
|||
|
|
vitalData.breath_status = sensorData.breath_status;
|
|||
|
|
vitalData.sleep_time = sensorData.sleep_time;
|
|||
|
|
vitalData.bed_status = sensorData.bed_status;
|
|||
|
|
vitalData.abnormal_state = sensorData.abnormal_state;
|
|||
|
|
vitalData.avg_heart_rate = sensorData.avg_heart_rate;
|
|||
|
|
vitalData.avg_breath_rate = sensorData.avg_breath_rate;
|
|||
|
|
vitalData.turn_count = sensorData.turn_count;
|
|||
|
|
vitalData.large_move_ratio = sensorData.large_move_ratio;
|
|||
|
|
vitalData.small_move_ratio = sensorData.small_move_ratio;
|
|||
|
|
vitalData.pos_x = sensorData.pos_x;
|
|||
|
|
vitalData.pos_y = sensorData.pos_y;
|
|||
|
|
vitalData.pos_z = sensorData.pos_z;
|
|||
|
|
vitalData.deep_sleep_time = sensorData.deep_sleep_time;
|
|||
|
|
vitalData.light_sleep_time = sensorData.light_sleep_time;
|
|||
|
|
vitalData.awake_time = sensorData.awake_time;
|
|||
|
|
vitalData.sleep_total_time = sensorData.sleep_total_time;
|
|||
|
|
vitalData.deep_sleep_ratio = sensorData.deep_sleep_ratio;
|
|||
|
|
vitalData.light_sleep_ratio = sensorData.light_sleep_ratio;
|
|||
|
|
vitalData.awake_ratio = sensorData.awake_ratio;
|
|||
|
|
vitalData.turnover_count = sensorData.turnover_count;
|
|||
|
|
vitalData.struggle_alert = sensorData.struggle_alert;
|
|||
|
|
vitalData.no_one_alert = sensorData.no_one_alert;
|
|||
|
|
vitalData.apnea_count = sensorData.apnea_count;
|
|||
|
|
vitalData.heartbeat_waveform = sensorData.heartbeat_waveform;
|
|||
|
|
vitalData.breathing_waveform = sensorData.breathing_waveform;
|
|||
|
|
|
|||
|
|
if (xQueueSend(vitalDataQueue, &vitalData, 0) == pdTRUE) {
|
|||
|
|
Serial.println("📤 生命体征数据已加入发送队列");
|
|||
|
|
} else {
|
|||
|
|
Serial.println("❌ 生命体征数据队列已满,数据丢失");
|
|||
|
|
}
|
|||
|
|
vitalPacketCounter = 0;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if(frameCounter % 100 == 0) {
|
|||
|
|
Serial.printf("📈 已处理 %d 个R60ABD1数据帧\n", frameCounter);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
inFrame = false;
|
|||
|
|
}
|
|||
|
|
} else {
|
|||
|
|
Serial.println("⚠️ R60ABD1帧缓冲区溢出,重置接收状态");
|
|||
|
|
inFrame = false;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
prevByte = c;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
vTaskDelay(1 / portTICK_PERIOD_MS);
|
|||
|
|
esp_task_wdt_reset();
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* @brief 分块发送数据
|
|||
|
|
* 将大数据分块发送,避免BLE MTU限制
|
|||
|
|
* @param data 要发送的数据字符串
|
|||
|
|
*/
|
|||
|
|
void sendDataInChunks(const String& data) {
|
|||
|
|
const int MAX_PACKET_SIZE = 20;
|
|||
|
|
const int HEADER_SIZE = 6;
|
|||
|
|
const int CHUNK_SIZE = MAX_PACKET_SIZE - HEADER_SIZE;
|
|||
|
|
|
|||
|
|
int totalLength = data.length();
|
|||
|
|
int numChunks = (totalLength + CHUNK_SIZE - 1) / CHUNK_SIZE;
|
|||
|
|
|
|||
|
|
Serial.printf("📦 开始分包发送,总长度: %d, 分包数: %d\n", totalLength, numChunks);
|
|||
|
|
|
|||
|
|
for(int i = 0; i < numChunks; i++) {
|
|||
|
|
int start = i * CHUNK_SIZE;
|
|||
|
|
int chunkLength = min(CHUNK_SIZE, totalLength - start);
|
|||
|
|
String chunk = data.substring(start, start + chunkLength);
|
|||
|
|
|
|||
|
|
String packetHeader = String("[") + String(i+1) + String("/") + String(numChunks) + String("]");
|
|||
|
|
|
|||
|
|
int maxDataLength = MAX_PACKET_SIZE - packetHeader.length();
|
|||
|
|
if (chunk.length() > maxDataLength) {
|
|||
|
|
chunk = chunk.substring(0, maxDataLength);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
String packet = packetHeader + chunk;
|
|||
|
|
|
|||
|
|
Serial.printf("📤 发送分包 %s: %s\n", packetHeader.c_str(), chunk.c_str());
|
|||
|
|
|
|||
|
|
if (!deviceConnected) {
|
|||
|
|
Serial.println("❌ BLE未连接,无法发送数据");
|
|||
|
|
return;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
pCharacteristic->setValue(packet.c_str());
|
|||
|
|
pCharacteristic->notify();
|
|||
|
|
Serial.println("✅ 分包发送成功");
|
|||
|
|
|
|||
|
|
if (i < numChunks - 1) {
|
|||
|
|
Serial.printf("⏳ 等待接收端处理第%d个包...\n", i+1);
|
|||
|
|
vTaskDelay(20 / portTICK_PERIOD_MS);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
Serial.println("📦 分包发送完成");
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* @brief 发送JSON数据到BLE
|
|||
|
|
* 将JSON格式数据通过BLE发送给客户端
|
|||
|
|
* @param jsonData JSON格式数据字符串
|
|||
|
|
*/
|
|||
|
|
void sendJSONDataToBLE(const String& jsonData) {
|
|||
|
|
Serial.printf("📤 准备发送JSON数据: %s\n", jsonData.c_str());
|
|||
|
|
|
|||
|
|
if (!deviceConnected) {
|
|||
|
|
Serial.println("❌ BLE未连接,无法发送JSON数据");
|
|||
|
|
return;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
const int MAX_PACKET_SIZE = 20;
|
|||
|
|
int totalLength = jsonData.length();
|
|||
|
|
int numChunks = (totalLength + MAX_PACKET_SIZE - 1) / MAX_PACKET_SIZE;
|
|||
|
|
|
|||
|
|
Serial.printf("📦 开始分包发送JSON,总长度: %d, 分包数: %d\n", totalLength, numChunks);
|
|||
|
|
|
|||
|
|
for(int i = 0; i < numChunks; i++) {
|
|||
|
|
int start = i * MAX_PACKET_SIZE;
|
|||
|
|
int chunkLength = min(MAX_PACKET_SIZE, totalLength - start);
|
|||
|
|
String chunk = jsonData.substring(start, start + chunkLength);
|
|||
|
|
|
|||
|
|
Serial.printf("📤 发送JSON分包 %d/%d: %s\n", i+1, numChunks, chunk.c_str());
|
|||
|
|
|
|||
|
|
pCharacteristic->setValue(chunk.c_str());
|
|||
|
|
pCharacteristic->notify();
|
|||
|
|
Serial.println("✅ JSON分包发送成功");
|
|||
|
|
|
|||
|
|
if (i < numChunks - 1) {
|
|||
|
|
Serial.printf("⏳ 等待接收端处理第%d个包...\n", i+1);
|
|||
|
|
vTaskDelay(10 / portTICK_PERIOD_MS);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
Serial.println("📦 JSON分包发送完成");
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* @brief 发送自定义JSON数据到BLE
|
|||
|
|
* 构造自定义JSON格式数据并通过BLE发送
|
|||
|
|
* @param jsonType JSON类型
|
|||
|
|
* @param jsonString JSON内容字符串
|
|||
|
|
* @return 是否发送成功
|
|||
|
|
*/
|
|||
|
|
bool sendCustomJSONData(const String& jsonType, const String& jsonString) {
|
|||
|
|
if (!deviceConnected) {
|
|||
|
|
Serial.println("❌ BLE未连接,无法发送自定义JSON数据");
|
|||
|
|
return false;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
String fullJSON = String("{\"type\":\"") + jsonType + String("\",") + jsonString + String("}");
|
|||
|
|
|
|||
|
|
Serial.printf("📤 发送自定义JSON数据类型 '%s': %s\n", jsonType.c_str(), fullJSON.c_str());
|
|||
|
|
|
|||
|
|
sendJSONDataToBLE(fullJSON);
|
|||
|
|
|
|||
|
|
return true;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* @brief 发送雷达数据到BLE
|
|||
|
|
* 发送雷达传感器数据(已移至FreeRTOS任务处理)
|
|||
|
|
*/
|
|||
|
|
void sendRadarDataToBLE() {
|
|||
|
|
Serial.println("ℹ️ 雷达数据发送已移至FreeRTOS任务处理");
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* @brief 处理查询雷达数据命令
|
|||
|
|
* 处理来自BLE的雷达数据查询请求
|
|||
|
|
* @param doc JSON文档对象
|
|||
|
|
* @return 是否处理成功
|
|||
|
|
*/
|
|||
|
|
bool processQueryRadarData(JsonDocument& doc) {
|
|||
|
|
const char* command = doc["command"];
|
|||
|
|
if (command != nullptr && strcmp(command, "queryRadarData") == 0) {
|
|||
|
|
Serial.println("收到查询雷达数据命令");
|
|||
|
|
|
|||
|
|
if (deviceConnected) {
|
|||
|
|
String radarDataMsg = String("{\"type\":\"radarData\",\"success\":true") +
|
|||
|
|
String(",\"deviceId\":") + String(currentDeviceId) +
|
|||
|
|
String(",\"timestamp\":") + String(millis()) +
|
|||
|
|
String(",\"presence\":") + String(sensorData.presence) +
|
|||
|
|
String(",\"heartRate\":") + String(sensorData.heart_rate, 1) +
|
|||
|
|
String(",\"breathRate\":") + String(sensorData.breath_rate, 1) +
|
|||
|
|
String(",\"motion\":") + String(sensorData.motion) +
|
|||
|
|
String(",\"heartbeatWaveform\":") + String((int)sensorData.breath_waveform[0]) +
|
|||
|
|
String(",\"breathingWaveform\":") + String((int)sensorData.heart_waveform[0]) +
|
|||
|
|
String(",\"distance\":") + String(sensorData.distance) +
|
|||
|
|
String(",\"bodyMovement\":") + String(sensorData.body_movement) +
|
|||
|
|
String(",\"breathStatus\":") + String(sensorData.breath_status) +
|
|||
|
|
String(",\"sleepState\":") + String(sensorData.sleep_state) +
|
|||
|
|
String(",\"sleepTime\":") + String(sensorData.sleep_time) +
|
|||
|
|
String(",\"sleepScore\":") + String(sensorData.sleep_score) +
|
|||
|
|
String(",\"avgHeartRate\":") + String(sensorData.avg_heart_rate) +
|
|||
|
|
String(",\"avgBreathRate\":") + String(sensorData.avg_breath_rate) +
|
|||
|
|
String(",\"turnCount\":") + String(sensorData.turn_count) +
|
|||
|
|
String(",\"largeMoveRatio\":") + String(sensorData.large_move_ratio) +
|
|||
|
|
String(",\"smallMoveRatio\":") + String(sensorData.small_move_ratio) +
|
|||
|
|
String("}");
|
|||
|
|
|
|||
|
|
sendJSONDataToBLE(radarDataMsg);
|
|||
|
|
Serial.println("已发送雷达数据");
|
|||
|
|
Serial.printf("发送的数据: %s\n", radarDataMsg.c_str());
|
|||
|
|
} else {
|
|||
|
|
Serial.println("BLE未连接,无法发送雷达数据");
|
|||
|
|
}
|
|||
|
|
return true;
|
|||
|
|
}
|
|||
|
|
return false;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* @brief 处理启动持续发送命令
|
|||
|
|
* 处理来自BLE的启动持续发送请求
|
|||
|
|
* @param doc JSON文档对象
|
|||
|
|
* @return 是否处理成功
|
|||
|
|
*/
|
|||
|
|
bool processStartContinuousSend(JsonDocument& doc) {
|
|||
|
|
const char* command = doc["command"];
|
|||
|
|
if (command != nullptr && strcmp(command, "startContinuousSend") == 0) {
|
|||
|
|
if (doc["interval"].is<uint32_t>()) {
|
|||
|
|
continuousSendInterval = doc["interval"].as<uint32_t>();
|
|||
|
|
if (continuousSendInterval < 100) continuousSendInterval = 100;
|
|||
|
|
if (continuousSendInterval > 10000) continuousSendInterval = 10000;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
continuousSendEnabled = true;
|
|||
|
|
bleFlow.reset();
|
|||
|
|
|
|||
|
|
Serial.printf("⚙️ 启动持续发送模式,间隔: %lu ms\n", continuousSendInterval);
|
|||
|
|
|
|||
|
|
if (deviceConnected) {
|
|||
|
|
String confirmMsg = String("{\"type\":\"startContinuousSendResult\",\"success\":true,\"message\":\"已启动持续发送模式\",\"interval\":") +
|
|||
|
|
String(continuousSendInterval) + "}";
|
|||
|
|
|
|||
|
|
sendJSONDataToBLE(confirmMsg);
|
|||
|
|
Serial.println("✅ 启动确认消息发送成功");
|
|||
|
|
Serial.println("🚀 已启动持续发送模式");
|
|||
|
|
} else {
|
|||
|
|
Serial.println("❌ BLE未连接,无法发送确认消息");
|
|||
|
|
}
|
|||
|
|
return true;
|
|||
|
|
}
|
|||
|
|
return false;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* @brief 处理停止持续发送命令
|
|||
|
|
* 处理来自BLE的停止持续发送请求
|
|||
|
|
* @param doc JSON文档对象
|
|||
|
|
* @return 是否处理成功
|
|||
|
|
*/
|
|||
|
|
bool processStopContinuousSend(JsonDocument& doc) {
|
|||
|
|
const char* command = doc["command"];
|
|||
|
|
if (command != nullptr && strcmp(command, "stopContinuousSend") == 0) {
|
|||
|
|
continuousSendEnabled = false;
|
|||
|
|
|
|||
|
|
Serial.println("🛑 停止持续发送模式");
|
|||
|
|
|
|||
|
|
if (deviceConnected) {
|
|||
|
|
String confirmMsg = String("{\"type\":\"stopContinuousSendResult\",\"success\":true,\"message\":\"已停止持续发送模式\"}");
|
|||
|
|
|
|||
|
|
sendJSONDataToBLE(confirmMsg);
|
|||
|
|
Serial.println("✅ 停止确认消息发送成功");
|
|||
|
|
Serial.println("⏹️ 已停止持续发送模式");
|
|||
|
|
} else {
|
|||
|
|
Serial.println("❌ BLE未连接,无法发送确认消息");
|
|||
|
|
}
|
|||
|
|
return true;
|
|||
|
|
}
|
|||
|
|
return false;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* @brief 处理BLE配置数据
|
|||
|
|
* 处理从BLE接收到的配置数据,解析JSON命令并执行相应操作
|
|||
|
|
*/
|
|||
|
|
void processBLEConfig() {
|
|||
|
|
if (completeData.length() > 0 && (millis() - lastReceiveTime > 3000)) {
|
|||
|
|
Serial.println("⏰ [超时] 数据接收超时3秒,自动当作接收完成");
|
|||
|
|
|
|||
|
|
if (receivedData.length() == 0) {
|
|||
|
|
Serial.println("📥 [BLE] 超时后将completeData当作接收数据进行处理");
|
|||
|
|
receivedData = completeData;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
completeData = "";
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if (receivedData.length() > 0) {
|
|||
|
|
String bleData = receivedData;
|
|||
|
|
receivedData = "";
|
|||
|
|
bleData.trim();
|
|||
|
|
|
|||
|
|
Serial.printf("⚙️ [BLE] 准备解析JSON: %s\n", bleData.c_str());
|
|||
|
|
|
|||
|
|
if (bleData.startsWith("{") && bleData.endsWith("}")) {
|
|||
|
|
JsonDocument doc;
|
|||
|
|
DeserializationError error = deserializeJson(doc, bleData);
|
|||
|
|
|
|||
|
|
if (error) {
|
|||
|
|
String errorMsg = String("❌ [BLE] JSON解析失败: ") + String(error.c_str());
|
|||
|
|
Serial.println(errorMsg);
|
|||
|
|
if (deviceConnected) {
|
|||
|
|
String responseMsg = String("{\"type\":\"error\",\"message\":\"配置格式错误,请使用JSON格式\",\"originalData\":\"") + bleData + String("\"}");
|
|||
|
|
|
|||
|
|
sendJSONDataToBLE(responseMsg);
|
|||
|
|
}
|
|||
|
|
} else {
|
|||
|
|
Serial.println("✅ [BLE] JSON解析成功");
|
|||
|
|
|
|||
|
|
bool processed = false;
|
|||
|
|
|
|||
|
|
if (!processed) processed = processSetDeviceId(doc);
|
|||
|
|
|
|||
|
|
if (!processed) processed = processWiFiConfigCommand(doc);
|
|||
|
|
|
|||
|
|
if (!processed) processed = processQueryStatus(doc);
|
|||
|
|
|
|||
|
|
if (!processed) processed = processQueryRadarData(doc);
|
|||
|
|
|
|||
|
|
if (!processed) processed = processStartContinuousSend(doc);
|
|||
|
|
|
|||
|
|
if (!processed) processed = processStopContinuousSend(doc);
|
|||
|
|
|
|||
|
|
if (!processed) processed = processScanWiFi(doc);
|
|||
|
|
|
|||
|
|
if (!processed) processed = processGetSavedNetworks(doc);
|
|||
|
|
|
|||
|
|
if (!processed) processed = processEchoRequest(doc);
|
|||
|
|
|
|||
|
|
if (!processed) {
|
|||
|
|
Serial.println("❓[BLE] 未知命令");
|
|||
|
|
if (deviceConnected) {
|
|||
|
|
String responseMsg = String("{\"type\":\"error\",\"message\":\"未知命令\",\"receivedData\":\"") + bleData + String("\"}");
|
|||
|
|
|
|||
|
|
sendJSONDataToBLE(responseMsg);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
} else {
|
|||
|
|
Serial.println("📥 [BLE] 接收到非JSON数据");
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* @brief 处理设置设备ID命令
|
|||
|
|
* 处理来自BLE的设置设备ID请求
|
|||
|
|
* @param doc JSON文档对象
|
|||
|
|
* @return 是否处理成功
|
|||
|
|
*/
|
|||
|
|
bool processSetDeviceId(JsonDocument& doc) {
|
|||
|
|
const char* command = doc["command"];
|
|||
|
|
if (command != nullptr && strcmp(command, "setDeviceId") == 0) {
|
|||
|
|
uint16_t newDeviceId = doc["newDeviceId"];
|
|||
|
|
|
|||
|
|
if (newDeviceId < 1000 || newDeviceId > 1999){
|
|||
|
|
Serial.printf("[错误] 设备ID超出范围,有效范围: 1000-1999\n");
|
|||
|
|
if (deviceConnected) {
|
|||
|
|
String errorMsg = String("{\"type\":\"error\",\"message\":\"设备ID超出范围,有效范围: 1000-1999\"}");
|
|||
|
|
|
|||
|
|
sendJSONDataToBLE(errorMsg);
|
|||
|
|
}
|
|||
|
|
return true;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
currentDeviceId = newDeviceId;
|
|||
|
|
|
|||
|
|
Serial.printf("[设备ID] 已设置新的设备ID: %u\n", currentDeviceId);
|
|||
|
|
|
|||
|
|
saveDeviceId();
|
|||
|
|
Serial.printf("设备ID已保存到Flash: %u\n", currentDeviceId);
|
|||
|
|
|
|||
|
|
if (deviceConnected) {
|
|||
|
|
String confirmMsg = String("{\"type\":\"setDeviceIdResult\",\"success\":true,\"message\":\"设备ID设置成功\",\"newDeviceId\":") +
|
|||
|
|
String(newDeviceId) + "}";
|
|||
|
|
|
|||
|
|
sendJSONDataToBLE(confirmMsg);
|
|||
|
|
|
|||
|
|
sendStatusToBLE();
|
|||
|
|
}
|
|||
|
|
return true;
|
|||
|
|
}
|
|||
|
|
return false;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* @brief 发送设备状态到BLE
|
|||
|
|
* 将当前设备状态信息通过BLE发送给客户端
|
|||
|
|
*/
|
|||
|
|
void sendStatusToBLE() {
|
|||
|
|
if (deviceConnected) {
|
|||
|
|
String statusMsg = String("{\"type\":\"status\",\"wifiConfigured\":") +
|
|||
|
|
String(wifiManager.getSavedNetworkCount() > 0 ? "true" : "false") +
|
|||
|
|
String(",\"wifiConnected\":") +
|
|||
|
|
String(WiFi.status() == WL_CONNECTED ? "true" : "false") +
|
|||
|
|
String(",\"ipAddress\":\"") +
|
|||
|
|
(WiFi.status() == WL_CONNECTED ? WiFi.localIP().toString() : "") + "\"" +
|
|||
|
|
String(",\"deviceId\":") +
|
|||
|
|
String(currentDeviceId) + "}";
|
|||
|
|
|
|||
|
|
sendJSONDataToBLE(statusMsg);
|
|||
|
|
Serial.println("已发送连接状态信息");
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* @brief 处理查询状态命令
|
|||
|
|
* 处理来自BLE的查询设备状态请求
|
|||
|
|
* @param doc JSON文档对象
|
|||
|
|
* @return 是否处理成功
|
|||
|
|
*/
|
|||
|
|
bool processQueryStatus(JsonDocument& doc) {
|
|||
|
|
const char* command = doc["command"];
|
|||
|
|
if (command != nullptr && strcmp(command, "queryStatus") == 0) {
|
|||
|
|
if (deviceConnected) {
|
|||
|
|
String statusMsg = String("{\"type\":\"deviceStatus\",\"success\":true,\"deviceId\":") +
|
|||
|
|
String(currentDeviceId) +
|
|||
|
|
String(",\"wifiConfigured\":") +
|
|||
|
|
String(wifiManager.getSavedNetworkCount() > 0 ? "true" : "false") +
|
|||
|
|
String(",\"wifiConnected\":") +
|
|||
|
|
String(WiFi.status() == WL_CONNECTED ? "true" : "false") +
|
|||
|
|
String(",\"ipAddress\":\"") +
|
|||
|
|
(WiFi.status() == WL_CONNECTED ? WiFi.localIP().toString() : "") +
|
|||
|
|
String("\"}");
|
|||
|
|
|
|||
|
|
sendJSONDataToBLE(statusMsg);
|
|||
|
|
Serial.println("已发送设备状态信息");
|
|||
|
|
}
|
|||
|
|
return true;
|
|||
|
|
}
|
|||
|
|
return false;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* @brief 处理WiFi配置命令
|
|||
|
|
* 处理来自BLE的WiFi配置请求
|
|||
|
|
* @param doc JSON文档对象
|
|||
|
|
* @return 是否处理成功
|
|||
|
|
*/
|
|||
|
|
bool processWiFiConfigCommand(JsonDocument& doc) {
|
|||
|
|
const char* command = doc["command"];
|
|||
|
|
if (command != nullptr && strcmp(command, "setWiFiConfig") == 0) {
|
|||
|
|
Serial.println("📱 [BLE-WiFi] 收到WiFi配置命令");
|
|||
|
|
const char* newSSID = doc["ssid"];
|
|||
|
|
const char* newPassword = doc["password"];
|
|||
|
|
|
|||
|
|
if (newSSID != nullptr && newPassword != nullptr) {
|
|||
|
|
return wifiManager.handleConfigurationData(newSSID, newPassword);
|
|||
|
|
} else {
|
|||
|
|
Serial.println("❌ [BLE-WiFi] WiFi配置参数不完整");
|
|||
|
|
if (deviceConnected) {
|
|||
|
|
String errorMsg = String("{\"type\":\"error\",\"message\":\"WiFi配置参数不完整,需要ssid和password字段\"}");
|
|||
|
|
sendJSONDataToBLE(errorMsg);
|
|||
|
|
}
|
|||
|
|
return true;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
return false;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* @brief 处理扫描WiFi命令
|
|||
|
|
* 处理来自BLE的WiFi扫描请求
|
|||
|
|
* @param doc JSON文档对象
|
|||
|
|
* @return 是否处理成功
|
|||
|
|
*/
|
|||
|
|
bool processScanWiFi(JsonDocument& doc) {
|
|||
|
|
const char* command = doc["command"];
|
|||
|
|
if (command != nullptr && strcmp(command, "scanWiFi") == 0) {
|
|||
|
|
Serial.println("📱 [BLE-WiFi] 收到WiFi扫描命令");
|
|||
|
|
wifiManager.scanAndSendResults();
|
|||
|
|
return true;
|
|||
|
|
}
|
|||
|
|
return false;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
bool processGetSavedNetworks(JsonDocument& doc) {
|
|||
|
|
const char* command = doc["command"];
|
|||
|
|
if (command != nullptr && strcmp(command, "getSavedNetworks") == 0) {
|
|||
|
|
Serial.println("📱 [BLE-WiFi] 收到获取已保存WiFi网络命令");
|
|||
|
|
wifiManager.getSavedNetworks();
|
|||
|
|
return true;
|
|||
|
|
}
|
|||
|
|
return false;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* @brief 处理回显请求命令
|
|||
|
|
* 处理来自BLE的回显测试请求
|
|||
|
|
* @param doc JSON文档对象
|
|||
|
|
* @return 是否处理成功
|
|||
|
|
*/
|
|||
|
|
bool processEchoRequest(JsonDocument& doc) {
|
|||
|
|
const char* command = doc["command"];
|
|||
|
|
if (command != nullptr && strcmp(command, "echo") == 0) {
|
|||
|
|
Serial.println("📱 [BLE] 收到回显请求");
|
|||
|
|
|
|||
|
|
const char* echoContent = doc["content"];
|
|||
|
|
if (echoContent != nullptr) {
|
|||
|
|
String echoResponse = String("{\"type\":\"echoResponse\",\"originalContent\":\"") +
|
|||
|
|
echoContent + String("\",\"receivedSuccessfully\":true}");
|
|||
|
|
|
|||
|
|
if (deviceConnected) {
|
|||
|
|
sendJSONDataToBLE(echoResponse);
|
|||
|
|
Serial.printf("📤 [BLE] 回显响应已发送: %s\n", echoContent);
|
|||
|
|
}
|
|||
|
|
} else {
|
|||
|
|
String echoResponse = String("{\"type\":\"echoResponse\",\"receivedSuccessfully\":true,\"message\":\"Echo command received\"}");
|
|||
|
|
|
|||
|
|
if (deviceConnected) {
|
|||
|
|
sendJSONDataToBLE(echoResponse);
|
|||
|
|
Serial.println("📤 [BLE] 简单回显响应已发送");
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
return true;
|
|||
|
|
}
|
|||
|
|
return false;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* @brief 发送原始数据回显响应
|
|||
|
|
* 将接收到的原始数据通过BLE回显给客户端
|
|||
|
|
* @param rawData 原始数据字符串
|
|||
|
|
*/
|
|||
|
|
void sendRawEchoResponse(const String& rawData) {
|
|||
|
|
if (deviceConnected) {
|
|||
|
|
String echoResponse = String("{\"type\":\"rawEchoResponse\",\"originalData\":\"") +
|
|||
|
|
rawData + String("\",\"received\":true}");
|
|||
|
|
|
|||
|
|
sendJSONDataToBLE(echoResponse);
|
|||
|
|
Serial.printf("📤 [BLE] 原始数据回显已发送: %s\n", rawData.c_str());
|
|||
|
|
}
|
|||
|
|
}
|