-
-
Notifications
You must be signed in to change notification settings - Fork 1k
Custom android serial plugin #4706
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Custom android serial plugin #4706
Conversation
WalkthroughAdds a self-contained Capacitor USB-serial plugin for Android (native Java + Capacitor wrapper), a TypeScript protocol adapter, UI and port-handler integration, Android manifest/build updates, and comprehensive documentation and quick-start guides to enable MSP over USB on Android devices. Changes
Sequence Diagram(s)sequenceDiagram
participant App as App (CapacitorSerial.js)
participant Native as BetaflightSerialPlugin
participant USB as USB Device
rect rgb(220,235,255)
Note over App,Native: Init & discovery
App->>Native: setupNativeListeners()
App->>Native: getDevices()
Native->>USB: probe devices
USB-->>Native: device list
Native-->>App: devices array (event/response)
end
rect rgb(220,255,230)
Note over App,Native: Connect flow
App->>Native: requestPermission(deviceId)
Native-->>App: permission granted (event)
App->>Native: connect(deviceId, params)
Native->>USB: open/configure port
Native-->>App: connected
end
rect rgb(255,235,230)
Note over App,Native: Data exchange
App->>App: send(Uint8Array) -> hex
App->>Native: write(hexString)
Native->>USB: write bytes
USB-->>Native: bytes written
USB-->>Native: incoming bytes
Native->>App: dataReceived(hexString) (event)
App->>App: hex -> Uint8Array -> dispatch to consumer
end
Estimated code review effort🎯 4 (Complex) | ⏱️ ~60 minutes
Possibly related PRs
Suggested labels
Suggested reviewers
Pre-merge checks and finishing touches❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
✨ Finishing touches
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
…l Android APK; publish artifacts to draft release
…; publish after desktop+android
…rd docker job to workflow_call only
…2gtk-4.1, gtk3, ayatana-appindicator, librsvg2, patchelf)
…D for Tauri before* commands
…eb build with 'cd .. && yarn <cmd>'
…ackage.json not found
…tifact path (src/dist/) to fix missing assets
… libudev-sys and OpenSSL dependencies
…lity and formatting
131c168 to
9a8bf7f
Compare
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 4
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (2)
src/js/port_handler.js (2)
88-99: Remove the constant|| trueso capacitor devices are only queried when actually supportedIn
updateDeviceListyou have:case "capacitorserial": if (this.showCapacitorOption || true) { ports = await serial.getDevices("capacitorserial"); } break;This makes the condition always true, so
serial.getDevices("capacitorserial")is called on all platforms, even when Capacitor Serial isn’t available. Combined with the web stub that throwsunimplemented, this results in unnecessary errors (caught, but noisy) and is also what Biome is flagging.Suggest:
- case "capacitorserial": - if (this.showCapacitorOption || true) { - ports = await serial.getDevices("capacitorserial"); - } - break; + case "capacitorserial": + if (this.showCapacitorOption) { + ports = await serial.getDevices("capacitorserial"); + } + break;This keeps capacitor discovery behind the same capability gate as the UI, and avoids calling the native-only plugin on web builds.
Also applies to: 318-333
115-145: Align capacitor device removal and active-port selection with the new protocolThere are a couple of consistency gaps around Capacitor Serial:
removedSerialDeviceonly distinguishes bluetooth vs “everything else” (webserial). When aremovedDeviceevent comes in with apathstarting with"capacitor-", you never callupdateDeviceList("capacitorserial"), socurrentCapacitorPortscan remain stale after a detach.- In
selectActivePort, the “already connected” branch looks only incurrentSerialPorts:If the active connection is via Capacitor Serial, this won’t match any entry and the logic will ignore the active port.if (serial.connected) { selectedPort = this.currentSerialPorts.find( (device) => device === serial.getConnectedPort(), ); }- Capacitor ports are considered last in the auto-selection pipeline, which is fine, but they should be treated consistently wherever other serial backends are.
To make behavior consistent, consider:
- Updating
removedSerialDeviceto detect capacitor paths:- const updatePromise = devicePath.startsWith("bluetooth")
? this.updateDeviceList("webbluetooth"): this.updateDeviceList("webserial");
- const updatePromise = devicePath.startsWith("bluetooth")
? this.updateDeviceList("webbluetooth"): devicePath.startsWith("capacitor-")? this.updateDeviceList("capacitorserial"): this.updateDeviceList("webserial");
- Including
currentCapacitorPortswhen resolving the already-connected port, or comparing by.pathagainstserial.getConnectedPort().path, so capacitor connections are respected in the “use the connected device” branch.That will keep the port lists and auto-selection behavior in sync with the new protocol.
Also applies to: 198-243, 254-262, 285-303, 352-366
🧹 Nitpick comments (5)
vite.config.js (1)
123-123: Network binding looks good for Android development workflow.Binding to
0.0.0.0is appropriate for testing the Capacitor plugin on Android devices. Since this affects only the dev server, the security implications are limited to development environments.Consider making this configurable via an environment variable (e.g.,
VITE_HOST) to give developers control over network exposure, especially when working on shared or untrusted networks.Optional refactor: Make host configurable
server: { port: 8000, strictPort: true, - host: "0.0.0.0", // Listen on all network interfaces for Android device access + host: process.env.VITE_HOST || "0.0.0.0", // Listen on all network interfaces for Android device access },package.json (1)
48-48: Consider the long-term dependency strategy.The plugin is currently referenced as a local file dependency. While this works for development and self-contained distributions, consider whether the plugin should eventually be published to npm for better package management and easier updates across installations.
Local file dependencies can have implications:
- Lock files may include absolute paths in some environments
- Different behavior between development and production
- Harder to version independently
If the plugin is intended to remain tightly coupled to the configurator, the current approach is acceptable. Otherwise, consider publishing to npm (public or private registry).
README_IMPLEMENTATION.md (1)
68-132: Add languages to fenced code blocks to satisfy markdownlintSeveral fenced blocks (architecture tree, file lists, checklists) are missing language identifiers, which triggers MD040. Suggest adding appropriate languages, for example:
```textfor directory trees / ASCII diagrams```bashfor shell commands```javascriptfor JS examplesThis will keep linters quiet and improve rendering in some viewers.
Also applies to: 188-206
src/js/protocols/CapacitorSerial.js (1)
315-332: Consider making hex conversion slightly more defensiveRight now
hexStringToUint8Arrayassumes an even-length hex string and silently truncates the last nibble if length is odd. If there’s any chance of malformed input from native, consider:
- Early-returning or logging when
hexString.length % 2 !== 0- Guarding
parseIntfailures (e.g. withNumber.isNaN)This would make failures easier to diagnose if native ever emits bad data.
CAPACITOR_SERIAL_IMPLEMENTATION.md (1)
19-38: Specify languages on fenced code blocks for better linting and readabilityA few code/diagram blocks (architecture tree, serial usage examples, USB library URLs) don’t specify a language. Adding one (e.g.
text,bash,javascript,xml) will satisfy markdownlint and improve syntax highlighting.Also applies to: 81-93, 151-166, 290-306
📜 Review details
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
⛔ Files ignored due to path filters (2)
capacitor-plugin-betaflight-serial/yarn.lockis excluded by!**/yarn.lock,!**/*.lockyarn.lockis excluded by!**/yarn.lock,!**/*.lock
📒 Files selected for processing (28)
CAPACITOR_SERIAL_IMPLEMENTATION.md(1 hunks)QUICK_START.md(1 hunks)README_IMPLEMENTATION.md(1 hunks)android/app/capacitor.build.gradle(1 hunks)android/app/src/main/AndroidManifest.xml(2 hunks)android/app/src/main/res/xml/device_filter.xml(1 hunks)android/build.gradle(1 hunks)android/capacitor.settings.gradle(1 hunks)capacitor-plugin-betaflight-serial/.gitignore(1 hunks)capacitor-plugin-betaflight-serial/README.md(1 hunks)capacitor-plugin-betaflight-serial/android/build.gradle(1 hunks)capacitor-plugin-betaflight-serial/android/proguard-rules.pro(1 hunks)capacitor-plugin-betaflight-serial/android/src/main/java/com/betaflight/plugin/serial/BetaflightSerialPlugin.java(1 hunks)capacitor-plugin-betaflight-serial/package.json(1 hunks)capacitor-plugin-betaflight-serial/rollup.config.js(1 hunks)capacitor-plugin-betaflight-serial/src/definitions.ts(1 hunks)capacitor-plugin-betaflight-serial/src/index.ts(1 hunks)capacitor-plugin-betaflight-serial/src/web.ts(1 hunks)capacitor-plugin-betaflight-serial/tsconfig.json(1 hunks)package.json(1 hunks)src/components/port-picker/PortPicker.vue(3 hunks)src/components/port-picker/PortsInput.vue(5 hunks)src/index.html(1 hunks)src/js/port_handler.js(10 hunks)src/js/protocols/CapacitorSerial.js(1 hunks)src/js/serial.js(3 hunks)src/js/utils/checkBrowserCompatibility.js(2 hunks)vite.config.js(1 hunks)
🧰 Additional context used
🧠 Learnings (11)
📓 Common learnings
Learnt from: haslinghuis
Repo: betaflight/betaflight-configurator PR: 0
File: :0-0
Timestamp: 2025-05-14T21:51:09.253Z
Learning: On Android Chrome browsers, Web Bluetooth API is supported for BLE devices and Web USB API is supported, but Web Serial API is not supported (except limited Bluetooth serial support added in 2025). The Betaflight Configurator should detect and use available APIs on Android rather than requiring all three.
Learnt from: haslinghuis
Repo: betaflight/betaflight-configurator PR: 4576
File: src/js/port_usage.js:17-23
Timestamp: 2025-08-22T16:43:20.901Z
Learning: In betaflight-configurator, the serial facade architecture requires accessing metrics like bitrate, bytesReceived, and bytesSent from serial._protocol rather than the top-level serial object. This change maintains compatibility with existing port utilization calculations that have been stable for over 11 years.
Learnt from: haslinghuis
Repo: betaflight/betaflight-configurator PR: 4687
File: src/js/protocols/TauriSerial.js:299-310
Timestamp: 2025-11-06T17:26:50.201Z
Learning: In tauri-plugin-serialplugin Android development, the Android project must be initialized via `tauri android init` before the USB support patches (AndroidManifest.xml permissions, device_filter.xml, MainActivity.kt) can be applied. Without initialization, the plugin returns empty results from available_ports_direct() because the generated Android project structure and USB permissions don't exist yet.
📚 Learning: 2025-06-19T22:13:09.136Z
Learnt from: blckmn
Repo: betaflight/betaflight-configurator PR: 4521
File: src/js/protocols/WebSerial.js:148-151
Timestamp: 2025-06-19T22:13:09.136Z
Learning: In WebSerial.js, there's a timing issue where the cached `this.ports` array doesn't immediately reflect newly permitted devices after `requestPermissionDevice()` completes. The `getDevices()` method needs to refresh the device list from the browser API to return accurate data immediately following a permission request and user acceptance.
Applied to files:
src/components/port-picker/PortsInput.vuesrc/js/serial.jssrc/js/protocols/CapacitorSerial.jscapacitor-plugin-betaflight-serial/src/web.tssrc/js/port_handler.js
📚 Learning: 2025-10-25T21:16:32.474Z
Learnt from: haslinghuis
Repo: betaflight/betaflight-configurator PR: 4379
File: src/js/protocols/TauriSerial.js:203-259
Timestamp: 2025-10-25T21:16:32.474Z
Learning: In TauriSerial (src/js/protocols/TauriSerial.js), the requestPermissionDevice() method is not needed and not invoked. Tauri automatically discovers serial devices through the constructor's loadDevices() and startDeviceMonitoring() calls, bypassing the browser permission model that WebSerial requires. Devices are auto-detected via a 1-second polling interval without user permission prompts.
Applied to files:
src/components/port-picker/PortsInput.vuesrc/js/serial.jssrc/js/protocols/CapacitorSerial.jscapacitor-plugin-betaflight-serial/src/web.tssrc/js/port_handler.jscapacitor-plugin-betaflight-serial/android/src/main/java/com/betaflight/plugin/serial/BetaflightSerialPlugin.javacapacitor-plugin-betaflight-serial/src/definitions.tscapacitor-plugin-betaflight-serial/README.md
📚 Learning: 2025-06-09T00:32:21.385Z
Learnt from: haslinghuis
Repo: betaflight/betaflight-configurator PR: 0
File: :0-0
Timestamp: 2025-06-09T00:32:21.385Z
Learning: In the betaflight-configurator codebase, port paths use counter prefixes (e.g., "bluetooth1", "bluetooth2", "serial1") rather than direct protocol identifiers. The protocol selection logic correctly uses `portPath.startsWith("bluetooth")` to detect bluetooth ports regardless of the counter suffix, rather than direct string matching against protocol map keys.
Applied to files:
src/js/serial.jssrc/js/port_handler.js
📚 Learning: 2025-08-22T16:43:20.901Z
Learnt from: haslinghuis
Repo: betaflight/betaflight-configurator PR: 4576
File: src/js/port_usage.js:17-23
Timestamp: 2025-08-22T16:43:20.901Z
Learning: In betaflight-configurator, the serial facade architecture requires accessing metrics like bitrate, bytesReceived, and bytesSent from serial._protocol rather than the top-level serial object. This change maintains compatibility with existing port utilization calculations that have been stable for over 11 years.
Applied to files:
src/js/serial.jsCAPACITOR_SERIAL_IMPLEMENTATION.mdsrc/js/protocols/CapacitorSerial.jscapacitor-plugin-betaflight-serial/android/src/main/java/com/betaflight/plugin/serial/BetaflightSerialPlugin.javacapacitor-plugin-betaflight-serial/README.md
📚 Learning: 2025-05-14T21:51:09.253Z
Learnt from: haslinghuis
Repo: betaflight/betaflight-configurator PR: 0
File: :0-0
Timestamp: 2025-05-14T21:51:09.253Z
Learning: On Android Chrome browsers, Web Bluetooth API is supported for BLE devices and Web USB API is supported, but Web Serial API is not supported (except limited Bluetooth serial support added in 2025). The Betaflight Configurator should detect and use available APIs on Android rather than requiring all three.
Applied to files:
src/js/serial.jsCAPACITOR_SERIAL_IMPLEMENTATION.mdsrc/js/protocols/CapacitorSerial.jssrc/js/utils/checkBrowserCompatibility.jscapacitor-plugin-betaflight-serial/src/web.tssrc/js/port_handler.jscapacitor-plugin-betaflight-serial/android/src/main/java/com/betaflight/plugin/serial/BetaflightSerialPlugin.javacapacitor-plugin-betaflight-serial/src/definitions.tscapacitor-plugin-betaflight-serial/README.mdQUICK_START.md
📚 Learning: 2025-11-06T17:26:50.201Z
Learnt from: haslinghuis
Repo: betaflight/betaflight-configurator PR: 4687
File: src/js/protocols/TauriSerial.js:299-310
Timestamp: 2025-11-06T17:26:50.201Z
Learning: In tauri-plugin-serialplugin Android development, the Android project must be initialized via `tauri android init` before the USB support patches (AndroidManifest.xml permissions, device_filter.xml, MainActivity.kt) can be applied. Without initialization, the plugin returns empty results from available_ports_direct() because the generated Android project structure and USB permissions don't exist yet.
Applied to files:
capacitor-plugin-betaflight-serial/android/build.gradleCAPACITOR_SERIAL_IMPLEMENTATION.mdsrc/js/protocols/CapacitorSerial.jsandroid/app/src/main/AndroidManifest.xmlcapacitor-plugin-betaflight-serial/android/src/main/java/com/betaflight/plugin/serial/BetaflightSerialPlugin.javaQUICK_START.md
📚 Learning: 2025-05-14T21:39:16.388Z
Learnt from: haslinghuis
Repo: betaflight/betaflight-configurator PR: 0
File: :0-0
Timestamp: 2025-05-14T21:39:16.388Z
Learning: On Android Chrome browsers, Web Bluetooth API is supported from version 133+ (requires Android 6.0+) and Web USB API is supported from version 136+, but Web Serial API is not supported. The Betaflight Configurator should detect and use available APIs on Android rather than requiring all three.
Applied to files:
CAPACITOR_SERIAL_IMPLEMENTATION.mdsrc/js/protocols/CapacitorSerial.jssrc/js/utils/checkBrowserCompatibility.jscapacitor-plugin-betaflight-serial/src/web.tssrc/js/port_handler.jscapacitor-plugin-betaflight-serial/android/src/main/java/com/betaflight/plugin/serial/BetaflightSerialPlugin.javacapacitor-plugin-betaflight-serial/README.md
📚 Learning: 2025-09-10T18:26:10.136Z
Learnt from: haslinghuis
Repo: betaflight/betaflight-configurator PR: 4601
File: package.json:125-125
Timestamp: 2025-09-10T18:26:10.136Z
Learning: The betaflight-configurator project uses Node 20.19.4, which satisfies Vite 7's minimum Node requirement of 20.19+.
Applied to files:
package.json
📚 Learning: 2025-09-10T18:26:10.136Z
Learnt from: haslinghuis
Repo: betaflight/betaflight-configurator PR: 4601
File: package.json:125-125
Timestamp: 2025-09-10T18:26:10.136Z
Learning: In betaflight-configurator, dependency updates are handled incrementally - Vue dependencies are updated separately from Vite dependencies for better isolation and maintainability.
Applied to files:
package.json
📚 Learning: 2025-06-20T12:35:49.283Z
Learnt from: mituritsyn
Repo: betaflight/betaflight-configurator PR: 4526
File: src/js/gui.js:43-43
Timestamp: 2025-06-20T12:35:49.283Z
Learning: In the Betaflight Configurator codebase, tabs in `defaultCloudBuildTabOptions` are conditionally displayed based on firmware build options. The logic in `serial_backend.js` checks `FC.CONFIG.buildOptions` and only adds tabs to `GUI.allowedTabs` if the firmware was built with support for that feature.
Applied to files:
src/js/port_handler.js
🧬 Code graph analysis (4)
capacitor-plugin-betaflight-serial/src/index.ts (1)
capacitor-plugin-betaflight-serial/src/definitions.ts (1)
BetaflightSerialPlugin(1-90)
capacitor-plugin-betaflight-serial/src/web.ts (1)
capacitor-plugin-betaflight-serial/src/definitions.ts (3)
SerialDevice(95-135)ConnectOptions(140-175)WriteOptions(180-185)
src/js/port_handler.js (2)
src/js/utils/checkBrowserCompatibility.js (5)
checkBrowserCompatibility(72-147)checkWebBluetoothSupport(163-175)checkWebSerialSupport(149-161)checkWebUSBSupport(177-189)checkCapacitorSerialSupport(63-70)src/js/serial.js (2)
serial(221-221)serial(221-221)
capacitor-plugin-betaflight-serial/android/src/main/java/com/betaflight/plugin/serial/BetaflightSerialPlugin.java (2)
capacitor-plugin-betaflight-serial/src/definitions.ts (1)
BetaflightSerialPlugin(1-90)capacitor-plugin-betaflight-serial/src/web.ts (6)
requestPermission(6-8)getDevices(10-12)connect(14-16)disconnect(18-20)write(22-24)read(26-28)
🪛 Biome (2.1.2)
src/js/port_handler.js
[error] 329-329: Unexpected constant condition.
(lint/correctness/noConstantCondition)
🪛 LanguageTool
CAPACITOR_SERIAL_IMPLEMENTATION.md
[style] ~275-~275: Consider an alternative adverb to strengthen your wording.
Context: ... 3. Verify flight controller is powered properly 4. Check for conflicting apps using USB...
(PROPERLY_THOROUGHLY)
[style] ~313-~313: Consider using a different verb for a more formal wording.
Context: ...Extensible* - Easy to add features or fix issues 6. Documented - Comprehensiv...
(FIX_RESOLVE)
README_IMPLEMENTATION.md
[style] ~167-~167: The double modal “required Designed” is nonstandard (only accepted in certain dialects). Consider “to be Designed”.
Context: ...oding built-in - No patching required - Designed for MSP from the start ### ✅ Comprehen...
(NEEDS_FIXED)
[uncategorized] ~305-~305: If this is a compound adjective that modifies the following noun, use a hyphen.
Context: ... the Android app 4. Test on device ### Short Term 1. Test with various devices 2. Gather ...
(EN_COMPOUND_ADJECTIVE_INTERNAL)
[uncategorized] ~311-~311: If this is a compound adjective that modifies the following noun, use a hyphen.
Context: ...x any issues 4. Performance tuning ### Long Term 1. User documentation 2. Beta release 3...
(EN_COMPOUND_ADJECTIVE_INTERNAL)
[style] ~351-~351: Some style guides suggest that commas should set off the year in a month-day-year date.
Context: ...rly! 🎯 --- Created: November 17, 2025 Status: ✅ Ready for Testing **L...
(MISSING_COMMA_AFTER_YEAR)
QUICK_START.md
[style] ~19-~19: Consider shortening or rephrasing this to strengthen your wording.
Context: ...d the Plugin (Optional) If you want to make changes to the plugin TypeScript code: ```bash cd...
(MAKE_CHANGES)
[style] ~220-~220: Consider shortening or rephrasing this to strengthen your wording.
Context: ...ified ``` ## Development Workflow ### Making Changes to Native Code 1. Edit `capacitor-plugin-...
(MAKE_CHANGES)
[style] ~226-~226: Consider shortening or rephrasing this to strengthen your wording.
Context: ...3. Rebuild app: yarn android:run ### Making Changes to TypeScript 1. Edit files in `capacitor...
(MAKE_CHANGES)
[style] ~233-~233: Consider shortening or rephrasing this to strengthen your wording.
Context: ...4. Rebuild app: yarn android:run ### Making Changes to Protocol Adapter 1. Edit `src/js/proto...
(MAKE_CHANGES)
🪛 markdownlint-cli2 (0.18.1)
CAPACITOR_SERIAL_IMPLEMENTATION.md
45-45: Fenced code blocks should have a language specified
(MD040, fenced-code-language)
70-70: Fenced code blocks should have a language specified
(MD040, fenced-code-language)
192-192: Fenced code blocks should have a language specified
(MD040, fenced-code-language)
README_IMPLEMENTATION.md
68-68: Fenced code blocks should have a language specified
(MD040, fenced-code-language)
84-84: Fenced code blocks should have a language specified
(MD040, fenced-code-language)
99-99: Fenced code blocks should have a language specified
(MD040, fenced-code-language)
116-116: Fenced code blocks should have a language specified
(MD040, fenced-code-language)
🔇 Additional comments (30)
capacitor-plugin-betaflight-serial/.gitignore (1)
1-17: Clean and comprehensive .gitignore for Capacitor plugin with Android development.The .gitignore appropriately covers build artifacts, npm dependencies, IDE/editor files, and Android-specific build outputs. The entries follow standard patterns for Capacitor plugins and align well with the Android native code integration described in the PR.
capacitor-plugin-betaflight-serial/tsconfig.json (1)
1-22: LGTM! Standard Capacitor plugin TypeScript configuration.The TypeScript configuration is appropriate for a Capacitor plugin, with sensible compiler options, strict checks enabled, and proper module/target settings for modern environments.
capacitor-plugin-betaflight-serial/android/proguard-rules.pro (1)
1-1: LGTM! Necessary ProGuard rule for Capacitor plugin.The rule correctly preserves the plugin classes from obfuscation, which is required for Capacitor's reflection-based plugin invocation mechanism.
android/app/capacitor.build.gradle (1)
12-12: LGTM! Standard Capacitor plugin dependency.The plugin dependency is correctly wired into the Android app module using Gradle's project reference syntax.
android/build.gradle (1)
24-24: LGTM! JitPack repository for USB serial library.Adding JitPack is the standard approach for resolving GitHub-hosted Android libraries like usb-serial-for-android.
android/capacitor.settings.gradle (1)
5-6: LGTM! Standard Capacitor plugin module registration.The plugin module is correctly included and pointed to the node_modules location where it will be installed.
android/app/src/main/res/xml/device_filter.xml (1)
1-34: LGTM! Comprehensive USB device filter coverage.The device filters correctly enumerate common Betaflight-compatible USB-to-serial devices with accurate vendor and product IDs (verified in decimal format). This includes:
- FTDI adapters (FT232R)
- STM32 boards and bootloaders
- CP210x Silicon Labs adapters
- GD32, AT32, and APM32 microcontroller boards
- Raspberry Pi Pico variants
The filter integrates with AndroidManifest.xml to enable USB device attachment detection for the plugin.
src/js/serial.js (3)
5-5: LGTM! CapacitorSerial import added.The import follows the same pattern as other protocol implementations.
23-23: LGTM! CapacitorSerial protocol registration.The protocol is correctly registered in the protocols array with a name and instance, consistent with other protocol registrations.
89-91: LGTM! Capacitor port routing logic.The routing logic correctly detects "capacitor-" prefixed port paths and selects the CapacitorSerial protocol instance. This follows the same pattern as the bluetooth protocol routing and aligns with the port path convention used throughout the configurator.
Based on learnings
src/components/port-picker/PortPicker.vue (1)
18-18: LGTM! Clean integration following established patterns.The Capacitor device props and bindings mirror the existing bluetooth/serial/usb patterns perfectly. The implementation is consistent with the component's architecture.
Also applies to: 25-25, 62-65, 86-89
src/index.html (1)
34-34: LGTM! Bindings are consistent with existing port types.The Capacitor device bindings follow the established pattern and correctly reference the PortHandler properties.
Also applies to: 40-40
src/js/utils/checkBrowserCompatibility.js (1)
63-70: LGTM! Capacitor Serial support correctly extends compatibility checks for Android.The new
checkCapacitorSerialSupport()function properly detects Android platform and integrates into the compatibility logic. This allows Android devices to pass compatibility checks using Capacitor Serial even when Web Serial API is unavailable.Based on learnings.
Also applies to: 76-76, 85-89, 95-95
capacitor-plugin-betaflight-serial/package.json (1)
1-71: Package structure looks good overall.The package.json follows standard Capacitor plugin conventions with appropriate scripts, dependencies, and metadata. The build workflow and tooling configuration are well-structured.
android/app/src/main/AndroidManifest.xml (1)
25-31: LGTM! Standard Android USB host configuration.The manifest changes correctly implement USB device support with proper intent filters, metadata references, and feature declarations. This follows Android USB host best practices.
Also applies to: 51-51
capacitor-plugin-betaflight-serial/README.md (1)
1-248: LGTM! Excellent comprehensive documentation.The README provides thorough documentation covering features, installation, Android setup, API usage, and examples. The step-by-step setup instructions and TypeScript examples are clear and helpful.
src/components/port-picker/PortsInput.vue (1)
45-52: LGTM! UI extension follows established patterns perfectly.The Capacitor device integration in PortsInput mirrors the existing serial/bluetooth/usb implementation patterns. The event handling and prop definitions are consistent with the component's architecture.
Also applies to: 62-64, 122-125, 154-157, 201-202
capacitor-plugin-betaflight-serial/src/index.ts (1)
1-10: LGTM! Standard Capacitor plugin entry point.The plugin registration and exports follow Capacitor conventions with proper lazy loading of the web implementation and TypeScript type exports.
capacitor-plugin-betaflight-serial/rollup.config.js (1)
1-22: Rollup config looks consistent for a Capacitor plugin bundleInput/output formats and externals match the expected Capacitor plugin pattern; I don’t see any issues here.
src/js/protocols/CapacitorSerial.js (1)
21-27: Protocol metrics and event wiring align with the serial facadeKeeping
bitrate,bytesSent,bytesReceived, andfailedon the protocol instance and incrementing them inhandleDataReceived/sendmatches how port utilization expects to read metrics fromserial._protocol, so this should slot cleanly into the existing facade.Also applies to: 64-71, 188-205
capacitor-plugin-betaflight-serial/src/web.ts (1)
5-28: Explicit web stub behavior is clear and appropriateThrowing
unimplementedwith a clear message for all methods makes it obvious this plugin is native-only and should not be used in the browser; no changes needed here.capacitor-plugin-betaflight-serial/src/definitions.ts (1)
1-202: Type definitions for the plugin API look solidThe interfaces capture the plugin surface and event contracts cleanly (including listener overloads and hex-string data), which should make both native and JS sides easier to keep in sync.
capacitor-plugin-betaflight-serial/android/build.gradle (1)
1-62: Android plugin Gradle configuration looks consistentNamespace, sdk levels, Java 17, capacitor-android, and usb-serial-for-android dependencies are wired in a standard way for a library module; I don’t see issues in this build file itself.
capacitor-plugin-betaflight-serial/android/src/main/java/com/betaflight/plugin/serial/BetaflightSerialPlugin.java (4)
81-99: LGTM! Proper API level handling for broadcast receiver registration.The code correctly handles the Android 13+ requirement for RECEIVER_NOT_EXPORTED flag when registering broadcast receivers, with appropriate fallback for older API levels.
175-247: LGTM! Robust connection handling with proper error recovery.The connect method correctly:
- Validates required parameters
- Closes existing connections before opening new ones
- Checks permissions before attempting connection
- Returns structured success/error responses
- Cleans up on failure
265-274: Good handling of empty write payloads.Treating empty hex strings as a no-op (returning 0 bytes sent) is a sensible design choice that prevents errors while maintaining API consistency.
453-475: LGTM! Comprehensive resource cleanup.The cleanup method correctly handles all resources in reverse order of acquisition and is null-safe, preventing resource leaks even in error scenarios.
QUICK_START.md (3)
80-126: LGTM! Comprehensive Android testing guide.The testing instructions cover all essential aspects:
- Prerequisites clearly listed
- Step-by-step setup for USB debugging
- Expected behavior with clear success criteria
- Debugging commands with adb filtering
This will help users quickly validate the plugin functionality.
145-189: LGTM! Excellent troubleshooting section.The common issues section anticipates real problems users will encounter (permission dialogs, device detection, build errors) and provides actionable solutions. The graduated approach from simple to comprehensive fixes is well thought out.
240-259: Helpful comparison table with PR #4698.The comparison clearly articulates the key differences between this custom implementation and the external plugin approach, helping reviewers and users understand the trade-offs and benefits of this design choice.
| public void requestPermission(PluginCall call) { | ||
| try { | ||
| List<UsbSerialDriver> availableDrivers = UsbSerialProber.getDefaultProber().findAllDrivers(usbManager); | ||
|
|
||
| if (availableDrivers.isEmpty()) { | ||
| JSObject result = new JSObject(); | ||
| result.put("devices", new JSArray()); | ||
| call.resolve(result); | ||
| return; | ||
| } | ||
|
|
||
| pendingPermissionCall = call; | ||
| permissionRequestedDevices.clear(); | ||
|
|
||
| // Request permission for each device | ||
| for (UsbSerialDriver driver : availableDrivers) { | ||
| UsbDevice device = driver.getDevice(); | ||
|
|
||
| if (!usbManager.hasPermission(device)) { | ||
| String deviceKey = getDeviceKey(device); | ||
| permissionRequestedDevices.put(deviceKey, device); | ||
|
|
||
| PendingIntent permissionIntent = PendingIntent.getBroadcast( | ||
| getContext(), | ||
| 0, | ||
| new Intent(ACTION_USB_PERMISSION), | ||
| Build.VERSION.SDK_INT >= Build.VERSION_CODES.S | ||
| ? PendingIntent.FLAG_MUTABLE | PendingIntent.FLAG_UPDATE_CURRENT | ||
| : PendingIntent.FLAG_UPDATE_CURRENT | ||
| ); | ||
|
|
||
| usbManager.requestPermission(device, permissionIntent); | ||
| Log.d(TAG, "Requested permission for device: " + deviceKey); | ||
| } | ||
| } | ||
|
|
||
| // If all devices already have permission, resolve immediately | ||
| if (permissionRequestedDevices.isEmpty()) { | ||
| resolveWithDeviceList(call); | ||
| } | ||
| } catch (Exception e) { | ||
| Log.e(TAG, "Error requesting permission", e); | ||
| call.reject("Failed to request permission: " + e.getMessage()); | ||
| } | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Concurrency issue: requestPermission lacks protection against overlapping calls.
If requestPermission is called multiple times before permission dialogs are resolved, the following race conditions occur:
pendingPermissionCallgets overwritten (line 128), causing earlier calls to never resolvepermissionRequestedDevices.clear()(line 129) disrupts tracking for in-flight permission requests- The first call may resolve with incomplete device lists or hang indefinitely
Consider one of these solutions:
Solution 1: Reject overlapping calls
@PluginMethod
public void requestPermission(PluginCall call) {
try {
+ if (pendingPermissionCall != null) {
+ call.reject("Permission request already in progress");
+ return;
+ }
+
List<UsbSerialDriver> availableDrivers = UsbSerialProber.getDefaultProber().findAllDrivers(usbManager);Solution 2: Queue calls (more complex)
Maintain a queue of pending calls and process them sequentially.
🤖 Prompt for AI Agents
In
capacitor-plugin-betaflight-serial/android/src/main/java/com/betaflight/plugin/serial/BetaflightSerialPlugin.java
around lines 117 to 161, requestPermission currently allows overlapping calls
which overwrites pendingPermissionCall and clears permissionRequestedDevices
causing lost/blocked responses; fix by rejecting overlapping calls at the start
(if pendingPermissionCall != null then call.reject("Permission request already
in progress") and return), only call permissionRequestedDevices.clear() when you
have established that you own the new request (i.e., after checking no active
pending call), and ensure you always clear pendingPermissionCall and
permissionRequestedDevices when the flow completes or errors so subsequent
requests can proceed.
| private byte[] hexStringToByteArray(String hexString) { | ||
| int len = hexString.length(); | ||
| byte[] data = new byte[len / 2]; | ||
| for (int i = 0; i < len; i += 2) { | ||
| data[i / 2] = (byte) ((Character.digit(hexString.charAt(i), 16) << 4) | ||
| + Character.digit(hexString.charAt(i + 1), 16)); | ||
| } | ||
| return data; | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Critical: hexStringToByteArray lacks input validation.
The method will crash or behave incorrectly with:
- Odd-length hex strings (e.g., "abc" → ArrayIndexOutOfBoundsException)
- Invalid hex characters (e.g., "zz" → NumberFormatException with return value -1)
This is a direct crash risk since write() accepts user-provided hex strings.
Apply this diff to add validation:
private byte[] hexStringToByteArray(String hexString) {
+ if (hexString.length() % 2 != 0) {
+ throw new IllegalArgumentException("Hex string must have even length");
+ }
+
int len = hexString.length();
byte[] data = new byte[len / 2];
for (int i = 0; i < len; i += 2) {
- data[i / 2] = (byte) ((Character.digit(hexString.charAt(i), 16) << 4)
- + Character.digit(hexString.charAt(i + 1), 16));
+ int highNibble = Character.digit(hexString.charAt(i), 16);
+ int lowNibble = Character.digit(hexString.charAt(i + 1), 16);
+
+ if (highNibble == -1 || lowNibble == -1) {
+ throw new IllegalArgumentException("Invalid hex string: contains non-hexadecimal characters");
+ }
+
+ data[i / 2] = (byte) ((highNibble << 4) + lowNibble);
}
return data;
}🤖 Prompt for AI Agents
In
capacitor-plugin-betaflight-serial/android/src/main/java/com/betaflight/plugin/serial/BetaflightSerialPlugin.java
around lines 498 to 506, the hexStringToByteArray method lacks input validation
and can crash on odd-length strings or invalid hex characters; update the method
to validate input: check for null/empty, optionally strip a leading "0x"/"0X"
and whitespace, verify the remaining length is even, and ensure every character
is a valid hex digit (0-9, a-f, A-F) before parsing; if validation fails, throw
an IllegalArgumentException with a clear message rather than letting it produce
ArrayIndexOutOfBoundsException or negative digit values.
| "repository": { | ||
| "type": "git", | ||
| "url": "github.com/betaflight/betaflight-configurator" | ||
| }, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fix the repository URL format.
The repository URL is missing the protocol prefix. It should be either "https://github.com/betaflight/betaflight-configurator" or "git+https://github.com/betaflight/betaflight-configurator.git".
Apply this diff:
"repository": {
"type": "git",
- "url": "github.com/betaflight/betaflight-configurator"
+ "url": "https://github.com/betaflight/betaflight-configurator"
},📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| "repository": { | |
| "type": "git", | |
| "url": "github.com/betaflight/betaflight-configurator" | |
| }, | |
| "repository": { | |
| "type": "git", | |
| "url": "https://github.com/betaflight/betaflight-configurator" | |
| }, |
🤖 Prompt for AI Agents
In capacitor-plugin-betaflight-serial/package.json around lines 17 to 20 the
repository.url is missing the protocol and proper git suffix; update the value
to a proper URL such as "https://github.com/betaflight/betaflight-configurator"
or "git+https://github.com/betaflight/betaflight-configurator.git" so the
repository field conforms to npm/package.json expectations.
| ```bash | ||
| cd /home/mark/dev/betaflight/betaflight-configurator/capacitor-plugin-serial/betaflight-configurator | ||
|
|
||
| # Install the custom plugin (local package) | ||
| yarn install |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Remove hardcoded user-specific path.
Line 8 contains a hardcoded path specific to a developer's local environment (/home/mark/dev/betaflight/...). This should be replaced with a generic placeholder or removed entirely.
Apply this diff:
```bash
-cd /home/mark/dev/betaflight/betaflight-configurator/capacitor-plugin-serial/betaflight-configurator
+# Navigate to your configurator directory
+cd betaflight-configurator
# Install the custom plugin (local package)
yarn install🤖 Prompt for AI Agents
In QUICK_START.md around lines 7 to 11, remove the hardcoded user-specific path
and replace it with a generic instruction: change the explicit "/home/mark/..."
line to a neutral navigational note such as "Navigate to your configurator
directory" followed by the command "cd betaflight-configurator" (or simply
provide the single "cd betaflight-configurator" command), leaving the following
"yarn install" step unchanged so the guide works for all users.



Summary by CodeRabbit
New Features
Documentation
Chores