@@ -21,6 +21,7 @@ SDRPP_MOD_INFO{
21
21
ConfigManager config;
22
22
23
23
enum Mode {
24
+ MODE_NONE = -1 ,
24
25
MODE_BASEBAND,
25
26
MODE_VFO
26
27
};
@@ -46,6 +47,20 @@ class IQExporterModule : public ModuleManager::Instance {
46
47
modes.define (" Baseband" , MODE_BASEBAND);
47
48
modes.define (" VFO" , MODE_VFO);
48
49
50
+ // Define VFO samplerates
51
+ for (int i = 3000 ; i <= 192000 ; i <<= 1 ) {
52
+ samplerates.define (i, getSrScaled (i), i);
53
+ }
54
+ for (int i = 250000 ; i < 1000000 ; i += 250000 ) {
55
+ samplerates.define (i, getSrScaled (i), i);
56
+ }
57
+ for (int i = 1000000 ; i < 10000000 ; i += 500000 ) {
58
+ samplerates.define (i, getSrScaled (i), i);
59
+ }
60
+ for (int i = 10000000 ; i <= 100000000 ; i += 5000000 ) {
61
+ samplerates.define (i, getSrScaled (i), i);
62
+ }
63
+
49
64
// Define protocols
50
65
protocols.define (" TCP" , PROTOCOL_TCP);
51
66
protocols.define (" UDP" , PROTOCOL_UDP);
@@ -58,10 +73,15 @@ class IQExporterModule : public ModuleManager::Instance {
58
73
59
74
// Load config
60
75
bool autoStart = false ;
76
+ Mode nMode = MODE_BASEBAND;
61
77
config.acquire ();
62
78
if (config.conf [name].contains (" mode" )) {
63
79
std::string modeStr = config.conf [name][" mode" ];
64
- if (modes.keyExists (modeStr)) { mode = modes.value (modes.keyId (modeStr)); }
80
+ if (modes.keyExists (modeStr)) { nMode = modes.value (modes.keyId (modeStr)); }
81
+ }
82
+ if (config.conf [name].contains (" samplerate" )) {
83
+ int sr = config.conf [name][" samplerate" ];
84
+ if (samplerates.keyExists (sr)) { samplerate = samplerates.value (samplerates.keyId (sr)); }
65
85
}
66
86
if (config.conf [name].contains (" protocol" )) {
67
87
std::string protoStr = config.conf [name][" protocol" ];
@@ -85,15 +105,22 @@ class IQExporterModule : public ModuleManager::Instance {
85
105
config.release ();
86
106
87
107
// Set menu IDs
88
- modeId = modes.valueId (mode);
108
+ modeId = modes.valueId (nMode);
109
+ srId = samplerates.valueId (samplerate);
89
110
protoId = protocols.valueId (proto);
90
111
sampTypeId = sampleTypes.valueId (sampType);
91
112
92
113
// Allocate buffer
93
114
buffer = dsp::buffer::alloc<uint8_t >(STREAM_BUFFER_SIZE * sizeof (dsp::complex_t ));
94
115
95
116
// Init DSP
96
- handler.init (NULL , dataHandler, this );
117
+ handler.init (&iqStream, dataHandler, this );
118
+
119
+ // Set operating mode
120
+ setMode (nMode);
121
+
122
+ // Start if needed
123
+ if (autoStart) { start (); }
97
124
98
125
// Register menu entry
99
126
gui::menu.registerEntry (name, menuHandler, this , this );
@@ -103,6 +130,12 @@ class IQExporterModule : public ModuleManager::Instance {
103
130
// Un-register menu entry
104
131
gui::menu.removeEntry (name);
105
132
133
+ // Stop networking
134
+ stop ();
135
+
136
+ // Stop DSP
137
+ setMode (MODE_NONE);
138
+
106
139
// Free buffer
107
140
dsp::buffer::free (buffer);
108
141
}
@@ -124,20 +157,81 @@ class IQExporterModule : public ModuleManager::Instance {
124
157
void start () {
125
158
if (running) { return ; }
126
159
127
- // TODO
160
+ // Acquire lock on the socket
161
+ std::lock_guard lck1 (sockMtx);
162
+
163
+ // Start listening or open UDP socket
164
+ try {
165
+ if (proto == PROTOCOL_TCP) {
166
+ // Create listener
167
+ listener = net::listen (hostname, port);
168
+
169
+ // Start listen worker
170
+ listenWorkerThread = std::thread (&IQExporterModule::listenWorker, this );
171
+ }
172
+ else {
173
+ // Open UDP socket
174
+ sock = net::openudp (hostname, port, " 0.0.0.0" , 0 , true );
175
+ }
176
+ }
177
+ catch (const std::exception & e) {
178
+ flog::error (" [IQExporter] Could not start socket: {}" , e.what ());
179
+ }
128
180
129
181
running = true ;
130
182
}
131
183
132
184
void stop () {
133
185
if (!running) { return ; }
134
186
135
- // TODO
187
+ // Acquire lock on the socket
188
+ std::lock_guard lck1 (sockMtx);
189
+
190
+ // Stop listening or close UDP socket
191
+ if (proto == PROTOCOL_TCP) {
192
+ // Stop listener
193
+ if (listener) {
194
+ listener->stop ();
195
+ }
196
+
197
+ // Wait for worker to stop
198
+ if (listenWorkerThread.joinable ()) { listenWorkerThread.join (); }
199
+
200
+ // Free listener
201
+ listener.reset ();
202
+
203
+ // Close socket and free it
204
+ if (sock) {
205
+ sock->close ();
206
+ sock.reset ();
207
+ }
208
+ }
209
+ else {
210
+ // Close UDP socket and free it
211
+ if (sock) {
212
+ sock->close ();
213
+ sock.reset ();
214
+ }
215
+ }
136
216
137
217
running = false ;
138
218
}
139
219
140
220
private:
221
+ std::string getSrScaled (double sr) {
222
+ char buf[1024 ];
223
+ if (sr >= 1000000.0 ) {
224
+ sprintf (buf, " %.1lf MS/s" , sr / 1000000.0 );
225
+ }
226
+ else if (sr >= 1000.0 ) {
227
+ sprintf (buf, " %.1lf KS/s" , sr / 1000.0 );
228
+ }
229
+ else {
230
+ sprintf (buf, " %.1lf S/s" , sr);
231
+ }
232
+ return std::string (buf);
233
+ }
234
+
141
235
static void menuHandler (void * ctx) {
142
236
IQExporterModule* _this = (IQExporterModule*)ctx;
143
237
float menuWidth = ImGui::GetContentRegionAvail ().x ;
@@ -156,10 +250,27 @@ class IQExporterModule : public ModuleManager::Instance {
156
250
config.release (true );
157
251
}
158
252
253
+ // In VFO mode, show samplerate selector
254
+ if (_this->mode == MODE_VFO) {
255
+ ImGui::LeftLabel (" Samplerate" );
256
+ ImGui::FillWidth ();
257
+ if (ImGui::Combo ((" ##iq_exporter_sr_" + _this->name ).c_str (), &_this->srId , _this->samplerates .txt )) {
258
+ _this->samplerate = _this->samplerates .value (_this->srId );
259
+ if (_this->vfo ) {
260
+ _this->vfo ->setBandwidthLimits (_this->samplerate , _this->samplerate , true );
261
+ _this->vfo ->setSampleRate (_this->samplerate , _this->samplerate );
262
+ }
263
+ config.acquire ();
264
+ config.conf [_this->name ][" samplerate" ] = _this->samplerates .key (_this->srId );
265
+ config.release (true );
266
+ }
267
+ }
268
+
159
269
// Mode protocol selector
160
270
ImGui::LeftLabel (" Protocol" );
161
271
ImGui::FillWidth ();
162
272
if (ImGui::Combo ((" ##iq_exporter_proto_" + _this->name ).c_str (), &_this->protoId , _this->protocols .txt )) {
273
+ _this->proto = _this->protocols .value (_this->protoId );
163
274
config.acquire ();
164
275
config.conf [_this->name ][" protocol" ] = _this->protocols .key (_this->protoId );
165
276
config.release (true );
@@ -169,6 +280,7 @@ class IQExporterModule : public ModuleManager::Instance {
169
280
ImGui::LeftLabel (" Sample type" );
170
281
ImGui::FillWidth ();
171
282
if (ImGui::Combo ((" ##iq_exporter_samp_" + _this->name ).c_str (), &_this->sampTypeId , _this->sampleTypes .txt )) {
283
+ _this->sampType = _this->sampleTypes .value (_this->sampTypeId );
172
284
config.acquire ();
173
285
config.conf [_this->name ][" sampleType" ] = _this->sampleTypes .key (_this->sampTypeId );
174
286
config.release (true );
@@ -209,12 +321,94 @@ class IQExporterModule : public ModuleManager::Instance {
209
321
}
210
322
}
211
323
324
+ // Status text
325
+ ImGui::TextUnformatted (" Status:" );
326
+ ImGui::SameLine ();
327
+ if (_this->sock && _this->sock ->isOpen ()) {
328
+ ImGui::TextColored (ImVec4 (0.0 , 1.0 , 0.0 , 1.0 ), (_this->proto == PROTOCOL_TCP) ? " Connected" : " Sending" );
329
+ }
330
+ else if (_this->listener && _this->listener ->listening ()) {
331
+ ImGui::TextColored (ImVec4 (1.0 , 1.0 , 0.0 , 1.0 ), " Listening" );
332
+ }
333
+ else {
334
+ ImGui::TextUnformatted (" Idle" );
335
+ }
336
+
212
337
if (!_this->enabled ) { ImGui::EndDisabled (); }
213
338
}
214
339
215
340
void setMode (Mode newMode) {
341
+ // If there is no mode to change, do nothing
342
+ flog::debug (" Mode change" );
343
+ if (mode == newMode) {
344
+ flog::debug (" New mode same as existing mode, doing nothing" );
345
+ return ;
346
+ }
347
+
348
+ // Stop the DSP
349
+ flog::debug (" Stopping DSP" );
350
+ handler.stop ();
351
+
216
352
// Delete VFO or unbind IQ stream
353
+ if (vfo) {
354
+ flog::debug (" Deleting old VFO" );
355
+ sigpath::vfoManager.deleteVFO (vfo);
356
+ vfo = NULL ;
357
+ }
358
+ if (mode == MODE_BASEBAND) {
359
+ flog::debug (" Unbinding old stream" );
360
+ sigpath::iqFrontEnd.unbindIQStream (&iqStream);
361
+ }
217
362
363
+ // If the mode was none, we're done
364
+ if (newMode == MODE_NONE) {
365
+ flog::debug (" Exiting, new mode is NONE" );
366
+ return ;
367
+ }
368
+
369
+ // Create VFO or bind IQ stream
370
+ if (newMode == MODE_VFO) {
371
+ flog::debug (" Creating new VFO" );
372
+ // Create VFO
373
+ vfo = sigpath::vfoManager.createVFO (name, ImGui::WaterfallVFO::REF_CENTER, 0 , samplerate, samplerate, samplerate, samplerate, true );
374
+
375
+ // Set its output as the input to the DSP
376
+ handler.setInput (vfo->output );
377
+ }
378
+ else {
379
+ flog::debug (" Binding IQ stream" );
380
+ // Bind IQ stream
381
+ sigpath::iqFrontEnd.bindIQStream (&iqStream);
382
+
383
+ // Set its output as the input to the DSP
384
+ handler.setInput (&iqStream);
385
+ }
386
+
387
+ // Start DSP
388
+ flog::debug (" Starting DSP" );
389
+ handler.start ();
390
+
391
+ // Update mode
392
+ flog::debug (" Updating mode" );
393
+ mode = newMode;
394
+ modeId = modes.valueId (newMode);
395
+ }
396
+
397
+ void listenWorker () {
398
+ while (true ) {
399
+ // Accept a client
400
+ auto newSock = listener->accept ();
401
+ if (!newSock) { break ; }
402
+
403
+ // Update socket
404
+ {
405
+ std::lock_guard lck (sockMtx);
406
+ sock = newSock;
407
+ }
408
+
409
+ // Wait until disconnection
410
+ // TODO
411
+ }
218
412
}
219
413
220
414
static void dataHandler (dsp::complex_t * data, int count, void * ctx) {
@@ -254,8 +448,10 @@ class IQExporterModule : public ModuleManager::Instance {
254
448
std::string name;
255
449
bool enabled = true ;
256
450
257
- Mode mode = MODE_BASEBAND ;
451
+ Mode mode = MODE_NONE ;
258
452
int modeId;
453
+ int samplerate = 1000000.0 ;
454
+ int srId;
259
455
Protocol proto = PROTOCOL_TCP;
260
456
int protoId;
261
457
SampleType sampType = SAMPLE_TYPE_INT16;
@@ -265,15 +461,20 @@ class IQExporterModule : public ModuleManager::Instance {
265
461
bool running = false ;
266
462
267
463
OptionList<std::string, Mode> modes;
464
+ OptionList<int , int > samplerates;
268
465
OptionList<std::string, Protocol> protocols;
269
466
OptionList<std::string, SampleType> sampleTypes;
270
467
271
468
VFOManager::VFO* vfo = NULL ;
469
+ dsp::stream<dsp::complex_t > iqStream;
272
470
dsp::sink::Handler<dsp::complex_t > handler;
273
471
uint8_t * buffer = NULL ;
274
472
473
+ std::thread listenWorkerThread;
474
+
275
475
std::mutex sockMtx;
276
476
std::shared_ptr<net::Socket> sock;
477
+ std::shared_ptr<net::Listener> listener;
277
478
};
278
479
279
480
MOD_EXPORT void _INIT_ () {
0 commit comments