Skip to content

Commit ee68c49

Browse files
committed
alarm: Add support for multiple alarms
Replace single alarm with launcher screen showing 4 different alarms. Each alarm displays time, recurrence, and enable/disable toggle in a vertical list. Tapping alarm time opens configuration screen for that specific alarm.
1 parent 7128fc0 commit ee68c49

File tree

7 files changed

+367
-109
lines changed

7 files changed

+367
-109
lines changed

src/components/alarm/AlarmController.cpp

Lines changed: 148 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,14 @@ using namespace std::chrono_literals;
2626

2727
AlarmController::AlarmController(Controllers::DateTime& dateTimeController, Controllers::FS& fs)
2828
: dateTimeController {dateTimeController}, fs {fs} {
29+
// Initialize all alarms with defaults
30+
for (uint8_t i = 0; i < MaxAlarms; i++) {
31+
alarms[i].version = alarmFormatVersion;
32+
alarms[i].hours = 6 + i;
33+
alarms[i].minutes = 0;
34+
alarms[i].recurrence = RecurType::Weekdays;
35+
alarms[i].isEnabled = false;
36+
}
2937
}
3038

3139
namespace {
@@ -39,9 +47,13 @@ void AlarmController::Init(System::SystemTask* systemTask) {
3947
this->systemTask = systemTask;
4048
alarmTimer = xTimerCreate("Alarm", 1, pdFALSE, this, SetOffAlarm);
4149
LoadSettingsFromFile();
42-
if (alarm.isEnabled) {
43-
NRF_LOG_INFO("[AlarmController] Loaded alarm was enabled, scheduling");
44-
ScheduleAlarm();
50+
// Check if any alarms are enabled and schedule the next one
51+
for (uint8_t i = 0; i < MaxAlarms; i++) {
52+
if (alarms[i].isEnabled) {
53+
NRF_LOG_INFO("[AlarmController] Found enabled alarm %u, scheduling next alarm", i);
54+
ScheduleAlarm();
55+
break;
56+
}
4557
}
4658
}
4759

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

56-
void AlarmController::SetAlarmTime(uint8_t alarmHr, uint8_t alarmMin) {
57-
if (alarm.hours == alarmHr && alarm.minutes == alarmMin) {
68+
void AlarmController::SetAlarmTime(uint8_t index, uint8_t alarmHr, uint8_t alarmMin) {
69+
if (index >= MaxAlarms) {
5870
return;
5971
}
60-
alarm.hours = alarmHr;
61-
alarm.minutes = alarmMin;
72+
if (alarms[index].hours == alarmHr && alarms[index].minutes == alarmMin) {
73+
return;
74+
}
75+
alarms[index].hours = alarmHr;
76+
alarms[index].minutes = alarmMin;
6277
alarmChanged = true;
6378
}
6479

6580
void AlarmController::ScheduleAlarm() {
66-
// Determine the next time the alarm needs to go off and set the timer
81+
// Determine the next alarm to schedule and set the timer
6782
xTimerStop(alarmTimer, 0);
6883

84+
nextAlarmIndex = CalculateNextAlarm();
85+
if (nextAlarmIndex >= MaxAlarms) {
86+
// No enabled alarms found
87+
return;
88+
}
89+
6990
auto now = dateTimeController.CurrentDateTime();
7091
alarmTime = now;
7192
time_t ttAlarmTime = std::chrono::system_clock::to_time_t(std::chrono::time_point_cast<std::chrono::system_clock::duration>(alarmTime));
7293
tm* tmAlarmTime = std::localtime(&ttAlarmTime);
7394

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

82-
tmAlarmTime->tm_hour = alarm.hours;
83-
tmAlarmTime->tm_min = alarm.minutes;
103+
tmAlarmTime->tm_hour = alarms[nextAlarmIndex].hours;
104+
tmAlarmTime->tm_min = alarms[nextAlarmIndex].minutes;
84105
tmAlarmTime->tm_sec = 0;
85106

86107
// if alarm is in weekday-only mode, make sure it shifts to the next weekday
87-
if (alarm.recurrence == RecurType::Weekdays) {
108+
if (alarms[nextAlarmIndex].recurrence == RecurType::Weekdays) {
88109
if (tmAlarmTime->tm_wday == 0) { // Sunday, shift 1 day
89110
tmAlarmTime->tm_mday += 1;
90111
} else if (tmAlarmTime->tm_wday == 6) { // Saturday, shift 2 days
@@ -98,69 +119,104 @@ void AlarmController::ScheduleAlarm() {
98119
auto secondsToAlarm = std::chrono::duration_cast<std::chrono::seconds>(alarmTime - now).count();
99120
xTimerChangePeriod(alarmTimer, secondsToAlarm * configTICK_RATE_HZ, 0);
100121
xTimerStart(alarmTimer, 0);
101-
102-
if (!alarm.isEnabled) {
103-
alarm.isEnabled = true;
104-
alarmChanged = true;
105-
}
106122
}
107123

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

112-
void AlarmController::DisableAlarm() {
113-
xTimerStop(alarmTimer, 0);
114-
if (alarm.isEnabled) {
115-
alarm.isEnabled = false;
128+
void AlarmController::DisableAlarm(uint8_t index) {
129+
if (index >= MaxAlarms) {
130+
return;
131+
}
132+
if (alarms[index].isEnabled) {
133+
alarms[index].isEnabled = false;
116134
alarmChanged = true;
135+
ScheduleAlarm();
136+
}
137+
}
138+
139+
void AlarmController::SetEnabled(uint8_t index, bool enabled) {
140+
if (index >= MaxAlarms) {
141+
return;
142+
}
143+
if (alarms[index].isEnabled != enabled) {
144+
alarms[index].isEnabled = enabled;
145+
alarmChanged = true;
146+
ScheduleAlarm();
117147
}
118148
}
119149

120150
void AlarmController::SetOffAlarmNow() {
121151
isAlerting = true;
152+
alertingAlarmIndex = nextAlarmIndex;
122153
systemTask->PushMessage(System::Messages::SetOffAlarm);
123154
}
124155

125156
void AlarmController::StopAlerting() {
126157
isAlerting = false;
127158
// Disable alarm unless it is recurring
128-
if (alarm.recurrence == RecurType::None) {
129-
alarm.isEnabled = false;
159+
if (alarms[alertingAlarmIndex].recurrence == RecurType::None) {
160+
alarms[alertingAlarmIndex].isEnabled = false;
130161
alarmChanged = true;
131-
} else {
132-
// set next instance
133-
ScheduleAlarm();
134162
}
163+
ScheduleAlarm();
135164
}
136165

137-
void AlarmController::SetRecurrence(RecurType recurrence) {
138-
if (alarm.recurrence != recurrence) {
139-
alarm.recurrence = recurrence;
166+
void AlarmController::SetRecurrence(uint8_t index, RecurType recurrence) {
167+
if (index >= MaxAlarms) {
168+
return;
169+
}
170+
if (alarms[index].recurrence != recurrence) {
171+
alarms[index].recurrence = recurrence;
140172
alarmChanged = true;
141173
}
142174
}
143175

144176
void AlarmController::LoadSettingsFromFile() {
145177
lfs_file_t alarmFile;
146-
AlarmSettings alarmBuffer;
147178

148179
if (fs.FileOpen(&alarmFile, "/.system/alarm.dat", LFS_O_RDONLY) != LFS_ERR_OK) {
149180
NRF_LOG_WARNING("[AlarmController] Failed to open alarm data file");
150181
return;
151182
}
152183

153-
fs.FileRead(&alarmFile, reinterpret_cast<uint8_t*>(&alarmBuffer), sizeof(alarmBuffer));
154-
fs.FileClose(&alarmFile);
155-
if (alarmBuffer.version != alarmFormatVersion) {
156-
NRF_LOG_WARNING("[AlarmController] Loaded alarm settings has version %u instead of %u, discarding",
157-
alarmBuffer.version,
158-
alarmFormatVersion);
159-
return;
160-
}
184+
// Read version byte to determine format
185+
uint8_t version;
186+
fs.FileRead(&alarmFile, &version, sizeof(version));
187+
fs.FileSeek(&alarmFile, 0);
188+
189+
if (version == 1) {
190+
// Migrate from old single-alarm format
191+
NRF_LOG_INFO("[AlarmController] Migrating from version 1 to version 2");
192+
AlarmSettings oldAlarm;
193+
fs.FileRead(&alarmFile, reinterpret_cast<uint8_t*>(&oldAlarm), sizeof(oldAlarm));
194+
fs.FileClose(&alarmFile);
195+
196+
// Copy old alarm to first slot
197+
alarms[0] = oldAlarm;
198+
alarms[0].version = alarmFormatVersion;
161199

162-
alarm = alarmBuffer;
163-
NRF_LOG_INFO("[AlarmController] Loaded alarm settings from file");
200+
// Initialize other alarms with defaults
201+
for (uint8_t i = 1; i < MaxAlarms; i++) {
202+
alarms[i].version = alarmFormatVersion;
203+
alarms[i].hours = 6 + i;
204+
alarms[i].minutes = 0;
205+
alarms[i].recurrence = RecurType::Weekdays;
206+
alarms[i].isEnabled = false;
207+
}
208+
209+
alarmChanged = true;
210+
NRF_LOG_INFO("[AlarmController] Migrated alarm settings from version 1");
211+
} else if (version == alarmFormatVersion) {
212+
// Read new multi-alarm format
213+
fs.FileRead(&alarmFile, reinterpret_cast<uint8_t*>(alarms.data()), sizeof(alarms));
214+
fs.FileClose(&alarmFile);
215+
NRF_LOG_INFO("[AlarmController] Loaded %u alarms from file", MaxAlarms);
216+
} else {
217+
NRF_LOG_WARNING("[AlarmController] Unknown alarm version %u, using defaults", version);
218+
fs.FileClose(&alarmFile);
219+
}
164220
}
165221

166222
void AlarmController::SaveSettingsToFile() const {
@@ -175,7 +231,56 @@ void AlarmController::SaveSettingsToFile() const {
175231
return;
176232
}
177233

178-
fs.FileWrite(&alarmFile, reinterpret_cast<const uint8_t*>(&alarm), sizeof(alarm));
234+
fs.FileWrite(&alarmFile, reinterpret_cast<const uint8_t*>(alarms.data()), sizeof(alarms));
179235
fs.FileClose(&alarmFile);
180-
NRF_LOG_INFO("[AlarmController] Saved alarm settings with format version %u to file", alarm.version);
236+
NRF_LOG_INFO("[AlarmController] Saved %u alarms with format version %u to file", MaxAlarms, alarmFormatVersion);
237+
}
238+
239+
uint8_t AlarmController::CalculateNextAlarm() const {
240+
auto now = dateTimeController.CurrentDateTime();
241+
uint32_t minSecondsToAlarm = UINT32_MAX;
242+
uint8_t nextIndex = MaxAlarms; // Invalid index means no alarm found
243+
244+
for (uint8_t i = 0; i < MaxAlarms; i++) {
245+
if (!alarms[i].isEnabled) {
246+
continue;
247+
}
248+
249+
// Calculate seconds to this alarm
250+
auto alarmTimePoint = now;
251+
time_t ttAlarmTime =
252+
std::chrono::system_clock::to_time_t(std::chrono::time_point_cast<std::chrono::system_clock::duration>(alarmTimePoint));
253+
tm* tmAlarmTime = std::localtime(&ttAlarmTime);
254+
255+
// If the time has already passed today, schedule for tomorrow
256+
if (alarms[i].hours < dateTimeController.Hours() ||
257+
(alarms[i].hours == dateTimeController.Hours() && alarms[i].minutes <= dateTimeController.Minutes())) {
258+
tmAlarmTime->tm_mday += 1;
259+
tmAlarmTime->tm_wday = (tmAlarmTime->tm_wday + 1) % 7;
260+
}
261+
262+
tmAlarmTime->tm_hour = alarms[i].hours;
263+
tmAlarmTime->tm_min = alarms[i].minutes;
264+
tmAlarmTime->tm_sec = 0;
265+
266+
// Handle weekday-only mode
267+
if (alarms[i].recurrence == RecurType::Weekdays) {
268+
if (tmAlarmTime->tm_wday == 0) { // Sunday, shift 1 day
269+
tmAlarmTime->tm_mday += 1;
270+
} else if (tmAlarmTime->tm_wday == 6) { // Saturday, shift 2 days
271+
tmAlarmTime->tm_mday += 2;
272+
}
273+
}
274+
tmAlarmTime->tm_isdst = -1;
275+
276+
auto thisAlarmTime = std::chrono::system_clock::from_time_t(std::mktime(tmAlarmTime));
277+
auto secondsToThisAlarm = std::chrono::duration_cast<std::chrono::seconds>(thisAlarmTime - now).count();
278+
279+
if (secondsToThisAlarm > 0 && static_cast<uint32_t>(secondsToThisAlarm) < minSecondsToAlarm) {
280+
minSecondsToAlarm = static_cast<uint32_t>(secondsToThisAlarm);
281+
nextIndex = i;
282+
}
283+
}
284+
285+
return nextIndex;
181286
}

src/components/alarm/AlarmController.h

Lines changed: 46 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919

2020
#include <FreeRTOS.h>
2121
#include <timers.h>
22+
#include <array>
2223
#include <cstdint>
2324
#include "components/datetime/DateTimeController.h"
2425

@@ -30,44 +31,73 @@ namespace Pinetime {
3031
namespace Controllers {
3132
class AlarmController {
3233
public:
34+
static constexpr uint8_t MaxAlarms = 4;
35+
3336
AlarmController(Controllers::DateTime& dateTimeController, Controllers::FS& fs);
3437

3538
void Init(System::SystemTask* systemTask);
3639
void SaveAlarm();
37-
void SetAlarmTime(uint8_t alarmHr, uint8_t alarmMin);
40+
void SetAlarmTime(uint8_t index, uint8_t alarmHr, uint8_t alarmMin);
3841
void ScheduleAlarm();
39-
void DisableAlarm();
42+
void DisableAlarm(uint8_t index);
4043
void SetOffAlarmNow();
4144
uint32_t SecondsToAlarm() const;
4245
void StopAlerting();
4346
enum class RecurType { None, Daily, Weekdays };
4447

45-
uint8_t Hours() const {
46-
return alarm.hours;
48+
uint8_t Hours(uint8_t index) const {
49+
if (index >= MaxAlarms) {
50+
return 0;
51+
}
52+
return alarms[index].hours;
4753
}
4854

49-
uint8_t Minutes() const {
50-
return alarm.minutes;
55+
uint8_t Minutes(uint8_t index) const {
56+
if (index >= MaxAlarms) {
57+
return 0;
58+
}
59+
return alarms[index].minutes;
5160
}
5261

5362
bool IsAlerting() const {
5463
return isAlerting;
5564
}
5665

57-
bool IsEnabled() const {
58-
return alarm.isEnabled;
66+
uint8_t AlertingAlarmIndex() const {
67+
return alertingAlarmIndex;
68+
}
69+
70+
bool IsEnabled(uint8_t index) const {
71+
if (index >= MaxAlarms) {
72+
return false;
73+
}
74+
return alarms[index].isEnabled;
5975
}
6076

61-
RecurType Recurrence() const {
62-
return alarm.recurrence;
77+
bool AnyAlarmEnabled() const {
78+
for (uint8_t i = 0; i < MaxAlarms; i++) {
79+
if (alarms[i].isEnabled) {
80+
return true;
81+
}
82+
}
83+
return false;
84+
}
85+
86+
void SetEnabled(uint8_t index, bool enabled);
87+
88+
RecurType Recurrence(uint8_t index) const {
89+
if (index >= MaxAlarms) {
90+
return RecurType::None;
91+
}
92+
return alarms[index].recurrence;
6393
}
6494

65-
void SetRecurrence(RecurType recurrence);
95+
void SetRecurrence(uint8_t index, RecurType recurrence);
6696

6797
private:
6898
// Versions 255 is reserved for now, so the version field can be made
6999
// bigger, should it ever be needed.
70-
static constexpr uint8_t alarmFormatVersion = 1;
100+
static constexpr uint8_t alarmFormatVersion = 2;
71101

72102
struct AlarmSettings {
73103
uint8_t version = alarmFormatVersion;
@@ -79,16 +109,19 @@ namespace Pinetime {
79109

80110
bool isAlerting = false;
81111
bool alarmChanged = false;
112+
uint8_t alertingAlarmIndex = 0;
113+
uint8_t nextAlarmIndex = 0;
82114

83115
Controllers::DateTime& dateTimeController;
84116
Controllers::FS& fs;
85117
System::SystemTask* systemTask = nullptr;
86118
TimerHandle_t alarmTimer;
87-
AlarmSettings alarm;
119+
std::array<AlarmSettings, MaxAlarms> alarms;
88120
std::chrono::time_point<std::chrono::system_clock, std::chrono::nanoseconds> alarmTime;
89121

90122
void LoadSettingsFromFile();
91123
void SaveSettingsToFile() const;
124+
uint8_t CalculateNextAlarm() const;
92125
};
93126
}
94127
}

0 commit comments

Comments
 (0)