feat: 添加MQTT客户端任务,优化数据上报频率控制,移除冗余日志输出

This commit is contained in:
Admin
2026-04-17 17:58:51 +08:00
parent 0fa483f450
commit 5d0202afae
23 changed files with 6511 additions and 3634 deletions

BIN
demo.md Normal file

Binary file not shown.

873
docs/emotion_algorithm.md Normal file
View File

@@ -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处理 | 低置信度时标记为未知并强制落地 |

21
extra_script.py Normal file
View File

@@ -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)

BIN
github_guide.md Normal file

Binary file not shown.

View File

@@ -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
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

193
src/config.h Normal file
View File

@@ -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

356
src/data_processor.cpp Normal file
View File

@@ -0,0 +1,356 @@
/**
* @file data_processor.cpp
* @brief 数据处理模块实现
*/
#include "data_processor.h"
#include <math.h>
// ==================== 心率数据处理器实现 ====================
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();
}

174
src/data_processor.h Normal file
View File

@@ -0,0 +1,174 @@
/**
* @file data_processor.h
* @brief 数据处理模块头文件
* @description 处理雷达传感器提供的心率和呼吸数据
*/
#ifndef DATA_PROCESSOR_H
#define DATA_PROCESSOR_H
#include <Arduino.h>
#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

View File

@@ -0,0 +1,838 @@
/**
* @file emotion_analyzer_simple.cpp
* @brief 简化版情绪分析器实现
*/
#include "emotion_analyzer_simple.h"
#include <math.h>
// ==================== 情绪分析器实现 ====================
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;
}

View File

@@ -0,0 +1,189 @@
/**
* @file emotion_analyzer_simple.h
* @brief 简化版情绪分析器头文件
* @description 适配雷达传感器数据,分析用户情绪状态
*/
#ifndef EMOTION_ANALYZER_SIMPLE_H
#define EMOTION_ANALYZER_SIMPLE_H
#include <Arduino.h>
#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

View File

@@ -5,616 +5,76 @@
#include <Preferences.h>
#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);
}

File diff suppressed because it is too large Load Diff

584
src/mqtt.cpp Normal file
View File

@@ -0,0 +1,584 @@
#include "mqtt.h"
#include "wifi_manager.h"
#include "radar_manager.h"
#include <mbedtls/md5.h>
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<JsonObject>();
if (strcmp(method, "thing.service.property.set") == 0) {
bool ok = true;
if (params["continuousSendEnabled"].is<bool>()) {
continuousSendEnabled = params["continuousSendEnabled"].as<bool>();
}
if (params["continuousSendInterval"].is<unsigned long>()) {
continuousSendInterval = params["continuousSendInterval"].as<unsigned long>();
}
JsonDocument replyData;
replyData["success"] = ok;
if (publishMqttReply(topic, id, method, ok ? 0 : -1, replyData.as<JsonVariant>())) {
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<JsonVariant>())) {
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<JsonVariant>())) {
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);
}
}

51
src/mqtt.h Normal file
View File

@@ -0,0 +1,51 @@
#ifndef MQTT_MANAGER_H
#define MQTT_MANAGER_H
#include <Arduino.h>
#include <ArduinoJson.h>
#include <WiFi.h>
#include <PubSubClient.h>
#include <freertos/FreeRTOS.h>
#include <freertos/task.h>
#include <esp_task_wdt.h>
#include <Preferences.h>
#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

File diff suppressed because it is too large Load Diff

View File

@@ -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 {

978
src/sleep_analyzer.cpp Normal file
View File

@@ -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("━━━━━━━━━━━━━━━━━━━━━━━━━━━━");
}

223
src/sleep_analyzer.h Normal file
View File

@@ -0,0 +1,223 @@
#ifndef SLEEP_ANALYZER_H
#define SLEEP_ANALYZER_H
#include <Arduino.h>
#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

650
src/tasks_manager.cpp Normal file
View File

@@ -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 <BLEDevice.h>
#include <esp_task_wdt.h>
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<char>(0xFF));
manufacturerData.push_back(static_cast<char>(0xFF));
manufacturerData.push_back('R');
manufacturerData.push_back(0x01);
manufacturerData.push_back(0x00);
uint32_t snHash = generateDeviceHash();
manufacturerData.push_back(static_cast<char>((snHash >> 24) & 0xFF));
manufacturerData.push_back(static_cast<char>((snHash >> 16) & 0xFF));
manufacturerData.push_back(static_cast<char>((snHash >> 8) & 0xFF));
manufacturerData.push_back(static_cast<char>(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);
}

73
src/tasks_manager.h Normal file
View File

@@ -0,0 +1,73 @@
#ifndef TASKS_MANAGER_H
#define TASKS_MANAGER_H
#include <freertos/FreeRTOS.h>
#include <freertos/task.h>
#include <BLEDevice.h>
#include <BLEServer.h>
#include <BLEUtils.h>
#include <BLE2902.h>
#include <WiFi.h>
#include <Arduino.h>
#include <Preferences.h>
#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

View File

@@ -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);
// 如果同步扫描失败,使用主动扫描重试
if (n <= 0) {
Serial.println("🔄 同步扫描失败,重试主动扫描...");
// 尝试多次扫描,提高成功率
int n = 0;
int retryCount = 3;
int retryCount = 3;
while (n <= 0 && retryCount > 0) {
Serial.printf("🔍 同步扫描WiFi网络 (尝试 %d/3)...\n", 4 - retryCount);
while (n <= 0 && retryCount > 0) {
Serial.printf("🔍 扫描WiFi网络 (尝试 %d/3)...\n", 4 - retryCount);
// 使用更可靠的扫描参数被动扫描包含隐藏SSID更长的扫描时间
n = WiFi.scanNetworks(false, true, true, 5000); // 5000ms扫描时间
// 使用主动扫描重试,增加扫描时间
n = WiFi.scanNetworks(false, true, false, 800);
if (n <= 0) {
Serial.printf("❌ 扫描失败,返回值: %d, 剩余重试: %d\n", n, retryCount - 1);
retryCount--;
vTaskDelay(1000 / portTICK_PERIOD_MS);
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;
}

View File

@@ -5,6 +5,9 @@
#include <WiFi.h>
#include <Preferences.h>
#include <ArduinoJson.h>
#include <FreeRTOS.h>
#include <semphr.h>
#include <task.h>
/**
* @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

214
睡眠方案.txt Normal file
View File

@@ -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
Movement0~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
Task2HR/RR/HRV计算
Task3睡眠状态机核心
Task4UI / MQTT上传
⏱ 更新周期
Presence10~20Hz
HR/RR1Hz
睡眠分析1Hz
🚀 十二、关键工程优化(你必须做)
1⃣ 防误判“无人”
if (HR有效 || RR有效)
强制 presence = true
2⃣ 防抖动(超级关键)
状态必须持续 N 秒才切换
3⃣ 数据无效保护
if (!HRV valid)
不参与深睡判断
✅ 总结(最核心一句话)
👉 这套方案本质是:
“雷达判断人 → 生理判断睡 → 时间保证稳定 → 状态机做最终决策”