diff --git a/recorder/src/recorder_interface.h b/recorder/src/recorder_interface.h new file mode 100644 index 0000000..6e03219 --- /dev/null +++ b/recorder/src/recorder_interface.h @@ -0,0 +1,13 @@ +#pragma once + +enum { + RECORDER_IFACE_CMD_GET_MODE, + RECORDER_IFACE_CMD_SET_MODE, + RECORDER_IFACE_CMD_START, + RECORDER_IFACE_CMD_STOP +}; + +enum { + RECORDER_MODE_BASEBAND, + RECORDER_MODE_AUDIO +}; \ No newline at end of file diff --git a/rigctl_server/CMakeLists.txt b/rigctl_server/CMakeLists.txt new file mode 100644 index 0000000..101dd25 --- /dev/null +++ b/rigctl_server/CMakeLists.txt @@ -0,0 +1,22 @@ +cmake_minimum_required(VERSION 3.13) +project(rigctl_server) + +if (MSVC) + set(CMAKE_CXX_FLAGS "-O2 /std:c++17 /EHsc") +elseif (CMAKE_CXX_COMPILER_ID MATCHES "Clang") + set(CMAKE_CXX_FLAGS "-O3 -std=c++17 -Wno-unused-command-line-argument -undefined dynamic_lookup") +else () + set(CMAKE_CXX_FLAGS "-O3 -std=c++17") +endif () + +file(GLOB SRC "src/*.cpp") + +include_directories("src/") +include_directories("../recorder/src") + +add_library(rigctl_server SHARED ${SRC}) +target_link_libraries(rigctl_server PRIVATE sdrpp_core) +set_target_properties(rigctl_server PROPERTIES PREFIX "") + +# Install directives +install(TARGETS rigctl_server DESTINATION lib/sdrpp/plugins) \ No newline at end of file diff --git a/rigctl_server/src/main.cpp b/rigctl_server/src/main.cpp new file mode 100644 index 0000000..37e6a27 --- /dev/null +++ b/rigctl_server/src/main.cpp @@ -0,0 +1,226 @@ +#include +#include +#include +#include +#include +#include +#include +#include + + +SDRPP_MOD_INFO { + /* Name: */ "rigctl_server", + /* Description: */ "My fancy new module", + /* Author: */ "Ryzerth", + /* Version: */ 0, 1, 0, + /* Max instances */ -1 +}; + +class SigctlServerModule : public ModuleManager::Instance { +public: + SigctlServerModule(std::string name) { + this->name = name; + + strcpy(hostname, "localhost"); + + gui::menu.registerEntry(name, menuHandler, this, NULL); + } + + ~SigctlServerModule() { + gui::menu.removeEntry(name); + } + + void enable() { + enabled = true; + } + + void disable() { + enabled = false; + } + + bool isEnabled() { + return enabled; + } + +private: + static void menuHandler(void* ctx) { + SigctlServerModule* _this = (SigctlServerModule*)ctx; + float menuWidth = ImGui::GetContentRegionAvailWidth(); + + bool listening = (_this->listener && _this->listener->isListening()); + + if (listening) { style::beginDisabled(); } + ImGui::InputText("##testestssss", _this->hostname, 1023); + ImGui::SameLine(); + ImGui::SetNextItemWidth(menuWidth - ImGui::GetCursorPosX()); + ImGui::InputInt("##anothertest", &_this->port, 0, 0); + if (listening) { style::endDisabled(); } + + int test = 0; + + ImGui::Text("Controlled VFO"); + ImGui::SameLine(); + ImGui::SetNextItemWidth(menuWidth - ImGui::GetCursorPosX()); + ImGui::Combo("##anotesssss", &test, "Radio\0"); + + 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(); + } + 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::Text("Status:"); + ImGui::SameLine(); + if (_this->client && _this->client->isOpen()) { + ImGui::TextColored(ImVec4(0.0, 1.0, 0.0, 1.0), "Connected"); + } + else if (listening) { + ImGui::TextColored(ImVec4(1.0, 1.0, 0.0, 1.0), "Listening"); + } + else { + ImGui::Text("Idle"); + } + } + + static void clientHandler(net::Conn _client, void* ctx) { + SigctlServerModule* _this = (SigctlServerModule*)ctx; + 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!"); + + _this->listener->acceptAsync(clientHandler, _this); + } + + static void dataHandler(int count, uint8_t* data, void* ctx) { + SigctlServerModule* _this = (SigctlServerModule*)ctx; + + for (int i = 0; i < count; i++) { + if (data[i] == '\n') { + _this->commandHandler(_this->command); + _this->command.clear(); + continue; + } + _this->command += (char)data[i]; + } + + _this->client->readAsync(1024, _this->dataBuf, dataHandler, _this); + } + + void commandHandler(std::string cmd) { + std::string corr = ""; + std::vector parts; + bool lastWasSpace = false; + std::string resp = ""; + + // Split command into parts and remove excess spaces + for (char c : cmd) { + if (lastWasSpace && c == ' ') { continue; } + else if (c == ' ') { + parts.push_back(corr); + corr.clear(); + lastWasSpace = true; + } + else { + lastWasSpace = false; + corr += c; + } + } + if (!corr.empty()) { + parts.push_back(corr); + } + + // NOTE: THIS STUFF ISN'T THREADSAFE AND WILL LIKELY BREAK. + + // Execute commands + if (parts.size() == 0) { return; } + else if (parts[0] == "F") { + // 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; + } + + // 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); + } + + char buf[128]; + sprintf(buf, "%" PRIu64, (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); + } + else if (parts[0] == "LOS") { + // TODO: Stop Recorder + core::modComManager.callInterface("Recorder", RECORDER_IFACE_CMD_STOP, NULL, NULL); + } + else if (parts[0] == "q") { + // Will close automatically + } + else { + spdlog::error("Rigctl client sent invalid command: '{0}'", cmd); + resp = "RPRT 1\n"; + client->write(resp.size(), (uint8_t*)resp.c_str()); + return; + } + } + + std::string name; + bool enabled = true; + + char hostname[1024]; + int port = 4532; + uint8_t dataBuf[1024]; + net::Listener listener; + net::Conn client; + + std::string command = ""; + +}; + +MOD_EXPORT void _INIT_() { + // Nothing here +} + +MOD_EXPORT ModuleManager::Instance* _CREATE_INSTANCE_(std::string name) { + return new SigctlServerModule(name); +} + +MOD_EXPORT void _DELETE_INSTANCE_(void* instance) { + delete (SigctlServerModule*)instance; +} + +MOD_EXPORT void _END_() { + // Nothing here +} \ No newline at end of file