mirror of
https://github.com/AlexandreRouma/SDRPlusPlus.git
synced 2025-04-07 07:45:40 +02:00
Finished soapy module + added file source + added RTL_TCP source (windows only rn)
This commit is contained in:
parent
47b04ffef4
commit
60342de9c0
@ -1,11 +1,13 @@
|
|||||||
cmake_minimum_required(VERSION 3.13)
|
cmake_minimum_required(VERSION 3.13)
|
||||||
project(sdrpp_core)
|
project(sdrpp_core)
|
||||||
|
|
||||||
|
# Cross platform modules
|
||||||
add_subdirectory("core")
|
add_subdirectory("core")
|
||||||
add_subdirectory("radio")
|
add_subdirectory("radio")
|
||||||
add_subdirectory("recorder")
|
add_subdirectory("recorder")
|
||||||
add_subdirectory("soapy")
|
add_subdirectory("soapy")
|
||||||
add_subdirectory("file_source")
|
add_subdirectory("file_source")
|
||||||
|
add_subdirectory("rtl_tcp_source")
|
||||||
add_subdirectory("demo")
|
add_subdirectory("demo")
|
||||||
|
|
||||||
add_executable(sdrpp "src/main.cpp" "win32/resources.rc")
|
add_executable(sdrpp "src/main.cpp" "win32/resources.rc")
|
||||||
|
@ -186,6 +186,8 @@ int sdrpp_main() {
|
|||||||
ImGui_ImplGlfw_NewFrame();
|
ImGui_ImplGlfw_NewFrame();
|
||||||
ImGui::NewFrame();
|
ImGui::NewFrame();
|
||||||
|
|
||||||
|
//ImGui::ShowDemoWindow();
|
||||||
|
|
||||||
if (_maximized != maximized) {
|
if (_maximized != maximized) {
|
||||||
_maximized = maximized;
|
_maximized = maximized;
|
||||||
core::configManager.aquire();
|
core::configManager.aquire();
|
||||||
|
@ -48,7 +48,8 @@ namespace dsp {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void setFrequency(float frequency) {
|
void setFrequency(float frequency) {
|
||||||
_phasorSpeed = (2 * 3.1415926535) / (_sampleRate / frequency);
|
_frequency = frequency;
|
||||||
|
_phasorSpeed = (2 * 3.1415926535 * frequency) / _sampleRate;
|
||||||
}
|
}
|
||||||
|
|
||||||
void setBlockSize(int blockSize) {
|
void setBlockSize(int blockSize) {
|
||||||
|
@ -123,6 +123,8 @@ void windowInit() {
|
|||||||
fftHeight = core::configManager.conf["fftHeight"];
|
fftHeight = core::configManager.conf["fftHeight"];
|
||||||
gui::waterfall.setFFTHeight(fftHeight);
|
gui::waterfall.setFFTHeight(fftHeight);
|
||||||
|
|
||||||
|
gui::menu.order = core::configManager.conf["menuOrder"].get<std::vector<std::string>>();
|
||||||
|
|
||||||
core::configManager.release();
|
core::configManager.release();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -411,8 +413,7 @@ void drawWindow() {
|
|||||||
ImGui::SetCursorPosX((ImGui::GetWindowSize().x / 2.0f) - (ImGui::CalcTextSize("Zoom").x / 2.0f));
|
ImGui::SetCursorPosX((ImGui::GetWindowSize().x / 2.0f) - (ImGui::CalcTextSize("Zoom").x / 2.0f));
|
||||||
ImGui::Text("Zoom");
|
ImGui::Text("Zoom");
|
||||||
ImGui::SetCursorPosX((ImGui::GetWindowSize().x / 2.0f) - 10);
|
ImGui::SetCursorPosX((ImGui::GetWindowSize().x / 2.0f) - 10);
|
||||||
// TODO: use global sample rate value from DSP instead of 8000000
|
ImGui::VSliderFloat("##_7_", ImVec2(20.0f, 150.0f), &bw.val, gui::waterfall.getBandwidth(), 1000.0f, "");
|
||||||
ImGui::VSliderFloat("##_7_", ImVec2(20.0f, 150.0f), &bw.val, 8000000, 1000.0f, "");
|
|
||||||
|
|
||||||
ImGui::NewLine();
|
ImGui::NewLine();
|
||||||
|
|
||||||
|
@ -17,7 +17,6 @@
|
|||||||
#include <gui/frequency_select.h>
|
#include <gui/frequency_select.h>
|
||||||
#include <fftw3.h>
|
#include <fftw3.h>
|
||||||
#include <signal_path/dsp.h>
|
#include <signal_path/dsp.h>
|
||||||
#include <io/soapy.h>
|
|
||||||
#include <gui/icons.h>
|
#include <gui/icons.h>
|
||||||
#include <gui/bandplan.h>
|
#include <gui/bandplan.h>
|
||||||
#include <watcher.h>
|
#include <watcher.h>
|
||||||
|
@ -27,6 +27,7 @@ void Menu::draw() {
|
|||||||
item = items[name];
|
item = items[name];
|
||||||
if (ImGui::CollapsingHeader(name.c_str(), ImGuiTreeNodeFlags_DefaultOpen)) {
|
if (ImGui::CollapsingHeader(name.c_str(), ImGuiTreeNodeFlags_DefaultOpen)) {
|
||||||
item.drawHandler(item.ctx);
|
item.drawHandler(item.ctx);
|
||||||
|
ImGui::Spacing();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -134,6 +134,5 @@ namespace audiomenu {
|
|||||||
}
|
}
|
||||||
ImGui::Spacing();
|
ImGui::Spacing();
|
||||||
}
|
}
|
||||||
ImGui::Spacing();
|
|
||||||
}
|
}
|
||||||
};
|
};
|
@ -47,6 +47,5 @@ namespace bandplanmenu {
|
|||||||
bandplan::BandPlan_t plan = bandplan::bandplans[bandplan::bandplanNames[bandplanId]];
|
bandplan::BandPlan_t plan = bandplan::bandplans[bandplan::bandplanNames[bandplanId]];
|
||||||
ImGui::Text("Country: %s (%s)", plan.countryName.c_str(), plan.countryCode.c_str());
|
ImGui::Text("Country: %s (%s)", plan.countryName.c_str(), plan.countryCode.c_str());
|
||||||
ImGui::Text("Author: %s", plan.authorName.c_str());
|
ImGui::Text("Author: %s", plan.authorName.c_str());
|
||||||
ImGui::Spacing();
|
|
||||||
}
|
}
|
||||||
};
|
};
|
@ -18,6 +18,5 @@ namespace displaymenu {
|
|||||||
core::configManager.conf["showWaterfall"] = showWaterfall;
|
core::configManager.conf["showWaterfall"] = showWaterfall;
|
||||||
core::configManager.release(true);
|
core::configManager.release(true);
|
||||||
}
|
}
|
||||||
ImGui::Spacing();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -2,7 +2,6 @@
|
|||||||
#include <signal_path/dsp.h>
|
#include <signal_path/dsp.h>
|
||||||
#include <signal_path/vfo_manager.h>
|
#include <signal_path/vfo_manager.h>
|
||||||
#include <signal_path/source.h>
|
#include <signal_path/source.h>
|
||||||
#include <io/soapy.h>
|
|
||||||
#include <module.h>
|
#include <module.h>
|
||||||
|
|
||||||
namespace sigpath {
|
namespace sigpath {
|
||||||
|
@ -7,6 +7,8 @@ else()
|
|||||||
set(CMAKE_CXX_FLAGS "-O3 -std=c++17 -fpermissive")
|
set(CMAKE_CXX_FLAGS "-O3 -std=c++17 -fpermissive")
|
||||||
endif (MSVC)
|
endif (MSVC)
|
||||||
|
|
||||||
|
include_directories("src/")
|
||||||
|
|
||||||
file(GLOB SRC "src/*.cpp")
|
file(GLOB SRC "src/*.cpp")
|
||||||
|
|
||||||
add_library(file_source SHARED ${SRC})
|
add_library(file_source SHARED ${SRC})
|
||||||
|
@ -3,6 +3,8 @@
|
|||||||
#include <module.h>
|
#include <module.h>
|
||||||
#include <gui/gui.h>
|
#include <gui/gui.h>
|
||||||
#include <signal_path/signal_path.h>
|
#include <signal_path/signal_path.h>
|
||||||
|
#include <wavreader.h>
|
||||||
|
#include <core.h>
|
||||||
|
|
||||||
#define CONCAT(a, b) ((std::string(a) + b).c_str())
|
#define CONCAT(a, b) ((std::string(a) + b).c_str())
|
||||||
|
|
||||||
@ -29,6 +31,11 @@ public:
|
|||||||
handler.tuneHandler = tune;
|
handler.tuneHandler = tune;
|
||||||
handler.stream = &stream;
|
handler.stream = &stream;
|
||||||
sigpath::sourceManager.registerSource("File", &handler);
|
sigpath::sourceManager.registerSource("File", &handler);
|
||||||
|
|
||||||
|
reader = new WavReader("D:/satpic/raw_recordings/NOAA-18_09-08-2018_21-39-00_baseband_NR.wav");
|
||||||
|
|
||||||
|
spdlog::info("Samplerate: {0}, Bit depth: {1}, Channel count: {2}", reader->getSampleRate(), reader->getBitDepth(), reader->getChannelCount());
|
||||||
|
|
||||||
spdlog::info("FileSourceModule '{0}': Instance created!", name);
|
spdlog::info("FileSourceModule '{0}': Instance created!", name);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -39,6 +46,7 @@ public:
|
|||||||
private:
|
private:
|
||||||
static void menuSelected(void* ctx) {
|
static void menuSelected(void* ctx) {
|
||||||
FileSourceModule* _this = (FileSourceModule*)ctx;
|
FileSourceModule* _this = (FileSourceModule*)ctx;
|
||||||
|
core::setInputSampleRate(_this->reader->getSampleRate());
|
||||||
spdlog::info("FileSourceModule '{0}': Menu Select!", _this->name);
|
spdlog::info("FileSourceModule '{0}': Menu Select!", _this->name);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -49,11 +57,15 @@ private:
|
|||||||
|
|
||||||
static void start(void* ctx) {
|
static void start(void* ctx) {
|
||||||
FileSourceModule* _this = (FileSourceModule*)ctx;
|
FileSourceModule* _this = (FileSourceModule*)ctx;
|
||||||
|
_this->workerThread = std::thread(worker, _this);
|
||||||
spdlog::info("FileSourceModule '{0}': Start!", _this->name);
|
spdlog::info("FileSourceModule '{0}': Start!", _this->name);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void stop(void* ctx) {
|
static void stop(void* ctx) {
|
||||||
FileSourceModule* _this = (FileSourceModule*)ctx;
|
FileSourceModule* _this = (FileSourceModule*)ctx;
|
||||||
|
_this->stream.stopWriter();
|
||||||
|
_this->workerThread.join();
|
||||||
|
_this->stream.clearWriteStop();
|
||||||
spdlog::info("FileSourceModule '{0}': Stop!", _this->name);
|
spdlog::info("FileSourceModule '{0}': Stop!", _this->name);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -62,15 +74,39 @@ private:
|
|||||||
spdlog::info("FileSourceModule '{0}': Tune: {1}!", _this->name, freq);
|
spdlog::info("FileSourceModule '{0}': Tune: {1}!", _this->name, freq);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
static void menuHandler(void* ctx) {
|
static void menuHandler(void* ctx) {
|
||||||
FileSourceModule* _this = (FileSourceModule*)ctx;
|
FileSourceModule* _this = (FileSourceModule*)ctx;
|
||||||
ImGui::Text("Hi from %s!", _this->name.c_str());
|
ImGui::Text("Hi from %s!", _this->name.c_str());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void worker(void* ctx) {
|
||||||
|
FileSourceModule* _this = (FileSourceModule*)ctx;
|
||||||
|
double sampleRate = _this->reader->getSampleRate();
|
||||||
|
int blockSize = sampleRate / 200.0;
|
||||||
|
int16_t* inBuf = new int16_t[blockSize * 2];
|
||||||
|
dsp::complex_t* outBuf = new dsp::complex_t[blockSize];
|
||||||
|
|
||||||
|
_this->stream.setMaxLatency(blockSize * 2);
|
||||||
|
|
||||||
|
while (true) {
|
||||||
|
_this->reader->readSamples(inBuf, blockSize * 2 * sizeof(int16_t));
|
||||||
|
for (int i = 0; i < blockSize; i++) {
|
||||||
|
outBuf[i].q = (float)inBuf[i * 2] / (float)0x7FFF;
|
||||||
|
outBuf[i].i = (float)inBuf[(i * 2) + 1] / (float)0x7FFF;
|
||||||
|
}
|
||||||
|
if (_this->stream.write(outBuf, blockSize) < 0) { break; };
|
||||||
|
//std::this_thread::sleep_for(std::chrono::milliseconds(5));
|
||||||
|
}
|
||||||
|
|
||||||
|
delete[] inBuf;
|
||||||
|
delete[] outBuf;
|
||||||
|
}
|
||||||
|
|
||||||
std::string name;
|
std::string name;
|
||||||
dsp::stream<dsp::complex_t> stream;
|
dsp::stream<dsp::complex_t> stream;
|
||||||
SourceManager::SourceHandler handler;
|
SourceManager::SourceHandler handler;
|
||||||
|
WavReader* reader;
|
||||||
|
std::thread workerThread;
|
||||||
};
|
};
|
||||||
|
|
||||||
MOD_EXPORT void _INIT_() {
|
MOD_EXPORT void _INIT_() {
|
||||||
|
62
file_source/src/wavreader.h
Normal file
62
file_source/src/wavreader.h
Normal file
@ -0,0 +1,62 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
#include <stdint.h>
|
||||||
|
#include <fstream>
|
||||||
|
|
||||||
|
#define WAV_SIGNATURE "RIFF"
|
||||||
|
#define WAV_TYPE "WAVE"
|
||||||
|
#define WAV_FORMAT_MARK "fmt "
|
||||||
|
#define WAV_DATA_MARK "data"
|
||||||
|
#define WAV_SAMPLE_TYPE_PCM 1
|
||||||
|
|
||||||
|
class WavReader {
|
||||||
|
public:
|
||||||
|
WavReader(std::string path) {
|
||||||
|
file = std::ifstream(path.c_str(), std::ios::binary);
|
||||||
|
file.read((char*)&hdr, sizeof(WavHeader_t));
|
||||||
|
}
|
||||||
|
|
||||||
|
uint16_t getBitDepth() {
|
||||||
|
return hdr.bitDepth;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint16_t getChannelCount() {
|
||||||
|
return hdr.channelCount;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint32_t getSampleRate() {
|
||||||
|
return hdr.sampleRate;
|
||||||
|
}
|
||||||
|
|
||||||
|
void readSamples(void* data, size_t size) {
|
||||||
|
file.read((char*)data, size);
|
||||||
|
bytesRead += size;
|
||||||
|
}
|
||||||
|
|
||||||
|
void close() {
|
||||||
|
|
||||||
|
file.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
struct WavHeader_t {
|
||||||
|
char signature[4]; // "RIFF"
|
||||||
|
uint32_t fileSize; // data bytes + sizeof(WavHeader_t) - 8
|
||||||
|
char fileType[4]; // "WAVE"
|
||||||
|
char formatMarker[4]; // "fmt "
|
||||||
|
uint32_t formatHeaderLength; // Always 16
|
||||||
|
uint16_t sampleType; // PCM (1)
|
||||||
|
uint16_t channelCount;
|
||||||
|
uint32_t sampleRate;
|
||||||
|
uint32_t bytesPerSecond;
|
||||||
|
uint16_t bytesPerSample;
|
||||||
|
uint16_t bitDepth;
|
||||||
|
char dataMarker[4]; // "data"
|
||||||
|
uint32_t dataSize;
|
||||||
|
};
|
||||||
|
|
||||||
|
std::ifstream file;
|
||||||
|
size_t bytesRead = 0;
|
||||||
|
WavHeader_t hdr;
|
||||||
|
};
|
@ -29,5 +29,6 @@
|
|||||||
"windowSize": {
|
"windowSize": {
|
||||||
"h": 720,
|
"h": 720,
|
||||||
"w": 1280
|
"w": 1280
|
||||||
}
|
},
|
||||||
|
"menuOrder": ["Source", "Radio", "Recorder", "Audio", "Band Plan", "Display"]
|
||||||
}
|
}
|
@ -14,5 +14,6 @@
|
|||||||
"windowSize": {
|
"windowSize": {
|
||||||
"h": 720,
|
"h": 720,
|
||||||
"w": 1280
|
"w": 1280
|
||||||
}
|
},
|
||||||
|
"menuOrder": ["Source", "Radio", "Recorder", "Audio", "Band Plan", "Display"]
|
||||||
}
|
}
|
@ -2,5 +2,6 @@
|
|||||||
"Radio": "./radio/Release/radio.dll",
|
"Radio": "./radio/Release/radio.dll",
|
||||||
"Recorder": "./recorder/Release/recorder.dll",
|
"Recorder": "./recorder/Release/recorder.dll",
|
||||||
"Soapy": "./soapy/Release/soapy.dll",
|
"Soapy": "./soapy/Release/soapy.dll",
|
||||||
"FileSource": "./file_source/Release/file_source.dll"
|
"FileSource": "./file_source/Release/file_source.dll",
|
||||||
|
"RTLTCPSource": "./rtl_tcp_source/Release/rtl_tcp_source.dll"
|
||||||
}
|
}
|
19
root_dev/soapy_source_config.json
Normal file
19
root_dev/soapy_source_config.json
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
{
|
||||||
|
"device": "Generic RTL2832U OEM :: 00000001",
|
||||||
|
"devices": {
|
||||||
|
"Generic RTL2832U OEM :: 00000001": {
|
||||||
|
"gains": {
|
||||||
|
"TUNER": 12.817999839782715
|
||||||
|
},
|
||||||
|
"sampleRate": 2560000.0
|
||||||
|
},
|
||||||
|
"HackRF One #0 901868dc282c8f8b": {
|
||||||
|
"gains": {
|
||||||
|
"AMP": 0.0,
|
||||||
|
"LNA": 24.711999893188477,
|
||||||
|
"VGA": 15.906000137329102
|
||||||
|
},
|
||||||
|
"sampleRate": 8000000.0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
20
rtl_tcp_source/CMakeLists.txt
Normal file
20
rtl_tcp_source/CMakeLists.txt
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
cmake_minimum_required(VERSION 3.13)
|
||||||
|
project(rtl_tcp_source)
|
||||||
|
|
||||||
|
if (MSVC)
|
||||||
|
set(CMAKE_CXX_FLAGS "-O2 /std:c++17")
|
||||||
|
else()
|
||||||
|
set(CMAKE_CXX_FLAGS "-O3 -std=c++17 -fpermissive")
|
||||||
|
endif (MSVC)
|
||||||
|
|
||||||
|
include_directories("src/")
|
||||||
|
|
||||||
|
file(GLOB SRC "src/*.cpp")
|
||||||
|
|
||||||
|
add_library(rtl_tcp_source SHARED ${SRC})
|
||||||
|
target_link_libraries(rtl_tcp_source PRIVATE sdrpp_core)
|
||||||
|
set_target_properties(rtl_tcp_source PROPERTIES PREFIX "")
|
||||||
|
|
||||||
|
if(WIN32)
|
||||||
|
target_link_libraries(rtl_tcp_source PRIVATE wsock32 ws2_32)
|
||||||
|
endif()
|
188
rtl_tcp_source/src/main.cpp
Normal file
188
rtl_tcp_source/src/main.cpp
Normal file
@ -0,0 +1,188 @@
|
|||||||
|
#include <rtltcp_client.h>
|
||||||
|
#include <imgui.h>
|
||||||
|
#include <spdlog/spdlog.h>
|
||||||
|
#include <module.h>
|
||||||
|
#include <gui/gui.h>
|
||||||
|
#include <signal_path/signal_path.h>
|
||||||
|
#include <core.h>
|
||||||
|
#include <gui/style.h>
|
||||||
|
|
||||||
|
#define CONCAT(a, b) ((std::string(a) + b).c_str())
|
||||||
|
|
||||||
|
MOD_INFO {
|
||||||
|
/* Name: */ "fike_source",
|
||||||
|
/* Description: */ "File input module for SDR++",
|
||||||
|
/* Author: */ "Ryzerth",
|
||||||
|
/* Version: */ "0.1.0"
|
||||||
|
};
|
||||||
|
|
||||||
|
class RTLTCPSourceModule {
|
||||||
|
public:
|
||||||
|
RTLTCPSourceModule(std::string name) {
|
||||||
|
this->name = name;
|
||||||
|
|
||||||
|
stream.init(100);
|
||||||
|
|
||||||
|
sampleRate = 2560000.0;
|
||||||
|
|
||||||
|
handler.ctx = this;
|
||||||
|
handler.selectHandler = menuSelected;
|
||||||
|
handler.deselectHandler = menuDeselected;
|
||||||
|
handler.menuHandler = menuHandler;
|
||||||
|
handler.startHandler = start;
|
||||||
|
handler.stopHandler = stop;
|
||||||
|
handler.tuneHandler = tune;
|
||||||
|
handler.stream = &stream;
|
||||||
|
sigpath::sourceManager.registerSource("RTL-TCP", &handler);
|
||||||
|
|
||||||
|
spdlog::info("RTLTCPSourceModule '{0}': Instance created!", name);
|
||||||
|
}
|
||||||
|
|
||||||
|
~RTLTCPSourceModule() {
|
||||||
|
spdlog::info("RTLTCPSourceModule '{0}': Instance deleted!", name);
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
static void menuSelected(void* ctx) {
|
||||||
|
RTLTCPSourceModule* _this = (RTLTCPSourceModule*)ctx;
|
||||||
|
core::setInputSampleRate(_this->sampleRate);
|
||||||
|
spdlog::info("RTLTCPSourceModule '{0}': Menu Select!", _this->name);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void menuDeselected(void* ctx) {
|
||||||
|
RTLTCPSourceModule* _this = (RTLTCPSourceModule*)ctx;
|
||||||
|
spdlog::info("RTLTCPSourceModule '{0}': Menu Deselect!", _this->name);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void start(void* ctx) {
|
||||||
|
RTLTCPSourceModule* _this = (RTLTCPSourceModule*)ctx;
|
||||||
|
if (!_this->client.connectToRTL(_this->ip, _this->port)) {
|
||||||
|
spdlog::error("Could not connect to {0}:{1}", _this->ip, _this->port);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
_this->client.setFrequency(_this->freq);
|
||||||
|
_this->client.setSampleRate(_this->sampleRate);
|
||||||
|
_this->client.setGainIndex(_this->gain);
|
||||||
|
_this->client.setGainMode(!_this->tunerAGC);
|
||||||
|
_this->client.setDirectSampling(_this->directSamplingMode);
|
||||||
|
_this->client.setAGCMode(_this->rtlAGC);
|
||||||
|
_this->running = true;
|
||||||
|
_this->workerThread = std::thread(worker, _this);
|
||||||
|
spdlog::info("RTLTCPSourceModule '{0}': Start!", _this->name);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void stop(void* ctx) {
|
||||||
|
RTLTCPSourceModule* _this = (RTLTCPSourceModule*)ctx;
|
||||||
|
_this->running = false;
|
||||||
|
_this->stream.stopWriter();
|
||||||
|
_this->workerThread.join();
|
||||||
|
_this->stream.clearWriteStop();
|
||||||
|
_this->client.disconnect();
|
||||||
|
spdlog::info("RTLTCPSourceModule '{0}': Stop!", _this->name);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void tune(double freq, void* ctx) {
|
||||||
|
RTLTCPSourceModule* _this = (RTLTCPSourceModule*)ctx;
|
||||||
|
if (_this->running) {
|
||||||
|
_this->client.setFrequency(freq);
|
||||||
|
}
|
||||||
|
_this->freq = freq;
|
||||||
|
spdlog::info("RTLTCPSourceModule '{0}': Tune: {1}!", _this->name, freq);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void menuHandler(void* ctx) {
|
||||||
|
RTLTCPSourceModule* _this = (RTLTCPSourceModule*)ctx;
|
||||||
|
float menuWidth = ImGui::GetContentRegionAvailWidth();
|
||||||
|
float portWidth = ImGui::CalcTextSize("00000").x + 20;
|
||||||
|
|
||||||
|
ImGui::SetNextItemWidth(menuWidth - portWidth);
|
||||||
|
ImGui::InputText(CONCAT("##_ip_select_", _this->name), _this->ip, 1024);
|
||||||
|
ImGui::SameLine();
|
||||||
|
ImGui::SetNextItemWidth(portWidth);
|
||||||
|
ImGui::InputInt(CONCAT("##_port_select_", _this->name), &_this->port, 0);
|
||||||
|
|
||||||
|
ImGui::SetNextItemWidth(ImGui::CalcTextSize("OOOOOOOOOO").x);
|
||||||
|
if (ImGui::Combo("Direct sampling", &_this->directSamplingMode, "Disabled\0I branch\0Q branch\0")) {
|
||||||
|
if (_this->running) {
|
||||||
|
_this->client.setDirectSampling(_this->directSamplingMode);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ImGui::Checkbox("RTL AGC", &_this->rtlAGC)) {
|
||||||
|
if (_this->running) {
|
||||||
|
_this->client.setAGCMode(_this->rtlAGC);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ImGui::Checkbox("Tuner AGC", &_this->tunerAGC)) {
|
||||||
|
if (_this->running) {
|
||||||
|
_this->client.setGainMode(!_this->tunerAGC);
|
||||||
|
if (!_this->tunerAGC) {
|
||||||
|
_this->client.setGainIndex(_this->gain);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_this->tunerAGC) { style::beginDisabled(); }
|
||||||
|
ImGui::SetNextItemWidth(menuWidth);
|
||||||
|
if (ImGui::SliderInt(CONCAT("##_gain_select_", _this->name), &_this->gain, 0, 29, "")) {
|
||||||
|
if (_this->running) {
|
||||||
|
_this->client.setGainIndex(_this->gain);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (_this->tunerAGC) { style::endDisabled(); }
|
||||||
|
}
|
||||||
|
|
||||||
|
static void worker(void* ctx) {
|
||||||
|
RTLTCPSourceModule* _this = (RTLTCPSourceModule*)ctx;
|
||||||
|
int blockSize = _this->sampleRate / 200.0;
|
||||||
|
uint8_t* inBuf = new uint8_t[blockSize * 2];
|
||||||
|
dsp::complex_t* outBuf = new dsp::complex_t[blockSize];
|
||||||
|
|
||||||
|
_this->stream.setMaxLatency(blockSize * 2);
|
||||||
|
|
||||||
|
while (true) {
|
||||||
|
// Read samples here
|
||||||
|
_this->client.receiveData(inBuf, blockSize * 2);
|
||||||
|
for (int i = 0; i < blockSize; i++) {
|
||||||
|
outBuf[i].q = ((double)inBuf[i * 2] - 128.0) / 128.0;
|
||||||
|
outBuf[i].i = ((double)inBuf[(i * 2) + 1] - 128.0) / 128.0;
|
||||||
|
}
|
||||||
|
if (_this->stream.write(outBuf, blockSize) < 0) { break; };
|
||||||
|
}
|
||||||
|
|
||||||
|
delete[] inBuf;
|
||||||
|
delete[] outBuf;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string name;
|
||||||
|
dsp::stream<dsp::complex_t> stream;
|
||||||
|
double sampleRate;
|
||||||
|
SourceManager::SourceHandler handler;
|
||||||
|
std::thread workerThread;
|
||||||
|
RTLTCPClient client;
|
||||||
|
bool running = false;
|
||||||
|
double freq;
|
||||||
|
char ip[1024] = "localhost";
|
||||||
|
int port = 1234;
|
||||||
|
int gain = 0;
|
||||||
|
bool rtlAGC = false;
|
||||||
|
bool tunerAGC = false;
|
||||||
|
int directSamplingMode = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
MOD_EXPORT void _INIT_() {
|
||||||
|
// Do your one time init here
|
||||||
|
}
|
||||||
|
|
||||||
|
MOD_EXPORT void* _CREATE_INSTANCE_(std::string name) {
|
||||||
|
return new RTLTCPSourceModule(name);
|
||||||
|
}
|
||||||
|
|
||||||
|
MOD_EXPORT void _DELETE_INSTANCE_(void* instance) {
|
||||||
|
delete (RTLTCPSourceModule*)instance;
|
||||||
|
}
|
||||||
|
|
||||||
|
MOD_EXPORT void _STOP_() {
|
||||||
|
// Do your one shutdown here
|
||||||
|
}
|
120
rtl_tcp_source/src/rtltcp_client.h
Normal file
120
rtl_tcp_source/src/rtltcp_client.h
Normal file
@ -0,0 +1,120 @@
|
|||||||
|
#pragma once
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <stdint.h>
|
||||||
|
#include <inttypes.h>
|
||||||
|
#include <WinSock2.h>
|
||||||
|
#include <WS2tcpip.h>
|
||||||
|
|
||||||
|
#ifdef _WIN32
|
||||||
|
#define __attribute__(x)
|
||||||
|
#pragma pack(push, 1)
|
||||||
|
#endif
|
||||||
|
struct command_t{
|
||||||
|
unsigned char cmd;
|
||||||
|
unsigned int param;
|
||||||
|
}__attribute__((packed));
|
||||||
|
#ifdef _WIN32
|
||||||
|
#pragma pack(pop)
|
||||||
|
#endif
|
||||||
|
|
||||||
|
class RTLTCPClient {
|
||||||
|
public:
|
||||||
|
RTLTCPClient() {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
bool connectToRTL(char* host, uint16_t port) {
|
||||||
|
struct addrinfo *result = NULL;
|
||||||
|
struct addrinfo *ptr = NULL;
|
||||||
|
struct addrinfo hints;
|
||||||
|
|
||||||
|
ZeroMemory( &hints, sizeof(hints) );
|
||||||
|
hints.ai_family = AF_UNSPEC;
|
||||||
|
hints.ai_socktype = SOCK_STREAM;
|
||||||
|
hints.ai_protocol = IPPROTO_TCP;
|
||||||
|
|
||||||
|
char buf[128];
|
||||||
|
sprintf(buf, "%hu", port);
|
||||||
|
|
||||||
|
int iResult = getaddrinfo(host, buf, &hints, &result);
|
||||||
|
if (iResult != 0) {
|
||||||
|
// TODO: log error
|
||||||
|
WSACleanup();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
ptr = result;
|
||||||
|
|
||||||
|
sock = socket(ptr->ai_family, ptr->ai_socktype, ptr->ai_protocol);
|
||||||
|
|
||||||
|
if (sock == INVALID_SOCKET) {
|
||||||
|
// TODO: log error
|
||||||
|
freeaddrinfo(result);
|
||||||
|
WSACleanup();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
iResult = connect(sock, ptr->ai_addr, (int)ptr->ai_addrlen);
|
||||||
|
if (iResult == SOCKET_ERROR) {
|
||||||
|
closesocket(sock);
|
||||||
|
freeaddrinfo(result);
|
||||||
|
WSACleanup();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
freeaddrinfo(result);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void disconnect() {
|
||||||
|
closesocket(sock);
|
||||||
|
WSACleanup();
|
||||||
|
}
|
||||||
|
|
||||||
|
// struct command_t {
|
||||||
|
// uint8_t cmd;
|
||||||
|
// uint32_t arg;
|
||||||
|
// };
|
||||||
|
|
||||||
|
void sendCommand(uint8_t command, uint32_t param) {
|
||||||
|
command_t cmd;
|
||||||
|
cmd.cmd = command;
|
||||||
|
cmd.param = htonl(param);
|
||||||
|
send(sock, (char*)&cmd, sizeof(command_t), 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
void receiveData(uint8_t* buf, size_t count) {
|
||||||
|
recv(sock, (char*)buf, count, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
void setFrequency(double freq) {
|
||||||
|
sendCommand(1, freq);
|
||||||
|
}
|
||||||
|
|
||||||
|
void setSampleRate(double sr) {
|
||||||
|
sendCommand(2, sr);
|
||||||
|
}
|
||||||
|
|
||||||
|
void setGainMode(int mode) {
|
||||||
|
sendCommand(3, mode);
|
||||||
|
}
|
||||||
|
|
||||||
|
void setGain(double gain) {
|
||||||
|
sendCommand(4, gain);
|
||||||
|
}
|
||||||
|
|
||||||
|
void setAGCMode(int mode) {
|
||||||
|
sendCommand(8, mode);
|
||||||
|
}
|
||||||
|
|
||||||
|
void setDirectSampling(int mode) {
|
||||||
|
sendCommand(9, mode);
|
||||||
|
}
|
||||||
|
|
||||||
|
void setGainIndex(int index) {
|
||||||
|
sendCommand(13, index);
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
SOCKET sock;
|
||||||
|
|
||||||
|
};
|
@ -7,6 +7,7 @@
|
|||||||
#include <SoapySDR/Modules.hpp>
|
#include <SoapySDR/Modules.hpp>
|
||||||
#include <SoapySDR/Logger.hpp>
|
#include <SoapySDR/Logger.hpp>
|
||||||
#include <core.h>
|
#include <core.h>
|
||||||
|
#include <gui/style.h>
|
||||||
|
|
||||||
#define CONCAT(a, b) ((std::string(a) + b).c_str())
|
#define CONCAT(a, b) ((std::string(a) + b).c_str())
|
||||||
|
|
||||||
@ -17,6 +18,8 @@ MOD_INFO {
|
|||||||
/* Version: */ "0.1.0"
|
/* Version: */ "0.1.0"
|
||||||
};
|
};
|
||||||
|
|
||||||
|
ConfigManager config;
|
||||||
|
|
||||||
class SoapyModule {
|
class SoapyModule {
|
||||||
public:
|
public:
|
||||||
SoapyModule(std::string name) {
|
SoapyModule(std::string name) {
|
||||||
@ -24,15 +27,18 @@ public:
|
|||||||
|
|
||||||
//TODO: Make module tune on source select change (in sdrpp_core)
|
//TODO: Make module tune on source select change (in sdrpp_core)
|
||||||
|
|
||||||
devList = SoapySDR::Device::enumerate();
|
uiGains = new float[1];
|
||||||
txtDevList = "";
|
|
||||||
for (auto& dev : devList) {
|
refresh();
|
||||||
txtDevList += dev["label"];
|
|
||||||
txtDevList += '\0';
|
|
||||||
}
|
|
||||||
|
|
||||||
stream.init(100);
|
stream.init(100);
|
||||||
|
|
||||||
|
// Select default device
|
||||||
|
config.aquire();
|
||||||
|
std::string devName = config.conf["device"];
|
||||||
|
config.release();
|
||||||
|
selectDevice(devName);
|
||||||
|
|
||||||
handler.ctx = this;
|
handler.ctx = this;
|
||||||
handler.selectHandler = menuSelected;
|
handler.selectHandler = menuSelected;
|
||||||
handler.deselectHandler = menuDeselected;
|
handler.deselectHandler = menuDeselected;
|
||||||
@ -42,10 +48,17 @@ public:
|
|||||||
handler.tuneHandler = tune;
|
handler.tuneHandler = tune;
|
||||||
handler.stream = &stream;
|
handler.stream = &stream;
|
||||||
sigpath::sourceManager.registerSource("SoapySDR", &handler);
|
sigpath::sourceManager.registerSource("SoapySDR", &handler);
|
||||||
spdlog::info("SoapyModule '{0}': Instance created!", name);
|
|
||||||
|
|
||||||
// Select default device
|
spdlog::info("SoapyModule '{0}': Instance created!", name);
|
||||||
selectDevice(devList[0]["label"]);
|
}
|
||||||
|
|
||||||
|
void refresh() {
|
||||||
|
devList = SoapySDR::Device::enumerate();
|
||||||
|
txtDevList = "";
|
||||||
|
for (auto& dev : devList) {
|
||||||
|
txtDevList += dev["label"];
|
||||||
|
txtDevList += '\0';
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
~SoapyModule() {
|
~SoapyModule() {
|
||||||
@ -54,6 +67,7 @@ public:
|
|||||||
|
|
||||||
private:
|
private:
|
||||||
void selectSampleRate(double samplerate) {
|
void selectSampleRate(double samplerate) {
|
||||||
|
spdlog::info("Setting sample rate to {0}", samplerate);
|
||||||
if (sampleRates.size() == 0) {
|
if (sampleRates.size() == 0) {
|
||||||
devId = -1;
|
devId = -1;
|
||||||
return;
|
return;
|
||||||
@ -65,6 +79,7 @@ private:
|
|||||||
srId = i;
|
srId = i;
|
||||||
sampleRate = sr;
|
sampleRate = sr;
|
||||||
found = true;
|
found = true;
|
||||||
|
core::setInputSampleRate(sampleRate);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
i++;
|
i++;
|
||||||
@ -98,15 +113,57 @@ private:
|
|||||||
}
|
}
|
||||||
|
|
||||||
SoapySDR::Device* dev = SoapySDR::Device::make(devArgs);
|
SoapySDR::Device* dev = SoapySDR::Device::make(devArgs);
|
||||||
|
|
||||||
|
gainList = dev->listGains(SOAPY_SDR_RX, 0);
|
||||||
|
delete[] uiGains;
|
||||||
|
uiGains = new float[gainList.size()];
|
||||||
|
for (auto gain : gainList) {
|
||||||
|
gainRanges.push_back(dev->getGainRange(SOAPY_SDR_RX, 0, gain));
|
||||||
|
}
|
||||||
|
|
||||||
sampleRates = dev->listSampleRates(SOAPY_SDR_RX, 0);
|
sampleRates = dev->listSampleRates(SOAPY_SDR_RX, 0);
|
||||||
txtSrList = "";
|
txtSrList = "";
|
||||||
for (double sr : sampleRates) {
|
for (double sr : sampleRates) {
|
||||||
txtSrList += std::to_string((int)sr);
|
txtSrList += std::to_string((int)sr);
|
||||||
txtSrList += '\0';
|
txtSrList += '\0';
|
||||||
}
|
}
|
||||||
SoapySDR::Device::unmake(dev);
|
|
||||||
selectSampleRate(sampleRates[0]);
|
|
||||||
|
|
||||||
|
SoapySDR::Device::unmake(dev);
|
||||||
|
|
||||||
|
config.aquire();
|
||||||
|
if (config.conf["devices"].contains(name)) {
|
||||||
|
int i = 0;
|
||||||
|
for (auto gain : gainList) {
|
||||||
|
if (config.conf["devices"][name]["gains"].contains(gain)) {
|
||||||
|
uiGains[i] = config.conf["devices"][name]["gains"][gain];
|
||||||
|
}
|
||||||
|
i++;
|
||||||
|
}
|
||||||
|
selectSampleRate(config.conf["devices"][name]["sampleRate"]);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
int i = 0;
|
||||||
|
for (auto gain : gainList) {
|
||||||
|
uiGains[i] = gainRanges[i].minimum();
|
||||||
|
i++;
|
||||||
|
}
|
||||||
|
selectSampleRate(sampleRates[0]); // Select default
|
||||||
|
}
|
||||||
|
config.release();
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
void saveCurrent() {
|
||||||
|
json conf;
|
||||||
|
conf["sampleRate"] = sampleRate;
|
||||||
|
int i = 0;
|
||||||
|
for (auto gain : gainList) {
|
||||||
|
conf["gains"][gain] = uiGains[i];
|
||||||
|
i++;
|
||||||
|
}
|
||||||
|
config.aquire();
|
||||||
|
config.conf["devices"][devArgs["label"]] = conf;
|
||||||
|
config.release(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void menuSelected(void* ctx) {
|
static void menuSelected(void* ctx) {
|
||||||
@ -126,7 +183,17 @@ private:
|
|||||||
static void start(void* ctx) {
|
static void start(void* ctx) {
|
||||||
SoapyModule* _this = (SoapyModule*)ctx;
|
SoapyModule* _this = (SoapyModule*)ctx;
|
||||||
_this->dev = SoapySDR::Device::make(_this->devArgs);
|
_this->dev = SoapySDR::Device::make(_this->devArgs);
|
||||||
|
|
||||||
_this->dev->setSampleRate(SOAPY_SDR_RX, 0, _this->sampleRate);
|
_this->dev->setSampleRate(SOAPY_SDR_RX, 0, _this->sampleRate);
|
||||||
|
|
||||||
|
int i = 0;
|
||||||
|
for (auto gain : _this->gainList) {
|
||||||
|
_this->dev->setGain(SOAPY_SDR_RX, 0, gain, _this->uiGains[i]);
|
||||||
|
i++;
|
||||||
|
}
|
||||||
|
|
||||||
|
_this->dev->setFrequency(SOAPY_SDR_RX, 0, _this->freq);
|
||||||
|
|
||||||
_this->devStream = _this->dev->setupStream(SOAPY_SDR_RX, "CF32");
|
_this->devStream = _this->dev->setupStream(SOAPY_SDR_RX, "CF32");
|
||||||
_this->dev->activateStream(_this->devStream);
|
_this->dev->activateStream(_this->devStream);
|
||||||
_this->running = true;
|
_this->running = true;
|
||||||
@ -137,6 +204,11 @@ private:
|
|||||||
static void stop(void* ctx) {
|
static void stop(void* ctx) {
|
||||||
SoapyModule* _this = (SoapyModule*)ctx;
|
SoapyModule* _this = (SoapyModule*)ctx;
|
||||||
_this->running = false;
|
_this->running = false;
|
||||||
|
_this->dev->deactivateStream(_this->devStream);
|
||||||
|
_this->dev->closeStream(_this->devStream);
|
||||||
|
_this->workerThread.join();
|
||||||
|
SoapySDR::Device::unmake(_this->dev);
|
||||||
|
|
||||||
spdlog::info("SoapyModule '{0}': Stop!", _this->name);
|
spdlog::info("SoapyModule '{0}': Stop!", _this->name);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -159,14 +231,56 @@ private:
|
|||||||
}
|
}
|
||||||
|
|
||||||
float menuWidth = ImGui::GetContentRegionAvailWidth();
|
float menuWidth = ImGui::GetContentRegionAvailWidth();
|
||||||
|
|
||||||
|
if (_this->running) { style::beginDisabled(); }
|
||||||
|
|
||||||
ImGui::SetNextItemWidth(menuWidth);
|
ImGui::SetNextItemWidth(menuWidth);
|
||||||
if (ImGui::Combo(CONCAT("##_dev_select_", _this->name), &_this->devId, _this->txtDevList.c_str())) {
|
if (ImGui::Combo(CONCAT("##_dev_select_", _this->name), &_this->devId, _this->txtDevList.c_str())) {
|
||||||
_this->selectDevice(_this->devList[_this->devId]["label"]);
|
_this->selectDevice(_this->devList[_this->devId]["label"]);
|
||||||
|
config.aquire();
|
||||||
|
config.conf["device"] = _this->devList[_this->devId]["label"];
|
||||||
|
config.release(true);
|
||||||
}
|
}
|
||||||
ImGui::SetNextItemWidth(menuWidth);
|
|
||||||
|
|
||||||
if (ImGui::Combo(CONCAT("##_sr_select_", _this->name), &_this->srId, _this->txtSrList.c_str())) {
|
if (ImGui::Combo(CONCAT("##_sr_select_", _this->name), &_this->srId, _this->txtSrList.c_str())) {
|
||||||
_this->selectSampleRate(_this->sampleRates[_this->srId]);
|
_this->selectSampleRate(_this->sampleRates[_this->srId]);
|
||||||
core::setInputSampleRate(_this->sampleRate);
|
_this->saveCurrent();
|
||||||
|
}
|
||||||
|
|
||||||
|
ImGui::SameLine();
|
||||||
|
float refreshBtnWdith = menuWidth - ImGui::GetCursorPosX();
|
||||||
|
if (ImGui::Button(CONCAT("Refresh##_dev_select_", _this->name), ImVec2(refreshBtnWdith, 0))) {
|
||||||
|
_this->refresh();
|
||||||
|
_this->selectDevice(config.conf["device"]);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_this->running) { style::endDisabled(); }
|
||||||
|
|
||||||
|
float gainNameLen = 0;
|
||||||
|
float len;
|
||||||
|
for (auto gain : _this->gainList) {
|
||||||
|
len = ImGui::CalcTextSize((gain + " gain").c_str()).x;
|
||||||
|
if (len > gainNameLen) {
|
||||||
|
gainNameLen = len;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
gainNameLen += 5.0f;
|
||||||
|
|
||||||
|
int i = 0;
|
||||||
|
for (auto gain : _this->gainList) {
|
||||||
|
ImGui::Text((gain + " gain").c_str());
|
||||||
|
ImGui::SameLine();
|
||||||
|
ImGui::SetCursorPosX(gainNameLen);
|
||||||
|
ImGui::SetNextItemWidth(menuWidth - gainNameLen);
|
||||||
|
if (ImGui::SliderFloat((gain + std::string("##_gain_sel_") + _this->name).c_str(), &_this->uiGains[i],
|
||||||
|
_this->gainRanges[i].minimum(), _this->gainRanges[i].maximum())) {
|
||||||
|
if (_this->running) {
|
||||||
|
_this->dev->setGain(SOAPY_SDR_RX, 0, gain, _this->uiGains[i]);
|
||||||
|
}
|
||||||
|
_this->saveCurrent();
|
||||||
|
}
|
||||||
|
i++;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -203,10 +317,18 @@ private:
|
|||||||
bool running = false;
|
bool running = false;
|
||||||
std::vector<double> sampleRates;
|
std::vector<double> sampleRates;
|
||||||
int srId = -1;
|
int srId = -1;
|
||||||
|
float* uiGains;
|
||||||
|
std::vector<std::string> gainList;
|
||||||
|
std::vector<SoapySDR::Range> gainRanges;
|
||||||
};
|
};
|
||||||
|
|
||||||
MOD_EXPORT void _INIT_() {
|
MOD_EXPORT void _INIT_() {
|
||||||
// Do your one time init here
|
config.setPath(ROOT_DIR "/soapy_source_config.json");
|
||||||
|
json defConf;
|
||||||
|
defConf["device"] = "";
|
||||||
|
defConf["devices"] = json({});
|
||||||
|
config.load(defConf);
|
||||||
|
config.enableAutoSave();
|
||||||
}
|
}
|
||||||
|
|
||||||
MOD_EXPORT void* _CREATE_INSTANCE_(std::string name) {
|
MOD_EXPORT void* _CREATE_INSTANCE_(std::string name) {
|
||||||
@ -218,5 +340,6 @@ MOD_EXPORT void _DELETE_INSTANCE_(void* instance) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
MOD_EXPORT void _STOP_() {
|
MOD_EXPORT void _STOP_() {
|
||||||
// Do your one shutdown here
|
config.disableAutoSave();
|
||||||
|
config.save();
|
||||||
}
|
}
|
Loading…
x
Reference in New Issue
Block a user