void StartMeasureTimer();
void StopMeasureTimer();
void StopContactGoodTimer();
+ void OnMeasureTimerTick();
void FinishMeasuringFlow(ECGMainView *mainView);
void OnBottomButtonPressed(ECGMainView *mainView);
void ShowContactGood(ECGMainView *mainView);
bool IsWaitingContact(ECGMainView *mainView) const;
void SetScreenAlwaysOnForEcg(bool enable);
+ void StartPpgAfibSession();
+ void StopPpgAfibSession();
+ bool PollAfibResult();
+ void OnMeasureTimeout();
- bool screenAlwaysOnForEcg_{false};
+ static constexpr int32_t kMeasureTickMs = 200; // 测量定时器周期
+ static constexpr int32_t kPollIntervalMs = 1000; // 测量定时器间隔
+
+ bool screenAlwaysOnForEcg_{false}; // 屏幕长亮
+ bool measureDataReceived_{false}; // 测量数据是否收到
+ int32_t pollElapsedMs_{0}; // 测量定时器流逝时间
};
} // namespace OHOS
static ECGMeasuringView *GetInstance();
UIViewGroup *InitView();
void ResetMeasurement();
- /** @return true when countdown finished */
- bool OnTimerTick();
+ void UpdateWaveformFromRaw();
+ void AppendHeartRatePoint(uint8_t hr);
+ /** 每 1s 调用一次,返回 true 表示 30s 倒计时已结束 */
+ bool OnCountdownSecondTick();
void StopMeasurement();
+ static constexpr int16_t kMeasureDurationSec = 30;
+
private:
void InitChart();
- void UpdateWaveform();
+ void RefreshChart();
void UpdateCountdownLabel();
+ int16_t MapHeartRateToChartY(uint8_t hr) const;
UIViewGroup *group_{nullptr};
UILabel *titleLabel_{nullptr};
UILabel *countdownLabel_{nullptr};
UILabel *hintLabel_{nullptr};
Point wavePoints_[48]{};
- float wavePhase_{0.0f};
- int16_t countdownSec_{30};
+ int16_t countdownSec_{kMeasureDurationSec};
static constexpr int16_t kWavePointCount = 48;
};
namespace OHOS {
+/** 与 ppg_hal afib_result 一致,0xFF 表示测量未完成 */
+static constexpr uint8_t ECG_AFIB_RESULT_INVALID = 0xFF;
+
enum class ECGResultType : uint8_t {
SINUS_RHYTHM = 0,
ATRIAL_FIBRILLATION = 1,
struct ECGRecord {
uint32_t timestamp;
uint8_t bpm;
- ECGResultType result;
+ uint8_t afibResult;
};
class ECGModel {
const std::deque<ECGRecord> &GetHistoryRecords() const;
std::string FormatShortTimestamp(uint32_t timestamp) const;
std::string GetResultText(ECGResultType type) const;
+ std::string GetAfibResultText(uint8_t afibResult) const;
ColorType GetResultColor(ECGResultType type) const;
+ ColorType GetAfibResultColor(uint8_t afibResult) const;
+ ECGResultType MapAfibToResultType(uint8_t afibResult) const;
- void SetMeasureResult(uint8_t bpm, ECGResultType result);
+ void SetMeasureResult(uint8_t bpm, uint8_t afibResult);
uint8_t GetResultBpm() const;
+ uint8_t GetAfibResult() const;
ECGResultType GetResultType() const;
- void AddMeasureToHistory();
+ bool SaveMeasureRecord(uint8_t bpm, uint8_t afibResult);
private:
- ECGRecord lastRecord_{0, 0, ECGResultType::INCONCLUSIVE};
+ bool CreateECGDataDirectory();
+ bool LoadHistoryFromStorage();
+ bool SaveHistoryToStorage();
+
+ ECGRecord lastRecord_{0, 0, ECG_AFIB_RESULT_INVALID};
std::deque<ECGRecord> historyRecords_;
uint8_t resultBpm_{0};
- ECGResultType resultType_{ECGResultType::SINUS_RHYTHM};
+ uint8_t resultAfib_{ECG_AFIB_RESULT_INVALID};
};
} // namespace OHOS
InitImageView(recentHeart_, 129, 182+35+12+8, 54, 54, "ecg_recent_heart", ECG_PATH_HEART_RED_ICON);
- InitUILabel(recentBpm_, 129+54+12, 182+35+12, 66, 66, "ecg_recent_bpm", "--",
+ InitUILabel(recentBpm_, 129+54+8, 182+35+12, 74, 66, "ecg_recent_bpm", "--",
DEFAULT_VECTOR_FONT_FILENAME, 65);
- recentBpm_->SetAlign(TEXT_ALIGNMENT_CENTER, TEXT_ALIGNMENT_CENTER);
+ recentBpm_->SetAlign(TEXT_ALIGNMENT_RIGHT, TEXT_ALIGNMENT_CENTER);
InitUILabel(recentBpmUnit_, 129+54+12+66+8, 258, 72, 30, "ecg_recent_bpm_unit", "次/分",
DEFAULT_VECTOR_FONT_FILENAME, 30);
}
ECGRecord last = model->GetLastRecord();
recentTime_->SetText(model->FormatShortTimestamp(last.timestamp).c_str());
- recentResult_->SetText(model->GetResultText(last.result).c_str());
- recentResult_->SetTextColor(model->GetResultColor(last.result));
+ recentResult_->SetText(model->GetAfibResultText(last.afibResult).c_str());
+ recentResult_->SetTextColor(model->GetAfibResultColor(last.afibResult));
if (last.bpm > 0) {
recentBpm_->SetText(std::to_string(last.bpm).c_str());
} else {
InitUILabel(resultLabel, ECG_HISTORY_CARD_MARGIN + 40 + 38 + 8 + 4, y + 36 + 5, 120, 35, "ecg_list_result", "",
DEFAULT_VECTOR_FONT_FILENAME, 30);
resultLabel->SetAlign(TEXT_ALIGNMENT_LEFT, TEXT_ALIGNMENT_CENTER);
- resultLabel->SetText(model->GetResultText(rec.result).c_str());
- resultLabel->SetTextColor(model->GetResultColor(rec.result));
+ resultLabel->SetText(model->GetAfibResultText(rec.afibResult).c_str());
+ resultLabel->SetTextColor(model->GetAfibResultColor(rec.afibResult));
UILabel *bpmLabel = nullptr;
std::string bpmStr = rec.bpm > 0 ? (std::to_string(rec.bpm) + " 次/分") : "-- 次/分";
#include <cmsis_os2.h>
#include <custom_utils.h>
+#ifdef SUPPORT_PPG_SERVICE
+#include "ppg_hal.h"
+#include "ppg_service.h"
+#endif
+
namespace OHOS {
static ECGMainPresenter *g_pECGMainPresenter = nullptr;
REGIST_MENU_PRO(VIEW_ECG, ECGMainView, ECGMainPresenter, PNG_APPLIST_ECG_IMAGE_BIG,
PNG_APPLIST_ECG_IMAGE, PNG_APPLIST_ECG_IMAGE, "心电", STR_ECG);
+/** 测量定时器回调 */
static void ECGMeasureTimerCallback(void *arg) {
UNUSED(arg);
- ECGMeasuringView *view = ECGMeasuringView::GetInstance();
- if (view == nullptr) {
- return;
- }
- if (view->OnTimerTick()) {
- ECGMainPresenter *presenter = ECGMainPresenter::GetInstance();
- ECGMainView *mainView = ECGMainView::GetInstance();
- if (presenter != nullptr && mainView != nullptr) {
- presenter->FinishMeasuringFlow(mainView);
- }
+ ECGMainPresenter *presenter = ECGMainPresenter::GetInstance();
+ if (presenter != nullptr) {
+ presenter->OnMeasureTimerTick();
}
}
+/** 完成测量流程定时器回调 */
static void ECGCompleteTimerCallback(void *arg) {
UNUSED(arg);
ECGMainView *mainView = ECGMainView::GetInstance();
}
}
+/** 接触良好定时器回调 */
static void ECGContactGoodTimerCallback(void *arg) {
UNUSED(arg);
ECGMainPresenter *presenter = ECGMainPresenter::GetInstance();
void ECGMainPresenter::OnResume(void) {
RootView::GetInstance()->SetOnKeyActListener(this);
ECGMainView *mainView = ECGMainView::GetInstance();
- if (mainView != nullptr && mainView->GetCurrentViewId() == ECG_MEASURING_VIEW) {
+ if (mainView != nullptr && mainView->GetCurrentViewId() == ECG_MEASURING_VIEW &&
+ !measureDataReceived_) {
SetScreenAlwaysOnForEcg(true);
}
}
StopContactGoodTimer();
}
+/** 停止接触良好定时器 */
void ECGMainPresenter::StopContactGoodTimer() {
if (g_ecgContactGoodTimer != nullptr) {
osTimerStop(g_ecgContactGoodTimer);
}
}
+/** 是否等待接触 */
bool ECGMainPresenter::IsWaitingContact(ECGMainView *mainView) const {
return mainView != nullptr && mainView->GetCurrentViewId() == ECG_INSTRUCTION_VIEW &&
mainView->GetInstructionSubType() == ECG_INSTRUCTION_TYPE_WAIT_CONTACT;
}
+/** 显示等待接触 */
void ECGMainPresenter::ShowWaitContact(ECGMainView *mainView) {
int16_t hide = mainView->GetCurrentViewId();
if (hide == ECG_INSTRUCTION_VIEW) {
}
}
+/** 显示接触良好 */
void ECGMainPresenter::ShowContactGood(ECGMainView *mainView) {
StopContactGoodTimer();
mainView->ChangeView(ECG_INSTRUCTION_VIEW, ECG_INSTRUCTION_VIEW, ECG_INSTRUCTION_TYPE_CONTACT_GOOD);
}
void ECGMainPresenter::NavigateBack() {
+ StopMeasureTimer();
NativeAbility::GetInstance().Back(TransitionType::TRANSITION_BACK_TO_HEXAGONS);
}
return true;
}
int16_t current = mainView->GetCurrentViewId();
+ if (current == ECG_MEASURING_VIEW) {
+ StopMeasureTimer();
+ }
if (current == ECG_ENTRY_VIEW) {
NavigateBack();
} else if (current == ECG_HISTORY_VIEW) {
return true;
}
+void ECGMainPresenter::StartPpgAfibSession() {
+#ifdef SUPPORT_PPG_SERVICE
+ ppg_afib_start();
+ WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP, "[ECG] ppg afib start");
+#else
+ (void)0;
+#endif
+}
+
+void ECGMainPresenter::StopPpgAfibSession() {
+#ifdef SUPPORT_PPG_SERVICE
+ ppg_afib_stop();
+ WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP, "[ECG] ppg afib stop");
+#else
+ (void)0;
+#endif
+}
+
void ECGMainPresenter::StartMeasureTimer() {
StopMeasureTimer();
+ measureDataReceived_ = false;
+ pollElapsedMs_ = 0;
uapi_set_screen_on_api();
SetScreenAlwaysOnForEcg(true);
+ StartPpgAfibSession();
g_ecgMeasureTimer =
osTimerNew((osTimerFunc_t)ECGMeasureTimerCallback, osTimerPeriodic, nullptr, nullptr);
if (g_ecgMeasureTimer != nullptr) {
- osTimerStart(g_ecgMeasureTimer, 200);
+ osTimerStart(g_ecgMeasureTimer, kMeasureTickMs);
}
}
void ECGMainPresenter::StopMeasureTimer() {
SetScreenAlwaysOnForEcg(false);
+ StopPpgAfibSession();
if (g_ecgMeasureTimer != nullptr) {
osTimerStop(g_ecgMeasureTimer);
osTimerDelete(g_ecgMeasureTimer);
g_ecgMeasureTimer = nullptr;
}
+ measureDataReceived_ = false;
+ pollElapsedMs_ = 0;
}
+/** 设置屏幕长亮 */
void ECGMainPresenter::SetScreenAlwaysOnForEcg(bool enable) {
if (enable) {
if (!screenAlwaysOnForEcg_) {
}
}
+bool ECGMainPresenter::PollAfibResult() {
+#ifdef SUPPORT_PPG_SERVICE
+ uint8_t ecgHr = 0;
+ uint8_t afibResult = PPG_ECG_AFIB_RESULT_INVALID;
+ ppg_get_ecg_afib(&ecgHr, &afibResult);
+ WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP, "[ECG] poll hr=%u afib=%u", ecgHr, afibResult);
+
+ ECGMeasuringView *view = ECGMeasuringView::GetInstance();
+ if (view != nullptr && ecgHr > 0) {
+ view->AppendHeartRatePoint(ecgHr);
+ }
+
+ if (afibResult != PPG_ECG_AFIB_RESULT_INVALID) {
+ ECGModel *model = ECGModel::GetInstance();
+ model->SetMeasureResult(ecgHr, afibResult);
+ model->SaveMeasureRecord(ecgHr, afibResult);
+ measureDataReceived_ = true;
+ return true;
+ }
+#else
+ (void)0;
+#endif
+ return false;
+}
+
+/** 测量超时 */
+void ECGMainPresenter::OnMeasureTimeout() {
+ StopMeasureTimer();
+ ECGMainView *mainView = ECGMainView::GetInstance();
+ if (mainView != nullptr && mainView->GetCurrentViewId() == ECG_MEASURING_VIEW) {
+ mainView->ChangeView(ECG_MEASURING_VIEW, ECG_ABNORMAL_VIEW);
+ }
+}
+
+/** 测量定时器回调 */
+void ECGMainPresenter::OnMeasureTimerTick() {
+ if (measureDataReceived_) {
+ return;
+ }
+
+ ECGMainView *mainView = ECGMainView::GetInstance();
+ ECGMeasuringView *view = ECGMeasuringView::GetInstance();
+ if (mainView == nullptr || view == nullptr ||
+ mainView->GetCurrentViewId() != ECG_MEASURING_VIEW) {
+ return;
+ }
+
+ view->UpdateWaveformFromRaw();
+
+ pollElapsedMs_ += kMeasureTickMs;
+ if (pollElapsedMs_ >= kPollIntervalMs) {
+ pollElapsedMs_ -= kPollIntervalMs;
+ if (PollAfibResult()) {
+ FinishMeasuringFlow(mainView);
+ return;
+ }
+ if (view->OnCountdownSecondTick()) {
+ WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP, "[ECG] measure timeout without afib result");
+ OnMeasureTimeout();
+ return;
+ }
+ }
+}
+
void ECGMainPresenter::StartMeasureFlow(ECGMainView *mainView) {
ShowWaitContact(mainView);
}
void ECGMainPresenter::FinishMeasuringFlow(ECGMainView *mainView) {
+ if (measureDataReceived_ == false) {
+ return;
+ }
StopMeasureTimer();
- ECGModel *model = ECGModel::GetInstance();
- model->SetMeasureResult(76, ECGResultType::SINUS_RHYTHM);
- model->AddMeasureToHistory();
mainView->ChangeView(ECG_MEASURING_VIEW, ECG_MEASURE_COMPLETE_VIEW);
osTimerId_t completeTimer =
osTimerNew((osTimerFunc_t)ECGCompleteTimerCallback, osTimerOnce, nullptr, nullptr);
return true;
}
if (viewId == ECG_CLICK_EXIT) {
+ if (current == ECG_MEASURING_VIEW) {
+ StopMeasureTimer();
+ }
mainView->ChangeView(current, ECG_ENTRY_VIEW);
return true;
}
if (viewId == ECG_CLICK_RETRY) {
if (current == ECG_ABNORMAL_VIEW) {
- ShowWaitContact(mainView);
+ StartMeasureFlow(mainView);
}
return true;
}
unsigned short keyState = event.GetState();
ECGMainView *mainView = ECGMainView::GetInstance();
- if (keyID == static_cast<uint16_t>(ZliteKeyCode::ZLITE_KEY_FUNC) && keyState == 1 && mainView != nullptr) {
+ if (keyID == static_cast<uint16_t>(ZliteKeyCode::ZLITE_KEY_POWER) && keyState == 1 &&
+ mainView != nullptr && mainView->GetCurrentViewId() == ECG_INSTRUCTION_VIEW) {
OnBottomButtonPressed(mainView);
return true;
}
void ECGMeasureResultView::UpdateText() {
ECGModel *model = ECGModel::GetInstance();
- ECGResultType type = model->GetResultType();
+ uint8_t afibResult = model->GetAfibResult();
uint8_t bpm = model->GetResultBpm();
if (bpmValueLabel_ != nullptr) {
- if (type == ECGResultType::INCONCLUSIVE || bpm == 0) {
+ if (bpm == 0) {
bpmValueLabel_->SetText("--");
} else {
bpmValueLabel_->SetText(std::to_string(bpm).c_str());
}
}
if (resultLabel_ != nullptr) {
- resultLabel_->SetText(model->GetResultText(type).c_str());
- resultLabel_->SetTextColor(model->GetResultColor(type));
+ resultLabel_->SetText(model->GetAfibResultText(afibResult).c_str());
+ resultLabel_->SetTextColor(model->GetAfibResultColor(afibResult));
}
}
#include "ecg/include/ECGViewClickIds.h"
#include "uicomponents/UIComponents.h"
#include "wearable_log.h"
-#include <cmath>
-#ifndef sinf
-#define sinf(x) static_cast<float>(sin(x))
+#ifdef SUPPORT_PPG_SERVICE
+#include "ppg_hal.h"
#endif
namespace OHOS {
}
}
-void ECGMeasuringView::UpdateWaveform() {
+int16_t ECGMeasuringView::MapHeartRateToChartY(uint8_t hr) const {
+ if (hr < 40) {
+ hr = 40;
+ } else if (hr > 180) {
+ hr = 180;
+ }
+ int16_t y = static_cast<int16_t>(20 + (static_cast<int>(hr) - 40) * 180 / 140);
+ if (y < 20) {
+ y = 20;
+ } else if (y > 200) {
+ y = 200;
+ }
+ return y;
+}
+
+void ECGMeasuringView::RefreshChart() {
if (dataSerial_ == nullptr || chart_ == nullptr) {
return;
}
- wavePhase_ += 0.35f;
- for (int i = 0; i < kWavePointCount; i++) {
- float t = wavePhase_ + static_cast<float>(i) * 0.28f;
- wavePoints_[i].x = static_cast<int16_t>(i);
- int16_t base = 110;
- int16_t v = base + static_cast<int16_t>(35.0f * sinf(t)) +
- static_cast<int16_t>(18.0f * sinf(t * 4.1f));
- if (v < 20) {
- v = 20;
- } else if (v > 200) {
- v = 200;
- }
- wavePoints_[i].y = v;
- }
dataSerial_->ClearData();
dataSerial_->AddPoints(wavePoints_, kWavePointCount);
chart_->Invalidate();
}
+void ECGMeasuringView::UpdateWaveformFromRaw() {
+#ifdef SUPPORT_PPG_SERVICE
+ int32_t rawBuf[128] = {0};
+ uint32_t count = ppg_read_ecg_raw(rawBuf, 128);
+ if (count < static_cast<uint32_t>(kWavePointCount)) {
+ return;
+ }
+
+ uint32_t start = count - static_cast<uint32_t>(kWavePointCount);
+ int32_t minVal = rawBuf[start];
+ int32_t maxVal = rawBuf[start];
+ for (int i = 0; i < kWavePointCount; ++i) {
+ int32_t v = rawBuf[start + static_cast<uint32_t>(i)];
+ if (v < minVal) {
+ minVal = v;
+ }
+ if (v > maxVal) {
+ maxVal = v;
+ }
+ }
+ int32_t range = maxVal - minVal;
+ if (range <= 0) {
+ range = 1;
+ }
+
+ for (int i = 0; i < kWavePointCount; ++i) {
+ wavePoints_[i].x = static_cast<int16_t>(i);
+ int32_t v = rawBuf[start + static_cast<uint32_t>(i)];
+ int16_t y = static_cast<int16_t>(20 + (v - minVal) * 180 / range);
+ if (y < 20) {
+ y = 20;
+ } else if (y > 200) {
+ y = 200;
+ }
+ wavePoints_[i].y = y;
+ }
+ RefreshChart();
+#else
+ (void)0;
+#endif
+}
+
+void ECGMeasuringView::AppendHeartRatePoint(uint8_t hr) {
+ if (hr == 0) {
+ return;
+ }
+ int16_t y = MapHeartRateToChartY(hr);
+ for (int i = 0; i < kWavePointCount - 1; ++i) {
+ wavePoints_[i].x = static_cast<int16_t>(i);
+ wavePoints_[i].y = wavePoints_[i + 1].y;
+ }
+ wavePoints_[kWavePointCount - 1].x = static_cast<int16_t>(kWavePointCount - 1);
+ wavePoints_[kWavePointCount - 1].y = y;
+ RefreshChart();
+}
+
void ECGMeasuringView::UpdateCountdownLabel() {
if (countdownLabel_ == nullptr) {
return;
}
void ECGMeasuringView::ResetMeasurement() {
- countdownSec_ = 30;
- wavePhase_ = 0.0f;
+ countdownSec_ = kMeasureDurationSec;
+ for (int i = 0; i < kWavePointCount; ++i) {
+ wavePoints_[i].x = static_cast<int16_t>(i);
+ wavePoints_[i].y = 110;
+ }
UpdateCountdownLabel();
- UpdateWaveform();
+ RefreshChart();
}
-bool ECGMeasuringView::OnTimerTick() {
- UpdateWaveform();
+bool ECGMeasuringView::OnCountdownSecondTick() {
if (countdownSec_ > 0) {
countdownSec_--;
UpdateCountdownLabel();
#include "ecg/include/ECGModel.h"
+#include "custom_utils.h"
#include "kv_store.h"
#include "securec.h"
#include "wearable_log.h"
+#include <cstdio>
#include <ctime>
+#include <sys/stat.h>
+#include <unistd.h>
namespace OHOS {
namespace {
static constexpr const char *ECG_KV_DISCLAIMER = "ecg.disclaimer";
+static constexpr const char *ECG_DATA_DIR = "/user/userdata/health/ecg";
+static constexpr const char *ECG_HISTORY_FILE = "/user/userdata/health/ecg/ECGHistory.bin";
+static constexpr uint32_t ECG_HISTORY_MAGIC = 0x45434701U;
+static constexpr uint32_t ECG_HISTORY_MAX_COUNT = 50U;
+
+struct ECGHistoryStorage {
+ uint32_t magic;
+ uint32_t count;
+ ECGRecord records[50];
+};
} // namespace
ECGModel::ECGModel() { LoadData(); }
return true;
}
+bool ECGModel::CreateECGDataDirectory() {
+ if (access(ECG_DATA_DIR, F_OK) == 0) {
+ return true;
+ }
+ if (mkdir(ECG_DATA_DIR, S_IRWXU) != 0) {
+ WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP, "ECGModel CreateECGDataDirectory mkdir fail");
+ return false;
+ }
+ return true;
+}
+
+bool ECGModel::LoadHistoryFromStorage() {
+ historyRecords_.clear();
+ lastRecord_ = {0, 0, ECG_AFIB_RESULT_INVALID};
+
+ FILE *fp = fopen(ECG_HISTORY_FILE, "rb");
+ if (fp == nullptr) {
+ return false;
+ }
+
+ ECGHistoryStorage storage = {0};
+ size_t readSize = fread(&storage, sizeof(ECGHistoryStorage), 1, fp);
+ fclose(fp);
+
+ if (readSize != 1 || storage.magic != ECG_HISTORY_MAGIC || storage.count == 0 ||
+ storage.count > ECG_HISTORY_MAX_COUNT) {
+ return false;
+ }
+
+ for (uint32_t i = 0; i < storage.count; ++i) {
+ historyRecords_.push_back(storage.records[i]);
+ }
+ if (!historyRecords_.empty()) {
+ lastRecord_ = historyRecords_.front();
+ }
+ return true;
+}
+
+bool ECGModel::SaveHistoryToStorage() {
+ if (!CreateECGDataDirectory()) {
+ return false;
+ }
+
+ ECGHistoryStorage storage = {0};
+ storage.magic = ECG_HISTORY_MAGIC;
+ storage.count = static_cast<uint32_t>(historyRecords_.size());
+ if (storage.count > ECG_HISTORY_MAX_COUNT) {
+ storage.count = ECG_HISTORY_MAX_COUNT;
+ }
+ for (uint32_t i = 0; i < storage.count; ++i) {
+ storage.records[i] = historyRecords_[i];
+ }
+
+ FILE *fp = fopen(ECG_HISTORY_FILE, "wb");
+ if (fp == nullptr) {
+ WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP, "ECGModel SaveHistory fopen fail");
+ return false;
+ }
+ size_t writeSize = fwrite(&storage, sizeof(ECGHistoryStorage), 1, fp);
+ fclose(fp);
+ return writeSize == 1;
+}
+
void ECGModel::LoadData() {
+ if (LoadHistoryFromStorage()) {
+ return;
+ }
historyRecords_.clear();
- time_t now = time(nullptr);
- uint32_t ts = now > 0 ? static_cast<uint32_t>(now) : 0;
-
- ECGRecord r1{ts - 3600, 76, ECGResultType::SINUS_RHYTHM};
- ECGRecord r2{ts - 86400, 82, ECGResultType::ATRIAL_FIBRILLATION};
- ECGRecord r3{ts - 172800, 0, ECGResultType::INCONCLUSIVE};
- historyRecords_.push_back(r1);
- historyRecords_.push_back(r2);
- historyRecords_.push_back(r3);
- lastRecord_ = r1;
+ lastRecord_ = {0, 0, ECG_AFIB_RESULT_INVALID};
}
ECGRecord ECGModel::GetLastRecord() const { return lastRecord_; }
return "--";
}
time_t t = static_cast<time_t>(timestamp);
- tm *timeInfo = localtime(&t);
- if (timeInfo == nullptr) {
+ tm timeInfo = {0};
+ if (localtime_r(&t, &timeInfo) == nullptr) {
return "--";
}
char buf[32] = {0};
- if (sprintf_s(buf, sizeof(buf), "%02d-%02d %02d:%02d", timeInfo->tm_mon + 1,
- timeInfo->tm_mday, timeInfo->tm_hour, timeInfo->tm_min) < 0) {
+ if (sprintf_s(buf, sizeof(buf), "%02d-%02d %02d:%02d", timeInfo.tm_mon + 1,
+ timeInfo.tm_mday, timeInfo.tm_hour, timeInfo.tm_min) < 0) {
return "--";
}
return std::string(buf);
}
+ECGResultType ECGModel::MapAfibToResultType(uint8_t afibResult) const {
+ switch (afibResult) {
+ case 0:
+ return ECGResultType::SINUS_RHYTHM;
+ case 2:
+ return ECGResultType::ATRIAL_FIBRILLATION;
+ default:
+ return ECGResultType::INCONCLUSIVE;
+ }
+}
+
+std::string ECGModel::GetAfibResultText(uint8_t afibResult) const {
+ switch (afibResult) {
+ case 0:
+ return "窦性心律";
+ case 1:
+ return "不确定(不规则)";
+ case 2:
+ return "可能房颤";
+ case 3:
+ return "不确定(信号/特征)";
+ case 4:
+ return "心率过低";
+ case 5:
+ return "心率过高";
+ default:
+ return "不确定";
+ }
+}
+
+ColorType ECGModel::GetAfibResultColor(uint8_t afibResult) const {
+ return GetResultColor(MapAfibToResultType(afibResult));
+}
+
std::string ECGModel::GetResultText(ECGResultType type) const {
switch (type) {
case ECGResultType::SINUS_RHYTHM:
return "窦性心律";
case ECGResultType::ATRIAL_FIBRILLATION:
- return "房颤";
+ return "可能房颤";
case ECGResultType::INCONCLUSIVE:
default:
return "不确定";
return color;
}
-void ECGModel::SetMeasureResult(uint8_t bpm, ECGResultType result) {
+void ECGModel::SetMeasureResult(uint8_t bpm, uint8_t afibResult) {
resultBpm_ = bpm;
- resultType_ = result;
+ resultAfib_ = afibResult;
}
uint8_t ECGModel::GetResultBpm() const { return resultBpm_; }
-ECGResultType ECGModel::GetResultType() const { return resultType_; }
+uint8_t ECGModel::GetAfibResult() const { return resultAfib_; }
-void ECGModel::AddMeasureToHistory() {
- time_t now = time(nullptr);
- uint32_t ts = now > 0 ? static_cast<uint32_t>(now) : 0;
- ECGRecord record{ts, resultBpm_, resultType_};
+ECGResultType ECGModel::GetResultType() const { return MapAfibToResultType(resultAfib_); }
+
+bool ECGModel::SaveMeasureRecord(uint8_t bpm, uint8_t afibResult) {
+ uint32_t now = 0;
+ if (uapi_get_timestampe_ss(&now) != ERRCODE_SUCC) {
+ WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP, "ECGModel SaveMeasureRecord get timestamp fail");
+ return false;
+ }
+
+ ECGRecord record = {now, bpm, afibResult};
historyRecords_.push_front(record);
- if (historyRecords_.size() > 10) {
+ while (historyRecords_.size() > ECG_HISTORY_MAX_COUNT) {
historyRecords_.pop_back();
}
lastRecord_ = record;
+ resultBpm_ = bpm;
+ resultAfib_ = afibResult;
+
+ if (!SaveHistoryToStorage()) {
+ WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP, "ECGModel SaveMeasureRecord storage fail");
+ return false;
+ }
+ WEARABLE_LOGD(WEARABLE_LOG_MODULE_APP, "ECGModel saved bpm=%u afib=%u ts=%u", bpm, afibResult,
+ now);
+ return true;
}
} // namespace OHOS