From 06aee1116916ae81ef40daad0b309ef98f97d075 Mon Sep 17 00:00:00 2001 From: James Rich <2199651+jamesarich@users.noreply.github.com> Date: Sat, 15 Aug 2026 18:55:04 +0000 Subject: [PATCH] feat(lora): honor a pinned-preset intent advertised for UNSET (#6711) Co-authored-by: Claude Opus 5 --- .../core/model/RegionPresetConstraint.kt | 6 +++- .../core/model/LoRaRegionPresetsTest.kt | 31 +++++++++++++++++++ .../radio/component/LoRaConfigItemList.kt | 6 +++- 3 files changed, 41 insertions(+), 2 deletions(-) diff --git a/core/model/src/commonMain/kotlin/org/meshtastic/core/model/RegionPresetConstraint.kt b/core/model/src/commonMain/kotlin/org/meshtastic/core/model/RegionPresetConstraint.kt index edfc15f389..b5b00023cc 100644 --- a/core/model/src/commonMain/kotlin/org/meshtastic/core/model/RegionPresetConstraint.kt +++ b/core/model/src/commonMain/kotlin/org/meshtastic/core/model/RegionPresetConstraint.kt @@ -82,6 +82,9 @@ fun LoRaRegionPresetMap?.repairPresetFor(region: RegionCode, current: ModemPrese * [current] is the [ModemPreset.LONG_FAST] placeholder a factory-flashed node reports (proto default), the region's * advertised default is adopted instead (the firmware map's when present, else the app's built-in default). Any other * preset at UNSET was set deliberately (e.g. a vendor build pinning USERPREFS_LORACONFIG_MODEM_PRESET) and is kept. + * + * A build that pins a preset can also advertise it as an UNSET map entry (firmware #11507), stating outright that the + * preset is deliberate; that keeps even a pinned LONG_FAST, which the placeholder heuristic alone cannot distinguish. */ fun LoRaRegionPresetMap?.presetForRegionChange( previousRegion: RegionCode, @@ -89,7 +92,8 @@ fun LoRaRegionPresetMap?.presetForRegionChange( current: ModemPreset, ): ModemPreset { val repaired = repairPresetFor(newRegion, current) - val freshSetup = previousRegion == RegionCode.UNSET && current == ModemPreset.LONG_FAST + val pinned = constraintFor(RegionCode.UNSET) != null + val freshSetup = previousRegion == RegionCode.UNSET && !pinned && current == ModemPreset.LONG_FAST return if (freshSetup) { // Re-repair the adopted default: a malformed map's advertised default may not be in its own legal set. val preferred = constraintFor(newRegion)?.defaultPreset ?: defaultPresetFor(newRegion) ?: repaired diff --git a/core/model/src/commonTest/kotlin/org/meshtastic/core/model/LoRaRegionPresetsTest.kt b/core/model/src/commonTest/kotlin/org/meshtastic/core/model/LoRaRegionPresetsTest.kt index 87b8caf51c..f73d7d8d76 100644 --- a/core/model/src/commonTest/kotlin/org/meshtastic/core/model/LoRaRegionPresetsTest.kt +++ b/core/model/src/commonTest/kotlin/org/meshtastic/core/model/LoRaRegionPresetsTest.kt @@ -213,4 +213,35 @@ class LoRaRegionPresetsTest { oddMap.presetForRegionChange(RegionCode.UNSET, RegionCode.US, ModemPreset.LONG_FAST), ) } + + // A pinned build advertises its preset as UNSET's map entry (firmware #11507): the same fixture plus a + // single-preset UNSET group, here stating a deliberate LONG_FAST pin. + private val pinnedMap = + map.copy( + groups = + map.groups + + LoRaPresetGroup( + presets = listOf(ModemPreset.LONG_FAST), + default_preset = ModemPreset.LONG_FAST, + licensed_only = false, + ), + region_groups = map.region_groups + LoRaRegionPresets(region = RegionCode.UNSET, group_index = 3), + ) + + @Test + fun `an UNSET map entry marks even a LongFast pin as deliberate at fresh setup`() { + // Without the entry this placeholder would adopt EU_N_868's MEDIUM_FAST default (asserted above). + assertEquals( + ModemPreset.LONG_FAST, + pinnedMap.presetForRegionChange(RegionCode.UNSET, RegionCode.EU_N_868, ModemPreset.LONG_FAST), + ) + } + + @Test + fun `a pin stated via UNSET entry is still repaired when illegal in the region`() { + assertEquals( + ModemPreset.TINY_FAST, + pinnedMap.presetForRegionChange(RegionCode.UNSET, RegionCode.UA_433, ModemPreset.LONG_FAST), + ) + } } diff --git a/feature/settings/src/commonMain/kotlin/org/meshtastic/feature/settings/radio/component/LoRaConfigItemList.kt b/feature/settings/src/commonMain/kotlin/org/meshtastic/feature/settings/radio/component/LoRaConfigItemList.kt index 5d90ab0a28..23b7c490d2 100644 --- a/feature/settings/src/commonMain/kotlin/org/meshtastic/feature/settings/radio/component/LoRaConfigItemList.kt +++ b/feature/settings/src/commonMain/kotlin/org/meshtastic/feature/settings/radio/component/LoRaConfigItemList.kt @@ -169,7 +169,11 @@ fun LoRaConfigScreen(viewModel: RadioConfigViewModel, onBack: () -> Unit) { val regionPresetMap = if (capabilities.supportsLoraRegionPresetMap) state.loraRegionPresetMap else null val presetConstraint = remember(regionPresetMap, formState.value.region) { - regionPresetMap.constraintFor(formState.value.region) + // UNSET's map entry states pin intent (firmware #11507), not a constraint: never let it + // narrow the picker while the region is still unset. + formState.value.region + .takeIf { it != RegionCode.UNSET } + ?.let { regionPresetMap.constraintFor(it) } } val presetsGated = presetConstraint?.isGated(state.localIsLicensed) == true DropDownPreference(