Skip to content

Add a lightweight SDR plugin subsystem with bundled FT8/FT4 and WSPR decoder extensions #3474

Description

@anynetcorp

Request preparation

  • I used an AI assistant to help structure this request
  • I checked for existing issues covering the same feature

What would you like?

Summary

Implement a lightweight SDR plugin/extension subsystem for AetherSDR,
modelled on the extension architecture found in Web-888/KiwiSDR (see
Web-888 Extensions Tab).
The first two bundled plugins would be:

  • FT8 / FT4 decoder (background, per-band, reports to PSKReporter)
  • WSPR decoder (background, per-band, reports to WSPRnet)

Both are well-proven in embedded/SDR contexts and have excellent open-source
C library foundations we can link directly.


Motivation

AetherSDR already bundles several signal-processing engines
(CwDecoder via ggmorse, RNNoiseFilter, RADEEngine, SpectralNR) as
self-contained src/core/ classes. A formal plugin interface would:

  1. Allow optional DSP tasks to run against the live audio stream
    without touching the main RX/audio path.
  2. Enable community contributors to add decoders (JS8Call, AX.25,
    APRS, P25, …) without modifying core files.
  3. Mirror the extension UX that Web-888/KiwiSDR users already
    understand: enable/disable per extension, callsign + grid
    configuration, optional network reporting.

Proposed Architecture

Plugin Interface (src/core/SdrPlugin.h)

class SdrPlugin : public QObject {
    Q_OBJECT
public:
    virtual ~SdrPlugin() = default;

    // Called once after construction; give the plugin its config section
    virtual void initialize(const QString& configPrefix) = 0;

    // Called by AudioEngine for every decoded audio block
    // rate: sample rate (Hz), mono float32, same 24 kHz stream as CwDecoder
    virtual void feedAudio(const float* samples, int count, int sampleRate) = 0;

    // Human-readable name shown in the plugin applet / settings tab
    virtual QString name() const = 0;

    // Returns the QWidget* panel to embed in PluginApplet (may be nullptr)
    virtual QWidget* createPanel(QWidget* parent) = 0;

signals:
    void messageDecoded(const QString& text, double freqHz, double snr);
};

Plugins are registered in RadioModel or a new PluginManager singleton.
AudioEngine::feedAudioData() iterates the active plugin list after its
existing NR2 / RN2 / CW chain — no changes to the VITA-49 decode path.


FT8 / FT4 Plugin (src/plugins/Ft8Plugin)

Library: kgoba/ft8_lib (MIT licence,
pure C, no external dependencies, ~200 KB RAM, proven on STM32F7).

Integration notes:

  • FT8 uses 15-second windows; FT4 uses 7.5-second windows.
    The plugin accumulates audio internally and triggers a decode at each
    window boundary (wall-clock aligned, same as WSJT-X).
  • Input: 24 kHz float32 mono from AudioEngine (already available via
    the CwDecoder feed path — same feedAudio() hook).
    ft8_lib's demo decoder expects 12 kHz; a single r8brain stage
    (already a dependency) downsamples inline.
  • Decoded messages are emitted via messageDecoded() and displayed in a
    scrolling QPlainTextEdit panel docked in the AppletPanel.
  • PSKReporter upload: optional HTTP POST to https://www.pskreporter.info/
    using QNetworkAccessManager (already used by SmartLinkClient / Auth0).
  • Settings (stored via AppSettings, keys e.g. Ft8PluginEnabled,
    Ft8PluginCallsign, Ft8PluginGrid) configured in a new
    Radio Setup → Extensions tab.

Bundling strategy (same pattern as RNNoise / ggmorse):

# CMakeLists.txt — add under optional deps block
option(ENABLE_FT8 "Enable FT8/FT4 decoder plugin" ON)
if(ENABLE_FT8)
    add_subdirectory(third_party/ft8_lib)
    target_link_libraries(AetherSDR PRIVATE ft8_lib)
    target_compile_definitions(AetherSDR PRIVATE HAVE_FT8)
endif()

WSPR Plugin (src/plugins/WsprPlugin)

WSPR uses 2-minute windows on even UTC minutes.
Candidate library: twit65/WSPR-library
or the decoder extracted from wsprd (WSJT-X, GPLv3 — check licence
compatibility before bundling; alternatively call wsprd as a subprocess).

Upload target: http://wsprnet.org/post (same QNetworkAccessManager).


GUI / UX

  • New EXT toggle button in AppletPanel toggle row (alongside ANLG, RX,
    TX, PHNE, P/CW, EQ).
  • PluginApplet: tabbed widget, one tab per enabled plugin, showing its
    createPanel() widget.
  • Radio Setup → Extensions tab (new 10th tab in RadioSetupDialog):
    table of all known plugins with Enable checkbox, callsign field, grid field,
    PSKReporter/WSPRnet upload toggle.
  • Settings keys follow existing AppSettings PascalCase convention:
    Ft8PluginEnabled, Ft8PluginCallsign, Ft8PluginGrid,
    Ft8PluginPskReporter, WsprPluginEnabled, etc.

Audio Path — No Regression Risk

The plugin audio feed is read-only and downstream of all existing DSP:

Metadata

Metadata

Assignees

No one assigned

    Labels

    GUIUser interfaceNew FeatureNew feature requestaudioAudio engine and streamingenhancementImprovement to existing featuremaintainer-reviewRequires maintainer review before any action is takenpriority: lowLow priority

    Type

    No type

    Fields

    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions