Skip to content

Conversation

tushar11kh
Copy link
Contributor

@tushar11kh tushar11kh commented Sep 15, 2025

Fixes #986

Overview

This PR adds USB transfer capability to the Badge Magic app, allowing users to send animations and text to FOSSASIA badges via USB connection alongside the existing Bluetooth functionality.

What's Implemented

  1. USB CDC/ACM Communication

    • Implemented USB serial communication following the badgemagic-firmware reference.
    • Uses the same data protocol as BLE but with 64-byte chunks instead of 16-byte.
    • Supports both:
      • Normal mode: VID=0x10FC, PID=0x55E0
      • Bootloader mode: VID=0x0416, PID=0x5020 (taken from badgemagic-rs)
  2. User Interface

    • Added transfer method selection tray (Bluetooth ↔ USB).
    • Maintains existing Bluetooth functionality unchanged.
  3. Android Integration

    • Added USB permissions and device filters in AndroidManifest.xml.
    • Created device_filter.xml for FOSSASIA device detection.
    • Proper error handling and user feedback.

Package Dependency Update

  • Forked and updated usb_serial package to support modern Android SDK requirements:
  • Updates include:
    • Dependencies and SDK targets

Technical Details

  • Data Format: Same as BLE, just different packet sizing (64-byte chunks).
  • Device Support: All RISC-V WCH CH582 based FOSSASIA badges.
  • Modes Supported: Normal operation + bootloader mode detection.
  • Architecture: Mirrors existing BLE state pattern with UsbScanState and UsbWriteState.

Testing Needed

Since I don’t have access to physical hardware, I need help with testing:

  • Does the app recognize when a FOSSASIA badge is connected via USB?
  • Does USB transfer actually work with real hardware?
  • Are error messages helpful when things go wrong?

How to Test

  1. Connect a FOSSASIA BadgeMagic via USB.
  2. Create a message or animation in the app.
  3. Click "Transfer" and select the USB option.
  4. Verify if transfer succeeds and the badge displays the content.

Screenshots / Recordings

WhatsApp.Video.2025-09-15.at.04.08.05.mp4

Note

This implements only data transfer, not firmware flashing.

Checklist:

  • No hard coding: I have used resources from constants.dart without hard coding any value.
  • No end of file edits: No modifications done at end of resource files.
  • Code reformatting: I have reformatted code and fixed indentation in every file included in this pull request.
  • Code analyzation: My code passes analyzations run in flutter analyze and tests run in flutter test.

Summary by Sourcery

Add USB transfer capability to the Badge Magic app by implementing USB CDC/ACM serial communication, a transfer method selection UI, and corresponding Android configuration and dependency updates

New Features:

  • Add USB CDC/ACM communication for BadgeMagic devices via usb_serial with 64-byte data chunks and support for normal and bootloader modes
  • Add a bottom-sheet transfer method tray and TransferProvider to enable users to choose between Bluetooth and USB and route transfers accordingly
  • Introduce USB module components (PayloadBuilder, UsbCdc, UsbScanState, UsbWriteState) to handle scanning, payload chunking, and chunked USB writes with retry logic
  • Update Android configuration with USB host feature, permissions, device_filter.xml for FOSSASIA badges, bump minSdkVersion to 24, and switch to an updated usb_serial SDK-34+ fork

Summary by Sourcery

Add USB transfer support on Android by implementing USB CDC/ACM communication, integrating it into the animation transfer flow, adding a transfer method UI and provider, and updating Android configurations and dependencies

New Features:

  • Implement USB CDC/ACM serial communication on Android for BadgeMagic devices with 64-byte data chunks and support for normal and bootloader modes
  • Extend animation transfer logic to support USB alongside existing Bluetooth path using PayloadBuilder, UsbScanState, and UsbWriteState
  • Add a transfer method selection UI (TransferMethodTray) and TransferProvider to allow users to choose between Bluetooth and USB
  • Configure AndroidManifest and device_filter.xml to declare USB host feature, intent filters, and permissions for FOSSASIA badges

Build:

  • Update usb_serial dependency to a forked GitHub repo compatible with Android SDK 34+

Copy link
Contributor

sourcery-ai bot commented Sep 15, 2025

Reviewer's Guide

This PR integrates Android-only USB CDC/ACM serial communication alongside existing BLE transfers by reusing the BadgeMagic data protocol (but with 64-byte chunks), adds a transfer method UI with a provider, updates Android manifest and resources for USB host support, and switches to a modern usb_serial SDK-34+ fork.

Sequence diagram for USB transfer process

