diff --git a/CMakeLists.txt b/CMakeLists.txt index 352bae40..4905cc29 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -45,6 +45,7 @@ option(OPT_BUILD_FREQUENCY_MANAGER "Build the Frequency Manager module" ON) option(OPT_BUILD_RECORDER "Audio and baseband recorder" ON) option(OPT_BUILD_RIGCTL_SERVER "Rigctl backend for controlling SDR++ with software like gpredict" ON) option(OPT_BUILD_SCANNER "Frequency scanner" OFF) +option(OPT_BUILD_SCHEDULER "Build the scheduler" OFF) # Other options option(USE_INTERNAL_LIBCORRECT "Use an external version of libcorrect" ON) @@ -171,6 +172,10 @@ if (OPT_BUILD_SCANNER) add_subdirectory("misc_modules/scanner") endif (OPT_BUILD_SCANNER) +if (OPT_BUILD_SCHEDULER) +add_subdirectory("misc_modules/scheduler") +endif (OPT_BUILD_SCHEDULER) + add_executable(sdrpp "src/main.cpp" "win32/resources.rc") target_link_libraries(sdrpp PRIVATE sdrpp_core) @@ -208,7 +213,7 @@ if (${CMAKE_SYSTEM_NAME} MATCHES "Darwin") add_custom_target(do_always ALL cp \"$/libsdrpp_core.dylib\" \"$\") endif () -# cmake .. "-DCMAKE_TOOLCHAIN_FILE=C:/dev/vcpkg/scripts/buildsystems/vcpkg.cmake" -DOPT_BUILD_BLADERF_SOURCE=ON -DOPT_BUILD_LIMESDR_SOURCE=ON -DOPT_BUILD_SDRPLAY_SOURCE=ON -DOPT_BUILD_M17_DECODER=ON +# cmake .. "-DCMAKE_TOOLCHAIN_FILE=C:/dev/vcpkg/scripts/buildsystems/vcpkg.cmake" -DOPT_BUILD_BLADERF_SOURCE=ON -DOPT_BUILD_LIMESDR_SOURCE=ON -DOPT_BUILD_SDRPLAY_SOURCE=ON -DOPT_BUILD_M17_DECODER=ON -DOPT_BUILD_SCANNER=ON -DOPT_BUILD_SCHEDULER=ON # Install directives install(TARGETS sdrpp DESTINATION bin) diff --git a/core/src/utils/freq_formatting.h b/core/src/utils/freq_formatting.h new file mode 100644 index 00000000..f18a6cc9 --- /dev/null +++ b/core/src/utils/freq_formatting.h @@ -0,0 +1,35 @@ +#pragma once +#include + +namespace utils { + std::string formatFreq(double freq) { + char str[128]; + if (freq >= 1000000.0) { + sprintf(str, "%.06lf", freq / 1000000.0); + int len = strlen(str) - 1; + while ((str[len] == '0' || str[len] == '.') && len > 0) { + len--; + if (str[len] == '.') { len--; break; } + } + return std::string(str).substr(0, len + 1) + "MHz"; + } + else if (freq >= 1000.0) { + sprintf(str, "%.06lf", freq / 1000.0); + int len = strlen(str) - 1; + while ((str[len] == '0' || str[len] == '.') && len > 0) { + len--; + if (str[len] == '.') { len--; break; } + } + return std::string(str).substr(0, len + 1) + "KHz"; + } + else { + sprintf(str, "%.06lf", freq); + int len = strlen(str) - 1; + while ((str[len] == '0' || str[len] == '.') && len > 0) { + len--; + if (str[len] == '.') { len--; break; } + } + return std::string(str).substr(0, len + 1) + "Hz"; + } + } +} \ No newline at end of file diff --git a/misc_modules/frequency_manager/src/main.cpp b/misc_modules/frequency_manager/src/main.cpp index 81bb0f38..ac709424 100644 --- a/misc_modules/frequency_manager/src/main.cpp +++ b/misc_modules/frequency_manager/src/main.cpp @@ -11,6 +11,7 @@ #include #include #include +#include SDRPP_MOD_INFO { /* Name: */ "frequency_manager", @@ -102,36 +103,6 @@ public: } private: - static std::string freqToStr(double freq) { - char str[128]; - if (freq >= 1000000.0) { - sprintf(str, "%.06lf", freq / 1000000.0); - int len = strlen(str) - 1; - while ((str[len] == '0' || str[len] == '.') && len > 0) { - len--; - if (str[len] == '.') { len--; break; } - } - return std::string(str).substr(0, len + 1) + "MHz"; - } - else if (freq >= 1000.0) { - sprintf(str, "%.06lf", freq / 1000.0); - int len = strlen(str) - 1; - while ((str[len] == '0' || str[len] == '.') && len > 0) { - len--; - if (str[len] == '.') { len--; break; } - } - return std::string(str).substr(0, len + 1) + "KHz"; - } - else { - sprintf(str, "%.06lf", freq); - int len = strlen(str) - 1; - while ((str[len] == '0' || str[len] == '.') && len > 0) { - len--; - if (str[len] == '.') { len--; break; } - } - return std::string(str).substr(0, len + 1) + "Hz"; - } - } static void applyBookmark(FrequencyBookmark bm, std::string vfoName) { if (vfoName == "") { @@ -525,7 +496,7 @@ private: } ImGui::TableSetColumnIndex(1); - ImGui::Text("%s %s", freqToStr(bm.frequency).c_str(), demodModeList[bm.mode]); + ImGui::Text("%s %s", utils::formatFreq(bm.frequency).c_str(), demodModeList[bm.mode]); ImVec2 max = ImGui::GetCursorPos(); } ImGui::EndTable(); @@ -756,8 +727,8 @@ private: ImGui::Text(hoveredBookmarkName.c_str()); ImGui::Separator(); ImGui::Text("List: %s", hoveredBookmark.listName.c_str()); - ImGui::Text("Frequency: %s", freqToStr(hoveredBookmark.bookmark.frequency).c_str()); - ImGui::Text("Bandwidth: %s", freqToStr(hoveredBookmark.bookmark.bandwidth).c_str()); + ImGui::Text("Frequency: %s", utils::formatFreq(hoveredBookmark.bookmark.frequency).c_str()); + ImGui::Text("Bandwidth: %s", utils::formatFreq(hoveredBookmark.bookmark.bandwidth).c_str()); ImGui::Text("Mode: %s", demodModeList[hoveredBookmark.bookmark.mode]); ImGui::EndTooltip(); } diff --git a/misc_modules/scheduler/CMakeLists.txt b/misc_modules/scheduler/CMakeLists.txt new file mode 100644 index 00000000..f51a8f5c --- /dev/null +++ b/misc_modules/scheduler/CMakeLists.txt @@ -0,0 +1,21 @@ +cmake_minimum_required(VERSION 3.13) +project(scheduler) + +file(GLOB SRC "src/*.cpp") + +add_library(scheduler SHARED ${SRC}) +target_link_libraries(scheduler PRIVATE sdrpp_core) +set_target_properties(scheduler PROPERTIES PREFIX "") + +target_include_directories(scheduler PRIVATE "src/") + +if (MSVC) + target_compile_options(scheduler PRIVATE /O2 /Ob2 /std:c++17 /EHsc) +elseif (CMAKE_CXX_COMPILER_ID MATCHES "Clang") + target_compile_options(scheduler PRIVATE -O3 -std=c++17 -Wno-unused-command-line-argument -undefined dynamic_lookup) +else () + target_compile_options(scheduler PRIVATE -O3 -std=c++17) +endif () + +# Install directives +install(TARGETS scheduler DESTINATION lib/sdrpp/plugins) \ No newline at end of file diff --git a/misc_modules/scheduler/src/actions/start_recorder.h b/misc_modules/scheduler/src/actions/start_recorder.h new file mode 100644 index 00000000..ddf8d189 --- /dev/null +++ b/misc_modules/scheduler/src/actions/start_recorder.h @@ -0,0 +1,46 @@ +#pragma once +#include + +namespace sched_action { + class StartRecorderClass : public ActionClass { + public: + StartRecorderClass() {} + ~StartRecorderClass() {} + + void trigger() { + + } + + void showEditMenu() { + + } + + void loadFromConfig(json config) { + if (config.contains("recorder")) { recorderName = config["recorder"]; } + } + + json saveToConfig() { + json config; + config["recorder"] = recorderName; + return config; + } + + + std::string getName() { + return "Start \"" + recorderName + "\""; + } + + bool isValid() { + return valid; + } + + private: + std::string recorderName; + bool valid = false; + + }; + + Action StartRecorder() { + return Action(new StartRecorderClass); + } +} \ No newline at end of file diff --git a/misc_modules/scheduler/src/actions/tune_vfo.h b/misc_modules/scheduler/src/actions/tune_vfo.h new file mode 100644 index 00000000..48ed61e5 --- /dev/null +++ b/misc_modules/scheduler/src/actions/tune_vfo.h @@ -0,0 +1,53 @@ +#pragma once +#include +#include + +namespace sched_action { + class TuneVFOClass : public ActionClass { + public: + TuneVFOClass() {} + ~TuneVFOClass() {} + + void trigger() { + + } + + void prepareEditMenu() {} + + void validateEditMenu() {} + + void showEditMenu() { + + } + + void loadFromConfig(json config) { + if (config.contains("vfo")) { vfoName = config["vfo"]; } + if (config.contains("frequency")) { frequency = config["frequency"]; } + } + + json saveToConfig() { + json config; + config["vfo"] = vfoName; + config["frequency"] = frequency; + return config; + } + + std::string getName() { + return "Tune \"" + vfoName + "\" to " + utils::formatFreq(frequency); + } + + bool isValid() { + return valid; + } + + private: + std::string vfoName; + double frequency; + bool valid = false; + + }; + + Action TuneVFO() { + return Action(new TuneVFOClass); + } +} \ No newline at end of file diff --git a/misc_modules/scheduler/src/main.cpp b/misc_modules/scheduler/src/main.cpp new file mode 100644 index 00000000..8d860496 --- /dev/null +++ b/misc_modules/scheduler/src/main.cpp @@ -0,0 +1,148 @@ +#include +#include +#include +#include +#include + +SDRPP_MOD_INFO { + /* Name: */ "scheduler", + /* Description: */ "SDR++ Scheduler", + /* Author: */ "Ryzerth", + /* Version: */ 0, 1, 0, + /* Max instances */ -1 +}; + +class DemoModule : public ModuleManager::Instance { +public: + DemoModule(std::string name) { + this->name = name; + gui::menu.registerEntry(name, menuHandler, this, NULL); + + Task t; + t.selected = false; + + json recStartConfig; + recStartConfig["recorder"] = "Recorder"; + + json tuneVFOConfig; + tuneVFOConfig["vfo"] = "Radio"; + tuneVFOConfig["frequency"] = 103500000.0; + + auto recStart = sched_action::StartRecorder(); + auto tuneVFO = sched_action::TuneVFO(); + + recStart->loadFromConfig(recStartConfig); + tuneVFO->loadFromConfig(tuneVFOConfig); + + t.addAction(tuneVFO); + t.addAction(recStart); + + tasks["Test"] = t; + tasks["Another test"] = t; + } + + ~DemoModule() { + gui::menu.removeEntry(name); + } + + void postInit() {} + + void enable() { + enabled = true; + } + + void disable() { + enabled = false; + } + + bool isEnabled() { + return enabled; + } + +private: + static void menuHandler(void* ctx) { + DemoModule* _this = (DemoModule*)ctx; + + // If editing, show menu + if (!_this->editedTask.empty()) { + gui::mainWindow.lockWaterfallControls = true; + std::string id = "Edit##scheduler_edit_task_" + _this->name; + ImGui::OpenPopup(id.c_str()); + if (ImGui::BeginPopup(id.c_str(), ImGuiWindowFlags_NoResize)) { + bool valid = false; + bool open = _this->tasks[_this->editedTask].showEditMenu(_this->editedName, valid); + + // Stop editing of closed + if (!open) { + // Rename if name changed and valid + if (valid && strcmp(_this->editedName, _this->editedTask.c_str())) { + Task task = _this->tasks[_this->editedTask]; + _this->tasks.erase(_this->editedTask); + _this->tasks[_this->editedName] = task; + } + + // Stop showing edit window + _this->editedTask.clear(); + } + + ImGui::EndPopup(); + } + } + + if (ImGui::BeginTable(("freq_manager_bkm_table"+_this->name).c_str(), 2, ImGuiTableFlags_Borders | ImGuiTableFlags_RowBg | ImGuiTableFlags_ScrollY, ImVec2(0, 200))) { + ImGui::TableSetupColumn("Name"); + ImGui::TableSetupColumn("Countdown"); + ImGui::TableSetupScrollFreeze(2, 1); + ImGui::TableHeadersRow(); + for (auto& [name, bm] : _this->tasks) { + ImGui::TableNextRow(); + ImGui::TableSetColumnIndex(0); + ImVec2 min = ImGui::GetCursorPos(); + + if (ImGui::Selectable((name + "##_freq_mgr_bkm_name_" + _this->name).c_str(), &bm.selected, ImGuiSelectableFlags_SpanAllColumns | ImGuiSelectableFlags_SelectOnClick)) { + // if shift or control isn't pressed, deselect all others + if (!ImGui::IsKeyDown(GLFW_KEY_LEFT_SHIFT) && !ImGui::IsKeyDown(GLFW_KEY_RIGHT_SHIFT) && + !ImGui::IsKeyDown(GLFW_KEY_LEFT_CONTROL) && !ImGui::IsKeyDown(GLFW_KEY_RIGHT_CONTROL)) { + for (auto& [_name, _bm] : _this->tasks) { + if (name == _name) { continue; } + _bm.selected = false; + } + } + } + if (ImGui::TableGetHoveredColumn() >= 0 && ImGui::IsItemHovered() && ImGui::IsMouseDoubleClicked(ImGuiMouseButton_Left) && _this->editedTask.empty()) { + _this->editedTask = name; + strcpy(_this->editedName, name.c_str()); + } + + ImGui::TableSetColumnIndex(1); + ImGui::Text("todo"); + } + ImGui::EndTable(); + } + } + + std::string name; + bool enabled = true; + + std::string editedTask = ""; + char editedName[1024]; + + std::map tasks; + +}; + +MOD_EXPORT void _INIT_() { + // Nothing here +} + +MOD_EXPORT ModuleManager::Instance* _CREATE_INSTANCE_(std::string name) { + return new DemoModule(name); +} + +MOD_EXPORT void _DELETE_INSTANCE_(void* instance) { + delete (DemoModule*)instance; +} + +MOD_EXPORT void _END_() { + // Nothing here +} \ No newline at end of file diff --git a/misc_modules/scheduler/src/sched_action.h b/misc_modules/scheduler/src/sched_action.h new file mode 100644 index 00000000..1f53c8b0 --- /dev/null +++ b/misc_modules/scheduler/src/sched_action.h @@ -0,0 +1,28 @@ +#pragma once +#include +#include +#include + +using namespace nlohmann; + +namespace sched_action { + class ActionClass { + public: + virtual ~ActionClass() { + spdlog::warn("Base destructor"); + }; + virtual void trigger() = 0; + virtual void prepareEditMenu() = 0; + virtual void validateEditMenu() = 0; + virtual void showEditMenu() = 0; + virtual void loadFromConfig(json config) = 0; + virtual json saveToConfig() = 0; + virtual std::string getName() = 0; + virtual bool isValid() = 0; + }; + + typedef std::shared_ptr Action; +} + +#include +#include \ No newline at end of file diff --git a/misc_modules/scheduler/src/sched_task.h b/misc_modules/scheduler/src/sched_task.h new file mode 100644 index 00000000..afd83bcb --- /dev/null +++ b/misc_modules/scheduler/src/sched_task.h @@ -0,0 +1,75 @@ +#pragma once +#include +#include +#include +#include + +class Task { +public: + void trigger() { + for (auto& act : actions) { + act->trigger(); + } + } + + void addAction(sched_action::Action act) { + actions.push_back(act); + } + + bool removeAction(sched_action::Action act) { + auto it = std::find(actions.begin(), actions.end(), act); + if (it == actions.end()) { return false; } + actions.erase(it); + return true; + } + + bool showEditMenu(char* name, bool& valid) { + ImGui::LeftLabel("Name"); + ImGui::InputText("##scheduler_task_edit_name", name, 1023); + + if (ImGui::BeginTable("scheduler_task_triggers", 1, ImGuiTableFlags_Borders | ImGuiTableFlags_RowBg | ImGuiTableFlags_ScrollY, ImVec2(0, 100))) { + ImGui::TableSetupColumn("Triggers"); + ImGui::TableSetupScrollFreeze(1, 1); + ImGui::TableHeadersRow(); + + // Fill rows here + ImGui::TableNextRow(); + ImGui::TableSetColumnIndex(0); + ImGui::Text("Every day at 00:00:00"); + + ImGui::EndTable(); + } + + if (ImGui::BeginTable("scheduler_task_actions", 1, ImGuiTableFlags_Borders | ImGuiTableFlags_RowBg | ImGuiTableFlags_ScrollY, ImVec2(0, 100))) { + ImGui::TableSetupColumn("Actions"); + ImGui::TableSetupScrollFreeze(1, 1); + ImGui::TableHeadersRow(); + + for (auto& act : actions) { + ImGui::TableNextRow(); + ImGui::TableSetColumnIndex(0); + ImGui::Text(act->getName().c_str()); + } + + ImGui::EndTable(); + } + + if (ImGui::Button("Apply")) { + valid = true; + return false; + } + ImGui::SameLine(); + if (ImGui::Button("Cancel")) { + valid = false; + return false; + } + + return true; + } + + bool selected; + +private: + std::vector actions; + +}; \ No newline at end of file diff --git a/source_modules/rfspace_source/src/main.cpp b/source_modules/rfspace_source/src/main.cpp index 7d7a97a4..a988b1bd 100644 --- a/source_modules/rfspace_source/src/main.cpp +++ b/source_modules/rfspace_source/src/main.cpp @@ -14,8 +14,8 @@ #define CONCAT(a, b) ((std::string(a) + b).c_str()) SDRPP_MOD_INFO { - /* Name: */ "spyserver_source", - /* Description: */ "Airspy HF+ source module for SDR++", + /* Name: */ "rfspace_source", + /* Description: */ "RFspace source module for SDR++", /* Author: */ "Ryzerth", /* Version: */ 0, 1, 0, /* Max instances */ 1 diff --git a/source_modules/spyserver_source/src/main.cpp b/source_modules/spyserver_source/src/main.cpp index 57591bab..1a4cc05a 100644 --- a/source_modules/spyserver_source/src/main.cpp +++ b/source_modules/spyserver_source/src/main.cpp @@ -15,7 +15,7 @@ SDRPP_MOD_INFO { /* Name: */ "spyserver_source", - /* Description: */ "Airspy HF+ source module for SDR++", + /* Description: */ "SpyServer source module for SDR++", /* Author: */ "Ryzerth", /* Version: */ 0, 1, 0, /* Max instances */ 1