Time and timezone
A node needs two independent things: a correct wall clock (UTC, from NTP) and a timezone (so "22:00" means what the user thinks it means). Schedules, automation triggers, and timeseries timestamps all depend on both.
Time synchronisation
Enable it in the SDK config:
esp_rmaker_config_t cfg = { .enable_time_sync = true };
esp_rmaker_node_t *node = esp_rmaker_node_init(&cfg, "Light", "light");
That starts SNTP (ESP-IDF) or NTP (POSIX) against pool.ntp.org by default, in polling mode, with a callback on every successful sync.
ESP_RMAKER_DEFAULT_CONFIG sets enable_time_sync = true, so you get it unless you deliberately turn it off. Turn it off only if something else in your firmware owns the clock.
Two startup flows
Which one you get depends on CONFIG_MBEDTLS_HAVE_TIME_DATE — whether mbedTLS validates certificate validity dates. This matters more than it sounds, because it determines whether a node with no reachable NTP server can boot at all.
Synchronous (MBEDTLS_HAVE_TIME_DATE set) | Decoupled (unset) | |
|---|---|---|
| Valid clock needed for TLS | Yes — it is a hard prerequisite | No |
| Startup behaviour | Blocks indefinitely before the MQTT connection until the clock syncs | Never blocks; MQTT connects immediately |
| Schedule arming | Inline, once time is valid | Deferred; a poll observes sync later and arms then |
| Timeseries before sync | N/A — cannot connect without a clock | Deferred or dropped until the clock is valid |
Cloud getTimeSync fallback | Unusable — it arrives over MQTT, which cannot connect | Works |
With MBEDTLS_HAVE_TIME_DATE set, a node that cannot reach an NTP server will hang at startup, not fail. If your devices ship into networks that block NTP, either use the decoupled flow or supply a reachable time source.
The poll interval in the decoupled flow is CONFIG_RMNG_TIME_SYNC_POLL_INTERVAL_S (default 2 seconds). It is ignored in the synchronous flow.
Coarse time from the cloud
On the post-connect handshake, the node asks the cloud for getTimeSync — but only if its clock is still invalid at that point. The response carries server time in epoch milliseconds and is applied only while the clock is still invalid. SNTP remains authoritative and steps the clock when it eventually syncs.
Accuracy is bounded by cloud-to-node delivery latency, so treat it as a coarse fallback that gets a decoupled-flow node functioning, not as a time source.
If time sync is off
The node runs without a synchronised clock. Schedules and automations may behave incorrectly, and timeseries timestamps will be wrong. The clock can still be set by an external SNTP owner in your firmware or by the cloud getTimeSync response.
The timezone service
Enable it with one call, after node init:
esp_rmaker_timezone_service_enable();
This creates a service named Time with two writable string parameters:
| Parameter | Type | Example |
|---|---|---|
TZ | esp.param.tz | America/New_York — an IANA timezone name |
TZ-POSIX | esp.param.tz_posix | EST5EDT,M3.2.0,M11.1.0 — a POSIX TZ string |
Disable it again with esp_rmaker_timezone_service_disable().
Prefer TZ over TZ-POSIX
The two are not symmetric:
- Writing
TZapplies the timezone by IANA name, then derives and reportsTZ-POSIXas well. You get both, consistently, including DST rules. - Writing
TZ-POSIXapplies that string and reports onlyTZ-POSIX. The IANA name cannot be derived back from a POSIX string, soTZis left alone and the two now disagree.
Set TZ whenever you have the choice. TZ-POSIX exists for cases where no IANA name applies.
If applying a timezone fails, the node reports no state change for either parameter — so an unchanged reported value is your signal that the write was rejected.
Daylight saving
Handled by the POSIX TZ rules, so an IANA timezone gets DST correctly with no work from you. Schedules recompute their next occurrence on each fire, which means a schedule set for 07:00 local stays at 07:00 local across a DST boundary.
Timezone change events
Both changes post events you can act on:
| Event | Data |
|---|---|
RMAKER_EVENT_TZ_CHANGED | Location string, e.g. "America/Los_Angeles" |
RMAKER_EVENT_TZ_POSIX_CHANGED | POSIX string, e.g. "PST8PDT,M3.2.0,M11.1.0" |
TZ_CHANGED always comes with a TZ_POSIX_CHANGED; the reverse is not true, because a client can write the POSIX string directly. If you only want one handler, use TZ_POSIX_CHANGED — it fires for both paths.
Timezone changes require a valid system clock, so expect these events after the first successful time sync, not before.
Checking it works
On the serial console:
> local-time
Current local time: Wed Jul 29 14:32:07 2026
If that prints a 1970 date or fails, time sync has not completed and no schedule will fire. Work that problem before anything else — most "schedules don't work" reports are really "time doesn't work".
Related
- Firmware specifications → Time Synchronization — the full flow description
- Firmware specifications → Optional Services → Timezone — the wire contract and payload examples
- Schedules and automations
- Time series data — why timestamps depend on this