Add full Companion OTA source builds
This commit is contained in:
1 parent
1bceda54a6
commit
88ba5b26cb
19 files changed
+1212
-86
No files matched your search
+12
-2
@@ -3,7 +3,9 @@
|
||||
#if defined(ENABLE_OTA)
|
||||
#include "helpers/ota/OtaContext.h" // OTA mesh-integration is centralized here so every role gets it
|
||||
#include "helpers/ota/OtaProtocol.h" // decode_adv -> the `ota neighbors` discovery table
|
||||
#if !defined(OTA_SEEDER_ONLY)
|
||||
#include "helpers/ota/OtaSelf.h" // ota_self_firmware -> auto-advertise our own image
|
||||
#endif
|
||||
#if defined(ESP32_PLATFORM) && (defined(WIFI_OTA_SEEDER) || defined(WIFI_SSID))
|
||||
#include "helpers/esp32/WiFiOtaSeeder.h"
|
||||
#endif
|
||||
@@ -280,6 +282,7 @@ void __attribute__((noinline)) Mesh::serviceLoopMaintenance() {
|
||||
}
|
||||
}
|
||||
#if defined(ENABLE_OTA)
|
||||
#if !defined(OTA_SEEDER_ONLY)
|
||||
// Deferred apply-reboot: a verified `ota applydelta` approves the update but does NOT reboot inline,
|
||||
// so its "verified; applying" reply can be delivered first (over LoRa that reply is the operator's
|
||||
// only confirmation the apply started). Reboot once that reply has actually been transmitted (the
|
||||
@@ -297,6 +300,7 @@ void __attribute__((noinline)) Mesh::serviceLoopMaintenance() {
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
const bool ota_active = isTempRadioActive();
|
||||
if (!ota_active) {
|
||||
_ota_temp_was_active = false;
|
||||
@@ -312,6 +316,7 @@ void __attribute__((noinline)) Mesh::serviceLoopMaintenance() {
|
||||
// one-shot on first tick: resume an interrupted fetch left staged in flash before a reboot. Only adopt
|
||||
// a PARTIAL container (continue fetching the holes); a COMPLETE one is left for manual/auto-install,
|
||||
// not re-adopted at boot. requestMissing() (inside resumeStaged) drives the rest via REQ/DATA.
|
||||
#if !defined(OTA_SEEDER_ONLY)
|
||||
if (!_ota_resumed) {
|
||||
_ota_resumed = true;
|
||||
ota::OtaContext& oc = ota::ota_ctx();
|
||||
@@ -320,6 +325,7 @@ void __attribute__((noinline)) Mesh::serviceLoopMaintenance() {
|
||||
oc.manager.reset_session(); // don't auto-adopt a complete staged container on boot
|
||||
}
|
||||
}
|
||||
#endif
|
||||
ota::ota_ctx().manager.set_clock(_ms->getMillis()); // for discovery jitter/ages + the pending-query timer
|
||||
ota::ota_ctx().manager.loop(); // re-request still-missing OTA blocks + fire scheduled queries
|
||||
#if defined(NRF52_PLATFORM) && defined(OTA_SD_STORE)
|
||||
@@ -332,9 +338,11 @@ void __attribute__((noinline)) Mesh::serviceLoopMaintenance() {
|
||||
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.
|
||||
// Install-capable nodes add their running firmware to the served set.
|
||||
// Seeder-only nodes advertise only host-provided folder entries.
|
||||
#if !defined(OTA_SEEDER_ONLY)
|
||||
if (!oc.serving) oc.serving = ota::ota_serve_self(oc, 0);
|
||||
#endif
|
||||
oc.manager.announce();
|
||||
if (_ota_announce_count < 250) _ota_announce_count++;
|
||||
}
|
||||
@@ -345,6 +353,7 @@ void __attribute__((noinline)) Mesh::serviceLoopMaintenance() {
|
||||
: OTA_ANNOUNCE_DISABLED_POLL_MS;
|
||||
_next_ota_announce = futureMillis(gap);
|
||||
}
|
||||
#if !defined(OTA_SEEDER_ONLY)
|
||||
{ // auto-install (once per COMPLETE fetch): only signed images, and apply_fetched enforces trust
|
||||
ota::OtaContext& oc = ota::ota_ctx();
|
||||
if (oc.manager.fetchState() != ota::OtaManager::COMPLETE) {
|
||||
@@ -358,6 +367,7 @@ void __attribute__((noinline)) Mesh::serviceLoopMaintenance() {
|
||||
}
|
||||
}
|
||||
#endif
|
||||
#endif
|
||||
}
|
||||
|
||||
bool Mesh::allowPacketTransmit(const Packet* packet) const {
|
||||
|
||||
@@ -8,24 +8,28 @@
|
||||
void ArduinoSerialInterface::resetReceiveState() {
|
||||
_state = RECV_STATE_IDLE;
|
||||
_controlSequencePos = 0;
|
||||
_secondaryControlSequencePos = 0;
|
||||
_frame_len = 0;
|
||||
rx_len = 0;
|
||||
}
|
||||
|
||||
bool ArduinoSerialInterface::checkControlSequence(uint8_t c) {
|
||||
if (_controlSequence == nullptr || _controlSequence[0] == 0) return false;
|
||||
bool ArduinoSerialInterface::checkControlSequence(uint8_t c,
|
||||
const char* sequence,
|
||||
size_t& position,
|
||||
bool& received) {
|
||||
if (sequence == nullptr || sequence[0] == 0) return false;
|
||||
|
||||
if (c == (uint8_t)_controlSequence[_controlSequencePos]) {
|
||||
_controlSequencePos++;
|
||||
if (_controlSequence[_controlSequencePos] == 0) {
|
||||
_controlSequencePos = 0;
|
||||
_controlSequenceReceived = true;
|
||||
if (c == (uint8_t)sequence[position]) {
|
||||
position++;
|
||||
if (sequence[position] == 0) {
|
||||
position = 0;
|
||||
received = true;
|
||||
return true;
|
||||
}
|
||||
} else {
|
||||
// Preserve a possible new match when this byte is also the first byte of
|
||||
// the sequence (notably useful for sequences beginning with "+++").
|
||||
_controlSequencePos = c == (uint8_t)_controlSequence[0] ? 1 : 0;
|
||||
position = c == (uint8_t)sequence[0] ? 1 : 0;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
@@ -33,6 +37,7 @@ bool ArduinoSerialInterface::checkControlSequence(uint8_t c) {
|
||||
void ArduinoSerialInterface::setPassthroughMode(bool enabled) {
|
||||
_passthroughMode = enabled;
|
||||
_controlSequenceReceived = false;
|
||||
_secondaryControlSequenceReceived = false;
|
||||
resetReceiveState();
|
||||
}
|
||||
|
||||
@@ -42,14 +47,22 @@ bool ArduinoSerialInterface::takeControlSequence() {
|
||||
return received;
|
||||
}
|
||||
|
||||
bool ArduinoSerialInterface::takeSecondaryControlSequence() {
|
||||
bool received = _secondaryControlSequenceReceived;
|
||||
_secondaryControlSequenceReceived = false;
|
||||
return received;
|
||||
}
|
||||
|
||||
void ArduinoSerialInterface::enable() {
|
||||
_isEnabled = true;
|
||||
_controlSequenceReceived = false;
|
||||
_secondaryControlSequenceReceived = false;
|
||||
resetReceiveState();
|
||||
}
|
||||
void ArduinoSerialInterface::disable() {
|
||||
_isEnabled = false;
|
||||
_controlSequenceReceived = false;
|
||||
_secondaryControlSequenceReceived = false;
|
||||
resetReceiveState();
|
||||
}
|
||||
|
||||
@@ -90,7 +103,11 @@ size_t ArduinoSerialInterface::checkRecvFrame(uint8_t dest[]) {
|
||||
|
||||
switch (_state) {
|
||||
case RECV_STATE_IDLE:
|
||||
if (checkControlSequence((uint8_t)c)) {
|
||||
if (checkControlSequence((uint8_t)c, _controlSequence,
|
||||
_controlSequencePos, _controlSequenceReceived)
|
||||
|| checkControlSequence((uint8_t)c, _secondaryControlSequence,
|
||||
_secondaryControlSequencePos,
|
||||
_secondaryControlSequenceReceived)) {
|
||||
// Leave any following bytes buffered for the passthrough consumer.
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -7,29 +7,37 @@ class ArduinoSerialInterface : public BaseSerialInterface {
|
||||
bool _isEnabled;
|
||||
bool _passthroughMode;
|
||||
bool _controlSequenceReceived;
|
||||
bool _secondaryControlSequenceReceived;
|
||||
uint8_t _state;
|
||||
size_t _controlSequencePos;
|
||||
size_t _secondaryControlSequencePos;
|
||||
uint16_t _frame_len;
|
||||
uint16_t rx_len;
|
||||
Stream* _serial;
|
||||
const char* _controlSequence;
|
||||
const char* _secondaryControlSequence;
|
||||
uint8_t rx_buf[MAX_FRAME_SIZE];
|
||||
|
||||
bool checkControlSequence(uint8_t c);
|
||||
bool checkControlSequence(uint8_t c, const char* sequence,
|
||||
size_t& position, bool& received);
|
||||
void resetReceiveState();
|
||||
|
||||
public:
|
||||
ArduinoSerialInterface()
|
||||
: _isEnabled(false), _passthroughMode(false),
|
||||
_controlSequenceReceived(false), _state(0), _controlSequencePos(0),
|
||||
_controlSequenceReceived(false), _secondaryControlSequenceReceived(false),
|
||||
_state(0), _controlSequencePos(0), _secondaryControlSequencePos(0),
|
||||
_frame_len(0), rx_len(0), _serial(nullptr),
|
||||
_controlSequence(nullptr) {}
|
||||
_controlSequence(nullptr), _secondaryControlSequence(nullptr) {}
|
||||
|
||||
void begin(Stream& serial, const char* controlSequence = nullptr) {
|
||||
void begin(Stream& serial, const char* controlSequence = nullptr,
|
||||
const char* secondaryControlSequence = nullptr) {
|
||||
_serial = &serial;
|
||||
_controlSequence = controlSequence;
|
||||
_secondaryControlSequence = secondaryControlSequence;
|
||||
_passthroughMode = false;
|
||||
_controlSequenceReceived = false;
|
||||
_secondaryControlSequenceReceived = false;
|
||||
resetReceiveState();
|
||||
#ifdef RAK_4631
|
||||
pinMode(WB_IO2, OUTPUT);
|
||||
@@ -44,6 +52,7 @@ public:
|
||||
// Returns true once for each complete control sequence received while the
|
||||
// binary frame parser was idle.
|
||||
bool takeControlSequence();
|
||||
bool takeSecondaryControlSequence();
|
||||
|
||||
// BaseSerialInterface methods
|
||||
void enable() override;
|
||||
|
||||
@@ -127,12 +127,26 @@ bool handle_ota_command(const char* command, char* reply, mesh::MainBoard& board
|
||||
|
||||
// ---- help: list the commands in plain words (aliases in parentheses) ----
|
||||
if (is_cmd(a, "help|?|h", &rest)) {
|
||||
#if defined(OTA_SEEDER_ONLY)
|
||||
snprintf(reply, 160,
|
||||
"OTA seeder: status | stats | ls=find images | get <#> folder=capture | cancel | "
|
||||
"announce | folder | config. LoRa install is disabled.");
|
||||
#else
|
||||
snprintf(reply, 160,
|
||||
"OTA: status | stats=admin ids/hashes | ls=find updates | get <#>=download | install | cancel | "
|
||||
"announce | self | folder | cache | config | key. Use `ota ls [page]`.");
|
||||
#endif
|
||||
|
||||
// ---- inventory dashboard: running fw (self), the one fetch session, serving state ----
|
||||
} else if (*a == 0 || is_cmd(a, "status|st", &rest)) {
|
||||
#if defined(OTA_SEEDER_ONLY)
|
||||
uint8_t dig[4]; c.manager.servedDigest(dig);
|
||||
char dighx[9]; mesh::Utils::toHex(dighx, dig, 4);
|
||||
snprintf(reply, 160,
|
||||
"OTA seeder | install:disabled | folder:%s | serving:%u dg=%s | target:00000000 (source only)",
|
||||
c.folder_active ? c.folder_dest_info : "not connected",
|
||||
(unsigned)c.manager.servedCount(), dighx);
|
||||
#else
|
||||
SelfFwInfo fi; bool s = ota_self_firmware(fi);
|
||||
char selfhx[9]; if (s && fi.valid) mesh::Utils::toHex(selfhx, fi.body_hash, 4); else strcpy(selfhx, "?");
|
||||
OtaManager::FetchState fs = c.manager.fetchState();
|
||||
@@ -164,6 +178,7 @@ bool handle_ota_command(const char* command, char* reply, mesh::MainBoard& board
|
||||
#endif
|
||||
if (n < 146) n += snprintf(reply + n, 160 - n, " | bl:%s blrc:%02X",
|
||||
bl_state, ota_bootloader_last_rc());
|
||||
#endif
|
||||
#endif
|
||||
|
||||
// ---- admin OTA stats: crypto identities (our fw's content-id + body_hash), serving set, live fetch,
|
||||
@@ -279,12 +294,20 @@ bool handle_ota_command(const char* command, char* reply, mesh::MainBoard& board
|
||||
if (!sel) { strcpy(reply, "ERR no such update (see the numbers in `ota ls`)"); return true; }
|
||||
// destination is MANDATORY: with none given, show the choices (flash always; folder iff a link is up).
|
||||
if (*dst == 0) {
|
||||
#if defined(OTA_SEEDER_ONLY)
|
||||
if (c.folder_dest)
|
||||
snprintf(reply, 160, "choose a destination: `ota pull %s folder` (folder: %s)",
|
||||
selstr, c.folder_dest_info);
|
||||
else
|
||||
strcpy(reply, "ERR no folder connected (run motatool serve --tcp)");
|
||||
#else
|
||||
if (c.folder_dest)
|
||||
snprintf(reply, 160, "choose a destination: `ota pull %s flash` | `ota pull %s folder` (folder: %s)",
|
||||
selstr, selstr, c.folder_dest_info);
|
||||
else
|
||||
snprintf(reply, 160, "choose a destination: `ota pull %s flash` (folder: none connected - motatool serve)",
|
||||
selstr);
|
||||
#endif
|
||||
return true;
|
||||
}
|
||||
if (c.apply_pending) { strcpy(reply, "ERR busy applying"); return true; }
|
||||
@@ -295,12 +318,17 @@ bool handle_ota_command(const char* command, char* reply, mesh::MainBoard& board
|
||||
uint8_t selmid[4]; uint32_t seltgt = sel->target_id; memcpy(selmid, sel->mid, 4); // sel may move on reset
|
||||
OtaStore* store; const char* dname;
|
||||
if (strncmp(dst, "flash", 5) == 0) {
|
||||
#if defined(OTA_SEEDER_ONLY)
|
||||
strcpy(reply, "ERR seeder-only build cannot stage or install firmware; use `folder`");
|
||||
return true;
|
||||
#else
|
||||
#if defined(NRF52_PLATFORM) && defined(OTA_SD_STORE)
|
||||
c.stopSdCacheFetch(); // manual install download takes priority over archiving
|
||||
#endif
|
||||
store = &c.fetch_store; c.fetch_store.clear(); dname = "flash"; validate = false; // seed lives in the folder
|
||||
#if defined(NRF52_PLATFORM) && !defined(OTA_SD_STORE)
|
||||
c.manager.set_accept_full(false); // nRF52 flash can install only in-place deltas
|
||||
#endif
|
||||
#endif
|
||||
} else if (strncmp(dst, "folder", 6) == 0) {
|
||||
if (!c.folder_dest) { strcpy(reply, "ERR no folder connected (run motatool serve --tcp/--serial)"); return true; }
|
||||
@@ -337,9 +365,15 @@ bool handle_ota_command(const char* command, char* reply, mesh::MainBoard& board
|
||||
// ---- broadcast our tiny beacon so peers discover us. If not already serving, set up flash-backed
|
||||
// self-serve first (so we're a real, fetchable source of our own running firmware). ----
|
||||
} else if (is_cmd(a, "announce|adv", &rest)) {
|
||||
#if defined(OTA_SEEDER_ONLY)
|
||||
c.manager.announce();
|
||||
snprintf(reply, 160, "OK beacon sent (serving=%u host mOTA)",
|
||||
(unsigned)c.manager.servedCount());
|
||||
#else
|
||||
if (!c.serving) c.serving = ota_serve_self(c, 0);
|
||||
c.manager.announce();
|
||||
sprintf(reply, "OK beacon sent (serving=%s)", c.serving ? "self fw" : "nothing");
|
||||
#endif
|
||||
|
||||
// ---- running firmware identity (compare against a delta's base_hash) ----
|
||||
} else if (is_cmd(a, "self|id", &rest)) {
|
||||
@@ -366,7 +400,10 @@ bool handle_ota_command(const char* command, char* reply, mesh::MainBoard& board
|
||||
// yes" round-trip - unreliable over LoRa): refuse unless the fetch is COMPLETE, then the apply path
|
||||
// validates in order (payload hash -> built-for-this-firmware -> signature/trust) and returns the
|
||||
// FIRST failing gate, so the operator knows exactly why it refused; it proceeds only if all pass.
|
||||
#if defined(NRF52_PLATFORM) && defined(OTA_SD_STORE)
|
||||
#if defined(OTA_SEEDER_ONLY)
|
||||
strcpy(reply, "ERR seeder-only build cannot install firmware via LoRa");
|
||||
return true;
|
||||
#elif defined(NRF52_PLATFORM) && defined(OTA_SD_STORE)
|
||||
if (c.sdCacheFetching()) {
|
||||
strcpy(reply, "ERR SD archive capture is active; use `ota cancel` before installing");
|
||||
return true;
|
||||
@@ -390,7 +427,9 @@ bool handle_ota_command(const char* command, char* reply, mesh::MainBoard& board
|
||||
const char* p = rest;
|
||||
if (strncmp(p, "on", 2) == 0) {
|
||||
#if defined(OTA_FOLDER_SERIAL)
|
||||
#if !defined(OTA_SEEDER_ONLY)
|
||||
if (!c.serving) c.serving = ota_serve_self(c, 0); // keep serving our own fw alongside the folder
|
||||
#endif
|
||||
char m2[120]; c.attach_folder(m2, sizeof(m2)); c.manager.announce();
|
||||
strncpy(reply, m2, 159); reply[159] = 0;
|
||||
#else
|
||||
@@ -398,7 +437,11 @@ bool handle_ota_command(const char* command, char* reply, mesh::MainBoard& board
|
||||
#endif
|
||||
} else if (strncmp(p, "off", 3) == 0) {
|
||||
c.detach_folder(); c.manager.announce();
|
||||
#if defined(OTA_SEEDER_ONLY)
|
||||
strcpy(reply, "OK folder detached (serving nothing)");
|
||||
#else
|
||||
strcpy(reply, "OK folder detached (still serving own fw)");
|
||||
#endif
|
||||
} else { // status + list served entries (* = our own fw)
|
||||
int n = snprintf(reply, 159, "folder=%s serving=%u:", c.folder_active ? "on" : "off",
|
||||
(unsigned)c.manager.servedCount());
|
||||
@@ -459,6 +502,11 @@ bool handle_ota_command(const char* command, char* reply, mesh::MainBoard& board
|
||||
else
|
||||
snprintf(reply, 160, "OK SD OTA archive capture %s (saved on card)", enabled ? "on" : "off");
|
||||
} else
|
||||
#endif
|
||||
#if defined(OTA_SEEDER_ONLY)
|
||||
if (strncmp(p, "autofetch ", 10) == 0 || strncmp(p, "autoinstall ", 12) == 0) {
|
||||
strcpy(reply, "ERR seeder-only build keeps autofetch and autoinstall off");
|
||||
} else
|
||||
#endif
|
||||
if (strncmp(p, "autofetch ", 10) == 0) {
|
||||
const char* v = p + 10;
|
||||
@@ -499,12 +547,18 @@ bool handle_ota_command(const char* command, char* reply, mesh::MainBoard& board
|
||||
c.autoinstall == OtaContext::AUTOINSTALL_TRUSTED ? "trusted" : "off",
|
||||
(unsigned)c.manager.checkpoint_blocks(), (unsigned)c.manager.advert_mins(),
|
||||
(unsigned)c.manager.max_hops(), (unsigned)c.allow.count());
|
||||
#else
|
||||
#if defined(OTA_SEEDER_ONLY)
|
||||
sprintf(reply, "ota config: mode=seeder-only autofetch=off autoinstall=off checkpoint=%u advert=%umin hops=%u",
|
||||
(unsigned)c.manager.checkpoint_blocks(), (unsigned)c.manager.advert_mins(),
|
||||
(unsigned)c.manager.max_hops());
|
||||
#else
|
||||
sprintf(reply, "ota config: autofetch=%s autoinstall=%s checkpoint=%u advert=%umin hops=%u 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.manager.advert_mins(),
|
||||
(unsigned)c.manager.max_hops(), (unsigned)c.allow.count());
|
||||
#endif
|
||||
#endif
|
||||
}
|
||||
|
||||
@@ -536,6 +590,11 @@ bool handle_ota_command(const char* command, char* reply, mesh::MainBoard& board
|
||||
|
||||
// Raw / internal primitives (manual content load + low-level apply steps), under `ota dev ...`.
|
||||
static bool handle_dev(const char* d, char* reply, OtaContext& c) {
|
||||
#if defined(OTA_SEEDER_ONLY)
|
||||
(void)d;
|
||||
(void)c;
|
||||
strcpy(reply, "ERR ota dev staging/apply is disabled on this seeder-only build");
|
||||
#else
|
||||
if (strncmp(d, "stage ", 6) == 0) {
|
||||
uint32_t sz = parse_u32(d + 6);
|
||||
if (sz == 0 || sz > OTA_SERVE_BUF_SIZE) { sprintf(reply, "ERR size 1..%u", OTA_SERVE_BUF_SIZE); }
|
||||
@@ -646,6 +705,7 @@ static bool handle_dev(const char* d, char* reply, OtaContext& c) {
|
||||
} else {
|
||||
strcpy(reply, "ota dev: stage|recv|serve|announce|verify|want|apply slot|manifest|verify|commit|clear");
|
||||
}
|
||||
#endif
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
@@ -44,9 +44,13 @@ namespace ota {
|
||||
class FolderMotaStore; // pull destination over the seeder link (full type only where instantiated/used)
|
||||
|
||||
#ifndef OTA_SERVE_BUF_SIZE
|
||||
#if defined(OTA_SEEDER_ONLY)
|
||||
// Source-only Companions never accept the manual in-memory stage command.
|
||||
// Keep the member valid without reserving 16 KB of nRF52840 SRAM.
|
||||
#define OTA_SERVE_BUF_SIZE 1
|
||||
// nRF52 self-serving streams from flash; this buffer is only for the manual `ota dev stage` helper.
|
||||
// Keep it to one flash page so the OTA singleton does not consume another 16 KB of scarce SRAM.
|
||||
#if defined(NRF52_PLATFORM) && (defined(OTA_FLASH_STORE) || defined(OTA_SD_STORE))
|
||||
#elif defined(NRF52_PLATFORM) && (defined(OTA_FLASH_STORE) || defined(OTA_SD_STORE))
|
||||
#define OTA_SERVE_BUF_SIZE 4096
|
||||
#else
|
||||
#define OTA_SERVE_BUF_SIZE 16384
|
||||
@@ -58,7 +62,12 @@ class FolderMotaStore; // pull destination over the seeder link (full type onl
|
||||
|
||||
struct OtaContext {
|
||||
OtaManager manager;
|
||||
#if defined(NRF52_PLATFORM) && defined(OTA_SD_STORE)
|
||||
#if defined(OTA_SEEDER_ONLY)
|
||||
// A seeder-only node never stages an image for itself. Keep a valid default
|
||||
// store object for OtaManager, while folder captures replace it with the
|
||||
// host-backed FolderMotaStore for the duration of the pull.
|
||||
OtaStoreRam<1> fetch_store;
|
||||
#elif defined(NRF52_PLATFORM) && defined(OTA_SD_STORE)
|
||||
OtaStoreSdNrf52 fetch_store; // MeshTower V2: persistent SD staging, full + delta
|
||||
OtaCacheSdNrf52 sd_cache; // persistent capture + source for every OTA container heard
|
||||
#elif defined(NRF52_PLATFORM) && defined(OTA_FLASH_STORE)
|
||||
@@ -131,6 +140,11 @@ struct OtaContext {
|
||||
// so the deferred-reboot path (mesh loop) takes over. Caller ensures the fetch is COMPLETE. Shared by
|
||||
// manual `ota applydelta` and the auto-install path.
|
||||
bool apply_fetched(char* msg) {
|
||||
#if defined(OTA_SEEDER_ONLY)
|
||||
strncpy(msg, "refused: this build serves mOTA images but cannot install one", 96);
|
||||
msg[95] = 0;
|
||||
return false;
|
||||
#else
|
||||
#if defined(NRF52_PLATFORM) && defined(OTA_SD_STORE)
|
||||
if (sdCacheFetching()) {
|
||||
strncpy(msg, "refused: SD archive capture owns the OTA receive slot", 96);
|
||||
@@ -165,6 +179,7 @@ struct OtaContext {
|
||||
#endif
|
||||
if (ok) apply_pending = true;
|
||||
return ok;
|
||||
#endif
|
||||
}
|
||||
|
||||
// Deferred apply-reboot: a verified `ota applydelta` approves the update but does NOT reboot inline,
|
||||
@@ -231,8 +246,13 @@ struct OtaContext {
|
||||
folder_active = true;
|
||||
_folder_link = link;
|
||||
_folder_source = source;
|
||||
#if defined(OTA_SEEDER_ONLY)
|
||||
snprintf(msg, cap, "OK folder attached (%s) - serving %u host mOTA total",
|
||||
label ? label : "external", (unsigned)manager.servedCount());
|
||||
#else
|
||||
snprintf(msg, cap, "OK folder attached (%s) - serving %u mOTA total (own fw + folder)",
|
||||
label ? label : "external", (unsigned)manager.servedCount());
|
||||
#endif
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -379,10 +399,19 @@ struct OtaContext {
|
||||
if (_fi.target_id) target_id = _fi.target_id;
|
||||
if (_fi.hw_id[0]) hw = _fi.hw_id;
|
||||
}
|
||||
#if defined(OTA_SEEDER_ONLY)
|
||||
// This role advertises only host-provided containers. A zero local target
|
||||
// prevents auto-selection of firmware for the seeder itself.
|
||||
target_id = 0;
|
||||
#endif
|
||||
manager.begin(target_id, send, ctx);
|
||||
if (hw) { strncpy(hw_id, hw, sizeof(hw_id) - 1); hw_id[sizeof(hw_id) - 1] = 0; }
|
||||
// a node only fetches firmware it can apply: ESP32 A/B -> sequential, nRF52 single-slot -> in-place
|
||||
#if defined(NRF52_PLATFORM) && defined(OTA_SD_STORE)
|
||||
#if defined(OTA_SEEDER_ONLY)
|
||||
manager.set_accept_full(true);
|
||||
manager.set_autofetch(OtaManager::AUTOFETCH_OFF);
|
||||
autoinstall = AUTOINSTALL_OFF;
|
||||
#elif defined(NRF52_PLATFORM) && defined(OTA_SD_STORE)
|
||||
manager.set_accept_full(true);
|
||||
manager.set_apply_codec(CODEC_DETOOLS_INPLACE);
|
||||
#elif defined(NRF52_PLATFORM)
|
||||
|
||||
Reference in new issue
Block a user