From 665fd5b692263fd88dde100c85c104a0f3cd49dd Mon Sep 17 00:00:00 2001 From: =?utf8?q?=E8=A2=81=E6=B8=85=E6=A2=A6?= <3449750381@qq.com> Date: Sat, 20 Jul 2024 10:41:06 +0000 Subject: [PATCH] Merge #565 into sdk602patch0626 from refs/changes/565/head MIME-Version: 1.0 Content-Type: text/plain; charset=utf8 Content-Transfer-Encoding: 8bit Code review from 袁清梦 * refs/changes/565/head: (2 commits squashed) - 优化动态心率显示算法保证数据较为稳定 - Merge branch 'sdk602patch0626' of https://codeup.aliyun.com/64191456b9581d62e8f81ac0/FD230 into sdk602patch0626 Signed-off-by: Caisking <3449750381@qq.com> Reviewed-by: snickersun@163.com Merged-by: snickersun@163.com CR-link: https://codeup.aliyun.com/64191456b9581d62e8f81ac0/FD230/change/565 --- .../nativeui/traing/include/TraingModel.h | 57 ++- .../nativeui/traing/src/TraingModel.cpp | 328 ++++++++++++++---- 2 files changed, 297 insertions(+), 88 deletions(-) diff --git a/code/sdk/application/wearable/fd230app/nativeui/traing/include/TraingModel.h b/code/sdk/application/wearable/fd230app/nativeui/traing/include/TraingModel.h index 766141caf..827b975a1 100644 --- a/code/sdk/application/wearable/fd230app/nativeui/traing/include/TraingModel.h +++ b/code/sdk/application/wearable/fd230app/nativeui/traing/include/TraingModel.h @@ -25,6 +25,10 @@ #include #include #include +#include +#include +#include +#include #define GILROY_REGULAR_FONT "./font_library/gilroy-regular-3.ttf" #define GILROY_BOLD_FONT "./font_library/gilroy-bold-4.ttf" @@ -184,11 +188,11 @@ namespace OHOS Exercisetype sportType; // 训练类型(0:健身类 1:跳舞类 2:休闲运动 3:球类 4:水上运动:5:冰上运动:6:极限运动:7:其他) UIResourceTextId sportId; // 训练Id ExerciseTag sportTagType; // 训练目标类型(0: 距离 1:时长 2:卡路里 3:步数) - int apc; //平均配速 - int fpc; //最快配速 - int spc; //最慢配速 - int cdc; //步频 - int std; //步幅 + int apc; // 平均配速 + int fpc; // 最快配速 + int spc; // 最慢配速 + int cdc; // 步频 + int std; // 步幅 void Initialize() { @@ -203,12 +207,17 @@ namespace OHOS heart = 0; avgheart = 0; tag = 0; - start_date = nullptr; - start_time = nullptr; - end_time = nullptr; + start_date = ""; + start_time = ""; + end_time = ""; sportType = Exercisetype::FITNESS; sportId = UIResourceTextId::STR_EXTREME_SPORTS; sportTagType = ExerciseTag::UNKNOW; + apc = 0; + fpc = 0; + spc = 0; + cdc = 0; + std = 0; } void Reset() @@ -252,8 +261,6 @@ namespace OHOS void SetSportdata(SportData *data); /*获取运动卡路里*/ int GetCal(); - /*设置时间戳*/ - void SetTimestamp(); /*解析时间戳*/ std::string AnalysisTimestamp(uint64_t timestamp); /*获取运动总时长*/ @@ -292,6 +299,8 @@ namespace OHOS /*平均心率计算*/ int CalculateAverageHeartRate(int newHeartRate); + /*显示心率计算*/ + int CalculateFilteredHeartRate(int newHeartRate); /*保存训练记录到json文件*/ bool CheckDataJson(); @@ -305,6 +314,21 @@ namespace OHOS /*获取训练类Id*/ UIResourceTextId GetSportTypeId(Exercisetype type); + /*重新初始化训练配置*/ + void ResetSharedTrainingData(); + /*设置训练开始与结束*/ + void SetTimestamp(); + /*设置开始时间*/ + void SetStartDateTime(); + /*设置结束时间*/ + void SetEndTime(); + /*获取日期*/ + bool GetFormattedDate(std::string &dateOutput, const char *errorMsg); + /*获取时间*/ + bool GetFormattedTime(std::string &timeOutput, const char *errorMsg); + /*保存数据*/ + void RecordSportData(); + private: TraingModel(const TraingModel &) = delete; TraingModel &operator=(const TraingModel &) = delete; @@ -316,9 +340,7 @@ namespace OHOS ExerciseTag sportTagType_ = ExerciseTag::UNKNOW; // 训练目标类型 int sportTag_ = 0; // 训练目标 - int totalHeartRate = 0; - int countHeartRate = 0; - int freq = 0; // 训练发送数据频率 + int freq = 0; // 训练发送数据频率 bool Gettagstuats{false}; // 训练达标标志位 bool Startstate{false}; // 开始训练标志位 @@ -327,6 +349,15 @@ namespace OHOS bool MomentumFlag{false}; // 训练量达标标志位 bool Openstate{false}; // 运动是否开始 + std::vector heartRateData; // 存储所有心率数据 + std::vector recentHeartRates; // 显示心率 + std::unordered_map delayed; // 延迟删除的心率值 + std::priority_queue maxHeap; // 最大堆,存储最大心率值 + std::priority_queue, std::greater> minHeap; // 最小堆,存储最小心率值 + int totalHeartRate; // 总心率 + int countHeartRate; // 心率计数 + const int windowSize = 5; // 移动平均窗口大小 + /*std::unique_ptr sportdata;*/ // 使用 std::unique_ptr 管理 SportData 对象 }; } diff --git a/code/sdk/application/wearable/fd230app/nativeui/traing/src/TraingModel.cpp b/code/sdk/application/wearable/fd230app/nativeui/traing/src/TraingModel.cpp index 1b26dbfbe..d04880b9a 100644 --- a/code/sdk/application/wearable/fd230app/nativeui/traing/src/TraingModel.cpp +++ b/code/sdk/application/wearable/fd230app/nativeui/traing/src/TraingModel.cpp @@ -41,6 +41,8 @@ #include #include "samgr_lite.h" #include +#include +#include #include "traingrecord/include/TraingRecordModel.h" #define MAX_FILE_PATH_LENGTH 128 @@ -367,6 +369,34 @@ namespace OHOS { return sportState_; } + // 计算平均心率的函数 + double CalculateAverageHeartRate(const std::vector &heartRates) + { + if (heartRates.size() <= 2) + { + // 如果心率数据点少于等于2,无法去掉最高和最低值 + return 0.0; // 或者返回心率的平均值 + } + + // 创建一个副本以避免修改原始数据 + std::vector sortedHeartRates = heartRates; + + // 排序 + std::sort(sortedHeartRates.begin(), sortedHeartRates.end()); + + // 去掉一个最高值和一个最低值 + sortedHeartRates.erase(sortedHeartRates.begin()); // 去掉最低值 + sortedHeartRates.pop_back(); // 去掉最高值 + + // 计算剩余心率的平均值 + double sum = 0; + for (int rate : sortedHeartRates) + { + sum += rate; + } + + return sum / sortedHeartRates.size(); // 返回平均值 + } /*分析数据*/ void TraingModel::AnalysisData(cywee_activity_result_t *cyweeInfo) { @@ -382,17 +412,20 @@ namespace OHOS sportdata->std = cyweeInfo->step_length; sportdata->pc = cyweeInfo->pace; sportdata->distance = cyweeInfo->distance; - + /*最慢配速*/ if (sportdata->spc < sportdata->pc) { sportdata->spc = sportdata->pc; } - - sportdata->heart = cyweeInfo->hrs; - SetIntensity(sportdata->heart); // 设置训练强度 - if (sportdata->heart > 50) + if (cyweeInfo->hrs > 50) { + sportdata->heart = CalculateFilteredHeartRate(cyweeInfo->hrs); // 处理显示心率 sportdata->avgheart = CalculateAverageHeartRate(cyweeInfo->hrs); // 处理正常的平均心率 + SetIntensity(sportdata->heart); // 设置训练强度 + } + else + { + sportdata->heart = cyweeInfo->hrs; } // 达到最高心率进行心率提醒弹窗 if (sportdata->heart >= ConfigModel::GetInstance()->get_configData_int(ConfigModel::CONFIGMODEL_ITEM_TRAINING_HEART_MAX)) @@ -519,7 +552,31 @@ namespace OHOS { return sportdata->sportTagType; } - /*设置时间*/ + /*重置训练配置*/ + void TraingModel::ResetSharedTrainingData() + { + sportdata->Reset(); // 重置结构体 + heartRateData.clear(); // 清空心率数据 + recentHeartRates.clear(); + delayed.clear(); // 重置 delayed 字典 + + while (!maxHeap.empty()) + { // 清空最大堆 + maxHeap.pop(); + } + while (!minHeap.empty()) + { // 清空最小堆 + minHeap.pop(); + } + + totalHeartRate = 0; + countHeartRate = 0; + + Gettagstuats = false; // 重置训练达标标志位 + Openstate = true; // 开启运动开始标志位 + } + + /*设置运动开始与结束*/ void TraingModel::SetTimestamp() { uint32_t time; @@ -528,19 +585,19 @@ namespace OHOS WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP, "Failed to get timestamp"); return; } + ExerciseState currentState = GetSportState(); // 获取当前运动状态 - /*增加标志位互斥保障"开始""结束"的唯一性操作*/ - if (currentState == ExerciseState::Start && (!Startstate)) + + // 增加标志位互斥保障"开始"和"结束"的唯一性操作 + if (currentState == ExerciseState::Start && !Startstate) { - sportdata->Reset(); // 重置结构体 + ResetSharedTrainingData(); // 重置共享训练数据 - /*将Model的成员变量赋值到结构体里面*/ printf("SetTimestamp::time:%u \r\n", time); sportdata->starttime = time; sportdata->sportType = sportType_; // 训练类型 - Gettagstuats = false; // 重置训练达标标志位 - Openstate = true; // 开启运动开始标志位 - if (sportTag_) // 有值才设置[训练目标] + + if (sportTag_) // 有值才设置[训练目标] { sportdata->sportTagType = sportTagType_; sportdata->tag = sportTag_; @@ -548,77 +605,114 @@ namespace OHOS (Openingway == true) ? (printf("app发起运动\n")) : (printf("手表发起运动\n")); - char *start_date = (char *)malloc(9 * sizeof(char)); // 分配内存给start_date - if (uapi_get_YYMMDD_Time(start_date, '/') != ERRCODE_SUCC) - { - WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP, "Failed to get start date"); - free(start_date); // 释放内存 - return; - } - WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP, "start_date[%s]\r\n", start_date); - sportdata->start_date = std::string(start_date); - free(start_date); // 释放内存 - - char *start_time = (char *)malloc(9 * sizeof(char)); // 分配内存给start_time - if (uapi_get_HHMMSS_Time(start_time, ':', true) != ERRCODE_SUCC) - { - WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP, "Failed to get start time"); - free(start_time); // 释放内存 - return; - } - sportdata->start_time = std::string(start_time); - free(start_time); // 释放内存 - WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP, "sportdata->start_time [%s]\r\n", (sportdata->start_time).c_str()); + // 设置起始日期和时间 + SetStartDateTime(); Startstate = true; Endstate = false; // 确保"开始"和"结束"状态互斥 - printf("开始运动\r\n"); + WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP, "开始运动"); } - else if (currentState == ExerciseState::End && (!Endstate)) + else if (currentState == ExerciseState::End && !Endstate) { sportdata->endtime = time; - char *end_time = (char *)malloc(9 * sizeof(char)); // 分配内存给end_time - if (uapi_get_HHMMSS_Time(end_time, ':', true) != ERRCODE_SUCC) - { - WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP, "Failed to get end time"); - free(end_time); // 释放内存 - return; - } - // WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP,"end_time [%s]\r\n", end_time); - sportdata->end_time = std::string(end_time); + SetEndTime(); + sportState_ = ExerciseState::General; Openstate = false; // 关闭运动标志位 - free(end_time); // 释放内存 - WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP, "sportdata->end_time [%s]\r\n", (sportdata->end_time).c_str()); + // 记录运动数据 + RecordSportData(); + + Startstate = false; + Endstate = true; + + // 重置训练目标 [发生在训练前] + sportTag_ = 0; + sportTagType_ = ExerciseTag::UNKNOW; + + WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP, "结束运动"); + } + } + /*设置开始时间*/ + void TraingModel::SetStartDateTime() + { + std::string startDate, startTime; + + if (!GetFormattedDate(startDate, "Failed to get start date")) + return; + if (!GetFormattedTime(startTime, "Failed to get start time")) + return; + + sportdata->start_date = startDate; + sportdata->start_time = startTime; + + WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP, "sportdata->start_time [%s]\r\n", sportdata->start_time.c_str()); + } + /*设置结束时间*/ + void TraingModel::SetEndTime() + { + std::string endTime; + + if (GetFormattedTime(endTime, "Failed to get end time")) + { + sportdata->end_time = endTime; + } + + WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP, "sportdata->end_time [%s]\r\n", sportdata->end_time.c_str()); + } + /*获取日期字符串*/ + bool TraingModel::GetFormattedDate(std::string &dateOutput, const char *errorMsg) + { + char *date = (char *)malloc(9 * sizeof(char)); // 分配内存给start_date + if (uapi_get_YYMMDD_Time(date, '/') != ERRCODE_SUCC) + { + WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP, "%s", errorMsg); + free(date); + return false; + } + dateOutput = std::string(date); + free(date); // 释放内存 + return true; + } + /*获取时间字符串*/ + bool TraingModel::GetFormattedTime(std::string &timeOutput, const char *errorMsg) + { + char *time = (char *)malloc(9 * sizeof(char)); // 分配内存给start_time + if (uapi_get_HHMMSS_Time(time, ':', true) != ERRCODE_SUCC) + { + WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP, "%s", errorMsg); + free(time); + return false; + } + timeOutput = std::string(time); + free(time); // 释放内存 + return true; + } + /*保存数据*/ + void TraingModel::RecordSportData() + { #if READTEST - if (SaveSportData() == false) + if (SaveSportData() == false) + { + WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP, "SaveSportData error!"); + } +#else + if (MomentumFlag) + { + GetGoaldegree(); // 设置目标完成度 + if (SaveSportData()) { - WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP, "SaveSportData error!"); + TraingRecordModel::GetInstance().SetJsonStatus(true); + WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP, "SaveSportData ok!"); } -#else - /*存储正常数据[运动量达标]*/ - if (MomentumFlag) + else { - GetGoaldegree(); // 设置目标完成度 - if (SaveSportData()) - { - TraingRecordModel::GetInstance().SetJsonStatus(true); - WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP, "SaveSportData ok!"); - } - else - { - WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP, "SaveSportData error!"); - } + WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP, "SaveSportData error!"); } -#endif - Startstate = false; - Endstate = true; - - printf("结束运动\r\n"); } +#endif } // 获取训练时长时间 std::string TraingModel::GetTrainingTime() @@ -652,17 +746,90 @@ namespace OHOS return ss.str(); } - /*处理平均心率*/ int TraingModel::CalculateAverageHeartRate(int newHeartRate) { - totalHeartRate += newHeartRate; // 累加心率值 - countHeartRate++; // 记录心率值的个数 + // 将新的心率添加到数据列表 + heartRateData.push_back(newHeartRate); + totalHeartRate += newHeartRate; + countHeartRate++; + + // 将新的心率插入到堆中 + maxHeap.push(newHeartRate); + minHeap.push(newHeartRate); + + // 如果超过窗口大小,移除最早的心率 + if (heartRateData.size() > windowSize) + { + int oldestRate = heartRateData[0]; + totalHeartRate -= oldestRate; + countHeartRate--; + + // 将老旧值从列表中移除 + heartRateData.erase(heartRateData.begin()); - int averageHeartRate = totalHeartRate / countHeartRate; // 计算平均心率 - return averageHeartRate; + // 标记老旧值为待删除 + delayed[oldestRate]++; + } + + // 清理堆中的老旧值 + while (!maxHeap.empty() && delayed[maxHeap.top()] > 0) + { + delayed[maxHeap.top()]--; + maxHeap.pop(); + } + + while (!minHeap.empty() && delayed[minHeap.top()] > 0) + { + delayed[minHeap.top()]--; + minHeap.pop(); + } + + WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP, "新心率: %d, 当前心率总和: %d, 心率计数: %d", + newHeartRate, totalHeartRate, countHeartRate); + + // 如果心率数量少于 3,直接返回当前的平均值 + if (countHeartRate < 3) + { + return totalHeartRate / countHeartRate; // 无法去除最大最小值 + } + + // 取出当前未被删除的最大和最小的心率 + int maxRate = maxHeap.top(); + int minRate = minHeap.top(); + + // 进行去极值后的平均计算 + int average = (totalHeartRate - maxRate - minRate) / (countHeartRate - 2); + + // 打印计算结果 + WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP, "去极值后的平均心率: %d (去掉最大值: %d, 去掉最小值: %d)", + average, maxRate, minRate); + + return average; } - /*设置训练目标类型以及训练目标*/ + // 显示心率 + int TraingModel::CalculateFilteredHeartRate(int newHeartRate) + { + // 将新的心率添加到最近心率列表 + recentHeartRates.push_back(newHeartRate); + + // 如果超过窗口大小,移除最早的心率 + if (recentHeartRates.size() > windowSize) + { + recentHeartRates.erase(recentHeartRates.begin()); + } + + // 计算当前窗口中的平均心率 + int total = std::accumulate(recentHeartRates.begin(), recentHeartRates.end(), 0); + + // 打印当前窗口的平均心率 + WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP, "过滤后当前窗口的平均心率: %d", + (total / recentHeartRates.size())); + + return total / recentHeartRates.size(); // 返回计算后的平均值 + } + + // 设置训练目标类型以及训练目标 void TraingModel::SetSportTag(ExerciseTag tagtpye, int tag) { WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP, "tagtpye[%d] tag[%d]", tagtpye, tag); @@ -685,7 +852,18 @@ namespace OHOS */ int age = ConfigModel::GetInstance()->GetUserAge(); int maxHeartRate = MAX_HEART_RATE - age; - double intensityPercentage = (heart / (double)maxHeartRate) * 100; + + // 确保最大心率为正值 + if (maxHeartRate <= 0) + { + WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP, "maxHeartRate %d is error", maxHeartRate); + return; + } + + // 将 heart 转换为 double 以避免整数除法 + double heartRateAsDouble = static_cast(heart); + double intensityPercentage = (heartRateAsDouble / (double)maxHeartRate) * 100; + if (intensityPercentage < LOW_INTENSITY_THRESHOLD) { // 较低强度 @@ -731,7 +909,7 @@ namespace OHOS } int TraingModel::GetSportTagData() { - if (sportTag_) + if (sportdata->tag) { // 根据设置的训练目标类型去对应相对的数据 switch (sportdata->sportTagType) -- 2.17.1