Skip to content

feat: sync system clock via SNTP on boot#178

Open
joaosouz4dev wants to merge 1 commit into
memovai:mainfrom
joaosouz4dev:feat/sntp-time-sync
Open

feat: sync system clock via SNTP on boot#178
joaosouz4dev wants to merge 1 commit into
memovai:mainfrom
joaosouz4dev:feat/sntp-time-sync

Conversation

@joaosouz4dev

@joaosouz4dev joaosouz4dev commented May 21, 2026

Copy link
Copy Markdown

Summary

The ESP32-S3 has no battery-backed RTC, so on every cold boot time(NULL) returns 0 (Unix epoch) until something explicitly sets the clock. Today the only code path that sets the clock is the get_current_time tool (main/tools/tool_get_time.c:54), which only runs when the LLM decides to call it.

That leaves every early-boot consumer of time() with bogus values until the user happens to message the bot:

  • Session log / chat history timestamps start at 1970.
  • Cron jobs that compare against time(NULL) may fire immediately or never.
  • Any heartbeat or maintenance task that timestamps work on boot.

This PR adds a tiny time_sync module that starts SNTP against pool.ntp.org as soon as WiFi has an IP, applies MIMI_TIMEZONE, and exposes two helpers for callers that need to gate on a valid clock:

esp_err_t time_sync_start(void);     // idempotent, non-blocking
bool      time_sync_wait(uint32_t timeout_ms);
bool      time_sync_is_set(void);    // same > 1700000000 threshold used today

What's in the diff

  • main/time/time_sync.{h,c} — new ~80-LoC module wrapping esp_netif_sntp_init.
  • main/CMakeLists.txt — register the new source. esp_netif is already in REQUIRES, so no new component dependency.
  • main/mimi.c — call time_sync_start() right after wifi_manager_wait_connected() succeeds (and only on that path, so onboarding-mode boots are unaffected).

SNTP runs in the background and logs System time synchronized: ... from the sync callback when the first sample arrives (typically 1–3 seconds after WIFI_CONNECTED). Calling time_sync_start() more than once is a no-op.

Test plan

  • Build: idf.py fullclean && idf.py build
  • Flash and monitor: idf.py -p <PORT> flash monitor
  • Confirm a time_sync: System time synchronized: YYYY-MM-DD HH:MM:SS log appears within ~5 s of wifi: Connected!
  • Confirm session log filenames and any cron entries use a real date, not 1970-01-01
  • Disconnect WiFi temporarily then reconnect — SNTP re-syncs on its own (lwIP default is hourly)

Summary by CodeRabbit

  • New Features
    • Implemented SNTP-based system time synchronization that initiates automatically after WiFi connection, ensuring the system clock is accurately set before time-sensitive services initialize.
    • Added timezone configuration support through environment variables.

Review Change Stack

The ESP32 has no battery-backed RTC, so on every cold boot the clock
sits at the Unix epoch until something sets it. Today the only path
that updates the clock is the get_current_time tool, which only runs
when the LLM decides to call it. That leaves every early-boot
code path (session log timestamps, cron, anything that reads time())
with bogus values until the user happens to message the bot.

This patch adds a small time_sync module that starts SNTP against
pool.ntp.org as soon as WiFi has an IP, sets MIMI_TIMEZONE, and
exposes time_sync_wait() / time_sync_is_set() for callers that need
to gate on a valid clock.

  main/time/time_sync.{h,c}  new module (~80 LoC)
  main/CMakeLists.txt        register the new source
  main/mimi.c                kick time_sync_start() right after
                             wifi_manager_wait_connected() succeeds

esp_netif (already in REQUIRES) provides esp_netif_sntp.h, so no
new component dependency is needed. SNTP runs in the background:
time_sync_start() returns immediately and the sync_cb logs when
the first sample arrives.
@coderabbitai

coderabbitai Bot commented May 21, 2026

Copy link
Copy Markdown
📝 Walkthrough

Walkthrough

This PR adds SNTP-based system time synchronization for an ESP device. A new time_sync module provides three functions to start background synchronization, wait for completion with timeout, and check readiness. The module is integrated into the boot flow to initialize time immediately after WiFi connection, before time-sensitive subsystems.

Changes

SNTP Time Synchronization

Layer / File(s) Summary
Time Sync API Contract
main/time/time_sync.h
Public header declares three functions: time_sync_start() to initiate background SNTP sync with timezone setup, time_sync_wait(timeout_ms) to block until time is synchronized or timeout expires, and time_sync_is_set() to query synchronization status.
SNTP Core Implementation
main/time/time_sync.c
Module state (event group, started flag), SNTP callback that logs synchronized time and signals completion via FreeRTOS event bit, time_sync_start() that idempotently initializes event group and SNTP with timezone configuration, time_sync_wait() that blocks on completion with timeout support, and time_sync_is_set() using an epoch threshold for readiness determination.
Boot Flow Integration
main/CMakeLists.txt, main/mimi.c
Adds time/time_sync.c to build sources and integrates time synchronization into the main boot sequence: header is included and time_sync_start() is called immediately after WiFi connection and before time-sensitive subsystems.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Poem

