Skip to content

Commit 1f2b50c

Browse files
add beginning of IQ exporter module
1 parent f486c65 commit 1f2b50c

File tree

5 files changed

+312
-0
lines changed

5 files changed

+312
-0
lines changed

CMakeLists.txt

+5
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@ option(OPT_BUILD_WEATHER_SAT_DECODER "Build the HRPT decoder module (no dependen
5050
# Misc
5151
option(OPT_BUILD_DISCORD_PRESENCE "Build the Discord Rich Presence module" ON)
5252
option(OPT_BUILD_FREQUENCY_MANAGER "Build the Frequency Manager module" ON)
53+
option(OPT_BUILD_IQ_EXPORTER "Build the IQ Exporter module" OFF)
5354
option(OPT_BUILD_RECORDER "Audio and baseband recorder" ON)
5455
option(OPT_BUILD_RIGCTL_CLIENT "Rigctl client to make SDR++ act as a panadapter" ON)
5556
option(OPT_BUILD_RIGCTL_SERVER "Rigctl backend for controlling SDR++ with software like gpredict" ON)
@@ -257,6 +258,10 @@ if (OPT_BUILD_FREQUENCY_MANAGER)
257258
add_subdirectory("misc_modules/frequency_manager")
258259
endif (OPT_BUILD_FREQUENCY_MANAGER)
259260

261+
if (OPT_BUILD_IQ_EXPORTER)
262+
add_subdirectory("misc_modules/iq_exporter")
263+
endif (OPT_BUILD_IQ_EXPORTER)
264+
260265
if (OPT_BUILD_RECORDER)
261266
add_subdirectory("misc_modules/recorder")
262267
endif (OPT_BUILD_RECORDER)

make_windows_package.ps1

+2
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,8 @@ cp $build_dir/misc_modules/discord_integration/Release/discord_integration.dll s
7979

8080
cp $build_dir/misc_modules/frequency_manager/Release/frequency_manager.dll sdrpp_windows_x64/modules/
8181

82+
cp $build_dir/misc_modules/iq_exporter/Release/iq_exporter.dll sdrpp_windows_x64/modules/
83+
8284
cp $build_dir/misc_modules/recorder/Release/recorder.dll sdrpp_windows_x64/modules/
8385

8486
cp $build_dir/misc_modules/rigctl_client/Release/rigctl_client.dll sdrpp_windows_x64/modules/
+6
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
cmake_minimum_required(VERSION 3.13)
2+
project(iq_exporter)
3+
4+
file(GLOB SRC "src/*.cpp")
5+
6+
include(${SDRPP_MODULE_CMAKE})

misc_modules/iq_exporter/src/main.cpp

+298
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,298 @@
1+
#include <utils/net.h>
2+
#include <imgui.h>
3+
#include <module.h>
4+
#include <gui/gui.h>
5+
#include <gui/style.h>
6+
#include <utils/optionlist.h>
7+
#include <algorithm>
8+
#include <dsp/sink/handler_sink.h>
9+
#include <volk/volk.h>
10+
#include <signal_path/signal_path.h>
11+
#include <core.h>
12+
13+
SDRPP_MOD_INFO{
14+
/* Name: */ "iq_exporter",
15+
/* Description: */ "Export raw IQ through TCP or UDP",
16+
/* Author: */ "Ryzerth",
17+
/* Version: */ 0, 1, 0,
18+
/* Max instances */ -1
19+
};
20+
21+
ConfigManager config;
22+
23+
enum Mode {
24+
MODE_BASEBAND,
25+
MODE_VFO
26+
};
27+
28+
enum Protocol {
29+
PROTOCOL_TCP,
30+
PROTOCOL_UDP
31+
};
32+
33+
enum SampleType {
34+
SAMPLE_TYPE_INT8,
35+
SAMPLE_TYPE_INT16,
36+
SAMPLE_TYPE_INT32,
37+
SAMPLE_TYPE_FLOAT32
38+
};
39+
40+
class IQExporterModule : public ModuleManager::Instance {
41+
public:
42+
IQExporterModule(std::string name) {
43+
this->name = name;
44+
45+
// Define operating modes
46+
modes.define("Baseband", MODE_BASEBAND);
47+
modes.define("VFO", MODE_VFO);
48+
49+
// Define protocols
50+
protocols.define("TCP", PROTOCOL_TCP);
51+
protocols.define("UDP", PROTOCOL_UDP);
52+
53+
// Define sample types
54+
sampleTypes.define("Int8", SAMPLE_TYPE_INT8);
55+
sampleTypes.define("Int16", SAMPLE_TYPE_INT16);
56+
sampleTypes.define("Int32", SAMPLE_TYPE_INT32);
57+
sampleTypes.define("Float32", SAMPLE_TYPE_FLOAT32);
58+
59+
// Load config
60+
bool autoStart = false;
61+
config.acquire();
62+
if (config.conf[name].contains("mode")) {
63+
std::string modeStr = config.conf[name]["mode"];
64+
if (modes.keyExists(modeStr)) { mode = modes.value(modes.keyId(modeStr)); }
65+
}
66+
if (config.conf[name].contains("protocol")) {
67+
std::string protoStr = config.conf[name]["protocol"];
68+
if (protocols.keyExists(protoStr)) { proto = protocols.value(protocols.keyId(protoStr)); }
69+
}
70+
if (config.conf[name].contains("sampleType")) {
71+
std::string sampTypeStr = config.conf[name]["sampleType"];
72+
if (sampleTypes.keyExists(sampTypeStr)) { sampType = sampleTypes.value(sampleTypes.keyId(sampTypeStr)); }
73+
}
74+
if (config.conf[name].contains("host")) {
75+
std::string hostStr = config.conf[name]["host"];
76+
strcpy(hostname, hostStr.c_str());
77+
}
78+
if (config.conf[name].contains("port")) {
79+
port = config.conf[name]["port"];
80+
port = std::clamp<int>(port, 1, 65535);
81+
}
82+
if (config.conf[name].contains("running")) {
83+
autoStart = config.conf[name]["running"];
84+
}
85+
config.release();
86+
87+
// Set menu IDs
88+
modeId = modes.valueId(mode);
89+
protoId = protocols.valueId(proto);
90+
sampTypeId = sampleTypes.valueId(sampType);
91+
92+
// Allocate buffer
93+
buffer = dsp::buffer::alloc<uint8_t>(STREAM_BUFFER_SIZE * sizeof(dsp::complex_t));
94+
95+
// Init DSP
96+
handler.init(NULL, dataHandler, this);
97+
98+
// Register menu entry
99+
gui::menu.registerEntry(name, menuHandler, this, this);
100+
}
101+
102+
~IQExporterModule() {
103+
// Un-register menu entry
104+
gui::menu.removeEntry(name);
105+
106+
// Free buffer
107+
dsp::buffer::free(buffer);
108+
}
109+
110+
void postInit() {}
111+
112+
void enable() {
113+
enabled = true;
114+
}
115+
116+
void disable() {
117+
enabled = false;
118+
}
119+
120+
bool isEnabled() {
121+
return enabled;
122+
}
123+
124+
void start() {
125+
if (running) { return; }
126+
127+
// TODO
128+
129+
running = true;
130+
}
131+
132+
void stop() {
133+
if (!running) { return; }
134+
135+
// TODO
136+
137+
running = false;
138+
}
139+
140+
private:
141+
static void menuHandler(void* ctx) {
142+
IQExporterModule* _this = (IQExporterModule*)ctx;
143+
float menuWidth = ImGui::GetContentRegionAvail().x;
144+
145+
if (!_this->enabled) { ImGui::BeginDisabled(); }
146+
147+
if (_this->running) { ImGui::BeginDisabled(); }
148+
149+
// Mode selector
150+
ImGui::LeftLabel("Mode");
151+
ImGui::FillWidth();
152+
if (ImGui::Combo(("##iq_exporter_mode_" + _this->name).c_str(), &_this->modeId, _this->modes.txt)) {
153+
_this->setMode(_this->modes.value(_this->modeId));
154+
config.acquire();
155+
config.conf[_this->name]["mode"] = _this->modes.key(_this->modeId);
156+
config.release(true);
157+
}
158+
159+
// Mode protocol selector
160+
ImGui::LeftLabel("Protocol");
161+
ImGui::FillWidth();
162+
if (ImGui::Combo(("##iq_exporter_proto_" + _this->name).c_str(), &_this->protoId, _this->protocols.txt)) {
163+
config.acquire();
164+
config.conf[_this->name]["protocol"] = _this->protocols.key(_this->protoId);
165+
config.release(true);
166+
}
167+
168+
// Sample type selector
169+
ImGui::LeftLabel("Sample type");
170+
ImGui::FillWidth();
171+
if (ImGui::Combo(("##iq_exporter_samp_" + _this->name).c_str(), &_this->sampTypeId, _this->sampleTypes.txt)) {
172+
config.acquire();
173+
config.conf[_this->name]["sampleType"] = _this->sampleTypes.key(_this->sampTypeId);
174+
config.release(true);
175+
}
176+
177+
// Hostname and port field
178+
if (ImGui::InputText(("##iq_exporter_host_" + _this->name).c_str(), _this->hostname, sizeof(_this->hostname))) {
179+
config.acquire();
180+
config.conf[_this->name]["host"] = _this->hostname;
181+
config.release(true);
182+
}
183+
ImGui::SameLine();
184+
ImGui::FillWidth();
185+
if (ImGui::InputInt(("##iq_exporter_port_" + _this->name).c_str(), &_this->port, 0, 0)) {
186+
_this->port = std::clamp<int>(_this->port, 1, 65535);
187+
config.acquire();
188+
config.conf[_this->name]["port"] = _this->port;
189+
config.release(true);
190+
}
191+
192+
if (_this->running) { ImGui::EndDisabled(); }
193+
194+
// Start/Stop buttons
195+
if (_this->running) {
196+
if (ImGui::Button(("Stop##iq_exporter_stop_" + _this->name).c_str(), ImVec2(menuWidth, 0))) {
197+
_this->stop();
198+
config.acquire();
199+
config.conf[_this->name]["running"] = false;
200+
config.release(true);
201+
}
202+
}
203+
else {
204+
if (ImGui::Button(("Start##iq_exporter_start_" + _this->name).c_str(), ImVec2(menuWidth, 0))) {
205+
_this->start();
206+
config.acquire();
207+
config.conf[_this->name]["running"] = true;
208+
config.release(true);
209+
}
210+
}
211+
212+
if (!_this->enabled) { ImGui::EndDisabled(); }
213+
}
214+
215+
void setMode(Mode newMode) {
216+
// Delete VFO or unbind IQ stream
217+
218+
}
219+
220+
static void dataHandler(dsp::complex_t* data, int count, void* ctx) {
221+
IQExporterModule* _this = (IQExporterModule*)ctx;
222+
223+
// Acquire lock on socket
224+
std::lock_guard lck(_this->sockMtx);
225+
226+
// If not valid or open, give uo
227+
if (!_this->sock || !_this->sock->isOpen()) { return; }
228+
229+
// Convert the samples or send directory for float32
230+
int size;
231+
switch (_this->sampType) {
232+
case SAMPLE_TYPE_INT8:
233+
volk_32f_s32f_convert_8i((int8_t*)_this->buffer, (float*)data, 128.0f, count*2);
234+
size = sizeof(int8_t)*2;
235+
break;
236+
case SAMPLE_TYPE_INT16:
237+
volk_32fc_convert_16ic((lv_16sc_t*)_this->buffer, (lv_32fc_t*)data, count);
238+
size = sizeof(int16_t)*2;
239+
break;
240+
case SAMPLE_TYPE_INT32:
241+
volk_32f_s32f_convert_32i((int32_t*)_this->buffer, (float*)data, (float)2147483647.0f, count*2);
242+
size = sizeof(int32_t)*2;
243+
break;
244+
case SAMPLE_TYPE_FLOAT32:
245+
_this->sock->send((uint8_t*)data, count*sizeof(dsp::complex_t));
246+
default:
247+
return;
248+
}
249+
250+
// Send converted samples
251+
_this->sock->send(_this->buffer, count*size);
252+
}
253+
254+
std::string name;
255+
bool enabled = true;
256+
257+
Mode mode = MODE_BASEBAND;
258+
int modeId;
259+
Protocol proto = PROTOCOL_TCP;
260+
int protoId;
261+
SampleType sampType = SAMPLE_TYPE_INT16;
262+
int sampTypeId;
263+
char hostname[1024] = "localhost";
264+
int port = 1234;
265+
bool running = false;
266+
267+
OptionList<std::string, Mode> modes;
268+
OptionList<std::string, Protocol> protocols;
269+
OptionList<std::string, SampleType> sampleTypes;
270+
271+
VFOManager::VFO* vfo = NULL;
272+
dsp::sink::Handler<dsp::complex_t> handler;
273+
uint8_t* buffer = NULL;
274+
275+
std::mutex sockMtx;
276+
std::shared_ptr<net::Socket> sock;
277+
};
278+
279+
MOD_EXPORT void _INIT_() {
280+
json def = json({});
281+
std::string root = (std::string)core::args["root"];
282+
config.setPath(root + "/iq_exporter_config_config.json");
283+
config.load(def);
284+
config.enableAutoSave();
285+
}
286+
287+
MOD_EXPORT ModuleManager::Instance* _CREATE_INSTANCE_(std::string name) {
288+
return new IQExporterModule(name);
289+
}
290+
291+
MOD_EXPORT void _DELETE_INSTANCE_(void* instance) {
292+
delete (IQExporterModule*)instance;
293+
}
294+
295+
MOD_EXPORT void _END_() {
296+
config.disableAutoSave();
297+
config.save();
298+
}

readme.md

+1
Original file line numberDiff line numberDiff line change
@@ -377,6 +377,7 @@ Modules in beta are still included in releases for the most part but not enabled
377377
|---------------------|------------|--------------|-----------------------------|:----------------:|:----------------:|:---------------------------:|
378378
| discord_integration | Working | - | OPT_BUILD_DISCORD_PRESENCE ||||
379379
| frequency_manager | Working | - | OPT_BUILD_FREQUENCY_MANAGER ||||
380+
| iq_exporter | Unfinished | - | OPT_BUILD_IQ_EXPORTER ||||
380381
| recorder | Working | - | OPT_BUILD_RECORDER ||||
381382
| rigctl_client | Unfinished | - | OPT_BUILD_RIGCTL_CLIENT ||||
382383
| rigctl_server | Working | - | OPT_BUILD_RIGCTL_SERVER ||||

0 commit comments

Comments
 (0)