#include #include #include #include #include "alarm.h" #include #include #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 &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 alarm::days_enabled() const { std::array 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& 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 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 alarms; uint8_t armed_alarm = 0; void alarm_trigger_callback() { printf("alarm triggered!\n"); { std::array 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(); }