From be7aea1457666fca5d8e8c8b96a3f9bea0258d8f Mon Sep 17 00:00:00 2001 From: Kevin Hester Date: Sat, 20 Mar 2021 01:28:55 +0800 Subject: [PATCH 01/60] update to sdk 30 --- app/build.gradle | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/build.gradle b/app/build.gradle index 5cac80212..49381c9e5 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -30,13 +30,13 @@ android { keyPassword "$meshtasticKeyPassword" } } */ - compileSdkVersion 29 + compileSdkVersion 30 // leave undefined to use version plugin wants // buildToolsVersion "30.0.2" // Note: 30.0.2 doesn't yet work on Github actions CI defaultConfig { applicationId "com.geeksville.mesh" minSdkVersion 21 // The oldest emulator image I have tried is 22 (though 21 probably works) - targetSdkVersion 29 + targetSdkVersion 30 versionCode 20211 // format is Mmmss (where M is 1+the numeric major number versionName "1.2.11" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" From fb07b1dc83719ee32d911f07645d01dcc093db87 Mon Sep 17 00:00:00 2001 From: Kevin Hester Date: Sat, 20 Mar 2021 11:23:31 +0800 Subject: [PATCH 02/60] clean up position sending, also always provide time to the mesh --- .../geeksville/mesh/service/MeshService.kt | 51 +++++++++---------- 1 file changed, 23 insertions(+), 28 deletions(-) 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 b58f90d56..fafc47bf9 100644 --- a/app/src/main/java/com/geeksville/mesh/service/MeshService.kt +++ b/app/src/main/java/com/geeksville/mesh/service/MeshService.kt @@ -942,28 +942,22 @@ class MeshService : Service(), Logging { /// If we just changed our nodedb, we might want to do somethings private fun onNodeDBChanged() { maybeUpdateServiceStatusNotification() - - serviceScope.handledLaunch(Dispatchers.Main) { - setupLocationRequest() - } } - private var locationRequestInterval: Long = 0; private fun setupLocationRequest() { - val desiredInterval: Long = if (myNodeInfo?.hasGPS == true) { - 0L // no requests when device has GPS - } else if (numOnlineNodes < 2) { - 5 * 60 * 1000L // send infrequently, device needs these requests to set its clock + var desiredInterval = 0L + + if (myNodeInfo?.hasGPS == true) + desiredInterval = + radioConfig?.preferences?.positionBroadcastSecs?.times(1000L) ?: 5 * 60 * 1000L + + stopLocationRequests() + if (desiredInterval != 0L) { + debug("desired GPS assistance interval $desiredInterval") + startLocationRequests(desiredInterval) } else { - radioConfig?.preferences?.positionBroadcastSecs?.times(1000L) ?: 5 * 60 * 1000L - } - - debug("desired location request $desiredInterval, current $locationRequestInterval") - - if (desiredInterval != locationRequestInterval) { - if (locationRequestInterval > 0) stopLocationRequests() - if (desiredInterval > 0) startLocationRequests(desiredInterval) - locationRequestInterval = desiredInterval + debug("No GPS assistance desired, but sending UTC time to mesh") + sendPositionScoped() } } @@ -1366,8 +1360,7 @@ class MeshService : Service(), Logging { sendRadioConfig(newConfig.build()) } - } - else + } else warn("Device is too old to understand region changes") } } @@ -1384,6 +1377,8 @@ class MeshService : Service(), Logging { reportConnection() updateRegion() + + setupLocationRequest() // start sending location packets if needed } private fun handleConfigComplete(configCompleteId: Int) { @@ -1462,13 +1457,13 @@ class MeshService : Service(), Logging { * Must be called from serviceScope. Use sendPositionScoped() for direct calls. */ private fun sendPosition( - lat: Double, - lon: Double, - alt: Int, + lat: Double = 0.0, + lon: Double = 0.0, + alt: Int = 0, destNum: Int = DataPacket.NODENUM_BROADCAST, wantResponse: Boolean = false ) { - debug("Sending our position to=$destNum lat=$lat, lon=$lon, alt=$alt") + debug("Sending our position/time to=$destNum lat=$lat, lon=$lon, alt=$alt") val position = MeshProtos.Position.newBuilder().also { it.longitudeI = Position.degI(lon) @@ -1495,15 +1490,15 @@ class MeshService : Service(), Logging { } private fun sendPositionScoped( - lat: Double, - lon: Double, - alt: Int, + lat: Double = 0.0, + lon: Double = 0.0, + alt: Int = 0, destNum: Int = DataPacket.NODENUM_BROADCAST, wantResponse: Boolean = false ) = serviceScope.handledLaunch { try { sendPosition(lat, lon, alt, destNum, wantResponse) - } catch (ex: RadioNotConnectedException) { + } catch (ex: BLEException) { warn("Ignoring disconnected radio during gps location update") } } From c9d18b00a4789d5e91a14e47e63925c9240d292e Mon Sep 17 00:00:00 2001 From: Kevin Hester Date: Sat, 20 Mar 2021 17:47:02 +0800 Subject: [PATCH 03/60] don't let user update firmware without setting a region. --- .../com/geeksville/mesh/IMeshService.aidl | 3 ++ .../java/com/geeksville/mesh/model/UIState.kt | 11 ++--- .../geeksville/mesh/service/MeshService.kt | 48 ++++++++++++------- .../geeksville/mesh/ui/SettingsFragment.kt | 39 +++++++-------- 4 files changed, 53 insertions(+), 48 deletions(-) diff --git a/app/src/main/aidl/com/geeksville/mesh/IMeshService.aidl b/app/src/main/aidl/com/geeksville/mesh/IMeshService.aidl index a9ee757d0..17382d016 100644 --- a/app/src/main/aidl/com/geeksville/mesh/IMeshService.aidl +++ b/app/src/main/aidl/com/geeksville/mesh/IMeshService.aidl @@ -113,4 +113,7 @@ interface IMeshService { Return a number 0-100 for progress. -1 for completed and success, -2 for failure */ int getUpdateStatus(); + + int getRegion(); + void setRegion(int regionCode); } 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 fe4175e9b..6b79ba688 100644 --- a/app/src/main/java/com/geeksville/mesh/model/UIState.kt +++ b/app/src/main/java/com/geeksville/mesh/model/UIState.kt @@ -134,15 +134,10 @@ class UIViewModel(private val app: Application) : AndroidViewModel(app), Logging } } - var region: RadioConfigProtos.RegionCode? - get() = radioConfig.value?.preferences?.region + var region: RadioConfigProtos.RegionCode + get() = meshService?.region?.let { RadioConfigProtos.RegionCode.forNumber(it) } ?: RadioConfigProtos.RegionCode.Unset set(value) { - val config = radioConfig.value - if (value != null && config != null) { - val builder = config.toBuilder() - builder.preferencesBuilder.region = value - setRadioConfig(builder.build()) - } + meshService?.region = value.number } /// hardware info about our local device (can be null) 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 fafc47bf9..2d1749955 100644 --- a/app/src/main/java/com/geeksville/mesh/service/MeshService.kt +++ b/app/src/main/java/com/geeksville/mesh/service/MeshService.kt @@ -1313,6 +1313,28 @@ class MeshService : Service(), Logging { } + private fun setRegionOnDevice() { + val curConfigRegion = + radioConfig?.preferences?.region ?: RadioConfigProtos.RegionCode.Unset + + if (curConfigRegion.number != curRegionValue && curRegionValue != RadioConfigProtos.RegionCode.Unset_VALUE) + if (deviceVersion >= minFirmwareVersion) { + info("Telling device to upgrade region") + + // Tell the device to set the new region field (old devices will simply ignore this) + radioConfig?.let { currentConfig -> + val newConfig = currentConfig.toBuilder() + + val newPrefs = currentConfig.preferences.toBuilder() + newPrefs.regionValue = curRegionValue + newConfig.preferences = newPrefs.build() + + sendRadioConfig(newConfig.build()) + } + } else + warn("Device is too old to understand region changes") + } + /** * If we are updating nodes we might need to use old (fixed by firmware build) * region info to populate our new universal ROMs. @@ -1346,23 +1368,7 @@ class MeshService : Service(), Logging { } // If nothing was set in our (new style radio preferences, but we now have a valid setting - slam it in) - if (curConfigRegion == RadioConfigProtos.RegionCode.Unset && curRegionValue != RadioConfigProtos.RegionCode.Unset_VALUE) { - if (deviceVersion >= minFirmwareVersion) { - info("Telling device to upgrade region") - - // Tell the device to set the new region field (old devices will simply ignore this) - radioConfig?.let { currentConfig -> - val newConfig = currentConfig.toBuilder() - - val newPrefs = currentConfig.preferences.toBuilder() - newPrefs.regionValue = curRegionValue - newConfig.preferences = newPrefs.build() - - sendRadioConfig(newConfig.build()) - } - } else - warn("Device is too old to understand region changes") - } + setRegionOnDevice() } } @@ -1651,7 +1657,7 @@ class MeshService : Service(), Logging { offlineSentPackets.add(p) } - val binder = object : IMeshService.Stub() { + private val binder = object : IMeshService.Stub() { override fun setDeviceAddress(deviceAddr: String?) = toRemoteExceptions { debug("Passing through device change to radio service: ${deviceAddr.anonymize}") @@ -1675,6 +1681,12 @@ class MeshService : Service(), Logging { } override fun getUpdateStatus(): Int = SoftwareUpdateService.progress + override fun getRegion(): Int = curRegionValue + + override fun setRegion(regionCode: Int) = toRemoteExceptions { + curRegionValue = regionCode + setRegionOnDevice() + } override fun startFirmwareUpdate() = toRemoteExceptions { doFirmwareUpdate() 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 fde6466bd..11d552ab3 100644 --- a/app/src/main/java/com/geeksville/mesh/ui/SettingsFragment.kt +++ b/app/src/main/java/com/geeksville/mesh/ui/SettingsFragment.kt @@ -524,7 +524,7 @@ class SettingsFragment : ScreenFragment("Settings"), Logging { } /// Set the correct update button configuration based on current progress - private fun refreshUpdateButton() { + private fun refreshUpdateButton(enable: Boolean) { debug("Reiniting the udpate button") val info = model.myNodeInfo.value val service = model.meshService @@ -535,7 +535,7 @@ class SettingsFragment : ScreenFragment("Settings"), Logging { val progress = service.updateStatus - binding.updateFirmwareButton.isEnabled = + binding.updateFirmwareButton.isEnabled = enable && (progress < 0) // if currently doing an upgrade disable button if (progress >= 0) { @@ -572,7 +572,7 @@ class SettingsFragment : ScreenFragment("Settings"), Logging { val connected = model.isConnected.value val isConnected = connected == MeshService.ConnectionState.CONNECTED - binding.nodeSettings.visibility = if(isConnected) View.VISIBLE else View.GONE + binding.nodeSettings.visibility = if (isConnected) View.VISIBLE else View.GONE if (connected == MeshService.ConnectionState.DISCONNECTED) model.ownerName.value = "" @@ -582,25 +582,19 @@ class SettingsFragment : ScreenFragment("Settings"), Logging { val spinner = binding.regionSpinner val unsetIndex = regions.indexOf(RadioConfigProtos.RegionCode.Unset.name) spinner.onItemSelectedListener = null - if(region != null) { - debug("current region is $region") - var regionIndex = regions.indexOf(region.name) - if(regionIndex == -1) // Not found, probably because the device has a region our app doesn't yet understand. Punt and say Unset - regionIndex = unsetIndex - // We don't want to be notified of our own changes, so turn off listener while making them - spinner.setSelection(regionIndex, false) - spinner.onItemSelectedListener = regionSpinnerListener - spinner.isEnabled = true - } - else { - warn("region is unset!") - spinner.setSelection(unsetIndex, false) - spinner.isEnabled = false // leave disabled, because we can't get our region - } + debug("current region is $region") + var regionIndex = regions.indexOf(region.name) + if (regionIndex == -1) // Not found, probably because the device has a region our app doesn't yet understand. Punt and say Unset + regionIndex = unsetIndex + + // We don't want to be notified of our own changes, so turn off listener while making them + spinner.setSelection(regionIndex, false) + spinner.onItemSelectedListener = regionSpinnerListener + spinner.isEnabled = true // If actively connected possibly let the user update firmware - refreshUpdateButton() + refreshUpdateButton(region != RadioConfigProtos.RegionCode.Unset) // Update the status string (highest priority messages first) val info = model.myNodeInfo.value @@ -620,7 +614,7 @@ class SettingsFragment : ScreenFragment("Settings"), Logging { } } - private val regionSpinnerListener = object : AdapterView.OnItemSelectedListener{ + private val regionSpinnerListener = object : AdapterView.OnItemSelectedListener { override fun onItemSelected( parent: AdapterView<*>, view: View, @@ -652,7 +646,8 @@ class SettingsFragment : ScreenFragment("Settings"), Logging { // init our region spinner val spinner = binding.regionSpinner - val regionAdapter = ArrayAdapter(requireContext(), android.R.layout.simple_spinner_item, regions) + val regionAdapter = + ArrayAdapter(requireContext(), android.R.layout.simple_spinner_item, regions) regionAdapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item) spinner.adapter = regionAdapter @@ -965,7 +960,7 @@ class SettingsFragment : ScreenFragment("Settings"), Logging { private val updateProgressReceiver: BroadcastReceiver = object : BroadcastReceiver() { override fun onReceive(context: Context?, intent: Intent?) { - refreshUpdateButton() + refreshUpdateButton(true) } } From 278d82e99a3193cab7cd4c43ec2d9e0246c6bafd Mon Sep 17 00:00:00 2001 From: Kevin Hester Date: Sat, 20 Mar 2021 18:47:12 +0800 Subject: [PATCH 04/60] cleanly kill job if we get an exception during firmware update --- .../main/java/com/geeksville/mesh/service/MeshService.kt | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) 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 2d1749955..8071413b8 100644 --- a/app/src/main/java/com/geeksville/mesh/service/MeshService.kt +++ b/app/src/main/java/com/geeksville/mesh/service/MeshService.kt @@ -1624,8 +1624,10 @@ class MeshService : Service(), Logging { } else { debug("Creating firmware update coroutine") updateJob = serviceScope.handledLaunch { - debug("Starting firmware update coroutine") - SoftwareUpdateService.doUpdate(this@MeshService, safe, filename) + exceptionReporter { + debug("Starting firmware update coroutine") + SoftwareUpdateService.doUpdate(this@MeshService, safe, filename) + } } } } From 6e545e628b2db6ea682a473b0b9711a9e79b3b71 Mon Sep 17 00:00:00 2001 From: Daniel Nemenyi Date: Mon, 22 Mar 2021 09:14:29 +0000 Subject: [PATCH 05/60] Fix expired link The 'Firmware update required' modal contains an expired link to the Wiki. This commit points the user towards the Firmware Installation section on Github instead. * app/src/main/res/values/strings.xml --- app/src/main/res/values/strings.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 11f071ffd..346dfd579 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -90,7 +90,7 @@ Protocol stress test Advanced settings Firmware update required - The radio firmware is too old to talk to this application, please go to the settings pane and choose "Update Firmware". For more information on this see our wiki. + The radio firmware is too old to talk to this application, please go to the settings pane and choose "Update Firmware". For more information on this see our Firmware Installation guide on Github. Okay You must set a region! Region From 518241c3c9b573bb4cf538b0822336cdc60b28e0 Mon Sep 17 00:00:00 2001 From: Kevin Hester Date: Tue, 23 Mar 2021 13:05:50 +0800 Subject: [PATCH 06/60] less logspam --- .../main/java/com/geeksville/mesh/service/SafeBluetooth.kt | 4 ++-- .../com/geeksville/mesh/service/SoftwareUpdateService.kt | 6 +++++- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/app/src/main/java/com/geeksville/mesh/service/SafeBluetooth.kt b/app/src/main/java/com/geeksville/mesh/service/SafeBluetooth.kt index ee460c9ca..283ed00fc 100644 --- a/app/src/main/java/com/geeksville/mesh/service/SafeBluetooth.kt +++ b/app/src/main/java/com/geeksville/mesh/service/SafeBluetooth.kt @@ -325,7 +325,7 @@ class SafeBluetooth(private val context: Context, private val device: BluetoothD if (newWork.timeoutMillis != 0L) { activeTimeout = serviceScope.launch { - debug("Starting failsafe timer ${newWork.timeoutMillis}") + // debug("Starting failsafe timer ${newWork.timeoutMillis}") delay(newWork.timeoutMillis) errormsg("Failsafe BLE timer expired!") completeWork( @@ -415,7 +415,7 @@ class SafeBluetooth(private val context: Context, private val device: BluetoothD if (work == null) warn("wor completed, but we already killed it via failsafetimer? status=$status, res=$res") else { - debug("work ${work.tag} is completed, resuming status=$status, res=$res") + // debug("work ${work.tag} is completed, resuming status=$status, res=$res") if (status != 0) work.completion.resumeWithException( BLEStatusException( diff --git a/app/src/main/java/com/geeksville/mesh/service/SoftwareUpdateService.kt b/app/src/main/java/com/geeksville/mesh/service/SoftwareUpdateService.kt index b9050105e..663070f85 100644 --- a/app/src/main/java/com/geeksville/mesh/service/SoftwareUpdateService.kt +++ b/app/src/main/java/com/geeksville/mesh/service/SoftwareUpdateService.kt @@ -374,13 +374,17 @@ class SoftwareUpdateService : JobIntentService(), Logging { throw DeviceRejectedException() // Send all the blocks + var oldProgress = -1 // used to limit # of log spam while (firmwareNumSent < firmwareSize) { // If we are doing the spiffs partition, we limit progress to a max of 50%, so that the user doesn't think we are done // yet val maxProgress = if(flashRegion != FLASH_REGION_APPLOAD) 50 else 100 sendProgress(context, firmwareNumSent * maxProgress / firmwareSize, isAppload) - debug("sending block ${progress}%") + if(progress != oldProgress) { + debug("sending block ${progress}%") + oldProgress = progress; + } var blockSize = 512 - 3 // Max size MTU excluding framing if (blockSize > firmwareStream.available()) From 2327c5e693505e9cd978f64644a92e50f4e8998f Mon Sep 17 00:00:00 2001 From: Kevin Hester Date: Tue, 23 Mar 2021 13:07:32 +0800 Subject: [PATCH 07/60] fix autobug while shutting down interface --- .../main/java/com/geeksville/mesh/service/BluetoothInterface.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/main/java/com/geeksville/mesh/service/BluetoothInterface.kt b/app/src/main/java/com/geeksville/mesh/service/BluetoothInterface.kt index a424f8640..59489907c 100644 --- a/app/src/main/java/com/geeksville/mesh/service/BluetoothInterface.kt +++ b/app/src/main/java/com/geeksville/mesh/service/BluetoothInterface.kt @@ -313,7 +313,7 @@ class BluetoothInterface(val service: RadioInterfaceService, val address: String var fromNumChanged = false private fun startWatchingFromNum() { - safe!!.setNotify(fromNum, true) { + safe?.setNotify(fromNum, true) { // We might get multiple notifies before we get around to reading from the radio - so just set one flag fromNumChanged = true debug("fromNum changed") From cea7755aa4c07d7691641f4ab0ca64312d2bba4f Mon Sep 17 00:00:00 2001 From: Kevin Hester Date: Tue, 23 Mar 2021 13:17:36 +0800 Subject: [PATCH 08/60] ignore BLE errors while closing --- .../main/java/com/geeksville/mesh/service/BLEException.kt | 5 ++++- .../java/com/geeksville/mesh/service/BluetoothInterface.kt | 7 ++++++- .../main/java/com/geeksville/mesh/service/SafeBluetooth.kt | 2 +- 3 files changed, 11 insertions(+), 3 deletions(-) diff --git a/app/src/main/java/com/geeksville/mesh/service/BLEException.kt b/app/src/main/java/com/geeksville/mesh/service/BLEException.kt index 4ee38cd1b..7aa2724bf 100644 --- a/app/src/main/java/com/geeksville/mesh/service/BLEException.kt +++ b/app/src/main/java/com/geeksville/mesh/service/BLEException.kt @@ -5,4 +5,7 @@ import java.util.* open class BLEException(msg: String) : IOException(msg) -open class BLECharacteristicNotFoundException(uuid: UUID) : BLEException("Can't get characteristic $uuid") \ No newline at end of file +open class BLECharacteristicNotFoundException(uuid: UUID) : BLEException("Can't get characteristic $uuid") + +/// Our interface is being shut down +open class BLEConnectionClosing() : BLEException("Connection closing ") \ No newline at end of file diff --git a/app/src/main/java/com/geeksville/mesh/service/BluetoothInterface.kt b/app/src/main/java/com/geeksville/mesh/service/BluetoothInterface.kt index 59489907c..1fbfdcad2 100644 --- a/app/src/main/java/com/geeksville/mesh/service/BluetoothInterface.kt +++ b/app/src/main/java/com/geeksville/mesh/service/BluetoothInterface.kt @@ -469,7 +469,12 @@ class BluetoothInterface(val service: RadioInterfaceService, val address: String safe = null // We do this first, because if we throw we still want to mark that we no longer have a valid connection - s?.close() + try { + s?.close() + } + catch(_: BLEConnectionClosing) { + warn("Ignoring BLE errors while closing") + } } else { debug("Radio was not connected, skipping disable") } diff --git a/app/src/main/java/com/geeksville/mesh/service/SafeBluetooth.kt b/app/src/main/java/com/geeksville/mesh/service/SafeBluetooth.kt index 283ed00fc..c77830656 100644 --- a/app/src/main/java/com/geeksville/mesh/service/SafeBluetooth.kt +++ b/app/src/main/java/com/geeksville/mesh/service/SafeBluetooth.kt @@ -773,7 +773,7 @@ class SafeBluetooth(private val context: Context, private val device: BluetoothD closeGatt() - failAllWork(BLEException("Connection closing")) + failAllWork(BLEConnectionClosing()) } /** From dd9a2b99d7bfbfdc48d15ab41aaade4d9680e264 Mon Sep 17 00:00:00 2001 From: Kevin Hester Date: Tue, 23 Mar 2021 13:21:51 +0800 Subject: [PATCH 09/60] fix autobug NPE --- .../java/com/geeksville/mesh/MainActivity.kt | 43 ++++++++++--------- 1 file changed, 22 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 1acff242c..7369dd899 100644 --- a/app/src/main/java/com/geeksville/mesh/MainActivity.kt +++ b/app/src/main/java/com/geeksville/mesh/MainActivity.kt @@ -659,30 +659,32 @@ class MainActivity : AppCompatActivity(), Logging, debug("Getting latest radioconfig from service") try { - val info = service.myNodeInfo + val info: MyNodeInfo? = service.myNodeInfo // this can be null model.myNodeInfo.value = info - val isOld = info.minAppVersion > BuildConfig.VERSION_CODE - if (isOld) - showAlert(R.string.app_too_old, R.string.must_update) - else { - - val curVer = DeviceVersion(info.firmwareVersion ?: "0.0.0") - if (curVer < MeshService.minFirmwareVersion) - showAlert(R.string.firmware_too_old, R.string.firmware_old) + if (info != null) { + val isOld = info.minAppVersion > BuildConfig.VERSION_CODE + if (isOld) + showAlert(R.string.app_too_old, R.string.must_update) else { - // If our app is too old/new, we probably don't understand the new radioconfig messages, so we don't read them until here - model.radioConfig.value = - RadioConfigProtos.RadioConfig.parseFrom(service.radioConfig) + val curVer = DeviceVersion(info.firmwareVersion ?: "0.0.0") + if (curVer < MeshService.minFirmwareVersion) + showAlert(R.string.firmware_too_old, R.string.firmware_old) + else { + // If our app is too old/new, we probably don't understand the new radioconfig messages, so we don't read them until here - model.channels.value = - ChannelSet(AppOnlyProtos.ChannelSet.parseFrom(service.channels)) + model.radioConfig.value = + RadioConfigProtos.RadioConfig.parseFrom(service.radioConfig) - updateNodesFromDevice() + model.channels.value = + ChannelSet(AppOnlyProtos.ChannelSet.parseFrom(service.channels)) - // we have a connection to our device now, do the channel change - perhapsChangeChannel() + updateNodesFromDevice() + + // we have a connection to our device now, do the channel change + perhapsChangeChannel() + } } } } catch (ex: RemoteException) { @@ -968,12 +970,11 @@ class MainActivity : AppCompatActivity(), Logging, try { bindMeshService() - } - catch(ex: BindFailedException) { + } catch (ex: BindFailedException) { // App is probably shutting down, ignore errormsg("Bind of MeshService failed") } - + val bonded = RadioInterfaceService.getBondedDeviceAddress(this) != null if (!bonded && usbDevice == null) // we will handle USB later showSettingsPage() @@ -1100,7 +1101,7 @@ class MainActivity : AppCompatActivity(), Logging, positionToMeter(my_position!!, position).roundToInt() fs.write( ("${packet_proto.from.toUInt().toString(16)}," + - "${packet_proto.rxSnr},${packet_proto.rxTime},$dist\n") + "${packet_proto.rxSnr},${packet_proto.rxTime},$dist\n") .toByteArray() ) } From 12ea70174e51fe3ec6879cdef0eac3dfb6caa2c6 Mon Sep 17 00:00:00 2001 From: Kevin Hester Date: Tue, 23 Mar 2021 13:40:03 +0800 Subject: [PATCH 10/60] fix autobug when changing channels --- .../geeksville/mesh/service/MeshService.kt | 21 +++++++++++++------ 1 file changed, 15 insertions(+), 6 deletions(-) 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 8071413b8..152f78d2e 100644 --- a/app/src/main/java/com/geeksville/mesh/service/MeshService.kt +++ b/app/src/main/java/com/geeksville/mesh/service/MeshService.kt @@ -549,7 +549,7 @@ class MeshService : Service(), Logging { setChannel(it) } - channels = asChannels.toTypedArray() + channels = fixupChannelList(asChannels).toTypedArray() } /// Generate a new mesh packet builder with our node as the sender, and the specified node num @@ -1305,11 +1305,20 @@ class MeshService : Service(), Logging { radioConfig = null // prefill the channel array with null channels - channels = Array(myInfo.maxChannels) { - val b = ChannelProtos.Channel.newBuilder() - b.index = it - b.build() - } + channels = fixupChannelList(listOf()).toTypedArray() + } + + /// scan the channel list and make sure it has one PRIMARY channel and is maxChannels long + private fun fixupChannelList(lIn: List): List { + val mi = myNodeInfo + var l = lIn + if (mi != null) + while (l.size < mi.maxChannels) { + val b = ChannelProtos.Channel.newBuilder() + b.index = l.size + l += b.build() + } + return l } From ad7a189c5174ecf380957aab2bf0955521f05197 Mon Sep 17 00:00:00 2001 From: Kevin Hester Date: Wed, 24 Mar 2021 13:48:26 +0800 Subject: [PATCH 11/60] add default channel button --- .../java/com/geeksville/mesh/model/Channel.kt | 9 ++- .../com/geeksville/mesh/ui/ChannelFragment.kt | 81 ++++++++++++------- .../res/drawable/ic_twotone_public_24.xml | 15 ++++ app/src/main/res/layout/channel_fragment.xml | 33 ++++++-- app/src/main/res/values/strings.xml | 4 + 5 files changed, 105 insertions(+), 37 deletions(-) create mode 100644 app/src/main/res/drawable/ic_twotone_public_24.xml 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 665a8de25..06855b66a 100644 --- a/app/src/main/java/com/geeksville/mesh/model/Channel.kt +++ b/app/src/main/java/com/geeksville/mesh/model/Channel.kt @@ -17,10 +17,15 @@ data class Channel( 0xf0, 0xbc, 0xff, 0xab, 0xcf, 0x4e, 0x69, 0xbf ) + private val cleartextPSK = ByteString.EMPTY + private val defaultPSK = byteArrayOfInts(1) // a shortstring code to indicate we need our default PSK + // TH=he unsecured channel that devices ship with val defaultChannel = Channel( ChannelProtos.ChannelSettings.newBuilder() - .setModemConfig(ChannelProtos.ChannelSettings.ModemConfig.Bw125Cr45Sf128).build() + .setModemConfig(ChannelProtos.ChannelSettings.ModemConfig.Bw125Cr48Sf4096) + .setPsk(ByteString.copyFrom(defaultPSK)) + .build() ) } @@ -50,7 +55,7 @@ data class Channel( val pskIndex = settings.psk.byteAt(0).toInt() if (pskIndex == 0) - ByteString.EMPTY // Treat as an empty PSK (no encryption) + cleartextPSK else { // Treat an index of 1 as the old channelDefaultKey and work up from there val bytes = channelDefaultKey.clone() 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 9ca623088..a8f5457b1 100644 --- a/app/src/main/java/com/geeksville/mesh/ui/ChannelFragment.kt +++ b/app/src/main/java/com/geeksville/mesh/ui/ChannelFragment.kt @@ -18,7 +18,6 @@ import com.geeksville.android.Logging import com.geeksville.android.hideKeyboard import com.geeksville.mesh.AppOnlyProtos import com.geeksville.mesh.ChannelProtos -import com.geeksville.mesh.MeshProtos import com.geeksville.mesh.R import com.geeksville.mesh.databinding.ChannelFragmentBinding import com.geeksville.mesh.model.Channel @@ -50,6 +49,7 @@ fun ImageView.setOpaque() { class ChannelFragment : ScreenFragment("Channel"), Logging { private var _binding: ChannelFragmentBinding? = null + // This property is only valid between onCreateView and onDestroyView. private val binding get() = _binding!! @@ -81,6 +81,12 @@ class ChannelFragment : ScreenFragment("Channel"), Logging { val channels = model.channels.value val channel = channels?.primaryChannel + val connected = model.isConnected.value == MeshService.ConnectionState.CONNECTED + + // Only let buttons work if we are connected to the radio + binding.shareButton.isEnabled = connected + binding.resetButton.isEnabled = connected + binding.editableCheckbox.isChecked = false // start locked if (channel != null) { binding.qrView.visibility = View.VISIBLE @@ -89,7 +95,6 @@ class ChannelFragment : ScreenFragment("Channel"), Logging { // For now, we only let the user edit/save channels while the radio is awake - because the service // doesn't cache radioconfig writes. - val connected = model.isConnected.value == MeshService.ConnectionState.CONNECTED binding.editableCheckbox.isEnabled = connected binding.qrView.setImageBitmap(channels.getChannelQR()) @@ -143,6 +148,28 @@ class ChannelFragment : ScreenFragment("Channel"), Logging { } } + /// Send new channel settings to the device + private fun installSettings(newChannel: ChannelProtos.ChannelSettings) { + val newSet = + ChannelSet(AppOnlyProtos.ChannelSet.newBuilder().addSettings(newChannel).build()) + // Try to change the radio, if it fails, tell the user why and throw away their redits + try { + model.setChannels(newSet) + // Since we are writing to radioconfig, that will trigger the rest of the GUI update (QR code etc) + } catch (ex: RemoteException) { + errormsg("ignoring channel problem", ex) + + setGUIfromModel() // Throw away user edits + + // Tell the user to try again + Snackbar.make( + binding.editableCheckbox, + R.string.radio_sleeping, + Snackbar.LENGTH_SHORT + ).show() + } + } + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) @@ -150,6 +177,21 @@ class ChannelFragment : ScreenFragment("Channel"), Logging { requireActivity().hideKeyboard() } + binding.resetButton.setOnClickListener { _ -> + // User just locked it, we should warn and then apply changes to radio + MaterialAlertDialogBuilder(requireContext()) + .setTitle(R.string.reset_to_defaults) + .setMessage(R.string.are_you_shure_change_default) + .setNeutralButton(R.string.cancel) { _, _ -> + setGUIfromModel() // throw away any edits + } + .setPositiveButton(getString(R.string.accept)) { _, _ -> + debug("Switching back to default channel") + installSettings(Channel.defaultChannel.settings) + } + .show() + } + // Note: Do not use setOnCheckedChanged here because we don't want to be called when we programmatically disable editing binding.editableCheckbox.setOnClickListener { _ -> val checked = binding.editableCheckbox.isChecked @@ -178,41 +220,26 @@ class ChannelFragment : ScreenFragment("Channel"), Logging { ignoreCase = true ) ) { + // Install a new customized channel + debug("ASSIGNING NEW AES256 KEY") val random = SecureRandom() val bytes = ByteArray(32) random.nextBytes(bytes) newSettings.psk = ByteString.copyFrom(bytes) + + val selectedChannelOptionString = + binding.filledExposedDropdown.editableText.toString() + val modemConfig = getModemConfig(selectedChannelOptionString) + + if (modemConfig != ChannelProtos.ChannelSettings.ModemConfig.UNRECOGNIZED) + newSettings.modemConfig = modemConfig } else { debug("Switching back to default channel") newSettings = Channel.defaultChannel.settings.toBuilder() } - val selectedChannelOptionString = - binding.filledExposedDropdown.editableText.toString() - val modemConfig = getModemConfig(selectedChannelOptionString) - - if (modemConfig != ChannelProtos.ChannelSettings.ModemConfig.UNRECOGNIZED) - newSettings.modemConfig = modemConfig - - val newChannel = newSettings.build() - val newSet = ChannelSet(AppOnlyProtos.ChannelSet.newBuilder().addSettings(newChannel).build()) - // Try to change the radio, if it fails, tell the user why and throw away their redits - try { - model.setChannels(newSet) - // Since we are writing to radioconfig, that will trigger the rest of the GUI update (QR code etc) - } catch (ex: RemoteException) { - errormsg("ignoring channel problem", ex) - - setGUIfromModel() // Throw away user edits - - // Tell the user to try again - Snackbar.make( - binding.editableCheckbox, - R.string.radio_sleeping, - Snackbar.LENGTH_SHORT - ).show() - } + installSettings(newSettings.build()) } } .show() diff --git a/app/src/main/res/drawable/ic_twotone_public_24.xml b/app/src/main/res/drawable/ic_twotone_public_24.xml new file mode 100644 index 000000000..403051e61 --- /dev/null +++ b/app/src/main/res/drawable/ic_twotone_public_24.xml @@ -0,0 +1,15 @@ + + + + diff --git a/app/src/main/res/layout/channel_fragment.xml b/app/src/main/res/layout/channel_fragment.xml index 461eafab0..597bcb4bf 100644 --- a/app/src/main/res/layout/channel_fragment.xml +++ b/app/src/main/res/layout/channel_fragment.xml @@ -1,6 +1,7 @@ @@ -91,30 +92,46 @@ + android:layout_height="wrap_content" + android:hint="@string/set_channel_options" /> +