2026-06-30 23:03:09 -04:00
< script setup lang = "ts" >
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>
2026-07-14 08:40:18 -04:00
import { ref , computed , watch } from 'vue'
2026-06-30 23:03:09 -04:00
import { useMeshStore } from '@/stores/mesh'
2026-07-21 08:36:59 -04:00
import { LORA _REGIONS , regionByCode , meshcorePlanFor , MESHCORE _RF _PRESETS } from '@/utils/loraRegions'
2026-06-30 23:03:09 -04:00
const mesh = useMeshStore ( )
const rebooting = ref ( false )
const rebootError = ref < string | null > ( null )
async function handleReboot ( ) {
rebooting . value = true
rebootError . value = null
try {
await mesh . rebootRadio ( )
} catch ( e ) {
rebootError . value = e instanceof Error ? e . message : 'Failed to reboot radio'
} finally {
rebooting . value = false
}
}
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>
2026-07-14 08:40:18 -04:00
// ── Editable settings (persisted via mesh.configure) ──
const form = ref ( {
region : '' ,
deviceKind : 'auto' ,
channel : 'archipelago' ,
name : '' ,
broadcastIdentity : true ,
2026-07-21 06:53:10 -04:00
// 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 : '' ,
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>
2026-07-14 08:40:18 -04:00
} )
const saving = ref ( false )
const saveError = ref < string | null > ( null )
const saveDone = ref ( false )
let seeded = false
// Seed the form once from status (don't clobber in-progress edits on poll)
watch (
( ) => mesh . status ,
( s ) => {
if ( ! s || seeded ) return
seeded = true
form . value . region = s . lora _region ? ? ''
form . value . deviceKind = s . device _kind ? ? 'auto'
form . value . channel = s . channel _name || 'archipelago'
form . value . name = s . self _advert _name ? ? ''
2026-07-21 06:53:10 -04:00
const rp = ( s as Record < string , unknown > ) . lora _radio _params as
| { freq _khz : number ; bw _hz : number ; sf : number ; cr : number }
| null
| undefined
if ( rp ) {
2026-07-21 08:36:59 -04:00
applyingPreset = true
2026-07-21 06:53:10 -04:00
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 )
2026-07-21 08:36:59 -04:00
// Show the matching named preset if the stored values are one; else Custom.
const match = MESHCORE _RF _PRESETS . find (
( p ) =>
Math . round ( p . freqMhz * 1000 ) === rp . freq _khz &&
Math . round ( p . bwKhz * 1000 ) === rp . bw _hz &&
p . sf === rp . sf &&
p . cr === rp . cr ,
)
rfPreset . value = match ? match . id : 'custom'
applyingPreset = false
2026-07-21 06:53:10 -04:00
}
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>
2026-07-14 08:40:18 -04:00
} ,
{ immediate : true } ,
)
2026-07-21 08:36:59 -04:00
// RF preset dropdown: picking a named plan fills the four fields; hand-editing
// any field flips the dropdown to Custom so it never misrepresents the values.
const rfPreset = ref ( '' )
let applyingPreset = false
function selectRfPreset ( id : string ) {
rfPreset . value = id
if ( id === 'custom' || id === '' ) return
const p = MESHCORE _RF _PRESETS . find ( ( x ) => x . id === id )
if ( ! p ) return
applyingPreset = true
form . value . rfFreqMhz = String ( p . freqMhz )
form . value . rfBwKhz = String ( p . bwKhz )
form . value . rfSf = String ( p . sf )
form . value . rfCr = String ( p . cr )
applyingPreset = false
}
watch (
( ) => [ form . value . rfFreqMhz , form . value . rfBwKhz , form . value . rfSf , form . value . rfCr ] ,
( ) => {
if ( ! applyingPreset && rfPreset . value && rfPreset . value !== 'custom' ) rfPreset . value = 'custom'
} ,
)
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>
2026-07-14 08:40:18 -04:00
const selectedRegion = computed ( ( ) => regionByCode ( form . value . region ) )
const deviceType = computed ( ( ) => mesh . status ? . device _type ? ? 'unknown' )
// Firmware whose options apply: explicit pin wins, else the connected type.
const effectiveKind = computed ( ( ) => {
if ( form . value . deviceKind !== 'auto' ) return form . value . deviceKind
const t = deviceType . value . toLowerCase ( )
return t === 'meshcore' || t === 'meshtastic' || t === 'reticulum' ? t : 'auto'
} )
const meshcorePlan = computed ( ( ) => meshcorePlanFor ( form . value . region ) )
async function saveSettings ( ) {
saving . value = true
saveError . value = null
saveDone . value = false
try {
2026-07-21 08:36:59 -04:00
// MeshCore RF params: a named preset sends its plan; Custom sends the
// four fields (all required — partial input is an error); "Keep the
// radio's current settings" omits the key so the config is untouched.
let rfParams : { freq _khz : number ; bw _hz : number ; sf : number ; cr : number } | undefined
if ( rfPreset . value && rfPreset . value !== 'custom' ) {
const p = MESHCORE _RF _PRESETS . find ( ( x ) => x . id === rfPreset . value )
if ( p ) {
rfParams = {
freq _khz : Math . round ( p . freqMhz * 1000 ) ,
bw _hz : Math . round ( p . bwKhz * 1000 ) ,
sf : p . sf ,
cr : p . cr ,
}
}
} else if ( rfPreset . value === 'custom' ) {
const rf = [ form . value . rfFreqMhz , form . value . rfBwKhz , form . value . rfSf , form . value . rfCr ]
if ( ! rf . every ( ( v ) => String ( v ) . trim ( ) !== '' ) ) {
throw new Error ( 'Fill in all four RF fields (frequency, bandwidth, SF, CR)' )
}
rfParams = {
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 ) ,
}
2026-07-21 06:53:10 -04:00
}
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>
2026-07-14 08:40:18 -04:00
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 ,
2026-07-21 08:36:59 -04:00
... ( rfParams ? { lora _radio _params : rfParams } : { } ) ,
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>
2026-07-14 08:40:18 -04:00
} )
saveDone . value = true
setTimeout ( ( ) => { saveDone . value = false } , 3000 )
} catch ( e ) {
saveError . value = e instanceof Error ? e . message : 'Failed to save mesh settings'
} finally {
saving . value = false
}
}
2026-06-30 23:03:09 -04:00
< / script >
< template >
< div class = "glass-card mesh-device-panel" >
< h3 class = "mesh-panel-title" > Device < / h3 >
< p class = "mesh-panel-sub" > Firmware , identity , and radio controls for the connected mesh device < / p >
< div v-if = "mesh.status" class="mesh-device-panel-grid" >
< div class = "mesh-stat" >
< span class = "mesh-stat-label" > Firmware < / span >
< span class = "mesh-stat-value" > { { mesh . status . firmware _version ? ? '—' } } < / span >
< / div >
< div class = "mesh-stat" >
< span class = "mesh-stat-label" > Node ID < / span >
< span class = "mesh-stat-value" > { { mesh . status . self _node _id != null ? ` ! ${ mesh . status . self _node _id . toString ( 16 ) . padStart ( 8 , '0' ) } ` : '—' } } < / span >
< / div >
< div class = "mesh-stat" >
< span class = "mesh-stat-label" > Type < / span >
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>
2026-07-14 08:40:18 -04:00
< span class = "mesh-stat-value" > { { deviceType === 'unknown' ? '—' : deviceType } } < / span >
< / div >
< / div >
<!-- Radio settings -- >
< div class = "mt-5 pt-4 border-t border-white/10" >
< h4 class = "text-sm font-semibold text-white mb-3" > Radio Settings < / h4 >
< div class = "grid gap-3 sm:grid-cols-2" >
< div >
< label class = "block text-xs text-white/60 mb-1" > LoRa region / frequency plan < / label >
< select v-model = "form.region" class="w-full rounded-lg bg-white/[0.06] border border-white/10 text-white px-3 py-2 text-sm focus:outline-none focus:border-orange-400/60" >
< option value = "" > Keep the radio ' s current region < / option >
< option v-for = "r in LORA_REGIONS" :key="r.code" :value="r.code" > {{ r.label }} < / option >
< / select >
< p v-if = "selectedRegion && selectedRegion.dutyCyclePct < 100" class="text-[11px] text-amber-400/80 mt-1" >
{ { selectedRegion . code } } : { { selectedRegion . dutyCyclePct } } % duty - cycle limit , { { selectedRegion . band } } MHz , max { { selectedRegion . maxPowerDbm } } dBm
< / p >
< p v-if = "effectiveKind === 'meshtastic' || effectiveKind === 'auto'" class="text-[11px] text-white/40 mt-1" >
Applied to fresh ( region - unset ) Meshtastic radios ; a radio that already has a region keeps it .
< / p >
< p v -else -if = " effectiveKind = = = ' meshcore ' & & meshcorePlan " class = "text-[11px] text-sky-300/80 mt-1" >
2026-07-21 06:53:10 -04:00
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 .
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>
2026-07-14 08:40:18 -04:00
< / p >
< p v -else -if = " effectiveKind = = = ' meshcore ' " class = "text-[11px] text-sky-300/80 mt-1" >
2026-07-21 06:53:10 -04:00
Program the radio 's RF settings with the fields below — every radio on your mesh must match{{ selectedRegion ? ` (${selectedRegion.band} MHz band)` : ' ' } } .
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>
2026-07-14 08:40:18 -04:00
< / p >
< p v -else -if = " effectiveKind = = = ' reticulum ' " class = "text-[11px] text-sky-300/80 mt-1" >
RNode RF parameters are managed by the Reticulum daemon ' s interface config on this node .
< / p >
< / div >
< div >
< label class = "block text-xs text-white/60 mb-1" > Radio firmware < / label >
< select v-model = "form.deviceKind" class="w-full rounded-lg bg-white/[0.06] border border-white/10 text-white px-3 py-2 text-sm focus:outline-none focus:border-orange-400/60" >
< option value = "auto" > Auto - detect ( Meshcore → Meshtastic → RNode ) < / option >
< option value = "meshcore" > MeshCore < / option >
< option value = "meshtastic" > Meshtastic < / option >
< option value = "reticulum" > Reticulum / RNode < / option >
< / select >
< p class = "text-[11px] text-white/40 mt-1" >
Pin the flashed firmware so no other protocol ' s probe bytes touch the port .
< / p >
< / div >
< div v-if = "effectiveKind !== 'reticulum'" >
< label class = "block text-xs text-white/60 mb-1" > Channel < / label >
< input v-model = "form.channel" maxlength="11" class="w-full rounded-lg bg-white/[0.06] border border-white/10 text-white px-3 py-2 text-sm focus:outline-none focus:border-orange-400/60" / >
< / div >
< div >
< label class = "block text-xs text-white/60 mb-1" > Name on the mesh < / label >
< input v-model = "form.name" maxlength="24" placeholder="node name" class="w-full rounded-lg bg-white/[0.06] border border-white/10 text-white px-3 py-2 text-sm focus:outline-none focus:border-orange-400/60" / >
< / div >
< / div >
2026-07-21 06:53:10 -04:00
<!-- MeshCore LoRa PHY params — every radio on the local mesh must match
or it hears RF energy but decodes nothing . Empty = leave the radio ' s
flashed settings untouched . -- >
< div v-if = "effectiveKind === 'meshcore'" class="mt-4" >
< h5 class = "text-xs font-semibold text-white/80 mb-2" > MeshCore RF parameters < / h5 >
2026-07-21 08:36:59 -04:00
< div class = "mb-3" >
< label class = "block text-xs text-white/60 mb-1" > Frequency plan < / label >
< select :value = "rfPreset" class = "w-full rounded-lg bg-white/[0.06] border border-white/10 text-white px-3 py-2 text-sm focus:outline-none focus:border-orange-400/60" @ change = "selectRfPreset(($event.target as HTMLSelectElement).value)" >
< option value = "" > Keep the radio ' s current settings < / option >
< option v-for = "p in MESHCORE_RF_PRESETS" :key="p.id" :value="p.id" > {{ p.label }} < / option >
< option value = "custom" > Custom … < / option >
< / select >
< / div >
< div v-if = "rfPreset === 'custom'" class="grid gap-3 grid-cols-2 sm:grid-cols-4" >
2026-07-21 06:53:10 -04:00
< div >
< label class = "block text-xs text-white/60 mb-1" > Frequency ( MHz ) < / label >
< input v-model = "form.rfFreqMhz" inputmode="decimal" placeholder="869.618" class="w-full rounded-lg bg-white/[0.06] border border-white/10 text-white px-3 py-2 text-sm focus:outline-none focus:border-orange-400/60" / >
< / div >
< div >
< label class = "block text-xs text-white/60 mb-1" > Bandwidth ( kHz ) < / label >
< select v-model = "form.rfBwKhz" class="w-full rounded-lg bg-white/[0.06] border border-white/10 text-white px-3 py-2 text-sm focus:outline-none focus:border-orange-400/60" >
< option value = "" > — < / option >
< option v-for ="bw in ['62.5', '125', '250', '500']" :key="bw" :value ="bw" > {{ bw }} < / option >
< / select >
< / div >
< div >
< label class = "block text-xs text-white/60 mb-1" > Spreading factor < / label >
< select v-model = "form.rfSf" class="w-full rounded-lg bg-white/[0.06] border border-white/10 text-white px-3 py-2 text-sm focus:outline-none focus:border-orange-400/60" >
< option value = "" > — < / option >
< option v-for = "sf in [5, 6, 7, 8, 9, 10, 11, 12]" :key="sf" :value="String(sf)" > SF {{ sf }} < / option >
< / select >
< / div >
< div >
< label class = "block text-xs text-white/60 mb-1" > Coding rate < / label >
< select v-model = "form.rfCr" class="w-full rounded-lg bg-white/[0.06] border border-white/10 text-white px-3 py-2 text-sm focus:outline-none focus:border-orange-400/60" >
< option value = "" > — < / option >
< option v-for = "cr in [5, 6, 7, 8]" :key="cr" :value="String(cr)" > 4 / {{ cr }} < / option >
< / select >
< / div >
< / div >
< p class = "text-[11px] text-white/40 mt-1" >
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 .
< / p >
< / div >
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>
2026-07-14 08:40:18 -04:00
< label class = "flex items-center gap-2 mt-3 text-sm text-white/80 cursor-pointer" >
< input v-model = "form.broadcastIdentity" type="checkbox" class="h-4 w-4 accent-orange-500" / >
Periodically broadcast this node ' s identity on the mesh
< / label >
< p v-if = "effectiveKind === 'auto'" class="text-[11px] text-white/40 mt-3" >
Options adapt to the detected firmware : region + channel program Meshtastic radios ;
MeshCore and RNode own their RF parameters in firmware / daemon config ; name and identity apply to all .
< / p >
< div class = "flex items-center gap-3 mt-4" >
< button
class = "glass-button glass-button-warning px-4 py-2 rounded-lg text-sm font-medium disabled:opacity-50"
: disabled = "saving"
@ click = "saveSettings"
>
{ { saving ? 'Saving…' : 'Save Settings' } }
< / button >
< span v-if = "saveDone" class="text-xs text-green-400" > Saved — applies on next radio session < / span >
< span v-if = "saveError" class="text-xs text-red-400" > {{ saveError }} < / span >
2026-06-30 23:03:09 -04:00
< / div >
< / div >
< div class = "mesh-device-panel-actions" >
< button
class = "glass-button mesh-device-reboot-btn"
: disabled = "rebooting || !mesh.status?.device_connected"
@ click = "handleReboot"
>
< span v-if = "rebooting" class="mesh-spinner" aria-hidden="true" > < / span >
< template v-else > Reboot Radio < / template >
< / button >
< p class = "mesh-device-reboot-hint" > Use this if the device stops responding to sent messages or seems stuck . < / p >
< p v-if = "rebootError" class="mesh-device-reboot-error" > {{ rebootError }} < / p >
< / div >
< / div >
< / template >