fix: USB/OTA update flow - version check, chunk size, UX improvements
- MainActivity: suppress toast when activity not resumed, auto-navigate to Settings on new firmware - SettingsActivity: unified BLE/USB radio buttons + single update button, version check before OTA, show dialog before loadFirmware, spinner timeout - BleRelayManager: expose getEspVersion() - OtaManager: reduce chunk size 500→248 to fit BLE MTU 255 - ESP: WRITE→WRITE_NR on OTA characteristic (was silently dropping data) - Layout/strings: remove intro text, add method radio buttons - Bump all firmware versions to v5
This commit is contained in:
parent
e05d57a32c
commit
0513e7777d
8 changed files with 67 additions and 32 deletions
Binary file not shown.
|
|
@ -217,6 +217,8 @@ class BleRelayManager(
|
|||
appVersion = version
|
||||
}
|
||||
|
||||
fun getEspVersion(): String? = espVersion
|
||||
|
||||
@SuppressLint("MissingPermission")
|
||||
private fun connectToDevice(device: android.bluetooth.BluetoothDevice) {
|
||||
handler.post { callbacks.onStatusChanged(BleStatus.Connecting) }
|
||||
|
|
|
|||
|
|
@ -22,7 +22,7 @@ class MainActivity : AppCompatActivity(), BleCallbacks {
|
|||
private const val REQUEST_SETTINGS = 1
|
||||
private const val POST_SETTINGS_RESCAN_MS = 500L
|
||||
private const val ON_RESUME_SCAN_DELAY_MS = 300L
|
||||
private const val BUNDLED_FIRMWARE_VERSION = "2"
|
||||
private const val BUNDLED_FIRMWARE_VERSION = "5"
|
||||
}
|
||||
|
||||
private lateinit var manager: BleRelayManager
|
||||
|
|
@ -222,8 +222,10 @@ class MainActivity : AppCompatActivity(), BleCallbacks {
|
|||
|
||||
override fun onFirmwareVersion(espVersion: String, appVersion: String) {
|
||||
runOnUiThread {
|
||||
if (isFinishing || isDestroyed || !lifecycle.currentState.isAtLeast(androidx.lifecycle.Lifecycle.State.RESUMED)) return@runOnUiThread
|
||||
if (espVersion.isEmpty() || espVersion != appVersion) {
|
||||
Toast.makeText(this, getString(R.string.ota_update_available), Toast.LENGTH_LONG).show()
|
||||
Toast.makeText(this, getString(R.string.ota_update_available), Toast.LENGTH_SHORT).show()
|
||||
startActivityForResult(Intent(this, SettingsActivity::class.java), REQUEST_SETTINGS)
|
||||
} else {
|
||||
Toast.makeText(this, getString(R.string.ota_up_to_date), Toast.LENGTH_SHORT).show()
|
||||
}
|
||||
|
|
|
|||
|
|
@ -23,7 +23,7 @@ class OtaManager(
|
|||
) {
|
||||
companion object {
|
||||
private const val TAG = "OtaManager"
|
||||
private const val OTA_CHUNK_DATA_SIZE = 500
|
||||
private const val OTA_CHUNK_DATA_SIZE = 248
|
||||
private const val OTA_CMD_START: Byte = 0x10
|
||||
private const val OTA_CMD_END: Byte = 0x11
|
||||
private const val OTA_STATUS_ACK: Byte = 0x00
|
||||
|
|
|
|||
|
|
@ -64,7 +64,7 @@ class SettingsActivity : AppCompatActivity() {
|
|||
private const val REBOOT_WAIT_MS = 5000L
|
||||
private const val REBOOT_POLL_MS = 500L
|
||||
private const val REBOOT_MAX_WAIT_MS = 15000L
|
||||
private const val EXPECTED_FIRMWARE_VERSION = "1"
|
||||
private const val EXPECTED_FIRMWARE_VERSION = "5"
|
||||
}
|
||||
|
||||
private enum class UsbAction { NONE, EXCHANGE_KEY, FLASH }
|
||||
|
|
@ -77,6 +77,8 @@ class SettingsActivity : AppCompatActivity() {
|
|||
private var otaManager: OtaManager? = null
|
||||
private var otaJob: Job? = null
|
||||
private var lastTheme: String? = null
|
||||
private val rebootTimeoutHandler = Handler(Looper.getMainLooper())
|
||||
private var rebootTimeoutRunnable: Runnable? = null
|
||||
|
||||
private val usbAttachedReceiver = object : BroadcastReceiver() {
|
||||
override fun onReceive(context: Context, intent: Intent) {
|
||||
|
|
@ -138,13 +140,12 @@ class SettingsActivity : AppCompatActivity() {
|
|||
supportActionBar?.setDisplayHomeAsUpEnabled(true)
|
||||
supportActionBar?.title = getString(R.string.title_settings)
|
||||
|
||||
findViewById<Button>(R.id.flashFirmwareButton).setOnClickListener {
|
||||
startFlashing()
|
||||
}
|
||||
|
||||
val otaButton = findViewById<Button>(R.id.otaUpdateButton)
|
||||
otaButton.setOnClickListener {
|
||||
startOtaUpdate()
|
||||
findViewById<Button>(R.id.updateModuleButton).setOnClickListener {
|
||||
if (findViewById<RadioGroup>(R.id.flashMethodGroup).checkedRadioButtonId == R.id.methodUsb) {
|
||||
startFlashing()
|
||||
} else {
|
||||
startOtaUpdate()
|
||||
}
|
||||
}
|
||||
|
||||
registerReceiver(usbAttachedReceiver, IntentFilter(UsbManager.ACTION_USB_DEVICE_ATTACHED), Context.RECEIVER_EXPORTED)
|
||||
|
|
@ -219,15 +220,26 @@ class SettingsActivity : AppCompatActivity() {
|
|||
Toast.makeText(this, getString(R.string.ota_needs_usb_first), Toast.LENGTH_LONG).show()
|
||||
return
|
||||
}
|
||||
otaManager = OtaManager(this, manager)
|
||||
|
||||
if (otaManager?.loadFirmware() != true) {
|
||||
Toast.makeText(this, getString(R.string.ota_load_failed), Toast.LENGTH_SHORT).show()
|
||||
val espVersion = manager.getEspVersion()
|
||||
if (espVersion != null && espVersion == EXPECTED_FIRMWARE_VERSION) {
|
||||
Toast.makeText(this, getString(R.string.ota_up_to_date), Toast.LENGTH_SHORT).show()
|
||||
return
|
||||
}
|
||||
|
||||
showOtaProgressDialog()
|
||||
|
||||
otaManager = OtaManager(this, manager)
|
||||
|
||||
if (otaManager?.loadFirmware() != true) {
|
||||
progressDialog?.dismiss()
|
||||
progressDialog = null
|
||||
Toast.makeText(this, getString(R.string.ota_load_failed), Toast.LENGTH_SHORT).show()
|
||||
return
|
||||
}
|
||||
|
||||
manager.enableOtaNotifications()
|
||||
|
||||
otaJob = lifecycleScope.launch {
|
||||
val result = otaManager!!.performOta(object : OtaManager.OtaCallback {
|
||||
override fun onProgress(percent: Int, message: String) {
|
||||
|
|
@ -267,6 +279,8 @@ class SettingsActivity : AppCompatActivity() {
|
|||
|
||||
override fun onDestroy() {
|
||||
super.onDestroy()
|
||||
rebootTimeoutRunnable?.let { rebootTimeoutHandler.removeCallbacks(it) }
|
||||
rebootTimeoutRunnable = null
|
||||
currentAction = UsbAction.NONE
|
||||
flasherJob?.cancel()
|
||||
flasherJob = null
|
||||
|
|
@ -396,9 +410,10 @@ class SettingsActivity : AppCompatActivity() {
|
|||
lifecycleScope.launch {
|
||||
val version = readEspVersion(usbManager, device)
|
||||
if (version == EXPECTED_FIRMWARE_VERSION && !expectingReboot) {
|
||||
Log.d(TAG, "Firmware version $version matches, skipping flash")
|
||||
Log.d(TAG, "Firmware version $version matches, up to date")
|
||||
progressDialog?.dismiss()
|
||||
progressDialog = null
|
||||
Toast.makeText(this@SettingsActivity, getString(R.string.ota_up_to_date), Toast.LENGTH_SHORT).show()
|
||||
currentAction = UsbAction.EXCHANGE_KEY
|
||||
doKeyExchange(usbManager, device)
|
||||
return@launch
|
||||
|
|
@ -437,6 +452,8 @@ class SettingsActivity : AppCompatActivity() {
|
|||
getString(R.string.title_firmware_flash),
|
||||
getString(R.string.msg_env_waiting_reboot)
|
||||
)
|
||||
rebootTimeoutRunnable = Runnable { cleanupAction() }
|
||||
rebootTimeoutHandler.postDelayed(rebootTimeoutRunnable!!, REBOOT_MAX_WAIT_MS)
|
||||
} catch (e: kotlinx.coroutines.CancellationException) {
|
||||
cleanupAction()
|
||||
throw e
|
||||
|
|
@ -453,6 +470,8 @@ class SettingsActivity : AppCompatActivity() {
|
|||
// === USB dispatch ===
|
||||
|
||||
private fun onDeviceReady(usbManager: UsbManager, device: UsbDevice) {
|
||||
rebootTimeoutRunnable?.let { rebootTimeoutHandler.removeCallbacks(it) }
|
||||
rebootTimeoutRunnable = null
|
||||
when (currentAction) {
|
||||
UsbAction.EXCHANGE_KEY -> doKeyExchange(usbManager, device)
|
||||
UsbAction.FLASH -> doFlashing(usbManager, device)
|
||||
|
|
@ -501,6 +520,8 @@ class SettingsActivity : AppCompatActivity() {
|
|||
private fun cleanupAction() {
|
||||
currentAction = UsbAction.NONE
|
||||
expectingReboot = false
|
||||
rebootTimeoutRunnable?.let { rebootTimeoutHandler.removeCallbacks(it) }
|
||||
rebootTimeoutRunnable = null
|
||||
dialog?.dismiss()
|
||||
dialog = null
|
||||
progressDialog?.dismiss()
|
||||
|
|
|
|||
|
|
@ -5,25 +5,36 @@
|
|||
android:orientation="vertical"
|
||||
android:padding="24dp">
|
||||
|
||||
<TextView
|
||||
<RadioGroup
|
||||
android:id="@+id/flashMethodGroup"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/msg_flash_mode_help"
|
||||
android:textSize="14sp"
|
||||
android:layout_marginBottom="16dp" />
|
||||
android:orientation="horizontal"
|
||||
android:layout_marginBottom="8dp"
|
||||
android:checkedButton="@id/methodBluetooth">
|
||||
|
||||
<RadioButton
|
||||
android:id="@+id/methodBluetooth"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:gravity="center"
|
||||
android:text="@string/method_bluetooth" />
|
||||
|
||||
<RadioButton
|
||||
android:id="@+id/methodUsb"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:gravity="center"
|
||||
android:text="@string/method_usb" />
|
||||
</RadioGroup>
|
||||
|
||||
<Button
|
||||
android:id="@+id/flashFirmwareButton"
|
||||
android:id="@+id/updateModuleButton"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/button_firmware_flash" />
|
||||
|
||||
<Button
|
||||
android:id="@+id/otaUpdateButton"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/button_ota_update"
|
||||
android:layout_marginTop="8dp" />
|
||||
android:text="@string/button_update_module" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/otaStatusText"
|
||||
|
|
|
|||
|
|
@ -27,9 +27,9 @@
|
|||
|
||||
<!-- SettingsActivity -->
|
||||
<string name="title_settings">Instellingen</string>
|
||||
<string name="msg_settings_intro">Sluit de ESP aan op USB en kies een actie.</string>
|
||||
<string name="msg_flash_mode_help">Sluit de module aan op de USB-poort.</string>
|
||||
<string name="button_firmware_flash">Firmware bijwerken</string>
|
||||
<string name="method_bluetooth">Bluetooth</string>
|
||||
<string name="method_usb">USB</string>
|
||||
<string name="button_update_module">Module bijwerken</string>
|
||||
<string name="usb_access_denied">USB-toegang geweigerd</string>
|
||||
<string name="key_read_success">Beveiligingssleutel gelezen</string>
|
||||
<string name="key_exchange_failed">Sleutel uitwisselen mislukt - probeer opnieuw</string>
|
||||
|
|
@ -104,8 +104,7 @@
|
|||
<string name="theme_applied">Thema: %s</string>
|
||||
|
||||
<!-- OTA Updates -->
|
||||
<string name="button_ota_update">Firmware bijwerken via Bluetooth</string>
|
||||
<string name="title_ota_update">OTA Firmware Update</string>
|
||||
<string name="title_ota_update">Module bijwerken</string>
|
||||
<string name="msg_ota_starting">OTA update starten...</string>
|
||||
<string name="ota_update_available">Nieuwe firmware beschikbaar</string>
|
||||
<string name="ota_up_to_date">Firmware is up‑to‑date</string>
|
||||
|
|
|
|||
|
|
@ -34,7 +34,7 @@
|
|||
#define NVS_NAMESPACE "hmac"
|
||||
#define NVS_KEY_NAME "key"
|
||||
#define NVS_UUID_NAME "uuid"
|
||||
#define FIRMWARE_VERSION "2"
|
||||
#define FIRMWARE_VERSION "5"
|
||||
|
||||
NimBLECharacteristic *pCharacteristic;
|
||||
bool deviceConnected = false;
|
||||
|
|
@ -343,7 +343,7 @@ void setup() {
|
|||
|
||||
NimBLECharacteristic *pOtaChar = pService->createCharacteristic(
|
||||
OTA_UUID,
|
||||
NIMBLE_PROPERTY::WRITE | NIMBLE_PROPERTY::NOTIFY
|
||||
NIMBLE_PROPERTY::WRITE_NR | NIMBLE_PROPERTY::NOTIFY
|
||||
);
|
||||
pOtaChar->setCallbacks(new OtaCharacteristicCallbacks());
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue