Templates: vendor_usb peripheral block in Android codegen#2
Conversation
Wires Mob.VendorUsb (companion mob PR) into generated Android projects. Files: - MobBridge.kt.eex (+309): 7 @JvmStatic methods, bulk-IN reader thread, ACTION_USB_PERMISSION receiver, jmethodID cache slots, all 7 wrapped in try/catch → error envelope (matches camera/audio convention). list_devices uses optInt(name, default) for nil-safe filter parsing. - beam_jni.c.eex (+58): 7 native trampolines. - AndroidManifest.xml.eex (+6): uses-feature usb.host required=false (don't filter app off non-USB devices on Play Store; runtime ACTION_USB_PERMISSION still gates per-device access).
|
Holding this open alongside mob#5. Companion runtime PR can't merge as-is — Android NIF surface migrated from C to Zig after both PRs were opened (see comment on mob#5). This templates PR will land together with the rebased runtime PR. The Kotlin/JNI work here is unaffected by that migration; only the C-side glue needs porting. |
|
Tracked under GenericJam/mob#6 (porting plan for the companion runtime PR). The templates side here is structurally fine; it'll land alongside the rebased runtime PR. |
|
Update: the runtime side of vendor_usb landed in GenericJam/mob#a3a1ed6 (closes #6). The Zig port adopts a This PR should still merge clean against current master (it predates the C → Zig migration on the runtime side; templates side is unchanged shape). Will pick it up in a follow-up pass. |
…sions Closes three items from mob/issues.md: #1 — Phoenix LiveReload `mac_listener` warnings on iOS device. The host binary isn't bundled and couldn't watch a sandboxed iOS filesystem anyway. Endpoint config in `mob_app.ex` now sets `code_reloader: false`, `watchers: []`, `live_reload: false`. #2 — esbuild + tailwind "version not configured" warnings on-device. Both are dev-time asset compilers that get pulled in as runtime apps; their host config (`config/dev.exs`) isn't bundled. Set their version constants directly via `Application.put_env` before `ensure_all_started`. They never run, just stop warning. #4 — Hardcoded port 4200 collided when two Mob LV apps were installed on the same device (Bandit returns :eaddrinuse, endpoint supervisor crashes, BEAM dies). The on-device default is now `4200 + :erlang.phash2(:<app_name>, 800)` — deterministic per-app range 4200..4999, p<0.5% collision odds at five installed apps. `Mob.LiveView.local_url/1` reads the same env, so the WebView URL stays in sync automatically. Generated `mob.exs` now ships `# config :mob, liveview_port: 4200` commented out — uncomment to pin a specific value (e.g. for a test harness). Tests updated to assert the new shape instead of the old `4200` literal.
Templates side of
Mob.VendorUsb(companion runtime/NIF/SDK inmob#5).Wires generated Android project to expose USB host for vendor-class devices (AtomVM ESP32 modems, custom HID, FTDI, industrial sensors, lab equipment).
Files
MobBridge.kt.eex(+309)@JvmStaticmethods:vendor_usb_list_devices,request_permission,open,bulk_write,start_reading,stop_reading,close:dataevents back to BEAMBroadcastReceiverforACTION_USB_PERMISSIONnativeDeliverVendorUsbOpened/Permission/Event/Datatry { ... } catch (e: Exception) { ...nativeDeliverVendorUsbEvent(pid, -1, \"error\", \"exception\") }matching existingcamera_*/audio_*convention. Unhandled JVM exception in JNI = BEAM dies, no crash dump. Defense caught 2 nil-encoding bugs during testing.vendor_usb_list_devicesusesoptInt(name, default)— defends against Elixirnil→ JSON string\"nil\"(whichgetIntchokes on, throwing JSONException → unhandled → BEAM dies).beam_jni.c.eex(+58)mob_nif.centry points.AndroidManifest.xml.eex(+6)uses-feature android:name=\"android.hardware.usb.host\" android:required=\"false\"— declares optional USB host so Play Store doesn't filter app off non-USB devices; runtimeACTION_USB_PERMISSIONstill gates per-device access.Tested
Generated project with these templates, deployed to OnePlus CPH2451 (Android 16). Full lifecycle confirmed against real vendor-class device (AtomVM ESP32 + Taixin TX-AH HaLow modem):
list_devicesreturns device JSONrequest_permissiontriggers system dialog →permission_granted_jsonenvelopeopenreturns session IDstart_reading+bulk_writeround-trip ETF length-prefixed framesclosereleases interface cleanly