🐰 A rabbit hops in with time so true,
SNTP syncs what the system knew,
Event groups dance, callbacks align,
Boot flows smoother, precise and fine!
Hippity-hop, now clocks will chime! ⏰

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'feat: sync system clock via SNTP on boot' clearly and accurately summarizes the main change: adding SNTP-based time synchronization on boot.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (1)
main/mimi.c (1)

154-188: ⚡ Quick win

Consider briefly waiting for first sync before starting time-sensitive services.

cron_service_start() (Line 186) and heartbeat_start() (Line 187) are launched immediately after time_sync_start() returns, but SNTP typically needs 1–3 s to deliver the first sample. Any cron evaluation or heartbeat tick that fires in that window still sees epoch time — the exact failure mode this PR sets out to fix. The new time_sync_wait() API exists for this purpose; a short bounded wait (e.g. 3–5 s) here would close the gap without meaningfully delaying boot.

⏱️ Sketch
         /* Start network-dependent services */
         ESP_ERROR_CHECK(agent_loop_start());
         ESP_ERROR_CHECK(telegram_bot_start());
         ESP_ERROR_CHECK(feishu_bot_start());
+        /* Best-effort: let SNTP land before time-sensitive services tick. */
+        if (!time_sync_wait(5000)) {
+            ESP_LOGW(TAG, "Proceeding without confirmed time sync");
+        }
         cron_service_start();
         heartbeat_start();
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@main/mimi.c` around lines 154 - 188, The code starts cron_service_start() and
heartbeat_start() immediately after time_sync_start(), allowing services to run
before SNTP has provided a valid time; call time_sync_wait() with a short
bounded timeout (e.g. 3000–5000 ms) after time_sync_start() and before
cron_service_start() and heartbeat_start() to block until first sync or timeout,
and handle the return (log on timeout/failure but proceed) so time-sensitive
services see a sane RTC before they run.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@main/mimi.c`:
- Line 156: time_sync_start() return value is discarded; capture its esp_err_t
result in main (where time_sync_start() is called) and handle failures by
logging the error (e.g., using ESP_LOGE or processLogger equivalent) or
propagating the error upward; specifically, assign the result of
time_sync_start() to a variable, check for != ESP_OK, and emit a clear error
message that includes the error code and context (e.g., "time_sync_start
failed") so failures like ESP_ERR_NO_MEM or SNTP init errors are visible at
boot.

---

Nitpick comments:
In `@main/mimi.c`:
- Around line 154-188: The code starts cron_service_start() and
heartbeat_start() immediately after time_sync_start(), allowing services to run
before SNTP has provided a valid time; call time_sync_wait() with a short
bounded timeout (e.g. 3000–5000 ms) after time_sync_start() and before
cron_service_start() and heartbeat_start() to block until first sync or timeout,
and handle the return (log on timeout/failure but proceed) so time-sensitive
services see a sane RTC before they run.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 0a8a755e-d6e4-4d04-be2c-069d59b29282

📥 Commits

Reviewing files that changed from the base of the PR and between bb10ea0 and da6020f.

📒 Files selected for processing (4)
  • main/CMakeLists.txt
  • main/mimi.c
  • main/time/time_sync.c
  • main/time/time_sync.h

Comment thread main/mimi.c
ESP_LOGI(TAG, "WiFi connected: %s", wifi_manager_get_ip());
/* Kick SNTP as soon as we have an IP so the RTC is set
* before time-sensitive code (session logs, cron, etc.). */
time_sync_start();

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Don't silently discard time_sync_start() failure.

time_sync_start() returns esp_err_t and can fail with ESP_ERR_NO_MEM or a propagated SNTP init error. Discarding it makes a broken time sync indistinguishable from a working one at runtime — the only signal will be the absence of the "System time synchronized" log a few seconds later. Log the failure (or surface it) so it's diagnosable from boot output.

🪵 Proposed fix
             /* Kick SNTP as soon as we have an IP so the RTC is set
              * before time-sensitive code (session logs, cron, etc.). */
-            time_sync_start();
+            esp_err_t sntp_err = time_sync_start();
+            if (sntp_err != ESP_OK) {
+                ESP_LOGW(TAG, "Time sync start failed: %s", esp_err_to_name(sntp_err));
+            }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@main/mimi.c` at line 156, time_sync_start() return value is discarded;
capture its esp_err_t result in main (where time_sync_start() is called) and
handle failures by logging the error (e.g., using ESP_LOGE or processLogger
equivalent) or propagating the error upward; specifically, assign the result of
time_sync_start() to a variable, check for != ESP_OK, and emit a clear error
message that includes the error code and context (e.g., "time_sync_start
failed") so failures like ESP_ERR_NO_MEM or SNTP init errors are visible at
boot.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant