diff --git a/app/build.gradle b/app/build.gradle index 8c837a2c0..8292c32a8 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -31,8 +31,8 @@ android { applicationId "com.geeksville.mesh" minSdkVersion 21 // The oldest emulator image I have tried is 22 (though 21 probably works) targetSdkVersion 29 - versionCode 20130 // format is Mmmss (where M is 1+the numeric major number - versionName "1.1.30" + versionCode 20133 // format is Mmmss (where M is 1+the numeric major number + versionName "1.1.33" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" } buildTypes { diff --git a/app/src/main/java/com/geeksville/mesh/service/Constants.kt b/app/src/main/java/com/geeksville/mesh/service/Constants.kt index 4d4b68b9b..3b99c917a 100644 --- a/app/src/main/java/com/geeksville/mesh/service/Constants.kt +++ b/app/src/main/java/com/geeksville/mesh/service/Constants.kt @@ -9,6 +9,7 @@ const val prefix = "com.geeksville.mesh" // a bool true means now connected, false means not const val EXTRA_CONNECTED = "$prefix.Connected" +const val EXTRA_PROGRESS = "$prefix.Progress" /// a bool true means we expect this condition to continue until, false means device might come back const val EXTRA_PERMANENT = "$prefix.Permanent" 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 101eda63d..bde6821fc 100644 --- a/app/src/main/java/com/geeksville/mesh/service/MeshService.kt +++ b/app/src/main/java/com/geeksville/mesh/service/MeshService.kt @@ -23,6 +23,7 @@ import com.geeksville.mesh.MeshProtos.ToRadio import com.geeksville.mesh.database.MeshtasticDatabase import com.geeksville.mesh.database.PacketRepository import com.geeksville.mesh.database.entity.Packet +import com.geeksville.mesh.service.SoftwareUpdateService.Companion.ProgressNotStarted import com.geeksville.util.* import com.google.android.gms.common.api.ApiException import com.google.android.gms.common.api.ResolvableApiException @@ -891,6 +892,7 @@ class MeshService : Service(), Logging { // Do our startup init try { connectTimeMsec = System.currentTimeMillis() + SoftwareUpdateService.sendProgress(this, ProgressNotStarted) // Kinda crufty way of reiniting software update startConfig() } catch (ex: InvalidProtocolBufferException) { 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 69d57639c..84bcfbc1a 100644 --- a/app/src/main/java/com/geeksville/mesh/service/SoftwareUpdateService.kt +++ b/app/src/main/java/com/geeksville/mesh/service/SoftwareUpdateService.kt @@ -147,6 +147,8 @@ class SoftwareUpdateService : JobIntentService(), Logging { const val ACTION_START_UPDATE = "$prefix.START_UPDATE" + const val ACTION_UPDATE_PROGRESS = "$prefix.UPDATE_PROGRESS" + const val EXTRA_MACADDR = "macaddr" private const val SCAN_PERIOD: Long = 10000 @@ -170,10 +172,15 @@ class SoftwareUpdateService : JobIntentService(), Logging { private val MANUFACTURE_CHARACTER = longBLEUUID("2a29") private val HW_VERSION_CHARACTER = longBLEUUID("2a27") + const val ProgressSuccess = -1 + const val ProgressUpdateFailed = -2 + const val ProgressBleException = -3 + const val ProgressNotStarted = -4 + /** * % progress through the update */ - var progress = 0 + var progress = ProgressNotStarted /** * Convenience method for enqueuing work in to this service. @@ -187,6 +194,17 @@ class SoftwareUpdateService : JobIntentService(), Logging { } + fun sendProgress(context: Context, p: Int) { + if(progress != p) { + progress = p + + val intent = Intent(ACTION_UPDATE_PROGRESS).putExtra( + EXTRA_PROGRESS, + p + ) + context.sendBroadcast(intent) + } + } /** Return true if we thing the firmwarte shoulde be updated * @@ -200,7 +218,7 @@ class SoftwareUpdateService : JobIntentService(), Logging { val minVer = DeviceVersion("0.7.8") // The oldest device version with a working software update service - (curVer > deviceVersion) && (deviceVersion >= minVer) + ((curVer > deviceVersion) && (deviceVersion >= minVer)) } catch (ex: Exception) { errormsg("Error finding swupdate info", ex) false // If we fail parsing our update info @@ -262,7 +280,7 @@ class SoftwareUpdateService : JobIntentService(), Logging { errormsg("Ignoring failure to update spiffs on old appload") } assets.appLoad?.let { doUpdate(context, sync, it, FLASH_REGION_APPLOAD) } - progress = -1 // success + sendProgress(context, ProgressSuccess) } // writable region codes in the ESP32 update code @@ -288,7 +306,7 @@ class SoftwareUpdateService : JobIntentService(), Logging { info("Starting firmware update for $assetName, flash region $flashRegion") - progress = 0 + sendProgress(context,0) val totalSizeDesc = getCharacteristic(SW_UPDATE_TOTALSIZE_CHARACTER) val dataDesc = getCharacteristic(SW_UPDATE_DATA_CHARACTER) val crc32Desc = getCharacteristic(SW_UPDATE_CRC32_CHARACTER) @@ -336,7 +354,7 @@ class SoftwareUpdateService : JobIntentService(), Logging { // yet val maxProgress = if(flashRegion != FLASH_REGION_APPLOAD) 50 else 100 - progress = firmwareNumSent * maxProgress / firmwareSize + sendProgress(context, firmwareNumSent * maxProgress / firmwareSize) debug("sending block ${progress}%") var blockSize = 512 - 3 // Max size MTU excluding framing @@ -366,7 +384,7 @@ class SoftwareUpdateService : JobIntentService(), Logging { sync.readCharacteristic(updateResultDesc) .getIntValue(BluetoothGattCharacteristic.FORMAT_UINT8, 0) if (updateResult != 0) { - progress = -2 + sendProgress(context, ProgressUpdateFailed) throw Exception("Device update failed, reason=$updateResult") } @@ -377,7 +395,7 @@ class SoftwareUpdateService : JobIntentService(), Logging { } } } catch (ex: BLEException) { - progress = -3 + sendProgress(context, ProgressBleException) throw ex // Unexpected BLE exception } } 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 936dfff5f..ec8f2ac53 100644 --- a/app/src/main/java/com/geeksville/mesh/ui/SettingsFragment.kt +++ b/app/src/main/java/com/geeksville/mesh/ui/SettingsFragment.kt @@ -29,7 +29,6 @@ import com.geeksville.android.GeeksvilleApplication import com.geeksville.android.Logging import com.geeksville.android.hideKeyboard import com.geeksville.android.isGooglePlayAvailable -import com.geeksville.concurrent.handledLaunch import com.geeksville.mesh.MainActivity import com.geeksville.mesh.R import com.geeksville.mesh.android.bluetoothManager @@ -40,6 +39,9 @@ import com.geeksville.mesh.service.BluetoothInterface import com.geeksville.mesh.service.MeshService import com.geeksville.mesh.service.RadioInterfaceService import com.geeksville.mesh.service.SerialInterface +import com.geeksville.mesh.service.SoftwareUpdateService.Companion.ACTION_UPDATE_PROGRESS +import com.geeksville.mesh.service.SoftwareUpdateService.Companion.ProgressNotStarted +import com.geeksville.mesh.service.SoftwareUpdateService.Companion.ProgressSuccess import com.geeksville.util.anonymize import com.geeksville.util.exceptionReporter import com.google.android.gms.location.LocationRequest @@ -50,9 +52,9 @@ import com.hoho.android.usbserial.driver.UsbSerialDriver import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Job -import kotlinx.coroutines.delay import java.util.regex.Pattern + object SLogging : Logging {} /// Change to a new macaddr selection, updating GUI and radio @@ -484,27 +486,13 @@ class SettingsFragment : ScreenFragment("Settings"), Logging { private fun doFirmwareUpdate() { model.meshService?.let { service -> - mainScope.handledLaunch { - debug("User started firmware update") - binding.updateFirmwareButton.isEnabled = false // Disable until things complete - binding.updateProgressBar.visibility = View.VISIBLE - binding.updateProgressBar.progress = 0 // start from scratch + debug("User started firmware update") + binding.updateFirmwareButton.isEnabled = false // Disable until things complete + binding.updateProgressBar.visibility = View.VISIBLE + binding.updateProgressBar.progress = 0 // start from scratch - binding.scanStatusText.text = "Updating firmware, wait up to eight minutes..." - try { - service.startFirmwareUpdate() - while (service.updateStatus >= 0) { - binding.updateProgressBar.progress = service.updateStatus - delay(2000) // Only check occasionally - } - } finally { - val isSuccess = (service.updateStatus == -1) - binding.scanStatusText.text = - if (isSuccess) "Update successful" else "Update failed" - binding.updateProgressBar.isEnabled = false - binding.updateFirmwareButton.isEnabled = !isSuccess - } - } + // We rely on our broadcast receiver to show progress as this progresses + service.startFirmwareUpdate() } } @@ -516,20 +504,55 @@ class SettingsFragment : ScreenFragment("Settings"), Logging { return binding.root } - private fun initNodeInfo() { - val connected = model.isConnected.value - - // If actively connected possibly let the user update firmware + /// Set the correct update button configuration based on current progress + private fun refreshUpdateButton() { + debug("Reiniting the udpate button") val info = model.myNodeInfo.value - if (connected == MeshService.ConnectionState.CONNECTED && info != null && info.shouldUpdate && info.couldUpdate) { + val service = model.meshService + if (model.isConnected.value == MeshService.ConnectionState.CONNECTED && info != null && info.shouldUpdate && info.couldUpdate && service != null) { binding.updateFirmwareButton.visibility = View.VISIBLE - binding.updateFirmwareButton.isEnabled = true binding.updateFirmwareButton.text = getString(R.string.update_to).format(getString(R.string.cur_firmware_version)) + + val progress = service.updateStatus + + binding.updateFirmwareButton.isEnabled = + (progress < 0) // if currently doing an upgrade disable button + + if (progress >= 0) { + binding.updateProgressBar.progress = progress // update partial progress + binding.scanStatusText.setText(R.string.updating_firmware) + binding.updateProgressBar.visibility = View.VISIBLE + } else + when (progress) { + ProgressSuccess -> { + binding.scanStatusText.setText(R.string.update_successful) + binding.updateProgressBar.visibility = View.GONE + } + ProgressNotStarted -> { + // Do nothing - because we don't want to overwrite the status text in this case + binding.updateProgressBar.visibility = View.GONE + } + else -> { + binding.scanStatusText.setText(R.string.update_failed) + binding.updateProgressBar.visibility = View.VISIBLE + } + } + binding.updateProgressBar.isEnabled = false + } else { binding.updateFirmwareButton.visibility = View.GONE binding.updateProgressBar.visibility = View.GONE } + } + + private fun initNodeInfo() { + val connected = model.isConnected.value + + refreshUpdateButton() + + // If actively connected possibly let the user update firmware + val info = model.myNodeInfo.value when (connected) { MeshService.ConnectionState.CONNECTED -> { @@ -784,11 +807,6 @@ class SettingsFragment : ScreenFragment("Settings"), Logging { initClassicScan() } - override fun onPause() { - super.onPause() - scanModel.stopScan() - } - /** * If the user has not turned on location access throw up a toast warning */ @@ -842,11 +860,29 @@ class SettingsFragment : ScreenFragment("Settings"), Logging { } } + private val updateProgressFilter = IntentFilter(ACTION_UPDATE_PROGRESS) + + private val updateProgressReceiver: BroadcastReceiver = object : BroadcastReceiver() { + override fun onReceive(context: Context?, intent: Intent?) { + refreshUpdateButton() + } + } + + override fun onPause() { + super.onPause() + scanModel.stopScan() + + requireActivity().unregisterReceiver(updateProgressReceiver) + } + override fun onResume() { super.onResume() + if (!hasCompanionDeviceApi) scanModel.startScan() + requireActivity().registerReceiver(updateProgressReceiver, updateProgressFilter) + // Keep reminding user BLE is still off val hasUSB = activity?.let { SerialInterface.findDrivers(it).isNotEmpty() } ?: true if (!hasUSB) { diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 35e624739..427e2efb9 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -77,4 +77,7 @@ Debug Panel 500 last messages Clear + Updating firmware, wait up to eight minutes... + Update successful + Update failed