wire up RTC to out-of-the-box capable nodes

This commit is contained in:
liquidraver
2026-06-07 12:00:05 +02:00
parent 9ef6774d6a
commit 01fd56a573
16 changed files with 474 additions and 0 deletions
+6
View File
@@ -444,6 +444,12 @@ target_sources(app PRIVATE
helpers/fatal_reboot.c
)
# Boot-time hardware-RTC auto-discovery (compact raw-I2C). Always compiled so
# the zephcore_rtc_* symbols exist on every board; it self-stubs internally
# when CONFIG_ZEPHCORE_RTC_AUTODISCOVER is off or no zephcore,rtc-i2c node is
# present in DT.
target_sources(app PRIVATE adapters/clock/ZephyrRTCDiscover.c)
# ========== Battery Curve Selection ==========
# helpers/battery_curve.c is always compiled — it provides battery_curve_lookup()
# and the weak battery_curve_default. A board-specific battery_curve.c is compiled
+14
View File
@@ -523,6 +523,20 @@ config ZEPHCORE_APC
endmenu
config ZEPHCORE_RTC_AUTODISCOVER
bool "Auto-discover a hardware I2C RTC at boot"
default y
depends on I2C
help
Probe I2C RTC chips declared with the "zephcore,rtc-i2c" binding
(DS3231 / PCF8563 / RV3028 / RX8130CE). Boards with a battery/cap-
backed RTC opt in by including boards/common/rtc-i2c.dtsi; if a chip
holds a valid time it is restored at boot — shown tagged "L" until the
next external sync. On every GPS/app/CLI sync the time is written back
so it survives power-off. Compact raw-I2C reader (no Zephyr RTC
subsystem). Safe to leave on: a board with no rtc-i2c.dtsi include has
no RTC nodes and compiles to nothing.
menu "GPS Configuration"
config ZEPHCORE_GPS_POLL_INTERVAL_SEC
+243
View File
@@ -0,0 +1,243 @@
/*
* SPDX-License-Identifier: Apache-2.0
*
* Compact raw-I2C hardware-RTC auto-discovery. See ZephyrRTCDiscover.h.
*
* Register layouts (sec/min/hour/.../month/year, all BCD) and the per-chip
* power-loss flags are carried in devicetree via the "zephcore,rtc-i2c"
* binding, so this reader is generic — adding a new chip is a DT node, not
* code. Maps were taken from Zephyr's own drivers (rtc_pcf8563.c,
* rtc_ds3231.c, rtc_rv3028.c, rtc_rx8130ce.c).
*/
#include "ZephyrRTCDiscover.h"
#include <zephyr/kernel.h>
#include <zephyr/device.h>
#include <zephyr/drivers/i2c.h>
#include <zephyr/logging/log.h>
LOG_MODULE_REGISTER(zephcore_rtc, CONFIG_ZEPHCORE_DATASTORE_LOG_LEVEL);
#define RTC_COMPAT zephcore_rtc_i2c
#if IS_ENABLED(CONFIG_ZEPHCORE_RTC_AUTODISCOVER) && DT_HAS_COMPAT_STATUS_OKAY(RTC_COMPAT)
/* Validity flag lives in the seconds byte itself (PCF8563 VL bit). */
#define RTC_STATUS_IN_SECONDS 0xFF
struct rtc_desc {
const struct device *bus;
uint16_t addr;
uint8_t time_reg; /* register of the seconds byte */
uint8_t date_index; /* day-of-month offset in the 7-byte block */
uint8_t status_reg; /* power-loss flag register, or RTC_STATUS_IN_SECONDS */
uint8_t status_mask; /* "time unreliable" bit within status_reg */
const char *name;
};
#define RTC_DESC_ENTRY(node) \
{ \
.bus = DEVICE_DT_GET(DT_BUS(node)), \
.addr = (uint16_t)DT_REG_ADDR(node), \
.time_reg = (uint8_t)DT_PROP(node, time_reg), \
.date_index = (uint8_t)DT_PROP(node, date_index), \
.status_reg = (uint8_t)DT_PROP(node, status_reg), \
.status_mask = (uint8_t)DT_PROP(node, status_mask), \
.name = DT_NODE_FULL_NAME(node), \
},
static const struct rtc_desc rtc_descs[] = {
DT_FOREACH_STATUS_OKAY(RTC_COMPAT, RTC_DESC_ENTRY)
};
/* Chip we'll read/write going forward (first one found present). */
static const struct rtc_desc *s_active;
static bool s_probed;
#define BCD2BIN(x) ((((x) >> 4) & 0x0F) * 10 + ((x) & 0x0F))
#define BIN2BCD(x) ((((x) / 10) << 4) | ((x) % 10))
/* A byte is valid BCD if both nibbles are 0-9, and its decoded value fits the
* field. Used to tell a real RTC apart from an unrelated I2C chip that happens
* to share an address (e.g. an MPU-class IMU at 0x68, same as DS3231) — we must
* never adopt and write time into such a device. */
static bool bcd_field_ok(uint8_t v, unsigned max)
{
if ((v & 0x0F) > 9 || (v >> 4) > 9) {
return false;
}
return BCD2BIN(v) <= max;
}
/* Howard Hinnant's civil<->days algorithms (proleptic Gregorian, UTC). */
static int64_t days_from_civil(int y, unsigned m, unsigned d)
{
y -= (m <= 2);
int64_t era = (y >= 0 ? y : y - 399) / 400;
unsigned yoe = (unsigned)(y - era * 400);
unsigned doy = (153 * (m + (m > 2 ? -3 : 9)) + 2) / 5 + d - 1;
unsigned doe = yoe * 365 + yoe / 4 - yoe / 100 + doy;
return era * 146097 + (int)doe - 719468;
}
static void civil_from_days(int64_t z, int *y, unsigned *m, unsigned *d)
{
z += 719468;
int64_t era = (z >= 0 ? z : z - 146096) / 146097;
unsigned doe = (unsigned)(z - era * 146097);
unsigned yoe = (doe - doe / 1460 + doe / 36524 - doe / 146096) / 365;
int yy = (int)yoe + (int)(era * 400);
unsigned doy = doe - (365 * yoe + yoe / 4 - yoe / 100);
unsigned mp = (5 * doy + 2) / 153;
*d = doy - (153 * mp + 2) / 5 + 1;
*m = mp < 10 ? mp + 3 : mp - 9;
*y = yy + (*m <= 2);
}
/* Read the chip's power-loss flag. true => held time is unreliable. */
static bool rtc_time_unreliable(const struct rtc_desc *d, const uint8_t blk[7])
{
if (d->status_reg == RTC_STATUS_IN_SECONDS) {
return (blk[0] & d->status_mask) != 0;
}
uint8_t st;
if (i2c_reg_read_byte(d->bus, d->addr, d->status_reg, &st) != 0) {
return true; /* can't confirm => don't trust it */
}
return (st & d->status_mask) != 0;
}
/* Probe all chips once; cache the first present one in s_active. If a present
* chip holds a sane time, return it via epoch_out. */
static bool rtc_probe(uint32_t *epoch_out)
{
for (size_t i = 0; i < ARRAY_SIZE(rtc_descs); i++) {
const struct rtc_desc *d = &rtc_descs[i];
uint8_t blk[7];
if (!device_is_ready(d->bus)) {
continue;
}
if (i2c_burst_read(d->bus, d->addr, d->time_reg, blk, sizeof(blk)) != 0) {
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. */
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 */
}
if (s_active == NULL) {
s_active = d; /* confirmed RTC => our write-back target */
}
if (rtc_time_unreliable(d, blk)) {
LOG_WRN("%s present but time flagged unreliable", d->name);
continue;
}
unsigned sec = BCD2BIN(sb);
unsigned min = BCD2BIN(mb);
unsigned hour = BCD2BIN(hb);
unsigned day = BCD2BIN(db);
unsigned month = BCD2BIN(ob);
unsigned year = 2000 + BCD2BIN(yb);
if (year < 2025) {
LOG_INF("%s present, time not yet set (%04u-%02u-%02u)",
d->name, year, month, day);
continue; /* RTC adopted for write-back, but no valid time */
}
int64_t e = days_from_civil((int)year, month, day) * 86400LL +
hour * 3600 + min * 60 + sec;
if (epoch_out) {
*epoch_out = (uint32_t)e;
}
LOG_INF("RTC %s: restored %04u-%02u-%02u %02u:%02u:%02u UTC",
d->name, year, month, day, hour, min, sec);
return true;
}
return false;
}
bool zephcore_rtc_restore(uint32_t *epoch_out)
{
s_probed = true;
return rtc_probe(epoch_out);
}
void zephcore_rtc_save(uint32_t epoch)
{
if (!s_probed) {
/* Restore wasn't run (unexpected) — discover now. */
(void)rtc_probe(NULL);
s_probed = true;
}
if (s_active == NULL) {
return;
}
const struct rtc_desc *d = s_active;
int y;
unsigned m, day;
civil_from_days((int64_t)epoch / 86400, &y, &m, &day);
unsigned rem = epoch % 86400;
unsigned hour = rem / 3600;
unsigned min = (rem % 3600) / 60;
unsigned sec = rem % 60;
unsigned dow = (unsigned)(((epoch / 86400) + 4) % 7); /* 1970-01-01 = Thu */
uint8_t blk[7];
blk[0] = BIN2BCD(sec);
blk[1] = BIN2BCD(min);
blk[2] = BIN2BCD(hour);
/* weekday occupies whichever of index 3/4 the date doesn't. */
blk[d->date_index] = BIN2BCD(day);
blk[d->date_index == 4 ? 3 : 4] = (uint8_t)dow;
blk[5] = BIN2BCD(m);
blk[6] = BIN2BCD((unsigned)(y % 100));
if (i2c_burst_write(d->bus, d->addr, d->time_reg, blk, sizeof(blk)) != 0) {
LOG_WRN("RTC %s: time write failed", d->name);
return;
}
/* Clear the power-loss flag (for chips whose flag is a separate reg;
* the seconds-bit chips clear it implicitly when we wrote sec above). */
if (d->status_reg != RTC_STATUS_IN_SECONDS) {
uint8_t st;
if (i2c_reg_read_byte(d->bus, d->addr, d->status_reg, &st) == 0) {
(void)i2c_reg_write_byte(d->bus, d->addr, d->status_reg,
st & (uint8_t)~d->status_mask);
}
}
LOG_DBG("RTC %s: persisted time", d->name);
}
#else /* no zephcore,rtc-i2c node in DT — link-compatible stubs */
bool zephcore_rtc_restore(uint32_t *epoch_out)
{
ARG_UNUSED(epoch_out);
return false;
}
void zephcore_rtc_save(uint32_t epoch)
{
ARG_UNUSED(epoch);
}
#endif /* DT_HAS_COMPAT_STATUS_OKAY(zephcore_rtc_i2c) */
@@ -0,0 +1,43 @@
/*
* SPDX-License-Identifier: Apache-2.0
*
* Boot-time hardware-RTC auto-discovery (compact raw-I2C reader).
*
* Probes every I2C RTC chip declared with the "zephcore,rtc-i2c" binding
* (boards/common/rtc-i2c.dtsi, opt-in per board). Chips that aren't physically
* present fail the probe and are skipped — like the environment sensors.
*
* If a present chip holds a valid time, zephcore_rtc_restore() returns it so
* the soft clock can be seeded at boot (shown tagged "L" — local). On every
* authoritative sync (GPS/app/CLI) the caller writes it back via
* zephcore_rtc_save() so time survives the next power-off.
*/
#pragma once
#include <stdbool.h>
#include <stdint.h>
#ifdef __cplusplus
extern "C" {
#endif
/*
* Probe all declared RTC chips. If one is present and holds a sane time
* (year >= 2025 and its power-loss flag is clear), store the Unix epoch in
* *epoch_out and return true. The present chip (valid time or not) is
* remembered as the write-back target. Returns false if none present or no
* trustworthy time is held.
*/
bool zephcore_rtc_restore(uint32_t *epoch_out);
/*
* Persist an authoritative epoch to the discovered RTC chip and clear its
* power-loss flag. No-op if no RTC was discovered. Safe to call often, but
* intended only for real syncs (GPS/app/CLI), not per-packet clock nudges.
*/
void zephcore_rtc_save(uint32_t epoch);
#ifdef __cplusplus
}
#endif
+2
View File
@@ -17,6 +17,7 @@
#include <adapters/ble/ZephyrBLE.h>
#include <adapters/gps/ZephyrGPSManager.h>
#include <helpers/time_sync.h>
#include <adapters/clock/ZephyrRTCDiscover.h>
#if IS_ENABLED(CONFIG_ZEPHCORE_UI_DESIGN_BUTTON) || IS_ENABLED(CONFIG_ZEPHCORE_UI_DESIGN_JOYSTICK)
#include <ui_task.h>
#define ZEPHCORE_HAS_UI_TASK 1
@@ -2208,6 +2209,7 @@ bool CompanionMesh::handleProtocolFrame(const uint8_t *data, size_t len)
if (secs >= curr) {
getRTCClock()->setCurrentTime(secs);
time_sync_report(TIME_SYNC_APP);
zephcore_rtc_save(secs); /* persist to hardware RTC */
sendPacketOk();
} else {
sendPacketError(ERR_ILLEGAL_ARG);
+69
View File
@@ -0,0 +1,69 @@
/*
* Common I2C Real-Time-Clock descriptors for ZephCore
* SPDX-License-Identifier: Apache-2.0
*
* OPT-IN: include this inside an &i2cN { ... } block ONLY on boards that
* physically have a battery/cap-backed RTC. Kept separate from
* sensors-i2c.dtsi because these declare fixed I2C addresses (0x68/0x51/
* 0x52/0x32) that collide with common parts an IMU/accelerometer sits at
* 0x68 (same as DS3231). Including this on a board without an RTC would risk
* a devicetree unit-address clash and a runtime mis-probe, so it is opt-in.
*
* Usage (board overlay/dts, alongside the sensors include):
* &i2c0 {
* #include "../../common/sensors-i2c.dtsi"
* #include "../../common/rtc-i2c.dtsi"
* };
*
* These bind NO Zephyr RTC driver they're data-only descriptors the
* boot-time auto-discovery (adapters/clock/ZephyrRTCDiscover.c) probes via
* raw I2C to restore wall-clock time at boot. A chip that isn't actually
* present fails the probe and is skipped. One chip per I2C address:
*
* Chip Addr Also covers (same regs)
*
* DS3231 0x68 DS1307, DS3232
* PCF8563 0x51 (PCF85063A is 0x51 too but differs override)
* RV3028 0x52
* RX8130CE 0x32 (RV8803 is 0x32 too but differs override)
*
* status-reg 0xFF means the power-loss flag is in the seconds byte. A board
* with a same-address variant should /delete-node/ the conflicting one and
* add its own in the board overlay.
*/
rtc_ds3231: rtc-ds3231@68 {
compatible = "zephcore,rtc-i2c";
reg = <0x68>;
time-reg = <0x00>;
date-index = <4>;
status-reg = <0x0F> /* control/status */
status-mask = <0x80> /* OSF — oscillator stopped */
};
rtc_pcf8563: rtc-pcf8563@51 {
compatible = "zephcore,rtc-i2c";
reg = <0x51>;
time-reg = <0x02>;
date-index = <3>; /* PCF8563 puts date before weekday */
status-reg = <0xFF> /* flag is in the seconds byte */
status-mask = <0x80> /* VL — clock integrity not guaranteed */
};
rtc_rv3028: rtc-rv3028@52 {
compatible = "zephcore,rtc-i2c";
reg = <0x52>;
time-reg = <0x00>;
date-index = <4>;
status-reg = <0x0e> /* status */
status-mask = <0x01> /* PORF — power-on reset */
};
rtc_rx8130ce: rtc-rx8130ce@32 {
compatible = "zephcore,rtc-i2c";
reg = <0x32>;
time-reg = <0x10>;
date-index = <4>;
status-reg = <0x1d> /* flag */
status-mask = <0x02> /* VLF — voltage low */
};
+5
View File
@@ -103,3 +103,8 @@ ina219: ina219@40 {
shunt-milliohm = <100>;
lsb-microamp = <10>;
};
/* Real-time clocks are NOT here they live in the opt-in "rtc-i2c.dtsi"
* (included only by boards that physically have a battery-backed RTC), so
* their fixed addresses (0x68/0x51/0x52/0x32) can't collide with an IMU or
* other peripheral on boards that don't. */
@@ -299,6 +299,9 @@
* devices that aren't populated. T-Echo factory sensor is BME280 at
* the alt address 0x77, added separately below. */
#include "../../common/sensors-i2c.dtsi"
/* T-Echo has an onboard PCF8563 RTC opt in to boot-time time restore
* (auto-detected at runtime among the supported chips). */
#include "../../common/rtc-i2c.dtsi"
};
/* T-Echo BME280 sits at I2C 0x77 (alt address), conflicting with the
@@ -287,6 +287,9 @@
/* All supported environment & power sensors — auto-detected at runtime */
#include "../../common/sensors-i2c.dtsi"
/* ThinkNode M1 has a battery-backed hardware RTC opt in to boot-time
* time restore (auto-detected at runtime among the supported chips). */
#include "../../common/rtc-i2c.dtsi"
};
/* ---- SPI2 for LoRa SX1262 ---- */
@@ -208,6 +208,9 @@
/* All supported environment & power sensors — auto-detected at runtime */
#include "../../common/sensors-i2c.dtsi"
/* ThinkNode M3 has an onboard PCF8563 RTC opt in to boot-time time
* restore (auto-detected at runtime among the supported chips). */
#include "../../common/rtc-i2c.dtsi"
};
/* SPI1 for LR1110 */
@@ -241,6 +241,9 @@
/* All supported environment & power sensors — auto-detected at runtime */
#include "../../common/sensors-i2c.dtsi"
/* ThinkNode M6 has an onboard PCF8563 RTC opt in to boot-time time
* restore (auto-detected at runtime among the supported chips). */
#include "../../common/rtc-i2c.dtsi"
};
/* SPI1 for SX1262 LoRa */
@@ -0,0 +1,43 @@
# SPDX-License-Identifier: Apache-2.0
#
# Data-only descriptor for an I2C RTC chip read by ZephyrRTCDiscover
# (adapters/clock/ZephyrRTCDiscover.c). This binds NO Zephyr RTC driver —
# the chip is accessed with raw I2C using the register layout described by
# the properties below. This keeps the boot-time RTC restore compact
# (~2-3 KB total, "Path B") instead of pulling in the full RTC subsystem.
#
# All supported chips use a 7-byte BCD time block (sec, min, hour, then
# weekday/date, month, year) starting at "time-reg". Only the day-of-month
# byte offset varies between parts, captured by "date-index".
description: ZephCore data-only I2C RTC descriptor (raw-I2C, no Zephyr RTC driver)
compatible: "zephcore,rtc-i2c"
include: [i2c-device.yaml]
properties:
time-reg:
type: int
required: true
description: Register address of the seconds byte (start of the 7-byte BCD block).
date-index:
type: int
required: true
description: >
Byte offset of the day-of-month within the 7-byte block. 4 for chips
ordered sec/min/hour/weekday/date/month/year (DS3231, RV3028, RX8130CE);
3 for PCF8563 which swaps to sec/min/hour/date/weekday/month/year.
status-reg:
type: int
required: true
description: >
Register holding the "time invalid / power was lost" flag. Use 0xFF to
indicate the flag lives in the seconds byte itself (PCF8563 VL bit).
status-mask:
type: int
required: true
description: Bit mask within status-reg that means the held time is unreliable.
+3
View File
@@ -6,6 +6,7 @@
#include "CommonCLI.h"
#include "battery_curve.h"
#include <helpers/time_sync.h>
#include <adapters/clock/ZephyrRTCDiscover.h>
#include <helpers/TxtDataHelpers.h>
#include <helpers/AdvertDataHelpers.h>
#include <adapters/board/ZephyrBoard.h>
@@ -336,6 +337,7 @@ void CommonCLI::handleCommand(uint32_t sender_timestamp, const char* command, ch
if (sender_timestamp > curr) {
getRTCClock()->setCurrentTime(sender_timestamp + 1);
time_sync_report(TIME_SYNC_CLI);
zephcore_rtc_save(sender_timestamp + 1); /* persist to hardware RTC */
uint32_t now = getRTCClock()->getCurrentTime();
time_t t = (time_t)now;
struct tm *tm = gmtime(&t);
@@ -356,6 +358,7 @@ void CommonCLI::handleCommand(uint32_t sender_timestamp, const char* command, ch
if (secs > curr) {
getRTCClock()->setCurrentTime(secs);
time_sync_report(TIME_SYNC_CLI);
zephcore_rtc_save(secs); /* persist to hardware RTC */
time_t t = (time_t)secs;
struct tm *tm = gmtime(&t);
snprintf(reply, CLI_REPLY_SIZE, "OK - clock set: %02d:%02d - %d/%d/%d UTC",
+12
View File
@@ -21,6 +21,7 @@ LOG_MODULE_REGISTER(zephcore_main, CONFIG_ZEPHCORE_MAIN_LOG_LEVEL);
#include <ZephyrDataStore.h>
#include <adapters/clock/ZephyrRTCClock.h>
#include <adapters/clock/ZephyrRTCDiscover.h>
#include <zephyr/bluetooth/bluetooth.h>
#include <zephyr/drivers/hwinfo.h>
#include <zephyr/sys/reboot.h>
@@ -754,6 +755,7 @@ static void gps_fix_callback(double lat, double lon, int64_t utc_time)
LOG_INF("GPS fix: RTC sync time=%lld", utc_time);
rtc_clock.setCurrentTime((uint32_t)utc_time);
time_sync_report(TIME_SYNC_GPS);
zephcore_rtc_save((uint32_t)utc_time); /* persist to hardware RTC */
}
#ifdef ZEPHCORE_LORA
@@ -855,6 +857,16 @@ int main(void)
/* Initialize sensor manager (GPS, environment sensors) */
sensor_manager_init();
/* Restore wall-clock time from a battery-backed hardware RTC if one is
* present on I2C. Shown tagged "L" (local) until the next GPS/app/CLI
* sync; no-op on boards without an RTC. */
{
uint32_t rtc_epoch;
if (zephcore_rtc_restore(&rtc_epoch)) {
rtc_clock.setCurrentTime(rtc_epoch);
}
}
/* Initialize UI subsystem (buttons, buzzer, display).
* If display is present, ui_init() handles display init + auto-off.
* If no display, fall back to raw OLED sleep for power saving. */
+11
View File
@@ -51,6 +51,7 @@ extern "C" void bt_ctlr_assert_handle(char *file, uint32_t line)
#include <app/RepeaterDataStore.h>
#include <app/RepeaterMesh.h>
#include <adapters/clock/ZephyrRTCClock.h>
#include <adapters/clock/ZephyrRTCDiscover.h>
#include <ZephyrSensorManager.h>
/* UI subsystem (display, buttons, buzzer) */
@@ -271,6 +272,7 @@ static void gps_fix_callback(double lat, double lon, int64_t utc_time)
if (utc_time > 0) {
LOG_INF("GPS fix: RTC sync time=%lld", utc_time);
rtc_clock.setCurrentTime((uint32_t)utc_time);
zephcore_rtc_save((uint32_t)utc_time); /* persist to hardware RTC */
}
int lat_deg = (int)lat;
@@ -431,6 +433,15 @@ int main(void)
/* Initialize sensor manager */
sensor_manager_init();
/* Restore wall-clock time from a battery-backed hardware RTC if present
* (shown tagged "L" until the next GPS/CLI sync; no-op if no RTC). */
{
uint32_t rtc_epoch;
if (zephcore_rtc_restore(&rtc_epoch)) {
rtc_clock.setCurrentTime(rtc_epoch);
}
}
/* Set GPS to repeater mode: power off now, wake every 48h for time sync only.
* This prevents GPS from draining power on boards that have it (e.g., Wio Tracker). */
if (gps_is_available()) {
+11
View File
@@ -51,6 +51,7 @@ extern "C" void bt_ctlr_assert_handle(char *file, uint32_t line)
#include <app/RepeaterDataStore.h>
#include <app/RoomServerMesh.h>
#include <adapters/clock/ZephyrRTCClock.h>
#include <adapters/clock/ZephyrRTCDiscover.h>
#include <ZephyrSensorManager.h>
/* UI subsystem (display, buttons, buzzer) */
@@ -284,6 +285,7 @@ static void gps_fix_callback(double lat, double lon, int64_t utc_time)
if (utc_time > 0) {
LOG_INF("GPS fix: RTC sync time=%lld", utc_time);
rtc_clock.setCurrentTime((uint32_t)utc_time);
zephcore_rtc_save((uint32_t)utc_time); /* persist to hardware RTC */
}
int lat_deg = (int)lat;
@@ -449,6 +451,15 @@ int main(void)
/* Initialize sensor manager */
sensor_manager_init();
/* Restore wall-clock time from a battery-backed hardware RTC if present
* (shown tagged "L" until the next GPS/CLI sync; no-op if no RTC). */
{
uint32_t rtc_epoch;
if (zephcore_rtc_restore(&rtc_epoch)) {
rtc_clock.setCurrentTime(rtc_epoch);
}
}
/* Set GPS to repeater mode: power off now, wake every 48h for time sync only.
* This prevents GPS from draining power on boards that have it (e.g., Wio Tracker). */
if (gps_is_available()) {