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
+ }
0 commit comments