Skip to content

Commit

Permalink
Add restartable SDR device (merbanan#2411)
Browse files Browse the repository at this point in the history
  • Loading branch information
zuckschwerdt authored Mar 7, 2023
1 parent 46de49f commit 6a50144
Show file tree
Hide file tree
Showing 3 changed files with 168 additions and 55 deletions.
5 changes: 5 additions & 0 deletions conf/rtl_433.example.conf
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,11 @@ ppm_error 0
# default is "250k", other valid settings are 1024k, 2048k, 3200k
sample_rate 250k

# as command line option:
# [-D restart | pause | quit | manual] Input device run mode options.
# default is "quit"
device_mode quit

## Demodulator options

# as command line option:
Expand Down
18 changes: 17 additions & 1 deletion include/rtl_433.h
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ struct mg_mgr;
typedef enum {
CONVERT_NATIVE,
CONVERT_SI,
CONVERT_CUSTOMARY
CONVERT_CUSTOMARY,
} conversion_mode_t;

typedef enum {
Expand All @@ -43,7 +43,23 @@ typedef enum {
REPORT_TIME_OFF,
} time_mode_t;

typedef enum {
DEVICE_MODE_QUIT,
DEVICE_MODE_RESTART,
DEVICE_MODE_PAUSE,
DEVICE_MODE_MANUAL,
} device_mode_t;

typedef enum {
DEVICE_STATE_STOPPED,
DEVICE_STATE_STARTING,
DEVICE_STATE_GRACE,
DEVICE_STATE_STARTED,
} device_state_t;

typedef struct r_cfg {
device_mode_t dev_mode; ///< Input device run mode
device_state_t dev_state; ///< Input device run state
char *dev_query;
char const *dev_info;
char *gain_str;
Expand Down
200 changes: 146 additions & 54 deletions src/rtl_433.c
Original file line number Diff line number Diff line change
Expand Up @@ -151,6 +151,7 @@ static void usage(int exit_code)
" [-H <seconds>] Hop interval for polling of multiple frequencies (default: %i seconds)\n"
" [-p <ppm_error>] Correct rtl-sdr tuner frequency offset error (default: 0)\n"
" [-s <sample rate>] Set sample rate (default: %i Hz)\n"
" [-D restart | pause | quit | manual] Input device run mode options.\n"
"\t\t= Demodulator options =\n"
" [-R <device> | help] Enable only the specified device decoding protocol (can be used multiple times)\n"
" Specify a negative number to disable a device decoding protocol (can be used multiple times)\n"
Expand Down Expand Up @@ -207,7 +208,7 @@ static void help_protocols(r_device *devices, unsigned num_devices, int exit_cod
}

_Noreturn
static void help_device(void)
static void help_device_selection(void)
{
term_help_printf(
"\t\t= Input device selection =\n"
Expand Down Expand Up @@ -244,6 +245,21 @@ static void help_gain(void)
exit(0);
}

_Noreturn
static void help_device_mode(void)
{
term_help_printf(
"\t\t= Input device run mode =\n"
" [-D restart | pause | quit | manual] Input device run mode options.\n"
"\tSupported input device run modes:\n"
"\t restart: Restart the input device on errors\n"
"\t pause: Pause the input device on errors, waits for e.g. HTTP-API control\n"
"\t quit: Quit on input device errors (default)\n"
"\t manual: Don't start an input device, waits for e.g. HTTP-API control\n"
"\tWithout this option the default is to start the SDR and quit on errors.\n");
exit(0);
}

_Noreturn
static void help_output(void)
{
Expand Down Expand Up @@ -743,7 +759,7 @@ static int hasopt(int test, int argc, char *argv[], char const *optstring)

static void parse_conf_option(r_cfg_t *cfg, int opt, char *arg);

#define OPTSTRING "hVvqDc:x:z:p:a:AI:S:m:M:r:w:W:l:d:t:f:H:g:s:b:n:R:X:F:K:C:T:UGy:E:Y:"
#define OPTSTRING "hVvqD:c:x:z:p:a:AI:S:m:M:r:w:W:l:d:t:f:H:g:s:b:n:R:X:F:K:C:T:UGy:E:Y:"

// these should match the short options exactly
static struct conf_keywords const conf_keywords[] = {
Expand All @@ -753,6 +769,7 @@ static struct conf_keywords const conf_keywords[] = {
{"config_file", 'c'},
{"report_meta", 'M'},
{"device", 'd'},
{"device_mode", 'D'},
{"settings", 't'},
{"gain", 'g'},
{"frequency", 'f'},
Expand Down Expand Up @@ -859,10 +876,31 @@ static void parse_conf_option(r_cfg_t *cfg, int opt, char *arg)
break;
case 'd':
if (!arg)
help_device();
help_device_selection();

cfg->dev_query = arg;
break;
case 'D':
if (!arg)
help_device_mode();

if (strcmp(arg, "quit") == 0) {
cfg->dev_mode = DEVICE_MODE_RESTART;
}
else if (strcmp(arg, "restart") == 0) {
cfg->dev_mode = DEVICE_MODE_RESTART;
}
else if (strcmp(arg, "pause") == 0) {
cfg->dev_mode = DEVICE_MODE_PAUSE;
}
else if (strcmp(arg, "manual") == 0) {
cfg->dev_mode = DEVICE_MODE_MANUAL;
}
else {
fprintf(stderr, "Invalid input device run mode: %s\n", arg);
help_device_mode();
}
break;
case 't':
// this option changed, check and warn if old meaning is used
if (!arg || *arg == '-') {
Expand Down Expand Up @@ -1062,9 +1100,6 @@ static void parse_conf_option(r_cfg_t *cfg, int opt, char *arg)
else
cfg->report_meta = atobv(arg, 1);
break;
case 'D':
fprintf(stderr, "debug option (-D) is deprecated. See -v to increase verbosity\n");
break;
case 'z':
fprintf(stderr, "override_short (-z) is deprecated.\n");
break;
Expand Down Expand Up @@ -1161,7 +1196,7 @@ static void parse_conf_option(r_cfg_t *cfg, int opt, char *arg)
add_rtltcp_output(cfg, arg_param(arg));
}
else {
fprintf(stderr, "Invalid output format %s\n", arg);
fprintf(stderr, "Invalid output format: %s\n", arg);
usage(1);
}
break;
Expand All @@ -1183,7 +1218,7 @@ static void parse_conf_option(r_cfg_t *cfg, int opt, char *arg)
cfg->conversion_mode = CONVERT_CUSTOMARY;
}
else {
fprintf(stderr, "Invalid conversion mode %s\n", arg);
fprintf(stderr, "Invalid conversion mode: %s\n", arg);
usage(1);
}
break;
Expand Down Expand Up @@ -1384,6 +1419,55 @@ static void acquire_callback(sdr_event_t *ev, void *ctx)
//fprintf(stderr, "acquire_callback bc done...\n");
}

static int start_sdr(r_cfg_t *cfg)
{
int r;
r = sdr_open(&cfg->dev, cfg->dev_query, cfg->verbosity);
if (r < 0) {
return -1; // exit(2);
}
cfg->dev_info = sdr_get_dev_info(cfg->dev);
cfg->demod->sample_size = sdr_get_sample_size(cfg->dev);
// cfg->demod->sample_signed = sdr_get_sample_signed(cfg->dev);

/* Set the sample rate */
r = sdr_set_sample_rate(cfg->dev, cfg->samp_rate, 1); // always verbose

if (cfg->verbosity || cfg->demod->level_limit < 0.0)
print_logf(LOG_NOTICE, "Input", "Bit detection level set to %.1f%s.", cfg->demod->level_limit, (cfg->demod->level_limit < 0.0 ? "" : " (Auto)"));

r = sdr_apply_settings(cfg->dev, cfg->settings_str, 1); // always verbose for soapy

/* Enable automatic gain if gain_str empty (or 0 for RTL-SDR), set manual gain otherwise */
r = sdr_set_tuner_gain(cfg->dev, cfg->gain_str, 1); // always verbose

if (cfg->ppm_error) {
r = sdr_set_freq_correction(cfg->dev, cfg->ppm_error, 1); // always verbose
}

/* Reset endpoint before we start reading from it (mandatory) */
r = sdr_reset(cfg->dev, cfg->verbosity);
if (r < 0) {
print_log(LOG_ERROR, "Input", "Failed to reset buffers.");
}
r = sdr_activate(cfg->dev);

if (cfg->verbosity) {
print_log(LOG_NOTICE, "Input", "Reading samples in async mode...");
}

r = sdr_set_center_freq(cfg->dev, cfg->center_frequency, 1); // always verbose

r = sdr_start(cfg->dev, acquire_callback, (void *)get_mgr(cfg),
DEFAULT_ASYNC_BUF_NUMBER, cfg->out_block_size);
if (r < 0) {
print_logf(LOG_ERROR, "Input", "async start failed (%i).", r);
}

cfg->dev_state = DEVICE_STATE_STARTING;
return r;
}

static void timer_handler(struct mg_connection *nc, int ev, void *ev_data)
{
//fprintf(stderr, "%s: %d, %d, %p, %p\n", __func__, nc->sock, ev, nc->user_data, ev_data);
Expand All @@ -1396,14 +1480,54 @@ static void timer_handler(struct mg_connection *nc, int ev, void *ev_data)
//fprintf(stderr, "timer event, current time: %.2lf, next timer: %.2lf\n", now, next);
mg_set_timer(nc, next); // Send us timer event again after 1.5 seconds

if (cfg->watchdog == 0) {
// We expect a frame at least every 250 ms
write_err("Async read stalled, exiting!\n");
cfg->exit_code = 3;
// Did we acquire data frames in the last interval?
if (cfg->watchdog != 0) {
if (cfg->dev_state == DEVICE_STATE_STARTING
|| cfg->dev_state == DEVICE_STATE_GRACE) {
cfg->dev_state = DEVICE_STATE_STARTED;
}
cfg->watchdog = 0;
break;
}

// Upon starting allow more time until the first frame
if (cfg->dev_state == DEVICE_STATE_STARTING) {
cfg->dev_state = DEVICE_STATE_GRACE;
break;
}
// We expect a frame at least every 250 ms but didn't get one
if (cfg->dev_state == DEVICE_STATE_GRACE) {
if (cfg->dev_mode == DEVICE_MODE_QUIT) {
print_log(LOG_ERROR, "Input", "Input device start failed, exiting!");
}
else if (cfg->dev_mode == DEVICE_MODE_RESTART) {
print_log(LOG_WARNING, "Input", "Input device start failed, restarting!");
}
else { // DEVICE_MODE_PAUSE or DEVICE_MODE_MANUAL
print_log(LOG_WARNING, "Input", "Input device start failed, pausing!");
}
}
else if (cfg->dev_state == DEVICE_STATE_STARTED) {
if (cfg->dev_mode == DEVICE_MODE_QUIT) {
print_log(LOG_ERROR, "Input", "Async read stalled, exiting!");
}
else if (cfg->dev_mode == DEVICE_MODE_RESTART) {
print_log(LOG_WARNING, "Input", "Async read stalled, restarting!");
}
else { // DEVICE_MODE_PAUSE or DEVICE_MODE_MANUAL
print_log(LOG_WARNING, "Input", "Async read stalled, pausing!");
}
}
cfg->exit_code = 3;
sdr_stop(cfg->dev);
cfg->dev_state = DEVICE_STATE_STOPPED;
if (cfg->dev_mode == DEVICE_MODE_QUIT) {
cfg->exit_async = 1;
sdr_stop(cfg->dev);
}
cfg->watchdog = 0;
if (cfg->dev_mode == DEVICE_MODE_RESTART) {
start_sdr(cfg);
}
// do nothing for DEVICE_MODE_PAUSE or DEVICE_MODE_MANUAL

break;
}
Expand Down Expand Up @@ -1815,20 +1939,12 @@ int main(int argc, char **argv) {
exit(0);
}

// Normal case, no test data, no in files
if (cfg->sr_filename) {
print_logf(LOG_ERROR, "Input", "SR writing not recommended for live input");
exit(1);
}

// Normal case, no test data, no in files
r = sdr_open(&cfg->dev, cfg->dev_query, cfg->verbosity);
if (r < 0) {
exit(2);
}
cfg->dev_info = sdr_get_dev_info(cfg->dev);
demod->sample_size = sdr_get_sample_size(cfg->dev);
//demod->sample_signed = sdr_get_sample_signed(cfg->dev);

#ifndef _WIN32
struct sigaction sigact;
sigact.sa_handler = sighandler;
Expand All @@ -1843,40 +1959,22 @@ int main(int argc, char **argv) {
#else
SetConsoleCtrlHandler((PHANDLER_ROUTINE)console_handler, TRUE);
#endif
/* Set the sample rate */
r = sdr_set_sample_rate(cfg->dev, cfg->samp_rate, 1); // always verbose

if (cfg->verbosity >= LOG_NOTICE || demod->level_limit < 0.0)
print_logf(LOG_NOTICE, "Input", "Bit detection level set to %.1f%s.", demod->level_limit, (demod->level_limit < 0.0 ? "" : " (Auto)"));

r = sdr_apply_settings(cfg->dev, cfg->settings_str, 1); // always verbose for soapy

/* Enable automatic gain if gain_str empty (or 0 for RTL-SDR), set manual gain otherwise */
r = sdr_set_tuner_gain(cfg->dev, cfg->gain_str, 1); // always verbose

if (cfg->ppm_error)
r = sdr_set_freq_correction(cfg->dev, cfg->ppm_error, 1); // always verbose

/* Reset endpoint before we start reading from it (mandatory) */
r = sdr_reset(cfg->dev, cfg->verbosity);
if (r < 0)
print_log(LOG_ERROR, "Input", "Failed to reset buffers.");
r = sdr_activate(cfg->dev);

if (cfg->verbosity >= LOG_NOTICE) {
print_log(LOG_NOTICE, "Input", "Reading samples in async mode...");
}

// TODO: remove this before next release
print_log(LOG_NOTICE, "Input", "The internals of input handling changed, read about and report problems on PR #1978");

if (cfg->dev_mode != DEVICE_MODE_MANUAL) {
r = start_sdr(cfg);
if (r < 0) {
exit(2);
}
}

if (cfg->duration > 0) {
time(&cfg->stop_time);
cfg->stop_time += cfg->duration;
}

r = sdr_set_center_freq(cfg->dev, cfg->center_frequency, 1); // always verbose

time(&cfg->hop_start_time);

// add dummy socket to receive broadcasts
Expand All @@ -1885,12 +1983,6 @@ int main(int argc, char **argv) {
// Send us MG_EV_TIMER event after 2.5 seconds
mg_set_timer(nc, mg_time() + 2.5);

r = sdr_start(cfg->dev, acquire_callback, (void *)get_mgr(cfg),
DEFAULT_ASYNC_BUF_NUMBER, cfg->out_block_size);
if (r < 0) {
print_logf(LOG_ERROR, "Input", "async start failed (%i).", r);
}

while (!cfg->exit_async) {
mg_mgr_poll(cfg->mgr, 500);
}
Expand Down

0 comments on commit 6a50144

Please sign in to comment.