From 5a1d88c7d0f76cd08bec9807bfbc553d5b1b6361 Mon Sep 17 00:00:00 2001 From: hayati ayguen Date: Wed, 7 Sep 2016 22:21:19 +0200 Subject: [PATCH 1/6] silence compiler warning, complaining C90 declaration after statement Signed-off-by: hayati ayguen --- src/librtlsdr.c | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/librtlsdr.c b/src/librtlsdr.c index 6779581..d2ff6a9 100644 --- a/src/librtlsdr.c +++ b/src/librtlsdr.c @@ -1493,10 +1493,11 @@ int rtlsdr_get_direct_sampling(rtlsdr_dev_t *dev) int rtlsdr_set_ds_mode(rtlsdr_dev_t *dev, enum rtlsdr_ds_mode mode, uint32_t freq_threshold) { + uint32_t center_freq; if (!dev) return -1; - uint32_t center_freq = rtlsdr_get_center_freq(dev); + center_freq = rtlsdr_get_center_freq(dev); if ( !center_freq ) return -2; @@ -1524,11 +1525,11 @@ int rtlsdr_set_ds_mode(rtlsdr_dev_t *dev, enum rtlsdr_ds_mode mode, uint32_t fre static int rtlsdr_update_ds(rtlsdr_dev_t *dev, uint32_t freq) { + int new_ds = 0; int curr_ds = rtlsdr_get_direct_sampling(dev); if ( curr_ds < 0 ) return -1; - int new_ds = 0; switch (dev->direct_sampling_mode) { default: case RTLSDR_DS_IQ: break; From e4106d875be11dd456e66f4b51146f2630b22612 Mon Sep 17 00:00:00 2001 From: Hayati Ayguen Date: Thu, 8 Sep 2016 20:27:02 +0000 Subject: [PATCH 2/6] allow units for samplerate with rtl_test option '-s' Signed-off-by: Hayati Ayguen --- src/rtl_test.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/rtl_test.c b/src/rtl_test.c index b3b74ba..7ee3b8c 100644 --- a/src/rtl_test.c +++ b/src/rtl_test.c @@ -288,7 +288,7 @@ int main(int argc, char **argv) dev_given = 1; break; case 's': - samp_rate = (uint32_t)atof(optarg); + samp_rate = (uint32_t)atofs(optarg); break; case 'b': out_block_size = (uint32_t)atof(optarg); From 1fe28c760928e3afdf80b942750e85c5c81b2be1 Mon Sep 17 00:00:00 2001 From: Hayati Ayguen Date: Fri, 9 Sep 2016 19:29:05 +0000 Subject: [PATCH 3/6] scanning via csv command file with specific gain and command-execution triggers changes: * extended output at option -L to print rms as level in dB * changed center frequency from up- to downconversion of 1/4 capture_rate for better fitting to the out-of-center R820T narrow-band IF-filters, e.g. 350 kHz main feature: * added option -C for .csv command file * keywords, that are replaced in arguments when executing commands - keywords must be freestanding in the argument list - keywords are "!freq!", "!gain!", "!mlevel!", "!crit!", "!reflevel!", "!reftol!" for frequency, gain, measured level, operation: "in", "out", "<" or ">". ">" will give a "squelch", reference level and reference tolerance, * gather/log scan/level statistics when exiting rtl_fm with command file * power measurement: muting with command file is dropping full packets - after DC filtering other additions: * added option -m for setting minimum capture rate * added option -B for changing number samples to drop/mute after changing frequency * added option -n to disable demodulation output to stdout/file Signed-off-by: Hayati Ayguen --- src/convenience/convenience.c | 68 ++++ src/convenience/convenience.h | 3 + src/rtl_fm.c | 615 +++++++++++++++++++++++++++++----- 3 files changed, 601 insertions(+), 85 deletions(-) diff --git a/src/convenience/convenience.c b/src/convenience/convenience.c index e234333..90467aa 100755 --- a/src/convenience/convenience.c +++ b/src/convenience/convenience.c @@ -22,6 +22,7 @@ #include #include #include +#include #ifndef _WIN32 #include @@ -29,6 +30,7 @@ #include #include #include +#include #define _USE_MATH_DEFINES #endif @@ -43,6 +45,8 @@ double atofs(char *s) int len; double suff = 1.0; len = strlen(s); + /* allow formatting spaces from .csv command file */ + while ( len > 1 && isspace(s[len-1]) ) --len; last = s[len-1]; s[len-1] = '\0'; switch (last) { @@ -325,4 +329,68 @@ int verbose_device_search(char *s) return -1; } +#ifndef _WIN32 + +void executeInBackground( char * file, char * args, char * searchStr[], char * replaceStr[] ) +{ + pid_t pid; + char * argv[256] = { NULL }; + int k, argc = 0; + argv[argc++] = file; + if (args) { + argv[argc] = strtok(args, " "); + while (argc < 256 && argv[argc]) { + argv[++argc] = strtok(NULL, " "); + for (k=0; argv[argc] && searchStr && replaceStr && searchStr[k] && replaceStr[k]; k++) { + if (!strcmp(argv[argc], searchStr[k])) { + argv[argc] = replaceStr[k]; + break; + } + } + } + } + + pid = fork(); + switch (pid) + { + case -1: + /* Fork() has failed */ + fprintf(stderr, "error: fork for '%s' failed!\n", file); + break; + case 0: + execvp(file, argv); + fprintf(stderr, "error: execv of '%s' from within fork failed!\n", file); + exit(10); + break; + default: + /* This is processed by the parent */ + break; + } +} + +#else + +void executeInBackground( char * file, char * args, char * searchStr[], char * replaceStr[] ) +{ + char * argv[256] = { NULL }; + int k, argc = 0; + argv[argc++] = file; + if (args) { + argv[argc] = strtok(args, " \t"); + while (argc < 256 && argv[argc]) { + argv[++argc] = strtok(NULL, " \t"); + for (k=0; argv[argc] && searchStr && replaceStr && searchStr[k] && replaceStr[k]; k++) { + if (!strcmp(argv[argc], searchStr[k])) { + argv[argc] = replaceStr[k]; + break; + } + } + } + } + + spawnvp(P_NOWAIT, file, argv); +} + +#endif + // vim: tabstop=8:softtabstop=8:shiftwidth=8:noexpandtab diff --git a/src/convenience/convenience.h b/src/convenience/convenience.h index d3b46dc..7da641b 100644 --- a/src/convenience/convenience.h +++ b/src/convenience/convenience.h @@ -154,4 +154,7 @@ int verbose_reset_buffer(rtlsdr_dev_t *dev); int verbose_device_search(char *s); + +void executeInBackground( char * file, char * args, char * searchStr[], char * replaceStr[] ); + #endif /*__CONVENIENCE_H*/ diff --git a/src/rtl_fm.c b/src/rtl_fm.c index 872a32b..abf1ce4 100644 --- a/src/rtl_fm.c +++ b/src/rtl_fm.c @@ -54,6 +54,7 @@ #include #include #include +#include #ifndef _WIN32 #include @@ -85,9 +86,13 @@ #define MAXIMUM_OVERSAMPLE 16 #define MAXIMUM_BUF_LENGTH (MAXIMUM_OVERSAMPLE * DEFAULT_BUF_LENGTH) #define AUTO_GAIN -100 -#define BUFFER_DUMP 4096 +#define DEFAULT_BUFFER_DUMP 4096 -#define FREQUENCIES_LIMIT 1000 +#define FREQUENCIES_LIMIT 1024 + +static int BufferDump = DEFAULT_BUFFER_DUMP; +static int OutputToStdout = 1; +static int MinCaptureRate = 1000000; static volatile int do_exit = 0; static int lcm_post[17] = {1,1,1,3,1,5,3,7,1,9,5,11,3,13,7,15,1}; @@ -104,12 +109,42 @@ static int levelMax = 0; static int levelMaxMax = 0; static double levelSum = 0.0; +enum trigExpr { crit_IN =0, crit_OUT, crit_LT, crit_GT }; +char * aCritStr[] = { "in", "out", "<", ">" }; + +struct cmd_state +{ + const char * filename; + FILE * file; + int lineNo; + char acLine[4096]; + uint32_t freq; + int gain; + enum trigExpr trigCrit; + double refLevel; + double refLevelTol; + int numMeas; + int numBlockTrigger; + char * command; + char * args; + double levelSum; + int numSummed; + int omitFirstFreqLevels; + int waitTrigger[FREQUENCIES_LIMIT]; + int statNumLevels[FREQUENCIES_LIMIT]; + uint32_t statFreq[FREQUENCIES_LIMIT]; + double statSumLevels[FREQUENCIES_LIMIT]; + float statMinLevel[FREQUENCIES_LIMIT]; + float statMaxLevel[FREQUENCIES_LIMIT]; +}; + struct dongle_state { int exit_flag; pthread_t thread; rtlsdr_dev_t *dev; int dev_index; + uint32_t userFreq; uint32_t freq; uint32_t rate; uint32_t bandwidth; @@ -158,6 +193,7 @@ struct demod_state pthread_cond_t ready; pthread_mutex_t ready_m; struct output_state *output_target; + struct cmd_state *cmd; }; struct output_state @@ -185,6 +221,7 @@ struct controller_state int wb_mode; pthread_cond_t hop; pthread_mutex_t hop_m; + struct cmd_state *cmd; }; // multiple of these, eventually @@ -192,6 +229,8 @@ struct dongle_state dongle; struct demod_state demod; struct output_state output; struct controller_state controller; +struct cmd_state cmd; + void usage(void) { @@ -201,6 +240,11 @@ void usage(void) "\t-f frequency_to_tune_to [Hz]\n" "\t use multiple -f for scanning (requires squelch)\n" "\t ranges supported, -f 118M:137M:25k\n" + "\t[-C command_filename: command file with comma seperated values (.csv). sets modulation 'raw']\n" + "\t\tcommand file contains lines with: freq,gain,trig-crit,trig_level,trig_tolerance,#meas,#blocks,trigger_command,arguments\n" + "\t\t with trig_crit one of 'in', 'out', 'lt' or 'gt'\n" + "\t[-B num_samples at capture rate: remove that many samples at capture_rate after changing frequency (default: 4096)]\n" + "\t[-m minimum_capture_rate Hz (default: 1m, min=900k, max=3.2m)]\n" "\t[-v increase verbosity (default: 0)]\n" "\t[-M modulation (default: fm)]\n" "\t fm or nbfm or nfm, wbfm or wfm, raw or iq, am, usb, lsb\n" @@ -212,11 +256,11 @@ void usage(void) "\t[-D direct_sampling_mode (default: 0, 1 = I, 2 = Q, 3 = I below threshold, 4 = Q below threshold)]\n" "\t[-D direct_sampling_threshold_frequency (default: 0 use tuner specific frequency threshold for 3 and 4)]\n" "\t[-g tuner_gain (default: automatic)]\n" - "\t[-w tuner_bandwidth (default: automatic. enables offset tuning)]\n" + "\t[-w tuner_bandwidth in Hz (default: automatic)]\n" "\t[-l squelch_level (default: 0/off)]\n" "\t[-L N prints levels every N calculations]\n" "\t output are comma separated values (csv):\n" - "\t mean since last output, max since last output, overall max, squelch\n" + "\t avg rms since last output, max rms since last output, overall max rms, squelch (paramed), rms, rms level, avg rms level\n" "\t[-c de-emphasis_time_constant in us for wbfm. 'us' or 'eu' for 75/50 us (default: us)]\n" //"\t for fm squelch is inverted\n" "\t[-o oversampling (default: 1, 4 recommended)]\n" @@ -233,6 +277,7 @@ void usage(void) "\t direct: enable direct sampling (bypasses tuner, uses rtl2832 xtal)\n" "\t offset: enable offset tuning (only e4000 tuner)\n" "\t[-q dc_avg_factor for option rdc (default: 9)]\n" + "\t[-n disables demodulation output to stdout/file]\n" "\tfilename ('-' means stdout)\n" "\t omitting the filename also uses stdout\n\n" "Experimental options:\n" @@ -306,45 +351,88 @@ double log2(double n) } #endif + +/* uint8_t negation = 255 - x */ +#define NEG_U8( x ) ( 255 - x ) +/* MUL_PLUS_J: (a + j*b ) * j = -b + j * a */ +/* MUL_MINUS_ONE: (a + j*b ) * -1 = -a + j * -b */ +/* MUL_MINUS_J: (a + j*b ) * -j = b + j * -a */ +#define MUL_PLUS_J_U8( X, J ) \ + tmp = X[J]; \ + X[J] = NEG_U8( X[J+1] ); \ + X[J+1] = tmp + +#define MUL_MINUS_ONE_U8( X, J ) \ + X[J] = NEG_U8( X[J] ); \ + X[J+1] = NEG_U8( X[J+1] ) + +#define MUL_MINUS_J_U8( X, J ) \ + tmp = X[J]; \ + X[J] = X[J+1]; \ + X[J+1] = NEG_U8( tmp ) + + +#define MUL_PLUS_J_INT( X, J ) \ + tmp = X[J]; \ + X[J] = - X[J+1]; \ + X[J+1] = tmp + +#define MUL_MINUS_ONE_INT( X, J ) \ + X[J] = - X[J]; \ + X[J+1] = - X[J+1] + +#define MUL_MINUS_J_INT( X, J ) \ + tmp = X[J]; \ + X[J] = X[J+1]; \ + X[J+1] = -tmp + + void rotate16_90(int16_t *buf, uint32_t len) -/* 90 rotation is 1+0j, 0+1j, -1+0j, 0-1j - or [0, 1, -3, 2, -4, -5, 7, -6] */ { + /* 90 degree rotation is 1, +j, -1, -j */ uint32_t i; int16_t tmp; for (i=0; ilp_len = i2; } +static char * trim(char * s) { + char *p = s; + int l = strlen(p); + + while(isspace(p[l - 1])) p[--l] = 0; + while(*p && isspace(*p)) ++p; + + return p; +} + + +static void cmd_init(struct cmd_state *c) +{ + int k; + c->filename = NULL; + c->file = NULL; + c->lineNo = 1; + c->freq = 0; + c->gain = 0; + c->trigCrit = crit_IN; + c->refLevel = 0.0; + c->refLevelTol = 0.0; + c->numMeas = 0; + c->numBlockTrigger = 0; + c->command = NULL; + c->args = NULL; + c->levelSum = 0.0; + c->numSummed = 0; + c->omitFirstFreqLevels = 1; + for (k = 0; k < FREQUENCIES_LIMIT; k++) { + c->waitTrigger[k] = 0; + c->statNumLevels[k] = 0; + c->statFreq[k] = 0; + c->statSumLevels[k] = 0.0; + c->statMinLevel[k] = 0.0F; + c->statMaxLevel[k] = 0.0F; + } +} + +static int toNextCmdLine(struct cmd_state *c) +{ + const char * delim = ","; + char * pLine = NULL; + char * pCmdFreq = NULL; + char * pCmdGain = NULL; + char * pCmdTrigCrit = NULL; + char * pCmdLevel = NULL; + char * pCmdTol = NULL; + char * pCmdNumMeas = NULL; + char * pCmdNumBlockTrigger = NULL; + int numValidLines = 1; /* assume valid lines */ + while (1) { + if (c->file && feof(c->file)) { + if (!numValidLines) { + fprintf(stderr, "error: command file '%s' does not contain any valid lines!\n", c->filename); + return 0; + } + fclose(c->file); + c->file = NULL; + } + if (!c->file) { + c->file = fopen(c->filename, "r"); + numValidLines = 0; + c->lineNo = 0; + } + if (!c->file) + return 0; + pLine = fgets(c->acLine, 4096, c->file); + if (!pLine) { + continue; + } + c->lineNo++; + pLine = trim(c->acLine); + if (pLine[0]=='#' || pLine[0]==0) + continue; /* detect comment lines and empty lines */ + + pCmdFreq = strtok(pLine, delim); + if (!pCmdFreq) { fprintf(stderr, "error parsing frequency in line %d of command file!\n", c->lineNo); continue; } + c->freq = (uint32_t)atofs(trim(pCmdFreq)); + + pCmdGain = strtok(NULL, delim); + if (!pCmdGain) { fprintf(stderr, "error parsing gain in line %d of command file!\n", c->lineNo); continue; } + pCmdGain = trim(pCmdGain); + if (!strcmp(pCmdGain,"auto") || !strcmp(pCmdGain,"a")) + c->gain = AUTO_GAIN; + else + c->gain = (int)(atof(pCmdGain) * 10); + + pCmdTrigCrit = strtok(NULL, delim); + if (!pCmdTrigCrit) { fprintf(stderr, "error parsing expr in line %d of command file!\n", c->lineNo); continue; } + pCmdTrigCrit = trim(pCmdTrigCrit); + if (!strcmp(pCmdTrigCrit,"in")) c->trigCrit = crit_IN; + else if (!strcmp(pCmdTrigCrit,"==")) c->trigCrit = crit_IN; + else if (!strcmp(pCmdTrigCrit,"out")) c->trigCrit = crit_OUT; + else if (!strcmp(pCmdTrigCrit,"!=")) c->trigCrit = crit_OUT; + else if (!strcmp(pCmdTrigCrit,"<>")) c->trigCrit = crit_OUT; + else if (!strcmp(pCmdTrigCrit,"lt")) c->trigCrit = crit_LT; + else if (!strcmp(pCmdTrigCrit,"<")) c->trigCrit = crit_LT; + else if (!strcmp(pCmdTrigCrit,"gt")) c->trigCrit = crit_GT; + else if (!strcmp(pCmdTrigCrit,">")) c->trigCrit = crit_GT; + else { fprintf(stderr, "error parsing expr in line %d of command file!\n", c->lineNo); continue; } + + pCmdLevel = strtok(NULL, delim); + if (!pCmdLevel) { fprintf(stderr, "error parsing level in line %d of command file!\n", c->lineNo); continue; } + c->refLevel = atof(trim(pCmdLevel)); + + pCmdTol = strtok(NULL, delim); + if (!pCmdTol) { fprintf(stderr, "error parsing tolerance in line %d of command file!\n", c->lineNo); continue; } + c->refLevelTol = atof(trim(pCmdTol)); + + pCmdNumMeas = strtok(NULL, delim); + if (!pCmdNumMeas) { fprintf(stderr, "error parsing #measurements in line %d of command file!\n", c->lineNo); continue; } + c->numMeas = atoi(trim(pCmdNumMeas)); + if (c->numMeas <= 0) { fprintf(stderr, "warning: fixed #measurements from %d to 10 in line %d of command file!\n", c->numMeas, c->lineNo); c->numMeas=10; } + + pCmdNumBlockTrigger = strtok(NULL, delim); + if (!pCmdNumBlockTrigger) { fprintf(stderr, "error parsing #blockTrigger in line %d of command file!\n", c->lineNo); continue; } + c->numBlockTrigger = atoi(trim(pCmdNumBlockTrigger)); + + c->command = strtok(NULL, delim); + /* no check: allow empty string. just trim it */ + if (c->command) + c->command = trim(c->command); + + c->args = strtok(NULL, delim); + /* no check: allow empty string. just trim it */ + if (c->args) + c->args = trim(c->args); + + if (verbosity >= 2) + fprintf(stderr, "read from cmd file: freq %.3f kHz, gain %0.1f dB, level %s {%.1f +/- %.1f}, cmd '%s %s'\n", + c->freq /1000.0, c->gain /10.0, + aCritStr[c->trigCrit], c->refLevel, c->refLevelTol, + (c->command ? c->command : "%"), (c->args ? c->args : "") ); + + numValidLines++; + return 1; + } + + return 0; +} + +static int testTrigCrit(struct cmd_state *c, double level) +{ + switch(c->trigCrit) + { + case crit_IN: return ( c->refLevel-c->refLevelTol <= level && level <= c->refLevel+c->refLevelTol ); + case crit_OUT: return ( c->refLevel-c->refLevelTol > level || level > c->refLevel+c->refLevelTol ); + case crit_LT: return ( level < c->refLevel-c->refLevelTol ); + case crit_GT: return ( level > c->refLevel+c->refLevelTol ); + } + return 0; +} + +static void checkTriggerCommand(struct cmd_state *c) +{ + char acRepFreq[32], acRepGain[32], acRepMLevel[32], acRepRefLevel[32], acRepRefTolerance[32]; + char * execSearchStrings[7] = { "!freq!", "!gain!", "!mlevel!", "!crit!", "!reflevel!", "!reftol!", NULL }; + char * execReplaceStrings[7] = { acRepFreq, acRepGain, acRepMLevel, NULL, acRepRefLevel, acRepRefTolerance, NULL }; + double triggerLevel; + int k, triggerCommand = 0; + + if (c->numSummed != c->numMeas) + return; + + if (c->omitFirstFreqLevels) { + /* workaround: measured levels of first controlled frequency looks wrong! */ + c->omitFirstFreqLevels--; + return; + } + + /* decrease all counters */ + for ( k = 0; k < FREQUENCIES_LIMIT; k++ ) { + if ( c->waitTrigger[k] > 0 ) { + c->waitTrigger[k] -= c->numMeas; + if ( c->waitTrigger[k] < 0 ) + c->waitTrigger[k] = 0; + } + } + triggerLevel = 20.0 * log10( 1E-10 + c->levelSum / c->numSummed ); + triggerCommand = testTrigCrit(c, triggerLevel); + + /* update statistics */ + if ( c->lineNo < FREQUENCIES_LIMIT ) { + if ( c->statNumLevels[c->lineNo] == 0 ) { + ++c->statNumLevels[c->lineNo]; + c->statFreq[c->lineNo] = c->freq; + c->statSumLevels[c->lineNo] = triggerLevel; + c->statMinLevel[c->lineNo] = (float)triggerLevel; + c->statMaxLevel[c->lineNo] = (float)triggerLevel; + } else if ( c->statFreq[c->lineNo] == c->freq ) { + ++c->statNumLevels[c->lineNo]; + c->statSumLevels[c->lineNo] += triggerLevel; + if ( c->statMinLevel[c->lineNo] > (float)triggerLevel ) + c->statMinLevel[c->lineNo] = (float)triggerLevel; + if ( c->statMaxLevel[c->lineNo] < (float)triggerLevel ) + c->statMaxLevel[c->lineNo] = (float)triggerLevel; + } + } + + if ( c->lineNo < FREQUENCIES_LIMIT && c->waitTrigger[c->lineNo] <= 0 ) { + c->waitTrigger[c->lineNo] = triggerCommand ? c->numBlockTrigger : 0; + if (verbosity) + fprintf(stderr, "frequency %.3f kHz: level %.1f dB => %s\n", + (double)c->freq /1000.0, triggerLevel, (triggerCommand ? "activates trigger" : "does not trigger") ); + if (triggerCommand && c->command && c->command[0]) { + fprintf(stderr, "command to trigger is '%s %s'\n", c->command, c->args); + /* prepare search/replace of special parameters for command arguments */ + snprintf(acRepFreq, 32, "%u", c->freq); + snprintf(acRepGain, 32, "%d", c->gain); + snprintf(acRepMLevel, 32, "%d", (int)(0.5 + triggerLevel*10.0) ); + execReplaceStrings[3] = aCritStr[c->trigCrit]; + snprintf(acRepRefLevel, 32, "%d", (int)(0.5 + c->refLevel*10.0) ); + snprintf(acRepRefTolerance, 32, "%d", (int)(0.5 + c->refLevelTol*10.0) ); + executeInBackground( c->command, c->args, execSearchStrings, execReplaceStrings ); + } + } else { + fprintf(stderr, "frequency %.3f kHz: level %.1f dB => %s, blocks for %d\n", + (double)c->freq /1000.0, triggerLevel, (triggerCommand ? "would trigger" : "does not trigger"), + (c->lineNo < FREQUENCIES_LIMIT ? c->waitTrigger[c->lineNo] : -1 ) ); + } + c->numSummed++; +} + + int low_pass_simple(int16_t *signal2, int len, int step) // no wrap around, length must be multiple of step { @@ -714,19 +1027,30 @@ int mad(int16_t *samples, int len, int step) return sum / (len / step); } -int rms(int16_t *samples, int len, int step) +int rms(int16_t *samples, int len, int step, int omitDCfix) /* largely lifted from rtl_power */ { - int i; - long p, t, s; double dc, err; - - p = t = 0L; + int i, num; + int32_t t, s; + uint32_t p; /* use sign bit to prevent overflow */ + + p = 0; + t = 0L; + while (len > step * 32768) /* 8 bit squared = 16 bit. limit to 2^16 for 32 bit squared sum */ + ++step; /* increase step to prevent overflow */ for (i=0; icmd; + double freqK, avgRms, rmsLevel, avgRmsLevel; int i, ds_p; int sr = 0; ds_p = d->downsample_passes; @@ -822,32 +1148,51 @@ void full_demod(struct demod_state *d) } /* power squelch */ if (d->squelch_level) { - sr = rms(d->lowpassed, d->lp_len, 1); - if (sr < d->squelch_level) { - d->squelch_hits++; - for (i=0; ilp_len; i++) { - d->lowpassed[i] = 0; - } - } else { - d->squelch_hits = 0;} + sr = rms(d->lowpassed, d->lp_len, 1, d->dc_block_raw); + if (sr >= 0) { + if (sr < d->squelch_level) { + d->squelch_hits++; + for (i=0; ilp_len; i++) { + d->lowpassed[i] = 0; + } + } else { + d->squelch_hits = 0;} + } } if (printLevels) { if (!sr) - sr = rms(d->lowpassed, d->lp_len, 1); + sr = rms(d->lowpassed, d->lp_len, 1, d->dc_block_raw); --printLevelNo; - if (printLevels) { + if (printLevels && sr >= 0) { levelSum += sr; if (levelMax < sr) levelMax = sr; if (levelMaxMax < sr) levelMaxMax = sr; if (!printLevelNo) { printLevelNo = printLevels; - fprintf(stderr, "%f, %d, %d, %d\n", (levelSum / printLevels), levelMax, levelMaxMax, d->squelch_level ); + freqK = dongle.userFreq /1000.0; + avgRms = levelSum / printLevels; + rmsLevel = 20.0 * log10( 1E-10 + sr ); + avgRmsLevel = 20.0 * log10( 1E-10 + avgRms ); + fprintf(stderr, "%.3f kHz, %.1f avg rms, %d max rms, %d max max rms, %d squelch rms, %d rms, %.1f dB rms level, %.2f dB avg rms level\n", + freqK, avgRms, levelMax, levelMaxMax, d->squelch_level, sr, rmsLevel, avgRmsLevel ); levelMax = 0; levelSum = 0; } } } + + if (c->filename) { + if (!sr) + sr = rms(d->lowpassed, d->lp_len, 1, d->dc_block_raw); + if (!c->numSummed) + c->levelSum = 0; + if (c->numSummed < c->numMeas && sr >= 0) { + c->levelSum += sr; + c->numSummed++; + } + } + d->mode_demod(d); /* lowpassed -> result */ if (d->mode_demod == &raw_demod) { return; @@ -868,18 +1213,23 @@ void full_demod(struct demod_state *d) static void rtlsdr_callback(unsigned char *buf, uint32_t len, void *ctx) { - int i; struct dongle_state *s = ctx; struct demod_state *d = s->demod_target; + struct cmd_state *c = d->cmd; + int i, muteLen = s->mute; if (do_exit) { return;} if (!ctx) { return;} if (s->mute) { - for (i=0; imute; i++) { - buf[i] = 127;} - s->mute = 0; + if(muteLen > (int)len) + muteLen = len; + s->mute -= muteLen; /* we may need to mute multiple blocks */ + if(!c->filename) { + for (i=0; idc_block_raw) { dc_block_raw_filter(d, s->buf16, (int)len); } - /* 3rd: up-mixing */ + if (muteLen && c->filename) + return; /* "mute" after the dc_block_raw_filter(), giving it time to remove the new DC */ + /* 3rd: down-mixing */ if (!s->offset_tuning) { - rotate16_90(s->buf16, (int)len); - /* rotate_90(buf, len); */ + rotate16_neg90(s->buf16, (int)len); } pthread_rwlock_wrlock(&d->rw); memcpy(d->lowpassed, s->buf16, 2*len); @@ -912,6 +1263,7 @@ static void *demod_thread_fn(void *arg) { struct demod_state *d = arg; struct output_state *o = d->output_target; + struct cmd_state *c = d->cmd; while (!do_exit) { safe_cond_wait(&d->ready, &d->ready_m); pthread_rwlock_wrlock(&d->rw); @@ -925,11 +1277,24 @@ static void *demod_thread_fn(void *arg) safe_cond_signal(&controller.hop, &controller.hop_m); continue; } - pthread_rwlock_wrlock(&o->rw); - memcpy(o->result, d->result, 2*d->result_len); - o->result_len = d->result_len; - pthread_rwlock_unlock(&o->rw); - safe_cond_signal(&o->ready, &o->ready_m); + + if (do_exit) + break; + + if (c->filename && c->numSummed >= c->numMeas) { + checkTriggerCommand(c); + + safe_cond_signal(&controller.hop, &controller.hop_m); + continue; + } + + if (OutputToStdout) { + pthread_rwlock_wrlock(&o->rw); + memcpy(o->result, d->result, 2*d->result_len); + o->result_len = d->result_len; + pthread_rwlock_unlock(&o->rw); + safe_cond_signal(&o->ready, &o->ready_m); + } } return 0; } @@ -947,51 +1312,53 @@ static void *output_thread_fn(void *arg) return 0; } -static void optimal_settings(int freq, int rate) +static void optimal_settings(uint32_t freq, uint32_t rate) { // giant ball of hacks // seems unable to do a single pass, 2:1 - int capture_freq, capture_rate; + uint32_t capture_freq, capture_rate; struct dongle_state *d = &dongle; struct demod_state *dm = &demod; struct controller_state *cs = &controller; - dm->downsample = (1000000 / dm->rate_in) + 1; + dm->downsample = (MinCaptureRate / dm->rate_in) + 1; if (dm->downsample_passes) { dm->downsample_passes = (int)log2(dm->downsample) + 1; dm->downsample = 1 << dm->downsample_passes; } - if (verbosity) { + if (verbosity >= 2) { fprintf(stderr, "downsample_passes = %d (= # of fifth_order() iterations), downsample = %d\n", dm->downsample_passes, dm->downsample ); } capture_freq = freq; capture_rate = dm->downsample * dm->rate_in; - if (verbosity) + if (verbosity >= 2) fprintf(stderr, "capture_rate = dm->downsample * dm->rate_in = %d * %d = %d\n", dm->downsample, dm->rate_in, capture_rate ); if (!d->offset_tuning) { - capture_freq = freq + capture_rate/4; - if (verbosity) - fprintf(stderr, "optimal_settings(freq = %d): capture_freq = freq + capture_rate/4 = %d\n", freq, capture_freq ); + capture_freq = freq - capture_rate/4; + if (verbosity >= 2) + fprintf(stderr, "optimal_settings(freq = %u): capture_freq = freq - capture_rate/4 = %u\n", freq, capture_freq ); } capture_freq += cs->edge * dm->rate_in / 2; - if (verbosity) - fprintf(stderr, "optimal_settings(freq = %d): capture_freq += cs->edge * dm->rate_in / 2 = %d * %d / 2 = %d\n", freq, cs->edge, dm->rate_in, capture_freq ); + if (verbosity >= 2) + fprintf(stderr, "optimal_settings(freq = %u): capture_freq += cs->edge * dm->rate_in / 2 = %d * %d / 2 = %u\n", freq, cs->edge, dm->rate_in, capture_freq ); dm->output_scale = (1<<15) / (128 * dm->downsample); if (dm->output_scale < 1) { dm->output_scale = 1;} if (dm->mode_demod == &fm_demod) { dm->output_scale = 1;} - d->freq = (uint32_t)capture_freq; - d->rate = (uint32_t)capture_rate; - if (verbosity) - fprintf(stderr, "optimal_settings(freq = %d) delivers freq %.0f, rate %.0f\n", freq, (double)d->freq, (double)d->rate ); + d->userFreq = freq; + d->freq = capture_freq; + d->rate = capture_rate; + if (verbosity >= 2) + fprintf(stderr, "optimal_settings(freq = %u) delivers freq %.0f, rate %.0f\n", freq, (double)d->freq, (double)d->rate ); } static void *controller_thread_fn(void *arg) { // thoughts for multiple dongles // might be no good using a controller thread if retune/rate blocks - int i; + int i, r, execWaitHop = 1; struct controller_state *s = arg; + struct cmd_state *c = s->cmd; if (s->wb_mode) { if (verbosity) @@ -1001,6 +1368,14 @@ static void *controller_thread_fn(void *arg) } /* set up primary channel */ + if (c->filename) { + dongle.mute = dongle.rate; /* over a second - until parametrized the dongle */ + toNextCmdLine(c); + /*fprintf(stderr, "\nswitched to next command line. new freq %u\n", c->freq);*/ + s->freqs[0] = c->freq; + execWaitHop = 0; + } + optimal_settings(s->freqs[0], demod.rate_in); if (dongle.direct_sampling) { verbose_direct_sampling(dongle.dev, 1);} @@ -1009,7 +1384,7 @@ static void *controller_thread_fn(void *arg) /* Set the frequency */ if (verbosity) { - fprintf(stderr, "verbose_set_frequency(%.0f Hz)\n", (double)dongle.freq); + fprintf(stderr, "verbose_set_frequency(%.3f kHz)\n", (double)dongle.userFreq /1000.0); if (!dongle.offset_tuning) fprintf(stderr, " frequency is away from parametrized one, to avoid negative impact from dc\n"); } @@ -1026,14 +1401,60 @@ static void *controller_thread_fn(void *arg) fprintf(stderr, "Output at %u Hz.\n", demod.rate_in/demod.post_downsample); while (!do_exit) { - safe_cond_wait(&s->hop, &s->hop_m); - if (s->freq_len <= 1) { + if (execWaitHop) + safe_cond_wait(&s->hop, &s->hop_m); + execWaitHop = 1; /* execute following safe_cond_wait()'s */ + /* fprintf(stderr, "\nreceived hop condition\n"); */ + if (s->freq_len <= 1 && !c->filename) { continue;} - /* hacky hopping */ - s->freq_now = (s->freq_now + 1) % s->freq_len; - optimal_settings(s->freqs[s->freq_now], demod.rate_in); - rtlsdr_set_center_freq(dongle.dev, dongle.freq); - dongle.mute = BUFFER_DUMP; + if (!c->filename) { + /* hacky hopping */ + s->freq_now = (s->freq_now + 1) % s->freq_len; + optimal_settings(s->freqs[s->freq_now], demod.rate_in); + rtlsdr_set_center_freq(dongle.dev, dongle.freq); + dongle.mute = DEFAULT_BUFFER_DUMP; + } else { + dongle.mute = 2 * 3200000; /* over a second - until parametrized the dongle */ + c->numSummed = 0; + + toNextCmdLine(c); + + optimal_settings(c->freq, demod.rate_in); + /* 1- set center frequency */ + rtlsdr_set_center_freq(dongle.dev, dongle.freq); + /* 2- Set the tuner gain */ + if (c->gain == AUTO_GAIN) { + r = rtlsdr_set_tuner_gain_mode(dongle.dev, 0); + if (r != 0) + fprintf(stderr, "WARNING: Failed to set automatic tuner gain.\n"); + } else { + c->gain = nearest_gain(dongle.dev, c->gain); + r = rtlsdr_set_tuner_gain_mode(dongle.dev, 1); + if (r < 0) + fprintf(stderr, "WARNING: Failed to enable manual gain.\n"); + r = rtlsdr_set_tuner_gain(dongle.dev, c->gain); + if (r != 0) + fprintf(stderr, "WARNING: Failed to set tuner gain.\n"); + } + /* 3- Set tuner bandwidth */ + r = rtlsdr_set_tuner_bandwidth(dongle.dev, dongle.bandwidth); + if (r < 0) + fprintf(stderr, "WARNING: Failed to set bandwidth.\n"); + /* 4- Set ADC samplerate * + r = rtlsdr_set_sample_rate(dongle.dev, dongle.rate); + if (r < 0) + fprintf(stderr, "WARNING: Failed to set sample rate.\n"); + */ + + c->levelSum = 0; + c->numSummed = 0; + /* reset DC filters */ + demod.dc_avg = 0; + demod.dc_avgI = 0; + demod.dc_avgQ = 0; + dongle.mute = BufferDump; + } + } return 0; } @@ -1061,12 +1482,13 @@ void frequency_range(struct controller_state *s, char *arg) void dongle_init(struct dongle_state *s) { s->rate = DEFAULT_SAMPLE_RATE; - s->gain = AUTO_GAIN; // tenths of a dB + s->gain = AUTO_GAIN; /* tenths of a dB */ s->mute = 0; s->direct_sampling = 0; s->offset_tuning = 0; s->demod_target = &demod; s->bandwidth = 0; + s->buf_len = 32 * 512; /* see rtl_tcp */ } void demod_init(struct demod_state *s) @@ -1100,6 +1522,7 @@ void demod_init(struct demod_state *s) pthread_cond_init(&s->ready, NULL); pthread_mutex_init(&s->ready_m, NULL); s->output_target = &output; + s->cmd = &cmd; } void demod_cleanup(struct demod_state *s) @@ -1132,6 +1555,7 @@ void controller_init(struct controller_state *s) s->wb_mode = 0; pthread_cond_init(&s->hop, NULL); pthread_mutex_init(&s->hop_m, NULL); + s->cmd = &cmd; } void controller_cleanup(struct controller_state *s) @@ -1176,8 +1600,9 @@ int main(int argc, char **argv) demod_init(&demod); output_init(&output); controller_init(&controller); + cmd_init(&cmd); - while ((opt = getopt(argc, argv, "d:f:g:s:b:l:L:o:t:r:p:E:q:F:A:M:c:h:w:D:Tv")) != -1) { + while ((opt = getopt(argc, argv, "d:f:C:B:m:g:s:b:l:L:o:t:r:p:E:q:F:A:M:c:h:w:D:Tnv")) != -1) { switch (opt) { case 'd': dongle.dev_index = verbose_device_search(optarg); @@ -1194,6 +1619,19 @@ int main(int argc, char **argv) controller.freq_len++; } break; + case 'C': + cmd.filename = optarg; + demod.mode_demod = &raw_demod; + break; + case 'm': + MinCaptureRate = (int)atofs(optarg); + break; + case 'B': + BufferDump = atoi(optarg); + break; + case 'n': + OutputToStdout = 0; + break; case 'g': dongle.gain = (int)(atof(optarg) * 10); break; @@ -1305,8 +1743,6 @@ int main(int argc, char **argv) break; case 'w': dongle.bandwidth = (uint32_t)atofs(optarg); - if (dongle.bandwidth) - dongle.offset_tuning = 1; /* automatically switch offset tuning, when using bandwidth filter */ break; case 'h': case '?': @@ -1379,7 +1815,7 @@ int main(int argc, char **argv) } rtlsdr_set_agc_mode(dongle.dev, rtlagc); - + rtlsdr_set_bias_tee(dongle.dev, enable_biastee); if (enable_biastee) fprintf(stderr, "activated bias-T on GPIO PIN 0\n"); @@ -1389,7 +1825,7 @@ int main(int argc, char **argv) /* Set direct sampling with threshold */ rtlsdr_set_ds_mode(dongle.dev, ds_mode, ds_threshold); - verbose_set_bandwidth(dongle.dev, dongle.bandwidth); + verbose_set_bandwidth(dongle.dev, dongle.bandwidth); if (verbosity && dongle.bandwidth) { @@ -1425,7 +1861,7 @@ int main(int argc, char **argv) verbose_reset_buffer(dongle.dev); pthread_create(&controller.thread, NULL, controller_thread_fn, (void *)(&controller)); - usleep(100000); + usleep(1000000); /* it looks, that startup of dongle level takes some time at startup! */ pthread_create(&output.thread, NULL, output_thread_fn, (void *)(&output)); pthread_create(&demod.thread, NULL, demod_thread_fn, (void *)(&demod)); pthread_create(&dongle.thread, NULL, dongle_thread_fn, (void *)(&dongle)); @@ -1453,6 +1889,15 @@ int main(int argc, char **argv) output_cleanup(&output); controller_cleanup(&controller); + if (cmd.filename) { + int k; + /* output scan statistics */ + for (k = 0; k < FREQUENCIES_LIMIT; k++) { + if (cmd.statNumLevels[k] > 0) + fprintf(stderr, "%u, %.1f, %.2f, %.1f\n", cmd.statFreq[k], cmd.statMinLevel[k], cmd.statSumLevels[k] / cmd.statNumLevels[k], cmd.statMaxLevel[k] ); + } + } + if (output.file != stdout) { fclose(output.file);} From 893225602c0899d65d61211b1a1480f480c000dc Mon Sep 17 00:00:00 2001 From: Hayati Ayguen Date: Fri, 9 Sep 2016 20:08:32 +0000 Subject: [PATCH 4/6] README explaining rtl_fm option '-C' and the command file Signed-off-by: Hayati Ayguen --- README.rtlfm_cmdfile | 76 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 76 insertions(+) create mode 100644 README.rtlfm_cmdfile diff --git a/README.rtlfm_cmdfile b/README.rtlfm_cmdfile new file mode 100644 index 0000000..1836345 --- /dev/null +++ b/README.rtlfm_cmdfile @@ -0,0 +1,76 @@ + +rtl_fm now has option '-C' for a command file, from which a list of frequencies are read. +So it's similar to using a frequency range, as with "-f 118M:137M:25k" +The difference is, that you can parametrize one frequency per line together with +- the tuner gain +- condition for triggering +- measurement duration +and a command to execute. +Lines starting with '#' are skipped / interpreted as comments. +Parameters a seperated by comma. + +Here's an example: +--- +# freq in Hz, gain in dB, trig_crit (in/out/lt/gt), trig_level, trig_tolerance, #meas, #blocks, trigger_command +# windows: rtl_fm -f 105.2m -E rdc -w 350k -s 200k -m 2.2m -B 200000 -C cmdfile.csv -n -v +# linux: ./rtl_fm -f 105.2m -E rdc -w 350k -s 200k -m 2.2m -B 200000 -C cmdfile.csv -n -v +# +# windows command examples: +# cmd.exe, /C echo hello world +# cmd.exe, /C start notepad +# calc.exe +# +# linux examples: +# ssmtp +# sendxmpp +## for piping some message to ssmtp or sendxmpp you'll need to write small scripts + +100.7m, 30, in, 0, 1, 10, 100, +33.0m, 20,out,60, 3, 10, 400, /home/odroid/test/simple.sh, frq !freq! gain !gain! measured !mlevel! tenth dB !crit! { !reflevel! +/- !reftol! tenth dB } + +--- + +* first frequency is 100.7 MHz, tuned with ~ 30 dB tuner gain; +condition is 'in' { 0 +/- 1 } dB, +with 10 measurements, averaging the rms level in dB. +If condition is true, then a command could be triggered, which would be executed in background. +Then, a next trigger for this frequency is blocked for 100 measurements. +There is nothing triggered for 100.7 MHz. + +* 2nd frequency is 33.0 MHz, tuned with ~ 20 dB tuner gain; +condition is 'out' { 60 +/- 3 } dB, +with 10 measurements. +That means, the trigger is activated when averaged level is below 57 dB or above 63 dB. +Next trigger for this frequency is blocked for 400 measurements. +Triggered command is the shell script '/home/odroid/test/simple.sh', +with the arguments 'frq !freq! gain !gain! measured !mlevel! tenth dB !crit! { !reflevel! +/- !reftol! tenth dB }'. +You can use following keywords in the arguments, which need to be free standing!: +- !freq! +current frequency in Hz + +- !gain! +current tuner gain in tenth dB to allow easier evaluation from scripts. + +- !mlevel! +average measured level in tenth dB + +- !crit! +one of "in", "out", "<" or ">" for the tested condition + +- !reflevel! +condition's reference level in tenth dB + +- !reftol! +condition's reference tolerance in tenth dB + + +Application might be monitoring of some stations +and triggering a notification, e.g. via ssmtp or sendxmpp, +when a stations power level is below it's expected value. + +Another application might be triggering a recording with a second RTL dongle. + + +Send comments, suggestions or reports to +Hayati Ayguen + From aa79aa24a79bdf1f051faef05846b1dfe7d07c5e Mon Sep 17 00:00:00 2001 From: hayati ayguen Date: Wed, 14 Sep 2016 08:23:38 +0200 Subject: [PATCH 5/6] added options adcmax and adcrms in command file and blocksize * adc options are for determining optimal gain settings and detect oversteering * added option -W for block size * omit setting of frequency/gain/bandwidth when no change to optimise scanning Signed-off-by: hayati ayguen --- src/rtl_fm.c | 164 ++++++++++++++++++++++++++++++++++++++++++--------- 1 file changed, 135 insertions(+), 29 deletions(-) diff --git a/src/rtl_fm.c b/src/rtl_fm.c index abf1ce4..3502984 100644 --- a/src/rtl_fm.c +++ b/src/rtl_fm.c @@ -118,6 +118,11 @@ struct cmd_state FILE * file; int lineNo; char acLine[4096]; + int checkADCmax; + int checkADCrms; + uint32_t prevFreq; + int prevGain; + uint32_t prevBandwidth; uint32_t freq; int gain; enum trigExpr trigCrit; @@ -156,6 +161,9 @@ struct dongle_state int direct_sampling; int mute; struct demod_state *demod_target; + double samplePowSum; + int samplePowCount; + unsigned char sampleMax; }; struct demod_state @@ -257,6 +265,7 @@ void usage(void) "\t[-D direct_sampling_threshold_frequency (default: 0 use tuner specific frequency threshold for 3 and 4)]\n" "\t[-g tuner_gain (default: automatic)]\n" "\t[-w tuner_bandwidth in Hz (default: automatic)]\n" + "\t[-W length of single buffer in units of 512 samples (default: 32 was 256)]\n" "\t[-l squelch_level (default: 0/off)]\n" "\t[-L N prints levels every N calculations]\n" "\t output are comma separated values (csv):\n" @@ -475,6 +484,11 @@ static void cmd_init(struct cmd_state *c) c->filename = NULL; c->file = NULL; c->lineNo = 1; + c->checkADCmax = 0; + c->checkADCrms = 0; + c->prevFreq = -1; + c->prevGain = -200; + c->prevBandwidth = -1; c->freq = 0; c->gain = 0; c->trigCrit = crit_IN; @@ -486,7 +500,7 @@ static void cmd_init(struct cmd_state *c) c->args = NULL; c->levelSum = 0.0; c->numSummed = 0; - c->omitFirstFreqLevels = 1; + c->omitFirstFreqLevels = 3; for (k = 0; k < FREQUENCIES_LIMIT; k++) { c->waitTrigger[k] = 0; c->statNumLevels[k] = 0; @@ -536,7 +550,17 @@ static int toNextCmdLine(struct cmd_state *c) pCmdFreq = strtok(pLine, delim); if (!pCmdFreq) { fprintf(stderr, "error parsing frequency in line %d of command file!\n", c->lineNo); continue; } - c->freq = (uint32_t)atofs(trim(pCmdFreq)); + pCmdFreq = trim(pCmdFreq); + /* check keywords */ + if (!strcmp(pCmdFreq, "adc") || !strcmp(pCmdFreq, "adcmax")) { + c->checkADCmax = 1; + continue; + } + else if (!strcmp(pCmdFreq, "adcrms")) { + c->checkADCrms = 1; + continue; + } + c->freq = (uint32_t)atofs(pCmdFreq); pCmdGain = strtok(NULL, delim); if (!pCmdGain) { fprintf(stderr, "error parsing gain in line %d of command file!\n", c->lineNo); continue; } @@ -612,13 +636,16 @@ static int testTrigCrit(struct cmd_state *c, double level) return 0; } -static void checkTriggerCommand(struct cmd_state *c) +static void checkTriggerCommand(struct cmd_state *c, unsigned char adcSampleMax, double powerSum, int powerCount ) { char acRepFreq[32], acRepGain[32], acRepMLevel[32], acRepRefLevel[32], acRepRefTolerance[32]; char * execSearchStrings[7] = { "!freq!", "!gain!", "!mlevel!", "!crit!", "!reflevel!", "!reftol!", NULL }; char * execReplaceStrings[7] = { acRepFreq, acRepGain, acRepMLevel, NULL, acRepRefLevel, acRepRefTolerance, NULL }; double triggerLevel; + double adcRms = 0.0; int k, triggerCommand = 0; + int adcMax = (int)adcSampleMax - 127; + char adcText[128]; if (c->numSummed != c->numMeas) return; @@ -658,11 +685,24 @@ static void checkTriggerCommand(struct cmd_state *c) } } + adcText[0] = 0; + if (c->checkADCmax && c->checkADCrms) { + adcRms = (powerCount >0) ? sqrt( powerSum / powerCount ) : -1.0; + sprintf(adcText, "adc max %3d%s rms %5.1f ", adcMax, (adcMax>=64 ? (adcMax>=120 ? "!!" : "! " ) : " "), adcRms ); + } + else if (c->checkADCmax) + sprintf(adcText, "adc max %3d%s ", adcMax, (adcMax>=64 ? (adcMax>=120 ? "!!" : "! " ) : " ") ); + else if (c->checkADCrms) { + adcRms = (powerCount >0) ? sqrt( powerSum / powerCount ) : -1.0; + sprintf(adcText, "adc rms %5.1f ", adcRms ); + } + if ( c->lineNo < FREQUENCIES_LIMIT && c->waitTrigger[c->lineNo] <= 0 ) { c->waitTrigger[c->lineNo] = triggerCommand ? c->numBlockTrigger : 0; if (verbosity) - fprintf(stderr, "frequency %.3f kHz: level %.1f dB => %s\n", - (double)c->freq /1000.0, triggerLevel, (triggerCommand ? "activates trigger" : "does not trigger") ); + fprintf(stderr, "%.3f kHz: gain %4.1f + level %4.1f dB %s=> %s\n", + (double)c->freq /1000.0, 0.1*c->gain, triggerLevel, adcText, + (triggerCommand ? "activates trigger" : "does not trigger") ); if (triggerCommand && c->command && c->command[0]) { fprintf(stderr, "command to trigger is '%s %s'\n", c->command, c->args); /* prepare search/replace of special parameters for command arguments */ @@ -674,9 +714,9 @@ static void checkTriggerCommand(struct cmd_state *c) snprintf(acRepRefTolerance, 32, "%d", (int)(0.5 + c->refLevelTol*10.0) ); executeInBackground( c->command, c->args, execSearchStrings, execReplaceStrings ); } - } else { - fprintf(stderr, "frequency %.3f kHz: level %.1f dB => %s, blocks for %d\n", - (double)c->freq /1000.0, triggerLevel, (triggerCommand ? "would trigger" : "does not trigger"), + } else if (verbosity) { + fprintf(stderr, "%.3f kHz: gain %4.1f + level %4.1f dB %s=> %s, blocks for %d\n", + (double)c->freq /1000.0, 0.1*c->gain, triggerLevel, adcText, (triggerCommand ? "would trigger" : "does not trigger"), (c->lineNo < FREQUENCIES_LIMIT ? c->waitTrigger[c->lineNo] : -1 ) ); } c->numSummed++; @@ -1129,6 +1169,7 @@ void full_demod(struct demod_state *d) double freqK, avgRms, rmsLevel, avgRmsLevel; int i, ds_p; int sr = 0; + static int printBlockLen = 1; ds_p = d->downsample_passes; if (ds_p) { for (i=0; i < ds_p; i++) { @@ -1185,6 +1226,12 @@ void full_demod(struct demod_state *d) if (c->filename) { if (!sr) sr = rms(d->lowpassed, d->lp_len, 1, d->dc_block_raw); + if ( printBlockLen && verbosity ) { + fprintf(stderr, "block length for rms after decimation is %d samples\n", d->lp_len); + if ( d->lp_len < 128 ) + fprintf(stderr, "\n WARNING: increase block length with option -W\n\n"); + --printBlockLen; + } if (!c->numSummed) c->levelSum = 0; if (c->numSummed < c->numMeas && sr >= 0) { @@ -1217,6 +1264,9 @@ static void rtlsdr_callback(unsigned char *buf, uint32_t len, void *ctx) struct demod_state *d = s->demod_target; struct cmd_state *c = d->cmd; int i, muteLen = s->mute; + unsigned char sampleMax; + uint32_t sampleP, samplePowSum = 0.0; + int samplePowCount = 0, step = 2; if (do_exit) { return;} @@ -1230,6 +1280,33 @@ static void rtlsdr_callback(unsigned char *buf, uint32_t len, void *ctx) for (i=0; isamplePowSum = 0.0; + s->samplePowCount = 0; + s->sampleMax = 0; + } + /* OR all samples to allow checking overflow + * - before conversion to 16 bit and before DC filtering. + * we only get bitmask of positive samples (after -127) but that won't matter */ + if (c->checkADCmax ) { + sampleMax = s->sampleMax; + for (i=0; i<(int)len; i++) { + if ( buf[i] > sampleMax ) + sampleMax = buf[i]; + } + s->sampleMax = sampleMax; + } + if (c->checkADCrms ) { + while ( len >= 16384 * step ) + step += 2; + for (i=0; i<(int)len; i+= step) { + sampleP = ( (int)buf[i] -127 ) * ( (int)buf[i] -127 ); /* I^2 */ + sampleP += ( (int)buf[i+1] -127 ) * ( (int)buf[i+1] -127 ); /* Q^2 */ + samplePowSum += sampleP; + ++samplePowCount; + } + s->samplePowSum += (double)samplePowSum / samplePowCount; + s->samplePowCount += 1; } /* 1st: convert to 16 bit - to allow easier calculation of DC */ for (i=0; i<(int)len; i++) { @@ -1282,7 +1359,7 @@ static void *demod_thread_fn(void *arg) break; if (c->filename && c->numSummed >= c->numMeas) { - checkTriggerCommand(c); + checkTriggerCommand(c, dongle.sampleMax, dongle.samplePowSum, dongle.samplePowCount); safe_cond_signal(&controller.hop, &controller.hop_m); continue; @@ -1421,25 +1498,40 @@ static void *controller_thread_fn(void *arg) optimal_settings(c->freq, demod.rate_in); /* 1- set center frequency */ - rtlsdr_set_center_freq(dongle.dev, dongle.freq); + if (c->prevFreq != dongle.freq) { + rtlsdr_set_center_freq(dongle.dev, dongle.freq); + c->prevFreq != dongle.freq; + } /* 2- Set the tuner gain */ - if (c->gain == AUTO_GAIN) { - r = rtlsdr_set_tuner_gain_mode(dongle.dev, 0); - if (r != 0) - fprintf(stderr, "WARNING: Failed to set automatic tuner gain.\n"); - } else { - c->gain = nearest_gain(dongle.dev, c->gain); - r = rtlsdr_set_tuner_gain_mode(dongle.dev, 1); - if (r < 0) - fprintf(stderr, "WARNING: Failed to enable manual gain.\n"); - r = rtlsdr_set_tuner_gain(dongle.dev, c->gain); - if (r != 0) - fprintf(stderr, "WARNING: Failed to set tuner gain.\n"); + if (c->prevGain != c->gain) { + if (c->gain == AUTO_GAIN) { + r = rtlsdr_set_tuner_gain_mode(dongle.dev, 0); + if (r != 0) + fprintf(stderr, "WARNING: Failed to set automatic tuner gain.\n"); + else + c->prevGain = c->gain; + } else { + c->gain = nearest_gain(dongle.dev, c->gain); + r = rtlsdr_set_tuner_gain_mode(dongle.dev, 1); + if (r < 0) + fprintf(stderr, "WARNING: Failed to enable manual gain.\n"); + else { + r = rtlsdr_set_tuner_gain(dongle.dev, c->gain); + if (r != 0) + fprintf(stderr, "WARNING: Failed to set tuner gain.\n"); + else + c->prevGain = c->gain; + } + } } /* 3- Set tuner bandwidth */ - r = rtlsdr_set_tuner_bandwidth(dongle.dev, dongle.bandwidth); - if (r < 0) - fprintf(stderr, "WARNING: Failed to set bandwidth.\n"); + if (c->prevBandwidth != dongle.bandwidth) { + r = rtlsdr_set_tuner_bandwidth(dongle.dev, dongle.bandwidth); + if (r < 0) + fprintf(stderr, "WARNING: Failed to set bandwidth.\n"); + else + c->prevBandwidth != dongle.bandwidth; + } /* 4- Set ADC samplerate * r = rtlsdr_set_sample_rate(dongle.dev, dongle.rate); if (r < 0) @@ -1453,6 +1545,10 @@ static void *controller_thread_fn(void *arg) demod.dc_avgI = 0; demod.dc_avgQ = 0; dongle.mute = BufferDump; + /* reset adc max and power */ + dongle.samplePowSum = 0.0; + dongle.samplePowCount = 0; + dongle.sampleMax = 0; } } @@ -1487,6 +1583,9 @@ void dongle_init(struct dongle_state *s) s->direct_sampling = 0; s->offset_tuning = 0; s->demod_target = &demod; + s->samplePowSum = 0.0; + s->samplePowCount = 0; + s->sampleMax = 0; s->bandwidth = 0; s->buf_len = 32 * 512; /* see rtl_tcp */ } @@ -1602,7 +1701,7 @@ int main(int argc, char **argv) controller_init(&controller); cmd_init(&cmd); - while ((opt = getopt(argc, argv, "d:f:C:B:m:g:s:b:l:L:o:t:r:p:E:q:F:A:M:c:h:w:D:Tnv")) != -1) { + while ((opt = getopt(argc, argv, "d:f:C:B:m:g:s:b:l:L:o:t:r:p:E:q:F:A:M:c:h:w:W:D:Tnv")) != -1) { switch (opt) { case 'd': dongle.dev_index = verbose_device_search(optarg); @@ -1744,6 +1843,11 @@ int main(int argc, char **argv) case 'w': dongle.bandwidth = (uint32_t)atofs(optarg); break; + case 'W': + dongle.buf_len = 512 * atoi(optarg); + if (dongle.buf_len > MAXIMUM_BUF_LENGTH) + dongle.buf_len = MAXIMUM_BUF_LENGTH; + break; case 'h': case '?': default: @@ -1766,10 +1870,10 @@ int main(int argc, char **argv) if (controller.freq_len > 1) { demod.terminate_on_squelch = 0;} - if (argc <= optind) { - output.filename = "-"; - } else { + if (optind < argc) { output.filename = argv[optind]; + } else { + output.filename = "-"; } ACTUAL_BUF_LENGTH = lcm_post[demod.post_downsample] * DEFAULT_BUF_LENGTH; @@ -1853,6 +1957,8 @@ int main(int argc, char **argv) fprintf(stderr, "Failed to open %s\n", output.filename); exit(1); } + else + fprintf(stderr, "Open %s for write\n", output.filename); } //r = rtlsdr_set_testmode(dongle.dev, 1); From 99c3e0811e2df98c3b45355a98a2a16802e1258b Mon Sep 17 00:00:00 2001 From: hayati ayguen Date: Thu, 15 Sep 2016 07:57:38 +0200 Subject: [PATCH 6/6] updated README.rtlfm_cmdfile: added keywords adcmax and adcrms also added option -W Signed-off-by: hayati ayguen --- README.rtlfm_cmdfile | 24 +++++++++++++++++++++--- 1 file changed, 21 insertions(+), 3 deletions(-) diff --git a/README.rtlfm_cmdfile b/README.rtlfm_cmdfile index 1836345..52fdf1a 100644 --- a/README.rtlfm_cmdfile +++ b/README.rtlfm_cmdfile @@ -11,8 +11,9 @@ Parameters a seperated by comma. Here's an example: --- -# freq in Hz, gain in dB, trig_crit (in/out/lt/gt), trig_level, trig_tolerance, #meas, #blocks, trigger_command -# windows: rtl_fm -f 105.2m -E rdc -w 350k -s 200k -m 2.2m -B 200000 -C cmdfile.csv -n -v +# freq in Hz or special keyword, gain in dB, trig_crit (in/out/lt/gt), trig_level, trig_tolerance, #meas, #blocks, trigger_command +# windows: rtl_fm -f 105.2m -E rdc -w 350k -s 200k -m 2.2m -B 200000 -C cmdfile.csv -n -v +# rtl_fm -f 105.2m -E rdc -w 350k -s 200k -m 2.2m -W 4 -B 10000 -C cmdfile.csv -n -v # linux: ./rtl_fm -f 105.2m -E rdc -w 350k -s 200k -m 2.2m -B 200000 -C cmdfile.csv -n -v # # windows command examples: @@ -25,15 +26,32 @@ Here's an example: # sendxmpp ## for piping some message to ssmtp or sendxmpp you'll need to write small scripts +# 'adcmax' keyword in first column activates measurement of max adc value at capture rate to determine optimum gain and avoid oversteering +# 'adcrms' keyword activates rms calculation at capture rate. for usual it's similar to adcmax. there are differences in case of oversteering +# activate verbose output (option '-v') to get the output. The maximum possible sample value is 128. +# You should have approx. 6 dB headroom to allow measuring stronger signals, that means measured baseline values should be below 64. +# An the other side you'll want to measure weaker signals, so the measured baseline value should be above a minimum of approx 8 or 16. +adcmax, +adcrms, 100.7m, 30, in, 0, 1, 10, 100, 33.0m, 20,out,60, 3, 10, 400, /home/odroid/test/simple.sh, frq !freq! gain !gain! measured !mlevel! tenth dB !crit! { !reflevel! +/- !reftol! tenth dB } +# now check for optimal gain in some steps, which should be done per frequency!: +100.7m, 0, gt, 400, 1, 1, 100, +100.7m, 3, gt, 400, 1, 1, 100, +100.7m, 6, gt, 400, 1, 1, 100, +100.7m, 12, gt, 400, 1, 1, 100, +100.7m, 18, gt, 400, 1, 1, 100, --- * first frequency is 100.7 MHz, tuned with ~ 30 dB tuner gain; condition is 'in' { 0 +/- 1 } dB, with 10 measurements, averaging the rms level in dB. -If condition is true, then a command could be triggered, which would be executed in background. +Each single measurement is processed after decimation of block/buffer-size many samples (see option -W). + The resulting number of decimated samples might get too small to allow a reliable measurement. + This number also depends on the capture rate: ensure minimum capture rate with option '-m'. This is also important for reducing aliases. + Check the output 'block length after decimation is ... samples'! +If condition for measured level is true, then a command can be triggered, which is executed in background. Then, a next trigger for this frequency is blocked for 100 measurements. There is nothing triggered for 100.7 MHz.