diff --git a/core/archipelago/src/api/rpc/mesh/status.rs b/core/archipelago/src/api/rpc/mesh/status.rs index fd8c2813..74332a69 100644 --- a/core/archipelago/src/api/rpc/mesh/status.rs +++ b/core/archipelago/src/api/rpc/mesh/status.rs @@ -41,6 +41,10 @@ impl RpcHandler { // 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( + "lora_radio_params".into(), + serde_json::to_value(config.lora_radio_params).unwrap_or_default(), + ); obj.insert( "device_kind".into(), config diff --git a/neode-ui/src/stores/mesh.ts b/neode-ui/src/stores/mesh.ts index f8cfc670..65e6c023 100644 --- a/neode-ui/src/stores/mesh.ts +++ b/neode-ui/src/stores/mesh.ts @@ -48,6 +48,9 @@ export interface MeshConfigureParams { receive_block_headers?: boolean lora_region?: string device_kind?: string + /** MeshCore LoRa PHY params in firmware field units (freq_khz = MHz×1000, + * bw_hz = kHz×1000). null clears; omitted leaves unchanged. */ + lora_radio_params?: { freq_khz: number; bw_hz: number; sf: number; cr: number } | null } export interface MeshPeer { diff --git a/neode-ui/src/views/mesh/MeshDevicePanel.vue b/neode-ui/src/views/mesh/MeshDevicePanel.vue index 2d7da3e6..5364dedd 100644 --- a/neode-ui/src/views/mesh/MeshDevicePanel.vue +++ b/neode-ui/src/views/mesh/MeshDevicePanel.vue @@ -27,6 +27,12 @@ const form = ref({ channel: 'archipelago', name: '', broadcastIdentity: true, + // MeshCore LoRa PHY params (human units; converted to firmware units on + // save). All four empty = leave the radio's flashed settings untouched. + rfFreqMhz: '', + rfBwKhz: '', + rfSf: '', + rfCr: '', }) const saving = ref(false) const saveError = ref(null) @@ -43,6 +49,16 @@ watch( form.value.deviceKind = s.device_kind ?? 'auto' form.value.channel = s.channel_name || 'archipelago' form.value.name = s.self_advert_name ?? '' + const rp = (s as Record).lora_radio_params as + | { freq_khz: number; bw_hz: number; sf: number; cr: number } + | null + | undefined + if (rp) { + form.value.rfFreqMhz = String(rp.freq_khz / 1000) + form.value.rfBwKhz = String(rp.bw_hz / 1000) + form.value.rfSf = String(rp.sf) + form.value.rfCr = String(rp.cr) + } }, { immediate: true }, ) @@ -62,12 +78,31 @@ async function saveSettings() { saveError.value = null saveDone.value = false try { + // MeshCore RF params: send only when all four are filled (partial input + // is a user error surfaced by the API's range validation; all-empty + // means "leave the radio's flashed settings alone"). + const rf = [form.value.rfFreqMhz, form.value.rfBwKhz, form.value.rfSf, form.value.rfCr] + const rfAll = rf.every((v) => String(v).trim() !== '') + const rfAny = rf.some((v) => String(v).trim() !== '') + if (rfAny && !rfAll) { + throw new Error('Fill in all four RF fields (frequency, bandwidth, SF, CR) or leave all empty') + } await mesh.configure({ lora_region: form.value.region, device_kind: form.value.deviceKind, channel_name: form.value.channel.trim() || 'archipelago', ...(form.value.name.trim() ? { advert_name: form.value.name.trim() } : {}), broadcast_identity: form.value.broadcastIdentity, + ...(rfAll + ? { + lora_radio_params: { + freq_khz: Math.round(parseFloat(form.value.rfFreqMhz) * 1000), + bw_hz: Math.round(parseFloat(form.value.rfBwKhz) * 1000), + sf: parseInt(form.value.rfSf, 10), + cr: parseInt(form.value.rfCr, 10), + }, + } + : {}), }) saveDone.value = true setTimeout(() => { saveDone.value = false }, 3000) @@ -93,18 +128,6 @@ async function saveSettings() { Node ID {{ mesh.status.self_node_id != null ? `!${mesh.status.self_node_id.toString(16).padStart(8, '0')}` : '—' }} -
- Name - {{ mesh.status.self_advert_name ?? '—' }} -
-
- Region (radio) - {{ mesh.status.region ?? 'Not set' }} -
-
- Channel - {{ mesh.status.channel_name }} -
Type {{ deviceType === 'unknown' ? '—' : deviceType }} @@ -128,10 +151,10 @@ async function saveSettings() { Applied to fresh (region-unset) Meshtastic radios; a radio that already has a region keeps it.

- MeshCore community plan for {{ selectedRegion?.code }}: {{ meshcorePlan.freqMhz }} MHz, {{ meshcorePlan.bwKhz }} kHz, SF{{ meshcorePlan.sf }}, CR4/{{ meshcorePlan.cr }} — the radio's flashed RF settings apply; adjust via a MeshCore client if they differ. + MeshCore community plan for {{ selectedRegion?.code }}: {{ meshcorePlan.freqMhz }} MHz, {{ meshcorePlan.bwKhz }} kHz, SF{{ meshcorePlan.sf }}, CR4/{{ meshcorePlan.cr }} — set the RF fields below to program the radio.

- MeshCore radios keep their flashed RF settings — verify the radio matches your region's band{{ selectedRegion ? ` (${selectedRegion.band} MHz)` : '' }}. + Program the radio's RF settings with the fields below — every radio on your mesh must match{{ selectedRegion ? ` (${selectedRegion.band} MHz band)` : '' }}.

RNode RF parameters are managed by the Reticulum daemon's interface config on this node. @@ -158,6 +181,43 @@ async function saveSettings() {

+ + +
+
MeshCore RF parameters
+
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+

+ Saved settings program the radio on its next connect (it reboots once to apply). Leave all four empty to keep the radio's own settings. +

+