BLE sw update kinda works again

This commit is contained in:
geeksville 2020-02-24 15:47:53 -08:00
parent 7ed5a3efac
commit 601aeb83d7
3 changed files with 163 additions and 151 deletions

View file

@ -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.
*/