From d41896bcfcd24d5bd94fea5d5927816633a10c46 Mon Sep 17 00:00:00 2001 From: Tanner Stocks Date: Mon, 27 Feb 2023 18:07:23 -0700 Subject: [PATCH 1/2] Modify Timer app to have snoozable alert --- src/components/timer/TimerController.cpp | 40 ++++- src/components/timer/TimerController.h | 27 ++- src/displayapp/DisplayApp.cpp | 10 +- src/displayapp/Messages.h | 1 + src/displayapp/screens/Timer.cpp | 202 +++++++++++++++++++---- src/displayapp/screens/Timer.h | 31 +++- src/libs/date | 1 + src/systemtask/Messages.h | 1 + src/systemtask/SystemTask.cpp | 14 +- 9 files changed, 279 insertions(+), 48 deletions(-) create mode 160000 src/libs/date diff --git a/src/components/timer/TimerController.cpp b/src/components/timer/TimerController.cpp index 5e7f1eed21..8cb54a9847 100644 --- a/src/components/timer/TimerController.cpp +++ b/src/components/timer/TimerController.cpp @@ -13,20 +13,32 @@ void TimerController::Init(Pinetime::System::SystemTask* systemTask) { timer = xTimerCreate("Timer", 1, pdFALSE, this, TimerCallback); } -void TimerController::StartTimer(std::chrono::milliseconds duration) { - xTimerChangePeriod(timer, pdMS_TO_TICKS(duration.count()), 0); +void TimerController::StartTimer(std::chrono::hours hours, std::chrono::minutes minutes) { + this->hours = hours; + this->minutes = minutes; + auto duration = std::chrono::duration_cast(hours) + + std::chrono::duration_cast(minutes); + StartTimer(duration); +} + +void TimerController::StartTimer(std::chrono::milliseconds duration){ + state = TimerState::Running; + auto durationSeconds = std::chrono::duration_cast(duration); + xTimerChangePeriod(timer, durationSeconds.count() * configTICK_RATE_HZ, 0); xTimerStart(timer, 0); } std::chrono::milliseconds TimerController::GetTimeRemaining() { if (IsRunning()) { TickType_t remainingTime = xTimerGetExpiryTime(timer) - xTaskGetTickCount(); - return std::chrono::milliseconds(remainingTime * 1000 / configTICK_RATE_HZ); + auto remainingSeconds = std::chrono::seconds(remainingTime / configTICK_RATE_HZ); + return std::chrono::duration_cast(remainingSeconds); } return std::chrono::milliseconds(0); } void TimerController::StopTimer() { + state = TimerState::Dormant; xTimerStop(timer, 0); } @@ -35,5 +47,25 @@ bool TimerController::IsRunning() { } void TimerController::OnTimerEnd() { - systemTask->PushMessage(System::Messages::OnTimerDone); + + if(!useAlert){ + state = TimerState::Dormant; + systemTask->PushMessage(System::Messages::OnTimerDone); + return; + } + + state = TimerState::Alerting; + systemTask->PushMessage(System::Messages::StartTimerAlert); +} + +void TimerController::SnoozeAlert(){ + if(state != TimerState::Alerting) return; + state = TimerState::Snoozed; + auto snoozeSeconds = std::chrono::duration_cast(snoozeTime); + xTimerChangePeriod(timer, snoozeSeconds.count() * configTICK_RATE_HZ, 0); + xTimerStart(timer, 0); +} + +void TimerController::StopAlerting() { + StopTimer(); } diff --git a/src/components/timer/TimerController.h b/src/components/timer/TimerController.h index 1c2e44b68e..16e4124635 100644 --- a/src/components/timer/TimerController.h +++ b/src/components/timer/TimerController.h @@ -9,15 +9,15 @@ namespace Pinetime { namespace System { class SystemTask; } - namespace Controllers { - class TimerController { public: TimerController() = default; void Init(System::SystemTask* systemTask); + void StartTimer(std::chrono::hours hours, std::chrono::minutes minutes); + void StartTimer(std::chrono::milliseconds duration); void StopTimer(); @@ -28,9 +28,32 @@ namespace Pinetime { void OnTimerEnd(); + enum class TimerState { Dormant, Running, Alerting, Snoozed }; + + void SnoozeAlert(); + + void StopAlerting(); + + std::chrono::hours Hours() const{ + return hours; + } + + std::chrono::minutes Minutes() const{ + return minutes; + } + + TimerState State() const{ + return state; + } + private: System::SystemTask* systemTask = nullptr; TimerHandle_t timer; + bool useAlert = true; + std::chrono::hours hours = std::chrono::hours(0); + std::chrono::minutes minutes = std::chrono::minutes(0); + std::chrono::milliseconds snoozeTime = std::chrono::duration_cast(std::chrono::minutes(10)); + TimerState state = TimerState::Dormant; }; } } diff --git a/src/displayapp/DisplayApp.cpp b/src/displayapp/DisplayApp.cpp index ccba7ee6ea..cccc28f858 100644 --- a/src/displayapp/DisplayApp.cpp +++ b/src/displayapp/DisplayApp.cpp @@ -213,6 +213,14 @@ void DisplayApp::Refresh() { } motorController.RunForDuration(35); break; + case Messages::TimerAlerting: + if (currentApp == Apps::Timer) { + auto* Timer = static_cast(currentScreen.get()); + Timer->SetAlerting(); + } else { + LoadNewScreen(Apps::Timer, DisplayApp::FullRefreshDirections::None); + } + break; case Messages::AlarmTriggered: if (currentApp == Apps::Alarm) { auto* alarm = static_cast(currentScreen.get()); @@ -410,7 +418,7 @@ void DisplayApp::LoadScreen(Apps app, DisplayApp::FullRefreshDirections directio Screens::Notifications::Modes::Preview); break; case Apps::Timer: - currentScreen = std::make_unique(timerController); + currentScreen = std::make_unique(timerController, *systemTask, motorController); break; case Apps::Alarm: currentScreen = std::make_unique(alarmController, settingsController.GetClockType(), *systemTask, motorController); diff --git a/src/displayapp/Messages.h b/src/displayapp/Messages.h index b670b1aae4..274188d335 100644 --- a/src/displayapp/Messages.h +++ b/src/displayapp/Messages.h @@ -16,6 +16,7 @@ namespace Pinetime { ButtonDoubleClicked, NewNotification, TimerDone, + TimerAlerting, BleFirmwareUpdateStarted, UpdateTimeOut, DimScreen, diff --git a/src/displayapp/screens/Timer.cpp b/src/displayapp/screens/Timer.cpp index df78a5a0f2..aa9a755c4a 100644 --- a/src/displayapp/screens/Timer.cpp +++ b/src/displayapp/screens/Timer.cpp @@ -5,19 +5,28 @@ #include using namespace Pinetime::Applications::Screens; +using Pinetime::Controllers::TimerController; static void btnEventHandler(lv_obj_t* obj, lv_event_t event) { auto* screen = static_cast(obj->user_data); if (event == LV_EVENT_PRESSED) { - screen->ButtonPressed(); + screen->ButtonPressed(obj); } else if (event == LV_EVENT_RELEASED || event == LV_EVENT_PRESS_LOST) { - screen->MaskReset(); + screen->ButtonReleased(obj); } else if (event == LV_EVENT_SHORT_CLICKED) { - screen->ToggleRunning(); + screen->ButtonShortClicked(obj); } } -Timer::Timer(Controllers::TimerController& timerController) : timerController {timerController} { +static void SnoozeAlertTaskCallback(lv_task_t* task) { + auto* screen = static_cast(task->user_data); + screen->SnoozeAlert(); +} + +Timer::Timer(Controllers::TimerController& timerController, + System::SystemTask& systemTask, + Controllers::MotorController& motorController) + : timerController {timerController}, systemTask {systemTask}, motorController {motorController} { lv_obj_t* colonLabel = lv_label_create(lv_scr_act(), nullptr); lv_obj_set_style_local_text_font(colonLabel, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, &jetbrains_mono_76); @@ -25,10 +34,11 @@ Timer::Timer(Controllers::TimerController& timerController) : timerController {t lv_label_set_text_static(colonLabel, ":"); lv_obj_align(colonLabel, lv_scr_act(), LV_ALIGN_CENTER, 0, -29); + hourCounter.Create(); minuteCounter.Create(); - 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); + + lv_obj_align(hourCounter.GetObject(), nullptr, LV_ALIGN_IN_TOP_LEFT, 0, 0); + lv_obj_align(minuteCounter.GetObject(), nullptr, LV_ALIGN_IN_TOP_RIGHT, 0, 0); highlightObjectMask = lv_objmask_create(lv_scr_act(), nullptr); lv_obj_set_size(highlightObjectMask, 240, 50); @@ -59,26 +69,98 @@ Timer::Timer(Controllers::TimerController& timerController) : timerController {t lv_obj_set_event_cb(btnPlayPause, btnEventHandler); 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); + txtPlayPause = lv_label_create(btnPlayPause, nullptr); - if (timerController.IsRunning()) { - SetTimerRunning(); - } else { - SetTimerStopped(); + btnStop = lv_btn_create(lv_scr_act(), nullptr); + btnStop->user_data = this; + lv_obj_set_event_cb(btnStop, btnEventHandler); + lv_obj_set_size(btnStop, 115, 50); + lv_obj_align(btnStop, lv_scr_act(), LV_ALIGN_IN_BOTTOM_LEFT, 0, 0); + lv_obj_set_style_local_bg_color(btnStop, LV_BTN_PART_MAIN, LV_STATE_DEFAULT, LV_COLOR_RED); + lv_obj_set_hidden(btnStop, true); + + txtStop = lv_label_create(btnStop, nullptr); + lv_label_set_text_static(txtStop, Symbols::stop); + + btnSnooze = lv_btn_create(lv_scr_act(), nullptr); + btnSnooze->user_data = this; + lv_obj_set_event_cb(btnSnooze, btnEventHandler); + lv_obj_set_size(btnSnooze, 115, 50); + lv_obj_align(btnSnooze, lv_scr_act(), LV_ALIGN_IN_BOTTOM_RIGHT, 0, 0); + lv_obj_set_style_local_bg_color(btnSnooze, LV_BTN_PART_MAIN, LV_STATE_DEFAULT, LV_COLOR_BLUE); + lv_obj_set_hidden(btnSnooze, true); + + txtSnooze = lv_label_create(btnSnooze, nullptr); + + switch (timerController.State()){ + case TimerController::TimerState::Dormant: + Reset(); + break; + case TimerController::TimerState::Running: + ShowTimerRunning(); + break; + case TimerController::TimerState::Alerting: + SetAlerting(); + break; + case TimerController::TimerState::Snoozed: + ShowSnoozed(); + break; } taskRefresh = lv_task_create(RefreshTaskCallback, LV_DISP_DEF_REFR_PERIOD, LV_TASK_PRIO_MID, this); } Timer::~Timer() { + if (timerController.State() == TimerController::TimerState::Alerting) { + StopAlerting(); + } lv_task_del(taskRefresh); lv_obj_clean(lv_scr_act()); } -void Timer::ButtonPressed() { - pressTime = xTaskGetTickCount(); - buttonPressing = true; +void Timer::ButtonPressed(lv_obj_t* obj) { + if(obj == btnPlayPause) { + pressTime = xTaskGetTickCount(); + buttonPressing = true; + return; + } +} + +void Timer::ButtonReleased(lv_obj_t* obj) { + if(obj == btnPlayPause){ + MaskReset(); + return; + } +} + +void Timer::ButtonShortClicked(lv_obj_t* obj) { + if(obj == btnPlayPause){ + ToggleRunning(); + return; + } + if(obj == btnSnooze){ + if(timerController.State() == TimerController::TimerState::Snoozed) return; + SnoozeAlert(); + return; + } + if(obj == btnStop){ + StopAlerting(); + return; + } +} + +// Pysical side button +bool Timer::OnButtonPushed() { + if (timerController.State() == TimerController::TimerState::Alerting) { + SnoozeAlert(); + return true; + } + return false; +} + +bool Timer::OnTouchEvent(Pinetime::Applications::TouchEvents event) { + // Don't allow closing the screen by swiping while the timer is alerting + return event == TouchEvents::SwipeDown && timerController.State() == TimerController::TimerState::Alerting; } void Timer::MaskReset() { @@ -104,9 +186,10 @@ void Timer::UpdateMask() { void Timer::Refresh() { if (timerController.IsRunning()) { - auto secondsRemaining = std::chrono::duration_cast(timerController.GetTimeRemaining()); - minuteCounter.SetValue(secondsRemaining.count() / 60); - secondCounter.SetValue(secondsRemaining.count() % 60); + auto minutesRemaining = std::chrono::duration_cast(timerController.GetTimeRemaining()); + auto hoursRemaining = std::chrono::duration_cast(minutesRemaining); + hourCounter.SetValue(hoursRemaining.count()); + minuteCounter.SetValue(minutesRemaining.count() % 60); } else if (buttonPressing && xTaskGetTickCount() > pressTime + pdMS_TO_TICKS(150)) { lv_label_set_text_static(txtPlayPause, "Reset"); maskPosition += 15; @@ -119,35 +202,86 @@ void Timer::Refresh() { } } -void Timer::SetTimerRunning() { +void Timer::ShowTimerRunning() { + hourCounter.HideControls(); minuteCounter.HideControls(); - secondCounter.HideControls(); lv_label_set_text_static(txtPlayPause, "Pause"); } -void Timer::SetTimerStopped() { +void Timer::ShowTimerStopped() { + hourCounter.ShowControls(); minuteCounter.ShowControls(); - secondCounter.ShowControls(); lv_label_set_text_static(txtPlayPause, "Start"); } void Timer::ToggleRunning() { if (timerController.IsRunning()) { - auto secondsRemaining = std::chrono::duration_cast(timerController.GetTimeRemaining()); - minuteCounter.SetValue(secondsRemaining.count() / 60); - secondCounter.SetValue(secondsRemaining.count() % 60); + auto minutesRemaining = std::chrono::duration_cast(timerController.GetTimeRemaining()); + auto hoursRemaining = std::chrono::duration_cast(minutesRemaining); + hourCounter.SetValue(hoursRemaining.count()); + minuteCounter.SetValue(minutesRemaining.count() % 60); timerController.StopTimer(); - SetTimerStopped(); - } else if (secondCounter.GetValue() + minuteCounter.GetValue() > 0) { - auto timerDuration = std::chrono::minutes(minuteCounter.GetValue()) + std::chrono::seconds(secondCounter.GetValue()); - timerController.StartTimer(timerDuration); + ShowTimerStopped(); + } else if (hourCounter.GetValue() + minuteCounter.GetValue() > 0) { + timerController.StartTimer(std::chrono::hours(hourCounter.GetValue()), std::chrono::minutes(minuteCounter.GetValue())); Refresh(); - SetTimerRunning(); + ShowTimerRunning(); } } void Timer::Reset() { - minuteCounter.SetValue(0); - secondCounter.SetValue(0); - SetTimerStopped(); + hourCounter.SetValue(timerController.Hours().count()); + minuteCounter.SetValue(timerController.Minutes().count()); + ShowTimerStopped(); +} + +void Timer::ShowAlertingButtons() { + ShowTimerRunning(); + lv_obj_set_hidden(btnPlayPause, true); + lv_obj_set_hidden(btnStop, false); + lv_obj_set_hidden(btnSnooze, false); +} + +void Timer::SetAlerting() { + ShowAlertingButtons(); + lv_label_set_text_static(txtSnooze, "zZ"); + + taskStopAlert = lv_task_create(SnoozeAlertTaskCallback, pdMS_TO_TICKS(alertTime.count()), LV_TASK_PRIO_MID, this); + motorController.StartRinging(); + systemTask.PushMessage(System::Messages::DisableSleeping); +} + +void Timer::ShowSnoozed(){ + ShowAlertingButtons(); + + lv_label_set_text_static(txtSnooze, "Snoozed"); + lv_obj_set_state(btnSnooze, LV_STATE_DISABLED); + lv_obj_set_state(txtSnooze, LV_STATE_DISABLED); +} + +void Timer::SnoozeAlert() { + ShowSnoozed(); + + timerController.SnoozeAlert(); + motorController.StopRinging(); + + if (taskStopAlert != nullptr) { + lv_task_del(taskStopAlert); + taskStopAlert = nullptr; + } + systemTask.PushMessage(System::Messages::EnableSleeping); +} + +void Timer::StopAlerting() { + timerController.StopAlerting(); + motorController.StopRinging(); + if (taskStopAlert != nullptr) { + lv_task_del(taskStopAlert); + taskStopAlert = nullptr; + } + systemTask.PushMessage(System::Messages::EnableSleeping); + lv_obj_set_hidden(btnStop, true); + lv_obj_set_hidden(btnSnooze, true); + lv_obj_set_hidden(btnPlayPause, false); + Reset(); } diff --git a/src/displayapp/screens/Timer.h b/src/displayapp/screens/Timer.h index a6e260633b..a4318890fc 100644 --- a/src/displayapp/screens/Timer.h +++ b/src/displayapp/screens/Timer.h @@ -12,31 +12,52 @@ namespace Pinetime::Applications::Screens { class Timer : public Screen { public: - Timer(Controllers::TimerController& timerController); + Timer(Controllers::TimerController& timerController, + System::SystemTask& systemTask, + Controllers::MotorController& motorController); ~Timer() override; void Refresh() override; void Reset(); void ToggleRunning(); - void ButtonPressed(); + void ButtonPressed(lv_obj_t* obj); + void ButtonReleased(lv_obj_t* obj); + void ButtonShortClicked(lv_obj_t* obj); + bool OnButtonPushed() override; + bool OnTouchEvent(TouchEvents event) override; void MaskReset(); + void SetAlerting(); + void SnoozeAlert(); + void StopAlerting(); private: - void SetTimerRunning(); - void SetTimerStopped(); + void ShowTimerRunning(); + void ShowTimerStopped(); + void ShowAlertingButtons(); + void ShowSnoozed(); void UpdateMask(); + Controllers::TimerController& timerController; + System::SystemTask& systemTask; + Controllers::MotorController& motorController; lv_obj_t* btnPlayPause; lv_obj_t* txtPlayPause; + lv_obj_t* btnStop; + lv_obj_t* txtStop; + lv_obj_t* btnSnooze; + lv_obj_t* txtSnooze; + lv_task_t* taskStopAlert = nullptr; lv_obj_t* btnObjectMask; lv_obj_t* highlightObjectMask; lv_objmask_mask_t* btnMask; lv_objmask_mask_t* highlightMask; lv_task_t* taskRefresh; + Widgets::Counter hourCounter = Widgets::Counter(0, 23, jetbrains_mono_76); Widgets::Counter minuteCounter = Widgets::Counter(0, 59, jetbrains_mono_76); - Widgets::Counter secondCounter = Widgets::Counter(0, 59, jetbrains_mono_76); + + std::chrono::milliseconds alertTime = std::chrono::duration_cast(std::chrono::minutes(10)); bool buttonPressing = false; lv_coord_t maskPosition = 0; diff --git a/src/libs/date b/src/libs/date new file mode 160000 index 0000000000..9502bc27a3 --- /dev/null +++ b/src/libs/date @@ -0,0 +1 @@ +Subproject commit 9502bc27a3b32fa5759ea82a76c22551620ebcd2 diff --git a/src/systemtask/Messages.h b/src/systemtask/Messages.h index 9679bbb5f0..26e2dfd2f7 100644 --- a/src/systemtask/Messages.h +++ b/src/systemtask/Messages.h @@ -27,6 +27,7 @@ namespace Pinetime { OnChargingEvent, OnPairing, SetOffAlarm, + StartTimerAlert, MeasureBatteryTimerExpired, BatteryPercentageUpdated, LowBattery, diff --git a/src/systemtask/SystemTask.cpp b/src/systemtask/SystemTask.cpp index 3ceb63d47a..b4f027e373 100644 --- a/src/systemtask/SystemTask.cpp +++ b/src/systemtask/SystemTask.cpp @@ -294,6 +294,12 @@ void SystemTask::Work() { } displayApp.PushMessage(Pinetime::Applications::Display::Messages::AlarmTriggered); break; + case Messages::StartTimerAlert: + if (state == SystemTaskState::Sleeping) { + GoToRunning(); + } + displayApp.PushMessage(Pinetime::Applications::Display::Messages::TimerAlerting); + break; case Messages::BleConnected: ReloadIdleTimer(); isBleDiscoveryTimerRunning = true; @@ -374,9 +380,11 @@ void SystemTask::Work() { break; case Messages::OnNewHour: using Pinetime::Controllers::AlarmController; + using Pinetime::Controllers::TimerController; if (settingsController.GetNotificationStatus() != Controllers::Settings::Notification::Sleep && settingsController.GetChimeOption() == Controllers::Settings::ChimesOption::Hours && - alarmController.State() != AlarmController::AlarmState::Alerting) { + alarmController.State() != AlarmController::AlarmState::Alerting && + timerController.State() != TimerController::TimerState::Alerting) { if (state == SystemTaskState::Sleeping) { GoToRunning(); displayApp.PushMessage(Pinetime::Applications::Display::Messages::Chime); @@ -385,9 +393,11 @@ void SystemTask::Work() { break; case Messages::OnNewHalfHour: using Pinetime::Controllers::AlarmController; + using Pinetime::Controllers::TimerController; if (settingsController.GetNotificationStatus() != Controllers::Settings::Notification::Sleep && settingsController.GetChimeOption() == Controllers::Settings::ChimesOption::HalfHours && - alarmController.State() != AlarmController::AlarmState::Alerting) { + alarmController.State() != AlarmController::AlarmState::Alerting && + timerController.State() != TimerController::TimerState::Alerting) { if (state == SystemTaskState::Sleeping) { GoToRunning(); displayApp.PushMessage(Pinetime::Applications::Display::Messages::Chime); From 387ff127cdfb094b722a6ea4c14aa2095f1cfb44 Mon Sep 17 00:00:00 2001 From: Tanner Stocks Date: Mon, 27 Feb 2023 18:42:58 -0700 Subject: [PATCH 2/2] Disable and enable snooze button --- src/displayapp/screens/Timer.cpp | 9 +++++++-- src/displayapp/screens/Timer.h | 1 + 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/src/displayapp/screens/Timer.cpp b/src/displayapp/screens/Timer.cpp index aa9a755c4a..12e5c3a597 100644 --- a/src/displayapp/screens/Timer.cpp +++ b/src/displayapp/screens/Timer.cpp @@ -139,7 +139,6 @@ void Timer::ButtonShortClicked(lv_obj_t* obj) { return; } if(obj == btnSnooze){ - if(timerController.State() == TimerController::TimerState::Snoozed) return; SnoozeAlert(); return; } @@ -242,9 +241,15 @@ void Timer::ShowAlertingButtons() { lv_obj_set_hidden(btnSnooze, false); } -void Timer::SetAlerting() { +void Timer::ShowAlerting() { ShowAlertingButtons(); lv_label_set_text_static(txtSnooze, "zZ"); + lv_obj_set_state(btnSnooze, LV_STATE_DEFAULT); + lv_obj_set_state(txtSnooze, LV_STATE_DEFAULT); +} + +void Timer::SetAlerting() { + ShowAlerting(); taskStopAlert = lv_task_create(SnoozeAlertTaskCallback, pdMS_TO_TICKS(alertTime.count()), LV_TASK_PRIO_MID, this); motorController.StartRinging(); diff --git a/src/displayapp/screens/Timer.h b/src/displayapp/screens/Timer.h index a4318890fc..feca1f17cb 100644 --- a/src/displayapp/screens/Timer.h +++ b/src/displayapp/screens/Timer.h @@ -33,6 +33,7 @@ namespace Pinetime::Applications::Screens { void ShowTimerRunning(); void ShowTimerStopped(); void ShowAlertingButtons(); + void ShowAlerting(); void ShowSnoozed(); void UpdateMask();