Files
ledcontrol/APP.md
T

138 lines
15 KiB
Markdown
Raw Normal View History

2026-05-11 13:40:21 -05:00
# 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`).