From 9e0a30cc618bc83d1ae634e25a90335253664573 Mon Sep 17 00:00:00 2001 From: doesthings Date: Sun, 9 Aug 2026 22:38:12 +0300 Subject: [PATCH] fix: report FCC/CE apply success honestly + fix 4G allowlist contradiction MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit sendFrames() returned true if ANY of the 42 writes (21 frames x 2 rounds) succeeded, so the app reported "FCC enabled" even when the link dropped after the first write. It now requires every write to succeed and treats an empty frame list as a failure, matching sendFramesUnix() (the 4G path) in the same file. The aggregation rule is extracted to allFramesSucceeded() and unit tested. No DUML frames, ports, timing, or ACK handling changed — this only makes success reporting truthful. Also removes wa140 (Mini 4 Pro) from MODELS_WITH_4G: the Mini series has no cellular module, and the comment above the set already said so while the set itself included wa140. Resolves that self-contradiction (issue #27). The sendFrames fix re-applies the approach from PR #17 (ther3ptil31987-prog), cleanly on current main. --- .../java/com/freefcc/app/DumlTransport.kt | 24 +++++++--- .../main/java/com/freefcc/app/FccViewModel.kt | 10 ++-- .../app/DumlTransportSendFramesTest.kt | 46 +++++++++++++++++++ 3 files changed, 70 insertions(+), 10 deletions(-) create mode 100644 app/src/test/java/com/freefcc/app/DumlTransportSendFramesTest.kt diff --git a/app/src/main/java/com/freefcc/app/DumlTransport.kt b/app/src/main/java/com/freefcc/app/DumlTransport.kt index 338b0cb..8ec7eaa 100644 --- a/app/src/main/java/com/freefcc/app/DumlTransport.kt +++ b/app/src/main/java/com/freefcc/app/DumlTransport.kt @@ -242,7 +242,7 @@ class DumlTransport { * RC link, vs ~12s with the old generous timeouts). * * @param onProgress Called with a 0..1 float as frames are sent - * @return true if at least one frame was written successfully + * @return true only if every frame write succeeded (false for an empty list) */ fun sendFrames( frames: List, @@ -253,19 +253,22 @@ class DumlTransport { port: Int = PORT, onProgress: (Float) -> Unit = {} ): Boolean { + if (frames.isEmpty()) return false + // If port is the default (40009), scan for the actual working port. // If port is explicitly set (e.g. 40007 for LED), use it directly. val effectivePort = if (port == PORT) findWorkingPort() else port - var anySuccess = false + // Attempt every frame regardless of earlier failures, but only report + // success if every single write succeeded — a partial send is a failed + // apply, not a success. Matches sendFramesUnix() (4G) in this file. + val results = mutableListOf() val totalSends = frames.size * rounds var sent = 0 for (round in 0 until rounds) { for (frame in frames) { - if (sendOneFrame(frame, readWindowMs, effectivePort)) { - anySuccess = true - } + results.add(sendOneFrame(frame, readWindowMs, effectivePort)) sent++ onProgress(sent.toFloat() / totalSends) if (interFrameDelayMs > 0) Thread.sleep(interFrameDelayMs) @@ -274,7 +277,7 @@ class DumlTransport { Thread.sleep(interRoundDelayMs) } } - return anySuccess + return allFramesSucceeded(results) } /** @@ -560,6 +563,15 @@ class DumlTransport { /** TCP connect timeout for all socket opens to the DUML proxy. */ private const val CONNECT_TIMEOUT_MS = 2000 + + /** + * A frame series counts as sent only if it was non-empty and every + * single write succeeded. A partial send is a failed apply, not a + * success — this is what stops the app from reporting "FCC enabled" + * when the link dropped after the first of 42 writes. + */ + internal fun allFramesSucceeded(results: List): Boolean = + results.isNotEmpty() && results.all { it } } /** Reused read buffer for ACK reads — avoids a per-frame allocation. */ diff --git a/app/src/main/java/com/freefcc/app/FccViewModel.kt b/app/src/main/java/com/freefcc/app/FccViewModel.kt index af7511e..d05e85b 100644 --- a/app/src/main/java/com/freefcc/app/FccViewModel.kt +++ b/app/src/main/java/com/freefcc/app/FccViewModel.kt @@ -75,11 +75,13 @@ class FccViewModel(private val app: Application) : AndroidViewModel(app) { * "frames written but 4G didn't activate" message. * * Sources: DJI product list, captured profiles (only wa341 confirmed - * working on real hardware). wa233/wa234 = Matrice 300/350 series, - * wm630 = Inspire 3, wa341 = Mavic 4 Pro. All are DJI enterprise models - * that ship with or accept the Cellular Dongle 2. + * working on real hardware). wm630 = Inspire 3, wa341 = Mavic 4 Pro, and + * the wa233/wa234 Matrice-class codes accept the Cellular Dongle 2. + * wa140 (Mini 4 Pro) is intentionally NOT here — the Mini series has no + * cellular module, so it must not pass the 4G gate (previously it was + * both excluded in this comment and included in the set). */ - private val MODELS_WITH_4G = setOf("wa341", "wa233", "wa234", "wm630", "wa140") + private val MODELS_WITH_4G = setOf("wa341", "wa233", "wa234", "wm630") } private val _state = MutableStateFlow(AppState()) diff --git a/app/src/test/java/com/freefcc/app/DumlTransportSendFramesTest.kt b/app/src/test/java/com/freefcc/app/DumlTransportSendFramesTest.kt new file mode 100644 index 0000000..a261b29 --- /dev/null +++ b/app/src/test/java/com/freefcc/app/DumlTransportSendFramesTest.kt @@ -0,0 +1,46 @@ +package com.freefcc.app + +import org.junit.Assert.assertFalse +import org.junit.Assert.assertTrue +import org.junit.Test + +/** + * Covers DumlTransport.allFramesSucceeded() — the aggregation rule for + * sendFrames(): a non-empty send series reports success only if EVERY write + * succeeded, and an empty series is always a failure. This is what prevents + * the app from reporting "FCC enabled" when the link dropped partway through + * the 21-frame x 2-round apply (the old "any success = true" behaviour). + */ +class DumlTransportSendFramesTest { + + @Test + fun allWritesSucceed_reportsSuccess() { + assertTrue(DumlTransport.allFramesSucceeded(listOf(true, true, true))) + } + + @Test + fun singleWriteSucceeds_reportsSuccess() { + assertTrue(DumlTransport.allFramesSucceeded(listOf(true))) + } + + @Test + fun oneWriteFails_reportsFailure() { + // The exact scenario from the bug: link drops after the first write. + assertFalse(DumlTransport.allFramesSucceeded(listOf(true, false, true))) + } + + @Test + fun onlyFirstWriteSucceeds_reportsFailure() { + assertFalse(DumlTransport.allFramesSucceeded(listOf(true, false, false))) + } + + @Test + fun allWritesFail_reportsFailure() { + assertFalse(DumlTransport.allFramesSucceeded(listOf(false, false))) + } + + @Test + fun emptySeries_reportsFailure() { + assertFalse(DumlTransport.allFramesSucceeded(emptyList())) + } +}