diff --git a/core/model/src/main/kotlin/org/meshtastic/core/model/ChannelOption.kt b/core/model/src/main/kotlin/org/meshtastic/core/model/ChannelOption.kt index d7b9d3c0b..38bc6f5f0 100644 --- a/core/model/src/main/kotlin/org/meshtastic/core/model/ChannelOption.kt +++ b/core/model/src/main/kotlin/org/meshtastic/core/model/ChannelOption.kt @@ -78,10 +78,9 @@ val LoRaConfig.numChannels: Int if (bw <= 0f) return 1 // Return 1 if bandwidth is zero or negative // Calculate number of channels: spacing = gap between channels (0 for continuous spectrum) - // Match firmware: uint32_t numChannels = round((myRegion->freqEnd - myRegion->freqStart + myRegion->spacing) / - // channelSpacing); + // Match firmware: uint32_t numChannels = round((myRegion->freqEnd - myRegion->freqStart) / channelSpacing); val channelSpacing = regionInfo.spacing + bw - val num = Math.round((regionInfo.freqEnd - regionInfo.freqStart + regionInfo.spacing) / channelSpacing).toInt() + val num = Math.round((regionInfo.freqEnd - regionInfo.freqStart) / channelSpacing).toInt() // If the regional frequency range is smaller than the bandwidth, the firmware would // fall back to a default preset. In the app, we return 1 to avoid a crash. @@ -100,13 +99,14 @@ internal fun LoRaConfig.radioFreq(channelNum: Int): Float { return if (regionInfo != null) { val bw = bandwidth(regionInfo) val channelSpacing = regionInfo.spacing + bw - // Match firmware: float freq = myRegion->freqStart + (bw / 2000) + (channel_num * channelSpacing); + // Match firmware: float freq = myRegion->freqStart + myRegion->spacing + (bw / 2000) + (channel_num * + // channelSpacing); // Note: firmware channel_num is 0-indexed in the calculation, but the app uses 1-indexed for some reason? // Let's re-verify the firmware logic. // Firmware: channel_num = hash(channelName) % numChannels; - // freq = myRegion->freqStart + (bw / 2000) + (channel_num * channelSpacing); + // freq = myRegion->freqStart + myRegion->spacing + (bw / 2000) + (channel_num * channelSpacing); // The app's channelNum function returns 1..numChannels if channelNum is 0. - (regionInfo.freqStart + bw / 2) + (channelNum - 1) * channelSpacing + (regionInfo.freqStart + regionInfo.spacing + bw / 2) + (channelNum - 1) * channelSpacing } else { 0f } diff --git a/core/model/src/test/kotlin/org/meshtastic/core/model/ChannelOptionTest.kt b/core/model/src/test/kotlin/org/meshtastic/core/model/ChannelOptionTest.kt index 70197d8e2..cdd0b5ce5 100644 --- a/core/model/src/test/kotlin/org/meshtastic/core/model/ChannelOptionTest.kt +++ b/core/model/src/test/kotlin/org/meshtastic/core/model/ChannelOptionTest.kt @@ -1,5 +1,5 @@ /* - * Copyright (c) 2025 Meshtastic LLC + * Copyright (c) 2025-2026 Meshtastic LLC * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -14,13 +14,14 @@ * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ - package org.meshtastic.core.model import org.junit.Assert.assertEquals import org.junit.Assert.assertNotNull import org.junit.Test +import org.meshtastic.proto.ConfigKt.loRaConfig import org.meshtastic.proto.ConfigProtos.Config.LoRaConfig.ModemPreset +import org.meshtastic.proto.ConfigProtos.Config.LoRaConfig.RegionCode class ChannelOptionTest { @@ -78,4 +79,24 @@ class ChannelOptionTest { ChannelOption.entries.size, ) } + + @Test + fun `test radioFreq and numChannels for NARROW_868`() { + val loraConfig = loRaConfig { + region = RegionCode.NARROW_868 + usePreset = true + modemPreset = ModemPreset.NARROW_FAST + } + + // bw = 0.0625, spacing = 0.015, channelSpacing = 0.0775 + // Range = 869.65 - 869.4 = 0.25 + // numChannels = round(0.25 / 0.0775) = 3 + assertEquals(3, loraConfig.numChannels) + + // Slot 1: freqStart + spacing + bw/2 = 869.4 + 0.015 + 0.03125 = 869.44625 + assertEquals(869.44625f, loraConfig.radioFreq(1), 0.0001f) + + // Slot 3: 869.44625 + 2 * 0.0775 = 869.44625 + 0.155 = 869.60125 + assertEquals(869.60125f, loraConfig.radioFreq(3), 0.0001f) + } }