mirror of
https://github.com/vk496/MeshCore.git
synced 2026-10-22 19:41:24 +00:00
ota: admin OTA statistics — ota stats reply
Add an admin-only `ota stats` reply: one dense line with the running firmware's merkle content-id (mid) AND its EndF body_hash (only body_hash was surfaced before), version, served-set count + digest, live fetch state/progress, and policy — snprintf-bounded to the 160-byte reply. The remote CLI path is already admin-gated, so it's admin-only over the mesh (send it from the app's repeater command screen, or the WiFi/serial OTA console). A new servedDigest() accessor exposes the beacon set-digest. HW-verified on RAK4631: reports the fw identity + live fetch state (incl. during a warm-start capture).
This commit is contained in:
1 parent
8953214761
commit
b159737ede
3 files changed
+63
-2
No files matched your search
@@ -47,6 +47,16 @@ ota status
|
||||
Shows your current firmware version, your node's update "target" (its hardware/role id), and whether a
|
||||
download is in progress.
|
||||
|
||||
For a denser admin view — your firmware's content id (`mid`) **and** its body hash, the fingerprint of the
|
||||
set you're serving, live download progress, and the current policy — use:
|
||||
|
||||
```
|
||||
ota stats
|
||||
```
|
||||
|
||||
On a remote node this is **admin-only** (the remote command console requires the admin password) — send it
|
||||
from the app's repeater command screen, or the WiFi/serial OTA console.
|
||||
|
||||
### 2. Find updates available near me
|
||||
|
||||
```
|
||||
@@ -229,6 +239,7 @@ that only contains what changed). You get them by:
|
||||
|---|---|
|
||||
| List all commands | `ota help` |
|
||||
| See my firmware + any download | `ota status` (or just `ota`) |
|
||||
| Admin: ids/hashes + serving + policy | `ota stats` (admin-only remotely) |
|
||||
| Find updates nearby | `ota ls` |
|
||||
| Download update #1 | `ota get 1` |
|
||||
| Cancel a download | `ota cancel` |
|
||||
|
||||
@@ -52,6 +52,19 @@ static const char* state_word(OtaManager::FetchState s) {
|
||||
}
|
||||
}
|
||||
|
||||
// A compact one-word fetch state for the dense `ota stats` line (state_word's phrases are too long).
|
||||
static const char* state_short(OtaManager::FetchState s) {
|
||||
switch (s) {
|
||||
case OtaManager::WANT_MANIFEST: return "manifest";
|
||||
case OtaManager::WANT_LEAVES: return "leaves";
|
||||
case OtaManager::FETCHING: return "dl";
|
||||
case OtaManager::COMPLETE: return "done";
|
||||
case OtaManager::FAILED: return "failed";
|
||||
case OtaManager::PAUSED: return "paused";
|
||||
default: return "idle";
|
||||
}
|
||||
}
|
||||
|
||||
// Render the packed fw_version as "v1.2.3" (or "v1.2.3.4" when a prerelease byte is set).
|
||||
static void ver_str(char* out, size_t cap, uint32_t v) {
|
||||
FwVersion fw = FwVersion::unpack(v);
|
||||
@@ -97,8 +110,8 @@ 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)) {
|
||||
snprintf(reply, 160,
|
||||
"OTA: status | ls=find updates | get <#>=download | install | cancel | announce | self | "
|
||||
"folder | config | key. Try `ota ls`.");
|
||||
"OTA: status | stats=admin ids/hashes | ls=find updates | get <#>=download | install | cancel | "
|
||||
"announce | self | folder | config | key. Try `ota ls`.");
|
||||
|
||||
// ---- inventory dashboard: running fw (self), the one fetch session, serving state ----
|
||||
} else if (*a == 0 || is_cmd(a, "status|st", &rest)) {
|
||||
@@ -128,6 +141,41 @@ bool handle_ota_command(const char* command, char* reply, mesh::MainBoard& board
|
||||
c.bootloaderCaps().present ? "apply" : "NONE", ota_bootloader_last_rc());
|
||||
#endif
|
||||
|
||||
// ---- admin OTA stats: crypto identities (our fw's content-id + body_hash), serving set, live fetch,
|
||||
// policy — one dense line. The remote-admin CLI path is admin-gated, so this is admin-only over the
|
||||
// mesh (send it from the app's repeater command screen, or the WiFi/serial OTA console).
|
||||
} else if (is_cmd(a, "stats", &rest)) {
|
||||
// our running fw's merkle content-id (mid) comes from the self serve entry; the EndF body_hash (fw
|
||||
// identity, matched against a delta base) is separate — surface BOTH (only body_hash showed before).
|
||||
const OtaManager::ServeEntry* self = nullptr;
|
||||
for (uint8_t i = 0; i < c.manager.servedCount(); i++) {
|
||||
const OtaManager::ServeEntry* e = c.manager.servedEntry(i);
|
||||
if (e && e->is_self) { self = e; break; }
|
||||
}
|
||||
SelfFwInfo fi; bool sok = ota_self_firmware(fi);
|
||||
char midhx[9], bodyhx[9], verbuf[20], dighx[9];
|
||||
if (self) mesh::Utils::toHex(midhx, self->mid, 4); else strcpy(midhx, "?");
|
||||
if (sok && fi.valid) mesh::Utils::toHex(bodyhx, fi.body_hash, 4); else strcpy(bodyhx, "?");
|
||||
if (self) ver_str(verbuf, sizeof verbuf, self->fw_version); else strcpy(verbuf, "v?");
|
||||
uint8_t dig[4]; c.manager.servedDigest(dig); mesh::Utils::toHex(dighx, dig, 4);
|
||||
OtaManager::FetchState fs = c.manager.fetchState();
|
||||
char fbuf[72];
|
||||
if (fs == OtaManager::IDLE) {
|
||||
strcpy(fbuf, "fetch idle");
|
||||
} else {
|
||||
char fmid[9]; mesh::Utils::toHex(fmid, c.manager.fetchManifestId(), 4);
|
||||
unsigned have = (unsigned)c.manager.blocksHave(), tot = (unsigned)c.manager.blocksTotal();
|
||||
unsigned pct = tot ? (unsigned)((uint64_t)have * 100 / tot) : 0;
|
||||
unsigned age = c.session_started_ms ? (unsigned)((millis() - c.session_started_ms) / 1000) : 0;
|
||||
snprintf(fbuf, sizeof fbuf, "fetch %s %u/%u %u%% id=%s %us", state_short(fs), have, tot, pct, fmid, age);
|
||||
}
|
||||
uint8_t af = c.manager.autofetch();
|
||||
snprintf(reply, 160, "OTA | fw %s id=%s body=%s %ub %uK | serv %u dg=%s | %s | af=%s hops=%u",
|
||||
verbuf, midhx, bodyhx, (unsigned)(self ? self->have_count : 0),
|
||||
(unsigned)((sok ? fi.image_len : 0) / 1024), (unsigned)c.manager.servedCount(), dighx, fbuf,
|
||||
af == OtaManager::AUTOFETCH_ANY ? "any" : af == OtaManager::AUTOFETCH_SIGNED ? "signed" : "off",
|
||||
(unsigned)c.manager.max_hops());
|
||||
|
||||
// ---- what's available around me (catalogued from beacons + OTA_HAVE), best/most-recent first ----
|
||||
} else if (is_cmd(a, "neighbors|nbrs|updates|ls|n", &rest)) {
|
||||
// Kick a fresh round of catalog queries (async — rows arrive over the next seconds); render what we
|
||||
|
||||
@@ -174,6 +174,8 @@ public:
|
||||
uint8_t servedCount() const { return _n_serve; } // total mOTAs we offer (own fw + folder)
|
||||
// Read-only view of one served entry (for `ota serve` listing): mid/target/fwver/codec/flags + is_self.
|
||||
const ServeEntry* servedEntry(uint8_t i) const { return i < _n_serve ? &_serve[i] : nullptr; }
|
||||
// 4-byte fingerprint of our served set (== the set_digest carried in the beacon); for admin OTA stats.
|
||||
void servedDigest(uint8_t out[4]) const { setDigest(out); }
|
||||
|
||||
// Broadcast the tiny per-node BEACON (OTA_ADV): seeder_id + count + set-digest of everything we serve.
|
||||
// Constant size regardless of how many mOTAs — peers ask for the catalog via OTA_QUERY only on interest.
|
||||
|
||||
Reference in new issue
Block a user