mirror of
https://github.com/meshtastic/Meshtastic-Android.git
synced 2026-04-20 22:23:37 +00:00
feat: calculate default values for LoRa channel_num and frequency (#664)
This commit is contained in:
parent
f27ae8feba
commit
527d94d32a
7 changed files with 141 additions and 29 deletions
|
|
@ -1,6 +1,7 @@
|
|||
package com.geeksville.mesh
|
||||
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||
import com.geeksville.mesh.model.Channel
|
||||
import com.geeksville.mesh.model.ChannelSet
|
||||
import org.junit.Assert
|
||||
import org.junit.Test
|
||||
|
|
@ -15,4 +16,18 @@ class ChannelTest {
|
|||
Assert.assertTrue(ch.getChannelUrl().toString().startsWith(ChannelSet.prefix))
|
||||
Assert.assertEquals(ChannelSet(ch.getChannelUrl()), ch)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun channelNumGood() {
|
||||
val ch = Channel.default
|
||||
|
||||
Assert.assertEquals(20, ch.channelNum)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun radioFreqGood() {
|
||||
val ch = Channel.default
|
||||
|
||||
Assert.assertEquals(906.875f, ch.radioFreq)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -43,9 +43,7 @@ data class Channel(
|
|||
val name: String
|
||||
get() = settings.name.ifEmpty {
|
||||
// We have a new style 'empty' channel name. Use the same logic from the device to convert that to a human readable name
|
||||
if (loraConfig.bandwidth != 0)
|
||||
"Unset"
|
||||
else when (loraConfig.modemPreset) {
|
||||
if (loraConfig.usePreset) when (loraConfig.modemPreset) {
|
||||
ModemPreset.SHORT_FAST -> "ShortFast"
|
||||
ModemPreset.SHORT_SLOW -> "ShortSlow"
|
||||
ModemPreset.MEDIUM_FAST -> "MediumFast"
|
||||
|
|
@ -55,7 +53,7 @@ data class Channel(
|
|||
ModemPreset.LONG_MODERATE -> "LongMod"
|
||||
ModemPreset.VERY_LONG_SLOW -> "VLongSlow"
|
||||
else -> "Invalid"
|
||||
}
|
||||
} else "Custom"
|
||||
}
|
||||
|
||||
val psk: ByteString
|
||||
|
|
@ -90,6 +88,26 @@ data class Channel(
|
|||
return "#${name}-${suffix}"
|
||||
}
|
||||
|
||||
/**
|
||||
* hash a string into an integer using the djb2 algorithm by Dan Bernstein
|
||||
* http://www.cse.yorku.ca/~oz/hash.html
|
||||
*/
|
||||
val hash: UInt // using UInt instead of Long to match RadioInterface.cpp results
|
||||
get() {
|
||||
var hash: UInt = 5381u
|
||||
for (c in name) {
|
||||
hash += (hash shl 5) + c.code.toUInt()
|
||||
print("$c ${c.code} $hash ")
|
||||
}
|
||||
return hash
|
||||
}
|
||||
|
||||
val channelNum: Int
|
||||
get() = if (loraConfig.channelNum != 0) loraConfig.channelNum
|
||||
else (hash % RegionInfo.numChannels(loraConfig).toUInt()).toInt() + 1
|
||||
|
||||
val radioFreq: Float get() = RegionInfo.radioFreq(loraConfig, channelNum)
|
||||
|
||||
override fun equals(other: Any?): Boolean = (other is Channel)
|
||||
&& psk.toByteArray() contentEquals other.psk.toByteArray()
|
||||
&& name == other.name
|
||||
|
|
|
|||
|
|
@ -1,27 +1,85 @@
|
|||
package com.geeksville.mesh.model
|
||||
|
||||
import com.geeksville.mesh.ConfigProtos.Config.LoRaConfig
|
||||
import com.geeksville.mesh.ConfigProtos.Config.LoRaConfig.ModemPreset
|
||||
import com.geeksville.mesh.ConfigProtos.Config.LoRaConfig.RegionCode
|
||||
import com.geeksville.mesh.R
|
||||
|
||||
fun LoRaConfig.bandwidth() = if (usePreset) {
|
||||
val wideLora = region == RegionCode.LORA_24
|
||||
ChannelOption.bandwidth(modemPreset) * if (wideLora) 3.25f else 1f
|
||||
} else when (bandwidth) {
|
||||
31 -> .03125f
|
||||
62 -> .0625f
|
||||
200 -> .203125f
|
||||
400 -> .40625f
|
||||
800 -> .8125f
|
||||
1600 -> 1.6250f
|
||||
else -> bandwidth / 1000f
|
||||
}
|
||||
|
||||
enum class RegionInfo(
|
||||
val regionCode: RegionCode,
|
||||
val freqStart: Float,
|
||||
val freqEnd: Float,
|
||||
) {
|
||||
US(RegionCode.US, 902.0f, 928.0f),
|
||||
EU_433(RegionCode.EU_433, 433.0f, 434.0f),
|
||||
EU_868(RegionCode.EU_868, 869.4f, 869.65f),
|
||||
CN(RegionCode.CN, 470.0f, 510.0f),
|
||||
JP(RegionCode.JP, 920.8f, 927.8f),
|
||||
ANZ(RegionCode.ANZ, 915.0f, 928.0f),
|
||||
RU(RegionCode.RU, 868.7f, 869.2f),
|
||||
KR(RegionCode.KR, 920.0f, 923.0f),
|
||||
TW(RegionCode.TW, 920.0f, 925.0f),
|
||||
IN(RegionCode.IN, 865.0f, 867.0f),
|
||||
NZ_865(RegionCode.NZ_865, 864.0f, 868.0f),
|
||||
TH(RegionCode.TH, 920.0f, 925.0f),
|
||||
UA_433(RegionCode.UA_433, 433.0f, 434.7f),
|
||||
UA_868(RegionCode.UA_868, 868.0f, 868.6f),
|
||||
LORA_24(RegionCode.LORA_24, 2400.0f, 2483.5f),
|
||||
UNSET(RegionCode.UNSET, 902.0f, 928.0f);
|
||||
|
||||
companion object {
|
||||
fun numChannels(loraConfig: LoRaConfig): Int {
|
||||
for (option in values()) {
|
||||
if (option.regionCode == loraConfig.region)
|
||||
return ((option.freqEnd - option.freqStart) / loraConfig.bandwidth()).toInt()
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
fun radioFreq(loraConfig: LoRaConfig, channelNum: Int): Float = with(loraConfig) {
|
||||
if (overrideFrequency != 0f) return overrideFrequency + frequencyOffset
|
||||
for (option in values()) {
|
||||
if (option.regionCode == region)
|
||||
return (option.freqStart + bandwidth() / 2) + (channelNum - 1) * bandwidth()
|
||||
}
|
||||
return 0f
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
enum class ChannelOption(
|
||||
val modemPreset: ModemPreset,
|
||||
val configRes: Int,
|
||||
val bandwidth: Float,
|
||||
) {
|
||||
SHORT_FAST(ModemPreset.SHORT_FAST, R.string.modem_config_short),
|
||||
SHORT_SLOW(ModemPreset.SHORT_SLOW, R.string.modem_config_slow_short),
|
||||
MEDIUM_FAST(ModemPreset.MEDIUM_FAST, R.string.modem_config_medium),
|
||||
MEDIUM_SLOW(ModemPreset.MEDIUM_SLOW, R.string.modem_config_slow_medium),
|
||||
LONG_FAST(ModemPreset.LONG_FAST, R.string.modem_config_long),
|
||||
LONG_MODERATE(ModemPreset.LONG_MODERATE, R.string.modem_config_mod_long),
|
||||
LONG_SLOW(ModemPreset.LONG_SLOW, R.string.modem_config_slow_long),
|
||||
VERY_LONG_SLOW(ModemPreset.VERY_LONG_SLOW, R.string.modem_config_very_long);
|
||||
LONG_SLOW(ModemPreset.LONG_SLOW, R.string.modem_config_slow_long, .125f),
|
||||
VERY_LONG_SLOW(ModemPreset.VERY_LONG_SLOW, R.string.modem_config_very_long, .0625f),
|
||||
MEDIUM_SLOW(ModemPreset.MEDIUM_SLOW, R.string.modem_config_slow_medium, .250f),
|
||||
MEDIUM_FAST(ModemPreset.MEDIUM_FAST, R.string.modem_config_medium, .250f),
|
||||
SHORT_SLOW(ModemPreset.SHORT_SLOW, R.string.modem_config_slow_short, .250f),
|
||||
SHORT_FAST(ModemPreset.SHORT_FAST, R.string.modem_config_short, .250f),
|
||||
LONG_MODERATE(ModemPreset.LONG_MODERATE, R.string.modem_config_mod_long, .125f),
|
||||
LONG_FAST(ModemPreset.LONG_FAST, R.string.modem_config_long, .250f);
|
||||
|
||||
companion object {
|
||||
fun fromConfig(modemPreset: ModemPreset?): ChannelOption? {
|
||||
fun bandwidth(modemPreset: ModemPreset?): Float {
|
||||
for (option in values()) {
|
||||
if (option.modemPreset == modemPreset) return option
|
||||
if (option.modemPreset == modemPreset) return option.bandwidth
|
||||
}
|
||||
return null
|
||||
return 0f
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -258,6 +258,8 @@ fun RadioConfigNavHost(node: NodeInfo, viewModel: UIViewModel = viewModel()) {
|
|||
viewModel.clearPacketResponse()
|
||||
val parsed = AdminProtos.AdminMessage.parseFrom(data.payload)
|
||||
debug("packet for destNum ${destNum.toUInt()} received ${parsed.payloadVariantCase} from $from")
|
||||
// check destination: lora config or channel editor
|
||||
val goChannels = (packetResponseState as PacketResponseState.Loading).total > 2
|
||||
when (parsed.payloadVariantCase) {
|
||||
AdminProtos.AdminMessage.PayloadVariantCase.GET_CHANNEL_RESPONSE -> {
|
||||
val response = parsed.getChannelResponse
|
||||
|
|
@ -265,7 +267,7 @@ fun RadioConfigNavHost(node: NodeInfo, viewModel: UIViewModel = viewModel()) {
|
|||
// Stop once we get to the first disabled entry
|
||||
if (response.role != ChannelProtos.Channel.Role.DISABLED) {
|
||||
channelList.add(response.index, response.settings)
|
||||
if (response.index + 1 < maxChannels) {
|
||||
if (response.index + 1 < maxChannels && goChannels) {
|
||||
// Not done yet, request next channel
|
||||
viewModel.getChannel(destNum, response.index + 1)
|
||||
} else {
|
||||
|
|
@ -285,8 +287,6 @@ fun RadioConfigNavHost(node: NodeInfo, viewModel: UIViewModel = viewModel()) {
|
|||
}
|
||||
|
||||
AdminProtos.AdminMessage.PayloadVariantCase.GET_CONFIG_RESPONSE -> {
|
||||
// check destination: lora config or channel editor
|
||||
val goChannels = (packetResponseState as PacketResponseState.Loading).total > 1
|
||||
packetResponseState = PacketResponseState.Empty
|
||||
val response = parsed.getConfigResponse
|
||||
radioConfig = response
|
||||
|
|
@ -370,6 +370,11 @@ fun RadioConfigNavHost(node: NodeInfo, viewModel: UIViewModel = viewModel()) {
|
|||
viewModel.requestNodedbReset(destNum)
|
||||
}
|
||||
|
||||
ConfigType.LORA_CONFIG -> {
|
||||
(packetResponseState as PacketResponseState.Loading).total = 2
|
||||
channelList.clear()
|
||||
viewModel.getChannel(destNum, 0)
|
||||
}
|
||||
is ConfigType -> {
|
||||
viewModel.getConfig(destNum, configType.number)
|
||||
}
|
||||
|
|
@ -491,6 +496,7 @@ fun RadioConfigNavHost(node: NodeInfo, viewModel: UIViewModel = viewModel()) {
|
|||
composable("lora") {
|
||||
LoRaConfigItemList(
|
||||
loraConfig = radioConfig.lora,
|
||||
primarySettings = channelList.getOrNull(0) ?: return@composable,
|
||||
enabled = connected,
|
||||
focusManager = focusManager,
|
||||
onSaveClicked = { loraInput ->
|
||||
|
|
|
|||
|
|
@ -34,6 +34,7 @@ fun EditTextPreference(
|
|||
keyboardActions: KeyboardActions,
|
||||
onValueChanged: (Int) -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
onFocusChanged: (FocusState) -> Unit = {},
|
||||
trailingIcon: (@Composable () -> Unit)? = null,
|
||||
) {
|
||||
var valueState by remember(value) { mutableStateOf(value.toUInt().toString()) }
|
||||
|
|
@ -54,7 +55,7 @@ fun EditTextPreference(
|
|||
onValueChanged(int)
|
||||
}
|
||||
},
|
||||
onFocusChanged = {},
|
||||
onFocusChanged = onFocusChanged,
|
||||
modifier = modifier,
|
||||
trailingIcon = trailingIcon
|
||||
)
|
||||
|
|
@ -68,7 +69,8 @@ fun EditTextPreference(
|
|||
keyboardActions: KeyboardActions,
|
||||
onValueChanged: (Float) -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
onFocusChanged: (FocusState) -> Unit = {},
|
||||
) {
|
||||
var valueState by remember(value) { mutableStateOf(value.toString()) }
|
||||
|
||||
EditTextPreference(
|
||||
|
|
@ -87,7 +89,7 @@ fun EditTextPreference(
|
|||
onValueChanged(float)
|
||||
}
|
||||
},
|
||||
onFocusChanged = {},
|
||||
onFocusChanged = onFocusChanged,
|
||||
modifier = modifier
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -13,8 +13,11 @@ import androidx.compose.ui.Modifier
|
|||
import androidx.compose.ui.focus.FocusManager
|
||||
import androidx.compose.ui.platform.LocalFocusManager
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import com.geeksville.mesh.ChannelProtos.ChannelSettings
|
||||
import com.geeksville.mesh.ConfigProtos.Config.LoRaConfig
|
||||
import com.geeksville.mesh.copy
|
||||
import com.geeksville.mesh.model.Channel
|
||||
import com.geeksville.mesh.model.RegionInfo
|
||||
import com.geeksville.mesh.ui.components.DropDownPreference
|
||||
import com.geeksville.mesh.ui.components.EditListPreference
|
||||
import com.geeksville.mesh.ui.components.EditTextPreference
|
||||
|
|
@ -25,11 +28,13 @@ import com.geeksville.mesh.ui.components.SwitchPreference
|
|||
@Composable
|
||||
fun LoRaConfigItemList(
|
||||
loraConfig: LoRaConfig,
|
||||
primarySettings: ChannelSettings,
|
||||
enabled: Boolean,
|
||||
focusManager: FocusManager,
|
||||
onSaveClicked: (LoRaConfig) -> Unit,
|
||||
) {
|
||||
var loraInput by remember(loraConfig) { mutableStateOf(loraConfig) }
|
||||
val primaryChannel = Channel(primarySettings, loraInput)
|
||||
|
||||
LazyColumn(
|
||||
modifier = Modifier.fillMaxSize()
|
||||
|
|
@ -125,11 +130,16 @@ fun LoRaConfigItemList(
|
|||
}
|
||||
|
||||
item {
|
||||
var isFocused by remember { mutableStateOf(false) }
|
||||
EditTextPreference(title = "Channel number",
|
||||
value = loraInput.channelNum,
|
||||
value = if (isFocused || loraInput.channelNum != 0) loraInput.channelNum else primaryChannel.channelNum,
|
||||
enabled = enabled,
|
||||
keyboardActions = KeyboardActions(onDone = { focusManager.clearFocus() }),
|
||||
onValueChanged = { loraInput = loraInput.copy { channelNum = it } })
|
||||
onFocusChanged = { isFocused = it.isFocused },
|
||||
onValueChanged = {
|
||||
if (it <= RegionInfo.numChannels(loraInput)) // max numChannels
|
||||
loraInput = loraInput.copy { channelNum = it }
|
||||
})
|
||||
}
|
||||
|
||||
item {
|
||||
|
|
@ -163,10 +173,12 @@ fun LoRaConfigItemList(
|
|||
item { Divider() }
|
||||
|
||||
item {
|
||||
var isFocused by remember { mutableStateOf(false) }
|
||||
EditTextPreference(title = "Override frequency (MHz)",
|
||||
value = loraInput.overrideFrequency,
|
||||
value = if (isFocused || loraInput.overrideFrequency != 0f) loraInput.overrideFrequency else primaryChannel.radioFreq,
|
||||
enabled = enabled,
|
||||
keyboardActions = KeyboardActions(onDone = { focusManager.clearFocus() }),
|
||||
onFocusChanged = { isFocused = it.isFocused },
|
||||
onValueChanged = { loraInput = loraInput.copy { overrideFrequency = it } })
|
||||
}
|
||||
|
||||
|
|
@ -185,9 +197,10 @@ fun LoRaConfigItemList(
|
|||
|
||||
@Preview(showBackground = true)
|
||||
@Composable
|
||||
fun LoRaConfigPreview(){
|
||||
fun LoRaConfigPreview() {
|
||||
LoRaConfigItemList(
|
||||
loraConfig = LoRaConfig.getDefaultInstance(),
|
||||
loraConfig = Channel.default.loraConfig,
|
||||
primarySettings = Channel.default.settings,
|
||||
enabled = true,
|
||||
focusManager = LocalFocusManager.current,
|
||||
onSaveClicked = { },
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ class ChannelSetTest {
|
|||
/** make sure we match the python and device code behavior */
|
||||
@Test
|
||||
fun matchPython() {
|
||||
val url = Uri.parse("https://meshtastic.org/e/#CgMSAQESAA")
|
||||
val url = Uri.parse("https://meshtastic.org/e/#CgMSAQESBggBQANIAQ")
|
||||
val cs = ChannelSet(url)
|
||||
Assert.assertEquals("LongFast", cs.primaryChannel!!.name)
|
||||
Assert.assertEquals("#LongFast-I", cs.primaryChannel!!.humanName)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue