Skip to content

Fix serial PTT input non-functional: port never opened + polarity default mismatch#2125

Merged
ten9876 merged 1 commit intoten9876:mainfrom
chibondking:fix/serial-ptt-port-open
Apr 28, 2026
Merged

Fix serial PTT input non-functional: port never opened + polarity default mismatch#2125
ten9876 merged 1 commit intoten9876:mainfrom
chibondking:fix/serial-ptt-port-open

Conversation

@chibondking
Copy link
Copy Markdown
Collaborator

Problem

Users report that configuring DSR (or CTS) as PTT Input in Radio Setup → Serial has no effect — pressing the footswitch produces no RF output and no interlock state change, even when hardware is confirmed working via FRStack.

Three independent bugs blocked the PTT path:


Bug 1 — No way to open the serial port without restarting

loadSettings() only opened the port when SerialAutoOpen == "True". The Serial tab had no Open/Close buttons, so users who configured settings in a running session had no mechanism to activate the port until the next restart. Even with auto-open enabled, the label "Auto-open serial port on startup" implied a restart was required — many users did not realize that closing the dialog alone was sufficient to trigger loadSettings().

Bug 2 — Polarity default mismatch (silent inversion)

loadSettings() fell back to "ActiveLow" for SerialCtsPolarity and SerialDsrPolarity when the key was absent from AppSettings. The polarity combo in the dialog defaulted to displaying "Active High" (index 0), but its currentIndexChanged signal is connected after the initial index is set, so leaving polarity at the displayed default wrote nothing to AppSettings. Result: a user who configured DSR → PTT Input and left polarity at the displayed "Active High" default would have the controller silently invert the logic — footswitch press reads as inactive, PTT never fires.

Bug 3 — updatePolling() not called when function assignments change on an open port

When the dialog was closed and loadSettings() ran with SerialAutoOpen == "False" (port not being re-opened), the poll timer state was never refreshed. If a user changed a pin function from None → PttInput while the port was already open, the 10 ms poll timer would not start and the new assignment would be silently inert.


Fix

src/core/SerialPortController.cpp

  • loadSettings(): open condition now checks SerialAutoOpen || SerialPortOpen so the new Open button in the dialog takes effect when the dialog closes.
  • loadSettings(): added explicit close path (!shouldOpen && isOpen()) so the Close button also takes effect without a restart.
  • loadSettings(): added updatePolling() in the else branch (port state unchanged) so pin function changes on an already-open port correctly start or stop the 10 ms poll timer.
  • loadSettings(): changed SerialCtsPolarity and SerialDsrPolarity fallback defaults from "ActiveLow" to "ActiveHigh" to match the combo's displayed default, eliminating the silent polarity inversion for first-time users who leave the polarity combo untouched.
  • saveSettings(): now also persists SerialPortOpen so a port opened manually via the button survives restart (same semantics as SerialAutoOpen).

src/gui/RadioSetupDialog.cpp

  • Added Open and Close buttons to the Serial tab with a live status indicator ("Open" green / "Closed" grey). Clicking Open sets SerialPortOpen = True in AppSettings; clicking Close sets it to False. Both take effect when the dialog is closed, triggering loadSettings() on the worker thread via the existing QMetaObject::invokeMethod in MainWindow.
  • Retained the "Auto-open serial port on startup" checkbox beneath the buttons.

Signal path (verified end-to-end)

footswitch asserts DSR (active high)
  → SerialPortController::pollInputPins() [10 ms QTimer, worker thread]
    → dsrActive=true, m_lastDsrActive=false, debounceOk=true
      → emit externalPttChanged(true)           [queued → main thread]
        → RadioModel::setTransmit(true)
          → sendCmd("xmit 1")                   [TCP → FLEX-8600 → RF output]

CTS PTT path is identical (m_ctsFn == InputFunction::PttInput).


Steps to reproduce (before fix)

  1. Connect USB-serial adapter; footswitch on DSR pin, active high.
  2. Open Radio Setup → Serial.
  3. Select port, set DSR = PTT Input, Polarity = Active High.
  4. Close dialog (auto-open not checked, no restart).
  5. Press footswitch → no keying, no interlock change.

