ota: configurable advertise interval (default 24h) + advertise on served-set change
The discovery beacon previously re-announced at a random 3-10 min interval. Replace that with a fixed, user-configurable cadence: - OtaManager::advert_mins() — re-advertise every N minutes after the boot burst; 0 disables periodic re-advertise (boot burst only). Default 24h. - Persisted in NodePrefs (CommonCLI) and runtime-tunable: `ota config advert <minutes>` (0..10080; 0 = off), and shown in `ota config`. - When periodic advert is disabled, the scheduler still re-checks the config on a slow timer, so a later `ota config advert <mins>` takes effect live. Also advertise immediately whenever the served set changes — when a motatool folder is attached to / detached from the ESP32 WiFi seeder — so peers learn about newly-available firmware without waiting for the next interval (the `ota folder` serial path already announced on attach). Docs: protocol beacon-cadence note + user-guide `ota config advert`.
This commit is contained in:
1 parent
0a1cf2b231
commit
9ff1c883c2
8 files changed
+55
-23
No files matched your search
@@ -310,7 +310,10 @@ Message types:
|
||||
Because a node may serve **many** mOTAs (its own firmware plus an external folder — §10), discovery is split
|
||||
so the periodic beacon stays tiny regardless of catalog size:
|
||||
|
||||
**Tier 1 — `OTA_ADV` beacon** (10 bytes, constant, flooded periodically):
|
||||
**Tier 1 — `OTA_ADV` beacon** (10 bytes, constant). Flooded as a short burst at boot, then every
|
||||
`advert_mins` minutes (default 24h; runtime-tunable via `ota config advert`, `0` disables the periodic
|
||||
re-advertise). It is also emitted immediately whenever the served set changes (e.g. a `motatool` folder is
|
||||
attached/detached), so peers learn about newly-available firmware without waiting for the next interval:
|
||||
|
||||
```
|
||||
seeder_id[4] advertiser node id = pubkey[0:4]; the QUERY address + distinct-source id
|
||||
|
||||
@@ -129,6 +129,9 @@ ota config autofetch off # back to manual (default)
|
||||
ota config autoinstall trusted # auto-INSTALL a downloaded update IF it's signed by a key you trust
|
||||
ota config autoinstall off # never auto-install (default)
|
||||
|
||||
ota config advert 1440 # re-advertise this node every N minutes (default 1440 = 24h)
|
||||
ota config advert 0 # disable periodic re-advertise (still advertises briefly at boot)
|
||||
|
||||
ota config # show the current settings
|
||||
```
|
||||
|
||||
|
||||
@@ -130,6 +130,7 @@ void halt() {
|
||||
if (ota_seeder_client && ota_seeder_client.connected()) return; // still serving the current client
|
||||
if (ota_seeder_attached) { // previous client just disconnected
|
||||
mesh::ota::ota_ctx().detach_folder();
|
||||
mesh::ota::ota_ctx().manager.announce(); // served set shrank back to our own fw -> re-advertise
|
||||
ota_seeder_attached = false;
|
||||
WIFI_DEBUG_PRINTLN("OTA seeder: client disconnected, relay stopped");
|
||||
}
|
||||
@@ -138,6 +139,7 @@ void halt() {
|
||||
ota_seeder_client = c; // rebind the persistent Stream to it
|
||||
if (mesh::ota::ota_ctx().manager.add_source(&ota_seeder_source)) {
|
||||
ota_seeder_attached = true;
|
||||
mesh::ota::ota_ctx().manager.announce(); // new served set -> advertise the folder's fw to peers
|
||||
WIFI_DEBUG_PRINTLN("OTA seeder: client connected, relaying its folder");
|
||||
} else {
|
||||
ota_seeder_client.stop(); // no free source slot
|
||||
|
||||
+20
-18
@@ -13,14 +13,12 @@
|
||||
#ifndef OTA_ANNOUNCE_BURST_MS
|
||||
#define OTA_ANNOUNCE_BURST_MS 20000UL // spacing during the boot burst (~1 min total), then ...
|
||||
#endif
|
||||
// ... then re-announce at a RANDOM interval in [MIN, MAX] so a long-running node stays discoverable
|
||||
// (a fresh `ota ls` neighbour sees it within minutes, not at boot only) without all nodes beaconing in
|
||||
// lockstep. The beacon is tiny + lowest-priority + duty-gated, so a few-minute cadence is cheap.
|
||||
#ifndef OTA_ANNOUNCE_INTERVAL_MIN_MS
|
||||
#define OTA_ANNOUNCE_INTERVAL_MIN_MS 180000UL // 3 min
|
||||
#endif
|
||||
#ifndef OTA_ANNOUNCE_INTERVAL_MAX_MS
|
||||
#define OTA_ANNOUNCE_INTERVAL_MAX_MS 600000UL // 10 min
|
||||
// ... then re-announce at a FIXED cadence so a long-running node stays discoverable (a fresh `ota ls`
|
||||
// neighbour eventually sees it, not at boot only). The cadence is OtaManager::advert_mins() minutes (default
|
||||
// 24h, runtime-tunable via `ota config advert` + persisted; 0 = disabled = boot burst only). The beacon is
|
||||
// tiny + lowest-priority + duty-gated, so even a frequent cadence is cheap.
|
||||
#ifndef OTA_ANNOUNCE_DISABLED_POLL_MS
|
||||
#define OTA_ANNOUNCE_DISABLED_POLL_MS 600000UL // when periodic advert is off, re-check config every 10 min
|
||||
#endif
|
||||
#endif
|
||||
|
||||
@@ -90,17 +88,21 @@ void Mesh::loop() {
|
||||
}
|
||||
if (millisHasNowPassed(_next_ota_announce)) { // auto-advertise so peers discover us (tiny beacon)
|
||||
ota::OtaContext& oc = ota::ota_ctx();
|
||||
// To be discoverable as a source of our OWN firmware, set up flash-backed self-serve once; then the
|
||||
// beacon (announce) advertises our served set and peers can QUERY + fetch it.
|
||||
if (!oc.serving) oc.serving = ota::ota_serve_self(oc, 0);
|
||||
oc.manager.announce();
|
||||
// boot burst (a few closely-spaced adverts so a co-booting peer catches one), then a random
|
||||
// few-minute cadence so a node stays discoverable long after boot (not just at boot).
|
||||
uint32_t gap = (_ota_announce_count < OTA_ANNOUNCE_BURST)
|
||||
? OTA_ANNOUNCE_BURST_MS
|
||||
: _rng->nextInt(OTA_ANNOUNCE_INTERVAL_MIN_MS, OTA_ANNOUNCE_INTERVAL_MAX_MS);
|
||||
bool in_burst = _ota_announce_count < OTA_ANNOUNCE_BURST;
|
||||
uint32_t mins = oc.manager.advert_mins(); // periodic cadence in minutes; 0 = disabled (boot burst only)
|
||||
if (in_burst || mins != 0) {
|
||||
// To be discoverable as a source of our OWN firmware, set up flash-backed self-serve once; then the
|
||||
// beacon (announce) advertises our served set and peers can QUERY + fetch it.
|
||||
if (!oc.serving) oc.serving = ota::ota_serve_self(oc, 0);
|
||||
oc.manager.announce();
|
||||
if (_ota_announce_count < 250) _ota_announce_count++;
|
||||
}
|
||||
// Re-arm: tight spacing during the boot burst; afterwards the fixed cadence (default 24h). When periodic
|
||||
// advert is disabled (0), re-check on a slow timer so a later `ota config advert <mins>` takes effect live.
|
||||
uint32_t gap = in_burst ? OTA_ANNOUNCE_BURST_MS
|
||||
: (mins != 0) ? mins * 60000UL
|
||||
: OTA_ANNOUNCE_DISABLED_POLL_MS;
|
||||
_next_ota_announce = futureMillis(gap);
|
||||
if (_ota_announce_count < 250) _ota_announce_count++;
|
||||
}
|
||||
{ // auto-install (once per COMPLETE fetch): only signed images, and apply_fetched enforces trust
|
||||
ota::OtaContext& oc = ota::ota_ctx();
|
||||
|
||||
@@ -52,6 +52,7 @@ void CommonCLI::syncOtaConfigFromPrefs() {
|
||||
mesh::ota::OtaContext& c = mesh::ota::ota_ctx();
|
||||
c.manager.set_autofetch(_prefs->ota_autofetch);
|
||||
c.manager.set_checkpoint_blocks(_prefs->ota_checkpoint_blocks);
|
||||
c.manager.set_advert_mins(_prefs->ota_advert_interval);
|
||||
c.autoinstall = _prefs->ota_autoinstall;
|
||||
c.allow.clear();
|
||||
for (uint8_t i = 0; i < _prefs->ota_signer_count && i < MAX_OTA_SIGNERS; i++)
|
||||
@@ -119,12 +120,14 @@ void CommonCLI::loadPrefsInt(FILESYSTEM* fs, const char* filename) {
|
||||
// defaults: a short file makes the reads below no-ops (read returns 0 bytes, values unchanged).
|
||||
_prefs->ota_autofetch = 0; _prefs->ota_autoinstall = 0; _prefs->ota_signer_count = 0;
|
||||
_prefs->ota_checkpoint_blocks = 4; // = OTA_CHECKPOINT_BLOCKS; older prefs lack it -> stays at default
|
||||
_prefs->ota_advert_interval = 1440; // = OTA_ADVERT_INTERVAL_MINS (24h); older prefs lack it -> stays default
|
||||
file.read((uint8_t *)&_prefs->ota_autofetch, sizeof(_prefs->ota_autofetch)); // 295
|
||||
file.read((uint8_t *)&_prefs->ota_autoinstall, sizeof(_prefs->ota_autoinstall)); // 296
|
||||
file.read((uint8_t *)&_prefs->ota_signer_count, sizeof(_prefs->ota_signer_count)); // 297
|
||||
file.read((uint8_t *)_prefs->ota_signers, sizeof(_prefs->ota_signers)); // 298
|
||||
file.read((uint8_t *)&_prefs->ota_checkpoint_blocks, sizeof(_prefs->ota_checkpoint_blocks)); // 426
|
||||
// next: 428
|
||||
file.read((uint8_t *)&_prefs->ota_advert_interval, sizeof(_prefs->ota_advert_interval)); // 428
|
||||
// next: 430
|
||||
|
||||
// sanitise bad pref values
|
||||
_prefs->rx_delay_base = constrain(_prefs->rx_delay_base, 0, 20.0f);
|
||||
@@ -159,6 +162,7 @@ void CommonCLI::loadPrefsInt(FILESYSTEM* fs, const char* filename) {
|
||||
_prefs->ota_autofetch = constrain(_prefs->ota_autofetch, 0, 2);
|
||||
_prefs->ota_autoinstall = constrain(_prefs->ota_autoinstall, 0, 1);
|
||||
if (_prefs->ota_checkpoint_blocks > 4096) _prefs->ota_checkpoint_blocks = 4; // 0=never; cap absurd
|
||||
if (_prefs->ota_advert_interval > 10080) _prefs->ota_advert_interval = 1440; // 0=off; cap at 7 days
|
||||
if (_prefs->ota_signer_count > 4) _prefs->ota_signer_count = 0; // corrupt count -> drop keys
|
||||
|
||||
file.close();
|
||||
@@ -230,7 +234,8 @@ void CommonCLI::savePrefs(FILESYSTEM* fs) {
|
||||
file.write((uint8_t *)&_prefs->ota_signer_count, sizeof(_prefs->ota_signer_count)); // 297
|
||||
file.write((uint8_t *)_prefs->ota_signers, sizeof(_prefs->ota_signers)); // 298
|
||||
file.write((uint8_t *)&_prefs->ota_checkpoint_blocks, sizeof(_prefs->ota_checkpoint_blocks)); // 426
|
||||
// next: 428
|
||||
file.write((uint8_t *)&_prefs->ota_advert_interval, sizeof(_prefs->ota_advert_interval)); // 428
|
||||
// next: 430
|
||||
|
||||
file.close();
|
||||
}
|
||||
@@ -359,6 +364,7 @@ void CommonCLI::handleCommand(uint32_t sender_timestamp, char* command, char* re
|
||||
mesh::ota::OtaContext& c = mesh::ota::ota_ctx();
|
||||
_prefs->ota_autofetch = c.manager.autofetch();
|
||||
_prefs->ota_checkpoint_blocks = c.manager.checkpoint_blocks();
|
||||
_prefs->ota_advert_interval = c.manager.advert_mins();
|
||||
_prefs->ota_autoinstall = c.autoinstall;
|
||||
_prefs->ota_signer_count = c.allow.count();
|
||||
for (uint8_t i = 0; i < c.allow.count() && i < MAX_OTA_SIGNERS; i++)
|
||||
|
||||
@@ -71,6 +71,7 @@ struct NodePrefs { // persisted to file
|
||||
uint8_t ota_signer_count; // # of allowlisted signer pubkeys below
|
||||
uint8_t ota_signers[4][32]; // trusted Ed25519 signer pubkeys (== MAX_OTA_SIGNERS)
|
||||
uint16_t ota_checkpoint_blocks; // resume checkpoint cadence (blocks); 0=never. Default 4 (runtime-tunable)
|
||||
uint16_t ota_advert_interval; // OTA beacon re-advertise cadence (mins); 0=off. Default 1440 (24h, tunable)
|
||||
};
|
||||
|
||||
class CommonCLICallbacks {
|
||||
|
||||
@@ -272,12 +272,17 @@ bool handle_ota_command(const char* command, char* reply, mesh::MainBoard& board
|
||||
if (n < 0 || n > 4096) { strcpy(reply, "ERR usage: ota config checkpoint <0..4096> (blocks; 0=never)"); return true; }
|
||||
c.manager.set_checkpoint_blocks((uint16_t)n); c.config_dirty = true;
|
||||
sprintf(reply, "OK checkpoint every %ld blocks (saved)%s", n, n == 0 ? " — periodic resume disabled" : "");
|
||||
} else if (strncmp(p, "advert ", 7) == 0) { // beacon re-advertise cadence (minutes; 0=disable)
|
||||
long m = atol(p + 7);
|
||||
if (m < 0 || m > 10080) { strcpy(reply, "ERR usage: ota config advert <0..10080> (minutes; 0=disable)"); return true; }
|
||||
c.manager.set_advert_mins((uint16_t)m); c.config_dirty = true;
|
||||
sprintf(reply, "OK re-advertise every %ld min (saved)%s", m, m == 0 ? " — periodic advert disabled" : "");
|
||||
} else { // show current policy
|
||||
uint8_t af = c.manager.autofetch();
|
||||
sprintf(reply, "ota config: autofetch=%s autoinstall=%s checkpoint=%u keys=%u (persisted)",
|
||||
sprintf(reply, "ota config: autofetch=%s autoinstall=%s checkpoint=%u advert=%umin keys=%u (persisted)",
|
||||
af == OtaManager::AUTOFETCH_ANY ? "any" : af == OtaManager::AUTOFETCH_SIGNED ? "signed" : "off",
|
||||
c.autoinstall == OtaContext::AUTOINSTALL_TRUSTED ? "trusted" : "off",
|
||||
(unsigned)c.manager.checkpoint_blocks(), (unsigned)c.allow.count());
|
||||
(unsigned)c.manager.checkpoint_blocks(), (unsigned)c.manager.advert_mins(), (unsigned)c.allow.count());
|
||||
}
|
||||
|
||||
// ---- trusted signer allowlist (security config; persisted): `ota key add|rm <hex>` / `ota key` lists ----
|
||||
|
||||
@@ -37,6 +37,9 @@ typedef bool (*ServeReadFn)(void* ctx, uint32_t off, uint8_t* buf, uint32_t len)
|
||||
#ifndef OTA_CHECKPOINT_BLOCKS
|
||||
#define OTA_CHECKPOINT_BLOCKS 4 // persist progress (store.checkpoint) every N committed blocks (resume)
|
||||
#endif
|
||||
#ifndef OTA_ADVERT_INTERVAL_MINS
|
||||
#define OTA_ADVERT_INTERVAL_MINS 1440 // re-advertise our served set every N minutes after the boot burst (24h)
|
||||
#endif
|
||||
#ifndef OTA_MF_FRAG
|
||||
#define OTA_MF_FRAG 176 // manifest bytes per OTA_MANIFEST fragment (<= MAX_PACKET_PAYLOAD - header)
|
||||
#endif
|
||||
@@ -204,6 +207,12 @@ public:
|
||||
// committed blocks. 0 = never (resume only from a finalized container). Default OTA_CHECKPOINT_BLOCKS.
|
||||
void set_checkpoint_blocks(uint16_t n) { _checkpoint_blocks = n; }
|
||||
uint16_t checkpoint_blocks() const { return _checkpoint_blocks; }
|
||||
|
||||
// Beacon re-advertise cadence in MINUTES (runtime-tunable, persisted in NodePrefs): after the boot burst,
|
||||
// re-send the discovery beacon every N minutes so a long-running node stays discoverable. 0 = disabled
|
||||
// (boot burst only). Default OTA_ADVERT_INTERVAL_MINS (24h).
|
||||
void set_advert_mins(uint16_t m) { _advert_mins = m; }
|
||||
uint16_t advert_mins() const { return _advert_mins; }
|
||||
bool fetched_is_signed() const { return (_fflags & MFLAG_SIGNED) != 0; } // flags of the fetched manifest
|
||||
|
||||
// This node's id (pubkey[0:4]), stamped into adverts we send so receivers can count distinct seeders.
|
||||
@@ -325,6 +334,7 @@ private:
|
||||
uint8_t _seeder_id[4] = {0,0,0,0}; // our node id (pubkey[0:4]) for advert seeder counting
|
||||
uint8_t _autofetch = AUTOFETCH_OFF; // auto-fetch policy (persisted in NodePrefs)
|
||||
uint16_t _checkpoint_blocks = OTA_CHECKPOINT_BLOCKS; // resume checkpoint cadence (persisted)
|
||||
uint16_t _advert_mins = OTA_ADVERT_INTERVAL_MINS; // beacon re-advertise cadence, minutes; 0=off (persisted)
|
||||
uint8_t _fflags = 0; // flags of the manifest currently being fetched
|
||||
// multi-fragment reassembly of the current block (per-block 2-phase: fetch data, then its proof)
|
||||
uint32_t _reasm_block = NO_BLOCK; // block being reassembled / awaiting proof (none)
|
||||
|
||||
Reference in new issue
Block a user