feat(mesh): radio hot-swap — probe on plug-in, keep-as-is vs apply-our-settings
Demo images / Build & push demo images (push) Failing after 1m49s
Demo images / Build & push demo images (push) Failing after 1m49s
Plugging in any LoRa radio now surfaces the device setup modal EVERY time (dismissals clear on unplug; works while mesh is enabled; 2-poll debounce so ordinary reconnect blips don't flash it), showing what's currently on the stick before anything is written: - mesh.probe-device RPC: identifies the firmware read-only in the same Reticulum->Meshcore->Meshtastic order as auto-detect. Meshtastic yields region/modem-preset/channels (want_config is a read); MeshCore yields name/node-id + firmware version via the previously-unused CMD_DEVICE_QUERY; RNode via the bare KISS DETECT (no daemon spawn). Path must be a detected candidate port; the live session's port refuses. - "Keep As Is": manage_radio=false persists in MeshConfig and the session skips every on-connect config write (region, channel, PHY params, advert name) — the radio runs exactly as flashed, hot-swappable. - "Set Up with Archipelago Settings": second screen shows the params that will be applied (channel, region, the node's persisted RF params - e.g. the validated Portugal preset - with the per-region community plan as fallback) before writing anything. - Stale-type fix: disconnect now resets device_type/firmware_version, so a swapped stick no longer shows the previous radio's firmware; probed kind pins device_kind so the right probe runs first on connect. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Fable 5
parent
2f26cb2bc4
commit
8b744d377c
@@ -383,6 +383,7 @@ impl RpcHandler {
|
||||
|
||||
// Mesh networking (Meshcore LoRa)
|
||||
"mesh.status" => self.handle_mesh_status().await,
|
||||
"mesh.probe-device" => self.handle_mesh_probe_device(params).await,
|
||||
"mesh.peers" => self.handle_mesh_peers().await,
|
||||
"mesh.messages" => self.handle_mesh_messages(params).await,
|
||||
"mesh.debug-dump" => self.handle_mesh_debug_dump().await,
|
||||
|
||||
@@ -181,6 +181,11 @@ impl RpcHandler {
|
||||
config.lora_radio_params = Some(parsed);
|
||||
}
|
||||
}
|
||||
// Hot-swap "keep as is": false = never write config to the radio
|
||||
// (region/channel/PHY/advert name) — use it exactly as flashed.
|
||||
if let Some(manage) = params.get("manage_radio").and_then(|v| v.as_bool()) {
|
||||
config.manage_radio = manage;
|
||||
}
|
||||
// Firmware pin: probe only the named firmware on the port ("auto"/""
|
||||
// clears the pin and restores strict-probe auto-detect).
|
||||
if let Some(kind) = params.get("device_kind").and_then(|v| v.as_str()) {
|
||||
|
||||
@@ -52,6 +52,9 @@ impl RpcHandler {
|
||||
.map(|k| k.to_string().to_lowercase())
|
||||
.into(),
|
||||
);
|
||||
// Hot-swap "keep as is" state: false = archipelago never writes
|
||||
// config to the radio (see MeshConfig::manage_radio).
|
||||
obj.insert("manage_radio".into(), config.manage_radio.into());
|
||||
// USB identity per detected port so the setup modal can show the
|
||||
// actual board (product string on native-USB boards, vid:pid as
|
||||
// the fallback for bridge chips).
|
||||
@@ -78,6 +81,35 @@ impl RpcHandler {
|
||||
Ok(value)
|
||||
}
|
||||
|
||||
/// mesh.probe-device — Identify the firmware on a detected serial port and
|
||||
/// read its current configuration WITHOUT provisioning it. Powers the
|
||||
/// hot-swap "device detected" modal's current-details view. The path must
|
||||
/// be one of the currently detected candidate ports (no arbitrary device
|
||||
/// paths), and probing the live session's own port is refused.
|
||||
pub(in crate::api::rpc) async fn handle_mesh_probe_device(
|
||||
&self,
|
||||
params: Option<serde_json::Value>,
|
||||
) -> Result<serde_json::Value> {
|
||||
let path = params
|
||||
.as_ref()
|
||||
.and_then(|p| p.get("path"))
|
||||
.and_then(|v| v.as_str())
|
||||
.ok_or_else(|| anyhow::anyhow!("Missing path"))?
|
||||
.to_string();
|
||||
let detected = mesh::detect_devices().await;
|
||||
anyhow::ensure!(
|
||||
detected.iter().any(|d| d == &path),
|
||||
"{path} is not a detected mesh-radio candidate port"
|
||||
);
|
||||
let service = self.mesh_service.read().await;
|
||||
let probe = match service.as_ref() {
|
||||
Some(svc) => svc.probe_device(&path).await?,
|
||||
// No mesh service yet (radio never enabled) — probe directly.
|
||||
None => mesh::listener::probe_device(&path).await?,
|
||||
};
|
||||
Ok(serde_json::to_value(probe)?)
|
||||
}
|
||||
|
||||
/// mesh.peers — List discovered mesh peers.
|
||||
pub(in crate::api::rpc) async fn handle_mesh_peers(&self) -> Result<serde_json::Value> {
|
||||
let service = self.mesh_service.read().await;
|
||||
|
||||
Reference in New Issue
Block a user