Modularize some model classes (#3153)

pull/3154/head^2
Phil Oliver 2025-09-19 15:53:43 -04:00 zatwierdzone przez GitHub
rodzic ab2fff219d
commit 8fb41aab74
Nie znaleziono w bazie danych klucza dla tego podpisu
ID klucza GPG: B5690EEEBB952194
27 zmienionych plików z 215 dodań i 190 usunięć

Wyświetl plik

@ -18,14 +18,14 @@
package com.geeksville.mesh
import androidx.test.ext.junit.runners.AndroidJUnit4
import com.geeksville.mesh.model.Channel
import com.geeksville.mesh.model.URL_PREFIX
import com.geeksville.mesh.model.getChannelUrl
import com.geeksville.mesh.model.numChannels
import com.geeksville.mesh.model.toChannelSet
import org.junit.Assert
import org.junit.Test
import org.junit.runner.RunWith
import org.meshtastic.core.model.Channel
import org.meshtastic.core.model.numChannels
@RunWith(AndroidJUnit4::class)
class ChannelTest {

Wyświetl plik

@ -29,21 +29,19 @@ import com.geeksville.mesh.R
import com.geeksville.mesh.channelSet
import com.geeksville.mesh.channelSettings
import com.geeksville.mesh.copy
import com.geeksville.mesh.model.Channel
import com.geeksville.mesh.ui.common.components.ScannedQrCodeDialog
import org.junit.Assert
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
import org.meshtastic.core.model.Channel
@RunWith(AndroidJUnit4::class)
class ScannedQrCodeDialogTest {
@get:Rule
val composeTestRule = createComposeRule()
@get:Rule val composeTestRule = createComposeRule()
private fun getString(id: Int): String =
InstrumentationRegistry.getInstrumentation().targetContext.getString(id)
private fun getString(id: Int): String = InstrumentationRegistry.getInstrumentation().targetContext.getString(id)
private fun getRandomKey() = Channel.getRandomKey()
@ -56,26 +54,28 @@ class ScannedQrCodeDialogTest {
settings.addAll(
listOf(
Channel.default.settings,
channelSettings { name = "2"; psk = getRandomKey() },
channelSettings { name = "3"; psk = getRandomKey() },
channelSettings { name = "admin"; psk = getRandomKey() },
)
channelSettings {
name = "2"
psk = getRandomKey()
},
channelSettings {
name = "3"
psk = getRandomKey()
},
channelSettings {
name = "admin"
psk = getRandomKey()
},
),
)
loraConfig = Channel.default.loraConfig
.copy { modemPreset = ConfigProtos.Config.LoRaConfig.ModemPreset.SHORT_FAST }
loraConfig =
Channel.default.loraConfig.copy { modemPreset = ConfigProtos.Config.LoRaConfig.ModemPreset.SHORT_FAST }
}
private fun testScannedQrCodeDialog(
onDismiss: () -> Unit = {},
onConfirm: (ChannelSet) -> Unit = {},
) = composeTestRule.setContent {
ScannedQrCodeDialog(
channels = channels,
incoming = incoming,
onDismiss = onDismiss,
onConfirm = onConfirm,
)
}
private fun testScannedQrCodeDialog(onDismiss: () -> Unit = {}, onConfirm: (ChannelSet) -> Unit = {}) =
composeTestRule.setContent {
ScannedQrCodeDialog(channels = channels, incoming = incoming, onDismiss = onDismiss, onConfirm = onConfirm)
}
@Test
fun testScannedQrCodeDialog_showsDialogTitle() {
@ -149,11 +149,12 @@ class ScannedQrCodeDialogTest {
}
// Verify onConfirm is called with the correct ChannelSet
val expectedChannelSet = channels.copy {
val list = LinkedHashSet(settings + incoming.settingsList)
settings.clear()
settings.addAll(list)
}
val expectedChannelSet =
channels.copy {
val list = LinkedHashSet(settings + incoming.settingsList)
settings.clear()
settings.addAll(list)
}
Assert.assertEquals(expectedChannelSet, actualChannelSet)
}
}

Wyświetl plik

@ -29,7 +29,7 @@ import com.geeksville.mesh.analytics.NopAnalytics
import com.geeksville.mesh.android.BuildUtils.debug
import com.geeksville.mesh.android.BuildUtils.info
import com.geeksville.mesh.android.prefs.AnalyticsPrefs
import com.geeksville.mesh.model.DeviceHardware
import org.meshtastic.core.model.DeviceHardware
import timber.log.Timber
abstract class GeeksvilleApplication :

Wyświetl plik

@ -49,7 +49,6 @@ import com.geeksville.mesh.BuildConfig
import com.geeksville.mesh.analytics.AnalyticsProvider
import com.geeksville.mesh.analytics.FirebaseAnalytics
import com.geeksville.mesh.android.prefs.AnalyticsPrefs
import com.geeksville.mesh.model.DeviceHardware
import com.geeksville.mesh.util.exceptionReporter
import com.google.android.gms.common.ConnectionResult
import com.google.android.gms.common.GoogleApiAvailabilityLight
@ -60,6 +59,7 @@ import com.google.firebase.crashlytics.setCustomKeys
import com.google.firebase.initialize
import com.suddenh4x.ratingdialog.AppRating
import io.opentelemetry.api.GlobalOpenTelemetry
import org.meshtastic.core.model.DeviceHardware
import timber.log.Timber
abstract class GeeksvilleApplication :

Wyświetl plik

@ -20,8 +20,8 @@ package com.geeksville.mesh.database.entity
import androidx.room.ColumnInfo
import androidx.room.Entity
import androidx.room.PrimaryKey
import com.geeksville.mesh.model.DeviceHardware
import kotlinx.serialization.Serializable
import org.meshtastic.core.model.DeviceHardware
import org.meshtastic.core.network.model.NetworkDeviceHardware
@Serializable

Wyświetl plik

@ -1,120 +0,0 @@
/*
* Copyright (c) 2025 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
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.geeksville.mesh.model
import com.geeksville.mesh.ChannelProtos
import com.geeksville.mesh.ConfigProtos.Config.LoRaConfig.ModemPreset
import com.geeksville.mesh.ConfigKt.loRaConfig
import com.geeksville.mesh.ConfigProtos
import com.geeksville.mesh.channelSettings
import com.google.protobuf.ByteString
import java.security.SecureRandom
/** Utility function to make it easy to declare byte arrays - FIXME move someplace better */
fun byteArrayOfInts(vararg ints: Int) = ByteArray(ints.size) { pos -> ints[pos].toByte() }
fun xorHash(b: ByteArray) = b.fold(0) { acc, x -> acc xor (x.toInt() and 0xff) }
data class Channel(
val settings: ChannelProtos.ChannelSettings = default.settings,
val loraConfig: ConfigProtos.Config.LoRaConfig = default.loraConfig,
) {
companion object {
// These bytes must match the well known and not secret bytes used the default channel AES128 key device code
private val channelDefaultKey = byteArrayOfInts(
0xd4, 0xf1, 0xbb, 0x3a, 0x20, 0x29, 0x07, 0x59,
0xf0, 0xbc, 0xff, 0xab, 0xcf, 0x4e, 0x69, 0x01
)
private val cleartextPSK = ByteString.EMPTY
private val defaultPSK =
byteArrayOfInts(1) // a shortstring code to indicate we need our default PSK
// The default channel that devices ship with
val default = Channel(
channelSettings { psk = ByteString.copyFrom(defaultPSK) },
// references: NodeDB::installDefaultConfig / Channels::initDefaultChannel
loRaConfig {
usePreset = true
modemPreset = ModemPreset.LONG_FAST
hopLimit = 3
txEnabled = true
}
)
fun getRandomKey(size: Int = 32): ByteString {
val bytes = ByteArray(size)
val random = SecureRandom()
random.nextBytes(bytes)
return ByteString.copyFrom(bytes)
}
}
// Return the name of our channel as a human readable string. If empty string, assume "Default" per mesh.proto spec
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.usePreset) when (loraConfig.modemPreset) {
ModemPreset.SHORT_TURBO -> "ShortTurbo"
ModemPreset.SHORT_FAST -> "ShortFast"
ModemPreset.SHORT_SLOW -> "ShortSlow"
ModemPreset.MEDIUM_FAST -> "MediumFast"
ModemPreset.MEDIUM_SLOW -> "MediumSlow"
ModemPreset.LONG_FAST -> "LongFast"
ModemPreset.LONG_SLOW -> "LongSlow"
ModemPreset.LONG_MODERATE -> "LongMod"
ModemPreset.VERY_LONG_SLOW -> "VLongSlow"
else -> "Invalid"
} else "Custom"
}
val psk: ByteString
get() = if (settings.psk.size() != 1) {
settings.psk // A standard PSK
} else {
// One of our special 1 byte PSKs, see mesh.proto for docs.
val pskIndex = settings.psk.byteAt(0).toInt()
if (pskIndex == 0) {
cleartextPSK
} else {
// Treat an index of 1 as the old channelDefaultKey and work up from there
val bytes = channelDefaultKey.clone()
bytes[bytes.size - 1] = (0xff and (bytes[bytes.size - 1] + pskIndex - 1)).toByte()
ByteString.copyFrom(bytes)
}
}
/**
* Given a channel name and psk, return the (0 to 255) hash for that channel
*/
val hash: Int get() = xorHash(name.toByteArray()) xor xorHash(psk.toByteArray())
val channelNum: Int get() = loraConfig.channelNum(name)
val radioFreq: Float get() = loraConfig.radioFreq(channelNum)
override fun equals(other: Any?): Boolean = (other is Channel)
&& psk.toByteArray() contentEquals other.psk.toByteArray()
&& name == other.name
override fun hashCode(): Int {
var result = settings.hashCode()
result = 31 * result + loraConfig.hashCode()
return result
}
}

