1178 lines
42 KiB
C++
1178 lines
42 KiB
C++
|
|
#include <Arduino.h>
|
|||
|
|
#include <WiFi.h>
|
|||
|
|
#include <esp_task_wdt.h>
|
|||
|
|
#include <ArduinoJson.h>
|
|||
|
|
#include <Preferences.h>
|
|||
|
|
#include <BLEDevice.h>
|
|||
|
|
#include <BLEServer.h>
|
|||
|
|
#include <BLEUtils.h>
|
|||
|
|
#include <BLE2902.h>
|
|||
|
|
#include <HTTPClient.h>
|
|||
|
|
#include <freertos/FreeRTOS.h>
|
|||
|
|
#include <freertos/task.h>
|
|||
|
|
#include <freertos/queue.h>
|
|||
|
|
|
|||
|
|
#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;
|
|||
|
|
}
|
|||
|
|
}
|