Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
191 changes: 148 additions & 43 deletions src/components/alarm/AlarmController.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,14 @@ using namespace std::chrono_literals;

AlarmController::AlarmController(Controllers::DateTime& dateTimeController, Controllers::FS& fs)
: dateTimeController {dateTimeController}, fs {fs} {
// Initialize all alarms with defaults
for (uint8_t i = 0; i < MaxAlarms; i++) {
alarms[i].version = alarmFormatVersion;
alarms[i].hours = 6 + i;
alarms[i].minutes = 0;
alarms[i].recurrence = RecurType::Weekdays;
alarms[i].isEnabled = false;
}
}

namespace {
Expand All @@ -39,9 +47,13 @@ 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();
// Check if any alarms are enabled and schedule the next one
for (uint8_t i = 0; i < MaxAlarms; i++) {
if (alarms[i].isEnabled) {
NRF_LOG_INFO("[AlarmController] Found enabled alarm %u, scheduling next alarm", i);
ScheduleAlarm();
break;
}
}
}

Expand All @@ -53,38 +65,47 @@ void AlarmController::SaveAlarm() {
alarmChanged = false;
}

void AlarmController::SetAlarmTime(uint8_t alarmHr, uint8_t alarmMin) {
if (alarm.hours == alarmHr && alarm.minutes == alarmMin) {
void AlarmController::SetAlarmTime(uint8_t index, uint8_t alarmHr, uint8_t alarmMin) {
if (index >= MaxAlarms) {
return;
}
alarm.hours = alarmHr;
alarm.minutes = alarmMin;
if (alarms[index].hours == alarmHr && alarms[index].minutes == alarmMin) {
return;
}
alarms[index].hours = alarmHr;
alarms[index].minutes = alarmMin;
alarmChanged = true;
}

void AlarmController::ScheduleAlarm() {
// Determine the next time the alarm needs to go off and set the timer
// Determine the next alarm to schedule and set the timer
xTimerStop(alarmTimer, 0);

nextAlarmIndex = CalculateNextAlarm();
if (nextAlarmIndex >= MaxAlarms) {
// No enabled alarms found
return;
}

auto now = dateTimeController.CurrentDateTime();
alarmTime = now;
time_t ttAlarmTime = std::chrono::system_clock::to_time_t(std::chrono::time_point_cast<std::chrono::system_clock::duration>(alarmTime));
tm* tmAlarmTime = std::localtime(&ttAlarmTime);

// If the time being set has already passed today,the alarm should be set for tomorrow
if (alarm.hours < dateTimeController.Hours() ||
(alarm.hours == dateTimeController.Hours() && alarm.minutes <= dateTimeController.Minutes())) {
if (alarms[nextAlarmIndex].hours < dateTimeController.Hours() ||
(alarms[nextAlarmIndex].hours == dateTimeController.Hours() && alarms[nextAlarmIndex].minutes <= dateTimeController.Minutes())) {
tmAlarmTime->tm_mday += 1;
// tm_wday doesn't update automatically
tmAlarmTime->tm_wday = (tmAlarmTime->tm_wday + 1) % 7;
}

tmAlarmTime->tm_hour = alarm.hours;
tmAlarmTime->tm_min = alarm.minutes;
tmAlarmTime->tm_hour = alarms[nextAlarmIndex].hours;
tmAlarmTime->tm_min = alarms[nextAlarmIndex].minutes;
tmAlarmTime->tm_sec = 0;

// if alarm is in weekday-only mode, make sure it shifts to the next weekday
if (alarm.recurrence == RecurType::Weekdays) {
if (alarms[nextAlarmIndex].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
Expand All @@ -98,69 +119,104 @@ void AlarmController::ScheduleAlarm() {
auto secondsToAlarm = std::chrono::duration_cast<std::chrono::seconds>(alarmTime - now).count();
xTimerChangePeriod(alarmTimer, secondsToAlarm * configTICK_RATE_HZ, 0);
xTimerStart(alarmTimer, 0);

if (!alarm.isEnabled) {
alarm.isEnabled = true;
alarmChanged = true;
}
}

uint32_t AlarmController::SecondsToAlarm() const {
return std::chrono::duration_cast<std::chrono::seconds>(alarmTime - dateTimeController.CurrentDateTime()).count();
}

void AlarmController::DisableAlarm() {
xTimerStop(alarmTimer, 0);
if (alarm.isEnabled) {
alarm.isEnabled = false;
void AlarmController::DisableAlarm(uint8_t index) {
if (index >= MaxAlarms) {
return;
}
if (alarms[index].isEnabled) {
alarms[index].isEnabled = false;
alarmChanged = true;
ScheduleAlarm();
}
}

void AlarmController::SetEnabled(uint8_t index, bool enabled) {
if (index >= MaxAlarms) {
return;
}
if (alarms[index].isEnabled != enabled) {
alarms[index].isEnabled = enabled;
alarmChanged = true;
ScheduleAlarm();
}
}

void AlarmController::SetOffAlarmNow() {
isAlerting = true;
alertingAlarmIndex = nextAlarmIndex;
systemTask->PushMessage(System::Messages::SetOffAlarm);
}

void AlarmController::StopAlerting() {
isAlerting = false;
// Disable alarm unless it is recurring
if (alarm.recurrence == RecurType::None) {
alarm.isEnabled = false;
if (alarms[alertingAlarmIndex].recurrence == RecurType::None) {
alarms[alertingAlarmIndex].isEnabled = false;
alarmChanged = true;
} else {
// set next instance
ScheduleAlarm();
}
ScheduleAlarm();
}

void AlarmController::SetRecurrence(RecurType recurrence) {
if (alarm.recurrence != recurrence) {
alarm.recurrence = recurrence;
void AlarmController::SetRecurrence(uint8_t index, RecurType recurrence) {
if (index >= MaxAlarms) {
return;
}
if (alarms[index].recurrence != recurrence) {
alarms[index].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;
}
// Read version byte to determine format
uint8_t version;
fs.FileRead(&alarmFile, &version, sizeof(version));
fs.FileSeek(&alarmFile, 0);

if (version == 1) {
// Migrate from old single-alarm format
NRF_LOG_INFO("[AlarmController] Migrating from version 1 to version 2");
AlarmSettings oldAlarm;
fs.FileRead(&alarmFile, reinterpret_cast<uint8_t*>(&oldAlarm), sizeof(oldAlarm));
fs.FileClose(&alarmFile);

// Copy old alarm to first slot
alarms[0] = oldAlarm;
alarms[0].version = alarmFormatVersion;

alarm = alarmBuffer;
NRF_LOG_INFO("[AlarmController] Loaded alarm settings from file");
// Initialize other alarms with defaults
for (uint8_t i = 1; i < MaxAlarms; i++) {
alarms[i].version = alarmFormatVersion;
alarms[i].hours = 6 + i;
alarms[i].minutes = 0;
alarms[i].recurrence = RecurType::Weekdays;
alarms[i].isEnabled = false;
}

alarmChanged = true;
NRF_LOG_INFO("[AlarmController] Migrated alarm settings from version 1");
} else if (version == alarmFormatVersion) {
// Read new multi-alarm format
fs.FileRead(&alarmFile, reinterpret_cast<uint8_t*>(alarms.data()), sizeof(alarms));
fs.FileClose(&alarmFile);
NRF_LOG_INFO("[AlarmController] Loaded %u alarms from file", MaxAlarms);
} else {
NRF_LOG_WARNING("[AlarmController] Unknown alarm version %u, using defaults", version);
fs.FileClose(&alarmFile);
}
}

void AlarmController::SaveSettingsToFile() const {
Expand All @@ -175,7 +231,56 @@ void AlarmController::SaveSettingsToFile() const {
return;
}

fs.FileWrite(&alarmFile, reinterpret_cast<const uint8_t*>(&alarm), sizeof(alarm));
fs.FileWrite(&alarmFile, reinterpret_cast<const uint8_t*>(alarms.data()), sizeof(alarms));
fs.FileClose(&alarmFile);
NRF_LOG_INFO("[AlarmController] Saved alarm settings with format version %u to file", alarm.version);
NRF_LOG_INFO("[AlarmController] Saved %u alarms with format version %u to file", MaxAlarms, alarmFormatVersion);
}

uint8_t AlarmController::CalculateNextAlarm() const {
auto now = dateTimeController.CurrentDateTime();
uint32_t minSecondsToAlarm = UINT32_MAX;
uint8_t nextIndex = MaxAlarms; // Invalid index means no alarm found

for (uint8_t i = 0; i < MaxAlarms; i++) {
if (!alarms[i].isEnabled) {
continue;
}

// Calculate seconds to this alarm
auto alarmTimePoint = now;
time_t ttAlarmTime =
std::chrono::system_clock::to_time_t(std::chrono::time_point_cast<std::chrono::system_clock::duration>(alarmTimePoint));
tm* tmAlarmTime = std::localtime(&ttAlarmTime);

// If the time has already passed today, schedule for tomorrow
if (alarms[i].hours < dateTimeController.Hours() ||
(alarms[i].hours == dateTimeController.Hours() && alarms[i].minutes <= dateTimeController.Minutes())) {
tmAlarmTime->tm_mday += 1;
tmAlarmTime->tm_wday = (tmAlarmTime->tm_wday + 1) % 7;
}

tmAlarmTime->tm_hour = alarms[i].hours;
tmAlarmTime->tm_min = alarms[i].minutes;
tmAlarmTime->tm_sec = 0;

// Handle weekday-only mode
if (alarms[i].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
tmAlarmTime->tm_mday += 2;
}
}
tmAlarmTime->tm_isdst = -1;

auto thisAlarmTime = std::chrono::system_clock::from_time_t(std::mktime(tmAlarmTime));
auto secondsToThisAlarm = std::chrono::duration_cast<std::chrono::seconds>(thisAlarmTime - now).count();

if (secondsToThisAlarm > 0 && static_cast<uint32_t>(secondsToThisAlarm) < minSecondsToAlarm) {
minSecondsToAlarm = static_cast<uint32_t>(secondsToThisAlarm);
nextIndex = i;
}
}

return nextIndex;
}
59 changes: 46 additions & 13 deletions src/components/alarm/AlarmController.h
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@

#include <FreeRTOS.h>
#include <timers.h>
#include <array>
#include <cstdint>
#include "components/datetime/DateTimeController.h"

Expand All @@ -30,44 +31,73 @@ namespace Pinetime {
namespace Controllers {
class AlarmController {
public:
static constexpr uint8_t MaxAlarms = 4;

AlarmController(Controllers::DateTime& dateTimeController, Controllers::FS& fs);

void Init(System::SystemTask* systemTask);
void SaveAlarm();
void SetAlarmTime(uint8_t alarmHr, uint8_t alarmMin);
void SetAlarmTime(uint8_t index, uint8_t alarmHr, uint8_t alarmMin);
void ScheduleAlarm();
void DisableAlarm();
void DisableAlarm(uint8_t index);
void SetOffAlarmNow();
uint32_t SecondsToAlarm() const;
void StopAlerting();
enum class RecurType { None, Daily, Weekdays };

uint8_t Hours() const {
return alarm.hours;
uint8_t Hours(uint8_t index) const {
if (index >= MaxAlarms) {
return 0;
}
return alarms[index].hours;
}

uint8_t Minutes() const {
return alarm.minutes;
uint8_t Minutes(uint8_t index) const {
if (index >= MaxAlarms) {
return 0;
}
return alarms[index].minutes;
}

bool IsAlerting() const {
return isAlerting;
}

bool IsEnabled() const {
return alarm.isEnabled;
uint8_t AlertingAlarmIndex() const {
return alertingAlarmIndex;
}

bool IsEnabled(uint8_t index) const {
if (index >= MaxAlarms) {
return false;
}
return alarms[index].isEnabled;
}

RecurType Recurrence() const {
return alarm.recurrence;
bool AnyAlarmEnabled() const {
for (uint8_t i = 0; i < MaxAlarms; i++) {
if (alarms[i].isEnabled) {
return true;
}
}
return false;
}

void SetEnabled(uint8_t index, bool enabled);

RecurType Recurrence(uint8_t index) const {
if (index >= MaxAlarms) {
return RecurType::None;
}
return alarms[index].recurrence;
}

void SetRecurrence(RecurType recurrence);
void SetRecurrence(uint8_t index, 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;
static constexpr uint8_t alarmFormatVersion = 2;

struct AlarmSettings {
uint8_t version = alarmFormatVersion;
Expand All @@ -79,16 +109,19 @@ namespace Pinetime {

bool isAlerting = false;
bool alarmChanged = false;
uint8_t alertingAlarmIndex = 0;
uint8_t nextAlarmIndex = 0;

Controllers::DateTime& dateTimeController;
Controllers::FS& fs;
System::SystemTask* systemTask = nullptr;
TimerHandle_t alarmTimer;
AlarmSettings alarm;
std::array<AlarmSettings, MaxAlarms> alarms;
std::chrono::time_point<std::chrono::system_clock, std::chrono::nanoseconds> alarmTime;

void LoadSettingsFromFile();
void SaveSettingsToFile() const;
uint8_t CalculateNextAlarm() const;
};
}
}
Loading