-
Notifications
You must be signed in to change notification settings - Fork 14
Module System Overview
SDR++CE's modular architecture allows extending functionality through dynamically loaded plugins. This system provides clean separation between core infrastructure and feature implementations.
Purpose: Provide IQ data streams from hardware or files
Examples: RTL-SDR, HackRF, PlutoSDR, File Source, Network Source
Pattern: Handler-based registration with SourceManager
// Handler structure
SourceManager::SourceHandler handler;
handler.ctx = this;
handler.menuHandler = drawMenu; // UI callback
handler.startHandler = startAcquisition; // Begin streaming
handler.stopHandler = stopAcquisition; // Stop streaming
handler.tuneHandler = tuneFrequency; // Frequency change
handler.stream = &outputStream; // IQ output stream
sigpath::sourceManager.registerSource("My SDR", &handler);Purpose: Process VFO signals to extract audio or data
Examples: AM/FM demodulators, Digital protocols (DMR, P25, etc.)
Pattern: Factory pattern providing dsp::hier_block instances
// Register decoder factory
sigpath::vfoManager.registerDemodulator("My Protocol", this);
// Factory method
dsp::hier_block<dsp::complex_t, dsp::stereo_t>* createDemodulator(
VFOManager::VFO* vfo, std::string name, ConfigManager* config,
dsp::stream<dsp::complex_t>* input, double bandwidth) {
return new MyDemodulator(input, bandwidth);
}Purpose: Output audio to devices, files, or network
Examples: Audio devices (ALSA, WASAPI), Network streaming, File recording
Pattern: Provider pattern with SinkManager
// Provider structure
SinkManager::SinkProvider provider;
provider.create = createSink;
provider.ctx = this;
sigpath::sinkManager.registerSinkProvider("My Sink", provider);
// Factory function
static SinkManager::Sink* createSink(SinkManager::Stream* stream,
std::string name, void* ctx) {
return new MySink(stream, name, (MyModule*)ctx);
}Purpose: Utilities and tools that don't fit other categories
Examples: Scanner, Frequency Manager, Recorder, Protocol analyzers
Pattern: Direct UI integration and optional ModuleCom interfaces
Every module must export these C functions:
extern "C" {
// Module metadata
SDRPP_EXPORT ModuleInfo_t* _INFO_();
// Module lifecycle
SDRPP_EXPORT void _INIT_();
SDRPP_EXPORT void _END_();
// Instance management
SDRPP_EXPORT ModuleManager::Instance* _CREATE_INSTANCE_(std::string name);
SDRPP_EXPORT void _DELETE_INSTANCE_(ModuleManager::Instance* instance);
}// Define module metadata
SDRPP_MOD_INFO{
/* Name: */ "my_module",
/* Description: */ "My awesome module",
/* Author: */ "Your Name",
/* Version: */ "1.0.0",
/* Max instances */ 1 // -1 for unlimited
};All module instances inherit from ModuleManager::Instance:
class MyModule : public ModuleManager::Instance {
public:
MyModule(std::string name);
~MyModule();
// Lifecycle methods
void postInit() override; // Called after all modules loaded
void enable() override; // Module enabled in config
void disable() override; // Module disabled
bool isEnabled() override; // Query enabled state
private:
std::string _name;
bool _enabled = false;
};my_module/
├── CMakeLists.txt
├── src/
│ ├── main.cpp # Module exports and main class
│ ├── main.h # Class declarations
│ └── dsp/ # Custom DSP blocks (optional)
└── README.md
cmake_minimum_required(VERSION 3.13)
project(my_module)
# Find SDR++ core
find_package(PkgConfig REQUIRED)
pkg_check_modules(SDRPP_CORE REQUIRED sdrpp_core)
# Build module as shared library
file(GLOB_RECURSE SRC "src/*.cpp")
add_library(my_module SHARED ${SRC})
target_link_libraries(my_module PRIVATE sdrpp_core)
target_include_directories(my_module PRIVATE
"src/"
${SDRPP_CORE_INCLUDE_DIRS}
)
# Install to modules directory
install(TARGETS my_module DESTINATION lib/sdrpp/plugins)#include <module.h>
#include <gui/gui.h>
#include <signal_path/signal_path.h>
#include <core.h>
// Module information
SDRPP_MOD_INFO{
/* Name: */ "my_module",
/* Description: */ "Example module template",
/* Author: */ "SDR++ Community",
/* Version: */ "1.0.0",
/* Max instances */ -1
};
class MyModule : public ModuleManager::Instance {
public:
MyModule(std::string name) : _name(name) {
// Module initialization
}
void postInit() override {
// Register UI after all modules loaded
gui::menu.registerEntry(_name, menuHandler, this);
}
void enable() override {
_enabled = true;
}
void disable() override {
_enabled = false;
}
bool isEnabled() override {
return _enabled;
}
private:
static void menuHandler(void* ctx) {
((MyModule*)ctx)->drawMenu();
}
void drawMenu() {
if (SmGui::BeginMenu(_name.c_str())) {
SmGui::Text("Module is %s", _enabled ? "enabled" : "disabled");
SmGui::EndMenu();
}
}
std::string _name;
bool _enabled = false;
};
// Required exports
MOD_EXPORT void _INIT_() {
// One-time initialization
}
MOD_EXPORT ModuleManager::Instance* _CREATE_INSTANCE_(std::string name) {
return new MyModule(name);
}
MOD_EXPORT void _DELETE_INSTANCE_(ModuleManager::Instance* instance) {
delete (MyModule*)instance;
}
MOD_EXPORT void _END_() {
// Cleanup
}Modules can expose services to other modules:
// Define command codes
#define CMD_GET_STATUS 0
#define CMD_SET_FREQUENCY 1
// Interface handler
int interfaceHandler(int command, void* in, void* out, void* ctx) {
MyModule* module = (MyModule*)ctx;
switch (command) {
case CMD_GET_STATUS:
*(bool*)out = module->isRunning();
return 0;
case CMD_SET_FREQUENCY:
module->setFrequency(*(double*)in);
return 0;
default:
return -1; // Unknown command
}
}
// Register interface (in constructor)
core::modComManager.registerInterface(_name, interfaceHandler, this);
// Call interface from other modules
bool running;
if (core::modComManager.callInterface("module_name", CMD_GET_STATUS, nullptr, &running)) {
// Command succeeded
}void MyModule::loadConfig() {
core::configManager.acquire();
// Always check if section exists
if (core::configManager.conf.contains(_name)) {
auto& cfg = core::configManager.conf[_name];
if (cfg.contains("enabled")) {
_enabled = cfg["enabled"];
}
if (cfg.contains("frequency")) {
_frequency = cfg["frequency"];
}
}
core::configManager.release();
}
void MyModule::saveConfig() {
core::configManager.acquire();
core::configManager.conf[_name]["enabled"] = _enabled;
core::configManager.conf[_name]["frequency"] = _frequency;
core::configManager.release(true); // Mark as dirty
}All module configuration keys must be defined in core/src/core.cpp:
// In defConfig creation
defConfig["myModule"]["enabled"] = false;
defConfig["myModule"]["frequency"] = 100000000.0;
defConfig["myModule"]["gain"] = 20.0;Without default schema entries, settings will be lost during configuration repair.
- Use atomic variables for parameter updates
- Never allocate memory in DSP processing
- Avoid blocking operations
- Pre-allocate all buffers
- Only modify UI state from UI thread
- Use proper locking for shared data
- Batch configuration updates
- Handle missing modules gracefully
// Always validate inputs
if (!inputStream || !outputStream) {
flog::error("[{}] Invalid stream configuration", _name);
return false;
}
// Handle missing dependencies gracefully
if (!core::modComManager.interfaceExists("required_module")) {
flog::warn("[{}] Required module not available, disabling feature", _name);
_featureEnabled = false;
}// Pre-allocate buffers
MyModule::MyModule(std::string name) {
_buffer = new float[MAX_BUFFER_SIZE];
_tempBuffer = new dsp::complex_t[MAX_SAMPLES];
}
// Use SIMD when possible
#ifdef VOLK_AVAILABLE
volk_32fc_magnitude_32f(_magnitudes, _samples, count);
#else
// Fallback implementation
for (int i = 0; i < count; i++) {
_magnitudes[i] = abs(_samples[i]);
}
#endifNext: Module Development Guide for step-by-step module creation