Handle USB TX writes that die mid-message (device off the bus)#407
Merged
Conversation
Field report (POTA activation 2026-07-03): 5 of 265 transmissions logged "libusb native write FAILED (rc=5 UNKNOWN) ... TX DROPPED" - the rig keyed and transmitted dead air. rc=5 is LIBUSB_TRANSFER_NO_DEVICE: onOutputComplete stores the failing transfer's (positive) libusb_transfer_status as nativeWrite's return value, but describeLibusbWriteError only mapped negative libusb_error codes, so the actual failure mode (device falling off the bus mid-TX, typically RF into the USB link at power) was logged as UNKNOWN. Two of the five drops were load-bearing: a first RR73 and a first R-report went out silent, stretching both QSOs. Changes: - describeLibusbWriteError now names the positive transfer statuses (TRANSFER_NO_DEVICE etc.) alongside the negative error codes. - The UsbRequest fallback is gated by shouldFallbackToUsbRequest(): it restarts the message from byte 0, which is only sane for failures before streaming began (its original purpose - kernels where libusb can't run). Mid-stream deaths (>1s in), device-gone and user-cancelled failures now drop the cycle cleanly instead of ever keying an off-grid, overlapping restart. - The operator gets an on-screen warning when a TX write drops (rig keys but sends silence - previously invisible in the field); the user's own STOP press does not warn. String added to all 16 locales. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## dev #407 +/- ##
=========================================
Coverage 20.80% 20.80%
Complexity 133 133
=========================================
Files 148 148
Lines 19297 19297
Branches 2876 2876
=========================================
Hits 4015 4015
Misses 15115 15115
Partials 167 167
Flags with carried forward coverage won't be shown. Click here to find out more. 🚀 New features to boost your workflow:
|
There was a problem hiding this comment.
Pull request overview
Improves robustness and operator visibility when USB TX audio writes fail mid-message (e.g., USB audio device dropping off the bus), by distinguishing libusb setup errors from mid-stream transfer-status failures and preventing unsafe “restart-from-zero” fallbacks.
Changes:
- Extend
describeLibusbWriteError()to name positivelibusb_transfer_statusvalues (e.g.,rc=5 TRANSFER_NO_DEVICE). - Add
shouldFallbackToUsbRequest(rc, elapsedMs)gating to avoid UsbRequest restart after mid-stream/native failures or device-gone/cancelled scenarios. - Add an operator-facing warning toast for dropped TX audio, plus unit tests covering the new decision matrices.
Reviewed changes
Copilot reviewed 20 out of 20 changed files in this pull request and generated 2 comments.
Show a summary per file
| File | Description |
|---|---|
| ft8af/app/src/main/java/com/k1af/ft8af/wave/UsbAudioDevice.java | Adds native-write elapsed-time gating and maps positive transfer-status codes for clearer diagnostics. |
| ft8af/app/src/main/java/com/k1af/ft8af/ft8transmit/FT8TransmitSignal.java | Shows on-screen warning when TX audio is dropped (except user-cancel). |
| ft8af/app/src/test/java/com/k1af/ft8af/wave/UsbAudioWriteErrorTest.java | Adds tests for transfer-status naming and fallback gating matrix. |
| ft8af/app/src/test/java/com/k1af/ft8af/ft8transmit/FT8TransmitSignalTest.java | Adds tests for the “warn on dropped TX audio” decision matrix. |
| ft8af/app/src/main/res/values/strings_compose.xml | Adds base tx_audio_dropped string. |
| ft8af/app/src/main/res/values-ar/strings_compose.xml | Adds Arabic translation for tx_audio_dropped. |
| ft8af/app/src/main/res/values-cs/strings_compose.xml | Adds Czech translation for tx_audio_dropped. |
| ft8af/app/src/main/res/values-es/strings_compose.xml | Adds Spanish translation for tx_audio_dropped. |
| ft8af/app/src/main/res/values-fr/strings_compose.xml | Adds French translation for tx_audio_dropped. |
| ft8af/app/src/main/res/values-in/strings_compose.xml | Adds Indonesian translation for tx_audio_dropped. |
| ft8af/app/src/main/res/values-it/strings_compose.xml | Adds Italian translation for tx_audio_dropped. |
| ft8af/app/src/main/res/values-ja/strings_compose.xml | Adds Japanese translation for tx_audio_dropped. |
| ft8af/app/src/main/res/values-ko/strings_compose.xml | Adds Korean translation for tx_audio_dropped. |
| ft8af/app/src/main/res/values-nl/strings_compose.xml | Adds Dutch translation for tx_audio_dropped. |
| ft8af/app/src/main/res/values-pl/strings_compose.xml | Adds Polish translation for tx_audio_dropped. |
| ft8af/app/src/main/res/values-ru/strings_compose.xml | Adds Russian translation for tx_audio_dropped. |
| ft8af/app/src/main/res/values-tr/strings_compose.xml | Adds Turkish translation for tx_audio_dropped. |
| ft8af/app/src/main/res/values-uk/strings_compose.xml | Adds Ukrainian translation for tx_audio_dropped. |
| ft8af/app/src/main/res/values-zh-rCN/strings_compose.xml | Adds Simplified Chinese translation for tx_audio_dropped. |
| ft8af/app/src/main/res/values-zh-rTW/strings_compose.xml | Adds Traditional Chinese translation for tx_audio_dropped. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Copilot review findings, both valid: - writeElapsedMs was measured with System.currentTimeMillis(); a wall clock step (NTP set, user change) could fake a fast failure and wrongly allow the restart-from-zero UsbRequest fallback after audio already went to air. Use SystemClock.elapsedRealtime(). - The tx_audio_dropped toast hard-coded "USB audio device dropped" as the cause, but writeAudio can fail for other reasons (libusb setup errors, UsbRequest path failures). Genericized to "USB audio error" in all 16 locales; the precise cause stays in debug.log via describeLibusbWriteError. Also move the shouldWarnTxDropped tests into their own TxDropWarningTest file so this PR no longer appends to the same FT8TransmitSignalTest region as PR #406 (eliminates the guaranteed merge conflict between the two open PRs). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
From the 2026-07-03 POTA activation log: 5 of 265 transmissions silently died —
libusb native write FAILED (rc=5 UNKNOWN) ... TX DROPPED, no audio sent this cycle (rig keyed but silent). Two were load-bearing (the first RR73 to N2JFD at 15:26:29 and the first R-13 to W5TRL at 15:33:29 went out as dead air), stretching those QSOs by several cycles. Companion fix to #406.rc=5was mislabelled:onOutputComplete(inusb_audio_capture.cpp) stores the failing transfer's positivelibusb_transfer_statusasnativeWrite's return value, butdescribeLibusbWriteErroronly mapped the negativeLIBUSB_ERROR_*codes. 5 =LIBUSB_TRANSFER_NO_DEVICE: the USB sound card fell off the bus mid-transmission — classic RF-into-the-USB-cable at TX power in a portable setup (a re-enumeration follows the 15:09:07 drop in the log). Field mitigation: ferrite chokes on the USB leg.Changes
describeLibusbWriteErrornow names the positive transfer statuses (rc=5 TRANSFER_NO_DEVICEinstead ofUNKNOWN).UsbRequestfallback rewrites the whole message from byte 0. That's only sane when the native attempt failed before streaming began (its original purpose: kernels where libusb can't run). NewshouldFallbackToUsbRequest(rc, elapsedMs)gate: mid-stream deaths (>1 s in), device-gone (NO_DEVICE/TRANSFER_NO_DEVICE), and user-cancelled writes drop the cycle cleanly. Previously this only "worked" becauserequest.initialize()happens to fail fast when the device is gone.shouldWarnTxDropped). String added to all 16 locales.No attempt to salvage the in-flight message: a mid-message gap breaks the FT8 symbol grid, and the protocol-level repeat already handles the retry.
Tests
UsbAudioWriteErrorTest: transfer-status names (including the previously-asserted-wrongrc=5 UNKNOWNcase, nowTRANSFER_NO_DEVICE), and the fullshouldFallbackToUsbRequestmatrix (setup failure allowed, mid-stream/boundary/device-gone/cancelled/success denied).FT8TransmitSignalTest:shouldWarnTxDroppedmatrix.testDebugUnitTestsuite green.🤖 Generated with Claude Code