⚠ This page is served via a proxy. Original site: https://github.com
This service does not collect credentials or authentication data.
Skip to content

Conversation

@4np
Copy link

@4np 4np commented Jan 21, 2026

Context

Problem statement

After cold start, repeaters without Real Time Clock (RTC) module will by default be set to May 15, 2024 10:52:31 UTC (the initial base date that's used throughout the firmware, e.g. 1715770351). Often, the time on these repeaters is then not synchronized properly and the time stays invalid for long periods of time.

Note: While working on this pull request, I noticed that quite some repeaters were sending adverts that were 1h off. I am in Europe, in the CET TimeZone, which is UTC+1. So it looks like quite some people are setting the clock on their repeater to CET rather than normalizing to UTC. I am unsure how that happens, perhaps through the Python CLI?

In more detail

TLDR: This PR adds automatic peer-based time synchronization for MeshCore nodes without hardware RTC modules, using airtime-compensated timestamps from nearby peers with statistical outlier filtering and hop-count-weighted consensus via the SETUP_RTC_WITH_PEER_SYNC macro.

PeerSyncRTCClock is an intelligent time synchronization wrapper that enables MeshCore nodes without hardware RTC modules to maintain accurate time by synchronizing with nearby peers' advertisement packet timestamps. This is particularly critical for repeater nodes and low-cost variants that lack dedicated RTC hardware but still need accurate timekeeping for message timestamps, telemetry data, and network coordination. When a hardware RTC is detected via I2C auto-discovery during initialization, it takes absolute priority and peer sync is completely disabled; otherwise, the system automatically activates peer-based synchronization as a fallback.

The synchronization process collects timestamps from received advertisement packets and compensates for multi-hop transmission delay by adding the estimated total airtime calculated as (hop_count + 1) × airtime_per_hop_ms, where airtime is dynamically computed from current radio settings (spreading factor, bandwidth, packet length). This airtime compensation is crucial because it avoids circular logic—we cannot use our potentially-incorrect local clock to measure elapsed time when trying to fix that clock, so instead we adjust timestamps forward by the estimated transmission duration before storing them. The system then applies robust statistical outlier filtering using MAD (Median Absolute Deviation) with a 3×MAD threshold to reject malicious or incorrect timestamps, requiring at least 70% consensus (15 of 21 samples by default) before trusting the result.

After filtering, the system calculates a weighted median where closer peers receive exponentially higher influence based on hop count. A 1-hop peer gets weight 20 while a 20-hop peer gets weight 1, ensuring that nearby trusted sources dominate the consensus rather than distant or potentially compromised nodes. The system uses adaptive validation that's lenient (May 2024 to May 2034 range) before the first successful sync to bootstrap from a cold start. It becomes strict (±24 hours) afterward to prevent drift. Once the clock is accurate (offset less than 2 minutes), peer sync automatically pauses for a configurable duration (default 24 hours) to reduce CPU overhead, resuming periodically to verify continued accuracy. All timing operations use RTC timestamps rather than millis(), making the system fully compatible with deep sleep modes (for example, see #1353).

This represents a fundamental improvement over the previous system, which had no peer-based synchronization capability whatsoever—nodes and repeaters without hardware RTC would drift indefinitely with no mechanism to self-correct, rendering timestamps on messages, telemetry readings, and log entries increasingly meaningless over time. Now these variants can automatically maintain time accuracy within seconds across the entire mesh through distributed consensus, being robust against both accidental clock errors and intentional time-spoofing attacks through statistical validation and proximity-based trust weighting.

Features

Priority System:

  • Hardware RTC always takes priority when present (detected via I2C auto-discovery)
  • Peer sync only activates as fallback when no hardware RTC is found

Timestamp Collection:

  • Collects timestamps from received advertisement packets
  • Filters by hop count (default: max 20 hops for reliability)
  • Compensates for multi-hop transmission delay: timestamp + ((hop_count + 1) × airtime_per_hop_ms)
  • Records collection time using RTC timestamps (sleep-safe, not millis())
  • Adjusts timestamps for elapsed time before using them (accounts for clock ticking)
  • Adaptive validation:
    • Before first sync: Lenient validation (May 2024 to ~May 2034 range) to bootstrap from cold start
    • After successful sync: Strict 24-hour validation to prevent drift
  • Maintains rolling buffer of recent timestamps

Statistical Outlier Filtering + Weighted Median:

  • Collects minimum sample size (default: 21 samples) before filtering
  • Applies MAD (Median Absolute Deviation) to detect outliers:
    • Calculates initial median of all samples
    • Computes MAD = median(|timestamp - median|)
    • Filters timestamps beyond median ± 3×MAD (minimum 60 seconds threshold)
    • Removes detected outliers from buffer permanently
  • Requires minimum consensus (default: 15 of 21 = ~70% good samples)
  • Calculates weighted median of filtered samples
  • Weights by hop count: closer peers = higher influence
    • Weight formula: (MAX_HOP_COUNT + 1 - actual_hop_count)
    • Example: 1-hop gets weight 20, 20-hop gets weight 1

Clock Update Logic:

  • Only updates if median is ahead by minimum threshold (default: 2 minutes)
  • Rate-limited to prevent frequent updates (minimum 5 minutes between syncs)
  • Clears sample buffer after successful sync
  • Automatically pauses peer sync when clock is accurate (default: 24 hours) to reduce CPU overhead
  • Resumes peer sync after pause period expires or when clock needs adjustment

Configuration Options:

  • PEER_SYNC_MAX_HOP_COUNT: Maximum hops for time sync (default: 20)
  • PEER_SYNC_MIN_OFFSET_SECONDS: Minimum offset to trigger sync (default: 120s / 2 min)
  • PEER_SYNC_SAMPLE_SIZE: Timestamps to collect before outlier filtering and median timestamp determination (default: 21)
  • PEER_SYNC_MIN_SAMPLES_AFTER_FILTERING: Minimum good samples after outlier filtering (default: 15)
  • PEER_SYNC_MIN_SYNCS_BEFORE_STRICT_VALIDATION: Successful syncs before 24h validation (default: 1)
  • PEER_SYNC_PAUSE_DURATION_SECONDS: Pause duration when clock is accurate (default: 86400s / 24h)

Scope

I have implemented the changes for the RAK4631 variant to make it easier to review. I'll add the changes for the other variants when this has been reviewed.

Relevant issues

Disclaimer

I have used Claude Code to assist with this implementation.

Examples

Cold start (the 1st clock sync)

After initial cold start, and an initial advert is heard:

DEBUG: Mesh::collectPeerTimestamp: timestamp=1715878353, hop_count=5, airtime_per_hop=1229ms
DEBUG: PeerSyncRTCClock::addPeerTimestamp called: timestamp=1715878353, hop_count=5, airtime_per_hop=1229ms
DEBUG: PeerSyncRTCClock: Current time: 1715770445, Peer timestamp: 1715878353, Diff: 107908 seconds
DEBUG: PeerSyncRTCClock: Timestamp validation passed (initial mode, syncs=0/1)
DEBUG: PeerSyncRTCClock: Compensating for airtime: 5 hops (+1 transmission) × 1229ms = 7s, adjusted 1715878353 → 1715878360 (2024-05-16 16:52:40 UTC)
DEBUG: PeerSyncRTCClock: Timestamp accepted, sample_count=1
DEBUG: PeerSyncRTCClock: Weighted median from 1 samples (total weight: 16, position: 0, hops: 5)
DEBUG: PeerSyncRTCClock: Collecting timestamps (1 of 21 needed samples)
DEBUG:   Weighted median estimate: 1715878360 (2024-05-16 16:52:40 UTC), drift: +1 days, 5 hours

After we have 20 samples since cold start:

DEBUG: Mesh::collectPeerTimestamp: timestamp=1715843095, hop_count=12, airtime_per_hop=1295ms
DEBUG: PeerSyncRTCClock::addPeerTimestamp called: timestamp=1715843095, hop_count=12, airtime_per_hop=1295ms
DEBUG: PeerSyncRTCClock: Current time: 1715772367, Peer timestamp: 1715843095, Diff: 70728 seconds
DEBUG: PeerSyncRTCClock: Timestamp validation passed (initial mode, syncs=0/1)
DEBUG: PeerSyncRTCClock: Compensating for airtime: 12 hops (+1 transmission) × 1295ms = 16s, adjusted 1715843095 → 1715843111 (2024-05-16 07:05:11 UTC)
DEBUG: PeerSyncRTCClock: Timestamp accepted, sample_count=20
DEBUG: PeerSyncRTCClock: Weighted median from 20 samples (total weight: 230, position: 10, hops: 7)
DEBUG: PeerSyncRTCClock: Collecting timestamps (20 of 21 needed samples)
DEBUG:   Weighted median estimate: 1769000785 (2026-01-21 13:06:25 UTC), drift: +616 days, 1 hours

Note: samples with hops exceeding PEER_SYNC_MAX_HOP_COUNT will be ignore / discarded.

After the 21st sample (e.g. PEER_SYNC_SAMPLE_SIZE), when there is a pool of timestamps that's large enough to reliably determine a median timestamp, outlier detection will discard the samples that deviate too much:

DEBUG: Mesh::collectPeerTimestamp: timestamp=1715770353, hop_count=18, airtime_per_hop=1295ms
DEBUG: PeerSyncRTCClock::addPeerTimestamp called: timestamp=1715770353, hop_count=18, airtime_per_hop=1295ms
DEBUG: PeerSyncRTCClock: Current time: 1715772406, Peer timestamp: 1715770353, Diff: -2053 seconds
DEBUG: PeerSyncRTCClock: Timestamp validation passed (initial mode, syncs=0/1)
DEBUG: PeerSyncRTCClock: Compensating for airtime: 18 hops (+1 transmission) × 1295ms = 24s, adjusted 1715770353 → 1715770377 (2024-05-15 10:52:57 UTC)
DEBUG: PeerSyncRTCClock: Timestamp accepted, sample_count=21
DEBUG: PeerSyncRTCClock: Removed 10 persistent outliers from buffer, 11 samples remain
DEBUG: PeerSyncRTCClock: Only 11 of 21 samples remain after filtering (need 15 minimum)

In this example, MAD (Median Absolute Deviation) based outlier detection discarded 10 outliers, resulting in a remaining pool of 11 samples. As this is lower than 15 (e.g. PEER_SYNC_MIN_SAMPLES_AFTER_FILTERING) collection continues until we again have a pool of 21 samples (e.g. PEER_SYNC_SAMPLE_SIZE):

DEBUG: PeerSyncRTCClock::addPeerTimestamp called: timestamp=1769001870, hop_count=7, airtime_per_hop=1229ms
DEBUG: PeerSyncRTCClock: Current time: 1715773229, Peer timestamp: 1769001870, Diff: 53228641 seconds
DEBUG: PeerSyncRTCClock: Timestamp validation passed (initial mode, syncs=0/1)
DEBUG: PeerSyncRTCClock: Compensating for airtime: 7 hops (+1 transmission) × 1229ms = 9s, adjusted 1769001870 → 1769001879 (2026-01-21 13:24:39 UTC)
DEBUG: PeerSyncRTCClock: Timestamp accepted, sample_count=21
DEBUG: PeerSyncRTCClock: Removed 1 persistent outliers from buffer, 20 samples remain
DEBUG: PeerSyncRTCClock::attemptClockSync called, sample_count=20
DEBUG: PeerSyncRTCClock: Collected samples:
DEBUG:   Sample 1: timestamp=1769001667 (2026-01-21 13:21:07 UTC), hops=10, weight=11, age=2689s
DEBUG:   Sample 2: timestamp=1769001647 (2026-01-21 13:20:47 UTC), hops=7, weight=14, age=2564s
DEBUG:   Sample 3: timestamp=1769001626 (2026-01-21 13:20:26 UTC), hops=13, weight=8, age=2513s
DEBUG:   Sample 4: timestamp=1769001628 (2026-01-21 13:20:28 UTC), hops=4, weight=17, age=2383s
DEBUG:   Sample 5: timestamp=1769001684 (2026-01-21 13:21:24 UTC), hops=16, weight=5, age=1791s
DEBUG:   Sample 6: timestamp=1769001684 (2026-01-21 13:21:24 UTC), hops=0, weight=21, age=1600s
DEBUG:   Sample 7: timestamp=1769001686 (2026-01-21 13:21:26 UTC), hops=13, weight=8, age=1429s
DEBUG:   Sample 8: timestamp=1769001648 (2026-01-21 13:20:48 UTC), hops=8, weight=13, age=1318s
DEBUG:   Sample 9: timestamp=1769001682 (2026-01-21 13:21:22 UTC), hops=3, weight=18, age=1228s
DEBUG:   Sample 10: timestamp=1769001653 (2026-01-21 13:20:53 UTC), hops=10, weight=11, age=912s
DEBUG:   Sample 11: timestamp=1769001664 (2026-01-21 13:21:04 UTC), hops=8, weight=13, age=891s
DEBUG:   Sample 12: timestamp=1769001647 (2026-01-21 13:20:47 UTC), hops=10, weight=11, age=810s
DEBUG:   Sample 13: timestamp=1769001677 (2026-01-21 13:21:17 UTC), hops=2, weight=19, age=646s
DEBUG:   Sample 14: timestamp=1769001644 (2026-01-21 13:20:44 UTC), hops=8, weight=13, age=605s
DEBUG:   Sample 15: timestamp=1769001612 (2026-01-21 13:20:12 UTC), hops=10, weight=11, age=557s
DEBUG:   Sample 16: timestamp=1769001683 (2026-01-21 13:21:23 UTC), hops=0, weight=21, age=514s
DEBUG:   Sample 17: timestamp=1769001662 (2026-01-21 13:21:02 UTC), hops=10, weight=11, age=234s
DEBUG:   Sample 18: timestamp=1769001660 (2026-01-21 13:21:00 UTC), hops=8, weight=13, age=205s
DEBUG:   Sample 19: timestamp=1769001658 (2026-01-21 13:20:58 UTC), hops=6, weight=15, age=177s
DEBUG:   Sample 20: timestamp=1769001714 (2026-01-21 13:21:54 UTC), hops=8, weight=13, age=44s
DEBUG:   Raw statistics: min=1769001612 (2026-01-21 13:20:12 UTC), max=1769001714 (2026-01-21 13:21:54 UTC), spread=102 seconds, average=51014742
DEBUG: PeerSyncRTCClock: Weighted median from 20 samples (total weight: 266, position: 10, hops: 10)
DEBUG: PeerSyncRTCClock: Offset check: offset=53228433, min_required=120
DEBUG: PeerSyncRTCClock: Weighted median: 1769001662 (2026-01-21 13:21:02 UTC)
DEBUG: PeerSyncRTCClock: *** SYNCING CLOCK ***
DEBUG:   First clock sync
DEBUG:   Syncing clock from 20 peers
DEBUG:   Old time: 1715773229 (2024-05-15 11:40:29 UTC)
DEBUG:   New time: 1769001662 (2026-01-21 13:21:02 UTC)
DEBUG:   Offset: +53228433 seconds (+887140 minutes)
DEBUG: PeerSyncRTCClock: Clock sync complete, total_syncs=1
DEBUG: PeerSyncRTCClock: Strict 24h validation will be ENABLED on next sync

This time, the outlier detection only removed a single outlier, resulting in a remaining pool of 20 timestamps. As this is higher than 15 (e.g. PEER_SYNC_MIN_SAMPLES_AFTER_FILTERING), the pool will be used to determine the weighted median timestamp and synchronize the clock.

In this particular case the peer-synced clock was about 40 seconds off compared to the actual UTC time.

After the first clock sync

The first clock sync after cold start is quite lenient as it will accept a time between May 15, 2024 10:52:31 UTC (the initial base date that's used throughout the firmware: 1715770351) and ~May 2034 (base + 10 years).

After the first clock sync, the Peer based synchronization logic becomes more strict and will only accept timestamp that lie within a 24h difference.

DEBUG: PeerSyncRTCClock::addPeerTimestamp called: timestamp=1769001735, hop_count=12, airtime_per_hop=1229ms
DEBUG: PeerSyncRTCClock: Current time: 1769001744, Peer timestamp: 1769001735, Diff: -9 seconds
DEBUG: PeerSyncRTCClock: Timestamp validation passed (strict 24h mode)
DEBUG: PeerSyncRTCClock: Compensating for airtime: 12 hops (+1 transmission) × 1229ms = 15s, adjusted 1769001735 → 1769001750 (2026-01-21 13:22:30 UTC)
DEBUG: PeerSyncRTCClock: Timestamp accepted, sample_count=1
DEBUG: PeerSyncRTCClock: Weighted median from 1 samples (total weight: 9, position: 0, hops: 12)
DEBUG: PeerSyncRTCClock: Collecting timestamps (1 of 21 needed samples)
DEBUG:   Weighted median estimate: 1769001750 (2026-01-21 13:22:30 UTC), drift: +6 seconds

After again 21 samples are collected (e.g. PEER_SYNC_SAMPLE_SIZE), the computed drift is now much smaller:

DEBUG: Mesh::collectPeerTimestamp: timestamp=1769005474, hop_count=13, airtime_per_hop=1164ms
DEBUG: PeerSyncRTCClock::addPeerTimestamp called: timestamp=1769005474, hop_count=13, airtime_per_hop=1164ms
DEBUG: PeerSyncRTCClock: Current time: 1769005495, Peer timestamp: 1769005474, Diff: -21 seconds
DEBUG: PeerSyncRTCClock: Timestamp validation passed (strict 24h mode)
DEBUG: PeerSyncRTCClock: Compensating for airtime: 13 hops (+1 transmission) × 1164ms = 16s, adjusted 1769005474 → 1769005490 (2026-01-21 14:24:50 UTC)
DEBUG: PeerSyncRTCClock: Timestamp accepted, sample_count=21
DEBUG: PeerSyncRTCClock: Removed 2 persistent outliers from buffer, 19 samples remain
DEBUG: PeerSyncRTCClock::attemptClockSync called, sample_count=19
DEBUG: PeerSyncRTCClock: Collected samples:
DEBUG:   Sample 1: timestamp=1769005501 (2026-01-21 14:25:01 UTC), hops=12, weight=9, age=3751s
DEBUG:   Sample 2: timestamp=1769005493 (2026-01-21 14:24:53 UTC), hops=10, weight=11, age=3689s
DEBUG:   Sample 3: timestamp=1769005485 (2026-01-21 14:24:45 UTC), hops=13, weight=8, age=3525s
DEBUG:   Sample 4: timestamp=1769005471 (2026-01-21 14:24:31 UTC), hops=10, weight=11, age=2523s
DEBUG:   Sample 5: timestamp=1769005482 (2026-01-21 14:24:42 UTC), hops=9, weight=12, age=2435s
DEBUG:   Sample 6: timestamp=1769005520 (2026-01-21 14:25:20 UTC), hops=5, weight=16, age=2163s
DEBUG:   Sample 7: timestamp=1769005511 (2026-01-21 14:25:11 UTC), hops=3, weight=18, age=2019s
DEBUG:   Sample 8: timestamp=1769005544 (2026-01-21 14:25:44 UTC), hops=11, weight=10, age=1620s
DEBUG:   Sample 9: timestamp=1769005488 (2026-01-21 14:24:48 UTC), hops=12, weight=9, age=1443s
DEBUG:   Sample 10: timestamp=1769005505 (2026-01-21 14:25:05 UTC), hops=6, weight=15, age=1343s
DEBUG:   Sample 11: timestamp=1769005498 (2026-01-21 14:24:58 UTC), hops=7, weight=14, age=952s
DEBUG:   Sample 12: timestamp=1769005502 (2026-01-21 14:25:02 UTC), hops=7, weight=14, age=750s
DEBUG:   Sample 13: timestamp=1769005472 (2026-01-21 14:24:32 UTC), hops=7, weight=14, age=680s
DEBUG:   Sample 14: timestamp=1769005502 (2026-01-21 14:25:02 UTC), hops=6, weight=15, age=645s
DEBUG:   Sample 15: timestamp=1769005479 (2026-01-21 14:24:39 UTC), hops=9, weight=12, age=610s
DEBUG:   Sample 16: timestamp=1769005469 (2026-01-21 14:24:29 UTC), hops=16, weight=5, age=463s
DEBUG:   Sample 17: timestamp=1769005518 (2026-01-21 14:25:18 UTC), hops=0, weight=21, age=151s
DEBUG:   Sample 18: timestamp=1769005484 (2026-01-21 14:24:44 UTC), hops=8, weight=13, age=60s
DEBUG:   Sample 19: timestamp=1769005490 (2026-01-21 14:24:50 UTC), hops=13, weight=8, age=0s
DEBUG:   Raw statistics: min=1769005469 (2026-01-21 14:24:29 UTC), max=1769005544 (2026-01-21 14:25:44 UTC), spread=75 seconds, average=186649123
DEBUG: PeerSyncRTCClock: Weighted median from 19 samples (total weight: 235, position: 10, hops: 7)
DEBUG: PeerSyncRTCClock: Offset check: offset=3, min_required=120
DEBUG: PeerSyncRTCClock: Weighted median: 1769005498 (2026-01-21 14:24:58 UTC)
DEBUG: PeerSyncRTCClock: Offset 3 is less than minimum 120, not syncing
DEBUG: PeerSyncRTCClock: Clock accurate, pausing peer sync for 24 hours to reduce CPU overhead

The weighted median for this run determined that the clock offset is now 3 seconds. As this is lower than the threshold of 120 seconds (e.g. PEER_SYNC_MIN_OFFSET_SECONDS), the clock is not updated.

To reduce CPU cycles, peer based clock synchronization is now paused for 24 hours (e.g. PEER_SYNC_PAUSE_DURATION_SECONDS).

Note: as the peer-based clock synchronization will never be as accurate as a true RTC module, it will always deviate to some extent from the actual UTC time. In my testing it deviated about 30 seconds, which IMHO is pretty accurate.

4np added 2 commits January 20, 2026 17:26
…rts sent by peers.

PeerSyncRTCClock is an intelligent time synchronization wrapper that enables MeshCore nodes without hardware RTC modules to maintain accurate time by synchronizing with nearby peers' advertisement packet timestamps. This is particularly critical for repeater nodes and low-cost variants that lack dedicated RTC hardware but still need accurate timekeeping for message timestamps, telemetry data, and network coordination. When a hardware RTC is detected via I2C auto-discovery during initialization, it takes absolute priority and peer sync is completely disabled; otherwise, the system automatically activates peer-based synchronization as a fallback.

The synchronization process collects timestamps from received advertisement packets and compensates for multi-hop transmission delay by adding the estimated total airtime calculated as (hop_count + 1) × airtime_per_hop_ms, where airtime is dynamically computed from current radio settings (spreading factor, bandwidth, packet length). This airtime compensation is crucial because it avoids circular logic—we cannot use our potentially-incorrect local clock to measure elapsed time when trying to fix that clock, so instead we adjust timestamps forward by the estimated transmission duration before storing them. The system then applies robust statistical outlier filtering using MAD (Median Absolute Deviation) with a 3×MAD threshold to reject malicious or incorrect timestamps, requiring at least 70% consensus (15 of 21 samples by default) before trusting the result.

After filtering, the system calculates a weighted median where closer peers receive exponentially higher influence based on hop count. A 1-hop peer gets weight 20 while a 20-hop peer gets weight 1, ensuring that nearby trusted sources dominate the consensus rather than distant or potentially compromised nodes. The system uses adaptive validation that's lenient (May 2024 to May 2034 range) before the first successful sync to bootstrap from a cold start but becomes strict (±24 hours) afterward to prevent drift. Once the clock is accurate (offset less than 2 minutes), peer sync automatically pauses for a configurable duration (default 24 hours) to reduce CPU overhead, resuming periodically to verify continued accuracy. All timing operations use RTC timestamps rather than millis(), making the system fully compatible with deep sleep modes common in battery-powered repeaters and sensor nodes.

This represents a fundamental improvement over the previous system, which had no peer-based synchronization capability whatsoever—nodes and repeaters without hardware RTC would drift indefinitely with no mechanism to self-correct, rendering timestamps on messages, telemetry readings, and log entries increasingly meaningless over time. Now these variants can automatically maintain time accuracy within seconds across the entire mesh through distributed consensus, being robust against both accidental clock errors and intentional time-spoofing attacks through statistical validation and proximity-based trust weighting.

MESH_DEBUG_PRINTLN("RadioLibWrapper: noise_floor = %d", (int)_noise_floor);
// Only log if noise floor changed
if (_noise_floor != old_noise_floor) {
Copy link
Author

Choose a reason for hiding this comment

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

This log entry is rather chatty, this will now only log whenever the noise floor changes.

@@ -0,0 +1,524 @@
/*
* PEER-BASED TIME SYNCHRONIZATION SYSTEM
Copy link
Author

@4np 4np Jan 21, 2026

Choose a reason for hiding this comment

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

The peer based time synchronization logic is also explained here in detail in in-line documentation.

#include <helpers/AutoDiscoverRTCClock.h>

/**
* \brief Helper macro to setup RTC clock with automatic peer synchronization
Copy link
Author

@4np 4np Jan 21, 2026

Choose a reason for hiding this comment

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

This is the new helper macro to simplify RTC use in the various hardware variants.

@4np 4np marked this pull request as ready for review January 21, 2026 15:07
@oltaco
Copy link
Contributor

oltaco commented Jan 21, 2026

Thanks for trying to solve clock sync. I’m not convinced that this relatively minor issue warrants the additional complexity introduced by this PR. A simpler and safer approach might be to allow repeater owners to configure a whitelist of nodes whose adverts they trust?

There’s also a non-trivial risk of clock poisoning when relying on anonymous advert timestamps, and a future dated clock can be a more annoying problem for ESP32 repeaters which can't just restart to reset their clock.

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.

2 participants