Steps to verify (after fix)

  1. Same hardware setup.
  2. Open Radio Setup → Serial; configure port and pin assignment.
  3. Click Open → status label changes to "Open" (green).
  4. Close dialog.
  5. Press footswitch → radio keys, RF output observed.
  6. Re-open dialog, click Close → status "Closed"; footswitch no longer keys.
  7. Enable Auto-open serial port on startup, exit and restart → port opens automatically on startup, footswitch keys without manual Open step.

Files changed

src/core/SerialPortController.cpp loadSettings: SerialPortOpen flag, close path,
updatePolling, polarity defaults (ActiveLow→ActiveHigh)
saveSettings: persist SerialPortOpen
src/gui/RadioSetupDialog.cpp Serial tab: add Open/Close buttons + status label

…ault mismatch

## Problem

Users report that configuring DSR (or CTS) as PTT Input in Radio Setup → Serial
has no effect — pressing the footswitch produces no RF output and no interlock
state change, even when hardware is confirmed working via FRStack.

Three independent bugs blocked the PTT path:

---

### Bug 1 — No way to open the serial port without restarting

`loadSettings()` only opened the port when `SerialAutoOpen == "True"`. The
Serial tab had no Open/Close buttons, so users who configured settings in a
running session had no mechanism to activate the port until the next restart.
Even with auto-open enabled, the label "Auto-open serial port on **startup**"
implied a restart was required — many users did not realize that closing the
dialog alone was sufficient to trigger `loadSettings()`.

### Bug 2 — Polarity default mismatch (silent inversion)

`loadSettings()` fell back to `"ActiveLow"` for `SerialCtsPolarity` and
`SerialDsrPolarity` when the key was absent from AppSettings. The polarity
combo in the dialog defaulted to displaying `"Active High"` (index 0), but its
`currentIndexChanged` signal is connected *after* the initial index is set, so
leaving polarity at the displayed default wrote nothing to AppSettings. Result:
a user who configured DSR → PTT Input and left polarity at the displayed
"Active High" default would have the controller silently invert the logic —
footswitch press reads as inactive, PTT never fires.

### Bug 3 — `updatePolling()` not called when function assignments change on an open port

When the dialog was closed and `loadSettings()` ran with `SerialAutoOpen ==
"False"` (port not being re-opened), the poll timer state was never refreshed.
If a user changed a pin function from None → PttInput while the port was already
open, the 10 ms poll timer would not start and the new assignment would be
silently inert.

---

## Fix

### `src/core/SerialPortController.cpp`

- **`loadSettings()`**: open condition now checks `SerialAutoOpen || SerialPortOpen`
  so the new Open button in the dialog takes effect when the dialog closes.
- **`loadSettings()`**: added explicit close path (`!shouldOpen && isOpen()`) so
  the Close button also takes effect without a restart.
- **`loadSettings()`**: added `updatePolling()` in the else branch (port state
  unchanged) so pin function changes on an already-open port correctly start or
  stop the 10 ms poll timer.
- **`loadSettings()`**: changed `SerialCtsPolarity` and `SerialDsrPolarity`
  fallback defaults from `"ActiveLow"` to `"ActiveHigh"` to match the combo's
  displayed default, eliminating the silent polarity inversion for first-time
  users who leave the polarity combo untouched.
- **`saveSettings()`**: now also persists `SerialPortOpen` so a port opened
  manually via the button survives restart (same semantics as `SerialAutoOpen`).

### `src/gui/RadioSetupDialog.cpp`

- Added **Open** and **Close** buttons to the Serial tab with a live status
  indicator ("Open" green / "Closed" grey). Clicking Open sets
  `SerialPortOpen = True` in AppSettings; clicking Close sets it to `False`.
  Both take effect when the dialog is closed, triggering `loadSettings()` on
  the worker thread via the existing `QMetaObject::invokeMethod` in MainWindow.
- Retained the "Auto-open serial port on startup" checkbox beneath the buttons.

---

## Signal path (verified end-to-end)

```
footswitch asserts DSR (active high)
  → SerialPortController::pollInputPins() [10 ms QTimer, worker thread]
    → dsrActive=true, m_lastDsrActive=false, debounceOk=true
      → emit externalPttChanged(true)           [queued → main thread]
        → RadioModel::setTransmit(true)
          → sendCmd("xmit 1")                   [TCP → FLEX-8600 → RF output]
```

