mirror of
https://github.com/MarekZegare4/MeshCore-Solo.git
synced 2026-11-01 10:21:59 +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:
@@ -7,7 +7,7 @@ board supports are shown.
|
||||
|
||||
| Section | What's there |
|
||||
| ------- | ------------ |
|
||||
| **Display** | Brightness, auto-off, wake on message, the message alert (banner, corner envelope or off), lock screen, the lock clock's look and the [PIN](./lock.md), battery as icon / % / volts, time zone, clock format and seconds; on e-ink also rotation and full refresh, and on a 4.26" panel the text and icon size |
|
||||
| **Display** | Brightness, auto-off, wake on message, the message alert (banner, corner envelope or off), lock screen, the lock clock's look and the [PIN](./lock.md), battery as icon / % / volts, time zone and summer time, clock format and seconds; on e-ink also rotation and full refresh, and on a 4.26" panel the text and icon size |
|
||||
| **Sound** | Buzzer on / off / auto (quiet while the app is connected), volume, quiet hours, the melody for messages, channels and adverts |
|
||||
| **Home Pages** | Order of the home pages, and which are shown |
|
||||
| **Radio** | TX power, preset, frequency, SF / BW / CR, saved presets, Auto pwr, the scope list, then the advanced options (below) |
|
||||
@@ -26,6 +26,10 @@ A few notes:
|
||||
apart. The default one is used for direct messages and repeating, and a channel
|
||||
follows it unless it picks its own (see [Messages](./messages.md#channels)). Anyone who
|
||||
types the same name gets the same scope; it isn't encryption.
|
||||
- **Summer time** puts the clock an hour ahead and back again by itself, by
|
||||
the rule of your region: Europe (the last Sundays of March and October),
|
||||
North America, south-east Australia or New Zealand. The time zone stays
|
||||
the winter one, e.g. UTC+1 for Poland. The alarm and quiet hours follow it.
|
||||
- **Low battery** shuts the device down at the voltage you choose, which is
|
||||
also 0 % on the battery indicator.
|
||||
- **Batt curve** sets the cell voltage at 0, 10 … 100 % for a battery that
|
||||
@@ -124,7 +128,7 @@ so its documentation applies:
|
||||
| `agc.reset.interval` | Seconds between radio gain resets (in steps of 4, `0` off), with `get` / `set` |
|
||||
| `get name`, `set name …` | The device name |
|
||||
| `get pin`, `set pin 123456` | The Bluetooth PIN, `0` for a random one; used from the next start |
|
||||
| `get tz.offset`, `set tz.offset 2` | Time zone, in hours from UTC |
|
||||
| `get tz.offset`, `set tz.offset 1` | Time zone, in hours from UTC (the winter one when summer time is on) |
|
||||
| `get lat`, `set lat …`, `get lon`, `set lon …` | The fixed position |
|
||||
| `ver`, `board` | Firmware version, hardware |
|
||||
| `reboot`, `poweroff` | Restart or turn off |
|
||||
|
||||
+2
-2
@@ -18,8 +18,8 @@ The **Clock** home page shows the time with a bar under it that fills with
|
||||
the seconds (Settings › Display › Clock seconds), the date, and up to three
|
||||
fields you choose (hold Enter): battery, temperature, humidity, pressure, altitude
|
||||
(barometric or GPS), light, CO₂, GPS position, satellites, contacts, unread
|
||||
messages. The time comes from GPS or the phone app; the time zone is in
|
||||
Settings.
|
||||
messages. The time comes from GPS or the phone app; the time zone and summer
|
||||
time are in Settings.
|
||||
|
||||
**Enter** on the clock opens the clock tools:
|
||||
|
||||
|
||||
@@ -349,6 +349,7 @@ static void sanitize(NodePrefs& p) {
|
||||
flag(p.fav_sort, 1);
|
||||
flag(p.msg_wake, 1);
|
||||
if (p.contact_expiry_idx >= NodePrefs::CONTACT_EXPIRY_COUNT) p.contact_expiry_idx = 0;
|
||||
if (p.dst_rule >= NodePrefs::DST_COUNT) p.dst_rule = NodePrefs::DST_OFF;
|
||||
if (p.display_brightness_pct > 100) p.display_brightness_pct = 0;
|
||||
flag(p.quiet_hours, 0);
|
||||
if (p.quiet_from > 23) p.quiet_from = 22;
|
||||
|
||||
@@ -124,7 +124,8 @@ void MyMesh::botDmRecord(const uint8_t* pubkey) {
|
||||
// wraps past midnight.
|
||||
bool MyMesh::botInQuietHours() const {
|
||||
int h;
|
||||
if (!localHour(getRTCClock()->getCurrentTime(), _prefs.tz_offset_hours, h)) return false; // clock not set -- don't suppress
|
||||
const uint32_t now = getRTCClock()->getCurrentTime();
|
||||
if (!localHour(now, tzHoursAt(_prefs, now), h)) return false; // clock not set -- don't suppress
|
||||
return hourInWindow(h, _prefs.bot_quiet_start, _prefs.bot_quiet_end);
|
||||
}
|
||||
|
||||
@@ -188,7 +189,7 @@ void MyMesh::tryBotReplyDM(const ContactInfo& from, const char* text, uint8_t ho
|
||||
expandMsg(_prefs.bot_reply_dm, expanded, sizeof(expanded),
|
||||
sensors.node_lat, sensors.node_lon,
|
||||
sensors.node_lat != 0.0 || sensors.node_lon != 0.0,
|
||||
ts, _prefs.tz_offset_hours,
|
||||
ts, tzHoursAt(_prefs, ts),
|
||||
&sensors, (float)board.getBattMilliVolts() / 1000.0f,
|
||||
from.name, hops, board.getMCUTemperature(), &s_bot_in);
|
||||
uint32_t expected_ack, est_timeout;
|
||||
@@ -230,7 +231,7 @@ void MyMesh::tryBotReplyChannel(uint8_t channel_idx, const char* text, uint8_t h
|
||||
expandMsg(_prefs.bot_reply_ch, expanded, sizeof(expanded),
|
||||
sensors.node_lat, sensors.node_lon,
|
||||
sensors.node_lat != 0.0 || sensors.node_lon != 0.0,
|
||||
ts, _prefs.tz_offset_hours,
|
||||
ts, tzHoursAt(_prefs, ts),
|
||||
&sensors, (float)board.getBattMilliVolts() / 1000.0f,
|
||||
sender_name, hops, board.getMCUTemperature(), &s_bot_in);
|
||||
// Anti-loop: don't echo a message identical to our own reply (e.g. another bot
|
||||
@@ -275,7 +276,7 @@ void MyMesh::tryBotReplyRoom(const ContactInfo& from, const uint8_t* sender_pref
|
||||
expandMsg(_prefs.bot_reply_room, expanded, sizeof(expanded),
|
||||
sensors.node_lat, sensors.node_lon,
|
||||
sensors.node_lat != 0.0 || sensors.node_lon != 0.0,
|
||||
ts, _prefs.tz_offset_hours,
|
||||
ts, tzHoursAt(_prefs, ts),
|
||||
&sensors, (float)board.getBattMilliVolts() / 1000.0f,
|
||||
sender_name, hops, board.getMCUTemperature(), &s_bot_in);
|
||||
// Anti-loop, mirrors the channel path: don't echo a message identical to
|
||||
@@ -437,7 +438,7 @@ bool MyMesh::botCommandReply(const char* cmd, const char* arg, const char* arg2,
|
||||
expandMsg(tmpl, out, out_len,
|
||||
sensors.node_lat, sensors.node_lon,
|
||||
sensors.node_lat != 0.0 || sensors.node_lon != 0.0,
|
||||
ts, _prefs.tz_offset_hours,
|
||||
ts, tzHoursAt(_prefs, ts),
|
||||
&sensors, (float)board.getBattMilliVolts() / 1000.0f,
|
||||
sender_name, hops, board.getMCUTemperature(), &s_bot_in);
|
||||
if (strchr(out, '{')) strcpy(out, "n/a"); // requested sensor not present
|
||||
|
||||
@@ -268,6 +268,11 @@ struct NodePrefsFields {
|
||||
// ── Clock & alarm ─────────────────────────────────────────────────────
|
||||
uint8_t clock_hide_seconds; // 0=show HH:MM:SS/refresh 1s (default), 1=hide/refresh 60s
|
||||
uint8_t clock_12h; // 0=24h (default), 1=12h with AM/PM
|
||||
// Summer time: the rule that puts the clock an hour ahead for part of the
|
||||
// year, on top of tz_offset_hours (then the standard, winter offset). Every
|
||||
// local time goes through tzHoursAt() below.
|
||||
enum : uint8_t { DST_OFF, DST_EU, DST_US, DST_AU, DST_NZ, DST_COUNT };
|
||||
uint8_t dst_rule;
|
||||
// Alarm clock — a wake alarm, configured from the Clock page (Enter › Alarm).
|
||||
// Stored as a local time-of-day; the actual fire instant is (re)computed as an
|
||||
// absolute time in tickBackground(), which is what makes it robust to RTC
|
||||
@@ -830,7 +835,7 @@ class NodePrefs : public NodePrefsFields, public ConfigSerializer {
|
||||
s.def("kb_main", keyboard_main_alphabet); s.def("kb_cardkb", keyboard_cardkb_compact);
|
||||
}
|
||||
void clockSolo(Section& s) {
|
||||
s.def("hide_sec", clock_hide_seconds); s.def("h12", clock_12h);
|
||||
s.def("hide_sec", clock_hide_seconds); s.def("h12", clock_12h); s.def("dst", dst_rule);
|
||||
s.def("alarm", alarm_on); s.def("alarm_h", alarm_hour); s.def("alarm_m", alarm_min);
|
||||
s.def("alarm_rep", alarm_repeat_mask);
|
||||
}
|
||||
@@ -996,6 +1001,48 @@ static inline bool hourInWindow(int h, int start, int end) {
|
||||
if (start == end) return false;
|
||||
return start < end ? (h >= start && h < end) : (h >= start || h < end);
|
||||
}
|
||||
// Days since 1970-01-01 of a calendar date (H. Hinnant's days_from_civil).
|
||||
static inline int32_t daysFromCivil(int y, int m, int d) {
|
||||
y -= m <= 2;
|
||||
const int era = (y >= 0 ? y : y - 399) / 400;
|
||||
const int yoe = y - era * 400;
|
||||
const int doy = (153 * (m + (m > 2 ? -3 : 9)) + 2) / 5 + d - 1;
|
||||
return era * 146097 + yoe * 365 + yoe / 4 - yoe / 100 + doy - 719468;
|
||||
}
|
||||
// The day (since 1970) of the n-th Sunday of a month, n = 1..4, 5 = the last.
|
||||
static inline int32_t nthSunday(int y, int m, int n) {
|
||||
const int32_t first = daysFromCivil(y, m, 1);
|
||||
int32_t d = first + (3 - first % 7 + 7) % 7 + (n - 1) * 7; // 1970-01-01 was a Thursday
|
||||
if (n == 5 && d >= daysFromCivil(m == 12 ? y + 1 : y, m == 12 ? 1 : m + 1, 1)) d -= 7;
|
||||
return d;
|
||||
}
|
||||
// Summer time in force at `utc`. The changes, per NodePrefs::dst_rule: Europe
|
||||
// (EU / UK) last Sunday of March - last Sunday of October at 01:00 UTC; North
|
||||
// America second Sunday of March - first Sunday of November; south-east
|
||||
// Australia first Sunday of October - first Sunday of April; New Zealand last
|
||||
// Sunday of September - first Sunday of April; those three forward at 02:00,
|
||||
// back at 02:00 summer time in America and at 03:00 in the other two.
|
||||
static inline bool inSummerTime(const NodePrefs& p, uint32_t utc) {
|
||||
// Hours in local standard time, or UTC with `utc` set.
|
||||
struct Rule { uint8_t on_mon, on_nth, on_hour, off_mon, off_nth, off_hour, utc; };
|
||||
static const Rule RULES[NodePrefs::DST_COUNT - 1] = {
|
||||
{ 3, 5, 1, 10, 5, 1, 1 }, { 3, 2, 2, 11, 1, 1, 0 }, { 10, 1, 2, 4, 1, 2, 0 }, { 9, 5, 2, 4, 1, 2, 0 },
|
||||
};
|
||||
if (p.dst_rule == NodePrefs::DST_OFF || p.dst_rule >= NodePrefs::DST_COUNT || utc < 1000000000UL) return false;
|
||||
const Rule& r = RULES[p.dst_rule - 1];
|
||||
const int64_t std_off = r.utc ? 0 : (int64_t)p.tz_offset_hours * 3600;
|
||||
time_t lt = (time_t)((int64_t)utc + (int64_t)p.tz_offset_hours * 3600);
|
||||
const int y = gmtime(<)->tm_year + 1900;
|
||||
const int64_t on = (int64_t)nthSunday(y, r.on_mon, r.on_nth) * 86400 + r.on_hour * 3600 - std_off;
|
||||
const int64_t off = (int64_t)nthSunday(y, r.off_mon, r.off_nth) * 86400 + r.off_hour * 3600 - std_off;
|
||||
const int64_t t = utc;
|
||||
return on < off ? (t >= on && t < off) : (t >= on || t < off); // southern summers span New Year
|
||||
}
|
||||
// The offset from UTC in force at `utc`, in hours: the time zone plus summer time.
|
||||
static inline int8_t tzHoursAt(const NodePrefs& p, uint32_t utc) {
|
||||
return (int8_t)(p.tz_offset_hours + (inSummerTime(p, utc) ? 1 : 0));
|
||||
}
|
||||
static inline int8_t tzHoursAt(const NodePrefs* p, uint32_t utc) { return p ? tzHoursAt(*p, utc) : 0; }
|
||||
// The local hour from a UTC time; false while the clock isn't set.
|
||||
static inline bool localHour(uint32_t utc, int8_t tz_hours, int& h) {
|
||||
if (utc < 1000000000UL) return false;
|
||||
@@ -1012,7 +1059,7 @@ static inline bool localTm(uint32_t utc, int8_t tz_hours, struct tm& out) {
|
||||
// Settings > Sound > Quiet hours in force at `utc` (never with the clock unset).
|
||||
static inline bool inQuietHours(const NodePrefs& p, uint32_t utc) {
|
||||
int h;
|
||||
return p.quiet_hours && localHour(utc, p.tz_offset_hours, h) && hourInWindow(h, p.quiet_from, p.quiet_to);
|
||||
return p.quiet_hours && localHour(utc, tzHoursAt(p, utc), h) && hourInWindow(h, p.quiet_from, p.quiet_to);
|
||||
}
|
||||
|
||||
// Bounds for a usable repeater radio profile. The frequency range is passed 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),
|
||||
|
||||
|
||||
@@ -100,7 +100,7 @@ void UITask::buildBot() {
|
||||
if (log.count() == 0) label(g, "No replies yet", THEME_FONT_BODY, theme::TEXT_MUTED);
|
||||
for (int i = 0; i < log.count(); i++) {
|
||||
const BotLogEntry& e = log.at(i);
|
||||
const uint32_t t = e.ts + (int32_t)p->tz_offset_hours * 3600;
|
||||
const uint32_t t = e.ts + (int32_t)tzHoursAt(p, e.ts) * 3600;
|
||||
static const char* const WHERE[] = { "DM", "channel", "room" };
|
||||
char hd[64];
|
||||
snprintf(hd, sizeof(hd), "%02u:%02u %s (%s)", (unsigned)(t / 3600 % 24), (unsigned)(t / 60 % 60),
|
||||
|
||||
@@ -1378,7 +1378,7 @@ void UITask::flowHistoryPopup() {
|
||||
const bool named = f.node[0] >= 0 && the_mesh.getContactByIdx(f.node[0], c) && c.name[0];
|
||||
snprintf(title, sizeof(title), "%s%s%s%.24s", FLOW_KINDS[h.kind < PacketFlow::K_COUNT ? h.kind : PacketFlow::K_OTHER],
|
||||
named ? " " : "", named && !f.origin ? "via " : "", named ? c.name : "");
|
||||
const uint32_t t = h.ts + (int32_t)_prefs->tz_offset_hours * 3600;
|
||||
const uint32_t t = h.ts + (int32_t)tzHoursAt(_prefs, h.ts) * 3600;
|
||||
int o = snprintf(sub, sizeof(sub), "%02u:%02u:%02u ", (unsigned)(t / 3600 % 24), (unsigned)(t / 60 % 60), (unsigned)(t % 60));
|
||||
if (h.hops == 0) o += snprintf(sub + o, sizeof(sub) - o, "direct");
|
||||
else o += snprintf(sub + o, sizeof(sub) - o, "%u hop%s", (unsigned)h.hops, h.hops == 1 ? "" : "s");
|
||||
|
||||
@@ -632,8 +632,8 @@ void UITask::settingsGroup(int i) {
|
||||
g = group(body, "SYSTEM");
|
||||
listRow(g, LV_SYMBOL_EDIT " Name", the_mesh.getNodeName(), onNodeName, NULL);
|
||||
if (_prefs) {
|
||||
snprintf(sub, sizeof(sub), "%s, %s", settingText(*_prefs, SETTING(tz_offset_hours), v1, sizeof(v1)),
|
||||
_prefs->clock_12h ? "12 h" : "24 h");
|
||||
snprintf(sub, sizeof(sub), "%s%s, %s", settingText(*_prefs, SETTING(tz_offset_hours), v1, sizeof(v1)),
|
||||
inSummerTime(*_prefs, rtc_clock.getCurrentTime()) ? " summer" : "", _prefs->clock_12h ? "12 h" : "24 h");
|
||||
listRow(g, UI_SYMBOL_CLOCK " Time", sub, onOpenSchemaPage, (void*)(uintptr_t)PG_TIME);
|
||||
schemaRow(g, SETTING(units_imperial));
|
||||
}
|
||||
|
||||
@@ -1166,9 +1166,9 @@ static void tileBadge(lv_obj_t* tile, int n) {
|
||||
lv_obj_align(lv_obj_get_child(tile, -1), LV_ALIGN_TOP_RIGHT, -2, 2);
|
||||
}
|
||||
|
||||
// Local time (NodePrefs::tz_offset_hours), now or at `utc`; false before the clock is set.
|
||||
// Local time (time zone + summer time), now or at `utc`; false before the clock is set.
|
||||
static bool localTime(const NodePrefs* p, struct tm& out, uint32_t utc = rtc_clock.getCurrentTime()) {
|
||||
return localTm(utc, p ? p->tz_offset_hours : 0, out);
|
||||
return localTm(utc, tzHoursAt(p, utc), out);
|
||||
}
|
||||
static const char* const MONTHS[] = { "Jan", "Feb", "Mar", "Apr", "May", "Jun",
|
||||
"Jul", "Aug", "Sep", "Oct", "Nov", "Dec" };
|
||||
|
||||
@@ -153,12 +153,11 @@ public:
|
||||
display.drawTextCentered(display.width() / 2, display.height() / 2, "No replies yet");
|
||||
return 2000;
|
||||
}
|
||||
const int8_t tz = _prefs ? _prefs->tz_offset_hours : 0;
|
||||
int mq_delay = 0;
|
||||
drawList(display, log.count(), _sel, _scroll, [&](int i, int y, bool sel, int reserve) {
|
||||
drawRowSelection(display, y, sel, reserve);
|
||||
char line[200];
|
||||
botcfg::logLine(log.at(i), tz, line, sizeof(line));
|
||||
botcfg::logLine(log.at(i), tzHoursAt(_prefs, log.at(i).ts), line, sizeof(line));
|
||||
char filt[200];
|
||||
display.translateUTF8ToBlocks(filt, line, sizeof(filt));
|
||||
int r = display.drawTextEllipsized(2, y, display.width() - 2 - reserve, filt, sel);
|
||||
|
||||
@@ -263,7 +263,7 @@ class ClockToolsScreen : public UIScreen {
|
||||
// screen), the grid goes tight and the month and year stand beside it.
|
||||
int renderCalendar(DisplayDriver& d) {
|
||||
struct tm t;
|
||||
if (!localTm(rtc_clock.getCurrentTime(), _prefs ? _prefs->tz_offset_hours : 0, t) || t.tm_year < 120) {
|
||||
if (!localTm(rtc_clock.getCurrentTime(), tzHoursAt(_prefs, rtc_clock.getCurrentTime()), t) || t.tm_year < 120) {
|
||||
drawScreenHeader(d, "Calendar");
|
||||
d.drawTextCentered(d.width() / 2, d.height() / 2 - d.getLineHeight() / 2, "Clock not set");
|
||||
return 60000;
|
||||
|
||||
@@ -1002,7 +1002,7 @@ public:
|
||||
|
||||
void drawClockPage(DisplayDriver& d, uint32_t unix_ts) {
|
||||
struct tm ti;
|
||||
localTm(unix_ts, _node_prefs ? _node_prefs->tz_offset_hours : 0, ti);
|
||||
localTm(unix_ts, tzHoursAt(_node_prefs, unix_ts), ti);
|
||||
const bool h12 = _node_prefs && _node_prefs->clock_12h;
|
||||
const bool show_sec = !Features::IS_EINK && (!_node_prefs || !_node_prefs->clock_hide_seconds);
|
||||
const int W = d.width(), H = d.height();
|
||||
@@ -1086,7 +1086,7 @@ public:
|
||||
const uint32_t unix_ts = _rtc->getCurrentTime();
|
||||
const bool synced = unix_ts >= 1000000000UL;
|
||||
struct tm ti;
|
||||
if (synced) localTm(unix_ts, _node_prefs ? _node_prefs->tz_offset_hours : 0, ti);
|
||||
if (synced) localTm(unix_ts, tzHoursAt(_node_prefs, unix_ts), ti);
|
||||
const bool h12 = _node_prefs && _node_prefs->clock_12h;
|
||||
const bool compact = _node_prefs && _node_prefs->lock_compact && W > H;
|
||||
// The month and the rows under the big clock: on a tall screen (portrait
|
||||
@@ -1338,7 +1338,7 @@ public:
|
||||
uint32_t now_ts = _rtc->getCurrentTime();
|
||||
if (now_ts >= 1000000000UL) {
|
||||
struct tm lt;
|
||||
localTm(now_ts, _node_prefs->tz_offset_hours, lt);
|
||||
localTm(now_ts, tzHoursAt(_node_prefs, now_ts), lt);
|
||||
int hh = lt.tm_hour;
|
||||
if (_node_prefs->clock_12h) { hh %= 12; if (hh == 0) hh = 12; }
|
||||
snprintf(filtered_name, sizeof(filtered_name), "%d:%02d", hh, lt.tm_min);
|
||||
|
||||
@@ -632,14 +632,13 @@ private:
|
||||
_list_max_scroll = max_scroll;
|
||||
|
||||
NodePrefs* p = _task->getNodePrefs();
|
||||
int32_t tz_off = (int32_t)(p ? p->tz_offset_hours : 0) * 3600;
|
||||
|
||||
for (int i = 0; i < visible; i++) {
|
||||
int idx = total - 1 - (_list_scroll + i);
|
||||
if (idx < 0) break;
|
||||
const TrailPoint& pt = _store->at(idx);
|
||||
|
||||
time_t lt = (time_t)((int32_t)pt.ts + tz_off);
|
||||
time_t lt = (time_t)((int32_t)pt.ts + (int32_t)tzHoursAt(p, pt.ts) * 3600);
|
||||
struct tm* ti = gmtime(<);
|
||||
|
||||
char dist[12];
|
||||
|
||||
Reference in New Issue
Block a user