Set dynamic range noise smoothing factor\n"
+" (0..1, smaller=more smoothing)\n"
+"--adaptive-range-percentile Set dynamic range noise percentile\n"
+"--adaptive-range-scan-delay Set data collection interval for dynamic\n"
+" range gain scanning (seconds)\n"
+"--adaptive-range-rescan-delay Set rescan interval for dynamic range\n"
+" gain scanning (seconds)\n"
+"--adaptive-min-gain Set gain adjustment range lower limit (dB)\n"
+"--adaptive-max-gain Set gain adjustment range upper limit (dB)\n"
+"\n"
+// ------ 80 char limit ----------------------------------------------------------|
+" Network connections\n"
+"\n"
"--net Enable networking with default ports unless overridden\n"
-"--modeac Enable decoding of SSR Modes 3/A & 3/C\n"
-"--no-modeac-auto Don't enable Mode A/C if requested by a Beast connection\n"
+"--no-modeac-auto Don't enable Mode A/C if requested by a net connection\n"
"--net-only Enable just networking, no RTL device or file used\n"
-"--net-bind-address IP address to bind to (default: Any; Use 127.0.0.1 for private)\n"
+"--net-bind-address IP address to bind to (use 127.0.0.1 for private)\n"
"--net-ri-port TCP raw input listen ports (default: 30001)\n"
"--net-ro-port TCP raw output listen ports (default: 30002)\n"
"--net-sbs-port TCP BaseStation output listen ports (default: 30003)\n"
"--net-bi-port TCP Beast input listen ports (default: 30004,30104)\n"
"--net-bo-port TCP Beast output listen ports (default: 30005)\n"
-"--net-stratux-port TCP Stratux output listen ports (default: disabled)\n"
+"--net-stratux-port TCP Stratux output listen ports (default: disabled)\n"
"--net-ro-size TCP output minimum size (default: 0)\n"
"--net-ro-interval TCP output memory flush rate in seconds (default: 0)\n"
-"--net-heartbeat TCP heartbeat rate in seconds (default: 60 sec; 0 to disable)\n"
+"--net-heartbeat TCP heartbeat rate in seconds\n"
+" (default: 60 sec; 0 to disable)\n"
"--net-buffer TCP buffer size 64Kb * (2^n) (default: n=0, 64Kb)\n"
-"--net-verbatim Make Beast-format output connections default to verbatim mode\n"
-" (forward all messages, without applying CRC corrections)\n"
-"--forward-mlat Allow forwarding of received mlat results to output ports\n"
-"--lat Reference/receiver latitude for surface posn (opt)\n"
-"--lon Reference/receiver longitude for surface posn (opt)\n"
-"--max-range Absolute maximum range for position decoding (in nm, default: 300)\n"
-"--fix Enable single-bit error correction using CRC\n"
-"--fix-2bit Enable two-bit error correction using CRC (use with caution)\n"
-"--no-fix Disable error correction using CRC\n"
-"--no-fix-df Disable error correction of the DF message field (reduces CPU requirements)\n"
-"--no-crc-check Disable messages with broken CRC (discouraged)\n"
-"--enable-df24 Enable decoding of DF24 Comm-D ELM messages\n"
-"--mlat display raw messages in Beast ascii mode\n"
-"--stats With --ifile print stats at exit. No other output\n"
-"--stats-range Collect/show range histogram\n"
+"--net-verbatim Make output connections default to verbatim mode\n"
+" (forward all messages without correction)\n"
+"--forward-mlat Allow forwarding of received mlat results\n"
+"\n"
+// ------ 80 char limit ----------------------------------------------------------|
+" Stats and json output\n"
+"\n"
+"--stats Show stats summary at exit.\n"
"--stats-every Show and reset stats every seconds\n"
-"--onlyaddr Show only ICAO addresses (testing purposes)\n"
-"--metric Use metric units (meters, km/h, ...)\n"
-"--gnss Show altitudes as HAE/GNSS (with H suffix) when available\n"
-"--snip Strip IQ file removing samples < level\n"
-"--quiet Disable output to stdout. Use for daemon applications\n"
-"--show-only Show only messages from the given ICAO on stdout\n"
-"--write-json Periodically write json output to (for serving by a separate webserver)\n"
+"--stats-range Collect/show range histogram\n"
+"--write-json Periodically write json output to \n"
+" (for serving by a separate webserver)\n"
"--write-json-every Write json aircraft output every t seconds (default 1)\n"
"--json-stats-every Write json stats output every t seconds (default 60)\n"
-"--json-location-accuracy Accuracy of receiver location in json metadata: 0=no location, 1=approximate, 2=exact\n"
-#if 0
-"--dcfilter Apply a 1Hz DC filter to input data (requires more CPU)\n"
-#endif
-"--wisdom Read DSP wisdom from given path\n"
+"--json-location-accuracy Accuracy of receiver location in json metadata\n"
+" (0=no location, 1=approximate, 2=exact)\n"
+"\n"
+" Interactive mode\n"
+"\n"
+"--interactive Interactive mode refreshing data on screen.\n"
+" Implies --throttle\n"
+"--interactive-ttl Remove from list if idle for \n"
+"--interactive-show-distance Show aircraft distance and bearing\n"
+" (requires --lat and --lon)\n"
+"--interactive-distance-units Distance units ('km', 'sm', 'nm')\n"
+"--interactive-callsign-filter Filter rows by callsign against regex\n"
+"\n"
+" Misc\n"
+"\n"
"--version Show version, build and DSP options\n"
"--help Show this help\n"
);
@@ -363,7 +425,8 @@ static void showHelp(void)
// Accumulate stats data from stats_current to stats_periodic, stats_alltime and stats_latest;
// reset stats_current
-static void flush_stats(uint64_t now)
+void flush_stats(uint64_t now);
+void flush_stats(uint64_t now)
{
add_stats(&Modes.stats_current, &Modes.stats_periodic, &Modes.stats_periodic);
add_stats(&Modes.stats_current, &Modes.stats_alltime, &Modes.stats_alltime);
@@ -534,7 +597,7 @@ int main(int argc, char **argv) {
} else if ( (!strcmp(argv[j], "--device") || !strcmp(argv[j], "--device-index")) && more) {
Modes.dev_name = strdup(argv[++j]);
} else if (!strcmp(argv[j],"--gain") && more) {
- Modes.gain = (int) (atof(argv[++j])*10); // Gain is in tens of DBs
+ Modes.gain = atof(argv[++j]);
} else if (!strcmp(argv[j],"--dcfilter")) {
#if 0
Modes.dc_filter = 1;
@@ -693,7 +756,37 @@ int main(int argc, char **argv) {
fprintf(stderr,
"Failed to read wisdom file %s: %s\n", argv[j], strerror(errno));
exit(1);
- }
+ }
+ } else if (!strcmp(argv[j], "--adaptive-min-gain") && more) {
+ Modes.adaptive_min_gain_db = atof(argv[++j]);
+ } else if (!strcmp(argv[j], "--adaptive-max-gain") && more) {
+ Modes.adaptive_max_gain_db = atof(argv[++j]);
+ } else if (!strcmp(argv[j], "--adaptive-burst")) {
+ Modes.adaptive_burst_control = true;
+ } else if (!strcmp(argv[j], "--adaptive-burst-alpha") && more) {
+ Modes.adaptive_burst_alpha = atof(argv[++j]);
+ } else if (!strcmp(argv[j], "--adaptive-burst-delay") && more) {
+ Modes.adaptive_burst_change_delay = atoi(argv[++j]);
+ } else if (!strcmp(argv[j], "--adaptive-burst-loud-rate") && more) {
+ Modes.adaptive_burst_loud_rate = atof(argv[++j]);
+ } else if (!strcmp(argv[j], "--adaptive-burst-loud-runlength") && more) {
+ Modes.adaptive_burst_loud_runlength = atoi(argv[++j]);
+ } else if (!strcmp(argv[j], "--adaptive-burst-quiet-rate") && more) {
+ Modes.adaptive_burst_quiet_rate = atof(argv[++j]);
+ } else if (!strcmp(argv[j], "--adaptive-burst-quiet-runlength") && more) {
+ Modes.adaptive_burst_quiet_runlength = atoi(argv[++j]);
+ } else if (!strcmp(argv[j], "--adaptive-range")) {
+ Modes.adaptive_range_control = true;
+ } else if (!strcmp(argv[j], "--adaptive-range-alpha") && more) {
+ Modes.adaptive_range_alpha = atof(argv[++j]);
+ } else if (!strcmp(argv[j], "--adaptive-range-percentile") && more) {
+ Modes.adaptive_range_percentile = atoi(argv[++j]);
+ } else if (!strcmp(argv[j], "--adaptive-range-target") && more) {
+ Modes.adaptive_range_target = atof(argv[++j]);
+ } else if (!strcmp(argv[j], "--adaptive-range-scan-delay") && more) {
+ Modes.adaptive_range_scan_delay = atoi(argv[++j]);
+ } else if (!strcmp(argv[j], "--adaptive-range-rescan-delay") && more) {
+ Modes.adaptive_range_rescan_delay = atoi(argv[++j]);
} else if (sdrHandleOption(argc, argv, &j)) {
/* handled */
} else {
@@ -724,7 +817,7 @@ int main(int argc, char **argv) {
if (!sdrOpen()) {
exit(1);
}
-
+
if (Modes.net) {
modesInitNet();
}
@@ -740,6 +833,8 @@ int main(int argc, char **argv) {
for (j = 0; j < 15; ++j)
Modes.stats_1min[j].start = Modes.stats_1min[j].end = Modes.stats_current.start;
+ adaptive_init();
+
// write initial json files so they're not missing
writeJsonToFile("receiver.json", generateReceiverJson);
writeJsonToFile("stats.json", generateStatsJson);
diff --git a/dump1090.h b/dump1090.h
index 81a5b25..41799a7 100644
--- a/dump1090.h
+++ b/dump1090.h
@@ -92,7 +92,7 @@
#define MODES_RTL_BUF_SIZE (16*16384) // 256k
#define MODES_MAG_BUF_SAMPLES (MODES_RTL_BUF_SIZE / 2) // Each sample is 2 bytes
#define MODES_MAG_BUFFERS 12 // Number of magnitude buffers (should be smaller than RTL_BUFFERS for flowcontrol to work)
-#define MODES_AUTO_GAIN -100 // Use automatic gain
+#define MODES_AUTO_GAIN -10 // Use automatic gain
#define MODES_MAX_GAIN 999999 // Use max available gain
#define MODES_MSG_SQUELCH_DB 4.0 // Minimum SNR, in dB
#define MODES_MSG_ENCODER_ERRS 3 // Maximum number of encoding errors
@@ -272,6 +272,7 @@ typedef enum {
#include "convert.h"
#include "sdr.h"
#include "fifo.h"
+#include "adaptive.h"
//======================== structure declarations =========================
@@ -296,9 +297,9 @@ struct _Modes { // Internal state
// Sample conversion
int dc_filter; // should we apply a DC filter?
- // RTLSDR
+ // RTLSDR and some other SDRs
char * dev_name;
- int gain;
+ float gain; // value in dB, or MODES_AUTO_GAIN, or MODES_MAX_GAIN
int freq;
// Networking
@@ -388,6 +389,25 @@ struct _Modes { // Internal state
int stats_newest_1min; // Index into stats_1min of the most recent 1-minute window
struct stats stats_5min; // Accumulated stats from the last 5 complete 1-minute windows
struct stats stats_15min; // Accumulated stats from the last 15 complete 1-minute windows
+
+ // Adaptive gain config
+ float adaptive_min_gain_db;
+ float adaptive_max_gain_db;
+
+ bool adaptive_burst_control;
+ float adaptive_burst_alpha;
+ unsigned adaptive_burst_change_delay;
+ float adaptive_burst_loud_rate;
+ unsigned adaptive_burst_loud_runlength;
+ float adaptive_burst_quiet_rate;
+ unsigned adaptive_burst_quiet_runlength;
+
+ bool adaptive_range_control;
+ float adaptive_range_alpha;
+ unsigned adaptive_range_percentile;
+ float adaptive_range_target;
+ unsigned adaptive_range_scan_delay;
+ unsigned adaptive_range_rescan_delay;
};
extern struct _Modes Modes;
diff --git a/net_io.c b/net_io.c
index 7b2145f..438a34b 100644
--- a/net_io.c
+++ b/net_io.c
@@ -1803,64 +1803,85 @@ static char * appendStatsJson(char *p,
p = safe_snprintf(p, end, "]}");
}
- {
- uint64_t demod_cpu_millis = (uint64_t)st->demod_cpu.tv_sec*1000UL + st->demod_cpu.tv_nsec/1000000UL;
- uint64_t reader_cpu_millis = (uint64_t)st->reader_cpu.tv_sec*1000UL + st->reader_cpu.tv_nsec/1000000UL;
- uint64_t background_cpu_millis = (uint64_t)st->background_cpu.tv_sec*1000UL + st->background_cpu.tv_nsec/1000000UL;
+ uint64_t demod_cpu_millis = (uint64_t)st->demod_cpu.tv_sec*1000UL + st->demod_cpu.tv_nsec/1000000UL;
+ uint64_t reader_cpu_millis = (uint64_t)st->reader_cpu.tv_sec*1000UL + st->reader_cpu.tv_nsec/1000000UL;
+ uint64_t background_cpu_millis = (uint64_t)st->background_cpu.tv_sec*1000UL + st->background_cpu.tv_nsec/1000000UL;
- p = safe_snprintf(p, end,
- ",\"cpr\":{\"surface\":%u"
- ",\"airborne\":%u"
- ",\"global_ok\":%u"
- ",\"global_bad\":%u"
- ",\"global_range\":%u"
- ",\"global_speed\":%u"
- ",\"global_skipped\":%u"
- ",\"local_ok\":%u"
- ",\"local_aircraft_relative\":%u"
- ",\"local_receiver_relative\":%u"
- ",\"local_skipped\":%u"
- ",\"local_range\":%u"
- ",\"local_speed\":%u"
- ",\"filtered\":%u}"
- ",\"altitude_suppressed\":%u"
- ",\"cpu\":{\"demod\":%llu,\"reader\":%llu,\"background\":%llu}"
- ",\"tracks\":{\"all\":%u"
- ",\"single_message\":%u"
- ",\"unreliable\":%u}"
- ",\"messages\":%u",
- st->cpr_surface,
- st->cpr_airborne,
- st->cpr_global_ok,
- st->cpr_global_bad,
- st->cpr_global_range_checks,
- st->cpr_global_speed_checks,
- st->cpr_global_skipped,
- st->cpr_local_ok,
- st->cpr_local_aircraft_relative,
- st->cpr_local_receiver_relative,
- st->cpr_local_skipped,
- st->cpr_local_range_checks,
- st->cpr_local_speed_checks,
- st->cpr_filtered,
- st->suppressed_altitude_messages,
- (unsigned long long)demod_cpu_millis,
- (unsigned long long)reader_cpu_millis,
- (unsigned long long)background_cpu_millis,
- st->unique_aircraft,
- st->single_message_aircraft,
- st->unreliable_aircraft,
- st->messages_total);
+ p = safe_snprintf(p, end,
+ ",\"cpr\":{\"surface\":%u"
+ ",\"airborne\":%u"
+ ",\"global_ok\":%u"
+ ",\"global_bad\":%u"
+ ",\"global_range\":%u"
+ ",\"global_speed\":%u"
+ ",\"global_skipped\":%u"
+ ",\"local_ok\":%u"
+ ",\"local_aircraft_relative\":%u"
+ ",\"local_receiver_relative\":%u"
+ ",\"local_skipped\":%u"
+ ",\"local_range\":%u"
+ ",\"local_speed\":%u"
+ ",\"filtered\":%u}"
+ ",\"altitude_suppressed\":%u"
+ ",\"cpu\":{\"demod\":%llu,\"reader\":%llu,\"background\":%llu}"
+ ",\"tracks\":{\"all\":%u"
+ ",\"single_message\":%u"
+ ",\"unreliable\":%u}"
+ ",\"messages\":%u",
+ st->cpr_surface,
+ st->cpr_airborne,
+ st->cpr_global_ok,
+ st->cpr_global_bad,
+ st->cpr_global_range_checks,
+ st->cpr_global_speed_checks,
+ st->cpr_global_skipped,
+ st->cpr_local_ok,
+ st->cpr_local_aircraft_relative,
+ st->cpr_local_receiver_relative,
+ st->cpr_local_skipped,
+ st->cpr_local_range_checks,
+ st->cpr_local_speed_checks,
+ st->cpr_filtered,
+ st->suppressed_altitude_messages,
+ (unsigned long long)demod_cpu_millis,
+ (unsigned long long)reader_cpu_millis,
+ (unsigned long long)background_cpu_millis,
+ st->unique_aircraft,
+ st->single_message_aircraft,
+ st->unreliable_aircraft,
+ st->messages_total);
- for (i = 0; i < 32; ++i) {
- if (i == 0)
- p = safe_snprintf(p, end, ",\"messages_by_df\":[%u", st->messages_by_df[i]);
- else
- p = safe_snprintf(p, end, ",%u", st->messages_by_df[i]);
- }
- p = safe_snprintf(p, end, "]}");
+ for (i = 0; i < 32; ++i) {
+ if (i == 0)
+ p = safe_snprintf(p, end, ",\"messages_by_df\":[%u", st->messages_by_df[i]);
+ else
+ p = safe_snprintf(p, end, ",%u", st->messages_by_df[i]);
}
+ p = safe_snprintf(p, end, "]");
+ p = safe_snprintf(p, end,
+ ",\"adaptive\":"
+ "{\"gain_db\":%.1f"
+ ",\"gain_reduced_seconds\":%u"
+ ",\"loud_undecoded\":%u"
+ ",\"loud_decoded\":%u"
+ ",\"noise_dbfs\":%.1f"
+ ",\"gain_seconds\":[",
+ sdrGetGainDb(st->adaptive_gain),
+ st->adaptive_gain_reduced_seconds,
+ st->adaptive_loud_undecoded,
+ st->adaptive_loud_decoded,
+ st->adaptive_noise_dbfs);
+ bool first = true;
+ for (unsigned i = 0; i < STATS_GAIN_COUNT; ++i) {
+ if (st->adaptive_gain_seconds[i] > 0) {
+ p = safe_snprintf(p, end, "%s[%.1f,%u]",
+ first ? "" : ",",
+ sdrGetGainDb(i), st->adaptive_gain_seconds[i]);
+ first = false;
+ }
+ }
+ p = safe_snprintf(p, end, "]}}");
return p;
}
diff --git a/sdr.c b/sdr.c
index 4c33de1..484302a 100644
--- a/sdr.c
+++ b/sdr.c
@@ -44,6 +44,10 @@ typedef struct {
void (*run)();
void (*stop)();
void (*close)();
+ int (*getgain)();
+ int (*getmaxgain)();
+ double (*getgaindb)(int);
+ int (*setgain)(int);
} sdr_handler;
static void noInitConfig()
@@ -81,6 +85,28 @@ static void noClose()
{
}
+static int noGetGain()
+{
+ return -1;
+}
+
+static int noGetMaxGain()
+{
+ return -1;
+}
+
+static double noGetGainDb(int step)
+{
+ MODES_NOTUSED(step);
+ return 0.0;
+}
+
+static int noSetGain(int step)
+{
+ MODES_NOTUSED(step);
+ return 0;
+}
+
static bool unsupportedOpen()
{
fprintf(stderr, "Support for this SDR type was not enabled in this build.\n");
@@ -89,24 +115,24 @@ static bool unsupportedOpen()
static sdr_handler sdr_handlers[] = {
#ifdef ENABLE_RTLSDR
- { "rtlsdr", SDR_RTLSDR, rtlsdrInitConfig, rtlsdrShowHelp, rtlsdrHandleOption, rtlsdrOpen, rtlsdrRun, rtlsdrStop, rtlsdrClose },
+ { "rtlsdr", SDR_RTLSDR, rtlsdrInitConfig, rtlsdrShowHelp, rtlsdrHandleOption, rtlsdrOpen, rtlsdrRun, rtlsdrStop, rtlsdrClose, rtlsdrGetGain, rtlsdrGetMaxGain, rtlsdrGetGainDb, rtlsdrSetGain },
#endif
#ifdef ENABLE_BLADERF
- { "bladerf", SDR_BLADERF, bladeRFInitConfig, bladeRFShowHelp, bladeRFHandleOption, bladeRFOpen, bladeRFRun, noStop, bladeRFClose },
+ { "bladerf", SDR_BLADERF, bladeRFInitConfig, bladeRFShowHelp, bladeRFHandleOption, bladeRFOpen, bladeRFRun, noStop, bladeRFClose, noGetGain, noGetMaxGain, noGetGainDb, noSetGain },
#endif
#ifdef ENABLE_HACKRF
- { "hackrf", SDR_HACKRF, hackRFInitConfig, hackRFShowHelp, hackRFHandleOption, hackRFOpen, hackRFRun, noStop, hackRFClose },
+ { "hackrf", SDR_HACKRF, hackRFInitConfig, hackRFShowHelp, hackRFHandleOption, hackRFOpen, hackRFRun, noStop, hackRFClose, noGetGain, noGetMaxGain, noGetGainDb, noSetGain },
#endif
#ifdef ENABLE_LIMESDR
- { "limesdr", SDR_LIMESDR, limesdrInitConfig, limesdrShowHelp, limesdrHandleOption, limesdrOpen, limesdrRun, noStop, limesdrClose },
+ { "limesdr", SDR_LIMESDR, limesdrInitConfig, limesdrShowHelp, limesdrHandleOption, limesdrOpen, limesdrRun, noStop, limesdrClose, noGetGain, noGetMaxGain, noGetGainDb, noSetGain },
#endif
- { "none", SDR_NONE, noInitConfig, noShowHelp, noHandleOption, noOpen, noRun, noStop, noClose },
- { "ifile", SDR_IFILE, ifileInitConfig, ifileShowHelp, ifileHandleOption, ifileOpen, ifileRun, noStop, ifileClose },
+ { "none", SDR_NONE, noInitConfig, noShowHelp, noHandleOption, noOpen, noRun, noStop, noClose, noGetGain, noGetMaxGain, noGetGainDb, noSetGain },
+ { "ifile", SDR_IFILE, ifileInitConfig, ifileShowHelp, ifileHandleOption, ifileOpen, ifileRun, noStop, ifileClose, noGetGain, noGetMaxGain, noGetGainDb, noSetGain },
- { NULL, SDR_NONE, NULL, NULL, NULL, NULL, NULL, NULL, NULL } /* must come last */
+ { NULL, SDR_NONE, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL } /* must come last */
};
void sdrInitConfig()
@@ -163,7 +189,7 @@ bool sdrHandleOption(int argc, char **argv, int *jptr)
static sdr_handler *current_handler()
{
- static sdr_handler unsupported_handler = { "unsupported", SDR_NONE, noInitConfig, noShowHelp, noHandleOption, unsupportedOpen, noRun, noStop, noClose };
+ static sdr_handler unsupported_handler = { "unsupported", SDR_NONE, noInitConfig, noShowHelp, noHandleOption, unsupportedOpen, noRun, noStop, noClose, noGetGain, noGetMaxGain, noGetGainDb, noSetGain };
for (int i = 0; sdr_handlers[i].name; ++i) {
if (Modes.sdr_type == sdr_handlers[i].sdr_type) {
@@ -223,3 +249,25 @@ void sdrUpdateCPUTime(struct timespec *addTo)
Modes.reader_cpu_accumulator.tv_nsec = 0;
pthread_mutex_unlock(&Modes.reader_cpu_mutex);
}
+
+int sdrGetGain()
+{
+ return current_handler()->getgain();
+}
+
+int sdrGetMaxGain()
+{
+ return current_handler()->getmaxgain();
+}
+
+double sdrGetGainDb(int step)
+{
+ return current_handler()->getgaindb(step);
+}
+
+int sdrSetGain(int step)
+{
+ return current_handler()->setgain(step);
+}
+
+
diff --git a/sdr.h b/sdr.h
index b73655b..9ab0ab0 100644
--- a/sdr.h
+++ b/sdr.h
@@ -31,6 +31,12 @@ void sdrRun();
void sdrStop();
void sdrClose();
+// Gain control
+int sdrGetGain(); // return current gain step 0..N, or -1 if gain control is not supported
+int sdrGetMaxGain(); // return maximum gain step, or -1 if gain control is not supported
+double sdrGetGainDb(int step); // return gain in dB for the given gain step, or 0.0 if gain control is not supported
+int sdrSetGain(int step); // set gain step; return actual gain step used, or -1 if gain control is not supported
+
// Call periodically from the SDR read thread to update reader thread CPU stats:
void sdrMonitor();
// Retrieve CPU stats and add new CPU time to *addTo
diff --git a/sdr_bladerf.c b/sdr_bladerf.c
index 74d0a6f..5f7c726 100644
--- a/sdr_bladerf.c
+++ b/sdr_bladerf.c
@@ -254,7 +254,7 @@ bool bladeRFOpen()
goto error;
}
- if ((status = bladerf_set_gain(BladeRF.device, BLADERF_MODULE_RX, Modes.gain / 10.0)) < 0) {
+ if ((status = bladerf_set_gain(BladeRF.device, BLADERF_MODULE_RX, Modes.gain)) < 0) {
fprintf(stderr, "bladerf_set_gain(RX) failed: %s\n", bladerf_strerror(status));
goto error;
}
diff --git a/sdr_limesdr.c b/sdr_limesdr.c
index 8809aa0..c935130 100644
--- a/sdr_limesdr.c
+++ b/sdr_limesdr.c
@@ -285,7 +285,7 @@ bool limesdrOpen(void)
goto error;
}
} else {
- if (LMS_SetGaindB(LimeSDR.dev, LMS_CH_RX, LimeSDR.stream.channel, Modes.gain / 10)) {
+ if (LMS_SetGaindB(LimeSDR.dev, LMS_CH_RX, LimeSDR.stream.channel, Modes.gain)) {
limesdrLogHandler(LMS_LOG_ERROR, "unable to set gain");
goto error;
}
diff --git a/sdr_rtlsdr.c b/sdr_rtlsdr.c
index fd192c2..e7a8c5f 100644
--- a/sdr_rtlsdr.c
+++ b/sdr_rtlsdr.c
@@ -66,6 +66,9 @@ static struct {
uint8_t *bounce_buffer;
iq_convert_fn converter;
struct converter_state *converter_state;
+ int *gains;
+ int gain_steps;
+ int current_gain;
} RTLSDR;
//
@@ -81,6 +84,9 @@ void rtlsdrInitConfig()
RTLSDR.bounce_buffer = NULL;
RTLSDR.converter = NULL;
RTLSDR.converter_state = NULL;
+ RTLSDR.gains = NULL;
+ RTLSDR.gain_steps = 0;
+ RTLSDR.current_gain = 0;
}
static void show_rtlsdr_devices()
@@ -173,7 +179,16 @@ bool rtlsdrHandleOption(int argc, char **argv, int *jptr)
return true;
}
-bool rtlsdrOpen(void) {
+// sort function to sort by increasing gain
+static int sort_gains(const void *left, const void *right)
+{
+ const int *left_int = (const int *)left;
+ const int *right_int = (const int *)right;
+ return *left_int - *right_int;
+}
+
+bool rtlsdrOpen(void)
+{
if (!rtlsdr_get_device_count()) {
fprintf(stderr, "rtlsdr: no supported devices found.\n");
return false;
@@ -210,41 +225,48 @@ bool rtlsdrOpen(void) {
if (RTLSDR.direct_sampling) {
fprintf(stderr, "rtlsdr: direct sampling from input %d\n", RTLSDR.direct_sampling);
rtlsdr_set_direct_sampling(RTLSDR.dev, RTLSDR.direct_sampling);
+ RTLSDR.gain_steps = 0;
} else {
- if (Modes.gain == MODES_AUTO_GAIN) {
- fprintf(stderr, "rtlsdr: enabling tuner AGC\n");
- rtlsdr_set_tuner_gain_mode(RTLSDR.dev, 0);
- } else {
- int *gains;
- int numgains;
+ int *gains;
+ int numgains;
- numgains = rtlsdr_get_tuner_gains(RTLSDR.dev, NULL);
- if (numgains <= 0) {
- fprintf(stderr, "rtlsdr: error getting tuner gains\n");
- return false;
+ numgains = rtlsdr_get_tuner_gains(RTLSDR.dev, NULL);
+ if (numgains <= 0) {
+ fprintf(stderr, "rtlsdr: error getting tuner gains\n");
+ return false;
}
- gains = malloc(numgains * sizeof(int));
- if (rtlsdr_get_tuner_gains(RTLSDR.dev, gains) != numgains) {
- fprintf(stderr, "rtlsdr: error getting tuner gains\n");
- free(gains);
- return false;
- }
-
- int target = (Modes.gain == MODES_MAX_GAIN ? 9999 : Modes.gain);
- int closest = -1;
-
- for (int i = 0; i < numgains; ++i) {
- if (closest == -1 || abs(gains[i] - target) < abs(gains[closest] - target))
- closest = i;
- }
-
- rtlsdr_set_tuner_gain(RTLSDR.dev, gains[closest]);
+ gains = malloc((numgains + 1) * sizeof(int));
+ if (rtlsdr_get_tuner_gains(RTLSDR.dev, gains) != numgains) {
+ fprintf(stderr, "rtlsdr: error getting tuner gains\n");
free(gains);
-
- fprintf(stderr, "rtlsdr: tuner gain set to %.1f dB\n",
- rtlsdr_get_tuner_gain(RTLSDR.dev)/10.0);
+ return false;
}
+
+ qsort(gains, numgains, sizeof(gains[0]), sort_gains);
+
+ // Fake an entry at slightly higher than max manual gain;
+ // we will use this for the "tuner AGC enabled" settings
+ // which due to librtlsdr quirks behaves like a "more than
+ // max" gain. :/
+ gains[numgains] = gains[numgains-1] + 90; // +9.0dB
+
+ RTLSDR.gain_steps = numgains + 1;
+ RTLSDR.gains = gains;
+
+ int selected = -1;
+ if (Modes.gain == MODES_AUTO_GAIN) {
+ selected = numgains;
+ } else if (Modes.gain == MODES_MAX_GAIN) {
+ selected = numgains - 1;
+ } else {
+ for (int i = 0; i < numgains; ++i) {
+ if (selected == -1 || fabs(gains[i]/10.0 - Modes.gain) < fabs(gains[selected]/10.0 - Modes.gain))
+ selected = i;
+ }
+ }
+
+ rtlsdrSetGain(selected);
}
if (RTLSDR.digital_agc) {
@@ -276,6 +298,9 @@ bool rtlsdrOpen(void) {
}
#endif
+ if (Modes.adaptive_range_target == 0)
+ Modes.adaptive_range_target = 30.0;
+
return true;
}
@@ -382,4 +407,65 @@ void rtlsdrClose()
free(RTLSDR.bounce_buffer);
RTLSDR.bounce_buffer = NULL;
+
+ free(RTLSDR.gains);
+ RTLSDR.gains = NULL;
}
+
+int rtlsdrGetGain()
+{
+ return RTLSDR.current_gain;
+}
+
+int rtlsdrGetMaxGain()
+{
+ return RTLSDR.gain_steps - 1;
+}
+
+double rtlsdrGetGainDb(int step)
+{
+ if (!RTLSDR.gains)
+ return 0.0;
+
+ if (step < 0)
+ step = 0;
+ if (step >= RTLSDR.gain_steps)
+ step = RTLSDR.gain_steps - 1;
+ return RTLSDR.gains[step] / 10.0;
+}
+
+int rtlsdrSetGain(int step)
+{
+ if (!RTLSDR.gains)
+ return -1;
+
+ if (step < 0)
+ step = 0;
+ if (step >= RTLSDR.gain_steps)
+ step = RTLSDR.gain_steps - 1;
+
+ if (step == RTLSDR.gain_steps - 1) {
+ if (rtlsdr_set_tuner_gain_mode(RTLSDR.dev, 0) < 0) {
+ fprintf(stderr, "rtlsdr: failed to enable tuner AGC\n");
+ return RTLSDR.current_gain;
+ }
+
+ fprintf(stderr, "rtlsdr: tuner gain set to about %.1f dB (gain step %d) (tuner AGC enabled)\n", RTLSDR.gains[step] / 10.0, step);
+ } else {
+ if (rtlsdr_set_tuner_gain_mode(RTLSDR.dev, 1) < 0) {
+ fprintf(stderr, "rtlsdr: failed to disable tuner AGC\n");
+ return RTLSDR.current_gain;
+ }
+
+ if (rtlsdr_set_tuner_gain(RTLSDR.dev, RTLSDR.gains[step]) < 0) {
+ fprintf(stderr, "rtlsdr: failed to set tuner gain to %.1fdB\n", RTLSDR.gains[step] / 10.0);
+ return RTLSDR.current_gain;
+ }
+
+ fprintf(stderr, "rtlsdr: tuner gain set to %.1f dB (gain step %d)\n", RTLSDR.gains[step] / 10.0, step);
+ }
+
+ RTLSDR.current_gain = step;
+ return step;
+}
+
diff --git a/sdr_rtlsdr.h b/sdr_rtlsdr.h
index af6c53d..a4d7d65 100644
--- a/sdr_rtlsdr.h
+++ b/sdr_rtlsdr.h
@@ -28,5 +28,9 @@ void rtlsdrRun();
void rtlsdrStop();
void rtlsdrClose();
bool rtlsdrHandleOption(int argc, char **argv, int *jptr);
+int rtlsdrGetGain();
+int rtlsdrGetMaxGain();
+double rtlsdrGetGainDb(int step);
+int rtlsdrSetGain(int step);
#endif
diff --git a/sdr_stub.c b/sdr_stub.c
new file mode 100644
index 0000000..1ac69a7
--- /dev/null
+++ b/sdr_stub.c
@@ -0,0 +1,92 @@
+// Part of dump1090, a Mode S message decoder for RTLSDR devices.
+//
+// sdr_stub.c: generic SDR infrastructure, stubbed out to do nothing
+//
+// Copyright (c) 2021 FlightAware LLC
+//
+// This file is free software: you may copy, redistribute and/or modify it
+// under the terms of the GNU General Public License as published by the
+// Free Software Foundation, either version 2 of the License, or (at your
+// option) any later version.
+//
+// This file is distributed in the hope that it will be useful, but
+// WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+// General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program. If not, see .
+
+#include "dump1090.h"
+
+void sdrInitConfig()
+{
+ /* nothing */
+}
+
+void sdrShowHelp()
+{
+ /* nothing */
+}
+
+bool sdrHandleOption(int argc, char **argv, int *jptr)
+{
+ MODES_NOTUSED(argc);
+ MODES_NOTUSED(argv);
+ MODES_NOTUSED(jptr);
+ return false;
+}
+
+bool sdrOpen()
+{
+ return false;
+}
+
+void sdrRun()
+{
+ /* nothing */
+}
+
+void sdrStop()
+{
+ /* nothing */
+}
+
+void sdrClose()
+{
+ /* nothing */
+}
+
+void sdrMonitor()
+{
+ /* nothing */
+}
+
+void sdrUpdateCPUTime(struct timespec *addTo)
+{
+ MODES_NOTUSED(addTo);
+}
+
+int sdrGetGain()
+{
+ return -1;
+}
+
+int sdrGetMaxGain()
+{
+ return -1;
+}
+
+double sdrGetGainDb(int step)
+{
+ MODES_NOTUSED(step);
+ return 0.0;
+}
+
+int sdrSetGain(int step)
+{
+ MODES_NOTUSED(step);
+ return -1;
+}
+
+
diff --git a/stats.c b/stats.c
index d433746..0ebd64d 100644
--- a/stats.c
+++ b/stats.c
@@ -3,6 +3,7 @@
// stats.c: statistics helpers.
//
// Copyright (c) 2015 Oliver Jowett
+// Copyright (c) 2021 FlightAware LLC
//
// This file is free software: you may copy, redistribute and/or modify it
// under the terms of the GNU General Public License as published by the
@@ -114,6 +115,46 @@ void display_stats(struct stats *st) {
st->strong_signal_count);
}
+ if (Modes.adaptive_burst_control || Modes.adaptive_range_control) {
+ printf("Adaptive gain:\n"
+ " %5u loud undecoded bursts\n"
+ " %5u loud decoded messages\n"
+ " %5.1f dBFS current noise floor\n"
+ " %5.1f dB current gain setting\n",
+ st->adaptive_loud_undecoded,
+ st->adaptive_loud_decoded,
+ st->adaptive_noise_dbfs,
+ sdrGetGainDb(st->adaptive_gain));
+
+ uint32_t total_seconds = 0;
+ for (unsigned i = 0; i < STATS_GAIN_COUNT; ++i)
+ total_seconds += st->adaptive_gain_seconds[i];
+
+ if (total_seconds) {
+ unsigned count = 0;
+ for (unsigned i = 0; i < STATS_GAIN_COUNT; ++i) {
+ count += st->adaptive_gain_seconds[i];
+ if (count >= total_seconds/2) {
+ printf(" %5.1f dB median gain\n", sdrGetGainDb(i));
+ break;
+ }
+ }
+
+ printf(" %5u seconds (%5.1f%%) at reduced gain due to loud messages\n",
+ st->adaptive_gain_reduced_seconds, 100.0 * st->adaptive_gain_reduced_seconds / total_seconds);
+
+ printf(" Gain histogram:\n");
+ for (unsigned i = 0; i < STATS_GAIN_COUNT; ++i) {
+ unsigned seconds = st->adaptive_gain_seconds[i];
+ if (seconds) {
+ printf(" %5.1f dB: %5u seconds (%5.1f%%)\n",
+ sdrGetGainDb(i), seconds, 100.0 * seconds / total_seconds);
+ }
+ }
+
+ }
+ }
+
if (Modes.net) {
printf("Messages from network clients:\n");
printf(" %8u Mode A/C messages received\n", st->remote_received_modeac);
@@ -125,28 +166,29 @@ void display_stats(struct stats *st) {
printf(" %8u accepted with %d-bit error repaired\n", st->remote_accepted[j], j);
}
- printf("%8u total usable messages\n",
+ printf("Decoder:\n"
+ " %8u total usable messages\n",
st->messages_total);
for (unsigned i = 0; i < 32; ++i) {
if (st->messages_by_df[i])
- printf(" %8u DF%u messages\n", st->messages_by_df[i], i);
+ printf(" %8u DF%u messages\n", st->messages_by_df[i], i);
}
- printf("%8u surface position messages received\n"
- "%8u airborne position messages received\n"
- "%8u global CPR attempts with valid positions\n"
- "%8u global CPR attempts with bad data\n"
- " %8u global CPR attempts that failed the range check\n"
- " %8u global CPR attempts that failed the speed check\n"
- "%8u global CPR attempts with insufficient data\n"
- "%8u local CPR attempts with valid positions\n"
- " %8u aircraft-relative positions\n"
- " %8u receiver-relative positions\n"
- "%8u local CPR attempts that did not produce useful positions\n"
- " %8u local CPR attempts that failed the range check\n"
- " %8u local CPR attempts that failed the speed check\n"
- "%8u CPR messages that look like transponder failures filtered\n",
+ printf(" %8u surface position messages received\n"
+ " %8u airborne position messages received\n"
+ " %8u global CPR attempts with valid positions\n"
+ " %8u global CPR attempts with bad data\n"
+ " %8u global CPR attempts that failed the range check\n"
+ " %8u global CPR attempts that failed the speed check\n"
+ " %8u global CPR attempts with insufficient data\n"
+ " %8u local CPR attempts with valid positions\n"
+ " %8u aircraft-relative positions\n"
+ " %8u receiver-relative positions\n"
+ " %8u local CPR attempts that did not produce useful positions\n"
+ " %8u local CPR attempts that failed the range check\n"
+ " %8u local CPR attempts that failed the speed check\n"
+ " %8u CPR messages that look like transponder failures filtered\n",
st->cpr_surface,
st->cpr_airborne,
st->cpr_global_ok,
@@ -162,10 +204,10 @@ void display_stats(struct stats *st) {
st->cpr_local_speed_checks,
st->cpr_filtered);
- printf("%8u non-ES altitude messages from ES-equipped aircraft ignored\n", st->suppressed_altitude_messages);
- printf("%8u unique aircraft tracks\n", st->unique_aircraft);
- printf("%8u aircraft tracks where only one message was seen\n", st->single_message_aircraft);
- printf("%8u aircraft tracks which were not marked reliable\n", st->unreliable_aircraft);
+ printf(" %8u non-ES altitude messages from ES-equipped aircraft ignored\n", st->suppressed_altitude_messages);
+ printf(" %8u unique aircraft tracks\n", st->unique_aircraft);
+ printf(" %8u aircraft tracks where only one message was seen\n", st->single_message_aircraft);
+ printf(" %8u aircraft tracks which were not marked reliable\n", st->unreliable_aircraft);
{
uint64_t demod_cpu_millis = (uint64_t)st->demod_cpu.tv_sec*1000UL + st->demod_cpu.tv_nsec/1000000UL;
@@ -272,7 +314,14 @@ void add_stats(const struct stats *st1, const struct stats *st2, struct stats *t
else
target->start = st2->start;
- target->end = st1->end > st2->end ? st1->end : st2->end;
+ const struct stats *newer;
+ if (st1->end > st2->end) {
+ newer = st1;
+ } else {
+ newer = st2;
+ }
+
+ target->end = newer->end;
target->demod_preambles = st1->demod_preambles + st2->demod_preambles;
target->demod_rejected_bad = st1->demod_rejected_bad + st2->demod_rejected_bad;
@@ -344,4 +393,13 @@ void add_stats(const struct stats *st1, const struct stats *st2, struct stats *t
// range histogram
for (i = 0; i < RANGE_BUCKET_COUNT; ++i)
target->range_histogram[i] = st1->range_histogram[i] + st2->range_histogram[i];
+
+ // adaptive gain measurements
+ target->adaptive_gain = newer->adaptive_gain;
+ for (unsigned i = 0; i < STATS_GAIN_COUNT; ++i)
+ target->adaptive_gain_seconds[i] = st1->adaptive_gain_seconds[i] + st2->adaptive_gain_seconds[i];
+ target->adaptive_gain_reduced_seconds = st1->adaptive_gain_reduced_seconds + st2->adaptive_gain_reduced_seconds;
+ target->adaptive_loud_undecoded = st1->adaptive_loud_undecoded + st2->adaptive_loud_undecoded;
+ target->adaptive_loud_decoded = st1->adaptive_loud_decoded + st2->adaptive_loud_decoded;
+ target->adaptive_noise_dbfs = newer->adaptive_noise_dbfs;
}
diff --git a/stats.h b/stats.h
index 1935b12..387cb17 100644
--- a/stats.h
+++ b/stats.h
@@ -1,8 +1,9 @@
// Part of dump1090, a Mode S message decoder for RTLSDR devices.
//
-// stats.c: statistics structures and prototypes.
+// stats.h: statistics structures and prototypes.
//
// Copyright (c) 2015 Oliver Jowett
+// Copyright (c) 2021 FlightAware LLC
//
// This file is free software: you may copy, redistribute and/or modify it
// under the terms of the GNU General Public License as published by the
@@ -128,6 +129,15 @@ struct stats {
// range histogram
#define RANGE_BUCKET_COUNT 76
uint32_t range_histogram[RANGE_BUCKET_COUNT];
+
+ // adaptive gain measurements
+#define STATS_GAIN_COUNT 64
+ unsigned adaptive_gain; // Current gain step in use
+ uint32_t adaptive_gain_seconds[STATS_GAIN_COUNT]; // Seconds spent at each gain step
+ uint32_t adaptive_gain_reduced_seconds; // Seconds spent at a reduced gain due to burst detection
+ uint32_t adaptive_loud_undecoded; // Total number of loud, undecoded bursts
+ uint32_t adaptive_loud_decoded; // Total number of loud, decoded messages
+ double adaptive_noise_dbfs; // Current adaptive-dynamic-range smoothed noise measurement, dBFS
};
void add_stats(const struct stats *st1, const struct stats *st2, struct stats *target);