Skip to content

Releases: doesthings/FreeFCC

v1.5.3

Choose a tag to compare

@doesthings doesthings released this 17 Jul 23:43
  • Added detected DUML port to the Info page

v1.5.2 — Install permission check before download

Choose a tag to compare

@doesthings doesthings released this 17 Jul 12:23

v1.5.2 — Install permission check before download

When you tap Download in the Update section, the app now first checks if it has the "Install unknown apps" permission. If not, it opens the system Settings page so you can grant it before downloading the APK. Once granted, tap Download again and it proceeds normally.

This prevents the frustrating situation where the download completes successfully but the install silently fails because the permission was never granted.

Full changelog

v1.5.1...v1.5.2

v1.5.1 — 5x faster FCC apply (tested on real RC2)

Choose a tag to compare

@doesthings doesthings released this 17 Jul 12:16

v1.5.1 — 5x faster FCC apply (tested on real RC2)

FCC unlock now completes in ~1.4 seconds instead of ~6.7 seconds — a 5x speedup. Tested and confirmed working on real RC2 hardware.

What changed

  • Inter-frame delay: 150ms → 30ms
  • Inter-round delay: 400ms → 100ms
  • Read window: 80ms → 50ms
  • Keepalive inter-frame: 100ms → 30ms
  • Keepalive read window: 80ms → 50ms

What did NOT change

  • All 21 frames identical (same cmd sets, cmd IDs, destinations, payloads)
  • 2 rounds (same as before)
  • Byte order (same as v1.5.0 — the working layout)
  • CRC-8 / CRC-16 algorithms (unchanged)
  • Everything else from v1.5.0 (LED fix, 4G hardening, auto-update fix, update banner)

Full changelog

v1.5.0...v1.5.1

v1.5.0 — Critical fix: FCC unlock broken since v1.4.8 (byte order revert)

Choose a tag to compare

@doesthings doesthings released this 17 Jul 11:59

v1.5.0 — Critical fix: FCC unlock broken since v1.4.8

The problem

v1.4.8 introduced a DUML frame byte order change that broke FCC unlock on real RC2 hardware. v1.4.7 worked perfectly.

Root cause

The DUML frame builder was modified based on an incorrect analysis of the wire format. The correct byte layout is defined by the public dji-firmware-tools project (comm_mkdupc.py, GPL-3.0), which is the authoritative reference for the DUML protocol:

[4] = sender [5] = receiver/dst [6-7] = sequence [8] = cmdType [9] = cmdSet [10] = cmdId

Verification

Confirmed against three independent sources:

  1. dji-firmware-tools (public GPL-3.0 reference) — the DJICmdV1Header struct defines [5]=receiver_info, [8]=cmd_type_data
  2. Real RC2 hardware — v1.4.7 layout works, v1.4.8+ layout breaks
  3. Empirical testing — the reverted layout restores FCC unlock

What was fixed

  • Reverted frame builder to the correct byte layout ([5]=dst, [8]=cmdType)
  • Reverted response validation to match
  • Reverted FCC profile timing to the known-working values (150ms inter-frame, 400ms inter-round, 80ms read window, 2 rounds)
  • Reverted keepalive timing (100ms inter-frame, 80ms read window)

What was NOT reverted (still improved from v1.4.7)

  • LED fix (no longer blocks FCC keepalive)
  • 4G hardening (model whitelist, dongle pre-check, serial caching, single-socket reuse)
  • Auto-update fix (rate-limit retry, install permission check, error surfacing, Check Again button)
  • Update-available banner on FCC page

Full changelog

v1.4.9...v1.5.0

v1.4.9 — Auto-update fixed: rate-limit retry, install permission, error surfacing

Choose a tag to compare

@doesthings doesthings released this 17 Jul 11:16

v1.4.9 — Auto-update fixed

The auto-update was completely broken. Here's what was wrong and how it's fixed:

Rate-limit retry bug (critical)
After a failed update check (no Wi-Fi, timeout, etc.), the Retry button was a silent no-op for 1 hour. The timestamp was saved before the fetch, so any failure consumed the rate-limit window. Now the timestamp is saved only on success — failed checks can be retried immediately. The Retry button also bypasses the rate-limit entirely.

