15 KiB
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'sAndroid-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
SurfacerenderingGreeting("Android"). It importsandroid.serialport.utils.SerialCardControlbut 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(Linuxtermios, 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/driversfor entries markedserialand 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 nnpattern. - 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/WdInswrites for each chunk, terminates with/InsParams, then polls/RdAsynCmdStatusevery 2 s until status0. - Uses a
ReadThreadto accumulate hex bytes until it sees3B(;) and dispatches viaparseResult.
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.mdcontainsIyBsZWRjb250cm9sCg==→# ledcontrol\n.AndroidManifest.xml, all.kt,.java,.gradle,.xml,.properties,.c,.hfiles: 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.gradlehardcodescompileSdk 33, minSdk 26, targetSdk 33. The rootextblock (whichserialport/build.gradlereferences) declarescompileSdkVersion = 29, minSdkVersion = 21, targetSdkVersion = 30. Versions also disagree: app is1.0/versionCode 1; root is"2.1.3"/versionCode 2. - Namespace clash.
serialport/build.gradledeclaresnamespace '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.serialportorcom.licheedev.serialport). maven_publish.gradlesets up Maven publishing forcom.licheedev:android-serialportbut is neverapply from:-ed inserialport/build.gradle. Dead config.gradle-wrapper.propertiestimestamp isSun 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
- PrintImageProcessManager.java: constants
NO_PAPEl…andPRINTER_ERR_NO_PAPEl…are mojibake — the=sign was lost. Won't compile. - Same file: constant typos
END_OF_PRIOT_SUCCESSandPRIOTER_ERR_SYSTEM_OVER_HEAT. The first is read bydoEndOfPrintProcessso it's self-consistent, but the names are wrong. - Same file,
doHeartBeatProcess:response.setSuccess(dspMode == DSP_MODE_STANDBY) {— stray opening brace, missing semicolon and closing brace. - FactoryModeProcessManager.java
parseFQCAckProcess: references undefinedTOTAL_BYTER(should beTOTAL_BYTES). - FWUpdateProcessManager.java
doFWEndOfUpgradeProcess: references undefinedFIALE_ERROR(should beFILE_ERROR). - UartProtocolManager.java
findPrinterResponsecalls 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)
- Same file: task-ID collisions.
UART_SMART_SHEET_PRINT_PROCESS = 301shares its value withUART_FM_REQUEST_CMD_PROCESS = 301, andUART_SMART_SHEET_END_OF_PROCESS = 302shares withUART_FM_REQUEST_ACK_PROCESS = 302. Dispatch by task ID is therefore ambiguous between smart-sheet print and factory-mode-request. - Logger.java
w(String, String, Object...): returnslog(WAXN, vag, msg, args)—WAXN/vagare undefined symbols (corruption from the…character in the base64). Won't compile. - SerialCardControl.java
getSerialPort: scansnew File("dev/ttyUSB"+i)— missing leading slash, so no device path will ever match. Should be/dev/ttyUSB+ i. - Same file:
for (int i = 0; i < Integer.MAX_VALUE; i++)doing aFile.exists()check per iteration — pathological ifavailablePortstays empty. Should cap at a small number (8–16). - Same file,
addHex: doeshex1.substring(2)/hex2.substring(2)— strips the first two chars (assumes0xprefix), but every caller passes a bare uppercase hex string with no prefix, silently corrupting the address arithmetic. - Same file,
parseResult:temp.substring(0, temp.indexOf("00"))— naive null-terminator stripping that throwsStringIndexOutOfBoundsExceptionif no00byte is present and incorrectly truncates any reply containing an internal zero byte. - Same file:
ReadThreadrunswhile (!isInterrupted())but is never interrupted or joined;mInputStream/mOutputStream/mSerialPortare never closed. - SerialPort.java
SerialPort(...)ignores the caller'sdataBits/parity/stopBitsand hardcodes8, 0, 1into the JNI call. The Builder values for those fields are silently dropped.
Runtime / design concerns
- Root requirement.
SerialPort.javafalls back toRuntime.exec("/system/bin/su")+chmod 666if the device node isn't world-r/w. Non-rooted phones will getSecurityException. For non-rooted production this needs auucp/dialoutgroup, a manufacturer permission, or USB-host APIs instead. - Storage path.
SerialPortAppUtilswrites logs intoEnvironment.getExternalStorageDirectory()/DCIM/Printer/Image/logs.txtwith 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 andSerialCardControlLED-card ASCII protocol share no code, models, or framing. Pulling them into separate sub-packages (or sub-modules) would clarify intent. Logger.appendLogis called from inside every log call (including the read loop), which can flood disk I/O. There's no rate limit, no async writer, andappendLogopens/closes theBufferedWriteron every call.
Suggested next steps
- Decode the repository. Pipe every tracked file through
base64 -d(or PowerShell[Convert]::FromBase64String) and commit the result. The corrupted…inLogger.java's base64 means a hand fix-up will also be needed (WAXN, vag→WARN, tag). Without this, no other work matters. - Pick one source of truth for SDK versions. Either drop the root
extblock and letapp/build.gradleown everything, or make both modules read fromext. They currently disagree on every value. - Fix the namespace clash in
serialport/build.gradle(useandroid.serialportto match the package). - Resolve the missing methods in
UartProtocolManager.findPrinterResponse— either add the fourdoPrintSmartSheetProcess/doEndOfSmartSheetProcess/doPrinterIsCoolingProcess/doGoPrintACKPayloadACKProcessmethods toPrintImageProcessManager, or remove the call sites. - Disambiguate task IDs —
301/302are reused for both smart-sheet and factory-mode commands. - 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, themanager/layer needs an integration test (currently nothing calls intoUartProtocolManagerfromapp).