#include "displayapp/screens/WatchFaceDigital.h" #include #include #include #include #include "displayapp/screens/BatteryIcon.h" #include "displayapp/screens/BleIcon.h" #include "displayapp/screens/NotificationIcon.h" #include "displayapp/screens/Symbols.h" #include "components/battery/BatteryController.h" #include "components/ble/BleController.h" #include "components/ble/NotificationManager.h" #include "components/heartrate/HeartRateController.h" #include "components/motion/MotionController.h" #include "components/settings/Settings.h" using namespace Pinetime::Applications::Screens; WatchFaceDigital::WatchFaceDigital(DisplayApp* app, Controllers::DateTime& dateTimeController, Controllers::Battery& batteryController, Controllers::Ble& bleController, Controllers::NotificationManager& notificatioManager, Controllers::Settings& settingsController, Controllers::HeartRateController& heartRateController, Controllers::MotionController& motionController, System::SystemTask& systemTask) : Screen(app), systemTask {systemTask}, currentDateTime {{}}, dateTimeController {dateTimeController}, batteryController {batteryController}, bleController {bleController}, notificatioManager {notificatioManager}, settingsController {settingsController}, heartRateController {heartRateController}, motionController {motionController} { batteryIcon = lv_label_create(lv_scr_act(), nullptr); lv_label_set_text_static(batteryIcon, Symbols::batteryFull); lv_obj_align(batteryIcon, nullptr, LV_ALIGN_IN_TOP_RIGHT, 0, 0); batteryPlug = lv_label_create(lv_scr_act(), nullptr); lv_obj_set_style_local_text_color(batteryPlug, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, lv_color_hex(0xFF0000)); lv_label_set_text_static(batteryPlug, Symbols::plug); lv_obj_align(batteryPlug, batteryIcon, LV_ALIGN_OUT_LEFT_MID, 0, 0); batteryPercentLabel = lv_label_create(lv_scr_act(), nullptr); lv_obj_set_style_local_text_color( batteryPercentLabel, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, lv_color_hex(0x999999)); lv_obj_set_style_local_text_font( batteryPercentLabel, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, &neofont1); lv_label_set_text_static( batteryPercentLabel, battery_percent_label_text); lv_obj_align( batteryPercentLabel, batteryIcon, LV_ALIGN_OUT_BOTTOM_RIGHT, 0, 0); lv_label_set_align( batteryPercentLabel, LV_LABEL_ALIGN_RIGHT); batteryVoltageLabel = lv_label_create(lv_scr_act(), nullptr); lv_obj_set_style_local_text_font( batteryVoltageLabel, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, &neofont1); lv_obj_set_style_local_text_color( batteryVoltageLabel, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, lv_color_hex(0x999999)); lv_label_set_text_static( batteryVoltageLabel, battery_voltage_label_text); lv_obj_align( batteryVoltageLabel, batteryPercentLabel, LV_ALIGN_OUT_BOTTOM_RIGHT, 0, 0); bleIcon = lv_label_create(lv_scr_act(), nullptr); lv_obj_set_style_local_text_color(bleIcon, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, lv_color_hex(0x0082FC)); lv_label_set_text_static(bleIcon, Symbols::bluetooth); lv_obj_align(bleIcon, batteryPlug, LV_ALIGN_OUT_LEFT_MID, -5, 0); notificationIcon = lv_label_create(lv_scr_act(), nullptr); lv_obj_set_style_local_text_color(notificationIcon, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, lv_color_hex(0x00FF00)); lv_label_set_text_static(notificationIcon, NotificationIcon::GetIcon(false)); lv_obj_align(notificationIcon, nullptr, LV_ALIGN_IN_TOP_LEFT, 0, 0); label_date = lv_label_create(lv_scr_act(), nullptr); lv_obj_align(label_date, nullptr, LV_ALIGN_CENTER, 0, 60); lv_obj_set_auto_realign(label_date, true); lv_obj_set_style_local_text_color(label_date, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, lv_color_hex(0x999999)); label_temp = lv_label_create(lv_scr_act(), nullptr); lv_obj_set_style_local_text_color( label_temp, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, lv_color_hex(0x555555)); lv_label_set_text_fmt(label_temp, "??°C"); lv_obj_align(label_temp, nullptr, LV_ALIGN_CENTER, 60, -60); memfragLabel = lv_label_create(lv_scr_act(), nullptr); lv_obj_set_style_local_text_font(memfragLabel, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, &neofont1); lv_obj_set_style_local_text_color(memfragLabel, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, lv_color_hex(0x272727)); lv_label_set_text_static(memfragLabel, memfrag_label_text); lv_obj_align(memfragLabel, nullptr, LV_ALIGN_IN_RIGHT_MID, 0, -50); label_time = lv_label_create(lv_scr_act(), nullptr); lv_obj_set_style_local_text_font(label_time, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, &neofont3); lv_label_set_text_fmt(label_time, hhmm_label_text); lv_obj_align(label_time, nullptr, LV_ALIGN_CENTER, 0, 0); lv_label_set_long_mode(label_time, LV_LABEL_LONG_CROP); lv_label_set_align(label_time, LV_LABEL_ALIGN_RIGHT); label_time_pm = lv_label_create(lv_scr_act(), nullptr); lv_label_set_text_static(label_time_pm, "P "); lv_obj_align(label_time_pm, label_time, LV_ALIGN_OUT_LEFT_TOP, 0, 0); label_time_seconds = lv_label_create(lv_scr_act(), nullptr); lv_label_set_text_static(label_time_seconds, seconds_label_text); lv_obj_align(label_time_seconds, label_time, LV_ALIGN_OUT_RIGHT_BOTTOM, 0, 0); label_time_deciseconds = lv_label_create(lv_scr_act(), nullptr); lv_obj_set_style_local_text_font(label_time_deciseconds, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, &neofont1); lv_label_set_text_static(label_time_deciseconds, seconds_label_text); lv_obj_align(label_time_deciseconds, label_time_seconds, LV_ALIGN_OUT_RIGHT_BOTTOM, 0, 0); backgroundLabel = lv_label_create(lv_scr_act(), nullptr); lv_obj_set_click(backgroundLabel, true); lv_label_set_long_mode(backgroundLabel, LV_LABEL_LONG_CROP); lv_obj_set_size(backgroundLabel, 240, 240); lv_obj_set_pos(backgroundLabel, 0, 0); lv_label_set_text_static(backgroundLabel, ""); heartbeatIcon = lv_label_create(lv_scr_act(), nullptr); lv_label_set_text_static(heartbeatIcon, Symbols::heartBeat); lv_obj_set_style_local_text_color(heartbeatIcon, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, lv_color_hex(0xCE1B1B)); lv_obj_align(heartbeatIcon, nullptr, LV_ALIGN_IN_BOTTOM_LEFT, 0, 0); heartbeatValue = lv_label_create(lv_scr_act(), nullptr); lv_obj_set_style_local_text_color(heartbeatValue, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, lv_color_hex(0xCE1B1B)); lv_label_set_text_static(heartbeatValue, ""); lv_obj_align(heartbeatValue, heartbeatIcon, LV_ALIGN_OUT_RIGHT_MID, 5, 0); stepValue = lv_label_create(lv_scr_act(), nullptr); lv_obj_set_style_local_text_color(stepValue, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, lv_color_hex(0x00FFE7)); lv_label_set_text_static(stepValue, "0"); lv_obj_align(stepValue, nullptr, LV_ALIGN_IN_BOTTOM_RIGHT, 0, 0); stepIcon = lv_label_create(lv_scr_act(), nullptr); lv_obj_set_style_local_text_color(stepIcon, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, lv_color_hex(0x00FFE7)); lv_label_set_text_static(stepIcon, Symbols::shoe); lv_obj_align(stepIcon, stepValue, LV_ALIGN_OUT_LEFT_MID, -5, 0); taskRefresh = lv_task_create(RefreshTaskCallback, LV_DISP_DEF_REFR_PERIOD, LV_TASK_PRIO_MID, this); Refresh(); RefreshStats(); } WatchFaceDigital::~WatchFaceDigital() { lv_task_del(taskRefresh); lv_obj_clean(lv_scr_act()); } bool WatchFaceDigital::OnTouchEvent(Pinetime::Applications::TouchEvents event) { if (event == Pinetime::Applications::TouchEvents::Tap) { RefreshStats(); } return false; } namespace { template struct AllocStats { inline AllocStats(const Malloc& m, const Free& f) : ourMalloc(m), ourFree(f) { }; const Malloc& ourMalloc; const Free& ourFree; bool tryAlloc(uint16_t s1, uint16_t s2) { bool good = false; #ifndef INFINISIM portENTER_CRITICAL(); #endif void*p1 = ourMalloc(s1); if (p1 != nullptr) { if (s2 > 0) { void*p2 = ourMalloc(s2); if (p2 != nullptr) { good = true; ourFree(p2); } } else { good = true; } ourFree(p1); } #ifndef INFINISIM portEXIT_CRITICAL(); #endif return good; } inline auto stats(uint16_t maximumPlusOne = 10000) { uint16_t min1 = 0; uint16_t max1 = 0; while (1) { max1 = 1 + max1 * 2; if (max1 > maximumPlusOne) { max1 = maximumPlusOne; break; } if (!tryAlloc(max1,0)) { break; } min1 = max1; } for (decltype(max1) d; (d = ((max1 - min1) >> 4)) > 0;) { auto mid = min1 + (d << 3); if (tryAlloc(mid, 0)) { min1 = mid; } else { max1 = mid; } } uint16_t min2 = 0; uint16_t max2 = 0; while (1) { max2 = 1 + max2 * 2; if (max2 > min1) { max2 = min1; max2 += (1 << 3); break; } if (!tryAlloc(min1,max2)) { break; } min2 = max2; } for (decltype(max2) d; (d = ((max2 - min2) >> 4)) > 0;) { auto mid = min2 + (d << 3); if (tryAlloc(min1, mid)) { min2 = mid; } else { max2 = mid; } } return std::array({ min1, min2 }); } }; inline void*cppMalloc(uint16_t x) { return operator new(x); } inline void cppFree(void*x) { operator delete(x); } inline auto memfragStats() { #ifdef INFINISIM auto a = AllocStats{malloc,free}.stats((200+1)*8); #else auto a = AllocStats(pvPortMalloc,vPortFree).stats((200+1)*8); #endif // auto a = AllocStats{cppMalloc,cppFree}.stats(200); auto b = AllocStats{lv_mem_alloc,lv_mem_free}.stats(8008); // return std::array{ 1, 10, 100, 1000 }; return std::array{ a[0], a[1], b[0], b[1] }; } } void WatchFaceDigital::RefreshStats() { char *s = memfrag_label_text; for (uint16_t x : memfragStats()) { s += 2; x >>= 3; (s--)[0] = '0' + x%10; x /= 10; (s--)[0] = '0' + x%10; x /= 10; s[0] = '0' + x%10; x /= 10; s += 4; } lv_label_set_text_static(memfragLabel, memfrag_label_text); } void WatchFaceDigital::Refresh() { bool batteryRefreshed = false; powerPresent = batteryController.IsPowerPresent(); if (powerPresent.IsUpdated()) { lv_label_set_text_static(batteryPlug, BatteryIcon::GetPlugIcon(powerPresent.Get())); } batteryPercentRemaining = batteryController.PercentRemaining(); if (batteryPercentRemaining.IsUpdated()) { batteryRefreshed = true; auto batteryPercent = batteryPercentRemaining.Get(); if (batteryPercent == 100) { lv_obj_set_style_local_text_color(batteryIcon, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, LV_COLOR_GREEN); } else { lv_obj_set_style_local_text_color(batteryIcon, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, LV_COLOR_WHITE); } lv_label_set_text_static(batteryIcon, BatteryIcon::GetBatteryIcon(batteryPercent)); } bleState = bleController.IsConnected(); if (bleState.IsUpdated()) { lv_label_set_text_static(bleIcon, BleIcon::GetIcon(bleState.Get())); } lv_obj_realign(batteryIcon); lv_obj_realign(batteryPlug); lv_obj_realign(bleIcon); notificationState = notificatioManager.AreNewNotificationsAvailable(); if (notificationState.IsUpdated()) { lv_label_set_text_static(notificationIcon, NotificationIcon::GetIcon(notificationState.Get())); } systemTask.ObtainMotionSensorTemperature(temperature); if (temperature.IsUpdated()) { lv_obj_set_style_local_text_color( label_temp, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, lv_color_hex(0x999999)); lv_label_set_text_fmt(label_temp, "%d°C", temperature.Get()); #if 0 lv_label_set_text_fmt(label_temp, "T%d [%d,%d]", ((int)(systemTask.motionSensor.temperature_last_read_value+23)), ((int)(systemTask.motionSensor.temperature_last_result)), ((int)(systemTask.motionSensor.temperature_read_counter)) ); #endif } uint8_t second = dateTimeController.Seconds(); uint8_t decisecond = dateTimeController.Deciseconds(); if (second != displayedSecond) { displayedSecond = second; seconds_label_text[1] = '0' + (second / 10); seconds_label_text[2] = '0' + (second % 10); lv_label_set_text_static(label_time_seconds, seconds_label_text); } if (decisecond != displayedDecisecond) { displayedDecisecond = decisecond; deciseconds_label_text[0] = '0' + decisecond; lv_label_set_text_static(label_time_deciseconds, deciseconds_label_text); } currentDateTime = dateTimeController.CurrentDateTime(); if (currentDateTime.IsUpdated()) { auto newDateTime = currentDateTime.Get(); auto dp = date::floor(newDateTime); auto time = date::make_time(newDateTime - dp); auto yearMonthDay = date::year_month_day(dp); auto year = static_cast(yearMonthDay.year()); auto month = static_cast(static_cast(yearMonthDay.month())); auto day = static_cast(yearMonthDay.day()); auto dayOfWeek = static_cast(date::weekday(yearMonthDay).iso_encoding()); uint8_t hour = time.hours().count(); uint8_t minute = time.minutes().count(); if (displayedHour != hour || displayedMinute != minute) { refreshBatteryInfo = true; displayedHour = hour; displayedMinute = minute; bool hide_pm = true; uint8_t h0; if (settingsController.GetClockType() == Controllers::Settings::ClockType::H12) { if (hour == 0) { hour = 12; } else if (hour == 12) { hide_pm = false; } else if (hour > 12) { hour -= 12; hide_pm = false; } h0 = hour < 10 ? ' ' : '1'; } else { h0 = '0' + (hour / 10); } hhmm_label_text[0] = h0; hhmm_label_text[1] = '0' + (hour%10); hhmm_label_text[3] = '0' + (minute / 10); hhmm_label_text[4] = '0' + (minute % 10); lv_label_set_text_static(label_time, hhmm_label_text); lv_obj_set_hidden(label_time_pm, hide_pm); } if ((year != currentYear) || (month != currentMonth) || (dayOfWeek != currentDayOfWeek) || (day != currentDay)) { if (settingsController.GetClockType() == Controllers::Settings::ClockType::H24) { lv_label_set_text_fmt( label_date, "%s %d %s %d", dateTimeController.DayOfWeekShortToString(), day, dateTimeController.MonthShortToString(), year); } else { lv_label_set_text_fmt( label_date, "%s %s %d %d", dateTimeController.DayOfWeekShortToString(), dateTimeController.MonthShortToString(), day, year); } currentYear = year; currentMonth = month; currentDayOfWeek = dayOfWeek; currentDay = day; } } heartbeat = heartRateController.HeartRate(); heartbeatRunning = heartRateController.State() != Controllers::HeartRateController::States::Stopped; if (heartbeat.IsUpdated() || heartbeatRunning.IsUpdated()) { if (heartbeatRunning.Get()) { lv_obj_set_style_local_text_color(heartbeatIcon, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, lv_color_hex(0xCE1B1B)); auto v = heartbeat.Get(); if (v) { lv_label_set_text_fmt(heartbeatValue, "%d", v); } else { lv_label_set_text_static(heartbeatValue, ""); } } else { lv_obj_set_style_local_text_color(heartbeatIcon, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, lv_color_hex(0x1B1B1B)); lv_label_set_text_static(heartbeatValue, ""); } lv_obj_realign(heartbeatIcon); lv_obj_realign(heartbeatValue); } stepCount = motionController.NbSteps(); motionSensorOk = motionController.IsSensorOk(); if (stepCount.IsUpdated() || motionSensorOk.IsUpdated()) { lv_label_set_text_fmt(stepValue, "%lu", stepCount.Get()); lv_obj_realign(stepValue); lv_obj_realign(stepIcon); } if (batteryRefreshed || refreshBatteryInfo) { if (refreshBatteryInfo) { batteryController.MeasureVoltage(); refreshBatteryInfo = false; } { auto x = batteryController.Voltage(); battery_voltage_label_text[3] = '0' + (x%10); x /= 10; battery_voltage_label_text[2] = '0' + (x%10); x /= 10; battery_voltage_label_text[1] = '0' + (x%10); x /= 10; battery_voltage_label_text[0] = '0' + (x%10); } lv_label_set_text_static(batteryVoltageLabel, battery_voltage_label_text); { uint8_t x = batteryController.PercentRemaining(); battery_percent_label_text[2] = '0' + (x%10); x /= 10; battery_percent_label_text[1] = '0' + (x%10); x /= 10; battery_percent_label_text[0] = x ? '0' + (x%10): ' '; } lv_label_set_text_static(batteryPercentLabel, battery_percent_label_text); } }