mirror of
https://github.com/MarekZegare4/MeshCore-Solo.git
synced 2026-11-02 19:02:00 +00:00
feat: summer time - Settings > Summer time (Europe, North America, Australia, New Zealand) puts the clock an hour ahead and back by itself; every local time goes through tzHoursAt(), the alarm keeps its time across the change (a skipped time rings an hour later, a repeated one once)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -64,34 +64,44 @@ private:
|
||||
_events->push(UiEventType::ClockAlert, label);
|
||||
}
|
||||
|
||||
// The UTC instant a local wall time first happens. Summer time starting
|
||||
// skips an hour: a time inside it comes as long after the jump as it was
|
||||
// meant to be after its start (02:30 rings at 03:30). Summer time ending
|
||||
// repeats one: the first of the two counts.
|
||||
int64_t wallToUtc(int64_t local) const {
|
||||
const int8_t tz = _prefs->tz_offset_hours;
|
||||
for (int8_t h = tz + 1; h >= tz; h--) { // the summer offset gives the earlier instant
|
||||
int64_t u = local - (int64_t)h * 3600;
|
||||
if (tzHoursAt(_prefs, (uint32_t)u) == h) return u;
|
||||
}
|
||||
return local - (int64_t)tz * 3600; // skipped
|
||||
}
|
||||
|
||||
// Next absolute wall instant matching alarm_hour:alarm_min in local time,
|
||||
// strictly after now_wall (an alarm set to the current minute waits a day).
|
||||
// With alarm_repeat_mask == 0 that's just tomorrow's occurrence (one-shot).
|
||||
// With a repeat mask set, scan today..+6 days for the next weekday whose bit
|
||||
// is set (struct tm's tm_wday convention, same as the mask) -- today counts
|
||||
// only if its time hasn't already passed.
|
||||
// With alarm_repeat_mask == 0 that's just the next occurrence (one-shot).
|
||||
// With a repeat mask set, scan today..+7 days for the next weekday whose bit
|
||||
// is set (struct tm's tm_wday convention, same as the mask). Each day's time
|
||||
// is turned into UTC with that day's offset, so summer time starting or
|
||||
// ending in between doesn't move it.
|
||||
uint32_t computeAlarmNextFire(uint32_t now_wall) const {
|
||||
int tz = _prefs->tz_offset_hours;
|
||||
int64_t now_local = (int64_t)now_wall + (int64_t)tz * 3600;
|
||||
int64_t now_local = (int64_t)now_wall + (int64_t)tzHoursAt(_prefs, now_wall) * 3600;
|
||||
time_t t = (time_t)now_local;
|
||||
struct tm* ti = gmtime(&t);
|
||||
int64_t sod = ti->tm_hour * 3600 + ti->tm_min * 60 + ti->tm_sec; // secs since local midnight
|
||||
int64_t midnight = now_local - sod;
|
||||
int64_t time_of_day = (int64_t)_prefs->alarm_hour * 3600 + (int64_t)_prefs->alarm_min * 60;
|
||||
const int wday = ti->tm_wday;
|
||||
uint8_t mask = _prefs->alarm_repeat_mask;
|
||||
if (mask != 0) {
|
||||
for (int d = 0; d < 7; d++) {
|
||||
if (mask & (1 << ((ti->tm_wday + d) % 7))) {
|
||||
int64_t target = midnight + (int64_t)d * 86400 + time_of_day;
|
||||
if (target > now_local) return (uint32_t)(target - (int64_t)tz * 3600);
|
||||
}
|
||||
}
|
||||
// Mask had no bit set (shouldn't happen -- the UI only offers non-empty
|
||||
// presets) -- fall through to the one-shot calculation so it still fires.
|
||||
// A mask with no weekday bit (shouldn't happen -- the UI only offers
|
||||
// non-empty presets) falls back to one-shot so it still fires.
|
||||
if ((mask & 0x7F) == 0) mask = 0x7F;
|
||||
for (int d = 0; d <= 7; d++) {
|
||||
if (!(mask & (1 << ((wday + d) % 7)))) continue;
|
||||
int64_t u = wallToUtc(midnight + (int64_t)d * 86400 + time_of_day);
|
||||
if (u > (int64_t)now_wall) return (uint32_t)u;
|
||||
}
|
||||
int64_t target = midnight + time_of_day;
|
||||
if (target <= now_local) target += 86400;
|
||||
return (uint32_t)(target - (int64_t)tz * 3600);
|
||||
return (uint32_t)wallToUtc(midnight + 86400 + time_of_day); // not reached
|
||||
}
|
||||
|
||||
void evaluateAlarm() {
|
||||
@@ -101,9 +111,11 @@ private:
|
||||
_alarm_check_ms = now_ms;
|
||||
uint32_t now_wall = rtc_clock.getCurrentTime();
|
||||
if (now_wall < 1000000000UL) return; // need a real time sync first
|
||||
// A time zone change (Settings, CLI tz.offset, the app) moves the local
|
||||
// alarm time: reschedule here rather than trusting every caller to say so.
|
||||
if (_alarm_tz != _prefs->tz_offset_hours) { _alarm_tz = _prefs->tz_offset_hours; _alarm_next_fire = 0; }
|
||||
// A time zone change (Settings, CLI tz.offset, the app) or summer time
|
||||
// starting / ending moves the local alarm time: reschedule here rather
|
||||
// than trusting every caller to say so.
|
||||
const int8_t tz = tzHoursAt(_prefs, now_wall);
|
||||
if (_alarm_tz != tz) { _alarm_tz = tz; _alarm_next_fire = 0; }
|
||||
if (_alarm_next_fire == 0) _alarm_next_fire = computeAlarmNextFire(now_wall);
|
||||
if (now_wall < _alarm_next_fire) return;
|
||||
if (now_wall - _alarm_next_fire < ALARM_CATCHUP_SECS) {
|
||||
@@ -127,7 +139,7 @@ private:
|
||||
NodePrefs* _prefs = nullptr;
|
||||
UiEventQueue* _events = nullptr;
|
||||
uint32_t _alarm_next_fire = 0; // unix; 0 = (re)compute lazily once time is valid
|
||||
int8_t _alarm_tz = 0; // the tz_offset_hours _alarm_next_fire was computed with
|
||||
int8_t _alarm_tz = 0; // the offset (tzHoursAt) _alarm_next_fire was computed with
|
||||
uint32_t _alarm_check_ms = 0; // throttle the wall-clock read to ~2 Hz
|
||||
bool _timer_running = false;
|
||||
uint32_t _timer_deadline_ms = 0;
|
||||
|
||||
@@ -88,8 +88,9 @@ static void expand(const char* tmpl, char* out, int n, const NodePrefs* p) {
|
||||
}
|
||||
#endif
|
||||
float batt = (float)board.getBattMilliVolts() / 1000.0f;
|
||||
::expandMsg(tmpl, out, n, lat, lon, gps_valid, rtc_clock.getCurrentTime(),
|
||||
p ? p->tz_offset_hours : 0, &sensors, batt, nullptr, -1, board.getMCUTemperature());
|
||||
const uint32_t now = rtc_clock.getCurrentTime();
|
||||
::expandMsg(tmpl, out, n, lat, lon, gps_valid, now,
|
||||
tzHoursAt(p, now), &sensors, batt, nullptr, -1, board.getMCUTemperature());
|
||||
}
|
||||
|
||||
// A typed message: a reply's leading "@[nick] " stays as it is (a nick may
|
||||
|
||||
@@ -26,7 +26,7 @@ static const Key KEYS[] = {
|
||||
K("display.alert", msg_alert), K("lock", auto_lock), K("lock.clock", lock_compact),
|
||||
K("display.text.size", ui_text_scale), K("display.icon.size", ui_icon_scale),
|
||||
K("batt.display", batt_display_mode), K("batt.shutdown", low_batt_mv), K("gps.interval", gps_interval),
|
||||
K("clock.12h", clock_12h), K("clock.seconds", clock_hide_seconds), K("units.imperial", units_imperial),
|
||||
K("clock.12h", clock_12h), K("clock.summer", dst_rule), K("clock.seconds", clock_hide_seconds), K("units.imperial", units_imperial),
|
||||
K("sound.volume", buzzer_volume), K("quiet", quiet_hours), K("quiet.from", quiet_from), K("quiet.until", quiet_to),
|
||||
K("melody.dm", notif_melody_dm), K("melody.channel", notif_melody_ch), K("melody.advert", notif_melody_ad),
|
||||
K("advert.sound", advert_sound_scope),
|
||||
|
||||
@@ -157,6 +157,10 @@ static void optLockLook(uint8_t v, char* b, int n, const NodePrefs&) { snprintf(
|
||||
static void optExpiry(uint8_t v, char* b, int n, const NodePrefs&) {
|
||||
snprintf(b, n, "%s", NodePrefs::contactExpiryLabel(v));
|
||||
}
|
||||
static void optDst(uint8_t v, char* b, int n, const NodePrefs&) {
|
||||
static const char* L[NodePrefs::DST_COUNT] = { "Off", "Europe", "North America", "Australia", "New Zealand" };
|
||||
snprintf(b, n, "%s", L[v < NodePrefs::DST_COUNT ? v : 0]);
|
||||
}
|
||||
static void optChSort(uint8_t v, char* b, int n, const NodePrefs&) {
|
||||
static const char* L[NodePrefs::CH_SORT_COUNT] = { "Slots", "Newest", "Name" };
|
||||
snprintf(b, n, "%s", L[v < NodePrefs::CH_SORT_COUNT ? v : 0]);
|
||||
@@ -276,6 +280,7 @@ static const Setting ALL[] = {
|
||||
MAP("GPS power saving", "Sleep between fixes", SEC_POWER, gps_interval, GPS_DUTY, optGpsDuty, applyGpsDuty),
|
||||
|
||||
MAP("Time zone", "For the clock and the alarm", SEC_TIME, tz_offset_hours, TZ, optTz, nullptr),
|
||||
IDX("Summer time", "An hour ahead, changed by itself", SEC_TIME, dst_rule, NodePrefs::DST_COUNT, optDst, nullptr),
|
||||
SW("12-hour clock", "AM / PM instead of 24 h", SEC_TIME, clock_12h, nullptr),
|
||||
MAP("Clock seconds", "On the home clock", SEC_TIME, clock_hide_seconds, INVERTED, nullptr, nullptr),
|
||||
|
||||
|
||||
Reference in New Issue
Block a user