Wyświetl plik

@ -25,6 +25,7 @@ import com.geeksville.mesh.android.BuildUtils.errormsg
import com.google.zxing.BarcodeFormat
import com.google.zxing.MultiFormatWriter
import com.journeyapps.barcodescanner.BarcodeEncoder
import org.meshtastic.core.model.Channel
import java.net.MalformedURLException
import kotlin.jvm.Throws

Wyświetl plik

@ -62,6 +62,7 @@ import kotlinx.coroutines.flow.toList
import kotlinx.coroutines.flow.update
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import org.meshtastic.core.model.DeviceHardware
import org.meshtastic.core.navigation.NodesRoutes
import java.io.BufferedWriter
import java.io.FileNotFoundException

Wyświetl plik

@ -74,7 +74,6 @@ import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.filterNotNull
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.flow.flatMapLatest
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.map
@ -84,6 +83,7 @@ import kotlinx.coroutines.flow.shareIn
import kotlinx.coroutines.flow.stateIn
import kotlinx.coroutines.flow.update
import kotlinx.coroutines.launch
import org.meshtastic.core.model.DeviceHardware
import javax.inject.Inject
// Given a human name, strip out the first letter of the first three words and return that as the

Wyświetl plik

@ -21,9 +21,9 @@ import com.geeksville.mesh.android.BuildUtils.debug
import com.geeksville.mesh.android.BuildUtils.warn
import com.geeksville.mesh.database.entity.DeviceHardwareEntity
import com.geeksville.mesh.database.entity.asExternalModel
import com.geeksville.mesh.model.DeviceHardware
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import org.meshtastic.core.model.DeviceHardware
import org.meshtastic.core.network.DeviceHardwareRemoteDataSource
import java.util.concurrent.TimeUnit
import javax.inject.Inject

