From a0cd439efc9b0d0d9610dea7ff749f102d5a316d Mon Sep 17 00:00:00 2001 From: NeroBurner Date: Sat, 28 Sep 2024 08:14:08 +0200 Subject: [PATCH] Alarm persist to flash (#1367) * AlarmController: Add saving alarm time to file Save the set alarm time to the SPI NOR flash, so it does not reset to the default value when the watch resets, e.g. due to watchdog timeout or reflashing of a new version of InfiniTime. Just like the `Settings.h` `LoadSettingsFromFile()` the previous alarm at boot (if available) and `SaveSettingsToFile()` the current alarm when the `Alarm.h` screen is closed (only if the settings have changed). The alarm-settings file is stored in `.system/alarm.dat`. The `.system` folder is created if it doesn't yet exist. Fixes: https://github.com/InfiniTimeOrg/InfiniTime/issues/1330 * alarmController: close .system dir after usage Close the `lfs_dir` object for the `.system` dir after usage. Otherwise on the second changed alarm the system will lockup because the `.system` dir is already open and was never closed. --------- Co-authored-by: Galdor Takacs --- src/components/alarm/AlarmController.cpp | 101 ++++++++++++++++++++--- src/components/alarm/AlarmController.h | 46 +++++++---- src/displayapp/screens/Alarm.cpp | 26 +++--- src/main.cpp | 2 +- src/systemtask/SystemTask.cpp | 8 +- 5 files changed, 135 insertions(+), 48 deletions(-) diff --git a/src/components/alarm/AlarmController.cpp b/src/components/alarm/AlarmController.cpp index c4eb8ed0..7dbfb0e3 100644 --- a/src/components/alarm/AlarmController.cpp +++ b/src/components/alarm/AlarmController.cpp @@ -19,11 +19,13 @@ #include "systemtask/SystemTask.h" #include "task.h" #include +#include using namespace Pinetime::Controllers; using namespace std::chrono_literals; -AlarmController::AlarmController(Controllers::DateTime& dateTimeController) : dateTimeController {dateTimeController} { +AlarmController::AlarmController(Controllers::DateTime& dateTimeController, Controllers::FS& fs) + : dateTimeController {dateTimeController}, fs {fs} { } namespace { @@ -36,11 +38,28 @@ namespace { void AlarmController::Init(System::SystemTask* systemTask) { this->systemTask = systemTask; alarmTimer = xTimerCreate("Alarm", 1, pdFALSE, this, SetOffAlarm); + LoadSettingsFromFile(); + if (alarm.isEnabled) { + NRF_LOG_INFO("[AlarmController] Loaded alarm was enabled, scheduling"); + ScheduleAlarm(); + } +} + +void AlarmController::SaveAlarm() { + // verify if it is necessary to save + if (alarmChanged) { + SaveSettingsToFile(); + } + alarmChanged = false; } void AlarmController::SetAlarmTime(uint8_t alarmHr, uint8_t alarmMin) { - hours = alarmHr; - minutes = alarmMin; + if (alarm.hours == alarmHr && alarm.minutes == alarmMin) { + return; + } + alarm.hours = alarmHr; + alarm.minutes = alarmMin; + alarmChanged = true; } void AlarmController::ScheduleAlarm() { @@ -53,18 +72,19 @@ void AlarmController::ScheduleAlarm() { tm* tmAlarmTime = std::localtime(&ttAlarmTime); // If the time being set has already passed today,the alarm should be set for tomorrow - if (hours < dateTimeController.Hours() || (hours == dateTimeController.Hours() && minutes <= dateTimeController.Minutes())) { + if (alarm.hours < dateTimeController.Hours() || + (alarm.hours == dateTimeController.Hours() && alarm.minutes <= dateTimeController.Minutes())) { tmAlarmTime->tm_mday += 1; // tm_wday doesn't update automatically tmAlarmTime->tm_wday = (tmAlarmTime->tm_wday + 1) % 7; } - tmAlarmTime->tm_hour = hours; - tmAlarmTime->tm_min = minutes; + tmAlarmTime->tm_hour = alarm.hours; + tmAlarmTime->tm_min = alarm.minutes; tmAlarmTime->tm_sec = 0; // if alarm is in weekday-only mode, make sure it shifts to the next weekday - if (recurrence == RecurType::Weekdays) { + if (alarm.recurrence == RecurType::Weekdays) { if (tmAlarmTime->tm_wday == 0) { // Sunday, shift 1 day tmAlarmTime->tm_mday += 1; } else if (tmAlarmTime->tm_wday == 6) { // Saturday, shift 2 days @@ -79,7 +99,10 @@ void AlarmController::ScheduleAlarm() { xTimerChangePeriod(alarmTimer, secondsToAlarm * configTICK_RATE_HZ, 0); xTimerStart(alarmTimer, 0); - state = AlarmState::Set; + if (!alarm.isEnabled) { + alarm.isEnabled = true; + alarmChanged = true; + } } uint32_t AlarmController::SecondsToAlarm() const { @@ -88,20 +111,72 @@ uint32_t AlarmController::SecondsToAlarm() const { void AlarmController::DisableAlarm() { xTimerStop(alarmTimer, 0); - state = AlarmState::Not_Set; + isAlerting = false; + if (alarm.isEnabled) { + alarm.isEnabled = false; + alarmChanged = true; + } } void AlarmController::SetOffAlarmNow() { - state = AlarmState::Alerting; + isAlerting = true; systemTask->PushMessage(System::Messages::SetOffAlarm); } void AlarmController::StopAlerting() { - // Alarm state is off unless this is a recurring alarm - if (recurrence == RecurType::None) { - state = AlarmState::Not_Set; + isAlerting = false; + // Disable alarm unless it is recurring + if (alarm.recurrence == RecurType::None) { + alarm.isEnabled = false; + alarmChanged = true; } else { // set next instance ScheduleAlarm(); } } + +void AlarmController::SetRecurrence(RecurType recurrence) { + if (alarm.recurrence != recurrence) { + alarm.recurrence = recurrence; + alarmChanged = true; + } +} + +void AlarmController::LoadSettingsFromFile() { + lfs_file_t alarmFile; + AlarmSettings alarmBuffer; + + if (fs.FileOpen(&alarmFile, "/.system/alarm.dat", LFS_O_RDONLY) != LFS_ERR_OK) { + NRF_LOG_WARNING("[AlarmController] Failed to open alarm data file"); + return; + } + + fs.FileRead(&alarmFile, reinterpret_cast(&alarmBuffer), sizeof(alarmBuffer)); + fs.FileClose(&alarmFile); + if (alarmBuffer.version != alarmFormatVersion) { + NRF_LOG_WARNING("[AlarmController] Loaded alarm settings has version %u instead of %u, discarding", + alarmBuffer.version, + alarmFormatVersion); + return; + } + + alarm = alarmBuffer; + NRF_LOG_INFO("[AlarmController] Loaded alarm settings from file"); +} + +void AlarmController::SaveSettingsToFile() const { + lfs_dir systemDir; + if (fs.DirOpen("/.system", &systemDir) != LFS_ERR_OK) { + fs.DirCreate("/.system"); + } + fs.DirClose(&systemDir); + lfs_file_t alarmFile; + if (fs.FileOpen(&alarmFile, "/.system/alarm.dat", LFS_O_WRONLY | LFS_O_CREAT) != LFS_ERR_OK) { + NRF_LOG_WARNING("[AlarmController] Failed to open alarm data file for saving"); + return; + } + + fs.FileWrite(&alarmFile, reinterpret_cast(&alarm), sizeof(alarm)); + fs.FileClose(&alarmFile); + NRF_LOG_INFO("[AlarmController] Saved alarm settings with format version %u to file", alarm.version); +} diff --git a/src/components/alarm/AlarmController.h b/src/components/alarm/AlarmController.h index 8ac0de9a..278e9cdb 100644 --- a/src/components/alarm/AlarmController.h +++ b/src/components/alarm/AlarmController.h @@ -30,47 +30,65 @@ namespace Pinetime { namespace Controllers { class AlarmController { public: - AlarmController(Controllers::DateTime& dateTimeController); + AlarmController(Controllers::DateTime& dateTimeController, Controllers::FS& fs); void Init(System::SystemTask* systemTask); + void SaveAlarm(); void SetAlarmTime(uint8_t alarmHr, uint8_t alarmMin); void ScheduleAlarm(); void DisableAlarm(); void SetOffAlarmNow(); uint32_t SecondsToAlarm() const; void StopAlerting(); - enum class AlarmState { Not_Set, Set, Alerting }; enum class RecurType { None, Daily, Weekdays }; uint8_t Hours() const { - return hours; + return alarm.hours; } uint8_t Minutes() const { - return minutes; + return alarm.minutes; } - AlarmState State() const { - return state; + bool IsAlerting() const { + return isAlerting; + } + + bool IsEnabled() const { + return alarm.isEnabled; } RecurType Recurrence() const { - return recurrence; + return alarm.recurrence; } - void SetRecurrence(RecurType recurType) { - recurrence = recurType; - } + void SetRecurrence(RecurType recurrence); private: + // Versions 255 is reserved for now, so the version field can be made + // bigger, should it ever be needed. + static constexpr uint8_t alarmFormatVersion = 1; + + struct AlarmSettings { + uint8_t version = alarmFormatVersion; + uint8_t hours = 7; + uint8_t minutes = 0; + RecurType recurrence = RecurType::None; + bool isEnabled = false; + }; + + bool isAlerting = false; + bool alarmChanged = false; + Controllers::DateTime& dateTimeController; + Controllers::FS& fs; System::SystemTask* systemTask = nullptr; TimerHandle_t alarmTimer; - uint8_t hours = 7; - uint8_t minutes = 0; + AlarmSettings alarm; std::chrono::time_point alarmTime; - AlarmState state = AlarmState::Not_Set; - RecurType recurrence = RecurType::None; + + void LoadSettingsFromFile(); + void SaveSettingsToFile() const; }; } } diff --git a/src/displayapp/screens/Alarm.cpp b/src/displayapp/screens/Alarm.cpp index 292fb075..b1e67363 100644 --- a/src/displayapp/screens/Alarm.cpp +++ b/src/displayapp/screens/Alarm.cpp @@ -117,7 +117,7 @@ Alarm::Alarm(Controllers::AlarmController& alarmController, UpdateAlarmTime(); - if (alarmController.State() == Controllers::AlarmController::AlarmState::Alerting) { + if (alarmController.IsAlerting()) { SetAlerting(); } else { SetSwitchState(LV_ANIM_OFF); @@ -125,14 +125,15 @@ Alarm::Alarm(Controllers::AlarmController& alarmController, } Alarm::~Alarm() { - if (alarmController.State() == AlarmController::AlarmState::Alerting) { + if (alarmController.IsAlerting()) { StopAlerting(); } lv_obj_clean(lv_scr_act()); + alarmController.SaveAlarm(); } void Alarm::DisableAlarm() { - if (alarmController.State() == AlarmController::AlarmState::Set) { + if (alarmController.IsEnabled()) { alarmController.DisableAlarm(); lv_switch_off(enableSwitch, LV_ANIM_ON); } @@ -172,7 +173,7 @@ bool Alarm::OnButtonPushed() { HideInfo(); return true; } - if (alarmController.State() == AlarmController::AlarmState::Alerting) { + if (alarmController.IsAlerting()) { StopAlerting(); return true; } @@ -181,7 +182,7 @@ bool Alarm::OnButtonPushed() { bool Alarm::OnTouchEvent(Pinetime::Applications::TouchEvents event) { // Don't allow closing the screen by swiping while the alarm is alerting - return alarmController.State() == AlarmController::AlarmState::Alerting && event == TouchEvents::SwipeDown; + return alarmController.IsAlerting() && event == TouchEvents::SwipeDown; } void Alarm::OnValueChanged() { @@ -222,15 +223,10 @@ void Alarm::StopAlerting() { } void Alarm::SetSwitchState(lv_anim_enable_t anim) { - switch (alarmController.State()) { - case AlarmController::AlarmState::Set: - lv_switch_on(enableSwitch, anim); - break; - case AlarmController::AlarmState::Not_Set: - lv_switch_off(enableSwitch, anim); - break; - default: - break; + if (alarmController.IsEnabled()) { + lv_switch_on(enableSwitch, anim); + } else { + lv_switch_off(enableSwitch, anim); } } @@ -247,7 +243,7 @@ void Alarm::ShowInfo() { txtMessage = lv_label_create(btnMessage, nullptr); lv_obj_set_style_local_bg_color(btnMessage, LV_BTN_PART_MAIN, LV_STATE_DEFAULT, LV_COLOR_NAVY); - if (alarmController.State() == AlarmController::AlarmState::Set) { + if (alarmController.IsEnabled()) { auto timeToAlarm = alarmController.SecondsToAlarm(); auto daysToAlarm = timeToAlarm / 86400; diff --git a/src/main.cpp b/src/main.cpp index ab50fa74..84f30eef 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -104,7 +104,7 @@ Pinetime::Controllers::DateTime dateTimeController {settingsController}; Pinetime::Drivers::Watchdog watchdog; Pinetime::Controllers::NotificationManager notificationManager; Pinetime::Controllers::MotionController motionController; -Pinetime::Controllers::AlarmController alarmController {dateTimeController}; +Pinetime::Controllers::AlarmController alarmController {dateTimeController, fs}; Pinetime::Controllers::TouchHandler touchHandler; Pinetime::Controllers::ButtonHandler buttonHandler; Pinetime::Controllers::BrightnessController brightnessController {}; diff --git a/src/systemtask/SystemTask.cpp b/src/systemtask/SystemTask.cpp index e55c9ad8..fc4e8f7e 100644 --- a/src/systemtask/SystemTask.cpp +++ b/src/systemtask/SystemTask.cpp @@ -216,7 +216,7 @@ void SystemTask::Work() { GoToSleep(); break; case Messages::OnNewTime: - if (alarmController.State() == Controllers::AlarmController::AlarmState::Set) { + if (alarmController.IsEnabled()) { alarmController.ScheduleAlarm(); } break; @@ -317,8 +317,7 @@ void SystemTask::Work() { case Messages::OnNewHour: using Pinetime::Controllers::AlarmController; if (settingsController.GetNotificationStatus() != Controllers::Settings::Notification::Sleep && - settingsController.GetChimeOption() == Controllers::Settings::ChimesOption::Hours && - alarmController.State() != AlarmController::AlarmState::Alerting) { + settingsController.GetChimeOption() == Controllers::Settings::ChimesOption::Hours && !alarmController.IsAlerting()) { GoToRunning(); displayApp.PushMessage(Pinetime::Applications::Display::Messages::Chime); } @@ -326,8 +325,7 @@ void SystemTask::Work() { case Messages::OnNewHalfHour: using Pinetime::Controllers::AlarmController; if (settingsController.GetNotificationStatus() != Controllers::Settings::Notification::Sleep && - settingsController.GetChimeOption() == Controllers::Settings::ChimesOption::HalfHours && - alarmController.State() != AlarmController::AlarmState::Alerting) { + settingsController.GetChimeOption() == Controllers::Settings::ChimesOption::HalfHours && !alarmController.IsAlerting()) { GoToRunning(); displayApp.PushMessage(Pinetime::Applications::Display::Messages::Chime); }