feat(ota): broadcast OTA start/fail milestones on the alert channel
An operator who triggers `ota update` via remote management only sees the
command's reply; the actual OTA runs ~2.5s later and reboots on success,
so the start/fail notifications land outside the reply window. Mirror the
key OTA milestones onto the configured alert channel (in addition to the
Serial log):
- START ("OTA update starting") from beginDeferredOtaUpdate(), i.e. while
the loop still runs -- a flood queued at the deferred fire could never
transmit before the flash blocks the loop / reboots on success.
- FAIL ("OTA aborted: ...") at both abort points (teardown barrier
withheld flashing; preflight/download error).
Success has no message: a successful flash reboots into the new image, so
the node returning on the new version is the signal.
New MyMesh::otaAlert() gates on the `alert on/off` master switch and rides
the configured alert scope (AlertReporter::sendText -> sendChannel ->
resolveAlertScope); no-op when alerts are off or no channel is set. Only
these start/fail milestones -- routine slot connect/disconnect is
unaffected (stays in AlertReporter's fault logic). Documented in ALERTS.md.
Both observer firmwares build.
This commit is contained in:
1 parent
686a2f240a
commit
f68a0fb9e7
3 files changed
+37
No files matched your search
@@ -31,6 +31,16 @@ Alert floods ride the **repeater's default scope** by default (the same Transpor
|
||||
|
||||
A "recovered" message is sent once when the underlying connection comes back. After firing, a fault is rate-limited by `alert.interval` (default 60 minutes) before it can re-fire - this prevents flapping links from spamming the channel.
|
||||
|
||||
## OTA milestone alerts
|
||||
|
||||
Key `ota update` milestones are also broadcast on the alert channel (in addition to the Serial log), so an operator who triggered the update via remote management still gets feedback: the real OTA work runs ~2.5 s after the command and reboots on success, both **outside** the command's reply window.
|
||||
|
||||
- **Start** - `OTA update starting`, sent when the update is confirmed and scheduled (during the reply window, so it transmits before the flash blocks the loop).
|
||||
- **Fail/abort** - `OTA aborted: MQTT stop unclean, bridge resumed` (the teardown barrier withheld flashing) or `OTA aborted: <reason>` (preflight/download error). The bridge is resumed either way.
|
||||
- **Success** has no dedicated message: a successful flash reboots into the new image, so the node reappearing on the new version is the success signal.
|
||||
|
||||
These share the alert channel, scope (`alert.region`), and the `alert` master switch with fault alerts - if `alert` is `off` or no channel is configured, OTA milestones are silent. Unlike fault alerts they are **not** rate-limited (OTA is operator-initiated and rare), and they are limited to these start/fail milestones - routine MQTT slot connect/disconnect never triggers an OTA alert.
|
||||
|
||||
## Defaults
|
||||
|
||||
| Setting | Default | Notes |
|
||||
|
||||
@@ -1575,11 +1575,18 @@ void MyMesh::loop() {
|
||||
// resume the bridge instead of flashing under uncertain ownership.
|
||||
if (bridge && !bridge->canFlashAfterStop()) {
|
||||
Serial.println("OTA: aborted, MQTT stop did not complete cleanly - resuming bridge");
|
||||
otaAlert("OTA aborted: MQTT stop unclean, bridge resumed");
|
||||
setBridgeState(true);
|
||||
} else if (!_cli.getBoard()->otaFromManifest(getFirmwareVer(), false, ota_reply)) {
|
||||
Serial.print("OTA: aborted, resuming bridge - "); Serial.println(ota_reply);
|
||||
char ota_alert_msg[160];
|
||||
snprintf(ota_alert_msg, sizeof(ota_alert_msg), "OTA aborted: %s", ota_reply);
|
||||
otaAlert(ota_alert_msg);
|
||||
setBridgeState(true);
|
||||
}
|
||||
// Success path: otaFromManifest() flashes and reboots into the new image
|
||||
// (never returns), so there is no in-boot "success" alert — the START alert
|
||||
// plus the node returning on the new version is the success signal.
|
||||
}
|
||||
#endif
|
||||
|
||||
|
||||
@@ -344,12 +344,32 @@ public:
|
||||
#endif
|
||||
}
|
||||
|
||||
#if defined(WITH_MQTT_BRIDGE)
|
||||
// Broadcast a key OTA milestone (start/fail only) on the configured alert
|
||||
// channel, in addition to the Serial log — so an operator who triggered
|
||||
// `ota update` via remote management still gets feedback that lands well after
|
||||
// the command's reply window. Respects the `alert on/off` master switch and
|
||||
// rides the configured alert scope (sendChannel -> resolveAlertScope); a no-op
|
||||
// when alerts are off or no channel is set. Deliberately NOT wired to routine
|
||||
// slot connect/disconnect — those remain in AlertReporter's fault logic.
|
||||
void otaAlert(const char* msg) {
|
||||
auto* obs = _cli.getObserverPrefs();
|
||||
if (obs && obs->alert_enabled) _alerter.sendText(msg);
|
||||
}
|
||||
#endif
|
||||
|
||||
// Schedule the pull-OTA flash to run from loop() in ~2.5 s, leaving time for the
|
||||
// "Beginning update..." CLI reply (CLI_REPLY_DELAY_MILLIS = 600 ms) to transmit
|
||||
// before the flash blocks the loop and reboots.
|
||||
bool beginDeferredOtaUpdate() override {
|
||||
_ota_update_at = millis() + 2500;
|
||||
if (_ota_update_at == 0) _ota_update_at = 1; // 0 means "none"
|
||||
#if defined(WITH_MQTT_BRIDGE)
|
||||
// Broadcast START now, while the loop still runs (the 2.5 s reply window):
|
||||
// the deferred flash blocks the loop and, on success, reboots — so a start
|
||||
// alert queued at fire time could never transmit. See otaAlert().
|
||||
otaAlert("OTA update starting");
|
||||
#endif
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
Reference in new issue
Block a user