Move all user-facing strings to strings.xml, add user-facing-text.md reference

This commit is contained in:
eddy 2026-07-29 20:48:20 +02:00
commit c46db32cc7
6 changed files with 195 additions and 55 deletions

View file

@ -39,7 +39,7 @@ class EspFlasher(private val context: Context) {
}
private class FlashStep(
val name: String,
val nameResId: Int,
val address: Int,
val data: ByteArray,
val baseProgress: Int,
@ -52,22 +52,22 @@ class EspFlasher(private val context: Context) {
coroutineContext.ensureActive()
val connection = usbManager.openDevice(device)
?: throw IOException("Kan USB-apparaat niet openen")
?: throw IOException(context.getString(R.string.err_usb_open))
try {
val intf = UsbUtils.findCdcDataInterface(device)
?: throw IOException("Geen CDC data interface gevonden")
?: throw IOException(context.getString(R.string.err_no_cdc))
if (!connection.claimInterface(intf, true)) {
throw IOException("Kan USB interface niet claimen")
throw IOException(context.getString(R.string.err_usb_claim))
}
val epOut = UsbUtils.findBulkEndpoint(intf, UsbConstants.USB_DIR_OUT)
?: throw IOException("Geen USB OUT endpoint gevonden")
?: throw IOException(context.getString(R.string.err_no_out))
val epIn = UsbUtils.findBulkEndpoint(intf, UsbConstants.USB_DIR_IN)
?: throw IOException("Geen USB IN endpoint gevonden")
?: throw IOException(context.getString(R.string.err_no_in))
callback.onProgress(0, "Verbinden met ESP bootloader...")
callback.onProgress(0, context.getString(R.string.flash_connecting))
val transport = UsbSerialTransport(connection, epOut, epIn)
coroutineContext.ensureActive()
@ -78,20 +78,14 @@ class EspFlasher(private val context: Context) {
val bootApp0 = assets.open("firmware/boot_app0.bin").readBytes()
val firmware = assets.open("firmware/firmware.bin").readBytes()
// Flash sectie layout (partition table check):
// NVS 0x9000-0xDFFF — NIET overschreven (key/UUID blijft behouden)
// otadata 0xE000-0xFFFF — overschreven door boot_app0
// app0 0x10000-... — firmware overschrijft
// Alleen schrijven naar benodigde adressen, geen full erase
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)
FlashStep(R.string.flash_step_bootloader, 0x0, bootloader, 30, 10, false),
FlashStep(R.string.flash_step_partitions, 0x8000, partitions, 40, 10, false),
FlashStep(R.string.flash_step_boot_app0, 0xe000, bootApp0, 50, 10, false),
FlashStep(R.string.flash_step_firmware, 0x10000, firmware, 60, 35, true)
)
callback.onProgress(5, "Sync met bootloader...")
callback.onProgress(5, context.getString(R.string.flash_sync))
val api = EspFlasherApi.connect(transport)
.withCallBack(object : ProgressCallback {
override fun onProgress(pct: Float) {
@ -102,31 +96,32 @@ class EspFlasher(private val context: Context) {
}
})
callback.onProgress(10, "Chip detecteren...")
callback.onProgress(10, context.getString(R.string.flash_detect))
val detected = api.chipDetect()
val chipId = detected.getChipId()
Log.i(TAG, "Detected chip: $chipId")
callback.onProgress(15, "Chip: ${chipId.readableName}")
callback.onProgress(15, context.getString(R.string.flash_chip, chipId.readableName))
callback.onProgress(20, "Stub laden...")
callback.onProgress(20, context.getString(R.string.flash_stub))
val stub = detected.loadStub()
for (step in steps) {
coroutineContext.ensureActive()
val stepName = context.getString(step.nameResId)
val sizeStr = if (step.data.size > 1024) "${step.data.size / 1024}KB" else "${step.data.size} bytes"
callback.onProgress(step.baseProgress, "${step.name} schrijven ($sizeStr)...")
callback.onProgress(step.baseProgress, "$stepName 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
callback.onProgress(endProgress.coerceAtMost(95), "${step.name} geschreven")
callback.onProgress(endProgress.coerceAtMost(95), "$stepName geschreven")
}
callback.onProgress(95, "ESP resetten...")
stub.reset()
callback.onProgress(100, "Firmware geflashed!")
callback.onProgress(100, context.getString(R.string.flash_done))
} finally {
connection.close()
}

View file

@ -278,7 +278,8 @@ class SettingsActivity : AppCompatActivity() {
showConflictDialog(usbManager, device, result.espUuid, result.espKey, appKey ?: "")
}
is KeyResult.Error -> {
Toast.makeText(this@SettingsActivity, getString(R.string.key_exchange_failed), Toast.LENGTH_LONG).show()
val msg = if (result.arg != null) getString(result.resId, result.arg!!) else getString(result.resId)
Toast.makeText(this@SettingsActivity, msg, Toast.LENGTH_LONG).show()
cleanupAction()
}
null -> {
@ -431,25 +432,25 @@ class SettingsActivity : AppCompatActivity() {
sealed class KeyResult {
data class Success(val deviceUuid: String, val hexKey: String) : KeyResult()
data class Conflict(val espUuid: String, val espKey: String) : KeyResult()
data class Error(val message: String) : KeyResult()
data class Error(val resId: Int, val arg: String? = null) : KeyResult()
}
private fun readKeyFromEsp(usbManager: UsbManager, device: UsbDevice, appUuid: String?, appKey: String?): KeyResult? {
val intf = UsbUtils.findCdcDataInterface(device)
?: return KeyResult.Error("Geen CDC interface gevonden")
?: return KeyResult.Error(R.string.err_no_cdc)
val connection = usbManager.openDevice(device)
?: return KeyResult.Error("Kan USB apparaat niet openen")
?: return KeyResult.Error(R.string.err_usb_open)
if (!connection.claimInterface(intf, true)) {
connection.close()
return KeyResult.Error("Kan USB interface niet claimen")
return KeyResult.Error(R.string.err_usb_claim)
}
val epOut = UsbUtils.findBulkEndpoint(intf, UsbConstants.USB_DIR_OUT)
?: run { connection.close(); return KeyResult.Error("Geen USB OUT endpoint gevonden") }
?: run { connection.close(); return KeyResult.Error(R.string.err_no_out) }
val epIn = UsbUtils.findBulkEndpoint(intf, UsbConstants.USB_DIR_IN)
?: run { connection.close(); return KeyResult.Error("Geen USB IN endpoint gevonden") }
?: run { connection.close(); return KeyResult.Error(R.string.err_no_in) }
try {
setupCdcControl(connection, device)
@ -489,7 +490,7 @@ class SettingsActivity : AppCompatActivity() {
}
return KeyResult.Success(espUuidUpper, espKeyUpper)
} else {
return KeyResult.Error("Ongeldig sleutel/UUID formaat")
return KeyResult.Error(R.string.err_invalid_key)
}
} else if (noKey) {
val newKey: String
@ -508,7 +509,7 @@ class SettingsActivity : AppCompatActivity() {
val w = connection.bulkTransfer(epOut, setKeyCmd, setKeyCmd.size, USB_BULK_TIMEOUT_MS)
Log.d(TAG, "Sent SET_KEY ($w bytes)")
if (w < 0) {
return KeyResult.Error("SET_KEY schrijven mislukt")
return KeyResult.Error(R.string.err_set_key_write)
}
Thread.sleep(RESPONSE_WAIT_MS)
@ -516,14 +517,14 @@ class SettingsActivity : AppCompatActivity() {
Log.d(TAG, "SET_KEY response: ${okKeyResponse.take(200)}")
if (!okKeyResponse.contains("OK")) {
return KeyResult.Error("ESP bevestigde sleutel niet")
return KeyResult.Error(R.string.err_key_not_ack)
}
val setIdCmd = "SET_ID:$newUuid\n".toByteArray(StandardCharsets.US_ASCII)
val w2 = connection.bulkTransfer(epOut, setIdCmd, setIdCmd.size, USB_BULK_TIMEOUT_MS)
Log.d(TAG, "Sent SET_ID ($w2 bytes)")
if (w2 < 0) {
return KeyResult.Error("SET_ID schrijven mislukt")
return KeyResult.Error(R.string.err_set_id_write)
}
Thread.sleep(RESPONSE_WAIT_MS)
@ -533,17 +534,17 @@ class SettingsActivity : AppCompatActivity() {
if (okUuidResponse.contains("OK")) {
return KeyResult.Success(newUuid, newKey)
} else {
return KeyResult.Error("ESP bevestigde UUID niet")
return KeyResult.Error(R.string.err_uuid_not_ack)
}
}
Log.w(TAG, "Attempt $attempt: no KEY response, retrying")
}
return KeyResult.Error("Geen sleutel gevonden na 5 pogingen")
return KeyResult.Error(R.string.err_key_retries)
} catch (e: Exception) {
Log.e(TAG, "Key exchange error", e)
return KeyResult.Error("Fout: ${e.message}")
return KeyResult.Error(R.string.err_generic, e.message)
} finally {
try { connection.close() } catch (_: Exception) {}
}
@ -692,7 +693,8 @@ class SettingsActivity : AppCompatActivity() {
finish()
}
is KeyResult.Error -> {
Toast.makeText(this@SettingsActivity, result.message, Toast.LENGTH_LONG).show()
val msg = if (result.arg != null) getString(result.resId, result.arg!!) else getString(result.resId)
Toast.makeText(this@SettingsActivity, msg, Toast.LENGTH_LONG).show()
cleanupAction()
}
else -> {
@ -707,20 +709,20 @@ class SettingsActivity : AppCompatActivity() {
private fun writeKeysToEsp(usbManager: UsbManager, device: UsbDevice,
appKey: String, appUuid: String): KeyResult? {
val intf = UsbUtils.findCdcDataInterface(device)
?: return KeyResult.Error("Geen CDC interface gevonden")
?: return KeyResult.Error(R.string.err_no_cdc)
val connection = usbManager.openDevice(device)
?: return KeyResult.Error("Kan USB apparaat niet openen")
?: return KeyResult.Error(R.string.err_usb_open)
if (!connection.claimInterface(intf, true)) {
connection.close()
return KeyResult.Error("Kan USB interface niet claimen")
return KeyResult.Error(R.string.err_usb_claim)
}
val epOut = UsbUtils.findBulkEndpoint(intf, UsbConstants.USB_DIR_OUT)
?: run { connection.close(); return KeyResult.Error("Geen USB OUT endpoint gevonden") }
?: run { connection.close(); return KeyResult.Error(R.string.err_no_out) }
val epIn = UsbUtils.findBulkEndpoint(intf, UsbConstants.USB_DIR_IN)
?: run { connection.close(); return KeyResult.Error("Geen USB IN endpoint gevonden") }
?: run { connection.close(); return KeyResult.Error(R.string.err_no_in) }
try {
setupCdcControl(connection, device)
@ -729,20 +731,20 @@ class SettingsActivity : AppCompatActivity() {
val setKeyCmd = "SET_KEY:$appKey\n".toByteArray(StandardCharsets.US_ASCII)
val w = connection.bulkTransfer(epOut, setKeyCmd, setKeyCmd.size, USB_BULK_TIMEOUT_MS)
if (w < 0) return KeyResult.Error("SET_KEY schrijven mislukt")
if (w < 0) return KeyResult.Error(R.string.err_set_key_write)
Log.d(TAG, "Sent SET_KEY ($w bytes)")
Thread.sleep(RESPONSE_WAIT_MS)
val okKeyResponse = readResponse(connection, epIn, SET_KEY_TIMEOUT_MS)
if (!okKeyResponse.contains("OK")) return KeyResult.Error("ESP bevestigde sleutel niet")
if (!okKeyResponse.contains("OK")) return KeyResult.Error(R.string.err_key_not_ack)
val setIdCmd = "SET_ID:$appUuid\n".toByteArray(StandardCharsets.US_ASCII)
val w2 = connection.bulkTransfer(epOut, setIdCmd, setIdCmd.size, USB_BULK_TIMEOUT_MS)
if (w2 < 0) return KeyResult.Error("SET_ID schrijven mislukt")
if (w2 < 0) return KeyResult.Error(R.string.err_set_id_write)
Thread.sleep(RESPONSE_WAIT_MS)
val okUuidResponse = readResponse(connection, epIn, SET_KEY_TIMEOUT_MS)
if (!okUuidResponse.contains("OK")) return KeyResult.Error("ESP bevestigde UUID niet")
if (!okUuidResponse.contains("OK")) return KeyResult.Error(R.string.err_uuid_not_ack)
val prefs = PrefsHelper.getPrefs(this)
prefs.edit()
@ -752,7 +754,7 @@ class SettingsActivity : AppCompatActivity() {
return KeyResult.Success(appUuid, appKey)
} catch (e: Exception) {
Log.e(TAG, "Write keys error", e)
return KeyResult.Error("Fout: ${e.message}")
return KeyResult.Error(R.string.err_generic, e.message)
} finally {
try { connection.close() } catch (_: Exception) {}
}

View file

@ -11,7 +11,7 @@
android:layout_gravity="end|top"
android:src="@drawable/ic_settings"
android:background="?android:attr/selectableItemBackgroundBorderless"
android:contentDescription="Instellingen" />
android:contentDescription="@string/cd_settings" />
<LinearLayout
android:layout_width="wrap_content"
@ -46,7 +46,7 @@
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="bottom|start"
android:text="Disconnected"
android:text="@string/status_default"
android:textSize="14sp" />
</FrameLayout>

View file

@ -8,7 +8,7 @@
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Sluit de ESP aan op USB en kies een actie."
android:text="@string/msg_settings_intro"
android:textSize="16sp"
android:layout_marginBottom="24dp" />
@ -16,7 +16,7 @@
android:id="@+id/exchangeKeyButton"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Sleutel Uitwisselen" />
android:text="@string/button_key_exchange" />
<View
android:layout_width="match_parent"
@ -28,7 +28,7 @@
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Zet de ESP in download-modus (houd reset 2s in tot de groene LED aangaat) en tik op 'Firmware Flashen'."
android:text="@string/msg_flash_mode_help"
android:textSize="14sp"
android:layout_marginBottom="16dp" />
@ -36,7 +36,7 @@
android:id="@+id/flashFirmwareButton"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Firmware Flashen" />
android:text="@string/button_firmware_flash" />
<View
android:layout_width="match_parent"

View file

@ -4,6 +4,7 @@
<string name="settings">Instellingen</string>
<!-- MainActivity status -->
<string name="status_default">Disconnected</string>
<string name="status_discovering_services">Verbonden, services zoeken…</string>
<string name="status_connecting">Verbinden…</string>
<string name="status_connected">Connected</string>
@ -19,11 +20,16 @@
<string name="button_retry_scan">Opnieuw scannen</string>
<!-- MainActivity UI -->
<string name="cd_settings">Instellingen</string>
<string name="button_on">ON</string>
<string name="button_hold">HOLD</string>
<!-- 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="button_key_exchange">Sleutel Uitwisselen</string>
<string name="msg_flash_mode_help">Zet de ESP in download-modus (houd reset 2s in tot de groene LED aangaat) en tik op Firmware Flashen.</string>
<string name="button_firmware_flash">Firmware Flashen</string>
<string name="build_number">Buildnummer: %s</string>
<string name="usb_access_denied">USB-toegang geweigerd</string>
<string name="key_read_success">Beveiligingssleutel gelezen</string>
@ -55,6 +61,32 @@
<string name="button_close">Sluiten</string>
<string name="button_cancel">Annuleren</string>
<!-- SettingsActivity error messages -->
<string name="err_no_cdc">Geen CDC interface gevonden</string>
<string name="err_usb_open">Kan USB apparaat niet openen</string>
<string name="err_usb_claim">Kan USB interface niet claimen</string>
<string name="err_no_out">Geen USB OUT endpoint gevonden</string>
<string name="err_no_in">Geen USB IN endpoint gevonden</string>
<string name="err_invalid_key">Ongeldig sleutel/UUID formaat</string>
<string name="err_key_retries">Geen sleutel gevonden na 5 pogingen</string>
<string name="err_generic">Fout: %s</string>
<string name="err_set_key_write">SET_KEY schrijven mislukt</string>
<string name="err_set_id_write">SET_ID schrijven mislukt</string>
<string name="err_key_not_ack">ESP bevestigde sleutel niet</string>
<string name="err_uuid_not_ack">ESP bevestigde UUID niet</string>
<!-- EspFlasher -->
<string name="flash_connecting">Verbinden met ESP bootloader…</string>
<string name="flash_step_bootloader">Bootloader</string>
<string name="flash_step_partitions">Partitie tabel</string>
<string name="flash_step_boot_app0">Boot app0</string>
<string name="flash_step_firmware">Firmware</string>
<string name="flash_sync">Sync met bootloader…</string>
<string name="flash_detect">Chip detecteren…</string>
<string name="flash_chip">Chip: %s</string>
<string name="flash_stub">Stub laden…</string>
<string name="flash_done">Firmware geflashed!</string>
<!-- Label customization -->
<string name="section_labels">Knoptekst</string>
<string name="hint_label_relay1">Relay 1</string>