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 <h_ayguen@web.de>
master
Hayati Ayguen 2016-09-09 19:29:05 +00:00 committed by Lucas Teske
parent 75149240ef
commit 3a82583519
No known key found for this signature in database
GPG Key ID: 6C39C1C16A9DA7BE
3 changed files with 600 additions and 84 deletions

View File

@ -22,6 +22,7 @@
#include <string.h> #include <string.h>
#include <stdio.h> #include <stdio.h>
#include <stdlib.h> #include <stdlib.h>
#include <ctype.h>
#ifndef _WIN32 #ifndef _WIN32
#include <unistd.h> #include <unistd.h>
@ -29,6 +30,7 @@
#include <windows.h> #include <windows.h>
#include <fcntl.h> #include <fcntl.h>
#include <io.h> #include <io.h>
#include <process.h>
#define _USE_MATH_DEFINES #define _USE_MATH_DEFINES
#endif #endif
@ -43,6 +45,8 @@ double atofs(char *s)
int len; int len;
double suff = 1.0; double suff = 1.0;
len = strlen(s); len = strlen(s);
/* allow formatting spaces from .csv command file */
while ( len > 1 && isspace(s[len-1]) ) --len;
last = s[len-1]; last = s[len-1];
s[len-1] = '\0'; s[len-1] = '\0';
switch (last) { switch (last) {
@ -325,4 +329,68 @@ int verbose_device_search(char *s)
return -1; 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 // vim: tabstop=8:softtabstop=8:shiftwidth=8:noexpandtab

View File

@ -154,4 +154,7 @@ int verbose_reset_buffer(rtlsdr_dev_t *dev);
int verbose_device_search(char *s); int verbose_device_search(char *s);
void executeInBackground( char * file, char * args, char * searchStr[], char * replaceStr[] );
#endif /*__CONVENIENCE_H*/ #endif /*__CONVENIENCE_H*/

View File

@ -54,6 +54,7 @@
#include <string.h> #include <string.h>
#include <stdio.h> #include <stdio.h>
#include <stdlib.h> #include <stdlib.h>
#include <ctype.h>
#ifndef _WIN32 #ifndef _WIN32
#include <unistd.h> #include <unistd.h>
@ -85,9 +86,13 @@
#define MAXIMUM_OVERSAMPLE 16 #define MAXIMUM_OVERSAMPLE 16
#define MAXIMUM_BUF_LENGTH (MAXIMUM_OVERSAMPLE * DEFAULT_BUF_LENGTH) #define MAXIMUM_BUF_LENGTH (MAXIMUM_OVERSAMPLE * DEFAULT_BUF_LENGTH)
#define AUTO_GAIN -100 #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 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}; 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 int levelMaxMax = 0;
static double levelSum = 0.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 struct dongle_state
{ {
int exit_flag; int exit_flag;
pthread_t thread; pthread_t thread;
rtlsdr_dev_t *dev; rtlsdr_dev_t *dev;
int dev_index; int dev_index;
uint32_t userFreq;
uint32_t freq; uint32_t freq;
uint32_t rate; uint32_t rate;
uint32_t bandwidth; uint32_t bandwidth;
@ -158,6 +193,7 @@ struct demod_state
pthread_cond_t ready; pthread_cond_t ready;
pthread_mutex_t ready_m; pthread_mutex_t ready_m;
struct output_state *output_target; struct output_state *output_target;
struct cmd_state *cmd;
}; };
struct output_state struct output_state
@ -185,6 +221,7 @@ struct controller_state
int wb_mode; int wb_mode;
pthread_cond_t hop; pthread_cond_t hop;
pthread_mutex_t hop_m; pthread_mutex_t hop_m;
struct cmd_state *cmd;
}; };
// multiple of these, eventually // multiple of these, eventually
@ -192,6 +229,8 @@ struct dongle_state dongle;
struct demod_state demod; struct demod_state demod;
struct output_state output; struct output_state output;
struct controller_state controller; struct controller_state controller;
struct cmd_state cmd;
void usage(void) void usage(void)
{ {
@ -201,6 +240,11 @@ void usage(void)
"\t-f frequency_to_tune_to [Hz]\n" "\t-f frequency_to_tune_to [Hz]\n"
"\t use multiple -f for scanning (requires squelch)\n" "\t use multiple -f for scanning (requires squelch)\n"
"\t ranges supported, -f 118M:137M:25k\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[-v increase verbosity (default: 0)]\n"
"\t[-M modulation (default: fm)]\n" "\t[-M modulation (default: fm)]\n"
"\t fm or nbfm or nfm, wbfm or wfm, raw or iq, am, usb, lsb\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_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[-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[-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 squelch_level (default: 0/off)]\n"
"\t[-L N prints levels every N calculations]\n" "\t[-L N prints levels every N calculations]\n"
"\t output are comma separated values (csv):\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[-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 for fm squelch is inverted\n"
"\t[-o oversampling (default: 1, 4 recommended)]\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 direct: enable direct sampling (bypasses tuner, uses rtl2832 xtal)\n"
"\t offset: enable offset tuning (only e4000 tuner)\n" "\t offset: enable offset tuning (only e4000 tuner)\n"
"\t[-q dc_avg_factor for option rdc (default: 9)]\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" "\tfilename ('-' means stdout)\n"
"\t omitting the filename also uses stdout\n\n" "\t omitting the filename also uses stdout\n\n"
"Experimental options:\n" "Experimental options:\n"
@ -306,45 +351,88 @@ double log2(double n)
} }
#endif #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) 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; uint32_t i;
int16_t tmp; int16_t tmp;
for (i=0; i<len; i+=8) { for (i=0; i<len; i+=8) {
tmp = - buf[i+3]; MUL_PLUS_J_INT( buf, i+2 );
buf[i+3] = buf[i+2]; MUL_MINUS_ONE_INT( buf, i+4 );
buf[i+2] = tmp; MUL_MINUS_J_INT( buf, i+6 );
}
}
buf[i+4] = - buf[i+4]; void rotate16_neg90(int16_t *buf, uint32_t len)
buf[i+5] = - buf[i+5]; {
/* -90 degree rotation is 1, -j, -1, +j */
tmp = - buf[i+6]; uint32_t i;
buf[i+6] = buf[i+7]; int16_t tmp;
buf[i+7] = tmp; for (i=0; i<len; i+=8) {
MUL_MINUS_J_INT( buf, i+2 );
MUL_MINUS_ONE_INT( buf, i+4 );
MUL_PLUS_J_INT( buf, i+6 );
} }
} }
void rotate_90(unsigned char *buf, uint32_t len) void rotate_90(unsigned char *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; uint32_t i;
unsigned char tmp; unsigned char tmp;
for (i=0; i<len; i+=8) { for (i=0; i<len; i+=8) {
/* uint8_t negation = 255 - x */ MUL_PLUS_J_U8( buf, i+2 );
tmp = 255 - buf[i+3]; MUL_MINUS_ONE_U8( buf, i+4 );
buf[i+3] = buf[i+2]; MUL_MINUS_J_U8( buf, i+6 );
buf[i+2] = tmp; }
}
buf[i+4] = 255 - buf[i+4]; void rotate_neg90(unsigned char *buf, uint32_t len)
buf[i+5] = 255 - buf[i+5]; {
/* -90 degree rotation is 1, -j, -1, +j */
tmp = 255 - buf[i+6]; uint32_t i;
buf[i+6] = buf[i+7]; unsigned char tmp;
buf[i+7] = tmp; for (i=0; i<len; i+=8) {
MUL_MINUS_J_U8( buf, 2 );
MUL_MINUS_ONE_U8( buf, 4 );
MUL_PLUS_J_U8( buf, 6 );
} }
} }
@ -370,6 +458,231 @@ void low_pass(struct demod_state *d)
d->lp_len = i2; d->lp_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) int low_pass_simple(int16_t *signal2, int len, int step)
// no wrap around, length must be multiple of 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); 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 */ /* largely lifted from rtl_power */
{ {
int i;
long p, t, s;
double dc, err; double dc, err;
int i, num;
int32_t t, s;
uint32_t p; /* use sign bit to prevent overflow */
p = t = 0L; 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; i<len; i+=step) { for (i=0; i<len; i+=step) {
s = (long)samples[i]; s = (long)samples[i];
t += s; t += s;
p += s * s; p += s * s;
} }
if (omitDCfix) {
/* DC is already corrected. No need to do it again */
num = len / step;
return (int)sqrt( (double)(p) / num );
}
/* correct for dc offset in squares */ /* correct for dc offset in squares */
dc = (double)(t*step) / (double)len; dc = (double)(t*step) / (double)len;
err = t * 2 * dc - dc * dc * len; err = t * 2 * dc - dc * dc * len;
@ -801,6 +1125,8 @@ void arbitrary_resample(int16_t *buf1, int16_t *buf2, int len1, int len2)
void full_demod(struct demod_state *d) void full_demod(struct demod_state *d)
{ {
struct cmd_state *c = d->cmd;
double freqK, avgRms, rmsLevel, avgRmsLevel;
int i, ds_p; int i, ds_p;
int sr = 0; int sr = 0;
ds_p = d->downsample_passes; ds_p = d->downsample_passes;
@ -822,32 +1148,51 @@ void full_demod(struct demod_state *d)
} }
/* power squelch */ /* power squelch */
if (d->squelch_level) { if (d->squelch_level) {
sr = rms(d->lowpassed, d->lp_len, 1); sr = rms(d->lowpassed, d->lp_len, 1, d->dc_block_raw);
if (sr < d->squelch_level) { if (sr >= 0) {
d->squelch_hits++; if (sr < d->squelch_level) {
for (i=0; i<d->lp_len; i++) { d->squelch_hits++;
d->lowpassed[i] = 0; for (i=0; i<d->lp_len; i++) {
} d->lowpassed[i] = 0;
} else { }
d->squelch_hits = 0;} } else {
d->squelch_hits = 0;}
}
} }
if (printLevels) { if (printLevels) {
if (!sr) if (!sr)
sr = rms(d->lowpassed, d->lp_len, 1); sr = rms(d->lowpassed, d->lp_len, 1, d->dc_block_raw);
--printLevelNo; --printLevelNo;
if (printLevels) { if (printLevels && sr >= 0) {
levelSum += sr; levelSum += sr;
if (levelMax < sr) levelMax = sr; if (levelMax < sr) levelMax = sr;
if (levelMaxMax < sr) levelMaxMax = sr; if (levelMaxMax < sr) levelMaxMax = sr;
if (!printLevelNo) { if (!printLevelNo) {
printLevelNo = printLevels; 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; levelMax = 0;
levelSum = 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 */ d->mode_demod(d); /* lowpassed -> result */
if (d->mode_demod == &raw_demod) { if (d->mode_demod == &raw_demod) {
return; return;
@ -868,18 +1213,23 @@ void full_demod(struct demod_state *d)
static void rtlsdr_callback(unsigned char *buf, uint32_t len, void *ctx) static void rtlsdr_callback(unsigned char *buf, uint32_t len, void *ctx)
{ {
int i;
struct dongle_state *s = ctx; struct dongle_state *s = ctx;
struct demod_state *d = s->demod_target; struct demod_state *d = s->demod_target;
struct cmd_state *c = d->cmd;
int i, muteLen = s->mute;
if (do_exit) { if (do_exit) {
return;} return;}
if (!ctx) { if (!ctx) {
return;} return;}
if (s->mute) { if (s->mute) {
for (i=0; i<s->mute; i++) { if(muteLen > (int)len)
buf[i] = 127;} muteLen = len;
s->mute = 0; s->mute -= muteLen; /* we may need to mute multiple blocks */
if(!c->filename) {
for (i=0; i<muteLen; i++)
buf[i] = 127;
}
} }
/* 1st: convert to 16 bit - to allow easier calculation of DC */ /* 1st: convert to 16 bit - to allow easier calculation of DC */
for (i=0; i<(int)len; i++) { for (i=0; i<(int)len; i++) {
@ -889,10 +1239,11 @@ static void rtlsdr_callback(unsigned char *buf, uint32_t len, void *ctx)
if (d->dc_block_raw) { if (d->dc_block_raw) {
dc_block_raw_filter(d, s->buf16, (int)len); 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) { if (!s->offset_tuning) {
rotate16_90(s->buf16, (int)len); rotate16_neg90(s->buf16, (int)len);
/* rotate_90(buf, len); */
} }
pthread_rwlock_wrlock(&d->rw); pthread_rwlock_wrlock(&d->rw);
memcpy(d->lowpassed, s->buf16, 2*len); memcpy(d->lowpassed, s->buf16, 2*len);
@ -912,6 +1263,7 @@ static void *demod_thread_fn(void *arg)
{ {
struct demod_state *d = arg; struct demod_state *d = arg;
struct output_state *o = d->output_target; struct output_state *o = d->output_target;
struct cmd_state *c = d->cmd;
while (!do_exit) { while (!do_exit) {
safe_cond_wait(&d->ready, &d->ready_m); safe_cond_wait(&d->ready, &d->ready_m);
pthread_rwlock_wrlock(&d->rw); pthread_rwlock_wrlock(&d->rw);
@ -925,11 +1277,24 @@ static void *demod_thread_fn(void *arg)
safe_cond_signal(&controller.hop, &controller.hop_m); safe_cond_signal(&controller.hop, &controller.hop_m);
continue; continue;
} }
pthread_rwlock_wrlock(&o->rw);
memcpy(o->result, d->result, 2*d->result_len); if (do_exit)
o->result_len = d->result_len; break;
pthread_rwlock_unlock(&o->rw);
safe_cond_signal(&o->ready, &o->ready_m); 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; return 0;
} }
@ -947,51 +1312,53 @@ static void *output_thread_fn(void *arg)
return 0; return 0;
} }
static void optimal_settings(int freq, int rate) static void optimal_settings(uint32_t freq, uint32_t rate)
{ {
// giant ball of hacks // giant ball of hacks
// seems unable to do a single pass, 2:1 // 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 dongle_state *d = &dongle;
struct demod_state *dm = &demod; struct demod_state *dm = &demod;
struct controller_state *cs = &controller; struct controller_state *cs = &controller;
dm->downsample = (1000000 / dm->rate_in) + 1; dm->downsample = (MinCaptureRate / dm->rate_in) + 1;
if (dm->downsample_passes) { if (dm->downsample_passes) {
dm->downsample_passes = (int)log2(dm->downsample) + 1; dm->downsample_passes = (int)log2(dm->downsample) + 1;
dm->downsample = 1 << dm->downsample_passes; 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 ); fprintf(stderr, "downsample_passes = %d (= # of fifth_order() iterations), downsample = %d\n", dm->downsample_passes, dm->downsample );
} }
capture_freq = freq; capture_freq = freq;
capture_rate = dm->downsample * dm->rate_in; 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 ); fprintf(stderr, "capture_rate = dm->downsample * dm->rate_in = %d * %d = %d\n", dm->downsample, dm->rate_in, capture_rate );
if (!d->offset_tuning) { if (!d->offset_tuning) {
capture_freq = freq + capture_rate/4; capture_freq = freq - capture_rate/4;
if (verbosity) if (verbosity >= 2)
fprintf(stderr, "optimal_settings(freq = %d): capture_freq = freq + capture_rate/4 = %d\n", freq, capture_freq ); 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; capture_freq += cs->edge * dm->rate_in / 2;
if (verbosity) if (verbosity >= 2)
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 ); 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); dm->output_scale = (1<<15) / (128 * dm->downsample);
if (dm->output_scale < 1) { if (dm->output_scale < 1) {
dm->output_scale = 1;} dm->output_scale = 1;}
if (dm->mode_demod == &fm_demod) { if (dm->mode_demod == &fm_demod) {
dm->output_scale = 1;} dm->output_scale = 1;}
d->freq = (uint32_t)capture_freq; d->userFreq = freq;
d->rate = (uint32_t)capture_rate; d->freq = capture_freq;
if (verbosity) d->rate = capture_rate;
fprintf(stderr, "optimal_settings(freq = %d) delivers freq %.0f, rate %.0f\n", freq, (double)d->freq, (double)d->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) static void *controller_thread_fn(void *arg)
{ {
// thoughts for multiple dongles // thoughts for multiple dongles
// might be no good using a controller thread if retune/rate blocks // 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 controller_state *s = arg;
struct cmd_state *c = s->cmd;
if (s->wb_mode) { if (s->wb_mode) {
if (verbosity) if (verbosity)
@ -1001,6 +1368,14 @@ static void *controller_thread_fn(void *arg)
} }
/* set up primary channel */ /* 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); optimal_settings(s->freqs[0], demod.rate_in);
if (dongle.direct_sampling) { if (dongle.direct_sampling) {
verbose_direct_sampling(dongle.dev, 1);} verbose_direct_sampling(dongle.dev, 1);}
@ -1009,7 +1384,7 @@ static void *controller_thread_fn(void *arg)
/* Set the frequency */ /* Set the frequency */
if (verbosity) { 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) if (!dongle.offset_tuning)
fprintf(stderr, " frequency is away from parametrized one, to avoid negative impact from dc\n"); 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); fprintf(stderr, "Output at %u Hz.\n", demod.rate_in/demod.post_downsample);
while (!do_exit) { while (!do_exit) {
safe_cond_wait(&s->hop, &s->hop_m); if (execWaitHop)
if (s->freq_len <= 1) { 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;} continue;}
/* hacky hopping */ if (!c->filename) {
s->freq_now = (s->freq_now + 1) % s->freq_len; /* hacky hopping */
optimal_settings(s->freqs[s->freq_now], demod.rate_in); s->freq_now = (s->freq_now + 1) % s->freq_len;
rtlsdr_set_center_freq(dongle.dev, dongle.freq); optimal_settings(s->freqs[s->freq_now], demod.rate_in);
dongle.mute = BUFFER_DUMP; 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; return 0;
} }
@ -1061,12 +1482,13 @@ void frequency_range(struct controller_state *s, char *arg)
void dongle_init(struct dongle_state *s) void dongle_init(struct dongle_state *s)
{ {
s->rate = DEFAULT_SAMPLE_RATE; 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->mute = 0;
s->direct_sampling = 0; s->direct_sampling = 0;
s->offset_tuning = 0; s->offset_tuning = 0;
s->demod_target = &demod; s->demod_target = &demod;
s->bandwidth = 0; s->bandwidth = 0;
s->buf_len = 32 * 512; /* see rtl_tcp */
} }
void demod_init(struct demod_state *s) 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_cond_init(&s->ready, NULL);
pthread_mutex_init(&s->ready_m, NULL); pthread_mutex_init(&s->ready_m, NULL);
s->output_target = &output; s->output_target = &output;
s->cmd = &cmd;
} }
void demod_cleanup(struct demod_state *s) void demod_cleanup(struct demod_state *s)
@ -1132,6 +1555,7 @@ void controller_init(struct controller_state *s)
s->wb_mode = 0; s->wb_mode = 0;
pthread_cond_init(&s->hop, NULL); pthread_cond_init(&s->hop, NULL);
pthread_mutex_init(&s->hop_m, NULL); pthread_mutex_init(&s->hop_m, NULL);
s->cmd = &cmd;
} }
void controller_cleanup(struct controller_state *s) void controller_cleanup(struct controller_state *s)
@ -1176,8 +1600,9 @@ int main(int argc, char **argv)
demod_init(&demod); demod_init(&demod);
output_init(&output); output_init(&output);
controller_init(&controller); 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) { switch (opt) {
case 'd': case 'd':
dongle.dev_index = verbose_device_search(optarg); dongle.dev_index = verbose_device_search(optarg);
@ -1194,6 +1619,19 @@ int main(int argc, char **argv)
controller.freq_len++; controller.freq_len++;
} }
break; 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': case 'g':
dongle.gain = (int)(atof(optarg) * 10); dongle.gain = (int)(atof(optarg) * 10);
break; break;
@ -1305,8 +1743,6 @@ int main(int argc, char **argv)
break; break;
case 'w': case 'w':
dongle.bandwidth = (uint32_t)atofs(optarg); dongle.bandwidth = (uint32_t)atofs(optarg);
if (dongle.bandwidth)
dongle.offset_tuning = 1; /* automatically switch offset tuning, when using bandwidth filter */
break; break;
case 'h': case 'h':
case '?': case '?':
@ -1379,7 +1815,7 @@ int main(int argc, char **argv)
} }
rtlsdr_set_agc_mode(dongle.dev, rtlagc); rtlsdr_set_agc_mode(dongle.dev, rtlagc);
rtlsdr_set_bias_tee(dongle.dev, enable_biastee); rtlsdr_set_bias_tee(dongle.dev, enable_biastee);
if (enable_biastee) if (enable_biastee)
fprintf(stderr, "activated bias-T on GPIO PIN 0\n"); 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 */ /* Set direct sampling with threshold */
rtlsdr_set_ds_mode(dongle.dev, ds_mode, ds_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) if (verbosity && dongle.bandwidth)
{ {
@ -1425,7 +1861,7 @@ int main(int argc, char **argv)
verbose_reset_buffer(dongle.dev); verbose_reset_buffer(dongle.dev);
pthread_create(&controller.thread, NULL, controller_thread_fn, (void *)(&controller)); 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(&output.thread, NULL, output_thread_fn, (void *)(&output));
pthread_create(&demod.thread, NULL, demod_thread_fn, (void *)(&demod)); pthread_create(&demod.thread, NULL, demod_thread_fn, (void *)(&demod));
pthread_create(&dongle.thread, NULL, dongle_thread_fn, (void *)(&dongle)); pthread_create(&dongle.thread, NULL, dongle_thread_fn, (void *)(&dongle));
@ -1453,6 +1889,15 @@ int main(int argc, char **argv)
output_cleanup(&output); output_cleanup(&output);
controller_cleanup(&controller); 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) { if (output.file != stdout) {
fclose(output.file);} fclose(output.file);}