From bdd05f3efdfbe4d7bd32c1d737cad1c6d3d84431 Mon Sep 17 00:00:00 2001 From: jason Date: Mon, 11 May 2026 13:40:21 -0500 Subject: [PATCH] App decoding and documentation --- APP.md | 137 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 137 insertions(+) create mode 100644 APP.md diff --git a/APP.md b/APP.md new file mode 100644 index 0000000..dc95178 --- /dev/null +++ b/APP.md @@ -0,0 +1,137 @@ +# APP.md — `ledcontrol` + +## Overview + +A two-module Android Gradle project. Despite the name `ledcontrol`, the working code is mostly a **UART/serial-port library wrapping a printer protocol**, with a stub Compose UI on top and a separate "serial card" controller that looks like the LED-controller piece the project name advertises. + +- **`app`** — Android application (`com.led.control`). A Jetpack Compose "Hello Android" starter. Imports the serial layer but does not actually use it. +- **`serialport`** — Android library. Cedric Priscal's `Android-SerialPort-API` (Apache 2.0) bundled with custom protocol managers and an LED-card command driver. + +**Build status:** the repo will not build as-is. Every source file on disk is the base64-encoded *text* of the file's real contents (see [Repo-import corruption](#repo-import-corruption)). On top of that, several files contain compile-blocking typos and references to methods that don't exist. + +## Repository layout + +``` +ledcontrol/ +├── build.gradle # Root: AGP 8.0.1, Kotlin 1.7.20; ext.compileSdk=29/min=21/target=30, versionName "2.1.3" +├── settings.gradle # includes :app, :serialport +├── gradle.properties # androidx, kotlin.code.style=official +├── gradle/wrapper/ # gradle 8.0 +├── maven_publish.gradle # publish coords com.licheedev:android-serialport (unused — not applied) +├── README.md # base64 of "# ledcontrol\n" +│ +├── app/ +│ ├── build.gradle # compileSdk 33, minSdk 26, target 33, Compose BOM 2022.10.00 +│ └── src/main/ +│ ├── AndroidManifest.xml # single MainActivity, Theme.Ledcontrol +│ ├── java/com/led/control/ +│ │ ├── MainActivity.kt # ComponentActivity + Compose, renders Greeting("Android") +│ │ └── ui/theme/{Color,Theme,Type}.kt # Material3 boilerplate +│ └── res/ # default icons, themes, colors, backup rules +│ +└── serialport/ + ├── build.gradle # library, namespace 'com.led.control' (clashes with app) + ├── CMakeLists.txt # builds libserial_port.so from src/main/cpp/SerialPort.c + └── src/main/ + ├── AndroidManifest.xml # empty manifest + ├── cpp/ + │ ├── SerialPort.h # JNI header (machine-generated) + │ ├── SerialPort.c # Cedric Priscal's termios open/close, Apache 2.0 + │ └── gen_SerialPort_h.sh # javah helper script + └── java/android/serialport/ + ├── SerialPort.java # Builder-based wrapper, JNI open()/close(), chmod-via-su fallback + ├── SerialPortFinder.java # parses /proc/tty/drivers to enumerate /dev/tty* + ├── manager/ + │ ├── UartProtocolManager.java # 64-byte frame builder + response dispatch + │ ├── PrintImageProcessManager.java # parses printer ACK frames + │ ├── FWUpdateProcessManager.java # parses firmware-upgrade ACKs + │ └── FactoryModeProcessManager.java # parses factory-mode ACKs + ├── model/ + │ ├── PrinterRequestModel.java # taskId, payload, copies, fileName1-8 + │ └── PrinterResponseModel.java # success/message/error + DSP versions, paper, door + └── utils/ + ├── SerialCardControl.java # singleton driving an LED control card over /dev/ttyUSB* + ├── ByteUtil.java # hex helpers + chunked file reader + ├── Logger.java # android.util.Log wrapper + file append + ├── SerialPortAppUtils.java # appends to DCIM/Printer/Image/logs.txt + ├── SerialPortConstants.java # LOG_ENABLED = true + └── TaskExcuteResult.java # success() / fail(reason) callback (typo in name) +``` + +## What each piece actually does + +### `app` module +- [MainActivity.kt](app/src/main/java/com/led/control/MainActivity.kt) hosts a Compose `Surface` rendering `Greeting("Android")`. It imports `android.serialport.utils.SerialCardControl` but never calls it. No UI wired to any serial functionality. This is essentially the Android Studio "Empty Compose Activity" template. +- Theme files are unmodified Material3 defaults. + +### `serialport` — native + base wrapper +- [SerialPort.c](serialport/src/main/cpp/SerialPort.c) implements `Java_android_serialport_SerialPort_open` (Linux `termios`, baud/dataBits/parity/stopBits) and `..._close`. Standard Cedric Priscal code, Apache 2.0. +- [SerialPort.java](serialport/src/main/java/android/serialport/SerialPort.java) opens the device, falling back to `Runtime.exec(sSuPath)` + `chmod 666 ` if the path is not readable/writable — **requires root** for that fallback. Exposes a Builder. +- [SerialPortFinder.java](serialport/src/main/java/android/serialport/SerialPortFinder.java) walks `/proc/tty/drivers` for entries marked `serial` and enumerates matching files in `/dev`. + +### `serialport` — printer protocol layer (`manager/` + `model/`) +A **64-byte UART frame** protocol prefixed with `0x1B 0x2A 0x43 0x41` (`ESC * C A`). Used to talk to a thermal/photo printer of some kind. +- [UartProtocolManager.java](serialport/src/main/java/android/serialport/manager/UartProtocolManager.java) builds outgoing frames for: heartbeat, start-up, print request, image info (filename), go-print ACK, end-of-print ACK, printer cooling ACK, ask-FW-version, smart-sheet print, and factory-mode commands (burn-in, camera test, FQC, shipping, version-ask, reset, FW upgrade request/go/end). `findPrinterResponse()` dispatches an incoming payload to the appropriate parser by task ID. +- [PrintImageProcessManager.java](serialport/src/main/java/android/serialport/manager/PrintImageProcessManager.java) parses print-side ACKs: print-request ACK, heartbeat (DSP mode/door/paper), go-print ACK, end-of-print result, FW version response. Carries a large error-code table (over-heat, jam, no-paper, decode error, etc.). +- [FactoryModeProcessManager.java](serialport/src/main/java/android/serialport/manager/FactoryModeProcessManager.java) parses factory-mode ACKs by checking the first eight bytes against the expected `1B 2A 43 41 01 01 F0 nn` pattern. +- [FWUpdateProcessManager.java](serialport/src/main/java/android/serialport/manager/FWUpdateProcessManager.java) parses firmware-upgrade ACKs (allow/reject + reason; end-of-upgrade success/error). + +### `serialport` — LED-card driver (`utils/SerialCardControl.java`) +Looks like a different device entirely — an ASCII command protocol (`/GetInsProps:h,1;`, `/WdIns:h,0,…;`, `/InsParams:…;`, `/RdAsynCmdStatus:h,1;`). This is consistent with several LED-controller cards (e.g. Linsn-style boards). It: +- Opens the first existing `/dev/ttyUSB` at 115200 8N1. +- Reads a binary config file in 1024-byte chunks via `ByteUtil.readBinaryFile`, queues `/WdIns` writes for each chunk, terminates with `/InsParams`, then polls `/RdAsynCmdStatus` every 2 s until status `0`. +- Uses a `ReadThread` to accumulate hex bytes until it sees `3B` (`;`) and dispatches via `parseResult`. + +This is almost certainly the "led control" that gave the project its name. It is **not invoked from anywhere in the app**. + +## Cross-cutting findings + +### Repo-import corruption +**Every source/config file on disk is the base64 encoding of its real contents** rather than the source itself. Examples: +- `README.md` contains `IyBsZWRjb250cm9sCg==` → `# ledcontrol\n`. +- `AndroidManifest.xml`, all `.kt`, `.java`, `.gradle`, `.xml`, `.properties`, `.c`, `.h` files: same pattern. + +The recent commits `chore: import from github.com/twx284558/ledcontrol` strongly suggest a broken import pipeline encoded the files. **Nothing here will compile until the files are decoded back to plain text.** At least one base64 blob ([Logger.java](serialport/src/main/java/android/serialport/utils/Logger.java)) also has a stray Unicode `…` (U+2026) embedded in the base64 — when decoded it produces source with mid-identifier corruption (`WAXN, vag` in place of `WARN, tag`), so the encoding step itself was lossy in at least one spot. + +### Build configuration inconsistencies +- **SDK levels diverge between modules.** `app/build.gradle` hardcodes `compileSdk 33, minSdk 26, targetSdk 33`. The root `ext` block (which `serialport/build.gradle` references) declares `compileSdkVersion = 29, minSdkVersion = 21, targetSdkVersion = 30`. Versions also disagree: app is `1.0` / `versionCode 1`; root is `"2.1.3"` / `versionCode 2`. +- **Namespace clash.** `serialport/build.gradle` declares `namespace 'com.led.control'`, which is the same namespace as the app module. AGP 7+ will fail manifest merge / R-class generation. The library should have its own namespace (e.g. `android.serialport` or `com.licheedev.serialport`). +- **`maven_publish.gradle`** sets up Maven publishing for `com.licheedev:android-serialport` but is never `apply from:`-ed in `serialport/build.gradle`. Dead config. +- **`gradle-wrapper.properties`** timestamp is `Sun May 10 16:21:44 CST 2026` — future-dated. +- **No NDK ABI filters** declared in `serialport/build.gradle`; native lib will build for all default ABIs. + +### Concrete bugs in the (decoded) Java +1. [PrintImageProcessManager.java](serialport/src/main/java/android/serialport/manager/PrintImageProcessManager.java): constants `NO_PAPEl…` and `PRINTER_ERR_NO_PAPEl…` are mojibake — the `=` sign was lost. Won't compile. +2. Same file: constant typos `END_OF_PRIOT_SUCCESS` and `PRIOTER_ERR_SYSTEM_OVER_HEAT`. The first is read by `doEndOfPrintProcess` so it's self-consistent, but the names are wrong. +3. Same file, `doHeartBeatProcess`: `response.setSuccess(dspMode == DSP_MODE_STANDBY) {` — stray opening brace, missing semicolon and closing brace. +4. [FactoryModeProcessManager.java](serialport/src/main/java/android/serialport/manager/FactoryModeProcessManager.java) `parseFQCAckProcess`: references undefined `TOTAL_BYTER` (should be `TOTAL_BYTES`). +5. [FWUpdateProcessManager.java](serialport/src/main/java/android/serialport/manager/FWUpdateProcessManager.java) `doFWEndOfUpgradeProcess`: references undefined `FIALE_ERROR` (should be `FILE_ERROR`). +6. [UartProtocolManager.java](serialport/src/main/java/android/serialport/manager/UartProtocolManager.java) `findPrinterResponse` calls methods that don't exist: + - `PrintImageProcessManager.doGoPrintACKPetProcess` (real method: `doGoPrintACKPayloadACKProcess`) + - `PrintImageProcessManager.doPrinterIsCoolingProcess` (not defined) + - `PrintImageProcessManager.doPrintSmartSheetProcess` (not defined) + - `PrintImageProcessManager.doEndOfSmartSheetProcess` (not defined) + - `FWUpdateProcessManager.doFWUpgradeRequestACKPetProcess` (real method: `doFWUpgradeRequestACKProcess`) +7. Same file: task-ID collisions. `UART_SMART_SHEET_PRINT_PROCESS = 301` shares its value with `UART_FM_REQUEST_CMD_PROCESS = 301`, and `UART_SMART_SHEET_END_OF_PROCESS = 302` shares with `UART_FM_REQUEST_ACK_PROCESS = 302`. Dispatch by task ID is therefore ambiguous between smart-sheet print and factory-mode-request. +8. [Logger.java](serialport/src/main/java/android/serialport/utils/Logger.java) `w(String, String, Object...)`: returns `log(WAXN, vag, msg, args)` — `WAXN`/`vag` are undefined symbols (corruption from the `…` character in the base64). Won't compile. +9. [SerialCardControl.java](serialport/src/main/java/android/serialport/utils/SerialCardControl.java) `getSerialPort`: scans `new File("dev/ttyUSB"+i)` — **missing leading slash**, so no device path will ever match. Should be `/dev/ttyUSB` + i. +10. Same file: `for (int i = 0; i < Integer.MAX_VALUE; i++)` doing a `File.exists()` check per iteration — pathological if `availablePort` stays empty. Should cap at a small number (8–16). +11. Same file, `addHex`: does `hex1.substring(2)` / `hex2.substring(2)` — strips the first two chars (assumes `0x` prefix), but every caller passes a bare uppercase hex string with no prefix, silently corrupting the address arithmetic. +12. Same file, `parseResult`: `temp.substring(0, temp.indexOf("00"))` — naive null-terminator stripping that throws `StringIndexOutOfBoundsException` if no `00` byte is present and incorrectly truncates any reply containing an internal zero byte. +13. Same file: `ReadThread` runs `while (!isInterrupted())` but is never interrupted or joined; `mInputStream`/`mOutputStream`/`mSerialPort` are never closed. +14. [SerialPort.java](serialport/src/main/java/android/serialport/SerialPort.java) `SerialPort(...)` ignores the caller's `dataBits`/`parity`/`stopBits` and hardcodes `8, 0, 1` into the JNI call. The Builder values for those fields are silently dropped. + +### Runtime / design concerns +- **Root requirement.** `SerialPort.java` falls back to `Runtime.exec("/system/bin/su")` + `chmod 666` if the device node isn't world-r/w. Non-rooted phones will get `SecurityException`. For non-rooted production this needs a `uucp`/`dialout` group, a manufacturer permission, or USB-host APIs instead. +- **Storage path.** `SerialPortAppUtils` writes logs into `Environment.getExternalStorageDirectory()/DCIM/Printer/Image/logs.txt` with no scoped-storage guard. Will fail silently on Android 10+ for non-MediaStore writes outside the app's own files dir. +- **Two unrelated protocols in one library.** The `manager/` printer protocol and `SerialCardControl` LED-card ASCII protocol share no code, models, or framing. Pulling them into separate sub-packages (or sub-modules) would clarify intent. +- **`Logger.appendLog`** is called from inside every log call (including the read loop), which can flood disk I/O. There's no rate limit, no async writer, and `appendLog` opens/closes the `BufferedWriter` on every call. + +## Suggested next steps + +1. **Decode the repository.** Pipe every tracked file through `base64 -d` (or PowerShell `[Convert]::FromBase64String`) and commit the result. The corrupted `…` in `Logger.java`'s base64 means a hand fix-up will also be needed (`WAXN, vag` → `WARN, tag`). Without this, no other work matters. +2. **Pick one source of truth for SDK versions.** Either drop the root `ext` block and let `app/build.gradle` own everything, or make both modules read from `ext`. They currently disagree on every value. +3. **Fix the namespace clash** in `serialport/build.gradle` (use `android.serialport` to match the package). +4. **Resolve the missing methods in `UartProtocolManager.findPrinterResponse`** — either add the four `doPrintSmartSheetProcess` / `doEndOfSmartSheetProcess` / `doPrinterIsCoolingProcess` / `doGoPrintACKPayloadACKProcess` methods to `PrintImageProcessManager`, or remove the call sites. +5. **Disambiguate task IDs** — `301`/`302` are reused for both smart-sheet and factory-mode commands. +6. **Decide what the app is.** If it's an LED-control tool, the Compose UI needs to expose `SerialCardControl.sendConfigFileToControlCard`. If it's also/instead a printer driver, the `manager/` layer needs an integration test (currently nothing calls into `UartProtocolManager` from `app`).