Skip to main content

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 TLSYes — it is a hard prerequisiteNo
Startup behaviourBlocks indefinitely before the MQTT connection until the clock syncsNever blocks; MQTT connects immediately
Schedule armingInline, once time is validDeferred; a poll observes sync later and arms then
Timeseries before syncN/A — cannot connect without a clockDeferred or dropped until the clock is valid
Cloud getTimeSync fallbackUnusable — it arrives over MQTT, which cannot connectWorks
caution

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 getTimeSyncbut 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:

ParameterTypeExample
TZesp.param.tzAmerica/New_York — an IANA timezone name
TZ-POSIXesp.param.tz_posixEST5EDT,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 TZ applies the timezone by IANA name, then derives and reports TZ-POSIX as well. You get both, consistently, including DST rules.
  • Writing TZ-POSIX applies that string and reports only TZ-POSIX. The IANA name cannot be derived back from a POSIX string, so TZ is 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:

EventData
RMAKER_EVENT_TZ_CHANGEDLocation string, e.g. "America/Los_Angeles"
RMAKER_EVENT_TZ_POSIX_CHANGEDPOSIX 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".