初始提交:雷达系统代码,包含WiFi管理和雷达数据处理功能
This commit is contained in:
7
.gitignore
vendored
Normal file
7
.gitignore
vendored
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
.pio
|
||||||
|
.vscode/.browse.c_cpp.db*
|
||||||
|
.vscode/c_cpp_properties.json
|
||||||
|
.vscode/launch.json
|
||||||
|
.vscode/ipch
|
||||||
|
Git提交方法.md
|
||||||
|
.trae/
|
||||||
10
.vscode/extensions.json
vendored
Normal file
10
.vscode/extensions.json
vendored
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
{
|
||||||
|
// See http://go.microsoft.com/fwlink/?LinkId=827846
|
||||||
|
// for the documentation about the extensions.json format
|
||||||
|
"recommendations": [
|
||||||
|
"platformio.platformio-ide"
|
||||||
|
],
|
||||||
|
"unwantedRecommendations": [
|
||||||
|
"ms-vscode.cpptools-extension-pack"
|
||||||
|
]
|
||||||
|
}
|
||||||
52
.vscode/settings.json
vendored
Normal file
52
.vscode/settings.json
vendored
Normal file
@@ -0,0 +1,52 @@
|
|||||||
|
{
|
||||||
|
"files.associations": {
|
||||||
|
"cmath": "cpp",
|
||||||
|
"array": "cpp",
|
||||||
|
"string": "cpp",
|
||||||
|
"string_view": "cpp",
|
||||||
|
"new": "cpp",
|
||||||
|
"atomic": "cpp",
|
||||||
|
"cctype": "cpp",
|
||||||
|
"clocale": "cpp",
|
||||||
|
"cstdarg": "cpp",
|
||||||
|
"cstddef": "cpp",
|
||||||
|
"cstdint": "cpp",
|
||||||
|
"cstdio": "cpp",
|
||||||
|
"cstdlib": "cpp",
|
||||||
|
"cstring": "cpp",
|
||||||
|
"ctime": "cpp",
|
||||||
|
"cwchar": "cpp",
|
||||||
|
"cwctype": "cpp",
|
||||||
|
"deque": "cpp",
|
||||||
|
"unordered_map": "cpp",
|
||||||
|
"unordered_set": "cpp",
|
||||||
|
"vector": "cpp",
|
||||||
|
"exception": "cpp",
|
||||||
|
"algorithm": "cpp",
|
||||||
|
"functional": "cpp",
|
||||||
|
"iterator": "cpp",
|
||||||
|
"map": "cpp",
|
||||||
|
"memory": "cpp",
|
||||||
|
"memory_resource": "cpp",
|
||||||
|
"numeric": "cpp",
|
||||||
|
"optional": "cpp",
|
||||||
|
"random": "cpp",
|
||||||
|
"system_error": "cpp",
|
||||||
|
"tuple": "cpp",
|
||||||
|
"type_traits": "cpp",
|
||||||
|
"utility": "cpp",
|
||||||
|
"fstream": "cpp",
|
||||||
|
"initializer_list": "cpp",
|
||||||
|
"iomanip": "cpp",
|
||||||
|
"iosfwd": "cpp",
|
||||||
|
"istream": "cpp",
|
||||||
|
"limits": "cpp",
|
||||||
|
"ostream": "cpp",
|
||||||
|
"sstream": "cpp",
|
||||||
|
"stdexcept": "cpp",
|
||||||
|
"streambuf": "cpp",
|
||||||
|
"cinttypes": "cpp",
|
||||||
|
"typeinfo": "cpp"
|
||||||
|
},
|
||||||
|
"C_Cpp.errorSquiggles": "disabled"
|
||||||
|
}
|
||||||
516
BLE_API.md
Normal file
516
BLE_API.md
Normal file
@@ -0,0 +1,516 @@
|
|||||||
|
# ESP32 BLE API 文档
|
||||||
|
|
||||||
|
## 概述
|
||||||
|
|
||||||
|
本文档描述了 ESP32 雷达设备的 BLE(蓝牙低功耗)通信 API,用于通过蓝牙连接配置和管理设备。
|
||||||
|
|
||||||
|
## 通信协议
|
||||||
|
|
||||||
|
- **传输方式**: BLE (Bluetooth Low Energy)
|
||||||
|
- **数据格式**: JSON
|
||||||
|
- **编码**: UTF-8
|
||||||
|
- **分包大小**: 20 字节/包
|
||||||
|
- **特征值 UUID**: `beb5483e-36e1-4688-b7f5-ea07361b26a8`
|
||||||
|
- **服务 UUID**: `a8c1e5c0-3d5d-4a9d-8d5e-7c8b6a4e2f1a`
|
||||||
|
|
||||||
|
## 通用响应格式
|
||||||
|
|
||||||
|
所有响应都遵循以下 JSON 格式:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"type": "响应类型",
|
||||||
|
"success": true/false,
|
||||||
|
"message": "可选的消息描述",
|
||||||
|
...
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## API 列表
|
||||||
|
|
||||||
|
### 1. WiFi 扫描
|
||||||
|
|
||||||
|
扫描周围可用的 WiFi 网络。
|
||||||
|
|
||||||
|
#### 请求
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"command": "scanWiFi"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 响应
|
||||||
|
|
||||||
|
成功时:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"type": "scanWiFiResult",
|
||||||
|
"success": true,
|
||||||
|
"count": 3,
|
||||||
|
"networks": [
|
||||||
|
{
|
||||||
|
"ssid": "WiFi名称1",
|
||||||
|
"rssi": -45,
|
||||||
|
"channel": 6,
|
||||||
|
"encryption": 3
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ssid": "WiFi名称2",
|
||||||
|
"rssi": -60,
|
||||||
|
"channel": 11,
|
||||||
|
"encryption": 4
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
失败时:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"type": "scanWiFiResult",
|
||||||
|
"success": false,
|
||||||
|
"message": "未扫描到任何WiFi网络",
|
||||||
|
"networks": [],
|
||||||
|
"count": 0
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 字段说明
|
||||||
|
|
||||||
|
| 字段 | 类型 | 说明 |
|
||||||
|
|------|------|------|
|
||||||
|
| `ssid` | string | WiFi 网络名称 |
|
||||||
|
| `rssi` | number | 信号强度(dBm),值越大信号越强 |
|
||||||
|
| `channel` | number | WiFi 通道号(1-13) |
|
||||||
|
| `encryption` | number | 加密类型:<br>0 = 开放<br>1 = WEP<br>2 = WPA_PSK<br>3 = WPA2_PSK<br>4 = WPA_WPA2_PSK |
|
||||||
|
|
||||||
|
#### 示例
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
// 发送扫描命令
|
||||||
|
const scanCommand = JSON.stringify({ command: "scanWiFi" });
|
||||||
|
await characteristic.writeValue(scanCommand);
|
||||||
|
|
||||||
|
// 接收响应
|
||||||
|
characteristic.addEventListener('characteristicvaluechanged', (event) => {
|
||||||
|
const response = JSON.parse(event.target.value);
|
||||||
|
if (response.type === "scanWiFiResult") {
|
||||||
|
console.log(`扫描到 ${response.count} 个网络`);
|
||||||
|
response.networks.forEach(network => {
|
||||||
|
console.log(`SSID: ${network.ssid}, 信号: ${network.rssi} dBm`);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 2. 获取已保存的 WiFi 网络
|
||||||
|
|
||||||
|
获取设备中已保存的 WiFi 网络列表。
|
||||||
|
|
||||||
|
#### 请求
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"command": "getSavedNetworks"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 响应
|
||||||
|
|
||||||
|
成功时:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"type": "savedNetworks",
|
||||||
|
"success": true,
|
||||||
|
"count": 2,
|
||||||
|
"networks": [
|
||||||
|
{
|
||||||
|
"ssid": "MyWiFi"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ssid": "OfficeWiFi"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
无保存网络时:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"type": "savedNetworks",
|
||||||
|
"success": true,
|
||||||
|
"count": 0,
|
||||||
|
"networks": []
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 字段说明
|
||||||
|
|
||||||
|
| 字段 | 类型 | 说明 |
|
||||||
|
|------|------|------|
|
||||||
|
| `ssid` | string | 已保存的 WiFi 网络名称(不包含密码) |
|
||||||
|
|
||||||
|
#### 示例
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
// 发送获取命令
|
||||||
|
const getCommand = JSON.stringify({ command: "getSavedNetworks" });
|
||||||
|
await characteristic.writeValue(getCommand);
|
||||||
|
|
||||||
|
// 接收响应
|
||||||
|
characteristic.addEventListener('characteristicvaluechanged', (event) => {
|
||||||
|
const response = JSON.parse(event.target.value);
|
||||||
|
if (response.type === "savedNetworks") {
|
||||||
|
console.log(`已保存 ${response.count} 个网络`);
|
||||||
|
response.networks.forEach(network => {
|
||||||
|
console.log(`SSID: ${network.ssid}`);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 3. 配置 WiFi
|
||||||
|
|
||||||
|
设置 WiFi 网络配置。
|
||||||
|
|
||||||
|
#### 请求
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"command": "setWiFiConfig",
|
||||||
|
"ssid": "WiFi名称",
|
||||||
|
"password": "WiFi密码"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 响应
|
||||||
|
|
||||||
|
成功时:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"type": "wifiConfigResult",
|
||||||
|
"success": true,
|
||||||
|
"message": "WiFi配置成功"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
失败时:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"type": "wifiConfigResult",
|
||||||
|
"success": false,
|
||||||
|
"message": "错误描述"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 错误说明
|
||||||
|
|
||||||
|
| 错误信息 | 原因 | 解决方案 |
|
||||||
|
|---------|--------|---------|
|
||||||
|
| `未扫描到任何WiFi网络,请检查设备位置` | 设备扫描不到任何 WiFi 网络 | 将设备移到更靠近路由器的位置 |
|
||||||
|
| `未找到目标WiFi网络,请检查WiFi名称是否正确` | 扫描结果中不存在目标 WiFi | 检查 WiFi 名称是否拼写正确,确保设备在 WiFi 覆盖范围内 |
|
||||||
|
| `目标WiFi信号过弱,请将设备靠近路由器` | 目标 WiFi 信号强度低于阈值(-200 dBm) | 将设备移到更靠近路由器的位置 |
|
||||||
|
| `WiFi配置失败,请检查密码是否正确` | 密码错误或连接超时 | 检查 WiFi 密码是否正确,确保设备在 WiFi 覆盖范围内 |
|
||||||
|
|
||||||
|
#### 字段说明
|
||||||
|
|
||||||
|
| 字段 | 类型 | 必需 | 说明 |
|
||||||
|
|------|------|--------|------|
|
||||||
|
| `ssid` | string | ✅ | WiFi 网络名称(最多 31 字符) |
|
||||||
|
| `password` | string | ✅ | WiFi 密码(最多 63 字符) |
|
||||||
|
|
||||||
|
#### 示例
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
// 发送配置命令
|
||||||
|
const configCommand = JSON.stringify({
|
||||||
|
command: "setWiFiConfig",
|
||||||
|
ssid: "MyWiFi",
|
||||||
|
password: "mypassword"
|
||||||
|
});
|
||||||
|
await characteristic.writeValue(configCommand);
|
||||||
|
|
||||||
|
// 接收响应
|
||||||
|
characteristic.addEventListener('characteristicvaluechanged', (event) => {
|
||||||
|
const response = JSON.parse(event.target.value);
|
||||||
|
if (response.type === "wifiConfigResult") {
|
||||||
|
if (response.success) {
|
||||||
|
console.log("WiFi 配置成功");
|
||||||
|
} else {
|
||||||
|
console.log("WiFi 配置失败:", response.message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 4. 查询设备状态
|
||||||
|
|
||||||
|
查询设备的当前状态,包括 WiFi 连接状态、设备 ID 等。
|
||||||
|
|
||||||
|
#### 请求
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"command": "queryStatus"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 响应
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"type": "deviceStatus",
|
||||||
|
"success": true,
|
||||||
|
"deviceId": 1001,
|
||||||
|
"wifiConfigured": true,
|
||||||
|
"wifiConnected": true,
|
||||||
|
"ipAddress": "192.168.137.241"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 字段说明
|
||||||
|
|
||||||
|
| 字段 | 类型 | 说明 |
|
||||||
|
|------|------|------|
|
||||||
|
| `deviceId` | number | 设备 ID(1000-1999) |
|
||||||
|
| `wifiConfigured` | boolean | 是否已配置 WiFi |
|
||||||
|
| `wifiConnected` | boolean | WiFi 是否已连接 |
|
||||||
|
| `ipAddress` | string | 设备 IP 地址(已连接时) |
|
||||||
|
|
||||||
|
#### 示例
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
// 发送查询命令
|
||||||
|
const queryCommand = JSON.stringify({ command: "queryStatus" });
|
||||||
|
await characteristic.writeValue(queryCommand);
|
||||||
|
|
||||||
|
// 接收响应
|
||||||
|
characteristic.addEventListener('characteristicvaluechanged', (event) => {
|
||||||
|
const response = JSON.parse(event.target.value);
|
||||||
|
if (response.type === "deviceStatus") {
|
||||||
|
console.log(`设备 ID: ${response.deviceId}`);
|
||||||
|
console.log(`WiFi 已配置: ${response.wifiConfigured}`);
|
||||||
|
console.log(`WiFi 已连接: ${response.wifiConnected}`);
|
||||||
|
console.log(`IP 地址: ${response.ipAddress}`);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 5. 设置设备 ID
|
||||||
|
|
||||||
|
设置设备的唯一标识 ID。
|
||||||
|
|
||||||
|
#### 请求
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"command": "setDeviceId",
|
||||||
|
"newDeviceId": 1001
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 响应
|
||||||
|
|
||||||
|
成功时:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"type": "setDeviceIdResult",
|
||||||
|
"success": true,
|
||||||
|
"message": "设备ID设置成功",
|
||||||
|
"newDeviceId": 1001
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
失败时:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"type": "setDeviceIdResult",
|
||||||
|
"success": false,
|
||||||
|
"message": "设备ID超出范围,有效范围: 1000-1999"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 字段说明
|
||||||
|
|
||||||
|
| 字段 | 类型 | 必需 | 说明 |
|
||||||
|
|------|------|--------|------|
|
||||||
|
| `newDeviceId` | number | ✅ | 新设备 ID(1000-1999) |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 6. 启动持续发送
|
||||||
|
|
||||||
|
启动雷达数据的持续发送模式。
|
||||||
|
|
||||||
|
#### 请求
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"command": "startContinuousSend",
|
||||||
|
"interval": 200
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 响应
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"type": "startContinuousSendResult",
|
||||||
|
"success": true,
|
||||||
|
"message": "已启动持续发送模式",
|
||||||
|
"interval": 200
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 字段说明
|
||||||
|
|
||||||
|
| 字段 | 类型 | 必需 | 说明 |
|
||||||
|
|------|------|--------|------|
|
||||||
|
| `interval` | number | ❌ | 发送间隔(毫秒),默认 200ms,范围 100-10000 |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 7. 停止持续发送
|
||||||
|
|
||||||
|
停止雷达数据的持续发送模式。
|
||||||
|
|
||||||
|
#### 请求
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"command": "stopContinuousSend"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 响应
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"type": "stopContinuousSendResult",
|
||||||
|
"success": true,
|
||||||
|
"message": "已停止持续发送模式"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 8. 查询雷达数据
|
||||||
|
|
||||||
|
查询当前的雷达传感器数据。
|
||||||
|
|
||||||
|
#### 请求
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"command": "queryRadarData"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 响应
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"type": "radarData",
|
||||||
|
"success": true,
|
||||||
|
"deviceId": 1001,
|
||||||
|
"timestamp": 1234567890,
|
||||||
|
"presence": 1,
|
||||||
|
"heartRate": 75.5,
|
||||||
|
"breathRate": 16.2,
|
||||||
|
"motion": 0,
|
||||||
|
"heartbeatWaveform": 120,
|
||||||
|
"breathingWaveform": 85,
|
||||||
|
"sleepState": 0
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 字段说明
|
||||||
|
|
||||||
|
| 字段 | 类型 | 说明 |
|
||||||
|
|------|------|------|
|
||||||
|
| `timestamp` | number | 时间戳(毫秒) |
|
||||||
|
| `presence` | number | 是否检测到人体(0=无,1=有) |
|
||||||
|
| `heartRate` | number | 心率(次/分钟) |
|
||||||
|
| `breathRate` | number | 呼吸率(次/分钟) |
|
||||||
|
| `motion` | number | 运动状态(0=静止,1=轻微,2=明显) |
|
||||||
|
| `heartbeatWaveform` | number | 心跳波形值 |
|
||||||
|
| `breathingWaveform` | number | 呼吸波形值 |
|
||||||
|
| `sleepState` | number | 睡眠状态(0=清醒,1=浅睡,2=深睡,3=REM) |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 9. 回显测试
|
||||||
|
|
||||||
|
用于测试 BLE 连接是否正常。
|
||||||
|
|
||||||
|
#### 请求
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"command": "echo",
|
||||||
|
"content": "测试内容"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 响应
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"type": "echoResponse",
|
||||||
|
"originalContent": "测试内容",
|
||||||
|
"receivedSuccessfully": true
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 错误响应
|
||||||
|
|
||||||
|
当请求失败时,设备会返回错误响应:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"type": "error",
|
||||||
|
"message": "错误描述",
|
||||||
|
"receivedData": "原始请求数据"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## 连接流程
|
||||||
|
|
||||||
|
1. 扫描并连接 BLE 设备
|
||||||
|
2. 开启 notify 订阅
|
||||||
|
3. 发送 `queryStatus` 命令获取设备状态
|
||||||
|
4. 根据需要配置 WiFi 或查询雷达数据
|
||||||
|
|
||||||
|
## 注意事项
|
||||||
|
|
||||||
|
1. **分包传输**: 所有 JSON 数据都会被分包发送(每包 20 字节),客户端需要正确拼接
|
||||||
|
2. **UTF-8 编码**: 确保使用 UTF-8 编码处理中文字符
|
||||||
|
3. **超时处理**: 建议为每个命令设置超时时间(推荐 5-10 秒)
|
||||||
|
4. **设备 ID 范围**: 有效范围为 1000-1999
|
||||||
|
5. **WiFi 限制**: 仅支持 2.4GHz WiFi 网络
|
||||||
|
6. **最大保存网络数**: 最多保存 10 个 WiFi 配置
|
||||||
|
|
||||||
|
## 版本历史
|
||||||
|
|
||||||
|
| 版本 | 日期 | 说明 |
|
||||||
|
|------|------|------|
|
||||||
|
| 1.0 | 2025-02-03 | 初始版本,包含所有基础 API |
|
||||||
259
README.md
Normal file
259
README.md
Normal file
@@ -0,0 +1,259 @@
|
|||||||
|
# Rader_Success_5 - ESP32 雷达数据采集与传输系统
|
||||||
|
|
||||||
|
## 项目简介
|
||||||
|
|
||||||
|
本项目是一个基于 ESP32 的智能雷达数据采集与传输系统,通过 R60ABD1 雷达传感器采集人体生命体征数据,并通过蓝牙(BLE)与小程序交互,同时支持 WiFi 连接将数据上传到云服务器数据库。
|
||||||
|
|
||||||
|
## 主要功能
|
||||||
|
|
||||||
|
### 1. 雷达数据采集
|
||||||
|
- **人体存在检测**:实时检测区域内是否有人
|
||||||
|
- **心率监测**:监测人体心率(BPM)
|
||||||
|
- **呼吸率监测**:监测人体呼吸频率
|
||||||
|
- **睡眠监测**:监测睡眠状态(清醒/浅睡/深睡/REM)
|
||||||
|
- **运动检测**:检测人体运动状态
|
||||||
|
- **距离测量**:测量人体与设备的距离
|
||||||
|
- **睡眠质量评分**:提供睡眠质量评估
|
||||||
|
|
||||||
|
### 2. 蓝牙通信(BLE)
|
||||||
|
- 设备配置与管理
|
||||||
|
- WiFi 网络扫描与配置
|
||||||
|
- 设备状态查询
|
||||||
|
- 雷达数据实时查询
|
||||||
|
- 持续数据发送模式
|
||||||
|
- 设备 ID 设置
|
||||||
|
|
||||||
|
### 3. WiFi 连接
|
||||||
|
- 自动 WiFi 连接与重连
|
||||||
|
- 支持多 WiFi 网络配置(最多 10 个)
|
||||||
|
- 网络信号强度检测
|
||||||
|
- 网络状态监控
|
||||||
|
|
||||||
|
### 4. 数据上传
|
||||||
|
- 将雷达数据上传到 InfluxDB 云数据库
|
||||||
|
- 支持每日数据汇总
|
||||||
|
- 支持睡眠数据专项上传
|
||||||
|
|
||||||
|
## 硬件配置
|
||||||
|
|
||||||
|
### 主控芯片
|
||||||
|
- **ESP32**:双核微控制器,支持 WiFi 和 BLE
|
||||||
|
|
||||||
|
### 雷达传感器
|
||||||
|
- **型号**:R60ABD1
|
||||||
|
- **通信接口**:UART
|
||||||
|
- **检测范围**:人体存在、心率、呼吸率、睡眠状态等
|
||||||
|
|
||||||
|
### 引脚定义
|
||||||
|
| 引脚 | 功能 | 说明 |
|
||||||
|
|------|------|------|
|
||||||
|
| GPIO 0 | Boot 按钮 | 配置清除按钮 |
|
||||||
|
| GPIO 48 | 网络状态 LED | 指示网络连接状态 |
|
||||||
|
| GPIO 4 | 配置清除 LED | 指示配置清除状态 |
|
||||||
|
| GPIO 8 | 自定义 GPIO | 用户自定义功能 |
|
||||||
|
| GPIO 9 | 自定义 GPIO | 用户自定义功能 |
|
||||||
|
|
||||||
|
### LED 状态指示
|
||||||
|
| 状态 | LED 行为 | 说明 |
|
||||||
|
|------|----------|------|
|
||||||
|
| 初始化/未连接 | 慢闪(1秒间隔) | 正在初始化或未连接网络 |
|
||||||
|
| 连接中 | 快闪(200ms间隔) | 正在连接 WiFi |
|
||||||
|
| 已连接 | 呼吸灯 | WiFi 已连接 |
|
||||||
|
| 断开连接 | 慢闪(1秒间隔) | WiFi 断开连接 |
|
||||||
|
|
||||||
|
## 软件架构
|
||||||
|
|
||||||
|
### 核心模块
|
||||||
|
|
||||||
|
#### 1. RadarManager(雷达管理器)
|
||||||
|
- 雷达数据采集与解析
|
||||||
|
- UART 通信管理
|
||||||
|
- 数据队列管理
|
||||||
|
- BLE 数据发送
|
||||||
|
|
||||||
|
#### 2. WiFiManager(WiFi 管理器)
|
||||||
|
- WiFi 网络扫描
|
||||||
|
- WiFi 连接管理
|
||||||
|
- 配置存储与加载
|
||||||
|
- 自动重连机制
|
||||||
|
|
||||||
|
#### 3. 主程序(main.cpp)
|
||||||
|
- 任务调度与管理
|
||||||
|
- LED 状态控制
|
||||||
|
- 按钮监控
|
||||||
|
- 系统初始化
|
||||||
|
|
||||||
|
### FreeRTOS 任务
|
||||||
|
| 任务名称 | 功能 | 优先级 |
|
||||||
|
|----------|------|--------|
|
||||||
|
| uartProcessTask | UART 数据处理 | 高 |
|
||||||
|
| radarDataTask | 雷达数据处理 | 高 |
|
||||||
|
| bleSendTask | BLE 数据发送 | 中 |
|
||||||
|
| vitalSendTask | 生命体征数据发送 | 中 |
|
||||||
|
| wifiMonitorTask | WiFi 状态监控 | 中 |
|
||||||
|
| ledControlTask | LED 状态控制 | 低 |
|
||||||
|
| bootButtonMonitorTask | 按钮监控 | 低 |
|
||||||
|
| configClearLedTask | 配置清除 LED 控制 | 低 |
|
||||||
|
|
||||||
|
## BLE API 文档
|
||||||
|
|
||||||
|
详细的 BLE API 文档请参考 [BLE_API.md](BLE_API.md)
|
||||||
|
|
||||||
|
### 主要 API 命令
|
||||||
|
- `scanWiFi` - 扫描 WiFi 网络
|
||||||
|
- `getSavedNetworks` - 获取已保存的 WiFi 网络
|
||||||
|
- `setWiFiConfig` - 配置 WiFi 网络
|
||||||
|
- `queryStatus` - 查询设备状态
|
||||||
|
- `setDeviceId` - 设置设备 ID
|
||||||
|
- `startContinuousSend` - 启动持续发送
|
||||||
|
- `stopContinuousSend` - 停止持续发送
|
||||||
|
- `queryRadarData` - 查询雷达数据
|
||||||
|
- `echo` - 回显测试
|
||||||
|
|
||||||
|
### BLE 连接参数
|
||||||
|
- **服务 UUID**: `a8c1e5c0-3d5d-4a9d-8d5e-7c8b6a4e2f1a`
|
||||||
|
- **特征值 UUID**: `beb5483e-36e1-4688-b7f5-ea07361b26a8`
|
||||||
|
- **分包大小**: 20 字节/包
|
||||||
|
- **数据格式**: JSON(UTF-8 编码)
|
||||||
|
|
||||||
|
## 数据结构
|
||||||
|
|
||||||
|
### 传感器数据(SensorData)
|
||||||
|
```cpp
|
||||||
|
typedef struct {
|
||||||
|
float breath_rate; // 呼吸率
|
||||||
|
float heart_rate; // 心率
|
||||||
|
uint8_t breath_valid; // 呼吸率有效标志
|
||||||
|
uint8_t heart_valid; // 心率有效标志
|
||||||
|
uint8_t presence; // 存在状态
|
||||||
|
uint8_t motion; // 运动状态
|
||||||
|
int heartbeat_waveform; // 心跳波形
|
||||||
|
int breathing_waveform; // 呼吸波形
|
||||||
|
uint16_t distance; // 距离
|
||||||
|
uint8_t sleep_state; // 睡眠状态
|
||||||
|
uint32_t sleep_time; // 睡眠时长
|
||||||
|
uint8_t sleep_score; // 睡眠评分
|
||||||
|
// ... 更多字段
|
||||||
|
} SensorData;
|
||||||
|
```
|
||||||
|
|
||||||
|
### 睡眠状态定义
|
||||||
|
| 值 | 状态 | 说明 |
|
||||||
|
|----|------|------|
|
||||||
|
| 0 | 清醒 | 处于清醒状态 |
|
||||||
|
| 1 | 浅睡 | 处于浅度睡眠 |
|
||||||
|
| 2 | 深睡 | 处于深度睡眠 |
|
||||||
|
| 3 | REM | 快速眼动睡眠 |
|
||||||
|
|
||||||
|
## 配置管理
|
||||||
|
|
||||||
|
### 设备 ID
|
||||||
|
- **有效范围**:1000-1999
|
||||||
|
- **存储位置**:ESP32 Flash(Preferences)
|
||||||
|
- **默认值**:0000(未设置)
|
||||||
|
|
||||||
|
### WiFi 配置
|
||||||
|
- **最大保存数量**:10 个网络
|
||||||
|
- **最小信号强度**:-200 dBm
|
||||||
|
- **连接超时**:15 秒
|
||||||
|
- **重连间隔**:2 秒
|
||||||
|
|
||||||
|
### 配置清除
|
||||||
|
- **触发方式**:启动时按住 Boot 按钮 3 秒
|
||||||
|
- **清除内容**:所有 WiFi 配置和设备 ID
|
||||||
|
- **LED 指示**:呼吸灯模式
|
||||||
|
|
||||||
|
## 编译与烧录
|
||||||
|
|
||||||
|
### 开发环境
|
||||||
|
- **IDE**:Arduino IDE 2.x
|
||||||
|
- **框架**:Arduino ESP32
|
||||||
|
- **编译器**:GCC for ESP32
|
||||||
|
|
||||||
|
### 依赖库
|
||||||
|
- `Arduino.h` - Arduino 核心库
|
||||||
|
- `WiFi.h` - WiFi 功能库
|
||||||
|
- `BLEDevice.h` - BLE 功能库
|
||||||
|
- `ArduinoJson.h` - JSON 处理库
|
||||||
|
- `Preferences.h` - Flash 存储库
|
||||||
|
- `HTTPClient.h` - HTTP 客户端库
|
||||||
|
|
||||||
|
### 编译步骤
|
||||||
|
1. 使用 platformIO ,在vscode中打开项目
|
||||||
|
3. 选择正确的串口
|
||||||
|
4. 点击"上传"按钮编译并烧录
|
||||||
|
|
||||||
|
## 使用说明
|
||||||
|
|
||||||
|
### 首次使用
|
||||||
|
1. 上电启动设备
|
||||||
|
2. 通过手机蓝牙连接设备
|
||||||
|
3. 使用小程序扫描 WiFi 网络
|
||||||
|
4. 配置 WiFi 连接信息
|
||||||
|
5. 设置设备 ID(可选)
|
||||||
|
6. 设备自动连接 WiFi 并开始上传数据
|
||||||
|
|
||||||
|
### 日常使用
|
||||||
|
1. 设备自动连接已保存的 WiFi
|
||||||
|
2. 雷达数据实时采集
|
||||||
|
3. 数据自动上传到云服务器
|
||||||
|
4. 可通过 BLE 查询设备状态和雷达数据
|
||||||
|
|
||||||
|
### 配置清除
|
||||||
|
1. 断电重启设备
|
||||||
|
2. 按住 Boot 按钮不放
|
||||||
|
3. 上电后保持按住 3 秒
|
||||||
|
4. 配置清除 LED 呼吸闪烁
|
||||||
|
5. 释放按钮,配置已清除
|
||||||
|
|
||||||
|
## 项目结构
|
||||||
|
|
||||||
|
```
|
||||||
|
Rader_Success_5/
|
||||||
|
├── src/
|
||||||
|
│ ├── main.cpp # 主程序
|
||||||
|
│ ├── radar_manager.cpp # 雷达管理器实现
|
||||||
|
│ ├── radar_manager.h # 雷达管理器头文件
|
||||||
|
│ ├── wifi_manager.cpp # WiFi 管理器实现
|
||||||
|
│ └── wifi_manager.h # WiFi 管理器头文件
|
||||||
|
├── BLE_API.md # BLE API 文档
|
||||||
|
├── 传感器数据.txt # 传感器数据说明
|
||||||
|
└── README.md # 项目说明文档
|
||||||
|
```
|
||||||
|
|
||||||
|
## 技术特点
|
||||||
|
|
||||||
|
1. **模块化设计**:雷达管理和 WiFi 管理分离,便于维护和扩展
|
||||||
|
2. **多任务处理**:使用 FreeRTOS 实现多任务并发处理
|
||||||
|
3. **队列通信**:使用 FreeRTOS 队列实现任务间数据传递
|
||||||
|
4. **流控机制**:BLE 数据发送采用流控,避免数据拥塞
|
||||||
|
5. **自动重连**:WiFi 断开后自动重连,提高系统稳定性
|
||||||
|
6. **Flash 存储**:使用 Preferences 持久化存储配置信息
|
||||||
|
7. **LED 状态指示**:直观的 LED 状态显示,便于用户了解设备状态
|
||||||
|
|
||||||
|
## 注意事项
|
||||||
|
|
||||||
|
1. **WiFi 限制**:仅支持 2.4GHz WiFi 网络
|
||||||
|
2. **BLE 连接**:一次只能连接一个 BLE 客户端
|
||||||
|
3. **数据上传**:需要确保 WiFi 连接正常才能上传数据
|
||||||
|
4. **设备 ID**:必须在有效范围内(1000-1999)
|
||||||
|
5. **配置清除**:清除配置后需要重新配置 WiFi 和设备 ID
|
||||||
|
6. **雷达安装**:建议安装在床铺正上方,距离 1-2 米
|
||||||
|
|
||||||
|
## 版本历史
|
||||||
|
|
||||||
|
| 版本 | 日期 | 说明 |
|
||||||
|
|------|------|------|
|
||||||
|
| 5.0 | 2025-02-28 | 第5版,重构代码结构,添加雷达和 WiFi 管理器模块 |
|
||||||
|
|
||||||
|
## 许可证
|
||||||
|
|
||||||
|
本项目仅供学习和研究使用。
|
||||||
|
|
||||||
|
## 联系方式
|
||||||
|
|
||||||
|
如有问题或建议,请联系项目维护者。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
**项目地址**:http://lmhrt.cn:6771/ming/Rader_Success_5.git
|
||||||
37
include/README
Normal file
37
include/README
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
|
||||||
|
This directory is intended for project header files.
|
||||||
|
|
||||||
|
A header file is a file containing C declarations and macro definitions
|
||||||
|
to be shared between several project source files. You request the use of a
|
||||||
|
header file in your project source file (C, C++, etc) located in `src` folder
|
||||||
|
by including it, with the C preprocessing directive `#include'.
|
||||||
|
|
||||||
|
```src/main.c
|
||||||
|
|
||||||
|
#include "header.h"
|
||||||
|
|
||||||
|
int main (void)
|
||||||
|
{
|
||||||
|
...
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Including a header file produces the same results as copying the header file
|
||||||
|
into each source file that needs it. Such copying would be time-consuming
|
||||||
|
and error-prone. With a header file, the related declarations appear
|
||||||
|
in only one place. If they need to be changed, they can be changed in one
|
||||||
|
place, and programs that include the header file will automatically use the
|
||||||
|
new version when next recompiled. The header file eliminates the labor of
|
||||||
|
finding and changing all the copies as well as the risk that a failure to
|
||||||
|
find one copy will result in inconsistencies within a program.
|
||||||
|
|
||||||
|
In C, the convention is to give header files names that end with `.h'.
|
||||||
|
|
||||||
|
Read more about using header files in official GCC documentation:
|
||||||
|
|
||||||
|
* Include Syntax
|
||||||
|
* Include Operation
|
||||||
|
* Once-Only Headers
|
||||||
|
* Computed Includes
|
||||||
|
|
||||||
|
https://gcc.gnu.org/onlinedocs/cpp/Header-Files.html
|
||||||
46
lib/README
Normal file
46
lib/README
Normal file
@@ -0,0 +1,46 @@
|
|||||||
|
|
||||||
|
This directory is intended for project specific (private) libraries.
|
||||||
|
PlatformIO will compile them to static libraries and link into the executable file.
|
||||||
|
|
||||||
|
The source code of each library should be placed in a separate directory
|
||||||
|
("lib/your_library_name/[Code]").
|
||||||
|
|
||||||
|
For example, see the structure of the following example libraries `Foo` and `Bar`:
|
||||||
|
|
||||||
|
|--lib
|
||||||
|
| |
|
||||||
|
| |--Bar
|
||||||
|
| | |--docs
|
||||||
|
| | |--examples
|
||||||
|
| | |--src
|
||||||
|
| | |- Bar.c
|
||||||
|
| | |- Bar.h
|
||||||
|
| | |- library.json (optional. for custom build options, etc) https://docs.platformio.org/page/librarymanager/config.html
|
||||||
|
| |
|
||||||
|
| |--Foo
|
||||||
|
| | |- Foo.c
|
||||||
|
| | |- Foo.h
|
||||||
|
| |
|
||||||
|
| |- README --> THIS FILE
|
||||||
|
|
|
||||||
|
|- platformio.ini
|
||||||
|
|--src
|
||||||
|
|- main.c
|
||||||
|
|
||||||
|
Example contents of `src/main.c` using Foo and Bar:
|
||||||
|
```
|
||||||
|
#include <Foo.h>
|
||||||
|
#include <Bar.h>
|
||||||
|
|
||||||
|
int main (void)
|
||||||
|
{
|
||||||
|
...
|
||||||
|
}
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
The PlatformIO Library Dependency Finder will find automatically dependent
|
||||||
|
libraries by scanning project source files.
|
||||||
|
|
||||||
|
More information about PlatformIO Library Dependency Finder
|
||||||
|
- https://docs.platformio.org/page/librarymanager/ldf.html
|
||||||
17
platformio.ini
Normal file
17
platformio.ini
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
; PlatformIO Project Configuration File
|
||||||
|
;
|
||||||
|
; Build options: build flags, source filter
|
||||||
|
; Upload options: custom upload port, speed and extra flags
|
||||||
|
; Library options: dependencies, extra library storages
|
||||||
|
; Advanced options: extra scripting
|
||||||
|
;
|
||||||
|
; Please visit documentation for the other options and examples
|
||||||
|
; https://docs.platformio.org/page/projectconf.html
|
||||||
|
|
||||||
|
[env:freenove_esp32_s3_wroom]
|
||||||
|
platform = espressif32
|
||||||
|
board = freenove_esp32_s3_wroom
|
||||||
|
framework = arduino
|
||||||
|
lib_deps =
|
||||||
|
bblanchon/ArduinoJson@^7.4.2
|
||||||
|
emelianov/modbus-esp8266@^4.1.0
|
||||||
620
src/main.cpp
Normal file
620
src/main.cpp
Normal file
@@ -0,0 +1,620 @@
|
|||||||
|
#include <Arduino.h>
|
||||||
|
#include <WiFi.h>
|
||||||
|
#include <esp_task_wdt.h>
|
||||||
|
#include <ArduinoJson.h>
|
||||||
|
#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; // 清除配置持续时间(毫秒)
|
||||||
|
|
||||||
|
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("🌅 启动时发送睡眠数据到数据库");
|
||||||
|
sendSleepDataToInfluxDB();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @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();
|
||||||
|
}
|
||||||
|
|
||||||
2582
src/main_backup.cpp.bak
Normal file
2582
src/main_backup.cpp.bak
Normal file
File diff suppressed because it is too large
Load Diff
1727
src/radar_manager.cpp
Normal file
1727
src/radar_manager.cpp
Normal file
File diff suppressed because it is too large
Load Diff
235
src/radar_manager.h
Normal file
235
src/radar_manager.h
Normal file
@@ -0,0 +1,235 @@
|
|||||||
|
#ifndef RADAR_MANAGER_H
|
||||||
|
#define RADAR_MANAGER_H
|
||||||
|
|
||||||
|
#include <Arduino.h>
|
||||||
|
#include <ArduinoJson.h>
|
||||||
|
#include <BLEDevice.h>
|
||||||
|
#include <BLEServer.h>
|
||||||
|
#include <BLEUtils.h>
|
||||||
|
#include <BLE2902.h>
|
||||||
|
#include <HTTPClient.h>
|
||||||
|
#include <freertos/FreeRTOS.h>
|
||||||
|
#include <freertos/task.h>
|
||||||
|
#include <freertos/queue.h>
|
||||||
|
#include <Preferences.h>
|
||||||
|
|
||||||
|
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 TASK_STACK_SIZE 8192 // 任务堆栈大小
|
||||||
|
|
||||||
|
#define FRAME_HEADER1 0x53 // 帧头字节1
|
||||||
|
#define FRAME_HEADER2 0x59 // 帧头字节2
|
||||||
|
#define FRAME_TAIL1 0x54 // 帧尾字节1
|
||||||
|
#define FRAME_TAIL2 0x43 // 帧尾字节2
|
||||||
|
|
||||||
|
// 控制字定义
|
||||||
|
#define CTRL_PRESENCE 0x80 // 人体存在检测
|
||||||
|
#define CTRL_BREATH 0x81 // 呼吸检测
|
||||||
|
#define CTRL_SLEEP 0x84 // 睡眠监测
|
||||||
|
#define CTRL_HEARTRATE 0x85 // 心率监测
|
||||||
|
|
||||||
|
// 命令字定义
|
||||||
|
#define CMD_REPORT 0x80 // 主动上报
|
||||||
|
#define CMD_QUERY 0x81 // 查询命令
|
||||||
|
#define CMD_SET 0x82 // 设置命令
|
||||||
|
|
||||||
|
// 定义R60ABD1数据结构
|
||||||
|
typedef struct {
|
||||||
|
uint8_t present; // 有人/无人状态 (DP1)
|
||||||
|
uint16_t distance; // 人体距离 (DP3) 单位cm
|
||||||
|
uint8_t heartRate; // 心率 (DP6) 单位BPM
|
||||||
|
uint8_t breathRate; // 呼吸率 (DP8) 单位次/分钟
|
||||||
|
uint8_t heartWave; // 心率波形 (DP7) 数值+128
|
||||||
|
uint8_t breathWave; // 呼吸波形 (DP10) 数值+128
|
||||||
|
uint8_t sleepState; // 睡眠状态 (DP12)
|
||||||
|
uint32_t sleepTime; // 睡眠时长 (DP13) 单位秒
|
||||||
|
uint8_t sleepScore; // 睡眠质量评分 (DP14)
|
||||||
|
uint8_t bedEntry; // 入床/离床状态 (DP11)
|
||||||
|
uint8_t abnormal; // 异常状态 (DP18)
|
||||||
|
} R60ABD1Data; // R60ABD1雷达数据结构体
|
||||||
|
|
||||||
|
typedef struct { // 传感器数据结构体
|
||||||
|
float breath_rate; // 呼吸率
|
||||||
|
float heart_rate; // 心率
|
||||||
|
uint8_t breath_valid; // 呼吸率有效标志
|
||||||
|
uint8_t heart_valid; // 心率有效标志
|
||||||
|
uint8_t presence; // 存在状态
|
||||||
|
uint8_t motion; // 运动状态
|
||||||
|
int heartbeat_waveform; // 心跳波形
|
||||||
|
int breathing_waveform; // 呼吸波形
|
||||||
|
uint16_t distance; // 距离
|
||||||
|
uint8_t body_movement; // 身体运动
|
||||||
|
uint8_t breath_status; // 呼吸状态
|
||||||
|
uint8_t sleep_state; // 睡眠状态
|
||||||
|
uint32_t sleep_time; // 睡眠时长
|
||||||
|
uint8_t sleep_score; // 睡眠评分
|
||||||
|
uint8_t sleep_grade; // 睡眠等级
|
||||||
|
uint8_t bed_entry; // 入床状态
|
||||||
|
uint8_t abnormal_state; // 异常状态
|
||||||
|
uint8_t avg_heart_rate; // 平均心率
|
||||||
|
uint8_t avg_breath_rate; // 平均呼吸率
|
||||||
|
uint8_t turn_count; // 翻身次数
|
||||||
|
uint8_t large_move_ratio; // 大幅运动比例
|
||||||
|
uint8_t small_move_ratio; // 小幅运动比例
|
||||||
|
int16_t pos_x; // X坐标
|
||||||
|
int16_t pos_y; // Y坐标
|
||||||
|
int16_t pos_z; // Z坐标
|
||||||
|
int8_t breath_waveform[5]; // 呼吸波形数组
|
||||||
|
int8_t heart_waveform[5]; // 心跳波形数组
|
||||||
|
uint16_t deep_sleep_time; // 深度睡眠时长
|
||||||
|
uint16_t light_sleep_time; // 浅度睡眠时长
|
||||||
|
uint16_t awake_time; // 清醒时长
|
||||||
|
uint16_t sleep_total_time; // 总睡眠时长
|
||||||
|
uint8_t deep_sleep_ratio; // 深度睡眠比例
|
||||||
|
uint8_t light_sleep_ratio; // 浅度睡眠比例
|
||||||
|
uint8_t awake_ratio; // 清醒比例
|
||||||
|
uint8_t turnover_count; // 翻身计数
|
||||||
|
uint8_t struggle_alert; // 挣扎警报
|
||||||
|
uint8_t no_one_alert; // 无人警报
|
||||||
|
uint8_t bed_status; // 床状态
|
||||||
|
uint8_t bed_Out_Time; // 离床时间
|
||||||
|
uint8_t apnea_count; // 呼吸暂停次数
|
||||||
|
} SensorData; // 传感器数据结构体
|
||||||
|
|
||||||
|
typedef struct { // 相位数据结构体
|
||||||
|
int heartbeat_waveform; // 心跳波形
|
||||||
|
int breathing_waveform; // 呼吸波形
|
||||||
|
} PhaseData; // 相位数据结构体
|
||||||
|
|
||||||
|
typedef struct { // 生命体征数据结构体
|
||||||
|
float heart_rate; // 心率
|
||||||
|
float breath_rate; // 呼吸率
|
||||||
|
uint8_t presence; // 存在状态
|
||||||
|
uint8_t motion; // 运动状态
|
||||||
|
uint16_t distance; // 距离
|
||||||
|
uint8_t sleep_state; // 睡眠状态
|
||||||
|
uint8_t sleep_score; // 睡眠评分
|
||||||
|
uint8_t body_movement; // 身体运动
|
||||||
|
uint8_t breath_status; // 呼吸状态
|
||||||
|
uint32_t sleep_time; // 睡眠时长
|
||||||
|
uint8_t bed_entry; // 入床状态
|
||||||
|
uint8_t abnormal_state; // 异常状态
|
||||||
|
uint8_t avg_heart_rate; // 平均心率
|
||||||
|
uint8_t avg_breath_rate; // 平均呼吸率
|
||||||
|
uint8_t turn_count; // 翻身次数
|
||||||
|
uint8_t large_move_ratio; // 大幅运动比例
|
||||||
|
uint8_t small_move_ratio; // 小幅运动比例
|
||||||
|
int16_t pos_x; // X坐标
|
||||||
|
int16_t pos_y; // Y坐标
|
||||||
|
int16_t pos_z; // Z坐标
|
||||||
|
uint16_t deep_sleep_time; // 深度睡眠时长
|
||||||
|
uint16_t light_sleep_time; // 浅度睡眠时长
|
||||||
|
uint16_t awake_time; // 清醒时长
|
||||||
|
uint16_t sleep_total_time; // 总睡眠时长
|
||||||
|
uint8_t deep_sleep_ratio; // 深度睡眠比例
|
||||||
|
uint8_t light_sleep_ratio; // 浅度睡眠比例
|
||||||
|
uint8_t awake_ratio; // 清醒比例
|
||||||
|
uint8_t turnover_count; // 翻身计数
|
||||||
|
uint8_t struggle_alert; // 挣扎警报
|
||||||
|
uint8_t no_one_alert; // 无人警报
|
||||||
|
uint8_t bed_status; // 床状态
|
||||||
|
uint8_t apnea_count; // 呼吸暂停次数
|
||||||
|
int heartbeat_waveform; // 心跳波形
|
||||||
|
int breathing_waveform; // 呼吸波形
|
||||||
|
} VitalData; // 生命体征数据结构体
|
||||||
|
|
||||||
|
typedef struct { // 上次发送数据结构体
|
||||||
|
float heart_rate; // 心率
|
||||||
|
float breath_rate; // 呼吸率
|
||||||
|
uint8_t presence; // 存在状态
|
||||||
|
uint8_t motion; // 运动状态
|
||||||
|
uint8_t sleep_state; // 睡眠状态
|
||||||
|
} LastSentData; // 上次发送数据结构体
|
||||||
|
|
||||||
|
class BLEFlowController { // BLE流控制器类
|
||||||
|
private:
|
||||||
|
size_t maxBytesPerSecond; // 最大每秒发送字节数
|
||||||
|
size_t bytesSent; // 已发送字节数
|
||||||
|
unsigned long lastResetTime; // 上次重置时间
|
||||||
|
unsigned long lastSendTime; // 上次发送时间
|
||||||
|
|
||||||
|
public:
|
||||||
|
BLEFlowController(size_t maxBps);
|
||||||
|
bool canSend(size_t dataSize);
|
||||||
|
bool check();
|
||||||
|
void recordSend(size_t dataSize);
|
||||||
|
void reset();
|
||||||
|
};
|
||||||
|
|
||||||
|
extern SensorData sensorData; // 传感器数据
|
||||||
|
extern HardwareSerial mySerial1; // 硬件串口1
|
||||||
|
extern QueueHandle_t phaseDataQueue; // 相位数据队列
|
||||||
|
extern QueueHandle_t vitalDataQueue; // 生命体征数据队列
|
||||||
|
extern QueueHandle_t uartQueue; // UART数据队列
|
||||||
|
extern TaskHandle_t bleSendTaskHandle; // BLE发送任务句柄
|
||||||
|
extern TaskHandle_t vitalSendTaskHandle; // 生命体征发送任务句柄
|
||||||
|
extern TaskHandle_t uartProcessTaskHandle; // UART处理任务句柄
|
||||||
|
extern BLEServer* pServer; // BLE服务器指针
|
||||||
|
extern BLECharacteristic* pCharacteristic; // BLE特征值指针
|
||||||
|
extern bool deviceConnected; // 设备连接状态
|
||||||
|
extern bool oldDeviceConnected; // 旧设备连接状态
|
||||||
|
extern String receivedData; // 接收到的数据
|
||||||
|
extern String completeData; // 完整数据
|
||||||
|
extern unsigned long lastReceiveTime; // 上次接收数据时间
|
||||||
|
extern bool continuousSendEnabled; // 持续发送使能标志
|
||||||
|
extern unsigned long continuousSendInterval; // 持续发送间隔
|
||||||
|
extern BLEFlowController bleFlow; // BLE流控制器
|
||||||
|
extern unsigned long lastSensorUpdate; // 上次传感器更新时间
|
||||||
|
extern LastSentData lastSentData; // 上次发送的数据
|
||||||
|
extern unsigned long lastCheckTime; // 上次检测时间
|
||||||
|
|
||||||
|
extern uint16_t currentDeviceId; // 当前设备ID
|
||||||
|
extern Preferences preferences; // Flash存储对象
|
||||||
|
extern WiFiManager wifiManager; // WiFi管理器
|
||||||
|
|
||||||
|
void initRadarManager();
|
||||||
|
void initR60ABD1();
|
||||||
|
bool parseR60ABD1Frame(uint8_t *frame, uint16_t frameLen);
|
||||||
|
int16_t parseSignedCoordinate(uint16_t raw_value);
|
||||||
|
void sendRadarCommand(uint8_t ctrl, uint8_t cmd, uint8_t value);
|
||||||
|
void IRAM_ATTR serialRxCallback();
|
||||||
|
|
||||||
|
void bleSendTask(void *parameter);
|
||||||
|
void vitalSendTask(void *parameter);
|
||||||
|
void radarDataTask(void *parameter);
|
||||||
|
void uartProcessTask(void *parameter);
|
||||||
|
|
||||||
|
void sendDataInChunks(const String& data);
|
||||||
|
void sendJSONDataToBLE(const String& jsonData);
|
||||||
|
bool sendCustomJSONData(const String& jsonType, const String& jsonString);
|
||||||
|
void sendRadarDataToBLE();
|
||||||
|
|
||||||
|
bool processQueryRadarData(JsonDocument& doc);
|
||||||
|
bool processStartContinuousSend(JsonDocument& doc);
|
||||||
|
bool processStopContinuousSend(JsonDocument& doc);
|
||||||
|
void processBLEConfig();
|
||||||
|
|
||||||
|
bool processSetDeviceId(JsonDocument& doc);
|
||||||
|
bool processQueryStatus(JsonDocument& doc);
|
||||||
|
bool processWiFiConfigCommand(JsonDocument& doc);
|
||||||
|
bool processScanWiFi(JsonDocument& doc);
|
||||||
|
bool processGetSavedNetworks(JsonDocument& doc);
|
||||||
|
bool processEchoRequest(JsonDocument& doc);
|
||||||
|
void sendRawEchoResponse(const String& rawData);
|
||||||
|
void sendStatusToBLE();
|
||||||
|
|
||||||
|
void loadDeviceId();
|
||||||
|
void saveDeviceId();
|
||||||
|
|
||||||
|
void sendDailyDataToInfluxDB(String dailyDataLine);
|
||||||
|
void sendSleepDataToInfluxDB();
|
||||||
|
|
||||||
|
class MyServerCallbacks: public BLEServerCallbacks {
|
||||||
|
void onConnect(BLEServer* pServer);
|
||||||
|
void onDisconnect(BLEServer* pServer);
|
||||||
|
};
|
||||||
|
|
||||||
|
class MyCallbacks: public BLECharacteristicCallbacks {
|
||||||
|
void onWrite(BLECharacteristic *pCharacteristic);
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif
|
||||||
782
src/wifi_manager.cpp
Normal file
782
src/wifi_manager.cpp
Normal file
@@ -0,0 +1,782 @@
|
|||||||
|
#include "wifi_manager.h"
|
||||||
|
|
||||||
|
// 外部变量和函数声明
|
||||||
|
extern bool deviceConnected;
|
||||||
|
void sendJSONDataToBLE(const String& jsonData);
|
||||||
|
void setNetworkStatus(NetworkStatus status);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief WiFi管理器构造函数
|
||||||
|
* 初始化WiFi管理器的成员变量
|
||||||
|
*/
|
||||||
|
WiFiManager::WiFiManager() {
|
||||||
|
savedNetworkCount = 0;
|
||||||
|
currentState = WIFI_IDLE;
|
||||||
|
lastReconnectAttempt = 0;
|
||||||
|
isScanning = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 初始化WiFi管理器
|
||||||
|
* 开启Preferences存储,并加载保存的WiFi配置
|
||||||
|
*/
|
||||||
|
void WiFiManager::begin() {
|
||||||
|
preferences.begin("wifi_manager", false);
|
||||||
|
loadWiFiConfigs();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 加载保存的WiFi配置
|
||||||
|
* 从Flash中读取之前保存的WiFi网络配置
|
||||||
|
* @return 是否成功加载到配置
|
||||||
|
*/
|
||||||
|
bool WiFiManager::loadWiFiConfigs() {
|
||||||
|
savedNetworkCount = 0;
|
||||||
|
|
||||||
|
for (int i = 0; i < MAX_WIFI_NETWORKS; i++) {
|
||||||
|
String key = "wifi_" + String(i);// 构建WiFi配置键名
|
||||||
|
String configStr = preferences.getString(key.c_str(), "");// 从Preferences中读取字符串
|
||||||
|
|
||||||
|
if (configStr.length() > 0) {
|
||||||
|
JsonDocument doc;
|
||||||
|
DeserializationError error = deserializeJson(doc, configStr);// 反序列化JSON字符串
|
||||||
|
|
||||||
|
if (!error) {
|
||||||
|
const char* ssid = doc["ssid"];
|
||||||
|
const char* password = doc["password"];
|
||||||
|
|
||||||
|
if (ssid && password) {
|
||||||
|
strncpy(savedNetworks[savedNetworkCount].ssid, ssid, 31);// 保存SSID
|
||||||
|
savedNetworks[savedNetworkCount].ssid[31] = '\0';
|
||||||
|
strncpy(savedNetworks[savedNetworkCount].password, password, 63);
|
||||||
|
savedNetworks[savedNetworkCount].password[63] = '\0';
|
||||||
|
savedNetworkCount++;
|
||||||
|
|
||||||
|
Serial.printf("📶 加载WiFi配置 %d: %s\n", savedNetworkCount, ssid);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Serial.printf("✅ 共加载 %d 个WiFi配置\n", savedNetworkCount);
|
||||||
|
return savedNetworkCount > 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 保存WiFi配置
|
||||||
|
* 将WiFi网络配置保存到Flash中
|
||||||
|
* @param ssid WiFi网络名称
|
||||||
|
* @param password WiFi网络密码
|
||||||
|
* @return 是否保存成功
|
||||||
|
*/
|
||||||
|
bool WiFiManager::saveWiFiConfig(const char* ssid, const char* password) {
|
||||||
|
// 检查是否已存在该网络配置
|
||||||
|
for (int i = 0; i < savedNetworkCount; i++) {
|
||||||
|
// 已存在相同的SSID
|
||||||
|
if (strcmp(savedNetworks[i].ssid, ssid) == 0) {
|
||||||
|
// 更新现有配置
|
||||||
|
strncpy(savedNetworks[i].password, password, 63);// 更新密码
|
||||||
|
savedNetworks[i].password[63] = '\0';
|
||||||
|
|
||||||
|
JsonDocument doc;
|
||||||
|
doc["ssid"] = ssid;
|
||||||
|
doc["password"] = password;
|
||||||
|
String configStr;
|
||||||
|
serializeJson(doc, configStr);// 序列化JSON文档
|
||||||
|
|
||||||
|
String key = "wifi_" + String(i);
|
||||||
|
preferences.putString(key.c_str(), configStr);// 保存到Preferences
|
||||||
|
|
||||||
|
Serial.printf("🔄 更新WiFi配置: %s\n", ssid);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 未找到相同的SSID,添加新的配置
|
||||||
|
if (savedNetworkCount < MAX_WIFI_NETWORKS) {
|
||||||
|
strncpy(savedNetworks[savedNetworkCount].ssid, ssid, 31);// 保存SSID
|
||||||
|
savedNetworks[savedNetworkCount].ssid[31] = '\0';
|
||||||
|
strncpy(savedNetworks[savedNetworkCount].password, password, 63);// 保存密码
|
||||||
|
savedNetworks[savedNetworkCount].password[63] = '\0';
|
||||||
|
|
||||||
|
JsonDocument doc;
|
||||||
|
doc["ssid"] = ssid;
|
||||||
|
doc["password"] = password;
|
||||||
|
String configStr;
|
||||||
|
serializeJson(doc, configStr);
|
||||||
|
|
||||||
|
String key = "wifi_" + String(savedNetworkCount);
|
||||||
|
preferences.putString(key.c_str(), configStr);
|
||||||
|
savedNetworkCount++;
|
||||||
|
|
||||||
|
Serial.printf("➕ 新增WiFi配置: %s (总计: %d)\n", ssid, savedNetworkCount);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
Serial.println("❌ WiFi配置已满,无法添加");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 连接到指定WiFi网络
|
||||||
|
* 尝试连接到给定的WiFi网络
|
||||||
|
* @param ssid WiFi网络名称
|
||||||
|
* @param password WiFi网络密码
|
||||||
|
* @return 是否连接成功
|
||||||
|
*/
|
||||||
|
bool WiFiManager::connectToNetwork(const char* ssid, const char* password) {
|
||||||
|
Serial.printf("🌐 [WiFi] 尝试连接到 SSID: %s\n", ssid);
|
||||||
|
|
||||||
|
currentState = WIFI_CONNECTING;
|
||||||
|
setNetworkStatus(NET_CONNECTING);
|
||||||
|
|
||||||
|
WiFi.mode(WIFI_STA);
|
||||||
|
vTaskDelay(200 / portTICK_PERIOD_MS);
|
||||||
|
WiFi.begin(ssid, password);
|
||||||
|
|
||||||
|
unsigned long startTime = millis();
|
||||||
|
unsigned long lastStatusPrint = 0;
|
||||||
|
|
||||||
|
while (WiFi.status() != WL_CONNECTED && (millis() - startTime) < WIFI_CONNECT_TIMEOUT) {
|
||||||
|
if (millis() - lastStatusPrint >= 500) {
|
||||||
|
Serial.printf("[WiFi] 连接中,状态: %d\n", WiFi.status());
|
||||||
|
lastStatusPrint = millis();
|
||||||
|
}
|
||||||
|
yield();
|
||||||
|
vTaskDelay(50 / portTICK_PERIOD_MS);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (WiFi.status() == WL_CONNECTED) {
|
||||||
|
Serial.println("✅ [WiFi] 连接成功!");
|
||||||
|
Serial.printf("🌐 IP地址: %s\n", WiFi.localIP().toString().c_str());
|
||||||
|
Serial.printf("🔒 信号强度: %d dBm\n", WiFi.RSSI());
|
||||||
|
|
||||||
|
currentState = WIFI_CONNECTED;
|
||||||
|
setNetworkStatus(NET_CONNECTED);
|
||||||
|
|
||||||
|
// 向蓝牙发送当前连接的WiFi配置信息
|
||||||
|
if (deviceConnected) {
|
||||||
|
JsonDocument doc;
|
||||||
|
doc["type"] = "wifiConnected";
|
||||||
|
doc["success"] = true;
|
||||||
|
doc["ssid"] = ssid;
|
||||||
|
doc["password"] = password;
|
||||||
|
doc["ip"] = WiFi.localIP().toString();
|
||||||
|
doc["rssi"] = WiFi.RSSI();
|
||||||
|
|
||||||
|
String jsonStr;
|
||||||
|
serializeJson(doc, jsonStr);
|
||||||
|
sendJSONDataToBLE(jsonStr);
|
||||||
|
|
||||||
|
Serial.printf("📱 [BLE] 发送WiFi连接信息: %s\n", jsonStr.c_str());
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
} else {
|
||||||
|
Serial.println("❌ [WiFi] 连接超时");
|
||||||
|
currentState = WIFI_DISCONNECTED;
|
||||||
|
setNetworkStatus(NET_DISCONNECTED);
|
||||||
|
|
||||||
|
// 向蓝牙发送连接失败信息
|
||||||
|
if (deviceConnected) {
|
||||||
|
JsonDocument doc;
|
||||||
|
doc["type"] = "wifiConnected";
|
||||||
|
doc["success"] = false;
|
||||||
|
doc["ssid"] = ssid;
|
||||||
|
doc["message"] = "WiFi连接失败,请检查密码是否正确";
|
||||||
|
|
||||||
|
String jsonStr;
|
||||||
|
serializeJson(doc, jsonStr);
|
||||||
|
sendJSONDataToBLE(jsonStr);
|
||||||
|
|
||||||
|
Serial.printf("📱 [BLE] 发送WiFi连接失败信息: %s\n", jsonStr.c_str());
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 扫描并匹配WiFi网络
|
||||||
|
* 扫描附近的WiFi网络,并尝试匹配已保存的配置
|
||||||
|
* 如果找到多个匹配的网络,按信号强度从强到弱依次尝试连接
|
||||||
|
* @return 是否成功连接到匹配的网络
|
||||||
|
*/
|
||||||
|
bool WiFiManager::scanAndMatchNetworks() {
|
||||||
|
if (isScanning) {
|
||||||
|
Serial.println("⏳ [WiFi] 正在扫描中,等待扫描完成...");
|
||||||
|
int waitCount = 0;
|
||||||
|
while (isScanning && waitCount < 50) {
|
||||||
|
vTaskDelay(100 / portTICK_PERIOD_MS);
|
||||||
|
waitCount++;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isScanning) {
|
||||||
|
Serial.println("⚠️ [WiFi] 等待超时,跳过本次扫描");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
Serial.println("✅ [WiFi] 扫描已完成,开始新的扫描");
|
||||||
|
}
|
||||||
|
|
||||||
|
Serial.println("🔍 [WiFi] 开始扫描WiFi网络...");
|
||||||
|
currentState = WIFI_SCANNING;
|
||||||
|
isScanning = true;
|
||||||
|
|
||||||
|
if (WiFi.status() == WL_CONNECTED) {
|
||||||
|
Serial.println("📶 WiFi已连接,断开后扫描");
|
||||||
|
WiFi.disconnect(false);
|
||||||
|
vTaskDelay(200 / portTICK_PERIOD_MS);
|
||||||
|
}
|
||||||
|
|
||||||
|
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网络或扫描失败");
|
||||||
|
currentState = WIFI_DISCONNECTED;
|
||||||
|
isScanning = false;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 收集所有匹配的、信号强度符合要求的网络
|
||||||
|
struct CandidateNetwork {
|
||||||
|
const char* ssid;
|
||||||
|
const char* password;
|
||||||
|
int rssi;
|
||||||
|
};
|
||||||
|
|
||||||
|
CandidateNetwork availableNetworks[MAX_WIFI_NETWORKS];
|
||||||
|
int availableCount = 0;
|
||||||
|
|
||||||
|
// 遍历已保存的网络,寻找匹配的网络
|
||||||
|
for (int i = 0; i < savedNetworkCount; i++) {
|
||||||
|
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) {
|
||||||
|
// 添加到可用网络列表
|
||||||
|
if (availableCount < MAX_WIFI_NETWORKS) {
|
||||||
|
availableNetworks[availableCount].ssid = savedNetworks[i].ssid;
|
||||||
|
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");
|
||||||
|
}
|
||||||
|
break; // 找到匹配后跳出内层循环
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 如果没有找到任何可用网络
|
||||||
|
if (availableCount == 0) {
|
||||||
|
Serial.println("❌ 未找到匹配的WiFi网络或信号过弱");
|
||||||
|
WiFi.scanDelete();
|
||||||
|
currentState = WIFI_DISCONNECTED;
|
||||||
|
isScanning = false;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 按信号强度从强到弱排序(冒泡排序)
|
||||||
|
for (int i = 0; i < availableCount - 1; i++) {
|
||||||
|
for (int j = 0; j < availableCount - i - 1; j++) {
|
||||||
|
if (availableNetworks[j].rssi < availableNetworks[j + 1].rssi) {
|
||||||
|
CandidateNetwork temp = availableNetworks[j];
|
||||||
|
availableNetworks[j] = availableNetworks[j + 1];
|
||||||
|
availableNetworks[j + 1] = temp;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Serial.printf("📋 找到 %d 个可用网络,按信号强度排序:\n", availableCount);
|
||||||
|
for (int i = 0; i < availableCount; i++) {
|
||||||
|
Serial.printf(" %d. %s (信号: %d dBm)\n", i + 1,
|
||||||
|
availableNetworks[i].ssid, availableNetworks[i].rssi);
|
||||||
|
}
|
||||||
|
|
||||||
|
WiFi.scanDelete();
|
||||||
|
|
||||||
|
// 依次尝试连接所有可用网络
|
||||||
|
for (int i = 0; i < availableCount; i++) {
|
||||||
|
Serial.printf("🔄 [%d/%d] 尝试连接: %s (信号: %d dBm)\n",
|
||||||
|
i + 1, availableCount,
|
||||||
|
availableNetworks[i].ssid,
|
||||||
|
availableNetworks[i].rssi);
|
||||||
|
|
||||||
|
vTaskDelay(300 / portTICK_PERIOD_MS);
|
||||||
|
|
||||||
|
if (connectToNetwork(availableNetworks[i].ssid, availableNetworks[i].password)) {
|
||||||
|
Serial.printf("✅ 成功连接到: %s\n", availableNetworks[i].ssid);
|
||||||
|
isScanning = false;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 连接失败,继续尝试下一个
|
||||||
|
Serial.printf("❌ %s 连接失败\n", availableNetworks[i].ssid);
|
||||||
|
|
||||||
|
// 如果不是最后一个,准备尝试下一个
|
||||||
|
if (i < availableCount - 1) {
|
||||||
|
Serial.println("➡️ 尝试下一个网络...");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Serial.println("❌ 所有可用网络均连接失败");
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 初始化WiFi连接
|
||||||
|
* 启动WiFi初始化过程,尝试连接到已保存的网络
|
||||||
|
* @return 是否初始化成功
|
||||||
|
*/
|
||||||
|
bool WiFiManager::initializeWiFi() {
|
||||||
|
Serial.println("🚀 [WiFi] 初始化WiFi连接...");
|
||||||
|
|
||||||
|
if (savedNetworkCount == 0) {
|
||||||
|
Serial.println("⚠️ 未保存的WiFi配置");
|
||||||
|
currentState = WIFI_IDLE;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (scanAndMatchNetworks()) {
|
||||||
|
Serial.println("✅ WiFi初始化成功");
|
||||||
|
return true;
|
||||||
|
} else {
|
||||||
|
Serial.println("❌ WiFi初始化失败");
|
||||||
|
currentState = WIFI_DISCONNECTED;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 扫描WiFi网络并发送结果
|
||||||
|
* 扫描附近的WiFi网络,过滤信号弱的网络,将结果通过BLE发送给客户端
|
||||||
|
*/
|
||||||
|
void WiFiManager::scanAndSendResults() {
|
||||||
|
if (isScanning) {
|
||||||
|
Serial.println("⏳ [WiFi] 正在扫描中,等待扫描完成...");
|
||||||
|
int waitCount = 0;
|
||||||
|
while (isScanning && waitCount < 50) {
|
||||||
|
vTaskDelay(100 / portTICK_PERIOD_MS);
|
||||||
|
waitCount++;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isScanning) {
|
||||||
|
Serial.println("⚠️ [WiFi] 等待超时,跳过本次扫描");
|
||||||
|
if (deviceConnected) {
|
||||||
|
String errorMsg = String("{\"type\":\"scanWiFiResult\",\"success\":false,\"message\":\"等待扫描超时,请稍后再试\",\"networks\":[],\"count\":0}");
|
||||||
|
sendJSONDataToBLE(errorMsg);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Serial.println("✅ [WiFi] 扫描已完成,开始新的扫描");
|
||||||
|
}
|
||||||
|
|
||||||
|
Serial.println("📱 [BLE-WiFi] 开始WiFi扫描...");
|
||||||
|
isScanning = true;
|
||||||
|
|
||||||
|
// 确保完全断开WiFi
|
||||||
|
if (WiFi.status() == WL_CONNECTED) {
|
||||||
|
Serial.println("📶 WiFi已连接,断开后扫描");
|
||||||
|
WiFi.disconnect(true);
|
||||||
|
vTaskDelay(500 / portTICK_PERIOD_MS);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 更完整的WiFi初始化
|
||||||
|
WiFi.mode(WIFI_OFF);
|
||||||
|
vTaskDelay(100 / portTICK_PERIOD_MS);
|
||||||
|
|
||||||
|
WiFi.mode(WIFI_STA);
|
||||||
|
vTaskDelay(500 / portTICK_PERIOD_MS);
|
||||||
|
|
||||||
|
// 尝试多次扫描,提高成功率
|
||||||
|
int n = 0;
|
||||||
|
int retryCount = 3;
|
||||||
|
|
||||||
|
while (n <= 0 && retryCount > 0) {
|
||||||
|
Serial.printf("🔍 扫描WiFi网络 (尝试 %d/3)...\n", 4 - retryCount);
|
||||||
|
// 使用更可靠的扫描参数:被动扫描,包含隐藏SSID,更长的扫描时间
|
||||||
|
n = WiFi.scanNetworks(false, true, true, 5000); // 5000ms扫描时间
|
||||||
|
|
||||||
|
if (n <= 0) {
|
||||||
|
Serial.printf("❌ 扫描失败,返回值: %d, 剩余重试: %d\n", n, retryCount - 1);
|
||||||
|
retryCount--;
|
||||||
|
vTaskDelay(1000 / portTICK_PERIOD_MS);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Serial.printf("🔍 最终扫描到 %d 个WiFi网络\n", n);
|
||||||
|
|
||||||
|
if (n <= 0) {
|
||||||
|
Serial.println("❌ 未扫描到任何WiFi网络或扫描失败");
|
||||||
|
isScanning = false;
|
||||||
|
if (deviceConnected) {
|
||||||
|
String errorMsg = String("{\"type\":\"scanWiFiResult\",\"success\":false,\"message\":\"未扫描到任何WiFi网络或扫描失败\",\"networks\":[],\"count\":0}");
|
||||||
|
sendJSONDataToBLE(errorMsg);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 构建WiFi网络列表的JSON数据
|
||||||
|
String wifiList = String("{\"type\":\"scanWiFiResult\",\"success\":true,\"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(",\"encryption\":");
|
||||||
|
|
||||||
|
// 根据加密类型添加相应的描述
|
||||||
|
switch (WiFi.encryptionType(i)) {
|
||||||
|
case WIFI_AUTH_OPEN:
|
||||||
|
wifiList += String("\"open\"");
|
||||||
|
break;
|
||||||
|
case WIFI_AUTH_WEP:
|
||||||
|
wifiList += String("\"WEP\"");
|
||||||
|
break;
|
||||||
|
case WIFI_AUTH_WPA_PSK:
|
||||||
|
wifiList += String("\"WPA\"");
|
||||||
|
break;
|
||||||
|
case WIFI_AUTH_WPA2_PSK:
|
||||||
|
wifiList += String("\"WPA2\"");
|
||||||
|
break;
|
||||||
|
case WIFI_AUTH_WPA_WPA2_PSK:
|
||||||
|
wifiList += String("\"WPA/WPA2\"");
|
||||||
|
break;
|
||||||
|
case WIFI_AUTH_WPA2_ENTERPRISE:
|
||||||
|
wifiList += String("\"WPA2-EAP\"");
|
||||||
|
break;
|
||||||
|
case WIFI_AUTH_WPA3_PSK:
|
||||||
|
wifiList += String("\"WPA3\"");
|
||||||
|
break;
|
||||||
|
case WIFI_AUTH_WPA2_WPA3_PSK:
|
||||||
|
wifiList += String("\"WPA2/WPA3\"");
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
wifiList += String("\"unknown\"");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
wifiList += "}";
|
||||||
|
first = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
wifiList += "]}";
|
||||||
|
|
||||||
|
Serial.printf("✅ 发送WiFi扫描结果,包含 %d 个可用网络\n", first ? 0 : n);
|
||||||
|
|
||||||
|
WiFi.scanDelete();
|
||||||
|
isScanning = false;
|
||||||
|
|
||||||
|
if (deviceConnected) {
|
||||||
|
sendJSONDataToBLE(wifiList);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 开始配网模式
|
||||||
|
* 进入配网模式,扫描WiFi网络并发送结果给客户端
|
||||||
|
* @return 是否成功进入配网模式
|
||||||
|
*/
|
||||||
|
bool WiFiManager::startConfiguration() {
|
||||||
|
Serial.println("⚙️ [WiFi] 开始配网模式...");
|
||||||
|
currentState = WIFI_CONFIGURING;
|
||||||
|
scanAndSendResults();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 处理配网数据
|
||||||
|
* 处理从客户端收到的WiFi配网信息,先扫描WiFi是否有匹配的网络,再尝试连接并保存
|
||||||
|
* @param ssid WiFi网络名称
|
||||||
|
* @param password WiFi网络密码
|
||||||
|
* @return 是否配置成功
|
||||||
|
*/
|
||||||
|
bool WiFiManager::handleConfigurationData(const char* ssid, const char* password) {
|
||||||
|
Serial.printf("📱 [BLE-WiFi] 收到配网信息: SSID='%s'\n", ssid);
|
||||||
|
|
||||||
|
if (ssid == nullptr || password == nullptr || strlen(ssid) == 0) {
|
||||||
|
Serial.println("❌ 配网参数无效");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isScanning) {
|
||||||
|
Serial.println("⏳ [WiFi] 正在扫描中,等待扫描完成...");
|
||||||
|
int waitCount = 0;
|
||||||
|
while (isScanning && waitCount < 50) {
|
||||||
|
vTaskDelay(100 / portTICK_PERIOD_MS);
|
||||||
|
waitCount++;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isScanning) {
|
||||||
|
Serial.println("⚠️ [WiFi] 等待超时");
|
||||||
|
if (deviceConnected) {
|
||||||
|
String errorMsg = String("{\"type\":\"wifiConfigResult\",\"success\":false,\"message\":\"等待扫描超时,请稍后再试\"}");
|
||||||
|
sendJSONDataToBLE(errorMsg);
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
Serial.println("✅ [WiFi] 扫描已完成,开始新的扫描");
|
||||||
|
}
|
||||||
|
|
||||||
|
// 先扫描WiFi网络,检查是否存在匹配的网络
|
||||||
|
Serial.println("🔍 [WiFi] 扫描WiFi网络,检查是否存在匹配的网络...");
|
||||||
|
currentState = WIFI_SCANNING;
|
||||||
|
isScanning = true;
|
||||||
|
|
||||||
|
int n = WiFi.scanNetworks();
|
||||||
|
Serial.printf("🔍 扫描到 %d 个WiFi网络\n", n);
|
||||||
|
|
||||||
|
if (n == 0) {
|
||||||
|
Serial.println("❌ 未扫描到任何WiFi网络");
|
||||||
|
WiFi.scanDelete();
|
||||||
|
isScanning = false;
|
||||||
|
currentState = WIFI_DISCONNECTED;
|
||||||
|
|
||||||
|
if (deviceConnected) {
|
||||||
|
String resultMsg = String("{\"type\":\"wifiConfigResult\",\"success\":false,\"message\":\"未扫描到任何WiFi网络,请检查设备位置\"}");
|
||||||
|
sendJSONDataToBLE(resultMsg);
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool networkFound = false;
|
||||||
|
bool signalTooWeak = false;
|
||||||
|
int foundRssi = 0;
|
||||||
|
|
||||||
|
for (int i = 0; i < n; i++) {
|
||||||
|
if (WiFi.SSID(i) == String(ssid)) {
|
||||||
|
foundRssi = WiFi.RSSI(i);
|
||||||
|
Serial.printf("📶 找到匹配网络: %s, 信号: %d dBm\n", ssid, foundRssi);
|
||||||
|
|
||||||
|
if (foundRssi >= MIN_RSSI_THRESHOLD) {
|
||||||
|
Serial.printf("✅ 信号强度符合要求,准备连接...\n");
|
||||||
|
networkFound = true;
|
||||||
|
break;
|
||||||
|
} else {
|
||||||
|
Serial.printf("⚠️ 信号强度过低,跳过\n");
|
||||||
|
signalTooWeak = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!networkFound) {
|
||||||
|
String errorMsg;
|
||||||
|
|
||||||
|
if (signalTooWeak) {
|
||||||
|
errorMsg = String("{\"type\":\"wifiConfigResult\",\"success\":false,\"message\":\"目标WiFi信号过弱,请将设备靠近路由器\"}");
|
||||||
|
Serial.printf("❌ 目标WiFi信号过弱: %d dBm (阈值: %d dBm)\n", foundRssi, MIN_RSSI_THRESHOLD);
|
||||||
|
} else {
|
||||||
|
errorMsg = String("{\"type\":\"wifiConfigResult\",\"success\":false,\"message\":\"未找到目标WiFi网络,请检查WiFi名称是否正确\"}");
|
||||||
|
Serial.println("❌ 未找到目标WiFi网络");
|
||||||
|
}
|
||||||
|
|
||||||
|
WiFi.scanDelete();
|
||||||
|
isScanning = false;
|
||||||
|
currentState = WIFI_DISCONNECTED;
|
||||||
|
|
||||||
|
if (deviceConnected) {
|
||||||
|
sendJSONDataToBLE(errorMsg);
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
WiFi.scanDelete();
|
||||||
|
isScanning = false;
|
||||||
|
vTaskDelay(300 / portTICK_PERIOD_MS);
|
||||||
|
|
||||||
|
// 尝试连接到指定网络
|
||||||
|
if (connectToNetwork(ssid, password)) {
|
||||||
|
// 连接成功后保存配置
|
||||||
|
if (saveWiFiConfig(ssid, password)) {
|
||||||
|
Serial.println("✅ WiFi配置成功并已保存");
|
||||||
|
|
||||||
|
if (deviceConnected) {
|
||||||
|
String resultMsg = String("{\"type\":\"wifiConfigResult\",\"success\":true,\"message\":\"WiFi配置成功\"}");
|
||||||
|
sendJSONDataToBLE(resultMsg);
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Serial.println("❌ WiFi配置失败");
|
||||||
|
isScanning = false;
|
||||||
|
currentState = WIFI_DISCONNECTED;
|
||||||
|
|
||||||
|
if (deviceConnected) {
|
||||||
|
String resultMsg = String("{\"type\":\"wifiConfigResult\",\"success\":false,\"message\":\"WiFi配置失败,请检查密码是否正确\"}");
|
||||||
|
sendJSONDataToBLE(resultMsg);
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 处理WiFi重连
|
||||||
|
* 检查WiFi连接状态,当断开连接时尝试重连
|
||||||
|
*/
|
||||||
|
void WiFiManager::handleReconnect() {
|
||||||
|
// 检查当前是否已连接
|
||||||
|
if (currentState == WIFI_CONNECTED) {
|
||||||
|
if (WiFi.status() == WL_CONNECTED) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// 连接已断开
|
||||||
|
currentState = WIFI_DISCONNECTED;
|
||||||
|
setNetworkStatus(NET_DISCONNECTED);
|
||||||
|
Serial.println("⚠️ WiFi连接断开");
|
||||||
|
|
||||||
|
// 扫描并发送WiFi列表到蓝牙
|
||||||
|
scanAndSendResults();
|
||||||
|
}
|
||||||
|
|
||||||
|
// 处理重连逻辑
|
||||||
|
if (currentState == WIFI_DISCONNECTED) {
|
||||||
|
unsigned long currentTime = millis();
|
||||||
|
|
||||||
|
// 按照设定的间隔尝试重连
|
||||||
|
if (currentTime - lastReconnectAttempt >= WIFI_RECONNECT_INTERVAL) {
|
||||||
|
lastReconnectAttempt = currentTime;
|
||||||
|
|
||||||
|
if (scanAndMatchNetworks()) {
|
||||||
|
Serial.println("✅ WiFi重连成功");
|
||||||
|
} else {
|
||||||
|
Serial.println("❌ WiFi重连失败,2秒后重试");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 获取当前WiFi状态
|
||||||
|
* @return 当前的WiFi管理器状态
|
||||||
|
*/
|
||||||
|
WiFiManagerState WiFiManager::getState() {
|
||||||
|
return currentState;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 检查WiFi是否已连接
|
||||||
|
* @return WiFi是否已成功连接
|
||||||
|
*/
|
||||||
|
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网络
|
||||||
|
* @param ssid WiFi网络名称
|
||||||
|
* @param password WiFi网络密码
|
||||||
|
* @return 是否添加成功
|
||||||
|
*/
|
||||||
|
bool WiFiManager::addWiFiConfig(const char* ssid, const char* password) {
|
||||||
|
return saveWiFiConfig(ssid, password);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 清除所有WiFi配置
|
||||||
|
* 删除所有保存的WiFi网络配置
|
||||||
|
*/
|
||||||
|
void WiFiManager::clearAllConfigs() {
|
||||||
|
for (int i = 0; i < MAX_WIFI_NETWORKS; i++) {
|
||||||
|
String key = "wifi_" + String(i);
|
||||||
|
preferences.remove(key.c_str());
|
||||||
|
}
|
||||||
|
savedNetworkCount = 0;
|
||||||
|
Serial.println("🗑️ 已清除所有WiFi配置");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 获取已保存的网络数量
|
||||||
|
* @return 已保存的WiFi网络配置数量
|
||||||
|
*/
|
||||||
|
int WiFiManager::getSavedNetworkCount() {
|
||||||
|
return savedNetworkCount;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 获取已保存的WiFi网络列表
|
||||||
|
* 将保存的WiFi网络配置通过BLE发送给客户端
|
||||||
|
*/
|
||||||
|
void WiFiManager::getSavedNetworks() {
|
||||||
|
Serial.printf("📋 [WiFi] 获取已保存的WiFi网络列表,共 %d 个\n", savedNetworkCount);
|
||||||
|
|
||||||
|
if (savedNetworkCount == 0) {
|
||||||
|
Serial.println("⚠️ 没有保存的WiFi网络");
|
||||||
|
if (deviceConnected) {
|
||||||
|
String responseMsg = String("{\"type\":\"savedNetworks\",\"success\":true,\"count\":0,\"networks\":[]}");
|
||||||
|
sendJSONDataToBLE(responseMsg);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
String wifiList = String("{\"type\":\"savedNetworks\",\"success\":true,\"count\":") + String(savedNetworkCount) + String(",\"networks\":[");
|
||||||
|
|
||||||
|
for (int i = 0; i < savedNetworkCount; i++) {
|
||||||
|
if (i > 0) {
|
||||||
|
wifiList += ",";
|
||||||
|
}
|
||||||
|
wifiList += String("{\"ssid\":\"") + String(savedNetworks[i].ssid) + String("\"}");
|
||||||
|
}
|
||||||
|
|
||||||
|
wifiList += "]}";
|
||||||
|
|
||||||
|
Serial.printf("📤 [WiFi] 发送已保存的WiFi网络列表: %s\n", wifiList.c_str());
|
||||||
|
|
||||||
|
if (deviceConnected) {
|
||||||
|
sendJSONDataToBLE(wifiList);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 更新WiFi管理器状态
|
||||||
|
* 定期调用此函数,处理WiFi重连等状态管理
|
||||||
|
*/
|
||||||
|
void WiFiManager::update() {
|
||||||
|
handleReconnect();
|
||||||
|
}
|
||||||
122
src/wifi_manager.h
Normal file
122
src/wifi_manager.h
Normal file
@@ -0,0 +1,122 @@
|
|||||||
|
#ifndef WIFI_MANAGER_H
|
||||||
|
#define WIFI_MANAGER_H
|
||||||
|
|
||||||
|
#include <Arduino.h>
|
||||||
|
#include <WiFi.h>
|
||||||
|
#include <Preferences.h>
|
||||||
|
#include <ArduinoJson.h>
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 最大WiFi网络配置数量
|
||||||
|
* 定义设备可以保存的WiFi网络配置的最大数量
|
||||||
|
*/
|
||||||
|
#define MAX_WIFI_NETWORKS 10
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 最小信号强度阈值
|
||||||
|
* 定义WiFi网络信号强度的最低要求,低于此值的网络会被过滤
|
||||||
|
* 单位:dBm
|
||||||
|
*/
|
||||||
|
#define MIN_RSSI_THRESHOLD -200
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief WiFi连接超时时间
|
||||||
|
* 定义WiFi连接的最大等待时间,超过此时间认为连接失败
|
||||||
|
* 单位:毫秒
|
||||||
|
*/
|
||||||
|
#define WIFI_CONNECT_TIMEOUT 3000
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief WiFi重连间隔时间
|
||||||
|
* 定义WiFi断开后,尝试重新连接的时间间隔
|
||||||
|
* 单位:毫秒
|
||||||
|
*/
|
||||||
|
#define WIFI_RECONNECT_INTERVAL 2000
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief WiFi网络信息结构
|
||||||
|
* 存储WiFi网络的详细信息,用于扫描和显示
|
||||||
|
*/
|
||||||
|
typedef struct {
|
||||||
|
char ssid[32]; // WiFi网络名称
|
||||||
|
char password[64]; // WiFi网络密码
|
||||||
|
int rssi; // 信号强度,单位:dBm
|
||||||
|
uint8_t channel; // WiFi通道
|
||||||
|
uint8_t encryption; // 加密类型
|
||||||
|
} WiFiNetworkInfo;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief WiFi配置结构
|
||||||
|
* 存储WiFi网络的配置信息,用于保存和加载
|
||||||
|
*/
|
||||||
|
typedef struct {
|
||||||
|
char ssid[32]; // WiFi网络名称
|
||||||
|
char password[64]; // WiFi网络密码
|
||||||
|
} WiFiConfig;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief WiFi管理器状态枚举
|
||||||
|
* 定义WiFi管理器的不同工作状态
|
||||||
|
*/
|
||||||
|
enum WiFiManagerState {
|
||||||
|
WIFI_IDLE, // 空闲状态
|
||||||
|
WIFI_SCANNING, // 扫描网络中
|
||||||
|
WIFI_CONNECTING, // 连接网络中
|
||||||
|
WIFI_CONNECTED, // 已连接
|
||||||
|
WIFI_DISCONNECTED, // 断开连接
|
||||||
|
WIFI_CONFIGURING // 配网模式
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 网络状态枚举
|
||||||
|
* 定义网络的不同状态,用于LED控制
|
||||||
|
*/
|
||||||
|
enum NetworkStatus {
|
||||||
|
NET_INITIAL, // 初始化/未连接 - 慢闪
|
||||||
|
NET_CONNECTING, // 连接中 - 快闪
|
||||||
|
NET_CONNECTED, // 已连接 - 呼吸灯
|
||||||
|
NET_DISCONNECTED // 断开连接 - 慢闪
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief WiFi管理器类
|
||||||
|
* 负责WiFi网络的扫描、连接、配置和管理
|
||||||
|
*/
|
||||||
|
class WiFiManager {
|
||||||
|
private:
|
||||||
|
Preferences preferences; // 用于存储WiFi配置的Preferences对象
|
||||||
|
WiFiConfig savedNetworks[MAX_WIFI_NETWORKS]; // 保存的WiFi网络配置数组
|
||||||
|
int savedNetworkCount; // 已保存的网络数量
|
||||||
|
WiFiManagerState currentState; // 当前WiFi管理器状态
|
||||||
|
unsigned long lastReconnectAttempt; // 上次尝试重连的时间
|
||||||
|
bool isScanning; // 是否正在扫描
|
||||||
|
|
||||||
|
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(); // 断开连接
|
||||||
|
|
||||||
|
void scanAndSendResults(); // 扫描并发送结果
|
||||||
|
bool addWiFiConfig(const char* ssid, const char* password); // 添加WiFi配置
|
||||||
|
void clearAllConfigs(); // 清除所有配置
|
||||||
|
int getSavedNetworkCount(); // 获取已保存的网络数量
|
||||||
|
void getSavedNetworks(); // 获取已保存的WiFi网络列表
|
||||||
|
|
||||||
|
void update(); // 更新WiFi管理器状态
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif
|
||||||
11
test/README
Normal file
11
test/README
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
|
||||||
|
This directory is intended for PlatformIO Test Runner and project tests.
|
||||||
|
|
||||||
|
Unit Testing is a software testing method by which individual units of
|
||||||
|
source code, sets of one or more MCU program modules together with associated
|
||||||
|
control data, usage procedures, and operating procedures, are tested to
|
||||||
|
determine whether they are fit for use. Unit testing finds problems early
|
||||||
|
in the development cycle.
|
||||||
|
|
||||||
|
More information about PlatformIO Unit Testing:
|
||||||
|
- https://docs.platformio.org/en/latest/advanced/unit-testing/index.html
|
||||||
82
test_json_parser.js
Normal file
82
test_json_parser.js
Normal file
@@ -0,0 +1,82 @@
|
|||||||
|
// JSON分包接收处理函数
|
||||||
|
let streamBuffer = ""
|
||||||
|
|
||||||
|
function addLog(message) {
|
||||||
|
console.log(message);
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleParsedJson(obj) {
|
||||||
|
console.log("处理解析后的JSON对象:", obj);
|
||||||
|
}
|
||||||
|
|
||||||
|
function processChunk(fragment){
|
||||||
|
addLog('进入processChunk')
|
||||||
|
streamBuffer += fragment
|
||||||
|
let braceCount = 0
|
||||||
|
let jsonStart = -1
|
||||||
|
for(let i = 0; i < streamBuffer.length; i++){
|
||||||
|
const ch = streamBuffer[i]
|
||||||
|
|
||||||
|
if(ch === "{"){
|
||||||
|
if(braceCount === 0){
|
||||||
|
jsonStart = i
|
||||||
|
}
|
||||||
|
braceCount++
|
||||||
|
}else if(ch === "}"){
|
||||||
|
braceCount--
|
||||||
|
if(braceCount === 0 && jsonStart !== -1){
|
||||||
|
const jsonStr = streamBuffer.substring(jsonStart, i + 1)
|
||||||
|
addLog("收到完整JSON:" + jsonStr)
|
||||||
|
try{
|
||||||
|
const obj = JSON.parse(jsonStr)
|
||||||
|
handleParsedJson(obj)
|
||||||
|
}catch(e){
|
||||||
|
addLog("JSON解析失败:" + e)
|
||||||
|
}
|
||||||
|
streamBuffer = streamBuffer.substring(i + 1)
|
||||||
|
i = -1
|
||||||
|
jsonStart = -1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
//缓冲区过大时清理防越界
|
||||||
|
if (streamBuffer.length > 20000) {
|
||||||
|
addLog("⚠ 清空超大缓冲区(异常保护)")
|
||||||
|
streamBuffer = ""
|
||||||
|
braceCount = 0
|
||||||
|
jsonStart = -1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 模拟分包发送JSON数据
|
||||||
|
function simulateJSONSending() {
|
||||||
|
const jsonData = {
|
||||||
|
type: "radarData",
|
||||||
|
deviceId: 1001,
|
||||||
|
timestamp: Date.now(),
|
||||||
|
presence: 1,
|
||||||
|
heartRate: 72.5,
|
||||||
|
breathRate: 16.2,
|
||||||
|
motion: 0,
|
||||||
|
rssi: -45,
|
||||||
|
heartbeatWaveform: 120,
|
||||||
|
breathingWaveform: 45,
|
||||||
|
rawSignal: -25
|
||||||
|
};
|
||||||
|
|
||||||
|
const jsonString = JSON.stringify(jsonData);
|
||||||
|
console.log("原始JSON数据:", jsonString);
|
||||||
|
|
||||||
|
// 模拟分包发送
|
||||||
|
const packetSize = 15; // 每包15个字符
|
||||||
|
for (let i = 0; i < jsonString.length; i += packetSize) {
|
||||||
|
const packet = jsonString.substring(i, Math.min(i + packetSize, jsonString.length));
|
||||||
|
console.log(`发送分包 ${Math.floor(i/packetSize)+1}:`, packet);
|
||||||
|
processChunk(packet);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 测试函数
|
||||||
|
console.log("=== 开始测试JSON分包接收处理 ===");
|
||||||
|
simulateJSONSending();
|
||||||
|
console.log("=== 测试完成 ===");
|
||||||
Reference in New Issue
Block a user