kopia lustrzana https://github.com/meshtastic/Meshtastic-Android
Remove UI for firmware update (button and progress) and accompanying logic (#870)
Use non-deprecated method for checking IP address formatpull/871/head^2
rodzic
56d622013b
commit
2de49c143b
|
@ -204,7 +204,9 @@ class MeshService : Service(), Logging {
|
|||
debug("Sending to radio ${built.toPIIString()}")
|
||||
val b = built.toByteArray()
|
||||
|
||||
if (SoftwareUpdateService.isUpdating) throw IsUpdatingException()
|
||||
if (false) { // TODO check if radio is updating
|
||||
throw IsUpdatingException()
|
||||
}
|
||||
|
||||
radioInterfaceService.sendToRadio(b)
|
||||
changeStatus(p.packet.id, MessageStatus.ENROUTE)
|
||||
|
@ -1133,13 +1135,7 @@ class MeshService : Service(), Logging {
|
|||
// Do our startup init
|
||||
try {
|
||||
connectTimeMsec = System.currentTimeMillis()
|
||||
SoftwareUpdateService.sendProgress(
|
||||
this,
|
||||
SoftwareUpdateService.ProgressNotStarted,
|
||||
true
|
||||
) // Kinda crufty way of reiniting software update
|
||||
startConfig()
|
||||
|
||||
} catch (ex: InvalidProtocolBufferException) {
|
||||
errormsg(
|
||||
"Invalid protocol buffer sent by device - update device software and try again",
|
||||
|
@ -1342,10 +1338,7 @@ class MeshService : Service(), Logging {
|
|||
hwModelStr,
|
||||
firmwareVersion,
|
||||
firmwareUpdateFilename?.appLoad != null && firmwareUpdateFilename?.littlefs != null,
|
||||
isBluetoothInterface && SoftwareUpdateService.shouldUpdate(
|
||||
this@MeshService,
|
||||
DeviceVersion(firmwareVersion)
|
||||
),
|
||||
shouldUpdate = false, // TODO add check after re-implementing firmware updates
|
||||
currentPacketId and 0xffffffffL,
|
||||
5 * 60 * 1000, // constants from current device code
|
||||
minAppVersion,
|
||||
|
@ -1632,10 +1625,8 @@ class MeshService : Service(), Logging {
|
|||
private fun setFirmwareUpdateFilename(model: String?) {
|
||||
firmwareUpdateFilename = try {
|
||||
if (model != null)
|
||||
SoftwareUpdateService.getUpdateFilename(
|
||||
this,
|
||||
model
|
||||
)
|
||||
// TODO reimplement this after we have a new firmware update mechanism
|
||||
null
|
||||
else
|
||||
null
|
||||
} catch (ex: Exception) {
|
||||
|
@ -1664,7 +1655,7 @@ class MeshService : Service(), Logging {
|
|||
updateJob = serviceScope.handledLaunch {
|
||||
exceptionReporter {
|
||||
debug("Starting firmware update coroutine")
|
||||
SoftwareUpdateService.doUpdate(this@MeshService, safe, filename)
|
||||
// TODO perform update with new firmware update mechanism
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1691,7 +1682,7 @@ class MeshService : Service(), Logging {
|
|||
clientPackages[receiverName] = packageName
|
||||
}
|
||||
|
||||
override fun getUpdateStatus(): Int = SoftwareUpdateService.progress
|
||||
override fun getUpdateStatus(): Int = 0 // TODO reimplement this after we have a new firmware update mechanism
|
||||
|
||||
override fun startFirmwareUpdate() = toRemoteExceptions {
|
||||
doFirmwareUpdate()
|
||||
|
|
|
@ -1,18 +1,6 @@
|
|||
package com.geeksville.mesh.service
|
||||
|
||||
import android.bluetooth.BluetoothAdapter
|
||||
import android.bluetooth.BluetoothGattCharacteristic
|
||||
import android.bluetooth.BluetoothManager
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import androidx.core.app.JobIntentService
|
||||
import com.geeksville.mesh.android.Logging
|
||||
import com.geeksville.mesh.MainActivity
|
||||
import com.geeksville.mesh.R
|
||||
import com.geeksville.mesh.model.DeviceVersion
|
||||
import com.geeksville.mesh.util.exceptionReporter
|
||||
import java.util.*
|
||||
import java.util.zip.CRC32
|
||||
|
||||
/**
|
||||
* Some misformatted ESP32s have problems
|
||||
|
@ -69,379 +57,4 @@ fun toNetworkByteArray(value: Int, formatType: Int): ByteArray {
|
|||
return mValue
|
||||
}
|
||||
|
||||
|
||||
data class UpdateFilenames(val appLoad: String?, val littlefs: String?)
|
||||
|
||||
/**
|
||||
* typical flow
|
||||
*
|
||||
* startScan
|
||||
* startUpdate
|
||||
*
|
||||
* stopScan
|
||||
*
|
||||
* FIXME - if we don't find a device stop our scan
|
||||
* FIXME - broadcast when we found devices, made progress sending blocks or when the update is complete
|
||||
* FIXME - make the user decide to start an update on a particular device
|
||||
*/
|
||||
class SoftwareUpdateService : JobIntentService(), Logging {
|
||||
|
||||
|
||||
private val bluetoothAdapter: BluetoothAdapter by lazy(LazyThreadSafetyMode.NONE) {
|
||||
val bluetoothManager = getSystemService(Context.BLUETOOTH_SERVICE) as BluetoothManager
|
||||
bluetoothManager.adapter!!
|
||||
}
|
||||
|
||||
|
||||
private fun startUpdate(macaddr: String) {
|
||||
info("starting update to $macaddr")
|
||||
|
||||
val device = bluetoothAdapter.getRemoteDevice(macaddr)
|
||||
|
||||
val sync =
|
||||
SafeBluetooth(
|
||||
this@SoftwareUpdateService,
|
||||
device
|
||||
)
|
||||
|
||||
sync.connect()
|
||||
sync.use { _ ->
|
||||
// we begin by setting our MTU size as high as it can go
|
||||
sync.requestMtu(512)
|
||||
|
||||
sync.discoverServices() // Get our services
|
||||
|
||||
val updateFilename = getUpdateFilename(this, sync)
|
||||
if (updateFilename != null) {
|
||||
doUpdate(this, sync, updateFilename)
|
||||
} else
|
||||
warn("Device is already up-to-date no update needed.")
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
override fun onHandleWork(intent: Intent) {
|
||||
// We have received work to do. The system or framework is already
|
||||
// holding a wake lock for us at this point, so we can just go.
|
||||
|
||||
// Report failures but do not crash the app
|
||||
exceptionReporter {
|
||||
debug("Executing work: $intent")
|
||||
when (intent.action) {
|
||||
ACTION_START_UPDATE -> {
|
||||
val addr = intent.getStringExtra(EXTRA_MACADDR)
|
||||
?: throw Exception("EXTRA_MACADDR not specified")
|
||||
startUpdate(addr) // FIXME, pass in as an intent arg instead
|
||||
}
|
||||
else -> TODO("Unhandled case")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
companion object : Logging {
|
||||
/**
|
||||
* Unique job ID for this service. Must be the same for all work.
|
||||
*/
|
||||
private const val JOB_ID = 1000
|
||||
|
||||
fun startUpdateIntent(macAddress: String): Intent {
|
||||
val i = Intent(ACTION_START_UPDATE)
|
||||
i.putExtra(EXTRA_MACADDR, macAddress)
|
||||
|
||||
return i
|
||||
}
|
||||
|
||||
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
|
||||
|
||||
private val TAG =
|
||||
MainActivity::class.java.simpleName // FIXME - use my logging class instead
|
||||
|
||||
private val SW_UPDATE_UUID = UUID.fromString("cb0b9a0b-a84c-4c0d-bdbb-442e3144ee30")
|
||||
private val SW_UPDATE_TOTALSIZE_CHARACTER =
|
||||
UUID.fromString("e74dd9c0-a301-4a6f-95a1-f0e1dbea8e1e") // write|read total image size, 32 bit, write this first, then read read back to see if it was acceptable (0 mean not accepted)
|
||||
private val SW_UPDATE_DATA_CHARACTER =
|
||||
UUID.fromString("e272ebac-d463-4b98-bc84-5cc1a39ee517") // write data, variable sized, recommended 512 bytes, write one for each block of file
|
||||
private val SW_UPDATE_CRC32_CHARACTER =
|
||||
UUID.fromString("4826129c-c22a-43a3-b066-ce8f0d5bacc6") // write crc32, write last - writing this will complete the OTA operation, now you can read result
|
||||
private val SW_UPDATE_RESULT_CHARACTER =
|
||||
UUID.fromString("5e134862-7411-4424-ac4a-210937432c77") // read|notify result code, readable but will notify when the OTA operation completes
|
||||
private val SW_UPDATE_REGION_CHARACTER =
|
||||
UUID.fromString("5e134862-7411-4424-ac4a-210937432c67") // write - used to set the region we are setting (appload vs littlefs)
|
||||
|
||||
private val SW_VERSION_CHARACTER = longBLEUUID("2a28")
|
||||
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 = ProgressNotStarted
|
||||
|
||||
/**
|
||||
* Convenience method for enqueuing work in to this service.
|
||||
*/
|
||||
fun enqueueWork(context: Context, work: Intent) {
|
||||
enqueueWork(
|
||||
context,
|
||||
SoftwareUpdateService::class.java,
|
||||
JOB_ID, work
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* true if we are busy with an update right now
|
||||
*/
|
||||
val isUpdating get() = progress >= 0
|
||||
|
||||
/**
|
||||
* Update our progress indication for GUIs
|
||||
*
|
||||
* @param isAppload if false, we don't report failure indications (because we consider littlefs non critical for now). But do report to analytics
|
||||
*/
|
||||
fun sendProgress(context: Context, p: Int, isAppload: Boolean) {
|
||||
if (!isAppload && p < 0)
|
||||
errormsg("Error while writing littlefs $p") // treat errors writing littlefs as non fatal for now (user partition probably missized and most people don't need it)
|
||||
else
|
||||
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
|
||||
*
|
||||
* @param swVer the version of the software running on the target
|
||||
*/
|
||||
fun shouldUpdate(
|
||||
context: Context,
|
||||
deviceVersion: DeviceVersion
|
||||
): Boolean = try {
|
||||
val curVer = DeviceVersion(context.getString(R.string.cur_firmware_version))
|
||||
val minVer =
|
||||
DeviceVersion("0.7.8") // The oldest device version with a working software update service
|
||||
|
||||
((curVer > deviceVersion) && (deviceVersion >= minVer))
|
||||
} catch (ex: Exception) {
|
||||
errormsg("Error finding swupdate info", ex)
|
||||
false // If we fail parsing our update info
|
||||
}
|
||||
|
||||
/** Return a Pair of appload filename, littlefs filename this device needs to use as an update (or null if no update needed)
|
||||
*/
|
||||
fun getUpdateFilename(
|
||||
context: Context,
|
||||
mfg: String
|
||||
): UpdateFilenames {
|
||||
val curVer = context.getString(R.string.cur_firmware_version)
|
||||
|
||||
// Check to see if the file exists (some builds might not include update files for size reasons)
|
||||
val firmwareFiles = context.assets.list("firmware") ?: arrayOf()
|
||||
|
||||
val appLoad = "firmware-$mfg-$curVer.bin"
|
||||
val littlefs = "littlefs-$curVer.bin"
|
||||
|
||||
return UpdateFilenames(
|
||||
if (firmwareFiles.contains(appLoad))
|
||||
"firmware/$appLoad"
|
||||
else
|
||||
null,
|
||||
if (firmwareFiles.contains(littlefs))
|
||||
"firmware/$littlefs"
|
||||
else
|
||||
null
|
||||
)
|
||||
}
|
||||
|
||||
/** Return the filename this device needs to use as an update (or null if no update needed)
|
||||
* No longer used, because we get update info inband from our radio API
|
||||
*/
|
||||
fun getUpdateFilename(context: Context, sync: SafeBluetooth): UpdateFilenames? {
|
||||
val service = sync.gatt!!.services.find { it.uuid == SW_UPDATE_UUID }!!
|
||||
|
||||
//val hwVerDesc = service.getCharacteristic(HW_VERSION_CHARACTER)
|
||||
val mfgDesc = service.getCharacteristic(MANUFACTURE_CHARACTER)
|
||||
//val swVerDesc = service.getCharacteristic(SW_VERSION_CHARACTER)
|
||||
|
||||
// looks like HELTEC
|
||||
val mfg = sync.readCharacteristic(mfgDesc).getStringValue(0)
|
||||
|
||||
return getUpdateFilename(context, mfg)
|
||||
}
|
||||
|
||||
/**
|
||||
* A public function so that if you have your own SafeBluetooth connection already open
|
||||
* you can use it for the software update.
|
||||
*/
|
||||
fun doUpdate(context: Context, sync: SafeBluetooth, assets: UpdateFilenames) {
|
||||
// calculate total firmware size (littlefs + appLoad)
|
||||
var totalFirmwareSize = 0
|
||||
if (assets.appLoad != null && assets.littlefs != null) {
|
||||
totalFirmwareSize += context.assets.open(assets.appLoad).available()
|
||||
totalFirmwareSize += context.assets.open(assets.littlefs).available()
|
||||
}
|
||||
// we must attempt littlefs first, because if we update the appload the device will reboot afterwards
|
||||
try {
|
||||
assets.littlefs?.let { doUpdate(context, sync, it, FLASH_REGION_LITTLEFS, totalFirmwareSize) }
|
||||
} catch (_: BLECharacteristicNotFoundException) {
|
||||
// If we can't update littlefs (because not supported by target), do not fail
|
||||
errormsg("Ignoring failure to update littlefs on old appload")
|
||||
} catch (_: DeviceRejectedException) {
|
||||
// the spi filesystem of this device is malformatted, fail silently because most users don't need the web server
|
||||
errormsg("Device rejected invalid littlefs partition")
|
||||
}
|
||||
|
||||
assets.appLoad?.let { doUpdate(context, sync, it, FLASH_REGION_APPLOAD, totalFirmwareSize) }
|
||||
sendProgress(context, ProgressSuccess, true)
|
||||
}
|
||||
|
||||
// writable region codes in the ESP32 update code
|
||||
private val FLASH_REGION_APPLOAD = 0
|
||||
private val FLASH_REGION_LITTLEFS = 100
|
||||
|
||||
/**
|
||||
* A public function so that if you have your own SafeBluetooth connection already open
|
||||
* you can use it for the software update.
|
||||
*/
|
||||
private fun doUpdate(
|
||||
context: Context,
|
||||
sync: SafeBluetooth,
|
||||
assetName: String,
|
||||
flashRegion: Int = FLASH_REGION_APPLOAD,
|
||||
totalFirmwareSize: Int = 0
|
||||
) {
|
||||
val isAppload = flashRegion == FLASH_REGION_APPLOAD
|
||||
|
||||
try {
|
||||
val g = sync.gatt!!
|
||||
val service = g.services.find { it.uuid == SW_UPDATE_UUID }
|
||||
?: throw BLEException("Couldn't find update service")
|
||||
|
||||
/**
|
||||
* Get a chracteristic, but in a safe manner because some buggy BLE implementations might return null
|
||||
*/
|
||||
fun getCharacteristic(uuid: UUID) =
|
||||
service.getCharacteristic(uuid)
|
||||
?: throw BLECharacteristicNotFoundException(uuid)
|
||||
|
||||
info("Starting firmware update for $assetName, flash region $flashRegion")
|
||||
|
||||
sendProgress(context, 0, isAppload)
|
||||
val totalSizeDesc = getCharacteristic(SW_UPDATE_TOTALSIZE_CHARACTER)
|
||||
val dataDesc = getCharacteristic(SW_UPDATE_DATA_CHARACTER)
|
||||
val crc32Desc = getCharacteristic(SW_UPDATE_CRC32_CHARACTER)
|
||||
val updateResultDesc = getCharacteristic(SW_UPDATE_RESULT_CHARACTER)
|
||||
|
||||
/// Try to set the destination region for programming (littlefs vs appload etc)
|
||||
/// Old apploads don't have this feature, but we only fail if the user was trying to set a
|
||||
/// littlefs - otherwise we assume appload.
|
||||
try {
|
||||
val updateRegionDesc = getCharacteristic(SW_UPDATE_REGION_CHARACTER)
|
||||
sync.writeCharacteristic(
|
||||
updateRegionDesc,
|
||||
toNetworkByteArray(flashRegion, BluetoothGattCharacteristic.FORMAT_UINT8)
|
||||
)
|
||||
} catch (ex: BLECharacteristicNotFoundException) {
|
||||
errormsg("Can't set flash programming region (old appload?")
|
||||
if (flashRegion != FLASH_REGION_APPLOAD) {
|
||||
throw ex
|
||||
}
|
||||
warn("Ignoring setting appload flashRegion")
|
||||
}
|
||||
|
||||
context.assets.open(assetName).use { firmwareStream ->
|
||||
val firmwareCrc = CRC32()
|
||||
var firmwareNumSent = 0
|
||||
val firmwareSize = firmwareStream.available()
|
||||
|
||||
// Start the update by writing the # of bytes in the image
|
||||
sync.writeCharacteristic(
|
||||
totalSizeDesc,
|
||||
toNetworkByteArray(firmwareSize, BluetoothGattCharacteristic.FORMAT_UINT32)
|
||||
)
|
||||
|
||||
// Our write completed, queue up a readback
|
||||
val totalSizeReadback = sync.readCharacteristic(totalSizeDesc)
|
||||
.getIntValue(BluetoothGattCharacteristic.FORMAT_UINT32, 0)
|
||||
if (totalSizeReadback == 0)
|
||||
throw DeviceRejectedException()
|
||||
|
||||
// Send all the blocks
|
||||
var oldProgress = -1 // used to limit # of log spam
|
||||
while (firmwareNumSent < firmwareSize) {
|
||||
// If we are doing the littlefs partition, we limit progress to a max of maxProgress
|
||||
// when updating the appload partition, progress from (100 - maxProgress) to 100%
|
||||
// maxProgress = littlefs% = 100% - appLoad%; (int * 10 + 5) / 10 used for rounding
|
||||
val maxProgress = ((firmwareSize * 1000 / totalFirmwareSize) + 5) / 10
|
||||
val minProgress = if (flashRegion != FLASH_REGION_APPLOAD)
|
||||
0 else (100 - maxProgress)
|
||||
sendProgress(
|
||||
context,
|
||||
minProgress + firmwareNumSent * maxProgress / firmwareSize,
|
||||
isAppload
|
||||
)
|
||||
if (progress != oldProgress) {
|
||||
debug("sending block ${progress}%")
|
||||
oldProgress = progress
|
||||
}
|
||||
var blockSize = 512 - 3 // Max size MTU excluding framing
|
||||
|
||||
if (blockSize > firmwareStream.available())
|
||||
blockSize = firmwareStream.available()
|
||||
val buffer = ByteArray(blockSize)
|
||||
|
||||
// slightly expensive to keep reallocing this buffer, but whatever
|
||||
logAssert(firmwareStream.read(buffer) == blockSize)
|
||||
firmwareCrc.update(buffer)
|
||||
|
||||
sync.writeCharacteristic(dataDesc, buffer)
|
||||
firmwareNumSent += blockSize
|
||||
}
|
||||
|
||||
try {
|
||||
// We have finished sending all our blocks, so post the CRC so our state machine can advance
|
||||
val c = firmwareCrc.value
|
||||
info("Sent all blocks, crc is $c")
|
||||
sync.writeCharacteristic(
|
||||
crc32Desc,
|
||||
toNetworkByteArray(c.toInt(), BluetoothGattCharacteristic.FORMAT_UINT32)
|
||||
)
|
||||
|
||||
// we just read the update result if !0 we have an error
|
||||
val updateResult =
|
||||
sync.readCharacteristic(updateResultDesc)
|
||||
.getIntValue(BluetoothGattCharacteristic.FORMAT_UINT8, 0)
|
||||
if (updateResult != 0) {
|
||||
sendProgress(context, ProgressUpdateFailed, isAppload)
|
||||
throw Exception("Device update failed, reason=$updateResult")
|
||||
}
|
||||
|
||||
// Device will now reboot
|
||||
} catch (ex: BLEException) {
|
||||
// We might get SyncContinuation timeout on the final write, assume the device simply rebooted to run the new load and we missed it
|
||||
errormsg("Assuming successful update", ex)
|
||||
}
|
||||
}
|
||||
} catch (ex: BLEException) {
|
||||
sendProgress(context, ProgressBleException, isAppload)
|
||||
throw ex // Unexpected BLE exception
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
data class UpdateFilenames(val appLoad: String?, val littlefs: String?)
|
|
@ -1,12 +1,11 @@
|
|||
package com.geeksville.mesh.ui
|
||||
|
||||
import android.content.BroadcastReceiver
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.content.IntentFilter
|
||||
import android.net.InetAddresses
|
||||
import android.os.Build
|
||||
import android.os.Bundle
|
||||
import android.os.Handler
|
||||
import android.os.Looper
|
||||
import android.text.Editable
|
||||
import android.util.Patterns
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
|
@ -21,7 +20,6 @@ import androidx.fragment.app.activityViewModels
|
|||
import androidx.lifecycle.asLiveData
|
||||
import com.geeksville.mesh.ConfigProtos
|
||||
import com.geeksville.mesh.R
|
||||
import com.geeksville.mesh.analytics.DataPair
|
||||
import com.geeksville.mesh.ModuleConfigProtos
|
||||
import com.geeksville.mesh.android.*
|
||||
import com.geeksville.mesh.databinding.SettingsFragmentBinding
|
||||
|
@ -31,7 +29,6 @@ import com.geeksville.mesh.model.UIViewModel
|
|||
import com.geeksville.mesh.model.getInitials
|
||||
import com.geeksville.mesh.repository.location.LocationRepository
|
||||
import com.geeksville.mesh.service.MeshService
|
||||
import com.geeksville.mesh.service.SoftwareUpdateService
|
||||
import com.geeksville.mesh.util.exceptionToSnackbar
|
||||
import com.geeksville.mesh.util.getAssociationResult
|
||||
import com.geeksville.mesh.util.onEditorAction
|
||||
|
@ -56,25 +53,6 @@ class SettingsFragment : ScreenFragment("Settings"), Logging {
|
|||
private val hasGps by lazy { requireContext().hasGps() }
|
||||
private val hasCompanionDeviceApi by lazy { requireContext().hasCompanionDeviceApi() }
|
||||
|
||||
private fun doFirmwareUpdate() {
|
||||
model.meshService?.let { service ->
|
||||
|
||||
debug("User started firmware update")
|
||||
GeeksvilleApplication.analytics.track(
|
||||
"firmware_update",
|
||||
DataPair("content_type", "start")
|
||||
)
|
||||
binding.updateFirmwareButton.isEnabled = false // Disable until things complete
|
||||
binding.updateProgressBar.visibility = View.VISIBLE
|
||||
binding.updateProgressBar.progress = 0 // start from scratch
|
||||
|
||||
exceptionToSnackbar(requireView()) {
|
||||
// We rely on our broadcast receiver to show progress as this progresses
|
||||
service.startFirmwareUpdate()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun onCreateView(
|
||||
inflater: LayoutInflater, container: ViewGroup?,
|
||||
savedInstanceState: Bundle?
|
||||
|
@ -83,56 +61,6 @@ class SettingsFragment : ScreenFragment("Settings"), Logging {
|
|||
return binding.root
|
||||
}
|
||||
|
||||
/// Set the correct update button configuration based on current progress
|
||||
private fun refreshUpdateButton(enable: Boolean) {
|
||||
debug("Reiniting the update button")
|
||||
val info = model.myNodeInfo.value
|
||||
val service = model.meshService
|
||||
if (model.isConnected() && info != null && info.shouldUpdate && info.couldUpdate && service != null) {
|
||||
binding.updateFirmwareButton.visibility = View.VISIBLE
|
||||
binding.updateFirmwareButton.text =
|
||||
getString(R.string.update_to).format(getString(R.string.short_firmware_version))
|
||||
|
||||
val progress = service.updateStatus
|
||||
|
||||
binding.updateFirmwareButton.isEnabled = enable &&
|
||||
(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) {
|
||||
SoftwareUpdateService.ProgressSuccess -> {
|
||||
GeeksvilleApplication.analytics.track(
|
||||
"firmware_update",
|
||||
DataPair("content_type", "success")
|
||||
)
|
||||
binding.scanStatusText.setText(R.string.update_successful)
|
||||
binding.updateProgressBar.visibility = View.GONE
|
||||
}
|
||||
SoftwareUpdateService.ProgressNotStarted -> {
|
||||
// Do nothing - because we don't want to overwrite the status text in this case
|
||||
binding.updateProgressBar.visibility = View.GONE
|
||||
}
|
||||
else -> {
|
||||
GeeksvilleApplication.analytics.track(
|
||||
"firmware_update",
|
||||
DataPair("content_type", "failure")
|
||||
)
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Pull the latest device info from the model and into the GUI
|
||||
*/
|
||||
|
@ -168,9 +96,6 @@ class SettingsFragment : ScreenFragment("Settings"), Logging {
|
|||
spinner.onItemSelectedListener = regionSpinnerListener
|
||||
spinner.isEnabled = !model.isManaged
|
||||
|
||||
// If actively connected possibly let the user update firmware
|
||||
refreshUpdateButton(isConnected)
|
||||
|
||||
// Update the status string (highest priority messages first)
|
||||
val info = model.myNodeInfo.value
|
||||
when (connectionState) {
|
||||
|
@ -313,17 +238,6 @@ class SettingsFragment : ScreenFragment("Settings"), Logging {
|
|||
}
|
||||
}
|
||||
|
||||
binding.updateFirmwareButton.setOnClickListener {
|
||||
MaterialAlertDialogBuilder(requireContext())
|
||||
.setMessage("${getString(R.string.update_firmware)}?")
|
||||
.setNeutralButton(R.string.cancel) { _, _ ->
|
||||
}
|
||||
.setPositiveButton(getString(R.string.okay)) { _, _ ->
|
||||
doFirmwareUpdate()
|
||||
}
|
||||
.show()
|
||||
}
|
||||
|
||||
binding.usernameEditText.onEditorAction(EditorInfo.IME_ACTION_DONE) {
|
||||
debug("received IME_ACTION_DONE")
|
||||
val n = binding.usernameEditText.text.toString().trim()
|
||||
|
@ -390,6 +304,7 @@ class SettingsFragment : ScreenFragment("Settings"), Logging {
|
|||
binding.reportBugButton.setOnClickListener(::showReportBugDialog)
|
||||
}
|
||||
|
||||
@Suppress("UNUSED_PARAMETER")
|
||||
private fun showReportBugDialog(view: View) {
|
||||
MaterialAlertDialogBuilder(requireContext())
|
||||
.setTitle(R.string.report_a_bug)
|
||||
|
@ -420,19 +335,19 @@ class SettingsFragment : ScreenFragment("Settings"), Logging {
|
|||
}
|
||||
|
||||
private fun addManualDeviceButton() {
|
||||
val b = binding.radioButtonManual
|
||||
val e = binding.editManualAddress
|
||||
val deviceSelectIPAddress = binding.radioButtonManual
|
||||
val inputIPAddress = binding.editManualAddress
|
||||
|
||||
b.isEnabled = false
|
||||
|
||||
binding.deviceRadioGroup.addView(b)
|
||||
|
||||
b.setOnClickListener {
|
||||
b.isChecked = scanModel.onSelected(BTScanModel.DeviceListEntry("", "t" + e.text, true))
|
||||
deviceSelectIPAddress.isEnabled = false
|
||||
deviceSelectIPAddress.setOnClickListener {
|
||||
deviceSelectIPAddress.isChecked = scanModel.onSelected(BTScanModel.DeviceListEntry("", "t" + inputIPAddress.text, true))
|
||||
}
|
||||
binding.deviceRadioGroup.addView(e)
|
||||
e.doAfterTextChanged {
|
||||
b.isEnabled = Patterns.IP_ADDRESS.matcher(e.text).matches()
|
||||
|
||||
binding.deviceRadioGroup.addView(deviceSelectIPAddress)
|
||||
binding.deviceRadioGroup.addView(inputIPAddress)
|
||||
|
||||
inputIPAddress.doAfterTextChanged {
|
||||
deviceSelectIPAddress.isEnabled = inputIPAddress.text.isIPAddress()
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -545,25 +460,9 @@ class SettingsFragment : ScreenFragment("Settings"), Logging {
|
|||
}
|
||||
}
|
||||
|
||||
private val updateProgressFilter = IntentFilter(SoftwareUpdateService.ACTION_UPDATE_PROGRESS)
|
||||
|
||||
private val updateProgressReceiver: BroadcastReceiver = object : BroadcastReceiver() {
|
||||
override fun onReceive(context: Context?, intent: Intent?) {
|
||||
refreshUpdateButton(true)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onPause() {
|
||||
super.onPause()
|
||||
|
||||
requireActivity().unregisterReceiver(updateProgressReceiver)
|
||||
}
|
||||
|
||||
override fun onResume() {
|
||||
super.onResume()
|
||||
|
||||
requireActivity().registerReceiver(updateProgressReceiver, updateProgressFilter)
|
||||
|
||||
// Warn user if BLE device is selected but BLE disabled
|
||||
if (scanModel.selectedBluetooth) checkBTEnabled()
|
||||
|
||||
|
@ -580,4 +479,14 @@ class SettingsFragment : ScreenFragment("Settings"), Logging {
|
|||
companion object {
|
||||
const val SCAN_PERIOD: Long = 10000 // Stops scanning after 10 seconds
|
||||
}
|
||||
|
||||
private fun Editable.isIPAddress(): Boolean {
|
||||
return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
|
||||
InetAddresses.isNumericAddress(this.toString())
|
||||
} else {
|
||||
@Suppress("DEPRECATION")
|
||||
Patterns.IP_ADDRESS.matcher(this).matches()
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -74,9 +74,10 @@
|
|||
android:layout_marginTop="16dp"
|
||||
android:layout_marginEnd="8dp"
|
||||
android:text="@string/looking_for_meshtastic_devices"
|
||||
app:layout_constraintEnd_toStartOf="@+id/updateFirmwareButton"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@+id/nodeSettings" />
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@+id/nodeSettings"
|
||||
/>
|
||||
|
||||
<ProgressBar
|
||||
android:id="@+id/scanProgressBar"
|
||||
|
@ -123,7 +124,9 @@
|
|||
android:ems="10"
|
||||
android:hint="@string/ip_address"
|
||||
android:inputType="number|text"
|
||||
android:visibility="visible" />
|
||||
android:visibility="visible"
|
||||
android:importantForAutofill="no"
|
||||
/>
|
||||
|
||||
</RadioGroup>
|
||||
|
||||
|
@ -137,25 +140,6 @@
|
|||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintBottom_toTopOf="@+id/reportBugButton" />
|
||||
|
||||
<com.google.android.material.button.MaterialButton
|
||||
android:id="@+id/updateFirmwareButton"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginEnd="16dp"
|
||||
android:text="@string/update_firmware"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintTop_toTopOf="@+id/scanStatusText" />
|
||||
|
||||
<ProgressBar
|
||||
android:id="@+id/updateProgressBar"
|
||||
style="?android:attr/progressBarStyleHorizontal"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:visibility="gone"
|
||||
app:layout_constraintEnd_toEndOf="@+id/updateFirmwareButton"
|
||||
app:layout_constraintStart_toStartOf="@+id/updateFirmwareButton"
|
||||
app:layout_constraintTop_toBottomOf="@+id/updateFirmwareButton" />
|
||||
|
||||
<CheckBox
|
||||
android:id="@+id/provideLocationCheckbox"
|
||||
android:layout_width="wrap_content"
|
||||
|
|
Ładowanie…
Reference in New Issue