From 62b2a4d1dbe99cb14700b47547b8c6f73854257c Mon Sep 17 00:00:00 2001 From: Mathias Tausen Date: Sun, 11 Feb 2024 21:25:13 +0100 Subject: [PATCH 1/2] Timer: Remember last timer setting Remember last duration used in Timer app. Stop with long press while paused. Reset to zero with long press while stopped. --- src/components/timer/Timer.cpp | 47 ++++++++-- src/components/timer/Timer.h | 20 ++++- src/displayapp/DisplayApp.cpp | 5 +- src/displayapp/screens/Timer.cpp | 148 +++++++++++++++++++++++-------- src/displayapp/screens/Timer.h | 12 ++- 5 files changed, 181 insertions(+), 51 deletions(-) diff --git a/src/components/timer/Timer.cpp b/src/components/timer/Timer.cpp index 279178cd..a861a492 100644 --- a/src/components/timer/Timer.cpp +++ b/src/components/timer/Timer.cpp @@ -3,26 +3,57 @@ using namespace Pinetime::Controllers; Timer::Timer(void* const timerData, TimerCallbackFunction_t timerCallbackFunction) { + pausedAtTimer = std::chrono::seconds(0); + state = Stopped; timer = xTimerCreate("Timer", 1, pdFALSE, timerData, timerCallbackFunction); } -void Timer::StartTimer(std::chrono::milliseconds duration) { - xTimerChangePeriod(timer, pdMS_TO_TICKS(duration.count()), 0); - xTimerStart(timer, 0); -} - std::chrono::milliseconds Timer::GetTimeRemaining() { if (IsRunning()) { - TickType_t remainingTime = xTimerGetExpiryTime(timer) - xTaskGetTickCount(); + const TickType_t remainingTime = xTimerGetExpiryTime(timer) - xTaskGetTickCount(); return std::chrono::milliseconds(remainingTime * 1000 / configTICK_RATE_HZ); } + + if (state == Paused) + return pausedAtTimer; + return std::chrono::milliseconds(0); } -void Timer::StopTimer() { - xTimerStop(timer, 0); +Timer::TimerState Timer::GetState() { + return state; +} + +void Timer::Start(const std::chrono::milliseconds duration) { + TimerStart(duration); + state = Running; +} + +void Timer::Stop() { + TimerStop(); + state = Stopped; +} + +void Timer::Pause() { + pausedAtTimer = GetTimeRemaining(); + TimerStop(); + state = Paused; +} + +void Timer::Resume() { + TimerStart(pausedAtTimer); + state = Running; } bool Timer::IsRunning() { return (xTimerIsTimerActive(timer) == pdTRUE); +}; + +void Timer::TimerStart(const std::chrono::milliseconds duration) { + xTimerChangePeriod(timer, pdMS_TO_TICKS(duration.count()), 0); + xTimerStart(timer, 0); +} + +void Timer::TimerStop() { + xTimerStop(timer, 0); } diff --git a/src/components/timer/Timer.h b/src/components/timer/Timer.h index 2469666f..ee48fc12 100644 --- a/src/components/timer/Timer.h +++ b/src/components/timer/Timer.h @@ -11,16 +11,30 @@ namespace Pinetime { public: Timer(void* timerData, TimerCallbackFunction_t timerCallbackFunction); - void StartTimer(std::chrono::milliseconds duration); + void Start(const std::chrono::milliseconds duration); - void StopTimer(); + void Stop(); + + void Pause(); + + void Resume(); std::chrono::milliseconds GetTimeRemaining(); - bool IsRunning(); + typedef enum TimerState { Running, Stopped, Paused } TimerState; + + TimerState GetState(); private: TimerHandle_t timer; + + std::chrono::milliseconds pausedAtTimer; + + TimerState state; + + bool IsRunning(); + void TimerStart(const std::chrono::milliseconds duration); + void TimerStop(); }; } } diff --git a/src/displayapp/DisplayApp.cpp b/src/displayapp/DisplayApp.cpp index 3fd34b3a..f6cab5b1 100644 --- a/src/displayapp/DisplayApp.cpp +++ b/src/displayapp/DisplayApp.cpp @@ -265,11 +265,12 @@ void DisplayApp::Refresh() { } if (currentApp == Apps::Timer) { lv_disp_trig_activity(nullptr); - auto* timer = static_cast(currentScreen.get()); - timer->Reset(); } else { LoadNewScreen(Apps::Timer, DisplayApp::FullRefreshDirections::Up); } + Screens::Timer* timer; + timer = static_cast(currentScreen.get()); + timer->Stop(); motorController.RunForDuration(35); break; case Messages::AlarmTriggered: diff --git a/src/displayapp/screens/Timer.cpp b/src/displayapp/screens/Timer.cpp index a1ede6be..c1ef88b8 100644 --- a/src/displayapp/screens/Timer.cpp +++ b/src/displayapp/screens/Timer.cpp @@ -6,6 +6,8 @@ using namespace Pinetime::Applications::Screens; +static std::chrono::milliseconds lastTimerSetting; + static void btnEventHandler(lv_obj_t* obj, lv_event_t event) { auto* screen = static_cast(obj->user_data); if (event == LV_EVENT_PRESSED) { @@ -17,6 +19,11 @@ static void btnEventHandler(lv_obj_t* obj, lv_event_t event) { } } +static void counterChangeHandler(void *timerScreen) { + Timer* timer = (Timer*)timerScreen; + lastTimerSetting = timer->GetCounters(); +} + Timer::Timer(Controllers::Timer& timerController) : timer {timerController} { lv_obj_t* colonLabel = lv_label_create(lv_scr_act(), nullptr); @@ -29,6 +36,8 @@ Timer::Timer(Controllers::Timer& timerController) : timer {timerController} { secondCounter.Create(); lv_obj_align(minuteCounter.GetObject(), nullptr, LV_ALIGN_IN_TOP_LEFT, 0, 0); lv_obj_align(secondCounter.GetObject(), nullptr, LV_ALIGN_IN_TOP_RIGHT, 0, 0); + minuteCounter.SetValueChangedEventCallback(this, counterChangeHandler); + secondCounter.SetValueChangedEventCallback(this, counterChangeHandler); highlightObjectMask = lv_objmask_create(lv_scr_act(), nullptr); lv_obj_set_size(highlightObjectMask, 240, 50); @@ -60,12 +69,20 @@ Timer::Timer(Controllers::Timer& timerController) : timer {timerController} { lv_obj_set_size(btnPlayPause, LV_HOR_RES, 50); txtPlayPause = lv_label_create(lv_scr_act(), nullptr); - lv_obj_align(txtPlayPause, btnPlayPause, LV_ALIGN_CENTER, 0, 0); - if (timer.IsRunning()) { - SetTimerRunning(); - } else { - SetTimerStopped(); + switch (timer.GetState()) { + case Controllers::Timer::TimerState::Stopped: + SetCounters(lastTimerSetting); + SetInterfaceStopped(); + break; + case Controllers::Timer::TimerState::Running: + SetCounters(timer.GetTimeRemaining()); + SetInterfaceRunning(); + break; + case Controllers::Timer::TimerState::Paused: + SetCounters(timer.GetTimeRemaining()); + SetInterfacePaused(); + break; } taskRefresh = lv_task_create(RefreshTaskCallback, LV_DISP_DEF_REFR_PERIOD, LV_TASK_PRIO_MID, this); @@ -76,6 +93,11 @@ Timer::~Timer() { lv_obj_clean(lv_scr_act()); } +void Timer::SetButtonText(const char* text) { + lv_label_set_text_static(txtPlayPause, text); + lv_obj_align(txtPlayPause, btnPlayPause, LV_ALIGN_CENTER, 0, 0); +} + void Timer::ButtonPressed() { pressTime = xTaskGetTickCount(); buttonPressing = true; @@ -83,15 +105,40 @@ void Timer::ButtonPressed() { void Timer::MaskReset() { buttonPressing = false; + // A click event is processed before a release event, // so the release event would override the "Pause" text without this check - if (!timer.IsRunning()) { - lv_label_set_text_static(txtPlayPause, "Start"); + switch (timer.GetState()) { + case Controllers::Timer::TimerState::Stopped: + SetButtonText("Start"); + break; + case Controllers::Timer::TimerState::Running: + SetButtonText("Pause"); + break; + case Controllers::Timer::TimerState::Paused: + SetButtonText("Resume"); + break; } + maskPosition = 0; UpdateMask(); } +void Timer::HandleHold() { + if (timer.GetState() == Controllers::Timer::TimerState::Stopped) { + SetButtonText("Reset"); + } else { + SetButtonText("Stop"); + } + + maskPosition += 15; + if (maskPosition > 240) { + HandleLongPress(); + } else { + UpdateMask(); + } +} + void Timer::UpdateMask() { lv_draw_mask_line_param_t maskLine; @@ -102,52 +149,81 @@ void Timer::UpdateMask() { lv_objmask_update_mask(btnObjectMask, btnMask, &maskLine); } +void Timer::HandleLongPress() { + if (timer.GetState() == Controllers::Timer::TimerState::Stopped) { + Reset(); + } else { + Stop(); + } + + MaskReset(); +} + void Timer::Refresh() { - if (timer.IsRunning()) { + if (timer.GetState() == Controllers::Timer::TimerState::Running) { auto secondsRemaining = std::chrono::duration_cast(timer.GetTimeRemaining()); - minuteCounter.SetValue(secondsRemaining.count() / 60); - secondCounter.SetValue(secondsRemaining.count() % 60); - } else if (buttonPressing && xTaskGetTickCount() > pressTime + pdMS_TO_TICKS(150)) { - lv_label_set_text_static(txtPlayPause, "Reset"); - maskPosition += 15; - if (maskPosition > 240) { - MaskReset(); - Reset(); - } else { - UpdateMask(); + SetCounters(secondsRemaining); + } else { + if (buttonPressing && xTaskGetTickCount() > pressTime + pdMS_TO_TICKS(150)) { + HandleHold(); } } } -void Timer::SetTimerRunning() { +void Timer::SetInterfaceRunning() { minuteCounter.HideControls(); secondCounter.HideControls(); - lv_label_set_text_static(txtPlayPause, "Pause"); + SetButtonText("Pause"); } -void Timer::SetTimerStopped() { +void Timer::SetInterfacePaused() { + minuteCounter.HideControls(); + secondCounter.HideControls(); + SetButtonText("Resume"); +} + +void Timer::SetInterfaceStopped() { minuteCounter.ShowControls(); secondCounter.ShowControls(); - lv_label_set_text_static(txtPlayPause, "Start"); + SetButtonText("Start"); +} + +void Timer::SetCounters(const std::chrono::milliseconds& duration) { + SetCounters(std::chrono::duration_cast(duration)); +} + +void Timer::SetCounters(const std::chrono::seconds& duration) { + minuteCounter.SetValue(duration.count() / 60); + secondCounter.SetValue(duration.count() % 60); +} + +std::chrono::seconds Timer::GetCounters() { + return std::chrono::minutes(minuteCounter.GetValue()) + std::chrono::seconds(secondCounter.GetValue()); } void Timer::ToggleRunning() { - if (timer.IsRunning()) { - auto secondsRemaining = std::chrono::duration_cast(timer.GetTimeRemaining()); - minuteCounter.SetValue(secondsRemaining.count() / 60); - secondCounter.SetValue(secondsRemaining.count() % 60); - timer.StopTimer(); - SetTimerStopped(); - } else if (secondCounter.GetValue() + minuteCounter.GetValue() > 0) { - auto timerDuration = std::chrono::minutes(minuteCounter.GetValue()) + std::chrono::seconds(secondCounter.GetValue()); - timer.StartTimer(timerDuration); - Refresh(); - SetTimerRunning(); + if (timer.GetState() == Controllers::Timer::TimerState::Stopped) { + if (secondCounter.GetValue() + minuteCounter.GetValue() > 0) { + timer.Start(GetCounters()); + Refresh(); + SetInterfaceRunning(); + } + } else if (timer.GetState() == Controllers::Timer::TimerState::Running) { + timer.Pause(); + SetInterfacePaused(); + } else { // Paused + timer.Resume(); + SetInterfaceRunning(); } } void Timer::Reset() { - minuteCounter.SetValue(0); - secondCounter.SetValue(0); - SetTimerStopped(); + lastTimerSetting = std::chrono::seconds(0); + Stop(); +} + +void Timer::Stop() { + timer.Stop(); + SetCounters(lastTimerSetting); + SetInterfaceStopped(); } diff --git a/src/displayapp/screens/Timer.h b/src/displayapp/screens/Timer.h index 409cae1c..535deba3 100644 --- a/src/displayapp/screens/Timer.h +++ b/src/displayapp/screens/Timer.h @@ -17,15 +17,23 @@ namespace Pinetime::Applications { Timer(Controllers::Timer& timerController); ~Timer() override; void Refresh() override; + void Stop(); void Reset(); void ToggleRunning(); void ButtonPressed(); void MaskReset(); + std::chrono::seconds GetCounters(); private: - void SetTimerRunning(); - void SetTimerStopped(); + void SetInterfaceRunning(); + void SetInterfacePaused(); + void SetInterfaceStopped(); + void HandleHold(); void UpdateMask(); + void HandleLongPress(); + void SetCounters(const std::chrono::milliseconds& duration); + void SetCounters(const std::chrono::seconds& duration); + void SetButtonText(const char* text); Pinetime::Controllers::Timer& timer; lv_obj_t* btnPlayPause; From ca0b300f06460085b16a9d36aaf74ef959336e2b Mon Sep 17 00:00:00 2001 From: Mathias Tausen Date: Sun, 28 Apr 2024 15:40:02 +0200 Subject: [PATCH 2/2] Decrease FreeRTOS heap size ld reports "region RAM overflowed with stack" otherwise --- src/FreeRTOSConfig.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/FreeRTOSConfig.h b/src/FreeRTOSConfig.h index 67c33a34..4696e386 100644 --- a/src/FreeRTOSConfig.h +++ b/src/FreeRTOSConfig.h @@ -62,7 +62,7 @@ #define configTICK_RATE_HZ 1024 #define configMAX_PRIORITIES (3) #define configMINIMAL_STACK_SIZE (120) -#define configTOTAL_HEAP_SIZE (1024 * 40) +#define configTOTAL_HEAP_SIZE (1024 * 39) #define configMAX_TASK_NAME_LEN (4) #define configUSE_16_BIT_TICKS 0 #define configIDLE_SHOULD_YIELD 1