Wyświetl plik

@ -32,13 +32,13 @@ import com.geeksville.mesh.concurrent.handledLaunch
import com.geeksville.mesh.config
import com.geeksville.mesh.deviceMetadata
import com.geeksville.mesh.fromRadio
import com.geeksville.mesh.model.Channel
import com.geeksville.mesh.model.getInitials
import com.geeksville.mesh.queueStatus
import com.google.protobuf.ByteString
import dagger.assisted.Assisted
import dagger.assisted.AssistedInject
import kotlinx.coroutines.delay
import org.meshtastic.core.model.Channel
import kotlin.random.Random
private val defaultLoRaConfig =

Wyświetl plik

@ -74,7 +74,6 @@ import com.geeksville.mesh.fromRadio
import com.geeksville.mesh.model.DeviceVersion
import com.geeksville.mesh.model.NO_DEVICE_SELECTED
import com.geeksville.mesh.model.Node
import com.geeksville.mesh.model.getFullTracerouteResponse
import com.geeksville.mesh.position
import com.geeksville.mesh.repository.datastore.RadioConfigRepository
import com.geeksville.mesh.repository.location.LocationRepository
@ -104,6 +103,7 @@ import kotlinx.coroutines.flow.flatMapLatest
import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach
import org.meshtastic.core.model.getFullTracerouteResponse
import java.util.Random
import java.util.UUID
import java.util.concurrent.ConcurrentHashMap