Install permission (critical for RC2)
The RC2 controller blocks in-app APK installation unless "Install unknown apps" is granted per-app. FreeFCC was firing the installer intent without checking this permission, so the install silently failed. Now it:

  • Checks canRequestPackageInstalls() before installing
  • Opens the Settings page to grant the permission if missing
  • Falls back to telling the user to install via SD card + FileManager if Settings is unavailable (DJI hides it on some firmware)

Install reliability

  • Copies the APK to external storage before installing (the RC2's installer may not handle content:// URIs from app-private cache)
  • Checks that a package installer exists before launching (tells user to sideload �1_PackageInstaller if missing)
  • Grants URI permission to all resolved installer activities

Error surfacing
All errors were silently swallowed (catch (_: Exception) { null }). Now:

  • UpdateChecker logs the actual exception class + message
  • Install failures show on the Update page, not just in the log
  • Download failure message mentions Wi-Fi (the RC2 has no SIM)

Update-available banner
Added a banner on the main FCC page showing "Update available — vX.Y" so users don't have to manually check the Update tab.

Full changelog

v1.4.8...v1.4.9

v1.4.8 — Reliability fixes: LED no longer breaks FCC, 7x faster apply, 4G hardened

Choose a tag to compare

@doesthings doesthings released this 17 Jul 10:54

v1.4.8 — Reliability Fixes

LED toggle no longer breaks FCC mode (critical fix)

Toggling LEDs after activating FCC no longer causes FCC to stop working. The LED command was holding a hardware lock that blocked the FCC keepalive for ~1.5 seconds, creating a gap where DJI Fly could reset the radio to CE. Fixed by:

  • LED command no longer holds the hardware lock (uses port 40007, keepalive uses 40009 — different subsystems, no conflict)
  • LED now uses a separate transport instance
  • LED send pattern updated to 2 bursts × 5 writes × 100ms (10 total, matching the reference app)
  • Keepalive now retries with 200ms backoff on lock-failure instead of silently skipping ticks

7x faster FCC apply

FCC unlock now completes in ~880ms instead of ~6.7s:

  • Inter-frame delay: 150ms → 10ms
  • Inter-round delay: 400ms → 100ms
  • Read window: 80ms → 40ms

The 10ms inter-frame delay is proven safe — it's the same timing the 4G activation path has always used successfully.

4G activation hardened

  • Reuses one Unix socket for all 128 frames (was opening 128 separate sockets)
  • Added aircraft model whitelist — refuses 4G on Mini series (no cellular module)
  • Added fast 4G dongle pre-check before sending any frames
  • Serial number validated (≥6 chars) and cached across sessions

DUML frame builder corrected

  • Fixed byte order to match the DJI proxy spec: byte 5 = cmdType, byte 8 = dst
  • Thread-safe sequence counter with random initialization

Full changelog

v1.4.7...v1.4.8

v1.4.7

Choose a tag to compare

@doesthings doesthings released this 14 Jul 01:54

FreeFCC v1.4.7 — Critical Fixes + Crash Protection

Critical Fixes

  • Fixed update installation broken by scoped storage: the previous version removed WRITE_EXTERNAL_STORAGE but installUpdate still tried to copy the APK to Environment.getExternalStorageDirectory()/FreeFCC/ — which is unwritable on Android 10+ scoped storage. Now uses the cached APK directly via FileProvider (the file_paths.xml cache-path already covers cacheDir/updates/).
  • Fixed app crash on malformed profile: autoConnectAndApply and send4gActivationFrames had no catch block — a corrupt or missing profile asset would crash the app. Both now catch and report errors gracefully.
  • Fixed FCC being re-applied after CE restore: disableFcc now stops the keepalive service first. Previously, keepalive would re-apply FCC ~2 seconds after the user tapped "Stop FCC Mode", undoing the restore.
  • Fixed crash on Support page links: the Ko-fi and GitHub buttons called startActivity with an ACTION_VIEW intent, which throws ActivityNotFoundException on the RC2 (no browser installed). Now wrapped in try-catch.

Correctness & Safety

  • Keepalive stops if profile fails to load: if fcc_keepalive.json is missing or corrupt, the service now stopSelf() instead of becoming a silent foreground no-op with a notification but no frame sends.
  • Fixed keepalive interval drift: delay(INTERVAL_MS) moved to the start of the loop so the 2s interval is consistent regardless of send duration. Previously the actual interval was send_time + 2s.
  • Fixed serial probe binary corruption: listenForSerial now uses ISO-8859-1 instead of UTF-8 for decoding binary telemetry. UTF-8 multi-byte sequences could corrupt ASCII serial strings.
  • Rate-limited update checks: GitHub API is checked at most once per hour (unauthenticated limit is 60/hour). Prevents rate-limiting during development or repeated app restarts.
  • Keepalive notification now opens the app when tapped (added PendingIntent).
  • Guarded download against double-tap: downloadUpdate checks isDownloadingUpdate before launching.
  • Validated hex string parsing: hexToBytes now requires even-length input (throws instead of silently truncating).

About the FCC apply mechanism

A user raised concerns about the FCC apply mechanism being "brittle and unsafe" because it replays captured frames without verifying the ACK. This is a valid observation. However, there is no publicly documented DUML command to query the current radio region (FCC vs CE) — the dji-firmware-tools project confirms this. All known FCC unlock tools work the same way: write frames, discard the ACK, and report success based on TCP write completion. Verification is only possible by observing the DJI Fly transmission graph (the app already documents this in "How Do I Know If It Worked?").

The key improvements made in this version that address the safety concerns:

  • The profile is now the minimal 21-frame universal set (restored in v1.4.5), not the 31-frame set that included unrelated altitude/distance/NFZ writes
  • disableFcc now stops keepalive before restoring CE, preventing the "persistent contention" the user described
  • The keepalive service now respects the user's stop intent (persistent flag + sticky-restart guard)

Compatibility

DJI RC2, RC Pro 2, RC Plus (smart controllers with a screen). 21-frame universal FCC profile. No Google Play Services required.

v1.4.6

Choose a tag to compare

@doesthings doesthings released this 14 Jul 01:30

FreeFCC v1.4.6 — Hardening + Bug Fixes

New Features

  • BootReceiver: if auto-FCC is enabled, the keepalive service restarts automatically after a controller reboot — no need to reopen FreeFCC manually
  • Sticky restart respects user intent: if the system kills and restarts the keepalive service, it checks the persistent flag so it won't silently re-enable after you stopped it
  • UI keepalive toggle synced: the toggle reflects the actual service state after a process restart

Security

  • APK SHA-256 verification: downloaded updates are verified against the GitHub asset digest before offering install — a tampered or corrupted download is refused
  • FileProvider scope narrowed: the cache path is now restricted to updates/ instead of the entire cache directory
  • Removed deprecated storage permissions: READ_EXTERNAL_STORAGE/WRITE_EXTERNAL_STORAGE are no-ops on API 30+ and have been removed
  • allowBackup=false: the auto-FCC flag is device-local and no longer backed up / restored across devices
  • Android 14 foreground service fix: added CHANGE_WIFI_STATE so the connectedDevice foreground service type has the required underlying permission on API 34+

Bug Fixes

  • Keepalive no longer swallows CancellationException — coroutine cancellation now takes effect promptly instead of waiting for the next blocking call
  • installUpdate now runs on a background thread — the APK copy was blocking the UI thread (ANR risk)
  • FileOutputStream leak fixed in the update downloader (now uses .use {})
  • Crash protection: enableFcc, disableFcc, setLed, and queryDeviceInfo now catch parse/load errors instead of crashing the app on a malformed profile asset
  • queryDeviceInfo empty-frames guard: no more NoSuchElementException if device_info.json is empty
  • sendAndReceive short-read guard: a truncated response no longer throws ArrayIndexOutOfBoundsException
  • Keepalive interval drift fixed: the 2s interval now starts at the beginning of each loop, so the actual interval is consistent regardless of send duration
  • Keepalive state desync fixed: the persistent flag is checked on process restart so the UI toggle matches reality
  • Duplicate install intents removed: the file manager intent that competed with the system installer has been removed
  • HardwareLock.end() is now safe to call without ownership — no more IllegalMonitorStateException from a stray release

Performance

  • Keepalive profile cached at service creation instead of re-parsing JSON and rebuilding frames with CRC every 2 seconds
  • ACK read buffer reused instead of allocating a new 2KB buffer per frame
  • Named constants for connect timeout, settle delay, and alternate ports (8901–8904)

Code Quality

  • Hardcoded version strings in the UI now use APP_VERSION (no more stale "1.4.4" after a bump)
  • Build version bumped to 1.4.6 (versionCode 14)

Compatibility

Same as before: DJI RC2, RC Pro 2, RC Plus (smart controllers with a screen). 21-frame universal FCC profile, 2 rounds, 150ms between frames.

v1.4.5

Choose a tag to compare

@doesthings doesthings released this 14 Jul 01:05

FreeFCC v1.4.5

Changes

  • Restored 21-frame FCC profile — matches the verified universal frame set. Removed unverified altitude/distance/NFZ parameter writes that the DJI Fly app overrides at runtime anyway (C0 class flag). Only the FCC radio region switch is a verified effect.
  • Protocol rename: DUMPL → DUML — corrected to the official name (DJI Universal Markup Language) per the dji-firmware-tools wiki. All code, comments, docs, class names, and filenames updated. Zero dumpl mentions in source or APK.
  • README accuracy — dropped unverified altitude/distance/NFZ claims from the tagline and features. Clarified the app targets DJI smart controllers with a screen (RC2, RC Pro 2, RC Plus).

Merged PRs

  • #11 (ther3ptil31987-prog): Fix keepalive service bypassing the hardware write lock — extracted the lock into a process-wide HardwareLock singleton so keepalive frames can't overlap manual operations on the TCP port. Includes a concurrency test.
  • #12 (ther3ptil31987-prog): Fix signed-release CI job to check the keystore secret — the guard was missing SIGNING_KEYSTORE_B64, causing an opaque build failure when that one secret was absent. Applied directly (gh token lacks workflow scope).

FCC Mode

  • Switches radio from CE to FCC mode (higher power, more channels)
  • 21-frame profile, 2 rounds, 150ms between frames
  • Confirmed working on RC2 + RC Pro 2

Compatibility

  • DJI RC2: Mini 5 Pro, Mini 4 Pro, Air 3S, Neo 1, Neo 2, Avata 360
  • DJI RC Pro 2: Mavic 4 Pro (direct install, no launcher needed for FCC)
  • DJI RC Plus: Use freefcc-launcher for 4G

Keepalive + Auto-FCC + 4G + LED + Device Info

  • Foreground service re-applies FCC every 2s to prevent CE reset
  • Auto-FCC toggle: auto-connect, apply, start keepalive, launch DJI Fly
  • 4G activation frames (Unix domain socket, experimental)
  • LED control (port 40007)
  • Device info query

Note on Altitude

The 120m CE altitude limit is enforced by the DJI Fly app via a C0 class runtime flag. DUML parameter writes set the flight controller values, but the DJI Fly app overrides them. There is no known way to bypass this without modifying the DJI Fly app or flashing patched firmware.

v1.4.33 — Restored working FCC profile + install fix

Choose a tag to compare

@doesthings doesthings released this 13 Jul 21:45

Critical Fix

FCC Profile Restored

The trigger SET commands from the other tool only work over USB/RCLink direct transport — they get filtered by the RC2's DUMPL proxy at TCP 40009. Restored the original 21-frame profile confirmed working over TCP:

  • sender=130, cmd_type=32 (standard DUMPL proxy compatible)
  • No trigger SETs, no NLD keepalives
  • FCC radio + WIFI + OFDM + FC params — the exact frames that work on RC2

Keepalive

Simplified to re-apply FCC radio mode every 2s (service mode → set FCC → commit → exit)

Install Fix

Copies APK to SD card and opens file manager. Added storage permissions.

Note on 120m Altitude

The 120m CE altitude limit is enforced by the DJI Fly app, not the flight controller. The other tool bypasses it via USB/RCLink direct transport (which we can't use via TCP). Altitude unlock via DUMPL TCP is not possible without modifying the DJI Fly app.