CTS PTT path is identical (`m_ctsFn == InputFunction::PttInput`).

---

## Steps to reproduce (before fix)

1. Connect USB-serial adapter; footswitch on DSR pin, active high.
2. Open Radio Setup → Serial.
3. Select port, set DSR = PTT Input, Polarity = Active High.
4. Close dialog (auto-open not checked, no restart).
5. Press footswitch → no keying, no interlock change.

## Steps to verify (after fix)

1. Same hardware setup.
2. Open Radio Setup → Serial; configure port and pin assignment.
3. Click **Open** → status label changes to "Open" (green).
4. Close dialog.
5. Press footswitch → radio keys, RF output observed.
6. Re-open dialog, click **Close** → status "Closed"; footswitch no longer keys.
7. Enable **Auto-open serial port on startup**, exit and restart → port opens
   automatically on startup, footswitch keys without manual Open step.

---

## Files changed

  src/core/SerialPortController.cpp   loadSettings: SerialPortOpen flag, close path,
                                      updatePolling, polarity defaults (ActiveLow→ActiveHigh)
                                      saveSettings: persist SerialPortOpen
  src/gui/RadioSetupDialog.cpp        Serial tab: add Open/Close buttons + status label
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.

Nice fix, @chibondking — the three-bug root cause analysis is thorough and the signal path verification in the PR description is appreciated. The polarity default mismatch is exactly the kind of subtle issue that produces "it works in FRStack but not here" reports.

SerialPortController.cpp

Polarity defaults (ActiveLowActiveHigh): Correct fix. The combo's index 0 is "Active High", currentIndexChanged never fires for the default, so AppSettings has no key — and the fallback must match. Good catch.

shouldOpen / close path / updatePolling() in else branch: All three look correct. The updatePolling() call in the else branch properly handles the case where a user changes a pin function while the port is already open — without it the 10 ms poll timer would never start.

saveSettings() persisting SerialPortOpen: One thing to note — after saveSettings() runs, both SerialAutoOpen and SerialPortOpen will equal isOpen(). This means a port opened manually via the "Open" button will also auto-open on restart (by design per the PR description), which makes the "Auto-open serial port on startup" checkbox somewhat misleading — unchecking it won't prevent startup open if SerialPortOpen is still True from the last session. Not a blocker, but worth considering whether the intent is clearer if SerialPortOpen is reset to "False" on application startup (in loadSettings() after the open attempt), so only SerialAutoOpen controls restart behavior.

RadioSetupDialog.cpp

Open/Close buttons + status label: The pattern matches the existing FlexControl Detect/Close UI (line ~3098), so this fits the codebase conventions well.

Status label reads desired state, not actual state: The updatePortStatus lambda reads SerialPortOpen from AppSettings, which reflects what the user requested, not whether open() succeeded. If the serial port is busy or unplugged, open() will fail in loadSettings() on the worker thread, but the dialog will still show "Open" (green) until the next dialog open when saveSettings() corrects it to isOpen() == false. Consider connecting to SerialPortController::errorOccurred to update the status, or at minimum noting in the label that the open takes effect on dialog close (e.g., "Open (pending)" or tooltip text).

Summary

The three bugs are real, the fixes are correct, and the scope is clean — only the two files stated. No null-pointer risks, no resource leaks, thread model looks right (AppSettings written on main thread, loadSettings() reads on worker thread after dialog close). The updatePolling() addition in the else branch is a nice detail that would have been easy to miss.

Thanks for the contribution — solid work. 🎙️ 73

@ten9876 ten9876 merged commit 939f93a into ten9876:main Apr 28, 2026
5 checks passed
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]>
@rayslinky
Copy link
Copy Markdown

The Open/Close don't seem to actually do anything for my serial port. It still has to be connected at program open in order to function. If I set it to /dev/ttyUSB0, close the program, open it, with the setting for PTT input, it will work to PTT on open. If I then open the dialog and click close, the ptt still works from ttyUSB0. Even if i refresh and change the port to anything else, close/open, the PTT from ttyUSB0 still functions.

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.

3 participants