From 5f8e1ef307a82fc06a9b9ab54a1b48528d317bc4 Mon Sep 17 00:00:00 2001 From: Mark Thompson <129641948+NotherNgineer@users.noreply.github.com> Date: Sun, 21 Jan 2024 12:47:28 -0600 Subject: [PATCH] Daylight Savings Time support (#1793) * Daylight Savings Time support * Cleanup * Clean-up * Revert ADSB change * Clean-up * Corrected date in comment, ironically --- firmware/application/apps/ui_adsb_rx.cpp | 2 +- firmware/application/apps/ui_aprs_rx.cpp | 2 +- firmware/application/apps/ui_numbers.cpp | 13 +- firmware/application/apps/ui_settings.cpp | 60 ++++- firmware/application/apps/ui_settings.hpp | 72 +++++- firmware/application/portapack.cpp | 1 + firmware/application/rtc_time.cpp | 216 ++++++++++++++++++ firmware/application/rtc_time.hpp | 15 ++ firmware/application/ui_record_view.cpp | 2 +- firmware/application/usb_serial_shell.cpp | 5 +- .../common/portapack_persistent_memory.cpp | 24 +- .../common/portapack_persistent_memory.hpp | 22 +- firmware/common/ui_widget.cpp | 4 +- 13 files changed, 403 insertions(+), 35 deletions(-) diff --git a/firmware/application/apps/ui_adsb_rx.cpp b/firmware/application/apps/ui_adsb_rx.cpp index a3a836195..a91b8ab0c 100644 --- a/firmware/application/apps/ui_adsb_rx.cpp +++ b/firmware/application/apps/ui_adsb_rx.cpp @@ -407,7 +407,7 @@ void ADSBRxView::on_frame(const ADSBFrameMessage* message) { status_good_frame.toggle(); rtc::RTC datetime; - rtcGetTime(&RTCD1, &datetime); + rtcGetTime(&RTCD1, &datetime); // Reading RTC directly to avoid DST transitions when calculating delta frame.set_rx_timestamp(datetime.minute() * 60 + datetime.second()); // NB: Reference to update entry in-place. diff --git a/firmware/application/apps/ui_aprs_rx.cpp b/firmware/application/apps/ui_aprs_rx.cpp index a703555a1..323b58db2 100644 --- a/firmware/application/apps/ui_aprs_rx.cpp +++ b/firmware/application/apps/ui_aprs_rx.cpp @@ -258,7 +258,7 @@ void APRSTableView::on_pkt(const APRSPacketMessage* message) { std::string source_formatted = packet.get_source_formatted(); std::string info_string = packet.get_stream_text(); - rtcGetTime(&RTCD1, &datetime); + rtc_time::now(datetime); auto& entry = ::on_packet(recent, packet.get_source()); entry.reset_age(); entry.inc_hit(); diff --git a/firmware/application/apps/ui_numbers.cpp b/firmware/application/apps/ui_numbers.cpp index 6c0edafe2..d8acbc21b 100644 --- a/firmware/application/apps/ui_numbers.cpp +++ b/firmware/application/apps/ui_numbers.cpp @@ -277,18 +277,9 @@ NumbersStationView::NumbersStationView( symfield_code.set_offset(10, 12); // End /* - rtc::RTC datetime; - rtcGetTime(&RTCD1, &datetime); - - // Thanks, Sakamoto-sama ! - y = datetime.year(); - m = datetime.month(); - d = datetime.day(); - y -= m < 3; - dayofweek = (y + y/4 - y/100 + y/400 + month_table[m-1] + d) % 7; - + dayofweek = rtc_time::current_day_of_week(); text_title.set(day_of_week[dayofweek]); -*/ + */ button_exit.on_select = [&nav](Button&) { nav.pop(); diff --git a/firmware/application/apps/ui_settings.cpp b/firmware/application/apps/ui_settings.cpp index 4bd037602..038da4960 100644 --- a/firmware/application/apps/ui_settings.cpp +++ b/firmware/application/apps/ui_settings.cpp @@ -61,10 +61,9 @@ SetDateTimeView::SetDateTimeView( NavigationView& nav) { button_save.on_select = [&nav, this](Button&) { const auto model = this->form_collect(); - const rtc::RTC new_datetime{ - model.year, model.month, model.day, - model.hour, model.minute, model.second}; - rtcSetTime(&RTCD1, &new_datetime); + rtc::RTC new_datetime{model.year, model.month, model.day, model.hour, model.minute, model.second}; + pmem::set_config_dst(model.dst); + rtc_time::set(new_datetime); // NB: 1 hour will be subtracted if value is stored in RTC during DST nav.pop(); }, @@ -80,20 +79,49 @@ SetDateTimeView::SetDateTimeView( &field_hour, &field_minute, &field_second, + &text_weekday, + &text_day_of_year, + &checkbox_dst_enable, + &options_dst_start_which, + &options_dst_start_weekday, + &options_dst_start_month, + &options_dst_end_which, + &options_dst_end_weekday, + &options_dst_end_month, &button_save, &button_cancel, }); + // Populate DST options (same string text for start & end) + options_dst_start_which.set_options(which_options); + options_dst_end_which.set_options(which_options); + options_dst_start_weekday.set_options(weekday_options); + options_dst_end_weekday.set_options(weekday_options); + options_dst_start_month.set_options(month_options); + options_dst_end_month.set_options(month_options); + + const auto date_changed_fn = [this](int32_t) { + auto weekday = rtc_time::day_of_week(field_year.value(), field_month.value(), field_day.value()); + auto doy = rtc_time::day_of_year(field_year.value(), field_month.value(), field_day.value()); + bool valid_date = (field_day.value() <= rtc_time::days_per_month(field_year.value(), field_month.value())); + text_weekday.set(valid_date ? weekday_options[weekday].first : "-"); + text_day_of_year.set(valid_date ? to_string_dec_uint(doy, 3) : "-"); + }; + + field_year.on_change = date_changed_fn; + field_month.on_change = date_changed_fn; + field_day.on_change = date_changed_fn; + rtc::RTC datetime; - rtcGetTime(&RTCD1, &datetime); + rtc_time::now(datetime); SetDateTimeModel model{ datetime.year(), datetime.month(), datetime.day(), datetime.hour(), datetime.minute(), - datetime.second()}; - + datetime.second(), + pmem::config_dst()}; form_init(model); } @@ -108,16 +136,32 @@ void SetDateTimeView::form_init(const SetDateTimeModel& model) { field_hour.set_value(model.hour); field_minute.set_value(model.minute); field_second.set_value(model.second); + checkbox_dst_enable.set_value(model.dst.b.dst_enabled); + options_dst_start_which.set_by_value(model.dst.b.start_which); + options_dst_start_weekday.set_by_value(model.dst.b.start_weekday); + options_dst_start_month.set_by_value(model.dst.b.start_month); + options_dst_end_which.set_by_value(model.dst.b.end_which); + options_dst_end_weekday.set_by_value(model.dst.b.end_weekday); + options_dst_end_month.set_by_value(model.dst.b.end_month); } SetDateTimeModel SetDateTimeView::form_collect() { + pmem::dst_config_t dst; + dst.b.dst_enabled = static_cast(checkbox_dst_enable.value()); + dst.b.start_which = static_cast(options_dst_start_which.selected_index_value()); + dst.b.start_weekday = static_cast(options_dst_start_weekday.selected_index_value()); + dst.b.start_month = static_cast(options_dst_start_month.selected_index_value()); + dst.b.end_which = static_cast(options_dst_end_which.selected_index_value()); + dst.b.end_weekday = static_cast(options_dst_end_weekday.selected_index_value()); + dst.b.end_month = static_cast(options_dst_end_month.selected_index_value()); return { .year = static_cast(field_year.value()), .month = static_cast(field_month.value()), .day = static_cast(field_day.value()), .hour = static_cast(field_hour.value()), .minute = static_cast(field_minute.value()), - .second = static_cast(field_second.value())}; + .second = static_cast(field_second.value()), + .dst = dst}; } /* SetRadioView ******************************************/ diff --git a/firmware/application/apps/ui_settings.hpp b/firmware/application/apps/ui_settings.hpp index 4016de66d..683967695 100644 --- a/firmware/application/apps/ui_settings.hpp +++ b/firmware/application/apps/ui_settings.hpp @@ -44,6 +44,7 @@ struct SetDateTimeModel { uint8_t hour; uint8_t minute; uint8_t second; + portapack::persistent_memory::dst_config_t dst; }; class SetDateTimeView : public View { @@ -55,56 +56,111 @@ class SetDateTimeView : public View { std::string title() const override { return "Date/Time"; }; private: + using option_t = std::pair; + std::vector which_options = {{"1st", 0}, {"2nd", 1}, {"3rd", 2}, {"4th", 3}, {"Last", 4}}; + std::vector weekday_options = {{"Sun", 0}, {"Mon", 1}, {"Tue", 2}, {"Wed", 3}, {"Thu", 4}, {"Fri", 5}, {"Sat", 6}}; + std::vector month_options = {{"Jan", 1}, {"Feb", 2}, {"Mar", 3}, {"Apr", 4}, {"May", 5}, {"Jun", 6}, {"Jul", 7}, {"Aug", 8}, {"Sep", 9}, {"Oct", 10}, {"Nov", 11}, {"Dec", 12}}; + Labels labels{ {{1 * 8, 1 * 16}, "Adjust the RTC clock date &", Color::light_grey()}, {{1 * 8, 2 * 16}, "time. If clock resets after", Color::light_grey()}, {{1 * 8, 3 * 16}, "reboot, coin batt. is dead. ", Color::light_grey()}, - {{5 * 8, 8 * 16 - 2}, "YYYY-MM-DD HH:MM:SS", Color::grey()}, - {{9 * 8, 9 * 16}, "- - : :", Color::light_grey()}}; + {{1 * 8, 5 * 16 - 2}, "YYYY-MM-DD HH:MM:SS DoW DoY", Color::grey()}, + {{5 * 8, 6 * 16}, "- - : :", Color::light_grey()}, + {{1 * 8, 11 * 16}, "DST adds 1 hour to RTC time.", Color::light_grey()}, + {{0 * 8, 12 * 16}, "Start: 0:00 on Nth DDD in", Color::light_grey()}, + {{0 * 8, 13 * 16}, "End: 1:00 on Nth DDD in", Color::light_grey()}}; NumberField field_year{ - {5 * 8, 9 * 16}, + {1 * 8, 6 * 16}, 4, {2015, 2099}, 1, '0', + true, }; NumberField field_month{ - {10 * 8, 9 * 16}, + {6 * 8, 6 * 16}, 2, {1, 12}, 1, '0', + true, }; NumberField field_day{ - {13 * 8, 9 * 16}, + {9 * 8, 6 * 16}, 2, {1, 31}, 1, '0', + true, }; NumberField field_hour{ - {16 * 8, 9 * 16}, + {12 * 8, 6 * 16}, 2, {0, 23}, 1, '0', + true, }; NumberField field_minute{ - {19 * 8, 9 * 16}, + {15 * 8, 6 * 16}, 2, {0, 59}, 1, '0', + true, }; NumberField field_second{ - {22 * 8, 9 * 16}, + {18 * 8, 6 * 16}, 2, {0, 59}, 1, '0', + true, }; + Text text_weekday{ + {22 * 8, 6 * 16, 3 * 8, 16}, + ""}; + Text text_day_of_year{ + {26 * 8, 6 * 16, 3 * 8, 16}, + ""}; + + Checkbox checkbox_dst_enable{ + {2 * 8, 9 * 16}, + 23, + "Enable Daylight Savings"}; + + OptionsField options_dst_start_which{ + {15 * 8, 12 * 16}, + 4, + {}}; + + OptionsField options_dst_start_weekday{ + {20 * 8, 12 * 16}, + 3, + {}}; + + OptionsField options_dst_start_month{ + {27 * 8, 12 * 16}, + 3, + {}}; + + OptionsField options_dst_end_which{ + {15 * 8, 13 * 16}, + 4, + {}}; + + OptionsField options_dst_end_weekday{ + {20 * 8, 13 * 16}, + 3, + {}}; + + OptionsField options_dst_end_month{ + {27 * 8, 13 * 16}, + 3, + {}}; Button button_save{ {2 * 8, 16 * 16, 12 * 8, 32}, diff --git a/firmware/application/portapack.cpp b/firmware/application/portapack.cpp index 6b013c614..7bf32d17f 100644 --- a/firmware/application/portapack.cpp +++ b/firmware/application/portapack.cpp @@ -411,6 +411,7 @@ bool init() { /* Cache some configuration data from persistent memory. */ persistent_memory::cache::init(); + rtc_time::dst_init(); chThdSleepMilliseconds(10); clock_manager.init_clock_generator(); diff --git a/firmware/application/rtc_time.cpp b/firmware/application/rtc_time.cpp index 37e1762b8..998901184 100644 --- a/firmware/application/rtc_time.cpp +++ b/firmware/application/rtc_time.cpp @@ -1,5 +1,6 @@ /* * Copyright (C) 2015 Jared Boone, ShareBrained Technology, Inc. + * Copyright (C) 2024 Mark Thompson * * This file is part of PortaPack. * @@ -20,24 +21,239 @@ */ #include "rtc_time.hpp" +#include "portapack_persistent_memory.hpp" + +using namespace portapack; +namespace pmem = portapack::persistent_memory; namespace rtc_time { Signal<> signal_tick_second; +bool dst_enabled{false}; +bool dst_in_range{false}; +uint16_t dst_start_doy; +uint16_t dst_end_doy; +uint16_t dst_current_doy{0xFFFF}; +uint16_t dst_current_year{0xFFFF}; + +static const uint16_t months_with_31 = 0x0AD5; // Bits represents months with 31 days (bit 0 for January, etc) + void on_tick_second() { signal_tick_second.emit(); } +// Check if DST is configured and active (called at power-up) +void dst_init() { + rtc::RTC datetime; + rtcGetTime(&RTCD1, &datetime); + dst_update_date_range(datetime.year(), LPC_RTC->DOY); +} + +// Return current time & date (adjusted for DST if enabled) rtc::RTC now() { rtc::RTC datetime; rtcGetTime(&RTCD1, &datetime); + if (dst_enabled) { + datetime = dst_adjust_returned_time(datetime); + } return datetime; } rtc::RTC now(rtc::RTC& out_datetime) { rtcGetTime(&RTCD1, &out_datetime); + if (dst_enabled) { + out_datetime = dst_adjust_returned_time(out_datetime); + } return out_datetime; } +// Add 1 hour (spring forward) if needed for DST (called whenever RTC is read if DST is enabled) +rtc::RTC dst_adjust_returned_time(rtc::RTC& datetime) { + // Read day of year from RTC and check for change + uint32_t doy = LPC_RTC->DOY; + + // Update DST start/end day-of-year only when year changes, otherwise just check if in range when day changes + if (doy != dst_current_doy) { + if (datetime.year() != dst_current_year) + dst_update_date_range(datetime.year(), doy); + else + dst_check_date_range(doy); + } + + // Not in DST date range + if (!dst_in_range) + return datetime; + + // Add 1 hour to RTC time + uint16_t year = datetime.year(); + uint8_t month = datetime.month(); + uint8_t day = datetime.day(); + uint8_t hour = datetime.hour(); + + if (++hour > 23) { + hour = 0; + if (++day > days_per_month(year, month)) { + day = 1; + if (++month > 12) { + year++; + } + } + } + rtc::RTC dst_datetime{year, month, day, hour, datetime.minute(), datetime.second()}; + return dst_datetime; +} + +// Check if current date is within the DST range (called when date changes) +void dst_check_date_range(uint16_t doy) { + dst_current_doy = doy; + + // Check if date is within DST range + // (note that dates are reversed in Southern hemisphere because Summer starts in December) + if (dst_start_doy <= dst_end_doy) + dst_in_range = ((doy >= dst_start_doy) && (doy < dst_end_doy)); + else + dst_in_range = ((doy >= dst_start_doy) || (doy < dst_end_doy)); +} + +// Update DST parameters (called at power-up, when year changes, or DST settings are changed) +void dst_update_date_range(uint16_t year, uint16_t doy) { + dst_enabled = pmem::dst_enabled(); + if (dst_enabled) { + const pmem::dst_config_t dst = pmem::config_dst(); + dst_current_year = year; + dst_start_doy = day_of_year_of_nth_weekday(dst_current_year, dst.b.start_month, dst.b.start_which, dst.b.start_weekday); + dst_end_doy = day_of_year_of_nth_weekday(dst_current_year, dst.b.end_month, dst.b.end_which, dst.b.end_weekday); + + dst_check_date_range(doy); + } else { + dst_in_range = false; + } +} + +// Set RTC clock. +// If the user has enabled DST, the time entered and passed to this function is interpreted as having been adjusted for DST. +// When DST functionality is enabled in pmem, the value stored in the RTC hardware is non-DST time, and we only fudge the time when read. +// Thus, it's necessary to subtract an hour before storing it in the RTC if we're in the DST date range. +// (If RTC is desired to represent UTC, then DST should obviously be disabled in settings.) +// NB: Firmware should not change RTC without going through this function. +void set(rtc::RTC& new_datetime) { + uint16_t year = new_datetime.year(); + uint8_t month = new_datetime.month(); + uint8_t day = new_datetime.day(); + uint8_t hour = new_datetime.hour(); + + // NB: DST code relies on the Day of Year (DOY) value to be initialized in the RTC by firmware (RTC hardware only does increment and wrap) + uint16_t doy = day_of_year(year, month, day); + dst_update_date_range(year, doy); + + // NB: Currently we only support the DST transition at midnight RTC time (not configurable to hour granularity). + // Note on handling the the hour before/after DST time change: + // + // If entered hour==0 on dst_start_day: + // Time entered is INVALID due to spring-forward; code below rolls back RTC to last hour of previous day. + // + // If entered hour==0 on dst_end_doy: + // This hour occurs twice due to fall-back; code below treats as if DST has ended (the second occurrence) and doesn't roll back RTC + // + if (dst_in_range) { + // Subtract 1 hour from requested time before storing in RTC + if (hour-- == 0) { + hour = 23; + if (day-- == 0) { + if (month-- == 0) { + month = 12; + year--; + } + day = days_per_month(year, month); + } + } + + // Update day-of-year if date was changed above + if (day != new_datetime.day()) { + doy = day_of_year(year, month, day); + dst_update_date_range(year, doy); + } + } + + // NB: Writing RTC twice takes a second, but ensures that new value can be read back immediately + // (if not written twice, the old value will be returned if read back in the following 1-2 seconds) + // (you will notice with older Mayhem versions that running the Date/Time Settings app again quickly will show the old time) + LPC_RTC->DOY = doy; + rtc::RTC adjusted_datetime{year, month, day, hour, new_datetime.minute(), new_datetime.second()}; + rtcSetTime(&RTCD1, &adjusted_datetime); + rtcSetTime(&RTCD1, &adjusted_datetime); +} + +// Calculates 1-based day of year for Nth weekday in month (weekday and n are 0-based, month is 1-based) +uint16_t day_of_year_of_nth_weekday(uint16_t year, uint8_t month, uint8_t n, uint8_t weekday) { + uint8_t w = day_of_week(year, month, 1); + uint8_t nn = n; + + // special handling for "last" weekday - are there 4 or 5 in the month? + if (n == 4) { + if (month == 2) { + // February + // only weekday w occurs 5 times on the 29th on leap years only + if ((weekday != w) || !leap_year(year)) + nn = 3; + } else { + // Other months have either 30 or 31 days; + // weekdays w and w+1 occur 5 times (on the 29th & 30th); weekday w+2 occurs 5 times only if month has 31 days + if ((weekday != w) && (weekday != (w + 1) % 7) && + ((weekday != (w + 2) % 7) || ((months_with_31 & (1 << (month - 1))) == 0))) + nn = 3; + } + } + return day_of_year(year, month, 1) + (nn * 7) + weekday + ((weekday < w) ? 7 : 0) - w; +} + +// Calculates 1-based day of year (input month & day are 1-based) +uint16_t day_of_year(uint16_t year, uint8_t month, uint8_t day) { + // 1-based day of year for 1st day or month (index is 1-based month) + static uint16_t month_to_day_in_year[1 + 12] = { + 0, // placeholder for 1-based indexing + 1, + 1 + 31, + 1 + 31 + 28, + 1 + 31 + 28 + 31, + 1 + 31 + 28 + 31 + 30, + 1 + 31 + 28 + 31 + 30 + 31, + 1 + 31 + 28 + 31 + 30 + 31 + 30, + 1 + 31 + 28 + 31 + 30 + 31 + 30 + 31, + 1 + 31 + 28 + 31 + 30 + 31 + 30 + 31 + 31, + 1 + 31 + 28 + 31 + 30 + 31 + 30 + 31 + 31 + 30, + 1 + 31 + 28 + 31 + 30 + 31 + 30 + 31 + 31 + 30 + 31, + 1 + 31 + 28 + 31 + 30 + 31 + 30 + 31 + 31 + 30 + 31 + 30, + }; + return month_to_day_in_year[month] + day - (((month > 2) && leap_year(year)) ? 0 : 1); +} + +bool leap_year(uint16_t year) { + // Technically should be: ((year % 4 == 0) && (year % 100 != 0)) || (year % 400 == 0) + // but year 2100 is a long way off and the LPC43xx RTC doesn't bother handling it either + return ((year & 3) == 0); +} + +uint8_t days_per_month(uint16_t year, uint8_t month) { + if (month == 2) + return leap_year(year) ? 29 : 28; + else + return ((months_with_31 & (1 << (month - 1))) != 0) ? 31 : 30; +} + +uint8_t current_day_of_week() { + rtc::RTC datetime; + now(datetime); + return day_of_week(datetime.year(), datetime.month(), datetime.day()); +} + +// Returns 0-based weekday for date (0=Sunday, 1=Monday, etc) - using Zeller's Congruence formula +// (month and day are 1-based) +uint8_t day_of_week(uint16_t year, uint8_t month, uint8_t day) { + int m = (month < 3) ? month + 13 : month + 1; + int y = (month < 3) ? year - 1 : year; + return (day - 1 + (13 * m / 5) + y + (y / 4) - (y / 100) + (y / 400)) % 7; +} + } /* namespace rtc_time */ diff --git a/firmware/application/rtc_time.hpp b/firmware/application/rtc_time.hpp index d77a8a32e..52839a1a6 100644 --- a/firmware/application/rtc_time.hpp +++ b/firmware/application/rtc_time.hpp @@ -33,12 +33,27 @@ extern Signal<> signal_tick_second; void on_tick_second(); +/* Sets the current RTCTime in the RTC. */ +void set(rtc::RTC& new_datetime); + /* Returns the current RTCTime from the RTC. */ rtc::RTC now(); /* Returns the current RTCTime from the RTC. */ rtc::RTC now(rtc::RTC& out_datetime); +/* Daylight Savings Time functions */ +void dst_init(); +rtc::RTC dst_adjust_returned_time(rtc::RTC& datetime); +void dst_check_date_range(uint16_t doy); +void dst_update_date_range(uint16_t year, uint16_t doy); +uint8_t days_per_month(uint16_t year, uint8_t month); +uint8_t current_day_of_week(); +uint8_t day_of_week(uint16_t year, uint8_t month, uint8_t day); +bool leap_year(uint16_t year); +uint16_t day_of_year(uint16_t year, uint8_t month, uint8_t day); +uint16_t day_of_year_of_nth_weekday(uint16_t year, uint8_t month, uint8_t n, uint8_t weekday); + } /* namespace rtc_time */ #endif /*__RTC_TIME_H__*/ diff --git a/firmware/application/ui_record_view.cpp b/firmware/application/ui_record_view.cpp index 3c0a37b99..9aac8945e 100644 --- a/firmware/application/ui_record_view.cpp +++ b/firmware/application/ui_record_view.cpp @@ -181,7 +181,7 @@ void RecordView::start() { std::filesystem::path base_path; if (filename_date_frequency) { - rtcGetTime(&RTCD1, &datetime); + rtc_time::now(datetime); // ISO 8601 std::string date_time = diff --git a/firmware/application/usb_serial_shell.cpp b/firmware/application/usb_serial_shell.cpp index a930e3df0..26fdf118a 100644 --- a/firmware/application/usb_serial_shell.cpp +++ b/firmware/application/usb_serial_shell.cpp @@ -404,7 +404,7 @@ static void cmd_rtcget(BaseSequentialStream* chp, int argc, char* argv[]) { (void)argv; rtc::RTC datetime; - rtcGetTime(&RTCD1, &datetime); + rtc_time::now(datetime); chprintf(chp, "Current time: %04d-%02d-%02d %02d:%02d:%02d\r\n", datetime.year(), datetime.month(), datetime.day(), datetime.hour(), datetime.minute(), datetime.second()); } @@ -420,11 +420,12 @@ static void cmd_rtcset(BaseSequentialStream* chp, int argc, char* argv[]) { return; } + // TODO: additional commands/parameters for DST? rtc::RTC new_datetime{ (uint16_t)strtol(argv[0], NULL, 10), (uint8_t)strtol(argv[1], NULL, 10), (uint8_t)strtol(argv[2], NULL, 10), (uint32_t)strtol(argv[3], NULL, 10), (uint32_t)strtol(argv[4], NULL, 10), (uint32_t)strtol(argv[5], NULL, 10)}; - rtcSetTime(&RTCD1, &new_datetime); + rtc_time::set(new_datetime); chprintf(chp, "ok\r\n"); } diff --git a/firmware/common/portapack_persistent_memory.cpp b/firmware/common/portapack_persistent_memory.cpp index 87b1e8428..7723f77cd 100644 --- a/firmware/common/portapack_persistent_memory.cpp +++ b/firmware/common/portapack_persistent_memory.cpp @@ -33,6 +33,7 @@ #include "ui_styles.hpp" #include "ui_painter.hpp" #include "utility.hpp" +#include "rtc_time.hpp" #include #include @@ -235,6 +236,9 @@ struct data_t { // recovery mode magic value storage uint32_t config_mode_storage; + // Daylight savings time + dst_config_t dst_config; + constexpr data_t() : structure_version(data_structure_version_enum::VERSION_CURRENT), target_frequency(target_frequency_reset_value), @@ -287,7 +291,8 @@ struct data_t { headphone_volume_cb(-600), misc_config(), ui_config2(), - config_mode_storage(CONFIG_MODE_NORMAL_VALUE) { + config_mode_storage(CONFIG_MODE_NORMAL_VALUE), + dst_config() { } }; @@ -972,6 +977,22 @@ bool config_disable_config_mode_direct() { return ((misc_config_u32 & MC_CONFIG_DISABLE_CONFIG_MODE) != 0); } +// Daylight savings time +bool dst_enabled() { + return data->dst_config.b.dst_enabled; +} +void set_dst_enabled(bool v) { + data->dst_config.b.dst_enabled = v; + rtc_time::dst_init(); +} +dst_config_t config_dst() { + return data->dst_config; +} +void set_config_dst(dst_config_t v) { + data->dst_config = v; + rtc_time::dst_init(); +} + // PMem to sdcard settings bool should_use_sdcard_for_pmem() { @@ -1073,6 +1094,7 @@ bool debug_dump() { // pmem_dump_file.write_line("UNUSED_8: " + to_string_dec_uint(data->UNUSED_8)); pmem_dump_file.write_line("headphone_volume_cb: " + to_string_dec_int(data->headphone_volume_cb)); pmem_dump_file.write_line("config_mode_storage: 0x" + to_string_hex(data->config_mode_storage, 8)); + pmem_dump_file.write_line("dst_config: 0x" + to_string_hex((uint32_t)data->dst_config.v, 8)); // ui_config bits const auto backlight_timer = portapack::persistent_memory::config_backlight_timer(); diff --git a/firmware/common/portapack_persistent_memory.hpp b/firmware/common/portapack_persistent_memory.hpp index bf90da7f1..81a73de92 100644 --- a/firmware/common/portapack_persistent_memory.hpp +++ b/firmware/common/portapack_persistent_memory.hpp @@ -116,6 +116,21 @@ enum encoder_dial_sensitivity { NUM_DIAL_SENSITIVITY }; +typedef union { + uint32_t v; + struct { + uint8_t start_which : 4; + uint8_t start_weekday : 4; + uint8_t start_month : 4; + uint8_t end_which : 4; + uint8_t end_weekday : 4; + uint8_t end_month : 4; + uint8_t UNUSED : 7; + uint8_t dst_enabled : 1; + } b; +} dst_config_t; +static_assert(sizeof(dst_config_t) == sizeof(uint32_t)); + namespace cache { /* Set values in cache to sensible defaults. */ @@ -241,9 +256,14 @@ void set_pocsag_ignore_address(uint32_t address); bool clkout_enabled(); void set_clkout_enabled(bool v); -uint16_t clkout_freq(); void set_clkout_freq(uint16_t freq); +bool dst_enabled(); +void set_dst_enabled(bool v); +uint16_t clkout_freq(); +dst_config_t config_dst(); +void set_config_dst(dst_config_t v); + /* Recon app */ bool recon_autosave_freqs(); bool recon_autostart_recon(); diff --git a/firmware/common/ui_widget.cpp b/firmware/common/ui_widget.cpp index 46986e3c1..0d9d78a54 100644 --- a/firmware/common/ui_widget.cpp +++ b/firmware/common/ui_widget.cpp @@ -32,8 +32,10 @@ #include "irq_controls.hpp" #include "string_format.hpp" #include "usb_serial_device_to_host.h" +#include "rtc_time.hpp" using namespace portapack; +using namespace rtc_time; namespace ui { @@ -433,7 +435,7 @@ void Labels::getWidgetName(std::string& result) { /* LiveDateTime **********************************************************/ void LiveDateTime::on_tick_second() { - rtcGetTime(&RTCD1, &datetime); + rtc_time::now(datetime); text = ""; if (!hide_clock) { if (date_enabled) {