Compare commits
14 Commits
a4777d571c
...
78e81da80f
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
78e81da80f | ||
| 8aee83507d | |||
|
|
56592a01f5 | ||
|
|
bdf2be1c16 | ||
|
|
da729abb0f | ||
| 4bc342afcd | |||
| da6ca2f52e | |||
| 0a281fa564 | |||
| 1cd4b1fa7d | |||
| 0f1e543f2c | |||
| 393822de2a | |||
| 124a1107f8 | |||
|
|
f25d7d145d | ||
|
|
4391c14145 |
3
.gitignore
vendored
3
.gitignore
vendored
@@ -3,3 +3,6 @@
|
||||
.vscode/c_cpp_properties.json
|
||||
.vscode/launch.json
|
||||
.vscode/ipch
|
||||
GIT_GUIDE.md
|
||||
git_upload.bat
|
||||
.trae/
|
||||
|
||||
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 |
|
||||
244
GIT_GUIDE.md
Normal file
244
GIT_GUIDE.md
Normal file
@@ -0,0 +1,244 @@
|
||||
# Git 操作指南
|
||||
|
||||
## 快速上传脚本
|
||||
|
||||
双击运行 `git_upload.bat` 即可快速上传代码到 Git 仓库。
|
||||
|
||||
## 常用 Git 命令
|
||||
|
||||
### 1. 基础操作
|
||||
|
||||
```powershell
|
||||
# 查看当前状态
|
||||
& "C:\Program Files\Git\bin\git.exe" status
|
||||
|
||||
# 查看修改内容
|
||||
& "C:\Program Files\Git\bin\git.exe" diff
|
||||
|
||||
# 查看提交历史
|
||||
& "C:\Program Files\Git\bin\git.exe" log --oneline
|
||||
|
||||
# 查看最近 5 次提交
|
||||
& "C:\Program Files\Git\bin\git.exe" log -5
|
||||
```
|
||||
|
||||
### 2. 提交代码
|
||||
|
||||
```powershell
|
||||
# 添加所有文件
|
||||
& "C:\Program Files\Git\bin\git.exe" add -A
|
||||
|
||||
# 添加指定文件
|
||||
& "C:\Program Files\Git\bin\git.exe" add 文件名
|
||||
|
||||
# 提交更改
|
||||
& "C:\Program Files\Git\bin\git.exe" commit -m "提交信息"
|
||||
|
||||
# 添加并提交(一步完成)
|
||||
& "C:\Program Files\Git\bin\git.exe" commit -am "提交信息"
|
||||
```
|
||||
|
||||
### 3. 推送到远程仓库
|
||||
|
||||
```powershell
|
||||
# 推送到远程仓库
|
||||
& "C:\Program Files\Git\bin\git.exe" push
|
||||
|
||||
# 首次推送(设置上游分支)
|
||||
& "C:\Program Files\Git\bin\git.exe" push -u origin main
|
||||
|
||||
# 强制推送(谨慎使用)
|
||||
& "C:\Program Files\Git\bin\git.exe" push --force
|
||||
```
|
||||
|
||||
### 4. 拉取代码
|
||||
|
||||
```powershell
|
||||
# 拉取远程代码
|
||||
& "C:\Program Files\Git\bin\git.exe" pull
|
||||
|
||||
# 拉取并合并
|
||||
& "C:\Program Files\Git\bin\git.exe" pull origin main
|
||||
```
|
||||
|
||||
### 5. 分支操作
|
||||
|
||||
```powershell
|
||||
# 查看所有分支
|
||||
& "C:\Program Files\Git\bin\git.exe" branch -a
|
||||
|
||||
# 创建新分支
|
||||
& "C:\Program Files\Git\bin\git.exe" branch 分支名
|
||||
|
||||
# 切换分支
|
||||
& "C:\Program Files\Git\bin\git.exe" checkout 分支名
|
||||
|
||||
# 创建并切换到新分支
|
||||
& "C:\Program Files\Git\bin\git.exe" checkout -b 分支名
|
||||
|
||||
# 删除本地分支
|
||||
& "C:\Program Files\Git\bin\git.exe" branch -d 分支名
|
||||
|
||||
# 删除远程分支
|
||||
& "C:\Program Files\Git\bin\git.exe" push origin --delete 分支名
|
||||
```
|
||||
|
||||
### 6. 撤销操作
|
||||
|
||||
```powershell
|
||||
# 撤销工作区的修改
|
||||
& "C:\Program Files\Git\bin\git.exe" restore 文件名
|
||||
|
||||
# 撤销所有工作区的修改
|
||||
& "C:\Program Files\Git\bin\git.exe" restore .
|
||||
|
||||
# 撤销暂存区的修改
|
||||
& "C:\Program Files\Git\bin\git.exe" reset HEAD 文件名
|
||||
|
||||
# 撤销最后一次提交(保留修改)
|
||||
& "C:\Program Files\Git\bin\git.exe" reset --soft HEAD~1
|
||||
|
||||
# 撤销最后一次提交(丢弃修改)
|
||||
& "C:\Program Files\Git\bin\git.exe" reset --hard HEAD~1
|
||||
```
|
||||
|
||||
### 7. 远程仓库操作
|
||||
|
||||
```powershell
|
||||
# 查看远程仓库
|
||||
& "C:\Program Files\Git\bin\git.exe" remote -v
|
||||
|
||||
# 添加远程仓库
|
||||
& "C:\Program Files\Git\bin\git.exe" remote add origin 远程仓库地址
|
||||
|
||||
# 删除远程仓库
|
||||
& "C:\Program Files\Git\bin\git.exe" remote remove origin
|
||||
|
||||
# 修改远程仓库地址
|
||||
& "C:\Program Files\Git\bin\git.exe" remote set-url origin 新地址
|
||||
```
|
||||
|
||||
### 8. 查看文件
|
||||
|
||||
```powershell
|
||||
# 查看已跟踪的文件
|
||||
& "C:\Program Files\Git\bin\git.exe" ls-files
|
||||
|
||||
# 查看被忽略的文件
|
||||
& "C:\Program Files\Git\bin\git.exe" status --ignored
|
||||
|
||||
# 查看文件大小
|
||||
& "C:\Program Files\Git\bin\git.exe" ls-files -s
|
||||
```
|
||||
|
||||
## 完整上传流程
|
||||
|
||||
### 方式一:使用脚本(推荐)
|
||||
|
||||
```powershell
|
||||
# 双击运行 git_upload.bat
|
||||
# 按照提示输入提交信息即可
|
||||
```
|
||||
|
||||
### 方式二:手动执行命令
|
||||
|
||||
```powershell
|
||||
# 1. 查看当前状态
|
||||
& "C:\Program Files\Git\bin\git.exe" status
|
||||
|
||||
# 2. 添加所有修改的文件
|
||||
& "C:\Program Files\Git\bin\git.exe" add -A
|
||||
|
||||
# 3. 提交更改
|
||||
& "C:\Program Files\Git\bin\git.exe" commit -m "你的提交信息"
|
||||
|
||||
# 4. 推送到远程仓库
|
||||
& "C:\Program Files\Git\bin\git.exe" push
|
||||
```
|
||||
|
||||
## 项目配置信息
|
||||
|
||||
- **远程仓库地址**: `http://lmhrt.cn:6771/ming/Rader_Success_5.git`
|
||||
- **主分支**: `main`
|
||||
- **Git 路径**: `C:\Program Files\Git\bin\git.exe`
|
||||
|
||||
## 常见问题
|
||||
|
||||
### 1. 推送失败,提示需要拉取
|
||||
|
||||
```powershell
|
||||
# 先拉取远程代码
|
||||
& "C:\Program Files\Git\bin\git.exe" pull
|
||||
|
||||
# 解决冲突后再推送
|
||||
& "C:\Program Files\Git\bin\git.exe" push
|
||||
```
|
||||
|
||||
### 2. 忘记添加 .gitignore
|
||||
|
||||
```powershell
|
||||
# 从暂存区移除已添加的文件
|
||||
& "C:\Program Files\Git\bin\git.exe" rm -r --cached .pio
|
||||
|
||||
# 提交更改
|
||||
& "C:\Program Files\Git\bin\git.exe" commit -m "Remove .pio from tracking"
|
||||
& "C:\Program Files\Git\bin\git.exe" push
|
||||
```
|
||||
|
||||
### 3. 修改最后一次提交信息
|
||||
|
||||
```powershell
|
||||
# 修改最后一次提交(未推送)
|
||||
& "C:\Program Files\Git\bin\git.exe" commit --amend -m "新的提交信息"
|
||||
```
|
||||
|
||||
### 4. 查看文件历史
|
||||
|
||||
```powershell
|
||||
# 查看指定文件的历史
|
||||
& "C:\Program Files\Git\bin\git.exe" log --follow 文件名
|
||||
|
||||
# 查看指定文件在某次提交的内容
|
||||
& "C:\Program Files\Git\bin\git.exe" show 提交哈希:文件名
|
||||
```
|
||||
|
||||
## 提交信息规范
|
||||
|
||||
建议使用清晰的提交信息,例如:
|
||||
|
||||
```
|
||||
- "Add new feature: radar data upload"
|
||||
- "Fix bug: WiFi connection timeout"
|
||||
- "Update documentation: README.md"
|
||||
- "Refactor: improve code structure"
|
||||
- "Optimize: reduce memory usage"
|
||||
```
|
||||
|
||||
## 注意事项
|
||||
|
||||
1. **提交前检查**: 使用 `git status` 查看要提交的文件
|
||||
2. **合理提交**: 频繁提交,每次提交一个完整的功能或修复
|
||||
3. **提交信息**: 使用清晰、简洁的提交信息
|
||||
4. **推送前拉取**: 推送前先 `git pull` 避免冲突
|
||||
5. **敏感信息**: 不要提交密码、密钥等敏感信息
|
||||
|
||||
## 快捷别名(可选)
|
||||
|
||||
如果经常使用 Git,可以在 PowerShell 配置文件中添加别名:
|
||||
|
||||
```powershell
|
||||
# 在 PowerShell 配置文件中添加
|
||||
function gs { & "C:\Program Files\Git\bin\git.exe" status $args }
|
||||
function ga { & "C:\Program Files\Git\bin\git.exe" add $args }
|
||||
function gc { & "C:\Program Files\Git\bin\git.exe" commit -m $args }
|
||||
function gp { & "C:\Program Files\Git\bin\git.exe" push $args }
|
||||
function gl { & "C:\Program Files\Git\bin\git.exe" pull $args }
|
||||
```
|
||||
|
||||
使用示例:
|
||||
```powershell
|
||||
gs # git status
|
||||
ga -A # git add -A
|
||||
gc "update" # git commit -m "update"
|
||||
gp # git push
|
||||
```
|
||||
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
|
||||
@@ -1,92 +0,0 @@
|
||||
# 数据格式说明
|
||||
|
||||
## 1. 雷达数据格式 (HBR01)
|
||||
|
||||
### 格式特征
|
||||
- 以 `HBR01:` 开头
|
||||
- 后跟8个用逗号分隔的数值字段
|
||||
- 每行一条数据记录
|
||||
|
||||
### 数据字段说明
|
||||
```
|
||||
HBR01:presence,heart_rate,breath_rate,motion,rssi,heartbeat_waveform,breathing_waveform,raw_signal
|
||||
```
|
||||
|
||||
### 字段含义
|
||||
1. `presence` - 人员存在状态 (0=无人, 1=有人)
|
||||
2. `heart_rate` - 心率 (bpm)
|
||||
3. `breath_rate` - 呼吸率 (bpm)
|
||||
4. `motion` - 运动状态 (0=静止, 1=运动)
|
||||
5. `rssi` - 信号强度
|
||||
6. `heartbeat_waveform` - 心跳波形数据
|
||||
7. `breathing_waveform` - 呼吸波形数据
|
||||
8. `raw_signal` - 原始信号数据
|
||||
|
||||
### 示例数据
|
||||
```
|
||||
HBR01:1,72,16,0,-45,120,45,-25
|
||||
HBR01:0,0,0,0,-50,0,0,10
|
||||
```
|
||||
|
||||
### 处理方式
|
||||
在ESP32代码中通过 `parseSensorLine()` 函数处理,按行读取并解析逗号分隔的数值。
|
||||
|
||||
## 2. JSON数据格式
|
||||
|
||||
### 格式特征
|
||||
- 以花括号 `{` 开始,`}` 结束
|
||||
- 符合标准JSON格式
|
||||
- 可能因长度超过BLE MTU而需要分包传输
|
||||
|
||||
### 示例数据
|
||||
```json
|
||||
{
|
||||
"type": "radarData",
|
||||
"deviceId": 1001,
|
||||
"timestamp": 1640995200000,
|
||||
"presence": 1,
|
||||
"heartRate": 72.5,
|
||||
"breathRate": 16.2,
|
||||
"motion": 0,
|
||||
"rssi": -45,
|
||||
"heartbeatWaveform": 120,
|
||||
"breathingWaveform": 45,
|
||||
"rawSignal": -25
|
||||
}
|
||||
```
|
||||
|
||||
### 分包传输特点
|
||||
- 每包最大20字节(BLE限制)
|
||||
- 需要通过匹配花括号来重组完整JSON
|
||||
- 可能出现一个完整JSON被分成多个BLE包的情况
|
||||
|
||||
### 接收处理方式
|
||||
使用JavaScript代码中的 `processChunk()` 函数处理:
|
||||
1. 将接收到的数据片段追加到缓冲区
|
||||
2. 通过计数花括号来识别完整的JSON对象
|
||||
3. 提取完整JSON并解析
|
||||
4. 清理缓冲区中已处理的部分
|
||||
|
||||
## 3. 两种格式的主要区别
|
||||
|
||||
| 特性 | 雷达数据 (HBR01) | JSON数据 |
|
||||
|------|------------------|----------|
|
||||
| 格式标识 | HBR01:开头 | 花括号{}包围 |
|
||||
| 数据结构 | 固定8个数值字段 | 灵活的键值对 |
|
||||
| 分隔符 | 逗号分隔 | JSON标准格式 |
|
||||
| 分包处理 | 按行处理 | 按花括号匹配 |
|
||||
| 解析方式 | 数值转换 | JSON解析 |
|
||||
|
||||
## 4. 处理建议
|
||||
|
||||
### 雷达数据处理
|
||||
- 按行读取处理
|
||||
- 验证以"HBR01:"开头
|
||||
- 按逗号分割提取8个字段
|
||||
- 进行数值有效性检查
|
||||
|
||||
### JSON数据处理
|
||||
- 使用缓冲区累积数据
|
||||
- 通过花括号匹配识别完整对象
|
||||
- 使用JSON解析库处理
|
||||
- 注意缓冲区大小限制和清理
|
||||
@@ -1,44 +0,0 @@
|
||||
import random
|
||||
import time
|
||||
|
||||
def generate_radar_data_lines(count=100):
|
||||
"""生成模拟雷达数据行"""
|
||||
lines = []
|
||||
|
||||
for i in range(count):
|
||||
# 生成8个随机数值字段
|
||||
# 字段含义参考代码: presence, heart_rate, breath_rate, motion, rssi, heartbeat_waveform, breathing_waveform, raw_signal
|
||||
presence = random.choice([0, 1]) # 0=无人, 1=有人
|
||||
heart_rate = random.uniform(0, 200) if presence else 0 # 心率 (bpm)
|
||||
breath_rate = random.uniform(0, 60) if presence else 0 # 呼吸率 (bpm)
|
||||
motion = random.choice([0, 1]) if presence else 0 # 运动状态
|
||||
rssi = random.randint(-100, -30) # 信号强度
|
||||
heartbeat_waveform = random.randint(-1000, 1000) # 心跳波形
|
||||
breathing_waveform = random.randint(-1000, 1000) # 呼吸波形
|
||||
raw_signal = random.randint(-100, 100) # 原始信号
|
||||
|
||||
# 格式化为HBR01数据行
|
||||
line = f"HBR01:{presence},{heart_rate:.0f},{breath_rate:.0f},{motion},{rssi},{heartbeat_waveform},{breathing_waveform},{raw_signal}"
|
||||
lines.append(line)
|
||||
|
||||
return lines
|
||||
|
||||
def save_to_file(lines, filename="generated_sensor_data.txt"):
|
||||
"""保存数据到文件"""
|
||||
with open(filename, "w", encoding="utf-8") as f:
|
||||
for line in lines:
|
||||
f.write(line + "\n")
|
||||
print(f"已生成 {len(lines)} 行雷达数据并保存到 {filename}")
|
||||
|
||||
def main():
|
||||
# 生成1000行测试数据
|
||||
lines = generate_radar_data_lines(1000)
|
||||
save_to_file(lines, "generated_sensor_data.txt")
|
||||
|
||||
# 显示前10行作为示例
|
||||
print("\n前10行数据示例:")
|
||||
for i, line in enumerate(lines[:10]):
|
||||
print(f"{i+1:2d}: {line}")
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
37
git_upload.bat
Normal file
37
git_upload.bat
Normal file
@@ -0,0 +1,37 @@
|
||||
@echo off
|
||||
chcp 65001 >nul
|
||||
echo ========================================
|
||||
echo Git 上传脚本 - Rader_Success_5
|
||||
echo ========================================
|
||||
echo.
|
||||
|
||||
set GIT_PATH="C:\Program Files\Git\bin\git.exe"
|
||||
set REMOTE_URL=http://lmhrt.cn:6771/ming/Rader_Success_5.git
|
||||
|
||||
echo [1/5] 检查 Git 状态...
|
||||
%GIT_PATH% status
|
||||
echo.
|
||||
|
||||
echo [2/5] 添加所有文件到暂存区...
|
||||
%GIT_PATH% add -A
|
||||
echo.
|
||||
|
||||
echo [3/5] 提交更改...
|
||||
set /p commit_msg="请输入提交信息 (默认: Update project): "
|
||||
if "%commit_msg%"=="" set commit_msg=Update project
|
||||
%GIT_PATH% commit -m "%commit_msg%"
|
||||
echo.
|
||||
|
||||
echo [4/5] 拉取远程代码...
|
||||
%GIT_PATH% pull
|
||||
echo.
|
||||
|
||||
echo [5/5] 推送到远程仓库...
|
||||
%GIT_PATH% push
|
||||
echo.
|
||||
|
||||
echo ========================================
|
||||
echo 上传完成!
|
||||
echo ========================================
|
||||
echo.
|
||||
pause
|
||||
36
src/Radar.h
36
src/Radar.h
@@ -1,36 +0,0 @@
|
||||
#ifndef RADAR_H
|
||||
#define RADAR_H
|
||||
// 在main.cpp顶部添加R60ABD1协议相关定义
|
||||
#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;
|
||||
|
||||
|
||||
#endif
|
||||
2960
src/main.cpp
2960
src/main.cpp
File diff suppressed because it is too large
Load Diff
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
1721
src/radar_manager.cpp
Normal file
1721
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
|
||||
608
src/wifi_manager.cpp
Normal file
608
src/wifi_manager.cpp
Normal file
@@ -0,0 +1,608 @@
|
||||
#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);
|
||||
String configStr = preferences.getString(key.c_str(), "");
|
||||
|
||||
if (configStr.length() > 0) {
|
||||
JsonDocument doc;
|
||||
DeserializationError error = deserializeJson(doc, configStr);
|
||||
|
||||
if (!error) {
|
||||
const char* ssid = doc["ssid"];
|
||||
const char* password = doc["password"];
|
||||
|
||||
if (ssid && password) {
|
||||
strncpy(savedNetworks[savedNetworkCount].ssid, ssid, 31);
|
||||
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++) {
|
||||
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);
|
||||
|
||||
String key = "wifi_" + String(i);
|
||||
preferences.putString(key.c_str(), configStr);
|
||||
|
||||
Serial.printf("🔄 更新WiFi配置: %s\n", ssid);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
// 添加新配置
|
||||
if (savedNetworkCount < MAX_WIFI_NETWORKS) {
|
||||
strncpy(savedNetworks[savedNetworkCount].ssid, ssid, 31);
|
||||
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);
|
||||
|
||||
return true;
|
||||
} else {
|
||||
Serial.println("❌ [WiFi] 连接超时");
|
||||
currentState = WIFI_DISCONNECTED;
|
||||
setNetworkStatus(NET_DISCONNECTED);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 扫描并匹配WiFi网络
|
||||
* 扫描附近的WiFi网络,并尝试匹配已保存的配置
|
||||
* 如果找到多个匹配的网络,优先连接信号强度最强的那一组
|
||||
* @return 是否成功连接到匹配的网络
|
||||
*/
|
||||
bool WiFiManager::scanAndMatchNetworks() {
|
||||
if (isScanning) {
|
||||
Serial.println("⚠️ [WiFi] 正在扫描中,跳过本次扫描");
|
||||
return false;
|
||||
}
|
||||
|
||||
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 {
|
||||
const char* ssid;
|
||||
const char* password;
|
||||
int rssi;
|
||||
} bestNetwork = {nullptr, nullptr, -1000}; // 初始化为非常弱的信号
|
||||
|
||||
// 遍历已保存的网络,寻找匹配的网络
|
||||
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 (rssi > bestNetwork.rssi) {
|
||||
bestNetwork.ssid = savedNetworks[i].ssid;
|
||||
bestNetwork.password = savedNetworks[i].password;
|
||||
bestNetwork.rssi = rssi;
|
||||
Serial.printf("📈 更新最佳网络: %s, 信号: %d dBm\n",
|
||||
bestNetwork.ssid, bestNetwork.rssi);
|
||||
}
|
||||
} else {
|
||||
Serial.printf("⚠️ 信号强度过低,跳过\n");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 如果找到最佳网络,尝试连接
|
||||
if (bestNetwork.ssid != nullptr) {
|
||||
Serial.printf("✅ 选择信号最强的网络: %s, 信号: %d dBm\n",
|
||||
bestNetwork.ssid, bestNetwork.rssi);
|
||||
|
||||
WiFi.scanDelete();
|
||||
vTaskDelay(300 / portTICK_PERIOD_MS);
|
||||
|
||||
if (connectToNetwork(bestNetwork.ssid, bestNetwork.password)) {
|
||||
isScanning = false;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
Serial.println("❌ 未找到匹配的WiFi网络或信号过弱");
|
||||
currentState = WIFI_DISCONNECTED;
|
||||
isScanning = false;
|
||||
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] 正在扫描中,跳过本次扫描");
|
||||
if (deviceConnected) {
|
||||
String errorMsg = String("{\"type\":\"scanWiFiResult\",\"success\":false,\"message\":\"正在扫描中,请稍后再试\",\"networks\":[],\"count\":0}");
|
||||
sendJSONDataToBLE(errorMsg);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
Serial.println("📱 [BLE-WiFi] 开始WiFi扫描...");
|
||||
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网络或扫描失败");
|
||||
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;
|
||||
}
|
||||
|
||||
// 先扫描WiFi网络,检查是否存在匹配的网络
|
||||
Serial.println("🔍 [WiFi] 扫描WiFi网络,检查是否存在匹配的网络...");
|
||||
int n = WiFi.scanNetworks();
|
||||
Serial.printf("🔍 扫描到 %d 个WiFi网络\n", n);
|
||||
|
||||
if (n == 0) {
|
||||
Serial.println("❌ 未扫描到任何WiFi网络");
|
||||
|
||||
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网络");
|
||||
}
|
||||
|
||||
if (deviceConnected) {
|
||||
sendJSONDataToBLE(errorMsg);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// 尝试连接到指定网络
|
||||
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配置失败");
|
||||
|
||||
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连接断开");
|
||||
}
|
||||
|
||||
// 处理重连逻辑
|
||||
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 15000
|
||||
|
||||
/**
|
||||
* @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
|
||||
330
tcp-output(1).py
330
tcp-output(1).py
@@ -1,330 +0,0 @@
|
||||
import time
|
||||
import random
|
||||
import requests
|
||||
from datetime import datetime
|
||||
|
||||
# 添加InfluxDB配置
|
||||
INFLUXDB_URL = 'http://8.134.11.76:8086'
|
||||
INFLUXDB_TOKEN = 'KuTa5ZsqoHIhi2IglOO06zExUYw1_mJ6K0mcA9X1y6O6CJDog3_Cgr8mUw1SwpuCCKRElqxa6wAhrrhsYPytkg=='
|
||||
INFLUXDB_ORG = 'gzlg'
|
||||
INFLUXDB_BUCKET = 'gzlg'
|
||||
|
||||
# 配置设备ID:设置为0则自动注册,设置为1000~1999则固定使用该ID
|
||||
CONFIG_DEVICE_ID = 1003
|
||||
|
||||
# 分配的设备ID(初始为None,注册后更新)
|
||||
assigned_device_id = None
|
||||
# 相位计数器
|
||||
phase_counter = 0
|
||||
|
||||
def get_next_device_id():
|
||||
"""从InfluxDB查询已注册的设备ID,并返回下一个可用的ID"""
|
||||
try:
|
||||
# 查询InfluxDB中已存在的设备ID
|
||||
query = '''
|
||||
from(bucket: "{}")
|
||||
|> range(start: -365d)
|
||||
|> filter(fn: (r) => r["_measurement"] == "device_data")
|
||||
|> keep(columns: ["deviceId"])
|
||||
|> distinct(column: "deviceId")
|
||||
|> sort()
|
||||
'''.format(INFLUXDB_BUCKET)
|
||||
|
||||
url = f"{INFLUXDB_URL}/api/v2/query?org={INFLUXDB_ORG}"
|
||||
headers = {
|
||||
"Authorization": f"Token {INFLUXDB_TOKEN}",
|
||||
"Content-Type": "application/json"
|
||||
}
|
||||
data = {
|
||||
"query": query
|
||||
}
|
||||
|
||||
response = requests.post(url, headers=headers, json=data)
|
||||
print(f"🔍 InfluxDB查询响应状态: {response.status_code}")
|
||||
if response.status_code == 200:
|
||||
print(f"🔍 InfluxDB查询响应内容: {response.text}")
|
||||
# 解析响应数据
|
||||
device_ids = []
|
||||
lines = response.text.strip().split('\n')
|
||||
for line in lines:
|
||||
if line and not line.startswith('#') and 'deviceId' not in line:
|
||||
try:
|
||||
# 解析CSV格式的响应数据
|
||||
# 格式类似于: ,_result,0,1001,1001
|
||||
parts = line.split(',')
|
||||
if len(parts) >= 5:
|
||||
# deviceId在第4个位置(索引3)
|
||||
device_id_str = parts[3].strip()
|
||||
if device_id_str:
|
||||
device_id = int(device_id_str)
|
||||
if 1000 <= device_id <= 1999: # 只考虑1000-1999范围内的设备ID
|
||||
device_ids.append(device_id)
|
||||
print(f"📱 发现设备ID: {device_id}")
|
||||
except (ValueError, IndexError) as e:
|
||||
# 忽略解析错误的行
|
||||
print(f"⚠️ 忽略无法解析的行: {line}")
|
||||
continue
|
||||
|
||||
# 对设备ID进行排序
|
||||
device_ids.sort()
|
||||
|
||||
# 找到下一个可用的ID
|
||||
next_id = 1001
|
||||
for device_id in device_ids:
|
||||
if device_id == next_id:
|
||||
next_id += 1
|
||||
elif device_id > next_id:
|
||||
break
|
||||
|
||||
# 确保ID在有效范围内
|
||||
if next_id > 1999:
|
||||
next_id = 1001 # 如果超出范围,从头开始
|
||||
|
||||
print(f"📊 查询到已注册设备: {device_ids}")
|
||||
print(f"🆕 分配新设备ID: {next_id}")
|
||||
return next_id
|
||||
else:
|
||||
print(f"❌ 查询InfluxDB失败: {response.status_code} - {response.text}")
|
||||
# 如果查询失败,返回默认ID
|
||||
return 1001
|
||||
except Exception as e:
|
||||
print(f"❌ 查询设备ID时出错: {e}")
|
||||
import traceback
|
||||
traceback.print_exc()
|
||||
# 如果查询失败,返回默认ID
|
||||
return 1001
|
||||
|
||||
def register_device():
|
||||
"""注册设备并获取设备ID"""
|
||||
global assigned_device_id
|
||||
|
||||
# 检查配置的设备ID
|
||||
if CONFIG_DEVICE_ID == 0:
|
||||
# 自动注册模式
|
||||
try:
|
||||
# 获取下一个可用的设备ID
|
||||
next_device_id = get_next_device_id()
|
||||
assigned_device_id = next_device_id
|
||||
|
||||
print(f"✅ 设备注册成功! 设备ID: {assigned_device_id} (0x{assigned_device_id:04X})")
|
||||
return True
|
||||
except Exception as e:
|
||||
print(f"❌ 注册过程中发生错误: {e}")
|
||||
return False
|
||||
elif 1000 <= CONFIG_DEVICE_ID <= 1999:
|
||||
# 固定设备ID模式
|
||||
assigned_device_id = CONFIG_DEVICE_ID
|
||||
print(f"✅ 使用固定设备ID: {assigned_device_id} (0x{assigned_device_id:04X})")
|
||||
return True
|
||||
else:
|
||||
# 配置的设备ID无效
|
||||
print(f"❌ 配置的设备ID {CONFIG_DEVICE_ID} 无效,请设置为0(自动注册)或1000~1999之间的值")
|
||||
return False
|
||||
|
||||
def save_data_to_influxdb(protocol_id, data_value):
|
||||
"""保存日常数据到InfluxDB"""
|
||||
try:
|
||||
# 根据协议ID确定字段名
|
||||
field_mapping = {
|
||||
1: "heartRate",
|
||||
2: "breathingRate",
|
||||
13: "personDetected",
|
||||
14: "humanActivity",
|
||||
15: "humanDistance", # 人体距离 (cm)
|
||||
16: "humanPosition", # 人体方位 (cm)
|
||||
17: "sleepState" # 睡眠状态
|
||||
}
|
||||
|
||||
if protocol_id in field_mapping:
|
||||
field_name = field_mapping[protocol_id]
|
||||
|
||||
# 创建数据点 - 使用 "daily_data" 作为测量值名称
|
||||
data_point = {
|
||||
"measurement": "daily_data", # 改为 daily_data 以区分日常数据
|
||||
"tags": {
|
||||
"deviceId": assigned_device_id,
|
||||
"dataType": "daily" # 标识这是日常数据
|
||||
},
|
||||
"time": datetime.utcnow().isoformat() + "Z",
|
||||
"fields": {}
|
||||
}
|
||||
|
||||
# 对特定字段进行数值处理
|
||||
if protocol_id in [1, 2]: # 心率和呼吸频率需要除以10
|
||||
data_point["fields"][field_name] = float(data_value) / 10.0
|
||||
elif protocol_id in [13, 14]: # 人检/活动数据,确保是整数0或1
|
||||
# 强制转换为整数,确保只有0或1
|
||||
data_point["fields"][field_name] = int(data_value)
|
||||
if data_point["fields"][field_name] not in [0, 1]:
|
||||
print(f"⚠️ 警告: 人检/活动数据值异常: {data_value}, 强制转换为: {data_point['fields'][field_name]}")
|
||||
elif protocol_id == 15: # 人体距离,范围0-65535
|
||||
data_point["fields"][field_name] = int(data_value)
|
||||
elif protocol_id == 16: # 人体方位,可以是正负值
|
||||
data_point["fields"][field_name] = int(data_value)
|
||||
elif protocol_id == 17: # 睡眠状态,使用预定义值
|
||||
data_point["fields"][field_name] = int(data_value)
|
||||
else:
|
||||
data_point["fields"][field_name] = data_value
|
||||
|
||||
# 发送数据到InfluxDB
|
||||
url = f"{INFLUXDB_URL}/api/v2/write?org={INFLUXDB_ORG}&bucket={INFLUXDB_BUCKET}"
|
||||
headers = {
|
||||
"Authorization": f"Token {INFLUXDB_TOKEN}",
|
||||
"Content-Type": "text/plain; charset=utf-8"
|
||||
}
|
||||
|
||||
# 构造行协议格式的数据,明确指定数据类型
|
||||
if protocol_id in [13, 14, 15, 16, 17]:
|
||||
# 对于人检/活动/距离/位置/睡眠状态数据,使用整数格式
|
||||
line_protocol = f"daily_data,deviceId={assigned_device_id},dataType=daily {field_name}={int(data_point['fields'][field_name])}i"
|
||||
else:
|
||||
# 对于其他数据,使用浮点数格式
|
||||
line_protocol = f"daily_data,deviceId={assigned_device_id},dataType=daily {field_name}={data_point['fields'][field_name]}"
|
||||
|
||||
response = requests.post(url, headers=headers, data=line_protocol)
|
||||
if response.status_code == 204:
|
||||
print(f"✅ 日常数据已保存到InfluxDB设备{assigned_device_id}上: {field_name}={data_point['fields'][field_name]}")
|
||||
else:
|
||||
print(f"❌ 保存日常数据到InfluxDB失败: {response.status_code} - {response.text}")
|
||||
else:
|
||||
print(f"⚠️ 未知的协议ID: {protocol_id}")
|
||||
|
||||
except Exception as e:
|
||||
print(f"❌ 保存日常数据到InfluxDB时出错: {e}")
|
||||
import traceback
|
||||
traceback.print_exc()
|
||||
|
||||
def save_sleep_data_to_influxdb(sleep_data):
|
||||
"""保存睡眠数据到InfluxDB"""
|
||||
try:
|
||||
# 构造睡眠数据的行协议格式 - 使用 "sleep_data" 作为测量值名称
|
||||
line_protocol = f"sleep_data,deviceId={assigned_device_id},dataType=sleep "
|
||||
|
||||
# 按顺序添加各个字段
|
||||
fields = []
|
||||
fields.append(f"sleepQualityScore={int(sleep_data['sleepQualityScore'])}i") # 1B 睡眠质量评分 (0~100)
|
||||
fields.append(f"totalSleepDuration={int(sleep_data['totalSleepDuration'])}i") # 2B 睡眠总时长 (0~65535 分钟)
|
||||
fields.append(f"awakeDurationRatio={int(sleep_data['awakeDurationRatio'])}i") # 1B 清醒时长占比 (0~100)
|
||||
fields.append(f"lightSleepRatio={int(sleep_data['lightSleepRatio'])}i") # 1B 浅睡时长占比 (0~100)
|
||||
fields.append(f"deepSleepRatio={int(sleep_data['deepSleepRatio'])}i") # 1B 深睡时长占比 (0~100)
|
||||
fields.append(f"outOfBedDuration={int(sleep_data['outOfBedDuration'])}i") # 1B 离床时长 (0~255)
|
||||
fields.append(f"outOfBedCount={int(sleep_data['outOfBedCount'])}i") # 1B 离床次数 (0~255)
|
||||
fields.append(f"turnCount={int(sleep_data['turnCount'])}i") # 1B 翻身次数 (0~255)
|
||||
fields.append(f"avgBreathingRate={int(sleep_data['avgBreathingRate'])}i") # 1B 平均呼吸 (0~25)
|
||||
fields.append(f"avgHeartRate={int(sleep_data['avgHeartRate'])}i") # 1B 平均心跳 (0~100)
|
||||
fields.append(f"apneaCount={int(sleep_data['apneaCount'])}i") # 1B 呼吸暂停次数 (0~10)
|
||||
|
||||
line_protocol += ",".join(fields)
|
||||
|
||||
# 发送数据到InfluxDB
|
||||
url = f"{INFLUXDB_URL}/api/v2/write?org={INFLUXDB_ORG}&bucket={INFLUXDB_BUCKET}"
|
||||
headers = {
|
||||
"Authorization": f"Token {INFLUXDB_TOKEN}",
|
||||
"Content-Type": "text/plain; charset=utf-8"
|
||||
}
|
||||
|
||||
response = requests.post(url, headers=headers, data=line_protocol)
|
||||
if response.status_code == 204:
|
||||
print(f"✅ 睡眠数据已保存到InfluxDB设备{assigned_device_id}上")
|
||||
else:
|
||||
print(f"❌ 保存睡眠数据到InfluxDB失败: {response.status_code} - {response.text}")
|
||||
except Exception as e:
|
||||
print(f"❌ 保存睡眠数据到InfluxDB时出错: {e}")
|
||||
import traceback
|
||||
traceback.print_exc()
|
||||
|
||||
def generate_random_sleep_data():
|
||||
"""生成随机睡眠数据用于测试"""
|
||||
sleep_data = {
|
||||
"sleepQualityScore": random.randint(0, 100), # 1B 睡眠质量评分 (0~100)
|
||||
"totalSleepDuration": random.randint(0, 65535), # 2B 睡眠总时长 (0~65535 分钟)
|
||||
"awakeDurationRatio": random.randint(0, 100), # 1B 清醒时长占比 (0~100)
|
||||
"lightSleepRatio": random.randint(0, 100), # 1B 浅睡时长占比 (0~100)
|
||||
"deepSleepRatio": random.randint(0, 100), # 1B 深睡时长占比 (0~100)
|
||||
"outOfBedDuration": random.randint(0, 255), # 1B 离床时长 (0~255)
|
||||
"outOfBedCount": random.randint(0, 255), # 1B 离床次数 (0~255)
|
||||
"turnCount": random.randint(0, 255), # 1B 翻身次数 (0~255)
|
||||
"avgBreathingRate": random.randint(0, 25), # 1B 平均呼吸 (0~25)
|
||||
"avgHeartRate": random.randint(0, 100), # 1B 平均心跳 (0~100)
|
||||
"apneaCount": random.randint(0, 10), # 1B 呼吸暂停次数 (0~10)
|
||||
}
|
||||
|
||||
# 确保比例字段总和为100
|
||||
total_ratio = sleep_data["awakeDurationRatio"] + sleep_data["lightSleepRatio"] + sleep_data["deepSleepRatio"]
|
||||
if total_ratio != 100:
|
||||
# 调整浅睡时长占比以确保总和为100
|
||||
adjustment = 100 - total_ratio
|
||||
sleep_data["lightSleepRatio"] = max(0, min(100, sleep_data["lightSleepRatio"] + adjustment))
|
||||
|
||||
# 再次检查总和
|
||||
total_ratio = sleep_data["awakeDurationRatio"] + sleep_data["lightSleepRatio"] + sleep_data["deepSleepRatio"]
|
||||
if total_ratio != 100:
|
||||
# 如果仍然不是100,则最后一次调整浅睡占比
|
||||
sleep_data["lightSleepRatio"] += (100 - total_ratio)
|
||||
sleep_data["lightSleepRatio"] = max(0, min(100, sleep_data["lightSleepRatio"]))
|
||||
|
||||
return sleep_data
|
||||
|
||||
def main():
|
||||
global assigned_device_id
|
||||
|
||||
try:
|
||||
# 第一步:注册设备获取设备ID
|
||||
if not register_device():
|
||||
print("❌ 设备注册失败,程序退出")
|
||||
return
|
||||
|
||||
print(f"🎯 开始使用设备ID {assigned_device_id} 发送数据...")
|
||||
|
||||
# 启动时立即发送一次睡眠数据
|
||||
print("⏰ 启动时生成并发送睡眠数据...")
|
||||
initial_sleep_data = generate_random_sleep_data()
|
||||
save_sleep_data_to_influxdb(initial_sleep_data)
|
||||
|
||||
# 记录上次发送睡眠数据的时间
|
||||
last_sleep_data_time = time.time()
|
||||
|
||||
# 第二步:开始发送数据
|
||||
while True:
|
||||
# 初始化数据值
|
||||
data_value = 0
|
||||
|
||||
# 发送其他数据
|
||||
protocol_id = random.choice([1, 2, 13, 14, 15, 16, 17]) # 1=心跳, 2=呼吸, 13=检测到人, 14=人体活动, 15=人体距离, 16=人体方位, 17=睡眠状态
|
||||
|
||||
if protocol_id == 1: # 心跳
|
||||
data_value = random.randint(600, 1000)
|
||||
elif protocol_id == 2: # 呼吸
|
||||
data_value = random.randint(120, 200)
|
||||
elif protocol_id == 13: # 检测到人(1检测到,0未检测到)
|
||||
data_value = random.choice([1])
|
||||
elif protocol_id == 14: # 人体活动(1活动,0静止)
|
||||
data_value = random.choice([0])
|
||||
elif protocol_id == 15: # 人体距离 (cm),范围0-65535
|
||||
data_value = random.randint(0, 65535)
|
||||
elif protocol_id == 16: # 人体方位 (cm),可以有正负值
|
||||
data_value = random.randint(-32768, 32767)
|
||||
elif protocol_id == 17: # 睡眠状态 (0x00=深睡, 0x01=浅睡, 0x02=清醒, 0x03=无)
|
||||
data_value = random.choice([0, 1, 2, 3])
|
||||
|
||||
# 直接发送数据到InfluxDB
|
||||
save_data_to_influxdb(protocol_id, data_value)
|
||||
|
||||
# 每隔一段时间(例如30分钟)发送一次睡眠数据
|
||||
current_time = time.time()
|
||||
if current_time - last_sleep_data_time >= 1800: # 30分钟 = 1800秒
|
||||
print("⏰ 生成并发送睡眠数据...")
|
||||
sleep_data = generate_random_sleep_data()
|
||||
save_sleep_data_to_influxdb(sleep_data)
|
||||
last_sleep_data_time = current_time
|
||||
|
||||
# 设置发送间隔
|
||||
time.sleep(0.4) # 每0.4秒发送一次数据
|
||||
|
||||
except KeyboardInterrupt:
|
||||
print(f"\n🛑 设备 {assigned_device_id} 发送端已停止")
|
||||
except Exception as e:
|
||||
print(f"❌ 发生错误: {e}")
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -1,72 +0,0 @@
|
||||
import json
|
||||
import time
|
||||
|
||||
def simulate_json_chunk_sending(data, chunk_size=15):
|
||||
"""
|
||||
模拟JSON数据分包发送
|
||||
"""
|
||||
json_string = json.dumps(data)
|
||||
print(f"原始JSON数据: {json_string}")
|
||||
print(f"数据总长度: {len(json_string)} 字符")
|
||||
|
||||
chunks = []
|
||||
# 分包
|
||||
for i in range(0, len(json_string), chunk_size):
|
||||
chunk = json_string[i:i+chunk_size]
|
||||
chunks.append(chunk)
|
||||
|
||||
print(f"\n分包结果 ({len(chunks)} 个包):")
|
||||
for i, chunk in enumerate(chunks):
|
||||
print(f" 包 {i+1}: {chunk}")
|
||||
|
||||
return chunks
|
||||
|
||||
def create_sample_json_data():
|
||||
"""
|
||||
创建示例JSON数据
|
||||
"""
|
||||
return {
|
||||
"type": "radarData",
|
||||
"deviceId": 1001,
|
||||
"timestamp": int(time.time() * 1000),
|
||||
"presence": 1,
|
||||
"heartRate": 72.5,
|
||||
"breathRate": 16.2,
|
||||
"motion": 0,
|
||||
"rssi": -45,
|
||||
"heartbeatWaveform": 120,
|
||||
"breathingWaveform": 45,
|
||||
"rawSignal": -25
|
||||
}
|
||||
|
||||
def create_status_json_data():
|
||||
"""
|
||||
创建状态JSON数据
|
||||
"""
|
||||
return {
|
||||
"type": "status",
|
||||
"wifiConfigured": True,
|
||||
"wifiConnected": True,
|
||||
"ipAddress": "192.168.1.100",
|
||||
"deviceId": 1001
|
||||
}
|
||||
|
||||
def main():
|
||||
print("=== JSON分包发送模拟测试 ===\n")
|
||||
|
||||
# 测试雷达数据
|
||||
print("1. 雷达数据分包测试:")
|
||||
radar_data = create_sample_json_data()
|
||||
simulate_json_chunk_sending(radar_data, 15)
|
||||
|
||||
print("\n" + "="*50 + "\n")
|
||||
|
||||
# 测试状态数据
|
||||
print("2. 状态数据分包测试:")
|
||||
status_data = create_status_json_data()
|
||||
simulate_json_chunk_sending(status_data, 15)
|
||||
|
||||
print("\n=== 测试完成 ===")
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
394
传感器数据.txt
394
传感器数据.txt
@@ -1,394 +0,0 @@
|
||||
HBR01:0,0,0,0,9,0,0,-30
|
||||
HBR01:0,0,0,0,9,0,0,-20
|
||||
HBR01:0,0,0,0,9,0,0,-21
|
||||
HBR01:0,0,0,0,9,0,0,-19
|
||||
HBR01:0,0,0,0,9,0,0,-22
|
||||
HBR01:0,0,0,0,9,0,0,-36
|
||||
HBR01:0,0,0,0,9,0,0,-35
|
||||
HBR01:0,0,0,0,9,0,0,-39
|
||||
HBR01:0,0,0,0,9,0,0,-40
|
||||
HBR01:0,0,0,0,9,0,0,-40
|
||||
HBR01:0,0,0,0,9,0,0,-40
|
||||
HBR01:0,0,0,0,9,0,0,-40
|
||||
HBR01:0,0,0,0,9,0,0,-23
|
||||
HBR01:0,0,0,0,9,0,0,0
|
||||
HBR01:0,0,0,0,9,0,0,0
|
||||
HBR01:0,0,0,0,9,0,0,-11
|
||||
HBR01:0,0,0,0,9,0,0,-19
|
||||
HBR01:0,0,0,0,9,0,0,-29
|
||||
HBR01:0,0,0,0,9,0,0,-34
|
||||
HBR01:0,0,0,0,9,0,0,-19
|
||||
HBR01:0,0,0,0,9,0,0,-22
|
||||
HBR01:0,0,0,0,9,0,0,-10
|
||||
HBR01:0,0,0,0,9,0,0,-9
|
||||
HBR01:0,0,0,0,9,0,0,-12
|
||||
HBR01:0,0,0,0,9,0,0,-13
|
||||
HBR01:0,0,0,0,9,0,0,-19
|
||||
HBR01:0,0,0,0,9,0,0,-23
|
||||
HBR01:0,0,0,0,9,0,0,-33
|
||||
HBR01:0,0,0,0,9,0,0,-33
|
||||
HBR01:0,0,0,0,9,0,0,-19
|
||||
HBR01:0,0,0,0,9,0,0,-3
|
||||
HBR01:0,0,0,0,9,0,0,-22
|
||||
HBR01:0,0,0,0,9,0,0,-13
|
||||
HBR01:0,0,0,0,9,0,0,-18
|
||||
HBR01:0,0,0,0,9,0,0,-12
|
||||
HBR01:0,0,0,0,9,0,0,0
|
||||
HBR01:0,0,0,0,9,0,0,-16
|
||||
HBR01:0,0,0,0,9,0,0,-27
|
||||
HBR01:0,0,0,0,9,0,0,-14
|
||||
HBR01:0,0,0,0,9,0,0,-23
|
||||
HBR01:0,0,0,0,9,0,0,-16
|
||||
HBR01:0,0,0,0,10,0,0,-40
|
||||
HBR01:0,0,0,0,10,0,0,-42
|
||||
HBR01:0,0,0,0,10,0,0,-38
|
||||
HBR01:0,0,0,0,10,0,0,-40
|
||||
HBR01:0,0,0,0,10,0,0,-43
|
||||
HBR01:0,0,0,0,10,0,0,0
|
||||
HBR01:0,0,0,0,10,0,0,0
|
||||
HBR01:0,0,0,0,10,0,0,0
|
||||
HBR01:0,0,0,0,10,0,0,1
|
||||
HBR01:0,0,0,0,10,0,0,0
|
||||
HBR01:0,0,0,0,10,0,0,0
|
||||
HBR01:0,0,0,0,10,0,0,0
|
||||
HBR01:0,0,0,0,10,0,0,-4
|
||||
HBR01:0,0,0,0,10,0,0,-22
|
||||
HBR01:0,0,0,0,10,0,0,-40
|
||||
HBR01:0,0,0,0,11,0,0,-57
|
||||
HBR01:0,0,0,0,11,0,0,-29
|
||||
HBR01:0,0,0,0,11,0,0,-22
|
||||
HBR01:0,0,0,0,11,0,0,-10
|
||||
HBR01:0,0,0,0,11,0,0,-11
|
||||
HBR01:0,0,0,0,11,0,0,-11
|
||||
HBR01:0,0,0,0,11,0,0,-10
|
||||
HBR01:0,0,0,0,11,0,0,4
|
||||
HBR01:0,0,0,0,11,0,0,-16
|
||||
HBR01:0,0,0,0,11,0,0,-59
|
||||
HBR01:0,0,0,0,11,0,0,-9
|
||||
HBR01:0,0,0,0,11,0,0,-15
|
||||
HBR01:0,0,0,0,11,0,0,-21
|
||||
HBR01:0,0,0,0,11,0,0,-26
|
||||
HBR01:0,0,0,0,11,0,0,-28
|
||||
HBR01:0,0,0,0,11,0,0,-26
|
||||
HBR01:0,0,0,0,11,0,0,-26
|
||||
HBR01:0,0,0,0,11,0,0,-26
|
||||
HBR01:0,0,0,0,11,0,0,-24
|
||||
HBR01:0,0,0,0,11,0,0,-24
|
||||
HBR01:0,0,0,0,11,0,0,-14
|
||||
HBR01:0,0,0,0,11,0,0,-28
|
||||
HBR01:0,0,0,0,11,0,0,-30
|
||||
HBR01:0,0,0,0,11,0,0,-9
|
||||
HBR01:0,0,0,0,11,0,0,-25
|
||||
HBR01:0,0,0,0,11,0,0,-29
|
||||
HBR01:0,0,0,0,11,0,0,-24
|
||||
HBR01:0,0,0,0,11,0,0,-23
|
||||
HBR01:0,0,0,0,11,0,0,-31
|
||||
HBR01:0,0,0,0,11,0,0,-26
|
||||
HBR01:0,0,0,0,11,0,0,-25
|
||||
HBR01:0,0,0,0,11,0,0,-21
|
||||
HBR01:0,0,0,0,11,0,0,-36
|
||||
HBR01:0,0,0,0,11,0,0,-25
|
||||
HBR01:0,0,0,0,11,0,0,-26
|
||||
HBR01:0,0,0,0,11,0,0,-39
|
||||
HBR01:0,0,0,0,11,0,0,-27
|
||||
HBR01:0,0,0,0,11,0,0,-24
|
||||
HBR01:0,0,0,0,11,0,0,-24
|
||||
HBR01:0,0,0,0,11,0,0,-27
|
||||
HBR01:0,0,0,0,11,0,0,-27
|
||||
HBR01:0,0,0,0,11,0,0,-26
|
||||
HBR01:0,0,0,0,11,0,0,-36
|
||||
HBR01:0,0,0,0,11,0,0,-40
|
||||
HBR01:0,0,0,0,11,0,0,-40
|
||||
HBR01:0,0,0,0,11,0,0,-40
|
||||
HBR01:0,0,0,0,11,0,0,-40
|
||||
HBR01:0,0,0,0,11,0,0,-40
|
||||
HBR01:0,0,0,0,11,0,0,-59
|
||||
HBR01:0,0,0,0,11,0,0,-41
|
||||
HBR01:0,0,0,0,11,0,0,-52
|
||||
HBR01:0,0,0,0,11,0,0,-39
|
||||
HBR01:0,0,0,0,11,0,0,-35
|
||||
HBR01:0,0,0,0,11,0,0,-43
|
||||
HBR01:0,0,0,0,11,0,0,-30
|
||||
HBR01:0,0,0,0,11,0,0,-17
|
||||
HBR01:0,0,0,0,11,0,0,-11
|
||||
HBR01:0,0,0,0,11,0,0,-20
|
||||
HBR01:0,0,0,0,11,0,0,-11
|
||||
HBR01:0,0,0,0,11,0,0,-16
|
||||
HBR01:0,0,0,0,11,0,0,-11
|
||||
HBR01:0,0,0,0,11,0,0,-12
|
||||
HBR01:0,0,0,0,11,0,0,-7
|
||||
HBR01:0,0,0,0,11,0,0,-9
|
||||
HBR01:0,0,0,0,11,0,0,-11
|
||||
HBR01:0,0,0,0,11,0,0,-10
|
||||
HBR01:0,0,0,0,11,0,0,-14
|
||||
HBR01:0,0,0,0,11,0,0,-24
|
||||
HBR01:0,0,0,0,11,0,0,-15
|
||||
HBR01:0,0,0,0,11,0,0,-20
|
||||
HBR01:0,0,0,0,11,0,0,-16
|
||||
HBR01:0,0,0,0,11,0,0,-11
|
||||
HBR01:0,0,0,0,11,0,0,-12
|
||||
HBR01:0,0,0,0,12,0,0,9
|
||||
HBR01:0,0,0,0,12,0,0,-11
|
||||
HBR01:0,0,0,0,12,0,0,-11
|
||||
HBR01:0,0,0,0,12,0,0,-16
|
||||
HBR01:0,0,0,0,12,0,0,11
|
||||
HBR01:0,0,0,0,12,0,0,11
|
||||
HBR01:0,0,0,0,12,0,0,1
|
||||
HBR01:0,0,0,0,12,0,0,-4
|
||||
HBR01:0,0,0,0,12,0,0,-13
|
||||
HBR01:0,0,0,0,12,0,0,-3
|
||||
HBR01:0,0,0,0,12,0,0,-6
|
||||
HBR01:0,0,0,0,12,0,0,3
|
||||
HBR01:0,0,0,0,12,0,0,-12
|
||||
HBR01:0,0,0,0,12,0,0,-23
|
||||
HBR01:0,0,0,0,12,0,0,-40
|
||||
HBR01:0,0,0,0,12,0,0,-22
|
||||
HBR01:0,0,0,0,12,0,0,-40
|
||||
HBR01:0,0,0,0,12,0,0,-20
|
||||
HBR01:0,0,0,0,12,0,0,-21
|
||||
HBR01:0,0,0,0,12,0,0,-42
|
||||
HBR01:0,0,0,0,12,0,0,-40
|
||||
HBR01:0,0,0,0,13,0,0,-51
|
||||
HBR01:0,0,0,0,13,0,0,-40
|
||||
HBR01:0,0,0,0,13,0,0,-40
|
||||
HBR01:0,0,0,0,13,0,0,-40
|
||||
HBR01:0,0,0,0,13,0,0,-45
|
||||
HBR01:0,0,0,0,13,0,0,-48
|
||||
HBR01:0,0,0,0,13,0,0,-35
|
||||
HBR01:0,0,0,0,13,0,0,-38
|
||||
HBR01:0,0,0,0,13,0,0,-48
|
||||
HBR01:0,0,0,0,13,0,0,-19
|
||||
HBR01:0,0,0,0,13,0,0,-12
|
||||
HBR01:0,0,0,0,13,0,0,-26
|
||||
HBR01:0,0,0,0,13,0,0,-23
|
||||
HBR01:0,0,0,0,13,0,0,-9
|
||||
HBR01:0,0,0,0,13,0,0,4
|
||||
HBR01:0,0,0,0,13,0,0,-1
|
||||
HBR01:0,0,0,0,13,0,0,0
|
||||
HBR01:0,0,0,0,13,0,0,0
|
||||
HBR01:0,0,0,0,13,0,0,-1
|
||||
HBR01:0,0,0,0,13,0,0,1
|
||||
HBR01:0,0,0,0,13,0,0,0
|
||||
HBR01:0,0,0,0,13,0,0,-1
|
||||
HBR01:0,0,0,0,13,0,0,0
|
||||
HBR01:0,0,0,0,13,0,0,21
|
||||
HBR01:0,0,0,0,13,0,0,15
|
||||
HBR01:0,0,0,0,13,0,0,0
|
||||
HBR01:0,0,0,0,13,0,0,21
|
||||
HBR01:0,0,0,0,14,0,0,18
|
||||
HBR01:0,0,0,0,14,0,0,0
|
||||
HBR01:0,0,0,0,14,0,0,2
|
||||
HBR01:0,0,0,0,14,0,0,4
|
||||
HBR01:0,0,0,0,14,0,0,-3
|
||||
HBR01:0,0,0,0,14,0,0,5
|
||||
HBR01:0,0,0,0,14,0,0,-1
|
||||
HBR01:0,0,0,0,14,0,0,-22
|
||||
HBR01:0,0,0,0,14,0,0,4
|
||||
HBR01:0,0,0,0,14,0,0,3
|
||||
HBR01:0,0,0,0,14,0,0,-18
|
||||
HBR01:0,0,0,0,14,0,0,-16
|
||||
HBR01:0,0,0,0,14,0,0,-6
|
||||
HBR01:0,0,0,0,14,0,0,-23
|
||||
HBR01:0,0,0,0,14,0,0,-28
|
||||
HBR01:0,0,0,0,14,0,0,-24
|
||||
HBR01:0,0,0,0,14,0,0,-24
|
||||
HBR01:0,0,0,0,14,0,0,-39
|
||||
HBR01:0,0,0,0,14,0,0,-24
|
||||
HBR01:0,0,0,0,14,0,0,-38
|
||||
HBR01:0,0,0,0,14,0,0,-40
|
||||
HBR01:0,0,0,0,14,0,0,-37
|
||||
HBR01:0,0,0,0,14,0,0,-40
|
||||
HBR01:0,0,0,0,14,0,0,-40
|
||||
HBR01:0,0,0,0,14,0,0,-40
|
||||
HBR01:0,0,0,0,15,0,0,-40
|
||||
HBR01:0,0,0,0,15,0,0,-45
|
||||
HBR01:0,0,0,0,15,0,0,-36
|
||||
HBR01:0,0,0,0,15,0,0,-29
|
||||
HBR01:0,0,0,0,15,0,0,-10
|
||||
HBR01:0,0,0,0,15,0,0,-43
|
||||
HBR01:0,0,0,0,15,0,0,-12
|
||||
HBR01:0,0,0,0,15,0,0,-40
|
||||
HBR01:0,0,0,0,15,0,0,-33
|
||||
HBR01:0,0,0,0,15,0,0,-37
|
||||
HBR01:0,0,0,0,15,0,0,-30
|
||||
HBR01:0,0,0,0,15,0,0,-35
|
||||
HBR01:0,0,0,0,15,0,0,-26
|
||||
HBR01:0,0,0,0,15,0,0,2
|
||||
HBR01:0,0,0,0,15,0,0,-30
|
||||
HBR01:0,0,0,0,15,0,0,0
|
||||
HBR01:0,0,0,0,15,0,0,-22
|
||||
HBR01:0,0,0,0,15,0,0,-23
|
||||
HBR01:0,0,0,0,15,0,0,-30
|
||||
HBR01:0,0,0,0,15,0,0,-33
|
||||
HBR01:0,0,0,0,15,0,0,-15
|
||||
HBR01:0,0,0,0,15,0,0,0
|
||||
HBR01:0,0,0,0,15,0,0,-12
|
||||
HBR01:0,0,0,0,15,0,0,-20
|
||||
HBR01:0,0,0,0,15,0,0,-20
|
||||
HBR01:0,0,0,0,15,0,0,-10
|
||||
HBR01:0,0,0,0,15,0,0,-25
|
||||
HBR01:0,0,0,0,15,0,0,-35
|
||||
HBR01:0,0,0,0,15,0,0,-40
|
||||
HBR01:0,0,0,0,15,0,0,-40
|
||||
HBR01:0,0,0,0,15,0,0,-40
|
||||
HBR01:0,0,0,0,15,0,0,-62
|
||||
HBR01:0,0,0,0,15,0,0,-33
|
||||
HBR01:0,0,0,0,15,0,0,-24
|
||||
HBR01:0,0,0,0,15,0,0,-30
|
||||
HBR01:0,0,0,0,15,0,0,-40
|
||||
HBR01:0,0,0,0,15,0,0,-45
|
||||
HBR01:0,0,0,0,15,0,0,-29
|
||||
HBR01:0,0,0,0,15,0,0,-31
|
||||
HBR01:0,0,0,0,15,0,0,-37
|
||||
HBR01:0,0,0,0,15,0,0,-43
|
||||
HBR01:0,0,0,0,15,0,0,-23
|
||||
HBR01:0,0,0,0,15,0,0,-18
|
||||
HBR01:0,0,0,0,15,0,0,-7
|
||||
HBR01:0,0,0,0,15,0,0,-21
|
||||
HBR01:0,0,0,0,15,0,0,0
|
||||
HBR01:0,0,0,0,15,0,0,-40
|
||||
HBR01:0,0,0,0,16,0,0,-69
|
||||
HBR01:0,0,0,0,16,0,0,-30
|
||||
HBR01:0,0,0,0,16,0,0,-34
|
||||
HBR01:0,0,0,0,16,0,0,-1
|
||||
HBR01:0,0,0,0,16,0,0,-1
|
||||
HBR01:0,0,0,0,16,0,0,-8
|
||||
HBR01:0,0,0,0,16,0,0,-26
|
||||
HBR01:0,0,0,0,16,0,0,-15
|
||||
HBR01:0,0,0,0,16,0,0,-16
|
||||
HBR01:0,0,0,0,16,0,0,-17
|
||||
HBR01:0,0,0,0,16,0,0,-26
|
||||
HBR01:0,0,0,0,16,0,0,-14
|
||||
HBR01:0,0,0,0,16,0,0,-16
|
||||
HBR01:0,0,0,0,16,0,0,-18
|
||||
HBR01:0,0,0,0,16,0,0,-14
|
||||
HBR01:0,0,0,0,16,0,0,-9
|
||||
HBR01:0,0,0,0,16,0,0,-9
|
||||
HBR01:0,0,0,0,16,0,0,-14
|
||||
HBR01:0,0,0,0,16,0,0,-16
|
||||
HBR01:0,0,0,0,16,0,0,-30
|
||||
HBR01:0,0,0,0,16,0,0,-40
|
||||
HBR01:0,0,0,0,15,0,0,-23
|
||||
HBR01:0,0,0,0,15,0,0,-27
|
||||
HBR01:0,0,0,0,15,0,0,-23
|
||||
HBR01:0,0,0,0,15,0,0,-11
|
||||
HBR01:0,0,0,0,15,0,0,-21
|
||||
HBR01:0,0,0,0,15,0,0,-22
|
||||
HBR01:0,0,0,0,15,0,0,-12
|
||||
HBR01:0,0,0,0,15,0,0,-16
|
||||
HBR01:0,0,0,0,15,0,0,-35
|
||||
HBR01:0,0,0,0,15,0,0,-18
|
||||
HBR01:0,0,0,0,15,0,0,-18
|
||||
HBR01:0,0,0,0,15,0,0,-40
|
||||
HBR01:0,0,0,0,15,0,0,-25
|
||||
HBR01:0,0,0,0,16,0,0,-1
|
||||
HBR01:0,0,0,0,16,0,0,0
|
||||
HBR01:0,0,0,0,16,0,0,0
|
||||
HBR01:0,0,0,0,16,0,0,-24
|
||||
HBR01:0,0,0,0,16,0,0,-16
|
||||
HBR01:0,0,0,0,16,0,0,-24
|
||||
HBR01:0,0,0,0,16,0,0,-22
|
||||
HBR01:0,0,0,0,16,0,0,-38
|
||||
HBR01:0,0,0,0,16,0,0,-32
|
||||
HBR01:0,0,0,0,16,0,0,-20
|
||||
HBR01:0,0,0,0,16,0,0,-21
|
||||
HBR01:0,0,0,0,16,0,0,-9
|
||||
HBR01:0,0,0,0,16,0,0,-19
|
||||
HBR01:0,0,0,0,16,0,0,-31
|
||||
HBR01:0,0,0,0,16,0,0,-34
|
||||
HBR01:0,0,0,0,15,0,0,-22
|
||||
HBR01:0,0,0,0,15,0,0,-34
|
||||
HBR01:0,0,0,0,15,0,0,-21
|
||||
HBR01:0,0,0,0,15,0,0,-26
|
||||
HBR01:0,0,0,0,15,0,0,-40
|
||||
HBR01:0,0,0,0,15,0,0,-46
|
||||
HBR01:0,0,0,0,15,0,0,-7
|
||||
HBR01:0,0,0,0,15,0,0,-4
|
||||
HBR01:0,0,0,0,15,0,0,-26
|
||||
HBR01:0,0,0,0,15,0,0,-30
|
||||
HBR01:0,0,0,0,15,0,0,-14
|
||||
HBR01:0,0,0,0,15,0,0,-19
|
||||
HBR01:0,0,0,0,15,0,0,-28
|
||||
HBR01:0,0,0,0,15,0,0,0
|
||||
HBR01:0,0,0,0,15,0,0,-9
|
||||
HBR01:0,0,0,0,15,0,0,-13
|
||||
HBR01:0,0,0,0,15,0,0,-27
|
||||
HBR01:0,0,0,0,15,0,0,-13
|
||||
HBR01:0,0,0,0,15,0,0,0
|
||||
HBR01:0,0,0,0,15,0,0,-29
|
||||
HBR01:0,0,0,0,15,0,0,-13
|
||||
HBR01:0,0,0,0,15,0,0,-25
|
||||
HBR01:0,0,0,0,15,0,0,-32
|
||||
HBR01:0,0,0,0,15,0,0,-25
|
||||
HBR01:0,0,0,0,15,0,0,-18
|
||||
HBR01:0,0,0,0,15,0,0,-18
|
||||
HBR01:0,0,0,0,15,0,0,-14
|
||||
HBR01:0,0,0,0,15,0,0,-17
|
||||
HBR01:0,0,0,0,15,0,0,-29
|
||||
HBR01:0,0,0,0,15,0,0,-31
|
||||
HBR01:0,0,0,0,15,0,0,-39
|
||||
HBR01:0,0,0,0,15,0,0,-11
|
||||
HBR01:0,0,0,0,15,0,0,-24
|
||||
HBR01:0,0,0,0,15,0,0,-40
|
||||
HBR01:0,0,0,0,15,0,0,-40
|
||||
HBR01:0,0,0,0,15,0,0,-33
|
||||
HBR01:0,0,0,0,15,0,0,-41
|
||||
HBR01:0,0,0,0,15,0,0,-32
|
||||
HBR01:0,0,0,0,15,0,0,-32
|
||||
HBR01:0,0,0,0,15,0,0,-32
|
||||
HBR01:0,0,0,0,15,0,0,-18
|
||||
HBR01:0,0,0,0,15,0,0,-28
|
||||
HBR01:0,0,0,0,15,0,0,-21
|
||||
HBR01:0,0,0,0,15,0,0,-18
|
||||
HBR01:0,0,0,0,15,0,0,-7
|
||||
HBR01:0,0,0,0,15,0,0,0
|
||||
HBR01:0,0,0,0,15,0,0,-14
|
||||
HBR01:0,0,0,0,15,0,0,-11
|
||||
HBR01:0,0,0,0,15,0,0,-23
|
||||
HBR01:0,0,0,0,15,0,0,-22
|
||||
HBR01:0,0,0,0,15,0,0,-14
|
||||
HBR01:0,0,0,0,15,0,0,-25
|
||||
HBR01:0,0,0,0,15,0,0,-15
|
||||
HBR01:0,0,0,0,15,0,0,-28
|
||||
HBR01:0,0,0,0,15,0,0,1
|
||||
HBR01:0,0,0,0,15,0,0,1
|
||||
HBR01:0,0,0,0,15,0,0,-20
|
||||
HBR01:0,0,0,0,15,0,0,-40
|
||||
HBR01:0,0,0,0,15,0,0,-25
|
||||
HBR01:0,0,0,0,15,0,0,0
|
||||
HBR01:0,0,0,0,15,0,0,0
|
||||
HBR01:0,0,0,0,15,0,0,-28
|
||||
HBR01:0,0,0,0,15,0,0,-20
|
||||
HBR01:0,0,0,0,15,0,0,-10
|
||||
HBR01:0,0,0,0,15,0,0,-16
|
||||
HBR01:0,0,0,0,15,0,0,-12
|
||||
HBR01:0,0,0,0,15,0,0,-26
|
||||
HBR01:0,0,0,0,15,0,0,-33
|
||||
HBR01:0,0,0,0,15,0,0,-21
|
||||
HBR01:0,0,0,0,15,0,0,-22
|
||||
HBR01:0,0,0,0,15,0,0,-29
|
||||
HBR01:0,0,0,0,15,0,0,-40
|
||||
HBR01:0,0,0,0,15,0,0,-33
|
||||
HBR01:0,0,0,0,15,0,0,-28
|
||||
HBR01:0,0,0,0,15,0,0,-19
|
||||
HBR01:0,0,0,0,15,0,0,-26
|
||||
HBR01:0,0,0,0,15,0,0,-30
|
||||
HBR01:0,0,0,0,15,0,0,-20
|
||||
HBR01:0,0,0,0,15,0,0,-29
|
||||
HBR01:0,0,0,0,15,0,0,-35
|
||||
HBR01:0,0,0,0,15,0,0,-25
|
||||
HBR01:0,0,0,0,15,0,0,-27
|
||||
HBR01:0,0,0,0,15,0,0,-38
|
||||
HBR01:0,0,0,0,15,0,0,-27
|
||||
HBR01:0,0,0,0,15,0,0,-27
|
||||
HBR01:0,0,0,0,15,0,0,-48
|
||||
HBR01:0,0,0,0,15,0,0,-31
|
||||
HBR01:0,0,0,0,15,0,0,-26
|
||||
HBR01:0,0,0,0,15,0,0,-16
|
||||
HBR01:0,0,0,0,15,0,0,-6
|
||||
HBR01:0,0,0,0,15,0,0,-13
|
||||
HBR01:0,0,0,0,15,0,0,-11
|
||||
HBR01:0,0,0,0,14,0,0,-20
|
||||
HBR01:0,0,0,0,14,0,0,-58
|
||||
HBR01:0,0,0,0,14,0,0,-25
|
||||
HBR01:0,0,0,0,14,0,0,-11
|
||||
Reference in New Issue
Block a user