Modularize some model classes (#3153)

This commit is contained in:
Phil Oliver 2025-09-19 15:53:43 -04:00 committed by GitHub
parent ab2fff219d
commit 8fb41aab74
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
27 changed files with 215 additions and 190 deletions

View file

@ -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

View file

@ -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
}
}

View file

@ -1,307 +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.ConfigProtos.Config.LoRaConfig
import com.geeksville.mesh.ConfigProtos.Config.LoRaConfig.ModemPreset
import com.geeksville.mesh.ConfigProtos.Config.LoRaConfig.RegionCode
import kotlin.math.floor
/** hash a string into an integer using the djb2 algorithm by Dan Bernstein http://www.cse.yorku.ca/~oz/hash.html */
private fun hash(name: String): UInt { // using UInt instead of Long to match RadioInterface.cpp results
var hash = 5381u
for (c in name) {
hash += (hash shl 5) + c.code.toUInt()
}
return hash
}
private val ModemPreset.bandwidth: Float
get() {
for (option in ChannelOption.entries) {
if (option.modemPreset == this) return option.bandwidth
}
return 0f
}
private fun LoRaConfig.bandwidth(regionInfo: RegionInfo?) = if (usePreset) {
modemPreset.bandwidth * if (regionInfo?.wideLora == true) 3.25f else 1f
} else {
when (bandwidth) {
31 -> .03125f
62 -> .0625f
200 -> .203125f
400 -> .40625f
800 -> .8125f
1600 -> 1.6250f
else -> bandwidth / 1000f
}
}
val LoRaConfig.numChannels: Int
get() {
val regionInfo = RegionInfo.fromRegionCode(region)
if (regionInfo == null) return 0
val bw = bandwidth(regionInfo)
if (bw <= 0f) return 1 // Return 1 if bandwidth is zero or negative
val num = floor((regionInfo.freqEnd - regionInfo.freqStart) / bw)
// 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.
return if (num > 0) num.toInt() else 1
}
internal fun LoRaConfig.channelNum(primaryName: String): Int = when {
channelNum != 0 -> channelNum
numChannels == 0 -> 0
else -> (hash(primaryName) % numChannels.toUInt()).toInt() + 1
}
internal fun LoRaConfig.radioFreq(channelNum: Int): Float {
if (overrideFrequency != 0f) return overrideFrequency + frequencyOffset
val regionInfo = RegionInfo.fromRegionCode(region)
return if (regionInfo != null) {
(regionInfo.freqStart + bandwidth(regionInfo) / 2) + (channelNum - 1) * bandwidth(regionInfo)
} else {
0f
}
}
/**
* Regulatory regions for radio usage
*
* @property regionCode The region code
* @property description A human readable description of the region
* @property freqStart The starting frequency in MHz
* @property freqEnd The ending frequency in MHz
* @property wideLora Whether the region uses wide Lora
* @see
* [LoRaWAN Regional Parameters](https://lora-alliance.org/wp-content/uploads/2020/11/lorawan_regional_parameters_v1.0.3reva_0.pdf)
*/
@Suppress("MagicNumber")
enum class RegionInfo(
val regionCode: RegionCode,
val description: String,
val freqStart: Float,
val freqEnd: Float,
val wideLora: Boolean = false,
) {
/** This needs to be last. Same as US. */
UNSET(RegionCode.UNSET, "Please set a region", 902.0f, 928.0f),
/**
* United States
*
* @see [Springer Link](https://link.springer.com/content/pdf/bbm%3A978-1-4842-4357-2%2F1.pdf)
* @see [The Things Network](https://www.thethingsnetwork.org/docs/lorawan/regional-parameters/)
*/
US(RegionCode.US, "United States", 902.0f, 928.0f),
/** European Union 433MHz */
EU_433(RegionCode.EU_433, "European Union 433MHz", 433.0f, 434.0f),
/**
* European Union 868MHz
*
* Special Note: The link above describes LoRaWAN's band plan, stating a power limit of 16 dBm. This is their own
* suggested specification, we do not need to follow it. The European Union regulations clearly state that the power
* limit for this frequency range is 500 mW, or 27 dBm. It also states that we can use interference avoidance and
* spectrum access techniques (such as LBT + AFA) to avoid a duty cycle. (Please refer to line P page 22 of this
* document.)
*
* @see
* [ETSI EN 300 220-2 V3.1.1](https://www.etsi.org/deliver/etsi_en/300200_300299/30022002/03.01.01_60/en_30022002v030101p.pdf)
*/
EU_868(RegionCode.EU_868, "European Union 868MHz", 869.4f, 869.65f),
/** China */
CN(RegionCode.CN, "China", 470.0f, 510.0f),
/**
* Japan
*
* @see [ARIB STD-T108](https://www.arib.or.jp/english/html/overview/doc/5-STD-T108v1_5-E1.pdf)
* @see [Qiita](https://qiita.com/ammo0613/items/d952154f1195b64dc29f)
*/
JP(RegionCode.JP, "Japan", 920.5f, 923.5f),
/**
* Australia / New Zealand
*
* @see [IoT Spectrum Fact Sheet](https://www.iot.org.au/wp/wp-content/uploads/2016/12/IoTSpectrumFactSheet.pdf)
* @see
* [IoT Spectrum in NZ Briefing Paper](https://iotalliance.org.nz/wp-content/uploads/sites/4/2019/05/IoT-Spectrum-in-NZ-Briefing-Paper.pdf)
*/
ANZ(RegionCode.ANZ, "Australia / Brazil / New Zealand", 915.0f, 928.0f),
/**
* Korea
*
* @see [Law.go.kr](https://www.law.go.kr/LSW/admRulLsInfoP.do?admRulId=53943&efYd=0)
* @see
* [LoRaWAN Regional Parameters](https://resources.lora-alliance.org/technical-specifications/rp002-1-0-4-regional-parameters)
*/
KR(RegionCode.KR, "Korea", 920.0f, 923.0f),
/**
* Taiwan, 920-925Mhz, limited to 0.5W indoor or coastal, 1.0W outdoor. 5.8.1 in the Low-power Radio-frequency
* Devices Technical Regulations
*
* @see [NCC Taiwan](https://www.ncc.gov.tw/english/files/23070/102_5190_230703_1_doc_C.PDF)
* @see [National Gazette](https://gazette.nat.gov.tw/egFront/e_detail.do?metaid=147283)
*/
TW(RegionCode.TW, "Taiwan", 920.0f, 925.0f),
/**
* Russia Note:
* - We do LBT, so 100% is allowed.
*
* @see [Digital.gov.ru](https://digital.gov.ru/uploaded/files/prilozhenie-12-k-reshenyu-gkrch-18-46-03-1.pdf)
*/
RU(RegionCode.RU, "Russia", 868.7f, 869.2f),
/** India */
IN(RegionCode.IN, "India", 865.0f, 867.0f),
/**
* New Zealand 865MHz
*
* @see [RSM NZ](https://rrf.rsm.govt.nz/smart-web/smart/page/-smart/domain/licence/LicenceSummary.wdk?id=219752)
* @see
* [IoT Spectrum in NZ Briefing Paper](https://iotalliance.org.nz/wp-content/uploads/sites/4/2019/05/IoT-Spectrum-in-NZ-Briefing-Paper.pdf)
*/
NZ_865(RegionCode.NZ_865, "New Zealand 865MHz", 864.0f, 868.0f),
/** Thailand */
TH(RegionCode.TH, "Thailand", 920.0f, 925.0f),
/**
* Ukraine 433MHz 433,05-434,7 Mhz 10 mW
*
* @see [NKZRZI](https://nkrzi.gov.ua/images/upload/256/5810/PDF_UUZ_19_01_2016.pdf)
*/
UA_433(RegionCode.UA_433, "Ukraine 433MHz", 433.0f, 434.7f),
/**
* Ukraine 868MHz 868,0-868,6 Mhz 25 mW
*
* @see [NKZRZI](https://nkrzi.gov.ua/images/upload/256/5810/PDF_UUZ_19_01_2016.pdf)
*/
UA_868(RegionCode.UA_868, "Ukraine 868MHz", 868.0f, 868.6f),
/**
* Malaysia 433MHz 433 - 435 MHz at 100mW, no restrictions.
*
* @see [MCMC](https://www.mcmc.gov.my/skmmgovmy/media/General/pdf/Short-Range-Devices-Specification.pdf)
*/
MY_433(RegionCode.MY_433, "Malaysia 433MHz", 433.0f, 435.0f),
/**
* Malaysia 919MHz 919 - 923 Mhz at 500mW, no restrictions. 923 - 924 MHz at 500mW with 1% duty cycle OR frequency
* hopping. Frequency hopping is used for 919 - 923 MHz.
*
* @see [MCMC](https://www.mcmc.gov.my/skmmgovmy/media/General/pdf/Short-Range-Devices-Specification.pdf)
*/
MY_919(RegionCode.MY_919, "Malaysia 919MHz", 919.0f, 924.0f),
/**
* Singapore 923MHz SG_923 Band 30d: 917 - 925 MHz at 100mW, no restrictions.
*
* @see
* [IMDA](https://www.imda.gov.sg/-/media/imda/files/regulation-licensing-and-consultations/ict-standards/telecommunication-standards/radio-comms/imdatssrd.pdf)
*/
SG_923(RegionCode.SG_923, "Singapore 923MHz", 917.0f, 925.0f),
/**
* Philippines 433MHz 433 - 434.7 MHz <10 mW erp, NTC approved device required
*
* @see [Firmware Issue #4948](https://github.com/meshtastic/firmware/issues/4948#issuecomment-2394926135)
*/
PH_433(RegionCode.PH_433, "Philippines 433MHz", 433.0f, 434.7f),
/**
* Philippines 868MHz 868 - 869.4 MHz <25 mW erp, NTC approved device required
*
* @see [Firmware Issue #4948](https://github.com/meshtastic/firmware/issues/4948#issuecomment-2394926135)
*/
PH_868(RegionCode.PH_868, "Philippines 868MHz", 868.0f, 869.4f),
/**
* Philippines 915MHz 915 - 918 MHz <250 mW EIRP, no external antenna allowed
*
* @see [Firmware Issue #4948](https://github.com/meshtastic/firmware/issues/4948#issuecomment-2394926135)
*/
PH_915(RegionCode.PH_915, "Philippines 915MHz", 915.0f, 918.0f),
/** 2.4 GHZ WLAN Band equivalent. Only for SX128x chips. */
LORA_24(RegionCode.LORA_24, "2.4 GHz", 2400.0f, 2483.5f, wideLora = true),
/**
* Australia / New Zealand 433MHz 433.05 - 434.79 MHz, 25mW EIRP max, No duty cycle restrictions
*
* @see [ACMA](https://www.acma.gov.au/licences/low-interference-potential-devices-lipd-class-licence)
* @see [NZ Gazette](https://gazette.govt.nz/notice/id/2022-go3100)
*/
ANZ_433(RegionCode.ANZ_433, "Australia / New Zealand 433MHz", 433.05f, 434.79f),
/**
* Kazakhstan 433MHz 433.075 - 434.775 MHz <10 mW EIRP, Low Powered Devices (LPD)
*
* @see [Firmware Issue #7204](https://github.com/meshtastic/firmware/issues/7204)
*/
KZ_433(RegionCode.KZ_433, "Kazakhstan 433MHz", 433.075f, 434.775f),
/**
* Kazakhstan 863MHz 863 - 868 MHz <25 mW EIRP, 500kHz channels allowed, must not be used at airfields
*
* @see [Firmware Issue #7204](https://github.com/meshtastic/firmware/issues/7204)
*/
KZ_863(RegionCode.KZ_863, "Kazakhstan 863MHz", 863.0f, 868.0f, wideLora = true),
/**
* Nepal 865Mhz 865 - 868 Mhz
*
* @see [Firmware Issue #7380](https://github.com/meshtastic/firmware/pull/7380)
*/
NP_865(RegionCode.NP_865, "Nepal 865MHz", 865.0f, 868.0f, wideLora = false),
/**
* Brazil 902MHz 902 - 907.5 MHz
*
* @see [Firmware Issue #7399](https://github.com/meshtastic/firmware/pull/7399)
*/
BR_902(RegionCode.BR_902, "Brazil 902MHz", 902.0f, 907.5f, wideLora = false),
;
companion object {
fun fromRegionCode(regionCode: RegionCode): RegionInfo? = entries.find { it.regionCode == regionCode }
}
}
enum class ChannelOption(val modemPreset: ModemPreset, val bandwidth: Float) {
SHORT_TURBO(ModemPreset.SHORT_TURBO, bandwidth = .500f),
SHORT_FAST(ModemPreset.SHORT_FAST, .250f),
SHORT_SLOW(ModemPreset.SHORT_SLOW, .250f),
MEDIUM_FAST(ModemPreset.MEDIUM_FAST, .250f),
MEDIUM_SLOW(ModemPreset.MEDIUM_SLOW, .250f),
LONG_FAST(ModemPreset.LONG_FAST, .250f),
LONG_MODERATE(ModemPreset.LONG_MODERATE, .125f),
LONG_SLOW(ModemPreset.LONG_SLOW, .125f),
VERY_LONG_SLOW(ModemPreset.VERY_LONG_SLOW, .0625f),
}

