mirror of
https://github.com/meshtastic/Meshtastic-Android.git
synced 2026-09-20 02:37:21 -04:00
fix(model): align the US first-setup preset with the device screen (#7244)
This commit is contained in:
1 parent
5f242f6505
commit
1b1d8845b0
5 files changed
+119
-16
No files matched your search
@@ -115,6 +115,9 @@ internal fun LoRaConfig.radioFreq(channelNum: Int): Float {
|
||||
*/
|
||||
private val FIRMWARE_2_8 = DeviceVersion("2.8.0")
|
||||
|
||||
/** The firmware release that introduced the LONG_TURBO preset (firmware#8985). */
|
||||
private val FIRMWARE_2_7_17 = DeviceVersion("2.7.17")
|
||||
|
||||
/**
|
||||
* Regulatory regions for radio usage
|
||||
*
|
||||
@@ -470,7 +473,7 @@ enum class ChannelOption(
|
||||
|
||||
// Historical parameters for firmware predating the removal of VERY_LONG_SLOW.
|
||||
VERY_LONG_SLOW(ModemPreset.VERY_LONG_SLOW, 0.0625f, spreadingFactor = 12),
|
||||
LONG_TURBO(ModemPreset.LONG_TURBO, 0.500f, spreadingFactor = 11),
|
||||
LONG_TURBO(ModemPreset.LONG_TURBO, 0.500f, spreadingFactor = 11, minFirmware = FIRMWARE_2_7_17),
|
||||
LONG_FAST(ModemPreset.LONG_FAST, 0.250f, spreadingFactor = 11),
|
||||
LONG_MODERATE(ModemPreset.LONG_MODERATE, 0.125f, spreadingFactor = 11),
|
||||
LONG_SLOW(ModemPreset.LONG_SLOW, 0.125f, spreadingFactor = 12),
|
||||
|
||||
+21
-8
@@ -79,9 +79,10 @@ fun LoRaRegionPresetMap?.repairPresetFor(region: RegionCode, current: ModemPrese
|
||||
* [current] selected.
|
||||
*
|
||||
* Keeps [current] (legality-repaired via [repairPresetFor]) except at fresh setup: when [previousRegion] is UNSET and
|
||||
* [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.
|
||||
* [current] is the [ModemPreset.LONG_FAST] placeholder a factory-flashed node reports (proto default), a region default
|
||||
* is adopted instead: the app's [firstSetupDefaultFor] where the firmware map confirms it, else the map's own 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.
|
||||
@@ -95,8 +96,8 @@ fun LoRaRegionPresetMap?.presetForRegionChange(
|
||||
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
|
||||
// Re-repaired: a malformed map's advertised default may not be in its own legal set.
|
||||
val preferred = firstSetupDefaultFor(newRegion) ?: constraintFor(newRegion)?.defaultPreset ?: repaired
|
||||
repairPresetFor(newRegion, preferred)
|
||||
} else {
|
||||
repaired
|
||||
@@ -104,9 +105,9 @@ fun LoRaRegionPresetMap?.presetForRegionChange(
|
||||
}
|
||||
|
||||
/**
|
||||
* The app's built-in default modem presets for regions whose default differs from the global [ChannelOption.DEFAULT].
|
||||
* Mirrors the per-region defaults newer firmware advertises in [LoRaRegionPresetMap], so the default channel and a
|
||||
* fresh setup over old firmware (which sends no map) land on the same preset a new node would.
|
||||
* The app's first-setup default modem presets, for regions whose default differs from the global
|
||||
* [ChannelOption.DEFAULT]. Firmware's [LoRaRegionPresetMap] advertises only its region-table default, which for US is
|
||||
* not what its own on-device chooser installs on a first-ever setup, so these override the advertised default.
|
||||
*/
|
||||
private val REGION_DEFAULT_PRESETS = mapOf(RegionCode.US to ModemPreset.LONG_TURBO)
|
||||
|
||||
@@ -115,3 +116,15 @@ private val REGION_DEFAULT_PRESETS = mapOf(RegionCode.US to ModemPreset.LONG_TUR
|
||||
* should fall back to [ChannelOption.DEFAULT] / the current preset.
|
||||
*/
|
||||
fun defaultPresetFor(region: RegionCode): ModemPreset? = REGION_DEFAULT_PRESETS[region]
|
||||
|
||||
/**
|
||||
* The preset a first-ever setup in [region] should install, or `null` to leave the choice alone.
|
||||
*
|
||||
* Gated on the firmware map: an unconstrained node is running firmware whose own region chooser installs the
|
||||
* region-table default, and overriding it there would make the same node set up from the phone and from its screen
|
||||
* disagree. Only a preset the map lists as legal is adopted.
|
||||
*/
|
||||
fun LoRaRegionPresetMap?.firstSetupDefaultFor(region: RegionCode): ModemPreset? {
|
||||
val constraint = constraintFor(region) ?: return null
|
||||
return defaultPresetFor(region)?.takeIf { it in constraint.presets }
|
||||
}
|
||||
@@ -138,7 +138,8 @@ class CapabilitiesTest {
|
||||
ChannelOption.NARROW_FAST,
|
||||
ChannelOption.NARROW_SLOW,
|
||||
)
|
||||
assertEquals(gated, ChannelOption.entries.filter { it.minFirmware != null }.toSet())
|
||||
// LongTurbo's radio support lands earlier, in v2.7.17 (firmware#8985), so it gates on its own version.
|
||||
assertEquals(gated + ChannelOption.LONG_TURBO, ChannelOption.entries.filter { it.minFirmware != null }.toSet())
|
||||
|
||||
val old = caps("2.7.26")
|
||||
val new = caps("2.8.0")
|
||||
@@ -146,6 +147,8 @@ class CapabilitiesTest {
|
||||
assertFalse(old.supportsPreset(preset), "${preset.name} should be hidden on 2.7 firmware")
|
||||
assertTrue(new.supportsPreset(preset), "${preset.name} should be shown on 2.8 firmware")
|
||||
}
|
||||
assertFalse(caps("2.7.16").supportsPreset(ChannelOption.LONG_TURBO), "LongTurbo predates 2.7.17")
|
||||
assertTrue(old.supportsPreset(ChannelOption.LONG_TURBO), "LongTurbo ships from 2.7.17")
|
||||
// Established presets are never gated, even with unknown firmware.
|
||||
assertTrue(caps(null).supportsPreset(ChannelOption.LONG_FAST))
|
||||
// Unknown firmware hides gated presets; debug forceEnableAll shows them.
|
||||
|
||||
+84
-2
@@ -255,14 +255,26 @@ class LoRaRegionPresetsTest {
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `fresh setup placeholder adopts the built-in default when unconstrained`() {
|
||||
fun `fresh setup keeps LongFast when firmware advertises no map`() {
|
||||
// Pre-2.8 firmware installs its own region-table default on its screen; the app must not diverge from it.
|
||||
val nullMap: LoRaRegionPresetMap? = null
|
||||
assertEquals(
|
||||
ModemPreset.LONG_TURBO,
|
||||
ModemPreset.LONG_FAST,
|
||||
nullMap.presetForRegionChange(RegionCode.UNSET, RegionCode.US, ModemPreset.LONG_FAST),
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `no first-setup default is offered without a map`() {
|
||||
val nullMap: LoRaRegionPresetMap? = null
|
||||
assertNull(nullMap.firstSetupDefaultFor(RegionCode.US))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `no first-setup default when the map omits it from the region`() {
|
||||
assertNull(map.firstSetupDefaultFor(RegionCode.US))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `fresh setup pin that is illegal in the region is still repaired`() {
|
||||
assertEquals(
|
||||
@@ -320,4 +332,74 @@ class LoRaRegionPresetsTest {
|
||||
pinnedMap.presetForRegionChange(RegionCode.UNSET, RegionCode.UA_433, ModemPreset.LONG_FAST),
|
||||
)
|
||||
}
|
||||
|
||||
// Mirrors what firmware 2.8 advertises for US: its whole legal preset list, defaulting to the region-table
|
||||
// LongFast rather than the LongTurbo its own first-setup chooser installs.
|
||||
private val firmware28Map =
|
||||
LoRaRegionPresetMap.Builder()
|
||||
.also { wb ->
|
||||
wb.groups =
|
||||
listOf(
|
||||
LoRaPresetGroup.Builder()
|
||||
.also { g ->
|
||||
g.presets =
|
||||
listOf(
|
||||
ModemPreset.LONG_FAST,
|
||||
ModemPreset.LONG_SLOW,
|
||||
ModemPreset.MEDIUM_SLOW,
|
||||
ModemPreset.MEDIUM_FAST,
|
||||
ModemPreset.SHORT_SLOW,
|
||||
ModemPreset.SHORT_FAST,
|
||||
ModemPreset.LONG_MODERATE,
|
||||
ModemPreset.SHORT_TURBO,
|
||||
ModemPreset.LONG_TURBO,
|
||||
ModemPreset.MEDIUM_TURBO,
|
||||
)
|
||||
g.default_preset = ModemPreset.LONG_FAST
|
||||
g.licensed_only = false
|
||||
}
|
||||
.build(),
|
||||
)
|
||||
wb.region_groups =
|
||||
listOf(
|
||||
LoRaRegionPresets.Builder()
|
||||
.also { r ->
|
||||
r.region = RegionCode.US
|
||||
r.group_index = 0
|
||||
}
|
||||
.build(),
|
||||
)
|
||||
}
|
||||
.build()
|
||||
|
||||
@Test
|
||||
fun `first-setup default is offered when the map lists it as legal`() {
|
||||
assertEquals(ModemPreset.LONG_TURBO, firmware28Map.firstSetupDefaultFor(RegionCode.US))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `fresh US setup adopts LongTurbo over the advertised LongFast default`() {
|
||||
assertEquals(
|
||||
ModemPreset.LONG_TURBO,
|
||||
firmware28Map.presetForRegionChange(RegionCode.UNSET, RegionCode.US, ModemPreset.LONG_FAST),
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `a later US change on the advertised map keeps the running preset`() {
|
||||
// Default adoption stays a fresh-setup rule: an already-configured node is never moved off its preset.
|
||||
assertEquals(
|
||||
ModemPreset.LONG_FAST,
|
||||
firmware28Map.presetForRegionChange(RegionCode.EU_868, RegionCode.US, ModemPreset.LONG_FAST),
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `fresh setup falls back to the advertised default when the built-in is illegal there`() {
|
||||
// The US group here omits LONG_TURBO, so the built-in cannot be adopted.
|
||||
assertEquals(
|
||||
ModemPreset.LONG_FAST,
|
||||
map.presetForRegionChange(RegionCode.UNSET, RegionCode.US, ModemPreset.LONG_FAST),
|
||||
)
|
||||
}
|
||||
}
|
||||
+6
-4
@@ -63,7 +63,7 @@ import org.jetbrains.compose.resources.stringResource
|
||||
import org.koin.compose.viewmodel.koinViewModel
|
||||
import org.meshtastic.core.model.Channel
|
||||
import org.meshtastic.core.model.ConnectionState
|
||||
import org.meshtastic.core.model.defaultPresetFor
|
||||
import org.meshtastic.core.model.firstSetupDefaultFor
|
||||
import org.meshtastic.core.navigation.Route
|
||||
import org.meshtastic.core.navigation.SettingsRoute
|
||||
import org.meshtastic.core.resources.Res
|
||||
@@ -217,9 +217,11 @@ fun ChannelScreen(
|
||||
messageRes = Res.string.are_you_sure_change_default,
|
||||
onConfirm = {
|
||||
Logger.d { "Switching back to default channel" }
|
||||
// The default channel takes the region's preferred preset (e.g. US -> LongTurbo) so its derived name,
|
||||
// hash, and frequency match what a freshly-set-up node in that region would use.
|
||||
val preset = defaultPresetFor(viewModel.region) ?: Channel.default.loraConfig.modem_preset
|
||||
// The default channel takes the region's first-setup preset (e.g. US -> LongTurbo) so its derived
|
||||
// name, hash, and frequency match what a freshly-set-up node in that region would use.
|
||||
val preset =
|
||||
radioConfigState.loraRegionPresetMap.firstSetupDefaultFor(viewModel.region)
|
||||
?: Channel.default.loraConfig.modem_preset
|
||||
val lora =
|
||||
Channel.default.loraConfig
|
||||
.newBuilder()
|
||||
|
||||
Reference in new issue
Block a user