337 lines
9.4 KiB
C++
337 lines
9.4 KiB
C++
#include <array>
|
|
#include <cstdint>
|
|
#include <cstdlib>
|
|
#include <vector>
|
|
#include "alarm.h"
|
|
|
|
#include <cstdio>
|
|
#include <cstring>
|
|
|
|
#include "dateutils.h"
|
|
#include "link.h"
|
|
#include "ui.h"
|
|
#include "hardware/rtc.h"
|
|
#include "pico/multicore.h"
|
|
#include "pico/util/datetime.h"
|
|
|
|
|
|
alarm::alarm(uint8_t hours, uint8_t minutes, const std::array<bool, 7> &days_enabled, bool every_other_week, bool even_week){
|
|
this->minutes_store = minutes;
|
|
|
|
// Initialize scheduled field
|
|
this->scheduled = 0;
|
|
|
|
// Set bits 0-6 for days of the week
|
|
for (int i = 0; i < 7; i++) {
|
|
if (days_enabled[i]) {
|
|
this->scheduled |= (1 << i);
|
|
}
|
|
}
|
|
|
|
// Set bit 7 for every other week functionality
|
|
if (every_other_week) {
|
|
this->scheduled |= (1 << 7);
|
|
}
|
|
|
|
// Initialize hours field
|
|
this->hours_and_stuff_store = 0;
|
|
|
|
// Set bit 0 for even/odd week (only relevant if every_other_week is true)
|
|
if (every_other_week && even_week) {
|
|
this->hours_and_stuff_store |= (1 << 0);
|
|
}
|
|
|
|
// Set bit 1 as disabled flag
|
|
this->hours_and_stuff_store &= ~(1 << 1);
|
|
|
|
// Bit 2 is reserved (left as 0)
|
|
|
|
// Set bits 3-7 with actual hours value (shift left by 3)
|
|
this->hours_and_stuff_store |= (hours & 0x1F) << 3; // 0x1F masks to 5 bits (0-31)
|
|
};
|
|
|
|
uint8_t alarm::minutes() const {
|
|
return minutes_store;
|
|
}
|
|
|
|
uint8_t alarm::hours() const {
|
|
return (hours_and_stuff_store >> 3) & 0x1F; // Extract bits 3-7 and mask to 5 bits
|
|
}
|
|
|
|
std::array<bool, 7> alarm::days_enabled() const {
|
|
std::array<bool, 7> days;
|
|
for (int i = 0; i < 7; i++) {
|
|
days[i] = (scheduled & (1 << i)) != 0;
|
|
}
|
|
return days;
|
|
}
|
|
|
|
bool alarm::every_other_week() const {
|
|
return (scheduled & (1 << 7)) != 0;
|
|
}
|
|
|
|
bool alarm::even_week() const {
|
|
return (hours_and_stuff_store & (1 << 0)) != 0;
|
|
}
|
|
|
|
bool alarm::enabled() const {
|
|
return (hours_and_stuff_store & (1 << 1)) != 0;
|
|
}
|
|
|
|
// Enable/disable methods
|
|
void alarm::enable() {
|
|
hours_and_stuff_store |= (1 << 1); // Set bit 1
|
|
}
|
|
|
|
void alarm::disable() {
|
|
hours_and_stuff_store &= ~(1 << 1); // Clear bit 1
|
|
}
|
|
|
|
void alarm::set_state(bool enabled) {
|
|
if (enabled) {
|
|
enable();
|
|
} else {
|
|
disable();
|
|
}
|
|
}
|
|
|
|
void alarm::set_minutes(uint8_t minutes) {
|
|
this->minutes_store = minutes;
|
|
}
|
|
|
|
void alarm::set_hours(uint8_t hours) {
|
|
this->hours_and_stuff_store &= ~(0x1F << 3); // Clear bits 3-7
|
|
this->hours_and_stuff_store |= (hours & 0x1F) << 3; // Set new hours
|
|
}
|
|
|
|
void alarm::set_days_enabled(const std::array<bool, 7>& days_enabled) {
|
|
this->scheduled &= (1 << 7); // Keep only bit 7 (every_other_week)
|
|
|
|
// Set new day bits
|
|
for (int i = 0; i < 7; i++) {
|
|
if (days_enabled[i]) {
|
|
this->scheduled |= (1 << i);
|
|
}
|
|
}
|
|
}
|
|
|
|
void alarm::set_every_other_week(bool every_other_week) {
|
|
if (every_other_week) {
|
|
this->scheduled |= (1 << 7); // Set bit 7
|
|
} else {
|
|
this->scheduled &= ~(1 << 7); // Clear bit 7
|
|
}
|
|
}
|
|
|
|
void alarm::set_even_week(bool even_week) {
|
|
if (even_week) {
|
|
this->hours_and_stuff_store |= (1 << 0); // Set bit 0
|
|
} else {
|
|
this->hours_and_stuff_store &= ~(1 << 0); // Clear bit 0
|
|
}
|
|
}
|
|
|
|
#include "pico/util/datetime.h"
|
|
|
|
|
|
void alarm::get_next_ring_time(datetime_t &ret) const {
|
|
if (!enabled()) {
|
|
ret = {0};
|
|
return;
|
|
}
|
|
|
|
datetime_t now;
|
|
rtc_get_datetime(&now);
|
|
|
|
datetime_t candidate = now;
|
|
candidate.hour = hours();
|
|
candidate.min = minutes();
|
|
candidate.sec = 0;
|
|
|
|
std::array<bool, 7> days = days_enabled();
|
|
bool any_day_enabled = false;
|
|
for (uint8_t i = 0; i < 7; i++) {
|
|
if (days[i]) {
|
|
any_day_enabled = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (!any_day_enabled) {
|
|
// One-time alarm: schedule for tomorrow if time passed today
|
|
if (candidate.hour < now.hour ||
|
|
(candidate.hour == now.hour && candidate.min <= now.min)) {
|
|
|
|
candidate.day++;
|
|
|
|
// Handle month overflow
|
|
const uint8_t days_in_month[] = {0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};
|
|
uint8_t max_days = days_in_month[candidate.month];
|
|
if (candidate.month == 2 && (candidate.year % 4 == 0 &&
|
|
(candidate.year % 100 != 0 || candidate.year % 400 == 0))) {
|
|
max_days = 29;
|
|
}
|
|
|
|
if (candidate.day > max_days) {
|
|
candidate.day = 1;
|
|
candidate.month++;
|
|
if (candidate.month > 12) {
|
|
candidate.month = 1;
|
|
candidate.year++;
|
|
}
|
|
}
|
|
|
|
candidate.dotw = (now.dotw + 1) % 7;
|
|
}
|
|
memcpy(&ret, &candidate, sizeof(datetime_t));
|
|
return;
|
|
}
|
|
|
|
// Optimized ISO 8601 week calculation
|
|
auto get_iso_week_number = [](const datetime_t& dt) -> uint8_t {
|
|
uint16_t ordinal = dt.day;
|
|
const uint8_t days_in_month[] = {0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};
|
|
|
|
for (uint8_t m = 1; m < dt.month; m++) {
|
|
ordinal += days_in_month[m];
|
|
if (m == 2 && (dt.year % 4 == 0 && (dt.year % 100 != 0 || dt.year % 400 == 0))) {
|
|
ordinal++;
|
|
}
|
|
}
|
|
|
|
uint8_t iso_weekday = (dt.dotw == 0) ? 7 : dt.dotw;
|
|
uint8_t week = (ordinal - iso_weekday + 10) / 7;
|
|
|
|
if (week == 0) {
|
|
int16_t prev_year = dt.year - 1; // fix warning
|
|
bool leap = (prev_year % 4 == 0 && (prev_year % 100 != 0 || prev_year % 400 == 0));
|
|
|
|
datetime_t prev_jan1 = {prev_year, 1, 1, 0, 0, 0, 0};
|
|
uint8_t jan1_weekday = (prev_jan1.dotw == 0) ? 7 : prev_jan1.dotw;
|
|
|
|
if (jan1_weekday == 4 || jan1_weekday == 5 || (leap && jan1_weekday == 3)) {
|
|
return 53;
|
|
}
|
|
return 52;
|
|
}
|
|
|
|
if (week == 53) {
|
|
bool leap = (dt.year % 4 == 0 && (dt.year % 100 != 0 || dt.year % 400 == 0));
|
|
uint16_t year_length = leap ? 366 : 365;
|
|
|
|
uint16_t remaining_days = year_length - ordinal;
|
|
uint8_t dec31_weekday = (iso_weekday + remaining_days) % 7;
|
|
if (dec31_weekday == 0) dec31_weekday = 7;
|
|
|
|
if (dec31_weekday <= 3) {
|
|
return 1;
|
|
}
|
|
}
|
|
|
|
return week;
|
|
};
|
|
|
|
for (uint8_t days_ahead = 0; days_ahead < 14; days_ahead++) {
|
|
datetime_t test_date = now;
|
|
|
|
test_date.day += days_ahead;
|
|
|
|
const uint8_t days_in_month[] = {0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};
|
|
uint8_t max_days = days_in_month[test_date.month];
|
|
if (test_date.month == 2 && (test_date.year % 4 == 0 &&
|
|
(test_date.year % 100 != 0 || test_date.year % 400 == 0))) {
|
|
max_days = 29;
|
|
}
|
|
|
|
while (test_date.day > max_days) {
|
|
test_date.day -= max_days;
|
|
test_date.month++;
|
|
if (test_date.month > 12) {
|
|
test_date.month = 1;
|
|
test_date.year++;
|
|
}
|
|
max_days = days_in_month[test_date.month];
|
|
if (test_date.month == 2 && (test_date.year % 4 == 0 &&
|
|
(test_date.year % 100 != 0 || test_date.year % 400 == 0))) {
|
|
max_days = 29;
|
|
}
|
|
}
|
|
|
|
uint8_t dotw = (now.dotw + days_ahead) % 7;
|
|
test_date.dotw = dotw;
|
|
|
|
if (!days[dotw]) continue;
|
|
|
|
test_date.hour = hours();
|
|
test_date.min = minutes();
|
|
test_date.sec = 0;
|
|
|
|
if (days_ahead == 0) {
|
|
if (test_date.hour < now.hour ||
|
|
(test_date.hour == now.hour && test_date.min <= now.min)) {
|
|
continue;
|
|
}
|
|
}
|
|
|
|
if (every_other_week()) {
|
|
uint8_t current_week = get_iso_week_number(now);
|
|
uint8_t test_week = get_iso_week_number(test_date);
|
|
|
|
bool is_even_week = (test_week % 2 == 0);
|
|
|
|
if (even_week() != is_even_week) {
|
|
continue;
|
|
}
|
|
}
|
|
memcpy(&ret, &test_date, sizeof(datetime_t));
|
|
return;
|
|
}
|
|
|
|
ret = {0};
|
|
return;
|
|
}
|
|
|
|
|
|
|
|
std::vector<alarm> alarms;
|
|
|
|
uint8_t armed_alarm = 0;
|
|
void alarm_trigger_callback() {
|
|
printf("alarm triggered!\n");
|
|
|
|
{
|
|
std::array<bool, 7> days_enabled = alarms[armed_alarm].days_enabled();
|
|
bool one_time = true;
|
|
for (bool b : days_enabled) {
|
|
if (b) {one_time = false;break;}
|
|
}
|
|
if (one_time) {alarms[armed_alarm].disable();}
|
|
}
|
|
rearm_alarm_timers();
|
|
if (get_core_num()) {
|
|
alarm_gone_off_page();
|
|
} else {
|
|
multicore_launch_core1(&alarm_gone_off_page);
|
|
}
|
|
}
|
|
|
|
void rearm_alarm_timers() {
|
|
printf("arming alarm...\n");
|
|
rtc_disable_alarm();
|
|
if (alarms.empty()) return;
|
|
datetime_t first_alarm;
|
|
alarms[0].get_next_ring_time(first_alarm);
|
|
datetime_t testing;
|
|
for (uint8_t i = 1; i < alarms.size(); i++) {
|
|
if (alarms[i].enabled()) {
|
|
alarms[i].get_next_ring_time(testing);
|
|
if (compare_datetime(&testing, &first_alarm) == SMALLER_THAN) {
|
|
memcpy(&first_alarm, &testing, sizeof(datetime_t));
|
|
armed_alarm = i;
|
|
}
|
|
}
|
|
}
|
|
printf("alarm set to %d:%d:%d %d.%d.%d\n",first_alarm.hour,first_alarm.min,first_alarm.sec,first_alarm.day,first_alarm.month,first_alarm.year);
|
|
rtc_set_alarm(&first_alarm,&alarm_trigger_callback);
|
|
rtc_enable_alarm();
|
|
} |