Wyświetl plik

@ -44,10 +44,10 @@ import androidx.compose.ui.text.input.KeyboardType
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import com.geeksville.mesh.R
import com.geeksville.mesh.model.Channel
import com.geeksville.mesh.util.encodeToString
import com.geeksville.mesh.util.toByteString
import com.google.protobuf.ByteString
import org.meshtastic.core.model.Channel
@Suppress("LongMethod")
@Composable
@ -62,7 +62,6 @@ fun EditBase64Preference(
onGenerateKey: (() -> Unit)? = null,
trailingIcon: (@Composable () -> Unit)? = null,
) {
var valueState by remember { mutableStateOf(value.encodeToString()) }
val isError = value.encodeToString() != valueState
@ -74,11 +73,12 @@ fun EditBase64Preference(
}
}
val (icon, description) = when {
isError -> Icons.TwoTone.Close to stringResource(R.string.error)
onGenerateKey != null && !isFocused -> Icons.TwoTone.Refresh to stringResource(R.string.reset)
else -> null to null
}
val (icon, description) =
when {
isError -> Icons.TwoTone.Close to stringResource(R.string.error)
onGenerateKey != null && !isFocused -> Icons.TwoTone.Refresh to stringResource(R.string.reset)
else -> null to null
}
OutlinedTextField(
value = valueState,
@ -86,16 +86,13 @@ fun EditBase64Preference(
valueState = it
runCatching { it.toByteString() }.onSuccess(onValueChange)
},
modifier = modifier
.fillMaxWidth()
.onFocusChanged { focusState -> isFocused = focusState.isFocused },
modifier = modifier.fillMaxWidth().onFocusChanged { focusState -> isFocused = focusState.isFocused },
enabled = enabled,
readOnly = readOnly,
label = { Text(text = title) },
isError = isError,
keyboardOptions = KeyboardOptions.Default.copy(
keyboardType = KeyboardType.Password, imeAction = ImeAction.Done
),
keyboardOptions =
KeyboardOptions.Default.copy(keyboardType = KeyboardType.Password, imeAction = ImeAction.Done),
keyboardActions = keyboardActions,
trailingIcon = {
if (icon != null) {
@ -113,11 +110,12 @@ fun EditBase64Preference(
Icon(
imageVector = icon,
contentDescription = description,
tint = if (isError) {
tint =
if (isError) {
MaterialTheme.colorScheme.error
} else {
LocalContentColor.current
}
},
)
}
} else if (trailingIcon != null) {
@ -137,6 +135,6 @@ private fun EditBase64PreferencePreview() {
keyboardActions = KeyboardActions {},
onValueChange = {},
onGenerateKey = {},
modifier = Modifier.padding(16.dp)
modifier = Modifier.padding(16.dp),
)
}

Wyświetl plik

@ -55,9 +55,9 @@ import com.geeksville.mesh.ConfigProtos.Config.LoRaConfig.ModemPreset
import com.geeksville.mesh.R
import com.geeksville.mesh.channelSet
import com.geeksville.mesh.copy
import com.geeksville.mesh.model.Channel
import com.geeksville.mesh.model.UIViewModel
import com.geeksville.mesh.ui.settings.radio.components.ChannelSelection
import org.meshtastic.core.model.Channel
@Composable
fun ScannedQrCodeDialog(viewModel: UIViewModel, incoming: ChannelSet) {

Wyświetl plik

@ -64,11 +64,11 @@ import com.geeksville.mesh.AppOnlyProtos
import com.geeksville.mesh.ChannelProtos.ChannelSettings
import com.geeksville.mesh.ConfigProtos.Config.LoRaConfig
import com.geeksville.mesh.R
import com.geeksville.mesh.model.Channel
import com.geeksville.mesh.model.getChannel
import com.geeksville.mesh.ui.common.theme.StatusColors.StatusGreen
import com.geeksville.mesh.ui.common.theme.StatusColors.StatusRed
import com.geeksville.mesh.ui.common.theme.StatusColors.StatusYellow
import org.meshtastic.core.model.Channel
private const val PRECISE_POSITION_BITS = 32

Wyświetl plik

@ -59,11 +59,11 @@ import androidx.lifecycle.compose.collectAsStateWithLifecycle
import com.geeksville.mesh.MeshProtos
import com.geeksville.mesh.R
import com.geeksville.mesh.model.MetricsViewModel
import com.geeksville.mesh.model.fullRouteDiscovery
import com.geeksville.mesh.model.getTracerouteResponse
import com.geeksville.mesh.ui.common.components.SimpleAlertDialog
import com.geeksville.mesh.ui.common.theme.AppTheme
import com.geeksville.mesh.ui.metrics.CommonCharts.MS_PER_SEC
import org.meshtastic.core.model.fullRouteDiscovery
import org.meshtastic.core.model.getTracerouteResponse
import java.text.DateFormat
@OptIn(ExperimentalFoundationApi::class)

Wyświetl plik

@ -127,7 +127,6 @@ import com.geeksville.mesh.MeshProtos
import com.geeksville.mesh.R
import com.geeksville.mesh.database.entity.FirmwareRelease
import com.geeksville.mesh.database.entity.asDeviceVersion
import com.geeksville.mesh.model.DeviceHardware
import com.geeksville.mesh.model.DeviceVersion
import com.geeksville.mesh.model.MetricsState
import com.geeksville.mesh.model.MetricsViewModel
@ -159,6 +158,7 @@ import com.geeksville.mesh.util.toDistanceString
import com.geeksville.mesh.util.toSmallDistanceString
import com.geeksville.mesh.util.toSpeedString
import com.mikepenz.markdown.m3.Markdown
import org.meshtastic.core.model.DeviceHardware
import org.meshtastic.core.navigation.NodeDetailRoutes
import org.meshtastic.core.navigation.Route
import org.meshtastic.core.navigation.SettingsRoutes

Wyświetl plik

@ -55,13 +55,13 @@ import androidx.compose.ui.tooling.preview.PreviewLightDark
import androidx.compose.ui.unit.dp
import androidx.compose.ui.window.Dialog
import com.geeksville.mesh.R
import com.geeksville.mesh.model.Channel
import com.geeksville.mesh.ui.common.components.CopyIconButton
import com.geeksville.mesh.ui.common.theme.AppTheme
import com.geeksville.mesh.ui.common.theme.StatusColors.StatusGreen
import com.geeksville.mesh.ui.common.theme.StatusColors.StatusRed
import com.geeksville.mesh.ui.common.theme.StatusColors.StatusYellow
import com.google.protobuf.ByteString
import org.meshtastic.core.model.Channel
@Composable
private fun KeyStatusDialog(@StringRes title: Int, @StringRes text: Int, key: ByteString?, onDismiss: () -> Unit = {}) =

Wyświetl plik

@ -72,7 +72,6 @@ import com.geeksville.mesh.ChannelProtos.ChannelSettings
import com.geeksville.mesh.ConfigProtos.Config.LoRaConfig
import com.geeksville.mesh.R
import com.geeksville.mesh.channelSettings
import com.geeksville.mesh.model.Channel
import com.geeksville.mesh.model.DeviceVersion
import com.geeksville.mesh.ui.common.components.PreferenceCategory
import com.geeksville.mesh.ui.common.components.PreferenceFooter
@ -81,6 +80,7 @@ import com.geeksville.mesh.ui.common.components.dragContainer
import com.geeksville.mesh.ui.common.components.dragDropItemsIndexed
import com.geeksville.mesh.ui.common.components.rememberDragDropState
import com.geeksville.mesh.ui.settings.radio.RadioConfigViewModel
import org.meshtastic.core.model.Channel
@Composable
private fun ChannelItem(

Wyświetl plik

@ -46,11 +46,11 @@ import com.geeksville.mesh.ChannelProtos
import com.geeksville.mesh.R
import com.geeksville.mesh.channelSettings
import com.geeksville.mesh.copy
import com.geeksville.mesh.model.Channel
import com.geeksville.mesh.ui.common.components.EditBase64Preference
import com.geeksville.mesh.ui.common.components.EditTextPreference
import com.geeksville.mesh.ui.common.components.PositionPrecisionPreference
import com.geeksville.mesh.ui.common.components.SwitchPreference
import org.meshtastic.core.model.Channel
@Suppress("LongMethod")
@Composable

Wyświetl plik

@ -38,9 +38,6 @@ import com.geeksville.mesh.ConfigProtos.Config.LoRaConfig
import com.geeksville.mesh.R
import com.geeksville.mesh.config
import com.geeksville.mesh.copy
import com.geeksville.mesh.model.Channel
import com.geeksville.mesh.model.RegionInfo
import com.geeksville.mesh.model.numChannels
import com.geeksville.mesh.ui.common.components.DropDownPreference
import com.geeksville.mesh.ui.common.components.EditTextPreference
import com.geeksville.mesh.ui.common.components.PreferenceCategory
@ -48,6 +45,9 @@ import com.geeksville.mesh.ui.common.components.PreferenceFooter
import com.geeksville.mesh.ui.common.components.SignedIntegerEditTextPreference
import com.geeksville.mesh.ui.common.components.SwitchPreference
import com.geeksville.mesh.ui.settings.radio.RadioConfigViewModel
import org.meshtastic.core.model.Channel
import org.meshtastic.core.model.RegionInfo
import org.meshtastic.core.model.numChannels
@Composable
fun LoRaConfigScreen(viewModel: RadioConfigViewModel = hiltViewModel()) {

Wyświetl plik

@ -96,7 +96,6 @@ import com.geeksville.mesh.android.BuildUtils.errormsg
import com.geeksville.mesh.android.GeeksvilleApplication
import com.geeksville.mesh.channelSet
import com.geeksville.mesh.copy
import com.geeksville.mesh.model.Channel
import com.geeksville.mesh.model.UIViewModel
import com.geeksville.mesh.model.getChannelUrl
import com.geeksville.mesh.model.qrCode
@ -115,6 +114,7 @@ import com.google.accompanist.permissions.rememberPermissionState
import com.journeyapps.barcodescanner.ScanContract
import com.journeyapps.barcodescanner.ScanOptions
import kotlinx.coroutines.launch
import org.meshtastic.core.model.Channel
import org.meshtastic.core.navigation.Route
/**

Wyświetl plik

@ -16,10 +16,11 @@
*/
plugins {
alias(libs.plugins.meshtastic.android.library)
alias(libs.plugins.kover)
alias(libs.plugins.meshtastic.android.library)
alias(libs.plugins.meshtastic.kotlinx.serialization)
}
android { namespace = "org.meshtastic.core.model" }
dependencies {}
dependencies { implementation(projects.core.proto) }

Wyświetl plik

@ -0,0 +1,143 @@
/*
* Copyright (c) 2025 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
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package org.meshtastic.core.model
import com.geeksville.mesh.ChannelProtos
import com.geeksville.mesh.ConfigKt.loRaConfig
import com.geeksville.mesh.ConfigProtos
import com.geeksville.mesh.ConfigProtos.Config.LoRaConfig.ModemPreset
import com.geeksville.mesh.channelSettings
import com.google.protobuf.ByteString
import java.security.SecureRandom
/** Utility function to make it easy to declare byte arrays - FIXME move someplace better */
fun byteArrayOfInts(vararg ints: Int) = ByteArray(ints.size) { pos -> ints[pos].toByte() }
fun xorHash(b: ByteArray) = b.fold(0) { acc, x -> acc xor (x.toInt() and 0xff) }
data class Channel(
val settings: ChannelProtos.ChannelSettings = default.settings,
val loraConfig: ConfigProtos.Config.LoRaConfig = default.loraConfig,
) {
companion object {
// These bytes must match the well known and not secret bytes used the default channel AES128 key device code
private val channelDefaultKey =
byteArrayOfInts(
0xd4,
0xf1,
0xbb,
0x3a,
0x20,
0x29,
0x07,
0x59,
0xf0,
0xbc,
0xff,
0xab,
0xcf,
0x4e,
0x69,
0x01,
)
private val cleartextPSK = ByteString.EMPTY
private val defaultPSK = byteArrayOfInts(1) // a shortstring code to indicate we need our default PSK
// The default channel that devices ship with
val default =
Channel(
channelSettings { psk = ByteString.copyFrom(defaultPSK) },
// references: NodeDB::installDefaultConfig / Channels::initDefaultChannel
loRaConfig {
usePreset = true
modemPreset = ModemPreset.LONG_FAST
hopLimit = 3
txEnabled = true
},
)
fun getRandomKey(size: Int = 32): ByteString {
val bytes = ByteArray(size)
val random = SecureRandom()
random.nextBytes(bytes)
return ByteString.copyFrom(bytes)
}
}
// Return the name of our channel as a human readable string. If empty string, assume "Default" per mesh.proto spec
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.usePreset) {
when (loraConfig.modemPreset) {
ModemPreset.SHORT_TURBO -> "ShortTurbo"
ModemPreset.SHORT_FAST -> "ShortFast"
ModemPreset.SHORT_SLOW -> "ShortSlow"
ModemPreset.MEDIUM_FAST -> "MediumFast"
ModemPreset.MEDIUM_SLOW -> "MediumSlow"
ModemPreset.LONG_FAST -> "LongFast"
ModemPreset.LONG_SLOW -> "LongSlow"
ModemPreset.LONG_MODERATE -> "LongMod"
ModemPreset.VERY_LONG_SLOW -> "VLongSlow"
else -> "Invalid"
}
} else {
"Custom"
}
}
val psk: ByteString
get() =
if (settings.psk.size() != 1) {
settings.psk // A standard PSK
} else {
// One of our special 1 byte PSKs, see mesh.proto for docs.
val pskIndex = settings.psk.byteAt(0).toInt()
if (pskIndex == 0) {
cleartextPSK
} else {
// Treat an index of 1 as the old channelDefaultKey and work up from there
val bytes = channelDefaultKey.clone()
bytes[bytes.size - 1] = (0xff and (bytes[bytes.size - 1] + pskIndex - 1)).toByte()
ByteString.copyFrom(bytes)
}
}
/** Given a channel name and psk, return the (0 to 255) hash for that channel */
val hash: Int
get() = xorHash(name.toByteArray()) xor xorHash(psk.toByteArray())
val channelNum: Int
get() = loraConfig.channelNum(name)
val radioFreq: Float
get() = loraConfig.radioFreq(channelNum)
override fun equals(other: Any?): Boolean =
(other is Channel) && psk.toByteArray() contentEquals other.psk.toByteArray() && name == other.name
override fun hashCode(): Int {
var result = settings.hashCode()
result = 31 * result + loraConfig.hashCode()
return result
}
}

Wyświetl plik

@ -15,7 +15,7 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.geeksville.mesh.model
package org.meshtastic.core.model
import com.geeksville.mesh.ConfigProtos.Config.LoRaConfig
import com.geeksville.mesh.ConfigProtos.Config.LoRaConfig.ModemPreset

Wyświetl plik

@ -15,7 +15,7 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.geeksville.mesh.model
package org.meshtastic.core.model
import kotlinx.serialization.Serializable
@ -33,5 +33,5 @@ data class DeviceHardware(
val platformioTarget: String = "",
val requiresDfu: Boolean? = null,
val supportLevel: Int? = null,
val tags: List<String>? = null
val tags: List<String>? = null,
)

Wyświetl plik

@ -15,7 +15,7 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.geeksville.mesh.model
package org.meshtastic.core.model
import com.geeksville.mesh.MeshProtos
import com.geeksville.mesh.MeshProtos.RouteDiscovery