kopia lustrzana https://github.com/meshtastic/Meshtastic-Android
support spiffs updates over OTA (not yet tested)
for https://github.com/meshtastic/Meshtastic-device/issues/496pull/203/head
rodzic
b854c57aa4
commit
31e0136b2a
|
@ -1,5 +1,8 @@
|
||||||
package com.geeksville.mesh.service
|
package com.geeksville.mesh.service
|
||||||
|
|
||||||
import java.io.IOException
|
import java.io.IOException
|
||||||
|
import java.util.*
|
||||||
|
|
||||||
open class BLEException(msg: String) : IOException(msg)
|
open class BLEException(msg: String) : IOException(msg)
|
||||||
|
|
||||||
|
open class BLECharacteristicNotFoundException(uuid: UUID) : BLEException("Can't get characteristic $uuid")
|
|
@ -489,6 +489,6 @@ class BluetoothInterface(val service: RadioInterfaceService, val address: String
|
||||||
* Get a chracteristic, but in a safe manner because some buggy BLE implementations might return null
|
* Get a chracteristic, but in a safe manner because some buggy BLE implementations might return null
|
||||||
*/
|
*/
|
||||||
private fun getCharacteristic(uuid: UUID) =
|
private fun getCharacteristic(uuid: UUID) =
|
||||||
bservice.getCharacteristic(uuid) ?: throw BLEException("Can't get characteristic $uuid")
|
bservice.getCharacteristic(uuid) ?: throw BLECharacteristicNotFoundException(uuid)
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1334,7 +1334,7 @@ class MeshService : Service(), Logging {
|
||||||
return 0 // We don't have mynodeinfo yet, so just let the radio eventually assign an ID
|
return 0 // We don't have mynodeinfo yet, so just let the radio eventually assign an ID
|
||||||
}
|
}
|
||||||
|
|
||||||
var firmwareUpdateFilename: String? = null
|
var firmwareUpdateFilename: UpdateFilenames? = null
|
||||||
|
|
||||||
/***
|
/***
|
||||||
* Return the filename we will install on the device
|
* Return the filename we will install on the device
|
||||||
|
|
|
@ -18,7 +18,12 @@ import java.util.zip.CRC32
|
||||||
*/
|
*/
|
||||||
fun toNetworkByteArray(value: Int, formatType: Int): ByteArray {
|
fun toNetworkByteArray(value: Int, formatType: Int): ByteArray {
|
||||||
|
|
||||||
val len: Int = 4 // getTypeLen(formatType)
|
val len = when (formatType) {
|
||||||
|
BluetoothGattCharacteristic.FORMAT_UINT8 -> 1
|
||||||
|
BluetoothGattCharacteristic.FORMAT_UINT32 -> 4
|
||||||
|
else -> TODO()
|
||||||
|
}
|
||||||
|
|
||||||
val mValue = ByteArray(len)
|
val mValue = ByteArray(len)
|
||||||
|
|
||||||
when (formatType) {
|
when (formatType) {
|
||||||
|
@ -44,6 +49,9 @@ fun toNetworkByteArray(value: Int, formatType: Int): ByteArray {
|
||||||
mValue.get(offset++) = (value shr 16 and 0xFF).toByte()
|
mValue.get(offset++) = (value shr 16 and 0xFF).toByte()
|
||||||
mValue.get(offset) = (value shr 24 and 0xFF).toByte()
|
mValue.get(offset) = (value shr 24 and 0xFF).toByte()
|
||||||
} */
|
} */
|
||||||
|
BluetoothGattCharacteristic.FORMAT_UINT8 ->
|
||||||
|
mValue[0] = (value and 0xFF).toByte()
|
||||||
|
|
||||||
BluetoothGattCharacteristic.FORMAT_UINT32 -> {
|
BluetoothGattCharacteristic.FORMAT_UINT32 -> {
|
||||||
mValue[0] = (value and 0xFF).toByte()
|
mValue[0] = (value and 0xFF).toByte()
|
||||||
mValue[1] = (value shr 8 and 0xFF).toByte()
|
mValue[1] = (value shr 8 and 0xFF).toByte()
|
||||||
|
@ -55,6 +63,9 @@ fun toNetworkByteArray(value: Int, formatType: Int): ByteArray {
|
||||||
return mValue
|
return mValue
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
data class UpdateFilenames(val appLoad: String?, val spiffs: String?)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* typical flow
|
* typical flow
|
||||||
*
|
*
|
||||||
|
@ -152,6 +163,8 @@ class SoftwareUpdateService : JobIntentService(), Logging {
|
||||||
UUID.fromString("4826129c-c22a-43a3-b066-ce8f0d5bacc6") // write crc32, write last - writing this will complete the OTA operation, now you can read result
|
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 =
|
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
|
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 spiffs)
|
||||||
|
|
||||||
private val SW_VERSION_CHARACTER = longBLEUUID("2a28")
|
private val SW_VERSION_CHARACTER = longBLEUUID("2a28")
|
||||||
private val MANUFACTURE_CHARACTER = longBLEUUID("2a29")
|
private val MANUFACTURE_CHARACTER = longBLEUUID("2a29")
|
||||||
|
@ -210,28 +223,36 @@ class SoftwareUpdateService : JobIntentService(), Logging {
|
||||||
false // If we fail parsing our update info
|
false // If we fail parsing our update info
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Return the filename this device needs to use as an update (or null if no update needed)
|
/** Return a Pair of apploadfilename, spiffs filename this device needs to use as an update (or null if no update needed)
|
||||||
*/
|
*/
|
||||||
fun getUpdateFilename(
|
fun getUpdateFilename(
|
||||||
context: Context,
|
context: Context,
|
||||||
mfg: String
|
mfg: String
|
||||||
): String? {
|
): UpdateFilenames {
|
||||||
val curver = context.getString(R.string.cur_firmware_version)
|
val curver = context.getString(R.string.cur_firmware_version)
|
||||||
|
|
||||||
val base = "firmware-$mfg-$curver.bin"
|
|
||||||
|
|
||||||
// Check to see if the file exists (some builds might not include update files for size reasons)
|
// 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 firmwareFiles = context.assets.list("firmware") ?: arrayOf()
|
||||||
return if (firmwareFiles.contains(base))
|
|
||||||
"firmware/$base"
|
val appLoad = "firmware-$mfg-$curver.bin"
|
||||||
else
|
val spiffs = "spiffs-$curver.bin"
|
||||||
null
|
|
||||||
|
return UpdateFilenames(
|
||||||
|
if (firmwareFiles.contains(appLoad))
|
||||||
|
"firmware/$appLoad"
|
||||||
|
else
|
||||||
|
null,
|
||||||
|
if (firmwareFiles.contains(spiffs))
|
||||||
|
"firmware/$spiffs"
|
||||||
|
else
|
||||||
|
null
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Return the filename this device needs to use as an update (or null if no update needed)
|
/** 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
|
* No longer used, because we get update info inband from our radio API
|
||||||
*/
|
*/
|
||||||
fun getUpdateFilename(context: Context, sync: SafeBluetooth): String? {
|
fun getUpdateFilename(context: Context, sync: SafeBluetooth): UpdateFilenames? {
|
||||||
val service = sync.gatt!!.services.find { it.uuid == SW_UPDATE_UUID }!!
|
val service = sync.gatt!!.services.find { it.uuid == SW_UPDATE_UUID }!!
|
||||||
|
|
||||||
//val hwVerDesc = service.getCharacteristic(HW_VERSION_CHARACTER)
|
//val hwVerDesc = service.getCharacteristic(HW_VERSION_CHARACTER)
|
||||||
|
@ -248,19 +269,66 @@ class SoftwareUpdateService : JobIntentService(), Logging {
|
||||||
* A public function so that if you have your own SafeBluetooth connection already open
|
* A public function so that if you have your own SafeBluetooth connection already open
|
||||||
* you can use it for the software update.
|
* you can use it for the software update.
|
||||||
*/
|
*/
|
||||||
fun doUpdate(context: Context, sync: SafeBluetooth, assetName: String) {
|
fun doUpdate(context: Context, sync: SafeBluetooth, assets: UpdateFilenames) {
|
||||||
|
// we must attempt spiffs first, because if we update the appload the device will reboot afterwards
|
||||||
|
try {
|
||||||
|
assets.spiffs?.let { doUpdate(context, sync, it, FLASH_REGION_SPIFFS) }
|
||||||
|
}
|
||||||
|
catch(_: BLECharacteristicNotFoundException) {
|
||||||
|
// If we can't update spiffs (because not supported by target), do not fail
|
||||||
|
errormsg("Ignoring failure to update spiffs on old appload")
|
||||||
|
}
|
||||||
|
assets.appLoad?.let { doUpdate(context, sync, it, FLASH_REGION_APPLOAD) }
|
||||||
|
progress = -1 // success
|
||||||
|
}
|
||||||
|
|
||||||
|
// writable region codes in the ESP32 update code
|
||||||
|
private val FLASH_REGION_APPLOAD = 0
|
||||||
|
private val FLASH_REGION_SPIFFS = 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) {
|
||||||
try {
|
try {
|
||||||
val g = sync.gatt!!
|
val g = sync.gatt!!
|
||||||
val service = g.services.find { it.uuid == SW_UPDATE_UUID }
|
val service = g.services.find { it.uuid == SW_UPDATE_UUID }
|
||||||
?: throw BLEException("Couldn't find update service")
|
?: throw BLEException("Couldn't find update service")
|
||||||
|
|
||||||
info("Starting firmware update for $assetName")
|
/**
|
||||||
|
* 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")
|
||||||
|
|
||||||
progress = 0
|
progress = 0
|
||||||
val totalSizeDesc = service.getCharacteristic(SW_UPDATE_TOTALSIZE_CHARACTER)
|
val totalSizeDesc = getCharacteristic(SW_UPDATE_TOTALSIZE_CHARACTER)
|
||||||
val dataDesc = service.getCharacteristic(SW_UPDATE_DATA_CHARACTER)
|
val dataDesc = getCharacteristic(SW_UPDATE_DATA_CHARACTER)
|
||||||
val crc32Desc = service.getCharacteristic(SW_UPDATE_CRC32_CHARACTER)
|
val crc32Desc = getCharacteristic(SW_UPDATE_CRC32_CHARACTER)
|
||||||
val updateResultDesc = service.getCharacteristic(SW_UPDATE_RESULT_CHARACTER)
|
val updateResultDesc = getCharacteristic(SW_UPDATE_RESULT_CHARACTER)
|
||||||
|
|
||||||
|
/// Try to set the destination region for programming (spiffs vs appload etc)
|
||||||
|
/// Old apploads don't have this feature, but we only fail if the user was trying to set a
|
||||||
|
/// spiffs - 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) {
|
||||||
|
errormsg("Can't set flash programming region (old appload?)")
|
||||||
|
throw ex
|
||||||
|
}
|
||||||
|
warn("Ignoring setting appload flashRegion (old appload?")
|
||||||
|
}
|
||||||
|
|
||||||
context.assets.open(assetName).use { firmwareStream ->
|
context.assets.open(assetName).use { firmwareStream ->
|
||||||
val firmwareCrc = CRC32()
|
val firmwareCrc = CRC32()
|
||||||
|
@ -320,8 +388,6 @@ class SoftwareUpdateService : JobIntentService(), Logging {
|
||||||
// We might get SyncContinuation timeout on the final write, assume the device simply rebooted to run the new load and we missed it
|
// 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)
|
errormsg("Assuming successful update", ex)
|
||||||
}
|
}
|
||||||
|
|
||||||
progress = -1 // success
|
|
||||||
}
|
}
|
||||||
} catch (ex: BLEException) {
|
} catch (ex: BLEException) {
|
||||||
progress = -3
|
progress = -3
|
||||||
|
|
Ładowanie…
Reference in New Issue