Skip to content

Add ShackSwitch antenna switch integration (Peripherals tab + applet)#2214

Merged
ten9876 merged 1 commit intoten9876:mainfrom
nigelfenton:main
May 1, 2026
Merged

Add ShackSwitch antenna switch integration (Peripherals tab + applet)#2214
ten9876 merged 1 commit intoten9876:mainfrom
nigelfenton:main

Conversation

@nigelfenton
Copy link
Copy Markdown
Contributor

Summary

Adds native support for the ShackSwitch open-source Arduino antenna switch in AetherSDR via a new Peripherals tab and ShackSwitchApplet.

ShackSwitch uses the existing Antenna Genius AG protocol, so no new protocol support is required — auto-detection works via the AG UDP beacon.

What's added

  • ShackSwitchApplet — shows up to 8 labelled antenna port buttons; active port highlighted; click to switch antenna
  • Peripherals tab in Radio Setup — lists connected ShackSwitch devices with a Settings button (opens the ShackSwitch web UI)
  • SO2R / dual-radio mode — Input A and Input B shown side by side with independent port selection
  • Single-radio mode — Input B UI hidden automatically for single-radio hardware
  • Dummy-load / deselect — pressing the active port button deselects it
  • Conflict indicator — flags when both inputs share the same antenna port
  • Auto port count — 4-port (R4) vs 8-port (Uno Q) inferred from AG TCP prologue version field
  • isShackSwitch flag on AntennaGeniusModel drives all conditional UI — invisible to users without ShackSwitch hardware

Hardware tested

  • ShackSwitch R4 1×4 (Arduino Uno R4 WiFi, single radio, 4 ports)
  • ShackSwitch v2.0 (Arduino Uno Q, SO2R dual radio, 8 ports, MCP23017 matrix)
  • AetherSDR on Windows 11, FlexRadio 6700 as SDR source

Rollback

The feature is entirely self-contained — ShackSwitchApplet.cpp/.h can be deleted and a single git revert removes all changes to existing files cleanly.

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 contribution, @nigelfenton — well-structured PR with clean separation from the existing AG code path. The approach of reusing AntennaGeniusModel for protocol compatibility is solid. A few things to address before merge:


Dangling reference capture in RadioSetupDialog.cpp

In buildPeripheralsTab(), the web UI button handler captures settings by reference:

auto& settings = AppSettings::instance();
// ...
connect(webBtn, &QPushButton::clicked, this, [this, &settings]() {
    QString ip = settings.value("SS_ManualIp", "").toString();
    // ...
});

settings is a local reference on the stack of buildPeripheralsTab(). Capturing it by reference in a lambda that outlives the function (button click handler) is undefined behavior — the lambda holds a dangling reference after the function returns.

Fix: drop the &settings capture and call AppSettings::instance() directly inside the lambda, or capture this only (which you're already doing):

connect(webBtn, &QPushButton::clicked, this, [this]() {
    auto& settings = AppSettings::instance();
    // ...
});

Duplicated ShackSwitch detection logic

The check serial.startsWith("G0JKN") || name.contains("ShackSwitch", Qt::CaseInsensitive) appears in at least 5 locations across 3 files:

  • AntennaGeniusApplet.cpp
  • MainWindow.cpp (lambda + two connect handlers)
  • RadioSetupDialog.cpp (isRealAg, isSsConnected, webBtn handler — 3 times)

The PR description mentions an "isShackSwitch flag on AntennaGeniusModel" but I don't see such a method on the model. Consider adding a static helper or member function on AntennaGeniusModel:

static bool isShackSwitch(const AgDeviceInfo& info) {
    return info.serial.startsWith("G0JKN") ||
           info.name.contains("ShackSwitch", Qt::CaseInsensitive);
}

This avoids the risk of the detection logic drifting between call sites if e.g. a new hardware revision uses a different serial prefix.

Misleading comment on openShackSwitchWebUi

The comment says "Tries Chromium-based browsers with --app flag; falls back to default browser" but the implementation is just QDesktopServices::openUrl(). Either update the comment to match the implementation, or remove the wrapper entirely and call QDesktopServices::openUrl() directly at the call site — the function adds no value as-is.

7-second QTimer::singleShot delay for AG manual connect

In MainWindow.cpp onConnectionStateChanged:

QTimer::singleShot(7000, this, [this, agIp, agPort]() {
    if (!m_antennaGenius.isConnected())
        m_antennaGenius.connectToAddress(QHostAddress(agIp), agPort);
});

A hardcoded 7s delay is fragile — if the ShackSwitch TCP handshake takes longer than expected, or if onConnectionStateChanged fires again during that window (reconnect scenario), this could race. The isConnected() guard helps, but consider:

  • Is 7s always long enough for ShackSwitch to complete its connect + enrichment?
  • If the user disconnects and reconnects the radio within 7s, the timer from the first connection still fires with stale agIp/agPort values.

A safer pattern would be to cancel the pending timer on disconnect (store the timer ID and call killTimer or use a QTimer member).

Minor: webPort field on AgDeviceInfo

The new webPort field defaults to 0 and is parsed from the UDP beacon's webport key. The beacon validation (info.serial.isEmpty() || info.ip.isNull()) doesn't check webPort, which is correct since it's optional. Just noting that the port validation in the click handler (> 1024 threshold, falling back to SS_WebPort setting, then to 5000) is well thought out.


Everything else looks good — the AppletPanel integration follows the existing AG pattern, the CMakeLists change is minimal, and the model enrichment logic for late UDP beacons is a nice touch. The AgDeviceInfo struct additions (webPort, lastRadioBand getter) are clean and backward-compatible. Thanks for the thorough testing notes and rollback section in the PR description.

Adds a ShackSwitchApplet and Peripherals tab that auto-detects and
controls a ShackSwitch v2.0 antenna switch (Arduino Uno Q) via the
existing Antenna Genius AG protocol.

Features:
- Auto-detection from AG UDP beacon (webport field used for Settings URL)
- Peripherals tab in RadioSetupDialog lists connected ShackSwitch devices
- ShackSwitchApplet shows up to 8 antenna ports as labelled buttons;
  active port highlighted; clicking switches the antenna
- SO2R / dual-radio mode: Input A and Input B shown side by side with
  independent port selection and interlock awareness
- Single-radio mode (R4 hardware): Input B UI hidden automatically;
  determined from AG prologue version field at connect time
- Dummy-load / deselect: pressing the active port button deselects it
- Conflict indicator: flags when both inputs share the same port
- Settings button opens the ShackSwitch web UI in the default browser
- Port count inferred from AG TCP prologue (4-port R4 vs 8-port Uno Q)
- isShackSwitch flag on AntennaGeniusModel drives all conditional UI

Hardware tested:
- ShackSwitch R4 1x4 (Arduino Uno R4 WiFi, single radio, 4 ports)
- ShackSwitch v2.0 (Arduino Uno Q, SO2R, 8 ports, MCP23017 matrix)
- AetherSDR on Windows 11, FlexRadio 6700 as SDR source
@ten9876 ten9876 merged commit bbf0aaf into ten9876:main May 1, 2026
5 checks passed
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