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
+32
View File
@@ -59,6 +59,10 @@ const FROM_RADIO_MAX: usize = 4096;
const ADMIN_SET_CONFIG_FIELD: u64 = 34;
/// AdminMessage.set_channel oneof field number (carries a `Channel`).
const ADMIN_SET_CHANNEL_FIELD: u64 = 33;
/// AdminMessage.reboot_seconds oneof field number (int32). Verified against
/// meshtastic/protobufs admin.proto: `reboot_seconds = 97` (NOT 40 — the
/// payload_variant numbers jump after the setters).
const ADMIN_REBOOT_SECONDS_FIELD: u64 = 97;
/// FromRadio.channel (field 10): a `Channel` streamed during want_config.
const FROM_RADIO_CHANNEL: u64 = 10;
const FROM_RADIO_QUEUE_STATUS: u64 = 11;
@@ -383,6 +387,34 @@ impl MeshtasticDevice {
Ok(())
}
/// Reboot the locally-connected radio via `AdminMessage { reboot_seconds }`
/// on the ADMIN_APP port — the same local-admin path `set_advert_name` /
/// `set_lora_region` use (no session passkey needed over serial). The
/// firmware reboots after `seconds`, which clears a wedged / RX-deaf radio
/// (a radio that has stopped hearing the mesh while still transmitting) and
/// re-runs its LoRa init. The listener's reboot→reconnect loop reopens the
/// serial link when it comes back.
pub async fn reboot(&mut self, seconds: i64) -> Result<()> {
let Some(node_num) = self.node_num else {
anyhow::bail!("Meshtastic reboot: node_num unknown");
};
// AdminMessage { reboot_seconds(97): int32 }. We only ever pass a small
// positive delay, which encodes as a plain varint.
let mut admin = Vec::new();
encode_varint_field_into(ADMIN_REBOOT_SECONDS_FIELD, seconds as u64, &mut admin);
let packet = encode_mesh_packet(node_num, ADMIN_APP, &admin);
self.send_to_radio(&encode_to_radio_variant(TO_RADIO_PACKET, &packet))
.await
.context("Failed to send Meshtastic reboot admin packet")?;
info!(
node_num,
seconds, "Sent Meshtastic radio reboot (device will reboot to recover)"
);
Ok(())
}
/// Provision archy's two channels so the radio works like off-the-shelf
/// Meshtastic AND carries our private group:
/// - slot 0 (PRIMARY) = the DEFAULT public channel (name "", default key)