Skip to content

[bug] Fix Flex TCXO frequency offset calibration#2119

Merged
ten9876 merged 1 commit intoten9876:mainfrom
jensenpat:aether/pll-calibration-command
Apr 28, 2026
Merged

[bug] Fix Flex TCXO frequency offset calibration#2119
ten9876 merged 1 commit intoten9876:mainfrom
jensenpat:aether/pll-calibration-command

Conversation

@jensenpat
Copy link
Copy Markdown
Collaborator

@jensenpat jensenpat commented Apr 28, 2026

Screenshot 2026-04-27 at 6 38 10 PM

Summary

This PR fixes the Radio Setup → RX → Frequency Offset calibration workflow for FlexRadio hardware and documents the protocol lesson so future follow-up agents do not rediscover the same command mismatch.

The existing UI exposed a Start button for manual frequency-offset calibration on radios without a GPSDO, but it sent radio calibrate. On the reporter's FLEX-6600 running firmware v4.1.5, that command returns 0x50000016 (unknown command), so the button could not actually start calibration.

Issue #2095 supplied a SmartSDR TCP command capture showing the working sequence:

  1. radio set freq_error_ppb=0
  2. radio pll_start
  3. Radio status broadcasts pll_done=0 while calibration is running.
  4. Radio status broadcasts pll_done=1 freq_error_ppb=<value> cal_freq=<value> when calibration completes.

I also verified the command against a second source: the official FlexLib API v2.10.1 source. In Radio.cs, StartOffsetEnabled=false sends radio pll_start, and the same radio status fields (cal_freq, freq_error_ppb, and pll_done) are parsed from status updates.

Fixes #1237.
Fixes #2095.

What Changed

Calibration command path

  • Replaced the unsupported radio calibrate call with the SmartSDR/FlexLib-compatible sequence:
    • radio set cal_freq=<entered MHz>
    • radio set freq_error_ppb=0
    • radio pll_start
  • Uses sendCmdPublic for radio pll_start so the UI can react to the immediate command response.
  • Leaves the user-entered calibration frequency as the explicit source of truth when Start is clicked.

Completion tracking and user feedback

  • Added a small calibration status label beside the Start button.
  • Shows Starting... immediately after the user starts calibration.
  • Shows Calibrating... after radio pll_start is accepted or when pll_done=0 arrives.
  • Shows Complete (<ppb> ppb) when pll_done=1 arrives with a freq_error_ppb value.
  • Shows Complete if the completion status arrives without a ppb value.
  • Shows Error 0x... if radio pll_start returns a non-zero response.
  • Shows No response if calibration does not report pll_done=1 within 20 seconds.
  • Keeps the successful completion message visible instead of clearing it after a timer, so the operator can read the measured value and decide whether to enter or adjust the offset.
  • Treats completion as a pll_done=0 then pll_done=1 transition for the active Start press, rather than accepting the first pll_done=1 seen after the reset commands.
  • Guards the delayed radio pll_start command response so it cannot overwrite the label after the active run has already been cleared.
  • Makes timeout callbacks run-specific so a prior successful calibration's 20-second timer cannot fire during a later calibration and incorrectly mark the newer run as No response.

GPSDO behavior

  • Removed the GPSDO-present gate that hid the manual calibration controls.
  • With a GPSDO installed, the dialog now says: GPSDO installed. Manual frequency offset calibration available.
  • This matches the requested SmartSDR/Mac-style behavior: the operator can manually calibrate TCXO or choose GPSDO/external/auto reference behavior from the adjacent 10 MHz Reference source dropdown.
  • The app no longer decides that frequency calibration is unnecessary just because a GPSDO is present.

Debug visibility

  • Added aether.protocol debug breadcrumbs for the calibration lifecycle:
    • calibration requested, including cal_freq and the reset-to-zero step
    • radio pll_start accepted
    • radio pll_start failed, including response code/body
    • every pll_done status transition observed by the dialog
    • final freq_error_ppb and cal_freq on completion
    • timeout when pll_done=1 never arrives
  • This gives reporters and maintainers a clean way to verify that the radio accepted the command and that the app observed the expected completion status.