sequenceDiagram
    actor User
    participant App
    participant TransferProvider
    participant UsbCdc
    User->>App: Tap "Transfer" button
    App->>TransferProvider: openTray()
    User->>App: Select "USB" in tray
    App->>TransferProvider: selectMethod(ConnectionType.usb)
    App->>UsbCdc: openDevice()
    UsbCdc-->>App: Device opened
    App->>UsbCdc: write(data in 64-byte chunks)
    UsbCdc-->>App: Write success/failure
    App->>User: Show transfer result
Loading

Class diagram for new and updated USB transfer classes

classDiagram
    class TransferProvider {
      - ConnectionType? _selectedMethod
      - bool _showTray
      + openTray()
      + closeTray()
      + selectMethod(ConnectionType)
      + reset()
      + selectedMethod
      + showTray
    }
    class PayloadBuilder {
      - DataTransferManager manager
      + buildPayloads()
    }
    class UsbCdc {
      - UsbPort? _port
      + openDevice()
      + write(List<int>)
      + close()
      + listDevices()
      + isFossasiaDeviceConnected()
    }
    class UsbWriteState {
      - PayloadBuilder builder
      + processState()
    }
    class UsbScanState {
      - PayloadBuilder builder
      + processState()
    }
    TransferProvider <|.. AnimationBadgeProvider
    PayloadBuilder --> DataTransferManager
    UsbWriteState --> PayloadBuilder
    UsbScanState --> PayloadBuilder
    UsbWriteState --> UsbCdc
    UsbScanState --> UsbCdc
Loading

File-Level Changes

Change Details Files
Implemented USB CDC/ACM communication for FOSSASIA badges
  • Created UsbCdc wrapper to list/open/write/close USB devices
  • Added PayloadBuilder to split raw data into 64-byte USB chunks
  • Developed UsbScanState and UsbWriteState mirroring BLE states with retry logic and error toasts
lib/bademagic_module/usb/usb_cdc.dart
lib/bademagic_module/usb/payload_builder.dart
lib/bademagic_module/usb/usb_scan_state.dart
lib/bademagic_module/usb/usb_write_state.dart
Added transfer method selection UI and provider
  • Introduced TransferProvider to manage selected method and tray visibility
  • Created TransferMethodTray widget for Bluetooth/USB options
  • Modified HomeScreen to open tray, handle selection, and call unified _handleTransfer
lib/view/homescreen.dart
lib/providers/transfer_provider.dart
lib/view/widgets/transfer_method_tray.dart
lib/constants.dart
Updated Android configuration for USB host support
  • Added usb.host feature and USB_DEVICE_ATTACHED intent in AndroidManifest
  • Included device_filter.xml to detect FOSSASIA badge IDs
  • Set minSdkVersion bump and proper permissions
android/app/src/main/AndroidManifest.xml
android/app/src/main/res/xml/device_filter.xml
Switched to updated usb_serial SDK-34+ fork
  • Replaced pubspec dependency with Git fork of usb_serial updated for modern Android SDK
  • Removed outdated version reference
  • Ran flutter analyze and tests to ensure compatibility
pubspec.yaml

Assessment against linked issues

Issue Objective Addressed Explanation
#986 Implement USB transfer support for FOSSASIA BadgeMagic devices in the app, allowing users to send animations and text via USB connection.
#986 Update the user interface to allow users to select between Bluetooth and USB transfer methods.
#986 Integrate necessary Android platform changes (permissions, device filters, dependencies) to enable USB communication with supported badges.

Possibly linked issues

  • Add support for USB transfer  #986: The PR adds USB transfer support to the Badge Magic app, directly addressing the issue's request for USB transfer capability for badges.
  • Add support for USB transfer  #986: The PR adds USB transfer support, providing an alternative method to transfer data to the badge, which addresses the issue of 'transfer not working' when Bluetooth fails.

Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

Copy link
Contributor

@sourcery-ai sourcery-ai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey there - I've reviewed your changes - here's some feedback:

  • The HomeScreen widget is becoming very large and complex with USB transfer logic and UI baked in—extract the transfer tray and handleTransfer flow into smaller widgets or a dedicated controller/service to simplify maintenance.
  • The handleAnimationTransfer method in AnimationBadgeProvider now has a big BLE vs. USB conditional; refactor by pulling out the USB-specific and BLE-specific flows into separate helper methods to clean up the logic.
  • TransferMethodTray references colorPrimaryDark which isn’t defined in constants.dart—either define this color or switch to an existing constant like colorPrimary to fix the undefined reference.
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- The HomeScreen widget is becoming very large and complex with USB transfer logic and UI baked in—extract the transfer tray and handleTransfer flow into smaller widgets or a dedicated controller/service to simplify maintenance.
- The handleAnimationTransfer method in AnimationBadgeProvider now has a big BLE vs. USB conditional; refactor by pulling out the USB-specific and BLE-specific flows into separate helper methods to clean up the logic.
- TransferMethodTray references colorPrimaryDark which isn’t defined in constants.dart—either define this color or switch to an existing constant like colorPrimary to fix the undefined reference.

