Merge branch 'master' into patch-1

This commit is contained in:
Dr. Rubisco 2021-07-29 12:38:23 -04:00 committed by GitHub
commit d84273a0db
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
16 changed files with 454 additions and 406 deletions

View File

@ -47,51 +47,49 @@ void ConfigManager::save(bool lock) {
}
void ConfigManager::enableAutoSave() {
if (!autoSaveEnabled) {
autoSaveEnabled = true;
termFlag = false;
autoSaveThread = std::thread(autoSaveWorker, this);
}
if (autoSaveEnabled) { return; }
autoSaveEnabled = true;
termFlag = false;
autoSaveThread = std::thread(&ConfigManager::autoSaveWorker, this);
}
void ConfigManager::disableAutoSave() {
if (autoSaveEnabled) {
{
std::lock_guard<std::mutex> lock(termMtx);
autoSaveEnabled = false;
termFlag = true;
}
termCond.notify_one();
if (autoSaveThread.joinable()) { autoSaveThread.join(); }
if (!autoSaveEnabled) { return; }
{
std::lock_guard<std::mutex> lock(termMtx);
autoSaveEnabled = false;
termFlag = true;
}
termCond.notify_one();
if (autoSaveThread.joinable()) { autoSaveThread.join(); }
}
void ConfigManager::acquire() {
mtx.lock();
}
void ConfigManager::release(bool changed) {
this->changed |= changed;
void ConfigManager::release(bool modified) {
changed |= modified;
mtx.unlock();
}
void ConfigManager::autoSaveWorker(ConfigManager* _this) {
while (_this->autoSaveEnabled) {
if (!_this->mtx.try_lock()) {
void ConfigManager::autoSaveWorker() {
while (autoSaveEnabled) {
if (!mtx.try_lock()) {
spdlog::warn("ConfigManager locked, waiting...");
std::this_thread::sleep_for(std::chrono::milliseconds(1000));
continue;
}
if (_this->changed) {
_this->changed = false;
_this->save(false);
if (changed) {
changed = false;
save(false);
}
_this->mtx.unlock();
mtx.unlock();
// Sleep but listen for wakeup call
{
std::unique_lock<std::mutex> lock(_this->termMtx);
_this->termCond.wait_for(lock, std::chrono::milliseconds(1000), [_this]() { return _this->termFlag; } );
std::unique_lock<std::mutex> lock(termMtx);
termCond.wait_for(lock, std::chrono::milliseconds(1000), [this]() { return termFlag; } );
}
}
}

View File

@ -17,21 +17,21 @@ public:
void enableAutoSave();
void disableAutoSave();
void acquire();
void release(bool changed = false);
void release(bool modified = false);
json conf;
private:
static void autoSaveWorker(ConfigManager* _this);
void autoSaveWorker();
std::string path = "";
bool changed = false;
bool autoSaveEnabled = false;
volatile bool changed = false;
volatile bool autoSaveEnabled = false;
std::thread autoSaveThread;
std::mutex mtx;
std::mutex termMtx;
std::condition_variable termCond;
bool termFlag = false;
volatile bool termFlag = false;
};

View File

@ -206,6 +206,7 @@ int sdrpp_main(int argc, char *argv[]) {
defConfig["showWaterfall"] = true;
defConfig["source"] = "";
defConfig["decimationPower"] = 0;
defConfig["iqCorrection"] = false;
defConfig["streams"]["Radio"]["muted"] = false;
defConfig["streams"]["Radio"]["sink"] = "Audio";
@ -462,6 +463,11 @@ int sdrpp_main(int argc, char *argv[]) {
glfwSwapBuffers(core::window);
}
// Shut down all modules
for (auto& [name, mod] : core::moduleManager.modules) {
mod.end();
}
// Cleanup
ImGui_ImplOpenGL3_Shutdown();
ImGui_ImplGlfw_Shutdown();
@ -472,5 +478,8 @@ int sdrpp_main(int argc, char *argv[]) {
sigpath::signalPath.stop();
core::configManager.disableAutoSave();
core::configManager.save();
return 0;
}

View File

@ -24,9 +24,10 @@ namespace sdrpp_credits {
const char* libraries[] = {
"Dear ImGui (ocornut)",
"fftw3 (fftw.org)",
"glew (Nigel Stewart)",
"glfw (Camilla Löwy)",
"json (nlohmann)",
"RtAudio",
"SoapySDR (PothosWare)",
"spdlog (gabime)",
"Portable File Dialogs"
};

76
core/src/dsp/correction.h Normal file
View File

@ -0,0 +1,76 @@
#pragma once
#include <dsp/block.h>
#include <dsp/stream.h>
#include <dsp/types.h>
#include <dsp/window.h>
namespace dsp {
class IQCorrector : public generic_block<IQCorrector> {
public:
IQCorrector() {}
IQCorrector(stream<complex_t>* in, float rate) { init(in, rate); }
void init(stream<complex_t>* in, float rate) {
_in = in;
correctionRate = rate;
offset.re = 0;
offset.im = 0;
generic_block<IQCorrector>::registerInput(_in);
generic_block<IQCorrector>::registerOutput(&out);
generic_block<IQCorrector>::_block_init = true;
}
void setInput(stream<complex_t>* in) {
assert(generic_block<IQCorrector>::_block_init);
std::lock_guard<std::mutex> lck(generic_block<IQCorrector>::ctrlMtx);
generic_block<IQCorrector>::tempStop();
generic_block<IQCorrector>::unregisterInput(_in);
_in = in;
generic_block<IQCorrector>::registerInput(_in);
generic_block<IQCorrector>::tempStart();
}
void setCorrectionRate(float rate) {
correctionRate = rate;
}
int run() {
int count = _in->read();
if (count < 0) { return -1; }
if (bypass) {
memcpy(out.writeBuf, _in->readBuf, count * sizeof(complex_t));
_in->flush();
if (!out.swap(count)) { return -1; }
return count;
}
for (int i = 0; i < count; i++) {
out.writeBuf[i] = _in->readBuf[i] - offset;
offset = offset + (out.writeBuf[i] * correctionRate);
}
_in->flush();
if (!out.swap(count)) { return -1; }
return count;
}
stream<complex_t> out;
// TEMPORARY FOR DEBUG PURPOSES
bool bypass = false;
complex_t offset;
private:
stream<complex_t>* _in;
float correctionRate = 0.00001;
};
}

View File

@ -15,6 +15,10 @@ namespace credits {
void show() {
ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(20.0f, 20.0f));
ImGui::PushStyleColor(ImGuiCol_Border, ImVec4(0,0,0,0));
ImVec2 dispSize = ImGui::GetIO().DisplaySize;
ImVec2 center = ImVec2(dispSize.x/2.0f, dispSize.y/2.0f);
ImGui::SetNextWindowPos(center, ImGuiCond_Always, ImVec2(0.5f, 0.5f));
ImGui::OpenPopup("Credits");
ImGui::BeginPopupModal("Credits", NULL, ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoMove);
@ -55,11 +59,8 @@ namespace credits {
ImGui::Spacing();
ImGui::Text("SDR++ v" VERSION_STR " (Built at " __TIME__ ", " __DATE__ ")");
ImVec2 dispSize = ImGui::GetIO().DisplaySize;
ImVec2 winSize = ImGui::GetWindowSize();
ImGui::SetWindowPos(ImVec2(std::round((dispSize.x/2) - (winSize.x/2)), std::round((dispSize.y/2) - (winSize.y/2))));
ImGui::EndPopup();
ImGui::PopStyleVar(1);
ImGui::PopStyleColor();
ImGui::PopStyleVar();
}
}

View File

@ -404,9 +404,11 @@ void MainWindow::draw() {
ImGui::SameLine();
ImGui::SetCursorPosX(ImGui::GetWindowSize().x - 387);
int snrWidth = std::min<int>(300, ImGui::GetWindowSize().x - ImGui::GetCursorPosX() - 87);
ImGui::SetCursorPosX(ImGui::GetWindowSize().x - (snrWidth+87));
ImGui::SetCursorPosY(ImGui::GetCursorPosY() + 5);
ImGui::SetNextItemWidth(300);
ImGui::SetNextItemWidth(snrWidth);
ImGui::SNRMeter((vfo != NULL) ? gui::waterfall.selectedVFOSNR : 0);
ImGui::SameLine();

View File

@ -108,7 +108,7 @@ namespace displaymenu {
ImGui::SameLine();
ImGui::SetNextItemWidth(menuWidth - ImGui::GetCursorPosX());
if (ImGui::InputInt("##sdrpp_fft_rate", &fftRate, 1, 10)) {
std::clamp<int>(fftRate, 1, 200);
fftRate = std::max<int>(1, fftRate);
sigpath::signalPath.setFFTRate(fftRate);
core::configManager.acquire();
core::configManager.conf["fftRate"] = fftRate;

View File

@ -12,6 +12,7 @@ namespace sourecmenu {
double customOffset = 0.0;
double effectiveOffset = 0.0;
int decimationPower = 0;
bool iqCorrection = false;
EventHandler<std::string> sourceRegisteredHandler;
EventHandler<std::string> sourceUnregisterHandler;
@ -124,6 +125,8 @@ namespace sourecmenu {
customOffset = core::configManager.conf["offset"];
offsetMode = core::configManager.conf["offsetMode"];
decimationPower = core::configManager.conf["decimationPower"];
iqCorrection = core::configManager.conf["iqCorrection"];
sigpath::signalPath.setIQCorrection(iqCorrection);
updateOffset();
refreshSources();
@ -158,6 +161,13 @@ namespace sourecmenu {
sigpath::sourceManager.showSelectedMenu();
if (ImGui::Checkbox("IQ Correction##_sdrpp_iq_corr", &iqCorrection)) {
sigpath::signalPath.setIQCorrection(iqCorrection);
core::configManager.acquire();
core::configManager.conf["iqCorrection"] = iqCorrection;
core::configManager.release(true);
}
ImGui::Text("Offset mode");
ImGui::SameLine();
ImGui::SetNextItemWidth(itemWidth - ImGui::GetCursorPosX());

View File

@ -362,7 +362,7 @@ namespace ImGui {
// If the mouse wheel is moved on the frequency scale
if (mouseWheel != 0 && mouseInFreq) {
viewOffset -= (double)mouseWheel * viewBandwidth / 20.0;
viewOffset -= (double)mouseWheel * viewBandwidth / 20.0;
if (viewOffset + (viewBandwidth / 2.0) > wholeBandwidth / 2.0) {
double freqOffset = (viewOffset + (viewBandwidth / 2.0)) - (wholeBandwidth / 2.0);
@ -520,7 +520,7 @@ namespace ImGui {
float pixel;
float dataRange = waterfallMax - waterfallMin;
int count = std::min<float>(waterfallHeight, fftLines);
if (rawFFTs != NULL) {
if (rawFFTs != NULL && fftLines >= 0) {
for (int i = 0; i < count; i++) {
drawDataSize = (viewBandwidth / wholeBandwidth) * rawFFTSize;
drawDataStart = (((double)rawFFTSize / 2.0) * (offsetRatio + 1)) - (drawDataSize / 2);
@ -530,6 +530,12 @@ namespace ImGui {
waterfallFb[(i * dataWidth) + j] = waterfallPallet[(int)(pixel * (WATERFALL_RESOLUTION - 1))];
}
}
for (int i = count; i < waterfallHeight; i++) {
for (int j = 0; j < dataWidth; j++) {
waterfallFb[(i * dataWidth) + j] = (uint32_t)255 << 24;
}
}
}
delete[] tempData;
waterfallUpdate = true;
@ -1028,15 +1034,16 @@ namespace ImGui {
void WaterFall::setRawFFTSize(int size, bool lock) {
std::lock_guard<std::mutex> lck(buf_mtx);
rawFFTSize = size;
int wfSize = std::max<int>(1, waterfallHeight);
if (rawFFTs != NULL) {
int wfSize = std::max<int>(1, waterfallHeight);
rawFFTs = (float*)realloc(rawFFTs, rawFFTSize * wfSize * sizeof(float));
}
else {
int wfSize = std::max<int>(1, waterfallHeight);
rawFFTs = (float*)malloc(rawFFTSize * wfSize * sizeof(float));
}
fftLines = 0;
memset(rawFFTs, 0, rawFFTSize * waterfallHeight * sizeof(float));
updateWaterfallFb();
}
void WaterFall::setBandPlanPos(int pos) {

View File

@ -16,6 +16,7 @@ void SignalPath::init(uint64_t sampleRate, int fftRate, int fftSize, dsp::stream
// split.init(input);
inputBuffer.init(input);
corrector.init(&inputBuffer.out, 50.0f / sampleRate);
split.init(&inputBuffer.out);
reshape.init(&fftStream, fftSize, (sampleRate / fftRate) - fftSize);
@ -43,6 +44,8 @@ void SignalPath::setSampleRate(double sampleRate) {
vfo.vfo->start();
}
corrector.setCorrectionRate(50.0f / sampleRate);
split.start();
}
@ -55,6 +58,7 @@ void SignalPath::start() {
decimator->start();
}
inputBuffer.start();
if (iqCorrection) { corrector.start(); }
split.start();
reshape.start();
fftHandlerSink.start();
@ -66,6 +70,7 @@ void SignalPath::stop() {
decimator->stop();
}
inputBuffer.stop();
if (iqCorrection) { corrector.stop(); }
split.stop();
reshape.stop();
fftHandlerSink.stop();
@ -159,7 +164,13 @@ void SignalPath::setDecimation(int dec) {
// If no decimation, reconnect
if (!dec) {
split.setInput(&inputBuffer.out);
if (iqCorrection) {
split.setInput(&corrector.out);
}
else {
split.setInput(&inputBuffer.out);
}
if (running) { split.start(); }
core::setInputSampleRate(sourceSampleRate);
return;
@ -167,7 +178,17 @@ void SignalPath::setDecimation(int dec) {
// Create new decimators
for (int i = 0; i < dec; i++) {
dsp::HalfDecimator<dsp::complex_t>* decimator = new dsp::HalfDecimator<dsp::complex_t>((i == 0) ? &inputBuffer.out : &decimators[i-1]->out, &halfBandWindow);
dsp::HalfDecimator<dsp::complex_t>* decimator;
if (iqCorrection && i == 0) {
decimator = new dsp::HalfDecimator<dsp::complex_t>(&corrector.out, &halfBandWindow);
}
else if (i == 0) {
decimator = new dsp::HalfDecimator<dsp::complex_t>(&inputBuffer.out, &halfBandWindow);
}
else {
decimator = new dsp::HalfDecimator<dsp::complex_t>(&decimators[i-1]->out, &halfBandWindow);
}
if (running) { decimator->start(); }
decimators.push_back(decimator);
}
@ -176,4 +197,33 @@ void SignalPath::setDecimation(int dec) {
// Update the DSP sample rate
core::setInputSampleRate(sourceSampleRate);
}
void SignalPath::setIQCorrection(bool enabled) {
if (iqCorrection == enabled) { return; }
if (!iqCorrection && enabled) {
if (decimation) {
decimators[0]->setInput(&corrector.out);
}
else {
split.setInput(&corrector.out);
}
if (running) { corrector.start(); }
}
else if (iqCorrection && !enabled) {
if (running) { corrector.stop(); }
if (decimation) {
decimators[0]->setInput(&inputBuffer.out);
}
else {
split.setInput(&inputBuffer.out);
}
}
iqCorrection = enabled;
if (!enabled) {
corrector.offset.re = 0;
corrector.offset.im = 0;
}
}

View File

@ -4,6 +4,7 @@
#include <map>
#include <dsp/sink.h>
#include <dsp/decimation.h>
#include <dsp/correction.h>
class SignalPath {
public:
@ -24,6 +25,7 @@ public:
void stopFFT();
void setBuffering(bool enabled);
void setDecimation(int dec);
void setIQCorrection(bool enabled);
dsp::SampleFrameBuffer<dsp::complex_t> inputBuffer;
double sourceSampleRate = 0;
@ -36,6 +38,7 @@ private:
};
dsp::Splitter<dsp::complex_t> split;
dsp::IQCorrector corrector;
// FFT
dsp::stream<dsp::complex_t> fftStream;
@ -53,4 +56,5 @@ private:
int inputBlockSize;
bool bufferingEnabled = false;
bool running = false;
bool iqCorrection = false;
};

View File

@ -1,3 +1,3 @@
#pragma once
#define VERSION_STR "1.0.0_rc1"
#define VERSION_STR "1.0.0_rc2"

View File

@ -198,19 +198,20 @@ private:
ImGui::EndTable();
if (strlen(nameBuf) == 0) { style::beginDisabled(); }
bool applyDisabled = (strlen(nameBuf) == 0) || (bookmarks.find(editedBookmarkName) != bookmarks.end() && editedBookmarkName != firstEditedBookmarkName);
if (applyDisabled) { style::beginDisabled(); }
if (ImGui::Button("Apply")) {
open = false;
// If editing, delete the original one
if (editOpen) {
bookmarks.erase(firstEeditedBookmarkName);
bookmarks.erase(firstEditedBookmarkName);
}
bookmarks[nameBuf] = editedBookmark;
bookmarks[editedBookmarkName] = editedBookmark;
saveByName(selectedListName);
}
if (strlen(nameBuf) == 0) { style::endDisabled(); }
if (applyDisabled) { style::endDisabled(); }
ImGui::SameLine();
if (ImGui::Button("Cancel")) {
open = false;
@ -463,7 +464,7 @@ private:
_this->editedBookmark.selected = false;
_this->editOpen = true;
_this->createOpen = true;
// Find new unique default name
if (_this->bookmarks.find("New Bookmark") == _this->bookmarks.end()) {
@ -492,7 +493,7 @@ private:
_this->editOpen = true;
_this->editedBookmark = _this->bookmarks[selectedNames[0]];
_this->editedBookmarkName = selectedNames[0];
_this->firstEeditedBookmarkName = selectedNames[0];
_this->firstEditedBookmarkName = selectedNames[0];
}
if (selectedNames.size() != 1 && _this->selectedListName != "") { style::endDisabled(); }
@ -679,7 +680,9 @@ private:
std::string hoveredBookmarkName;
if (_this->bookmarkDisplayMode == BOOKMARK_DISP_MODE_TOP) {
for (auto const bm : _this->waterfallBookmarks) {
int count = _this->waterfallBookmarks.size();
for (int i = count-1; i >= 0; i--) {
auto& bm = _this->waterfallBookmarks[i];
double centerXpos = args.fftRectMin.x + std::round((bm.bookmark.frequency - args.lowFreq) * args.freqToPixelRatio);
ImVec2 nameSize = ImGui::CalcTextSize(bm.bookmarkName.c_str());
ImVec2 rectMin = ImVec2(centerXpos-(nameSize.x/2)-5, args.fftRectMin.y);
@ -696,7 +699,9 @@ private:
}
}
else if (_this->bookmarkDisplayMode == BOOKMARK_DISP_MODE_BOTTOM) {
for (auto const bm : _this->waterfallBookmarks) {
int count = _this->waterfallBookmarks.size();
for (int i = count-1; i >= 0; i--) {
auto& bm = _this->waterfallBookmarks[i];
double centerXpos = args.fftRectMin.x + std::round((bm.bookmark.frequency - args.lowFreq) * args.freqToPixelRatio);
ImVec2 nameSize = ImGui::CalcTextSize(bm.bookmarkName.c_str());
ImVec2 rectMin = ImVec2(centerXpos-(nameSize.x/2)-5, args.fftRectMax.y-nameSize.y);
@ -808,7 +813,7 @@ private:
std::map<std::string, FrequencyBookmark> bookmarks;
std::string editedBookmarkName = "";
std::string firstEeditedBookmarkName = "";
std::string firstEditedBookmarkName = "";
FrequencyBookmark editedBookmark;
std::vector<std::string> listNames;
@ -861,5 +866,6 @@ MOD_EXPORT void _DELETE_INSTANCE_(void* instance) {
}
MOD_EXPORT void _END_() {
// Nothing here
config.disableAutoSave();
config.save();
}

View File

@ -10,6 +10,8 @@
#include <meteor_demodulator_interface.h>
#include <config.h>
#include <options.h>
#include <cctype>
#include <radio_interface.h>
#define CONCAT(a, b) ((std::string(a) + b).c_str())
#define MAX_COMMAND_LENGTH 8192
@ -360,374 +362,248 @@ private:
// NOTE: THIS STUFF ISN'T THREADSAFE AND WILL LIKELY BREAK.
// Execute commands
// If the command is empty, do nothing
if (parts.size() == 0) { return; }
else if (parts[0].at(0) == '\\') { // Check to see if command is longform
parts[0].replace(0,1,""); // Remove leading backslash
if (parts[0] == "set_freq") {
std::lock_guard lck(vfoMtx);
// if number of arguments isn't correct, return error
if (parts.size() != 2) {
spdlog::error("Rigctl client sent invalid command: '{0}'", cmd);
resp = "RPRT 1\n";
client->write(resp.size(), (uint8_t*)resp.c_str());
return;
}
// If not controlling the VFO, return
if (!tuningEnabled) {
resp = "RPRT 0\n";
client->write(resp.size(), (uint8_t*)resp.c_str());
return;
}
// Parse frequency and assign it to the VFO
long long freq = std::stoll(parts[1]);
tuner::tune(tuner::TUNER_MODE_NORMAL, selectedVfo, freq);
resp = "RPRT 0\n";
client->write(resp.size(), (uint8_t*)resp.c_str());
// If the command is a compound command, execute each one separately
if (parts[0].size() > 1 && parts[0][0] != '\\') {
std::string arguments;
if (parts.size() > 1) { arguments = cmd.substr(parts[0].size()); }
for (char c : parts[0]) {
commandHandler(c + arguments);
}
else if (parts[0] == "get_freq") {
std::lock_guard lck(vfoMtx);
return;
}
// Get center frequency of the SDR
double freq = gui::waterfall.getCenterFrequency();
spdlog::info("Rigctl command: '{0}'", cmd);
// Add the offset of the VFO if it exists
if (sigpath::vfoManager.vfoExists(selectedVfo)) {
freq += sigpath::vfoManager.getOffset(selectedVfo);
}
// Otherwise, execute the command
if (parts[0] == "F" || parts[0] == "\\set_freq") {
std::lock_guard lck(vfoMtx);
// Respond with the frequency
char buf[128];
sprintf(buf, "%" PRIu64 "\n", (uint64_t)freq);
client->write(strlen(buf), (uint8_t*)buf);
}
else if (parts[0] == "set_mode") {
std::lock_guard lck(vfoMtx);
// if number of arguments isn't correct, return error
if (parts.size() != 3) {
spdlog::error("Rigctl client sent invalid command: '{0}'", cmd);
resp = "RPRT 1\n";
client->write(resp.size(), (uint8_t*)resp.c_str());
return;
}
// If not controlling the VFO, return
if (!tuningEnabled) {
resp = "RPRT 0\n";
client->write(resp.size(), (uint8_t*)resp.c_str());
return;
}
// If client is querying, respond accordingly
if(parts[1] == "?") {
resp = "FM WFM AM DSB USB CW LSB RAW";
client->write(resp.size(), (uint8_t*)resp.c_str());
return;
}
// Parse mode and bandwidth
int mode;
if(parts[1] == "FM"){
mode = RADIO_IFACE_MODE_NFM;
}else if(parts[1] == "WFM"){
mode = RADIO_IFACE_MODE_WFM;
}else if(parts[1] == "AM"){
mode = RADIO_IFACE_MODE_AM;
}else if(parts[1] == "DSB"){
mode = RADIO_IFACE_MODE_DSB;
}else if(parts[1] == "USB"){
mode = RADIO_IFACE_MODE_USB;
}else if(parts[1] == "CW"){
mode = RADIO_IFACE_MODE_CW;
}else if(parts[1] == "LSB"){
mode = RADIO_IFACE_MODE_LSB;
}else if(parts[1] == "RAW"){
mode = RADIO_IFACE_MODE_RAW;
}else{
// If mode is not supported, return error
spdlog::error("Rigctl client sent invalid command: '{0}'", cmd);
resp = "RPRT 1\n";
client->write(resp.size(), (uint8_t*)resp.c_str());
return;
}
int bandwidth = std::stoi(parts[2]);
// Set mode and bandwidth and respond
core::modComManager.callInterface(selectedVfo, RADIO_IFACE_CMD_SET_MODE, &mode, 0);
sigpath::vfoManager.setBandwidth(selectedVfo, bandwidth, true);
resp = "RPRT 0\n";
client->write(resp.size(), (uint8_t*)resp.c_str());
}
else if (parts[0] == "get_mode") {
std::lock_guard lck(vfoMtx);
// Initialize output stream
std::stringstream buf;
// Get mode enum and parse to the output stream
int mode;
core::modComManager.callInterface(selectedVfo, RADIO_IFACE_CMD_GET_MODE, 0, &mode);
switch(mode) {
case RADIO_IFACE_MODE_NFM : buf << "FM\n";
break;
case RADIO_IFACE_MODE_WFM : buf << "WFM\n";
break;
case RADIO_IFACE_MODE_AM : buf << "AM\n";
break;
case RADIO_IFACE_MODE_DSB : buf << "DSB\n";
break;
case RADIO_IFACE_MODE_USB : buf << "USB\n";
break;
case RADIO_IFACE_MODE_CW : buf << "CW\n";
break;
case RADIO_IFACE_MODE_LSB : buf << "LSB\n";
break;
case RADIO_IFACE_MODE_RAW : buf << "RAW\n";
break;
}
// Send bandwidth to output stream and respond
buf << sigpath::vfoManager.getBandwidth(selectedVfo) << "\n";
resp = buf.str();
client->write(resp.size(), (uint8_t*)resp.c_str());
}
else if (parts[0] == "set_vfo") {
// if number of arguments isn't correct, return error
if (parts.size() != 2) {
spdlog::error("Rigctl client sent invalid command: '{0}'", cmd);
resp = "RPRT 1\n";
client->write(resp.size(), (uint8_t*)resp.c_str());
return;
}
// Respond
resp = "RPRT 0\n";
client->write(resp.size(), (uint8_t*)resp.c_str());
}
else if (parts[0] == "get_vfo") {
// Respond with VFO
resp = "VFO\n";
client->write(resp.size(), (uint8_t*)resp.c_str());
}
else if (parts[0] == "recorder_start") {
std::lock_guard lck(recorderMtx);
// If not controlling the recorder, return
if (!recordingEnabled) {
resp = "RPRT 0\n";
client->write(resp.size(), (uint8_t*)resp.c_str());
return;
}
// Send the command to the selected recorder
if (recorderType == RECORDER_TYPE_METEOR_DEMODULATOR) {
core::modComManager.callInterface(selectedRecorder, METEOR_DEMODULATOR_IFACE_CMD_START, NULL, NULL);
}
else {
core::modComManager.callInterface(selectedRecorder, RECORDER_IFACE_CMD_START, NULL, NULL);
}
// Respond with a sucess
resp = "RPRT 0\n";
client->write(resp.size(), (uint8_t*)resp.c_str());
}
else if (parts[0] == "recorder_stop") {
std::lock_guard lck(recorderMtx);
// If not controlling the recorder, return
if (!recordingEnabled) {
resp = "RPRT 0\n";
client->write(resp.size(), (uint8_t*)resp.c_str());
return;
}
// Send the command to the selected recorder
if (recorderType == RECORDER_TYPE_METEOR_DEMODULATOR) {
core::modComManager.callInterface(selectedRecorder, METEOR_DEMODULATOR_IFACE_CMD_STOP, NULL, NULL);
}
else {
core::modComManager.callInterface(selectedRecorder, RECORDER_IFACE_CMD_STOP, NULL, NULL);
}
// Respond with a sucess
resp = "RPRT 0\n";
client->write(resp.size(), (uint8_t*)resp.c_str());
}
else if (parts[0] == "quit") {
// Will close automatically
}
else {
// If command is not recognized, return error
spdlog::error("Rigctl client sent invalid command: '{0}'", cmd);
// if number of arguments isn't correct, return error
if (parts.size() != 2) {
resp = "RPRT 1\n";
client->write(resp.size(), (uint8_t*)resp.c_str());
return;
}
// If not controlling the VFO, return
if (!tuningEnabled) {
resp = "RPRT 0\n";
client->write(resp.size(), (uint8_t*)resp.c_str());
}
// Parse frequency and assign it to the VFO
long long freq = std::stoll(parts[1]);
tuner::tune(tuner::TUNER_MODE_NORMAL, selectedVfo, freq);
resp = "RPRT 0\n";
client->write(resp.size(), (uint8_t*)resp.c_str());
}
else {
for(int i = 0; i < parts[0].length(); i++){ // Loop adds support for compound commands
if (parts[0].at(i) == 'F') {
std::lock_guard lck(vfoMtx);
else if (parts[0] == "f" || parts[0] == "\\get_freq") {
std::lock_guard lck(vfoMtx);
// if number of arguments isn't correct, return error
if (parts.size() != 2) {
spdlog::error("Rigctl client sent invalid command: '{0}'", cmd);
resp = "RPRT 1\n";
client->write(resp.size(), (uint8_t*)resp.c_str());
return;
}
// Get center frequency of the SDR
double freq = gui::waterfall.getCenterFrequency();
// If not controlling the VFO, return
if (!tuningEnabled) {
resp = "RPRT 0\n";
client->write(resp.size(), (uint8_t*)resp.c_str());
return;
}
// Add the offset of the VFO if it exists
if (sigpath::vfoManager.vfoExists(selectedVfo)) {
freq += sigpath::vfoManager.getOffset(selectedVfo);
}
// Parse frequency and assign it to the VFO
long long freq = std::stoll(parts[1]);
tuner::tune(tuner::TUNER_MODE_NORMAL, selectedVfo, freq);
resp = "RPRT 0\n";
client->write(resp.size(), (uint8_t*)resp.c_str());
}
else if (parts[0].at(i) == 'f') {
std::lock_guard lck(vfoMtx);
// Respond with the frequency
char buf[128];
sprintf(buf, "%" PRIu64 "\n", (uint64_t)freq);
client->write(strlen(buf), (uint8_t*)buf);
}
else if (parts[0] == "M" || parts[0] == "\\set_mode") {
std::lock_guard lck(vfoMtx);
resp = "RPRT 0\n";
// Get center frequency of the SDR
double freq = gui::waterfall.getCenterFrequency();
// If client is querying, respond accordingly
if (parts.size() >= 2 && parts[1] == "?") {
resp = "FM WFM AM DSB USB CW LSB RAW\n";
client->write(resp.size(), (uint8_t*)resp.c_str());
return;
}
// Add the offset of the VFO if it exists
if (sigpath::vfoManager.vfoExists(selectedVfo)) {
freq += sigpath::vfoManager.getOffset(selectedVfo);
}
// if number of arguments isn't correct or the VFO is not "VFO", return error
if (parts.size() != 3) {
resp = "RPRT 1\n";
client->write(resp.size(), (uint8_t*)resp.c_str());
return;
}
// Respond with the frequency
char buf[128];
sprintf(buf, "%" PRIu64 "\n", (uint64_t)freq);
client->write(strlen(buf), (uint8_t*)buf);
}
else if (parts[0].at(i) == 'M') {
std::lock_guard lck(vfoMtx);
// if number of arguments isn't correct, return error
if (parts.size() != 3) {
spdlog::error("Rigctl client sent invalid command: '{0}'", cmd);
resp = "RPRT 1\n";
client->write(resp.size(), (uint8_t*)resp.c_str());
return;
}
// If not controlling the VFO, return
if (!tuningEnabled) {
resp = "RPRT 0\n";
client->write(resp.size(), (uint8_t*)resp.c_str());
return;
}
// If client is querying, respond accordingly
if(parts[1] == "?") {
resp = "FM WFM AM DSB USB CW LSB RAW";
client->write(resp.size(), (uint8_t*)resp.c_str());
return;
}
// Parse mode and bandwidth
int mode;
if(parts[1] == "FM"){
mode = RADIO_IFACE_MODE_NFM;
}else if(parts[1] == "WFM"){
mode = RADIO_IFACE_MODE_WFM;
}else if(parts[1] == "AM"){
mode = RADIO_IFACE_MODE_AM;
}else if(parts[1] == "DSB"){
mode = RADIO_IFACE_MODE_DSB;
}else if(parts[1] == "USB"){
mode = RADIO_IFACE_MODE_USB;
}else if(parts[1] == "CW"){
mode = RADIO_IFACE_MODE_CW;
}else if(parts[1] == "LSB"){
mode = RADIO_IFACE_MODE_LSB;
}else if(parts[1] == "RAW"){
mode = RADIO_IFACE_MODE_RAW;
}else{
// If mode is not supported, return error
spdlog::error("Rigctl client sent invalid command: '{0}'", cmd);
resp = "RPRT 1\n";
client->write(resp.size(), (uint8_t*)resp.c_str());
return;
}
int bandwidth = std::stoi(parts[2]);
// Set mode and bandwidth and respond
core::modComManager.callInterface(selectedVfo, RADIO_IFACE_CMD_SET_MODE, &mode, 0);
sigpath::vfoManager.setBandwidth(selectedVfo, bandwidth, true);
resp = "RPRT 0\n";
client->write(resp.size(), (uint8_t*)resp.c_str());
}
else if (parts[0].at(i) == 'm') {
std::lock_guard lck(vfoMtx);
// Initialize output stream
std::stringstream buf;
// Get mode enum and parse to the output stream
int mode;
core::modComManager.callInterface(selectedVfo, RADIO_IFACE_CMD_GET_MODE, 0, &mode);
switch(mode) {
case RADIO_IFACE_MODE_NFM : buf << "FM\n";
break;
case RADIO_IFACE_MODE_WFM : buf << "WFM\n";
break;
case RADIO_IFACE_MODE_AM : buf << "AM\n";
break;
case RADIO_IFACE_MODE_DSB : buf << "DSB\n";
break;
case RADIO_IFACE_MODE_USB : buf << "USB\n";
break;
case RADIO_IFACE_MODE_CW : buf << "CW\n";
break;
case RADIO_IFACE_MODE_LSB : buf << "LSB\n";
break;
case RADIO_IFACE_MODE_RAW : buf << "RAW\n";
break;
}
// Send bandwidth to output stream and respond
buf << sigpath::vfoManager.getBandwidth(selectedVfo) << "\n";
resp = buf.str();
client->write(resp.size(), (uint8_t*)resp.c_str());
}
else if (parts[0].at(i) == 'V') {
// if number of arguments isn't correct, return error
if (parts.size() != 3) {
spdlog::error("Rigctl client sent invalid command: '{0}'", cmd);
resp = "RPRT 1\n";
client->write(resp.size(), (uint8_t*)resp.c_str());
return;
}
// Respond
resp = "RPRT 0\n";
client->write(resp.size(), (uint8_t*)resp.c_str());
}
else if (parts[0].at(i) == 'v') {
// Respond with VFO
resp = "VFO\n";
client->write(resp.size(), (uint8_t*)resp.c_str());
}
else if (parts[0].at(i) == 'q') {
// Will close automatically
}
else {
// If command is not recognized, return error
spdlog::error("Rigctl client sent invalid command: '{0}'", cmd);
// Check that the bandwidth is an integer
for (char c : parts[2]) {
if (!std::isdigit(c)) {
resp = "RPRT 1\n";
client->write(resp.size(), (uint8_t*)resp.c_str());
return;
}
}
float newBandwidth = std::atoi(parts[2].c_str());
int newMode;
if (parts[1] == "FM") {
newMode = RADIO_IFACE_MODE_NFM;
}
else if (parts[1] == "WFM") {
newMode = RADIO_IFACE_MODE_WFM;
}
else if (parts[1] == "AM") {
newMode = RADIO_IFACE_MODE_AM;
}
else if (parts[1] == "DSB") {
newMode = RADIO_IFACE_MODE_DSB;
}
else if (parts[1] == "USB") {
newMode = RADIO_IFACE_MODE_USB;
}
else if (parts[1] == "CW") {
newMode = RADIO_IFACE_MODE_CW;
}
else if (parts[1] == "LSB") {
newMode = RADIO_IFACE_MODE_LSB;
}
else if (parts[1] == "RAW") {
newMode = RADIO_IFACE_MODE_RAW;
}
else {
resp = "RPRT 1\n";
client->write(resp.size(), (uint8_t*)resp.c_str());
return;
}
// If tuning is enabled, set the mode and optionally the bandwidth
if (!selectedVfo.empty() && core::modComManager.getModuleName(selectedVfo) == "radio" && tuningEnabled) {
core::modComManager.callInterface(selectedVfo, RADIO_IFACE_CMD_SET_MODE, &newMode, NULL);
if (newBandwidth) {
core::modComManager.callInterface(selectedVfo, RADIO_IFACE_CMD_SET_BANDWIDTH, &newBandwidth, NULL);
}
}
client->write(resp.size(), (uint8_t*)resp.c_str());
}
else if (parts[0] == "m" || parts[0] == "\\get_mode") {
std::lock_guard lck(vfoMtx);
resp = "RAW ";
if (!selectedVfo.empty() && core::modComManager.getModuleName(selectedVfo) == "radio") {
int mode;
core::modComManager.callInterface(selectedVfo, RADIO_IFACE_CMD_GET_MODE, NULL, &mode);
if (mode == RADIO_IFACE_MODE_NFM) {
resp = "FM ";
}
else if (mode == RADIO_IFACE_MODE_WFM) {
resp = "WFM ";
}
else if (mode == RADIO_IFACE_MODE_AM) {
resp = "AM ";
}
else if (mode == RADIO_IFACE_MODE_DSB) {
resp = "DSB ";
}
else if (mode == RADIO_IFACE_MODE_USB) {
resp = "USB ";
}
else if (mode == RADIO_IFACE_MODE_CW) {
resp = "CW ";
}
else if (mode == RADIO_IFACE_MODE_LSB) {
resp = "LSB ";
}
}
if (!selectedVfo.empty()) {
resp += std::to_string((int)sigpath::vfoManager.getBandwidth(selectedVfo)) + "\n";
}
else {
resp += "0\n";
}
client->write(resp.size(), (uint8_t*)resp.c_str());
}
else if (parts[0] == "V" || parts[0] == "\\set_vfo") {
std::lock_guard lck(vfoMtx);
resp = "RPRT 0\n";
// if number of arguments isn't correct or the VFO is not "VFO", return error
if (parts.size() != 2) {
resp = "RPRT 1\n";
client->write(resp.size(), (uint8_t*)resp.c_str());
return;
}
if (parts[1] == "?") {
resp = "VFO\n";
}
else if (parts[1] != "VFO") {
resp = "RPRT 1\n";
}
client->write(resp.size(), (uint8_t*)resp.c_str());
}
else if (parts[0] == "v" || parts[0] == "\\get_vfo") {
std::lock_guard lck(vfoMtx);
resp = "VFO\n";
client->write(resp.size(), (uint8_t*)resp.c_str());
}
else if (parts[0] == "\\recorder_start") {
std::lock_guard lck(recorderMtx);
// If not controlling the recorder, return
if (!recordingEnabled) {
resp = "RPRT 0\n";
client->write(resp.size(), (uint8_t*)resp.c_str());
return;
}
// Send the command to the selected recorder
if (recorderType == RECORDER_TYPE_METEOR_DEMODULATOR) {
core::modComManager.callInterface(selectedRecorder, METEOR_DEMODULATOR_IFACE_CMD_START, NULL, NULL);
}
else {
core::modComManager.callInterface(selectedRecorder, RECORDER_IFACE_CMD_START, NULL, NULL);
}
// Respond with a sucess
resp = "RPRT 0\n";
client->write(resp.size(), (uint8_t*)resp.c_str());
}
else if (parts[0] == "\\recorder_stop") {
std::lock_guard lck(recorderMtx);
// If not controlling the recorder, return
if (!recordingEnabled) {
resp = "RPRT 0\n";
client->write(resp.size(), (uint8_t*)resp.c_str());
return;
}
// Send the command to the selected recorder
if (recorderType == RECORDER_TYPE_METEOR_DEMODULATOR) {
core::modComManager.callInterface(selectedRecorder, METEOR_DEMODULATOR_IFACE_CMD_STOP, NULL, NULL);
}
else {
core::modComManager.callInterface(selectedRecorder, RECORDER_IFACE_CMD_STOP, NULL, NULL);
}
// Respond with a sucess
resp = "RPRT 0\n";
client->write(resp.size(), (uint8_t*)resp.c_str());
}
else if (parts[0] == "quit") {
// Will close automatically
}
else {
// If command is not recognized, return error
spdlog::error("Rigctl client sent invalid command: '{0}'", cmd);
resp = "RPRT 1\n";
client->write(resp.size(), (uint8_t*)resp.c_str());
return;
}
}

View File

@ -68,7 +68,7 @@ public:
directSamplingMode = config.conf["directSamplingMode"];
rtlAGC = config.conf["rtlAGC"];
tunerAGC = config.conf["tunerAGC"];
gain = config.conf["gainIndex"];
gain = std::clamp<int>(config.conf["gainIndex"], 0, 28);
biasTee = config.conf["biasTee"];
offsetTuning = config.conf["offsetTuning"];
hostStr = hostStr.substr(0, 1023);
@ -127,12 +127,20 @@ private:
spdlog::warn("Setting sample rate to {0}", _this->sampleRate);
_this->client.setFrequency(_this->freq);
_this->client.setSampleRate(_this->sampleRate);
_this->client.setGainMode(!_this->tunerAGC);
_this->client.setDirectSampling(_this->directSamplingMode);
_this->client.setAGCMode(_this->rtlAGC);
_this->client.setGainIndex(_this->gain);
_this->client.setBiasTee(_this->biasTee);
_this->client.setOffsetTuning(_this->offsetTuning);
if (_this->tunerAGC) {
_this->client.setGainMode(0);
}
else {
_this->client.setGainMode(1);
// Setting it twice because for some reason it refuses to do it on the first time
_this->client.setGainIndex(_this->gain);
}
_this->running = true;
_this->workerThread = std::thread(worker, _this);
spdlog::info("RTLTCPSourceModule '{0}': Start!", _this->name);
@ -229,7 +237,7 @@ private:
if (_this->tunerAGC) { style::beginDisabled(); }
ImGui::SetNextItemWidth(menuWidth);
if (ImGui::SliderInt(CONCAT("##_gain_select_", _this->name), &_this->gain, 0, 29, "")) {
if (ImGui::SliderInt(CONCAT("##_gain_select_", _this->name), &_this->gain, 0, 28, "")) {
if (_this->running) {
_this->client.setGainIndex(_this->gain);
}