diff --git a/core/src/config.cpp b/core/src/config.cpp index 83a6fe1..a638dbd 100644 --- a/core/src/config.cpp +++ b/core/src/config.cpp @@ -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 lock(termMtx); - autoSaveEnabled = false; - termFlag = true; - } - termCond.notify_one(); - if (autoSaveThread.joinable()) { autoSaveThread.join(); } + if (!autoSaveEnabled) { return; } + { + std::lock_guard 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 lock(_this->termMtx); - _this->termCond.wait_for(lock, std::chrono::milliseconds(1000), [_this]() { return _this->termFlag; } ); + std::unique_lock lock(termMtx); + termCond.wait_for(lock, std::chrono::milliseconds(1000), [this]() { return termFlag; } ); } } } \ No newline at end of file diff --git a/core/src/config.h b/core/src/config.h index 0ebf6fc..e6bf10a 100644 --- a/core/src/config.h +++ b/core/src/config.h @@ -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; }; \ No newline at end of file diff --git a/core/src/core.cpp b/core/src/core.cpp index 8d60165..1553a48 100644 --- a/core/src/core.cpp +++ b/core/src/core.cpp @@ -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; } diff --git a/core/src/credits.cpp b/core/src/credits.cpp index 4fc3b94..2a65d9f 100644 --- a/core/src/credits.cpp +++ b/core/src/credits.cpp @@ -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" }; diff --git a/core/src/dsp/correction.h b/core/src/dsp/correction.h new file mode 100644 index 0000000..d785a32 --- /dev/null +++ b/core/src/dsp/correction.h @@ -0,0 +1,76 @@ +#pragma once +#include +#include +#include +#include + +namespace dsp { + class IQCorrector : public generic_block { + public: + IQCorrector() {} + + IQCorrector(stream* in, float rate) { init(in, rate); } + + void init(stream* in, float rate) { + _in = in; + correctionRate = rate; + offset.re = 0; + offset.im = 0; + generic_block::registerInput(_in); + generic_block::registerOutput(&out); + generic_block::_block_init = true; + } + + void setInput(stream* in) { + assert(generic_block::_block_init); + std::lock_guard lck(generic_block::ctrlMtx); + generic_block::tempStop(); + generic_block::unregisterInput(_in); + _in = in; + generic_block::registerInput(_in); + generic_block::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 out; + + // TEMPORARY FOR DEBUG PURPOSES + bool bypass = false; + complex_t offset; + + private: + stream* _in; + float correctionRate = 0.00001; + + + }; +} \ No newline at end of file diff --git a/core/src/gui/dialogs/credits.cpp b/core/src/gui/dialogs/credits.cpp index 3fb4731..4368346 100644 --- a/core/src/gui/dialogs/credits.cpp +++ b/core/src/gui/dialogs/credits.cpp @@ -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(); } } \ No newline at end of file diff --git a/core/src/gui/main_window.cpp b/core/src/gui/main_window.cpp index 513a646..a7c9b61 100644 --- a/core/src/gui/main_window.cpp +++ b/core/src/gui/main_window.cpp @@ -404,9 +404,11 @@ void MainWindow::draw() { ImGui::SameLine(); - ImGui::SetCursorPosX(ImGui::GetWindowSize().x - 387); + int snrWidth = std::min(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(); diff --git a/core/src/gui/menus/display.cpp b/core/src/gui/menus/display.cpp index e42cd5b..1e03e7b 100644 --- a/core/src/gui/menus/display.cpp +++ b/core/src/gui/menus/display.cpp @@ -108,7 +108,7 @@ namespace displaymenu { ImGui::SameLine(); ImGui::SetNextItemWidth(menuWidth - ImGui::GetCursorPosX()); if (ImGui::InputInt("##sdrpp_fft_rate", &fftRate, 1, 10)) { - std::clamp(fftRate, 1, 200); + fftRate = std::max(1, fftRate); sigpath::signalPath.setFFTRate(fftRate); core::configManager.acquire(); core::configManager.conf["fftRate"] = fftRate; diff --git a/core/src/gui/menus/source.cpp b/core/src/gui/menus/source.cpp index 1afe0e0..43c8b95 100644 --- a/core/src/gui/menus/source.cpp +++ b/core/src/gui/menus/source.cpp @@ -12,6 +12,7 @@ namespace sourecmenu { double customOffset = 0.0; double effectiveOffset = 0.0; int decimationPower = 0; + bool iqCorrection = false; EventHandler sourceRegisteredHandler; EventHandler 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()); diff --git a/core/src/gui/widgets/waterfall.cpp b/core/src/gui/widgets/waterfall.cpp index f33c12d..d325bc0 100644 --- a/core/src/gui/widgets/waterfall.cpp +++ b/core/src/gui/widgets/waterfall.cpp @@ -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(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 lck(buf_mtx); rawFFTSize = size; + int wfSize = std::max(1, waterfallHeight); if (rawFFTs != NULL) { - int wfSize = std::max(1, waterfallHeight); rawFFTs = (float*)realloc(rawFFTs, rawFFTSize * wfSize * sizeof(float)); } else { - int wfSize = std::max(1, waterfallHeight); rawFFTs = (float*)malloc(rawFFTSize * wfSize * sizeof(float)); } + fftLines = 0; memset(rawFFTs, 0, rawFFTSize * waterfallHeight * sizeof(float)); + updateWaterfallFb(); } void WaterFall::setBandPlanPos(int pos) { diff --git a/core/src/signal_path/dsp.cpp b/core/src/signal_path/dsp.cpp index 37f385e..269a4f0 100644 --- a/core/src/signal_path/dsp.cpp +++ b/core/src/signal_path/dsp.cpp @@ -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* decimator = new dsp::HalfDecimator((i == 0) ? &inputBuffer.out : &decimators[i-1]->out, &halfBandWindow); + dsp::HalfDecimator* decimator; + if (iqCorrection && i == 0) { + decimator = new dsp::HalfDecimator(&corrector.out, &halfBandWindow); + } + else if (i == 0) { + decimator = new dsp::HalfDecimator(&inputBuffer.out, &halfBandWindow); + } + else { + decimator = new dsp::HalfDecimator(&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; + } } \ No newline at end of file diff --git a/core/src/signal_path/dsp.h b/core/src/signal_path/dsp.h index a644850..5f12221 100644 --- a/core/src/signal_path/dsp.h +++ b/core/src/signal_path/dsp.h @@ -4,6 +4,7 @@ #include #include #include +#include class SignalPath { public: @@ -24,6 +25,7 @@ public: void stopFFT(); void setBuffering(bool enabled); void setDecimation(int dec); + void setIQCorrection(bool enabled); dsp::SampleFrameBuffer inputBuffer; double sourceSampleRate = 0; @@ -36,6 +38,7 @@ private: }; dsp::Splitter split; + dsp::IQCorrector corrector; // FFT dsp::stream fftStream; @@ -53,4 +56,5 @@ private: int inputBlockSize; bool bufferingEnabled = false; bool running = false; + bool iqCorrection = false; }; \ No newline at end of file diff --git a/core/src/version.h b/core/src/version.h index 7a2ed30..e9d9683 100644 --- a/core/src/version.h +++ b/core/src/version.h @@ -1,3 +1,3 @@ #pragma once -#define VERSION_STR "1.0.0_rc1" \ No newline at end of file +#define VERSION_STR "1.0.0_rc2" \ No newline at end of file diff --git a/frequency_manager/src/main.cpp b/frequency_manager/src/main.cpp index 7868ab0..1c7880c 100644 --- a/frequency_manager/src/main.cpp +++ b/frequency_manager/src/main.cpp @@ -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 bookmarks; std::string editedBookmarkName = ""; - std::string firstEeditedBookmarkName = ""; + std::string firstEditedBookmarkName = ""; FrequencyBookmark editedBookmark; std::vector listNames; @@ -861,5 +866,6 @@ MOD_EXPORT void _DELETE_INSTANCE_(void* instance) { } MOD_EXPORT void _END_() { - // Nothing here + config.disableAutoSave(); + config.save(); } \ No newline at end of file diff --git a/rigctl_server/src/main.cpp b/rigctl_server/src/main.cpp index 2c8b17b..1bc0018 100644 --- a/rigctl_server/src/main.cpp +++ b/rigctl_server/src/main.cpp @@ -10,6 +10,8 @@ #include #include #include +#include +#include #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; } } diff --git a/rtl_tcp_source/src/main.cpp b/rtl_tcp_source/src/main.cpp index cc0d889..745f93d 100644 --- a/rtl_tcp_source/src/main.cpp +++ b/rtl_tcp_source/src/main.cpp @@ -68,7 +68,7 @@ public: directSamplingMode = config.conf["directSamplingMode"]; rtlAGC = config.conf["rtlAGC"]; tunerAGC = config.conf["tunerAGC"]; - gain = config.conf["gainIndex"]; + gain = std::clamp(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); }