## Individual Comments

### Comment 1
<location> `lib/view/homescreen.dart:622` </location>
<code_context>
+                    ),
+                  ),
+                  // Transfer Method Tray Overlay
+                  Consumer<TransferProvider>(
+                    builder: (context, transferProvider, _) {
+                      if (transferProvider.showTray) {
</code_context>

<issue_to_address>
**issue (bug_risk):** The overlay for the transfer tray may block all interactions, including the tray itself.

The overlay's full-screen coverage may block tray interactions. Adjust the Stack order or hitTestBehavior to keep the tray accessible.
</issue_to_address>

### Comment 2
<location> `lib/providers/animation_badge_provider.dart:285-294` </location>
<code_context>
+    if (connectionType == ConnectionType.bluetooth) {
</code_context>

<issue_to_address>
**suggestion (bug_risk):** The USB transfer path does not reset animation state for special animations.

The BLE path resets animation mode and index for certain animations, but the USB path does not. This may cause inconsistent UI states. Please align the USB logic with BLE for animation state resets.
</issue_to_address>

### Comment 3
<location> `lib/providers/animation_badge_provider.dart:335-344` </location>
<code_context>
+  Future<void> write(List<int> data) async {
+    if (_port == null) throw Exception("USB port not open");
+
+    try {
+      await _port!.write(Uint8List.fromList(data));
+      logger.d("USB chunk written: ${data.length} bytes");
+    } catch (e) {
+      logger.e("Failed to write USB chunk: $e");
+      rethrow;
</code_context>

<issue_to_address>
**suggestion:** Error handling for USB transfer is limited to logging.

Please add user-facing error notifications for USB transfer failures to match the BLE error handling approach.
</issue_to_address>

### Comment 4
<location> `lib/bademagic_module/usb/usb_cdc.dart:35-37` </location>
<code_context>
+    logger.d("Found FOSSASIA device: ${device.vid}:${device.pid}");
+    
+      // ✅ ADD THIS BOOTLOADER DETECTION
+  if (device.vid == bootloaderVendorId && device.pid == bootloaderProductId) {
+    logger.e("Device is in bootloader mode - cannot transfer data");
+    throw Exception("Device is in bootloader mode. Please disconnect, then connect without holding any buttons.");
+  }
+
</code_context>

<issue_to_address>
**suggestion:** Throwing an exception for bootloader mode may not be user-friendly.

Display a user-facing message explaining the bootloader mode before throwing the exception to clarify why the transfer is blocked.

Suggested implementation:

```
  if (device.vid == bootloaderVendorId && device.pid == bootloaderProductId) {
    logger.e("Device is in bootloader mode - cannot transfer data");
    // Show user-facing message before throwing exception
    // NOTE: Replace `context` with your actual BuildContext variable if needed
    ScaffoldMessenger.of(context).showSnackBar(
      SnackBar(
        content: Text(
          "Device is in bootloader mode. Please disconnect, then connect without holding any buttons. Data transfer is blocked in bootloader mode.",
        ),
        backgroundColor: Colors.red,
        duration: Duration(seconds: 5),
      ),
    );
    throw Exception("Device is in bootloader mode. Please disconnect, then connect without holding any buttons.");
  }

```

- You must ensure that a valid `BuildContext` named `context` is available at this point in your code. If this is not inside a widget or you do not have access to `context`, you will need to refactor the code to pass it in, or use another user-facing notification mechanism appropriate for your app.
- Import `package:flutter/material.dart` if not already imported.
- If you use another UI framework, replace the `ScaffoldMessenger`/`SnackBar` code with the appropriate user notification method.
</issue_to_address>

### Comment 5
<location> `lib/view/widgets/transfer_method_tray.dart:17` </location>
<code_context>
+  @override
+  Widget build(BuildContext context) {
+    return Container(
+      height: MediaQuery.of(context).size.height / 3.8,
+      decoration: BoxDecoration(
+        color: Theme.of(context).dialogBackgroundColor,
</code_context>

<issue_to_address>
**suggestion:** Tray height is hardcoded as a fraction of screen height.

This approach may cause layout issues on devices with atypical screen sizes. Consider setting min/max height constraints or making the tray height responsive to its content.

Suggested implementation:

```
    return Container(
      constraints: BoxConstraints(
        minHeight: 200,
        maxHeight: MediaQuery.of(context).size.height * 0.6,
      ),
      decoration: BoxDecoration(
        color: Theme.of(context).dialogBackgroundColor,
        borderRadius: const BorderRadius.only(
          topLeft: Radius.circular(16),
          topRight: Radius.circular(16),
        ),
        boxShadow: [
          BoxShadow(
            color: Colors.black.withOpacity(0.2),
            blurRadius: 8,
            offset: const Offset(0, -2),
          ),

```

If the tray's child widgets are not already responsive to their content, consider wrapping the main content inside the tray with an `IntrinsicHeight` or using a `Column` with `MainAxisSize.min` to ensure the tray adapts to its content. Adjust the minHeight and maxHeight values as appropriate for your design.
</issue_to_address>

### Comment 6
<location> `lib/providers/transfer_provider.dart:22-14` </location>
<code_context>
+    notifyListeners();
+  }
+
+  void selectMethod(ConnectionType method) {
+    _selectedMethod = method;
+    _showTray = false;
+    notifyListeners();
+  }
+
</code_context>

<issue_to_address>
**suggestion:** Selecting a method closes the tray immediately.

If transfer initiation is delayed, users may not receive immediate feedback. Consider keeping the tray open until the transfer begins or add a loading indicator.

Suggested implementation:

```
  ConnectionType? _selectedMethod;
  bool _showTray = false;
  bool _isLoading = false;

  ConnectionType? get selectedMethod => _selectedMethod;
  bool get showTray => _showTray;
  bool get isLoading => _isLoading;

  void openTray() {
    _showTray = true;
    notifyListeners();

```

```
  void selectMethod(ConnectionType method) {
    _selectedMethod = method;
    notifyListeners();
  }

  void startTransfer() {
    _isLoading = true;
    _showTray = false;
    notifyListeners();
  }

```

You will need to update the UI code to:
- Call `startTransfer()` when the transfer is actually initiated.
- Show a loading indicator when `isLoading` is true.
- Ensure the tray remains open after method selection, and only closes when transfer starts.
</issue_to_address>

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

@tushar11kh
Copy link
Contributor Author

for all other platforms, app will work, but when you press transfer via usb, it will say Platform unsupported.

@samruddhi-Rahegaonkar
Copy link
Member

@tushar11kh Please address all the suggestions given by the sourcey AI, and implement wherever necessary.

@mariobehling mariobehling changed the title feat: added usb transfer support(Android only) feat: added usb transfer support (Android only) Sep 16, 2025
@tushar11kh
Copy link
Contributor Author

I believe I implemented all suggestions given by the sourcey AI.

@tushar11kh
Copy link
Contributor Author

@mariobehling @samruddhi-Rahegaonkar
shall I update this to the current branch? If you gonna review it?

Copy link
Member

@mariobehling mariobehling left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@tushar11kh Thanks! Please resolve conflicts.

Copy link
Member

@samruddhi-Rahegaonkar samruddhi-Rahegaonkar left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

USB transfer is Android-only, but the UI tray shows USB as an option on all platforms.
changes : Hide or disable the USB option for non-Android platforms (iOS, Web, macOS) to avoid user confusion.

Copy link
Member

@samruddhi-Rahegaonkar samruddhi-Rahegaonkar left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Consider internationalisation for new error strings.

@nope3472
Copy link
Contributor

nope3472 commented Oct 1, 2025

Hey @tushar11kh, please remove the unrelated file changes like Podfile.lock from this PR. Only include changes relevant to the issue. Thanks!

@tushar11kh tushar11kh force-pushed the android_usb_transfer branch from 26d2b83 to 5ed638a Compare October 2, 2025 12:05
@tushar11kh tushar11kh force-pushed the android_usb_transfer branch from 8e296d2 to 092ddb4 Compare October 2, 2025 12:25
Copy link
Contributor

github-actions bot commented Oct 2, 2025

Build Status

Build successful. APKs to test: https://github.com/fossasia/badgemagic-app/actions/runs/18233285928/artifacts/4179072041.

Screenshots

Android Screenshots
iPhone Screenshots
iPad Screenshots

@tushar11kh
Copy link
Contributor Author

@mariobehling @samruddhi-Rahegaonkar @nope3472

  1. Resolved conflicts
  2. Transfer tray with bluetooth and usb options is only visible on android devices.
  3. few strings are duplicated , and few are not added to localization, you can open separate issue for it.
  4. unrelated file changes like Podfile.lock are not pushed.

@tushar11kh tushar11kh force-pushed the android_usb_transfer branch from 1f6a915 to 9e3f079 Compare October 3, 2025 19:56
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.

Add support for USB transfer

4 participants