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>
This commit is contained in:
NeroBurner 2024-09-28 08:14:08 +02:00 committed by GitHub
parent 997e4cee8c
commit a0cd439efc
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
5 changed files with 135 additions and 48 deletions

View file

@ -19,11 +19,13 @@
#include "systemtask/SystemTask.h" #include "systemtask/SystemTask.h"
#include "task.h" #include "task.h"
#include <chrono> #include <chrono>
#include <libraries/log/nrf_log.h>
using namespace Pinetime::Controllers; using namespace Pinetime::Controllers;
using namespace std::chrono_literals; 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 { namespace {
@ -36,11 +38,28 @@ namespace {
void AlarmController::Init(System::SystemTask* systemTask) { void AlarmController::Init(System::SystemTask* systemTask) {
this->systemTask = systemTask; this->systemTask = systemTask;
alarmTimer = xTimerCreate("Alarm", 1, pdFALSE, this, SetOffAlarm); 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) { void AlarmController::SetAlarmTime(uint8_t alarmHr, uint8_t alarmMin) {
hours = alarmHr; if (alarm.hours == alarmHr && alarm.minutes == alarmMin) {
minutes = alarmMin; return;
}
alarm.hours = alarmHr;
alarm.minutes = alarmMin;
alarmChanged = true;
} }
void AlarmController::ScheduleAlarm() { void AlarmController::ScheduleAlarm() {
@ -53,18 +72,19 @@ void AlarmController::ScheduleAlarm() {
tm* tmAlarmTime = std::localtime(&ttAlarmTime); tm* tmAlarmTime = std::localtime(&ttAlarmTime);
// If the time being set has already passed today,the alarm should be set for tomorrow // 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; tmAlarmTime->tm_mday += 1;
// tm_wday doesn't update automatically // tm_wday doesn't update automatically
tmAlarmTime->tm_wday = (tmAlarmTime->tm_wday + 1) % 7; tmAlarmTime->tm_wday = (tmAlarmTime->tm_wday + 1) % 7;
} }
tmAlarmTime->tm_hour = hours; tmAlarmTime->tm_hour = alarm.hours;
tmAlarmTime->tm_min = minutes; tmAlarmTime->tm_min = alarm.minutes;
tmAlarmTime->tm_sec = 0; tmAlarmTime->tm_sec = 0;
// if alarm is in weekday-only mode, make sure it shifts to the next weekday // 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 if (tmAlarmTime->tm_wday == 0) { // Sunday, shift 1 day
tmAlarmTime->tm_mday += 1; tmAlarmTime->tm_mday += 1;
} else if (tmAlarmTime->tm_wday == 6) { // Saturday, shift 2 days } else if (tmAlarmTime->tm_wday == 6) { // Saturday, shift 2 days
@ -79,7 +99,10 @@ void AlarmController::ScheduleAlarm() {
xTimerChangePeriod(alarmTimer, secondsToAlarm * configTICK_RATE_HZ, 0); xTimerChangePeriod(alarmTimer, secondsToAlarm * configTICK_RATE_HZ, 0);
xTimerStart(alarmTimer, 0); xTimerStart(alarmTimer, 0);
state = AlarmState::Set; if (!alarm.isEnabled) {
alarm.isEnabled = true;
alarmChanged = true;
}
} }
uint32_t AlarmController::SecondsToAlarm() const { uint32_t AlarmController::SecondsToAlarm() const {
@ -88,20 +111,72 @@ uint32_t AlarmController::SecondsToAlarm() const {
void AlarmController::DisableAlarm() { void AlarmController::DisableAlarm() {
xTimerStop(alarmTimer, 0); xTimerStop(alarmTimer, 0);
state = AlarmState::Not_Set; isAlerting = false;
if (alarm.isEnabled) {
alarm.isEnabled = false;
alarmChanged = true;
}
} }
void AlarmController::SetOffAlarmNow() { void AlarmController::SetOffAlarmNow() {
state = AlarmState::Alerting; isAlerting = true;
systemTask->PushMessage(System::Messages::SetOffAlarm); systemTask->PushMessage(System::Messages::SetOffAlarm);
} }
void AlarmController::StopAlerting() { void AlarmController::StopAlerting() {
// Alarm state is off unless this is a recurring alarm isAlerting = false;
if (recurrence == RecurType::None) { // Disable alarm unless it is recurring
state = AlarmState::Not_Set; if (alarm.recurrence == RecurType::None) {
alarm.isEnabled = false;
alarmChanged = true;
} else { } else {
// set next instance // set next instance
ScheduleAlarm(); 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);
}

View file

@ -30,47 +30,65 @@ namespace Pinetime {
namespace Controllers { namespace Controllers {
class AlarmController { class AlarmController {
public: public:
AlarmController(Controllers::DateTime& dateTimeController); AlarmController(Controllers::DateTime& dateTimeController, Controllers::FS& fs);
void Init(System::SystemTask* systemTask); void Init(System::SystemTask* systemTask);
void SaveAlarm();
void SetAlarmTime(uint8_t alarmHr, uint8_t alarmMin); void SetAlarmTime(uint8_t alarmHr, uint8_t alarmMin);
void ScheduleAlarm(); void ScheduleAlarm();
void DisableAlarm(); void DisableAlarm();
void SetOffAlarmNow(); void SetOffAlarmNow();
uint32_t SecondsToAlarm() const; uint32_t SecondsToAlarm() const;
void StopAlerting(); void StopAlerting();
enum class AlarmState { Not_Set, Set, Alerting };
enum class RecurType { None, Daily, Weekdays }; enum class RecurType { None, Daily, Weekdays };
uint8_t Hours() const { uint8_t Hours() const {
return hours; return alarm.hours;
} }
uint8_t Minutes() const { uint8_t Minutes() const {
return minutes; return alarm.minutes;
} }
AlarmState State() const { bool IsAlerting() const {
return state; return isAlerting;
}
bool IsEnabled() const {
return alarm.isEnabled;
} }
RecurType Recurrence() const { RecurType Recurrence() const {
return recurrence; return alarm.recurrence;
} }
void SetRecurrence(RecurType recurType) { void SetRecurrence(RecurType recurrence);
recurrence = recurType;
}
private: 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::DateTime& dateTimeController;
Controllers::FS& fs;
System::SystemTask* systemTask = nullptr; System::SystemTask* systemTask = nullptr;
TimerHandle_t alarmTimer; TimerHandle_t alarmTimer;
uint8_t hours = 7; AlarmSettings alarm;
uint8_t minutes = 0;
std::chrono::time_point<std::chrono::system_clock, std::chrono::nanoseconds> alarmTime; 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;
}; };
} }
} }

View file

@ -117,7 +117,7 @@ Alarm::Alarm(Controllers::AlarmController& alarmController,
UpdateAlarmTime(); UpdateAlarmTime();
if (alarmController.State() == Controllers::AlarmController::AlarmState::Alerting) { if (alarmController.IsAlerting()) {
SetAlerting(); SetAlerting();
} else { } else {
SetSwitchState(LV_ANIM_OFF); SetSwitchState(LV_ANIM_OFF);
@ -125,14 +125,15 @@ Alarm::Alarm(Controllers::AlarmController& alarmController,
} }
Alarm::~Alarm() { Alarm::~Alarm() {
if (alarmController.State() == AlarmController::AlarmState::Alerting) { if (alarmController.IsAlerting()) {
StopAlerting(); StopAlerting();
} }
lv_obj_clean(lv_scr_act()); lv_obj_clean(lv_scr_act());
alarmController.SaveAlarm();
} }
void Alarm::DisableAlarm() { void Alarm::DisableAlarm() {
if (alarmController.State() == AlarmController::AlarmState::Set) { if (alarmController.IsEnabled()) {
alarmController.DisableAlarm(); alarmController.DisableAlarm();
lv_switch_off(enableSwitch, LV_ANIM_ON); lv_switch_off(enableSwitch, LV_ANIM_ON);
} }
@ -172,7 +173,7 @@ bool Alarm::OnButtonPushed() {
HideInfo(); HideInfo();
return true; return true;
} }
if (alarmController.State() == AlarmController::AlarmState::Alerting) { if (alarmController.IsAlerting()) {
StopAlerting(); StopAlerting();
return true; return true;
} }
@ -181,7 +182,7 @@ bool Alarm::OnButtonPushed() {
bool Alarm::OnTouchEvent(Pinetime::Applications::TouchEvents event) { bool Alarm::OnTouchEvent(Pinetime::Applications::TouchEvents event) {
// Don't allow closing the screen by swiping while the alarm is alerting // 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() { void Alarm::OnValueChanged() {
@ -222,15 +223,10 @@ void Alarm::StopAlerting() {
} }
void Alarm::SetSwitchState(lv_anim_enable_t anim) { void Alarm::SetSwitchState(lv_anim_enable_t anim) {
switch (alarmController.State()) { if (alarmController.IsEnabled()) {
case AlarmController::AlarmState::Set: lv_switch_on(enableSwitch, anim);
lv_switch_on(enableSwitch, anim); } else {
break; lv_switch_off(enableSwitch, anim);
case AlarmController::AlarmState::Not_Set:
lv_switch_off(enableSwitch, anim);
break;
default:
break;
} }
} }
@ -247,7 +243,7 @@ void Alarm::ShowInfo() {
txtMessage = lv_label_create(btnMessage, nullptr); txtMessage = lv_label_create(btnMessage, nullptr);
lv_obj_set_style_local_bg_color(btnMessage, LV_BTN_PART_MAIN, LV_STATE_DEFAULT, LV_COLOR_NAVY); 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 timeToAlarm = alarmController.SecondsToAlarm();
auto daysToAlarm = timeToAlarm / 86400; auto daysToAlarm = timeToAlarm / 86400;

View file

@ -104,7 +104,7 @@ Pinetime::Controllers::DateTime dateTimeController {settingsController};
Pinetime::Drivers::Watchdog watchdog; Pinetime::Drivers::Watchdog watchdog;
Pinetime::Controllers::NotificationManager notificationManager; Pinetime::Controllers::NotificationManager notificationManager;
Pinetime::Controllers::MotionController motionController; Pinetime::Controllers::MotionController motionController;
Pinetime::Controllers::AlarmController alarmController {dateTimeController}; Pinetime::Controllers::AlarmController alarmController {dateTimeController, fs};
Pinetime::Controllers::TouchHandler touchHandler; Pinetime::Controllers::TouchHandler touchHandler;
Pinetime::Controllers::ButtonHandler buttonHandler; Pinetime::Controllers::ButtonHandler buttonHandler;
Pinetime::Controllers::BrightnessController brightnessController {}; Pinetime::Controllers::BrightnessController brightnessController {};

View file

@ -216,7 +216,7 @@ void SystemTask::Work() {
GoToSleep(); GoToSleep();
break; break;
case Messages::OnNewTime: case Messages::OnNewTime:
if (alarmController.State() == Controllers::AlarmController::AlarmState::Set) { if (alarmController.IsEnabled()) {
alarmController.ScheduleAlarm(); alarmController.ScheduleAlarm();
} }
break; break;
@ -317,8 +317,7 @@ void SystemTask::Work() {
case Messages::OnNewHour: case Messages::OnNewHour:
using Pinetime::Controllers::AlarmController; using Pinetime::Controllers::AlarmController;
if (settingsController.GetNotificationStatus() != Controllers::Settings::Notification::Sleep && if (settingsController.GetNotificationStatus() != Controllers::Settings::Notification::Sleep &&
settingsController.GetChimeOption() == Controllers::Settings::ChimesOption::Hours && settingsController.GetChimeOption() == Controllers::Settings::ChimesOption::Hours && !alarmController.IsAlerting()) {
alarmController.State() != AlarmController::AlarmState::Alerting) {
GoToRunning(); GoToRunning();
displayApp.PushMessage(Pinetime::Applications::Display::Messages::Chime); displayApp.PushMessage(Pinetime::Applications::Display::Messages::Chime);
} }
@ -326,8 +325,7 @@ void SystemTask::Work() {
case Messages::OnNewHalfHour: case Messages::OnNewHalfHour:
using Pinetime::Controllers::AlarmController; using Pinetime::Controllers::AlarmController;
if (settingsController.GetNotificationStatus() != Controllers::Settings::Notification::Sleep && if (settingsController.GetNotificationStatus() != Controllers::Settings::Notification::Sleep &&
settingsController.GetChimeOption() == Controllers::Settings::ChimesOption::HalfHours && settingsController.GetChimeOption() == Controllers::Settings::ChimesOption::HalfHours && !alarmController.IsAlerting()) {
alarmController.State() != AlarmController::AlarmState::Alerting) {
GoToRunning(); GoToRunning();
displayApp.PushMessage(Pinetime::Applications::Display::Messages::Chime); displayApp.PushMessage(Pinetime::Applications::Display::Messages::Chime);
} }