Skip to content

Templates: vendor_usb peripheral block in Android codegen#2

Merged
GenericJam merged 1 commit into
GenericJam:masterfrom
HeroesLament:peripheral-vendor-usb-v2
May 14, 2026
Merged

Templates: vendor_usb peripheral block in Android codegen#2
GenericJam merged 1 commit into
GenericJam:masterfrom
HeroesLament:peripheral-vendor-usb-v2

Conversation

@HeroesLament
Copy link
Copy Markdown

@HeroesLament HeroesLament commented May 9, 2026

Templates side of Mob.VendorUsb (companion runtime/NIF/SDK in mob#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)

  • 7 @JvmStatic methods: vendor_usb_list_devices, request_permission, open, bulk_write, start_reading, stop_reading, close
  • Bulk-IN reader thread per session, posts :data events back to BEAM
  • BroadcastReceiver for ACTION_USB_PERMISSION
  • jmethodID cache slots for nativeDeliverVendorUsbOpened / Permission / Event / Data
  • All 7 methods wrapped in try { ... } catch (e: Exception) { ...nativeDeliverVendorUsbEvent(pid, -1, \"error\", \"exception\") } matching existing camera_* / audio_* convention. Unhandled JVM exception in JNI = BEAM dies, no crash dump. Defense caught 2 nil-encoding bugs during testing.
  • vendor_usb_list_devices uses optInt(name, default) — defends against Elixir nil → JSON string \"nil\" (which getInt chokes on, throwing JSONException → unhandled → BEAM dies).

beam_jni.c.eex (+58)

  • 7 native trampolines forwarding to matching mob_nif.c entry 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; runtime ACTION_USB_PERMISSION still 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_devices returns device JSON
  • request_permission triggers system dialog → permission_granted_json envelope
  • open returns session ID
  • start_reading + bulk_write round-trip ETF length-prefixed frames
  • close releases interface cleanly
  • All 7 try/catch wrappers exercised by deliberately malformed input — error envelopes delivered, BEAM survives.

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).
@GenericJam
Copy link
Copy Markdown
Owner

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.

@GenericJam
Copy link
Copy Markdown
Owner

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.

@GenericJam
Copy link
Copy Markdown
Owner

Update: the runtime side of vendor_usb landed in GenericJam/mob#a3a1ed6 (closes #6). The Zig port adopts a cacheOptional model — on the unmodified template, MobBridge lookups for vendor_usb_* fail gracefully and the BEAM emits :unsupported events instead of crashing. So this templates PR is no longer a hard blocker for vendor_usb being merged into mob proper, but it IS still the gate for end-to-end functionality on Android (the Kotlin side that this PR adds is what actually talks to UsbManager).

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.

@GenericJam GenericJam merged commit 5145f3d into GenericJam:master May 14, 2026
1 check passed
GenericJam added a commit that referenced this pull request May 14, 2026
…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.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants