App decoding and documentation
This commit is contained in:
@@ -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 <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 (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`).
|
||||||
Reference in New Issue
Block a user