Finished RigCTL server

This commit is contained in:
Ryzerth 2021-07-18 04:30:55 +02:00
parent 2ddb1b93c4
commit 336d69c043
17 changed files with 522 additions and 69 deletions

View File

@ -187,6 +187,7 @@ int sdrpp_main(int argc, char *argv[]) {
defConfig["moduleInstances"]["Frequency Manager"] = "frequency_manager"; defConfig["moduleInstances"]["Frequency Manager"] = "frequency_manager";
defConfig["moduleInstances"]["Recorder"] = "recorder"; defConfig["moduleInstances"]["Recorder"] = "recorder";
defConfig["moduleInstances"]["Rigctl Server"] = "rigctl_server";
// Themes // Themes

View File

@ -2,7 +2,6 @@
namespace sdrpp_credits { namespace sdrpp_credits {
const char* contributors[] = { const char* contributors[] = {
"Alexandre Rouma (Author)",
"Aang23", "Aang23",
"Alexsey Shestacov", "Alexsey Shestacov",
"Aosync", "Aosync",

View File

@ -219,4 +219,127 @@ namespace dsp {
stream<int16_t>* _in; stream<int16_t>* _in;
}; };
class ComplexToInt16C : public generic_block<ComplexToInt16C> {
public:
ComplexToInt16C() {}
ComplexToInt16C(stream<complex_t>* in) { init(in); }
void init(stream<complex_t>* in) {
_in = in;
generic_block<ComplexToInt16C>::registerInput(_in);
generic_block<ComplexToInt16C>::registerOutput(&out);
generic_block<ComplexToInt16C>::_block_init = true;
}
void setInput(stream<complex_t>* in) {
assert(generic_block<ComplexToInt16C>::_block_init);
std::lock_guard<std::mutex> lck(generic_block<ComplexToInt16C>::ctrlMtx);
generic_block<ComplexToInt16C>::tempStop();
generic_block<ComplexToInt16C>::unregisterInput(_in);
_in = in;
generic_block<ComplexToInt16C>::registerInput(_in);
generic_block<ComplexToInt16C>::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<int16_t> out;
private:
stream<complex_t>* _in;
};
class Int16ToFloat : public generic_block<Int16ToFloat> {
public:
Int16ToFloat() {}
Int16ToFloat(stream<int16_t>* in) { init(in); }
void init(stream<int16_t>* in) {
_in = in;
generic_block<Int16ToFloat>::registerInput(_in);
generic_block<Int16ToFloat>::registerOutput(&out);
generic_block<Int16ToFloat>::_block_init = true;
}
void setInput(stream<int16_t>* in) {
assert(generic_block<Int16ToFloat>::_block_init);
std::lock_guard<std::mutex> lck(generic_block<Int16ToFloat>::ctrlMtx);
generic_block<Int16ToFloat>::tempStop();
generic_block<Int16ToFloat>::unregisterInput(_in);
_in = in;
generic_block<Int16ToFloat>::registerInput(_in);
generic_block<Int16ToFloat>::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<float> out;
private:
stream<int16_t>* _in;
};
class FloatToInt16 : public generic_block<FloatToInt16> {
public:
FloatToInt16() {}
FloatToInt16(stream<float>* in) { init(in); }
void init(stream<float>* in) {
_in = in;
generic_block<FloatToInt16>::registerInput(_in);
generic_block<FloatToInt16>::registerOutput(&out);
generic_block<FloatToInt16>::_block_init = true;
}
void setInput(stream<float>* in) {
assert(generic_block<FloatToInt16>::_block_init);
std::lock_guard<std::mutex> lck(generic_block<FloatToInt16>::ctrlMtx);
generic_block<FloatToInt16>::tempStop();
generic_block<FloatToInt16>::unregisterInput(_in);
_in = in;
generic_block<FloatToInt16>::registerInput(_in);
generic_block<FloatToInt16>::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<int16_t> out;
private:
stream<float>* _in;
};
} }

View File

@ -27,7 +27,7 @@ namespace credits {
ImGui::Spacing(); ImGui::Spacing();
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); ImGui::Columns(3, "CreditColumns", true);
@ -53,7 +53,7 @@ namespace credits {
ImGui::Spacing(); ImGui::Spacing();
ImGui::Spacing(); 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 dispSize = ImGui::GetIO().DisplaySize;
ImVec2 winSize = ImGui::GetWindowSize(); ImVec2 winSize = ImGui::GetWindowSize();

View File

@ -87,7 +87,7 @@ void MainWindow::init() {
vfoCreatedHandler.handler = vfoAddedHandler; vfoCreatedHandler.handler = vfoAddedHandler;
vfoCreatedHandler.ctx = this; vfoCreatedHandler.ctx = this;
sigpath::vfoManager.vfoCreatedEvent.bindHandler(&vfoCreatedHandler); sigpath::vfoManager.onVfoCreated.bindHandler(&vfoCreatedHandler);
spdlog::info("Loading modules"); spdlog::info("Loading modules");
@ -211,6 +211,8 @@ void MainWindow::init() {
} }
initComplete = true; initComplete = true;
onInitComplete.emit(true);
} }
void MainWindow::fftHandler(dsp::complex_t* samples, int count, void* ctx) { void MainWindow::fftHandler(dsp::complex_t* samples, int count, void* ctx) {

View File

@ -34,6 +34,7 @@ public:
bool lockWaterfallControls = false; bool lockWaterfallControls = false;
Event<bool> onPlayStateChange; Event<bool> onPlayStateChange;
Event<bool> onInitComplete;
private: private:
void generateFFTWindow(int win, int size); void generateFFTWindow(int win, int size);

View File

@ -72,7 +72,7 @@ namespace vfo_color_menu {
} }
vfoAddHndl.handler = vfoAddHandler; vfoAddHndl.handler = vfoAddHandler;
sigpath::vfoManager.vfoCreatedEvent.bindHandler(&vfoAddHndl); sigpath::vfoManager.onVfoCreated.bindHandler(&vfoAddHndl);
core::configManager.release(modified); core::configManager.release(modified);
} }

View File

@ -5,7 +5,10 @@
namespace tuner { namespace tuner {
void centerTuning(std::string vfoName, double freq) { 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 BW = gui::waterfall.getBandwidth();
double viewBW = gui::waterfall.getViewBandwidth(); double viewBW = gui::waterfall.getViewBandwidth();
gui::waterfall.setViewOffset((BW / 2.0) - (viewBW / 2.0)); gui::waterfall.setViewOffset((BW / 2.0) - (viewBW / 2.0));
@ -20,6 +23,7 @@ namespace tuner {
centerTuning(vfoName, freq); centerTuning(vfoName, freq);
return; return;
} }
if (gui::waterfall.vfos.find(vfoName) == gui::waterfall.vfos.end()) { return; }
double viewBW = gui::waterfall.getViewBandwidth(); double viewBW = gui::waterfall.getViewBandwidth();
double BW = gui::waterfall.getBandwidth(); double BW = gui::waterfall.getBandwidth();

View File

@ -97,6 +97,7 @@ void ModuleManager::createInstance(std::string name, std::string module) {
inst.module = modules[module]; inst.module = modules[module];
inst.instance = inst.module.createInstance(name); inst.instance = inst.module.createInstance(name);
instances[name] = inst; instances[name] = inst;
onInstanceCreated.emit(name);
} }
void ModuleManager::deleteInstance(std::string 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); spdlog::error("Tried to remove non-existant instance '{0}'", name);
return; return;
} }
onInstanceDelete.emit(name);
Instance_t inst = instances[name]; Instance_t inst = instances[name];
inst.module.deleteInstance(inst.instance); inst.module.deleteInstance(inst.instance);
instances.erase(name); instances.erase(name);
onInstanceDeleted.emit(name);
} }
void ModuleManager::deleteInstance(ModuleManager::Instance* instance) { void ModuleManager::deleteInstance(ModuleManager::Instance* instance) {
@ -137,6 +140,14 @@ bool ModuleManager::instanceEnabled(std::string name) {
return instances[name].instance->isEnabled(); 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) { int ModuleManager::countModuleInstances(std::string module) {
if (modules.find(module) == modules.end()) { if (modules.find(module) == modules.end()) {
spdlog::error("Cannot count instances of '{0}', Module doesn't exist", module); spdlog::error("Cannot count instances of '{0}', Module doesn't exist", module);

View File

@ -2,6 +2,7 @@
#include <string> #include <string>
#include <map> #include <map>
#include <json.hpp> #include <json.hpp>
#include <utils/event.h>
#ifdef _WIN32 #ifdef _WIN32
#ifdef SDRPP_IS_CORE #ifdef SDRPP_IS_CORE
@ -83,9 +84,14 @@ public:
void enableInstance(std::string name); void enableInstance(std::string name);
void disableInstance(std::string name); void disableInstance(std::string name);
bool instanceEnabled(std::string name); bool instanceEnabled(std::string name);
std::string getInstanceModuleName(std::string name);
int countModuleInstances(std::string module); int countModuleInstances(std::string module);
Event<std::string> onInstanceCreated;
Event<std::string> onInstanceDelete;
Event<std::string> onInstanceDeleted;
std::map<std::string, ModuleManager::Module_t> modules; std::map<std::string, ModuleManager::Module_t> modules;
std::map<std::string, ModuleManager::Instance_t> instances; std::map<std::string, ModuleManager::Instance_t> instances;

View File

@ -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); VFOManager::VFO* vfo = new VFO(name, reference, offset, bandwidth, sampleRate, minBandwidth, maxBandwidth, bandwidthLocked);
vfos[name] = vfo; vfos[name] = vfo;
vfoCreatedEvent.emit(vfo); onVfoCreated.emit(vfo);
return vfo; return vfo;
} }
@ -107,9 +107,10 @@ void VFOManager::deleteVFO(VFOManager::VFO* vfo) {
if (name == "") { if (name == "") {
return; return;
} }
vfoDeletedEvent.emit(vfo); onVfoDelete.emit(vfo);
vfos.erase(name); vfos.erase(name);
delete vfo; delete vfo;
onVfoDeleted.emit(name);
} }
void VFOManager::setOffset(std::string name, double offset) { void VFOManager::setOffset(std::string name, double offset) {

View File

@ -54,8 +54,9 @@ public:
void updateFromWaterfall(ImGui::WaterFall* wtf); void updateFromWaterfall(ImGui::WaterFall* wtf);
Event<VFOManager::VFO*> vfoCreatedEvent; Event<VFOManager::VFO*> onVfoCreated;
Event<VFOManager::VFO*> vfoDeletedEvent; Event<VFOManager::VFO*> onVfoDelete;
Event<std::string> onVfoDeleted;
private: private:
std::map<std::string, VFO*> vfos; std::map<std::string, VFO*> vfos;

View File

@ -15,7 +15,7 @@
#include <dsp/processing.h> #include <dsp/processing.h>
#include <dsp/routing.h> #include <dsp/routing.h>
#include <dsp/sink.h> #include <dsp/sink.h>
#include <meteor_demodulator_interface.h>
#include <gui/widgets/folder_select.h> #include <gui/widgets/folder_select.h>
#include <gui/widgets/constellation_diagram.h> #include <gui/widgets/constellation_diagram.h>
@ -76,6 +76,7 @@ public:
sink.start(); sink.start();
gui::menu.registerEntry(name, menuHandler, this, this); gui::menu.registerEntry(name, menuHandler, this, this);
core::modComManager.registerInterface("meteor_demodulator", name, moduleInterfaceHandler, this);
} }
~MeteorDemodulatorModule() { ~MeteorDemodulatorModule() {
@ -146,26 +147,13 @@ private:
if (_this->recording) { if (_this->recording) {
if (ImGui::Button(CONCAT("Stop##_recorder_rec_", _this->name), ImVec2(menuWidth, 0))) { if (ImGui::Button(CONCAT("Stop##_recorder_rec_", _this->name), ImVec2(menuWidth, 0))) {
std::lock_guard<std::mutex> lck(_this->recMtx); _this->stopRecording();
_this->recording = false;
_this->recFile.close();
_this->dataWritten = 0;
} }
ImGui::TextColored(ImVec4(1.0f, 0.0f, 0.0f, 1.0f), "Recording %.2fMB", (float)_this->dataWritten / 1000000.0f); ImGui::TextColored(ImVec4(1.0f, 0.0f, 0.0f, 1.0f), "Recording %.2fMB", (float)_this->dataWritten / 1000000.0f);
} }
else { else {
if (ImGui::Button(CONCAT("Record##_recorder_rec_", _this->name), ImVec2(menuWidth, 0))) { if (ImGui::Button(CONCAT("Record##_recorder_rec_", _this->name), ImVec2(menuWidth, 0))) {
std::lock_guard<std::mutex> lck(_this->recMtx); _this->startRecording();
_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!");
}
} }
ImGui::Text("Idle --.--MB"); ImGui::Text("Idle --.--MB");
} }
@ -195,6 +183,37 @@ private:
_this->dataWritten += count * 2; _this->dataWritten += count * 2;
} }
void startRecording() {
std::lock_guard<std::mutex> 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<std::mutex> 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; std::string name;
bool enabled = true; bool enabled = true;

View File

@ -0,0 +1,6 @@
#pragma once
enum {
METEOR_DEMODULATOR_IFACE_CMD_START,
METEOR_DEMODULATOR_IFACE_CMD_STOP
};

View File

@ -39,7 +39,7 @@ std::string genFileName(std::string prefix, bool isVfo, std::string name = "") {
tm *ltm = localtime(&now); tm *ltm = localtime(&now);
char buf[1024]; char buf[1024];
double freq = gui::waterfall.getCenterFrequency();; double freq = gui::waterfall.getCenterFrequency();;
if (isVfo) { if (isVfo && gui::waterfall.vfos.find(name) != gui::waterfall.vfos.end()) {
freq += gui::waterfall.vfos[name]->generalOffset; 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); 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);

View File

@ -13,6 +13,7 @@ file(GLOB SRC "src/*.cpp")
include_directories("src/") include_directories("src/")
include_directories("../recorder/src") include_directories("../recorder/src")
include_directories("../meteor_demodulator/src")
add_library(rigctl_server SHARED ${SRC}) add_library(rigctl_server SHARED ${SRC})
target_link_libraries(rigctl_server PRIVATE sdrpp_core) target_link_libraries(rigctl_server PRIVATE sdrpp_core)

View File

@ -6,7 +6,12 @@
#include <signal_path/signal_path.h> #include <signal_path/signal_path.h>
#include <core.h> #include <core.h>
#include <recorder_interface.h> #include <recorder_interface.h>
#include <meteor_demodulator_interface.h>
#include <config.h>
#include <options.h>
#define CONCAT(a, b) ((std::string(a) + b).c_str())
#define MAX_COMMAND_LENGTH 8192
SDRPP_MOD_INFO { SDRPP_MOD_INFO {
/* Name: */ "rigctl_server", /* Name: */ "rigctl_server",
@ -16,18 +21,48 @@ SDRPP_MOD_INFO {
/* Max instances */ -1 /* Max instances */ -1
}; };
enum {
RECORDER_TYPE_RECORDER,
RECORDER_TYPE_METEOR_DEMODULATOR
};
ConfigManager config;
class SigctlServerModule : public ModuleManager::Instance { class SigctlServerModule : public ModuleManager::Instance {
public: public:
SigctlServerModule(std::string name) { SigctlServerModule(std::string name) {
this->name = 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); gui::menu.registerEntry(name, menuHandler, this, NULL);
} }
~SigctlServerModule() { ~SigctlServerModule() {
gui::menu.removeEntry(name); gui::menu.removeEntry(name);
// TODO: Use several handler instead of one for the recorders and remove event bindings
} }
void enable() { void enable() {
@ -50,36 +85,77 @@ private:
bool listening = (_this->listener && _this->listener->isListening()); bool listening = (_this->listener && _this->listener->isListening());
if (listening) { style::beginDisabled(); } 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::SameLine();
ImGui::SetNextItemWidth(menuWidth - ImGui::GetCursorPosX()); 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(); } if (listening) { style::endDisabled(); }
int test = 0;
ImGui::Text("Controlled VFO"); ImGui::Text("Controlled VFO");
ImGui::SameLine(); ImGui::SameLine();
ImGui::SetNextItemWidth(menuWidth - ImGui::GetCursorPosX()); 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::Text("Controlled Recorder");
ImGui::SameLine(); ImGui::SameLine();
ImGui::SetNextItemWidth(menuWidth - ImGui::GetCursorPosX()); ImGui::SetNextItemWidth(menuWidth - ImGui::GetCursorPosX());
ImGui::Combo("##anotesssss2", &test, "Recorder\0"); {
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);
}
}
if (listening && ImGui::Button("Stop##lasttestamirite", ImVec2(menuWidth, 0))) { ImGui::BeginTable(CONCAT("Stop##_rigctl_srv_tbl_", _this->name), 2);
if (_this->client) { _this->client->close(); } ImGui::TableNextRow();
_this->listener->close(); 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);
} }
else if (!listening && ImGui::Button("Start##lasttestamirite", ImVec2(menuWidth, 0))) { ImGui::TableSetColumnIndex(1);
try { if (ImGui::Checkbox(CONCAT("Recording##_rigctl_srv_tune_ena_", _this->name), &_this->recordingEnabled)) {
_this->listener = net::listen(net::PROTO_TCP, _this->hostname, _this->port); config.acquire();
_this->listener->acceptAsync(clientHandler, _this); config.conf[_this->name]["recording"] = _this->recordingEnabled;
config.release(true);
} }
catch (std::exception e) { ImGui::EndTable();
spdlog::error("Could not start rigctl server: {0}", e.what());
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:"); 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) { static void clientHandler(net::Conn _client, void* ctx) {
SigctlServerModule* _this = (SigctlServerModule*)ctx; SigctlServerModule* _this = (SigctlServerModule*)ctx;
spdlog::info("New client!"); //spdlog::info("New client!");
_this->client = std::move(_client); _this->client = std::move(_client);
_this->client->readAsync(1024, _this->dataBuf, dataHandler, _this); _this->client->readAsync(1024, _this->dataBuf, dataHandler, _this);
_this->client->waitForEnd(); _this->client->waitForEnd();
_this->client->close(); _this->client->close();
spdlog::info("Client disconnected!"); //spdlog::info("Client disconnected!");
_this->listener->acceptAsync(clientHandler, _this); _this->listener->acceptAsync(clientHandler, _this);
} }
@ -118,7 +329,7 @@ private:
_this->command.clear(); _this->command.clear();
continue; 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); _this->client->readAsync(1024, _this->dataBuf, dataHandler, _this);
@ -152,6 +363,8 @@ private:
// Execute commands // Execute commands
if (parts.size() == 0) { return; } if (parts.size() == 0) { return; }
else if (parts[0] == "F") { else if (parts[0] == "F") {
std::lock_guard lck(vfoMtx);
// if number of arguments isn't correct, return error // if number of arguments isn't correct, return error
if (parts.size() != 2) { if (parts.size() != 2) {
resp = "RPRT 1\n"; resp = "RPRT 1\n";
@ -159,33 +372,74 @@ private:
return; return;
} }
// Parse frequency and assign it to the VFO // If not controlling the VFO, return
long long freq = std::stoll(parts[1]); 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"; resp = "RPRT 0\n";
client->write(resp.size(), (uint8_t*)resp.c_str()); client->write(resp.size(), (uint8_t*)resp.c_str());
tuner::tune(tuner::TUNER_MODE_NORMAL, gui::waterfall.selectedVFO, freq);
} }
else if (parts[0] == "f") { else if (parts[0] == "f") {
std::lock_guard lck(vfoMtx);
// Get center frequency of the SDR
double freq = gui::waterfall.getCenterFrequency(); double freq = gui::waterfall.getCenterFrequency();
// Get frequency of VFO if it exists // Add the offset of the VFO if it exists
if (sigpath::vfoManager.vfoExists(gui::waterfall.selectedVFO)) { if (sigpath::vfoManager.vfoExists(selectedVfo)) {
freq += sigpath::vfoManager.getOffset(gui::waterfall.selectedVFO); freq += sigpath::vfoManager.getOffset(selectedVfo);
} }
// Respond with the frequency
char buf[128]; char buf[128];
sprintf(buf, "%" PRIu64 "\n", (uint64_t)freq); sprintf(buf, "%" PRIu64 "\n", (uint64_t)freq);
client->write(strlen(buf), (uint8_t*)buf); client->write(strlen(buf), (uint8_t*)buf);
} }
else if (parts[0] == "AOS") { else if (parts[0] == "AOS") {
// TODO: Start Recorder std::lock_guard lck(recorderMtx);
core::modComManager.callInterface("Recorder", RECORDER_IFACE_CMD_START, NULL, NULL); // 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"; resp = "RPRT 0\n";
client->write(resp.size(), (uint8_t*)resp.c_str()); client->write(resp.size(), (uint8_t*)resp.c_str());
} }
else if (parts[0] == "LOS") { else if (parts[0] == "LOS") {
// TODO: Stop Recorder std::lock_guard lck(recorderMtx);
core::modComManager.callInterface("Recorder", RECORDER_IFACE_CMD_STOP, NULL, NULL); // 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"; resp = "RPRT 0\n";
client->write(resp.size(), (uint8_t*)resp.c_str()); client->write(resp.size(), (uint8_t*)resp.c_str());
} }
@ -211,10 +465,33 @@ private:
std::string command = ""; std::string command = "";
EventHandler<bool> initHandler;
EventHandler<std::string> modChangedHandler;
EventHandler<VFOManager::VFO*> vfoCreatedHandler;
EventHandler<std::string> vfoDeletedHandler;
std::vector<std::string> vfoNames;
std::string vfoNamesTxt;
std::vector<std::string> 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_() { 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) { MOD_EXPORT ModuleManager::Instance* _CREATE_INSTANCE_(std::string name) {
@ -226,5 +503,6 @@ MOD_EXPORT void _DELETE_INSTANCE_(void* instance) {
} }
MOD_EXPORT void _END_() { MOD_EXPORT void _END_() {
// Nothing here config.disableAutoSave();
config.save();
} }