diff --git a/core/src/core.cpp b/core/src/core.cpp index bbf3a57e..3e76370c 100644 --- a/core/src/core.cpp +++ b/core/src/core.cpp @@ -187,6 +187,7 @@ int sdrpp_main(int argc, char *argv[]) { defConfig["moduleInstances"]["Frequency Manager"] = "frequency_manager"; defConfig["moduleInstances"]["Recorder"] = "recorder"; + defConfig["moduleInstances"]["Rigctl Server"] = "rigctl_server"; // Themes diff --git a/core/src/credits.cpp b/core/src/credits.cpp index 59024d03..25d4a90c 100644 --- a/core/src/credits.cpp +++ b/core/src/credits.cpp @@ -2,7 +2,6 @@ namespace sdrpp_credits { const char* contributors[] = { - "Alexandre Rouma (Author)", "Aang23", "Alexsey Shestacov", "Aosync", diff --git a/core/src/dsp/convertion.h b/core/src/dsp/convertion.h index 519d70dd..e12aa470 100644 --- a/core/src/dsp/convertion.h +++ b/core/src/dsp/convertion.h @@ -219,4 +219,127 @@ namespace dsp { stream* _in; }; + + class ComplexToInt16C : public generic_block { + public: + ComplexToInt16C() {} + + ComplexToInt16C(stream* in) { init(in); } + + void init(stream* in) { + _in = in; + 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(); + } + + int run() { + int count = _in->read(); + if (count < 0) { return -1; } + + volk_32f_s32f_convert_16i(out.writeBuf, (float*)_in->readBuf, 32768.0f, count * 2); + + _in->flush(); + if (!out.swap(count)) { return -1; } + return count; + } + + stream out; + + private: + stream* _in; + + }; + + class Int16ToFloat : public generic_block { + public: + Int16ToFloat() {} + + Int16ToFloat(stream* in) { init(in); } + + void init(stream* in) { + _in = in; + 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(); + } + + int run() { + int count = _in->read(); + if (count < 0) { return -1; } + + volk_16i_s32f_convert_32f(out.writeBuf, _in->readBuf, 32768.0f, count); + + _in->flush(); + if (!out.swap(count)) { return -1; } + return count; + } + + stream out; + + private: + stream* _in; + + }; + + class FloatToInt16 : public generic_block { + public: + FloatToInt16() {} + + FloatToInt16(stream* in) { init(in); } + + void init(stream* in) { + _in = in; + 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(); + } + + int run() { + int count = _in->read(); + if (count < 0) { return -1; } + + volk_32f_s32f_convert_16i(out.writeBuf, _in->readBuf, 32768.0f, count); + + _in->flush(); + if (!out.swap(count)) { return -1; } + return count; + } + + stream out; + + private: + stream* _in; + + }; } \ No newline at end of file diff --git a/core/src/gui/dialogs/credits.cpp b/core/src/gui/dialogs/credits.cpp index 3f4f4a30..3fb47316 100644 --- a/core/src/gui/dialogs/credits.cpp +++ b/core/src/gui/dialogs/credits.cpp @@ -27,7 +27,7 @@ namespace credits { ImGui::Spacing(); ImGui::Spacing(); - ImGui::Text("This software is brought to you by\n\n"); + ImGui::Text("This software is brought to you by Alexandre Rouma with the help of\n\n"); ImGui::Columns(3, "CreditColumns", true); @@ -53,7 +53,7 @@ namespace credits { ImGui::Spacing(); ImGui::Spacing(); ImGui::Spacing(); - ImGui::Text("SDR++ v" VERSION_STR); + ImGui::Text("SDR++ v" VERSION_STR " (Built at " __TIME__ ", " __DATE__ ")"); ImVec2 dispSize = ImGui::GetIO().DisplaySize; ImVec2 winSize = ImGui::GetWindowSize(); diff --git a/core/src/gui/main_window.cpp b/core/src/gui/main_window.cpp index cc405f90..2fd7b692 100644 --- a/core/src/gui/main_window.cpp +++ b/core/src/gui/main_window.cpp @@ -87,7 +87,7 @@ void MainWindow::init() { vfoCreatedHandler.handler = vfoAddedHandler; vfoCreatedHandler.ctx = this; - sigpath::vfoManager.vfoCreatedEvent.bindHandler(&vfoCreatedHandler); + sigpath::vfoManager.onVfoCreated.bindHandler(&vfoCreatedHandler); spdlog::info("Loading modules"); @@ -211,6 +211,8 @@ void MainWindow::init() { } initComplete = true; + + onInitComplete.emit(true); } void MainWindow::fftHandler(dsp::complex_t* samples, int count, void* ctx) { diff --git a/core/src/gui/main_window.h b/core/src/gui/main_window.h index 2e3a9416..39727b71 100644 --- a/core/src/gui/main_window.h +++ b/core/src/gui/main_window.h @@ -34,6 +34,7 @@ public: bool lockWaterfallControls = false; Event onPlayStateChange; + Event onInitComplete; private: void generateFFTWindow(int win, int size); diff --git a/core/src/gui/menus/vfo_color.cpp b/core/src/gui/menus/vfo_color.cpp index 22407596..dbb322f5 100644 --- a/core/src/gui/menus/vfo_color.cpp +++ b/core/src/gui/menus/vfo_color.cpp @@ -72,7 +72,7 @@ namespace vfo_color_menu { } vfoAddHndl.handler = vfoAddHandler; - sigpath::vfoManager.vfoCreatedEvent.bindHandler(&vfoAddHndl); + sigpath::vfoManager.onVfoCreated.bindHandler(&vfoAddHndl); core::configManager.release(modified); } diff --git a/core/src/gui/tuner.cpp b/core/src/gui/tuner.cpp index 6ea4689a..99896e50 100644 --- a/core/src/gui/tuner.cpp +++ b/core/src/gui/tuner.cpp @@ -5,7 +5,10 @@ namespace tuner { void centerTuning(std::string vfoName, double freq) { - if (vfoName != "") { sigpath::vfoManager.setOffset(vfoName, 0); } + if (vfoName != "") { + if (gui::waterfall.vfos.find(vfoName) == gui::waterfall.vfos.end()) { return; } + sigpath::vfoManager.setOffset(vfoName, 0); + } double BW = gui::waterfall.getBandwidth(); double viewBW = gui::waterfall.getViewBandwidth(); gui::waterfall.setViewOffset((BW / 2.0) - (viewBW / 2.0)); @@ -20,6 +23,7 @@ namespace tuner { centerTuning(vfoName, freq); return; } + if (gui::waterfall.vfos.find(vfoName) == gui::waterfall.vfos.end()) { return; } double viewBW = gui::waterfall.getViewBandwidth(); double BW = gui::waterfall.getBandwidth(); diff --git a/core/src/module.cpp b/core/src/module.cpp index b04c839b..0ee1c9e5 100644 --- a/core/src/module.cpp +++ b/core/src/module.cpp @@ -97,6 +97,7 @@ void ModuleManager::createInstance(std::string name, std::string module) { inst.module = modules[module]; inst.instance = inst.module.createInstance(name); instances[name] = inst; + onInstanceCreated.emit(name); } void ModuleManager::deleteInstance(std::string name) { @@ -104,9 +105,11 @@ void ModuleManager::deleteInstance(std::string name) { spdlog::error("Tried to remove non-existant instance '{0}'", name); return; } + onInstanceDelete.emit(name); Instance_t inst = instances[name]; inst.module.deleteInstance(inst.instance); instances.erase(name); + onInstanceDeleted.emit(name); } void ModuleManager::deleteInstance(ModuleManager::Instance* instance) { @@ -137,6 +140,14 @@ bool ModuleManager::instanceEnabled(std::string name) { return instances[name].instance->isEnabled(); } +std::string ModuleManager::getInstanceModuleName(std::string name) { + if (instances.find(name) == instances.end()) { + spdlog::error("Cannot get module name of'{0}', instance doesn't exist", name); + return false; + } + return std::string(instances[name].module.info->name); +} + int ModuleManager::countModuleInstances(std::string module) { if (modules.find(module) == modules.end()) { spdlog::error("Cannot count instances of '{0}', Module doesn't exist", module); diff --git a/core/src/module.h b/core/src/module.h index fdb0ba48..7dd58b96 100644 --- a/core/src/module.h +++ b/core/src/module.h @@ -2,6 +2,7 @@ #include #include #include +#include #ifdef _WIN32 #ifdef SDRPP_IS_CORE @@ -83,9 +84,14 @@ public: void enableInstance(std::string name); void disableInstance(std::string name); bool instanceEnabled(std::string name); + std::string getInstanceModuleName(std::string name); int countModuleInstances(std::string module); + Event onInstanceCreated; + Event onInstanceDelete; + Event onInstanceDeleted; + std::map modules; std::map instances; diff --git a/core/src/signal_path/vfo_manager.cpp b/core/src/signal_path/vfo_manager.cpp index 9536a1f8..bdefb2f6 100644 --- a/core/src/signal_path/vfo_manager.cpp +++ b/core/src/signal_path/vfo_manager.cpp @@ -92,7 +92,7 @@ VFOManager::VFO* VFOManager::createVFO(std::string name, int reference, double o } VFOManager::VFO* vfo = new VFO(name, reference, offset, bandwidth, sampleRate, minBandwidth, maxBandwidth, bandwidthLocked); vfos[name] = vfo; - vfoCreatedEvent.emit(vfo); + onVfoCreated.emit(vfo); return vfo; } @@ -107,9 +107,10 @@ void VFOManager::deleteVFO(VFOManager::VFO* vfo) { if (name == "") { return; } - vfoDeletedEvent.emit(vfo); + onVfoDelete.emit(vfo); vfos.erase(name); delete vfo; + onVfoDeleted.emit(name); } void VFOManager::setOffset(std::string name, double offset) { diff --git a/core/src/signal_path/vfo_manager.h b/core/src/signal_path/vfo_manager.h index b6c640ed..75ae5bf8 100644 --- a/core/src/signal_path/vfo_manager.h +++ b/core/src/signal_path/vfo_manager.h @@ -54,8 +54,9 @@ public: void updateFromWaterfall(ImGui::WaterFall* wtf); - Event vfoCreatedEvent; - Event vfoDeletedEvent; + Event onVfoCreated; + Event onVfoDelete; + Event onVfoDeleted; private: std::map vfos; diff --git a/meteor_demodulator/src/main.cpp b/meteor_demodulator/src/main.cpp index 259d2f7c..03ed4ae8 100644 --- a/meteor_demodulator/src/main.cpp +++ b/meteor_demodulator/src/main.cpp @@ -15,7 +15,7 @@ #include #include #include - +#include #include #include @@ -76,6 +76,7 @@ public: sink.start(); gui::menu.registerEntry(name, menuHandler, this, this); + core::modComManager.registerInterface("meteor_demodulator", name, moduleInterfaceHandler, this); } ~MeteorDemodulatorModule() { @@ -146,26 +147,13 @@ private: if (_this->recording) { if (ImGui::Button(CONCAT("Stop##_recorder_rec_", _this->name), ImVec2(menuWidth, 0))) { - std::lock_guard lck(_this->recMtx); - _this->recording = false; - _this->recFile.close(); - _this->dataWritten = 0; + _this->stopRecording(); } ImGui::TextColored(ImVec4(1.0f, 0.0f, 0.0f, 1.0f), "Recording %.2fMB", (float)_this->dataWritten / 1000000.0f); } else { if (ImGui::Button(CONCAT("Record##_recorder_rec_", _this->name), ImVec2(menuWidth, 0))) { - std::lock_guard lck(_this->recMtx); - _this->dataWritten = 0; - std::string filename = genFileName(_this->folderSelect.expandString(_this->folderSelect.path) + "/meteor", ".s"); - _this->recFile = std::ofstream(filename, std::ios::binary); - if (_this->recFile.is_open()) { - spdlog::info("Recording to '{0}'", filename); - _this->recording = true; - } - else { - spdlog::error("Could not open file for recording!"); - } + _this->startRecording(); } ImGui::Text("Idle --.--MB"); } @@ -195,6 +183,37 @@ private: _this->dataWritten += count * 2; } + void startRecording() { + std::lock_guard lck(recMtx); + dataWritten = 0; + std::string filename = genFileName(folderSelect.expandString(folderSelect.path) + "/meteor", ".s"); + recFile = std::ofstream(filename, std::ios::binary); + if (recFile.is_open()) { + spdlog::info("Recording to '{0}'", filename); + recording = true; + } + else { + spdlog::error("Could not open file for recording!"); + } + } + + void stopRecording() { + std::lock_guard lck(recMtx); + recording = false; + recFile.close(); + dataWritten = 0; + } + + static void moduleInterfaceHandler(int code, void* in, void* out, void* ctx) { + MeteorDemodulatorModule* _this = (MeteorDemodulatorModule*)ctx; + if (code == METEOR_DEMODULATOR_IFACE_CMD_START) { + if (!_this->recording) { _this->startRecording(); } + } + else if (code == METEOR_DEMODULATOR_IFACE_CMD_STOP) { + if (_this->recording) { _this->stopRecording(); } + } + } + std::string name; bool enabled = true; diff --git a/meteor_demodulator/src/meteor_demodulator_interface.h b/meteor_demodulator/src/meteor_demodulator_interface.h new file mode 100644 index 00000000..4f08a762 --- /dev/null +++ b/meteor_demodulator/src/meteor_demodulator_interface.h @@ -0,0 +1,6 @@ +#pragma once + +enum { + METEOR_DEMODULATOR_IFACE_CMD_START, + METEOR_DEMODULATOR_IFACE_CMD_STOP +}; \ No newline at end of file diff --git a/recorder/src/main.cpp b/recorder/src/main.cpp index 69a5b9a5..e3a98d50 100644 --- a/recorder/src/main.cpp +++ b/recorder/src/main.cpp @@ -39,7 +39,7 @@ std::string genFileName(std::string prefix, bool isVfo, std::string name = "") { tm *ltm = localtime(&now); char buf[1024]; double freq = gui::waterfall.getCenterFrequency();; - if (isVfo) { + if (isVfo && gui::waterfall.vfos.find(name) != gui::waterfall.vfos.end()) { freq += gui::waterfall.vfos[name]->generalOffset; } sprintf(buf, "%.0lfHz_%02d-%02d-%02d_%02d-%02d-%02d.wav", freq, ltm->tm_hour, ltm->tm_min, ltm->tm_sec, ltm->tm_mday, ltm->tm_mon + 1, ltm->tm_year + 1900); diff --git a/rigctl_server/CMakeLists.txt b/rigctl_server/CMakeLists.txt index 101dd256..cb7ff5ef 100644 --- a/rigctl_server/CMakeLists.txt +++ b/rigctl_server/CMakeLists.txt @@ -13,6 +13,7 @@ file(GLOB SRC "src/*.cpp") include_directories("src/") include_directories("../recorder/src") +include_directories("../meteor_demodulator/src") add_library(rigctl_server SHARED ${SRC}) target_link_libraries(rigctl_server PRIVATE sdrpp_core) diff --git a/rigctl_server/src/main.cpp b/rigctl_server/src/main.cpp index 613931ea..6993bdf7 100644 --- a/rigctl_server/src/main.cpp +++ b/rigctl_server/src/main.cpp @@ -6,7 +6,12 @@ #include #include #include +#include +#include +#include +#define CONCAT(a, b) ((std::string(a) + b).c_str()) +#define MAX_COMMAND_LENGTH 8192 SDRPP_MOD_INFO { /* Name: */ "rigctl_server", @@ -16,18 +21,48 @@ SDRPP_MOD_INFO { /* Max instances */ -1 }; +enum { + RECORDER_TYPE_RECORDER, + RECORDER_TYPE_METEOR_DEMODULATOR +}; + +ConfigManager config; + class SigctlServerModule : public ModuleManager::Instance { public: SigctlServerModule(std::string name) { this->name = name; - - strcpy(hostname, "localhost"); + + config.acquire(); + if (!config.conf.contains(name)) { + config.conf[name]["host"] = "localhost"; + config.conf[name]["port"] = 4532; + config.conf[name]["tuning"] = true; + config.conf[name]["recording"] = false; + config.conf[name]["autoStart"] = false; + config.conf[name]["vfo"] = ""; + config.conf[name]["recorder"] = ""; + } + std::string host = config.conf[name]["host"]; + strcpy(hostname, host.c_str()); + port = config.conf[name]["port"]; + tuningEnabled = config.conf[name]["tuning"]; + recordingEnabled = config.conf[name]["recording"]; + autoStart = config.conf[name]["autoStart"]; + selectedVfo = config.conf[name]["vfo"]; + selectedRecorder = config.conf[name]["recorder"]; + config.release(true); + + initHandler.handler = _initHandler; + initHandler.ctx = this; + gui::mainWindow.onInitComplete.bindHandler(&initHandler); gui::menu.registerEntry(name, menuHandler, this, NULL); } ~SigctlServerModule() { gui::menu.removeEntry(name); + // TODO: Use several handler instead of one for the recorders and remove event bindings } void enable() { @@ -50,36 +85,77 @@ private: bool listening = (_this->listener && _this->listener->isListening()); if (listening) { style::beginDisabled(); } - ImGui::InputText("##testestssss", _this->hostname, 1023); + if (ImGui::InputText(CONCAT("##_rigctl_srv_host_", _this->name), _this->hostname, 1023)) { + config.acquire(); + config.conf[_this->name]["host"] = std::string(_this->hostname); + config.release(true); + } ImGui::SameLine(); ImGui::SetNextItemWidth(menuWidth - ImGui::GetCursorPosX()); - ImGui::InputInt("##anothertest", &_this->port, 0, 0); + if (ImGui::InputInt(CONCAT("##_rigctl_srv_port_", _this->name), &_this->port, 0, 0)) { + config.acquire(); + config.conf[_this->name]["port"] = _this->port; + config.release(true); + } if (listening) { style::endDisabled(); } - int test = 0; - ImGui::Text("Controlled VFO"); ImGui::SameLine(); ImGui::SetNextItemWidth(menuWidth - ImGui::GetCursorPosX()); - ImGui::Combo("##anotesssss", &test, "Radio\0"); - + { + std::lock_guard lck(_this->vfoMtx); + if (ImGui::Combo(CONCAT("##_rigctl_srv_vfo_", _this->name), &_this->vfoId, _this->vfoNamesTxt.c_str())) { + _this->selectVfoByName(_this->vfoNames[_this->vfoId], false); + } + if (!_this->selectedVfo.empty()) { + config.acquire(); + config.conf[_this->name]["vfo"] = _this->selectedVfo; + config.release(true); + } + } + ImGui::Text("Controlled Recorder"); ImGui::SameLine(); ImGui::SetNextItemWidth(menuWidth - ImGui::GetCursorPosX()); - ImGui::Combo("##anotesssss2", &test, "Recorder\0"); - - if (listening && ImGui::Button("Stop##lasttestamirite", ImVec2(menuWidth, 0))) { - if (_this->client) { _this->client->close(); } - _this->listener->close(); + { + std::lock_guard lck(_this->vfoMtx); + if (ImGui::Combo(CONCAT("##_rigctl_srv_rec_", _this->name), &_this->recorderId, _this->recorderNamesTxt.c_str())) { + _this->selectRecorderByName(_this->recorderNames[_this->recorderId], false); + } + if (!_this->selectedRecorder.empty()) { + config.acquire(); + config.conf[_this->name]["recorder"] = _this->selectedRecorder; + config.release(true); + } } - else if (!listening && ImGui::Button("Start##lasttestamirite", ImVec2(menuWidth, 0))) { - try { - _this->listener = net::listen(net::PROTO_TCP, _this->hostname, _this->port); - _this->listener->acceptAsync(clientHandler, _this); - } - catch (std::exception e) { - spdlog::error("Could not start rigctl server: {0}", e.what()); - } + + ImGui::BeginTable(CONCAT("Stop##_rigctl_srv_tbl_", _this->name), 2); + ImGui::TableNextRow(); + ImGui::TableSetColumnIndex(0); + if (ImGui::Checkbox(CONCAT("Tuning##_rigctl_srv_tune_ena_", _this->name), &_this->tuningEnabled)) { + config.acquire(); + config.conf[_this->name]["tuning"] = _this->tuningEnabled; + config.release(true); + } + ImGui::TableSetColumnIndex(1); + if (ImGui::Checkbox(CONCAT("Recording##_rigctl_srv_tune_ena_", _this->name), &_this->recordingEnabled)) { + config.acquire(); + config.conf[_this->name]["recording"] = _this->recordingEnabled; + config.release(true); + } + ImGui::EndTable(); + + if (ImGui::Checkbox(CONCAT("Listen on startup##_rigctl_srv_auto_lst_", _this->name), &_this->autoStart)) { + config.acquire(); + config.conf[_this->name]["autoStart"] = _this->autoStart; + config.release(true); + } + + if (listening && ImGui::Button(CONCAT("Stop##_rigctl_srv_stop_", _this->name), ImVec2(menuWidth, 0))) { + _this->stopServer(); + } + else if (!listening && ImGui::Button(CONCAT("Start##_rigctl_srv_stop_", _this->name), ImVec2(menuWidth, 0))) { + _this->startServer(); } ImGui::Text("Status:"); @@ -95,16 +171,151 @@ private: } } + void startServer() { + try { + listener = net::listen(net::PROTO_TCP, hostname, port); + listener->acceptAsync(clientHandler, this); + } + catch (std::exception e) { + spdlog::error("Could not start rigctl server: {0}", e.what()); + } + } + + void stopServer() { + if (client) { client->close(); } + listener->close(); + } + + void refreshModules() { + vfoNames.clear(); + vfoNamesTxt.clear(); + recorderNames.clear(); + recorderNamesTxt.clear(); + + // List recording capable modules + for (auto const& [_name, inst] : core::moduleManager.instances) { + std::string mod = core::moduleManager.getInstanceModuleName(_name); + if (mod != "recorder" && mod != "meteor_demodulator") { continue; } + recorderNames.push_back(_name); + recorderNamesTxt += _name; + recorderNamesTxt += '\0'; + } + + // List VFOs + for (auto const& [_name, vfo] : gui::waterfall.vfos) { + vfoNames.push_back(_name); + vfoNamesTxt += _name; + vfoNamesTxt += '\0'; + } + } + + void selectVfoByName(std::string _name, bool lock = true) { + if (vfoNames.empty()) { + if (lock) { std::lock_guard lck(vfoMtx); } + selectedVfo.clear(); + return; + } + + // Find the ID of the VFO, if not found, select first VFO in the list + auto vfoIt = std::find(vfoNames.begin(), vfoNames.end(), _name); + if (vfoIt == vfoNames.end()) { + selectVfoByName(vfoNames[0]); + return; + } + + // Select the VFO + { + if (lock) { std::lock_guard lck(vfoMtx); } + vfoId = std::distance(vfoNames.begin(), vfoIt); + selectedVfo = _name; + } + } + + void selectRecorderByName(std::string _name, bool lock = true) { + if (recorderNames.empty()) { + if (lock) { std::lock_guard lck(recorderMtx); } + selectedRecorder.clear(); + return; + } + + // Find the ID of the VFO, if not found, select first VFO in the list + auto recIt = std::find(recorderNames.begin(), recorderNames.end(), _name); + if (recIt == recorderNames.end()) { + selectRecorderByName(recorderNames[0]); + return; + } + + std::string type = core::modComManager.getModuleName(_name); + + + // Select the VFO + { + if (lock) { std::lock_guard lck(recorderMtx); } + recorderId = std::distance(recorderNames.begin(), recIt); + selectedRecorder = _name; + if (type == "meteor_demodulator") { + recorderType = RECORDER_TYPE_METEOR_DEMODULATOR; + } + else { + recorderType = RECORDER_TYPE_RECORDER; + } + } + } + + static void _initHandler(bool dummy, void* ctx) { + SigctlServerModule* _this = (SigctlServerModule*)ctx; + + // Refresh modules + _this->refreshModules(); + + // Select VFO and recorder from config + _this->selectVfoByName(_this->selectedVfo); + _this->selectRecorderByName(_this->selectedRecorder); + + // Bind handlers + _this->vfoCreatedHandler.handler = _vfoCreatedHandler; + _this->vfoCreatedHandler.ctx = _this; + _this->vfoDeletedHandler.handler = _vfoDeletedHandler; + _this->vfoDeletedHandler.ctx = _this; + _this->modChangedHandler.handler = _modChangeHandler; + _this->modChangedHandler.ctx = _this; + sigpath::vfoManager.onVfoCreated.bindHandler(&_this->vfoCreatedHandler); + sigpath::vfoManager.onVfoDeleted.bindHandler(&_this->vfoDeletedHandler); + core::moduleManager.onInstanceCreated.bindHandler(&_this->modChangedHandler); + core::moduleManager.onInstanceDeleted.bindHandler(&_this->modChangedHandler); + + // If autostart is enabled, start the server + if (_this->autoStart) { _this->startServer(); } + } + + static void _vfoCreatedHandler(VFOManager::VFO* vfo, void* ctx) { + SigctlServerModule* _this = (SigctlServerModule*)ctx; + _this->refreshModules(); + _this->selectVfoByName(_this->selectedVfo); + } + + static void _vfoDeletedHandler(std::string _name, void* ctx) { + SigctlServerModule* _this = (SigctlServerModule*)ctx; + _this->refreshModules(); + _this->selectVfoByName(_this->selectedVfo); + } + + static void _modChangeHandler(std::string _name, void* ctx) { + SigctlServerModule* _this = (SigctlServerModule*)ctx; + _this->refreshModules(); + _this->selectRecorderByName(_this->selectedRecorder); + } + static void clientHandler(net::Conn _client, void* ctx) { SigctlServerModule* _this = (SigctlServerModule*)ctx; - spdlog::info("New client!"); + //spdlog::info("New client!"); _this->client = std::move(_client); _this->client->readAsync(1024, _this->dataBuf, dataHandler, _this); _this->client->waitForEnd(); _this->client->close(); - spdlog::info("Client disconnected!"); + //spdlog::info("Client disconnected!"); _this->listener->acceptAsync(clientHandler, _this); } @@ -118,7 +329,7 @@ private: _this->command.clear(); continue; } - _this->command += (char)data[i]; + if (_this->command.size() < MAX_COMMAND_LENGTH) { _this->command += (char)data[i]; } } _this->client->readAsync(1024, _this->dataBuf, dataHandler, _this); @@ -152,6 +363,8 @@ private: // Execute commands if (parts.size() == 0) { return; } else if (parts[0] == "F") { + std::lock_guard lck(vfoMtx); + // if number of arguments isn't correct, return error if (parts.size() != 2) { resp = "RPRT 1\n"; @@ -159,33 +372,74 @@ private: return; } - // Parse frequency and assign it to the VFO - long long freq = std::stoll(parts[1]); - resp = "RPRT 0\n"; - client->write(resp.size(), (uint8_t*)resp.c_str()); - tuner::tune(tuner::TUNER_MODE_NORMAL, gui::waterfall.selectedVFO, freq); - } - else if (parts[0] == "f") { - double freq = gui::waterfall.getCenterFrequency(); - - // Get frequency of VFO if it exists - if (sigpath::vfoManager.vfoExists(gui::waterfall.selectedVFO)) { - freq += sigpath::vfoManager.getOffset(gui::waterfall.selectedVFO); + // 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()); + } + else if (parts[0] == "f") { + std::lock_guard lck(vfoMtx); + + // Get center frequency of the SDR + double freq = gui::waterfall.getCenterFrequency(); + + // Add the offset of the VFO if it exists + if (sigpath::vfoManager.vfoExists(selectedVfo)) { + freq += sigpath::vfoManager.getOffset(selectedVfo); + } + + // 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] == "AOS") { - // TODO: Start Recorder - core::modComManager.callInterface("Recorder", RECORDER_IFACE_CMD_START, NULL, NULL); + 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] == "LOS") { - // TODO: Stop Recorder - core::modComManager.callInterface("Recorder", RECORDER_IFACE_CMD_STOP, NULL, NULL); + 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()); } @@ -211,10 +465,33 @@ private: std::string command = ""; + EventHandler initHandler; + EventHandler modChangedHandler; + EventHandler vfoCreatedHandler; + EventHandler vfoDeletedHandler; + + std::vector vfoNames; + std::string vfoNamesTxt; + std::vector recorderNames; + std::string recorderNamesTxt; + std::mutex vfoMtx; + std::mutex recorderMtx; + + std::string selectedVfo = ""; + std::string selectedRecorder = ""; + int vfoId = 0; + int recorderId = 0; + int recorderType = RECORDER_TYPE_RECORDER; + + bool tuningEnabled = true; + bool recordingEnabled = false; + bool autoStart = false; }; MOD_EXPORT void _INIT_() { - // Nothing here + config.setPath(options::opts.root + "/rigctl_server_config.json"); + config.load(json::object()); + config.enableAutoSave(); } MOD_EXPORT ModuleManager::Instance* _CREATE_INSTANCE_(std::string name) { @@ -226,5 +503,6 @@ MOD_EXPORT void _DELETE_INSTANCE_(void* instance) { } MOD_EXPORT void _END_() { - // Nothing here + config.disableAutoSave(); + config.save(); } \ No newline at end of file