7fcb8dd346
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.
123 lines
4.4 KiB
Kotlin
123 lines
4.4 KiB
Kotlin
plugins {
|
|
id("com.android.application")
|
|
id("org.jetbrains.kotlin.android")
|
|
id("com.chaquo.python")
|
|
}
|
|
|
|
android {
|
|
namespace = "com.ledgrab.android"
|
|
compileSdk = 34
|
|
|
|
defaultConfig {
|
|
applicationId = "com.ledgrab.android"
|
|
minSdk = 24 // Android 7.0 — covers nearly all TV boxes
|
|
targetSdk = 34
|
|
versionCode = 1
|
|
versionName = "0.3.0"
|
|
|
|
ndk {
|
|
// All three ABIs: arm64-v8a (real TV hardware), x86_64 (modern
|
|
// emulators), x86 (legacy emulators). Wheels in android/wheels/
|
|
// must be kept in sync — see build-scripts/build-pydantic-core.sh.
|
|
abiFilters += listOf("arm64-v8a", "x86_64", "x86")
|
|
}
|
|
}
|
|
|
|
// Signing config from env vars (CI) — only registered when all four are set.
|
|
// Local release builds fall back to the debug signing config.
|
|
val ciKeystorePath = System.getenv("ANDROID_KEYSTORE_PATH")
|
|
val ciKeystorePassword = System.getenv("ANDROID_KEYSTORE_PASSWORD")
|
|
val ciKeyAlias = System.getenv("ANDROID_KEY_ALIAS")
|
|
val ciKeyPassword = System.getenv("ANDROID_KEY_PASSWORD")
|
|
val hasCiSigning = listOf(ciKeystorePath, ciKeystorePassword, ciKeyAlias, ciKeyPassword)
|
|
.all { !it.isNullOrBlank() } && file(ciKeystorePath!!).exists()
|
|
|
|
signingConfigs {
|
|
if (hasCiSigning) {
|
|
create("release") {
|
|
storeFile = file(ciKeystorePath!!)
|
|
storePassword = ciKeystorePassword
|
|
keyAlias = ciKeyAlias
|
|
keyPassword = ciKeyPassword
|
|
}
|
|
}
|
|
}
|
|
|
|
buildTypes {
|
|
release {
|
|
isMinifyEnabled = false
|
|
signingConfig = if (hasCiSigning) {
|
|
signingConfigs.getByName("release")
|
|
} else {
|
|
signingConfigs.getByName("debug")
|
|
}
|
|
}
|
|
}
|
|
|
|
compileOptions {
|
|
sourceCompatibility = JavaVersion.VERSION_17
|
|
targetCompatibility = JavaVersion.VERSION_17
|
|
}
|
|
|
|
kotlinOptions {
|
|
jvmTarget = "17"
|
|
}
|
|
}
|
|
|
|
chaquopy {
|
|
defaultConfig {
|
|
version = "3.11"
|
|
|
|
pip {
|
|
// Pre-built wheels directory (for pydantic-core etc.)
|
|
// Must use absolute file:// URI with forward slashes
|
|
options("--find-links", "file:///${rootDir.absolutePath.replace("\\", "/")}/wheels/")
|
|
|
|
// ── Android-compatible dependencies ─────────────────
|
|
// Listed explicitly because pyproject.toml includes
|
|
// desktop-only packages with no Android wheels.
|
|
// See CLAUDE.md "Android Dependency Sync" for policy.
|
|
install("fastapi")
|
|
install("uvicorn") // without [standard] — no uvloop/httptools
|
|
install("httpx")
|
|
install("numpy")
|
|
install("pydantic") // needs pydantic-core wheel in wheels/
|
|
install("pydantic-settings")
|
|
install("PyYAML")
|
|
install("structlog")
|
|
install("python-json-logger")
|
|
install("python-dateutil")
|
|
install("python-multipart")
|
|
install("jinja2")
|
|
install("zeroconf")
|
|
install("aiomqtt")
|
|
install("openrgb-python")
|
|
// opencv-python-headless: no cp311 Android wheel on Chaquopy.
|
|
// LedGrab's cv2 usage is guarded with try/except ImportError
|
|
// and falls back to numpy/Pillow alternatives on Android.
|
|
install("Pillow")
|
|
install("websockets")
|
|
}
|
|
}
|
|
}
|
|
|
|
// LedGrab Python source is included via a directory junction:
|
|
// android/app/src/main/python/ledgrab -> server/src/ledgrab
|
|
// This is the standard Chaquopy way to include local Python packages.
|
|
// Create the junction (run from repo root, no admin needed):
|
|
// cmd /c "mklink /J android\app\src\main\python\ledgrab server\src\ledgrab"
|
|
|
|
|
|
dependencies {
|
|
implementation("androidx.core:core-ktx:1.13.1")
|
|
implementation("androidx.appcompat:appcompat:1.7.0")
|
|
implementation("androidx.leanback:leanback:1.0.0")
|
|
implementation("com.google.android.material:material:1.12.0")
|
|
implementation("androidx.lifecycle:lifecycle-service:2.8.7")
|
|
// QR code generation for displaying server URL on TV
|
|
implementation("com.google.zxing:core:3.5.3")
|
|
// USB-serial drivers (CH340, CP2102, FTDI, Prolific, CDC-ACM) for
|
|
// driving Adalight/AmbiLED controllers plugged into Android TV boxes.
|
|
implementation("com.github.mik3y:usb-serial-for-android:3.8.1")
|
|
}
|