InfiniTime.git

commit a0cd439efc9b0d0d9610dea7ff749f102d5a316d

Author: NeroBurner <pyro4hell@gmail.com>

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 <g@ldor.de>

 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 -


diff --git a/src/components/alarm/AlarmController.cpp b/src/components/alarm/AlarmController.cpp
index c4eb8ed0d580561f1a762425a5e65903ae3f1535..7dbfb0e3079964a50c32495fed39b810e772b830 100644
--- a/src/components/alarm/AlarmController.cpp
+++ b/src/components/alarm/AlarmController.cpp
@@ -19,11 +19,13 @@ #include "components/alarm/AlarmController.h"
 #include "systemtask/SystemTask.h"
 #include "task.h"
 #include <chrono>
+#include <libraries/log/nrf_log.h>
 
 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 @@
 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 @@   time_t ttAlarmTime = std::chrono::system_clock::to_time_t(std::chrono::time_point_cast(alarmTime));
   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 @@   auto secondsToAlarm = std::chrono::duration_cast(alarmTime - now).count();
   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 @@ }
 
 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<uint8_t*>(&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<const uint8_t*>(&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 8ac0de9af1776aee242ddb0257c50adb86ef15de..278e9cdb6a89428097b7cb25902a55d69b789a98 100644
--- a/src/components/alarm/AlarmController.h
+++ b/src/components/alarm/AlarmController.h
@@ -30,47 +30,65 @@
   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;
+      }
+
+      bool IsAlerting() const {
+        return isAlerting;
       }
 
-      AlarmState State() const {
-        return state;
+      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<std::chrono::system_clock, std::chrono::nanoseconds> 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 292fb075afe73d5fe0d060e87132bdc580f5e6ac..b1e673639a26e57bfee43efe97d3aad9c86ec96b 100644
--- a/src/displayapp/screens/Alarm.cpp
+++ b/src/displayapp/screens/Alarm.cpp
@@ -117,7 +117,7 @@   lv_obj_set_style_local_bg_color(enableSwitch, LV_SWITCH_PART_BG, LV_STATE_DEFAULT, bgColor);
 
   UpdateAlarmTime();
 
-  if (alarmController.State() == Controllers::AlarmController::AlarmState::Alerting) {
+  if (alarmController.IsAlerting()) {
     SetAlerting();
   } else {
     SetSwitchState(LV_ANIM_OFF);
@@ -125,14 +125,15 @@   }
 }
 
 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 @@   if (txtMessage != nullptr && btnMessage != nullptr) {
     HideInfo();
     return true;
   }
-  if (alarmController.State() == AlarmController::AlarmState::Alerting) {
+  if (alarmController.IsAlerting()) {
     StopAlerting();
     return true;
   }
@@ -181,7 +182,7 @@ }
 
 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 @@   lv_obj_set_hidden(btnStop, true);
 }
 
 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 @@   lv_obj_align(btnMessage, lv_scr_act(), LV_ALIGN_CENTER, 0, 0);
   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 ab50fa74a2ccd387cbcf6ab28ee51666f2ce3cf8..84f30eeffcbb8290eb5f6ad7b8cad8df2ee11ed3 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 e55c9ad8e5d98b1f243c4372af124c55b69af17c..fc4e8f7ec608dac60fdeda33c80f3708cc0d904c 100644
--- a/src/systemtask/SystemTask.cpp
+++ b/src/systemtask/SystemTask.cpp
@@ -216,7 +216,7 @@         case Messages::GoToSleep:
           GoToSleep();
           break;
         case Messages::OnNewTime:
-          if (alarmController.State() == Controllers::AlarmController::AlarmState::Set) {
+          if (alarmController.IsEnabled()) {
             alarmController.ScheduleAlarm();
           }
           break;
@@ -317,8 +317,7 @@           break;
         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 @@           break;
         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);
           }