Live value updates

  • The frequency calibration and ppb fields now refresh from RadioModel::infoChanged when the user is not actively editing them.
  • This lets the completed freq_error_ppb value flow back into the field when the radio reports the new state, without overwriting a user in the middle of typing.

Layout polish

  • Reworked the Frequency Offset controls from independent horizontal rows into a small grid.
  • The calibration frequency entry, Start button, completion status, frequency offset label, and ppb entry now share consistent columns and visually line up better with the 10 MHz Reference source row.

Agent documentation

  • Added an Agent Notes: 10 MHz TXO Calibration section to docs/tx-audio-signal-path.md.
  • The note records:
    • why radio calibrate should not be used
    • why GPSDO-present radios should still expose manual calibration controls
    • the SmartSDR/FlexLib command sequence
    • the pll_done completion semantics
    • the recommended debug logging workflow
    • the capture gotcha that Flex command traffic is TCP, normally port 4992, not the UDP VITA/discovery streams

Why This Fix

The original bug was not a UI-only issue. The button failed because AetherSDR was using a command that the radio firmware does not recognize for this workflow. The reporter's SmartSDR capture and FlexLib both point to radio pll_start as the correct start command.

The completion behavior also matters: radio pll_start only acknowledges that the radio accepted the command. The actual calibration result arrives later as radio status, so the dialog has to listen for the calibration state transition and report the final freq_error_ppb value.

Follow-up testing showed that the radio can broadcast stale pll_done=1 status immediately after radio set freq_error_ppb=0, before the new calibration has actually entered pll_done=0. If the UI treats that first pll_done=1 as completion, it can show the previous or zeroed ppb value, clear its active flag too early, and then get stuck on Calibrating... when the delayed pll_start response arrives. This PR now waits for pll_done=0 for the active run before accepting pll_done=1 as completion.

Additional repeated-run testing exposed a second race: a previous run's 20-second timeout can still be pending after that run completed successfully. If the operator starts another calibration before the old timer fires, that stale callback can see the shared active flag from the newer run and incorrectly set the UI to No response. The timeout and delayed pll_start response callbacks now carry the run id they were created for, so stale callbacks are ignored.

Finally, GPSDO presence should not remove the user's ability to manually calibrate. A GPSDO-equipped radio can still expose manual offset calibration, and SmartSDR/Mac does so. This PR follows that model and lets the operator choose the reference source instead of making that choice implicitly in the dialog.

User Impact

  • The Frequency Offset Start button should now start calibration on affected FlexRadio models.
  • Operators can see when calibration is starting, running, failed, timed out, or complete.
  • Successful calibration results stay visible until the next calibration/status change.
  • Stale pre-run pll_done=1 updates are ignored, so the Start button stays disabled until the real calibration finishes or times out.
  • Stale timeout callbacks from previous runs are ignored, so repeated calibrations do not get marked No response before the radio returns the real result.
  • GPSDO-equipped radios can still use the manual calibration controls.
  • Debug logs now provide enough lifecycle detail to confirm whether calibration fired and completed correctly.

Validation

  • Built successfully with:
    • cmake --build build --parallel 10
  • Ran the configured CTest suite with:
    • ctest --test-dir build --output-on-failure -j 10
  • CTest reported no tests were found in this build tree.
  • Confirmed CHANGELOG.md and generated What's New data are not modified; those files are left for the project maintainer.

👨🏼‍💻 Generated with OpenAI Codex (GPT-5.5 Pro 4/23) and tested by @jensenpat

