-
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 | mirror)).
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). -
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.
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 the router's internal network clock source state. -
TimeSyncRequestmay cause the router to queue an internal response when it is acting as a source. -
TimeSyncResponseupdates the consumer-side estimate 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: never announces; uses the best active source. -
Source: always announces. -
Auto: announces only when no active source is present.
Sources are chosen by priority (lower is better). Ties are broken by sender ID (lexicographic).
The tracker keeps the active source set, so if the current winner becomes inactive and another
source with the same or worse priority is still active, failover can happen immediately without
waiting for a fresh announce. If no announce is seen within source_timeout_ms, a source is
considered inactive.
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 - 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. - A consumer sends
TimeSyncRequestwitht1_ms. - The source replies with
TimeSyncResponseand timestampst2_ms/t3_ms. - 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 by calling poll_timesync() periodically from your main loop, then processing
the router's normal TX/RX queues. The poll is non-blocking: it only queues an announce or request
when the configured interval says work is due.
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: