diff --git a/demo.md b/demo.md new file mode 100644 index 0000000..9820509 Binary files /dev/null and b/demo.md differ diff --git a/docs/emotion_algorithm.md b/docs/emotion_algorithm.md new file mode 100644 index 0000000..ded3f45 --- /dev/null +++ b/docs/emotion_algorithm.md @@ -0,0 +1,873 @@ +# 情绪分析算法详细文档 + +## 一、概述 + +本算法基于生理信号(心率、呼吸率、心率变异性、体动数据)进行情绪状态分析,输出主要情绪、次要情绪、情绪强度、效价、唤醒度等多维度指标。 + +--- + +## 二、情绪类型定义 + +| 枚举值 | 情绪类型 | 英文名 | +|--------|----------|--------| +| 0 | 平静 | CALM | +| 1 | 高兴 | HAPPY | +| 2 | 兴奋 | EXCITED | +| 3 | 焦虑 | ANXIOUS | +| 4 | 愤怒 | ANGRY | +| 5 | 悲伤 | SAD | +| 6 | 压力 | STRESSED | +| 7 | 放松 | RELAXED | +| 8 | 未知 | UNKNOWN | + +--- + +## 三、整体算法流程图 + +``` +┌─────────────────────────────────────────────────────────────────┐ +│ 开始分析 │ +└─────────────────────────────────────────────────────────────────┘ + │ + ▼ +┌─────────────────────────────────────────────────────────────────┐ +│ 输入数据预处理 │ +│ ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐ │ +│ │ 心率数据 │ │ 呼吸数据 │ │ HRV数据 │ │ 体动数据 │ │ +│ └──────────┘ └──────────┘ └──────────┘ └──────────┘ │ +│ │ │ │ │ │ +│ └─────────────┴─────────────┴─────────────┘ │ +│ ▼ │ +│ ┌────────────────────────┐ │ +│ │ 滑动窗口平滑处理 │ │ +│ │ (WINDOW_SIZE = 15) │ │ +│ └────────────────────────┘ │ +└─────────────────────────────────────────────────────────────────┘ + │ + ▼ +┌─────────────────────────────────────────────────────────────────┐ +│ 计算各情绪得分 │ +│ ┌──────────────────────────────────────────────────────────┐ │ +│ │ CalmScore() = 0.45×HR + 0.22×Stability + 0.27×RR │ │ +│ │ + 0.16×RR_reg + 0.10×HRV + 0.08×Move │ │ +│ │ │ │ +│ │ HappyScore() = 0.45×HR + 0.10×Variability + 0.34×RR │ │ +│ │ + 0.10×HRV + 0.08×Move │ │ +│ │ │ │ +│ │ ExcitedScore() = 0.45×HR + 0.10×Trend + 0.34×RR │ │ +│ │ + 0.10×HRV + 0.10×Move │ │ +│ │ │ │ +│ │ AnxiousScore() = 0.36×HR + 0.16×Std + 0.25×(1-HRV) │ │ +│ │ + 0.10×Stress + 0.16×(1-RR_reg) │ │ +│ │ + 0.10×Move │ │ +│ │ │ │ +│ │ AngryScore() = 0.36×HR + 0.20×Trend + 0.25×(1-HRV) │ │ +│ │ + 0.20×(1-RR_reg) + 0.10×Move │ │ +│ │ │ │ +│ │ SadScore() = 0.40×(-HR) + 0.15×(1-Std) + 0.28×(-RR) │ │ +│ │ + 0.10×RR_reg + 0.10×(1-HRV) │ │ +│ │ + 0.10×(1-Move) │ │ +│ │ │ │ +│ │ StressedScore()= 0.36×HR + 0.20×Trend + 0.25×(1-HRV) │ │ +│ │ + 0.10×Stress + 0.16×(1-RR_reg) │ │ +│ │ + 0.10×Move │ │ +│ │ │ │ +│ │ RelaxedScore() = 0.40×(-HR) + 0.22×(1-Std) + 0.16×(-RR) │ │ +│ │ + 0.10×RR_reg + 0.10×(1-Move) │ │ +│ └──────────────────────────────────────────────────────────┘ │ +└─────────────────────────────────────────────────────────────────┘ + │ + ▼ +┌─────────────────────────────────────────────────────────────────┐ +│ 概率归一化 #1 │ +│ │ +│ P_i = Score_i / Σ(Score_j), j = 0 to 8 │ +│ │ +└─────────────────────────────────────────────────────────────────┘ + │ + ▼ +┌─────────────────────────────────────────────────────────────────┐ +│ Top1 概率放大 │ +│ │ +│ Score_max = Score_max × 1.3 │ +│ │ +└─────────────────────────────────────────────────────────────────┘ + │ + ▼ +┌─────────────────────────────────────────────────────────────────┐ +│ 概率归一化 #2 │ +│ │ +│ P_i = Score_i / Σ(Score_j), j = 0 to 8 │ +│ │ +└─────────────────────────────────────────────────────────────────┘ + │ + ▼ +┌─────────────────────────────────────────────────────────────────┐ +│ 自适应平滑处理 │ +│ │ +│ diff = |P_i - P_prev_i| │ +│ α = (diff > 0.2) ? 0.6 : 0.25 │ +│ P_i = α × P_i + (1-α) × P_prev_i │ +│ │ +└─────────────────────────────────────────────────────────────────┘ + │ + ▼ +┌─────────────────────────────────────────────────────────────────┐ +│ 提取 Top1 和 Top2 │ +│ │ +│ 遍历找出: maxProb(最大), secondProb(第二大) │ +│ primaryEmotion = emotion[maxIdx] │ +│ secondaryEmotion = emotion[secondIdx] │ +│ │ +└─────────────────────────────────────────────────────────────────┘ + │ + ▼ +┌─────────────────────────────────────────────────────────────────┐ +│ UNKNOWN 判断 │ +│ │ +│ if (maxProb < 0.20 && (maxProb - secondProb) < 0.03) │ +│ primaryEmotion = UNKNOWN │ +│ │ +└─────────────────────────────────────────────────────────────────┘ + │ + ▼ +┌─────────────────────────────────────────────────────────────────┐ +│ 负面情绪合并 │ +│ │ +│ if (primaryEmotion ∈ {ANXIOUS, ANGRY, STRESSED}) │ +│ combinedProb = P[ANXIOUS] + P[ANGRY] + P[STRESSED] │ +│ primaryEmotion = STRESSED │ +│ confidence = max(combinedProb, 各负面情绪概率) │ +│ │ +└─────────────────────────────────────────────────────────────────┘ + │ + ▼ +┌─────────────────────────────────────────────────────────────────┐ +│ 计算情绪强度 │ +│ │ +│ hrFactor = |HR - HR_baseline| / 40.0 │ +│ hrvFactor = 1 - sigmoid(HRV_rmssd, 0.02, 40) │ +│ rrFactor = |RR - RR_baseline| / 10.0 │ +│ │ +│ intensity = 0.4 + 0.3×clamp(hrFactor) │ +│ + 0.2×clamp(hrvFactor) + 0.1×clamp(rrFactor) │ +│ │ +│ intensity = clamp(intensity, 0.3, 1.0) │ +│ │ +└─────────────────────────────────────────────────────────────────┘ + │ + ▼ +┌─────────────────────────────────────────────────────────────────┐ +│ 计算情绪维度 │ +│ │ +│ valence = (P[hAPPY] + P[eXCITED] + P[rELAXED] + P[cALM]) │ +│ - (P[aNXIOUS] + P[aNGRY] + P[sAD] + P[sTRESSED]) │ +│ │ +│ arousal = (P[eXCITED] + P[aNXIOUS] + P[aNGRY]) │ +│ / (P[eXCITED] + P[aNXIOUS] + P[aNGRY] │ +│ + P[cALM] + P[rELAXED] + P[sAD]) │ +│ │ +└─────────────────────────────────────────────────────────────────┘ + │ + ▼ +┌─────────────────────────────────────────────────────────────────┐ +│ UNKNOWN 强制落地 │ +│ │ +│ if (primaryEmotion == UNKNOWN) │ +│ if (arousal > 0.6) → EXCITED │ +│ else if (valence < -0.2) → STRESSED │ +│ else → CALM │ +│ │ +└─────────────────────────────────────────────────────────────────┘ + │ + ▼ +┌─────────────────────────────────────────────────────────────────┐ +│ 计算压力水平 │ +│ │ +│ stressLevel = f(HRV, HR, RR, 压力指数) │ +│ anxietyLevel = f(HR, HRV, 呼吸规律性) │ +│ relaxationLevel = f(HRV, HR, 体动) │ +│ │ +│ sympatheticActivity = 1 - autonomicBalance │ +│ parasympatheticActivity = autonomicBalance │ +│ │ +└─────────────────────────────────────────────────────────────────┘ + │ + ▼ +┌─────────────────────────────────────────────────────────────────┐ +│ 输出结果 │ +│ primaryEmotion, secondaryEmotion, confidence, intensity, │ +│ valence, arousal, stressLevel, anxietyLevel, │ +│ relaxationLevel, sympatheticActivity, parasympatheticActivity │ +└─────────────────────────────────────────────────────────────────┘ +``` + +--- + +## 四、核心计算函数详解 + +### 4.1 Sigmoid 函数 + +```cpp +float sigmoid(float x, float k, float x0) { + return 1.0f / (1.0f + exp(-k * (x - x0))); +} +``` + +**数学公式:** +``` +σ(x; k, x₀) = 1 / (1 + e^(-k(x-x₀))) +``` + +**参数说明:** +- `k`: 斜率参数,控制曲线的陡峭程度 +- `x₀`: 中心点参数,曲线中点对应的x值 + +**函数图像特征:** +- 当 `x = x₀` 时,σ = 0.5 +- 当 `k > 0` 时,曲线单调递增 +- `k` 越大,曲线越陡峭 + +--- + +### 4.2 Gaussian 函数 + +```cpp +float gaussian(float x, float mean, float std) { + float diff = x - mean; + return exp(-(diff * diff) / (2 * std * std)); +} +``` + +**数学公式:** +``` +G(x; μ, σ) = exp(-(x-μ)² / (2σ²)) +``` + +**参数说明:** +- `μ`: 均值,函数峰值位置 +- `σ`: 标准差,控制曲线宽度 + +**函数图像特征:** +- 当 `x = μ` 时,G = 1(最大值) +- `σ` 越小,曲线越尖锐 +- `σ` 越大,曲线越平缓 + +--- + +### 4.3 归一化函数 + +```cpp +// 心率归一化 +float normalizeHR(float hr, float baseline) { + return (hr - baseline) / baseline; +} + +// 呼吸率归一化 +float normalizeRR(float rr, float baseline) { + return (rr - baseline) / baseline; +} + +// HRV归一化 +float normalizeHRV(float rmssd) { + return clamp(rmssd / 100.0f, 0.0f, 1.5f); +} + +// 体动归一化 +float normalizeMovement(float movement) { + return clamp(movement / 100.0f, 0.0f, 1.0f); +} +``` + +--- + +## 五、各情绪评分算法 + +### 5.1 平静情绪 (Calm) + +**适用场景:** 心率稳定、接近静息基线,呼吸规律,体动较低 + +**计算公式:** +``` +Score_calm = 0.45 × G(|HR_norm|, 0, 0.3) + + 0.22 × (1 - clamp(HR_std / 12, 0, 1)) + + 0.27 × G(|RR_norm|, 0, 0.5) + + 0.16 × RR_regularity + + 0.10 × σ(HRV_norm, 2.0, 0.7) + + 0.08 × G(Move_norm, 0.15, 0.2) +``` + +**权重分析:** +| 指标 | 权重 | 说明 | +|------|------|------| +| 心率偏离度 | 0.45 | 最重要,心率需接近基线 | +| 心率稳定性 | 0.22 | 心率变化小 | +| 呼吸率偏离度 | 0.27 | 呼吸需正常 | +| 呼吸规律性 | 0.16 | 呼吸规律 | +| HRV水平 | 0.10 | HRV正常或较高 | +| 体动水平 | 0.08 | 体动低 | + +--- + +### 5.2 高兴情绪 (Happy) + +**适用场景:** 心率略高于基线、有适度变异性,呼吸正常,HRV较高 + +**计算公式:** +``` +Score_happy = 0.45 × G(HR_norm, 0.3, 0.25) + + 0.10 × [1.5 < HR_std < 10 ? 1 : 0] + + 0.34 × G(|RR_norm|, 0, 0.5) + + 0.10 × σ(HRV_norm, 2.0, 0.9) + + 0.08 × G(Move_norm, 0.45, 0.25) +``` + +**权重分析:** +| 指标 | 权重 | 说明 | +|------|------|------| +| 心率偏离度 | 0.45 | 略高于基线,Gaussian中心0.3 | +| 心率变异性 | 0.10 | 适度变异(1.5-10) | +| 呼吸率偏离度 | 0.34 | 呼吸正常 | +| HRV水平 | 0.10 | HRV较高 | +| 体动水平 | 0.08 | 适中活跃 | + +--- + +### 5.3 兴奋情绪 (Excited) + +**适用场景:** 心率显著升高且趋势上升,呼吸加快,HRV中等,体动较高 + +**计算公式:** +``` +Score_excited = 0.45 × σ(HR_norm, 2.0, 0.6) + + 0.10 × [HR_trend > 1.5 ? 1 : 0] + + 0.34 × σ(RR_norm, 2.0, 0.5) + + 0.10 × G(HRV_norm, 0.7, 0.4) + + 0.10 × σ(Move_norm, 2.0, 0.6) +``` + +**权重分析:** +| 指标 | 权重 | 说明 | +|------|------|------| +| 心率偏离度 | 0.45 | 显著升高( sigmoid中心0.6) | +| 心率趋势 | 0.10 | 趋势上升加分 | +| 呼吸率偏离度 | 0.34 | 呼吸加快 | +| HRV水平 | 0.10 | 中等HRV | +| 体动水平 | 0.10 | 高活跃 | + +--- + +### 5.4 焦虑情绪 (Anxious) + +**适用场景:** 心率升高但变异小,HRV低,压力指数高,呼吸不规律 + +**计算公式:** +``` +Score_anxious = 0.36 × σ(HR_norm, 2.0, 0.4) + + 0.16 × (1 - σ(HR_std / 6, 2.0, 0.5)) + + 0.25 × (1 - σ(HRV_norm, 2.0, 0.6)) + + 0.10 × σ(HRV.stressIndex / 30, 2.0, 0.5) + + 0.16 × (1 - RR_regularity) + + 0.10 × G(Move_norm, 0.55, 0.3) +``` + +**权重分析:** +| 指标 | 权重 | 说明 | +|------|------|------| +| 心率偏离度 | 0.36 | 升高( sigmoid中心0.4,较早触发) | +| 心率稳定性 | 0.16 | 变异小(焦虑时心率僵化)| +| HRV水平 | 0.25 | HRV低(高权重)| +| 压力指数 | 0.10 | 压力指数高 | +| 呼吸规律性 | 0.16 | 呼吸不规律 | +| 体动水平 | 0.10 | 躁动状态 | + +--- + +### 5.5 愤怒情绪 (Angry) + +**适用场景:** 心率快速升高且趋势明显,HRV低,呼吸不规律,体动高 + +**计算公式:** +``` +Score_angry = 0.36 × σ(HR_norm, 2.0, 0.75) + + 0.20 × σ(HR_trend / 4, 2.0, 0.5) + + 0.25 × (1 - σ(HRV_norm, 2.0, 0.5)) + + 0.20 × (1 - RR_regularity) + + 0.10 × σ(Move_norm, 2.0, 0.7) +``` + +**权重分析:** +| 指标 | 权重 | 说明 | +|------|------|------| +| 心率偏离度 | 0.36 | 显著升高( sigmoid中心0.75) | +| 心率趋势 | 0.20 | 快速上升趋势 | +| HRV水平 | 0.25 | HRV低 | +| 呼吸规律性 | 0.20 | 不规律 | +| 体动水平 | 0.10 | 高活跃 | + +--- + +### 5.6 悲伤情绪 (Sad) + +**适用场景:** 心率偏低,呼吸浅慢,HRV低,体动低 + +**计算公式:** +``` +Score_sad = 0.40 × σ(-HR_norm, 2.0, 0.2) + + 0.15 × (1 - σ(HR_std / 4, 2.0, 0.3)) + + 0.28 × σ(-RR_norm, 2.0, 0.3) + + 0.10 × RR_regularity + + 0.10 × (1 - σ(HRV_norm, 2.0, 0.4)) + + 0.10 × (1 - σ(Move_norm, 2.0, 0.15)) +``` + +**权重分析:** +| 指标 | 权重 | 说明 | +|------|------|------| +| 心率偏离度 | 0.40 | 心率偏低(负偏离)| +| 心率稳定性 | 0.15 | 变化小 | +| 呼吸率偏离度 | 0.28 | 呼吸浅慢(负偏离)| +| 呼吸规律性 | 0.10 | 规律 | +| HRV水平 | 0.10 | HRV低 | +| 体动水平 | 0.10 | 低活动 | + +--- + +### 5.7 压力情绪 (Stressed) + +**适用场景:** 心率升高,HRV低,压力指数高,呼吸不规律 + +**计算公式:** +``` +Score_stressed = 0.36 × σ(HR_norm, 2.0, 0.5) + + 0.20 × σ(HR_trend / 3, 2.0, 0.5) + + 0.25 × (1 - σ(HRV_norm, 2.0, 0.4)) + + 0.10 × σ(HRV.stressIndex / 25, 2.0, 0.5) + + 0.16 × (1 - RR_regularity) + + 0.10 × G(Move_norm, 0.6, 0.25) +``` + +**权重分析:** +| 指标 | 权重 | 说明 | +|------|------|------| +| 心率偏离度 | 0.36 | 中度升高 | +| 心率趋势 | 0.20 | 上升趋势 | +| HRV水平 | 0.25 | HRV低(高权重)| +| 压力指数 | 0.10 | 压力指数高 | +| 呼吸规律性 | 0.16 | 不规律 | +| 体动水平 | 0.10 | 适中 | + +--- + +### 5.8 放松情绪 (Relaxed) + +**适用场景:** 心率偏低,变异适中,呼吸慢且规律,体动极低 + +**计算公式:** +``` +Score_relaxed = 0.40 × σ(-HR_norm, 2.0, 0.25) + + 0.22 × (1 - σ(HR_std / 8, 2.0, 0.4)) + + 0.16 × σ(-RR_norm, 2.0, 0.3) + + 0.10 × RR_regularity + + 0.10 × (1 - σ(Move_norm, 2.0, 0.15)) +``` + +**权重分析:** +| 指标 | 权重 | 说明 | +|------|------|------| +| 心率偏离度 | 0.40 | 心率偏低(负偏离)| +| 心率稳定性 | 0.22 | 稳定 | +| 呼吸率偏离度 | 0.16 | 呼吸慢(负偏离)| +| 呼吸规律性 | 0.10 | 规律 | +| 体动水平 | 0.10 | 极低活动 | + +--- + +## 六、次要情绪算法 + +### 6.1 计算原理 + +次要情绪是概率第二大的情绪类型,不具有独立的评分模型。 + +``` +secondaryEmotion = argmax_i (P_i),其中 i ≠ primaryEmotion +``` + +### 6.2 提取算法 + +```cpp +int secondIdx = 0; +float maxProb = emotionProbs[0], secondProb = 0; +maxIdx = 0; + +for (int i = 1; i < 9; i++) { + if (emotionProbs[i] > maxProb) { + secondProb = maxProb; + secondIdx = maxIdx; + maxProb = emotionProbs[i]; + maxIdx = i; + } else if (emotionProbs[i] > secondProb) { + secondProb = emotionProbs[i]; + secondIdx = i; + } +} + +result.primaryEmotion = (EmotionType)maxIdx; +result.secondaryEmotion = (EmotionType)secondIdx; +result.confidence = maxProb; +``` + +### 6.3 输出格式 + +```cpp +Serial.printf("主要情绪:%s (置信度: %.1f%%);\n", + EMOTION_NAMES[emotionResult.primaryEmotion], + emotionResult.confidence * 100); + +Serial.printf("次要情绪: %s倾向\n", + EMOTION_NAMES[emotionResult.secondaryEmotion]); + +Serial.printf("主要情绪得分:%.3f, 次要情绪得分:%.3f, 差值: %.3f\n", + emotionResult.confidence, + emotionResult.confidence * 0.8f, // 估算的次要得分 + emotionResult.confidence * 0.2f); +``` + +--- + +## 七、概率处理流程 + +### 7.1 归一化公式 + +```cpp +void SimpleEmotionAnalyzer::normalizeProbabilities() { + float sum = 0; + for (int i = 0; i < 9; i++) { + sum += emotionProbs[i]; + } + if (sum > 0) { + for (int i = 0; i < 9; i++) { + emotionProbs[i] /= sum; + } + } +} +``` + +**数学表达:** +``` +P_i = Score_i / Σ Score_j, j ∈ {0,1,2,...,8} +``` + +### 7.2 Top1放大机制 + +```cpp +// 找出最大概率索引 +int maxIdx = 0; +for (int i = 1; i < 9; i++) { + if (emotionProbs[i] > emotionProbs[maxIdx]) { + maxIdx = i; + } +} + +// Top1放大1.3倍 +emotionProbs[maxIdx] *= 1.3f; + +// 再次归一化 +normalizeProbabilities(); +``` + +**目的:** 强行制造"赢家",增强主要情绪的区分度 + +### 7.3 自适应平滑 + +```cpp +void SimpleEmotionAnalyzer::smoothProbabilities() { + for (int i = 0; i < 9; i++) { + float diff = fabs(emotionProbs[i] - prevProbs[i]); + float adaptiveAlpha = (diff > 0.2f) ? 0.6f : 0.25f; + + emotionProbs[i] = adaptiveAlpha * emotionProbs[i] + + (1.0f - adaptiveAlpha) * prevProbs[i]; + prevProbs[i] = emotionProbs[i]; + } +} +``` + +**数学表达:** +``` +P_i(t) = α × P_i(t) + (1-α) × P_i(t-1) + +其中: + α = 0.6, 当 |P_i(t) - P_i(t-1)| > 0.2(变化快 → 快响应) + α = 0.25, 当 |P_i(t) - P_i(t-1)| ≤ 0.2(变化慢 → 适度抑制) +``` + +--- + +## 八、情绪强度计算 + +### 8.1 计算公式 + +```cpp +float hrFactor = fabs(hrData.bpmSmoothed - baseline.hrResting) / 40.0f; +float hrvFactor = hrvData.isValid ? (1.0f - sigmoid(hrvData.rmssd, 0.02f, 40)) : 0.5f; +float rrFactor = rrData.isValid ? fabs(rrData.rateSmoothed - baseline.rrResting) / 10.0f : 0.3f; + +result.intensity = 0.4f + + 0.3f * constrain_value(hrFactor, 0.0f, 1.0f) + + 0.2f * constrain_value(hrvFactor, 0.0f, 1.0f) + + 0.1f * constrain_value(rrFactor, 0.0f, 1.0f); + +result.intensity = constrain_value(result.intensity, 0.3f, 1.0f); +``` + +### 8.2 公式表达 + +``` +intensity = clamp(0.4 + 0.3×clamp(hrFactor) + 0.2×clamp(hrvFactor) + 0.1×clamp(rrFactor), 0.3, 1.0) + +其中: + hrFactor = |HR - HR_baseline| / 40 + hrvFactor = 1 - σ(HRV_rmssd, 0.02, 40) + rrFactor = |RR - RR_baseline| / 10 +``` + +### 8.3 权重分析 + +| 因素 | 权重 | 说明 | +|------|------|------| +| 基线偏移 | 0.4 | 基础强度 | +| 心率偏移 | 0.3 | 心率偏离基线程度 | +| HRV偏离 | 0.2 | HRV偏离正常程度 | +| 呼吸偏移 | 0.1 | 呼吸偏离程度 | + +--- + +## 九、情绪维度计算 + +### 9.1 效价 (Valence) + +**定义:** 情绪的正面/负面倾向 + +```cpp +void SimpleEmotionAnalyzer::calculateDimensions(const HeartRateData& hr, + const RespirationData& rr) { + float positive = emotionProbs[EMOTION_HAPPY] + emotionProbs[EMOTION_EXCITED] + + emotionProbs[EMOTION_RELAXED] + emotionProbs[EMOTION_CALM]; + float negative = emotionProbs[EMOTION_ANXIOUS] + emotionProbs[EMOTION_ANGRY] + + emotionProbs[EMOTION_SAD] + emotionProbs[EMOTION_STRESSED]; + + lastResult.valence = (positive - negative); +} +``` + +**公式:** +``` +valence = (P_happy + P_excited + P_relaxed + P_calm) + - (P_anxious + P_angry + P_sad + P_stressed) + +范围:[-1, +1] + -1 = 完全负面情绪 + 0 = 中性 + +1 = 完全正面情绪 +``` + +### 9.2 唤醒度 (Arousal) + +**定义:** 情绪的激活程度 + +```cpp + float highArousal = emotionProbs[EMOTION_EXCITED] + emotionProbs[EMOTION_ANXIOUS] + + emotionProbs[EMOTION_ANGRY]; + float lowArousal = emotionProbs[EMOTION_CALM] + emotionProbs[EMOTION_RELAXED] + + emotionProbs[EMOTION_SAD]; + float total = highArousal + lowArousal; + + lastResult.arousal = total > 0 ? highArousal / total : 0.5f; +``` + +**公式:** +``` +arousal = (P_excited + P_anxious + P_angry) + / (P_excited + P_anxious + P_angry + P_calm + P_relaxed + P_sad) + +范围:[0, 1] + 0 = 低唤醒(平静、放松、悲伤) + 1 = 高唤醒(兴奋、焦虑、愤怒) +``` + +--- + +## 十、压力评估计算 + +### 10.1 压力水平 (Stress Level) + +```cpp +void SimpleEmotionAnalyzer::calculateStressLevels(const HeartRateData& hr, + const RespirationData& rr, + const HRVEstimate& hrv) { + float stressScore = 0; + + // HRV压力指数 + if (hrv.isValid) { + stressScore += 0.4f * sigmoid(hrv.stressIndex / 30.0f, 2.0f, 0.5f); + } + + // 心率偏离 + if (hr.isValid) { + float hrNorm = fabs(normalizeHR(hr.bpmSmoothed, baseline.hrResting)); + stressScore += 0.35f * sigmoid(hrNorm, 2.0f, 0.5f); + } + + // 呼吸不规律 + if (rr.isValid) { + float irregularity = 1.0f - rr.regularity; + stressScore += 0.25f * irregularity; + } + + result.stressLevel = constrain_value(stressScore * 100.0f, 0.0f, 100.0f); +} +``` + +### 10.2 焦虑水平 (Anxiety Level) + +```cpp + float anxietyScore = 0; + + if (hr.isValid) { + float hrNorm = normalizeHR(hr.bpmSmoothed, baseline.hrResting); + anxietyScore += 0.4f * sigmoid(hrNorm, 2.0f, 0.4f); + anxietyScore += 0.2f * (1.0f - sigmoid(hr.bpmStd / 6.0f, 2.0f, 0.5f)); + } + + if (hrv.isValid) { + anxietyScore += 0.25f * (1.0f - sigmoid(hrv.rmssd / 50.0f, 2.0f, 0.5f)); + } + + if (rr.isValid) { + anxietyScore += 0.15f * (1.0f - rr.regularity); + } + + result.anxietyLevel = constrain_value(anxietyScore * 100.0f, 0.0f, 100.0f); +``` + +### 10.3 放松水平 (Relaxation Level) + +```cpp + float relaxationScore = 0; + + if (hrv.isValid) { + relaxationScore += 0.45f * sigmoid(hrv.rmssd / 80.0f, 2.0f, 0.6f); + } + + if (hr.isValid) { + float hrNorm = -normalizeHR(hr.bpmSmoothed, baseline.hrResting); + relaxationScore += 0.30f * sigmoid(hrNorm, 2.0f, 0.25f); + relaxationScore += 0.15f * (1.0f - sigmoid(hr.bpmStd / 10.0f, 2.0f, 0.5f)); + } + + if (movement.isValid) { + relaxationScore += 0.10f * (1.0f - sigmoid(movement.movementSmoothed / 30.0f, + 2.0f, 0.5f)); + } + + result.relaxationLevel = constrain_value(relaxationScore * 100.0f, 0.0f, 100.0f); +``` + +--- + +## 十一、特殊处理规则 + +### 11.1 UNKNOWN 判断条件 + +```cpp +if (maxProb < 0.20f && (maxProb - secondProb) < 0.03f) { + result.primaryEmotion = EMOTION_UNKNOWN; + result.confidence = maxProb; +} +``` + +**触发条件(同时满足):** +1. 最大概率 < 0.20(低置信度) +2. 最大概率与第二大概率差值 < 0.03(多情绪接近) + +### 11.2 负面情绪合并 + +```cpp +if (result.primaryEmotion == EMOTION_ANXIOUS || + result.primaryEmotion == EMOTION_ANGRY || + result.primaryEmotion == EMOTION_STRESSED) { + + float combinedProb = emotionProbs[EMOTION_ANXIOUS] + + emotionProbs[EMOTION_ANGRY] + + emotionProbs[EMOTION_STRESSED]; + result.primaryEmotion = EMOTION_STRESSED; + result.confidence = max(combinedProb, + max(emotionProbs[EMOTION_ANXIOUS], + max(emotionProbs[EMOTION_ANGRY], + emotionProbs[EMOTION_STRESSED]))); +} +``` + +**逻辑:** 焦虑、愤怒、压力三种情绪合并为"压力"情绪 + +### 11.3 UNKNOWN 强制落地 + +```cpp +if (result.primaryEmotion == EMOTION_UNKNOWN) { + if (result.arousal > 0.6f) { + result.primaryEmotion = EMOTION_EXCITED; + } else if (result.valence < -0.2f) { + result.primaryEmotion = EMOTION_STRESSED; + } else { + result.primaryEmotion = EMOTION_CALM; + } +} +``` + +**规则:** +| 条件 | 映射结果 | +|------|----------| +| arousal > 0.6 | EXCITED(高唤醒→兴奋)| +| valence < -0.2 且 arousal ≤ 0.6 | STRESSED(负面→压力)| +| 其他 | CALM(默认平静)| + +--- + +## 十二、输出数据结构 + +```cpp +struct EmotionResult { + EmotionType primaryEmotion; // 主要情绪 + EmotionType secondaryEmotion; // 次要情绪 + float confidence; // 置信度 0-1 + float intensity; // 强度 0-1 + + float valence; // 效价 -1到+1 + float arousal; // 唤醒度 0-1 + + float stressLevel; // 压力水平 0-100 + float anxietyLevel; // 焦虑水平 0-100 + float relaxationLevel; // 放松水平 0-100 + + float sympatheticActivity; // 交感神经活动 + float parasympatheticActivity; // 副交感神经活动 + + bool isValid; // 数据有效性 + unsigned long timestamp; // 时间戳 +}; +``` + +--- + +## 十三、算法特点总结 + +| 特点 | 说明 | +|------|------| +| 多指标融合 | 综合心率、呼吸、HRV、体动4类数据 | +| 权重分配 | 不同情绪对各指标的权重不同 | +| 非线性函数 | 使用Sigmoid和Gaussian模拟生理反应 | +| 自适应平滑 | 根据变化速度动态调整平滑系数 | +| Top1放大 | 增强主要情绪区分度 | +| 负面情绪合并 | 焦虑/愤怒/压力统一为压力评估 | +| UNKNOWN处理 | 低置信度时标记为未知并强制落地 | diff --git a/extra_script.py b/extra_script.py new file mode 100644 index 0000000..3e527d8 --- /dev/null +++ b/extra_script.py @@ -0,0 +1,21 @@ +import os +import sys +from pathlib import Path + +Import("env") + +# 修复中文路径问题 +def fix_chinese_path(source, target, env): + project_dir = Path(env["PROJECT_DIR"]) + build_dir = Path(env["BUILD_DIR"]) + + # 确保构建目录存在 + build_dir.mkdir(parents=True, exist_ok=True) + + # 设置环境变量以支持UTF-8 + os.environ['PYTHONIOENCODING'] = 'utf-8' + + return None + +# 在构建前执行 +env.AddPreAction("buildprog", fix_chinese_path) diff --git a/github_guide.md b/github_guide.md new file mode 100644 index 0000000..c48aa95 Binary files /dev/null and b/github_guide.md differ diff --git a/platformio.ini b/platformio.ini index 83d4f5f..6a3f8c6 100644 --- a/platformio.ini +++ b/platformio.ini @@ -5,13 +5,17 @@ ; Library options: dependencies, extra library storages ; Advanced options: extra scripting ; -; Please visit documentation for the other options and examples +; Please visit documentation for other options and examples ; https://docs.platformio.org/page/projectconf.html [env:freenove_esp32_s3_wroom] platform = espressif32 board = freenove_esp32_s3_wroom framework = arduino -lib_deps = +extra_scripts = pre:extra_script.py +build_flags = + -Wl,-Map=/dev/null +lib_deps = bblanchon/ArduinoJson@^7.4.2 emelianov/modbus-esp8266@^4.1.0 + knolleary/PubSubClient@^2.8.0 diff --git a/src/config.h b/src/config.h new file mode 100644 index 0000000..a1b8360 --- /dev/null +++ b/src/config.h @@ -0,0 +1,193 @@ +/** + * @file config.h + * @brief ESP32情绪分析系统配置文件 + * @description 适配雷达传感器串口数据输入 + */ + +#ifndef CONFIG_H +#define CONFIG_H + +// ==================== 串口配置 ==================== +// 雷达传感器串口 (UART2) +#define RADAR_UART_NUM UART_NUM_2 +#define RADAR_RX_PIN 16 // 接收引脚 +#define RADAR_TX_PIN 17 // 发送引脚 +#define RADAR_BAUD_RATE 115200 // 波特率 + +// 调试串口 (USB) +#define DEBUG_UART_NUM UART_NUM_0 +#define DEBUG_BAUD_RATE 115200 + +// ==================== 数据协议配置 ==================== +// 支持的协议格式 +#define PROTOCOL_JSON 0 // JSON格式 +#define PROTOCOL_BINARY 1 // 二进制格式 +#define PROTOCOL_TEXT 2 // 文本格式 + +// 当前使用的协议 +#define CURRENT_PROTOCOL PROTOCOL_JSON + +// JSON数据字段名 (根据您的雷达协议修改) +#define JSON_FIELD_HEART_RATE "heart_rate" +#define JSON_FIELD_RESPIRATION "respiration_rate" +#define JSON_FIELD_HR_QUALITY "hr_quality" +#define JSON_FIELD_RR_QUALITY "rr_quality" + +// 文本格式示例: "HR:72,RR:16\n" +#define TEXT_HR_PREFIX "HR:" +#define TEXT_RR_PREFIX "RR:" + +// 二进制协议帧头帧尾 +#define BINARY_FRAME_HEADER 0xAA +#define BINARY_FRAME_TAIL 0x55 + +// ==================== 采样配置 ==================== +#define SAMPLE_RATE_HZ 50 // 数据更新率 50Hz +#define SAMPLE_INTERVAL_MS 20 // 采样间隔 20ms + +// 数据缓冲大小 +#define RX_BUFFER_SIZE 512 // 串口接收缓冲 +#define DATA_BUFFER_SIZE 300 // 数据存储缓冲 + +// ==================== 心率参数 ==================== +// 正常心率范围 +#define HR_MIN_NORMAL 40 // 最低正常心率 BPM +#define HR_MAX_NORMAL 200 // 最高正常心率 BPM +#define HR_RESTING_MIN 50 // 静息心率下限 +#define HR_RESTING_MAX 90 // 静息心率上限 + +// 异常检测阈值 +#define HR_SUDDEN_CHANGE 30 // 突然变化阈值 BPM +#define HR_STABLE_WINDOW 10 // 稳定性检测窗口(秒) + +// ==================== 呼吸参数 ==================== +// 正常呼吸频率范围 (次/分钟) +#define RR_MIN_NORMAL 8 // 最低正常呼吸频率 +#define RR_MAX_NORMAL 30 // 最高正常呼吸频率 +#define RR_RESTING_MIN 12 // 静息呼吸频率下限 +#define RR_RESTING_MAX 20 // 静息呼吸频率上限 + +// 异常检测阈值 +#define RR_SUDDEN_CHANGE 10 // 突然变化阈值 +#define RR_STABLE_WINDOW 15 // 稳定性检测窗口(秒) + +// ==================== HRV参数 ==================== +// HRV分析窗口 +#define HRV_WINDOW_BEATS 50 // HRV分析所需心跳数 +#define HRV_MIN_BEATS 10 // 最小心跳数要求 +#define HRV_ESTIMATION_WINDOW 60 // HRV估算窗口(秒) + +// HRV指标范围 (RMSSD, ms) +#define HRV_VERY_LOW 20 // 极低HRV +#define HRV_LOW 50 // 低HRV +#define HRV_NORMAL 100 // 正常HRV +#define HRV_HIGH 150 // 高HRV + +// ==================== 情绪分类阈值 ==================== +// 基于心率的情绪阈值 +#define EMOTION_HR_LOW 60 // 低心率阈值 +#define EMOTION_HR_NORMAL 80 // 正常心率阈值 +#define EMOTION_HR_ELEVATED 100 // 升高心率阈值 +#define EMOTION_HR_HIGH 120 // 高心率阈值 + +// 基于呼吸的情绪阈值 +#define EMOTION_RR_SLOW 10 // 缓慢呼吸 +#define EMOTION_RR_NORMAL 16 // 正常呼吸 +#define EMOTION_RR_FAST 22 // 快速呼吸 + +// HRV情绪阈值 +#define EMOTION_HRV_LOW 30 // 低HRV (压力) +#define EMOTION_HRV_NORMAL 60 // 正常HRV +#define EMOTION_HRV_HIGH 100 // 高HRV (放松) + +// ==================== 数据平滑参数 ==================== +// 指数移动平均系数 +#define EMA_ALPHA_HR 0.15f // 心率平滑系数 +#define EMA_ALPHA_RR 0.10f // 呼吸平滑系数 + +// 异常值过滤 +#define OUTLIER_FILTER_ENABLED true // 启用异常值过滤 +#define OUTLIER_THRESHOLD 3.0f // 异常值标准差倍数 + +// ==================== 情绪定义 ==================== +enum EmotionType { + EMOTION_CALM = 0, // 平静 + EMOTION_HAPPY, // 快乐 + EMOTION_EXCITED, // 兴奋 + EMOTION_ANXIOUS, // 焦虑 + EMOTION_ANGRY, // 愤怒 + EMOTION_SAD, // 悲伤 + EMOTION_STRESSED, // 压力 + EMOTION_RELAXED, // 放松 + EMOTION_UNKNOWN // 未知 +}; + +// 情绪名称字符串 +static const char* EMOTION_NAMES[] = { + "平静", + "快乐", + "兴奋", + "焦虑", + "愤怒", + "悲伤", + "压力", + "放松", + "未知" +}; + +// 情绪描述 +static const char* EMOTION_DESCRIPTIONS[] = { + "心情平静,情绪稳定", + "心情愉悦,积极正向", + "情绪高涨,充满活力", + "感到焦虑或不安", + "情绪激动,可能有愤怒", + "情绪低落,可能感到悲伤", + "压力较大,需要注意休息", + "身心放松,状态良好", + "情绪状态未知" +}; + +// 情绪建议 +static const char* EMOTION_SUGGESTIONS[] = { + "状态良好,继续保持", + "心情不错,享受当下", + "精力充沛,注意适当调节", + "建议深呼吸放松,或进行冥想", + "建议冷静下来,可以尝试深呼吸", + "可以尝试与朋友交流,或做些喜欢的事", + "建议休息放松,避免过度工作", + "状态很好,继续保持", + "" +}; + +// ==================== 调试配置 ==================== +#define DEBUG_SERIAL true // 启用串口调试 +#define DEBUG_VERBOSE false // 详细调试信息 +#define DEBUG_PROTOCOL true // 显示协议解析信息 + +// ==================== 工具函数宏 ==================== +#define constrain_value(x, min, max) ((x) < (min) ? (min) : ((x) > (max) ? (max) : (x))) +#define map_value(x, in_min, in_max, out_min, out_max) \ + (((x) - (in_min)) * ((out_max) - (out_min)) / ((in_max) - (in_min)) + (out_min)) + +// 调试宏 +#if DEBUG_SERIAL + #define DEBUG_PRINT(x) Serial.print(x) + #define DEBUG_PRINTLN(x) Serial.println(x) + #define DEBUG_PRINTF(fmt, ...) Serial.printf(fmt, ##__VA_ARGS__) +#else + #define DEBUG_PRINT(x) + #define DEBUG_PRINTLN(x) + #define DEBUG_PRINTF(fmt, ...) +#endif + +#if DEBUG_VERBOSE + #define VERBOSE_PRINT(x) Serial.print(x) + #define VERBOSE_PRINTLN(x) Serial.println(x) +#else + #define VERBOSE_PRINT(x) + #define VERBOSE_PRINTLN(x) +#endif + +#endif // CONFIG_H diff --git a/src/data_processor.cpp b/src/data_processor.cpp new file mode 100644 index 0000000..fca1f6c --- /dev/null +++ b/src/data_processor.cpp @@ -0,0 +1,356 @@ +/** + * @file data_processor.cpp + * @brief 数据处理模块实现 + */ + +#include "data_processor.h" +#include + +// ==================== 心率数据处理器实现 ==================== + +HeartRateProcessor::HeartRateProcessor(int histSize) + : historySize(histSize), historyIndex(0), historyCount(0), + lastSmoothed(72), alpha(EMA_ALPHA_HR), bpmSum(0), bpmSumSq(0), + lastValidBpm(72), lastValidTime(0) { + bpmHistory = new float[historySize]; + memset(bpmHistory, 0, historySize * sizeof(float)); +} + +HeartRateProcessor::~HeartRateProcessor() { + delete[] bpmHistory; +} + +void HeartRateProcessor::addData(float bpm, float confidence) { + // 数据验证 + if (bpm < HR_MIN_NORMAL || bpm > HR_MAX_NORMAL) { + return; + } + + // 突变检测 + if (historyCount > 0) { + float lastBpm = bpmHistory[(historyIndex - 1 + historySize) % historySize]; + if (fabs(bpm - lastBpm) > HR_SUDDEN_CHANGE) { + // 可能是异常值,进行平滑处理 + bpm = lastSmoothed + (bpm - lastSmoothed) * 0.3f; + } + } + + // 存储数据 + bpmHistory[historyIndex] = bpm; + historyIndex = (historyIndex + 1) % historySize; + if (historyCount < historySize) historyCount++; + + // 更新统计 + bpmSum += bpm; + bpmSumSq += bpm * bpm; + + // 平滑处理 + lastSmoothed = alpha * bpm + (1.0f - alpha) * lastSmoothed; + + lastValidBpm = bpm; + lastValidTime = millis(); +} + +HeartRateData HeartRateProcessor::getData() { + HeartRateData data; + memset(&data, 0, sizeof(data)); + + if (historyCount == 0) { + data.isValid = false; + return data; + } + + // 当前值 + data.bpm = bpmHistory[(historyIndex - 1 + historySize) % historySize]; + data.bpmSmoothed = lastSmoothed; + + // 计算均值 + data.bpmMean = bpmSum / historyCount; + + // 计算标准差 + float variance = (bpmSumSq / historyCount) - (data.bpmMean * data.bpmMean); + data.bpmStd = variance > 0 ? sqrt(variance) : 0; + + // 计算最值 + data.bpmMin = 300; + data.bpmMax = 0; + for (int i = 0; i < historyCount; i++) { + if (bpmHistory[i] < data.bpmMin) data.bpmMin = bpmHistory[i]; + if (bpmHistory[i] > data.bpmMax) data.bpmMax = bpmHistory[i]; + } + + // 计算趋势 + data.trend = calculateTrend(); + + // 计算变异性 + data.variability = calculateVariability(); + + // 数据质量评估 + if (millis() - lastValidTime < 3000) { + data.quality = 0.8f + 0.2f * (1.0f - data.bpmStd / 30.0f); + data.quality = constrain_value(data.quality, 0.0f, 1.0f); + } else { + data.quality = 0.3f; + } + + data.isValid = true; + data.timestamp = millis(); + + return data; +} + +HRVEstimate HeartRateProcessor::estimateHRV() { + HRVEstimate hrv; + memset(&hrv, 0, sizeof(hrv)); + + if (historyCount < 10) { + hrv.isValid = false; + return hrv; + } + + // 从心率变异性估算HRV + // 这是一个近似方法,真实的HRV需要RR间期数据 + + // 估算RMSSD:基于心率标准差 + HeartRateData hrData = getData(); + float hrVariability = hrData.bpmStd; + + // 经验公式:RMSSD ≈ 心率标准差 * 某个系数 + // 这个系数需要根据实际情况调整 + hrv.rmssd = hrVariability * 8.0f; // 经验系数 + + // 估算SDNN + hrv.sdnn = hrVariability * 10.0f; + + // 压力指数 + if (hrv.rmssd > 0) { + hrv.stressIndex = 1000.0f / hrv.rmssd; + } else { + hrv.stressIndex = 50; + } + + // 自主神经平衡(使用 sigmoid 归一化,正常范围 0.3~0.7) + // rmssd 正常值:20-50ms,映射到 autonomicBalance 0.3-0.7 + if (hrv.rmssd > 0) { + float normalized = (hrv.rmssd - 20.0f) / 30.0f; // 归一化到 0~1 + hrv.autonomicBalance = 0.3f + 0.4f * constrain_value(normalized, 0.0f, 1.0f); + } else { + hrv.autonomicBalance = 0.5f; // 默认中性值 + } + + hrv.isValid = true; + return hrv; +} + +void HeartRateProcessor::reset() { + historyIndex = 0; + historyCount = 0; + lastSmoothed = 72; + bpmSum = 0; + bpmSumSq = 0; + memset(bpmHistory, 0, historySize * sizeof(float)); +} + +float HeartRateProcessor::calculateVariability() { + if (historyCount < 3) return 0; + + // 计算相邻值差异的均方根 + float sumSqDiff = 0; + int count = 0; + + for (int i = 1; i < historyCount; i++) { + int idx1 = (historyIndex - i - 1 + historySize) % historySize; + int idx2 = (historyIndex - i + historySize) % historySize; + float diff = bpmHistory[idx1] - bpmHistory[idx2]; + sumSqDiff += diff * diff; + count++; + } + + return count > 0 ? sqrt(sumSqDiff / count) : 0; +} + +float HeartRateProcessor::calculateTrend() { + if (historyCount < 10) return 0; + + // 计算前半和后半的均值差 + int half = historyCount / 2; + float firstHalf = 0, secondHalf = 0; + + for (int i = 0; i < half; i++) { + int idx = (historyIndex - historyCount + i + historySize) % historySize; + firstHalf += bpmHistory[idx]; + } + firstHalf /= half; + + for (int i = historyCount - half; i < historyCount; i++) { + int idx = (historyIndex - historyCount + i + historySize) % historySize; + secondHalf += bpmHistory[idx]; + } + secondHalf /= half; + + return secondHalf - firstHalf; +} + +// ==================== 呼吸数据处理器实现 ==================== + +RespirationProcessor::RespirationProcessor(int histSize) + : historySize(histSize), historyIndex(0), historyCount(0), + lastSmoothed(16), alpha(EMA_ALPHA_RR), lastValidRate(16), lastValidTime(0) { + rateHistory = new float[historySize]; + memset(rateHistory, 0, historySize * sizeof(float)); +} + +RespirationProcessor::~RespirationProcessor() { + delete[] rateHistory; +} + +void RespirationProcessor::addData(float rate, float confidence) { + // 数据验证 + if (rate < RR_MIN_NORMAL || rate > RR_MAX_NORMAL) { + return; + } + + // 突变检测 + if (historyCount > 0) { + float lastRate = rateHistory[(historyIndex - 1 + historySize) % historySize]; + if (fabs(rate - lastRate) > RR_SUDDEN_CHANGE) { + rate = lastSmoothed + (rate - lastSmoothed) * 0.3f; + } + } + + // 存储数据 + rateHistory[historyIndex] = rate; + historyIndex = (historyIndex + 1) % historySize; + if (historyCount < historySize) historyCount++; + + // 平滑处理 + lastSmoothed = alpha * rate + (1.0f - alpha) * lastSmoothed; + + lastValidRate = rate; + lastValidTime = millis(); +} + +RespirationData RespirationProcessor::getData() { + RespirationData data; + memset(&data, 0, sizeof(data)); + + if (historyCount == 0) { + data.isValid = false; + return data; + } + + // 当前值 + data.rate = rateHistory[(historyIndex - 1 + historySize) % historySize]; + data.rateSmoothed = lastSmoothed; + + // 计算均值 + float sum = 0; + for (int i = 0; i < historyCount; i++) { + sum += rateHistory[i]; + } + data.rateMean = sum / historyCount; + + // 计算标准差 + float sumSq = 0; + for (int i = 0; i < historyCount; i++) { + float diff = rateHistory[i] - data.rateMean; + sumSq += diff * diff; + } + data.rateStd = sqrt(sumSq / historyCount); + + // 计算规律性 + data.regularity = calculateRegularity(); + + // 计算变异性 + data.variability = calculateVariability(); + + // 数据质量评估 + if (millis() - lastValidTime < 5000) { + data.quality = 0.7f + 0.3f * data.regularity; + data.quality = constrain_value(data.quality, 0.0f, 1.0f); + } else { + data.quality = 0.3f; + } + + data.isValid = true; + data.timestamp = millis(); + + return data; +} + +void RespirationProcessor::reset() { + historyIndex = 0; + historyCount = 0; + lastSmoothed = 16; + memset(rateHistory, 0, historySize * sizeof(float)); +} + +float RespirationProcessor::calculateRegularity() { + if (historyCount < 5) return 0.8f; + + // 计算变异系数 + float sum = 0; + for (int i = 0; i < historyCount; i++) { + sum += rateHistory[i]; + } + float mean = sum / historyCount; + + float sumSq = 0; + for (int i = 0; i < historyCount; i++) { + float diff = rateHistory[i] - mean; + sumSq += diff * diff; + } + float std = sqrt(sumSq / historyCount); + + float cv = (mean > 0) ? std / mean : 0; + + // 转换为规律性(CV越小,规律性越高) + return constrain_value(1.0f - cv * 3.0f, 0.0f, 1.0f); +} + +float RespirationProcessor::calculateVariability() { + if (historyCount < 3) return 0; + + float sumDiff = 0; + for (int i = 1; i < historyCount; i++) { + int idx1 = (historyIndex - i - 1 + historySize) % historySize; + int idx2 = (historyIndex - i + historySize) % historySize; + sumDiff += fabs(rateHistory[idx1] - rateHistory[idx2]); + } + + return sumDiff / (historyCount - 1); +} + +// ==================== 综合数据处理器实现 ==================== + +PhysioDataProcessor::PhysioDataProcessor() { + hrProcessor = new HeartRateProcessor(100); + rrProcessor = new RespirationProcessor(50); +} + +PhysioDataProcessor::~PhysioDataProcessor() { + delete hrProcessor; + delete rrProcessor; +} + +void PhysioDataProcessor::update(float hr, float rr, float hrConf, float rrConf) { + hrProcessor->addData(hr, hrConf); + rrProcessor->addData(rr, rrConf); +} + +HeartRateData PhysioDataProcessor::getHeartRateData() { + return hrProcessor->getData(); +} + +RespirationData PhysioDataProcessor::getRespirationData() { + return rrProcessor->getData(); +} + +HRVEstimate PhysioDataProcessor::getHRVEstimate() { + return hrProcessor->estimateHRV(); +} + +void PhysioDataProcessor::reset() { + hrProcessor->reset(); + rrProcessor->reset(); +} diff --git a/src/data_processor.h b/src/data_processor.h new file mode 100644 index 0000000..45afb96 --- /dev/null +++ b/src/data_processor.h @@ -0,0 +1,174 @@ +/** + * @file data_processor.h + * @brief 数据处理模块头文件 + * @description 处理雷达传感器提供的心率和呼吸数据 + */ + +#ifndef DATA_PROCESSOR_H +#define DATA_PROCESSOR_H + +#include +#include "config.h" + +// ==================== 心率数据结构 ==================== + +/** + * @brief 心率处理后的数据 + */ +struct HeartRateData { + float bpm; // 当前心率 BPM + float bpmSmoothed; // 平滑后心率 + float bpmMean; // 平均心率 + float bpmStd; // 心率标准差 + float bpmMin; // 最小心率 + float bpmMax; // 最大心率 + float trend; // 趋势(上升/下降) + float variability; // 心率变异性 + float quality; // 数据质量 0-1 + bool isValid; // 数据是否有效 + unsigned long timestamp; // 时间戳 +}; + +/** + * @brief 呼吸处理后的数据 + */ +struct RespirationData { + float rate; // 呼吸频率 次/分钟 + float rateSmoothed; // 平滑后呼吸频率 + float rateMean; // 平均呼吸频率 + float rateStd; // 呼吸频率标准差 + float regularity; // 呼吸规律性 0-1 + float variability; // 呼吸变异性 + float quality; // 数据质量 0-1 + bool isValid; // 数据是否有效 + unsigned long timestamp; // 时间戳 +}; + +/** + * @brief HRV估算数据 + */ +struct HRVEstimate { + float rmssd; // 估算的RMSSD + float sdnn; // 估算的SDNN + float stressIndex; // 压力指数 + float autonomicBalance; // 自主神经平衡 + bool isValid; // 是否有效 +}; + +// ==================== 心率数据处理器 ==================== + +/** + * @brief 心率数据处理器 + */ +class HeartRateProcessor { +private: + // 历史数据缓冲 + float* bpmHistory; + int historySize; + int historyIndex; + int historyCount; + + // 平滑滤波 + float lastSmoothed; + float alpha; + + // 统计数据 + float bpmSum; + float bpmSumSq; + + // 上一次有效值 + float lastValidBpm; + unsigned long lastValidTime; + +public: + HeartRateProcessor(int histSize = 100); + ~HeartRateProcessor(); + + // 添加新的心率数据 + void addData(float bpm, float confidence = 80); + + // 获取处理后的数据 + HeartRateData getData(); + + // 计算HRV估算 + HRVEstimate estimateHRV(); + + // 重置 + void reset(); + +private: + void updateStatistics(); + float calculateVariability(); + float calculateTrend(); +}; + +// ==================== 呼吸数据处理器 ==================== + +/** + * @brief 呼吸数据处理器 + */ +class RespirationProcessor { +private: + // 历史数据缓冲 + float* rateHistory; + int historySize; + int historyIndex; + int historyCount; + + // 平滑滤波 + float lastSmoothed; + float alpha; + + // 上一次有效值 + float lastValidRate; + unsigned long lastValidTime; + +public: + RespirationProcessor(int histSize = 50); + ~RespirationProcessor(); + + // 添加新的呼吸数据 + void addData(float rate, float confidence = 80); + + // 获取处理后的数据 + RespirationData getData(); + + // 重置 + void reset(); + +private: + float calculateRegularity(); + float calculateVariability(); +}; + +// ==================== 综合数据处理 ==================== + +/** + * @brief 生理数据综合处理器 + */ +class PhysioDataProcessor { +private: + HeartRateProcessor* hrProcessor; + RespirationProcessor* rrProcessor; + +public: + PhysioDataProcessor(); + ~PhysioDataProcessor(); + + // 更新数据 + void update(float hr, float rr, float hrConf = 80, float rrConf = 80); + + // 获取心率数据 + HeartRateData getHeartRateData(); + + // 获取呼吸数据 + RespirationData getRespirationData(); + + // 获取HRV估算 + HRVEstimate getHRVEstimate(); + + // 重置 + void reset(); +}; + +#endif // DATA_PROCESSOR_H diff --git a/src/emotion_analyzer_simple.cpp b/src/emotion_analyzer_simple.cpp new file mode 100644 index 0000000..8b66d18 --- /dev/null +++ b/src/emotion_analyzer_simple.cpp @@ -0,0 +1,838 @@ +/** + * @file emotion_analyzer_simple.cpp + * @brief 简化版情绪分析器实现 + */ + +#include "emotion_analyzer_simple.h" +#include + +// ==================== 情绪分析器实现 ==================== + +SimpleEmotionAnalyzer::SimpleEmotionAnalyzer(int histSize) + : smoothingFactor(0.15f), historySize(histSize), historyIndex(0), historyCount(0) { + + memset(emotionProbs, 0, sizeof(emotionProbs)); + memset(prevProbs, 0, sizeof(prevProbs)); + memset(hrWindow, 0, sizeof(hrWindow)); + memset(rrWindow, 0, sizeof(rrWindow)); + memset(hrvWindow, 0, sizeof(hrvWindow)); + windowIndex = 0; + windowCount = 0; + emotionHistory = new EmotionType[historySize]; + memset(emotionHistory, 0, historySize * sizeof(EmotionType)); + + memset(&baseline, 0, sizeof(baseline)); + baseline.hrResting = 72; + baseline.rrResting = 16; + baseline.isColdStarting = true; + baseline.coldStartHrSum = 0; + baseline.coldStartRrSum = 0; + baseline.coldStartCount = 0; + + memset(&lastResult, 0, sizeof(lastResult)); +} + +SimpleEmotionAnalyzer::~SimpleEmotionAnalyzer() { + delete[] emotionHistory; +} + +EmotionResult SimpleEmotionAnalyzer::analyze(const HeartRateData& hrData, + const RespirationData& rrData, + const HRVEstimate& hrvData, + const BodyMovementData& movementData) { + EmotionResult result; + memset(&result, 0, sizeof(result)); + result.timestamp = millis(); + + // 检查数据有效性 + if (!hrData.isValid && !rrData.isValid) { + result.isValid = false; + return result; + } + + // 时间窗口平滑(减少抖动) + float smoothedHR = getSmoothedHR(hrData); + float smoothedRR = getSmoothedRR(rrData); + float smoothedHRV = getSmoothedHRV(hrvData); + + // 统一推进窗口索引(确保 HR/RR/HRV 同步) + windowIndex = (windowIndex + 1) % WINDOW_SIZE; + if (windowCount < WINDOW_SIZE - 1) windowCount++; + + HeartRateData smoothHrData = hrData; + if (hrData.isValid) { + smoothHrData.bpmSmoothed = smoothedHR; + } + + RespirationData smoothRrData = rrData; + if (rrData.isValid) { + smoothRrData.rateSmoothed = smoothedRR; + } + + HRVEstimate smoothHrvData = hrvData; + if (hrvData.isValid) { + smoothHrvData.rmssd = smoothedHRV; + } + + // 计算各情绪得分 + emotionProbs[EMOTION_CALM] = calculateCalmScore(smoothHrData, smoothRrData, smoothHrvData, movementData); + emotionProbs[EMOTION_HAPPY] = calculateHappyScore(smoothHrData, smoothRrData, smoothHrvData, movementData); + emotionProbs[EMOTION_EXCITED] = calculateExcitedScore(smoothHrData, smoothRrData, smoothHrvData, movementData); + emotionProbs[EMOTION_ANXIOUS] = calculateAnxiousScore(smoothHrData, smoothRrData, smoothHrvData, movementData); + emotionProbs[EMOTION_ANGRY] = calculateAngryScore(smoothHrData, smoothRrData, smoothHrvData, movementData); + emotionProbs[EMOTION_SAD] = calculateSadScore(smoothHrData, smoothRrData, smoothHrvData, movementData); + emotionProbs[EMOTION_STRESSED] = calculateStressedScore(smoothHrData, smoothRrData, smoothHrvData, movementData); + emotionProbs[EMOTION_RELAXED] = calculateRelaxedScore(smoothHrData, smoothRrData, smoothHrvData, movementData); + emotionProbs[EMOTION_UNKNOWN] = 0.02f; + + // 归一化 + normalizeProbabilities(); + + // Top1 放大(强行制造赢家) + int maxIdx = 0; + for (int i = 1; i < 9; i++) { + if (emotionProbs[i] > emotionProbs[maxIdx]) { + maxIdx = i; + } + } + emotionProbs[maxIdx] *= 1.3f; + + // 再次归一化 + normalizeProbabilities(); + + smoothProbabilities(); + + // 找出主要情绪(同时找第二大概率) + int secondIdx = 0; + float maxProb = emotionProbs[0], secondProb = 0; + maxIdx = 0; // 重置 maxIdx + for (int i = 1; i < 9; i++) { + if (emotionProbs[i] > maxProb) { + secondProb = maxProb; + secondIdx = maxIdx; + maxProb = emotionProbs[i]; + maxIdx = i; + } else if (emotionProbs[i] > secondProb) { + secondProb = emotionProbs[i]; + secondIdx = i; + } + } + + result.primaryEmotion = (EmotionType)maxIdx; + result.secondaryEmotion = (EmotionType)secondIdx; + result.confidence = maxProb; + + // 智能 UNKNOWN 判断:低置信度 且 多情绪接近(改为 AND) + if (maxProb < 0.20f && (maxProb - secondProb) < 0.03f) { + result.primaryEmotion = EMOTION_UNKNOWN; + result.confidence = maxProb; + } + + if (result.primaryEmotion == EMOTION_ANXIOUS || + result.primaryEmotion == EMOTION_ANGRY || + result.primaryEmotion == EMOTION_STRESSED) { + + float combinedProb = emotionProbs[EMOTION_ANXIOUS] + + emotionProbs[EMOTION_ANGRY] + + emotionProbs[EMOTION_STRESSED]; + result.primaryEmotion = EMOTION_STRESSED; + result.confidence = max(combinedProb, + max(emotionProbs[EMOTION_ANXIOUS], + max(emotionProbs[EMOTION_ANGRY], + emotionProbs[EMOTION_STRESSED]))); + } + + float hrFactor = fabs(hrData.bpmSmoothed - baseline.hrResting) / 40.0f; + float hrvFactor = hrvData.isValid ? (1.0f - sigmoid(hrvData.rmssd, 0.02f, 40)) : 0.5f; + float rrFactor = rrData.isValid ? fabs(rrData.rateSmoothed - baseline.rrResting) / 10.0f : 0.3f; + + result.intensity = 0.4f + + 0.3f * constrain_value(hrFactor, 0.0f, 1.0f) + + 0.2f * constrain_value(hrvFactor, 0.0f, 1.0f) + + 0.1f * constrain_value(rrFactor, 0.0f, 1.0f); + result.intensity = constrain_value(result.intensity, 0.3f, 1.0f); + + // 计算情绪维度 + calculateDimensions(hrData, rrData); + result.valence = lastResult.valence; + result.arousal = lastResult.arousal; + + // 强制分类:UNKNOWN 必须落地(根据 arousal/valence 强制选择) + if (result.primaryEmotion == EMOTION_UNKNOWN) { + if (result.arousal > 0.6f) { + result.primaryEmotion = EMOTION_EXCITED; + } else if (result.valence < -0.2f) { + result.primaryEmotion = EMOTION_STRESSED; + } else { + result.primaryEmotion = EMOTION_CALM; + } + } + + // 计算压力水平 + calculateStressLevels(hrData, rrData, hrvData); + result.stressLevel = lastResult.stressLevel; + result.anxietyLevel = lastResult.anxietyLevel; + result.relaxationLevel = lastResult.relaxationLevel; + + // 自主神经活动 + if (hrvData.isValid) { + result.sympatheticActivity = 1.0f - hrvData.autonomicBalance; + result.parasympatheticActivity = hrvData.autonomicBalance; + } else { + result.sympatheticActivity = 0.5f; + result.parasympatheticActivity = 0.5f; + } + + result.isValid = true; + + // 记录历史 + emotionHistory[historyIndex] = result.primaryEmotion; + historyIndex = (historyIndex + 1) % historySize; + if (historyCount < historySize) historyCount++; + + lastResult = result; + return result; +} + +void SimpleEmotionAnalyzer::setBaseline(const UserBaseline& bl) { + baseline = bl; +} + +void SimpleEmotionAnalyzer::calibrateBaseline(const HeartRateData& hrData, + const RespirationData& rrData, + const BodyMovementData& movementData) { + bool isRestingState = true; + + if (movementData.isValid && movementData.movementSmoothed > 30) { + isRestingState = false; + } + + if (hrData.isValid && hrData.bpmStd > 8.0f) { + isRestingState = false; + } + + if (!isRestingState) return; + + // 冷启动阶段:收集前30秒数据用于初始化基线 + if (baseline.isColdStarting && baseline.coldStartCount < UserBaseline::COLD_START_SAMPLES) { + if (hrData.isValid) { + baseline.coldStartHrSum += hrData.bpmSmoothed; + } + if (rrData.isValid) { + baseline.coldStartRrSum += rrData.rateSmoothed; + } + baseline.coldStartCount++; + + // 冷启动完成:使用平均值初始化基线 + if (baseline.coldStartCount >= UserBaseline::COLD_START_SAMPLES) { + if (hrData.isValid && baseline.coldStartHrSum > 0) { + baseline.hrResting = baseline.coldStartHrSum / baseline.coldStartCount; + } + if (rrData.isValid && baseline.coldStartRrSum > 0) { + baseline.rrResting = baseline.coldStartRrSum / baseline.coldStartCount; + } + baseline.isColdStarting = false; + baseline.isCalibrated = true; + baseline.calibrationTime = millis(); + } + return; // 冷启动期间不进行正常校准 + } + + // 正常校准模式(指数移动平均) + if (hrData.isValid) { + baseline.hrResting = baseline.hrResting * 0.7f + hrData.bpmSmoothed * 0.3f; + if (hrData.bpmMin > 0 && hrData.bpmMin < baseline.hrMin) { + baseline.hrMin = hrData.bpmMin; + } + if (hrData.bpmMax > baseline.hrMax) { + baseline.hrMax = hrData.bpmMax; + } + } + + if (rrData.isValid) { + baseline.rrResting = baseline.rrResting * 0.7f + rrData.rateSmoothed * 0.3f; + } + + baseline.isCalibrated = true; + baseline.calibrationTime = millis(); +} + +EmotionType SimpleEmotionAnalyzer::getRecentDominantEmotion(int seconds) { + int counts[9] = {0}; + int entries = min(historyCount, (int)(seconds / 2)); // 假设每2秒一个记录 + + for (int i = 0; i < entries; i++) { + int idx = (historyIndex - 1 - i + historySize) % historySize; + counts[emotionHistory[idx]]++; + } + + int maxCount = 0; + EmotionType dominant = EMOTION_CALM; + for (int i = 0; i < 9; i++) { + if (counts[i] > maxCount) { + maxCount = counts[i]; + dominant = (EmotionType)i; + } + } + + return dominant; +} + +void SimpleEmotionAnalyzer::reset() { + memset(emotionProbs, 0, sizeof(emotionProbs)); + memset(prevProbs, 0, sizeof(prevProbs)); + memset(hrWindow, 0, sizeof(hrWindow)); + memset(rrWindow, 0, sizeof(rrWindow)); + memset(hrvWindow, 0, sizeof(hrvWindow)); + windowIndex = 0; + windowCount = 0; + memset(emotionHistory, 0, historySize * sizeof(EmotionType)); + historyIndex = 0; + historyCount = 0; + memset(&lastResult, 0, sizeof(lastResult)); +} + +// ==================== 情绪规则匹配 ==================== + +float SimpleEmotionAnalyzer::calculateCalmScore(const HeartRateData& hr, + const RespirationData& rr, + const HRVEstimate& hrv, + const BodyMovementData& movement) { + float score = 0; + + // 心率接近基线,变化小 + if (hr.isValid) { + float hrNorm = fabs(normalizeHR(hr.bpmSmoothed, baseline.hrResting)); + float hrScore = gaussian(hrNorm, 0, 0.3f); + float stabilityScore = 1.0f - constrain_value(hr.bpmStd / 12.0f, 0.0f, 1.0f); + score += 0.45f * hrScore + 0.22f * stabilityScore; + } + + // 呼吸规律,正常频率 + if (rr.isValid) { + float rrNorm = fabs(normalizeRR(rr.rateSmoothed, baseline.rrResting)); + float rateScore = gaussian(rrNorm, 0, 0.5f); + float regularityScore = rr.regularity; + score += 0.27f * rateScore + 0.16f * regularityScore; + } + + // HRV正常或较高 + if (hrv.isValid) { + float hrvNorm = normalizeHRV(hrv.rmssd); + float hrvScore = sigmoid(hrvNorm, 2.0f, 0.7f); + score += 0.10f * hrvScore; + } + + // 体动低或适中(平静状态) + if (movement.isValid) { + float moveNorm = normalizeMovement(movement.movementSmoothed); + float movementScore = gaussian(moveNorm, 0.15f, 0.2f); + score += 0.08f * movementScore; + } + + return constrain_value(score, 0.0f, 1.0f); +} + +float SimpleEmotionAnalyzer::calculateHappyScore(const HeartRateData& hr, + const RespirationData& rr, + const HRVEstimate& hrv, + const BodyMovementData& movement) { + float score = 0; + + // 心率略高于基线 + if (hr.isValid) { + float hrNorm = normalizeHR(hr.bpmSmoothed, baseline.hrResting); + float hrScore = gaussian(hrNorm, 0.3f, 0.25f); + score += 0.45f * hrScore; + + // 有适度变异性 + if (hr.bpmStd > 1.5f && hr.bpmStd < 10) { + score += 0.10f; + } + } + + // 呼吸正常 + if (rr.isValid) { + float rrNorm = fabs(normalizeRR(rr.rateSmoothed, baseline.rrResting)); + float rateScore = gaussian(rrNorm, 0, 0.5f); + score += 0.34f * rateScore; + } + + // HRV较高 + if (hrv.isValid) { + float hrvNorm = normalizeHRV(hrv.rmssd); + float hrvScore = sigmoid(hrvNorm, 2.0f, 0.9f); + score += 0.10f * hrvScore; + } + + // 体动适中到较高(开心的活跃状态) + if (movement.isValid) { + float moveNorm = normalizeMovement(movement.movementSmoothed); + float movementScore = gaussian(moveNorm, 0.45f, 0.25f); + score += 0.08f * movementScore; + } + + return constrain_value(score, 0.0f, 1.0f); +} + +float SimpleEmotionAnalyzer::calculateExcitedScore(const HeartRateData& hr, + const RespirationData& rr, + const HRVEstimate& hrv, + const BodyMovementData& movement) { + float score = 0; + + // 心率显著升高 + if (hr.isValid) { + float hrNorm = normalizeHR(hr.bpmSmoothed, baseline.hrResting); + float hrScore = sigmoid(hrNorm, 2.0f, 0.6f); + score += 0.45f * hrScore; + + // 趋势上升 + if (hr.trend > 1.5f) { + score += 0.10f; + } + } + + // 呼吸加快但规律 + if (rr.isValid) { + float rrNorm = normalizeRR(rr.rateSmoothed, baseline.rrResting); + float rateScore = sigmoid(rrNorm, 2.0f, 0.5f); + score += 0.34f * rateScore; + } + + // HRV中等 + if (hrv.isValid) { + float hrvNorm = normalizeHRV(hrv.rmssd); + float hrvScore = gaussian(hrvNorm, 0.7f, 0.4f); + score += 0.10f * hrvScore; + } + + // 体动较高(兴奋的活跃状态) + if (movement.isValid) { + float moveNorm = normalizeMovement(movement.movementSmoothed); + float movementScore = sigmoid(moveNorm, 2.0f, 0.6f); + score += 0.10f * movementScore; + } + + return constrain_value(score, 0.0f, 1.0f); +} + +float SimpleEmotionAnalyzer::calculateAnxiousScore(const HeartRateData& hr, + const RespirationData& rr, + const HRVEstimate& hrv, + const BodyMovementData& movement) { + float score = 0; + + // 心率升高,变异小 + if (hr.isValid) { + float hrNorm = normalizeHR(hr.bpmSmoothed, baseline.hrResting); + float hrScore = sigmoid(hrNorm, 2.0f, 0.4f); + float stdScore = 1.0f - sigmoid(hr.bpmStd / 6.0f, 2.0f, 0.5f); + score += 0.36f * hrScore + 0.16f * stdScore; + } + + // HRV低 + if (hrv.isValid) { + float hrvNorm = normalizeHRV(hrv.rmssd); + float hrvScore = 1.0f - sigmoid(hrvNorm, 2.0f, 0.6f); + score += 0.25f * hrvScore; + + // 压力指数高 + float stressScore = sigmoid(hrv.stressIndex / 30.0f, 2.0f, 0.5f); + score += 0.10f * stressScore; + } + + // 呼吸不规律 + if (rr.isValid) { + float irregularityScore = 1.0f - rr.regularity; + score += 0.16f * irregularityScore; + } + + // 体动适中到较高(焦虑的躁动状态) + if (movement.isValid) { + float moveNorm = normalizeMovement(movement.movementSmoothed); + float movementScore = gaussian(moveNorm, 0.55f, 0.3f); + score += 0.10f * movementScore; + } + + return constrain_value(score, 0.0f, 1.0f); +} + +float SimpleEmotionAnalyzer::calculateAngryScore(const HeartRateData& hr, + const RespirationData& rr, + const HRVEstimate& hrv, + const BodyMovementData& movement) { + float score = 0; + + // 心率快速升高 + if (hr.isValid) { + float hrNorm = normalizeHR(hr.bpmSmoothed, baseline.hrResting); + float hrScore = sigmoid(hrNorm, 2.0f, 0.75f); + float trendScore = sigmoid(hr.trend / 4.0f, 2.0f, 0.5f); + score += 0.36f * hrScore + 0.20f * trendScore; + } + + // HRV低 + if (hrv.isValid) { + float hrvNorm = normalizeHRV(hrv.rmssd); + float hrvScore = 1.0f - sigmoid(hrvNorm, 2.0f, 0.5f); + score += 0.25f * hrvScore; + } + + // 呼吸浅或不规律 + if (rr.isValid) { + float irregularityScore = 1.0f - rr.regularity; + score += 0.20f * irregularityScore; + } + + // 体动高(愤怒的激动状态) + if (movement.isValid) { + float moveNorm = normalizeMovement(movement.movementSmoothed); + float movementScore = sigmoid(moveNorm, 2.0f, 0.7f); + score += 0.10f * movementScore; + } + + return constrain_value(score, 0.0f, 1.0f); +} + +float SimpleEmotionAnalyzer::calculateSadScore(const HeartRateData& hr, + const RespirationData& rr, + const HRVEstimate& hrv, + const BodyMovementData& movement) { + float score = 0; + + // 心率偏低 + if (hr.isValid) { + float hrNorm = -normalizeHR(hr.bpmSmoothed, baseline.hrResting); + float hrScore = sigmoid(hrNorm, 2.0f, 0.2f); + score += 0.40f * hrScore; + } + + // 呼吸浅慢 + if (rr.isValid) { + float rrNorm = -normalizeRR(rr.rateSmoothed, baseline.rrResting); + float rateScore = sigmoid(rrNorm, 2.0f, 0.3f); + float varScore = 1.0f - constrain_value(rr.variability / 4.5f, 0.0f, 1.0f); + score += 0.28f * rateScore + 0.20f * varScore; + } + + // HRV可能降低 + if (hrv.isValid) { + float hrvNorm = normalizeHRV(hrv.rmssd); + float hrvScore = 1.0f - sigmoid(hrvNorm, 2.0f, 0.9f); + score += 0.10f * hrvScore; + } + + // 体动低(悲伤的无精打采状态) + if (movement.isValid) { + float moveNorm = normalizeMovement(movement.movementSmoothed); + float movementScore = 1.0f - sigmoid(moveNorm, 2.0f, 0.2f); + score += 0.10f * movementScore; + } + + return constrain_value(score, 0.0f, 1.0f); +} + +float SimpleEmotionAnalyzer::calculateStressedScore(const HeartRateData& hr, + const RespirationData& rr, + const HRVEstimate& hrv, + const BodyMovementData& movement) { + float score = 0; + + // 心率持续偏高 + if (hr.isValid) { + float hrNorm = normalizeHR(hr.bpmSmoothed, baseline.hrResting); + float hrScore = sigmoid(hrNorm, 2.0f, 0.4f); + score += 0.36f * hrScore; + } + + // HRV显著降低 + if (hrv.isValid) { + float hrvNorm = normalizeHRV(hrv.rmssd); + float hrvScore = 1.0f - sigmoid(hrvNorm, 2.0f, 0.6f); + float stressScore = sigmoid(hrv.stressIndex / 35.0f, 2.0f, 0.5f); + score += 0.34f * hrvScore + 0.10f * stressScore; + } + + // 呼吸浅快 + if (rr.isValid) { + float rrNorm = normalizeRR(rr.rateSmoothed, baseline.rrResting); + float rateScore = sigmoid(rrNorm, 2.0f, 0.5f); + score += 0.20f * rateScore; + } + + // 体动适中(压力的紧张状态) + if (movement.isValid) { + float moveNorm = normalizeMovement(movement.movementSmoothed); + float movementScore = gaussian(moveNorm, 0.4f, 0.25f); + score += 0.08f * movementScore; + } + + return constrain_value(score, 0.0f, 1.0f); +} + +float SimpleEmotionAnalyzer::calculateRelaxedScore(const HeartRateData& hr, + const RespirationData& rr, + const HRVEstimate& hrv, + const BodyMovementData& movement) { + float score = 0; + + // 心率低且稳定 + if (hr.isValid) { + float hrNorm = -normalizeHR(hr.bpmSmoothed, baseline.hrResting); + float hrScore = sigmoid(hrNorm, 2.0f, 0.25f); + float stabilityScore = 1.0f - constrain_value(hr.bpmStd / 8.0f, 0.0f, 1.0f); + score += 0.31f * hrScore + 0.16f * stabilityScore; + } + + // HRV高 + if (hrv.isValid) { + float hrvNorm = normalizeHRV(hrv.rmssd); + float hrvScore = sigmoid(hrvNorm, 2.0f, 1.0f); + score += 0.28f * hrvScore; + } + + // 呼吸慢且规律 + if (rr.isValid) { + float rrNorm = -normalizeRR(rr.rateSmoothed, baseline.rrResting); + float rateScore = sigmoid(rrNorm, 2.0f, 0.3f); + float regularityScore = rr.regularity; + score += 0.16f * rateScore + 0.10f * regularityScore; + } + + // 体动低(放松的静止状态) + if (movement.isValid) { + float moveNorm = normalizeMovement(movement.movementSmoothed); + float movementScore = 1.0f - sigmoid(moveNorm, 2.0f, 0.15f); + score += 0.10f * movementScore; + } + + return constrain_value(score, 0.0f, 1.0f); +} + +// ==================== 辅助函数 ==================== + +float SimpleEmotionAnalyzer::sigmoid(float x, float k, float x0) { + return 1.0f / (1.0f + exp(-k * (x - x0))); +} + +float SimpleEmotionAnalyzer::gaussian(float x, float mean, float std) { + float diff = x - mean; + return exp(-(diff * diff) / (2 * std * std)); +} + +void SimpleEmotionAnalyzer::normalizeProbabilities() { + float sum = 0; + for (int i = 0; i < 9; i++) { + sum += emotionProbs[i]; + } + if (sum > 0) { + for (int i = 0; i < 9; i++) { + emotionProbs[i] /= sum; + } + } +} + +void SimpleEmotionAnalyzer::smoothProbabilities() { + for (int i = 0; i < 9; i++) { + // 自适应平滑:变化快→快响应,变化慢→适度抑制 + float diff = fabs(emotionProbs[i] - prevProbs[i]); + float adaptiveAlpha = (diff > 0.2f) ? 0.6f : 0.25f; + + emotionProbs[i] = adaptiveAlpha * emotionProbs[i] + + (1.0f - adaptiveAlpha) * prevProbs[i]; + prevProbs[i] = emotionProbs[i]; + } +} + +void SimpleEmotionAnalyzer::calculateDimensions(const HeartRateData& hr, + const RespirationData& rr) { + // 效价:正面到负面 + float positive = emotionProbs[EMOTION_HAPPY] + emotionProbs[EMOTION_EXCITED] + + emotionProbs[EMOTION_RELAXED] + emotionProbs[EMOTION_CALM]; + float negative = emotionProbs[EMOTION_ANXIOUS] + emotionProbs[EMOTION_ANGRY] + + emotionProbs[EMOTION_SAD] + emotionProbs[EMOTION_STRESSED]; + + lastResult.valence = (positive - negative); + + // 唤醒度 + float highArousal = emotionProbs[EMOTION_EXCITED] + emotionProbs[EMOTION_ANXIOUS] + + emotionProbs[EMOTION_ANGRY]; + float lowArousal = emotionProbs[EMOTION_CALM] + emotionProbs[EMOTION_RELAXED] + + emotionProbs[EMOTION_SAD]; + float total = highArousal + lowArousal; + + lastResult.arousal = total > 0 ? highArousal / total : 0.5f; +} + +void SimpleEmotionAnalyzer::calculateStressLevels(const HeartRateData& hr, + const RespirationData& rr, + const HRVEstimate& hrv) { + // 压力水平(标准化归一化模型) + float hrNorm = 0, hrvNorm = 0, rrNorm = 0; + + if (hr.isValid) { + hrNorm = normalizeHR(hr.bpmSmoothed, baseline.hrResting); + hrNorm = constrain_value(hrNorm, -1.0f, 1.0f); + } + + if (hrv.isValid) { + hrvNorm = 1.0f - normalizeHRV(hrv.rmssd); + hrvNorm = constrain_value(hrvNorm, 0.0f, 1.0f); + } + + if (rr.isValid) { + rrNorm = normalizeRR(rr.rateSmoothed, baseline.rrResting); + rrNorm = constrain_value(rrNorm, -1.0f, 1.0f); + } + + // 标准化权重:HR(35%) + HRV(40%) + RR(25%) + lastResult.stressLevel = constrain_value( + (0.35f * sigmoid(hrNorm, 2.0f, 0.3f) + + 0.40f * hrvNorm + + 0.25f * sigmoid(rrNorm, 2.0f, 0.3f)) * 100.0f, + 0.0f, 100.0f + ); + + // 焦虑水平 + lastResult.anxietyLevel = emotionProbs[EMOTION_ANXIOUS] * 70 + + emotionProbs[EMOTION_STRESSED] * 50 + + (1.0f - rr.regularity) * 20; + lastResult.anxietyLevel = constrain_value(lastResult.anxietyLevel, 0.0f, 100.0f); + + // 放松水平 + lastResult.relaxationLevel = emotionProbs[EMOTION_RELAXED] * 70 + + emotionProbs[EMOTION_CALM] * 50; + if (hrv.isValid) { + lastResult.relaxationLevel += hrv.rmssd / 150.0f * 20; + } + lastResult.relaxationLevel = constrain_value(lastResult.relaxationLevel, 0.0f, 100.0f); +} + +// ==================== 时间窗口平滑函数 ==================== + +float SimpleEmotionAnalyzer::getSmoothedHR(const HeartRateData& hr) { + if (!hr.isValid) return baseline.hrResting; + + hrWindow[windowIndex] = hr.bpmSmoothed; + + float sum = 0; + int count = min(windowCount + 1, WINDOW_SIZE); + for (int i = 0; i < count; i++) { + int idx = (windowIndex - i + WINDOW_SIZE) % WINDOW_SIZE; + sum += hrWindow[idx]; + } + + return count > 0 ? sum / count : hr.bpmSmoothed; +} + +float SimpleEmotionAnalyzer::getSmoothedRR(const RespirationData& rr) { + if (!rr.isValid) return baseline.rrResting; + + rrWindow[windowIndex] = rr.rateSmoothed; + + float sum = 0; + int count = min(windowCount + 1, WINDOW_SIZE); + for (int i = 0; i < count; i++) { + int idx = (windowIndex - i + WINDOW_SIZE) % WINDOW_SIZE; + sum += rrWindow[idx]; + } + + return count > 0 ? sum / count : rr.rateSmoothed; +} + +float SimpleEmotionAnalyzer::getSmoothedHRV(const HRVEstimate& hrv) { + if (!hrv.isValid) return 40.0f; + + hrvWindow[windowIndex] = hrv.rmssd; + + float sum = 0; + int count = min(windowCount + 1, WINDOW_SIZE); + for (int i = 0; i < count; i++) { + int idx = (windowIndex - i + WINDOW_SIZE) % WINDOW_SIZE; + sum += hrvWindow[idx]; + } + + return count > 0 ? sum / count : hrv.rmssd; +} + +// ==================== 归一化辅助函数 ==================== + +float SimpleEmotionAnalyzer::normalizeHR(float hr, float baselineHR) { + return (hr - baselineHR) / 20.0f; +} + +float SimpleEmotionAnalyzer::normalizeRR(float rr, float baselineRR) { + return (rr - baselineRR) / 4.0f; +} + +float SimpleEmotionAnalyzer::normalizeHRV(float hrv) { + return hrv / 50.0f; +} + +float SimpleEmotionAnalyzer::normalizeMovement(float movement) { + return movement / 100.0f; +} + +// ==================== 输出格式化工具实现 ==================== + +String EmotionOutput::toBrief(const EmotionResult& result) { + if (!result.isValid) return "检测中..."; + return String(EMOTION_NAMES[result.primaryEmotion]) + " " + + String((int)(result.confidence * 100)) + "%"; +} + +String EmotionOutput::toDetailed(const EmotionResult& result) { + String output = ""; + + if (!result.isValid) { + return "数据无效,无法分析情绪"; + } + + output += "情绪: " + String(EMOTION_NAMES[result.primaryEmotion]); + output += " (" + String((int)(result.confidence * 100)) + "%)"; + output += "\n强度: " + String((int)(result.intensity * 100)) + "%"; + output += "\n\n维度分析:"; + output += "\n 效价: " + String(result.valence, 2); + output += "\n 唤醒: " + String(result.arousal, 2); + output += "\n\n指标:"; + output += "\n 压力: " + String((int)result.stressLevel); + output += "\n 焦虑: " + String((int)result.anxietyLevel); + output += "\n 放松: " + String((int)result.relaxationLevel); + output += "\n\n描述: " + String(EMOTION_DESCRIPTIONS[result.primaryEmotion]); + output += "\n建议: " + String(EMOTION_SUGGESTIONS[result.primaryEmotion]); + + return output; +} + +String EmotionOutput::toJson(const EmotionResult& result) { + String json = "{"; + json += "\"emotion\":\"" + String(EMOTION_NAMES[result.primaryEmotion]) + "\","; + json += "\"confidence\":" + String(result.confidence, 3) + ","; + json += "\"intensity\":" + String(result.intensity, 3) + ","; + json += "\"valence\":" + String(result.valence, 3) + ","; + json += "\"arousal\":" + String(result.arousal, 3) + ","; + json += "\"stressLevel\":" + String(result.stressLevel, 1) + ","; + json += "\"anxietyLevel\":" + String(result.anxietyLevel, 1) + ","; + json += "\"relaxationLevel\":" + String(result.relaxationLevel, 1) + ","; + json += "\"sympatheticActivity\":" + String(result.sympatheticActivity, 3) + ","; + json += "\"parasympatheticActivity\":" + String(result.parasympatheticActivity, 3) + ","; + json += "\"isValid\":" + String(result.isValid ? "true" : "false") + ","; + json += "\"timestamp\":" + String(result.timestamp); + json += "}"; + return json; +} + +String EmotionOutput::toCsv(const EmotionResult& result, unsigned long timestamp) { + String csv = ""; + csv += String(timestamp) + ","; + csv += String(result.primaryEmotion) + ","; + csv += String(result.confidence, 3) + ","; + csv += String(result.intensity, 3) + ","; + csv += String(result.valence, 3) + ","; + csv += String(result.arousal, 3) + ","; + csv += String(result.stressLevel, 1) + ","; + csv += String(result.anxietyLevel, 1) + ","; + csv += String(result.relaxationLevel, 1); + return csv; +} diff --git a/src/emotion_analyzer_simple.h b/src/emotion_analyzer_simple.h new file mode 100644 index 0000000..ba19014 --- /dev/null +++ b/src/emotion_analyzer_simple.h @@ -0,0 +1,189 @@ +/** + * @file emotion_analyzer_simple.h + * @brief 简化版情绪分析器头文件 + * @description 适配雷达传感器数据,分析用户情绪状态 + */ + +#ifndef EMOTION_ANALYZER_SIMPLE_H +#define EMOTION_ANALYZER_SIMPLE_H + +#include +#include "config.h" +#include "data_processor.h" + +// ==================== 情绪分析结果 ==================== + +/** + * @brief 情绪分析结果 + */ +struct EmotionResult { + // 主要情绪 + EmotionType primaryEmotion; + EmotionType secondaryEmotion; // 次要情绪 + float confidence; // 置信度 0-1 + float intensity; // 强度 0-1 + + // 情绪维度 + float valence; // 效价 -1到+1 + float arousal; // 唤醒度 0-1 + + // 压力评估 + float stressLevel; // 压力水平 0-100 + float anxietyLevel; // 焦虑水平 0-100 + float relaxationLevel; // 放松水平 0-100 + + // 自主神经评估 + float sympatheticActivity; // 交感神经活动 + float parasympatheticActivity; // 副交感神经活动 + + // 数据有效性 + bool isValid; + unsigned long timestamp; +}; + +// ==================== 用户基线 ==================== + +/** + * @brief 用户生理基线 + */ +struct UserBaseline { + float hrResting; // 静息心率 + float hrMin; // 最小心率 + float hrMax; // 最大心率 + float rrResting; // 静息呼吸频率 + bool isCalibrated; + unsigned long calibrationTime; + + // 冷启动缓冲 + static const int COLD_START_SAMPLES = 15; // 约30秒数据(2秒/样本) + float coldStartHrSum; + float coldStartRrSum; + int coldStartCount; + bool isColdStarting; +}; + +/** + * @brief 体动数据 + */ +struct BodyMovementData { + uint8_t movement; // 体动参数 0-100 + float movementSmoothed; // 平滑后体动 + float movementMean; // 平均体动 + float movementStd; // 体动标准差 + float activityLevel; // 活动水平 0-1 + bool isValid; // 数据是否有效 + unsigned long timestamp; // 时间戳 +}; + +// ==================== 情绪分析器 ==================== + +/** + * @brief 简化版情绪分析器 + */ +class SimpleEmotionAnalyzer { +private: + // 用户基线 + UserBaseline baseline; + + // 上一次结果 + EmotionResult lastResult; + + // 情绪概率缓冲(用于平滑) + float emotionProbs[9]; + float smoothingFactor; + float prevProbs[9]; + + // 历史情绪记录 + EmotionType* emotionHistory; + int historySize; + int historyIndex; + int historyCount; + + // 时间窗口缓冲(用于输入数据平滑) + static const int WINDOW_SIZE = 15; + float hrWindow[WINDOW_SIZE]; + float rrWindow[WINDOW_SIZE]; + float hrvWindow[WINDOW_SIZE]; + int windowIndex; + int windowCount; + +public: + SimpleEmotionAnalyzer(int histSize = 30); + ~SimpleEmotionAnalyzer(); + + // 分析情绪 + EmotionResult analyze(const HeartRateData& hrData, + const RespirationData& rrData, + const HRVEstimate& hrvData, + const BodyMovementData& movementData); + + // 设置基线 + void setBaseline(const UserBaseline& bl); + UserBaseline getBaseline() const { return baseline; } + + // 自动校准基线 + void calibrateBaseline(const HeartRateData& hrData, + const RespirationData& rrData, + const BodyMovementData& movementData); + + // 设置平滑因子 + void setSmoothing(float factor) { smoothingFactor = constrain_value(factor, 0.0f, 1.0f); } + + // 获取最近的主要情绪 + EmotionType getRecentDominantEmotion(int seconds = 60); + + // 重置 + void reset(); + +private: + // 情绪规则匹配 + float calculateCalmScore(const HeartRateData& hr, const RespirationData& rr, const HRVEstimate& hrv, const BodyMovementData& movement); + float calculateHappyScore(const HeartRateData& hr, const RespirationData& rr, const HRVEstimate& hrv, const BodyMovementData& movement); + float calculateExcitedScore(const HeartRateData& hr, const RespirationData& rr, const HRVEstimate& hrv, const BodyMovementData& movement); + float calculateAnxiousScore(const HeartRateData& hr, const RespirationData& rr, const HRVEstimate& hrv, const BodyMovementData& movement); + float calculateAngryScore(const HeartRateData& hr, const RespirationData& rr, const HRVEstimate& hrv, const BodyMovementData& movement); + float calculateSadScore(const HeartRateData& hr, const RespirationData& rr, const HRVEstimate& hrv, const BodyMovementData& movement); + float calculateStressedScore(const HeartRateData& hr, const RespirationData& rr, const HRVEstimate& hrv, const BodyMovementData& movement); + float calculateRelaxedScore(const HeartRateData& hr, const RespirationData& rr, const HRVEstimate& hrv, const BodyMovementData& movement); + + // 辅助函数 + float sigmoid(float x, float k = 1.0f, float x0 = 0.0f); + float gaussian(float x, float mean, float std); + void normalizeProbabilities(); + void smoothProbabilities(); + void calculateDimensions(const HeartRateData& hr, const RespirationData& rr); + void calculateStressLevels(const HeartRateData& hr, const RespirationData& rr, const HRVEstimate& hrv); + + // 时间窗口平滑 + float getSmoothedHR(const HeartRateData& hr); + float getSmoothedRR(const RespirationData& rr); + float getSmoothedHRV(const HRVEstimate& hrv); + + // 归一化辅助函数(统一尺度) + float normalizeHR(float hr, float baseline); + float normalizeRR(float rr, float baseline); + float normalizeHRV(float hrv); + float normalizeMovement(float movement); +}; + +// ==================== 输出格式化工具 ==================== + +/** + * @brief 情绪结果格式化工具 + */ +class EmotionOutput { +public: + // 简洁输出 + static String toBrief(const EmotionResult& result); + + // 详细输出 + static String toDetailed(const EmotionResult& result); + + // JSON输出 + static String toJson(const EmotionResult& result); + + // CSV输出 + static String toCsv(const EmotionResult& result, unsigned long timestamp); +}; + +#endif // EMOTION_ANALYZER_SIMPLE_H diff --git a/src/main.cpp b/src/main.cpp index 7b1aa5c..8de2d64 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -5,616 +5,76 @@ #include #include "wifi_manager.h" #include "radar_manager.h" - -// ESP32 GPIO控制演示 -#define BOOT_BUTTON_PIN 0 // Boot按钮引脚 -#define NETWORK_LED_PIN 5 // 网络状态LED指示灯开发板48引脚,雷达板5引脚 -#define CONFIG_CLEAR_PIN 4 // 配置清除指示灯 -#define GPIO8 8 // 自定义GPIO8 -#define GPIO9 9 // 自定义GPIO9 - -uint8_t WiFi_Connect_First_bit = 1; // WiFi首次连接标志位 - -// 配置清除指示灯状态枚举 -enum ConfigClearStatus { - CONFIG_NORMAL, // 正常运行 - LOW - CONFIG_PREPARING, // 准备清除 - HIGH - CONFIG_CLEARING, // 清除过程中 - 呼吸灯 - CONFIG_COMPLETED // 清除完成 - 快速闪烁3次 -}; - -NetworkStatus currentNetworkStatus = NET_INITIAL; // 当前网络状态 -unsigned long lastBlinkTime = 0; // 上次LED闪烁时间 -bool ledState = false; // LED状态 -int breatheValue = 0; // 呼吸灯当前亮度值 -bool breatheIncreasing = true; // 呼吸灯是否在增加亮度 - -ConfigClearStatus currentConfigClearStatus = CONFIG_NORMAL; // 当前配置清除状态 -unsigned long lastConfigBlinkTime = 0; // 上次配置清除LED闪烁时间 -bool configLedState = false; // 配置清除LED状态 -int configBreatheValue = 0; // 配置清除呼吸灯当前亮度值 -bool configBreatheIncreasing = true; // 配置清除呼吸灯是否在增加亮度 - -const int SLOW_BLINK_INTERVAL = 1000; // 慢闪间隔(毫秒) -const int FAST_BLINK_INTERVAL = 200; // 快闪间隔(毫秒) -const int BREATHE_INTERVAL = 40; // 呼吸灯更新间隔(毫秒) -const int BREATHE_MIN = 0; // 呼吸灯最小亮度值 -const int BREATHE_MAX = 155; // 呼吸灯最大亮度值 -const int BREATHE_STEP = 5; // 呼吸灯亮度步进值 - -const uint16_t MIN_DEVICE_ID = 1000; // 最小设备ID -const uint16_t MAX_DEVICE_ID = 1999; // 最大设备ID - -const unsigned long CLEAR_CONFIG_DURATION = 3000; // 清除配置持续时间(毫秒) +#include "data_processor.h" +#include "emotion_analyzer_simple.h" +#include "tasks_manager.h" Preferences preferences; // Flash存储对象 WiFiManager wifiManager; // WiFi管理器对象 uint16_t currentDeviceId = 0000; // 当前设备ID -bool clearConfigRequested = false; // 清除配置请求标志 -bool forceLedOff = false; // 强制关闭LED标志 -void configClearLedTask(void *parameter); -void bootButtonMonitorTask(void *parameter); void checkBootButton(); -void clearStoredConfig(); -void ledControlTask(void *parameter); -void setNetworkStatus(NetworkStatus status); -void wifiMonitorTask(void *parameter); -void WiFiEvent(WiFiEvent_t event); void loadDeviceId(); -void saveDeviceId(); - -String getFieldNameByProtocolId(int protocolId); - -/** - * @brief 根据协议ID获取字段名称 - * 将协议ID映射到对应的字段名称,用于数据序列化和反序列化 - * @param protocolId 协议ID - * @return 对应的字段名称字符串 - */ -String getFieldNameByProtocolId(int protocolId) { - switch(protocolId) { - case 1: - return "heartRate"; - case 2: - return "breathingRate"; - case 13: - return "personDetected"; - case 14: - return "humanActivity"; - case 15: - return "humanDistance"; - case 16: - return "humanPosition"; - case 17: - return "sleepState"; - default: - return "unknown"; - } -} - -/** - * @brief 检查Boot按钮状态 - * 在启动时检查Boot按钮是否被按下,如果按下则进入配置清除流程 - */ -void checkBootButton() { - Serial.println("🔍 检查Boot按钮状态..."); - - pinMode(BOOT_BUTTON_PIN, INPUT_PULLUP); - - delay(10); - - int buttonState = digitalRead(BOOT_BUTTON_PIN); - Serial.printf("📊 Boot按钮状态: %s\n", buttonState == LOW ? "按下" : "释放"); - - if (buttonState == LOW) { - Serial.println("⚠️ 检测到Boot按钮按下,请释放按钮后继续启动"); - Serial.println("⏰ 等待按钮释放..."); - - while (digitalRead(BOOT_BUTTON_PIN) == LOW) { - delay(100); - } - - Serial.println("✅ Boot按钮已释放,正常启动"); - } else { - Serial.println("✅ Boot按钮未按下,正常启动"); - } -} - -/** - * @brief 加载设备ID - * 从Flash中读取保存的设备ID - */ -void loadDeviceId() { - currentDeviceId = preferences.getUShort("deviceId", 1001); - Serial.printf("从Flash加载设备ID: %u\n", currentDeviceId); -} - -/** - * @brief 保存设备ID - * 将设备ID保存到Flash中 - */ -void saveDeviceId() { - preferences.putUShort("deviceId", currentDeviceId); - Serial.printf("设备ID已保存到Flash: %u\n", currentDeviceId); -} - -/** - * @brief 清除存储的配置 - * 清除Flash中保存的所有配置,包括设备ID和WiFi配置 - */ -void clearStoredConfig() { - Serial.println("🧹 开始清除存储的配置..."); - - uint16_t oldDeviceId = preferences.getUShort("deviceId", 0); - - preferences.remove("deviceId"); - preferences.remove("wifi_first"); - - wifiManager.clearAllConfigs(); - - Serial.println("✅ 配置已清除完成"); - Serial.printf("🗑️ 被清除的设备ID: %u\n", oldDeviceId); - - currentDeviceId = 1001; - WiFi_Connect_First_bit = 1; - - WiFi.disconnect(true); - setNetworkStatus(NET_DISCONNECTED); - - Serial.println("🔄 已清除Flash与内存中的配置,请重新配置WiFi和设备ID"); - - if (deviceConnected) { - sendStatusToBLE(); - } -} - -/** - * @brief 配置清除LED控制任务 - * 根据配置清除状态控制CONFIG_CLEAR_PIN引脚的LED显示 - * @param parameter 任务参数(未使用) - */ -void configClearLedTask(void *parameter) { - while (1) { - switch (currentConfigClearStatus) { - case CONFIG_NORMAL: - analogWrite(CONFIG_CLEAR_PIN, 0); - break; - - case CONFIG_PREPARING: - analogWrite(CONFIG_CLEAR_PIN, 255); - break; - - case CONFIG_CLEARING: - if (millis() - lastConfigBlinkTime >= BREATHE_INTERVAL) { - analogWrite(CONFIG_CLEAR_PIN, configBreatheValue); - - if (configBreatheIncreasing) { - configBreatheValue += 5; - if (configBreatheValue >= BREATHE_MAX) { - configBreatheValue = BREATHE_MAX; - configBreatheIncreasing = false; - } - } else { - configBreatheValue -= 5; - if (configBreatheValue <= BREATHE_MIN) { - configBreatheValue = BREATHE_MIN; - configBreatheIncreasing = true; - } - } - lastConfigBlinkTime = millis(); - } - break; - - case CONFIG_COMPLETED: - if (millis() - lastConfigBlinkTime >= FAST_BLINK_INTERVAL) { - configLedState = !configLedState; - digitalWrite(CONFIG_CLEAR_PIN, configLedState ? HIGH : LOW); - lastConfigBlinkTime = millis(); - - static int blinkCount = 0; - blinkCount++; - - if (blinkCount >= 6) { - blinkCount = 0; - currentConfigClearStatus = CONFIG_NORMAL; - digitalWrite(CONFIG_CLEAR_PIN, LOW); - } - } - break; - } - - vTaskDelay(10 / portTICK_PERIOD_MS); - } -} - -/** - * @brief BOOT按钮监控任务 - * 持续监控BOOT按钮状态,检测长按3秒事件并触发配置清除 - * @param parameter 任务参数(未使用) - */ -void bootButtonMonitorTask(void *parameter) { - Serial.println("🔍 启动BOOT按钮监控任务..."); - - pinMode(BOOT_BUTTON_PIN, INPUT_PULLUP); - - unsigned long buttonPressStartTime = 0; - bool buttonPressed = false; - - while (1) { - int buttonState = digitalRead(BOOT_BUTTON_PIN); - - if (buttonState == LOW && !buttonPressed) { - buttonPressed = true; - buttonPressStartTime = millis(); - Serial.println("⚠️ 检测到BOOT按钮按下,长按3秒将清除配置"); - - currentConfigClearStatus = CONFIG_PREPARING; - } - else if (buttonState == HIGH && buttonPressed) { - if (!clearConfigRequested) { - currentConfigClearStatus = CONFIG_NORMAL; - Serial.println("❌ 按钮释放,取消清除操作"); - } - buttonPressed = false; - } - - if (buttonPressed && (millis() - buttonPressStartTime >= CLEAR_CONFIG_DURATION)) { - if (!clearConfigRequested) { - clearConfigRequested = true; - forceLedOff = true; - ledcWrite(0, 0); - Serial.println("✅ 长按3秒确认,将清除配置"); - Serial.println("💡 网络LED已强制熄灭"); - - clearStoredConfig(); - - Serial.println("🔄 配置清除完成,LED将闪烁3次表示完成..."); - - analogWrite(CONFIG_CLEAR_PIN, 0); - Serial.println("🔄 系统即将重启..."); - - vTaskDelay(1000 / portTICK_PERIOD_MS); - ESP.restart(); - } - } - - vTaskDelay(50 / portTICK_PERIOD_MS); - - esp_task_wdt_reset(); - } -} - -/** - * @brief LED控制任务 - * 根据网络状态控制NETWORK_LED_PIN引脚的LED显示 - * 支持慢闪、快闪和呼吸灯效果 - * @param parameter 任务参数(未使用) - */ -void ledControlTask(void *parameter) { - while (1) { - if (forceLedOff) { - ledcWrite(0, 0); - vTaskDelay(10 / portTICK_PERIOD_MS); - continue; - } - - switch (currentNetworkStatus) { - case NET_INITIAL: - case NET_DISCONNECTED: - if (millis() - lastBlinkTime >= SLOW_BLINK_INTERVAL) { - ledState = !ledState; - if(ledState) { - ledcWrite(0, 255); - } else { - ledcWrite(0, 0); - } - lastBlinkTime = millis(); - } - break; - - case NET_CONNECTING: - if (millis() - lastBlinkTime >= FAST_BLINK_INTERVAL) { - ledState = !ledState; - if(ledState) { - ledcWrite(0, 255); - } else { - ledcWrite(0, 0); - } - lastBlinkTime = millis(); - } - break; - - case NET_CONNECTED: - if (millis() - lastBlinkTime >= BREATHE_INTERVAL) { - ledcWrite(0, breatheValue); - - if (breatheIncreasing) { - breatheValue += BREATHE_STEP; - if (breatheValue >= BREATHE_MAX) { - breatheValue = BREATHE_MAX; - breatheIncreasing = false; - } - } else { - breatheValue -= BREATHE_STEP; - if (breatheValue <= BREATHE_MIN) { - breatheValue = BREATHE_MIN; - breatheIncreasing = true; - } - } - lastBlinkTime = millis(); - } - break; - } - - vTaskDelay(10 / portTICK_PERIOD_MS); - } -} - -/** - * @brief 设置网络状态 - * 更新当前网络状态,并重置呼吸灯参数 - * @param status 网络状态 - */ -void setNetworkStatus(NetworkStatus status) { - currentNetworkStatus = status; - - if (status == NET_CONNECTED) { - breatheValue = BREATHE_MIN; - breatheIncreasing = true; - } -} - -/** - * @brief WiFi事件处理函数 - * 处理WiFi连接状态变化事件,更新网络状态和LED显示 - * @param event WiFi事件类型 - */ -void WiFiEvent(WiFiEvent_t event) { - switch (event) { - case ARDUINO_EVENT_WIFI_STA_START: - setNetworkStatus(NET_INITIAL); - break; - - case ARDUINO_EVENT_WIFI_STA_CONNECTED: - setNetworkStatus(NET_CONNECTING); - break; - - case ARDUINO_EVENT_WIFI_STA_GOT_IP: - setNetworkStatus(NET_CONNECTED); - break; - - case ARDUINO_EVENT_WIFI_STA_DISCONNECTED: - setNetworkStatus(NET_DISCONNECTED); - break; - - case ARDUINO_EVENT_WIFI_STA_STOP: - setNetworkStatus(NET_DISCONNECTED); - break; - } -} - -/** - * @brief WiFi监控任务 - * 定期更新WiFi管理器状态,处理WiFi重连等逻辑 - * @param parameter 任务参数(未使用) - */ -void wifiMonitorTask(void *parameter) { - Serial.println("📡 WiFi监控任务启动"); - - while(1) { - wifiManager.update(); - vTaskDelay(500 / portTICK_PERIOD_MS); - } -} /** * @brief 系统初始化函数 * 初始化所有硬件外设、任务和通信模块 */ void setup() { - Serial.begin(115200); - - checkBootButton(); - - analogWrite(CONFIG_CLEAR_PIN, 0); - - Serial.println("🚀 ESP32-R60ABD1系统启动"); - Serial.println("🔧 初始化系统组件..."); - - pinMode(BOOT_BUTTON_PIN, INPUT); - pinMode(NETWORK_LED_PIN, OUTPUT); - pinMode(CONFIG_CLEAR_PIN, OUTPUT); - pinMode(GPIO8, OUTPUT); - pinMode(GPIO9, OUTPUT); - - digitalWrite(CONFIG_CLEAR_PIN, LOW); - digitalWrite(GPIO8, LOW); - digitalWrite(GPIO9, LOW); - digitalWrite(NETWORK_LED_PIN, LOW); - digitalWrite(CONFIG_CLEAR_PIN, LOW); - - ledcSetup(0, 5000, 8); - ledcSetup(1, 5000, 8); - ledcAttachPin(NETWORK_LED_PIN, 0); - ledcAttachPin(CONFIG_CLEAR_PIN, 1); - - WiFi.onEvent(WiFiEvent); - - setNetworkStatus(NET_INITIAL); - - esp_task_wdt_init(30, true); - esp_task_wdt_add(NULL); - - preferences.begin("radar_data", false); - - wifiManager.begin(); - - Serial.println("💾 加载设备配置..."); - loadDeviceId(); - - Serial.println("🏗️ 初始化雷达管理器..."); - initRadarManager(); - - xTaskCreate( - configClearLedTask, - "Config Clear LED Task", - 2048, - NULL, - 1, - NULL - ); - - xTaskCreate( - bootButtonMonitorTask, - "Boot Button Monitor Task", - 2048, - NULL, - 1, - NULL - ); - - xTaskCreate( - ledControlTask, - "LED Control Task", - 2048, - NULL, - 1, - NULL - ); - - xTaskCreate( - wifiMonitorTask, - "WiFi Monitor Task", - 4096, - NULL, - 2, - NULL - ); - - Serial.println("✅ FreeRTOS任务创建成功"); - - Serial.println("📶 初始化BLE服务..."); - String deviceName = "Radar_" + String(currentDeviceId); - BLEDevice::init(deviceName.c_str()); - pServer = BLEDevice::createServer(); - pServer->setCallbacks(new MyServerCallbacks()); - - BLEService *pService = pServer->createService(SERVICE_UUID); - pCharacteristic = pService->createCharacteristic( - CHARACTERISTIC_UUID, - BLECharacteristic::PROPERTY_READ | - BLECharacteristic::PROPERTY_WRITE | - BLECharacteristic::PROPERTY_NOTIFY - ); - pCharacteristic->setCallbacks(new MyCallbacks()); - pCharacteristic->addDescriptor(new BLE2902()); - - pService->start(); - BLEAdvertising *pAdvertising = BLEDevice::getAdvertising(); - pAdvertising->addServiceUUID(SERVICE_UUID); - pAdvertising->setScanResponse(true); - pAdvertising->setMinPreferred(0x06); - pAdvertising->setMinPreferred(0x12); - BLEDevice::startAdvertising(); - - Serial.println(String("BLE已启动,设备名称: Radar_") + String(currentDeviceId)); - - Serial.println("🌐 检查WiFi配置..."); - if (wifiManager.getSavedNetworkCount() > 0) { - Serial.printf("💾 检测到 %d 个已保存的WiFi配置,尝试连接...\n", wifiManager.getSavedNetworkCount()); - if (wifiManager.initializeWiFi()) { - Serial.println("✅ WiFi连接成功!"); - } else { - Serial.println("❌ WiFi连接失败,请通过BLE重新配置"); - } - } else { - Serial.println("⚠️ 未检测到WiFi配置,请通过BLE进行网络配置"); - } - - size_t wifi_first_len = preferences.getBytes("wifi_first", &WiFi_Connect_First_bit, sizeof(WiFi_Connect_First_bit)); - if (wifi_first_len == sizeof(WiFi_Connect_First_bit)) { - Serial.printf("从Flash读取 WiFi_Connect_First_bit: %u\n", WiFi_Connect_First_bit); - } else { - Serial.println("Flash中无 wifi_first 条目,保留内存中原始值"); - } - - if(WiFi_Connect_First_bit == 0) - { - unsigned long wifiWaitStart = millis(); - unsigned long lastWifiWaitPrint = 0; - const unsigned long WIFI_WAIT_TIMEOUT = 15000; - while (WiFi.status() != WL_CONNECTED && (millis() - wifiWaitStart) < WIFI_WAIT_TIMEOUT) { - if (millis() - lastWifiWaitPrint >= 1000) { - Serial.println("等待WiFi连接..."); - lastWifiWaitPrint = millis(); - } - yield(); - vTaskDelay(10 / portTICK_PERIOD_MS); - } - } - - Serial.println("WiFi连接成功!"); - initR60ABD1(); - - Serial.println("🎉 系统初始化完成,等待雷达数据..."); - - if (WiFi.status() == WL_CONNECTED) { - Serial.println("🌅 启动时发送睡眠数据到数据库"); + Serial.begin(115200); // 初始化串口通信 + checkBootButton(); // 检查Boot按钮状态 + analogWrite(CONFIG_CLEAR_PIN, 0); // 关闭配置清除指示灯 + WiFi.onEvent(WiFiEvent); // 注册WiFi事件处理函数 + setNetworkStatus(NET_INITIAL); // 初始化网络状态 + esp_task_wdt_init(30, true); // 初始化看门狗定时器 + esp_task_wdt_add(NULL); // 将主任务添加到看门狗 + preferences.begin("radar_data", false); // 初始化Flash存储 + loadDeviceId(); // 加载设备ID + initRadarManager(); // 初始化雷达管理器 + initAllTasks(); // 创建所有FreeRTOS任务 + if (WiFi.status() == WL_CONNECTED) // 启动时发送睡眠数据 sendSleepDataToInfluxDB(); - } + Serial.println("✅ 系统初始化完成"); } /** * @brief 主循环函数 - * 处理BLE连接状态和定期发送雷达命令 + * 主循环只负责看门狗重置,保持空闲 */ void loop() { esp_task_wdt_reset(); - - if (!deviceConnected && oldDeviceConnected) { - vTaskDelay(500 / portTICK_PERIOD_MS); - pServer->startAdvertising(); - Serial.println("开始BLE广播"); - oldDeviceConnected = deviceConnected; - } - if (deviceConnected && !oldDeviceConnected) { - oldDeviceConnected = deviceConnected; - } - { - static const uint8_t radar_cmds[][3] = { - {0x84, 0x81, 0x0F}, - {0x84, 0x8D, 0x0F}, - {0x84, 0x8F, 0x0F}, - {0x84, 0x8E, 0x0F}, - {0x84, 0x91, 0x0F}, - {0x84, 0x92, 0x0F}, - {0x84, 0x83, 0x0F}, - {0x84, 0x84, 0x0F}, - {0x84, 0x85, 0x0F}, - {0x84, 0x86, 0x0F}, - {0x84, 0x90, 0x0F} - }; - - const size_t cmdCount = sizeof(radar_cmds) / sizeof(radar_cmds[0]); - static size_t cmdIndex = 0; - static unsigned long lastCmdMillis = 0; - const unsigned long CMD_INTERVAL = 2000UL; - - unsigned long now = millis(); - if (now - lastCmdMillis >= CMD_INTERVAL) { - sendRadarCommand(radar_cmds[cmdIndex][0], radar_cmds[cmdIndex][1], radar_cmds[cmdIndex][2]); - lastCmdMillis = now; - cmdIndex++; - if (cmdIndex >= cmdCount) cmdIndex = 0; - } - } - - processBLEConfig(); - esp_task_wdt_reset(); - esp_task_wdt_reset(); - - esp_task_wdt_reset(); + vTaskDelay(100 / portTICK_PERIOD_MS); } +/** + * @brief 检查Boot按钮状态 + * 在启动时检查Boot按钮是否松开,等待松开后再启动避免频繁重启 + */ +void checkBootButton() { + pinMode(BOOT_BUTTON_PIN, INPUT_PULLUP); + delay(10); + + if (digitalRead(BOOT_BUTTON_PIN) == LOW) { + Serial.println("⚠️ 检测到Boot按钮按下,请释放按钮后继续启动"); + while (digitalRead(BOOT_BUTTON_PIN) == LOW) { + delay(50); + } + Serial.println("✅ Boot按钮已释放,正常启动"); + } +} + +/** + * @brief 加载设备ID + * 从Flash中读取保存的设备ID,如果Flash中没有则使用默认值1001并保存 + */ +void loadDeviceId() { + if (preferences.isKey("deviceId")) { + currentDeviceId = preferences.getUShort("deviceId", 1001); + } else { + currentDeviceId = 1001; + preferences.putUShort("deviceId", currentDeviceId); + Serial.printf("Flash中无设备ID,使用默认值1001并保存\n"); + } + Serial.printf("从Flash加载设备ID: %u\n", currentDeviceId); +} \ No newline at end of file diff --git a/src/main_backup.cpp.bak b/src/main_backup.cpp.bak deleted file mode 100644 index b493864..0000000 --- a/src/main_backup.cpp.bak +++ /dev/null @@ -1,2582 +0,0 @@ -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include "wifi_manager.h" - -// ESP32 GPIO控制演示 -#define BOOT_BUTTON_PIN 0 // Boot按钮引脚 -#define NETWORK_LED_PIN 5 // 网络状态LED指示灯裸板48引脚,应用板5引脚 -#define CONFIG_CLEAR_PIN 4 // 配置清除指示灯 -#define GPIO8 8 // 自定义GPIO8 -#define GPIO9 9 // 自定义GPIO9 - -uint8_t presence_Bit = 1;//标志位 -uint8_t WiFi_Connect_First_bit = 1; //WiFi第一次连接标志位 - -// 网络状态枚举 -enum NetworkStatus { - NET_INITIAL, // 初始化/未连接 - 慢闪 - NET_CONNECTING, // 连接中 - 快闪 - NET_CONNECTED, // 已连接 - 呼吸灯 - NET_DISCONNECTED // 断开连接 - 慢闪 -}; - -// 配置清除指示灯状态枚举 -enum ConfigClearStatus { - CONFIG_NORMAL, // 正常运行 - LOW - CONFIG_PREPARING, // 准备清除 - HIGH - CONFIG_CLEARING, // 清除过程中 - 呼吸灯 - CONFIG_COMPLETED // 清除完成 - 快速闪烁3次 -}; - -// 网络状态全局变量 -NetworkStatus currentNetworkStatus = NET_INITIAL; -unsigned long lastBlinkTime = 0; -bool ledState = false; -int breatheValue = 0; -bool breatheIncreasing = true; - -// 配置清除指示灯状态变量 -ConfigClearStatus currentConfigClearStatus = CONFIG_NORMAL; -unsigned long lastConfigBlinkTime = 0; -bool configLedState = false; -int configBreatheValue = 0; -bool configBreatheIncreasing = true; - -// LED控制相关参数 -const int SLOW_BLINK_INTERVAL = 1000; // 慢闪间隔 1秒 -const int FAST_BLINK_INTERVAL = 200; // 快闪间隔 0.2秒 -const int BREATHE_INTERVAL = 20; // 呼吸灯更新间隔 -const int BREATHE_MIN = 0; // 呼吸灯最小值 -const int BREATHE_MAX = 155; // 呼吸灯最大值 -const int BREATHE_STEP = 5; // 呼吸灯步进值 - -// 协议ID映射表 - 对应Python脚本中的field_mapping -const int PROTOCOL_HEART_RATE = 1; // 心率 -const int PROTOCOL_BREATH_RATE = 2; // 呼吸率 -const int PROTOCOL_PERSON_DETECTED = 13; // 人检 -const int PROTOCOL_HUMAN_ACTIVITY = 14; // 人体活动 -const int PROTOCOL_HUMAN_DISTANCE = 15; // 人体距离 -const int PROTOCOL_HUMAN_POSITION = 16; // 人体方位 -const int PROTOCOL_SLEEP_STATE = 17; // 睡眠状态 - -// R60ABD1新增协议ID -const int PROTOCOL_BODY_MOVEMENT = 18; // 体动幅度 -const int PROTOCOL_BREATH_STATUS = 19; // 呼吸信息 -const int PROTOCOL_SLEEP_TIME = 20; // 睡眠时长 -const int PROTOCOL_SLEEP_SCORE = 21; // 睡眠质量评分 -const int PROTOCOL_BED_ENTRY = 22; // 入床/离床状态 -const int PROTOCOL_ABNORMAL_STATE = 23; // 异常状态 -const int PROTOCOL_AVG_HEART_RATE = 24; // 平均心率 -const int PROTOCOL_AVG_BREATH_RATE = 25; // 平均呼吸率 -const int PROTOCOL_TURN_COUNT = 26; // 翻身次数 -const int PROTOCOL_LARGE_MOVE_RATIO = 27; // 大幅度体动占比 -const int PROTOCOL_SMALL_MOVE_RATIO = 28; // 小幅度体动占比 -const int PROTOCOL_POS_X = 29; // 人体X坐标 -const int PROTOCOL_POS_Y = 30; // 人体Y坐标 -const int PROTOCOL_POS_Z = 31; // 人体Z坐标 -const int PROTOCOL_DEEP_SLEEP_TIME = 32; // 深睡时长 -const int PROTOCOL_LIGHT_SLEEP_TIME = 33; // 浅睡时长 -const int PROTOCOL_AWAKE_TIME = 34; // 清醒时长 -const int PROTOCOL_SLEEP_TOTAL_TIME = 35; // 睡眠总时长 -const int PROTOCOL_DEEP_SLEEP_RATIO = 36; // 深睡占比 -const int PROTOCOL_LIGHT_SLEEP_RATIO = 37; // 浅睡占比 -const int PROTOCOL_AWAKE_RATIO = 38; // 清醒占比 -const int PROTOCOL_TURNOVER_COUNT = 39; // 翻身次数 -const int PROTOCOL_STRUGGLE_ALERT = 40; // 挣扎报警 -const int PROTOCOL_NO_ONE_ALERT = 41; // 无人计时报警 -const int PROTOCOL_BED_STATUS = 42; // 入床状态 -const int PROTOCOL_APNEA_COUNT = 43; // 呼吸暂停次数 - -bool clearConfigRequested = false; - -unsigned long bootButtonPressTime = 0; - -const unsigned long CLEAR_CONFIG_DURATION = 3000; // 长按3秒清除配置 - -#define BUFFER_SIZE 2000 // 固定存储2000个数据点 -// 环形缓冲区结构 -struct CircularBuffer { - float data[BUFFER_SIZE]; - unsigned long timestamps[BUFFER_SIZE]; // 存储时间戳 - int head; // 最新数据位置 - int tail; // 最旧数据位置 - int count; // 当前有效数据数量 - bool isFull; // 缓冲区是否已满 - float sum; // 当前数据总和 -}; - - - -Preferences preferences; - -WiFiManager wifiManager; - -uint16_t currentDeviceId = 0000; // 默认设备ID为0000 -const uint16_t MIN_DEVICE_ID = 1000; // 设备ID最小值 -const uint16_t MAX_DEVICE_ID = 1999; // 设备ID最大值 - -const uint32_t PHASE_SEND_INTERVAL = 1;// 每1毫秒发送一次相位数据 -const uint32_t VITAL_SEND_INTERVAL = 10;// 每10毫秒发送一次生命体征数据 - -#define SERVICE_UUID "a8c1e5c0-3d5d-4a9d-8d5e-7c8b6a4e2f1a" -// #define SERVICE_UUID "4fafc201-1fb5-459e-8fcc-c5c9c331914b" -#define CHARACTERISTIC_UUID "beb5483e-36e1-4688-b7f5-ea07361b26a8" - -#define UART_RX_BUFFER_SIZE 4096 - -BLEServer* pServer = NULL; -BLECharacteristic* pCharacteristic = NULL; - - -bool deviceConnected = false; -bool oldDeviceConnected = false; -String receivedData = ""; -String completeData = ""; -unsigned long lastReceiveTime = 0; - -const char* influxDBHost = "8.134.11.76"; -const int influxDBPort = 8086; -const char* influxDBToken = "KuTa5ZsqoHIhi2IglOO06zExUYw1_mJ6K0mcA9X1y6O6CJDog3_Cgr8mUw1SwpuCCKRElqxa6wAhrrhsYPytkg=="; -const char* influxDBOrg = "gzlg"; -const char* influxDBBucket = "gzlg"; - -const unsigned long SENSOR_TIMEOUT = 40000; -static uint32_t packetCounter = 0; -static bool shouldSendOtherData = false; - -unsigned long lastSensorUpdate = 0; - -// 睡眠数据发送相关 -unsigned long lastSleepDataTime = 0; -const unsigned long SLEEP_DATA_INTERVAL = 5000; // 5秒 = 5000毫秒 - -// 更新SensorData结构体以支持R60ABD1的全部功能点 -typedef struct { - float breath_rate; // 呼吸率 (DP8) 单位:次/分钟 - float heart_rate; // 心率 (DP6) 单位:BPM - uint8_t breath_valid; // 呼吸率有效性 - uint8_t heart_valid; // 心率有效性 - uint8_t presence ; // 存在检测 (DP1) 0=无人, 1=有人 - uint8_t motion = 0; // 运动状态 (DP2) 0=无, 1=静止, 2=活跃 - int heartbeat_waveform; // 心跳波形 (DP7) 数值+128 - int breathing_waveform; // 呼吸波形 (DP10) 数值+128 - - // R60ABD1新增字段 - uint16_t distance; // 人体距离 (DP3) 单位:cm - uint8_t body_movement; // 体动幅度 (DP4) 0-100 - uint8_t breath_status = 04; // 呼吸信息 (DP9) 01=正常, 02=呼吸过高, 03=呼吸过低, 04=无 - uint8_t sleep_state = 3; // 睡眠状态 (DP12) 0=深睡, 1=浅睡, 2=清醒, 3=无睡眠 - uint32_t sleep_time; // 睡眠时长 (DP13) 单位:秒 - uint8_t sleep_score; // 睡眠质量评分 (DP14) 0-100 - uint8_t sleep_grade; //睡眠质量评级 - uint8_t bed_entry; // 入床/离床状态 (DP11) 0=离床, 1=入床 - uint8_t abnormal_state; // 异常状态 (DP18) 0=正常, 1=异常 - uint8_t avg_heart_rate; // 平均心率 (DP15) - uint8_t avg_breath_rate; // 平均呼吸率 (DP15) - uint8_t turn_count; // 翻身次数 (DP15) - uint8_t large_move_ratio;// 大幅度体动占比 (DP15) - uint8_t small_move_ratio;// 小幅度体动占比 (DP15) - - // 根据技术文档新增字段 - int16_t pos_x; // 人体X坐标 (DP5) - int16_t pos_y; // 人体Y坐标 (DP5) - int16_t pos_z; // 人体Z坐标 (DP5) - int8_t breath_waveform[5]; // 呼吸波形数据 (DP10) - int8_t heart_waveform[5]; // 心率波形数据 (DP7) - uint16_t deep_sleep_time; // 深睡时长 (DP15) 单位:分钟 - uint16_t light_sleep_time; // 浅睡时长 (DP14) 单位:分钟 - uint16_t awake_time; // 清醒时长 (DP13) 单位:分钟 - uint16_t sleep_total_time; // 睡眠总时长 (DP18) 单位:分钟 - uint8_t deep_sleep_ratio; // 深睡占比 (DP16) - uint8_t light_sleep_ratio; // 浅睡占比 (DP16) - uint8_t awake_ratio; // 清醒占比 (DP16) - uint8_t turnover_count; // 翻身次数 (DP16) - uint8_t struggle_alert; // 挣扎报警 (DP21) - uint8_t no_one_alert; // 无人计时报警 (DP22) - uint8_t bed_status = 02; //入床状态 (DP11) - uint8_t bed_Out_Time; // 离床时间 (DP11) - uint8_t apnea_count; // 呼吸暂停次数 -} SensorData; - -SensorData sensorData ; - -HardwareSerial mySerial1(1); // 使用UART1 -const int BAUD_RATE = 115200; -const int UART1_RX = 3; // UART1的RX引脚,配合原理图 -const int UART1_TX = 2; // UART1的TX引脚,配合原理图 - -// FreeRTOS任务和队列定义 -QueueHandle_t phaseDataQueue; -QueueHandle_t vitalDataQueue; -QueueHandle_t uartQueue = NULL; // 添加串口数据队列 -TaskHandle_t bleSendTaskHandle = NULL; -TaskHandle_t vitalSendTaskHandle = NULL; -TaskHandle_t uartProcessTaskHandle = NULL; // 添加串口处理任务句柄 - -#define QUEUE_SIZE 50 -#define TASK_STACK_SIZE 8192 -#define TASK_PRIORITY 1 - -typedef struct { - int heartbeat_waveform; - int breathing_waveform; -} PhaseData; - -typedef struct { - float heart_rate; - float breath_rate; - uint8_t presence; - uint8_t motion; - uint16_t distance; // 人体距离 - uint8_t sleep_state = 3; // 睡眠状态 - uint8_t sleep_score; // 睡眠质量评分 - uint8_t body_movement; // 体动幅度 - uint8_t breath_status; // 呼吸信息 - uint32_t sleep_time; // 睡眠时长 - uint8_t bed_entry; // 入床/离床状态 - uint8_t abnormal_state; // 异常状态 - uint8_t avg_heart_rate; // 平均心率 - uint8_t avg_breath_rate; // 平均呼吸率 - uint8_t turn_count; // 翻身次数 - uint8_t large_move_ratio;// 大幅度体动占比 - uint8_t small_move_ratio;// 小幅度体动占比 - int16_t pos_x; // 人体X坐标 - int16_t pos_y; // 人体Y坐标 - int16_t pos_z; // 人体Z坐标 - uint16_t deep_sleep_time; // 深睡时长 - uint16_t light_sleep_time; // 浅睡时长 - uint16_t awake_time; // 清醒时长 - uint16_t sleep_total_time; // 睡眠总时长 - uint8_t deep_sleep_ratio; // 深睡占比 - uint8_t light_sleep_ratio; // 浅睡占比 - uint8_t awake_ratio; // 清醒占比 - uint8_t turnover_count; // 翻身次数 - uint8_t struggle_alert; // 挣扎报警 - uint8_t no_one_alert; // 无人计时报警 - uint8_t bed_status; // 入床状态 - uint8_t apnea_count; // 呼吸暂停次数 - int heartbeat_waveform; // 心跳波形 - int breathing_waveform; // 呼吸波形 -} VitalData; - -// 睡眠数据结构 - 对应Python脚本中的sleep_data -typedef struct { - uint8_t sleepQualityScore; // 睡眠质量评分 (0~100) - uint16_t totalSleepDuration; // 睡眠总时长 (0~65535 分钟) - uint8_t awakeDurationRatio; // 清醒时长占比 (0~100) - uint8_t lightSleepRatio; // 浅睡时长占比 (0~100) - uint8_t deepSleepRatio; // 深睡时长占比 (0~100) - uint8_t outOfBedDuration; // 离床时长 (0~255) - uint8_t outOfBedCount; // 离床次数 (0~255) - uint8_t turnCount; // 翻身次数 (0~255) - uint8_t avgBreathingRate; // 平均呼吸 (0~25) - uint8_t avgHeartRate; // 平均心跳 (0~100) - uint8_t apneaCount; // 呼吸暂停次数 (0~10) -} SleepData; - -// 添加流量控制类 -class BLEFlowController { -private: - size_t maxBytesPerSecond; - size_t bytesSent; - unsigned long lastResetTime; - unsigned long lastSendTime; - -public: - BLEFlowController(size_t maxBps) : maxBytesPerSecond(maxBps), bytesSent(0) { - lastResetTime = millis(); - lastSendTime = 0; - } - - bool canSend(size_t dataSize) { - unsigned long currentTime = millis(); - - // 每秒重置计数器 - if(currentTime - lastResetTime >= 1000) { - bytesSent = 0; - lastResetTime = currentTime; - } - - // 检查速率限制(放宽限制) - if((bytesSent + dataSize) > maxBytesPerSecond) { - return false; - } - - // 最小发送间隔控制(重要!) - if(currentTime - lastSendTime < 5) { // 进一步减小最小间隔到5ms - return false; - } - - return true; - } - - bool check() { - // 这是一个简化版本的检查方法,只检查最小发送间隔 - unsigned long currentTime = millis(); - if(currentTime - lastSendTime < 5) { // 最小间隔5ms - return false; - } - return true; - } - - void recordSend(size_t dataSize) { - bytesSent += dataSize; - lastSendTime = millis(); - } - - void reset() { - bytesSent = 0; - lastResetTime = millis(); - lastSendTime = 0; - } -}; - -// 全局变量用于控制持续发送 -bool continuousSendEnabled = false; -unsigned long continuousSendInterval = 500; // 默认1秒发送一次 -BLEFlowController bleFlow(500); // 提高到500 B/s限制,进一步提高数据传输速率 - -void configClearLedTask(void *parameter); // 配置清除指示灯控制任务 -void bootButtonMonitorTask(void *parameter); // BOOT按钮监控任务 -void checkBootButton(); // 检查Boot按钮状态 -void clearStoredConfig(); // 清除所有存储的配置 -void ledControlTask(void *parameter); // LED控制任务 -void setNetworkStatus(NetworkStatus status); // 设置网络状态 - -void processBLEConfig(); -bool processWiFiConfigCommand(JsonDocument& doc); -bool processScanWiFi(JsonDocument& doc); -bool processEchoRequest(JsonDocument& doc); -void sendRawEchoResponse(const String& rawData); - -bool parseR60ABD1Frame(uint8_t *frame, uint16_t frameLen); // 解析传感器数据行 -int16_t parseSignedCoordinate(uint16_t raw_value); // 解析有符号坐标值 -void initR60ABD1(); // 初始化R60ABD1雷达 -void sendRadarCommand(uint8_t ctrl, uint8_t cmd, uint8_t value); // 发送雷达命令 - -void wifiMonitorTask(void *parameter); // WiFi监控任务 -void WiFiEvent(WiFiEvent_t event); // WiFi事件处理 -void loadDeviceId(); // 加载设备ID -void saveDeviceId(); // 保存设备ID -bool processSetDeviceId(JsonDocument& doc); // 处理设置设备ID命令 -void sendStatusToBLE(); // 发送状态信息到BLE -bool processQueryStatus(JsonDocument& doc); // 处理查询状态命令 - -// 发送日常数据到InfluxDB - 高频实时监测数据 -void sendDailyDataToInfluxDB(String dailyDataLine); - -// 发送睡眠数据到InfluxDB - 低频汇总数据 -void sendSleepDataToInfluxDB(); - -bool processQueryRadarData(JsonDocument& doc); // 处理查询雷达数据命令 -bool processStartContinuousSend(JsonDocument& doc); // 处理开始持续发送命令 -bool processStopContinuousSend(JsonDocument& doc); // 处理停止持续发送命令 -void sendRadarDataToBLE(); // 发送雷达数据到BLE -void sendDataInChunks(const String& data); // 分包发送函数 -void sendJSONDataToBLE(const String& jsonData); // 发送JSON数据到BLE(使用纯数据分包发送,不带包头) -bool sendCustomJSONData(const String& jsonType, const String& jsonString); // 发送自定义JSON数据到BLE - -// 根据协议ID获取对应的字段名 -String getFieldNameByProtocolId(int protocolId); -// 根据协议ID获取对应的字段名 -String getFieldNameByProtocolId(int protocolId) { - switch(protocolId) { - case PROTOCOL_HEART_RATE: - return "heartRate"; - case PROTOCOL_BREATH_RATE: - return "breathingRate"; - case PROTOCOL_PERSON_DETECTED: - return "personDetected"; - case PROTOCOL_HUMAN_ACTIVITY: - return "humanActivity"; - case PROTOCOL_HUMAN_DISTANCE: - return "humanDistance"; - case PROTOCOL_HUMAN_POSITION: - return "humanPosition"; - case PROTOCOL_SLEEP_STATE: - return "sleepState"; - default: - return "unknown"; - } -} - -// FreeRTOS任务函数声明 -void bleSendTask(void *parameter); -void vitalSendTask(void *parameter); -void radarDataTask(void *parameter); -void uartProcessTask(void *parameter); // 添加串口处理任务声明 - - -// 使用正确的函数签名 - 不带参数的回调函数 -void IRAM_ATTR serialRxCallback() { - // 直接使用全局变量mySerial1和uartQueue - if (uartQueue != NULL) { - // 读取所有可用数据 - while(mySerial1.available()) { - char c = mySerial1.read(); - - // 将字符放入队列(从中断安全函数) - BaseType_t xHigherPriorityTaskWoken = pdFALSE; - xQueueSendFromISR(uartQueue, &c, &xHigherPriorityTaskWoken); - - if(xHigherPriorityTaskWoken) { - portYIELD_FROM_ISR(); - } - } - } -} - -class MyServerCallbacks: public BLEServerCallbacks { - void onConnect(BLEServer* pServer) { - deviceConnected = true; - Serial.println("✅ [BLE] 客户端已连接"); - - // 发送连接状态信息 - sendStatusToBLE(); - }; - - void onDisconnect(BLEServer* pServer) { - deviceConnected = false; - Serial.println("🔴 [BLE] 客户端已断开"); - - // 重置持续发送状态 - continuousSendEnabled = false; - Serial.println("🔄 重置持续发送状态"); - } -}; - -class MyCallbacks: public BLECharacteristicCallbacks { - void onWrite(BLECharacteristic *pCharacteristic) { - std::string value = pCharacteristic->getValue(); - Serial.printf("🔵 [BLE] 收到写入数据,长度: %d 字节\n", value.length()); - - if (value.length() > 0) { - String fragment = ""; - for (int i = 0; i < value.length(); i++) - fragment += value[i]; - - Serial.printf("📄 [BLE] 接收数据片段: %s\n", fragment.c_str()); - - completeData += fragment; - lastReceiveTime = millis(); - - // 检查是否收到完整的JSON数据 - int openBrace = completeData.indexOf('{'); - int closeBrace = completeData.lastIndexOf('}'); - - if (openBrace >= 0 && closeBrace > openBrace) { - String jsonData = completeData.substring(openBrace, closeBrace + 1); - completeData = completeData.substring(closeBrace + 1); - - Serial.printf("📥 [BLE] 完整JSON数据: %s\n", jsonData.c_str()); - - // 将JSON数据存储到receivedData变量中供后续处理 - receivedData = jsonData; - } - } - } -}; - - - -bool processScanWiFi(JsonDocument& doc) { - const char* command = doc["command"]; - if (command != nullptr && strcmp(command, "scanWiFi") == 0) { - Serial.println("📱 [BLE-WiFi] 收到WiFi扫描命令"); - wifiManager.scanAndSendResults(); - return true; - } - return false; -} - -// 处理回显命令 - 将收到的任何数据原样返回 -bool processEchoRequest(JsonDocument& doc) { - const char* command = doc["command"]; - if (command != nullptr && strcmp(command, "echo") == 0) { - Serial.println("📱 [BLE] 收到回显请求"); - - // 获取要回显的内容 - const char* echoContent = doc["content"]; - if (echoContent != nullptr) { - String echoResponse = String("{\"type\":\"echoResponse\",\"originalContent\":\"") + - echoContent + String("\",\"receivedSuccessfully\":true}"); - - if (deviceConnected) { - sendJSONDataToBLE(echoResponse); - Serial.printf("📤 [BLE] 回显响应已发送: %s\n", echoContent); - } - } else { - // 如果没有内容参数,至少回应已收到 - String echoResponse = String("{\"type\":\"echoResponse\",\"receivedSuccessfully\":true,\"message\":\"Echo command received\"}"); - - if (deviceConnected) { - sendJSONDataToBLE(echoResponse); - Serial.println("📤 [BLE] 简单回显响应已发送"); - } - } - - return true; - } - return false; -} - -// 通用回显函数 - 将收到的任何原始数据返回 -void sendRawEchoResponse(const String& rawData) { - if (deviceConnected) { - String echoResponse = String("{\"type\":\"rawEchoResponse\",\"originalData\":\"") + - rawData + String("\",\"received\":true}"); - - sendJSONDataToBLE(echoResponse); - Serial.printf("📤 [BLE] 原始数据回显已发送: %s\n", rawData.c_str()); - } -} - -// 处理查询状态命令 -bool processQueryStatus(JsonDocument& doc) { - const char* command = doc["command"]; - if (command != nullptr && strcmp(command, "queryStatus") == 0) { - if (deviceConnected) { - String statusMsg = String("{\"type\":\"deviceStatus\",\"success\":true,\"deviceId\":") + - String(currentDeviceId) + - String(",\"wifiConfigured\":") + - String(wifiManager.getSavedNetworkCount() > 0 ? "true" : "false") + - String(",\"wifiConnected\":") + - String(WiFi.status() == WL_CONNECTED ? "true" : "false") + - String(",\"ipAddress\":\"") + - (WiFi.status() == WL_CONNECTED ? WiFi.localIP().toString() : "") + - String("\"}"); - - sendJSONDataToBLE(statusMsg); - Serial.println("已发送设备状态信息"); - } - return true; - } - return false; -} - -// 处理查询雷达数据命令(蓝牙) -//这个函数是一个响应式的数据发送函数,仅在接收到查询请求时才会发送当前的雷达传感器数据 -bool processQueryRadarData(JsonDocument& doc) { - const char* command = doc["command"]; - if (command != nullptr && strcmp(command, "queryRadarData") == 0) { - Serial.println("收到查询雷达数据命令"); - - // 发送最新的雷达数据 - if (deviceConnected) { - String radarDataMsg = String("{\"type\":\"radarData\",\"success\":true") + - String(",\"deviceId\":") + String(currentDeviceId) + // 设备ID - String(",\"timestamp\":") + String(millis()) + // 时间戳 - String(",\"presence\":") + String(sensorData.presence) + - String(",\"heartRate\":") + String(sensorData.heart_rate, 1) + - String(",\"breathRate\":") + String(sensorData.breath_rate, 1) + - String(",\"motion\":") + String(sensorData.motion) + - String(",\"heartbeatWaveform\":") + String((int)sensorData.breath_waveform[0]) + - String(",\"breathingWaveform\":") + String((int)sensorData.heart_waveform[0]) + - String(",\"distance\":") + String(sensorData.distance) + - String(",\"bodyMovement\":") + String(sensorData.body_movement) + - String(",\"breathStatus\":") + String(sensorData.breath_status) + - String(",\"sleepState\":") + String(sensorData.sleep_state) + - String(",\"sleepTime\":") + String(sensorData.sleep_time) + - String(",\"sleepScore\":") + String(sensorData.sleep_score) + - String(",\"avgHeartRate\":") + String(sensorData.avg_heart_rate) + - String(",\"avgBreathRate\":") + String(sensorData.avg_breath_rate) + - String(",\"turnCount\":") + String(sensorData.turn_count) + - String(",\"largeMoveRatio\":") + String(sensorData.large_move_ratio) + - String(",\"smallMoveRatio\":") + String(sensorData.small_move_ratio) + - String("}"); - - // 使用新的分包发送函数发送JSON数据 - sendJSONDataToBLE(radarDataMsg); - Serial.println("已发送雷达数据"); - Serial.printf("发送的数据: %s\n", radarDataMsg.c_str()); - } else { - Serial.println("BLE未连接,无法发送雷达数据"); - } - return true; - } - return false; -} - -// 处理开始持续发送命令 -bool processStartContinuousSend(JsonDocument& doc) { - const char* command = doc["command"]; - if (command != nullptr && strcmp(command, "startContinuousSend") == 0) { - // 获取发送间隔参数(可选,默认1000ms) - if (doc["interval"].is()) { - continuousSendInterval = doc["interval"].as(); - // 限制最小间隔为100ms,最大间隔为10000ms - if (continuousSendInterval < 100) continuousSendInterval = 100; - if (continuousSendInterval > 10000) continuousSendInterval = 10000; - } - - continuousSendEnabled = true; - bleFlow.reset(); // 重置流量控制器 - - Serial.printf("⚙️ 启动持续发送模式,间隔: %lu ms\n", continuousSendInterval); - - // 发送JSON格式的确认消息 - if (deviceConnected) { - String confirmMsg = String("{\"type\":\"startContinuousSendResult\",\"success\":true,\"message\":\"已启动持续发送模式\",\"interval\":") + - String(continuousSendInterval) + "}"; - - // 使用新的分包发送函数发送JSON数据 - sendJSONDataToBLE(confirmMsg); - Serial.println("✅ 启动确认消息发送成功"); - - delay(5); // 减少延迟以提高实时性 - Serial.println("🚀 已启动持续发送模式"); - } else { - Serial.println("❌ BLE未连接,无法发送确认消息"); - } - return true; - } - return false; -} - -// 处理停止持续发送命令 -bool processStopContinuousSend(JsonDocument& doc) { - const char* command = doc["command"]; - if (command != nullptr && strcmp(command, "stopContinuousSend") == 0) { - continuousSendEnabled = false; - - Serial.println("🛑 停止持续发送模式"); - - // 发送JSON格式的确认消息 - if (deviceConnected) { - String confirmMsg = String("{\"type\":\"stopContinuousSendResult\",\"success\":true,\"message\":\"已停止持续发送模式\"}"); - - // 使用新的分包发送函数发送JSON数据 - sendJSONDataToBLE(confirmMsg); - Serial.println("✅ 停止确认消息发送成功"); - - delay(5); // 减少延迟以提高实时性 - Serial.println("⏹️ 已停止持续发送模式"); - } else { - Serial.println("❌ BLE未连接,无法发送确认消息"); - } - return true; - } - return false; -} - -// 分包发送函数 - 优化版本,解决分包混乱和数据截断问题 -void sendDataInChunks(const String& data) { - const int MAX_PACKET_SIZE = 20; // BLE最大包大小 - const int HEADER_SIZE = 6; // 包头大小 "[N/M]" - const int CHUNK_SIZE = MAX_PACKET_SIZE - HEADER_SIZE; // 实际数据大小 - - int totalLength = data.length(); - int numChunks = (totalLength + CHUNK_SIZE - 1) / CHUNK_SIZE; - - // 只有在数据较长需要分包时才显示分包信息 - Serial.printf("📦 开始分包发送,总长度: %d, 分包数: %d\n", totalLength, numChunks); - - for(int i = 0; i < numChunks; i++) { - int start = i * CHUNK_SIZE; - int chunkLength = min(CHUNK_SIZE, totalLength - start); - String chunk = data.substring(start, start + chunkLength); - - // 构造简单包头: "[序号/总数]",确保总长度不超过6字符 - // 例如: "[1/5]" = 4字符 - String packetHeader = String("[") + String(i+1) + String("/") + String(numChunks) + String("]"); - - // 确保总包大小不超过20字节 - int maxDataLength = MAX_PACKET_SIZE - packetHeader.length(); - if (chunk.length() > maxDataLength) { - chunk = chunk.substring(0, maxDataLength); - } - - String packet = packetHeader + chunk; - - Serial.printf("📤 发送分包 %s: %s\n", packetHeader.c_str(), chunk.c_str()); - - // 检查BLE连接状态 - if (!deviceConnected) { - Serial.println("❌ BLE未连接,无法发送数据"); - return; - } - - // 发送数据包 - pCharacteristic->setValue(packet.c_str()); - pCharacteristic->notify(); - Serial.println("✅ 分包发送成功"); - - // 等待一段时间确保接收端处理完成,避免数据丢失 - if (i < numChunks - 1) { - Serial.printf("⏳ 等待接收端处理第%d个包...\n", i+1); - vTaskDelay(20 / portTICK_PERIOD_MS); // 非阻塞延时,允许调度 - } - } - - Serial.println("📦 分包发送完成"); -} - -// 新增:发送JSON数据到BLE(使用纯数据分包发送,不带包头) -void sendJSONDataToBLE(const String& jsonData) { - Serial.printf("📤 准备发送JSON数据: %s\n", jsonData.c_str()); - - // 检查BLE连接状态 - if (!deviceConnected) { - Serial.println("❌ BLE未连接,无法发送JSON数据"); - return; - } - - // 直接发送JSON数据,使用纯数据分包(不带包头) - const int MAX_PACKET_SIZE = 20; // BLE最大包大小 - int totalLength = jsonData.length(); - int numChunks = (totalLength + MAX_PACKET_SIZE - 1) / MAX_PACKET_SIZE; - - Serial.printf("📦 开始分包发送JSON,总长度: %d, 分包数: %d\n", totalLength, numChunks); - - for(int i = 0; i < numChunks; i++) { - int start = i * MAX_PACKET_SIZE; - int chunkLength = min(MAX_PACKET_SIZE, totalLength - start); - String chunk = jsonData.substring(start, start + chunkLength); - - Serial.printf("📤 发送JSON分包 %d/%d: %s\n", i+1, numChunks, chunk.c_str()); - - // 发送数据包 - pCharacteristic->setValue(chunk.c_str()); - pCharacteristic->notify(); - Serial.println("✅ JSON分包发送成功"); - - // 等待一段时间确保接收端处理完成,避免数据丢失 - if (i < numChunks - 1) { - Serial.printf("⏳ 等待接收端处理第%d个包...\n", i+1); - vTaskDelay(10 / portTICK_PERIOD_MS); // 非阻塞延时,允许调度 - } - } - - Serial.println("📦 JSON分包发送完成"); -} - -// 新增:发送自定义JSON数据到BLE -bool sendCustomJSONData(const String& jsonType, const String& jsonString) { - if (!deviceConnected) { - Serial.println("❌ BLE未连接,无法发送自定义JSON数据"); - return false; - } - - // 构造完整的JSON数据 - String fullJSON = String("{\"type\":\"") + jsonType + String("\",") + jsonString + String("}"); - - Serial.printf("📤 发送自定义JSON数据类型 '%s': %s\n", jsonType.c_str(), fullJSON.c_str()); - - // 使用纯数据分包发送函数发送JSON数据 - sendJSONDataToBLE(fullJSON); - - return true; -} - -// 发送雷达数据到BLE(已移至FreeRTOS任务处理) -void sendRadarDataToBLE() { - // 此函数已废弃,雷达数据发送现在由FreeRTOS任务处理 - // 保留此函数以防其他代码引用 - Serial.println("ℹ️ 雷达数据发送已移至FreeRTOS任务处理"); -} - -// 处理BLE配置和命令 -void processBLEConfig() { - // 增强超时处理机制 - if (completeData.length() > 0 && (millis() - lastReceiveTime > 3000)) { - Serial.println("⏰ [超时] 数据接收超时3秒,清空缓冲区"); - completeData = ""; - } - - if (receivedData.length() > 0) { - String bleData = receivedData; - receivedData = ""; - bleData.trim(); - - Serial.printf("⚙️ [BLE] 准备解析JSON: %s\n", bleData.c_str()); - - if (bleData.startsWith("{") && bleData.endsWith("}")) { - JsonDocument doc; - DeserializationError error = deserializeJson(doc, bleData); - - if (error) { - String errorMsg = String("❌ [BLE] JSON解析失败: ") + String(error.c_str()); - Serial.println(errorMsg); - if (deviceConnected) { - // 即使JSON解析失败,也发送原始数据的回显 - String responseMsg = String("{\"type\":\"error\",\"message\":\"配置格式错误,请使用JSON格式\",\"originalData\":\"") + bleData + String("\"}"); - - // 使用新的分包发送函数发送JSON数据 - sendJSONDataToBLE(responseMsg); - - // 同时发送原始数据回显 - sendRawEchoResponse(bleData); - } - } else { - Serial.println("✅ [BLE] JSON解析成功"); - - // 处理各种命令 - bool processed = false; - - // 处理设置设备ID命令 - if (!processed) processed = processSetDeviceId(doc); - - // 处理WiFi配置命令 - if (!processed) processed = processWiFiConfigCommand(doc); - - // 处理查询状态命令 - if (!processed) processed = processQueryStatus(doc); - - // 处理查询雷达数据命令 - if (!processed) processed = processQueryRadarData(doc); - - // 处理开始持续发送命令 - if (!processed) processed = processStartContinuousSend(doc); - - // 处理停止持续发送命令 - if (!processed) processed = processStopContinuousSend(doc); - - // 处理WiFi扫描命令 - if (!processed) processed = processScanWiFi(doc); - - // 处理回显请求 - if (!processed) processed = processEchoRequest(doc); - - // 如果没有处理任何命令,发送错误响应 - if (!processed) { - Serial.println("❓[BLE] 未知命令"); - if (deviceConnected) { - String responseMsg = String("{\"type\":\"error\",\"message\":\"未知命令\",\"receivedData\":\"") + bleData + String("\"}"); - - // 使用新的分包发送函数发送JSON数据 - sendJSONDataToBLE(responseMsg); - - // 同时发送原始数据回显 - sendRawEchoResponse(bleData); - } - } else { - // 如果命令被处理了,也发送回显确认 - sendRawEchoResponse(bleData); - } - } - } else { - Serial.println("📥 [BLE] 接收到非JSON数据"); - } - } -} - -// FreeRTOS任务:蓝牙数据发送(精简格式以节省空间) -//主动发送:这是一个持续运行的FreeRTOS任务,不断地从队列中获取数据并主动发送到蓝牙,用于实时数据传输 -void bleSendTask(void *parameter) -{ - Serial.println("🔁 R60ABD1蓝牙数据发送任务启动"); - - while (1) { - PhaseData phaseData; - - // 从队列接收相位数据用于蓝牙发送 - if (xQueueReceive(phaseDataQueue, &phaseData, portMAX_DELAY) == pdTRUE) { - esp_task_wdt_reset(); - - // 蓝牙传输 - 构造并发送完整的R60ABD1雷达数据 - if (deviceConnected && continuousSendEnabled) { - String radarDataCore; - - // 检查是否检测到人 - if (sensorData.presence > 0) - { - // 检测到人时,发送实际数据,包含R60ABD1扩展功能 - radarDataCore = String(sensorData.heart_rate, 1) + String("|") + - String(sensorData.breath_rate, 1) + String("|") + - String((int)sensorData.heart_waveform[0]) + String("|") + - String((int)sensorData.breath_waveform[0]) + String("|") + - String(sensorData.presence) + String("|") + - String(sensorData.motion) + String("|") + - // String(sensorData.distance) + String("|") + // R60ABD1距离 - String(sensorData.sleep_state); // R60ABD1睡眠状态 - // String(sensorData.sleep_time) + String("|") + // R60ABD1睡眠时长 - // String(sensorData.sleep_score); // R60ABD1睡眠评分 - } - else { - // 未检测到人时,发送零数据,但仍然包含实际的RSSI值 - radarDataCore = String("0.0") + String("|") + - String("0.0") + String("|") + - String("0") + String("|") + - String("0") + String("|") + - String("0") + String("|") + - String("0") + String("|") + - // String("0") + String("|") + // 距离 - String("0"); // 睡眠状态 - // String("0") + String("|") + // 睡眠时长 - // String("0"); // 睡眠评分 - } - - // 计算CRC校验和 - unsigned int crc = 0xFFFF; - for (int i = 0; i < radarDataCore.length(); i++) { - crc ^= (unsigned int)radarDataCore.charAt(i); - for (int j = 0; j < 8; j++) { - if (crc & 0x0001) { - crc >>= 1; - crc ^= 0xA001; - } else { - crc >>= 1; - } - } - } - - // 添加校验和到数据末尾 - String radarDataMsg = radarDataCore + String("|") + String(crc, HEX); - - Serial.printf("📤 通过蓝牙发送R60ABD1雷达数据: %s\n", radarDataMsg.c_str()); - - // 检查数据长度决定发送方式 - const int MAX_BLE_PACKET_SIZE = 20; // BLE最大包大小 - if (radarDataMsg.length() <= MAX_BLE_PACKET_SIZE) { - // 数据较短,直接发送 - pCharacteristic->setValue(radarDataMsg.c_str()); - pCharacteristic->notify(); - Serial.println("✅ R60ABD1雷达数据蓝牙发送成功"); - } else { - // 数据较长,使用分包发送 - Serial.println("🔄 R60ABD1雷达数据较长,使用分包发送"); - sendDataInChunks(radarDataMsg); - } - } - - esp_task_wdt_reset(); - } - - vTaskDelay(1 / portTICK_PERIOD_MS); // 减少延迟以提高实时性 - // 在BLE任务循环中重置看门狗 - esp_task_wdt_reset(); - } -} -// FreeRTOS任务:发送生命体征数据到数据库 -void vitalSendTask(void *parameter) { - Serial.println("🔁🔁 生命体征数据发送任务启动(WiFi数据库传输)"); - - // 记录上次发送睡眠数据的时间 - unsigned long lastSleepDataTime = 0; - const unsigned long SLEEP_DATA_INTERVAL = 5000; // 5秒 = 5000毫秒 - - while (1) { - VitalData vitalData; - - // 从队列接收生命体征数据用于WiFi数据库发送 - if (xQueueReceive(vitalDataQueue, &vitalData, portMAX_DELAY) == pdTRUE) { - esp_task_wdt_reset(); - - // WiFi数据库传输 - 按照Python脚本的双模块架构发送数据 - if (WiFi.status() == WL_CONNECTED) { - // 发送日常数据(高频实时监测数据)- daily_data - // 构造日常数据行协议 - String dailyDataLine = "daily_data,deviceId=" + String(currentDeviceId) + ",dataType=daily "; - - // 添加各个实时监测字段 - bool firstField = true; - - if (vitalData.heart_rate > 0) { - if (!firstField) dailyDataLine += ","; - dailyDataLine += "heartRate=" + String(vitalData.heart_rate, 1);// 心率 - firstField = false; - } - - if (vitalData.breath_rate > 0) { - if (!firstField) dailyDataLine += ","; - dailyDataLine += "breathingRate=" + String(vitalData.breath_rate, 1);// 呼吸率 - firstField = false; - } - - if (!firstField) dailyDataLine += ","; - dailyDataLine += "personDetected=" + String(vitalData.presence) + "i";// 人检 - firstField = false; - - if (!firstField) dailyDataLine += ","; - dailyDataLine += "humanActivity=" + String(vitalData.motion) + "i";// 活动 - firstField = false; - - if (vitalData.distance > 0) { - if (!firstField) dailyDataLine += ","; - dailyDataLine += "humanDistance=" + String(vitalData.distance) + "i";// 距离 - firstField = false; - } - - if (vitalData.sleep_state >= 0) { - if (!firstField) dailyDataLine += ","; - dailyDataLine += "sleepState=" + String(vitalData.sleep_state) + "i";// 睡眠状态 - firstField = false; - } - - // 添加人体坐标数据 (DP5) - X坐标 - if (!firstField) dailyDataLine += ","; - dailyDataLine += "humanPositionX=" + String(vitalData.pos_x) + "i";// X坐标 - firstField = false; - - // 添加人体坐标数据 (DP5) - Y坐标 - if (!firstField) dailyDataLine += ","; - dailyDataLine += "humanPositionY=" + String(vitalData.pos_y) + "i";// Y坐标 - firstField = false; - - // 添加人体坐标数据 (DP5) - Z坐标 - if (!firstField) dailyDataLine += ","; - dailyDataLine += "humanPositionZ=" + String(vitalData.pos_z) + "i";// Z坐标 - firstField = false; - - // 添加波形数据 - if (!firstField) dailyDataLine += ","; - dailyDataLine += "heartbeatWaveform=" + String((int)sensorData.heart_waveform[0]) + "i";// 心跳波形 - firstField = false; - - if (!firstField) dailyDataLine += ","; - dailyDataLine += "breathingWaveform=" + String((int)sensorData.breath_waveform[0]) + "i";// 呼吸波形 - firstField = false; - - // 添加异常状态 - if (!firstField) dailyDataLine += ","; - dailyDataLine += "abnormalState=" + String(vitalData.abnormal_state) + "i";// 异常状态 - firstField = false; - - // 添加入床状态 - if (!firstField) dailyDataLine += ","; - dailyDataLine += "bedStatus=" + String(vitalData.bed_status) + "i";// 入床状态 - firstField = false; - - // 挣扎警报和无人警报 - if (!firstField) dailyDataLine += ","; - dailyDataLine += "struggleAlert=" + String(vitalData.struggle_alert) + "i";// 挣扎警报 - firstField = false; - - if (!firstField) dailyDataLine += ","; - dailyDataLine += "noOneAlert=" + String(vitalData.no_one_alert) + "i";// 无人警报 - firstField = false; - - // 发送日常数据到数据库 - if (!dailyDataLine.endsWith(" ")) { // 确保有数据要发送 - sendDailyDataToInfluxDB(dailyDataLine); - esp_task_wdt_reset(); - delay(10); - } - - // 检查是否需要发送睡眠数据(低频汇总数据)- sleep_data - unsigned long currentTime = millis(); - if (currentTime - lastSleepDataTime >= SLEEP_DATA_INTERVAL) { - sendSleepDataToInfluxDB(); - lastSleepDataTime = currentTime; - Serial.println("⏰ 睡眠数据定时发送完成"); - } - } - } else { - Serial.println("❌❌ WiFi未连接,无法发送雷达数据到数据库"); - - // 定期打印WiFi状态以便调试 - static unsigned long lastWifiCheck = 0; - if (millis() - lastWifiCheck > 10000) { - Serial.printf("📶📶 WiFi状态: %d, 已配置: %s\n", - WiFi.status(), wifiManager.getSavedNetworkCount() > 0 ? "是" : "否"); - lastWifiCheck = millis(); - } - } - - esp_task_wdt_reset(); - } - - vTaskDelay(10 / portTICK_PERIOD_MS); // 适当延迟 - } - - -// 发送日常数据到InfluxDB - 高频实时监测数据 -void sendDailyDataToInfluxDB(String dailyDataLine) { - if (WiFi.status() != WL_CONNECTED) { - Serial.println("❌ WiFi未连接,无法发送日常数据到数据库"); - return; - } - - HTTPClient http; - http.setTimeout(2000); - - String url = String("http://") + String(influxDBHost) + ":" + String(influxDBPort) + "/api/v2/write?org=" + String(influxDBOrg) + "&bucket=" + String(influxDBBucket); - - http.begin(url); - http.addHeader("Authorization", String("Token ") + String(influxDBToken)); - http.addHeader("Content-Type", "text/plain; charset=utf-8"); - http.setReuse(true); - - Serial.println(String("📊 发送日常数据到InfluxDB: ") + dailyDataLine); - - int httpResponseCode = http.POST(dailyDataLine);// 发送日常数据到InfluxDB - - if (httpResponseCode == 204) { - Serial.println("✅ 日常数据发送成功"); - } else { - Serial.println(String("❌ 发送日常数据失败: ") + String(httpResponseCode) + " - " + http.getString()); - } - - http.end(); -} - -// 发送睡眠数据到InfluxDB - 模仿Python脚本的低频发送模式 -//这个函数是专门用于发送睡眠数据到InfluxDB数据库的 -void sendSleepDataToInfluxDB() { - if (WiFi.status() != WL_CONNECTED) { - Serial.println("❌ WiFi未连接,无法发送睡眠数据到数据库"); - return; - } - - // 检查总睡眠时长是否为0,如果为0则不上传数据 - if (sensorData.sleep_total_time == 0) { - Serial.println("😴 总睡眠时长为0,跳过上传睡眠数据"); - return; - } - - HTTPClient http; - http.setTimeout(2000); - - String url = String("http://") + String(influxDBHost) + ":" + String(influxDBPort) + "/api/v2/write?org=" + String(influxDBOrg) + "&bucket=" + String(influxDBBucket); - - http.begin(url); - http.addHeader("Authorization", String("Token ") + String(influxDBToken)); - http.addHeader("Content-Type", "text/plain; charset=utf-8"); - http.setReuse(true); - - // 构造睡眠数据的行协议格式 - String lineProtocol = String("sleep_data,deviceId=") + String(currentDeviceId) + ",dataType=sleep "; - - // 按顺序添加各个字段 - 构建睡眠分析数据 - String fields = ""; - fields += String("sleepQualityScore=") + String((int)sensorData.sleep_score) + "i";//睡眠评分 - fields += ",sleepQualityGrade=" + String((int)sensorData.sleep_grade) + "i";//睡眠质量评级 - fields += ",totalSleepDuration=" + String((int)sensorData.sleep_total_time) + "i";// 总睡眠时长 - fields += ",awakeDurationRatio=" + String((int)sensorData.awake_ratio) + "i";// 清醒时长占比 - fields += ",lightSleepRatio=" + String((int)sensorData.light_sleep_ratio) + "i";// 浅睡时长占比 - fields += ",deepSleepRatio=" + String((int)sensorData.deep_sleep_ratio) + "i";// 深睡时长占比 - fields += ",outOfBedDuration=" + String((int)sensorData.bed_Out_Time) + "i";// 离床时长 - fields += ",outOfBedCount=" + String((int)sensorData.turn_count) + "i";// 离床次数 - fields += ",turnCount=" + String((int)sensorData.turnover_count) + "i";// 转身次数 - fields += ",avgBreathingRate=" + String((int)sensorData.avg_breath_rate) + "i";// 平均呼吸率 - fields += ",avgHeartRate=" + String((int)sensorData.avg_heart_rate) + "i";// 平均心率 - fields += ",apneaCount=" + String((int)sensorData.apnea_count) + "i";// 呼吸暂停次数 - fields += ",abnormalState=" + String((int)sensorData.abnormal_state) + "i";// 睡眠异常状态 - fields += ",bodyMovement=" + String((int)sensorData.body_movement) + "i";// 体动幅度 - fields += ",breathStatus=" + String((int)sensorData.breath_status) + "i";// 呼吸状态 - fields += ",sleepState=" + String((int)sensorData.sleep_state) + "i";// 睡眠状态 - fields += ",largeMoveRatio=" + String((int)sensorData.large_move_ratio) + "i";// 大幅度体动占比 - fields += ",smallMoveRatio=" + String((int)sensorData.small_move_ratio) + "i";// 小幅度体动占比 - fields += ",struggleAlert=" + String((int)sensorData.struggle_alert) + "i";// 挣扎警报 - fields += ",noOneAlert=" + String((int)sensorData.no_one_alert) + "i";// 无人警报 - fields += ",awakeDuration=" + String((int)sensorData.awake_time) + "i";// 清醒时长 - fields += ",lightSleepDuration=" + String((int)sensorData.light_sleep_time) + "i";// 浅睡时长 - fields += ",deepSleepDuration=" + String((int)sensorData.deep_sleep_time) + "i";// 深睡时长 - // // 添加波形数组数据(前3个点) - // fields += ",breathWaveform1=" + String((int)sensorData.breath_waveform[0]) + "i";// 呼吸波形点1 - // fields += ",breathWaveform2=" + String((int)sensorData.breath_waveform[1]) + "i";// 呼吸波形点2 - // fields += ",breathWaveform3=" + String((int)sensorData.breath_waveform[2]) + "i";// 呼吸波形点3 - // fields += ",heartWaveform1=" + String((int)sensorData.heart_waveform[0]) + "i";// 心率波形点1 - // fields += ",heartWaveform2=" + String((int)sensorData.heart_waveform[1]) + "i";// 心率波形点2 - // fields += ",heartWaveform3=" + String((int)sensorData.heart_waveform[2]) + "i";// 心率波形点3 - - lineProtocol += fields; - - Serial.println(String("🌙 发送睡眠数据到InfluxDB: ") + lineProtocol); - - int httpResponseCode = http.POST(lineProtocol);// 发送睡眠数据到InfluxDB - - if (httpResponseCode == 204) { - Serial.println(String("✅ 睡眠数据已保存到InfluxDB设备") + String(currentDeviceId) + "上"); - } else { - Serial.println(String("❌ 保存睡眠数据到InfluxDB失败: ") + String(httpResponseCode) + " - " + http.getString()); - } - - http.end(); -} - -// 雷达数据处理任务 - 高优先级 -void radarDataTask(void *parameter) { - Serial.println("🔁 雷达数据处理任务启动(最高优先级)"); - - while (1) { - // 雷达数据处理已移至专用任务处理,这里不需要做任何事情 - // 串口数据现在由uartProcessTask处理 - - // 短暂延迟以允许其他任务运行 - vTaskDelay(100 / portTICK_PERIOD_MS); - } -} - - -// 修改串口处理任务以支持二进制协议 -//存储数据:将解析后的数据分别存储到两个结构体中:vitalData,phaseData -void uartProcessTask(void *parameter) { - uint8_t buffer[256]; // 接收缓冲区 - int bufferIndex = 0; - bool inFrame = false; - uint8_t prevByte = 0; - - Serial.println("✅ R60ABD1串口数据处理任务启动"); - - while(1) { - uint8_t c; - // 从队列接收字节(最多等待10ms) - if(xQueueReceive(uartQueue, &c, 10 / portTICK_PERIOD_MS) == pdTRUE) { - // 重置看门狗,防止超时 - esp_task_wdt_reset(); - if(!inFrame) { - // 寻找帧头 - if(prevByte == FRAME_HEADER1 && c == FRAME_HEADER2) { - // 找到帧头,开始接收帧 - buffer[0] = FRAME_HEADER1; - buffer[1] = FRAME_HEADER2; - bufferIndex = 2; - inFrame = true; - Serial.println("🔍 检测到R60ABD1帧头"); - } - } else { - // 在帧中接收数据 - if(bufferIndex < sizeof(buffer)) { - buffer[bufferIndex++] = c; - - // 检查是否到达帧尾 - if(bufferIndex >= 8 && // 至少包含基本帧头信息 - buffer[bufferIndex-2] == FRAME_TAIL1 && - buffer[bufferIndex-1] == FRAME_TAIL2) { - // 完整帧接收完成,进行解析 - if(parseR60ABD1Frame(buffer, bufferIndex)) { - // 数据解析成功,更新统计 - static uint32_t frameCounter = 0; - frameCounter++; - - // 更新全局传感器数据 - lastSensorUpdate = millis(); - - // 使用FreeRTOS队列发送数据到任务 - static uint32_t phasePacketCounter = 0; - static uint32_t vitalPacketCounter = 0; - - phasePacketCounter++; - if (phasePacketCounter >= PHASE_SEND_INTERVAL) { - PhaseData phaseData; - phaseData.heartbeat_waveform = sensorData.heartbeat_waveform;// 心率波形数据 - phaseData.breathing_waveform = sensorData.breathing_waveform;// 呼吸波形数据 - - if (xQueueSend(phaseDataQueue, &phaseData, 0) == pdTRUE) { - // 成功发送到队列 - } else { - Serial.println("❌ 相位数据队列已满,数据丢失"); - } - phasePacketCounter = 0; - } - - vitalPacketCounter++; - if (vitalPacketCounter >= VITAL_SEND_INTERVAL) { - VitalData vitalData; - vitalData.heart_rate = sensorData.heart_rate;// 心率数据 - vitalData.breath_rate = sensorData.breath_rate;// 呼吸率数据 - vitalData.presence = sensorData.presence;// 人员检测数据 - vitalData.motion = sensorData.motion;// 运动数据 - vitalData.distance = sensorData.distance;// 距离数据 - vitalData.sleep_state = sensorData.sleep_state;// 睡眠状态数据 - vitalData.sleep_score = sensorData.sleep_score;// 睡眠评分数据(无) - vitalData.body_movement = sensorData.body_movement;// 体动数据 - vitalData.breath_status = sensorData.breath_status;// 呼吸状态数据(去) - vitalData.sleep_time = sensorData.sleep_time;// 睡眠时间数据 - vitalData.bed_status = sensorData.bed_status;//入床状态数据 - vitalData.abnormal_state = sensorData.abnormal_state;// 异常状态数据 - vitalData.avg_heart_rate = sensorData.avg_heart_rate;// 平均心率数据 - vitalData.avg_breath_rate = sensorData.avg_breath_rate;// 平均呼吸率数据 - vitalData.turn_count = sensorData.turn_count;// 离床次数数据 - vitalData.large_move_ratio = sensorData.large_move_ratio;// 大动作比例数据 - vitalData.small_move_ratio = sensorData.small_move_ratio;// 小动作比例数据 - vitalData.pos_x = sensorData.pos_x;// X坐标数据 - vitalData.pos_y = sensorData.pos_y;// Y坐标数据 - vitalData.pos_z = sensorData.pos_z;// Z坐标数据 - vitalData.deep_sleep_time = sensorData.deep_sleep_time;// 深睡时间数据 - vitalData.light_sleep_time = sensorData.light_sleep_time;// 浅睡时间数据 - vitalData.awake_time = sensorData.awake_time;// 唤醒时间数据 - vitalData.sleep_total_time = sensorData.sleep_total_time;// 睡眠总时间数据(去) - vitalData.deep_sleep_ratio = sensorData.deep_sleep_ratio;// 深睡比例数据(去) - vitalData.light_sleep_ratio = sensorData.light_sleep_ratio;// 浅睡比例数据(去) - vitalData.awake_ratio = sensorData.awake_ratio;// 清醒比例数据(去) - vitalData.turnover_count = sensorData.turnover_count;// 离床次数数据(去) - vitalData.struggle_alert = sensorData.struggle_alert;// 挤压警报数据 - vitalData.no_one_alert = sensorData.no_one_alert;// 无人警报数据 - vitalData.apnea_count = sensorData.apnea_count;// 呼吸暂停次数数据(去) - vitalData.heartbeat_waveform = sensorData.heartbeat_waveform;// 心率波形数据 - vitalData.breathing_waveform = sensorData.breathing_waveform;// 呼吸波形数据 - - if (xQueueSend(vitalDataQueue, &vitalData, 0) == pdTRUE) { - Serial.println("📤 生命体征数据已加入发送队列"); - } else { - Serial.println("❌ 生命体征数据队列已满,数据丢失"); - } - vitalPacketCounter = 0; - } - - if(frameCounter % 100 == 0) { - Serial.printf("📈 已处理 %d 个R60ABD1数据帧\n", frameCounter); - } - - } - - // 重置状态,准备接收下一帧 - inFrame = false; - } - } else { - // 缓冲区溢出,重置状态 - Serial.println("⚠️ R60ABD1帧缓冲区溢出,重置接收状态"); - inFrame = false; - } - } - - prevByte = c; - } - - // 允许其他任务运行 - vTaskDelay(1 / portTICK_PERIOD_MS); - // 在循环中定期重置看门狗 - esp_task_wdt_reset(); - } -} - -// 替换现有的parseSensorLine函数 -bool parseR60ABD1Frame(uint8_t *frame, uint16_t frameLen) { - if(frameLen < 8) return false; // 最小帧长度检查 - - // 验证帧头和帧尾 - if(frame[0] != FRAME_HEADER1 || frame[1] != FRAME_HEADER2 || - frame[frameLen-2] != FRAME_TAIL1 || frame[frameLen-1] != FRAME_TAIL2) { - return false; - } - - // 验证校验和 - uint8_t checksum = 0; - for(int i = 0; i < frameLen-3; i++) { // 不包括校验和字节和帧尾 - checksum += frame[i]; - // 在长循环中重置看门狗,防止超时 - if(i % 50 == 0) { - esp_task_wdt_reset(); - } - } - if(checksum != frame[frameLen-3]) { // 校验和在倒数第三个字节 - Serial.println("❌ R60ABD1帧校验和错误"); - return false; - } - - for(int i = 0; i < frameLen && i < 20; i++) { // 显示前20个字节 - Serial.printf("%02X ", frame[i]); - } - if(frameLen > 20) Serial.print("... "); - Serial.printf("| 校验:0x%02X\n", frame[frameLen-3]); - - // 提取帧内容 - uint8_t ctrlByte = frame[2]; // 控制字 - uint8_t cmdByte = frame[3]; // 命令字 - uint16_t dataLen = (frame[4] << 8) | frame[5]; // 数据长度 - - // 根据控制字和命令字处理不同类型的数据 - switch(ctrlByte) - { - case CTRL_PRESENCE: - // 根据技术文档,0x80控制字的不同命令字对应不同的数据点 - switch(cmdByte) - { - // 开关人体存在监测 - case 0x00: - case 0x80: - if(dataLen >= 1) { - if(frame[6] == 0x01) { - Serial.println("🔄 人体存在监测功能已开启"); - sendRadarCommand(0x84, 0x00, 0x01); // 睡眠监测 - delay(50); - } else { - Serial.println("🔄 人体存在监测功能已关闭"); - } - } - break; - - // DP1: 存在信息主动上报 - case 0x01: - if(dataLen >= 1) { - sensorData.presence = frame[6]; // 0:无人, 1:有人 - Serial.printf("👤 人体存在: %s\n", sensorData.presence ? "有人" : "无人"); - } - break; - - // DP2: 运动信息上报 - case 0x02: - if(dataLen >= 1) { - sensorData.motion = frame[6]; // 0:无, 1:静止, 2:活跃 - const char* states[] = {"无", "静止", "活跃"}; - Serial.printf("🏃 运动状态: %s\n", states[sensorData.motion]); - } - break; - - // DP3: 体动参数 - case 0x03: - if(dataLen >= 1) { - sensorData.body_movement = frame[6]; // 0-100 - Serial.printf("📊体动参数: %d\n", sensorData.body_movement); - } - break; - - // DP4: 人体距离 - case 0x04: - if(dataLen >= 2) { - sensorData.distance = ((uint16_t)frame[6] << 8) | frame[7]; // 单位:厘米 - Serial.printf("📏人体距离: %d cm\n", sensorData.distance); - } - break; - - // DP5: 人体方位坐标 - 修正版 - case 0x05: - if(dataLen >= 6) { - // 6字节: X(2B), Y(2B), Z(2B) - // 从frame[6]开始是数据域 - - // 解析X坐标 - uint16_t x_raw = ((uint16_t)frame[6] << 8) | frame[7]; - sensorData.pos_x = parseSignedCoordinate(x_raw); - - // 解析Y坐标 - uint16_t y_raw = ((uint16_t)frame[8] << 8) | frame[9]; - sensorData.pos_y = parseSignedCoordinate(y_raw); - - // 解析Z坐标 - uint16_t z_raw = ((uint16_t)frame[10] << 8) | frame[11]; - sensorData.pos_z = parseSignedCoordinate(z_raw); - - // 调试输出 - Serial.printf("📍方位坐标 - 原始: X=0x%04X, Y=0x%04X, Z=0x%04X\n", - x_raw, y_raw, z_raw); - Serial.printf(" 解析后: X=%d, Y=%d, Z=%d cm\n", - sensorData.pos_x, sensorData.pos_y, sensorData.pos_z); - } - break; - - default: - Serial.printf("❓未知的0x80命令字: 0x%02X\n", cmdByte); - break; - } - break; - - case CTRL_BREATH: - // 根据技术文档,0x81控制字的不同命令字对应不同的呼吸相关数据点 - switch(cmdByte) { - // 开关呼吸监测功能 - case 0x00: - case 0x80: - if(dataLen >= 1) { - if(frame[6] == 0x01) - Serial.println("🔄 呼吸监测功能已开启"); - else - Serial.println("🔄 呼吸监测功能已关闭"); - } - break; - - // DP9: 呼吸信息 - case 0x01: - if(dataLen >= 1) { - sensorData.breath_status = frame[6]; - const char* info_str[] = {"", "正常", "呼吸过高(>25)", "呼吸过低(<10)", "无"}; - if(sensorData.breath_status >= 1 && sensorData.breath_status <= 4) { - Serial.printf("🔍 呼吸信息: %s\n", info_str[sensorData.breath_status]); - } else { - Serial.printf("🔍 呼吸信息: 未知状态(0x%02X)\n", sensorData.breath_status); - } - } - break; - - // DP8: 呼吸数值上报 - case 0x02: - if(dataLen >= 1) { - sensorData.breath_rate = (float)frame[6]; // 0-35次/分钟 - sensorData.breath_valid = (sensorData.breath_rate >= 0.0f && - sensorData.breath_rate <= 35.0f); - Serial.printf("💨 呼吸率: %.1f 次/分\n", sensorData.breath_rate); - } - break; - - // DP10: 呼吸波形 - case 0x05: - if(dataLen >= 5) { - // 1秒上报5个点,每个点需要减去128 - for(int i = 0; i < 5 && i < dataLen; i++) { - sensorData.breath_waveform[i] = (int8_t)(frame[6+i] - 128); - } - // 示例:打印第一个点 - Serial.printf("📈 呼吸波形: %d\n", sensorData.breath_waveform[0]); - } - break; - - default: - Serial.printf("❓未知的0x81命令字: 0x%02X\n", cmdByte); - break; - } - break; - - case CTRL_HEARTRATE: - // 根据技术文档,0x85控制字的不同命令字对应不同的心率相关数据点 - switch(cmdByte) { - // 开关心率监测功能 - case 0x00: - case 0x80: - if(dataLen >= 1) { - if(frame[6] == 0x01) - Serial.println("🔄 心率监测功能已开启"); - else - Serial.println("🔄 心率监测功能已关闭"); - } - break; - // DP6: 心跳数值 - case 0x02: - if(dataLen >= 1) { - sensorData.heart_rate = (float)frame[6]; // 60-120次/分钟 - sensorData.heart_valid = (sensorData.heart_rate >= 60.0f && - sensorData.heart_rate <= 120.0f); - Serial.printf("❤️ 心率: %.1f 次/分\n", sensorData.heart_rate); - } - break; - - // DP7: 心率波形 - case 0x05: - if(dataLen >= 5) { - // 1秒上报5个点,每个点需要减去128 - for(int i = 0; i < 5 && i < dataLen; i++) { - sensorData.heart_waveform[i] = (int8_t)(frame[6+i] - 128); - } - Serial.printf("📈 心率波形: %d\n", sensorData.heart_waveform[0]); - } - break; - - default: - Serial.printf("❓未知的0x85命令字: 0x%02X\n", cmdByte); - break; - } - break; - - case CTRL_SLEEP: - // 根据技术文档,0x84控制字的不同命令字对应不同的睡眠相关数据点 - switch(cmdByte) { - // DP10: 开关睡眠监测功能 - case 0x00: - case 0x80: - if(dataLen >= 1) { - if(frame[6] == 0x01) - Serial.println("🔄 睡眠监测功能已开启"); - else - Serial.println("🔄 睡眠监测功能已关闭"); - } - break; - - // DP11: 入床/离床状态 - case 0x01: - case 0x81: - if(dataLen >= 1) { - sensorData.bed_status = frame[6]; // 0x00:离床, 0x01:入床, 0x02:无 - const char* status_str[] = {"离床", "入床", "无"}; - Serial.printf("🛏️ 床状态: %s\n", status_str[sensorData.bed_status]); - } - break; - - // DP13: 清醒时长 (2字节,单位:分钟) - case 0x03: - case 0x83: - if(dataLen >= 2) { - sensorData.awake_time = ((uint16_t)frame[6] << 8) | (uint16_t)frame[7]; - Serial.printf("⏰ 清醒时长: %d 分钟\n", sensorData.awake_time); - } - break; - - // DP14: 浅睡时长 (2字节,单位:分钟) - case 0x04: - case 0x84: - if(dataLen >= 2) { - sensorData.light_sleep_time = ((uint16_t)frame[6] << 8) | (uint16_t)frame[7]; - Serial.printf("😪 浅睡时长: %d 分钟\n", sensorData.light_sleep_time); - } - break; - - // DP15: 深睡时长 (2字节,单位:分钟) - case 0x05: - case 0x85: - if(dataLen >= 2) { - sensorData.deep_sleep_time = ((uint16_t)frame[6] << 8) | (uint16_t)frame[7]; - Serial.printf("💤 深睡时长: %d 分钟\n", sensorData.deep_sleep_time); - } - break; - - // DP16: 睡眠质量评分 (1字节,0-100分) - case 0x06: - //case 0x86: - if(dataLen >= 1) { - sensorData.sleep_score = frame[6]; - Serial.printf("⭐ 睡眠质量评分: %d 分\n", sensorData.sleep_score); - } - break; - - case 0x86: - if(dataLen >= 2) { - //sensorData.sleep_score = ((uint16_t)frame[6] << 8) | (uint16_t)frame[7]; - sensorData.sleep_score = frame[6]; - Serial.printf("⭐ 睡眠质量评分: %d 分\n", sensorData.sleep_score); - } - break; - - - - // DP17: 睡眠综合状态 (8字节) - case 0x0C: - case 0x8D: - if(dataLen >= 8) { - sensorData.presence = frame[6]; // 存在状态: 1有人, 0无人 - sensorData.sleep_state = frame[7]; // 睡眠状态: 3离床, 2清醒, 1浅睡, 0深睡 - sensorData.avg_breath_rate = frame[8]; // 平均呼吸率 - sensorData.avg_heart_rate = frame[9]; // 平均心率 - sensorData.turnover_count = frame[10]; // 翻身次数 - sensorData.large_move_ratio = frame[11]; // 大幅度体动占比(0-100) - sensorData.small_move_ratio = frame[12]; // 小幅度体动占比(0-100) - sensorData.apnea_count = frame[13]; // 呼吸暂停次数 - - Serial.printf("📊 睡眠综合状态 - 存在:%d, 睡眠状态:%d, 睡眠平均呼吸:%d, 睡眠平均心率:%d, 翻身次数:%d, 大动占比:%d%%, 小动占比:%d%%, 呼吸暂停次数:%d\n", - sensorData.presence, sensorData.sleep_state, - sensorData.avg_breath_rate, sensorData.avg_heart_rate, - sensorData.turnover_count, sensorData.large_move_ratio, - sensorData.small_move_ratio, sensorData.apnea_count); - } - break; - - // DP18: 睡眠质量分析报告 (12字节) - case 0x0D: - case 0x8F: - if(dataLen >= 12) { - sensorData.sleep_score = frame[6]; // 1B 睡眠评分 - sensorData.sleep_total_time = ((uint16_t)frame[7] << 8) | (uint16_t)frame[8]; // 2B 总时长(分钟) - - // 修正:以下三项为1字节的百分比,非2字节的绝对时长 - sensorData.awake_ratio = frame[9]; // 1B 清醒时长占比 - sensorData.light_sleep_ratio = frame[10]; // 1B 浅睡时长占比 - sensorData.deep_sleep_ratio = frame[11]; // 1B 深睡时长占比 - - sensorData.bed_Out_Time = frame[12]; // 1B 离床时长 - sensorData.turn_count = frame[13]; // 1B 离床次数 - sensorData.turnover_count = frame[14]; // 1B 翻身次数 - sensorData.avg_breath_rate = frame[15]; // 1B 平均呼吸率 - sensorData.avg_heart_rate = frame[16]; // 1B 平均心跳 - sensorData.apnea_count = frame[17]; // 1B 呼吸暂停次数 - - // 打印日志也应相应修改 - Serial.printf("📈 睡眠分析报告 - 评分:%d, 总时长:%d分, 清醒占比:%d%%, 浅睡占比:%d%%, 深睡占比:%d%%, 离床时长:%d, 离床次数:%d, 翻身:%d次, 平均呼吸:%d, 平均心跳:%d\n", - sensorData.sleep_score, - sensorData.sleep_total_time, - sensorData.awake_ratio, - sensorData.light_sleep_ratio, - sensorData.deep_sleep_ratio, - sensorData.bed_Out_Time, - sensorData.turn_count, - sensorData.turnover_count, - sensorData.avg_breath_rate, - sensorData.avg_heart_rate); - } - break; - - // DP19: 睡眠异常上报 - case 0x0E: - case 0x8E: - if(dataLen >= 1) { - sensorData.abnormal_state = frame[6]; - const char* abnormal_str[] = { - "睡眠时长不足4小时", "睡眠时长大于12小时", "长时间异常无人" - }; - if(sensorData.abnormal_state < 3) { - Serial.printf("⚠️ 睡眠异常: %s\n", abnormal_str[sensorData.abnormal_state]); - } - } - break; - - // DP20: 睡眠质量评级 - case 0x10: - case 0x90: - if(dataLen >= 1) { - sensorData.sleep_grade = frame[6]; - const char* rating_str[] = {"无", "睡眠质量良好", "睡眠质量一般", "睡眠质量较差"}; - if(sensorData.sleep_grade < 4) { - Serial.printf("🏆 睡眠质量评级: %s\n", rating_str[sensorData.sleep_grade]); - } - } - break; - - // DP21: 异常挣扎状态 - case 0x11: - case 0x91: - if(dataLen >= 1) { - sensorData.struggle_alert = frame[6]; // 0x00:无, 0x01:正常, 0x02:异常挣扎 - const char* struggle_str[] = {"无", "正常", "异常挣扎"}; - if(sensorData.struggle_alert < 3) { - Serial.printf("⚠️ 挣扎状态: %s\n", struggle_str[sensorData.struggle_alert]); - } - } - break; - - // DP22: 无人计时状态 - case 0x12: - case 0x92: - if(dataLen >= 1) { - sensorData.no_one_alert = frame[6]; // 0x00:无, 0x01:正常, 0x02:异常 - const char* no_one_str[] = {"无", "正常", "异常"}; - if(sensorData.no_one_alert < 3) { - Serial.printf("⏰ 无人计时状态: %s\n", no_one_str[sensorData.no_one_alert]); - } - } - break; - - default: - Serial.printf("❓未知的0x84命令字: 0x%02X\n", cmdByte); - break; - } - break; - - // case 0x01: // 心跳包标识 - // Serial.println("💓 心跳包"); - // break; - - case 0x07: // 雷达探测范围信息 - if(dataLen >= 1) { - if(frame[6] == 0x00) - Serial.println("雷达探测范围外"); - else - Serial.println("雷达探测范围内"); - } - break; - - default: - Serial.printf("❓未知控制字: 0x%02X\n", ctrlByte); - break; - } - - - - - // 更新传感器时间戳 - lastSensorUpdate = millis(); - - // 验证数据有效性 - sensorData.heart_valid = (sensorData.heart_rate > 0 && sensorData.heart_rate < 200); - sensorData.breath_valid = (sensorData.breath_rate >= 0.1f && sensorData.breath_rate <= 60.0f); - - if( sensorData.heart_valid ==1 && sensorData.heart_valid == 1 && presence_Bit == 1 ) - { - sensorData.presence = 1; - presence_Bit = 0; - } - - return true; -} - -// 解析有符号坐标值 -int16_t parseSignedCoordinate(uint16_t raw_value) { - // 方法1:手动解析符号位 - bool is_negative = (raw_value & 0x8000) != 0; // 检查最高位 - uint16_t magnitude = raw_value & 0x7FFF; // 取低15位数值 - - int16_t result = (int16_t)magnitude; - if (is_negative) { - result = -result; // 如果是负数,加上负号 - } - - return result; -} - -// 修正后的发送雷达命令函数 -void sendRadarCommand(uint8_t ctrl, uint8_t cmd, uint8_t value) { - uint8_t command[10]; - command[0] = 0x53; // 帧头1 - command[1] = 0x59; // 帧头2 - command[2] = ctrl; // 控制字 - command[3] = cmd; // 命令字 - command[4] = 0x00; // 长度高字节 - command[5] = 0x01; // 长度低字节 - command[6] = value; // 数据 - - // 修正校验和计算:帧头(2) + 控制字(1) + 命令字(1) + 长度(2) + 数据(1) = 前7个字节 - uint8_t checksum = 0; - for(int i = 0; i < 7; i++) { // 计算前7个字节的和 - checksum += command[i]; - } - command[7] = checksum; // 校验码 - - // 帧尾 - command[8] = 0x54; // 帧尾1 - command[9] = 0x43; // 帧尾2 - - // 发送命令 - mySerial1.write(command, 10); - - // 调试输出 - Serial.printf("📤 发送: "); - for(int i = 0; i < 10; i++) { - Serial.printf("%02X ", command[i]); - } - Serial.println(); - - Serial.printf(" 控制字=0x%02X, 命令字=0x%02X, 值=0x%02X, 校验和=0x%02X\n", - ctrl, cmd, value, checksum); -} - -// 处理设置设备ID命令 -bool processSetDeviceId(JsonDocument& doc) { - const char* command = doc["command"]; - if (command != nullptr && strcmp(command, "setDeviceId") == 0) { - // 处理设置设备ID的命令 - uint16_t newDeviceId = doc["newDeviceId"]; - - //验证设备ID范围 - if (newDeviceId < MIN_DEVICE_ID || newDeviceId > MAX_DEVICE_ID){ - Serial.printf("[错误] 设备ID超出范围,有效范围: %d-%d\n", MIN_DEVICE_ID, MAX_DEVICE_ID); - if (deviceConnected) { - String errorMsg = String("{\"type\":\"error\",\"message\":\"设备ID超出范围,有效范围: ") + - String(MIN_DEVICE_ID) + "-" + String(MAX_DEVICE_ID) + "\"}"; - - // 使用新的分包发送函数发送JSON数据 - sendJSONDataToBLE(errorMsg); - } - return true; // 已处理该命令 - } - - currentDeviceId = newDeviceId; - - Serial.printf("[设备ID] 已设置新的设备ID: %u\n", currentDeviceId); - - // 保存设备ID到Preferences - saveDeviceId(); - - // 发送确认消息 - if (deviceConnected) { - String confirmMsg = String("{\"type\":\"setDeviceIdResult\",\"success\":true,\"message\":\"设备ID设置成功\",\"newDeviceId\":") + - String(newDeviceId) + "}"; - - // 使用新的分包发送函数发送JSON数据 - sendJSONDataToBLE(confirmMsg); - - // 发送更新后的状态信息 - sendStatusToBLE(); - } - return true; - } - return false; -} - -// 加载设备ID -void loadDeviceId() { - currentDeviceId = preferences.getUShort("deviceId", 1001);// 从Flash加载设备ID - Serial.printf("从Flash加载设备ID: %u\n", currentDeviceId); -} - -// 保存设备ID -void saveDeviceId() { - preferences.putUShort("deviceId", currentDeviceId);// 保存设备ID到Flash - Serial.printf("设备ID已保存到Flash: %u\n", currentDeviceId); -} - -// 发送状态信息到BLE -void sendStatusToBLE() { - if (deviceConnected) { - String statusMsg = String("{\"type\":\"status\",\"wifiConfigured\":") + - String(wifiManager.getSavedNetworkCount() > 0 ? "true" : "false") + - String(",\"wifiConnected\":") + - String(WiFi.status() == WL_CONNECTED ? "true" : "false") + - String(",\"ipAddress\":\"") + - (WiFi.status() == WL_CONNECTED ? WiFi.localIP().toString() : "") + "\"" + - String(",\"deviceId\":") + - String(currentDeviceId) + "}"; - - sendJSONDataToBLE(statusMsg); - Serial.println("已发送连接状态信息"); - } -} - -bool processWiFiConfigCommand(JsonDocument& doc) { - const char* command = doc["command"]; - if (command != nullptr && strcmp(command, "setWiFiConfig") == 0) { - Serial.println("📱 [BLE-WiFi] 收到WiFi配置命令"); - const char* newSSID = doc["ssid"]; - const char* newPassword = doc["password"]; - - if (newSSID != nullptr && newPassword != nullptr) { - return wifiManager.handleConfigurationData(newSSID, newPassword); - } else { - Serial.println("❌ [BLE-WiFi] WiFi配置参数不完整"); - if (deviceConnected) { - String errorMsg = String("{\"type\":\"error\",\"message\":\"WiFi配置参数不完整,需要ssid和password字段\"}"); - sendJSONDataToBLE(errorMsg); - } - return true; - } - } - return false; -} - -// 在setup()函数中添加R60ABD1初始化 -void initR60ABD1() { - Serial.println("🔧 初始化R60ABD1雷达模组..."); - - // 发送查询指令以激活数据上报 - Serial.println("📡 发送查询指令以激活数据上报..."); - // 查询存在信息: 53 59 80 81 00 01 00 7D 54 43 - uint8_t queryPresenceCmd[] = {0x53, 0x59, 0x80, 0x81, 0x00, 0x01, 0x00, 0x7D, 0x54, 0x43}; - mySerial1.write(queryPresenceCmd, sizeof(queryPresenceCmd)); - - // 1. 确认开启核心功能 - Serial.println("📡 开启核心监测功能..."); - // 发送多次人体存在监测开启命令以确保生效 - - sendRadarCommand(0x80, 0x00, 0x01); // 人体存在 - delay(50); - sendRadarCommand(0x81, 0x00, 0x01); // 呼吸监测 - delay(50); - sendRadarCommand(0x85, 0x00, 0x01); // 心率监测 - delay(50); - sendRadarCommand(0x84, 0x00, 0x01); // 睡眠监测 - delay(50); - - // 2. 波形数据开启(需要确认命令字) - Serial.println("📡 尝试开启波形数据..."); - sendRadarCommand(0x81, 0x0C, 0x01); // 呼吸波形 - delay(50); - sendRadarCommand(0x85, 0x0A, 0x01); // 心率波形 - delay(50); - - //确认开启特殊功能 - - sendRadarCommand(0x84, 0x13, 0x01); // 异常挣扎状态开关设置 - delay(50); - sendRadarCommand(0x84, 0x14, 0x01); // 无人计时功能开关设置 - delay(50); - - // 3. 验证初始化结果 - Serial.println("🔍 查询当前状态..."); - sendRadarCommand(0x80, 0x80, 0x0F); // 查询人体存在状态 - delay(50); - sendRadarCommand(0x81, 0x80, 0x0F); // 查询呼吸监测状态 - delay(50); - sendRadarCommand(0x85, 0x80, 0x0F); // 查询心率监测状态 - delay(50); - sendRadarCommand(0x84, 0x80, 0x0F); // 查询睡眠监测状态 - - Serial.println("✅ R60ABD1雷达初始化完成"); - Serial.println("📋 串口将输出解析后的雷达数据,包括:\n - 生命体征数据(心率、呼吸率)\n - 睡眠监测数据(状态、评分、时长)\n - 人体检测数据(存在、距离、运动)\n - 活动监测数据(体动幅度、翻身次数)"); -} - -// 检查Boot按钮状态 -void checkBootButton() { - Serial.println("🔍 检查Boot按钮状态..."); - - // 配置Boot引脚为输入模式(内部上拉) - pinMode(BOOT_BUTTON_PIN, INPUT_PULLUP); - - // 短暂延时确保引脚稳定 - delay(10); - - int buttonState = digitalRead(BOOT_BUTTON_PIN); - Serial.printf("📊 Boot按钮状态: %s\n", buttonState == LOW ? "按下" : "释放"); - - if (buttonState == LOW) { - // 检测到Boot按钮按下,设置指示灯为准备清除状态 - currentConfigClearStatus = CONFIG_PREPARING; - Serial.println("⚠️ 检测到Boot按钮按下,长按3秒将清除配置"); - Serial.println("⏰ 倒计时开始..."); - - // 检测长按3秒 - bootButtonPressTime = millis(); - while (digitalRead(BOOT_BUTTON_PIN) == LOW) { - // 打印倒计时 - unsigned long pressedTime = millis() - bootButtonPressTime; - unsigned long remaining = (CLEAR_CONFIG_DURATION - pressedTime) / 1000; - - if (remaining <= 3 && remaining > 0) { - Serial.printf("⏳ 继续按住 %lu 秒将清除配置...\n", remaining); - } - - if (pressedTime >= CLEAR_CONFIG_DURATION) { - clearConfigRequested = true; - Serial.println("✅ 长按3秒确认,将清除配置"); - // 设置指示灯为清除过程中状态(呼吸灯) - currentConfigClearStatus = CONFIG_CLEARING; - break; - } - - delay(1000); // 每秒检查一次 - } - - if (!clearConfigRequested) { - // 按钮释放,恢复正常状态 - currentConfigClearStatus = CONFIG_NORMAL; - Serial.println("❌ 按钮释放,取消清除操作"); - } - } else { - Serial.println("✅ Boot按钮未按下,正常启动"); - } -} - -void clearStoredConfig() { - Serial.println("🧹 开始清除存储的配置..."); - - uint16_t oldDeviceId = preferences.getUShort("deviceId", 0); - - preferences.remove("deviceId"); - preferences.remove("wifi_first"); - - wifiManager.clearAllConfigs(); - - Serial.println("✅ 配置已清除完成"); - Serial.printf("🗑️ 被清除的设备ID: %u\n", oldDeviceId); - - currentDeviceId = 1001; - WiFi_Connect_First_bit = 1; - - WiFi.disconnect(true); - setNetworkStatus(NET_DISCONNECTED); - - Serial.println("🔄 已清除Flash与内存中的配置,请重新配置WiFi和设备ID"); - - if (deviceConnected) { - sendStatusToBLE(); - } -} - - -// 配置清除指示灯控制任务 -void configClearLedTask(void *parameter) { - while (1) { - switch (currentConfigClearStatus) { - case CONFIG_NORMAL: // 正常运行 - LOW - analogWrite(CONFIG_CLEAR_PIN, 0); // 关闭LED - break; - - case CONFIG_PREPARING: // 准备清除 - HIGH - analogWrite(CONFIG_CLEAR_PIN, 255); // 开启LED - break; - - case CONFIG_CLEARING: // 清除过程中 - 呼吸灯效果 - if (millis() - lastConfigBlinkTime >= BREATHE_INTERVAL) { - // 呼吸灯效果 - analogWrite(CONFIG_CLEAR_PIN, configBreatheValue); - - // 更新呼吸灯值 - if (configBreatheIncreasing) { - configBreatheValue += 5; // 使用固定步进值 - if (configBreatheValue >= BREATHE_MAX) { - configBreatheValue = BREATHE_MAX; - configBreatheIncreasing = false; - } - } else { - configBreatheValue -= 5; // 使用固定步进值 - if (configBreatheValue <= BREATHE_MIN) { - configBreatheValue = BREATHE_MIN; - configBreatheIncreasing = true; - } - } - lastConfigBlinkTime = millis(); - } - break; - - case CONFIG_COMPLETED: // 清除完成 - 快速闪烁3次 - if (millis() - lastConfigBlinkTime >= FAST_BLINK_INTERVAL) { - configLedState = !configLedState; - digitalWrite(CONFIG_CLEAR_PIN, configLedState ? HIGH : LOW); - lastConfigBlinkTime = millis(); - - // 计算闪烁次数并切换回正常状态 - static int blinkCount = 0; - blinkCount++; - - if (blinkCount >= 6) { // 闪烁3次 (HIGH-LOW为1次) - blinkCount = 0; - currentConfigClearStatus = CONFIG_NORMAL; - digitalWrite(CONFIG_CLEAR_PIN, LOW); // 确保回到LOW状态 - } - } - break; - } - - vTaskDelay(10 / portTICK_PERIOD_MS); - } -} - -// BOOT按钮监控任务 -void bootButtonMonitorTask(void *parameter) { - Serial.println("🔍 启动BOOT按钮监控任务..."); - - // 配置Boot引脚为输入模式(内部上拉) - pinMode(BOOT_BUTTON_PIN, INPUT_PULLUP); - - unsigned long buttonPressStartTime = 0; - bool buttonPressed = false; - - while (1) { - int buttonState = digitalRead(BOOT_BUTTON_PIN); - - if (buttonState == LOW && !buttonPressed) { - // 按钮刚被按下 - buttonPressed = true; - buttonPressStartTime = millis(); - Serial.println("⚠️ 检测到BOOT按钮按下,长按3秒将清除配置"); - - // 设置指示灯为准备清除状态 - currentConfigClearStatus = CONFIG_PREPARING; - } - else if (buttonState == HIGH && buttonPressed) { - // 按钮被释放 - if (!clearConfigRequested) { - // 如果还没有确认清除,则取消操作 - currentConfigClearStatus = CONFIG_NORMAL; - Serial.println("❌ 按钮释放,取消清除操作"); - } - buttonPressed = false; - } - - // 检查是否长按了3秒 - if (buttonPressed && (millis() - buttonPressStartTime >= CLEAR_CONFIG_DURATION)) { - if (!clearConfigRequested) { - clearConfigRequested = true; - analogWrite(NETWORK_LED_PIN, 0); // 关闭网络状态LED - Serial.println("✅ 长按3秒确认,将清除配置"); - - // 设置指示灯为清除过程中状态(呼吸灯) - //currentConfigClearStatus = CONFIG_CLEARING; - - // 清除配置 - clearStoredConfig(); - - Serial.println("🔄 配置清除完成,LED将闪烁3次表示完成..."); - - // 设置指示灯为清除完成状态(快速闪烁3次) - // currentConfigClearStatus = CONFIG_COMPLETED; - - // 等待闪烁完成 - // while(currentConfigClearStatus == CONFIG_COMPLETED) { - // vTaskDelay(100 / portTICK_PERIOD_MS); - // esp_task_wdt_reset(); - // } - analogWrite(CONFIG_CLEAR_PIN, 0); // 关闭LED - // analogWrite(NETWORK_LED_PIN, 0); // 关闭网络状态LED - Serial.println("🔄 系统即将重启..."); - - // 短暂延迟后重启 - vTaskDelay(1000 / portTICK_PERIOD_MS); - ESP.restart(); - } - } - - vTaskDelay(50 / portTICK_PERIOD_MS); // 每50ms检查一次 - - // 重置看门狗 - esp_task_wdt_reset(); - } -} - -// LED控制任务 -void ledControlTask(void *parameter) { - while (1) { - switch (currentNetworkStatus) { - case NET_INITIAL: // 未连接 - 慢闪 - case NET_DISCONNECTED: // 断开连接 - 慢闪 - if (millis() - lastBlinkTime >= SLOW_BLINK_INTERVAL) { - ledState = !ledState; - if(ledState) { - ledcWrite(0, 255); // 设置为最大亮度 - } else { - ledcWrite(0, 0); // 关闭LED - } - lastBlinkTime = millis(); - } - break; - - case NET_CONNECTING: // 连接中 - 快闪 - if (millis() - lastBlinkTime >= FAST_BLINK_INTERVAL) { - ledState = !ledState; - if(ledState) { - ledcWrite(0, 255); // 设置为最大亮度 - } else { - ledcWrite(0, 0); // 关闭LED - } - lastBlinkTime = millis(); - } - break; - - case NET_CONNECTED: // 已连接 - 呼吸灯效果 - if (millis() - lastBlinkTime >= BREATHE_INTERVAL) { - // 呼吸灯效果 - ledcWrite(0, breatheValue); // 使用ledcWrite替代analogWrite - - // 更新呼吸灯值 - if (breatheIncreasing) { - breatheValue += BREATHE_STEP; - if (breatheValue >= BREATHE_MAX) { - breatheValue = BREATHE_MAX; - breatheIncreasing = false; - } - } else { - breatheValue -= BREATHE_STEP; - if (breatheValue <= BREATHE_MIN) { - breatheValue = BREATHE_MIN; - breatheIncreasing = true; - } - } - lastBlinkTime = millis(); - } - break; - } - - vTaskDelay(10 / portTICK_PERIOD_MS); - } -} - -// 设置网络状态 -void setNetworkStatus(NetworkStatus status) { - currentNetworkStatus = status; - - // 切换到呼吸灯模式时,重置呼吸灯参数 - if (status == NET_CONNECTED) { - breatheValue = BREATHE_MIN; - breatheIncreasing = true; - } -} - -// WiFi事件处理 -void WiFiEvent(WiFiEvent_t event) { - switch (event) { - case ARDUINO_EVENT_WIFI_STA_START: - setNetworkStatus(NET_INITIAL); - break; - - case ARDUINO_EVENT_WIFI_STA_CONNECTED: - setNetworkStatus(NET_CONNECTING); - break; - - case ARDUINO_EVENT_WIFI_STA_GOT_IP: - setNetworkStatus(NET_CONNECTED); - break; - - case ARDUINO_EVENT_WIFI_STA_DISCONNECTED: - setNetworkStatus(NET_DISCONNECTED); - break; - - case ARDUINO_EVENT_WIFI_STA_STOP: - setNetworkStatus(NET_DISCONNECTED); - break; - } -} - -void wifiMonitorTask(void *parameter) { - Serial.println("📡 WiFi监控任务启动"); - - while(1) { - wifiManager.update(); - vTaskDelay(500 / portTICK_PERIOD_MS); - } -} -void setup() { - Serial.begin(115200); - - // 第一步:检查Boot按钮是否被按下 - checkBootButton(); - - // 确保结束后恢复到正常状态 - analogWrite(CONFIG_CLEAR_PIN, 0); // 关闭呼吸灯 - Serial.println("稳定期结束,开始工作。"); - - // 如果请求清除配置,这里会先执行清除操作 - if (clearConfigRequested) { - clearStoredConfig(); - // 清除后重启系统 - Serial.println("🔄 系统即将重启..."); - delay(1000); - ESP.restart(); - } - - // 添加启动信息 - Serial.println("🚀 ESP32-R60ABD1系统启动"); - Serial.println("🔧 初始化系统组件..."); - - // 初始化GPIO模式 - pinMode(BOOT_BUTTON_PIN, INPUT); // Boot按钮引脚,输入模式,不启用内部上拉 - pinMode(NETWORK_LED_PIN, OUTPUT); // 网络状态LED,输出模式 - pinMode(CONFIG_CLEAR_PIN, OUTPUT); // 配置清除指示灯,输出模式 - pinMode(GPIO8, OUTPUT); // 自定义GPIO8,输出模式 - pinMode(GPIO9, OUTPUT); // 自定义GPIO9,输出模式 - - // 初始化配置清除指示灯为LOW(正常运行状态) - digitalWrite(CONFIG_CLEAR_PIN, LOW); - digitalWrite(GPIO8, LOW); - digitalWrite(GPIO9, LOW); - // 初始状态设置 - digitalWrite(NETWORK_LED_PIN, LOW); // 网络状态LED,输出模式,输出低电平 - digitalWrite(CONFIG_CLEAR_PIN, LOW); - - // 设置PWM通道用于呼吸灯效果 - ledcSetup(0, 5000, 8); // PWM通道0, 5kHz, 8位分辨率 - ledcSetup(1, 5000, 8); // PWM通道1, 5kHz, 8位分辨率 - ledcAttachPin(NETWORK_LED_PIN, 0); // 网络状态LED使用通道0 - ledcAttachPin(CONFIG_CLEAR_PIN, 1); // 配置清除LED使用通道1 - - // 注册WiFi事件 - WiFi.onEvent(WiFiEvent); - - - // 设置初始网络状态 - setNetworkStatus(NET_INITIAL); - - esp_task_wdt_init(30, true); // 初始化任务看门狗,30秒超时 - esp_task_wdt_add(NULL); - - // 增加串口缓冲区大小以处理R60ABD1数据 - mySerial1.setRxBufferSize(UART_RX_BUFFER_SIZE); - mySerial1.begin(BAUD_RATE, SERIAL_8N1, UART1_RX, UART1_TX); - - Serial.println("UART1配置完成,缓冲区大小: 4096字节"); - - preferences.begin("radar_data", false); - - wifiManager.begin(); - - Serial.println("💾 加载设备配置..."); - loadDeviceId(); - - Serial.println("🏗️ 创建FreeRTOS队列..."); - // 创建FreeRTOS队列 - phaseDataQueue = xQueueCreate(QUEUE_SIZE, sizeof(PhaseData)); - vitalDataQueue = xQueueCreate(QUEUE_SIZE, sizeof(VitalData)); - uartQueue = xQueueCreate(2048, sizeof(char)); // 创建串口数据队列 - - if (phaseDataQueue == NULL || vitalDataQueue == NULL || uartQueue == NULL) { - Serial.println("❌ 队列创建失败"); - } else { - Serial.println("✅ FreeRTOS队列创建成功"); - } - - // 设置串口中断回调函数 - mySerial1.onReceive(serialRxCallback); - Serial.println("✅ 串口中断回调已设置"); - - Serial.println("🏃 创建FreeRTOS任务..."); - // 创建FreeRTOS任务 - xTaskCreatePinnedToCore( - bleSendTask, - "BleSendTask", - TASK_STACK_SIZE, - NULL, - 3, // 提高优先级到3 - &bleSendTaskHandle, - 1 - ); - - xTaskCreatePinnedToCore( - vitalSendTask, - "VitalSendTask", - TASK_STACK_SIZE, - NULL, - 2, // 中等优先级 - &vitalSendTaskHandle, - 1 - ); - - // 创建专门的雷达数据处理任务 - xTaskCreatePinnedToCore( - radarDataTask, - "RadarProcessTask", - 4096, - NULL, - 4, // 最高优先级 - NULL, - 1 - ); - - // 创建R60ABD1串口数据处理任务 - xTaskCreatePinnedToCore( - uartProcessTask, // R60ABD1串口处理任务 - "UartProcessTask", - 4096, - NULL, - 5, // 最高优先级 - &uartProcessTaskHandle, - 1 - ); - - // 创建配置清除指示灯控制任务 - xTaskCreate( - configClearLedTask, // 任务函数 - "Config Clear LED Task", // 任务名称 - 2048, // 堆栈大小 - NULL, // 参数 - 1, // 优先级 - NULL // 任务句柄 - ); - - // 创建BOOT按钮监控任务 - xTaskCreate( - bootButtonMonitorTask, // 任务函数 - "Boot Button Monitor Task", // 任务名称 - 2048, // 堆栈大小 - NULL, // 参数 - 1, // 优先级 - NULL // 任务句柄 - ); - - // 创建LED控制任务 - xTaskCreate( - ledControlTask, // 任务函数 - "LED Control Task", // 任务名称 - 2048, // 堆栈大小 - NULL, // 参数 - 1, // 优先级 - NULL // 任务句柄 - ); - - // 创建WiFi监控任务 - xTaskCreate( - wifiMonitorTask, // 任务函数 - "WiFi Monitor Task", // 任务名称 - 4096, // 堆栈大小 - NULL, // 参数 - 2, // 优先级 - NULL // 任务句柄 - ); - - Serial.println("✅ FreeRTOS任务创建成功"); - - Serial.println("📶 初始化BLE服务..."); - String deviceName = "Radar_" + String(currentDeviceId); - BLEDevice::init(deviceName.c_str()); - pServer = BLEDevice::createServer(); - pServer->setCallbacks(new MyServerCallbacks()); - - BLEService *pService = pServer->createService(SERVICE_UUID); - pCharacteristic = pService->createCharacteristic( - CHARACTERISTIC_UUID, - BLECharacteristic::PROPERTY_READ | - BLECharacteristic::PROPERTY_WRITE | - BLECharacteristic::PROPERTY_NOTIFY - ); - pCharacteristic->setCallbacks(new MyCallbacks()); - pCharacteristic->addDescriptor(new BLE2902()); - - pService->start(); - BLEAdvertising *pAdvertising = BLEDevice::getAdvertising(); - pAdvertising->addServiceUUID(SERVICE_UUID); - pAdvertising->setScanResponse(true); - pAdvertising->setMinPreferred(0x06); - pAdvertising->setMinPreferred(0x12); - BLEDevice::startAdvertising(); - - Serial.println(String("BLE已启动,设备名称: Radar_") + String(currentDeviceId)); - - Serial.println("🌐 检查WiFi配置..."); - if (wifiManager.getSavedNetworkCount() > 0) { - Serial.printf("💾 检测到 %d 个已保存的WiFi配置,尝试连接...\n", wifiManager.getSavedNetworkCount()); - if (wifiManager.initializeWiFi()) { - Serial.println("✅ WiFi连接成功!"); - } else { - Serial.println("❌ WiFi连接失败,请通过BLE重新配置"); - } - } else { - Serial.println("⚠️ 未检测到WiFi配置,请通过BLE进行网络配置"); - } - - // 从Flash加载WiFi首次连接标志(模仿getString行为:若Flash无该键,则保持当前变量值不变) - size_t wifi_first_len = preferences.getBytes("wifi_first", &WiFi_Connect_First_bit, sizeof(WiFi_Connect_First_bit)); - if (wifi_first_len == sizeof(WiFi_Connect_First_bit)) { - Serial.printf("从Flash读取 WiFi_Connect_First_bit: %u\n", WiFi_Connect_First_bit); - } else { - Serial.println("Flash中无 wifi_first 条目,保留内存中原始值"); - } - - if(WiFi_Connect_First_bit == 0) - { - unsigned long wifiWaitStart = millis(); - unsigned long lastWifiWaitPrint = 0; - const unsigned long WIFI_WAIT_TIMEOUT = 15000; // 最多等待15秒 - // 使用非阻塞方式等待WiFi连接,期间允许任务调度运行 - while (WiFi.status() != WL_CONNECTED && (millis() - wifiWaitStart) < WIFI_WAIT_TIMEOUT) { - if (millis() - lastWifiWaitPrint >= 1000) { - Serial.println("等待WiFi连接..."); - lastWifiWaitPrint = millis(); - } - yield(); - vTaskDelay(10 / portTICK_PERIOD_MS); - } - } - - // 系统初始化函数 - Serial.println("WiFi连接成功!"); - // 初始化R60ABD1雷达模组 - initR60ABD1(); // 调用R60ABD1初始化函数 - - Serial.println("🎉 系统初始化完成,等待雷达数据..."); - - // 启动时发送一次睡眠数据 - if (WiFi.status() == WL_CONNECTED) { - Serial.println("🌅 启动时发送睡眠数据到数据库"); - sendSleepDataToInfluxDB(); - } -} - -void loop() { - esp_task_wdt_reset(); - - if (!deviceConnected && oldDeviceConnected) { - vTaskDelay(500 / portTICK_PERIOD_MS); - pServer->startAdvertising(); - Serial.println("开始BLE广播"); - oldDeviceConnected = deviceConnected; - } - if (deviceConnected && !oldDeviceConnected) { - oldDeviceConnected = deviceConnected; - } - // 使用非阻塞方式轮询发送一系列查询命令,间隔2000ms - { - static const uint8_t radar_cmds[][3] = { - {0x84, 0x81, 0x0F}, // 入床/离床状态查询 - {0x84, 0x8D, 0x0F}, // 睡眠综合状态查询 - {0x84, 0x8F, 0x0F}, // 睡眠统计查询 - {0x84, 0x8E, 0x0F}, // 睡眠异常查询 - {0x84, 0x91, 0x0F}, // 异常挣扎查询 - {0x84, 0x92, 0x0F}, // 无人计时查询 - {0x84, 0x83, 0x0F}, // 清醒时长查询 - {0x84, 0x84, 0x0F}, // 浅睡时长查询 - {0x84, 0x85, 0x0F}, // 深睡时长查询 - {0x84, 0x86, 0x0F}, // 睡眠质量评分查询 - {0x84, 0x90, 0x0F} // 睡眠质量评级 - }; - - const size_t cmdCount = sizeof(radar_cmds) / sizeof(radar_cmds[0]); - static size_t cmdIndex = 0; - static unsigned long lastCmdMillis = 0; - const unsigned long CMD_INTERVAL = 2000UL; // 2秒 - - unsigned long now = millis(); - if (now - lastCmdMillis >= CMD_INTERVAL) { - // 发送当前命令 - sendRadarCommand(radar_cmds[cmdIndex][0], radar_cmds[cmdIndex][1], radar_cmds[cmdIndex][2]); - lastCmdMillis = now; - cmdIndex++; - if (cmdIndex >= cmdCount) cmdIndex = 0; - } - } - - processBLEConfig(); - esp_task_wdt_reset(); - esp_task_wdt_reset(); - // updateStatusFlags(); - - // 在主循环中添加更频繁的看门狗重置 - esp_task_wdt_reset(); - delay(1); -} \ No newline at end of file diff --git a/src/mqtt.cpp b/src/mqtt.cpp new file mode 100644 index 0000000..c98b176 --- /dev/null +++ b/src/mqtt.cpp @@ -0,0 +1,584 @@ +#include "mqtt.h" +#include "wifi_manager.h" +#include "radar_manager.h" +#include + +extern uint64_t device_sn; +extern Preferences preferences; +extern WiFiManager wifiManager; +extern String getDeviceMacAddress(); + +TaskHandle_t mqttTaskHandle = NULL; + +const char* mqttServer = "www.lmhrt.cn"; +const int mqttPort = 1883; +const char* mqttDeviceModel = "radar_1.0"; +const char* mqttProductKey = "your_product_key"; +const char* mqttProductSecret = "your_product_secret"; + +String deviceMacAddress = ""; + +WiFiClient mqttWiFiClient; +PubSubClient mqttClient(mqttWiFiClient); + +static uint32_t mqttMessageId = 1; + +unsigned long lastSleepDataTime = 0; +const unsigned long SLEEP_DATA_INTERVAL = 10000; +unsigned long lastDailyDataTime = 0; +const unsigned long DAILY_DATA_INTERVAL = 5000; + +String getMqttDeviceName() { + if (device_sn != 0) { + return String((unsigned long long)device_sn); + } + + String fallback = getDeviceMacAddress(); + fallback.replace(":", ""); + return fallback; +} + +/** + * @brief 获取MQTT客户端ID + * 按照enjoy-iot规范构建客户端ID + * + * 格式:{productKey}_{deviceName}_{model} + * + * @return 客户端ID字符串 + * @example "radar_2024_12345678_v1" + */ +String getMqttClientId() { + return String(mqttProductKey) + "_" + getMqttDeviceName() + "_" + String(mqttDeviceModel); +} + +/** + * @brief 获取MQTT下行订阅主题 + * 用于订阅平台下发的所有指令 + * + * 格式:/sys/{productKey}/{deviceName}/c/# + * - /sys/ 系统主题前缀 + * - {productKey} 产品标识 + * - {deviceName} 设备名称 + * - /c/ 下行指令标识 (command) + * - # 通配符,匹配所有子主题 + * + * @return 订阅主题字符串 + * @example "/sys/radar_2024/12345678/c/#" + */ +String getMqttSubscribeTopic() { + return String("/sys/") + mqttProductKey + "/" + getMqttDeviceName() + "/c/#"; +} + +/** + * @brief 获取MQTT属性上报主题 + * 用于设备向平台上报属性数据 + * + * 格式:/sys/{productKey}/{deviceName}/s/event/property/post + * - /sys/ 系统主题前缀 + * - {productKey} 产品标识 + * - {deviceName} 设备名称 + * - /s/ 上行状态标识 (status) + * - /event/property/post 属性上报事件 + * + * @return 上报主题字符串 + * @example "/sys/radar_2024/12345678/s/event/property/post" + */ +String getMqttPropertyPostTopic() { + return String("/sys/") + mqttProductKey + "/" + getMqttDeviceName() + "/s/event/property/post"; +} + +/** + * @brief 生成下一个MQTT消息ID + * 每次调用返回递增的ID,用于消息追踪和请求-响应匹配 + * + * @return 消息ID字符串 + * @example "1" -> "2" -> "3" ... + */ +static String nextMqttMessageId() { + return String(mqttMessageId++); +} + +/** + * @brief 计算MQTT连接密码 + * 按照enjoy-iot规范,使用MD5计算密码 + * + * 公式:password = MD5(productSecret + clientId) + * + * @param clientId 客户端ID + * @return 32位小写十六进制MD5字符串 + * @example MD5("abc123" + "radar_2024_12345678_v1") -> "a1b2c3d4e5f6..." + */ +String makeMqttPassword(const String& clientId) { + String raw = String(mqttProductSecret) + clientId; + + unsigned char digest[16]; + mbedtls_md5_context ctx; + mbedtls_md5_init(&ctx); + mbedtls_md5_starts_ret(&ctx); + mbedtls_md5_update_ret(&ctx, (const unsigned char*)raw.c_str(), raw.length()); + mbedtls_md5_finish_ret(&ctx, digest); + mbedtls_md5_free(&ctx); + + char md5str[33]; + for (int i = 0; i < 16; i++) { + sprintf(&md5str[i * 2], "%02x", digest[i]); + } + md5str[32] = '\0'; + return String(md5str); +} + +/** + * @brief 统一属性上报函数 + * 封装enjoy-iot规范的属性上报格式,被sendDailyDataToMQTT和sendSleepDataToMQTT调用 + * + * 载荷格式: + * { + * "id": "消息ID", + * "method": "thing.event.property.post", + * "params": { + * "deviceId": "设备ID", + * "reportType": "daily/sleep", + * ...业务字段... + * } + * } + * + * @param params 业务参数JSON对象(调用者填充业务字段) + * @param reportType 上报类型 ("daily" 或 "sleep") + * @return true 发布成功,false 发布失败 + */ +static bool publishPropertyReport(JsonDocument& params, const char* reportType) { + JsonDocument payloadDoc; + + payloadDoc["id"] = nextMqttMessageId(); + + payloadDoc["method"] = "thing.event.property.post"; + + params["deviceId"] = String((unsigned long long)device_sn); + params["reportType"] = reportType; + + payloadDoc["params"] = params; + + String topic = getMqttPropertyPostTopic(); + + String payload; + serializeJson(payloadDoc, payload); + + return mqttClient.publish(topic.c_str(), payload.c_str()); +} + +/** + * @brief 构建回复主题 + * 将下行请求主题转换为上行回复主题 + * + * 转换规则: + * - /c/ 替换为 /s/ + * - 末尾添加 _reply + * + * @param requestTopic 请求主题 + * @return 回复主题字符串 + * @example "/sys/.../c/service/property/set" -> "/sys/.../s/service/property/set_reply" + */ +String buildReplyTopic(const char* requestTopic) { + String topic = String(requestTopic); + topic.replace("/c/", "/s/"); + topic += "_reply"; + return topic; +} + +/** + * @brief 发送MQTT回复 + * 统一处理平台下发指令的回复 + * + * 回复格式: + * { + * "id": "原请求ID", + * "method": "原method_reply", + * "code": 0, // 0=成功,其他=失败 + * "data": { ... } + * } + * + * @param requestTopic 请求主题 + * @param requestId 请求ID + * @param requestMethod 请求方法 + * @param code 状态码 (0=成功,-1=失败) + * @param data 回复数据 + * @return true 发送成功,false 发送失败 + */ +bool publishMqttReply(const char* requestTopic, + const char* requestId, + const char* requestMethod, + int code, + JsonVariant data) { + JsonDocument replyDoc; + replyDoc["id"] = requestId ? requestId : ""; + replyDoc["method"] = String(requestMethod ? requestMethod : "") + "_reply"; + replyDoc["code"] = code; + replyDoc["data"] = data; + + String replyTopic = buildReplyTopic(requestTopic); + String payload; + serializeJson(replyDoc, payload); + + Serial.printf("[MQTT] reply topic: %s\n", replyTopic.c_str()); + Serial.printf("[MQTT] reply payload: %s\n", payload.c_str()); + + return mqttClient.publish(replyTopic.c_str(), payload.c_str()); +} + +/** + * @brief MQTT消息回调函数 + * 处理平台下发的指令,支持属性设置、属性读取和自定义服务 + * + * 支持的method: + * - thing.service.property.set: 设置设备属性(如continuousSendEnabled、continuousSendInterval) + * - thing.service.property.get: 读取设备属性(返回当前传感器数据和配置) + * - thing.service.*: 自定义服务(预留扩展) + * + * @param topic 消息主题 + * @param payload 消息载荷 + * @param length 载荷长度 + */ +void mqttMessageCallback(char* topic, byte* payload, unsigned int length) { + String message; + for (unsigned int i = 0; i < length; i++) { + message += (char)payload[i]; + } + + Serial.printf("[MQTT] 收到主题: %s\n", topic); + Serial.printf("[MQTT] 收到内容: %s\n", message.c_str()); + + JsonDocument doc; + DeserializationError err = deserializeJson(doc, message); + if (err) { + Serial.printf("[MQTT] JSON解析失败: %s\n", err.c_str()); + return; + } + + const char* method = doc["method"] | ""; + const char* id = doc["id"] | ""; + JsonObject params = doc["params"].as(); + + if (strcmp(method, "thing.service.property.set") == 0) { + bool ok = true; + + if (params["continuousSendEnabled"].is()) { + continuousSendEnabled = params["continuousSendEnabled"].as(); + } + + if (params["continuousSendInterval"].is()) { + continuousSendInterval = params["continuousSendInterval"].as(); + } + + JsonDocument replyData; + replyData["success"] = ok; + + if (publishMqttReply(topic, id, method, ok ? 0 : -1, replyData.as())) { + Serial.println("[MQTT] property.set reply 发送成功"); + } else { + Serial.println("[MQTT] property.set reply 发送失败"); + } + + } else if (strcmp(method, "thing.service.property.get") == 0) { + JsonDocument replyData; + + replyData["heartRate"] = sensorData.heart_rate; + replyData["breathingRate"] = sensorData.breath_rate; + replyData["personDetected"] = sensorData.presence; + replyData["humanActivity"] = sensorData.motion; + replyData["humanDistance"] = sensorData.distance; + replyData["sleepState"] = sensorData.sleep_state; + replyData["continuousSendEnabled"] = continuousSendEnabled; + replyData["continuousSendInterval"] = continuousSendInterval; + + if (publishMqttReply(topic, id, method, 0, replyData.as())) { + Serial.println("[MQTT] property.get reply 发送成功"); + } else { + Serial.println("[MQTT] property.get reply 发送失败"); + } + + } else if (strncmp(method, "thing.service.", 14) == 0) { + JsonDocument replyData; + bool ok = true; + + replyData["success"] = ok; + + if (publishMqttReply(topic, id, method, ok ? 0 : -1, replyData.as())) { + Serial.printf("[MQTT] service reply 发送成功, method=%s\n", method); + } else { + Serial.printf("[MQTT] service reply 发送失败, method=%s\n", method); + } + + } else { + Serial.printf("[MQTT] 暂不支持 method=%s\n", method); + } +} + +/** + * @brief 初始化MQTT客户端 + * 配置MQTT服务器地址、端口、缓冲区大小和消息回调函数 + * + * 初始化内容: + * 1. 获取设备MAC地址 + * 2. 设置MQTT服务器地址和端口 + * 3. 设置消息缓冲区大小为1024字节 + * 4. 注册下行消息回调函数 + */ +void initMQTT() { + deviceMacAddress = getDeviceMacAddress(); + mqttClient.setServer(mqttServer, mqttPort); + mqttClient.setBufferSize(MQTT_MAX_PACKET_SIZE); + mqttClient.setCallback(mqttMessageCallback); + + Serial.printf("[MQTT] broker: %s:%d\n", mqttServer, mqttPort); + Serial.printf("[MQTT] clientId: %s\n", getMqttClientId().c_str()); + Serial.printf("[MQTT] username: %s\n", getMqttDeviceName().c_str()); +} + +/** + * @brief 连接MQTT服务器 + * 使用enjoy-iot规范的身份认证方式连接,并订阅下行主题 + * + * 注意:此函数同时承担首次连接和断线重连的职责 + * - 首次连接:checkMQTTStatus() 检测到未连接时调用 + * - 断线重连:checkMQTTStatus() 检测到断开时调用 + * + * 连接流程: + * 1. 检查WiFi是否已连接 + * 2. 计算clientId、username、password + * 3. 连接MQTT服务器 + * 4. 订阅下行主题 /sys/{productKey}/{deviceName}/c/# + * + * 密码计算:password = MD5(productSecret + clientId) + */ +void reconnectMQTT() { + if (!WiFi.isConnected()) { + return; + } + + String clientId = getMqttClientId(); + String username = getMqttDeviceName(); + String password = makeMqttPassword(clientId); + + bool connected = mqttClient.connect(clientId.c_str(), username.c_str(), password.c_str()); + + if (connected) { + Serial.printf("[MQTT] 连接成功, clientId=%s\n", clientId.c_str()); + + String subTopic = getMqttSubscribeTopic(); + mqttClient.subscribe(subTopic.c_str()); + Serial.printf("[MQTT] 已订阅: %s\n", subTopic.c_str()); + } else { + Serial.printf("[MQTT] 连接失败, state=%d\n", mqttClient.state()); + } +} + +/** + * @brief 检查MQTT连接状态 + * 如果未连接则尝试重连,并保持心跳 + * + * 重连策略: + * - 仅在WiFi已连接时尝试重连 + * - 每5秒尝试一次重连,避免频繁重连 + * - 调用mqttClient.loop()保持心跳和处理消息 + */ +void checkMQTTStatus() { + if (!mqttClient.connected() && WiFi.isConnected()) { + static unsigned long lastReconnectAttempt = 0; + unsigned long now = millis(); + if (now - lastReconnectAttempt > 5000) { + lastReconnectAttempt = now; + reconnectMQTT(); + } + } + mqttClient.loop(); +} + +/** + * @brief 发送日常数据到MQTT + * 上报当前雷达监测的实时状态数据 + * + * 上报字段: + * - heartRate: 心率 + * - breathingRate: 呼吸率 + * - personDetected: 人体存在 + * - humanActivity: 人体活动 + * - humanDistance: 人体距离 + * - sleepState: 睡眠状态 + * - humanPositionX/Y/Z: 人体坐标 + * - heartbeatWaveform: 心跳波形 + * - breathingWaveform: 呼吸波形 + * - abnormalState: 异常状态 + * - bedStatus: 床状态 + * - struggleAlert: 挣扎警报 + * - noOneAlert: 无人警报 + * + * 触发条件:mqttTask中每次取到数据时调用 + */ +void sendDailyDataToMQTT() { + if (WiFi.status() != WL_CONNECTED) { + return; + } + + checkMQTTStatus(); + + if (!mqttClient.connected()) { + Serial.println("[MQTT] 未连接,跳过发送日常数据"); + return; + } + + JsonDocument doc; + + if (sensorData.heart_rate > 0) { + doc["heartRate"] = sensorData.heart_rate; + } + + if (sensorData.breath_rate > 0) { + doc["breathingRate"] = sensorData.breath_rate; + } + + doc["personDetected"] = sensorData.presence; + doc["humanActivity"] = sensorData.motion; + + if (sensorData.distance > 0) { + doc["humanDistance"] = sensorData.distance; + } + + doc["sleepState"] = sensorData.sleep_state; + doc["humanPositionX"] = sensorData.pos_x; + doc["humanPositionY"] = sensorData.pos_y; + doc["humanPositionZ"] = sensorData.pos_z; + doc["heartbeatWaveform"] = (int)sensorData.heart_waveform[0]; + doc["breathingWaveform"] = (int)sensorData.breath_waveform[0]; + doc["abnormalState"] = sensorData.abnormal_state; + doc["bedStatus"] = sensorData.bed_status; + doc["struggleAlert"] = sensorData.struggle_alert; + doc["noOneAlert"] = sensorData.no_one_alert; + + if (publishPropertyReport(doc, "daily")) { + Serial.println("[MQTT] 日常数据上报成功"); + } else { + Serial.printf("[MQTT] 日常数据上报失败, state=%d\n", mqttClient.state()); + } +} + +/** + * @brief 发送睡眠数据到MQTT + * 上报睡眠统计数据和质量评估 + * + * 上报字段: + * - sleepQualityScore: 睡眠质量评分 + * - sleepQualityGrade: 睡眠质量等级 + * - totalSleepDuration: 总睡眠时长 + * - awakeDurationRatio: 清醒时长比例 + * - lightSleepRatio: 浅睡比例 + * - deepSleepRatio: 深睡比例 + * - outOfBedDuration: 离床时长 + * - outOfBedCount: 离床次数 + * - turnCount: 翻身次数 + * - avgBreathingRate: 平均呼吸率 + * - avgHeartRate: 平均心率 + * - apneaCount: 呼吸暂停次数 + * - awakeDuration: 清醒时长 + * - lightSleepDuration: 浅睡时长 + * - deepSleepDuration: 深睡时长 + * + * 触发条件: + * - mqttTask中每10秒调用一次 + * - 仅在sleep_state为0(深睡)或1(浅睡)时上报 + */ +void sendSleepDataToMQTT() { + if (WiFi.status() != WL_CONNECTED) { + Serial.println("[MQTT] WiFi未连接,跳过发送睡眠数据"); + return; + } + + checkMQTTStatus(); + + if (!mqttClient.connected()) { + Serial.println("[MQTT] MQTT未连接,跳过发送睡眠数据"); + return; + } + + if (sensorData.sleep_state != 0 && sensorData.sleep_state != 1) { + Serial.printf("[MQTT] 当前不是睡眠状态,sleep_state=%d\n", sensorData.sleep_state); + return; + } + + JsonDocument doc; + doc["sleepQualityScore"] = sensorData.sleep_score; + doc["sleepQualityGrade"] = sensorData.sleep_grade; + doc["totalSleepDuration"] = sensorData.sleep_total_time; + doc["awakeDurationRatio"] = sensorData.awake_ratio; + doc["lightSleepRatio"] = sensorData.light_sleep_ratio; + doc["deepSleepRatio"] = sensorData.deep_sleep_ratio; + doc["outOfBedDuration"] = sensorData.bed_Out_Time; + doc["outOfBedCount"] = sensorData.turn_count; + doc["turnCount"] = sensorData.turnover_count; + doc["avgBreathingRate"] = sensorData.avg_breath_rate; + doc["avgHeartRate"] = sensorData.avg_heart_rate; + doc["apneaCount"] = sensorData.apnea_count; + doc["abnormalState"] = sensorData.abnormal_state; + doc["bodyMovement"] = sensorData.body_movement; + doc["breathStatus"] = sensorData.breath_status; + doc["sleepState"] = sensorData.sleep_state; + doc["largeMoveRatio"] = sensorData.large_move_ratio; + doc["smallMoveRatio"] = sensorData.small_move_ratio; + doc["struggleAlert"] = sensorData.struggle_alert; + doc["noOneAlert"] = sensorData.no_one_alert; + doc["awakeDuration"] = sensorData.awake_time; + doc["lightSleepDuration"] = sensorData.light_sleep_time; + doc["deepSleepDuration"] = sensorData.deep_sleep_time; + + if (publishPropertyReport(doc, "sleep")) { + Serial.println("[MQTT] 睡眠数据上报成功"); + } else { + Serial.printf("[MQTT] 睡眠数据上报失败, state=%d\n", mqttClient.state()); + } +} + +/** + * @brief MQTT任务 + * 在FreeRTOS任务中处理所有MQTT功能: + * - 保持MQTT连接心跳 + * - 处理MQTT消息回调 + * - 定时发送日常数据和睡眠数据 + * @param parameter 任务参数(未使用) + */ +void mqttTask(void *parameter) { + Serial.println("📡 MQTT任务启动"); + + initMQTT(); + + while (1) { + esp_task_wdt_reset(); + + if (WiFi.status() == WL_CONNECTED) { + checkMQTTStatus(); + + if (mqttClient.connected()) { + unsigned long currentTime = millis(); + + if (sensorData.heart_rate == 0 || sensorData.breath_rate == 0) { + Serial.println("⚠️ 心率和呼吸率都为0,跳过发送数据到MQTT"); + } else if (currentTime - lastDailyDataTime >= DAILY_DATA_INTERVAL) { + sendDailyDataToMQTT(); + lastDailyDataTime = currentTime; + } + + esp_task_wdt_reset(); + + if (currentTime - lastSleepDataTime >= SLEEP_DATA_INTERVAL) { + sendSleepDataToMQTT(); + lastSleepDataTime = currentTime; + } + } else { + static unsigned long lastWifiCheck = 0; + if (millis() - lastWifiCheck > 10000) { + lastWifiCheck = millis(); + } + } + } + + vTaskDelay(10 / portTICK_PERIOD_MS); + } +} \ No newline at end of file diff --git a/src/mqtt.h b/src/mqtt.h new file mode 100644 index 0000000..3a7f469 --- /dev/null +++ b/src/mqtt.h @@ -0,0 +1,51 @@ +#ifndef MQTT_MANAGER_H +#define MQTT_MANAGER_H + +#include +#include +#include +#include +#include +#include +#include +#include + +#undef MQTT_MAX_PACKET_SIZE +#define MQTT_MAX_PACKET_SIZE 1024 + +class WiFiManager; + +extern uint64_t device_sn; +extern Preferences preferences; +extern WiFiManager wifiManager; +extern bool continuousSendEnabled; +extern unsigned long continuousSendInterval; + +extern const char* mqttServer; +extern const int mqttPort; +extern const char* mqttProductKey; +extern const char* mqttDeviceModel; +extern const char* mqttProductSecret; +extern String deviceMacAddress; + +extern WiFiClient mqttWiFiClient; +extern PubSubClient mqttClient; + +extern TaskHandle_t mqttTaskHandle; + +void mqttTask(void *parameter); +String getMqttDeviceName(); +String getMqttClientId(); +String getMqttSubscribeTopic(); +String getMqttPropertyPostTopic(); +String makeMqttPassword(const String& clientId); +String buildReplyTopic(const char* requestTopic); +bool publishMqttReply(const char* requestTopic, const char* requestId, const char* requestMethod, int code, JsonVariant data); +void mqttMessageCallback(char* topic, byte* payload, unsigned int length); +void initMQTT(); +void reconnectMQTT(); +void checkMQTTStatus(); +void sendDailyDataToMQTT(); +void sendSleepDataToMQTT(); + +#endif \ No newline at end of file diff --git a/src/radar_manager.cpp b/src/radar_manager.cpp index a6c38ab..71f0bec 100644 --- a/src/radar_manager.cpp +++ b/src/radar_manager.cpp @@ -2,14 +2,17 @@ #include "wifi_manager.h" #include #include +#include extern uint16_t currentDeviceId; // 当前设备ID extern Preferences preferences; // Flash存储对象 extern WiFiManager wifiManager; // WiFi管理器对象 const int BAUD_RATE = 115200; // 串口波特率 -const int UART1_RX = 3; // UART1接收引脚 -const int UART1_TX = 2; // UART1发送引脚 +const int UART1_RX = 11; // UART1接收新板引脚 +const int UART1_TX = 10; // UART1发送新板引脚 +//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; // 生命体征数据发送间隔(毫秒) @@ -18,10 +21,15 @@ 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服务器地址 +static int dnsFailCount = 0; // DNS解析失败计数器 +const int DNS_FAIL_RESET_THRESHOLD = 3; // DNS失败重置网络阈值 + +void resetWiFiConnection(); // WiFi连接重置函数声明 + +//const char* influxDBHost = "8.134.11.76"; // InfluxDB服务器公网地址 +const char* influxDBHost = "www.lmhrt.cn"; // InfluxDB服务器域名地址 const int influxDBPort = 8086; // InfluxDB服务器端口 const char* influxDBToken = "KuTa5ZsqoHIhi2IglOO06zExUYw1_mJ6K0mcA9X1y6O6CJDog3_Cgr8mUw1SwpuCCKRElqxa6wAhrrhsYPytkg=="; // InfluxDB访问令牌 const char* influxDBOrg = "gzlg"; // InfluxDB组织名称 @@ -51,6 +59,7 @@ unsigned long lastReceiveTime = 0; // 上次接收数据时间 bool continuousSendEnabled = false; // 持续发送使能标志 unsigned long continuousSendInterval = 500; // 持续发送间隔(毫秒) BLEFlowController bleFlow(500); // BLE流控制器对象 +SemaphoreHandle_t bleSendMutex; // BLE发送互斥锁 unsigned long lastSensorUpdate = 0; // 上次传感器更新时间 LastSentData lastSentData = {0}; // 上次发送的数据,初始化为0 @@ -143,7 +152,6 @@ void MyServerCallbacks::onDisconnect(BLEServer* pServer) { deviceConnected = false; Serial.println("🔴 [BLE] 客户端已断开"); continuousSendEnabled = false; - Serial.println("🔄 重置持续发送状态"); } /** @@ -153,24 +161,21 @@ void MyServerCallbacks::onDisconnect(BLEServer* pServer) { */ 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 == '{') { @@ -183,7 +188,7 @@ void MyCallbacks::onWrite(BLECharacteristic *pCharacteristic) { } } } - + if (closeBrace > openBrace) { String jsonData = completeData.substring(openBrace, closeBrace + 1); completeData = completeData.substring(closeBrace + 1); @@ -197,27 +202,28 @@ void MyCallbacks::onWrite(BLECharacteristic *pCharacteristic) { /** * @brief 初始化雷达管理器 - * 初始化串口、队列和FreeRTOS任务 + * 初始化队列和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("✅ 串口中断回调已设置"); - + + bleSendMutex = xSemaphoreCreateMutex(); + if (bleSendMutex == NULL) { + Serial.println("❌ BLE发送互斥锁创建失败"); + } else { + Serial.println("✅ BLE发送互斥锁创建成功"); + } + xTaskCreatePinnedToCore( bleSendTask, "BleSendTask", @@ -334,10 +340,8 @@ bool parseR60ABD1Frame(uint8_t *frame, uint16_t frameLen) { } 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]; @@ -346,28 +350,26 @@ bool parseR60ABD1Frame(uint8_t *frame, uint16_t frameLen) { 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("🔄 人体存在监测功能已关闭"); + sendRadarCommand(0x84, 0x00, 0x01); // 睡眠监测 } } break; case 0x01: if(dataLen >= 1) { - sensorData.presence = frame[6]; + sensorData.presence = frame[6]; // 0:无人, 1:有人 Serial.printf("👤 人体存在: %s\n", sensorData.presence ? "有人" : "无人"); } break; case 0x02: if(dataLen >= 1) { - sensorData.motion = frame[6]; + sensorData.motion = frame[6]; // 0:无, 1:静止, 2:活跃 const char* states[] = {"无", "静止", "活跃"}; Serial.printf("🏃 运动状态: %s\n", states[sensorData.motion]); } @@ -375,7 +377,7 @@ bool parseR60ABD1Frame(uint8_t *frame, uint16_t frameLen) { case 0x03: if(dataLen >= 1) { - sensorData.body_movement = frame[6]; + sensorData.body_movement = frame[6]; // 0-100 Serial.printf("📊体动参数: %d\n", sensorData.body_movement); } break; @@ -397,11 +399,6 @@ bool parseR60ABD1Frame(uint8_t *frame, uint16_t frameLen) { 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; @@ -416,22 +413,12 @@ bool parseR60ABD1Frame(uint8_t *frame, uint16_t frameLen) { 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; @@ -440,7 +427,6 @@ bool parseR60ABD1Frame(uint8_t *frame, uint16_t frameLen) { 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; @@ -449,12 +435,10 @@ bool parseR60ABD1Frame(uint8_t *frame, uint16_t frameLen) { 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; @@ -464,10 +448,6 @@ bool parseR60ABD1Frame(uint8_t *frame, uint16_t frameLen) { case 0x00: case 0x80: if(dataLen >= 1) { - if(frame[6] == 0x01) - Serial.println("🔄 心率监测功能已开启"); - else - Serial.println("🔄 心率监测功能已关闭"); } break; case 0x02: @@ -475,7 +455,6 @@ bool parseR60ABD1Frame(uint8_t *frame, uint16_t frameLen) { 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; @@ -484,12 +463,10 @@ bool parseR60ABD1Frame(uint8_t *frame, uint16_t frameLen) { 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; @@ -499,10 +476,6 @@ bool parseR60ABD1Frame(uint8_t *frame, uint16_t frameLen) { case 0x00: case 0x80: if(dataLen >= 1) { - if(frame[6] == 0x01) - Serial.println("🔄 睡眠监测功能已开启"); - else - Serial.println("🔄 睡眠监测功能已关闭"); } break; @@ -510,8 +483,6 @@ bool parseR60ABD1Frame(uint8_t *frame, uint16_t frameLen) { case 0x81: if(dataLen >= 1) { sensorData.bed_status = frame[6]; - const char* status_str[] = {"离床", "入床", "无"}; - Serial.printf("🛏️ 床状态: %s\n", status_str[sensorData.bed_status]); } break; @@ -519,7 +490,6 @@ bool parseR60ABD1Frame(uint8_t *frame, uint16_t frameLen) { 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; @@ -527,7 +497,6 @@ bool parseR60ABD1Frame(uint8_t *frame, uint16_t frameLen) { 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; @@ -535,21 +504,18 @@ bool parseR60ABD1Frame(uint8_t *frame, uint16_t frameLen) { 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; @@ -564,12 +530,6 @@ bool parseR60ABD1Frame(uint8_t *frame, uint16_t frameLen) { 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; @@ -589,18 +549,6 @@ bool parseR60ABD1Frame(uint8_t *frame, uint16_t frameLen) { 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; @@ -608,12 +556,6 @@ bool parseR60ABD1Frame(uint8_t *frame, uint16_t frameLen) { 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; @@ -621,10 +563,6 @@ bool parseR60ABD1Frame(uint8_t *frame, uint16_t frameLen) { 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; @@ -632,10 +570,6 @@ bool parseR60ABD1Frame(uint8_t *frame, uint16_t frameLen) { 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; @@ -643,25 +577,16 @@ bool parseR60ABD1Frame(uint8_t *frame, uint16_t frameLen) { 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; @@ -723,15 +648,6 @@ void sendRadarCommand(uint8_t ctrl, uint8_t cmd, uint8_t value) { 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() { @@ -843,27 +759,23 @@ void bleSendTask(void *parameter) { } 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雷达数据蓝牙发送成功"); + if (xSemaphoreTake(bleSendMutex, portMAX_DELAY) == pdTRUE) { + pCharacteristic->setValue(radarDataMsg.c_str()); + pCharacteristic->notify(); + xSemaphoreGive(bleSendMutex); + } } 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("📊 数据已更新,上次发送数据已保存"); } } } @@ -873,6 +785,10 @@ void bleSendTask(void *parameter) { } } +bool isInRefusedCooldown(); +unsigned long getRefusedCooldownRemaining(); +void updateRefusedTime(); + /** * @brief 生命体征数据发送任务 * 从队列中获取生命体征数据并发送到InfluxDB数据库 @@ -884,6 +800,14 @@ void vitalSendTask(void *parameter) { unsigned long lastSleepDataTime = 0; const unsigned long SLEEP_DATA_INTERVAL = 5000; + VitalData cachedData = {0}; + bool hasCachedData = false; + unsigned long lastSendTime = 0; + const unsigned long FORCE_SEND_INTERVAL = 60000; // 每分钟强制发送一次 + const unsigned long MIN_SEND_INTERVAL = 1500; // 最小发送间隔1秒 + VitalData pendingData = {0}; + bool hasPendingData = false; + while (1) { VitalData vitalData; @@ -891,102 +815,267 @@ void vitalSendTask(void *parameter) { esp_task_wdt_reset(); if (WiFi.status() == WL_CONNECTED) { - // 检查心率和呼吸率是否都为0,如果是则跳过发送 if (vitalData.heart_rate == 0 || vitalData.breath_rate == 0) { - Serial.println("⚠️ 心率和呼吸率都为0,跳过发送数据到数据库"); + Serial.printf("⚠️ 心率=%.1f, 呼吸率=%.1f,跳过发送数据到数据库\n", + vitalData.heart_rate, vitalData.breath_rate); 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.heart_rate > 200 || vitalData.breath_rate > 50 || + vitalData.heart_rate < 0 || vitalData.breath_rate < 0) { + Serial.printf("❌ 数据异常 - 心率:%.1f, 呼吸:%.1f,丢弃\n", + vitalData.heart_rate, vitalData.breath_rate); + continue; } - if (vitalData.breath_rate > 0) { - if (!firstField) dailyDataLine += ","; - dailyDataLine += "breathingRate=" + String(vitalData.breath_rate, 1); - firstField = false; - } + bool dataChanged = false; + bool shouldSend = 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(); + if (!hasCachedData) { + dataChanged = true; + shouldSend = true; + Serial.printf("📊 首次数据 - 心率:%.1f, 呼吸:%.1f, 距离:%d\n", + vitalData.heart_rate, vitalData.breath_rate, vitalData.distance); + } else { + if (memcmp(&vitalData, &cachedData, sizeof(VitalData)) != 0) { + dataChanged = true; + shouldSend = true; + + if (fabs(vitalData.heart_rate - cachedData.heart_rate) >= 1.0f || + fabs(vitalData.breath_rate - cachedData.breath_rate) >= 1.0f) { + Serial.printf("📊 数据变化: 心率 %.1f->%.1f, 呼吸 %.1f->%.1f, 距离 %d->%d, 状态 %d->%d\n", + cachedData.heart_rate, vitalData.heart_rate, + cachedData.breath_rate, vitalData.breath_rate, + cachedData.distance, vitalData.distance, + cachedData.sleep_state, vitalData.sleep_state); + } else { + Serial.printf("📊 数据变化: 距离 %d->%d, 状态 %d->%d, 运动 %d->%d, 异常 %d->%d\n", + cachedData.distance, vitalData.distance, + cachedData.sleep_state, vitalData.sleep_state, + cachedData.motion, vitalData.motion, + cachedData.abnormal_state, vitalData.abnormal_state); + } + } } unsigned long currentTime = millis(); - if (currentTime - lastSleepDataTime >= SLEEP_DATA_INTERVAL) { - sendSleepDataToInfluxDB(); - lastSleepDataTime = currentTime; - Serial.println("⏰ 睡眠数据定时发送完成"); + if (!shouldSend && (currentTime - lastSendTime >= FORCE_SEND_INTERVAL)) { + shouldSend = true; + Serial.printf("⏰ 定时发送触发 (距上次 %d 秒)\n", (currentTime - lastSendTime) / 1000); + } + + cachedData = vitalData; + hasCachedData = true; + + if (shouldSend) { + unsigned long timeSinceLastSend = millis() - lastSendTime; + + if (isInRefusedCooldown()) { + Serial.printf("⏳ 连接被拒绝冷却中,等待 %d 秒\n", getRefusedCooldownRemaining() / 1000); + if (timeSinceLastSend < MIN_SEND_INTERVAL && lastSendTime > 0) { + pendingData = vitalData; + hasPendingData = true; + } + } else if (timeSinceLastSend < MIN_SEND_INTERVAL && lastSendTime > 0) { + pendingData = vitalData; + hasPendingData = true; + Serial.printf("⏳ 发送间隔不足 (%d ms),缓存最新数据等待 %d ms\n", + (int)timeSinceLastSend, MIN_SEND_INTERVAL - (int)timeSinceLastSend); + } else { + Serial.printf("📡 发送数据 - 心率:%.1f, 呼吸:%.1f, 距离:%d\n", + vitalData.heart_rate, vitalData.breath_rate, vitalData.distance); + + 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(" ")) { + bool sendSuccess = sendDailyDataToInfluxDB(dailyDataLine); + esp_task_wdt_reset(); + + if (sendSuccess) { + lastSendTime = millis(); + } else { + Serial.println("❌ 发送失败"); + } + } + + unsigned long currentTime = millis(); + if (currentTime - lastSleepDataTime >= SLEEP_DATA_INTERVAL) { + sendSleepDataToInfluxDB(); + lastSleepDataTime = currentTime; + } + + hasPendingData = false; + } + } else { + Serial.printf("💾 缓存数据未变化 - 心率:%.1f, 呼吸:%.1f\n", + vitalData.heart_rate, vitalData.breath_rate); + + if (hasPendingData && (millis() - lastSendTime >= MIN_SEND_INTERVAL) && !isInRefusedCooldown()) { + Serial.printf("⏰ 发送缓存数据 - 心率:%.1f, 呼吸:%.1f\n", + pendingData.heart_rate, pendingData.breath_rate); + + String dailyDataLine = "daily_data,deviceId=" + String(currentDeviceId) + ",dataType=daily "; + + bool firstField = true; + + if (pendingData.heart_rate > 0) { + if (!firstField) dailyDataLine += ","; + dailyDataLine += "heartRate=" + String(pendingData.heart_rate, 1); + firstField = false; + } + + if (pendingData.breath_rate > 0) { + if (!firstField) dailyDataLine += ","; + dailyDataLine += "breathingRate=" + String(pendingData.breath_rate, 1); + firstField = false; + } + + if (!firstField) dailyDataLine += ","; + dailyDataLine += "personDetected=" + String(pendingData.presence) + "i"; + firstField = false; + + if (!firstField) dailyDataLine += ","; + dailyDataLine += "humanActivity=" + String(pendingData.motion) + "i"; + firstField = false; + + if (pendingData.distance > 0) { + if (!firstField) dailyDataLine += ","; + dailyDataLine += "humanDistance=" + String(pendingData.distance) + "i"; + firstField = false; + } + + if (pendingData.sleep_state >= 0) { + if (!firstField) dailyDataLine += ","; + dailyDataLine += "sleepState=" + String(pendingData.sleep_state) + "i"; + firstField = false; + } + + if (!firstField) dailyDataLine += ","; + dailyDataLine += "humanPositionX=" + String(pendingData.pos_x) + "i"; + firstField = false; + + if (!firstField) dailyDataLine += ","; + dailyDataLine += "humanPositionY=" + String(pendingData.pos_y) + "i"; + firstField = false; + + if (!firstField) dailyDataLine += ","; + dailyDataLine += "humanPositionZ=" + String(pendingData.pos_z) + "i"; + firstField = false; + + if (!firstField) dailyDataLine += ","; + dailyDataLine += "heartbeatWaveform=" + String((int)pendingData.heartbeat_waveform) + "i"; + firstField = false; + + if (!firstField) dailyDataLine += ","; + dailyDataLine += "breathingWaveform=" + String((int)pendingData.breathing_waveform) + "i"; + firstField = false; + + if (!firstField) dailyDataLine += ","; + dailyDataLine += "abnormalState=" + String(pendingData.abnormal_state) + "i"; + firstField = false; + + if (!firstField) dailyDataLine += ","; + dailyDataLine += "bedStatus=" + String(pendingData.bed_status) + "i"; + firstField = false; + + if (!firstField) dailyDataLine += ","; + dailyDataLine += "struggleAlert=" + String(pendingData.struggle_alert) + "i"; + firstField = false; + + if (!firstField) dailyDataLine += ","; + dailyDataLine += "noOneAlert=" + String(pendingData.no_one_alert) + "i"; + firstField = false; + + if (!dailyDataLine.endsWith(" ")) { + bool sendSuccess = sendDailyDataToInfluxDB(dailyDataLine); + esp_task_wdt_reset(); + + if (sendSuccess) { + lastSendTime = millis(); + } else { + Serial.println("❌ 缓存数据发送失败"); + } + } + + hasPendingData = false; + } } } else { - Serial.println("❌❌ WiFi未连接,无法发送雷达数据到数据库"); - - static unsigned long lastWifiCheck = 0; - if (millis() - lastWifiCheck > 10000) { - Serial.printf("📶📶 WiFi状态: %d\n", WiFi.status()); - lastWifiCheck = millis(); + UBaseType_t queueItems = uxQueueMessagesWaiting(vitalDataQueue); + if (queueItems > 0) { + VitalData tempData; + while (xQueueReceive(vitalDataQueue, &tempData, 0) == pdTRUE) { + } } } @@ -1001,34 +1090,116 @@ void vitalSendTask(void *parameter) { * @brief 发送日常数据到InfluxDB数据库 * 通过HTTP协议将数据写入InfluxDB时序数据库 * @param dailyDataLine 数据行字符串 + * @return 是否发送成功 */ -void sendDailyDataToInfluxDB(String dailyDataLine) { +bool isInRefusedCooldown() { + static unsigned long lastRefusedTime = 0; + const unsigned long REFUSED_COOLDOWN = 10000; + + if (lastRefusedTime == 0) return false; + + unsigned long currentTime = millis(); + return (currentTime - lastRefusedTime < REFUSED_COOLDOWN); +} + +unsigned long getRefusedCooldownRemaining() { + static unsigned long lastRefusedTime = 0; + const unsigned long REFUSED_COOLDOWN = 10000; + + if (lastRefusedTime == 0) return 0; + + unsigned long currentTime = millis(); + unsigned long elapsed = currentTime - lastRefusedTime; + if (elapsed >= REFUSED_COOLDOWN) return 0; + return REFUSED_COOLDOWN - elapsed; +} + +void updateRefusedTime() { + static unsigned long lastRefusedTime = 0; + lastRefusedTime = millis(); +} + +void resetWiFiConnection() { + Serial.println("🔄 开始重置WiFi连接..."); + WiFi.disconnect(true); + vTaskDelay(200 / portTICK_PERIOD_MS); + WiFi.mode(WIFI_OFF); + vTaskDelay(200 / portTICK_PERIOD_MS); + WiFi.mode(WIFI_STA); + vTaskDelay(200 / portTICK_PERIOD_MS); + Serial.println("✅ WiFi连接已重置,将触发重连"); + wifiManager.handleReconnect(); +} + +bool sendDailyDataToInfluxDB(String dailyDataLine) { if (WiFi.status() != WL_CONNECTED) { - Serial.println("❌ WiFi未连接,无法发送日常数据到数据库"); - return; + Serial.println("❌ WiFi未连接,跳过发送"); + return false; } + int rssi = WiFi.RSSI(); + if (rssi < -85) { + Serial.printf("⚠️ WiFi信号过弱 (%d dBm),跳过发送\n", rssi); + return false; + } + + if (WiFi.localIP() == IPAddress(0, 0, 0, 0)) { + Serial.println("❌ WiFi没有获取到IP地址,跳过发送"); + return false; + } + + if (isInRefusedCooldown()) { + Serial.printf("⚠️ 连接被拒绝冷却中,等待 %d 秒\n", getRefusedCooldownRemaining() / 1000); + return false; + } + + IPAddress resolvedIP; + if (!WiFi.hostByName(influxDBHost, resolvedIP)) { + Serial.printf("❌ DNS解析失败: %s\n", influxDBHost); + dnsFailCount++; + Serial.printf("⚠️ DNS解析失败计数: %d/%d\n", dnsFailCount, DNS_FAIL_RESET_THRESHOLD); + + if (dnsFailCount >= DNS_FAIL_RESET_THRESHOLD) { + Serial.println("⚠️ DNS解析连续失败次数过多,重置WiFi连接..."); + dnsFailCount = 0; + resetWiFiConnection(); + } + return false; + } else { + dnsFailCount = 0; + } + + String url = String("http://") + resolvedIP.toString() + ":" + String(influxDBPort) + "/api/v2/write?org=" + String(influxDBOrg) + "&bucket=" + String(influxDBBucket); + HTTPClient http; - http.setTimeout(2000); - - String url = String("http://") + String(influxDBHost) + ":" + String(influxDBPort) + "/api/v2/write?org=" + String(influxDBOrg) + "&bucket=" + String(influxDBBucket); - + http.setTimeout(15000); + http.setConnectTimeout(8000); + http.setReuse(false); + 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); - + http.addHeader("Connection", "close"); + http.addHeader("Host", String(influxDBHost)); + int httpResponseCode = http.POST(dailyDataLine); + String errorMsg = http.errorToString(httpResponseCode); if (httpResponseCode == 204) { - Serial.println("✅ 日常数据发送成功"); - } else { - Serial.println(String("❌ 发送日常数据失败: ") + String(httpResponseCode) + " - " + http.getString()); + Serial.println("✅ 日常数据已保存到InfluxDB"); + http.end(); + return true; } + Serial.printf("❌ HTTP响应: %d (%s)\n", httpResponseCode, errorMsg.c_str()); http.end(); + + if (errorMsg.indexOf("connection refused") >= 0 || httpResponseCode == -1) { + updateRefusedTime(); + Serial.println("⚠️ 服务器拒绝连接,进入冷却期"); + } + + return false; } /** @@ -1037,24 +1208,22 @@ void sendDailyDataToInfluxDB(String dailyDataLine) { */ 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); + http.setTimeout(5000); // 增加超时到5秒 + http.setConnectTimeout(5000); // 连接超时5秒 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 "; @@ -1087,14 +1256,41 @@ void sendSleepDataToInfluxDB() { Serial.println(String("🌙 发送睡眠数据到InfluxDB: ") + lineProtocol); - int httpResponseCode = http.POST(lineProtocol); + // 重试机制 + int maxRetries = 3; + int httpResponseCode = -1; - if (httpResponseCode == 204) { - Serial.println(String("✅ 睡眠数据已保存到InfluxDB设备") + String(currentDeviceId) + "上"); - } else { - Serial.println(String("❌ 保存睡眠数据到InfluxDB失败: ") + String(httpResponseCode) + " - " + http.getString()); + for (int retry = 0; retry < maxRetries; retry++) { + if (retry > 0) { + Serial.printf("🔄 睡眠数据第%d次重试...\n", retry + 1); + delay(500); + } + + httpResponseCode = http.POST(lineProtocol); + + if (httpResponseCode == 204) { + Serial.println(String("✅ 睡眠数据已保存到InfluxDB设备") + String(currentDeviceId) + "上"); + http.end(); + return; + } + + // 如果是连接错误,继续重试 + if (httpResponseCode == -1 || httpResponseCode == 0) { + Serial.printf("⚠️ 睡眠数据连接失败 (错误码:%d),准备重试...\n", httpResponseCode); + http.end(); + http.begin(url); + http.addHeader("Authorization", String("Token ") + String(influxDBToken)); + http.addHeader("Content-Type", "text/plain; charset=utf-8"); + continue; + } else { + break; + } } + Serial.printf("❌ 保存睡眠数据到InfluxDB失败 (重试%d次): %d - %s\n", + maxRetries, httpResponseCode, + httpResponseCode == -1 ? "连接超时" : http.getString().c_str()); + http.end(); } @@ -1117,11 +1313,17 @@ void radarDataTask(void *parameter) { * @param parameter 任务参数(未使用) */ void uartProcessTask(void *parameter) { + Serial.println("🔧 初始化雷达UART串口..."); + mySerial1.setRxBufferSize(UART_RX_BUFFER_SIZE); + mySerial1.begin(BAUD_RATE, SERIAL_8N1, UART1_RX, UART1_TX); + mySerial1.onReceive(serialRxCallback); + Serial.println("✅ UART1配置完成,缓冲区大小: 4096字节"); + uint8_t buffer[256]; int bufferIndex = 0; bool inFrame = false; uint8_t prevByte = 0; - + Serial.println("✅ R60ABD1串口数据处理任务启动"); while(1) { @@ -1134,7 +1336,6 @@ void uartProcessTask(void *parameter) { buffer[1] = FRAME_HEADER2; bufferIndex = 2; inFrame = true; - Serial.println("🔍 检测到R60ABD1帧头"); } } else { if(bufferIndex < sizeof(buffer)) { @@ -1155,12 +1356,12 @@ void uartProcessTask(void *parameter) { phasePacketCounter++; if (phasePacketCounter >= PHASE_SEND_INTERVAL) { PhaseData phaseData; + memset(&phaseData, 0, sizeof(PhaseData)); // 初始化为0 phaseData.heartbeat_waveform = sensorData.heartbeat_waveform; phaseData.breathing_waveform = sensorData.breathing_waveform; if (xQueueSend(phaseDataQueue, &phaseData, 0) == pdTRUE) { } else { - Serial.println("❌ 相位数据队列已满,数据丢失"); } phasePacketCounter = 0; } @@ -1168,6 +1369,7 @@ void uartProcessTask(void *parameter) { vitalPacketCounter++; if (vitalPacketCounter >= VITAL_SEND_INTERVAL) { VitalData vitalData; + memset(&vitalData, 0, sizeof(VitalData)); // 初始化为0 vitalData.heart_rate = sensorData.heart_rate; vitalData.breath_rate = sensorData.breath_rate; vitalData.presence = sensorData.presence; @@ -1203,15 +1405,12 @@ void uartProcessTask(void *parameter) { 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); } } @@ -1237,47 +1436,47 @@ void uartProcessTask(void *parameter) { * @param data 要发送的数据字符串 */ void sendDataInChunks(const String& data) { + if (xSemaphoreTake(bleSendMutex, portMAX_DELAY) != pdTRUE) { + return; + } + 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); - + + Serial.printf("[BLE发送] 分包数据 %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未连接,无法发送数据"); + xSemaphoreGive(bleSendMutex); 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("📦 分包发送完成"); + + xSemaphoreGive(bleSendMutex); } /** @@ -1286,37 +1485,35 @@ void sendDataInChunks(const String& data) { * @param jsonData JSON格式数据字符串 */ void sendJSONDataToBLE(const String& jsonData) { - Serial.printf("📤 准备发送JSON数据: %s\n", jsonData.c_str()); - - if (!deviceConnected) { - Serial.println("❌ BLE未连接,无法发送JSON数据"); + if (xSemaphoreTake(bleSendMutex, portMAX_DELAY) != pdTRUE) { return; } - + + Serial.printf("[BLE发送] %s\n", jsonData.c_str()); + + if (!deviceConnected) { + xSemaphoreGive(bleSendMutex); + 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分包发送完成"); + + xSemaphoreGive(bleSendMutex); } /** @@ -1328,16 +1525,13 @@ void sendJSONDataToBLE(const String& jsonData) { */ 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; } @@ -1415,14 +1609,10 @@ bool processStartContinuousSend(JsonDocument& doc) { Serial.printf("⚙️ 启动持续发送模式,间隔: %lu ms\n", continuousSendInterval); if (deviceConnected) { - String confirmMsg = String("{\"type\":\"startContinuousSendResult\",\"success\":true,\"message\":\"已启动持续发送模式\",\"interval\":") + + String confirmMsg = String("{\"type\":\"startContinuousSendResult\",\"success\":true,\"message\":\"已启动持续发送模式\",\"interval\":") + String(continuousSendInterval) + "}"; - + sendJSONDataToBLE(confirmMsg); - Serial.println("✅ 启动确认消息发送成功"); - Serial.println("🚀 已启动持续发送模式"); - } else { - Serial.println("❌ BLE未连接,无法发送确认消息"); } return true; } @@ -1444,12 +1634,8 @@ bool processStopContinuousSend(JsonDocument& doc) { if (deviceConnected) { String confirmMsg = String("{\"type\":\"stopContinuousSendResult\",\"success\":true,\"message\":\"已停止持续发送模式\"}"); - + sendJSONDataToBLE(confirmMsg); - Serial.println("✅ 停止确认消息发送成功"); - Serial.println("⏹️ 已停止持续发送模式"); - } else { - Serial.println("❌ BLE未连接,无法发送确认消息"); } return true; } @@ -1461,66 +1647,59 @@ bool processStopContinuousSend(JsonDocument& doc) { * 处理从BLE接收到的配置数据,解析JSON命令并执行相应操作 */ void processBLEConfig() { - if (completeData.length() > 0 && (millis() - lastReceiveTime > 3000)) { - Serial.println("⏰ [超时] 数据接收超时3秒,自动当作接收完成"); - - if (receivedData.length() == 0) { - Serial.println("📥 [BLE] 超时后将completeData当作接收数据进行处理"); - receivedData = completeData; - } - + unsigned long currentMillis = millis(); + + if (completeData.length() > 0 && receivedData.length() == 0 && + (currentMillis - lastReceiveTime > 5000)) { + Serial.println("⏰ [超时] 数据接收超时5秒,自动当作接收完成"); + Serial.printf(" completeData长度: %d, 内容: %s\n", completeData.length(), completeData.c_str()); + + receivedData = completeData; completeData = ""; } - + if (receivedData.length() > 0) { String bleData = receivedData; receivedData = ""; bleData.trim(); - - Serial.printf("⚙️ [BLE] 准备解析JSON: %s\n", bleData.c_str()); - + + Serial.printf("[BLE] 解析: %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); - } + String errorMsg = String("{\"type\":\"error\",\"message\":\"JSON解析失败: ") + String(error.c_str()) + String("\",\"originalData\":\"") + bleData + String("\"}"); + sendJSONDataToBLE(errorMsg); } 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) { JsonDocument errorDoc; errorDoc["type"] = "error"; errorDoc["message"] = "未知命令"; - errorDoc["receivedData"] = bleData; // 自动转义特殊字符 + errorDoc["receivedData"] = bleData; String responseMsg; serializeJson(errorDoc, responseMsg); @@ -1560,7 +1739,7 @@ bool processSetDeviceId(JsonDocument& doc) { Serial.printf("[设备ID] 已设置新的设备ID: %u\n", currentDeviceId); - saveDeviceId(); + preferences.putUShort("deviceId", currentDeviceId); Serial.printf("设备ID已保存到Flash: %u\n", currentDeviceId); if (deviceConnected) { @@ -1638,6 +1817,7 @@ bool processWiFiConfigCommand(JsonDocument& doc) { const char* newPassword = doc["password"]; if (newSSID != nullptr && newPassword != nullptr) { + return wifiManager.handleConfigurationData(newSSID, newPassword); } else { Serial.println("❌ [BLE-WiFi] WiFi配置参数不完整"); @@ -1661,8 +1841,16 @@ 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; + + // 调用新的startScan接口(会暂停重连任务) + if (wifiManager.startScan(30000)) { + Serial.println("✅ 扫描已启动"); + return true; + } else { + String errorMsg = String("{\"type\":\"scanWiFiResult\",\"success\":false,\"message\":\"无法启动扫描\",\"networks\":[],\"count\":0}"); + sendJSONDataToBLE(errorMsg); + return false; + } } return false; } @@ -1692,20 +1880,17 @@ bool processEchoRequest(JsonDocument& doc) { 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; @@ -1718,10 +1903,9 @@ bool processEchoRequest(JsonDocument& doc) { */ void sendRawEchoResponse(const String& rawData) { if (deviceConnected) { - String echoResponse = String("{\"type\":\"rawEchoResponse\",\"originalData\":\"") + + String echoResponse = String("{\"type\":\"rawEchoResponse\",\"originalData\":\"") + rawData + String("\",\"received\":true}"); - + sendJSONDataToBLE(echoResponse); - Serial.printf("📤 [BLE] 原始数据回显已发送: %s\n", rawData.c_str()); } } diff --git a/src/radar_manager.h b/src/radar_manager.h index cb30051..69939a3 100644 --- a/src/radar_manager.h +++ b/src/radar_manager.h @@ -18,7 +18,7 @@ class WiFiManager; #define SERVICE_UUID "a8c1e5c0-3d5d-4a9d-8d5e-7c8b6a4e2f1a" // BLE服务UUID #define CHARACTERISTIC_UUID "beb5483e-36e1-4688-b7f5-ea07361b26a8" // BLE特征值UUID #define UART_RX_BUFFER_SIZE 4096 // UART接收缓冲区大小 -#define QUEUE_SIZE 50 // 队列大小 +#define QUEUE_SIZE 200 // 队列大小(增加到200以防止溢出) #define TASK_STACK_SIZE 8192 // 任务堆栈大小 #define FRAME_HEADER1 0x53 // 帧头字节1 @@ -177,6 +177,7 @@ extern String completeData; // 完整数据 extern unsigned long lastReceiveTime; // 上次接收数据时间 extern bool continuousSendEnabled; // 持续发送使能标志 extern unsigned long continuousSendInterval; // 持续发送间隔 +extern unsigned long lastSleepDataTime; // 上次发送睡眠数据时间 extern BLEFlowController bleFlow; // BLE流控制器 extern unsigned long lastSensorUpdate; // 上次传感器更新时间 extern LastSentData lastSentData; // 上次发送的数据 @@ -217,10 +218,7 @@ bool processEchoRequest(JsonDocument& doc); void sendRawEchoResponse(const String& rawData); void sendStatusToBLE(); -void loadDeviceId(); -void saveDeviceId(); - -void sendDailyDataToInfluxDB(String dailyDataLine); +bool sendDailyDataToInfluxDB(String dailyDataLine); void sendSleepDataToInfluxDB(); class MyServerCallbacks: public BLEServerCallbacks { diff --git a/src/sleep_analyzer.cpp b/src/sleep_analyzer.cpp new file mode 100644 index 0000000..cb3a43a --- /dev/null +++ b/src/sleep_analyzer.cpp @@ -0,0 +1,978 @@ +#include "sleep_analyzer.h" + +const float SleepAnalyzer::SLEEPINESS_THRESHOLD = 0.6f; +const float SleepAnalyzer::BASELINE_MOVEMENT_THRESHOLD = 0.2f; +const float SleepAnalyzer::BASELINE_HR_STABILITY_THRESHOLD = 5.0f; +const float SleepAnalyzer::BASELINE_RR_STABILITY_THRESHOLD = 2.0f; +const float SleepAnalyzer::EMA_ALPHA = 0.2f; +const float SleepAnalyzer::CONFIDENCE_MARGIN = 0.1f; +const float SleepAnalyzer::BASELINE_BETA = 0.01f; +const float SleepAnalyzer::HYSTERESIS_ENTER_DEEP = 0.7f; +const float SleepAnalyzer::HYSTERESIS_EXIT_DEEP = 0.5f; +const float SleepAnalyzer::HYSTERESIS_ENTER_REM = 0.6f; +const float SleepAnalyzer::HYSTERESIS_EXIT_REM = 0.4f; + +SleepAnalyzer::SleepAnalyzer() { + reset(); +} + +SleepAnalyzer::~SleepAnalyzer() { +} + +void SleepAnalyzer::reset() { + currentState = SLEEP_NO_PERSON; + pendingState = SLEEP_NO_PERSON; + memset(&stats, 0, sizeof(SleepStatistics)); + memset(&score, 0, sizeof(SleepScore)); + memset(&cycle, 0, sizeof(SleepCycle)); + + stateEnterTime = millis(); + pendingStateTime = 0; + noPersonTimer = 0; + outOfBedTimer = 0; + sleepinessDuration = 0; + awakeDuration = 0; + deepSleepDuration = 0; + lightSleepDuration = 0; + remSleepDuration = 0; + movementHighDuration = 0; + gettingUpDuration = 0; + deepStableDuration = 0; + + baselineHR = 70.0f; + baselineRR = 16.0f; + baselineCalibrated = false; + baselineSampleCount = 0; + baselineHRSum = 0; + baselineRRSum = 0; + lastBaselineHR = 0; + lastBaselineRR = 0; + hrStabilitySum = 0; + rrStabilitySum = 0; + stabilitySampleCount = 0; + + currentSleepiness = 0; + currentDeepScore = 0; + currentLightScore = 0; + currentAwakeScore = 0; + currentRemScore = 0; + + lastRRValue = 0; + wasAsleep = false; +} + +PresenceData SleepAnalyzer::evaluatePresence() { + PresenceData p; + memset(&p, 0, sizeof(PresenceData)); + + if (sensorData.heart_valid || sensorData.breath_valid) { + p.isPresent = true; + p.confidence = 0.9f; + } + + if (sensorData.presence == 1) { + p.isPresent = true; + p.confidence = max(p.confidence, 0.7f); + } + + if (sensorData.distance > 0) { + p.distance = sensorData.distance; + if (p.distance > 20 && p.distance < 100) { + p.isPresent = true; + } + } else { + p.distance = -1; + } + + p.motionEnergy = sensorData.body_movement / 100.0f; + + return p; +} + +float SleepAnalyzer::sigmoid(float x) { + return 1.0f / (1.0f + expf(-x)); +} + +float SleepAnalyzer::emaSmooth(float input, float last, float alpha) { + return alpha * input + (1.0f - alpha) * last; +} + +float SleepAnalyzer::updateBaseline(float current, float input, float beta) { + return (1.0f - beta) * current + beta * input; +} + +bool SleepAnalyzer::isBestScore(float score, float s2, float s3, float s4, float margin) { + return (score > s2 + margin && score > s3 + margin && score > s4 + margin); +} + +float SleepAnalyzer::normalizeHR(float hr) { + if (!baselineCalibrated) return 0.0f; + float norm = (hr - baselineHR) / 20.0f; + return constrain_value(norm, -1.0f, 1.0f); +} + +float SleepAnalyzer::normalizeRR(float rr) { + if (!baselineCalibrated) return 0.0f; + float norm = (rr - baselineRR) / 4.0f; + return constrain_value(norm, -1.0f, 1.0f); +} + +float SleepAnalyzer::normalizeHRV(float hrv) { + float norm = hrv / 50.0f; + return constrain_value(norm, 0.0f, 1.0f); +} + +float SleepAnalyzer::normalizeMovement(float movement) { + return constrain_value(movement / 100.0f, 0.0f, 1.0f); +} + +void SleepAnalyzer::calibrateBaseline(const HeartRateData& hrData, + const RespirationData& rrData, + const BodyMovementData& movementData) { + if (!hrData.isValid || !rrData.isValid) return; + if (currentState != SLEEP_AWAKE && currentState != SLEEP_IN_BED) return; + + if (movementData.isValid && normalizeMovement(movementData.movement) > BASELINE_MOVEMENT_THRESHOLD) { + return; + } + + if (baselineCalibrated) { + float hrDiff = fabs(hrData.bpmSmoothed - lastBaselineHR); + float rrDiff = fabs(rrData.rateSmoothed - lastBaselineRR); + hrStabilitySum += hrDiff; + rrStabilitySum += rrDiff; + stabilitySampleCount++; + + if (stabilitySampleCount >= 5) { + float avgHrDiff = hrStabilitySum / stabilitySampleCount; + float avgRrDiff = rrStabilitySum / stabilitySampleCount; + if (avgHrDiff > BASELINE_HR_STABILITY_THRESHOLD || + avgRrDiff > BASELINE_RR_STABILITY_THRESHOLD) { + hrStabilitySum = 0; + rrStabilitySum = 0; + stabilitySampleCount = 0; + return; + } + hrStabilitySum = 0; + rrStabilitySum = 0; + stabilitySampleCount = 0; + } + + baselineHR = updateBaseline(baselineHR, hrData.bpmSmoothed, BASELINE_BETA); + baselineRR = updateBaseline(baselineRR, rrData.rateSmoothed, BASELINE_BETA); + lastBaselineHR = hrData.bpmSmoothed; + lastBaselineRR = rrData.rateSmoothed; + return; + } + + baselineHRSum += hrData.bpmSmoothed; + baselineRRSum += rrData.rateSmoothed; + baselineSampleCount++; + + if (baselineSampleCount >= 30) { + lastBaselineHR = baselineHR; + lastBaselineRR = baselineRR; + baselineHR = baselineHRSum / baselineSampleCount; + baselineRR = baselineRRSum / baselineSampleCount; + baselineCalibrated = true; + baselineHRSum = 0; + baselineRRSum = 0; + baselineSampleCount = 0; + Serial.printf("📐 睡眠基线校准完成: HR=%.1f, RR=%.1f\n", baselineHR, baselineRR); + } +} + +float SleepAnalyzer::calculateSleepinessScore(const HeartRateData& hrData, + const RespirationData& rrData, + const HRVEstimate& hrvData, + const BodyMovementData& movementData) { + float hrSleepFactor = 0.5f, hrvNorm = 0, rrStable = 0, moveNorm = 0; + + if (hrData.isValid) { + float hrNorm = normalizeHR(hrData.bpmSmoothed); + hrSleepFactor = (1.0f - hrNorm) * 0.5f; + } + if (hrvData.isValid) { + hrvNorm = normalizeHRV(hrvData.rmssd); + } + if (rrData.isValid) { + rrStable = 1.0f - constrain_value(rrData.variability / 5.0f, 0.0f, 1.0f); + } + if (movementData.isValid) { + moveNorm = normalizeMovement(movementData.movement); + } + + float x = 3.0f * (hrSleepFactor - 0.5f) + + 2.5f * (hrvNorm - 0.5f) + + 2.0f * (rrStable - 0.5f) + + 2.5f * (0.5f - moveNorm); + + return sigmoid(x); +} + +float SleepAnalyzer::calculateDeepSleepScore(const HeartRateData& hrData, + const RespirationData& rrData, + const HRVEstimate& hrvData, + const BodyMovementData& movementData) { + if (movementData.isValid && movementData.movement > DEEP_SLEEP_HARD_MOVEMENT_LIMIT) { + return 0.0f; + } + + float hrSleepFactor = 0.5f, hrvNorm = 0, moveNorm = 0; + + if (hrData.isValid) { + float hrNorm = normalizeHR(hrData.bpmSmoothed); + hrSleepFactor = (1.0f - hrNorm) * 0.5f; + } + if (hrvData.isValid) { + hrvNorm = normalizeHRV(hrvData.rmssd); + } + if (movementData.isValid) { + moveNorm = normalizeMovement(movementData.movement); + } + + float x = 4.0f * (hrSleepFactor - 0.5f) + + 3.0f * (hrvNorm - 0.5f) + + 2.0f * (0.5f - moveNorm); + + return sigmoid(x); +} + +float SleepAnalyzer::calculateLightSleepScore(const HeartRateData& hrData, + const RespirationData& rrData, + const HRVEstimate& hrvData, + const BodyMovementData& movementData) { + float hrMid = 0, hrvMid = 0, moveMid = 0, rrStable = 0; + + if (hrData.isValid) { + float hrNorm = normalizeHR(hrData.bpmSmoothed); + float hrSleepFactor = (1.0f - hrNorm) * 0.5f; + hrMid = 1.0f - fabs(hrSleepFactor - 0.5f) * 2.0f; + } + if (hrvData.isValid) { + float hrvNorm = normalizeHRV(hrvData.rmssd); + hrvMid = 1.0f - fabs(hrvNorm - 0.5f) * 2.0f; + } + if (movementData.isValid) { + float moveNorm = normalizeMovement(movementData.movement); + if (moveNorm >= 0.1f && moveNorm <= 0.4f) { + moveMid = 1.0f - fabs(moveNorm - 0.25f) * 4.0f; + } + } + if (rrData.isValid) { + rrStable = rrData.regularity; + } + + float light = 0.3f * hrMid + + 0.3f * hrvMid + + 0.2f * moveMid + + 0.2f * rrStable; + + return constrain_value(light, 0.0f, 1.0f); +} + +float SleepAnalyzer::calculateAwakeScore(const HeartRateData& hrData, + const RespirationData& rrData, + const HRVEstimate& hrvData, + const BodyMovementData& movementData) { + float moveNorm = 0, hrAwakeFactor = 0.5f, rrVar = 0; + + if (movementData.isValid) { + moveNorm = normalizeMovement(movementData.movement); + } + if (hrData.isValid) { + float hrNorm = normalizeHR(hrData.bpmSmoothed); + hrAwakeFactor = (hrNorm + 1.0f) * 0.5f; + } + if (rrData.isValid) { + rrVar = constrain_value(rrData.variability / 5.0f, 0.0f, 1.0f); + } + + float x = 3.0f * (moveNorm - 0.3f) + + 2.0f * (hrAwakeFactor - 0.5f) + + 1.5f * (rrVar - 0.3f); + + return sigmoid(x); +} + +float SleepAnalyzer::calculateRemScore(const HeartRateData& hrData, + const RespirationData& rrData, + const HRVEstimate& hrvData, + const BodyMovementData& movementData) { + float hrHigh = 0, hrvLow = 0, rrUnstable = 0, moveLow = 0, rrChange = 0; + + if (hrData.isValid) { + float hrNorm = normalizeHR(hrData.bpmSmoothed); + hrHigh = constrain_value(hrNorm, 0.0f, 1.0f); + } + if (hrvData.isValid) { + float hrvNorm = normalizeHRV(hrvData.rmssd); + hrvLow = 1.0f - hrvNorm; + } + if (rrData.isValid) { + rrUnstable = constrain_value(rrData.variability / 5.0f, 0.0f, 1.0f); + if (lastRRValue > 0) { + float rrDiff = fabs(rrData.rateSmoothed - lastRRValue); + rrChange = constrain_value(rrDiff / 3.0f, 0.0f, 1.0f); + } + lastRRValue = rrData.rateSmoothed; + } + if (movementData.isValid) { + float moveNorm = normalizeMovement(movementData.movement); + moveLow = 1.0f - moveNorm; + } + + if (movementData.isValid && movementData.movement > DEEP_SLEEP_HARD_MOVEMENT_LIMIT) { + return 0.0f; + } + + float x = 2.5f * (hrHigh - 0.3f) + + 2.0f * (hrvLow - 0.3f) + + 1.5f * (rrUnstable - 0.3f) + + 2.0f * (rrChange - 0.3f) + + 2.0f * (moveLow - 0.5f); + + return sigmoid(x); +} + +bool SleepAnalyzer::tryTransitionTo(SleepState target, unsigned long confirmMs) { + if (pendingState != target) { + pendingState = target; + pendingStateTime = millis(); + return false; + } + if (millis() - pendingStateTime >= confirmMs) { + currentState = target; + stateEnterTime = millis(); + pendingState = target; + return true; + } + return false; +} + +void SleepAnalyzer::updateSleepCycle() { + if (currentState == SLEEP_DEEP_SLEEP && !cycle.inDeepPhase) { + cycle.inDeepPhase = true; + if (cycle.cycleStartTime == 0) { + cycle.cycleStartTime = millis(); + } + } + + if (currentState == SLEEP_REM_SLEEP && !cycle.inRemPhase) { + cycle.inRemPhase = true; + } + + if (currentState == SLEEP_LIGHT_SLEEP && cycle.inDeepPhase) { + cycle.inDeepPhase = false; + cycle.lastDeepEndTime = millis(); + } + + if (currentState == SLEEP_LIGHT_SLEEP && cycle.inRemPhase) { + cycle.inRemPhase = false; + cycle.lastRemEndTime = millis(); + } + + if ((currentState == SLEEP_AWAKE || currentState == SLEEP_OUT_OF_BED) && + (cycle.inDeepPhase || cycle.inRemPhase) && + cycle.cycleStartTime > 0) { + cycle.cycleCount++; + cycle.inDeepPhase = false; + cycle.inRemPhase = false; + cycle.cycleStartTime = millis(); + stats.sleepCycles = cycle.cycleCount; + Serial.printf("🔄 完成第 %d 个睡眠周期\n", cycle.cycleCount); + } +} + +void SleepAnalyzer::updateState(PresenceData& presence, + const HeartRateData& hrData, + const RespirationData& rrData, + const HRVEstimate& hrvData, + const BodyMovementData& movementData) { + unsigned long now = millis(); + + if (currentState != SLEEP_NO_PERSON && currentState != SLEEP_SESSION_END && + (now - stateEnterTime) < MIN_STATE_DWELL_MS) { + return; + } + + switch (currentState) { + case SLEEP_NO_PERSON: + if (presence.isPresent) { + noPersonTimer = 0; + if (presence.distance > 0 && presence.distance > 80) { + currentState = SLEEP_OUT_OF_BED; + } else { + currentState = SLEEP_IN_BED; + } + stateEnterTime = now; + pendingState = currentState; + Serial.printf("🔄 状态切换: 无人 → %s\n", SLEEP_STATE_NAMES[currentState]); + } + break; + + case SLEEP_IN_BED: + if (!presence.isPresent) { + noPersonTimer += 1000; + bool hrExists = hrData.isValid && hrData.bpmSmoothed > 0; + if (noPersonTimer > NO_PERSON_END_SECONDS * 1000 && !hrExists) { + currentState = SLEEP_SESSION_END; + stateEnterTime = now; + pendingState = currentState; + if (wasAsleep) { + calculateSleepScore(); + } + Serial.println("🔄 状态切换: 在床 → 会话结束"); + } else if (noPersonTimer > NO_PERSON_END_SECONDS * 1000 && hrExists) { + Serial.println("⚠️ 检测到无人但HR存在,可能遮挡,暂不结束会话"); + } else if (noPersonTimer > OUT_OF_BED_SECONDS * 1000) { + currentState = SLEEP_OUT_OF_BED; + stateEnterTime = now; + pendingState = currentState; + Serial.println("🔄 状态切换: 在床 → 离床"); + } + } else { + noPersonTimer = 0; + float rawSleepiness = calculateSleepinessScore(hrData, rrData, hrvData, movementData); + currentSleepiness = emaSmooth(rawSleepiness, currentSleepiness, EMA_ALPHA); + float movement = movementData.isValid ? movementData.movement : 100; + + if (currentSleepiness > SLEEPINESS_THRESHOLD && + movement < SLEEPINESS_MOVEMENT_THRESHOLD) { + sleepinessDuration += 1000; + if (sleepinessDuration >= SLEEPINESS_MIN_SECONDS * 1000) { + currentState = SLEEP_LIGHT_SLEEP; + stateEnterTime = now; + pendingState = currentState; + stats.sleepStartTime = now; + stats.sleepLatency = (now - stats.sessionStartTime) / 1000; + wasAsleep = true; + cycle.cycleStartTime = now; + Serial.printf("🔄 状态切换: 在床 → 浅睡 (入睡耗时:%lus)\n", stats.sleepLatency); + } + } else { + sleepinessDuration = 0; + if (movement >= MOVEMENT_HIGH_THRESHOLD) { + currentState = SLEEP_AWAKE; + stateEnterTime = now; + pendingState = currentState; + stats.sessionStartTime = now; + Serial.println("🔄 状态切换: 在床 → 清醒"); + } + } + } + break; + + case SLEEP_AWAKE: + if (!presence.isPresent) { + noPersonTimer += 1000; + bool hrExists = hrData.isValid && hrData.bpmSmoothed > 0; + if (noPersonTimer > NO_PERSON_END_SECONDS * 1000 && !hrExists) { + currentState = SLEEP_SESSION_END; + stateEnterTime = now; + pendingState = currentState; + if (wasAsleep) { + calculateSleepScore(); + } + Serial.println("🔄 状态切换: 清醒 → 会话结束"); + } else if (noPersonTimer > NO_PERSON_END_SECONDS * 1000 && hrExists) { + Serial.println("⚠️ 检测到无人但HR存在,可能遮挡,暂不结束会话"); + } else if (noPersonTimer > OUT_OF_BED_SECONDS * 1000) { + currentState = SLEEP_OUT_OF_BED; + stateEnterTime = now; + pendingState = currentState; + Serial.println("🔄 状态切换: 清醒 → 离床"); + } + } else { + noPersonTimer = 0; + float rawSleepiness = calculateSleepinessScore(hrData, rrData, hrvData, movementData); + currentSleepiness = emaSmooth(rawSleepiness, currentSleepiness, EMA_ALPHA); + float movement = movementData.isValid ? movementData.movement : 100; + + if (wasAsleep && movement >= GETTING_UP_MOVEMENT_THRESHOLD) { + gettingUpDuration += 1000; + if (gettingUpDuration >= GETTING_UP_MIN_SECONDS * 1000) { + currentState = SLEEP_GETTING_UP; + stateEnterTime = now; + pendingState = currentState; + gettingUpDuration = 0; + Serial.println("🔄 状态切换: 清醒 → 起床"); + break; + } + } else { + gettingUpDuration = 0; + } + + if (currentSleepiness > SLEEPINESS_THRESHOLD && + movement < SLEEPINESS_MOVEMENT_THRESHOLD) { + sleepinessDuration += 1000; + if (sleepinessDuration >= SLEEPINESS_MIN_SECONDS * 1000) { + currentState = SLEEP_LIGHT_SLEEP; + stateEnterTime = now; + pendingState = currentState; + stats.sleepStartTime = now; + stats.sleepLatency = (now - stats.sessionStartTime) / 1000; + wasAsleep = true; + cycle.cycleStartTime = now; + Serial.printf("🔄 状态切换: 清醒 → 浅睡 (入睡耗时:%lus)\n", stats.sleepLatency); + } + } else { + sleepinessDuration = 0; + } + } + break; + + case SLEEP_LIGHT_SLEEP: + if (!presence.isPresent) { + noPersonTimer += 1000; + if (noPersonTimer > OUT_OF_BED_SECONDS * 1000) { + currentState = SLEEP_OUT_OF_BED; + stateEnterTime = now; + pendingState = currentState; + stats.wakeCount++; + Serial.println("🔄 状态切换: 浅睡 → 离床"); + } + } else { + noPersonTimer = 0; + + if (movementData.isValid && movementData.movement > FAST_AWAKE_MOVEMENT_THRESHOLD) { + currentState = SLEEP_AWAKE; + stateEnterTime = now; + pendingState = SLEEP_AWAKE; + stats.wakeCount++; + awakeDuration = 0; + deepSleepDuration = 0; + deepStableDuration = 0; + Serial.println("🔄 状态切换: 浅睡 → 清醒 (快速触发:体动大)"); + break; + } + + float rawDeep = calculateDeepSleepScore(hrData, rrData, hrvData, movementData); + float rawLight = calculateLightSleepScore(hrData, rrData, hrvData, movementData); + float rawAwake = calculateAwakeScore(hrData, rrData, hrvData, movementData); + float rawRem = calculateRemScore(hrData, rrData, hrvData, movementData); + + currentDeepScore = emaSmooth(rawDeep, currentDeepScore, EMA_ALPHA); + currentLightScore = emaSmooth(rawLight, currentLightScore, EMA_ALPHA); + currentAwakeScore = emaSmooth(rawAwake, currentAwakeScore, EMA_ALPHA); + currentRemScore = emaSmooth(rawRem, currentRemScore, EMA_ALPHA); + + if (isBestScore(currentAwakeScore, currentDeepScore, currentLightScore, currentRemScore, CONFIDENCE_MARGIN)) { + awakeDuration += 1000; + deepSleepDuration = 0; + deepStableDuration = 0; + if (tryTransitionTo(SLEEP_AWAKE, AWAKE_SLOW_CONFIRM_SECONDS * 1000)) { + stats.wakeCount++; + awakeDuration = 0; + Serial.println("🔄 状态切换: 浅睡 → 清醒 (慢速触发)"); + } + } else if (isBestScore(currentDeepScore, currentAwakeScore, currentLightScore, currentRemScore, CONFIDENCE_MARGIN) && + currentDeepScore >= HYSTERESIS_ENTER_DEEP) { + deepSleepDuration += 1000; + if (movementData.isValid && movementData.movement < DEEP_SLEEP_HARD_MOVEMENT_LIMIT) { + deepStableDuration += 1000; + } else { + deepStableDuration = 0; + } + awakeDuration = 0; + if (deepStableDuration >= DEEP_STABLE_MIN_SECONDS * 1000 && + tryTransitionTo(SLEEP_DEEP_SLEEP, DEEP_SLEEP_CONFIRM_SECONDS * 1000)) { + deepSleepDuration = 0; + Serial.println("🔄 状态切换: 浅睡 → 深睡 (稳定≥5分钟)"); + } + } else if (isBestScore(currentRemScore, currentAwakeScore, currentDeepScore, currentLightScore, CONFIDENCE_MARGIN) && + currentRemScore >= HYSTERESIS_ENTER_REM) { + remSleepDuration += 1000; + deepSleepDuration = 0; + deepStableDuration = 0; + awakeDuration = 0; + if (tryTransitionTo(SLEEP_REM_SLEEP, REM_CONFIRM_SECONDS * 1000)) { + remSleepDuration = 0; + Serial.println("🔄 状态切换: 浅睡 → REM"); + } + } else { + deepSleepDuration = 0; + awakeDuration = 0; + deepStableDuration = 0; + pendingState = SLEEP_LIGHT_SLEEP; + } + } + break; + + case SLEEP_DEEP_SLEEP: + if (!presence.isPresent) { + noPersonTimer += 1000; + if (noPersonTimer > OUT_OF_BED_SECONDS * 1000) { + currentState = SLEEP_OUT_OF_BED; + stateEnterTime = now; + pendingState = currentState; + stats.wakeCount++; + Serial.println("🔄 状态切换: 深睡 → 离床"); + } + } else { + noPersonTimer = 0; + + if (movementData.isValid && movementData.movement > FAST_AWAKE_MOVEMENT_THRESHOLD) { + currentState = SLEEP_AWAKE; + stateEnterTime = now; + pendingState = SLEEP_AWAKE; + stats.wakeCount++; + lightSleepDuration = 0; + movementHighDuration = 0; + Serial.println("🔄 状态切换: 深睡 → 清醒 (快速触发:体动大)"); + break; + } + + if (movementData.isValid && movementData.movement > DEEP_SLEEP_HARD_MOVEMENT_LIMIT) { + lightSleepDuration += 1000; + if (tryTransitionTo(SLEEP_LIGHT_SLEEP, LIGHT_SLEEP_CONFIRM_SECONDS * 1000)) { + lightSleepDuration = 0; + Serial.println("🔄 状态切换: 深睡 → 浅睡 (体动超限)"); + } + } else { + lightSleepDuration = 0; + pendingState = SLEEP_DEEP_SLEEP; + } + + if (movementData.isValid && movementData.movement > MOVEMENT_HIGH_THRESHOLD) { + movementHighDuration += 1000; + if (tryTransitionTo(SLEEP_AWAKE, AWAKE_CONFIRM_SECONDS * 1000)) { + stats.wakeCount++; + movementHighDuration = 0; + Serial.println("🔄 状态切换: 深睡 → 清醒"); + } + } else { + movementHighDuration = 0; + } + + float rawRem = calculateRemScore(hrData, rrData, hrvData, movementData); + float rawLight = calculateLightSleepScore(hrData, rrData, hrvData, movementData); + currentRemScore = emaSmooth(rawRem, currentRemScore, EMA_ALPHA); + currentLightScore = emaSmooth(rawLight, currentLightScore, EMA_ALPHA); + + if (currentRemScore > currentLightScore && currentRemScore > HYSTERESIS_EXIT_DEEP && + currentDeepScore < HYSTERESIS_EXIT_DEEP) { + remSleepDuration += 1000; + if (tryTransitionTo(SLEEP_REM_SLEEP, REM_CONFIRM_SECONDS * 1000)) { + remSleepDuration = 0; + Serial.println("🔄 状态切换: 深睡 → REM"); + } + } else { + remSleepDuration = 0; + } + } + break; + + case SLEEP_REM_SLEEP: + if (!presence.isPresent) { + noPersonTimer += 1000; + if (noPersonTimer > OUT_OF_BED_SECONDS * 1000) { + currentState = SLEEP_OUT_OF_BED; + stateEnterTime = now; + pendingState = currentState; + stats.wakeCount++; + Serial.println("🔄 状态切换: REM → 离床"); + } + } else { + noPersonTimer = 0; + + if (movementData.isValid && movementData.movement > FAST_AWAKE_MOVEMENT_THRESHOLD) { + currentState = SLEEP_AWAKE; + stateEnterTime = now; + pendingState = SLEEP_AWAKE; + stats.wakeCount++; + Serial.println("🔄 状态切换: REM → 清醒 (快速触发:体动大)"); + break; + } + + float rawRem = calculateRemScore(hrData, rrData, hrvData, movementData); + float rawLight = calculateLightSleepScore(hrData, rrData, hrvData, movementData); + float rawDeep = calculateDeepSleepScore(hrData, rrData, hrvData, movementData); + float rawAwake = calculateAwakeScore(hrData, rrData, hrvData, movementData); + + currentRemScore = emaSmooth(rawRem, currentRemScore, EMA_ALPHA); + currentLightScore = emaSmooth(rawLight, currentLightScore, EMA_ALPHA); + currentDeepScore = emaSmooth(rawDeep, currentDeepScore, EMA_ALPHA); + currentAwakeScore = emaSmooth(rawAwake, currentAwakeScore, EMA_ALPHA); + + if (isBestScore(currentAwakeScore, currentRemScore, currentLightScore, currentDeepScore, CONFIDENCE_MARGIN)) { + awakeDuration += 1000; + if (tryTransitionTo(SLEEP_AWAKE, AWAKE_SLOW_CONFIRM_SECONDS * 1000)) { + stats.wakeCount++; + awakeDuration = 0; + Serial.println("🔄 状态切换: REM → 清醒"); + } + } else if (isBestScore(currentLightScore, currentRemScore, currentAwakeScore, currentDeepScore, CONFIDENCE_MARGIN) && + currentRemScore < HYSTERESIS_EXIT_REM) { + if (tryTransitionTo(SLEEP_LIGHT_SLEEP, LIGHT_SLEEP_CONFIRM_SECONDS * 1000)) { + Serial.println("🔄 状态切换: REM → 浅睡"); + } + } else if (isBestScore(currentDeepScore, currentRemScore, currentLightScore, currentAwakeScore, CONFIDENCE_MARGIN) && + currentDeepScore >= HYSTERESIS_ENTER_DEEP) { + deepStableDuration += 1000; + if (deepStableDuration >= DEEP_STABLE_MIN_SECONDS * 1000 && + tryTransitionTo(SLEEP_DEEP_SLEEP, DEEP_SLEEP_CONFIRM_SECONDS * 1000)) { + deepStableDuration = 0; + Serial.println("🔄 状态切换: REM → 深睡 (稳定≥5分钟)"); + } + } else { + awakeDuration = 0; + deepStableDuration = 0; + pendingState = SLEEP_REM_SLEEP; + } + } + break; + + case SLEEP_OUT_OF_BED: + if (presence.isPresent) { + noPersonTimer = 0; + currentState = SLEEP_IN_BED; + stateEnterTime = now; + pendingState = currentState; + Serial.println("🔄 状态切换: 离床 → 在床"); + } else { + noPersonTimer += 1000; + bool hrExists = hrData.isValid && hrData.bpmSmoothed > 0; + if (noPersonTimer > NO_PERSON_END_SECONDS * 1000 && !hrExists) { + currentState = SLEEP_SESSION_END; + stateEnterTime = now; + pendingState = currentState; + if (wasAsleep) { + calculateSleepScore(); + } + Serial.println("🔄 状态切换: 离床 → 会话结束"); + } else if (noPersonTimer > NO_PERSON_END_SECONDS * 1000 && hrExists) { + Serial.println("⚠️ 检测到无人但HR存在,可能遮挡,暂不结束会话"); + } + } + break; + + case SLEEP_GETTING_UP: + if (!presence.isPresent) { + noPersonTimer += 1000; + bool hrExists = hrData.isValid && hrData.bpmSmoothed > 0; + if (noPersonTimer > NO_PERSON_END_SECONDS * 1000 && !hrExists) { + currentState = SLEEP_SESSION_END; + stateEnterTime = now; + pendingState = currentState; + if (wasAsleep) { + calculateSleepScore(); + } + Serial.println("🔄 状态切换: 起床 → 会话结束"); + } + } else { + float movement = movementData.isValid ? movementData.movement : 0; + if (movement < GETTING_UP_MOVEMENT_THRESHOLD) { + gettingUpDuration += 1000; + if (gettingUpDuration >= 60000) { + currentState = SLEEP_AWAKE; + stateEnterTime = now; + pendingState = currentState; + gettingUpDuration = 0; + Serial.println("🔄 状态切换: 起床 → 清醒 (重新躺下)"); + } + } else { + gettingUpDuration = 0; + } + } + break; + + case SLEEP_SESSION_END: + if (presence.isPresent) { + currentState = SLEEP_IN_BED; + stateEnterTime = now; + pendingState = currentState; + noPersonTimer = 0; + Serial.println("🔄 状态切换: 会话结束 → 在床(新会话)"); + } + break; + } +} + +void SleepAnalyzer::updateStatistics(unsigned long dt) { + switch (currentState) { + case SLEEP_LIGHT_SLEEP: + stats.lightSleepTime += dt; + stats.totalSleepTime += dt; + break; + case SLEEP_DEEP_SLEEP: + stats.deepSleepTime += dt; + stats.totalSleepTime += dt; + break; + case SLEEP_REM_SLEEP: + stats.remSleepTime += dt; + stats.totalSleepTime += dt; + break; + case SLEEP_AWAKE: + stats.awakeTime += dt; + break; + case SLEEP_OUT_OF_BED: + stats.outOfBedTime += dt; + break; + default: + break; + } +} + +void SleepAnalyzer::calculateSleepScore() { + float totalHours = stats.totalSleepTime / 3600000.0f; + if (totalHours >= 7.0f && totalHours <= 9.0f) { + score.durationScore = 18.0f; + } else if (totalHours >= 6.0f && totalHours < 7.0f) { + score.durationScore = 13.0f; + } else if (totalHours > 9.0f && totalHours <= 10.0f) { + score.durationScore = 13.0f; + } else { + score.durationScore = 5.0f; + } + + float deepRatio = (stats.totalSleepTime > 0) ? + (float)stats.deepSleepTime / stats.totalSleepTime : 0; + if (deepRatio > 0.2f) { + score.deepScore = 14.0f; + } else if (deepRatio > 0.15f) { + score.deepScore = 11.0f; + } else if (deepRatio > 0.1f) { + score.deepScore = 7.0f; + } else { + score.deepScore = 3.0f; + } + + if (stats.wakeCount <= 1) { + score.continuityScore = 11.0f; + } else if (stats.wakeCount <= 3) { + score.continuityScore = 7.0f; + } else if (stats.wakeCount <= 5) { + score.continuityScore = 4.0f; + } else { + score.continuityScore = 2.0f; + } + + score.physiologyScore = 7.0f; + + float latencyMin = stats.sleepLatency / 60.0f; + if (latencyMin < 20.0f) { + score.latencyScore = 8.0f; + } else if (latencyMin < 30.0f) { + score.latencyScore = 6.0f; + } else if (latencyMin < 45.0f) { + score.latencyScore = 3.0f; + } else { + score.latencyScore = 1.0f; + } + + float sleepEfficiency = 0; + if (stats.totalSleepTime + stats.awakeTime > 0) { + sleepEfficiency = (float)stats.totalSleepTime / (stats.totalSleepTime + stats.awakeTime); + } + if (sleepEfficiency > 0.9f) { + score.efficiencyScore = 14.0f; + } else if (sleepEfficiency > 0.8f) { + score.efficiencyScore = 10.0f; + } else if (sleepEfficiency > 0.7f) { + score.efficiencyScore = 6.0f; + } else { + score.efficiencyScore = 3.0f; + } + + float remRatio = (stats.totalSleepTime > 0) ? + (float)stats.remSleepTime / stats.totalSleepTime : 0; + float cycleScoreVal = 0; + if (stats.sleepCycles >= 4) { + cycleScoreVal = 20.0f; + } else if (stats.sleepCycles >= 3) { + cycleScoreVal = 15.0f; + } else if (stats.sleepCycles >= 2) { + cycleScoreVal = 10.0f; + } else if (stats.sleepCycles >= 1) { + cycleScoreVal = 6.0f; + } else { + cycleScoreVal = 2.0f; + } + if (remRatio >= 0.2f && remRatio <= 0.25f) { + cycleScoreVal += 8.0f; + } else if (remRatio >= 0.15f) { + cycleScoreVal += 5.0f; + } else if (remRatio > 0) { + cycleScoreVal += 2.0f; + } + score.cycleScore = constrain_value(cycleScoreVal, 0.0f, 28.0f); + + float rawTotal = score.durationScore + score.deepScore + + score.continuityScore + score.physiologyScore + + score.latencyScore + score.efficiencyScore + + score.cycleScore; + score.totalScore = constrain_value(rawTotal / 100.0f * 100.0f, 0.0f, 100.0f); + + Serial.println("━━━━━━━━━━ 睡眠评分 ━━━━━━━━━━"); + Serial.printf(" 时长评分: %.0f/18\n", score.durationScore); + Serial.printf(" 深睡评分: %.0f/14\n", score.deepScore); + Serial.printf(" 连续性评分: %.0f/11\n", score.continuityScore); + Serial.printf(" 生理质量评分: %.0f/7\n", score.physiologyScore); + Serial.printf(" 入睡速度评分: %.0f/8\n", score.latencyScore); + Serial.printf(" 睡眠效率评分: %.0f/14 (效率:%.0f%%)\n", score.efficiencyScore, sleepEfficiency * 100); + Serial.printf(" 周期评分: %.0f/28 (周期数:%d, REM占比:%.0f%%)\n", + score.cycleScore, stats.sleepCycles, remRatio * 100); + Serial.printf(" 总分: %.0f/100\n", score.totalScore); + Serial.println("━━━━━━━━━━━━━━━━━━━━━━━━━━━━"); +} + +void SleepAnalyzer::update(const HeartRateData& hrData, + const RespirationData& rrData, + const HRVEstimate& hrvData, + const BodyMovementData& movementData) { + calibrateBaseline(hrData, rrData, movementData); + + PresenceData presence = evaluatePresence(); + + updateState(presence, hrData, rrData, hrvData, movementData); + + updateSleepCycle(); + + updateStatistics(1000); +} + +void SleepAnalyzer::printState() { + unsigned long stateDuration = (millis() - stateEnterTime) / 1000; + Serial.printf("🛏️ 睡眠状态: %s | 持续: %02lu:%02lu:%02lu | 困倦度: %.2f | 周期: %d\n", + SLEEP_STATE_NAMES[currentState], + stateDuration / 3600, (stateDuration % 3600) / 60, stateDuration % 60, + currentSleepiness, cycle.cycleCount); +} + +void SleepAnalyzer::printStatistics() { + Serial.println("━━━━━━━━━━ 睡眠统计 ━━━━━━━━━━"); + Serial.printf(" 总睡眠: %02lu:%02lu:%02lu\n", + stats.totalSleepTime / 3600000, + (stats.totalSleepTime % 3600000) / 60000, + (stats.totalSleepTime % 60000) / 1000); + Serial.printf(" 深睡: %02lu:%02lu:%02lu\n", + stats.deepSleepTime / 3600000, + (stats.deepSleepTime % 3600000) / 60000, + (stats.deepSleepTime % 60000) / 1000); + Serial.printf(" 浅睡: %02lu:%02lu:%02lu\n", + stats.lightSleepTime / 3600000, + (stats.lightSleepTime % 3600000) / 60000, + (stats.lightSleepTime % 60000) / 1000); + Serial.printf(" REM: %02lu:%02lu:%02lu\n", + stats.remSleepTime / 3600000, + (stats.remSleepTime % 3600000) / 60000, + (stats.remSleepTime % 60000) / 1000); + Serial.printf(" 清醒: %02lu:%02lu:%02lu\n", + stats.awakeTime / 3600000, + (stats.awakeTime % 3600000) / 60000, + (stats.awakeTime % 60000) / 1000); + Serial.printf(" 离床: %02lu:%02lu:%02lu\n", + stats.outOfBedTime / 3600000, + (stats.outOfBedTime % 3600000) / 60000, + (stats.outOfBedTime % 60000) / 1000); + Serial.printf(" 醒来次数: %d\n", stats.wakeCount); + Serial.printf(" 入睡耗时: %lus\n", stats.sleepLatency); + Serial.printf(" 睡眠周期: %d\n", stats.sleepCycles); + float sleepEfficiency = 0; + if (stats.totalSleepTime + stats.awakeTime > 0) { + sleepEfficiency = (float)stats.totalSleepTime / (stats.totalSleepTime + stats.awakeTime); + } + Serial.printf(" 睡眠效率: %.0f%%\n", sleepEfficiency * 100); + Serial.println("━━━━━━━━━━━━━━━━━━━━━━━━━━━━"); +} diff --git a/src/sleep_analyzer.h b/src/sleep_analyzer.h new file mode 100644 index 0000000..a7257a2 --- /dev/null +++ b/src/sleep_analyzer.h @@ -0,0 +1,223 @@ +#ifndef SLEEP_ANALYZER_H +#define SLEEP_ANALYZER_H + +#include +#include "config.h" +#include "data_processor.h" +#include "emotion_analyzer_simple.h" +#include "radar_manager.h" + +enum SleepState { + SLEEP_NO_PERSON = 0, + SLEEP_IN_BED, + SLEEP_AWAKE, + SLEEP_LIGHT_SLEEP, + SLEEP_DEEP_SLEEP, + SLEEP_REM_SLEEP, + SLEEP_OUT_OF_BED, + SLEEP_GETTING_UP, + SLEEP_SESSION_END +}; + +static const char* SLEEP_STATE_NAMES[] = { + "无人", + "在床", + "清醒", + "浅睡", + "深睡", + "REM", + "离床", + "起床", + "会话结束" +}; + +struct PresenceData { + bool isPresent; + float distance; + float confidence; + float motionEnergy; +}; + +struct SleepCycle { + int cycleCount; + unsigned long cycleStartTime; + bool inDeepPhase; + bool inRemPhase; + unsigned long lastDeepEndTime; + unsigned long lastRemEndTime; +}; + +struct SleepStatistics { + unsigned long totalSleepTime; + unsigned long deepSleepTime; + unsigned long lightSleepTime; + unsigned long remSleepTime; + unsigned long awakeTime; + unsigned long outOfBedTime; + unsigned long sleepLatency; + int wakeCount; + int sleepCycles; + unsigned long sessionStartTime; + unsigned long sleepStartTime; + unsigned long lastWakeTime; +}; + +struct SleepScore { + float durationScore; + float deepScore; + float continuityScore; + float physiologyScore; + float latencyScore; + float efficiencyScore; + float cycleScore; + float totalScore; +}; + +struct TrainingData { + float hr; + float rr; + float hrv; + float movement; + int label; +}; + +class SleepAnalyzer { +private: + static const float EMA_ALPHA; + static const float CONFIDENCE_MARGIN; + static const float BASELINE_BETA; + static const int MIN_STATE_DWELL_MS = 10000; + static const float HYSTERESIS_ENTER_DEEP; + static const float HYSTERESIS_EXIT_DEEP; + static const float HYSTERESIS_ENTER_REM; + static const float HYSTERESIS_EXIT_REM; + + SleepState currentState; + SleepState pendingState; + SleepStatistics stats; + SleepScore score; + SleepCycle cycle; + + unsigned long stateEnterTime; + unsigned long pendingStateTime; + unsigned long noPersonTimer; + unsigned long outOfBedTimer; + unsigned long sleepinessDuration; + unsigned long awakeDuration; + unsigned long deepSleepDuration; + unsigned long lightSleepDuration; + unsigned long remSleepDuration; + unsigned long movementHighDuration; + unsigned long gettingUpDuration; + unsigned long deepStableDuration; + + float baselineHR; + float baselineRR; + bool baselineCalibrated; + int baselineSampleCount; + float baselineHRSum; + float baselineRRSum; + float lastBaselineHR; + float lastBaselineRR; + float hrStabilitySum; + float rrStabilitySum; + int stabilitySampleCount; + + float lastRRValue; + + static const int DEEP_SLEEP_CONFIRM_SECONDS = 60; + static const int LIGHT_SLEEP_CONFIRM_SECONDS = 30; + static const int AWAKE_CONFIRM_SECONDS = 15; + static const int AWAKE_SLOW_CONFIRM_SECONDS = 30; + static const int NO_PERSON_END_SECONDS = 600; + static const int OUT_OF_BED_SECONDS = 30; + static const int SLEEPINESS_MIN_SECONDS = 300; + static const int SLEEPINESS_MAX_SECONDS = 600; + static const int MOVEMENT_HIGH_THRESHOLD = 50; + static const int DEEP_SLEEP_MOVEMENT_THRESHOLD = 10; + static const int DEEP_SLEEP_HARD_MOVEMENT_LIMIT = 15; + static const int FAST_AWAKE_MOVEMENT_THRESHOLD = 60; + static const int SLEEPINESS_MOVEMENT_THRESHOLD = 10; + static const int DEEP_STABLE_MIN_SECONDS = 300; + static const int REM_CONFIRM_SECONDS = 60; + static const int GETTING_UP_MIN_SECONDS = 300; + static const int GETTING_UP_MOVEMENT_THRESHOLD = 30; + static const float SLEEPINESS_THRESHOLD; + static const float BASELINE_MOVEMENT_THRESHOLD; + static const float BASELINE_HR_STABILITY_THRESHOLD; + static const float BASELINE_RR_STABILITY_THRESHOLD; + + float currentSleepiness; + float currentDeepScore; + float currentLightScore; + float currentAwakeScore; + float currentRemScore; + + bool wasAsleep; + + PresenceData evaluatePresence(); + float sigmoid(float x); + float emaSmooth(float input, float last, float alpha); + float updateBaseline(float current, float input, float beta); + float calculateSleepinessScore(const HeartRateData& hrData, + const RespirationData& rrData, + const HRVEstimate& hrvData, + const BodyMovementData& movementData); + float calculateDeepSleepScore(const HeartRateData& hrData, + const RespirationData& rrData, + const HRVEstimate& hrvData, + const BodyMovementData& movementData); + float calculateLightSleepScore(const HeartRateData& hrData, + const RespirationData& rrData, + const HRVEstimate& hrvData, + const BodyMovementData& movementData); + float calculateAwakeScore(const HeartRateData& hrData, + const RespirationData& rrData, + const HRVEstimate& hrvData, + const BodyMovementData& movementData); + float calculateRemScore(const HeartRateData& hrData, + const RespirationData& rrData, + const HRVEstimate& hrvData, + const BodyMovementData& movementData); + + void updateState(PresenceData& presence, + const HeartRateData& hrData, + const RespirationData& rrData, + const HRVEstimate& hrvData, + const BodyMovementData& movementData); + void updateStatistics(unsigned long dt); + void updateSleepCycle(); + void calculateSleepScore(); + void calibrateBaseline(const HeartRateData& hrData, + const RespirationData& rrData, + const BodyMovementData& movementData); + + float normalizeHR(float hr); + float normalizeRR(float rr); + float normalizeHRV(float hrv); + float normalizeMovement(float movement); + + bool tryTransitionTo(SleepState target, unsigned long confirmMs); + bool isBestScore(float score, float s2, float s3, float s4, float margin); + +public: + SleepAnalyzer(); + ~SleepAnalyzer(); + + void update(const HeartRateData& hrData, + const RespirationData& rrData, + const HRVEstimate& hrvData, + const BodyMovementData& movementData); + + SleepState getCurrentState() const { return currentState; } + SleepStatistics getStatistics() const { return stats; } + SleepScore getScore() const { return score; } + SleepCycle getCycle() const { return cycle; } + float getSleepiness() const { return currentSleepiness; } + + void reset(); + void printState(); + void printStatistics(); +}; + +#endif diff --git a/src/tasks_manager.cpp b/src/tasks_manager.cpp new file mode 100644 index 0000000..5805ba6 --- /dev/null +++ b/src/tasks_manager.cpp @@ -0,0 +1,650 @@ +#include "tasks_manager.h" +#include "wifi_manager.h" +#include "radar_manager.h" +#include "data_processor.h" +#include "emotion_analyzer_simple.h" +#include +#include + +NetworkStatus currentNetworkStatus = NET_INITIAL; +unsigned long lastBlinkTime = 0; +bool ledState = false; +int breatheValue = 0; +bool breatheIncreasing = true; +uint8_t WiFi_Connect_First_bit = 1; +uint64_t device_sn = 0; + +PhysioDataProcessor* physioProcessor; +SimpleEmotionAnalyzer* emotionAnalyzer; + +bool clearConfigRequested = false; +bool forceLedOff = false; + +/** + * @brief 加载设备SN + * 从Flash中读取保存的设备SN(支持64位雪花算法ID) + */ +void loadDeviceSN() { + device_sn = preferences.getULong64("deviceSn", 0); + Serial.printf("从Flash加载设备SN: %llu\n", device_sn); +} + +/** + * @brief 保存设备SN + * 将设备SN保存到Flash中(支持64位雪花算法ID) + */ +void saveDeviceId() { + preferences.putULong64("deviceSn", device_sn); + Serial.printf("设备SN已保存到Flash: %llu\n", device_sn); +} + +/** + * @brief 计算CRC32哈希值 + * @param data 输入数据指针 + * @param length 数据长度 + * @return CRC32哈希值 + */ +uint32_t calculateCRC32(const uint8_t* data, size_t length) { + uint32_t crc = 0xFFFFFFFF; + for (size_t i = 0; i < length; i++) { + crc ^= data[i]; + for (int j = 0; j < 8; j++) { + crc = (crc >> 1) ^ (0xEDB88320 & -(crc & 1)); + } + } + return ~crc; +} + +/** + * @brief 生成设备唯一标识哈希 + * 将 device_sn + MAC 地址拼接后计算 CRC32 哈希 + * @return 4字节哈希值 + */ +uint32_t generateDeviceHash() { + uint8_t mac[6]; + esp_read_mac(mac, ESP_MAC_WIFI_STA); + + char macHex[13]; + snprintf(macHex, sizeof(macHex), "%02X%02X%02X%02X%02X%02X", + mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]); + + char snStr[21]; + snprintf(snStr, sizeof(snStr), "%llu", device_sn); + + String hashInput = String("SN") + String(snStr) + String("|") + String(macHex); + + uint32_t hash = calculateCRC32((const uint8_t*)hashInput.c_str(), hashInput.length()); + + Serial.printf("🔐 [HASH] 输入: %s, 哈希: 0x%08X\n", hashInput.c_str(), hash); + + return hash; +} + +/** + * @brief 构建BLE厂商数据 + * 构造BLE广播厂商数据,包含FF FF标识和SN哈希值 + * @return 9字节厂商数据字符串 + */ +std::string buildBLEManufacturerData() { + std::string manufacturerData; + manufacturerData.reserve(9); + + manufacturerData.push_back(static_cast(0xFF)); + manufacturerData.push_back(static_cast(0xFF)); + manufacturerData.push_back('R'); + manufacturerData.push_back(0x01); + manufacturerData.push_back(0x00); + + uint32_t snHash = generateDeviceHash(); + manufacturerData.push_back(static_cast((snHash >> 24) & 0xFF)); + manufacturerData.push_back(static_cast((snHash >> 16) & 0xFF)); + manufacturerData.push_back(static_cast((snHash >> 8) & 0xFF)); + manufacturerData.push_back(static_cast(snHash & 0xFF)); + + return manufacturerData; +} + +/** + * @brief 刷新BLE广播数据 + * 更新BLE广播的厂商数据和设备名称,使用SN码作为设备名 + */ +void refreshBLEAdvertisingData() { + BLEAdvertising *pAdvertising = BLEDevice::getAdvertising(); + if (pAdvertising == nullptr) { + Serial.println("⚠️ [BLE] 广播对象为空,无法刷新广播数据"); + return; + } + + char snName[32]; + if (device_sn > 0) { + snprintf(snName, sizeof(snName), "Radar_%llu", device_sn); + } else { + String macAddr = getDeviceMacAddress(); + macAddr.replace(":", ""); + snprintf(snName, sizeof(snName), "Radar_%s", macAddr.c_str()); + } + + BLEAdvertisementData advertisementData; + advertisementData.setFlags(0x06); + advertisementData.setCompleteServices(BLEUUID(SERVICE_UUID)); + advertisementData.setManufacturerData(buildBLEManufacturerData()); + + BLEAdvertisementData scanResponseData; + scanResponseData.setName(snName); + + pAdvertising->setAdvertisementData(advertisementData); + pAdvertising->setScanResponseData(scanResponseData); + pAdvertising->setScanResponse(true); + pAdvertising->setMinPreferred(0x06); + pAdvertising->setMinPreferred(0x12); + + Serial.printf("📡 [BLE] 已刷新广播 ManufacturerData, device_sn=%llu\n", device_sn); +} + +/** + * @brief 获取设备MAC地址 + * 读取WiFi STA接口的MAC地址并格式化为字符串 + * @return MAC地址字符串,格式为 XX:XX:XX:XX:XX:XX + */ +String getDeviceMacAddress() { + uint8_t mac[6]; + esp_read_mac(mac, ESP_MAC_WIFI_STA); + char macStr[18]; + snprintf(macStr, sizeof(macStr), "%02X:%02X:%02X:%02X:%02X:%02X", + mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]); + return String(macStr); +} + +/** + * @brief 设置网络状态 + * 更新当前网络状态,并重置呼吸灯参数 + * @param status 网络状态 + */ +void setNetworkStatus(NetworkStatus status) { + currentNetworkStatus = status; + + if (status == NET_CONNECTED) { + breatheValue = BREATHE_MIN; + breatheIncreasing = true; + } +} + +/** + * @brief 清除存储的配置 + * 清除Flash中保存的WiFi配置和设备ID,重置WiFi连接状态 + */ +void clearStoredConfig() { + Serial.println("🧹 开始清除存储的配置..."); + + uint16_t oldDeviceId = preferences.getUShort("deviceId", 0); + + preferences.remove("deviceId"); + preferences.remove("wifi_first"); + + wifiManager.clearAllConfigs(); + + Serial.println("✅ 配置已清除完成"); + Serial.printf("🗑️ 被清除的设备ID: %u\n", oldDeviceId); + + WiFi_Connect_First_bit = 1; + + WiFi.disconnect(true); + setNetworkStatus(NET_DISCONNECTED); + + Serial.println("🔄 已清除Flash与内存中的配置,请重新配置WiFi和设备ID"); + + if (deviceConnected) { + sendStatusToBLE(); + } +} + +/** + * @brief BOOT按钮监控任务 + * 监控BOOT按钮按下事件,长按3秒清除存储的配置 + * @param parameter 任务参数(未使用) + */ +void bootButtonMonitorTask(void *parameter) { + Serial.println("🔍 启动BOOT按钮监控任务..."); + + pinMode(CONFIG_CLEAR_PIN, OUTPUT); + digitalWrite(CONFIG_CLEAR_PIN, LOW); + + unsigned long buttonPressStartTime = 0; + bool buttonPressed = false; + + while (1) { + int buttonState = digitalRead(BOOT_BUTTON_PIN); + + if (buttonState == LOW && !buttonPressed) { + buttonPressed = true; + buttonPressStartTime = millis(); + Serial.println("⚠️ 检测到BOOT按钮按下,长按3秒将清除配置"); + + digitalWrite(CONFIG_CLEAR_PIN, HIGH); + } + else if (buttonState == HIGH && buttonPressed) { + if (!clearConfigRequested) { + digitalWrite(CONFIG_CLEAR_PIN, LOW); + Serial.println("❌ 按钮释放,取消清除操作"); + } + buttonPressed = false; + } + + if (buttonPressed && (millis() - buttonPressStartTime >= CLEAR_CONFIG_DURATION)) { + if (!clearConfigRequested) { + clearConfigRequested = true; + forceLedOff = true; + + clearStoredConfig(); + + Serial.println("🔄 系统即将重启..."); + + vTaskDelay(1000 / portTICK_PERIOD_MS); + digitalWrite(CONFIG_CLEAR_PIN, LOW); + ledcWrite(0, 0); + ESP.restart(); + } + } + + vTaskDelay(50 / portTICK_PERIOD_MS); + esp_task_wdt_reset(); + } +} + +/** + * @brief 睡眠分析任务 + * 每秒更新生理数据、运行睡眠状态机、输出睡眠状态到串口 + * @param parameter 任务参数(未使用) + */ +void sleepAnalysisTask(void *parameter) { + SleepAnalyzer* sleepAnalyzer = new SleepAnalyzer(); + + static unsigned long lastSleepAnalysisTime = 0; + const unsigned long SLEEP_ANALYSIS_INTERVAL = 1000; + static unsigned long lastStatsPrintTime = 0; + const unsigned long STATS_PRINT_INTERVAL = 30000; + + while (1) { + unsigned long currentTime = millis(); + + if (currentTime - lastSleepAnalysisTime >= SLEEP_ANALYSIS_INTERVAL) { + lastSleepAnalysisTime = currentTime; + + if (sensorData.heart_valid || sensorData.breath_valid) { + float hr = sensorData.heart_valid ? sensorData.heart_rate : 0; + float rr = sensorData.breath_valid ? sensorData.breath_rate : 0; + + if (hr > 0 || rr > 0) { + physioProcessor->update(hr, rr, + sensorData.heart_valid ? 80 : 0, + sensorData.breath_valid ? 80 : 0); + + HeartRateData hrData = physioProcessor->getHeartRateData(); + RespirationData rrData = physioProcessor->getRespirationData(); + HRVEstimate hrvData = physioProcessor->getHRVEstimate(); + + BodyMovementData movementData; + memset(&movementData, 0, sizeof(BodyMovementData)); + movementData.movement = sensorData.body_movement; + movementData.movementSmoothed = sensorData.body_movement; + movementData.movementMean = sensorData.body_movement; + movementData.activityLevel = sensorData.body_movement / 100.0f; + movementData.isValid = (sensorData.body_movement >= 0 && sensorData.body_movement <= 100); + movementData.timestamp = currentTime; + + sleepAnalyzer->update(hrData, rrData, hrvData, movementData); + + sleepAnalyzer->printState(); + + if (currentTime - lastStatsPrintTime >= STATS_PRINT_INTERVAL) { + lastStatsPrintTime = currentTime; + sleepAnalyzer->printStatistics(); + } + } + } + } + + vTaskDelay(50 / portTICK_PERIOD_MS); + esp_task_wdt_reset(); + } +} + +/** + * @brief LED控制任务 + * 根据网络状态控制LED显示:断开时慢闪、连接中快闪、已连接时呼吸灯效果 + * @param parameter 任务参数(未使用) + */ +void ledControlTask(void *parameter) { + Serial.println("💡 启动LED控制任务..."); + + pinMode(NETWORK_LED_PIN, OUTPUT); + digitalWrite(NETWORK_LED_PIN, LOW); + ledcSetup(0, 5000, 8); + ledcAttachPin(NETWORK_LED_PIN, 0); + + while (1) { + if (forceLedOff) { + ledcWrite(0, 0); + vTaskDelay(10 / portTICK_PERIOD_MS); + continue; + } + + switch (currentNetworkStatus) { + case NET_INITIAL: + case NET_DISCONNECTED: + if (millis() - lastBlinkTime >= SLOW_BLINK_INTERVAL) { + ledState = !ledState; + if(ledState) { + ledcWrite(0, 255); + } else { + ledcWrite(0, 0); + } + lastBlinkTime = millis(); + } + break; + + case NET_CONNECTING: + if (millis() - lastBlinkTime >= FAST_BLINK_INTERVAL) { + ledState = !ledState; + if(ledState) { + ledcWrite(0, 255); + } else { + ledcWrite(0, 0); + } + lastBlinkTime = millis(); + } + break; + + case NET_CONNECTED: + if (millis() - lastBlinkTime >= BREATHE_INTERVAL) { + ledcWrite(0, breatheValue); + + if (breatheIncreasing) { + breatheValue += BREATHE_STEP; + if (breatheValue >= BREATHE_MAX) { + breatheValue = BREATHE_MAX; + breatheIncreasing = false; + } + } else { + breatheValue -= BREATHE_STEP; + if (breatheValue <= BREATHE_MIN) { + breatheValue = BREATHE_MIN; + breatheIncreasing = true; + } + } + lastBlinkTime = millis(); + } + break; + } + + vTaskDelay(10 / portTICK_PERIOD_MS); + } +} + +void WiFiEvent(WiFiEvent_t event) { + switch (event) { + case ARDUINO_EVENT_WIFI_STA_START: + setNetworkStatus(NET_INITIAL); + break; + + case ARDUINO_EVENT_WIFI_STA_CONNECTED: + setNetworkStatus(NET_CONNECTING); + break; + + case ARDUINO_EVENT_WIFI_STA_GOT_IP: + setNetworkStatus(NET_CONNECTED); + break; + + case ARDUINO_EVENT_WIFI_STA_DISCONNECTED: + setNetworkStatus(NET_DISCONNECTED); + break; + + case ARDUINO_EVENT_WIFI_STA_STOP: + setNetworkStatus(NET_DISCONNECTED); + break; + } +} + +/** + * @brief WiFi监控任务 + * 初始化WiFi并定期打印连接状态和信号强度 + * @param parameter 任务参数(未使用) + */ +void wifiMonitorTask(void *parameter) { + Serial.println("📡 WiFi监控任务启动"); + + wifiManager.begin(); + + if (wifiManager.getSavedNetworkCount() > 0) { + Serial.printf("💾 检测到 %d 个已保存的WiFi配置,尝试连接...\n", wifiManager.getSavedNetworkCount()); + if (wifiManager.initializeWiFi()) { + Serial.println("✅ WiFi连接成功!"); + } else { + Serial.println("❌ WiFi连接失败,请通过BLE重新配置"); + } + } else { + Serial.println("⚠️ 未检测到WiFi配置,请通过BLE进行网络配置"); + } + + size_t wifi_first_len = preferences.getBytes("wifi_first", &WiFi_Connect_First_bit, sizeof(WiFi_Connect_First_bit)); + if (wifi_first_len == sizeof(WiFi_Connect_First_bit)) { + Serial.printf("从Flash读取 WiFi_Connect_First_bit: %u\n", WiFi_Connect_First_bit); + } else { + Serial.println("Flash中无 wifi_first 条目,保留内存中原始值"); + } + + if(WiFi_Connect_First_bit == 0) + { + unsigned long wifiWaitStart = millis(); + unsigned long lastWifiWaitPrint = 0; + const unsigned long WIFI_WAIT_TIMEOUT = 15000; + while (WiFi.status() != WL_CONNECTED && (millis() - wifiWaitStart) < WIFI_WAIT_TIMEOUT) { + if (millis() - lastWifiWaitPrint >= 1000) { + lastWifiWaitPrint = millis(); + } + yield(); + vTaskDelay(10 / portTICK_PERIOD_MS); + } + } + + Serial.println("📡 WiFi初始化完成,开始监控..."); + + while(1) { + static unsigned long lastPrint = 0; + if (millis() - lastPrint > 30000) { + Serial.printf("📡 WiFi状态: %d, RSSI: %d dBm\n", + WiFi.status(), WiFi.RSSI()); + lastPrint = millis(); + } + vTaskDelay(5000 / portTICK_PERIOD_MS); + } +} + +/** + * @brief BLE配置处理任务 + * 处理BLE配置命令,监控设备连接状态并管理广播 + * @param parameter 任务参数(未使用) + */ +void bleConfigTask(void *parameter) { + Serial.println("📡 BLE配置处理任务启动"); + + char snName[32]; + if (device_sn > 0) { + snprintf(snName, sizeof(snName), "Radar_%llu", device_sn); + } else { + String macAddr = getDeviceMacAddress(); + macAddr.replace(":", ""); + snprintf(snName, sizeof(snName), "Radar_%s", macAddr.c_str()); + } + BLEDevice::init(snName); + 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(); + refreshBLEAdvertisingData(); + BLEDevice::startAdvertising(); + + Serial.println(String("✅ BLE已启动,设备名称: ") + snName); + + while(1) { + processBLEConfig(); + + if (!deviceConnected && oldDeviceConnected) { + vTaskDelay(500 / portTICK_PERIOD_MS); + pServer->startAdvertising(); + Serial.println("开始BLE广播"); + oldDeviceConnected = deviceConnected; + } + if (deviceConnected && !oldDeviceConnected) { + oldDeviceConnected = deviceConnected; + } + + vTaskDelay(10 / portTICK_PERIOD_MS); + } +} + +/** + * @brief 雷达命令发送任务 + * 每2秒轮流向雷达模组发送11条不同命令,查询心率、呼吸率、睡眠状态等数据 + * @param parameter 任务参数(未使用) + */ +void radarCmdTask(void *parameter) { + Serial.println("📡 雷达命令发送任务启动"); + initR60ABD1(); + + static const uint8_t radar_cmds[][3] = { + {0x84, 0x81, 0x0F}, // 0x81: 查询心率/呼吸率 + {0x84, 0x8D, 0x0F}, // 0x8D: 查询睡眠状态 + {0x84, 0x8F, 0x0F}, // 0x8F: 查询体动数据 + {0x84, 0x8E, 0x0F}, // 0x8E: 查询人员存在 + {0x84, 0x91, 0x0F}, // 0x91: 查询呼吸波形 + {0x84, 0x92, 0x0F}, // 0x92: 查询呼吸波形(备用) + {0x84, 0x83, 0x0F}, // 0x83: 查询心跳波形 + {0x84, 0x84, 0x0F}, // 0x84: 查询心跳波形(备用) + {0x84, 0x85, 0x0F}, // 0x85: 查询心跳波形(扩展) + {0x84, 0x86, 0x0F}, // 0x86: 查询心跳波形(扩展) + {0x84, 0x90, 0x0F} // 0x90: 查询综合状态 + }; + + static size_t cmdIndex = 0; + static unsigned long lastCmdMillis = 0; + const unsigned long CMD_INTERVAL = 2000UL; + + while (1) { + unsigned long now = millis(); + + if (now - lastCmdMillis >= CMD_INTERVAL) { + sendRadarCommand( + radar_cmds[cmdIndex][0], + radar_cmds[cmdIndex][1], + radar_cmds[cmdIndex][2] + ); + + lastCmdMillis = now; + cmdIndex++; + if (cmdIndex >= sizeof(radar_cmds) / sizeof(radar_cmds[0])) { + cmdIndex = 0; + } + } + + vTaskDelay(100 / portTICK_PERIOD_MS); + esp_task_wdt_reset(); + } +} + +/** + * @brief 情绪分析任务 + * 每秒更新生理数据、校准基线、分析情绪并输出结果 + * @param parameter 任务参数(未使用) + */ +void emotionAnalysisTask(void *parameter) { + physioProcessor = new PhysioDataProcessor(); + emotionAnalyzer = new SimpleEmotionAnalyzer(60); + + static unsigned long lastEmotionAnalysisTime = 0; + const unsigned long EMOTION_ANALYSIS_INTERVAL = 1000; + + while (1) { + unsigned long currentTime = millis(); + + if (currentTime - lastEmotionAnalysisTime >= EMOTION_ANALYSIS_INTERVAL) { + lastEmotionAnalysisTime = currentTime; + + if (sensorData.heart_valid || sensorData.breath_valid) { + float hr = sensorData.heart_valid ? sensorData.heart_rate : 0; + float rr = sensorData.breath_valid ? sensorData.breath_rate : 0; + + if (hr > 0 || rr > 0) { + physioProcessor->update(hr, rr, + sensorData.heart_valid ? 80 : 0, + sensorData.breath_valid ? 80 : 0); + + HeartRateData hrData = physioProcessor->getHeartRateData(); + RespirationData rrData = physioProcessor->getRespirationData(); + HRVEstimate hrvData = physioProcessor->getHRVEstimate(); + + BodyMovementData movementData; + memset(&movementData, 0, sizeof(BodyMovementData)); + movementData.movement = sensorData.body_movement; + movementData.movementSmoothed = sensorData.body_movement; + movementData.movementMean = sensorData.body_movement; + movementData.activityLevel = sensorData.body_movement / 100.0f; + movementData.isValid = (sensorData.body_movement >= 0 && sensorData.body_movement <= 100); + movementData.timestamp = currentTime; + + if (hrData.isValid && rrData.isValid) { + emotionAnalyzer->calibrateBaseline(hrData, rrData, movementData); + } + + EmotionResult emotionResult = emotionAnalyzer->analyze(hrData, rrData, hrvData, movementData); + + if (emotionResult.isValid) { + Serial.println("━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"); + Serial.printf("主要情绪:%s (置信度: %.1f%%);", + EMOTION_NAMES[emotionResult.primaryEmotion], + emotionResult.confidence * 100); + Serial.printf("次要情绪: %s倾向\n", + EMOTION_NAMES[emotionResult.secondaryEmotion]); + Serial.printf("情绪强度:%.1f%% ", emotionResult.intensity * 100); + Serial.printf("效价:%.2f(负面到正面) ", emotionResult.valence); + Serial.printf("唤醒度:%.2f(平静到激动)\n", emotionResult.arousal); + Serial.printf("压力水平:%.1f ", emotionResult.stressLevel); + Serial.printf("焦虑水平:%.1f ", emotionResult.anxietyLevel); + Serial.printf("放松水平:%.1f ", emotionResult.relaxationLevel); + Serial.printf("交感神经活动:%.2f ", emotionResult.sympatheticActivity); + Serial.printf("副交感神经活动:%.2f\n", emotionResult.parasympatheticActivity); + } + } + } + } + + vTaskDelay(50 / portTICK_PERIOD_MS); + esp_task_wdt_reset(); + } +} + +/** + * @brief 初始化所有FreeRTOS任务 + * 创建并启动所有后台任务:BOOT按钮监控、LED控制、WiFi监控、MQTT、BLE配置、雷达命令发送、情绪分析、睡眠分析 + */ +void initAllTasks() { + loadDeviceSN(); + xTaskCreate(bootButtonMonitorTask, "Boot Button Monitor Task", 2048, NULL, 1, NULL); + xTaskCreate(ledControlTask, "LED Control Task", 2048, NULL, 1, NULL); + xTaskCreate(wifiMonitorTask, "WiFi Monitor Task", 4096, NULL, 2, NULL); + xTaskCreatePinnedToCore(mqttTask, "MQTT Task", 8192, NULL, 2, &mqttTaskHandle, 1); + xTaskCreate(bleConfigTask, "BLE Config Task", 4096, NULL, 1, NULL); + xTaskCreate(radarCmdTask, "Radar Cmd Task", 2048, NULL, 2, NULL); + xTaskCreate(emotionAnalysisTask, "Emotion Analysis Task", 4096, NULL, 1, NULL); + xTaskCreate(sleepAnalysisTask, "Sleep Analysis Task", 4096, NULL, 1, NULL); +} diff --git a/src/tasks_manager.h b/src/tasks_manager.h new file mode 100644 index 0000000..1ba8176 --- /dev/null +++ b/src/tasks_manager.h @@ -0,0 +1,73 @@ +#ifndef TASKS_MANAGER_H +#define TASKS_MANAGER_H + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "wifi_manager.h" +#include "data_processor.h" +#include "emotion_analyzer_simple.h" +#include "sleep_analyzer.h" +#include "mqtt.h" + +// ESP32 GPIO控制演示 +#define BOOT_BUTTON_PIN 0 // Boot按钮引脚 +#define NETWORK_LED_PIN 5 // 网络状态LED指示灯开发板48引脚,雷达板5引脚 +#define CONFIG_CLEAR_PIN 4 // 配置清除指示灯 +#define GPIO8 8 // 自定义GPIO8 +#define GPIO9 9 // 自定义GPIO9 + +#define CLEAR_CONFIG_DURATION 3000 // 清除配置持续时间(毫秒) +#define SLOW_BLINK_INTERVAL 1000 // 慢闪间隔(毫秒) +#define FAST_BLINK_INTERVAL 200 // 快闪间隔(毫秒) +#define BREATHE_INTERVAL 40 // 呼吸灯更新间隔(毫秒) +#define BREATHE_MIN 0 // 呼吸灯最小亮度值 +#define BREATHE_MAX 155 // 呼吸灯最大亮度值 +#define BREATHE_STEP 5 // 呼吸灯亮度步进值 + +extern Preferences preferences; +extern WiFiManager wifiManager; +extern uint16_t currentDeviceId; +extern uint64_t device_sn; +extern bool deviceConnected; +extern bool oldDeviceConnected; +extern BLEServer* pServer; +extern BLECharacteristic* pCharacteristic; +extern NetworkStatus currentNetworkStatus; +extern unsigned long lastBlinkTime; +extern bool ledState; +extern int breatheValue; +extern bool breatheIncreasing; +extern uint8_t WiFi_Connect_First_bit; +extern void sendRadarCommand(uint8_t cmd, uint8_t subCmd, uint8_t param); +extern void initR60ABD1(); +extern void processBLEConfig(); +extern void sendStatusToBLE(); +extern void sendSleepDataToInfluxDB(); +extern void setNetworkStatus(NetworkStatus status); +extern void clearStoredConfig(); +extern void loadDeviceId(); +extern void saveDeviceId(); +extern uint32_t generateDeviceHash(); +extern std::string buildBLEManufacturerData(); +extern void refreshBLEAdvertisingData(); +extern String getDeviceMacAddress(); + +void initAllTasks(); +void WiFiEvent(WiFiEvent_t event); + +void bootButtonMonitorTask(void *parameter); +void ledControlTask(void *parameter); +void wifiMonitorTask(void *parameter); +void bleConfigTask(void *parameter); +void radarCmdTask(void *parameter); +void emotionAnalysisTask(void *parameter); +void sleepAnalysisTask(void *parameter); + +#endif diff --git a/src/wifi_manager.cpp b/src/wifi_manager.cpp index 8db9142..ca0e890 100644 --- a/src/wifi_manager.cpp +++ b/src/wifi_manager.cpp @@ -14,6 +14,24 @@ WiFiManager::WiFiManager() { currentState = WIFI_IDLE; lastReconnectAttempt = 0; isScanning = false; + manualConfigActive = false; + scanInProgress = false; + reconnectTaskHandle = NULL; + + // 创建扫描信号量(二值信号量) + scanSemaphore = xSemaphoreCreateBinary(); + if (scanSemaphore == NULL) { + Serial.println("❌ 创建扫描信号量失败"); + } else { + // 初始状态:信号量不可用 + xSemaphoreTake(scanSemaphore, 0); + } + + // 创建状态互斥锁 + stateMutex = xSemaphoreCreateMutex(); + if (stateMutex == NULL) { + Serial.println("❌ 创建状态互斥锁失败"); + } } /** @@ -23,6 +41,48 @@ WiFiManager::WiFiManager() { void WiFiManager::begin() { preferences.begin("wifi_manager", false); loadWiFiConfigs(); + + // 注册WiFi事件监听器 + WiFi.onEvent([this](WiFiEvent_t event, WiFiEventInfo_t info) { + switch (event) { + case ARDUINO_EVENT_WIFI_STA_DISCONNECTED: + Serial.println("⚠️ [WiFi事件] WiFi断开连接"); + if (currentState == WIFI_CONNECTED) { + currentState = WIFI_DISCONNECTED; + setNetworkStatus(NET_DISCONNECTED); + Serial.println("🔄 [WiFi事件] 已触发重连标志"); + } + break; + + case ARDUINO_EVENT_WIFI_STA_CONNECTED: + break; + + case ARDUINO_EVENT_WIFI_STA_GOT_IP: + currentState = WIFI_CONNECTED; + setNetworkStatus(NET_CONNECTED); + break; + + case ARDUINO_EVENT_WIFI_STA_LOST_IP: + Serial.println("⚠️ [WiFi事件] 丢失IP地址"); + break; + + default: + break; + } + }); + + // 创建重连任务(低优先级,会被扫描任务抢占) + if (reconnectTaskHandle == NULL) { + xTaskCreate( + reconnectTask, + "WiFi Reconnect Task", + 4096, + this, + 1, // 低优先级 + &reconnectTaskHandle + ); + Serial.println("✅ WiFi重连任务已创建"); + } } /** @@ -125,8 +185,6 @@ bool WiFiManager::saveWiFiConfig(const char* ssid, const char* password) { * @return 是否连接成功 */ bool WiFiManager::connectToNetwork(const char* ssid, const char* password) { - Serial.printf("🌐 [WiFi] 尝试连接到 SSID: %s\n", ssid); - currentState = WIFI_CONNECTING; setNetworkStatus(NET_CONNECTING); @@ -139,7 +197,6 @@ bool WiFiManager::connectToNetwork(const char* ssid, const char* password) { while (WiFi.status() != WL_CONNECTED && (millis() - startTime) < WIFI_CONNECT_TIMEOUT) { if (millis() - lastStatusPrint >= 500) { - Serial.printf("[WiFi] 连接中,状态: %d\n", WiFi.status()); lastStatusPrint = millis(); } yield(); @@ -154,6 +211,12 @@ bool WiFiManager::connectToNetwork(const char* ssid, const char* password) { currentState = WIFI_CONNECTED; setNetworkStatus(NET_CONNECTED); + // 清除手动配置标志位,恢复WiFi重连机制 + if (manualConfigActive) { + manualConfigActive = false; + Serial.println("🔧 [WiFi] 手动配置完成,WiFi重连机制已恢复"); + } + // 向蓝牙发送当前连接的WiFi配置信息 if (deviceConnected) { JsonDocument doc; @@ -203,10 +266,16 @@ bool WiFiManager::connectToNetwork(const char* ssid, const char* password) { * @return 是否成功连接到匹配的网络 */ bool WiFiManager::scanAndMatchNetworks() { + // 首先检查是否有已保存的WiFi网络 + if (savedNetworkCount == 0) { + Serial.println("⚠️ [WiFi] 没有已保存的WiFi网络,跳过重连扫描"); + return false; + } + if (isScanning) { Serial.println("⏳ [WiFi] 正在扫描中,等待扫描完成..."); int waitCount = 0; - while (isScanning && waitCount < 50) { + while (isScanning && waitCount < 100) { vTaskDelay(100 / portTICK_PERIOD_MS); waitCount++; } @@ -223,25 +292,87 @@ bool WiFiManager::scanAndMatchNetworks() { currentState = WIFI_SCANNING; isScanning = true; - if (WiFi.status() == WL_CONNECTED) { - Serial.println("📶 WiFi已连接,断开后扫描"); - WiFi.disconnect(false); - vTaskDelay(200 / portTICK_PERIOD_MS); + // 先完全断开并重置WiFi + Serial.println("🔄 [WiFi] 重置WiFi硬件..."); + WiFi.disconnect(true); // true = 关闭WiFi radio + vTaskDelay(500 / portTICK_PERIOD_MS); + + // 重新初始化WiFi + WiFi.mode(WIFI_STA); + vTaskDelay(300 / portTICK_PERIOD_MS); + + // 扫描重试机制 + int n = -1; + int scanRetryCount = 0; + const int MAX_SCAN_RETRIES = 5; + + while (n <= 0 && scanRetryCount < MAX_SCAN_RETRIES && !manualConfigActive) { + if (scanRetryCount > 0) { + Serial.printf("🔄 [WiFi] 扫描重试 %d/%d...\n", scanRetryCount, MAX_SCAN_RETRIES); + // 重试前再次重置WiFi + WiFi.disconnect(true); + vTaskDelay(500 / portTICK_PERIOD_MS); + WiFi.mode(WIFI_STA); + vTaskDelay(300 / portTICK_PERIOD_MS); + } + + n = WiFi.scanNetworks(false, true, false, 1000); // 增加扫描时间到1秒 + Serial.printf("🔍 扫描结果: %d 个WiFi网络 (尝试 %d/%d), WiFi状态: %d\n", + n, scanRetryCount + 1, MAX_SCAN_RETRIES, WiFi.status()); + scanRetryCount++; } - WiFi.mode(WIFI_STA); - vTaskDelay(200 / portTICK_PERIOD_MS); - - int n = WiFi.scanNetworks(); - Serial.printf("🔍 扫描到 %d 个WiFi网络\n", n); - - if (n <= 0) { - Serial.println("❌ 未扫描到任何WiFi网络或扫描失败"); + // 检查是否被蓝牙配网中断 + if (manualConfigActive) { + Serial.println("🔧 [WiFi] 重连扫描被蓝牙配网中断"); currentState = WIFI_DISCONNECTED; isScanning = false; + WiFi.scanDelete(); return false; } + if (n <= 0) { + Serial.println("❌ 多次重试后仍未扫描到WiFi网络,WiFi硬件可能异常"); + currentState = WIFI_DISCONNECTED; + isScanning = false; + // 清理WiFi状态 + WiFi.scanDelete(); + // 尝试完全重置WiFi + WiFi.mode(WIFI_OFF); + vTaskDelay(100 / portTICK_PERIOD_MS); + WiFi.mode(WIFI_STA); + return false; + } + + // 发送扫描结果到蓝牙(重连时的扫描也发送) + // 再次检查是否被中断 + if (manualConfigActive) { + Serial.println("🔧 [WiFi] 重连扫描被蓝牙配网中断"); + currentState = WIFI_DISCONNECTED; + isScanning = false; + WiFi.scanDelete(); + return false; + } + + if (deviceConnected) { + String wifiList = String("{\"type\":\"scanWiFiResult\",\"success\":true,\"source\":\"reconnect\",\"count\":") + String(n) + String(",\"networks\":["); + bool first = true; + for (int i = 0; i < n; ++i) { + if (WiFi.RSSI(i) >= MIN_RSSI_THRESHOLD) { + if (!first) { + wifiList += ","; + } + wifiList += String("{\"ssid\":\"") + WiFi.SSID(i) + String("\",\"rssi\":") + + String(WiFi.RSSI(i)) + String(",\"channel\":") + + String(WiFi.channel(i)) + String("}"); + first = false; + } + } + wifiList += "]}"; + Serial.printf("📱 [BLE] 发送重连扫描结果,共 %d 个网络\n", n); + sendJSONDataToBLE(wifiList); + } + // 收集所有匹配的、信号强度符合要求的网络 struct CandidateNetwork { const char* ssid; @@ -257,8 +388,6 @@ bool WiFiManager::scanAndMatchNetworks() { for (int j = 0; j < n; j++) { if (WiFi.SSID(j) == String(savedNetworks[i].ssid)) { int rssi = WiFi.RSSI(j); - Serial.printf("📶 找到匹配网络: %s, 信号: %d dBm\n", - savedNetworks[i].ssid, rssi); // 检查信号强度是否符合要求 if (rssi >= MIN_RSSI_THRESHOLD) { @@ -268,8 +397,6 @@ bool WiFiManager::scanAndMatchNetworks() { availableNetworks[availableCount].password = savedNetworks[i].password; availableNetworks[availableCount].rssi = rssi; availableCount++; - Serial.printf("✅ 添加到候选列表: %s, 信号: %d dBm\n", - savedNetworks[i].ssid, rssi); } } else { Serial.printf("⚠️ 信号强度过低,跳过\n"); @@ -309,6 +436,14 @@ bool WiFiManager::scanAndMatchNetworks() { // 依次尝试连接所有可用网络 for (int i = 0; i < availableCount; i++) { + // 检查是否被蓝牙配网中断 + if (manualConfigActive) { + Serial.println("🔧 [WiFi] 重连被蓝牙配网中断"); + currentState = WIFI_DISCONNECTED; + isScanning = false; + return false; + } + Serial.printf("🔄 [%d/%d] 尝试连接: %s (信号: %d dBm)\n", i + 1, availableCount, availableNetworks[i].ssid, @@ -332,24 +467,9 @@ bool WiFiManager::scanAndMatchNetworks() { } Serial.println("❌ 所有可用网络均连接失败"); - currentState = WIFI_DISCONNECTED; + currentState = WIFI_DISCONNECTED;// 扫描失败,设置为断开连接状态 isScanning = false; - // 向蓝牙发送所有WiFi连接失败信息 - if (deviceConnected) { - String wifiList = "{\"type\":\"wifiConnected\",\"success\":false,\"message\":\"所有保存的WiFi均连接失败\",\"networks\":["; - bool first = true; - for (int i = 0; i < availableCount; i++) { - if (!first) wifiList += ","; - wifiList += "{\"ssid\":\"" + String(availableNetworks[i].ssid) + "\",\"rssi\":" + String(availableNetworks[i].rssi) + "}"; - first = false; - } - wifiList += "],\"count\":" + String(availableCount) + "}"; - - sendJSONDataToBLE(wifiList); - Serial.printf("📱 [BLE] 发送所有WiFi连接失败信息: %s\n", wifiList.c_str()); - } - return false; } @@ -405,33 +525,33 @@ void WiFiManager::scanAndSendResults() { Serial.println("📱 [BLE-WiFi] 开始WiFi扫描..."); isScanning = true; - // 确保完全断开WiFi + // 如果已连接,先断开以确保扫描质量 if (WiFi.status() == WL_CONNECTED) { - Serial.println("📶 WiFi已连接,断开后扫描"); + Serial.println("📶 WiFi已连接,断开后进行同步扫描"); WiFi.disconnect(true); vTaskDelay(500 / portTICK_PERIOD_MS); } - // 更完整的WiFi初始化 - WiFi.mode(WIFI_OFF); - vTaskDelay(100 / portTICK_PERIOD_MS); + // 使用同步扫描,确保扫描完整完成,避免时序竞争 + Serial.println("🔍 [WiFi] 使用同步扫描..."); + int n = WiFi.scanNetworks(false, true, false, 300); // false = 同步扫描 - WiFi.mode(WIFI_STA); - vTaskDelay(500 / portTICK_PERIOD_MS); - - // 尝试多次扫描,提高成功率 - int n = 0; - int retryCount = 3; - - while (n <= 0 && retryCount > 0) { - Serial.printf("🔍 扫描WiFi网络 (尝试 %d/3)...\n", 4 - retryCount); - // 使用更可靠的扫描参数:被动扫描,包含隐藏SSID,更长的扫描时间 - n = WiFi.scanNetworks(false, true, true, 5000); // 5000ms扫描时间 + // 如果同步扫描失败,使用主动扫描重试 + if (n <= 0) { + Serial.println("🔄 同步扫描失败,重试主动扫描..."); - if (n <= 0) { - Serial.printf("❌ 扫描失败,返回值: %d, 剩余重试: %d\n", n, retryCount - 1); - retryCount--; - vTaskDelay(1000 / portTICK_PERIOD_MS); + int retryCount = 3; + while (n <= 0 && retryCount > 0) { + Serial.printf("🔍 同步扫描WiFi网络 (尝试 %d/3)...\n", 4 - retryCount); + + // 使用主动扫描重试,增加扫描时间 + n = WiFi.scanNetworks(false, true, false, 800); + + if (n <= 0) { + Serial.printf("❌ 扫描失败,返回值: %d, 剩余重试: %d\n", n, retryCount - 1); + retryCount--; + vTaskDelay(1000 / portTICK_PERIOD_MS); + } } } @@ -507,18 +627,6 @@ void WiFiManager::scanAndSendResults() { } } -/** - * @brief 开始配网模式 - * 进入配网模式,扫描WiFi网络并发送结果给客户端 - * @return 是否成功进入配网模式 - */ -bool WiFiManager::startConfiguration() { - Serial.println("⚙️ [WiFi] 开始配网模式..."); - currentState = WIFI_CONFIGURING; - scanAndSendResults(); - return true; -} - /** * @brief 处理配网数据 * 处理从客户端收到的WiFi配网信息,先扫描WiFi是否有匹配的网络,再尝试连接并保存 @@ -527,13 +635,57 @@ bool WiFiManager::startConfiguration() { * @return 是否配置成功 */ bool WiFiManager::handleConfigurationData(const char* ssid, const char* password) { + // 立即设置手动配置标志位,暂停所有WiFi操作 + manualConfigActive = true; + + Serial.println("🔧 [WiFi] 手动配置模式已激活,立即停止所有WiFi操作"); + + // 立即断开当前WiFi连接 + WiFi.disconnect(true); + + // 如果正在扫描,立即停止扫描 + if (isScanning || scanInProgress) { + Serial.println("🛑 [WiFi] 检测到正在扫描,立即停止"); + WiFi.scanDelete(); + isScanning = false; + scanInProgress = false; + currentState = WIFI_DISCONNECTED; + vTaskDelay(200 / portTICK_PERIOD_MS); + } + Serial.printf("📱 [BLE-WiFi] 收到配网信息: SSID='%s'\n", ssid); - if (ssid == nullptr || password == nullptr || strlen(ssid) == 0) { - Serial.println("❌ 配网参数无效"); + if (ssid == nullptr || strlen(ssid) == 0) { + Serial.println("❌ 配网参数无效:SSID为空"); + manualConfigActive = false; return false; } + // 如果密码为空,尝试从已保存的WiFi中查找密码 + const char* actualPassword = password; + char savedPassword[64] = {0}; + bool foundSavedPassword = false; + + if (password == nullptr || strlen(password) == 0) { + Serial.println("🔑 密码为空,尝试从已保存的WiFi中查找..."); + + for (int i = 0; i < savedNetworkCount; i++) { + if (strcmp(savedNetworks[i].ssid, ssid) == 0) { + strncpy(savedPassword, savedNetworks[i].password, 63); + savedPassword[63] = '\0'; + actualPassword = savedPassword; + foundSavedPassword = true; + Serial.printf("✅ 找到已保存的WiFi密码: %s\n", ssid); + break; + } + } + + if (!foundSavedPassword) { + Serial.println("⚠️ 未找到已保存的密码,将尝试无密码连接"); + actualPassword = ""; + } + } + if (isScanning) { Serial.println("⏳ [WiFi] 正在扫描中,等待扫描完成..."); int waitCount = 0; @@ -548,6 +700,7 @@ bool WiFiManager::handleConfigurationData(const char* ssid, const char* password String errorMsg = String("{\"type\":\"wifiConfigResult\",\"success\":false,\"message\":\"等待扫描超时,请稍后再试\"}"); sendJSONDataToBLE(errorMsg); } + manualConfigActive = false; return false; } @@ -559,10 +712,35 @@ bool WiFiManager::handleConfigurationData(const char* ssid, const char* password currentState = WIFI_SCANNING; isScanning = true; - int n = WiFi.scanNetworks(); - Serial.printf("🔍 扫描到 %d 个WiFi网络\n", n); + // 重新初始化WiFi + WiFi.mode(WIFI_STA); + vTaskDelay(200 / portTICK_PERIOD_MS); - if (n == 0) { + // 扫描重试机制 + int n = -1; + int scanRetryCount = 0; + const int MAX_SCAN_RETRIES = 3; + + while (n <= 0 && scanRetryCount < MAX_SCAN_RETRIES && manualConfigActive) { + if (scanRetryCount > 0) { + Serial.printf("🔄 [WiFi] 扫描重试 %d/%d...\n", scanRetryCount, MAX_SCAN_RETRIES); + vTaskDelay(500 / portTICK_PERIOD_MS); + } + + n = WiFi.scanNetworks(false, true, false, 500); + Serial.printf("🔍 扫描结果: %d 个WiFi网络 (尝试 %d/%d)\n", n, scanRetryCount + 1, MAX_SCAN_RETRIES); + scanRetryCount++; + } + + // 检查是否被中断 + if (!manualConfigActive) { + Serial.println("⚠️ [WiFi] 配网被中断"); + WiFi.scanDelete(); + isScanning = false; + return false; + } + + if (n <= 0) { Serial.println("❌ 未扫描到任何WiFi网络"); WiFi.scanDelete(); isScanning = false; @@ -572,6 +750,8 @@ bool WiFiManager::handleConfigurationData(const char* ssid, const char* password String resultMsg = String("{\"type\":\"wifiConfigResult\",\"success\":false,\"message\":\"未扫描到任何WiFi网络,请检查设备位置\"}"); sendJSONDataToBLE(resultMsg); } + vTaskDelay(3000 / portTICK_PERIOD_MS); + manualConfigActive = false; return false; } @@ -610,9 +790,16 @@ bool WiFiManager::handleConfigurationData(const char* ssid, const char* password isScanning = false; currentState = WIFI_DISCONNECTED; + if (manualConfigActive) { + manualConfigActive = false; + Serial.println("🔧 [WiFi] 手动配置失败,WiFi重连机制已恢复"); + } + if (deviceConnected) { sendJSONDataToBLE(errorMsg); } + vTaskDelay(3000 / portTICK_PERIOD_MS); + manualConfigActive = false; return false; } @@ -621,15 +808,16 @@ bool WiFiManager::handleConfigurationData(const char* ssid, const char* password vTaskDelay(300 / portTICK_PERIOD_MS); // 尝试连接到指定网络 - if (connectToNetwork(ssid, password)) { + if (connectToNetwork(ssid, actualPassword)) { // 连接成功后保存配置 - if (saveWiFiConfig(ssid, password)) { + if (saveWiFiConfig(ssid, actualPassword)) { Serial.println("✅ WiFi配置成功并已保存"); if (deviceConnected) { String resultMsg = String("{\"type\":\"wifiConfigResult\",\"success\":true,\"message\":\"WiFi配置成功\"}"); sendJSONDataToBLE(resultMsg); } + manualConfigActive = false; return true; } } @@ -638,10 +826,17 @@ bool WiFiManager::handleConfigurationData(const char* ssid, const char* password isScanning = false; currentState = WIFI_DISCONNECTED; + if (manualConfigActive) { + manualConfigActive = false; + Serial.println("🔧 [WiFi] 手动配置失败,WiFi重连机制已恢复"); + } + if (deviceConnected) { String resultMsg = String("{\"type\":\"wifiConfigResult\",\"success\":false,\"message\":\"WiFi配置失败,请检查密码是否正确\"}"); sendJSONDataToBLE(resultMsg); } + vTaskDelay(3000 / portTICK_PERIOD_MS); + manualConfigActive = false; return false; } @@ -650,45 +845,97 @@ bool WiFiManager::handleConfigurationData(const char* ssid, const char* password * 检查WiFi连接状态,当断开连接时尝试重连 */ void WiFiManager::handleReconnect() { + Serial.println("📞 [handleReconnect] 进入重连处理"); + Serial.printf("📞 [handleReconnect] manualConfigActive: %d, scanInProgress: %d\n", + manualConfigActive, scanInProgress); + + // 手动配置模式下暂停自动重连 + if (manualConfigActive) { + Serial.println("⏸️ [handleReconnect] 手动配置模式,跳过重连"); + return; + } + + // 扫描进行中,暂停重连 + if (scanInProgress) { + Serial.println("⏸️ [handleReconnect] 扫描进行中,跳过重连"); + return; + } + + // 检查是否有已保存的WiFi网络 + if (savedNetworkCount == 0) { + Serial.println("⚠️ [handleReconnect] 没有已保存的WiFi网络,跳过重连"); + return; + } + // 检查当前是否已连接 if (currentState == WIFI_CONNECTED) { if (WiFi.status() == WL_CONNECTED) { + Serial.println("✅ [handleReconnect] WiFi已连接,无需重连"); return; } // 连接已断开 - currentState = WIFI_DISCONNECTED; - setNetworkStatus(NET_DISCONNECTED); - Serial.println("⚠️ WiFi连接断开"); - - // 扫描并发送WiFi列表到蓝牙 - scanAndSendResults(); + if (xSemaphoreTake(stateMutex, pdMS_TO_TICKS(100)) == pdTRUE) { + currentState = WIFI_DISCONNECTED; + setNetworkStatus(NET_DISCONNECTED); + xSemaphoreGive(stateMutex); + } + Serial.println("⚠️ [handleReconnect] WiFi连接断开,开始重连"); } // 处理重连逻辑 if (currentState == WIFI_DISCONNECTED) { - unsigned long currentTime = millis(); + Serial.println("🔄 [handleReconnect] 开始快速重连..."); - // 按照设定的间隔尝试重连 - if (currentTime - lastReconnectAttempt >= WIFI_RECONNECT_INTERVAL) { - lastReconnectAttempt = currentTime; + // 直接尝试重连已保存的网络(不扫描,更快速) + for (int i = 0; i < savedNetworkCount; i++) { + Serial.printf("🔄 [handleReconnect] 尝试重连: %s\n", savedNetworks[i].ssid); - if (scanAndMatchNetworks()) { - Serial.println("✅ WiFi重连成功"); - } else { - Serial.println("❌ WiFi重连失败,2秒后重试"); + // 使用简化的重连方式 + WiFi.mode(WIFI_STA); + vTaskDelay(100 / portTICK_PERIOD_MS); + WiFi.begin(savedNetworks[i].ssid, savedNetworks[i].password); + + // 等待连接 + unsigned long startTime = millis(); + while (WiFi.status() != WL_CONNECTED && (millis() - startTime) < 8000) { + vTaskDelay(100 / portTICK_PERIOD_MS); + + // 检查是否被中断 + if (manualConfigActive) { + Serial.println("🔧 [handleReconnect] 被蓝牙配网中断"); + return; + } } + + if (WiFi.status() == WL_CONNECTED) { + Serial.printf("✅ [handleReconnect] 重连成功: %s\n", savedNetworks[i].ssid); + Serial.printf("🌐 IP地址: %s\n", WiFi.localIP().toString().c_str()); + Serial.printf("📶 信号强度: %d dBm\n", WiFi.RSSI()); + + currentState = WIFI_CONNECTED; + setNetworkStatus(NET_CONNECTED); + return; + } + + Serial.printf("❌ [handleReconnect] 重连失败: %s (状态:%d)\n", + savedNetworks[i].ssid, WiFi.status()); + WiFi.disconnect(false); // false = 不关闭radio + vTaskDelay(200 / portTICK_PERIOD_MS); + } + + // 所有网络都尝试失败 + Serial.println("❌ [handleReconnect] 所有已保存的网络都重连失败"); + + // 尝试完整扫描匹配(作为后备方案) + Serial.println("🔍 [handleReconnect] 尝试扫描匹配..."); + if (scanAndMatchNetworks()) { + Serial.println("✅ [handleReconnect] 扫描匹配成功"); + } else { + Serial.println("❌ [handleReconnect] 扫描匹配失败,稍后重试"); } } } -/** - * @brief 获取当前WiFi状态 - * @return 当前的WiFi管理器状态 - */ -WiFiManagerState WiFiManager::getState() { - return currentState; -} - /** * @brief 检查WiFi是否已连接 * @return WiFi是否已成功连接 @@ -697,16 +944,6 @@ bool WiFiManager::isConnected() { return currentState == WIFI_CONNECTED && WiFi.status() == WL_CONNECTED; } -/** - * @brief 断开WiFi连接 - * 主动断开当前的WiFi连接 - */ -void WiFiManager::disconnect() { - WiFi.disconnect(true); - currentState = WIFI_DISCONNECTED; - setNetworkStatus(NET_DISCONNECTED); -} - /** * @brief 添加WiFi配置 * 向保存的配置中添加新的WiFi网络 @@ -774,9 +1011,152 @@ void WiFiManager::getSavedNetworks() { } /** - * @brief 更新WiFi管理器状态 - * 定期调用此函数,处理WiFi重连等状态管理 + * @brief 重连任务 - 后台持续运行 + * 优先级低,会被扫描任务抢占 + * @param parameter WiFiManager实例指针 */ -void WiFiManager::update() { - handleReconnect(); +void WiFiManager::reconnectTask(void* parameter) { + WiFiManager* manager = (WiFiManager*)parameter; + + Serial.println("📡 [重连任务] 启动"); + Serial.printf("📡 [重连任务] 初始状态: %d, manualConfigActive: %d\n", + manager->currentState, manager->manualConfigActive); + + while (true) { + // 等待扫描信号量,最多等500ms + // 如果收到信号量,说明有扫描请求,暂停重连 + if (xSemaphoreTake(manager->scanSemaphore, pdMS_TO_TICKS(500)) == pdTRUE) { + // 收到扫描请求,进入等待状态 + Serial.println("🛑 [重连任务] 检测到扫描请求,暂停重连"); + + // 等待扫描完成信号(通过scanInProgress标志) + int waitCount = 0; + while (manager->scanInProgress) { + if (waitCount % 10 == 0) { + Serial.printf("⏳ [重连任务] 等待扫描完成... (%d秒)\n", waitCount / 10); + } + vTaskDelay(100 / portTICK_PERIOD_MS); + waitCount++; + } + + Serial.println("▶️ [重连任务] 扫描结束,恢复重连"); + Serial.printf("▶️ [重连任务] 当前状态: %d, WiFi状态: %d\n", + manager->currentState, WiFi.status()); + + // 关键修复:清空信号量,确保下次真正等待新的扫描请求 + // 因为xSemaphoreGive会使信号量变为可用,需要再Take一次清空 + xSemaphoreTake(manager->scanSemaphore, 0); // 非阻塞清空 + Serial.println("✅ [重连任务] 信号量已清空,继续循环"); + continue; + } + + // 没有扫描请求,执行正常重连逻辑 + if (manager->currentState == WIFI_DISCONNECTED && + !manager->manualConfigActive) { + + unsigned long currentTime = millis(); + unsigned long timeSinceLastAttempt = currentTime - manager->lastReconnectAttempt; + + // 断开后立即尝试重连(前3次),之后按间隔重连 + static int quickRetryCount = 0; + bool shouldRetry = false; + + if (quickRetryCount < 3) { + // 前3次快速重连(每次1秒间隔) + shouldRetry = (timeSinceLastAttempt >= 1000); + if (shouldRetry) { + quickRetryCount++; + Serial.printf("🚀 [重连任务] 快速重连 #%d\n", quickRetryCount); + } + } else { + // 之后按正常间隔重连 + shouldRetry = (timeSinceLastAttempt >= WIFI_RECONNECT_INTERVAL); + } + + if (shouldRetry) { + manager->lastReconnectAttempt = currentTime; + Serial.println("🔄 [重连任务] 尝试重连..."); + Serial.printf("🔄 [重连任务] 距上次尝试: %lu ms, WiFi状态: %d\n", + timeSinceLastAttempt, WiFi.status()); + manager->handleReconnect(); + + // 如果重连成功,重置快速重连计数 + if (manager->currentState == WIFI_CONNECTED) { + quickRetryCount = 0; + } + } + } + + // 检查实际WiFi状态 + if (manager->currentState == WIFI_CONNECTED && WiFi.status() != WL_CONNECTED) { + if (xSemaphoreTake(manager->stateMutex, pdMS_TO_TICKS(100)) == pdTRUE) { + manager->currentState = WIFI_DISCONNECTED; + setNetworkStatus(NET_DISCONNECTED); + xSemaphoreGive(manager->stateMutex); + Serial.println("⚠️ [重连任务] 检测到连接断开"); + } + } + + vTaskDelay(100 / portTICK_PERIOD_MS); + } +} + +/** + * @brief 启动扫描 - 抢占式 + * 通过信号量通知重连任务暂停,然后执行扫描 + * @param timeoutMs 扫描超时时间(毫秒) + * @return 是否成功启动扫描 + */ +bool WiFiManager::startScan(uint32_t timeoutMs) { + Serial.println("🔔 [startScan] 进入扫描启动函数"); + Serial.printf("🔔 [startScan] scanInProgress: %d, currentState: %d\n", + scanInProgress, currentState); + + // 检查是否已在扫描中 + if (scanInProgress) { + Serial.println("⏳ [startScan] 扫描已在进行中"); + return false; + } + + Serial.println("🔔 [startScan] 发送扫描请求信号..."); + + // 设置扫描标志(重连任务会检测这个标志) + scanInProgress = true; + Serial.println("✅ [startScan] scanInProgress 已设置为 true"); + + // 释放信号量,通知重连任务暂停 + xSemaphoreGive(scanSemaphore); + Serial.println("✅ [startScan] 信号量已释放"); + + // 短暂延迟,确保重连任务收到信号并暂停 + vTaskDelay(200 / portTICK_PERIOD_MS); + + // 获取状态锁,修改状态 + if (xSemaphoreTake(stateMutex, pdMS_TO_TICKS(100)) == pdTRUE) { + currentState = WIFI_SCANNING; + xSemaphoreGive(stateMutex); + Serial.println("✅ [startScan] 状态已设置为 WIFI_SCANNING"); + } + + Serial.println("🔍 [startScan] 重连任务已暂停,开始扫描..."); + + // 执行实际扫描(此时重连任务已暂停) + scanAndSendResults(); + + // 扫描完成,清除标志,恢复重连任务 + scanInProgress = false; + Serial.println("✅ [startScan] scanInProgress 已设置为 false"); + + // 恢复状态 + if (xSemaphoreTake(stateMutex, pdMS_TO_TICKS(100)) == pdTRUE) { + if (currentState == WIFI_SCANNING) { + currentState = (WiFi.status() == WL_CONNECTED) ? WIFI_CONNECTED : WIFI_DISCONNECTED; + } + xSemaphoreGive(stateMutex); + Serial.printf("✅ [startScan] 状态已恢复为: %d, WiFi状态: %d\n", + currentState, WiFi.status()); + } + + Serial.println("✅ [startScan] 扫描完成,重连任务已恢复"); + return true; } \ No newline at end of file diff --git a/src/wifi_manager.h b/src/wifi_manager.h index aeaffa5..3edf2d3 100644 --- a/src/wifi_manager.h +++ b/src/wifi_manager.h @@ -5,6 +5,9 @@ #include #include #include +#include +#include +#include /** * @brief 最大WiFi网络配置数量 @@ -24,14 +27,14 @@ * 定义WiFi连接的最大等待时间,超过此时间认为连接失败 * 单位:毫秒 */ -#define WIFI_CONNECT_TIMEOUT 3000 +#define WIFI_CONNECT_TIMEOUT 10000 /** * @brief WiFi重连间隔时间 * 定义WiFi断开后,尝试重新连接的时间间隔 * 单位:毫秒 */ -#define WIFI_RECONNECT_INTERVAL 2000 +#define WIFI_RECONNECT_INTERVAL 3000 /** * @brief WiFi网络信息结构 @@ -90,33 +93,46 @@ private: WiFiManagerState currentState; // 当前WiFi管理器状态 unsigned long lastReconnectAttempt; // 上次尝试重连的时间 bool isScanning; // 是否正在扫描 + bool manualConfigActive; // 手动配置标志位(setWiFiConfig 配置时为True,暂停重连) + bool lastScanHadAvailableNetwork; // 上次扫描是否有可用的已保存网络 + + // FreeRTOS资源 + TaskHandle_t reconnectTaskHandle; // 重连任务句柄 + SemaphoreHandle_t scanSemaphore; // 扫描信号量(二值) + SemaphoreHandle_t stateMutex; // 状态互斥锁 + volatile bool scanInProgress; // 扫描进行标志 + + // 重连任务函数(静态) + static void reconnectTask(void* parameter); bool scanAndMatchNetworks(); // 扫描并匹配网络 bool connectToNetwork(const char* ssid, const char* password); // 连接到指定网络 - void sendScanResultsViaBLE(); // 发送扫描结果到BLE bool saveWiFiConfig(const char* ssid, const char* password); // 保存WiFi配置 bool loadWiFiConfigs(); // 加载WiFi配置 public: + + WiFiManager(); // 构造函数 void begin(); // 初始化WiFi管理器 bool initializeWiFi(); // 初始化WiFi连接 - bool startConfiguration(); // 开始配网模式 bool handleConfigurationData(const char* ssid, const char* password); // 处理配网数据 void handleReconnect(); // 处理重连 - WiFiManagerState getState(); // 获取当前状态 bool isConnected(); // 检查是否已连接 - void disconnect(); // 断开连接 + // 扫描接口(会阻塞重连任务) + bool startScan(uint32_t timeoutMs = 30000); void scanAndSendResults(); // 扫描并发送结果 bool addWiFiConfig(const char* ssid, const char* password); // 添加WiFi配置 void clearAllConfigs(); // 清除所有配置 int getSavedNetworkCount(); // 获取已保存的网络数量 void getSavedNetworks(); // 获取已保存的WiFi网络列表 - void update(); // 更新WiFi管理器状态 + // 简化update(不再处理重连,重连在独立任务) + void update() {} // 更新WiFi管理器状态 + }; #endif \ No newline at end of file diff --git a/睡眠方案.txt b/睡眠方案.txt new file mode 100644 index 0000000..5e7fa7b --- /dev/null +++ b/睡眠方案.txt @@ -0,0 +1,214 @@ +🧠 一、系统总体架构(通用版) +雷达输入(两种类型) + ↓ +统一存在检测层(Presence Layer) + ↓ +生理信号层(HR / RR / HRV / Movement) + ↓ +睡眠状态机(核心) + ↓ +睡眠分期 + ↓ +事件检测(入睡 / 醒来 / 离床) + ↓ +统计 + 评分 +🧩 二、雷达适配层设计(重点) + +你要做的是:统一接口,底层适配 + +✅ 统一输出结构 +typedef struct { + bool isPresent; // 是否有人 + float distance; // 距离(无距离雷达填-1) + float confidence; // 存在置信度 0~1 + float motionEnergy; // 微动能量(关键!) +} PresenceData; +🟢 情况1:支持距离雷达(如FMCW) +isPresent = (distance > 20cm && distance < 100cm) + && (energy > threshold); + +confidence = energy归一化; + +👉 优势: + +可以判断是否在床上 +可以做“离床”精准检测 +🔵 情况2:不支持距离(如存在检测雷达) + +👉 用“微动 + 呼吸”判断 + +isPresent = (motionEnergy > lowThreshold) + || (检测到呼吸信号); + +confidence = motionEnergy归一化; +⚠️ 关键优化(必须做) + +👉 防误判(静止误判无人): + +if (HR 或 RR 有效) + isPresent = true; +🚶 三、有人 / 离床 / 无人逻辑(统一) +✅ 状态定义 +NO_PERSON +IN_BED +OUT_OF_BED +🧾 通用逻辑 +if (!presence.isPresent) { + noPersonTimer += dt; + + if (noPersonTimer > 10min) { + state = NO_PERSON; + endSleepSession(); + } else { + state = OUT_OF_BED; + awakeTime += dt; + } + +} else { + noPersonTimer = 0; + + // 有距离版本 + if (presence.distance > 80cm) + state = OUT_OF_BED; + else + state = IN_BED; +} +😴 四、入睡判断(统一方案) +✅ 输入 +HR(心率) +RR(呼吸) +HRV +Movement(0~100) +📐 Sleepiness Score +S = 0.35*(1 - HR_norm) + + 0.25*(HRV_norm) + + 0.20*(1 - RR_var) + + 0.20*(1 - Movement_norm) +🧾 判定 +if (S > 0.6 持续 5~10分钟) + → 入睡 +⚠️ 雷达适配补充 + +👉 无距离雷达必须加: + +if (!presence.isPresent) + 不允许入睡 +🌙 五、睡眠分期(核心) +🧠 特征统一归一化 +HR_norm = (HR - baselineHR)/20 +RR_norm = (RR - baselineRR)/4 +HRV_norm = HRV / 50 +Move_norm= Movement / 100 +🟢 深睡(Deep Sleep) +DeepScore = + 0.4*(1 - HR_norm) ++ 0.3*(HRV_norm) ++ 0.2*(1 - Move_norm) ++ 0.1*(RR稳定) + +条件加强版: + +Movement < 10 +HRV > baseline +🔵 浅睡(Light Sleep) +LightScore = + 0.3*(HR适中) ++ 0.3*(HRV中) ++ 0.2*(Move 10~40) ++ 0.2*(RR稳定) +🔴 清醒(Awake) +AwakeScore = + 0.5*(Move_norm) ++ 0.3*(HR_norm) ++ 0.2*(RR波动) +🧾 分类 +max(Deep, Light, Awake) +⏰ 六、醒来判断 +✅ 强规则(推荐) +if (Movement > 50 持续 2分钟) + → 醒来 +✅ 融合规则 +if (HR ↑ && RR ↑ && Movement ↑) + → 醒来 +🚪 七、离床判断(通用版) +🟢 有距离雷达 +if (distance > 80cm) + OUT_OF_BED +🔵 无距离雷达 +if (!presence.isPresent 持续 > 30秒) + OUT_OF_BED +🧾 最终结束 +if (无人 > 10分钟) + → 结束睡眠 +📊 八、睡眠统计 + +记录: + +totalSleepTime +deepSleepTime +lightSleepTime +awakeTime +outOfBedTime +sleepLatency(入睡时间) +wakeCount +⭐ 九、睡眠评分系统 +🎯 总分100 +1️⃣ 时长(30) +7~9小时 → 满分 +2️⃣ 深睡比例(25) +Deep / Total > 20% +3️⃣ 连续性(20) +醒来少 → 高分 +4️⃣ 生理质量(15) +HRV高 + HR稳定 +5️⃣ 入睡速度(10) +<20分钟 +📐 总公式 +Score = +0.3*duration + +0.25*deep + +0.2*continuity + +0.15*physiology + +0.1*latency +🔁 十、完整状态机(统一) +NO_PERSON + ↓ +IN_BED + ↓ +AWAKE + ↓ (满足入睡) +LIGHT_SLEEP + ↓ +DEEP_SLEEP + ↑↓ +LIGHT_SLEEP + ↓ +AWAKE + ↓ +OUT_OF_BED + ↓(10min) +END +⚙️ 十一、ESP32建议架构(实战) +🧵 任务划分(FreeRTOS) +Task1:雷达采集(Presence) +Task2:HR/RR/HRV计算 +Task3:睡眠状态机(核心) +Task4:UI / MQTT上传 +⏱ 更新周期 +Presence:10~20Hz +HR/RR:1Hz +睡眠分析:1Hz +🚀 十二、关键工程优化(你必须做) +1️⃣ 防误判“无人” +if (HR有效 || RR有效) + 强制 presence = true +2️⃣ 防抖动(超级关键) +状态必须持续 N 秒才切换 +3️⃣ 数据无效保护 +if (!HRV valid) + 不参与深睡判断 +✅ 总结(最核心一句话) + +👉 这套方案本质是: + +“雷达判断人 → 生理判断睡 → 时间保证稳定 → 状态机做最终决策” \ No newline at end of file