@jensenpat jensenpat changed the title Fix FlexRadio frequency offset calibration [bug] Fix Flex TCXO frequency offset calibration Apr 28, 2026
@jensenpat jensenpat force-pushed the aether/pll-calibration-command branch from 30edc3e to d3b1f2b Compare April 28, 2026 01:50
@jensenpat jensenpat force-pushed the aether/pll-calibration-command branch from d3b1f2b to 1edd104 Compare April 28, 2026 01:56
@jensenpat jensenpat marked this pull request as ready for review April 28, 2026 01:59
Copy link
Copy Markdown
Contributor

@aethersdr-agent aethersdr-agent Bot left a comment

Choose a reason for hiding this comment

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

Good fix, @jensenpat. The core change -- replacing the non-functional radio calibrate with the SmartSDR/FlexLib-compatible radio pll_start sequence -- is well-researched and correctly implemented. The race condition handling (stale pll_done=1 before pll_done=0, run-specific timeout guards) shows careful follow-up testing.

A few observations:

Code looks correct:

  • sendCmdPublic callback signature matches RadioModel.h (int code, const QString& body).
  • statusReceived signal signature matches existing usage throughout the codebase (QString& object, QMap<QString, QString>& kvs).
  • lcProtocol is declared in LogManager.h and used elsewhere -- consistent with project conventions.
  • QPointer guards on dialog, startBtn, and calStatus correctly protect the deferred pll_start response callback from use-after-delete.
  • The calibrationRun counter correctly scopes both the timeout and the pll_start response to the run that created them, while the statusReceived handler (connected once at tab build) only needs the shared calibrationActive / pllRunningSeen flags since it's not per-run.
  • AppSettings is used for persistence (not QSettings) -- consistent with project convention.

Minor items worth noting (non-blocking):

  1. statusReceived handler doesn't guard widget lifetime -- unlike the pll_start callback which uses QPointer, the statusReceived lambda captures raw startBtn and calStatus pointers. Since the connection's receiver is this (the dialog), Qt will disconnect on dialog destruction, so this is safe in practice. But if the tab is ever rebuilt while the dialog lives (e.g., a future "reconnect" flow), those pointers would dangle. Low risk given the current deferred-tab architecture.

  2. QString("Error 0x%1").arg(code, 0, 16).toUpper() -- toUpper() uppercases the entire string including "Error" → "ERROR". Likely intended to uppercase the hex digits, but the visual effect is fine either way. If you wanted only hex uppercased: .arg(code, 0, 16).toUpper() could be .arg(QString::number(code, 16).toUpper()) with the prefix added separately. Cosmetic only.

  3. Doc placement -- the agent notes in tx-audio-signal-path.md are explicitly "not part of the TX audio chain" per the note itself. A separate docs/flex-calibration-notes.md might be a more natural home, but I understand the rationale of putting it where agents already look. Maintainer's call.

Overall this is a thorough, well-documented fix. The PR description is excellent -- the command sequence sourcing, race condition analysis, and debug workflow are all clearly explained. Thanks for the contribution. 73!

@ten9876 ten9876 merged commit d86e34f into ten9876:main Apr 28, 2026
5 checks passed
@jensenpat jensenpat deleted the aether/pll-calibration-command branch April 29, 2026 04:53
ten9876 added a commit that referenced this pull request Apr 29, 2026
Community-driven release.  WAVE Phase 2 visualization (#2124),
DAX-aware TCI multi-stream routing for FlexRadio firmware 4.2.18
(#2140), TCXO frequency-offset calibration (#2119), VFO marker
tri-state UX (#2141), v4.2.18 discovery beacon parsing (#2138).
Bug fixes from the community: r8b heap corruption (#2114, NF0T),
serial PTT triple-fix (#2125, chibondking), slice-audio mute on
band change (#2128, jensenpat), CWX Live toggle (#2122, jensenpat),
connect-radio dialog polish (#2121, jensenpat).

Co-authored-by: Claude Opus 4.7 (1M context) <[email protected]>
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.

10 Mhz TXO frequency calibration not working closed #1237 and no responce. Freqency Offset Calibration non Fuctional

2 participants