fix(mesh): native E2E DM for archy↔archy text + software radio-reboot

- send_message now sends archy↔archy plain text as a native TEXT_MESSAGE_APP
  DM (firmware PKC-encrypts E2E), not wrapped in the binary typed envelope
  that silently broke archy↔archy LoRa delivery. Archy peers' Sent rows are
  marked encrypted so the E2E pill shows; rich typed msgs still use the
  typed-wire path.
- Add a software radio-reboot to recover a wedged/RX-deaf radio without
  physical access (and for the Device-tab settings panel): driver reboot()
  via AdminMessage reboot_seconds=97 (verified vs meshtastic/protobufs),
  MeshCommand::RebootRadio, MeshService::reboot_radio, RPC mesh.reboot-radio.
- Handoff doc: docs/SESSION-1.8.0-OTA-PROGRESS.md "RESUME HERE" — RF link is
  the proven blocker (radios not hearing each other); modem_preset mismatch
  is the prime suspect; on-device Meshtastic-app check + fix plan documented.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
archipelago
2026-06-30 10:39:34 -04:00
co-authored by Claude Opus 4.8
parent b4531bb4fc
commit fbfeeeb0f5
8 changed files with 312 additions and 67 deletions
@@ -366,6 +366,7 @@ impl RpcHandler {
"mesh.send" => self.handle_mesh_send(params).await,
"mesh.send-channel" => self.handle_mesh_send_channel(params).await,
"mesh.broadcast" => self.handle_mesh_broadcast().await,
"mesh.reboot-radio" => self.handle_mesh_reboot_radio(params).await,
"mesh.configure" => self.handle_mesh_configure(params).await,
"mesh.send-invoice" => self.handle_mesh_send_invoice(params).await,
"mesh.send-coordinate" => self.handle_mesh_send_coordinate(params).await,
@@ -86,6 +86,29 @@ impl RpcHandler {
Ok(serde_json::json!({ "broadcast": true }))
}
/// mesh.reboot-radio — Reboot the locally-connected radio firmware to
/// recover a wedged / RX-deaf radio. Optional `seconds` delay (default 2).
pub(in crate::api::rpc) async fn handle_mesh_reboot_radio(
&self,
params: Option<serde_json::Value>,
) -> Result<serde_json::Value> {
let seconds = params
.as_ref()
.and_then(|p| p.get("seconds"))
.and_then(|v| v.as_i64())
.unwrap_or(2);
let service = self.mesh_service.read().await;
let svc = service
.as_ref()
.ok_or_else(|| anyhow::anyhow!("Mesh service not running. Enable mesh first."))?;
svc.reboot_radio(seconds).await?;
info!(seconds, "Mesh radio reboot requested via RPC");
Ok(serde_json::json!({ "reboot": true, "seconds": seconds }))
}
/// mesh.configure — Enable/disable mesh and set device path.
pub(in crate::api::rpc) async fn handle_mesh_configure(
&self,