From 3d57e6416a551f6e1190d756a7d46a090ccdbb92 Mon Sep 17 00:00:00 2001 From: skiinder Date: Fri, 27 Mar 2026 11:25:22 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E6=B7=BB=E5=8A=A0=E6=97=B6=E9=97=B4?= =?UTF-8?q?=E5=90=8C=E6=AD=A5=E7=AE=A1=E7=90=86=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 新增time_manager模块用于统一管理时间同步状态 - 实现BLE时间同步GATT服务(time_sync_event和ble_time_sync_module) - 添加time_sync_protocol定义统一的协议帧格式 - 支持UTC时间戳、时区偏移和精度信息的时间同步 - 实现settings持久化存储时间校准数据 - 提供time_manager快照API供其他模块查询当前时间状态 - 增加对BLE/USB/手动三种同步源的支持和区分 --- CMakeLists.txt | 3 + inc/time_manager.h | 68 +++++ inc/time_sync_protocol.h | 33 +++ src/events/time_sync_event.c | 37 +++ src/events/time_sync_event.h | 35 +++ src/modules/ble_time_sync_module.c | 175 +++++++++++++ src/modules/time_manager_module.c | 407 +++++++++++++++++++++++++++++ 7 files changed, 758 insertions(+) create mode 100644 inc/time_manager.h create mode 100644 inc/time_sync_protocol.h create mode 100644 src/events/time_sync_event.c create mode 100644 src/events/time_sync_event.h create mode 100644 src/modules/ble_time_sync_module.c create mode 100644 src/modules/time_manager_module.c diff --git a/CMakeLists.txt b/CMakeLists.txt index f5c2a51..1e84dfb 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -28,10 +28,12 @@ target_sources(app PRIVATE src/events/keyboard_led_event.c src/events/mode_event.c src/events/qdec_step_event.c + src/events/time_sync_event.c src/modules/battery_module.c src/modules/ble_adv_ctrl_module.c src/modules/ble_battery_module.c src/modules/ble_bond_module.c + src/modules/ble_time_sync_module.c src/modules/ble_slot_ctrl_module.c src/modules/display_module.c src/modules/hid_tx_manager_module.c @@ -39,6 +41,7 @@ target_sources(app PRIVATE src/modules/led_state_module.c src/modules/mode_switch_module.c src/modules/qdec_module.c + src/modules/time_manager_module.c src/modules/usb_hid_module.c src/modules/ble_hid_module.c ) diff --git a/inc/time_manager.h b/inc/time_manager.h new file mode 100644 index 0000000..c4d181c --- /dev/null +++ b/inc/time_manager.h @@ -0,0 +1,68 @@ +#ifndef TIME_MANAGER_H__ +#define TIME_MANAGER_H__ + +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/* + * 时间同步来源保持传输无关: + * - BLE/USB/手动设置都复用同一套枚举; + * - 后续如果新增其他同步链路,只需要补枚举值,不需要改事件语义。 + */ +enum time_sync_source { + TIME_SYNC_SOURCE_NONE = 0, + TIME_SYNC_SOURCE_BLE, + TIME_SYNC_SOURCE_USB, + TIME_SYNC_SOURCE_MANUAL, +}; + +/* + * 时间同步更新载荷: + * - utc_ms 统一使用 UTC 毫秒时间戳,避免内部状态受本地时区影响; + * - timezone_min 记录“显示层”所需的时区偏移,当前不拆 DST; + * - accuracy_ms 允许上位机表达这次校时的可信度,未知时传 0 即可。 + */ +struct time_sync_update { + uint64_t utc_ms; + int16_t timezone_min; + uint32_t accuracy_ms; + enum time_sync_source source; +}; + +/* + * 时间快照用于提供给显示、日志或后续 USB/调试接口: + * - synchronized=true 表示当前开机周期内已经收到有效校时; + * - has_persisted_time=true 仅表示 flash 里存过一次历史校时,不代表当前时间仍然可信; + * - ready=false 表示 time_manager 还没等到 settings_loader 完成初始化。 + */ +struct time_manager_snapshot { + uint64_t utc_ms; + int16_t timezone_min; + uint32_t accuracy_ms; + enum time_sync_source source; + bool ready; + bool synchronized; + bool has_persisted_time; +}; + +/* 返回当前模块是否已经完成初始化,供同步入口快速拒绝“过早写入”。 */ +bool time_manager_is_ready(void); + +/* + * 获取当前时间快照: + * - 返回 0:snapshot 已填充; + * - 返回 -EINVAL:参数为空; + * - 返回 -EAGAIN:模块未 ready; + * - 返回 -ENODATA:当前开机周期尚未完成有效校时。 + */ +int time_manager_get_snapshot(struct time_manager_snapshot *snapshot); + +#ifdef __cplusplus +} +#endif + +#endif /* TIME_MANAGER_H__ */ diff --git a/inc/time_sync_protocol.h b/inc/time_sync_protocol.h new file mode 100644 index 0000000..f4c49fa --- /dev/null +++ b/inc/time_sync_protocol.h @@ -0,0 +1,33 @@ +#ifndef TIME_SYNC_PROTOCOL_H__ +#define TIME_SYNC_PROTOCOL_H__ + +#include + +#include + +/* + * 统一定义时间同步协议帧格式,方便 BLE/USB 两条链路共享: + * + * byte 0 : version + * byte 1 : flags + * byte 2-3 : timezone_min (little-endian, int16) + * byte 4-11: utc_ms (little-endian, uint64) + * byte 12-15: accuracy_ms (little-endian, uint32) + */ +#define TIME_SYNC_PROTOCOL_VERSION 1U +#define TIME_SYNC_PROTOCOL_PAYLOAD_SIZE 16U + +#define TIME_SYNC_PROTOCOL_OFFSET_VERSION 0U +#define TIME_SYNC_PROTOCOL_OFFSET_FLAGS 1U +#define TIME_SYNC_PROTOCOL_OFFSET_TIMEZONE 2U +#define TIME_SYNC_PROTOCOL_OFFSET_UTC_MS 4U +#define TIME_SYNC_PROTOCOL_OFFSET_ACCURACY_MS 12U + +/* + * 预留 flags 字段: + * - 当前版本只要求时区字段有效; + * - 后续如果要加 DST、闰秒或来源质量扩展,可以继续复用这个字节。 + */ +#define TIME_SYNC_PROTOCOL_FLAG_TIMEZONE_VALID BIT(0) + +#endif /* TIME_SYNC_PROTOCOL_H__ */ diff --git a/src/events/time_sync_event.c b/src/events/time_sync_event.c new file mode 100644 index 0000000..a1af66b --- /dev/null +++ b/src/events/time_sync_event.c @@ -0,0 +1,37 @@ +#include "time_sync_event.h" + +/* 统一输出来源字符串,便于日志快速确认是哪条链路在校时。 */ +static const char *time_sync_source_name(enum time_sync_source source) +{ + switch (source) { + case TIME_SYNC_SOURCE_NONE: + return "none"; + case TIME_SYNC_SOURCE_BLE: + return "ble"; + case TIME_SYNC_SOURCE_USB: + return "usb"; + case TIME_SYNC_SOURCE_MANUAL: + return "manual"; + default: + return "unknown"; + } +} + +/* 事件日志聚焦关键信息:来源、时区和 UTC 毫秒时间戳。 */ +static void log_time_sync_event(const struct app_event_header *aeh) +{ + const struct time_sync_event *event = cast_time_sync_event(aeh); + const struct time_sync_update *update = time_sync_event_get_update(event); + + APP_EVENT_MANAGER_LOG(aeh, + "src=%s tz=%d utc_ms=%llu acc=%u", + time_sync_source_name(update->source), + update->timezone_min, + (unsigned long long)update->utc_ms, + update->accuracy_ms); +} + +APP_EVENT_TYPE_DEFINE(time_sync_event, + log_time_sync_event, + NULL, + APP_EVENT_FLAGS_CREATE(APP_EVENT_TYPE_FLAGS_INIT_LOG_ENABLE)); diff --git a/src/events/time_sync_event.h b/src/events/time_sync_event.h new file mode 100644 index 0000000..fc17439 --- /dev/null +++ b/src/events/time_sync_event.h @@ -0,0 +1,35 @@ +#ifndef TIME_SYNC_EVENT_H__ +#define TIME_SYNC_EVENT_H__ + +#include +#include + +#include "time_manager.h" + +/* + * time_sync_event 是“同步入口 -> time_manager”的统一事件: + * - BLE/USB/本地设置都提交同一类事件; + * - time_manager 是唯一消费者,也是唯一能修改运行时钟状态的模块。 + */ +struct time_sync_event { + struct app_event_header header; + struct time_sync_update update; +}; + +APP_EVENT_TYPE_DECLARE(time_sync_event); + +static inline void time_sync_event_submit(const struct time_sync_update *update) +{ + struct time_sync_event *event = new_time_sync_event(); + + event->update = *update; + APP_EVENT_SUBMIT(event); +} + +static inline const struct time_sync_update *time_sync_event_get_update( + const struct time_sync_event *event) +{ + return &event->update; +} + +#endif /* TIME_SYNC_EVENT_H__ */ diff --git a/src/modules/ble_time_sync_module.c b/src/modules/ble_time_sync_module.c new file mode 100644 index 0000000..b5b4e7c --- /dev/null +++ b/src/modules/ble_time_sync_module.c @@ -0,0 +1,175 @@ +#include + +#include +#include + +#include + +#define MODULE ble_time_sync +#include + +#include "time_manager.h" +#include "time_sync_event.h" +#include "time_sync_protocol.h" + +#include +LOG_MODULE_REGISTER(MODULE, LOG_LEVEL_INF); + +#define BT_UUID_TIME_SYNC_SERVICE_VAL \ + BT_UUID_128_ENCODE(0x0b7f5000, 0x38d2, 0x4f62, 0x8f6f, 0x36c4fd73a110) +#define BT_UUID_TIME_SYNC_WRITE_CHAR_VAL \ + BT_UUID_128_ENCODE(0x0b7f5001, 0x38d2, 0x4f62, 0x8f6f, 0x36c4fd73a110) + +#define BT_UUID_TIME_SYNC_SERVICE \ + BT_UUID_DECLARE_128(BT_UUID_TIME_SYNC_SERVICE_VAL) +#define BT_UUID_TIME_SYNC_WRITE_CHAR \ + BT_UUID_DECLARE_128(BT_UUID_TIME_SYNC_WRITE_CHAR_VAL) + +struct ble_time_sync_ctx { + bool ble_stack_ready; + bool time_manager_ready; + bool module_ready; +}; + +static struct ble_time_sync_ctx ble_time_sync; + +/* 统一检查协议版本和长度,避免在回调里分散出现偏移判断。 */ +static bool ble_time_sync_payload_is_valid(const uint8_t *buf, uint16_t len) +{ + if (!buf || (len != TIME_SYNC_PROTOCOL_PAYLOAD_SIZE)) { + return false; + } + + if (buf[TIME_SYNC_PROTOCOL_OFFSET_VERSION] != TIME_SYNC_PROTOCOL_VERSION) { + return false; + } + + if ((buf[TIME_SYNC_PROTOCOL_OFFSET_FLAGS] & + TIME_SYNC_PROTOCOL_FLAG_TIMEZONE_VALID) == 0U) { + return false; + } + + return true; +} + +/* + * 把私有 GATT payload 解码为统一的 time_sync_update: + * - BLE 只负责协议适配; + * - 传输无关的时间语义都转成公共结构体后再交给事件层。 + */ +static void ble_time_sync_decode_payload(const uint8_t *buf, + struct time_sync_update *update) +{ + update->utc_ms = sys_get_le64(&buf[TIME_SYNC_PROTOCOL_OFFSET_UTC_MS]); + update->timezone_min = + (int16_t)sys_get_le16(&buf[TIME_SYNC_PROTOCOL_OFFSET_TIMEZONE]); + update->accuracy_ms = + sys_get_le32(&buf[TIME_SYNC_PROTOCOL_OFFSET_ACCURACY_MS]); + update->source = TIME_SYNC_SOURCE_BLE; +} + +/* + * GATT 写回调必须尽量短: + * - 不支持 offset/prepare write,避免被拆成长写事务; + * - 校验和解码完成后直接提交统一事件,不在这里做耗时存储。 + */ +static ssize_t write_time_sync(struct bt_conn *conn, + const struct bt_gatt_attr *attr, + const void *buf, + uint16_t len, + uint16_t offset, + uint8_t flags) +{ + struct time_sync_update update; + + ARG_UNUSED(conn); + ARG_UNUSED(attr); + + if (!ble_time_sync.module_ready || !time_manager_is_ready()) { + return BT_GATT_ERR(BT_ATT_ERR_UNLIKELY); + } + + if (offset != 0U) { + return BT_GATT_ERR(BT_ATT_ERR_INVALID_OFFSET); + } + + if ((flags & BT_GATT_WRITE_FLAG_PREPARE) != 0U) { + return BT_GATT_ERR(BT_ATT_ERR_ATTRIBUTE_NOT_LONG); + } + + if (!ble_time_sync_payload_is_valid(buf, len)) { + return BT_GATT_ERR(BT_ATT_ERR_VALUE_NOT_ALLOWED); + } + + ble_time_sync_decode_payload(buf, &update); + time_sync_event_submit(&update); + + LOG_INF("Accepted BLE time sync utc_ms=%llu tz=%d acc=%u", + (unsigned long long)update.utc_ms, + update.timezone_min, + update.accuracy_ms); + + return len; +} + +BT_GATT_SERVICE_DEFINE(ble_time_sync_svc, + BT_GATT_PRIMARY_SERVICE(BT_UUID_TIME_SYNC_SERVICE), + BT_GATT_CHARACTERISTIC(BT_UUID_TIME_SYNC_WRITE_CHAR, + BT_GATT_CHRC_WRITE | + BT_GATT_CHRC_WRITE_WITHOUT_RESP, + BT_GATT_PERM_WRITE_ENCRYPT, + NULL, + write_time_sync, + NULL), +); + +/* + * 只有 BLE 栈和 time_manager 都 ready 后,才把模块状态标记为 READY: + * - 虽然静态 GATT service 会跟随蓝牙栈注册; + * - 但真正是否接受写入,仍由 module_ready 再做一层保护。 + */ +static void ble_time_sync_update_ready_state(void) +{ + bool should_be_ready = ble_time_sync.ble_stack_ready && + ble_time_sync.time_manager_ready; + + if (should_be_ready == ble_time_sync.module_ready) { + return; + } + + ble_time_sync.module_ready = should_be_ready; + module_set_state(should_be_ready ? MODULE_STATE_READY : MODULE_STATE_STANDBY); + + LOG_INF("BLE time sync %s", should_be_ready ? "ready" : "standby"); +} + +/* 模块依赖只来自 ble_state 和 time_manager,两者 READY 顺序不做假设。 */ +static bool handle_module_state_event(const struct module_state_event *event) +{ + if (check_state(event, MODULE_ID(ble_state), MODULE_STATE_READY)) { + ble_time_sync.ble_stack_ready = true; + ble_time_sync_update_ready_state(); + return false; + } + + if (check_state(event, MODULE_ID(time_manager), MODULE_STATE_READY)) { + ble_time_sync.time_manager_ready = true; + ble_time_sync_update_ready_state(); + return false; + } + + return false; +} + +static bool app_event_handler(const struct app_event_header *aeh) +{ + if (is_module_state_event(aeh)) { + return handle_module_state_event(cast_module_state_event(aeh)); + } + + __ASSERT_NO_MSG(false); + return false; +} + +APP_EVENT_LISTENER(MODULE, app_event_handler); +APP_EVENT_SUBSCRIBE(MODULE, module_state_event); diff --git a/src/modules/time_manager_module.c b/src/modules/time_manager_module.c new file mode 100644 index 0000000..94e79b6 --- /dev/null +++ b/src/modules/time_manager_module.c @@ -0,0 +1,407 @@ +#include +#include + +#include +#include +#include + +#include + +#define MODULE time_manager +#include +#include + +#include "time_manager.h" +#include "time_sync_event.h" + +#include +LOG_MODULE_REGISTER(MODULE, LOG_LEVEL_INF); + +#define TIME_MANAGER_SAVE_DELAY_MS 1000 +#define TIME_MANAGER_STORAGE_KEY "state" + +/* + * 持久化数据只记录“最近一次成功校时”的元数据: + * - 它可以帮助我们知道设备曾经被校时过; + * - 但由于当前没有独立 RTC,重启后不能把这份数据当作“当前仍准确”的时间。 + */ +struct time_manager_storage_data { + uint64_t utc_ms; + int16_t timezone_min; + uint32_t accuracy_ms; + uint8_t source; + uint8_t valid_marker; +}; + +struct time_manager_ctx { + struct k_spinlock lock; + struct k_work_delayable save_work; + struct time_manager_storage_data persisted; + uint64_t base_utc_ms; + int64_t base_uptime_ms; + int16_t timezone_min; + uint32_t accuracy_ms; + enum time_sync_source source; + bool ready; + bool synchronized; + bool has_persisted_time; + bool storage_dirty; + bool storage_loaded; +}; + +static struct time_manager_ctx time_ctx; + +/* 统一判断来源是否合法,避免把损坏配置或“未设置来源”写进时间状态。 */ +static bool time_manager_source_is_valid(enum time_sync_source source) +{ + switch (source) { + case TIME_SYNC_SOURCE_BLE: + case TIME_SYNC_SOURCE_USB: + case TIME_SYNC_SOURCE_MANUAL: + return true; + default: + return false; + } +} + +/* 对时区做保守校验,避免异常包把显示层带到离谱偏移。 */ +static bool time_manager_timezone_is_valid(int16_t timezone_min) +{ + return (timezone_min >= -(24 * 60)) && (timezone_min <= (24 * 60)); +} + +/* + * 保存工作在系统工作队列里执行: + * - 这样 GATT 写回调和事件派发路径都只做内存更新; + * - 真正可能触发 flash 擦写的 settings_save_one 被挪到异步上下文。 + */ +static int time_manager_store_state(const struct time_manager_storage_data *storage) +{ + char key[] = MODULE_NAME "/" TIME_MANAGER_STORAGE_KEY; + int err = settings_save_one(key, storage, sizeof(*storage)); + + if (err) { + LOG_ERR("Failed to save time state err=%d", err); + return err; + } + + LOG_INF("Stored time state src=%u utc_ms=%llu", + storage->source, + (unsigned long long)storage->utc_ms); + + return 0; +} + +/* + * 从运行态复制一份稳定快照,再在锁外执行 flash 写入: + * - 锁内只做小块 memcpy,避免长时间持锁; + * - 即使保存失败,也不回滚内存时间状态,避免影响当前功能。 + */ +static void time_manager_save_work_fn(struct k_work *work) +{ + struct time_manager_storage_data storage; + bool should_store; + k_spinlock_key_t key; + + ARG_UNUSED(work); + + key = k_spin_lock(&time_ctx.lock); + should_store = time_ctx.storage_dirty && time_ctx.has_persisted_time; + storage = time_ctx.persisted; + time_ctx.storage_dirty = false; + k_spin_unlock(&time_ctx.lock, key); + + if (!should_store) { + return; + } + + (void)time_manager_store_state(&storage); +} + +/* + * 把一次同步结果写入运行态: + * - base_utc_ms + base_uptime_ms 组成当前时间基准; + * - persisted 只保存“最近一次同步结果”,后续异步落盘。 + */ +static void time_manager_apply_update(const struct time_sync_update *update) +{ + k_spinlock_key_t key = k_spin_lock(&time_ctx.lock); + + time_ctx.base_utc_ms = update->utc_ms; + time_ctx.base_uptime_ms = k_uptime_get(); + time_ctx.timezone_min = update->timezone_min; + time_ctx.accuracy_ms = update->accuracy_ms; + time_ctx.source = update->source; + time_ctx.synchronized = true; + time_ctx.has_persisted_time = true; + time_ctx.persisted.utc_ms = update->utc_ms; + time_ctx.persisted.timezone_min = update->timezone_min; + time_ctx.persisted.accuracy_ms = update->accuracy_ms; + time_ctx.persisted.source = (uint8_t)update->source; + time_ctx.persisted.valid_marker = 1U; + time_ctx.storage_dirty = true; + + k_spin_unlock(&time_ctx.lock, key); + + /* + * 保存延迟 1 秒: + * - 避免未来 BLE/USB 连续校时时反复触发 flash 写入; + * - 也避免在同步入口上下文里直接阻塞等待存储完成。 + */ + k_work_reschedule(&time_ctx.save_work, K_MSEC(TIME_MANAGER_SAVE_DELAY_MS)); + + LOG_INF("Time synchronized src=%u tz=%d utc_ms=%llu acc=%u", + update->source, + update->timezone_min, + (unsigned long long)update->utc_ms, + update->accuracy_ms); +} + +/* + * settings 子系统回调只负责恢复原始持久化结构: + * - 不在这里决定“当前时间是否有效”; + * - 运行态初始化放到 settings_loader ready 之后统一完成。 + */ +static int settings_set(const char *key, + size_t len_rd, + settings_read_cb read_cb, + void *cb_arg) +{ + ssize_t rc; + + if (strcmp(key, TIME_MANAGER_STORAGE_KEY) != 0) { + return 0; + } + + if (len_rd != sizeof(time_ctx.persisted)) { + LOG_WRN("Time state size mismatch got=%u expect=%u", + (uint32_t)len_rd, + sizeof(time_ctx.persisted)); + time_ctx.storage_loaded = false; + return 0; + } + + rc = read_cb(cb_arg, &time_ctx.persisted, sizeof(time_ctx.persisted)); + time_ctx.storage_loaded = (rc == sizeof(time_ctx.persisted)); + + if (!time_ctx.storage_loaded) { + LOG_WRN("Time state read failed rc=%d", (int)rc); + } + + return 0; +} + +SETTINGS_STATIC_HANDLER_DEFINE(time_manager, + MODULE_NAME, + NULL, + settings_set, + NULL, + NULL); + +/* + * settings 恢复完成后初始化运行态: + * - 有持久化历史仅表示“以前同步过”,不能直接把时间标记成有效; + * - 当前 boot 仍然需要新的同步事件来建立可信时间基准。 + */ +static void time_manager_init_after_settings_loaded(void) +{ + k_spinlock_key_t key = k_spin_lock(&time_ctx.lock); + + time_ctx.ready = true; + time_ctx.synchronized = false; + time_ctx.source = TIME_SYNC_SOURCE_NONE; + time_ctx.base_utc_ms = 0U; + time_ctx.base_uptime_ms = 0; + time_ctx.timezone_min = 0; + time_ctx.accuracy_ms = 0U; + time_ctx.has_persisted_time = time_ctx.storage_loaded && + (time_ctx.persisted.valid_marker == 1U) && + time_manager_source_is_valid( + (enum time_sync_source)time_ctx.persisted.source) && + time_manager_timezone_is_valid( + time_ctx.persisted.timezone_min); + time_ctx.storage_dirty = false; + + k_spin_unlock(&time_ctx.lock, key); + + if (time_ctx.has_persisted_time) { + LOG_INF("Loaded persisted time metadata tz=%d src=%u utc_ms=%llu", + time_ctx.persisted.timezone_min, + time_ctx.persisted.source, + (unsigned long long)time_ctx.persisted.utc_ms); + } else { + LOG_INF("No valid persisted time metadata"); + } + + module_set_state(MODULE_STATE_READY); +} + +/* + * 校时事件入口只做快速校验和内存更新: + * - 数据格式错误直接丢弃; + * - flash 持久化交给延迟工作处理。 + */ +static bool handle_time_sync_event(const struct time_sync_event *event) +{ + const struct time_sync_update *update = time_sync_event_get_update(event); + + if (!time_ctx.ready) { + LOG_WRN("Drop time sync before manager ready"); + return false; + } + + if (!time_manager_source_is_valid(update->source)) { + LOG_WRN("Drop time sync invalid source=%u", update->source); + return false; + } + + if (!time_manager_timezone_is_valid(update->timezone_min)) { + LOG_WRN("Drop time sync invalid timezone=%d", update->timezone_min); + return false; + } + + if (update->utc_ms == 0U) { + LOG_WRN("Drop time sync utc_ms=0"); + return false; + } + + time_manager_apply_update(update); + return false; +} + +/* + * 掉电前尽量把最后一次同步结果落盘: + * - 如果没有脏数据,立即返回; + * - 如果有待保存数据,则同步执行一次保存,降低突然掉电时的丢失概率。 + */ +static bool handle_power_down_event(void) +{ + struct time_manager_storage_data storage; + bool should_store; + k_spinlock_key_t key; + + if (!time_manager_is_ready()) { + return false; + } + + (void)k_work_cancel_delayable(&time_ctx.save_work); + + key = k_spin_lock(&time_ctx.lock); + should_store = time_ctx.storage_dirty && time_ctx.has_persisted_time; + storage = time_ctx.persisted; + time_ctx.storage_dirty = false; + k_spin_unlock(&time_ctx.lock, key); + + if (!should_store) { + return false; + } + + (void)time_manager_store_state(&storage); + return false; +} + +/* 仅在 settings_loader 完成后宣布 READY,保证外部读取到的是稳定状态。 */ +static bool handle_module_state_event(const struct module_state_event *event) +{ + if (!check_state(event, MODULE_ID(settings_loader), MODULE_STATE_READY)) { + return false; + } + + if (time_ctx.ready) { + return false; + } + + k_work_init_delayable(&time_ctx.save_work, time_manager_save_work_fn); + time_manager_init_after_settings_loaded(); + return false; +} + +bool time_manager_is_ready(void) +{ + k_spinlock_key_t key = k_spin_lock(&time_ctx.lock); + bool ready = time_ctx.ready; + + k_spin_unlock(&time_ctx.lock, key); + return ready; +} + +/* + * 获取快照时只复制一次基准,再在锁外计算当前 UTC: + * - 这样不会让调用者在锁里做时间换算; + * - 也避免 64 位字段在 32 位 MCU 上被撕裂读取。 + */ +int time_manager_get_snapshot(struct time_manager_snapshot *snapshot) +{ + uint64_t base_utc_ms; + int64_t base_uptime_ms; + int16_t timezone_min; + uint32_t accuracy_ms; + enum time_sync_source source; + bool ready; + bool synchronized; + bool has_persisted_time; + k_spinlock_key_t key; + int64_t elapsed_ms; + + if (!snapshot) { + return -EINVAL; + } + + key = k_spin_lock(&time_ctx.lock); + ready = time_ctx.ready; + synchronized = time_ctx.synchronized; + has_persisted_time = time_ctx.has_persisted_time; + base_utc_ms = time_ctx.base_utc_ms; + base_uptime_ms = time_ctx.base_uptime_ms; + timezone_min = time_ctx.timezone_min; + accuracy_ms = time_ctx.accuracy_ms; + source = time_ctx.source; + k_spin_unlock(&time_ctx.lock, key); + + snapshot->ready = ready; + snapshot->synchronized = synchronized; + snapshot->has_persisted_time = has_persisted_time; + snapshot->timezone_min = timezone_min; + snapshot->accuracy_ms = accuracy_ms; + snapshot->source = source; + snapshot->utc_ms = 0U; + + if (!ready) { + return -EAGAIN; + } + + if (!synchronized) { + return -ENODATA; + } + + elapsed_ms = k_uptime_get() - base_uptime_ms; + if (elapsed_ms < 0) { + elapsed_ms = 0; + } + + snapshot->utc_ms = base_utc_ms + (uint64_t)elapsed_ms; + return 0; +} + +static bool app_event_handler(const struct app_event_header *aeh) +{ + if (is_module_state_event(aeh)) { + return handle_module_state_event(cast_module_state_event(aeh)); + } + + if (is_time_sync_event(aeh)) { + return handle_time_sync_event(cast_time_sync_event(aeh)); + } + + if (is_power_down_event(aeh)) { + return handle_power_down_event(); + } + + __ASSERT_NO_MSG(false); + return false; +} + +APP_EVENT_LISTENER(MODULE, app_event_handler); +APP_EVENT_SUBSCRIBE(MODULE, module_state_event); +APP_EVENT_SUBSCRIBE(MODULE, time_sync_event); +APP_EVENT_SUBSCRIBE_EARLY(MODULE, power_down_event);