优化雷达信号处理,添加心率和呼吸率平滑处理和异常值检测
This commit is contained in:
58
.trae/documents/plan_20260203_063543.md
Normal file
58
.trae/documents/plan_20260203_063543.md
Normal file
@@ -0,0 +1,58 @@
|
||||
# 修改蓝牙发送雷达数据模式
|
||||
|
||||
## 1. 需求分析
|
||||
- 修改蓝牙发送机制,实现基于数据变化的发送
|
||||
- 当心率、呼吸、存在检测、体动状态、睡眠状态发生变化时才发送
|
||||
- 根据小程序设定的时间间隔进行定时检测
|
||||
- 如果检测到变化,立即更新数据
|
||||
- 按设定间隔检测数据更新,有变化则发送全部数据
|
||||
|
||||
## 2. 实现方案
|
||||
|
||||
### 2.1 添加数据存储结构
|
||||
- 在 `radar_manager.h` 中添加 `LastSentData` 结构体,用于存储上一次发送的数据
|
||||
- 包含需要检测变化的字段:心率、呼吸率、存在状态、运动状态、睡眠状态
|
||||
|
||||
### 2.2 修改全局变量
|
||||
- 在 `radar_manager.cpp` 中添加 `lastSentData` 全局变量
|
||||
- 添加 `lastCheckTime` 变量用于记录上次检测时间
|
||||
|
||||
### 2.3 实现数据变化检测函数
|
||||
- 添加 `isDataChanged()` 函数,比较当前数据与上次发送数据
|
||||
- 实现阈值判断,避免微小波动导致频繁发送
|
||||
|
||||
### 2.4 重写 BLE 数据发送任务
|
||||
- 修改 `bleSendTask` 任务,实现以下逻辑:
|
||||
1. 定时检测数据变化(基于 `continuousSendInterval`)
|
||||
2. 检测到变化时立即发送数据
|
||||
3. 发送后更新 `lastSentData` 为当前数据
|
||||
4. 保持与现有命令处理的兼容性
|
||||
|
||||
### 2.5 保持命令处理逻辑
|
||||
- 保留 `processStartContinuousSend` 和 `processStopContinuousSend` 函数
|
||||
- 确保小程序可以正常控制发送模式和间隔
|
||||
|
||||
## 3. 代码修改点
|
||||
|
||||
### 3.1 radar_manager.h
|
||||
- 添加 `LastSentData` 结构体定义
|
||||
- 在全局变量声明中添加 `lastSentData` 和相关变量
|
||||
|
||||
### 3.2 radar_manager.cpp
|
||||
- 初始化 `lastSentData` 变量
|
||||
- 实现 `isDataChanged()` 函数
|
||||
- 重写 `bleSendTask` 任务函数
|
||||
- 确保数据发送格式与现有代码保持一致
|
||||
|
||||
## 4. 技术要点
|
||||
- 使用阈值检测避免微小数据波动导致的频繁发送
|
||||
- 保持定时检测机制,确保数据及时性
|
||||
- 维持与现有 BLE API 的兼容性
|
||||
- 按照现有代码的注释格式编写新代码
|
||||
- 确保内存使用合理,避免内存泄漏
|
||||
|
||||
## 5. 预期效果
|
||||
- 减少不必要的蓝牙数据传输
|
||||
- 只在数据发生实际变化时发送
|
||||
- 保持数据的实时性和准确性
|
||||
- 与现有小程序控制逻辑完全兼容
|
||||
37
.trae/documents/plan_20260204_063627.md
Normal file
37
.trae/documents/plan_20260204_063627.md
Normal file
@@ -0,0 +1,37 @@
|
||||
## 实现BLE API规范计划
|
||||
|
||||
### 步骤1: 分析现有实现
|
||||
- 检查main_backup.cpp.bak中的BLE命令处理函数实现
|
||||
- 确认它们是否符合BLE_API.md中定义的API规范
|
||||
- 识别需要移植的函数
|
||||
|
||||
### 步骤2: 移植BLE命令处理函数
|
||||
- 将以下函数从main_backup.cpp.bak移植到main.cpp中:
|
||||
- processWiFiConfigCommand() - 处理WiFi配置命令
|
||||
- processScanWiFi() - 处理WiFi扫描命令
|
||||
- processGetSavedNetworks() - 处理获取已保存网络命令
|
||||
- processEchoRequest() - 处理回显测试命令
|
||||
- processSetDeviceId() - 处理设置设备ID命令
|
||||
- processQueryStatus() - 处理查询状态命令
|
||||
- processQueryRadarData() - 处理查询雷达数据命令
|
||||
- processStartContinuousSend() - 处理启动持续发送命令
|
||||
- processStopContinuousSend() - 处理停止持续发送命令
|
||||
|
||||
### 步骤3: 更新processBLEConfig函数
|
||||
- 更新main.cpp中的processBLEConfig()函数,添加命令处理逻辑
|
||||
- 实现JSON解析和命令分发功能
|
||||
- 确保按照API规范处理所有支持的命令
|
||||
|
||||
### 步骤4: 添加必要的辅助函数
|
||||
- 添加sendRawEchoResponse()函数,处理回显测试响应
|
||||
- 确保所有函数都按照API规范返回正确的响应格式
|
||||
|
||||
### 步骤5: 验证实现
|
||||
- 编译项目,确保没有错误
|
||||
- 检查所有函数是否符合API规范
|
||||
- 确认响应格式是否与API文档一致
|
||||
|
||||
### 预期结果
|
||||
- 项目能够成功编译
|
||||
- BLE命令处理函数符合BLE_API.md中定义的API规范
|
||||
- 设备能够正确处理所有支持的BLE命令并返回符合规范的响应
|
||||
26
.trae/documents/修复编译错误计划.md
Normal file
26
.trae/documents/修复编译错误计划.md
Normal file
@@ -0,0 +1,26 @@
|
||||
# 修复编译错误计划
|
||||
|
||||
## 问题分析
|
||||
编译时出现了多个错误,主要是因为删除了 `radar_manager.cpp` 和 `radar_manager.h` 文件后,`main.cpp` 中仍然引用了这些文件中定义的变量和函数。
|
||||
|
||||
## 修复步骤
|
||||
|
||||
### 1. 修复 SAMPLE_RATE 重复定义
|
||||
- 删除 `main.cpp` 中的 `SAMPLE_RATE` 定义,使用 `radar_vitals.h` 中的定义
|
||||
|
||||
### 2. 添加缺失的 BLE 相关代码
|
||||
- 添加 `MyServerCallbacks` 类定义
|
||||
- 添加 `MyCallbacks` 类定义
|
||||
- 添加 `processBLEConfig` 函数定义
|
||||
- 添加 `sendStatusToBLE` 函数定义
|
||||
|
||||
### 3. 修复其他未声明的变量和函数
|
||||
- 确保所有使用的变量和函数都有正确的声明
|
||||
|
||||
### 4. 测试编译
|
||||
- 运行 PlatformIO 编译命令,确保所有错误都已修复
|
||||
|
||||
## 预期结果
|
||||
- 编译成功,没有错误
|
||||
- 系统能够正常启动和运行
|
||||
- 新的雷达模块能够正常工作
|
||||
49
.trae/documents/删除无用代码优化项目.md
Normal file
49
.trae/documents/删除无用代码优化项目.md
Normal file
@@ -0,0 +1,49 @@
|
||||
# 删除无用代码优化项目
|
||||
|
||||
## 目标
|
||||
在保持API功能不变的情况下,删除项目中没有用的代码部分,减少Flash占用,提高代码可读性。
|
||||
|
||||
## 分析结果
|
||||
通过对代码库的分析,我发现了以下可以删除的无用部分:
|
||||
|
||||
### 1. main.cpp
|
||||
- **I2S相关配置**:第17-18行的I2S配置定义,因为使用的是直接ADC读取方式
|
||||
- **setup_i2s()函数**:第127-131行,只是一个空函数,没有实际功能
|
||||
- **重复的雷达处理调用**:第166行的`radar_vitals_process(&sample, &vitals);`调用,因为已经在第163行调用了`radar_process(I_buf, Q_buf, DATA_LEN);`
|
||||
- **冗余的WiFi连接成功消息**:第357行,总是会执行,即使WiFi连接失败
|
||||
- **注释掉的代码**:第361-364行,已经被注释掉,没有实际功能
|
||||
|
||||
### 2. radar_vitals.cpp
|
||||
- **旧的信号处理函数**:第29-41行的`bp_breath`和`bp_heart`函数,使用的是新的处理方法
|
||||
- **重复的相位解缠函数**:第44-50行的`unwrap`函数,在`radar_process`中已经实现
|
||||
- **旧的BPM计算函数**:第53-80行的`calc_bpm`函数,使用的是新的频率估计方法
|
||||
- **旧的主处理函数**:第203-252行的`radar_vitals_process`函数,使用的是新的`radar_process`函数
|
||||
- **未使用的系统参数**:第12-15行的实时系统参数定义
|
||||
- **未使用的缓冲区大小**:第18行的`RADAR_BUFFER_SIZE`定义
|
||||
- **未使用的队列长度**:第19行的`QUEUE_LENGTH`定义
|
||||
- **未使用的变量**:第14-17行的`phase_buf`、`buf_idx`、`I_mean`、`Q_mean`、`prev_phase`变量
|
||||
|
||||
### 3. ble_api.cpp
|
||||
- **未使用的函数**:第60-73行的`sendCustomJSONData`函数
|
||||
- **未使用的函数**:第194-197行的`sendRawEchoResponse`函数
|
||||
- **无效的超时处理**:第369-378行的超时处理代码,因为相关变量没有被设置
|
||||
|
||||
### 4. io_flash.h
|
||||
- **重复的常量定义**:第40-50行的常量定义,已经在`io_flash.cpp`中定义
|
||||
|
||||
### 5. io_flash.cpp
|
||||
- **重复的局部变量**:第15-23行的局部变量定义,在函数内部已经有局部定义
|
||||
|
||||
## 实施步骤
|
||||
1. **修改main.cpp**:删除I2S相关配置、setup_i2s()函数、重复的雷达处理调用、冗余的WiFi连接成功消息和注释掉的代码
|
||||
2. **修改radar_vitals.cpp**:删除旧的信号处理函数、重复的相位解缠函数、旧的BPM计算函数、旧的主处理函数、未使用的系统参数和变量
|
||||
3. **修改ble_api.cpp**:删除未使用的函数和无效的超时处理代码
|
||||
4. **修改io_flash.h**:删除重复的常量定义
|
||||
5. **修改io_flash.cpp**:删除重复的局部变量
|
||||
6. **编译项目**:确保项目能够成功编译,没有错误
|
||||
7. **验证功能**:确保API功能保持不变
|
||||
|
||||
## 预期结果
|
||||
- 减少Flash占用,提高代码可读性
|
||||
- 保持API功能不变,确保系统正常运行
|
||||
- 消除冗余代码,减少维护成本
|
||||
22
.trae/documents/备份并删除radar_manager.h文件.md
Normal file
22
.trae/documents/备份并删除radar_manager.h文件.md
Normal file
@@ -0,0 +1,22 @@
|
||||
## 备份并删除radar_manager.h文件计划
|
||||
|
||||
### 步骤1: 确认文件引用情况
|
||||
- 检查是否有其他文件引用了radar_manager.h
|
||||
- 验证main.cpp中确实没有包含radar_manager.h
|
||||
|
||||
### 步骤2: 备份文件
|
||||
- 将radar_manager.h文件重命名为radar_manager.h.bak作为备份
|
||||
|
||||
### 步骤3: 删除文件
|
||||
- 删除radar_manager.h文件
|
||||
|
||||
### 步骤4: 验证编译
|
||||
- 运行platformio编译命令,确保项目能够成功编译
|
||||
- 检查是否有任何编译错误
|
||||
|
||||
### 步骤5: 清理临时文件
|
||||
- 确认项目编译成功后,可以选择删除备份文件(如果需要)
|
||||
|
||||
### 预期结果
|
||||
- 项目能够成功编译,没有任何错误
|
||||
- 证明radar_manager.h文件是冗余的,可以安全删除
|
||||
@@ -15,3 +15,5 @@ framework = arduino
|
||||
lib_deps =
|
||||
bblanchon/ArduinoJson@^7.4.2
|
||||
emelianov/modbus-esp8266@^4.1.0
|
||||
build_flags = -Wno-redefinition
|
||||
source_filter = +<*> -<radar_manager.cpp>
|
||||
|
||||
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
|
||||
288
src/io_flash.cpp
Normal file
288
src/io_flash.cpp
Normal file
@@ -0,0 +1,288 @@
|
||||
#include "io_flash.h"
|
||||
#include <esp_task_wdt.h>
|
||||
|
||||
// 全局变量定义
|
||||
Preferences preferences; // 声明preferences变量
|
||||
uint16_t currentDeviceId; // 当前设备ID
|
||||
bool clearConfigRequested = false; // 清除配置请求标志
|
||||
bool forceLedOff = false; // 强制关闭LED标志
|
||||
ConfigClearStatus currentConfigClearStatus = CONFIG_NORMAL; // 当前配置清除状态
|
||||
|
||||
// 常量定义
|
||||
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 unsigned long CLEAR_CONFIG_DURATION = 3000; // 清除配置持续时间(毫秒)
|
||||
|
||||
/**
|
||||
* @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
|
||||
*/
|
||||
void clearStoredConfig() {
|
||||
Serial.println("🧹 开始清除存储的配置...");
|
||||
|
||||
uint16_t oldDeviceId = preferences.getUShort("deviceId", 0);
|
||||
|
||||
preferences.remove("deviceId");
|
||||
|
||||
Serial.println("✅ 配置已清除完成");
|
||||
Serial.printf("🗑️ 被清除的设备ID: %u\n", oldDeviceId);
|
||||
|
||||
currentDeviceId = 1001;
|
||||
|
||||
Serial.println("🔄 已清除Flash与内存中的配置");
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* @brief 配置清除LED控制任务
|
||||
* 根据配置清除状态控制CONFIG_CLEAR_PIN引脚的LED显示
|
||||
* @param parameter 任务参数(未使用)
|
||||
*/
|
||||
void configClearLedTask(void *parameter) {
|
||||
unsigned long lastConfigBlinkTime = 0; // 上次配置清除LED闪烁时间
|
||||
bool configLedState = false; // 配置清除LED状态
|
||||
int configBreatheValue = 0; // 配置清除呼吸灯当前亮度值
|
||||
bool configBreatheIncreasing = true; // 配置清除呼吸灯是否在增加亮度
|
||||
|
||||
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);
|
||||
} else {
|
||||
ledcWrite(0, 255); // 保持LED常亮
|
||||
}
|
||||
vTaskDelay(100 / portTICK_PERIOD_MS);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* @brief IO和Flash初始化函数
|
||||
* 初始化IO引脚、Flash存储和LED控制
|
||||
*/
|
||||
void io_flash_init() {
|
||||
// 初始化IO引脚
|
||||
pinMode(BOOT_BUTTON_PIN, INPUT);
|
||||
pinMode(NETWORK_LED_PIN, OUTPUT);
|
||||
pinMode(CONFIG_CLEAR_PIN, OUTPUT);
|
||||
pinMode(GPIO8, OUTPUT);
|
||||
pinMode(GPIO9, OUTPUT);
|
||||
pinMode(Radar_Start, OUTPUT); // 初始化雷达启动引脚
|
||||
|
||||
// 设置初始状态
|
||||
digitalWrite(CONFIG_CLEAR_PIN, LOW);
|
||||
digitalWrite(GPIO8, LOW);
|
||||
digitalWrite(GPIO9, LOW);
|
||||
digitalWrite(NETWORK_LED_PIN, LOW);
|
||||
digitalWrite(CONFIG_CLEAR_PIN, LOW);
|
||||
digitalWrite(Radar_Start, HIGH); // 拉高雷达启动引脚,启动雷达
|
||||
|
||||
// 初始化LED控制
|
||||
ledcSetup(0, 5000, 8);
|
||||
ledcSetup(1, 5000, 8);
|
||||
ledcAttachPin(NETWORK_LED_PIN, 0);
|
||||
ledcAttachPin(CONFIG_CLEAR_PIN, 1);
|
||||
|
||||
// 初始化Flash存储
|
||||
preferences.begin("radar_data", false);
|
||||
|
||||
// 加载设备配置
|
||||
Serial.println("💾 加载设备配置...");
|
||||
loadDeviceId();
|
||||
|
||||
// 初始化任务
|
||||
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
|
||||
);
|
||||
}
|
||||
42
src/io_flash.h
Normal file
42
src/io_flash.h
Normal file
@@ -0,0 +1,42 @@
|
||||
#ifndef IO_FLASH_H
|
||||
#define IO_FLASH_H
|
||||
|
||||
#include <Arduino.h>
|
||||
#include <Preferences.h>
|
||||
|
||||
// 引脚定义
|
||||
#define BOOT_BUTTON_PIN 0 // Boot按钮引脚
|
||||
#define NETWORK_LED_PIN 35 // 网络状态LED指示灯开发板48引脚,雷达板35引脚
|
||||
#define CONFIG_CLEAR_PIN 4 // 配置清除指示灯
|
||||
#define Radar_Start 36 // 定义雷达启动引脚拉高启动
|
||||
#define GPIO8 8 // 自定义GPIO8
|
||||
#define GPIO9 9 // 自定义GPIO9
|
||||
|
||||
// 配置清除指示灯状态枚举
|
||||
enum ConfigClearStatus {
|
||||
CONFIG_NORMAL, // 正常运行 - LOW
|
||||
CONFIG_PREPARING, // 准备清除 - HIGH
|
||||
CONFIG_CLEARING, // 清除过程中 - 呼吸灯
|
||||
CONFIG_COMPLETED // 清除完成 - 快速闪烁3次
|
||||
};
|
||||
|
||||
// 全局变量声明
|
||||
extern Preferences preferences;
|
||||
extern uint16_t currentDeviceId; // 当前设备ID
|
||||
extern bool clearConfigRequested; // 清除配置请求标志
|
||||
extern bool forceLedOff; // 强制关闭LED标志
|
||||
extern ConfigClearStatus currentConfigClearStatus; // 当前配置清除状态
|
||||
|
||||
// 函数声明
|
||||
void checkBootButton();
|
||||
void loadDeviceId();
|
||||
void saveDeviceId();
|
||||
void clearStoredConfig();
|
||||
void io_flash_init();
|
||||
|
||||
// 任务声明
|
||||
void configClearLedTask(void *parameter);
|
||||
void bootButtonMonitorTask(void *parameter);
|
||||
void ledControlTask(void *parameter);
|
||||
|
||||
#endif // IO_FLASH_H
|
||||
2740
src/main.cpp
2740
src/main.cpp
File diff suppressed because it is too large
Load Diff
200
src/radar_vitals.cpp
Normal file
200
src/radar_vitals.cpp
Normal file
@@ -0,0 +1,200 @@
|
||||
#include "radar_vitals.h"
|
||||
#include <math.h>
|
||||
#include <driver/adc.h>
|
||||
|
||||
/******** 参数 ********/
|
||||
#define ADC_MAX 4095.0f
|
||||
#define VREF 3.3f
|
||||
#define TWO_PI 6.2831853f
|
||||
#define RADAR_LAMBDA 0.0125f // 24GHz 波长(米)
|
||||
|
||||
/******** 内部状态 ********/
|
||||
static float fs = 20.0f;
|
||||
|
||||
// 状态变量
|
||||
static float phase[DATA_LEN];
|
||||
static float unwrap_phase[DATA_LEN];
|
||||
static human_state_t human_state = NO_PERSON;
|
||||
static float resp_bpm = 0;
|
||||
static float heart_bpm = 0;
|
||||
|
||||
// 平滑处理相关变量
|
||||
static float resp_bpm_history[10] = {0};
|
||||
static float heart_bpm_history[10] = {0};
|
||||
static int history_index = 0;
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
/******** 新的雷达处理函数 ********/
|
||||
void radar_process(float *I, float *Q, int len)
|
||||
{
|
||||
/* 1. 去直流 (DC Removal)
|
||||
* I'(n) = I(n) - mean(I)
|
||||
* Q'(n) = Q(n) - mean(Q)
|
||||
*/
|
||||
float meanI = 0, meanQ = 0;
|
||||
for(int i=0;i<len;i++){ meanI += I[i]; meanQ += Q[i]; }
|
||||
meanI /= len; meanQ /= len;
|
||||
for(int i=0;i<len;i++){ I[i] -= meanI; Q[i] -= meanQ; }
|
||||
|
||||
/* 2. IQ → 相位
|
||||
* φ(n) = atan2(Q, I)
|
||||
*/
|
||||
for(int i=0;i<len;i++){
|
||||
phase[i] = atan2f(Q[i], I[i]);
|
||||
}
|
||||
|
||||
/* 3. 相位展开 (Unwrap)
|
||||
* Δφ > π → Δφ -= 2π
|
||||
*/
|
||||
unwrap_phase[0] = phase[0];
|
||||
for(int i=1;i<len;i++){
|
||||
float d = phase[i] - phase[i-1];
|
||||
if(d > M_PI) d -= 2*M_PI;
|
||||
if(d < -M_PI) d += 2*M_PI;
|
||||
unwrap_phase[i] = unwrap_phase[i-1] + d;
|
||||
}
|
||||
|
||||
/* 4. 空人判断:相位方差
|
||||
* Var(φ) = E[(φ-μ)^2]
|
||||
*/
|
||||
float mean = 0, var = 0;
|
||||
for(int i=0;i<len;i++) mean += unwrap_phase[i];
|
||||
mean /= len;
|
||||
for(int i=0;i<len;i++){
|
||||
float d = unwrap_phase[i] - mean;
|
||||
var += d * d;
|
||||
}
|
||||
var /= len;
|
||||
|
||||
if(var < 1e-4){
|
||||
human_state = NO_PERSON;
|
||||
return;
|
||||
}
|
||||
|
||||
/* 5. 活动检测:相位差分能量
|
||||
* E = mean[(φ(n)-φ(n-1))^2]
|
||||
*/
|
||||
float energy = 0;
|
||||
for(int i=1;i<len;i++){
|
||||
float d = unwrap_phase[i] - unwrap_phase[i-1];
|
||||
energy += d * d;
|
||||
}
|
||||
energy /= len;
|
||||
|
||||
if(energy > 0.05){
|
||||
human_state = MOTION;
|
||||
} else {
|
||||
human_state = STATIC_HUMAN;
|
||||
}
|
||||
|
||||
/* 6. 呼吸 / 心率频率估计
|
||||
* 方法:频段能量最大值搜索(简化 DFT)
|
||||
* Fs_eff = SAMPLE_RATE / decimation
|
||||
*/
|
||||
const float Fs_eff = 200.0f; // 4kHz / 20
|
||||
|
||||
// ---- 呼吸:0.1 ~ 0.5 Hz ----
|
||||
float max_resp_mag = 0; float resp_freq = 0;
|
||||
for(float f=0.1f; f<=0.5f; f+=0.02f){
|
||||
float re=0, im=0;
|
||||
for(int n=0;n<len;n++){
|
||||
float ang = 2*M_PI*f*n/Fs_eff;
|
||||
re += unwrap_phase[n]*cosf(ang);
|
||||
im -= unwrap_phase[n]*sinf(ang);
|
||||
}
|
||||
float mag = re*re + im*im;
|
||||
if(mag > max_resp_mag){ max_resp_mag = mag; resp_freq = f; }
|
||||
}
|
||||
float new_resp_bpm = resp_freq * 60.0f;
|
||||
|
||||
// ---- 心率:0.8 ~ 2.5 Hz ----
|
||||
float max_hr_mag = 0; float hr_freq = 0;
|
||||
for(float f=0.8f; f<=2.5f; f+=0.05f){
|
||||
float re=0, im=0;
|
||||
for(int n=0;n<len;n++){
|
||||
float ang = 2*M_PI*f*n/Fs_eff;
|
||||
re += unwrap_phase[n]*cosf(ang);
|
||||
im -= unwrap_phase[n]*sinf(ang);
|
||||
}
|
||||
float mag = re*re + im*im;
|
||||
if(mag > max_hr_mag){ max_hr_mag = mag; hr_freq = f; }
|
||||
}
|
||||
float new_heart_bpm = hr_freq * 60.0f;
|
||||
|
||||
// 异常值检测和过滤
|
||||
if(new_heart_bpm < 40 || new_heart_bpm > 180){
|
||||
new_heart_bpm = heart_bpm; // 使用上次值
|
||||
}
|
||||
if(new_resp_bpm < 4 || new_resp_bpm > 40){
|
||||
new_resp_bpm = resp_bpm; // 使用上次值
|
||||
}
|
||||
|
||||
// 移动平均滤波
|
||||
heart_bpm_history[history_index] = new_heart_bpm;
|
||||
resp_bpm_history[history_index] = new_resp_bpm;
|
||||
history_index = (history_index + 1) % 10;
|
||||
|
||||
// 计算平均值
|
||||
float sum_hr = 0, sum_resp = 0;
|
||||
for(int i=0; i<10; i++){
|
||||
sum_hr += heart_bpm_history[i];
|
||||
sum_resp += resp_bpm_history[i];
|
||||
}
|
||||
heart_bpm = sum_hr / 10.0f;
|
||||
resp_bpm = sum_resp / 10.0f;
|
||||
|
||||
// 四舍五入到最接近的整数
|
||||
heart_bpm = roundf(heart_bpm);
|
||||
resp_bpm = roundf(resp_bpm);
|
||||
}
|
||||
|
||||
|
||||
/******** 获取人体状态 ********/
|
||||
human_state_t radar_get_state(){ return human_state; }
|
||||
|
||||
/******** 获取呼吸率 ********/
|
||||
float radar_get_resp_bpm(){ return resp_bpm; }
|
||||
|
||||
/******** 获取心率 ********/
|
||||
float radar_get_heart_bpm(){ return heart_bpm; }
|
||||
|
||||
/******** 初始化 ********/
|
||||
void radar_vitals_init(float fs_hz)
|
||||
{
|
||||
fs = fs_hz;
|
||||
human_state = NO_PERSON;
|
||||
resp_bpm = 0;
|
||||
heart_bpm = 0;
|
||||
|
||||
// 初始化历史数据数组
|
||||
for(int i=0; i<10; i++){
|
||||
resp_bpm_history[i] = 0;
|
||||
heart_bpm_history[i] = 0;
|
||||
}
|
||||
history_index = 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 雷达初始化函数
|
||||
* 初始化雷达相关的ADC和引脚
|
||||
*/
|
||||
void radar_init() {
|
||||
// 配置雷达I/Q引脚为ADC输入
|
||||
pinMode(PIN_I, INPUT);
|
||||
pinMode(PIN_Q, INPUT);
|
||||
|
||||
// 初始化ADC
|
||||
adc1_config_width(ADC_WIDTH_BIT_12);
|
||||
adc1_config_channel_atten(ADC1_CHANNEL_0, ADC_ATTEN_DB_12); // PIN_I (GPIO1) 对应 ADC1_CHANNEL_0
|
||||
adc1_config_channel_atten(ADC1_CHANNEL_1, ADC_ATTEN_DB_12); // PIN_Q (GPIO2) 对应 ADC1_CHANNEL_1
|
||||
|
||||
Serial.println("🏗️ 初始化雷达管理器...");
|
||||
radar_vitals_init(SAMPLE_RATE);
|
||||
}
|
||||
|
||||
|
||||
51
src/radar_vitals.h
Normal file
51
src/radar_vitals.h
Normal file
@@ -0,0 +1,51 @@
|
||||
#ifndef RADAR_VITALS_H
|
||||
#define RADAR_VITALS_H
|
||||
#include <stdint.h>
|
||||
#include <Arduino.h>
|
||||
|
||||
#define PIN_I 9
|
||||
#define PIN_Q 10
|
||||
|
||||
#define ADC_WIDTH ADC_WIDTH_BIT_12 // 配置ADC宽度为12位
|
||||
#define ADC_ATTEN ADC_ATTEN_DB_12 // 配置ADC衰减为12dB
|
||||
|
||||
|
||||
|
||||
#define SAMPLE_RATE 4000 // 基础采样率(Hz)
|
||||
#define DATA_LEN 256 // 数据长度
|
||||
|
||||
// 人体状态枚举
|
||||
typedef enum {
|
||||
NO_PERSON = 0,
|
||||
STATIC_HUMAN,
|
||||
MOTION
|
||||
} human_state_t;
|
||||
|
||||
/******** 雷达采样 ********/
|
||||
typedef struct {
|
||||
uint16_t i_value; // ADC 原始值
|
||||
uint16_t q_value; // ADC 原始值
|
||||
uint32_t timestamp; // ms
|
||||
} radar_sample_t;
|
||||
|
||||
/******** 输出结果 ********/
|
||||
typedef struct {
|
||||
float heart_bpm;
|
||||
float breath_bpm;
|
||||
uint8_t valid; // 1: 有效
|
||||
human_state_t state; // 人体状态
|
||||
} radar_vitals_t;
|
||||
|
||||
/******** 接口 ********/
|
||||
void radar_vitals_init(float fs_hz);
|
||||
void radar_vitals_process(
|
||||
const radar_sample_t *sample,
|
||||
radar_vitals_t *out
|
||||
);
|
||||
void radar_process(float *I, float *Q, int len);
|
||||
human_state_t radar_get_state();
|
||||
float radar_get_resp_bpm();
|
||||
float radar_get_heart_bpm();
|
||||
void radar_init();
|
||||
|
||||
#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()
|
||||
@@ -1,82 +0,0 @@
|
||||
// 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("=== 测试完成 ===");
|
||||
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