Merge #565 into sdk602patch0626 from refs/changes/565/head
author袁清梦 <3449750381@qq.com>
Sat, 20 Jul 2024 10:41:06 +0000 (10:41 +0000)
committersnickersun@163.com <accounts_62b90f7fcf51ade33b6f628e@mail.teambition.com>
Sat, 20 Jul 2024 10:41:06 +0000 (10:41 +0000)
Code review from 袁清梦

* refs/changes/565/head: (2 commits squashed)

  - 优化动态心率显示算法保证数据较为稳定

  - Merge branch &#39;sdk602patch0626&#39; of https://codeup.aliyun.com/64191456b9581d62e8f81ac0/FD230 into sdk602patch0626

Signed-off-by: Caisking <3449750381@qq.com>
Reviewed-by: snickersun@163.com <accounts_62b90f7fcf51ade33b6f628e@mail.teambition.com>
Merged-by: snickersun@163.com <accounts_62b90f7fcf51ade33b6f628e@mail.teambition.com>
link: https://codeup.aliyun.com/64191456b9581d62e8f81ac0/FD230/change/565
code/sdk/application/wearable/fd230app/nativeui/traing/include/TraingModel.h
code/sdk/application/wearable/fd230app/nativeui/traing/src/TraingModel.cpp

index 766141c..827b975 100644 (file)
 #include <string>
 #include <memory>
 #include <cmath>
+#include <vector>
+#include <queue>
+#include <iostream>
+#include <unordered_map>
 
 #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<int> heartRateData;                                        // 存储所有心率数据
+        std::vector<int> recentHeartRates;                                     // 显示心率
+        std::unordered_map<int, int> delayed;                                  // 延迟删除的心率值
+        std::priority_queue<int> maxHeap;                                      // 最大堆,存储最大心率值
+        std::priority_queue<int, std::vector<int>, std::greater<int>> minHeap; // 最小堆,存储最小心率值
+        int totalHeartRate;                                                    // 总心率
+        int countHeartRate;                                                    // 心率计数
+        const int windowSize = 5;                                             // 移动平均窗口大小
+
         /*std::unique_ptr<SportData> sportdata;*/ // 使用 std::unique_ptr 管理 SportData 对象
     };
 }
index 1b26dbf..d04880b 100644 (file)
@@ -41,6 +41,8 @@
 #include <list>
 #include "samgr_lite.h"
 #include <vector>
+#include <queue>
+#include <numeric>
 #include "traingrecord/include/TraingRecordModel.h"
 
 #define MAX_FILE_PATH_LENGTH 128
@@ -367,6 +369,34 @@ namespace OHOS
     {
         return sportState_;
     }
+    // 计算平均心率的函数
+    double CalculateAverageHeartRate(const std::vector<int> &heartRates)
+    {
+        if (heartRates.size() <= 2)
+        {
+            // 如果心率数据点少于等于2,无法去掉最高和最低值
+            return 0.0; // 或者返回心率的平均值
+        }
+
+        // 创建一个副本以避免修改原始数据
+        std::vector<int> 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<double>(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)