Skip to content

Fix FT-2000 CAT: SWR meter dead during TX (only first of two coalesced replies parsed)#667

Merged
patrickrb merged 1 commit into
devfrom
optio/task-b9890dbe-4748-4158-ad84-c82a8da43945
Jul 23, 2026
Merged

Fix FT-2000 CAT: SWR meter dead during TX (only first of two coalesced replies parsed)#667
patrickrb merged 1 commit into
devfrom
optio/task-b9890dbe-4748-4158-ad84-c82a8da43945

Conversation

@patrickrb

Copy link
Copy Markdown
Owner

Summary

Yaesu38Rig ("YAESU FT-2000 series") loses its SWR meter reading during TX. On every meter poll the rig is asked for two meters back-to-back — RM4; (ALC) then RM6; (SWR) — and the transport routinely delivers both replies coalesced in a single onReceiveData chunk ("RM4nnn;RM6nnn;"). The old parser handled only the first ;-terminated command and re-buffered the remainder with its terminator; the next poll's clearBufferData() then wiped that carry-over, so the second (SWR) reply was permanently dropped.

Net effect for FT-2000 users: the SWR bar never moves during transmit, and the high-SWR safety alert never fires.

This is the same root cause already fixed for the FT-891 in #665, on a distinct, still-affected rig.

Root cause

onReceiveData was a one-command-per-read parser:

Yaesu3Command yaesu3Command = Yaesu3Command.getCommand(buffer.toString());
clearBufferData();
buffer.append(s.substring(s.indexOf(";") + 1)); // rest kept WITH terminator, parsed never

Any second complete command in the same chunk was left in the buffer with its trailing ; and then destroyed by the next readMeters()/readFreqFromRig() clearBufferData(). Because the SWR read is always the second of the two, it is the one consistently lost.

Fix

onReceiveData now drains every complete ;-terminated command from the accumulated buffer and carries only the unterminated tail into the next read. The framing decision is extracted into a pure, package-private static helper so it can be unit-tested without the rig's Timer-scheduling constructor:

  • splitCommands(String) -> Frames — pure String → {List<String> commands, String remainder}.
  • processCommand(String) — the existing per-command FA/FB/RM handling, unchanged in behavior.
  • Collapsed a latent double Yaesu3Command.getFrequency() call.

The > 1000-char no-terminator overflow guard is preserved.

Kept self-contained (no new shared class) deliberately: #665 introduces a shared CatLineSplitter but is still open, so adding my own copy would collide. Once #665 merges, the remaining identical Yaesu gen-3 siblings (FTDX-450 / DX10 / Wolf-SDR) can adopt the shared helper as follow-ups.

Testing

  • New Yaesu38RigTest — 8 pure-JVM cases (no Robolectric; the helper is Android-free): single command, two coalesced meter replies both drained (the regression), complete + partial tail, no terminator, empty input, leading terminator, split-across-reads reassembly via remainder carry, three-commands-plus-tail.
  • ./gradlew :app:testDebugUnitTest — full suite green; Yaesu38RigTest 8/8.

Risk

Low and localized to one rig's read path. No protocol/DSP change; frame semantics are unchanged for the common single-command case, and the fix only adds recovery of previously-dropped coalesced commands. No hardware was available, so validation is via the unit tests and the identical-to-#665 root cause.

Related

🤖 Generated with Claude Code

…d replies parsed)

Yaesu38Rig ("YAESU FT-2000 series") polls meters by sending two reads
back-to-back — RM4; (ALC) then RM6; (SWR). The transport routinely
delivers both replies coalesced in a single onReceiveData chunk
("RM4nnn;RM6nnn;"). The old parser consumed only the first
';'-terminated command (ALC) and re-buffered the rest *with* its
terminator; the next meter poll's clearBufferData() then wiped that
carry-over, so the SWR (second) reply was permanently dropped. Result:
the SWR meter never moved during TX and the high-SWR safety alert never
fired.

Fix: onReceiveData now drains EVERY complete ';'-terminated command from
the accumulated buffer and carries only the unterminated tail into the
next read. The framing is extracted to a pure, package-private static
helper (splitCommands + Frames) so it is unit-testable without the rig's
Timer-scheduling constructor; per-command handling moves to
processCommand. Also collapses a latent double getFrequency() call.

