kopia lustrzana https://github.com/meshtastic/Meshtastic-Android
BLE sw update kinda works again
rodzic
7ed5a3efac
commit
601aeb83d7
|
@ -178,7 +178,9 @@ class SafeBluetooth(private val context: Context, private val device: BluetoothD
|
|||
}
|
||||
|
||||
override fun onMtuChanged(gatt: BluetoothGatt, mtu: Int, status: Int) {
|
||||
completeWork(status, mtu)
|
||||
// Alas, passing back an Int mtu isn't working and since I don't really care what MTU
|
||||
// the device was willing to let us have I'm just punting and returning Unit
|
||||
completeWork(status, Unit)
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -378,19 +380,18 @@ class SafeBluetooth(private val context: Context, private val device: BluetoothD
|
|||
|
||||
private fun queueRequestMtu(
|
||||
len: Int,
|
||||
cont: Continuation<Int>
|
||||
cont: Continuation<Unit>
|
||||
) = queueWork("reqMtu", cont) { gatt!!.requestMtu(len) }
|
||||
|
||||
fun asyncRequestMtu(
|
||||
len: Int,
|
||||
cb: (Result<Int>) -> Unit
|
||||
cb: (Result<Unit>) -> Unit
|
||||
) {
|
||||
logAssert(workQueue.isEmpty() && currentWork == null) // I don't think anything should be able to sneak in front
|
||||
queueRequestMtu(len, CallbackContinuation(cb))
|
||||
}
|
||||
|
||||
fun requestMtu(len: Int): Int =
|
||||
makeSync { queueRequestMtu(len, it) }
|
||||
fun requestMtu(len: Int): Unit = makeSync { queueRequestMtu(len, it) }
|
||||
|
||||
private fun queueWriteCharacteristic(
|
||||
c: BluetoothGattCharacteristic,
|
||||
|
|
|
@ -1,19 +1,15 @@
|
|||
package com.geeksville.mesh.service
|
||||
|
||||
import android.bluetooth.BluetoothAdapter
|
||||
import android.bluetooth.BluetoothDevice
|
||||
import android.bluetooth.BluetoothGattCharacteristic
|
||||
import android.bluetooth.BluetoothManager
|
||||
import android.bluetooth.le.ScanCallback
|
||||
import android.bluetooth.le.ScanFilter
|
||||
import android.bluetooth.le.ScanResult
|
||||
import android.bluetooth.le.ScanSettings
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.os.ParcelUuid
|
||||
import androidx.core.app.JobIntentService
|
||||
import com.geeksville.android.Logging
|
||||
import com.geeksville.mesh.MainActivity
|
||||
import com.geeksville.mesh.R
|
||||
import com.geeksville.util.exceptionReporter
|
||||
import java.util.*
|
||||
import java.util.zip.CRC32
|
||||
|
||||
|
@ -36,10 +32,11 @@ class SoftwareUpdateService : JobIntentService(), Logging {
|
|||
bluetoothManager.adapter!!
|
||||
}
|
||||
|
||||
lateinit var device: BluetoothDevice
|
||||
|
||||
fun startUpdate() {
|
||||
info("starting update")
|
||||
fun startUpdate(macaddr: String) {
|
||||
info("starting update to $macaddr")
|
||||
|
||||
val device = bluetoothAdapter.getRemoteDevice(macaddr)
|
||||
|
||||
val sync =
|
||||
SafeBluetooth(
|
||||
|
@ -47,147 +44,141 @@ class SoftwareUpdateService : JobIntentService(), Logging {
|
|||
device
|
||||
)
|
||||
|
||||
val firmwareStream = assets.open("firmware.bin")
|
||||
val firmwareCrc = CRC32()
|
||||
var firmwareNumSent = 0
|
||||
val firmwareSize = firmwareStream.available()
|
||||
|
||||
sync.connect()
|
||||
sync.use { _ ->
|
||||
sync.discoverServices() // Get our services
|
||||
|
||||
sync.discoverServices() // Get our services
|
||||
// we begin by setting our MTU size as high as it can go
|
||||
sync.requestMtu(512)
|
||||
|
||||
// we begin by setting our MTU size as high as it can go
|
||||
sync.requestMtu(512)
|
||||
val service = sync.gatt!!.services.find { it.uuid == SW_UPDATE_UUID }!!
|
||||
|
||||
val service = sync.gatt!!.services.find { it.uuid == SW_UPDATE_UUID }!!
|
||||
fun doFirmwareUpdate(assetName: String) {
|
||||
|
||||
val totalSizeDesc = service.getCharacteristic(SW_UPDATE_TOTALSIZE_CHARACTER)
|
||||
val dataDesc = service.getCharacteristic(SW_UPDATE_DATA_CHARACTER)
|
||||
val crc32Desc = service.getCharacteristic(SW_UPDATE_CRC32_CHARACTER)
|
||||
val updateResultDesc = service.getCharacteristic(SW_UPDATE_RESULT_CHARACTER)
|
||||
info("Starting firmware update for $assetName")
|
||||
|
||||
// Start the update by writing the # of bytes in the image
|
||||
logAssert(
|
||||
totalSizeDesc.setValue(
|
||||
firmwareSize,
|
||||
BluetoothGattCharacteristic.FORMAT_UINT32,
|
||||
0
|
||||
)
|
||||
)
|
||||
sync.writeCharacteristic(totalSizeDesc)
|
||||
val totalSizeDesc = service.getCharacteristic(SW_UPDATE_TOTALSIZE_CHARACTER)
|
||||
val dataDesc = service.getCharacteristic(SW_UPDATE_DATA_CHARACTER)
|
||||
val crc32Desc = service.getCharacteristic(SW_UPDATE_CRC32_CHARACTER)
|
||||
val updateResultDesc = service.getCharacteristic(SW_UPDATE_RESULT_CHARACTER)
|
||||
|
||||
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
|
||||
logAssert(
|
||||
totalSizeDesc.setValue(
|
||||
firmwareSize,
|
||||
BluetoothGattCharacteristic.FORMAT_UINT32,
|
||||
0
|
||||
)
|
||||
)
|
||||
sync.writeCharacteristic(totalSizeDesc)
|
||||
|
||||
// Our write completed, queue up a readback
|
||||
val totalSizeReadback = sync.readCharacteristic(totalSizeDesc)
|
||||
.getIntValue(BluetoothGattCharacteristic.FORMAT_UINT32, 0)
|
||||
if (totalSizeReadback == 0) // FIXME - handle this case
|
||||
throw Exception("Device rejected file size")
|
||||
val totalSizeReadback = sync.readCharacteristic(totalSizeDesc)
|
||||
.getIntValue(BluetoothGattCharacteristic.FORMAT_UINT32, 0)
|
||||
if (totalSizeReadback == 0) // FIXME - handle this case
|
||||
throw Exception("Device rejected file size")
|
||||
|
||||
// Send all the blocks
|
||||
while (firmwareNumSent < firmwareSize) {
|
||||
info("sending block ${firmwareNumSent * 100 / firmwareSize}%")
|
||||
var blockSize = 512 - 3 // Max size MTU excluding framing
|
||||
// Send all the blocks
|
||||
while (firmwareNumSent < firmwareSize) {
|
||||
debug("sending block ${firmwareNumSent * 100 / firmwareSize}%")
|
||||
var blockSize = 512 - 3 // Max size MTU excluding framing
|
||||
|
||||
if (blockSize > firmwareStream.available())
|
||||
blockSize = firmwareStream.available()
|
||||
val buffer = ByteArray(blockSize)
|
||||
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)
|
||||
// slightly expensive to keep reallocing this buffer, but whatever
|
||||
logAssert(firmwareStream.read(buffer) == blockSize)
|
||||
firmwareCrc.update(buffer)
|
||||
|
||||
// updateGatt.beginReliableWrite()
|
||||
dataDesc.value = buffer
|
||||
sync.writeCharacteristic(dataDesc)
|
||||
firmwareNumSent += blockSize
|
||||
}
|
||||
// updateGatt.beginReliableWrite()
|
||||
dataDesc.value = buffer
|
||||
sync.writeCharacteristic(dataDesc)
|
||||
firmwareNumSent += blockSize
|
||||
}
|
||||
|
||||
// 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")
|
||||
logAssert(crc32Desc.setValue(c.toInt(), BluetoothGattCharacteristic.FORMAT_UINT32, 0))
|
||||
sync.writeCharacteristic(crc32Desc)
|
||||
// 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")
|
||||
logAssert(
|
||||
crc32Desc.setValue(
|
||||
c.toInt(),
|
||||
BluetoothGattCharacteristic.FORMAT_UINT32,
|
||||
0
|
||||
)
|
||||
)
|
||||
sync.writeCharacteristic(crc32Desc)
|
||||
|
||||
// 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) // FIXME - handle this case
|
||||
throw Exception("Device update failed, reason=$updateResult")
|
||||
// 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) // FIXME - handle this case
|
||||
throw Exception("Device update failed, reason=$updateResult")
|
||||
|
||||
// FIXME perhaps ask device to reboot
|
||||
}
|
||||
|
||||
|
||||
private val scanCallback = object : ScanCallback() {
|
||||
override fun onScanFailed(errorCode: Int) {
|
||||
throw NotImplementedError()
|
||||
}
|
||||
|
||||
override fun onBatchScanResults(results: MutableList<ScanResult>?) {
|
||||
throw NotImplementedError()
|
||||
}
|
||||
|
||||
// For each device that appears in our scan, ask for its GATT, when the gatt arrives,
|
||||
// check if it is an eligable device and store it in our list of candidates
|
||||
// if that device later disconnects remove it as a candidate
|
||||
override fun onScanResult(callbackType: Int, result: ScanResult) {
|
||||
|
||||
info("onScanResult")
|
||||
|
||||
// We don't need any more results now
|
||||
bluetoothAdapter.bluetoothLeScanner.stopScan(this)
|
||||
|
||||
device = result.device
|
||||
}
|
||||
}
|
||||
|
||||
// Until my race condition with scanning is fixed
|
||||
fun connectToTestDevice() {
|
||||
device = bluetoothAdapter.getRemoteDevice("B4:E6:2D:EA:32:B7")
|
||||
}
|
||||
|
||||
private fun scanLeDevice(enable: Boolean) {
|
||||
when (enable) {
|
||||
true -> {
|
||||
// Stops scanning after a pre-defined scan period.
|
||||
/* handler.postDelayed({
|
||||
mScanning = false
|
||||
bluetoothAdapter.stopLeScan(leScanCallback)
|
||||
}, SCAN_PERIOD)
|
||||
mScanning = true */
|
||||
|
||||
val scanner = bluetoothAdapter.bluetoothLeScanner
|
||||
|
||||
// filter and only accept devices that have a sw update service
|
||||
val filter = ScanFilter.Builder().setServiceUuid(ParcelUuid(SW_UPDATE_UUID)).build()
|
||||
|
||||
/* ScanSettings.CALLBACK_TYPE_FIRST_MATCH seems to trigger a bug returning an error of
|
||||
SCAN_FAILED_OUT_OF_HARDWARE_RESOURCES (error #5)
|
||||
*/
|
||||
val settings =
|
||||
ScanSettings.Builder().setScanMode(ScanSettings.SCAN_MODE_LOW_LATENCY).
|
||||
// setMatchMode(ScanSettings.MATCH_NUM_ONE_ADVERTISEMENT).
|
||||
// setCallbackType(ScanSettings.CALLBACK_TYPE_FIRST_MATCH).
|
||||
build()
|
||||
scanner.startScan(listOf(filter), settings, scanCallback)
|
||||
// FIXME perhaps ask device to reboot
|
||||
}
|
||||
}
|
||||
else -> {
|
||||
// mScanning = false
|
||||
// bluetoothAdapter.stopLeScan(leScanCallback)
|
||||
|
||||
/// Return the filename this device needs to use as an update (or null if no update needed)
|
||||
fun getUpdateFilename(): String? {
|
||||
val hwVerDesc = service.getCharacteristic(HW_VERSION_CHARACTER)
|
||||
val mfgDesc = service.getCharacteristic(MANUFACTURE_CHARACTER)
|
||||
val swVerDesc = service.getCharacteristic(SW_VERSION_CHARACTER)
|
||||
|
||||
// looks like 1.0-US
|
||||
val hwVer = sync.readCharacteristic(hwVerDesc).getStringValue(0)
|
||||
|
||||
// looks like HELTEC
|
||||
val mfg = sync.readCharacteristic(mfgDesc).getStringValue(0)
|
||||
|
||||
// looks like 0.0.12
|
||||
val swVer = sync.readCharacteristic(swVerDesc).getStringValue(0)
|
||||
|
||||
val curver = getString(R.string.cur_firmware_version)
|
||||
|
||||
// FIXME, instead compare version strings carefully to see if less than
|
||||
val needsUpdate = (curver != swVer)
|
||||
|
||||
return if (!needsUpdate)
|
||||
null
|
||||
else {
|
||||
val regionRegex = Regex(".+-(.+)")
|
||||
val (region) = regionRegex.find(hwVer)?.destructured
|
||||
?: throw Exception("Malformed hw version")
|
||||
|
||||
"firmware/firmware-$mfg-$region-$curver.bin"
|
||||
}
|
||||
}
|
||||
|
||||
val updateFilename = getUpdateFilename()
|
||||
if (updateFilename != null) {
|
||||
doFirmwareUpdate(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.
|
||||
debug("Executing work: $intent")
|
||||
when (intent.action) {
|
||||
scanDevicesIntent.action -> scanLeDevice(true)
|
||||
startUpdateIntent.action -> {
|
||||
connectToTestDevice() // FIXME, pass in as an intent arg instead
|
||||
startUpdate()
|
||||
|
||||
// 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")
|
||||
}
|
||||
else -> TODO("Unhandled case")
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -197,8 +188,16 @@ class SoftwareUpdateService : JobIntentService(), Logging {
|
|||
*/
|
||||
private const val JOB_ID = 1000
|
||||
|
||||
val scanDevicesIntent = Intent("$prefix.SCAN_DEVICES")
|
||||
val startUpdateIntent = Intent("$prefix.START_UPDATE")
|
||||
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 EXTRA_MACADDR = "macaddr"
|
||||
|
||||
private const val SCAN_PERIOD: Long = 10000
|
||||
|
||||
|
@ -215,6 +214,10 @@ class SoftwareUpdateService : JobIntentService(), Logging {
|
|||
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_VERSION_CHARACTER = longBLEUUID("2a28")
|
||||
private val MANUFACTURE_CHARACTER = longBLEUUID("2a29")
|
||||
private val HW_VERSION_CHARACTER = longBLEUUID("2a27")
|
||||
|
||||
/**
|
||||
* Convenience method for enqueuing work in to this service.
|
||||
*/
|
||||
|
|
|
@ -1,8 +1,10 @@
|
|||
package com.geeksville.mesh.ui
|
||||
|
||||
import androidx.compose.Composable
|
||||
import androidx.compose.ambient
|
||||
import androidx.compose.state
|
||||
import androidx.ui.animation.Crossfade
|
||||
import androidx.ui.core.ContextAmbient
|
||||
import androidx.ui.core.Text
|
||||
import androidx.ui.layout.Column
|
||||
import androidx.ui.layout.Container
|
||||
|
@ -16,6 +18,8 @@ import com.geeksville.android.Logging
|
|||
import com.geeksville.mesh.R
|
||||
import com.geeksville.mesh.model.NodeDB
|
||||
import com.geeksville.mesh.model.UIState
|
||||
import com.geeksville.mesh.service.RadioInterfaceService
|
||||
import com.geeksville.mesh.service.SoftwareUpdateService
|
||||
|
||||
|
||||
object UILog : Logging
|
||||
|
@ -40,13 +44,33 @@ fun HomeContent() {
|
|||
)
|
||||
}
|
||||
|
||||
Text(if (UIState.isConnected.value) "Connected" else "Not Connected")
|
||||
if (UIState.isConnected.value) {
|
||||
Column {
|
||||
Text("Connected")
|
||||
|
||||
/// Create a software update button
|
||||
val context = ambient(ContextAmbient)
|
||||
RadioInterfaceService.getBondedDeviceAddress(context)?.let { macAddress ->
|
||||
Button(text = "Update firmware",
|
||||
onClick = {
|
||||
SoftwareUpdateService.enqueueWork(
|
||||
context,
|
||||
SoftwareUpdateService.startUpdateIntent(macAddress)
|
||||
)
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
Text("Not Connected")
|
||||
}
|
||||
}
|
||||
|
||||
NodeDB.nodes.values.forEach {
|
||||
NodeInfoCard(it)
|
||||
}
|
||||
|
||||
|
||||
/* FIXME - doens't work yet - probably because I'm not using release keys
|
||||
// If account is null, then show the signin button, otherwise
|
||||
val context = ambient(ContextAmbient)
|
||||
|
@ -62,22 +86,6 @@ fun HomeContent() {
|
|||
})
|
||||
}
|
||||
} */
|
||||
|
||||
/*
|
||||
Button(text = "Start scan",
|
||||
onClick = {
|
||||
if (bluetoothAdapter != null) {
|
||||
// Note: We don't want this service to die just because our activity goes away (because it is doing a software update)
|
||||
// So we use the application context instead of the activity
|
||||
SoftwareUpdateService.enqueueWork(
|
||||
applicationContext,
|
||||
SoftwareUpdateService.startUpdateIntent
|
||||
)
|
||||
}
|
||||
})
|
||||
|
||||
Button(text = "send packets",
|
||||
onClick = { sendTestPackets() }) */
|
||||
}
|
||||
}
|
||||
|
||||
|
|
Ładowanie…
Reference in New Issue