diff --git a/android/app/src/main/java/com/eddy/atomrelay/EspFlasher.kt b/android/app/src/main/java/com/eddy/atomrelay/EspFlasher.kt new file mode 100644 index 0000000..03e3eef --- /dev/null +++ b/android/app/src/main/java/com/eddy/atomrelay/EspFlasher.kt @@ -0,0 +1,134 @@ +package com.eddy.atomrelay + +import android.content.Context +import android.hardware.usb.UsbConstants +import android.hardware.usb.UsbDevice +import android.hardware.usb.UsbManager +import android.os.Handler +import android.os.Looper +import android.util.Log +import org.dkaukov.esp32.core.EspFlasherApi +import org.dkaukov.esp32.io.ProgressCallback +import java.io.IOException + +class EspFlasher(private val context: Context) { + + companion object { + private const val TAG = "EspFlasher" + } + + interface FlashCallback { + fun onProgress(percent: Int, message: String) + fun onComplete() + fun onError(message: String) + } + + private val handler = Handler(Looper.getMainLooper()) + + fun flashDevice(usbManager: UsbManager, device: UsbDevice, callback: FlashCallback) { + Thread { + try { + doFlash(usbManager, device, callback) + } catch (e: Exception) { + Log.e(TAG, "Flash failed", e) + handler.post { callback.onError(e.message ?: "Onbekende fout") } + } + }.start() + } + + private class FlashStep( + val name: String, + val address: Int, + val data: ByteArray, + val baseProgress: Int, + val range: Int, + val compress: Boolean + ) + + private fun doFlash(usbManager: UsbManager, device: UsbDevice, callback: FlashCallback) { + Thread.sleep(500) + + val connection = usbManager.openDevice(device) + ?: throw IOException("Kan USB-apparaat niet openen") + + try { + val intf = UsbUtils.findCdcDataInterface(device) + ?: throw IOException("Geen CDC data interface gevonden") + + if (!connection.claimInterface(intf, true)) { + throw IOException("Kan USB interface niet claimen") + } + + val epOut = UsbUtils.findBulkEndpoint(intf, UsbConstants.USB_DIR_OUT) + ?: throw IOException("Geen USB OUT endpoint gevonden") + val epIn = UsbUtils.findBulkEndpoint(intf, UsbConstants.USB_DIR_IN) + ?: throw IOException("Geen USB IN endpoint gevonden") + + postProgress(callback, 0, "Verbinden met ESP bootloader...") + + val transport = UsbSerialTransport(connection, epOut, epIn) + + val assets = context.assets + val bootloader = assets.open("firmware/bootloader.bin").readBytes() + val partitions = assets.open("firmware/partitions.bin").readBytes() + val bootApp0 = assets.open("firmware/boot_app0.bin").readBytes() + val firmware = assets.open("firmware/firmware.bin").readBytes() + + val totalSize = bootloader.size + partitions.size + bootApp0.size + firmware.size + + val steps = listOf( + FlashStep("Bootloader", 0x0, bootloader, 30, 10, false), + FlashStep("Partitie tabel", 0x8000, partitions, 40, 10, false), + FlashStep("Boot app0", 0xe000, bootApp0, 50, 10, false), + FlashStep("Firmware", 0x10000, firmware, 60, 35, true) + ) + + postProgress(callback, 5, "Sync met bootloader...") + val api = EspFlasherApi.connect(transport) + .withCallBack(object : ProgressCallback { + override fun onProgress(pct: Float) { + Log.d(TAG, "Lib progress: $pct%") + } + override fun onInfo(value: String) { + Log.d(TAG, "Info: $value") + } + }) + + postProgress(callback, 10, "Chip detecteren...") + val detected = api.chipDetect() + val chipId = detected.getChipId() + Log.i(TAG, "Detected chip: $chipId") + postProgress(callback, 15, "Chip: ${chipId.readableName}") + + postProgress(callback, 20, "Stub laden...") + val stub = detected.loadStub() + + postProgress(callback, 25, "Flash wissen...") + stub.eraseFlash() + + for (step in steps) { + val sizeStr = if (step.data.size > 1024) "${step.data.size / 1024}KB" else "${step.data.size} bytes" + postProgress(callback, step.baseProgress, "${step.name} schrijven ($sizeStr)...") + + val writeStub = if (step.compress) stub.withCompression(true) else stub.withCompression(false) + writeStub.writeFlash(step.address, step.data) + + val endProgress = step.baseProgress + step.range + postProgress(callback, endProgress.coerceAtMost(95), "${step.name} geschreven") + } + + postProgress(callback, 95, "ESP resetten...") + stub.reset() + + postProgress(callback, 100, "Firmware geflashed!") + handler.post { callback.onComplete() } + } finally { + connection.close() + } + } + + private fun postProgress(callback: FlashCallback, percent: Int, message: String) { + Log.d(TAG, "Progress $percent%: $message") + handler.post { callback.onProgress(percent, message) } + } +}