diff --git a/app/build.gradle b/app/build.gradle
index 49e8b7a97..cc66f08c2 100644
--- a/app/build.gradle
+++ b/app/build.gradle
@@ -43,8 +43,8 @@ android {
applicationId "com.geeksville.mesh"
minSdkVersion 21 // The oldest emulator image I have tried is 22 (though 21 probably works)
targetSdkVersion 30
- versionCode 20342 // format is Mmmss (where M is 1+the numeric major number
- versionName "1.3.42"
+ versionCode 20345 // format is Mmmss (where M is 1+the numeric major number
+ versionName "1.3.45"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
// per https://developer.android.com/studio/write/vector-asset-studio
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index 535d6953d..bbd202024 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -137,7 +137,7 @@
@@ -146,11 +146,11 @@
diff --git a/app/src/main/aidl/com/geeksville/mesh/IMeshService.aidl b/app/src/main/aidl/com/geeksville/mesh/IMeshService.aidl
index 329f3715e..55206fcc6 100644
--- a/app/src/main/aidl/com/geeksville/mesh/IMeshService.aidl
+++ b/app/src/main/aidl/com/geeksville/mesh/IMeshService.aidl
@@ -72,20 +72,12 @@ interface IMeshService {
List getNodes();
/// This method is only intended for use in our GUI, so the user can set radio options
- /// It returns a DeviceConfig protobuf.
- byte []getDeviceConfig();
+ /// It sets a Config protobuf via admin packet
+ void setConfig(in byte []payload);
/// This method is only intended for use in our GUI, so the user can set radio options
- /// It sets a DeviceConfig protobuf
- void setDeviceConfig(in byte []payload);
-
- /// This method is only intended for use in our GUI, so the user can set radio options
- /// It returns a ChannelSet protobuf.
- byte []getChannels();
-
- /// This method is only intended for use in our GUI, so the user can set radio options
- /// It sets a ChannelSet protobuf
- void setChannels(in byte []payload);
+ /// It sets a Channel protobuf via admin packet
+ void setChannel(in byte []payload);
/// Send Shutdown admin packet to nodeNum
void requestShutdown(in int idNum);
diff --git a/app/src/main/java/com/geeksville/mesh/MainActivity.kt b/app/src/main/java/com/geeksville/mesh/MainActivity.kt
index 0ec7b5716..aa02eca0f 100644
--- a/app/src/main/java/com/geeksville/mesh/MainActivity.kt
+++ b/app/src/main/java/com/geeksville/mesh/MainActivity.kt
@@ -13,7 +13,6 @@ import android.text.method.LinkMovementMethod
import android.view.Menu
import android.view.MenuItem
import android.view.MotionEvent
-import android.view.View
import android.widget.TextView
import android.widget.Toast
import androidx.activity.result.contract.ActivityResultContracts
@@ -40,6 +39,7 @@ import com.geeksville.mesh.model.BluetoothViewModel
import com.geeksville.mesh.model.ChannelSet
import com.geeksville.mesh.model.DeviceVersion
import com.geeksville.mesh.model.UIViewModel
+import com.geeksville.mesh.repository.radio.BluetoothInterface
import com.geeksville.mesh.repository.radio.RadioInterfaceService
import com.geeksville.mesh.repository.radio.SerialInterface
import com.geeksville.mesh.service.*
@@ -57,7 +57,7 @@ import kotlinx.coroutines.Job
import kotlinx.coroutines.cancel
import java.nio.charset.Charset
import java.text.DateFormat
-import java.util.*
+import java.util.Date
import javax.inject.Inject
/*
@@ -302,8 +302,7 @@ class MainActivity : BaseActivity(), Logging {
}
private fun initToolbar() {
- val toolbar =
- findViewById(R.id.toolbar) as Toolbar
+ val toolbar = binding.toolbar as Toolbar
setSupportActionBar(toolbar)
supportActionBar?.setDisplayShowTitleEnabled(false)
}
@@ -416,6 +415,7 @@ class MainActivity : BaseActivity(), Logging {
/** Show an alert that may contain HTML */
private fun showAlert(titleText: Int, messageText: Int) {
+
// make links clickable per https://stackoverflow.com/a/62642807
// val messageStr = getText(messageText)
@@ -476,6 +476,8 @@ class MainActivity : BaseActivity(), Logging {
}
}
}
+ } else if (BluetoothInterface.invalidVersion) {
+ showAlert(R.string.firmware_too_old, R.string.firmware_old)
}
} catch (ex: RemoteException) {
warn("Abandoning connect $ex, because we probably just lost device connection")
@@ -493,11 +495,7 @@ class MainActivity : BaseActivity(), Logging {
private fun showSnackbar(msgId: Int) {
try {
- Snackbar.make(
- findViewById(android.R.id.content),
- msgId,
- Snackbar.LENGTH_LONG
- ).show()
+ Snackbar.make(binding.root, msgId, Snackbar.LENGTH_LONG).show()
} catch (ex: IllegalStateException) {
errormsg("Snackbar couldn't find view for msgId $msgId")
}
@@ -505,11 +503,7 @@ class MainActivity : BaseActivity(), Logging {
private fun showSnackbar(msg: String) {
try {
- Snackbar.make(
- findViewById(android.R.id.content),
- msg,
- Snackbar.LENGTH_INDEFINITE
- )
+ Snackbar.make(binding.root, msg, Snackbar.LENGTH_INDEFINITE)
.apply { view.findViewById(R.id.snackbar_text).isSingleLine = false }
.setAction(R.string.okay) {
// dismiss
diff --git a/app/src/main/java/com/geeksville/mesh/model/Channel.kt b/app/src/main/java/com/geeksville/mesh/model/Channel.kt
index 5b9077aca..6be86b586 100644
--- a/app/src/main/java/com/geeksville/mesh/model/Channel.kt
+++ b/app/src/main/java/com/geeksville/mesh/model/Channel.kt
@@ -29,7 +29,8 @@ data class Channel(
// The default channel that devices ship with
val default = Channel(
channelSettings { psk = ByteString.copyFrom(defaultPSK) },
- loRaConfig { usePreset = true; modemPreset = ModemPreset.LONG_FAST }
+ // reference: NodeDB::installDefaultConfig
+ loRaConfig { txEnabled = true; modemPreset = ModemPreset.LONG_FAST; hopLimit = 3 }
)
}
diff --git a/app/src/main/java/com/geeksville/mesh/model/ChannelSet.kt b/app/src/main/java/com/geeksville/mesh/model/ChannelSet.kt
index 9789bdb13..3d6a6c356 100644
--- a/app/src/main/java/com/geeksville/mesh/model/ChannelSet.kt
+++ b/app/src/main/java/com/geeksville/mesh/model/ChannelSet.kt
@@ -15,7 +15,7 @@ data class ChannelSet(
) : Logging {
companion object {
- const val prefix = "https://www.meshtastic.org/e/#"
+ const val prefix = "https://meshtastic.org/e/#"
private const val base64Flags = Base64.URL_SAFE + Base64.NO_WRAP + Base64.NO_PADDING
@@ -65,7 +65,7 @@ data class ChannelSet(
// We encode as UPPER case for the QR code URL because QR codes are more efficient for that special case
val bitMatrix =
multiFormatWriter.encode(
- getChannelUrl(true).toString(),
+ getChannelUrl(false).toString(),
BarcodeFormat.QR_CODE,
960,
960
diff --git a/app/src/main/java/com/geeksville/mesh/model/UIState.kt b/app/src/main/java/com/geeksville/mesh/model/UIState.kt
index 2404ff116..b9862b3d8 100644
--- a/app/src/main/java/com/geeksville/mesh/model/UIState.kt
+++ b/app/src/main/java/com/geeksville/mesh/model/UIState.kt
@@ -44,8 +44,9 @@ import java.io.BufferedWriter
import java.io.FileNotFoundException
import java.io.FileWriter
import java.text.SimpleDateFormat
-import java.util.*
+import java.util.Locale
import javax.inject.Inject
+import kotlin.math.max
import kotlin.math.roundToInt
/// Given a human name, strip out the first letter of the first three words and return that as the initials for
@@ -94,7 +95,6 @@ class UIViewModel @Inject constructor(
private val _channels = MutableStateFlow(ChannelSet())
val channels: StateFlow = _channels
- val channelSet get() = channels.value.protobuf
private val _quickChatActions = MutableStateFlow>(emptyList())
val quickChatActions: StateFlow> = _quickChatActions
@@ -271,48 +271,76 @@ class UIViewModel @Inject constructor(
inline fun updateDeviceConfig(crossinline body: (Config.DeviceConfig) -> Config.DeviceConfig) {
val data = body(config.device)
- setDeviceConfig(config { device = data })
+ setConfig(config { device = data })
}
inline fun updatePositionConfig(crossinline body: (Config.PositionConfig) -> Config.PositionConfig) {
val data = body(config.position)
- setDeviceConfig(config { position = data })
+ setConfig(config { position = data })
}
inline fun updatePowerConfig(crossinline body: (Config.PowerConfig) -> Config.PowerConfig) {
val data = body(config.power)
- setDeviceConfig(config { power = data })
+ setConfig(config { power = data })
}
inline fun updateNetworkConfig(crossinline body: (Config.NetworkConfig) -> Config.NetworkConfig) {
val data = body(config.network)
- setDeviceConfig(config { network = data })
+ setConfig(config { network = data })
}
inline fun updateDisplayConfig(crossinline body: (Config.DisplayConfig) -> Config.DisplayConfig) {
val data = body(config.display)
- setDeviceConfig(config { display = data })
+ setConfig(config { display = data })
}
inline fun updateLoraConfig(crossinline body: (Config.LoRaConfig) -> Config.LoRaConfig) {
val data = body(config.lora)
- setDeviceConfig(config { lora = data })
+ setConfig(config { lora = data })
}
inline fun updateBluetoothConfig(crossinline body: (Config.BluetoothConfig) -> Config.BluetoothConfig) {
val data = body(config.bluetooth)
- setDeviceConfig(config { bluetooth = data })
+ setConfig(config { bluetooth = data })
}
// Set the radio config (also updates our saved copy in preferences)
- fun setDeviceConfig(config: Config) {
- meshService?.deviceConfig = config.toByteArray()
+ fun setConfig(config: Config) {
+ meshService?.setConfig(config.toByteArray())
}
+ /// Convert the channels array to and from [AppOnlyProtos.ChannelSet]
+ private var _channelSet: AppOnlyProtos.ChannelSet
+ get() = channels.value.protobuf
+ set(value) {
+ (0 until max(_channelSet.settingsCount, value.settingsCount)).map { i ->
+ channel {
+ role = when (i) {
+ 0 -> ChannelProtos.Channel.Role.PRIMARY
+ in 1 until value.settingsCount -> ChannelProtos.Channel.Role.SECONDARY
+ else -> ChannelProtos.Channel.Role.DISABLED
+ }
+ index = i
+ settings = value.settingsList.getOrNull(i) ?: channelSettings { }
+ }
+ }.forEach {
+ meshService?.setChannel(it.toByteArray())
+ }
+
+ viewModelScope.launch {
+ channelSetRepository.clearSettings()
+ channelSetRepository.addAllSettings(value)
+ }
+
+ val newConfig = config { lora = value.loraConfig }
+ if (config.lora != newConfig.lora) setConfig(newConfig)
+ }
+ val channelSet get() = _channelSet
+
/// Set the radio config (also updates our saved copy in preferences)
- fun setChannels(c: ChannelSet) {
+ fun setChannels(channelSet: ChannelSet) {
debug("Setting new channels!")
- meshService?.channels = c.protobuf.toByteArray()
+ this._channelSet = channelSet.protobuf
}
/// our name in hte radio
@@ -356,7 +384,8 @@ class UIViewModel @Inject constructor(
}
}
- val adminChannelIndex: Int get() = channelSet.settingsList.map { it.name }.indexOf("admin")
+ val adminChannelIndex: Int
+ get() = channelSet.settingsList.map { it.name.lowercase() }.indexOf("admin")
fun requestShutdown(idNum: Int) {
try {
diff --git a/app/src/main/java/com/geeksville/mesh/repository/datastore/ChannelSetRepository.kt b/app/src/main/java/com/geeksville/mesh/repository/datastore/ChannelSetRepository.kt
index 254383732..bcb1f7f11 100644
--- a/app/src/main/java/com/geeksville/mesh/repository/datastore/ChannelSetRepository.kt
+++ b/app/src/main/java/com/geeksville/mesh/repository/datastore/ChannelSetRepository.kt
@@ -42,7 +42,7 @@ class ChannelSetRepository @Inject constructor(
suspend fun addSettings(channel: ChannelProtos.Channel) {
channelSetStore.updateData { preference ->
- preference.toBuilder().addSettings(channel.index, channel.settings).build()
+ preference.toBuilder().addSettings(channel.settings).build()
}
}
diff --git a/app/src/main/java/com/geeksville/mesh/repository/radio/BluetoothInterface.kt b/app/src/main/java/com/geeksville/mesh/repository/radio/BluetoothInterface.kt
index 00ca34e61..3c843a9c7 100644
--- a/app/src/main/java/com/geeksville/mesh/repository/radio/BluetoothInterface.kt
+++ b/app/src/main/java/com/geeksville/mesh/repository/radio/BluetoothInterface.kt
@@ -99,8 +99,12 @@ class BluetoothInterface(
/// this service UUID is publically visible for scanning
val BTM_SERVICE_UUID: UUID = UUID.fromString("6ba1b218-15a8-461f-9fa8-5dcae273eafd")
- val BTM_FROMRADIO_CHARACTER: UUID =
+ var invalidVersion = false
+ val EOL_FROMRADIO_CHARACTER: UUID =
UUID.fromString("8ba2bcc2-ee02-4a55-a531-c525c5e454d5")
+
+ val BTM_FROMRADIO_CHARACTER: UUID =
+ UUID.fromString("2c55e69e-4993-11ed-b878-0242ac120002")
val BTM_TORADIO_CHARACTER: UUID =
UUID.fromString("f75c76d2-129e-4dad-a1dd-7866124401e7")
val BTM_FROMNUM_CHARACTER: UUID =
@@ -149,6 +153,7 @@ class BluetoothInterface(
?: throw RadioNotConnectedException("BLE service not found")
private lateinit var fromNum: BluetoothGattCharacteristic
+ private lateinit var fromRadio: BluetoothGattCharacteristic
/**
* With the new rev2 api, our first send is to start the configure readbacks. In that case,
@@ -228,7 +233,6 @@ class BluetoothInterface(
/// Attempt to read from the fromRadio mailbox, if data is found broadcast it to android apps
private fun doReadFromRadio(firstRead: Boolean) {
safe?.let { s ->
- val fromRadio = getCharacteristic(BTM_FROMRADIO_CHARACTER)
s.asyncReadCharacteristic(fromRadio) {
try {
val b = it.getOrThrow()
@@ -357,6 +361,16 @@ class BluetoothInterface(
fromNum = getCharacteristic(BTM_FROMNUM_CHARACTER)
+ // We changed UUIDs to be able to identify old firmware (<1.3.43)
+ fromRadio = if (bservice.characteristics.map { it.uuid }
+ .contains(EOL_FROMRADIO_CHARACTER)) {
+ invalidVersion = true
+ getCharacteristic(EOL_FROMRADIO_CHARACTER)
+ } else {
+ invalidVersion = false
+ getCharacteristic(BTM_FROMRADIO_CHARACTER)
+ }
+
// We treat the first send by a client as special
isFirstSend = true
diff --git a/app/src/main/java/com/geeksville/mesh/service/MeshService.kt b/app/src/main/java/com/geeksville/mesh/service/MeshService.kt
index d91466143..ffcf1dbad 100644
--- a/app/src/main/java/com/geeksville/mesh/service/MeshService.kt
+++ b/app/src/main/java/com/geeksville/mesh/service/MeshService.kt
@@ -94,9 +94,6 @@ class MeshService : Service(), Logging {
class NodeNumNotFoundException(id: Int) : NodeNotFoundException("NodeNum not found $id")
class IdNotFoundException(id: String) : NodeNotFoundException("ID not found $id")
- class NoDeviceConfigException(message: String = "No radio settings received (is our app too old?)") :
- RadioNotConnectedException(message)
-
/** We treat software update as similar to loss of comms to the regular bluetooth service (so things like sendPosition for background GPS ignores the problem */
class IsUpdatingException :
RadioNotConnectedException("Operation prohibited during firmware update")
@@ -117,7 +114,7 @@ class MeshService : Service(), Logging {
/** The minimmum firmware version we know how to talk to. We'll still be able to talk to 1.0 firmwares but only well enough to ask them to firmware update
*/
- val minDeviceVersion = DeviceVersion("1.3.41")
+ val minDeviceVersion = DeviceVersion("1.3.43")
}
enum class ConnectionState {
@@ -466,36 +463,6 @@ class MeshService : Service(), Logging {
/// Admin channel index
private var adminChannelIndex: Int = 0
- /// Convert the channels array into a ChannelSet
- private var channelSet: AppOnlyProtos.ChannelSet
- get() {
- // this is never called
- return AppOnlyProtos.ChannelSet.getDefaultInstance()
- }
- set(value) {
- val asChannels = value.settingsList.mapIndexed { i, c ->
- ChannelProtos.Channel.newBuilder().apply {
- role =
- if (i == 0) ChannelProtos.Channel.Role.PRIMARY else ChannelProtos.Channel.Role.SECONDARY
- index = i
- settings = c
- }.build()
- }
-
- debug("Sending channels to device")
- asChannels.forEach {
- setChannel(it)
- }
-
- serviceScope.handledLaunch {
- channelSetRepository.clearSettings()
- channelSetRepository.addAllSettings(value)
- }
-
- val newConfig = config { lora = value.loraConfig }
- if (localConfig.lora != newConfig.lora) sendDeviceConfig(newConfig)
- }
-
/// Generate a new mesh packet builder with our node as the sender, and the specified node num
private fun newMeshPacketTo(idNum: Int) = MeshPacket.newBuilder().apply {
if (myNodeInfo == null)
@@ -745,28 +712,8 @@ class MeshService : Service(), Logging {
val ch = a.getChannelResponse
debug("Admin: Received channel ${ch.index}")
- val packetToSave = MeshLog(
- UUID.randomUUID().toString(),
- "Channel",
- System.currentTimeMillis(),
- ch.toString()
- )
- insertMeshLog(packetToSave)
-
if (ch.index + 1 < mi.maxChannels) {
-
- // Stop once we get to the first disabled entry
- if (/* ch.hasSettings() || */ ch.role != ChannelProtos.Channel.Role.DISABLED) {
- // Not done yet, add new entries and request next channel
- addChannelSettings(ch)
- requestChannel(ch.index + 1)
- } else {
- debug("We've received the last channel, allowing rest of app to start...")
- onHasSettings()
- }
- } else {
- debug("Received max channels, starting app")
- onHasSettings()
+ handleChannel(ch)
}
}
}
@@ -963,7 +910,7 @@ class MeshService : Service(), Logging {
}
private fun addChannelSettings(ch: ChannelProtos.Channel) {
- if (ch.index == 0 || ch.settings.name == "admin") adminChannelIndex = ch.index
+ if (ch.index == 0 || ch.settings.name.lowercase() == "admin") adminChannelIndex = ch.index
serviceScope.handledLaunch {
channelSetRepository.addSettings(ch)
}
@@ -1143,6 +1090,7 @@ class MeshService : Service(), Logging {
MeshProtos.FromRadio.CONFIG_COMPLETE_ID_FIELD_NUMBER -> handleConfigComplete(proto.configCompleteId)
MeshProtos.FromRadio.MY_INFO_FIELD_NUMBER -> handleMyInfo(proto.myInfo)
MeshProtos.FromRadio.NODE_INFO_FIELD_NUMBER -> handleNodeInfo(proto.nodeInfo)
+ MeshProtos.FromRadio.CHANNEL_FIELD_NUMBER -> handleChannel(proto.channel)
MeshProtos.FromRadio.CONFIG_FIELD_NUMBER -> handleDeviceConfig(proto.config)
MeshProtos.FromRadio.MODULECONFIG_FIELD_NUMBER -> handleModuleConfig(proto.moduleConfig)
else -> errormsg("Unexpected FromRadio variant")
@@ -1185,6 +1133,18 @@ class MeshService : Service(), Logging {
// setModuleConfig(config)
}
+ private fun handleChannel(ch: ChannelProtos.Channel) {
+ debug("Received channel ${ch.index}")
+ val packetToSave = MeshLog(
+ UUID.randomUUID().toString(),
+ "Channel",
+ System.currentTimeMillis(),
+ ch.toString()
+ )
+ insertMeshLog(packetToSave)
+ if (ch.role != ChannelProtos.Channel.Role.DISABLED) addChannelSettings(ch)
+ }
+
/**
* Convert a protobuf NodeInfo into our model objects and update our node DB
*/
@@ -1361,8 +1321,8 @@ class MeshService : Service(), Logging {
if (deviceVersion < minDeviceVersion || appVersion < minAppVersion) {
info("Device firmware or app is too old, faking config so firmware update can occur")
clearLocalConfig()
- onHasSettings()
- } else requestChannel(0) // Now start reading channels
+ }
+ onHasSettings()
}
} else
warn("Ignoring stale config complete")
@@ -1387,7 +1347,7 @@ class MeshService : Service(), Logging {
}
private fun setChannel(ch: ChannelProtos.Channel) {
- if (ch.index == 0 || ch.settings.name == "admin") adminChannelIndex = ch.index
+ if (ch.index == 0 || ch.settings.name.lowercase() == "admin") adminChannelIndex = ch.index
sendToRadio(newMeshPacketTo(myNodeNum).buildAdminPacket(wantResponse = true) {
setChannel = ch
})
@@ -1424,6 +1384,9 @@ class MeshService : Service(), Logging {
configNonce += 1
newNodes.clear()
newMyNodeInfo = null
+
+ if (BluetoothInterface.invalidVersion) onHasSettings() // Device firmware is too old
+
debug("Starting config nonce=$configNonce")
sendToRadio(ToRadio.newBuilder().apply {
@@ -1475,15 +1438,13 @@ class MeshService : Service(), Logging {
/** Send our current radio config to the device
*/
- private fun sendDeviceConfig(c: ConfigProtos.Config) {
+ private fun setConfig(config: ConfigProtos.Config) {
if (deviceVersion < minDeviceVersion) return
debug("Setting new radio config!")
sendToRadio(newMeshPacketTo(myNodeNum).buildAdminPacket {
- setConfig = c
+ setConfig = config
})
-
- // Update our cached copy
- setLocalConfig(c)
+ setLocalConfig(config) // Update our cached copy
}
/**
@@ -1698,23 +1659,14 @@ class MeshService : Service(), Logging {
}
}
- override fun getDeviceConfig(): ByteArray = toRemoteExceptions {
- this@MeshService.localConfig.toByteArray()
- ?: throw NoDeviceConfigException()
- }
-
- override fun setDeviceConfig(payload: ByteArray) = toRemoteExceptions {
+ override fun setConfig(payload: ByteArray) = toRemoteExceptions {
val parsed = ConfigProtos.Config.parseFrom(payload)
- sendDeviceConfig(parsed)
+ setConfig(parsed)
}
- override fun getChannels(): ByteArray = toRemoteExceptions {
- channelSet.toByteArray()
- }
-
- override fun setChannels(payload: ByteArray?) = toRemoteExceptions {
- val parsed = AppOnlyProtos.ChannelSet.parseFrom(payload)
- channelSet = parsed
+ override fun setChannel(payload: ByteArray?) = toRemoteExceptions {
+ val parsed = ChannelProtos.Channel.parseFrom(payload)
+ setChannel(parsed)
}
override fun getNodes(): MutableList = toRemoteExceptions {
diff --git a/app/src/main/java/com/geeksville/mesh/ui/ChannelFragment.kt b/app/src/main/java/com/geeksville/mesh/ui/ChannelFragment.kt
index 25ad2d77b..5684be78a 100644
--- a/app/src/main/java/com/geeksville/mesh/ui/ChannelFragment.kt
+++ b/app/src/main/java/com/geeksville/mesh/ui/ChannelFragment.kt
@@ -313,11 +313,12 @@ class ChannelFragment : ScreenFragment("Channel"), Logging {
}
// No matter what apply the speed selection from the user
- val newLoRaConfig = loRaConfig {
- region = model.region
- txEnabled = model.txEnabled
+ val newLoRaConfig = model.config.lora.copy {
usePreset = true
modemPreset = newModemPreset
+ bandwidth = 0
+ spreadFactor = 0
+ codingRate = 0
}
val humanName = Channel(newSettings, newLoRaConfig).humanName
diff --git a/app/src/main/java/com/geeksville/mesh/ui/MapFragment.kt b/app/src/main/java/com/geeksville/mesh/ui/MapFragment.kt
index e3d334fb6..db27d2805 100644
--- a/app/src/main/java/com/geeksville/mesh/ui/MapFragment.kt
+++ b/app/src/main/java/com/geeksville/mesh/ui/MapFragment.kt
@@ -26,7 +26,6 @@ import com.geeksville.mesh.util.SqlTileWriterExt
import com.geeksville.mesh.util.SqlTileWriterExt.SourceCount
import com.geeksville.mesh.util.formatAgo
import com.google.android.material.dialog.MaterialAlertDialogBuilder
-import com.google.android.material.floatingactionbutton.FloatingActionButton
import dagger.hilt.android.AndroidEntryPoint
import org.osmdroid.api.IMapController
import org.osmdroid.config.Configuration
@@ -57,7 +56,6 @@ class MapFragment : ScreenFragment("Map"), Logging, View.OnClickListener {
// UI Elements
private lateinit var binding: MapViewBinding
private lateinit var map: MapView
- private lateinit var downloadBtn: FloatingActionButton
private lateinit var cacheEstimate: TextView
private lateinit var executeJob: Button
private var downloadPrompt: AlertDialog? = null
@@ -96,8 +94,6 @@ class MapFragment : ScreenFragment("Map"), Logging, View.OnClickListener {
inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?
): View {
binding = MapViewBinding.inflate(inflater)
- downloadBtn = binding.root.findViewById(R.id.downloadButton)
- binding.cacheLayout.visibility = View.GONE
return binding.root
}
@@ -138,7 +134,7 @@ class MapFragment : ScreenFragment("Map"), Logging, View.OnClickListener {
}
zoomToNodes(mapController)
}
- downloadBtn.setOnClickListener(this)
+ binding.downloadButton.setOnClickListener(this)
}
override fun onClick(v: View) {
@@ -419,9 +415,9 @@ class MapFragment : ScreenFragment("Map"), Logging, View.OnClickListener {
private fun renderDownloadButton() {
if (!(map.tileProvider.tileSource as OnlineTileSourceBase).tileSourcePolicy.acceptsBulkDownload()) {
- downloadBtn.hide()
+ binding.downloadButton.hide()
} else {
- downloadBtn.show()
+ binding.downloadButton.show()
}
}
diff --git a/app/src/main/java/com/geeksville/mesh/ui/SettingsFragment.kt b/app/src/main/java/com/geeksville/mesh/ui/SettingsFragment.kt
index 3c9bae206..4f37591da 100644
--- a/app/src/main/java/com/geeksville/mesh/ui/SettingsFragment.kt
+++ b/app/src/main/java/com/geeksville/mesh/ui/SettingsFragment.kt
@@ -299,14 +299,15 @@ class SettingsFragment : ScreenFragment("Settings"), Logging {
model.localConfig.asLiveData().observe(viewLifecycleOwner) {
if (!model.isConnected()) {
val configCount = it.allFields.size
- binding.scanStatusText.text = "Device config ($configCount / 7)"
+ if (configCount > 0)
+ binding.scanStatusText.text = "Device config ($configCount / 7)"
} else updateNodeInfo()
}
model.channels.asLiveData().observe(viewLifecycleOwner) {
- if (!model.isConnected()) {
- val channelCount = it.protobuf.settingsCount
- if (channelCount > 0) binding.scanStatusText.text = "Channels ($channelCount / 8)"
+ if (!model.isConnected()) it.protobuf.let { ch ->
+ if (!ch.hasLoraConfig() && ch.settingsCount > 0)
+ binding.scanStatusText.text = "Channels (${ch.settingsCount} / 8)"
}
}
diff --git a/app/src/main/proto b/app/src/main/proto
index d3dfaa63a..c85caacf3 160000
--- a/app/src/main/proto
+++ b/app/src/main/proto
@@ -1 +1 @@
-Subproject commit d3dfaa63a5108c1da7571cd780efaf561b99cc74
+Subproject commit c85caacf3c92717ad5547927c784cbe527ee1d74
diff --git a/app/src/main/res/layout/map_view.xml b/app/src/main/res/layout/map_view.xml
index 85597ab6d..96d4a9543 100644
--- a/app/src/main/res/layout/map_view.xml
+++ b/app/src/main/res/layout/map_view.xml
@@ -28,6 +28,7 @@
android:id="@+id/cache_layout"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
+ android:visibility="gone"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent">
diff --git a/app/src/test/java/com/geeksville/mesh/model/ChannelSetTest.kt b/app/src/test/java/com/geeksville/mesh/model/ChannelSetTest.kt
index 524b23ff0..0613d0478 100644
--- a/app/src/test/java/com/geeksville/mesh/model/ChannelSetTest.kt
+++ b/app/src/test/java/com/geeksville/mesh/model/ChannelSetTest.kt
@@ -8,10 +8,10 @@ class ChannelSetTest {
/** make sure we match the python and device code behavior */
@Test
fun matchPython() {
- val url = Uri.parse("https://www.meshtastic.org/e/#CgUYAiIBAQ")
+ val url = Uri.parse("https://meshtastic.org/e/#CgMSAQESAA")
val cs = ChannelSet(url)
Assert.assertEquals("LongFast", cs.primaryChannel!!.name)
- Assert.assertEquals("#LongFast-K", cs.primaryChannel!!.humanName)
+ Assert.assertEquals("#LongFast-I", cs.primaryChannel!!.humanName)
Assert.assertEquals(url, cs.getChannelUrl(false))
}
}