View file

@ -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

View file

@ -1,37 +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 kotlinx.serialization.Serializable
@Serializable
data class DeviceHardware(
val activelySupported: Boolean = false,
val architecture: String = "",
val displayName: String = "",
val hasInkHud: Boolean? = null,
val hasMui: Boolean? = null,
val hwModel: Int = 0,
val hwModelSlug: String = "",
val images: List<String>? = null,
val partitionScheme: String? = null,
val platformioTarget: String = "",
val requiresDfu: Boolean? = null,
val supportLevel: Int? = null,
val tags: List<String>? = null
)

View file

@ -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

View file

@ -1,87 +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.MeshProtos
import com.geeksville.mesh.MeshProtos.RouteDiscovery
import com.geeksville.mesh.Portnums
val MeshProtos.MeshPacket.fullRouteDiscovery: RouteDiscovery?
get() =
with(decoded) {
if (hasDecoded() && !wantResponse && portnum == Portnums.PortNum.TRACEROUTE_APP) {
runCatching { RouteDiscovery.parseFrom(payload).toBuilder() }
.getOrNull()
?.apply {
val fullRoute = listOf(to) + routeList + from
clearRoute()
addAllRoute(fullRoute)
val fullRouteBack = listOf(from) + routeBackList + to
clearRouteBack()
if (hopStart > 0 && snrBackCount > 0) { // otherwise back route is invalid
addAllRouteBack(fullRouteBack)
}
}
?.build()
} else {
null
}
}
@Suppress("MagicNumber")
private fun formatTraceroutePath(nodesList: List<String>, snrList: List<Int>): String {
// nodesList should include both origin and destination nodes
// origin will not have an SNR value, but destination should
val snrStr =
if (snrList.size == nodesList.size - 1) {
snrList
} else {
// use unknown SNR for entire route if snrList has invalid size
List(nodesList.size - 1) { -128 }
}
.map { snr ->
val str = if (snr == -128) "?" else "${snr / 4f}"
"$str dB"
}
return nodesList
.map { userName -> "$userName" }
.flatMapIndexed { i, nodeStr -> if (i == 0) listOf(nodeStr) else listOf(snrStr[i - 1], nodeStr) }
.joinToString("\n")
}
private fun RouteDiscovery.getTracerouteResponse(getUser: (nodeNum: Int) -> String): String = buildString {
if (routeList.isNotEmpty()) {
append("Route traced toward destination:\n\n")
append(formatTraceroutePath(routeList.map(getUser), snrTowardsList))
}
if (routeBackList.isNotEmpty()) {
append("\n\n")
append("Route traced back to us:\n\n")
append(formatTraceroutePath(routeBackList.map(getUser), snrBackList))
}
}
fun MeshProtos.MeshPacket.getTracerouteResponse(getUser: (nodeNum: Int) -> String): String? =
fullRouteDiscovery?.getTracerouteResponse(getUser)
/** Returns a traceroute response string only when the result is complete (both directions). */
fun MeshProtos.MeshPacket.getFullTracerouteResponse(getUser: (nodeNum: Int) -> String): String? = fullRouteDiscovery
?.takeIf { it.routeList.isNotEmpty() && it.routeBackList.isNotEmpty() }
?.getTracerouteResponse(getUser)

View file

@ -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

View file

@ -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

View file

@ -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 =

View file

@ -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

View file

@ -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),
)
}

View file

@ -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) {

View file

@ -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

View file

@ -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)

View file

@ -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

View file

@ -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 = {}) =

View file

@ -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(

View file

@ -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

View file

@ -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()) {

View file

@ -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
/**