Skip to content

Handle USB TX writes that die mid-message (device off the bus)#407

Merged
patrickrb merged 2 commits into
devfrom
fix/usb-tx-drop-handling
Jul 4, 2026
Merged

Handle USB TX writes that die mid-message (device off the bus)#407
patrickrb merged 2 commits into
devfrom
fix/usb-tx-drop-handling

Conversation

@patrickrb

Copy link
Copy Markdown
Owner

Problem

From the 2026-07-03 POTA activation log: 5 of 265 transmissions silently diedlibusb 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=5 was mislabelled: onOutputComplete (in usb_audio_capture.cpp) stores the failing transfer's positive libusb_transfer_status as nativeWrite's return value, but describeLibusbWriteError only mapped the negative LIBUSB_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

  1. Correct diagnosticsdescribeLibusbWriteError now names the positive transfer statuses (rc=5 TRANSFER_NO_DEVICE instead of UNKNOWN).
  2. No restart-from-zero on air — the UsbRequest fallback 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). New shouldFallbackToUsbRequest(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" because request.initialize() happens to fail fast when the device is gone.
  3. Operator visibility — a dropped TX write now shows an on-screen warning ("TX audio failed — no signal was sent this cycle"); at the rig everything looks normal, so the operator otherwise can't tell they transmitted silence. The user's own STOP press does not warn (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-wrong rc=5 UNKNOWN case, now TRANSFER_NO_DEVICE), and the full shouldFallbackToUsbRequest matrix (setup failure allowed, mid-stream/boundary/device-gone/cancelled/success denied).
  • FT8TransmitSignalTest: shouldWarnTxDropped matrix.
  • Full testDebugUnitTest suite green.

🤖 Generated with Claude Code

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

codecov Bot commented Jul 4, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 20.80%. Comparing base (68398ec) to head (02d821a).

Additional details and impacted files

Impacted file tree graph

@@            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           
Flag Coverage Δ
android 12.19% <ø> (ø)
native 9.93% <ø> (ø)

Flags with carried forward coverage won't be shown. Click here to find out more.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

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 positive libusb_transfer_status values (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.

Comment thread ft8af/app/src/main/java/com/k1af/ft8af/wave/UsbAudioDevice.java Outdated
Comment thread ft8af/app/src/main/res/values/strings_compose.xml Outdated
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>
@patrickrb patrickrb merged commit de72bd3 into dev Jul 4, 2026
17 checks passed
@patrickrb patrickrb deleted the fix/usb-tx-drop-handling branch July 4, 2026 12:00
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.

2 participants