This is the same root cause fixed for the FT-891 in #665, on a distinct
still-affected rig. Kept self-contained (no shared splitter class) to
avoid colliding with #665's in-flight CatLineSplitter; the identical
Yaesu gen-3 siblings (FTDX-450/DX10/Wolf-SDR) remain follow-ups.

Tests: Yaesu38RigTest — 8 pure-JVM cases (coalesced pair, split-across-
reads carry, partial tail, leading terminator, empty). Full
:app:testDebugUnitTest green.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@codecov

codecov Bot commented Jul 23, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 36.79%. Comparing base (ac169cc) to head (e8ba664).
⚠️ Report is 4 commits behind head on dev.

Additional details and impacted files

Impacted file tree graph

@@             Coverage Diff              @@
##                dev     #667      +/-   ##
============================================
+ Coverage     36.73%   36.79%   +0.06%     
- Complexity      197      207      +10     
============================================
  Files           216      218       +2     
  Lines         26888    26994     +106     
  Branches       3287     3311      +24     
============================================
+ Hits           9877     9933      +56     
- Misses        16785    16829      +44     
- Partials        226      232       +6     
Flag Coverage Δ
android 15.29% <ø> (+0.25%) ⬆️
native 9.93% <ø> (ø)

Flags with carried forward coverage won't be shown. Click here to find out more.
see 8 files with indirect coverage changes

🚀 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

Fixes FT-2000-series (Yaesu gen-3) CAT reply parsing so coalesced ;-terminated replies (notably ALC + SWR meter polls arriving as "RM4...;RM6...;" in a single read) are fully drained and processed, preventing the SWR meter from going stale during TX.

Changes:

  • Updated Yaesu38Rig.onReceiveData to split accumulated input into all complete ;-terminated commands plus an unterminated remainder, and to process each command in order.
  • Extracted per-command handling into processCommand(String) and framing logic into a pure helper splitCommands(String) with a small Frames carrier type.
  • Added a new pure-JVM unit test class Yaesu38RigTest covering coalesced replies, partial tails, leading terminators, and multi-command inputs.

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated no comments.

File Description
ft8af/app/src/main/java/com/k1af/ft8af/rigs/Yaesu38Rig.java Drains and processes all complete CAT commands per receive chunk, preserving only the unterminated tail to avoid dropping coalesced replies.
ft8af/app/src/test/java/com/k1af/ft8af/rigs/Yaesu38RigTest.java Adds unit tests that lock in correct framing behavior, including the ALC+SWR coalesced-reply regression.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

@patrickrb
patrickrb merged commit c740c8a into dev Jul 23, 2026
18 checks passed
@patrickrb
patrickrb deleted the optio/task-b9890dbe-4748-4158-ad84-c82a8da43945 branch July 23, 2026 21:19
patrickrb added a commit that referenced this pull request Jul 23, 2026
…ly dropped) (#669)

The FT-450, DX10-series and Wolf-SDR handlers share the same one-command-
per-read reassembly the FT-891 (#665) and FT-2000 (#667) fixes already
corrected: onReceiveData parsed only the FIRST ';'-terminated command in a
read and re-buffered the remainder WITH its retained terminator. The meter
poll sends the ALC and SWR reads back-to-back (setRead39Meters_ALC() then
_SWR()), so the transport routinely coalesces both replies ("RM4nnn;RM6nnn;")
into one read. The old parse consumed only the ALC frame; the next poll's
clearBufferData() then wiped the re-buffered SWR frame, so the SWR reading was
permanently lost and the high-SWR safety alert never fired during TX. The
retained ';' also poisoned the following read's parse.

Fix: drain every complete command per read via the shared CatLineSplitter
(introduced by #665, the text sibling of CivFrameSplitter), carrying only the
unterminated tail into the next call. Per-command handling is extracted
verbatim into processCommand so onReceiveData stays a thin drain loop; this
also consolidates three byte-identical copies onto the shared splitter and
collapses a latent double getFrequency() call. No protocol/behavior change
beyond no longer dropping coalesced replies.

Tests: added a CatLineSplitterTest case modelling the gen-3 meter poll
(coalesced ALC+partial-SWR read then the SWR tail) proving the SWR frame
survives. Full :app:testDebugUnitTest green.

Co-authored-by: Optio Agent <optio-agent@noreply.github.com>
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
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