-
Notifications
You must be signed in to change notification settings - Fork 1
Time Sync
This page explains the built-in time sync support that ships with the timesync feature.
- Enable the
timesyncCargo feature. - Python builds in this repo enable it by default ( pyproject.toml (source)).
When enabled, the build adds the TIME_SYNC endpoint (broadcast mode Always) plus built-in
time sync packet types in code.
The current model is router-owned:
- The router keeps an internal non-monotonic network clock separate from the monotonic clock you
use for router timing (
Router::new(...)onstd, orRouter::new_with_clock(...)when you provide a custom clock). - The monotonic clock drives scheduling, request timestamps, holdover, and slew; it is not treated as UTC.
-
TIME_SYNCtraffic is consumed internally by the router. It does not dispatch to normal local endpoint handlers. - Packet timestamps prefer the internal network clock when one is available.
- The internal clock can merge partial sources, for example date from one source and time-of-day or subsecond precision from another.
If a router transmits a time sync packet and the same frame loops back into that router, it is normally ignored by RX dedupe before internal time sync processing runs.
All payload fields are u64 values in little-endian order. Timestamps are in milliseconds.
-
TimeSyncAnnounce:[priority, time_ms] -
TimeSyncRequest:[seq, t1_ms] -
TimeSyncResponse:[seq, t1_ms, t2_ms, t3_ms]
With the discovery feature enabled, discovery also adds a built-in
DISCOVERY_TIMESYNC_SOURCES control packet that advertises concrete time source sender IDs.
The richer DISCOVERY_TOPOLOGY packet then attributes those source IDs to specific remote routers
and carries their inter-router connections.
t4_ms is captured locally when the response is received; it is not part of the packet payload.
The router handles the built-in time sync packet types internally:
-
TimeSyncAnnounceupdates per-remote-source state; the router does not collapse all remotes into one shared slot. -
TimeSyncRequestmay cause the router to queue an internal response when it is acting as a source. -
TimeSyncResponseupdates the leader's remote sample and steers the local network clock using the local monotonic receive time. - When discovery is enabled, outbound
TIME_SYNCtraffic prefers exact discovered source paths over genericTIME_SYNCendpoint reachability. - Internally generated
TimeSyncResponsepackets are returned to the requesting ingress side instead of being broadcast to every side.
Applications can read the resulting network time through:
- Rust:
router.network_time()/router.network_time_ms() - C:
seds_router_get_network_time()/seds_router_get_network_time_ms() - Python:
router.network_time()/router.network_time_ms()
TimeSyncTracker maintains the current best source and exposes a small state machine:
-
Consumer: follows the elected producer. Ifconsumer_promotion_enabledis set and there are no active producers, a consumer that already has non-uptime-based network time may temporarily promote itself to keep the network aligned. -
Source: participates in election as a producer. It announces and serves only while it is the elected leader; otherwise it follows the leader like a consumer. -
Auto: acts like an opportunistic producer. It only enters the election when no active producer is present and it has usable network time.
Sources are chosen by priority (lower is better). Ties are broken by sender ID (lexicographic).
When several producers have the same configured priority, the elected leader advertises a boosted
priority for as long as it remains leader so the tie resolves consistently without changing the
standby producers' configured priorities. If no announce is seen within source_timeout_ms, a
source is considered inactive.
Failover uses holdover plus slew:
- the network clock keeps running monotonically during producer loss or producer change
- a new leader does not step the clock backward or forward immediately
- instead, the router slews toward the new leader at the configured
max_slew_ppmrate
The internal clock also supports merging partial absolute sources:
- complete date from one source
- time-of-day from another source
- subsecond precision from another source
When a complete date+time base exists, the router advances it forward using the monotonic clock.
With both timesync and discovery enabled:
- discovery advertisements include
TIME_SYNCendpoint reachability - routers and relays also advertise reachable time source sender IDs
-
export_topology()includes both reachable endpoints and reachable time source IDs per side, plus a top-levelroutersgraph showing which router owns each source ID and how routers are connected - a consumer can route requests toward the exact side that leads to its selected source instead of
sending requests to every side that merely exposes
TIME_SYNC
If no exact source route is known yet, routing still falls back to ordinary endpoint-based discovery or flooding.
- A source periodically sends
TimeSyncAnnounce. - The elected follower sends
TimeSyncRequestwith monotonict1_ms. - The elected source replies with
TimeSyncResponseand timestampst2_ms/t3_msin network time. - The consumer captures
t4_msand callscompute_offset_delay(t1, t2, t3, t4).
The returned offset_ms and delay_ms mirror the standard NTP-style round trip calculation.
For most applications using router-managed timesync, you do not need to manually send or decode these packets. The router can announce, request, respond, and maintain the internal network clock itself once time sync is configured.
Drive that runtime with periodic(timeout_ms) in normal application loops. That runs time sync,
discovery, and queue draining together. If you need to skip time sync for a cycle while keeping the
feature enabled, use periodic_no_timesync(timeout_ms).
poll_timesync() remains available as a lower-level hook when you want to queue only due
announce/request traffic and manage the surrounding queue processing yourself.
If you want a producer to advertise real UTC, inject that absolute time through the local network time setters. The router's timing callback remains monotonic-only.
In addition to packet-driven sync, a source/master can set its local network time directly.
Rust router APIs:
set_local_network_time(PartialNetworkTime)set_local_network_date(...)set_local_network_time_hm(...)set_local_network_time_hms(...)set_local_network_time_hms_millis(...)set_local_network_time_hms_nanos(...)set_local_network_datetime(...)set_local_network_datetime_millis(...)set_local_network_datetime_nanos(...)
C APIs:
seds_router_set_local_network_time(...)seds_router_set_local_network_date(...)seds_router_set_local_network_time_hm(...)seds_router_set_local_network_time_hms(...)seds_router_set_local_network_time_hms_millis(...)seds_router_set_local_network_time_hms_nanos(...)seds_router_set_local_network_datetime(...)seds_router_set_local_network_datetime_millis(...)seds_router_set_local_network_datetime_nanos(...)
Python APIs:
router.set_local_network_time(...)router.set_local_network_date(...)router.set_local_network_time_hm(...)router.set_local_network_time_hms(...)router.set_local_network_time_hms_millis(...)router.set_local_network_time_hms_nanos(...)router.set_local_network_datetime(...)router.set_local_network_datetime_millis(...)router.set_local_network_datetime_nanos(...)
These setters are safe to call from multiple threads because the internal clock update is serialized by the router. For complete date+time values, the implementation re-samples the monotonic clock at commit so short context switches during the call do not leave the stored time stale.
Rust helpers live in sedsprintf_rs::timesync:
-
TimeSyncConfig,TimeSyncRole,TimeSyncTracker -
send_timesync_announce,send_timesync_request,send_timesync_response -
build_timesync_*,decode_timesync_*,compute_offset_delay
Router-managed APIs live on Router and the C/Python FFI surfaces described above.
Example implementations:
rust-example-code/timesync_example.rs (source)
c-example-code/src/timesync_example.c (source)
python-example/timesync_example.py (source)
rtos-example-code/freertos_timesync.c (source)
rtos-example-code/threadx_timesync.c (source)