Files
2026-05-11 13:40:21 -05:00

15 KiB
Raw Permalink Blame History

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). 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 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 implements Java_android_serialport_SerialPort_open (Linux termios, baud/dataBits/parity/stopBits) and ..._close. Standard Cedric Priscal code, Apache 2.0.
  • 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 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 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 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 parses factory-mode ACKs by checking the first eight bytes against the expected 1B 2A 43 41 01 01 F0 nn pattern.
  • 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) 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: 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 parseFQCAckProcess: references undefined TOTAL_BYTER (should be TOTAL_BYTES).
  5. FWUpdateProcessManager.java doFWEndOfUpgradeProcess: references undefined FIALE_ERROR (should be FILE_ERROR).
  6. 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 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 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(...) 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, vagWARN, 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 IDs301/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).