diff --git a/android/app/src/main/java/com/eddy/atomrelay/EspFlasher.kt b/android/app/src/main/java/com/eddy/atomrelay/EspFlasher.kt index 8d4b111..8c7704a 100644 --- a/android/app/src/main/java/com/eddy/atomrelay/EspFlasher.kt +++ b/android/app/src/main/java/com/eddy/atomrelay/EspFlasher.kt @@ -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() } diff --git a/android/app/src/main/java/com/eddy/atomrelay/SettingsActivity.kt b/android/app/src/main/java/com/eddy/atomrelay/SettingsActivity.kt index 7380b34..fc4895e 100644 --- a/android/app/src/main/java/com/eddy/atomrelay/SettingsActivity.kt +++ b/android/app/src/main/java/com/eddy/atomrelay/SettingsActivity.kt @@ -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) {} } diff --git a/android/app/src/main/res/layout/activity_main.xml b/android/app/src/main/res/layout/activity_main.xml index 50ac9ab..b8b0040 100644 --- a/android/app/src/main/res/layout/activity_main.xml +++ b/android/app/src/main/res/layout/activity_main.xml @@ -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" /> \ No newline at end of file diff --git a/android/app/src/main/res/layout/activity_settings.xml b/android/app/src/main/res/layout/activity_settings.xml index afa9538..8837376 100644 --- a/android/app/src/main/res/layout/activity_settings.xml +++ b/android/app/src/main/res/layout/activity_settings.xml @@ -8,7 +8,7 @@ @@ -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" /> @@ -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" /> Instellingen + Disconnected Verbonden, services zoeken… Verbinden… Connected @@ -19,11 +20,16 @@ Opnieuw scannen + Instellingen ON HOLD Instellingen + Sluit de ESP aan op USB en kies een actie. + Sleutel Uitwisselen + Zet de ESP in download-modus (houd reset 2s in tot de groene LED aangaat) en tik op Firmware Flashen. + Firmware Flashen Buildnummer: %s USB-toegang geweigerd Beveiligingssleutel gelezen @@ -55,6 +61,32 @@ Sluiten Annuleren + + Geen CDC interface gevonden + Kan USB apparaat niet openen + Kan USB interface niet claimen + Geen USB OUT endpoint gevonden + Geen USB IN endpoint gevonden + Ongeldig sleutel/UUID formaat + Geen sleutel gevonden na 5 pogingen + Fout: %s + SET_KEY schrijven mislukt + SET_ID schrijven mislukt + ESP bevestigde sleutel niet + ESP bevestigde UUID niet + + + Verbinden met ESP bootloader… + Bootloader + Partitie tabel + Boot app0 + Firmware + Sync met bootloader… + Chip detecteren… + Chip: %s + Stub laden… + Firmware geflashed! + Knoptekst Relay 1 diff --git a/docs/user-facing-text.md b/docs/user-facing-text.md new file mode 100644 index 0000000..f40a153 --- /dev/null +++ b/docs/user-facing-text.md @@ -0,0 +1,111 @@ +# User-facing text — Atom Relay + +## MainActivity — statusbalk +| Huidige tekst | strings.xml key | +|---|---| +| Connected | `status_connected` | +| Verbinden… | `status_connecting` | +| Servicefout | `status_service_error` | +| Service not found | `status_service_not_found` | +| Communicatiefout | `status_comm_error` | +| Geen HMAC sleutel - open Instellingen | `status_no_key` | +| Permissies geweigerd — open Instellingen | `status_permissions_denied` | +| Bluetooth not enabled | `status_bluetooth_disabled` | +| Scanning… | `status_scanning` | +| Device not found | `status_device_not_found` | +| Verkeerd apparaat — opnieuw scannen… | `status_wrong_device` | +| Verbonden, services zoeken… | `status_discovering_services` | + +## MainActivity — knoppen +| Huidige tekst | strings.xml key | +|---|---| +| Instellingen (contentDescription) | `cd_settings` | +| Disconnected (placeholder) | `status_default` | +| HOLD (fallback knoplabel) | `button_hold` | +| Opnieuw scannen (fallback) | `button_retry_scan` | +| User-defined labels 1 & 2 | PrefsHelper (user input) | + +## SettingsActivity — UI +| Huidige tekst | strings.xml key | +|---|---| +| Sluit de ESP aan op USB en kies een actie. | `msg_settings_intro` | +| Sleutel Uitwisselen | `button_key_exchange` | +| Zet de ESP in download-modus (houd reset 2s in tot de groene LED aangaat) en tik op Firmware Flashen. | `msg_flash_mode_help` | +| Firmware Flashen | `button_firmware_flash` | +| Knoptekst | `section_labels` | +| Relay 1 | `hint_label_relay1` | +| Relay 2 | `hint_label_relay2` | +| Weergave | `section_theme` | +| Systeem | `theme_system` | +| Licht | `theme_light` | +| Donker | `theme_dark` | +| Thema: %s | `theme_applied` | +| Buildnummer: %s | `build_number` | + +## SettingsActivity — dialogs / toasts +| Huidige tekst | strings.xml key | +|---|---| +| Sleutel Uitwisselen | `title_key_exchange` | +| Sluit de ESP aan via USB. | `msg_connect_usb` | +| Sleutel uitlezen… | `msg_reading_key` | +| Beveiligingssleutel gelezen | `key_read_success` | +| Sleutel uitwisselen mislukt - probeer opnieuw | `key_exchange_failed` | +| USB-toegang geweigerd | `usb_access_denied` | +| Annuleren | `button_cancel` | +| Sluiten | `button_close` | +| Firmware Flashen | `title_firmware_flash` | +| Zet de ESP in download-modus (houd reset 2s in tot groene LED) en sluit aan via USB. | `msg_flash_download_mode` | +| Initialiseren… | `msg_initializing` | +| Firmware geflashed!\\n\\nOntkoppel de ESP en sluit opnieuw aan om de sleutel in te stellen. | `msg_flash_complete` | +| Flashen mislukt: %s | `flash_failed` | +| Sleutels komen niet overeen | `title_key_conflict` | +| De ESP (SylDa_Server) heeft andere sleutels dan deze app. Wat wil je doen? | `msg_key_conflict` | +| ESP sleutels overnemen | `button_keep_esp_keys` | +| App sleutels overzetten | `button_overwrite_esp` | +| ESP sleutels overschrijven? | `title_overwrite_confirm` | +| Dit vervangt de sleutels van de ESP. De ESP kan daarna niet meer met de oorspronkelijke app communiceren. | `msg_overwrite_warning` | +| Ik weet het zeker | `checkbox_confirm_overwrite` | +| Bevestigen | `button_confirm` | +| Sleutels overschrijven | `title_overwriting_keys` | +| Sleutels schrijven naar ESP… | `msg_writing_keys` | + +## SettingsActivity — foutmeldingen (hardcoded in .kt) +| Huidige tekst | strings.xml key | +|---|---| +| Geen CDC interface gevonden | `err_no_cdc` | +| Kan USB apparaat niet openen | `err_usb_open` | +| Kan USB interface niet claimen | `err_usb_claim` | +| Geen USB OUT endpoint gevonden | `err_no_out` | +| Geen USB IN endpoint gevonden | `err_no_in` | +| Ongeldig sleutel/UUID formaat | `err_invalid_key` | +| Geen sleutel gevonden na 5 pogingen | `err_key_retries` | +| Fout: %s | `err_generic` | +| SET_KEY schrijven mislukt | `err_set_key_write` | +| SET_ID schrijven mislukt | `err_set_id_write` | +| ESP bevestigde sleutel niet | `err_key_not_ack` | +| ESP bevestigde UUID niet | `err_uuid_not_ack` | + +## EspFlasher — hardcoded in .kt +| Huidige tekst | strings.xml key | +|---|---| +| Kan USB-apparaat niet openen | `err_usb_open` (reuse) | +| Geen CDC data interface gevonden | `err_no_cdc` (reuse) | +| Kan USB interface niet claimen | `err_usb_claim` (reuse) | +| Geen USB OUT endpoint gevonden | `err_no_out` (reuse) | +| Geen USB IN endpoint gevonden | `err_no_in` (reuse) | +| Verbinden met ESP bootloader... | `flash_connecting` | +| Bootloader | `flash_step_bootloader` | +| Partitie tabel | `flash_step_partitions` | +| Boot app0 | `flash_step_boot_app0` | +| Firmware | `flash_step_firmware` | +| Sync met bootloader... | `flash_sync` | +| Chip detecteren... | `flash_detect` | +| Chip: %s | `flash_chip` | +| Stub laden... | `flash_stub` | +| Firmware geflashed! | `flash_done` | + +## AndroidManifest +| Huidige tekst | strings.xml key | +|---|---| +| Atom Relay | `app_name` | +| Instellingen | `settings` |