feat(mesh): device-detected setup modal with board images + full radio settings
Plugging in a LoRa radio now pops a global two-step setup modal on any page: step 1 shows the ACTUAL detected board (25 board SVGs vendored from the Meshtastic web-flasher, GPL-3.0; matched by USB product string on native-USB boards, vid:pid heuristics for bridge chips, animated fallback otherwise); step 2 configures with Archipelago presets — the LoRa region pre-selected from the node's location (new lat/lon→region mapping), channel 'archipelago', node name, and an advanced firmware pin. Options adapt per protocol: region+channel program Meshtastic; MeshCore shows its community frequency plan (firmware owns RF); RNode defers to the Reticulum daemon config. Backend: mesh.configure now accepts lora_region (validated against the driver's region table) and device_kind (probe pin); mesh.status returns the persisted values plus per-port USB identity (sysfs vid/pid/product) for board identification. The Mesh Device tab gains a full settings form (region with duty-cycle/legality hints, firmware pin, channel, name, identity broadcast) replacing the read-only panel; the old Mesh-page-only onboarding modal is superseded by the global flow. Mock backend: stateful mesh config so the dev UI demos the full detect→configure round-trip (disable mesh → modal fires with Heltec V3). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Fable 5
parent
c014c4b3ee
commit
7b36b5cc34
@@ -145,6 +145,32 @@ impl RpcHandler {
|
||||
{
|
||||
config.receive_block_headers = receive;
|
||||
}
|
||||
// LoRa region (Meshtastic): validated against the driver's region
|
||||
// table so a typo can't be persisted and silently ignored on connect.
|
||||
// Empty string clears the setting (radio keeps/uses its own region).
|
||||
if let Some(region) = params.get("lora_region").and_then(|v| v.as_str()) {
|
||||
let trimmed = region.trim();
|
||||
if trimmed.is_empty() || trimmed.eq_ignore_ascii_case("unset") {
|
||||
config.lora_region = None;
|
||||
} else if mesh::meshtastic_region_is_valid(trimmed) {
|
||||
config.lora_region = Some(trimmed.to_uppercase());
|
||||
} else {
|
||||
anyhow::bail!("Unknown LoRa region: {trimmed}");
|
||||
}
|
||||
}
|
||||
// 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()) {
|
||||
config.device_kind = match kind.trim().to_lowercase().as_str() {
|
||||
"" | "auto" => None,
|
||||
"meshcore" => Some(mesh::types::DeviceType::Meshcore),
|
||||
"meshtastic" => Some(mesh::types::DeviceType::Meshtastic),
|
||||
"reticulum" | "rnode" => Some(mesh::types::DeviceType::Reticulum),
|
||||
other => anyhow::bail!(
|
||||
"Unknown device_kind: {other} (expected auto|meshcore|meshtastic|reticulum)"
|
||||
),
|
||||
};
|
||||
}
|
||||
|
||||
mesh::save_config(&self.config.data_dir, &config).await?;
|
||||
|
||||
@@ -161,6 +187,8 @@ impl RpcHandler {
|
||||
"device_path": config.device_path,
|
||||
"announce_block_headers": config.announce_block_headers,
|
||||
"receive_block_headers": config.receive_block_headers,
|
||||
"lora_region": config.lora_region,
|
||||
"device_kind": config.device_kind.map(|k| k.to_string()),
|
||||
}))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -37,6 +37,24 @@ impl RpcHandler {
|
||||
"receive_block_headers".into(),
|
||||
config.receive_block_headers.into(),
|
||||
);
|
||||
// Persisted config values the settings UI edits (distinct from the
|
||||
// live radio-reported `region`): the configured LoRa region and
|
||||
// the firmware pin ("meshcore"|"meshtastic"|"reticulum"|null=auto).
|
||||
obj.insert("lora_region".into(), config.lora_region.clone().into());
|
||||
obj.insert(
|
||||
"device_kind".into(),
|
||||
config
|
||||
.device_kind
|
||||
.map(|k| k.to_string().to_lowercase())
|
||||
.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).
|
||||
obj.insert(
|
||||
"detected_device_info".into(),
|
||||
serde_json::to_value(mesh::detect_devices_info().await).unwrap_or_default(),
|
||||
);
|
||||
// Raw serial-device presence, in BOTH branches. MeshStatus has no
|
||||
// such field, so while the service was running the UI couldn't
|
||||
// tell "no radio plugged in" from "radio present but the session
|
||||
|
||||
Reference in New Issue
Block a user