Adds end-to-end support for driving USB-connected Adalight / AmbiLED
LED controllers from Android TV boxes. Android's security model blocks
direct USB access from Python, so writes route through a Kotlin
UsbSerialBridge singleton via Chaquopy.
Python side:
- New SerialTransport Protocol (serial_transport.py) with open / write /
flush / close. Desktop uses PySerialTransport (wraps pyserial),
Android uses AndroidSerialTransport (wraps the Kotlin bridge).
- list_serial_ports() factory returns desktop COM ports on desktop,
USB devices on Android — callers don't branch.
- URL scheme extended: existing COM3[:baud] and /dev/ttyUSB0[:baud]
unchanged; new usb:VID:PID[:serial][@baud] for Android (@ is the
baud separator since : is already used between VID and PID).
- AdalightClient and SerialDeviceProvider refactored to go through
the transport — no more direct pyserial imports in hot paths.
- 17 new unit tests cover URL parsing, PySerial transport, factory
selection, platform-branching discovery. Full suite 750 passing.
Kotlin side:
- UsbSerialBridge.kt singleton uses com.hoho.android.usbserial (mik3y)
which ships drivers for CH340, CP2102, FTDI, Prolific, and CDC-ACM
(Arduino). Exposes listDevices, open, write, close via @JvmStatic
for Chaquopy. First open() attempt without permission triggers the
system USB permission dialog; next call succeeds once user grants.
- usb-serial-for-android is distributed via JitPack — added that repo
in settings.gradle.kts and the dependency in app/build.gradle.kts.
- AndroidManifest declares uses-feature android.hardware.usb.host
(required=false so non-USB-host phones still install).
- LedGrabApp.onCreate calls UsbSerialBridge.init(this) so the bridge
resolves the UsbManager without needing an Activity ref.
Verified: ./gradlew compileDebugKotlin succeeds; off-Android import
of android_serial_transport works. Real-hardware smoke test on a
TV box with a CH340/CP2102/FTDI adapter still pending.
ESP-NOW (espnow_client / espnow_provider) still imports pyserial
directly because it needs bidirectional reads — separate refactor
to extend the transport with read() if that path ever needs Android
USB support.
Adds a native Android TV application that runs the full LedGrab Python
server in-process via Chaquopy. Captures the TV box screen using the
MediaProjection API and exposes the existing web UI on the device's
local network — users configure via phone/tablet browser.
Android (new /android/ module):
- Kotlin shell: MainActivity, CaptureService (foreground service),
ScreenCapture (MediaProjection + ImageReader), PythonBridge (Chaquopy).
- Polished Leanback-themed UI with QR code for easy web UI access.
- AGP 8.9 + Chaquopy 17 + Gradle 8.11 (avoids the AGP 8.7 thread-lock bug).
- Pre-built pydantic-core wheels for arm64-v8a, x86_64, x86 cross-compiled
with maturin + Android NDK, linked against Chaquopy's libpython3.11.so.
Python server platform guards:
- New utils/platform.py with is_android()/is_windows()/is_linux() helpers.
- Guard every top-level import of desktop-only packages (mss, psutil,
sounddevice, pyserial, PyAudioWPatch, etc.) with try/except ImportError.
- Android-incompatible calls gated with None-checks so the server runs on
reduced capabilities on Android (no CPU/RAM metrics, no mss displays).
- utils/image_codec.py gains a Pillow fallback for resize + JPEG encode
when cv2 is unavailable; all internal cv2.resize callers migrated.
- New android_entry.py start_server/stop_server invoked from Kotlin.
- get_displays API falls back to best available engine when mss fails.
New capture engines:
- MediaProjectionEngine: receives RGBA frames pushed from Kotlin through
a thread-safe queue; caches last frame for static-screen previews.
- ScrcpyClientEngine: optional H.264 streaming via scrcpy-client library
(priority 10, overrides the ADB-screencap engine when installed).
Frontend:
- Tab loaders previously required an apiKey; now correctly treat
"auth disabled" as authenticated (Android has no auth by default).
- Re-trigger the active tab's loader after loadServerInfo resolves
authRequired, since initTabs runs earlier.
- Add i18n keys for the demo / mediaprojection / scrcpy_client engines.
Docs:
- TODO.md: follow-ups for multi-ABI wheel rebuilds, CI pipeline, USB
serial LED controllers, root-only capture, perf metrics abstraction.
- CLAUDE.md: Android dependency sync policy (pip --exclude doesn't exist).
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>