From 8bd48f3d3f4f614fe4f590061a7024233590ef91 Mon Sep 17 00:00:00 2001 From: "hank.milliken94@protonmail.com" Date: Fri, 25 Apr 2025 19:43:47 +0200 Subject: [PATCH 1/2] feat: add persistance to stopwatch --- src/CMakeLists.txt | 3 + .../stopwatch/StopWatchController.cpp | 72 +++++ .../stopwatch/StopWatchController.h | 68 +++++ src/displayapp/Controllers.h | 2 + src/displayapp/DisplayApp.cpp | 4 + src/displayapp/DisplayApp.h | 3 + src/displayapp/DisplayAppRecovery.cpp | 1 + src/displayapp/DisplayAppRecovery.h | 2 + src/displayapp/screens/ApplicationList.cpp | 2 + src/displayapp/screens/ApplicationList.h | 2 + src/displayapp/screens/StopWatch.cpp | 253 ++++++++++-------- src/displayapp/screens/StopWatch.h | 102 ++++--- src/main.cpp | 3 + src/systemtask/SystemTask.cpp | 2 + src/systemtask/SystemTask.h | 3 + 15 files changed, 362 insertions(+), 160 deletions(-) create mode 100644 src/components/stopwatch/StopWatchController.cpp create mode 100644 src/components/stopwatch/StopWatchController.h diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index e2b69b8b02..b4d8e12eda 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -467,6 +467,7 @@ list(APPEND SOURCE_FILES components/settings/Settings.cpp components/timer/Timer.cpp components/alarm/AlarmController.cpp + components/stopwatch/StopWatchController.cpp components/fs/FS.cpp drivers/Cst816s.cpp FreeRTOS/port.c @@ -536,6 +537,7 @@ list(APPEND RECOVERY_SOURCE_FILES components/settings/Settings.cpp components/timer/Timer.cpp components/alarm/AlarmController.cpp + components/stopwatch/StopWatchController.cpp drivers/Cst816s.cpp FreeRTOS/port.c FreeRTOS/port_cmsis_systick.c @@ -655,6 +657,7 @@ set(INCLUDE_FILES components/settings/Settings.h components/timer/Timer.h components/alarm/AlarmController.h + components/stopwatch/StopWatchController.h drivers/Cst816s.h FreeRTOS/portmacro.h FreeRTOS/portmacro_cmsis.h diff --git a/src/components/stopwatch/StopWatchController.cpp b/src/components/stopwatch/StopWatchController.cpp new file mode 100644 index 0000000000..274ba17796 --- /dev/null +++ b/src/components/stopwatch/StopWatchController.cpp @@ -0,0 +1,72 @@ +#include "components/stopwatch/StopWatchController.h" + +using namespace Pinetime::Controllers; + +StopWatchController::StopWatchController() { + Clear(); +} + +// State Change + +void StopWatchController::Start() { + currentState = StopWatchStates::Running; + startTime = xTaskGetTickCount(); +} + +void StopWatchController::Pause() { + timeElapsedPreviously = GetElapsedTime(); + currentState = StopWatchStates::Paused; +} + +void StopWatchController::Clear() { + currentState = StopWatchStates::Cleared; + timeElapsedPreviously = 0; + + for (uint8_t i = 0; i < histSize; i++) { + history[i].number = 0; + history[i].timeSinceStart = 0; + } + maxLapNumber = 0; +} + +// Lap + +void StopWatchController::AddLapToHistory() { + TickType_t lapEnd = GetElapsedTime(); + history--; + history[0].timeSinceStart = lapEnd; + history[0].number = ++maxLapNumber % lapNumberBoundary; +} + +uint16_t StopWatchController::GetMaxLapNumber() { + return maxLapNumber; +} + +std::optional StopWatchController::GetLapFromHistory(uint8_t index) { + if (index >= histSize || history[index].number == 0) { + return {}; + } + return history[index]; +} + +// Data / State acess + +TickType_t StopWatchController::GetElapsedTime() { + if (!IsRunning()) { + return timeElapsedPreviously; + } + TickType_t delta = xTaskGetTickCount() - startTime; + return (timeElapsedPreviously + delta) % elapsedTimeBoundary; +} + +bool StopWatchController::IsRunning() { + return currentState == StopWatchStates::Running; +} + +bool StopWatchController::IsCleared() { + return currentState == StopWatchStates::Cleared; +} + +bool StopWatchController::IsPaused() { + return currentState == StopWatchStates::Paused; +} \ No newline at end of file diff --git a/src/components/stopwatch/StopWatchController.h b/src/components/stopwatch/StopWatchController.h new file mode 100644 index 0000000000..22d7bce358 --- /dev/null +++ b/src/components/stopwatch/StopWatchController.h @@ -0,0 +1,68 @@ +#pragma once + +#include +#include +#include +#include "utility/CircularBuffer.h" + +namespace Pinetime { + namespace System { + class SystemTask; + } + + namespace Controllers { + + enum class StopWatchStates { Cleared, Running, Paused }; + + struct LapInfo { + uint16_t number = 0; // Used to label the lap + TickType_t timeSinceStart = 0; // Excluding pauses + }; + + class StopWatchController { + public: + StopWatchController(); + + // StopWatch functionality and data + void Start(); + void Pause(); + void Clear(); + + TickType_t GetElapsedTime(); + + // Lap functionality + + /// Only the latest histSize laps are stored + void AddLapToHistory(); + + /// Returns maxLapNumber + uint16_t GetMaxLapNumber(); + + /// Indexes into lap history, with 0 being the latest lap. + std::optional GetLapFromHistory(uint8_t index); + + bool IsRunning(); + bool IsCleared(); + bool IsPaused(); + + private: + // Time at which stopwatch wraps around to zero (1000 hours) + static constexpr TickType_t elapsedTimeBoundary = static_cast(configTICK_RATE_HZ) * 60 * 60 * 1000; + // Current state of stopwatch + StopWatchStates currentState = StopWatchStates::Cleared; + // Start time of current duration + TickType_t startTime; + // How much time was elapsed before current duration + TickType_t timeElapsedPreviously; + + // Maximum number of stored laps + static constexpr uint8_t histSize = 4; + // Value at which lap numbers wrap around to zero + static constexpr uint16_t lapNumberBoundary = 1000; + // Lap storage + Utility::CircularBuffer history; + // Highest lap number; less than lapNumberBoundary, may exceed histSize + uint16_t maxLapNumber; + }; + } +} \ No newline at end of file diff --git a/src/displayapp/Controllers.h b/src/displayapp/Controllers.h index 9992426c5d..d34a9fe26c 100644 --- a/src/displayapp/Controllers.h +++ b/src/displayapp/Controllers.h @@ -19,6 +19,7 @@ namespace Pinetime { class MotorController; class MotionController; class AlarmController; + class StopWatchController; class BrightnessController; class SimpleWeatherService; class FS; @@ -42,6 +43,7 @@ namespace Pinetime { Pinetime::Controllers::MotorController& motorController; Pinetime::Controllers::MotionController& motionController; Pinetime::Controllers::AlarmController& alarmController; + Pinetime::Controllers::StopWatchController& stopWatchController; Pinetime::Controllers::BrightnessController& brightnessController; Pinetime::Controllers::SimpleWeatherService* weatherController; Pinetime::Controllers::FS& filesystem; diff --git a/src/displayapp/DisplayApp.cpp b/src/displayapp/DisplayApp.cpp index 6671ac9e51..6539729084 100644 --- a/src/displayapp/DisplayApp.cpp +++ b/src/displayapp/DisplayApp.cpp @@ -79,6 +79,7 @@ DisplayApp::DisplayApp(Drivers::St7789& lcd, Pinetime::Controllers::MotorController& motorController, Pinetime::Controllers::MotionController& motionController, Pinetime::Controllers::AlarmController& alarmController, + Pinetime::Controllers::StopWatchController& stopWatchController, Pinetime::Controllers::BrightnessController& brightnessController, Pinetime::Controllers::TouchHandler& touchHandler, Pinetime::Controllers::FS& filesystem, @@ -95,6 +96,7 @@ DisplayApp::DisplayApp(Drivers::St7789& lcd, motorController {motorController}, motionController {motionController}, alarmController {alarmController}, + stopWatchController {stopWatchController}, brightnessController {brightnessController}, touchHandler {touchHandler}, filesystem {filesystem}, @@ -110,6 +112,7 @@ DisplayApp::DisplayApp(Drivers::St7789& lcd, motorController, motionController, alarmController, + stopWatchController, brightnessController, nullptr, filesystem, @@ -527,6 +530,7 @@ void DisplayApp::LoadScreen(Apps app, DisplayApp::FullRefreshDirections directio batteryController, bleController, alarmController, + stopWatchController, dateTimeController, filesystem, std::move(apps)); diff --git a/src/displayapp/DisplayApp.h b/src/displayapp/DisplayApp.h index 2f276eaf9e..727fbf5ed7 100644 --- a/src/displayapp/DisplayApp.h +++ b/src/displayapp/DisplayApp.h @@ -14,6 +14,7 @@ #include "displayapp/screens/Screen.h" #include "components/timer/Timer.h" #include "components/alarm/AlarmController.h" +#include "components/stopwatch/StopWatchController.h" #include "touchhandler/TouchHandler.h" #include "displayapp/Messages.h" @@ -64,6 +65,7 @@ namespace Pinetime { Pinetime::Controllers::MotorController& motorController, Pinetime::Controllers::MotionController& motionController, Pinetime::Controllers::AlarmController& alarmController, + Pinetime::Controllers::StopWatchController& stopWatchController, Pinetime::Controllers::BrightnessController& brightnessController, Pinetime::Controllers::TouchHandler& touchHandler, Pinetime::Controllers::FS& filesystem, @@ -94,6 +96,7 @@ namespace Pinetime { Pinetime::Controllers::MotorController& motorController; Pinetime::Controllers::MotionController& motionController; Pinetime::Controllers::AlarmController& alarmController; + Pinetime::Controllers::StopWatchController& stopWatchController; Pinetime::Controllers::BrightnessController& brightnessController; Pinetime::Controllers::TouchHandler& touchHandler; Pinetime::Controllers::FS& filesystem; diff --git a/src/displayapp/DisplayAppRecovery.cpp b/src/displayapp/DisplayAppRecovery.cpp index bcb8db0e9d..ac8d93db67 100644 --- a/src/displayapp/DisplayAppRecovery.cpp +++ b/src/displayapp/DisplayAppRecovery.cpp @@ -22,6 +22,7 @@ DisplayApp::DisplayApp(Drivers::St7789& lcd, Pinetime::Controllers::MotorController& /*motorController*/, Pinetime::Controllers::MotionController& /*motionController*/, Pinetime::Controllers::AlarmController& /*alarmController*/, + Pinetime::Controllers::StopWatchController& /*stopWatchController*/, Pinetime::Controllers::BrightnessController& /*brightnessController*/, Pinetime::Controllers::TouchHandler& /*touchHandler*/, Pinetime::Controllers::FS& /*filesystem*/, diff --git a/src/displayapp/DisplayAppRecovery.h b/src/displayapp/DisplayAppRecovery.h index 162ff2575e..badc79fbf6 100644 --- a/src/displayapp/DisplayAppRecovery.h +++ b/src/displayapp/DisplayAppRecovery.h @@ -32,6 +32,7 @@ namespace Pinetime { class TouchHandler; class MotorController; class AlarmController; + class StopWatchController; class BrightnessController; class FS; class SimpleWeatherService; @@ -58,6 +59,7 @@ namespace Pinetime { Pinetime::Controllers::MotorController& motorController, Pinetime::Controllers::MotionController& motionController, Pinetime::Controllers::AlarmController& alarmController, + Pinetime::Controllers::StopWatchController& stopWatchController, Pinetime::Controllers::BrightnessController& brightnessController, Pinetime::Controllers::TouchHandler& touchHandler, Pinetime::Controllers::FS& filesystem, diff --git a/src/displayapp/screens/ApplicationList.cpp b/src/displayapp/screens/ApplicationList.cpp index fb46b41384..a000923d82 100644 --- a/src/displayapp/screens/ApplicationList.cpp +++ b/src/displayapp/screens/ApplicationList.cpp @@ -22,6 +22,7 @@ ApplicationList::ApplicationList(DisplayApp* app, const Pinetime::Controllers::Battery& batteryController, const Pinetime::Controllers::Ble& bleController, const Pinetime::Controllers::AlarmController& alarmController, + const Pinetime::Controllers::StopWatchController& stopWatchController, Controllers::DateTime& dateTimeController, Pinetime::Controllers::FS& filesystem, std::array&& apps) @@ -30,6 +31,7 @@ ApplicationList::ApplicationList(DisplayApp* app, batteryController {batteryController}, bleController {bleController}, alarmController {alarmController}, + stopWatchController {stopWatchController}, dateTimeController {dateTimeController}, filesystem {filesystem}, apps {std::move(apps)}, diff --git a/src/displayapp/screens/ApplicationList.h b/src/displayapp/screens/ApplicationList.h index 4a57d7c034..cd25a0b36e 100644 --- a/src/displayapp/screens/ApplicationList.h +++ b/src/displayapp/screens/ApplicationList.h @@ -19,6 +19,7 @@ namespace Pinetime { const Pinetime::Controllers::Battery& batteryController, const Pinetime::Controllers::Ble& bleController, const Pinetime::Controllers::AlarmController& alarmController, + const Pinetime::Controllers::StopWatchController& stopWatchController, Controllers::DateTime& dateTimeController, Pinetime::Controllers::FS& filesystem, std::array&& apps); @@ -34,6 +35,7 @@ namespace Pinetime { const Pinetime::Controllers::Battery& batteryController; const Pinetime::Controllers::Ble& bleController; const Pinetime::Controllers::AlarmController& alarmController; + const Pinetime::Controllers::StopWatchController& stopWatchController; Controllers::DateTime& dateTimeController; Pinetime::Controllers::FS& filesystem; std::array apps; diff --git a/src/displayapp/screens/StopWatch.cpp b/src/displayapp/screens/StopWatch.cpp index ff852beb69..ed798321c6 100644 --- a/src/displayapp/screens/StopWatch.cpp +++ b/src/displayapp/screens/StopWatch.cpp @@ -4,49 +4,51 @@ #include "displayapp/InfiniTimeTheme.h" using namespace Pinetime::Applications::Screens; +using namespace Pinetime::Controllers; namespace { - TimeSeparated_t convertTicksToTimeSegments(const TickType_t timeElapsed) { - // Centiseconds - const int timeElapsedCentis = timeElapsed * 100 / configTICK_RATE_HZ; - - const int hundredths = (timeElapsedCentis % 100); - const int secs = (timeElapsedCentis / 100) % 60; - const int mins = ((timeElapsedCentis / 100) / 60) % 60; - const int hours = ((timeElapsedCentis / 100) / 60) / 60; - return TimeSeparated_t {hours, mins, secs, hundredths}; + TimeSeparated ConvertTicksToTimeSegments(const TickType_t timeElapsed) { + const uint32_t timeElapsedSecs = timeElapsed / configTICK_RATE_HZ; + const uint16_t timeElapsedFraction = timeElapsed % configTICK_RATE_HZ; + + const uint8_t hundredths = timeElapsedFraction * 100 / configTICK_RATE_HZ; + const uint8_t secs = (timeElapsedSecs) % 60; + const uint8_t mins = (timeElapsedSecs / 60) % 60; + const uint16_t hours = (timeElapsedSecs / 60) / 60; + return TimeSeparated {hours, mins, secs, hundredths, timeElapsedSecs}; } - void play_pause_event_handler(lv_obj_t* obj, lv_event_t event) { + void PlayPauseEventHandler(lv_obj_t* obj, lv_event_t event) { auto* stopWatch = static_cast(obj->user_data); if (event == LV_EVENT_CLICKED) { - stopWatch->playPauseBtnEventHandler(); + stopWatch->PlayPauseBtnEventHandler(); } } - void stop_lap_event_handler(lv_obj_t* obj, lv_event_t event) { + void StopLapEventHandler(lv_obj_t* obj, lv_event_t event) { auto* stopWatch = static_cast(obj->user_data); if (event == LV_EVENT_CLICKED) { - stopWatch->stopLapBtnEventHandler(); + stopWatch->StopLapBtnEventHandler(); } } constexpr TickType_t blinkInterval = pdMS_TO_TICKS(1000); } -StopWatch::StopWatch(System::SystemTask& systemTask) : wakeLock(systemTask) { +StopWatch::StopWatch(System::SystemTask& systemTask, StopWatchController& stopWatchController) + : wakeLock(systemTask), stopWatchController {stopWatchController} { static constexpr uint8_t btnWidth = 115; static constexpr uint8_t btnHeight = 80; btnPlayPause = lv_btn_create(lv_scr_act(), nullptr); btnPlayPause->user_data = this; - lv_obj_set_event_cb(btnPlayPause, play_pause_event_handler); + lv_obj_set_event_cb(btnPlayPause, PlayPauseEventHandler); lv_obj_set_size(btnPlayPause, btnWidth, btnHeight); lv_obj_align(btnPlayPause, lv_scr_act(), LV_ALIGN_IN_BOTTOM_RIGHT, 0, 0); txtPlayPause = lv_label_create(btnPlayPause, nullptr); btnStopLap = lv_btn_create(lv_scr_act(), nullptr); btnStopLap->user_data = this; - lv_obj_set_event_cb(btnStopLap, stop_lap_event_handler); + lv_obj_set_event_cb(btnStopLap, StopLapEventHandler); lv_obj_set_size(btnStopLap, btnWidth, btnHeight); lv_obj_align(btnStopLap, lv_scr_act(), LV_ALIGN_IN_BOTTOM_LEFT, 0, 0); txtStopLap = lv_label_create(btnStopLap, nullptr); @@ -55,26 +57,48 @@ StopWatch::StopWatch(System::SystemTask& systemTask) : wakeLock(systemTask) { lapText = lv_label_create(lv_scr_act(), nullptr); lv_obj_set_style_local_text_color(lapText, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, Colors::lightGray); - lv_label_set_text_static(lapText, "\n"); + lv_label_set_text_static(lapText, ""); lv_label_set_long_mode(lapText, LV_LABEL_LONG_BREAK); lv_label_set_align(lapText, LV_LABEL_ALIGN_CENTER); lv_obj_set_width(lapText, LV_HOR_RES_MAX); - lv_obj_align(lapText, lv_scr_act(), LV_ALIGN_IN_BOTTOM_MID, 0, -btnHeight); - - msecTime = lv_label_create(lv_scr_act(), nullptr); - lv_label_set_text_static(msecTime, "00"); - lv_obj_set_style_local_text_color(msecTime, LV_LABEL_PART_MAIN, LV_STATE_DISABLED, Colors::lightGray); - lv_obj_align(msecTime, lapText, LV_ALIGN_OUT_TOP_MID, 0, 0); + lv_obj_align(lapText, lv_scr_act(), LV_ALIGN_IN_BOTTOM_MID, 0, -btnHeight - 2); time = lv_label_create(lv_scr_act(), nullptr); + lv_obj_set_style_local_text_color(time, LV_LABEL_PART_MAIN, LV_STATE_DISABLED, Colors::lightGray); lv_obj_set_style_local_text_font(time, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, &jetbrains_mono_76); lv_label_set_text_static(time, "00:00"); - lv_obj_set_style_local_text_color(time, LV_LABEL_PART_MAIN, LV_STATE_DISABLED, Colors::lightGray); - lv_obj_align(time, msecTime, LV_ALIGN_OUT_TOP_MID, 0, 0); + lv_label_set_long_mode(time, LV_LABEL_LONG_CROP); + lv_label_set_align(time, LV_LABEL_ALIGN_CENTER); + lv_obj_set_width(time, LV_HOR_RES_MAX); + lv_obj_align(time, lv_scr_act(), LV_ALIGN_IN_TOP_MID, 0, 0); - SetInterfaceStopped(); + msecTime = lv_label_create(lv_scr_act(), nullptr); + lv_obj_set_style_local_text_color(msecTime, LV_LABEL_PART_MAIN, LV_STATE_DISABLED, Colors::lightGray); + lv_label_set_text_static(msecTime, "00"); + lv_obj_align(msecTime, time, LV_ALIGN_OUT_BOTTOM_MID, 0, -2); taskRefresh = lv_task_create(RefreshTaskCallback, LV_DISP_DEF_REFR_PERIOD, LV_TASK_PRIO_MID, this); + + // Figure out what the current state of the stopwatch is and select the correct display + if (stopWatchController.IsCleared()) { + DisplayCleared(); + } else { + if (stopWatchController.GetMaxLapNumber() > 0) { + RenderLaps(); + } + RenderTime(); + + if (stopWatchController.IsRunning()) { + lv_obj_set_state(btnStopLap, LV_STATE_DISABLED); + lv_obj_set_state(txtStopLap, LV_STATE_DISABLED); + DisplayStarted(); + wakeLock.Lock(); + } else if (stopWatchController.IsPaused()) { + lv_obj_set_state(btnStopLap, LV_STATE_DEFAULT); + lv_obj_set_state(txtStopLap, LV_STATE_DEFAULT); + DisplayPaused(); + } + } } StopWatch::~StopWatch() { @@ -82,14 +106,14 @@ StopWatch::~StopWatch() { lv_obj_clean(lv_scr_act()); } -void StopWatch::SetInterfacePaused() { +void StopWatch::DisplayPaused() { lv_obj_set_style_local_bg_color(btnStopLap, LV_BTN_PART_MAIN, LV_STATE_DEFAULT, LV_COLOR_RED); lv_obj_set_style_local_bg_color(btnPlayPause, LV_BTN_PART_MAIN, LV_STATE_DEFAULT, Colors::blue); lv_label_set_text_static(txtPlayPause, Symbols::play); lv_label_set_text_static(txtStopLap, Symbols::stop); } -void StopWatch::SetInterfaceRunning() { +void StopWatch::DisplayStarted() { lv_obj_set_state(time, LV_STATE_DEFAULT); lv_obj_set_state(msecTime, LV_STATE_DEFAULT); lv_obj_set_style_local_bg_color(btnPlayPause, LV_BTN_PART_MAIN, LV_STATE_DEFAULT, Colors::bgAlt); @@ -102,7 +126,7 @@ void StopWatch::SetInterfaceRunning() { lv_obj_set_state(txtStopLap, LV_STATE_DEFAULT); } -void StopWatch::SetInterfaceStopped() { +void StopWatch::DisplayCleared() { lv_obj_set_state(time, LV_STATE_DISABLED); lv_obj_set_state(msecTime, LV_STATE_DISABLED); lv_obj_set_style_local_bg_color(btnPlayPause, LV_BTN_PART_MAIN, LV_STATE_DEFAULT, Colors::blue); @@ -110,11 +134,7 @@ void StopWatch::SetInterfaceStopped() { lv_label_set_text_static(time, "00:00"); lv_label_set_text_static(msecTime, "00"); - if (isHoursLabelUpdated) { - lv_obj_set_style_local_text_font(time, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, &jetbrains_mono_76); - lv_obj_realign(time); - isHoursLabelUpdated = false; - } + SetHoursVisible(false); lv_label_set_text_static(lapText, ""); lv_label_set_text_static(txtPlayPause, Symbols::play); @@ -123,97 +143,118 @@ void StopWatch::SetInterfaceStopped() { lv_obj_set_state(txtStopLap, LV_STATE_DISABLED); } -void StopWatch::Reset() { - SetInterfaceStopped(); - currentState = States::Init; - oldTimeElapsed = 0; - lapsDone = 0; -} - -void StopWatch::Start() { - SetInterfaceRunning(); - startTime = xTaskGetTickCount(); - currentState = States::Running; - wakeLock.Lock(); +void StopWatch::RenderTime() { + TimeSeparated elapsedTime = ConvertTicksToTimeSegments(stopWatchController.GetElapsedTime()); + renderedSeconds = elapsedTime.epochSecs; + if (renderedSeconds.IsUpdated()) { + SetHoursVisible(elapsedTime.hours != 0); + if (!hoursVisible) { + lv_label_set_text_fmt(time, "%02d:%02d", elapsedTime.mins, elapsedTime.secs); + } else { + lv_label_set_text_fmt(time, "%02d:%02d:%02d", elapsedTime.hours, elapsedTime.mins, elapsedTime.secs); + } + } + lv_label_set_text_fmt(msecTime, "%02d", elapsedTime.hundredths); } -void StopWatch::Pause() { - SetInterfacePaused(); - startTime = 0; - // Store the current time elapsed in cache - oldTimeElapsed = laps[lapsDone]; - blinkTime = xTaskGetTickCount() + blinkInterval; - currentState = States::Halted; - wakeLock.Release(); +void StopWatch::RenderPause() { + const TickType_t currentTime = xTaskGetTickCount(); + if (currentTime - lastBlinkTime > blinkInterval) { + lastBlinkTime = currentTime; + if (lv_obj_get_state(time, LV_LABEL_PART_MAIN) == LV_STATE_DEFAULT) { + lv_obj_set_state(time, LV_STATE_DISABLED); + lv_obj_set_state(msecTime, LV_STATE_DISABLED); + } else { + lv_obj_set_state(time, LV_STATE_DEFAULT); + lv_obj_set_state(msecTime, LV_STATE_DEFAULT); + } + } } -void StopWatch::Refresh() { - if (currentState == States::Running) { - laps[lapsDone] = oldTimeElapsed + xTaskGetTickCount() - startTime; +void StopWatch::RenderLaps() { + lv_label_set_text(lapText, ""); + for (int i = displayedLaps - 1; i >= 0; i--) { + std::optional lap = stopWatchController.GetLapFromHistory(i); - TimeSeparated_t currentTimeSeparated = convertTicksToTimeSegments(laps[lapsDone]); - if (currentTimeSeparated.hours == 0) { - lv_label_set_text_fmt(time, "%02d:%02d", currentTimeSeparated.mins, currentTimeSeparated.secs); - } else { - lv_label_set_text_fmt(time, "%02d:%02d:%02d", currentTimeSeparated.hours, currentTimeSeparated.mins, currentTimeSeparated.secs); - if (!isHoursLabelUpdated) { - lv_obj_set_style_local_text_font(time, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, &jetbrains_mono_42); - lv_obj_realign(time); - isHoursLabelUpdated = true; - } - } - lv_label_set_text_fmt(msecTime, "%02d", currentTimeSeparated.hundredths); - } else if (currentState == States::Halted) { - const TickType_t currentTime = xTaskGetTickCount(); - if (currentTime > blinkTime) { - blinkTime = currentTime + blinkInterval; - if (lv_obj_get_state(time, LV_LABEL_PART_MAIN) == LV_STATE_DEFAULT) { - lv_obj_set_state(time, LV_STATE_DISABLED); - lv_obj_set_state(msecTime, LV_STATE_DISABLED); + if (lap) { + TimeSeparated laptime = ConvertTicksToTimeSegments(lap->timeSinceStart); + char buffer[19]; + if (laptime.hours > 0) { + snprintf(buffer, + sizeof(buffer), + "\n#%-3d %3d:%02d:%02d.%02d", + lap->number, + laptime.hours, + laptime.mins, + laptime.secs, + laptime.hundredths); + } else if (laptime.mins > 0) { + snprintf(buffer, sizeof(buffer), "\n#%-3d %2d:%02d.%02d", lap->number, laptime.mins, laptime.secs, laptime.hundredths); } else { - lv_obj_set_state(time, LV_STATE_DEFAULT); - lv_obj_set_state(msecTime, LV_STATE_DEFAULT); + snprintf(buffer, sizeof(buffer), "\n#%-3d %2d.%02d", lap->number, laptime.secs, laptime.hundredths); } + lv_label_ins_text(lapText, LV_LABEL_POS_LAST, buffer); } } + lv_obj_realign(lapText); } -void StopWatch::playPauseBtnEventHandler() { - if (currentState == States::Init || currentState == States::Halted) { - Start(); - } else if (currentState == States::Running) { - Pause(); +void StopWatch::SetHoursVisible(bool visible) { + if (hoursVisible != visible) { + lv_font_t* font = visible ? &jetbrains_mono_42 : &jetbrains_mono_76; + lv_obj_set_style_local_text_font(time, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, font); + lv_obj_set_height(time, font->line_height); + lv_obj_align(time, lv_scr_act(), LV_ALIGN_IN_TOP_MID, 0, visible ? 5 : 0); + lv_obj_align(msecTime, time, LV_ALIGN_OUT_BOTTOM_MID, 0, visible ? 5 : -2); + displayedLaps = visible ? 4 : 3; + hoursVisible = visible; + if (stopWatchController.GetLapFromHistory(0)) { + RenderLaps(); + } } } -void StopWatch::stopLapBtnEventHandler() { - // If running, then this button is used to save laps - if (currentState == States::Running) { - lv_label_set_text(lapText, ""); - lapsDone = std::min(lapsDone + 1, maxLapCount); - for (int i = lapsDone - displayedLaps; i < lapsDone; i++) { - if (i < 0) { - lv_label_ins_text(lapText, LV_LABEL_POS_LAST, "\n"); - continue; - } - TimeSeparated_t times = convertTicksToTimeSegments(laps[i]); - char buffer[17]; - if (times.hours == 0) { - snprintf(buffer, sizeof(buffer), "#%2d %2d:%02d.%02d\n", i + 1, times.mins, times.secs, times.hundredths); - } else { - snprintf(buffer, sizeof(buffer), "#%2d %2d:%02d:%02d.%02d\n", i + 1, times.hours, times.mins, times.secs, times.hundredths); - } - lv_label_ins_text(lapText, LV_LABEL_POS_LAST, buffer); - } - } else if (currentState == States::Halted) { - Reset(); +void StopWatch::Refresh() { + if (stopWatchController.IsRunning()) { + RenderTime(); + } else if (stopWatchController.IsPaused()) { + RenderPause(); + } +} + +void StopWatch::PlayPauseBtnEventHandler() { + if (stopWatchController.IsCleared() || stopWatchController.IsPaused()) { + stopWatchController.Start(); + DisplayStarted(); + wakeLock.Lock(); + } else if (stopWatchController.IsRunning()) { + OnPause(); + } +} + +void StopWatch::StopLapBtnEventHandler() { + if (stopWatchController.IsRunning()) { + stopWatchController.AddLapToHistory(); + RenderLaps(); + } else if (stopWatchController.IsPaused()) { + stopWatchController.Clear(); + DisplayCleared(); + wakeLock.Release(); } } bool StopWatch::OnButtonPushed() { - if (currentState == States::Running) { - Pause(); + if (stopWatchController.IsRunning()) { + OnPause(); return true; } return false; } + +void StopWatch::OnPause() { + stopWatchController.Pause(); + lastBlinkTime = xTaskGetTickCount(); + RenderTime(); // make sure displayed time is not stale + DisplayPaused(); + wakeLock.Release(); +} \ No newline at end of file diff --git a/src/displayapp/screens/StopWatch.h b/src/displayapp/screens/StopWatch.h index 55a178dcbe..6075de918c 100644 --- a/src/displayapp/screens/StopWatch.h +++ b/src/displayapp/screens/StopWatch.h @@ -3,72 +3,66 @@ #include "displayapp/screens/Screen.h" #include -#include -#include "portmacro_cmsis.h" - +#include "components/stopwatch/StopWatchController.h" #include "systemtask/SystemTask.h" #include "systemtask/WakeLock.h" -#include "displayapp/apps/Apps.h" -#include "displayapp/Controllers.h" #include "Symbols.h" +#include "utility/DirtyValue.h" -namespace Pinetime { - namespace Applications { - namespace Screens { - - enum class States { Init, Running, Halted }; +namespace Pinetime::Applications { + namespace Screens { - struct TimeSeparated_t { - int hours; - int mins; - int secs; - int hundredths; - }; + struct TimeSeparated { + uint16_t hours; + uint8_t mins; + uint8_t secs; + uint8_t hundredths; + uint32_t epochSecs; + }; - class StopWatch : public Screen { - public: - explicit StopWatch(System::SystemTask& systemTask); - ~StopWatch() override; - void Refresh() override; + class StopWatch : public Screen { + public: + explicit StopWatch(System::SystemTask& systemTask, Controllers::StopWatchController& stopWatchController); + ~StopWatch() override; + void Refresh() override; - void playPauseBtnEventHandler(); - void stopLapBtnEventHandler(); - bool OnButtonPushed() override; + void PlayPauseBtnEventHandler(); + void StopLapBtnEventHandler(); + bool OnButtonPushed() override; - private: - void SetInterfacePaused(); - void SetInterfaceRunning(); - void SetInterfaceStopped(); + private: + void OnPause(); - void Reset(); - void Start(); - void Pause(); + void DisplayPaused(); + void DisplayStarted(); + void DisplayCleared(); - Pinetime::System::WakeLock wakeLock; - States currentState = States::Init; - TickType_t startTime; - TickType_t oldTimeElapsed = 0; - TickType_t blinkTime = 0; - static constexpr int maxLapCount = 20; - TickType_t laps[maxLapCount + 1]; - static constexpr int displayedLaps = 2; - int lapsDone = 0; - lv_obj_t *time, *msecTime, *btnPlayPause, *btnStopLap, *txtPlayPause, *txtStopLap; - lv_obj_t* lapText; - bool isHoursLabelUpdated = false; + void RenderTime(); + void RenderPause(); + void RenderLaps(); - lv_task_t* taskRefresh; - }; - } + void SetHoursVisible(bool visible); - template <> - struct AppTraits { - static constexpr Apps app = Apps::StopWatch; - static constexpr const char* icon = Screens::Symbols::stopWatch; + Pinetime::System::WakeLock wakeLock; + Controllers::StopWatchController& stopWatchController; + TickType_t lastBlinkTime = 0; + uint8_t displayedLaps = 3; + lv_obj_t *time, *msecTime, *btnPlayPause, *btnStopLap, *txtPlayPause, *txtStopLap; + lv_obj_t* lapText; + Utility::DirtyValue renderedSeconds; + bool hoursVisible = false; - static Screens::Screen* Create(AppControllers& controllers) { - return new Screens::StopWatch(*controllers.systemTask); - }; + lv_task_t* taskRefresh; }; } -} + + template <> + struct AppTraits { + static constexpr Apps app = Apps::StopWatch; + static constexpr const char* icon = Screens::Symbols::stopWatch; + + static Screens::Screen* Create(AppControllers& controllers) { + return new Screens::StopWatch(*controllers.systemTask, controllers.stopWatchController); + }; + }; +} \ No newline at end of file diff --git a/src/main.cpp b/src/main.cpp index 24f13caddd..06ed4af4b7 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -105,6 +105,7 @@ Pinetime::Drivers::Watchdog watchdog; Pinetime::Controllers::NotificationManager notificationManager; Pinetime::Controllers::MotionController motionController; Pinetime::Controllers::AlarmController alarmController {dateTimeController, fs}; +Pinetime::Controllers::StopWatchController stopWatchController; Pinetime::Controllers::TouchHandler touchHandler; Pinetime::Controllers::ButtonHandler buttonHandler; Pinetime::Controllers::BrightnessController brightnessController {}; @@ -121,6 +122,7 @@ Pinetime::Applications::DisplayApp displayApp(lcd, motorController, motionController, alarmController, + stopWatchController, brightnessController, touchHandler, fs, @@ -134,6 +136,7 @@ Pinetime::System::SystemTask systemTask(spi, bleController, dateTimeController, alarmController, + stopWatchController, watchdog, notificationManager, heartRateSensor, diff --git a/src/systemtask/SystemTask.cpp b/src/systemtask/SystemTask.cpp index eb013d6d1a..1d2178331a 100644 --- a/src/systemtask/SystemTask.cpp +++ b/src/systemtask/SystemTask.cpp @@ -40,6 +40,7 @@ SystemTask::SystemTask(Drivers::SpiMaster& spi, Controllers::Ble& bleController, Controllers::DateTime& dateTimeController, Controllers::AlarmController& alarmController, + Controllers::StopWatchController& stopWatchController, Drivers::Watchdog& watchdog, Pinetime::Controllers::NotificationManager& notificationManager, Pinetime::Drivers::Hrs3300& heartRateSensor, @@ -60,6 +61,7 @@ SystemTask::SystemTask(Drivers::SpiMaster& spi, bleController {bleController}, dateTimeController {dateTimeController}, alarmController {alarmController}, + stopWatchController {stopWatchController}, watchdog {watchdog}, notificationManager {notificationManager}, heartRateSensor {heartRateSensor}, diff --git a/src/systemtask/SystemTask.h b/src/systemtask/SystemTask.h index 0060e36096..bb2cad24ff 100644 --- a/src/systemtask/SystemTask.h +++ b/src/systemtask/SystemTask.h @@ -16,6 +16,7 @@ #include "components/ble/NimbleController.h" #include "components/ble/NotificationManager.h" #include "components/alarm/AlarmController.h" +#include "components/stopwatch/StopWatchController.h" #include "components/fs/FS.h" #include "touchhandler/TouchHandler.h" #include "buttonhandler/ButtonHandler.h" @@ -61,6 +62,7 @@ namespace Pinetime { Controllers::Ble& bleController, Controllers::DateTime& dateTimeController, Controllers::AlarmController& alarmController, + Controllers::StopWatchController& stopWatchController, Drivers::Watchdog& watchdog, Pinetime::Controllers::NotificationManager& notificationManager, Pinetime::Drivers::Hrs3300& heartRateSensor, @@ -101,6 +103,7 @@ namespace Pinetime { Pinetime::Controllers::Ble& bleController; Pinetime::Controllers::DateTime& dateTimeController; Pinetime::Controllers::AlarmController& alarmController; + Pinetime::Controllers::StopWatchController& stopWatchController; QueueHandle_t systemTasksMsgQueue; Pinetime::Drivers::Watchdog& watchdog; Pinetime::Controllers::NotificationManager& notificationManager; From a55637f32bfd4a416f57c4b31127cce3936b3a78 Mon Sep 17 00:00:00 2001 From: "hank.milliken94@protonmail.com" Date: Fri, 25 Apr 2025 22:53:05 +0200 Subject: [PATCH 2/2] feat: show timer on the Digital Watch Face --- src/displayapp/screens/StopWatch.cpp | 4 ++- src/displayapp/screens/StopWatch.h | 2 ++ src/displayapp/screens/WatchFaceDigital.cpp | 29 +++++++++++++++++++++ src/displayapp/screens/WatchFaceDigital.h | 7 +++++ 4 files changed, 41 insertions(+), 1 deletion(-) diff --git a/src/displayapp/screens/StopWatch.cpp b/src/displayapp/screens/StopWatch.cpp index ed798321c6..35fcbe8424 100644 --- a/src/displayapp/screens/StopWatch.cpp +++ b/src/displayapp/screens/StopWatch.cpp @@ -6,7 +6,7 @@ using namespace Pinetime::Applications::Screens; using namespace Pinetime::Controllers; -namespace { +namespace Pinetime::Applications::Screens { TimeSeparated ConvertTicksToTimeSegments(const TickType_t timeElapsed) { const uint32_t timeElapsedSecs = timeElapsed / configTICK_RATE_HZ; const uint16_t timeElapsedFraction = timeElapsed % configTICK_RATE_HZ; @@ -17,7 +17,9 @@ namespace { const uint16_t hours = (timeElapsedSecs / 60) / 60; return TimeSeparated {hours, mins, secs, hundredths, timeElapsedSecs}; } +} +namespace { void PlayPauseEventHandler(lv_obj_t* obj, lv_event_t event) { auto* stopWatch = static_cast(obj->user_data); if (event == LV_EVENT_CLICKED) { diff --git a/src/displayapp/screens/StopWatch.h b/src/displayapp/screens/StopWatch.h index 6075de918c..0a9f4bc5d1 100644 --- a/src/displayapp/screens/StopWatch.h +++ b/src/displayapp/screens/StopWatch.h @@ -20,6 +20,8 @@ namespace Pinetime::Applications { uint32_t epochSecs; }; + TimeSeparated ConvertTicksToTimeSegments(const TickType_t timeElapsed); + class StopWatch : public Screen { public: explicit StopWatch(System::SystemTask& systemTask, Controllers::StopWatchController& stopWatchController); diff --git a/src/displayapp/screens/WatchFaceDigital.cpp b/src/displayapp/screens/WatchFaceDigital.cpp index 3163c6e750..edd8150a47 100644 --- a/src/displayapp/screens/WatchFaceDigital.cpp +++ b/src/displayapp/screens/WatchFaceDigital.cpp @@ -6,6 +6,8 @@ #include "displayapp/screens/NotificationIcon.h" #include "displayapp/screens/Symbols.h" #include "displayapp/screens/WeatherSymbols.h" +#include "displayapp/screens/WeatherSymbols.h" +#include "displayapp/screens/StopWatch.h" #include "components/battery/BatteryController.h" #include "components/ble/BleController.h" #include "components/ble/NotificationManager.h" @@ -24,6 +26,7 @@ WatchFaceDigital::WatchFaceDigital(Controllers::DateTime& dateTimeController, Controllers::Settings& settingsController, Controllers::HeartRateController& heartRateController, Controllers::MotionController& motionController, + Controllers::StopWatchController& stopWatchController, Controllers::SimpleWeatherService& weatherService) : currentDateTime {{}}, dateTimeController {dateTimeController}, @@ -31,6 +34,7 @@ WatchFaceDigital::WatchFaceDigital(Controllers::DateTime& dateTimeController, settingsController {settingsController}, heartRateController {heartRateController}, motionController {motionController}, + stopWatchController {stopWatchController}, weatherService {weatherService}, statusIcons(batteryController, bleController, alarmController) { @@ -86,6 +90,16 @@ WatchFaceDigital::WatchFaceDigital(Controllers::DateTime& dateTimeController, lv_label_set_text_static(stepIcon, Symbols::shoe); lv_obj_align(stepIcon, stepValue, LV_ALIGN_OUT_LEFT_MID, -5, 0); + stopWatchIcon = lv_label_create(lv_scr_act(), nullptr); + lv_obj_set_style_local_text_color(stopWatchIcon, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, lv_color_hex(0xFFFFFF)); + lv_label_set_text_static(stopWatchIcon, ""); + lv_obj_align(stopWatchIcon, lv_scr_act(), LV_ALIGN_IN_TOP_LEFT, 0, 0); + + stopWatchValue = lv_label_create(lv_scr_act(), nullptr); + lv_obj_set_style_local_text_color(stopWatchValue, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, lv_color_hex(0xFFFFFF)); + lv_label_set_text_static(stopWatchValue, ""); + lv_obj_align(stopWatchValue, stopWatchIcon, LV_ALIGN_OUT_RIGHT_MID, 5, 0); + taskRefresh = lv_task_create(RefreshTaskCallback, LV_DISP_DEF_REFR_PERIOD, LV_TASK_PRIO_MID, this); Refresh(); } @@ -172,6 +186,21 @@ void WatchFaceDigital::Refresh() { lv_obj_realign(stepIcon); } + stopWatchTime = stopWatchController.GetElapsedTime(); + stopWatchRunning = !stopWatchController.IsCleared(); + if (stopWatchTime.IsUpdated() || stopWatchRunning.IsUpdated()) { + if (stopWatchRunning.Get()) { + TimeSeparated elapsedTime = ConvertTicksToTimeSegments(stopWatchTime.Get()); + lv_label_set_text_fmt(stopWatchValue, "%02d:%02d:%02d:%02d", elapsedTime.hours, elapsedTime.mins, elapsedTime.secs, elapsedTime.hundredths); + lv_label_set_text_static(stopWatchIcon, Symbols::stopWatch); + } else { + lv_label_set_text_fmt(stopWatchValue, ""); + lv_label_set_text_static(stopWatchIcon, ""); + } + lv_obj_realign(stopWatchValue); + lv_obj_realign(stopWatchIcon); + } + currentWeather = weatherService.Current(); if (currentWeather.IsUpdated()) { auto optCurrentWeather = currentWeather.Get(); diff --git a/src/displayapp/screens/WatchFaceDigital.h b/src/displayapp/screens/WatchFaceDigital.h index 3005cea56f..6ac2126866 100644 --- a/src/displayapp/screens/WatchFaceDigital.h +++ b/src/displayapp/screens/WatchFaceDigital.h @@ -36,6 +36,7 @@ namespace Pinetime { Controllers::Settings& settingsController, Controllers::HeartRateController& heartRateController, Controllers::MotionController& motionController, + Controllers::StopWatchController& stopWatchController, Controllers::SimpleWeatherService& weather); ~WatchFaceDigital() override; @@ -47,6 +48,8 @@ namespace Pinetime { Utility::DirtyValue> currentDateTime {}; Utility::DirtyValue stepCount {}; + Utility::DirtyValue stopWatchTime {}; + Utility::DirtyValue stopWatchRunning {}; Utility::DirtyValue heartbeat {}; Utility::DirtyValue heartbeatRunning {}; Utility::DirtyValue notificationState {}; @@ -61,6 +64,8 @@ namespace Pinetime { lv_obj_t* heartbeatValue; lv_obj_t* stepIcon; lv_obj_t* stepValue; + lv_obj_t* stopWatchIcon; + lv_obj_t* stopWatchValue; lv_obj_t* notificationIcon; lv_obj_t* weatherIcon; lv_obj_t* temperature; @@ -70,6 +75,7 @@ namespace Pinetime { Controllers::Settings& settingsController; Controllers::HeartRateController& heartRateController; Controllers::MotionController& motionController; + Controllers::StopWatchController& stopWatchController; Controllers::SimpleWeatherService& weatherService; lv_task_t* taskRefresh; @@ -91,6 +97,7 @@ namespace Pinetime { controllers.settingsController, controllers.heartRateController, controllers.motionController, + controllers.stopWatchController, *controllers.weatherController); };