From 8a384af1d2234c0c55188f55aabb1d174683c29a Mon Sep 17 00:00:00 2001 From: Andre K Date: Tue, 11 Oct 2022 16:25:46 -0300 Subject: [PATCH 01/16] Change UUID for FromRadio characteristic --- .../com/geeksville/mesh/repository/radio/BluetoothInterface.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 00ca34e6..d77dab87 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 @@ -100,7 +100,7 @@ class BluetoothInterface( val BTM_SERVICE_UUID: UUID = UUID.fromString("6ba1b218-15a8-461f-9fa8-5dcae273eafd") val BTM_FROMRADIO_CHARACTER: UUID = - UUID.fromString("8ba2bcc2-ee02-4a55-a531-c525c5e454d5") + UUID.fromString("2c55e69e-4993-11ed-b878-0242ac120002") val BTM_TORADIO_CHARACTER: UUID = UUID.fromString("f75c76d2-129e-4dad-a1dd-7866124401e7") val BTM_FROMNUM_CHARACTER: UUID = From 07ac4f8392cf57db8b71aa26f1f704c8717a3d02 Mon Sep 17 00:00:00 2001 From: andrekir Date: Tue, 11 Oct 2022 19:21:03 -0300 Subject: [PATCH 02/16] bump minDeviceVersion to 1.3.43 --- app/src/main/java/com/geeksville/mesh/service/MeshService.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 d9146614..560c65a2 100644 --- a/app/src/main/java/com/geeksville/mesh/service/MeshService.kt +++ b/app/src/main/java/com/geeksville/mesh/service/MeshService.kt @@ -117,7 +117,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 { From 88b94bd018c4eeea89ce3d0d67365a305392e635 Mon Sep 17 00:00:00 2001 From: andrekir Date: Tue, 11 Oct 2022 16:27:36 -0300 Subject: [PATCH 03/16] move ChannelSet out of service --- .../com/geeksville/mesh/IMeshService.aidl | 16 ++--- .../java/com/geeksville/mesh/model/UIState.kt | 51 ++++++++++++---- .../geeksville/mesh/service/MeshService.kt | 60 +++---------------- .../com/geeksville/mesh/ui/ChannelFragment.kt | 5 +- 4 files changed, 54 insertions(+), 78 deletions(-) diff --git a/app/src/main/aidl/com/geeksville/mesh/IMeshService.aidl b/app/src/main/aidl/com/geeksville/mesh/IMeshService.aidl index 329f3715..55206fcc 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/model/UIState.kt b/app/src/main/java/com/geeksville/mesh/model/UIState.kt index 2404ff11..9886ecf2 100644 --- a/app/src/main/java/com/geeksville/mesh/model/UIState.kt +++ b/app/src/main/java/com/geeksville/mesh/model/UIState.kt @@ -94,7 +94,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 +270,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) { + val asChannels = value.settingsList.mapIndexed { i, c -> + channel { + role = if (i == 0) ChannelProtos.Channel.Role.PRIMARY + else ChannelProtos.Channel.Role.SECONDARY + index = i + settings = c + } + } + + debug("Sending channels to device") + asChannels.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 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 d9146614..1f2bcd21 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") @@ -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) @@ -1475,15 +1442,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 +1663,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 25ad2d77..a04cbc06 100644 --- a/app/src/main/java/com/geeksville/mesh/ui/ChannelFragment.kt +++ b/app/src/main/java/com/geeksville/mesh/ui/ChannelFragment.kt @@ -314,10 +314,11 @@ class ChannelFragment : ScreenFragment("Channel"), Logging { // No matter what apply the speed selection from the user val newLoRaConfig = loRaConfig { - region = model.region - txEnabled = model.txEnabled usePreset = true modemPreset = newModemPreset + region = model.region + txEnabled = model.txEnabled + txPower = model.config.lora.txPower } val humanName = Channel(newSettings, newLoRaConfig).humanName From 22671a73db7c4fe8d257610ef468fbbd0fd890a4 Mon Sep 17 00:00:00 2001 From: andrekir Date: Wed, 12 Oct 2022 23:40:54 -0300 Subject: [PATCH 04/16] detect UUID to warn firmware is too old --- .../main/java/com/geeksville/mesh/MainActivity.kt | 4 ++++ .../mesh/repository/radio/BluetoothInterface.kt | 13 ++++++++++++- .../java/com/geeksville/mesh/service/MeshService.kt | 3 +++ 3 files changed, 19 insertions(+), 1 deletion(-) diff --git a/app/src/main/java/com/geeksville/mesh/MainActivity.kt b/app/src/main/java/com/geeksville/mesh/MainActivity.kt index 0ec7b571..8d728cd3 100644 --- a/app/src/main/java/com/geeksville/mesh/MainActivity.kt +++ b/app/src/main/java/com/geeksville/mesh/MainActivity.kt @@ -40,6 +40,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.* @@ -416,6 +417,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 +478,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") 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 d77dab87..9d57100c 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,6 +99,10 @@ class BluetoothInterface( /// this service UUID is publically visible for scanning val BTM_SERVICE_UUID: UUID = UUID.fromString("6ba1b218-15a8-461f-9fa8-5dcae273eafd") + 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 = @@ -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,13 @@ 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 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 dbb34d82..66de2ef5 100644 --- a/app/src/main/java/com/geeksville/mesh/service/MeshService.kt +++ b/app/src/main/java/com/geeksville/mesh/service/MeshService.kt @@ -1391,6 +1391,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 { From 868a0f7bd31b05b7ce898acc0dc58d81b7f63d17 Mon Sep 17 00:00:00 2001 From: andrekir Date: Thu, 13 Oct 2022 00:02:00 -0300 Subject: [PATCH 05/16] reset invalidVersion just in case --- .../geeksville/mesh/repository/radio/BluetoothInterface.kt | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) 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 9d57100c..3c843a9c 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 @@ -366,7 +366,10 @@ class BluetoothInterface( .contains(EOL_FROMRADIO_CHARACTER)) { invalidVersion = true getCharacteristic(EOL_FROMRADIO_CHARACTER) - } else getCharacteristic(BTM_FROMRADIO_CHARACTER) + } else { + invalidVersion = false + getCharacteristic(BTM_FROMRADIO_CHARACTER) + } // We treat the first send by a client as special isFirstSend = true From 2e4cae048f3ce71e951175a981d80738d8094daf Mon Sep 17 00:00:00 2001 From: andrekir Date: Thu, 13 Oct 2022 00:18:19 -0300 Subject: [PATCH 06/16] update QR code convention --- app/src/main/AndroidManifest.xml | 6 +++--- app/src/main/java/com/geeksville/mesh/model/ChannelSet.kt | 4 ++-- .../test/java/com/geeksville/mesh/model/ChannelSetTest.kt | 2 +- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 535d6953..bbd20202 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/java/com/geeksville/mesh/model/ChannelSet.kt b/app/src/main/java/com/geeksville/mesh/model/ChannelSet.kt index 9789bdb1..3d6a6c35 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/test/java/com/geeksville/mesh/model/ChannelSetTest.kt b/app/src/test/java/com/geeksville/mesh/model/ChannelSetTest.kt index 524b23ff..a70c0bbd 100644 --- a/app/src/test/java/com/geeksville/mesh/model/ChannelSetTest.kt +++ b/app/src/test/java/com/geeksville/mesh/model/ChannelSetTest.kt @@ -8,7 +8,7 @@ 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/#CgUYAiIBAQ") val cs = ChannelSet(url) Assert.assertEquals("LongFast", cs.primaryChannel!!.name) Assert.assertEquals("#LongFast-K", cs.primaryChannel!!.humanName) From 003da7c37ac6bd1d055f70454ba8de72770307b5 Mon Sep 17 00:00:00 2001 From: andrekir Date: Thu, 13 Oct 2022 00:29:22 -0300 Subject: [PATCH 07/16] 1.3.43 --- app/build.gradle | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/build.gradle b/app/build.gradle index 49e8b7a9..9f8ea339 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 20343 // format is Mmmss (where M is 1+the numeric major number + versionName "1.3.43" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" // per https://developer.android.com/studio/write/vector-asset-studio From f310bb20197db22718975934786b69c0fbc57bdb Mon Sep 17 00:00:00 2001 From: andrekir Date: Thu, 13 Oct 2022 18:20:51 -0300 Subject: [PATCH 08/16] change ModemPreset and don't reset LoRaConfig --- .../main/java/com/geeksville/mesh/ui/ChannelFragment.kt | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) 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 a04cbc06..5684be78 100644 --- a/app/src/main/java/com/geeksville/mesh/ui/ChannelFragment.kt +++ b/app/src/main/java/com/geeksville/mesh/ui/ChannelFragment.kt @@ -313,12 +313,12 @@ class ChannelFragment : ScreenFragment("Channel"), Logging { } // No matter what apply the speed selection from the user - val newLoRaConfig = loRaConfig { + val newLoRaConfig = model.config.lora.copy { usePreset = true modemPreset = newModemPreset - region = model.region - txEnabled = model.txEnabled - txPower = model.config.lora.txPower + bandwidth = 0 + spreadFactor = 0 + codingRate = 0 } val humanName = Channel(newSettings, newLoRaConfig).humanName From d29c86ee74ad64cbd91013a60cb51b5b5213e69e Mon Sep 17 00:00:00 2001 From: andrekir Date: Sun, 16 Oct 2022 12:36:21 -0300 Subject: [PATCH 09/16] admin channel not case-sensitive --- app/src/main/java/com/geeksville/mesh/model/UIState.kt | 3 ++- app/src/main/java/com/geeksville/mesh/service/MeshService.kt | 4 ++-- 2 files changed, 4 insertions(+), 3 deletions(-) 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 9886ecf2..d1505c69 100644 --- a/app/src/main/java/com/geeksville/mesh/model/UIState.kt +++ b/app/src/main/java/com/geeksville/mesh/model/UIState.kt @@ -383,7 +383,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/service/MeshService.kt b/app/src/main/java/com/geeksville/mesh/service/MeshService.kt index 66de2ef5..f2841949 100644 --- a/app/src/main/java/com/geeksville/mesh/service/MeshService.kt +++ b/app/src/main/java/com/geeksville/mesh/service/MeshService.kt @@ -930,7 +930,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) } @@ -1354,7 +1354,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 }) From a3cc422afc56daef6c763a42b5591f6f831c0fe8 Mon Sep 17 00:00:00 2001 From: andrekir Date: Sun, 16 Oct 2022 12:40:05 -0300 Subject: [PATCH 10/16] reset channels no longer in use --- .../java/com/geeksville/mesh/model/UIState.kt | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) 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 d1505c69..b9862b3d 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 @@ -312,17 +313,17 @@ class UIViewModel @Inject constructor( private var _channelSet: AppOnlyProtos.ChannelSet get() = channels.value.protobuf set(value) { - val asChannels = value.settingsList.mapIndexed { i, c -> + (0 until max(_channelSet.settingsCount, value.settingsCount)).map { i -> channel { - role = if (i == 0) ChannelProtos.Channel.Role.PRIMARY - else ChannelProtos.Channel.Role.SECONDARY + 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 = c + settings = value.settingsList.getOrNull(i) ?: channelSettings { } } - } - - debug("Sending channels to device") - asChannels.forEach { + }.forEach { meshService?.setChannel(it.toByteArray()) } From f4007744814dac3bfdc28a054852e91b8189e1f3 Mon Sep 17 00:00:00 2001 From: andrekir Date: Sun, 16 Oct 2022 19:02:27 -0300 Subject: [PATCH 11/16] fix default channel LoRa settings --- app/src/main/java/com/geeksville/mesh/model/Channel.kt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) 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 5b9077ac..6be86b58 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 } ) } From 9d0c637ec1c1a7ee33081fba581381d3f4e0c15e Mon Sep 17 00:00:00 2001 From: andrekir Date: Sun, 16 Oct 2022 19:12:23 -0300 Subject: [PATCH 12/16] updating proto submodule to latest --- app/src/main/proto | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/main/proto b/app/src/main/proto index d3dfaa63..c85caacf 160000 --- a/app/src/main/proto +++ b/app/src/main/proto @@ -1 +1 @@ -Subproject commit d3dfaa63a5108c1da7571cd780efaf561b99cc74 +Subproject commit c85caacf3c92717ad5547927c784cbe527ee1d74 From 6c37b451c3c2001359722568047772ceab5149f4 Mon Sep 17 00:00:00 2001 From: andrekir Date: Sun, 16 Oct 2022 19:16:33 -0300 Subject: [PATCH 13/16] clean up view bindings --- .../java/com/geeksville/mesh/MainActivity.kt | 18 ++++-------------- .../java/com/geeksville/mesh/ui/MapFragment.kt | 10 +++------- app/src/main/res/layout/map_view.xml | 1 + 3 files changed, 8 insertions(+), 21 deletions(-) diff --git a/app/src/main/java/com/geeksville/mesh/MainActivity.kt b/app/src/main/java/com/geeksville/mesh/MainActivity.kt index 8d728cd3..aa02eca0 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 @@ -58,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 /* @@ -303,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) } @@ -497,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") } @@ -509,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/ui/MapFragment.kt b/app/src/main/java/com/geeksville/mesh/ui/MapFragment.kt index 4b44917a..5bb19681 100644 --- a/app/src/main/java/com/geeksville/mesh/ui/MapFragment.kt +++ b/app/src/main/java/com/geeksville/mesh/ui/MapFragment.kt @@ -24,7 +24,6 @@ import com.geeksville.mesh.model.map.CustomOverlayManager import com.geeksville.mesh.model.map.CustomTileSource 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 @@ -54,7 +53,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 @@ -92,8 +90,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 } @@ -134,7 +130,7 @@ class MapFragment : ScreenFragment("Map"), Logging, View.OnClickListener { } zoomToNodes(mapController) } - downloadBtn.setOnClickListener(this) + binding.downloadButton.setOnClickListener(this) } override fun onClick(v: View) { @@ -384,9 +380,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/res/layout/map_view.xml b/app/src/main/res/layout/map_view.xml index 85597ab6..96d4a954 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"> From ce140383bb33f2a9cb2a445fd0d275121550e385 Mon Sep 17 00:00:00 2001 From: andrekir Date: Sun, 16 Oct 2022 19:19:03 -0300 Subject: [PATCH 14/16] install channels from wantConfig --- .../datastore/ChannelSetRepository.kt | 2 +- .../geeksville/mesh/service/MeshService.kt | 39 ++++++++----------- .../geeksville/mesh/ui/SettingsFragment.kt | 9 +++-- 3 files changed, 22 insertions(+), 28 deletions(-) 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 25438373..bcb1f7f1 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/service/MeshService.kt b/app/src/main/java/com/geeksville/mesh/service/MeshService.kt index f2841949..ffcf1dba 100644 --- a/app/src/main/java/com/geeksville/mesh/service/MeshService.kt +++ b/app/src/main/java/com/geeksville/mesh/service/MeshService.kt @@ -712,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) } } } @@ -1110,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") @@ -1152,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 */ @@ -1328,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") 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 3c9bae20..4f37591d 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)" } } From a3aa2a51aa3d81bc42ce0f540ad2fa745f778534 Mon Sep 17 00:00:00 2001 From: andrekir Date: Sun, 16 Oct 2022 19:30:45 -0300 Subject: [PATCH 15/16] 1.3.45 --- app/build.gradle | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/build.gradle b/app/build.gradle index 9f8ea339..cc66f08c 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 20343 // format is Mmmss (where M is 1+the numeric major number - versionName "1.3.43" + 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 From bfb9c9ea56752dabde7fe68fbbf6cf7a5a82fe91 Mon Sep 17 00:00:00 2001 From: andrekir Date: Tue, 18 Oct 2022 06:31:38 -0300 Subject: [PATCH 16/16] fix ChannelSetTest --- app/src/test/java/com/geeksville/mesh/model/ChannelSetTest.kt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) 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 a70c0bbd..0613d047 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://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)) } }