Files
ledcontrol/APP.md
T
2026-05-11 13:40:21 -05:00

138 lines
15 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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 <device>` 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<i>` 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 (816).
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`).