From 5564dcbde07e80e4de3ae4c63dd0f6edc2fe97f2 Mon Sep 17 00:00:00 2001 From: liquidraver <504870+liquidraver@users.noreply.github.com> Date: Tue, 9 Jun 2026 14:46:30 +0200 Subject: [PATCH] fix RTC and time display --- zephcore/adapters/clock/ZephyrRTCDiscover.c | 33 +++++++++++++-------- zephcore/helpers/ui-button/time_sync.c | 11 +++++++ zephcore/helpers/ui-button/ui_task.c | 21 +++++++++++++ 3 files changed, 52 insertions(+), 13 deletions(-) diff --git a/zephcore/adapters/clock/ZephyrRTCDiscover.c b/zephcore/adapters/clock/ZephyrRTCDiscover.c index a2909fe..db37305 100644 --- a/zephcore/adapters/clock/ZephyrRTCDiscover.c +++ b/zephcore/adapters/clock/ZephyrRTCDiscover.c @@ -124,27 +124,34 @@ static bool rtc_probe(uint32_t *epoch_out) continue; /* no ACK => chip absent */ } - /* Mask off flag/century bits, then require the block to be valid - * BCD. This is what proves a real RTC lives here (vs. an unrelated - * chip sharing the address) — only then is it safe to adopt and - * later WRITE to. A factory-fresh RTC reads a clean 2000-01-01, - * which is valid BCD (just stale), so it's still adopted. */ + /* Mask off flag/century bits. We trust this is a real RTC (vs. an + * unrelated chip sharing the address) if EITHER the block is valid + * BCD, OR the chip's power-loss flag is set — the latter is itself + * proof it's an RTC that lost power and whose time registers may be + * garbage. Adopting in the power-loss case is essential: otherwise a + * battery-depleted RTC (garbage registers, VL/OSF/PORF latched) would + * never become the write-back target, so a sync could never + * re-initialise it and the clock would stay blank forever. */ uint8_t sb = blk[0] & 0x7F, mb = blk[1] & 0x7F, hb = blk[2] & 0x3F; uint8_t db = blk[d->date_index] & 0x3F, ob = blk[5] & 0x1F, yb = blk[6]; - if (!bcd_field_ok(sb, 59) || !bcd_field_ok(mb, 59) || - !bcd_field_ok(hb, 23) || !bcd_field_ok(db, 31) || - !bcd_field_ok(ob, 12) || !bcd_field_ok(yb, 99) || - BCD2BIN(db) < 1 || BCD2BIN(ob) < 1) { - continue; /* not a real RTC at this address — do not adopt */ + bool bcd_ok = bcd_field_ok(sb, 59) && bcd_field_ok(mb, 59) && + bcd_field_ok(hb, 23) && bcd_field_ok(db, 31) && + bcd_field_ok(ob, 12) && bcd_field_ok(yb, 99) && + BCD2BIN(db) >= 1 && BCD2BIN(ob) >= 1; + bool unreliable = rtc_time_unreliable(d, blk); + + if (!bcd_ok && !unreliable) { + continue; /* neither valid time nor a lost-power RTC => skip */ } if (s_active == NULL) { - s_active = d; /* confirmed RTC => our write-back target */ + s_active = d; /* RTC => our write-back target */ } - if (rtc_time_unreliable(d, blk)) { - LOG_WRN("%s present but time flagged unreliable", d->name); + if (unreliable) { + LOG_WRN("%s present, power-loss flag set — clock will be set " + "on the next GPS/app/CLI sync", d->name); continue; } diff --git a/zephcore/helpers/ui-button/time_sync.c b/zephcore/helpers/ui-button/time_sync.c index 499180e..ecb35d0 100644 --- a/zephcore/helpers/ui-button/time_sync.c +++ b/zephcore/helpers/ui-button/time_sync.c @@ -9,6 +9,7 @@ */ #include +#include #include static enum time_sync_source s_last = TIME_SYNC_NONE; @@ -22,6 +23,16 @@ void time_sync_report(enum time_sync_source src) enum time_sync_source time_sync_get_source(void) { + /* Live GPS hold is authoritative and uses the SAME signal that blocks + * phone time-sync (gps_has_time_sync(), expires 2h after the last fix), + * so the tag can never disagree with that policy: while GPS holds the + * clock the tag is G regardless of the freshness window below. After a + * reboot the flag is reset, so this returns false and we fall through to + * the local/freshness logic (→ L until something re-syncs). */ + if (gps_has_time_sync()) { + return TIME_SYNC_GPS; + } + /* Local/manual (CLI) and "never synced" are both just local time. */ if (s_last == TIME_SYNC_NONE || s_last == TIME_SYNC_CLI) { return TIME_SYNC_CLI; diff --git a/zephcore/helpers/ui-button/ui_task.c b/zephcore/helpers/ui-button/ui_task.c index 17a42c0..7d41f05 100644 --- a/zephcore/helpers/ui-button/ui_task.c +++ b/zephcore/helpers/ui-button/ui_task.c @@ -25,6 +25,7 @@ #include "ui_task.h" #include "ui_pages.h" +#include #ifdef CONFIG_ZEPHCORE_UI_BUZZER #include "buzzer.h" @@ -909,8 +910,28 @@ void ui_set_battery(uint16_t mv, uint8_t pct) void ui_set_clock(uint32_t epoch) { struct ui_state *s = get_state(); + static enum time_sync_source last_src = TIME_SYNC_NONE; + + /* 1735689600 = 2025-01-01: the same "time is set" threshold the top-bar + * renderer uses. The clock only becomes visible once epoch crosses it. */ + bool was_valid = s->rtc_epoch > 1735689600; + bool now_valid = epoch > 1735689600; s->rtc_epoch = epoch; + + /* Repaint the EPD (which only redraws on demand) when the visible top-bar + * clock/source-tag changes, so the user never has to change pages to see + * an update. Both transitions are picked up on the next housekeeping tick: + * - clock first becomes valid → boot RTC restore / first sync + * - source tag changes → L→A (app), →G (GPS fix), A→L (12h expiry) + * Steady state triggers nothing (epoch valid + source unchanged). */ + enum time_sync_source src = time_sync_get_source(); + bool source_changed = (src != last_src); + last_src = src; + + if (now_valid && (!was_valid || source_changed)) { + schedule_render(); + } } void ui_add_recent(const char *name, int16_t rssi, uint32_t age_s)