Improve repeater LoRa clock synchronization
This commit is contained in:
1 parent
6562d15a90
commit
3897794416
4 files changed
+121
-53
No files matched your search
+29
-10
@@ -590,6 +590,7 @@ send text.flood checking ridge link
|
||||
- `get clock.sync.status`
|
||||
- `get clock.sync.mesh`
|
||||
- `set clock.sync.mesh <on|off>`
|
||||
- `clock.sync.mesh now`
|
||||
- `get clock.sync.internet`
|
||||
- `set clock.sync.internet <on|off>`
|
||||
- `get clock.sync.drift`
|
||||
@@ -598,7 +599,7 @@ send text.flood checking ridge link
|
||||
- `set clock.sync.samples <3-16>`
|
||||
|
||||
**Defaults:**
|
||||
- `clock.sync.mesh`: `off`
|
||||
- `clock.sync.mesh`: `on` for nRF52 repeaters; `off` for other builds
|
||||
- `clock.sync.internet`: `off`
|
||||
- `clock.sync.drift`: `3600` seconds
|
||||
- `clock.sync.samples`: `9`
|
||||
@@ -607,8 +608,22 @@ When either source is enabled, the repeater makes its first clock-bootstrap
|
||||
attempt after 30 minutes of uptime. A successful estimate changes the RTC only
|
||||
when the absolute difference is **greater than** `clock.sync.drift`; correction
|
||||
can move the clock forward or backward. A valid estimate within the threshold
|
||||
completes the one-time check without changing the clock. If no source is
|
||||
available yet, the repeater retries every 30 minutes.
|
||||
counts as a successful sync without changing the clock. Seven days after each
|
||||
successful estimate, the repeater evaluates time again; the seven-day deadline
|
||||
therefore starts from the last successful estimate rather than from boot. This
|
||||
is a lazy uptime deadline: the check runs on the first normal loop/wake after it
|
||||
becomes due and does not wake the device by itself. If no source or consensus is
|
||||
available, the repeater retries every 30 minutes until one succeeds, then starts
|
||||
a new seven-day interval. Every reboot starts with the initial 30-minute attempt.
|
||||
An existing saved setting always overrides the platform default.
|
||||
|
||||
`clock.sync.mesh now` bypasses the startup/seven-day deadline and queues a
|
||||
LoRa-only consensus evaluation on the next normal loop, even when the internet
|
||||
source is also enabled. It uses any currently fresh samples without clearing the
|
||||
16-slot table. If there is not yet enough evidence, mesh collection remains open
|
||||
and the next attempt follows the normal 30-minute retry. The command requires
|
||||
`clock.sync.mesh` to be on and does not bypass CLI, GPS, or NTP suppression; it
|
||||
also retains the normal quorum, timestamp-validity, and drift checks.
|
||||
|
||||
`clock.sync.mesh` collects signature-verified advert timestamps and MAC-valid,
|
||||
decrypted Public-channel plain-text timestamps, but only after the packet passes
|
||||
@@ -626,6 +641,9 @@ succeed but an 8-vs-8 split cannot. The median is used. `clock.sync.samples`
|
||||
accepts `3` through `16`; samples older than two hours are ignored. Status
|
||||
reports `collecting` when fewer than the configured number of paths exist, and
|
||||
`no consensus` when enough paths exist but the effective quorum does not agree.
|
||||
Mesh collection begins immediately after boot. Following a successful estimate,
|
||||
it resumes two hours before the next seven-day deadline so only evidence that
|
||||
can still be fresh at evaluation time is processed.
|
||||
|
||||
A timestamp is eligible for a clock-sync sample only when it falls between the
|
||||
UTC build epoch embedded by `build.sh` and that time plus ten calendar years.
|
||||
@@ -664,15 +682,16 @@ prove that the advertising node's own clock is correct. Mesh time is therefore
|
||||
a consensus estimate, not an authoritative time service.
|
||||
|
||||
`clock.sync.internet` is available on WiFi MQTT repeater-observer builds. Its
|
||||
30-minute query runs on the MQTT/WiFi task and is read-only until the repeater
|
||||
applies the configured drift test. On other repeater builds, the preference can
|
||||
be stored but status reports that internet time is unavailable. MQTT builds
|
||||
retain their existing startup NTP behavior required for MQTT/TLS/JWT operation;
|
||||
this setting controls the additional delayed drift check. Startup NTP is always
|
||||
preferred when it succeeds, regardless of this setting.
|
||||
initial and seven-day queries run on the MQTT/WiFi task and are read-only until
|
||||
the repeater applies the configured drift test. Failed queries retry after 30
|
||||
minutes. On other repeater builds, the preference can be stored but status
|
||||
reports that internet time is unavailable. MQTT builds retain their existing
|
||||
startup NTP behavior required for MQTT/TLS/JWT operation; this setting controls
|
||||
the additional delayed drift checks. Startup NTP is always preferred when it
|
||||
succeeds, regardless of this setting.
|
||||
|
||||
Changing any `clock.sync.*` setting starts a new attempt for the current boot.
|
||||
Settings are persistent in `/clock_sync`.
|
||||
Settings are persistent in `/clock_sync`; samples and schedule state are not.
|
||||
|
||||
A backward correction is intentionally allowed, but peers that already recorded
|
||||
a later timestamp from this node may temporarily reject its lower timestamps as
|
||||
|
||||
@@ -86,8 +86,8 @@ set flood.retry.ignore none
|
||||
| `flood.channel.scope` | Assigns a transport-region scope to received unscoped floods. Exact channel keys beat `txt:*`; `login:*` covers the remote-login family, and `other:*` covers every remaining flood type except TRACE, including OTA. TRACE remains unchanged across scope boundaries. ACL permission `4` can manage the table. | `get flood.channel.scope[.n]`, `set flood.channel.scope[.n] <channel|txt:*|login:*|other:*> <region>`, `del flood.channel.scope.<n>|all` | `set flood.channel.scope login:* west` |
|
||||
| `flood.filter` | Persistent repeater-only forwarding rules for flood routes `0x00`/`0x01`, selected by payload type and optional received hop count/range (omitted means `all`). New tables seed slot 1 with `ota all suspend=tempradio`; only rows marked `suspend=tempradio` are skipped during temporary-radio operation. Login-capable `anon_req`/`path`/`response` types become filterable at hop `7`; flood `txt_msg` becomes filterable at hop `5`. Standard direct traceroute, other direct routing, and local receive/logging are unchanged. | `get flood.filter[.n]`, `set flood.filter[.n] <type> [N|N+|N-M|all] [suspend=tempradio]`, `del flood.filter.<n>|all` | `set flood.filter.1 0x0C all suspend=tempradio` |
|
||||
| `flood.moderation` | Decrypts keyed `GRP_TXT` channels and applies drop, per-username messages/minute, and maximum-hop controls, optionally matched against the first 1-3 path hashes. Supports `public`, `#channel`, and 128/256-bit channel keys. Sender names and truncated path hashes are moderation hints, not authenticated identities. | `get flood.moderation[.n]`, `set flood.moderation[.n] <channel> <sender> <drop|rate=X/min|hops=N> [path=...]`, `del flood.moderation.<n>|all` | `set flood.moderation public "Noisy User" rate=5/min hops=4` |
|
||||
| `clock.sync.mesh` | After 30 minutes of uptime, estimates UTC from a configurable consensus of fresh signed-advert or valid Public-channel sources. Only timestamps from firmware build time through build time plus ten years are recorded. Successful CLI, GPS, or WiFi/NTP clock updates suppress LoRa time collection until reboot; after reboot LoRa is the fallback if NTP cannot sync. | `get clock.sync.mesh`, `set clock.sync.mesh <on|off>`, `get clock.sync.status` | `set clock.sync.mesh on` |
|
||||
| `clock.sync.internet` | Adds a read-only internet/NTP estimate at the same delayed check on WiFi MQTT repeater-observer builds. Other builds retain the setting but report internet unavailable. | `get clock.sync.internet`, `set clock.sync.internet <on|off>` | `set clock.sync.internet on` |
|
||||
| `clock.sync.mesh` | Defaults on for nRF52 repeaters and off for other builds; a saved setting overrides that default. After 30 minutes of uptime, it estimates UTC from a configurable consensus of fresh signed-advert or valid Public-channel sources, then repeats lazily seven days after each successful estimate. Failed attempts retry every 30 minutes; `clock.sync.mesh now` queues an immediate LoRa-only attempt without bypassing quorum or source suppression. Only timestamps from firmware build time through build time plus ten years are recorded. Successful CLI, GPS, or WiFi/NTP clock updates suppress LoRa time collection until reboot; after reboot LoRa is the fallback if NTP cannot sync. | `get clock.sync.mesh`, `set clock.sync.mesh <on|off>`, `clock.sync.mesh now`, `get clock.sync.status` | `set clock.sync.mesh on` |
|
||||
| `clock.sync.internet` | Adds a read-only internet/NTP estimate at the same initial and seven-day checks on WiFi MQTT repeater-observer builds. Other builds retain the setting but report internet unavailable. | `get clock.sync.internet`, `set clock.sync.internet <on|off>` | `set clock.sync.internet on` |
|
||||
| `clock.sync.drift` | Absolute correction threshold in seconds. The clock is moved forward or backward only when the estimate differs by more than this value. | `get clock.sync.drift`, `set clock.sync.drift <30-86400>` | `set clock.sync.drift 3600` |
|
||||
| `clock.sync.samples` | Minimum number of distinct, in-window receive paths that must agree before mesh time can be used. A strict majority of all fresh samples is also required. Range `3-16`; default `9`. | `get clock.sync.samples`, `set clock.sync.samples <3-16>` | `set clock.sync.samples 9` |
|
||||
| `outpath` | Overrides the primary direct route used for replies to the current remote client. | `get outpath`, `set outpath <hops>`, `set outpath direct`, `set outpath clear`, `set outpath flood` | `set outpath A1B2C3,D4E5F6` |
|
||||
|
||||
@@ -2457,10 +2457,11 @@ MyMesh::MyMesh(mesh::MainBoard &board, mesh::Radio &radio, mesh::MillisecondCloc
|
||||
memset(flood_channel_scopes, 0, sizeof(flood_channel_scopes));
|
||||
memset(flood_group_moderation, 0, sizeof(flood_group_moderation));
|
||||
memset(clock_sync_samples, 0, sizeof(clock_sync_samples));
|
||||
clock_sync_mesh_enabled = false;
|
||||
clock_sync_mesh_enabled = CLOCK_SYNC_MESH_DEFAULT_ENABLED != 0;
|
||||
clock_sync_internet_enabled = false;
|
||||
clock_sync_complete = false;
|
||||
clock_sync_internet_pending = false;
|
||||
clock_sync_force_mesh_pending = false;
|
||||
clock_sync_mesh_suppressed_by = CLOCK_SYNC_MESH_SUPPRESS_NONE;
|
||||
clock_sync_last_result = CLOCK_SYNC_RESULT_WAITING;
|
||||
clock_sync_last_source = CLOCK_SYNC_SOURCE_NONE;
|
||||
@@ -5326,7 +5327,7 @@ void MyMesh::deleteFloodGroupModeration(const char* args, char* reply) {
|
||||
}
|
||||
|
||||
void MyMesh::loadClockSyncPrefs() {
|
||||
clock_sync_mesh_enabled = false;
|
||||
clock_sync_mesh_enabled = CLOCK_SYNC_MESH_DEFAULT_ENABLED != 0;
|
||||
clock_sync_internet_enabled = false;
|
||||
clock_sync_drift_seconds = CLOCK_SYNC_DRIFT_DEFAULT_SECONDS;
|
||||
clock_sync_required_samples = CLOCK_SYNC_REQUIRED_SAMPLES_DEFAULT;
|
||||
@@ -5383,6 +5384,7 @@ bool MyMesh::saveClockSyncPrefs() {
|
||||
void MyMesh::resetClockSyncAttempt() {
|
||||
clock_sync_complete = false;
|
||||
clock_sync_internet_pending = false;
|
||||
clock_sync_force_mesh_pending = false;
|
||||
clock_sync_last_result = CLOCK_SYNC_RESULT_WAITING;
|
||||
clock_sync_last_source = CLOCK_SYNC_SOURCE_NONE;
|
||||
clock_sync_last_sample_count = 0;
|
||||
@@ -5409,6 +5411,7 @@ void MyMesh::suppressMeshClockSyncForBoot(uint8_t source) {
|
||||
|| clock_sync_mesh_suppressed_by != CLOCK_SYNC_MESH_SUPPRESS_NONE) return;
|
||||
|
||||
clock_sync_mesh_suppressed_by = source;
|
||||
clock_sync_force_mesh_pending = false;
|
||||
memset(clock_sync_samples, 0, sizeof(clock_sync_samples));
|
||||
MESH_DEBUG_PRINTLN("Clock sync: LoRa estimate suppressed by %s until reboot",
|
||||
clockSyncMeshSuppressionName(source));
|
||||
@@ -5425,6 +5428,20 @@ void MyMesh::checkGpsClockSyncOverride() {
|
||||
}
|
||||
}
|
||||
|
||||
bool MyMesh::isClockSyncCollectionActive() const {
|
||||
if (!clock_sync_mesh_enabled
|
||||
|| clock_sync_mesh_suppressed_by != CLOCK_SYNC_MESH_SUPPRESS_NONE) return false;
|
||||
if (!clock_sync_complete) return true;
|
||||
if (clock_sync_next_attempt_uptime == 0) return false;
|
||||
if (uptime_millis >= clock_sync_next_attempt_uptime) return true;
|
||||
|
||||
// Keep only the evidence that could still be fresh at the next scheduled
|
||||
// evaluation. This opens a two-hour rolling collection window before each
|
||||
// seven-day re-sync without decrypting Public traffic for the entire week.
|
||||
return clock_sync_next_attempt_uptime - uptime_millis
|
||||
<= (uint64_t)CLOCK_SYNC_SAMPLE_MAX_AGE_MILLIS;
|
||||
}
|
||||
|
||||
static void deriveClockSyncPathId(const mesh::Packet* packet,
|
||||
uint8_t path_id[CLOCK_SYNC_PATH_ID_SIZE]) {
|
||||
uint8_t material[2 + MAX_PATH_SIZE];
|
||||
@@ -5466,8 +5483,7 @@ uint32_t MyMesh::estimateClockTransitMillis(const mesh::Packet* packet) const {
|
||||
|
||||
void MyMesh::recordClockSyncSample(uint8_t source_kind, const uint8_t source_id[4], uint32_t epoch,
|
||||
const mesh::Packet* packet) {
|
||||
if (!clock_sync_mesh_enabled || clock_sync_complete
|
||||
|| clock_sync_mesh_suppressed_by != CLOCK_SYNC_MESH_SUPPRESS_NONE || source_id == NULL
|
||||
if (!isClockSyncCollectionActive() || source_id == NULL
|
||||
|| packet == NULL || !packet->isRouteFlood() || !clockSyncEpochIsValid(epoch)) return;
|
||||
|
||||
uint32_t transit_millis = estimateClockTransitMillis(packet);
|
||||
@@ -5536,8 +5552,7 @@ void MyMesh::recordClockSyncSample(uint8_t source_kind, const uint8_t source_id[
|
||||
}
|
||||
|
||||
void MyMesh::recordAcceptedFloodClockSample(const mesh::Packet* packet) {
|
||||
if (!clock_sync_mesh_enabled || clock_sync_complete
|
||||
|| clock_sync_mesh_suppressed_by != CLOCK_SYNC_MESH_SUPPRESS_NONE || packet == NULL) return;
|
||||
if (!isClockSyncCollectionActive() || packet == NULL) return;
|
||||
|
||||
if (packet->getPayloadType() == PAYLOAD_TYPE_GRP_TXT) {
|
||||
recordPublicChannelClockSample(packet);
|
||||
@@ -5555,8 +5570,7 @@ void MyMesh::recordAcceptedFloodClockSample(const mesh::Packet* packet) {
|
||||
}
|
||||
|
||||
void MyMesh::recordPublicChannelClockSample(const mesh::Packet* packet) {
|
||||
if (!clock_sync_mesh_enabled || clock_sync_complete
|
||||
|| clock_sync_mesh_suppressed_by != CLOCK_SYNC_MESH_SUPPRESS_NONE) return;
|
||||
if (!isClockSyncCollectionActive()) return;
|
||||
uint32_t timestamp = 0;
|
||||
char sender[FLOOD_GROUP_MODERATION_NAME_LEN];
|
||||
if (!decodeFloodGroupPlainText(packet, FLOOD_PUBLIC_CHANNEL_SECRET, CIPHER_KEY_SIZE,
|
||||
@@ -5620,7 +5634,7 @@ bool MyMesh::applyClockEstimate(uint32_t estimate, uint8_t source, uint8_t sampl
|
||||
clock_sync_last_estimate = estimate;
|
||||
clock_sync_last_abs_drift = magnitude > UINT32_MAX ? UINT32_MAX : (uint32_t)magnitude;
|
||||
clock_sync_complete = true;
|
||||
clock_sync_next_attempt_uptime = 0;
|
||||
clock_sync_next_attempt_uptime = uptime_millis + CLOCK_SYNC_RESYNC_INTERVAL_MILLIS;
|
||||
|
||||
if (magnitude <= clock_sync_drift_seconds) {
|
||||
clock_sync_last_result = CLOCK_SYNC_RESULT_WITHIN_DRIFT;
|
||||
@@ -5667,37 +5681,49 @@ void MyMesh::checkClockSync() {
|
||||
|
||||
bool mesh_available = clock_sync_mesh_enabled
|
||||
&& clock_sync_mesh_suppressed_by == CLOCK_SYNC_MESH_SUPPRESS_NONE;
|
||||
if (clock_sync_complete || (!mesh_available && !clock_sync_internet_enabled)
|
||||
|| uptime_millis < clock_sync_next_attempt_uptime) return;
|
||||
bool force_mesh = clock_sync_force_mesh_pending && mesh_available;
|
||||
if ((!mesh_available && !clock_sync_internet_enabled)
|
||||
|| (!force_mesh && uptime_millis < clock_sync_next_attempt_uptime)) return;
|
||||
|
||||
// A manual mesh request bypasses both the normal deadline and the optional
|
||||
// internet-first probe once. It never bypasses the source safety latch.
|
||||
if (force_mesh) clock_sync_force_mesh_pending = false;
|
||||
|
||||
// A successful estimate closes the current attempt but leaves a seven-day
|
||||
// deadline behind. Reopen the attempt when that deadline arrives; failures
|
||||
// below retain the existing 30-minute retry cadence until the next success.
|
||||
if (clock_sync_complete) clock_sync_complete = false;
|
||||
|
||||
if (!force_mesh) {
|
||||
#ifdef WITH_MQTT_BRIDGE
|
||||
if (clock_sync_internet_enabled && mqtt_bridge != NULL && mqtt_bridge->isRunning()) {
|
||||
if (clock_sync_internet_pending) {
|
||||
uint32_t estimate = 0;
|
||||
bool finished = false;
|
||||
bool success = mqtt_bridge->takeNtpTimeEstimate(estimate, finished);
|
||||
if (!finished && _ms->getMillis() - clock_sync_internet_requested_millis < 30000UL) return;
|
||||
if (clock_sync_internet_enabled && mqtt_bridge != NULL && mqtt_bridge->isRunning()) {
|
||||
if (clock_sync_internet_pending) {
|
||||
uint32_t estimate = 0;
|
||||
bool finished = false;
|
||||
bool success = mqtt_bridge->takeNtpTimeEstimate(estimate, finished);
|
||||
if (!finished && _ms->getMillis() - clock_sync_internet_requested_millis < 30000UL) return;
|
||||
clock_sync_internet_pending = false;
|
||||
if (success && applyClockEstimate(estimate, CLOCK_SYNC_SOURCE_INTERNET, 1)) return;
|
||||
clock_sync_last_result = CLOCK_SYNC_RESULT_INTERNET_UNAVAILABLE;
|
||||
} else if (mqtt_bridge->requestNtpTimeEstimate()) {
|
||||
clock_sync_internet_pending = true;
|
||||
clock_sync_internet_requested_millis = _ms->getMillis();
|
||||
clock_sync_last_result = CLOCK_SYNC_RESULT_INTERNET_PENDING;
|
||||
return;
|
||||
} else {
|
||||
clock_sync_last_result = CLOCK_SYNC_RESULT_INTERNET_UNAVAILABLE;
|
||||
}
|
||||
} else if (clock_sync_internet_enabled) {
|
||||
clock_sync_internet_pending = false;
|
||||
if (success && applyClockEstimate(estimate, CLOCK_SYNC_SOURCE_INTERNET, 1)) return;
|
||||
clock_sync_last_result = CLOCK_SYNC_RESULT_INTERNET_UNAVAILABLE;
|
||||
} else if (mqtt_bridge->requestNtpTimeEstimate()) {
|
||||
clock_sync_internet_pending = true;
|
||||
clock_sync_internet_requested_millis = _ms->getMillis();
|
||||
clock_sync_last_result = CLOCK_SYNC_RESULT_INTERNET_PENDING;
|
||||
return;
|
||||
} else {
|
||||
clock_sync_last_result = CLOCK_SYNC_RESULT_INTERNET_UNAVAILABLE;
|
||||
}
|
||||
} else if (clock_sync_internet_enabled) {
|
||||
clock_sync_internet_pending = false;
|
||||
clock_sync_last_result = CLOCK_SYNC_RESULT_INTERNET_UNAVAILABLE;
|
||||
}
|
||||
#else
|
||||
if (clock_sync_internet_enabled) {
|
||||
clock_sync_internet_pending = false;
|
||||
clock_sync_last_result = CLOCK_SYNC_RESULT_INTERNET_UNAVAILABLE;
|
||||
}
|
||||
if (clock_sync_internet_enabled) {
|
||||
clock_sync_internet_pending = false;
|
||||
clock_sync_last_result = CLOCK_SYNC_RESULT_INTERNET_UNAVAILABLE;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
if (mesh_available) {
|
||||
uint32_t estimate = 0;
|
||||
@@ -5755,23 +5781,24 @@ void MyMesh::formatClockSyncStatus(char* reply, size_t reply_len) const {
|
||||
}
|
||||
const char* source = clock_sync_last_source == CLOCK_SYNC_SOURCE_INTERNET ? "internet"
|
||||
: (clock_sync_last_source == CLOCK_SYNC_SOURCE_MESH ? "mesh" : "none");
|
||||
uint64_t remaining_ms = clock_sync_next_attempt_uptime > uptime_millis
|
||||
? clock_sync_next_attempt_uptime - uptime_millis : 0;
|
||||
unsigned long next_seconds = (unsigned long)((remaining_ms + 999ULL) / 1000ULL);
|
||||
if (clock_sync_complete) {
|
||||
if (clock_sync_last_source == CLOCK_SYNC_SOURCE_MESH) {
|
||||
snprintf(reply, reply_len, "> %s %lus via %s epoch=%lu agree=%u/%u paths=%u mesh=%s",
|
||||
snprintf(reply, reply_len, "> %s %lus via %s epoch=%lu agree=%u/%u paths=%u mesh=%s next=%lus",
|
||||
result, (unsigned long)clock_sync_last_abs_drift, source,
|
||||
(unsigned long)clock_sync_last_estimate,
|
||||
(unsigned int)clock_sync_last_sample_count,
|
||||
(unsigned int)clock_sync_last_required_count,
|
||||
(unsigned int)clock_sync_last_fresh_count, mesh_state);
|
||||
(unsigned int)clock_sync_last_fresh_count, mesh_state, next_seconds);
|
||||
} else {
|
||||
snprintf(reply, reply_len, "> %s %lus via %s, epoch=%lu drift=%lus mesh=%s",
|
||||
snprintf(reply, reply_len, "> %s %lus via %s, epoch=%lu drift=%lus mesh=%s next=%lus",
|
||||
result, (unsigned long)clock_sync_last_abs_drift, source,
|
||||
(unsigned long)clock_sync_last_estimate,
|
||||
(unsigned long)clock_sync_drift_seconds, mesh_state);
|
||||
(unsigned long)clock_sync_drift_seconds, mesh_state, next_seconds);
|
||||
}
|
||||
} else {
|
||||
uint64_t remaining_ms = clock_sync_next_attempt_uptime > uptime_millis
|
||||
? clock_sync_next_attempt_uptime - uptime_millis : 0;
|
||||
if (clock_sync_last_result == CLOCK_SYNC_RESULT_NO_CONSENSUS) {
|
||||
snprintf(reply, reply_len,
|
||||
"> no consensus, mesh=%s internet=%s agree=%u/%u paths=%u next=%lus",
|
||||
@@ -5779,14 +5806,14 @@ void MyMesh::formatClockSyncStatus(char* reply, size_t reply_len) const {
|
||||
(unsigned int)clock_sync_last_sample_count,
|
||||
(unsigned int)clock_sync_last_required_count,
|
||||
(unsigned int)clock_sync_last_fresh_count,
|
||||
(unsigned long)((remaining_ms + 999ULL) / 1000ULL));
|
||||
next_seconds);
|
||||
} else {
|
||||
snprintf(reply, reply_len, "> %s, mesh=%s internet=%s drift=%lus paths=%u/%u next=%lus",
|
||||
result, mesh_state,
|
||||
clock_sync_internet_enabled ? "on" : "off",
|
||||
(unsigned long)clock_sync_drift_seconds, (unsigned int)fresh,
|
||||
(unsigned int)clock_sync_required_samples,
|
||||
(unsigned long)((remaining_ms + 999ULL) / 1000ULL));
|
||||
next_seconds);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -6564,6 +6591,18 @@ void MyMesh::handleCommand(uint32_t sender_timestamp, ClientInfo* sender, char *
|
||||
sprintf(reply, "> %lu", (unsigned long)clock_sync_drift_seconds);
|
||||
} else if (strcmp(command, "get clock.sync.samples") == 0) {
|
||||
sprintf(reply, "> %u", (unsigned int)clock_sync_required_samples);
|
||||
} else if (strcmp(command, "clock.sync.mesh now") == 0) {
|
||||
if (!clock_sync_mesh_enabled) {
|
||||
strcpy(reply, "Err - mesh clock sync is off");
|
||||
} else if (clock_sync_mesh_suppressed_by != CLOCK_SYNC_MESH_SUPPRESS_NONE) {
|
||||
sprintf(reply, "Err - mesh sync suppressed by %s until reboot",
|
||||
clockSyncMeshSuppressionName(clock_sync_mesh_suppressed_by));
|
||||
} else {
|
||||
resetClockSyncAttempt();
|
||||
clock_sync_force_mesh_pending = true;
|
||||
clock_sync_next_attempt_uptime = uptime_millis;
|
||||
strcpy(reply, "OK - mesh clock sync queued");
|
||||
}
|
||||
} else if (strncmp(command, "set clock.sync.mesh ", 20) == 0) {
|
||||
const char* value = command + 20;
|
||||
bool enabled;
|
||||
|
||||
@@ -151,12 +151,20 @@ struct NeighbourInfo {
|
||||
#ifndef CLOCK_SYNC_SAMPLE_SLOTS
|
||||
#define CLOCK_SYNC_SAMPLE_SLOTS 16
|
||||
#endif
|
||||
#ifndef CLOCK_SYNC_MESH_DEFAULT_ENABLED
|
||||
#if defined(NRF52_PLATFORM)
|
||||
#define CLOCK_SYNC_MESH_DEFAULT_ENABLED 1
|
||||
#else
|
||||
#define CLOCK_SYNC_MESH_DEFAULT_ENABLED 0
|
||||
#endif
|
||||
#endif
|
||||
#define CLOCK_SYNC_REQUIRED_SAMPLES_MIN 3
|
||||
#define CLOCK_SYNC_REQUIRED_SAMPLES_MAX CLOCK_SYNC_SAMPLE_SLOTS
|
||||
#define CLOCK_SYNC_REQUIRED_SAMPLES_DEFAULT 9
|
||||
#define CLOCK_SYNC_PATH_ID_SIZE 8
|
||||
#define CLOCK_SYNC_STARTUP_DELAY_MILLIS (30ULL * 60ULL * 1000ULL)
|
||||
#define CLOCK_SYNC_RETRY_INTERVAL_MILLIS (30ULL * 60ULL * 1000ULL)
|
||||
#define CLOCK_SYNC_RESYNC_INTERVAL_MILLIS (7ULL * 24ULL * 60ULL * 60ULL * 1000ULL)
|
||||
#define CLOCK_SYNC_SAMPLE_MAX_AGE_MILLIS (2UL * 60UL * 60UL * 1000UL)
|
||||
#define CLOCK_SYNC_CONSENSUS_WINDOW_SECONDS 600UL
|
||||
#define CLOCK_SYNC_DRIFT_MIN_SECONDS 30UL
|
||||
@@ -283,6 +291,7 @@ class MyMesh : public mesh::Mesh, public CommonCLICallbacks
|
||||
bool clock_sync_internet_enabled;
|
||||
bool clock_sync_complete;
|
||||
bool clock_sync_internet_pending;
|
||||
bool clock_sync_force_mesh_pending;
|
||||
uint8_t clock_sync_mesh_suppressed_by;
|
||||
uint8_t clock_sync_last_result;
|
||||
uint8_t clock_sync_last_source;
|
||||
@@ -451,6 +460,7 @@ class MyMesh : public mesh::Mesh, public CommonCLICallbacks
|
||||
void resetClockSyncAttempt();
|
||||
void suppressMeshClockSyncForBoot(uint8_t source);
|
||||
void checkGpsClockSyncOverride();
|
||||
bool isClockSyncCollectionActive() const;
|
||||
uint32_t estimateClockTransitMillis(const mesh::Packet* packet) const;
|
||||
void recordClockSyncSample(uint8_t source_kind, const uint8_t source_id[4], uint32_t epoch,
|
||||
const mesh::Packet* packet);
|
||||
|
||||
Reference in new issue
Block a user